packages/apps/Bluetooth
Revision | 1ab97a2f4868aeb050cc0eae6e3a22081af69f05 (tree) |
---|---|
Zeit | 2017-06-24 10:24:59 |
Autor | Marie Janssen <jamuraa@goog...> |
Commiter | android-build-team Robot |
Avrcp: Limit available players changed
According to the spec, we should only send an Available Players Changed
update when a player is added or removed. (AVRCP 1.6.1 Sec 6.9.4 p74)
Only send an update when a player is added or removed. If a player
would change feature bits, display name, major or minor type, present it
as a new player instead and remove the old player.
Sync available players notifications with other notifications.
Bug: 34471252
Test: switch players, note that Available Players Changed is not sent
all the time
Change-Id: Icd3730afa6e182810920f28fa7db17b98e53ceea
(cherry picked from commit f2f6a4ea02e9456f71ea0490112845110ca5d975)
(cherry picked from commit 85ff6903243d244d4342f390bb40aa99097a9a7c)
@@ -161,9 +161,8 @@ public final class Avrcp { | ||
161 | 161 | private static final int MSG_SET_ABSOLUTE_VOLUME = 16; |
162 | 162 | private static final int MSG_ABS_VOL_TIMEOUT = 17; |
163 | 163 | private static final int MSG_SET_A2DP_AUDIO_STATE = 18; |
164 | - private static final int MSG_AVAILABLE_PLAYERS_CHANGED_RSP = 19; | |
165 | - private static final int MSG_NOW_PLAYING_CHANGED_RSP = 20; | |
166 | - private static final int MSG_UPDATE_MEDIA = 21; | |
164 | + private static final int MSG_NOW_PLAYING_CHANGED_RSP = 19; | |
165 | + private static final int MSG_UPDATE_MEDIA = 20; | |
167 | 166 | |
168 | 167 | private static final int CMD_TIMEOUT_DELAY = 2000; |
169 | 168 | private static final int MEDIA_DWELL_TIME = 1000; |
@@ -179,6 +178,7 @@ public final class Avrcp { | ||
179 | 178 | |
180 | 179 | /* List of Media player instances, useful for retrieving MediaPlayerList or MediaPlayerInfo */ |
181 | 180 | private SortedMap<Integer, MediaPlayerInfo> mMediaPlayerInfoList; |
181 | + private boolean mAvailablePlayerViewChanged; | |
182 | 182 | |
183 | 183 | /* List of media players which supports browse */ |
184 | 184 | private List<BrowsePlayerInfo> mBrowsePlayerInfoList; |
@@ -304,6 +304,7 @@ public final class Avrcp { | ||
304 | 304 | mMediaControllerCb = new MediaControllerListener(); |
305 | 305 | mAvrcpMediaRsp = new AvrcpMediaRsp(); |
306 | 306 | mMediaPlayerInfoList = new TreeMap<Integer, MediaPlayerInfo>(); |
307 | + mAvailablePlayerViewChanged = false; | |
307 | 308 | mBrowsePlayerInfoList = Collections.synchronizedList(new ArrayList<BrowsePlayerInfo>()); |
308 | 309 | mPassthroughDispatched = 0; |
309 | 310 | mPassthroughLogs = new EvictingQueue<MediaKeyLog>(PASSTHROUGH_LOG_MAX_SIZE); |
@@ -474,13 +475,6 @@ public final class Avrcp { | ||
474 | 475 | processRegisterNotification((byte[]) msg.obj, msg.arg1, msg.arg2); |
475 | 476 | break; |
476 | 477 | |
477 | - case MSG_AVAILABLE_PLAYERS_CHANGED_RSP: | |
478 | - if (DEBUG) Log.v(TAG, "MSG_AVAILABLE_PLAYERS_CHANGED_RSP"); | |
479 | - removeMessages(MSG_AVAILABLE_PLAYERS_CHANGED_RSP); | |
480 | - registerNotificationRspAvalPlayerChangedNative( | |
481 | - AvrcpConstants.NOTIFICATION_TYPE_CHANGED); | |
482 | - break; | |
483 | - | |
484 | 478 | case MSG_NOW_PLAYING_CHANGED_RSP: |
485 | 479 | if (DEBUG) Log.v(TAG, "MSG_NOW_PLAYING_CHANGED_RSP"); |
486 | 480 | removeMessages(MSG_NOW_PLAYING_CHANGED_RSP); |
@@ -986,19 +980,27 @@ public final class Avrcp { | ||
986 | 980 | } |
987 | 981 | |
988 | 982 | private void updateCurrentMediaState(boolean registering) { |
989 | - if (!registering && mAddrPlayerChangedNT == AvrcpConstants.NOTIFICATION_TYPE_INTERIM | |
990 | - && mReportedPlayerID != mCurrAddrPlayerID) { | |
991 | - registerNotificationRspAddrPlayerChangedNative( | |
992 | - AvrcpConstants.NOTIFICATION_TYPE_CHANGED, mCurrAddrPlayerID, sUIDCounter); | |
993 | - mAddrPlayerChangedNT = AvrcpConstants.NOTIFICATION_TYPE_CHANGED; | |
994 | - // Changing player sends reject to all these, so disable them. | |
995 | - mReportedPlayerID = mCurrAddrPlayerID; | |
996 | - mPlayStatusChangedNT = AvrcpConstants.NOTIFICATION_TYPE_CHANGED; | |
997 | - mTrackChangedNT = AvrcpConstants.NOTIFICATION_TYPE_CHANGED; | |
998 | - mPlayPosChangedNT = AvrcpConstants.NOTIFICATION_TYPE_CHANGED; | |
999 | - // If the player changed, they need to re-request anything here again | |
1000 | - // so we can skip the rest of the update. | |
1001 | - return; | |
983 | + // Only do player updates when we aren't registering for track changes. | |
984 | + if (!registering) { | |
985 | + if (mAvailablePlayerViewChanged) { | |
986 | + registerNotificationRspAvalPlayerChangedNative( | |
987 | + AvrcpConstants.NOTIFICATION_TYPE_CHANGED); | |
988 | + mAvailablePlayerViewChanged = false; | |
989 | + } | |
990 | + if (mAddrPlayerChangedNT == AvrcpConstants.NOTIFICATION_TYPE_INTERIM | |
991 | + && mReportedPlayerID != mCurrAddrPlayerID) { | |
992 | + registerNotificationRspAddrPlayerChangedNative( | |
993 | + AvrcpConstants.NOTIFICATION_TYPE_CHANGED, mCurrAddrPlayerID, sUIDCounter); | |
994 | + mAddrPlayerChangedNT = AvrcpConstants.NOTIFICATION_TYPE_CHANGED; | |
995 | + // Changing player sends reject to anything else we would notify... | |
996 | + mReportedPlayerID = mCurrAddrPlayerID; | |
997 | + mPlayStatusChangedNT = AvrcpConstants.NOTIFICATION_TYPE_CHANGED; | |
998 | + mTrackChangedNT = AvrcpConstants.NOTIFICATION_TYPE_CHANGED; | |
999 | + mPlayPosChangedNT = AvrcpConstants.NOTIFICATION_TYPE_CHANGED; | |
1000 | + // If the player changed, they need to re-request anything here again | |
1001 | + // so we can skip the rest of the update. | |
1002 | + return; | |
1003 | + } | |
1002 | 1004 | } |
1003 | 1005 | |
1004 | 1006 | MediaAttributes currentAttributes; |
@@ -1580,23 +1582,17 @@ public final class Avrcp { | ||
1580 | 1582 | @Override |
1581 | 1583 | public void onActiveSessionsChanged( |
1582 | 1584 | List<android.media.session.MediaController> newControllers) { |
1583 | - boolean playersChanged = false; | |
1584 | - | |
1585 | 1585 | // Update the current players |
1586 | 1586 | for (android.media.session.MediaController controller : newControllers) { |
1587 | 1587 | addMediaPlayerController(controller); |
1588 | - playersChanged = true; | |
1589 | 1588 | } |
1590 | 1589 | |
1591 | - if (playersChanged) { | |
1592 | - mHandler.sendEmptyMessage(MSG_AVAILABLE_PLAYERS_CHANGED_RSP); | |
1593 | - if (newControllers.size() > 0 && getAddressedPlayerInfo() == null) { | |
1594 | - if (DEBUG) | |
1595 | - Log.v(TAG, | |
1596 | - "No addressed player but active sessions, taking first."); | |
1597 | - setAddressedMediaSessionPackage(newControllers.get(0).getPackageName()); | |
1598 | - } | |
1590 | + if (newControllers.size() > 0 && getAddressedPlayerInfo() == null) { | |
1591 | + if (DEBUG) | |
1592 | + Log.v(TAG, "No addressed player but active sessions, taking first."); | |
1593 | + setAddressedMediaSessionPackage(newControllers.get(0).getPackageName()); | |
1599 | 1594 | } |
1595 | + scheduleMediaUpdate(); | |
1600 | 1596 | } |
1601 | 1597 | }; |
1602 | 1598 |
@@ -1612,7 +1608,7 @@ public final class Avrcp { | ||
1612 | 1608 | // If the player doesn't exist, we need to add it. |
1613 | 1609 | if (getMediaPlayerInfo(packageName) == null) { |
1614 | 1610 | addMediaPlayerPackage(packageName); |
1615 | - mHandler.sendEmptyMessage(MSG_AVAILABLE_PLAYERS_CHANGED_RSP); | |
1611 | + scheduleMediaUpdate(); | |
1616 | 1612 | } |
1617 | 1613 | synchronized (mMediaPlayerInfoList) { |
1618 | 1614 | for (Map.Entry<Integer, MediaPlayerInfo> entry : mMediaPlayerInfoList.entrySet()) { |
@@ -1758,9 +1754,8 @@ public final class Avrcp { | ||
1758 | 1754 | for (android.media.session.MediaController controller : controllers) { |
1759 | 1755 | addMediaPlayerController(controller); |
1760 | 1756 | } |
1761 | - if (controllers.size() > 0) { | |
1762 | - mHandler.sendEmptyMessage(MSG_AVAILABLE_PLAYERS_CHANGED_RSP); | |
1763 | - } | |
1757 | + | |
1758 | + scheduleMediaUpdate(); | |
1764 | 1759 | |
1765 | 1760 | if (mMediaPlayerInfoList.size() > 0) { |
1766 | 1761 | // Set the first one as the Addressed Player |
@@ -1808,10 +1803,20 @@ public final class Avrcp { | ||
1808 | 1803 | private boolean addMediaPlayerInfo(MediaPlayerInfo info) { |
1809 | 1804 | int updateId = -1; |
1810 | 1805 | boolean updated = false; |
1806 | + boolean currentRemoved = false; | |
1811 | 1807 | synchronized (mMediaPlayerInfoList) { |
1812 | 1808 | for (Map.Entry<Integer, MediaPlayerInfo> entry : mMediaPlayerInfoList.entrySet()) { |
1813 | - if (info.getPackageName().equals(entry.getValue().getPackageName())) { | |
1814 | - updateId = entry.getKey(); | |
1809 | + MediaPlayerInfo current = entry.getValue(); | |
1810 | + int id = entry.getKey(); | |
1811 | + if (info.getPackageName().equals(current.getPackageName())) { | |
1812 | + if (!current.equalView(info)) { | |
1813 | + // If we would present a different player, make it a new player | |
1814 | + // so that controllers know whether a player is browsable or not. | |
1815 | + mMediaPlayerInfoList.remove(id); | |
1816 | + currentRemoved = (mCurrAddrPlayerID == id); | |
1817 | + break; | |
1818 | + } | |
1819 | + updateId = id; | |
1815 | 1820 | updated = true; |
1816 | 1821 | break; |
1817 | 1822 | } |
@@ -1820,12 +1825,13 @@ public final class Avrcp { | ||
1820 | 1825 | // New player |
1821 | 1826 | mLastUsedPlayerID++; |
1822 | 1827 | updateId = mLastUsedPlayerID; |
1828 | + mAvailablePlayerViewChanged = true; | |
1823 | 1829 | } |
1824 | 1830 | mMediaPlayerInfoList.put(updateId, info); |
1825 | 1831 | if (DEBUG) |
1826 | 1832 | Log.d(TAG, (updated ? "update #" : "add #") + updateId + ":" + info.toString()); |
1827 | - if (updateId == mCurrAddrPlayerID) { | |
1828 | - updateCurrentController(mCurrAddrPlayerID, mCurrBrowsePlayerID); | |
1833 | + if (currentRemoved || updateId == mCurrAddrPlayerID) { | |
1834 | + updateCurrentController(updateId, mCurrBrowsePlayerID); | |
1829 | 1835 | } |
1830 | 1836 | } |
1831 | 1837 | return updated; |
@@ -1844,6 +1850,7 @@ public final class Avrcp { | ||
1844 | 1850 | if (removeKey != -1) { |
1845 | 1851 | if (DEBUG) |
1846 | 1852 | Log.d(TAG, "remove #" + removeKey + ":" + mMediaPlayerInfoList.get(removeKey)); |
1853 | + mAvailablePlayerViewChanged = true; | |
1847 | 1854 | return mMediaPlayerInfoList.remove(removeKey); |
1848 | 1855 | } |
1849 | 1856 |
@@ -16,6 +16,8 @@ | ||
16 | 16 | |
17 | 17 | package com.android.bluetooth.avrcp; |
18 | 18 | |
19 | +import android.annotation.NonNull; | |
20 | +import android.annotation.Nullable; | |
19 | 21 | import android.media.session.MediaSession; |
20 | 22 | |
21 | 23 | import com.android.bluetooth.Utils; |
@@ -200,17 +202,19 @@ class MediaPlayerInfo { | ||
200 | 202 | private int subType; |
201 | 203 | private byte playStatus; |
202 | 204 | private short[] featureBitMask; |
203 | - private String packageName; | |
204 | - private String displayableName; | |
205 | - private MediaController mediaController; | |
205 | + private @NonNull String packageName; | |
206 | + private @NonNull String displayableName; | |
207 | + private @Nullable MediaController mediaController; | |
206 | 208 | |
207 | - MediaPlayerInfo(MediaController controller, byte majorType, int subType, byte playStatus, | |
208 | - short[] featureBitMask, String packageName, String displayableName) { | |
209 | + MediaPlayerInfo(@Nullable MediaController controller, byte majorType, int subType, | |
210 | + byte playStatus, short[] featureBitMask, @NonNull String packageName, | |
211 | + @Nullable String displayableName) { | |
209 | 212 | this.setMajorType(majorType); |
210 | 213 | this.setSubType(subType); |
211 | 214 | this.playStatus = playStatus; |
212 | 215 | // store a copy the FeatureBitMask array |
213 | 216 | this.featureBitMask = Arrays.copyOf(featureBitMask, featureBitMask.length); |
217 | + Arrays.sort(this.featureBitMask); | |
214 | 218 | this.setPackageName(packageName); |
215 | 219 | this.setDisplayableName(displayableName); |
216 | 220 | this.setMediaController(controller); |
@@ -236,7 +240,7 @@ class MediaPlayerInfo { | ||
236 | 240 | this.mediaController = mediaController; |
237 | 241 | } |
238 | 242 | |
239 | - void setPackageName(String name) { | |
243 | + void setPackageName(@NonNull String name) { | |
240 | 244 | // Controller determines package name when it is set. |
241 | 245 | if (mediaController != null) return; |
242 | 246 | this.packageName = name; |
@@ -271,7 +275,8 @@ class MediaPlayerInfo { | ||
271 | 275 | return displayableName; |
272 | 276 | } |
273 | 277 | |
274 | - void setDisplayableName(String displayableName) { | |
278 | + void setDisplayableName(@Nullable String displayableName) { | |
279 | + if (displayableName == null) displayableName = ""; | |
275 | 280 | this.displayableName = displayableName; |
276 | 281 | } |
277 | 282 |
@@ -282,6 +287,7 @@ class MediaPlayerInfo { | ||
282 | 287 | void setFeatureBitMask(short[] featureBitMask) { |
283 | 288 | synchronized (this) { |
284 | 289 | this.featureBitMask = Arrays.copyOf(featureBitMask, featureBitMask.length); |
290 | + Arrays.sort(this.featureBitMask); | |
285 | 291 | } |
286 | 292 | } |
287 | 293 |
@@ -295,6 +301,14 @@ class MediaPlayerInfo { | ||
295 | 301 | return false; |
296 | 302 | } |
297 | 303 | |
304 | + /** Tests if the view of this player presented to the controller is different enough to | |
305 | + * justify sending an Available Players Changed update */ | |
306 | + public boolean equalView(MediaPlayerInfo other) { | |
307 | + return (this.majorType == other.getMajorType()) && (this.subType == other.getSubType()) | |
308 | + && Arrays.equals(this.featureBitMask, other.getFeatureBitMask()) | |
309 | + && this.displayableName.equals(other.getDisplayableName()); | |
310 | + } | |
311 | + | |
298 | 312 | @Override |
299 | 313 | public String toString() { |
300 | 314 | StringBuilder sb = new StringBuilder(); |