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