1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.pmc; 18 19 import android.app.AlarmManager; 20 import android.app.PendingIntent; 21 import android.bluetooth.BluetoothA2dp; 22 import android.bluetooth.BluetoothAdapter; 23 import android.bluetooth.BluetoothCodecConfig; 24 import android.bluetooth.BluetoothCodecStatus; 25 import android.bluetooth.BluetoothDevice; 26 import android.bluetooth.BluetoothProfile; 27 import android.content.BroadcastReceiver; 28 import android.content.Context; 29 import android.content.Intent; 30 import android.content.IntentFilter; 31 import android.media.MediaPlayer; 32 import android.net.Uri; 33 import android.os.Bundle; 34 import android.os.SystemClock; 35 import android.util.Log; 36 37 import java.util.ArrayList; 38 import java.util.List; 39 import java.util.Set; 40 41 /** 42 * Bluetooth A2DP Receiver functions for codec power testing. 43 */ 44 public class A2dpReceiver extends BroadcastReceiver { 45 public static final String TAG = "A2DPPOWER"; 46 public static final String A2DP_INTENT = "com.android.pmc.A2DP"; 47 public static final String A2DP_ALARM = "com.android.pmc.A2DP.Alarm"; 48 public static final int THOUSAND = 1000; 49 public static final int WAIT_SECONDS = 10; 50 public static final int ALARM_MESSAGE = 1; 51 52 public static final float NORMAL_VOLUME = 0.3f; 53 public static final float ZERO_VOLUME = 0.0f; 54 55 private final Context mContext; 56 private final AlarmManager mAlarmManager; 57 private final BluetoothAdapter mBluetoothAdapter; 58 59 private MediaPlayer mPlayer; 60 private BluetoothA2dp mBluetoothA2dp; 61 62 private PMCStatusLogger mPMCStatusLogger; 63 64 /** 65 * BroadcastReceiver() to get status after calling setCodecConfigPreference() 66 * 67 */ 68 private BroadcastReceiver mBluetoothA2dpReceiver = new BroadcastReceiver() { 69 @Override 70 public void onReceive(Context context, Intent intent) { 71 Log.d(TAG, "mBluetoothA2dpReceiver.onReceive() intent=" + intent); 72 String action = intent.getAction(); 73 74 if (BluetoothA2dp.ACTION_CODEC_CONFIG_CHANGED.equals(action)) { 75 getCodecValue(true); 76 } 77 } 78 }; 79 80 /** 81 * ServiceListener for A2DP connection/disconnection event 82 * 83 */ 84 private BluetoothProfile.ServiceListener mBluetoothA2dpServiceListener = 85 new BluetoothProfile.ServiceListener() { 86 public void onServiceConnected(int profile, 87 BluetoothProfile proxy) { 88 Log.d(TAG, "BluetoothA2dpServiceListener.onServiceConnected"); 89 mBluetoothA2dp = (BluetoothA2dp) proxy; 90 getCodecValue(true); 91 } 92 93 public void onServiceDisconnected(int profile) { 94 Log.d(TAG, "BluetoothA2dpServiceListener.onServiceDisconnected"); 95 mBluetoothA2dp = null; 96 } 97 }; 98 99 /** 100 * Constructor to be called by PMC 101 * 102 * @param context - PMC will provide a context 103 * @param alarmManager - PMC will provide alarmManager 104 */ A2dpReceiver(Context context, AlarmManager alarmManager)105 public A2dpReceiver(Context context, AlarmManager alarmManager) { 106 // Prepare for setting alarm service 107 mContext = context; 108 mAlarmManager = alarmManager; 109 110 mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); 111 if (mBluetoothAdapter == null) { 112 Log.e(TAG, "BluetoothAdapter is Null"); 113 return; 114 } else { 115 if (!mBluetoothAdapter.isEnabled()) { 116 Log.d(TAG, "BluetoothAdapter is NOT enabled, enable now"); 117 mBluetoothAdapter.enable(); 118 if (!mBluetoothAdapter.isEnabled()) { 119 Log.e(TAG, "Can't enable Bluetooth"); 120 return; 121 } 122 } 123 } 124 // Setup BroadcastReceiver for ACTION_CODEC_CONFIG_CHANGED 125 IntentFilter filter = new IntentFilter(); 126 if (mBluetoothAdapter != null) { 127 mBluetoothAdapter.getProfileProxy(mContext, 128 mBluetoothA2dpServiceListener, 129 BluetoothProfile.A2DP); 130 Log.d(TAG, "After getProfileProxy()"); 131 } 132 filter = new IntentFilter(); 133 filter.addAction(BluetoothA2dp.ACTION_CODEC_CONFIG_CHANGED); 134 mContext.registerReceiver(mBluetoothA2dpReceiver, filter); 135 136 Log.d(TAG, "A2dpReceiver()"); 137 } 138 139 /** 140 * initialize() to setup Bluetooth adapters and check if Bluetooth device is connected 141 * it is called when PMC command is received to start streaming 142 */ initialize()143 private boolean initialize() { 144 Log.d(TAG, "Start initialize()"); 145 146 // Check if any Bluetooth devices are connected 147 ArrayList<BluetoothDevice> results = new ArrayList<BluetoothDevice>(); 148 Set<BluetoothDevice> bondedDevices = mBluetoothAdapter.getBondedDevices(); 149 if (bondedDevices == null) { 150 Log.e(TAG, "Bonded devices list is null"); 151 return false; 152 } 153 for (BluetoothDevice bd : bondedDevices) { 154 if (bd.isConnected()) { 155 results.add(bd); 156 } 157 } 158 159 if (results.isEmpty()) { 160 Log.e(TAG, "No device is connected"); 161 return false; 162 } 163 164 Log.d(TAG, "Finish initialize()"); 165 166 return true; 167 } 168 169 /** 170 * Method to receive the broadcast from Python client or AlarmManager 171 * 172 * @param context - system will provide a context to this function 173 * @param intent - system will provide an intent to this function 174 */ 175 @Override onReceive(Context context, Intent intent)176 public void onReceive(Context context, Intent intent) { 177 if (!intent.getAction().equals(A2DP_INTENT)) return; 178 boolean alarm = intent.hasExtra(A2DP_ALARM); 179 if (alarm) { 180 Log.v(TAG, "Alarm Message to Stop playing"); 181 mPMCStatusLogger.logStatus("SUCCEED"); 182 mPlayer.stop(); 183 // Release the Media Player 184 mPlayer.release(); 185 } else { 186 Log.d(TAG, "Received PMC command message"); 187 processParameters(intent); 188 } 189 } 190 191 /** 192 * Method to process parameters from Python client 193 * 194 * @param intent - system will provide an intent to this function 195 */ processParameters(Intent intent)196 private void processParameters(Intent intent) { 197 int codecType = BluetoothCodecConfig.SOURCE_CODEC_TYPE_INVALID; 198 int sampleRate = BluetoothCodecConfig.SAMPLE_RATE_NONE; 199 int bitsPerSample = BluetoothCodecConfig.BITS_PER_SAMPLE_NONE; 200 int channelMode = BluetoothCodecConfig.CHANNEL_MODE_STEREO; 201 // codecSpecific1 is for LDAC quality so far 202 // Other code specific values are not used now 203 long codecSpecific1 = 0, codecSpecific2 = 0, codecSpecific3 = 0, 204 codecSpecific4 = 0; 205 int playTime = 0; 206 String musicUrl; 207 String tmpStr; 208 209 // Create the logger object 210 mPMCStatusLogger = new PMCStatusLogger(TAG + ".log", TAG); 211 212 // For a baseline case when Blueooth is off but music is playing with speaker is muted 213 boolean bt_off_mute = false; 214 215 Bundle extras = intent.getExtras(); 216 217 if (extras == null) { 218 Log.e(TAG, "No parameters specified"); 219 return; 220 } 221 222 if (extras.containsKey("BT_OFF_Mute")) { 223 Log.v(TAG, "Mute is specified for Bluetooth off baseline case"); 224 bt_off_mute = true; 225 } 226 227 // initialize() if we are testing over Bluetooth, we do NOT test 228 // over bluetooth for the play music with Bluetooth off test case. 229 if (!bt_off_mute) { 230 if (!initialize()) { 231 mPMCStatusLogger.logStatus("initialize() Failed"); 232 return; 233 } 234 } 235 // Check if it is baseline Bluetooth is on but not stream 236 if (extras.containsKey("BT_ON_NotPlay")) { 237 Log.v(TAG, "NotPlay is specified for baseline case that only Bluetooth is on"); 238 // Do nothing further 239 mPMCStatusLogger.logStatus("READY"); 240 mPMCStatusLogger.logStatus("SUCCEED"); 241 return; 242 } 243 244 if (!extras.containsKey("PlayTime")) { 245 Log.e(TAG, "No Play Time specified"); 246 return; 247 } 248 tmpStr = extras.getString("PlayTime"); 249 Log.d(TAG, "Play Time = " + tmpStr); 250 playTime = Integer.valueOf(tmpStr); 251 252 if (!extras.containsKey("MusicURL")) { 253 Log.e(TAG, "No Music URL specified"); 254 return; 255 } 256 musicUrl = extras.getString("MusicURL"); 257 Log.d(TAG, "Music URL = " + musicUrl); 258 259 // playTime and musicUrl are necessary 260 if (playTime == 0 || musicUrl.isEmpty() || musicUrl == null) { 261 Log.d(TAG, "Invalid paramters"); 262 return; 263 } 264 // Check if it is the baseline that Bluetooth is off but streaming with speakers muted 265 if (!bt_off_mute) { 266 if (!extras.containsKey("CodecType")) { 267 Log.e(TAG, "No Codec Type specified"); 268 return; 269 } 270 tmpStr = extras.getString("CodecType"); 271 Log.d(TAG, "Codec Type= " + tmpStr); 272 codecType = Integer.valueOf(tmpStr); 273 274 if (!extras.containsKey("SampleRate")) { 275 Log.e(TAG, "No Sample Rate specified"); 276 return; 277 } 278 tmpStr = extras.getString("SampleRate"); 279 Log.d(TAG, "Sample Rate = " + tmpStr); 280 sampleRate = Integer.valueOf(tmpStr); 281 282 if (!extras.containsKey("BitsPerSample")) { 283 Log.e(TAG, "No BitsPerSample specified"); 284 return; 285 } 286 tmpStr = extras.getString("BitsPerSample"); 287 Log.d(TAG, "BitsPerSample = " + tmpStr); 288 bitsPerSample = Integer.valueOf(tmpStr); 289 290 if (extras.containsKey("ChannelMode")) { 291 tmpStr = extras.getString("ChannelMode"); 292 Log.d(TAG, "ChannelMode = " + tmpStr); 293 channelMode = Integer.valueOf(tmpStr); 294 } 295 296 if (extras.containsKey("LdacPlaybackQuality")) { 297 tmpStr = extras.getString("LdacPlaybackQuality"); 298 Log.d(TAG, "LdacPlaybackQuality = " + tmpStr); 299 codecSpecific1 = Integer.valueOf(tmpStr); 300 } 301 302 if (extras.containsKey("CodecSpecific2")) { 303 tmpStr = extras.getString("CodecSpecific2"); 304 Log.d(TAG, "CodecSpecific2 = " + tmpStr); 305 codecSpecific1 = Integer.valueOf(tmpStr); 306 } 307 308 if (extras.containsKey("CodecSpecific3")) { 309 tmpStr = extras.getString("CodecSpecific3"); 310 Log.d(TAG, "CodecSpecific3 = " + tmpStr); 311 codecSpecific1 = Integer.valueOf(tmpStr); 312 } 313 314 if (extras.containsKey("CodecSpecific4")) { 315 tmpStr = extras.getString("CodecSpecific4"); 316 Log.d(TAG, "CodecSpecific4 = " + tmpStr); 317 codecSpecific1 = Integer.valueOf(tmpStr); 318 } 319 320 if (codecType == BluetoothCodecConfig.SOURCE_CODEC_TYPE_INVALID 321 || sampleRate == BluetoothCodecConfig.SAMPLE_RATE_NONE 322 || bitsPerSample == BluetoothCodecConfig.BITS_PER_SAMPLE_NONE) { 323 Log.d(TAG, "Invalid parameters"); 324 return; 325 } 326 } 327 328 if (playMusic(musicUrl, bt_off_mute)) { 329 // Set the requested Codecs on the device for normal codec cases 330 if (!bt_off_mute) { 331 if (!setCodecValue(codecType, sampleRate, bitsPerSample, channelMode, 332 codecSpecific1, codecSpecific2, codecSpecific3, codecSpecific4)) { 333 mPMCStatusLogger.logStatus("setCodecValue() Failed"); 334 } 335 } 336 mPMCStatusLogger.logStatus("READY"); 337 startAlarm(playTime); 338 } else { 339 mPMCStatusLogger.logStatus("playMusic() Failed"); 340 } 341 } 342 343 344 /** 345 * Function to setup MediaPlayer and play music 346 * 347 * @param musicURL - Music URL 348 * @param btOffMute - true is to mute speakers 349 * 350 */ playMusic(String musicURL, boolean btOffMute)351 private boolean playMusic(String musicURL, boolean btOffMute) { 352 353 mPlayer = MediaPlayer.create(mContext, Uri.parse(musicURL)); 354 if (mPlayer == null) { 355 Log.e(TAG, "Failed to create Media Player"); 356 return false; 357 } 358 Log.d(TAG, "Media Player created: " + musicURL); 359 360 if (btOffMute) { 361 Log.v(TAG, "Mute Speakers for Bluetooth off baseline case"); 362 mPlayer.setVolume(ZERO_VOLUME, ZERO_VOLUME); 363 } else { 364 Log.d(TAG, "Set Normal Volume for speakers"); 365 mPlayer.setVolume(NORMAL_VOLUME, NORMAL_VOLUME); 366 } 367 // Play Music now and setup looping 368 mPlayer.start(); 369 mPlayer.setLooping(true); 370 if (!mPlayer.isPlaying()) { 371 Log.e(TAG, "Media Player is not playing"); 372 return false; 373 } 374 375 return true; 376 } 377 378 /** 379 * Function to be called to start alarm 380 * 381 * @param alarmStartTime - time when the music needs to be started or stopped 382 */ startAlarm(int alarmStartTime)383 private void startAlarm(int alarmStartTime) { 384 385 Intent alarmIntent = new Intent(A2DP_INTENT); 386 alarmIntent.putExtra(A2DP_ALARM, ALARM_MESSAGE); 387 388 long triggerTime = SystemClock.elapsedRealtime() 389 + alarmStartTime * THOUSAND; 390 mAlarmManager.setExactAndAllowWhileIdle( 391 AlarmManager.ELAPSED_REALTIME_WAKEUP, triggerTime, 392 PendingIntent.getBroadcast(mContext, 0, 393 alarmIntent, PendingIntent.FLAG_UPDATE_CURRENT)); 394 } 395 396 /** 397 * Function to get current codec config 398 * @param printCapabilities - Flag to indicate if to print local and selectable capabilities 399 */ getCodecValue(boolean printCapabilities)400 private BluetoothCodecConfig getCodecValue(boolean printCapabilities) { 401 BluetoothCodecStatus codecStatus = null; 402 BluetoothCodecConfig codecConfig = null; 403 List<BluetoothCodecConfig> codecsLocalCapabilities = new ArrayList<>(); 404 List<BluetoothCodecConfig> codecsSelectableCapabilities = new ArrayList<>(); 405 406 if (mBluetoothA2dp != null) { 407 BluetoothDevice activeDevice = getA2dpActiveDevice(); 408 if (activeDevice == null) { 409 Log.e(TAG, "getCodecValue: Active device is null"); 410 return null; 411 } 412 codecStatus = mBluetoothA2dp.getCodecStatus(activeDevice); 413 if (codecStatus != null) { 414 codecConfig = codecStatus.getCodecConfig(); 415 codecsLocalCapabilities = codecStatus.getCodecsLocalCapabilities(); 416 codecsSelectableCapabilities = codecStatus.getCodecsSelectableCapabilities(); 417 } 418 } 419 if (codecConfig == null) return null; 420 421 Log.d(TAG, "GetCodecValue: " + codecConfig.toString()); 422 423 if (printCapabilities) { 424 Log.d(TAG, "Local Codec Capabilities "); 425 for (BluetoothCodecConfig config : codecsLocalCapabilities) { 426 Log.d(TAG, config.toString()); 427 } 428 Log.d(TAG, "Codec Selectable Capabilities: "); 429 for (BluetoothCodecConfig config : codecsSelectableCapabilities) { 430 Log.d(TAG, config.toString()); 431 } 432 } 433 return codecConfig; 434 } 435 436 /** 437 * Function to set new codec config 438 * 439 * @param codecType - Codec Type 440 * @param sampleRate - Sample Rate 441 * @param bitsPerSample - Bit Per Sample 442 * @param codecSpecific1 - LDAC playback quality 443 * @param codecSpecific2 - codecSpecific2 444 * @param codecSpecific3 - codecSpecific3 445 * @param codecSpecific4 - codecSpecific4 446 */ setCodecValue(int codecType, int sampleRate, int bitsPerSample, int channelMode, long codecSpecific1, long codecSpecific2, long codecSpecific3, long codecSpecific4)447 private boolean setCodecValue(int codecType, int sampleRate, int bitsPerSample, 448 int channelMode, long codecSpecific1, long codecSpecific2, 449 long codecSpecific3, long codecSpecific4) { 450 Log.d(TAG, "SetCodecValue: Codec Type: " + codecType + " sampleRate: " + sampleRate 451 + " bitsPerSample: " + bitsPerSample + " Channel Mode: " + channelMode 452 + " LDAC quality: " + codecSpecific1); 453 454 BluetoothCodecConfig codecConfig = new BluetoothCodecConfig.Builder() 455 .setCodecType(codecType) 456 .setCodecPriority(BluetoothCodecConfig.CODEC_PRIORITY_HIGHEST) 457 .setSampleRate(sampleRate) 458 .setBitsPerSample(bitsPerSample) 459 .setChannelMode(channelMode) 460 .setCodecSpecific1(codecSpecific1) 461 .setCodecSpecific2(codecSpecific2) 462 .setCodecSpecific3(codecSpecific3) 463 .setCodecSpecific4(codecSpecific4) 464 .build(); 465 466 // Wait here to see if mBluetoothA2dp is set 467 for (int i = 0; i < WAIT_SECONDS; i++) { 468 Log.d(TAG, "Wait for BluetoothA2dp"); 469 if (mBluetoothA2dp != null) { 470 break; 471 } 472 473 try { 474 Thread.sleep(THOUSAND); 475 } catch (InterruptedException e) { 476 Log.d(TAG, "Sleep is interrupted"); 477 } 478 } 479 480 if (mBluetoothA2dp != null) { 481 BluetoothDevice activeDevice = getA2dpActiveDevice(); 482 if (activeDevice == null) { 483 Log.e(TAG, "setCodecValue: Active device is null. Codec is not set."); 484 return false; 485 } 486 Log.d(TAG, "setCodecConfigPreference()"); 487 mBluetoothA2dp.setCodecConfigPreference(getA2dpActiveDevice(), codecConfig); 488 } else { 489 Log.e(TAG, "mBluetoothA2dp is null. Codec is not set"); 490 return false; 491 } 492 // Wait here to see if the codec is changed to new value 493 for (int i = 0; i < WAIT_SECONDS; i++) { 494 if (verifyCodeConfig(codecType, sampleRate, 495 bitsPerSample, channelMode, codecSpecific1)) { 496 break; 497 } 498 try { 499 Thread.sleep(THOUSAND); 500 } catch (InterruptedException e) { 501 Log.d(TAG, "Sleep is interrupted"); 502 } 503 } 504 if (!verifyCodeConfig(codecType, sampleRate, 505 bitsPerSample, channelMode, codecSpecific1)) { 506 Log.e(TAG, "Codec config is NOT set correctly"); 507 return false; 508 } 509 return true; 510 } 511 getA2dpActiveDevice()512 private BluetoothDevice getA2dpActiveDevice() { 513 if (mBluetoothAdapter == null) { 514 return null; 515 } 516 List<BluetoothDevice> activeDevices = 517 mBluetoothAdapter.getActiveDevices(BluetoothProfile.A2DP); 518 return (activeDevices.size() > 0) ? activeDevices.get(0) : null; 519 } 520 521 /** 522 * Method to verify if the codec config values are changed 523 * 524 * @param codecType - Codec Type 525 * @param sampleRate - Sample Rate 526 * @param bitsPerSample - Bit Per Sample 527 * @param codecSpecific1 - LDAC playback quality 528 */ verifyCodeConfig(int codecType, int sampleRate, int bitsPerSample, int channelMode, long codecSpecific1)529 private boolean verifyCodeConfig(int codecType, int sampleRate, int bitsPerSample, 530 int channelMode, long codecSpecific1) { 531 BluetoothCodecConfig codecConfig = null; 532 codecConfig = getCodecValue(false); 533 if (codecConfig == null) return false; 534 535 if (codecType == BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC) { 536 if (codecConfig.getCodecType() == codecType 537 && codecConfig.getSampleRate() == sampleRate 538 && codecConfig.getBitsPerSample() == bitsPerSample 539 && codecConfig.getChannelMode() == channelMode 540 && codecConfig.getCodecSpecific1() == codecSpecific1) return true; 541 } else { 542 if (codecConfig.getCodecType() == codecType 543 && codecConfig.getSampleRate() == sampleRate 544 && codecConfig.getBitsPerSample() == bitsPerSample 545 && codecConfig.getChannelMode() == channelMode) return true; 546 } 547 548 return false; 549 } 550 551 } 552