am 334ab599: am 78bb2dad: Merge "Updated media browsing API & tests." into lmp-dev
* commit '334ab59920a85dfc83553fc89f7b90811fb51f4b': Updated media browsing API & tests.
@@ -322,15 +322,17 @@ LOCAL_SRC_FILES += \
|
||||
media/java/android/media/IRemoteVolumeObserver.aidl \
|
||||
media/java/android/media/IRingtonePlayer.aidl \
|
||||
media/java/android/media/IVolumeController.aidl \
|
||||
media/java/android/media/browse/IMediaBrowserService.aidl \
|
||||
media/java/android/media/browse/IMediaBrowserServiceCallbacks.aidl \
|
||||
media/java/android/media/projection/IMediaProjection.aidl \
|
||||
media/java/android/media/projection/IMediaProjectionCallback.aidl \
|
||||
media/java/android/media/projection/IMediaProjectionManager.aidl \
|
||||
media/java/android/media/routing/IMediaRouteService.aidl \
|
||||
media/java/android/media/routing/IMediaRouteClientCallback.aidl \
|
||||
media/java/android/media/routing/IMediaRouter.aidl \
|
||||
media/java/android/media/routing/IMediaRouterDelegate.aidl \
|
||||
media/java/android/media/routing/IMediaRouterRoutingCallback.aidl \
|
||||
media/java/android/media/routing/IMediaRouterStateCallback.aidl \
|
||||
media/java/android/media/projection/IMediaProjection.aidl \
|
||||
media/java/android/media/projection/IMediaProjectionCallback.aidl \
|
||||
media/java/android/media/projection/IMediaProjectionManager.aidl \
|
||||
media/java/android/media/session/IActiveSessionsListener.aidl \
|
||||
media/java/android/media/session/ISessionController.aidl \
|
||||
media/java/android/media/session/ISessionControllerCallback.aidl \
|
||||
|
||||
@@ -16154,6 +16154,77 @@ package android.media.audiofx {
|
||||
|
||||
}
|
||||
|
||||
package android.media.browse {
|
||||
|
||||
public final class MediaBrowser {
|
||||
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.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 subscribe(android.net.Uri, android.media.browse.MediaBrowser.SubscriptionCallback);
|
||||
method public void unsubscribe(android.net.Uri);
|
||||
}
|
||||
|
||||
public static class MediaBrowser.ConnectionCallback {
|
||||
ctor public MediaBrowser.ConnectionCallback();
|
||||
method public void onConnected();
|
||||
method public void onConnectionFailed();
|
||||
method public void onConnectionSuspended();
|
||||
}
|
||||
|
||||
public static abstract class MediaBrowser.SubscriptionCallback {
|
||||
ctor public MediaBrowser.SubscriptionCallback();
|
||||
method public void onChildrenLoaded(android.net.Uri, java.util.List<android.media.browse.MediaBrowserItem>);
|
||||
method public void onError(android.net.Uri);
|
||||
}
|
||||
|
||||
public static abstract class MediaBrowser.ThumbnailCallback {
|
||||
ctor public MediaBrowser.ThumbnailCallback();
|
||||
method public void onError(android.net.Uri);
|
||||
method public void onThumbnailLoaded(android.net.Uri, android.graphics.Bitmap);
|
||||
}
|
||||
|
||||
public final class MediaBrowserItem implements android.os.Parcelable {
|
||||
method public int describeContents();
|
||||
method public android.os.Bundle getExtras();
|
||||
method public int getFlags();
|
||||
method public java.lang.CharSequence getSummary();
|
||||
method public java.lang.CharSequence getTitle();
|
||||
method public android.net.Uri getUri();
|
||||
method public boolean isBrowsable();
|
||||
method public boolean isPlayable();
|
||||
method public void writeToParcel(android.os.Parcel, int);
|
||||
field public static final android.os.Parcelable.Creator CREATOR;
|
||||
field public static final int FLAG_BROWSABLE = 1; // 0x1
|
||||
field public static final int FLAG_PLAYABLE = 2; // 0x2
|
||||
}
|
||||
|
||||
public static final class MediaBrowserItem.Builder {
|
||||
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 setSummary(java.lang.CharSequence);
|
||||
}
|
||||
|
||||
public abstract class MediaBrowserService extends android.app.Service {
|
||||
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 public void setSessionToken(android.media.session.MediaSession.Token);
|
||||
field public static final java.lang.String SERVICE_ACTION = "android.media.browse.MediaBrowserService";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
package android.media.effect {
|
||||
|
||||
public abstract class Effect {
|
||||
|
||||
20
media/java/android/media/browse/IMediaBrowserService.aidl
Normal file
@@ -0,0 +1,20 @@
|
||||
// Copyright 2014 Google Inc. All Rights Reserved.
|
||||
|
||||
package android.media.browse;
|
||||
|
||||
import android.media.browse.IMediaBrowserServiceCallbacks;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
|
||||
/**
|
||||
* Media API allows clients to browse through hierarchy of a user’s media collection,
|
||||
* playback a specific media entry and interact with the now playing queue.
|
||||
* @hide
|
||||
*/
|
||||
oneway interface IMediaBrowserService {
|
||||
void connect(String pkg, in Bundle rootHints, IMediaBrowserServiceCallbacks callbacks);
|
||||
void disconnect(IMediaBrowserServiceCallbacks callbacks);
|
||||
|
||||
void addSubscription(in Uri uri, IMediaBrowserServiceCallbacks callbacks);
|
||||
void removeSubscription(in Uri uri, IMediaBrowserServiceCallbacks callbacks);
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
// Copyright 2014 Google Inc. All Rights Reserved.
|
||||
|
||||
package android.media.browse;
|
||||
|
||||
import android.content.pm.ParceledListSlice;
|
||||
import android.media.session.MediaSession;
|
||||
import android.net.Uri;
|
||||
|
||||
/**
|
||||
* Media API allows clients to browse through hierarchy of a user’s media collection,
|
||||
* playback a specific media entry and interact with the now playing queue.
|
||||
* @hide
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
void onConnect(in Uri root, in MediaSession.Token session);
|
||||
void onConnectFailed();
|
||||
void onLoadChildren(in Uri uri, in ParceledListSlice list);
|
||||
}
|
||||
704
media/java/android/media/browse/MediaBrowser.java
Normal file
@@ -0,0 +1,704 @@
|
||||
/*
|
||||
* Copyright (C) 2014 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.media.browse;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.ServiceConnection;
|
||||
import android.content.pm.ParceledListSlice;
|
||||
import android.graphics.Bitmap;
|
||||
import android.media.session.MediaSession;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
import android.os.RemoteException;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.Log;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Browses media content offered by a link MediaBrowserService.
|
||||
* <p>
|
||||
* This object is not thread-safe. All calls should happen on the thread on which the browser
|
||||
* was constructed.
|
||||
* </p>
|
||||
*/
|
||||
public final class MediaBrowser {
|
||||
private static final String TAG = "MediaBrowser";
|
||||
private static final boolean DBG = false;
|
||||
|
||||
private static final int CONNECT_STATE_DISCONNECTED = 0;
|
||||
private static final int CONNECT_STATE_CONNECTING = 1;
|
||||
private static final int CONNECT_STATE_CONNECTED = 2;
|
||||
private static final int CONNECT_STATE_SUSPENDED = 3;
|
||||
|
||||
private final Context mContext;
|
||||
private final ComponentName mServiceComponent;
|
||||
private final ConnectionCallback mCallback;
|
||||
private final Bundle mRootHints;
|
||||
private final Handler mHandler = new Handler();
|
||||
private final ArrayMap<Uri,Subscription> mSubscriptions =
|
||||
new ArrayMap<Uri, MediaBrowser.Subscription>();
|
||||
|
||||
private int mState = CONNECT_STATE_DISCONNECTED;
|
||||
private MediaServiceConnection mServiceConnection;
|
||||
private IMediaBrowserService mServiceBinder;
|
||||
private IMediaBrowserServiceCallbacks mServiceCallbacks;
|
||||
private Uri mRootUri;
|
||||
private MediaSession.Token mMediaSessionToken;
|
||||
|
||||
/**
|
||||
* Creates a media browser for the specified media browse service.
|
||||
*
|
||||
* @param context The context.
|
||||
* @param serviceComponent The component name of the media browse service.
|
||||
* @param callback The connection callback.
|
||||
* @param rootHints An optional bundle of service-specific arguments to send
|
||||
* to the media browse service when connecting and retrieving the root uri
|
||||
* for browsing, or null if none. The contents of this bundle may affect
|
||||
* the information returned when browsing.
|
||||
*/
|
||||
public MediaBrowser(Context context, ComponentName serviceComponent,
|
||||
ConnectionCallback callback, Bundle rootHints) {
|
||||
if (context == null) {
|
||||
throw new IllegalArgumentException("context must not be null");
|
||||
}
|
||||
if (serviceComponent == null) {
|
||||
throw new IllegalArgumentException("service component must not be null");
|
||||
}
|
||||
if (callback == null) {
|
||||
throw new IllegalArgumentException("connection callback must not be null");
|
||||
}
|
||||
mContext = context;
|
||||
mServiceComponent = serviceComponent;
|
||||
mCallback = callback;
|
||||
mRootHints = rootHints;
|
||||
}
|
||||
|
||||
/**
|
||||
* Connects to the media browse service.
|
||||
* <p>
|
||||
* The connection callback specified in the constructor will be invoked
|
||||
* when the connection completes or fails.
|
||||
* </p>
|
||||
*/
|
||||
public void connect() {
|
||||
if (mState != CONNECT_STATE_DISCONNECTED) {
|
||||
throw new IllegalStateException("connect() called while not disconnected (state="
|
||||
+ getStateLabel(mState) + ")");
|
||||
}
|
||||
// TODO: remove this extra check.
|
||||
if (DBG) {
|
||||
if (mServiceConnection != null) {
|
||||
throw new RuntimeException("mServiceConnection should be null. Instead it is "
|
||||
+ mServiceConnection);
|
||||
}
|
||||
}
|
||||
if (mServiceBinder != null) {
|
||||
throw new RuntimeException("mServiceBinder should be null. Instead it is "
|
||||
+ mServiceBinder);
|
||||
}
|
||||
if (mServiceCallbacks != null) {
|
||||
throw new RuntimeException("mServiceCallbacks should be null. Instead it is "
|
||||
+ mServiceCallbacks);
|
||||
}
|
||||
|
||||
mState = CONNECT_STATE_CONNECTING;
|
||||
|
||||
final Intent intent = new Intent(MediaBrowserService.SERVICE_ACTION);
|
||||
intent.setComponent(mServiceComponent);
|
||||
|
||||
final ServiceConnection thisConnection = mServiceConnection = new MediaServiceConnection();
|
||||
|
||||
try {
|
||||
mContext.bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);
|
||||
} catch (Exception ex) {
|
||||
Log.e(TAG, "Failed binding to service " + mServiceComponent);
|
||||
|
||||
// Tell them that it didn't work. We are already on the main thread,
|
||||
// but we don't want to do callbacks inside of connect(). So post it,
|
||||
// and then check that we are on the same ServiceConnection. We know
|
||||
// we won't also get an onServiceConnected or onServiceDisconnected,
|
||||
// so we won't be doing double callbacks.
|
||||
mHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
// Ensure that nobody else came in or tried to connect again.
|
||||
if (thisConnection == mServiceConnection) {
|
||||
forceCloseConnection();
|
||||
mCallback.onConnectionFailed();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (DBG) {
|
||||
Log.d(TAG, "connect...");
|
||||
dump();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnects from the media browse service.
|
||||
* @more
|
||||
* After this, no more callbacks will be received.
|
||||
*/
|
||||
public void disconnect() {
|
||||
// It's ok to call this any state, because allowing this lets apps not have
|
||||
// to check isConnected() unnecessarily. They won't appreciate the extra
|
||||
// assertions for this. We do everything we can here to go back to a sane state.
|
||||
if (mServiceCallbacks != null) {
|
||||
try {
|
||||
mServiceBinder.disconnect(mServiceCallbacks);
|
||||
} catch (RemoteException ex) {
|
||||
// We are disconnecting anyway. Log, just for posterity but it's not
|
||||
// a big problem.
|
||||
Log.w(TAG, "RemoteException during connect for " + mServiceComponent);
|
||||
}
|
||||
}
|
||||
forceCloseConnection();
|
||||
|
||||
if (DBG) {
|
||||
Log.d(TAG, "disconnect...");
|
||||
dump();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Null out the variables and unbind from the service. This doesn't include
|
||||
* calling disconnect on the service, because we only try to do that in the
|
||||
* clean shutdown cases.
|
||||
* <p>
|
||||
* Everywhere that calls this EXCEPT for disconnect() should follow it with
|
||||
* a call to mCallback.onConnectionFailed(). Disconnect doesn't do that callback
|
||||
* for a clean shutdown, but everywhere else is a dirty shutdown and should
|
||||
* notify the app.
|
||||
*/
|
||||
private void forceCloseConnection() {
|
||||
if (mServiceConnection != null) {
|
||||
mContext.unbindService(mServiceConnection);
|
||||
}
|
||||
mState = CONNECT_STATE_DISCONNECTED;
|
||||
mServiceConnection = null;
|
||||
mServiceBinder = null;
|
||||
mServiceCallbacks = null;
|
||||
mRootUri = null;
|
||||
mMediaSessionToken = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the browser is connected to the service.
|
||||
*/
|
||||
public boolean isConnected() {
|
||||
return mState == CONNECT_STATE_CONNECTED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the root Uri.
|
||||
* <p>
|
||||
* Note that the root uri may become invalid or change when when the
|
||||
* browser is disconnected.
|
||||
* </p>
|
||||
*
|
||||
* @throws IllegalStateException if not connected.
|
||||
*/
|
||||
public @NonNull Uri getRoot() {
|
||||
if (mState != CONNECT_STATE_CONNECTED) {
|
||||
throw new IllegalStateException("getSessionToken() called while not connected (state="
|
||||
+ getStateLabel(mState) + ")");
|
||||
}
|
||||
return mRootUri;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the media session token associated with the media browser.
|
||||
* <p>
|
||||
* Note that the session token may become invalid or change when when the
|
||||
* browser is disconnected.
|
||||
* </p>
|
||||
*
|
||||
* @return The session token for the browser, never null.
|
||||
*
|
||||
* @throws IllegalStateException if not connected.
|
||||
*/
|
||||
public @NonNull MediaSession.Token getSessionToken() {
|
||||
if (mState != CONNECT_STATE_CONNECTED) {
|
||||
throw new IllegalStateException("getSessionToken() called while not connected (state="
|
||||
+ mState + ")");
|
||||
}
|
||||
return mMediaSessionToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* Queries for information about the media items that are contained within
|
||||
* the specified Uri and subscribes to receive updates when they change.
|
||||
* <p>
|
||||
* The list of subscriptions is maintained even when not connected and is
|
||||
* restored after reconnection. It is ok to subscribe while not connected
|
||||
* but the results will not be returned until the connection completes.
|
||||
* </p><p>
|
||||
* If the uri is already subscribed with a different callback then the new
|
||||
* callback will replace the previous one.
|
||||
* </p>
|
||||
*
|
||||
* @param parentUri The uri of the parent media item whose list of children
|
||||
* will be subscribed.
|
||||
* @param callback The callback to receive the list of children.
|
||||
*/
|
||||
public void subscribe(@NonNull Uri parentUri, @NonNull SubscriptionCallback callback) {
|
||||
// Check arguments.
|
||||
if (parentUri == null) {
|
||||
throw new IllegalArgumentException("parentUri is null");
|
||||
}
|
||||
if (callback == null) {
|
||||
throw new IllegalArgumentException("callback is null");
|
||||
}
|
||||
|
||||
// Update or create the subscription.
|
||||
Subscription sub = mSubscriptions.get(parentUri);
|
||||
boolean newSubscription = sub == null;
|
||||
if (newSubscription) {
|
||||
sub = new Subscription(parentUri);
|
||||
mSubscriptions.put(parentUri, sub);
|
||||
}
|
||||
sub.callback = callback;
|
||||
|
||||
// If we are connected, tell the service that we are watching. If we aren't
|
||||
// connected, the service will be told when we connect.
|
||||
if (mState == CONNECT_STATE_CONNECTED && newSubscription) {
|
||||
try {
|
||||
mServiceBinder.addSubscription(parentUri, mServiceCallbacks);
|
||||
} catch (RemoteException ex) {
|
||||
// Process is crashing. We will disconnect, and upon reconnect we will
|
||||
// automatically reregister. So nothing to do here.
|
||||
Log.d(TAG, "addSubscription failed with RemoteException parentUri=" + parentUri);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unsubscribes for changes to the children of the specified Uri.
|
||||
* <p>
|
||||
* The query callback will no longer be invoked for results associated with
|
||||
* this Uri once this method returns.
|
||||
* </p>
|
||||
*
|
||||
* @param parentUri The uri of the parent media item whose list of children
|
||||
* will be unsubscribed.
|
||||
*/
|
||||
public void unsubscribe(@NonNull Uri parentUri) {
|
||||
// Check arguments.
|
||||
if (parentUri == null) {
|
||||
throw new IllegalArgumentException("parentUri is null");
|
||||
}
|
||||
|
||||
// Remove from our list.
|
||||
final Subscription sub = mSubscriptions.remove(parentUri);
|
||||
|
||||
// Tell the service if necessary.
|
||||
if (mState == CONNECT_STATE_CONNECTED && sub != null) {
|
||||
try {
|
||||
mServiceBinder.removeSubscription(parentUri, mServiceCallbacks);
|
||||
} catch (RemoteException ex) {
|
||||
// Process is crashing. We will disconnect, and upon reconnect we will
|
||||
// automatically reregister. So nothing to do here.
|
||||
Log.d(TAG, "removeSubscription failed with RemoteException parentUri=" + parentUri);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the thumbnail of a media item.
|
||||
*
|
||||
* @param uri The uri of the media item.
|
||||
* @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");
|
||||
}
|
||||
|
||||
/**
|
||||
* For debugging.
|
||||
*/
|
||||
private static String getStateLabel(int state) {
|
||||
switch (state) {
|
||||
case CONNECT_STATE_DISCONNECTED:
|
||||
return "CONNECT_STATE_DISCONNECTED";
|
||||
case CONNECT_STATE_CONNECTING:
|
||||
return "CONNECT_STATE_CONNECTING";
|
||||
case CONNECT_STATE_CONNECTED:
|
||||
return "CONNECT_STATE_CONNECTED";
|
||||
case CONNECT_STATE_SUSPENDED:
|
||||
return "CONNECT_STATE_SUSPENDED";
|
||||
default:
|
||||
return "UNKNOWN/" + state;
|
||||
}
|
||||
}
|
||||
|
||||
private final void onServiceConnected(final IMediaBrowserServiceCallbacks callback,
|
||||
final Uri root, final MediaSession.Token session) {
|
||||
mHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
// Check to make sure there hasn't been a disconnect or a different
|
||||
// ServiceConnection.
|
||||
if (!isCurrent(callback, "onConnect")) {
|
||||
return;
|
||||
}
|
||||
// Don't allow them to call us twice.
|
||||
if (mState != CONNECT_STATE_CONNECTING) {
|
||||
Log.w(TAG, "onConnect from service while mState="
|
||||
+ getStateLabel(mState) + "... ignoring");
|
||||
return;
|
||||
}
|
||||
mRootUri = root;
|
||||
mMediaSessionToken = session;
|
||||
mState = CONNECT_STATE_CONNECTED;
|
||||
|
||||
if (DBG) {
|
||||
Log.d(TAG, "ServiceCallbacks.onConnect...");
|
||||
dump();
|
||||
}
|
||||
mCallback.onConnected();
|
||||
|
||||
// we may receive some subscriptions before we are connected, so re-subscribe
|
||||
// everything now
|
||||
for (Uri uri : mSubscriptions.keySet()) {
|
||||
try {
|
||||
mServiceBinder.addSubscription(uri, mServiceCallbacks);
|
||||
} catch (RemoteException ex) {
|
||||
// Process is crashing. We will disconnect, and upon reconnect we will
|
||||
// automatically reregister. So nothing to do here.
|
||||
Log.d(TAG, "addSubscription failed with RemoteException parentUri=" + uri);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private final void onConnectionFailed(final IMediaBrowserServiceCallbacks callback) {
|
||||
mHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Log.e(TAG, "onConnectFailed for " + mServiceComponent);
|
||||
|
||||
// Check to make sure there hasn't been a disconnect or a different
|
||||
// ServiceConnection.
|
||||
if (!isCurrent(callback, "onConnectFailed")) {
|
||||
return;
|
||||
}
|
||||
// Don't allow them to call us twice.
|
||||
if (mState != CONNECT_STATE_CONNECTING) {
|
||||
Log.w(TAG, "onConnect from service while mState="
|
||||
+ getStateLabel(mState) + "... ignoring");
|
||||
return;
|
||||
}
|
||||
|
||||
// Clean up
|
||||
forceCloseConnection();
|
||||
|
||||
// Tell the app.
|
||||
mCallback.onConnectionFailed();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private final void onLoadChildren(final IMediaBrowserServiceCallbacks callback, final Uri uri,
|
||||
final ParceledListSlice list) {
|
||||
mHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
// Check that there hasn't been a disconnect or a different
|
||||
// ServiceConnection.
|
||||
if (!isCurrent(callback, "onLoadChildren")) {
|
||||
return;
|
||||
}
|
||||
|
||||
List<MediaBrowserItem> data = list.getList();
|
||||
if (DBG) {
|
||||
Log.d(TAG, "onLoadChildren for " + mServiceComponent + " uri=" + uri);
|
||||
}
|
||||
if (data == null) {
|
||||
data = Collections.emptyList();
|
||||
}
|
||||
|
||||
// Check that the subscription is still subscribed.
|
||||
final Subscription subscription = mSubscriptions.get(uri);
|
||||
if (subscription == null) {
|
||||
if (DBG) {
|
||||
Log.d(TAG, "onLoadChildren for uri that isn't subscribed uri="
|
||||
+ uri);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Tell the app.
|
||||
subscription.callback.onChildrenLoaded(uri, data);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return true if {@code callback} is the current ServiceCallbacks. Also logs if it's not.
|
||||
*/
|
||||
private boolean isCurrent(IMediaBrowserServiceCallbacks callback, String funcName) {
|
||||
if (mServiceCallbacks != callback) {
|
||||
if (mState != CONNECT_STATE_DISCONNECTED) {
|
||||
Log.i(TAG, funcName + " for " + mServiceComponent + " with mServiceConnection="
|
||||
+ mServiceCallbacks + " this=" + this);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private ServiceCallbacks getNewServiceCallbacks() {
|
||||
return new ServiceCallbacks(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Log internal state.
|
||||
* @hide
|
||||
*/
|
||||
void dump() {
|
||||
Log.d(TAG, "MediaBrowser...");
|
||||
Log.d(TAG, " mServiceComponent=" + mServiceComponent);
|
||||
Log.d(TAG, " mCallback=" + mCallback);
|
||||
Log.d(TAG, " mRootHints=" + mRootHints);
|
||||
Log.d(TAG, " mState=" + getStateLabel(mState));
|
||||
Log.d(TAG, " mServiceConnection=" + mServiceConnection);
|
||||
Log.d(TAG, " mServiceBinder=" + mServiceBinder);
|
||||
Log.d(TAG, " mServiceCallbacks=" + mServiceCallbacks);
|
||||
Log.d(TAG, " mRootUri=" + mRootUri);
|
||||
Log.d(TAG, " mMediaSessionToken=" + mMediaSessionToken);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Callbacks for connection related events.
|
||||
*/
|
||||
public static class ConnectionCallback {
|
||||
/**
|
||||
* Invoked after {@link MediaBrowser#connect()} when the request has successfully completed.
|
||||
*/
|
||||
public void onConnected() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked when the client is disconnected from the media browser.
|
||||
*/
|
||||
public void onConnectionSuspended() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked when the connection to the media browser failed.
|
||||
*/
|
||||
public void onConnectionFailed() {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Callbacks for subscription related events.
|
||||
*/
|
||||
public static abstract class SubscriptionCallback {
|
||||
/**
|
||||
* Called when the list of children is loaded or updated.
|
||||
*/
|
||||
public void onChildrenLoaded(@NonNull Uri parentUri,
|
||||
@NonNull List<MediaBrowserItem> children) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the Uri doesn't exist or other errors in subscribing.
|
||||
* <p>
|
||||
* If this is called, the subscription remains until {@link MediaBrowser#unsubscribe}
|
||||
* called, because some errors may heal themselves.
|
||||
* </p>
|
||||
*/
|
||||
public void onError(@NonNull Uri uri) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Callbacks for thumbnail loading.
|
||||
*/
|
||||
public static abstract class ThumbnailCallback {
|
||||
/**
|
||||
* Called when the thumbnail is loaded.
|
||||
*/
|
||||
public void onThumbnailLoaded(@NonNull Uri uri, @NonNull Bitmap bitmap) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the Uri doesn’t exist or the bitmap cannot be loaded.
|
||||
*/
|
||||
public void onError(@NonNull Uri uri) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ServiceConnection to the other app.
|
||||
*/
|
||||
private class MediaServiceConnection implements ServiceConnection {
|
||||
@Override
|
||||
public void onServiceConnected(ComponentName name, IBinder binder) {
|
||||
if (DBG) {
|
||||
Log.d(TAG, "MediaServiceConnection.onServiceConnected name=" + name
|
||||
+ " binder=" + binder);
|
||||
dump();
|
||||
}
|
||||
|
||||
// Make sure we are still the current connection, and that they haven't called
|
||||
// disconnect().
|
||||
if (!isCurrent("onServiceConnected")) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Save their binder
|
||||
mServiceBinder = IMediaBrowserService.Stub.asInterface(binder);
|
||||
|
||||
// We make a new mServiceCallbacks each time we connect so that we can drop
|
||||
// responses from previous connections.
|
||||
mServiceCallbacks = getNewServiceCallbacks();
|
||||
|
||||
// Call connect, which is async. When we get a response from that we will
|
||||
// say that we're connected.
|
||||
try {
|
||||
if (DBG) {
|
||||
Log.d(TAG, "ServiceCallbacks.onConnect...");
|
||||
dump();
|
||||
}
|
||||
mServiceBinder.connect(mContext.getPackageName(), mRootHints, mServiceCallbacks);
|
||||
} catch (RemoteException ex) {
|
||||
// Connect failed, which isn't good. But the auto-reconnect on the service
|
||||
// will take over and we will come back. We will also get the
|
||||
// onServiceDisconnected, which has all the cleanup code. So let that do it.
|
||||
Log.w(TAG, "RemoteException during connect for " + mServiceComponent);
|
||||
if (DBG) {
|
||||
Log.d(TAG, "ServiceCallbacks.onConnect...");
|
||||
dump();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServiceDisconnected(ComponentName name) {
|
||||
if (DBG) {
|
||||
Log.d(TAG, "MediaServiceConnection.onServiceDisconnected name=" + name
|
||||
+ " this=" + this + " mServiceConnection=" + mServiceConnection);
|
||||
dump();
|
||||
}
|
||||
|
||||
// Make sure we are still the current connection, and that they haven't called
|
||||
// disconnect().
|
||||
if (!isCurrent("onServiceDisconnected")) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Clear out what we set in onServiceConnected
|
||||
mServiceBinder = null;
|
||||
mServiceCallbacks = null;
|
||||
|
||||
// And tell the app that it's suspended.
|
||||
mState = CONNECT_STATE_SUSPENDED;
|
||||
mCallback.onConnectionSuspended();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this is the current ServiceConnection. Also logs if it's not.
|
||||
*/
|
||||
private boolean isCurrent(String funcName) {
|
||||
if (mServiceConnection != this) {
|
||||
if (mState != CONNECT_STATE_DISCONNECTED) {
|
||||
// Check mState, because otherwise this log is noisy.
|
||||
Log.i(TAG, funcName + " for " + mServiceComponent + " with mServiceConnection="
|
||||
+ mServiceConnection + " this=" + this);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Callbacks from the service.
|
||||
*/
|
||||
private static class ServiceCallbacks extends IMediaBrowserServiceCallbacks.Stub {
|
||||
private WeakReference<MediaBrowser> mMediaBrowser;
|
||||
|
||||
public ServiceCallbacks(MediaBrowser mediaBrowser) {
|
||||
mMediaBrowser = new WeakReference<MediaBrowser>(mediaBrowser);
|
||||
}
|
||||
|
||||
/**
|
||||
* The other side has acknowledged our connection. The parameters to this function
|
||||
* are the initial data as requested.
|
||||
*/
|
||||
@Override
|
||||
public void onConnect(final Uri root, final MediaSession.Token session) {
|
||||
MediaBrowser mediaBrowser = mMediaBrowser.get();
|
||||
if (mediaBrowser != null) {
|
||||
mediaBrowser.onServiceConnected(this, root, session);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The other side does not like us. Tell the app via onConnectionFailed.
|
||||
*/
|
||||
@Override
|
||||
public void onConnectFailed() {
|
||||
MediaBrowser mediaBrowser = mMediaBrowser.get();
|
||||
if (mediaBrowser != null) {
|
||||
mediaBrowser.onConnectionFailed(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadChildren(final Uri uri, final ParceledListSlice list) {
|
||||
MediaBrowser mediaBrowser = mMediaBrowser.get();
|
||||
if (mediaBrowser != null) {
|
||||
mediaBrowser.onLoadChildren(this, uri, list);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class Subscription {
|
||||
final Uri uri;
|
||||
SubscriptionCallback callback;
|
||||
|
||||
Subscription(Uri u) {
|
||||
this.uri = u;
|
||||
}
|
||||
}
|
||||
}
|
||||
241
media/java/android/media/browse/MediaBrowserItem.java
Normal file
@@ -0,0 +1,241 @@
|
||||
/*
|
||||
* Copyright (C) 2014 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.media.browse;
|
||||
|
||||
import android.annotation.IntDef;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.net.Uri;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
/**
|
||||
* Describes a media item in the list of items offered by a {@link MediaBrowserService}.
|
||||
*/
|
||||
public final class MediaBrowserItem implements Parcelable {
|
||||
private final Uri mUri;
|
||||
private final int mFlags;
|
||||
private final CharSequence mTitle;
|
||||
private final CharSequence mSummary;
|
||||
private final Bundle mExtras;
|
||||
|
||||
/** @hide */
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@IntDef(flag=true, value = { FLAG_BROWSABLE, FLAG_PLAYABLE })
|
||||
public @interface Flags { }
|
||||
|
||||
/**
|
||||
* Flag: Indicates that the item has children of its own.
|
||||
*/
|
||||
public static final int FLAG_BROWSABLE = 1 << 0;
|
||||
|
||||
/**
|
||||
* Flag: Indicates that the item is playable.
|
||||
* <p>
|
||||
* The Uri of this item may be passed to link android.media.session.MediaController#play(Uri)
|
||||
* to start playing it.
|
||||
* </p>
|
||||
*/
|
||||
public static final int FLAG_PLAYABLE = 1 << 1;
|
||||
|
||||
/**
|
||||
* Initialize a MediaBrowserItem object.
|
||||
*/
|
||||
private MediaBrowserItem(@NonNull Uri uri, int flags, @NonNull CharSequence title,
|
||||
CharSequence summary, Bundle extras) {
|
||||
if (uri == null) {
|
||||
throw new IllegalArgumentException("uri can not be null");
|
||||
}
|
||||
if (title == null) {
|
||||
throw new IllegalArgumentException("title can not be null");
|
||||
}
|
||||
mUri = uri;
|
||||
mFlags = flags;
|
||||
mTitle = title;
|
||||
mSummary = summary;
|
||||
mExtras = extras;
|
||||
}
|
||||
|
||||
/**
|
||||
* Private constructor.
|
||||
*/
|
||||
private MediaBrowserItem(Parcel in) {
|
||||
mUri = Uri.CREATOR.createFromParcel(in);
|
||||
mFlags = in.readInt();
|
||||
mTitle = in.readCharSequence();
|
||||
if (in.readInt() != 0) {
|
||||
mSummary = in.readCharSequence();
|
||||
} else {
|
||||
mSummary = null;
|
||||
}
|
||||
if (in.readInt() != 0) {
|
||||
mExtras = Bundle.CREATOR.createFromParcel(in);
|
||||
} else {
|
||||
mExtras = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel out, int flags) {
|
||||
mUri.writeToParcel(out, flags);
|
||||
out.writeInt(mFlags);
|
||||
out.writeCharSequence(mTitle);
|
||||
if (mSummary != null) {
|
||||
out.writeInt(1);
|
||||
out.writeCharSequence(mSummary);
|
||||
} else {
|
||||
out.writeInt(0);
|
||||
}
|
||||
if (mExtras != null) {
|
||||
out.writeInt(1);
|
||||
mExtras.writeToParcel(out, flags);
|
||||
} else {
|
||||
out.writeInt(0);
|
||||
}
|
||||
}
|
||||
|
||||
public static final Parcelable.Creator<MediaBrowserItem> CREATOR =
|
||||
new Parcelable.Creator<MediaBrowserItem>() {
|
||||
@Override
|
||||
public MediaBrowserItem createFromParcel(Parcel in) {
|
||||
return new MediaBrowserItem(in);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MediaBrowserItem[] newArray(int size) {
|
||||
return new MediaBrowserItem[size];
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the Uri of the item.
|
||||
*/
|
||||
public @NonNull Uri getUri() {
|
||||
return mUri;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the flags of the item.
|
||||
*/
|
||||
public @Flags int getFlags() {
|
||||
return mFlags;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this item is browsable.
|
||||
* @see #FLAG_BROWSABLE
|
||||
*/
|
||||
public boolean isBrowsable() {
|
||||
return (mFlags & FLAG_BROWSABLE) != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this item is playable.
|
||||
* @see #FLAG_PLAYABLE
|
||||
*/
|
||||
public boolean isPlayable() {
|
||||
return (mFlags & FLAG_PLAYABLE) != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the title of the item.
|
||||
* @more
|
||||
* The title will be shown as the first line of text when
|
||||
* describing each item to the user.
|
||||
*/
|
||||
public @NonNull CharSequence getTitle() {
|
||||
return mTitle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets summary of the item, or null if none.
|
||||
* @more
|
||||
* The summary will be shown as the second line of text when
|
||||
* describing each item to the user.
|
||||
*/
|
||||
public @Nullable CharSequence getSummary() {
|
||||
return mSummary;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets additional service-specified extras about the
|
||||
* item or its content, or null if none.
|
||||
*/
|
||||
public @Nullable Bundle getExtras() {
|
||||
return mExtras;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builder for {@link MediaBrowserItem} objects.
|
||||
*/
|
||||
public static final class Builder {
|
||||
private final Uri mUri;
|
||||
private final int mFlags;
|
||||
private final CharSequence mTitle;
|
||||
private CharSequence mSummary;
|
||||
private Bundle mExtras;
|
||||
|
||||
/**
|
||||
* Creates an item builder.
|
||||
*/
|
||||
public Builder(@NonNull Uri uri, @Flags int flags, @NonNull CharSequence title) {
|
||||
if (uri == null) {
|
||||
throw new IllegalArgumentException("uri can not be null");
|
||||
}
|
||||
if (title == null) {
|
||||
throw new IllegalArgumentException("title can not be null");
|
||||
}
|
||||
mUri = uri;
|
||||
mFlags = flags;
|
||||
mTitle = title;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets summary of the item, or null if none.
|
||||
*/
|
||||
public @NonNull Builder setSummary(@Nullable CharSequence summary) {
|
||||
mSummary = summary;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets additional service-specified extras about the
|
||||
* item or its content, or null if none.
|
||||
*/
|
||||
public @NonNull Builder setExtras(@Nullable Bundle extras) {
|
||||
mExtras = extras;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the item.
|
||||
*/
|
||||
public @NonNull MediaBrowserItem build() {
|
||||
return new MediaBrowserItem(mUri, mFlags, mTitle, mSummary, mExtras);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
363
media/java/android/media/browse/MediaBrowserService.java
Normal file
@@ -0,0 +1,363 @@
|
||||
/*
|
||||
* Copyright (C) 2014 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.media.browse;
|
||||
|
||||
import android.annotation.IntDef;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.annotation.SdkConstant;
|
||||
import android.annotation.SdkConstant.SdkConstantType;
|
||||
import android.app.Service;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ParceledListSlice;
|
||||
import android.graphics.Bitmap;
|
||||
import android.media.session.MediaSession;
|
||||
import android.net.Uri;
|
||||
import android.os.Binder;
|
||||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
import android.os.Handler;
|
||||
import android.os.RemoteException;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.FileDescriptor;
|
||||
import java.io.PrintWriter;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Base class for media browse services.
|
||||
* <p>
|
||||
* Media browse services enable applications to browse media content provided by an application
|
||||
* and ask the application to start playing it. They may also be used to control content that
|
||||
* is already playing by way of a {@link MediaSession}.
|
||||
* </p>
|
||||
*
|
||||
* To extend this class, you must declare the service in your manifest file with
|
||||
* an intent filter with the {@link #SERVICE_ACTION} action.
|
||||
*
|
||||
* For example:
|
||||
* </p><pre>
|
||||
* <service android:name=".MyMediaBrowserService"
|
||||
* android:label="@string/service_name" >
|
||||
* <intent-filter>
|
||||
* <action android:name="android.media.browse.MediaBrowserService" />
|
||||
* </intent-filter>
|
||||
* </service>
|
||||
* </pre>
|
||||
*
|
||||
*/
|
||||
public abstract class MediaBrowserService extends Service {
|
||||
private static final String TAG = "MediaBrowserService";
|
||||
|
||||
private final ArrayMap<IBinder, ConnectionRecord> mConnections = new ArrayMap();
|
||||
private final Handler mHandler = new Handler();
|
||||
private ServiceBinder mBinder;
|
||||
MediaSession.Token mSession;
|
||||
|
||||
/**
|
||||
* All the info about a connection.
|
||||
*/
|
||||
private class ConnectionRecord {
|
||||
String pkg;
|
||||
Bundle rootHints;
|
||||
IMediaBrowserServiceCallbacks callbacks;
|
||||
Uri root;
|
||||
HashSet<Uri> subscriptions = new HashSet();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 class ServiceBinder extends IMediaBrowserService.Stub {
|
||||
@Override
|
||||
public void connect(final String pkg, final Bundle rootHints,
|
||||
final IMediaBrowserServiceCallbacks callbacks) {
|
||||
|
||||
final int uid = Binder.getCallingUid();
|
||||
if (!isValidPackage(pkg, uid)) {
|
||||
throw new IllegalArgumentException("Package/uid mismatch: uid=" + uid
|
||||
+ " package=" + pkg);
|
||||
}
|
||||
|
||||
mHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
final IBinder b = callbacks.asBinder();
|
||||
|
||||
// Clear out the old subscriptions. We are getting new ones.
|
||||
mConnections.remove(b);
|
||||
|
||||
final ConnectionRecord connection = new ConnectionRecord();
|
||||
connection.pkg = pkg;
|
||||
connection.rootHints = rootHints;
|
||||
connection.callbacks = callbacks;
|
||||
|
||||
connection.root = MediaBrowserService.this.onGetRoot(pkg, uid, rootHints);
|
||||
|
||||
// If they didn't return something, don't allow this client.
|
||||
if (connection.root == null) {
|
||||
Log.i(TAG, "No root for client " + pkg + " from service "
|
||||
+ getClass().getName());
|
||||
try {
|
||||
callbacks.onConnectFailed();
|
||||
} catch (RemoteException ex) {
|
||||
Log.w(TAG, "Calling onConnectFailed() failed. Ignoring. "
|
||||
+ "pkg=" + pkg);
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
mConnections.put(b, connection);
|
||||
callbacks.onConnect(connection.root, mSession);
|
||||
} catch (RemoteException ex) {
|
||||
Log.w(TAG, "Calling onConnect() failed. Dropping client. "
|
||||
+ "pkg=" + pkg);
|
||||
mConnections.remove(b);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disconnect(final IMediaBrowserServiceCallbacks callbacks) {
|
||||
mHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
final IBinder b = callbacks.asBinder();
|
||||
|
||||
// Clear out the old subscriptions. We are getting new ones.
|
||||
final ConnectionRecord old = mConnections.remove(b);
|
||||
if (old != null) {
|
||||
// TODO
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void addSubscription(final Uri uri, final IMediaBrowserServiceCallbacks callbacks) {
|
||||
mHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
final IBinder b = callbacks.asBinder();
|
||||
|
||||
// Get the record for the connection
|
||||
final ConnectionRecord connection = mConnections.get(b);
|
||||
if (connection == null) {
|
||||
Log.w(TAG, "addSubscription for callback that isn't registered uri="
|
||||
+ uri);
|
||||
return;
|
||||
}
|
||||
|
||||
MediaBrowserService.this.addSubscription(uri, connection);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeSubscription(final Uri uri,
|
||||
final IMediaBrowserServiceCallbacks callbacks) {
|
||||
mHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
final IBinder b = callbacks.asBinder();
|
||||
|
||||
ConnectionRecord connection = mConnections.get(b);
|
||||
if (connection == null) {
|
||||
Log.w(TAG, "removeSubscription for callback that isn't registered uri="
|
||||
+ uri);
|
||||
return;
|
||||
}
|
||||
if (!connection.subscriptions.remove(uri)) {
|
||||
Log.w(TAG, "removeSubscription called for " + uri
|
||||
+ " which is not subscribed");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
mBinder = new ServiceBinder();
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
if (SERVICE_ACTION.equals(intent.getAction())) {
|
||||
return mBinder;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called to get the root uri for browsing by a particular client.
|
||||
* <p>
|
||||
* The implementation should verify that the client package has
|
||||
* permission to access browse media information before returning
|
||||
* the root uri; it should return null if the client is not
|
||||
* allowed to access this information.
|
||||
* </p>
|
||||
*
|
||||
* @param clientPackageName The package name of the application
|
||||
* which is requesting access to browse media.
|
||||
* @param clientUid The uid of the application which is requesting
|
||||
* access to browse media.
|
||||
* @param rootHints An optional bundle of service-specific arguments to send
|
||||
* to the media browse service when connecting and retrieving the root uri
|
||||
* 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);
|
||||
|
||||
/**
|
||||
* Called to get information about the children of a media item.
|
||||
*
|
||||
* @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);
|
||||
|
||||
/**
|
||||
* Called to get the thumbnail of a particular media item.
|
||||
*
|
||||
* @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);
|
||||
|
||||
/**
|
||||
* Call to set the media session.
|
||||
* <p>
|
||||
* This must be called before onCreate returns.
|
||||
*
|
||||
* @return The media session token, must not be null.
|
||||
*/
|
||||
public void setSessionToken(MediaSession.Token token) {
|
||||
if (token == null) {
|
||||
throw new IllegalStateException(this.getClass().getName()
|
||||
+ ".onCreateSession() set invalid MediaSession.Token");
|
||||
}
|
||||
mSession = token;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the session token, or null if it has not yet been created
|
||||
* or if it has been destroyed.
|
||||
*/
|
||||
public @Nullable MediaSession.Token getSessionToken() {
|
||||
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.
|
||||
* This will cause browsers to fetch subscribed content again.
|
||||
*
|
||||
* @param parentUri The uri of the parent media item whose
|
||||
* children changed.
|
||||
*/
|
||||
public void notifyChildrenChanged(@NonNull Uri parentUri) {
|
||||
throw new RuntimeException("implement me");
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether the given package is one of the ones that is owned by the uid.
|
||||
*/
|
||||
private boolean isValidPackage(String pkg, int uid) {
|
||||
if (pkg == null) {
|
||||
return false;
|
||||
}
|
||||
final PackageManager pm = getPackageManager();
|
||||
final String[] packages = pm.getPackagesForUid(uid);
|
||||
final int N = packages.length;
|
||||
for (int i=0; i<N; i++) {
|
||||
if (packages[i].equals(pkg)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the subscription and if it is a new subscription send the results.
|
||||
*/
|
||||
private void addSubscription(Uri uri, ConnectionRecord connection) {
|
||||
// Save the subscription
|
||||
final boolean added = connection.subscriptions.add(uri);
|
||||
|
||||
// If this is a new subscription, send the results
|
||||
if (added) {
|
||||
performLoadChildren(uri, connection);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
if (list == null) {
|
||||
throw new IllegalStateException("onLoadChildren returned null for uri " + uri);
|
||||
}
|
||||
final ParceledListSlice<MediaBrowserItem> pls = new ParceledListSlice(list);
|
||||
try {
|
||||
connection.callbacks.onLoadChildren(uri, pls);
|
||||
} catch (RemoteException ex) {
|
||||
// The other side is in the process of crashing.
|
||||
Log.w(TAG, "Calling onLoadChildren() failed for uri=" + uri
|
||||
+ " package=" + connection.pkg);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -185,8 +185,8 @@ import javax.net.ssl.SSLSession;
|
||||
public class ConnectivityService extends IConnectivityManager.Stub {
|
||||
private static final String TAG = "ConnectivityService";
|
||||
|
||||
private static final boolean DBG = true;
|
||||
private static final boolean VDBG = true; // STOPSHIP
|
||||
private static final boolean DBG = false;
|
||||
private static final boolean VDBG = false; // STOPSHIP
|
||||
|
||||
// network sampling debugging
|
||||
private static final boolean SAMPLE_DBG = false;
|
||||
|
||||
35
tests/MusicBrowserDemo/Android.mk
Normal file
@@ -0,0 +1,35 @@
|
||||
# Copyright (C) 2014 The Android Open Source Project
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
LOCAL_PATH := $(call my-dir)
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
|
||||
LOCAL_PACKAGE_NAME := MusicBrowserDemo
|
||||
#LOCAL_SDK_VERSION := current
|
||||
LOCAL_SRC_FILES := $(call all-java-files-under, src)
|
||||
LOCAL_STATIC_JAVA_LIBRARIES := \
|
||||
android-support-v4 \
|
||||
android-support-v7-appcompat
|
||||
|
||||
LOCAL_RESOURCE_DIR := \
|
||||
$(LOCAL_PATH)/res \
|
||||
frameworks/support/v7/appcompat/res
|
||||
LOCAL_PROGUARD_ENABLED := disabled
|
||||
#LOCAL_PROGUARD_FLAG_FILES := proguard.flags
|
||||
|
||||
LOCAL_AAPT_FLAGS := \
|
||||
--auto-add-overlay \
|
||||
--extra-packages android.support.v7.appcompat
|
||||
include $(BUILD_PACKAGE)
|
||||
45
tests/MusicBrowserDemo/AndroidManifest.xml
Normal file
@@ -0,0 +1,45 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright (C) 2014 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.example.android.musicbrowserdemo"
|
||||
android:versionCode="1"
|
||||
android:versionName="1.0" >
|
||||
|
||||
<uses-sdk
|
||||
android:minSdkVersion="9"
|
||||
android:targetSdkVersion="19" />
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:icon="@drawable/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:theme="@style/AppTheme"
|
||||
>
|
||||
|
||||
<activity
|
||||
android:name="com.example.android.musicbrowserdemo.MainActivity"
|
||||
android:label="@string/app_name"
|
||||
>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
BIN
tests/MusicBrowserDemo/res/drawable-hdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 4.7 KiB |
BIN
tests/MusicBrowserDemo/res/drawable-mdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
BIN
tests/MusicBrowserDemo/res/drawable-xhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 5.1 KiB |
BIN
tests/MusicBrowserDemo/res/drawable-xxhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
21
tests/MusicBrowserDemo/res/values/strings.xml
Normal file
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright (C) 2014 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<resources>
|
||||
|
||||
<string name="app_name">Music Browser</string>
|
||||
|
||||
</resources>
|
||||
36
tests/MusicBrowserDemo/res/values/styles.xml
Normal file
@@ -0,0 +1,36 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright (C) 2014 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<resources>
|
||||
|
||||
<!--
|
||||
Base application theme, dependent on API level. This theme is replaced
|
||||
by AppBaseTheme from res/values-vXX/styles.xml on newer devices.
|
||||
-->
|
||||
<style name="AppBaseTheme" parent="Theme.AppCompat.Light">
|
||||
<!--
|
||||
Theme customizations available in newer API levels can go in
|
||||
res/values-vXX/styles.xml, while customizations related to
|
||||
backward-compatibility can go here.
|
||||
-->
|
||||
</style>
|
||||
|
||||
<!-- Application theme. -->
|
||||
<style name="AppTheme" parent="AppBaseTheme">
|
||||
<!-- All customizations that are NOT specific to a particular API-level can go here. -->
|
||||
</style>
|
||||
|
||||
</resources>
|
||||
@@ -0,0 +1,149 @@
|
||||
/*
|
||||
* Copyright (C) 2010 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.example.android.musicbrowserdemo;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.media.browse.MediaBrowserService;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.FragmentActivity;
|
||||
import android.support.v4.app.FragmentTransaction;
|
||||
import android.support.v4.app.ListFragment;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.BaseAdapter;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
// TODO: Include an icon.
|
||||
|
||||
public class AppListFragment extends ListFragment {
|
||||
|
||||
private Adapter mAdapter;
|
||||
private List<Item> mItems;
|
||||
|
||||
public AppListFragment() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
mAdapter = new Adapter();
|
||||
setListAdapter(mAdapter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onListItemClick(ListView l, View v, int position, long id) {
|
||||
final Item item = mItems.get(position);
|
||||
|
||||
Log.i("AppListFragment", "Item clicked: " + position + " -- " + item.component);
|
||||
|
||||
final BrowserListFragment fragment = new BrowserListFragment();
|
||||
|
||||
final Bundle args = new Bundle();
|
||||
args.putParcelable(BrowserListFragment.ARG_COMPONENT, item.component);
|
||||
fragment.setArguments(args);
|
||||
|
||||
getFragmentManager().beginTransaction()
|
||||
.replace(android.R.id.content, fragment)
|
||||
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
|
||||
.addToBackStack(null)
|
||||
.commit();
|
||||
}
|
||||
|
||||
private static class Item {
|
||||
final String label;
|
||||
final ComponentName component;
|
||||
|
||||
Item(String l, ComponentName c) {
|
||||
this.label = l;
|
||||
this.component = c;
|
||||
}
|
||||
}
|
||||
|
||||
private class Adapter extends BaseAdapter {
|
||||
private final LayoutInflater mInflater;
|
||||
|
||||
Adapter() {
|
||||
super();
|
||||
|
||||
final Context context = getActivity();
|
||||
mInflater = LayoutInflater.from(context);
|
||||
|
||||
// Load the data
|
||||
final PackageManager pm = context.getPackageManager();
|
||||
final Intent intent = new Intent(MediaBrowserService.SERVICE_ACTION);
|
||||
final List<ResolveInfo> list = pm.queryIntentServices(intent, 0);
|
||||
final int N = list.size();
|
||||
mItems = new ArrayList(N);
|
||||
for (int i=0; i<N; i++) {
|
||||
final ResolveInfo ri = list.get(i);
|
||||
mItems.add(new Item(ri.loadLabel(pm).toString(), new ComponentName(
|
||||
ri.serviceInfo.applicationInfo.packageName,
|
||||
ri.serviceInfo.name)));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
return mItems.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Item getItem(int position) {
|
||||
return mItems.get(position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getItemId(int position) {
|
||||
return position;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemViewType(int position) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(int position, View convertView, ViewGroup parent) {
|
||||
if (convertView == null) {
|
||||
convertView = mInflater.inflate(android.R.layout.simple_list_item_1, parent, false);
|
||||
}
|
||||
|
||||
final TextView tv = (TextView)convertView;
|
||||
final Item item = mItems.get(position);
|
||||
tv.setText(item.label);
|
||||
|
||||
return convertView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getViewTypeCount() {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,212 @@
|
||||
/*
|
||||
* Copyright (C) 2010 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.example.android.musicbrowserdemo;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.media.browse.MediaBrowser;
|
||||
import android.media.browse.MediaBrowserItem;
|
||||
import android.media.browse.MediaBrowserService;
|
||||
import android.os.Bundle;
|
||||
import android.net.Uri;
|
||||
import android.support.v4.app.FragmentActivity;
|
||||
import android.support.v4.app.FragmentTransaction;
|
||||
import android.support.v4.app.ListFragment;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.BaseAdapter;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class BrowserListFragment extends ListFragment {
|
||||
private static final String TAG = "BrowserListFragment";
|
||||
|
||||
// Hints
|
||||
public static final String HINT_DISPLAY = "com.example.android.musicbrowserdemo.DISPLAY";
|
||||
|
||||
// For args
|
||||
public static final String ARG_COMPONENT = "component";
|
||||
public static final String ARG_URI = "uri";
|
||||
|
||||
private Adapter mAdapter;
|
||||
private List<Item> mItems = new ArrayList();
|
||||
private ComponentName mComponent;
|
||||
private Uri mUri;
|
||||
private MediaBrowser mBrowser;
|
||||
|
||||
private static class Item {
|
||||
final MediaBrowserItem media;
|
||||
|
||||
Item(MediaBrowserItem m) {
|
||||
this.media = m;
|
||||
}
|
||||
}
|
||||
|
||||
public BrowserListFragment() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
Log.d(TAG, "onActivityCreated -- " + hashCode());
|
||||
mAdapter = new Adapter();
|
||||
setListAdapter(mAdapter);
|
||||
|
||||
// Get our arguments
|
||||
final Bundle args = getArguments();
|
||||
mComponent = args.getParcelable(ARG_COMPONENT);
|
||||
mUri = args.getParcelable(ARG_URI);
|
||||
|
||||
// A hint about who we are, so the service can customize the results if it wants to.
|
||||
final Bundle rootHints = new Bundle();
|
||||
rootHints.putBoolean(HINT_DISPLAY, true);
|
||||
|
||||
mBrowser = new MediaBrowser(getActivity(), mComponent, mConnectionCallbacks, rootHints);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
mBrowser.connect();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop() {
|
||||
super.onStop();
|
||||
mBrowser.disconnect();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onListItemClick(ListView l, View v, int position, long id) {
|
||||
final Item item = mItems.get(position);
|
||||
|
||||
Log.i("BrowserListFragment", "Item clicked: " + position + " -- "
|
||||
+ mAdapter.getItem(position).media.getUri());
|
||||
|
||||
final BrowserListFragment fragment = new BrowserListFragment();
|
||||
|
||||
final Bundle args = new Bundle();
|
||||
args.putParcelable(BrowserListFragment.ARG_COMPONENT, mComponent);
|
||||
args.putParcelable(BrowserListFragment.ARG_URI, item.media.getUri());
|
||||
fragment.setArguments(args);
|
||||
|
||||
getFragmentManager().beginTransaction()
|
||||
.replace(android.R.id.content, fragment)
|
||||
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
|
||||
.addToBackStack(null)
|
||||
.commit();
|
||||
|
||||
}
|
||||
|
||||
final MediaBrowser.ConnectionCallback mConnectionCallbacks
|
||||
= new MediaBrowser.ConnectionCallback() {
|
||||
@Override
|
||||
public void onConnected() {
|
||||
Log.d(TAG, "mConnectionCallbacks.onConnected");
|
||||
if (mUri == null) {
|
||||
mUri = mBrowser.getRoot();
|
||||
}
|
||||
mBrowser.subscribe(mUri, new MediaBrowser.SubscriptionCallback() {
|
||||
@Override
|
||||
public void onChildrenLoaded(Uri parentUri, List<MediaBrowserItem> children) {
|
||||
Log.d(TAG, "onChildrenLoaded parentUri=" + parentUri
|
||||
+ " children= " + children);
|
||||
mItems.clear();
|
||||
final int N = children.size();
|
||||
for (int i=0; i<N; i++) {
|
||||
mItems.add(new Item(children.get(i)));
|
||||
}
|
||||
mAdapter.notifyDataSetChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Uri parentUri) {
|
||||
Log.d(TAG, "onError parentUri=" + parentUri);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConnectionSuspended() {
|
||||
Log.d(TAG, "mConnectionCallbacks.onConnectionSuspended");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConnectionFailed() {
|
||||
Log.d(TAG, "mConnectionCallbacks.onConnectionFailed");
|
||||
}
|
||||
};
|
||||
|
||||
private class Adapter extends BaseAdapter {
|
||||
private final LayoutInflater mInflater;
|
||||
|
||||
Adapter() {
|
||||
super();
|
||||
|
||||
final Context context = getActivity();
|
||||
mInflater = LayoutInflater.from(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
return mItems.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Item getItem(int position) {
|
||||
return mItems.get(position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getItemId(int position) {
|
||||
return position;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemViewType(int position) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(int position, View convertView, ViewGroup parent) {
|
||||
if (convertView == null) {
|
||||
convertView = mInflater.inflate(android.R.layout.simple_list_item_1, parent, false);
|
||||
}
|
||||
|
||||
final TextView tv = (TextView)convertView;
|
||||
final Item item = mItems.get(position);
|
||||
tv.setText(item.media.getTitle());
|
||||
|
||||
return convertView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getViewTypeCount() {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright (C) 2010 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.example.android.musicbrowserdemo;
|
||||
|
||||
import android.support.v4.app.FragmentActivity;
|
||||
import android.support.v4.app.FragmentManager;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.ListView;
|
||||
|
||||
/**
|
||||
* Main activity class.
|
||||
*/
|
||||
public class MainActivity extends FragmentActivity {
|
||||
|
||||
private static final String BROWSER_FRAGMENT_TAG = "browser";
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle icicle) {
|
||||
super.onCreate(icicle);
|
||||
|
||||
Log.d("MainActivity", "-------------------------------------------------------");
|
||||
|
||||
// If we are starting afresh, start at the app list.
|
||||
final FragmentManager fm = getSupportFragmentManager();
|
||||
if (fm.findFragmentById(android.R.id.content) == null) {
|
||||
fm.beginTransaction().add(android.R.id.content, new AppListFragment()).commit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
35
tests/MusicServiceDemo/Android.mk
Normal file
@@ -0,0 +1,35 @@
|
||||
# Copyright (C) 2013 The Android Open Source Project
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
LOCAL_PATH := $(call my-dir)
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
|
||||
LOCAL_PACKAGE_NAME := MusicServiceDemo
|
||||
#LOCAL_SDK_VERSION := current
|
||||
LOCAL_SRC_FILES := $(call all-java-files-under, src)
|
||||
LOCAL_STATIC_JAVA_LIBRARIES := \
|
||||
android-support-v4 \
|
||||
android-support-v7-appcompat
|
||||
|
||||
LOCAL_RESOURCE_DIR := \
|
||||
$(LOCAL_PATH)/res \
|
||||
frameworks/support/v7/appcompat/res
|
||||
LOCAL_PROGUARD_ENABLED := disabled
|
||||
#LOCAL_PROGUARD_FLAG_FILES := proguard.flags
|
||||
|
||||
LOCAL_AAPT_FLAGS := \
|
||||
--auto-add-overlay \
|
||||
--extra-packages android.support.v7.appcompat
|
||||
include $(BUILD_PACKAGE)
|
||||
55
tests/MusicServiceDemo/AndroidManifest.xml
Normal file
@@ -0,0 +1,55 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright (C) 2014 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.example.android.musicservicedemo"
|
||||
android:versionCode="1"
|
||||
android:versionName="1.0" >
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
|
||||
<uses-sdk
|
||||
android:minSdkVersion="9"
|
||||
android:targetSdkVersion="19" />
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:icon="@drawable/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:theme="@style/AppTheme"
|
||||
>
|
||||
|
||||
<activity
|
||||
android:name="com.example.android.automotive.musicplayer.MainActivity"
|
||||
android:label="@string/app_name" >
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<service
|
||||
android:name=".BrowserService"
|
||||
android:exported="true"
|
||||
>
|
||||
<intent-filter>
|
||||
<action android:name="android.media.browse.MediaBrowseService" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
20
tests/MusicServiceDemo/proguard-project.txt
Normal file
@@ -0,0 +1,20 @@
|
||||
# To enable ProGuard in your project, edit project.properties
|
||||
# to define the proguard.config property as described in that file.
|
||||
#
|
||||
# Add project specific ProGuard rules here.
|
||||
# By default, the flags in this file are appended to flags specified
|
||||
# in ${sdk.dir}/tools/proguard/proguard-android.txt
|
||||
# You can edit the include path and order by changing the ProGuard
|
||||
# include property in project.properties.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# Add any project specific keep options here:
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
BIN
tests/MusicServiceDemo/res/drawable-hdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 4.7 KiB |
BIN
tests/MusicServiceDemo/res/drawable-mdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
BIN
tests/MusicServiceDemo/res/drawable-xhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 5.1 KiB |
BIN
tests/MusicServiceDemo/res/drawable-xxhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
tests/MusicServiceDemo/res/drawable-xxhdpi/thumbsup.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
23
tests/MusicServiceDemo/res/layout/activity_main.xml
Normal file
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright (C) 2014 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context="com.example.android.automotive.musicplayer.MainActivity"
|
||||
tools:ignore="MergeRootFrame" />
|
||||
32
tests/MusicServiceDemo/res/layout/fragment_main.xml
Normal file
@@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright (C) 2014 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:paddingBottom="@dimen/activity_vertical_margin"
|
||||
android:paddingLeft="@dimen/activity_horizontal_margin"
|
||||
android:paddingRight="@dimen/activity_horizontal_margin"
|
||||
android:paddingTop="@dimen/activity_vertical_margin"
|
||||
tools:context="com.example.android.automotive.musicplayer.MainActivity$PlaceholderFragment" >
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/app_name" />
|
||||
|
||||
</RelativeLayout>
|
||||
22
tests/MusicServiceDemo/res/values/colors.xml
Normal file
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2014 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<resources>
|
||||
<color name="yellow">#ffffff00</color>
|
||||
<color name="green">#ff00ff00</color>
|
||||
<color name="blue">#ff0000ff</color>
|
||||
<color name="red">#ffff0000</color>
|
||||
</resources>
|
||||
23
tests/MusicServiceDemo/res/values/dimens.xml
Normal file
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright (C) 2014 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<resources>
|
||||
|
||||
<!-- Default screen margins, per the Android Design guidelines. -->
|
||||
<dimen name="activity_horizontal_margin">16dp</dimen>
|
||||
<dimen name="activity_vertical_margin">16dp</dimen>
|
||||
|
||||
</resources>
|
||||
25
tests/MusicServiceDemo/res/values/strings.xml
Normal file
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright (C) 2014 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<resources>
|
||||
|
||||
<string name="app_name">Music Service Demo</string>
|
||||
<string name="action_settings">Settings</string>
|
||||
<string name="thumbs_up">Thumbs Up</string>
|
||||
<string name="music_error">No music found</string>
|
||||
<string name="now_playing">Now Playing</string>
|
||||
|
||||
</resources>
|
||||
36
tests/MusicServiceDemo/res/values/styles.xml
Normal file
@@ -0,0 +1,36 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright (C) 2014 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<resources>
|
||||
|
||||
<!--
|
||||
Base application theme, dependent on API level. This theme is replaced
|
||||
by AppBaseTheme from res/values-vXX/styles.xml on newer devices.
|
||||
-->
|
||||
<style name="AppBaseTheme" parent="Theme.AppCompat.Light">
|
||||
<!--
|
||||
Theme customizations available in newer API levels can go in
|
||||
res/values-vXX/styles.xml, while customizations related to
|
||||
backward-compatibility can go here.
|
||||
-->
|
||||
</style>
|
||||
|
||||
<!-- Application theme. -->
|
||||
<style name="AppTheme" parent="AppBaseTheme">
|
||||
<!-- All customizations that are NOT specific to a particular API-level can go here. -->
|
||||
</style>
|
||||
|
||||
</resources>
|
||||
@@ -0,0 +1,246 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.example.android.musicservicedemo;
|
||||
|
||||
import android.app.SearchManager;
|
||||
import android.app.Service;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.UriMatcher;
|
||||
import android.content.res.Resources.NotFoundException;
|
||||
import android.database.MatrixCursor;
|
||||
import android.graphics.Bitmap;
|
||||
import android.media.AudioManager;
|
||||
import android.media.MediaPlayer;
|
||||
import android.media.MediaPlayer.OnCompletionListener;
|
||||
import android.media.MediaPlayer.OnErrorListener;
|
||||
import android.media.MediaPlayer.OnPreparedListener;
|
||||
import android.media.browse.MediaBrowserItem;
|
||||
import android.media.browse.MediaBrowserService;
|
||||
import android.media.session.MediaSession;
|
||||
import android.net.Uri;
|
||||
import android.net.wifi.WifiManager;
|
||||
import android.net.wifi.WifiManager.WifiLock;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
import android.os.Message;
|
||||
import android.os.PowerManager;
|
||||
import android.os.RemoteException;
|
||||
import android.os.SystemClock;
|
||||
import android.util.Log;
|
||||
|
||||
import com.example.android.musicservicedemo.browser.MusicProvider;
|
||||
import com.example.android.musicservicedemo.browser.MusicProviderTask;
|
||||
import com.example.android.musicservicedemo.browser.MusicProviderTaskListener;
|
||||
import com.example.android.musicservicedemo.browser.MusicTrack;
|
||||
|
||||
import org.json.JSONException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Service that implements MediaBrowserService and returns our menu hierarchy.
|
||||
*/
|
||||
public class BrowserService extends MediaBrowserService {
|
||||
private static final String TAG = "BrowserService";
|
||||
|
||||
// URI paths for browsing music
|
||||
public static final String BROWSE_ROOT_BASE_PATH = "browse";
|
||||
public static final String NOW_PLAYING_PATH = "now_playing";
|
||||
public static final String PIANO_BASE_PATH = "piano";
|
||||
public static final String VOICE_BASE_PATH = "voice";
|
||||
|
||||
// Content URIs
|
||||
public static final String AUTHORITY = "com.example.android.automotive.musicplayer";
|
||||
public static final Uri BASE_URI = Uri.parse("content://" + AUTHORITY);
|
||||
public static final Uri BROWSE_URI = Uri.withAppendedPath(BASE_URI, BROWSE_ROOT_BASE_PATH);
|
||||
|
||||
// URI matcher constants for browsing paths
|
||||
public static final int BROWSE_ROOT = 1;
|
||||
public static final int NOW_PLAYING = 2;
|
||||
public static final int PIANO = 3;
|
||||
public static final int VOICE = 4;
|
||||
|
||||
// Map the the URI paths with the URI matcher constants
|
||||
private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
|
||||
static {
|
||||
sUriMatcher.addURI(AUTHORITY, BROWSE_ROOT_BASE_PATH, BROWSE_ROOT);
|
||||
sUriMatcher.addURI(AUTHORITY, NOW_PLAYING_PATH, NOW_PLAYING);
|
||||
sUriMatcher.addURI(AUTHORITY, PIANO_BASE_PATH, PIANO);
|
||||
sUriMatcher.addURI(AUTHORITY, VOICE_BASE_PATH, VOICE);
|
||||
}
|
||||
|
||||
// Media metadata that will be provided for a media container
|
||||
public static final String[] MEDIA_CONTAINER_PROJECTION = {
|
||||
"uri",
|
||||
"title",
|
||||
"subtitle",
|
||||
"image_uri",
|
||||
"supported_actions"
|
||||
};
|
||||
|
||||
// MusicProvider will download the music catalog
|
||||
private MusicProvider mMusicProvider;
|
||||
|
||||
private MediaSession mSession;
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
|
||||
mSession = new MediaSession(this, "com.example.android.musicservicedemo.BrowserService");
|
||||
setSessionToken(mSession.getSessionToken());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Uri onGetRoot(String clientPackageName, int clientUid, Bundle rootHints) {
|
||||
return BROWSE_URI;
|
||||
}
|
||||
|
||||
@Override
|
||||
public 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());
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Bitmap onGetThumbnail(Uri uri, int width, int height, int density) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/*
|
||||
@Override
|
||||
public void query(final Query query, final IMetadataResultHandler metadataResultHandler,
|
||||
final IErrorHandler errorHandler)
|
||||
throws RemoteException {
|
||||
Log.d(TAG, "query: " + query);
|
||||
Utils.checkNotNull(query);
|
||||
Utils.checkNotNull(metadataResultHandler);
|
||||
Utils.checkNotNull(errorHandler);
|
||||
|
||||
// Handle async response
|
||||
new Thread(new Runnable() {
|
||||
public void run() {
|
||||
try {
|
||||
// Pre-load the list of music
|
||||
List<MusicTrack> musicTracks = getMusicList();
|
||||
if (musicTracks == null) {
|
||||
notifyListenersOnPlaybackStateUpdate(getCurrentPlaybackState());
|
||||
errorHandler.onError(new Error(Error.UNKNOWN,
|
||||
getString(R.string.music_error)));
|
||||
return;
|
||||
}
|
||||
|
||||
final Uri uri = query.getUri();
|
||||
int match = sUriMatcher.match(uri);
|
||||
Log.d(TAG, "Queried: " + uri + "; match: " + match);
|
||||
switch (match) {
|
||||
case BROWSE_ROOT:
|
||||
{
|
||||
Log.d(TAG, "Browse_root");
|
||||
|
||||
try {
|
||||
MatrixCursor matrixCursor = mMusicProvider
|
||||
.getRootContainerCurser();
|
||||
DataHolder holder = new DataHolder(MEDIA_CONTAINER_PROJECTION,
|
||||
matrixCursor, null);
|
||||
|
||||
Log.d(TAG, "on metadata response called " + holder.getCount());
|
||||
metadataResultHandler.onMetadataResponse(holder);
|
||||
} catch (RemoteException e) {
|
||||
Log.w(TAG, "Error delivering metadata in the callback.", e);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case NOW_PLAYING:
|
||||
{
|
||||
try {
|
||||
Log.d(TAG, "query NOW_PLAYING");
|
||||
MatrixCursor matrixCursor = mMusicProvider
|
||||
.getRootItemCursor(
|
||||
PIANO);
|
||||
DataHolder holder = new DataHolder(MEDIA_CONTAINER_PROJECTION,
|
||||
matrixCursor, null);
|
||||
Log.d(TAG, "on metadata response called " + holder.getCount());
|
||||
metadataResultHandler.onMetadataResponse(holder);
|
||||
} catch (RemoteException e) {
|
||||
Log.w(TAG, "Error querying NOW_PLAYING");
|
||||
}
|
||||
break;
|
||||
}
|
||||
case PIANO:
|
||||
{
|
||||
try {
|
||||
Log.d(TAG, "query PIANO");
|
||||
MatrixCursor matrixCursor = mMusicProvider
|
||||
.getRootItemCursor(
|
||||
PIANO);
|
||||
DataHolder holder = new DataHolder(MEDIA_CONTAINER_PROJECTION,
|
||||
matrixCursor, null);
|
||||
Log.d(TAG, "on metadata response called " + holder.getCount());
|
||||
metadataResultHandler.onMetadataResponse(holder);
|
||||
} catch (RemoteException e) {
|
||||
Log.w(TAG, "Error querying PIANO");
|
||||
}
|
||||
break;
|
||||
}
|
||||
case VOICE:
|
||||
{
|
||||
try {
|
||||
Log.d(TAG, "query VOICE");
|
||||
MatrixCursor matrixCursor = mMusicProvider
|
||||
.getRootItemCursor(
|
||||
VOICE);
|
||||
DataHolder holder = new DataHolder(MEDIA_CONTAINER_PROJECTION,
|
||||
matrixCursor, null);
|
||||
Log.d(TAG, "on metadata response called " + holder.getCount());
|
||||
metadataResultHandler.onMetadataResponse(holder);
|
||||
} catch (RemoteException e) {
|
||||
Log.w(TAG, "Error querying VOICE");
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
Log.w(TAG, "Skipping unmatched URI: " + uri);
|
||||
}
|
||||
}
|
||||
} catch (NotFoundException e) {
|
||||
Log.e(TAG, "::run:", e);
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "::run:", e);
|
||||
}
|
||||
} // end run
|
||||
}).start();
|
||||
}
|
||||
|
||||
*/
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
/* Copyright 2014 Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.example.android.musicservicedemo;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v7.app.ActionBarActivity;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import com.example.android.musicservicedemo.R;
|
||||
|
||||
// TODO Local UI
|
||||
|
||||
/**
|
||||
* Main activity of the app.
|
||||
*/
|
||||
public class MainActivity extends ActionBarActivity {
|
||||
|
||||
private static final String LOG = "MainActivity";
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_main);
|
||||
|
||||
if (savedInstanceState == null) {
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.add(R.id.container, new PlaceholderFragment())
|
||||
.commit();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see android.app.Activity#onCreateOptionsMenu(android.view.Menu)
|
||||
*/
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
|
||||
// Inflate the menu; this adds items to the action bar if it is present.
|
||||
//getMenuInflater().inflate(R.menu.main, menu);
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see android.app.Activity#onOptionsItemSelected(android.view.MenuItem)
|
||||
*/
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
// Handle action bar item clicks here. The action bar will
|
||||
// automatically handle clicks on the Home/Up button, so long
|
||||
// as you specify a parent activity in AndroidManifest.xml.
|
||||
int id = item.getItemId();
|
||||
// if (id == R.id.action_settings) {
|
||||
// return true;
|
||||
// }
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
/**
|
||||
* A placeholder fragment containing a simple view.
|
||||
*/
|
||||
public static class PlaceholderFragment extends Fragment {
|
||||
|
||||
public PlaceholderFragment() {
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see
|
||||
* android.support.v4.app.Fragment#onCreateView(android.view.LayoutInflater
|
||||
* , android.view.ViewGroup, android.os.Bundle)
|
||||
*/
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
View rootView = inflater.inflate(R.layout.fragment_main, container, false);
|
||||
return rootView;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.example.android.musicservicedemo;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
|
||||
public class Utils {
|
||||
|
||||
private static final String TAG = "Utils";
|
||||
|
||||
/**
|
||||
* Utility method to check that parameters are not null
|
||||
*
|
||||
* @param object
|
||||
*/
|
||||
public static final void checkNotNull(Object object) {
|
||||
if (object == null) {
|
||||
throw new NullPointerException();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility to download a bitmap
|
||||
*
|
||||
* @param source
|
||||
* @return
|
||||
*/
|
||||
public static Bitmap getBitmapFromURL(String source) {
|
||||
try {
|
||||
URL url = new URL(source);
|
||||
HttpURLConnection httpConnection = (HttpURLConnection) url.openConnection();
|
||||
httpConnection.setDoInput(true);
|
||||
httpConnection.connect();
|
||||
InputStream inputStream = httpConnection.getInputStream();
|
||||
return BitmapFactory.decodeStream(inputStream);
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "getBitmapFromUrl: " + source, e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility method to wrap an index
|
||||
*
|
||||
* @param i
|
||||
* @param size
|
||||
* @return
|
||||
*/
|
||||
public static int wrapIndex(int i, int size) {
|
||||
int m = i % size;
|
||||
if (m < 0) { // java modulus can be negative
|
||||
m += size;
|
||||
}
|
||||
return m;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,266 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.example.android.musicservicedemo.browser;
|
||||
|
||||
import android.database.MatrixCursor;
|
||||
import android.media.session.PlaybackState;
|
||||
import android.net.Uri;
|
||||
import android.util.Log;
|
||||
|
||||
import com.example.android.musicservicedemo.BrowserService;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.URLConnection;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Utility class to get a list of MusicTrack's based on a server-side JSON
|
||||
* configuration.
|
||||
*/
|
||||
public class MusicProvider {
|
||||
|
||||
private static final String TAG = "MusicProvider";
|
||||
|
||||
private static final String MUSIC_URL = "http://storage.googleapis.com/automotive-media/music.json";
|
||||
|
||||
private static String MUSIC = "music";
|
||||
private static String TITLE = "title";
|
||||
private static String ALBUM = "album";
|
||||
private static String ARTIST = "artist";
|
||||
private static String GENRE = "genre";
|
||||
private static String SOURCE = "source";
|
||||
private static String IMAGE = "image";
|
||||
private static String TRACK_NUMBER = "trackNumber";
|
||||
private static String TOTAL_TRACK_COUNT = "totalTrackCount";
|
||||
private static String DURATION = "duration";
|
||||
|
||||
// Cache for music track data
|
||||
private static List<MusicTrack> mMusicList;
|
||||
|
||||
/**
|
||||
* Get the cached list of music tracks
|
||||
*
|
||||
* @return
|
||||
* @throws JSONException
|
||||
*/
|
||||
public List<MusicTrack> getMedia() throws JSONException {
|
||||
if (null != mMusicList && mMusicList.size() > 0) {
|
||||
return mMusicList;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of music tracks from a server and return the list of
|
||||
* MusicTrack objects.
|
||||
*
|
||||
* @return
|
||||
* @throws JSONException
|
||||
*/
|
||||
public List<MusicTrack> retreiveMedia() throws JSONException {
|
||||
if (null != mMusicList) {
|
||||
return mMusicList;
|
||||
}
|
||||
int slashPos = MUSIC_URL.lastIndexOf('/');
|
||||
String path = MUSIC_URL.substring(0, slashPos + 1);
|
||||
JSONObject jsonObj = parseUrl(MUSIC_URL);
|
||||
|
||||
try {
|
||||
JSONArray videos = jsonObj.getJSONArray(MUSIC);
|
||||
if (null != videos) {
|
||||
mMusicList = new ArrayList<MusicTrack>();
|
||||
for (int j = 0; j < videos.length(); j++) {
|
||||
JSONObject music = videos.getJSONObject(j);
|
||||
String title = music.getString(TITLE);
|
||||
String album = music.getString(ALBUM);
|
||||
String artist = music.getString(ARTIST);
|
||||
String genre = music.getString(GENRE);
|
||||
String source = music.getString(SOURCE);
|
||||
// Media is stored relative to JSON file
|
||||
if (!source.startsWith("http")) {
|
||||
source = path + source;
|
||||
}
|
||||
String image = music.getString(IMAGE);
|
||||
if (!image.startsWith("http")) {
|
||||
image = path + image;
|
||||
}
|
||||
int trackNumber = music.getInt(TRACK_NUMBER);
|
||||
int totalTrackCount = music.getInt(TOTAL_TRACK_COUNT);
|
||||
int duration = music.getInt(DURATION) * 1000; // ms
|
||||
|
||||
mMusicList.add(new MusicTrack(title, album, artist, genre, source,
|
||||
image, trackNumber, totalTrackCount, duration));
|
||||
}
|
||||
}
|
||||
} catch (NullPointerException e) {
|
||||
Log.e(TAG, "retreiveMedia", e);
|
||||
}
|
||||
return mMusicList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Download a JSON file from a server, parse the content and return the JSON
|
||||
* object.
|
||||
*
|
||||
* @param urlString
|
||||
* @return
|
||||
*/
|
||||
private JSONObject parseUrl(String urlString) {
|
||||
InputStream is = null;
|
||||
try {
|
||||
java.net.URL url = new java.net.URL(urlString);
|
||||
URLConnection urlConnection = url.openConnection();
|
||||
is = new BufferedInputStream(urlConnection.getInputStream());
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(
|
||||
urlConnection.getInputStream(), "iso-8859-1"), 8);
|
||||
StringBuilder sb = new StringBuilder();
|
||||
String line = null;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
sb.append(line);
|
||||
}
|
||||
return new JSONObject(sb.toString());
|
||||
} catch (Exception e) {
|
||||
Log.d(TAG, "Failed to parse the json for media list", e);
|
||||
return null;
|
||||
} finally {
|
||||
if (null != is) {
|
||||
try {
|
||||
is.close();
|
||||
} catch (IOException e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public MatrixCursor getRootContainerCurser() {
|
||||
MatrixCursor matrixCursor = new MatrixCursor(BrowserService.MEDIA_CONTAINER_PROJECTION);
|
||||
Uri.Builder pianoBuilder = new Uri.Builder();
|
||||
pianoBuilder.authority(BrowserService.AUTHORITY);
|
||||
pianoBuilder.appendPath(BrowserService.PIANO_BASE_PATH);
|
||||
matrixCursor.addRow(new Object[] {
|
||||
pianoBuilder.build(),
|
||||
BrowserService.PIANO_BASE_PATH,
|
||||
"subtitle",
|
||||
null,
|
||||
0
|
||||
});
|
||||
|
||||
Uri.Builder voiceBuilder = new Uri.Builder();
|
||||
voiceBuilder.authority(BrowserService.AUTHORITY);
|
||||
voiceBuilder.appendPath(BrowserService.VOICE_BASE_PATH);
|
||||
matrixCursor.addRow(new Object[] {
|
||||
voiceBuilder.build(),
|
||||
BrowserService.VOICE_BASE_PATH,
|
||||
"subtitle",
|
||||
null,
|
||||
0
|
||||
});
|
||||
return matrixCursor;
|
||||
}
|
||||
|
||||
public MatrixCursor getRootItemCursor(int type) {
|
||||
if (type == BrowserService.NOW_PLAYING) {
|
||||
MatrixCursor matrixCursor = new MatrixCursor(BrowserService.MEDIA_CONTAINER_PROJECTION);
|
||||
|
||||
try {
|
||||
// Just return all of the tracks for now
|
||||
List<MusicTrack> musicTracks = retreiveMedia();
|
||||
for (MusicTrack musicTrack : musicTracks) {
|
||||
Uri.Builder builder = new Uri.Builder();
|
||||
builder.authority(BrowserService.AUTHORITY);
|
||||
builder.appendPath(BrowserService.NOW_PLAYING_PATH);
|
||||
builder.appendPath(musicTrack.getTitle());
|
||||
matrixCursor.addRow(new Object[] {
|
||||
builder.build(),
|
||||
musicTrack.getTitle(),
|
||||
musicTrack.getArtist(),
|
||||
musicTrack.getImage(),
|
||||
PlaybackState.ACTION_PLAY
|
||||
});
|
||||
Log.d(TAG, "Uri " + builder.build());
|
||||
}
|
||||
} catch (JSONException e) {
|
||||
Log.e(TAG, "::getRootItemCursor:", e);
|
||||
}
|
||||
|
||||
Log.d(TAG, "cursor: " + matrixCursor.getCount());
|
||||
return matrixCursor;
|
||||
} else if (type == BrowserService.PIANO) {
|
||||
MatrixCursor matrixCursor = new MatrixCursor(BrowserService.MEDIA_CONTAINER_PROJECTION);
|
||||
|
||||
try {
|
||||
List<MusicTrack> musicTracks = retreiveMedia();
|
||||
for (MusicTrack musicTrack : musicTracks) {
|
||||
Uri.Builder builder = new Uri.Builder();
|
||||
builder.authority(BrowserService.AUTHORITY);
|
||||
builder.appendPath(BrowserService.PIANO_BASE_PATH);
|
||||
builder.appendPath(musicTrack.getTitle());
|
||||
matrixCursor.addRow(new Object[] {
|
||||
builder.build(),
|
||||
musicTrack.getTitle(),
|
||||
musicTrack.getArtist(),
|
||||
musicTrack.getImage(),
|
||||
PlaybackState.ACTION_PLAY
|
||||
});
|
||||
Log.d(TAG, "Uri " + builder.build());
|
||||
}
|
||||
} catch (JSONException e) {
|
||||
Log.e(TAG, "::getRootItemCursor:", e);
|
||||
}
|
||||
|
||||
Log.d(TAG, "cursor: " + matrixCursor.getCount());
|
||||
return matrixCursor;
|
||||
} else if (type == BrowserService.VOICE) {
|
||||
MatrixCursor matrixCursor = new MatrixCursor(BrowserService.MEDIA_CONTAINER_PROJECTION);
|
||||
|
||||
try {
|
||||
List<MusicTrack> musicTracks = retreiveMedia();
|
||||
for (MusicTrack musicTrack : musicTracks) {
|
||||
Uri.Builder builder = new Uri.Builder();
|
||||
builder.authority(BrowserService.AUTHORITY);
|
||||
builder.appendPath(BrowserService.VOICE_BASE_PATH);
|
||||
builder.appendPath(musicTrack.getTitle());
|
||||
matrixCursor.addRow(new Object[] {
|
||||
builder.build(),
|
||||
musicTrack.getTitle(),
|
||||
musicTrack.getArtist(),
|
||||
musicTrack.getImage(),
|
||||
PlaybackState.ACTION_PLAY
|
||||
});
|
||||
Log.d(TAG, "Uri " + builder.build());
|
||||
}
|
||||
} catch (JSONException e) {
|
||||
Log.e(TAG, "::getRootItemCursor:", e);
|
||||
}
|
||||
|
||||
Log.d(TAG, "cursor: " + matrixCursor.getCount());
|
||||
return matrixCursor;
|
||||
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Copyright (C) 2014 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.example.android.musicservicedemo.browser;
|
||||
|
||||
import android.os.AsyncTask;
|
||||
import android.util.Log;
|
||||
|
||||
import org.json.JSONException;
|
||||
|
||||
/**
|
||||
* Asynchronous task to retrieve the music data using MusicProvider.
|
||||
*/
|
||||
public class MusicProviderTask extends AsyncTask<Void, Void, Void> {
|
||||
|
||||
private static final String TAG = "MusicProviderTask";
|
||||
|
||||
MusicProvider mMusicProvider;
|
||||
MusicProviderTaskListener mMusicProviderTaskListener;
|
||||
|
||||
/**
|
||||
* Initialize the task with the provider to download the music data and the
|
||||
* listener to be informed when the task is done.
|
||||
*
|
||||
* @param musicProvider
|
||||
* @param listener
|
||||
*/
|
||||
public MusicProviderTask(MusicProvider musicProvider,
|
||||
MusicProviderTaskListener listener) {
|
||||
mMusicProvider = musicProvider;
|
||||
mMusicProviderTaskListener = listener;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see android.os.AsyncTask#doInBackground(java.lang.Object[])
|
||||
*/
|
||||
@Override
|
||||
protected Void doInBackground(Void... arg0) {
|
||||
try {
|
||||
mMusicProvider.retreiveMedia();
|
||||
} catch (JSONException e) {
|
||||
Log.e(TAG, "::doInBackground:", e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see android.os.AsyncTask#onPostExecute(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
protected void onPostExecute(Void result) {
|
||||
mMusicProviderTaskListener.onMusicProviderTaskCompleted();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.example.android.musicservicedemo.browser;
|
||||
|
||||
/**
|
||||
* Callback listener for completion of MusicProviderTask
|
||||
*/
|
||||
public interface MusicProviderTaskListener {
|
||||
public void onMusicProviderTaskCompleted();
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.example.android.musicservicedemo.browser;
|
||||
|
||||
/**
|
||||
* A class to model music track metadata.
|
||||
*/
|
||||
public class MusicTrack {
|
||||
|
||||
private static final String TAG = "MusicTrack";
|
||||
|
||||
private String mTitle;
|
||||
private String mAlbum;
|
||||
private String mArtist;
|
||||
private String mGenre;
|
||||
private String mSource;
|
||||
private String mImage;
|
||||
private int mTrackNumber;
|
||||
private int mTotalTrackCount;
|
||||
private int mDuration;
|
||||
|
||||
/**
|
||||
* Constructor creating a MusicTrack instance.
|
||||
*
|
||||
* @param title
|
||||
* @param album
|
||||
* @param artist
|
||||
* @param genre
|
||||
* @param source
|
||||
* @param image
|
||||
* @param trackNumber
|
||||
* @param totalTrackCount
|
||||
* @param duration
|
||||
*/
|
||||
public MusicTrack(String title, String album, String artist, String genre, String source,
|
||||
String image, int trackNumber, int totalTrackCount, int duration) {
|
||||
this.mTitle = title;
|
||||
this.mAlbum = album;
|
||||
this.mArtist = artist;
|
||||
this.mGenre = genre;
|
||||
this.mSource = source;
|
||||
this.mImage = image;
|
||||
this.mTrackNumber = trackNumber;
|
||||
this.mTotalTrackCount = totalTrackCount;
|
||||
this.mDuration = duration;
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return mTitle;
|
||||
}
|
||||
|
||||
public void setTitle(String mTitle) {
|
||||
this.mTitle = mTitle;
|
||||
}
|
||||
|
||||
public String getAlbum() {
|
||||
return mAlbum;
|
||||
}
|
||||
|
||||
public void setAlbum(String mAlbum) {
|
||||
this.mAlbum = mAlbum;
|
||||
}
|
||||
|
||||
public String getArtist() {
|
||||
return mArtist;
|
||||
}
|
||||
|
||||
public void setArtist(String mArtist) {
|
||||
this.mArtist = mArtist;
|
||||
}
|
||||
|
||||
public String getGenre() {
|
||||
return mGenre;
|
||||
}
|
||||
|
||||
public void setGenre(String mGenre) {
|
||||
this.mGenre = mGenre;
|
||||
}
|
||||
|
||||
public String getSource() {
|
||||
return mSource;
|
||||
}
|
||||
|
||||
public void setSource(String mSource) {
|
||||
this.mSource = mSource;
|
||||
}
|
||||
|
||||
public String getImage() {
|
||||
return mImage;
|
||||
}
|
||||
|
||||
public void setImage(String mImage) {
|
||||
this.mImage = mImage;
|
||||
}
|
||||
|
||||
public int getTrackNumber() {
|
||||
return mTrackNumber;
|
||||
}
|
||||
|
||||
public void setTrackNumber(int mTrackNumber) {
|
||||
this.mTrackNumber = mTrackNumber;
|
||||
}
|
||||
|
||||
public int getTotalTrackCount() {
|
||||
return mTotalTrackCount;
|
||||
}
|
||||
|
||||
public void setTotalTrackCount(int mTotalTrackCount) {
|
||||
this.mTotalTrackCount = mTotalTrackCount;
|
||||
}
|
||||
|
||||
public int getDuration() {
|
||||
return mDuration;
|
||||
}
|
||||
|
||||
public void setDuration(int mDuration) {
|
||||
this.mDuration = mDuration;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return mTitle;
|
||||
}
|
||||
|
||||
}
|
||||