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 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 */ setCodecValue(int codecType, int sampleRate, int bitsPerSample, int channelMode, long codecSpecific1, long codecSpecific2, long codecSpecific3, long codecSpecific4)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 */ verifyCodeConfig(int codecType, int sampleRate, int bitsPerSample, int channelMode, long codecSpecific1)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