Initial changes to allow collections in widgets.
Change-Id: I3cfa899bae88cd252912cecebc12e93c27a3b7c9
This commit is contained in:
@@ -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 \
|
||||
|
||||
300
api/current.xml
300
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"
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
666
core/java/android/widget/RemoteViewsAdapter.java
Normal file
666
core/java/android/widget/RemoteViewsAdapter.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
140
core/java/android/widget/RemoteViewsService.java
Normal file
140
core/java/android/widget/RemoteViewsService.java
Normal 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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user