• 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         // 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