Merge "Re-enabling queued unbinding of services after requests to the service. (3394210)" into honeycomb
This commit is contained in:
@ -238854,7 +238854,7 @@
|
|||||||
>
|
>
|
||||||
</method>
|
</method>
|
||||||
<method name="onRemoteAdapterConnected"
|
<method name="onRemoteAdapterConnected"
|
||||||
return="void"
|
return="boolean"
|
||||||
abstract="false"
|
abstract="false"
|
||||||
native="false"
|
native="false"
|
||||||
synchronized="false"
|
synchronized="false"
|
||||||
@ -240832,7 +240832,7 @@
|
|||||||
>
|
>
|
||||||
</method>
|
</method>
|
||||||
<method name="onRemoteAdapterConnected"
|
<method name="onRemoteAdapterConnected"
|
||||||
return="void"
|
return="boolean"
|
||||||
abstract="false"
|
abstract="false"
|
||||||
native="false"
|
native="false"
|
||||||
synchronized="false"
|
synchronized="false"
|
||||||
|
@ -238864,7 +238864,7 @@
|
|||||||
>
|
>
|
||||||
</method>
|
</method>
|
||||||
<method name="onRemoteAdapterConnected"
|
<method name="onRemoteAdapterConnected"
|
||||||
return="void"
|
return="boolean"
|
||||||
abstract="false"
|
abstract="false"
|
||||||
native="false"
|
native="false"
|
||||||
synchronized="false"
|
synchronized="false"
|
||||||
@ -240842,7 +240842,7 @@
|
|||||||
>
|
>
|
||||||
</method>
|
</method>
|
||||||
<method name="onRemoteAdapterConnected"
|
<method name="onRemoteAdapterConnected"
|
||||||
return="void"
|
return="boolean"
|
||||||
abstract="false"
|
abstract="false"
|
||||||
native="false"
|
native="false"
|
||||||
synchronized="false"
|
synchronized="false"
|
||||||
|
@ -5289,12 +5289,15 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
|
|||||||
/**
|
/**
|
||||||
* Called back when the adapter connects to the RemoteViewsService.
|
* Called back when the adapter connects to the RemoteViewsService.
|
||||||
*/
|
*/
|
||||||
public void onRemoteAdapterConnected() {
|
public boolean onRemoteAdapterConnected() {
|
||||||
if (mRemoteAdapter != mAdapter) {
|
if (mRemoteAdapter != mAdapter) {
|
||||||
setAdapter(mRemoteAdapter);
|
setAdapter(mRemoteAdapter);
|
||||||
|
return false;
|
||||||
} else if (mRemoteAdapter != null) {
|
} else if (mRemoteAdapter != null) {
|
||||||
mRemoteAdapter.superNotifyDataSetChanged();
|
mRemoteAdapter.superNotifyDataSetChanged();
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -53,6 +53,12 @@ public abstract class AdapterViewAnimator extends AdapterView<Adapter>
|
|||||||
*/
|
*/
|
||||||
int mWhichChild = 0;
|
int mWhichChild = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The index of the child to restore after the asynchronous connection from the
|
||||||
|
* RemoteViewsAdapter has been.
|
||||||
|
*/
|
||||||
|
private int mRestoreWhichChild = -1;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether or not the first view(s) should be animated in
|
* Whether or not the first view(s) should be animated in
|
||||||
*/
|
*/
|
||||||
@ -780,7 +786,15 @@ public abstract class AdapterViewAnimator extends AdapterView<Adapter>
|
|||||||
// set mWhichChild
|
// set mWhichChild
|
||||||
mWhichChild = ss.whichChild;
|
mWhichChild = ss.whichChild;
|
||||||
|
|
||||||
setDisplayedChild(mWhichChild);
|
// When using RemoteAdapters, the async connection process can lead to
|
||||||
|
// onRestoreInstanceState to be called before setAdapter(), so we need to save the previous
|
||||||
|
// values to restore the list position after we connect, and can skip setting the displayed
|
||||||
|
// child until then.
|
||||||
|
if (mRemoteViewsAdapter != null && mAdapter == null) {
|
||||||
|
mRestoreWhichChild = mWhichChild;
|
||||||
|
} else {
|
||||||
|
setDisplayedChild(mWhichChild);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -957,12 +971,21 @@ public abstract class AdapterViewAnimator extends AdapterView<Adapter>
|
|||||||
/**
|
/**
|
||||||
* Called back when the adapter connects to the RemoteViewsService.
|
* Called back when the adapter connects to the RemoteViewsService.
|
||||||
*/
|
*/
|
||||||
public void onRemoteAdapterConnected() {
|
public boolean onRemoteAdapterConnected() {
|
||||||
if (mRemoteViewsAdapter != mAdapter) {
|
if (mRemoteViewsAdapter != mAdapter) {
|
||||||
setAdapter(mRemoteViewsAdapter);
|
setAdapter(mRemoteViewsAdapter);
|
||||||
|
|
||||||
|
// Restore the previous position (see onRestoreInstanceState)
|
||||||
|
if (mRestoreWhichChild > -1) {
|
||||||
|
setDisplayedChild(mRestoreWhichChild);
|
||||||
|
mRestoreWhichChild = -1;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
} else if (mRemoteViewsAdapter != null) {
|
} else if (mRemoteViewsAdapter != null) {
|
||||||
mRemoteViewsAdapter.superNotifyDataSetChanged();
|
mRemoteViewsAdapter.superNotifyDataSetChanged();
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -995,7 +1018,7 @@ public abstract class AdapterViewAnimator extends AdapterView<Adapter>
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onDetachedFromWindow() {
|
protected void onDetachedFromWindow() {
|
||||||
mAdapter = null;
|
setAdapter(null);
|
||||||
super.onDetachedFromWindow();
|
super.onDetachedFromWindow();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -48,11 +48,11 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
|
|||||||
private static final String TAG = "RemoteViewsAdapter";
|
private static final String TAG = "RemoteViewsAdapter";
|
||||||
|
|
||||||
// The max number of items in the cache
|
// The max number of items in the cache
|
||||||
private static final int sDefaultCacheSize = 36;
|
private static final int sDefaultCacheSize = 50;
|
||||||
// The delay (in millis) to wait until attempting to unbind from a service after a request.
|
// The delay (in millis) to wait until attempting to unbind from a service after a request.
|
||||||
// This ensures that we don't stay continually bound to the service and that it can be destroyed
|
// This ensures that we don't stay continually bound to the service and that it can be destroyed
|
||||||
// if we need the memory elsewhere in the system.
|
// if we need the memory elsewhere in the system.
|
||||||
private static final int sUnbindServiceDelay = 5000;
|
private static final int sUnbindServiceDelay = 1000;
|
||||||
// Type defs for controlling different messages across the main and worker message queues
|
// Type defs for controlling different messages across the main and worker message queues
|
||||||
private static final int sDefaultMessageType = 0;
|
private static final int sDefaultMessageType = 0;
|
||||||
private static final int sUnbindServiceMessageType = 1;
|
private static final int sUnbindServiceMessageType = 1;
|
||||||
@ -65,6 +65,9 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
|
|||||||
private WeakReference<RemoteAdapterConnectionCallback> mCallback;
|
private WeakReference<RemoteAdapterConnectionCallback> mCallback;
|
||||||
private FixedSizeRemoteViewsCache mCache;
|
private FixedSizeRemoteViewsCache mCache;
|
||||||
|
|
||||||
|
// A flag to determine whether we should notify data set changed after we connect
|
||||||
|
private boolean mNotifyDataSetChangedAfterOnServiceConnected = false;
|
||||||
|
|
||||||
// The set of requested views that are to be notified when the associated RemoteViews are
|
// The set of requested views that are to be notified when the associated RemoteViews are
|
||||||
// loaded.
|
// loaded.
|
||||||
private RemoteViewsFrameLayoutRefSet mRequestedViews;
|
private RemoteViewsFrameLayoutRefSet mRequestedViews;
|
||||||
@ -79,7 +82,10 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
|
|||||||
* are actually connected to/disconnected from their actual services.
|
* are actually connected to/disconnected from their actual services.
|
||||||
*/
|
*/
|
||||||
public interface RemoteAdapterConnectionCallback {
|
public interface RemoteAdapterConnectionCallback {
|
||||||
public void onRemoteAdapterConnected();
|
/**
|
||||||
|
* @return whether the adapter was set or not.
|
||||||
|
*/
|
||||||
|
public boolean onRemoteAdapterConnected();
|
||||||
|
|
||||||
public void onRemoteAdapterDisconnected();
|
public void onRemoteAdapterDisconnected();
|
||||||
}
|
}
|
||||||
@ -93,7 +99,8 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
|
|||||||
*/
|
*/
|
||||||
private static class RemoteViewsAdapterServiceConnection extends
|
private static class RemoteViewsAdapterServiceConnection extends
|
||||||
IRemoteViewsAdapterConnection.Stub {
|
IRemoteViewsAdapterConnection.Stub {
|
||||||
private boolean mConnected;
|
private boolean mIsConnected;
|
||||||
|
private boolean mIsConnecting;
|
||||||
private WeakReference<RemoteViewsAdapter> mAdapter;
|
private WeakReference<RemoteViewsAdapter> mAdapter;
|
||||||
private IRemoteViewsFactory mRemoteViewsFactory;
|
private IRemoteViewsFactory mRemoteViewsFactory;
|
||||||
|
|
||||||
@ -101,27 +108,58 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
|
|||||||
mAdapter = new WeakReference<RemoteViewsAdapter>(adapter);
|
mAdapter = new WeakReference<RemoteViewsAdapter>(adapter);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onServiceConnected(IBinder service) {
|
public synchronized void bind(Context context, int appWidgetId, Intent intent) {
|
||||||
mRemoteViewsFactory = IRemoteViewsFactory.Stub.asInterface(service);
|
if (!mIsConnecting) {
|
||||||
mConnected = true;
|
try {
|
||||||
|
final AppWidgetManager mgr = AppWidgetManager.getInstance(context);
|
||||||
|
mgr.bindRemoteViewsService(appWidgetId, intent, asBinder());
|
||||||
|
mIsConnecting = true;
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e("RemoteViewsAdapterServiceConnection", "bind(): " + e.getMessage());
|
||||||
|
mIsConnecting = false;
|
||||||
|
mIsConnected = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Queue up work that we need to do for the callback to run
|
public synchronized void unbind(Context context, int appWidgetId, Intent intent) {
|
||||||
|
try {
|
||||||
|
final AppWidgetManager mgr = AppWidgetManager.getInstance(context);
|
||||||
|
mgr.unbindRemoteViewsService(appWidgetId, intent);
|
||||||
|
mIsConnecting = false;
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e("RemoteViewsAdapterServiceConnection", "unbind(): " + e.getMessage());
|
||||||
|
mIsConnecting = false;
|
||||||
|
mIsConnected = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void onServiceConnected(IBinder service) {
|
||||||
|
mRemoteViewsFactory = IRemoteViewsFactory.Stub.asInterface(service);
|
||||||
|
|
||||||
|
// Remove any deferred unbind messages
|
||||||
final RemoteViewsAdapter adapter = mAdapter.get();
|
final RemoteViewsAdapter adapter = mAdapter.get();
|
||||||
if (adapter == null) return;
|
if (adapter == null) return;
|
||||||
|
|
||||||
|
// Queue up work that we need to do for the callback to run
|
||||||
adapter.mWorkerQueue.post(new Runnable() {
|
adapter.mWorkerQueue.post(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
// Call back to the service to notify that the data set changed
|
if (adapter.mNotifyDataSetChangedAfterOnServiceConnected) {
|
||||||
if (adapter.mServiceConnection.isConnected()) {
|
// Handle queued notifyDataSetChanged() if necessary
|
||||||
|
adapter.onNotifyDataSetChanged();
|
||||||
|
} else {
|
||||||
IRemoteViewsFactory factory =
|
IRemoteViewsFactory factory =
|
||||||
adapter.mServiceConnection.getRemoteViewsFactory();
|
adapter.mServiceConnection.getRemoteViewsFactory();
|
||||||
try {
|
try {
|
||||||
// call back to the factory
|
if (!factory.isCreated()) {
|
||||||
factory.onDataSetChanged();
|
// We only call onDataSetChanged() if this is the factory was just
|
||||||
|
// create in response to this bind
|
||||||
|
factory.onDataSetChanged();
|
||||||
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Log.e(TAG, "Error notifying factory of data set changed in " +
|
Log.e(TAG, "Error notifying factory of data set changed in " +
|
||||||
"onServiceConnected(): " + e.getMessage());
|
"onServiceConnected(): " + e.getMessage());
|
||||||
e.printStackTrace();
|
|
||||||
|
|
||||||
// Return early to prevent anything further from being notified
|
// Return early to prevent anything further from being notified
|
||||||
// (effectively nothing has changed)
|
// (effectively nothing has changed)
|
||||||
@ -130,13 +168,16 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
|
|||||||
|
|
||||||
// Request meta data so that we have up to date data when calling back to
|
// Request meta data so that we have up to date data when calling back to
|
||||||
// the remote adapter callback
|
// the remote adapter callback
|
||||||
adapter.updateMetaData();
|
adapter.updateTemporaryMetaData();
|
||||||
|
|
||||||
// Post a runnable to call back to the view to notify it that we have
|
// Notify the host that we've connected
|
||||||
// connected
|
|
||||||
adapter.mMainQueue.post(new Runnable() {
|
adapter.mMainQueue.post(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
|
synchronized (adapter.mCache) {
|
||||||
|
adapter.mCache.commitTemporaryMetaData();
|
||||||
|
}
|
||||||
|
|
||||||
final RemoteAdapterConnectionCallback callback =
|
final RemoteAdapterConnectionCallback callback =
|
||||||
adapter.mCallback.get();
|
adapter.mCallback.get();
|
||||||
if (callback != null) {
|
if (callback != null) {
|
||||||
@ -145,35 +186,44 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Enqueue unbind message
|
||||||
|
adapter.enqueueDeferredUnbindServiceMessage();
|
||||||
|
mIsConnected = true;
|
||||||
|
mIsConnecting = false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onServiceDisconnected() {
|
public synchronized void onServiceDisconnected() {
|
||||||
mConnected = false;
|
mIsConnected = false;
|
||||||
|
mIsConnecting = false;
|
||||||
mRemoteViewsFactory = null;
|
mRemoteViewsFactory = null;
|
||||||
|
|
||||||
|
// Clear the main/worker queues
|
||||||
final RemoteViewsAdapter adapter = mAdapter.get();
|
final RemoteViewsAdapter adapter = mAdapter.get();
|
||||||
if (adapter == null) return;
|
if (adapter == null) return;
|
||||||
|
|
||||||
// Clear the main/worker queues
|
adapter.mMainQueue.post(new Runnable() {
|
||||||
adapter.mMainQueue.removeMessages(sUnbindServiceMessageType);
|
@Override
|
||||||
adapter.mMainQueue.removeMessages(sDefaultMessageType);
|
public void run() {
|
||||||
adapter.mWorkerQueue.removeMessages(sDefaultMessageType);
|
// Dequeue any unbind messages
|
||||||
|
adapter.mMainQueue.removeMessages(sUnbindServiceMessageType);
|
||||||
|
|
||||||
final RemoteAdapterConnectionCallback callback = adapter.mCallback.get();
|
final RemoteAdapterConnectionCallback callback = adapter.mCallback.get();
|
||||||
if (callback != null) {
|
if (callback != null) {
|
||||||
callback.onRemoteAdapterDisconnected();
|
callback.onRemoteAdapterDisconnected();
|
||||||
}
|
}
|
||||||
adapter.mCache.reset();
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public IRemoteViewsFactory getRemoteViewsFactory() {
|
public synchronized IRemoteViewsFactory getRemoteViewsFactory() {
|
||||||
return mRemoteViewsFactory;
|
return mRemoteViewsFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isConnected() {
|
public synchronized boolean isConnected() {
|
||||||
return mConnected;
|
return mIsConnected;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -270,7 +320,6 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
|
|||||||
int count;
|
int count;
|
||||||
int viewTypeCount;
|
int viewTypeCount;
|
||||||
boolean hasStableIds;
|
boolean hasStableIds;
|
||||||
boolean isDataDirty;
|
|
||||||
|
|
||||||
// Used to determine how to construct loading views. If a loading view is not specified
|
// Used to determine how to construct loading views. If a loading view is not specified
|
||||||
// by the user, then we try and load the first view, and use its height as the height for
|
// by the user, then we try and load the first view, and use its height as the height for
|
||||||
@ -280,22 +329,31 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
|
|||||||
int mFirstViewHeight;
|
int mFirstViewHeight;
|
||||||
|
|
||||||
// A mapping from type id to a set of unique type ids
|
// A mapping from type id to a set of unique type ids
|
||||||
private Map<Integer, Integer> mTypeIdIndexMap;
|
private final HashMap<Integer, Integer> mTypeIdIndexMap = new HashMap<Integer, Integer>();
|
||||||
|
|
||||||
public RemoteViewsMetaData() {
|
public RemoteViewsMetaData() {
|
||||||
reset();
|
reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void set(RemoteViewsMetaData d) {
|
||||||
|
synchronized (d) {
|
||||||
|
count = d.count;
|
||||||
|
viewTypeCount = d.viewTypeCount;
|
||||||
|
hasStableIds = d.hasStableIds;
|
||||||
|
setLoadingViewTemplates(d.mUserLoadingView, d.mFirstView);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void reset() {
|
public void reset() {
|
||||||
count = 0;
|
count = 0;
|
||||||
|
|
||||||
// by default there is at least one dummy view type
|
// by default there is at least one dummy view type
|
||||||
viewTypeCount = 1;
|
viewTypeCount = 1;
|
||||||
hasStableIds = true;
|
hasStableIds = true;
|
||||||
isDataDirty = false;
|
|
||||||
mUserLoadingView = null;
|
mUserLoadingView = null;
|
||||||
mFirstView = null;
|
mFirstView = null;
|
||||||
mFirstViewHeight = 0;
|
mFirstViewHeight = 0;
|
||||||
mTypeIdIndexMap = new HashMap<Integer, Integer>();
|
mTypeIdIndexMap.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setLoadingViewTemplates(RemoteViews loadingView, RemoteViews firstView) {
|
public void setLoadingViewTemplates(RemoteViews loadingView, RemoteViews firstView) {
|
||||||
@ -385,6 +443,7 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
|
|||||||
|
|
||||||
// The meta data related to all the RemoteViews, ie. count, is stable, etc.
|
// The meta data related to all the RemoteViews, ie. count, is stable, etc.
|
||||||
private RemoteViewsMetaData mMetaData;
|
private RemoteViewsMetaData mMetaData;
|
||||||
|
private RemoteViewsMetaData mTemporaryMetaData;
|
||||||
|
|
||||||
// The cache/mapping of position to RemoteViewsMetaData. This set is guaranteed to be
|
// The cache/mapping of position to RemoteViewsMetaData. This set is guaranteed to be
|
||||||
// greater than or equal to the set of RemoteViews.
|
// greater than or equal to the set of RemoteViews.
|
||||||
@ -426,6 +485,7 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
|
|||||||
mPreloadLowerBound = 0;
|
mPreloadLowerBound = 0;
|
||||||
mPreloadUpperBound = -1;
|
mPreloadUpperBound = -1;
|
||||||
mMetaData = new RemoteViewsMetaData();
|
mMetaData = new RemoteViewsMetaData();
|
||||||
|
mTemporaryMetaData = new RemoteViewsMetaData();
|
||||||
mIndexMetaData = new HashMap<Integer, RemoteViewsIndexMetaData>();
|
mIndexMetaData = new HashMap<Integer, RemoteViewsIndexMetaData>();
|
||||||
mIndexRemoteViews = new HashMap<Integer, RemoteViews>();
|
mIndexRemoteViews = new HashMap<Integer, RemoteViews>();
|
||||||
mRequestedIndices = new HashSet<Integer>();
|
mRequestedIndices = new HashSet<Integer>();
|
||||||
@ -461,6 +521,9 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
|
|||||||
public RemoteViewsMetaData getMetaData() {
|
public RemoteViewsMetaData getMetaData() {
|
||||||
return mMetaData;
|
return mMetaData;
|
||||||
}
|
}
|
||||||
|
public RemoteViewsMetaData getTemporaryMetaData() {
|
||||||
|
return mTemporaryMetaData;
|
||||||
|
}
|
||||||
public RemoteViews getRemoteViewsAt(int position) {
|
public RemoteViews getRemoteViewsAt(int position) {
|
||||||
if (mIndexRemoteViews.containsKey(position)) {
|
if (mIndexRemoteViews.containsKey(position)) {
|
||||||
return mIndexRemoteViews.get(position);
|
return mIndexRemoteViews.get(position);
|
||||||
@ -474,6 +537,14 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void commitTemporaryMetaData() {
|
||||||
|
synchronized (mTemporaryMetaData) {
|
||||||
|
synchronized (mMetaData) {
|
||||||
|
mMetaData.set(mTemporaryMetaData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private int getRemoteViewsBitmapMemoryUsage() {
|
private int getRemoteViewsBitmapMemoryUsage() {
|
||||||
// Calculate the memory usage of all the RemoteViews bitmaps being cached
|
// Calculate the memory usage of all the RemoteViews bitmaps being cached
|
||||||
int mem = 0;
|
int mem = 0;
|
||||||
@ -505,12 +576,12 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
|
|||||||
mLoadIndices.add(position);
|
mLoadIndices.add(position);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public void queuePositionsToBePreloadedFromRequestedPosition(int position) {
|
public boolean queuePositionsToBePreloadedFromRequestedPosition(int position) {
|
||||||
// Check if we need to preload any items
|
// Check if we need to preload any items
|
||||||
if (mPreloadLowerBound <= position && position <= mPreloadUpperBound) {
|
if (mPreloadLowerBound <= position && position <= mPreloadUpperBound) {
|
||||||
int center = (mPreloadUpperBound + mPreloadLowerBound) / 2;
|
int center = (mPreloadUpperBound + mPreloadLowerBound) / 2;
|
||||||
if (Math.abs(position - center) < mMaxCountSlack) {
|
if (Math.abs(position - center) < mMaxCountSlack) {
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -537,6 +608,7 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
|
|||||||
// But remove all the indices that have already been loaded and are cached
|
// But remove all the indices that have already been loaded and are cached
|
||||||
mLoadIndices.removeAll(mIndexRemoteViews.keySet());
|
mLoadIndices.removeAll(mIndexRemoteViews.keySet());
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
public int getNextIndexToLoad() {
|
public int getNextIndexToLoad() {
|
||||||
// We try and prioritize items that have been requested directly, instead
|
// We try and prioritize items that have been requested directly, instead
|
||||||
@ -616,100 +688,114 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
|
|||||||
mWorkerQueue.post(new Runnable() {
|
mWorkerQueue.post(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
// Get the next index to load
|
if (mServiceConnection.isConnected()) {
|
||||||
int position = -1;
|
// Get the next index to load
|
||||||
synchronized (mCache) {
|
int position = -1;
|
||||||
position = mCache.getNextIndexToLoad();
|
synchronized (mCache) {
|
||||||
}
|
position = mCache.getNextIndexToLoad();
|
||||||
if (position > -1) {
|
}
|
||||||
// Load the item, and notify any existing RemoteViewsFrameLayouts
|
if (position > -1) {
|
||||||
updateRemoteViews(position);
|
// Load the item, and notify any existing RemoteViewsFrameLayouts
|
||||||
|
updateRemoteViews(position);
|
||||||
|
|
||||||
// Queue up for the next one to load
|
// Queue up for the next one to load
|
||||||
loadNextIndexInBackground();
|
loadNextIndexInBackground();
|
||||||
|
} else {
|
||||||
|
// No more items to load, so queue unbind
|
||||||
|
enqueueDeferredUnbindServiceMessage();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateMetaData() {
|
private void processException(String method, Exception e) {
|
||||||
if (mServiceConnection.isConnected()) {
|
Log.e("RemoteViewsAdapter", "Error in " + method + ": " + e.getMessage());
|
||||||
try {
|
|
||||||
IRemoteViewsFactory factory = mServiceConnection.getRemoteViewsFactory();
|
|
||||||
|
|
||||||
// get the properties/first view (so that we can use it to
|
// If we encounter a crash when updating, we should reset the metadata & cache and trigger
|
||||||
// measure our dummy views)
|
// a notifyDataSetChanged to update the widget accordingly
|
||||||
boolean hasStableIds = factory.hasStableIds();
|
final RemoteViewsMetaData metaData = mCache.getMetaData();
|
||||||
int viewTypeCount = factory.getViewTypeCount();
|
synchronized (metaData) {
|
||||||
int count = factory.getCount();
|
metaData.reset();
|
||||||
RemoteViews loadingView = factory.getLoadingView();
|
}
|
||||||
RemoteViews firstView = null;
|
synchronized (mCache) {
|
||||||
if ((count > 0) && (loadingView == null)) {
|
mCache.reset();
|
||||||
firstView = factory.getViewAt(0);
|
}
|
||||||
}
|
mMainQueue.post(new Runnable() {
|
||||||
final RemoteViewsMetaData metaData = mCache.getMetaData();
|
@Override
|
||||||
synchronized (metaData) {
|
public void run() {
|
||||||
metaData.hasStableIds = hasStableIds;
|
superNotifyDataSetChanged();
|
||||||
metaData.viewTypeCount = viewTypeCount + 1;
|
|
||||||
metaData.count = count;
|
|
||||||
metaData.setLoadingViewTemplates(loadingView, firstView);
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
// print the error
|
|
||||||
Log.e(TAG, "Error in requestMetaData(): " + e.getMessage());
|
|
||||||
|
|
||||||
// reset any members after the failed call
|
|
||||||
final RemoteViewsMetaData metaData = mCache.getMetaData();
|
|
||||||
synchronized (metaData) {
|
|
||||||
metaData.reset();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateTemporaryMetaData() {
|
||||||
|
IRemoteViewsFactory factory = mServiceConnection.getRemoteViewsFactory();
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
final RemoteViewsMetaData tmpMetaData = mCache.getTemporaryMetaData();
|
||||||
|
synchronized (tmpMetaData) {
|
||||||
|
tmpMetaData.hasStableIds = hasStableIds;
|
||||||
|
// We +1 because the base view type is the loading view
|
||||||
|
tmpMetaData.viewTypeCount = viewTypeCount + 1;
|
||||||
|
tmpMetaData.count = count;
|
||||||
|
tmpMetaData.setLoadingViewTemplates(loadingView, firstView);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
processException("updateMetaData", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateRemoteViews(final int position) {
|
private void updateRemoteViews(final int position) {
|
||||||
if (mServiceConnection.isConnected()) {
|
if (!mServiceConnection.isConnected()) return;
|
||||||
IRemoteViewsFactory factory = mServiceConnection.getRemoteViewsFactory();
|
IRemoteViewsFactory factory = mServiceConnection.getRemoteViewsFactory();
|
||||||
|
|
||||||
// Load the item information from the remote service
|
// Load the item information from the remote service
|
||||||
RemoteViews remoteViews = null;
|
RemoteViews remoteViews = null;
|
||||||
long itemId = 0;
|
long itemId = 0;
|
||||||
try {
|
try {
|
||||||
remoteViews = factory.getViewAt(position);
|
remoteViews = factory.getViewAt(position);
|
||||||
itemId = factory.getItemId(position);
|
itemId = factory.getItemId(position);
|
||||||
} catch (Throwable t) {
|
} catch (Exception e) {
|
||||||
Log.e(TAG, "Error in updateRemoteViews(" + position + "): " + t.getMessage());
|
Log.e(TAG, "Error in updateRemoteViews(" + position + "): " + e.getMessage());
|
||||||
t.printStackTrace();
|
|
||||||
|
|
||||||
// Return early to prevent additional work in re-centering the view cache, and
|
// Return early to prevent additional work in re-centering the view cache, and
|
||||||
// swapping from the loading view
|
// swapping from the loading view
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (remoteViews == null) {
|
if (remoteViews == null) {
|
||||||
// If a null view was returned, we break early to prevent it from getting
|
// If a null view was returned, we break early to prevent it from getting
|
||||||
// into our cache and causing problems later. The effect is that the child at this
|
// into our cache and causing problems later. The effect is that the child at this
|
||||||
// position will remain as a loading view until it is updated.
|
// position will remain as a loading view until it is updated.
|
||||||
Log.e(TAG, "Error in updateRemoteViews(" + position + "): " + " null RemoteViews " +
|
Log.e(TAG, "Error in updateRemoteViews(" + position + "): " + " null RemoteViews " +
|
||||||
"returned from RemoteViewsFactory.");
|
"returned from RemoteViewsFactory.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
synchronized (mCache) {
|
synchronized (mCache) {
|
||||||
// Cache the RemoteViews we loaded
|
// Cache the RemoteViews we loaded
|
||||||
mCache.insert(position, remoteViews, itemId);
|
mCache.insert(position, remoteViews, itemId);
|
||||||
|
|
||||||
// Notify all the views that we have previously returned for this index that
|
// Notify all the views that we have previously returned for this index that
|
||||||
// there is new data for it.
|
// there is new data for it.
|
||||||
final RemoteViews rv = remoteViews;
|
final RemoteViews rv = remoteViews;
|
||||||
final int typeId = mCache.getMetaDataAt(position).typeId;
|
final int typeId = mCache.getMetaDataAt(position).typeId;
|
||||||
mMainQueue.post(new Runnable() {
|
mMainQueue.post(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
mRequestedViews.notifyOnRemoteViewsLoaded(position, rv, typeId);
|
mRequestedViews.notifyOnRemoteViewsLoaded(position, rv, typeId);
|
||||||
enqueueDeferredUnbindServiceMessage();
|
}
|
||||||
}
|
});
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -718,7 +804,6 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
|
|||||||
}
|
}
|
||||||
|
|
||||||
public int getCount() {
|
public int getCount() {
|
||||||
requestBindService();
|
|
||||||
final RemoteViewsMetaData metaData = mCache.getMetaData();
|
final RemoteViewsMetaData metaData = mCache.getMetaData();
|
||||||
synchronized (metaData) {
|
synchronized (metaData) {
|
||||||
return metaData.count;
|
return metaData.count;
|
||||||
@ -731,7 +816,6 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
|
|||||||
}
|
}
|
||||||
|
|
||||||
public long getItemId(int position) {
|
public long getItemId(int position) {
|
||||||
requestBindService();
|
|
||||||
synchronized (mCache) {
|
synchronized (mCache) {
|
||||||
if (mCache.containsMetaDataAt(position)) {
|
if (mCache.containsMetaDataAt(position)) {
|
||||||
return mCache.getMetaDataAt(position).itemId;
|
return mCache.getMetaDataAt(position).itemId;
|
||||||
@ -741,7 +825,6 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
|
|||||||
}
|
}
|
||||||
|
|
||||||
public int getItemViewType(int position) {
|
public int getItemViewType(int position) {
|
||||||
requestBindService();
|
|
||||||
int typeId = 0;
|
int typeId = 0;
|
||||||
synchronized (mCache) {
|
synchronized (mCache) {
|
||||||
if (mCache.containsMetaDataAt(position)) {
|
if (mCache.containsMetaDataAt(position)) {
|
||||||
@ -773,13 +856,23 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
|
|||||||
}
|
}
|
||||||
|
|
||||||
public View getView(int position, View convertView, ViewGroup parent) {
|
public View getView(int position, View convertView, ViewGroup parent) {
|
||||||
requestBindService();
|
// "Request" an index so that we can queue it for loading, initiate subsequent
|
||||||
if (mServiceConnection.isConnected()) {
|
// preloading, etc.
|
||||||
// "Request" an index so that we can queue it for loading, initiate subsequent
|
synchronized (mCache) {
|
||||||
// preloading, etc.
|
boolean isInCache = mCache.containsRemoteViewAt(position);
|
||||||
synchronized (mCache) {
|
boolean isConnected = mServiceConnection.isConnected();
|
||||||
|
boolean hasNewItems = false;
|
||||||
|
|
||||||
|
if (!isConnected) {
|
||||||
|
// Requesting bind service will trigger a super.notifyDataSetChanged(), which will
|
||||||
|
// in turn trigger another request to getView()
|
||||||
|
requestBindService();
|
||||||
|
} else {
|
||||||
// Queue up other indices to be preloaded based on this position
|
// Queue up other indices to be preloaded based on this position
|
||||||
mCache.queuePositionsToBePreloadedFromRequestedPosition(position);
|
hasNewItems = mCache.queuePositionsToBePreloadedFromRequestedPosition(position);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isInCache) {
|
||||||
View convertViewChild = null;
|
View convertViewChild = null;
|
||||||
int convertViewTypeId = 0;
|
int convertViewTypeId = 0;
|
||||||
RemoteViewsFrameLayout layout = null;
|
RemoteViewsFrameLayout layout = null;
|
||||||
@ -792,51 +885,47 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
|
|||||||
|
|
||||||
// Second, we try and retrieve the RemoteViews from the cache, returning a loading
|
// Second, we try and retrieve the RemoteViews from the cache, returning a loading
|
||||||
// view and queueing it to be loaded if it has not already been loaded.
|
// view and queueing it to be loaded if it has not already been loaded.
|
||||||
if (mCache.containsRemoteViewAt(position)) {
|
Context context = parent.getContext();
|
||||||
Context context = parent.getContext();
|
RemoteViews rv = mCache.getRemoteViewsAt(position);
|
||||||
RemoteViews rv = mCache.getRemoteViewsAt(position);
|
int typeId = mCache.getMetaDataAt(position).typeId;
|
||||||
int typeId = mCache.getMetaDataAt(position).typeId;
|
|
||||||
|
|
||||||
// Reuse the convert view where possible
|
// Reuse the convert view where possible
|
||||||
if (layout != null) {
|
if (layout != null) {
|
||||||
if (convertViewTypeId == typeId) {
|
if (convertViewTypeId == typeId) {
|
||||||
rv.reapply(context, convertViewChild);
|
rv.reapply(context, convertViewChild);
|
||||||
return layout;
|
return layout;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
layout.removeAllViews();
|
||||||
// Otherwise, create a new view to be returned
|
|
||||||
View newView = rv.apply(context, parent);
|
|
||||||
newView.setTagInternal(com.android.internal.R.id.rowTypeId, new Integer(typeId));
|
|
||||||
if (layout != null) {
|
|
||||||
layout.removeAllViews();
|
|
||||||
} else {
|
|
||||||
layout = new RemoteViewsFrameLayout(context);
|
|
||||||
}
|
|
||||||
layout.addView(newView);
|
|
||||||
return layout;
|
|
||||||
} else {
|
} else {
|
||||||
// If the cache does not have the RemoteViews at this position, then create a
|
layout = new RemoteViewsFrameLayout(context);
|
||||||
// loading view and queue the actual position to be loaded in the background
|
|
||||||
RemoteViewsFrameLayout loadingView = null;
|
|
||||||
final RemoteViewsMetaData metaData = mCache.getMetaData();
|
|
||||||
synchronized (metaData) {
|
|
||||||
loadingView = metaData.createLoadingView(position, convertView, parent);
|
|
||||||
}
|
|
||||||
|
|
||||||
mRequestedViews.add(position, loadingView);
|
|
||||||
mCache.queueRequestedPositionToLoad(position);
|
|
||||||
loadNextIndexInBackground();
|
|
||||||
|
|
||||||
return loadingView;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Otherwise, create a new view to be returned
|
||||||
|
View newView = rv.apply(context, parent);
|
||||||
|
newView.setTagInternal(com.android.internal.R.id.rowTypeId, new Integer(typeId));
|
||||||
|
layout.addView(newView);
|
||||||
|
if (hasNewItems) loadNextIndexInBackground();
|
||||||
|
|
||||||
|
return layout;
|
||||||
|
} else {
|
||||||
|
// If the cache does not have the RemoteViews at this position, then create a
|
||||||
|
// loading view and queue the actual position to be loaded in the background
|
||||||
|
RemoteViewsFrameLayout loadingView = null;
|
||||||
|
final RemoteViewsMetaData metaData = mCache.getMetaData();
|
||||||
|
synchronized (metaData) {
|
||||||
|
loadingView = metaData.createLoadingView(position, convertView, parent);
|
||||||
|
}
|
||||||
|
|
||||||
|
mRequestedViews.add(position, loadingView);
|
||||||
|
mCache.queueRequestedPositionToLoad(position);
|
||||||
|
loadNextIndexInBackground();
|
||||||
|
|
||||||
|
return loadingView;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return new View(parent.getContext());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getViewTypeCount() {
|
public int getViewTypeCount() {
|
||||||
requestBindService();
|
|
||||||
final RemoteViewsMetaData metaData = mCache.getMetaData();
|
final RemoteViewsMetaData metaData = mCache.getMetaData();
|
||||||
synchronized (metaData) {
|
synchronized (metaData) {
|
||||||
return metaData.viewTypeCount;
|
return metaData.viewTypeCount;
|
||||||
@ -844,7 +933,6 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean hasStableIds() {
|
public boolean hasStableIds() {
|
||||||
requestBindService();
|
|
||||||
final RemoteViewsMetaData metaData = mCache.getMetaData();
|
final RemoteViewsMetaData metaData = mCache.getMetaData();
|
||||||
synchronized (metaData) {
|
synchronized (metaData) {
|
||||||
return metaData.hasStableIds;
|
return metaData.hasStableIds;
|
||||||
@ -855,44 +943,67 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
|
|||||||
return getCount() <= 0;
|
return getCount() <= 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void notifyDataSetChanged() {
|
|
||||||
mWorkerQueue.post(new Runnable() {
|
private void onNotifyDataSetChanged() {
|
||||||
|
// Complete the actual notifyDataSetChanged() call initiated earlier
|
||||||
|
IRemoteViewsFactory factory = mServiceConnection.getRemoteViewsFactory();
|
||||||
|
try {
|
||||||
|
factory.onDataSetChanged();
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "Error in updateNotifyDataSetChanged(): " + e.getMessage());
|
||||||
|
|
||||||
|
// Return early to prevent from further being notified (since nothing has
|
||||||
|
// changed)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flush the cache so that we can reload new items from the service
|
||||||
|
synchronized (mCache) {
|
||||||
|
mCache.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Re-request the new metadata (only after the notification to the factory)
|
||||||
|
updateTemporaryMetaData();
|
||||||
|
|
||||||
|
// Propagate the notification back to the base adapter
|
||||||
|
mMainQueue.post(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
// Complete the actual notifyDataSetChanged() call initiated earlier
|
|
||||||
if (mServiceConnection.isConnected()) {
|
|
||||||
IRemoteViewsFactory factory = mServiceConnection.getRemoteViewsFactory();
|
|
||||||
try {
|
|
||||||
factory.onDataSetChanged();
|
|
||||||
} catch (Exception e) {
|
|
||||||
Log.e(TAG, "Error in updateNotifyDataSetChanged(): " + e.getMessage());
|
|
||||||
|
|
||||||
// Return early to prevent from further being notified (since nothing has
|
|
||||||
// changed)
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Flush the cache so that we can reload new items from the service
|
|
||||||
synchronized (mCache) {
|
synchronized (mCache) {
|
||||||
mCache.reset();
|
mCache.commitTemporaryMetaData();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Re-request the new metadata (only after the notification to the factory)
|
superNotifyDataSetChanged();
|
||||||
updateMetaData();
|
enqueueDeferredUnbindServiceMessage();
|
||||||
|
|
||||||
// Propagate the notification back to the base adapter
|
|
||||||
mMainQueue.post(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
superNotifyDataSetChanged();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Note: we do not call super.notifyDataSetChanged() until the RemoteViewsFactory has had
|
// Reset the notify flagflag
|
||||||
// a chance to update itself and return new meta data associated with the new data.
|
mNotifyDataSetChangedAfterOnServiceConnected = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void notifyDataSetChanged() {
|
||||||
|
// Dequeue any unbind messages
|
||||||
|
mMainQueue.removeMessages(sUnbindServiceMessageType);
|
||||||
|
|
||||||
|
// If we are not connected, queue up the notifyDataSetChanged to be handled when we do
|
||||||
|
// connect
|
||||||
|
if (!mServiceConnection.isConnected()) {
|
||||||
|
if (mNotifyDataSetChangedAfterOnServiceConnected) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
mNotifyDataSetChangedAfterOnServiceConnected = true;
|
||||||
|
requestBindService();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
mWorkerQueue.post(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
onNotifyDataSetChanged();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void superNotifyDataSetChanged() {
|
void superNotifyDataSetChanged() {
|
||||||
@ -904,9 +1015,8 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
|
|||||||
boolean result = false;
|
boolean result = false;
|
||||||
switch (msg.what) {
|
switch (msg.what) {
|
||||||
case sUnbindServiceMessageType:
|
case sUnbindServiceMessageType:
|
||||||
final AppWidgetManager mgr = AppWidgetManager.getInstance(mContext);
|
|
||||||
if (mServiceConnection.isConnected()) {
|
if (mServiceConnection.isConnected()) {
|
||||||
mgr.unbindRemoteViewsService(mAppWidgetId, mIntent);
|
mServiceConnection.unbind(mContext, mAppWidgetId, mIntent);
|
||||||
}
|
}
|
||||||
result = true;
|
result = true;
|
||||||
break;
|
break;
|
||||||
@ -917,20 +1027,19 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void enqueueDeferredUnbindServiceMessage() {
|
private void enqueueDeferredUnbindServiceMessage() {
|
||||||
/* Temporarily disable delayed service unbinding
|
|
||||||
// Remove any existing deferred-unbind messages
|
// Remove any existing deferred-unbind messages
|
||||||
mMainQueue.removeMessages(sUnbindServiceMessageType);
|
mMainQueue.removeMessages(sUnbindServiceMessageType);
|
||||||
mMainQueue.sendEmptyMessageDelayed(sUnbindServiceMessageType, sUnbindServiceDelay);
|
mMainQueue.sendEmptyMessageDelayed(sUnbindServiceMessageType, sUnbindServiceDelay);
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean requestBindService() {
|
private boolean requestBindService() {
|
||||||
// Try binding the service (which will start it if it's not already running)
|
// Try binding the service (which will start it if it's not already running)
|
||||||
if (!mServiceConnection.isConnected()) {
|
if (!mServiceConnection.isConnected()) {
|
||||||
final AppWidgetManager mgr = AppWidgetManager.getInstance(mContext);
|
mServiceConnection.bind(mContext, mAppWidgetId, mIntent);
|
||||||
mgr.bindRemoteViewsService(mAppWidgetId, mIntent, mServiceConnection.asBinder());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Remove any existing deferred-unbind messages
|
||||||
|
mMainQueue.removeMessages(sUnbindServiceMessageType);
|
||||||
return mServiceConnection.isConnected();
|
return mServiceConnection.isConnected();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,11 +17,11 @@
|
|||||||
package android.widget;
|
package android.widget;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import android.app.Service;
|
import android.app.Service;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
|
import android.util.Log;
|
||||||
import android.util.Pair;
|
import android.util.Pair;
|
||||||
|
|
||||||
import com.android.internal.widget.IRemoteViewsFactory;
|
import com.android.internal.widget.IRemoteViewsFactory;
|
||||||
@ -35,8 +35,13 @@ public abstract class RemoteViewsService extends Service {
|
|||||||
|
|
||||||
private static final String LOG_TAG = "RemoteViewsService";
|
private static final String LOG_TAG = "RemoteViewsService";
|
||||||
|
|
||||||
// multimap implementation for reference counting
|
// Used for reference counting of RemoteViewsFactories
|
||||||
private HashMap<Intent.FilterComparison, Pair<RemoteViewsFactory, Integer>> mRemoteViewFactories;
|
// Because we are now unbinding when we are not using the Service (to allow them to be
|
||||||
|
// reclaimed), the references to the factories that are created need to be stored and used when
|
||||||
|
// the service is restarted (in response to user input for example). When the process is
|
||||||
|
// destroyed, so is this static cache of RemoteViewsFactories.
|
||||||
|
private static final HashMap<Intent.FilterComparison, RemoteViewsFactory> mRemoteViewFactories =
|
||||||
|
new HashMap<Intent.FilterComparison, RemoteViewsFactory>();
|
||||||
private final Object mLock = new Object();
|
private final Object mLock = new Object();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -126,9 +131,13 @@ public abstract class RemoteViewsService extends Service {
|
|||||||
* A private proxy class for the private IRemoteViewsFactory interface through the
|
* A private proxy class for the private IRemoteViewsFactory interface through the
|
||||||
* public RemoteViewsFactory interface.
|
* public RemoteViewsFactory interface.
|
||||||
*/
|
*/
|
||||||
private class RemoteViewsFactoryAdapter extends IRemoteViewsFactory.Stub {
|
private static class RemoteViewsFactoryAdapter extends IRemoteViewsFactory.Stub {
|
||||||
public RemoteViewsFactoryAdapter(RemoteViewsFactory factory) {
|
public RemoteViewsFactoryAdapter(RemoteViewsFactory factory, boolean isCreated) {
|
||||||
mFactory = factory;
|
mFactory = factory;
|
||||||
|
mIsCreated = isCreated;
|
||||||
|
}
|
||||||
|
public synchronized boolean isCreated() {
|
||||||
|
return mIsCreated;
|
||||||
}
|
}
|
||||||
public synchronized void onDataSetChanged() {
|
public synchronized void onDataSetChanged() {
|
||||||
mFactory.onDataSetChanged();
|
mFactory.onDataSetChanged();
|
||||||
@ -155,58 +164,28 @@ public abstract class RemoteViewsService extends Service {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private RemoteViewsFactory mFactory;
|
private RemoteViewsFactory mFactory;
|
||||||
}
|
private boolean mIsCreated;
|
||||||
|
|
||||||
public RemoteViewsService() {
|
|
||||||
mRemoteViewFactories =
|
|
||||||
new HashMap<Intent.FilterComparison, Pair<RemoteViewsFactory, Integer>>();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public IBinder onBind(Intent intent) {
|
public IBinder onBind(Intent intent) {
|
||||||
synchronized (mLock) {
|
synchronized (mLock) {
|
||||||
// increment the reference count to the particular factory associated with this intent
|
|
||||||
Intent.FilterComparison fc = new Intent.FilterComparison(intent);
|
Intent.FilterComparison fc = new Intent.FilterComparison(intent);
|
||||||
Pair<RemoteViewsFactory, Integer> factoryRef = null;
|
|
||||||
RemoteViewsFactory factory = null;
|
RemoteViewsFactory factory = null;
|
||||||
|
boolean isCreated = false;
|
||||||
if (!mRemoteViewFactories.containsKey(fc)) {
|
if (!mRemoteViewFactories.containsKey(fc)) {
|
||||||
factory = onGetViewFactory(intent);
|
factory = onGetViewFactory(intent);
|
||||||
factoryRef = new Pair<RemoteViewsFactory, Integer>(factory, 1);
|
mRemoteViewFactories.put(fc, factory);
|
||||||
mRemoteViewFactories.put(fc, factoryRef);
|
|
||||||
factory.onCreate();
|
factory.onCreate();
|
||||||
|
isCreated = false;
|
||||||
} else {
|
} else {
|
||||||
Pair<RemoteViewsFactory, Integer> oldFactoryRef = mRemoteViewFactories.get(fc);
|
factory = mRemoteViewFactories.get(fc);
|
||||||
factory = oldFactoryRef.first;
|
isCreated = true;
|
||||||
int newRefCount = oldFactoryRef.second.intValue() + 1;
|
|
||||||
factoryRef = new Pair<RemoteViewsFactory, Integer>(oldFactoryRef.first, newRefCount);
|
|
||||||
mRemoteViewFactories.put(fc, factoryRef);
|
|
||||||
}
|
}
|
||||||
return new RemoteViewsFactoryAdapter(factory);
|
return new RemoteViewsFactoryAdapter(factory, isCreated);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onUnbind(Intent intent) {
|
|
||||||
synchronized (mLock) {
|
|
||||||
Intent.FilterComparison fc = new Intent.FilterComparison(intent);
|
|
||||||
if (mRemoteViewFactories.containsKey(fc)) {
|
|
||||||
// this alleviates the user's responsibility of having to clear all factories
|
|
||||||
Pair<RemoteViewsFactory, Integer> oldFactoryRef =
|
|
||||||
mRemoteViewFactories.get(fc);
|
|
||||||
int newRefCount = oldFactoryRef.second.intValue() - 1;
|
|
||||||
if (newRefCount <= 0) {
|
|
||||||
oldFactoryRef.first.onDestroy();
|
|
||||||
mRemoteViewFactories.remove(fc);
|
|
||||||
} else {
|
|
||||||
Pair<RemoteViewsFactory, Integer> factoryRef =
|
|
||||||
new Pair<RemoteViewsFactory, Integer>(oldFactoryRef.first, newRefCount);
|
|
||||||
mRemoteViewFactories.put(fc, factoryRef);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return super.onUnbind(intent);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* To be implemented by the derived service to generate appropriate factories for
|
* To be implemented by the derived service to generate appropriate factories for
|
||||||
* the data.
|
* the data.
|
||||||
|
@ -27,5 +27,6 @@ interface IRemoteViewsFactory {
|
|||||||
int getViewTypeCount();
|
int getViewTypeCount();
|
||||||
long getItemId(int position);
|
long getItemId(int position);
|
||||||
boolean hasStableIds();
|
boolean hasStableIds();
|
||||||
|
boolean isCreated();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,6 +22,7 @@ import java.io.FileInputStream;
|
|||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.PrintWriter;
|
import java.io.PrintWriter;
|
||||||
|
import java.lang.ref.WeakReference;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
@ -62,6 +63,7 @@ import android.os.Process;
|
|||||||
import android.os.RemoteException;
|
import android.os.RemoteException;
|
||||||
import android.os.SystemClock;
|
import android.os.SystemClock;
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
|
import android.util.Log;
|
||||||
import android.util.Pair;
|
import android.util.Pair;
|
||||||
import android.util.Slog;
|
import android.util.Slog;
|
||||||
import android.util.TypedValue;
|
import android.util.TypedValue;
|
||||||
@ -121,18 +123,15 @@ class AppWidgetService extends IAppWidgetService.Stub
|
|||||||
* globally and may lead us to leak AppWidgetService instances (if there were more than one).
|
* globally and may lead us to leak AppWidgetService instances (if there were more than one).
|
||||||
*/
|
*/
|
||||||
static class ServiceConnectionProxy implements ServiceConnection {
|
static class ServiceConnectionProxy implements ServiceConnection {
|
||||||
private final AppWidgetService mAppWidgetService;
|
|
||||||
private final Pair<Integer, Intent.FilterComparison> mKey;
|
private final Pair<Integer, Intent.FilterComparison> mKey;
|
||||||
private final IBinder mConnectionCb;
|
private final IBinder mConnectionCb;
|
||||||
|
|
||||||
ServiceConnectionProxy(AppWidgetService appWidgetService,
|
ServiceConnectionProxy(Pair<Integer, Intent.FilterComparison> key, IBinder connectionCb) {
|
||||||
Pair<Integer, Intent.FilterComparison> key, IBinder connectionCb) {
|
|
||||||
mAppWidgetService = appWidgetService;
|
|
||||||
mKey = key;
|
mKey = key;
|
||||||
mConnectionCb = connectionCb;
|
mConnectionCb = connectionCb;
|
||||||
}
|
}
|
||||||
public void onServiceConnected(ComponentName name, IBinder service) {
|
public void onServiceConnected(ComponentName name, IBinder service) {
|
||||||
IRemoteViewsAdapterConnection cb =
|
final IRemoteViewsAdapterConnection cb =
|
||||||
IRemoteViewsAdapterConnection.Stub.asInterface(mConnectionCb);
|
IRemoteViewsAdapterConnection.Stub.asInterface(mConnectionCb);
|
||||||
try {
|
try {
|
||||||
cb.onServiceConnected(service);
|
cb.onServiceConnected(service);
|
||||||
@ -141,19 +140,13 @@ class AppWidgetService extends IAppWidgetService.Stub
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
public void onServiceDisconnected(ComponentName name) {
|
public void onServiceDisconnected(ComponentName name) {
|
||||||
IRemoteViewsAdapterConnection cb =
|
disconnect();
|
||||||
|
}
|
||||||
|
public void disconnect() {
|
||||||
|
final IRemoteViewsAdapterConnection cb =
|
||||||
IRemoteViewsAdapterConnection.Stub.asInterface(mConnectionCb);
|
IRemoteViewsAdapterConnection.Stub.asInterface(mConnectionCb);
|
||||||
try {
|
try {
|
||||||
cb.onServiceDisconnected();
|
cb.onServiceDisconnected();
|
||||||
mAppWidgetService.mServiceConnectionUpdateHandler.post(new Runnable() {
|
|
||||||
public void run() {
|
|
||||||
// We don't want to touch mBoundRemoteViewsServices from any other thread
|
|
||||||
// so queue this to run on the main thread.
|
|
||||||
if (mAppWidgetService.mBoundRemoteViewsServices.containsKey(mKey)) {
|
|
||||||
mAppWidgetService.mBoundRemoteViewsServices.remove(mKey);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch (RemoteException e) {
|
} catch (RemoteException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
@ -163,7 +156,6 @@ class AppWidgetService extends IAppWidgetService.Stub
|
|||||||
// Manages connections to RemoteViewsServices
|
// Manages connections to RemoteViewsServices
|
||||||
private final HashMap<Pair<Integer, FilterComparison>, ServiceConnection>
|
private final HashMap<Pair<Integer, FilterComparison>, ServiceConnection>
|
||||||
mBoundRemoteViewsServices = new HashMap<Pair<Integer,FilterComparison>,ServiceConnection>();
|
mBoundRemoteViewsServices = new HashMap<Pair<Integer,FilterComparison>,ServiceConnection>();
|
||||||
private final Handler mServiceConnectionUpdateHandler = new Handler();
|
|
||||||
|
|
||||||
Context mContext;
|
Context mContext;
|
||||||
Locale mLocale;
|
Locale mLocale;
|
||||||
@ -456,13 +448,24 @@ class AppWidgetService extends IAppWidgetService.Stub
|
|||||||
throw new IllegalArgumentException("Unknown component " + componentName);
|
throw new IllegalArgumentException("Unknown component " + componentName);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bind to the RemoteViewsService (which will trigger a callback to the
|
// If there is already a connection made for this service intent, then disconnect from
|
||||||
// RemoteViewsAdapter)
|
// that first. (This does not allow multiple connections to the same service under
|
||||||
|
// the same key)
|
||||||
|
ServiceConnectionProxy conn = null;
|
||||||
Pair<Integer, FilterComparison> key = Pair.create(appWidgetId,
|
Pair<Integer, FilterComparison> key = Pair.create(appWidgetId,
|
||||||
new FilterComparison(intent));
|
new FilterComparison(intent));
|
||||||
final ServiceConnection conn = new ServiceConnectionProxy(this, key, connection);
|
if (mBoundRemoteViewsServices.containsKey(key)) {
|
||||||
|
conn = (ServiceConnectionProxy) mBoundRemoteViewsServices.get(key);
|
||||||
|
conn.disconnect();
|
||||||
|
mContext.unbindService(conn);
|
||||||
|
mBoundRemoteViewsServices.remove(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bind to the RemoteViewsService (which will trigger a callback to the
|
||||||
|
// RemoteViewsAdapter.onServiceConnected())
|
||||||
final long token = Binder.clearCallingIdentity();
|
final long token = Binder.clearCallingIdentity();
|
||||||
try {
|
try {
|
||||||
|
conn = new ServiceConnectionProxy(key, connection);
|
||||||
mContext.bindService(intent, conn, Context.BIND_AUTO_CREATE);
|
mContext.bindService(intent, conn, Context.BIND_AUTO_CREATE);
|
||||||
mBoundRemoteViewsServices.put(key, conn);
|
mBoundRemoteViewsServices.put(key, conn);
|
||||||
} finally {
|
} finally {
|
||||||
@ -473,37 +476,43 @@ class AppWidgetService extends IAppWidgetService.Stub
|
|||||||
|
|
||||||
public void unbindRemoteViewsService(int appWidgetId, Intent intent) {
|
public void unbindRemoteViewsService(int appWidgetId, Intent intent) {
|
||||||
synchronized (mAppWidgetIds) {
|
synchronized (mAppWidgetIds) {
|
||||||
AppWidgetId id = lookupAppWidgetIdLocked(appWidgetId);
|
|
||||||
if (id == null) {
|
|
||||||
throw new IllegalArgumentException("bad appWidgetId");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unbind from the RemoteViewsService (which will trigger a callback to the bound
|
// Unbind from the RemoteViewsService (which will trigger a callback to the bound
|
||||||
// RemoteViewsAdapter)
|
// RemoteViewsAdapter)
|
||||||
Pair<Integer, FilterComparison> key = Pair.create(appWidgetId,
|
Pair<Integer, FilterComparison> key = Pair.create(appWidgetId,
|
||||||
new FilterComparison(intent));
|
new FilterComparison(intent));
|
||||||
if (mBoundRemoteViewsServices.containsKey(key)) {
|
if (mBoundRemoteViewsServices.containsKey(key)) {
|
||||||
final ServiceConnection conn = mBoundRemoteViewsServices.get(key);
|
// We don't need to use the appWidgetId until after we are sure there is something
|
||||||
mBoundRemoteViewsServices.remove(key);
|
// to unbind. Note that this may mask certain issues with apps calling unbind()
|
||||||
conn.onServiceDisconnected(null);
|
// more than necessary.
|
||||||
|
AppWidgetId id = lookupAppWidgetIdLocked(appWidgetId);
|
||||||
|
if (id == null) {
|
||||||
|
throw new IllegalArgumentException("bad appWidgetId");
|
||||||
|
}
|
||||||
|
|
||||||
|
ServiceConnectionProxy conn =
|
||||||
|
(ServiceConnectionProxy) mBoundRemoteViewsServices.get(key);
|
||||||
|
conn.disconnect();
|
||||||
mContext.unbindService(conn);
|
mContext.unbindService(conn);
|
||||||
|
mBoundRemoteViewsServices.remove(key);
|
||||||
|
} else {
|
||||||
|
Log.e("AppWidgetService", "Error (unbindRemoteViewsService): Connection not bound");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void unbindAppWidgetRemoteViewsServicesLocked(AppWidgetId id) {
|
private void unbindAppWidgetRemoteViewsServicesLocked(AppWidgetId id) {
|
||||||
|
int appWidgetId = id.appWidgetId;
|
||||||
|
// Unbind all connections to Services bound to this AppWidgetId
|
||||||
Iterator<Pair<Integer, Intent.FilterComparison>> it =
|
Iterator<Pair<Integer, Intent.FilterComparison>> it =
|
||||||
mBoundRemoteViewsServices.keySet().iterator();
|
mBoundRemoteViewsServices.keySet().iterator();
|
||||||
int appWidgetId = id.appWidgetId;
|
|
||||||
|
|
||||||
// Unbind all connections to AppWidgets bound to this id
|
|
||||||
while (it.hasNext()) {
|
while (it.hasNext()) {
|
||||||
final Pair<Integer, Intent.FilterComparison> key = it.next();
|
final Pair<Integer, Intent.FilterComparison> key = it.next();
|
||||||
if (key.first.intValue() == appWidgetId) {
|
if (key.first.intValue() == appWidgetId) {
|
||||||
final ServiceConnection conn = mBoundRemoteViewsServices.get(key);
|
final ServiceConnectionProxy conn = (ServiceConnectionProxy)
|
||||||
it.remove();
|
mBoundRemoteViewsServices.get(key);
|
||||||
conn.onServiceDisconnected(null);
|
conn.disconnect();
|
||||||
mContext.unbindService(conn);
|
mContext.unbindService(conn);
|
||||||
|
it.remove();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user