Initial changes to allow collections in widgets.

Change-Id: I3cfa899bae88cd252912cecebc12e93c27a3b7c9
This commit is contained in:
Winson Chung
2010-07-16 11:18:17 -07:00
parent b5b37f3bcc
commit 499cb9f516
15 changed files with 1436 additions and 2 deletions

View File

@@ -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 \

View File

@@ -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"

View File

@@ -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);
}
}
}

View File

@@ -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;
@@ -257,6 +260,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.

View File

@@ -287,6 +287,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.
*/

View File

@@ -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.
@@ -179,6 +181,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
*/
@@ -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);
@@ -3904,6 +3947,34 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
return result;
}
/**
* 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

View File

@@ -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;
@@ -106,6 +109,16 @@ public class GridView extends AbsListView {
return mAdapter;
}
/**
* 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.
*
@@ -739,6 +752,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

View File

@@ -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.
@@ -400,6 +403,16 @@ public class ListView extends AbsListView {
return mAdapter;
}
/**
* 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.
*
@@ -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

View File

@@ -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;
}
@@ -769,6 +778,37 @@ public class RemoteViews implements Parcelable, Filter {
setInt(viewId, "setTextColor", color);
}
/**
* 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.
*
@@ -915,6 +955,16 @@ public class RemoteViews implements Parcelable, Filter {
addAction(new ReflectionAction(viewId, methodName, ReflectionAction.BUNDLE, value));
}
/**
*
* @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.

View File

@@ -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);
}
}
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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);

View File

@@ -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();
}

View File

@@ -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);