Merge "Re-enabling queued unbinding of services after requests to the service. (3394210)" into honeycomb

This commit is contained in:
Winson Chung
2011-01-26 12:30:26 -08:00
committed by Android (Google) Code Review
8 changed files with 403 additions and 279 deletions

View File

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

View File

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

View File

@ -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;
} }
/** /**

View File

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

View File

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

View File

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

View File

@ -27,5 +27,6 @@ interface IRemoteViewsFactory {
int getViewTypeCount(); int getViewTypeCount();
long getItemId(int position); long getItemId(int position);
boolean hasStableIds(); boolean hasStableIds();
boolean isCreated();
} }

View File

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