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