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      */
AppWidgetHostView(Context context)100     public AppWidgetHostView(Context context) {
101         this(context, android.R.anim.fade_in, android.R.anim.fade_out);
102     }
103 
104     /**
105      * @hide
106      */
AppWidgetHostView(Context context, OnClickHandler handler)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"})
AppWidgetHostView(Context context, int animationIn, int animationOut)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      */
setOnClickHandler(OnClickHandler handler)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      */
setAppWidget(int appWidgetId, AppWidgetProviderInfo info)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      */
getDefaultPaddingForWidget(Context context, ComponentName component, Rect padding)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 
getAppWidgetId()206     public int getAppWidgetId() {
207         return mAppWidgetId;
208     }
209 
getAppWidgetInfo()210     public AppWidgetProviderInfo getAppWidgetInfo() {
211         return mInfo;
212     }
213 
214     @Override
dispatchSaveInstanceState(SparseArray<Parcelable> container)215     protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) {
216         final ParcelableSparseArray jail = new ParcelableSparseArray();
217         super.dispatchSaveInstanceState(jail);
218         container.put(generateId(), jail);
219     }
220 
generateId()221     private int generateId() {
222         final int id = getId();
223         return id == View.NO_ID ? mAppWidgetId : id;
224     }
225 
226     @Override
dispatchRestoreInstanceState(SparseArray<Parcelable> container)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
onLayout(boolean changed, int left, int top, int right, int bottom)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      */
updateAppWidgetSize(Bundle newOptions, int minWidth, int minHeight, int maxWidth, int maxHeight)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      */
updateAppWidgetSize(Bundle newOptions, int minWidth, int minHeight, int maxWidth, int maxHeight, boolean ignorePadding)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      */
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 and applying the remoteviews.
351      * @see {@link RemoteViews#applyAsync(Context, ViewGroup, RemoteViews.OnViewAppliedListener, Executor)}
352      *
353      * @param executor the executor to use or null.
354      * @hide
355      */
setAsyncExecutor(Executor executor)356     public void setAsyncExecutor(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      */
resetAppWidget(AppWidgetProviderInfo info)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      */
updateAppWidget(RemoteViews remoteViews)379     public void updateAppWidget(RemoteViews remoteViews) {
380         applyRemoteViews(remoteViews);
381     }
382 
383     /**
384      * @hide
385      */
applyRemoteViews(RemoteViews remoteViews)386     protected void applyRemoteViews(RemoteViews remoteViews) {
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) {
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 
applyContent(View content, boolean recycled, Exception exception)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 
updateContentDescription(AppWidgetProviderInfo info)498     private void updateContentDescription(AppWidgetProviderInfo info) {
499         if (info != null) {
500             LauncherApps launcherApps = getContext().getSystemService(LauncherApps.class);
501             ApplicationInfo appInfo = launcherApps.getApplicationInfo(
502                     info.provider.getPackageName(), 0, info.getProfile());
503             if (appInfo != null &&
504                     (appInfo.flags & ApplicationInfo.FLAG_SUSPENDED) != 0) {
505                 setContentDescription(
506                         Resources.getSystem().getString(
507                         com.android.internal.R.string.suspended_widget_accessibility, info.label));
508             } else {
509                 setContentDescription(info.label);
510             }
511         }
512     }
513 
inflateAsync(RemoteViews remoteViews)514     private void inflateAsync(RemoteViews remoteViews) {
515         // Prepare a local reference to the remote Context so we're ready to
516         // inflate any requested LayoutParams.
517         mRemoteContext = getRemoteContext();
518         int layoutId = remoteViews.getLayoutId();
519 
520         // If our stale view has been prepared to match active, and the new
521         // layout matches, try recycling it
522         if (layoutId == mLayoutId && mView != null) {
523             try {
524                 mLastExecutionSignal = remoteViews.reapplyAsync(mContext,
525                         mView,
526                         mAsyncExecutor,
527                         new ViewApplyListener(remoteViews, layoutId, true),
528                         mOnClickHandler);
529             } catch (Exception e) {
530                 // Reapply failed. Try apply
531             }
532         }
533         if (mLastExecutionSignal == null) {
534             mLastExecutionSignal = remoteViews.applyAsync(mContext,
535                     this,
536                     mAsyncExecutor,
537                     new ViewApplyListener(remoteViews, layoutId, false),
538                     mOnClickHandler);
539         }
540     }
541 
542     private class ViewApplyListener implements RemoteViews.OnViewAppliedListener {
543         private final RemoteViews mViews;
544         private final boolean mIsReapply;
545         private final int mLayoutId;
546 
ViewApplyListener(RemoteViews views, int layoutId, boolean isReapply)547         public ViewApplyListener(RemoteViews views, int layoutId, boolean isReapply) {
548             mViews = views;
549             mLayoutId = layoutId;
550             mIsReapply = isReapply;
551         }
552 
553         @Override
onViewApplied(View v)554         public void onViewApplied(View v) {
555             AppWidgetHostView.this.mLayoutId = mLayoutId;
556             mViewMode = VIEW_MODE_CONTENT;
557 
558             applyContent(v, mIsReapply, null);
559         }
560 
561         @Override
onError(Exception e)562         public void onError(Exception e) {
563             if (mIsReapply) {
564                 // Try a fresh replay
565                 mLastExecutionSignal = mViews.applyAsync(mContext,
566                         AppWidgetHostView.this,
567                         mAsyncExecutor,
568                         new ViewApplyListener(mViews, mLayoutId, false),
569                         mOnClickHandler);
570             } else {
571                 applyContent(null, false, e);
572             }
573         }
574     }
575 
576     /**
577      * Process data-changed notifications for the specified view in the specified
578      * set of {@link RemoteViews} views.
579      */
viewDataChanged(int viewId)580     void viewDataChanged(int viewId) {
581         View v = findViewById(viewId);
582         if ((v != null) && (v instanceof AdapterView<?>)) {
583             AdapterView<?> adapterView = (AdapterView<?>) v;
584             Adapter adapter = adapterView.getAdapter();
585             if (adapter instanceof BaseAdapter) {
586                 BaseAdapter baseAdapter = (BaseAdapter) adapter;
587                 baseAdapter.notifyDataSetChanged();
588             }  else if (adapter == null && adapterView instanceof RemoteAdapterConnectionCallback) {
589                 // If the adapter is null, it may mean that the RemoteViewsAapter has not yet
590                 // connected to its associated service, and hence the adapter hasn't been set.
591                 // In this case, we need to defer the notify call until it has been set.
592                 ((RemoteAdapterConnectionCallback) adapterView).deferNotifyDataSetChanged();
593             }
594         }
595     }
596 
597     /**
598      * Build a {@link Context} cloned into another package name, usually for the
599      * purposes of reading remote resources.
600      * @hide
601      */
getRemoteContext()602     protected Context getRemoteContext() {
603         try {
604             // Return if cloned successfully, otherwise default
605             return mContext.createApplicationContext(
606                     mInfo.providerInfo.applicationInfo,
607                     Context.CONTEXT_RESTRICTED);
608         } catch (NameNotFoundException e) {
609             Log.e(TAG, "Package name " +  mInfo.providerInfo.packageName + " not found");
610             return mContext;
611         }
612     }
613 
614     @Override
drawChild(Canvas canvas, View child, long drawingTime)615     protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
616         if (CROSSFADE) {
617             int alpha;
618             int l = child.getLeft();
619             int t = child.getTop();
620             if (mFadeStartTime > 0) {
621                 alpha = (int)(((drawingTime-mFadeStartTime)*255)/FADE_DURATION);
622                 if (alpha > 255) {
623                     alpha = 255;
624                 }
625                 Log.d(TAG, "drawChild alpha=" + alpha + " l=" + l + " t=" + t
626                         + " w=" + child.getWidth());
627                 if (alpha != 255 && mOld != null) {
628                     mOldPaint.setAlpha(255-alpha);
629                     //canvas.drawBitmap(mOld, l, t, mOldPaint);
630                 }
631             } else {
632                 alpha = 255;
633             }
634             int restoreTo = canvas.saveLayerAlpha(l, t, child.getWidth(), child.getHeight(), alpha,
635                     Canvas.HAS_ALPHA_LAYER_SAVE_FLAG | Canvas.CLIP_TO_LAYER_SAVE_FLAG);
636             boolean rv = super.drawChild(canvas, child, drawingTime);
637             canvas.restoreToCount(restoreTo);
638             if (alpha < 255) {
639                 invalidate();
640             } else {
641                 mFadeStartTime = -1;
642                 if (mOld != null) {
643                     mOld.recycle();
644                     mOld = null;
645                 }
646             }
647             return rv;
648         } else {
649             return super.drawChild(canvas, child, drawingTime);
650         }
651     }
652 
653     /**
654      * Prepare the given view to be shown. This might include adjusting
655      * {@link FrameLayout.LayoutParams} before inserting.
656      */
prepareView(View view)657     protected void prepareView(View view) {
658         // Take requested dimensions from child, but apply default gravity.
659         FrameLayout.LayoutParams requested = (FrameLayout.LayoutParams)view.getLayoutParams();
660         if (requested == null) {
661             requested = new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT,
662                     LayoutParams.MATCH_PARENT);
663         }
664 
665         requested.gravity = Gravity.CENTER;
666         view.setLayoutParams(requested);
667     }
668 
669     /**
670      * Inflate and return the default layout requested by AppWidget provider.
671      */
getDefaultView()672     protected View getDefaultView() {
673         if (LOGD) {
674             Log.d(TAG, "getDefaultView");
675         }
676         View defaultView = null;
677         Exception exception = null;
678 
679         try {
680             if (mInfo != null) {
681                 Context theirContext = getRemoteContext();
682                 mRemoteContext = theirContext;
683                 LayoutInflater inflater = (LayoutInflater)
684                         theirContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
685                 inflater = inflater.cloneInContext(theirContext);
686                 inflater.setFilter(sInflaterFilter);
687                 AppWidgetManager manager = AppWidgetManager.getInstance(mContext);
688                 Bundle options = manager.getAppWidgetOptions(mAppWidgetId);
689 
690                 int layoutId = mInfo.initialLayout;
691                 if (options.containsKey(AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY)) {
692                     int category = options.getInt(AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY);
693                     if (category == AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD) {
694                         int kgLayoutId = mInfo.initialKeyguardLayout;
695                         // If a default keyguard layout is not specified, use the standard
696                         // default layout.
697                         layoutId = kgLayoutId == 0 ? layoutId : kgLayoutId;
698                     }
699                 }
700                 defaultView = inflater.inflate(layoutId, this, false);
701             } else {
702                 Log.w(TAG, "can't inflate defaultView because mInfo is missing");
703             }
704         } catch (RuntimeException e) {
705             exception = e;
706         }
707 
708         if (exception != null) {
709             Log.w(TAG, "Error inflating AppWidget " + mInfo + ": " + exception.toString());
710         }
711 
712         if (defaultView == null) {
713             if (LOGD) Log.d(TAG, "getDefaultView couldn't find any view, so inflating error");
714             defaultView = getErrorView();
715         }
716 
717         return defaultView;
718     }
719 
720     /**
721      * Inflate and return a view that represents an error state.
722      */
getErrorView()723     protected View getErrorView() {
724         TextView tv = new TextView(mContext);
725         tv.setText(com.android.internal.R.string.gadget_host_error_inflating);
726         // TODO: get this color from somewhere.
727         tv.setBackgroundColor(Color.argb(127, 0, 0, 0));
728         return tv;
729     }
730 
731     /** @hide */
732     @Override
onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info)733     public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
734         super.onInitializeAccessibilityNodeInfoInternal(info);
735         info.setClassName(AppWidgetHostView.class.getName());
736     }
737 
738     private static class ParcelableSparseArray extends SparseArray<Parcelable> implements Parcelable {
describeContents()739         public int describeContents() {
740             return 0;
741         }
742 
writeToParcel(Parcel dest, int flags)743         public void writeToParcel(Parcel dest, int flags) {
744             final int count = size();
745             dest.writeInt(count);
746             for (int i = 0; i < count; i++) {
747                 dest.writeInt(keyAt(i));
748                 dest.writeParcelable(valueAt(i), 0);
749             }
750         }
751 
752         public static final Parcelable.Creator<ParcelableSparseArray> CREATOR =
753                 new Parcelable.Creator<ParcelableSparseArray>() {
754                     public ParcelableSparseArray createFromParcel(Parcel source) {
755                         final ParcelableSparseArray array = new ParcelableSparseArray();
756                         final ClassLoader loader = array.getClass().getClassLoader();
757                         final int count = source.readInt();
758                         for (int i = 0; i < count; i++) {
759                             array.put(source.readInt(), source.readParcelable(loader));
760                         }
761                         return array;
762                     }
763 
764                     public ParcelableSparseArray[] newArray(int size) {
765                         return new ParcelableSparseArray[size];
766                     }
767                 };
768     }
769 }
770