Merge "Add thumbnail related api." into lmp-dev

This commit is contained in:
Yao Chen
2014-07-23 00:06:05 +00:00
committed by Android (Google) Code Review
7 changed files with 291 additions and 50 deletions

View File

@@ -16164,10 +16164,11 @@ package android.media.browse {
ctor public MediaBrowser(android.content.Context, android.content.ComponentName, android.media.browse.MediaBrowser.ConnectionCallback, android.os.Bundle);
method public void connect();
method public void disconnect();
method public android.os.Bundle getExtras();
method public android.net.Uri getRoot();
method public android.media.session.MediaSession.Token getSessionToken();
method public boolean isConnected();
method public void loadThumbnail(android.net.Uri, int, int, int, android.media.browse.MediaBrowser.ThumbnailCallback);
method public void loadThumbnail(android.net.Uri, int, int, android.media.browse.MediaBrowser.ThumbnailCallback);
method public void subscribe(android.net.Uri, android.media.browse.MediaBrowser.SubscriptionCallback);
method public void unsubscribe(android.net.Uri);
}
@@ -16195,6 +16196,8 @@ package android.media.browse {
method public int describeContents();
method public android.os.Bundle getExtras();
method public int getFlags();
method public int getIconResId();
method public android.net.Uri getIconUri();
method public java.lang.CharSequence getSummary();
method public java.lang.CharSequence getTitle();
method public android.net.Uri getUri();
@@ -16210,6 +16213,8 @@ package android.media.browse {
ctor public MediaBrowserItem.Builder(android.net.Uri, int, java.lang.CharSequence);
method public android.media.browse.MediaBrowserItem build();
method public android.media.browse.MediaBrowserItem.Builder setExtras(android.os.Bundle);
method public android.media.browse.MediaBrowserItem.Builder setIconResId(int);
method public android.media.browse.MediaBrowserItem.Builder setIconUri(android.net.Uri);
method public android.media.browse.MediaBrowserItem.Builder setSummary(java.lang.CharSequence);
}
@@ -16217,16 +16222,19 @@ package android.media.browse {
ctor public MediaBrowserService();
method public void dump(java.io.FileDescriptor, java.io.PrintWriter, java.lang.String[]);
method public android.media.session.MediaSession.Token getSessionToken();
method public void notifyChange();
method public void notifyChildrenChanged(android.net.Uri);
method public android.os.IBinder onBind(android.content.Intent);
method public abstract android.net.Uri onGetRoot(java.lang.String, int, android.os.Bundle);
method public abstract android.graphics.Bitmap onGetThumbnail(android.net.Uri, int, int, int);
method public abstract java.util.List<android.media.browse.MediaBrowserItem> onLoadChildren(android.net.Uri);
method protected abstract android.media.browse.MediaBrowserService.BrowserRoot onGetRoot(java.lang.String, int, android.os.Bundle);
method protected abstract android.graphics.Bitmap onGetThumbnail(android.net.Uri, int, int);
method protected abstract java.util.List<android.media.browse.MediaBrowserItem> onLoadChildren(android.net.Uri);
method public void setSessionToken(android.media.session.MediaSession.Token);
field public static final java.lang.String SERVICE_ACTION = "android.media.browse.MediaBrowserService";
}
public static class MediaBrowserService.BrowserRoot {
ctor public MediaBrowserService.BrowserRoot(android.net.Uri, android.os.Bundle);
}
}
package android.media.effect {

View File

@@ -2,6 +2,7 @@
package android.media.browse;
import android.content.res.Configuration;
import android.media.browse.IMediaBrowserServiceCallbacks;
import android.net.Uri;
import android.os.Bundle;
@@ -17,4 +18,5 @@ oneway interface IMediaBrowserService {
void addSubscription(in Uri uri, IMediaBrowserServiceCallbacks callbacks);
void removeSubscription(in Uri uri, IMediaBrowserServiceCallbacks callbacks);
void loadThumbnail(in Uri uri, int width, int height, IMediaBrowserServiceCallbacks callbacks);
}

View File

@@ -3,8 +3,10 @@
package android.media.browse;
import android.content.pm.ParceledListSlice;
import android.graphics.Bitmap;
import android.media.session.MediaSession;
import android.net.Uri;
import android.os.Bundle;
/**
* Media API allows clients to browse through hierarchy of a user’s media collection,
@@ -16,9 +18,11 @@ oneway interface IMediaBrowserServiceCallbacks {
* Invoked when the connected has been established.
* @param root The root Uri for browsing.
* @param session The {@link MediaSession.Token media session token} that can be used to control
* the playback of the media app.
* the playback of the media app.
* @param extra Extras returned by the media service.
*/
void onConnect(in Uri root, in MediaSession.Token session);
void onConnect(in Uri root, in MediaSession.Token session, in Bundle extras);
void onConnectFailed();
void onLoadChildren(in Uri uri, in ParceledListSlice list);
void onLoadThumbnail(in Uri uri, int width, int height, in Bitmap bitmap);
}

View File

@@ -17,11 +17,13 @@
package android.media.browse;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.pm.ParceledListSlice;
import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.media.session.MediaSession;
import android.net.Uri;
@@ -36,7 +38,9 @@ import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* Browses media content offered by a link MediaBrowserService.
@@ -61,6 +65,8 @@ public final class MediaBrowser {
private final Handler mHandler = new Handler();
private final ArrayMap<Uri,Subscription> mSubscriptions =
new ArrayMap<Uri, MediaBrowser.Subscription>();
private final ArrayMap<ThumbnailRequest, HashSet<ThumbnailCallback>> mThumbnailCallbacks =
new ArrayMap<ThumbnailRequest, HashSet<ThumbnailCallback>>();
private int mState = CONNECT_STATE_DISCONNECTED;
private MediaServiceConnection mServiceConnection;
@@ -68,6 +74,7 @@ public final class MediaBrowser {
private IMediaBrowserServiceCallbacks mServiceCallbacks;
private Uri mRootUri;
private MediaSession.Token mMediaSessionToken;
private Bundle mExtras;
/**
* Creates a media browser for the specified media browse service.
@@ -162,7 +169,6 @@ public final class MediaBrowser {
/**
* Disconnects from the media browse service.
* @more
* After this, no more callbacks will be received.
*/
public void disconnect() {
@@ -223,7 +229,7 @@ public final class MediaBrowser {
* </p>
*
* @throws IllegalStateException if not connected.
*/
*/
public @NonNull Uri getRoot() {
if (mState != CONNECT_STATE_CONNECTED) {
throw new IllegalStateException("getSessionToken() called while not connected (state="
@@ -232,6 +238,19 @@ public final class MediaBrowser {
return mRootUri;
}
/**
* Gets any extras for the media service.
*
* @throws IllegalStateException if not connected.
*/
public @Nullable Bundle getExtras() {
if (mState != CONNECT_STATE_CONNECTED) {
throw new IllegalStateException("getExtras() called while not connected (state="
+ getStateLabel(mState) + ")");
}
return mExtras;
}
/**
* Gets the media session token associated with the media browser.
* <p>
@@ -332,18 +351,45 @@ public final class MediaBrowser {
/**
* Loads the thumbnail of a media item.
*
* @param uri The uri of the media item.
* @param uri The uri of the thumbnail.
* @param width The preferred width of the icon in dp.
* @param height The preferred width of the icon in dp.
* @param density The preferred density of the icon. Must be one of the android
* density buckets.
* @param callback The callback to receive the thumbnail.
*
* @throws IllegalStateException if not connected. TODO: Is this restriction necessary?
*/
public void loadThumbnail(@NonNull Uri uri, int width, int height, int density,
@NonNull ThumbnailCallback callback) {
throw new RuntimeException("implement me");
public void loadThumbnail(final @NonNull Uri uri, final int width, final int height,
final @NonNull ThumbnailCallback callback) {
if (uri == null) {
throw new IllegalArgumentException("thumbnail uri cannot be null");
}
if (callback == null) {
throw new IllegalArgumentException("thumbnail callback cannot be null");
}
mHandler.post(new Runnable() {
@Override
public void run() {
HashSet<ThumbnailCallback> callbackSet;
ThumbnailRequest request = new ThumbnailRequest(uri, width, height);
callbackSet = mThumbnailCallbacks.get(request);
if (callbackSet != null) {
callbackSet.add(callback);
mThumbnailCallbacks.put(request, callbackSet);
// same request has been sent. we will wait for the callback.
return;
}
callbackSet = new HashSet<ThumbnailCallback>();
callbackSet.add(callback);
mThumbnailCallbacks.put(request, callbackSet);
if (mState == CONNECT_STATE_CONNECTED) {
try {
mServiceBinder.loadThumbnail(uri, width, height, mServiceCallbacks);
} catch (RemoteException e) {
// Process is crashing. We will disconnect, and upon reconnect we will
// automatically reload the thumbnails. So nothing to do here.
Log.d(TAG, "loadThumbnail failed with RemoteException uri=" + uri);
}
}
}
});
}
/**
@@ -365,7 +411,7 @@ public final class MediaBrowser {
}
private final void onServiceConnected(final IMediaBrowserServiceCallbacks callback,
final Uri root, final MediaSession.Token session) {
final Uri root, final MediaSession.Token session, final Bundle extra) {
mHandler.post(new Runnable() {
@Override
public void run() {
@@ -382,6 +428,7 @@ public final class MediaBrowser {
}
mRootUri = root;
mMediaSessionToken = session;
mExtras = extra;
mState = CONNECT_STATE_CONNECTED;
if (DBG) {
@@ -402,6 +449,17 @@ public final class MediaBrowser {
}
}
for (ThumbnailRequest request : mThumbnailCallbacks.keySet()) {
try {
mServiceBinder.loadThumbnail(request.uri, request.width, request.height,
mServiceCallbacks);
} catch (RemoteException e) {
// Process is crashing. We will disconnect, and upon reconnect we will
// automatically reload. So nothing to do here.
Log.d(TAG, "loadThumbnail failed with RemoteException uri=" + request.uri);
}
}
}
});
}
@@ -468,6 +526,31 @@ public final class MediaBrowser {
});
}
private final void onLoadThumbnail(final IMediaBrowserServiceCallbacks callback,
final ThumbnailRequest request, final Bitmap bitmap) {
mHandler.post(new Runnable() {
@Override
public void run() {
// Check that there hasn't been a disconnect or a different
// ServiceConnection.
if (!isCurrent(callback, "onLoadThumbnail")) {
return;
}
Set<ThumbnailCallback> callbackSet = mThumbnailCallbacks.get(request);
if (callbackSet == null) {
Log.d(TAG, "onLoadThumbnail called for request=" + request +
" but the callback is not registered");
return;
}
for (ThumbnailCallback thumbnailCallback : callbackSet) {
thumbnailCallback.onThumbnailLoaded(request.uri, bitmap);
}
mThumbnailCallbacks.remove(request);
}
});
}
/**
* Return true if {@code callback} is the current ServiceCallbacks. Also logs if it's not.
@@ -567,6 +650,51 @@ public final class MediaBrowser {
}
}
private static class ThumbnailRequest {
Uri uri;
int width;
int height;
ThumbnailRequest(@NonNull Uri uri, int width, int height) {
if (uri == null) {
throw new IllegalArgumentException("thumbnail uri cannot be null");
}
this.uri = uri;
this.width = width;
this.height = height;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof ThumbnailRequest)) return false;
ThumbnailRequest that = (ThumbnailRequest) o;
if (height != that.height) return false;
if (width != that.width) return false;
if (!uri.equals(that.uri)) return false;
return true;
}
@Override
public int hashCode() {
int result = uri.hashCode();
result = 31 * result + width;
result = 31 * result + height;
return result;
}
@Override
public String toString() {
return "ThumbnailRequest{" +
"uri=" + uri +
", width=" + width +
", height=" + height +
'}';
}
}
/**
* ServiceConnection to the other app.
*/
@@ -666,10 +794,11 @@ public final class MediaBrowser {
* are the initial data as requested.
*/
@Override
public void onConnect(final Uri root, final MediaSession.Token session) {
public void onConnect(final Uri root, final MediaSession.Token session,
final Bundle extras) {
MediaBrowser mediaBrowser = mMediaBrowser.get();
if (mediaBrowser != null) {
mediaBrowser.onServiceConnected(this, root, session);
mediaBrowser.onServiceConnected(this, root, session, extras);
}
}
@@ -691,6 +820,15 @@ public final class MediaBrowser {
mediaBrowser.onLoadChildren(this, uri, list);
}
}
@Override
public void onLoadThumbnail(final Uri uri, int width, int height, final Bitmap bitmap) {
MediaBrowser mediaBrowser = mMediaBrowser.get();
if (mediaBrowser != null) {
ThumbnailRequest request = new ThumbnailRequest(uri, width, height);
mediaBrowser.onLoadThumbnail(this, request, bitmap);
}
}
}
private static class Subscription {

View File

@@ -32,6 +32,8 @@ import java.lang.annotation.RetentionPolicy;
*/
public final class MediaBrowserItem implements Parcelable {
private final Uri mUri;
private final Uri mIconUri;
private final int mIconResId;
private final int mFlags;
private final CharSequence mTitle;
private final CharSequence mSummary;
@@ -59,8 +61,8 @@ public final class MediaBrowserItem implements Parcelable {
/**
* Initialize a MediaBrowserItem object.
*/
private MediaBrowserItem(@NonNull Uri uri, int flags, @NonNull CharSequence title,
CharSequence summary, Bundle extras) {
private MediaBrowserItem(@NonNull Uri uri, @Nullable Uri iconUri, int iconResId, int flags,
@NonNull CharSequence title, CharSequence summary, Bundle extras) {
if (uri == null) {
throw new IllegalArgumentException("uri can not be null");
}
@@ -68,6 +70,8 @@ public final class MediaBrowserItem implements Parcelable {
throw new IllegalArgumentException("title can not be null");
}
mUri = uri;
mIconUri = iconUri;
mIconResId = iconResId;
mFlags = flags;
mTitle = title;
mSummary = summary;
@@ -79,6 +83,8 @@ public final class MediaBrowserItem implements Parcelable {
*/
private MediaBrowserItem(Parcel in) {
mUri = Uri.CREATOR.createFromParcel(in);
mIconUri = Uri.CREATOR.createFromParcel(in);
mIconResId = in.readInt();
mFlags = in.readInt();
mTitle = in.readCharSequence();
if (in.readInt() != 0) {
@@ -101,6 +107,8 @@ public final class MediaBrowserItem implements Parcelable {
@Override
public void writeToParcel(Parcel out, int flags) {
mUri.writeToParcel(out, flags);
mIconUri.writeToParcel(out, flags);
out.writeInt(mIconResId);
out.writeInt(mFlags);
out.writeCharSequence(mTitle);
if (mSummary != null) {
@@ -137,6 +145,20 @@ public final class MediaBrowserItem implements Parcelable {
return mUri;
}
/**
* Gets the Uri of the icon.
*/
public @Nullable Uri getIconUri() {
return mIconUri;
}
/**
* Gets the resource id of the icon.
*/
public int getIconResId() {
return mIconResId;
}
/**
* Gets the flags of the item.
*/
@@ -195,6 +217,8 @@ public final class MediaBrowserItem implements Parcelable {
private final Uri mUri;
private final int mFlags;
private final CharSequence mTitle;
private Uri mIconUri;
private int mIconResId;
private CharSequence mSummary;
private Bundle mExtras;
@@ -213,6 +237,26 @@ public final class MediaBrowserItem implements Parcelable {
mTitle = title;
}
/**
* Sets the uri of the icon.
* <p>
* If both {@link #setIconUri(Uri)} and {@link #setIconResId(int)} are called,
* the resource id will be used to load the icon.
* </p>
*/
public @NonNull Builder setIconUri(@Nullable Uri iconUri) {
mIconUri = iconUri;
return this;
}
/**
* Sets the resource id of the icon.
*/
public @NonNull Builder setIconResId(int resId) {
mIconResId = resId;
return this;
}
/**
* Sets summary of the item, or null if none.
*/
@@ -234,7 +278,8 @@ public final class MediaBrowserItem implements Parcelable {
* Builds the item.
*/
public @NonNull MediaBrowserItem build() {
return new MediaBrowserItem(mUri, mFlags, mTitle, mSummary, mExtras);
return new MediaBrowserItem(mUri, mIconUri, mIconResId,
mFlags, mTitle, mSummary, mExtras);
}
}
}

View File

@@ -25,6 +25,7 @@ import android.app.Service;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ParceledListSlice;
import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.media.session.MediaSession;
import android.net.Uri;
@@ -43,6 +44,7 @@ import java.lang.annotation.RetentionPolicy;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* Base class for media browse services.
@@ -81,7 +83,7 @@ public abstract class MediaBrowserService extends Service {
String pkg;
Bundle rootHints;
IMediaBrowserServiceCallbacks callbacks;
Uri root;
BrowserRoot root;
HashSet<Uri> subscriptions = new HashSet();
}
@@ -130,7 +132,8 @@ public abstract class MediaBrowserService extends Service {
} else {
try {
mConnections.put(b, connection);
callbacks.onConnect(connection.root, mSession);
callbacks.onConnect(connection.root.getRootUri(),
mSession, connection.root.getExtras());
} catch (RemoteException ex) {
Log.w(TAG, "Calling onConnect() failed. Dropping client. "
+ "pkg=" + pkg);
@@ -199,6 +202,22 @@ public abstract class MediaBrowserService extends Service {
}
});
}
@Override
public void loadThumbnail(final Uri uri, final int width, final int height,
final IMediaBrowserServiceCallbacks callbacks) {
mHandler.post(new Runnable() {
@Override
public void run() {
Bitmap bitmap = onGetThumbnail(uri, width, height);
try {
callbacks.onLoadThumbnail(uri, width, height, bitmap);
} catch (RemoteException e) {
Log.e(TAG, "RemoteException in calling onLoadThumbnail", e);
}
}
});
}
}
@Override
@@ -220,7 +239,7 @@ public abstract class MediaBrowserService extends Service {
}
/**
* Called to get the root uri for browsing by a particular client.
* Called to get the root information for browsing by a particular client.
* <p>
* The implementation should verify that the client package has
* permission to access browse media information before returning
@@ -237,8 +256,8 @@ public abstract class MediaBrowserService extends Service {
* for browsing, or null if none. The contents of this bundle may affect
* the information returned when browsing.
*/
public abstract @Nullable Uri onGetRoot(@NonNull String clientPackageName, int clientUid,
@Nullable Bundle rootHints);
protected abstract @Nullable BrowserRoot onGetRoot(@NonNull String clientPackageName,
int clientUid, @Nullable Bundle rootHints);
/**
* Called to get information about the children of a media item.
@@ -247,7 +266,7 @@ public abstract class MediaBrowserService extends Service {
* 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 @Nullable List<MediaBrowserItem> onLoadChildren(@NonNull Uri parentUri);
/**
* Called to get the thumbnail of a particular media item.
@@ -255,14 +274,11 @@ public abstract class MediaBrowserService extends Service {
* @param uri The uri of the media item.
* @param width The requested width of the icon in dp.
* @param height The requested height of the icon in dp.
* @param density The requested density of the icon. This is the approximate density of the
* screen on which the icon will be displayed. This density will be one of
* the android density buckets.
*
* @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, int density);
protected abstract @Nullable Bitmap onGetThumbnail(@NonNull Uri uri, int width, int height);
/**
* Call to set the media session.
@@ -287,15 +303,6 @@ public abstract class MediaBrowserService extends Service {
return mSession;
}
/**
* Notifies all connected media browsers that the content of
* the browse service has changed in some way.
* This will cause browsers to fetch subscribed content again.
*/
public void notifyChange() {
throw new RuntimeException("implement me");
}
/**
* Notifies all connected media browsers that the children of
* the specified Uri have changed in some way.
@@ -305,7 +312,19 @@ public abstract class MediaBrowserService extends Service {
* children changed.
*/
public void notifyChildrenChanged(@NonNull Uri parentUri) {
throw new RuntimeException("implement me");
if (parentUri == null) {
throw new IllegalArgumentException("parentUri cannot be null in notifyChildrenChanged");
}
for (IBinder binder : mConnections.keySet()) {
ConnectionRecord connection = mConnections.get(binder);
Set<Uri> uris = connection.subscriptions;
for (Uri uri : uris) {
if (uri.equals(parentUri)) {
performLoadChildren(uri, connection);
break;
}
}
}
}
/**
@@ -360,4 +379,25 @@ public abstract class MediaBrowserService extends Service {
+ " package=" + connection.pkg);
}
}
public static class BrowserRoot {
final private Uri mUri;
final private Bundle mExtras;
public BrowserRoot(@NonNull Uri uri, @Nullable Bundle extras) {
if (uri == null) {
throw new IllegalArgumentException("The root uri in BrowserRoot cannot be null. " +
"Use null for BrowserRoot instead.");
}
mUri = uri;
mExtras = extras;
}
Uri getRootUri() {
return mUri;
}
Bundle getExtras() {
return mExtras;
}
}
}

View File

@@ -31,6 +31,7 @@ import android.media.MediaPlayer.OnErrorListener;
import android.media.MediaPlayer.OnPreparedListener;
import android.media.browse.MediaBrowserItem;
import android.media.browse.MediaBrowserService;
import android.media.browse.MediaBrowserService.BrowserRoot;
import android.media.session.MediaSession;
import android.net.Uri;
import android.net.wifi.WifiManager;
@@ -115,24 +116,27 @@ public class BrowserService extends MediaBrowserService {
}
@Override
public Uri onGetRoot(String clientPackageName, int clientUid, Bundle rootHints) {
return BROWSE_URI;
protected BrowserRoot onGetRoot(String clientPackageName, int clientUid, Bundle rootHints) {
return new BrowserRoot(BROWSE_URI, null);
}
@Override
public List<MediaBrowserItem> onLoadChildren(Uri parentUri) {
protected List<MediaBrowserItem> onLoadChildren(Uri parentUri) {
final ArrayList<MediaBrowserItem> results = new ArrayList();
for (int i=0; i<10; i++) {
results.add(new MediaBrowserItem.Builder(Uri.withAppendedPath(BASE_URI, Integer.toString(i)),
MediaBrowserItem.FLAG_BROWSABLE, "Title " + i).setSummary("Summary " + i).build());
results.add(new MediaBrowserItem.Builder(
Uri.withAppendedPath(BASE_URI, Integer.toString(i)),
MediaBrowserItem.FLAG_BROWSABLE, "Title " + i)
.setSummary("Summary " + i)
.build());
}
return results;
}
@Override
public Bitmap onGetThumbnail(Uri uri, int width, int height, int density) {
protected Bitmap onGetThumbnail(Uri uri, int width, int height) {
return null;
}