diff options
author | Marko Zajc <marko@zajc.eu.org> | 2021-01-10 15:32:04 +0100 |
---|---|---|
committer | Marko Zajc <marko@zajc.eu.org> | 2021-01-10 15:32:04 +0100 |
commit | 06cec8d2589961a029920b2ade799c42e71bb465 (patch) | |
tree | e850f086c5d40a5419e5d74c202577afe9a465c1 | |
parent | 624125243d5888c11022377e9c7e4b1d43c84407 (diff) |
-rw-r--r-- | app/build.gradle | 3 | ||||
-rw-r--r-- | app/src/main/AndroidManifest.xml | 36 | ||||
-rw-r--r-- | app/src/main/aidl/org/eu/zajc/xheadset/IXHeadsetStateService.aidl | 5 | ||||
-rw-r--r-- | app/src/main/assets/xposed_init | 2 | ||||
-rw-r--r-- | app/src/main/java/org/eu/zajc/xheadset/XHeadsetModule.java (renamed from app/src/main/java/org/eu/zajc/xheadset/XHeadset.java) | 26 | ||||
-rw-r--r-- | app/src/main/java/org/eu/zajc/xheadset/XHeadsetStateCommunicator.java | 57 | ||||
-rw-r--r-- | app/src/main/java/org/eu/zajc/xheadset/XHeadsetStateService.java | 45 | ||||
-rw-r--r-- | app/src/main/java/org/eu/zajc/xheadset/XHeadsetTile.java | 31 | ||||
-rw-r--r-- | app/src/main/res/drawable/headset.xml | 16 | ||||
-rw-r--r-- | app/src/main/res/drawable/speaker.xml | 13 |
10 files changed, 207 insertions, 27 deletions
diff --git a/app/build.gradle b/app/build.gradle index f98f370..8590326 100644 --- a/app/build.gradle +++ b/app/build.gradle | |||
@@ -18,6 +18,9 @@ android { | |||
18 | lintOptions { | 18 | lintOptions { |
19 | abortOnError false | 19 | abortOnError false |
20 | } | 20 | } |
21 | defaultConfig { | ||
22 | vectorDrawables.useSupportLibrary = true | ||
23 | } | ||
21 | } | 24 | } |
22 | 25 | ||
23 | repositories { | 26 | repositories { |
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index bfc3c5e..76a7b24 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml | |||
@@ -1,16 +1,26 @@ | |||
1 | <manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" | 1 | <manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" package="org.eu.zajc.xheadset"> |
2 | package="org.eu.zajc.xheadset"> | ||
3 | 2 | ||
4 | <application android:label="Headset Enforcer"> | 3 | <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> |
5 | <meta-data | ||
6 | android:name="xposedmodule" | ||
7 | android:value="true" /> | ||
8 | <meta-data | ||
9 | android:name="xposeddescription" | ||
10 | android:value="This Xposed module allows you to manually override the headphone jack detection of an Android device." /> | ||
11 | <meta-data | ||
12 | android:name="xposedminversion" | ||
13 | android:value="53" /> | ||
14 | </application> | ||
15 | 4 | ||
5 | <application android:label="Headset Enforcer"> | ||
6 | |||
7 | <meta-data android:name="xposedmodule" android:value="true" /> | ||
8 | <meta-data android:name="xposeddescription" android:value="An Xposed module that fixes broken headphone detection using software" /> | ||
9 | <meta-data android:name="xposedminversion" android:value="53" /> | ||
10 | <meta-data android:name="android.service.quicksettings.TOGGLEABLE_TILE" android:value="true" /> | ||
11 | |||
12 | <service android:name=".XHeadsetTile" android:icon="@drawable/headset" android:label="Headset" android:permission="android.permission.BIND_QUICK_SETTINGS_TILE"> | ||
13 | <intent-filter> | ||
14 | <action android:name="android.service.quicksettings.action.QS_TILE" /> | ||
15 | </intent-filter> | ||
16 | </service> | ||
17 | |||
18 | <service android:name=".XHeadsetStateService" android:exported="true"> | ||
19 | <intent-filter> | ||
20 | <action android:name="org.eu.zajc.xheadset.XHeadsetStateService.BIND" /> | ||
21 | <category android:name="android.intent.category.DEFAULT" /> | ||
22 | </intent-filter> | ||
23 | </service> | ||
24 | |||
25 | </application> | ||
16 | </manifest> | 26 | </manifest> |
diff --git a/app/src/main/aidl/org/eu/zajc/xheadset/IXHeadsetStateService.aidl b/app/src/main/aidl/org/eu/zajc/xheadset/IXHeadsetStateService.aidl new file mode 100644 index 0000000..ca556bb --- /dev/null +++ b/app/src/main/aidl/org/eu/zajc/xheadset/IXHeadsetStateService.aidl | |||
@@ -0,0 +1,5 @@ | |||
1 | package org.eu.zajc.xheadset; | ||
2 | interface IXHeadsetStateService { | ||
3 | boolean isEnabled(); | ||
4 | boolean setEnabled(boolean enabled); | ||
5 | } | ||
diff --git a/app/src/main/assets/xposed_init b/app/src/main/assets/xposed_init index 18c2324..a00c3e8 100644 --- a/app/src/main/assets/xposed_init +++ b/app/src/main/assets/xposed_init | |||
@@ -1 +1 @@ | |||
org.eu.zajc.xheadset.XHeadset | org.eu.zajc.xheadset.XHeadsetModule | ||
diff --git a/app/src/main/java/org/eu/zajc/xheadset/XHeadset.java b/app/src/main/java/org/eu/zajc/xheadset/XHeadsetModule.java index 19eddc4..4889aa7 100644 --- a/app/src/main/java/org/eu/zajc/xheadset/XHeadset.java +++ b/app/src/main/java/org/eu/zajc/xheadset/XHeadsetModule.java | |||
@@ -1,25 +1,29 @@ | |||
1 | package org.eu.zajc.xheadset; | 1 | package org.eu.zajc.xheadset; |
2 | 2 | ||
3 | import android.graphics.Color; | 3 | import android.util.*; |
4 | import android.widget.TextView; | 4 | import android.app.*; |
5 | import android.widget.*; | ||
5 | 6 | ||
6 | import static de.robv.android.xposed.XposedHelpers.findAndHookMethod; | 7 | import static de.robv.android.xposed.XposedHelpers.findAndHookMethod; |
7 | import de.robv.android.xposed.IXposedHookLoadPackage; | 8 | import de.robv.android.xposed.*; |
8 | import de.robv.android.xposed.XC_MethodHook; | ||
9 | import de.robv.android.xposed.callbacks.XC_LoadPackage.LoadPackageParam; | 9 | import de.robv.android.xposed.callbacks.XC_LoadPackage.LoadPackageParam; |
10 | import android.util.Log; | ||
11 | 10 | ||
12 | public class XHeadset implements IXposedHookLoadPackage extends TileService { | 11 | public class XHeadsetModule implements IXposedHookLoadPackage { |
13 | 12 | ||
14 | @Override | 13 | @Override |
15 | public void handleLoadPackage(LoadPackageParam lpparam) throws Throwable { | 14 | public void handleLoadPackage(LoadPackageParam lpparam) throws Throwable { |
16 | 15 | final XHeadsetStateCommunicator comm = new XHeadsetStateCommunicator(); | |
17 | if (lpparam.packageName.equals("android")) { | 16 | if (lpparam.packageName.equals("android")) { |
18 | findAndHookMethod("com.android.server.WiredAccessoryManager", lpparam.classLoader, "updateLocked", String.class, int.class, new XC_MethodHook() { | 17 | findAndHookMethod("com.android.server.WiredAccessoryManager", lpparam.classLoader, "updateLocked", String.class, int.class, new XC_MethodHook() { |
19 | @Override | 18 | @Override |
20 | protected void beforeHookedMethod(MethodHookParam param) throws Throwable { | 19 | protected void beforeHookedMethod(MethodHookParam param) throws Throwable { |
21 | Log.e("XHeadsetYHRTG", "params: " + param.args[0] + " " + param.args[1]); | 20 | comm.bind(AndroidAppHelper.currentApplication()); |
22 | param.args[1] = (int) (1 << 1); | 21 | if (comm.isEnabled()) { |
22 | Log.e("XHeadset", "Spoofing headphone state"); | ||
23 | param.args[1] = (int) (1 << 1); | ||
24 | } else { | ||
25 | Log.e("XHeadset", "Not spoofing headphone state"); | ||
26 | } | ||
23 | } | 27 | } |
24 | }); | 28 | }); |
25 | } else if (lpparam.packageName.equals("com.android.systemui")) { | 29 | } else if (lpparam.packageName.equals("com.android.systemui")) { |
@@ -34,8 +38,4 @@ public class XHeadset implements IXposedHookLoadPackage extends TileService { | |||
34 | } | 38 | } |
35 | } | 39 | } |
36 | 40 | ||
37 | @Override | ||
38 | public void onClick() { | ||
39 | Log.e("XHeadsetYHRTG", "clicked"); | ||
40 | } | ||
41 | } | 41 | } |
diff --git a/app/src/main/java/org/eu/zajc/xheadset/XHeadsetStateCommunicator.java b/app/src/main/java/org/eu/zajc/xheadset/XHeadsetStateCommunicator.java new file mode 100644 index 0000000..b028b8d --- /dev/null +++ b/app/src/main/java/org/eu/zajc/xheadset/XHeadsetStateCommunicator.java | |||
@@ -0,0 +1,57 @@ | |||
1 | package org.eu.zajc.xheadset; | ||
2 | |||
3 | import java.io.*; | ||
4 | import android.os.*; | ||
5 | import android.util.*; | ||
6 | import android.content.*; | ||
7 | import android.app.*; | ||
8 | |||
9 | public class XHeadsetStateCommunicator { | ||
10 | |||
11 | protected IXHeadsetStateService service = null; | ||
12 | |||
13 | public void bind(Context context) { | ||
14 | if (service == null) | ||
15 | context.bindService(createIntent(), connection, Context.BIND_AUTO_CREATE); | ||
16 | } | ||
17 | |||
18 | public static Intent createIntent() { | ||
19 | return new Intent("org.eu.zajc.xheadset.XHeadsetStateService.BIND").setPackage("org.eu.zajc.xheadset"); | ||
20 | } | ||
21 | |||
22 | public void unbind(Context context) { | ||
23 | if (service != null) | ||
24 | context.unbindService(connection); | ||
25 | } | ||
26 | |||
27 | public boolean setEnabled(boolean enabled) { | ||
28 | try { | ||
29 | return service.setEnabled(enabled); | ||
30 | } catch (RemoteException e) { | ||
31 | Log.e("XHeadset", "An exception has occurred", e); | ||
32 | return false; | ||
33 | } | ||
34 | } | ||
35 | |||
36 | public boolean isEnabled() { | ||
37 | try { | ||
38 | return service.isEnabled(); | ||
39 | } catch (RemoteException e) { | ||
40 | Log.e("XHeadset", "An exception has occurred", e); | ||
41 | return XHeadsetStateService.DEFAULT_STATE; | ||
42 | } | ||
43 | } | ||
44 | |||
45 | private ServiceConnection connection = new ServiceConnection() { | ||
46 | @Override | ||
47 | public void onServiceConnected(ComponentName name, IBinder service) { | ||
48 | XHeadsetStateCommunicator.this.service = IXHeadsetStateService.Stub.asInterface(service); | ||
49 | } | ||
50 | |||
51 | @Override | ||
52 | public void onServiceDisconnected(ComponentName name) { | ||
53 | XHeadsetStateCommunicator.this.service = null; | ||
54 | } | ||
55 | }; | ||
56 | |||
57 | } | ||
diff --git a/app/src/main/java/org/eu/zajc/xheadset/XHeadsetStateService.java b/app/src/main/java/org/eu/zajc/xheadset/XHeadsetStateService.java new file mode 100644 index 0000000..d4410f8 --- /dev/null +++ b/app/src/main/java/org/eu/zajc/xheadset/XHeadsetStateService.java | |||
@@ -0,0 +1,45 @@ | |||
1 | package org.eu.zajc.xheadset; | ||
2 | |||
3 | import java.io.*; | ||
4 | import android.os.*; | ||
5 | import android.util.*; | ||
6 | import android.content.*; | ||
7 | import android.app.*; | ||
8 | |||
9 | public class XHeadsetStateService extends Service { | ||
10 | |||
11 | public static final boolean DEFAULT_STATE = false; | ||
12 | private static final String PREFS_FILE = "xheadset"; | ||
13 | |||
14 | public SharedPreferences getPrefs() { | ||
15 | return this.getPrefs(this); | ||
16 | } | ||
17 | |||
18 | public static SharedPreferences getPrefs(Context context) { | ||
19 | return context.getSharedPreferences(PREFS_FILE, MODE_PRIVATE); | ||
20 | } | ||
21 | |||
22 | public static boolean isEnabled(SharedPreferences prefs) { | ||
23 | return prefs.getBoolean("state", DEFAULT_STATE); | ||
24 | } | ||
25 | |||
26 | public static boolean setEnabled(SharedPreferences prefs, boolean enabled) { | ||
27 | return prefs.edit().putBoolean("state", enabled).commit(); | ||
28 | } | ||
29 | |||
30 | @Override | ||
31 | public IBinder onBind(Intent intent) { | ||
32 | return binder; | ||
33 | } | ||
34 | |||
35 | private final IXHeadsetStateService.Stub binder = new IXHeadsetStateService.Stub() { | ||
36 | public boolean isEnabled() { | ||
37 | return XHeadsetStateService.isEnabled(getPrefs()); | ||
38 | } | ||
39 | |||
40 | public boolean setEnabled(boolean enabled) { | ||
41 | return XHeadsetStateService.setEnabled(getPrefs(), enabled); | ||
42 | } | ||
43 | }; | ||
44 | |||
45 | } | ||
diff --git a/app/src/main/java/org/eu/zajc/xheadset/XHeadsetTile.java b/app/src/main/java/org/eu/zajc/xheadset/XHeadsetTile.java new file mode 100644 index 0000000..b6efe4a --- /dev/null +++ b/app/src/main/java/org/eu/zajc/xheadset/XHeadsetTile.java | |||
@@ -0,0 +1,31 @@ | |||
1 | package org.eu.zajc.xheadset; | ||
2 | |||
3 | import android.content.*; | ||
4 | import android.service.quicksettings.*; | ||
5 | import android.graphics.drawable.*; | ||
6 | import android.util.*; | ||
7 | |||
8 | public class XHeadsetTile extends TileService { | ||
9 | |||
10 | @Override | ||
11 | public void onStartListening() { | ||
12 | setEnabled(XHeadsetStateService.isEnabled(XHeadsetStateService.getPrefs(this))); | ||
13 | } | ||
14 | |||
15 | @Override | ||
16 | public void onClick() { | ||
17 | SharedPreferences prefs = XHeadsetStateService.getPrefs(this); | ||
18 | boolean oldState = XHeadsetStateService.isEnabled(prefs); | ||
19 | if (XHeadsetStateService.setEnabled(prefs, !oldState)) { | ||
20 | setEnabled(!oldState); | ||
21 | } | ||
22 | |||
23 | } | ||
24 | |||
25 | public void setEnabled(boolean enabled) { | ||
26 | Tile tile = getQsTile(); | ||
27 | tile.setIcon(Icon.createWithResource(this, enabled ? R.drawable.headset : R.drawable.speaker)); | ||
28 | tile.setState(enabled ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE); | ||
29 | tile.updateTile(); | ||
30 | } | ||
31 | } | ||
diff --git a/app/src/main/res/drawable/headset.xml b/app/src/main/res/drawable/headset.xml new file mode 100644 index 0000000..bfcbe0d --- /dev/null +++ b/app/src/main/res/drawable/headset.xml | |||
@@ -0,0 +1,16 @@ | |||
1 | <?xml version="1.0" encoding="utf-8"?> | ||
2 | <vector xmlns:android="http://schemas.android.com/apk/res/android" | ||
3 | android:width="24dp" | ||
4 | android:height="24dp" | ||
5 | android:viewportWidth="24" | ||
6 | android:viewportHeight="24"> | ||
7 | |||
8 | <path | ||
9 | android:fillAlpha=".1" | ||
10 | android:strokeAlpha=".1" | ||
11 | android:strokeWidth="1" | ||
12 | android:pathData="M0 0h24v24H0z" /> | ||
13 | <path | ||
14 | android:fillColor="#000000" | ||
15 | android:pathData="M12 1c-4.97 0-9 4.03-9 9v7c0 1.66 1.34 3 3 3h3v-8H5v-2c0-3.87 3.13-7 7-7s7 3.13 7 7v2h-4v8h3c1.66 0 3-1.34 3-3v-7c0-4.97-4.03-9-9-9z" /> | ||
16 | </vector> | ||
diff --git a/app/src/main/res/drawable/speaker.xml b/app/src/main/res/drawable/speaker.xml new file mode 100644 index 0000000..4185aef --- /dev/null +++ b/app/src/main/res/drawable/speaker.xml | |||
@@ -0,0 +1,13 @@ | |||
1 | <?xml version="1.0" encoding="utf-8"?> | ||
2 | <vector xmlns:android="http://schemas.android.com/apk/res/android" | ||
3 | android:width="24dp" | ||
4 | android:height="24dp" | ||
5 | android:viewportWidth="24" | ||
6 | android:viewportHeight="24"> | ||
7 | |||
8 | <path | ||
9 | android:pathData="M0 0h24v24H0z" /> | ||
10 | <path | ||
11 | android:fillColor="#000000" | ||
12 | android:pathData="M17 2H7c-1.1 0-2 0.9-2 2v16c0 1.1 0.9 1.99 2 1.99L17 22c1.1 0 2-0.9 2-2V4c0-1.1-0.9-2-2-2zm-5 2c1.1 0 2 0.9 2 2s-0.9 2-2 2c-1.11 0-2-0.9-2-2s0.89-2 2-2zm0 16c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5zm0-8c-1.66 0-3 1.34-3 3s1.34 3 3 3 3-1.34 3-3-1.34-3-3-3z" /> | ||
13 | </vector> | ||