Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/ClusterM/sony-headphones-control.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlexet Avdyukhin <a.avdyukhin@norsi-trans.ru>2022-03-05 17:49:18 +0300
committerAlexey 'Cluster' Avdyukhin <a.avdyukhin@norsi-trans.ru>2022-03-05 17:54:23 +0300
commitdc57e03cd71edc1f76af6261d756f4051e87b8ec (patch)
tree420ee7109549b6f39620d0648ee233d235ab6c6f
parentcbf75bebc9d8ad68ae0e09da67079f7ddcac346e (diff)
Refactoring, support for multiple paired headsets, official app process killing
-rw-r--r--app/src/main/AndroidManifest.xml2
-rw-r--r--app/src/main/java/com/clusterrr/sonyheadphonescontrol/HeadsetPacket.java26
-rw-r--r--app/src/main/java/com/clusterrr/sonyheadphonescontrol/TaskerFireReceiver.java247
-rw-r--r--app/src/main/res/values/strings.xml3
4 files changed, 210 insertions, 68 deletions
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index f0d2db9..b8dbeca 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -19,6 +19,8 @@
<uses-permission android:name="android.permission.BLUETOOTH" />
+ <uses-permission android:name="android.permission.KILL_BACKGROUND_PROCESSES" />
+
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
diff --git a/app/src/main/java/com/clusterrr/sonyheadphonescontrol/HeadsetPacket.java b/app/src/main/java/com/clusterrr/sonyheadphonescontrol/HeadsetPacket.java
new file mode 100644
index 0000000..7064d9e
--- /dev/null
+++ b/app/src/main/java/com/clusterrr/sonyheadphonescontrol/HeadsetPacket.java
@@ -0,0 +1,26 @@
+package com.clusterrr.sonyheadphonescontrol;
+
+public class HeadsetPacket {
+ private byte command;
+ private boolean toggle;
+ private byte[] data;
+
+ public HeadsetPacket(byte command, boolean toggle, byte[] data){
+ this.command = command;
+ this.toggle = toggle;
+ this.data = new byte[data.length];
+ System.arraycopy(data, 0, this.data,0, data.length);
+ }
+
+ public byte getCommand(){
+ return command;
+ }
+
+ public boolean getToggle() {
+ return toggle;
+ }
+
+ public byte[] getData() {
+ return data;
+ }
+}
diff --git a/app/src/main/java/com/clusterrr/sonyheadphonescontrol/TaskerFireReceiver.java b/app/src/main/java/com/clusterrr/sonyheadphonescontrol/TaskerFireReceiver.java
index 0eaa2ac..95dc41a 100644
--- a/app/src/main/java/com/clusterrr/sonyheadphonescontrol/TaskerFireReceiver.java
+++ b/app/src/main/java/com/clusterrr/sonyheadphonescontrol/TaskerFireReceiver.java
@@ -12,6 +12,7 @@ package com.clusterrr.sonyheadphonescontrol;
* See the License for the specific language governing permissions and limitations under the License.
*/
+import android.app.ActivityManager;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothManager;
@@ -24,6 +25,8 @@ import android.os.ParcelUuid;
import android.util.Log;
import android.widget.Toast;
+import androidx.annotation.NonNull;
+
import com.twofortyfouram.spackle.bundle.BundleScrubber;
import net.dinglisch.android.tasker.TaskerPlugin;
@@ -31,22 +34,36 @@ import net.dinglisch.android.tasker.TaskerPlugin;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.lang.reflect.Method;
import java.util.Date;
-import java.util.List;
import java.util.Set;
import java.util.UUID;
-import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeoutException;
public final class TaskerFireReceiver extends BroadcastReceiver {
public static final String TAG = "SonyHeadphonesControl";
- public static final UUID uuid = UUID.fromString("96cc203e-5068-46ad-b32d-e316f5e069ba");
- public static final UUID uuid_alt = UUID.fromString("ba69e0f5-16e3-2db3-ad46-68503e20cc96");
+ public static final UUID[] HEADSET_UUIDS = new UUID[]{
+ UUID.fromString("96cc203e-5068-46ad-b32d-e316f5e069ba"),
+ UUID.fromString("ba69e0f5-16e3-2db3-ad46-68503e20cc96")
+ };
public static final String ACTION_FIRE_SETTING = "com.twofortyfouram.locale.intent.action.FIRE_SETTING"; //$NON-NLS-1$
public static final String EXTRA_BUNDLE = "com.twofortyfouram.locale.intent.extra.BUNDLE"; //$NON-NLS-1$
public static final String EXTRA_STRING_BLURB = "com.twofortyfouram.locale.intent.extra.BLURB"; //$NON-NLS-1$
public static final String EXTRA_STRING_MODE = "mode";
public static final String EXTRA_STRING_VOLUME = "volume";
public static final String EXTRA_STRING_VOICE = "voice";
+ public static final int RECV_TIMEOUT = 200;
+ public static final byte MAGIC_PACKET_START = 0x3e;
+ public static final byte MAGIC_PACKET_END = 0x3c;
+ public static final byte COMMAND_ACK = 0x00;
+ public static final byte COMMAND_SET_MODE = 0x08;
+ public static final byte MODE_NOISE_CANCELLING = 2;
+ public static final byte MODE_WIND_CANCELLING = 1;
+ public static final byte MODE_AMBIENT_SOUND = 0;
+ public static final byte KEY_OFF = 0;
+ public static final byte KEY_NOISE_CANCELLING = 1;
+ public static final byte KEY_WIND_CANCELLING = 2;
+ public static final byte KEY_AMBIENT_SOUND = 3;
@Override
public void onReceive(final Context context, final Intent intent) {
@@ -66,29 +83,36 @@ public final class TaskerFireReceiver extends BroadcastReceiver {
execute(context, intent, mode, volume, voice);
}
- public static void execute(Context context, Intent intent, int mode, int volume, boolean voice) {
-
+ public static void execute(Context context, Intent intent, int mode, int volume, boolean voiceOptimized) {
boolean enabled = false;
- int noiseCancelling = 0;
+ byte noiseCancelling;
switch (mode) {
- case 0:
- enabled = false;
- break;
- case 1:
+ case KEY_NOISE_CANCELLING:
enabled = true;
- noiseCancelling = 2;
+ noiseCancelling = MODE_NOISE_CANCELLING;
+ volume = 0;
+ voiceOptimized = false;
break;
- case 2:
+ case KEY_WIND_CANCELLING:
enabled = true;
- noiseCancelling = 1;
+ noiseCancelling = MODE_WIND_CANCELLING;
+ volume = 0;
+ voiceOptimized = false;
break;
- case 3:
+ case KEY_AMBIENT_SOUND:
enabled = true;
+ noiseCancelling = MODE_AMBIENT_SOUND;
+ break;
+ default:
+ enabled = false;
noiseCancelling = 0;
+ volume = 0;
+ voiceOptimized = false;
+ break;
}
try {
- if (setAmbientSound(context, enabled, noiseCancelling, volume, voice)) {
+ if (setAmbientSound(context, enabled, noiseCancelling, volume, voiceOptimized)) {
if (intent != null)
TaskerPlugin.Setting.signalFinish(context, intent, TaskerPlugin.Setting.RESULT_CODE_OK, null);
else
@@ -105,7 +129,8 @@ public final class TaskerFireReceiver extends BroadcastReceiver {
}
} catch (IOException e) {
e.printStackTrace();
- final String message = context.getString(R.string.io_error);
+ final String message = context.getString(R.string.io_error) + ": " + e.getMessage() +
+ "\r\n" + context.getString(R.string.io_error_hint);
if (intent != null) {
Bundle vars = new Bundle();
vars.putString(TaskerPlugin.Setting.VARNAME_ERROR_MESSAGE, message);
@@ -115,90 +140,176 @@ public final class TaskerFireReceiver extends BroadcastReceiver {
}
} catch (InterruptedException e) {
e.printStackTrace();
+ final String message = context.getString(R.string.interrupted_error);
+ if (intent != null) {
+ Bundle vars = new Bundle();
+ vars.putString(TaskerPlugin.Setting.VARNAME_ERROR_MESSAGE, message);
+ TaskerPlugin.Setting.signalFinish(context, intent, TaskerPlugin.Setting.RESULT_CODE_FAILED, vars);
+ } else {
+ Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
+ }
+ } catch (TimeoutException e) {
+ e.printStackTrace();
+ final String message = context.getString(R.string.timeout_error);
+ if (intent != null) {
+ Bundle vars = new Bundle();
+ vars.putString(TaskerPlugin.Setting.VARNAME_ERROR_MESSAGE, message);
+ TaskerPlugin.Setting.signalFinish(context, intent, TaskerPlugin.Setting.RESULT_CODE_FAILED, vars);
+ } else {
+ Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
+ }
}
}
- static boolean setAmbientSound(Context context, boolean enabled, int noiseCancelling, int volume, boolean voice) throws IOException, InterruptedException {
- return sendData(context, new byte[]{0x00, 0x00, 0x00, 0x08, 0x68, 0x02, (byte) (enabled ? 0x10 : 0x00), 0x02, (byte) (noiseCancelling), 0x01, (byte) (voice ? 1 : 0), (byte) volume});
+ static boolean setAmbientSound(Context context, boolean enabled, byte noiseCancelling, int volume, boolean voice)
+ throws IOException, InterruptedException, TimeoutException {
+ return findByUUIDAndSend(context, HEADSET_UUIDS, COMMAND_SET_MODE,
+ new byte[]{0x68, 0x02,
+ (byte) (enabled ? 0x10 : 0x00), 0x02, (byte) (noiseCancelling), 0x01,
+ (byte) (voice ? 1 : 0), (byte) volume});
}
- static boolean sendData(Context context, byte[] data) throws IOException, InterruptedException {
+ static boolean findByUUIDAndSend(Context context, UUID[] uuids, byte command, byte[] data)
+ throws IOException, InterruptedException, TimeoutException {
BluetoothDevice headset = null;
- BluetoothManager bluetoothManager = (BluetoothManager) context.getSystemService(Context.BLUETOOTH_SERVICE);
- BluetoothAdapter adapter = bluetoothManager.getAdapter();
+ UUID uuid = null;
+
+ ActivityManager activityManager = (ActivityManager)context.getSystemService(Context.ACTIVITY_SERVICE);
+ activityManager.killBackgroundProcesses("com.sony.songpal.mdr");
+ BluetoothManager bluetoothManager =
+ (BluetoothManager) context.getSystemService(Context.BLUETOOTH_SERVICE);
+ BluetoothAdapter adapter = bluetoothManager.getAdapter();
Set<BluetoothDevice> devices = adapter.getBondedDevices();
for (BluetoothDevice device : devices) {
- ParcelUuid[] uuids = device.getUuids();
-
- if(uuids == null)
- continue;
-
- for (ParcelUuid u : uuids) {
- if (u.toString().equals(uuid.toString()) || u.toString().equals(uuid_alt.toString())) {
- headset = device;
- break;
+ if (!isDeviceConnected(device)) continue;
+ ParcelUuid[] DeviceUUIDs = device.getUuids();
+ if (uuids == null) continue;
+ for (ParcelUuid deviceUUID : DeviceUUIDs) {
+ for (UUID allowedUUIDs : uuids) {
+ if (deviceUUID.toString().equals(allowedUUIDs.toString())) {
+ headset = device;
+ uuid = allowedUUIDs;
+ break;
+ }
}
+ if (headset != null) break;
}
- if (headset != null)
- break;
+ if (headset != null) break;
}
if (headset == null) {
-// Log.e(TAG, "Headset not found");
+ Log.e(TAG, "Headset not found");
return false;
} else {
-// Log.d(TAG, "Headset found: " + headset.getAddress() + " " + headset.getName());
+ Log.i(TAG, "Headset found: " + headset.getAddress() + " " + headset.getName());
}
BluetoothSocket socket = headset.createRfcommSocketToServiceRecord(uuid);
try {
-// Log.i(TAG, "Socket connected: " + socket.isConnected());
socket.connect();
-// Log.i(TAG, "Socket connected: " + socket.isConnected());
-
- byte[] packet = new byte[data.length + 2];
- packet[0] = 0x0c;
- packet[1] = 0;
- for (int j = 0; j < data.length; j++) {
- packet[j + 2] = data[j];
- }
- sendPacket(socket, packet);
- packet[1] = 1;
- sendPacket(socket, packet);
-
- return true;
+ Log.d(TAG, "BluetoothSocket connected: " + socket.isConnected());
+ OutputStream o = socket.getOutputStream();
+ InputStream i = socket.getInputStream();
+ HeadsetPacket r;
+ sendPacket(o, command, false, data);
+ r = recvPacket(i, RECV_TIMEOUT);
+ if ((r.getCommand() != COMMAND_ACK) ||
+ (r.getData().length < 1) ||
+ (r.getData()[0] != 2))
+ throw new IOException("Invalid answer");
} finally {
socket.close();
}
+ return true;
}
- static void sendPacket(BluetoothSocket socket, byte[] data) throws IOException, InterruptedException {
- OutputStream o = socket.getOutputStream();
- InputStream i = socket.getInputStream();
- byte[] packet = new byte[data.length + 3];
- packet[0] = 0x3e;
- packet[packet.length - 1] = 0x3c;
- byte crc = 0;
+ static void sendPacket(@NonNull OutputStream o, @NonNull HeadsetPacket packet) throws IOException, InterruptedException {
+ sendPacket(o, packet.getCommand(), packet.getToggle(), packet.getData());
+ }
+
+ static void sendPacket(@NonNull OutputStream o, @NonNull byte command, @NonNull boolean toggle, @NonNull byte[] data)
+ throws IOException, InterruptedException {
+ byte[] packet = new byte[data.length + 9];
+ packet[0] = MAGIC_PACKET_START;
+ packet[1] = (byte) (data.length + 4);
+ packet[2] = toggle ? (byte) 1 : (byte) 0;
+ packet[3] = packet[4] = packet[5] = 0;
+ packet[6] = command;
for (int j = 0; j < data.length; j++) {
- crc += data[j];
- packet[j + 1] = data[j];
+ packet[j + 7] = data[j];
+ }
+ byte crc = 0;
+ for (int j = 1; j < packet.length - 2; j++) {
+ crc += packet[j];
+ }
+ packet[packet.length - 2] = (byte)crc;
+ packet[packet.length - 1] = MAGIC_PACKET_END;
+
+ StringBuilder sb = new StringBuilder();
+ for (int j = 0; j < packet.length; j++) {
+ sb.append(String.format(" %02x", packet[j]));
}
- packet[packet.length - 2] = crc;
+ Log.d(TAG, "write:" + sb);
o.write(packet);
+ }
- byte[] buffer = new byte[256];
- Date date = new Date();
- while (new Date().getTime() - date.getTime() < 200) {
+ public static HeadsetPacket recvPacket(@NonNull InputStream i, @NonNull int timeout) throws TimeoutException, IOException {
+ byte[] recvBuffer = new byte[256];
+ int received = 0;
+ int packetLength = -1;
+ int dataLength = -1;
+ int time = 0;
+ while ((time < timeout) && ((packetLength < 0) || (received < packetLength))) {
if (i.available() > 0) {
- int r = i.read(buffer);
- StringBuilder sb = new StringBuilder();
- for (int j = 0; j < r; j++) {
- sb.append(String.format(" %02x", buffer[j]));
+ int r = i.read(recvBuffer, received, recvBuffer.length - received);
+ received += r;
+ time = 0;
+ if ((received >= 1) && (recvBuffer[0] != MAGIC_PACKET_START))
+ throw new IOException("Invalid start magic");
+ if (received >= 2) {
+ dataLength = recvBuffer[1];
+ packetLength = recvBuffer[1] + 8;
}
-// Log.i(TAG, "Read: " + r + " bytes:" + sb);
+ }
+ try {
+ Thread.sleep(1);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
break;
}
- Thread.sleep(50);
+ time++;
+ }
+ if ((packetLength < 0) || (received < packetLength))
+ throw new TimeoutException();
+
+ StringBuilder sb = new StringBuilder();
+ for (int j = 0; j < packetLength; j++) {
+ sb.append(String.format(" %02x", recvBuffer[j]));
+ }
+ Log.d(TAG, "read:" + sb);
+
+ if (recvBuffer[packetLength-1] != MAGIC_PACKET_END)
+ throw new IOException("Invalid end magic");
+ byte crc = 0;
+ for (int j = 1; j < packetLength - 2; j++) {
+ crc += recvBuffer[j];
+ }
+ if (crc != recvBuffer[packetLength - 2])
+ throw new IOException("Invalid CRC");
+ boolean toggle = recvBuffer[2] != 0;
+ byte command = recvBuffer[6];
+ byte[] data = new byte[dataLength];
+ System.arraycopy(recvBuffer, 7, data, 0, dataLength);
+ return new HeadsetPacket(command, toggle, data);
+ }
+
+ public static boolean isDeviceConnected(BluetoothDevice device) {
+ try {
+ Method m = device.getClass().getMethod("isConnected", (Class[]) null);
+ boolean connected = (boolean) m.invoke(device, (Object[]) null);
+ return connected;
+ } catch (Exception e) {
+ throw new IllegalStateException(e);
}
}
} \ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 1b83a90..1cce210 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -10,6 +10,9 @@
<string name="save">Save</string>
<string name="apply">Apply</string>
<string name="io_error">IO error (Another application using a headset? Is it connected?)</string>
+ <string name="io_error_hint">IO error (Another application using a headset? Is it connected?)</string>
<string name="headset_not_found">Sony Headset is not found</string>
<string name="ok">OK</string>
+ <string name="timeout_error">Error: timeout</string>
+ <string name="interrupted_error">Error: interrupted</string>
</resources>