From e667a7add46a6389c64f2105bd33943cfe6a3fa4 Mon Sep 17 00:00:00 2001 From: Jim Miller Date: Thu, 9 Aug 2012 19:22:32 -0700 Subject: [PATCH] Update AppWidgetHost with better support for OnClickHandlers This updates AppWidgetHost and AppWidgetHostView to do a better job at refreshing widgets and their host views. It now allows an OnClickHandler to be specified when creating the AppWidgetHost which allows it to correctly update AppWidgetHostViews when needed. Change-Id: I710c1d00a8d145bf3a9fd5f5691885bec9d1c7e4 --- .../java/android/appwidget/AppWidgetHost.java | 24 +++-- .../android/appwidget/AppWidgetHostView.java | 56 ++++++++---- core/java/android/widget/RemoteViews.java | 91 +++++++++---------- 3 files changed, 101 insertions(+), 70 deletions(-) diff --git a/core/java/android/appwidget/AppWidgetHost.java b/core/java/android/appwidget/AppWidgetHost.java index 2c19c0c54536a..c76bf91fcae72 100644 --- a/core/java/android/appwidget/AppWidgetHost.java +++ b/core/java/android/appwidget/AppWidgetHost.java @@ -29,6 +29,7 @@ import android.os.ServiceManager; import android.util.DisplayMetrics; import android.util.TypedValue; import android.widget.RemoteViews; +import android.widget.RemoteViews.OnClickHandler; import com.android.internal.appwidget.IAppWidgetHost; import com.android.internal.appwidget.IAppWidgetService; @@ -83,7 +84,7 @@ public class AppWidgetHost { public UpdateHandler(Looper looper) { super(looper); } - + public void handleMessage(Message msg) { switch (msg.what) { case HANDLE_UPDATE: { @@ -105,16 +106,25 @@ public class AppWidgetHost { } } } - + Handler mHandler; int mHostId; Callbacks mCallbacks = new Callbacks(); final HashMap mViews = new HashMap(); + private OnClickHandler mOnClickHandler; public AppWidgetHost(Context context, int hostId) { + this(context, hostId, null); + } + + /** + * @hide + */ + public AppWidgetHost(Context context, int hostId, OnClickHandler handler) { mContext = context; mHostId = hostId; + mOnClickHandler = handler; mHandler = new UpdateHandler(context.getMainLooper()); mDisplayMetrics = context.getResources().getDisplayMetrics(); synchronized (sServiceLock) { @@ -132,7 +142,7 @@ public class AppWidgetHost { public void startListening() { int[] updatedIds; ArrayList updatedViews = new ArrayList(); - + try { if (mPackageName == null) { mPackageName = mContext.getPackageName(); @@ -180,7 +190,7 @@ public class AppWidgetHost { } /** - * Stop listening to changes for this AppWidget. + * Stop listening to changes for this AppWidget. */ public void deleteAppWidgetId(int appWidgetId) { synchronized (mViews) { @@ -235,6 +245,7 @@ public class AppWidgetHost { public final AppWidgetHostView createView(Context context, int appWidgetId, AppWidgetProviderInfo appWidget) { AppWidgetHostView view = onCreateView(context, appWidgetId, appWidget); + view.setOnClickHandler(mOnClickHandler); view.setAppWidget(appWidgetId, appWidget); synchronized (mViews) { mViews.put(appWidgetId, view); @@ -246,6 +257,7 @@ public class AppWidgetHost { throw new RuntimeException("system server dead?", e); } view.updateAppWidget(views); + return view; } @@ -255,7 +267,7 @@ public class AppWidgetHost { */ protected AppWidgetHostView onCreateView(Context context, int appWidgetId, AppWidgetProviderInfo appWidget) { - return new AppWidgetHostView(context); + return new AppWidgetHostView(context, mOnClickHandler); } /** @@ -265,7 +277,7 @@ public class AppWidgetHost { AppWidgetHostView v; // Convert complex to dp -- we are getting the AppWidgetProviderInfo from the - // AppWidgetService, which doesn't have our context, hence we need to do the + // AppWidgetService, which doesn't have our context, hence we need to do the // conversion here. appWidget.minWidth = TypedValue.complexToDimensionPixelSize(appWidget.minWidth, mDisplayMetrics); diff --git a/core/java/android/appwidget/AppWidgetHostView.java b/core/java/android/appwidget/AppWidgetHostView.java index ed95ae587de83..603ceb7626f51 100644 --- a/core/java/android/appwidget/AppWidgetHostView.java +++ b/core/java/android/appwidget/AppWidgetHostView.java @@ -44,6 +44,7 @@ import android.widget.AdapterView; import android.widget.BaseAdapter; import android.widget.FrameLayout; import android.widget.RemoteViews; +import android.widget.RemoteViews.OnClickHandler; import android.widget.RemoteViewsAdapter.RemoteAdapterConnectionCallback; import android.widget.TextView; @@ -83,7 +84,8 @@ public class AppWidgetHostView extends FrameLayout { long mFadeStartTime = -1; Bitmap mOld; Paint mOldPaint = new Paint(); - + private OnClickHandler mOnClickHandler; + /** * Create a host view. Uses default fade animations. */ @@ -91,10 +93,18 @@ public class AppWidgetHostView extends FrameLayout { this(context, android.R.anim.fade_in, android.R.anim.fade_out); } + /** + * @hide + */ + public AppWidgetHostView(Context context, OnClickHandler handler) { + this(context, android.R.anim.fade_in, android.R.anim.fade_out); + mOnClickHandler = handler; + } + /** * 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 */ @@ -108,6 +118,17 @@ public class AppWidgetHostView extends FrameLayout { setIsRootNamespace(true); } + /** + * Pass the given handler to RemoteViews when updating this widget. Unless this + * is done immediatly after construction, a call to {@link #updateAppWidget(RemoteViews)} + * should be made. + * @param handler + * @hide + */ + public void setOnClickHandler(OnClickHandler handler) { + mOnClickHandler = handler; + } + /** * Set the AppWidget that will be displayed by this view. This method also adds default padding * to widgets, as described in {@link #getDefaultPaddingForWidget(Context, ComponentName, Rect)} @@ -177,7 +198,7 @@ public class AppWidgetHostView extends FrameLayout { public int getAppWidgetId() { return mAppWidgetId; } - + public AppWidgetProviderInfo getAppWidgetInfo() { return mInfo; } @@ -281,12 +302,13 @@ public class AppWidgetHostView extends FrameLayout { * 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) { @@ -305,7 +327,7 @@ public class AppWidgetHostView extends FrameLayout { } } } - + if (remoteViews == null) { if (mViewMode == VIEW_MODE_DEFAULT) { // We've already done this -- nothing to do. @@ -324,7 +346,7 @@ public class AppWidgetHostView extends FrameLayout { // layout matches, try recycling it if (content == null && layoutId == mLayoutId) { try { - remoteViews.reapply(mContext, mView); + remoteViews.reapply(mContext, mView, mOnClickHandler); content = mView; recycled = true; if (LOGD) Log.d(TAG, "was able to recycled existing layout"); @@ -332,11 +354,11 @@ public class AppWidgetHostView extends FrameLayout { exception = e; } } - + // Try normal RemoteView inflation if (content == null) { try { - content = remoteViews.apply(mContext, this); + content = remoteViews.apply(mContext, this, mOnClickHandler); if (LOGD) Log.d(TAG, "had to inflate new layout"); } catch (RuntimeException e) { exception = e; @@ -346,7 +368,7 @@ public class AppWidgetHostView extends FrameLayout { mLayoutId = layoutId; mViewMode = VIEW_MODE_CONTENT; } - + if (content == null) { if (mViewMode == VIEW_MODE_ERROR) { // We've already done this -- nothing to do. @@ -356,7 +378,7 @@ public class AppWidgetHostView extends FrameLayout { content = getErrorView(); mViewMode = VIEW_MODE_ERROR; } - + if (!recycled) { prepareView(content); addView(content); @@ -455,7 +477,7 @@ public class AppWidgetHostView extends FrameLayout { return super.drawChild(canvas, child, drawingTime); } } - + /** * Prepare the given view to be shown. This might include adjusting * {@link FrameLayout.LayoutParams} before inserting. @@ -471,7 +493,7 @@ public class AppWidgetHostView extends FrameLayout { requested.gravity = Gravity.CENTER; view.setLayoutParams(requested); } - + /** * Inflate and return the default layout requested by AppWidget provider. */ @@ -481,7 +503,7 @@ public class AppWidgetHostView extends FrameLayout { } View defaultView = null; Exception exception = null; - + try { if (mInfo != null) { Context theirContext = mContext.createPackageContext( @@ -500,19 +522,19 @@ public class AppWidgetHostView extends FrameLayout { } catch (RuntimeException e) { exception = e; } - + if (exception != null) { Log.w(TAG, "Error inflating AppWidget " + mInfo + ": " + exception.toString()); } - + 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. */ diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java index dd05a03e2d144..dcf90e9123bc7 100644 --- a/core/java/android/widget/RemoteViews.java +++ b/core/java/android/widget/RemoteViews.java @@ -61,9 +61,9 @@ import java.util.ArrayList; * the content of the inflated hierarchy. */ public class RemoteViews implements Parcelable, Filter { - + private static final String LOG_TAG = "RemoteViews"; - + /** * The intent extra that contains the appWidgetId. * @hide @@ -71,11 +71,11 @@ public class RemoteViews implements Parcelable, Filter { static final String EXTRA_REMOTEADAPTER_APPWIDGET_ID = "remoteAdapterAppWidgetId"; /** - * The package name of the package containing the layout + * The package name of the package containing the layout * resource. (Added to the parcel) */ private final String mPackage; - + /** * The resource ID of the layout file. (Added to the parcel) */ @@ -86,7 +86,7 @@ public class RemoteViews implements Parcelable, Filter { * inflated */ private ArrayList mActions; - + /** * A class to keep track of memory usage by this RemoteViews */ @@ -522,13 +522,13 @@ public class RemoteViews implements Parcelable, Filter { .getCompatibilityInfo().applicationScale; final int[] pos = new int[2]; v.getLocationOnScreen(pos); - + final Rect rect = new Rect(); rect.left = (int) (pos[0] * appScale + 0.5f); rect.top = (int) (pos[1] * appScale + 0.5f); rect.right = (int) ((pos[0] + v.getWidth()) * appScale + 0.5f); rect.bottom = (int) ((pos[1] + v.getHeight()) * appScale + 0.5f); - + final Intent intent = new Intent(); intent.setSourceBounds(rect); handler.onClickHandler(v, pendingIntent, intent); @@ -567,7 +567,7 @@ public class RemoteViews implements Parcelable, Filter { this.filterMode = mode; this.level = level; } - + public SetDrawableParameters(Parcel parcel) { viewId = parcel.readInt(); targetBackground = parcel.readInt() != 0; @@ -581,7 +581,7 @@ public class RemoteViews implements Parcelable, Filter { } level = parcel.readInt(); } - + public void writeToParcel(Parcel dest, int flags) { dest.writeInt(TAG); dest.writeInt(viewId); @@ -596,12 +596,12 @@ public class RemoteViews implements Parcelable, Filter { } dest.writeInt(level); } - + @Override public void apply(View root, ViewGroup rootParent, OnClickHandler handler) { final View target = root.findViewById(viewId); if (target == null) return; - + // Pick the correct drawable to modify for this view Drawable targetDrawable = null; if (targetBackground) { @@ -610,7 +610,7 @@ public class RemoteViews implements Parcelable, Filter { ImageView imageView = (ImageView) target; targetDrawable = imageView.getDrawable(); } - + if (targetDrawable != null) { // Perform modifications only if values are set correctly if (alpha != -1) { @@ -634,7 +634,7 @@ public class RemoteViews implements Parcelable, Filter { public final static int TAG = 3; } - + private class ReflectionActionWithoutParams extends Action { int viewId; String methodName; @@ -938,7 +938,7 @@ public class RemoteViews implements Parcelable, Filter { out.writeString((String)this.value); break; case CHAR_SEQUENCE: - TextUtils.writeToParcel((CharSequence)this.value, out, flags); + TextUtils.writeToParcel((CharSequence)this.value, out, flags); break; case URI: out.writeInt(this.value != null ? 1 : 0); @@ -1314,7 +1314,7 @@ public class RemoteViews implements Parcelable, Filter { /** * Create a new RemoteViews object that will display the views contained * in the specified layout file. - * + * * @param packageName Name of the package that contains the layout resource * @param layoutId The id of the layout resource */ @@ -1364,7 +1364,7 @@ public class RemoteViews implements Parcelable, Filter { /** * Reads a RemoteViews object from a parcel. - * + * * @param parcel */ public RemoteViews(Parcel parcel) { @@ -1547,7 +1547,7 @@ public class RemoteViews implements Parcelable, Filter { /** * Add an action to be executed on the remote side when apply is called. - * + * * @param a The action to add */ private void addAction(Action a) { @@ -1619,7 +1619,7 @@ public class RemoteViews implements Parcelable, Filter { /** * Equivalent to calling View.setVisibility - * + * * @param viewId The id of the view whose visibility should change * @param visibility The new visibility for the view */ @@ -1629,7 +1629,7 @@ public class RemoteViews implements Parcelable, Filter { /** * Equivalent to calling TextView.setText - * + * * @param viewId The id of the view whose text should change * @param text The new text for the view */ @@ -1639,7 +1639,7 @@ public class RemoteViews implements Parcelable, Filter { /** * Equivalent to calling {@link TextView#setTextSize(int, float)} - * + * * @param viewId The id of the view whose text size should change * @param units The units of size (e.g. COMPLEX_UNIT_SP) * @param size The size of the text @@ -1649,33 +1649,29 @@ public class RemoteViews implements Parcelable, Filter { } /** - * Equivalent to calling + * Equivalent to calling * {@link TextView#setCompoundDrawablesWithIntrinsicBounds(int, int, int, int)}. * * @param viewId The id of the view whose text should change * @param left The id of a drawable to place to the left of the text, or 0 * @param top The id of a drawable to place above the text, or 0 * @param right The id of a drawable to place to the right of the text, or 0 - * @param bottom The id of a drawable to place below the text, or 0 + * @param bottom The id of a drawable to place below the text, or 0 */ public void setTextViewCompoundDrawables(int viewId, int left, int top, int right, int bottom) { addAction(new TextViewDrawableAction(viewId, false, left, top, right, bottom)); } /** - * Equivalent to calling {@link + * Equivalent to calling {@link * TextView#setCompoundDrawablesRelativeWithIntrinsicBounds(int, int, int, int)}. * * @param viewId The id of the view whose text should change - * @param start The id of a drawable to place before the text (relative to the + * @param start The id of a drawable to place before the text (relative to the * layout direction), or 0 * @param top The id of a drawable to place above the text, or 0 * @param end The id of a drawable to place after the text, or 0 -<<<<<<< HEAD - * @param bottom The id of a drawable to place below the text, or 0 -======= * @param bottom The id of a drawable to place below the text, or 0 ->>>>>>> 0a43f67e */ public void setTextViewCompoundDrawablesRelative(int viewId, int start, int top, int end, int bottom) { addAction(new TextViewDrawableAction(viewId, true, start, top, end, bottom)); @@ -1683,17 +1679,17 @@ public class RemoteViews implements Parcelable, Filter { /** * Equivalent to calling ImageView.setImageResource - * + * * @param viewId The id of the view whose drawable should change * @param srcId The new resource id for the drawable */ - public void setImageViewResource(int viewId, int srcId) { + public void setImageViewResource(int viewId, int srcId) { setInt(viewId, "setImageResource", srcId); } /** * Equivalent to calling ImageView.setImageURI - * + * * @param viewId The id of the view whose drawable should change * @param uri The Uri for the image */ @@ -1703,7 +1699,7 @@ public class RemoteViews implements Parcelable, Filter { /** * Equivalent to calling ImageView.setImageBitmap - * + * * @param viewId The id of the view whose bitmap should change * @param bitmap The new Bitmap for the drawable */ @@ -1726,7 +1722,7 @@ public class RemoteViews implements Parcelable, Filter { * {@link Chronometer#setFormat Chronometer.setFormat}, * and {@link Chronometer#start Chronometer.start()} or * {@link Chronometer#stop Chronometer.stop()}. - * + * * @param viewId The id of the {@link Chronometer} to change * @param base The time at which the timer would have read 0:00. This * time should be based off of @@ -1740,21 +1736,21 @@ public class RemoteViews implements Parcelable, Filter { setString(viewId, "setFormat", format); setBoolean(viewId, "setStarted", started); } - + /** * Equivalent to calling {@link ProgressBar#setMax ProgressBar.setMax}, * {@link ProgressBar#setProgress ProgressBar.setProgress}, and * {@link ProgressBar#setIndeterminate ProgressBar.setIndeterminate} * * If indeterminate is true, then the values for max and progress are ignored. - * + * * @param viewId The id of the {@link ProgressBar} to change * @param max The 100% value for the progress bar * @param progress The current value of the progress bar. - * @param indeterminate True if the progress bar is indeterminate, + * @param indeterminate True if the progress bar is indeterminate, * false if not. */ - public void setProgressBar(int viewId, int max, int progress, + public void setProgressBar(int viewId, int max, int progress, boolean indeterminate) { setBoolean(viewId, "setIndeterminate", indeterminate); if (!indeterminate) { @@ -1762,12 +1758,12 @@ public class RemoteViews implements Parcelable, Filter { setInt(viewId, "setProgress", progress); } } - + /** * Equivalent to calling * {@link android.view.View#setOnClickListener(android.view.View.OnClickListener)} * to launch the provided {@link PendingIntent}. - * + * * When setting the on-click action of items within collections (eg. {@link ListView}, * {@link StackView} etc.), this method will not work. Instead, use {@link * RemoteViews#setPendingIntentTemplate(int, PendingIntent) in conjunction with @@ -1827,7 +1823,7 @@ public class RemoteViews implements Parcelable, Filter { * view. *

* You can omit specific calls by marking their values with null or -1. - * + * * @param viewId The id of the view that contains the target * {@link Drawable} * @param targetBackground If true, apply these parameters to the @@ -1853,7 +1849,7 @@ public class RemoteViews implements Parcelable, Filter { /** * Equivalent to calling {@link android.widget.TextView#setTextColor(int)}. - * + * * @param viewId The id of the view whose text color should change * @param color Sets the text color for all the states (normal, selected, * focused) to be this color. @@ -2105,16 +2101,16 @@ public class RemoteViews implements Parcelable, Filter { /** * Inflates the view hierarchy represented by this object and applies * all of the actions. - * + * *

Caller beware: this may throw - * + * * @param context Default context to use * @param parent Parent that the resulting view hierarchy will be attached to. This method * does not attach the hierarchy. The caller should do so when appropriate. * @return The inflated view hierarchy */ public View apply(Context context, ViewGroup parent) { - return apply(context, parent, DEFAULT_ON_CLICK_HANDLER); + return apply(context, parent, null); } /** @hide */ @@ -2142,12 +2138,12 @@ public class RemoteViews implements Parcelable, Filter { * Applies all of the actions to the provided view. * *

Caller beware: this may throw - * + * * @param v The view to apply the actions to. This should be the result of * the {@link #apply(Context,ViewGroup)} call. */ public void reapply(Context context, View v) { - reapply(context, v, DEFAULT_ON_CLICK_HANDLER); + reapply(context, v, null); } /** @hide */ @@ -2170,6 +2166,7 @@ public class RemoteViews implements Parcelable, Filter { private void performApply(View v, ViewGroup parent, OnClickHandler handler) { if (mActions != null) { + handler = handler == null ? DEFAULT_ON_CLICK_HANDLER : handler; final int count = mActions.size(); for (int i = 0; i < count; i++) { Action a = mActions.get(i); @@ -2198,7 +2195,7 @@ public class RemoteViews implements Parcelable, Filter { /* (non-Javadoc) * Used to restrict the views which can be inflated - * + * * @see android.view.LayoutInflater.Filter#onLoadClass(java.lang.Class) */ public boolean onLoadClass(Class clazz) {