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 mPMCStatusLogger = new PMCStatusLogger(TAG + ".log", TAG); 146 147 // Check if any Bluetooth devices are connected 148 ArrayList<BluetoothDevice> results = new ArrayList<BluetoothDevice>(); 149 Set<BluetoothDevice> bondedDevices = mBluetoothAdapter.getBondedDevices(); 150 if (bondedDevices == null) { 151 Log.e(TAG, "Bonded devices list is null"); 152 return false; 153 } 154 for (BluetoothDevice bd : bondedDevices) { 155 if (bd.isConnected()) { 156 results.add(bd); 157 } 158 } 159 160 if (results.isEmpty()) { 161 Log.e(TAG, "No device is connected"); 162 return false; 163 } 164 165 Log.d(TAG, "Finish initialize()"); 166 167 return true; 168 } 169 170 /** 171 * Method to receive the broadcast from Python client or AlarmManager 172 * 173 * @param context - system will provide a context to this function 174 * @param intent - system will provide an intent to this function 175 */ 176 @Override onReceive(Context context, Intent intent)177 public void onReceive(Context context, Intent intent) { 178 if (!intent.getAction().equals(A2DP_INTENT)) return; 179 boolean alarm = intent.hasExtra(A2DP_ALARM); 180 if (alarm) { 181 Log.v(TAG, "Alarm Message to Stop playing"); 182 mPMCStatusLogger.logStatus("SUCCEED"); 183 mPlayer.stop(); 184 // Release the Media Player 185 mPlayer.release(); 186 } else { 187 Log.d(TAG, "Received PMC command message"); 188 processParameters(intent); 189 } 190 } 191 192 /** 193 * Method to process parameters from Python client 194 * 195 * @param intent - system will provide an intent to this function 196 */ processParameters(Intent intent)197 private void processParameters(Intent intent) { 198 int codecType = BluetoothCodecConfig.SOURCE_CODEC_TYPE_INVALID; 199 int sampleRate = BluetoothCodecConfig.SAMPLE_RATE_NONE; 200 int bitsPerSample = BluetoothCodecConfig.BITS_PER_SAMPLE_NONE; 201 int channelMode = BluetoothCodecConfig.CHANNEL_MODE_STEREO; 202 // codecSpecific1 is for LDAC quality so far 203 // Other code specific values are not used now 204 long codecSpecific1 = 0, codecSpecific2 = 0, codecSpecific3 = 0, 205 codecSpecific4 = 0; 206 int playTime = 0; 207 String musicUrl; 208 String tmpStr; 209 210 // For a baseline case when Blueooth is off but music is playing with speaker is muted 211 boolean bt_off_mute = false; 212 213 Bundle extras = intent.getExtras(); 214 215 if (extras == null) { 216 Log.e(TAG, "No parameters specified"); 217 return; 218 } 219 // Always initialize() 220 if (!initialize()) { 221 mPMCStatusLogger.logStatus("initialize() Failed"); 222 return; 223 } 224 // Check if it is baseline Bluetooth is on but not stream 225 if (extras.containsKey("BT_ON_NotPlay")) { 226 Log.v(TAG, "NotPlay is specified for baseline case that only Bluetooth is on"); 227 // Do nothing further 228 mPMCStatusLogger.logStatus("READY"); 229 mPMCStatusLogger.logStatus("SUCCEED"); 230 return; 231 } 232 233 if (!extras.containsKey("PlayTime")) { 234 Log.e(TAG, "No Play Time specified"); 235 return; 236 } 237 tmpStr = extras.getString("PlayTime"); 238 Log.d(TAG, "Play Time = " + tmpStr); 239 playTime = Integer.valueOf(tmpStr); 240 241 if (!extras.containsKey("MusicURL")) { 242 Log.e(TAG, "No Music URL specified"); 243 return; 244 } 245 musicUrl = extras.getString("MusicURL"); 246 Log.d(TAG, "Music URL = " + musicUrl); 247 248 // playTime and musicUrl are necessary 249 if (playTime == 0 || musicUrl.isEmpty() || musicUrl == null) { 250 Log.d(TAG, "Invalid paramters"); 251 return; 252 } 253 // Check if it is the baseline that Bluetooth is off but streaming with speakers muted 254 if (extras.containsKey("BT_OFF_Mute")) { 255 Log.v(TAG, "Mute is specified for Bluetooth off baseline case"); 256 bt_off_mute = true; 257 } else { 258 259 if (!extras.containsKey("CodecType")) { 260 Log.e(TAG, "No Codec Type specified"); 261 return; 262 } 263 tmpStr = extras.getString("CodecType"); 264 Log.d(TAG, "Codec Type= " + tmpStr); 265 codecType = Integer.valueOf(tmpStr); 266 267 if (!extras.containsKey("SampleRate")) { 268 Log.e(TAG, "No Sample Rate specified"); 269 return; 270 } 271 tmpStr = extras.getString("SampleRate"); 272 Log.d(TAG, "Sample Rate = " + tmpStr); 273 sampleRate = Integer.valueOf(tmpStr); 274 275 if (!extras.containsKey("BitsPerSample")) { 276 Log.e(TAG, "No BitsPerSample specified"); 277 return; 278 } 279 tmpStr = extras.getString("BitsPerSample"); 280 Log.d(TAG, "BitsPerSample = " + tmpStr); 281 bitsPerSample = Integer.valueOf(tmpStr); 282 283 if (extras.containsKey("ChannelMode")) { 284 tmpStr = extras.getString("ChannelMode"); 285 Log.d(TAG, "ChannelMode = " + tmpStr); 286 channelMode = Integer.valueOf(tmpStr); 287 } 288 289 if (extras.containsKey("LdacPlaybackQuality")) { 290 tmpStr = extras.getString("LdacPlaybackQuality"); 291 Log.d(TAG, "LdacPlaybackQuality = " + tmpStr); 292 codecSpecific1 = Integer.valueOf(tmpStr); 293 } 294 295 if (extras.containsKey("CodecSpecific2")) { 296 tmpStr = extras.getString("CodecSpecific2"); 297 Log.d(TAG, "CodecSpecific2 = " + tmpStr); 298 codecSpecific1 = Integer.valueOf(tmpStr); 299 } 300 301 if (extras.containsKey("CodecSpecific3")) { 302 tmpStr = extras.getString("CodecSpecific3"); 303 Log.d(TAG, "CodecSpecific3 = " + tmpStr); 304 codecSpecific1 = Integer.valueOf(tmpStr); 305 } 306 307 if (extras.containsKey("CodecSpecific4")) { 308 tmpStr = extras.getString("CodecSpecific4"); 309 Log.d(TAG, "CodecSpecific4 = " + tmpStr); 310 codecSpecific1 = Integer.valueOf(tmpStr); 311 } 312 313 if (codecType == BluetoothCodecConfig.SOURCE_CODEC_TYPE_INVALID 314 || sampleRate == BluetoothCodecConfig.SAMPLE_RATE_NONE 315 || bitsPerSample == BluetoothCodecConfig.BITS_PER_SAMPLE_NONE) { 316 Log.d(TAG, "Invalid paramters"); 317 return; 318 } 319 } 320 321 if (playMusic(musicUrl, bt_off_mute)) { 322 // Set the requested Codecs on the device for normal codec cases 323 if (!bt_off_mute) { 324 if (!setCodecValue(codecType, sampleRate, bitsPerSample, channelMode, 325 codecSpecific1, codecSpecific2, codecSpecific3, codecSpecific4)) { 326 mPMCStatusLogger.logStatus("setCodecValue() Failed"); 327 } 328 } 329 mPMCStatusLogger.logStatus("READY"); 330 startAlarm(playTime); 331 } else { 332 mPMCStatusLogger.logStatus("playMusic() Failed"); 333 } 334 } 335 336 337 /** 338 * Function to setup MediaPlayer and play music 339 * 340 * @param musicURL - Music URL 341 * @param bt_off_mute - true is to mute speakers 342 * 343 */ playMusic(String musicURL, boolean btOffMute)344 private boolean playMusic(String musicURL, boolean btOffMute) { 345 346 mPlayer = MediaPlayer.create(mContext, Uri.parse(musicURL)); 347 if (mPlayer == null) { 348 Log.e(TAG, "Failed to create Media Player"); 349 return false; 350 } 351 Log.d(TAG, "Media Player created: " + musicURL); 352 353 if (btOffMute) { 354 Log.v(TAG, "Mute Speakers for Bluetooth off baseline case"); 355 mPlayer.setVolume(ZERO_VOLUME, ZERO_VOLUME); 356 } else { 357 Log.d(TAG, "Set Normal Volume for speakers"); 358 mPlayer.setVolume(NORMAL_VOLUME, NORMAL_VOLUME); 359 } 360 361 // Play Music now and setup looping 362 mPlayer.start(); 363 mPlayer.setLooping(true); 364 if (!mPlayer.isPlaying()) { 365 Log.e(TAG, "Media Player is not playing"); 366 return false; 367 } 368 369 return true; 370 } 371 372 /** 373 * Function to be called to start alarm 374 * 375 * @param alarmStartTime - time when the music needs to be started or stopped 376 */ startAlarm(int alarmStartTime)377 private void startAlarm(int alarmStartTime) { 378 379 Intent alarmIntent = new Intent(A2DP_INTENT); 380 alarmIntent.putExtra(A2DP_ALARM, ALARM_MESSAGE); 381 382 long triggerTime = SystemClock.elapsedRealtime() 383 + alarmStartTime * THOUSAND; 384 mAlarmManager.setExactAndAllowWhileIdle( 385 AlarmManager.ELAPSED_REALTIME_WAKEUP, triggerTime, 386 PendingIntent.getBroadcast(mContext, 0, 387 alarmIntent, PendingIntent.FLAG_UPDATE_CURRENT)); 388 } 389 390 /** 391 * Function to get current codec config 392 * @param printCapabilities - Flag to indicate if to print local and selectable capabilities 393 */ getCodecValue(boolean printCapabilities)394 private BluetoothCodecConfig getCodecValue(boolean printCapabilities) { 395 BluetoothCodecStatus codecStatus = null; 396 BluetoothCodecConfig codecConfig = null; 397 BluetoothCodecConfig[] codecsLocalCapabilities = null; 398 BluetoothCodecConfig[] codecsSelectableCapabilities = null; 399 400 if (mBluetoothA2dp != null) { 401 codecStatus = mBluetoothA2dp.getCodecStatus(); 402 if (codecStatus != null) { 403 codecConfig = codecStatus.getCodecConfig(); 404 codecsLocalCapabilities = codecStatus.getCodecsLocalCapabilities(); 405 codecsSelectableCapabilities = codecStatus.getCodecsSelectableCapabilities(); 406 } 407 } 408 if (codecConfig == null) return null; 409 410 Log.d(TAG, "GetCodecValue: " + codecConfig.toString()); 411 412 if (printCapabilities) { 413 Log.d(TAG, "Local Codec Capabilities "); 414 for (BluetoothCodecConfig config : codecsLocalCapabilities) { 415 Log.d(TAG, config.toString()); 416 } 417 Log.d(TAG, "Codec Selectable Capabilities: "); 418 for (BluetoothCodecConfig config : codecsSelectableCapabilities) { 419 Log.d(TAG, config.toString()); 420 } 421 } 422 return codecConfig; 423 } 424 425 /** 426 * Function to set new codec config 427 * 428 * @param codecType - Codec Type 429 * @param sampleRate - Sample Rate 430 * @param bitsPerSample - Bit Per Sample 431 * @param codecSpecific1 - LDAC playback quality 432 * @param codecSpecific2 - codecSpecific2 433 * @param codecSpecific3 - codecSpecific3 434 * @param codecSpecific4 - codecSpecific4 435 */ setCodecValue(int codecType, int sampleRate, int bitsPerSample, int channelMode, long codecSpecific1, long codecSpecific2, long codecSpecific3, long codecSpecific4)436 private boolean setCodecValue(int codecType, int sampleRate, int bitsPerSample, 437 int channelMode, long codecSpecific1, long codecSpecific2, 438 long codecSpecific3, long codecSpecific4) { 439 Log.d(TAG, "SetCodecValue: Codec Type: " + codecType + " sampleRate: " + sampleRate 440 + " bitsPerSample: " + bitsPerSample + " Channel Mode: " + channelMode 441 + " LDAC quality: " + codecSpecific1); 442 443 BluetoothCodecConfig codecConfig = 444 new BluetoothCodecConfig(codecType, BluetoothCodecConfig.CODEC_PRIORITY_HIGHEST, 445 sampleRate, bitsPerSample, channelMode, 446 codecSpecific1, codecSpecific2, codecSpecific3, codecSpecific4); 447 448 // Wait here to see if mBluetoothA2dp is set 449 for (int i = 0; i < WAIT_SECONDS; i++) { 450 Log.d(TAG, "Wait for BluetoothA2dp"); 451 if (mBluetoothA2dp != null) { 452 break; 453 } 454 455 try { 456 Thread.sleep(THOUSAND); 457 } catch (InterruptedException e) { 458 Log.d(TAG, "Sleep is interrupted"); 459 } 460 } 461 462 if (mBluetoothA2dp != null) { 463 Log.d(TAG, "setCodecConfigPreference()"); 464 mBluetoothA2dp.setCodecConfigPreference(codecConfig); 465 } else { 466 Log.e(TAG, "mBluetoothA2dp is null. Codec is not set"); 467 return false; 468 } 469 // Wait here to see if the codec is changed to new value 470 for (int i = 0; i < WAIT_SECONDS; i++) { 471 if (verifyCodeConfig(codecType, sampleRate, 472 bitsPerSample, channelMode, codecSpecific1)) { 473 break; 474 } 475 try { 476 Thread.sleep(THOUSAND); 477 } catch (InterruptedException e) { 478 Log.d(TAG, "Sleep is interrupted"); 479 } 480 } 481 if (!verifyCodeConfig(codecType, sampleRate, 482 bitsPerSample, channelMode, codecSpecific1)) { 483 Log.e(TAG, "Codec config is NOT set correctly"); 484 return false; 485 } 486 return true; 487 } 488 489 /** 490 * Method to verify if the codec config values are changed 491 * 492 * @param codecType - Codec Type 493 * @param sampleRate - Sample Rate 494 * @param bitsPerSample - Bit Per Sample 495 * @param codecSpecific1 - LDAC playback quality 496 */ verifyCodeConfig(int codecType, int sampleRate, int bitsPerSample, int channelMode, long codecSpecific1)497 private boolean verifyCodeConfig(int codecType, int sampleRate, int bitsPerSample, 498 int channelMode, long codecSpecific1) { 499 BluetoothCodecConfig codecConfig = null; 500 codecConfig = getCodecValue(false); 501 if (codecConfig == null) return false; 502 503 if (codecType == BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC) { 504 if (codecConfig.getCodecType() == codecType 505 && codecConfig.getSampleRate() == sampleRate 506 && codecConfig.getBitsPerSample() == bitsPerSample 507 && codecConfig.getChannelMode() == channelMode 508 && codecConfig.getCodecSpecific1() == codecSpecific1) return true; 509 } else { 510 if (codecConfig.getCodecType() == codecType 511 && codecConfig.getSampleRate() == sampleRate 512 && codecConfig.getBitsPerSample() == bitsPerSample 513 && codecConfig.getChannelMode() == channelMode) return true; 514 } 515 516 return false; 517 } 518 519 } 520