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