Add preliminary support for Android Auto by changing PlaybackService to extend MediaBrowserServiceCompat. Allows playback from queue and basic control using Android Auto.

This commit is contained in:
Meir Schwarz 2016-06-29 16:09:55 -07:00
parent d295614933
commit bfe7cadd15
7 changed files with 129 additions and 8 deletions

View File

@ -37,6 +37,8 @@
android:backupAgent=".core.backup.OpmlBackupAgent"
android:restoreAnyVersion="true"
android:logo="@drawable/ic_launcher">
<meta-data android:name="com.google.android.gms.car.notification.SmallIcon"
android:resource="@drawable/ic_notification" />
<meta-data
android:name="com.google.android.backup.api_key"
android:value="AEdPqrEAAAAI3a05VToCTlqBymJrbFGaKQMvF-bBAuLsOdavBA"/>
@ -342,6 +344,10 @@
<meta-data
android:name="de.danoeh.antennapod.core.glide.ApGlideModule"
android:value="GlideModule" />
<meta-data
android:name="com.google.android.gms.car.application"
android:resource="@xml/automotive_app_desc"/>
</application>
</manifest>

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<automotiveApp>
<uses name="media"/>
</automotiveApp>

View File

@ -17,10 +17,14 @@
<service
android:name=".service.download.DownloadService"
android:enabled="true" />
<service
android:name=".service.playback.PlaybackService"
<service android:name=".service.playback.PlaybackService"
android:label="@string/app_name"
android:enabled="true"
android:exported="true" />
android:exported="true">
<intent-filter>
<action android:name="android.media.browse.MediaBrowserService"/>
</intent-filter>
</service>
<service
android:name=".service.GpodnetSyncService"
android:enabled="true" />

View File

@ -7,6 +7,8 @@ import android.media.MediaMetadataRetriever;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.annotation.Nullable;
import android.support.v4.media.MediaBrowserCompat;
import android.support.v4.media.MediaDescriptionCompat;
import java.util.Date;
import java.util.List;
@ -147,6 +149,21 @@ public class FeedMedia extends FeedFile implements Playable {
}
}
/**
* Returns a MediaItem representing the FeedMedia object.
* This is used by the MediaBrowserService
*/
public MediaBrowserCompat.MediaItem getMediaItem() {
Playable p = this;
MediaDescriptionCompat description = new MediaDescriptionCompat.Builder()
.setMediaId(String.valueOf(id))
.setTitle(p.getEpisodeTitle())
.setDescription(p.getFeedTitle())
.setSubtitle(p.getFeedTitle())
.build();
return new MediaBrowserCompat.MediaItem(description, MediaBrowserCompat.MediaItem.FLAG_PLAYABLE);
}
/**
* Uses mimetype to determine the type of media.
*/

View File

@ -17,10 +17,15 @@ import android.media.AudioManager;
import android.media.MediaPlayer;
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
import android.os.Vibrator;
import android.preference.PreferenceManager;
import android.support.annotation.NonNull;
import android.support.annotation.StringRes;
import android.support.v4.media.MediaBrowserCompat;
import android.support.v4.media.MediaBrowserServiceCompat;
import android.support.v4.media.MediaDescriptionCompat;
import android.support.v4.media.MediaMetadataCompat;
import android.support.v4.media.session.MediaSessionCompat;
import android.support.v4.media.session.PlaybackStateCompat;
@ -37,6 +42,7 @@ import com.bumptech.glide.Glide;
import com.bumptech.glide.request.target.Target;
import java.util.List;
import java.util.ArrayList;
import de.danoeh.antennapod.core.ClientConfig;
import de.danoeh.antennapod.core.R;
@ -51,6 +57,7 @@ import de.danoeh.antennapod.core.preferences.GpodnetPreferences;
import de.danoeh.antennapod.core.preferences.PlaybackPreferences;
import de.danoeh.antennapod.core.preferences.UserPreferences;
import de.danoeh.antennapod.core.receiver.MediaButtonReceiver;
import de.danoeh.antennapod.core.storage.DBReader;
import de.danoeh.antennapod.core.storage.DBTasks;
import de.danoeh.antennapod.core.storage.DBWriter;
import de.danoeh.antennapod.core.util.IntList;
@ -62,7 +69,7 @@ import de.danoeh.antennapod.core.util.playback.Playable;
/**
* Controls the MediaPlayer that plays a FeedMedia-file
*/
public class PlaybackService extends Service {
public class PlaybackService extends MediaBrowserServiceCompat {
public static final String FORCE_WIDGET_UPDATE = "de.danoeh.antennapod.FORCE_WIDGET_UPDATE";
public static final String STOP_WIDGET_UPDATE = "de.danoeh.antennapod.STOP_WIDGET_UPDATE";
/**
@ -194,7 +201,7 @@ public class PlaybackService extends Service {
private PlaybackServiceFlavorHelper flavorHelper;
/**
* Only used for Lollipop notifications.
* Used for Lollipop notifications, Android Wear, and Android Auto.
*/
private MediaSessionCompat mediaSession;
@ -202,8 +209,6 @@ public class PlaybackService extends Service {
private static volatile MediaType currentMediaType = MediaType.UNKNOWN;
private final IBinder mBinder = new LocalBinder();
public class LocalBinder extends Binder {
public PlaybackService getService() {
return PlaybackService.this;
@ -248,6 +253,8 @@ public class PlaybackService extends Service {
Log.d(TAG, "Service created.");
isRunning = true;
registerReceiver(autoStateUpdated, new IntentFilter(
"com.google.android.gms.car.media.STATUS"));
registerReceiver(headsetDisconnected, new IntentFilter(
Intent.ACTION_HEADSET_PLUG));
registerReceiver(shutdownReceiver, new IntentFilter(
@ -277,6 +284,7 @@ public class PlaybackService extends Service {
PendingIntent buttonReceiverIntent = PendingIntent.getBroadcast(this, 0, mediaButtonIntent, PendingIntent.FLAG_UPDATE_CURRENT);
mediaSession = new MediaSessionCompat(getApplicationContext(), TAG, eventReceiver, buttonReceiverIntent);
setSessionToken(mediaSession.getSessionToken());
try {
mediaSession.setCallback(sessionCallback);
@ -290,6 +298,16 @@ public class PlaybackService extends Service {
npe.printStackTrace();
}
List<MediaSessionCompat.QueueItem> queueItems = new ArrayList<>();
try {
for (FeedItem feedItem: taskManager.getQueue()) {
queueItems.add(new MediaSessionCompat.QueueItem(feedItem.getMedia().getMediaItem().getDescription(), feedItem.getId()));
}
mediaSession.setQueue(queueItems);
} catch (InterruptedException e) {
e.printStackTrace();
}
flavorHelper.initializeMediaPlayer(PlaybackService.this);
mediaSession.setActive(true);
@ -308,6 +326,7 @@ public class PlaybackService extends Service {
if (mediaSession != null) {
mediaSession.release();
}
unregisterReceiver(autoStateUpdated);
unregisterReceiver(headsetDisconnected);
unregisterReceiver(shutdownReceiver);
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
@ -323,10 +342,49 @@ public class PlaybackService extends Service {
taskManager.shutdown();
}
@Override
public BrowserRoot onGetRoot(String clientPackageName, int clientUid, Bundle rootHints) {
Log.d(TAG, "OnGetRoot: clientPackageName=" + clientPackageName +
"; clientUid=" + clientUid + " ; rootHints=" + rootHints);
return new BrowserRoot(
getResources().getString(R.string.app_name), // Name visible in Android Auto
null); // Bundle of optional extras
}
private MediaBrowserCompat.MediaItem createBrowsableMediaItemForRoot() {
MediaDescriptionCompat description = new MediaDescriptionCompat.Builder()
.setMediaId(getResources().getString(R.string.queue_label))
.setTitle(getResources().getString(R.string.queue_label))
.build();
return new MediaBrowserCompat.MediaItem(description,
MediaBrowserCompat.MediaItem.FLAG_BROWSABLE);
}
@Override
public void onLoadChildren(String parentId,
Result<List<MediaBrowserCompat.MediaItem>> result) {
Log.d(TAG, "OnLoadChildren: parentMediaId=" + parentId);
List<MediaBrowserCompat.MediaItem> mediaItems = new ArrayList<MediaBrowserCompat.MediaItem>();
if (parentId.equals(getResources().getString(R.string.app_name))) {
// Root List
mediaItems.add(createBrowsableMediaItemForRoot());
} else if (parentId.equals(getResources().getString(R.string.queue_label))){
// Child List
try {
for (FeedItem feedItem: taskManager.getQueue()) {
mediaItems.add(feedItem.getMedia().getMediaItem());
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
result.sendResult(mediaItems);
}
@Override
public IBinder onBind(Intent intent) {
Log.d(TAG, "Received onBind event");
return mBinder;
return super.onBind(intent);
}
@Override
@ -1221,6 +1279,22 @@ public class PlaybackService extends Service {
}
}
private final BroadcastReceiver autoStateUpdated = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String status = intent.getStringExtra("media_connection_status");
boolean isConnectedToCar = "media_connected".equals(status);
Log.d(TAG, "Received Auto Connection update: " + status);
if(!isConnectedToCar) {
Log.d(TAG, "Car was unplugged during playback.");
pauseIfPauseOnDisconnect();
} else {
mediaPlayer.setStartWhenPrepared(true);
mediaPlayer.prepare();
}
}
};
/**
* Pauses playback when the headset is disconnected and the preference is
* set
@ -1499,6 +1573,21 @@ public class PlaybackService extends Service {
}
}
@Override
public void onPlayFromMediaId(String mediaId, Bundle extras) {
Log.d(TAG, "onPlayFromMediaId: mediaId: " + mediaId + " extras: " + extras.toString());
FeedMedia p = DBReader.getFeedMedia(Long.parseLong(mediaId));
if(p != null) {
mediaPlayer.playMediaObject(p, !p.localFileAvailable(), true, true);
}
}
@Override
public void onPlayFromSearch (String query, Bundle extras) {
//Until we parse the query just play from queue
onPlay();
}
@Override
public void onPause() {
Log.d(TAG, "onPause()");

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

@ -4,6 +4,7 @@
tools:ignore="MissingTranslation">
<!-- Activitiy and fragment titles -->
<string name="app_name" translate="false">AntennaPod</string>
<string name="feeds_label">Feeds</string>
<string name="statistics_label">Statistics</string>
<string name="add_feed_label">Add Podcast</string>