From 3568ff0e5d4f3b23db48be20a973b0bb4287365e Mon Sep 17 00:00:00 2001 From: Khoa Nguyen Date: Mon, 6 May 2024 17:59:20 -0400 Subject: [PATCH 1/6] Added sending broadcast function for integration with other apps --- .../dosse/airpods/pods/BroadcastParam.java | 14 +++++++ .../com/dosse/airpods/pods/PodsService.java | 39 +++++++++++++++++++ .../airpods/pods/models/RegularPods.java | 6 +++ 3 files changed, 59 insertions(+) create mode 100644 app/src/main/java/com/dosse/airpods/pods/BroadcastParam.java diff --git a/app/src/main/java/com/dosse/airpods/pods/BroadcastParam.java b/app/src/main/java/com/dosse/airpods/pods/BroadcastParam.java new file mode 100644 index 0000000..75caee5 --- /dev/null +++ b/app/src/main/java/com/dosse/airpods/pods/BroadcastParam.java @@ -0,0 +1,14 @@ +package com.dosse.airpods.pods; + +public class BroadcastParam { + public static final String ACTION_STATUS = "com.dosse.airpods.status"; + public static final String EXTRA_IS_ALL_DISCONNECTED = "isAllDisconnected"; + public static final String EXTRA_MODEL = "model"; + public static final String EXTRA_IS_SINGLE = "isSingle"; + public static final String EXTRA_LEFT_POD_STATUS = "leftPodStatus"; + public static final String EXTRA_RIGHT_POD_STATUS = "rightPodStatus"; + public static final String EXTRA_POD_CASE_STATUS = "caseStatus"; + public static final String EXTRA_LEFT_POD_IN_EAR = "leftPodInEar"; + public static final String EXTRA_RIGHT_POD_IN_EAR = "rightPodInEar"; + public static final String EXTRA_SINGLE_POD_STATUS = "singlePodStatus"; +} diff --git a/app/src/main/java/com/dosse/airpods/pods/PodsService.java b/app/src/main/java/com/dosse/airpods/pods/PodsService.java index 233c986..0e4af42 100644 --- a/app/src/main/java/com/dosse/airpods/pods/PodsService.java +++ b/app/src/main/java/com/dosse/airpods/pods/PodsService.java @@ -21,6 +21,8 @@ import com.dosse.airpods.R; import com.dosse.airpods.notification.NotificationThread; +import com.dosse.airpods.pods.models.RegularPods; +import com.dosse.airpods.pods.models.SinglePods; import com.dosse.airpods.receivers.BluetoothListener; import com.dosse.airpods.receivers.BluetoothReceiver; import com.dosse.airpods.receivers.ScreenReceiver; @@ -28,6 +30,16 @@ import java.util.Objects; +import static com.dosse.airpods.pods.BroadcastParam.ACTION_STATUS; +import static com.dosse.airpods.pods.BroadcastParam.EXTRA_IS_ALL_DISCONNECTED; +import static com.dosse.airpods.pods.BroadcastParam.EXTRA_IS_SINGLE; +import static com.dosse.airpods.pods.BroadcastParam.EXTRA_LEFT_POD_IN_EAR; +import static com.dosse.airpods.pods.BroadcastParam.EXTRA_LEFT_POD_STATUS; +import static com.dosse.airpods.pods.BroadcastParam.EXTRA_MODEL; +import static com.dosse.airpods.pods.BroadcastParam.EXTRA_POD_CASE_STATUS; +import static com.dosse.airpods.pods.BroadcastParam.EXTRA_RIGHT_POD_IN_EAR; +import static com.dosse.airpods.pods.BroadcastParam.EXTRA_RIGHT_POD_STATUS; +import static com.dosse.airpods.pods.BroadcastParam.EXTRA_SINGLE_POD_STATUS; import static com.dosse.airpods.pods.PodsStatusScanCallback.getScanFilters; import static com.dosse.airpods.utils.SharedPreferencesUtils.isSavingBattery; @@ -91,6 +103,8 @@ private void startAirPodsScanner() { @Override public void onStatus(PodsStatus newStatus) { mStatus = newStatus; + + sendBroadcast(); } }; @@ -330,4 +344,29 @@ private void unregisterScreenReceiver() { Logger.error(t); } } + + private void sendBroadcast() { + Intent intent = new Intent(); + intent.setAction(ACTION_STATUS); + intent.putExtra(EXTRA_IS_ALL_DISCONNECTED, mStatus.isAllDisconnected()); + intent.putExtra(EXTRA_MODEL, mStatus.getAirpods().getModel()); + intent.putExtra(EXTRA_IS_SINGLE, mStatus.getAirpods().isSingle()); + + if (mStatus.getAirpods().isSingle()) { + intent.putExtra(EXTRA_SINGLE_POD_STATUS, + ((SinglePods) mStatus.getAirpods()).getParsedStatus()); + } else { + intent.putExtra(EXTRA_LEFT_POD_STATUS, + ((RegularPods) mStatus.getAirpods()).getParsedStatus(RegularPods.LEFT)); + intent.putExtra(EXTRA_RIGHT_POD_STATUS, + ((RegularPods) mStatus.getAirpods()).getParsedStatus(RegularPods.RIGHT)); + intent.putExtra(EXTRA_POD_CASE_STATUS, + ((RegularPods) mStatus.getAirpods()).getParsedStatus(RegularPods.CASE)); + intent.putExtra(EXTRA_LEFT_POD_IN_EAR, + ((RegularPods) mStatus.getAirpods()).isInEar(RegularPods.LEFT)); + intent.putExtra(EXTRA_RIGHT_POD_IN_EAR, + ((RegularPods) mStatus.getAirpods()).isInEar(RegularPods.RIGHT)); + } + sendBroadcast(intent); + } } diff --git a/app/src/main/java/com/dosse/airpods/pods/models/RegularPods.java b/app/src/main/java/com/dosse/airpods/pods/models/RegularPods.java index a50f6a8..00fba07 100644 --- a/app/src/main/java/com/dosse/airpods/pods/models/RegularPods.java +++ b/app/src/main/java/com/dosse/airpods/pods/models/RegularPods.java @@ -22,6 +22,12 @@ public String getParsedStatus(int pos) { return pods[pos].parseStatus(); } + public boolean isInEar(int pos) { + + + return pods[pos].isInEar(); + } + public int getInEarVisibility(int pos) { return pods[pos].inEarVisibility(); } From fc665e29aaa6dd8eeb9e9446995b6a3cf1212520 Mon Sep 17 00:00:00 2001 From: Khoa Nguyen Date: Mon, 6 May 2024 18:59:20 -0400 Subject: [PATCH 2/6] Added instruction for broadcast intent in README --- README.md | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/README.md b/README.md index 4894156..b9bab44 100644 --- a/README.md +++ b/README.md @@ -56,6 +56,54 @@ * Powerbeats 3 * Powerbeats Pro +## Integration +This app can post broadcast intents that can be read by any apps on the same device. + +To register for a broadcast receiver: +**AndroidManifest.xml** +```xml + + + + + +``` + +**Receiver** +```java +public class AirpodReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + if (intent.getAction().equals(ACTION_STATUS)) { + // Do something with "intent.getExtras()" here + } + } +} +``` + +**Register Receiver** +```java +IntentFilter airpodFilter = new IntentFilter(ACTION_STATUS); +AirpodReceiver airpodReceiver = new AirpodReceiver(); +registerReceiver(airpodReceiver, airpodFilter); +``` + +**Intent Extras** +Primary action: `com.dosse.airpods.status` contains the following intent extras: + +| Intent Extra | Type | Description | +|-------------------| ---- |--------------------------------------------------------------------------------------------------------------------------------------------------| +| isAllDisconnected | boolean | True if airpod (including case, left pod, right pod) lost connection, otherwise false | +| model | String | Model of the airpod | +| isSingle | boolean | True if the model is single (ie. Beats Studio, Beats Solo, etc.) | +| leftPodStatus | String | Battery percentage (with % suffix) of the left pod if it is connected, blank if disconnected. **Only applicable for non-single pod models** | +| rightPodStatus | String | Battery percentage (with % suffix) of the right pod if it is connected, blank if disconnected. **Only applicable for non-single pod models** | +| caseStatus | String | Battery percentage (with % suffix) of the charging case if it is connected, blank if disconnected. **Only applicable for non-single pod models** | +| singlePodStatus | String | Battery percentage (with % suffix) of the airpod if it is connected, blank if disconnected. **Only applicable for single pod models** | +| leftPodInEar | boolean | If left pod is detected to be in ear. **Only applicable for non-single pod models** | +| rightPodInEar | boolean | If right pod is detected to be in ear. **Only applicable for non-single pod models** | + ## DO NOT REUPLOAD TO GOOGLE PLAY **This app violates Google Play policies and is designed to break if you try to fix that unless you really know what you're doing.**
Legal actions can and will be taken when uploading a compiled version to the Google Play Store that does not comply with the terms and conditions of the app's license. As a result, you may no longer be able to publish apps to the Google Play Store. This app is not intended to be uploaded to the Google Play Store. The developer is legally allowed to demand compensation. From f5f8c378c470c5f8018acdf9a3fe66c97a0f2691 Mon Sep 17 00:00:00 2001 From: Khoa Nguyen Date: Mon, 6 May 2024 19:03:58 -0400 Subject: [PATCH 3/6] Minor format changes to README --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index b9bab44..86ae9aa 100644 --- a/README.md +++ b/README.md @@ -60,6 +60,7 @@ This app can post broadcast intents that can be read by any apps on the same device. To register for a broadcast receiver: + **AndroidManifest.xml** ```xml Date: Mon, 6 May 2024 19:58:55 -0400 Subject: [PATCH 4/6] Added charging status for pods --- README.md | 26 +++++++++++-------- .../dosse/airpods/pods/BroadcastParam.java | 5 ++++ .../com/dosse/airpods/pods/PodsService.java | 12 +++++++++ .../airpods/pods/models/RegularPods.java | 6 +++-- .../dosse/airpods/pods/models/SinglePods.java | 4 +++ 5 files changed, 40 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 86ae9aa..382f811 100644 --- a/README.md +++ b/README.md @@ -94,17 +94,21 @@ registerReceiver(airpodReceiver, airpodFilter); Primary action: `com.dosse.airpods.status` contains the following intent extras: -| Intent Extra | Type | Description | -|-------------------| ---- |--------------------------------------------------------------------------------------------------------------------------------------------------| -| isAllDisconnected | boolean | True if airpod (including case, left pod, right pod) lost connection, otherwise false | -| model | String | Model of the airpod | -| isSingle | boolean | True if the model is single (ie. Beats Studio, Beats Solo, etc.) | -| leftPodStatus | String | Battery percentage (with % suffix) of the left pod if it is connected, blank if disconnected. **Only applicable for non-single pod models** | -| rightPodStatus | String | Battery percentage (with % suffix) of the right pod if it is connected, blank if disconnected. **Only applicable for non-single pod models** | -| caseStatus | String | Battery percentage (with % suffix) of the charging case if it is connected, blank if disconnected. **Only applicable for non-single pod models** | -| singlePodStatus | String | Battery percentage (with % suffix) of the airpod if it is connected, blank if disconnected. **Only applicable for single pod models** | -| leftPodInEar | boolean | If left pod is detected to be in ear. **Only applicable for non-single pod models** | -| rightPodInEar | boolean | If right pod is detected to be in ear. **Only applicable for non-single pod models** | +| Intent Extra | Type | Description | +|--------------------| ---- |----------------------------------------------------------------------------------------------------------------------------------------------| +| isAllDisconnected | boolean | True if airpod (including case, left pod, right pod) lost connection, otherwise false | +| model | String | Model of the airpod | +| isSingle | boolean | True if the model is single (ie. Beats Studio, Beats Solo, etc.), otherwise false | +| leftPodStatus | String | Battery percentage (with % suffix) of the left pod if it is connected, blank if disconnected. **Only applicable for non-single pod models** | +| rightPodStatus | String | Battery percentage (with % suffix) of the right pod if it is connected, blank if disconnected. **Only applicable for non-single pod models** | +| caseStatus | String | Battery percentage (with % suffix) of the charging case if it is connected, blank if disconnected. **Only applicable for non-single pod models** | +| singlePodStatus | String | Battery percentage (with % suffix) of the airpod if it is connected, blank if disconnected. **Only applicable for single pod models** | +| isLeftPodCharging | boolean | True if left pod is charging in case, otherwise false. **Only applicable for non-single pod models** | +| isRightPodCharging | boolean | True if right pod is charging in case, otherwise false. **Only applicable for non-single pod models** | +| isCaseCharging | boolean | True if airpod case is charging, otherwise false. **Only applicable for non-single pod models** | +| isSinglePodCharging | boolean | True if single pod is charging, otherwise false. **Only applicable for single pod models** | +| leftPodInEar | boolean | If left pod is detected to be in ear. **Only applicable for non-single pod models** | +| rightPodInEar | boolean | If right pod is detected to be in ear. **Only applicable for non-single pod models** | ## DO NOT REUPLOAD TO GOOGLE PLAY **This app violates Google Play policies and is designed to break if you try to fix that unless you really know what you're doing.**
diff --git a/app/src/main/java/com/dosse/airpods/pods/BroadcastParam.java b/app/src/main/java/com/dosse/airpods/pods/BroadcastParam.java index 75caee5..b75e09b 100644 --- a/app/src/main/java/com/dosse/airpods/pods/BroadcastParam.java +++ b/app/src/main/java/com/dosse/airpods/pods/BroadcastParam.java @@ -11,4 +11,9 @@ public class BroadcastParam { public static final String EXTRA_LEFT_POD_IN_EAR = "leftPodInEar"; public static final String EXTRA_RIGHT_POD_IN_EAR = "rightPodInEar"; public static final String EXTRA_SINGLE_POD_STATUS = "singlePodStatus"; + public static final String EXTRA_LEFT_POD_CHARGING = "isLeftPodCharging"; + public static final String EXTRA_RIGHT_POD_CHARGING = "isRightPodCharging"; + public static final String EXTRA_POD_CASE_CHARGING = "isCaseCharging"; + public static final String EXTRA_SINGLE_POD_CHARGING = "isSinglePodCharging"; + } diff --git a/app/src/main/java/com/dosse/airpods/pods/PodsService.java b/app/src/main/java/com/dosse/airpods/pods/PodsService.java index 0e4af42..b8db6d7 100644 --- a/app/src/main/java/com/dosse/airpods/pods/PodsService.java +++ b/app/src/main/java/com/dosse/airpods/pods/PodsService.java @@ -33,12 +33,16 @@ import static com.dosse.airpods.pods.BroadcastParam.ACTION_STATUS; import static com.dosse.airpods.pods.BroadcastParam.EXTRA_IS_ALL_DISCONNECTED; import static com.dosse.airpods.pods.BroadcastParam.EXTRA_IS_SINGLE; +import static com.dosse.airpods.pods.BroadcastParam.EXTRA_LEFT_POD_CHARGING; import static com.dosse.airpods.pods.BroadcastParam.EXTRA_LEFT_POD_IN_EAR; import static com.dosse.airpods.pods.BroadcastParam.EXTRA_LEFT_POD_STATUS; import static com.dosse.airpods.pods.BroadcastParam.EXTRA_MODEL; +import static com.dosse.airpods.pods.BroadcastParam.EXTRA_POD_CASE_CHARGING; import static com.dosse.airpods.pods.BroadcastParam.EXTRA_POD_CASE_STATUS; +import static com.dosse.airpods.pods.BroadcastParam.EXTRA_RIGHT_POD_CHARGING; import static com.dosse.airpods.pods.BroadcastParam.EXTRA_RIGHT_POD_IN_EAR; import static com.dosse.airpods.pods.BroadcastParam.EXTRA_RIGHT_POD_STATUS; +import static com.dosse.airpods.pods.BroadcastParam.EXTRA_SINGLE_POD_CHARGING; import static com.dosse.airpods.pods.BroadcastParam.EXTRA_SINGLE_POD_STATUS; import static com.dosse.airpods.pods.PodsStatusScanCallback.getScanFilters; import static com.dosse.airpods.utils.SharedPreferencesUtils.isSavingBattery; @@ -355,6 +359,8 @@ private void sendBroadcast() { if (mStatus.getAirpods().isSingle()) { intent.putExtra(EXTRA_SINGLE_POD_STATUS, ((SinglePods) mStatus.getAirpods()).getParsedStatus()); + intent.putExtra(EXTRA_SINGLE_POD_CHARGING, + ((SinglePods) mStatus.getAirpods()).isCharging()); } else { intent.putExtra(EXTRA_LEFT_POD_STATUS, ((RegularPods) mStatus.getAirpods()).getParsedStatus(RegularPods.LEFT)); @@ -366,6 +372,12 @@ private void sendBroadcast() { ((RegularPods) mStatus.getAirpods()).isInEar(RegularPods.LEFT)); intent.putExtra(EXTRA_RIGHT_POD_IN_EAR, ((RegularPods) mStatus.getAirpods()).isInEar(RegularPods.RIGHT)); + intent.putExtra(EXTRA_LEFT_POD_CHARGING, + ((RegularPods) mStatus.getAirpods()).isCharging(RegularPods.LEFT)); + intent.putExtra(EXTRA_RIGHT_POD_CHARGING, + ((RegularPods) mStatus.getAirpods()).isCharging(RegularPods.RIGHT)); + intent.putExtra(EXTRA_POD_CASE_CHARGING, + ((RegularPods) mStatus.getAirpods()).isCharging(RegularPods.CASE)); } sendBroadcast(intent); } diff --git a/app/src/main/java/com/dosse/airpods/pods/models/RegularPods.java b/app/src/main/java/com/dosse/airpods/pods/models/RegularPods.java index 00fba07..20fa3d6 100644 --- a/app/src/main/java/com/dosse/airpods/pods/models/RegularPods.java +++ b/app/src/main/java/com/dosse/airpods/pods/models/RegularPods.java @@ -23,11 +23,13 @@ public String getParsedStatus(int pos) { } public boolean isInEar(int pos) { - - return pods[pos].isInEar(); } + public boolean isCharging(int pos) { + return pods[pos].isCharging(); + } + public int getInEarVisibility(int pos) { return pods[pos].inEarVisibility(); } diff --git a/app/src/main/java/com/dosse/airpods/pods/models/SinglePods.java b/app/src/main/java/com/dosse/airpods/pods/models/SinglePods.java index 3619d19..2147528 100644 --- a/app/src/main/java/com/dosse/airpods/pods/models/SinglePods.java +++ b/app/src/main/java/com/dosse/airpods/pods/models/SinglePods.java @@ -21,6 +21,10 @@ public String getParsedStatus() { return pod.parseStatus(); } + public boolean isCharging() { + return pod.isCharging(); + } + public int getBatImgVisibility() { return pod.batImgVisibility(); } From d2d6cb8099e19ebff3cff7f0bd0f236c687aff0e Mon Sep 17 00:00:00 2001 From: Khoa Nguyen Date: Tue, 7 May 2024 00:05:12 -0400 Subject: [PATCH 5/6] Added conditions for when ACL disconnect and turning off bluetooth --- .../com/dosse/airpods/pods/PodsService.java | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/dosse/airpods/pods/PodsService.java b/app/src/main/java/com/dosse/airpods/pods/PodsService.java index b8db6d7..8f66a9e 100644 --- a/app/src/main/java/com/dosse/airpods/pods/PodsService.java +++ b/app/src/main/java/com/dosse/airpods/pods/PodsService.java @@ -108,7 +108,10 @@ private void startAirPodsScanner() { public void onStatus(PodsStatus newStatus) { mStatus = newStatus; - sendBroadcast(); + // Sometimes after disconnecting, the scanner still lingers + if (mMaybeConnected) { + sendBroadcast(); + } } }; @@ -167,6 +170,10 @@ public void onStop() { Logger.debug("BT OFF"); mMaybeConnected = false; stopAirPodsScanner(); + + // Reset status back to disconnected and send the broadcast + mStatus = PodsStatus.DISCONNECTED; + sendBroadcast(); } @Override @@ -186,6 +193,10 @@ public void onDisconnect(BluetoothDevice bluetoothDevice) { // Airpods disconnected, remove notification but leave the scanner going. Logger.debug("ACL DISCONNECTED"); mMaybeConnected = false; + + // Reset status back to disconnected and send the broadcast + mStatus = PodsStatus.DISCONNECTED; + sendBroadcast(); } } }; @@ -352,6 +363,13 @@ private void unregisterScreenReceiver() { private void sendBroadcast() { Intent intent = new Intent(); intent.setAction(ACTION_STATUS); + + if (mStatus == PodsStatus.DISCONNECTED) { + intent.putExtra(EXTRA_IS_ALL_DISCONNECTED, true); + sendBroadcast(intent); + return; + } + intent.putExtra(EXTRA_IS_ALL_DISCONNECTED, mStatus.isAllDisconnected()); intent.putExtra(EXTRA_MODEL, mStatus.getAirpods().getModel()); intent.putExtra(EXTRA_IS_SINGLE, mStatus.getAirpods().isSingle()); From bc7fd568857ce206576915f964c23b19b5f10f2c Mon Sep 17 00:00:00 2001 From: Khoa Nguyen Date: Thu, 23 May 2024 14:43:04 -0400 Subject: [PATCH 6/6] Added custom permission. Broadcast now requires this custom permission to be granted before receiving --- README.md | 10 +++++- app/src/main/AndroidManifest.xml | 8 +++++ .../com/dosse/airpods/pods/PodsService.java | 31 ++++++++++--------- .../{pods => utils}/BroadcastParam.java | 4 +-- app/src/main/res/drawable/icon_small.xml | 9 ++++++ app/src/main/res/values/strings.xml | 3 ++ 6 files changed, 47 insertions(+), 18 deletions(-) rename app/src/main/java/com/dosse/airpods/{pods => utils}/BroadcastParam.java (86%) create mode 100644 app/src/main/res/drawable/icon_small.xml diff --git a/README.md b/README.md index 382f811..bbca059 100644 --- a/README.md +++ b/README.md @@ -64,13 +64,21 @@ To register for a broadcast receiver: **AndroidManifest.xml** ```xml + android:exported="true" + android:permission="com.dosse.airpods.permission.ACCESS_AIRPOD_INFORMATION"> ``` +**Check and request permission** +```java +if (checkSelfPermission(context, "com.dosse.airpods.permission.ACCESS_AIRPOD_INFORMATION") == PackageManager.PERMISSION_DENIED) { + context.requestPermissions(new String[]{"com.dosse.airpods.permission.ACCESS_AIRPOD_INFORMATION"}, 201); +} +``` + **Receiver** ```java public class AirpodReceiver extends BroadcastReceiver { diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 2221ca6..1fbe7a5 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -23,6 +23,14 @@ android:name="android.hardware.bluetooth_le" android:required="false" /> + + + + + + + \ 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 5aaff73..60c5024 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -47,6 +47,9 @@ Restart Service Do this if the notification is not showing + Read Airpod Information + access information about your AirPods, including battery status, connection status, and device settings + About Developed by Federico Dossena and Itai Levin F-Droid