• 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.UnsupportedAppUsage;
20 import android.app.Activity;
21 import android.app.ActivityOptions;
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 = 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 = 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 
427             // If our stale view has been prepared to match active, and the new
428             // layout matches, try recycling it
429             if (content == null && layoutId == mLayoutId) {
430                 try {
431                     remoteViews.reapply(mContext, mView, mOnClickHandler);
432                     content = mView;
433                     recycled = true;
434                     if (LOGD) Log.d(TAG, "was able to recycle existing layout");
435                 } catch (RuntimeException e) {
436                     exception = e;
437                 }
438             }
439 
440             // Try normal RemoteView inflation
441             if (content == null) {
442                 try {
443                     content = remoteViews.apply(mContext, this, mOnClickHandler);
444                     if (LOGD) Log.d(TAG, "had to inflate new layout");
445                 } catch (RuntimeException e) {
446                     exception = e;
447                 }
448             }
449 
450             mLayoutId = layoutId;
451             mViewMode = VIEW_MODE_CONTENT;
452         }
453 
454         applyContent(content, recycled, exception);
455     }
456 
applyContent(View content, boolean recycled, Exception exception)457     private void applyContent(View content, boolean recycled, Exception exception) {
458         if (content == null) {
459             if (mViewMode == VIEW_MODE_ERROR) {
460                 // We've already done this -- nothing to do.
461                 return ;
462             }
463             if (exception != null) {
464                 Log.w(TAG, "Error inflating RemoteViews : " + exception.toString());
465             }
466             content = getErrorView();
467             mViewMode = VIEW_MODE_ERROR;
468         }
469 
470         if (!recycled) {
471             prepareView(content);
472             addView(content);
473         }
474 
475         if (mView != content) {
476             removeView(mView);
477             mView = content;
478         }
479     }
480 
inflateAsync(RemoteViews remoteViews)481     private void inflateAsync(RemoteViews remoteViews) {
482         // Prepare a local reference to the remote Context so we're ready to
483         // inflate any requested LayoutParams.
484         mRemoteContext = getRemoteContext();
485         int layoutId = remoteViews.getLayoutId();
486 
487         // If our stale view has been prepared to match active, and the new
488         // layout matches, try recycling it
489         if (layoutId == mLayoutId && mView != null) {
490             try {
491                 mLastExecutionSignal = remoteViews.reapplyAsync(mContext,
492                         mView,
493                         mAsyncExecutor,
494                         new ViewApplyListener(remoteViews, layoutId, true),
495                         mOnClickHandler);
496             } catch (Exception e) {
497                 // Reapply failed. Try apply
498             }
499         }
500         if (mLastExecutionSignal == null) {
501             mLastExecutionSignal = remoteViews.applyAsync(mContext,
502                     this,
503                     mAsyncExecutor,
504                     new ViewApplyListener(remoteViews, layoutId, false),
505                     mOnClickHandler);
506         }
507     }
508 
509     private class ViewApplyListener implements RemoteViews.OnViewAppliedListener {
510         private final RemoteViews mViews;
511         private final boolean mIsReapply;
512         private final int mLayoutId;
513 
ViewApplyListener(RemoteViews views, int layoutId, boolean isReapply)514         public ViewApplyListener(RemoteViews views, int layoutId, boolean isReapply) {
515             mViews = views;
516             mLayoutId = layoutId;
517             mIsReapply = isReapply;
518         }
519 
520         @Override
onViewApplied(View v)521         public void onViewApplied(View v) {
522             AppWidgetHostView.this.mLayoutId = mLayoutId;
523             mViewMode = VIEW_MODE_CONTENT;
524 
525             applyContent(v, mIsReapply, null);
526         }
527 
528         @Override
onError(Exception e)529         public void onError(Exception e) {
530             if (mIsReapply) {
531                 // Try a fresh replay
532                 mLastExecutionSignal = mViews.applyAsync(mContext,
533                         AppWidgetHostView.this,
534                         mAsyncExecutor,
535                         new ViewApplyListener(mViews, mLayoutId, false),
536                         mOnClickHandler);
537             } else {
538                 applyContent(null, false, e);
539             }
540         }
541     }
542 
543     /**
544      * Process data-changed notifications for the specified view in the specified
545      * set of {@link RemoteViews} views.
546      */
viewDataChanged(int viewId)547     void viewDataChanged(int viewId) {
548         View v = findViewById(viewId);
549         if ((v != null) && (v instanceof AdapterView<?>)) {
550             AdapterView<?> adapterView = (AdapterView<?>) v;
551             Adapter adapter = adapterView.getAdapter();
552             if (adapter instanceof BaseAdapter) {
553                 BaseAdapter baseAdapter = (BaseAdapter) adapter;
554                 baseAdapter.notifyDataSetChanged();
555             }  else if (adapter == null && adapterView instanceof RemoteAdapterConnectionCallback) {
556                 // If the adapter is null, it may mean that the RemoteViewsAapter has not yet
557                 // connected to its associated service, and hence the adapter hasn't been set.
558                 // In this case, we need to defer the notify call until it has been set.
559                 ((RemoteAdapterConnectionCallback) adapterView).deferNotifyDataSetChanged();
560             }
561         }
562     }
563 
564     /**
565      * Build a {@link Context} cloned into another package name, usually for the
566      * purposes of reading remote resources.
567      * @hide
568      */
getRemoteContext()569     protected Context getRemoteContext() {
570         try {
571             // Return if cloned successfully, otherwise default
572             return mContext.createApplicationContext(
573                     mInfo.providerInfo.applicationInfo,
574                     Context.CONTEXT_RESTRICTED);
575         } catch (NameNotFoundException e) {
576             Log.e(TAG, "Package name " +  mInfo.providerInfo.packageName + " not found");
577             return mContext;
578         }
579     }
580 
581     /**
582      * Prepare the given view to be shown. This might include adjusting
583      * {@link FrameLayout.LayoutParams} before inserting.
584      */
prepareView(View view)585     protected void prepareView(View view) {
586         // Take requested dimensions from child, but apply default gravity.
587         FrameLayout.LayoutParams requested = (FrameLayout.LayoutParams)view.getLayoutParams();
588         if (requested == null) {
589             requested = new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT,
590                     LayoutParams.MATCH_PARENT);
591         }
592 
593         requested.gravity = Gravity.CENTER;
594         view.setLayoutParams(requested);
595     }
596 
597     /**
598      * Inflate and return the default layout requested by AppWidget provider.
599      */
getDefaultView()600     protected View getDefaultView() {
601         if (LOGD) {
602             Log.d(TAG, "getDefaultView");
603         }
604         View defaultView = null;
605         Exception exception = null;
606 
607         try {
608             if (mInfo != null) {
609                 Context theirContext = getRemoteContext();
610                 mRemoteContext = theirContext;
611                 LayoutInflater inflater = (LayoutInflater)
612                         theirContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
613                 inflater = inflater.cloneInContext(theirContext);
614                 inflater.setFilter(INFLATER_FILTER);
615                 AppWidgetManager manager = AppWidgetManager.getInstance(mContext);
616                 Bundle options = manager.getAppWidgetOptions(mAppWidgetId);
617 
618                 int layoutId = mInfo.initialLayout;
619                 if (options.containsKey(AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY)) {
620                     int category = options.getInt(AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY);
621                     if (category == AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD) {
622                         int kgLayoutId = mInfo.initialKeyguardLayout;
623                         // If a default keyguard layout is not specified, use the standard
624                         // default layout.
625                         layoutId = kgLayoutId == 0 ? layoutId : kgLayoutId;
626                     }
627                 }
628                 defaultView = inflater.inflate(layoutId, this, false);
629                 defaultView.setOnClickListener(this::onDefaultViewClicked);
630             } else {
631                 Log.w(TAG, "can't inflate defaultView because mInfo is missing");
632             }
633         } catch (RuntimeException e) {
634             exception = e;
635         }
636 
637         if (exception != null) {
638             Log.w(TAG, "Error inflating AppWidget " + mInfo + ": " + exception.toString());
639         }
640 
641         if (defaultView == null) {
642             if (LOGD) Log.d(TAG, "getDefaultView couldn't find any view, so inflating error");
643             defaultView = getErrorView();
644         }
645 
646         return defaultView;
647     }
648 
onDefaultViewClicked(View view)649     private void onDefaultViewClicked(View view) {
650         if (mInfo != null) {
651             LauncherApps launcherApps = getContext().getSystemService(LauncherApps.class);
652             List<LauncherActivityInfo> activities = launcherApps.getActivityList(
653                     mInfo.provider.getPackageName(), mInfo.getProfile());
654             if (!activities.isEmpty()) {
655                 LauncherActivityInfo ai = activities.get(0);
656                 launcherApps.startMainActivity(ai.getComponentName(), ai.getUser(),
657                         RemoteViews.getSourceBounds(view), null);
658             }
659         }
660     }
661 
662     /**
663      * Inflate and return a view that represents an error state.
664      */
getErrorView()665     protected View getErrorView() {
666         TextView tv = new TextView(mContext);
667         tv.setText(com.android.internal.R.string.gadget_host_error_inflating);
668         // TODO: get this color from somewhere.
669         tv.setBackgroundColor(Color.argb(127, 0, 0, 0));
670         return tv;
671     }
672 
673     /** @hide */
674     @Override
onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info)675     public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
676         super.onInitializeAccessibilityNodeInfoInternal(info);
677         info.setClassName(AppWidgetHostView.class.getName());
678     }
679 
680     /** @hide */
createSharedElementActivityOptions( int[] sharedViewIds, String[] sharedViewNames, Intent fillInIntent)681     public ActivityOptions createSharedElementActivityOptions(
682             int[] sharedViewIds, String[] sharedViewNames, Intent fillInIntent) {
683         Context parentContext = getContext();
684         while ((parentContext instanceof ContextWrapper)
685                 && !(parentContext instanceof Activity)) {
686             parentContext = ((ContextWrapper) parentContext).getBaseContext();
687         }
688         if (!(parentContext instanceof Activity)) {
689             return null;
690         }
691 
692         List<Pair<View, String>> sharedElements = new ArrayList<>();
693         Bundle extras = new Bundle();
694 
695         for (int i = 0; i < sharedViewIds.length; i++) {
696             View view = findViewById(sharedViewIds[i]);
697             if (view != null) {
698                 sharedElements.add(Pair.create(view, sharedViewNames[i]));
699 
700                 extras.putParcelable(sharedViewNames[i], RemoteViews.getSourceBounds(view));
701             }
702         }
703 
704         if (!sharedElements.isEmpty()) {
705             fillInIntent.putExtra(RemoteViews.EXTRA_SHARED_ELEMENT_BOUNDS, extras);
706             final ActivityOptions opts = ActivityOptions.makeSceneTransitionAnimation(
707                     (Activity) parentContext,
708                     sharedElements.toArray(new Pair[sharedElements.size()]));
709             opts.setPendingIntentLaunchFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
710             return opts;
711         }
712         return null;
713     }
714 }
715