diff options
author | The Android Open Source Project <initial-contribution@android.com> | 2009-03-11 12:11:56 -0700 |
---|---|---|
committer | The Android Open Source Project <initial-contribution@android.com> | 2009-03-11 12:11:56 -0700 |
commit | c39a6e0c51e182338deb8b63d07933b585134929 (patch) | |
tree | e55fc5bd38b1eb8fb4851a0fe1cc264a7fe2f245 /core/java/android/appwidget | |
parent | b2a3dd88a53cc8c6d19f6dc8ec4f3d6c4abd9b54 (diff) | |
download | frameworks_base-c39a6e0c51e182338deb8b63d07933b585134929.zip frameworks_base-c39a6e0c51e182338deb8b63d07933b585134929.tar.gz frameworks_base-c39a6e0c51e182338deb8b63d07933b585134929.tar.bz2 |
auto import from //branches/cupcake/...@137873
Diffstat (limited to 'core/java/android/appwidget')
-rw-r--r-- | core/java/android/appwidget/AppWidgetHost.java | 248 | ||||
-rw-r--r-- | core/java/android/appwidget/AppWidgetHostView.java | 312 | ||||
-rw-r--r-- | core/java/android/appwidget/AppWidgetManager.java | 320 | ||||
-rwxr-xr-x | core/java/android/appwidget/AppWidgetProvider.java | 154 | ||||
-rw-r--r-- | core/java/android/appwidget/AppWidgetProviderInfo.aidl | 19 | ||||
-rw-r--r-- | core/java/android/appwidget/AppWidgetProviderInfo.java | 168 | ||||
-rw-r--r-- | core/java/android/appwidget/package.html | 136 |
7 files changed, 1357 insertions, 0 deletions
diff --git a/core/java/android/appwidget/AppWidgetHost.java b/core/java/android/appwidget/AppWidgetHost.java new file mode 100644 index 0000000..10c2b02 --- /dev/null +++ b/core/java/android/appwidget/AppWidgetHost.java @@ -0,0 +1,248 @@ +/* + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.appwidget; + +import android.content.Context; +import android.os.Handler; +import android.os.IBinder; +import android.os.Looper; +import android.os.Message; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.widget.RemoteViews; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +import com.android.internal.appwidget.IAppWidgetHost; +import com.android.internal.appwidget.IAppWidgetService; + +/** + * AppWidgetHost provides the interaction with the AppWidget service for apps, + * like the home screen, that want to embed AppWidgets in their UI. + */ +public class AppWidgetHost { + + static final int HANDLE_UPDATE = 1; + static final int HANDLE_PROVIDER_CHANGED = 2; + + static Object sServiceLock = new Object(); + static IAppWidgetService sService; + + Context mContext; + String mPackageName; + + class Callbacks extends IAppWidgetHost.Stub { + public void updateAppWidget(int appWidgetId, RemoteViews views) { + Message msg = mHandler.obtainMessage(HANDLE_UPDATE); + msg.arg1 = appWidgetId; + msg.obj = views; + msg.sendToTarget(); + } + + public void providerChanged(int appWidgetId, AppWidgetProviderInfo info) { + Message msg = mHandler.obtainMessage(HANDLE_PROVIDER_CHANGED); + msg.arg1 = appWidgetId; + msg.obj = info; + msg.sendToTarget(); + } + } + + class UpdateHandler extends Handler { + public UpdateHandler(Looper looper) { + super(looper); + } + + public void handleMessage(Message msg) { + switch (msg.what) { + case HANDLE_UPDATE: { + updateAppWidgetView(msg.arg1, (RemoteViews)msg.obj); + break; + } + case HANDLE_PROVIDER_CHANGED: { + onProviderChanged(msg.arg1, (AppWidgetProviderInfo)msg.obj); + break; + } + } + } + } + + Handler mHandler; + + int mHostId; + Callbacks mCallbacks = new Callbacks(); + HashMap<Integer,AppWidgetHostView> mViews = new HashMap(); + + public AppWidgetHost(Context context, int hostId) { + mContext = context; + mHostId = hostId; + mHandler = new UpdateHandler(context.getMainLooper()); + synchronized (sServiceLock) { + if (sService == null) { + IBinder b = ServiceManager.getService(Context.APPWIDGET_SERVICE); + sService = IAppWidgetService.Stub.asInterface(b); + } + } + } + + /** + * Start receiving onAppWidgetChanged calls for your AppWidgets. Call this when your activity + * becomes visible, i.e. from onStart() in your Activity. + */ + public void startListening() { + int[] updatedIds = null; + ArrayList<RemoteViews> updatedViews = new ArrayList(); + + try { + if (mPackageName == null) { + mPackageName = mContext.getPackageName(); + } + updatedIds = sService.startListening(mCallbacks, mPackageName, mHostId, updatedViews); + } + catch (RemoteException e) { + throw new RuntimeException("system server dead?", e); + } + + final int N = updatedIds.length; + for (int i=0; i<N; i++) { + updateAppWidgetView(updatedIds[i], updatedViews.get(i)); + } + } + + /** + * Stop receiving onAppWidgetChanged calls for your AppWidgets. Call this when your activity is + * no longer visible, i.e. from onStop() in your Activity. + */ + public void stopListening() { + try { + sService.stopListening(mHostId); + } + catch (RemoteException e) { + throw new RuntimeException("system server dead?", e); + } + } + + /** + * Get a appWidgetId for a host in the calling process. + * + * @return a appWidgetId + */ + public int allocateAppWidgetId() { + try { + if (mPackageName == null) { + mPackageName = mContext.getPackageName(); + } + return sService.allocateAppWidgetId(mPackageName, mHostId); + } + catch (RemoteException e) { + throw new RuntimeException("system server dead?", e); + } + } + + /** + * Stop listening to changes for this AppWidget. + */ + public void deleteAppWidgetId(int appWidgetId) { + synchronized (mViews) { + mViews.remove(appWidgetId); + try { + sService.deleteAppWidgetId(appWidgetId); + } + catch (RemoteException e) { + throw new RuntimeException("system server dead?", e); + } + } + } + + /** + * Remove all records about this host from the AppWidget manager. + * <ul> + * <li>Call this when initializing your database, as it might be because of a data wipe.</li> + * <li>Call this to have the AppWidget manager release all resources associated with your + * host. Any future calls about this host will cause the records to be re-allocated.</li> + * </ul> + */ + public void deleteHost() { + try { + sService.deleteHost(mHostId); + } + catch (RemoteException e) { + throw new RuntimeException("system server dead?", e); + } + } + + /** + * Remove all records about all hosts for your package. + * <ul> + * <li>Call this when initializing your database, as it might be because of a data wipe.</li> + * <li>Call this to have the AppWidget manager release all resources associated with your + * host. Any future calls about this host will cause the records to be re-allocated.</li> + * </ul> + */ + public static void deleteAllHosts() { + try { + sService.deleteAllHosts(); + } + catch (RemoteException e) { + throw new RuntimeException("system server dead?", e); + } + } + + public final AppWidgetHostView createView(Context context, int appWidgetId, + AppWidgetProviderInfo appWidget) { + AppWidgetHostView view = onCreateView(context, appWidgetId, appWidget); + view.setAppWidget(appWidgetId, appWidget); + synchronized (mViews) { + mViews.put(appWidgetId, view); + } + RemoteViews views = null; + try { + views = sService.getAppWidgetViews(appWidgetId); + } catch (RemoteException e) { + throw new RuntimeException("system server dead?", e); + } + view.updateAppWidget(views); + return view; + } + + /** + * Called to create the AppWidgetHostView. Override to return a custom subclass if you + * need it. {@more} + */ + protected AppWidgetHostView onCreateView(Context context, int appWidgetId, + AppWidgetProviderInfo appWidget) { + return new AppWidgetHostView(context); + } + + /** + * Called when the AppWidget provider for a AppWidget has been upgraded to a new apk. + */ + protected void onProviderChanged(int appWidgetId, AppWidgetProviderInfo appWidget) { + } + + void updateAppWidgetView(int appWidgetId, RemoteViews views) { + AppWidgetHostView v; + synchronized (mViews) { + v = mViews.get(appWidgetId); + } + if (v != null) { + v.updateAppWidget(views); + } + } +} + + diff --git a/core/java/android/appwidget/AppWidgetHostView.java b/core/java/android/appwidget/AppWidgetHostView.java new file mode 100644 index 0000000..be0f96e --- /dev/null +++ b/core/java/android/appwidget/AppWidgetHostView.java @@ -0,0 +1,312 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.appwidget; + +import android.content.Context; +import android.content.pm.PackageManager; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.os.Handler; +import android.os.Message; +import android.os.SystemClock; +import android.util.Config; +import android.util.Log; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.animation.Animation; +import android.widget.FrameLayout; +import android.widget.RemoteViews; +import android.widget.TextView; + +/** + * Provides the glue to show AppWidget views. This class offers automatic animation + * between updates, and will try recycling old views for each incoming + * {@link RemoteViews}. + */ +public class AppWidgetHostView extends FrameLayout { + static final String TAG = "AppWidgetHostView"; + static final boolean LOGD = false; + static final boolean CROSSFADE = false; + + static final int VIEW_MODE_NOINIT = 0; + static final int VIEW_MODE_CONTENT = 1; + static final int VIEW_MODE_ERROR = 2; + static final int VIEW_MODE_DEFAULT = 3; + + static final int FADE_DURATION = 1000; + + // When we're inflating the initialLayout for a AppWidget, we only allow + // views that are allowed in RemoteViews. + static final LayoutInflater.Filter sInflaterFilter = new LayoutInflater.Filter() { + public boolean onLoadClass(Class clazz) { + return clazz.isAnnotationPresent(RemoteViews.RemoteView.class); + } + }; + + Context mContext; + + int mAppWidgetId; + AppWidgetProviderInfo mInfo; + View mView; + int mViewMode = VIEW_MODE_NOINIT; + int mLayoutId = -1; + long mFadeStartTime = -1; + Bitmap mOld; + Paint mOldPaint = new Paint(); + + /** + * Create a host view. Uses default fade animations. + */ + public AppWidgetHostView(Context context) { + this(context, android.R.anim.fade_in, android.R.anim.fade_out); + } + + /** + * Create a host view. Uses specified animations when pushing + * {@link #updateAppWidget(RemoteViews)}. + * + * @param animationIn Resource ID of in animation to use + * @param animationOut Resource ID of out animation to use + */ + public AppWidgetHostView(Context context, int animationIn, int animationOut) { + super(context); + mContext = context; + } + + /** + * Set the AppWidget that will be displayed by this view. + */ + public void setAppWidget(int appWidgetId, AppWidgetProviderInfo info) { + mAppWidgetId = appWidgetId; + mInfo = info; + } + + public int getAppWidgetId() { + return mAppWidgetId; + } + + public AppWidgetProviderInfo getAppWidgetInfo() { + return mInfo; + } + + /** + * Process a set of {@link RemoteViews} coming in as an update from the + * AppWidget provider. Will animate into these new views as needed. + */ + public void updateAppWidget(RemoteViews remoteViews) { + if (LOGD) Log.d(TAG, "updateAppWidget called mOld=" + mOld); + + boolean recycled = false; + View content = null; + Exception exception = null; + + // Capture the old view into a bitmap so we can do the crossfade. + if (CROSSFADE) { + if (mFadeStartTime < 0) { + if (mView != null) { + final int width = mView.getWidth(); + final int height = mView.getHeight(); + try { + mOld = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); + } catch (OutOfMemoryError e) { + // we just won't do the fade + mOld = null; + } + if (mOld != null) { + //mView.drawIntoBitmap(mOld); + } + } + } + } + + if (remoteViews == null) { + if (mViewMode == VIEW_MODE_DEFAULT) { + // We've already done this -- nothing to do. + return; + } + content = getDefaultView(); + mLayoutId = -1; + mViewMode = VIEW_MODE_DEFAULT; + } else { + int layoutId = remoteViews.getLayoutId(); + + // If our stale view has been prepared to match active, and the new + // layout matches, try recycling it + if (content == null && layoutId == mLayoutId) { + try { + remoteViews.reapply(mContext, mView); + content = mView; + recycled = true; + if (LOGD) Log.d(TAG, "was able to recycled existing layout"); + } catch (RuntimeException e) { + exception = e; + } + } + + // Try normal RemoteView inflation + if (content == null) { + try { + content = remoteViews.apply(mContext, this); + if (LOGD) Log.d(TAG, "had to inflate new layout"); + } catch (RuntimeException e) { + exception = e; + } + } + + mLayoutId = layoutId; + mViewMode = VIEW_MODE_CONTENT; + } + + if (content == null) { + if (mViewMode == VIEW_MODE_ERROR) { + // We've already done this -- nothing to do. + return ; + } + Log.w(TAG, "updateAppWidget couldn't find any view, using error view", exception); + content = getErrorView(); + mViewMode = VIEW_MODE_ERROR; + } + + if (!recycled) { + prepareView(content); + addView(content); + } + + if (mView != content) { + removeView(mView); + mView = content; + } + + if (CROSSFADE) { + if (mFadeStartTime < 0) { + // if there is already an animation in progress, don't do anything -- + // the new view will pop in on top of the old one during the cross fade, + // and that looks okay. + mFadeStartTime = SystemClock.uptimeMillis(); + invalidate(); + } + } + } + + protected boolean drawChild(Canvas canvas, View child, long drawingTime) { + if (CROSSFADE) { + int alpha; + int l = child.getLeft(); + int t = child.getTop(); + if (mFadeStartTime > 0) { + alpha = (int)(((drawingTime-mFadeStartTime)*255)/FADE_DURATION); + if (alpha > 255) { + alpha = 255; + } + Log.d(TAG, "drawChild alpha=" + alpha + " l=" + l + " t=" + t + + " w=" + child.getWidth()); + if (alpha != 255 && mOld != null) { + mOldPaint.setAlpha(255-alpha); + //canvas.drawBitmap(mOld, l, t, mOldPaint); + } + } else { + alpha = 255; + } + int restoreTo = canvas.saveLayerAlpha(l, t, child.getWidth(), child.getHeight(), alpha, + Canvas.HAS_ALPHA_LAYER_SAVE_FLAG | Canvas.CLIP_TO_LAYER_SAVE_FLAG); + boolean rv = super.drawChild(canvas, child, drawingTime); + canvas.restoreToCount(restoreTo); + if (alpha < 255) { + invalidate(); + } else { + mFadeStartTime = -1; + if (mOld != null) { + mOld.recycle(); + mOld = null; + } + } + return rv; + } else { + return super.drawChild(canvas, child, drawingTime); + } + } + + /** + * Prepare the given view to be shown. This might include adjusting + * {@link FrameLayout.LayoutParams} before inserting. + */ + protected void prepareView(View view) { + // Take requested dimensions from parent, but apply default gravity. + ViewGroup.LayoutParams requested = view.getLayoutParams(); + if (requested == null) { + requested = new FrameLayout.LayoutParams(LayoutParams.FILL_PARENT, + LayoutParams.FILL_PARENT); + } + + FrameLayout.LayoutParams params = + new FrameLayout.LayoutParams(requested.width, requested.height); + params.gravity = Gravity.CENTER; + view.setLayoutParams(params); + } + + /** + * Inflate and return the default layout requested by AppWidget provider. + */ + protected View getDefaultView() { + View defaultView = null; + Exception exception = null; + + try { + if (mInfo != null) { + Context theirContext = mContext.createPackageContext( + mInfo.provider.getPackageName(), 0 /* no flags */); + LayoutInflater inflater = (LayoutInflater) + theirContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + inflater = inflater.cloneInContext(theirContext); + inflater.setFilter(sInflaterFilter); + defaultView = inflater.inflate(mInfo.initialLayout, this, false); + } else { + Log.w(TAG, "can't inflate defaultView because mInfo is missing"); + } + } catch (PackageManager.NameNotFoundException e) { + exception = e; + } catch (RuntimeException e) { + exception = e; + } + + if (exception != null && LOGD) { + Log.w(TAG, "Error inflating AppWidget " + mInfo, exception); + } + + if (defaultView == null) { + if (LOGD) Log.d(TAG, "getDefaultView couldn't find any view, so inflating error"); + defaultView = getErrorView(); + } + + return defaultView; + } + + /** + * Inflate and return a view that represents an error state. + */ + protected View getErrorView() { + TextView tv = new TextView(mContext); + tv.setText(com.android.internal.R.string.gadget_host_error_inflating); + // TODO: get this color from somewhere. + tv.setBackgroundColor(Color.argb(127, 0, 0, 0)); + return tv; + } +} diff --git a/core/java/android/appwidget/AppWidgetManager.java b/core/java/android/appwidget/AppWidgetManager.java new file mode 100644 index 0000000..3b10ed2 --- /dev/null +++ b/core/java/android/appwidget/AppWidgetManager.java @@ -0,0 +1,320 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.appwidget; + +import android.content.ComponentName; +import android.content.Context; +import android.os.IBinder; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.util.Log; +import android.widget.RemoteViews; + +import com.android.internal.appwidget.IAppWidgetService; + +import java.lang.ref.WeakReference; +import java.util.List; +import java.util.WeakHashMap; + +/** + * Updates AppWidget state; gets information about installed AppWidget providers and other + * AppWidget related state. + */ +public class AppWidgetManager { + static final String TAG = "AppWidgetManager"; + + /** + * Send this from your {@link AppWidgetHost} activity when you want to pick an AppWidget to display. + * The AppWidget picker activity will be launched. + * <p> + * You must supply the following extras: + * <table> + * <tr> + * <td>{@link #EXTRA_APPWIDGET_ID}</td> + * <td>A newly allocated appWidgetId, which will be bound to the AppWidget provider + * once the user has selected one.</td> + * </tr> + * </table> + * + * <p> + * The system will respond with an onActivityResult call with the following extras in + * the intent: + * <table> + * <tr> + * <td>{@link #EXTRA_APPWIDGET_ID}</td> + * <td>The appWidgetId that you supplied in the original intent.</td> + * </tr> + * </table> + * <p> + * When you receive the result from the AppWidget pick activity, if the resultCode is + * {@link android.app.Activity#RESULT_OK}, an AppWidget has been selected. You should then + * check the AppWidgetProviderInfo for the returned AppWidget, and if it has one, launch its configuration + * activity. If {@link android.app.Activity#RESULT_CANCELED} is returned, you should delete + * the appWidgetId. + * + * @see #ACTION_APPWIDGET_CONFIGURE + */ + public static final String ACTION_APPWIDGET_PICK = "android.appwidget.action.APPWIDGET_PICK"; + + /** + * Sent when it is time to configure your AppWidget while it is being added to a host. + * This action is not sent as a broadcast to the AppWidget provider, but as a startActivity + * to the activity specified in the {@link AppWidgetProviderInfo AppWidgetProviderInfo meta-data}. + * + * <p> + * The intent will contain the following extras: + * <table> + * <tr> + * <td>{@link #EXTRA_APPWIDGET_ID}</td> + * <td>The appWidgetId to configure.</td> + * </tr> + * </table> + * + * <p>If you return {@link android.app.Activity#RESULT_OK} using + * {@link android.app.Activity#setResult Activity.setResult()}, the AppWidget will be added, + * and you will receive an {@link #ACTION_APPWIDGET_UPDATE} broadcast for this AppWidget. + * If you return {@link android.app.Activity#RESULT_CANCELED}, the host will cancel the add + * and not display this AppWidget, and you will receive a {@link #ACTION_APPWIDGET_DELETED} broadcast. + */ + public static final String ACTION_APPWIDGET_CONFIGURE = "android.appwidget.action.APPWIDGET_CONFIGURE"; + + /** + * An intent extra that contains one appWidgetId. + * <p> + * The value will be an int that can be retrieved like this: + * {@sample frameworks/base/tests/appwidgets/AppWidgetHostTest/src/com/android/tests/appwidgethost/AppWidgetHostActivity.java getExtra_EXTRA_APPWIDGET_ID} + */ + public static final String EXTRA_APPWIDGET_ID = "appWidgetId"; + + /** + * An intent extra that contains multiple appWidgetIds. + * <p> + * The value will be an int array that can be retrieved like this: + * {@sample frameworks/base/tests/appwidgets/AppWidgetHostTest/src/com/android/tests/appwidgethost/TestAppWidgetProvider.java getExtra_EXTRA_APPWIDGET_IDS} + */ + public static final String EXTRA_APPWIDGET_IDS = "appWidgetIds"; + + /** + * A sentiel value that the AppWidget manager will never return as a appWidgetId. + */ + public static final int INVALID_APPWIDGET_ID = 0; + + /** + * Sent when it is time to update your AppWidget. + * + * <p>This may be sent in response to a new instance for this AppWidget provider having + * been instantiated, the requested {@link AppWidgetProviderInfo#updatePeriodMillis update interval} + * having lapsed, or the system booting. + * + * <p> + * The intent will contain the following extras: + * <table> + * <tr> + * <td>{@link #EXTRA_APPWIDGET_IDS}</td> + * <td>The appWidgetIds to update. This may be all of the AppWidgets created for this + * provider, or just a subset. The system tries to send updates for as few AppWidget + * instances as possible.</td> + * </tr> + * </table> + * + * @see AppWidgetProvider#onUpdate AppWidgetProvider.onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) + */ + public static final String ACTION_APPWIDGET_UPDATE = "android.appwidget.action.APPWIDGET_UPDATE"; + + /** + * Sent when an instance of an AppWidget is deleted from its host. + * + * @see AppWidgetProvider#onDeleted AppWidgetProvider.onDeleted(Context context, int[] appWidgetIds) + */ + public static final String ACTION_APPWIDGET_DELETED = "android.appwidget.action.APPWIDGET_DELETED"; + + /** + * Sent when an instance of an AppWidget is removed from the last host. + * + * @see AppWidgetProvider#onEnabled AppWidgetProvider.onEnabled(Context context) + */ + public static final String ACTION_APPWIDGET_DISABLED = "android.appwidget.action.APPWIDGET_DISABLED"; + + /** + * Sent when an instance of an AppWidget is added to a host for the first time. + * This broadcast is sent at boot time if there is a AppWidgetHost installed with + * an instance for this provider. + * + * @see AppWidgetProvider#onEnabled AppWidgetProvider.onEnabled(Context context) + */ + public static final String ACTION_APPWIDGET_ENABLED = "android.appwidget.action.APPWIDGET_ENABLED"; + + /** + * Field for the manifest meta-data tag. + * + * @see AppWidgetProviderInfo + */ + public static final String META_DATA_APPWIDGET_PROVIDER = "android.appwidget.provider"; + + static WeakHashMap<Context, WeakReference<AppWidgetManager>> sManagerCache = new WeakHashMap(); + static IAppWidgetService sService; + + Context mContext; + + /** + * Get the AppWidgetManager instance to use for the supplied {@link android.content.Context + * Context} object. + */ + public static AppWidgetManager getInstance(Context context) { + synchronized (sManagerCache) { + if (sService == null) { + IBinder b = ServiceManager.getService(Context.APPWIDGET_SERVICE); + sService = IAppWidgetService.Stub.asInterface(b); + } + + WeakReference<AppWidgetManager> ref = sManagerCache.get(context); + AppWidgetManager result = null; + if (ref != null) { + result = ref.get(); + } + if (result == null) { + result = new AppWidgetManager(context); + sManagerCache.put(context, new WeakReference(result)); + } + return result; + } + } + + private AppWidgetManager(Context context) { + mContext = context; + } + + /** + * Set the RemoteViews to use for the specified appWidgetIds. + * + * <p> + * It is okay to call this method both inside an {@link #ACTION_APPWIDGET_UPDATE} broadcast, + * and outside of the handler. + * This method will only work when called from the uid that owns the AppWidget provider. + * + * @param appWidgetIds The AppWidget instances for which to set the RemoteViews. + * @param views The RemoteViews object to show. + */ + public void updateAppWidget(int[] appWidgetIds, RemoteViews views) { + try { + sService.updateAppWidgetIds(appWidgetIds, views); + } + catch (RemoteException e) { + throw new RuntimeException("system server dead?", e); + } + } + + /** + * Set the RemoteViews to use for the specified appWidgetId. + * + * <p> + * It is okay to call this method both inside an {@link #ACTION_APPWIDGET_UPDATE} broadcast, + * and outside of the handler. + * This method will only work when called from the uid that owns the AppWidget provider. + * + * @param appWidgetId The AppWidget instance for which to set the RemoteViews. + * @param views The RemoteViews object to show. + */ + public void updateAppWidget(int appWidgetId, RemoteViews views) { + updateAppWidget(new int[] { appWidgetId }, views); + } + + /** + * Set the RemoteViews to use for all AppWidget instances for the supplied AppWidget provider. + * + * <p> + * It is okay to call this method both inside an {@link #ACTION_APPWIDGET_UPDATE} broadcast, + * and outside of the handler. + * This method will only work when called from the uid that owns the AppWidget provider. + * + * @param provider The {@link ComponentName} for the {@link + * android.content.BroadcastReceiver BroadcastReceiver} provider + * for your AppWidget. + * @param views The RemoteViews object to show. + */ + public void updateAppWidget(ComponentName provider, RemoteViews views) { + try { + sService.updateAppWidgetProvider(provider, views); + } + catch (RemoteException e) { + throw new RuntimeException("system server dead?", e); + } + } + + /** + * Return a list of the AppWidget providers that are currently installed. + */ + public List<AppWidgetProviderInfo> getInstalledProviders() { + try { + return sService.getInstalledProviders(); + } + catch (RemoteException e) { + throw new RuntimeException("system server dead?", e); + } + } + + /** + * Get the available info about the AppWidget. + * + * @return A appWidgetId. If the appWidgetId has not been bound to a provider yet, or + * you don't have access to that appWidgetId, null is returned. + */ + public AppWidgetProviderInfo getAppWidgetInfo(int appWidgetId) { + try { + return sService.getAppWidgetInfo(appWidgetId); + } + catch (RemoteException e) { + throw new RuntimeException("system server dead?", e); + } + } + + /** + * Set the component for a given appWidgetId. + * + * <p class="note">You need the APPWIDGET_LIST permission. This method is to be used by the + * AppWidget picker. + * + * @param appWidgetId The AppWidget instance for which to set the RemoteViews. + * @param provider The {@link android.content.BroadcastReceiver} that will be the AppWidget + * provider for this AppWidget. + */ + public void bindAppWidgetId(int appWidgetId, ComponentName provider) { + try { + sService.bindAppWidgetId(appWidgetId, provider); + } + catch (RemoteException e) { + throw new RuntimeException("system server dead?", e); + } + } + + /** + * Get the list of appWidgetIds that have been bound to the given AppWidget + * provider. + * + * @param provider The {@link android.content.BroadcastReceiver} that is the + * AppWidget provider to find appWidgetIds for. + */ + public int[] getAppWidgetIds(ComponentName provider) { + try { + return sService.getAppWidgetIds(provider); + } + catch (RemoteException e) { + throw new RuntimeException("system server dead?", e); + } + } +} + diff --git a/core/java/android/appwidget/AppWidgetProvider.java b/core/java/android/appwidget/AppWidgetProvider.java new file mode 100755 index 0000000..f70de9c --- /dev/null +++ b/core/java/android/appwidget/AppWidgetProvider.java @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.appwidget; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; + +/** + * A conveience class to aid in implementing an AppWidget provider. + * Everything you can do with AppWidgetProvider, you can do with a regular {@link BroadcastReceiver}. + * AppWidgetProvider merely parses the relevant fields out of the Intent that is received in + * {@link #onReceive(Context,Intent) onReceive(Context,Intent)}, and calls hook methods + * with the received extras. + * + * <p>Extend this class and override one or more of the {@link #onUpdate}, {@link #onDeleted}, + * {@link #onEnabled} or {@link #onDisabled} methods to implement your own AppWidget functionality. + * + * <h3>Sample Code</h3> + * For an example of how to write a AppWidget provider, see the + * <a href="{@toroot}reference/android/appwidget/package-descr.html#providers">android.appwidget + * package overview</a>. + */ +public class AppWidgetProvider extends BroadcastReceiver { + /** + * Constructor to initialize AppWidgetProvider. + */ + public AppWidgetProvider() { + } + + /** + * Implements {@link BroadcastReceiver#onReceive} to dispatch calls to the various + * other methods on AppWidgetProvider. + * + * @param context The Context in which the receiver is running. + * @param intent The Intent being received. + */ + // BEGIN_INCLUDE(onReceive) + public void onReceive(Context context, Intent intent) { + // Protect against rogue update broadcasts (not really a security issue, + // just filter bad broacasts out so subclasses are less likely to crash). + String action = intent.getAction(); + if (AppWidgetManager.ACTION_APPWIDGET_UPDATE.equals(action)) { + Bundle extras = intent.getExtras(); + if (extras != null) { + int[] appWidgetIds = extras.getIntArray(AppWidgetManager.EXTRA_APPWIDGET_IDS); + if (appWidgetIds != null && appWidgetIds.length > 0) { + this.onUpdate(context, AppWidgetManager.getInstance(context), appWidgetIds); + } + } + } + else if (AppWidgetManager.ACTION_APPWIDGET_DELETED.equals(action)) { + Bundle extras = intent.getExtras(); + if (extras != null) { + int[] appWidgetIds = extras.getIntArray(AppWidgetManager.EXTRA_APPWIDGET_IDS); + if (appWidgetIds != null && appWidgetIds.length > 0) { + this.onDeleted(context, appWidgetIds); + } + } + } + else if (AppWidgetManager.ACTION_APPWIDGET_ENABLED.equals(action)) { + this.onEnabled(context); + } + else if (AppWidgetManager.ACTION_APPWIDGET_DISABLED.equals(action)) { + this.onDisabled(context); + } + } + // END_INCLUDE(onReceive) + + /** + * Called in response to the {@link AppWidgetManager#ACTION_APPWIDGET_UPDATE} broadcast when + * this AppWidget provider is being asked to provide {@link android.widget.RemoteViews RemoteViews} + * for a set of AppWidgets. Override this method to implement your own AppWidget functionality. + * + * {@more} + * + * @param context The {@link android.content.Context Context} in which this receiver is + * running. + * @param appWidgetManager A {@link AppWidgetManager} object you can call {@link + * AppWidgetManager#updateAppWidget} on. + * @param appWidgetIds The appWidgetIds for which an update is needed. Note that this + * may be all of the AppWidget instances for this provider, or just + * a subset of them. + * + * @see AppWidgetManager#ACTION_APPWIDGET_UPDATE + */ + public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { + } + + /** + * Called in response to the {@link AppWidgetManager#ACTION_APPWIDGET_DELETED} broadcast when + * one or more AppWidget instances have been deleted. Override this method to implement + * your own AppWidget functionality. + * + * {@more} + * + * @param context The {@link android.content.Context Context} in which this receiver is + * running. + * @param appWidgetIds The appWidgetIds that have been deleted from their host. + * + * @see AppWidgetManager#ACTION_APPWIDGET_DELETED + */ + public void onDeleted(Context context, int[] appWidgetIds) { + } + + /** + * Called in response to the {@link AppWidgetManager#ACTION_APPWIDGET_ENABLED} broadcast when + * the a AppWidget for this provider is instantiated. Override this method to implement your + * own AppWidget functionality. + * + * {@more} + * When the last AppWidget for this provider is deleted, + * {@link AppWidgetManager#ACTION_APPWIDGET_DISABLED} is sent by the AppWidget manager, and + * {@link #onDisabled} is called. If after that, an AppWidget for this provider is created + * again, onEnabled() will be called again. + * + * @param context The {@link android.content.Context Context} in which this receiver is + * running. + * + * @see AppWidgetManager#ACTION_APPWIDGET_ENABLED + */ + public void onEnabled(Context context) { + } + + /** + * Called in response to the {@link AppWidgetManager#ACTION_APPWIDGET_DISABLED} broadcast, which + * is sent when the last AppWidget instance for this provider is deleted. Override this method + * to implement your own AppWidget functionality. + * + * {@more} + * + * @param context The {@link android.content.Context Context} in which this receiver is + * running. + * + * @see AppWidgetManager#ACTION_APPWIDGET_DISABLED + */ + public void onDisabled(Context context) { + } +} diff --git a/core/java/android/appwidget/AppWidgetProviderInfo.aidl b/core/java/android/appwidget/AppWidgetProviderInfo.aidl new file mode 100644 index 0000000..82b3ada --- /dev/null +++ b/core/java/android/appwidget/AppWidgetProviderInfo.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2007, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.appwidget; + +parcelable AppWidgetProviderInfo; diff --git a/core/java/android/appwidget/AppWidgetProviderInfo.java b/core/java/android/appwidget/AppWidgetProviderInfo.java new file mode 100644 index 0000000..8530c35 --- /dev/null +++ b/core/java/android/appwidget/AppWidgetProviderInfo.java @@ -0,0 +1,168 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.appwidget; + +import android.os.Parcel; +import android.os.Parcelable; +import android.content.ComponentName; + +/** + * Describes the meta data for an installed AppWidget provider. The fields in this class + * correspond to the fields in the <code><appwidget-provider></code> xml tag. + */ +public class AppWidgetProviderInfo implements Parcelable { + /** + * Identity of this AppWidget component. This component should be a {@link + * android.content.BroadcastReceiver}, and it will be sent the AppWidget intents + * {@link android.appwidget as described in the AppWidget package documentation}. + * + * <p>This field corresponds to the <code>android:name</code> attribute in + * the <code><receiver></code> element in the AndroidManifest.xml file. + */ + public ComponentName provider; + + /** + * Minimum width of the AppWidget, in dp. + * + * <p>This field corresponds to the <code>android:minWidth</code> attribute in + * the AppWidget meta-data file. + */ + public int minWidth; + + /** + * Minimum height of the AppWidget, in dp. + * + * <p>This field corresponds to the <code>android:minHeight</code> attribute in + * the AppWidget meta-data file. + */ + public int minHeight; + + /** + * How often, in milliseconds, that this AppWidget wants to be updated. + * The AppWidget manager may place a limit on how often a AppWidget is updated. + * + * <p>This field corresponds to the <code>android:updatePeriodMillis</code> attribute in + * the AppWidget meta-data file. + */ + public int updatePeriodMillis; + + /** + * The resource id of the initial layout for this AppWidget. This should be + * displayed until the RemoteViews for the AppWidget is available. + * + * <p>This field corresponds to the <code>android:initialLayout</code> attribute in + * the AppWidget meta-data file. + */ + public int initialLayout; + + /** + * The activity to launch that will configure the AppWidget. + * + * <p>This class name of field corresponds to the <code>android:configure</code> attribute in + * the AppWidget meta-data file. The package name always corresponds to the package containing + * the AppWidget provider. + */ + public ComponentName configure; + + /** + * The label to display to the user in the AppWidget picker. If not supplied in the + * xml, the application label will be used. + * + * <p>This field corresponds to the <code>android:label</code> attribute in + * the <code><receiver></code> element in the AndroidManifest.xml file. + */ + public String label; + + /** + * The icon to display for this AppWidget in the AppWidget picker. If not supplied in the + * xml, the application icon will be used. + * + * <p>This field corresponds to the <code>android:icon</code> attribute in + * the <code><receiver></code> element in the AndroidManifest.xml file. + */ + public int icon; + + public AppWidgetProviderInfo() { + } + + /** + * Unflatten the AppWidgetProviderInfo from a parcel. + */ + public AppWidgetProviderInfo(Parcel in) { + if (0 != in.readInt()) { + this.provider = new ComponentName(in); + } + this.minWidth = in.readInt(); + this.minHeight = in.readInt(); + this.updatePeriodMillis = in.readInt(); + this.initialLayout = in.readInt(); + if (0 != in.readInt()) { + this.configure = new ComponentName(in); + } + this.label = in.readString(); + this.icon = in.readInt(); + } + + + public void writeToParcel(android.os.Parcel out, int flags) { + if (this.provider != null) { + out.writeInt(1); + this.provider.writeToParcel(out, flags); + } else { + out.writeInt(0); + } + out.writeInt(this.minWidth); + out.writeInt(this.minHeight); + out.writeInt(this.updatePeriodMillis); + out.writeInt(this.initialLayout); + if (this.configure != null) { + out.writeInt(1); + this.configure.writeToParcel(out, flags); + } else { + out.writeInt(0); + } + out.writeString(this.label); + out.writeInt(this.icon); + } + + public int describeContents() { + return 0; + } + + /** + * Parcelable.Creator that instantiates AppWidgetProviderInfo objects + */ + public static final Parcelable.Creator<AppWidgetProviderInfo> CREATOR + = new Parcelable.Creator<AppWidgetProviderInfo>() + { + public AppWidgetProviderInfo createFromParcel(Parcel parcel) + { + return new AppWidgetProviderInfo(parcel); + } + + public AppWidgetProviderInfo[] newArray(int size) + { + return new AppWidgetProviderInfo[size]; + } + }; + + public String toString() { + return "AppWidgetProviderInfo(provider=" + this.provider + ")"; + } +} + + diff --git a/core/java/android/appwidget/package.html b/core/java/android/appwidget/package.html new file mode 100644 index 0000000..b6cd9c7 --- /dev/null +++ b/core/java/android/appwidget/package.html @@ -0,0 +1,136 @@ +<body> +<p>Android allows applications to publish views to be embedded in other applications. These +views are called widgets, and are published by "AppWidget providers." The component that can +contain widgets is called a "AppWidget host." +</p> +<h3><a href="package-descr.html#providers">AppWidget Providers</a></h3> +<ul> + <li><a href="package-descr.html#provider_manifest">Declaring a widget in the AndroidManifest</a></li> + <li><a href="package-descr.html#provider_meta_data">Adding the AppWidgetProviderInfo meta-data</a></li> + <li><a href="package-descr.html#provider_AppWidgetProvider">Using the AppWidgetProvider class</a></li> + <li><a href="package-descr.html#provider_configuration">AppWidget Configuration UI</a></li> + <li><a href="package-descr.html#provider_broadcasts">AppWidget Broadcast Intents</a></li> +</ul> +<h3><a href="package-descr.html#">AppWidget Hosts</a></h3> + + +{@more} + + +<h2><a name="providers"></a>AppWidget Providers</h2> +<p> +Any application can publish widgets. All an application needs to do to publish a widget is +to have a {@link android.content.BroadcastReceiver} that receives the {@link +android.appwidget.AppWidgetManager#ACTION_APPWIDGET_UPDATE AppWidgetManager.ACTION_APPWIDGET_UPDATE} intent, +and provide some meta-data about the widget. Android provides the +{@link android.appwidget.AppWidgetProvider} class, which extends BroadcastReceiver, as a convenience +class to aid in handling the broadcasts. + +<h3><a name="provider_manifest"></a>Declaring a widget in the AndroidManifest</h3> + +<p> +First, declare the {@link android.content.BroadcastReceiver} in your application's +<code>AndroidManifest.xml</code> file. + +{@sample frameworks/base/tests/appwidgets/AppWidgetHostTest/AndroidManifest.xml AppWidgetProvider} + +<p> +The <b><code><receiver></b> element has the following attributes: +<ul> + <li><b><code>android:name</code> -</b> which specifies the + {@link android.content.BroadcastReceiver} or {@link android.appwidget.AppWidgetProvider} + class.</li> + <li><b><code>android:label</code> -</b> which specifies the string resource that + will be shown by the widget picker as the label.</li> + <li><b><code>android:icon</code> -</b> which specifies the drawable resource that + will be shown by the widget picker as the icon.</li> +</ul> + +<p> +The <b><code><intent-filter></b> element tells the {@link android.content.pm.PackageManager} +that this {@link android.content.BroadcastReceiver} receives the {@link +android.appwidget.AppWidgetManager#ACTION_APPWIDGET_UPDATE AppWidgetManager.ACTION_APPWIDGET_UPDATE} broadcast. +The widget manager will send other broadcasts directly to your widget provider as required. +It is only necessary to explicitly declare that you accept the {@link +android.appwidget.AppWidgetManager#ACTION_APPWIDGET_UPDATE AppWidgetManager.ACTION_APPWIDGET_UPDATE} broadcast. + +<p> +The <b><code><meta-data></code></b> element tells the widget manager which xml resource to +read to find the {@link android.appwidget.AppWidgetProviderInfo} for your widget provider. It has the following +attributes: +<ul> + <li><b><code>android:name="android.appwidget.provider"</code> -</b> identifies this meta-data + as the {@link android.appwidget.AppWidgetProviderInfo} descriptor.</li> + <li><b><code>android:resource</code> -</b> is the xml resource to use as that descriptor.</li> +</ul> + + +<h3><a name="provider_meta_data"></a>Adding the {@link android.appwidget.AppWidgetProviderInfo AppWidgetProviderInfo} meta-data</h3> + +<p> +For a widget, the values in the {@link android.appwidget.AppWidgetProviderInfo} structure are supplied +in an XML resource. In the example above, the xml resource is referenced with +<code>android:resource="@xml/appwidget_info"</code>. That XML file would go in your application's +directory at <code>res/xml/appwidget_info.xml</code>. Here is a simple example. + +{@sample frameworks/base/tests/appwidgets/AppWidgetHostTest/res/xml/appwidget_info.xml AppWidgetProviderInfo} + +<p> +The attributes are as documented in the {@link android.appwidget.AppWidgetProviderInfo GagetInfo} class. (86400000 milliseconds means once per day) + + +<h3><a name="provider_AppWidgetProvider"></a>Using the {@link android.appwidget.AppWidgetProvider AppWidgetProvider} class</h3> + +<p>The AppWidgetProvider class is the easiest way to handle the widget provider intent broadcasts. +See the <code>src/com/example/android/apis/appwidget/ExampleAppWidgetProvider.java</code> +sample class in ApiDemos for an example. + +<p class="note">Keep in mind that since the the AppWidgetProvider is a BroadcastReceiver, +your process is not guaranteed to keep running after the callback methods return. See +<a href="../../../guide/topics/fundamentals.html#broadlife">Application Fundamentals > +Broadcast Receiver Lifecycle</a> for more information. + + + +<h3><a name="provider_configuration"></a>AppWidget Configuration UI</h3> + +<p> +Widget hosts have the ability to start a configuration activity when a widget is instantiated. +The activity should be declared as normal in AndroidManifest.xml, and it should be listed in +the AppWidgetProviderInfo XML file in the <code>android:configure</code> attribute. + +<p>The activity you specified will be launched with the {@link +android.appwidget.AppWidgetManager#ACTION_APPWIDGET_CONFIGURE} action. See the documentation for that +action for more info. + +<p>See the <code>src/com/example/android/apis/appwidget/ExampleAppWidgetConfigure.java</code> +sample class in ApiDemos for an example. + + + +<h3><a name="providers_broadcasts"></a>AppWidget Broadcast Intents</h3> + +<p>{@link android.appwidget.AppWidgetProvider} is just a convenience class. If you would like +to receive the widget broadcasts directly, you can. The four intents you need to care about are: +<ul> + <li>{@link android.appwidget.AppWidgetManager#ACTION_APPWIDGET_UPDATE}</li> + <li>{@link android.appwidget.AppWidgetManager#ACTION_APPWIDGET_DELETED}</li> + <li>{@link android.appwidget.AppWidgetManager#ACTION_APPWIDGET_ENABLED}</li> + <li>{@link android.appwidget.AppWidgetManager#ACTION_APPWIDGET_DISABLED}</li> +</ul> + +<p>By way of example, the implementation of +{@link android.appwidget.AppWidgetProvider#onReceive} is quite simple:</p> + +{@sample frameworks/base/core/java/android/appwidget/AppWidgetProvider.java onReceive} + + +<h2>AppWidget Hosts</h3> +<p>Widget hosts are the containers in which widgets can be placed. Most of the look and feel +details are left up to the widget hosts. For example, the home screen has one way of viewing +widgets, but the lock screen could also contain widgets, and it would have a different way of +adding, removing and otherwise managing widgets.</p> +<p>For more information on implementing your own widget host, see the +{@link android.appwidget.AppWidgetHost AppWidgetHost} class.</p> +</body> + |