Merge pull request #2053 from MeirSD/develop
Add preliminary support for Android Auto by changing PlaybackService …
This commit is contained in:
commit
a806d58966
@ -37,6 +37,8 @@
|
|||||||
android:backupAgent=".core.backup.OpmlBackupAgent"
|
android:backupAgent=".core.backup.OpmlBackupAgent"
|
||||||
android:restoreAnyVersion="true"
|
android:restoreAnyVersion="true"
|
||||||
android:logo="@drawable/ic_launcher">
|
android:logo="@drawable/ic_launcher">
|
||||||
|
<meta-data android:name="com.google.android.gms.car.notification.SmallIcon"
|
||||||
|
android:resource="@drawable/ic_notification" />
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="com.google.android.backup.api_key"
|
android:name="com.google.android.backup.api_key"
|
||||||
android:value="AEdPqrEAAAAI3a05VToCTlqBymJrbFGaKQMvF-bBAuLsOdavBA"/>
|
android:value="AEdPqrEAAAAI3a05VToCTlqBymJrbFGaKQMvF-bBAuLsOdavBA"/>
|
||||||
@ -342,6 +344,10 @@
|
|||||||
<meta-data
|
<meta-data
|
||||||
android:name="de.danoeh.antennapod.core.glide.ApGlideModule"
|
android:name="de.danoeh.antennapod.core.glide.ApGlideModule"
|
||||||
android:value="GlideModule" />
|
android:value="GlideModule" />
|
||||||
|
|
||||||
|
<meta-data
|
||||||
|
android:name="com.google.android.gms.car.application"
|
||||||
|
android:resource="@xml/automotive_app_desc"/>
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
|
4
app/src/main/res/xml/automotive_app_desc.xml
Normal file
4
app/src/main/res/xml/automotive_app_desc.xml
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<automotiveApp>
|
||||||
|
<uses name="media"/>
|
||||||
|
</automotiveApp>
|
@ -17,10 +17,14 @@
|
|||||||
<service
|
<service
|
||||||
android:name=".service.download.DownloadService"
|
android:name=".service.download.DownloadService"
|
||||||
android:enabled="true" />
|
android:enabled="true" />
|
||||||
<service
|
<service android:name=".service.playback.PlaybackService"
|
||||||
android:name=".service.playback.PlaybackService"
|
android:label="@string/app_name"
|
||||||
android:enabled="true"
|
android:enabled="true"
|
||||||
android:exported="true" />
|
android:exported="true">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.media.browse.MediaBrowserService"/>
|
||||||
|
</intent-filter>
|
||||||
|
</service>
|
||||||
<service
|
<service
|
||||||
android:name=".service.GpodnetSyncService"
|
android:name=".service.GpodnetSyncService"
|
||||||
android:enabled="true" />
|
android:enabled="true" />
|
||||||
|
@ -7,6 +7,8 @@ import android.media.MediaMetadataRetriever;
|
|||||||
import android.os.Parcel;
|
import android.os.Parcel;
|
||||||
import android.os.Parcelable;
|
import android.os.Parcelable;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
|
import android.support.v4.media.MediaBrowserCompat;
|
||||||
|
import android.support.v4.media.MediaDescriptionCompat;
|
||||||
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.List;
|
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.
|
* Uses mimetype to determine the type of media.
|
||||||
*/
|
*/
|
||||||
|
@ -17,10 +17,15 @@ import android.media.AudioManager;
|
|||||||
import android.media.MediaPlayer;
|
import android.media.MediaPlayer;
|
||||||
import android.os.Binder;
|
import android.os.Binder;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
|
import android.os.Bundle;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
import android.os.Vibrator;
|
import android.os.Vibrator;
|
||||||
import android.preference.PreferenceManager;
|
import android.preference.PreferenceManager;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
import android.support.annotation.StringRes;
|
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.MediaMetadataCompat;
|
||||||
import android.support.v4.media.session.MediaSessionCompat;
|
import android.support.v4.media.session.MediaSessionCompat;
|
||||||
import android.support.v4.media.session.PlaybackStateCompat;
|
import android.support.v4.media.session.PlaybackStateCompat;
|
||||||
@ -37,6 +42,7 @@ import com.bumptech.glide.Glide;
|
|||||||
import com.bumptech.glide.request.target.Target;
|
import com.bumptech.glide.request.target.Target;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
import de.danoeh.antennapod.core.ClientConfig;
|
import de.danoeh.antennapod.core.ClientConfig;
|
||||||
import de.danoeh.antennapod.core.R;
|
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.PlaybackPreferences;
|
||||||
import de.danoeh.antennapod.core.preferences.UserPreferences;
|
import de.danoeh.antennapod.core.preferences.UserPreferences;
|
||||||
import de.danoeh.antennapod.core.receiver.MediaButtonReceiver;
|
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.DBTasks;
|
||||||
import de.danoeh.antennapod.core.storage.DBWriter;
|
import de.danoeh.antennapod.core.storage.DBWriter;
|
||||||
import de.danoeh.antennapod.core.util.IntList;
|
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
|
* 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 FORCE_WIDGET_UPDATE = "de.danoeh.antennapod.FORCE_WIDGET_UPDATE";
|
||||||
public static final String STOP_WIDGET_UPDATE = "de.danoeh.antennapod.STOP_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;
|
private PlaybackServiceFlavorHelper flavorHelper;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Only used for Lollipop notifications.
|
* Used for Lollipop notifications, Android Wear, and Android Auto.
|
||||||
*/
|
*/
|
||||||
private MediaSessionCompat mediaSession;
|
private MediaSessionCompat mediaSession;
|
||||||
|
|
||||||
@ -202,8 +209,6 @@ public class PlaybackService extends Service {
|
|||||||
|
|
||||||
private static volatile MediaType currentMediaType = MediaType.UNKNOWN;
|
private static volatile MediaType currentMediaType = MediaType.UNKNOWN;
|
||||||
|
|
||||||
private final IBinder mBinder = new LocalBinder();
|
|
||||||
|
|
||||||
public class LocalBinder extends Binder {
|
public class LocalBinder extends Binder {
|
||||||
public PlaybackService getService() {
|
public PlaybackService getService() {
|
||||||
return PlaybackService.this;
|
return PlaybackService.this;
|
||||||
@ -248,6 +253,8 @@ public class PlaybackService extends Service {
|
|||||||
Log.d(TAG, "Service created.");
|
Log.d(TAG, "Service created.");
|
||||||
isRunning = true;
|
isRunning = true;
|
||||||
|
|
||||||
|
registerReceiver(autoStateUpdated, new IntentFilter(
|
||||||
|
"com.google.android.gms.car.media.STATUS"));
|
||||||
registerReceiver(headsetDisconnected, new IntentFilter(
|
registerReceiver(headsetDisconnected, new IntentFilter(
|
||||||
Intent.ACTION_HEADSET_PLUG));
|
Intent.ACTION_HEADSET_PLUG));
|
||||||
registerReceiver(shutdownReceiver, new IntentFilter(
|
registerReceiver(shutdownReceiver, new IntentFilter(
|
||||||
@ -277,6 +284,7 @@ public class PlaybackService extends Service {
|
|||||||
PendingIntent buttonReceiverIntent = PendingIntent.getBroadcast(this, 0, mediaButtonIntent, PendingIntent.FLAG_UPDATE_CURRENT);
|
PendingIntent buttonReceiverIntent = PendingIntent.getBroadcast(this, 0, mediaButtonIntent, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||||
|
|
||||||
mediaSession = new MediaSessionCompat(getApplicationContext(), TAG, eventReceiver, buttonReceiverIntent);
|
mediaSession = new MediaSessionCompat(getApplicationContext(), TAG, eventReceiver, buttonReceiverIntent);
|
||||||
|
setSessionToken(mediaSession.getSessionToken());
|
||||||
|
|
||||||
try {
|
try {
|
||||||
mediaSession.setCallback(sessionCallback);
|
mediaSession.setCallback(sessionCallback);
|
||||||
@ -290,6 +298,16 @@ public class PlaybackService extends Service {
|
|||||||
npe.printStackTrace();
|
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);
|
flavorHelper.initializeMediaPlayer(PlaybackService.this);
|
||||||
|
|
||||||
mediaSession.setActive(true);
|
mediaSession.setActive(true);
|
||||||
@ -308,6 +326,7 @@ public class PlaybackService extends Service {
|
|||||||
if (mediaSession != null) {
|
if (mediaSession != null) {
|
||||||
mediaSession.release();
|
mediaSession.release();
|
||||||
}
|
}
|
||||||
|
unregisterReceiver(autoStateUpdated);
|
||||||
unregisterReceiver(headsetDisconnected);
|
unregisterReceiver(headsetDisconnected);
|
||||||
unregisterReceiver(shutdownReceiver);
|
unregisterReceiver(shutdownReceiver);
|
||||||
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
|
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
|
||||||
@ -323,10 +342,49 @@ public class PlaybackService extends Service {
|
|||||||
taskManager.shutdown();
|
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
|
@Override
|
||||||
public IBinder onBind(Intent intent) {
|
public IBinder onBind(Intent intent) {
|
||||||
Log.d(TAG, "Received onBind event");
|
Log.d(TAG, "Received onBind event");
|
||||||
return mBinder;
|
return super.onBind(intent);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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
|
* Pauses playback when the headset is disconnected and the preference is
|
||||||
* set
|
* 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
|
@Override
|
||||||
public void onPause() {
|
public void onPause() {
|
||||||
Log.d(TAG, "onPause()");
|
Log.d(TAG, "onPause()");
|
||||||
|
BIN
core/src/main/res/drawable/ic_notification.png
Normal file
BIN
core/src/main/res/drawable/ic_notification.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.8 KiB |
@ -4,6 +4,7 @@
|
|||||||
tools:ignore="MissingTranslation">
|
tools:ignore="MissingTranslation">
|
||||||
|
|
||||||
<!-- Activitiy and fragment titles -->
|
<!-- Activitiy and fragment titles -->
|
||||||
|
<string name="app_name" translate="false">AntennaPod</string>
|
||||||
<string name="feeds_label">Feeds</string>
|
<string name="feeds_label">Feeds</string>
|
||||||
<string name="statistics_label">Statistics</string>
|
<string name="statistics_label">Statistics</string>
|
||||||
<string name="add_feed_label">Add Podcast</string>
|
<string name="add_feed_label">Add Podcast</string>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user