Add support for BeatsFlex, BeatsSolo3, BeatsStudio3, BeatsX, Powerbeats3, Powerbeats Pro
@@ -9,8 +9,10 @@ import android.widget.RemoteViews;
|
||||
import androidx.core.app.NotificationCompat;
|
||||
|
||||
import com.dosse.airpods.R;
|
||||
import com.dosse.airpods.pods.Pod;
|
||||
import com.dosse.airpods.pods.PodsStatus;
|
||||
import com.dosse.airpods.pods.data.IPods;
|
||||
import com.dosse.airpods.pods.data.RegularPods;
|
||||
import com.dosse.airpods.pods.data.SinglePods;
|
||||
import com.dosse.airpods.utils.PermissionUtils;
|
||||
|
||||
public class NotificationBuilder {
|
||||
@@ -46,23 +48,20 @@ public class NotificationBuilder {
|
||||
mBuilder.setCustomBigContentView(notificationLocation[0]);
|
||||
}
|
||||
|
||||
if (status.isAirpods()) for (RemoteViews notification : notificationArr) {
|
||||
notification.setImageViewResource(R.id.leftPodImg, status.getLeftPod().isConnected() ? R.drawable.pod : R.drawable.pod_disconnected);
|
||||
notification.setImageViewResource(R.id.rightPodImg, status.getRightPod().isConnected() ? R.drawable.pod : R.drawable.pod_disconnected);
|
||||
notification.setImageViewResource(R.id.podCaseImg, status.getCasePod().isConnected() ? R.drawable.pod_case : R.drawable.pod_case_disconnected);
|
||||
}
|
||||
else if (status.isAirpodsPro()) for (RemoteViews notification : notificationArr) {
|
||||
notification.setImageViewResource(R.id.leftPodImg, status.getLeftPod().isConnected() ? R.drawable.podpro : R.drawable.podpro_disconnected);
|
||||
notification.setImageViewResource(R.id.rightPodImg, status.getRightPod().isConnected() ? R.drawable.podpro : R.drawable.podpro_disconnected);
|
||||
notification.setImageViewResource(R.id.podCaseImg, status.getCasePod().isConnected() ? R.drawable.podpro_case : R.drawable.podpro_case_disconnected);
|
||||
}
|
||||
else if (status.isAirpodsMax()) for (RemoteViews notification : notificationArr) {
|
||||
notification.setImageViewResource(R.id.leftPodImg, status.getMaxPod().isConnected() ? R.drawable.podmax : R.drawable.podmax_disconnected);
|
||||
}
|
||||
IPods airpods = status.getAirpods();
|
||||
boolean single = airpods.isSingle();
|
||||
|
||||
for (RemoteViews notification : notificationArr) {
|
||||
notification.setViewVisibility(R.id.rightPod, status.isAirpodsMax() ? View.GONE : View.VISIBLE);
|
||||
notification.setViewVisibility(R.id.podCase, status.isAirpodsMax() ? View.GONE : View.VISIBLE);
|
||||
if (!single) {
|
||||
notification.setImageViewResource(R.id.leftPodImg, ((RegularPods)airpods).getLeftDrawable());
|
||||
notification.setImageViewResource(R.id.rightPodImg, ((RegularPods)airpods).getRightDrawable());
|
||||
notification.setImageViewResource(R.id.podCaseImg, ((RegularPods)airpods).getCaseDrawable());
|
||||
} else {
|
||||
notification.setImageViewResource(R.id.leftPodImg, ((SinglePods)airpods).getDrawable());
|
||||
}
|
||||
|
||||
notification.setViewVisibility(R.id.rightPod, single ? View.GONE : View.VISIBLE);
|
||||
notification.setViewVisibility(R.id.podCase, single ? View.GONE : View.VISIBLE);
|
||||
}
|
||||
|
||||
if (isFreshStatus(status)) for (RemoteViews notification : notificationArr) {
|
||||
@@ -73,20 +72,31 @@ public class NotificationBuilder {
|
||||
notification.setViewVisibility(R.id.rightPodUpdating, View.INVISIBLE);
|
||||
notification.setViewVisibility(R.id.podCaseUpdating, View.INVISIBLE);
|
||||
|
||||
notification.setTextViewText(R.id.leftPodText, status.isAirpodsMax() ? status.getMaxPod().parseStatus() : status.getLeftPod().parseStatus());
|
||||
notification.setTextViewText(R.id.rightPodText, status.getRightPod().parseStatus());
|
||||
notification.setTextViewText(R.id.podCaseText, status.getCasePod().parseStatus());
|
||||
if (!single) {
|
||||
RegularPods regularPods = (RegularPods)airpods;
|
||||
|
||||
notification.setImageViewResource(R.id.leftBatImg, batImgSrcId(status.isAirpodsMax() ? status.getMaxPod() : status.getLeftPod()));
|
||||
notification.setImageViewResource(R.id.rightBatImg, batImgSrcId(status.getRightPod()));
|
||||
notification.setImageViewResource(R.id.caseBatImg, batImgSrcId(status.getCasePod()));
|
||||
notification.setTextViewText(R.id.leftPodText, regularPods.getParsedStatus(RegularPods.LEFT));
|
||||
notification.setTextViewText(R.id.rightPodText, regularPods.getParsedStatus(RegularPods.RIGHT));
|
||||
notification.setTextViewText(R.id.podCaseText, regularPods.getParsedStatus(RegularPods.CASE));
|
||||
|
||||
notification.setViewVisibility(R.id.leftBatImg, batImgVisibility(status.isAirpodsMax() ? status.getMaxPod() : status.getLeftPod()));
|
||||
notification.setViewVisibility(R.id.rightBatImg, batImgVisibility(status.getRightPod()));
|
||||
notification.setViewVisibility(R.id.caseBatImg, batImgVisibility(status.getCasePod()));
|
||||
notification.setImageViewResource(R.id.leftBatImg, regularPods.getBatImgSrcId(RegularPods.LEFT));
|
||||
notification.setImageViewResource(R.id.rightBatImg, regularPods.getBatImgSrcId(RegularPods.RIGHT));
|
||||
notification.setImageViewResource(R.id.caseBatImg, regularPods.getBatImgSrcId(RegularPods.CASE));
|
||||
|
||||
notification.setViewVisibility(R.id.leftInEarImg, status.getLeftPod().isInEar() ? View.VISIBLE : View.INVISIBLE);
|
||||
notification.setViewVisibility(R.id.rightInEarImg, status.getRightPod().isInEar() ? View.VISIBLE : View.INVISIBLE);
|
||||
notification.setViewVisibility(R.id.leftBatImg, regularPods.getBatImgVisibility(RegularPods.LEFT));
|
||||
notification.setViewVisibility(R.id.rightBatImg, regularPods.getBatImgVisibility(RegularPods.RIGHT));
|
||||
notification.setViewVisibility(R.id.caseBatImg, regularPods.getBatImgVisibility(RegularPods.CASE));
|
||||
|
||||
notification.setViewVisibility(R.id.leftInEarImg, regularPods.getInEarVisibility(RegularPods.LEFT));
|
||||
notification.setViewVisibility(R.id.rightInEarImg, regularPods.getInEarVisibility(RegularPods.RIGHT));
|
||||
} else {
|
||||
SinglePods singlePods = (SinglePods)airpods;
|
||||
|
||||
notification.setTextViewText(R.id.leftPodText, singlePods.getParsedStatus());
|
||||
notification.setImageViewResource(R.id.leftBatImg, singlePods.getBatImgSrcId());
|
||||
notification.setViewVisibility(R.id.leftBatImg, singlePods.getBatImgVisibility());
|
||||
notification.setViewVisibility(R.id.leftInEarImg, singlePods.getInEarVisibility());
|
||||
}
|
||||
}
|
||||
else for (RemoteViews notification : notificationArr) {
|
||||
notification.setViewVisibility(R.id.leftPodText, View.INVISIBLE);
|
||||
@@ -109,12 +119,4 @@ public class NotificationBuilder {
|
||||
return System.currentTimeMillis() - status.getTimestamp() < TIMEOUT_CONNECTED;
|
||||
}
|
||||
|
||||
private static int batImgSrcId (Pod pod) {
|
||||
return pod.isCharging() ? R.drawable.ic_battery_charging_full_green_24dp : R.drawable.ic_battery_alert_red_24dp;
|
||||
}
|
||||
|
||||
private static int batImgVisibility (Pod pod) {
|
||||
return (pod.isCharging() && pod.isConnected() || pod.isLowBattery()) ? View.VISIBLE : View.GONE;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -62,7 +62,7 @@ public abstract class NotificationThread extends Thread {
|
||||
Logger.debug("Creating notification");
|
||||
notificationShowing = true;
|
||||
}
|
||||
Logger.debug(status.parseStatusForLogger());
|
||||
Logger.debug(status.getAirpods().parseStatusForLogger());
|
||||
mNotifyManager.notify(NOTIFICATION_ID, builder.build(status));
|
||||
} else {
|
||||
if (notificationShowing) {
|
||||
|
||||
@@ -1,11 +1,23 @@
|
||||
package com.dosse.airpods.pods;
|
||||
|
||||
import java.util.Locale;
|
||||
import com.dosse.airpods.pods.data.AirPods1;
|
||||
import com.dosse.airpods.pods.data.AirPods2;
|
||||
import com.dosse.airpods.pods.data.AirPodsMax;
|
||||
import com.dosse.airpods.pods.data.AirPodsPro;
|
||||
import com.dosse.airpods.pods.data.BeatsFlex;
|
||||
import com.dosse.airpods.pods.data.BeatsSolo3;
|
||||
import com.dosse.airpods.pods.data.BeatsStudio3;
|
||||
import com.dosse.airpods.pods.data.BeatsX;
|
||||
import com.dosse.airpods.pods.data.IPods;
|
||||
import com.dosse.airpods.pods.data.Pod;
|
||||
import com.dosse.airpods.pods.data.Powerbeats3;
|
||||
import com.dosse.airpods.pods.data.PowerbeatsPro;
|
||||
import com.dosse.airpods.pods.data.RegularPods;
|
||||
|
||||
/**
|
||||
* Decoding the beacon:
|
||||
* 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
|
||||
* - The beacon coming from a pair of AirPods/Beats 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[1], they are right and left instead (see isFlipped). Values between 0 and 10 are battery 0-100%; Value 15 means it's disconnected
|
||||
@@ -13,19 +25,16 @@ import java.util.Locale;
|
||||
* - 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 11th character in the string represents the in-ear detection status. Bit 1 is the left pod; Bit 3 is the right pod.
|
||||
* - The 7th character in the string represents the AirPods model (E=AirPods pro, A=AirPods max)
|
||||
* - The 7th character in the string represents the model
|
||||
*
|
||||
* Notes:
|
||||
* 1) - isFlipped set by bit 1 of 10th character in the string; seems to be related to in-ear detection;
|
||||
*/
|
||||
public class PodsStatus {
|
||||
|
||||
public static final String MODEL_AIRPODS_NORMAL = "airpods12", MODEL_AIRPODS_PRO = "airpodspro", MODEL_AIRPODS_MAX = "airpodsmax";
|
||||
|
||||
public static final PodsStatus DISCONNECTED = new PodsStatus();
|
||||
|
||||
private Pod leftPod, rightPod, casePod, maxPod;
|
||||
private String model = MODEL_AIRPODS_NORMAL;
|
||||
private IPods pods;
|
||||
private final long timestamp = System.currentTimeMillis();
|
||||
|
||||
public PodsStatus () {
|
||||
@@ -40,90 +49,66 @@ public class PodsStatus {
|
||||
int leftStatus = Integer.parseInt("" + status.charAt(flip ? 12 : 13), 16); // Left airpod (0-10 batt; 15=disconnected)
|
||||
int rightStatus = Integer.parseInt("" + status.charAt(flip ? 13 : 12), 16); // Right airpod (0-10 batt; 15=disconnected)
|
||||
int caseStatus = Integer.parseInt("" + status.charAt(15), 16); // Case (0-10 batt; 15=disconnected)
|
||||
int maxStatus = Integer.parseInt("" + status.charAt(13), 16); // Airpods max (0-10 batt; 15=disconnected)
|
||||
int singleStatus = Integer.parseInt("" + status.charAt(13), 16); // Single (0-10 batt; 15=disconnected)
|
||||
|
||||
int chargeStatus = Integer.parseInt("" + status.charAt(14), 16); // Charge status (bit 0=left; bit 1=right; bit 2=case)
|
||||
|
||||
boolean chargeL = (chargeStatus & (flip ? 0b00000010 : 0b00000001)) != 0;
|
||||
boolean chargeR = (chargeStatus & (flip ? 0b00000001 : 0b00000010)) != 0;
|
||||
boolean chargeCase = (chargeStatus & 0b00000100) != 0;
|
||||
boolean chargeMax = (chargeStatus & 0b00000001) != 0;
|
||||
boolean chargeSingle = (chargeStatus & 0b00000001) != 0;
|
||||
|
||||
int inEarStatus = Integer.parseInt("" + status.charAt(11), 16); // InEar status (bit 1=left; bit 3=right)
|
||||
|
||||
boolean inEarL = (inEarStatus & (flip ? 0b00001000 : 0b00000010)) != 0;
|
||||
boolean inEarR = (inEarStatus & (flip ? 0b00000010 : 0b00001000)) != 0;
|
||||
|
||||
switch (status.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;
|
||||
}
|
||||
Pod leftPod = new Pod(leftStatus, chargeL, inEarL);
|
||||
Pod rightPod = new Pod(rightStatus, chargeR, inEarR);
|
||||
Pod casePod = new Pod(caseStatus, chargeCase, false);
|
||||
Pod singlePod = new Pod(singleStatus, chargeSingle, false);
|
||||
|
||||
leftPod = new Pod(leftStatus, chargeL, inEarL);
|
||||
rightPod = new Pod(rightStatus, chargeR, inEarR);
|
||||
casePod = new Pod(caseStatus, chargeCase, false);
|
||||
maxPod = new Pod(maxStatus, chargeMax, false);
|
||||
// Detect which model
|
||||
switch (status.charAt(7)) {
|
||||
case '2': pods = new AirPods1(leftPod, rightPod, casePod); // Airpods 1st gen
|
||||
break;
|
||||
case 'F': pods = new AirPods2(leftPod, rightPod, casePod); // Airpods 2nd gen
|
||||
break;
|
||||
case 'E': pods = new AirPodsPro(leftPod, rightPod, casePod); // Airpods Pro
|
||||
break;
|
||||
case 'A': pods = new AirPodsMax(singlePod); // Airpods Max
|
||||
break;
|
||||
case 'B': pods = new PowerbeatsPro(leftPod, rightPod, casePod); // Powerbeats Pro
|
||||
break;
|
||||
case '5': pods = new BeatsX(singlePod); // Beats X
|
||||
break;
|
||||
case '0': pods = new BeatsFlex(singlePod); // Beats Flex
|
||||
break;
|
||||
case '6': pods = new BeatsSolo3(singlePod); // Beats Solo 3
|
||||
break;
|
||||
case '9': pods = new BeatsStudio3(singlePod); // Beats Studio 3
|
||||
break;
|
||||
case '3': pods = new Powerbeats3(singlePod); // Powerbeats 3
|
||||
break;
|
||||
default: pods = new RegularPods(leftPod, rightPod, casePod); // Unknown
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isFlipped (String str) {
|
||||
return (Integer.parseInt("" + str.charAt(10), 16) & 0x02) == 0;
|
||||
}
|
||||
|
||||
//region Pod
|
||||
public Pod getLeftPod () {
|
||||
return leftPod;
|
||||
}
|
||||
|
||||
public Pod getRightPod () {
|
||||
return rightPod;
|
||||
}
|
||||
|
||||
public Pod getCasePod () {
|
||||
return casePod;
|
||||
}
|
||||
|
||||
public Pod getMaxPod () {
|
||||
return maxPod;
|
||||
}
|
||||
//endregion
|
||||
|
||||
public String parseStatusForLogger () {
|
||||
return isAirpodsMax() ?
|
||||
String.format(Locale.getDefault(), "Battery: %d%s Model: %s",
|
||||
maxPod.getStatus(), maxPod.isCharging() ? "+" : "", model) :
|
||||
String.format(Locale.getDefault(), "Left: %d%s%s Right: %d%s%s Case: %d%s Model: %s",
|
||||
leftPod.getStatus(), leftPod.isCharging() ? "+" : "", leftPod.isInEar() ? "$" : "",
|
||||
rightPod.getStatus(), rightPod.isCharging() ? "+" : "", rightPod.isInEar() ? "$" : "",
|
||||
casePod.getStatus(), casePod.isCharging() ? "+" : "", model);
|
||||
public IPods getAirpods () {
|
||||
return pods;
|
||||
}
|
||||
|
||||
public boolean isAllDisconnected () {
|
||||
if (this == DISCONNECTED)
|
||||
return true;
|
||||
|
||||
return leftPod.isDisconnected() &&
|
||||
rightPod.isDisconnected() &&
|
||||
casePod.isDisconnected() &&
|
||||
maxPod.isDisconnected();
|
||||
return pods.isDisconnected();
|
||||
}
|
||||
|
||||
//region Model
|
||||
public boolean isAirpods () {
|
||||
return model.equals(MODEL_AIRPODS_NORMAL);
|
||||
}
|
||||
|
||||
public boolean isAirpodsPro () {
|
||||
return model.equals(MODEL_AIRPODS_PRO);
|
||||
}
|
||||
|
||||
public boolean isAirpodsMax () {
|
||||
return model.equals(MODEL_AIRPODS_MAX);
|
||||
}
|
||||
//endregion
|
||||
|
||||
public long getTimestamp () {
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.dosse.airpods.pods.data;
|
||||
|
||||
public class AirPods1 extends RegularPods {
|
||||
|
||||
public AirPods1 (Pod leftPod, Pod rightPod, Pod casePod) {
|
||||
super(leftPod, rightPod, casePod);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getModel () {
|
||||
return MODEL_AIRPODS_GEN1;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.dosse.airpods.pods.data;
|
||||
|
||||
public class AirPods2 extends RegularPods {
|
||||
|
||||
public AirPods2 (Pod leftPod, Pod rightPod, Pod casePod) {
|
||||
super(leftPod, rightPod, casePod);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getModel () {
|
||||
return MODEL_AIRPODS_GEN2;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.dosse.airpods.pods.data;
|
||||
|
||||
import com.dosse.airpods.R;
|
||||
|
||||
public class AirPodsMax extends SinglePods {
|
||||
|
||||
public AirPodsMax (Pod singlePod) {
|
||||
super(singlePod);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDrawable () {
|
||||
return getPod().isConnected() ? R.drawable.podmax : R.drawable.podmax_disconnected;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getModel () {
|
||||
return MODEL_AIRPODS_MAX;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.dosse.airpods.pods.data;
|
||||
|
||||
import com.dosse.airpods.R;
|
||||
|
||||
public class AirPodsPro extends RegularPods {
|
||||
|
||||
public AirPodsPro (Pod leftPod, Pod rightPod, Pod casePod) {
|
||||
super(leftPod, rightPod, casePod);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLeftDrawable () {
|
||||
return getPod(LEFT).isConnected() ? R.drawable.podpro : R.drawable.podpro_disconnected;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRightDrawable () {
|
||||
return getPod(RIGHT).isConnected() ? R.drawable.podpro : R.drawable.podpro_disconnected;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCaseDrawable () {
|
||||
return getPod(CASE).isConnected() ? R.drawable.podpro_case : R.drawable.podpro_case_disconnected;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getModel () {
|
||||
return MODEL_AIRPODS_PRO;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.dosse.airpods.pods.data;
|
||||
|
||||
import com.dosse.airpods.R;
|
||||
|
||||
public class BeatsFlex extends SinglePods {
|
||||
|
||||
public BeatsFlex (Pod singlePod) {
|
||||
super(singlePod);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDrawable () {
|
||||
return getPod().isConnected() ? R.drawable.beatsflex : R.drawable.beatsflex_disconnected;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getModel () {
|
||||
return MODEL_BEATS_FLEX;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.dosse.airpods.pods.data;
|
||||
|
||||
import com.dosse.airpods.R;
|
||||
|
||||
public class BeatsSolo3 extends SinglePods {
|
||||
|
||||
public BeatsSolo3 (Pod singlePod) {
|
||||
super(singlePod);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDrawable () {
|
||||
return getPod().isConnected() ? R.drawable.beatssolo3 : R.drawable.beatssolo3_disconnected;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getModel () {
|
||||
return MODEL_BEATS_SOLO_3;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.dosse.airpods.pods.data;
|
||||
|
||||
import com.dosse.airpods.R;
|
||||
|
||||
public class BeatsStudio3 extends SinglePods {
|
||||
|
||||
public BeatsStudio3 (Pod singlePod) {
|
||||
super(singlePod);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDrawable () {
|
||||
return getPod().isConnected() ? R.drawable.beatsstudio3 : R.drawable.beatsstudio3_disconnected;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getModel () {
|
||||
return MODEL_BEATS_STUDIO_3;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.dosse.airpods.pods.data;
|
||||
|
||||
import com.dosse.airpods.R;
|
||||
|
||||
public class BeatsX extends SinglePods {
|
||||
|
||||
public BeatsX (Pod singlePod) {
|
||||
super(singlePod);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDrawable () {
|
||||
return getPod().isConnected() ? R.drawable.beatsx : R.drawable.beatsx_disconnected;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getModel () {
|
||||
return MODEL_BEATS_X;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.dosse.airpods.pods.data;
|
||||
|
||||
public interface IPods {
|
||||
|
||||
String MODEL_AIRPODS_GEN1 = "airpods1";
|
||||
String MODEL_AIRPODS_GEN2 = "airpods2";
|
||||
String MODEL_AIRPODS_PRO = "airpodspro";
|
||||
String MODEL_AIRPODS_MAX = "airpodsmax";
|
||||
String MODEL_BEATS_X = "beatsx";
|
||||
String MODEL_BEATS_FLEX = "beatsflex";
|
||||
String MODEL_BEATS_SOLO_3 = "beatssolo3";
|
||||
String MODEL_BEATS_STUDIO_3 = "beatsstudio3";
|
||||
String MODEL_POWERBEATS_3 = "powerbeats3";
|
||||
String MODEL_POWERBEATS_PRO = "powerbeatspro";
|
||||
String MODEL_UNKNOWN = "unknown";
|
||||
|
||||
|
||||
String getModel ();
|
||||
|
||||
boolean isSingle ();
|
||||
|
||||
boolean isDisconnected ();
|
||||
|
||||
String parseStatusForLogger ();
|
||||
|
||||
}
|
||||
@@ -1,4 +1,8 @@
|
||||
package com.dosse.airpods.pods;
|
||||
package com.dosse.airpods.pods.data;
|
||||
|
||||
import android.view.View;
|
||||
|
||||
import com.dosse.airpods.R;
|
||||
|
||||
public class Pod {
|
||||
|
||||
@@ -37,11 +41,23 @@ public class Pod {
|
||||
}
|
||||
|
||||
public boolean isDisconnected () {
|
||||
return status == DISCONNECTED_STATUS;
|
||||
return status == DISCONNECTED_STATUS;
|
||||
}
|
||||
|
||||
public boolean isLowBattery () {
|
||||
return status <= LOW_BATTERY_STATUS;
|
||||
}
|
||||
|
||||
}
|
||||
public int inEarVisibility () {
|
||||
return inEar ? View.VISIBLE : View.INVISIBLE;
|
||||
}
|
||||
|
||||
public int batImgVisibility () {
|
||||
return (charging && isConnected() || isLowBattery()) ? View.VISIBLE : View.GONE;
|
||||
}
|
||||
|
||||
public int batImgSrcId () {
|
||||
return charging ? R.drawable.ic_battery_charging_full_green_24dp : R.drawable.ic_battery_alert_red_24dp;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.dosse.airpods.pods.data;
|
||||
|
||||
import com.dosse.airpods.R;
|
||||
|
||||
public class Powerbeats3 extends SinglePods {
|
||||
|
||||
public Powerbeats3 (Pod singlePod) {
|
||||
super(singlePod);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDrawable () {
|
||||
return getPod().isConnected() ? R.drawable.powerbeats3 : R.drawable.powerbeats3_disconnected;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getModel () {
|
||||
return MODEL_POWERBEATS_3;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.dosse.airpods.pods.data;
|
||||
|
||||
import com.dosse.airpods.R;
|
||||
|
||||
public class PowerbeatsPro extends RegularPods {
|
||||
|
||||
public PowerbeatsPro (Pod leftPod, Pod rightPod, Pod casePod) {
|
||||
super(leftPod, rightPod, casePod);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLeftDrawable () {
|
||||
return getPod(LEFT).isConnected() ? R.drawable.powerbeatspro : R.drawable.powerbeatspro_disconnected;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRightDrawable () {
|
||||
return getPod(RIGHT).isConnected() ? R.drawable.powerbeatspro : R.drawable.powerbeatspro_disconnected;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCaseDrawable () {
|
||||
return getPod(CASE).isConnected() ? R.drawable.powerbeatspro_case : R.drawable.powerbeatspro_case_disconnected;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getModel () {
|
||||
return MODEL_POWERBEATS_PRO;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
package com.dosse.airpods.pods.data;
|
||||
|
||||
import com.dosse.airpods.R;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
public class RegularPods implements IPods {
|
||||
|
||||
public static final int LEFT = 0, RIGHT = 1, CASE = 2;
|
||||
|
||||
private final Pod[] pods;
|
||||
|
||||
public RegularPods (Pod leftPod, Pod rightPod, Pod casePod) {
|
||||
this.pods = new Pod[] {leftPod, rightPod, casePod};
|
||||
}
|
||||
|
||||
public Pod getPod (int pos) {
|
||||
return pods[pos];
|
||||
}
|
||||
|
||||
public String getParsedStatus (int pos) {
|
||||
return pods[pos].parseStatus();
|
||||
}
|
||||
|
||||
public int getInEarVisibility (int pos) {
|
||||
return pods[pos].inEarVisibility();
|
||||
}
|
||||
|
||||
public int getBatImgVisibility (int pos) {
|
||||
return pods[pos].batImgVisibility();
|
||||
}
|
||||
|
||||
public int getBatImgSrcId (int pos) {
|
||||
return pods[pos].batImgSrcId();
|
||||
}
|
||||
|
||||
public int getLeftDrawable () {
|
||||
return getPod(LEFT).isConnected() ? R.drawable.pod : R.drawable.pod_disconnected;
|
||||
}
|
||||
|
||||
public int getRightDrawable () {
|
||||
return getPod(RIGHT).isConnected() ? R.drawable.pod : R.drawable.pod_disconnected;
|
||||
}
|
||||
|
||||
public int getCaseDrawable () {
|
||||
return getPod(CASE).isConnected() ? R.drawable.pod_case : R.drawable.pod_case_disconnected;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getModel () {
|
||||
return MODEL_UNKNOWN;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSingle () {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDisconnected () {
|
||||
return pods[LEFT].isDisconnected() &&
|
||||
pods[RIGHT].isDisconnected() &&
|
||||
pods[CASE].isDisconnected();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String parseStatusForLogger () {
|
||||
return String.format(Locale.getDefault(), "Left: %d%s%s Right: %d%s%s Case: %d%s Model: %s",
|
||||
pods[LEFT].getStatus(), pods[LEFT].isCharging() ? "+" : "", pods[LEFT].isInEar() ? "$" : "",
|
||||
pods[RIGHT].getStatus(), pods[RIGHT].isCharging() ? "+" : "", pods[RIGHT].isInEar() ? "$" : "",
|
||||
pods[CASE].getStatus(), pods[CASE].isCharging() ? "+" : "", getModel());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package com.dosse.airpods.pods.data;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
public abstract class SinglePods implements IPods {
|
||||
|
||||
public abstract int getDrawable ();
|
||||
|
||||
private final Pod pod;
|
||||
|
||||
public SinglePods (Pod pod) {
|
||||
this.pod = pod;
|
||||
}
|
||||
|
||||
public Pod getPod () {
|
||||
return pod;
|
||||
}
|
||||
|
||||
public String getParsedStatus () {
|
||||
return pod.parseStatus();
|
||||
}
|
||||
|
||||
public int getInEarVisibility () {
|
||||
return pod.inEarVisibility();
|
||||
}
|
||||
|
||||
public int getBatImgVisibility () {
|
||||
return pod.batImgVisibility();
|
||||
}
|
||||
|
||||
public int getBatImgSrcId () {
|
||||
return pod.batImgSrcId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSingle () {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDisconnected () {
|
||||
return pod.isDisconnected();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String parseStatusForLogger () {
|
||||
return String.format(Locale.getDefault(), "Battery: %d%s Model: %s", pod.getStatus(), pod.isCharging() ? "+" : "", getModel());
|
||||
}
|
||||
|
||||
}
|
||||
|
After Width: | Height: | Size: 40 KiB |
|
After Width: | Height: | Size: 33 KiB |
|
After Width: | Height: | Size: 40 KiB |
|
After Width: | Height: | Size: 33 KiB |
|
After Width: | Height: | Size: 40 KiB |
|
After Width: | Height: | Size: 33 KiB |
|
After Width: | Height: | Size: 40 KiB |
|
After Width: | Height: | Size: 33 KiB |
|
After Width: | Height: | Size: 40 KiB |
|
After Width: | Height: | Size: 33 KiB |
|
After Width: | Height: | Size: 161 KiB |
|
After Width: | Height: | Size: 46 KiB |
|
After Width: | Height: | Size: 44 KiB |
|
After Width: | Height: | Size: 161 KiB |