Home | History | Annotate | Download | only in pmc
      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      */
    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      */
    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
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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