Loading android/app/jni/com_android_bluetooth_a2dp.cpp +31 −0 Original line number Diff line number Diff line Loading @@ -390,6 +390,33 @@ static jboolean disconnectA2dpNative(JNIEnv* env, jobject object, return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE; } static jboolean setSilenceDeviceNative(JNIEnv* env, jobject object, jbyteArray address, jboolean silence) { ALOGI("%s: sBluetoothA2dpInterface: %p", __func__, sBluetoothA2dpInterface); std::shared_lock<std::shared_timed_mutex> lock(interface_mutex); if (!sBluetoothA2dpInterface) { ALOGE("%s: Failed to get the Bluetooth A2DP Interface", __func__); return JNI_FALSE; } jbyte* addr = env->GetByteArrayElements(address, nullptr); RawAddress bd_addr = RawAddress::kEmpty; if (addr) { bd_addr.FromOctets(reinterpret_cast<const uint8_t*>(addr)); } if (bd_addr == RawAddress::kEmpty) { return JNI_FALSE; } bt_status_t status = sBluetoothA2dpInterface->set_silence_device(bd_addr, silence); if (status != BT_STATUS_SUCCESS) { ALOGE("%s: Failed A2DP set_silence_device, status: %d", __func__, status); } env->ReleaseByteArrayElements(address, addr, 0); return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE; } static jboolean setActiveDeviceNative(JNIEnv* env, jobject object, jbyteArray address) { ALOGI("%s: sBluetoothA2dpInterface: %p", __func__, sBluetoothA2dpInterface); Loading @@ -405,6 +432,9 @@ static jboolean setActiveDeviceNative(JNIEnv* env, jobject object, if (addr) { bd_addr.FromOctets(reinterpret_cast<const uint8_t*>(addr)); } if (bd_addr == RawAddress::kEmpty) { return JNI_FALSE; } bt_status_t status = sBluetoothA2dpInterface->set_active_device(bd_addr); if (status != BT_STATUS_SUCCESS) { ALOGE("%s: Failed A2DP set_active_device, status: %d", __func__, status); Loading Loading @@ -450,6 +480,7 @@ static JNINativeMethod sMethods[] = { {"cleanupNative", "()V", (void*)cleanupNative}, {"connectA2dpNative", "([B)Z", (void*)connectA2dpNative}, {"disconnectA2dpNative", "([B)Z", (void*)disconnectA2dpNative}, {"setSilenceDeviceNative", "([BZ)Z", (void*)setSilenceDeviceNative}, {"setActiveDeviceNative", "([B)Z", (void*)setActiveDeviceNative}, {"setCodecConfigPreferenceNative", "([B[Landroid/bluetooth/BluetoothCodecConfig;)Z", Loading android/app/src/com/android/bluetooth/a2dp/A2dpNativeInterface.java +11 −0 Original line number Diff line number Diff line Loading @@ -106,6 +106,16 @@ public class A2dpNativeInterface { return disconnectA2dpNative(getByteAddress(device)); } /** * Sets a connected A2DP remote device to silence mode. * * @param device the remote device * @return true on success, otherwise false. */ public boolean setSilenceDevice(BluetoothDevice device, boolean silence) { return setSilenceDeviceNative(getByteAddress(device), silence); } /** * Sets a connected A2DP remote device as active. * Loading Loading @@ -199,6 +209,7 @@ public class A2dpNativeInterface { private native void cleanupNative(); private native boolean connectA2dpNative(byte[] address); private native boolean disconnectA2dpNative(byte[] address); private native boolean setSilenceDeviceNative(byte[] address, boolean silence); private native boolean setActiveDeviceNative(byte[] address); private native boolean setCodecConfigPreferenceNative(byte[] address, BluetoothCodecConfig[] codecConfigArray); Loading android/app/src/com/android/bluetooth/a2dp/A2dpService.java +28 −0 Original line number Diff line number Diff line Loading @@ -466,6 +466,34 @@ public class A2dpService extends ProfileService { } } /** * Process a change in the silence mode for a {@link BluetoothDevice}. * * @param device the device to change silence mode * @param silence true to enable silence mode, false to disable. * @return true on success, false on error */ @VisibleForTesting public boolean setSilenceMode(BluetoothDevice device, boolean silence) { if (DBG) { Log.d(TAG, "setSilenceMode(" + device + "): " + silence); } if (silence && Objects.equals(mActiveDevice, device)) { if (mActiveDevice != null && AvrcpTargetService.get() != null) { AvrcpTargetService.get().storeVolumeForDevice(mActiveDevice); } removeActiveDevice(true); } else if (!silence && mActiveDevice == null) { // Set the device as the active device if currently no active device. setActiveDevice(device); } if (!mA2dpNativeInterface.setSilenceDevice(device, silence)) { Log.e(TAG, "Cannot set " + device + " silence mode " + silence + " in native layer"); return false; } return true; } /** * Set the active device. * Loading android/app/src/com/android/bluetooth/btservice/AdapterService.java +49 −1 Original line number Diff line number Diff line Loading @@ -189,6 +189,7 @@ public class AdapterService extends Service { private PhonePolicy mPhonePolicy; private ActiveDeviceManager mActiveDeviceManager; private DatabaseManager mDatabaseManager; private SilenceDeviceManager mSilenceDeviceManager; private AppOpsManager mAppOps; /** Loading Loading @@ -423,6 +424,9 @@ public class AdapterService extends Service { MetadataDatabase.class, MetadataDatabase.DATABASE_NAME).build(); mDatabaseManager.start(database); mSilenceDeviceManager = new SilenceDeviceManager(this, new ServiceFactory(), Looper.getMainLooper()); mSilenceDeviceManager.start(); setAdapterService(this); Loading Loading @@ -704,6 +708,10 @@ public class AdapterService extends Service { mPhonePolicy.cleanup(); } if (mSilenceDeviceManager != null) { mSilenceDeviceManager.cleanup(); } if (mActiveDeviceManager != null) { mActiveDeviceManager.cleanup(); } Loading Loading @@ -1363,6 +1371,34 @@ public class AdapterService extends Service { return service.getPhonebookAccessPermission(device); } @Override public boolean setSilenceMode(BluetoothDevice device, boolean silence) { if (!Utils.checkCaller()) { Log.w(TAG, "setSilenceMode() - Not allowed for non-active user"); return false; } AdapterService service = getService(); if (service == null) { return false; } return service.setSilenceMode(device, silence); } @Override public boolean getSilenceMode(BluetoothDevice device) { if (!Utils.checkCaller()) { Log.w(TAG, "getSilenceMode() - Not allowed for non-active user"); return false; } AdapterService service = getService(); if (service == null) { return false; } return service.getSilenceMode(device); } @Override public boolean setPhonebookAccessPermission(BluetoothDevice device, int value) { if (!Utils.checkCaller()) { Loading Loading @@ -2180,9 +2216,20 @@ public class AdapterService extends Service { : BluetoothDevice.ACCESS_REJECTED; } boolean setPhonebookAccessPermission(BluetoothDevice device, int value) { boolean setSilenceMode(BluetoothDevice device, boolean silence) { enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED, "Need BLUETOOTH PRIVILEGED permission"); mSilenceDeviceManager.setSilenceMode(device, silence); return true; } boolean getSilenceMode(BluetoothDevice device) { enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED, "Need BLUETOOTH PRIVILEGED permission"); return mSilenceDeviceManager.getSilenceMode(device); } boolean setPhonebookAccessPermission(BluetoothDevice device, int value) { SharedPreferences pref = getSharedPreferences(PHONEBOOK_ACCESS_PERMISSION_PREFERENCE_FILE, Context.MODE_PRIVATE); SharedPreferences.Editor editor = pref.edit(); Loading Loading @@ -2672,6 +2719,7 @@ public class AdapterService extends Service { for (ProfileService profile : mRegisteredProfiles) { profile.dump(sb); } mSilenceDeviceManager.dump(fd, writer, args); writer.write(sb.toString()); writer.flush(); Loading android/app/src/com/android/bluetooth/btservice/SilenceDeviceManager.java 0 → 100644 +355 −0 Original line number Diff line number Diff line /* * Copyright 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.btservice; import android.bluetooth.BluetoothA2dp; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothHeadset; import android.bluetooth.BluetoothProfile; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.os.UserHandle; import android.util.Log; import com.android.bluetooth.a2dp.A2dpService; import com.android.bluetooth.hfp.HeadsetService; import com.android.internal.annotations.VisibleForTesting; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; /** * The silence device manager controls silence mode for A2DP, HFP, and AVRCP. * * 1) If an active device (for A2DP or HFP) enters silence mode, the active device * for that profile will be set to null. * 2) If a device exits silence mode while the A2DP or HFP active device is null, * the device will be set as the active device for that profile. * 3) If a device is disconnected, it exits silence mode. * 4) If a device is set as the active device for A2DP or HFP, while silence mode * is enabled, then the device will exit silence mode. * 5) If a device is in silence mode, AVRCP position change event and HFP AG indicators * will be disabled. * 6) If a device is not connected with A2DP or HFP, it cannot enter silence mode. */ public class SilenceDeviceManager { private static final boolean DBG = true; private static final boolean VERBOSE = false; private static final String TAG = "SilenceDeviceManager"; private final AdapterService mAdapterService; private final ServiceFactory mFactory; private Handler mHandler = null; private Looper mLooper = null; private A2dpService mA2dpService = null; private HeadsetService mHeadsetService = null; private final Map<BluetoothDevice, Boolean> mSilenceDevices = new HashMap<>(); private final List<BluetoothDevice> mA2dpConnectedDevices = new ArrayList<>(); private final List<BluetoothDevice> mHfpConnectedDevices = new ArrayList<>(); private static final int MSG_SILENCE_DEVICE_STATE_CHANGED = 1; private static final int MSG_A2DP_CONNECTION_STATE_CHANGED = 10; private static final int MSG_HFP_CONNECTION_STATE_CHANGED = 11; private static final int MSG_A2DP_ACTIVE_DEIVCE_CHANGED = 20; private static final int MSG_HFP_ACTIVE_DEVICE_CHANGED = 21; private static final int ENABLE_SILENCE = 0; private static final int DISABLE_SILENCE = 1; // Broadcast receiver for all changes private final BroadcastReceiver mReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (action == null) { Log.e(TAG, "Received intent with null action"); return; } switch (action) { case BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED: mHandler.obtainMessage(MSG_A2DP_CONNECTION_STATE_CHANGED, intent).sendToTarget(); break; case BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED: mHandler.obtainMessage(MSG_HFP_CONNECTION_STATE_CHANGED, intent).sendToTarget(); break; case BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED: mHandler.obtainMessage(MSG_A2DP_ACTIVE_DEIVCE_CHANGED, intent).sendToTarget(); break; case BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED: mHandler.obtainMessage(MSG_HFP_ACTIVE_DEVICE_CHANGED, intent).sendToTarget(); break; default: Log.e(TAG, "Received unexpected intent, action=" + action); break; } } }; class SilenceDeviceManagerHandler extends Handler { SilenceDeviceManagerHandler(Looper looper) { super(looper); } @Override public void handleMessage(Message msg) { if (VERBOSE) { Log.d(TAG, "handleMessage: " + msg.what); } switch (msg.what) { case MSG_SILENCE_DEVICE_STATE_CHANGED: { BluetoothDevice device = (BluetoothDevice) msg.obj; boolean state = (msg.arg1 == ENABLE_SILENCE); handleSilenceDeviceStateChanged(device, state); } break; case MSG_A2DP_CONNECTION_STATE_CHANGED: { Intent intent = (Intent) msg.obj; BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); int prevState = intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, -1); int nextState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1); if (nextState == BluetoothProfile.STATE_CONNECTED) { // enter connected state addConnectedDevice(device, BluetoothProfile.A2DP); if (!mSilenceDevices.containsKey(device)) { mSilenceDevices.put(device, false); } } else if (prevState == BluetoothProfile.STATE_CONNECTED) { // exiting from connected state removeConnectedDevice(device, BluetoothProfile.A2DP); if (!isBluetoothAudioConnected(device)) { handleSilenceDeviceStateChanged(device, false); mSilenceDevices.remove(device); } } } break; case MSG_HFP_CONNECTION_STATE_CHANGED: { Intent intent = (Intent) msg.obj; BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); int prevState = intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, -1); int nextState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1); if (nextState == BluetoothProfile.STATE_CONNECTED) { // enter connected state addConnectedDevice(device, BluetoothProfile.HEADSET); if (!mSilenceDevices.containsKey(device)) { mSilenceDevices.put(device, false); } } else if (prevState == BluetoothProfile.STATE_CONNECTED) { // exiting from connected state removeConnectedDevice(device, BluetoothProfile.HEADSET); if (!isBluetoothAudioConnected(device)) { handleSilenceDeviceStateChanged(device, false); mSilenceDevices.remove(device); } } } break; case MSG_A2DP_ACTIVE_DEIVCE_CHANGED: { Intent intent = (Intent) msg.obj; BluetoothDevice a2dpActiveDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); if (getSilenceMode(a2dpActiveDevice)) { // Resume the device from silence mode. setSilenceMode(a2dpActiveDevice, false); } } break; case MSG_HFP_ACTIVE_DEVICE_CHANGED: { Intent intent = (Intent) msg.obj; BluetoothDevice hfpActiveDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); if (getSilenceMode(hfpActiveDevice)) { // Resume the device from silence mode. setSilenceMode(hfpActiveDevice, false); } } break; default: { Log.e(TAG, "Unknown message: " + msg.what); } break; } } }; SilenceDeviceManager(AdapterService service, ServiceFactory factory, Looper looper) { mAdapterService = service; mFactory = factory; mLooper = looper; } void start() { if (VERBOSE) { Log.v(TAG, "start()"); } mHandler = new SilenceDeviceManagerHandler(mLooper); IntentFilter filter = new IntentFilter(); filter.addAction(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED); filter.addAction(BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED); filter.addAction(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED); filter.addAction(BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED); mAdapterService.registerReceiver(mReceiver, filter); mA2dpService = mFactory.getA2dpService(); mHeadsetService = mFactory.getHeadsetService(); } void cleanup() { if (VERBOSE) { Log.v(TAG, "cleanup()"); } mSilenceDevices.clear(); mA2dpService = null; mHeadsetService = null; mAdapterService.unregisterReceiver(mReceiver); } @VisibleForTesting boolean setSilenceMode(BluetoothDevice device, boolean silence) { if (mHandler == null) { Log.e(TAG, "setSilenceMode() mHandler is null!"); return false; } Log.d(TAG, "setSilenceMode: " + device.getAddress() + ", " + silence); Message message = mHandler.obtainMessage(MSG_SILENCE_DEVICE_STATE_CHANGED, silence ? ENABLE_SILENCE : DISABLE_SILENCE, 0, device); mHandler.sendMessage(message); return true; } void handleSilenceDeviceStateChanged(BluetoothDevice device, boolean state) { boolean oldState = getSilenceMode(device); if (oldState == state) { return; } if (!isBluetoothAudioConnected(device)) { if (oldState) { // Device is disconnected, resume all silenced profiles. state = false; } else { Log.d(TAG, "Deivce is not connected to any Bluetooth audio."); return; } } mSilenceDevices.replace(device, state); if (mA2dpService == null) { Log.d(TAG, "A2dpService is null!"); return; } if (mHeadsetService == null) { Log.d(TAG, "HeadsetService is null!"); return; } mA2dpService.setSilenceMode(device, state); mHeadsetService.setSilenceMode(device, state); Log.i(TAG, "Silence mode change " + device.getAddress() + ": " + oldState + " -> " + state); broadcastSilenceStateChange(device, state); } void broadcastSilenceStateChange(BluetoothDevice device, boolean state) { Intent intent = new Intent(BluetoothDevice.ACTION_SILENCE_MODE_CHANGED); intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); intent.putExtra(BluetoothDevice.EXTRA_SILENCE_ENABLED, state); mAdapterService.sendBroadcastAsUser(intent, UserHandle.ALL, AdapterService.BLUETOOTH_PERM); } @VisibleForTesting boolean getSilenceMode(BluetoothDevice device) { boolean state = false; if (mSilenceDevices.containsKey(device)) { state = mSilenceDevices.get(device); } return state; } void addConnectedDevice(BluetoothDevice device, int profile) { if (VERBOSE) { Log.d(TAG, "addConnectedDevice: " + device.getAddress() + ", profile:" + profile); } switch (profile) { case BluetoothProfile.A2DP: if (!mA2dpConnectedDevices.contains(device)) { mA2dpConnectedDevices.add(device); } break; case BluetoothProfile.HEADSET: if (!mHfpConnectedDevices.contains(device)) { mHfpConnectedDevices.add(device); } break; } } void removeConnectedDevice(BluetoothDevice device, int profile) { if (VERBOSE) { Log.d(TAG, "removeConnectedDevice: " + device.getAddress() + ", profile:" + profile); } switch (profile) { case BluetoothProfile.A2DP: if (mA2dpConnectedDevices.contains(device)) { mA2dpConnectedDevices.remove(device); } break; case BluetoothProfile.HEADSET: if (mHfpConnectedDevices.contains(device)) { mHfpConnectedDevices.remove(device); } break; } } boolean isBluetoothAudioConnected(BluetoothDevice device) { return (mA2dpConnectedDevices.contains(device) || mHfpConnectedDevices.contains(device)); } protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) { writer.println("\nSilenceDeviceManager:"); writer.println(" Address | Is silenced?"); for (BluetoothDevice device : mSilenceDevices.keySet()) { writer.println(" " + device.getAddress() + " | " + getSilenceMode(device)); } } @VisibleForTesting BroadcastReceiver getBroadcastReceiver() { return mReceiver; } } Loading
android/app/jni/com_android_bluetooth_a2dp.cpp +31 −0 Original line number Diff line number Diff line Loading @@ -390,6 +390,33 @@ static jboolean disconnectA2dpNative(JNIEnv* env, jobject object, return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE; } static jboolean setSilenceDeviceNative(JNIEnv* env, jobject object, jbyteArray address, jboolean silence) { ALOGI("%s: sBluetoothA2dpInterface: %p", __func__, sBluetoothA2dpInterface); std::shared_lock<std::shared_timed_mutex> lock(interface_mutex); if (!sBluetoothA2dpInterface) { ALOGE("%s: Failed to get the Bluetooth A2DP Interface", __func__); return JNI_FALSE; } jbyte* addr = env->GetByteArrayElements(address, nullptr); RawAddress bd_addr = RawAddress::kEmpty; if (addr) { bd_addr.FromOctets(reinterpret_cast<const uint8_t*>(addr)); } if (bd_addr == RawAddress::kEmpty) { return JNI_FALSE; } bt_status_t status = sBluetoothA2dpInterface->set_silence_device(bd_addr, silence); if (status != BT_STATUS_SUCCESS) { ALOGE("%s: Failed A2DP set_silence_device, status: %d", __func__, status); } env->ReleaseByteArrayElements(address, addr, 0); return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE; } static jboolean setActiveDeviceNative(JNIEnv* env, jobject object, jbyteArray address) { ALOGI("%s: sBluetoothA2dpInterface: %p", __func__, sBluetoothA2dpInterface); Loading @@ -405,6 +432,9 @@ static jboolean setActiveDeviceNative(JNIEnv* env, jobject object, if (addr) { bd_addr.FromOctets(reinterpret_cast<const uint8_t*>(addr)); } if (bd_addr == RawAddress::kEmpty) { return JNI_FALSE; } bt_status_t status = sBluetoothA2dpInterface->set_active_device(bd_addr); if (status != BT_STATUS_SUCCESS) { ALOGE("%s: Failed A2DP set_active_device, status: %d", __func__, status); Loading Loading @@ -450,6 +480,7 @@ static JNINativeMethod sMethods[] = { {"cleanupNative", "()V", (void*)cleanupNative}, {"connectA2dpNative", "([B)Z", (void*)connectA2dpNative}, {"disconnectA2dpNative", "([B)Z", (void*)disconnectA2dpNative}, {"setSilenceDeviceNative", "([BZ)Z", (void*)setSilenceDeviceNative}, {"setActiveDeviceNative", "([B)Z", (void*)setActiveDeviceNative}, {"setCodecConfigPreferenceNative", "([B[Landroid/bluetooth/BluetoothCodecConfig;)Z", Loading
android/app/src/com/android/bluetooth/a2dp/A2dpNativeInterface.java +11 −0 Original line number Diff line number Diff line Loading @@ -106,6 +106,16 @@ public class A2dpNativeInterface { return disconnectA2dpNative(getByteAddress(device)); } /** * Sets a connected A2DP remote device to silence mode. * * @param device the remote device * @return true on success, otherwise false. */ public boolean setSilenceDevice(BluetoothDevice device, boolean silence) { return setSilenceDeviceNative(getByteAddress(device), silence); } /** * Sets a connected A2DP remote device as active. * Loading Loading @@ -199,6 +209,7 @@ public class A2dpNativeInterface { private native void cleanupNative(); private native boolean connectA2dpNative(byte[] address); private native boolean disconnectA2dpNative(byte[] address); private native boolean setSilenceDeviceNative(byte[] address, boolean silence); private native boolean setActiveDeviceNative(byte[] address); private native boolean setCodecConfigPreferenceNative(byte[] address, BluetoothCodecConfig[] codecConfigArray); Loading
android/app/src/com/android/bluetooth/a2dp/A2dpService.java +28 −0 Original line number Diff line number Diff line Loading @@ -466,6 +466,34 @@ public class A2dpService extends ProfileService { } } /** * Process a change in the silence mode for a {@link BluetoothDevice}. * * @param device the device to change silence mode * @param silence true to enable silence mode, false to disable. * @return true on success, false on error */ @VisibleForTesting public boolean setSilenceMode(BluetoothDevice device, boolean silence) { if (DBG) { Log.d(TAG, "setSilenceMode(" + device + "): " + silence); } if (silence && Objects.equals(mActiveDevice, device)) { if (mActiveDevice != null && AvrcpTargetService.get() != null) { AvrcpTargetService.get().storeVolumeForDevice(mActiveDevice); } removeActiveDevice(true); } else if (!silence && mActiveDevice == null) { // Set the device as the active device if currently no active device. setActiveDevice(device); } if (!mA2dpNativeInterface.setSilenceDevice(device, silence)) { Log.e(TAG, "Cannot set " + device + " silence mode " + silence + " in native layer"); return false; } return true; } /** * Set the active device. * Loading
android/app/src/com/android/bluetooth/btservice/AdapterService.java +49 −1 Original line number Diff line number Diff line Loading @@ -189,6 +189,7 @@ public class AdapterService extends Service { private PhonePolicy mPhonePolicy; private ActiveDeviceManager mActiveDeviceManager; private DatabaseManager mDatabaseManager; private SilenceDeviceManager mSilenceDeviceManager; private AppOpsManager mAppOps; /** Loading Loading @@ -423,6 +424,9 @@ public class AdapterService extends Service { MetadataDatabase.class, MetadataDatabase.DATABASE_NAME).build(); mDatabaseManager.start(database); mSilenceDeviceManager = new SilenceDeviceManager(this, new ServiceFactory(), Looper.getMainLooper()); mSilenceDeviceManager.start(); setAdapterService(this); Loading Loading @@ -704,6 +708,10 @@ public class AdapterService extends Service { mPhonePolicy.cleanup(); } if (mSilenceDeviceManager != null) { mSilenceDeviceManager.cleanup(); } if (mActiveDeviceManager != null) { mActiveDeviceManager.cleanup(); } Loading Loading @@ -1363,6 +1371,34 @@ public class AdapterService extends Service { return service.getPhonebookAccessPermission(device); } @Override public boolean setSilenceMode(BluetoothDevice device, boolean silence) { if (!Utils.checkCaller()) { Log.w(TAG, "setSilenceMode() - Not allowed for non-active user"); return false; } AdapterService service = getService(); if (service == null) { return false; } return service.setSilenceMode(device, silence); } @Override public boolean getSilenceMode(BluetoothDevice device) { if (!Utils.checkCaller()) { Log.w(TAG, "getSilenceMode() - Not allowed for non-active user"); return false; } AdapterService service = getService(); if (service == null) { return false; } return service.getSilenceMode(device); } @Override public boolean setPhonebookAccessPermission(BluetoothDevice device, int value) { if (!Utils.checkCaller()) { Loading Loading @@ -2180,9 +2216,20 @@ public class AdapterService extends Service { : BluetoothDevice.ACCESS_REJECTED; } boolean setPhonebookAccessPermission(BluetoothDevice device, int value) { boolean setSilenceMode(BluetoothDevice device, boolean silence) { enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED, "Need BLUETOOTH PRIVILEGED permission"); mSilenceDeviceManager.setSilenceMode(device, silence); return true; } boolean getSilenceMode(BluetoothDevice device) { enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED, "Need BLUETOOTH PRIVILEGED permission"); return mSilenceDeviceManager.getSilenceMode(device); } boolean setPhonebookAccessPermission(BluetoothDevice device, int value) { SharedPreferences pref = getSharedPreferences(PHONEBOOK_ACCESS_PERMISSION_PREFERENCE_FILE, Context.MODE_PRIVATE); SharedPreferences.Editor editor = pref.edit(); Loading Loading @@ -2672,6 +2719,7 @@ public class AdapterService extends Service { for (ProfileService profile : mRegisteredProfiles) { profile.dump(sb); } mSilenceDeviceManager.dump(fd, writer, args); writer.write(sb.toString()); writer.flush(); Loading
android/app/src/com/android/bluetooth/btservice/SilenceDeviceManager.java 0 → 100644 +355 −0 Original line number Diff line number Diff line /* * Copyright 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.btservice; import android.bluetooth.BluetoothA2dp; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothHeadset; import android.bluetooth.BluetoothProfile; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.os.UserHandle; import android.util.Log; import com.android.bluetooth.a2dp.A2dpService; import com.android.bluetooth.hfp.HeadsetService; import com.android.internal.annotations.VisibleForTesting; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; /** * The silence device manager controls silence mode for A2DP, HFP, and AVRCP. * * 1) If an active device (for A2DP or HFP) enters silence mode, the active device * for that profile will be set to null. * 2) If a device exits silence mode while the A2DP or HFP active device is null, * the device will be set as the active device for that profile. * 3) If a device is disconnected, it exits silence mode. * 4) If a device is set as the active device for A2DP or HFP, while silence mode * is enabled, then the device will exit silence mode. * 5) If a device is in silence mode, AVRCP position change event and HFP AG indicators * will be disabled. * 6) If a device is not connected with A2DP or HFP, it cannot enter silence mode. */ public class SilenceDeviceManager { private static final boolean DBG = true; private static final boolean VERBOSE = false; private static final String TAG = "SilenceDeviceManager"; private final AdapterService mAdapterService; private final ServiceFactory mFactory; private Handler mHandler = null; private Looper mLooper = null; private A2dpService mA2dpService = null; private HeadsetService mHeadsetService = null; private final Map<BluetoothDevice, Boolean> mSilenceDevices = new HashMap<>(); private final List<BluetoothDevice> mA2dpConnectedDevices = new ArrayList<>(); private final List<BluetoothDevice> mHfpConnectedDevices = new ArrayList<>(); private static final int MSG_SILENCE_DEVICE_STATE_CHANGED = 1; private static final int MSG_A2DP_CONNECTION_STATE_CHANGED = 10; private static final int MSG_HFP_CONNECTION_STATE_CHANGED = 11; private static final int MSG_A2DP_ACTIVE_DEIVCE_CHANGED = 20; private static final int MSG_HFP_ACTIVE_DEVICE_CHANGED = 21; private static final int ENABLE_SILENCE = 0; private static final int DISABLE_SILENCE = 1; // Broadcast receiver for all changes private final BroadcastReceiver mReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (action == null) { Log.e(TAG, "Received intent with null action"); return; } switch (action) { case BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED: mHandler.obtainMessage(MSG_A2DP_CONNECTION_STATE_CHANGED, intent).sendToTarget(); break; case BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED: mHandler.obtainMessage(MSG_HFP_CONNECTION_STATE_CHANGED, intent).sendToTarget(); break; case BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED: mHandler.obtainMessage(MSG_A2DP_ACTIVE_DEIVCE_CHANGED, intent).sendToTarget(); break; case BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED: mHandler.obtainMessage(MSG_HFP_ACTIVE_DEVICE_CHANGED, intent).sendToTarget(); break; default: Log.e(TAG, "Received unexpected intent, action=" + action); break; } } }; class SilenceDeviceManagerHandler extends Handler { SilenceDeviceManagerHandler(Looper looper) { super(looper); } @Override public void handleMessage(Message msg) { if (VERBOSE) { Log.d(TAG, "handleMessage: " + msg.what); } switch (msg.what) { case MSG_SILENCE_DEVICE_STATE_CHANGED: { BluetoothDevice device = (BluetoothDevice) msg.obj; boolean state = (msg.arg1 == ENABLE_SILENCE); handleSilenceDeviceStateChanged(device, state); } break; case MSG_A2DP_CONNECTION_STATE_CHANGED: { Intent intent = (Intent) msg.obj; BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); int prevState = intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, -1); int nextState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1); if (nextState == BluetoothProfile.STATE_CONNECTED) { // enter connected state addConnectedDevice(device, BluetoothProfile.A2DP); if (!mSilenceDevices.containsKey(device)) { mSilenceDevices.put(device, false); } } else if (prevState == BluetoothProfile.STATE_CONNECTED) { // exiting from connected state removeConnectedDevice(device, BluetoothProfile.A2DP); if (!isBluetoothAudioConnected(device)) { handleSilenceDeviceStateChanged(device, false); mSilenceDevices.remove(device); } } } break; case MSG_HFP_CONNECTION_STATE_CHANGED: { Intent intent = (Intent) msg.obj; BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); int prevState = intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, -1); int nextState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1); if (nextState == BluetoothProfile.STATE_CONNECTED) { // enter connected state addConnectedDevice(device, BluetoothProfile.HEADSET); if (!mSilenceDevices.containsKey(device)) { mSilenceDevices.put(device, false); } } else if (prevState == BluetoothProfile.STATE_CONNECTED) { // exiting from connected state removeConnectedDevice(device, BluetoothProfile.HEADSET); if (!isBluetoothAudioConnected(device)) { handleSilenceDeviceStateChanged(device, false); mSilenceDevices.remove(device); } } } break; case MSG_A2DP_ACTIVE_DEIVCE_CHANGED: { Intent intent = (Intent) msg.obj; BluetoothDevice a2dpActiveDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); if (getSilenceMode(a2dpActiveDevice)) { // Resume the device from silence mode. setSilenceMode(a2dpActiveDevice, false); } } break; case MSG_HFP_ACTIVE_DEVICE_CHANGED: { Intent intent = (Intent) msg.obj; BluetoothDevice hfpActiveDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); if (getSilenceMode(hfpActiveDevice)) { // Resume the device from silence mode. setSilenceMode(hfpActiveDevice, false); } } break; default: { Log.e(TAG, "Unknown message: " + msg.what); } break; } } }; SilenceDeviceManager(AdapterService service, ServiceFactory factory, Looper looper) { mAdapterService = service; mFactory = factory; mLooper = looper; } void start() { if (VERBOSE) { Log.v(TAG, "start()"); } mHandler = new SilenceDeviceManagerHandler(mLooper); IntentFilter filter = new IntentFilter(); filter.addAction(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED); filter.addAction(BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED); filter.addAction(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED); filter.addAction(BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED); mAdapterService.registerReceiver(mReceiver, filter); mA2dpService = mFactory.getA2dpService(); mHeadsetService = mFactory.getHeadsetService(); } void cleanup() { if (VERBOSE) { Log.v(TAG, "cleanup()"); } mSilenceDevices.clear(); mA2dpService = null; mHeadsetService = null; mAdapterService.unregisterReceiver(mReceiver); } @VisibleForTesting boolean setSilenceMode(BluetoothDevice device, boolean silence) { if (mHandler == null) { Log.e(TAG, "setSilenceMode() mHandler is null!"); return false; } Log.d(TAG, "setSilenceMode: " + device.getAddress() + ", " + silence); Message message = mHandler.obtainMessage(MSG_SILENCE_DEVICE_STATE_CHANGED, silence ? ENABLE_SILENCE : DISABLE_SILENCE, 0, device); mHandler.sendMessage(message); return true; } void handleSilenceDeviceStateChanged(BluetoothDevice device, boolean state) { boolean oldState = getSilenceMode(device); if (oldState == state) { return; } if (!isBluetoothAudioConnected(device)) { if (oldState) { // Device is disconnected, resume all silenced profiles. state = false; } else { Log.d(TAG, "Deivce is not connected to any Bluetooth audio."); return; } } mSilenceDevices.replace(device, state); if (mA2dpService == null) { Log.d(TAG, "A2dpService is null!"); return; } if (mHeadsetService == null) { Log.d(TAG, "HeadsetService is null!"); return; } mA2dpService.setSilenceMode(device, state); mHeadsetService.setSilenceMode(device, state); Log.i(TAG, "Silence mode change " + device.getAddress() + ": " + oldState + " -> " + state); broadcastSilenceStateChange(device, state); } void broadcastSilenceStateChange(BluetoothDevice device, boolean state) { Intent intent = new Intent(BluetoothDevice.ACTION_SILENCE_MODE_CHANGED); intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); intent.putExtra(BluetoothDevice.EXTRA_SILENCE_ENABLED, state); mAdapterService.sendBroadcastAsUser(intent, UserHandle.ALL, AdapterService.BLUETOOTH_PERM); } @VisibleForTesting boolean getSilenceMode(BluetoothDevice device) { boolean state = false; if (mSilenceDevices.containsKey(device)) { state = mSilenceDevices.get(device); } return state; } void addConnectedDevice(BluetoothDevice device, int profile) { if (VERBOSE) { Log.d(TAG, "addConnectedDevice: " + device.getAddress() + ", profile:" + profile); } switch (profile) { case BluetoothProfile.A2DP: if (!mA2dpConnectedDevices.contains(device)) { mA2dpConnectedDevices.add(device); } break; case BluetoothProfile.HEADSET: if (!mHfpConnectedDevices.contains(device)) { mHfpConnectedDevices.add(device); } break; } } void removeConnectedDevice(BluetoothDevice device, int profile) { if (VERBOSE) { Log.d(TAG, "removeConnectedDevice: " + device.getAddress() + ", profile:" + profile); } switch (profile) { case BluetoothProfile.A2DP: if (mA2dpConnectedDevices.contains(device)) { mA2dpConnectedDevices.remove(device); } break; case BluetoothProfile.HEADSET: if (mHfpConnectedDevices.contains(device)) { mHfpConnectedDevices.remove(device); } break; } } boolean isBluetoothAudioConnected(BluetoothDevice device) { return (mA2dpConnectedDevices.contains(device) || mHfpConnectedDevices.contains(device)); } protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) { writer.println("\nSilenceDeviceManager:"); writer.println(" Address | Is silenced?"); for (BluetoothDevice device : mSilenceDevices.keySet()) { writer.println(" " + device.getAddress() + " | " + getSilenceMode(device)); } } @VisibleForTesting BroadcastReceiver getBroadcastReceiver() { return mReceiver; } }