summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Android.mk2
-rw-r--r--AndroidManifest.xml9
-rw-r--r--jni/com_android_bluetooth_avrcp.cpp3
-rwxr-xr-xjni/com_android_bluetooth_btservice_AdapterService.cpp25
-rw-r--r--res/values/config.xml5
-rw-r--r--res/xml/file_paths.xml3
-rw-r--r--src/com/android/bluetooth/Utils.java23
-rwxr-xr-xsrc/com/android/bluetooth/a2dp/A2dpService.java4
-rwxr-xr-xsrc/com/android/bluetooth/avrcp/Avrcp.java269
-rw-r--r--src/com/android/bluetooth/btservice/AdapterService.java58
-rw-r--r--src/com/android/bluetooth/btservice/BondStateMachine.java4
-rw-r--r--src/com/android/bluetooth/gatt/GattService.java15
-rw-r--r--src/com/android/bluetooth/gatt/ScanClient.java2
-rw-r--r--src/com/android/bluetooth/hfp/HeadsetStateMachine.java5
-rw-r--r--src/com/android/bluetooth/map/BluetoothMapContent.java2
-rw-r--r--src/com/android/bluetooth/map/BluetoothMapContentObserver.java3
-rwxr-xr-xsrc/com/android/bluetooth/map/BluetoothMapUtils.java7
-rw-r--r--src/com/android/bluetooth/opp/BluetoothOppUtility.java19
-rw-r--r--src/com/android/bluetooth/pbap/BluetoothPbapVcardManager.java4
-rw-r--r--src/com/android/bluetooth/sap/SapMessage.java191
-rw-r--r--src/com/android/bluetooth/sap/SapServer.java57
-rw-r--r--src/com/android/bluetooth/sap/SapService.java1
22 files changed, 533 insertions, 178 deletions
diff --git a/Android.mk b/Android.mk
index e03acdc80..7b724d049 100644
--- a/Android.mk
+++ b/Android.mk
@@ -22,7 +22,7 @@ LOCAL_CERTIFICATE := platform
LOCAL_JNI_SHARED_LIBRARIES := libbluetooth_jni
LOCAL_JAVA_LIBRARIES := javax.obex telephony-common libprotobuf-java-micro
-LOCAL_STATIC_JAVA_LIBRARIES := com.android.vcard bluetooth.mapsapi sap-api-java-static
+LOCAL_STATIC_JAVA_LIBRARIES := com.android.vcard bluetooth.mapsapi sap-api-java-static android-support-v4
LOCAL_REQUIRED_MODULES := bluetooth.default
LOCAL_MULTILIB := 32
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 85910aa46..3652fd3a0 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -27,6 +27,7 @@
<uses-permission android:name="android.permission.BLUETOOTH_MAP" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+ <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_CONTACTS" />
<!-- WRITE_CONTACTS is used for test cases only -->
<uses-permission android:name="android.permission.WRITE_CONTACTS" />
@@ -80,6 +81,14 @@
android:pathPrefix="/btopp"
android:permission="android.permission.ACCESS_BLUETOOTH_SHARE" />
</provider>
+ <provider android:name="android.support.v4.content.FileProvider"
+ android:authorities="com.google.android.bluetooth.fileprovider"
+ android:grantUriPermissions="true"
+ android:exported="false">
+ <meta-data
+ android:name="android.support.FILE_PROVIDER_PATHS"
+ android:resource="@xml/file_paths" />
+ </provider>
<service
android:process="@string/process"
android:name = ".btservice.AdapterService">
diff --git a/jni/com_android_bluetooth_avrcp.cpp b/jni/com_android_bluetooth_avrcp.cpp
index 5207f4d99..ba1d78bbd 100644
--- a/jni/com_android_bluetooth_avrcp.cpp
+++ b/jni/com_android_bluetooth_avrcp.cpp
@@ -72,8 +72,7 @@ static void btavrcp_remote_features_callback(bt_bdaddr_t* bd_addr, btrc_remote_f
}
checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
- /* TODO: I think we leak the addr object, we should add a
- * sCallbackEnv->DeleteLocalRef(addr) */
+ sCallbackEnv->DeleteLocalRef(addr);
}
static void btavrcp_get_play_status_callback() {
diff --git a/jni/com_android_bluetooth_btservice_AdapterService.cpp b/jni/com_android_bluetooth_btservice_AdapterService.cpp
index e2af3ddc8..ad5288eff 100755
--- a/jni/com_android_bluetooth_btservice_AdapterService.cpp
+++ b/jni/com_android_bluetooth_btservice_AdapterService.cpp
@@ -1129,6 +1129,27 @@ static jboolean factoryResetNative(JNIEnv *env, jobject obj) {
return (ret == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
}
+static void interopDatabaseClearNative(JNIEnv *env, jobject obj) {
+ ALOGV("%s()", __FUNCTION__);
+ if (!sBluetoothInterface) return;
+ sBluetoothInterface->interop_database_clear();
+}
+
+static void interopDatabaseAddNative(JNIEnv *env, jobject obj, int feature,
+ jbyteArray address, int length) {
+ ALOGV("%s()", __FUNCTION__);
+ if (!sBluetoothInterface) return;
+
+ jbyte *addr = env->GetByteArrayElements(address, NULL);
+ if (addr == NULL) {
+ jniThrowIOException(env, EINVAL);
+ return;
+ }
+
+ sBluetoothInterface->interop_database_add(feature, (bt_bdaddr_t *)addr, length);
+ env->ReleaseByteArrayElements(address, addr, 0);
+}
+
static JNINativeMethod sMethods[] = {
/* name, signature, funcPtr */
{"classInitNative", "()V", (void *) classInitNative},
@@ -1157,7 +1178,9 @@ static JNINativeMethod sMethods[] = {
{"alarmFiredNative", "()V", (void *) alarmFiredNative},
{"readEnergyInfo", "()I", (void*) readEnergyInfo},
{"dumpNative", "(Ljava/io/FileDescriptor;)V", (void*) dumpNative},
- {"factoryResetNative", "()Z", (void*)factoryResetNative}
+ {"factoryResetNative", "()Z", (void*)factoryResetNative},
+ {"interopDatabaseClearNative", "()V", (void*) interopDatabaseClearNative},
+ {"interopDatabaseAddNative", "(I[BI)V", (void*) interopDatabaseAddNative}
};
int register_com_android_bluetooth_btservice_AdapterService(JNIEnv* env)
diff --git a/res/values/config.xml b/res/values/config.xml
index 1684183b6..b32d5853c 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -33,4 +33,9 @@
fire Bluetooth LE scan result callbacks in addition to having one
of the location permissions. -->
<bool name="strict_location_check">true</bool>
+
+ <!-- For AVRCP absolute volume feature. If the threshold is non-zero,
+ restrict the initial volume to the threshold.
+ Valid value is 1-14, and recommended value is 8 -->
+ <integer name="a2dp_absolute_volume_initial_threshold">8</integer>
</resources>
diff --git a/res/xml/file_paths.xml b/res/xml/file_paths.xml
new file mode 100644
index 000000000..72d848d15
--- /dev/null
+++ b/res/xml/file_paths.xml
@@ -0,0 +1,3 @@
+<paths xmlns:android="https://schemas.android.com/apk/res/android">
+ <external-path name="bluetooth" path="/" />
+</paths>
diff --git a/src/com/android/bluetooth/Utils.java b/src/com/android/bluetooth/Utils.java
index a16d3b9c4..6a467f9c3 100644
--- a/src/com/android/bluetooth/Utils.java
+++ b/src/com/android/bluetooth/Utils.java
@@ -288,14 +288,7 @@ final public class Utils {
return true;
}
// Enforce location permission for apps targeting M and later versions
- boolean enforceLocationPermission = true;
- try {
- enforceLocationPermission = context.getPackageManager().getApplicationInfo(
- callingPackage, 0).targetSdkVersion >= Build.VERSION_CODES.M;
- } catch (PackageManager.NameNotFoundException e) {
- // In case of exception, enforce permission anyway
- }
- if (enforceLocationPermission) {
+ if (isMApp(context, callingPackage)) {
throw new SecurityException("Need ACCESS_COARSE_LOCATION or "
+ "ACCESS_FINE_LOCATION permission to get scan results");
} else {
@@ -317,6 +310,20 @@ final public class Utils {
android.Manifest.permission.PEERS_MAC_ADDRESS) == PackageManager.PERMISSION_GRANTED;
}
+ public static boolean isLegacyForegroundApp(Context context, String pkgName) {
+ return !isMApp(context, pkgName) && isForegroundApp(context, pkgName);
+ }
+
+ private static boolean isMApp(Context context, String pkgName) {
+ try {
+ return context.getPackageManager().getApplicationInfo(pkgName, 0)
+ .targetSdkVersion >= Build.VERSION_CODES.M;
+ } catch (PackageManager.NameNotFoundException e) {
+ // In case of exception, assume M app
+ }
+ return true;
+ }
+
/**
* Return true if the specified package name is a foreground app.
*
diff --git a/src/com/android/bluetooth/a2dp/A2dpService.java b/src/com/android/bluetooth/a2dp/A2dpService.java
index e14302caf..a278927d3 100755
--- a/src/com/android/bluetooth/a2dp/A2dpService.java
+++ b/src/com/android/bluetooth/a2dp/A2dpService.java
@@ -213,6 +213,10 @@ public class A2dpService extends ProfileService {
mAvrcp.setA2dpAudioState(state);
}
+ public void resetAvrcpBlacklist(BluetoothDevice device) {
+ mAvrcp.resetBlackList(device.getAddress());
+ }
+
synchronized boolean isA2dpPlaying(BluetoothDevice device) {
enforceCallingOrSelfPermission(BLUETOOTH_PERM,
"Need BLUETOOTH permission");
diff --git a/src/com/android/bluetooth/avrcp/Avrcp.java b/src/com/android/bluetooth/avrcp/Avrcp.java
index abc0b8481..7d1403736 100755
--- a/src/com/android/bluetooth/avrcp/Avrcp.java
+++ b/src/com/android/bluetooth/avrcp/Avrcp.java
@@ -24,6 +24,8 @@ import android.bluetooth.BluetoothA2dp;
import android.bluetooth.BluetoothAvrcp;
import android.content.Context;
import android.content.Intent;
+import android.content.res.Resources;
+import android.content.SharedPreferences;
import android.graphics.Bitmap;
import android.media.AudioManager;
import android.media.IRemoteControlDisplay;
@@ -45,6 +47,7 @@ import android.os.SystemClock;
import android.util.Log;
import android.view.KeyEvent;
+import com.android.bluetooth.R;
import com.android.bluetooth.btservice.AdapterService;
import com.android.bluetooth.btservice.ProfileService;
import com.android.bluetooth.Utils;
@@ -54,6 +57,7 @@ import com.android.internal.util.StateMachine;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
import java.util.Set;
/**
@@ -63,6 +67,7 @@ import java.util.Set;
public final class Avrcp {
private static final boolean DEBUG = false;
private static final String TAG = "Avrcp";
+ private static final String ABSOLUTE_VOLUME_BLACKLIST = "absolute_volume_blacklist";
private Context mContext;
private final AudioManager mAudioManager;
@@ -84,12 +89,23 @@ public final class Avrcp {
private long mPrevPosMs;
private long mSkipStartTime;
private int mFeatures;
- private int mAbsoluteVolume;
- private int mLastSetVolume;
+ private int mRemoteVolume;
+ private int mLastRemoteVolume;
+ private int mInitialRemoteVolume;
+
+ /* Local volume in audio index 0-15 */
+ private int mLocalVolume;
+ private int mLastLocalVolume;
+ private int mAbsVolThreshold;
+
+ private String mAddress;
+ private HashMap<Integer, Integer> mVolumeMapping;
+
private int mLastDirection;
private final int mVolumeStep;
private final int mAudioStreamMax;
- private boolean mVolCmdInProgress;
+ private boolean mVolCmdAdjustInProgress;
+ private boolean mVolCmdSetInProgress;
private int mAbsVolRetryTimes;
private int mSkipAmount;
@@ -153,11 +169,17 @@ public final class Avrcp {
mPlaybackIntervalMs = 0L;
mPlayPosChangedNT = NOTIFICATION_TYPE_CHANGED;
mFeatures = 0;
- mAbsoluteVolume = -1;
- mLastSetVolume = -1;
+ mRemoteVolume = -1;
+ mInitialRemoteVolume = -1;
+ mLastRemoteVolume = -1;
mLastDirection = 0;
- mVolCmdInProgress = false;
+ mVolCmdAdjustInProgress = false;
+ mVolCmdSetInProgress = false;
mAbsVolRetryTimes = 0;
+ mLocalVolume = -1;
+ mLastLocalVolume = -1;
+ mAbsVolThreshold = 0;
+ mVolumeMapping = new HashMap<Integer, Integer>();
mContext = context;
@@ -166,6 +188,10 @@ public final class Avrcp {
mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
mAudioStreamMax = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
mVolumeStep = Math.max(AVRCP_BASE_VOLUME_STEP, AVRCP_MAX_VOL/mAudioStreamMax);
+ Resources resources = context.getResources();
+ if (resources != null) {
+ mAbsVolThreshold = resources.getInteger(R.integer.a2dp_absolute_volume_initial_threshold);
+ }
}
private void start() {
@@ -197,6 +223,8 @@ public final class Avrcp {
public void cleanup() {
cleanupNative();
+ if (mVolumeMapping != null)
+ mVolumeMapping.clear();
}
private static class RemoteControllerWeak implements RemoteController.OnClientUpdateListener {
@@ -283,7 +311,15 @@ public final class Avrcp {
if (DEBUG) Log.v(TAG, "MESSAGE_GET_RC_FEATURES: address="+address+
", features="+msg.arg1);
mFeatures = msg.arg1;
+ mFeatures = modifyRcFeatureFromBlacklist(mFeatures, address);
mAudioManager.avrcpSupportsAbsoluteVolume(address, isAbsoluteVolumeSupported());
+ mLastLocalVolume = -1;
+ mRemoteVolume = -1;
+ mLocalVolume = -1;
+ mInitialRemoteVolume = -1;
+ mAddress = address;
+ if (mVolumeMapping != null)
+ mVolumeMapping.clear();
break;
case MESSAGE_GET_PLAY_STATUS:
@@ -321,47 +357,158 @@ public final class Avrcp {
break;
case MESSAGE_VOLUME_CHANGED:
+ if (!isAbsoluteVolumeSupported()) {
+ if (DEBUG) Log.v(TAG, "ignore MESSAGE_VOLUME_CHANGED");
+ break;
+ }
+
if (DEBUG) Log.v(TAG, "MESSAGE_VOLUME_CHANGED: volume=" + ((byte)msg.arg1 & 0x7f)
+ " ctype=" + msg.arg2);
+
+ boolean volAdj = false;
if (msg.arg2 == AVRC_RSP_ACCEPT || msg.arg2 == AVRC_RSP_REJ) {
- if (mVolCmdInProgress == false) {
+ if (mVolCmdAdjustInProgress == false && mVolCmdSetInProgress == false) {
Log.e(TAG, "Unsolicited response, ignored");
break;
}
removeMessages(MESSAGE_ABS_VOL_TIMEOUT);
- mVolCmdInProgress = false;
+
+ volAdj = mVolCmdAdjustInProgress;
+ mVolCmdAdjustInProgress = false;
+ mVolCmdSetInProgress = false;
mAbsVolRetryTimes = 0;
}
- if (mAbsoluteVolume != msg.arg1 && (msg.arg2 == AVRC_RSP_ACCEPT ||
+
+ byte absVol = (byte)((byte)msg.arg1 & 0x7f); // discard MSB as it is RFD
+ // convert remote volume to local volume
+ int volIndex = convertToAudioStreamVolume(absVol);
+ if (mInitialRemoteVolume == -1) {
+ mInitialRemoteVolume = absVol;
+ if (mAbsVolThreshold > 0 && mAbsVolThreshold < mAudioStreamMax && volIndex > mAbsVolThreshold) {
+ if (DEBUG) Log.v(TAG, "remote inital volume too high " + volIndex + ">" + mAbsVolThreshold);
+ Message msg1 = mHandler.obtainMessage(MESSAGE_SET_ABSOLUTE_VOLUME, mAbsVolThreshold , 0);
+ mHandler.sendMessage(msg1);
+ mRemoteVolume = absVol;
+ mLocalVolume = volIndex;
+ break;
+ }
+ }
+
+ if (mLocalVolume != volIndex &&
+ (msg.arg2 == AVRC_RSP_ACCEPT ||
msg.arg2 == AVRC_RSP_CHANGED ||
msg.arg2 == AVRC_RSP_INTERIM)) {
- byte absVol = (byte)((byte)msg.arg1 & 0x7f); // discard MSB as it is RFD
- notifyVolumeChanged(absVol);
- mAbsoluteVolume = absVol;
+ /* If the volume has successfully changed */
+ mLocalVolume = volIndex;
+ if (mLastLocalVolume != -1 && msg.arg2 == AVRC_RSP_ACCEPT) {
+ if (mLastLocalVolume != volIndex) {
+ /* remote volume changed more than requested due to
+ * local and remote has different volume steps */
+ if (DEBUG) Log.d(TAG, "Remote returned volume does not match desired volume "
+ + mLastLocalVolume + " vs "
+ + volIndex);
+ mLastLocalVolume = mLocalVolume;
+ }
+ }
+ // remember the remote volume value, as it's the one supported by remote
+ if (volAdj) {
+ synchronized (mVolumeMapping) {
+ mVolumeMapping.put(volIndex, (int)absVol);
+ if (DEBUG) Log.v(TAG, "remember volume mapping " +volIndex+ "-"+absVol);
+ }
+ }
+
+ notifyVolumeChanged(mLocalVolume);
+ mRemoteVolume = absVol;
long pecentVolChanged = ((long)absVol * 100) / 0x7f;
Log.e(TAG, "percent volume changed: " + pecentVolChanged + "%");
} else if (msg.arg2 == AVRC_RSP_REJ) {
Log.e(TAG, "setAbsoluteVolume call rejected");
+ } else if (volAdj && mLastRemoteVolume > 0 && mLastRemoteVolume < AVRCP_MAX_VOL &&
+ mLocalVolume == volIndex &&
+ (msg.arg2 == AVRC_RSP_ACCEPT )) {
+ /* oops, the volume is still same, remote does not like the value
+ * retry a volume one step up/down */
+ if (DEBUG) Log.d(TAG, "Remote device didn't tune volume, let's try one more step.");
+ int retry_volume = Math.min(AVRCP_MAX_VOL,
+ Math.max(0, mLastRemoteVolume + mLastDirection));
+ if (setVolumeNative(retry_volume)) {
+ mLastRemoteVolume = retry_volume;
+ sendMessageDelayed(obtainMessage(MESSAGE_ABS_VOL_TIMEOUT),
+ CMD_TIMEOUT_DELAY);
+ mVolCmdAdjustInProgress = true;
+ }
}
break;
case MESSAGE_ADJUST_VOLUME:
+ if (!isAbsoluteVolumeSupported()) {
+ if (DEBUG) Log.v(TAG, "ignore MESSAGE_ADJUST_VOLUME");
+ break;
+ }
+
if (DEBUG) Log.d(TAG, "MESSAGE_ADJUST_VOLUME: direction=" + msg.arg1);
- if (mVolCmdInProgress) {
+
+ if (mVolCmdAdjustInProgress || mVolCmdSetInProgress) {
if (DEBUG) Log.w(TAG, "There is already a volume command in progress.");
break;
}
+
+ // Remote device didn't set initial volume. Let's black list it
+ if (mInitialRemoteVolume == -1) {
+ Log.d(TAG, "remote " + mAddress + " never tell us initial volume, black list it.");
+ blackListCurrentDevice();
+ break;
+ }
+
// Wait on verification on volume from device, before changing the volume.
- if (mAbsoluteVolume != -1 && (msg.arg1 == -1 || msg.arg1 == 1)) {
- int setVol = Math.min(AVRCP_MAX_VOL,
- Math.max(0, mAbsoluteVolume + msg.arg1*mVolumeStep));
+ if (mRemoteVolume != -1 && (msg.arg1 == -1 || msg.arg1 == 1)) {
+ int setVol = -1;
+ int targetVolIndex = -1;
+ if (mLocalVolume == 0 && msg.arg1 == -1) {
+ if (DEBUG) Log.w(TAG, "No need to Vol down from 0.");
+ break;
+ }
+ if (mLocalVolume == mAudioStreamMax && msg.arg1 == 1) {
+ if (DEBUG) Log.w(TAG, "No need to Vol up from max.");
+ break;
+ }
+
+ targetVolIndex = mLocalVolume + msg.arg1;
+ if (DEBUG) Log.d(TAG, "Adjusting volume to " + targetVolIndex);
+
+ Integer i;
+ synchronized (mVolumeMapping) {
+ i = mVolumeMapping.get(targetVolIndex);
+ }
+
+ if (i != null) {
+ /* if we already know this volume mapping, use it */
+ setVol = i.byteValue();
+ if (setVol == mRemoteVolume) {
+ if (DEBUG) Log.d(TAG, "got same volume from mapping for " + targetVolIndex + ", ignore.");
+ setVol = -1;
+ }
+ if (DEBUG) Log.d(TAG, "set volume from mapping " + targetVolIndex + "-" + setVol);
+ }
+
+ if (setVol == -1) {
+ /* otherwise use phone steps */
+ setVol = Math.min(AVRCP_MAX_VOL,
+ convertToAvrcpVolume(Math.max(0, targetVolIndex)));
+ if (DEBUG) Log.d(TAG, "set volume from local volume "+ targetVolIndex+"-"+ setVol);
+ }
+
if (setVolumeNative(setVol)) {
sendMessageDelayed(obtainMessage(MESSAGE_ABS_VOL_TIMEOUT),
CMD_TIMEOUT_DELAY);
- mVolCmdInProgress = true;
+ mVolCmdAdjustInProgress = true;
mLastDirection = msg.arg1;
- mLastSetVolume = setVol;
+ mLastRemoteVolume = setVol;
+ mLastLocalVolume = targetVolIndex;
+ } else {
+ if (DEBUG) Log.d(TAG, "setVolumeNative failed");
}
} else {
Log.e(TAG, "Unknown direction in MESSAGE_ADJUST_VOLUME");
@@ -369,29 +516,50 @@ public final class Avrcp {
break;
case MESSAGE_SET_ABSOLUTE_VOLUME:
+ if (!isAbsoluteVolumeSupported()) {
+ if (DEBUG) Log.v(TAG, "ignore MESSAGE_SET_ABSOLUTE_VOLUME");
+ break;
+ }
+
if (DEBUG) Log.v(TAG, "MESSAGE_SET_ABSOLUTE_VOLUME");
- if (mVolCmdInProgress) {
+
+ if (mVolCmdSetInProgress || mVolCmdAdjustInProgress) {
if (DEBUG) Log.w(TAG, "There is already a volume command in progress.");
break;
}
- if (setVolumeNative(msg.arg1)) {
+
+ // Remote device didn't set initial volume. Let's black list it
+ if (mInitialRemoteVolume == -1) {
+ if (DEBUG) Log.d(TAG, "remote " + mAddress + " never tell us initial volume, black list it.");
+ blackListCurrentDevice();
+ break;
+ }
+
+ int avrcpVolume = convertToAvrcpVolume(msg.arg1);
+ avrcpVolume = Math.min(AVRCP_MAX_VOL, Math.max(0, avrcpVolume));
+ if (DEBUG) Log.d(TAG, "Setting volume to " + msg.arg1+"-"+avrcpVolume);
+ if (setVolumeNative(avrcpVolume)) {
sendMessageDelayed(obtainMessage(MESSAGE_ABS_VOL_TIMEOUT), CMD_TIMEOUT_DELAY);
- mVolCmdInProgress = true;
- mLastSetVolume = msg.arg1;
+ mVolCmdSetInProgress = true;
+ mLastRemoteVolume = avrcpVolume;
+ mLastLocalVolume = msg.arg1;
+ } else {
+ if (DEBUG) Log.d(TAG, "setVolumeNative failed");
}
break;
case MESSAGE_ABS_VOL_TIMEOUT:
if (DEBUG) Log.v(TAG, "MESSAGE_ABS_VOL_TIMEOUT: Volume change cmd timed out.");
- mVolCmdInProgress = false;
+ mVolCmdAdjustInProgress = false;
+ mVolCmdSetInProgress = false;
if (mAbsVolRetryTimes >= MAX_ERROR_RETRY_TIMES) {
mAbsVolRetryTimes = 0;
} else {
mAbsVolRetryTimes += 1;
- if (setVolumeNative(mLastSetVolume)) {
+ if (setVolumeNative(mLastRemoteVolume)) {
sendMessageDelayed(obtainMessage(MESSAGE_ABS_VOL_TIMEOUT),
CMD_TIMEOUT_DELAY);
- mVolCmdInProgress = true;
+ mVolCmdSetInProgress = true;
}
}
break;
@@ -799,10 +967,13 @@ public final class Avrcp {
}
public void setAbsoluteVolume(int volume) {
- int avrcpVolume = convertToAvrcpVolume(volume);
- avrcpVolume = Math.min(AVRCP_MAX_VOL, Math.max(0, avrcpVolume));
+ if (volume == mLocalVolume) {
+ if (DEBUG) Log.v(TAG, "setAbsoluteVolume is setting same index, ignore "+volume);
+ return;
+ }
+
mHandler.removeMessages(MESSAGE_ADJUST_VOLUME);
- Message msg = mHandler.obtainMessage(MESSAGE_SET_ABSOLUTE_VOLUME, avrcpVolume, 0);
+ Message msg = mHandler.obtainMessage(MESSAGE_SET_ABSOLUTE_VOLUME, volume, 0);
mHandler.sendMessage(msg);
}
@@ -819,20 +990,50 @@ public final class Avrcp {
}
private void notifyVolumeChanged(int volume) {
- volume = convertToAudioStreamVolume(volume);
mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, volume,
AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_BLUETOOTH_ABS_VOLUME);
}
private int convertToAudioStreamVolume(int volume) {
// Rescale volume to match AudioSystem's volume
- return (int) Math.round((double) volume*mAudioStreamMax/AVRCP_MAX_VOL);
+ return (int) Math.floor((double) volume*mAudioStreamMax/AVRCP_MAX_VOL);
}
private int convertToAvrcpVolume(int volume) {
return (int) Math.ceil((double) volume*AVRCP_MAX_VOL/mAudioStreamMax);
}
+ private void blackListCurrentDevice() {
+ mFeatures &= ~BTRC_FEAT_ABSOLUTE_VOLUME;
+ mAudioManager.avrcpSupportsAbsoluteVolume(mAddress, isAbsoluteVolumeSupported());
+
+ SharedPreferences pref = mContext.getSharedPreferences(ABSOLUTE_VOLUME_BLACKLIST,
+ Context.MODE_PRIVATE);
+ SharedPreferences.Editor editor = pref.edit();
+ editor.putBoolean(mAddress, true);
+ editor.commit();
+ }
+
+ private int modifyRcFeatureFromBlacklist(int feature, String address) {
+ SharedPreferences pref = mContext.getSharedPreferences(ABSOLUTE_VOLUME_BLACKLIST,
+ Context.MODE_PRIVATE);
+ if (!pref.contains(address)) {
+ return feature;
+ }
+ if (pref.getBoolean(address, false)) {
+ feature &= ~BTRC_FEAT_ABSOLUTE_VOLUME;
+ }
+ return feature;
+ }
+
+ public void resetBlackList(String address) {
+ SharedPreferences pref = mContext.getSharedPreferences(ABSOLUTE_VOLUME_BLACKLIST,
+ Context.MODE_PRIVATE);
+ SharedPreferences.Editor editor = pref.edit();
+ editor.remove(address);
+ editor.commit();
+ }
+
/**
* This is called from A2dpStateMachine to set A2dp audio state.
*/
@@ -858,14 +1059,16 @@ public final class Avrcp {
ProfileService.println(sb, "mPrevPosMs: " + mPrevPosMs);
ProfileService.println(sb, "mSkipStartTime: " + mSkipStartTime);
ProfileService.println(sb, "mFeatures: " + mFeatures);
- ProfileService.println(sb, "mAbsoluteVolume: " + mAbsoluteVolume);
- ProfileService.println(sb, "mLastSetVolume: " + mLastSetVolume);
+ ProfileService.println(sb, "mRemoteVolume: " + mRemoteVolume);
+ ProfileService.println(sb, "mLastRemoteVolume: " + mLastRemoteVolume);
ProfileService.println(sb, "mLastDirection: " + mLastDirection);
ProfileService.println(sb, "mVolumeStep: " + mVolumeStep);
ProfileService.println(sb, "mAudioStreamMax: " + mAudioStreamMax);
- ProfileService.println(sb, "mVolCmdInProgress: " + mVolCmdInProgress);
+ ProfileService.println(sb, "mVolCmdAdjustInProgress: " + mVolCmdAdjustInProgress);
+ ProfileService.println(sb, "mVolCmdSetInProgress: " + mVolCmdSetInProgress);
ProfileService.println(sb, "mAbsVolRetryTimes: " + mAbsVolRetryTimes);
ProfileService.println(sb, "mSkipAmount: " + mSkipAmount);
+ ProfileService.println(sb, "mVolumeMapping: " + mVolumeMapping.toString());
}
// Do not modify without updating the HAL bt_rc.h files.
diff --git a/src/com/android/bluetooth/btservice/AdapterService.java b/src/com/android/bluetooth/btservice/AdapterService.java
index 8f7231236..3ac242ccd 100644
--- a/src/com/android/bluetooth/btservice/AdapterService.java
+++ b/src/com/android/bluetooth/btservice/AdapterService.java
@@ -360,6 +360,8 @@ public class AdapterService extends Service {
mAdapterStateMachine.sendMessage(mAdapterStateMachine.obtainMessage(AdapterState.BREDR_STOPPED));
} else if (isTurningOn) {
+ updateInteropDatabase();
+
//Process start pending
//Check if all services are started if so, update state
synchronized (mProfileServicesState) {
@@ -385,6 +387,59 @@ public class AdapterService extends Service {
}
}
+ private void updateInteropDatabase() {
+ interopDatabaseClearNative();
+
+ String interop_string = Settings.Global.getString(getContentResolver(),
+ Settings.Global.BLUETOOTH_INTEROPERABILITY_LIST);
+ if (interop_string == null) return;
+ Log.d(TAG, "updateInteropDatabase: [" + interop_string + "]");
+
+ String[] entries = interop_string.split(";");
+ for (String entry : entries) {
+ String[] tokens = entry.split(",");
+ if (tokens.length != 2) continue;
+
+ // Get feature
+ int feature = 0;
+ try {
+ feature = Integer.parseInt(tokens[1]);
+ } catch (NumberFormatException e) {
+ Log.e(TAG, "updateInteropDatabase: Invalid feature '" + tokens[1] + "'");
+ continue;
+ }
+
+ // Get address bytes and length
+ int length = (tokens[0].length() + 1) / 3;
+ if (length < 1 || length > 6) {
+ Log.e(TAG, "updateInteropDatabase: Malformed address string '" + tokens[0] + "'");
+ continue;
+ }
+
+ byte[] addr = new byte[6];
+ int offset = 0;
+ for (int i = 0; i < tokens[0].length(); ) {
+ if (tokens[0].charAt(i) == ':') {
+ i += 1;
+ } else {
+ try {
+ addr[offset++] = (byte) Integer.parseInt(tokens[0].substring(i, i + 2), 16);
+ } catch (NumberFormatException e) {
+ offset = 0;
+ break;
+ }
+ i += 2;
+ }
+ }
+
+ // Check if address was parsed ok, otherwise, move on...
+ if (offset == 0) continue;
+
+ // Add entry
+ interopDatabaseAddNative(feature, addr, length);
+ }
+ }
+
@Override
public void onCreate() {
super.onCreate();
@@ -2139,6 +2194,9 @@ public class AdapterService extends Service {
private native void alarmFiredNative();
private native void dumpNative(FileDescriptor fd);
+ private native void interopDatabaseClearNative();
+ private native void interopDatabaseAddNative(int feature, byte[] address, int length);
+
protected void finalize() {
cleanup();
if (TRACE_REF) {
diff --git a/src/com/android/bluetooth/btservice/BondStateMachine.java b/src/com/android/bluetooth/btservice/BondStateMachine.java
index 7bed94a03..b4241c13c 100644
--- a/src/com/android/bluetooth/btservice/BondStateMachine.java
+++ b/src/com/android/bluetooth/btservice/BondStateMachine.java
@@ -462,6 +462,10 @@ final class BondStateMachine extends StateMachine {
a2dpService.setPriority(device,BluetoothProfile.PRIORITY_UNDEFINED);
if(headsetService != null)
headsetService.setPriority(device,BluetoothProfile.PRIORITY_UNDEFINED);
+
+ // Clear Absolute Volume black list
+ if(a2dpService != null)
+ a2dpService.resetAvrcpBlacklist(device);
}
private void infoLog(String msg) {
diff --git a/src/com/android/bluetooth/gatt/GattService.java b/src/com/android/bluetooth/gatt/GattService.java
index a04d5a317..26ef18d1f 100644
--- a/src/com/android/bluetooth/gatt/GattService.java
+++ b/src/com/android/bluetooth/gatt/GattService.java
@@ -641,12 +641,13 @@ public class GattService extends ProfileService {
private boolean hasScanResultPermission(final ScanClient client) {
final boolean requiresLocationEnabled =
getResources().getBoolean(R.bool.strict_location_check);
- final boolean locationEnabled = Settings.Secure.getInt(getContentResolver(),
+ final boolean locationEnabledSetting = Settings.Secure.getInt(getContentResolver(),
Settings.Secure.LOCATION_MODE, Settings.Secure.LOCATION_MODE_OFF)
!= Settings.Secure.LOCATION_MODE_OFF;
-
- return (client.hasPeersMacAddressPermission ||
- (client.hasLocationPermission && (!requiresLocationEnabled || locationEnabled)));
+ final boolean locationEnabled = !requiresLocationEnabled || locationEnabledSetting
+ || client.legacyForegroundApp;
+ return (client.hasPeersMacAddressPermission
+ || (client.hasLocationPermission && locationEnabled));
}
// Check if a scan record matches a specific filters.
@@ -1378,12 +1379,12 @@ public class GattService extends ProfileService {
if (needsPrivilegedPermissionForScan(settings)) {
enforcePrivilegedPermission();
}
- boolean hasLocationPermission = Utils.checkCallerHasLocationPermission(this,
- mAppOps, callingPackage);
final ScanClient scanClient = new ScanClient(appIf, isServer, settings, filters, storages);
- scanClient.hasLocationPermission = hasLocationPermission;
+ scanClient.hasLocationPermission = Utils.checkCallerHasLocationPermission(this, mAppOps,
+ callingPackage);
scanClient.hasPeersMacAddressPermission = Utils.checkCallerHasPeersMacAddressPermission(
this);
+ scanClient.legacyForegroundApp = Utils.isLegacyForegroundApp(this, callingPackage);
mScanManager.startScan(scanClient);
}
diff --git a/src/com/android/bluetooth/gatt/ScanClient.java b/src/com/android/bluetooth/gatt/ScanClient.java
index d42df39d2..64d3e1ffc 100644
--- a/src/com/android/bluetooth/gatt/ScanClient.java
+++ b/src/com/android/bluetooth/gatt/ScanClient.java
@@ -40,6 +40,8 @@ import java.util.UUID;
boolean appDied;
boolean hasLocationPermission;
boolean hasPeersMacAddressPermission;
+ // Pre-M apps are allowed to get scan results even if location is disabled
+ boolean legacyForegroundApp;
private static final ScanSettings DEFAULT_SCAN_SETTINGS = new ScanSettings.Builder()
.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY).build();
diff --git a/src/com/android/bluetooth/hfp/HeadsetStateMachine.java b/src/com/android/bluetooth/hfp/HeadsetStateMachine.java
index d226e7cfe..0cf5f335f 100644
--- a/src/com/android/bluetooth/hfp/HeadsetStateMachine.java
+++ b/src/com/android/bluetooth/hfp/HeadsetStateMachine.java
@@ -2763,6 +2763,11 @@ final class HeadsetStateMachine extends StateMachine {
} else {
Log.e(TAG,"processNoiceReductionEvent: AudioParamNrec is null ");
}
+
+ if (mActiveScoDevice != null && mActiveScoDevice.equals(device)
+ && mAudioState == BluetoothHeadset.STATE_AUDIO_CONNECTED) {
+ setAudioParameters(device);
+ }
}
// 2 - WBS on
diff --git a/src/com/android/bluetooth/map/BluetoothMapContent.java b/src/com/android/bluetooth/map/BluetoothMapContent.java
index 28f5d423a..7ced91bdd 100644
--- a/src/com/android/bluetooth/map/BluetoothMapContent.java
+++ b/src/com/android/bluetooth/map/BluetoothMapContent.java
@@ -117,7 +117,7 @@ public class BluetoothMapContent {
// MAP specification states that the default value for parameter mask are
// the #REQUIRED attributes in the DTD, and not all enabled
public static final long PARAMETER_MASK_ALL_ENABLED = 0xFFFFFFFFL;
- public static final long PARAMETER_MASK_DEFAULT = 0x5E3L;
+ public static final long PARAMETER_MASK_DEFAULT = 0x5EBL;
public static final long CONVO_PARAMETER_MASK_ALL_ENABLED = 0xFFFFFFFFL;
public static final long CONVO_PARAMETER_MASK_DEFAULT =
CONVO_PARAM_MASK_CONVO_NAME |
diff --git a/src/com/android/bluetooth/map/BluetoothMapContentObserver.java b/src/com/android/bluetooth/map/BluetoothMapContentObserver.java
index 417be2555..9aae8cf54 100644
--- a/src/com/android/bluetooth/map/BluetoothMapContentObserver.java
+++ b/src/com/android/bluetooth/map/BluetoothMapContentObserver.java
@@ -3040,6 +3040,9 @@ public class BluetoothMapContentObserver {
if(status != 0/*0 is success*/) {
msgInfo.statusDelivered = status;
if(D) Log.d(TAG, "msgInfo.statusDelivered = " + status);
+ Sms.moveMessageToFolder(mContext, msgInfo.uri, Sms.MESSAGE_TYPE_FAILED, 0);
+ } else {
+ Sms.moveMessageToFolder(mContext, msgInfo.uri, Sms.MESSAGE_TYPE_SENT, 0);
}
}
if (msgInfo.partsDelivered == msgInfo.parts) {
diff --git a/src/com/android/bluetooth/map/BluetoothMapUtils.java b/src/com/android/bluetooth/map/BluetoothMapUtils.java
index 66ceecc28..f10c4ec00 100755
--- a/src/com/android/bluetooth/map/BluetoothMapUtils.java
+++ b/src/com/android/bluetooth/map/BluetoothMapUtils.java
@@ -394,15 +394,16 @@ public class BluetoothMapUtils {
static public byte[] truncateUtf8StringToBytearray(String utf8String, int maxLength)
throws UnsupportedEncodingException {
- byte[] utf8Bytes = null;
+ byte[] utf8Bytes = new byte[utf8String.length() + 1];
try {
- utf8Bytes = utf8String.getBytes("UTF-8");
+ System.arraycopy(utf8String.getBytes("UTF-8"), 0,
+ utf8Bytes, 0, utf8String.length());
} catch (UnsupportedEncodingException e) {
Log.e(TAG,"truncateUtf8StringToBytearray: getBytes exception ", e);
throw e;
}
- if (utf8Bytes.length > (maxLength - 1)) {
+ if (utf8Bytes.length > maxLength) {
/* if 'continuation' byte is in place 200,
* then strip previous bytes until utf-8 start byte is found */
if ( (utf8Bytes[maxLength - 1] & 0xC0) == 0x80 ) {
diff --git a/src/com/android/bluetooth/opp/BluetoothOppUtility.java b/src/com/android/bluetooth/opp/BluetoothOppUtility.java
index ea465f464..58f467794 100644
--- a/src/com/android/bluetooth/opp/BluetoothOppUtility.java
+++ b/src/com/android/bluetooth/opp/BluetoothOppUtility.java
@@ -54,6 +54,7 @@ import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
+import android.support.v4.content.FileProvider;
/**
* This class has some utilities for Opp application;
*/
@@ -186,7 +187,8 @@ public class BluetoothOppUtility {
return;
}
- Uri path = Uri.parse(fileName);
+ Uri path = FileProvider.getUriForFile(context,
+ "com.google.android.bluetooth.fileprovider", f);
// If there is no scheme, then it must be a file
if (path.getScheme() == null) {
path = Uri.fromFile(new File(fileName));
@@ -196,7 +198,22 @@ public class BluetoothOppUtility {
Intent activityIntent = new Intent(Intent.ACTION_VIEW);
activityIntent.setDataAndTypeAndNormalize(path, mimetype);
+ List<ResolveInfo> resInfoList = context.getPackageManager()
+ .queryIntentActivities(activityIntent,
+ PackageManager.MATCH_DEFAULT_ONLY);
+
+ // Grant permissions for any app that can handle a file to access it
+ for (ResolveInfo resolveInfo : resInfoList) {
+ String packageName = resolveInfo.activityInfo.packageName;
+ context.grantUriPermission(packageName, path,
+ Intent.FLAG_GRANT_WRITE_URI_PERMISSION |
+ Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ }
+
activityIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ activityIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ activityIntent.setFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+
try {
if (V) Log.d(TAG, "ACTION_VIEW intent sent out: " + path + " / " + mimetype);
context.startActivity(activityIntent);
diff --git a/src/com/android/bluetooth/pbap/BluetoothPbapVcardManager.java b/src/com/android/bluetooth/pbap/BluetoothPbapVcardManager.java
index de8275e72..748e7a598 100644
--- a/src/com/android/bluetooth/pbap/BluetoothPbapVcardManager.java
+++ b/src/com/android/bluetooth/pbap/BluetoothPbapVcardManager.java
@@ -623,7 +623,6 @@ public class BluetoothPbapVcardManager {
HandlerForStringBuffer buffer = null;
try {
- VCardFilter vcardfilter = new VCardFilter(ignorefilter ? null : filter);
composer = new BluetoothPbapCallLogComposer(mContext);
buffer = new HandlerForStringBuffer(op, ownerVCard);
if (!composer.init(CallLog.Calls.CONTENT_URI, selection, null, CALLLOG_SORT_ORDER)
@@ -638,9 +637,6 @@ public class BluetoothPbapVcardManager {
break;
}
String vcard = composer.createOneEntry(vcardType21);
- if (vcard != null) {
- vcard = vcardfilter.apply(vcard, vcardType21);
- }
if (vcard == null) {
Log.e(TAG,
"Failed to read a contact. Error reason: " + composer.getErrorReason());
diff --git a/src/com/android/bluetooth/sap/SapMessage.java b/src/com/android/bluetooth/sap/SapMessage.java
index 848745df9..e836fba01 100644
--- a/src/com/android/bluetooth/sap/SapMessage.java
+++ b/src/com/android/bluetooth/sap/SapMessage.java
@@ -28,7 +28,7 @@ public class SapMessage {
public static final String TAG = "SapMessage";
public static final boolean DEBUG = SapService.DEBUG;
public static final boolean VERBOSE = SapService.VERBOSE;
- public static final boolean TEST = SapService.PTS_TEST;
+ public static final boolean TEST = false;
/* Message IDs - SAP specification */
public static final int ID_CONNECT_REQ = 0x00;
@@ -404,10 +404,8 @@ public class SapMessage {
case ID_DISCONNECT_REQ: /* No params */
break;
default:
- if(TEST == false) {
- Log.e(TAG, "Unknown request type");
- return null;
- }
+ Log.e(TAG, "Unknown request type");
+ return null;
}
return newMessage;
}
@@ -456,18 +454,25 @@ public class SapMessage {
int paramId;
int paramLength;
boolean success = true;
+ int skipLen = 0;
+
for(int i = 0; i < count; i++) {
paramId = is.read();
is.read(); // Skip the reserved byte
paramLength = is.read();
paramLength = paramLength << 8 | is.read();
+
+ // As per SAP spec padding should be 0-3 bytes
+ if ((paramLength % 4) != 0)
+ skipLen = 4 - (paramLength % 4);
+
if(VERBOSE) Log.i(TAG, "parsing paramId: " + paramId + " with length: " + paramLength);
switch(paramId) {
case PARAM_MAX_MSG_SIZE_ID:
if(paramLength != PARAM_MAX_MSG_SIZE_LENGTH) {
Log.e(TAG, "Received PARAM_MAX_MSG_SIZE with wrong length: " +
paramLength + " skipping this parameter.");
- skip(is, paramLength + (4 - (paramLength % 4)));
+ skip(is, paramLength + skipLen);
success = false;
} else {
mMaxMsgSize = is.read();
@@ -478,18 +483,18 @@ public class SapMessage {
case PARAM_COMMAND_APDU_ID:
mApdu = new byte[paramLength];
read(is, mApdu);
- skip(is, 4 - (paramLength % 4));
+ skip(is, skipLen);
break;
case PARAM_COMMAND_APDU7816_ID:
mApdu7816 = new byte[paramLength];
read(is, mApdu7816);
- skip(is, 4 - (paramLength % 4));
+ skip(is, skipLen);
break;
case PARAM_TRANSPORT_PROTOCOL_ID:
if(paramLength != PARAM_TRANSPORT_PROTOCOL_LENGTH) {
Log.e(TAG, "Received PARAM_TRANSPORT_PROTOCOL with wrong length: " +
paramLength + " skipping this parameter.");
- skip(is, paramLength + (4 - (paramLength % 4)));
+ skip(is, paramLength + skipLen);
success = false;
} else {
mTransportProtocol = is.read();
@@ -497,95 +502,81 @@ public class SapMessage {
}
break;
case PARAM_CONNECTION_STATUS_ID:
- // not needed - server -> client
- if(TEST) {
- if(paramLength != PARAM_CONNECTION_STATUS_LENGTH) {
- Log.e(TAG, "Received PARAM_CONNECTION_STATUS with wrong length: " +
- paramLength + " skipping this parameter.");
- skip(is, paramLength + (4 - (paramLength % 4)));
- success = false;
- } else {
- mConnectionStatus = is.read();
- skip(is, 4 - PARAM_CONNECTION_STATUS_LENGTH);
- }
- break;
- } // Fall through if TEST == false
+ // not needed for server role, but used for module test
+ if(paramLength != PARAM_CONNECTION_STATUS_LENGTH) {
+ Log.e(TAG, "Received PARAM_CONNECTION_STATUS with wrong length: " +
+ paramLength + " skipping this parameter.");
+ skip(is, paramLength + skipLen);
+ success = false;
+ } else {
+ mConnectionStatus = is.read();
+ skip(is, 4 - PARAM_CONNECTION_STATUS_LENGTH);
+ }
+ break;
case PARAM_CARD_READER_STATUS_ID:
- // not needed - server -> client
- if(TEST) {
- if(paramLength != PARAM_CARD_READER_STATUS_LENGTH) {
- Log.e(TAG, "Received PARAM_CARD_READER_STATUS with wrong length: " +
- paramLength + " skipping this parameter.");
- skip(is, paramLength + (4 - (paramLength % 4)));
- success = false;
- } else {
- mCardReaderStatus = is.read();
- skip(is, 4 - PARAM_CARD_READER_STATUS_LENGTH);
- }
- break;
- } // Fall through if TEST == false
+ // not needed for server role, but used for module test
+ if(paramLength != PARAM_CARD_READER_STATUS_LENGTH) {
+ Log.e(TAG, "Received PARAM_CARD_READER_STATUS with wrong length: " +
+ paramLength + " skipping this parameter.");
+ skip(is, paramLength + skipLen);
+ success = false;
+ } else {
+ mCardReaderStatus = is.read();
+ skip(is, 4 - PARAM_CARD_READER_STATUS_LENGTH);
+ }
+ break;
case PARAM_STATUS_CHANGE_ID:
- // not needed - server -> client
- if(TEST) {
- if(paramLength != PARAM_STATUS_CHANGE_LENGTH) {
- Log.e(TAG, "Received PARAM_STATUS_CHANGE with wrong length: " +
- paramLength + " skipping this parameter.");
- skip(is, paramLength + (4 - (paramLength % 4)));
- success = false;
- } else {
- mStatusChange = is.read();
- skip(is, 4 - PARAM_STATUS_CHANGE_LENGTH);
- }
- break;
- } // Fall through if TEST == false
+ // not needed for server role, but used for module test
+ if(paramLength != PARAM_STATUS_CHANGE_LENGTH) {
+ Log.e(TAG, "Received PARAM_STATUS_CHANGE with wrong length: " +
+ paramLength + " skipping this parameter.");
+ skip(is, paramLength + skipLen);
+ success = false;
+ } else {
+ mStatusChange = is.read();
+ skip(is, 4 - PARAM_STATUS_CHANGE_LENGTH);
+ }
+ break;
case PARAM_RESULT_CODE_ID:
- // not needed - server -> client
- if(TEST) {
- if(paramLength != PARAM_RESULT_CODE_LENGTH) {
- Log.e(TAG, "Received PARAM_RESULT_CODE with wrong length: " +
- paramLength + " skipping this parameter.");
- skip(is, paramLength + (4 - (paramLength % 4)));
- success = false;
- } else {
- mResultCode = is.read();
- skip(is, 4 - PARAM_RESULT_CODE_LENGTH);
- }
- break;
- } // Fall through if TEST == false
+ // not needed for server role, but used for module test
+ if(paramLength != PARAM_RESULT_CODE_LENGTH) {
+ Log.e(TAG, "Received PARAM_RESULT_CODE with wrong length: " +
+ paramLength + " skipping this parameter.");
+ skip(is, paramLength + skipLen);
+ success = false;
+ } else {
+ mResultCode = is.read();
+ skip(is, 4 - PARAM_RESULT_CODE_LENGTH);
+ }
+ break;
case PARAM_DISCONNECT_TYPE_ID:
- // not needed - server -> client
- if(TEST) {
- if(paramLength != PARAM_DISCONNECT_TYPE_LENGTH) {
- Log.e(TAG, "Received PARAM_DISCONNECT_TYPE_ID with wrong length: " +
- paramLength + " skipping this parameter.");
- skip(is, paramLength + (4 - (paramLength % 4)));
- success = false;
- } else {
- mDisconnectionType = is.read();
- skip(is, 4 - PARAM_DISCONNECT_TYPE_LENGTH);
- }
- break;
- } // Fall through if TEST == false
+ // not needed for server role, but used for module test
+ if(paramLength != PARAM_DISCONNECT_TYPE_LENGTH) {
+ Log.e(TAG, "Received PARAM_DISCONNECT_TYPE_ID with wrong length: " +
+ paramLength + " skipping this parameter.");
+ skip(is, paramLength + skipLen);
+ success = false;
+ } else {
+ mDisconnectionType = is.read();
+ skip(is, 4 - PARAM_DISCONNECT_TYPE_LENGTH);
+ }
+ break;
case PARAM_RESPONSE_APDU_ID:
- // not needed - server -> client
- if(TEST) {
- mApduResp = new byte[paramLength];
- read(is, mApduResp);
- skip(is, 4 - (paramLength % 4));
- break;
- } // Fall through if TEST == false
+ // not needed for server role, but used for module test
+ mApduResp = new byte[paramLength];
+ read(is, mApduResp);
+ skip(is, skipLen);
+ break;
case PARAM_ATR_ID:
- // not needed - server -> client
- if(TEST) {
- mAtr = new byte[paramLength];
- read(is, mAtr);
- skip(is, 4 - (paramLength % 4));
- break;
- } // Fall through if TEST == false
+ // not needed for server role, but used for module test
+ mAtr = new byte[paramLength];
+ read(is, mAtr);
+ skip(is, skipLen);
+ break;
default:
Log.e(TAG, "Received unknown parameter ID: " + paramId + " length: " +
paramLength + " skipping this parameter.");
- skip(is, paramLength + (4 - (paramLength % 4)));
+ skip(is, paramLength + skipLen);
}
}
return success;
@@ -643,8 +634,10 @@ public class SapMessage {
/* Payload */
os.write(value);
- for(int i = 0, n = 4 - (value.length % 4) ; i < n; i++) {
- os.write(0); // Padding
+ if (value.length % 4 != 0) {
+ for (int i = 0; i < (4 - (value.length % 4)); ++i) {
+ os.write(0); // Padding
+ }
}
}
@@ -668,7 +661,7 @@ public class SapMessage {
writeParameter(os, PARAM_RESULT_CODE_ID, mResultCode,
PARAM_RESULT_CODE_LENGTH);
}
- if(mDisconnectionType != INVALID_VALUE && TEST) {
+ if(mDisconnectionType != INVALID_VALUE) {
writeParameter(os, PARAM_DISCONNECT_TYPE_ID, mDisconnectionType,
PARAM_DISCONNECT_TYPE_LENGTH);
}
@@ -680,14 +673,14 @@ public class SapMessage {
writeParameter(os, PARAM_STATUS_CHANGE_ID, mStatusChange,
PARAM_STATUS_CHANGE_LENGTH);
}
- if(mTransportProtocol != INVALID_VALUE && TEST) {
+ if(mTransportProtocol != INVALID_VALUE) {
writeParameter(os, PARAM_TRANSPORT_PROTOCOL_ID, mTransportProtocol,
PARAM_TRANSPORT_PROTOCOL_LENGTH);
}
- if(mApdu != null && TEST) {
+ if(mApdu != null) {
writeParameter(os, PARAM_COMMAND_APDU_ID, mApdu);
}
- if(mApdu7816 != null && TEST) {
+ if(mApdu7816 != null) {
writeParameter(os, PARAM_COMMAND_APDU7816_ID, mApdu7816);
}
if(mApduResp != null) {
@@ -838,10 +831,8 @@ public class SapMessage {
break;
}
default:
- if(TEST == false) {
- Log.e(TAG, "Unknown request type");
- throw new IllegalArgumentException();
- }
+ Log.e(TAG, "Unknown request type");
+ throw new IllegalArgumentException();
}
/* Update the ongoing requests queue */
if(mClearRilQueue == true) {
@@ -1214,7 +1205,7 @@ public class SapMessage {
public static String getMsgTypeName(int msgType) {
- if(TEST || VERBOSE) {
+ if(DEBUG || VERBOSE) {
switch (msgType)
{
case ID_CONNECT_REQ: return "ID_CONNECT_REQ";
diff --git a/src/com/android/bluetooth/sap/SapServer.java b/src/com/android/bluetooth/sap/SapServer.java
index 4135a2684..339f67631 100644
--- a/src/com/android/bluetooth/sap/SapServer.java
+++ b/src/com/android/bluetooth/sap/SapServer.java
@@ -8,6 +8,7 @@ import java.io.OutputStream;
import java.util.concurrent.CountDownLatch;
import com.android.bluetooth.R;
+
import android.app.AlarmManager;
import android.app.Notification;
import android.app.NotificationManager;
@@ -26,8 +27,10 @@ import android.os.Looper;
import android.os.Message;
import android.os.Parcel;
import android.os.SystemClock;
+import android.os.SystemProperties;
import android.telephony.TelephonyManager;
import android.util.Log;
+
//import com.android.internal.telephony.RIL;
import com.google.protobuf.micro.CodedOutputStreamMicro;
@@ -48,7 +51,6 @@ public class SapServer extends Thread implements Callback {
private static final String TAG_HANDLER = "SapServerHandler";
public static final boolean DEBUG = SapService.DEBUG;
public static final boolean VERBOSE = SapService.VERBOSE;
- public static final boolean PTS_TEST = SapService.PTS_TEST;
private enum SAP_STATE {
DISCONNECTED, CONNECTING, CONNECTING_CALL_ONGOING, CONNECTED,
@@ -212,6 +214,13 @@ public class SapServer extends Thread implements Callback {
String title, text, button, ticker;
Notification notification;
if(VERBOSE) Log.i(TAG, "setNotification type: " + type);
+ /* For PTS TC_SERVER_DCN_BV_03_I we need to expose the option to send immediate disconnect
+ * without first sending a graceful disconnect.
+ * To enable this option set
+ * bt.sap.pts="true" */
+ String pts_enabled = SystemProperties.get("bt.sap.pts");
+ Boolean pts_test = Boolean.parseBoolean(pts_enabled);
+
/* put notification up for the user to be able to disconnect from the client*/
Intent sapDisconnectIntent = new Intent(SapServer.SAP_DISCONNECT_ACTION);
if(type == SapMessage.DISC_GRACEFULL){
@@ -225,7 +234,7 @@ public class SapServer extends Thread implements Callback {
text = mContext.getString(R.string.bluetooth_sap_notif_disconnecting);
ticker = mContext.getString(R.string.bluetooth_sap_notif_ticker);
}
- if(!PTS_TEST)
+ if(!pts_test)
{
sapDisconnectIntent.putExtra(SapServer.SAP_DISCONNECT_TYPE_EXTRA, type);
PendingIntent pIntentDisconnect = PendingIntent.getBroadcast(mContext, type,
@@ -413,7 +422,10 @@ public class SapServer extends Thread implements Callback {
* - Initiate a FORCED shutdown
* - Wait for RIL deinit to complete
*/
- if(mState != SAP_STATE.DISCONNECTED) {
+ if (mState == SAP_STATE.CONNECTING_CALL_ONGOING) {
+ /* Most likely remote device closed rfcomm, update state */
+ changeState(SAP_STATE.DISCONNECTED);
+ } else if (mState != SAP_STATE.DISCONNECTED) {
if(mState != SAP_STATE.DISCONNECTING &&
mIsLocalInitDisconnect != true) {
sendDisconnectInd(SapMessage.DISC_FORCED);
@@ -500,7 +512,6 @@ public class SapServer extends Thread implements Callback {
if (isCallOngoing() == true) {
/* If a call is ongoing we set the state, inform the SAP client and wait for a state
* change intent from the TelephonyManager with state IDLE. */
- changeState(SAP_STATE.CONNECTING_CALL_ONGOING);
reply.setConnectionStatus(SapMessage.CON_STATUS_OK_ONGOING_CALL);
} else {
/* no call is ongoing, initiate the connect sequence:
@@ -642,11 +653,12 @@ public class SapServer extends Thread implements Callback {
if(DEBUG) Log.i(TAG_HANDLER, "in Shutdown()");
try {
- mRfcommOut.close();
+ if (mRfcommOut != null)
+ mRfcommOut.close();
} catch (IOException e) {}
try {
- mRfcommIn.close();
-
+ if (mRfcommIn != null)
+ mRfcommIn.close();
} catch (IOException e) {}
mRfcommIn = null;
mRfcommOut = null;
@@ -705,19 +717,25 @@ public class SapServer extends Thread implements Callback {
switch(sapMsg.getMsgType()) {
case SapMessage.ID_CONNECT_RESP:
- if (sapMsg.getConnectionStatus() == SapMessage.CON_STATUS_OK) {
- // This is successful connect response from RIL/modem.
- changeState(SAP_STATE.CONNECTED);
- } else if(sapMsg.getConnectionStatus() == SapMessage.CON_STATUS_OK_ONGOING_CALL
- && mState != SAP_STATE.CONNECTING_CALL_ONGOING) {
- changeState(SAP_STATE.CONNECTING_CALL_ONGOING);
- } else if(mState == SAP_STATE.CONNECTING_CALL_ONGOING) {
+ if(mState == SAP_STATE.CONNECTING_CALL_ONGOING) {
/* Hold back the connect resp if a call was ongoing when the connect req
- * was received.
+ * was received.
+ * A response with status call-ongoing was sent, and the connect response
+ * received from the RIL when call ends must be discarded.
*/
+ if (sapMsg.getConnectionStatus() == SapMessage.CON_STATUS_OK) {
+ // This is successful connect response from RIL/modem.
+ changeState(SAP_STATE.CONNECTED);
+ }
if(VERBOSE) Log.i(TAG, "Hold back the connect resp, as a call was ongoing" +
" when the initial response were sent.");
sapMsg = null;
+ } else if (sapMsg.getConnectionStatus() == SapMessage.CON_STATUS_OK) {
+ // This is successful connect response from RIL/modem.
+ changeState(SAP_STATE.CONNECTED);
+ } else if(sapMsg.getConnectionStatus() ==
+ SapMessage.CON_STATUS_OK_ONGOING_CALL) {
+ changeState(SAP_STATE.CONNECTING_CALL_ONGOING);
} else if(sapMsg.getConnectionStatus() != SapMessage.CON_STATUS_OK) {
/* Most likely the peer will try to connect again, hence we keep the
* connection to RIL open and stay in connecting state.
@@ -822,11 +840,18 @@ public class SapServer extends Thread implements Callback {
if(VERBOSE) Log.i(TAG_HANDLER, "sendRilMessage() - "
+ SapMessage.getMsgTypeName(sapMsg.getMsgType()));
try {
- sapMsg.writeReqToStream(mRilBtOutStream);
+ if(mRilBtOutStream != null) {
+ sapMsg.writeReqToStream(mRilBtOutStream);
+ } /* Else SAP was enabled on a build that did not support SAP, which we will not
+ * handle. */
} catch (IOException e) {
Log.e(TAG_HANDLER, "Unable to send message to RIL", e);
SapMessage errorReply = new SapMessage(SapMessage.ID_ERROR_RESP);
sendClientMessage(errorReply);
+ } catch (IllegalArgumentException e) {
+ Log.e(TAG_HANDLER, "Unable encode message", e);
+ SapMessage errorReply = new SapMessage(SapMessage.ID_ERROR_RESP);
+ sendClientMessage(errorReply);
}
}
diff --git a/src/com/android/bluetooth/sap/SapService.java b/src/com/android/bluetooth/sap/SapService.java
index d5caf8065..23d69ff03 100644
--- a/src/com/android/bluetooth/sap/SapService.java
+++ b/src/com/android/bluetooth/sap/SapService.java
@@ -47,7 +47,6 @@ public class SapService extends ProfileService {
private static final String TAG = "SapService";
public static final boolean DEBUG = false;
public static final boolean VERBOSE = false;
- public static final boolean PTS_TEST = false;
/* Message ID's */
private static final int START_LISTENER = 1;