Home | History | Annotate | Download | only in appwidget
      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      */
     98     public AppWidgetHostView(Context context) {
     99         this(context, android.R.anim.fade_in, android.R.anim.fade_out);
    100     }
    101 
    102     /**
    103      * @hide
    104      */
    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"})
    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      */
    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      */
    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      */
    178     public static Rect getDefaultPaddingForWidget(Context context, ComponentName component,
    179             Rect padding) {
    180         return getDefaultPaddingForWidget(context, padding);
    181     }
    182 
    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 
    201     private Rect getDefaultPadding() {
    202         return getDefaultPaddingForWidget(mContext, null);
    203     }
    204 
    205     public int getAppWidgetId() {
    206         return mAppWidgetId;
    207     }
    208 
    209     public AppWidgetProviderInfo getAppWidgetInfo() {
    210         return mInfo;
    211     }
    212 
    213     @Override
    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 
    223     private int generateId() {
    224         final int id = getId();
    225         return id == View.NO_ID ? mAppWidgetId : id;
    226     }
    227 
    228     @Override
    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
    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      */
    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
    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      */
    335     public void updateAppWidgetOptions(Bundle options) {
    336         AppWidgetManager.getInstance(mContext).updateAppWidgetOptions(mAppWidgetId, options);
    337     }
    338 
    339     /** {@inheritDoc} */
    340     @Override
    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      */
    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      */
    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      */
    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      */
    388     public void updateAppWidget(RemoteViews remoteViews) {
    389         applyRemoteViews(remoteViews, true);
    390     }
    391 
    392     /**
    393      * @hide
    394      */
    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 
    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 
    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 
    514         public ViewApplyListener(RemoteViews views, int layoutId, boolean isReapply) {
    515             mViews = views;
    516             mLayoutId = layoutId;
    517             mIsReapply = isReapply;
    518         }
    519 
    520         @Override
    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
    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      */
    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      */
    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      */
    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      */
    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 
    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      */
    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
    675     public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
    676         super.onInitializeAccessibilityNodeInfoInternal(info);
    677         info.setClassName(AppWidgetHostView.class.getName());
    678     }
    679 
    680     /** @hide */
    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