handleSinkStateChange(device, state, BluetoothA2dp.STATE_DISCONNECTED);
}
}
+ } else if (action.equals(AudioManager.VOLUME_CHANGED_ACTION)) {
+ int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1);
+ if (streamType == AudioManager.STREAM_MUSIC) {
+ BluetoothDevice sinks[] = getConnectedSinks();
+ if (sinks.length != 0 && isPhoneDocked(sinks[0])) {
+ String address = sinks[0].getAddress();
+ int newVolLevel =
+ intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, 0);
+ int oldVolLevel =
+ intent.getIntExtra(AudioManager.EXTRA_PREV_VOLUME_STREAM_VALUE, 0);
+ String path = mBluetoothService.getObjectPathFromAddress(address);
+ if (newVolLevel > oldVolLevel) {
+ avrcpVolumeUpNative(path);
+ } else if (newVolLevel < oldVolLevel) {
+ avrcpVolumeDownNative(path);
+ }
+ }
+ }
}
}
};
+
+ private boolean isPhoneDocked(BluetoothDevice device) {
+ // This works only because these broadcast intents are "sticky"
+ Intent i = mContext.registerReceiver(null, new IntentFilter(Intent.ACTION_DOCK_EVENT));
+ if (i != null) {
+ int state = i.getIntExtra(Intent.EXTRA_DOCK_STATE, Intent.EXTRA_DOCK_STATE_UNDOCKED);
+ if (state != Intent.EXTRA_DOCK_STATE_UNDOCKED) {
+ BluetoothDevice dockDevice = i.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+ if (dockDevice != null && device.equals(dockDevice)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
public BluetoothA2dpService(Context context, BluetoothService bluetoothService) {
mContext = context;
mIntentFilter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
mIntentFilter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED);
mIntentFilter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);
+ mIntentFilter.addAction(AudioManager.VOLUME_CHANGED_ACTION);
mContext.registerReceiver(mReceiver, mIntentFilter);
mAudioDevices = new HashMap<BluetoothDevice, Integer>();
private synchronized native boolean suspendSinkNative(String path);
private synchronized native boolean resumeSinkNative(String path);
private synchronized native Object []getSinkPropertiesNative(String path);
+ private synchronized native boolean avrcpVolumeUpNative(String path);
+ private synchronized native boolean avrcpVolumeDownNative(String path);
}
return JNI_FALSE;
}
+static jboolean avrcpVolumeUpNative(JNIEnv *env, jobject object,
+ jstring path) {
+#ifdef HAVE_BLUETOOTH
+ LOGV(__FUNCTION__);
+ if (nat) {
+ const char *c_path = env->GetStringUTFChars(path, NULL);
+ bool ret = dbus_func_args_async(env, nat->conn, -1, NULL, NULL, nat,
+ c_path, "org.bluez.Control", "VolumeUp",
+ DBUS_TYPE_INVALID);
+ env->ReleaseStringUTFChars(path, c_path);
+ return ret ? JNI_TRUE : JNI_FALSE;
+ }
+#endif
+ return JNI_FALSE;
+}
+
+static jboolean avrcpVolumeDownNative(JNIEnv *env, jobject object,
+ jstring path) {
+#ifdef HAVE_BLUETOOTH
+ LOGV(__FUNCTION__);
+ if (nat) {
+ const char *c_path = env->GetStringUTFChars(path, NULL);
+ bool ret = dbus_func_args_async(env, nat->conn, -1, NULL, NULL, nat,
+ c_path, "org.bluez.Control", "VolumeDown",
+ DBUS_TYPE_INVALID);
+ env->ReleaseStringUTFChars(path, c_path);
+ return ret ? JNI_TRUE : JNI_FALSE;
+ }
+#endif
+ return JNI_FALSE;
+}
+
#ifdef HAVE_BLUETOOTH
DBusHandlerResult a2dp_event_filter(DBusMessage *msg, JNIEnv *env) {
DBusError err;
free(user);
}
+
#endif
{"resumeSinkNative", "(Ljava/lang/String;)Z", (void*)resumeSinkNative},
{"getSinkPropertiesNative", "(Ljava/lang/String;)[Ljava/lang/Object;",
(void *)getSinkPropertiesNative},
+ {"avrcpVolumeUpNative", "(Ljava/lang/String;)Z", (void*)avrcpVolumeUpNative},
+ {"avrcpVolumeDownNative", "(Ljava/lang/String;)Z", (void*)avrcpVolumeDownNative},
};
int register_android_server_BluetoothA2dpService(JNIEnv *env) {