summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorWinson Chung <winsonc@google.com>2010-07-16 11:18:17 -0700
committerWinson Chung <winsonc@google.com>2010-07-19 14:48:31 -0700
commit499cb9f516062b654952d282f211bee44c31a3c2 (patch)
tree3c9bac8b31275e886bfbd07805c38839c185eab2
parentb5b37f3bcc3065959c27e588f065dfb33a061e1d (diff)
downloadframeworks_base-499cb9f516062b654952d282f211bee44c31a3c2.zip
frameworks_base-499cb9f516062b654952d282f211bee44c31a3c2.tar.gz
frameworks_base-499cb9f516062b654952d282f211bee44c31a3c2.tar.bz2
Initial changes to allow collections in widgets.
Change-Id: I3cfa899bae88cd252912cecebc12e93c27a3b7c9
-rw-r--r--Android.mk1
-rw-r--r--api/current.xml300
-rw-r--r--core/java/android/appwidget/AppWidgetHost.java23
-rw-r--r--core/java/android/appwidget/AppWidgetHostView.java19
-rw-r--r--core/java/android/appwidget/AppWidgetManager.java29
-rw-r--r--core/java/android/widget/AbsListView.java73
-rw-r--r--core/java/android/widget/GridView.java35
-rw-r--r--core/java/android/widget/ListView.java32
-rw-r--r--core/java/android/widget/RemoteViews.java50
-rw-r--r--core/java/android/widget/RemoteViewsAdapter.java666
-rw-r--r--core/java/android/widget/RemoteViewsService.java140
-rw-r--r--core/java/com/android/internal/appwidget/IAppWidgetHost.aidl1
-rw-r--r--core/java/com/android/internal/appwidget/IAppWidgetService.aidl1
-rw-r--r--core/java/com/android/internal/widget/IRemoteViewsFactory.aidl30
-rw-r--r--services/java/com/android/server/AppWidgetService.java38
15 files changed, 1436 insertions, 2 deletions
diff --git a/Android.mk b/Android.mk
index 27f9e1e..30a67e3 100644
--- a/Android.mk
+++ b/Android.mk
@@ -163,6 +163,7 @@ LOCAL_SRC_FILES += \
core/java/com/android/internal/view/IInputMethodClient.aidl \
core/java/com/android/internal/view/IInputMethodManager.aidl \
core/java/com/android/internal/view/IInputMethodSession.aidl \
+ core/java/com/android/internal/widget/IRemoteViewsFactory.aidl \
location/java/android/location/IGeocodeProvider.aidl \
location/java/android/location/IGpsStatusListener.aidl \
location/java/android/location/IGpsStatusProvider.aidl \
diff --git a/api/current.xml b/api/current.xml
index b5435e4..76b1c9a 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -34218,6 +34218,40 @@
<parameter name="context" type="android.content.Context">
</parameter>
</method>
+<method name="notifyAppWidgetViewDataChanged"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="appWidgetIds" type="int[]">
+</parameter>
+<parameter name="views" type="android.widget.RemoteViews">
+</parameter>
+<parameter name="viewId" type="int">
+</parameter>
+</method>
+<method name="notifyAppWidgetViewDataChanged"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="appWidgetId" type="int">
+</parameter>
+<parameter name="views" type="android.widget.RemoteViews">
+</parameter>
+<parameter name="viewId" type="int">
+</parameter>
+</method>
<method name="updateAppWidget"
return="void"
abstract="false"
@@ -207092,6 +207126,28 @@
visibility="public"
>
</method>
+<method name="onRemoteAdapterConnected"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="onRemoteAdapterDisconnected"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
<method name="onRestoreInstanceState"
return="void"
abstract="false"
@@ -207269,6 +207325,19 @@
<parameter name="listener" type="android.widget.AbsListView.RecyclerListener">
</parameter>
</method>
+<method name="setRemoteViewsAdapter"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="intent" type="android.content.Intent">
+</parameter>
+</method>
<method name="setScrollIndicators"
return="void"
abstract="false"
@@ -213725,6 +213794,19 @@
<parameter name="verticalSpacing" type="int">
</parameter>
</method>
+<method name="smoothScrollByOffset"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="offset" type="int">
+</parameter>
+</method>
<field name="AUTO_FIT"
type="int"
transient="false"
@@ -216276,6 +216358,19 @@
<parameter name="y" type="int">
</parameter>
</method>
+<method name="smoothScrollByOffset"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="offset" type="int">
+</parameter>
+</method>
<field name="CHOICE_MODE_MULTIPLE"
type="int"
transient="false"
@@ -219130,6 +219225,23 @@
<parameter name="value" type="int">
</parameter>
</method>
+<method name="setIntent"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="viewId" type="int">
+</parameter>
+<parameter name="methodName" type="java.lang.String">
+</parameter>
+<parameter name="value" type="android.content.Intent">
+</parameter>
+</method>
<method name="setLong"
return="void"
abstract="false"
@@ -219181,6 +219293,51 @@
<parameter name="indeterminate" type="boolean">
</parameter>
</method>
+<method name="setRelativeScrollPosition"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="viewId" type="int">
+</parameter>
+<parameter name="offset" type="int">
+</parameter>
+</method>
+<method name="setRemoteAdapter"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="viewId" type="int">
+</parameter>
+<parameter name="intent" type="android.content.Intent">
+</parameter>
+</method>
+<method name="setScrollPosition"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="viewId" type="int">
+</parameter>
+<parameter name="position" type="int">
+</parameter>
+</method>
<method name="setShort"
return="void"
abstract="false"
@@ -219343,6 +219500,149 @@
<implements name="java.lang.annotation.Annotation">
</implements>
</class>
+<class name="RemoteViewsService"
+ extends="android.app.Service"
+ abstract="true"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<constructor name="RemoteViewsService"
+ type="android.widget.RemoteViewsService"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</constructor>
+<method name="onBind"
+ return="android.os.IBinder"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="intent" type="android.content.Intent">
+</parameter>
+</method>
+<method name="onGetViewFactory"
+ return="android.widget.RemoteViewsService.RemoteViewsFactory"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="intent" type="android.content.Intent">
+</parameter>
+</method>
+</class>
+<interface name="RemoteViewsService.RemoteViewsFactory"
+ abstract="true"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<method name="getCount"
+ return="int"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getItemId"
+ return="long"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="position" type="int">
+</parameter>
+</method>
+<method name="getLoadingView"
+ return="android.widget.RemoteViews"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getViewAt"
+ return="android.widget.RemoteViews"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="position" type="int">
+</parameter>
+</method>
+<method name="getViewTypeCount"
+ return="int"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="hasStableIds"
+ return="boolean"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="onCreate"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="onDestroy"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+</interface>
<class name="ResourceCursorAdapter"
extends="android.widget.CursorAdapter"
abstract="true"
diff --git a/core/java/android/appwidget/AppWidgetHost.java b/core/java/android/appwidget/AppWidgetHost.java
index b2fc13f..6011eec 100644
--- a/core/java/android/appwidget/AppWidgetHost.java
+++ b/core/java/android/appwidget/AppWidgetHost.java
@@ -39,6 +39,7 @@ public class AppWidgetHost {
static final int HANDLE_UPDATE = 1;
static final int HANDLE_PROVIDER_CHANGED = 2;
+ static final int HANDLE_VIEW_DATA_CHANGED = 3;
final static Object sServiceLock = new Object();
static IAppWidgetService sService;
@@ -60,6 +61,14 @@ public class AppWidgetHost {
msg.obj = info;
msg.sendToTarget();
}
+
+ public void viewDataChanged(int appWidgetId, RemoteViews views, int viewId) {
+ Message msg = mHandler.obtainMessage(HANDLE_VIEW_DATA_CHANGED);
+ msg.arg1 = appWidgetId;
+ msg.arg2 = viewId;
+ msg.obj = views;
+ msg.sendToTarget();
+ }
}
class UpdateHandler extends Handler {
@@ -77,6 +86,10 @@ public class AppWidgetHost {
onProviderChanged(msg.arg1, (AppWidgetProviderInfo)msg.obj);
break;
}
+ case HANDLE_VIEW_DATA_CHANGED: {
+ viewDataChanged(msg.arg1, (RemoteViews) msg.obj, msg.arg2);
+ break;
+ }
}
}
}
@@ -250,6 +263,16 @@ public class AppWidgetHost {
v.updateAppWidget(views);
}
}
+
+ void viewDataChanged(int appWidgetId, RemoteViews views, int viewId) {
+ AppWidgetHostView v;
+ synchronized (mViews) {
+ v = mViews.get(appWidgetId);
+ }
+ if (v != null) {
+ v.viewDataChanged(views, viewId);
+ }
+ }
}
diff --git a/core/java/android/appwidget/AppWidgetHostView.java b/core/java/android/appwidget/AppWidgetHostView.java
index 3c19ea3..22f4266 100644
--- a/core/java/android/appwidget/AppWidgetHostView.java
+++ b/core/java/android/appwidget/AppWidgetHostView.java
@@ -32,6 +32,9 @@ import android.util.SparseArray;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
+import android.widget.Adapter;
+import android.widget.AdapterView;
+import android.widget.BaseAdapter;
import android.widget.FrameLayout;
import android.widget.RemoteViews;
import android.widget.TextView;
@@ -258,6 +261,22 @@ public class AppWidgetHostView extends FrameLayout {
}
/**
+ * Process data-changed notifications for the specified view in the specified
+ * set of {@link RemoteViews} views.
+ */
+ void viewDataChanged(RemoteViews remoteViews, int viewId) {
+ View v = findViewById(viewId);
+ if ((v != null) && (v instanceof AdapterView<?>)) {
+ AdapterView<?> adapterView = (AdapterView<?>) v;
+ Adapter adapter = adapterView.getAdapter();
+ if (adapter instanceof BaseAdapter) {
+ BaseAdapter baseAdapter = (BaseAdapter) adapter;
+ baseAdapter.notifyDataSetChanged();
+ }
+ }
+ }
+
+ /**
* Build a {@link Context} cloned into another package name, usually for the
* purposes of reading remote resources.
*/
diff --git a/core/java/android/appwidget/AppWidgetManager.java b/core/java/android/appwidget/AppWidgetManager.java
index 3f12bf9..5ee721f 100644
--- a/core/java/android/appwidget/AppWidgetManager.java
+++ b/core/java/android/appwidget/AppWidgetManager.java
@@ -288,6 +288,35 @@ public class AppWidgetManager {
}
/**
+ * Notifies the specified collection view in all the specified AppWidget instances
+ * to invalidate their currently data.
+ *
+ * @param appWidgetIds The AppWidget instances for which to notify of view data changes.
+ * @param views The RemoteViews which contains the view referenced at viewId.
+ * @param viewId The collection view id.
+ */
+ public void notifyAppWidgetViewDataChanged(int[] appWidgetIds, RemoteViews views, int viewId) {
+ try {
+ sService.notifyAppWidgetViewDataChanged(appWidgetIds, views, viewId);
+ }
+ catch (RemoteException e) {
+ throw new RuntimeException("system server dead?", e);
+ }
+ }
+
+ /**
+ * Notifies the specified collection view in all the specified AppWidget instance
+ * to invalidate it's currently data.
+ *
+ * @param appWidgetId The AppWidget instance for which to notify of view data changes.
+ * @param views The RemoteViews which contains the view referenced at viewId.
+ * @param viewId The collection view id.
+ */
+ public void notifyAppWidgetViewDataChanged(int appWidgetId, RemoteViews views, int viewId) {
+ notifyAppWidgetViewDataChanged(new int[] { appWidgetId }, views, viewId);
+ }
+
+ /**
* Return a list of the AppWidget providers that are currently installed.
*/
public List<AppWidgetProviderInfo> getInstalledProviders() {
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index 70c1e15..1658b2f 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -19,6 +19,7 @@ package android.widget;
import com.android.internal.R;
import android.content.Context;
+import android.content.Intent;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Rect;
@@ -71,7 +72,8 @@ import java.util.List;
*/
public abstract class AbsListView extends AdapterView<ListAdapter> implements TextWatcher,
ViewTreeObserver.OnGlobalLayoutListener, Filter.FilterListener,
- ViewTreeObserver.OnTouchModeChangeListener {
+ ViewTreeObserver.OnTouchModeChangeListener,
+ RemoteViewsAdapter.RemoteAdapterConnectionCallback {
/**
* Disables the transcript mode.
@@ -180,6 +182,11 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
ListAdapter mAdapter;
/**
+ * The remote adapter containing the data to be displayed by this view to be set
+ */
+ private RemoteViewsAdapter mRemoteAdapter;
+
+ /**
* Indicates whether the list selector should be drawn on top of the children or behind
*/
boolean mDrawSelectorOnTop = false;
@@ -2893,6 +2900,42 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
mFlingRunnable.startScroll(distance, duration);
}
+ /**
+ * Allows RemoteViews to scroll relatively to a position.
+ */
+ void smoothScrollByOffset(int position) {
+ int index = -1;
+ if (position < 0) {
+ index = getFirstVisiblePosition();
+ } else if (position > 0) {
+ index = getLastVisiblePosition();
+ }
+
+ if (index > -1) {
+ View child = getChildAt(index - getFirstVisiblePosition());
+ if (child != null) {
+ Rect visibleRect = new Rect();
+ if (child.getGlobalVisibleRect(visibleRect)) {
+ // the child is partially visible
+ int childRectArea = child.getWidth() * child.getHeight();
+ int visibleRectArea = visibleRect.width() * visibleRect.height();
+ float visibleArea = (visibleRectArea / (float) childRectArea);
+ final float visibleThreshold = 0.75f;
+ if ((position < 0) && (visibleArea < visibleThreshold)) {
+ // the top index is not perceivably visible so offset
+ // to account for showing that top index as well
+ ++index;
+ } else if ((position > 0) && (visibleArea < visibleThreshold)) {
+ // the bottom index is not perceivably visible so offset
+ // to account for showing that bottom index as well
+ --index;
+ }
+ }
+ smoothScrollToPosition(Math.max(0, Math.min(getCount(), index + position)));
+ }
+ }
+ }
+
private void createScrollingCache() {
if (mScrollingCacheEnabled && !mCachingStarted) {
setChildrenDrawnWithCacheEnabled(true);
@@ -3905,6 +3948,34 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
}
/**
+ * Sets up this AbsListView to use a remote views adapter which connects to a RemoteViewsService
+ * through the specified intent.
+ * @param intent the intent used to identify the RemoteViewsService for the adapter to connect to.
+ */
+ public void setRemoteViewsAdapter(Intent intent) {
+ mRemoteAdapter = new RemoteViewsAdapter(getContext(), intent, this);
+ }
+
+ /**
+ * Called back when the adapter connects to the RemoteViewsService.
+ */
+ public void onRemoteAdapterConnected() {
+ if (mRemoteAdapter != mAdapter) {
+ setAdapter(mRemoteAdapter);
+ }
+ }
+
+ /**
+ * Called back when the adapter disconnects from the RemoteViewsService.
+ */
+ public void onRemoteAdapterDisconnected() {
+ if (mRemoteAdapter == mAdapter) {
+ mRemoteAdapter = null;
+ setAdapter(null);
+ }
+ }
+
+ /**
* Sets the recycler listener to be notified whenever a View is set aside in
* the recycler for later reuse. This listener can be used to free resources
* associated to the View.
diff --git a/core/java/android/widget/GridView.java b/core/java/android/widget/GridView.java
index fe69a13..a5b3ed5 100644
--- a/core/java/android/widget/GridView.java
+++ b/core/java/android/widget/GridView.java
@@ -17,22 +17,25 @@
package android.widget;
import android.content.Context;
+import android.content.Intent;
import android.content.res.TypedArray;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.KeyEvent;
+import android.view.SoundEffectConstants;
import android.view.View;
import android.view.ViewDebug;
import android.view.ViewGroup;
-import android.view.SoundEffectConstants;
import android.view.animation.GridLayoutAnimationController;
+import android.widget.RemoteViews.RemoteView;
/**
* A view that shows items in two-dimensional scrolling grid. The items in the
* grid come from the {@link ListAdapter} associated with this view.
*/
+@RemoteView
public class GridView extends AbsListView {
public static final int NO_STRETCH = 0;
public static final int STRETCH_SPACING = 1;
@@ -107,6 +110,16 @@ public class GridView extends AbsListView {
}
/**
+ * Sets up this AbsListView to use a remote views adapter which connects to a RemoteViewsService
+ * through the specified intent.
+ * @param intent the intent used to identify the RemoteViewsService for the adapter to connect to.
+ */
+ @android.view.RemotableViewMethod
+ public void setRemoteViewsAdapter(Intent intent) {
+ super.setRemoteViewsAdapter(intent);
+ }
+
+ /**
* Sets the data behind this GridView.
*
* @param adapter the adapter providing the grid's data
@@ -740,6 +753,26 @@ public class GridView extends AbsListView {
}
/**
+ * Smoothly scroll to the specified adapter position. The view will
+ * scroll such that the indicated position is displayed.
+ * @param position Scroll to this adapter position.
+ */
+ @android.view.RemotableViewMethod
+ public void smoothScrollToPosition(int position) {
+ super.smoothScrollToPosition(position);
+ }
+
+ /**
+ * Smoothly scroll to the specified adapter position offset. The view will
+ * scroll such that the indicated position is displayed.
+ * @param offset The amount to offset from the adapter position to scroll to.
+ */
+ @android.view.RemotableViewMethod
+ public void smoothScrollByOffset(int offset) {
+ super.smoothScrollByOffset(offset);
+ }
+
+ /**
* Fills the grid based on positioning the new selection relative to the old
* selection. The new selection will be placed at, above, or below the
* location of the new selection depending on how the selection is moving.
diff --git a/core/java/android/widget/ListView.java b/core/java/android/widget/ListView.java
index 86913ae..ad9d930 100644
--- a/core/java/android/widget/ListView.java
+++ b/core/java/android/widget/ListView.java
@@ -20,6 +20,7 @@ import com.android.internal.R;
import com.google.android.collect.Lists;
import android.content.Context;
+import android.content.Intent;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
@@ -41,6 +42,7 @@ import android.view.ViewDebug;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.view.accessibility.AccessibilityEvent;
+import android.widget.RemoteViews.RemoteView;
import java.util.ArrayList;
@@ -65,6 +67,7 @@ import java.util.ArrayList;
* @attr ref android.R.styleable#ListView_headerDividersEnabled
* @attr ref android.R.styleable#ListView_footerDividersEnabled
*/
+@RemoteView
public class ListView extends AbsListView {
/**
* Used to indicate a no preference for a position type.
@@ -401,6 +404,16 @@ public class ListView extends AbsListView {
}
/**
+ * Sets up this AbsListView to use a remote views adapter which connects to a RemoteViewsService
+ * through the specified intent.
+ * @param intent the intent used to identify the RemoteViewsService for the adapter to connect to.
+ */
+ @android.view.RemotableViewMethod
+ public void setRemoteViewsAdapter(Intent intent) {
+ super.setRemoteViewsAdapter(intent);
+ }
+
+ /**
* Sets the data behind this ListView.
*
* The adapter passed to this method may be wrapped by a {@link WrapperListAdapter},
@@ -863,6 +876,25 @@ public class ListView extends AbsListView {
return topSelectionPixel;
}
+ /**
+ * Smoothly scroll to the specified adapter position. The view will
+ * scroll such that the indicated position is displayed.
+ * @param position Scroll to this adapter position.
+ */
+ @android.view.RemotableViewMethod
+ public void smoothScrollToPosition(int position) {
+ super.smoothScrollToPosition(position);
+ }
+
+ /**
+ * Smoothly scroll to the specified adapter position offset. The view will
+ * scroll such that the indicated position is displayed.
+ * @param offset The amount to offset from the adapter position to scroll to.
+ */
+ @android.view.RemotableViewMethod
+ public void smoothScrollByOffset(int offset) {
+ super.smoothScrollByOffset(offset);
+ }
/**
* Fills the list based on positioning the new selection relative to the old
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index fc02acf..bbc75b9 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -285,6 +285,7 @@ public class RemoteViews implements Parcelable, Filter {
static final int URI = 11;
static final int BITMAP = 12;
static final int BUNDLE = 13;
+ static final int INTENT = 14;
int viewId;
String methodName;
@@ -347,6 +348,9 @@ public class RemoteViews implements Parcelable, Filter {
case BUNDLE:
this.value = in.readBundle();
break;
+ case INTENT:
+ this.value = Intent.CREATOR.createFromParcel(in);
+ break;
default:
break;
}
@@ -402,6 +406,9 @@ public class RemoteViews implements Parcelable, Filter {
case BUNDLE:
out.writeBundle((Bundle) this.value);
break;
+ case INTENT:
+ ((Intent)this.value).writeToParcel(out, flags);
+ break;
default:
break;
}
@@ -435,6 +442,8 @@ public class RemoteViews implements Parcelable, Filter {
return Bitmap.class;
case BUNDLE:
return Bundle.class;
+ case INTENT:
+ return Intent.class;
default:
return null;
}
@@ -770,6 +779,37 @@ public class RemoteViews implements Parcelable, Filter {
}
/**
+ * Equivalent to calling {@link android.widget.AbsListView#setRemoteViewsAdapter(Intent)}.
+ *
+ * @param viewId The id of the view whose text should change
+ * @param intent The intent of the service which will be
+ * providing data to the RemoteViewsAdapter
+ */
+ public void setRemoteAdapter(int viewId, Intent intent) {
+ setIntent(viewId, "setRemoteViewsAdapter", intent);
+ }
+
+ /**
+ * Equivalent to calling {@link android.widget.AbsListView#smoothScrollToPosition(int, int)}.
+ *
+ * @param viewId The id of the view whose text should change
+ * @param position Scroll to this adapter position
+ */
+ public void setScrollPosition(int viewId, int position) {
+ setInt(viewId, "smoothScrollToPosition", position);
+ }
+
+ /**
+ * Equivalent to calling {@link android.widget.AbsListView#smoothScrollToPosition(int, int)}.
+ *
+ * @param viewId The id of the view whose text should change
+ * @param position Scroll by this adapter position offset
+ */
+ public void setRelativeScrollPosition(int viewId, int offset) {
+ setInt(viewId, "smoothScrollByOffset", offset);
+ }
+
+ /**
* Call a method taking one boolean on a view in the layout for this RemoteViews.
*
* @param viewId The id of the view whose text should change
@@ -916,6 +956,16 @@ public class RemoteViews implements Parcelable, Filter {
}
/**
+ *
+ * @param viewId
+ * @param methodName
+ * @param value
+ */
+ public void setIntent(int viewId, String methodName, Intent value) {
+ addAction(new ReflectionAction(viewId, methodName, ReflectionAction.INTENT, value));
+ }
+
+ /**
* Inflates the view hierarchy represented by this object and applies
* all of the actions.
*
diff --git a/core/java/android/widget/RemoteViewsAdapter.java b/core/java/android/widget/RemoteViewsAdapter.java
new file mode 100644
index 0000000..d426033
--- /dev/null
+++ b/core/java/android/widget/RemoteViewsAdapter.java
@@ -0,0 +1,666 @@
+/*
+ * 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.widget;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.graphics.Color;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.View.MeasureSpec;
+
+import com.android.internal.widget.IRemoteViewsFactory;
+
+/**
+ * An adapter to a RemoteViewsService which fetches and caches RemoteViews
+ * to be later inflated as child views.
+ */
+/** @hide */
+public class RemoteViewsAdapter extends BaseAdapter {
+
+ private static final String LOG_TAG = "RemoteViewsAdapter";
+
+ private Context mContext;
+ private Intent mIntent;
+ private RemoteViewsAdapterServiceConnection mServiceConnection;
+ private RemoteViewsCache mViewCache;
+
+ private HandlerThread mWorkerThread;
+ // items may be interrupted within the normally processed queues
+ private Handler mWorkerQueue;
+ private Handler mMainQueue;
+ // items are never dequeued from the priority queue and must run
+ private Handler mWorkerPriorityQueue;
+ private Handler mMainPriorityQueue;
+
+ /**
+ * An interface for the RemoteAdapter to notify other classes when adapters
+ * are actually connected to/disconnected from their actual services.
+ */
+ public interface RemoteAdapterConnectionCallback {
+ public void onRemoteAdapterConnected();
+
+ public void onRemoteAdapterDisconnected();
+ }
+
+ /**
+ * The service connection that gets populated when the RemoteViewsService is
+ * bound.
+ */
+ private class RemoteViewsAdapterServiceConnection implements ServiceConnection {
+ private boolean mConnected;
+ private IRemoteViewsFactory mRemoteViewsFactory;
+ private RemoteAdapterConnectionCallback mCallback;
+
+ public RemoteViewsAdapterServiceConnection(RemoteAdapterConnectionCallback callback) {
+ mCallback = callback;
+ }
+
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ mRemoteViewsFactory = IRemoteViewsFactory.Stub.asInterface(service);
+ mConnected = true;
+ // notifyDataSetChanged should be called first, to ensure that the
+ // views are not updated twice
+ notifyDataSetChanged();
+
+ // post a new runnable to load the appropriate data, then callback
+ mWorkerPriorityQueue.post(new Runnable() {
+ @Override
+ public void run() {
+ // we need to get the viewTypeCount specifically, so just get all the
+ // metadata
+ mViewCache.requestMetaData();
+
+ // post a runnable to call the callback on the main thread
+ mMainPriorityQueue.post(new Runnable() {
+ @Override
+ public void run() {
+ if (mCallback != null)
+ mCallback.onRemoteAdapterConnected();
+ }
+ });
+ }
+ });
+ }
+
+ public void onServiceDisconnected(ComponentName name) {
+ mRemoteViewsFactory = null;
+ mConnected = false;
+ if (mCallback != null)
+ mCallback.onRemoteAdapterDisconnected();
+
+ // clear the main/worker queues
+ mMainQueue.removeMessages(0);
+ mWorkerQueue.removeMessages(0);
+ }
+
+ public IRemoteViewsFactory getRemoteViewsFactory() {
+ return mRemoteViewsFactory;
+ }
+
+ public boolean isConnected() {
+ return mConnected;
+ }
+ }
+
+ /**
+ * An internal cache of remote views.
+ */
+ private class RemoteViewsCache {
+ private RemoteViewsInfo mViewCacheInfo;
+ private RemoteViewsIndexInfo[] mViewCache;
+
+ // if a user loading view is not provided, then we create a temporary one
+ // for the user using the height of the first view
+ private RemoteViews mUserLoadingView;
+ private RemoteViews mFirstView;
+ private int mFirstViewHeight;
+
+ // determines when the current cache window needs to be updated with new
+ // items (ie. when there is not enough slack)
+ private int mViewCacheStartPosition;
+ private int mViewCacheEndPosition;
+ private int mHalfCacheSize;
+ private int mCacheSlack;
+ private final float mCacheSlackPercentage = 0.75f;
+
+ // determines whether to reorder the posted items on the worker thread
+ // so that the items in the current window can be loaded first
+ private int mPriorityLoadingWindowSize;
+ private int mPriorityLoadingWindowStart;
+ private int mPriorityLoadingWindowEnd;
+
+ // determines which way to load items in the current window based on how
+ // the window shifted last
+ private boolean mLoadUpwards;
+
+ /**
+ * The data structure stored at each index of the cache. Any member
+ * that is not invalidated persists throughout the lifetime of the cache.
+ */
+ private class RemoteViewsIndexInfo {
+ FrameLayout flipper;
+ RemoteViews view;
+ long itemId;
+ int typeId;
+
+ RemoteViewsIndexInfo() {
+ invalidate();
+ }
+
+ void set(RemoteViews v, long id) {
+ view = v;
+ itemId = id;
+ if (v != null)
+ typeId = v.getLayoutId();
+ else
+ typeId = 0;
+ }
+
+ void invalidate() {
+ view = null;
+ itemId = 0;
+ typeId = 0;
+ }
+
+ final boolean isValid() {
+ return (view != null);
+ }
+ }
+
+ /**
+ * Remote adapter metadata. Useful for when we have to lock on something
+ * before updating the metadata.
+ */
+ private class RemoteViewsInfo {
+ int count;
+ int viewTypeCount;
+ boolean hasStableIds;
+ Map<Integer, Integer> mTypeIdIndexMap;
+
+ RemoteViewsInfo() {
+ count = 0;
+ // by default there is at least one dummy view type
+ viewTypeCount = 1;
+ hasStableIds = true;
+ mTypeIdIndexMap = new HashMap<Integer, Integer>();
+ }
+ }
+
+ public RemoteViewsCache(int halfCacheSize) {
+ mHalfCacheSize = halfCacheSize;
+ mCacheSlack = Math.round(mCacheSlackPercentage * mHalfCacheSize);
+ mViewCacheStartPosition = 0;
+ mViewCacheEndPosition = -1;
+ mPriorityLoadingWindowSize = 4;
+ mPriorityLoadingWindowStart = 0;
+ mPriorityLoadingWindowEnd = 0;
+ mLoadUpwards = false;
+
+ // initialize the cache
+ mViewCacheInfo = new RemoteViewsInfo();
+ mViewCache = new RemoteViewsIndexInfo[2 * mHalfCacheSize + 1];
+ for (int i = 0; i < mViewCache.length; ++i) {
+ mViewCache[i] = new RemoteViewsIndexInfo();
+ }
+ }
+
+ private final boolean contains(int position) {
+ // take the modulo of the position
+ return (mViewCacheStartPosition <= position) && (position < mViewCacheEndPosition);
+ }
+
+ private final boolean containsAndIsValid(int position) {
+ if (contains(position)) {
+ RemoteViewsIndexInfo indexInfo = mViewCache[getCacheIndex(position)];
+ if (indexInfo.isValid()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private final int getCacheIndex(int position) {
+ return (mViewCache.length + (position % mViewCache.length)) % mViewCache.length;
+ }
+
+ public void requestMetaData() {
+ if (mServiceConnection.isConnected()) {
+ try {
+ IRemoteViewsFactory factory = mServiceConnection.getRemoteViewsFactory();
+
+ // get the properties/first view (so that we can use it to
+ // measure our dummy views)
+ boolean hasStableIds = factory.hasStableIds();
+ int viewTypeCount = factory.getViewTypeCount();
+ int count = factory.getCount();
+ RemoteViews loadingView = factory.getLoadingView();
+ RemoteViews firstView = null;
+ if ((count > 0) && (loadingView == null)) {
+ firstView = factory.getViewAt(0);
+ }
+ synchronized (mViewCacheInfo) {
+ RemoteViewsInfo info = mViewCacheInfo;
+ info.hasStableIds = hasStableIds;
+ info.viewTypeCount = viewTypeCount + 1;
+ info.count = count;
+ mUserLoadingView = loadingView;
+ if (firstView != null) {
+ mFirstView = firstView;
+ mFirstViewHeight = -1;
+ }
+ }
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ protected void updateRemoteViewsInfo(int position) {
+ if (mServiceConnection.isConnected()) {
+ IRemoteViewsFactory factory = mServiceConnection.getRemoteViewsFactory();
+
+ // load the item information
+ RemoteViews remoteView = null;
+ long itemId = 0;
+ try {
+ remoteView = factory.getViewAt(position);
+ itemId = factory.getItemId(position);
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ }
+
+ synchronized (mViewCache) {
+ // skip if the window has moved
+ if (position < mViewCacheStartPosition || position >= mViewCacheEndPosition)
+ return;
+
+ final int positionIndex = position;
+ final int cacheIndex = getCacheIndex(position);
+ mViewCache[cacheIndex].set(remoteView, itemId);
+
+ // notify the main thread when done loading
+ // flush pending updates
+ mMainQueue.post(new Runnable() {
+ @Override
+ public void run() {
+ // swap the loader view for this view
+ synchronized (mViewCache) {
+ if (containsAndIsValid(positionIndex)) {
+ RemoteViewsIndexInfo indexInfo = mViewCache[cacheIndex];
+ FrameLayout flipper = indexInfo.flipper;
+
+ // recompose the flipper
+ View loadingView = flipper.getChildAt(0);
+ loadingView.setVisibility(View.GONE);
+ flipper.removeAllViews();
+ flipper.addView(loadingView);
+ flipper.addView(indexInfo.view.apply(mContext, flipper));
+
+ // hide the loader view and bring the new view to the front
+ flipper.requestLayout();
+ flipper.invalidate();
+ }
+ }
+ }
+ });
+ }
+ }
+ }
+
+ private RemoteViewsIndexInfo requestCachedIndexInfo(final int position) {
+ int indicesToLoadCount = 0;
+ int[] indicesToLoad = null;
+
+ synchronized (mViewCache) {
+ indicesToLoad = new int[mViewCache.length];
+ Arrays.fill(indicesToLoad, 0);
+
+ if (containsAndIsValid(position)) {
+ // return the info if it exists in the window and is loaded
+ return mViewCache[getCacheIndex(position)];
+ }
+
+ // if necessary update the window and load the new information
+ int centerPosition = (mViewCacheEndPosition + mViewCacheStartPosition) / 2;
+ if ((mViewCacheEndPosition <= mViewCacheStartPosition) || (Math.abs(position - centerPosition) > mCacheSlack)) {
+ int newStartPosition = position - mHalfCacheSize;
+ int newEndPosition = position + mHalfCacheSize;
+
+ // prune/add before the current start position
+ int effectiveStart = Math.max(newStartPosition, 0);
+ int effectiveEnd = Math.min(newEndPosition, getCount());
+
+ mWorkerQueue.removeMessages(0);
+
+ // invalidate items in the queue
+ boolean loadFromBeginning = effectiveStart < mViewCacheStartPosition;
+ int numLoadFromBeginning = mViewCacheStartPosition - effectiveStart;
+ boolean loadFromEnd = effectiveEnd > mViewCacheEndPosition;
+ int overlapStart = Math.max(mViewCacheStartPosition, effectiveStart);
+ int overlapEnd = Math.min(Math.max(mViewCacheStartPosition, mViewCacheEndPosition), effectiveEnd);
+ for (int i = newStartPosition; i < newEndPosition; ++i) {
+ if (loadFromBeginning && (effectiveStart <= i) && (i < overlapStart)) {
+ // load new items at the beginning in reverse order
+ mViewCache[getCacheIndex(i)].invalidate();
+ indicesToLoad[indicesToLoadCount++] = effectiveStart
+ + (numLoadFromBeginning - (i - effectiveStart) - 1);
+ } else if (loadFromEnd && (overlapEnd <= i) && (i < effectiveEnd)) {
+ mViewCache[getCacheIndex(i)].invalidate();
+ indicesToLoad[indicesToLoadCount++] = i;
+ } else if ((overlapStart <= i) && (i < overlapEnd)) {
+ // load the stuff in the middle that has not already
+ // been loaded
+ if (!mViewCache[getCacheIndex(i)].isValid()) {
+ indicesToLoad[indicesToLoadCount++] = i;
+ }
+ } else {
+ // invalidate all other cache indices (outside the effective start/end)
+ mViewCache[getCacheIndex(i)].invalidate();
+ }
+ }
+
+ mViewCacheStartPosition = newStartPosition;
+ mViewCacheEndPosition = newEndPosition;
+ mPriorityLoadingWindowStart = position;
+ mPriorityLoadingWindowEnd = position + mPriorityLoadingWindowSize;
+ mLoadUpwards = loadFromBeginning && !loadFromEnd;
+ } else if (contains(position)) {
+ // prioritize items around this position so that they load first
+ if (position < mPriorityLoadingWindowStart || position > mPriorityLoadingWindowEnd) {
+ mWorkerQueue.removeMessages(0);
+
+ int index;
+ int effectiveStart = Math.max(position - mPriorityLoadingWindowSize, 0);
+ int effectiveEnd = 0;
+ synchronized (mViewCacheInfo) {
+ effectiveEnd = Math.min(position + mPriorityLoadingWindowSize - 1,
+ mViewCacheInfo.count - 1);
+ }
+
+ for (int i = 0; i < mViewCache.length; ++i) {
+ if (mLoadUpwards) {
+ index = effectiveEnd - i;
+ } else {
+ index = effectiveStart + i;
+ }
+ if (!mViewCache[getCacheIndex(index)].isValid()) {
+ indicesToLoad[indicesToLoadCount++] = index;
+ }
+ }
+
+ mPriorityLoadingWindowStart = effectiveStart;
+ mPriorityLoadingWindowEnd = position + mPriorityLoadingWindowSize;
+ }
+ }
+ }
+
+ // post items to be loaded
+ int length = 0;
+ synchronized (mViewCacheInfo) {
+ length = mViewCacheInfo.count;
+ }
+ for (int i = 0; i < indicesToLoadCount; ++i) {
+ final int index = indicesToLoad[i];
+ if (0 <= index && index < length) {
+ mWorkerQueue.post(new Runnable() {
+ @Override
+ public void run() {
+ updateRemoteViewsInfo(index);
+ }
+ });
+ }
+ }
+
+ // return null so that a dummy view can be retrieved
+ return null;
+ }
+
+ public View getView(int position, View convertView, ViewGroup parent) {
+ if (mServiceConnection.isConnected()) {
+ // create the flipper views if necessary (we have to do this now
+ // for all the flippers while we have the reference to the parent)
+ createInitialLoadingFlipperViews(parent);
+
+ // request the item from the cache (queueing it to load if not
+ // in the cache already)
+ RemoteViewsIndexInfo indexInfo = requestCachedIndexInfo(position);
+
+ // update the flipper appropriately
+ synchronized (mViewCache) {
+ int cacheIndex = getCacheIndex(position);
+ FrameLayout flipper = mViewCache[cacheIndex].flipper;
+
+ if (indexInfo == null) {
+ // hide the item view and show the loading view
+ flipper.getChildAt(0).setVisibility(View.VISIBLE);
+ for (int i = 1; i < flipper.getChildCount(); ++i) {
+ flipper.getChildAt(i).setVisibility(View.GONE);
+ }
+ flipper.requestLayout();
+ flipper.invalidate();
+ } else {
+ // hide the loading view and show the item view
+ for (int i = 0; i < flipper.getChildCount() - 1; ++i) {
+ flipper.getChildAt(i).setVisibility(View.GONE);
+ }
+ flipper.getChildAt(flipper.getChildCount() - 1).setVisibility(View.VISIBLE);
+ flipper.requestLayout();
+ flipper.invalidate();
+ }
+ return flipper;
+ }
+ }
+ return new View(mContext);
+ }
+
+ private void createInitialLoadingFlipperViews(ViewGroup parent) {
+ // ensure that the cache has the appropriate initial flipper
+ synchronized (mViewCache) {
+ if (mViewCache[0].flipper == null) {
+ for (int i = 0; i < mViewCache.length; ++i) {
+ FrameLayout flipper = new FrameLayout(mContext);
+ if (mUserLoadingView != null) {
+ // use the user-specified loading view
+ flipper.addView(mUserLoadingView.apply(mContext, parent));
+ } else {
+ // calculate the original size of the first row for the loader view
+ synchronized (mViewCacheInfo) {
+ if (mFirstViewHeight < 0) {
+ View firstView = mFirstView.apply(mContext, parent);
+ firstView.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
+ MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
+ mFirstViewHeight = firstView.getMeasuredHeight();
+ }
+ }
+
+ // construct a new loader and add it to the flipper as the fallback
+ // default view
+ TextView textView = new TextView(mContext);
+ textView.setText("Loading...");
+ textView.setHeight(mFirstViewHeight);
+ textView.setGravity(Gravity.CENTER_HORIZONTAL | Gravity.CENTER_VERTICAL);
+ textView.setTextSize(18.0f);
+ textView.setTextColor(Color.argb(96, 255, 255, 255));
+ textView.setShadowLayer(2.0f, 0.0f, 1.0f, Color.BLACK);
+
+ flipper.addView(textView);
+ }
+ mViewCache[i].flipper = flipper;
+ }
+ }
+ }
+ }
+
+ public long getItemId(int position) {
+ synchronized (mViewCache) {
+ if (containsAndIsValid(position)) {
+ return mViewCache[getCacheIndex(position)].itemId;
+ }
+ }
+ return 0;
+ }
+
+ public int getItemViewType(int position) {
+ // synchronize to ensure that the type id/index map is updated synchronously
+ synchronized (mViewCache) {
+ if (containsAndIsValid(position)) {
+ int viewId = mViewCache[getCacheIndex(position)].typeId;
+ Map<Integer, Integer> typeMap = mViewCacheInfo.mTypeIdIndexMap;
+ // we +1 because the default dummy view get view type 0
+ if (typeMap.containsKey(viewId)) {
+ return typeMap.get(viewId);
+ } else {
+ int newIndex = typeMap.size() + 1;
+ typeMap.put(viewId, newIndex);
+ return newIndex;
+ }
+ }
+ }
+ // return the type of the default item
+ return 0;
+ }
+
+ public int getCount() {
+ synchronized (mViewCacheInfo) {
+ return mViewCacheInfo.count;
+ }
+ }
+
+ public int getViewTypeCount() {
+ synchronized (mViewCacheInfo) {
+ return mViewCacheInfo.viewTypeCount;
+ }
+ }
+
+ public boolean hasStableIds() {
+ synchronized (mViewCacheInfo) {
+ return mViewCacheInfo.hasStableIds;
+ }
+ }
+
+ public void flushCache() {
+ synchronized (mViewCache) {
+ // flush the internal cache and invalidate the adapter for future loads
+ mWorkerQueue.removeMessages(0);
+ mMainQueue.removeMessages(0);
+
+ for (int i = 0; i < mViewCache.length; ++i) {
+ mViewCache[i].invalidate();
+ }
+
+ mViewCacheStartPosition = 0;
+ mViewCacheEndPosition = -1;
+ }
+ }
+ }
+
+ public RemoteViewsAdapter(Context context, Intent intent, RemoteAdapterConnectionCallback callback) {
+ mContext = context;
+ mIntent = intent;
+
+ // initialize the worker thread
+ mWorkerThread = new HandlerThread("RemoteViewsCache-loader");
+ mWorkerThread.start();
+ mWorkerQueue = new Handler(mWorkerThread.getLooper());
+ mWorkerPriorityQueue = new Handler(mWorkerThread.getLooper());
+ mMainQueue = new Handler(Looper.myLooper());
+ mMainPriorityQueue = new Handler(Looper.myLooper());
+
+ // initialize the cache and the service connection on startup
+ mViewCache = new RemoteViewsCache(25);
+ mServiceConnection = new RemoteViewsAdapterServiceConnection(callback);
+ requestBindService();
+ }
+
+ protected void finalize() throws Throwable {
+ // remember to unbind from the service when finalizing
+ unbindService();
+ }
+
+ public int getCount() {
+ requestBindService();
+ return mViewCache.getCount();
+ }
+
+ public Object getItem(int position) {
+ // disallow arbitrary object to be associated with an item for the time being
+ return null;
+ }
+
+ public long getItemId(int position) {
+ requestBindService();
+ return mViewCache.getItemId(position);
+ }
+
+ public int getItemViewType(int position) {
+ requestBindService();
+ return mViewCache.getItemViewType(position);
+ }
+
+ public View getView(int position, View convertView, ViewGroup parent) {
+ requestBindService();
+ return mViewCache.getView(position, convertView, parent);
+ }
+
+ public int getViewTypeCount() {
+ requestBindService();
+ return mViewCache.getViewTypeCount();
+ }
+
+ public boolean hasStableIds() {
+ requestBindService();
+ return mViewCache.hasStableIds();
+ }
+
+ public boolean isEmpty() {
+ return getCount() <= 0;
+ }
+
+ public void notifyDataSetChanged() {
+ // flush the cache so that we can reload new items from the service
+ mViewCache.flushCache();
+ super.notifyDataSetChanged();
+ }
+
+ private boolean requestBindService() {
+ // try binding the service (which will start it if it's not already running)
+ if (!mServiceConnection.isConnected()) {
+ mContext.bindService(mIntent, mServiceConnection, Context.BIND_AUTO_CREATE);
+ }
+
+ return mServiceConnection.isConnected();
+ }
+
+ private void unbindService() {
+ if (mServiceConnection.isConnected()) {
+ mContext.unbindService(mServiceConnection);
+ }
+ }
+}
diff --git a/core/java/android/widget/RemoteViewsService.java b/core/java/android/widget/RemoteViewsService.java
new file mode 100644
index 0000000..7c9d7ff
--- /dev/null
+++ b/core/java/android/widget/RemoteViewsService.java
@@ -0,0 +1,140 @@
+/*
+ * 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.widget;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+import android.util.Pair;
+
+import com.android.internal.widget.IRemoteViewsFactory;
+
+/**
+ * The service to be connected to for a remote adapter to request RemoteViews. Users should
+ * extend the RemoteViewsService to provide the appropriate RemoteViewsFactory's used to
+ * populate the remote collection view (ListView, GridView, etc).
+ */
+public abstract class RemoteViewsService extends Service {
+
+ private static final String LOG_TAG = "RemoteViewsService";
+
+ // multimap implementation for reference counting
+ private HashMap<Intent, Pair<RemoteViewsFactory, Integer>> mRemoteViewFactories;
+ private final Object mLock = new Object();
+
+ /**
+ * An interface for an adapter between a remote collection view (ListView, GridView, etc) and
+ * the underlying data for that view. The implementor is responsible for making a RemoteView
+ * for each item in the data set.
+ */
+ public interface RemoteViewsFactory {
+ public void onCreate();
+ public void onDestroy();
+
+ public int getCount();
+ public RemoteViews getViewAt(int position);
+ public RemoteViews getLoadingView();
+ public int getViewTypeCount();
+ public long getItemId(int position);
+ public boolean hasStableIds();
+ }
+
+ /**
+ * A private proxy class for the private IRemoteViewsFactory interface through the
+ * public RemoteViewsFactory interface.
+ */
+ private class RemoteViewsFactoryAdapter extends IRemoteViewsFactory.Stub {
+ public RemoteViewsFactoryAdapter(RemoteViewsFactory factory) {
+ mFactory = factory;
+ }
+
+ public int getCount() {
+ return mFactory.getCount();
+ }
+ public RemoteViews getViewAt(int position) {
+ return mFactory.getViewAt(position);
+ }
+ public RemoteViews getLoadingView() {
+ return mFactory.getLoadingView();
+ }
+ public int getViewTypeCount() {
+ return mFactory.getViewTypeCount();
+ }
+ public long getItemId(int position) {
+ return mFactory.getItemId(position);
+ }
+ public boolean hasStableIds() {
+ return mFactory.hasStableIds();
+ }
+
+ private RemoteViewsFactory mFactory;
+ }
+
+ public RemoteViewsService() {
+ mRemoteViewFactories = new HashMap<Intent, Pair<RemoteViewsFactory, Integer>>();
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ synchronized (mLock) {
+ // increment the reference count to the particular factory associated with this intent
+ Pair<RemoteViewsFactory, Integer> factoryRef = null;
+ RemoteViewsFactory factory = null;
+ if (!mRemoteViewFactories.containsKey(intent)) {
+ factory = onGetViewFactory(intent);
+ factoryRef = new Pair<RemoteViewsFactory, Integer>(factory, 1);
+ mRemoteViewFactories.put(intent, factoryRef);
+ factory.onCreate();
+ } else {
+ Pair<RemoteViewsFactory, Integer> oldFactoryRef = mRemoteViewFactories.get(intent);
+ factory = oldFactoryRef.first;
+ int newRefCount = oldFactoryRef.second.intValue() + 1;
+ factoryRef = new Pair<RemoteViewsFactory, Integer>(oldFactoryRef.first, newRefCount);
+ mRemoteViewFactories.put(intent, factoryRef);
+ }
+ return new RemoteViewsFactoryAdapter(factory);
+ }
+ }
+
+ @Override
+ public boolean onUnbind(Intent intent) {
+ synchronized (mLock) {
+ if (mRemoteViewFactories.containsKey(intent)) {
+ // this alleviates the user's responsibility of having to clear all factories
+ Pair<RemoteViewsFactory, Integer> oldFactoryRef = mRemoteViewFactories.get(intent);
+ int newRefCount = oldFactoryRef.second.intValue() - 1;
+ if (newRefCount <= 0) {
+ oldFactoryRef.first.onDestroy();
+ mRemoteViewFactories.remove(intent);
+ } else {
+ Pair<RemoteViewsFactory, Integer> factoryRef = new Pair<RemoteViewsFactory, Integer>(oldFactoryRef.first, newRefCount);
+ mRemoteViewFactories.put(intent, factoryRef);
+ }
+ }
+ }
+ return super.onUnbind(intent);
+ }
+
+ /**
+ * To be implemented by the derived service to generate appropriate factories for
+ * the data.
+ */
+ public abstract RemoteViewsFactory onGetViewFactory(Intent intent);
+}
diff --git a/core/java/com/android/internal/appwidget/IAppWidgetHost.aidl b/core/java/com/android/internal/appwidget/IAppWidgetHost.aidl
index 2ed4773..f0920d1 100644
--- a/core/java/com/android/internal/appwidget/IAppWidgetHost.aidl
+++ b/core/java/com/android/internal/appwidget/IAppWidgetHost.aidl
@@ -24,5 +24,6 @@ import android.widget.RemoteViews;
oneway interface IAppWidgetHost {
void updateAppWidget(int appWidgetId, in RemoteViews views);
void providerChanged(int appWidgetId, in AppWidgetProviderInfo info);
+ void viewDataChanged(int appWidgetId, in RemoteViews views, int viewId);
}
diff --git a/core/java/com/android/internal/appwidget/IAppWidgetService.aidl b/core/java/com/android/internal/appwidget/IAppWidgetService.aidl
index 496aa1a..af75d5b 100644
--- a/core/java/com/android/internal/appwidget/IAppWidgetService.aidl
+++ b/core/java/com/android/internal/appwidget/IAppWidgetService.aidl
@@ -41,6 +41,7 @@ interface IAppWidgetService {
//
void updateAppWidgetIds(in int[] appWidgetIds, in RemoteViews views);
void updateAppWidgetProvider(in ComponentName provider, in RemoteViews views);
+ void notifyAppWidgetViewDataChanged(in int[] appWidgetIds, in RemoteViews views, int viewId);
List<AppWidgetProviderInfo> getInstalledProviders();
AppWidgetProviderInfo getAppWidgetInfo(int appWidgetId);
void bindAppWidgetId(int appWidgetId, in ComponentName provider);
diff --git a/core/java/com/android/internal/widget/IRemoteViewsFactory.aidl b/core/java/com/android/internal/widget/IRemoteViewsFactory.aidl
new file mode 100644
index 0000000..7851347
--- /dev/null
+++ b/core/java/com/android/internal/widget/IRemoteViewsFactory.aidl
@@ -0,0 +1,30 @@
+/*
+ * 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 com.android.internal.widget;
+
+import android.widget.RemoteViews;
+
+/** {@hide} */
+interface IRemoteViewsFactory {
+ int getCount();
+ RemoteViews getViewAt(int position);
+ RemoteViews getLoadingView();
+ int getViewTypeCount();
+ long getItemId(int position);
+ boolean hasStableIds();
+}
+
diff --git a/services/java/com/android/server/AppWidgetService.java b/services/java/com/android/server/AppWidgetService.java
index 1674221..3059732 100644
--- a/services/java/com/android/server/AppWidgetService.java
+++ b/services/java/com/android/server/AppWidgetService.java
@@ -425,6 +425,23 @@ class AppWidgetService extends IAppWidgetService.Stub
}
}
+ public void notifyAppWidgetViewDataChanged(int[] appWidgetIds, RemoteViews views, int viewId) {
+ if (appWidgetIds == null) {
+ return;
+ }
+ if (appWidgetIds.length == 0) {
+ return;
+ }
+ final int N = appWidgetIds.length;
+
+ synchronized (mAppWidgetIds) {
+ for (int i=0; i<N; i++) {
+ AppWidgetId id = lookupAppWidgetIdLocked(appWidgetIds[i]);
+ notifyAppWidgetViewDataChangedInstanceLocked(id, views, viewId);
+ }
+ }
+ }
+
public void updateAppWidgetProvider(ComponentName provider, RemoteViews views) {
synchronized (mAppWidgetIds) {
Provider p = lookupProviderLocked(provider);
@@ -462,6 +479,27 @@ class AppWidgetService extends IAppWidgetService.Stub
}
}
+ void notifyAppWidgetViewDataChangedInstanceLocked(AppWidgetId id, RemoteViews views, int viewId) {
+ // allow for stale appWidgetIds and other badness
+ // lookup also checks that the calling process can access the appWidgetId
+ // drop unbound appWidgetIds (shouldn't be possible under normal circumstances)
+ if (id != null && id.provider != null && !id.provider.zombie && !id.host.zombie) {
+ id.views = views;
+
+ // is anyone listening?
+ if (id.host.callbacks != null) {
+ try {
+ // the lock is held, but this is a oneway call
+ id.host.callbacks.viewDataChanged(id.appWidgetId, views, viewId);
+ } catch (RemoteException e) {
+ // It failed; remove the callback. No need to prune because
+ // we know that this host is still referenced by this instance.
+ id.host.callbacks = null;
+ }
+ }
+ }
+ }
+
public int[] startListening(IAppWidgetHost callbacks, String packageName, int hostId,
List<RemoteViews> updatedViews) {
int callingUid = enforceCallingUid(packageName);