blob: 7c10157145eba9e2347c01ac305f60dfe2f44dc0 [file] [log] [blame]
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.pmc;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.bluetooth.BluetoothA2dp;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothCodecConfig;
import android.bluetooth.BluetoothCodecStatus;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.media.MediaPlayer;
import android.net.Uri;
import android.os.Bundle;
import android.os.SystemClock;
import android.util.Log;
import java.util.ArrayList;
/**
* Bluetooth A2DP Receiver functions for codec power testing.
*/
public class A2dpReceiver extends BroadcastReceiver {
public static final String TAG = "A2DPPOWER";
public static final String A2DP_INTENT = "com.android.pmc.A2DP";
public static final String A2DP_ALARM = "com.android.pmc.A2DP.Alarm";
public static final String A2DP_ACTION = "com.android.pmc.A2DP.Action";
public static final String CURRENT_ALARM = "com.android.pmc.A2DP.CurrentAlarm";
public static final int ALARM_MESSAGE = 1;
public static final int THOUSAND = 1000;
public static final int WAIT_SECONDS = 10;
public static final int START_PLAY = 1;
public static final int PAUSE_PLAY = 2;
public static final int STOP_PLAY = 3;
public static final float NORMAL_VOLUME = 0.3f;
public static final float ZERO_VOLUME = 0.0f;
private final Context mContext;
private final AlarmManager mAlarmManager;
private int mPlayTime;
private int mIdleTime;
private int mNumAlarms;
private MediaPlayer mPlayer;
private BluetoothAdapter mBluetoothAdapter;
private BluetoothA2dp mBluetoothA2dp;
// For a baseline case when music is not play but BT is enabled.
private boolean mNotPlay = false;
// For a baseline case when BT is off but music is playing with speaker is muted
private boolean mMute = false;
/**
* BroadcastReceiver() to get status after calling setCodecConfigPreference()
*
*/
private BroadcastReceiver mBluetoothA2dpReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
Log.d(TAG, "mBluetoothA2dpReceiver.onReceive() intent=" + intent);
String action = intent.getAction();
if (BluetoothA2dp.ACTION_CODEC_CONFIG_CHANGED.equals(action)) {
getCodecValue(true);
}
}
};
/**
* ServiceListener for A2DP connection/disconnection event
*
*/
private BluetoothProfile.ServiceListener mBluetoothA2dpServiceListener =
new BluetoothProfile.ServiceListener() {
public void onServiceConnected(int profile,
BluetoothProfile proxy) {
Log.d(TAG, "BluetoothA2dpServiceListener.onServiceConnected");
mBluetoothA2dp = (BluetoothA2dp) proxy;
getCodecValue(true);
}
public void onServiceDisconnected(int profile) {
Log.d(TAG, "BluetoothA2dpServiceListener.onServiceDisconnected");
mBluetoothA2dp = null;
}
};
/**
* Constructor to be called by PMC
*
* @param context - PMC will provide a context
* @param alarmManager - PMC will provide alarmManager
*/
public A2dpReceiver(Context context, AlarmManager alarmManager) {
// Prepare for setting alarm service
mContext = context;
mAlarmManager = alarmManager;
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
if (mBluetoothAdapter == null) {
Log.e(TAG, "BluetoothAdapter is Null");
return;
} else {
if (!mBluetoothAdapter.isEnabled()) {
Log.d(TAG, "BluetoothAdapter is NOT enabled, enable now");
mBluetoothAdapter.enable();
if (!mBluetoothAdapter.isEnabled()) {
Log.e(TAG, "Can't enable Bluetooth");
return;
}
}
}
// Setup BroadcastReceiver for ACTION_CODEC_CONFIG_CHANGED
IntentFilter filter = new IntentFilter();
if (mBluetoothAdapter != null) {
mBluetoothAdapter.getProfileProxy(context,
mBluetoothA2dpServiceListener,
BluetoothProfile.A2DP);
}
filter = new IntentFilter();
filter.addAction(BluetoothA2dp.ACTION_CODEC_CONFIG_CHANGED);
context.registerReceiver(mBluetoothA2dpReceiver, filter);
}
/**
* Method to receive the broadcast from Python client or AlarmManager
*
* @param context - system will provide a context to this function
* @param intent - system will provide an intent to this function
*/
@Override
public void onReceive(Context context, Intent intent) {
if (!intent.getAction().equals(A2DP_INTENT)) return;
boolean alarm = intent.hasExtra(A2DP_ALARM);
if (alarm) {
Log.d(TAG, "Received Alarm broadcast message");
handleAlarm(intent);
} else {
Log.d(TAG, "Received PMC command message");
processParameters(intent);
}
}
/**
* Method to process parameters from Python client
*
* @param intent - system will provide an intent to this function
*/
private void processParameters(Intent intent) {
int codecType = BluetoothCodecConfig.SOURCE_CODEC_TYPE_INVALID;
int sampleRate = BluetoothCodecConfig.SAMPLE_RATE_NONE;
int bitsPerSample = BluetoothCodecConfig.BITS_PER_SAMPLE_NONE;
int channelMode = BluetoothCodecConfig.CHANNEL_MODE_STEREO;
// codecSpecific1 is for LDAC quality so far
// Other code specific values are not used now
long codecSpecific1 = 0, codecSpecific2 = 0, codecSpecific3 = 0,
codecSpecific4 = 0;
int startTime = 0, playTime = 0, idleTime = 0, repetitions = 1;
String musicUrl;
String tmpStr;
// Reset these values for each test cases
// For a baseline case when music is not play but BT is enabled.
mNotPlay = false;
// For a baseline case when BT is off but music is playing with speaker is muted
mMute = false;
Bundle extras = intent.getExtras();
if (extras == null) {
Log.e(TAG, "No parameters specified");
return;
}
if (!extras.containsKey("CodecType")) {
Log.e(TAG, "No Codec Type specified");
return;
}
tmpStr = extras.getString("CodecType");
Log.d(TAG, "Codec Type= " + tmpStr);
codecType = Integer.valueOf(tmpStr);
if (!extras.containsKey("SampleRate")) {
Log.e(TAG, "No Sample Rate specified");
return;
}
tmpStr = extras.getString("SampleRate");
Log.d(TAG, "Sample Rate = " + tmpStr);
sampleRate = Integer.valueOf(tmpStr);
if (!extras.containsKey("BitsPerSample")) {
Log.e(TAG, "No BitsPerSample specified");
return;
}
tmpStr = extras.getString("BitsPerSample");
Log.d(TAG, "BitsPerSample = " + tmpStr);
bitsPerSample = Integer.valueOf(tmpStr);
if (extras.containsKey("ChannelMode")) {
tmpStr = extras.getString("ChannelMode");
Log.d(TAG, "ChannelMode = " + tmpStr);
channelMode = Integer.valueOf(tmpStr);
}
if (extras.containsKey("LdacPlaybackQuality")) {
tmpStr = extras.getString("LdacPlaybackQuality");
Log.d(TAG, "LdacPlaybackQuality = " + tmpStr);
codecSpecific1 = Integer.valueOf(tmpStr);
}
if (extras.containsKey("CodecSpecific2")) {
tmpStr = extras.getString("CodecSpecific2");
Log.d(TAG, "CodecSpecific2 = " + tmpStr);
codecSpecific1 = Integer.valueOf(tmpStr);
}
if (extras.containsKey("CodecSpecific3")) {
tmpStr = extras.getString("CodecSpecific3");
Log.d(TAG, "CodecSpecific3 = " + tmpStr);
codecSpecific1 = Integer.valueOf(tmpStr);
}
if (extras.containsKey("CodecSpecific4")) {
tmpStr = extras.getString("CodecSpecific4");
Log.d(TAG, "CodecSpecific4 = " + tmpStr);
codecSpecific1 = Integer.valueOf(tmpStr);
}
if (!extras.containsKey("StartTime")) {
Log.e(TAG, "No Start Time specified");
return;
}
tmpStr = extras.getString("StartTime");
Log.d(TAG, "Start Time = " + tmpStr);
startTime = Integer.valueOf(tmpStr);
if (!extras.containsKey("PlayTime")) {
Log.e(TAG, "No Play Time specified");
return;
}
tmpStr = extras.getString("PlayTime");
Log.d(TAG, "Play Time = " + tmpStr);
playTime = Integer.valueOf(tmpStr);
if (extras.containsKey("IdleTime")) {
tmpStr = extras.getString("IdleTime");
Log.d(TAG, "Idle Time = " + tmpStr);
idleTime = Integer.valueOf(tmpStr);
}
if (extras.containsKey("Repetitions")) {
tmpStr = extras.getString("Repetitions");
Log.d(TAG, "Repetitions = " + tmpStr);
repetitions = Integer.valueOf(tmpStr);
}
if (!extras.containsKey("MusicURL")) {
Log.e(TAG, "No Music URL specified");
return;
}
musicUrl = extras.getString("MusicURL");
Log.d(TAG, "Music URL = " + musicUrl);
if (extras.containsKey("NotPlay")) {
Log.v(TAG, "NotPlay is specified for baseline case of only BT on");
mNotPlay = true;
}
if (extras.containsKey("Mute")) {
Log.v(TAG, "Mute is specified for BT off baseline case");
mMute = true;
}
if (playTime == 0 || startTime == 0 || musicUrl.isEmpty() || musicUrl == null
|| codecType == BluetoothCodecConfig.SOURCE_CODEC_TYPE_INVALID
|| sampleRate == BluetoothCodecConfig.SAMPLE_RATE_NONE
|| bitsPerSample == BluetoothCodecConfig.BITS_PER_SAMPLE_NONE) {
Log.d(TAG, "Invalid paramters");
return;
}
if (prepare(codecType, sampleRate, bitsPerSample, channelMode, codecSpecific1,
codecSpecific2, codecSpecific3, codecSpecific4,
playTime, idleTime, repetitions, musicUrl)) {
startAlarm(startTime, null);
}
}
/**
* Method to start or stop playing music when Alarm is triggered
*
* @param intent - system will provide an intent to this function
*/
private void handleAlarm(Intent intent) {
int action = intent.getIntExtra(A2DP_ACTION, 0);
Log.d(TAG, "handleAlarm() Action: " + action);
if (action == -1) {
Log.e(TAG, "Received Alarm with no Action");
return;
}
if (mPlayer == null) {
Log.e(TAG, "Media Player is null");
return;
}
if (mNotPlay) {
notPlayCase(intent, action);
return;
}
if (action == START_PLAY) {
Log.v(TAG, "Before Start Play");
mPlayer.start();
mPlayer.setLooping(true);
if (!mPlayer.isPlaying()) {
Log.e(TAG, "Media Player is not playing");
}
startAlarm(mPlayTime, intent);
} else if (action == PAUSE_PLAY) {
Log.v(TAG, "Before Pause play");
mPlayer.pause();
startAlarm(mIdleTime, intent);
} else if (action == STOP_PLAY) {
Log.v(TAG, "Before Stop play");
mPlayer.stop();
// Release the Media Player
mPlayer.release();
} else {
Log.e(TAG, "Unknown Action");
}
}
/**
* Method for baseline case "Not Play" when Alarm is triggered
*
* @param intent - system will provide an intent to this function
*/
private void notPlayCase(Intent intent, int action) {
if (action == START_PLAY) {
Log.v(TAG, "NotPlay case for PlAY action");
startAlarm(mPlayTime, intent);
} else if (action == PAUSE_PLAY) {
Log.v(TAG, "NotPlay case for PAUSE");
startAlarm(mIdleTime, intent);
} else if (action == STOP_PLAY) {
Log.v(TAG, "NotPlay case for Stop play");
// Release the Media Player
mPlayer.release();
} else {
Log.e(TAG, "Unknown Action");
}
}
/**
* Method to verify if the codec config values are changed
*
* @param codecType - Codec Type
* @param sampleRate - Sample Rate
* @param bitsPerSample - Bit Per Sample
* @param codecSpecific1 - LDAC playback quality
*/
private boolean verifyCodeConfig(int codecType, int sampleRate, int bitsPerSample,
int channelMode, long codecSpecific1) {
BluetoothCodecConfig codecConfig = null;
codecConfig = getCodecValue(false);
if (codecConfig == null) return false;
if (codecType == BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC) {
if (codecConfig.getCodecType() == codecType
&& codecConfig.getSampleRate() == sampleRate
&& codecConfig.getBitsPerSample() == bitsPerSample
&& codecConfig.getChannelMode() == channelMode
&& codecConfig.getCodecSpecific1() == codecSpecific1) return true;
} else {
if (codecConfig.getCodecType() == codecType
&& codecConfig.getSampleRate() == sampleRate
&& codecConfig.getBitsPerSample() == bitsPerSample
&& codecConfig.getChannelMode() == channelMode) return true;
}
return false;
}
/**
* Function to setup MediaPlayer
*
* @param codecType - Codec Type
* @param sampleRate - Sample Rate
* @param bitsPerSample - Bit Per Sample
* @param codecSpecific1 - LDAC playback quality
* @param codecSpecific2 - codecSpecific2
* @param codecSpecific3 - codecSpecific3
* @param codecSpecific4 - codecSpecific4
* @param playTime - Music playing duration
* @param idleTime - Pause time for music
* @param repetitions - number of cycles of play/pause
* @param musicURL - Music URL
*
*/
private boolean prepare(int codecType, int sampleRate, int bitsPerSample, int channelMode,
long codecSpecific1, long codecSpecific2, long codecSpecific3,
long codecSpecific4,
int playTime, int idleTime, int repetitions, String musicURL) {
ArrayList<BluetoothDevice> results = new ArrayList<BluetoothDevice>();
// When mMute is specific Bluetooth will be disabled by Python script
if (!mMute) {
for (BluetoothDevice bd : mBluetoothAdapter.getBondedDevices()) {
if (bd.isConnected()) {
results.add(bd);
}
}
if (results.isEmpty()) {
Log.e(TAG, "No device is connected");
return false;
}
// Set the requested Codecs on the device
setCodecValue(codecType, sampleRate, bitsPerSample, channelMode, codecSpecific1,
codecSpecific2, codecSpecific3, codecSpecific4);
// Wait here to see if the codec is changed to new value
for (int i = 0; i < WAIT_SECONDS; i++) {
if (verifyCodeConfig(codecType, sampleRate,
bitsPerSample, channelMode, codecSpecific1)) {
break;
}
try {
Thread.sleep(THOUSAND);
} catch (InterruptedException e) {
Log.d(TAG, "Sleep is interrupted");
}
}
if (!verifyCodeConfig(codecType, sampleRate,
bitsPerSample, channelMode, codecSpecific1)) {
Log.e(TAG, "Codec config is NOT set correctly");
return false;
}
}
mPlayTime = playTime;
mIdleTime = idleTime;
mNumAlarms = repetitions * 2;
mPlayer = MediaPlayer.create(mContext, Uri.parse(musicURL));
if (mPlayer == null) {
Log.e(TAG, "Failed to create Media Player");
return false;
}
Log.d(TAG, "Media Player created: " + musicURL);
if (mMute) {
Log.v(TAG, "Mute Speakers for BT off baseline case");
mPlayer.setVolume(ZERO_VOLUME, ZERO_VOLUME);
} else {
Log.d(TAG, "Set Normal Volume for speakers");
mPlayer.setVolume(NORMAL_VOLUME, NORMAL_VOLUME);
}
return true;
}
/**
* Function to be called to start alarm
*
* @param alarmStartTime - time when the music needs to be started or stopped
* @param intent - intent to content alarm number and current action
*/
public void startAlarm(int alarmStartTime, Intent intent) {
int currentAlarm = 0;
int nextAction = START_PLAY;
int currentAction = 0;
if (intent != null) {
if (currentAlarm >= mNumAlarms) {
Log.d(TAG, "All alarms are done");
return;
}
// Get alarm number inside the intent
currentAlarm = intent.getIntExtra(CURRENT_ALARM, 0);
currentAction = intent.getIntExtra(A2DP_ACTION, 0);
if (currentAlarm == mNumAlarms - 1) {
// last alarm
nextAction = STOP_PLAY;
} else if (currentAction == START_PLAY) {
nextAction = PAUSE_PLAY;
} else {
nextAction = START_PLAY;
}
}
Log.d(TAG, "Current Alarm: " + currentAlarm + " Action: " + currentAction
+ " trigger time: " + alarmStartTime);
Intent alarmIntent = new Intent(A2DP_INTENT);
alarmIntent.putExtra(A2DP_ALARM, ALARM_MESSAGE);
alarmIntent.putExtra(A2DP_ACTION, nextAction);
alarmIntent.putExtra(CURRENT_ALARM, ++currentAlarm);
long triggerTime = SystemClock.elapsedRealtime()
+ alarmStartTime * THOUSAND;
mAlarmManager.setExactAndAllowWhileIdle(
AlarmManager.ELAPSED_REALTIME_WAKEUP, triggerTime,
PendingIntent.getBroadcast(mContext, 0,
alarmIntent, PendingIntent.FLAG_UPDATE_CURRENT));
}
/**
* Function to get current codec config
* @param printCapabilities - Flag to indicate if to print local and selectable capabilities
*/
private BluetoothCodecConfig getCodecValue(boolean printCapabilities) {
BluetoothCodecStatus codecStatus = null;
BluetoothCodecConfig codecConfig = null;
BluetoothCodecConfig[] codecsLocalCapabilities = null;
BluetoothCodecConfig[] codecsSelectableCapabilities = null;
// In the mute case BT is off there will be not Codec Info available
if (mMute) return null;
if (mBluetoothA2dp != null) {
codecStatus = mBluetoothA2dp.getCodecStatus();
if (codecStatus != null) {
codecConfig = codecStatus.getCodecConfig();
codecsLocalCapabilities = codecStatus.getCodecsLocalCapabilities();
codecsSelectableCapabilities = codecStatus.getCodecsSelectableCapabilities();
}
}
if (codecConfig == null) return null;
Log.d(TAG, "GetCodecValue: " + codecConfig.toString());
if (printCapabilities) {
Log.d(TAG, "Local Codec Capabilities ");
for (BluetoothCodecConfig config : codecsLocalCapabilities) {
Log.d(TAG, config.toString());
}
Log.d(TAG, "Codec Selectable Capabilities: ");
for (BluetoothCodecConfig config : codecsSelectableCapabilities) {
Log.d(TAG, config.toString());
}
}
return codecConfig;
}
/**
* Function to set new codec config
*
* @param codecType - Codec Type
* @param sampleRate - Sample Rate
* @param bitsPerSample - Bit Per Sample
* @param codecSpecific1 - LDAC playback quality
* @param codecSpecific2 - codecSpecific2
* @param codecSpecific3 - codecSpecific3
* @param codecSpecific4 - codecSpecific4
*/
private void setCodecValue(int codecType, int sampleRate, int bitsPerSample,
int channelMode, long codecSpecific1, long codecSpecific2,
long codecSpecific3, long codecSpecific4) {
Log.d(TAG, "SetCodecValue: Codec Type: " + codecType + " sampleRate: " + sampleRate
+ " bitsPerSample: " + bitsPerSample + " Channel Mode: " + channelMode
+ " LDAC quality: " + codecSpecific1);
// In the mute case BT is off there will be not Codec Info available
if (mMute) return;
BluetoothCodecConfig codecConfig =
new BluetoothCodecConfig(codecType, BluetoothCodecConfig.CODEC_PRIORITY_HIGHEST,
sampleRate, bitsPerSample, channelMode,
codecSpecific1, codecSpecific2, codecSpecific3, codecSpecific4);
if (mBluetoothA2dp != null) {
Log.d(TAG, "setCodecConfigPreference()");
mBluetoothA2dp.setCodecConfigPreference(codecConfig);
} else {
Log.e(TAG, "mBluetoothA2dp is null. Codec is not set");
}
}
}