blob: be0f96e8ab6a6d6cef4dcdf4ba305c9604589fa5 [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/*
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
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -070017package android.appwidget;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080018
19import android.content.Context;
20import android.content.pm.PackageManager;
21import android.graphics.Bitmap;
22import android.graphics.Canvas;
23import android.graphics.Color;
24import android.graphics.Paint;
25import android.os.Handler;
26import android.os.Message;
27import android.os.SystemClock;
28import android.util.Config;
29import android.util.Log;
30import android.view.Gravity;
31import android.view.LayoutInflater;
32import android.view.View;
33import android.view.ViewGroup;
34import android.view.animation.Animation;
35import android.widget.FrameLayout;
36import android.widget.RemoteViews;
37import android.widget.TextView;
38
39/**
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -070040 * Provides the glue to show AppWidget views. This class offers automatic animation
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080041 * between updates, and will try recycling old views for each incoming
42 * {@link RemoteViews}.
43 */
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -070044public class AppWidgetHostView extends FrameLayout {
45 static final String TAG = "AppWidgetHostView";
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080046 static final boolean LOGD = false;
47 static final boolean CROSSFADE = false;
48
49 static final int VIEW_MODE_NOINIT = 0;
50 static final int VIEW_MODE_CONTENT = 1;
51 static final int VIEW_MODE_ERROR = 2;
52 static final int VIEW_MODE_DEFAULT = 3;
53
54 static final int FADE_DURATION = 1000;
55
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -070056 // When we're inflating the initialLayout for a AppWidget, we only allow
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080057 // views that are allowed in RemoteViews.
58 static final LayoutInflater.Filter sInflaterFilter = new LayoutInflater.Filter() {
59 public boolean onLoadClass(Class clazz) {
60 return clazz.isAnnotationPresent(RemoteViews.RemoteView.class);
61 }
62 };
63
64 Context mContext;
65
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -070066 int mAppWidgetId;
67 AppWidgetProviderInfo mInfo;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080068 View mView;
69 int mViewMode = VIEW_MODE_NOINIT;
70 int mLayoutId = -1;
71 long mFadeStartTime = -1;
72 Bitmap mOld;
73 Paint mOldPaint = new Paint();
74
75 /**
76 * Create a host view. Uses default fade animations.
77 */
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -070078 public AppWidgetHostView(Context context) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080079 this(context, android.R.anim.fade_in, android.R.anim.fade_out);
80 }
81
82 /**
83 * Create a host view. Uses specified animations when pushing
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -070084 * {@link #updateAppWidget(RemoteViews)}.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080085 *
86 * @param animationIn Resource ID of in animation to use
87 * @param animationOut Resource ID of out animation to use
88 */
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -070089 public AppWidgetHostView(Context context, int animationIn, int animationOut) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080090 super(context);
91 mContext = context;
92 }
93
94 /**
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -070095 * Set the AppWidget that will be displayed by this view.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080096 */
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -070097 public void setAppWidget(int appWidgetId, AppWidgetProviderInfo info) {
98 mAppWidgetId = appWidgetId;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080099 mInfo = info;
100 }
101
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700102 public int getAppWidgetId() {
103 return mAppWidgetId;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800104 }
105
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700106 public AppWidgetProviderInfo getAppWidgetInfo() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800107 return mInfo;
108 }
109
110 /**
111 * Process a set of {@link RemoteViews} coming in as an update from the
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700112 * AppWidget provider. Will animate into these new views as needed.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800113 */
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700114 public void updateAppWidget(RemoteViews remoteViews) {
115 if (LOGD) Log.d(TAG, "updateAppWidget called mOld=" + mOld);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800116
117 boolean recycled = false;
118 View content = null;
119 Exception exception = null;
120
121 // Capture the old view into a bitmap so we can do the crossfade.
122 if (CROSSFADE) {
123 if (mFadeStartTime < 0) {
124 if (mView != null) {
125 final int width = mView.getWidth();
126 final int height = mView.getHeight();
127 try {
128 mOld = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
129 } catch (OutOfMemoryError e) {
130 // we just won't do the fade
131 mOld = null;
132 }
133 if (mOld != null) {
134 //mView.drawIntoBitmap(mOld);
135 }
136 }
137 }
138 }
139
140 if (remoteViews == null) {
141 if (mViewMode == VIEW_MODE_DEFAULT) {
142 // We've already done this -- nothing to do.
143 return;
144 }
145 content = getDefaultView();
146 mLayoutId = -1;
147 mViewMode = VIEW_MODE_DEFAULT;
148 } else {
149 int layoutId = remoteViews.getLayoutId();
150
151 // If our stale view has been prepared to match active, and the new
152 // layout matches, try recycling it
153 if (content == null && layoutId == mLayoutId) {
154 try {
155 remoteViews.reapply(mContext, mView);
156 content = mView;
157 recycled = true;
158 if (LOGD) Log.d(TAG, "was able to recycled existing layout");
159 } catch (RuntimeException e) {
160 exception = e;
161 }
162 }
163
164 // Try normal RemoteView inflation
165 if (content == null) {
166 try {
167 content = remoteViews.apply(mContext, this);
168 if (LOGD) Log.d(TAG, "had to inflate new layout");
169 } catch (RuntimeException e) {
170 exception = e;
171 }
172 }
173
174 mLayoutId = layoutId;
175 mViewMode = VIEW_MODE_CONTENT;
176 }
177
178 if (content == null) {
179 if (mViewMode == VIEW_MODE_ERROR) {
180 // We've already done this -- nothing to do.
181 return ;
182 }
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700183 Log.w(TAG, "updateAppWidget couldn't find any view, using error view", exception);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800184 content = getErrorView();
185 mViewMode = VIEW_MODE_ERROR;
186 }
187
188 if (!recycled) {
189 prepareView(content);
190 addView(content);
191 }
192
193 if (mView != content) {
194 removeView(mView);
195 mView = content;
196 }
197
198 if (CROSSFADE) {
199 if (mFadeStartTime < 0) {
200 // if there is already an animation in progress, don't do anything --
201 // the new view will pop in on top of the old one during the cross fade,
202 // and that looks okay.
203 mFadeStartTime = SystemClock.uptimeMillis();
204 invalidate();
205 }
206 }
207 }
208
209 protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
210 if (CROSSFADE) {
211 int alpha;
212 int l = child.getLeft();
213 int t = child.getTop();
214 if (mFadeStartTime > 0) {
215 alpha = (int)(((drawingTime-mFadeStartTime)*255)/FADE_DURATION);
216 if (alpha > 255) {
217 alpha = 255;
218 }
219 Log.d(TAG, "drawChild alpha=" + alpha + " l=" + l + " t=" + t
220 + " w=" + child.getWidth());
221 if (alpha != 255 && mOld != null) {
222 mOldPaint.setAlpha(255-alpha);
223 //canvas.drawBitmap(mOld, l, t, mOldPaint);
224 }
225 } else {
226 alpha = 255;
227 }
228 int restoreTo = canvas.saveLayerAlpha(l, t, child.getWidth(), child.getHeight(), alpha,
229 Canvas.HAS_ALPHA_LAYER_SAVE_FLAG | Canvas.CLIP_TO_LAYER_SAVE_FLAG);
230 boolean rv = super.drawChild(canvas, child, drawingTime);
231 canvas.restoreToCount(restoreTo);
232 if (alpha < 255) {
233 invalidate();
234 } else {
235 mFadeStartTime = -1;
236 if (mOld != null) {
237 mOld.recycle();
238 mOld = null;
239 }
240 }
241 return rv;
242 } else {
243 return super.drawChild(canvas, child, drawingTime);
244 }
245 }
246
247 /**
248 * Prepare the given view to be shown. This might include adjusting
249 * {@link FrameLayout.LayoutParams} before inserting.
250 */
251 protected void prepareView(View view) {
252 // Take requested dimensions from parent, but apply default gravity.
253 ViewGroup.LayoutParams requested = view.getLayoutParams();
254 if (requested == null) {
255 requested = new FrameLayout.LayoutParams(LayoutParams.FILL_PARENT,
256 LayoutParams.FILL_PARENT);
257 }
258
259 FrameLayout.LayoutParams params =
260 new FrameLayout.LayoutParams(requested.width, requested.height);
261 params.gravity = Gravity.CENTER;
262 view.setLayoutParams(params);
263 }
264
265 /**
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700266 * Inflate and return the default layout requested by AppWidget provider.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800267 */
268 protected View getDefaultView() {
269 View defaultView = null;
270 Exception exception = null;
271
272 try {
273 if (mInfo != null) {
274 Context theirContext = mContext.createPackageContext(
275 mInfo.provider.getPackageName(), 0 /* no flags */);
276 LayoutInflater inflater = (LayoutInflater)
277 theirContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
278 inflater = inflater.cloneInContext(theirContext);
279 inflater.setFilter(sInflaterFilter);
280 defaultView = inflater.inflate(mInfo.initialLayout, this, false);
281 } else {
282 Log.w(TAG, "can't inflate defaultView because mInfo is missing");
283 }
284 } catch (PackageManager.NameNotFoundException e) {
285 exception = e;
286 } catch (RuntimeException e) {
287 exception = e;
288 }
289
290 if (exception != null && LOGD) {
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700291 Log.w(TAG, "Error inflating AppWidget " + mInfo, exception);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800292 }
293
294 if (defaultView == null) {
295 if (LOGD) Log.d(TAG, "getDefaultView couldn't find any view, so inflating error");
296 defaultView = getErrorView();
297 }
298
299 return defaultView;
300 }
301
302 /**
303 * Inflate and return a view that represents an error state.
304 */
305 protected View getErrorView() {
306 TextView tv = new TextView(mContext);
307 tv.setText(com.android.internal.R.string.gadget_host_error_inflating);
308 // TODO: get this color from somewhere.
309 tv.setBackgroundColor(Color.argb(127, 0, 0, 0));
310 return tv;
311 }
312}