summaryrefslogtreecommitdiff
path: root/core/java/android/widget/RemoteViewsAdapter.java
diff options
context:
space:
mode:
authorSunny Goyal <sunnygoyal@google.com>2016-10-27 17:14:43 +0000
committerAndroid (Google) Code Review <android-gerrit@google.com>2016-10-27 17:14:47 +0000
commit584264f6af08faea57653c65db4abcb8111009c0 (patch)
tree674317f0bd73ec401d77e1c0d8f58931763506bd /core/java/android/widget/RemoteViewsAdapter.java
parentf501c31454b267ead3e68ef610662fb5200c7141 (diff)
parent5c022639d7e1eebbeb190975980621a286bb2ff1 (diff)
Merge "Adding support for async view loading in RemoteViewsAdapter"
Diffstat (limited to 'core/java/android/widget/RemoteViewsAdapter.java')
-rw-r--r--core/java/android/widget/RemoteViewsAdapter.java200
1 files changed, 135 insertions, 65 deletions
diff --git a/core/java/android/widget/RemoteViewsAdapter.java b/core/java/android/widget/RemoteViewsAdapter.java
index e0f94fd207d3..11e0a3fa4774 100644
--- a/core/java/android/widget/RemoteViewsAdapter.java
+++ b/core/java/android/widget/RemoteViewsAdapter.java
@@ -45,6 +45,12 @@ import java.lang.ref.WeakReference;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedList;
+import java.util.concurrent.Executor;
+
+import java.lang.ref.WeakReference;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.LinkedList;
/**
* An adapter to a RemoteViewsService which fetches and caches RemoteViews
@@ -73,7 +79,8 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
private final Context mContext;
private final Intent mIntent;
private final int mAppWidgetId;
- private LayoutInflater mLayoutInflater;
+ private final Executor mAsyncViewLoadExecutor;
+
private RemoteViewsAdapterServiceConnection mServiceConnection;
private WeakReference<RemoteAdapterConnectionCallback> mCallback;
private OnClickHandler mRemoteViewsOnClickHandler;
@@ -120,15 +127,33 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
/**
* @return whether the adapter was set or not.
*/
- public boolean onRemoteAdapterConnected();
+ boolean onRemoteAdapterConnected();
- public void onRemoteAdapterDisconnected();
+ void onRemoteAdapterDisconnected();
/**
* This defers a notifyDataSetChanged on the pending RemoteViewsAdapter if it has not
* connected yet.
*/
- public void deferNotifyDataSetChanged();
+ void deferNotifyDataSetChanged();
+
+ void setRemoteViewsAdapter(Intent intent, boolean isAsync);
+ }
+
+ public static class AsyncRemoteAdapterAction implements Runnable {
+
+ private final RemoteAdapterConnectionCallback mCallback;
+ private final Intent mIntent;
+
+ public AsyncRemoteAdapterAction(RemoteAdapterConnectionCallback callback, Intent intent) {
+ mCallback = callback;
+ mIntent = intent;
+ }
+
+ @Override
+ public void run() {
+ mCallback.setRemoteViewsAdapter(mIntent, true);
+ }
}
/**
@@ -162,7 +187,7 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
}
mIsConnecting = true;
} catch (Exception e) {
- Log.e("RemoteViewsAdapterServiceConnection", "bind(): " + e.getMessage());
+ Log.e("RVAServiceConnection", "bind(): " + e.getMessage());
mIsConnecting = false;
mIsConnected = false;
}
@@ -180,7 +205,7 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
}
mIsConnecting = false;
} catch (Exception e) {
- Log.e("RemoteViewsAdapterServiceConnection", "unbind(): " + e.getMessage());
+ Log.e("RVAServiceConnection", "unbind(): " + e.getMessage());
mIsConnecting = false;
mIsConnected = false;
}
@@ -298,15 +323,29 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
* Updates this RemoteViewsFrameLayout depending on the view that was loaded.
* @param view the RemoteViews that was loaded. If null, the RemoteViews was not loaded
* successfully.
+ * @param forceApplyAsync when true, the host will always try to inflate the view
+ * asynchronously (for eg, when we are already showing the loading
+ * view)
*/
- public void onRemoteViewsLoaded(RemoteViews view, OnClickHandler handler) {
+ public void onRemoteViewsLoaded(RemoteViews view, OnClickHandler handler,
+ boolean forceApplyAsync) {
setOnClickHandler(handler);
- applyRemoteViews(view);
+ applyRemoteViews(view, forceApplyAsync || ((view != null) && view.prefersAsyncApply()));
}
+ /**
+ * Creates a default loading view. Uses the size of the first row as a guide for the
+ * size of the loading view.
+ */
@Override
protected View getDefaultView() {
- return mCache.getMetaData().createDefaultLoadingView(this);
+ int viewHeight = mCache.getMetaData().getLoadingTemplate(getContext()).defaultHeight;
+ // Compose the loading view text
+ TextView loadingTextView = (TextView) LayoutInflater.from(getContext()).inflate(
+ com.android.internal.R.layout.remote_views_adapter_default_loading_view,
+ this, false);
+ loadingTextView.setHeight(viewHeight);
+ return loadingTextView;
}
@Override
@@ -359,7 +398,7 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
if (refs != null) {
// Notify all the references for that position of the newly loaded RemoteViews
for (final RemoteViewsFrameLayout ref : refs) {
- ref.onRemoteViewsLoaded(view, mRemoteViewsOnClickHandler);
+ ref.onRemoteViewsLoaded(view, mRemoteViewsOnClickHandler, true);
if (mViewToLinkedList.containsKey(ref)) {
mViewToLinkedList.remove(ref);
}
@@ -402,9 +441,7 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
// 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
// the default loading view.
- RemoteViews mUserLoadingView;
- RemoteViews mFirstView;
- int mFirstViewHeight;
+ LoadingViewTemplate loadingTemplate;
// A mapping from type id to a set of unique type ids
private final SparseIntArray mTypeIdIndexMap = new SparseIntArray();
@@ -418,7 +455,7 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
count = d.count;
viewTypeCount = d.viewTypeCount;
hasStableIds = d.hasStableIds;
- setLoadingViewTemplates(d.mUserLoadingView, d.mFirstView);
+ loadingTemplate = d.loadingTemplate;
}
}
@@ -428,20 +465,10 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
// by default there is at least one dummy view type
viewTypeCount = 1;
hasStableIds = true;
- mUserLoadingView = null;
- mFirstView = null;
- mFirstViewHeight = 0;
+ loadingTemplate = null;
mTypeIdIndexMap.clear();
}
- public void setLoadingViewTemplates(RemoteViews loadingView, RemoteViews firstView) {
- mUserLoadingView = loadingView;
- if (firstView != null) {
- mFirstView = firstView;
- mFirstViewHeight = -1;
- }
- }
-
public int getMappedViewType(int typeId) {
int mappedTypeId = mTypeIdIndexMap.get(typeId, -1);
if (mappedTypeId == -1) {
@@ -457,33 +484,11 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
return (mappedType < viewTypeCount);
}
- /**
- * Creates a default loading view. Uses the size of the first row as a guide for the
- * size of the loading view.
- */
- private synchronized View createDefaultLoadingView(ViewGroup parent) {
- final Context context = parent.getContext();
- if (mFirstViewHeight < 0) {
- try {
- View firstView = mFirstView.apply(parent.getContext(), parent);
- firstView.measure(
- MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
- MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
- mFirstViewHeight = firstView.getMeasuredHeight();
- } catch (Exception e) {
- float density = context.getResources().getDisplayMetrics().density;
- mFirstViewHeight = Math.round(sDefaultLoadingViewHeight * density);
- Log.w(TAG, "Error inflating first RemoteViews" + e);
- }
- mFirstView = null;
+ public synchronized LoadingViewTemplate getLoadingTemplate(Context context) {
+ if (loadingTemplate == null) {
+ loadingTemplate = new LoadingViewTemplate(null, context);
}
-
- // Compose the loading view text
- TextView loadingTextView = (TextView) LayoutInflater.from(context).inflate(
- com.android.internal.R.layout.remote_views_adapter_default_loading_view,
- parent, false);
- loadingTextView.setHeight(mFirstViewHeight);
- return loadingTextView;
+ return loadingTemplate;
}
}
@@ -772,7 +777,7 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
}
public RemoteViewsAdapter(Context context, Intent intent,
- RemoteAdapterConnectionCallback callback) {
+ RemoteAdapterConnectionCallback callback, boolean useAsyncLoader) {
mContext = context;
mIntent = intent;
@@ -781,7 +786,6 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
}
mAppWidgetId = intent.getIntExtra(RemoteViews.EXTRA_REMOTEADAPTER_APPWIDGET_ID, -1);
- mLayoutInflater = LayoutInflater.from(context);
mRequestedViews = new RemoteViewsFrameLayoutRefSet();
// Strip the previously injected app widget id from service intent
@@ -794,6 +798,7 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
mWorkerThread.start();
mWorkerQueue = new Handler(mWorkerThread.getLooper());
mMainQueue = new Handler(Looper.myLooper(), this);
+ mAsyncViewLoadExecutor = useAsyncLoader ? new HandlerThreadExecutor(mWorkerThread) : null;
if (sCacheRemovalThread == null) {
sCacheRemovalThread = new HandlerThread("RemoteViewsAdapter-cachePruner");
@@ -941,10 +946,14 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
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);
+ LoadingViewTemplate loadingTemplate =
+ new LoadingViewTemplate(factory.getLoadingView(), mContext);
+ if ((count > 0) && (loadingTemplate.remoteViews == null)) {
+ RemoteViews firstView = factory.getViewAt(0);
+ if (firstView != null) {
+ loadingTemplate.loadFirstViewHeight(firstView, mContext,
+ new HandlerThreadExecutor(mWorkerThread));
+ }
}
final RemoteViewsMetaData tmpMetaData = mCache.getTemporaryMetaData();
synchronized (tmpMetaData) {
@@ -952,7 +961,7 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
// We +1 because the base view type is the loading view
tmpMetaData.viewTypeCount = viewTypeCount + 1;
tmpMetaData.count = count;
- tmpMetaData.setLoadingViewTemplates(loadingView, firstView);
+ tmpMetaData.loadingTemplate = loadingTemplate;
}
} catch(RemoteException e) {
processException("updateMetaData", e);
@@ -1100,18 +1109,25 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
hasNewItems = mCache.queuePositionsToBePreloadedFromRequestedPosition(position);
}
- final RemoteViewsFrameLayout layout =
- (convertView instanceof RemoteViewsFrameLayout)
- ? (RemoteViewsFrameLayout) convertView
- : new RemoteViewsFrameLayout(parent.getContext(), mCache);
+ final RemoteViewsFrameLayout layout;
+ if (convertView instanceof RemoteViewsFrameLayout) {
+ layout = (RemoteViewsFrameLayout) convertView;
+ } else {
+ layout = new RemoteViewsFrameLayout(parent.getContext(), mCache);
+ layout.setAsyncExecutor(mAsyncViewLoadExecutor);
+ }
+
if (isInCache) {
- layout.onRemoteViewsLoaded(rv, mRemoteViewsOnClickHandler);
+ // Apply the view synchronously if possible, to avoid flickering
+ layout.onRemoteViewsLoaded(rv, mRemoteViewsOnClickHandler, false);
if (hasNewItems) loadNextIndexInBackground();
} 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.
- layout.onRemoteViewsLoaded(mCache.getMetaData().mUserLoadingView,
- mRemoteViewsOnClickHandler);
+ layout.onRemoteViewsLoaded(
+ mCache.getMetaData().getLoadingTemplate(mContext).remoteViews,
+ mRemoteViewsOnClickHandler,
+ false);
mRequestedViews.add(position, layout);
mCache.queueRequestedPositionToLoad(position);
loadNextIndexInBackground();
@@ -1285,4 +1301,58 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
mMainQueue.removeMessages(sUnbindServiceMessageType);
return mServiceConnection.isConnected();
}
+
+ private static class HandlerThreadExecutor implements Executor {
+ private final HandlerThread mThread;
+
+ HandlerThreadExecutor(HandlerThread thread) {
+ mThread = thread;
+ }
+
+ @Override
+ public void execute(Runnable runnable) {
+ if (Thread.currentThread().getId() == mThread.getId()) {
+ runnable.run();
+ } else {
+ new Handler(mThread.getLooper()).post(runnable);
+ }
+ }
+ }
+
+ private static class LoadingViewTemplate {
+ public final RemoteViews remoteViews;
+ public int defaultHeight;
+
+ LoadingViewTemplate(RemoteViews views, Context context) {
+ remoteViews = views;
+
+ float density = context.getResources().getDisplayMetrics().density;
+ defaultHeight = Math.round(sDefaultLoadingViewHeight * density);
+ }
+
+ public void loadFirstViewHeight(
+ RemoteViews firstView, Context context, Executor executor) {
+ // Inflate the first view on the worker thread
+ firstView.applyAsync(context, new RemoteViewsFrameLayout(context, null), executor,
+ new RemoteViews.OnViewAppliedListener() {
+ @Override
+ public void onViewApplied(View v) {
+ try {
+ v.measure(
+ MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
+ MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
+ defaultHeight = v.getMeasuredHeight();
+ } catch (Exception e) {
+ onError(e);
+ }
+ }
+
+ @Override
+ public void onError(Exception e) {
+ // Do nothing. The default height will stay the same.
+ Log.w(TAG, "Error inflating first RemoteViews", e);
+ }
+ });
+ }
+ }
}