diff options
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( |
