diff options
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; |