Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 40dedd5a authored by Sunny Goyal's avatar Sunny Goyal Committed by Android (Google) Code Review
Browse files

Merge "Added support for async inflation of RemoteViews"

parents d7595b31 dd292f4f
Loading
Loading
Loading
Loading
+97 −0
Original line number Diff line number Diff line
@@ -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.
+6 −0
Original line number Diff line number Diff line
@@ -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 "";
}


+83 −36
Original line number Diff line number Diff line
@@ -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,48 +844,53 @@ public class ImageView extends View {
                mUri = null;
            }
        } else if (mUri != null) {
            final String scheme = mUri.getScheme();
            d = getDrawableFromUri(mUri);

            if (d == null) {
                Log.w(LOG_TAG, "resolveUri failed on bad bitmap uri: " + mUri);
                // Don't try again.
                mUri = null;
            }
        } else {
            return;
        }

        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(mUri);
                    d = r.r.getDrawable(r.id, mContext.getTheme());
                        mContext.getContentResolver().getResourceId(uri);
                return r.r.getDrawable(r.id, mContext.getTheme());
            } catch (Exception e) {
                    Log.w(LOG_TAG, "Unable to open content: " + mUri, 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(mUri);
                    d = Drawable.createFromResourceStream(
                            mUseCorrectStreamDensity ? res : null, null, stream, null);
                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: " + mUri, 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: " + mUri, e);
                        Log.w(LOG_TAG, "Unable to close content: " + uri, e);
                    }
                }
            }
        } else {
                d = Drawable.createFromPath(mUri.toString());
            return Drawable.createFromPath(uri.toString());
        }

            if (d == null) {
                Log.w(LOG_TAG, "resolveUri failed on bad bitmap uri: " + mUri);
                // Don't try again.
                mUri = null;
            }
        } else {
            return;
        }

        updateDrawable(d);
        return null;
    }

    @Override
+456 −9

File changed.

Preview size limit exceeded, changes collapsed.