Re-enabling queued unbinding of services after requests to the service. (3394210)
- Fix for crash when detaching from window
- Potential fix for occasional IllegalStateException when updating List based widgets
Change-Id: I3d3f2bb691552a1136111043db686c4926b510c6
diff --git a/api/11.xml b/api/11.xml
index d17325f..2180aea 100644
--- a/api/11.xml
+++ b/api/11.xml
@@ -238822,7 +238822,7 @@
>
</method>
<method name="onRemoteAdapterConnected"
- return="void"
+ return="boolean"
abstract="false"
native="false"
synchronized="false"
@@ -240800,7 +240800,7 @@
>
</method>
<method name="onRemoteAdapterConnected"
- return="void"
+ return="boolean"
abstract="false"
native="false"
synchronized="false"
@@ -260418,7 +260418,7 @@
deprecated="not deprecated"
visibility="public"
>
-<parameter name="arg0" type="T">
+<parameter name="t" type="T">
</parameter>
</method>
</interface>
diff --git a/api/current.xml b/api/current.xml
index b7d7b3a..e34bc8a 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -238833,7 +238833,7 @@
>
</method>
<method name="onRemoteAdapterConnected"
- return="void"
+ return="boolean"
abstract="false"
native="false"
synchronized="false"
@@ -240811,7 +240811,7 @@
>
</method>
<method name="onRemoteAdapterConnected"
- return="void"
+ return="boolean"
abstract="false"
native="false"
synchronized="false"
@@ -260429,7 +260429,7 @@
deprecated="not deprecated"
visibility="public"
>
-<parameter name="arg0" type="T">
+<parameter name="t" type="T">
</parameter>
</method>
</interface>
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index 6d66bbc..631ba0c 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -5289,12 +5289,15 @@
/**
* Called back when the adapter connects to the RemoteViewsService.
*/
- public void onRemoteAdapterConnected() {
+ public boolean onRemoteAdapterConnected() {
if (mRemoteAdapter != mAdapter) {
setAdapter(mRemoteAdapter);
+ return false;
} else if (mRemoteAdapter != null) {
mRemoteAdapter.superNotifyDataSetChanged();
+ return true;
}
+ return false;
}
/**
diff --git a/core/java/android/widget/AdapterViewAnimator.java b/core/java/android/widget/AdapterViewAnimator.java
index e34a204..23a1a5c 100644
--- a/core/java/android/widget/AdapterViewAnimator.java
+++ b/core/java/android/widget/AdapterViewAnimator.java
@@ -54,6 +54,12 @@
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
*/
boolean mAnimateFirstTime = true;
@@ -780,7 +786,15 @@
// set mWhichChild
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);
+ }
}
/**
@@ -956,12 +970,21 @@
/**
* Called back when the adapter connects to the RemoteViewsService.
*/
- public void onRemoteAdapterConnected() {
+ public boolean onRemoteAdapterConnected() {
if (mRemoteViewsAdapter != mAdapter) {
setAdapter(mRemoteViewsAdapter);
+
+ // Restore the previous position (see onRestoreInstanceState)
+ if (mRestoreWhichChild > -1) {
+ setDisplayedChild(mRestoreWhichChild);
+ mRestoreWhichChild = -1;
+ }
+ return false;
} else if (mRemoteViewsAdapter != null) {
mRemoteViewsAdapter.superNotifyDataSetChanged();
+ return true;
}
+ return false;
}
/**
@@ -994,7 +1017,7 @@
@Override
protected void onDetachedFromWindow() {
- mAdapter = null;
+ setAdapter(null);
super.onDetachedFromWindow();
}
}
diff --git a/core/java/android/widget/RemoteViewsAdapter.java b/core/java/android/widget/RemoteViewsAdapter.java
index f329a3e..2457f7f 100644
--- a/core/java/android/widget/RemoteViewsAdapter.java
+++ b/core/java/android/widget/RemoteViewsAdapter.java
@@ -48,11 +48,11 @@
private static final String TAG = "RemoteViewsAdapter";
// 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.
// 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.
- 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
private static final int sDefaultMessageType = 0;
private static final int sUnbindServiceMessageType = 1;
@@ -65,6 +65,9 @@
private WeakReference<RemoteAdapterConnectionCallback> mCallback;
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
// loaded.
private RemoteViewsFrameLayoutRefSet mRequestedViews;
@@ -79,7 +82,10 @@
* are actually connected to/disconnected from their actual services.
*/
public interface RemoteAdapterConnectionCallback {
- public void onRemoteAdapterConnected();
+ /**
+ * @return whether the adapter was set or not.
+ */
+ public boolean onRemoteAdapterConnected();
public void onRemoteAdapterDisconnected();
}
@@ -93,7 +99,8 @@
*/
private static class RemoteViewsAdapterServiceConnection extends
IRemoteViewsAdapterConnection.Stub {
- private boolean mConnected;
+ private boolean mIsConnected;
+ private boolean mIsConnecting;
private WeakReference<RemoteViewsAdapter> mAdapter;
private IRemoteViewsFactory mRemoteViewsFactory;
@@ -101,27 +108,58 @@
mAdapter = new WeakReference<RemoteViewsAdapter>(adapter);
}
- public void onServiceConnected(IBinder service) {
- mRemoteViewsFactory = IRemoteViewsFactory.Stub.asInterface(service);
- mConnected = true;
+ public synchronized void bind(Context context, int appWidgetId, Intent intent) {
+ if (!mIsConnecting) {
+ 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();
if (adapter == null) return;
+
+ // Queue up work that we need to do for the callback to run
adapter.mWorkerQueue.post(new Runnable() {
@Override
public void run() {
- // Call back to the service to notify that the data set changed
- if (adapter.mServiceConnection.isConnected()) {
+ if (adapter.mNotifyDataSetChangedAfterOnServiceConnected) {
+ // Handle queued notifyDataSetChanged() if necessary
+ adapter.onNotifyDataSetChanged();
+ } else {
IRemoteViewsFactory factory =
adapter.mServiceConnection.getRemoteViewsFactory();
try {
- // call back to the factory
- factory.onDataSetChanged();
+ if (!factory.isCreated()) {
+ // We only call onDataSetChanged() if this is the factory was just
+ // create in response to this bind
+ factory.onDataSetChanged();
+ }
} catch (Exception e) {
Log.e(TAG, "Error notifying factory of data set changed in " +
"onServiceConnected(): " + e.getMessage());
- e.printStackTrace();
// Return early to prevent anything further from being notified
// (effectively nothing has changed)
@@ -130,13 +168,16 @@
// Request meta data so that we have up to date data when calling back to
// the remote adapter callback
- adapter.updateMetaData();
+ adapter.updateTemporaryMetaData();
- // Post a runnable to call back to the view to notify it that we have
- // connected
+ // Notify the host that we've connected
adapter.mMainQueue.post(new Runnable() {
@Override
public void run() {
+ synchronized (adapter.mCache) {
+ adapter.mCache.commitTemporaryMetaData();
+ }
+
final RemoteAdapterConnectionCallback callback =
adapter.mCallback.get();
if (callback != null) {
@@ -145,35 +186,44 @@
}
});
}
+
+ // Enqueue unbind message
+ adapter.enqueueDeferredUnbindServiceMessage();
+ mIsConnected = true;
+ mIsConnecting = false;
}
});
}
- public void onServiceDisconnected() {
- mConnected = false;
+ public synchronized void onServiceDisconnected() {
+ mIsConnected = false;
+ mIsConnecting = false;
mRemoteViewsFactory = null;
+ // Clear the main/worker queues
final RemoteViewsAdapter adapter = mAdapter.get();
if (adapter == null) return;
- // Clear the main/worker queues
- adapter.mMainQueue.removeMessages(sUnbindServiceMessageType);
- adapter.mMainQueue.removeMessages(sDefaultMessageType);
- adapter.mWorkerQueue.removeMessages(sDefaultMessageType);
+ adapter.mMainQueue.post(new Runnable() {
+ @Override
+ public void run() {
+ // Dequeue any unbind messages
+ adapter.mMainQueue.removeMessages(sUnbindServiceMessageType);
- final RemoteAdapterConnectionCallback callback = adapter.mCallback.get();
- if (callback != null) {
- callback.onRemoteAdapterDisconnected();
- }
- adapter.mCache.reset();
+ final RemoteAdapterConnectionCallback callback = adapter.mCallback.get();
+ if (callback != null) {
+ callback.onRemoteAdapterDisconnected();
+ }
+ }
+ });
}
- public IRemoteViewsFactory getRemoteViewsFactory() {
+ public synchronized IRemoteViewsFactory getRemoteViewsFactory() {
return mRemoteViewsFactory;
}
- public boolean isConnected() {
- return mConnected;
+ public synchronized boolean isConnected() {
+ return mIsConnected;
}
}
@@ -270,7 +320,6 @@
int count;
int viewTypeCount;
boolean hasStableIds;
- boolean isDataDirty;
// 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
@@ -280,22 +329,31 @@
int mFirstViewHeight;
// 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() {
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() {
count = 0;
+
// by default there is at least one dummy view type
viewTypeCount = 1;
hasStableIds = true;
- isDataDirty = false;
mUserLoadingView = null;
mFirstView = null;
mFirstViewHeight = 0;
- mTypeIdIndexMap = new HashMap<Integer, Integer>();
+ mTypeIdIndexMap.clear();
}
public void setLoadingViewTemplates(RemoteViews loadingView, RemoteViews firstView) {
@@ -385,6 +443,7 @@
// The meta data related to all the RemoteViews, ie. count, is stable, etc.
private RemoteViewsMetaData mMetaData;
+ private RemoteViewsMetaData mTemporaryMetaData;
// The cache/mapping of position to RemoteViewsMetaData. This set is guaranteed to be
// greater than or equal to the set of RemoteViews.
@@ -426,6 +485,7 @@
mPreloadLowerBound = 0;
mPreloadUpperBound = -1;
mMetaData = new RemoteViewsMetaData();
+ mTemporaryMetaData = new RemoteViewsMetaData();
mIndexMetaData = new HashMap<Integer, RemoteViewsIndexMetaData>();
mIndexRemoteViews = new HashMap<Integer, RemoteViews>();
mRequestedIndices = new HashSet<Integer>();
@@ -461,6 +521,9 @@
public RemoteViewsMetaData getMetaData() {
return mMetaData;
}
+ public RemoteViewsMetaData getTemporaryMetaData() {
+ return mTemporaryMetaData;
+ }
public RemoteViews getRemoteViewsAt(int position) {
if (mIndexRemoteViews.containsKey(position)) {
return mIndexRemoteViews.get(position);
@@ -474,6 +537,14 @@
return null;
}
+ public void commitTemporaryMetaData() {
+ synchronized (mTemporaryMetaData) {
+ synchronized (mMetaData) {
+ mMetaData.set(mTemporaryMetaData);
+ }
+ }
+ }
+
private int getRemoteViewsBitmapMemoryUsage() {
// Calculate the memory usage of all the RemoteViews bitmaps being cached
int mem = 0;
@@ -505,12 +576,12 @@
mLoadIndices.add(position);
}
}
- public void queuePositionsToBePreloadedFromRequestedPosition(int position) {
+ public boolean queuePositionsToBePreloadedFromRequestedPosition(int position) {
// Check if we need to preload any items
if (mPreloadLowerBound <= position && position <= mPreloadUpperBound) {
int center = (mPreloadUpperBound + mPreloadLowerBound) / 2;
if (Math.abs(position - center) < mMaxCountSlack) {
- return;
+ return false;
}
}
@@ -537,6 +608,7 @@
// But remove all the indices that have already been loaded and are cached
mLoadIndices.removeAll(mIndexRemoteViews.keySet());
}
+ return true;
}
public int getNextIndexToLoad() {
// We try and prioritize items that have been requested directly, instead
@@ -616,100 +688,114 @@
mWorkerQueue.post(new Runnable() {
@Override
public void run() {
- // Get the next index to load
- int position = -1;
- synchronized (mCache) {
- position = mCache.getNextIndexToLoad();
- }
- if (position > -1) {
- // Load the item, and notify any existing RemoteViewsFrameLayouts
- updateRemoteViews(position);
+ if (mServiceConnection.isConnected()) {
+ // Get the next index to load
+ int position = -1;
+ synchronized (mCache) {
+ position = mCache.getNextIndexToLoad();
+ }
+ if (position > -1) {
+ // Load the item, and notify any existing RemoteViewsFrameLayouts
+ updateRemoteViews(position);
- // Queue up for the next one to load
- loadNextIndexInBackground();
+ // Queue up for the next one to load
+ loadNextIndexInBackground();
+ } else {
+ // No more items to load, so queue unbind
+ enqueueDeferredUnbindServiceMessage();
+ }
}
}
});
}
- private void updateMetaData() {
- if (mServiceConnection.isConnected()) {
- try {
- IRemoteViewsFactory factory = mServiceConnection.getRemoteViewsFactory();
+ private void processException(String method, Exception e) {
+ Log.e("RemoteViewsAdapter", "Error in " + method + ": " + e.getMessage());
- // 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 metaData = mCache.getMetaData();
- synchronized (metaData) {
- metaData.hasStableIds = hasStableIds;
- 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();
- }
+ // If we encounter a crash when updating, we should reset the metadata & cache and trigger
+ // a notifyDataSetChanged to update the widget accordingly
+ final RemoteViewsMetaData metaData = mCache.getMetaData();
+ synchronized (metaData) {
+ metaData.reset();
+ }
+ synchronized (mCache) {
+ mCache.reset();
+ }
+ mMainQueue.post(new Runnable() {
+ @Override
+ public void run() {
+ superNotifyDataSetChanged();
}
+ });
+ }
+
+ 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) {
- if (mServiceConnection.isConnected()) {
- IRemoteViewsFactory factory = mServiceConnection.getRemoteViewsFactory();
+ if (!mServiceConnection.isConnected()) return;
+ IRemoteViewsFactory factory = mServiceConnection.getRemoteViewsFactory();
- // Load the item information from the remote service
- RemoteViews remoteViews = null;
- long itemId = 0;
- try {
- remoteViews = factory.getViewAt(position);
- itemId = factory.getItemId(position);
- } catch (Throwable t) {
- Log.e(TAG, "Error in updateRemoteViews(" + position + "): " + t.getMessage());
- t.printStackTrace();
+ // Load the item information from the remote service
+ RemoteViews remoteViews = null;
+ long itemId = 0;
+ try {
+ remoteViews = factory.getViewAt(position);
+ itemId = factory.getItemId(position);
+ } catch (Exception e) {
+ Log.e(TAG, "Error in updateRemoteViews(" + position + "): " + e.getMessage());
- // Return early to prevent additional work in re-centering the view cache, and
- // swapping from the loading view
- return;
- }
+ // Return early to prevent additional work in re-centering the view cache, and
+ // swapping from the loading view
+ return;
+ }
- if (remoteViews == null) {
- // 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
- // position will remain as a loading view until it is updated.
- Log.e(TAG, "Error in updateRemoteViews(" + position + "): " + " null RemoteViews " +
- "returned from RemoteViewsFactory.");
- return;
- }
- synchronized (mCache) {
- // Cache the RemoteViews we loaded
- mCache.insert(position, remoteViews, itemId);
+ if (remoteViews == null) {
+ // 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
+ // position will remain as a loading view until it is updated.
+ Log.e(TAG, "Error in updateRemoteViews(" + position + "): " + " null RemoteViews " +
+ "returned from RemoteViewsFactory.");
+ return;
+ }
+ synchronized (mCache) {
+ // Cache the RemoteViews we loaded
+ mCache.insert(position, remoteViews, itemId);
- // Notify all the views that we have previously returned for this index that
- // there is new data for it.
- final RemoteViews rv = remoteViews;
- final int typeId = mCache.getMetaDataAt(position).typeId;
- mMainQueue.post(new Runnable() {
- @Override
- public void run() {
- mRequestedViews.notifyOnRemoteViewsLoaded(position, rv, typeId);
- enqueueDeferredUnbindServiceMessage();
- }
- });
- }
+ // Notify all the views that we have previously returned for this index that
+ // there is new data for it.
+ final RemoteViews rv = remoteViews;
+ final int typeId = mCache.getMetaDataAt(position).typeId;
+ mMainQueue.post(new Runnable() {
+ @Override
+ public void run() {
+ mRequestedViews.notifyOnRemoteViewsLoaded(position, rv, typeId);
+ }
+ });
}
}
@@ -718,7 +804,6 @@
}
public int getCount() {
- requestBindService();
final RemoteViewsMetaData metaData = mCache.getMetaData();
synchronized (metaData) {
return metaData.count;
@@ -731,7 +816,6 @@
}
public long getItemId(int position) {
- requestBindService();
synchronized (mCache) {
if (mCache.containsMetaDataAt(position)) {
return mCache.getMetaDataAt(position).itemId;
@@ -741,7 +825,6 @@
}
public int getItemViewType(int position) {
- requestBindService();
int typeId = 0;
synchronized (mCache) {
if (mCache.containsMetaDataAt(position)) {
@@ -773,13 +856,23 @@
}
public View getView(int position, View convertView, ViewGroup parent) {
- requestBindService();
- if (mServiceConnection.isConnected()) {
- // "Request" an index so that we can queue it for loading, initiate subsequent
- // preloading, etc.
- synchronized (mCache) {
+ // "Request" an index so that we can queue it for loading, initiate subsequent
+ // preloading, etc.
+ synchronized (mCache) {
+ boolean isInCache = mCache.containsRemoteViewAt(position);
+ 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
- mCache.queuePositionsToBePreloadedFromRequestedPosition(position);
+ hasNewItems = mCache.queuePositionsToBePreloadedFromRequestedPosition(position);
+ }
+
+ if (isInCache) {
View convertViewChild = null;
int convertViewTypeId = 0;
RemoteViewsFrameLayout layout = null;
@@ -792,51 +885,47 @@
// 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.
- if (mCache.containsRemoteViewAt(position)) {
- Context context = parent.getContext();
- RemoteViews rv = mCache.getRemoteViewsAt(position);
- int typeId = mCache.getMetaDataAt(position).typeId;
+ Context context = parent.getContext();
+ RemoteViews rv = mCache.getRemoteViewsAt(position);
+ int typeId = mCache.getMetaDataAt(position).typeId;
- // Reuse the convert view where possible
- if (layout != null) {
- if (convertViewTypeId == typeId) {
- rv.reapply(context, convertViewChild);
- return layout;
- }
+ // Reuse the convert view where possible
+ if (layout != null) {
+ if (convertViewTypeId == typeId) {
+ rv.reapply(context, convertViewChild);
+ return layout;
}
-
- // 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;
+ layout.removeAllViews();
} 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;
+ layout = new RemoteViewsFrameLayout(context);
}
+
+ // 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() {
- requestBindService();
final RemoteViewsMetaData metaData = mCache.getMetaData();
synchronized (metaData) {
return metaData.viewTypeCount;
@@ -844,7 +933,6 @@
}
public boolean hasStableIds() {
- requestBindService();
final RemoteViewsMetaData metaData = mCache.getMetaData();
synchronized (metaData) {
return metaData.hasStableIds;
@@ -855,44 +943,67 @@
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
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) {
- mCache.reset();
+ mCache.commitTemporaryMetaData();
}
- // Re-request the new metadata (only after the notification to the factory)
- updateMetaData();
-
- // Propagate the notification back to the base adapter
- mMainQueue.post(new Runnable() {
- @Override
- public void run() {
- superNotifyDataSetChanged();
- }
- });
+ superNotifyDataSetChanged();
+ enqueueDeferredUnbindServiceMessage();
}
});
- // Note: we do not call super.notifyDataSetChanged() until the RemoteViewsFactory has had
- // a chance to update itself and return new meta data associated with the new data.
+ // Reset the notify flagflag
+ 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() {
@@ -904,9 +1015,8 @@
boolean result = false;
switch (msg.what) {
case sUnbindServiceMessageType:
- final AppWidgetManager mgr = AppWidgetManager.getInstance(mContext);
if (mServiceConnection.isConnected()) {
- mgr.unbindRemoteViewsService(mAppWidgetId, mIntent);
+ mServiceConnection.unbind(mContext, mAppWidgetId, mIntent);
}
result = true;
break;
@@ -917,20 +1027,19 @@
}
private void enqueueDeferredUnbindServiceMessage() {
- /* Temporarily disable delayed service unbinding
// Remove any existing deferred-unbind messages
mMainQueue.removeMessages(sUnbindServiceMessageType);
mMainQueue.sendEmptyMessageDelayed(sUnbindServiceMessageType, sUnbindServiceDelay);
- */
}
private boolean requestBindService() {
// Try binding the service (which will start it if it's not already running)
if (!mServiceConnection.isConnected()) {
- final AppWidgetManager mgr = AppWidgetManager.getInstance(mContext);
- mgr.bindRemoteViewsService(mAppWidgetId, mIntent, mServiceConnection.asBinder());
+ mServiceConnection.bind(mContext, mAppWidgetId, mIntent);
}
+ // Remove any existing deferred-unbind messages
+ mMainQueue.removeMessages(sUnbindServiceMessageType);
return mServiceConnection.isConnected();
}
}
diff --git a/core/java/android/widget/RemoteViewsService.java b/core/java/android/widget/RemoteViewsService.java
index e5a3de2..fb2c416 100644
--- a/core/java/android/widget/RemoteViewsService.java
+++ b/core/java/android/widget/RemoteViewsService.java
@@ -17,11 +17,11 @@
package android.widget;
import java.util.HashMap;
-import java.util.Map;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
+import android.util.Log;
import android.util.Pair;
import com.android.internal.widget.IRemoteViewsFactory;
@@ -35,8 +35,13 @@
private static final String LOG_TAG = "RemoteViewsService";
- // multimap implementation for reference counting
- private HashMap<Intent.FilterComparison, Pair<RemoteViewsFactory, Integer>> mRemoteViewFactories;
+ // Used for reference counting of RemoteViewsFactories
+ // 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();
/**
@@ -126,9 +131,13 @@
* A private proxy class for the private IRemoteViewsFactory interface through the
* public RemoteViewsFactory interface.
*/
- private class RemoteViewsFactoryAdapter extends IRemoteViewsFactory.Stub {
- public RemoteViewsFactoryAdapter(RemoteViewsFactory factory) {
+ private static class RemoteViewsFactoryAdapter extends IRemoteViewsFactory.Stub {
+ public RemoteViewsFactoryAdapter(RemoteViewsFactory factory, boolean isCreated) {
mFactory = factory;
+ mIsCreated = isCreated;
+ }
+ public synchronized boolean isCreated() {
+ return mIsCreated;
}
public synchronized void onDataSetChanged() {
mFactory.onDataSetChanged();
@@ -155,58 +164,28 @@
}
private RemoteViewsFactory mFactory;
- }
-
- public RemoteViewsService() {
- mRemoteViewFactories =
- new HashMap<Intent.FilterComparison, Pair<RemoteViewsFactory, Integer>>();
+ private boolean mIsCreated;
}
@Override
public IBinder onBind(Intent intent) {
synchronized (mLock) {
- // increment the reference count to the particular factory associated with this intent
Intent.FilterComparison fc = new Intent.FilterComparison(intent);
- Pair<RemoteViewsFactory, Integer> factoryRef = null;
RemoteViewsFactory factory = null;
+ boolean isCreated = false;
if (!mRemoteViewFactories.containsKey(fc)) {
factory = onGetViewFactory(intent);
- factoryRef = new Pair<RemoteViewsFactory, Integer>(factory, 1);
- mRemoteViewFactories.put(fc, factoryRef);
+ mRemoteViewFactories.put(fc, factory);
factory.onCreate();
+ isCreated = false;
} else {
- Pair<RemoteViewsFactory, Integer> oldFactoryRef = mRemoteViewFactories.get(fc);
- factory = oldFactoryRef.first;
- int newRefCount = oldFactoryRef.second.intValue() + 1;
- factoryRef = new Pair<RemoteViewsFactory, Integer>(oldFactoryRef.first, newRefCount);
- mRemoteViewFactories.put(fc, factoryRef);
+ factory = mRemoteViewFactories.get(fc);
+ isCreated = true;
}
- 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
* the data.
diff --git a/core/java/com/android/internal/widget/IRemoteViewsFactory.aidl b/core/java/com/android/internal/widget/IRemoteViewsFactory.aidl
index ae9900c..60eca00 100644
--- a/core/java/com/android/internal/widget/IRemoteViewsFactory.aidl
+++ b/core/java/com/android/internal/widget/IRemoteViewsFactory.aidl
@@ -27,5 +27,6 @@
int getViewTypeCount();
long getItemId(int position);
boolean hasStableIds();
+ boolean isCreated();
}
diff --git a/services/java/com/android/server/AppWidgetService.java b/services/java/com/android/server/AppWidgetService.java
index 59a540b..ad25657 100644
--- a/services/java/com/android/server/AppWidgetService.java
+++ b/services/java/com/android/server/AppWidgetService.java
@@ -22,6 +22,7 @@
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
+import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
@@ -62,6 +63,7 @@
import android.os.RemoteException;
import android.os.SystemClock;
import android.util.AttributeSet;
+import android.util.Log;
import android.util.Pair;
import android.util.Slog;
import android.util.TypedValue;
@@ -121,18 +123,15 @@
* globally and may lead us to leak AppWidgetService instances (if there were more than one).
*/
static class ServiceConnectionProxy implements ServiceConnection {
- private final AppWidgetService mAppWidgetService;
private final Pair<Integer, Intent.FilterComparison> mKey;
private final IBinder mConnectionCb;
- ServiceConnectionProxy(AppWidgetService appWidgetService,
- Pair<Integer, Intent.FilterComparison> key, IBinder connectionCb) {
- mAppWidgetService = appWidgetService;
+ ServiceConnectionProxy(Pair<Integer, Intent.FilterComparison> key, IBinder connectionCb) {
mKey = key;
mConnectionCb = connectionCb;
}
public void onServiceConnected(ComponentName name, IBinder service) {
- IRemoteViewsAdapterConnection cb =
+ final IRemoteViewsAdapterConnection cb =
IRemoteViewsAdapterConnection.Stub.asInterface(mConnectionCb);
try {
cb.onServiceConnected(service);
@@ -141,19 +140,13 @@
}
}
public void onServiceDisconnected(ComponentName name) {
- IRemoteViewsAdapterConnection cb =
+ disconnect();
+ }
+ public void disconnect() {
+ final IRemoteViewsAdapterConnection cb =
IRemoteViewsAdapterConnection.Stub.asInterface(mConnectionCb);
try {
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) {
e.printStackTrace();
}
@@ -163,7 +156,6 @@
// Manages connections to RemoteViewsServices
private final HashMap<Pair<Integer, FilterComparison>, ServiceConnection>
mBoundRemoteViewsServices = new HashMap<Pair<Integer,FilterComparison>,ServiceConnection>();
- private final Handler mServiceConnectionUpdateHandler = new Handler();
Context mContext;
Locale mLocale;
@@ -456,13 +448,24 @@
throw new IllegalArgumentException("Unknown component " + componentName);
}
- // Bind to the RemoteViewsService (which will trigger a callback to the
- // RemoteViewsAdapter)
+ // If there is already a connection made for this service intent, then disconnect from
+ // 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,
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();
try {
+ conn = new ServiceConnectionProxy(key, connection);
mContext.bindService(intent, conn, Context.BIND_AUTO_CREATE);
mBoundRemoteViewsServices.put(key, conn);
} finally {
@@ -473,37 +476,43 @@
public void unbindRemoteViewsService(int appWidgetId, Intent intent) {
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
// RemoteViewsAdapter)
Pair<Integer, FilterComparison> key = Pair.create(appWidgetId,
new FilterComparison(intent));
if (mBoundRemoteViewsServices.containsKey(key)) {
- final ServiceConnection conn = mBoundRemoteViewsServices.get(key);
- mBoundRemoteViewsServices.remove(key);
- conn.onServiceDisconnected(null);
+ // We don't need to use the appWidgetId until after we are sure there is something
+ // to unbind. Note that this may mask certain issues with apps calling unbind()
+ // 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);
+ mBoundRemoteViewsServices.remove(key);
+ } else {
+ Log.e("AppWidgetService", "Error (unbindRemoteViewsService): Connection not bound");
}
}
}
private void unbindAppWidgetRemoteViewsServicesLocked(AppWidgetId id) {
+ int appWidgetId = id.appWidgetId;
+ // Unbind all connections to Services bound to this AppWidgetId
Iterator<Pair<Integer, Intent.FilterComparison>> it =
mBoundRemoteViewsServices.keySet().iterator();
- int appWidgetId = id.appWidgetId;
-
- // Unbind all connections to AppWidgets bound to this id
while (it.hasNext()) {
final Pair<Integer, Intent.FilterComparison> key = it.next();
if (key.first.intValue() == appWidgetId) {
- final ServiceConnection conn = mBoundRemoteViewsServices.get(key);
- it.remove();
- conn.onServiceDisconnected(null);
+ final ServiceConnectionProxy conn = (ServiceConnectionProxy)
+ mBoundRemoteViewsServices.get(key);
+ conn.disconnect();
mContext.unbindService(conn);
+ it.remove();
}
}
}