diff --git a/OpenPods/.idea/misc.xml b/OpenPods/.idea/misc.xml
index 70d034b..cfd74e2 100644
--- a/OpenPods/.idea/misc.xml
+++ b/OpenPods/.idea/misc.xml
@@ -5,6 +5,15 @@
- * After decoding a beacon, the status is written to leftStatus, rightStatus, caseStatus, chargeL, chargeR, chargeCase, inEarL, inEarR so that the NotificationThread can use the information + * After decoding a beacon, the status is written to leftStatus, rightStatus, caseStatus, maxStatus, chargeL, chargeR, chargeCase, inEarL, inEarR so that the NotificationThread can use the information *
* Notes: * 1) - isFlipped set by bit 1 of 10th character in the string; seems to be related to in-ear detection; @@ -139,8 +146,8 @@ public class PodsService extends Service { recentBeacons.add(result); - OpenPodsDebugLog("" + result.getRssi() + "db"); - OpenPodsDebugLog(decodeHex(data)); + OpenPodsDebugLog(String.format(Locale.getDefault(), "%ddb", result.getRssi())); + OpenPodsDebugLog(ScannerUtils.decodeHex(data)); ScanResult strongestBeacon = null; for (int i = 0; i < recentBeacons.size(); i++) { @@ -160,12 +167,13 @@ public class PodsService extends Service { if (result.getRssi() < -60) return; - String a = decodeHex(Objects.requireNonNull(Objects.requireNonNull(result.getScanRecord()).getManufacturerSpecificData(76))); - boolean flip = isFlipped(a); + String a = ScannerUtils.decodeHex(Objects.requireNonNull(Objects.requireNonNull(result.getScanRecord()).getManufacturerSpecificData(76))); + boolean flip = ScannerUtils.isFlipped(a); leftStatus = Integer.parseInt("" + a.charAt(flip ? 12 : 13), 16); // Left airpod (0-10 batt; 15=disconnected) rightStatus = Integer.parseInt("" + a.charAt(flip ? 13 : 12), 16); // Right airpod (0-10 batt; 15=disconnected) caseStatus = Integer.parseInt("" + a.charAt(15), 16); // Case (0-10 batt; 15=disconnected) + maxStatus = Integer.parseInt("" + a.charAt(13), 16); // Airpods max (0-10 batt; 15=disconnected) int chargeStatus = Integer.parseInt("" + a.charAt(14), 16); // Charge status (bit 0=left; bit 1=right; bit 2=case) @@ -178,7 +186,11 @@ public class PodsService extends Service { inEarL = (inEarStatus & (flip ? 0b00001000 : 0b00000010)) != 0; inEarR = (inEarStatus & (flip ? 0b00000010 : 0b00001000)) != 0; - model = (a.charAt(7) == 'E') ? MODEL_AIRPODS_PRO : MODEL_AIRPODS_NORMAL; // Detect if these are AirPods Pro or regular ones + switch (a.charAt(7)) { // Detect if these are AirPods Pro/Max or regular ones + case 'E': model = MODEL_AIRPODS_PRO; break; + case 'A': model = MODEL_AIRPODS_MAX; break; + default: model = MODEL_AIRPODS_NORMAL; + } lastSeenConnected = System.currentTimeMillis(); } catch (Throwable t) { @@ -221,23 +233,11 @@ public class PodsService extends Service { leftStatus = 15; rightStatus = 15; caseStatus = 15; + maxStatus = 15; } catch (Throwable ignored) { } } - private String decodeHex (byte[] bArr) { - StringBuilder ret = new StringBuilder(); - - for (byte b : bArr) - ret.append(String.format("%02X", b)); - - return ret.toString(); - } - - private boolean isFlipped (String str) { - return (Integer.parseInt("" + str.charAt(10), 16) & 0x02) == 0; - } - /** * The following class is a thread that manages the notification while your AirPods are connected. *
@@ -285,7 +285,7 @@ public class PodsService extends Service {
RemoteViews[] notificationArr = new RemoteViews[] {new RemoteViews(getPackageName(), R.layout.status_big), new RemoteViews(getPackageName(), R.layout.status_small)};
RemoteViews[] notificationLocation = new RemoteViews[] {new RemoteViews(getPackageName(), R.layout.location_disabled_big), new RemoteViews(getPackageName(), R.layout.location_disabled_small)};
- if (maybeConnected && !(leftStatus == 15 && rightStatus == 15 && caseStatus == 15)) {
+ if (maybeConnected && !(leftStatus == 15 && rightStatus == 15 && caseStatus == 15 && maxStatus == 15)) {
if (!notificationShowing) {
OpenPodsDebugLog("Creating notification");
notificationShowing = true;
@@ -310,17 +310,36 @@ public class PodsService extends Service {
}
if (notificationShowing) {
- OpenPodsDebugLog("Left: " + leftStatus + (chargeL ? "+" : "") + (inEarL ? "$" : "") + " Right: " + rightStatus + (chargeR ? "+" : "") + (inEarR ? "$" : "") + " Case: " + caseStatus + (chargeCase ? "+" : "") + " Model: " + model);
+ OpenPodsDebugLog(isMax() ? String.format(Locale.getDefault(), "Battery: %d Model: %s", maxStatus, model) :
+ String.format(Locale.getDefault(), "Left: %d%s%s Right: %d%s%s Case: %d%s Model: %s",
+ leftStatus, chargeL ? "+" : "", inEarL ? "$" : "",
+ rightStatus, chargeR ? "+" : "", inEarR ? "$" : "",
+ caseStatus, chargeCase ? "+" : "", model)
+ );
- if (model.equals(MODEL_AIRPODS_NORMAL)) for (RemoteViews notification : notificationArr) {
- notification.setImageViewResource(R.id.leftPodImg, leftStatus <= 10 ? R.drawable.pod : R.drawable.pod_disconnected);
- notification.setImageViewResource(R.id.rightPodImg, rightStatus <= 10 ? R.drawable.pod : R.drawable.pod_disconnected);
- notification.setImageViewResource(R.id.podCaseImg, caseStatus <= 10 ? R.drawable.pod_case : R.drawable.pod_case_disconnected);
+ switch (model) {
+ case MODEL_AIRPODS_NORMAL:
+ for (RemoteViews notification : notificationArr) {
+ notification.setImageViewResource(R.id.leftPodImg, leftStatus <= 10 ? R.drawable.pod : R.drawable.pod_disconnected);
+ notification.setImageViewResource(R.id.rightPodImg, rightStatus <= 10 ? R.drawable.pod : R.drawable.pod_disconnected);
+ notification.setImageViewResource(R.id.podCaseImg, caseStatus <= 10 ? R.drawable.pod_case : R.drawable.pod_case_disconnected);
+ }
+ break;
+ case MODEL_AIRPODS_PRO:
+ for (RemoteViews notification : notificationArr) {
+ notification.setImageViewResource(R.id.leftPodImg, leftStatus <= 10 ? R.drawable.podpro : R.drawable.podpro_disconnected);
+ notification.setImageViewResource(R.id.rightPodImg, rightStatus <= 10 ? R.drawable.podpro : R.drawable.podpro_disconnected);
+ notification.setImageViewResource(R.id.podCaseImg, caseStatus <= 10 ? R.drawable.podpro_case : R.drawable.podpro_case_disconnected);
+ }
+ break;
+ case MODEL_AIRPODS_MAX:
+ for (RemoteViews notification : notificationArr)
+ notification.setImageViewResource(R.id.leftPodImg, maxStatus <= 10 ? R.drawable.podmax : R.drawable.podmax_disconnected);
}
- else if (model.equals(MODEL_AIRPODS_PRO)) for (RemoteViews notification : notificationArr) {
- notification.setImageViewResource(R.id.leftPodImg, leftStatus <= 10 ? R.drawable.podpro : R.drawable.podpro_disconnected);
- notification.setImageViewResource(R.id.rightPodImg, rightStatus <= 10 ? R.drawable.podpro : R.drawable.podpro_disconnected);
- notification.setImageViewResource(R.id.podCaseImg, caseStatus <= 10 ? R.drawable.podpro_case : R.drawable.podpro_case_disconnected);
+
+ for (RemoteViews notification : notificationArr) {
+ notification.setViewVisibility(R.id.rightPod, isMax() ? View.GONE : View.VISIBLE);
+ notification.setViewVisibility(R.id.podCase, isMax() ? View.GONE : View.VISIBLE);
}
if (System.currentTimeMillis() - lastSeenConnected < TIMEOUT_CONNECTED) for (RemoteViews notification : notificationArr) {
@@ -331,23 +350,19 @@ public class PodsService extends Service {
notification.setViewVisibility(R.id.rightPodUpdating, View.INVISIBLE);
notification.setViewVisibility(R.id.podCaseUpdating, View.INVISIBLE);
- String podText_Left = (leftStatus == 10) ? "100%" : ((leftStatus < 10) ? ((leftStatus * 10 + 5) + "%") : "");
- String podText_Right = (rightStatus == 10) ? "100%" : ((rightStatus < 10) ? ((rightStatus * 10 + 5) + "%") : "");
- String podText_Case = (caseStatus == 10) ? "100%" : ((caseStatus < 10) ? ((caseStatus * 10 + 5) + "%") : "");
+ notification.setTextViewText(R.id.leftPodText, NotificationUtils.statusToString(isMax() ? maxStatus : leftStatus));
+ notification.setTextViewText(R.id.rightPodText, NotificationUtils.statusToString(rightStatus));
+ notification.setTextViewText(R.id.podCaseText, NotificationUtils.statusToString(caseStatus));
- notification.setTextViewText(R.id.leftPodText, podText_Left);
- notification.setTextViewText(R.id.rightPodText, podText_Right);
- notification.setTextViewText(R.id.podCaseText, podText_Case);
+ notification.setImageViewResource(R.id.leftBatImg, NotificationUtils.batImgSrcId(chargeL));
+ notification.setImageViewResource(R.id.rightBatImg, NotificationUtils.batImgSrcId(chargeR));
+ notification.setImageViewResource(R.id.caseBatImg, NotificationUtils.batImgSrcId(chargeCase));
- notification.setImageViewResource(R.id.leftBatImg, chargeL ? R.drawable.ic_battery_charging_full_green_24dp : R.drawable.ic_battery_alert_red_24dp);
- notification.setImageViewResource(R.id.rightBatImg, chargeR ? R.drawable.ic_battery_charging_full_green_24dp : R.drawable.ic_battery_alert_red_24dp);
- notification.setImageViewResource(R.id.caseBatImg, chargeCase ? R.drawable.ic_battery_charging_full_green_24dp : R.drawable.ic_battery_alert_red_24dp);
+ notification.setViewVisibility(R.id.leftBatImg, NotificationUtils.batImgVisibility(chargeL, leftStatus));
+ notification.setViewVisibility(R.id.rightBatImg, NotificationUtils.batImgVisibility(chargeR, rightStatus));
+ notification.setViewVisibility(R.id.caseBatImg, NotificationUtils.batImgVisibility(chargeCase, caseStatus));
- notification.setViewVisibility(R.id.leftBatImg, ((chargeL && leftStatus <= 10) || (leftStatus <= 1) ? View.VISIBLE : View.GONE));
- notification.setViewVisibility(R.id.rightBatImg, ((chargeR && rightStatus <= 10) || (rightStatus <= 1) ? View.VISIBLE : View.GONE));
- notification.setViewVisibility(R.id.caseBatImg, ((chargeCase && caseStatus <= 10) || (caseStatus <= 1) ? View.VISIBLE : View.GONE));
-
- notification.setViewVisibility(R.id.leftInEarImg, inEarL ? View.VISIBLE : View.INVISIBLE);
+ notification.setViewVisibility(R.id.leftInEarImg, inEarL && !isMax() ? View.VISIBLE : View.INVISIBLE);
notification.setViewVisibility(R.id.rightInEarImg, inEarR ? View.VISIBLE : View.INVISIBLE);
}
else for (RemoteViews notification : notificationArr) {
diff --git a/OpenPods/app/src/main/java/com/dosse/airpods/utils/NotificationUtils.java b/OpenPods/app/src/main/java/com/dosse/airpods/utils/NotificationUtils.java
new file mode 100644
index 0000000..fe0d280
--- /dev/null
+++ b/OpenPods/app/src/main/java/com/dosse/airpods/utils/NotificationUtils.java
@@ -0,0 +1,23 @@
+package com.dosse.airpods.utils;
+
+import static com.dosse.airpods.utils.ScannerUtils.isMax;
+
+import android.view.View;
+
+import com.dosse.airpods.R;
+
+public class NotificationUtils {
+
+ public static String statusToString (int status) {
+ return (status == 10) ? "100%" : ((status < 10) ? ((status * 10 + 5) + "%") : "");
+ }
+
+ public static int batImgSrcId (boolean charge) {
+ return charge ? R.drawable.ic_battery_charging_full_green_24dp : R.drawable.ic_battery_alert_red_24dp;
+ }
+
+ public static int batImgVisibility (boolean charge, int status) {
+ return ((charge && status <= 10 && !isMax()) || status <= 1) ? View.VISIBLE : View.GONE;
+ }
+
+}
diff --git a/OpenPods/app/src/main/java/com/dosse/airpods/PermissionUtils.java b/OpenPods/app/src/main/java/com/dosse/airpods/utils/PermissionUtils.java
similarity index 98%
rename from OpenPods/app/src/main/java/com/dosse/airpods/PermissionUtils.java
rename to OpenPods/app/src/main/java/com/dosse/airpods/utils/PermissionUtils.java
index 621330f..3ceb090 100644
--- a/OpenPods/app/src/main/java/com/dosse/airpods/PermissionUtils.java
+++ b/OpenPods/app/src/main/java/com/dosse/airpods/utils/PermissionUtils.java
@@ -1,4 +1,4 @@
-package com.dosse.airpods;
+package com.dosse.airpods.utils;
import static androidx.core.content.ContextCompat.checkSelfPermission;
diff --git a/OpenPods/app/src/main/java/com/dosse/airpods/utils/ScannerUtils.java b/OpenPods/app/src/main/java/com/dosse/airpods/utils/ScannerUtils.java
new file mode 100644
index 0000000..1df38d5
--- /dev/null
+++ b/OpenPods/app/src/main/java/com/dosse/airpods/utils/ScannerUtils.java
@@ -0,0 +1,30 @@
+package com.dosse.airpods.utils;
+
+import com.dosse.airpods.PodsService;
+
+public class ScannerUtils {
+
+ /*
+ * Decodes the byte array to a hexadecimal string
+ */
+ public static String decodeHex (byte[] bArr) {
+ StringBuilder ret = new StringBuilder();
+
+ for (byte b : bArr)
+ ret.append(String.format("%02X", b));
+
+ return ret.toString();
+ }
+
+ public static boolean isFlipped (String str) {
+ return (Integer.parseInt("" + str.charAt(10), 16) & 0x02) == 0;
+ }
+
+ /*
+ * Check if model is airpods max
+ */
+ public static boolean isMax () {
+ return PodsService.model.equals(PodsService.MODEL_AIRPODS_MAX);
+ }
+
+}
diff --git a/OpenPods/app/src/main/res/drawable/podmax.png b/OpenPods/app/src/main/res/drawable/podmax.png
new file mode 100644
index 0000000..78ecd84
Binary files /dev/null and b/OpenPods/app/src/main/res/drawable/podmax.png differ
diff --git a/OpenPods/app/src/main/res/drawable/podmax_disconnected.png b/OpenPods/app/src/main/res/drawable/podmax_disconnected.png
new file mode 100644
index 0000000..0621384
Binary files /dev/null and b/OpenPods/app/src/main/res/drawable/podmax_disconnected.png differ
diff --git a/OpenPods/app/src/main/res/values-de/strings.xml b/OpenPods/app/src/main/res/values-de/strings.xml
index 745b8a9..b56bc90 100644
--- a/OpenPods/app/src/main/res/values-de/strings.xml
+++ b/OpenPods/app/src/main/res/values-de/strings.xml
@@ -14,7 +14,7 @@