Merge "Added support for async inflation of RemoteViews"
This commit is contained in:
@@ -29,6 +29,7 @@ import android.graphics.Paint;
|
||||
import android.graphics.Rect;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.CancellationSignal;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.os.SystemClock;
|
||||
@@ -49,6 +50,8 @@ import android.widget.RemoteViews.OnClickHandler;
|
||||
import android.widget.RemoteViewsAdapter.RemoteAdapterConnectionCallback;
|
||||
import android.widget.TextView;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
/**
|
||||
* Provides the glue to show AppWidget views. This class offers automatic animation
|
||||
* between updates, and will try recycling old views for each incoming
|
||||
@@ -87,6 +90,9 @@ public class AppWidgetHostView extends FrameLayout {
|
||||
Paint mOldPaint = new Paint();
|
||||
private OnClickHandler mOnClickHandler;
|
||||
|
||||
private Executor mAsyncExecutor;
|
||||
private CancellationSignal mLastExecutionSignal;
|
||||
|
||||
/**
|
||||
* Create a host view. Uses default fade animations.
|
||||
*/
|
||||
@@ -339,6 +345,22 @@ public class AppWidgetHostView extends FrameLayout {
|
||||
return new FrameLayout.LayoutParams(context, attrs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets an executor which can be used for asynchronously inflating and applying the remoteviews.
|
||||
* @see {@link RemoteViews#applyAsync(Context, ViewGroup, RemoteViews.OnViewAppliedListener, Executor)}
|
||||
*
|
||||
* @param executor the executor to use or null.
|
||||
* @hide
|
||||
*/
|
||||
public void setAsyncExecutor(Executor executor) {
|
||||
if (mLastExecutionSignal != null) {
|
||||
mLastExecutionSignal.cancel();
|
||||
mLastExecutionSignal = null;
|
||||
}
|
||||
|
||||
mAsyncExecutor = executor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the AppWidgetProviderInfo for this view, and reset it to the
|
||||
* initial layout.
|
||||
@@ -380,6 +402,11 @@ public class AppWidgetHostView extends FrameLayout {
|
||||
}
|
||||
}
|
||||
|
||||
if (mLastExecutionSignal != null) {
|
||||
mLastExecutionSignal.cancel();
|
||||
mLastExecutionSignal = null;
|
||||
}
|
||||
|
||||
if (remoteViews == null) {
|
||||
if (mViewMode == VIEW_MODE_DEFAULT) {
|
||||
// We've already done this -- nothing to do.
|
||||
@@ -389,6 +416,10 @@ public class AppWidgetHostView extends FrameLayout {
|
||||
mLayoutId = -1;
|
||||
mViewMode = VIEW_MODE_DEFAULT;
|
||||
} else {
|
||||
if (mAsyncExecutor != null) {
|
||||
inflateAsync(remoteViews);
|
||||
return;
|
||||
}
|
||||
// Prepare a local reference to the remote Context so we're ready to
|
||||
// inflate any requested LayoutParams.
|
||||
mRemoteContext = getRemoteContext();
|
||||
@@ -421,6 +452,10 @@ public class AppWidgetHostView extends FrameLayout {
|
||||
mViewMode = VIEW_MODE_CONTENT;
|
||||
}
|
||||
|
||||
applyContent(content, recycled, exception);
|
||||
}
|
||||
|
||||
private void applyContent(View content, boolean recycled, Exception exception) {
|
||||
if (content == null) {
|
||||
if (mViewMode == VIEW_MODE_ERROR) {
|
||||
// We've already done this -- nothing to do.
|
||||
@@ -452,6 +487,68 @@ public class AppWidgetHostView extends FrameLayout {
|
||||
}
|
||||
}
|
||||
|
||||
private void inflateAsync(RemoteViews remoteViews) {
|
||||
// Prepare a local reference to the remote Context so we're ready to
|
||||
// inflate any requested LayoutParams.
|
||||
mRemoteContext = getRemoteContext();
|
||||
int layoutId = remoteViews.getLayoutId();
|
||||
|
||||
// If our stale view has been prepared to match active, and the new
|
||||
// layout matches, try recycling it
|
||||
if (layoutId == mLayoutId && mView != null) {
|
||||
try {
|
||||
mLastExecutionSignal = remoteViews.reapplyAsync(mContext,
|
||||
mView,
|
||||
mAsyncExecutor,
|
||||
new ViewApplyListener(remoteViews, layoutId, true),
|
||||
mOnClickHandler);
|
||||
} catch (Exception e) {
|
||||
// Reapply failed. Try apply
|
||||
}
|
||||
}
|
||||
if (mLastExecutionSignal == null) {
|
||||
mLastExecutionSignal = remoteViews.applyAsync(mContext,
|
||||
this,
|
||||
mAsyncExecutor,
|
||||
new ViewApplyListener(remoteViews, layoutId, false),
|
||||
mOnClickHandler);
|
||||
}
|
||||
}
|
||||
|
||||
private class ViewApplyListener implements RemoteViews.OnViewAppliedListener {
|
||||
private final RemoteViews mViews;
|
||||
private final boolean mIsReapply;
|
||||
private final int mLayoutId;
|
||||
|
||||
public ViewApplyListener(RemoteViews views, int layoutId, boolean isReapply) {
|
||||
mViews = views;
|
||||
mLayoutId = layoutId;
|
||||
mIsReapply = isReapply;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewApplied(View v) {
|
||||
AppWidgetHostView.this.mLayoutId = mLayoutId;
|
||||
mViewMode = VIEW_MODE_CONTENT;
|
||||
|
||||
applyContent(v, mIsReapply, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Exception e) {
|
||||
if (mIsReapply) {
|
||||
// Try a fresh replay
|
||||
mLastExecutionSignal = mViews.applyAsync(mContext,
|
||||
AppWidgetHostView.this,
|
||||
mAsyncExecutor,
|
||||
new ViewApplyListener(mViews, mLayoutId, false),
|
||||
mOnClickHandler);
|
||||
} else {
|
||||
applyContent(null, false, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process data-changed notifications for the specified view in the specified
|
||||
* set of {@link RemoteViews} views.
|
||||
|
||||
@@ -29,6 +29,12 @@ import java.lang.annotation.Target;
|
||||
@Target({ ElementType.METHOD })
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface RemotableViewMethod {
|
||||
/**
|
||||
* @return Method name which can be called on a background thread. It should have the
|
||||
* same arguments as the original method and should return a {@link Runnable} (or null)
|
||||
* which will be called on the UI thread.
|
||||
*/
|
||||
String asyncImpl() default "";
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -392,6 +392,26 @@ public class ImageView extends View {
|
||||
return mDrawable;
|
||||
}
|
||||
|
||||
private class ImageDrawableCallback implements Runnable {
|
||||
|
||||
private final Drawable drawable;
|
||||
private final Uri uri;
|
||||
private final int resource;
|
||||
|
||||
ImageDrawableCallback(Drawable drawable, Uri uri, int resource) {
|
||||
this.drawable = drawable;
|
||||
this.uri = uri;
|
||||
this.resource = resource;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
setImageDrawable(drawable);
|
||||
mUri = uri;
|
||||
mResource = resource;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a drawable as the content of this ImageView.
|
||||
*
|
||||
@@ -405,7 +425,7 @@ public class ImageView extends View {
|
||||
*
|
||||
* @attr ref android.R.styleable#ImageView_src
|
||||
*/
|
||||
@android.view.RemotableViewMethod
|
||||
@android.view.RemotableViewMethod(asyncImpl="setImageResourceAsync")
|
||||
public void setImageResource(@DrawableRes int resId) {
|
||||
// The resource configuration may have changed, so we should always
|
||||
// try to load the resource even if the resId hasn't changed.
|
||||
@@ -424,6 +444,11 @@ public class ImageView extends View {
|
||||
invalidate();
|
||||
}
|
||||
|
||||
/** @hide **/
|
||||
public Runnable setImageResourceAsync(@DrawableRes int resId) {
|
||||
return new ImageDrawableCallback(getContext().getDrawable(resId), null, resId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the content of this ImageView to the specified Uri.
|
||||
*
|
||||
@@ -435,7 +460,7 @@ public class ImageView extends View {
|
||||
*
|
||||
* @param uri the Uri of an image, or {@code null} to clear the content
|
||||
*/
|
||||
@android.view.RemotableViewMethod
|
||||
@android.view.RemotableViewMethod(asyncImpl="setImageURIAsync")
|
||||
public void setImageURI(@Nullable Uri uri) {
|
||||
if (mResource != 0 || (mUri != uri && (uri == null || mUri == null || !uri.equals(mUri)))) {
|
||||
updateDrawable(null);
|
||||
@@ -454,6 +479,19 @@ public class ImageView extends View {
|
||||
}
|
||||
}
|
||||
|
||||
/** @hide **/
|
||||
public Runnable setImageURIAsync(@Nullable Uri uri) {
|
||||
if (mResource != 0 || (mUri != uri && (uri == null || mUri == null || !uri.equals(mUri)))) {
|
||||
Drawable d = uri == null ? null : getDrawableFromUri(uri);
|
||||
if (d == null) {
|
||||
// Do not set the URI if the drawable couldn't be loaded.
|
||||
uri = null;
|
||||
}
|
||||
return new ImageDrawableCallback(d, uri, 0);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a drawable as the content of this ImageView.
|
||||
*
|
||||
@@ -490,11 +528,16 @@ public class ImageView extends View {
|
||||
* @param icon an Icon holding the desired image, or {@code null} to clear
|
||||
* the content
|
||||
*/
|
||||
@android.view.RemotableViewMethod
|
||||
@android.view.RemotableViewMethod(asyncImpl="setImageIconAsync")
|
||||
public void setImageIcon(@Nullable Icon icon) {
|
||||
setImageDrawable(icon == null ? null : icon.loadDrawable(mContext));
|
||||
}
|
||||
|
||||
/** @hide **/
|
||||
public Runnable setImageIconAsync(@Nullable Icon icon) {
|
||||
return new ImageDrawableCallback(icon == null ? null : icon.loadDrawable(mContext), null, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies a tint to the image drawable. Does not modify the current tint
|
||||
* mode, which is {@link PorterDuff.Mode#SRC_IN} by default.
|
||||
@@ -786,8 +829,7 @@ public class ImageView extends View {
|
||||
return;
|
||||
}
|
||||
|
||||
final Resources res = getResources();
|
||||
if (res == null) {
|
||||
if (getResources() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -802,37 +844,7 @@ public class ImageView extends View {
|
||||
mUri = null;
|
||||
}
|
||||
} else if (mUri != null) {
|
||||
final String scheme = mUri.getScheme();
|
||||
if (ContentResolver.SCHEME_ANDROID_RESOURCE.equals(scheme)) {
|
||||
try {
|
||||
// Load drawable through Resources, to get the source density information
|
||||
ContentResolver.OpenResourceIdResult r =
|
||||
mContext.getContentResolver().getResourceId(mUri);
|
||||
d = r.r.getDrawable(r.id, mContext.getTheme());
|
||||
} catch (Exception e) {
|
||||
Log.w(LOG_TAG, "Unable to open content: " + mUri, e);
|
||||
}
|
||||
} else if (ContentResolver.SCHEME_CONTENT.equals(scheme)
|
||||
|| ContentResolver.SCHEME_FILE.equals(scheme)) {
|
||||
InputStream stream = null;
|
||||
try {
|
||||
stream = mContext.getContentResolver().openInputStream(mUri);
|
||||
d = Drawable.createFromResourceStream(
|
||||
mUseCorrectStreamDensity ? res : null, null, stream, null);
|
||||
} catch (Exception e) {
|
||||
Log.w(LOG_TAG, "Unable to open content: " + mUri, e);
|
||||
} finally {
|
||||
if (stream != null) {
|
||||
try {
|
||||
stream.close();
|
||||
} catch (IOException e) {
|
||||
Log.w(LOG_TAG, "Unable to close content: " + mUri, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
d = Drawable.createFromPath(mUri.toString());
|
||||
}
|
||||
d = getDrawableFromUri(mUri);
|
||||
|
||||
if (d == null) {
|
||||
Log.w(LOG_TAG, "resolveUri failed on bad bitmap uri: " + mUri);
|
||||
@@ -846,6 +858,41 @@ public class ImageView extends View {
|
||||
updateDrawable(d);
|
||||
}
|
||||
|
||||
private Drawable getDrawableFromUri(Uri uri) {
|
||||
final String scheme = uri.getScheme();
|
||||
if (ContentResolver.SCHEME_ANDROID_RESOURCE.equals(scheme)) {
|
||||
try {
|
||||
// Load drawable through Resources, to get the source density information
|
||||
ContentResolver.OpenResourceIdResult r =
|
||||
mContext.getContentResolver().getResourceId(uri);
|
||||
return r.r.getDrawable(r.id, mContext.getTheme());
|
||||
} catch (Exception e) {
|
||||
Log.w(LOG_TAG, "Unable to open content: " + uri, e);
|
||||
}
|
||||
} else if (ContentResolver.SCHEME_CONTENT.equals(scheme)
|
||||
|| ContentResolver.SCHEME_FILE.equals(scheme)) {
|
||||
InputStream stream = null;
|
||||
try {
|
||||
stream = mContext.getContentResolver().openInputStream(uri);
|
||||
return Drawable.createFromResourceStream(
|
||||
mUseCorrectStreamDensity ? getResources() : null, null, stream, null);
|
||||
} catch (Exception e) {
|
||||
Log.w(LOG_TAG, "Unable to open content: " + uri, e);
|
||||
} finally {
|
||||
if (stream != null) {
|
||||
try {
|
||||
stream.close();
|
||||
} catch (IOException e) {
|
||||
Log.w(LOG_TAG, "Unable to close content: " + uri, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return Drawable.createFromPath(uri.toString());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int[] onCreateDrawableState(int extraSpace) {
|
||||
if (mState == null) {
|
||||
|
||||
@@ -39,8 +39,10 @@ import android.graphics.Rect;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.graphics.drawable.Icon;
|
||||
import android.net.Uri;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.CancellationSignal;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.os.StrictMode;
|
||||
@@ -66,6 +68,7 @@ import java.lang.annotation.Target;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
/**
|
||||
* A class that describes a view hierarchy that can be displayed in
|
||||
@@ -146,6 +149,8 @@ public class RemoteViews implements Parcelable, Filter {
|
||||
private static final Object[] sMethodsLock = new Object[0];
|
||||
private static final ArrayMap<Class<? extends View>, ArrayMap<MutablePair<String, Class<?>>, Method>> sMethods =
|
||||
new ArrayMap<Class<? extends View>, ArrayMap<MutablePair<String, Class<?>>, Method>>();
|
||||
private static final ArrayMap<Method, Method> sAsyncMethods = new ArrayMap<>();
|
||||
|
||||
private static final ThreadLocal<Object[]> sInvokeArgsTls = new ThreadLocal<Object[]>() {
|
||||
@Override
|
||||
protected Object[] initialValue() {
|
||||
@@ -293,9 +298,39 @@ public class RemoteViews implements Parcelable, Filter {
|
||||
return (getActionName() + viewId);
|
||||
}
|
||||
|
||||
/**
|
||||
* This is called on the background thread. It should perform any non-ui computations
|
||||
* and return the final action which will run on the UI thread.
|
||||
* Override this if some of the tasks can be performed async.
|
||||
*/
|
||||
public Action initActionAsync(ViewTree root, ViewGroup rootParent, OnClickHandler handler) {
|
||||
return this;
|
||||
}
|
||||
|
||||
int viewId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Action class used during async inflation of RemoteViews. Subclasses are not parcelable.
|
||||
*/
|
||||
private static abstract class RuntimeAction extends Action {
|
||||
@Override
|
||||
public final String getActionName() {
|
||||
return "RuntimeAction";
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void writeToParcel(Parcel dest, int flags) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
// Constant used during async execution. It is not parcelable.
|
||||
private static final Action ACTION_NOOP = new RuntimeAction() {
|
||||
@Override
|
||||
public void apply(View root, ViewGroup rootParent, OnClickHandler handler) { }
|
||||
};
|
||||
|
||||
/**
|
||||
* Merges the passed RemoteViews actions with this RemoteViews actions according to
|
||||
* action-specific merge rules.
|
||||
@@ -810,6 +845,36 @@ public class RemoteViews implements Parcelable, Filter {
|
||||
return method;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the async implementation of the provided method.
|
||||
*/
|
||||
private Method getAsyncMethod(Method method) {
|
||||
synchronized (sAsyncMethods) {
|
||||
int valueIndex = sAsyncMethods.indexOfKey(method);
|
||||
if (valueIndex >= 0) {
|
||||
return sAsyncMethods.valueAt(valueIndex);
|
||||
}
|
||||
|
||||
RemotableViewMethod annotation = method.getAnnotation(RemotableViewMethod.class);
|
||||
Method asyncMethod = null;
|
||||
if (!annotation.asyncImpl().isEmpty()) {
|
||||
try {
|
||||
asyncMethod = method.getDeclaringClass()
|
||||
.getMethod(annotation.asyncImpl(), method.getParameterTypes());
|
||||
if (!asyncMethod.getReturnType().equals(Runnable.class)) {
|
||||
throw new ActionException("Async implementation for " + method.getName() +
|
||||
" does not return a Runnable");
|
||||
}
|
||||
} catch (NoSuchMethodException ex) {
|
||||
throw new ActionException("Async implementation declared but not defined for " +
|
||||
method.getName());
|
||||
}
|
||||
}
|
||||
sAsyncMethods.put(method, asyncMethod);
|
||||
return asyncMethod;
|
||||
}
|
||||
}
|
||||
|
||||
private static String getParameters(Class<?> paramType) {
|
||||
if (paramType == null) return "()";
|
||||
return "(" + paramType + ")";
|
||||
@@ -1324,6 +1389,37 @@ public class RemoteViews implements Parcelable, Filter {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Action initActionAsync(ViewTree root, ViewGroup rootParent, OnClickHandler handler) {
|
||||
final View view = root.findViewById(viewId);
|
||||
if (view == null) return ACTION_NOOP;
|
||||
|
||||
Class<?> param = getParameterType();
|
||||
if (param == null) {
|
||||
throw new ActionException("bad type: " + this.type);
|
||||
}
|
||||
|
||||
try {
|
||||
Method method = getMethod(view, this.methodName, param);
|
||||
Method asyncMethod = getAsyncMethod(method);
|
||||
|
||||
if (asyncMethod != null) {
|
||||
Runnable endAction = (Runnable) asyncMethod.invoke(view, wrapArg(this.value));
|
||||
if (endAction == null) {
|
||||
return ACTION_NOOP;
|
||||
} else {
|
||||
return new RunnableAction(endAction);
|
||||
}
|
||||
}
|
||||
} catch (ActionException e) {
|
||||
throw e;
|
||||
} catch (Exception ex) {
|
||||
throw new ActionException(ex);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public int mergeBehavior() {
|
||||
// smoothScrollBy is cumulative, everything else overwites.
|
||||
if (methodName.equals("smoothScrollBy")) {
|
||||
@@ -1340,6 +1436,22 @@ public class RemoteViews implements Parcelable, Filter {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This is only used for async execution of actions and it not parcelable.
|
||||
*/
|
||||
private static final class RunnableAction extends RuntimeAction {
|
||||
private final Runnable mRunnable;
|
||||
|
||||
RunnableAction(Runnable r) {
|
||||
mRunnable = r;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void apply(View root, ViewGroup rootParent, OnClickHandler handler) {
|
||||
mRunnable.run();
|
||||
}
|
||||
}
|
||||
|
||||
private void configureRemoteViewsAsChild(RemoteViews rv) {
|
||||
mBitmapCache.assimilate(rv.mBitmapCache);
|
||||
rv.setBitmapCache(mBitmapCache);
|
||||
@@ -1400,6 +1512,43 @@ public class RemoteViews implements Parcelable, Filter {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Action initActionAsync(ViewTree root, ViewGroup rootParent, OnClickHandler handler) {
|
||||
// In the async implementation, update the view tree so that subsequent calls to
|
||||
// findViewById return the currect view.
|
||||
root.createTree();
|
||||
ViewTree target = root.findViewTreeById(viewId);
|
||||
if ((target == null) || !(target.mRoot instanceof ViewGroup)) {
|
||||
return ACTION_NOOP;
|
||||
}
|
||||
if (nestedViews == null) {
|
||||
// Clear all children when nested views omitted
|
||||
target.mChildren = null;
|
||||
return this;
|
||||
} else {
|
||||
// Inflate nested views and perform all the async tasks for the child remoteView.
|
||||
final Context context = root.mRoot.getContext();
|
||||
final AsyncApplyTask task = nestedViews.getAsyncApplyTask(
|
||||
context, (ViewGroup) target.mRoot, null, handler);
|
||||
final ViewTree tree = task.doInBackground();
|
||||
|
||||
// Update the global view tree, so that next call to findViewTreeById
|
||||
// goes through the subtree as well.
|
||||
target.addChild(tree);
|
||||
|
||||
return new RuntimeAction() {
|
||||
|
||||
@Override
|
||||
public void apply(View root, ViewGroup rootParent, OnClickHandler handler) throws ActionException {
|
||||
// This view will exist as we have already made sure
|
||||
final ViewGroup target = (ViewGroup) root.findViewById(viewId);
|
||||
task.onPostExecute(tree);
|
||||
target.addView(task.mResult);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateMemoryUsageEstimate(MemoryUsageCounter counter) {
|
||||
if (nestedViews != null) {
|
||||
@@ -1520,7 +1669,13 @@ public class RemoteViews implements Parcelable, Filter {
|
||||
public void apply(View root, ViewGroup rootParent, OnClickHandler handler) {
|
||||
final TextView target = (TextView) root.findViewById(viewId);
|
||||
if (target == null) return;
|
||||
if (useIcons) {
|
||||
if (drawablesLoaded) {
|
||||
if (isRelative) {
|
||||
target.setCompoundDrawablesRelativeWithIntrinsicBounds(id1, id2, id3, id4);
|
||||
} else {
|
||||
target.setCompoundDrawablesWithIntrinsicBounds(id1, id2, id3, id4);
|
||||
}
|
||||
} else if (useIcons) {
|
||||
final Context ctx = target.getContext();
|
||||
final Drawable id1 = i1 == null ? null : i1.loadDrawable(ctx);
|
||||
final Drawable id2 = i2 == null ? null : i2.loadDrawable(ctx);
|
||||
@@ -1540,6 +1695,33 @@ public class RemoteViews implements Parcelable, Filter {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Action initActionAsync(ViewTree root, ViewGroup rootParent, OnClickHandler handler) {
|
||||
final TextView target = (TextView) root.findViewById(viewId);
|
||||
if (target == null) return ACTION_NOOP;
|
||||
|
||||
TextViewDrawableAction copy = useIcons ?
|
||||
new TextViewDrawableAction(viewId, isRelative, i1, i2, i3, i4) :
|
||||
new TextViewDrawableAction(viewId, isRelative, d1, d2, d3, d4);
|
||||
|
||||
// Load the drawables on the background thread.
|
||||
copy.drawablesLoaded = true;
|
||||
final Context ctx = target.getContext();
|
||||
|
||||
if (useIcons) {
|
||||
copy.id1 = i1 == null ? null : i1.loadDrawable(ctx);
|
||||
copy.id2 = i2 == null ? null : i2.loadDrawable(ctx);
|
||||
copy.id3 = i3 == null ? null : i3.loadDrawable(ctx);
|
||||
copy.id4 = i4 == null ? null : i4.loadDrawable(ctx);
|
||||
} else {
|
||||
copy.id1 = d1 == 0 ? null : ctx.getDrawable(d1);
|
||||
copy.id2 = d2 == 0 ? null : ctx.getDrawable(d2);
|
||||
copy.id3 = d3 == 0 ? null : ctx.getDrawable(d3);
|
||||
copy.id4 = d4 == 0 ? null : ctx.getDrawable(d4);
|
||||
}
|
||||
return copy;
|
||||
}
|
||||
|
||||
public String getActionName() {
|
||||
return "TextViewDrawableAction";
|
||||
}
|
||||
@@ -1549,6 +1731,9 @@ public class RemoteViews implements Parcelable, Filter {
|
||||
int d1, d2, d3, d4;
|
||||
Icon i1, i2, i3, i4;
|
||||
|
||||
boolean drawablesLoaded = false;
|
||||
Drawable id1, id2, id3, id4;
|
||||
|
||||
public final static int TAG = 11;
|
||||
}
|
||||
|
||||
@@ -2852,7 +3037,15 @@ public class RemoteViews implements Parcelable, Filter {
|
||||
public View apply(Context context, ViewGroup parent, OnClickHandler handler) {
|
||||
RemoteViews rvToApply = getRemoteViewsToApply(context);
|
||||
|
||||
View result;
|
||||
View result = inflateView(context, rvToApply, parent);
|
||||
loadTransitionOverride(context, handler);
|
||||
|
||||
rvToApply.performApply(result, parent, handler);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private View inflateView(Context context, RemoteViews rv, ViewGroup parent) {
|
||||
// RemoteViews may be built by an application installed in another
|
||||
// user. So build a context that loads resources from that user but
|
||||
// still returns the current users userId so settings like data / time formats
|
||||
@@ -2880,13 +3073,7 @@ public class RemoteViews implements Parcelable, Filter {
|
||||
// we don't add a filter to the static version returned by getSystemService.
|
||||
inflater = inflater.cloneInContext(inflationContext);
|
||||
inflater.setFilter(this);
|
||||
result = inflater.inflate(rvToApply.getLayoutId(), parent, false);
|
||||
|
||||
loadTransitionOverride(context, handler);
|
||||
|
||||
rvToApply.performApply(result, parent, handler);
|
||||
|
||||
return result;
|
||||
return inflater.inflate(rv.getLayoutId(), parent, false);
|
||||
}
|
||||
|
||||
private static void loadTransitionOverride(Context context,
|
||||
@@ -2907,6 +3094,143 @@ public class RemoteViews implements Parcelable, Filter {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implement this interface to receive a callback when
|
||||
* {@link #applyAsync} or {@link #reapplyAsync} is finished.
|
||||
* @hide
|
||||
*/
|
||||
public interface OnViewAppliedListener {
|
||||
void onViewApplied(View v);
|
||||
|
||||
void onError(Exception e);
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the views asynchronously, moving as much of the task on the background
|
||||
* thread as possible.
|
||||
*
|
||||
* @see {@link #apply(Context, ViewGroup)}
|
||||
* @param context Default context to use
|
||||
* @param parent Parent that the resulting view hierarchy will be attached to. This method
|
||||
* does <strong>not</strong> attach the hierarchy. The caller should do so when appropriate.
|
||||
* @param listener the callback to run when all actions have been applied. May be null.
|
||||
* @param executor The executor to use. If null {@link AsyncTask#THREAD_POOL_EXECUTOR} is used.
|
||||
* @return CancellationSignal
|
||||
* @hide
|
||||
*/
|
||||
public CancellationSignal applyAsync(
|
||||
Context context, ViewGroup parent, Executor executor, OnViewAppliedListener listener) {
|
||||
return applyAsync(context, parent, executor, listener, null);
|
||||
}
|
||||
|
||||
private CancellationSignal startTaskOnExecutor(AsyncApplyTask task, Executor executor) {
|
||||
CancellationSignal cancelSignal = new CancellationSignal();
|
||||
cancelSignal.setOnCancelListener(task);
|
||||
|
||||
task.executeOnExecutor(executor == null ? AsyncTask.THREAD_POOL_EXECUTOR : executor);
|
||||
return cancelSignal;
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
public CancellationSignal applyAsync(Context context, ViewGroup parent,
|
||||
Executor executor, OnViewAppliedListener listener, OnClickHandler handler) {
|
||||
return startTaskOnExecutor(getAsyncApplyTask(context, parent, listener, handler), executor);
|
||||
}
|
||||
|
||||
private AsyncApplyTask getAsyncApplyTask(Context context, ViewGroup parent,
|
||||
OnViewAppliedListener listener, OnClickHandler handler) {
|
||||
return new AsyncApplyTask(getRemoteViewsToApply(context), parent, context, listener,
|
||||
handler, null);
|
||||
}
|
||||
|
||||
private class AsyncApplyTask extends AsyncTask<Void, Void, ViewTree>
|
||||
implements CancellationSignal.OnCancelListener {
|
||||
final RemoteViews mRV;
|
||||
final ViewGroup mParent;
|
||||
final Context mContext;
|
||||
final OnViewAppliedListener mListener;
|
||||
final OnClickHandler mHandler;
|
||||
|
||||
private View mResult;
|
||||
private ViewTree mTree;
|
||||
private Action[] mActions;
|
||||
private Exception mError;
|
||||
|
||||
private AsyncApplyTask(
|
||||
RemoteViews rv, ViewGroup parent, Context context, OnViewAppliedListener listener,
|
||||
OnClickHandler handler, View result) {
|
||||
mRV = rv;
|
||||
mParent = parent;
|
||||
mContext = context;
|
||||
mListener = listener;
|
||||
mHandler = handler;
|
||||
|
||||
mResult = result;
|
||||
loadTransitionOverride(context, handler);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ViewTree doInBackground(Void... params) {
|
||||
try {
|
||||
if (mResult == null) {
|
||||
mResult = inflateView(mContext, mRV, mParent);
|
||||
}
|
||||
|
||||
mTree = new ViewTree(mResult);
|
||||
if (mRV.mActions != null) {
|
||||
int count = mRV.mActions.size();
|
||||
mActions = new Action[count];
|
||||
for (int i = 0; i < count && !isCancelled(); i++) {
|
||||
// TODO: check if isCanclled in nested views.
|
||||
mActions[i] = mRV.mActions.get(i).initActionAsync(mTree, mParent, mHandler);
|
||||
}
|
||||
} else {
|
||||
mActions = null;
|
||||
}
|
||||
return mTree;
|
||||
} catch (Exception e) {
|
||||
mError = e;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(ViewTree viewTree) {
|
||||
if (mError == null) {
|
||||
try {
|
||||
if (mActions != null) {
|
||||
OnClickHandler handler = mHandler == null
|
||||
? DEFAULT_ON_CLICK_HANDLER : mHandler;
|
||||
for (Action a : mActions) {
|
||||
a.apply(viewTree.mRoot, mParent, handler);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
mError = e;
|
||||
}
|
||||
}
|
||||
|
||||
if (mListener != null) {
|
||||
if (mError != null) {
|
||||
mListener.onError(mError);
|
||||
} else {
|
||||
mListener.onViewApplied(viewTree.mRoot);
|
||||
}
|
||||
} else if (mError != null) {
|
||||
if (mError instanceof ActionException) {
|
||||
throw (ActionException) mError;
|
||||
} else {
|
||||
throw new ActionException(mError);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCancel() {
|
||||
cancel(true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies all of the actions to the provided view.
|
||||
*
|
||||
@@ -2936,6 +3260,43 @@ public class RemoteViews implements Parcelable, Filter {
|
||||
rvToApply.performApply(v, (ViewGroup) v.getParent(), handler);
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies all the actions to the provided view, moving as much of the task on the background
|
||||
* thread as possible.
|
||||
*
|
||||
* @see {@link #reapply(Context, View)}
|
||||
* @param context Default context to use
|
||||
* @param v The view to apply the actions to. This should be the result of
|
||||
* the {@link #apply(Context,ViewGroup)} call.
|
||||
* @param listener the callback to run when all actions have been applied. May be null.
|
||||
* @param executor The executor to use. If null {@link AsyncTask#THREAD_POOL_EXECUTOR} is used
|
||||
* @return CancellationSignal
|
||||
* @hide
|
||||
*/
|
||||
public CancellationSignal reapplyAsync(
|
||||
Context context, View v, Executor executor, OnViewAppliedListener listener) {
|
||||
return reapplyAsync(context, v, executor, listener, null);
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
public CancellationSignal reapplyAsync(Context context, View v, Executor executor,
|
||||
OnViewAppliedListener listener, OnClickHandler handler) {
|
||||
RemoteViews rvToApply = getRemoteViewsToApply(context);
|
||||
|
||||
// In the case that a view has this RemoteViews applied in one orientation, is persisted
|
||||
// across orientation change, and has the RemoteViews re-applied in the new orientation,
|
||||
// we throw an exception, since the layouts may be completely unrelated.
|
||||
if (hasLandscapeAndPortraitLayouts()) {
|
||||
if (v.getId() != rvToApply.getLayoutId()) {
|
||||
throw new RuntimeException("Attempting to re-apply RemoteViews to a view that" +
|
||||
" that does not share the same root layout id.");
|
||||
}
|
||||
}
|
||||
|
||||
return startTaskOnExecutor(new AsyncApplyTask(rvToApply, (ViewGroup) v.getParent(),
|
||||
context, listener, handler, v), executor);
|
||||
}
|
||||
|
||||
private void performApply(View v, ViewGroup parent, OnClickHandler handler) {
|
||||
if (mActions != null) {
|
||||
handler = handler == null ? DEFAULT_ON_CLICK_HANDLER : handler;
|
||||
@@ -3058,4 +3419,90 @@ public class RemoteViews implements Parcelable, Filter {
|
||||
return new RemoteViews[size];
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* A representation of the view hierarchy. Only views which have a valid ID are added
|
||||
* and can be searched.
|
||||
*/
|
||||
private static class ViewTree {
|
||||
private final View mRoot;
|
||||
|
||||
private ArrayList<ViewTree> mChildren;
|
||||
|
||||
private ViewTree(View root) {
|
||||
mRoot = root;
|
||||
}
|
||||
|
||||
public void createTree() {
|
||||
if (mChildren != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
mChildren = new ArrayList<>();
|
||||
if (mRoot instanceof ViewGroup && mRoot.isRootNamespace()) {
|
||||
ViewGroup vg = (ViewGroup) mRoot;
|
||||
int count = vg.getChildCount();
|
||||
for (int i = 0; i < count; i++) {
|
||||
addViewChild(vg.getChildAt(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ViewTree findViewTreeById(int id) {
|
||||
if (mRoot.getId() == id) {
|
||||
return this;
|
||||
}
|
||||
if (mChildren == null) {
|
||||
return null;
|
||||
}
|
||||
for (ViewTree tree : mChildren) {
|
||||
ViewTree result = tree.findViewTreeById(id);
|
||||
if (result != null) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public View findViewById(int id) {
|
||||
if (mChildren == null) {
|
||||
return mRoot.findViewById(id);
|
||||
}
|
||||
ViewTree tree = findViewTreeById(id);
|
||||
return tree == null ? null : tree.mRoot;
|
||||
}
|
||||
|
||||
public void addChild(ViewTree child) {
|
||||
if (mChildren == null) {
|
||||
mChildren = new ArrayList<>();
|
||||
}
|
||||
child.createTree();
|
||||
mChildren.add(child);
|
||||
}
|
||||
|
||||
private void addViewChild(View v) {
|
||||
final ViewTree target;
|
||||
|
||||
// If the view has a valid id, i.e., if can be found using findViewById, add it to the
|
||||
// tree, otherwise skip this view and add its children instead.
|
||||
if (v.getId() != 0) {
|
||||
ViewTree tree = new ViewTree(v);
|
||||
mChildren.add(tree);
|
||||
target = tree;
|
||||
} else {
|
||||
target = this;
|
||||
}
|
||||
|
||||
if (v instanceof ViewGroup && v.isRootNamespace()) {
|
||||
if (target.mChildren == null) {
|
||||
target.mChildren = new ArrayList<>();
|
||||
ViewGroup vg = (ViewGroup) v;
|
||||
int count = vg.getChildCount();
|
||||
for (int i = 0; i < count; i++) {
|
||||
target.addViewChild(vg.getChildAt(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user