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

Commit 69b07859 authored by Joe Onorato's avatar Joe Onorato Committed by Yao Chen
Browse files

Allow apps to be slow at loading children and thumbnails by making those...

Allow apps to be slow at loading children and thumbnails by making those functions allowed to be asynchronous.

Change-Id: Ibcaee3f0f8d9ba14f1b002df9c6d4594c6278045
parent 77bfc8f2
Loading
Loading
Loading
Loading
+7 −2
Original line number Diff line number Diff line
@@ -16235,8 +16235,8 @@ package android.media.browse {
    method public void notifyChildrenChanged(android.net.Uri);
    method public android.os.IBinder onBind(android.content.Intent);
    method public abstract android.media.browse.MediaBrowserService.BrowserRoot onGetRoot(java.lang.String, int, android.os.Bundle);
    method public abstract android.graphics.Bitmap onGetThumbnail(android.net.Uri, int, int);
    method public abstract java.util.List<android.media.browse.MediaBrowserItem> onLoadChildren(android.net.Uri);
    method protected abstract void onLoadChildren(android.net.Uri, android.media.browse.MediaBrowserService.Result<java.util.List<android.media.browse.MediaBrowserItem>>);
    method protected abstract void onLoadThumbnail(android.net.Uri, int, int, android.media.browse.MediaBrowserService.Result<android.graphics.Bitmap>);
    method public void setSessionToken(android.media.session.MediaSession.Token);
    field public static final java.lang.String SERVICE_ACTION = "android.media.browse.MediaBrowserService";
  }
@@ -16247,6 +16247,11 @@ package android.media.browse {
    method public android.net.Uri getRootUri();
  }
  public class MediaBrowserService.Result {
    method public void detach();
    method public void sendResult(T);
  }
}
package android.media.effect {
+154 −24
Original line number Diff line number Diff line
@@ -70,6 +70,13 @@ import java.util.Set;
 */
public abstract class MediaBrowserService extends Service {
    private static final String TAG = "MediaBrowserService";
    private static final boolean DBG = false;

    /**
     * The {@link Intent} that must be declared as handled by the service.
     */
    @SdkConstant(SdkConstantType.SERVICE_ACTION)
    public static final String SERVICE_ACTION = "android.media.browse.MediaBrowserService";

    private final ArrayMap<IBinder, ConnectionRecord> mConnections = new ArrayMap();
    private final Handler mHandler = new Handler();
@@ -88,10 +95,65 @@ public abstract class MediaBrowserService extends Service {
    }

    /**
     * The {@link Intent} that must be declared as handled by the service.
     * Completion handler for asynchronous callback methods in {@link MediaBrowserService}.
     * <p>
     * Each of the methods that takes one of these to send the result must call
     * {@link #sendResult} to respond to the caller with the given results.  If those
     * functions return without calling {@link #sendResult}, they must instead call
     * {@link #detach} before returning, and then may call {@link #sendResult} when
     * they are done.  If more than one of those methods is called, an exception will
     * be thrown.
     *
     * @see MediaBrowserService#onLoadChildren
     * @see MediaBrowserService#onLoadThumbnail
     */
    @SdkConstant(SdkConstantType.SERVICE_ACTION)
    public static final String SERVICE_ACTION = "android.media.browse.MediaBrowserService";
    public class Result<T> {
        private Object mDebug;
        private boolean mDetachCalled;
        private boolean mSendResultCalled;

        Result(Object debug) {
            mDebug = debug;
        }

        /**
         * Send the result back to the caller.
         */
        public void sendResult(T result) {
            if (mSendResultCalled) {
                throw new IllegalStateException("sendResult() called twice for: " + mDebug);
            }
            mSendResultCalled = true;
            onResultSent(result);
        }

        /**
         * Detach this message from the current thread and allow the {@link #sendResult}
         * call to happen later.
         */
        public void detach() {
            if (mDetachCalled) {
                throw new IllegalStateException("detach() called when detach() had already"
                        + " been called for: " + mDebug);
            }
            if (mSendResultCalled) {
                throw new IllegalStateException("detach() called when sendResult() had already"
                        + " been called for: " + mDebug);
            }
            mDetachCalled = true;
        }

        boolean isDone() {
            return mDetachCalled || mSendResultCalled;
        }

        /**
         * Called when the result is sent, after assertions about not being called twice
         * have happened.
         */
        void onResultSent(T result) {
        }
    }

    private class ServiceBinder extends IMediaBrowserService.Stub {
        @Override
@@ -206,14 +268,51 @@ public abstract class MediaBrowserService extends Service {
        @Override
        public void loadThumbnail(final Uri uri, final int width, final int height,
                final IMediaBrowserServiceCallbacks callbacks) {
            if (uri == null) {
                throw new IllegalStateException("loadThumbnail sent null list for uri " + uri);
            }
            mHandler.post(new Runnable() {
                @Override
                public void run() {
                    Bitmap bitmap = onGetThumbnail(uri, width, height);
                    // In theory we could return a result to a new connection, but in practice
                    // the other side in MediaBrowser uses a new IMediaBrowserServiceCallbacks
                    // object every time it calls connect(), so as long as it does that we won't
                    // see results sent for the wrong connection.
                    final ConnectionRecord connection = mConnections.get(callbacks.asBinder());
                    if (connection == null) {
                        if (DBG) {
                            Log.d(TAG, "Not loading bitmap for invalid connection. uri=" + uri);
                        }
                        return;
                    }

                    final Result<Bitmap> result = new Result<Bitmap>(uri) {
                        @Override
                        void onResultSent(Bitmap bitmap) {
                            if (mConnections.get(connection.callbacks.asBinder()) != connection) {
                                if (DBG) {
                                    Log.d(TAG, "Not sending onLoadThumbnail result for connection"
                                            + " that has been disconnected. pkg=" + connection.pkg
                                            + " uri=" + uri);
                                }
                                return;
                            }

                            try {
                                callbacks.onLoadThumbnail(uri, width, height, bitmap);
                            } catch (RemoteException e) {
                        Log.e(TAG, "RemoteException in calling onLoadThumbnail", e);
                                // The other side is in the process of crashing.
                                Log.w(TAG, "RemoteException in calling onLoadThumbnail", e);
                            }
                        }
                    };

                    onLoadThumbnail(uri, width, height, result);

                    if (!result.isDone()) {
                        throw new IllegalStateException("onLoadThumbnail must call detach() or"
                                + " sendResult() before returning for package=" + connection.pkg
                                + " uri=" + uri);
                    }
                }
            });
@@ -261,15 +360,28 @@ public abstract class MediaBrowserService extends Service {

    /**
     * Called to get information about the children of a media item.
     * <p>
     * Implementations must call result.{@link Result#sendResult result.sendResult} with the list
     * of children. If loading the children will be an expensive operation that should be performed
     * on another thread, result.{@link Result#detach result.detach} may be called before returning
     * from this function, and then {@link Result#sendResult result.sendResult} called when
     * the loading is complete.
     *
     * @param parentUri The uri of the parent media item whose
     * children are to be queried.
     * @return The list of children, or null if the uri is invalid.
     */
    public abstract @Nullable List<MediaBrowserItem> onLoadChildren(@NonNull Uri parentUri);
    protected abstract void onLoadChildren(@NonNull Uri parentUri,
            @NonNull Result<List<MediaBrowserItem>> result);

    /**
     * Called to get the thumbnail of a particular media item.
     * <p>
     * Implementations must call result.{@link Result#sendResult result.sendResult} with the bitmap.
     * If loading the bitmap will be an expensive operation that should be performed
     * on another thread, result.{@link Result#detach result.detach} may be called before returning
     * from this function, and then {@link Result#sendResult result.sendResult} called when
     * the loading is complete.
     *
     * @param uri The uri of the media item.
     * @param width The requested width of the icon in dp.
@@ -278,7 +390,8 @@ public abstract class MediaBrowserService extends Service {
     * @return The file descriptor of the thumbnail, which may then be loaded
     *          using a bitmap factory, or null if the item does not have a thumbnail.
     */
    public abstract @Nullable Bitmap onGetThumbnail(@NonNull Uri uri, int width, int height);
    protected abstract void onLoadThumbnail(@NonNull Uri uri, int width, int height,
            @NonNull Result<Bitmap> result);

    /**
     * Call to set the media session.
@@ -363,14 +476,22 @@ public abstract class MediaBrowserService extends Service {
     * Call onLoadChildren and then send the results back to the connection.
     * <p>
     * Callers must make sure that this connection is still connected.
     * <p>
     * TODO: Think about caching and combining these calls.
     */
    private void performLoadChildren(Uri uri, ConnectionRecord connection) {
        final List<MediaBrowserItem> list = onLoadChildren(uri);
    private void performLoadChildren(final Uri uri, final ConnectionRecord connection) {
        final Result<List<MediaBrowserItem>> result = new Result<List<MediaBrowserItem>>(uri) {
            @Override
            void onResultSent(List<MediaBrowserItem> list) {
                if (list == null) {
            throw new IllegalStateException("onLoadChildren returned null for uri " + uri);
                    throw new IllegalStateException("onLoadChildren sent null list for uri " + uri);
                }
                if (mConnections.get(connection.callbacks.asBinder()) != connection) {
                    if (DBG) {
                        Log.d(TAG, "Not sending onLoadChildren result for connection that has"
                                + " been disconnected. pkg=" + connection.pkg + " uri=" + uri);
                    }
                    return;
                }

                final ParceledListSlice<MediaBrowserItem> pls = new ParceledListSlice(list);
                try {
                    connection.callbacks.onLoadChildren(uri, pls);
@@ -380,6 +501,15 @@ public abstract class MediaBrowserService extends Service {
                            + " package=" + connection.pkg);
                }
            }
        };

        onLoadChildren(uri, result);

        if (!result.isDone()) {
            throw new IllegalStateException("onLoadChildren must call detach() or sendResult()"
                    + " before returning for package=" + connection.pkg + " uri=" + uri);
        }
    }

    /**
     * Contains information that the browser service needs to send to the client
+20 −14
Original line number Diff line number Diff line
@@ -121,23 +121,29 @@ public class BrowserService extends MediaBrowserService {
    }

    @Override
    protected List<MediaBrowserItem> onLoadChildren(Uri parentUri) {
        final ArrayList<MediaBrowserItem> results = new ArrayList();
    protected void onLoadChildren(final Uri parentUri,
            final Result<List<MediaBrowserItem>> result) {
        new Handler().postDelayed(new Runnable() {
                public void run() {
                    final ArrayList<MediaBrowserItem> list = new ArrayList();

                    for (int i=0; i<10; i++) {
            results.add(new MediaBrowserItem.Builder(
                        list.add(new MediaBrowserItem.Builder(
                                    Uri.withAppendedPath(BASE_URI, Integer.toString(i)),
                                    MediaBrowserItem.FLAG_BROWSABLE, "Title " + i)
                                .setSummary("Summary " + i)
                                .build());
                    }

        return results;
                    result.sendResult(list);
                }
            }, 2000);
        result.detach();
    }

    @Override
    protected Bitmap onGetThumbnail(Uri uri, int width, int height) {
        return null;
    protected void onLoadThumbnail(Uri uri, int width, int height, Result<Bitmap> result) {
        result.sendResult(null);
    }

    /*