From bbf6ff33740df7e742b5597ea806ec6d57c26465 Mon Sep 17 00:00:00 2001 From: Jakub Pawlowski Date: Tue, 1 Oct 2019 17:09:13 +0200 Subject: Restore Parameters after audio server restart Bug: 141724113 Test: kill audioserver during phone call Change-Id: I9b452a69ac711499b8a465b81225a3080a24e726 (cherry picked from commit 2974124d6f0325ec5c397fba070101a6b732aa2c) --- .../android/bluetooth/hfp/HeadsetStateMachine.java | 26 ++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/com/android/bluetooth/hfp/HeadsetStateMachine.java b/src/com/android/bluetooth/hfp/HeadsetStateMachine.java index c5485f992..06c48223d 100644 --- a/src/com/android/bluetooth/hfp/HeadsetStateMachine.java +++ b/src/com/android/bluetooth/hfp/HeadsetStateMachine.java @@ -1198,6 +1198,21 @@ public class HeadsetStateMachine extends StateMachine { } } + class MyAudioServerStateCallback extends AudioManager.AudioServerStateCallback { + @Override + public void onAudioServerDown() { + logi("onAudioServerDown"); + } + + @Override + public void onAudioServerUp() { + logi("onAudioServerUp restoring audio parameters"); + setAudioParameters(); + } + } + + MyAudioServerStateCallback mAudioServerStateCallback = new MyAudioServerStateCallback(); + class AudioOn extends ConnectedBase { @Override int getAudioStateInt() { @@ -1216,9 +1231,20 @@ public class HeadsetStateMachine extends StateMachine { mHeadsetService.setActiveDevice(mDevice); } setAudioParameters(); + + mSystemInterface.getAudioManager().setAudioServerStateCallback( + mHeadsetService.getMainExecutor(), mAudioServerStateCallback); + broadcastStateTransitions(); } + @Override + public void exit() { + super.exit(); + + mSystemInterface.getAudioManager().clearAudioServerStateCallback(); + } + @Override public boolean processMessage(Message message) { switch (message.what) { -- cgit v1.2.3 From 5626dea3f8440ceb1d8ba75d03a57c892a6f4160 Mon Sep 17 00:00:00 2001 From: Ted Wang Date: Mon, 2 Mar 2020 21:14:21 +0800 Subject: Block guest user from Bluetooth tethering connect/disconnect Guset user does not have permission to change WiFi network, block guest user to connect WiFi through Bluetooth thethering to secure it. Bug: 126206353 Test: atest BluetoothInstrumentationTests Merged-In: Id863513f2e6b4bfa7ab56446e2a77389348e8934 Change-Id: Id863513f2e6b4bfa7ab56446e2a77389348e8934 (cherry picked from commit 312202c1d75013d55cc3fad6a333445a91dfd4ce) --- src/com/android/bluetooth/pan/PanService.java | 13 +++++++++++++ .../unit/src/com/android/bluetooth/pan/PanServiceTest.java | 13 +++++++++++++ 2 files changed, 26 insertions(+) diff --git a/src/com/android/bluetooth/pan/PanService.java b/src/com/android/bluetooth/pan/PanService.java index aa5a1fd31..92eab7787 100644 --- a/src/com/android/bluetooth/pan/PanService.java +++ b/src/com/android/bluetooth/pan/PanService.java @@ -40,6 +40,7 @@ import com.android.bluetooth.Utils; import com.android.bluetooth.btservice.AdapterService; import com.android.bluetooth.btservice.MetricsLogger; import com.android.bluetooth.btservice.ProfileService; +import com.android.internal.annotations.VisibleForTesting; import java.net.InetAddress; import java.util.ArrayList; @@ -67,6 +68,9 @@ public class PanService extends ProfileService { private String mNapIfaceAddr; private boolean mNativeAvailable; + @VisibleForTesting + UserManager mUserManager; + private static final int MESSAGE_CONNECT = 1; private static final int MESSAGE_DISCONNECT = 2; private static final int MESSAGE_CONNECT_STATE_CHANGED = 11; @@ -116,6 +120,8 @@ public class PanService extends ProfileService { initializeNative(); mNativeAvailable = true; + mUserManager = (UserManager) getSystemService(Context.USER_SERVICE); + mNetworkFactory = new BluetoothTetheringNetworkFactory(getBaseContext(), getMainLooper(), this); setPanService(this); @@ -137,6 +143,9 @@ public class PanService extends ProfileService { cleanupNative(); mNativeAvailable = false; } + + mUserManager = null; + if (mPanDevices != null) { int[] desiredStates = {BluetoothProfile.STATE_CONNECTING, BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_DISCONNECTING}; @@ -319,6 +328,10 @@ public class PanService extends ProfileService { public boolean connect(BluetoothDevice device) { enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); + if (mUserManager.isGuestUser()) { + Log.w(TAG, "Guest user does not have the permission to change the WiFi network"); + return false; + } if (getConnectionState(device) != BluetoothProfile.STATE_DISCONNECTED) { Log.e(TAG, "Pan Device not disconnected: " + device); return false; diff --git a/tests/unit/src/com/android/bluetooth/pan/PanServiceTest.java b/tests/unit/src/com/android/bluetooth/pan/PanServiceTest.java index 6d4574f11..3573f847a 100644 --- a/tests/unit/src/com/android/bluetooth/pan/PanServiceTest.java +++ b/tests/unit/src/com/android/bluetooth/pan/PanServiceTest.java @@ -15,8 +15,12 @@ */ package com.android.bluetooth.pan; +import static org.mockito.Mockito.when; + import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; import android.content.Context; +import android.os.UserManager; import androidx.test.InstrumentationRegistry; import androidx.test.filters.MediumTest; @@ -47,6 +51,7 @@ public class PanServiceTest { @Rule public final ServiceTestRule mServiceRule = new ServiceTestRule(); @Mock private AdapterService mAdapterService; + @Mock private UserManager mMockUserManager; @Before public void setUp() throws Exception { @@ -61,6 +66,7 @@ public class PanServiceTest { // Try getting the Bluetooth adapter mAdapter = BluetoothAdapter.getDefaultAdapter(); Assert.assertNotNull(mAdapter); + mService.mUserManager = mMockUserManager; } @After @@ -78,4 +84,11 @@ public class PanServiceTest { public void testInitialize() { Assert.assertNotNull(PanService.getPanService()); } + + @Test + public void testGuestUserConnect() { + BluetoothDevice device = TestUtils.getTestDevice(mAdapter, 0); + when(mMockUserManager.isGuestUser()).thenReturn(true); + Assert.assertFalse(mService.connect(device)); + } } -- cgit v1.2.3 From 363c6e02b9dd76f242564de31433180302bb27de Mon Sep 17 00:00:00 2001 From: weichinweng Date: Thu, 5 Mar 2020 09:59:06 +0800 Subject: Fix bluetooth can't turn off during network reset (1/3) Remove disable Bluetooth action from AdapterService and move to BluetoothManagerService. Bug: 110181479 Test: manual Change-Id: Iaaa82675f072406970afc97ebe754b29f28d9490 Merged-In: Iaaa82675f072406970afc97ebe754b29f28d9490 --- src/com/android/bluetooth/btservice/AdapterService.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/com/android/bluetooth/btservice/AdapterService.java b/src/com/android/bluetooth/btservice/AdapterService.java index 009e42cef..a65bcfdf9 100644 --- a/src/com/android/bluetooth/btservice/AdapterService.java +++ b/src/com/android/bluetooth/btservice/AdapterService.java @@ -1559,7 +1559,6 @@ public class AdapterService extends Service { if (service == null) { return false; } - service.disable(); return service.factoryReset(); } -- cgit v1.2.3 From 9ccb9ff3fcf0df5c2bfe9f4ce3d1e09edec49648 Mon Sep 17 00:00:00 2001 From: Joseph Pirozzo Date: Thu, 12 Mar 2020 08:56:10 -0700 Subject: DO NOT MERGE: Revert "AVRCP Controller transient loss while idle" This revert is reapplied after a few CLs to match change order to AOSP. Bug: 136092891 This reverts commit 1812aafde9b3fd63e51f3579552f63278006c185. --- .../bluetooth/a2dpsink/A2dpSinkStreamHandler.java | 5 +---- .../avrcpcontroller/BluetoothMediaBrowserService.java | 14 -------------- .../bluetooth/a2dpsink/A2dpSinkStreamHandlerTest.java | 18 ------------------ 3 files changed, 1 insertion(+), 36 deletions(-) diff --git a/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamHandler.java b/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamHandler.java index 5e3b3567c..c24eca9e8 100644 --- a/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamHandler.java +++ b/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamHandler.java @@ -24,8 +24,6 @@ import android.media.AudioFocusRequest; import android.media.AudioManager; import android.media.AudioManager.OnAudioFocusChangeListener; import android.media.MediaPlayer; -import android.media.session.PlaybackState; - import android.os.Handler; import android.os.Message; import android.util.Log; @@ -227,8 +225,7 @@ public class A2dpSinkStreamHandler extends Handler { break; case DELAYED_PAUSE: - if (BluetoothMediaBrowserService.getPlaybackState() - == PlaybackState.STATE_PLAYING && !inCallFromStreamingDevice()) { + if (mStreamAvailable && !inCallFromStreamingDevice()) { sendAvrcpPause(); mSentPause = true; mStreamAvailable = false; diff --git a/src/com/android/bluetooth/avrcpcontroller/BluetoothMediaBrowserService.java b/src/com/android/bluetooth/avrcpcontroller/BluetoothMediaBrowserService.java index 304d5a2c9..3504cd49c 100644 --- a/src/com/android/bluetooth/avrcpcontroller/BluetoothMediaBrowserService.java +++ b/src/com/android/bluetooth/avrcpcontroller/BluetoothMediaBrowserService.java @@ -176,20 +176,6 @@ public class BluetoothMediaBrowserService extends MediaBrowserService { } } - /** - * Get playback state - */ - public static synchronized int getPlaybackState() { - if (sBluetoothMediaBrowserService != null) { - PlaybackState currentPlaybackState = - sBluetoothMediaBrowserService.mSession.getController().getPlaybackState(); - if (currentPlaybackState != null) { - return currentPlaybackState.getState(); - } - } - return PlaybackState.STATE_ERROR; - } - /** * Get object for controlling playback */ diff --git a/tests/unit/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamHandlerTest.java b/tests/unit/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamHandlerTest.java index c759a8a1f..53e8419f9 100644 --- a/tests/unit/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamHandlerTest.java +++ b/tests/unit/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamHandlerTest.java @@ -194,24 +194,6 @@ public class A2dpSinkStreamHandlerTest { verify(mMockA2dpSink, times(1)).informAudioTrackGainNative(0); } - @Test - public void testFocusGainTransient() { - // Focus was lost then regained. - testSnkPlay(); - mStreamHandler.handleMessage( - mStreamHandler.obtainMessage(A2dpSinkStreamHandler.AUDIO_FOCUS_CHANGE, - AudioManager.AUDIOFOCUS_LOSS_TRANSIENT)); - mStreamHandler.handleMessage( - mStreamHandler.obtainMessage(A2dpSinkStreamHandler.DELAYED_PAUSE)); - mStreamHandler.handleMessage( - mStreamHandler.obtainMessage(A2dpSinkStreamHandler.AUDIO_FOCUS_CHANGE, - AudioManager.AUDIOFOCUS_GAIN)); - verify(mMockAudioManager, times(0)).abandonAudioFocus(any()); - verify(mMockA2dpSink, times(0)).informAudioFocusStateNative(0); - verify(mMockA2dpSink, times(1)).informAudioTrackGainNative(0); - verify(mMockA2dpSink, times(2)).informAudioTrackGainNative(1.0f); - } - @Test public void testFocusLost() { // Focus was lost permanently, expect streaming to stop. -- cgit v1.2.3 From a8bcf640648a8f6395812a1f36e6064e52305035 Mon Sep 17 00:00:00 2001 From: Joseph Pirozzo Date: Thu, 12 Mar 2020 08:28:40 -0700 Subject: DO NOT MERGE: Revert "AVRCP Controller AbsoluteVolumeNotification" This revert is reapplied after a few CLs to match change order to AOSP. This reverts commit fc9aee6d0bb93f8947f8c50e66ff7ba0e6f7a662. Bug: 129478624 Change-Id: I4bfdaa1717df89492bd74a1c59ad151d2161d0ab --- .../AvrcpControllerStateMachine.java | 45 ++++++++-------------- .../AvrcpControllerStateMachineTest.java | 20 ---------- 2 files changed, 17 insertions(+), 48 deletions(-) diff --git a/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachine.java b/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachine.java index 66571c4e7..1d1a6356d 100644 --- a/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachine.java +++ b/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachine.java @@ -89,14 +89,7 @@ class AvrcpControllerStateMachine extends StateMachine { */ private static final int ABS_VOL_BASE = 127; - /* - * Notification types for Avrcp protocol JNI. - */ - private static final byte NOTIFICATION_RSP_TYPE_INTERIM = 0x00; - private static final byte NOTIFICATION_RSP_TYPE_CHANGED = 0x01; - private final AudioManager mAudioManager; - private final boolean mIsVolumeFixed; protected final BluetoothDevice mDevice; protected final byte[] mDeviceAddress; @@ -115,7 +108,6 @@ class AvrcpControllerStateMachine extends StateMachine { private int mAddressedPlayerId = -1; private SparseArray mAvailablePlayerList = new SparseArray(); private int mVolumeChangedNotificationsToIgnore = 0; - private int mVolumeNotificationLabel = -1; GetFolderList mGetFolderList = null; @@ -145,7 +137,6 @@ class AvrcpControllerStateMachine extends StateMachine { mGetFolderList = new GetFolderList(); addState(mGetFolderList, mConnected); mAudioManager = (AudioManager) service.getSystemService(Context.AUDIO_SERVICE); - mIsVolumeFixed = mAudioManager.isVolumeFixed(); setInitialState(mDisconnected); } @@ -318,13 +309,6 @@ class AvrcpControllerStateMachine extends StateMachine { setAbsVolume(msg.arg1, msg.arg2); return true; - case MESSAGE_PROCESS_REGISTER_ABS_VOL_NOTIFICATION: - mVolumeNotificationLabel = msg.arg1; - mService.sendRegisterAbsVolRspNative(mDeviceAddress, - NOTIFICATION_RSP_TYPE_INTERIM, - getAbsVolumeResponse(), mVolumeNotificationLabel); - return true; - case MESSAGE_GET_FOLDER_ITEMS: transitionTo(mGetFolderList); return true; @@ -564,9 +548,24 @@ class AvrcpControllerStateMachine extends StateMachine { } break; - default: + case CONNECT: + case DISCONNECT: + case MSG_AVRCP_PASSTHRU: + case MESSAGE_PROCESS_SET_ABS_VOL_CMD: + case MESSAGE_PROCESS_REGISTER_ABS_VOL_NOTIFICATION: + case MESSAGE_PROCESS_TRACK_CHANGED: + case MESSAGE_PROCESS_PLAY_POS_CHANGED: + case MESSAGE_PROCESS_PLAY_STATUS_CHANGED: + case MESSAGE_PROCESS_VOLUME_CHANGED_NOTIFICATION: + case MESSAGE_PLAY_ITEM: + case MESSAGE_PROCESS_ADDRESSED_PLAYER_CHANGED: // All of these messages should be handled by parent state immediately. return false; + + default: + logD(STATE_TAG + " deferring message " + msg.what + + " to connected!"); + deferMessage(msg); } return true; } @@ -695,17 +694,7 @@ class AvrcpControllerStateMachine extends StateMachine { mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, newIndex, AudioManager.FLAG_SHOW_UI); } - mService.sendAbsVolRspNative(mDeviceAddress, getAbsVolumeResponse(), label); - } - - private int getAbsVolumeResponse() { - if (mIsVolumeFixed) { - return ABS_VOL_BASE; - } - int maxVolume = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC); - int currIndex = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC); - int newIndex = (currIndex * ABS_VOL_BASE) / maxVolume; - return newIndex; + mService.sendAbsVolRspNative(mDeviceAddress, absVol, label); } MediaSession.Callback mSessionCallbacks = new MediaSession.Callback() { diff --git a/tests/unit/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachineTest.java b/tests/unit/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachineTest.java index 4fedc889a..b1d1743d0 100644 --- a/tests/unit/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachineTest.java +++ b/tests/unit/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachineTest.java @@ -23,7 +23,6 @@ import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothProfile; import android.content.Context; import android.content.Intent; -import android.media.AudioManager; import android.media.session.MediaController; import android.os.Looper; @@ -71,8 +70,6 @@ public class AvrcpControllerStateMachineTest { @Mock private AdapterService mAdapterService; @Mock - private AudioManager mAudioManager; - @Mock private AvrcpControllerService mAvrcpControllerService; AvrcpControllerStateMachine mAvrcpStateMachine; @@ -93,11 +90,6 @@ public class AvrcpControllerStateMachineTest { TestUtils.setAdapterService(mAdapterService); TestUtils.startService(mServiceRule, AvrcpControllerService.class); doReturn(mTargetContext.getResources()).when(mAvrcpControllerService).getResources(); - doReturn(15).when(mAudioManager).getStreamMaxVolume(anyInt()); - doReturn(8).when(mAudioManager).getStreamVolume(anyInt()); - doReturn(true).when(mAudioManager).isVolumeFixed(); - doReturn(mAudioManager).when(mAvrcpControllerService) - .getSystemService(Context.AUDIO_SERVICE); // This line must be called to make sure relevant objects are initialized properly mAdapter = BluetoothAdapter.getDefaultAdapter(); @@ -494,18 +486,6 @@ public class AvrcpControllerStateMachineTest { eq(mTestAddress), eq(AvrcpControllerService.PASS_THRU_CMD_ID_PLAY), eq(KEY_UP)); } - /** - * Test that Absolute Volume Registration is working - */ - @Test - public void testRegisterAbsVolumeNotification() { - setUpConnectedState(true, true); - mAvrcpStateMachine.sendMessage( - AvrcpControllerStateMachine.MESSAGE_PROCESS_REGISTER_ABS_VOL_NOTIFICATION); - verify(mAvrcpControllerService, timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)) - .sendRegisterAbsVolRspNative(any(), anyByte(), eq(127), anyInt()); - } - /** * Setup Connected State * -- cgit v1.2.3 From deb3aaa91a50390c23bab57bf3f7bfdfe996c513 Mon Sep 17 00:00:00 2001 From: Sal Savage Date: Fri, 17 May 2019 14:47:51 -0700 Subject: Broadcast MAP Client messages oldest first Bug: b/130260536 Test: Tested with kitchen sink on automotive hardware Change-Id: I6a373d7890e7680bb99d5e3c5a50c18648d8c003 (cherry picked from commit bfa93ce0bbb80d9e1de882769db4538627199b79) Merged-In: I6a373d7890e7680bb99d5e3c5a50c18648d8c003 Change-Id: Iaa4344b403a2ca539e818f09edf4e4b9f7a5c366 --- src/com/android/bluetooth/mapclient/MceStateMachine.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/com/android/bluetooth/mapclient/MceStateMachine.java b/src/com/android/bluetooth/mapclient/MceStateMachine.java index 0a428b418..867ab185f 100644 --- a/src/com/android/bluetooth/mapclient/MceStateMachine.java +++ b/src/com/android/bluetooth/mapclient/MceStateMachine.java @@ -42,7 +42,6 @@ package com.android.bluetooth.mapclient; import android.app.Activity; import android.app.PendingIntent; -import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothMapClient; import android.bluetooth.BluetoothProfile; @@ -69,7 +68,6 @@ import com.android.vcard.VCardProperty; import java.util.ArrayList; import java.util.Calendar; -import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.concurrent.ConcurrentHashMap; @@ -625,9 +623,12 @@ final class MceStateMachine extends StateMachine { } ArrayList messageListing = request.getList(); if (messageListing != null) { - for (com.android.bluetooth.mapclient.Message msg : messageListing) { + // Message listings by spec arrive ordered newest first but we wish to broadcast as + // oldest first. Iterate in reverse order so we initiate requests oldest first. + for (int i = messageListing.size() - 1; i >= 0; i--) { + com.android.bluetooth.mapclient.Message msg = messageListing.get(i); if (DBG) { - Log.d(TAG, "getting message "); + Log.d(TAG, "getting message for handle " + msg.getHandle()); } // A message listing coming from the server should always have up to date data mMessages.put(msg.getHandle(), new MessageMetadata(msg.getHandle(), -- cgit v1.2.3 From 3dad083344c6c2b570e10a552e2617fd80b960a6 Mon Sep 17 00:00:00 2001 From: Joseph Pirozzo Date: Thu, 11 Jul 2019 08:01:18 -0700 Subject: AVRCP Controller Shuffle/Repeat support Add Custom actions to the Playback status for Shuffle and Repeat capabilities when available from the remote device. Bug: 72495707 Test: AvrcpControllerStateMachineTest#testShuffle AvrcpControllerStateMachineTest#testRepeat Change Shuffle and Repeat mode on connected phone via Headunit Change-Id: Ia0a62d10032b6e918a38e66bd486a29199b772e2 (cherry picked from commit 1c862460a544108b8b12fb24827f9a717961d410) Merged-In: Ia0a62d10032b6e918a38e66bd486a29199b772e2 Change-Id: I8669dbbe9552e5a53cb8c864c3e00c1c4df569ec --- Android.mk | 1 + .../avrcpcontroller/AvrcpControllerService.java | 33 ++-- .../AvrcpControllerStateMachine.java | 74 ++++++-- .../bluetooth/avrcpcontroller/AvrcpPlayer.java | 102 +++++++---- .../BluetoothMediaBrowserService.java | 72 +++++--- .../avrcpcontroller/PlayerApplicationSettings.java | 201 ++++----------------- .../AvrcpControllerStateMachineTest.java | 56 +++++- 7 files changed, 285 insertions(+), 254 deletions(-) diff --git a/Android.mk b/Android.mk index a34d2a34f..1f73091ad 100644 --- a/Android.mk +++ b/Android.mk @@ -29,6 +29,7 @@ LOCAL_STATIC_JAVA_LIBRARIES := \ LOCAL_STATIC_ANDROID_LIBRARIES := \ androidx.core_core \ + androidx.legacy_legacy-support-v4 \ androidx.lifecycle_lifecycle-livedata \ androidx.room_room-runtime \ diff --git a/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerService.java b/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerService.java index 64e63df00..693b4a291 100644 --- a/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerService.java +++ b/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerService.java @@ -412,9 +412,10 @@ public class AvrcpControllerService extends ProfileService { if (stateMachine != null) { PlayerApplicationSettings supportedSettings = PlayerApplicationSettings.makeSupportedSettings(playerAttribRsp); + stateMachine.sendMessage( + AvrcpControllerStateMachine.MESSAGE_PROCESS_SUPPORTED_APPLICATION_SETTINGS, + supportedSettings); } - /* Do nothing */ - } private synchronized void onPlayerAppSettingChanged(byte[] address, byte[] playerAttribRsp, @@ -426,10 +427,12 @@ public class AvrcpControllerService extends ProfileService { AvrcpControllerStateMachine stateMachine = getStateMachine(device); if (stateMachine != null) { - PlayerApplicationSettings desiredSettings = + PlayerApplicationSettings currentSettings = PlayerApplicationSettings.makeSettings(playerAttribRsp); + stateMachine.sendMessage( + AvrcpControllerStateMachine.MESSAGE_PROCESS_CURRENT_APPLICATION_SETTINGS, + currentSettings); } - /* Do nothing */ } // Browsing related JNI callbacks. @@ -711,7 +714,7 @@ public class AvrcpControllerService extends ProfileService { /** * Send button press commands to addressed device * - * @param keyCode key code as defined in AVRCP specification + * @param keyCode key code as defined in AVRCP specification * @param keyState 0 = key pressed, 1 = key released * @return command was sent */ @@ -720,7 +723,7 @@ public class AvrcpControllerService extends ProfileService { /** * Send group navigation commands * - * @param keyCode next/previous + * @param keyCode next/previous * @param keyState state * @return command was sent */ @@ -741,7 +744,7 @@ public class AvrcpControllerService extends ProfileService { * Send response to set absolute volume * * @param absVol new volume - * @param label label + * @param label label */ public native void sendAbsVolRspNative(byte[] address, int absVol, int label); @@ -749,8 +752,8 @@ public class AvrcpControllerService extends ProfileService { * Register for any volume level changes * * @param rspType type of response - * @param absVol current volume - * @param label label + * @param absVol current volume + * @param label label */ public native void sendRegisterAbsVolRspNative(byte[] address, byte rspType, int absVol, int label); @@ -764,7 +767,7 @@ public class AvrcpControllerService extends ProfileService { * Fetch the current now playing list * * @param start first index to retrieve - * @param end last index to retrieve + * @param end last index to retrieve */ public native void getNowPlayingListNative(byte[] address, int start, int end); @@ -772,7 +775,7 @@ public class AvrcpControllerService extends ProfileService { * Fetch the current folder's listing * * @param start first index to retrieve - * @param end last index to retrieve + * @param end last index to retrieve */ public native void getFolderListNative(byte[] address, int start, int end); @@ -780,7 +783,7 @@ public class AvrcpControllerService extends ProfileService { * Fetch the listing of players * * @param start first index to retrieve - * @param end last index to retrieve + * @param end last index to retrieve */ public native void getPlayerListNative(byte[] address, int start, int end); @@ -788,15 +791,15 @@ public class AvrcpControllerService extends ProfileService { * Change the current browsed folder * * @param direction up/down - * @param uid folder unique id + * @param uid folder unique id */ public native void changeFolderPathNative(byte[] address, byte direction, long uid); /** * Play item with provided uid * - * @param scope scope of item to played - * @param uid song unique id + * @param scope scope of item to played + * @param uid song unique id * @param uidCounter counter */ public native void playItemNative(byte[] address, byte scope, long uid, int uidCounter); diff --git a/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachine.java b/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachine.java index 1d1a6356d..7a6c54126 100644 --- a/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachine.java +++ b/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachine.java @@ -24,10 +24,10 @@ import android.content.Intent; import android.media.AudioManager; import android.media.MediaMetadata; import android.media.browse.MediaBrowser.MediaItem; -import android.media.session.MediaSession; -import android.media.session.PlaybackState; import android.os.Bundle; import android.os.Message; +import android.support.v4.media.session.MediaSessionCompat; +import android.support.v4.media.session.PlaybackStateCompat; import android.util.Log; import android.util.SparseArray; @@ -42,6 +42,7 @@ import com.android.internal.util.StateMachine; import java.util.ArrayList; import java.util.List; + /** * Provides Bluetooth AVRCP Controller State Machine responsible for all remote control connections * and interactions with a remote controlable device. @@ -76,11 +77,15 @@ class AvrcpControllerStateMachine extends StateMachine { static final int MESSAGE_PROCESS_SET_ADDRESSED_PLAYER = 214; static final int MESSAGE_PROCESS_ADDRESSED_PLAYER_CHANGED = 215; static final int MESSAGE_PROCESS_NOW_PLAYING_CONTENTS_CHANGED = 216; + static final int MESSAGE_PROCESS_SUPPORTED_APPLICATION_SETTINGS = 217; + static final int MESSAGE_PROCESS_CURRENT_APPLICATION_SETTINGS = 218; //300->399 Events for Browsing static final int MESSAGE_GET_FOLDER_ITEMS = 300; static final int MESSAGE_PLAY_ITEM = 301; static final int MSG_AVRCP_PASSTHRU = 302; + static final int MSG_AVRCP_SET_SHUFFLE = 303; + static final int MSG_AVRCP_SET_REPEAT = 304; static final int MESSAGE_INTERNAL_ABS_VOL_TIMEOUT = 404; @@ -215,12 +220,12 @@ class AvrcpControllerStateMachine extends StateMachine { synchronized void onBrowsingDisconnected() { if (!mBrowsingConnected) return; - mAddressedPlayer.setPlayStatus(PlaybackState.STATE_ERROR); + mAddressedPlayer.setPlayStatus(PlaybackStateCompat.STATE_ERROR); mAddressedPlayer.updateCurrentTrack(null); mBrowseTree.mNowPlayingNode.setCached(false); BluetoothMediaBrowserService.notifyChanged(mBrowseTree.mNowPlayingNode); - PlaybackState.Builder pbb = new PlaybackState.Builder(); - pbb.setState(PlaybackState.STATE_ERROR, PlaybackState.PLAYBACK_POSITION_UNKNOWN, + PlaybackStateCompat.Builder pbb = new PlaybackStateCompat.Builder(); + pbb.setState(PlaybackStateCompat.STATE_ERROR, PlaybackStateCompat.PLAYBACK_POSITION_UNKNOWN, 1.0f).setActions(0); pbb.setErrorMessage(mService.getString(R.string.bluetooth_disconnected)); BluetoothMediaBrowserService.notifyChanged(pbb.build()); @@ -322,6 +327,14 @@ class AvrcpControllerStateMachine extends StateMachine { passThru(msg.arg1); return true; + case MSG_AVRCP_SET_REPEAT: + setRepeat(msg.arg1); + return true; + + case MSG_AVRCP_SET_SHUFFLE: + setShuffle(msg.arg1); + return true; + case MESSAGE_PROCESS_TRACK_CHANGED: mAddressedPlayer.updateCurrentTrack((MediaMetadata) msg.obj); BluetoothMediaBrowserService.trackChanged((MediaMetadata) msg.obj); @@ -331,7 +344,7 @@ class AvrcpControllerStateMachine extends StateMachine { mAddressedPlayer.setPlayStatus(msg.arg1); BluetoothMediaBrowserService.notifyChanged(mAddressedPlayer.getPlaybackState()); if (mAddressedPlayer.getPlaybackState().getState() - == PlaybackState.STATE_PLAYING + == PlaybackStateCompat.STATE_PLAYING && A2dpSinkService.getFocusState() == AudioManager.AUDIOFOCUS_NONE && !shouldRequestFocus()) { sendMessage(MSG_AVRCP_PASSTHRU, @@ -362,6 +375,18 @@ class AvrcpControllerStateMachine extends StateMachine { } return true; + case MESSAGE_PROCESS_SUPPORTED_APPLICATION_SETTINGS: + mAddressedPlayer.setSupportedPlayerApplicationSettings( + (PlayerApplicationSettings) msg.obj); + BluetoothMediaBrowserService.notifyChanged(mAddressedPlayer.getPlaybackState()); + return true; + + case MESSAGE_PROCESS_CURRENT_APPLICATION_SETTINGS: + mAddressedPlayer.setCurrentPlayerApplicationSettings( + (PlayerApplicationSettings) msg.obj); + BluetoothMediaBrowserService.notifyChanged(mAddressedPlayer.getPlaybackState()); + return true; + case DISCONNECT: transitionTo(mDisconnecting); return true; @@ -419,6 +444,20 @@ class AvrcpControllerStateMachine extends StateMachine { return (cmd == AvrcpControllerService.PASS_THRU_CMD_ID_REWIND) || (cmd == AvrcpControllerService.PASS_THRU_CMD_ID_FF); } + + private void setRepeat(int repeatMode) { + mService.setPlayerApplicationSettingValuesNative(mDeviceAddress, (byte) 1, + new byte[]{PlayerApplicationSettings.REPEAT_STATUS}, new byte[]{ + PlayerApplicationSettings.mapAvrcpPlayerSettingstoBTattribVal( + PlayerApplicationSettings.REPEAT_STATUS, repeatMode)}); + } + + private void setShuffle(int shuffleMode) { + mService.setPlayerApplicationSettingValuesNative(mDeviceAddress, (byte) 1, + new byte[]{PlayerApplicationSettings.SHUFFLE_STATUS}, new byte[]{ + PlayerApplicationSettings.mapAvrcpPlayerSettingstoBTattribVal( + PlayerApplicationSettings.SHUFFLE_STATUS, shuffleMode)}); + } } // Handle the get folder listing action @@ -538,7 +577,7 @@ class AvrcpControllerStateMachine extends StateMachine { case MESSAGE_GET_FOLDER_ITEMS: if (!mBrowseNode.equals(msg.obj)) { if (shouldAbort(mBrowseNode.getScope(), - ((BrowseTree.BrowseNode) msg.obj).getScope())) { + ((BrowseTree.BrowseNode) msg.obj).getScope())) { mAbort = true; } deferMessage(msg); @@ -564,7 +603,7 @@ class AvrcpControllerStateMachine extends StateMachine { default: logD(STATE_TAG + " deferring message " + msg.what - + " to connected!"); + + " to connected!"); deferMessage(msg); } return true; @@ -575,8 +614,8 @@ class AvrcpControllerStateMachine extends StateMachine { * necessary. * * @return true: a new folder in the same scope - * a new player while fetching contents of a folder - * false: other cases, specifically Now Playing while fetching a folder + * a new player while fetching contents of a folder + * false: other cases, specifically Now Playing while fetching a folder */ private boolean shouldAbort(int currentScope, int fetchScope) { if ((currentScope == fetchScope) @@ -697,7 +736,7 @@ class AvrcpControllerStateMachine extends StateMachine { mService.sendAbsVolRspNative(mDeviceAddress, absVol, label); } - MediaSession.Callback mSessionCallbacks = new MediaSession.Callback() { + MediaSessionCompat.Callback mSessionCallbacks = new MediaSessionCompat.Callback() { @Override public void onPlay() { logD("onPlay"); @@ -770,6 +809,19 @@ class AvrcpControllerStateMachine extends StateMachine { BrowseTree.BrowseNode node = mBrowseTree.findBrowseNodeByID(mediaId); sendMessage(MESSAGE_PLAY_ITEM, node); } + + @Override + public void onSetRepeatMode(int repeatMode) { + logD("onSetRepeatMode"); + sendMessage(MSG_AVRCP_SET_REPEAT, repeatMode); + } + + @Override + public void onSetShuffleMode(int shuffleMode) { + logD("onSetShuffleMode"); + sendMessage(MSG_AVRCP_SET_SHUFFLE, shuffleMode); + + } }; protected void broadcastConnectionStateChanged(int currentState) { diff --git a/src/com/android/bluetooth/avrcpcontroller/AvrcpPlayer.java b/src/com/android/bluetooth/avrcpcontroller/AvrcpPlayer.java index bed38d905..2ef3c1480 100644 --- a/src/com/android/bluetooth/avrcpcontroller/AvrcpPlayer.java +++ b/src/com/android/bluetooth/avrcpcontroller/AvrcpPlayer.java @@ -17,8 +17,9 @@ package com.android.bluetooth.avrcpcontroller; import android.media.MediaMetadata; -import android.media.session.PlaybackState; import android.os.SystemClock; +import android.support.v4.media.session.MediaSessionCompat; +import android.support.v4.media.session.PlaybackStateCompat; import android.util.Log; import java.util.Arrays; @@ -41,27 +42,31 @@ class AvrcpPlayer { public static final int FEATURE_PREVIOUS = 48; public static final int FEATURE_BROWSING = 59; - private int mPlayStatus = PlaybackState.STATE_NONE; - private long mPlayTime = PlaybackState.PLAYBACK_POSITION_UNKNOWN; + private int mPlayStatus = PlaybackStateCompat.STATE_NONE; + private long mPlayTime = PlaybackStateCompat.PLAYBACK_POSITION_UNKNOWN; private long mPlayTimeUpdate = 0; private float mPlaySpeed = 1; private int mId; private String mName = ""; private int mPlayerType; - private byte[] mPlayerFeatures; + private byte[] mPlayerFeatures = new byte[16]; private long mAvailableActions; private MediaMetadata mCurrentTrack; - private PlaybackState mPlaybackState; + private PlaybackStateCompat mPlaybackStateCompat; + private PlayerApplicationSettings mSupportedPlayerApplicationSettings = + new PlayerApplicationSettings(); + private PlayerApplicationSettings mCurrentPlayerApplicationSettings; AvrcpPlayer() { mId = INVALID_ID; //Set Default Actions in case Player data isn't available. - mAvailableActions = PlaybackState.ACTION_PAUSE | PlaybackState.ACTION_PLAY - | PlaybackState.ACTION_SKIP_TO_NEXT | PlaybackState.ACTION_SKIP_TO_PREVIOUS - | PlaybackState.ACTION_STOP; - PlaybackState.Builder playbackStateBuilder = new PlaybackState.Builder() + mAvailableActions = PlaybackStateCompat.ACTION_PAUSE | PlaybackStateCompat.ACTION_PLAY + | PlaybackStateCompat.ACTION_SKIP_TO_NEXT + | PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS + | PlaybackStateCompat.ACTION_STOP; + PlaybackStateCompat.Builder playbackStateBuilder = new PlaybackStateCompat.Builder() .setActions(mAvailableActions); - mPlaybackState = playbackStateBuilder.build(); + mPlaybackStateCompat = playbackStateBuilder.build(); } AvrcpPlayer(int id, String name, byte[] playerFeatures, int playStatus, int playerType) { @@ -70,10 +75,10 @@ class AvrcpPlayer { mPlayStatus = playStatus; mPlayerType = playerType; mPlayerFeatures = Arrays.copyOf(playerFeatures, playerFeatures.length); - updateAvailableActions(); - PlaybackState.Builder playbackStateBuilder = new PlaybackState.Builder() + PlaybackStateCompat.Builder playbackStateBuilder = new PlaybackStateCompat.Builder() .setActions(mAvailableActions); - mPlaybackState = playbackStateBuilder.build(); + mPlaybackStateCompat = playbackStateBuilder.build(); + updateAvailableActions(); } public int getId() { @@ -87,7 +92,8 @@ class AvrcpPlayer { public void setPlayTime(int playTime) { mPlayTime = playTime; mPlayTimeUpdate = SystemClock.elapsedRealtime(); - mPlaybackState = new PlaybackState.Builder(mPlaybackState).setState(mPlayStatus, mPlayTime, + mPlaybackStateCompat = new PlaybackStateCompat.Builder(mPlaybackStateCompat).setState( + mPlayStatus, mPlayTime, mPlaySpeed).build(); } @@ -97,30 +103,48 @@ class AvrcpPlayer { public void setPlayStatus(int playStatus) { mPlayTime += mPlaySpeed * (SystemClock.elapsedRealtime() - - mPlaybackState.getLastPositionUpdateTime()); + - mPlaybackStateCompat.getLastPositionUpdateTime()); mPlayStatus = playStatus; switch (mPlayStatus) { - case PlaybackState.STATE_STOPPED: + case PlaybackStateCompat.STATE_STOPPED: mPlaySpeed = 0; break; - case PlaybackState.STATE_PLAYING: + case PlaybackStateCompat.STATE_PLAYING: mPlaySpeed = 1; break; - case PlaybackState.STATE_PAUSED: + case PlaybackStateCompat.STATE_PAUSED: mPlaySpeed = 0; break; - case PlaybackState.STATE_FAST_FORWARDING: + case PlaybackStateCompat.STATE_FAST_FORWARDING: mPlaySpeed = 3; break; - case PlaybackState.STATE_REWINDING: + case PlaybackStateCompat.STATE_REWINDING: mPlaySpeed = -3; break; } - mPlaybackState = new PlaybackState.Builder(mPlaybackState).setState(mPlayStatus, mPlayTime, + mPlaybackStateCompat = new PlaybackStateCompat.Builder(mPlaybackStateCompat).setState( + mPlayStatus, mPlayTime, mPlaySpeed).build(); } + public void setSupportedPlayerApplicationSettings( + PlayerApplicationSettings playerApplicationSettings) { + mSupportedPlayerApplicationSettings = playerApplicationSettings; + updateAvailableActions(); + } + + public void setCurrentPlayerApplicationSettings( + PlayerApplicationSettings playerApplicationSettings) { + Log.d(TAG, "Settings changed"); + mCurrentPlayerApplicationSettings = playerApplicationSettings; + MediaSessionCompat session = BluetoothMediaBrowserService.getSession(); + session.setRepeatMode(mCurrentPlayerApplicationSettings.getSetting( + PlayerApplicationSettings.REPEAT_STATUS)); + session.setShuffleMode(mCurrentPlayerApplicationSettings.getSetting( + PlayerApplicationSettings.SHUFFLE_STATUS)); + } + public int getPlayStatus() { return mPlayStatus; } @@ -131,17 +155,22 @@ class AvrcpPlayer { return (mPlayerFeatures[byteNumber] & bitMask) == bitMask; } - public PlaybackState getPlaybackState() { + public boolean supportsSetting(int settingType, int settingValue) { + return mSupportedPlayerApplicationSettings.supportsSetting(settingType, settingValue); + } + + public PlaybackStateCompat getPlaybackState() { if (DBG) { Log.d(TAG, "getPlayBackState state " + mPlayStatus + " time " + mPlayTime); } - return mPlaybackState; + return mPlaybackStateCompat; } public synchronized void updateCurrentTrack(MediaMetadata update) { if (update != null) { long trackNumber = update.getLong(MediaMetadata.METADATA_KEY_TRACK_NUMBER); - mPlaybackState = new PlaybackState.Builder(mPlaybackState).setActiveQueueItemId( + mPlaybackStateCompat = new PlaybackStateCompat.Builder( + mPlaybackStateCompat).setActiveQueueItemId( trackNumber - 1).build(); } mCurrentTrack = update; @@ -153,26 +182,37 @@ class AvrcpPlayer { private void updateAvailableActions() { if (supportsFeature(FEATURE_PLAY)) { - mAvailableActions = mAvailableActions | PlaybackState.ACTION_PLAY; + mAvailableActions = mAvailableActions | PlaybackStateCompat.ACTION_PLAY; } if (supportsFeature(FEATURE_STOP)) { - mAvailableActions = mAvailableActions | PlaybackState.ACTION_STOP; + mAvailableActions = mAvailableActions | PlaybackStateCompat.ACTION_STOP; } if (supportsFeature(FEATURE_PAUSE)) { - mAvailableActions = mAvailableActions | PlaybackState.ACTION_PAUSE; + mAvailableActions = mAvailableActions | PlaybackStateCompat.ACTION_PAUSE; } if (supportsFeature(FEATURE_REWIND)) { - mAvailableActions = mAvailableActions | PlaybackState.ACTION_REWIND; + mAvailableActions = mAvailableActions | PlaybackStateCompat.ACTION_REWIND; } if (supportsFeature(FEATURE_FAST_FORWARD)) { - mAvailableActions = mAvailableActions | PlaybackState.ACTION_FAST_FORWARD; + mAvailableActions = mAvailableActions | PlaybackStateCompat.ACTION_FAST_FORWARD; } if (supportsFeature(FEATURE_FORWARD)) { - mAvailableActions = mAvailableActions | PlaybackState.ACTION_SKIP_TO_NEXT; + mAvailableActions = mAvailableActions | PlaybackStateCompat.ACTION_SKIP_TO_NEXT; } if (supportsFeature(FEATURE_PREVIOUS)) { - mAvailableActions = mAvailableActions | PlaybackState.ACTION_SKIP_TO_PREVIOUS; + mAvailableActions = mAvailableActions | PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS; } + if (mSupportedPlayerApplicationSettings.supportsSetting( + PlayerApplicationSettings.REPEAT_STATUS)) { + mAvailableActions |= PlaybackStateCompat.ACTION_SET_REPEAT_MODE; + } + if (mSupportedPlayerApplicationSettings.supportsSetting( + PlayerApplicationSettings.SHUFFLE_STATUS)) { + mAvailableActions |= PlaybackStateCompat.ACTION_SET_SHUFFLE_MODE; + } + mPlaybackStateCompat = new PlaybackStateCompat.Builder(mPlaybackStateCompat) + .setActions(mAvailableActions).build(); + if (DBG) Log.d(TAG, "Supported Actions = " + mAvailableActions); } } diff --git a/src/com/android/bluetooth/avrcpcontroller/BluetoothMediaBrowserService.java b/src/com/android/bluetooth/avrcpcontroller/BluetoothMediaBrowserService.java index 3504cd49c..774f95349 100644 --- a/src/com/android/bluetooth/avrcpcontroller/BluetoothMediaBrowserService.java +++ b/src/com/android/bluetooth/avrcpcontroller/BluetoothMediaBrowserService.java @@ -18,13 +18,17 @@ package com.android.bluetooth.avrcpcontroller; import android.media.MediaMetadata; import android.media.browse.MediaBrowser.MediaItem; -import android.media.session.MediaController; -import android.media.session.MediaSession; -import android.media.session.PlaybackState; import android.os.Bundle; -import android.service.media.MediaBrowserService; +import android.support.v4.media.MediaBrowserCompat; +import android.support.v4.media.MediaDescriptionCompat; +import android.support.v4.media.MediaMetadataCompat; +import android.support.v4.media.session.MediaControllerCompat; +import android.support.v4.media.session.MediaSessionCompat; +import android.support.v4.media.session.PlaybackStateCompat; import android.util.Log; +import androidx.media.MediaBrowserServiceCompat; + import com.android.bluetooth.R; import java.util.ArrayList; @@ -37,43 +41,44 @@ import java.util.List; * The applications are expected to use MediaBrowser (see API) and all the music * browsing/playback/metadata can be controlled via MediaBrowser and MediaController. * - * The current behavior of MediaSession exposed by this service is as follows: - * 1. MediaSession is active (i.e. SystemUI and other overview UIs can see updates) when device is - * connected and first starts playing. Before it starts playing we do not active the session. + * The current behavior of MediaSessionCompat exposed by this service is as follows: + * 1. MediaSessionCompat is active (i.e. SystemUI and other overview UIs can see updates) when + * device is connected and first starts playing. Before it starts playing we do not activate the + * session. * 1.1 The session is active throughout the duration of connection. * 2. The session is de-activated when the device disconnects. It will be connected again when (1) * happens. */ -public class BluetoothMediaBrowserService extends MediaBrowserService { +public class BluetoothMediaBrowserService extends MediaBrowserServiceCompat { private static final String TAG = "BluetoothMediaBrowserService"; private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG); private static BluetoothMediaBrowserService sBluetoothMediaBrowserService; - private MediaSession mSession; + private MediaSessionCompat mSession; // Browsing related structures. - private List mMediaQueue = new ArrayList<>(); + private List mMediaQueue = new ArrayList<>(); /** - * Initialize this BluetoothMediaBrowserService, creating our MediaSession, MediaPlayer and - * MediaMetaData, and setting up mechanisms to talk with the AvrcpControllerService. + * Initialize this BluetoothMediaBrowserService, creating our MediaSessionCompat, MediaPlayer + * and MediaMetaData, and setting up mechanisms to talk with the AvrcpControllerService. */ @Override public void onCreate() { if (DBG) Log.d(TAG, "onCreate"); super.onCreate(); - // Create and configure the MediaSession - mSession = new MediaSession(this, TAG); + // Create and configure the MediaSessionCompat + mSession = new MediaSessionCompat(this, TAG); setSessionToken(mSession.getSessionToken()); - mSession.setFlags(MediaSession.FLAG_HANDLES_MEDIA_BUTTONS - | MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS); + mSession.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS + | MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS); mSession.setQueueTitle(getString(R.string.bluetooth_a2dp_sink_queue_name)); mSession.setQueue(mMediaQueue); - PlaybackState.Builder playbackStateBuilder = new PlaybackState.Builder(); - playbackStateBuilder.setState(PlaybackState.STATE_ERROR, - PlaybackState.PLAYBACK_POSITION_UNKNOWN, 1.0f).setActions(0); + PlaybackStateCompat.Builder playbackStateBuilder = new PlaybackStateCompat.Builder(); + playbackStateBuilder.setState(PlaybackStateCompat.STATE_ERROR, + PlaybackStateCompat.PLAYBACK_POSITION_UNKNOWN, 1.0f).setActions(0); playbackStateBuilder.setErrorMessage(getString(R.string.bluetooth_disconnected)); mSession.setPlaybackState(playbackStateBuilder.build()); sBluetoothMediaBrowserService = this; @@ -91,9 +96,10 @@ public class BluetoothMediaBrowserService extends MediaBrowserService { @Override public synchronized void onLoadChildren(final String parentMediaId, - final Result> result) { + final Result> result) { if (DBG) Log.d(TAG, "onLoadChildren parentMediaId=" + parentMediaId); - List contents = getContents(parentMediaId); + List contents = + MediaBrowserCompat.MediaItem.fromMediaItemList(getContents(parentMediaId)); if (contents == null) { result.detach(); } else { @@ -112,7 +118,8 @@ public class BluetoothMediaBrowserService extends MediaBrowserService { mMediaQueue.clear(); if (songList != null) { for (MediaItem song : songList) { - mMediaQueue.add(new MediaSession.QueueItem(song.getDescription(), + mMediaQueue.add(new MediaSessionCompat.QueueItem( + MediaDescriptionCompat.fromMediaDescription(song.getDescription()), mMediaQueue.size())); } } @@ -129,7 +136,7 @@ public class BluetoothMediaBrowserService extends MediaBrowserService { } } - static synchronized void addressedPlayerChanged(MediaSession.Callback callback) { + static synchronized void addressedPlayerChanged(MediaSessionCompat.Callback callback) { if (sBluetoothMediaBrowserService != null) { sBluetoothMediaBrowserService.mSession.setCallback(callback); } else { @@ -139,13 +146,14 @@ public class BluetoothMediaBrowserService extends MediaBrowserService { static synchronized void trackChanged(MediaMetadata mediaMetadata) { if (sBluetoothMediaBrowserService != null) { - sBluetoothMediaBrowserService.mSession.setMetadata(mediaMetadata); + sBluetoothMediaBrowserService.mSession.setMetadata( + MediaMetadataCompat.fromMediaMetadata(mediaMetadata)); } else { Log.w(TAG, "trackChanged Unavailable"); } } - static synchronized void notifyChanged(PlaybackState playbackState) { + static synchronized void notifyChanged(PlaybackStateCompat playbackState) { Log.d(TAG, "notifyChanged PlaybackState" + playbackState); if (sBluetoothMediaBrowserService != null) { sBluetoothMediaBrowserService.mSession.setPlaybackState(playbackState); @@ -179,7 +187,7 @@ public class BluetoothMediaBrowserService extends MediaBrowserService { /** * Get object for controlling playback */ - public static synchronized MediaController.TransportControls getTransportControls() { + public static synchronized MediaControllerCompat.TransportControls getTransportControls() { if (sBluetoothMediaBrowserService != null) { return sBluetoothMediaBrowserService.mSession.getController().getTransportControls(); } else { @@ -198,4 +206,16 @@ public class BluetoothMediaBrowserService extends MediaBrowserService { Log.w(TAG, "setActive Unavailable"); } } + + /** + * Get Media session for updating state + */ + public static synchronized MediaSessionCompat getSession() { + if (sBluetoothMediaBrowserService != null) { + return sBluetoothMediaBrowserService.mSession; + } else { + Log.w(TAG, "getSession Unavailable"); + return null; + } + } } diff --git a/src/com/android/bluetooth/avrcpcontroller/PlayerApplicationSettings.java b/src/com/android/bluetooth/avrcpcontroller/PlayerApplicationSettings.java index c34a2d7d0..362548e5f 100644 --- a/src/com/android/bluetooth/avrcpcontroller/PlayerApplicationSettings.java +++ b/src/com/android/bluetooth/avrcpcontroller/PlayerApplicationSettings.java @@ -16,12 +16,11 @@ package com.android.bluetooth.avrcpcontroller; -import android.bluetooth.BluetoothAvrcpPlayerSettings; +import android.support.v4.media.session.PlaybackStateCompat; import android.util.Log; +import android.util.SparseArray; import java.util.ArrayList; -import java.util.HashMap; -import java.util.Map; /* * Contains information Player Application Setting extended from BluetootAvrcpPlayerSettings @@ -32,10 +31,10 @@ class PlayerApplicationSettings { /* * Values for SetPlayerApplicationSettings from AVRCP Spec V1.6 Appendix F. */ - private static final byte JNI_ATTRIB_EQUALIZER_STATUS = 0x01; - private static final byte JNI_ATTRIB_REPEAT_STATUS = 0x02; - private static final byte JNI_ATTRIB_SHUFFLE_STATUS = 0x03; - private static final byte JNI_ATTRIB_SCAN_STATUS = 0x04; + static final byte EQUALIZER_STATUS = 0x01; + static final byte REPEAT_STATUS = 0x02; + static final byte SHUFFLE_STATUS = 0x03; + static final byte SCAN_STATUS = 0x04; private static final byte JNI_EQUALIZER_STATUS_OFF = 0x01; private static final byte JNI_EQUALIZER_STATUS_ON = 0x02; @@ -55,18 +54,17 @@ class PlayerApplicationSettings { private static final byte JNI_STATUS_INVALID = -1; - /* * Hash map of current settings. */ - private Map mSettings = new HashMap(); + private SparseArray mSettings = new SparseArray<>(); /* * Hash map of supported values, a setting should be supported by the remote in order to enable * in mSettings. */ - private Map> mSupportedValues = - new HashMap>(); + private SparseArray> mSupportedValues = + new SparseArray>(); /* Convert from JNI array to Java classes. */ static PlayerApplicationSettings makeSupportedSettings(byte[] btAvrcpAttributeList) { @@ -82,8 +80,7 @@ class PlayerApplicationSettings { supportedValues.add( mapAttribIdValtoAvrcpPlayerSetting(attrId, btAvrcpAttributeList[i++])); } - newObj.mSupportedValues.put(mapBTAttribIdToAvrcpPlayerSettings(attrId), - supportedValues); + newObj.mSupportedValues.put(attrId, supportedValues); } } catch (ArrayIndexOutOfBoundsException exception) { Log.e(TAG, "makeSupportedSettings attributeList index error."); @@ -91,25 +88,13 @@ class PlayerApplicationSettings { return newObj; } - public BluetoothAvrcpPlayerSettings getAvrcpSettings() { - int supportedSettings = 0; - for (Integer setting : mSettings.keySet()) { - supportedSettings |= setting; - } - BluetoothAvrcpPlayerSettings result = new BluetoothAvrcpPlayerSettings(supportedSettings); - for (Integer setting : mSettings.keySet()) { - result.addSettingValue(setting, mSettings.get(setting)); - } - return result; - } - static PlayerApplicationSettings makeSettings(byte[] btAvrcpAttributeList) { PlayerApplicationSettings newObj = new PlayerApplicationSettings(); try { for (int i = 0; i < btAvrcpAttributeList.length; ) { byte attrId = btAvrcpAttributeList[i++]; - newObj.mSettings.put(mapBTAttribIdToAvrcpPlayerSettings(attrId), + newObj.mSettings.put(attrId, mapAttribIdValtoAvrcpPlayerSetting(attrId, btAvrcpAttributeList[i++])); } } catch (ArrayIndexOutOfBoundsException exception) { @@ -123,177 +108,69 @@ class PlayerApplicationSettings { mSupportedValues = updates.mSupportedValues; } - public void setValues(BluetoothAvrcpPlayerSettings updates) { - int supportedSettings = updates.getSettings(); - for (int i = 1; i <= BluetoothAvrcpPlayerSettings.SETTING_SCAN; i++) { - if ((i & supportedSettings) > 0) { - mSettings.put(i, updates.getSettingValue(i)); - } - } + public boolean supportsSetting(int settingType, int settingValue) { + if (null == mSupportedValues.get(settingType)) return false; + return mSupportedValues.valueAt(settingType).contains(settingValue); } - /* - * Check through all settings to ensure that they are all available to be set and then check - * that the desired value is in fact supported by our remote player. - */ - public boolean supportsSettings(BluetoothAvrcpPlayerSettings settingsToCheck) { - int settingSubset = settingsToCheck.getSettings(); - int supportedSettings = 0; - for (Integer setting : mSupportedValues.keySet()) { - supportedSettings |= setting; - } - try { - if ((supportedSettings & settingSubset) == settingSubset) { - for (Integer settingId : mSettings.keySet()) { - // The setting is in both settings to check and supported settings but the - // value is not supported. - if ((settingId & settingSubset) == settingId && (!mSupportedValues.get( - settingId).contains(settingsToCheck.getSettingValue(settingId)))) { - return false; - } - } - return true; - } - } catch (NullPointerException e) { - Log.e(TAG, - "supportsSettings received a supported setting that has no supported values."); - } - return false; + public boolean supportsSetting(int settingType) { + return (null != mSupportedValues.get(settingType)); } - // Convert currently desired settings into an attribute array to pass to the native layer to - // enable them. - public ArrayList getNativeSettings() { - int i = 0; - ArrayList attribArray = new ArrayList(); - for (Integer settingId : mSettings.keySet()) { - switch (settingId) { - case BluetoothAvrcpPlayerSettings.SETTING_EQUALIZER: - attribArray.add(JNI_ATTRIB_EQUALIZER_STATUS); - attribArray.add(mapAvrcpPlayerSettingstoBTattribVal(settingId, - mSettings.get(settingId))); - break; - case BluetoothAvrcpPlayerSettings.SETTING_REPEAT: - attribArray.add(JNI_ATTRIB_REPEAT_STATUS); - attribArray.add(mapAvrcpPlayerSettingstoBTattribVal(settingId, - mSettings.get(settingId))); - break; - case BluetoothAvrcpPlayerSettings.SETTING_SHUFFLE: - attribArray.add(JNI_ATTRIB_SHUFFLE_STATUS); - attribArray.add(mapAvrcpPlayerSettingstoBTattribVal(settingId, - mSettings.get(settingId))); - break; - case BluetoothAvrcpPlayerSettings.SETTING_SCAN: - attribArray.add(JNI_ATTRIB_SCAN_STATUS); - attribArray.add(mapAvrcpPlayerSettingstoBTattribVal(settingId, - mSettings.get(settingId))); - break; - default: - Log.w(TAG, "Unknown setting found in getNativeSettings: " + settingId); - } - } - return attribArray; + public int getSetting(int settingType) { + if (null == mSettings.get(settingType)) return -1; + return mSettings.get(settingType); } // Convert a native Attribute Id/Value pair into the AVRCP equivalent value. private static int mapAttribIdValtoAvrcpPlayerSetting(byte attribId, byte attribVal) { - if (attribId == JNI_ATTRIB_EQUALIZER_STATUS) { - switch (attribVal) { - case JNI_EQUALIZER_STATUS_OFF: - return BluetoothAvrcpPlayerSettings.STATE_OFF; - case JNI_EQUALIZER_STATUS_ON: - return BluetoothAvrcpPlayerSettings.STATE_ON; - } - } else if (attribId == JNI_ATTRIB_REPEAT_STATUS) { + if (attribId == REPEAT_STATUS) { switch (attribVal) { case JNI_REPEAT_STATUS_ALL_TRACK_REPEAT: - return BluetoothAvrcpPlayerSettings.STATE_ALL_TRACK; + return PlaybackStateCompat.REPEAT_MODE_ALL; case JNI_REPEAT_STATUS_GROUP_REPEAT: - return BluetoothAvrcpPlayerSettings.STATE_GROUP; + return PlaybackStateCompat.REPEAT_MODE_GROUP; case JNI_REPEAT_STATUS_OFF: - return BluetoothAvrcpPlayerSettings.STATE_OFF; + return PlaybackStateCompat.REPEAT_MODE_NONE; case JNI_REPEAT_STATUS_SINGLE_TRACK_REPEAT: - return BluetoothAvrcpPlayerSettings.STATE_SINGLE_TRACK; - } - } else if (attribId == JNI_ATTRIB_SCAN_STATUS) { - switch (attribVal) { - case JNI_SCAN_STATUS_ALL_TRACK_SCAN: - return BluetoothAvrcpPlayerSettings.STATE_ALL_TRACK; - case JNI_SCAN_STATUS_GROUP_SCAN: - return BluetoothAvrcpPlayerSettings.STATE_GROUP; - case JNI_SCAN_STATUS_OFF: - return BluetoothAvrcpPlayerSettings.STATE_OFF; + return PlaybackStateCompat.REPEAT_MODE_ONE; } - } else if (attribId == JNI_ATTRIB_SHUFFLE_STATUS) { + } else if (attribId == SHUFFLE_STATUS) { switch (attribVal) { case JNI_SHUFFLE_STATUS_ALL_TRACK_SHUFFLE: - return BluetoothAvrcpPlayerSettings.STATE_ALL_TRACK; + return PlaybackStateCompat.SHUFFLE_MODE_ALL; case JNI_SHUFFLE_STATUS_GROUP_SHUFFLE: - return BluetoothAvrcpPlayerSettings.STATE_GROUP; + return PlaybackStateCompat.SHUFFLE_MODE_GROUP; case JNI_SHUFFLE_STATUS_OFF: - return BluetoothAvrcpPlayerSettings.STATE_OFF; + return PlaybackStateCompat.SHUFFLE_MODE_NONE; } } - return BluetoothAvrcpPlayerSettings.STATE_INVALID; + return JNI_STATUS_INVALID; } // Convert an AVRCP Setting/Value pair into the native equivalent value; - private static byte mapAvrcpPlayerSettingstoBTattribVal(int mSetting, int mSettingVal) { - if (mSetting == BluetoothAvrcpPlayerSettings.SETTING_EQUALIZER) { - switch (mSettingVal) { - case BluetoothAvrcpPlayerSettings.STATE_OFF: - return JNI_EQUALIZER_STATUS_OFF; - case BluetoothAvrcpPlayerSettings.STATE_ON: - return JNI_EQUALIZER_STATUS_ON; - } - } else if (mSetting == BluetoothAvrcpPlayerSettings.SETTING_REPEAT) { + static byte mapAvrcpPlayerSettingstoBTattribVal(int mSetting, int mSettingVal) { + if (mSetting == REPEAT_STATUS) { switch (mSettingVal) { - case BluetoothAvrcpPlayerSettings.STATE_OFF: + case PlaybackStateCompat.REPEAT_MODE_NONE: return JNI_REPEAT_STATUS_OFF; - case BluetoothAvrcpPlayerSettings.STATE_SINGLE_TRACK: + case PlaybackStateCompat.REPEAT_MODE_ONE: return JNI_REPEAT_STATUS_SINGLE_TRACK_REPEAT; - case BluetoothAvrcpPlayerSettings.STATE_ALL_TRACK: + case PlaybackStateCompat.REPEAT_MODE_ALL: return JNI_REPEAT_STATUS_ALL_TRACK_REPEAT; - case BluetoothAvrcpPlayerSettings.STATE_GROUP: + case PlaybackStateCompat.REPEAT_MODE_GROUP: return JNI_REPEAT_STATUS_GROUP_REPEAT; } - } else if (mSetting == BluetoothAvrcpPlayerSettings.SETTING_SHUFFLE) { + } else if (mSetting == SHUFFLE_STATUS) { switch (mSettingVal) { - case BluetoothAvrcpPlayerSettings.STATE_OFF: + case PlaybackStateCompat.SHUFFLE_MODE_NONE: return JNI_SHUFFLE_STATUS_OFF; - case BluetoothAvrcpPlayerSettings.STATE_ALL_TRACK: + case PlaybackStateCompat.SHUFFLE_MODE_ALL: return JNI_SHUFFLE_STATUS_ALL_TRACK_SHUFFLE; - case BluetoothAvrcpPlayerSettings.STATE_GROUP: + case PlaybackStateCompat.SHUFFLE_MODE_GROUP: return JNI_SHUFFLE_STATUS_GROUP_SHUFFLE; } - } else if (mSetting == BluetoothAvrcpPlayerSettings.SETTING_SCAN) { - switch (mSettingVal) { - case BluetoothAvrcpPlayerSettings.STATE_OFF: - return JNI_SCAN_STATUS_OFF; - case BluetoothAvrcpPlayerSettings.STATE_ALL_TRACK: - return JNI_SCAN_STATUS_ALL_TRACK_SCAN; - case BluetoothAvrcpPlayerSettings.STATE_GROUP: - return JNI_SCAN_STATUS_GROUP_SCAN; - } } return JNI_STATUS_INVALID; } - - // convert a native Attribute Id into the AVRCP Setting equivalent value; - private static int mapBTAttribIdToAvrcpPlayerSettings(byte attribId) { - switch (attribId) { - case JNI_ATTRIB_EQUALIZER_STATUS: - return BluetoothAvrcpPlayerSettings.SETTING_EQUALIZER; - case JNI_ATTRIB_REPEAT_STATUS: - return BluetoothAvrcpPlayerSettings.SETTING_REPEAT; - case JNI_ATTRIB_SHUFFLE_STATUS: - return BluetoothAvrcpPlayerSettings.SETTING_SHUFFLE; - case JNI_ATTRIB_SCAN_STATUS: - return BluetoothAvrcpPlayerSettings.SETTING_SCAN; - default: - return BluetoothAvrcpPlayerSettings.STATE_INVALID; - } - } - } - diff --git a/tests/unit/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachineTest.java b/tests/unit/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachineTest.java index b1d1743d0..0ebb31e9e 100644 --- a/tests/unit/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachineTest.java +++ b/tests/unit/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachineTest.java @@ -23,8 +23,8 @@ import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothProfile; import android.content.Context; import android.content.Intent; -import android.media.session.MediaController; import android.os.Looper; +import android.support.v4.media.session.MediaControllerCompat; import androidx.test.InstrumentationRegistry; import androidx.test.filters.MediumTest; @@ -213,7 +213,7 @@ public class AvrcpControllerStateMachineTest { @Test public void testPlay() throws Exception { setUpConnectedState(true, true); - MediaController.TransportControls transportControls = + MediaControllerCompat.TransportControls transportControls = BluetoothMediaBrowserService.getTransportControls(); //Play @@ -232,7 +232,7 @@ public class AvrcpControllerStateMachineTest { @Test public void testPause() throws Exception { setUpConnectedState(true, true); - MediaController.TransportControls transportControls = + MediaControllerCompat.TransportControls transportControls = BluetoothMediaBrowserService.getTransportControls(); //Pause @@ -251,7 +251,7 @@ public class AvrcpControllerStateMachineTest { @Test public void testStop() throws Exception { setUpConnectedState(true, true); - MediaController.TransportControls transportControls = + MediaControllerCompat.TransportControls transportControls = BluetoothMediaBrowserService.getTransportControls(); //Stop @@ -270,7 +270,7 @@ public class AvrcpControllerStateMachineTest { @Test public void testNext() throws Exception { setUpConnectedState(true, true); - MediaController.TransportControls transportControls = + MediaControllerCompat.TransportControls transportControls = BluetoothMediaBrowserService.getTransportControls(); //Next @@ -290,7 +290,7 @@ public class AvrcpControllerStateMachineTest { @Test public void testPrevious() throws Exception { setUpConnectedState(true, true); - MediaController.TransportControls transportControls = + MediaControllerCompat.TransportControls transportControls = BluetoothMediaBrowserService.getTransportControls(); //Previous @@ -310,7 +310,7 @@ public class AvrcpControllerStateMachineTest { @Test public void testFastForward() throws Exception { setUpConnectedState(true, true); - MediaController.TransportControls transportControls = + MediaControllerCompat.TransportControls transportControls = BluetoothMediaBrowserService.getTransportControls(); //FastForward @@ -331,7 +331,7 @@ public class AvrcpControllerStateMachineTest { @Test public void testRewind() throws Exception { setUpConnectedState(true, true); - MediaController.TransportControls transportControls = + MediaControllerCompat.TransportControls transportControls = BluetoothMediaBrowserService.getTransportControls(); //Rewind @@ -346,6 +346,44 @@ public class AvrcpControllerStateMachineTest { eq(mTestAddress), eq(AvrcpControllerService.PASS_THRU_CMD_ID_REWIND), eq(KEY_UP)); } + /** + * Test media browser shuffle command + */ + @Test + public void testShuffle() throws Exception { + byte[] shuffleSetting = new byte[]{3}; + byte[] shuffleMode = new byte[]{2}; + + setUpConnectedState(true, true); + MediaControllerCompat.TransportControls transportControls = + BluetoothMediaBrowserService.getTransportControls(); + + //Shuffle + transportControls.setShuffleMode(1); + verify(mAvrcpControllerService, timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)) + .setPlayerApplicationSettingValuesNative( + eq(mTestAddress), eq((byte) 1), eq(shuffleSetting), eq(shuffleMode)); + } + + /** + * Test media browser repeat command + */ + @Test + public void testRepeat() throws Exception { + byte[] repeatSetting = new byte[]{2}; + byte[] repeatMode = new byte[]{3}; + + setUpConnectedState(true, true); + MediaControllerCompat.TransportControls transportControls = + BluetoothMediaBrowserService.getTransportControls(); + + //Shuffle + transportControls.setRepeatMode(2); + verify(mAvrcpControllerService, timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)) + .setPlayerApplicationSettingValuesNative( + eq(mTestAddress), eq((byte) 1), eq(repeatSetting), eq(repeatMode)); + } + /** * Test media browsing * Verify that a browse tree is created with the proper root @@ -475,7 +513,7 @@ public class AvrcpControllerStateMachineTest { BrowseTree.BrowseNode playerNodes = mAvrcpStateMachine.findNode(results.getID()); mAvrcpStateMachine.requestContents(results); - MediaController.TransportControls transportControls = + MediaControllerCompat.TransportControls transportControls = BluetoothMediaBrowserService.getTransportControls(); transportControls.play(); verify(mAvrcpControllerService, -- cgit v1.2.3 From 4f32edc6e08126dff80f25661a1dbac745d2b940 Mon Sep 17 00:00:00 2001 From: Sal Savage Date: Fri, 26 Jul 2019 12:45:09 -0700 Subject: Use Calendar.Builder for MAP Client timestamp parsing MAP Client was reporting different timestamps for a given message-listing object each time getUnreadMessages() was called. The milliseconds value was always different. This was a result of the Calendar object pre-populating all fields with the current date-time. The reported date-time would always have the current time's milliseconds field. Datetime parsing has been updated to use a Calendar.Builder which is clearer and doesn't use the current time when initializing. The milliseconds field is explicitly set to zero. Tests were added for the ObexTime class to prevent this from happening in the future. Bug: b/135606822 Test: atest ObexTimeTest.java Change-Id: Ie872b4bb30f68e0c00b15767eb0413a6e67ba630 (cherry picked from commit 85fb0fe5d730d93850c7db0bc7eb2dc26d2ced50) Merged-In: Ie872b4bb30f68e0c00b15767eb0413a6e67ba630 Change-Id: Iab4e3cf5937958f500d58b9ba6fa64c439cd4dc9 --- .../android/bluetooth/mapclient/obex/ObexTime.java | 43 +++++--- .../android/bluetooth/mapclient/ObexTimeTest.java | 114 +++++++++++++++++++++ 2 files changed, 143 insertions(+), 14 deletions(-) create mode 100644 tests/unit/src/com/android/bluetooth/mapclient/ObexTimeTest.java diff --git a/src/com/android/bluetooth/mapclient/obex/ObexTime.java b/src/com/android/bluetooth/mapclient/obex/ObexTime.java index 42a32c10f..cc58a5144 100644 --- a/src/com/android/bluetooth/mapclient/obex/ObexTime.java +++ b/src/com/android/bluetooth/mapclient/obex/ObexTime.java @@ -29,8 +29,17 @@ public final class ObexTime { public ObexTime(String time) { /* - * match OBEX time string: YYYYMMDDTHHMMSS with optional UTF offset - * +/-hhmm + * Match OBEX time string: YYYYMMDDTHHMMSS with optional UTF offset +/-hhmm + * + * Matched groups are numberes as follows: + * + * YYYY MM DD T HH MM SS + hh mm + * ^^^^ ^^ ^^ ^^ ^^ ^^ ^ ^^ ^^ + * 1 2 3 4 5 6 8 9 10 + * |---7---| + * + * All groups are guaranteed to be numeric so conversion will always succeed (except group 8 + * which is either + or -) */ Pattern p = Pattern.compile( "(\\d{4})(\\d{2})(\\d{2})T(\\d{2})(\\d{2})(\\d{2})(([+-])(\\d{2})(\\d{2})" + ")?"); @@ -39,20 +48,26 @@ public final class ObexTime { if (m.matches()) { /* - * matched groups are numberes as follows: YYYY MM DD T HH MM SS + - * hh mm ^^^^ ^^ ^^ ^^ ^^ ^^ ^ ^^ ^^ 1 2 3 4 5 6 8 9 10 all groups - * are guaranteed to be numeric so conversion will always succeed - * (except group 8 which is either + or -) + * MAP spec says to default to "Local Time basis" for a message listing timestamp. We'll + * use the system default timezone and assume it knows best what our local timezone is. + * The builder defaults to the default locale and timezone if none is provided. */ + Calendar.Builder builder = new Calendar.Builder(); - Calendar cal = Calendar.getInstance(); - cal.set(Integer.parseInt(m.group(1)), Integer.parseInt(m.group(2)) - 1, - Integer.parseInt(m.group(3)), Integer.parseInt(m.group(4)), - Integer.parseInt(m.group(5)), Integer.parseInt(m.group(6))); + /* Note that Calendar months are zero-based */ + builder.setDate(Integer.parseInt(m.group(1)), /* year */ + Integer.parseInt(m.group(2)) - 1, /* month */ + Integer.parseInt(m.group(3))); /* day of month */ + + /* Note the MAP timestamp doesn't have milliseconds and we're explicitly setting to 0 */ + builder.setTimeOfDay(Integer.parseInt(m.group(4)), /* hours */ + Integer.parseInt(m.group(5)), /* minutes */ + Integer.parseInt(m.group(6)), /* seconds */ + 0); /* milliseconds */ /* - * if 7th group is matched then we have UTC offset information - * included + * If 7th group is matched then we're no longer using "Local Time basis" and instead + * have a UTC based timestamp and offset information included */ if (m.group(7) != null) { int ohh = Integer.parseInt(m.group(9)); @@ -68,10 +83,10 @@ public final class ObexTime { TimeZone tz = TimeZone.getTimeZone("UTC"); tz.setRawOffset(offset); - cal.setTimeZone(tz); + builder.setTimeZone(tz); } - mDate = cal.getTime(); + mDate = builder.build().getTime(); } } diff --git a/tests/unit/src/com/android/bluetooth/mapclient/ObexTimeTest.java b/tests/unit/src/com/android/bluetooth/mapclient/ObexTimeTest.java new file mode 100644 index 000000000..5ef2d455b --- /dev/null +++ b/tests/unit/src/com/android/bluetooth/mapclient/ObexTimeTest.java @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2019 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.android.bluetooth.mapclient; + +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.Date; +import java.util.TimeZone; + +@RunWith(AndroidJUnit4.class) +public class ObexTimeTest { + private static final String TAG = ObexTimeTest.class.getSimpleName(); + + private static final String VALID_TIME_STRING = "20190101T121314"; + private static final String VALID_TIME_STRING_WITH_OFFSET_POS = "20190101T121314+0130"; + private static final String VALID_TIME_STRING_WITH_OFFSET_NEG = "20190101T121314-0130"; + + private static final String INVALID_TIME_STRING_OFFSET_EXTRA_DIGITS = "20190101T121314-99999"; + private static final String INVALID_TIME_STRING_BAD_DELIMITER = "20190101Q121314"; + + // MAP message listing times, per spec, use "local time basis" if UTC offset isn't given. The + // ObexTime class parses using the current default timezone (assumed to be the "local timezone") + // in the case that UTC isn't provided. However, the Date class assumes UTC ALWAYS when + // initializing off of a long value. We have to take that into account when determining our + // expected results for time strings that don't have an offset. + private static final long LOCAL_TIMEZONE_OFFSET = TimeZone.getDefault().getRawOffset(); + + // If you are a positive offset from GMT then GMT is in the "past" and you need to subtract that + // offset from the time. If you are negative then GMT is in the future and you need to add that + // offset to the time. + private static final long VALID_TS = 1546344794000L; // Jan 01, 2019 at 12:13:14 GMT + private static final long TS_OFFSET = 5400000L; // 1 Hour, 30 minutes -> milliseconds + private static final long VALID_TS_LOCAL_TZ = VALID_TS - LOCAL_TIMEZONE_OFFSET; + private static final long VALID_TS_OFFSET_POS = VALID_TS - TS_OFFSET; + private static final long VALID_TS_OFFSET_NEG = VALID_TS + TS_OFFSET; + + private static final Date VALID_DATE_LOCAL_TZ = new Date(VALID_TS_LOCAL_TZ); + private static final Date VALID_DATE_WITH_OFFSET_POS = new Date(VALID_TS_OFFSET_POS); + private static final Date VALID_DATE_WITH_OFFSET_NEG = new Date(VALID_TS_OFFSET_NEG); + + @Test + public void createWithValidDateTimeString_TimestampCorrect() { + ObexTime timestamp = new ObexTime(VALID_TIME_STRING); + Assert.assertEquals("Parsed timestamp must match expected", VALID_DATE_LOCAL_TZ, + timestamp.getTime()); + } + + @Test + public void createWithValidDateTimeStringWithPosOffset_TimestampCorrect() { + ObexTime timestamp = new ObexTime(VALID_TIME_STRING_WITH_OFFSET_POS); + Assert.assertEquals("Parsed timestamp must match expected", VALID_DATE_WITH_OFFSET_POS, + timestamp.getTime()); + } + + @Test + public void createWithValidDateTimeStringWithNegOffset_TimestampCorrect() { + ObexTime timestamp = new ObexTime(VALID_TIME_STRING_WITH_OFFSET_NEG); + Assert.assertEquals("Parsed timestamp must match expected", VALID_DATE_WITH_OFFSET_NEG, + timestamp.getTime()); + } + + @Test + public void createWithValidDate_TimestampCorrect() { + ObexTime timestamp = new ObexTime(VALID_DATE_LOCAL_TZ); + Assert.assertEquals("ObexTime created with a date must return the same date", + VALID_DATE_LOCAL_TZ, timestamp.getTime()); + } + + @Test + public void printValidTime_TimestampMatchesInput() { + ObexTime timestamp = new ObexTime(VALID_TIME_STRING); + Assert.assertEquals("Timestamp as a string must match the input string", VALID_TIME_STRING, + timestamp.toString()); + } + + @Test + public void createWithInvalidDelimiterString_TimestampIsNull() { + ObexTime timestamp = new ObexTime(INVALID_TIME_STRING_BAD_DELIMITER); + Assert.assertEquals("Parsed timestamp was invalid and must result in a null object", null, + timestamp.getTime()); + } + + @Test + public void createWithInvalidOffsetString_TimestampIsNull() { + ObexTime timestamp = new ObexTime(INVALID_TIME_STRING_OFFSET_EXTRA_DIGITS); + Assert.assertEquals("Parsed timestamp was invalid and must result in a null object", null, + timestamp.getTime()); + } + + @Test + public void printInvalidTime_ReturnsNull() { + ObexTime timestamp = new ObexTime(INVALID_TIME_STRING_BAD_DELIMITER); + Assert.assertEquals("Invalid timestamps must return null for toString()", null, + timestamp.toString()); + } +} -- cgit v1.2.3 From 4d931c8105fd62a7e2290f2e0e37d601fd0942d2 Mon Sep 17 00:00:00 2001 From: Andrew Cheng Date: Tue, 23 Jul 2019 10:05:33 -0700 Subject: Splitting over-the-air downloading into batches Splitting the over-the-air downloading of contacts into batches to ameliorate PBAP crash when system goes to S2R while download is in progress. Test: Use ACTS/SL4A: act.py -c .json -tc BtCarPbapTest:test_contact_download Bug: b/117588939 Change-Id: I7d138a7d497c04c54710702320ba1fdea8f15ea7 (cherry picked from commit 1128539a1aced99db216bd86080dbab9c2b710ed) Merged-In: I7d138a7d497c04c54710702320ba1fdea8f15ea7 Change-Id: Idd1b1b5b22360ab36b05ddfbf62e5043e873098d --- .../BluetoothPbapRequestPullPhoneBook.java | 2 +- .../BluetoothPbapRequestPullPhoneBookSize.java | 66 +++++++++++++++++ .../pbapclient/PbapClientConnectionHandler.java | 83 ++++++++++++++++------ 3 files changed, 127 insertions(+), 24 deletions(-) create mode 100644 src/com/android/bluetooth/pbapclient/BluetoothPbapRequestPullPhoneBookSize.java diff --git a/src/com/android/bluetooth/pbapclient/BluetoothPbapRequestPullPhoneBook.java b/src/com/android/bluetooth/pbapclient/BluetoothPbapRequestPullPhoneBook.java index 34ab7d52c..a5b3fbfea 100644 --- a/src/com/android/bluetooth/pbapclient/BluetoothPbapRequestPullPhoneBook.java +++ b/src/com/android/bluetooth/pbapclient/BluetoothPbapRequestPullPhoneBook.java @@ -73,7 +73,7 @@ final class BluetoothPbapRequestPullPhoneBook extends BluetoothPbapRequest { oap.add(OAP_TAGID_FORMAT, format); /* - * maxListCount is a special case which is handled in + * maxListCount == 0 is a special case which is handled in * BluetoothPbapRequestPullPhoneBookSize */ if (maxListCount > 0) { diff --git a/src/com/android/bluetooth/pbapclient/BluetoothPbapRequestPullPhoneBookSize.java b/src/com/android/bluetooth/pbapclient/BluetoothPbapRequestPullPhoneBookSize.java new file mode 100644 index 000000000..f0706610f --- /dev/null +++ b/src/com/android/bluetooth/pbapclient/BluetoothPbapRequestPullPhoneBookSize.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2016 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.android.bluetooth.pbapclient; + +import android.util.Log; + +import javax.obex.HeaderSet; + +final class BluetoothPbapRequestPullPhoneBookSize extends BluetoothPbapRequest { + + private static final boolean VDBG = Utils.VDBG; + + private static final String TAG = "BtPbapReqPullPhoneBookSize"; + + private static final String TYPE = "x-bt/phonebook"; + + private int mSize; + + BluetoothPbapRequestPullPhoneBookSize(String pbName, long filter) { + mHeaderSet.setHeader(HeaderSet.NAME, pbName); + + mHeaderSet.setHeader(HeaderSet.TYPE, TYPE); + + ObexAppParameters oap = new ObexAppParameters(); + // Set MaxListCount in the request to 0 to get PhonebookSize in the response. + // If a vCardSelector is present in the request, then the result shall + // contain the number of items that satisfy the selector’s criteria. + // See PBAP v1.2.3, Sec. 5.1.4.5. + oap.add(OAP_TAGID_MAX_LIST_COUNT, (short) 0); + if (filter != 0) { + oap.add(OAP_TAGID_FILTER, filter); + } + oap.addToHeaderSet(mHeaderSet); + } + + @Override + protected void readResponseHeaders(HeaderSet headerset) { + if (VDBG) { + Log.v(TAG, "readResponseHeaders"); + } + + ObexAppParameters oap = ObexAppParameters.fromHeaderSet(headerset); + + if (oap.exists(OAP_TAGID_PHONEBOOK_SIZE)) { + mSize = oap.getShort(OAP_TAGID_PHONEBOOK_SIZE); + } + } + + public int getSize() { + return mSize; + } +} diff --git a/src/com/android/bluetooth/pbapclient/PbapClientConnectionHandler.java b/src/com/android/bluetooth/pbapclient/PbapClientConnectionHandler.java index 914b5b163..43d384dfc 100644 --- a/src/com/android/bluetooth/pbapclient/PbapClientConnectionHandler.java +++ b/src/com/android/bluetooth/pbapclient/PbapClientConnectionHandler.java @@ -46,6 +46,15 @@ import javax.obex.ResponseCodes; * controlling state machine. */ class PbapClientConnectionHandler extends Handler { + // Tradeoff: larger BATCH_SIZE leads to faster download rates, while smaller + // BATCH_SIZE is less prone to IO Exceptions if there is a download in + // progress when Bluetooth stack is torn down. + private static final int DEFAULT_BATCH_SIZE = 250; + + // Upper limit on the indices of the vcf cards/entries, inclusive, + // i.e., valid indices are [0, 1, ... , UPPER_LIMIT] + private static final int UPPER_LIMIT = 65535; + static final String TAG = "PbapClientConnHandler"; static final boolean DBG = Utils.DBG; static final boolean VDBG = Utils.VDBG; @@ -239,29 +248,12 @@ class PbapClientConnectionHandler extends Handler { break; case MSG_DOWNLOAD: - try { - mAccountCreated = addAccount(mAccount); - if (!mAccountCreated) { - Log.e(TAG, "Account creation failed."); - return; - } - // Start at contact 1 to exclued Owner Card PBAP 1.1 sec 3.1.5.2 - BluetoothPbapRequestPullPhoneBook request = - new BluetoothPbapRequestPullPhoneBook(PB_PATH, mAccount, - PBAP_REQUESTED_FIELDS, VCARD_TYPE_30, 0, 1); - request.execute(mObexSession); - PhonebookPullRequest processor = - new PhonebookPullRequest(mPbapClientStateMachine.getContext(), - mAccount); - processor.setResults(request.getList()); - processor.onPullComplete(); - HashMap callCounter = new HashMap<>(); - downloadCallLog(MCH_PATH, callCounter); - downloadCallLog(ICH_PATH, callCounter); - downloadCallLog(OCH_PATH, callCounter); - } catch (IOException e) { - Log.w(TAG, "DOWNLOAD_CONTACTS Failure" + e.toString()); - } + downloadContacts(); + + HashMap callCounter = new HashMap<>(); + downloadCallLog(MCH_PATH, callCounter); + downloadCallLog(ICH_PATH, callCounter); + downloadCallLog(OCH_PATH, callCounter); break; default: @@ -369,6 +361,51 @@ class PbapClientConnectionHandler extends Handler { } } + void downloadContacts() { + try { + mAccountCreated = addAccount(mAccount); + if (!mAccountCreated) { + Log.e(TAG, "Account creation failed."); + return; + } + PhonebookPullRequest processor = + new PhonebookPullRequest(mPbapClientStateMachine.getContext(), + mAccount); + + // Download contacts in batches of size DEFAULT_BATCH_SIZE + BluetoothPbapRequestPullPhoneBookSize requestPbSize = + new BluetoothPbapRequestPullPhoneBookSize(PB_PATH, + PBAP_REQUESTED_FIELDS); + requestPbSize.execute(mObexSession); + + // "-1" because Owner Card is also included in PhoneBookSize + int numberOfContactsRemaining = requestPbSize.getSize() - 1; + + // Start at contact 1 to exclude Owner Card PBAP 1.1 sec 3.1.5.2 + int startOffset = 1; + while ((numberOfContactsRemaining > 0) && (startOffset <= UPPER_LIMIT)) { + int numberOfContactsToDownload = + Math.min(Math.min(DEFAULT_BATCH_SIZE, numberOfContactsRemaining), + UPPER_LIMIT - startOffset + 1); + BluetoothPbapRequestPullPhoneBook request = + new BluetoothPbapRequestPullPhoneBook(PB_PATH, mAccount, + PBAP_REQUESTED_FIELDS, VCARD_TYPE_30, + numberOfContactsToDownload, startOffset); + request.execute(mObexSession); + processor.setResults(request.getList()); + processor.onPullComplete(); + + startOffset += numberOfContactsToDownload; + numberOfContactsRemaining -= numberOfContactsToDownload; + } + if ((startOffset > UPPER_LIMIT) && (numberOfContactsRemaining > 0)) { + Log.w(TAG, "Download contacts incomplete, index exceeded upper limit."); + } + } catch (IOException e) { + Log.w(TAG, "Download contacts failure" + e.toString()); + } + } + void downloadCallLog(String path, HashMap callCounter) { try { BluetoothPbapRequestPullPhoneBook request = -- cgit v1.2.3 From 7c3a74093b155ffea85038c75621fb73a1a46b36 Mon Sep 17 00:00:00 2001 From: Joseph Pirozzo Date: Mon, 24 Jun 2019 17:08:50 -0700 Subject: AVRCP Controller AbsoluteVolumeNotification Update AbsoluteVolumeNotification request to properly respond to requests based upon the Fixed vs Dynamic volume levels. Bug: 129478624 Test: atest com.android.bluetooth.avrcpcontroller.AvrcpControllerStateMachineTest#testRegisterAbsVolumeNotification Change-Id: Ic9b745641a84712c005d40ecec50e55e9684d3ef (cherry picked from commit 8fd119d8a27d42e09a90280020925859fd0a1678) Merged-In: Ic9b745641a84712c005d40ecec50e55e9684d3ef Change-Id: I1212a70ab84e3aed926bcfcac04dc62efed4b489 --- .../AvrcpControllerStateMachine.java | 90 +++++++++++++++------- .../AvrcpControllerStateMachineTest.java | 20 +++++ 2 files changed, 84 insertions(+), 26 deletions(-) diff --git a/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachine.java b/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachine.java index 7a6c54126..83ba8505d 100644 --- a/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachine.java +++ b/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachine.java @@ -94,7 +94,14 @@ class AvrcpControllerStateMachine extends StateMachine { */ private static final int ABS_VOL_BASE = 127; + /* + * Notification types for Avrcp protocol JNI. + */ + private static final byte NOTIFICATION_RSP_TYPE_INTERIM = 0x00; + private static final byte NOTIFICATION_RSP_TYPE_CHANGED = 0x01; + private final AudioManager mAudioManager; + private final boolean mIsVolumeFixed; protected final BluetoothDevice mDevice; protected final byte[] mDeviceAddress; @@ -113,6 +120,7 @@ class AvrcpControllerStateMachine extends StateMachine { private int mAddressedPlayerId = -1; private SparseArray mAvailablePlayerList = new SparseArray(); private int mVolumeChangedNotificationsToIgnore = 0; + private int mVolumeNotificationLabel = -1; GetFolderList mGetFolderList = null; @@ -142,6 +150,7 @@ class AvrcpControllerStateMachine extends StateMachine { mGetFolderList = new GetFolderList(); addState(mGetFolderList, mConnected); mAudioManager = (AudioManager) service.getSystemService(Context.AUDIO_SERVICE); + mIsVolumeFixed = mAudioManager.isVolumeFixed(); setInitialState(mDisconnected); } @@ -311,7 +320,14 @@ class AvrcpControllerStateMachine extends StateMachine { removeMessages(MESSAGE_INTERNAL_ABS_VOL_TIMEOUT); sendMessageDelayed(MESSAGE_INTERNAL_ABS_VOL_TIMEOUT, ABS_VOL_TIMEOUT_MILLIS); - setAbsVolume(msg.arg1, msg.arg2); + handleAbsVolumeRequest(msg.arg1, msg.arg2); + return true; + + case MESSAGE_PROCESS_REGISTER_ABS_VOL_NOTIFICATION: + mVolumeNotificationLabel = msg.arg1; + mService.sendRegisterAbsVolRspNative(mDeviceAddress, + NOTIFICATION_RSP_TYPE_INTERIM, + getAbsVolume(), mVolumeNotificationLabel); return true; case MESSAGE_GET_FOLDER_ITEMS: @@ -587,24 +603,9 @@ class AvrcpControllerStateMachine extends StateMachine { } break; - case CONNECT: - case DISCONNECT: - case MSG_AVRCP_PASSTHRU: - case MESSAGE_PROCESS_SET_ABS_VOL_CMD: - case MESSAGE_PROCESS_REGISTER_ABS_VOL_NOTIFICATION: - case MESSAGE_PROCESS_TRACK_CHANGED: - case MESSAGE_PROCESS_PLAY_POS_CHANGED: - case MESSAGE_PROCESS_PLAY_STATUS_CHANGED: - case MESSAGE_PROCESS_VOLUME_CHANGED_NOTIFICATION: - case MESSAGE_PLAY_ITEM: - case MESSAGE_PROCESS_ADDRESSED_PLAYER_CHANGED: + default: // All of these messages should be handled by parent state immediately. return false; - - default: - logD(STATE_TAG + " deferring message " + msg.what - + " to connected!"); - deferMessage(msg); } return true; } @@ -717,23 +718,60 @@ class AvrcpControllerStateMachine extends StateMachine { } } + /** + * Handle a request to align our local volume with the volume of a remote device. If + * we're assuming the source volume is fixed then a response of ABS_VOL_MAX will always be + * sent and no volume adjustment action will be taken on the sink side. + * + * @param absVol A volume level based on a domain of [0, ABS_VOL_MAX] + * @param label Volume notification label + */ + private void handleAbsVolumeRequest(int absVol, int label) { + logD("handleAbsVolumeRequest: absVol = " + absVol + ", label = " + label); + if (mIsVolumeFixed) { + logD("Source volume is assumed to be fixed, responding with max volume"); + absVol = ABS_VOL_BASE; + } else { + mVolumeChangedNotificationsToIgnore++; + removeMessages(MESSAGE_INTERNAL_ABS_VOL_TIMEOUT); + sendMessageDelayed(MESSAGE_INTERNAL_ABS_VOL_TIMEOUT, + ABS_VOL_TIMEOUT_MILLIS); + setAbsVolume(absVol); + } + mService.sendAbsVolRspNative(mDeviceAddress, absVol, label); + } + + /** + * Align our volume with a requested absolute volume level + * + * @param absVol A volume level based on a domain of [0, ABS_VOL_MAX] + */ + private void setAbsVolume(int absVol) { + int maxLocalVolume = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC); + int curLocalVolume = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC); + int reqLocalVolume = (maxLocalVolume * absVol) / ABS_VOL_BASE; + logD("setAbsVolme: absVol = " + absVol + ", reqLocal = " + reqLocalVolume + + ", curLocal = " + curLocalVolume + ", maxLocal = " + maxLocalVolume); - private void setAbsVolume(int absVol, int label) { - int maxVolume = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC); - int currIndex = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC); - int newIndex = (maxVolume * absVol) / ABS_VOL_BASE; - logD(" setAbsVolume =" + absVol + " maxVol = " + maxVolume - + " cur = " + currIndex + " new = " + newIndex); /* * In some cases change in percentage is not sufficient enough to warrant * change in index values which are in range of 0-15. For such cases * no action is required */ - if (newIndex != currIndex) { - mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, newIndex, + if (reqLocalVolume != curLocalVolume) { + mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, reqLocalVolume, AudioManager.FLAG_SHOW_UI); } - mService.sendAbsVolRspNative(mDeviceAddress, absVol, label); + } + + private int getAbsVolume() { + if (mIsVolumeFixed) { + return ABS_VOL_BASE; + } + int maxVolume = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC); + int currIndex = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC); + int newIndex = (currIndex * ABS_VOL_BASE) / maxVolume; + return newIndex; } MediaSessionCompat.Callback mSessionCallbacks = new MediaSessionCompat.Callback() { diff --git a/tests/unit/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachineTest.java b/tests/unit/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachineTest.java index 0ebb31e9e..4c77e0afa 100644 --- a/tests/unit/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachineTest.java +++ b/tests/unit/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachineTest.java @@ -23,6 +23,7 @@ import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothProfile; import android.content.Context; import android.content.Intent; +import android.media.AudioManager; import android.os.Looper; import android.support.v4.media.session.MediaControllerCompat; @@ -70,6 +71,8 @@ public class AvrcpControllerStateMachineTest { @Mock private AdapterService mAdapterService; @Mock + private AudioManager mAudioManager; + @Mock private AvrcpControllerService mAvrcpControllerService; AvrcpControllerStateMachine mAvrcpStateMachine; @@ -90,6 +93,11 @@ public class AvrcpControllerStateMachineTest { TestUtils.setAdapterService(mAdapterService); TestUtils.startService(mServiceRule, AvrcpControllerService.class); doReturn(mTargetContext.getResources()).when(mAvrcpControllerService).getResources(); + doReturn(15).when(mAudioManager).getStreamMaxVolume(anyInt()); + doReturn(8).when(mAudioManager).getStreamVolume(anyInt()); + doReturn(true).when(mAudioManager).isVolumeFixed(); + doReturn(mAudioManager).when(mAvrcpControllerService) + .getSystemService(Context.AUDIO_SERVICE); // This line must be called to make sure relevant objects are initialized properly mAdapter = BluetoothAdapter.getDefaultAdapter(); @@ -524,6 +532,18 @@ public class AvrcpControllerStateMachineTest { eq(mTestAddress), eq(AvrcpControllerService.PASS_THRU_CMD_ID_PLAY), eq(KEY_UP)); } + /** + * Test that Absolute Volume Registration is working + */ + @Test + public void testRegisterAbsVolumeNotification() { + setUpConnectedState(true, true); + mAvrcpStateMachine.sendMessage( + AvrcpControllerStateMachine.MESSAGE_PROCESS_REGISTER_ABS_VOL_NOTIFICATION); + verify(mAvrcpControllerService, timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)) + .sendRegisterAbsVolRspNative(any(), anyByte(), eq(127), anyInt()); + } + /** * Setup Connected State * -- cgit v1.2.3 From 769be7335adfc4a2df42c78432f7efdd439db3b0 Mon Sep 17 00:00:00 2001 From: Joseph Pirozzo Date: Tue, 25 Jun 2019 09:54:46 -0700 Subject: AVRCP Controller request focus when idle When no music is playing and an incoming play request comes in from a paired device, request focus on it's behalf. Bug: 135112399 Test: atest com.android.bluetooth.avrcpcontroller.AvrcpControllerStateMachineTest Change-Id: Ia764ea54af95166be6b6c3d9b47f3e3c2d09a0d7 (cherry picked from commit e6ff50d6328dca536acd131d91c109072febd70f) Merged-In: Ia764ea54af95166be6b6c3d9b47f3e3c2d09a0d7 Change-Id: I16d132e6fc90c1bb0f21d05de3ddf877ff5ea5b4 --- .../bluetooth/a2dpsink/A2dpSinkService.java | 3 +- .../AvrcpControllerStateMachine.java | 10 ++-- .../AvrcpControllerStateMachineTest.java | 59 ++++++++++++++++++++-- 3 files changed, 63 insertions(+), 9 deletions(-) diff --git a/src/com/android/bluetooth/a2dpsink/A2dpSinkService.java b/src/com/android/bluetooth/a2dpsink/A2dpSinkService.java index e41def075..5271cc791 100644 --- a/src/com/android/bluetooth/a2dpsink/A2dpSinkService.java +++ b/src/com/android/bluetooth/a2dpsink/A2dpSinkService.java @@ -413,7 +413,8 @@ public class A2dpSinkService extends ProfileService { if (state == StackEvent.AUDIO_STATE_STARTED) { mA2dpSinkStreamHandler.obtainMessage( A2dpSinkStreamHandler.SRC_STR_START).sendToTarget(); - } else if (state == StackEvent.AUDIO_STATE_STOPPED) { + } else if (state == StackEvent.AUDIO_STATE_STOPPED + || state == StackEvent.AUDIO_STATE_REMOTE_SUSPEND) { mA2dpSinkStreamHandler.obtainMessage( A2dpSinkStreamHandler.SRC_STR_STOP).sendToTarget(); } diff --git a/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachine.java b/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachine.java index 83ba8505d..23f1ce75a 100644 --- a/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachine.java +++ b/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachine.java @@ -361,10 +361,13 @@ class AvrcpControllerStateMachine extends StateMachine { BluetoothMediaBrowserService.notifyChanged(mAddressedPlayer.getPlaybackState()); if (mAddressedPlayer.getPlaybackState().getState() == PlaybackStateCompat.STATE_PLAYING - && A2dpSinkService.getFocusState() == AudioManager.AUDIOFOCUS_NONE - && !shouldRequestFocus()) { + && A2dpSinkService.getFocusState() == AudioManager.AUDIOFOCUS_NONE) { + if (shouldRequestFocus()) { + mSessionCallbacks.onPrepare(); + } else { sendMessage(MSG_AVRCP_PASSTHRU, AvrcpControllerService.PASS_THRU_CMD_ID_PAUSE); + } } return true; @@ -882,6 +885,7 @@ class AvrcpControllerStateMachine extends StateMachine { private boolean shouldRequestFocus() { return mService.getResources() - .getBoolean(R.bool.a2dp_sink_automatically_request_audio_focus); + .getBoolean(R.bool.a2dp_sink_automatically_request_audio_focus) + || !mAudioManager.isMusicActive(); } } diff --git a/tests/unit/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachineTest.java b/tests/unit/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachineTest.java index 4c77e0afa..c766c7c61 100644 --- a/tests/unit/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachineTest.java +++ b/tests/unit/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachineTest.java @@ -26,6 +26,7 @@ import android.content.Intent; import android.media.AudioManager; import android.os.Looper; import android.support.v4.media.session.MediaControllerCompat; +import android.support.v4.media.session.PlaybackStateCompat; import androidx.test.InstrumentationRegistry; import androidx.test.filters.MediumTest; @@ -34,6 +35,7 @@ import androidx.test.runner.AndroidJUnit4; import com.android.bluetooth.R; import com.android.bluetooth.TestUtils; +import com.android.bluetooth.a2dpsink.A2dpSinkService; import com.android.bluetooth.btservice.AdapterService; import com.android.bluetooth.btservice.ProfileService; @@ -66,10 +68,15 @@ public class AvrcpControllerStateMachineTest { private ArgumentCaptor mIntentArgument = ArgumentCaptor.forClass(Intent.class); private byte[] mTestAddress = new byte[]{00, 01, 02, 03, 04, 05}; - @Rule public final ServiceTestRule mServiceRule = new ServiceTestRule(); + @Rule public final ServiceTestRule mAvrcpServiceRule = new ServiceTestRule(); + @Rule public final ServiceTestRule mA2dpServiceRule = new ServiceTestRule(); @Mock - private AdapterService mAdapterService; + private AdapterService mAvrcpAdapterService; + + @Mock + private AdapterService mA2dpAdapterService; + @Mock private AudioManager mAudioManager; @Mock @@ -90,8 +97,11 @@ public class AvrcpControllerStateMachineTest { // Setup mocks and test assets MockitoAnnotations.initMocks(this); - TestUtils.setAdapterService(mAdapterService); - TestUtils.startService(mServiceRule, AvrcpControllerService.class); + TestUtils.setAdapterService(mAvrcpAdapterService); + TestUtils.startService(mAvrcpServiceRule, AvrcpControllerService.class); + TestUtils.clearAdapterService(mAvrcpAdapterService); + TestUtils.setAdapterService(mA2dpAdapterService); + TestUtils.startService(mA2dpServiceRule, A2dpSinkService.class); doReturn(mTargetContext.getResources()).when(mAvrcpControllerService).getResources(); doReturn(15).when(mAudioManager).getStreamMaxVolume(anyInt()); doReturn(8).when(mAudioManager).getStreamVolume(anyInt()); @@ -113,7 +123,7 @@ public class AvrcpControllerStateMachineTest { if (!mTargetContext.getResources().getBoolean(R.bool.profile_supported_avrcp_controller)) { return; } - TestUtils.clearAdapterService(mAdapterService); + TestUtils.clearAdapterService(mA2dpAdapterService); } /** @@ -544,6 +554,45 @@ public class AvrcpControllerStateMachineTest { .sendRegisterAbsVolRspNative(any(), anyByte(), eq(127), anyInt()); } + /** + * Test playback does not request focus when another app is playing music. + */ + @Test + public void testPlaybackWhileMusicPlaying() { + Assert.assertEquals(AudioManager.AUDIOFOCUS_NONE, A2dpSinkService.getFocusState()); + doReturn(true).when(mAudioManager).isMusicActive(); + setUpConnectedState(true, true); + mAvrcpStateMachine.sendMessage( + AvrcpControllerStateMachine.MESSAGE_PROCESS_PLAY_STATUS_CHANGED, + PlaybackStateCompat.STATE_PLAYING); + TestUtils.waitForLooperToFinishScheduledTask(mAvrcpStateMachine.getHandler().getLooper()); + verify(mAudioManager, times(1)).isMusicActive(); + verify(mAvrcpControllerService, + timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).sendPassThroughCommandNative( + eq(mTestAddress), eq(AvrcpControllerService.PASS_THRU_CMD_ID_PAUSE), eq(KEY_DOWN)); + TestUtils.waitForLooperToFinishScheduledTask( + A2dpSinkService.getA2dpSinkService().getMainLooper()); + Assert.assertEquals(AudioManager.AUDIOFOCUS_NONE, A2dpSinkService.getFocusState()); + } + + /** + * Test playback requests focus while nothing is playing music. + */ + @Test + public void testPlaybackWhileIdle() { + Assert.assertEquals(AudioManager.AUDIOFOCUS_NONE, A2dpSinkService.getFocusState()); + doReturn(false).when(mAudioManager).isMusicActive(); + setUpConnectedState(true, true); + mAvrcpStateMachine.sendMessage( + AvrcpControllerStateMachine.MESSAGE_PROCESS_PLAY_STATUS_CHANGED, + PlaybackStateCompat.STATE_PLAYING); + TestUtils.waitForLooperToFinishScheduledTask(mAvrcpStateMachine.getHandler().getLooper()); + verify(mAudioManager, times(1)).isMusicActive(); + TestUtils.waitForLooperToFinishScheduledTask( + A2dpSinkService.getA2dpSinkService().getMainLooper()); + Assert.assertEquals(AudioManager.AUDIOFOCUS_GAIN, A2dpSinkService.getFocusState()); + } + /** * Setup Connected State * -- cgit v1.2.3 From 70710dc735cba120c1c2be86afba4e9318cf7929 Mon Sep 17 00:00:00 2001 From: Joseph Pirozzo Date: Thu, 8 Aug 2019 16:16:49 -0700 Subject: AVRCP Controller transient loss while idle While gaining and losing focus due to transient focus requests ensure Bluetooth restores to the correct playback state. This corrects issues that could arise during non media sonification durring a transient focus loss. Bug: 136092891 Test: atest com.android.bluetooth.a2dpsink.A2dpSinkStreamHandlerTest Change-Id: I58997b3b8b96da309ff4c31a2b3039dccf9b3578 (cherry picked from commit 32d3d5acac7eabd2bd00be46ea9e399a8b8db10b) Merged-In: I58997b3b8b96da309ff4c31a2b3039dccf9b3578 Change-Id: Ibefa524f14e2c0792278ab76161f7a8bcbc4fb9b --- .../bluetooth/a2dpsink/A2dpSinkStreamHandler.java | 4 +++- .../avrcpcontroller/BluetoothMediaBrowserService.java | 14 ++++++++++++++ .../bluetooth/a2dpsink/A2dpSinkStreamHandlerTest.java | 18 ++++++++++++++++++ 3 files changed, 35 insertions(+), 1 deletion(-) diff --git a/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamHandler.java b/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamHandler.java index c24eca9e8..fa872845a 100644 --- a/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamHandler.java +++ b/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamHandler.java @@ -26,6 +26,7 @@ import android.media.AudioManager.OnAudioFocusChangeListener; import android.media.MediaPlayer; import android.os.Handler; import android.os.Message; +import android.support.v4.media.session.PlaybackStateCompat; import android.util.Log; import com.android.bluetooth.R; @@ -225,7 +226,8 @@ public class A2dpSinkStreamHandler extends Handler { break; case DELAYED_PAUSE: - if (mStreamAvailable && !inCallFromStreamingDevice()) { + if (BluetoothMediaBrowserService.getPlaybackState() + == PlaybackStateCompat.STATE_PLAYING && !inCallFromStreamingDevice()) { sendAvrcpPause(); mSentPause = true; mStreamAvailable = false; diff --git a/src/com/android/bluetooth/avrcpcontroller/BluetoothMediaBrowserService.java b/src/com/android/bluetooth/avrcpcontroller/BluetoothMediaBrowserService.java index 774f95349..b8a33379e 100644 --- a/src/com/android/bluetooth/avrcpcontroller/BluetoothMediaBrowserService.java +++ b/src/com/android/bluetooth/avrcpcontroller/BluetoothMediaBrowserService.java @@ -184,6 +184,20 @@ public class BluetoothMediaBrowserService extends MediaBrowserServiceCompat { } } + /** + * Get playback state + */ + public static synchronized int getPlaybackState() { + if (sBluetoothMediaBrowserService != null) { + PlaybackStateCompat currentPlaybackState = + sBluetoothMediaBrowserService.mSession.getController().getPlaybackState(); + if (currentPlaybackState != null) { + return currentPlaybackState.getState(); + } + } + return PlaybackStateCompat.STATE_ERROR; + } + /** * Get object for controlling playback */ diff --git a/tests/unit/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamHandlerTest.java b/tests/unit/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamHandlerTest.java index 53e8419f9..c759a8a1f 100644 --- a/tests/unit/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamHandlerTest.java +++ b/tests/unit/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamHandlerTest.java @@ -194,6 +194,24 @@ public class A2dpSinkStreamHandlerTest { verify(mMockA2dpSink, times(1)).informAudioTrackGainNative(0); } + @Test + public void testFocusGainTransient() { + // Focus was lost then regained. + testSnkPlay(); + mStreamHandler.handleMessage( + mStreamHandler.obtainMessage(A2dpSinkStreamHandler.AUDIO_FOCUS_CHANGE, + AudioManager.AUDIOFOCUS_LOSS_TRANSIENT)); + mStreamHandler.handleMessage( + mStreamHandler.obtainMessage(A2dpSinkStreamHandler.DELAYED_PAUSE)); + mStreamHandler.handleMessage( + mStreamHandler.obtainMessage(A2dpSinkStreamHandler.AUDIO_FOCUS_CHANGE, + AudioManager.AUDIOFOCUS_GAIN)); + verify(mMockAudioManager, times(0)).abandonAudioFocus(any()); + verify(mMockA2dpSink, times(0)).informAudioFocusStateNative(0); + verify(mMockA2dpSink, times(1)).informAudioTrackGainNative(0); + verify(mMockA2dpSink, times(2)).informAudioTrackGainNative(1.0f); + } + @Test public void testFocusLost() { // Focus was lost permanently, expect streaming to stop. -- cgit v1.2.3 From fdba1f6020b53a5aeaf67776c36caafb16ca0c32 Mon Sep 17 00:00:00 2001 From: Joseph Pirozzo Date: Fri, 9 Aug 2019 16:16:16 -0700 Subject: AVRCP Controller getCurrentCalls NPE Check for null call list if media device and phone call device are different. Bug: 138256493 Test: Connect 2 phones, one HFP one A2DP, receive incomming call. Change-Id: I5f5c85d65283add3790dfaa1f252c3a14127f822 (cherry picked from commit 26041cc83d7f02da41b88eb71eeb30b392d0cfc9) Merged-In: I5f5c85d65283add3790dfaa1f252c3a14127f822 Change-Id: I58538ae0faddec9a66a3a695f70a2c030681f603 --- src/com/android/bluetooth/a2dpsink/A2dpSinkStreamHandler.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamHandler.java b/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamHandler.java index fa872845a..ffd648af9 100644 --- a/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamHandler.java +++ b/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamHandler.java @@ -17,6 +17,7 @@ package com.android.bluetooth.a2dpsink; import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothHeadsetClientCall; import android.content.Context; import android.content.pm.PackageManager; import android.media.AudioAttributes; @@ -361,7 +362,10 @@ public class A2dpSinkStreamHandler extends Handler { } HeadsetClientService headsetClientService = HeadsetClientService.getHeadsetClientService(); if (targetDevice != null && headsetClientService != null) { - return headsetClientService.getCurrentCalls(targetDevice).size() > 0; + List currentCalls = + headsetClientService.getCurrentCalls(targetDevice); + if (currentCalls == null) return false; + return currentCalls.size() > 0; } return false; } -- cgit v1.2.3 From 4ee2b85ad93152ee429b69262a25b4c640e58587 Mon Sep 17 00:00:00 2001 From: Joseph Pirozzo Date: Fri, 9 Aug 2019 17:33:36 -0700 Subject: AVRCP Controller onSkipToQueueItem invalid Prevent exceptions where skip to queue item results in an invalid song. Bug: 138803592 Test: atest com.android.bluetooth.avrcpcontroller.AvrcpControllerStateMachineTest#testSkipToQueueInvalid Change-Id: Icb9b0e85188335299fb62f0b30ada0a005ad20a3 (cherry picked from commit 4eec318acc6cd14e09d5f8ef3495aee8a7317b8c) Merged-In: Icb9b0e85188335299fb62f0b30ada0a005ad20a3 Change-Id: I4adeb57c190c8214c83a153161ac138a8cd6ad51 --- .../bluetooth/avrcpcontroller/BrowseTree.java | 9 +++++++- .../AvrcpControllerStateMachineTest.java | 25 ++++++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/src/com/android/bluetooth/avrcpcontroller/BrowseTree.java b/src/com/android/bluetooth/avrcpcontroller/BrowseTree.java index accea2a70..923282d34 100644 --- a/src/com/android/bluetooth/avrcpcontroller/BrowseTree.java +++ b/src/com/android/bluetooth/avrcpcontroller/BrowseTree.java @@ -100,7 +100,7 @@ public class BrowseTree { } BrowseNode getTrackFromNowPlayingList(int trackNumber) { - return mNowPlayingNode.mChildren.get(trackNumber); + return mNowPlayingNode.getChild(trackNumber); } // Each node of the tree is represented by Folder ID, Folder Name and the children. @@ -218,6 +218,13 @@ public class BrowseTree { return mChildren; } + synchronized BrowseNode getChild(int index) { + if (index < 0 || index >= mChildren.size()) { + return null; + } + return mChildren.get(index); + } + synchronized BrowseNode getParent() { return mParent; } diff --git a/tests/unit/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachineTest.java b/tests/unit/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachineTest.java index c766c7c61..f8ab6bbbf 100644 --- a/tests/unit/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachineTest.java +++ b/tests/unit/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachineTest.java @@ -364,6 +364,31 @@ public class AvrcpControllerStateMachineTest { eq(mTestAddress), eq(AvrcpControllerService.PASS_THRU_CMD_ID_REWIND), eq(KEY_UP)); } + /** + * Test media browser skip to queue item + */ + @Test + public void testSkipToQueueInvalid() throws Exception { + byte scope = 1; + int minSize = 0; + int maxSize = 255; + setUpConnectedState(true, true); + MediaControllerCompat.TransportControls transportControls = + BluetoothMediaBrowserService.getTransportControls(); + + //Play an invalid item below start + transportControls.skipToQueueItem(minSize - 1); + verify(mAvrcpControllerService, + timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(0)).playItemNative( + eq(mTestAddress), eq(scope), anyLong(), anyInt()); + + //Play an invalid item beyond end + transportControls.skipToQueueItem(maxSize + 1); + verify(mAvrcpControllerService, + timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(0)).playItemNative( + eq(mTestAddress), eq(scope), anyLong(), anyInt()); + } + /** * Test media browser shuffle command */ -- cgit v1.2.3 From 1b10f6a7b2a5b2b2d0bd1eae3cd94e4d16a6a80d Mon Sep 17 00:00:00 2001 From: Joseph Pirozzo Date: Thu, 10 Oct 2019 13:52:46 -0700 Subject: AVRCP MediaBrowserService support ACTION_PREPARE Report support for ACTION_PREPARE when connected to a bluetooth device capable of streaming. Bug: 141469207 Test: dumpsys media_session reports ACTION_PREPARE in actions Change-Id: I43702ba629750c02411cd39a1211bb352cc7457b (cherry picked from commit fb2633f7877e04a82f409a39b89a1a90189c70c4) Merged-In: I43702ba629750c02411cd39a1211bb352cc7457b Change-Id: I09cb56e8f59dfa15faea6581f379c06665076a63 --- src/com/android/bluetooth/avrcpcontroller/AvrcpPlayer.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/com/android/bluetooth/avrcpcontroller/AvrcpPlayer.java b/src/com/android/bluetooth/avrcpcontroller/AvrcpPlayer.java index 2ef3c1480..4736acffa 100644 --- a/src/com/android/bluetooth/avrcpcontroller/AvrcpPlayer.java +++ b/src/com/android/bluetooth/avrcpcontroller/AvrcpPlayer.java @@ -50,7 +50,7 @@ class AvrcpPlayer { private String mName = ""; private int mPlayerType; private byte[] mPlayerFeatures = new byte[16]; - private long mAvailableActions; + private long mAvailableActions = PlaybackStateCompat.ACTION_PREPARE; private MediaMetadata mCurrentTrack; private PlaybackStateCompat mPlaybackStateCompat; private PlayerApplicationSettings mSupportedPlayerApplicationSettings = @@ -63,7 +63,7 @@ class AvrcpPlayer { mAvailableActions = PlaybackStateCompat.ACTION_PAUSE | PlaybackStateCompat.ACTION_PLAY | PlaybackStateCompat.ACTION_SKIP_TO_NEXT | PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS - | PlaybackStateCompat.ACTION_STOP; + | PlaybackStateCompat.ACTION_STOP | PlaybackStateCompat.ACTION_PREPARE; PlaybackStateCompat.Builder playbackStateBuilder = new PlaybackStateCompat.Builder() .setActions(mAvailableActions); mPlaybackStateCompat = playbackStateBuilder.build(); -- cgit v1.2.3 From ec62a0319cdf3ac374527d182209ef6eb2c2550c Mon Sep 17 00:00:00 2001 From: Andrew Cheng Date: Wed, 7 Aug 2019 10:29:13 -0700 Subject: PBAP client, download favorite contacts Bug: 132636859 Test: ACTS/SL4A and PTS Change-Id: Ifb16fbad1b9fc2e75008678f4551f515c46161b3 (cherry picked from commit bef7cd95b9e718257bc0e290c815e8b004679846) Merged-In: Ifb16fbad1b9fc2e75008678f4551f515c46161b3 Change-Id: I7c00be60f19141c3120808eead51503f22e21891 --- .../pbapclient/PbapClientConnectionHandler.java | 69 +++++++++++++++++----- 1 file changed, 55 insertions(+), 14 deletions(-) diff --git a/src/com/android/bluetooth/pbapclient/PbapClientConnectionHandler.java b/src/com/android/bluetooth/pbapclient/PbapClientConnectionHandler.java index 43d384dfc..53670ff3a 100644 --- a/src/com/android/bluetooth/pbapclient/PbapClientConnectionHandler.java +++ b/src/com/android/bluetooth/pbapclient/PbapClientConnectionHandler.java @@ -32,8 +32,10 @@ import android.util.Log; import com.android.bluetooth.BluetoothObexTransport; import com.android.bluetooth.R; +import com.android.vcard.VCardEntry; import java.io.IOException; +import java.util.ArrayList; import java.util.HashMap; import javax.obex.ClientSession; @@ -104,9 +106,19 @@ class PbapClientConnectionHandler extends Handler { private static final int L2CAP_INVALID_PSM = -1; public static final String PB_PATH = "telecom/pb.vcf"; + public static final String FAV_PATH = "telecom/fav.vcf"; public static final String MCH_PATH = "telecom/mch.vcf"; public static final String ICH_PATH = "telecom/ich.vcf"; public static final String OCH_PATH = "telecom/och.vcf"; + public static final String SIM_PB_PATH = "SIM1/telecom/pb.vcf"; + public static final String SIM_MCH_PATH = "SIM1/telecom/mch.vcf"; + public static final String SIM_ICH_PATH = "SIM1/telecom/ich.vcf"; + public static final String SIM_OCH_PATH = "SIM1/telecom/och.vcf"; + + // PBAP v1.2.3 Sec. 7.1.2 + private static final int SUPPORTED_REPOSITORIES_LOCALPHONEBOOK = 1 << 0; + private static final int SUPPORTED_REPOSITORIES_SIMCARD = 1 << 1; + private static final int SUPPORTED_REPOSITORIES_FAVORITES = 1 << 3; public static final int PBAP_V1_2 = 0x0102; public static final byte VCARD_TYPE_21 = 0; @@ -248,7 +260,20 @@ class PbapClientConnectionHandler extends Handler { break; case MSG_DOWNLOAD: - downloadContacts(); + mAccountCreated = addAccount(mAccount); + if (!mAccountCreated) { + Log.e(TAG, "Account creation failed."); + return; + } + if (isRepositorySupported(SUPPORTED_REPOSITORIES_FAVORITES)) { + downloadContacts(FAV_PATH); + } + if (isRepositorySupported(SUPPORTED_REPOSITORIES_LOCALPHONEBOOK)) { + downloadContacts(PB_PATH); + } + if (isRepositorySupported(SUPPORTED_REPOSITORIES_SIMCARD)) { + downloadContacts(SIM_PB_PATH); + } HashMap callCounter = new HashMap<>(); downloadCallLog(MCH_PATH, callCounter); @@ -361,38 +386,46 @@ class PbapClientConnectionHandler extends Handler { } } - void downloadContacts() { + void downloadContacts(String path) { try { - mAccountCreated = addAccount(mAccount); - if (!mAccountCreated) { - Log.e(TAG, "Account creation failed."); - return; - } PhonebookPullRequest processor = new PhonebookPullRequest(mPbapClientStateMachine.getContext(), mAccount); // Download contacts in batches of size DEFAULT_BATCH_SIZE BluetoothPbapRequestPullPhoneBookSize requestPbSize = - new BluetoothPbapRequestPullPhoneBookSize(PB_PATH, + new BluetoothPbapRequestPullPhoneBookSize(path, PBAP_REQUESTED_FIELDS); requestPbSize.execute(mObexSession); - // "-1" because Owner Card is also included in PhoneBookSize - int numberOfContactsRemaining = requestPbSize.getSize() - 1; + int numberOfContactsRemaining = requestPbSize.getSize(); + int startOffset = 0; + if (PB_PATH.equals(path)) { + // PBAP v1.2.3, Sec 3.1.5. The first contact in pb is owner card 0.vcf, which we + // do not want to download. The other phonebook objects (e.g., fav) don't have an + // owner card, so they don't need an offset. + startOffset = 1; + // "-1" because Owner Card 0.vcf is also included in /pb, but not in /fav. + numberOfContactsRemaining -= 1; + } - // Start at contact 1 to exclude Owner Card PBAP 1.1 sec 3.1.5.2 - int startOffset = 1; while ((numberOfContactsRemaining > 0) && (startOffset <= UPPER_LIMIT)) { int numberOfContactsToDownload = Math.min(Math.min(DEFAULT_BATCH_SIZE, numberOfContactsRemaining), UPPER_LIMIT - startOffset + 1); BluetoothPbapRequestPullPhoneBook request = - new BluetoothPbapRequestPullPhoneBook(PB_PATH, mAccount, + new BluetoothPbapRequestPullPhoneBook(path, mAccount, PBAP_REQUESTED_FIELDS, VCARD_TYPE_30, numberOfContactsToDownload, startOffset); request.execute(mObexSession); - processor.setResults(request.getList()); + ArrayList vcards = request.getList(); + if (path == FAV_PATH) { + // mark each vcard as a favorite + for (VCardEntry v : vcards) { + v.setStarred(true); + } + } + processor.setResults(vcards); processor.onPullComplete(); startOffset += numberOfContactsToDownload; @@ -456,4 +489,12 @@ class PbapClientConnectionHandler extends Handler { Log.d(TAG, "Call Logs could not be deleted, they may not exist yet."); } } + + private boolean isRepositorySupported(int mask) { + if (mPseRec == null) { + if (VDBG) Log.v(TAG, "No PBAP Server SDP Record"); + return false; + } + return (mask & mPseRec.getSupportedRepositories()) != 0; + } } -- cgit v1.2.3 From cc193bd4b47afb5d17fe6f3295dd88a8924b9b31 Mon Sep 17 00:00:00 2001 From: Joseph Pirozzo Date: Tue, 29 Oct 2019 17:47:24 -0700 Subject: MAP Client support incomming MMS Add support for incomming MMS messages by leveraging the already existing parser. Bug: 119750641 Test: atest MapClientStateMachineTest Change-Id: Iad3806e39fa11b520f7bcc3b33426f0a65779530 (cherry picked from commit f3965e7d943daa79e2868c4c27f0bda3c59ad051) Merged-In: Iad3806e39fa11b520f7bcc3b33426f0a65779530 Change-Id: I54d1399350a5e3d89f51625dde101a1cc9129c06 --- src/com/android/bluetooth/mapclient/MceStateMachine.java | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/com/android/bluetooth/mapclient/MceStateMachine.java b/src/com/android/bluetooth/mapclient/MceStateMachine.java index 867ab185f..cdfc98699 100644 --- a/src/com/android/bluetooth/mapclient/MceStateMachine.java +++ b/src/com/android/bluetooth/mapclient/MceStateMachine.java @@ -58,6 +58,7 @@ import android.util.Log; import com.android.bluetooth.BluetoothMetricsProto; import com.android.bluetooth.btservice.MetricsLogger; import com.android.bluetooth.btservice.ProfileService; +import com.android.bluetooth.map.BluetoothMapbMessageMime; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.IState; import com.android.internal.util.State; @@ -345,7 +346,9 @@ final class MceStateMachine extends StateMachine { void setDefaultMessageType(SdpMasRecord sdpMasRecord) { int supportedMessageTypes = sdpMasRecord.getSupportedMessageTypes(); synchronized (mDefaultMessageType) { - if ((supportedMessageTypes & SdpMasRecord.MessageType.SMS_CDMA) > 0) { + if ((supportedMessageTypes & SdpMasRecord.MessageType.MMS) > 0) { + mDefaultMessageType = Bmessage.Type.MMS; + } else if ((supportedMessageTypes & SdpMasRecord.MessageType.SMS_CDMA) > 0) { mDefaultMessageType = Bmessage.Type.SMS_CDMA; } else if ((supportedMessageTypes & SdpMasRecord.MessageType.SMS_GSM) > 0) { mDefaultMessageType = Bmessage.Type.SMS_GSM; @@ -666,6 +669,7 @@ final class MceStateMachine extends StateMachine { switch (message.getType()) { case SMS_CDMA: case SMS_GSM: + case MMS: if (DBG) { Log.d(TAG, "Body: " + message.getBodyContent()); } @@ -706,6 +710,12 @@ final class MceStateMachine extends StateMachine { intent.putExtra(BluetoothMapClient.EXTRA_SENDER_CONTACT_NAME, originator.getDisplayName()); } + if (message.getType() == Bmessage.Type.MMS) { + BluetoothMapbMessageMime mmsBmessage = new BluetoothMapbMessageMime(); + mmsBmessage.parseMsgPart(message.getBodyContent()); + intent.putExtra(android.content.Intent.EXTRA_TEXT, + mmsBmessage.getMessageAsText()); + } // Only send to the current default SMS app if one exists String defaultMessagingPackage = Telephony.Sms.getDefaultSmsPackage(mService); if (defaultMessagingPackage != null) { @@ -713,8 +723,6 @@ final class MceStateMachine extends StateMachine { } mService.sendBroadcast(intent, android.Manifest.permission.RECEIVE_SMS); break; - - case MMS: case EMAIL: default: Log.e(TAG, "Received unhandled type" + message.getType().toString()); -- cgit v1.2.3 From 7ce948ec7cf933a9ac7f71be4fed820ffc3722fe Mon Sep 17 00:00:00 2001 From: Joseph Pirozzo Date: Tue, 29 Oct 2019 19:49:04 -0700 Subject: MAP Client BMessage parser length Validate and catch errors in the BMessage pertaining to erroneous length. Bug: 123244713 Test: atest BmessageTest Change-Id: Ie9101e0be12d627a6fd3cec73eec9b977d8d40bb (cherry picked from commit a353a95c230b5546a17daea10390fbd4be0f9e63) Merged-In: Ie9101e0be12d627a6fd3cec73eec9b977d8d40bb Change-Id: Ie83f30e898d0f81eb81100fdb70859f92680d167 --- .../bluetooth/mapclient/obex/BmessageParser.java | 6 ++ .../android/bluetooth/mapclient/BmessageTest.java | 96 ++++++++++++++++++++++ 2 files changed, 102 insertions(+) create mode 100644 tests/unit/src/com/android/bluetooth/mapclient/BmessageTest.java diff --git a/src/com/android/bluetooth/mapclient/obex/BmessageParser.java b/src/com/android/bluetooth/mapclient/obex/BmessageParser.java index 2705e3429..5b844dce5 100644 --- a/src/com/android/bluetooth/mapclient/obex/BmessageParser.java +++ b/src/com/android/bluetooth/mapclient/obex/BmessageParser.java @@ -309,6 +309,12 @@ class BmessageParser { String remng = mParser.remaining(); byte[] data = remng.getBytes(); + if (offset < 0 || offset > data.length) { + /* Handle possible exception for incorrect LENGTH value + * from MSE while parsing end of props */ + throw new ParseException("Invalid LENGTH value", mParser.pos()); + } + /* restart parsing from after 'message' */ mParser = new BmsgTokenizer(new String(data, offset, data.length - offset), restartPos); diff --git a/tests/unit/src/com/android/bluetooth/mapclient/BmessageTest.java b/tests/unit/src/com/android/bluetooth/mapclient/BmessageTest.java new file mode 100644 index 000000000..acd05ed02 --- /dev/null +++ b/tests/unit/src/com/android/bluetooth/mapclient/BmessageTest.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2019 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.android.bluetooth.mapclient; + +import static org.mockito.Mockito.*; + +import androidx.test.filters.MediumTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; + +@MediumTest +@RunWith(AndroidJUnit4.class) +public class BmessageTest { + private static final String TAG = BmessageTest.class.getSimpleName(); + private static final String SIMPLE_MMS_MESSAGE = + "BEGIN:BMSG\r\nVERSION:1.0\r\nSTATUS:READ\r\nTYPE:MMS\r\nFOLDER:null\r\nBEGIN:BENV\r\n" + + "BEGIN:VCARD\r\nVERSION:2.1\r\nN:null;;;;\r\nTEL:555-5555\r\nEND:VCARD\r\n" + + "BEGIN:BBODY\r\nLENGTH:39\r\nBEGIN:MSG\r\nThis is a new msg\r\nEND:MSG\r\n" + + "END:BBODY\r\nEND:BENV\r\nEND:BMSG\r\n"; + + private static final String NO_END_MESSAGE = + "BEGIN:BMSG\r\nVERSION:1.0\r\nSTATUS:READ\r\nTYPE:MMS\r\nFOLDER:null\r\nBEGIN:BENV\r\n" + + "BEGIN:VCARD\r\nVERSION:2.1\r\nN:null;;;;\r\nTEL:555-5555\r\nEND:VCARD\r\n" + + "BEGIN:BBODY\r\nLENGTH:39\r\nBEGIN:MSG\r\nThis is a new msg\r\n"; + + private static final String WRONG_LENGTH_MESSAGE = + "BEGIN:BMSG\r\nVERSION:1.0\r\nSTATUS:READ\r\nTYPE:MMS\r\nFOLDER:null\r\nBEGIN:BENV\r\n" + + "BEGIN:VCARD\r\nVERSION:2.1\r\nN:null;;;;\r\nTEL:555-5555\r\nEND:VCARD\r\n" + + "BEGIN:BBODY\r\nLENGTH:200\r\nBEGIN:MSG\r\nThis is a new msg\r\nEND:MSG\r\n" + + "END:BBODY\r\nEND:BENV\r\nEND:BMSG\r\n"; + + private static final String NO_BODY_MESSAGE = + "BEGIN:BMSG\r\nVERSION:1.0\r\nSTATUS:READ\r\nTYPE:MMS\r\nFOLDER:null\r\nBEGIN:BENV\r\n" + + "BEGIN:VCARD\r\nVERSION:2.1\r\nN:null;;;;\r\nTEL:555-5555\r\nEND:VCARD\r\n" + + "BEGIN:BBODY\r\nLENGTH:\r\n"; + + private static final String NEGATIVE_LENGTH_MESSAGE = + "BEGIN:BMSG\r\nVERSION:1.0\r\nSTATUS:READ\r\nTYPE:MMS\r\nFOLDER:null\r\nBEGIN:BENV\r\n" + + "BEGIN:VCARD\r\nVERSION:2.1\r\nN:null;;;;\r\nTEL:555-5555\r\nEND:VCARD\r\n" + + "BEGIN:BBODY\r\nLENGTH:-1\r\nBEGIN:MSG\r\nThis is a new msg\r\nEND:MSG\r\n" + + "END:BBODY\r\nEND:BENV\r\nEND:BMSG\r\n"; + + @Test + public void testNormalMessages() { + Bmessage message = BmessageParser.createBmessage(SIMPLE_MMS_MESSAGE); + Assert.assertNotNull(message); + } + + @Test + public void testParseWrongLengthMessage() { + Bmessage message = BmessageParser.createBmessage(WRONG_LENGTH_MESSAGE); + Assert.assertNull(message); + } + + @Test + public void testParseNoEndMessage() { + Bmessage message = BmessageParser.createBmessage(NO_END_MESSAGE); + Assert.assertNull(message); + } + + @Test + public void testParseReallyLongMessage() { + String testMessage = new String(new char[68048]).replace('\0', 'A'); + Bmessage message = BmessageParser.createBmessage(testMessage); + Assert.assertNull(message); + } + + @Test + public void testNoBodyMessage() { + Bmessage message = BmessageParser.createBmessage(NO_BODY_MESSAGE); + Assert.assertNull(message); + } + + @Test + public void testNegativeLengthMessage() { + Bmessage message = BmessageParser.createBmessage(NEGATIVE_LENGTH_MESSAGE); + Assert.assertNull(message); + } +} -- cgit v1.2.3 From 362108ec36150b8556892d2786f5c16b97051fd4 Mon Sep 17 00:00:00 2001 From: Joseph Pirozzo Date: Tue, 29 Oct 2019 20:36:18 -0700 Subject: MAP Client Only connect MNS in connected state Only allow the MNS to connect when the MAS client is in the connected state, this prevents the possibility of the MNS client from connecting if the MAS fails to connect. Bug: 135088863 Test: disallow MAS connection and verify MNS notifications don't arrive. Change-Id: I75de88e74708283ebbaef70251f7575c0da0c4d4 (cherry picked from commit ac9715de616a2e8de421d72dbb2fd1ef6a55842c) Merged-In: I75de88e74708283ebbaef70251f7575c0da0c4d4 Change-Id: I3103c679d37a0664da4f6e777d9d5f42af257f51 --- src/com/android/bluetooth/mapclient/MnsService.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/com/android/bluetooth/mapclient/MnsService.java b/src/com/android/bluetooth/mapclient/MnsService.java index c1ab39e00..b3317df90 100644 --- a/src/com/android/bluetooth/mapclient/MnsService.java +++ b/src/com/android/bluetooth/mapclient/MnsService.java @@ -17,6 +17,7 @@ package com.android.bluetooth.mapclient; import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothProfile; import android.bluetooth.BluetoothServerSocket; import android.bluetooth.BluetoothSocket; import android.os.Handler; @@ -129,6 +130,11 @@ public class MnsService { Log.e(TAG, "Error: NO statemachine for device: " + device.getAddress() + " (name: " + device.getName()); return false; + } else if (stateMachine.getState() != BluetoothProfile.STATE_CONNECTED) { + Log.e(TAG, "Error: statemachine for device: " + device.getAddress() + + " (name: " + device.getName() + ") is not currently CONNECTED : " + + stateMachine.getCurrentState()); + return false; } MnsObexServer srv = new MnsObexServer(stateMachine, sServerSockets); BluetoothObexTransport transport = new BluetoothObexTransport(socket); -- cgit v1.2.3 From f53904d617cfba99662175a2d9307c30e435c5c8 Mon Sep 17 00:00:00 2001 From: Joseph Pirozzo Date: Tue, 29 Oct 2019 20:52:35 -0700 Subject: MAP Client close connection on MNS disconnect If the MNS connection is terminated from the client side disconnect the corresponding MAS client. Bug: 129908795 Test: disconnect the MAP connection from the MAS server (MNS client) Change-Id: I39b524b2222d47a553eeaa4b9781f3200443cf8e (cherry picked from commit ec407574bee6c1cff4228daed8b413a82ff509b3) Merged-In: I39b524b2222d47a553eeaa4b9781f3200443cf8e Change-Id: I71426bef61234d1f1c61dfe94f23c266ea8e8a34 --- src/com/android/bluetooth/mapclient/MnsObexServer.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/com/android/bluetooth/mapclient/MnsObexServer.java b/src/com/android/bluetooth/mapclient/MnsObexServer.java index 53cd79bb2..33ba1ea74 100644 --- a/src/com/android/bluetooth/mapclient/MnsObexServer.java +++ b/src/com/android/bluetooth/mapclient/MnsObexServer.java @@ -90,6 +90,10 @@ class MnsObexServer extends ServerRequestHandler { if (VDBG) { Log.v(TAG, "onDisconnect"); } + MceStateMachine currentStateMachine = mStateMachineReference.get(); + if (currentStateMachine != null) { + currentStateMachine.disconnect(); + } } @Override -- cgit v1.2.3 From edaa9c5dda402e5d80bfb99a7ed5c4ead012488f Mon Sep 17 00:00:00 2001 From: Joseph Pirozzo Date: Tue, 29 Oct 2019 21:08:35 -0700 Subject: MAP Client disconnect state machine if MAS client disconnected If the MAS client disconnects (Obex error or remote device terminates connection) close the state machine safely. Bug: 138379476 Test: atest MapClientStateMachineTest Change-Id: I11362911fc96cb15416242456fa3cf026c085745 (cherry picked from commit 9b5cd43f3b297ddf9c0c2bd7136a7de7f45312a1) Merged-In: I11362911fc96cb15416242456fa3cf026c085745 Change-Id: Ie0ec535543ef477130878f7a722c2c89edf022a0 --- .../bluetooth/mapclient/MceStateMachine.java | 5 ++++ .../mapclient/MapClientStateMachineTest.java | 30 ++++++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/src/com/android/bluetooth/mapclient/MceStateMachine.java b/src/com/android/bluetooth/mapclient/MceStateMachine.java index cdfc98699..9b86aaef1 100644 --- a/src/com/android/bluetooth/mapclient/MceStateMachine.java +++ b/src/com/android/bluetooth/mapclient/MceStateMachine.java @@ -474,6 +474,11 @@ final class MceStateMachine extends StateMachine { } break; + case MSG_MAS_DISCONNECTED: + deferMessage(message); + transitionTo(mDisconnecting); + break; + case MSG_OUTBOUND_MESSAGE: mMasClient.makeRequest( new RequestPushMessage(FOLDER_OUTBOX, (Bmessage) message.obj, null, diff --git a/tests/unit/src/com/android/bluetooth/mapclient/MapClientStateMachineTest.java b/tests/unit/src/com/android/bluetooth/mapclient/MapClientStateMachineTest.java index 70854d8d1..ccfd9f810 100644 --- a/tests/unit/src/com/android/bluetooth/mapclient/MapClientStateMachineTest.java +++ b/tests/unit/src/com/android/bluetooth/mapclient/MapClientStateMachineTest.java @@ -150,6 +150,36 @@ public class MapClientStateMachineTest { Assert.assertEquals(BluetoothProfile.STATE_CONNECTED, mMceStateMachine.getState()); } + /** + * Test transition from STATE_CONNECTING --> (receive MSG_MAS_CONNECTED) --> STATE_CONNECTED + * --> (receive MSG_MAS_DISCONNECTED) --> STATE_DISCONNECTED + */ + @Test + public void testStateTransitionFromConnectedWithMasDisconnected() { + Log.i(TAG, "in testStateTransitionFromConnectedWithMasDisconnected"); + + setupSdpRecordReceipt(); + Message msg = Message.obtain(mHandler, MceStateMachine.MSG_MAS_CONNECTED); + mMceStateMachine.sendMessage(msg); + + // Wait until the message is processed and a broadcast request is sent to + // to MapClientService to change + // state from STATE_CONNECTING to STATE_CONNECTED + verify(mMockMapClientService, + timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(2)).sendBroadcast( + mIntentArgument.capture(), eq(ProfileService.BLUETOOTH_PERM)); + Assert.assertEquals(BluetoothProfile.STATE_CONNECTED, mMceStateMachine.getState()); + + msg = Message.obtain(mHandler, MceStateMachine.MSG_MAS_DISCONNECTED); + mMceStateMachine.sendMessage(msg); + verify(mMockMapClientService, + timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(4)).sendBroadcast( + mIntentArgument.capture(), eq(ProfileService.BLUETOOTH_PERM)); + + Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED, mMceStateMachine.getState()); + } + + /** * Test receiving an empty event report */ -- cgit v1.2.3 From 65bd91a08f78494f11638085974891edc4b50dca Mon Sep 17 00:00:00 2001 From: Simon Dai Date: Mon, 24 Jun 2019 15:28:02 -0700 Subject: Add bluetooth prefs for Android Automotive Add extra to BluetoothMediaBrowserService to redirect to settings when Bluetooth is not connected Fixes: 134514401 Test: verify extra in the media session state at startup and after disconnection. Change-Id: Iffbd6704eb82e02233b738c7ee06f5c4d8e3c0b5 (cherry picked from commit 6b9199e0548439300d41d70c7915012e2434ab78) Merged-In: Iffbd6704eb82e02233b738c7ee06f5c4d8e3c0b5 Change-Id: Ie5364fa2c0339c81e4444548d5e132caad789994 --- AndroidManifest.xml | 10 ++++++ res/values/strings.xml | 1 + src/com/android/bluetooth/BluetoothPrefs.java | 40 ++++++++++++++++++++++ .../AvrcpControllerStateMachine.java | 6 +--- .../BluetoothMediaBrowserService.java | 36 ++++++++++++++++--- 5 files changed, 83 insertions(+), 10 deletions(-) create mode 100644 src/com/android/bluetooth/BluetoothPrefs.java diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 8ce4707dc..6de611475 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -320,6 +320,16 @@ + + + + + + + Bluetooth audio disconnected" Bluetooth Audio Files bigger than 4GB cannot be transferred + Connect to Bluetooth diff --git a/src/com/android/bluetooth/BluetoothPrefs.java b/src/com/android/bluetooth/BluetoothPrefs.java new file mode 100644 index 000000000..2c7c87aaa --- /dev/null +++ b/src/com/android/bluetooth/BluetoothPrefs.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2019 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.android.bluetooth; + +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; + +/** + * Activity that routes to Bluetooth settings when launched + */ +public class BluetoothPrefs extends Activity { + + public static final String BLUETOOTH_SETTING_ACTION = "android.settings.BLUETOOTH_SETTINGS"; + public static final String BLUETOOTH_SETTING_CATEGORY = "android.intent.category.DEFAULT"; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + Intent launchIntent = new Intent(); + launchIntent.setAction(BLUETOOTH_SETTING_ACTION); + launchIntent.addCategory(BLUETOOTH_SETTING_CATEGORY); + startActivity(launchIntent); + finish(); + } +} diff --git a/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachine.java b/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachine.java index 23f1ce75a..ae94a4db5 100644 --- a/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachine.java +++ b/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachine.java @@ -233,11 +233,7 @@ class AvrcpControllerStateMachine extends StateMachine { mAddressedPlayer.updateCurrentTrack(null); mBrowseTree.mNowPlayingNode.setCached(false); BluetoothMediaBrowserService.notifyChanged(mBrowseTree.mNowPlayingNode); - PlaybackStateCompat.Builder pbb = new PlaybackStateCompat.Builder(); - pbb.setState(PlaybackStateCompat.STATE_ERROR, PlaybackStateCompat.PLAYBACK_POSITION_UNKNOWN, - 1.0f).setActions(0); - pbb.setErrorMessage(mService.getString(R.string.bluetooth_disconnected)); - BluetoothMediaBrowserService.notifyChanged(pbb.build()); + BluetoothMediaBrowserService.addressedPlayerChanged(null); mService.sBrowseTree.mRootNode.removeChild( mBrowseTree.mRootNode); BluetoothMediaBrowserService.notifyChanged(mService diff --git a/src/com/android/bluetooth/avrcpcontroller/BluetoothMediaBrowserService.java b/src/com/android/bluetooth/avrcpcontroller/BluetoothMediaBrowserService.java index b8a33379e..a0b1224ee 100644 --- a/src/com/android/bluetooth/avrcpcontroller/BluetoothMediaBrowserService.java +++ b/src/com/android/bluetooth/avrcpcontroller/BluetoothMediaBrowserService.java @@ -16,6 +16,8 @@ package com.android.bluetooth.avrcpcontroller; +import android.app.PendingIntent; +import android.content.Intent; import android.media.MediaMetadata; import android.media.browse.MediaBrowser.MediaItem; import android.os.Bundle; @@ -29,6 +31,7 @@ import android.util.Log; import androidx.media.MediaBrowserServiceCompat; +import com.android.bluetooth.BluetoothPrefs; import com.android.bluetooth.R; import java.util.ArrayList; @@ -60,6 +63,12 @@ public class BluetoothMediaBrowserService extends MediaBrowserServiceCompat { // Browsing related structures. private List mMediaQueue = new ArrayList<>(); + // Error messaging extras + public static final String ERROR_RESOLUTION_ACTION_INTENT = + "android.media.extras.ERROR_RESOLUTION_ACTION_INTENT"; + public static final String ERROR_RESOLUTION_ACTION_LABEL = + "android.media.extras.ERROR_RESOLUTION_ACTION_LABEL"; + /** * Initialize this BluetoothMediaBrowserService, creating our MediaSessionCompat, MediaPlayer * and MediaMetaData, and setting up mechanisms to talk with the AvrcpControllerService. @@ -76,11 +85,7 @@ public class BluetoothMediaBrowserService extends MediaBrowserServiceCompat { | MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS); mSession.setQueueTitle(getString(R.string.bluetooth_a2dp_sink_queue_name)); mSession.setQueue(mMediaQueue); - PlaybackStateCompat.Builder playbackStateBuilder = new PlaybackStateCompat.Builder(); - playbackStateBuilder.setState(PlaybackStateCompat.STATE_ERROR, - PlaybackStateCompat.PLAYBACK_POSITION_UNKNOWN, 1.0f).setActions(0); - playbackStateBuilder.setErrorMessage(getString(R.string.bluetooth_disconnected)); - mSession.setPlaybackState(playbackStateBuilder.build()); + setErrorPlaybackState(); sBluetoothMediaBrowserService = this; } @@ -94,6 +99,24 @@ public class BluetoothMediaBrowserService extends MediaBrowserServiceCompat { } } + private void setErrorPlaybackState() { + Bundle extras = new Bundle(); + extras.putString(ERROR_RESOLUTION_ACTION_LABEL, + getString(R.string.bluetooth_connect_action)); + Intent launchIntent = new Intent(); + launchIntent.setAction(BluetoothPrefs.BLUETOOTH_SETTING_ACTION); + launchIntent.addCategory(BluetoothPrefs.BLUETOOTH_SETTING_CATEGORY); + PendingIntent pendingIntent = PendingIntent.getActivity(getApplicationContext(), 0, + launchIntent, PendingIntent.FLAG_UPDATE_CURRENT); + extras.putParcelable(ERROR_RESOLUTION_ACTION_INTENT, pendingIntent); + PlaybackStateCompat errorState = new PlaybackStateCompat.Builder() + .setErrorMessage(getString(R.string.bluetooth_disconnected)) + .setExtras(extras) + .setState(PlaybackStateCompat.STATE_ERROR, 0, 0) + .build(); + mSession.setPlaybackState(errorState); + } + @Override public synchronized void onLoadChildren(final String parentMediaId, final Result> result) { @@ -138,6 +161,9 @@ public class BluetoothMediaBrowserService extends MediaBrowserServiceCompat { static synchronized void addressedPlayerChanged(MediaSessionCompat.Callback callback) { if (sBluetoothMediaBrowserService != null) { + if (callback == null) { + sBluetoothMediaBrowserService.setErrorPlaybackState(); + } sBluetoothMediaBrowserService.mSession.setCallback(callback); } else { Log.w(TAG, "addressedPlayerChanged Unavailable"); -- cgit v1.2.3 From 902bddd11cf3181a0ef5ee56c923e91b7b537d6c Mon Sep 17 00:00:00 2001 From: Joseph Pirozzo Date: Wed, 13 Nov 2019 09:59:15 -0800 Subject: AVRCP Controller State without Browsing Some devices do not support Browsing over AVRCP and the playback state of those devices was inconsistent on disconnection of the Control channel. This change improves the consistency of the data reported via the media_session. Bug: 144013853 Test: atest AvrcpControllerStateMachineTest Change-Id: I3ce26d76a81422d1e5af8b439d8bc1ab30d320dc (cherry picked from commit 527da93c0bfa15a95b1de9b93cb9d955885af849) Merged-In: I3ce26d76a81422d1e5af8b439d8bc1ab30d320dc Change-Id: I1eeb1d3bd1c4dcb3bd14ba9c2851a9ad4aee6b61 --- .../avrcpcontroller/AvrcpControllerStateMachine.java | 6 +++--- .../AvrcpControllerStateMachineTest.java | 17 +++++++++++++++++ 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachine.java b/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachine.java index ae94a4db5..e3abc4d8f 100644 --- a/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachine.java +++ b/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachine.java @@ -223,7 +223,6 @@ class AvrcpControllerStateMachine extends StateMachine { mService.sBrowseTree.mRootNode.addChild(mBrowseTree.mRootNode); BluetoothMediaBrowserService.notifyChanged(mService .sBrowseTree.mRootNode); - BluetoothMediaBrowserService.notifyChanged(mAddressedPlayer.getPlaybackState()); mBrowsingConnected = true; } @@ -233,12 +232,10 @@ class AvrcpControllerStateMachine extends StateMachine { mAddressedPlayer.updateCurrentTrack(null); mBrowseTree.mNowPlayingNode.setCached(false); BluetoothMediaBrowserService.notifyChanged(mBrowseTree.mNowPlayingNode); - BluetoothMediaBrowserService.addressedPlayerChanged(null); mService.sBrowseTree.mRootNode.removeChild( mBrowseTree.mRootNode); BluetoothMediaBrowserService.notifyChanged(mService .sBrowseTree.mRootNode); - BluetoothMediaBrowserService.trackChanged(null); mBrowsingConnected = false; } @@ -299,6 +296,7 @@ class AvrcpControllerStateMachine extends StateMachine { @Override public void enter() { if (mMostRecentState == BluetoothProfile.STATE_CONNECTING) { + BluetoothMediaBrowserService.notifyChanged(mAddressedPlayer.getPlaybackState()); broadcastConnectionStateChanged(BluetoothProfile.STATE_CONNECTED); BluetoothMediaBrowserService.addressedPlayerChanged(mSessionCallbacks); } else { @@ -712,6 +710,8 @@ class AvrcpControllerStateMachine extends StateMachine { @Override public void enter() { onBrowsingDisconnected(); + BluetoothMediaBrowserService.trackChanged(null); + BluetoothMediaBrowserService.addressedPlayerChanged(null); broadcastConnectionStateChanged(BluetoothProfile.STATE_DISCONNECTING); transitionTo(mDisconnected); } diff --git a/tests/unit/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachineTest.java b/tests/unit/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachineTest.java index f8ab6bbbf..0ca9c2a81 100644 --- a/tests/unit/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachineTest.java +++ b/tests/unit/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachineTest.java @@ -151,6 +151,10 @@ public class AvrcpControllerStateMachineTest { IsInstanceOf.instanceOf(AvrcpControllerStateMachine.Disconnected.class)); Assert.assertEquals(mAvrcpStateMachine.getState(), BluetoothProfile.STATE_DISCONNECTED); verify(mAvrcpControllerService).removeStateMachine(eq(mAvrcpStateMachine)); + MediaControllerCompat.TransportControls transportControls = + BluetoothMediaBrowserService.getTransportControls(); + Assert.assertEquals(PlaybackStateCompat.STATE_ERROR, + BluetoothMediaBrowserService.getPlaybackState()); } /** @@ -159,6 +163,11 @@ public class AvrcpControllerStateMachineTest { @Test public void testControlOnly() { int numBroadcastsSent = setUpConnectedState(true, false); + MediaControllerCompat.TransportControls transportControls = + BluetoothMediaBrowserService.getTransportControls(); + Assert.assertNotNull(transportControls); + Assert.assertEquals(PlaybackStateCompat.STATE_NONE, + BluetoothMediaBrowserService.getPlaybackState()); StackEvent event = StackEvent.connectionStateChanged(false, false); mAvrcpStateMachine.disconnect(); @@ -176,6 +185,8 @@ public class AvrcpControllerStateMachineTest { IsInstanceOf.instanceOf(AvrcpControllerStateMachine.Disconnected.class)); Assert.assertEquals(mAvrcpStateMachine.getState(), BluetoothProfile.STATE_DISCONNECTED); verify(mAvrcpControllerService).removeStateMachine(eq(mAvrcpStateMachine)); + Assert.assertEquals(PlaybackStateCompat.STATE_ERROR, + BluetoothMediaBrowserService.getPlaybackState()); } /** @@ -186,6 +197,8 @@ public class AvrcpControllerStateMachineTest { Assert.assertEquals(0, mAvrcpControllerService.sBrowseTree.mRootNode.getChildrenCount()); int numBroadcastsSent = setUpConnectedState(false, true); Assert.assertEquals(1, mAvrcpControllerService.sBrowseTree.mRootNode.getChildrenCount()); + Assert.assertEquals(PlaybackStateCompat.STATE_NONE, + BluetoothMediaBrowserService.getPlaybackState()); StackEvent event = StackEvent.connectionStateChanged(false, false); mAvrcpStateMachine.disconnect(); @@ -203,6 +216,10 @@ public class AvrcpControllerStateMachineTest { IsInstanceOf.instanceOf(AvrcpControllerStateMachine.Disconnected.class)); Assert.assertEquals(mAvrcpStateMachine.getState(), BluetoothProfile.STATE_DISCONNECTED); verify(mAvrcpControllerService).removeStateMachine(eq(mAvrcpStateMachine)); + MediaControllerCompat.TransportControls transportControls = + BluetoothMediaBrowserService.getTransportControls(); + Assert.assertEquals(PlaybackStateCompat.STATE_ERROR, + BluetoothMediaBrowserService.getPlaybackState()); } /** -- cgit v1.2.3 From 80c191cb3b35e635069b8b13ffc9e63bba79403d Mon Sep 17 00:00:00 2001 From: Joseph Pirozzo Date: Wed, 20 Nov 2019 09:07:23 -0800 Subject: A2DP Sink: Focus gain while transient loss While Bluetooth is under a transient focus loss if the user presses the play button, Bluetooth should attempt to regain audio focus rather than just wait passive for the transient holder to release it. Bug: 143916073 Test: atest A2dpSinkStreamHandlerTest Change-Id: I7b958a9ff06c9f1fcb9383a3a40ca147f4ad9399 (cherry picked from commit 97e20beea72e7b717465cb280ef576f5f2e29711) Merged-In: I7b958a9ff06c9f1fcb9383a3a40ca147f4ad9399 Change-Id: I78dfe8917d02c4b9d71d82db4fae586f52612050 --- .../android/bluetooth/a2dpsink/A2dpSinkStreamHandler.java | 5 +++-- .../bluetooth/a2dpsink/A2dpSinkStreamHandlerTest.java | 15 +++++++++++++++ 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamHandler.java b/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamHandler.java index ffd648af9..87ccfc873 100644 --- a/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamHandler.java +++ b/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamHandler.java @@ -183,8 +183,9 @@ public class A2dpSinkStreamHandler extends Handler { break; case AUDIO_FOCUS_CHANGE: + mAudioFocus = (int) message.obj; // message.obj is the newly granted audio focus. - switch ((int) message.obj) { + switch (mAudioFocus) { case AudioManager.AUDIOFOCUS_GAIN: removeMessages(DELAYED_PAUSE); // Begin playing audio, if we paused the remote, send a play now. @@ -245,7 +246,7 @@ public class A2dpSinkStreamHandler extends Handler { */ private void requestAudioFocusIfNone() { if (DBG) Log.d(TAG, "requestAudioFocusIfNone()"); - if (mAudioFocus == AudioManager.AUDIOFOCUS_NONE) { + if (mAudioFocus != AudioManager.AUDIOFOCUS_GAIN) { requestAudioFocus(); } // On the off change mMediaPlayer errors out and dies, we want to make sure we retry this. diff --git a/tests/unit/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamHandlerTest.java b/tests/unit/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamHandlerTest.java index c759a8a1f..b8fbbb9ef 100644 --- a/tests/unit/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamHandlerTest.java +++ b/tests/unit/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamHandlerTest.java @@ -194,6 +194,21 @@ public class A2dpSinkStreamHandlerTest { verify(mMockA2dpSink, times(1)).informAudioTrackGainNative(0); } + @Test + public void testFocusRerequest() { + // Focus was lost transiently, expect streaming to stop. + testSnkPlay(); + mStreamHandler.handleMessage( + mStreamHandler.obtainMessage(A2dpSinkStreamHandler.AUDIO_FOCUS_CHANGE, + AudioManager.AUDIOFOCUS_LOSS_TRANSIENT)); + verify(mMockAudioManager, times(0)).abandonAudioFocus(any()); + verify(mMockA2dpSink, times(0)).informAudioFocusStateNative(0); + verify(mMockA2dpSink, times(1)).informAudioTrackGainNative(0); + mStreamHandler.handleMessage( + mStreamHandler.obtainMessage(A2dpSinkStreamHandler.REQUEST_FOCUS, true)); + verify(mMockAudioManager, times(2)).requestAudioFocus(any()); + } + @Test public void testFocusGainTransient() { // Focus was lost then regained. -- cgit v1.2.3 From 3797ae45f48e706acae23d213ad1b828db0bad15 Mon Sep 17 00:00:00 2001 From: Joseph Pirozzo Date: Wed, 20 Nov 2019 10:30:57 -0800 Subject: AVRCP Controller Disable Automatic Focus Request Remove automatic focus requests when isMusicActive returns false as there are too many edge cases in behavior to work well. Bug: 144507838 Test: Attempt to play media from connected device while no audio is active Change-Id: I547d195012c56dc73029ef59ed24bbfe7d536646 (cherry picked from commit 7bf3489331e9b645c3692f9f86beb108a1a0e874) Merged-In: I547d195012c56dc73029ef59ed24bbfe7d536646 Change-Id: I5870c92a24d1501bd10162d28a3dde5e83b26251 --- .../android/bluetooth/avrcpcontroller/AvrcpControllerStateMachine.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachine.java b/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachine.java index e3abc4d8f..8704b078e 100644 --- a/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachine.java +++ b/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachine.java @@ -881,7 +881,6 @@ class AvrcpControllerStateMachine extends StateMachine { private boolean shouldRequestFocus() { return mService.getResources() - .getBoolean(R.bool.a2dp_sink_automatically_request_audio_focus) - || !mAudioManager.isMusicActive(); + .getBoolean(R.bool.a2dp_sink_automatically_request_audio_focus); } } -- cgit v1.2.3 From da1dc71cffb5fbb1e55186a20410e3d4f95246c3 Mon Sep 17 00:00:00 2001 From: Andrew Cheng Date: Thu, 15 Aug 2019 09:32:41 -0700 Subject: PBAP server, send favorite contacts Bug: 132636859 Test: ACTS/SL4A and PTS Change-Id: I57326dfafe898187477b524b0fe03b181292dda9 (cherry picked from commit 131e778204a9143ad27793a341f4e13925b7d44a) Merged-In: I57326dfafe898187477b524b0fe03b181292dda9 Change-Id: Icfa9a81d9aaf4ddad35e51b5410b8b367d820160 --- .../bluetooth/pbap/BluetoothPbapObexServer.java | 65 +++++++++++++++++----- .../bluetooth/pbap/BluetoothPbapService.java | 3 +- .../bluetooth/pbap/BluetoothPbapVcardManager.java | 37 +++++++++--- 3 files changed, 82 insertions(+), 23 deletions(-) diff --git a/src/com/android/bluetooth/pbap/BluetoothPbapObexServer.java b/src/com/android/bluetooth/pbap/BluetoothPbapObexServer.java index 979becdca..34ee96270 100755 --- a/src/com/android/bluetooth/pbap/BluetoothPbapObexServer.java +++ b/src/com/android/bluetooth/pbap/BluetoothPbapObexServer.java @@ -97,6 +97,7 @@ public class BluetoothPbapObexServer extends ServerRequestHandler { private static final String[] LEGAL_PATH = { "/telecom", "/telecom/pb", + "/telecom/fav", "/telecom/ich", "/telecom/och", "/telecom/mch", @@ -106,6 +107,7 @@ public class BluetoothPbapObexServer extends ServerRequestHandler { @SuppressWarnings("unused") private static final String[] LEGAL_PATH_WITH_SIM = { "/telecom", "/telecom/pb", + "/telecom/fav", "/telecom/ich", "/telecom/och", "/telecom/mch", @@ -138,6 +140,9 @@ public class BluetoothPbapObexServer extends ServerRequestHandler { // phone book private static final String PB = "pb"; + // favorites + private static final String FAV = "fav"; + private static final String TELECOM_PATH = "/telecom"; private static final String ICH_PATH = "/telecom/ich"; @@ -150,6 +155,8 @@ public class BluetoothPbapObexServer extends ServerRequestHandler { private static final String PB_PATH = "/telecom/pb"; + private static final String FAV_PATH = "/telecom/fav"; + // type for list vcard objects private static final String TYPE_LISTING = "x-bt/vcard-listing"; @@ -212,6 +219,8 @@ public class BluetoothPbapObexServer extends ServerRequestHandler { public static final int MISSED_CALL_HISTORY = 4; public static final int COMBINED_CALL_HISTORY = 5; + + public static final int FAVORITES = 6; } public BluetoothPbapObexServer(Handler callback, Context context, @@ -441,6 +450,8 @@ public class BluetoothPbapObexServer extends ServerRequestHandler { if (mCurrentPath.equals(PB_PATH)) { appParamValue.needTag = ContentType.PHONEBOOK; + } else if (mCurrentPath.equals(FAV_PATH)) { + appParamValue.needTag = ContentType.FAVORITES; } else if (mCurrentPath.equals(ICH_PATH)) { appParamValue.needTag = ContentType.INCOMING_CALL_HISTORY; } else if (mCurrentPath.equals(OCH_PATH)) { @@ -478,6 +489,11 @@ public class BluetoothPbapObexServer extends ServerRequestHandler { if (D) { Log.v(TAG, "download phonebook request"); } + } else if (isNameMatchTarget(name, FAV)) { + appParamValue.needTag = ContentType.FAVORITES; + if (D) { + Log.v(TAG, "download favorites request"); + } } else if (isNameMatchTarget(name, ICH)) { appParamValue.needTag = ContentType.INCOMING_CALL_HISTORY; appParamValue.callHistoryVersionCounter = @@ -751,7 +767,8 @@ public class BluetoothPbapObexServer extends ServerRequestHandler { result.append(""); // Phonebook listing request - if (appParamValue.needTag == ContentType.PHONEBOOK) { + if ((appParamValue.needTag == ContentType.PHONEBOOK) + || (appParamValue.needTag == ContentType.FAVORITES)) { String type = ""; if (appParamValue.searchAttr.equals("0")) { type = "name"; @@ -948,7 +965,7 @@ public class BluetoothPbapObexServer extends ServerRequestHandler { checkPbapFeatureSupport(mFolderVersionCounterbitMask); } boolean needSendPhonebookVersionCounters = false; - if (isNameMatchTarget(name, PB)) { + if (isNameMatchTarget(name, PB) || isNameMatchTarget(name, FAV)) { needSendPhonebookVersionCounters = checkPbapFeatureSupport(mFolderVersionCounterbitMask); } @@ -1192,11 +1209,12 @@ public class BluetoothPbapObexServer extends ServerRequestHandler { if (appParamValue.needTag == 0) { Log.w(TAG, "wrong path!"); return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE; - } else if (appParamValue.needTag == ContentType.PHONEBOOK) { + } else if ((appParamValue.needTag == ContentType.PHONEBOOK) + || (appParamValue.needTag == ContentType.FAVORITES)) { if (intIndex < 0 || intIndex >= size) { Log.w(TAG, "The requested vcard is not acceptable! name= " + name); return ResponseCodes.OBEX_HTTP_NOT_FOUND; - } else if (intIndex == 0) { + } else if ((intIndex == 0) && (appParamValue.needTag == ContentType.PHONEBOOK)) { // For PB_PATH, 0.vcf is the phone number of this phone. String ownerVcard = mVcardManager.getOwnerPhoneNumberVcard(vcard21, appParamValue.ignorefilter ? null : appParamValue.propertySelector); @@ -1252,30 +1270,49 @@ public class BluetoothPbapObexServer extends ServerRequestHandler { int requestSize = pbSize >= appParamValue.maxListCount ? appParamValue.maxListCount : pbSize; - int startPoint = appParamValue.listStartOffset; - if (startPoint < 0 || startPoint >= pbSize) { + /** + * startIndex (resp., lastIndex) corresponds to the index of the first (resp., last) + * vcard entry in the phonebook object. + * PBAP v1.2.3: only pb starts indexing at 0.vcf (owner card), the other phonebook + * objects (e.g., fav) start at 1.vcf. Additionally, the owner card is included in + * pb's pbSize. This means pbSize corresponds to the index of the last vcf in the fav + * phonebook object, but does not for the pb phonebook object. + */ + int startIndex = 1; + int lastIndex = pbSize; + if (appParamValue.needTag == BluetoothPbapObexServer.ContentType.PHONEBOOK) { + startIndex = 0; + lastIndex = pbSize - 1; + } + // [startPoint, endPoint] denote the range of vcf indices to send, inclusive. + int startPoint = startIndex + appParamValue.listStartOffset; + int endPoint = startPoint + requestSize - 1; + if (appParamValue.listStartOffset < 0 || startPoint > lastIndex) { Log.w(TAG, "listStartOffset is not correct! " + startPoint); return ResponseCodes.OBEX_HTTP_OK; } + if (endPoint > lastIndex) { + endPoint = lastIndex; + } // Limit the number of call log to CALLLOG_NUM_LIMIT - if (appParamValue.needTag != BluetoothPbapObexServer.ContentType.PHONEBOOK) { + if ((appParamValue.needTag != BluetoothPbapObexServer.ContentType.PHONEBOOK) + && (appParamValue.needTag != BluetoothPbapObexServer.ContentType.FAVORITES)) { if (requestSize > CALLLOG_NUM_LIMIT) { requestSize = CALLLOG_NUM_LIMIT; } } - int endPoint = startPoint + requestSize - 1; - if (endPoint > pbSize - 1) { - endPoint = pbSize - 1; - } if (D) { Log.d(TAG, "pullPhonebook(): requestSize=" + requestSize + " startPoint=" + startPoint + " endPoint=" + endPoint); } boolean vcard21 = appParamValue.vcard21; - if (appParamValue.needTag == BluetoothPbapObexServer.ContentType.PHONEBOOK) { + boolean favorites = + (appParamValue.needTag == BluetoothPbapObexServer.ContentType.FAVORITES); + if ((appParamValue.needTag == BluetoothPbapObexServer.ContentType.PHONEBOOK) + || favorites) { if (startPoint == 0) { String ownerVcard = mVcardManager.getOwnerPhoneNumberVcard(vcard21, appParamValue.ignorefilter ? null : appParamValue.propertySelector); @@ -1285,13 +1322,13 @@ public class BluetoothPbapObexServer extends ServerRequestHandler { return mVcardManager.composeAndSendPhonebookVcards(op, 1, endPoint, vcard21, ownerVcard, needSendBody, pbSize, appParamValue.ignorefilter, appParamValue.propertySelector, appParamValue.vCardSelector, - appParamValue.vCardSelectorOperator, mVcardSelector); + appParamValue.vCardSelectorOperator, mVcardSelector, favorites); } } else { return mVcardManager.composeAndSendPhonebookVcards(op, startPoint, endPoint, vcard21, null, needSendBody, pbSize, appParamValue.ignorefilter, appParamValue.propertySelector, appParamValue.vCardSelector, - appParamValue.vCardSelectorOperator, mVcardSelector); + appParamValue.vCardSelectorOperator, mVcardSelector, favorites); } } else { return mVcardManager.composeAndSendSelectedCallLogVcards(appParamValue.needTag, op, diff --git a/src/com/android/bluetooth/pbap/BluetoothPbapService.java b/src/com/android/bluetooth/pbap/BluetoothPbapService.java index e7dba2a96..280186f8f 100644 --- a/src/com/android/bluetooth/pbap/BluetoothPbapService.java +++ b/src/com/android/bluetooth/pbap/BluetoothPbapService.java @@ -140,7 +140,8 @@ public class BluetoothPbapService extends ProfileService implements IObexConnect private ObexServerSockets mServerSockets = null; private static final int SDP_PBAP_SERVER_VERSION = 0x0102; - private static final int SDP_PBAP_SUPPORTED_REPOSITORIES = 0x0001; + // PBAP v1.2.3, Sec. 7.1.2: local phonebook and favorites + private static final int SDP_PBAP_SUPPORTED_REPOSITORIES = 0x0009; private static final int SDP_PBAP_SUPPORTED_FEATURES = 0x021F; /* PBAP will use Bluetooth notification ID from 1000000 (included) to 2000000 (excluded). diff --git a/src/com/android/bluetooth/pbap/BluetoothPbapVcardManager.java b/src/com/android/bluetooth/pbap/BluetoothPbapVcardManager.java index 5ba2b4b8b..8801c16fb 100755 --- a/src/com/android/bluetooth/pbap/BluetoothPbapVcardManager.java +++ b/src/com/android/bluetooth/pbap/BluetoothPbapVcardManager.java @@ -153,7 +153,8 @@ public class BluetoothPbapVcardManager { int size; switch (type) { case BluetoothPbapObexServer.ContentType.PHONEBOOK: - size = getContactsSize(); + case BluetoothPbapObexServer.ContentType.FAVORITES: + size = getContactsSize(type); break; default: size = getCallHistorySize(type); @@ -165,16 +166,30 @@ public class BluetoothPbapVcardManager { return size; } - public final int getContactsSize() { + /** + * Returns the number of contacts (i.e., vcf) in a phonebook object. + * @param type specifies which phonebook object, e.g., pb, fav + * @return + */ + public final int getContactsSize(final int type) { final Uri myUri = DevicePolicyUtils.getEnterprisePhoneUri(mContext); Cursor contactCursor = null; + String selectionClause = null; + if (type == BluetoothPbapObexServer.ContentType.FAVORITES) { + selectionClause = Phone.STARRED + " = 1"; + } try { - contactCursor = mResolver.query(myUri, new String[]{Phone.CONTACT_ID}, null, null, - Phone.CONTACT_ID); + contactCursor = mResolver.query(myUri, + new String[]{Phone.CONTACT_ID}, selectionClause, + null, Phone.CONTACT_ID); if (contactCursor == null) { return 0; } - return getDistinctContactIdSize(contactCursor) + 1; // always has the 0.vcf + int contactsSize = getDistinctContactIdSize(contactCursor); + if (type == BluetoothPbapObexServer.ContentType.PHONEBOOK) { + contactsSize += 1; // pb has the 0.vcf owner's card + } + return contactsSize; } catch (CursorWindowAllocationException e) { Log.e(TAG, "CursorWindowAllocationException while getting Contacts size"); } finally { @@ -551,7 +566,7 @@ public class BluetoothPbapVcardManager { final int composeAndSendPhonebookVcards(Operation op, final int startPoint, final int endPoint, final boolean vcardType21, String ownerVCard, int needSendBody, int pbSize, boolean ignorefilter, byte[] filter, byte[] vcardselector, String vcardselectorop, - boolean vcardselect) { + boolean vcardselect, boolean favorites) { if (startPoint < 1 || startPoint > endPoint) { Log.e(TAG, "internal error: startPoint or endPoint is not correct."); return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; @@ -562,9 +577,15 @@ public class BluetoothPbapVcardManager { Cursor contactIdCursor = new MatrixCursor(new String[]{ Phone.CONTACT_ID }); + + String selectionClause = null; + if (favorites) { + selectionClause = Phone.STARRED + " = 1"; + } + try { - contactCursor = mResolver.query(myUri, PHONES_CONTACTS_PROJECTION, null, null, - Phone.CONTACT_ID); + contactCursor = mResolver.query(myUri, PHONES_CONTACTS_PROJECTION, selectionClause, + null, Phone.CONTACT_ID); if (contactCursor != null) { contactIdCursor = ContactCursorFilter.filterByRange(contactCursor, startPoint, endPoint); -- cgit v1.2.3 From 1efbe39d3e325566462f19c3fd80ead92c8e5581 Mon Sep 17 00:00:00 2001 From: Joseph Pirozzo Date: Wed, 11 Dec 2019 17:04:47 -0800 Subject: HFP Client call status update When a call is terminating only inform telecom about its termination when there is an actual state change from the attached phone. Bug: 138753272 Test: start and end call, observe logs from HfpClientDeviceBlock that a closing call still in its previous state doesn't get destroyed prematurely. Change-Id: Ia5d105daa239f64b73c058d064c2e5181d862e4e (cherry picked from commit c1f83b71f34d101c023db154607c8ea50218214f) Merged-In: Ia5d105daa239f64b73c058d064c2e5181d862e4e Change-Id: I5d9d59c64bc8a79b397d2d4ef6feef1320345f42 --- src/com/android/bluetooth/hfpclient/connserv/HfpClientDeviceBlock.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/com/android/bluetooth/hfpclient/connserv/HfpClientDeviceBlock.java b/src/com/android/bluetooth/hfpclient/connserv/HfpClientDeviceBlock.java index 05af73e08..b567371f1 100644 --- a/src/com/android/bluetooth/hfpclient/connserv/HfpClientDeviceBlock.java +++ b/src/com/android/bluetooth/hfpclient/connserv/HfpClientDeviceBlock.java @@ -224,7 +224,7 @@ public class HfpClientDeviceBlock { if (DBG) { Log.d(mTAG, "prevConn " + prevConn.isClosing() + " new call " + newCall.getState()); } - if (prevConn.isClosing() + if (prevConn.isClosing() && prevConn.getCall().getState() != newCall.getState() && newCall.getState() != BluetoothHeadsetClientCall.CALL_STATE_TERMINATED) { return true; } -- cgit v1.2.3 From c1c7176aa4b6785b2bd071f5caed0862faa14510 Mon Sep 17 00:00:00 2001 From: Sal Savage Date: Thu, 19 Dec 2019 15:19:14 -0800 Subject: Allow subsequent requests for media keys to replay the silent audio sample Bug: b/146010863 Test: Build, flash, interopt test with hard key events using automotive hardware. Change-Id: I4943c9f79011a1ee315fd3178af9171444dffa0b (cherry picked from commit f43be3b331a4a5575f2bbd4d631531c1f17e17fe) Merged-In: I4943c9f79011a1ee315fd3178af9171444dffa0b Change-Id: I8ef5ff15c3570fc98b80a1bfe8f750d7eaa5d32f --- .../bluetooth/a2dpsink/A2dpSinkStreamHandler.java | 47 +++++++++++----------- 1 file changed, 24 insertions(+), 23 deletions(-) diff --git a/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamHandler.java b/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamHandler.java index 87ccfc873..5aa3cbbd5 100644 --- a/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamHandler.java +++ b/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamHandler.java @@ -249,9 +249,6 @@ public class A2dpSinkStreamHandler extends Handler { if (mAudioFocus != AudioManager.AUDIOFOCUS_GAIN) { requestAudioFocus(); } - // On the off change mMediaPlayer errors out and dies, we want to make sure we retry this. - // This function immediately exits if we have a MediaPlayer object. - requestMediaKeyFocus(); } private synchronized int requestAudioFocus() { @@ -278,8 +275,11 @@ public class A2dpSinkStreamHandler extends Handler { } /** - * Creates a MediaPlayer that plays a silent audio sample so that MediaSessionService will be - * aware of the fact that Bluetooth is playing audio. + * Plays a silent audio sample so that MediaSessionService will be aware of the fact that + * Bluetooth is playing audio. + * + * Creates a new MediaPlayer if one does not already exist. Repeat calls to this function are + * safe and will result in the silent audio sample again. * * This allows the MediaSession in AVRCP Controller to be routed media key events, if we've * chosen to use it. @@ -287,25 +287,25 @@ public class A2dpSinkStreamHandler extends Handler { private synchronized void requestMediaKeyFocus() { if (DBG) Log.d(TAG, "requestMediaKeyFocus()"); - if (mMediaPlayer != null) return; - - AudioAttributes attrs = new AudioAttributes.Builder() - .setUsage(AudioAttributes.USAGE_MEDIA) - .build(); - - mMediaPlayer = MediaPlayer.create(mContext, R.raw.silent, attrs, - mAudioManager.generateAudioSessionId()); if (mMediaPlayer == null) { - Log.e(TAG, "Failed to initialize media player. You may not get media key events"); - return; - } + AudioAttributes attrs = new AudioAttributes.Builder() + .setUsage(AudioAttributes.USAGE_MEDIA) + .build(); + + mMediaPlayer = MediaPlayer.create(mContext, R.raw.silent, attrs, + mAudioManager.generateAudioSessionId()); + if (mMediaPlayer == null) { + Log.e(TAG, "Failed to initialize media player. You may not get media key events"); + return; + } - mMediaPlayer.setLooping(false); - mMediaPlayer.setOnErrorListener((mp, what, extra) -> { - Log.e(TAG, "Silent media player error: " + what + ", " + extra); - releaseMediaKeyFocus(); - return false; - }); + mMediaPlayer.setLooping(false); + mMediaPlayer.setOnErrorListener((mp, what, extra) -> { + Log.e(TAG, "Silent media player error: " + what + ", " + extra); + releaseMediaKeyFocus(); + return false; + }); + } mMediaPlayer.start(); BluetoothMediaBrowserService.setActive(true); @@ -314,7 +314,6 @@ public class A2dpSinkStreamHandler extends Handler { private synchronized void abandonAudioFocus() { if (DBG) Log.d(TAG, "abandonAudioFocus()"); stopFluorideStreaming(); - releaseMediaKeyFocus(); mAudioManager.abandonAudioFocus(mAudioFocusListener); mAudioFocus = AudioManager.AUDIOFOCUS_NONE; } @@ -337,9 +336,11 @@ public class A2dpSinkStreamHandler extends Handler { private void startFluorideStreaming() { mA2dpSinkService.informAudioFocusStateNative(STATE_FOCUS_GRANTED); mA2dpSinkService.informAudioTrackGainNative(1.0f); + requestMediaKeyFocus(); } private void stopFluorideStreaming() { + releaseMediaKeyFocus(); mA2dpSinkService.informAudioFocusStateNative(STATE_FOCUS_LOST); } -- cgit v1.2.3 From 46d12da05b1ed7420ce83525d95b847d0f69e9f2 Mon Sep 17 00:00:00 2001 From: Joseph Pirozzo Date: Thu, 30 Jan 2020 19:45:14 -0800 Subject: AVRCP Controller Media Controller not ready Flip two lines of code to ensure that the media controller is set before updating the available actions. Prevents a race condition where a media player responds to a media state change before the controller is available. Bug: 147469352 Test: Inspection (And many on off cycles) Change-Id: I2aad90c1028ab0ca81da09233e7fb49bf18d89c9 (cherry picked from commit 64d01153446c3751085a848a63e155285bb52805) Merged-In: I2aad90c1028ab0ca81da09233e7fb49bf18d89c9 Change-Id: Ieac01f46658d8e426666b8b316caf69d31fc4c5a --- .../android/bluetooth/avrcpcontroller/AvrcpControllerStateMachine.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachine.java b/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachine.java index 8704b078e..c319364c1 100644 --- a/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachine.java +++ b/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachine.java @@ -296,9 +296,9 @@ class AvrcpControllerStateMachine extends StateMachine { @Override public void enter() { if (mMostRecentState == BluetoothProfile.STATE_CONNECTING) { + BluetoothMediaBrowserService.addressedPlayerChanged(mSessionCallbacks); BluetoothMediaBrowserService.notifyChanged(mAddressedPlayer.getPlaybackState()); broadcastConnectionStateChanged(BluetoothProfile.STATE_CONNECTED); - BluetoothMediaBrowserService.addressedPlayerChanged(mSessionCallbacks); } else { logD("ReEnteringConnected"); } -- cgit v1.2.3 From 6034badf786924de33103f7dea77b9652c827b9c Mon Sep 17 00:00:00 2001 From: Joseph Pirozzo Date: Tue, 4 Feb 2020 15:30:29 -0800 Subject: AvrcpController Test update Resolve test failures by use of mocked resource overlays. Two tests were failing: - testPlaybackWhileMusicPlaying - testPlaybackWhileIdle Bug: 147432575 Test: atest AvrcpControllerStateMachineTest Change-Id: Id71a7e50bb00d0cc9b15f84a9bfd913a8320fc7e (cherry picked from commit 56ea19154bb0669e5e91e5578e68163a6b2643ee) Merged-In: Id71a7e50bb00d0cc9b15f84a9bfd913a8320fc7e Change-Id: Ife2945b8cf314d35425ffb51c37f94fdc3a18257 --- .../avrcpcontroller/AvrcpControllerStateMachineTest.java | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/tests/unit/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachineTest.java b/tests/unit/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachineTest.java index 0ca9c2a81..907f0dcdc 100644 --- a/tests/unit/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachineTest.java +++ b/tests/unit/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachineTest.java @@ -23,6 +23,7 @@ import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothProfile; import android.content.Context; import android.content.Intent; +import android.content.res.Resources; import android.media.AudioManager; import android.os.Looper; import android.support.v4.media.session.MediaControllerCompat; @@ -82,6 +83,9 @@ public class AvrcpControllerStateMachineTest { @Mock private AvrcpControllerService mAvrcpControllerService; + @Mock + private Resources mMockResources; + AvrcpControllerStateMachine mAvrcpStateMachine; @Before @@ -102,7 +106,9 @@ public class AvrcpControllerStateMachineTest { TestUtils.clearAdapterService(mAvrcpAdapterService); TestUtils.setAdapterService(mA2dpAdapterService); TestUtils.startService(mA2dpServiceRule, A2dpSinkService.class); - doReturn(mTargetContext.getResources()).when(mAvrcpControllerService).getResources(); + when(mMockResources.getBoolean(R.bool.a2dp_sink_automatically_request_audio_focus)) + .thenReturn(true); + doReturn(mMockResources).when(mAvrcpControllerService).getResources(); doReturn(15).when(mAudioManager).getStreamMaxVolume(anyInt()); doReturn(8).when(mAudioManager).getStreamVolume(anyInt()); doReturn(true).when(mAudioManager).isVolumeFixed(); @@ -601,6 +607,8 @@ public class AvrcpControllerStateMachineTest { */ @Test public void testPlaybackWhileMusicPlaying() { + when(mMockResources.getBoolean(R.bool.a2dp_sink_automatically_request_audio_focus)) + .thenReturn(false); Assert.assertEquals(AudioManager.AUDIOFOCUS_NONE, A2dpSinkService.getFocusState()); doReturn(true).when(mAudioManager).isMusicActive(); setUpConnectedState(true, true); @@ -608,7 +616,6 @@ public class AvrcpControllerStateMachineTest { AvrcpControllerStateMachine.MESSAGE_PROCESS_PLAY_STATUS_CHANGED, PlaybackStateCompat.STATE_PLAYING); TestUtils.waitForLooperToFinishScheduledTask(mAvrcpStateMachine.getHandler().getLooper()); - verify(mAudioManager, times(1)).isMusicActive(); verify(mAvrcpControllerService, timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).sendPassThroughCommandNative( eq(mTestAddress), eq(AvrcpControllerService.PASS_THRU_CMD_ID_PAUSE), eq(KEY_DOWN)); @@ -629,7 +636,6 @@ public class AvrcpControllerStateMachineTest { AvrcpControllerStateMachine.MESSAGE_PROCESS_PLAY_STATUS_CHANGED, PlaybackStateCompat.STATE_PLAYING); TestUtils.waitForLooperToFinishScheduledTask(mAvrcpStateMachine.getHandler().getLooper()); - verify(mAudioManager, times(1)).isMusicActive(); TestUtils.waitForLooperToFinishScheduledTask( A2dpSinkService.getA2dpSinkService().getMainLooper()); Assert.assertEquals(AudioManager.AUDIOFOCUS_GAIN, A2dpSinkService.getFocusState()); -- cgit v1.2.3 From 0c5249af8f81b0b9aac51cc233914b1649c5f000 Mon Sep 17 00:00:00 2001 From: Sal Savage Date: Thu, 6 Feb 2020 15:36:50 -0800 Subject: Return an empty list when the requested node is not in the tree This change makes it so a request for the data in a folder that doesn't exist always returns promptly. Prior to this, all requests would hang indefinitely instead. This is only a stand in for a future solution to send a null result through the media framework instead. Unfortunately, our structure needs some tweaking to make that work. This will be fine for the interim. Bug: 148604797 Test: Build, flash, browse deep into a tree, switch players to invalid that parents of the node you're on, try browsing upward. See that we return an empty list for all missing nodes. Change-Id: Ifd5ed106d3b46377f0d7cd2d1e565cde45ab06f9 Merged-In: Ifd5ed106d3b46377f0d7cd2d1e565cde45ab06f9 --- src/com/android/bluetooth/avrcpcontroller/AvrcpControllerService.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerService.java b/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerService.java index 693b4a291..56684cd7c 100644 --- a/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerService.java +++ b/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerService.java @@ -172,9 +172,11 @@ public class AvrcpControllerService extends ProfileService { } } + // If we don't find a node in the tree then do not have any way to browse for the contents. + // Return an empty list instead. if (requestedNode == null) { if (DBG) Log.d(TAG, "Didn't find a node"); - return null; + return new ArrayList(0); } else { if (!requestedNode.isCached()) { if (DBG) Log.d(TAG, "node is not cached"); -- cgit v1.2.3 From 01bcd0f9bf286e6987d24b1d3774d5343ee0c6f3 Mon Sep 17 00:00:00 2001 From: Andrew Cheng Date: Fri, 11 Oct 2019 17:39:42 -0700 Subject: Prevent phone's bd_addr from appearing in Accounts Bluetooth's pbapclient needs to create an Account to help manage the syncing of phonebook contacts and call logs. This Bluetooth-generated Account will show up in Settings > Accounts, leading to a confusing and poor user experience. This CL will prevent this Account from appearing. This fix relies on the fact that getAccountPreferences in AccountListPreferenceController will skip showing any Account that has a null 'label'. Bug: 131857089 Test: Verify in Settings > Accounts that the phone's bd_addr is no longer listed. Change-Id: I31dec95f3cd2d66f2f1b1c9bf80cb9c3031b585b Merged-In: I31dec95f3cd2d66f2f1b1c9bf80cb9c3031b585b (cherry picked from commit 8ff121fce067ffae9f76610f276a3bd88e110b87) --- res/xml/authenticator.xml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/res/xml/authenticator.xml b/res/xml/authenticator.xml index b719fec4f..ab08a6103 100644 --- a/res/xml/authenticator.xml +++ b/res/xml/authenticator.xml @@ -17,5 +17,4 @@ xmlns:android="http://schemas.android.com/apk/res/android" android:icon="@mipmap/bt_share" android:smallIcon="@mipmap/bt_share" - android:accountType="@string/pbap_account_type" - android:label="@string/pbap_account_type" /> + android:accountType="@string/pbap_account_type" /> -- cgit v1.2.3 From 8cc90212a21c562e0cd2e4c751465ebb1c942c95 Mon Sep 17 00:00:00 2001 From: Andrew Cheng Date: Wed, 20 Nov 2019 09:29:31 -0800 Subject: Set Browsing bit to 0 in PbapSupportedFeatures PbapClient currently doesn't support browsing. This CL sets the bit to zero in PbapSupportedFeatures to accurately reflect the functionalities. Bug: 139624050 Test: Check the snoop/HCI logs. Look under the OBEX protocol header of the connection request packet for PBAP. Under Application Parameters > PbapSupportedFeatures, verify the Browsing bit is now 0. Change-Id: I2fcd959abf6aba178cd5fffb2f6e69cf1863fd89 Merged-In: I2fcd959abf6aba178cd5fffb2f6e69cf1863fd89 (cherry picked from commit 522d6cb1d42b80b64a51645105ec5bb0b561a5e9) --- src/com/android/bluetooth/pbapclient/PbapClientConnectionHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/com/android/bluetooth/pbapclient/PbapClientConnectionHandler.java b/src/com/android/bluetooth/pbapclient/PbapClientConnectionHandler.java index 914b5b163..e2e35be1a 100644 --- a/src/com/android/bluetooth/pbapclient/PbapClientConnectionHandler.java +++ b/src/com/android/bluetooth/pbapclient/PbapClientConnectionHandler.java @@ -88,7 +88,7 @@ class PbapClientConnectionHandler extends Handler { private static final long PBAP_FILTER_NICKNAME = 1 << 23; private static final int PBAP_SUPPORTED_FEATURE = - PBAP_FEATURE_DEFAULT_IMAGE_FORMAT | PBAP_FEATURE_BROWSING | PBAP_FEATURE_DOWNLOADING; + PBAP_FEATURE_DEFAULT_IMAGE_FORMAT | PBAP_FEATURE_DOWNLOADING; private static final long PBAP_REQUESTED_FIELDS = PBAP_FILTER_VERSION | PBAP_FILTER_FN | PBAP_FILTER_N | PBAP_FILTER_PHOTO | PBAP_FILTER_ADR | PBAP_FILTER_EMAIL | PBAP_FILTER_TEL | PBAP_FILTER_NICKNAME; -- cgit v1.2.3 From b0add93bd09f8ad8383174080908b0e41026fcd2 Mon Sep 17 00:00:00 2001 From: Andrew Cheng Date: Fri, 15 Nov 2019 10:22:13 -0800 Subject: Delete call logs when calls are made without PBAP MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PbapClientConnectionHandler has code to remove calllogs when PBAP disconnects. However, if PBAP was never connected/enabled in the first place, and calls are made over HFP, these calllogs will not be removed when the device disconnects. This CL ensures calllogs are still removed in this case. Bug: 143723278 Test: (1) Verifying call logs are removed: Connect phone to carkit without enabling PBAP (e.g., don't allow permissions during pairing). Place a phone call over Bluetooth. Examine the calllog database directly: "sqlite3 calllog.db .dump” to verify call entry is present. Disconnect Bluetooth from the phone. Examine database again to verify entry is deleted. Repeat by disconnecting from the carkit, as well as disconnecting only HFP. (2) Verifying only HFP call logs are removed: Connect phone to carkit, with PBAP enabled this time. Place a phone call over HFP. Disable HFP profile, leaving PBAP enabled. Verify the HFP-entry is removed from calllog.db, while the PBAP-entries are still present. Change-Id: I305b323194f7732967db9a086d2c8a689798e64d Merged-In: I305b323194f7732967db9a086d2c8a689798e64d (cherry picked from commit f74a94dff84681645d2e519122b626bd5c4fe7fa) --- .../bluetooth/pbapclient/PbapClientService.java | 36 ++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/src/com/android/bluetooth/pbapclient/PbapClientService.java b/src/com/android/bluetooth/pbapclient/PbapClientService.java index f150cddec..02b1e7a51 100644 --- a/src/com/android/bluetooth/pbapclient/PbapClientService.java +++ b/src/com/android/bluetooth/pbapclient/PbapClientService.java @@ -19,9 +19,11 @@ package com.android.bluetooth.pbapclient; import android.accounts.Account; import android.accounts.AccountManager; import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothHeadsetClient; import android.bluetooth.BluetoothProfile; import android.bluetooth.IBluetoothPbapClient; import android.content.BroadcastReceiver; +import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; @@ -31,6 +33,7 @@ import android.util.Log; import com.android.bluetooth.R; import com.android.bluetooth.btservice.AdapterService; import com.android.bluetooth.btservice.ProfileService; +import com.android.bluetooth.hfpclient.connserv.HfpClientConnectionService; import com.android.bluetooth.sdp.SdpManager; import java.util.ArrayList; @@ -71,6 +74,9 @@ public class PbapClientService extends ProfileService { filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED); // delay initial download until after the user is unlocked to add an account. filter.addAction(Intent.ACTION_USER_UNLOCKED); + // To remove call logs when PBAP was never connected while calls were made, + // we also listen for HFP to become disconnected. + filter.addAction(BluetoothHeadsetClient.ACTION_CONNECTION_STATE_CHANGED); try { registerReceiver(mPbapBroadcastReceiver, filter); } catch (Exception e) { @@ -128,6 +134,21 @@ public class PbapClientService extends ProfileService { } } + private void removeHfpCallLog(String accountName, Context context) { + if (DBG) Log.d(TAG, "Removing call logs from " + accountName); + // Delete call logs belonging to accountName==BD_ADDR that also match + // component name "hfpclient". + ComponentName componentName = new ComponentName(context, HfpClientConnectionService.class); + String selectionFilter = CallLog.Calls.PHONE_ACCOUNT_ID + "=? AND " + + CallLog.Calls.PHONE_ACCOUNT_COMPONENT_NAME + "=?"; + String[] selectionArgs = new String[]{accountName, componentName.flattenToString()}; + try { + getContentResolver().delete(CallLog.Calls.CONTENT_URI, selectionFilter, selectionArgs); + } catch (IllegalArgumentException e) { + Log.w(TAG, "Call Logs could not be deleted, they may not exist yet."); + } + } + private void registerSdpRecord() { SdpManager sdpManager = SdpManager.getDefaultManager(); if (sdpManager == null) { @@ -171,6 +192,21 @@ public class PbapClientService extends ProfileService { for (PbapClientStateMachine stateMachine : mPbapClientStateMachineMap.values()) { stateMachine.resumeDownload(); } + } else if (action.equals(BluetoothHeadsetClient.ACTION_CONNECTION_STATE_CHANGED)) { + // PbapClientConnectionHandler has code to remove calllogs when PBAP disconnects. + // However, if PBAP was never connected/enabled in the first place, and calls are + // made over HFP, these calllogs will not be removed when the device disconnects. + // This code ensures callogs are still removed in this case. + BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); + int newState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1); + + if (newState == BluetoothProfile.STATE_DISCONNECTED) { + if (DBG) { + Log.d(TAG, "Received intent to disconnect HFP with " + device); + } + // HFP client stores entries in calllog.db by BD_ADDR and component name + removeHfpCallLog(device.getAddress(), context); + } } } } -- cgit v1.2.3 From 1b580b3a928781b087bfbaef4caa80041d038f9d Mon Sep 17 00:00:00 2001 From: Bill Yi Date: Mon, 16 Mar 2020 19:25:15 -0700 Subject: Import translations. DO NOT MERGE Auto-generated-cl: translation import Change-Id: I07dd19412b882deb8417dd99d3cee3fbc388327e --- res/values-ar/strings.xml | 8 ++++---- res/values-es/strings.xml | 2 +- res/values-km/strings.xml | 2 +- res/values-ky/strings.xml | 4 ++-- res/values-ml/strings.xml | 2 +- res/values-vi/strings.xml | 2 +- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/res/values-ar/strings.xml b/res/values-ar/strings.xml index 1525a71f5..a82501057 100644 --- a/res/values-ar/strings.xml +++ b/res/values-ar/strings.xml @@ -26,10 +26,10 @@ "وضع الطائرة" "لا يمكنك استخدام البلوتوث في وضع الطائرة." - "لإستخدام خدمات البلوتوث، يجب تشغيل البلوتوث أولاً." - "هل تريد تشغيل البلوتوث الآن؟" + "لإستخدام خدمات البلوتوث، يجب تفعيل البلوتوث أولاً." + "هل تريد تفعيل البلوتوث الآن؟" "إلغاء" - "تشغيل" + "تفعيل" "نقل الملف" "هل تقبل الملف الوارد؟" "رفض" @@ -77,7 +77,7 @@ "ليست هناك أي ملفات" "الملف غير موجود. \n" "يرجى الانتظار…" - "جارٍ تشغيل البلوتوث..." + "جارٍ تفعيل البلوتوث..." "سيتم استلام الملف. تحقق من التقدم في لوحة الإشعارات." "لا يمكن تلقي الملف." "تم إيقاف استلام الملف من \"%1$s\"" diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml index a9e448c41..7d371e0df 100644 --- a/res/values-es/strings.xml +++ b/res/values-es/strings.xml @@ -122,7 +122,7 @@ "Abrir" "Borrar de la lista" "Borrar" - "Está Sonando" + "Reproduciendo" "Guardar" "Cancelar" "Selecciona las cuentas que quieras compartir por Bluetooth. Tendrás que aceptar cualquier acceso a las cuentas al establecer conexión." diff --git a/res/values-km/strings.xml b/res/values-km/strings.xml index f4b7e7a5c..c9b6335fb 100644 --- a/res/values-km/strings.xml +++ b/res/values-km/strings.xml @@ -122,7 +122,7 @@ "បើក" "សម្អាត​ពី​បញ្ជី" "សម្អាត" - "Now Playing" + "ឥឡូវកំពុងចាក់" "រក្សាទុក" "បោះបង់" "ជ្រើសគណនីដែលអ្នកចង់ចែករំលែកតាមរយៈប៊្លូធូស។ អ្នកនៅតែត្រូវទទួលយកលទ្ធភាពចូលដំណើរការទាំងឡាយទៅកាន់គណនីនេះដដែល នៅពេលភ្ជាប់។" diff --git a/res/values-ky/strings.xml b/res/values-ky/strings.xml index 0ed4aa9bd..a0fa232fc 100644 --- a/res/values-ky/strings.xml +++ b/res/values-ky/strings.xml @@ -78,7 +78,7 @@ "Мындай файл жок. \n" "Күтө туруңуз…" "Bluetooth жандырылууда…" - "Файл алынат. Эскертмелер тактасынан жүрүшүн байкап турсаңыз болот." + "Файл алынат. Билдирмелер тактасынан жүрүшүн байкап турсаңыз болот." "Файлды алуу мүмкүн эмес." "\"%1$s\" жөнөткөн файлды алуу токтотулду" "Кийинкиге файл жөнөтүлүүдө: \"%1$s\"" @@ -125,7 +125,7 @@ "Азыр эмне ойноп жатат?" "Сактоо" "Жокко чыгаруу" - "Bluetooth аркылуу бөлүшө турган каттоо эсептерин тандаңыз. Туташкан сайын каттоо эсептерине кирүү мүмкүнчүлүгүн ырастап турушуңуз керек." + "Bluetooth аркылуу бөлүшө турган каттоо эсептерин тандаңыз. Туташкан сайын аккаунттарына кирүү мүмкүнчүлүгүн ырастап турушуңуз керек." "Калган көзөнөктөр:" "Колдонмонун сүрөтчөсү" "Bluetooth билдирүү бөлүшүү жөндөөлөрү" diff --git a/res/values-ml/strings.xml b/res/values-ml/strings.xml index 08e907f4f..5198b8762 100644 --- a/res/values-ml/strings.xml +++ b/res/values-ml/strings.xml @@ -20,7 +20,7 @@ "BluetoothShare മാനേജർ ആക്‌സസ്സുചെയ്യാനും ഫയലുകൾ കൈമാറാൻ അത് ഉപയോഗിക്കാനും അപ്ലിക്കേഷനെ അനുവദിക്കുന്നു." "വൈറ്റ്‌ലിസ്റ്റ് ബ്ലൂടൂത്ത് ഉപകരണ ആക്‌സസ്സ്." "ഒരു ബ്ലൂടൂത്ത് ഉപകരണം താൽക്കാലികമായി വൈറ്റ്‌ലിസ്റ്റുചെയ്യാൻ അപ്ലിക്കേഷനെ അനുവദിക്കുന്നു, അത് ഉപയോക്താവിന്റെ സ്ഥിരീകരണമില്ലാതെ ഈ ഉപകരണത്തിലേക്ക് ഫയലുകൾ അയയ്‌ക്കാൻ ആ ഉപകരണത്തെ അനുവദിക്കുന്നു." - "ബ്ലൂടൂത്ത്" + "Bluetooth" "അജ്ഞാത ഉപകരണം" "അറിയില്ല" "ഫ്ലൈറ്റ് മോഡ്" diff --git a/res/values-vi/strings.xml b/res/values-vi/strings.xml index 4a40dfe12..c60c8dec7 100644 --- a/res/values-vi/strings.xml +++ b/res/values-vi/strings.xml @@ -128,7 +128,7 @@ "Chọn tài khoản mà bạn muốn chia sẻ qua Bluetooth. Bạn vẫn phải chấp nhận mọi quyền truy cập vào tài khoản khi kết nối." "Số khe cắm còn lại:" "Biểu tượng ứng dụng" - "Cài đặt chia sẻ thư qua Bluetooth" + "Cài đặt cách chia sẻ thư qua Bluetooth" "Không chọn được tài khoản. Còn lại 0 khe cắm" "Đã kết nối âm thanh Bluetooth" "Đã ngắt kết nối âm thanh Bluetooth" -- cgit v1.2.3 From e60a883032572c6cebd1676aa9c45be5f72799ff Mon Sep 17 00:00:00 2001 From: Joseph Pirozzo Date: Thu, 19 Mar 2020 19:18:18 +0000 Subject: Revert "PBAP server, send favorite contacts" This reverts commit da1dc71cffb5fbb1e55186a20410e3d4f95246c3. Reason for revert: Not intended for this build. Change-Id: I3d8f51080e377d5dc7b67395a4979b8e5937a181 --- .../bluetooth/pbap/BluetoothPbapObexServer.java | 65 +++++----------------- .../bluetooth/pbap/BluetoothPbapService.java | 3 +- .../bluetooth/pbap/BluetoothPbapVcardManager.java | 37 +++--------- 3 files changed, 23 insertions(+), 82 deletions(-) diff --git a/src/com/android/bluetooth/pbap/BluetoothPbapObexServer.java b/src/com/android/bluetooth/pbap/BluetoothPbapObexServer.java index 34ee96270..979becdca 100755 --- a/src/com/android/bluetooth/pbap/BluetoothPbapObexServer.java +++ b/src/com/android/bluetooth/pbap/BluetoothPbapObexServer.java @@ -97,7 +97,6 @@ public class BluetoothPbapObexServer extends ServerRequestHandler { private static final String[] LEGAL_PATH = { "/telecom", "/telecom/pb", - "/telecom/fav", "/telecom/ich", "/telecom/och", "/telecom/mch", @@ -107,7 +106,6 @@ public class BluetoothPbapObexServer extends ServerRequestHandler { @SuppressWarnings("unused") private static final String[] LEGAL_PATH_WITH_SIM = { "/telecom", "/telecom/pb", - "/telecom/fav", "/telecom/ich", "/telecom/och", "/telecom/mch", @@ -140,9 +138,6 @@ public class BluetoothPbapObexServer extends ServerRequestHandler { // phone book private static final String PB = "pb"; - // favorites - private static final String FAV = "fav"; - private static final String TELECOM_PATH = "/telecom"; private static final String ICH_PATH = "/telecom/ich"; @@ -155,8 +150,6 @@ public class BluetoothPbapObexServer extends ServerRequestHandler { private static final String PB_PATH = "/telecom/pb"; - private static final String FAV_PATH = "/telecom/fav"; - // type for list vcard objects private static final String TYPE_LISTING = "x-bt/vcard-listing"; @@ -219,8 +212,6 @@ public class BluetoothPbapObexServer extends ServerRequestHandler { public static final int MISSED_CALL_HISTORY = 4; public static final int COMBINED_CALL_HISTORY = 5; - - public static final int FAVORITES = 6; } public BluetoothPbapObexServer(Handler callback, Context context, @@ -450,8 +441,6 @@ public class BluetoothPbapObexServer extends ServerRequestHandler { if (mCurrentPath.equals(PB_PATH)) { appParamValue.needTag = ContentType.PHONEBOOK; - } else if (mCurrentPath.equals(FAV_PATH)) { - appParamValue.needTag = ContentType.FAVORITES; } else if (mCurrentPath.equals(ICH_PATH)) { appParamValue.needTag = ContentType.INCOMING_CALL_HISTORY; } else if (mCurrentPath.equals(OCH_PATH)) { @@ -489,11 +478,6 @@ public class BluetoothPbapObexServer extends ServerRequestHandler { if (D) { Log.v(TAG, "download phonebook request"); } - } else if (isNameMatchTarget(name, FAV)) { - appParamValue.needTag = ContentType.FAVORITES; - if (D) { - Log.v(TAG, "download favorites request"); - } } else if (isNameMatchTarget(name, ICH)) { appParamValue.needTag = ContentType.INCOMING_CALL_HISTORY; appParamValue.callHistoryVersionCounter = @@ -767,8 +751,7 @@ public class BluetoothPbapObexServer extends ServerRequestHandler { result.append(""); // Phonebook listing request - if ((appParamValue.needTag == ContentType.PHONEBOOK) - || (appParamValue.needTag == ContentType.FAVORITES)) { + if (appParamValue.needTag == ContentType.PHONEBOOK) { String type = ""; if (appParamValue.searchAttr.equals("0")) { type = "name"; @@ -965,7 +948,7 @@ public class BluetoothPbapObexServer extends ServerRequestHandler { checkPbapFeatureSupport(mFolderVersionCounterbitMask); } boolean needSendPhonebookVersionCounters = false; - if (isNameMatchTarget(name, PB) || isNameMatchTarget(name, FAV)) { + if (isNameMatchTarget(name, PB)) { needSendPhonebookVersionCounters = checkPbapFeatureSupport(mFolderVersionCounterbitMask); } @@ -1209,12 +1192,11 @@ public class BluetoothPbapObexServer extends ServerRequestHandler { if (appParamValue.needTag == 0) { Log.w(TAG, "wrong path!"); return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE; - } else if ((appParamValue.needTag == ContentType.PHONEBOOK) - || (appParamValue.needTag == ContentType.FAVORITES)) { + } else if (appParamValue.needTag == ContentType.PHONEBOOK) { if (intIndex < 0 || intIndex >= size) { Log.w(TAG, "The requested vcard is not acceptable! name= " + name); return ResponseCodes.OBEX_HTTP_NOT_FOUND; - } else if ((intIndex == 0) && (appParamValue.needTag == ContentType.PHONEBOOK)) { + } else if (intIndex == 0) { // For PB_PATH, 0.vcf is the phone number of this phone. String ownerVcard = mVcardManager.getOwnerPhoneNumberVcard(vcard21, appParamValue.ignorefilter ? null : appParamValue.propertySelector); @@ -1270,49 +1252,30 @@ public class BluetoothPbapObexServer extends ServerRequestHandler { int requestSize = pbSize >= appParamValue.maxListCount ? appParamValue.maxListCount : pbSize; - /** - * startIndex (resp., lastIndex) corresponds to the index of the first (resp., last) - * vcard entry in the phonebook object. - * PBAP v1.2.3: only pb starts indexing at 0.vcf (owner card), the other phonebook - * objects (e.g., fav) start at 1.vcf. Additionally, the owner card is included in - * pb's pbSize. This means pbSize corresponds to the index of the last vcf in the fav - * phonebook object, but does not for the pb phonebook object. - */ - int startIndex = 1; - int lastIndex = pbSize; - if (appParamValue.needTag == BluetoothPbapObexServer.ContentType.PHONEBOOK) { - startIndex = 0; - lastIndex = pbSize - 1; - } - // [startPoint, endPoint] denote the range of vcf indices to send, inclusive. - int startPoint = startIndex + appParamValue.listStartOffset; - int endPoint = startPoint + requestSize - 1; - if (appParamValue.listStartOffset < 0 || startPoint > lastIndex) { + int startPoint = appParamValue.listStartOffset; + if (startPoint < 0 || startPoint >= pbSize) { Log.w(TAG, "listStartOffset is not correct! " + startPoint); return ResponseCodes.OBEX_HTTP_OK; } - if (endPoint > lastIndex) { - endPoint = lastIndex; - } // Limit the number of call log to CALLLOG_NUM_LIMIT - if ((appParamValue.needTag != BluetoothPbapObexServer.ContentType.PHONEBOOK) - && (appParamValue.needTag != BluetoothPbapObexServer.ContentType.FAVORITES)) { + if (appParamValue.needTag != BluetoothPbapObexServer.ContentType.PHONEBOOK) { if (requestSize > CALLLOG_NUM_LIMIT) { requestSize = CALLLOG_NUM_LIMIT; } } + int endPoint = startPoint + requestSize - 1; + if (endPoint > pbSize - 1) { + endPoint = pbSize - 1; + } if (D) { Log.d(TAG, "pullPhonebook(): requestSize=" + requestSize + " startPoint=" + startPoint + " endPoint=" + endPoint); } boolean vcard21 = appParamValue.vcard21; - boolean favorites = - (appParamValue.needTag == BluetoothPbapObexServer.ContentType.FAVORITES); - if ((appParamValue.needTag == BluetoothPbapObexServer.ContentType.PHONEBOOK) - || favorites) { + if (appParamValue.needTag == BluetoothPbapObexServer.ContentType.PHONEBOOK) { if (startPoint == 0) { String ownerVcard = mVcardManager.getOwnerPhoneNumberVcard(vcard21, appParamValue.ignorefilter ? null : appParamValue.propertySelector); @@ -1322,13 +1285,13 @@ public class BluetoothPbapObexServer extends ServerRequestHandler { return mVcardManager.composeAndSendPhonebookVcards(op, 1, endPoint, vcard21, ownerVcard, needSendBody, pbSize, appParamValue.ignorefilter, appParamValue.propertySelector, appParamValue.vCardSelector, - appParamValue.vCardSelectorOperator, mVcardSelector, favorites); + appParamValue.vCardSelectorOperator, mVcardSelector); } } else { return mVcardManager.composeAndSendPhonebookVcards(op, startPoint, endPoint, vcard21, null, needSendBody, pbSize, appParamValue.ignorefilter, appParamValue.propertySelector, appParamValue.vCardSelector, - appParamValue.vCardSelectorOperator, mVcardSelector, favorites); + appParamValue.vCardSelectorOperator, mVcardSelector); } } else { return mVcardManager.composeAndSendSelectedCallLogVcards(appParamValue.needTag, op, diff --git a/src/com/android/bluetooth/pbap/BluetoothPbapService.java b/src/com/android/bluetooth/pbap/BluetoothPbapService.java index 280186f8f..e7dba2a96 100644 --- a/src/com/android/bluetooth/pbap/BluetoothPbapService.java +++ b/src/com/android/bluetooth/pbap/BluetoothPbapService.java @@ -140,8 +140,7 @@ public class BluetoothPbapService extends ProfileService implements IObexConnect private ObexServerSockets mServerSockets = null; private static final int SDP_PBAP_SERVER_VERSION = 0x0102; - // PBAP v1.2.3, Sec. 7.1.2: local phonebook and favorites - private static final int SDP_PBAP_SUPPORTED_REPOSITORIES = 0x0009; + private static final int SDP_PBAP_SUPPORTED_REPOSITORIES = 0x0001; private static final int SDP_PBAP_SUPPORTED_FEATURES = 0x021F; /* PBAP will use Bluetooth notification ID from 1000000 (included) to 2000000 (excluded). diff --git a/src/com/android/bluetooth/pbap/BluetoothPbapVcardManager.java b/src/com/android/bluetooth/pbap/BluetoothPbapVcardManager.java index 8801c16fb..5ba2b4b8b 100755 --- a/src/com/android/bluetooth/pbap/BluetoothPbapVcardManager.java +++ b/src/com/android/bluetooth/pbap/BluetoothPbapVcardManager.java @@ -153,8 +153,7 @@ public class BluetoothPbapVcardManager { int size; switch (type) { case BluetoothPbapObexServer.ContentType.PHONEBOOK: - case BluetoothPbapObexServer.ContentType.FAVORITES: - size = getContactsSize(type); + size = getContactsSize(); break; default: size = getCallHistorySize(type); @@ -166,30 +165,16 @@ public class BluetoothPbapVcardManager { return size; } - /** - * Returns the number of contacts (i.e., vcf) in a phonebook object. - * @param type specifies which phonebook object, e.g., pb, fav - * @return - */ - public final int getContactsSize(final int type) { + public final int getContactsSize() { final Uri myUri = DevicePolicyUtils.getEnterprisePhoneUri(mContext); Cursor contactCursor = null; - String selectionClause = null; - if (type == BluetoothPbapObexServer.ContentType.FAVORITES) { - selectionClause = Phone.STARRED + " = 1"; - } try { - contactCursor = mResolver.query(myUri, - new String[]{Phone.CONTACT_ID}, selectionClause, - null, Phone.CONTACT_ID); + contactCursor = mResolver.query(myUri, new String[]{Phone.CONTACT_ID}, null, null, + Phone.CONTACT_ID); if (contactCursor == null) { return 0; } - int contactsSize = getDistinctContactIdSize(contactCursor); - if (type == BluetoothPbapObexServer.ContentType.PHONEBOOK) { - contactsSize += 1; // pb has the 0.vcf owner's card - } - return contactsSize; + return getDistinctContactIdSize(contactCursor) + 1; // always has the 0.vcf } catch (CursorWindowAllocationException e) { Log.e(TAG, "CursorWindowAllocationException while getting Contacts size"); } finally { @@ -566,7 +551,7 @@ public class BluetoothPbapVcardManager { final int composeAndSendPhonebookVcards(Operation op, final int startPoint, final int endPoint, final boolean vcardType21, String ownerVCard, int needSendBody, int pbSize, boolean ignorefilter, byte[] filter, byte[] vcardselector, String vcardselectorop, - boolean vcardselect, boolean favorites) { + boolean vcardselect) { if (startPoint < 1 || startPoint > endPoint) { Log.e(TAG, "internal error: startPoint or endPoint is not correct."); return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; @@ -577,15 +562,9 @@ public class BluetoothPbapVcardManager { Cursor contactIdCursor = new MatrixCursor(new String[]{ Phone.CONTACT_ID }); - - String selectionClause = null; - if (favorites) { - selectionClause = Phone.STARRED + " = 1"; - } - try { - contactCursor = mResolver.query(myUri, PHONES_CONTACTS_PROJECTION, selectionClause, - null, Phone.CONTACT_ID); + contactCursor = mResolver.query(myUri, PHONES_CONTACTS_PROJECTION, null, null, + Phone.CONTACT_ID); if (contactCursor != null) { contactIdCursor = ContactCursorFilter.filterByRange(contactCursor, startPoint, endPoint); -- cgit v1.2.3 From a7d25af6f9dab4de4a283da8ebb63e88782dfce3 Mon Sep 17 00:00:00 2001 From: Bill Yi Date: Thu, 2 Apr 2020 19:59:29 -0700 Subject: Import translations. DO NOT MERGE Auto-generated-cl: translation import Change-Id: I0148d54752958a93d6c9ac521ee4108fea4d1cf1 --- res/values-af/strings.xml | 1 + res/values-am/strings.xml | 1 + res/values-ar/strings.xml | 1 + res/values-as/strings.xml | 1 + res/values-az/strings.xml | 1 + res/values-b+sr+Latn/strings.xml | 1 + res/values-be/strings.xml | 1 + res/values-bg/strings.xml | 1 + res/values-bn/strings.xml | 1 + res/values-bs/strings.xml | 1 + res/values-ca/strings.xml | 1 + res/values-cs/strings.xml | 1 + res/values-da/strings.xml | 1 + res/values-de/strings.xml | 1 + res/values-el/strings.xml | 1 + res/values-en-rAU/strings.xml | 1 + res/values-en-rCA/strings.xml | 1 + res/values-en-rGB/strings.xml | 1 + res/values-en-rIN/strings.xml | 1 + res/values-en-rXC/strings.xml | 1 + res/values-es-rUS/strings.xml | 1 + res/values-es/strings.xml | 1 + res/values-et/strings.xml | 1 + res/values-eu/strings.xml | 1 + res/values-fa/strings.xml | 1 + res/values-fi/strings.xml | 1 + res/values-fr-rCA/strings.xml | 1 + res/values-fr/strings.xml | 1 + res/values-gl/strings.xml | 1 + res/values-gu/strings.xml | 1 + res/values-hi/strings.xml | 1 + res/values-hr/strings.xml | 1 + res/values-hu/strings.xml | 1 + res/values-hy/strings.xml | 1 + res/values-in/strings.xml | 1 + res/values-is/strings.xml | 1 + res/values-it/strings.xml | 1 + res/values-iw/strings.xml | 1 + res/values-ja/strings.xml | 1 + res/values-ka/strings.xml | 1 + res/values-kk/strings.xml | 1 + res/values-kk/strings_pbap.xml | 4 +--- res/values-km/strings.xml | 1 + res/values-kn/strings.xml | 1 + res/values-ko/strings.xml | 1 + res/values-ky/strings.xml | 1 + res/values-lo/strings.xml | 1 + res/values-lt/strings.xml | 1 + res/values-lv/strings.xml | 1 + res/values-mk/strings.xml | 1 + res/values-ml/strings.xml | 1 + res/values-mn/strings.xml | 1 + res/values-mr/strings.xml | 1 + res/values-ms/strings.xml | 1 + res/values-my/strings.xml | 1 + res/values-nb/strings.xml | 1 + res/values-ne/strings.xml | 1 + res/values-nl/strings.xml | 1 + res/values-or/strings.xml | 1 + res/values-pa/strings.xml | 1 + res/values-pl/strings.xml | 1 + res/values-pt-rPT/strings.xml | 1 + res/values-pt/strings.xml | 1 + res/values-ro/strings.xml | 1 + res/values-ru/strings.xml | 1 + res/values-si/strings.xml | 1 + res/values-sk/strings.xml | 1 + res/values-sl/strings.xml | 1 + res/values-sq/strings.xml | 1 + res/values-sr/strings.xml | 1 + res/values-sv/strings.xml | 1 + res/values-sw/strings.xml | 1 + res/values-ta/strings.xml | 1 + res/values-te/strings.xml | 1 + res/values-th/strings.xml | 1 + res/values-tl/strings.xml | 1 + res/values-tr/strings.xml | 1 + res/values-uk/strings.xml | 1 + res/values-ur/strings.xml | 1 + res/values-uz/strings.xml | 1 + res/values-vi/strings.xml | 1 + res/values-zh-rCN/strings.xml | 1 + res/values-zh-rHK/strings.xml | 1 + res/values-zh-rTW/strings.xml | 1 + res/values-zu/strings.xml | 1 + 85 files changed, 85 insertions(+), 3 deletions(-) diff --git a/res/values-af/strings.xml b/res/values-af/strings.xml index 060fd9013..f011684cf 100644 --- a/res/values-af/strings.xml +++ b/res/values-af/strings.xml @@ -134,4 +134,5 @@ "Bluetooth-oudio is ontkoppel" "Bluetooth-oudio" "Lêers groter as 4 GB kan nie oorgedra word nie" + "Koppel aan Bluetooth" diff --git a/res/values-am/strings.xml b/res/values-am/strings.xml index dbf4aadd6..0d2f87cc5 100644 --- a/res/values-am/strings.xml +++ b/res/values-am/strings.xml @@ -134,4 +134,5 @@ "የብሉቱዝ ኦዲዮ ግንኙነት ተቋርጧል" "የብሉቱዝ ኦዲዮ" "ከ4 ጊባ በላይ የሆኑ ፋይሎች ሊዛወሩ አይችሉም" + "ከብሉቱዝ ጋር ተገናኝ" diff --git a/res/values-ar/strings.xml b/res/values-ar/strings.xml index a82501057..2d4e70d29 100644 --- a/res/values-ar/strings.xml +++ b/res/values-ar/strings.xml @@ -142,4 +142,5 @@ "انقطع الاتصال بالبث الصوتي عبر البلوتوث." "بث صوتي عبر البلوتوث" "يتعذّر نقل الملفات التي يزيد حجمها عن 4 غيغابايت" + "الاتصال ببلوتوث" diff --git a/res/values-as/strings.xml b/res/values-as/strings.xml index fd6ab371a..a5f682ce6 100644 --- a/res/values-as/strings.xml +++ b/res/values-as/strings.xml @@ -134,4 +134,5 @@ "ব্লুটুথ অডিঅ\'ৰ সৈতে সংযোগ বিচ্ছিন্ন কৰা হ’ল" "ব্লুটুথ অডিঅ\'" "৪ জি. বি. তকৈ ডাঙৰ ফাইল স্থানান্তৰ কৰিব নোৱাৰি" + "ব্লুটুথৰ সৈতে সংযোগ কৰক" diff --git a/res/values-az/strings.xml b/res/values-az/strings.xml index 4abf91f83..ea55d1771 100644 --- a/res/values-az/strings.xml +++ b/res/values-az/strings.xml @@ -134,4 +134,5 @@ "Bluetooth audio ilə bağlantı kəsildi" "Bluetooth audio" "4GB-dən böyük olan faylları köçürmək mümkün deyil" + "Bluetooth\'a qoşulun" diff --git a/res/values-b+sr+Latn/strings.xml b/res/values-b+sr+Latn/strings.xml index 4d999f36a..9ad26e1e8 100644 --- a/res/values-b+sr+Latn/strings.xml +++ b/res/values-b+sr+Latn/strings.xml @@ -136,4 +136,5 @@ "Veza sa Bluetooth audijom je prekinuta" "Bluetooth audio" "Ne mogu da se prenose datoteke veće od 4 GB" + "Poveži sa Bluetooth-om" diff --git a/res/values-be/strings.xml b/res/values-be/strings.xml index 0026ea6d8..3c9dbd6cd 100644 --- a/res/values-be/strings.xml +++ b/res/values-be/strings.xml @@ -138,4 +138,5 @@ "Bluetooth-аўдыя адключана" "Bluetooth-аўдыя" "Немагчыма перадаць файлы, большыя за 4 ГБ" + "Падключыцца да Bluetooth" diff --git a/res/values-bg/strings.xml b/res/values-bg/strings.xml index 1566a37dc..4b57342d2 100644 --- a/res/values-bg/strings.xml +++ b/res/values-bg/strings.xml @@ -134,4 +134,5 @@ "Аудиовръзката през Bluetooth е прекратена" "Аудио през Bluetooth" "Файловете с размер над 4 ГБ не могат да бъдат прехвърлени" + "Свързване с Bluetooth" diff --git a/res/values-bn/strings.xml b/res/values-bn/strings.xml index 352f359c0..be016492a 100644 --- a/res/values-bn/strings.xml +++ b/res/values-bn/strings.xml @@ -134,4 +134,5 @@ "ব্লুটুথ অডিওর সংযোগ বিচ্ছিন্ন হয়েছে" "ব্লুটুথ অডিও" "৪GB থেকে বড় ফটো ট্রান্সফার করা যাবে না" + "ব্লুটুথের সাথে কানেক্ট করুন" diff --git a/res/values-bs/strings.xml b/res/values-bs/strings.xml index 7f6eca1e4..3c7dec44d 100644 --- a/res/values-bs/strings.xml +++ b/res/values-bs/strings.xml @@ -136,4 +136,5 @@ "Bluetooth audio je isključen" "Bluetooth Audio" "Nije moguće prenijeti fajlove veće od 4 GB" + "Poveži se na Bluetooth" diff --git a/res/values-ca/strings.xml b/res/values-ca/strings.xml index e24b808d3..bddf09672 100644 --- a/res/values-ca/strings.xml +++ b/res/values-ca/strings.xml @@ -134,4 +134,5 @@ "Àudio per Bluetooth desconnectat" "Àudio per Bluetooth" "No es poden transferir fitxers més grans de 4 GB" + "Connecta el Bluetooth" diff --git a/res/values-cs/strings.xml b/res/values-cs/strings.xml index 3e76d2bb4..9adb80c78 100644 --- a/res/values-cs/strings.xml +++ b/res/values-cs/strings.xml @@ -138,4 +138,5 @@ "Bluetooth Audio – odpojeno" "Bluetooth Audio" "Soubory větší než 4 GB nelze přenést" + "Připojit k Bluetooth" diff --git a/res/values-da/strings.xml b/res/values-da/strings.xml index 25819a183..25cf3d7be 100644 --- a/res/values-da/strings.xml +++ b/res/values-da/strings.xml @@ -134,4 +134,5 @@ "Bluetooth-lyden blev afbrudt" "Bluetooth-lyd" "File, der er større end 4 GB, kan ikke overføres" + "Opret forbindelse til Bluetooth" diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml index c9e655490..4ffe47022 100644 --- a/res/values-de/strings.xml +++ b/res/values-de/strings.xml @@ -134,4 +134,5 @@ "Bluetooth-Audio-Verbindung aufgehoben" "Bluetooth-Audio" "Dateien mit mehr als 4 GB können nicht übertragen werden" + "Mit Bluetooth verbinden" diff --git a/res/values-el/strings.xml b/res/values-el/strings.xml index 1506bd2e7..e249a3889 100644 --- a/res/values-el/strings.xml +++ b/res/values-el/strings.xml @@ -134,4 +134,5 @@ "Ο ήχος Bluetooth αποσυνδέθηκε" "Ήχος Bluetooth" "Δεν είναι δυνατή η μεταφορά αρχείων που ξεπερνούν τα 4 GB" + "Σύνδεση σε Bluetooth" diff --git a/res/values-en-rAU/strings.xml b/res/values-en-rAU/strings.xml index ddca4979a..8dcbab466 100644 --- a/res/values-en-rAU/strings.xml +++ b/res/values-en-rAU/strings.xml @@ -134,4 +134,5 @@ "Bluetooth audio disconnected" "Bluetooth Audio" "Files bigger than 4 GB cannot be transferred" + "Connect to Bluetooth" diff --git a/res/values-en-rCA/strings.xml b/res/values-en-rCA/strings.xml index e549cb6d4..07202ef21 100644 --- a/res/values-en-rCA/strings.xml +++ b/res/values-en-rCA/strings.xml @@ -134,4 +134,5 @@ "Bluetooth audio disconnected" "Bluetooth Audio" "Files bigger than 4 GB cannot be transferred" + "Connect to Bluetooth" diff --git a/res/values-en-rGB/strings.xml b/res/values-en-rGB/strings.xml index ddca4979a..8dcbab466 100644 --- a/res/values-en-rGB/strings.xml +++ b/res/values-en-rGB/strings.xml @@ -134,4 +134,5 @@ "Bluetooth audio disconnected" "Bluetooth Audio" "Files bigger than 4 GB cannot be transferred" + "Connect to Bluetooth" diff --git a/res/values-en-rIN/strings.xml b/res/values-en-rIN/strings.xml index ddca4979a..8dcbab466 100644 --- a/res/values-en-rIN/strings.xml +++ b/res/values-en-rIN/strings.xml @@ -134,4 +134,5 @@ "Bluetooth audio disconnected" "Bluetooth Audio" "Files bigger than 4 GB cannot be transferred" + "Connect to Bluetooth" diff --git a/res/values-en-rXC/strings.xml b/res/values-en-rXC/strings.xml index 02178d43d..8bbc66c7c 100644 --- a/res/values-en-rXC/strings.xml +++ b/res/values-en-rXC/strings.xml @@ -134,4 +134,5 @@ "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‎‏‎‏‏‏‎‎‎‎‎‏‏‎‎‏‏‏‏‏‎‏‏‏‎‏‎‏‎‎‏‎‎‎‎‏‏‏‎‏‏‎‎‏‎‏‏‏‎‎‎‏‏‎‏‏‏‎‏‏‎‎‏‎Bluetooth audio disconnected‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‏‎‏‎‎‏‎‎‎‎‎‎‎‎‎‎‎‏‏‎‎‏‏‏‏‎‎‏‏‎‏‏‏‎‎‎‎‏‎‏‎‎‏‎‏‏‎‏‎‎‎‏‏‎‎‏‎‎‏‏‏‎‎Bluetooth Audio‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‏‏‏‎‏‏‎‏‏‎‏‏‏‏‎‏‏‎‏‏‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‎‏‎‎‏‎‎‎‎‎‎‏‎‎‎‏‎‏‎‏‏‎‎‏‏‏‎Files bigger than 4GB cannot be transferred‎‏‎‎‏‎" + "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‎‏‏‎‏‏‏‏‎‏‎‎‏‎‏‏‏‎‏‎‏‏‏‏‏‏‎‎‏‏‏‏‎‏‎‏‏‏‏‎‏‎‎‏‎‏‏‎‏‎‎‎‎‏‏‎‎‎‎‎‎‏‎‎Connect to Bluetooth‎‏‎‎‏‎" diff --git a/res/values-es-rUS/strings.xml b/res/values-es-rUS/strings.xml index 4a4eaaaa3..eb95310b4 100644 --- a/res/values-es-rUS/strings.xml +++ b/res/values-es-rUS/strings.xml @@ -134,4 +134,5 @@ "Audio Bluetooth desconectado" "Audio Bluetooth" "No se pueden transferir los archivos de más de 4 GB" + "Conectarse a Bluetooth" diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml index 7d371e0df..a67ba6b4f 100644 --- a/res/values-es/strings.xml +++ b/res/values-es/strings.xml @@ -134,4 +134,5 @@ "Audio por Bluetooth desconectado" "Audio por Bluetooth" "No se pueden transferir archivos de más de 4 GB" + "Conectarse a un dispositivo Bluetooth" diff --git a/res/values-et/strings.xml b/res/values-et/strings.xml index dbc425adb..334174fbc 100644 --- a/res/values-et/strings.xml +++ b/res/values-et/strings.xml @@ -134,4 +134,5 @@ "Bluetoothi heli ühendus on katkestatud" "Bluetoothi heli" "Faile, mis on üle 4 GB, ei saa üle kanda" + "Ühenda Bluetoothiga" diff --git a/res/values-eu/strings.xml b/res/values-eu/strings.xml index cc6e417f0..72e609650 100644 --- a/res/values-eu/strings.xml +++ b/res/values-eu/strings.xml @@ -134,4 +134,5 @@ "Deskonektatu da Bluetooth bidezko audioa" "Bluetooth bidezko audioa" "Ezin dira transferitu 4 GB baino gehiagoko fitxategiak" + "Konektatu Bluetooth-era" diff --git a/res/values-fa/strings.xml b/res/values-fa/strings.xml index c95558eac..e2c1b75c9 100644 --- a/res/values-fa/strings.xml +++ b/res/values-fa/strings.xml @@ -134,4 +134,5 @@ "ارتباط بلوتوث صوتی قطع شد" "بلوتوث‌ صوتی" "فایل‌های بزرگ‌تر از ۴ گیگابایت نمی‌توانند منتقل شوند" + "اتصال به بلوتوث" diff --git a/res/values-fi/strings.xml b/res/values-fi/strings.xml index 0fbc55c5a..f1792c634 100644 --- a/res/values-fi/strings.xml +++ b/res/values-fi/strings.xml @@ -134,4 +134,5 @@ "Bluetooth-ääni katkaistu" "Bluetooth-ääni" "Yli 4 Gt:n kokoisia tiedostoja ei voi siirtää." + "Muodosta Bluetooth-yhteys" diff --git a/res/values-fr-rCA/strings.xml b/res/values-fr-rCA/strings.xml index b881cfcbd..4a62f98eb 100644 --- a/res/values-fr-rCA/strings.xml +++ b/res/values-fr-rCA/strings.xml @@ -134,4 +134,5 @@ "Audio Bluetooth déconnecté" "Audio Bluetooth" "Les fichiers dépassant 4 Go ne peuvent pas être transférés" + "Connexion au Bluetooth" diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml index 06fe2093c..e0caaee4f 100644 --- a/res/values-fr/strings.xml +++ b/res/values-fr/strings.xml @@ -134,4 +134,5 @@ "Audio Bluetooth déconnecté" "Audio Bluetooth" "Impossible de transférer les fichiers supérieurs à 4 Go" + "Se connecter au Bluetooth" diff --git a/res/values-gl/strings.xml b/res/values-gl/strings.xml index 9dedefde2..1f2b1d0ca 100644 --- a/res/values-gl/strings.xml +++ b/res/values-gl/strings.xml @@ -134,4 +134,5 @@ "Desconectouse o audio por Bluetooth" "Audio por Bluetooth" "Non se poden transferir ficheiros de máis de 4 GB" + "Conectar ao Bluetooth" diff --git a/res/values-gu/strings.xml b/res/values-gu/strings.xml index d6e10005a..4195292df 100644 --- a/res/values-gu/strings.xml +++ b/res/values-gu/strings.xml @@ -134,4 +134,5 @@ "બ્લૂટૂથ ઑડિઓ ડિસ્કનેક્ટ થયું" "બ્લૂટૂથ ઑડિઓ" "4GB કરતા મોટી ફાઇલ ટ્રાન્સફર કરી શકાતી નથી" + "બ્લૂટૂથ સાથે કનેક્ટ કરો" diff --git a/res/values-hi/strings.xml b/res/values-hi/strings.xml index d01d1377a..cb0bb5f7d 100644 --- a/res/values-hi/strings.xml +++ b/res/values-hi/strings.xml @@ -134,4 +134,5 @@ "ब्लूटूथ ऑडियो डिसकनेक्ट किया गया" "ब्लूटूथ ऑडियो" "4 जीबी से बड़ी फ़ाइलें ट्रांसफ़र नहीं की जा सकतीं" + "ब्लूटूथ से कनेक्ट करें" diff --git a/res/values-hr/strings.xml b/res/values-hr/strings.xml index d05c7b338..581e1a875 100644 --- a/res/values-hr/strings.xml +++ b/res/values-hr/strings.xml @@ -136,4 +136,5 @@ "Veza Bluetooth Audija prekinuta" "Bluetooth Audio" "Datoteke veće od 4 GB ne mogu se prenijeti" + "Povezivanje s Bluetoothom" diff --git a/res/values-hu/strings.xml b/res/values-hu/strings.xml index 4fa442f44..ff93e444d 100644 --- a/res/values-hu/strings.xml +++ b/res/values-hu/strings.xml @@ -134,4 +134,5 @@ "Bluetooth audió leválasztva" "Bluetooth audió" "A 4 GB-nál nagyobb fájlokat nem lehet átvinni" + "Csatlakozás Bluetooth-eszközhöz" diff --git a/res/values-hy/strings.xml b/res/values-hy/strings.xml index bf6fbf3b8..1fb6d74fb 100644 --- a/res/values-hy/strings.xml +++ b/res/values-hy/strings.xml @@ -134,4 +134,5 @@ "Bluetooth աուդիոն անջատված է" "Bluetooth աուդիո" "4 ԳԲ-ից մեծ ֆայլերը հնարավոր չէ փոխանցել" + "Միանալ Bluetooth-ին" diff --git a/res/values-in/strings.xml b/res/values-in/strings.xml index 15722dab8..01c4fe10d 100644 --- a/res/values-in/strings.xml +++ b/res/values-in/strings.xml @@ -134,4 +134,5 @@ "Bluetooth audio terputus" "Bluetooth Audio" "File yang berukuran lebih dari 4GB tidak dapat ditransfer" + "Hubungkan ke Bluetooth" diff --git a/res/values-is/strings.xml b/res/values-is/strings.xml index 624638903..65db6e918 100644 --- a/res/values-is/strings.xml +++ b/res/values-is/strings.xml @@ -134,4 +134,5 @@ "Bluetooth-hljóð aftengt" "Bluetooth-hljóð" "Ekki er hægt að flytja skrár sem eru stærri en 4 GB" + "Tengjast við Bluetooth" diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml index e4a486f33..27cefda39 100644 --- a/res/values-it/strings.xml +++ b/res/values-it/strings.xml @@ -134,4 +134,5 @@ "Audio Bluetooth disconnesso" "Audio Bluetooth" "Impossibile trasferire file con dimensioni superiori a 4 GB" + "Connettiti a Bluetooth" diff --git a/res/values-iw/strings.xml b/res/values-iw/strings.xml index 415bd1077..11446ff3b 100644 --- a/res/values-iw/strings.xml +++ b/res/values-iw/strings.xml @@ -138,4 +138,5 @@ "‏אודיו Bluetooth מנותק" "‏אודיו Bluetooth" "‏לא ניתן להעביר קבצים שגדולים מ-GB‏ 4" + "‏התחברות באמצעות Bluetooth" diff --git a/res/values-ja/strings.xml b/res/values-ja/strings.xml index 6171772a0..7688d485a 100644 --- a/res/values-ja/strings.xml +++ b/res/values-ja/strings.xml @@ -134,4 +134,5 @@ "Bluetooth オーディオは接続を解除されています" "Bluetooth オーディオ" "4 GB を超えるファイルは転送できません" + "Bluetooth に接続する" diff --git a/res/values-ka/strings.xml b/res/values-ka/strings.xml index 4983eaaf6..92b632919 100644 --- a/res/values-ka/strings.xml +++ b/res/values-ka/strings.xml @@ -134,4 +134,5 @@ "Bluetooth აუდიო გათიშულია" "Bluetooth აუდიო" "4 გბაიტზე დიდი მოცულობის ფაილების გადატანა ვერ მოხერხდება" + "Bluetooth-თან დაკავშირება" diff --git a/res/values-kk/strings.xml b/res/values-kk/strings.xml index 82de8a6a4..811187fa3 100644 --- a/res/values-kk/strings.xml +++ b/res/values-kk/strings.xml @@ -134,4 +134,5 @@ "Bluetooth дыбысы ажыратылды" "Bluetooth aудиосы" "Көлемі 4 ГБ-тан асатын файлдар тасымалданбайды" + "Bluetooth-ге қосылу" diff --git a/res/values-kk/strings_pbap.xml b/res/values-kk/strings_pbap.xml index 29ff2212d..11893b9ab 100644 --- a/res/values-kk/strings_pbap.xml +++ b/res/values-kk/strings_pbap.xml @@ -4,9 +4,7 @@ "%1$s үшін сессия кілтін теру" "Bluetooth сессия кілті қажет" "%1$s арқылы байланыс қабылдау уақыты өтіп кетті" - - - + "%1$s арқылы сессия кілтін енгізу уақыты өтіп кетті" "Obex растау талабы" "Сессия кілті" "%1$s үшін сессия кілтін теру" diff --git a/res/values-km/strings.xml b/res/values-km/strings.xml index c9b6335fb..1db467c4d 100644 --- a/res/values-km/strings.xml +++ b/res/values-km/strings.xml @@ -134,4 +134,5 @@ "សំឡេងប៊្លូធូសត្រូវបានផ្តាច់" "សំឡេងប៊្លូធូស" "ឯកសារ​ដែល​មាន​ទំហំ​ធំ​ជាង 4 GB មិន​អាចផ្ទេរ​បាន​ទេ" + "ភ្ជាប់​ប៊្លូធូស" diff --git a/res/values-kn/strings.xml b/res/values-kn/strings.xml index 0f0e06f92..1d87823c1 100644 --- a/res/values-kn/strings.xml +++ b/res/values-kn/strings.xml @@ -134,4 +134,5 @@ "ಬ್ಲೂಟೂತ್‌ ಆಡಿಯೊ ಸಂಪರ್ಕ ಕಡಿತಗೊಂಡಿದೆ" "ಬ್ಲೂಟೂತ್‌ ಆಡಿಯೊ" "4GB ಗಿಂತ ದೊಡ್ಡದಾದ ಫೈಲ್‌ಗಳನ್ನು ವರ್ಗಾಯಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ" + "ಬ್ಲೂಟೂತ್‌ಗೆ ಕನೆಕ್ಟ್ ಮಾಡಿ" diff --git a/res/values-ko/strings.xml b/res/values-ko/strings.xml index f5eeca948..3977271cc 100644 --- a/res/values-ko/strings.xml +++ b/res/values-ko/strings.xml @@ -134,4 +134,5 @@ "블루투스 오디오가 연결 해제됨" "블루투스 오디오" "4GB보다 큰 파일은 전송할 수 없습니다" + "블루투스에 연결" diff --git a/res/values-ky/strings.xml b/res/values-ky/strings.xml index a0fa232fc..f02c8748a 100644 --- a/res/values-ky/strings.xml +++ b/res/values-ky/strings.xml @@ -134,4 +134,5 @@ "Bluetooth аудио ажыратылды" "Bluetooth аудио" "4Гб чоң файлдарды өткөрүү мүмкүн эмес" + "Bluetooth\'га туташуу" diff --git a/res/values-lo/strings.xml b/res/values-lo/strings.xml index bebe622a8..3257841eb 100644 --- a/res/values-lo/strings.xml +++ b/res/values-lo/strings.xml @@ -134,4 +134,5 @@ "ຕັດການເຊື່ອມຕໍ່ສຽງ Bluetooth ແລ້ວ" "ສຽງ Bluetooth" "ບໍ່ສາມາດໂອນຍ້າຍໄຟລ໌ທີ່ໃຫຍກວ່າ 4GB ໄດ້" + "ເຊື່ອມຕໍ່ກັບ Bluetooth" diff --git a/res/values-lt/strings.xml b/res/values-lt/strings.xml index 5d0f75990..42562f4ad 100644 --- a/res/values-lt/strings.xml +++ b/res/values-lt/strings.xml @@ -138,4 +138,5 @@ "„Bluetooth“ garsas atjungtas" "„Bluetooth“ garsas" "Negalima perkelti didesnių nei 4 GB failų" + "Prisijungti prie „Bluetooth“" diff --git a/res/values-lv/strings.xml b/res/values-lv/strings.xml index 062a0195e..6edd98470 100644 --- a/res/values-lv/strings.xml +++ b/res/values-lv/strings.xml @@ -136,4 +136,5 @@ "Pārtraukts savienojums ar Bluetooth audio" "Bluetooth audio" "Nevar pārsūtīt failus, kas lielāki par 4 GB." + "Izveidot savienojumu ar Bluetooth" diff --git a/res/values-mk/strings.xml b/res/values-mk/strings.xml index 011c39dfb..5045b56db 100644 --- a/res/values-mk/strings.xml +++ b/res/values-mk/strings.xml @@ -134,4 +134,5 @@ "Аудиото преку Bluetooth е исклучено" "Аудио преку Bluetooth" "Не може да се пренесуваат датотеки поголеми од 4 GB" + "Поврзи се со Bluetooth" diff --git a/res/values-ml/strings.xml b/res/values-ml/strings.xml index 5198b8762..716bf1dd5 100644 --- a/res/values-ml/strings.xml +++ b/res/values-ml/strings.xml @@ -132,4 +132,5 @@ "Bluetooth ഓഡിയോ വിച്ഛേദിച്ചു" "Bluetooth ഓഡിയോ" "4GB-യിൽ കൂടുതലുള്ള ഫയലുകൾ കൈമാറാനാവില്ല" + "Bluetooth-ലേക്ക് കണക്‌റ്റ് ചെയ്യുക" diff --git a/res/values-mn/strings.xml b/res/values-mn/strings.xml index aec448c75..60f238a72 100644 --- a/res/values-mn/strings.xml +++ b/res/values-mn/strings.xml @@ -134,4 +134,5 @@ "Bluetooth аудиог салгасан" "Bluetooth Аудио" "4ГБ-с дээш хэмжээтэй файлыг шилжүүлэх боломжгүй" + "Bluetooth-тэй холбогдох" diff --git a/res/values-mr/strings.xml b/res/values-mr/strings.xml index 4db5d0aa8..43e0a545c 100644 --- a/res/values-mr/strings.xml +++ b/res/values-mr/strings.xml @@ -134,4 +134,5 @@ "ब्लूटूथ ऑडिओ डिस्कनेक्ट केला" "ब्लूटूथ ऑडिओ" "4 GB हून मोठ्या फायली ट्रान्सफर करता येणार नाहीत" + "ब्लूटूथशी कनेक्ट करा" diff --git a/res/values-ms/strings.xml b/res/values-ms/strings.xml index ab9c7a934..a0071ca53 100644 --- a/res/values-ms/strings.xml +++ b/res/values-ms/strings.xml @@ -134,4 +134,5 @@ "Audio Bluetooth diputuskan sambungannya" "Audio Bluetooth" "Fail lebih besar daripada 4GB tidak boleh dipindahkan" + "Sambung ke Bluetooth" diff --git a/res/values-my/strings.xml b/res/values-my/strings.xml index 7e7763a23..eb35b0f85 100644 --- a/res/values-my/strings.xml +++ b/res/values-my/strings.xml @@ -134,4 +134,5 @@ "ဘလူးတုသ်အသံ မချိတ်ဆက်ထားပါ" "ဘလူးတုသ် အသံ" "4GB ထက်ပိုကြီးသည့် ဖိုင်များကို လွှဲပြောင်းမရနိုင်ပါ" + "ဘလူးတုသ်သို့ ချိတ်ဆက်ရန်" diff --git a/res/values-nb/strings.xml b/res/values-nb/strings.xml index e0418b1b4..d6f9e7705 100644 --- a/res/values-nb/strings.xml +++ b/res/values-nb/strings.xml @@ -134,4 +134,5 @@ "Bluetooth-lyd er frakoblet" "Bluetooth-lyd" "Filer som er større enn 4 GB, kan ikke overføres" + "Koble til Bluetooth" diff --git a/res/values-ne/strings.xml b/res/values-ne/strings.xml index 639df71d3..6f9ab5888 100644 --- a/res/values-ne/strings.xml +++ b/res/values-ne/strings.xml @@ -134,4 +134,5 @@ "ब्लुटुथ सम्बन्धी अडियो यन्त्रलाई विच्छेद गरियो" "ब्लुटुथको अडियो" "४ जि.बि. भन्दा ठूला फाइलहरूलाई स्थानान्तरण गर्न सकिँदैन" + "ब्लुटुथमा जोड्नुहोस्" diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml index 920737897..8f496201b 100644 --- a/res/values-nl/strings.xml +++ b/res/values-nl/strings.xml @@ -134,4 +134,5 @@ "Bluetooth-audio ontkoppeld" "Bluetooth-audio" "Bestanden groter dan 4 GB kunnen niet worden overgedragen" + "Verbinding maken met bluetooth" diff --git a/res/values-or/strings.xml b/res/values-or/strings.xml index 8819773e2..94ad0410e 100644 --- a/res/values-or/strings.xml +++ b/res/values-or/strings.xml @@ -134,4 +134,5 @@ "ବ୍ଲୁଟୂଥ୍‍‌ ଅଡିଓ ବିଚ୍ଛିନ୍ନ କରାଗଲା" "ବ୍ଲୁଟୂଥ୍‍‌ ଅଡିଓ" "4GBରୁ ବଡ଼ ଫାଇଲ୍‌ଗୁଡ଼ିକୁ ଟ୍ରାନ୍ସଫର୍‌ କରାଯାଇପାରିବ ନାହିଁ" + "ବ୍ଲୁଟୁଥ୍ ସହ ସଂଯୋଗ କରନ୍ତୁ" diff --git a/res/values-pa/strings.xml b/res/values-pa/strings.xml index 07cab6a78..71c771b2a 100644 --- a/res/values-pa/strings.xml +++ b/res/values-pa/strings.xml @@ -134,4 +134,5 @@ "ਬਲੂਟੁੱਥ ਆਡੀਓ ਡਿਸਕਨੈਕਟ ਕੀਤੀ ਗਈ" "ਬਲੂਟੁੱਥ ਆਡੀਓ" "4GB ਤੋਂ ਵਧੇਰੇ ਵੱਡੀਆਂ ਫ਼ਾਈਲਾਂ ਨੂੰ ਟ੍ਰਾਂਸਫ਼ਰ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਦਾ" + "ਬਲੂਟੁੱਥ ਨਾਲ ਕਨੈਕਟ ਕਰੋ" diff --git a/res/values-pl/strings.xml b/res/values-pl/strings.xml index 8c4869f4c..3cfbe0d5f 100644 --- a/res/values-pl/strings.xml +++ b/res/values-pl/strings.xml @@ -138,4 +138,5 @@ "Dźwięk Bluetooth odłączony" "Dźwięk Bluetooth" "Nie można przenieść plików przekraczających 4 GB" + "Nawiązywanie połączeń przez Bluetooth" diff --git a/res/values-pt-rPT/strings.xml b/res/values-pt-rPT/strings.xml index 09e6781d3..9eae5c975 100644 --- a/res/values-pt-rPT/strings.xml +++ b/res/values-pt-rPT/strings.xml @@ -134,4 +134,5 @@ "Áudio Bluetooth desligado" "Áudio Bluetooth" "Não é possível transferir os ficheiros com mais de 4 GB." + "Ligar ao Bluetooth" diff --git a/res/values-pt/strings.xml b/res/values-pt/strings.xml index 8472432ec..b4a775e33 100644 --- a/res/values-pt/strings.xml +++ b/res/values-pt/strings.xml @@ -134,4 +134,5 @@ "Áudio Bluetooth desconectado" "Áudio Bluetooth" "Não é possível transferir arquivos maiores que 4 GB" + "Conectar ao Bluetooth" diff --git a/res/values-ro/strings.xml b/res/values-ro/strings.xml index 67f117d40..fadc4a6d5 100644 --- a/res/values-ro/strings.xml +++ b/res/values-ro/strings.xml @@ -136,4 +136,5 @@ "Audio prin Bluetooth deconectat" "Audio prin Bluetooth" "Fișierele mai mari de 4 GB nu pot fi transferate" + "Conectați-vă la Bluetooth" diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml index 87c1492aa..6625b8346 100644 --- a/res/values-ru/strings.xml +++ b/res/values-ru/strings.xml @@ -138,4 +138,5 @@ "Звук через Bluetooth отключен" "Звук через Bluetooth" "Можно перенести только файлы размером до 4 ГБ." + "Подключиться по Bluetooth" diff --git a/res/values-si/strings.xml b/res/values-si/strings.xml index ed6e3551a..bbe1e0053 100644 --- a/res/values-si/strings.xml +++ b/res/values-si/strings.xml @@ -134,4 +134,5 @@ "බ්ලූටූත් ශ්‍රව්‍යය විසන්ධි කරන ලදී" "බ්ලූටූත් ශ්‍රව්‍යය" "4GBට වඩා විශාල ගොනු මාරු කළ නොහැකිය" + "බ්ලූටූත් වෙත සබඳින්න" diff --git a/res/values-sk/strings.xml b/res/values-sk/strings.xml index d189d0da2..e28a7920d 100644 --- a/res/values-sk/strings.xml +++ b/res/values-sk/strings.xml @@ -138,4 +138,5 @@ "Rozhranie Bluetooth Audio je odpojené" "Bluetooth Audio" "Súbory väčšie ako 4 GB sa nedajú preniesť" + "Pripojiť k zariadeniu Bluetooth" diff --git a/res/values-sl/strings.xml b/res/values-sl/strings.xml index 912ba37e9..d681fb27e 100644 --- a/res/values-sl/strings.xml +++ b/res/values-sl/strings.xml @@ -138,4 +138,5 @@ "Zvok prek Bluetootha ni povezan" "Zvok prek Bluetootha" "Datotek, večjih od 4 GB, ni mogoče prenesti" + "Povezovanje z Bluetoothom" diff --git a/res/values-sq/strings.xml b/res/values-sq/strings.xml index 1a95dcd6a..5d1aff31b 100644 --- a/res/values-sq/strings.xml +++ b/res/values-sq/strings.xml @@ -134,4 +134,5 @@ "Audioja e \"bluetooth-it\" e shkëputur" "Audioja e \"bluetooth-it\"" "Skedarët më të mëdhenj se 4 GB nuk mund të transferohen" + "Lidhu me Bluetooth" diff --git a/res/values-sr/strings.xml b/res/values-sr/strings.xml index 6bdf67a61..25fc24fba 100644 --- a/res/values-sr/strings.xml +++ b/res/values-sr/strings.xml @@ -136,4 +136,5 @@ "Веза са Bluetooth аудијом је прекинута" "Bluetooth аудио" "Не могу да се преносе датотеке веће од 4 GB" + "Повежи са Bluetooth-ом" diff --git a/res/values-sv/strings.xml b/res/values-sv/strings.xml index 7c1609fbb..f243475b0 100644 --- a/res/values-sv/strings.xml +++ b/res/values-sv/strings.xml @@ -134,4 +134,5 @@ "Bluetooth-ljud är frånkopplat" "Bluetooth-ljud" "Det går inte att överföra filer som är större än 4 GB" + "Anslut till Bluetooth" diff --git a/res/values-sw/strings.xml b/res/values-sw/strings.xml index 39f803ef6..edc03d96d 100644 --- a/res/values-sw/strings.xml +++ b/res/values-sw/strings.xml @@ -134,4 +134,5 @@ "Imeondoa sauti ya Bluetooth" "Sauti ya Bluetooth" "Haiwezi kutuma faili zinazozidi GB 4" + "Unganisha kwenye Bluetooth" diff --git a/res/values-ta/strings.xml b/res/values-ta/strings.xml index e584f9ff7..67d872f9e 100644 --- a/res/values-ta/strings.xml +++ b/res/values-ta/strings.xml @@ -134,4 +134,5 @@ "புளூடூத் ஆடியோ துண்டிக்கப்பட்டது" "புளூடூத் ஆடியோ" "4ஜி.பை.க்கு மேலிருக்கும் ஃபைல்களை இடமாற்ற முடியாது" + "புளூடூத் உடன் இணை" diff --git a/res/values-te/strings.xml b/res/values-te/strings.xml index 7aaeaa459..e7f777546 100644 --- a/res/values-te/strings.xml +++ b/res/values-te/strings.xml @@ -134,4 +134,5 @@ "బ్లూటూత్ ఆడియో డిస్‌కనెక్ట్ చేయబడింది" "బ్లూటూత్ ఆడియో" "4GB కన్నా పెద్ద ఫైళ్లు బదిలీ చేయబడవు" + "బ్లూటూత్‌కు కనెక్ట్ చేయి" diff --git a/res/values-th/strings.xml b/res/values-th/strings.xml index 8cfec4fe8..48023cdf5 100644 --- a/res/values-th/strings.xml +++ b/res/values-th/strings.xml @@ -134,4 +134,5 @@ "ยกเลิกการเชื่อมต่อ Bluetooth Audio แล้ว" "Bluetooth Audio" "โอนไฟล์ที่มีขนาดใหญ่กว่า 4 GB ไม่ได้" + "เชื่อมต่อบลูทูธ" diff --git a/res/values-tl/strings.xml b/res/values-tl/strings.xml index 1f50cebbe..3820800f6 100644 --- a/res/values-tl/strings.xml +++ b/res/values-tl/strings.xml @@ -134,4 +134,5 @@ "Nadiskonekta ang Bluetooth audio" "Bluetooth Audio" "Hindi maililipat ang mga file na mas malaki sa 4GB" + "Kumonekta sa Bluetooth" diff --git a/res/values-tr/strings.xml b/res/values-tr/strings.xml index 71ce9a4a5..e1a0bfb88 100644 --- a/res/values-tr/strings.xml +++ b/res/values-tr/strings.xml @@ -134,4 +134,5 @@ "Bluetooth ses bağlantısı kesildi" "Bluetooth Ses" "4 GB\'tan büyük dosyalar aktarılamaz" + "Bluetooth\'a bağlan" diff --git a/res/values-uk/strings.xml b/res/values-uk/strings.xml index e357d1357..f7d6fbdd8 100644 --- a/res/values-uk/strings.xml +++ b/res/values-uk/strings.xml @@ -138,4 +138,5 @@ "Аудіо Bluetooth від’єднано" "Bluetooth Audio" "Не можна перенести файли, більші за 4 ГБ" + "Підключитися до Bluetooth" diff --git a/res/values-ur/strings.xml b/res/values-ur/strings.xml index cc13746bf..e944fb8c2 100644 --- a/res/values-ur/strings.xml +++ b/res/values-ur/strings.xml @@ -134,4 +134,5 @@ "بلوٹوتھ آڈیو غیر منسلک ہے" "بلوٹوتھ آڈیو" "‏4GB سے بڑی فائلیں منتقل نہیں کی جا سکتیں" + "بلوٹوتھ سے منسلک کریں" diff --git a/res/values-uz/strings.xml b/res/values-uz/strings.xml index dac670b6b..cadb8be49 100644 --- a/res/values-uz/strings.xml +++ b/res/values-uz/strings.xml @@ -134,4 +134,5 @@ "Bluetooth orqali ovoz o‘chirildi" "Bluetooth orqali ovoz" "4 GBdan katta hajmli videolar o‘tkazilmaydi" + "Bluetoothga ulanish" diff --git a/res/values-vi/strings.xml b/res/values-vi/strings.xml index c60c8dec7..e0653c096 100644 --- a/res/values-vi/strings.xml +++ b/res/values-vi/strings.xml @@ -134,4 +134,5 @@ "Đã ngắt kết nối âm thanh Bluetooth" "Âm thanh Bluetooth" "Không thể chuyển những tệp lớn hơn 4 GB" + "Kết nối với Bluetooth" diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml index 489853755..a5f6d2dad 100644 --- a/res/values-zh-rCN/strings.xml +++ b/res/values-zh-rCN/strings.xml @@ -134,4 +134,5 @@ "蓝牙音频已断开连接" "蓝牙音频" "无法传输 4GB 以上的文件" + "连接到蓝牙" diff --git a/res/values-zh-rHK/strings.xml b/res/values-zh-rHK/strings.xml index dfb352a71..2881ffe3c 100644 --- a/res/values-zh-rHK/strings.xml +++ b/res/values-zh-rHK/strings.xml @@ -134,4 +134,5 @@ "已與藍牙音訊解除連接" "藍牙音訊" "無法轉移 4 GB 以上的檔案" + "連接藍牙" diff --git a/res/values-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml index 046dfc682..c8ece0167 100644 --- a/res/values-zh-rTW/strings.xml +++ b/res/values-zh-rTW/strings.xml @@ -134,4 +134,5 @@ "已中斷與藍牙音訊的連線" "藍牙音訊" "無法轉移大於 4GB 的檔案" + "使用藍牙連線" diff --git a/res/values-zu/strings.xml b/res/values-zu/strings.xml index 1bc0fe622..da08ed2a9 100644 --- a/res/values-zu/strings.xml +++ b/res/values-zu/strings.xml @@ -134,4 +134,5 @@ "Umsindo we-Bluetooth unqanyuliwe" "Umsindo we-Bluetooth" "Amafayela amakhulu kuno-4GB awakwazi ukudluliselwa" + "Xhumeka ku-Bluetooth" -- cgit v1.2.3