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.content.ComponentName;
     20 import android.content.Context;
     21 import android.content.pm.ApplicationInfo;
     22 import android.content.pm.LauncherApps;
     23 import android.content.pm.PackageManager;
     24 import android.content.pm.PackageManager.NameNotFoundException;
     25 import android.content.res.Resources;
     26 import android.graphics.Bitmap;
     27 import android.graphics.Canvas;
     28 import android.graphics.Color;
     29 import android.graphics.Paint;
     30 import android.graphics.Rect;
     31 import android.os.Build;
     32 import android.os.Bundle;
     33 import android.os.CancellationSignal;
     34 import android.os.Parcel;
     35 import android.os.Parcelable;
     36 import android.os.SystemClock;
     37 import android.util.AttributeSet;
     38 import android.util.Log;
     39 import android.util.SparseArray;
     40 import android.view.Gravity;
     41 import android.view.LayoutInflater;
     42 import android.view.View;
     43 import android.view.ViewGroup;
     44 import android.view.accessibility.AccessibilityNodeInfo;
     45 import android.widget.Adapter;
     46 import android.widget.AdapterView;
     47 import android.widget.BaseAdapter;
     48 import android.widget.FrameLayout;
     49 import android.widget.RemoteViews;
     50 import android.widget.RemoteViews.OnClickHandler;
     51 import android.widget.RemoteViewsAdapter.RemoteAdapterConnectionCallback;
     52 import android.widget.TextView;
     53 
     54 import java.util.concurrent.Executor;
     55 
     56 /**
     57  * Provides the glue to show AppWidget views. This class offers automatic animation
     58  * between updates, and will try recycling old views for each incoming
     59  * {@link RemoteViews}.
     60  */
     61 public class AppWidgetHostView extends FrameLayout {
     62     static final String TAG = "AppWidgetHostView";
     63     static final boolean LOGD = false;
     64     static final boolean CROSSFADE = false;
     65 
     66     static final int VIEW_MODE_NOINIT = 0;
     67     static final int VIEW_MODE_CONTENT = 1;
     68     static final int VIEW_MODE_ERROR = 2;
     69     static final int VIEW_MODE_DEFAULT = 3;
     70 
     71     static final int FADE_DURATION = 1000;
     72 
     73     // When we're inflating the initialLayout for a AppWidget, we only allow
     74     // views that are allowed in RemoteViews.
     75     static final LayoutInflater.Filter sInflaterFilter = new LayoutInflater.Filter() {
     76         public boolean onLoadClass(Class clazz) {
     77             return clazz.isAnnotationPresent(RemoteViews.RemoteView.class);
     78         }
     79     };
     80 
     81     Context mContext;
     82     Context mRemoteContext;
     83 
     84     int mAppWidgetId;
     85     AppWidgetProviderInfo mInfo;
     86     View mView;
     87     int mViewMode = VIEW_MODE_NOINIT;
     88     int mLayoutId = -1;
     89     long mFadeStartTime = -1;
     90     Bitmap mOld;
     91     Paint mOldPaint = new Paint();
     92     private OnClickHandler mOnClickHandler;
     93 
     94     private Executor mAsyncExecutor;
     95     private CancellationSignal mLastExecutionSignal;
     96 
     97     /**
     98      * Create a host view.  Uses default fade animations.
     99      */
    100     public AppWidgetHostView(Context context) {
    101         this(context, android.R.anim.fade_in, android.R.anim.fade_out);
    102     }
    103 
    104     /**
    105      * @hide
    106      */
    107     public AppWidgetHostView(Context context, OnClickHandler handler) {
    108         this(context, android.R.anim.fade_in, android.R.anim.fade_out);
    109         mOnClickHandler = handler;
    110     }
    111 
    112     /**
    113      * Create a host view. Uses specified animations when pushing
    114      * {@link #updateAppWidget(RemoteViews)}.
    115      *
    116      * @param animationIn Resource ID of in animation to use
    117      * @param animationOut Resource ID of out animation to use
    118      */
    119     @SuppressWarnings({"UnusedDeclaration"})
    120     public AppWidgetHostView(Context context, int animationIn, int animationOut) {
    121         super(context);
    122         mContext = context;
    123         // We want to segregate the view ids within AppWidgets to prevent
    124         // problems when those ids collide with view ids in the AppWidgetHost.
    125         setIsRootNamespace(true);
    126     }
    127 
    128     /**
    129      * Pass the given handler to RemoteViews when updating this widget. Unless this
    130      * is done immediatly after construction, a call to {@link #updateAppWidget(RemoteViews)}
    131      * should be made.
    132      * @param handler
    133      * @hide
    134      */
    135     public void setOnClickHandler(OnClickHandler handler) {
    136         mOnClickHandler = handler;
    137     }
    138 
    139     /**
    140      * Set the AppWidget that will be displayed by this view. This method also adds default padding
    141      * to widgets, as described in {@link #getDefaultPaddingForWidget(Context, ComponentName, Rect)}
    142      * and can be overridden in order to add custom padding.
    143      */
    144     public void setAppWidget(int appWidgetId, AppWidgetProviderInfo info) {
    145         mAppWidgetId = appWidgetId;
    146         mInfo = info;
    147 
    148         // Sometimes the AppWidgetManager returns a null AppWidgetProviderInfo object for
    149         // a widget, eg. for some widgets in safe mode.
    150         if (info != null) {
    151             // We add padding to the AppWidgetHostView if necessary
    152             Rect padding = getDefaultPaddingForWidget(mContext, info.provider, null);
    153             setPadding(padding.left, padding.top, padding.right, padding.bottom);
    154             updateContentDescription(info);
    155         }
    156     }
    157 
    158     /**
    159      * As of ICE_CREAM_SANDWICH we are automatically adding padding to widgets targeting
    160      * ICE_CREAM_SANDWICH and higher. The new widget design guidelines strongly recommend
    161      * that widget developers do not add extra padding to their widgets. This will help
    162      * achieve consistency among widgets.
    163      *
    164      * Note: this method is only needed by developers of AppWidgetHosts. The method is provided in
    165      * order for the AppWidgetHost to account for the automatic padding when computing the number
    166      * of cells to allocate to a particular widget.
    167      *
    168      * @param context the current context
    169      * @param component the component name of the widget
    170      * @param padding Rect in which to place the output, if null, a new Rect will be allocated and
    171      *                returned
    172      * @return default padding for this widget, in pixels
    173      */
    174     public static Rect getDefaultPaddingForWidget(Context context, ComponentName component,
    175             Rect padding) {
    176         PackageManager packageManager = context.getPackageManager();
    177         ApplicationInfo appInfo;
    178 
    179         if (padding == null) {
    180             padding = new Rect(0, 0, 0, 0);
    181         } else {
    182             padding.set(0, 0, 0, 0);
    183         }
    184 
    185         try {
    186             appInfo = packageManager.getApplicationInfo(component.getPackageName(), 0);
    187         } catch (NameNotFoundException e) {
    188             // if we can't find the package, return 0 padding
    189             return padding;
    190         }
    191 
    192         if (appInfo.targetSdkVersion >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
    193             Resources r = context.getResources();
    194             padding.left = r.getDimensionPixelSize(com.android.internal.
    195                     R.dimen.default_app_widget_padding_left);
    196             padding.right = r.getDimensionPixelSize(com.android.internal.
    197                     R.dimen.default_app_widget_padding_right);
    198             padding.top = r.getDimensionPixelSize(com.android.internal.
    199                     R.dimen.default_app_widget_padding_top);
    200             padding.bottom = r.getDimensionPixelSize(com.android.internal.
    201                     R.dimen.default_app_widget_padding_bottom);
    202         }
    203         return padding;
    204     }
    205 
    206     public int getAppWidgetId() {
    207         return mAppWidgetId;
    208     }
    209 
    210     public AppWidgetProviderInfo getAppWidgetInfo() {
    211         return mInfo;
    212     }
    213 
    214     @Override
    215     protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) {
    216         final ParcelableSparseArray jail = new ParcelableSparseArray();
    217         super.dispatchSaveInstanceState(jail);
    218         container.put(generateId(), jail);
    219     }
    220 
    221     private int generateId() {
    222         final int id = getId();
    223         return id == View.NO_ID ? mAppWidgetId : id;
    224     }
    225 
    226     @Override
    227     protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
    228         final Parcelable parcelable = container.get(generateId());
    229 
    230         ParcelableSparseArray jail = null;
    231         if (parcelable != null && parcelable instanceof ParcelableSparseArray) {
    232             jail = (ParcelableSparseArray) parcelable;
    233         }
    234 
    235         if (jail == null) jail = new ParcelableSparseArray();
    236 
    237         try  {
    238             super.dispatchRestoreInstanceState(jail);
    239         } catch (Exception e) {
    240             Log.e(TAG, "failed to restoreInstanceState for widget id: " + mAppWidgetId + ", "
    241                     + (mInfo == null ? "null" : mInfo.provider), e);
    242         }
    243     }
    244 
    245     @Override
    246     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    247         try {
    248             super.onLayout(changed, left, top, right, bottom);
    249         } catch (final RuntimeException e) {
    250             Log.e(TAG, "Remote provider threw runtime exception, using error view instead.", e);
    251             removeViewInLayout(mView);
    252             View child = getErrorView();
    253             prepareView(child);
    254             addViewInLayout(child, 0, child.getLayoutParams());
    255             measureChild(child, MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY),
    256                     MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY));
    257             child.layout(0, 0, child.getMeasuredWidth() + mPaddingLeft + mPaddingRight,
    258                     child.getMeasuredHeight() + mPaddingTop + mPaddingBottom);
    259             mView = child;
    260             mViewMode = VIEW_MODE_ERROR;
    261         }
    262     }
    263 
    264     /**
    265      * Provide guidance about the size of this widget to the AppWidgetManager. The widths and
    266      * heights should correspond to the full area the AppWidgetHostView is given. Padding added by
    267      * the framework will be accounted for automatically. This information gets embedded into the
    268      * AppWidget options and causes a callback to the AppWidgetProvider.
    269      * @see AppWidgetProvider#onAppWidgetOptionsChanged(Context, AppWidgetManager, int, Bundle)
    270      *
    271      * @param newOptions The bundle of options, in addition to the size information,
    272      *          can be null.
    273      * @param minWidth The minimum width in dips that the widget will be displayed at.
    274      * @param minHeight The maximum height in dips that the widget will be displayed at.
    275      * @param maxWidth The maximum width in dips that the widget will be displayed at.
    276      * @param maxHeight The maximum height in dips that the widget will be displayed at.
    277      *
    278      */
    279     public void updateAppWidgetSize(Bundle newOptions, int minWidth, int minHeight, int maxWidth,
    280             int maxHeight) {
    281         updateAppWidgetSize(newOptions, minWidth, minHeight, maxWidth, maxHeight, false);
    282     }
    283 
    284     /**
    285      * @hide
    286      */
    287     public void updateAppWidgetSize(Bundle newOptions, int minWidth, int minHeight, int maxWidth,
    288             int maxHeight, boolean ignorePadding) {
    289         if (newOptions == null) {
    290             newOptions = new Bundle();
    291         }
    292 
    293         Rect padding = new Rect();
    294         if (mInfo != null) {
    295             padding = getDefaultPaddingForWidget(mContext, mInfo.provider, padding);
    296         }
    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      * Update the AppWidgetProviderInfo for this view, and reset it to the
    367      * initial layout.
    368      */
    369     void resetAppWidget(AppWidgetProviderInfo info) {
    370         mInfo = info;
    371         mViewMode = VIEW_MODE_NOINIT;
    372         updateAppWidget(null);
    373     }
    374 
    375     /**
    376      * Process a set of {@link RemoteViews} coming in as an update from the
    377      * AppWidget provider. Will animate into these new views as needed
    378      */
    379     public void updateAppWidget(RemoteViews remoteViews) {
    380         applyRemoteViews(remoteViews, true);
    381     }
    382 
    383     /**
    384      * @hide
    385      */
    386     protected void applyRemoteViews(RemoteViews remoteViews, boolean useAsyncIfPossible) {
    387         if (LOGD) Log.d(TAG, "updateAppWidget called mOld=" + mOld);
    388 
    389         boolean recycled = false;
    390         View content = null;
    391         Exception exception = null;
    392 
    393         // Capture the old view into a bitmap so we can do the crossfade.
    394         if (CROSSFADE) {
    395             if (mFadeStartTime < 0) {
    396                 if (mView != null) {
    397                     final int width = mView.getWidth();
    398                     final int height = mView.getHeight();
    399                     try {
    400                         mOld = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
    401                     } catch (OutOfMemoryError e) {
    402                         // we just won't do the fade
    403                         mOld = null;
    404                     }
    405                     if (mOld != null) {
    406                         //mView.drawIntoBitmap(mOld);
    407                     }
    408                 }
    409             }
    410         }
    411 
    412         if (mLastExecutionSignal != null) {
    413             mLastExecutionSignal.cancel();
    414             mLastExecutionSignal = null;
    415         }
    416 
    417         if (remoteViews == null) {
    418             if (mViewMode == VIEW_MODE_DEFAULT) {
    419                 // We've already done this -- nothing to do.
    420                 return;
    421             }
    422             content = getDefaultView();
    423             mLayoutId = -1;
    424             mViewMode = VIEW_MODE_DEFAULT;
    425         } else {
    426             if (mAsyncExecutor != null && useAsyncIfPossible) {
    427                 inflateAsync(remoteViews);
    428                 return;
    429             }
    430             // Prepare a local reference to the remote Context so we're ready to
    431             // inflate any requested LayoutParams.
    432             mRemoteContext = getRemoteContext();
    433             int layoutId = remoteViews.getLayoutId();
    434 
    435             // If our stale view has been prepared to match active, and the new
    436             // layout matches, try recycling it
    437             if (content == null && layoutId == mLayoutId) {
    438                 try {
    439                     remoteViews.reapply(mContext, mView, mOnClickHandler);
    440                     content = mView;
    441                     recycled = true;
    442                     if (LOGD) Log.d(TAG, "was able to recycle existing layout");
    443                 } catch (RuntimeException e) {
    444                     exception = e;
    445                 }
    446             }
    447 
    448             // Try normal RemoteView inflation
    449             if (content == null) {
    450                 try {
    451                     content = remoteViews.apply(mContext, this, mOnClickHandler);
    452                     if (LOGD) Log.d(TAG, "had to inflate new layout");
    453                 } catch (RuntimeException e) {
    454                     exception = e;
    455                 }
    456             }
    457 
    458             mLayoutId = layoutId;
    459             mViewMode = VIEW_MODE_CONTENT;
    460         }
    461 
    462         applyContent(content, recycled, exception);
    463         updateContentDescription(mInfo);
    464     }
    465 
    466     private void applyContent(View content, boolean recycled, Exception exception) {
    467         if (content == null) {
    468             if (mViewMode == VIEW_MODE_ERROR) {
    469                 // We've already done this -- nothing to do.
    470                 return ;
    471             }
    472             Log.w(TAG, "updateAppWidget couldn't find any view, using error view", exception);
    473             content = getErrorView();
    474             mViewMode = VIEW_MODE_ERROR;
    475         }
    476 
    477         if (!recycled) {
    478             prepareView(content);
    479             addView(content);
    480         }
    481 
    482         if (mView != content) {
    483             removeView(mView);
    484             mView = content;
    485         }
    486 
    487         if (CROSSFADE) {
    488             if (mFadeStartTime < 0) {
    489                 // if there is already an animation in progress, don't do anything --
    490                 // the new view will pop in on top of the old one during the cross fade,
    491                 // and that looks okay.
    492                 mFadeStartTime = SystemClock.uptimeMillis();
    493                 invalidate();
    494             }
    495         }
    496     }
    497 
    498     private void updateContentDescription(AppWidgetProviderInfo info) {
    499         if (info != null) {
    500             LauncherApps launcherApps = getContext().getSystemService(LauncherApps.class);
    501             ApplicationInfo appInfo = null;
    502             try {
    503                 appInfo = launcherApps.getApplicationInfo(
    504                         info.provider.getPackageName(), 0, info.getProfile());
    505             } catch (NameNotFoundException e) {
    506                 // ignore -- use null.
    507             }
    508             if (appInfo != null &&
    509                     (appInfo.flags & ApplicationInfo.FLAG_SUSPENDED) != 0) {
    510                 setContentDescription(
    511                         Resources.getSystem().getString(
    512                         com.android.internal.R.string.suspended_widget_accessibility, info.label));
    513             } else {
    514                 setContentDescription(info.label);
    515             }
    516         }
    517     }
    518 
    519     private void inflateAsync(RemoteViews remoteViews) {
    520         // Prepare a local reference to the remote Context so we're ready to
    521         // inflate any requested LayoutParams.
    522         mRemoteContext = getRemoteContext();
    523         int layoutId = remoteViews.getLayoutId();
    524 
    525         // If our stale view has been prepared to match active, and the new
    526         // layout matches, try recycling it
    527         if (layoutId == mLayoutId && mView != null) {
    528             try {
    529                 mLastExecutionSignal = remoteViews.reapplyAsync(mContext,
    530                         mView,
    531                         mAsyncExecutor,
    532                         new ViewApplyListener(remoteViews, layoutId, true),
    533                         mOnClickHandler);
    534             } catch (Exception e) {
    535                 // Reapply failed. Try apply
    536             }
    537         }
    538         if (mLastExecutionSignal == null) {
    539             mLastExecutionSignal = remoteViews.applyAsync(mContext,
    540                     this,
    541                     mAsyncExecutor,
    542                     new ViewApplyListener(remoteViews, layoutId, false),
    543                     mOnClickHandler);
    544         }
    545     }
    546 
    547     private class ViewApplyListener implements RemoteViews.OnViewAppliedListener {
    548         private final RemoteViews mViews;
    549         private final boolean mIsReapply;
    550         private final int mLayoutId;
    551 
    552         public ViewApplyListener(RemoteViews views, int layoutId, boolean isReapply) {
    553             mViews = views;
    554             mLayoutId = layoutId;
    555             mIsReapply = isReapply;
    556         }
    557 
    558         @Override
    559         public void onViewApplied(View v) {
    560             AppWidgetHostView.this.mLayoutId = mLayoutId;
    561             mViewMode = VIEW_MODE_CONTENT;
    562 
    563             applyContent(v, mIsReapply, null);
    564         }
    565 
    566         @Override
    567         public void onError(Exception e) {
    568             if (mIsReapply) {
    569                 // Try a fresh replay
    570                 mLastExecutionSignal = mViews.applyAsync(mContext,
    571                         AppWidgetHostView.this,
    572                         mAsyncExecutor,
    573                         new ViewApplyListener(mViews, mLayoutId, false),
    574                         mOnClickHandler);
    575             } else {
    576                 applyContent(null, false, e);
    577             }
    578         }
    579     }
    580 
    581     /**
    582      * Process data-changed notifications for the specified view in the specified
    583      * set of {@link RemoteViews} views.
    584      */
    585     void viewDataChanged(int viewId) {
    586         View v = findViewById(viewId);
    587         if ((v != null) && (v instanceof AdapterView<?>)) {
    588             AdapterView<?> adapterView = (AdapterView<?>) v;
    589             Adapter adapter = adapterView.getAdapter();
    590             if (adapter instanceof BaseAdapter) {
    591                 BaseAdapter baseAdapter = (BaseAdapter) adapter;
    592                 baseAdapter.notifyDataSetChanged();
    593             }  else if (adapter == null && adapterView instanceof RemoteAdapterConnectionCallback) {
    594                 // If the adapter is null, it may mean that the RemoteViewsAapter has not yet
    595                 // connected to its associated service, and hence the adapter hasn't been set.
    596                 // In this case, we need to defer the notify call until it has been set.
    597                 ((RemoteAdapterConnectionCallback) adapterView).deferNotifyDataSetChanged();
    598             }
    599         }
    600     }
    601 
    602     /**
    603      * Build a {@link Context} cloned into another package name, usually for the
    604      * purposes of reading remote resources.
    605      * @hide
    606      */
    607     protected Context getRemoteContext() {
    608         try {
    609             // Return if cloned successfully, otherwise default
    610             return mContext.createApplicationContext(
    611                     mInfo.providerInfo.applicationInfo,
    612                     Context.CONTEXT_RESTRICTED);
    613         } catch (NameNotFoundException e) {
    614             Log.e(TAG, "Package name " +  mInfo.providerInfo.packageName + " not found");
    615             return mContext;
    616         }
    617     }
    618 
    619     @Override
    620     protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
    621         if (CROSSFADE) {
    622             int alpha;
    623             int l = child.getLeft();
    624             int t = child.getTop();
    625             if (mFadeStartTime > 0) {
    626                 alpha = (int)(((drawingTime-mFadeStartTime)*255)/FADE_DURATION);
    627                 if (alpha > 255) {
    628                     alpha = 255;
    629                 }
    630                 Log.d(TAG, "drawChild alpha=" + alpha + " l=" + l + " t=" + t
    631                         + " w=" + child.getWidth());
    632                 if (alpha != 255 && mOld != null) {
    633                     mOldPaint.setAlpha(255-alpha);
    634                     //canvas.drawBitmap(mOld, l, t, mOldPaint);
    635                 }
    636             } else {
    637                 alpha = 255;
    638             }
    639             int restoreTo = canvas.saveLayerAlpha(l, t, child.getWidth(), child.getHeight(), alpha,
    640                     Canvas.HAS_ALPHA_LAYER_SAVE_FLAG | Canvas.CLIP_TO_LAYER_SAVE_FLAG);
    641             boolean rv = super.drawChild(canvas, child, drawingTime);
    642             canvas.restoreToCount(restoreTo);
    643             if (alpha < 255) {
    644                 invalidate();
    645             } else {
    646                 mFadeStartTime = -1;
    647                 if (mOld != null) {
    648                     mOld.recycle();
    649                     mOld = null;
    650                 }
    651             }
    652             return rv;
    653         } else {
    654             return super.drawChild(canvas, child, drawingTime);
    655         }
    656     }
    657 
    658     /**
    659      * Prepare the given view to be shown. This might include adjusting
    660      * {@link FrameLayout.LayoutParams} before inserting.
    661      */
    662     protected void prepareView(View view) {
    663         // Take requested dimensions from child, but apply default gravity.
    664         FrameLayout.LayoutParams requested = (FrameLayout.LayoutParams)view.getLayoutParams();
    665         if (requested == null) {
    666             requested = new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT,
    667                     LayoutParams.MATCH_PARENT);
    668         }
    669 
    670         requested.gravity = Gravity.CENTER;
    671         view.setLayoutParams(requested);
    672     }
    673 
    674     /**
    675      * Inflate and return the default layout requested by AppWidget provider.
    676      */
    677     protected View getDefaultView() {
    678         if (LOGD) {
    679             Log.d(TAG, "getDefaultView");
    680         }
    681         View defaultView = null;
    682         Exception exception = null;
    683 
    684         try {
    685             if (mInfo != null) {
    686                 Context theirContext = getRemoteContext();
    687                 mRemoteContext = theirContext;
    688                 LayoutInflater inflater = (LayoutInflater)
    689                         theirContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    690                 inflater = inflater.cloneInContext(theirContext);
    691                 inflater.setFilter(sInflaterFilter);
    692                 AppWidgetManager manager = AppWidgetManager.getInstance(mContext);
    693                 Bundle options = manager.getAppWidgetOptions(mAppWidgetId);
    694 
    695                 int layoutId = mInfo.initialLayout;
    696                 if (options.containsKey(AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY)) {
    697                     int category = options.getInt(AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY);
    698                     if (category == AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD) {
    699                         int kgLayoutId = mInfo.initialKeyguardLayout;
    700                         // If a default keyguard layout is not specified, use the standard
    701                         // default layout.
    702                         layoutId = kgLayoutId == 0 ? layoutId : kgLayoutId;
    703                     }
    704                 }
    705                 defaultView = inflater.inflate(layoutId, this, false);
    706             } else {
    707                 Log.w(TAG, "can't inflate defaultView because mInfo is missing");
    708             }
    709         } catch (RuntimeException e) {
    710             exception = e;
    711         }
    712 
    713         if (exception != null) {
    714             Log.w(TAG, "Error inflating AppWidget " + mInfo + ": " + exception.toString());
    715         }
    716 
    717         if (defaultView == null) {
    718             if (LOGD) Log.d(TAG, "getDefaultView couldn't find any view, so inflating error");
    719             defaultView = getErrorView();
    720         }
    721 
    722         return defaultView;
    723     }
    724 
    725     /**
    726      * Inflate and return a view that represents an error state.
    727      */
    728     protected View getErrorView() {
    729         TextView tv = new TextView(mContext);
    730         tv.setText(com.android.internal.R.string.gadget_host_error_inflating);
    731         // TODO: get this color from somewhere.
    732         tv.setBackgroundColor(Color.argb(127, 0, 0, 0));
    733         return tv;
    734     }
    735 
    736     /** @hide */
    737     @Override
    738     public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
    739         super.onInitializeAccessibilityNodeInfoInternal(info);
    740         info.setClassName(AppWidgetHostView.class.getName());
    741     }
    742 
    743     private static class ParcelableSparseArray extends SparseArray<Parcelable> implements Parcelable {
    744         public int describeContents() {
    745             return 0;
    746         }
    747 
    748         public void writeToParcel(Parcel dest, int flags) {
    749             final int count = size();
    750             dest.writeInt(count);
    751             for (int i = 0; i < count; i++) {
    752                 dest.writeInt(keyAt(i));
    753                 dest.writeParcelable(valueAt(i), 0);
    754             }
    755         }
    756 
    757         public static final Parcelable.Creator<ParcelableSparseArray> CREATOR =
    758                 new Parcelable.Creator<ParcelableSparseArray>() {
    759                     public ParcelableSparseArray createFromParcel(Parcel source) {
    760                         final ParcelableSparseArray array = new ParcelableSparseArray();
    761                         final ClassLoader loader = array.getClass().getClassLoader();
    762                         final int count = source.readInt();
    763                         for (int i = 0; i < count; i++) {
    764                             array.put(source.readInt(), source.readParcelable(loader));
    765                         }
    766                         return array;
    767                     }
    768 
    769                     public ParcelableSparseArray[] newArray(int size) {
    770                         return new ParcelableSparseArray[size];
    771                     }
    772                 };
    773     }
    774 }
    775