• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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