diff options
| author | doc HD <doc.divxm@gmail.com> | 2017-11-10 10:55:38 +0300 |
|---|---|---|
| committer | doc HD <doc.divxm@gmail.com> | 2017-11-10 10:55:38 +0300 |
| commit | 8871a396f1982f14be71f28f1f80eee53aeea913 (patch) | |
| tree | 21d281d1a523a2a17b66b4eb4debf5dce9ab423f | |
| parent | 9ca6e09802abac390af31f508461c27b297e5abe (diff) | |
| parent | 2fd7f1b5cccac229db45930a6cdcaa82e093d033 (diff) | |
Merge tag 'android-8.0.0_r30' into HEADo8.0
Android 8.0.0 release 30
| -rw-r--r-- | res/values-pt-rPT/strings.xml | 4 | ||||
| -rw-r--r-- | src/com/android/bluetooth/avrcp/AddressedMediaPlayer.java | 52 | ||||
| -rw-r--r-- | src/com/android/bluetooth/avrcp/Avrcp.java | 315 | ||||
| -rw-r--r-- | src/com/android/bluetooth/avrcp/BrowsedMediaPlayer.java | 88 |
4 files changed, 268 insertions, 191 deletions
diff --git a/res/values-pt-rPT/strings.xml b/res/values-pt-rPT/strings.xml index 7dc2b31b..1c8cf2eb 100644 --- a/res/values-pt-rPT/strings.xml +++ b/res/values-pt-rPT/strings.xml @@ -111,12 +111,12 @@ <string name="outbound_noti_title" msgid="8051906709452260849">"Partilha por Bluetooth: ficheiros enviados"</string> <string name="inbound_noti_title" msgid="4143352641953027595">"Partilha por Bluetooth: ficheiros recebidos"</string> <plurals name="noti_caption_unsuccessful" formatted="false" msgid="2020750076679526122"> - <item quantity="other"><xliff:g id="UNSUCCESSFUL_NUMBER_1">%1$d</xliff:g> sem êxito.</item> <item quantity="one"><xliff:g id="UNSUCCESSFUL_NUMBER_0">%1$d</xliff:g> sem êxito.</item> + <item quantity="other"><xliff:g id="UNSUCCESSFUL_NUMBER_1">%1$d</xliff:g> sem êxito.</item> </plurals> <plurals name="noti_caption_success" formatted="false" msgid="1572472450257645181"> - <item quantity="other"><xliff:g id="SUCCESSFUL_NUMBER_1">%1$d</xliff:g> com êxito, %2$s</item> <item quantity="one"><xliff:g id="SUCCESSFUL_NUMBER_0">%1$d</xliff:g> com êxito, %2$s</item> + <item quantity="other"><xliff:g id="SUCCESSFUL_NUMBER_1">%1$d</xliff:g> com êxito, %2$s</item> </plurals> <string name="transfer_menu_clear_all" msgid="790017462957873132">"Limpar lista"</string> <string name="transfer_menu_open" msgid="3368984869083107200">"Abrir"</string> diff --git a/src/com/android/bluetooth/avrcp/AddressedMediaPlayer.java b/src/com/android/bluetooth/avrcp/AddressedMediaPlayer.java index 8c94027f..0835aef3 100644 --- a/src/com/android/bluetooth/avrcp/AddressedMediaPlayer.java +++ b/src/com/android/bluetooth/avrcp/AddressedMediaPlayer.java @@ -48,20 +48,21 @@ public class AddressedMediaPlayer { static private final long SINGLE_QID = 1; static private final String UNKNOWN_TITLE = "(unknown)"; + static private final String GPM_BUNDLE_METADATA_KEY = + "com.google.android.music.mediasession.music_metadata"; + private AvrcpMediaRspInterface mMediaInterface; private @NonNull List<MediaSession.QueueItem> mNowPlayingList; private final List<MediaSession.QueueItem> mEmptyNowPlayingList; private long mLastTrackIdSent; - private boolean mNowPlayingListUpdated; public AddressedMediaPlayer(AvrcpMediaRspInterface mediaInterface) { mEmptyNowPlayingList = new ArrayList<MediaSession.QueueItem>(); mNowPlayingList = mEmptyNowPlayingList; mMediaInterface = mediaInterface; mLastTrackIdSent = MediaSession.QueueItem.UNKNOWN_ID; - mNowPlayingListUpdated = false; } void cleanup() { @@ -69,7 +70,6 @@ public class AddressedMediaPlayer { mNowPlayingList = mEmptyNowPlayingList; mMediaInterface = null; mLastTrackIdSent = MediaSession.QueueItem.UNKNOWN_ID; - mNowPlayingListUpdated = false; } /* get now playing list from addressed player */ @@ -81,7 +81,7 @@ public class AddressedMediaPlayer { mMediaInterface.folderItemsRsp(bdaddr, AvrcpConstants.RSP_NO_AVBL_PLAY, null); return; } - List<MediaSession.QueueItem> items = getNowPlayingList(mediaController); + List<MediaSession.QueueItem> items = updateNowPlayingList(mediaController); getFolderItemsFilterAttr(bdaddr, reqObj, items, AvrcpConstants.BTRC_SCOPE_NOW_PLAYING, reqObj.mStartItem, reqObj.mEndItem, mediaController); } @@ -91,7 +91,7 @@ public class AddressedMediaPlayer { @Nullable MediaController mediaController) { int status = AvrcpConstants.RSP_NO_ERROR; long mediaId = ByteBuffer.wrap(itemAttr.mUid).getLong(); - List<MediaSession.QueueItem> items = getNowPlayingList(mediaController); + List<MediaSession.QueueItem> items = updateNowPlayingList(mediaController); // NOTE: this is out-of-spec (AVRCP 1.6.1 sec 6.10.4.3, p90) but we answer it anyway // because some CTs ask for it. @@ -118,14 +118,10 @@ public class AddressedMediaPlayer { /* Refresh and get the queue of now playing. */ - private @NonNull List<MediaSession.QueueItem> getNowPlayingList( - @Nullable MediaController mediaController) { + @NonNull + List<MediaSession.QueueItem> updateNowPlayingList(@Nullable MediaController mediaController) { if (mediaController == null) return mEmptyNowPlayingList; List<MediaSession.QueueItem> items = mediaController.getQueue(); - if (items != null && !mNowPlayingListUpdated) { - mNowPlayingList = items; - return mNowPlayingList; - } if (items == null) { Log.i(TAG, "null queue from " + mediaController.getPackageName() + ", constructing single-item list"); @@ -137,18 +133,17 @@ public class AddressedMediaPlayer { items.add(current); } + if (!items.equals(mNowPlayingList)) sendNowPlayingListChanged(); mNowPlayingList = items; - if (mNowPlayingListUpdated) sendNowPlayingListChanged(); - return mNowPlayingList; } private void sendNowPlayingListChanged() { if (mMediaInterface == null) return; + if (DEBUG) Log.d(TAG, "sendNowPlayingListChanged()"); mMediaInterface.uidsChangedRsp(AvrcpConstants.NOTIFICATION_TYPE_CHANGED); mMediaInterface.nowPlayingChangedRsp(AvrcpConstants.NOTIFICATION_TYPE_CHANGED); - mNowPlayingListUpdated = false; } /* Constructs a queue item representing the current playing metadata from an @@ -186,6 +181,7 @@ public class AddressedMediaPlayer { private Bundle fillBundle(MediaMetadata metadata, Bundle currentExtras) { if (metadata == null) { + Log.i(TAG, "fillBundle: metadata is null"); return currentExtras; } @@ -207,15 +203,10 @@ public class AddressedMediaPlayer { return bundle; } - void updateNowPlayingList(@Nullable MediaController mediaController) { - mNowPlayingListUpdated = true; - getNowPlayingList(mediaController); - } - /* Instructs media player to play particular media item */ void playItem(byte[] bdaddr, byte[] uid, @Nullable MediaController mediaController) { long qid = ByteBuffer.wrap(uid).getLong(); - List<MediaSession.QueueItem> items = getNowPlayingList(mediaController); + List<MediaSession.QueueItem> items = updateNowPlayingList(mediaController); if (mediaController == null) { Log.e(TAG, "No mediaController when PlayItem " + qid + " requested"); @@ -246,7 +237,7 @@ public class AddressedMediaPlayer { } void getTotalNumOfItems(byte[] bdaddr, @Nullable MediaController mediaController) { - List<MediaSession.QueueItem> items = getNowPlayingList(mediaController); + List<MediaSession.QueueItem> items = updateNowPlayingList(mediaController); if (DEBUG) Log.d(TAG, "getTotalNumOfItems: " + items.size() + " items."); mMediaInterface.getTotalNumOfItemsRsp(bdaddr, AvrcpConstants.RSP_NO_ERROR, 0, items.size()); } @@ -256,7 +247,6 @@ public class AddressedMediaPlayer { long qid = getActiveQueueItemId(mediaController); byte[] track = ByteBuffer.allocate(AvrcpConstants.UID_SIZE).putLong(qid).array(); // The nowPlayingList changed: the new list has the full data for the current item - if (type == AvrcpConstants.NOTIFICATION_TYPE_CHANGED) sendNowPlayingListChanged(); mMediaInterface.trackChangedRsp(type, track); mLastTrackIdSent = qid; } @@ -391,10 +381,22 @@ public class AddressedMediaPlayer { MediaDescription desc = item.getDescription(); Bundle extras = desc.getExtras(); boolean isCurrentTrack = item.getQueueId() == getActiveQueueItemId(mediaController); + MediaMetadata data = null; if (isCurrentTrack) { if (DEBUG) Log.d(TAG, "getAttrValue: item is active, using current data"); - extras = fillBundle(mediaController.getMetadata(), extras); + data = mediaController.getMetadata(); + if (data == null) + Log.e(TAG, "getMetadata didn't give us any metadata for the current track"); + } + + if (data == null) { + // TODO: This code can be removed when b/63117921 is resolved + data = (MediaMetadata) extras.get(GPM_BUNDLE_METADATA_KEY); + extras = null; // We no longer need the data in here } + + extras = fillBundle(data, extras); + if (DEBUG) Log.d(TAG, "getAttrValue: item " + item + " : " + desc); switch (attr) { case AvrcpConstants.ATTRID_TITLE: @@ -511,7 +513,9 @@ public class AddressedMediaPlayer { private long getActiveQueueItemId(@Nullable MediaController controller) { if (controller == null) return MediaSession.QueueItem.UNKNOWN_ID; PlaybackState state = controller.getPlaybackState(); - if (state == null) return MediaSession.QueueItem.UNKNOWN_ID; + if (state == null || state.getState() == PlaybackState.STATE_BUFFERING + || state.getState() == PlaybackState.STATE_NONE) + return MediaSession.QueueItem.UNKNOWN_ID; long qid = state.getActiveQueueItemId(); if (qid != MediaSession.QueueItem.UNKNOWN_ID) return qid; // Check if we're presenting a "one item queue" diff --git a/src/com/android/bluetooth/avrcp/Avrcp.java b/src/com/android/bluetooth/avrcp/Avrcp.java index 712b2a86..61d7adf0 100644 --- a/src/com/android/bluetooth/avrcp/Avrcp.java +++ b/src/com/android/bluetooth/avrcp/Avrcp.java @@ -57,9 +57,11 @@ import com.android.bluetooth.Utils; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.SortedMap; import java.util.TreeMap; @@ -86,11 +88,12 @@ public final class Avrcp { private @NonNull PlaybackState mCurrentPlayState; private int mA2dpState; private int mPlayStatusChangedNT; - private int mReportedPlayStatus; + private byte mReportedPlayStatus; private int mTrackChangedNT; private int mPlayPosChangedNT; private int mAddrPlayerChangedNT; private int mReportedPlayerID; + private int mNowPlayingListChangedNT; private long mPlaybackIntervalMs; private long mLastReportedPosition; private long mNextPosMs; @@ -99,7 +102,6 @@ public final class Avrcp { private int mRemoteVolume; private int mLastRemoteVolume; private int mInitialRemoteVolume; - private BrowsablePlayerListBuilder mBrowsableListBuilder; /* Local volume in audio index 0-15 */ private int mLocalVolume; @@ -162,10 +164,8 @@ public final class Avrcp { private static final int MSG_ABS_VOL_TIMEOUT = 17; private static final int MSG_SET_A2DP_AUDIO_STATE = 18; private static final int MSG_NOW_PLAYING_CHANGED_RSP = 19; - private static final int MSG_UPDATE_MEDIA = 20; private static final int CMD_TIMEOUT_DELAY = 2000; - private static final int MEDIA_DWELL_TIME = 1000; private static final int MAX_ERROR_RETRY_TIMES = 6; private static final int AVRCP_MAX_VOL = 127; private static final int AVRCP_BASE_VOLUME_STEP = 1; @@ -244,6 +244,7 @@ public final class Avrcp { mTrackChangedNT = AvrcpConstants.NOTIFICATION_TYPE_CHANGED; mPlayPosChangedNT = AvrcpConstants.NOTIFICATION_TYPE_CHANGED; mAddrPlayerChangedNT = AvrcpConstants.NOTIFICATION_TYPE_CHANGED; + mNowPlayingListChangedNT = AvrcpConstants.NOTIFICATION_TYPE_CHANGED; mPlaybackIntervalMs = 0L; mLastReportedPosition = -1; mNextPosMs = -1; @@ -280,8 +281,6 @@ public final class Avrcp { mAbsVolThreshold = resources.getInteger(R.integer.a2dp_absolute_volume_initial_threshold); } - mBrowsableListBuilder = new BrowsablePlayerListBuilder(); - // Register for package removal intent broadcasts for media button receiver persistence IntentFilter pkgFilter = new IntentFilter(); pkgFilter.addAction(Intent.ACTION_PACKAGE_REMOVED); @@ -292,7 +291,7 @@ public final class Avrcp { context.registerReceiver(mAvrcpReceiver, pkgFilter); IntentFilter bootFilter = new IntentFilter(); - bootFilter.addAction(Intent.ACTION_BOOT_COMPLETED); + bootFilter.addAction(Intent.ACTION_USER_UNLOCKED); context.registerReceiver(mBootReceiver, bootFilter); } @@ -328,7 +327,7 @@ public final class Avrcp { if (manager == null || manager.isUserUnlocked()) { if (DEBUG) Log.d(TAG, "User already unlocked, initializing player lists"); // initialize browsable player list and build media player list - mBrowsableListBuilder.start(); + buildBrowsablePlayerList(); } } @@ -357,7 +356,6 @@ public final class Avrcp { mContext.unregisterReceiver(mAvrcpReceiver); mContext.unregisterReceiver(mBootReceiver); - mBrowsableListBuilder.cleanup(); mAddressedMediaPlayer.cleanup(); mAvrcpBrowseManager.cleanup(); } @@ -373,12 +371,13 @@ public final class Avrcp { @Override public void onMetadataChanged(MediaMetadata metadata) { if (DEBUG) Log.v(TAG, "onMetadataChanged"); - scheduleMediaUpdate(); + updateCurrentMediaState(false); } @Override public synchronized void onPlaybackStateChanged(PlaybackState state) { if (DEBUG) Log.v(TAG, "onPlaybackStateChanged: state " + state.toString()); - scheduleMediaUpdate(); + + updateCurrentMediaState(false); } @Override @@ -478,7 +477,7 @@ public final class Avrcp { case MSG_NOW_PLAYING_CHANGED_RSP: if (DEBUG) Log.v(TAG, "MSG_NOW_PLAYING_CHANGED_RSP"); removeMessages(MSG_NOW_PLAYING_CHANGED_RSP); - mAddressedMediaPlayer.updateNowPlayingList(mMediaController); + updateCurrentMediaState(false); break; case MSG_PLAY_INTERVAL_TIMEOUT: @@ -692,7 +691,7 @@ public final class Avrcp { case MSG_SET_A2DP_AUDIO_STATE: if (DEBUG) Log.v(TAG, "MSG_SET_A2DP_AUDIO_STATE:" + msg.arg1); mA2dpState = msg.arg1; - scheduleMediaUpdate(); + updateCurrentMediaState(false); break; case MSG_NATIVE_REQ_GET_FOLDER_ITEMS: { @@ -778,13 +777,6 @@ public final class Avrcp { handlePassthroughCmd(msg.arg1, msg.arg2); break; - case MSG_UPDATE_MEDIA: - if (DEBUG) Log.v(TAG, "MSG_UPDATE_MEDIA"); - // Throttle to once per MEDIA_DWELL_TIME - removeMessages(MSG_UPDATE_MEDIA); - updateCurrentMediaState(false); - break; - default: Log.e(TAG, "unknown message! msg.what=" + msg.what); break; @@ -834,10 +826,6 @@ public final class Avrcp { if (newState != null) mCurrentPlayState = newState; - if (mPlayStatusChangedNT == AvrcpConstants.NOTIFICATION_TYPE_INTERIM - && (mReportedPlayStatus != newPlayStatus)) { - sendPlaybackStatus(AvrcpConstants.NOTIFICATION_TYPE_CHANGED, newPlayStatus); - } return mCurrentPlayState; } @@ -974,11 +962,6 @@ public final class Avrcp { } } - private void scheduleMediaUpdate() { - Message msg = mHandler.obtainMessage(MSG_UPDATE_MEDIA); - mHandler.sendMessageDelayed(msg, MEDIA_DWELL_TIME); - } - private void updateCurrentMediaState(boolean registering) { // Only do player updates when we aren't registering for track changes. if (!registering) { @@ -986,9 +969,13 @@ public final class Avrcp { registerNotificationRspAvalPlayerChangedNative( AvrcpConstants.NOTIFICATION_TYPE_CHANGED); mAvailablePlayerViewChanged = false; + return; } if (mAddrPlayerChangedNT == AvrcpConstants.NOTIFICATION_TYPE_INTERIM && mReportedPlayerID != mCurrAddrPlayerID) { + registerNotificationRspAvalPlayerChangedNative( + AvrcpConstants.NOTIFICATION_TYPE_CHANGED); + mAvailablePlayerViewChanged = false; registerNotificationRspAddrPlayerChangedNative( AvrcpConstants.NOTIFICATION_TYPE_CHANGED, mCurrAddrPlayerID, sUIDCounter); mAddrPlayerChangedNT = AvrcpConstants.NOTIFICATION_TYPE_CHANGED; @@ -997,6 +984,8 @@ public final class Avrcp { mPlayStatusChangedNT = AvrcpConstants.NOTIFICATION_TYPE_CHANGED; mTrackChangedNT = AvrcpConstants.NOTIFICATION_TYPE_CHANGED; mPlayPosChangedNT = AvrcpConstants.NOTIFICATION_TYPE_CHANGED; + mNowPlayingListChangedNT = AvrcpConstants.NOTIFICATION_TYPE_CHANGED; + mAddressedMediaPlayer.updateNowPlayingList(mMediaController); // If the player changed, they need to re-request anything here again // so we can skip the rest of the update. return; @@ -1010,25 +999,63 @@ public final class Avrcp { if (mMediaController == null) { currentAttributes = new MediaAttributes(null); } else { - newState = mMediaController.getPlaybackState(); currentAttributes = new MediaAttributes(mMediaController.getMetadata()); } } - long newQueueId = MediaSession.QueueItem.UNKNOWN_ID; - if (newState != null) newQueueId = newState.getActiveQueueItemId(); - Log.v(TAG, "Media update: id " + mLastQueueId + "➡" + newQueueId + "? " - + currentAttributes.toRedactedString()); - // Notify track changed if: - // - The CT is registering for the notification - // - Queue ID is UNKNOWN and MediaMetadata is different - // - Queue ID is valid and different and MediaMetadata is different - if (registering || (((newQueueId == -1) || (newQueueId != mLastQueueId)) - && !currentAttributes.equals(mMediaAttributes))) { - sendTrackChangedRsp(registering); - mMediaAttributes = currentAttributes; - mLastQueueId = newQueueId; + byte newPlayStatus = getBluetoothPlayState(newState); + + if (newState.getState() != PlaybackState.STATE_BUFFERING + && newState.getState() != PlaybackState.STATE_NONE) { + long newQueueId = MediaSession.QueueItem.UNKNOWN_ID; + if (newState != null) newQueueId = newState.getActiveQueueItemId(); + Log.v(TAG, "Media update: id " + mLastQueueId + "➡" + newQueueId + "? " + + currentAttributes.toRedactedString() + " : " + + mMediaAttributes.toRedactedString()); + + // Dont send now playing list changed if the player doesn't support browsing + MediaPlayerInfo info = getAddressedPlayerInfo(); + if (info != null && info.isBrowseSupported()) { + Log.v(TAG, "Check if NowPlayingList is updated"); + mAddressedMediaPlayer.updateNowPlayingList(mMediaController); + } + + if ((newQueueId == -1 || newQueueId != mLastQueueId) + && currentAttributes.equals(mMediaAttributes) + && newPlayStatus == PLAYSTATUS_PLAYING + && mReportedPlayStatus == PLAYSTATUS_STOPPED) { + // Most carkits like seeing the track changed before the + // playback state changed, but some controllers are slow + // to update their metadata. Hold of on sending the playback state + // update until after we know the current metadata is up to date + // and track changed has been sent. This was seen on BMW carkits + Log.i(TAG, "Waiting for metadata update to send track changed"); + + return; + } + + // Notify track changed if: + // - The CT is registering for the notification + // - Queue ID is UNKNOWN and MediaMetadata is different + // - Queue ID is valid and different and MediaMetadata is different + if (registering || ((newQueueId == -1 || newQueueId != mLastQueueId) + && !currentAttributes.equals(mMediaAttributes))) { + Log.v(TAG, "Send track changed"); + mMediaAttributes = currentAttributes; + mLastQueueId = newQueueId; + sendTrackChangedRsp(registering); + } + } else { + Log.i(TAG, "Skipping update due to invalid playback state"); } + + // still send the updated play state if the playback state is none or buffering + Log.e(TAG, "play status change " + mReportedPlayStatus + "➡" + newPlayStatus); + if (mPlayStatusChangedNT == AvrcpConstants.NOTIFICATION_TYPE_INTERIM + && (mReportedPlayStatus != newPlayStatus)) { + sendPlaybackStatus(AvrcpConstants.NOTIFICATION_TYPE_CHANGED, newPlayStatus); + } + sendPlayPosNotificationRsp(false); } @@ -1063,14 +1090,13 @@ public final class Avrcp { case EVT_PLAY_STATUS_CHANGED: mPlayStatusChangedNT = AvrcpConstants.NOTIFICATION_TYPE_CHANGED; updatePlaybackState(); - sendPlaybackStatus(AvrcpConstants.NOTIFICATION_TYPE_INTERIM, - getBluetoothPlayState(mCurrentPlayState)); + sendPlaybackStatus(AvrcpConstants.NOTIFICATION_TYPE_INTERIM, mReportedPlayStatus); break; case EVT_TRACK_CHANGED: Log.v(TAG, "Track changed notification enabled"); mTrackChangedNT = AvrcpConstants.NOTIFICATION_TYPE_INTERIM; - updateCurrentMediaState(true); + sendTrackChangedRsp(true); break; case EVT_PLAY_POS_CHANGED: @@ -1105,6 +1131,7 @@ public final class Avrcp { case EVENT_NOW_PLAYING_CONTENT_CHANGED: if (DEBUG) Log.d(TAG, "Now Playing List changed notification enabled"); /* send interim response to remote device */ + mNowPlayingListChangedNT = AvrcpConstants.NOTIFICATION_TYPE_INTERIM; if (!registerNotificationRspNowPlayingChangedNative( AvrcpConstants.NOTIFICATION_TYPE_INTERIM)) { Log.e(TAG, "EVENT_NOW_PLAYING_CONTENT_CHANGED: " + @@ -1160,8 +1187,7 @@ public final class Avrcp { private boolean isPlayingState(@Nullable PlaybackState state) { if (state == null) return false; - return (state != null) && (state.getState() == PlaybackState.STATE_PLAYING) - || (state.getState() == PlaybackState.STATE_BUFFERING); + return (state != null) && (state.getState() == PlaybackState.STATE_PLAYING); } /** @@ -1390,10 +1416,10 @@ public final class Avrcp { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); - if (action.equals(Intent.ACTION_BOOT_COMPLETED)) { - if (DEBUG) Log.d(TAG, "Boot completed, initializing player lists"); + if (action.equals(Intent.ACTION_USER_UNLOCKED)) { + if (DEBUG) Log.d(TAG, "User unlocked, initializing player lists"); /* initializing media player's list */ - mBrowsableListBuilder.start(); + buildBrowsablePlayerList(); } } } @@ -1439,7 +1465,7 @@ public final class Avrcp { // new package has been added. if (isBrowsableListUpdated(packageName)) { // Rebuilding browsable players list - mBrowsableListBuilder.start(); + buildBrowsablePlayerList(); } } } @@ -1548,8 +1574,15 @@ public final class Avrcp { // checking for error cases if (mMediaPlayerInfoList.isEmpty()) { status = AvrcpConstants.RSP_NO_AVBL_PLAY; - Log.w(TAG, " No Available Players to set, sending response back "); + Log.w(TAG, "setBrowsedPlayer: No available players! "); } else { + // Workaround for broken controllers selecting ID 0 + // Seen at least on Ford, Chevrolet MyLink + if (selectedId == 0) { + Log.w(TAG, "setBrowsedPlayer: workaround invalid id 0"); + selectedId = mCurrAddrPlayerID; + } + // update current browse player id and start browsing service updateNewIds(mCurrAddrPlayerID, selectedId); String browsedPackage = getPackageName(selectedId); @@ -1582,9 +1615,15 @@ public final class Avrcp { @Override public void onActiveSessionsChanged( List<android.media.session.MediaController> newControllers) { + Set<String> updatedPackages = new HashSet<String>(); // Update the current players for (android.media.session.MediaController controller : newControllers) { + String packageName = controller.getPackageName(); + if (DEBUG) Log.v(TAG, "ActiveSession: " + MediaController.wrap(controller)); + // Only use the first (highest priority) controller from each package + if (updatedPackages.contains(packageName)) continue; addMediaPlayerController(controller); + updatedPackages.add(packageName); } if (newControllers.size() > 0 && getAddressedPlayerInfo() == null) { @@ -1592,7 +1631,7 @@ public final class Avrcp { Log.v(TAG, "No addressed player but active sessions, taking first."); setAddressedMediaSessionPackage(newControllers.get(0).getPackageName()); } - scheduleMediaUpdate(); + updateCurrentMediaState(false); } }; @@ -1602,13 +1641,17 @@ public final class Avrcp { updateCurrentController(0, mCurrBrowsePlayerID); return; } + if (packageName.equals("com.android.server.telecom")) { + Log.d(TAG, "Ignore addressed media session change to telecom"); + return; + } // No change. if (getPackageName(mCurrAddrPlayerID).equals(packageName)) return; if (DEBUG) Log.v(TAG, "Changing addressed media session to " + packageName); // If the player doesn't exist, we need to add it. if (getMediaPlayerInfo(packageName) == null) { addMediaPlayerPackage(packageName); - scheduleMediaUpdate(); + updateCurrentMediaState(false); } synchronized (mMediaPlayerInfoList) { for (Map.Entry<Integer, MediaPlayerInfo> entry : mMediaPlayerInfoList.entrySet()) { @@ -1616,7 +1659,7 @@ public final class Avrcp { int newAddrID = entry.getKey(); if (DEBUG) Log.v(TAG, "Set addressed #" + newAddrID + " " + entry.getValue()); updateCurrentController(newAddrID, mCurrBrowsePlayerID); - scheduleMediaUpdate(); + updateCurrentMediaState(false); return; } } @@ -1628,6 +1671,10 @@ public final class Avrcp { private void setActiveMediaSession(MediaSession.Token token) { android.media.session.MediaController activeController = new android.media.session.MediaController(mContext, token); + if (activeController.getPackageName().equals("com.android.server.telecom")) { + Log.d(TAG, "Ignore active media session change to telecom"); + return; + } if (DEBUG) Log.v(TAG, "Set active media session " + activeController.getPackageName()); addMediaPlayerController(activeController); setAddressedMediaSessionPackage(activeController.getPackageName()); @@ -1667,71 +1714,33 @@ public final class Avrcp { return browseServiceName; } - private class BrowsablePlayerListBuilder extends MediaBrowser.ConnectionCallback { - List<ResolveInfo> mWaiting; - BrowsePlayerInfo mCurrentPlayer; - MediaBrowser mCurrentBrowser; - boolean mPlayersChanged; - - public BrowsablePlayerListBuilder() {} - - public void start() { + void buildBrowsablePlayerList() { + synchronized (mBrowsePlayerInfoList) { mBrowsePlayerInfoList.clear(); - cleanup(); Intent intent = new Intent(android.service.media.MediaBrowserService.SERVICE_INTERFACE); - mWaiting = mPackageManager.queryIntentServices(intent, PackageManager.MATCH_ALL); - connectNextPlayer(); - } - - public void cleanup() { - if (mWaiting != null) mWaiting.clear(); - mPlayersChanged = false; - if (mCurrentBrowser != null) mCurrentBrowser.disconnect(); - } - - private void connectNextPlayer() { - if (mWaiting.isEmpty()) { - // Done. Send players changed if needed. - if (mPlayersChanged) { - registerNotificationRspAvalPlayerChangedNative( - AvrcpConstants.NOTIFICATION_TYPE_CHANGED); + List<ResolveInfo> playerList = + mPackageManager.queryIntentServices(intent, PackageManager.MATCH_ALL); + + for (ResolveInfo info : playerList) { + String displayableName = info.loadLabel(mPackageManager).toString(); + String serviceName = info.serviceInfo.name; + String packageName = info.serviceInfo.packageName; + + if (DEBUG) Log.d(TAG, "Adding " + serviceName + " to list of browsable players"); + BrowsePlayerInfo currentPlayer = + new BrowsePlayerInfo(packageName, displayableName, serviceName); + mBrowsePlayerInfoList.add(currentPlayer); + MediaPlayerInfo playerInfo = getMediaPlayerInfo(packageName); + MediaController controller = + (playerInfo == null) ? null : playerInfo.getMediaController(); + // Refresh the media player entry so it notices we can browse + if (controller != null) { + addMediaPlayerController(controller.getWrappedInstance()); + } else { + addMediaPlayerPackage(packageName); } - return; - } - ResolveInfo info = mWaiting.remove(0); - String displayableName = info.loadLabel(mPackageManager).toString(); - String serviceName = info.serviceInfo.name; - String packageName = info.serviceInfo.packageName; - - mCurrentPlayer = new BrowsePlayerInfo(packageName, displayableName, serviceName); - mCurrentBrowser = new MediaBrowser( - mContext, new ComponentName(packageName, serviceName), this, null); - if (DEBUG) Log.d(TAG, "Trying to connect to " + serviceName); - mCurrentBrowser.connect(); - } - - @Override - public void onConnected() { - Log.d(TAG, "BrowsablePlayerListBuilder: " + mCurrentPlayer.packageName + " OK"); - mCurrentBrowser.disconnect(); - mCurrentBrowser = null; - mBrowsePlayerInfoList.add(mCurrentPlayer); - MediaPlayerInfo info = getMediaPlayerInfo(mCurrentPlayer.packageName); - MediaController controller = (info == null) ? null : info.getMediaController(); - // Refresh the media player entry so it notices we can browse - if (controller != null) { - addMediaPlayerController(controller.getWrappedInstance()); - } else { - addMediaPlayerPackage(mCurrentPlayer.packageName); } - mPlayersChanged = true; - connectNextPlayer(); - } - - @Override - public void onConnectionFailed() { - Log.d(TAG, "BrowsablePlayerListBuilder: " + mCurrentPlayer.packageName + " FAIL"); - connectNextPlayer(); + updateCurrentMediaState(false); } } @@ -1755,7 +1764,7 @@ public final class Avrcp { addMediaPlayerController(controller); } - scheduleMediaUpdate(); + updateCurrentMediaState(false); if (mMediaPlayerInfoList.size() > 0) { // Set the first one as the Addressed Player @@ -1804,6 +1813,10 @@ public final class Avrcp { int updateId = -1; boolean updated = false; boolean currentRemoved = false; + if (info.getPackageName().equals("com.android.server.telecom")) { + Log.d(TAG, "Skip adding telecom to the media player info list"); + return updated; + } synchronized (mMediaPlayerInfoList) { for (Map.Entry<Integer, MediaPlayerInfo> entry : mMediaPlayerInfoList.entrySet()) { MediaPlayerInfo current = entry.getValue(); @@ -1828,11 +1841,10 @@ public final class Avrcp { mAvailablePlayerViewChanged = true; } mMediaPlayerInfoList.put(updateId, info); - if (DEBUG) - Log.d(TAG, (updated ? "update #" : "add #") + updateId + ":" + info.toString()); - if (currentRemoved || updateId == mCurrAddrPlayerID) { - updateCurrentController(updateId, mCurrBrowsePlayerID); - } + } + if (DEBUG) Log.d(TAG, (updated ? "update #" : "add #") + updateId + ":" + info.toString()); + if (currentRemoved || updateId == mCurrAddrPlayerID) { + updateCurrentController(updateId, mCurrBrowsePlayerID); } return updated; } @@ -1887,9 +1899,9 @@ public final class Avrcp { switch (pbState.getState()) { case PlaybackState.STATE_PLAYING: - case PlaybackState.STATE_BUFFERING: return PLAYSTATUS_PLAYING; + case PlaybackState.STATE_BUFFERING: case PlaybackState.STATE_STOPPED: case PlaybackState.STATE_NONE: case PlaybackState.STATE_CONNECTING: @@ -2031,7 +2043,12 @@ public final class Avrcp { /* prepare media list & return the media player list response object */ private MediaPlayerListRsp prepareMediaPlayerRspObj() { synchronized (mMediaPlayerInfoList) { - int numPlayers = mMediaPlayerInfoList.size(); + // TODO(apanicke): This hack will go away as soon as a developer + // option to enable or disable player selection is created. Right + // now this is needed to fix BMW i3 carkits and any other carkits + // that might try to connect to a player that isnt the current + // player based on this list + int numPlayers = 1; int[] playerIds = new int[numPlayers]; byte[] playerTypes = new byte[numPlayers]; @@ -2041,14 +2058,21 @@ public final class Avrcp { short[] featureBitMaskValues = new short[numPlayers * AvrcpConstants.AVRC_FEATURE_MASK_SIZE]; - int players = 0; + // Reserve the first spot for the currently addressed player if + // we have one + int players = mMediaPlayerInfoList.containsKey(mCurrAddrPlayerID) ? 1 : 0; for (Map.Entry<Integer, MediaPlayerInfo> entry : mMediaPlayerInfoList.entrySet()) { + int idx = players; + if (entry.getKey() == mCurrAddrPlayerID) + idx = 0; + else + continue; // TODO(apanicke): Remove, see above note MediaPlayerInfo info = entry.getValue(); - playerIds[players] = entry.getKey(); - playerTypes[players] = info.getMajorType(); - playerSubTypes[players] = info.getSubType(); - displayableNameArray[players] = info.getDisplayableName(); - playStatusValues[players] = info.getPlayStatus(); + playerIds[idx] = entry.getKey(); + playerTypes[idx] = info.getMajorType(); + playerSubTypes[idx] = info.getSubType(); + displayableNameArray[idx] = info.getDisplayableName(); + playStatusValues[idx] = info.getPlayStatus(); short[] featureBits = info.getFeatureBitMask(); for (int numBit = 0; numBit < featureBits.length; numBit++) { @@ -2056,19 +2080,18 @@ public final class Avrcp { byte octet = (byte) (featureBits[numBit] / 8); /* gives the bit position within the octet */ byte bit = (byte) (featureBits[numBit] % 8); - featureBitMaskValues[(players * AvrcpConstants.AVRC_FEATURE_MASK_SIZE) - + octet] |= (1 << bit); + featureBitMaskValues[(idx * AvrcpConstants.AVRC_FEATURE_MASK_SIZE) + octet] |= + (1 << bit); } /* printLogs */ if (DEBUG) { - Log.d(TAG, "Player " + playerIds[players] + ": " + displayableNameArray[players] - + " type: " + playerTypes[players] + ", " - + playerSubTypes[players] + " status: " - + playStatusValues[players]); + Log.d(TAG, "Player " + playerIds[idx] + ": " + displayableNameArray[idx] + + " type: " + playerTypes[idx] + ", " + playerSubTypes[idx] + + " status: " + playStatusValues[idx]); } - players++; + if (idx != 0) players++; } if (DEBUG) Log.d(TAG, "prepareMediaPlayerRspObj: numPlayers = " + numPlayers); @@ -2125,14 +2148,12 @@ public final class Avrcp { mMediaController = newController; if (mMediaController != null) { mMediaController.registerCallback(mMediaControllerCb, mHandler); - mAddressedMediaPlayer.updateNowPlayingList(mMediaController); } else { - mAddressedMediaPlayer.updateNowPlayingList(null); registerRsp = false; } } } - scheduleMediaUpdate(); + updateCurrentMediaState(false); return registerRsp; } @@ -2207,6 +2228,12 @@ public final class Avrcp { } private void handleGetItemAttr(AvrcpCmd.ItemAttrCmd itemAttr) { + if (itemAttr.mUidCounter != sUIDCounter) { + Log.e(TAG, "handleGetItemAttr: invaild uid counter."); + getItemAttrRspNative( + itemAttr.mAddress, AvrcpConstants.RSP_UID_CHANGED, (byte) 0, null, null); + return; + } if (itemAttr.mScope == AvrcpConstants.BTRC_SCOPE_NOW_PLAYING) { if (mCurrAddrPlayerID == NO_PLAYER_ID) { getItemAttrRspNative( @@ -2503,9 +2530,15 @@ public final class Avrcp { } public void nowPlayingChangedRsp(int type) { + if (mNowPlayingListChangedNT != AvrcpConstants.NOTIFICATION_TYPE_INTERIM) { + if (DEBUG) Log.d(TAG, "NowPlayingListChanged: Not registered or requesting."); + return; + } + if (!registerNotificationRspNowPlayingChangedNative(type)) { Log.e(TAG, "registerNotificationRspNowPlayingChangedNative failed!"); } + mNowPlayingListChangedNT = AvrcpConstants.NOTIFICATION_TYPE_CHANGED; } public void trackChangedRsp(int type, byte[] uid) { diff --git a/src/com/android/bluetooth/avrcp/BrowsedMediaPlayer.java b/src/com/android/bluetooth/avrcp/BrowsedMediaPlayer.java index c430ea99..397e17a9 100644 --- a/src/com/android/bluetooth/avrcp/BrowsedMediaPlayer.java +++ b/src/com/android/bluetooth/avrcp/BrowsedMediaPlayer.java @@ -53,6 +53,7 @@ class BrowsedMediaPlayer { /* package and service name of target Media Player which is set for browsing */ private String mPackageName; + private String mConnectingPackageName; private String mClassName; private Context mContext; private AvrcpMediaRspInterface mMediaInterface; @@ -83,20 +84,34 @@ class BrowsedMediaPlayer { private List<MediaBrowser.MediaItem> mFolderItems = null; /* Connection state callback handler */ - private MediaBrowser.ConnectionCallback browseMediaConnectionCallback = - new MediaBrowser.ConnectionCallback() { + class MediaConnectionCallback extends MediaBrowser.ConnectionCallback { + private String mCallbackPackageName; + private MediaBrowser mBrowser; + + public MediaConnectionCallback(String packageName) { + this.mCallbackPackageName = packageName; + } + + public void setBrowser(MediaBrowser b) { + mBrowser = b; + } @Override public void onConnected() { mConnState = CONNECTED; if (DEBUG) Log.d(TAG, "mediaBrowser CONNECTED to " + mPackageName); /* perform init tasks and set player as browsed player on successful connection */ - onBrowseConnect(); + onBrowseConnect(mCallbackPackageName, mBrowser); + + // Remove what could be a circular dependency causing GC to never happen on this object + mBrowser = null; } @Override public void onConnectionFailed() { mConnState = DISCONNECTED; + // Remove what could be a circular dependency causing GC to never happen on this object + mBrowser = null; Log.e(TAG, "mediaBrowser Connection failed with " + mPackageName + ", Sending fail response!"); mMediaInterface.setBrowsedPlayerRsp(mBDAddr, AvrcpConstants.RSP_INTERNAL_ERR, @@ -105,10 +120,11 @@ class BrowsedMediaPlayer { @Override public void onConnectionSuspended() { + mBrowser = null; mConnState = SUSPENDED; Log.e(TAG, "mediaBrowser SUSPENDED connection with " + mPackageName); } - }; + } /* Subscription callback handler. Subscribe to a folder to get its contents */ private MediaBrowser.SubscriptionCallback folderItemsCb = @@ -251,26 +267,44 @@ class BrowsedMediaPlayer { } /* initialize mediacontroller in order to communicate with media player. */ - private void onBrowseConnect() { - boolean isError = false; + private void onBrowseConnect(String connectedPackage, MediaBrowser browser) { + if (!connectedPackage.equals(mConnectingPackageName)) { + Log.w(TAG, "onBrowseConnect: recieved callback for package we aren't connecting to " + + connectedPackage); + return; + } + mConnectingPackageName = null; + + if (browser == null) { + Log.e(TAG, "onBrowseConnect: received a null browser for " + connectedPackage); + mMediaInterface.setBrowsedPlayerRsp( + mBDAddr, AvrcpConstants.RSP_INTERNAL_ERR, (byte) 0x00, 0, null); + return; + } + MediaSession.Token token = null; try { - /* get rootfolder uid from media player */ - if (mMediaId == null) { - mMediaId = mMediaBrowser.getRoot(); - /* - * assuming that root folder uid will not change on uids changed - */ - mRootFolderUid = mMediaId; - /* store root folder uid to stack */ - mPathStack.push(mMediaId); - } - - if (!mMediaBrowser.isConnected()) { + if (!browser.isConnected()) { Log.e(TAG, "setBrowsedPlayer: " + mPackageName + "not connected"); - } else if ((token = mMediaBrowser.getSessionToken()) == null) { + } else if ((token = browser.getSessionToken()) == null) { Log.e(TAG, "setBrowsedPlayer: " + mPackageName + "no Session token"); } else { + /* update to the new MediaBrowser */ + if (mMediaBrowser != null) mMediaBrowser.disconnect(); + mMediaBrowser = browser; + mPackageName = connectedPackage; + + /* get rootfolder uid from media player */ + if (mMediaId == null) { + mMediaId = mMediaBrowser.getRoot(); + /* + * assuming that root folder uid will not change on uids changed + */ + mRootFolderUid = mMediaId; + /* store root folder uid to stack */ + mPathStack.push(mMediaId); + } + mMediaController = MediaController.wrap( new android.media.session.MediaController(mContext, token)); /* get root folder items */ @@ -287,7 +321,7 @@ class BrowsedMediaPlayer { } public void setBrowsed(String packageName, String cls) { - mPackageName = packageName; + mConnectingPackageName = packageName; mClassName = cls; /* cleanup variables from previous browsed calls */ mFolderItems = null; @@ -298,10 +332,14 @@ class BrowsedMediaPlayer { * will be required while navigating up the folder */ mPathStack = new Stack<String>(); + /* Bind to MediaBrowseService of MediaPlayer */ - mMediaBrowser = new MediaBrowser(mContext, new ComponentName(mPackageName, mClassName), - browseMediaConnectionCallback, null); - mMediaBrowser.connect(); + MediaConnectionCallback callback = new MediaConnectionCallback(packageName); + MediaBrowser tempBrowser = new MediaBrowser( + mContext, new ComponentName(packageName, mClassName), callback, null); + callback.setBrowser(tempBrowser); + + tempBrowser.connect(); } /* called when connection to media player is closed */ @@ -494,7 +532,7 @@ class BrowsedMediaPlayer { */ private List<MediaBrowser.MediaItem> checkIndexOutofBounds( byte[] bdaddr, List<MediaBrowser.MediaItem> children, long startItem, long endItem) { - if (endItem > children.size()) endItem = children.size() - 1; + if (endItem >= children.size()) endItem = children.size() - 1; if (startItem >= Integer.MAX_VALUE) startItem = Integer.MAX_VALUE; try { List<MediaBrowser.MediaItem> childrenSubList = @@ -651,9 +689,11 @@ class BrowsedMediaPlayer { case AvrcpConstants.ATTRID_GENRE: attrValue = extras.getString(MediaMetadata.METADATA_KEY_GENRE); + break; case AvrcpConstants.ATTRID_PLAY_TIME: attrValue = extras.getString(MediaMetadata.METADATA_KEY_DURATION); + break; case AvrcpConstants.ATTRID_COVER_ART: Log.e(TAG, "getAttrValue: Cover art attribute not supported"); |
