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 */ 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 */ 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 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 */ 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 */ 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 */ 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 */ 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 codecStatus = mBluetoothA2dp.getCodecStatus(null); // Use current active device 407 if (codecStatus != null) { 408 codecConfig = codecStatus.getCodecConfig(); 409 codecsLocalCapabilities = codecStatus.getCodecsLocalCapabilities(); 410 codecsSelectableCapabilities = codecStatus.getCodecsSelectableCapabilities(); 411 } 412 } 413 if (codecConfig == null) return null; 414 415 Log.d(TAG, "GetCodecValue: " + codecConfig.toString()); 416 417 if (printCapabilities) { 418 Log.d(TAG, "Local Codec Capabilities "); 419 for (BluetoothCodecConfig config : codecsLocalCapabilities) { 420 Log.d(TAG, config.toString()); 421 } 422 Log.d(TAG, "Codec Selectable Capabilities: "); 423 for (BluetoothCodecConfig config : codecsSelectableCapabilities) { 424 Log.d(TAG, config.toString()); 425 } 426 } 427 return codecConfig; 428 } 429 430 /** 431 * Function to set new codec config 432 * 433 * @param codecType - Codec Type 434 * @param sampleRate - Sample Rate 435 * @param bitsPerSample - Bit Per Sample 436 * @param codecSpecific1 - LDAC playback quality 437 * @param codecSpecific2 - codecSpecific2 438 * @param codecSpecific3 - codecSpecific3 439 * @param codecSpecific4 - codecSpecific4 440 */ 441 private boolean setCodecValue(int codecType, int sampleRate, int bitsPerSample, 442 int channelMode, long codecSpecific1, long codecSpecific2, 443 long codecSpecific3, long codecSpecific4) { 444 Log.d(TAG, "SetCodecValue: Codec Type: " + codecType + " sampleRate: " + sampleRate 445 + " bitsPerSample: " + bitsPerSample + " Channel Mode: " + channelMode 446 + " LDAC quality: " + codecSpecific1); 447 448 BluetoothCodecConfig codecConfig = 449 new BluetoothCodecConfig(codecType, BluetoothCodecConfig.CODEC_PRIORITY_HIGHEST, 450 sampleRate, bitsPerSample, channelMode, 451 codecSpecific1, codecSpecific2, codecSpecific3, codecSpecific4); 452 453 // Wait here to see if mBluetoothA2dp is set 454 for (int i = 0; i < WAIT_SECONDS; i++) { 455 Log.d(TAG, "Wait for BluetoothA2dp"); 456 if (mBluetoothA2dp != null) { 457 break; 458 } 459 460 try { 461 Thread.sleep(THOUSAND); 462 } catch (InterruptedException e) { 463 Log.d(TAG, "Sleep is interrupted"); 464 } 465 } 466 467 if (mBluetoothA2dp != null) { 468 Log.d(TAG, "setCodecConfigPreference()"); 469 mBluetoothA2dp.setCodecConfigPreference(null, codecConfig); // Use current active device 470 } else { 471 Log.e(TAG, "mBluetoothA2dp is null. Codec is not set"); 472 return false; 473 } 474 // Wait here to see if the codec is changed to new value 475 for (int i = 0; i < WAIT_SECONDS; i++) { 476 if (verifyCodeConfig(codecType, sampleRate, 477 bitsPerSample, channelMode, codecSpecific1)) { 478 break; 479 } 480 try { 481 Thread.sleep(THOUSAND); 482 } catch (InterruptedException e) { 483 Log.d(TAG, "Sleep is interrupted"); 484 } 485 } 486 if (!verifyCodeConfig(codecType, sampleRate, 487 bitsPerSample, channelMode, codecSpecific1)) { 488 Log.e(TAG, "Codec config is NOT set correctly"); 489 return false; 490 } 491 return true; 492 } 493 494 /** 495 * Method to verify if the codec config values are changed 496 * 497 * @param codecType - Codec Type 498 * @param sampleRate - Sample Rate 499 * @param bitsPerSample - Bit Per Sample 500 * @param codecSpecific1 - LDAC playback quality 501 */ 502 private boolean verifyCodeConfig(int codecType, int sampleRate, int bitsPerSample, 503 int channelMode, long codecSpecific1) { 504 BluetoothCodecConfig codecConfig = null; 505 codecConfig = getCodecValue(false); 506 if (codecConfig == null) return false; 507 508 if (codecType == BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC) { 509 if (codecConfig.getCodecType() == codecType 510 && codecConfig.getSampleRate() == sampleRate 511 && codecConfig.getBitsPerSample() == bitsPerSample 512 && codecConfig.getChannelMode() == channelMode 513 && codecConfig.getCodecSpecific1() == codecSpecific1) return true; 514 } else { 515 if (codecConfig.getCodecType() == codecType 516 && codecConfig.getSampleRate() == sampleRate 517 && codecConfig.getBitsPerSample() == bitsPerSample 518 && codecConfig.getChannelMode() == channelMode) return true; 519 } 520 521 return false; 522 } 523 524 } 525