diff options
| author | Sunny Goyal <sunnygoyal@google.com> | 2017-10-18 00:17:20 -0700 |
|---|---|---|
| committer | Sunny Goyal <sunnygoyal@google.com> | 2017-10-20 16:43:23 -0700 |
| commit | e1273ebbb7d0d208d0bb0304ccefc5659633068c (patch) | |
| tree | b8f01ddb513a118f819287b5e043cd10049b9cdd /core/java/android/widget/RemoteViewsAdapter.java | |
| parent | 2ea41b52d21db3ad60d734aae64202080b1dc772 (diff) | |
Binding to the RemoteViewsService directly from the host
The host passes an IServiceConnection to the AppWidgetManager which
is used to bind to the RemoteViewsService. This allows the host to
recieve the connection callbacks directly instead of proxying it via
the AppWidgetManager. The host is also responsible for unbinding to
the service.
Bug: 26481160
Test: adb shell \
am instrument -w -e class android.widget.RemoteViewsAdapterTest \
com.android.frameworks.coretests/android.support.test.runner.AndroidJUnitRunner
Change-Id: Iac400095a319c3a43714c82fda7516be1ccc68af
Diffstat (limited to 'core/java/android/widget/RemoteViewsAdapter.java')
| -rw-r--r-- | core/java/android/widget/RemoteViewsAdapter.java | 647 |
1 files changed, 280 insertions, 367 deletions
diff --git a/core/java/android/widget/RemoteViewsAdapter.java b/core/java/android/widget/RemoteViewsAdapter.java index 51277e4316c8..e5ae0ca0070c 100644 --- a/core/java/android/widget/RemoteViewsAdapter.java +++ b/core/java/android/widget/RemoteViewsAdapter.java @@ -16,11 +16,14 @@ package android.widget; -import android.Manifest; +import android.annotation.WorkerThread; +import android.app.IServiceConnection; import android.appwidget.AppWidgetHostView; import android.appwidget.AppWidgetManager; +import android.content.ComponentName; import android.content.Context; import android.content.Intent; +import android.content.ServiceConnection; import android.content.pm.ApplicationInfo; import android.os.Handler; import android.os.HandlerThread; @@ -29,7 +32,6 @@ import android.os.Looper; import android.os.Message; import android.os.RemoteException; import android.util.Log; -import android.util.Slog; import android.util.SparseArray; import android.util.SparseBooleanArray; import android.util.SparseIntArray; @@ -39,7 +41,6 @@ import android.view.View.MeasureSpec; import android.view.ViewGroup; import android.widget.RemoteViews.OnClickHandler; -import com.android.internal.widget.IRemoteViewsAdapterConnection; import com.android.internal.widget.IRemoteViewsFactory; import java.lang.ref.WeakReference; @@ -49,67 +50,68 @@ import java.util.LinkedList; import java.util.concurrent.Executor; /** - * An adapter to a RemoteViewsService which fetches and caches RemoteViews - * to be later inflated as child views. + * An adapter to a RemoteViewsService which fetches and caches RemoteViews to be later inflated as + * child views. + * + * The adapter runs in the host process, typically a Launcher app. + * + * It makes a service connection to the {@link RemoteViewsService} running in the + * AppWidgetsProvider's process. This connection is made on a background thread (and proxied via + * the platform to get the bind permissions) and all interaction with the service is done on the + * background thread. + * + * On first bind, the adapter will load can cache the RemoteViews locally. Afterwards the + * connection is only made when new RemoteViews are required. + * @hide */ -/** @hide */ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback { - private static final String MULTI_USER_PERM = Manifest.permission.INTERACT_ACROSS_USERS_FULL; private static final String TAG = "RemoteViewsAdapter"; // The max number of items in the cache - private static final int sDefaultCacheSize = 40; + private static final int DEFAULT_CACHE_SIZE = 40; // 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 UNBIND_SERVICE_DELAY = 5000; // Default height for the default loading view, in case we cannot get inflate the first view - private static final int sDefaultLoadingViewHeight = 50; + private static final int DEFAULT_LOADING_VIEW_HEIGHT = 50; + + // We cache the FixedSizeRemoteViewsCaches across orientation. These are the related data + // structures; + private static final HashMap<RemoteViewsCacheKey, FixedSizeRemoteViewsCache> + sCachedRemoteViewsCaches = new HashMap<>(); + private static final HashMap<RemoteViewsCacheKey, Runnable> + sRemoteViewsCacheRemoveRunnables = new HashMap<>(); + + private static HandlerThread sCacheRemovalThread; + private static Handler sCacheRemovalQueue; - // 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; + // We keep the cache around for a duration after onSaveInstanceState for use on re-inflation. + // If a new RemoteViewsAdapter with the same intent / widget id isn't constructed within this + // duration, the cache is dropped. + private static final int REMOTE_VIEWS_CACHE_DURATION = 5000; private final Context mContext; private final Intent mIntent; private final int mAppWidgetId; private final Executor mAsyncViewLoadExecutor; - private RemoteViewsAdapterServiceConnection mServiceConnection; - private WeakReference<RemoteAdapterConnectionCallback> mCallback; private OnClickHandler mRemoteViewsOnClickHandler; private final FixedSizeRemoteViewsCache mCache; private int mVisibleWindowLowerBound; private int mVisibleWindowUpperBound; - // 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; - private HandlerThread mWorkerThread; + private final HandlerThread mWorkerThread; // items may be interrupted within the normally processed queues - private Handler mWorkerQueue; - private Handler mMainQueue; - - // We cache the FixedSizeRemoteViewsCaches across orientation. These are the related data - // structures; - private static final HashMap<RemoteViewsCacheKey, FixedSizeRemoteViewsCache> - sCachedRemoteViewsCaches = new HashMap<>(); - private static final HashMap<RemoteViewsCacheKey, Runnable> - sRemoteViewsCacheRemoveRunnables = new HashMap<>(); - - private static HandlerThread sCacheRemovalThread; - private static Handler sCacheRemovalQueue; - - // We keep the cache around for a duration after onSaveInstanceState for use on re-inflation. - // If a new RemoteViewsAdapter with the same intent / widget id isn't constructed within this - // duration, the cache is dropped. - private static final int REMOTE_VIEWS_CACHE_DURATION = 5000; + private final Handler mMainHandler; + private final RemoteServiceHandler mServiceHandler; + private final RemoteAdapterConnectionCallback mCallback; // Used to indicate to the AdapterView that it can use this Adapter immediately after // construction (happens when we have a cached FixedSizeRemoteViewsCache). @@ -158,154 +160,192 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback } } + static final int MSG_REQUEST_BIND = 1; + static final int MSG_NOTIFY_DATA_SET_CHANGED = 2; + static final int MSG_LOAD_NEXT_ITEM = 3; + static final int MSG_UNBIND_SERVICE = 4; + + private static final int MSG_MAIN_HANDLER_COMMIT_METADATA = 1; + private static final int MSG_MAIN_HANDLER_SUPER_NOTIFY_DATA_SET_CHANGED = 2; + private static final int MSG_MAIN_HANDLER_REMOTE_ADAPTER_CONNECTED = 3; + private static final int MSG_MAIN_HANDLER_REMOTE_ADAPTER_DISCONNECTED = 4; + private static final int MSG_MAIN_HANDLER_REMOTE_VIEWS_LOADED = 5; + /** - * The service connection that gets populated when the RemoteViewsService is - * bound. This must be a static inner class to ensure that no references to the outer - * RemoteViewsAdapter instance is retained (this would prevent the RemoteViewsAdapter from being - * garbage collected, and would cause us to leak activities due to the caching mechanism for - * FrameLayouts in the adapter). + * Handler for various interactions with the {@link RemoteViewsService}. */ - private static class RemoteViewsAdapterServiceConnection extends - IRemoteViewsAdapterConnection.Stub { - private boolean mIsConnected; - private boolean mIsConnecting; - private WeakReference<RemoteViewsAdapter> mAdapter; + private static class RemoteServiceHandler extends Handler implements ServiceConnection { + + private final WeakReference<RemoteViewsAdapter> mAdapter; + private final Context mContext; + private IRemoteViewsFactory mRemoteViewsFactory; - public RemoteViewsAdapterServiceConnection(RemoteViewsAdapter adapter) { - mAdapter = new WeakReference<RemoteViewsAdapter>(adapter); + // The last call to notifyDataSetChanged didn't succeed, try again on next service bind. + private boolean mNotifyDataSetChangedPending = false; + private boolean mBindRequested = false; + + RemoteServiceHandler(Looper workerLooper, RemoteViewsAdapter adapter, Context context) { + super(workerLooper); + mAdapter = new WeakReference<>(adapter); + mContext = context; } - public synchronized void bind(Context context, int appWidgetId, Intent intent) { - if (!mIsConnecting) { - try { - RemoteViewsAdapter adapter; - final AppWidgetManager mgr = AppWidgetManager.getInstance(context); - if ((adapter = mAdapter.get()) != null) { - mgr.bindRemoteViewsService(context.getOpPackageName(), appWidgetId, - intent, asBinder()); - } else { - Slog.w(TAG, "bind: adapter was null"); - } - mIsConnecting = true; - } catch (Exception e) { - Log.e("RVAServiceConnection", "bind(): " + e.getMessage()); - mIsConnecting = false; - mIsConnected = false; + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + // This is called on the same thread. + mRemoteViewsFactory = IRemoteViewsFactory.Stub.asInterface(service); + enqueueDeferredUnbindServiceMessage(); + + RemoteViewsAdapter adapter = mAdapter.get(); + if (adapter == null) { + return; + } + + if (mNotifyDataSetChangedPending) { + mNotifyDataSetChangedPending = false; + Message msg = Message.obtain(this, MSG_NOTIFY_DATA_SET_CHANGED); + handleMessage(msg); + msg.recycle(); + } else { + if (!sendNotifyDataSetChange(false)) { + return; } + + // Request meta data so that we have up to date data when calling back to + // the remote adapter callback + adapter.updateTemporaryMetaData(mRemoteViewsFactory); + adapter.mMainHandler.sendEmptyMessage(MSG_MAIN_HANDLER_COMMIT_METADATA); + adapter.mMainHandler.sendEmptyMessage(MSG_MAIN_HANDLER_REMOTE_ADAPTER_CONNECTED); } } - public synchronized void unbind(Context context, int appWidgetId, Intent intent) { - try { - RemoteViewsAdapter adapter; - final AppWidgetManager mgr = AppWidgetManager.getInstance(context); - if ((adapter = mAdapter.get()) != null) { - mgr.unbindRemoteViewsService(context.getOpPackageName(), appWidgetId, intent); - } else { - Slog.w(TAG, "unbind: adapter was null"); - } - mIsConnecting = false; - } catch (Exception e) { - Log.e("RVAServiceConnection", "unbind(): " + e.getMessage()); - mIsConnecting = false; - mIsConnected = false; + @Override + public void onServiceDisconnected(ComponentName name) { + mRemoteViewsFactory = null; + RemoteViewsAdapter adapter = mAdapter.get(); + if (adapter != null) { + adapter.mMainHandler.sendEmptyMessage(MSG_MAIN_HANDLER_REMOTE_ADAPTER_DISCONNECTED); } } - public synchronized void onServiceConnected(IBinder service) { - mRemoteViewsFactory = IRemoteViewsFactory.Stub.asInterface(service); + @Override + public void handleMessage(Message msg) { + RemoteViewsAdapter adapter = mAdapter.get(); - // 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() { - if (adapter.mNotifyDataSetChangedAfterOnServiceConnected) { - // Handle queued notifyDataSetChanged() if necessary - adapter.onNotifyDataSetChanged(); - } else { - IRemoteViewsFactory factory = - adapter.mServiceConnection.getRemoteViewsFactory(); - try { - if (!factory.isCreated()) { - // We only call onDataSetChanged() if this is the factory was just - // create in response to this bind - factory.onDataSetChanged(); - } - } catch (RemoteException e) { - Log.e(TAG, "Error notifying factory of data set changed in " + - "onServiceConnected(): " + e.getMessage()); - - // Return early to prevent anything further from being notified - // (effectively nothing has changed) - return; - } catch (RuntimeException e) { - Log.e(TAG, "Error notifying factory of data set changed in " + - "onServiceConnected(): " + e.getMessage()); - } + switch (msg.what) { + case MSG_REQUEST_BIND: { + if (adapter == null || mRemoteViewsFactory != null) { + enqueueDeferredUnbindServiceMessage(); + } + if (mBindRequested) { + return; + } + int flags = Context.BIND_AUTO_CREATE + | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE; + final IServiceConnection sd = mContext.getServiceDispatcher(this, this, flags); + Intent intent = (Intent) msg.obj; + int appWidgetId = msg.arg1; + mBindRequested = AppWidgetManager.getInstance(mContext) + .bindRemoteViewsService(mContext, appWidgetId, intent, sd, flags); + return; + } + case MSG_NOTIFY_DATA_SET_CHANGED: { + enqueueDeferredUnbindServiceMessage(); + if (adapter == null) { + return; + } + if (mRemoteViewsFactory == null) { + mNotifyDataSetChangedPending = true; + adapter.requestBindService(); + return; + } + if (!sendNotifyDataSetChange(true)) { + return; + } - // Request meta data so that we have up to date data when calling back to - // the remote adapter callback - adapter.updateTemporaryMetaData(); - - // 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) { - callback.onRemoteAdapterConnected(); - } - } - }); + // Flush the cache so that we can reload new items from the service + synchronized (adapter.mCache) { + adapter.mCache.reset(); } - // Enqueue unbind message - adapter.enqueueDeferredUnbindServiceMessage(); - mIsConnected = true; - mIsConnecting = false; - } - }); - } + // Re-request the new metadata (only after the notification to the factory) + adapter.updateTemporaryMetaData(mRemoteViewsFactory); + int newCount; + int[] visibleWindow; + synchronized (adapter.mCache.getTemporaryMetaData()) { + newCount = adapter.mCache.getTemporaryMetaData().count; + visibleWindow = adapter.getVisibleWindow(newCount); + } - public synchronized void onServiceDisconnected() { - mIsConnected = false; - mIsConnecting = false; - mRemoteViewsFactory = null; + // Pre-load (our best guess of) the views which are currently visible in the + // AdapterView. This mitigates flashing and flickering of loading views when a + // widget notifies that its data has changed. + for (int position : visibleWindow) { + // Because temporary meta data is only ever modified from this thread + // (ie. mWorkerThread), it is safe to assume that count is a valid + // representation. + if (position < newCount) { + adapter.updateRemoteViews(mRemoteViewsFactory, position, false); + } + } - // Clear the main/worker queues - final RemoteViewsAdapter adapter = mAdapter.get(); - if (adapter == null) return; + // Propagate the notification back to the base adapter + adapter.mMainHandler.sendEmptyMessage(MSG_MAIN_HANDLER_COMMIT_METADATA); + adapter.mMainHandler.sendEmptyMessage( + MSG_MAIN_HANDLER_SUPER_NOTIFY_DATA_SET_CHANGED); + return; + } - adapter.mMainQueue.post(new Runnable() { - @Override - public void run() { - // Dequeue any unbind messages - adapter.mMainQueue.removeMessages(sUnbindServiceMessageType); + case MSG_LOAD_NEXT_ITEM: { + if (adapter == null || mRemoteViewsFactory == null) { + return; + } + removeMessages(MSG_UNBIND_SERVICE); + // Get the next index to load + final int position = adapter.mCache.getNextIndexToLoad(); + if (position > -1) { + // Load the item, and notify any existing RemoteViewsFrameLayouts + adapter.updateRemoteViews(mRemoteViewsFactory, position, true); - final RemoteAdapterConnectionCallback callback = adapter.mCallback.get(); - if (callback != null) { - callback.onRemoteAdapterDisconnected(); + // Queue up for the next one to load + sendEmptyMessage(MSG_LOAD_NEXT_ITEM); + } else { + // No more items to load, so queue unbind + enqueueDeferredUnbindServiceMessage(); } + return; + } + case MSG_UNBIND_SERVICE: { + unbindNow(); + return; } - }); + } } - public synchronized IRemoteViewsFactory getRemoteViewsFactory() { - return mRemoteViewsFactory; + protected void unbindNow() { + if (mBindRequested) { + mBindRequested = false; + mContext.unbindService(this); + } + mRemoteViewsFactory = null; } - public synchronized boolean isConnected() { - return mIsConnected; + private boolean sendNotifyDataSetChange(boolean always) { + try { + if (always || !mRemoteViewsFactory.isCreated()) { + mRemoteViewsFactory.onDataSetChanged(); + } + return true; + } catch (RemoteException | RuntimeException e) { + Log.e(TAG, "Error in updateNotifyDataSetChanged(): " + e.getMessage()); + return false; + } + } + + private void enqueueDeferredUnbindServiceMessage() { + removeMessages(MSG_UNBIND_SERVICE); + sendEmptyMessageDelayed(MSG_UNBIND_SERVICE, UNBIND_SERVICE_DELAY); } } @@ -507,7 +547,6 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback * */ private static class FixedSizeRemoteViewsCache { - private static final String TAG = "FixedSizeRemoteViewsCache"; // The meta data related to all the RemoteViews, ie. count, is stable, etc. // The meta data objects are made final so that they can be locked on independently @@ -671,7 +710,7 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback } } - int count = 0; + int count; synchronized (mMetaData) { count = mMetaData.count; } @@ -786,9 +825,11 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback // Initialize the worker thread mWorkerThread = new HandlerThread("RemoteViewsCache-loader"); mWorkerThread.start(); - mWorkerQueue = new Handler(mWorkerThread.getLooper()); - mMainQueue = new Handler(Looper.myLooper(), this); + mMainHandler = new Handler(Looper.myLooper(), this); + mServiceHandler = new RemoteServiceHandler(mWorkerThread.getLooper(), this, + context.getApplicationContext()); mAsyncViewLoadExecutor = useAsyncLoader ? new HandlerThreadExecutor(mWorkerThread) : null; + mCallback = callback; if (sCacheRemovalThread == null) { sCacheRemovalThread = new HandlerThread("RemoteViewsAdapter-cachePruner"); @@ -796,10 +837,6 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback sCacheRemovalQueue = new Handler(sCacheRemovalThread.getLooper()); } - // Initialize the cache and the service connection on startup - mCallback = new WeakReference<RemoteAdapterConnectionCallback>(callback); - mServiceConnection = new RemoteViewsAdapterServiceConnection(this); - RemoteViewsCacheKey key = new RemoteViewsCacheKey(new Intent.FilterComparison(mIntent), mAppWidgetId); @@ -814,7 +851,7 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback } } } else { - mCache = new FixedSizeRemoteViewsCache(sDefaultCacheSize); + mCache = new FixedSizeRemoteViewsCache(DEFAULT_CACHE_SIZE); } if (!mDataReady) { requestBindService(); @@ -825,9 +862,8 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback @Override protected void finalize() throws Throwable { try { - if (mWorkerThread != null) { - mWorkerThread.quit(); - } + mServiceHandler.unbindNow(); + mWorkerThread.quit(); } finally { super.finalize(); } @@ -864,16 +900,13 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback sCachedRemoteViewsCaches.put(key, mCache); } - Runnable r = new Runnable() { - @Override - public void run() { - synchronized (sCachedRemoteViewsCaches) { - if (sCachedRemoteViewsCaches.containsKey(key)) { - sCachedRemoteViewsCaches.remove(key); - } - if (sRemoteViewsCacheRemoveRunnables.containsKey(key)) { - sRemoteViewsCacheRemoveRunnables.remove(key); - } + Runnable r = () -> { + synchronized (sCachedRemoteViewsCaches) { + if (sCachedRemoteViewsCaches.containsKey(key)) { + sCachedRemoteViewsCaches.remove(key); + } + if (sRemoteViewsCacheRemoveRunnables.containsKey(key)) { + sRemoteViewsCacheRemoveRunnables.remove(key); } } }; @@ -882,54 +915,8 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback } } - private void loadNextIndexInBackground() { - mWorkerQueue.post(new Runnable() { - @Override - public void run() { - 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, true); - - // Queue up for the next one to load - loadNextIndexInBackground(); - } else { - // No more items to load, so queue unbind - enqueueDeferredUnbindServiceMessage(); - } - } - } - }); - } - - private void processException(String method, Exception e) { - Log.e("RemoteViewsAdapter", "Error in " + method + ": " + e.getMessage()); - - // 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(); - + @WorkerThread + private void updateTemporaryMetaData(IRemoteViewsFactory factory) { try { // get the properties/first view (so that we can use it to // measure our dummy views) @@ -953,40 +940,40 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback tmpMetaData.count = count; tmpMetaData.loadingTemplate = loadingTemplate; } - } catch(RemoteException e) { - processException("updateMetaData", e); - } catch(RuntimeException e) { - processException("updateMetaData", e); + } catch (RemoteException | RuntimeException e) { + Log.e("RemoteViewsAdapter", "Error in updateMetaData: " + e.getMessage()); + + // If we encounter a crash when updating, we should reset the metadata & cache + // and trigger a notifyDataSetChanged to update the widget accordingly + synchronized (mCache.getMetaData()) { + mCache.getMetaData().reset(); + } + synchronized (mCache) { + mCache.reset(); + } + mMainHandler.sendEmptyMessage(MSG_MAIN_HANDLER_SUPER_NOTIFY_DATA_SET_CHANGED); } } - private void updateRemoteViews(final int position, boolean notifyWhenLoaded) { - IRemoteViewsFactory factory = mServiceConnection.getRemoteViewsFactory(); - + @WorkerThread + private void updateRemoteViews(IRemoteViewsFactory factory, int position, + boolean notifyWhenLoaded) { // Load the item information from the remote service - RemoteViews remoteViews = null; - long itemId = 0; + final RemoteViews remoteViews; + final long itemId; try { remoteViews = factory.getViewAt(position); itemId = factory.getItemId(position); - } catch (RemoteException e) { + + if (remoteViews == null) { + throw new RuntimeException("Null remoteViews"); + } + } catch (RemoteException | RuntimeException 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; - } catch (RuntimeException e) { - Log.e(TAG, "Error in updateRemoteViews(" + position + "): " + e.getMessage()); - 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; } if (remoteViews.mApplication != null) { @@ -1013,21 +1000,15 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback } synchronized (mCache) { if (viewTypeInRange) { - int[] visibleWindow = getVisibleWindow(mVisibleWindowLowerBound, - mVisibleWindowUpperBound, cacheCount); + int[] visibleWindow = getVisibleWindow(cacheCount); // Cache the RemoteViews we loaded mCache.insert(position, remoteViews, itemId, visibleWindow); - // Notify all the views that we have previously returned for this index that - // there is new data for it. - final RemoteViews rv = remoteViews; if (notifyWhenLoaded) { - mMainQueue.post(new Runnable() { - @Override - public void run() { - mRequestedViews.notifyOnRemoteViewsLoaded(position, rv); - } - }); + // Notify all the views that we have previously returned for this index that + // there is new data for it. + Message.obtain(mMainHandler, MSG_MAIN_HANDLER_REMOTE_VIEWS_LOADED, position, 0, + remoteViews).sendToTarget(); } } else { // We need to log an error here, as the the view type count specified by the @@ -1066,7 +1047,7 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback } public int getItemViewType(int position) { - int typeId = 0; + final int typeId; synchronized (mCache) { if (mCache.containsMetaDataAt(position)) { typeId = mCache.getMetaDataAt(position).typeId; @@ -1097,14 +1078,13 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback synchronized (mCache) { RemoteViews rv = mCache.getRemoteViewsAt(position); boolean isInCache = (rv != null); - boolean isConnected = mServiceConnection.isConnected(); boolean hasNewItems = false; if (convertView != null && convertView instanceof RemoteViewsFrameLayout) { mRequestedViews.removeView((RemoteViewsFrameLayout) convertView); } - if (!isInCache && !isConnected) { + if (!isInCache) { // Requesting bind service will trigger a super.notifyDataSetChanged(), which will // in turn trigger another request to getView() requestBindService(); @@ -1124,7 +1104,9 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback if (isInCache) { // Apply the view synchronously if possible, to avoid flickering layout.onRemoteViewsLoaded(rv, mRemoteViewsOnClickHandler, false); - if (hasNewItems) loadNextIndexInBackground(); + if (hasNewItems) { + mServiceHandler.sendEmptyMessage(MSG_LOAD_NEXT_ITEM); + } } else { // If the views is not loaded, apply the loading view. If the loading view doesn't // exist, the layout will create a default view based on the firstView height. @@ -1134,7 +1116,7 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback false); mRequestedViews.add(position, layout); mCache.queueRequestedPositionToLoad(position); - loadNextIndexInBackground(); + mServiceHandler.sendEmptyMessage(MSG_LOAD_NEXT_ITEM); } return layout; } @@ -1158,69 +1140,12 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback return getCount() <= 0; } - private void onNotifyDataSetChanged() { - // Complete the actual notifyDataSetChanged() call initiated earlier - IRemoteViewsFactory factory = mServiceConnection.getRemoteViewsFactory(); - try { - factory.onDataSetChanged(); - } catch (RemoteException e) { - Log.e(TAG, "Error in updateNotifyDataSetChanged(): " + e.getMessage()); - - // Return early to prevent from further being notified (since nothing has - // changed) - return; - } catch (RuntimeException e) { - Log.e(TAG, "Error in updateNotifyDataSetChanged(): " + e.getMessage()); - 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(); - int newCount; - int[] visibleWindow; - synchronized(mCache.getTemporaryMetaData()) { - newCount = mCache.getTemporaryMetaData().count; - visibleWindow = getVisibleWindow(mVisibleWindowLowerBound, - mVisibleWindowUpperBound, newCount); - } - - // Pre-load (our best guess of) the views which are currently visible in the AdapterView. - // This mitigates flashing and flickering of loading views when a widget notifies that - // its data has changed. - for (int i: visibleWindow) { - // Because temporary meta data is only ever modified from this thread (ie. - // mWorkerThread), it is safe to assume that count is a valid representation. - if (i < newCount) { - updateRemoteViews(i, false); - } - } - - // Propagate the notification back to the base adapter - mMainQueue.post(new Runnable() { - @Override - public void run() { - synchronized (mCache) { - mCache.commitTemporaryMetaData(); - } - - superNotifyDataSetChanged(); - enqueueDeferredUnbindServiceMessage(); - } - }); - - // Reset the notify flagflag - mNotifyDataSetChangedAfterOnServiceConnected = false; - } - /** * Returns a sorted array of all integers between lower and upper. */ - private int[] getVisibleWindow(int lower, int upper, int count) { + private int[] getVisibleWindow(int count) { + int lower = mVisibleWindowLowerBound; + int upper = mVisibleWindowUpperBound; // In the case that the window is invalid or uninitialized, return an empty window. if ((lower == 0 && upper == 0) || lower < 0 || upper < 0) { return new int[0]; @@ -1250,23 +1175,8 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback } 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()) { - mNotifyDataSetChangedAfterOnServiceConnected = true; - requestBindService(); - return; - } - - mWorkerQueue.post(new Runnable() { - @Override - public void run() { - onNotifyDataSetChanged(); - } - }); + mServiceHandler.removeMessages(MSG_UNBIND_SERVICE); + mServiceHandler.sendEmptyMessage(MSG_NOTIFY_DATA_SET_CHANGED); } void superNotifyDataSetChanged() { @@ -1275,35 +1185,38 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback @Override public boolean handleMessage(Message msg) { - boolean result = false; switch (msg.what) { - case sUnbindServiceMessageType: - if (mServiceConnection.isConnected()) { - mServiceConnection.unbind(mContext, mAppWidgetId, mIntent); + case MSG_MAIN_HANDLER_COMMIT_METADATA: { + mCache.commitTemporaryMetaData(); + return true; + } + case MSG_MAIN_HANDLER_SUPER_NOTIFY_DATA_SET_CHANGED: { + superNotifyDataSetChanged(); + return true; + } + case MSG_MAIN_HANDLER_REMOTE_ADAPTER_CONNECTED: { + if (mCallback != null) { + mCallback.onRemoteAdapterConnected(); + } + return true; + } + case MSG_MAIN_HANDLER_REMOTE_ADAPTER_DISCONNECTED: { + if (mCallback != null) { + mCallback.onRemoteAdapterDisconnected(); + } + return true; + } + case MSG_MAIN_HANDLER_REMOTE_VIEWS_LOADED: { + mRequestedViews.notifyOnRemoteViewsLoaded(msg.arg1, (RemoteViews) msg.obj); + return true; } - result = true; - break; - default: - break; } - return result; - } - - private void enqueueDeferredUnbindServiceMessage() { - // Remove any existing deferred-unbind messages - mMainQueue.removeMessages(sUnbindServiceMessageType); - mMainQueue.sendEmptyMessageDelayed(sUnbindServiceMessageType, sUnbindServiceDelay); + return false; } - private boolean requestBindService() { - // Try binding the service (which will start it if it's not already running) - if (!mServiceConnection.isConnected()) { - mServiceConnection.bind(mContext, mAppWidgetId, mIntent); - } - - // Remove any existing deferred-unbind messages - mMainQueue.removeMessages(sUnbindServiceMessageType); - return mServiceConnection.isConnected(); + private void requestBindService() { + mServiceHandler.removeMessages(MSG_UNBIND_SERVICE); + Message.obtain(mServiceHandler, MSG_REQUEST_BIND, mAppWidgetId, 0, mIntent).sendToTarget(); } private static class HandlerThreadExecutor implements Executor { @@ -1331,7 +1244,7 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback remoteViews = views; float density = context.getResources().getDisplayMetrics().density; - defaultHeight = Math.round(sDefaultLoadingViewHeight * density); + defaultHeight = Math.round(DEFAULT_LOADING_VIEW_HEIGHT * density); } public void loadFirstViewHeight( |
