Added in ear detection

This commit is contained in:
Electric
2020-11-30 15:21:01 +02:00
parent cd8e855428
commit 69c2577c12
8 changed files with 52 additions and 4 deletions
@@ -52,6 +52,7 @@ public class PodsService extends Service {
private static BluetoothLeScanner btScanner;
private static int leftStatus = 15, rightStatus = 15, caseStatus = 15;
private static boolean chargeL = false, chargeR = false, chargeCase = false;
private static boolean inEarL = false, inEarR = false;
private static final String MODEL_AIRPODS_NORMAL = "airpods12", MODEL_AIRPODS_PRO = "airpodspro";
private static String model = MODEL_AIRPODS_NORMAL;
@@ -75,12 +76,16 @@ public class PodsService extends Service {
* This was done through reverse engineering. Hopefully it's correct.
* - The beacon coming from a pair of AirPods contains a manufacturer specific data field n°76 of 27 bytes
* - We convert this data to a hexadecimal string
* - The 12th and 13th characters in the string represent the charge of the left and right pods. Under unknown circumstances, they are right and left instead (see isFlipped). Values between 0 and 10 are battery 0-100%; Value 15 means it's disconnected
* - The 12th and 13th characters in the string represent the charge of the left and right pods. Under unknown circumstances[1], they are right and left instead (see isFlipped). Values between 0 and 10 are battery 0-100%; Value 15 means it's disconnected
* - The 15th character in the string represents the charge of the case. Values between 0 and 10 are battery 0-100%; Value 15 means it's disconnected
* - The 14th character in the string represents the "in charge" status. Bit 0 (LSB) is the left pod; Bit 1 is the right pod; Bit 2 is the case. Bit 3 might be case open/closed but I'm not sure and it's not used
* - The 7th character in the string represents the AirPods model (E=AirPods pro)
* - The 11th character in the string represents the in-ear detection; TODO: finish documentation on in-ear detection
* <p>
* After decoding a beacon, the status is written to leftStatus, rightStatus, caseStatus, chargeL, chargeR, chargeCase so that the NotificationThread can use the information
* <p>
* Notes:
* 1) - isFlipped set by bit 1 of 10th character in the string; seems to be related to in-ear detection;
*/
private static final ArrayList<ScanResult> recentBeacons = new ArrayList<>();
private static final long RECENT_BEACONS_MAX_T_NS = 10000000000L; //10s
@@ -174,6 +179,11 @@ public class PodsService extends Service {
model = (a.charAt(7) == 'E') ? MODEL_AIRPODS_PRO : MODEL_AIRPODS_NORMAL; // Detect if these are AirPods Pro or regular ones
int inEarStatus = Integer.parseInt("" + a.charAt(11), 16);
inEarL = (inEarStatus & (flip ? 0b00001000 : 0b00000010)) != 0;
inEarR = (inEarStatus & (flip ? 0b00000010 : 0b00001000)) != 0;
lastSeenConnected = System.currentTimeMillis();
} catch (Throwable t) {
OpenPodsDebugLog("" + t);
@@ -319,7 +329,7 @@ public class PodsService extends Service {
}
if (notificationShowing) {
OpenPodsDebugLog("Left: " + leftStatus + (chargeL ? "+" : "") + " Right: " + rightStatus + (chargeR ? "+" : "") + " Case: " + caseStatus + (chargeCase ? "+" : "") + " Model: " + model);
OpenPodsDebugLog("Left: " + leftStatus + (chargeL ? "+" : "") + (inEarL ? "$" : "") + " Right: " + rightStatus + (chargeR ? "+" : "") + (inEarR ? "$" : "") + " Case: " + caseStatus + (chargeCase ? "+" : "") + " Model: " + 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);
@@ -355,6 +365,9 @@ public class PodsService extends Service {
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.rightInEarImg, inEarR ? View.VISIBLE : View.INVISIBLE);
}
else for (RemoteViews notification : notificationArr) {
notification.setViewVisibility(R.id.leftPodText, View.INVISIBLE);
@@ -366,6 +379,8 @@ public class PodsService extends Service {
notification.setViewVisibility(R.id.leftPodUpdating, View.VISIBLE);
notification.setViewVisibility(R.id.rightPodUpdating, View.VISIBLE);
notification.setViewVisibility(R.id.podCaseUpdating, View.VISIBLE);
notification.setViewVisibility(R.id.leftInEarImg, View.INVISIBLE);
notification.setViewVisibility(R.id.rightInEarImg, View.INVISIBLE);
}
try {
@@ -1,3 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
@@ -1,3 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
@@ -1,3 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="48dp"
android:height="48dp"
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@color/textColorPrimary"
android:pathData="M15,20c-0.3,0 -0.6,-0.1 -0.8,-0.1c-0.7,-0.4 -1.2,-0.9 -1.7,-2.4c-0.5,-1.6 -1.5,-2.3 -2.4,-3c-0.8,-0.6 -1.6,-1.2 -2.3,-2.5C7.3,11 7,9.9 7,9c0,-2.8 2.2,-5 5,-5s5,2.2 5,5h2c0,-3.9 -3.1,-7 -7,-7S5,5.1 5,9c0,1.3 0.4,2.6 1.1,3.9C7,14.5 8,15.4 8.9,16c0.8,0.6 1.4,1.1 1.7,2c0.6,1.8 1.4,2.8 2.7,3.5c0.5,0.2 1.1,0.4 1.6,0.4c2.2,0 4,-1.8 4,-4h-2C17,19.1 16.1,20 15,20zM9.5,9c0,1.4 1.1,2.5 2.5,2.5s2.5,-1.1 2.5,-2.5S13.4,6.5 12,6.5S9.5,7.6 9.5,9z" />
</vector>
@@ -23,6 +23,15 @@
android:layout_marginBottom="6dp"
android:src="@drawable/pod" />
<ImageView
android:id="@+id/leftInEarImg"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBottom="@id/leftPodImg"
android:layout_alignParentLeft="true"
android:layout_marginBottom="8dp"
android:src="@drawable/ic_inear_24dp" />
<TextView
android:id="@+id/leftPodText"
android:layout_width="wrap_content"
@@ -67,6 +76,16 @@
android:scaleX="-1"
android:src="@drawable/pod" />
<ImageView
android:id="@+id/rightInEarImg"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBottom="@id/rightPodImg"
android:layout_alignParentRight="true"
android:layout_marginBottom="8dp"
android:scaleX="-1"
android:src="@drawable/ic_inear_24dp" />
<TextView
android:id="@+id/rightPodText"
android:layout_width="wrap_content"
@@ -1,3 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="AppTheme" parent="Theme.AppCompat.DayNight.DarkActionBar">
+2 -2
View File
@@ -1,6 +1,6 @@
#Tue Nov 03 09:53:40 IST 2020
#Mon Nov 30 15:19:04 IST 2020
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-bin.zip