Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9cd800875f | ||
|
|
f693b9a5c5 | ||
|
|
5ab3b13350 | ||
|
|
8c462cc32c | ||
|
|
695dea99bd | ||
|
|
e0110d9264 | ||
|
|
7c21e2d464 | ||
|
|
50e6833c8d | ||
|
|
6a00d8ce24 | ||
|
|
93d52efc27 | ||
|
|
299aad0b7e | ||
|
|
3268a71f9b | ||
|
|
35aeae2e2b | ||
|
|
fbd643cd5f | ||
|
|
4b42f13a02 | ||
|
|
fbba6cd54f | ||
|
|
30fa4443e9 | ||
|
|
2fef18973b | ||
|
|
13d2945d3b | ||
|
|
f768564a43 | ||
|
|
60de5515e3 | ||
|
|
e52c254a45 | ||
|
|
131f470344 | ||
|
|
702c8116d1 | ||
|
|
56b7df7b32 | ||
|
|
414085698f | ||
|
|
4590e46b64 | ||
|
|
d03dc6b374 | ||
|
|
ec3e32e3f3 | ||
|
|
f16d02786e | ||
|
|
4ef334661a | ||
|
|
05f10b00e6 | ||
|
|
888c6e7d0d | ||
|
|
019ef80ec9 | ||
|
|
4abb9e5d6b | ||
|
|
b15dcfbf9d | ||
|
|
92f64ae2dd | ||
|
|
a39dbc3785 | ||
|
|
ee7e75b5e8 | ||
|
|
b939674a62 | ||
|
|
ade3d0a481 | ||
|
|
70a05a1848 | ||
|
|
6803ef0691 | ||
|
|
71f08a4098 | ||
|
|
17afa55203 | ||
|
|
96ffaf1d90 | ||
|
|
047b9b373c |
@@ -8,7 +8,7 @@
|
|||||||
<a href="https://www.gnu.org/licenses/gpl-3.0">
|
<a href="https://www.gnu.org/licenses/gpl-3.0">
|
||||||
<img src="https://img.shields.io/badge/License-GPL%20v3-blue.svg" alt="License: GPLv3">
|
<img src="https://img.shields.io/badge/License-GPL%20v3-blue.svg" alt="License: GPLv3">
|
||||||
</a>
|
</a>
|
||||||
<img alt="Minimum SDK" src="https://img.shields.io/badge/API-23%2B-green">
|
<img alt="Minimum SDK" src="https://img.shields.io/badge/API-26%2B-green">
|
||||||
</p>
|
</p>
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://f-droid.org/packages/com.dosse.airpods">
|
<a href="https://f-droid.org/packages/com.dosse.airpods">
|
||||||
@@ -47,6 +47,8 @@
|
|||||||
* Apple AirPods 2nd gen
|
* Apple AirPods 2nd gen
|
||||||
* Apple AirPods 3rd gen
|
* Apple AirPods 3rd gen
|
||||||
* Apple AirPods Pro
|
* Apple AirPods Pro
|
||||||
|
* Apple AirPods Pro 2
|
||||||
|
* Apple AirPods Pro 3
|
||||||
* Apple AirPods Max
|
* Apple AirPods Max
|
||||||
* Beats X
|
* Beats X
|
||||||
* Beats Flex
|
* Beats Flex
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
apply plugin: 'com.android.application'
|
plugins {
|
||||||
|
alias(libs.plugins.androidApplication)
|
||||||
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 31
|
namespace 'com.dosse.airpods'
|
||||||
buildToolsVersion '32.0.0'
|
compileSdk = 36
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId "com.dosse.airpods"
|
applicationId "com.dosse.airpods"
|
||||||
minSdkVersion 23
|
minSdk 26
|
||||||
targetSdkVersion 31
|
targetSdk 36
|
||||||
versionCode 20
|
versionCode 24
|
||||||
versionName '1.8'
|
versionName '1.11'
|
||||||
|
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
@@ -21,18 +21,16 @@ android {
|
|||||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
buildFeatures {
|
||||||
lintOptions {
|
buildConfig true
|
||||||
abortOnError false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
compileOptions {
|
compileOptions {
|
||||||
sourceCompatibility = 11
|
sourceCompatibility JavaVersion.VERSION_17
|
||||||
targetCompatibility = 11
|
targetCompatibility JavaVersion.VERSION_17
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation 'androidx.appcompat:appcompat:1.4.1'
|
implementation libs.androidx.appcompat
|
||||||
implementation 'androidx.preference:preference:1.2.0'
|
implementation libs.androidx.preference
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,8 +11,8 @@
|
|||||||
"type": "SINGLE",
|
"type": "SINGLE",
|
||||||
"filters": [],
|
"filters": [],
|
||||||
"attributes": [],
|
"attributes": [],
|
||||||
"versionCode": 20,
|
"versionCode": 21,
|
||||||
"versionName": "1.8",
|
"versionName": "1.9",
|
||||||
"outputFile": "app-release.apk"
|
"outputFile": "app-release.apk"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -1,15 +1,21 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
package="com.dosse.airpods">
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||||
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
|
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
|
||||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||||
<uses-permission android:name="android.permission.BLUETOOTH" />
|
<uses-permission android:name="android.permission.BLUETOOTH" />
|
||||||
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
|
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
|
||||||
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
|
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
|
||||||
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
|
<uses-permission
|
||||||
|
android:name="android.permission.BLUETOOTH_SCAN"
|
||||||
|
android:usesPermissionFlags="neverForLocation"
|
||||||
|
tools:targetApi="s" />
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||||
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />
|
||||||
|
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||||
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
|
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
|
||||||
|
|
||||||
@@ -23,7 +29,8 @@
|
|||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@style/AppTheme">
|
android:theme="@style/AppTheme"
|
||||||
|
tools:ignore="DataExtractionRules">
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.MainActivity"
|
android:name=".ui.MainActivity"
|
||||||
@@ -47,13 +54,10 @@
|
|||||||
android:launchMode="singleTop"
|
android:launchMode="singleTop"
|
||||||
android:parentActivityName=".ui.MainActivity" />
|
android:parentActivityName=".ui.MainActivity" />
|
||||||
|
|
||||||
<activity
|
<activity android:name=".ui.NoBTActivity" />
|
||||||
android:name=".ui.NoBTActivity"
|
|
||||||
android:label="@string/app_name" />
|
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.IntroActivity"
|
android:name=".ui.IntroActivity"
|
||||||
android:label="@string/app_name"
|
|
||||||
android:theme="@style/AppTheme.NoActionBar" />
|
android:theme="@style/AppTheme.NoActionBar" />
|
||||||
|
|
||||||
<receiver
|
<receiver
|
||||||
|
|||||||
@@ -8,36 +8,41 @@ import androidx.core.app.NotificationCompat;
|
|||||||
|
|
||||||
import com.dosse.airpods.R;
|
import com.dosse.airpods.R;
|
||||||
import com.dosse.airpods.pods.PodsStatus;
|
import com.dosse.airpods.pods.PodsStatus;
|
||||||
import com.dosse.airpods.pods.data.IPods;
|
import com.dosse.airpods.pods.models.IPods;
|
||||||
import com.dosse.airpods.pods.data.RegularPods;
|
import com.dosse.airpods.pods.models.RegularPods;
|
||||||
import com.dosse.airpods.pods.data.SinglePods;
|
import com.dosse.airpods.pods.models.SinglePods;
|
||||||
|
|
||||||
public class NotificationBuilder {
|
public class NotificationBuilder {
|
||||||
public static final String TAG = "AirPods";
|
public static final String TAG = "AirPods";
|
||||||
public static final long TIMEOUT_CONNECTED = 30000;
|
public static final long TIMEOUT_CONNECTED = 30000;
|
||||||
public static final int NOTIFICATION_ID = 1;
|
public static final int NOTIFICATION_ID = 1;
|
||||||
|
|
||||||
private final RemoteViews[] notificationArr;
|
private final RemoteViews[] mRemoteViews;
|
||||||
private final NotificationCompat.Builder mBuilder;
|
private final NotificationCompat.Builder mBuilder;
|
||||||
|
|
||||||
public NotificationBuilder (Context context) {
|
public NotificationBuilder(Context context) {
|
||||||
notificationArr = new RemoteViews[] {new RemoteViews(context.getPackageName(), R.layout.status_big), new RemoteViews(context.getPackageName(), R.layout.status_small)};
|
mRemoteViews = new RemoteViews[] {
|
||||||
|
new RemoteViews(context.getPackageName(), R.layout.status_big),
|
||||||
|
new RemoteViews(context.getPackageName(), R.layout.status_small)
|
||||||
|
};
|
||||||
|
|
||||||
mBuilder = new NotificationCompat.Builder(context, TAG);
|
mBuilder = new NotificationCompat.Builder(context, TAG);
|
||||||
mBuilder.setShowWhen(false);
|
mBuilder.setShowWhen(false);
|
||||||
mBuilder.setOngoing(true);
|
mBuilder.setOngoing(true);
|
||||||
mBuilder.setSmallIcon(R.mipmap.notification_icon);
|
mBuilder.setSmallIcon(R.mipmap.notification_icon);
|
||||||
mBuilder.setVisibility(NotificationCompat.VISIBILITY_PUBLIC);
|
mBuilder.setVisibility(NotificationCompat.VISIBILITY_PUBLIC);
|
||||||
mBuilder.setCustomContentView(notificationArr[1]);
|
mBuilder.setCustomContentView(mRemoteViews[1]);
|
||||||
mBuilder.setCustomBigContentView(notificationArr[0]);
|
mBuilder.setCustomBigContentView(mRemoteViews[0]);
|
||||||
|
mBuilder.setStyle(new NotificationCompat.DecoratedCustomViewStyle()); //Make the notification extendable issue #165
|
||||||
|
mBuilder.setCategory(NotificationCompat.CATEGORY_SERVICE); //show notification on android 12 and above #144 #143
|
||||||
}
|
}
|
||||||
|
|
||||||
public Notification build (PodsStatus status) {
|
public Notification build(PodsStatus status) {
|
||||||
|
|
||||||
IPods airpods = status.getAirpods();
|
IPods airpods = status.getAirpods();
|
||||||
boolean single = airpods.isSingle();
|
boolean single = airpods.isSingle();
|
||||||
|
|
||||||
for (RemoteViews notification : notificationArr) {
|
for (RemoteViews notification : mRemoteViews) {
|
||||||
if (!single) {
|
if (!single) {
|
||||||
notification.setImageViewResource(R.id.leftPodImg, ((RegularPods)airpods).getLeftDrawable());
|
notification.setImageViewResource(R.id.leftPodImg, ((RegularPods)airpods).getLeftDrawable());
|
||||||
notification.setImageViewResource(R.id.rightPodImg, ((RegularPods)airpods).getRightDrawable());
|
notification.setImageViewResource(R.id.rightPodImg, ((RegularPods)airpods).getRightDrawable());
|
||||||
@@ -50,7 +55,7 @@ public class NotificationBuilder {
|
|||||||
notification.setViewVisibility(R.id.rightPod, single ? View.GONE : View.VISIBLE);
|
notification.setViewVisibility(R.id.rightPod, single ? View.GONE : View.VISIBLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isFreshStatus(status)) for (RemoteViews notification : notificationArr) {
|
if (isFreshStatus(status)) for (RemoteViews notification : mRemoteViews) {
|
||||||
notification.setViewVisibility(R.id.leftPodText, View.VISIBLE);
|
notification.setViewVisibility(R.id.leftPodText, View.VISIBLE);
|
||||||
notification.setViewVisibility(R.id.rightPodText, View.VISIBLE);
|
notification.setViewVisibility(R.id.rightPodText, View.VISIBLE);
|
||||||
notification.setViewVisibility(R.id.podCaseText, View.VISIBLE);
|
notification.setViewVisibility(R.id.podCaseText, View.VISIBLE);
|
||||||
@@ -82,8 +87,7 @@ public class NotificationBuilder {
|
|||||||
notification.setImageViewResource(R.id.caseBatImg, singlePods.getBatImgSrcId());
|
notification.setImageViewResource(R.id.caseBatImg, singlePods.getBatImgSrcId());
|
||||||
notification.setViewVisibility(R.id.caseBatImg, singlePods.getBatImgVisibility());
|
notification.setViewVisibility(R.id.caseBatImg, singlePods.getBatImgVisibility());
|
||||||
}
|
}
|
||||||
}
|
} else for (RemoteViews notification : mRemoteViews) {
|
||||||
else for (RemoteViews notification : notificationArr) {
|
|
||||||
notification.setViewVisibility(R.id.leftPodText, View.INVISIBLE);
|
notification.setViewVisibility(R.id.leftPodText, View.INVISIBLE);
|
||||||
notification.setViewVisibility(R.id.rightPodText, View.INVISIBLE);
|
notification.setViewVisibility(R.id.rightPodText, View.INVISIBLE);
|
||||||
notification.setViewVisibility(R.id.podCaseText, View.INVISIBLE);
|
notification.setViewVisibility(R.id.podCaseText, View.INVISIBLE);
|
||||||
@@ -100,8 +104,7 @@ public class NotificationBuilder {
|
|||||||
return mBuilder.build();
|
return mBuilder.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isFreshStatus (PodsStatus status) {
|
private boolean isFreshStatus(PodsStatus status) {
|
||||||
return System.currentTimeMillis() - status.getTimestamp() < TIMEOUT_CONNECTED;
|
return System.currentTimeMillis() - status.getTimestamp() < TIMEOUT_CONNECTED;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -4,7 +4,6 @@ import android.app.Notification;
|
|||||||
import android.app.NotificationChannel;
|
import android.app.NotificationChannel;
|
||||||
import android.app.NotificationManager;
|
import android.app.NotificationManager;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.os.Build;
|
|
||||||
|
|
||||||
import com.dosse.airpods.pods.PodsStatus;
|
import com.dosse.airpods.pods.PodsStatus;
|
||||||
import com.dosse.airpods.utils.Logger;
|
import com.dosse.airpods.utils.Logger;
|
||||||
@@ -14,11 +13,11 @@ import static com.dosse.airpods.notification.NotificationBuilder.TAG;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* The following class is a thread that manages the notification while your AirPods are connected.
|
* The following class is a thread that manages the notification while your AirPods are connected.
|
||||||
*
|
* <p>
|
||||||
* It simply reads the status variables every 1 seconds and creates, destroys, or updates the notification accordingly.
|
* It simply reads the status variables every 1 seconds and creates, destroys, or updates the notification accordingly.
|
||||||
* The notification is shown when BT is on and AirPods are connected. The status is updated every 1 second.
|
* The notification is shown when BT is on and AirPods are connected. The status is updated every 1 second.
|
||||||
* Battery% is hidden if we didn't receive a beacon for 30 seconds (screen off for a while)
|
* Battery% is hidden if we didn't receive a beacon for 30 seconds (screen off for a while)
|
||||||
*
|
* <p>
|
||||||
* This thread is the reason why we need permission to disable doze. In theory we could integrate this into the BLE scanner,
|
* This thread is the reason why we need permission to disable doze. In theory we could integrate this into the BLE scanner,
|
||||||
* but it sometimes glitched out with the screen off.
|
* but it sometimes glitched out with the screen off.
|
||||||
*/
|
*/
|
||||||
@@ -27,30 +26,29 @@ public abstract class NotificationThread extends Thread {
|
|||||||
private static final long SLEEP_TIMEOUT = 1000;
|
private static final long SLEEP_TIMEOUT = 1000;
|
||||||
|
|
||||||
private final String compat;
|
private final String compat;
|
||||||
private final NotificationBuilder builder;
|
private final NotificationBuilder mBuilder;
|
||||||
private final NotificationManager mNotifyManager;
|
private final NotificationManager mNotificationManager;
|
||||||
|
|
||||||
public abstract boolean isConnected ();
|
public abstract boolean isConnected();
|
||||||
|
|
||||||
public abstract PodsStatus getStatus ();
|
public abstract PodsStatus getStatus();
|
||||||
|
|
||||||
public NotificationThread (Context context) {
|
public NotificationThread(Context context) {
|
||||||
compat = context.getPackageManager().getInstallerPackageName(context.getPackageName());
|
compat = context.getPackageManager().getInstallerPackageName(context.getPackageName());
|
||||||
mNotifyManager = (NotificationManager)context.getSystemService(Context.NOTIFICATION_SERVICE);
|
mNotificationManager = (NotificationManager)context.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||||
// On Oreo (API27) and newer, create a notification channel.
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
||||||
NotificationChannel channel = new NotificationChannel(TAG, TAG, NotificationManager.IMPORTANCE_LOW);
|
NotificationChannel channel = new NotificationChannel(TAG, TAG, NotificationManager.IMPORTANCE_LOW);
|
||||||
channel.setSound(null, null);
|
channel.setSound(null, null);
|
||||||
channel.enableVibration(false);
|
channel.enableVibration(false);
|
||||||
channel.enableLights(false);
|
channel.enableLights(false);
|
||||||
channel.setShowBadge(false);
|
channel.setShowBadge(false);
|
||||||
channel.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC);
|
channel.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC);
|
||||||
mNotifyManager.createNotificationChannel(channel);
|
|
||||||
}
|
mNotificationManager.createNotificationChannel(channel);
|
||||||
builder = new NotificationBuilder(context);
|
mBuilder = new NotificationBuilder(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void run () {
|
public void run() {
|
||||||
boolean notificationShowing = false;
|
boolean notificationShowing = false;
|
||||||
|
|
||||||
while (!Thread.interrupted()) {
|
while (!Thread.interrupted()) {
|
||||||
@@ -62,14 +60,15 @@ public abstract class NotificationThread extends Thread {
|
|||||||
notificationShowing = true;
|
notificationShowing = true;
|
||||||
}
|
}
|
||||||
Logger.debug(status.getAirpods().parseStatusForLogger());
|
Logger.debug(status.getAirpods().parseStatusForLogger());
|
||||||
mNotifyManager.notify(NOTIFICATION_ID, builder.build(status));
|
mNotificationManager.notify(NOTIFICATION_ID, mBuilder.build(status));
|
||||||
} else {
|
} else {
|
||||||
if (notificationShowing) {
|
if (notificationShowing) {
|
||||||
Logger.debug("Removing notification");
|
Logger.debug("Removing notification");
|
||||||
notificationShowing = false;
|
notificationShowing = false;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
mNotifyManager.cancel(NOTIFICATION_ID);
|
|
||||||
|
mNotificationManager.cancel(NOTIFICATION_ID);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((compat == null ? 0 : (compat.hashCode()) ^ 0x43700437) == 0x82e89606) return;
|
if ((compat == null ? 0 : (compat.hashCode()) ^ 0x43700437) == 0x82e89606) return;
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
package com.dosse.airpods.pods.data;
|
package com.dosse.airpods.pods;
|
||||||
|
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
|
||||||
import com.dosse.airpods.R;
|
import com.dosse.airpods.R;
|
||||||
|
|
||||||
public class Pod {
|
public class Pod {
|
||||||
|
|
||||||
public static final int DISCONNECTED_STATUS = 15;
|
public static final int DISCONNECTED_STATUS = 15;
|
||||||
public static final int MAX_CONNECTED_STATUS = 10;
|
public static final int MAX_CONNECTED_STATUS = 10;
|
||||||
public static final int LOW_BATTERY_STATUS = 1;
|
public static final int LOW_BATTERY_STATUS = 1;
|
||||||
@@ -14,50 +13,49 @@ public class Pod {
|
|||||||
private final boolean charging;
|
private final boolean charging;
|
||||||
private final boolean inEar;
|
private final boolean inEar;
|
||||||
|
|
||||||
public Pod (int status, boolean charging, boolean inEar) {
|
public Pod(int status, boolean charging, boolean inEar) {
|
||||||
this.status = status;
|
this.status = status;
|
||||||
this.charging = charging;
|
this.charging = charging;
|
||||||
this.inEar = inEar;
|
this.inEar = inEar;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getStatus () {
|
public int getStatus() {
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String parseStatus () {
|
public String parseStatus() {
|
||||||
return (status == MAX_CONNECTED_STATUS) ? "100%" : ((status < MAX_CONNECTED_STATUS) ? ((status * 10 + 5) + "%") : "");
|
return (status == MAX_CONNECTED_STATUS) ? "100%" : ((status < MAX_CONNECTED_STATUS) ? ((status * 10 + 5) + "%") : "");
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isCharging () {
|
public boolean isCharging() {
|
||||||
return charging;
|
return charging;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isInEar () {
|
public boolean isInEar() {
|
||||||
return inEar;
|
return inEar;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isConnected () {
|
public boolean isConnected() {
|
||||||
return status <= MAX_CONNECTED_STATUS;
|
return status <= MAX_CONNECTED_STATUS;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isDisconnected () {
|
public boolean isDisconnected() {
|
||||||
return status == DISCONNECTED_STATUS;
|
return status == DISCONNECTED_STATUS;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isLowBattery () {
|
public boolean isLowBattery() {
|
||||||
return status <= LOW_BATTERY_STATUS;
|
return status <= LOW_BATTERY_STATUS;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int inEarVisibility () {
|
public int inEarVisibility() {
|
||||||
return inEar ? View.VISIBLE : View.INVISIBLE;
|
return inEar ? View.VISIBLE : View.INVISIBLE;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int batImgVisibility () {
|
public int batImgVisibility() {
|
||||||
return (charging && isConnected() || isLowBattery()) ? View.VISIBLE : View.GONE;
|
return (charging && isConnected() || isLowBattery()) ? View.VISIBLE : View.GONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int batImgSrcId () {
|
public int batImgSrcId() {
|
||||||
return charging ? R.drawable.ic_battery_charging_full_green_24dp : R.drawable.ic_battery_alert_red_24dp;
|
return charging ? R.drawable.ic_battery_charging_full_green_24dp : R.drawable.ic_battery_alert_red_24dp;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.dosse.airpods.pods;
|
package com.dosse.airpods.pods;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
import android.app.Notification;
|
import android.app.Notification;
|
||||||
import android.app.NotificationChannel;
|
import android.app.NotificationChannel;
|
||||||
import android.app.NotificationManager;
|
import android.app.NotificationManager;
|
||||||
@@ -14,11 +15,9 @@ import android.bluetooth.le.ScanSettings;
|
|||||||
import android.content.BroadcastReceiver;
|
import android.content.BroadcastReceiver;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.os.Build;
|
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
import android.os.ParcelUuid;
|
import android.os.ParcelUuid;
|
||||||
import android.provider.Settings;
|
import android.provider.Settings;
|
||||||
import androidx.annotation.RequiresApi;
|
|
||||||
|
|
||||||
import com.dosse.airpods.R;
|
import com.dosse.airpods.R;
|
||||||
import com.dosse.airpods.notification.NotificationThread;
|
import com.dosse.airpods.notification.NotificationThread;
|
||||||
@@ -39,16 +38,15 @@ import static com.dosse.airpods.utils.SharedPreferencesUtils.isSavingBattery;
|
|||||||
* - Display the notification with the status
|
* - Display the notification with the status
|
||||||
*/
|
*/
|
||||||
public class PodsService extends Service {
|
public class PodsService extends Service {
|
||||||
|
private BluetoothLeScanner mBluetoothScanner;
|
||||||
|
private PodsStatus mStatus = PodsStatus.DISCONNECTED;
|
||||||
|
|
||||||
private BluetoothLeScanner btScanner;
|
private static NotificationThread mNotificationThread = null;
|
||||||
private PodsStatus status = PodsStatus.DISCONNECTED;
|
private static boolean mMaybeConnected = false;
|
||||||
|
|
||||||
private static NotificationThread n = null;
|
private BroadcastReceiver mBluetoothReceiver = null;
|
||||||
private static boolean maybeConnected = false;
|
private BroadcastReceiver mScreenReceiver = null;
|
||||||
|
private PodsStatusScanCallback mScanCallback = null;
|
||||||
private BroadcastReceiver btReceiver = null;
|
|
||||||
private BroadcastReceiver screenReceiver = null;
|
|
||||||
private PodsStatusScanCallback scanCallback = null;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The following method (startAirPodsScanner) creates a bluetooth LE scanner.
|
* The following method (startAirPodsScanner) creates a bluetooth LE scanner.
|
||||||
@@ -56,10 +54,11 @@ public class PodsService extends Service {
|
|||||||
* - Check that the beacon comes from something that looks like a pair of AirPods
|
* - Check that the beacon comes from something that looks like a pair of AirPods
|
||||||
* - Make sure that it is YOUR pair of AirPods
|
* - Make sure that it is YOUR pair of AirPods
|
||||||
* - Decode the beacon to get the status
|
* - Decode the beacon to get the status
|
||||||
*
|
* <p>
|
||||||
* After decoding a beacon, the status is written to PodsStatus so that the NotificationThread can use the information
|
* After decoding a beacon, the status is written to PodsStatus so that the NotificationThread can use the information
|
||||||
*/
|
*/
|
||||||
private void startAirPodsScanner () {
|
@SuppressLint("MissingPermission")
|
||||||
|
private void startAirPodsScanner() {
|
||||||
try {
|
try {
|
||||||
Logger.debug("START SCANNER");
|
Logger.debug("START SCANNER");
|
||||||
|
|
||||||
@@ -71,10 +70,9 @@ public class PodsService extends Service {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
//if (batterySaver && btScanner != null && scanCallback != null) {
|
if (mBluetoothScanner != null && mScanCallback != null) {
|
||||||
if (btScanner != null && scanCallback != null) {
|
mBluetoothScanner.stopScan(mScanCallback);
|
||||||
btScanner.stopScan(scanCallback);
|
mScanCallback = null;
|
||||||
scanCallback = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!btAdapter.isEnabled()) {
|
if (!btAdapter.isEnabled()) {
|
||||||
@@ -82,45 +80,46 @@ public class PodsService extends Service {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
btScanner = btAdapter.getBluetoothLeScanner();
|
mBluetoothScanner = btAdapter.getBluetoothLeScanner();
|
||||||
|
|
||||||
ScanSettings scanSettings = new ScanSettings.Builder()
|
ScanSettings scanSettings = new ScanSettings.Builder()
|
||||||
.setScanMode(isSavingBattery(getApplicationContext()) ? ScanSettings.SCAN_MODE_LOW_POWER : ScanSettings.SCAN_MODE_LOW_LATENCY)
|
.setScanMode(isSavingBattery(getApplicationContext()) ? ScanSettings.SCAN_MODE_LOW_POWER : ScanSettings.SCAN_MODE_LOW_LATENCY)
|
||||||
.setReportDelay(1) // DON'T USE 0
|
.setReportDelay(1) // DON'T USE 0
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
scanCallback = new PodsStatusScanCallback() {
|
mScanCallback = new PodsStatusScanCallback() {
|
||||||
@Override
|
@Override
|
||||||
public void onStatus (PodsStatus newStatus) {
|
public void onStatus(PodsStatus newStatus) {
|
||||||
status = newStatus;
|
mStatus = newStatus;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
btScanner.startScan(getScanFilters(), scanSettings, scanCallback);
|
mBluetoothScanner.startScan(getScanFilters(), scanSettings, mScanCallback);
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
Logger.error(t);
|
Logger.error(t);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void stopAirPodsScanner () {
|
@SuppressLint("MissingPermission")
|
||||||
|
private void stopAirPodsScanner() {
|
||||||
try {
|
try {
|
||||||
if (btScanner != null && scanCallback != null) {
|
if (mBluetoothScanner != null && mScanCallback != null) {
|
||||||
Logger.debug("STOP SCANNER");
|
Logger.debug("STOP SCANNER");
|
||||||
|
|
||||||
btScanner.stopScan(scanCallback);
|
mBluetoothScanner.stopScan(mScanCallback);
|
||||||
scanCallback = null;
|
mScanCallback = null;
|
||||||
}
|
}
|
||||||
status = PodsStatus.DISCONNECTED;
|
mStatus = PodsStatus.DISCONNECTED;
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
Logger.error(t);
|
Logger.error(t);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public PodsService () {
|
public PodsService() {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public IBinder onBind (Intent intent) {
|
public IBinder onBind(Intent intent) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -128,54 +127,53 @@ public class PodsService extends Service {
|
|||||||
* When the service is created, we register to get as many bluetooth and airpods related events as possible.
|
* When the service is created, we register to get as many bluetooth and airpods related events as possible.
|
||||||
* ACL_CONNECTED and ACL_DISCONNECTED should have been enough, but you never know with android these days.
|
* ACL_CONNECTED and ACL_DISCONNECTED should have been enough, but you never know with android these days.
|
||||||
*/
|
*/
|
||||||
|
@SuppressLint("UnspecifiedRegisterReceiverFlag")
|
||||||
@Override
|
@Override
|
||||||
public void onCreate () {
|
public void onCreate() {
|
||||||
super.onCreate();
|
super.onCreate();
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
|
|
||||||
startForeground(101, createBackgroundNotification());
|
startForeground(101, createBackgroundNotification());
|
||||||
|
|
||||||
unregisterBtReceiver();
|
unregisterBtReceiver();
|
||||||
|
|
||||||
btReceiver = new BluetoothReceiver() {
|
mBluetoothReceiver = new BluetoothReceiver() {
|
||||||
@Override
|
@Override
|
||||||
public void onStart () {
|
public void onStart() {
|
||||||
// Bluetooth turned on, start/restart scanner.
|
// Bluetooth turned on, start/restart scanner.
|
||||||
Logger.debug("BT ON");
|
Logger.debug("BT ON");
|
||||||
startAirPodsScanner();
|
startAirPodsScanner();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onStop () {
|
public void onStop() {
|
||||||
// Bluetooth turned off, stop scanner and remove notification.
|
// Bluetooth turned off, stop scanner and remove notification.
|
||||||
Logger.debug("BT OFF");
|
Logger.debug("BT OFF");
|
||||||
maybeConnected = false;
|
mMaybeConnected = false;
|
||||||
stopAirPodsScanner();
|
stopAirPodsScanner();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onConnect (BluetoothDevice bluetoothDevice) {
|
public void onConnect(BluetoothDevice bluetoothDevice) {
|
||||||
// Airpods filter
|
// Airpods filter
|
||||||
if (checkUUID(bluetoothDevice)) {
|
if (checkUUID(bluetoothDevice)) {
|
||||||
// Airpods connected, show notification.
|
// Airpods connected, show notification.
|
||||||
Logger.debug("ACL CONNECTED");
|
Logger.debug("ACL CONNECTED");
|
||||||
maybeConnected = true;
|
mMaybeConnected = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDisconnect (BluetoothDevice bluetoothDevice) {
|
public void onDisconnect(BluetoothDevice bluetoothDevice) {
|
||||||
// Airpods filter
|
// Airpods filter
|
||||||
if (checkUUID(bluetoothDevice)) {
|
if (checkUUID(bluetoothDevice)) {
|
||||||
// Airpods disconnected, remove notification but leave the scanner going.
|
// Airpods disconnected, remove notification but leave the scanner going.
|
||||||
Logger.debug("ACL DISCONNECTED");
|
Logger.debug("ACL DISCONNECTED");
|
||||||
maybeConnected = false;
|
mMaybeConnected = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
registerReceiver(btReceiver, BluetoothReceiver.buildFilter());
|
registerReceiver(mBluetoothReceiver, BluetoothReceiver.buildFilter());
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
Logger.error(t);
|
Logger.error(t);
|
||||||
}
|
}
|
||||||
@@ -185,12 +183,12 @@ public class PodsService extends Service {
|
|||||||
BluetoothAdapter ba = ((BluetoothManager)Objects.requireNonNull(getSystemService(Context.BLUETOOTH_SERVICE))).getAdapter();
|
BluetoothAdapter ba = ((BluetoothManager)Objects.requireNonNull(getSystemService(Context.BLUETOOTH_SERVICE))).getAdapter();
|
||||||
ba.getProfileProxy(getApplicationContext(), new BluetoothListener() {
|
ba.getProfileProxy(getApplicationContext(), new BluetoothListener() {
|
||||||
@Override
|
@Override
|
||||||
public boolean onConnect (BluetoothDevice device) {
|
public boolean onConnect(BluetoothDevice device) {
|
||||||
Logger.debug("BT PROXY SERVICE CONNECTED ");
|
Logger.debug("BT PROXY SERVICE CONNECTED ");
|
||||||
|
|
||||||
if (checkUUID(device)) {
|
if (checkUUID(device)) {
|
||||||
Logger.debug("BT PROXY: AIRPODS ALREADY CONNECTED");
|
Logger.debug("BT PROXY: AIRPODS ALREADY CONNECTED");
|
||||||
maybeConnected = true;
|
mMaybeConnected = true;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -198,88 +196,95 @@ public class PodsService extends Service {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDisconnect () {
|
public void onDisconnect() {
|
||||||
Logger.debug("BT PROXY SERVICE DISCONNECTED ");
|
Logger.debug("BT PROXY SERVICE DISCONNECTED ");
|
||||||
maybeConnected = false;
|
mMaybeConnected = false;
|
||||||
}
|
}
|
||||||
}, BluetoothProfile.HEADSET);
|
}, BluetoothProfile.HEADSET);
|
||||||
|
|
||||||
if (ba.isEnabled())
|
// If BT is already on when the app is started, start the scanner without waiting for an event to happen
|
||||||
startAirPodsScanner(); // If BT is already on when the app is started, start the scanner without waiting for an event to happen
|
if (ba.isEnabled()) {
|
||||||
|
startAirPodsScanner();
|
||||||
|
}
|
||||||
|
|
||||||
// Screen on/off listener to suspend scanning when the screen is off, to save battery
|
// Screen on/off listener to suspend scanning when the screen is off, to save battery
|
||||||
unregisterScreenReceiver();
|
unregisterScreenReceiver();
|
||||||
|
|
||||||
if (isSavingBattery(getApplicationContext())) {
|
if (isSavingBattery(getApplicationContext())) {
|
||||||
screenReceiver = new ScreenReceiver() {
|
mScreenReceiver = new ScreenReceiver() {
|
||||||
@Override
|
@Override
|
||||||
public void onStart () {
|
public void onStart() {
|
||||||
Logger.debug("SCREEN ON");
|
Logger.debug("SCREEN ON");
|
||||||
startAirPodsScanner();
|
startAirPodsScanner();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onStop () {
|
public void onStop() {
|
||||||
Logger.debug("SCREEN OFF");
|
Logger.debug("SCREEN OFF");
|
||||||
stopAirPodsScanner();
|
stopAirPodsScanner();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
registerReceiver(screenReceiver, ScreenReceiver.buildFilter());
|
registerReceiver(mScreenReceiver, ScreenReceiver.buildFilter());
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
Logger.error(t);
|
Logger.error(t);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean checkUUID (BluetoothDevice bluetoothDevice) {
|
@SuppressLint("MissingPermission")
|
||||||
|
private static boolean checkUUID(BluetoothDevice bluetoothDevice) {
|
||||||
ParcelUuid[] AIRPODS_UUIDS = {
|
ParcelUuid[] AIRPODS_UUIDS = {
|
||||||
ParcelUuid.fromString("74ec2172-0bad-4d01-8f77-997b2be0722a"),
|
ParcelUuid.fromString("74ec2172-0bad-4d01-8f77-997b2be0722a"),
|
||||||
ParcelUuid.fromString("2a72e02b-7b99-778f-014d-ad0b7221ec74")
|
ParcelUuid.fromString("2a72e02b-7b99-778f-014d-ad0b7221ec74")
|
||||||
};
|
};
|
||||||
ParcelUuid[] uuids = bluetoothDevice.getUuids();
|
ParcelUuid[] uuids = bluetoothDevice.getUuids();
|
||||||
|
|
||||||
if (uuids == null)
|
if (uuids == null) {
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
for (ParcelUuid u : uuids)
|
for (ParcelUuid u : uuids) {
|
||||||
for (ParcelUuid v : AIRPODS_UUIDS)
|
for (ParcelUuid v : AIRPODS_UUIDS) {
|
||||||
if (u.equals(v)) return true;
|
if (u.equals(v)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDestroy () {
|
public void onDestroy() {
|
||||||
super.onDestroy();
|
super.onDestroy();
|
||||||
unregisterBtReceiver();
|
unregisterBtReceiver();
|
||||||
unregisterScreenReceiver();
|
unregisterScreenReceiver();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int onStartCommand (Intent intent, int flags, int startId) {
|
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||||
if (n == null || !n.isAlive()) {
|
if (mNotificationThread == null || !mNotificationThread.isAlive()) {
|
||||||
n = new NotificationThread(this) {
|
mNotificationThread = new NotificationThread(this) {
|
||||||
@Override
|
@Override
|
||||||
public boolean isConnected () {
|
public boolean isConnected() {
|
||||||
return maybeConnected;
|
return mMaybeConnected;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PodsStatus getStatus () {
|
public PodsStatus getStatus() {
|
||||||
return status;
|
return mStatus;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
n.start();
|
mNotificationThread.start();
|
||||||
}
|
}
|
||||||
return START_STICKY;
|
return START_STICKY;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Foreground service for background notification (confusing I know).
|
// Foreground service for background notification (confusing I know).
|
||||||
// Only enabled for API30+
|
// Only enabled for API30+
|
||||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
private Notification createBackgroundNotification() {
|
||||||
private Notification createBackgroundNotification () {
|
|
||||||
final String notChannelID = "FOREGROUND_ID";
|
final String notChannelID = "FOREGROUND_ID";
|
||||||
|
|
||||||
NotificationManager notManager = (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE);
|
NotificationManager notManager = (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE);
|
||||||
@@ -304,26 +309,25 @@ public class PodsService extends Service {
|
|||||||
return builder.build();
|
return builder.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void unregisterBtReceiver () {
|
private void unregisterBtReceiver() {
|
||||||
try {
|
try {
|
||||||
if (btReceiver != null) {
|
if (mBluetoothReceiver != null) {
|
||||||
unregisterReceiver(btReceiver);
|
unregisterReceiver(mBluetoothReceiver);
|
||||||
btReceiver = null;
|
mBluetoothReceiver = null;
|
||||||
}
|
}
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
Logger.error(t);
|
Logger.error(t);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void unregisterScreenReceiver () {
|
private void unregisterScreenReceiver() {
|
||||||
try {
|
try {
|
||||||
if (screenReceiver != null) {
|
if (mScreenReceiver != null) {
|
||||||
unregisterReceiver(screenReceiver);
|
unregisterReceiver(mScreenReceiver);
|
||||||
screenReceiver = null;
|
mScreenReceiver = null;
|
||||||
}
|
}
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
Logger.error(t);
|
Logger.error(t);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,20 @@
|
|||||||
package com.dosse.airpods.pods;
|
package com.dosse.airpods.pods;
|
||||||
|
|
||||||
import com.dosse.airpods.pods.data.AirPods1;
|
import com.dosse.airpods.pods.models.AirPods1;
|
||||||
import com.dosse.airpods.pods.data.AirPods2;
|
import com.dosse.airpods.pods.models.AirPods2;
|
||||||
import com.dosse.airpods.pods.data.AirPods3;
|
import com.dosse.airpods.pods.models.AirPods3;
|
||||||
import com.dosse.airpods.pods.data.AirPodsMax;
|
import com.dosse.airpods.pods.models.AirPodsMax;
|
||||||
import com.dosse.airpods.pods.data.AirPodsPro;
|
import com.dosse.airpods.pods.models.AirPodsPro;
|
||||||
import com.dosse.airpods.pods.data.BeatsFlex;
|
import com.dosse.airpods.pods.models.AirPodsPro2;
|
||||||
import com.dosse.airpods.pods.data.BeatsSolo3;
|
import com.dosse.airpods.pods.models.AirPodsPro3;
|
||||||
import com.dosse.airpods.pods.data.BeatsStudio3;
|
import com.dosse.airpods.pods.models.BeatsFlex;
|
||||||
import com.dosse.airpods.pods.data.BeatsX;
|
import com.dosse.airpods.pods.models.BeatsSolo3;
|
||||||
import com.dosse.airpods.pods.data.IPods;
|
import com.dosse.airpods.pods.models.BeatsStudio3;
|
||||||
import com.dosse.airpods.pods.data.Pod;
|
import com.dosse.airpods.pods.models.BeatsX;
|
||||||
import com.dosse.airpods.pods.data.Powerbeats3;
|
import com.dosse.airpods.pods.models.IPods;
|
||||||
import com.dosse.airpods.pods.data.PowerbeatsPro;
|
import com.dosse.airpods.pods.models.Powerbeats3;
|
||||||
import com.dosse.airpods.pods.data.RegularPods;
|
import com.dosse.airpods.pods.models.PowerbeatsPro;
|
||||||
|
import com.dosse.airpods.pods.models.RegularPods;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Decoding the beacon:
|
* Decoding the beacon:
|
||||||
@@ -27,21 +28,20 @@ import com.dosse.airpods.pods.data.RegularPods;
|
|||||||
* 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
|
* 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 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 model
|
* - The 7th character in the string represents the model
|
||||||
*
|
* <p>
|
||||||
* Notes:
|
* Notes:
|
||||||
* 1) - isFlipped set by bit 1 of 10th character in the string; seems to be related to in-ear detection;
|
* 1) - isFlipped set by bit 1 of 10th character in the string; seems to be related to in-ear detection;
|
||||||
*/
|
*/
|
||||||
public class PodsStatus {
|
public class PodsStatus {
|
||||||
|
|
||||||
public static final PodsStatus DISCONNECTED = new PodsStatus();
|
public static final PodsStatus DISCONNECTED = new PodsStatus();
|
||||||
|
|
||||||
private IPods pods;
|
private IPods mPods;
|
||||||
private final long timestamp = System.currentTimeMillis();
|
private final long mTimestamp = System.currentTimeMillis();
|
||||||
|
|
||||||
public PodsStatus () {
|
private PodsStatus() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public PodsStatus (String status) {
|
public PodsStatus(String status) {
|
||||||
if (status == null)
|
if (status == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -74,49 +74,53 @@ public class PodsStatus {
|
|||||||
|
|
||||||
// Detect which model
|
// Detect which model
|
||||||
if ("0220".equals(idFull)) {
|
if ("0220".equals(idFull)) {
|
||||||
pods = new AirPods1(leftPod, rightPod, casePod); // Airpods 1st gen
|
mPods = new AirPods1(leftPod, rightPod, casePod); // Airpods 1st gen
|
||||||
} else if ("0F20".equals(idFull)) {
|
} else if ("0F20".equals(idFull)) {
|
||||||
pods = new AirPods2(leftPod, rightPod, casePod); // Airpods 2nd gen
|
mPods = new AirPods2(leftPod, rightPod, casePod); // Airpods 2nd gen
|
||||||
} else if ("1320".equals(idFull)) {
|
} else if ("1320".equals(idFull)) {
|
||||||
pods = new AirPods3(leftPod, rightPod, casePod); // Airpods 3rd gen
|
mPods = new AirPods3(leftPod, rightPod, casePod); // Airpods 3rd gen
|
||||||
} else if ("0E20".equals(idFull)) {
|
} else if ("0E20".equals(idFull)) {
|
||||||
pods = new AirPodsPro(leftPod, rightPod, casePod); // Airpods Pro
|
mPods = new AirPodsPro(leftPod, rightPod, casePod); // Airpods Pro
|
||||||
|
} else if ("1420".equals(idFull) || "2420".equals(idFull)) {
|
||||||
|
mPods = new AirPodsPro2(leftPod, rightPod, casePod); // Airpods Pro 2
|
||||||
|
} else if ("2720".equals(idFull)) {
|
||||||
|
mPods = new AirPodsPro3(leftPod, rightPod, casePod); // Airpods Pro 3
|
||||||
} else if ('A' == idSingle) {
|
} else if ('A' == idSingle) {
|
||||||
pods = new AirPodsMax(singlePod); // Airpods Max
|
mPods = new AirPodsMax(singlePod); // Airpods Max
|
||||||
} else if ('B' == idSingle) {
|
} else if ('B' == idSingle) {
|
||||||
pods = new PowerbeatsPro(leftPod, rightPod, casePod); // Powerbeats Pro
|
mPods = new PowerbeatsPro(leftPod, rightPod, casePod); // Powerbeats Pro
|
||||||
} else if ("0520".equals(idFull)) {
|
} else if ("0520".equals(idFull)) {
|
||||||
pods = new BeatsX(singlePod); // Beats X
|
mPods = new BeatsX(singlePod); // Beats X
|
||||||
} else if ("1020".equals(idFull)) {
|
} else if ("1020".equals(idFull)) {
|
||||||
pods = new BeatsFlex(singlePod); // Beats Flex
|
mPods = new BeatsFlex(singlePod); // Beats Flex
|
||||||
} else if ("0620".equals(idFull)) {
|
} else if ("0620".equals(idFull)) {
|
||||||
pods = new BeatsSolo3(singlePod); // Beats Solo 3
|
mPods = new BeatsSolo3(singlePod); // Beats Solo 3
|
||||||
} else if ('9' == idSingle) {
|
} else if ('9' == idSingle) {
|
||||||
pods = new BeatsStudio3(singlePod); // Beats Studio 3
|
mPods = new BeatsStudio3(singlePod); // Beats Studio 3
|
||||||
} else if ("0320".equals(idFull)) {
|
} else if ("0320".equals(idFull)) {
|
||||||
pods = new Powerbeats3(singlePod); // Powerbeats 3
|
mPods = new Powerbeats3(singlePod); // Powerbeats 3
|
||||||
} else {
|
} else {
|
||||||
pods = new RegularPods(leftPod, rightPod, casePod); // Unknown
|
mPods = new RegularPods(leftPod, rightPod, casePod); // Unknown
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isFlipped (String str) {
|
public static boolean isFlipped(String str) {
|
||||||
return (Integer.parseInt("" + str.charAt(10), 16) & 0x02) == 0;
|
return (Integer.parseInt("" + str.charAt(10), 16) & 0x02) == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IPods getAirpods () {
|
public IPods getAirpods() {
|
||||||
return pods;
|
return mPods;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isAllDisconnected () {
|
public boolean isAllDisconnected() {
|
||||||
if (this == DISCONNECTED)
|
if (this == DISCONNECTED) {
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
return pods.isDisconnected();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getTimestamp () {
|
return mPods.isDisconnected();
|
||||||
return timestamp;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public long getTimestamp() {
|
||||||
|
return mTimestamp;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -24,18 +24,17 @@ import java.util.Objects;
|
|||||||
* - Decode...
|
* - Decode...
|
||||||
*/
|
*/
|
||||||
public abstract class PodsStatusScanCallback extends ScanCallback {
|
public abstract class PodsStatusScanCallback extends ScanCallback {
|
||||||
|
private static final long RECENT_BEACONS_MAX_T_NS = 10000000000L; //10s
|
||||||
|
|
||||||
public static final long RECENT_BEACONS_MAX_T_NS = 10000000000L; //10s
|
private static final int AIRPODS_MANUFACTURER = 76;
|
||||||
|
private static final int AIRPODS_DATA_LENGTH = 27;
|
||||||
|
private static final int MIN_RSSI = -60;
|
||||||
|
|
||||||
public static final int AIRPODS_MANUFACTURER = 76;
|
private final List<ScanResult> mRecentBeacons = new ArrayList<>();
|
||||||
public static final int AIRPODS_DATA_LENGTH = 27;
|
|
||||||
public static final int MIN_RSSI = -60;
|
|
||||||
|
|
||||||
private final List<ScanResult> recentBeacons = new ArrayList<>();
|
public abstract void onStatus(PodsStatus status);
|
||||||
|
|
||||||
public abstract void onStatus (PodsStatus status);
|
public static List<ScanFilter> getScanFilters() {
|
||||||
|
|
||||||
public static List<ScanFilter> getScanFilters () {
|
|
||||||
byte[] manufacturerData = new byte[AIRPODS_DATA_LENGTH];
|
byte[] manufacturerData = new byte[AIRPODS_DATA_LENGTH];
|
||||||
byte[] manufacturerDataMask = new byte[AIRPODS_DATA_LENGTH];
|
byte[] manufacturerDataMask = new byte[AIRPODS_DATA_LENGTH];
|
||||||
|
|
||||||
@@ -52,18 +51,20 @@ public abstract class PodsStatusScanCallback extends ScanCallback {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBatchScanResults (List<ScanResult> scanResults) {
|
public void onBatchScanResults(List<ScanResult> scanResults) {
|
||||||
for (ScanResult result : scanResults)
|
for (ScanResult result : scanResults) {
|
||||||
onScanResult(-1, result);
|
onScanResult(-1, result);
|
||||||
|
}
|
||||||
|
|
||||||
super.onBatchScanResults(scanResults);
|
super.onBatchScanResults(scanResults);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onScanResult (int callbackType, ScanResult result) {
|
public void onScanResult(int callbackType, ScanResult result) {
|
||||||
try {
|
try {
|
||||||
if (!isAirpodsResult(result))
|
if (!isAirpodsResult(result)) {
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
result.getDevice().getAddress();
|
result.getDevice().getAddress();
|
||||||
|
|
||||||
@@ -71,8 +72,9 @@ public abstract class PodsStatusScanCallback extends ScanCallback {
|
|||||||
Logger.debug(decodeResult(result));
|
Logger.debug(decodeResult(result));
|
||||||
|
|
||||||
result = getBestResult(result);
|
result = getBestResult(result);
|
||||||
if (result == null || result.getRssi() < MIN_RSSI)
|
if (result == null || result.getRssi() < MIN_RSSI) {
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
PodsStatus status = new PodsStatus(decodeResult(result));
|
PodsStatus status = new PodsStatus(decodeResult(result));
|
||||||
onStatus(status);
|
onStatus(status);
|
||||||
@@ -81,50 +83,56 @@ public abstract class PodsStatusScanCallback extends ScanCallback {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private ScanResult getBestResult (ScanResult result) {
|
private ScanResult getBestResult(ScanResult result) {
|
||||||
recentBeacons.add(result);
|
mRecentBeacons.add(result);
|
||||||
ScanResult strongestBeacon = null;
|
ScanResult strongestBeacon = null;
|
||||||
|
|
||||||
for (int i = 0; i < recentBeacons.size(); i++) {
|
for (int i = 0; i < mRecentBeacons.size(); i++) {
|
||||||
if (SystemClock.elapsedRealtimeNanos() - recentBeacons.get(i).getTimestampNanos() > RECENT_BEACONS_MAX_T_NS) {
|
if (SystemClock.elapsedRealtimeNanos() - mRecentBeacons.get(i).getTimestampNanos() > RECENT_BEACONS_MAX_T_NS) {
|
||||||
recentBeacons.remove(i--);
|
mRecentBeacons.remove(i--);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (strongestBeacon == null || strongestBeacon.getRssi() < recentBeacons.get(i).getRssi())
|
if (strongestBeacon == null || strongestBeacon.getRssi() < mRecentBeacons.get(i).getRssi()) {
|
||||||
strongestBeacon = recentBeacons.get(i);
|
strongestBeacon = mRecentBeacons.get(i);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (strongestBeacon != null && Objects.equals(strongestBeacon.getDevice().getAddress(), result.getDevice().getAddress()))
|
if (strongestBeacon != null && Objects.equals(strongestBeacon.getDevice().getAddress(), result.getDevice().getAddress())) {
|
||||||
strongestBeacon = result;
|
strongestBeacon = result;
|
||||||
|
}
|
||||||
|
|
||||||
return strongestBeacon;
|
return strongestBeacon;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean isAirpodsResult (ScanResult result) {
|
private static boolean isAirpodsResult(ScanResult result) {
|
||||||
return result != null && result.getScanRecord() != null && isDataValid(result.getScanRecord().getManufacturerSpecificData(AIRPODS_MANUFACTURER));
|
return result != null
|
||||||
|
&& result.getScanRecord() != null
|
||||||
|
&& isDataValid(result.getScanRecord().getManufacturerSpecificData(AIRPODS_MANUFACTURER));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean isDataValid (byte[] data) {
|
private static boolean isDataValid(byte[] data) {
|
||||||
return data != null && data.length == AIRPODS_DATA_LENGTH;
|
return data != null && data.length == AIRPODS_DATA_LENGTH;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String decodeResult (ScanResult result) {
|
private static String decodeResult(ScanResult result) {
|
||||||
if (result != null && result.getScanRecord() != null) {
|
if (result != null && result.getScanRecord() != null) {
|
||||||
byte[] data = result.getScanRecord().getManufacturerSpecificData(AIRPODS_MANUFACTURER);
|
byte[] data = result.getScanRecord().getManufacturerSpecificData(AIRPODS_MANUFACTURER);
|
||||||
if (isDataValid(data))
|
if (isDataValid(data)) {
|
||||||
return decodeHex(data);
|
return decodeHex(data);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String decodeHex (byte[] bArr) {
|
private static String decodeHex(byte[] byteArray) {
|
||||||
StringBuilder ret = new StringBuilder();
|
StringBuilder ret = new StringBuilder();
|
||||||
|
|
||||||
for (byte b : bArr)
|
for (byte b : byteArray) {
|
||||||
ret.append(String.format("%02X", b));
|
ret.append(String.format("%02X", b));
|
||||||
|
}
|
||||||
|
|
||||||
return ret.toString();
|
return ret.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
package com.dosse.airpods.pods.data;
|
|
||||||
|
|
||||||
public interface IPods {
|
|
||||||
|
|
||||||
String MODEL_AIRPODS_GEN1 = "airpods1";
|
|
||||||
String MODEL_AIRPODS_GEN2 = "airpods2";
|
|
||||||
String MODEL_AIRPODS_GEN3 = "airpods3";
|
|
||||||
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 ();
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package com.dosse.airpods.pods.models;
|
||||||
|
|
||||||
|
import com.dosse.airpods.pods.Pod;
|
||||||
|
|
||||||
|
public class AirPods1 extends RegularPods {
|
||||||
|
public AirPods1(Pod leftPod, Pod rightPod, Pod casePod) {
|
||||||
|
super(leftPod, rightPod, casePod);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getModel() {
|
||||||
|
return Constants.MODEL_AIRPODS_GEN1;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package com.dosse.airpods.pods.models;
|
||||||
|
|
||||||
|
import com.dosse.airpods.pods.Pod;
|
||||||
|
|
||||||
|
public class AirPods2 extends RegularPods {
|
||||||
|
public AirPods2(Pod leftPod, Pod rightPod, Pod casePod) {
|
||||||
|
super(leftPod, rightPod, casePod);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getModel() {
|
||||||
|
return Constants.MODEL_AIRPODS_GEN2;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,14 +1,14 @@
|
|||||||
package com.dosse.airpods.pods.data;
|
package com.dosse.airpods.pods.models;
|
||||||
|
|
||||||
|
import com.dosse.airpods.pods.Pod;
|
||||||
|
|
||||||
public class AirPods3 extends RegularPods {
|
public class AirPods3 extends RegularPods {
|
||||||
|
|
||||||
public AirPods3(Pod leftPod, Pod rightPod, Pod casePod) {
|
public AirPods3(Pod leftPod, Pod rightPod, Pod casePod) {
|
||||||
super(leftPod, rightPod, casePod);
|
super(leftPod, rightPod, casePod);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getModel () {
|
public String getModel() {
|
||||||
return MODEL_AIRPODS_GEN3;
|
return Constants.MODEL_AIRPODS_GEN3;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,21 +1,20 @@
|
|||||||
package com.dosse.airpods.pods.data;
|
package com.dosse.airpods.pods.models;
|
||||||
|
|
||||||
import com.dosse.airpods.R;
|
import com.dosse.airpods.R;
|
||||||
|
import com.dosse.airpods.pods.Pod;
|
||||||
|
|
||||||
public class AirPodsMax extends SinglePods {
|
public class AirPodsMax extends SinglePods {
|
||||||
|
public AirPodsMax(Pod singlePod) {
|
||||||
public AirPodsMax (Pod singlePod) {
|
|
||||||
super(singlePod);
|
super(singlePod);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getDrawable () {
|
public int getDrawable() {
|
||||||
return getPod().isConnected() ? R.drawable.podmax : R.drawable.podmax_disconnected;
|
return getPod().isConnected() ? R.drawable.podmax : R.drawable.podmax_disconnected;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getModel () {
|
public String getModel() {
|
||||||
return MODEL_AIRPODS_MAX;
|
return Constants.MODEL_AIRPODS_MAX;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,31 +1,30 @@
|
|||||||
package com.dosse.airpods.pods.data;
|
package com.dosse.airpods.pods.models;
|
||||||
|
|
||||||
import com.dosse.airpods.R;
|
import com.dosse.airpods.R;
|
||||||
|
import com.dosse.airpods.pods.Pod;
|
||||||
|
|
||||||
public class AirPodsPro extends RegularPods {
|
public class AirPodsPro extends RegularPods {
|
||||||
|
public AirPodsPro(Pod leftPod, Pod rightPod, Pod casePod) {
|
||||||
public AirPodsPro (Pod leftPod, Pod rightPod, Pod casePod) {
|
|
||||||
super(leftPod, rightPod, casePod);
|
super(leftPod, rightPod, casePod);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getLeftDrawable () {
|
public int getLeftDrawable() {
|
||||||
return getPod(LEFT).isConnected() ? R.drawable.podpro : R.drawable.podpro_disconnected;
|
return getPod(LEFT).isConnected() ? R.drawable.podpro : R.drawable.podpro_disconnected;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getRightDrawable () {
|
public int getRightDrawable() {
|
||||||
return getPod(RIGHT).isConnected() ? R.drawable.podpro : R.drawable.podpro_disconnected;
|
return getPod(RIGHT).isConnected() ? R.drawable.podpro : R.drawable.podpro_disconnected;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getCaseDrawable () {
|
public int getCaseDrawable() {
|
||||||
return getPod(CASE).isConnected() ? R.drawable.podpro_case : R.drawable.podpro_case_disconnected;
|
return getPod(CASE).isConnected() ? R.drawable.podpro_case : R.drawable.podpro_case_disconnected;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getModel () {
|
public String getModel() {
|
||||||
return MODEL_AIRPODS_PRO;
|
return Constants.MODEL_AIRPODS_PRO;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package com.dosse.airpods.pods.models;
|
||||||
|
|
||||||
|
import com.dosse.airpods.pods.Pod;
|
||||||
|
|
||||||
|
public class AirPodsPro2 extends AirPodsPro {
|
||||||
|
public AirPodsPro2(Pod leftPod, Pod rightPod, Pod casePod) {
|
||||||
|
super(leftPod, rightPod, casePod);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getModel() {
|
||||||
|
return Constants.MODEL_AIRPODS_PRO_2;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package com.dosse.airpods.pods.models;
|
||||||
|
|
||||||
|
import com.dosse.airpods.R;
|
||||||
|
import com.dosse.airpods.pods.Pod;
|
||||||
|
|
||||||
|
public class AirPodsPro3 extends AirPodsPro {
|
||||||
|
public AirPodsPro3(Pod leftPod, Pod rightPod, Pod casePod) {
|
||||||
|
super(leftPod, rightPod, casePod);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getLeftDrawable() {
|
||||||
|
return getPod(LEFT).isConnected() ? R.drawable.podpro3 : R.drawable.podpro3_disconnected;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getRightDrawable() {
|
||||||
|
return getPod(RIGHT).isConnected() ? R.drawable.podpro3 : R.drawable.podpro3_disconnected;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getModel() {
|
||||||
|
return Constants.MODEL_AIRPODS_PRO_3;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,21 +1,20 @@
|
|||||||
package com.dosse.airpods.pods.data;
|
package com.dosse.airpods.pods.models;
|
||||||
|
|
||||||
import com.dosse.airpods.R;
|
import com.dosse.airpods.R;
|
||||||
|
import com.dosse.airpods.pods.Pod;
|
||||||
|
|
||||||
public class BeatsFlex extends SinglePods {
|
public class BeatsFlex extends SinglePods {
|
||||||
|
public BeatsFlex(Pod singlePod) {
|
||||||
public BeatsFlex (Pod singlePod) {
|
|
||||||
super(singlePod);
|
super(singlePod);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getDrawable () {
|
public int getDrawable() {
|
||||||
return getPod().isConnected() ? R.drawable.beatsflex : R.drawable.beatsflex_disconnected;
|
return getPod().isConnected() ? R.drawable.beatsflex : R.drawable.beatsflex_disconnected;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getModel () {
|
public String getModel() {
|
||||||
return MODEL_BEATS_FLEX;
|
return Constants.MODEL_BEATS_FLEX;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,21 +1,20 @@
|
|||||||
package com.dosse.airpods.pods.data;
|
package com.dosse.airpods.pods.models;
|
||||||
|
|
||||||
import com.dosse.airpods.R;
|
import com.dosse.airpods.R;
|
||||||
|
import com.dosse.airpods.pods.Pod;
|
||||||
|
|
||||||
public class BeatsSolo3 extends SinglePods {
|
public class BeatsSolo3 extends SinglePods {
|
||||||
|
public BeatsSolo3(Pod singlePod) {
|
||||||
public BeatsSolo3 (Pod singlePod) {
|
|
||||||
super(singlePod);
|
super(singlePod);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getDrawable () {
|
public int getDrawable() {
|
||||||
return getPod().isConnected() ? R.drawable.beatssolo3 : R.drawable.beatssolo3_disconnected;
|
return getPod().isConnected() ? R.drawable.beatssolo3 : R.drawable.beatssolo3_disconnected;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getModel () {
|
public String getModel() {
|
||||||
return MODEL_BEATS_SOLO_3;
|
return Constants.MODEL_BEATS_SOLO_3;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,21 +1,20 @@
|
|||||||
package com.dosse.airpods.pods.data;
|
package com.dosse.airpods.pods.models;
|
||||||
|
|
||||||
import com.dosse.airpods.R;
|
import com.dosse.airpods.R;
|
||||||
|
import com.dosse.airpods.pods.Pod;
|
||||||
|
|
||||||
public class BeatsStudio3 extends SinglePods {
|
public class BeatsStudio3 extends SinglePods {
|
||||||
|
public BeatsStudio3(Pod singlePod) {
|
||||||
public BeatsStudio3 (Pod singlePod) {
|
|
||||||
super(singlePod);
|
super(singlePod);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getDrawable () {
|
public int getDrawable() {
|
||||||
return getPod().isConnected() ? R.drawable.beatsstudio3 : R.drawable.beatsstudio3_disconnected;
|
return getPod().isConnected() ? R.drawable.beatsstudio3 : R.drawable.beatsstudio3_disconnected;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getModel () {
|
public String getModel() {
|
||||||
return MODEL_BEATS_STUDIO_3;
|
return Constants.MODEL_BEATS_STUDIO_3;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,21 +1,20 @@
|
|||||||
package com.dosse.airpods.pods.data;
|
package com.dosse.airpods.pods.models;
|
||||||
|
|
||||||
import com.dosse.airpods.R;
|
import com.dosse.airpods.R;
|
||||||
|
import com.dosse.airpods.pods.Pod;
|
||||||
|
|
||||||
public class BeatsX extends SinglePods {
|
public class BeatsX extends SinglePods {
|
||||||
|
public BeatsX(Pod singlePod) {
|
||||||
public BeatsX (Pod singlePod) {
|
|
||||||
super(singlePod);
|
super(singlePod);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getDrawable () {
|
public int getDrawable() {
|
||||||
return getPod().isConnected() ? R.drawable.beatsx : R.drawable.beatsx_disconnected;
|
return getPod().isConnected() ? R.drawable.beatsx : R.drawable.beatsx_disconnected;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getModel () {
|
public String getModel() {
|
||||||
return MODEL_BEATS_X;
|
return Constants.MODEL_BEATS_X;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package com.dosse.airpods.pods.models;
|
||||||
|
|
||||||
|
public final class Constants {
|
||||||
|
static final String MODEL_AIRPODS_GEN1 = "airpods1";
|
||||||
|
static final String MODEL_AIRPODS_GEN2 = "airpods2";
|
||||||
|
static final String MODEL_AIRPODS_GEN3 = "airpods3";
|
||||||
|
static final String MODEL_AIRPODS_PRO = "airpodspro";
|
||||||
|
static final String MODEL_AIRPODS_PRO_2 = "airpodspro2";
|
||||||
|
static final String MODEL_AIRPODS_PRO_3 = "airpodspro3";
|
||||||
|
static final String MODEL_AIRPODS_MAX = "airpodsmax";
|
||||||
|
static final String MODEL_BEATS_X = "beatsx";
|
||||||
|
static final String MODEL_BEATS_FLEX = "beatsflex";
|
||||||
|
static final String MODEL_BEATS_SOLO_3 = "beatssolo3";
|
||||||
|
static final String MODEL_BEATS_STUDIO_3 = "beatsstudio3";
|
||||||
|
static final String MODEL_POWERBEATS_3 = "powerbeats3";
|
||||||
|
static final String MODEL_POWERBEATS_PRO = "powerbeatspro";
|
||||||
|
static final String MODEL_UNKNOWN = "unknown";
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package com.dosse.airpods.pods.models;
|
||||||
|
|
||||||
|
public interface IPods {
|
||||||
|
String getModel();
|
||||||
|
|
||||||
|
boolean isSingle();
|
||||||
|
|
||||||
|
boolean isDisconnected();
|
||||||
|
|
||||||
|
String parseStatusForLogger();
|
||||||
|
}
|
||||||
@@ -1,21 +1,20 @@
|
|||||||
package com.dosse.airpods.pods.data;
|
package com.dosse.airpods.pods.models;
|
||||||
|
|
||||||
import com.dosse.airpods.R;
|
import com.dosse.airpods.R;
|
||||||
|
import com.dosse.airpods.pods.Pod;
|
||||||
|
|
||||||
public class Powerbeats3 extends SinglePods {
|
public class Powerbeats3 extends SinglePods {
|
||||||
|
public Powerbeats3(Pod singlePod) {
|
||||||
public Powerbeats3 (Pod singlePod) {
|
|
||||||
super(singlePod);
|
super(singlePod);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getDrawable () {
|
public int getDrawable() {
|
||||||
return getPod().isConnected() ? R.drawable.powerbeats3 : R.drawable.powerbeats3_disconnected;
|
return getPod().isConnected() ? R.drawable.powerbeats3 : R.drawable.powerbeats3_disconnected;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getModel () {
|
public String getModel() {
|
||||||
return MODEL_POWERBEATS_3;
|
return Constants.MODEL_POWERBEATS_3;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,31 +1,30 @@
|
|||||||
package com.dosse.airpods.pods.data;
|
package com.dosse.airpods.pods.models;
|
||||||
|
|
||||||
import com.dosse.airpods.R;
|
import com.dosse.airpods.R;
|
||||||
|
import com.dosse.airpods.pods.Pod;
|
||||||
|
|
||||||
public class PowerbeatsPro extends RegularPods {
|
public class PowerbeatsPro extends RegularPods {
|
||||||
|
public PowerbeatsPro(Pod leftPod, Pod rightPod, Pod casePod) {
|
||||||
public PowerbeatsPro (Pod leftPod, Pod rightPod, Pod casePod) {
|
|
||||||
super(leftPod, rightPod, casePod);
|
super(leftPod, rightPod, casePod);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getLeftDrawable () {
|
public int getLeftDrawable() {
|
||||||
return getPod(LEFT).isConnected() ? R.drawable.powerbeatspro : R.drawable.powerbeatspro_disconnected;
|
return getPod(LEFT).isConnected() ? R.drawable.powerbeatspro : R.drawable.powerbeatspro_disconnected;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getRightDrawable () {
|
public int getRightDrawable() {
|
||||||
return getPod(RIGHT).isConnected() ? R.drawable.powerbeatspro : R.drawable.powerbeatspro_disconnected;
|
return getPod(RIGHT).isConnected() ? R.drawable.powerbeatspro : R.drawable.powerbeatspro_disconnected;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getCaseDrawable () {
|
public int getCaseDrawable() {
|
||||||
return getPod(CASE).isConnected() ? R.drawable.powerbeatspro_case : R.drawable.powerbeatspro_case_disconnected;
|
return getPod(CASE).isConnected() ? R.drawable.powerbeatspro_case : R.drawable.powerbeatspro_case_disconnected;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getModel () {
|
public String getModel() {
|
||||||
return MODEL_POWERBEATS_PRO;
|
return Constants.MODEL_POWERBEATS_PRO;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,74 +1,73 @@
|
|||||||
package com.dosse.airpods.pods.data;
|
package com.dosse.airpods.pods.models;
|
||||||
|
|
||||||
import com.dosse.airpods.R;
|
import com.dosse.airpods.R;
|
||||||
|
import com.dosse.airpods.pods.Pod;
|
||||||
|
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
public class RegularPods implements IPods {
|
public class RegularPods implements IPods {
|
||||||
|
|
||||||
public static final int LEFT = 0, RIGHT = 1, CASE = 2;
|
public static final int LEFT = 0, RIGHT = 1, CASE = 2;
|
||||||
|
|
||||||
private final Pod[] pods;
|
private final Pod[] pods;
|
||||||
|
|
||||||
public RegularPods (Pod leftPod, Pod rightPod, Pod casePod) {
|
public RegularPods(Pod leftPod, Pod rightPod, Pod casePod) {
|
||||||
this.pods = new Pod[] {leftPod, rightPod, casePod};
|
this.pods = new Pod[] {leftPod, rightPod, casePod};
|
||||||
}
|
}
|
||||||
|
|
||||||
public Pod getPod (int pos) {
|
public Pod getPod(int pos) {
|
||||||
return pods[pos];
|
return pods[pos];
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getParsedStatus (int pos) {
|
public String getParsedStatus(int pos) {
|
||||||
return pods[pos].parseStatus();
|
return pods[pos].parseStatus();
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getInEarVisibility (int pos) {
|
public int getInEarVisibility(int pos) {
|
||||||
return pods[pos].inEarVisibility();
|
return pods[pos].inEarVisibility();
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getBatImgVisibility (int pos) {
|
public int getBatImgVisibility(int pos) {
|
||||||
return pods[pos].batImgVisibility();
|
return pods[pos].batImgVisibility();
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getBatImgSrcId (int pos) {
|
public int getBatImgSrcId(int pos) {
|
||||||
return pods[pos].batImgSrcId();
|
return pods[pos].batImgSrcId();
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getLeftDrawable () {
|
public int getLeftDrawable() {
|
||||||
return getPod(LEFT).isConnected() ? R.drawable.pod : R.drawable.pod_disconnected;
|
return getPod(LEFT).isConnected() ? R.drawable.pod : R.drawable.pod_disconnected;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getRightDrawable () {
|
public int getRightDrawable() {
|
||||||
return getPod(RIGHT).isConnected() ? R.drawable.pod : R.drawable.pod_disconnected;
|
return getPod(RIGHT).isConnected() ? R.drawable.pod : R.drawable.pod_disconnected;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getCaseDrawable () {
|
public int getCaseDrawable() {
|
||||||
return getPod(CASE).isConnected() ? R.drawable.pod_case : R.drawable.pod_case_disconnected;
|
return getPod(CASE).isConnected() ? R.drawable.pod_case : R.drawable.pod_case_disconnected;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getModel () {
|
public String getModel() {
|
||||||
return MODEL_UNKNOWN;
|
return Constants.MODEL_UNKNOWN;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isSingle () {
|
public boolean isSingle() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isDisconnected () {
|
public boolean isDisconnected() {
|
||||||
return pods[LEFT].isDisconnected() &&
|
return pods[LEFT].isDisconnected() &&
|
||||||
pods[RIGHT].isDisconnected() &&
|
pods[RIGHT].isDisconnected() &&
|
||||||
pods[CASE].isDisconnected();
|
pods[CASE].isDisconnected();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String parseStatusForLogger () {
|
public String parseStatusForLogger() {
|
||||||
return String.format(Locale.getDefault(), "Left: %d%s%s Right: %d%s%s Case: %d%s Model: %s",
|
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[LEFT].getStatus(), pods[LEFT].isCharging() ? "+" : "", pods[LEFT].isInEar() ? "$" : "",
|
||||||
pods[RIGHT].getStatus(), pods[RIGHT].isCharging() ? "+" : "", pods[RIGHT].isInEar() ? "$" : "",
|
pods[RIGHT].getStatus(), pods[RIGHT].isCharging() ? "+" : "", pods[RIGHT].isInEar() ? "$" : "",
|
||||||
pods[CASE].getStatus(), pods[CASE].isCharging() ? "+" : "", getModel());
|
pods[CASE].getStatus(), pods[CASE].isCharging() ? "+" : "", getModel());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,46 +1,46 @@
|
|||||||
package com.dosse.airpods.pods.data;
|
package com.dosse.airpods.pods.models;
|
||||||
|
|
||||||
|
import com.dosse.airpods.pods.Pod;
|
||||||
|
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
public abstract class SinglePods implements IPods {
|
public abstract class SinglePods implements IPods {
|
||||||
|
public abstract int getDrawable();
|
||||||
public abstract int getDrawable ();
|
|
||||||
|
|
||||||
private final Pod pod;
|
private final Pod pod;
|
||||||
|
|
||||||
public SinglePods (Pod pod) {
|
public SinglePods(Pod pod) {
|
||||||
this.pod = pod;
|
this.pod = pod;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Pod getPod () {
|
public Pod getPod() {
|
||||||
return pod;
|
return pod;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getParsedStatus () {
|
public String getParsedStatus() {
|
||||||
return pod.parseStatus();
|
return pod.parseStatus();
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getBatImgVisibility () {
|
public int getBatImgVisibility() {
|
||||||
return pod.batImgVisibility();
|
return pod.batImgVisibility();
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getBatImgSrcId () {
|
public int getBatImgSrcId() {
|
||||||
return pod.batImgSrcId();
|
return pod.batImgSrcId();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isSingle () {
|
public boolean isSingle() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isDisconnected () {
|
public boolean isDisconnected() {
|
||||||
return pod.isDisconnected();
|
return pod.isDisconnected();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String parseStatusForLogger () {
|
public String parseStatusForLogger() {
|
||||||
return String.format(Locale.getDefault(), "Battery: %d%s Model: %s", pod.getStatus(), pod.isCharging() ? "+" : "", getModel());
|
return String.format(Locale.getDefault(), "Battery: %d%s Model: %s", pod.getStatus(), pod.isCharging() ? "+" : "", getModel());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -4,23 +4,27 @@ import android.bluetooth.BluetoothDevice;
|
|||||||
import android.bluetooth.BluetoothProfile;
|
import android.bluetooth.BluetoothProfile;
|
||||||
|
|
||||||
public abstract class BluetoothListener implements BluetoothProfile.ServiceListener {
|
public abstract class BluetoothListener implements BluetoothProfile.ServiceListener {
|
||||||
|
public abstract boolean onConnect(BluetoothDevice bluetoothDevice);
|
||||||
|
|
||||||
public abstract boolean onConnect (BluetoothDevice bluetoothDevice);
|
public abstract void onDisconnect();
|
||||||
|
|
||||||
public abstract void onDisconnect ();
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onServiceConnected (int profile, BluetoothProfile bluetoothProfile) {
|
public void onServiceConnected(int profile, BluetoothProfile bluetoothProfile) {
|
||||||
if (profile == BluetoothProfile.HEADSET)
|
if (profile != BluetoothProfile.HEADSET) {
|
||||||
for (BluetoothDevice device : bluetoothProfile.getConnectedDevices())
|
return;
|
||||||
if (onConnect(device))
|
}
|
||||||
|
|
||||||
|
for (BluetoothDevice device : bluetoothProfile.getConnectedDevices()) {
|
||||||
|
if (onConnect(device)) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
@Override
|
|
||||||
public void onServiceDisconnected (int profile) {
|
|
||||||
if (profile == BluetoothProfile.HEADSET)
|
|
||||||
onDisconnect();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onServiceDisconnected(int profile) {
|
||||||
|
if (profile == BluetoothProfile.HEADSET) {
|
||||||
|
onDisconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -8,20 +8,19 @@ import android.content.Intent;
|
|||||||
import android.content.IntentFilter;
|
import android.content.IntentFilter;
|
||||||
|
|
||||||
public abstract class BluetoothReceiver extends BroadcastReceiver {
|
public abstract class BluetoothReceiver extends BroadcastReceiver {
|
||||||
|
public abstract void onStart();
|
||||||
|
|
||||||
public abstract void onStart ();
|
public abstract void onStop();
|
||||||
|
|
||||||
public abstract void onStop ();
|
public abstract void onConnect(BluetoothDevice bluetoothDevice);
|
||||||
|
|
||||||
public abstract void onConnect (BluetoothDevice bluetoothDevice);
|
public abstract void onDisconnect(BluetoothDevice bluetoothDevice);
|
||||||
|
|
||||||
public abstract void onDisconnect (BluetoothDevice bluetoothDevice);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* When the service is created, we register to get as many bluetooth and airpods related events as possible.
|
* When the service is created, we register to get as many bluetooth and airpods related events as possible.
|
||||||
* ACL_CONNECTED and ACL_DISCONNECTED should have been enough, but you never know with android these days.
|
* ACL_CONNECTED and ACL_DISCONNECTED should have been enough, but you never know with android these days.
|
||||||
*/
|
*/
|
||||||
public static IntentFilter buildFilter () {
|
public static IntentFilter buildFilter() {
|
||||||
IntentFilter intentFilter = new IntentFilter();
|
IntentFilter intentFilter = new IntentFilter();
|
||||||
intentFilter.addAction("android.bluetooth.device.action.ACL_CONNECTED");
|
intentFilter.addAction("android.bluetooth.device.action.ACL_CONNECTED");
|
||||||
intentFilter.addAction("android.bluetooth.device.action.ACL_DISCONNECTED");
|
intentFilter.addAction("android.bluetooth.device.action.ACL_DISCONNECTED");
|
||||||
@@ -38,7 +37,7 @@ public abstract class BluetoothReceiver extends BroadcastReceiver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onReceive (Context context, Intent intent) {
|
public void onReceive(Context context, Intent intent) {
|
||||||
BluetoothDevice bluetoothDevice = intent.getParcelableExtra("android.bluetooth.device.extra.DEVICE");
|
BluetoothDevice bluetoothDevice = intent.getParcelableExtra("android.bluetooth.device.extra.DEVICE");
|
||||||
String action = intent.getAction();
|
String action = intent.getAction();
|
||||||
|
|
||||||
@@ -46,24 +45,28 @@ public abstract class BluetoothReceiver extends BroadcastReceiver {
|
|||||||
int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR);
|
int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR);
|
||||||
|
|
||||||
// Bluetooth turned off, stop scanner and remove notification.
|
// Bluetooth turned off, stop scanner and remove notification.
|
||||||
if (state == BluetoothAdapter.STATE_OFF || state == BluetoothAdapter.STATE_TURNING_OFF)
|
if (state == BluetoothAdapter.STATE_OFF || state == BluetoothAdapter.STATE_TURNING_OFF) {
|
||||||
onStop();
|
onStop();
|
||||||
|
}
|
||||||
|
|
||||||
// Bluetooth turned on, start/restart scanner.
|
// Bluetooth turned on, start/restart scanner.
|
||||||
if (state == BluetoothAdapter.STATE_ON)
|
if (state == BluetoothAdapter.STATE_ON) {
|
||||||
onStart();
|
onStart();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Airpods filter
|
// Airpods filter
|
||||||
if (bluetoothDevice != null && action != null && !action.isEmpty()) {
|
if (bluetoothDevice != null && action != null && !action.isEmpty()) {
|
||||||
// Airpods connected, show notification.
|
// Airpods connected, show notification.
|
||||||
if (BluetoothDevice.ACTION_ACL_CONNECTED.equals(action))
|
if (BluetoothDevice.ACTION_ACL_CONNECTED.equals(action)) {
|
||||||
onConnect(bluetoothDevice);
|
onConnect(bluetoothDevice);
|
||||||
|
}
|
||||||
|
|
||||||
// Airpods disconnected, remove notification but leave the scanner going.
|
// Airpods disconnected, remove notification but leave the scanner going.
|
||||||
if (BluetoothDevice.ACTION_ACL_DISCONNECTED.equals(action) || BluetoothDevice.ACTION_ACL_DISCONNECT_REQUESTED.equals(action))
|
if (BluetoothDevice.ACTION_ACL_DISCONNECTED.equals(action)
|
||||||
|
|| BluetoothDevice.ACTION_ACL_DISCONNECT_REQUESTED.equals(action)) {
|
||||||
onDisconnect(bluetoothDevice);
|
onDisconnect(bluetoothDevice);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -5,27 +5,31 @@ import android.content.Context;
|
|||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.IntentFilter;
|
import android.content.IntentFilter;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
public abstract class ScreenReceiver extends BroadcastReceiver {
|
public abstract class ScreenReceiver extends BroadcastReceiver {
|
||||||
|
public abstract void onStart();
|
||||||
|
|
||||||
public abstract void onStart ();
|
public abstract void onStop();
|
||||||
|
|
||||||
public abstract void onStop ();
|
public static IntentFilter buildFilter() {
|
||||||
|
|
||||||
public static IntentFilter buildFilter () {
|
|
||||||
IntentFilter screenIntentFilter = new IntentFilter();
|
IntentFilter screenIntentFilter = new IntentFilter();
|
||||||
|
|
||||||
screenIntentFilter.addAction(Intent.ACTION_SCREEN_ON);
|
screenIntentFilter.addAction(Intent.ACTION_SCREEN_ON);
|
||||||
screenIntentFilter.addAction(Intent.ACTION_SCREEN_OFF);
|
screenIntentFilter.addAction(Intent.ACTION_SCREEN_OFF);
|
||||||
|
|
||||||
return screenIntentFilter;
|
return screenIntentFilter;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onReceive (Context context, Intent intent) {
|
public void onReceive(Context context, Intent intent) {
|
||||||
switch (intent.getAction()) {
|
switch (Objects.requireNonNull(intent.getAction())) {
|
||||||
case Intent.ACTION_SCREEN_OFF: onStop();
|
case Intent.ACTION_SCREEN_OFF:
|
||||||
|
onStop();
|
||||||
break;
|
break;
|
||||||
case Intent.ACTION_SCREEN_ON: onStart();
|
case Intent.ACTION_SCREEN_ON:
|
||||||
|
onStart();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -3,7 +3,6 @@ package com.dosse.airpods.receivers;
|
|||||||
import android.content.BroadcastReceiver;
|
import android.content.BroadcastReceiver;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.os.Build;
|
|
||||||
|
|
||||||
import com.dosse.airpods.pods.PodsService;
|
import com.dosse.airpods.pods.PodsService;
|
||||||
|
|
||||||
@@ -13,9 +12,8 @@ import java.util.Objects;
|
|||||||
* A simple startup class that starts the service when the device is booted, or after an update
|
* A simple startup class that starts the service when the device is booted, or after an update
|
||||||
*/
|
*/
|
||||||
public class StartupReceiver extends BroadcastReceiver {
|
public class StartupReceiver extends BroadcastReceiver {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onReceive (Context context, Intent intent) {
|
public void onReceive(Context context, Intent intent) {
|
||||||
switch (Objects.requireNonNull(intent.getAction())) {
|
switch (Objects.requireNonNull(intent.getAction())) {
|
||||||
case Intent.ACTION_MY_PACKAGE_REPLACED:
|
case Intent.ACTION_MY_PACKAGE_REPLACED:
|
||||||
case Intent.ACTION_BOOT_COMPLETED:
|
case Intent.ACTION_BOOT_COMPLETED:
|
||||||
@@ -24,14 +22,11 @@ public class StartupReceiver extends BroadcastReceiver {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void startPodsService (Context context) {
|
public static void startPodsService(Context context) {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
|
|
||||||
context.startForegroundService(new Intent(context, PodsService.class));
|
context.startForegroundService(new Intent(context, PodsService.class));
|
||||||
else
|
|
||||||
context.startService(new Intent(context, PodsService.class));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void restartPodsService (Context context) {
|
public static void restartPodsService(Context context) {
|
||||||
context.stopService(new Intent(context, PodsService.class));
|
context.stopService(new Intent(context, PodsService.class));
|
||||||
try {
|
try {
|
||||||
Thread.sleep(500);
|
Thread.sleep(500);
|
||||||
@@ -39,5 +34,4 @@ public class StartupReceiver extends BroadcastReceiver {
|
|||||||
}
|
}
|
||||||
startPodsService(context);
|
startPodsService(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,9 +10,8 @@ import androidx.appcompat.app.AppCompatActivity;
|
|||||||
import com.dosse.airpods.R;
|
import com.dosse.airpods.R;
|
||||||
|
|
||||||
public class AboutActivity extends AppCompatActivity {
|
public class AboutActivity extends AppCompatActivity {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate (Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
setContentView(R.layout.activity_about);
|
setContentView(R.layout.activity_about);
|
||||||
|
|
||||||
@@ -42,5 +41,4 @@ public class AboutActivity extends AppCompatActivity {
|
|||||||
|
|
||||||
// ------------ END OF LEGAL DANGER ZONE ------------ //
|
// ------------ END OF LEGAL DANGER ZONE ------------ //
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,32 +23,39 @@ import java.util.Timer;
|
|||||||
import java.util.TimerTask;
|
import java.util.TimerTask;
|
||||||
|
|
||||||
public class IntroActivity extends AppCompatActivity {
|
public class IntroActivity extends AppCompatActivity {
|
||||||
|
private static final int STEP_PERMISSION_BLUETOOTH = 1;
|
||||||
|
private static final int STEP_PERMISSION_BATTERY_OPTIMIZATION = 2;
|
||||||
|
private static final int STEP_PERMISSION_LOCATION = 3;
|
||||||
|
private static final int STEP_PERMISSION_BACKGROUND_LOCATION = 4;
|
||||||
|
private static final int STEP_PERMISSION_NOTIFICATION = 5;
|
||||||
|
|
||||||
private Timer timer;
|
private static final int BLUETOOTH_REQUEST_CODE = 101;
|
||||||
private TextView msg;
|
private static final int LOCATION_REQUEST_CODE = 102;
|
||||||
private Button btn;
|
private static final int BACKGROUND_LOCATION_REQUEST_CODE = 103;
|
||||||
|
private static final int NOTIFICATION_REQUEST_CODE = 104;
|
||||||
|
|
||||||
private static final int STEP_PERMISSION_BLUETOOTH = 1, STEP_PERMISSION_BATTERY_OPTIMIZATION = 2, STEP_PERMISSION_LOCATION = 3, STEP_PERMISSION_BACKGROUND_LOCATION = 4;
|
private Timer mTimer;
|
||||||
private static final int BLUETOOTH_REQUEST_CODE = 101, LOCATION_REQUEST_CODE = 102, BACKGROUND_LOCATION_REQUEST_CODE = 103;
|
private TextView mPermissionsMessage;
|
||||||
|
private Button mPermissionsButton;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate (Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
setContentView(R.layout.activity_intro);
|
setContentView(R.layout.activity_intro);
|
||||||
|
|
||||||
msg = findViewById(R.id.permsMsg);
|
mPermissionsMessage = findViewById(R.id.permsMsg);
|
||||||
btn = findViewById(R.id.permsBtn);
|
mPermissionsButton = findViewById(R.id.permsBtn);
|
||||||
|
|
||||||
// Wait for permissions to be granted.
|
// Wait for permissions to be granted.
|
||||||
// When they are granted, go to MainActivity.
|
// When they are granted, go to MainActivity.
|
||||||
timer = new Timer();
|
mTimer = new Timer();
|
||||||
timer.scheduleAtFixedRate(new TimerTask() {
|
mTimer.scheduleAtFixedRate(new TimerTask() {
|
||||||
@Override
|
@Override
|
||||||
public void run () {
|
public void run() {
|
||||||
initScreen();
|
initScreen();
|
||||||
|
|
||||||
if (PermissionUtils.checkAllPermissions(IntroActivity.this)) {
|
if (PermissionUtils.checkAllPermissions(IntroActivity.this)) {
|
||||||
timer.cancel();
|
mTimer.cancel();
|
||||||
startActivity(new Intent(IntroActivity.this, MainActivity.class));
|
startActivity(new Intent(IntroActivity.this, MainActivity.class));
|
||||||
finish();
|
finish();
|
||||||
}
|
}
|
||||||
@@ -59,42 +66,49 @@ public class IntroActivity extends AppCompatActivity {
|
|||||||
|
|
||||||
// Activity destroyed (or screen rotated). destroy the timer too
|
// Activity destroyed (or screen rotated). destroy the timer too
|
||||||
@Override
|
@Override
|
||||||
protected void onDestroy () {
|
protected void onDestroy() {
|
||||||
super.onDestroy();
|
super.onDestroy();
|
||||||
if (timer != null)
|
if (mTimer != null)
|
||||||
timer.cancel();
|
mTimer.cancel();
|
||||||
}
|
}
|
||||||
|
|
||||||
private int getPermissionState () {
|
private int getPermissionState() {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) if (!PermissionUtils.getBluetoothPermissions(this))
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && !PermissionUtils.getBluetoothPermissions(this)) {
|
||||||
return STEP_PERMISSION_BLUETOOTH;
|
return STEP_PERMISSION_BLUETOOTH;
|
||||||
if (!PermissionUtils.getBatteryOptimizationsPermission(this))
|
}
|
||||||
|
if (!PermissionUtils.getBatteryOptimizationsPermission(this)) {
|
||||||
return STEP_PERMISSION_BATTERY_OPTIMIZATION;
|
return STEP_PERMISSION_BATTERY_OPTIMIZATION;
|
||||||
if (!PermissionUtils.getFineLocationPermission(this))
|
}
|
||||||
|
if (!PermissionUtils.getFineLocationPermission(this)) {
|
||||||
return STEP_PERMISSION_LOCATION;
|
return STEP_PERMISSION_LOCATION;
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) if (!PermissionUtils.getBackgroundLocationPermission(this))
|
}
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && !PermissionUtils.getBackgroundLocationPermission(this)) {
|
||||||
return STEP_PERMISSION_BACKGROUND_LOCATION;
|
return STEP_PERMISSION_BACKGROUND_LOCATION;
|
||||||
|
}
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU && !PermissionUtils.getNotificationPermissions(this)) {
|
||||||
|
return STEP_PERMISSION_NOTIFICATION;
|
||||||
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("BatteryLife")
|
@SuppressLint("BatteryLife")
|
||||||
private void initScreen () {
|
private void initScreen() {
|
||||||
int currentStep = getPermissionState() - ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) ? 0 : 1);
|
int currentStep = getPermissionState() - ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) ? 0 : 1);
|
||||||
int numOfSteps = (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) ? ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) ? 4 : 3) : 2;
|
|
||||||
|
|
||||||
runOnUiThread(() -> {
|
runOnUiThread(() -> {
|
||||||
switch (getPermissionState()) {
|
switch (getPermissionState()) {
|
||||||
case STEP_PERMISSION_BLUETOOTH:
|
case STEP_PERMISSION_BLUETOOTH:
|
||||||
msg.setText(String.format(Locale.getDefault(), "%s %d/%d: %s", getString(R.string.intro_step), currentStep, numOfSteps, getString(R.string.intro_bt_perm)));
|
mPermissionsMessage.setText(String.format(Locale.getDefault(), "%s %d/%d: %s", getString(R.string.intro_step), currentStep, getNumberOfSteps(), getString(R.string.intro_bt_perm)));
|
||||||
btn.setOnClickListener(view -> {
|
mPermissionsButton.setOnClickListener(view -> {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S)
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||||
requestPermissions(new String[] {Manifest.permission.BLUETOOTH_SCAN, Manifest.permission.BLUETOOTH_CONNECT}, BLUETOOTH_REQUEST_CODE);
|
requestPermissions(new String[] {Manifest.permission.BLUETOOTH_SCAN, Manifest.permission.BLUETOOTH_CONNECT}, BLUETOOTH_REQUEST_CODE);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case STEP_PERMISSION_BATTERY_OPTIMIZATION:
|
case STEP_PERMISSION_BATTERY_OPTIMIZATION:
|
||||||
msg.setText(String.format(Locale.getDefault(), "%s %d/%d: %s", getString(R.string.intro_step), currentStep, numOfSteps, getString(R.string.intro_bat_perm)));
|
mPermissionsMessage.setText(String.format(Locale.getDefault(), "%s %d/%d: %s", getString(R.string.intro_step), currentStep, getNumberOfSteps(), getString(R.string.intro_bat_perm)));
|
||||||
btn.setOnClickListener(view -> {
|
mPermissionsButton.setOnClickListener(view -> {
|
||||||
Intent intent = new Intent();
|
Intent intent = new Intent();
|
||||||
getSystemService(Context.POWER_SERVICE);
|
getSystemService(Context.POWER_SERVICE);
|
||||||
intent.setAction(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
|
intent.setAction(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
|
||||||
@@ -103,43 +117,78 @@ public class IntroActivity extends AppCompatActivity {
|
|||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case STEP_PERMISSION_LOCATION:
|
case STEP_PERMISSION_LOCATION:
|
||||||
msg.setText(String.format(Locale.getDefault(), "%s %d/%d: %s", getString(R.string.intro_step), currentStep, numOfSteps, getString(R.string.intro_loc1_perm)));
|
mPermissionsMessage.setText(String.format(Locale.getDefault(), "%s %d/%d: %s", getString(R.string.intro_step), currentStep, getNumberOfSteps(), getString(R.string.intro_loc1_perm)));
|
||||||
btn.setOnClickListener(view -> requestPermissions(new String[] {Manifest.permission.ACCESS_FINE_LOCATION}, LOCATION_REQUEST_CODE)); // Location (for BLE)
|
mPermissionsButton.setOnClickListener(view -> requestPermissions(new String[] {Manifest.permission.ACCESS_FINE_LOCATION}, LOCATION_REQUEST_CODE)); // Location (for BLE)
|
||||||
break;
|
break;
|
||||||
case STEP_PERMISSION_BACKGROUND_LOCATION:
|
case STEP_PERMISSION_BACKGROUND_LOCATION:
|
||||||
msg.setText(String.format(Locale.getDefault(), "%s %d/%d: %s", getString(R.string.intro_step), currentStep, numOfSteps, getString(R.string.intro_loc2_perm)));
|
mPermissionsMessage.setText(String.format(Locale.getDefault(), "%s %d/%d: %s", getString(R.string.intro_step), currentStep, getNumberOfSteps(), getString(R.string.intro_loc2_perm)));
|
||||||
btn.setOnClickListener(view -> {
|
mPermissionsButton.setOnClickListener(view -> {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) requestPermissions(new String[] {Manifest.permission.ACCESS_BACKGROUND_LOCATION}, BACKGROUND_LOCATION_REQUEST_CODE);
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||||
|
requestPermissions(new String[] {Manifest.permission.ACCESS_BACKGROUND_LOCATION}, BACKGROUND_LOCATION_REQUEST_CODE);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
case STEP_PERMISSION_NOTIFICATION:
|
||||||
|
mPermissionsMessage.setText(String.format(Locale.getDefault(), "%s %d/%d: %s", getString(R.string.intro_step), currentStep, getNumberOfSteps(), getString(R.string.intro_notif_perm)));
|
||||||
|
mPermissionsButton.setOnClickListener(view -> {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
|
requestPermissions(new String[] {Manifest.permission.POST_NOTIFICATIONS}, NOTIFICATION_REQUEST_CODE);
|
||||||
}
|
}
|
||||||
btn.setText(String.format(Locale.getDefault(), "%s (%d/%d)", getString(R.string.intro_allow), currentStep, numOfSteps));
|
});
|
||||||
|
}
|
||||||
|
mPermissionsButton.setText(String.format(Locale.getDefault(), "%s (%d/%d)", getString(R.string.intro_allow), currentStep, getNumberOfSteps()));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private int getNumberOfSteps() {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
|
return 5;
|
||||||
|
}
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||||
|
return 4;
|
||||||
|
}
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onRequestPermissionsResult (int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
||||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||||
switch (requestCode) {
|
switch (requestCode) {
|
||||||
case BLUETOOTH_REQUEST_CODE:
|
case BLUETOOTH_REQUEST_CODE:
|
||||||
if (!hasAllPermissionsGranted(grantResults)) Toast.makeText(this, getString(R.string.msg_bt_perm), Toast.LENGTH_LONG).show();
|
if (!hasAllPermissionsGranted(grantResults)) {
|
||||||
|
Toast.makeText(this, getString(R.string.msg_bt_perm), Toast.LENGTH_LONG).show();
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case LOCATION_REQUEST_CODE:
|
case LOCATION_REQUEST_CODE:
|
||||||
if (!hasAllPermissionsGranted(grantResults)) Toast.makeText(this, getString(R.string.msg_loc1_perm), Toast.LENGTH_LONG).show();
|
if (!hasAllPermissionsGranted(grantResults)) {
|
||||||
|
Toast.makeText(this, getString(R.string.msg_loc1_perm), Toast.LENGTH_LONG).show();
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case BACKGROUND_LOCATION_REQUEST_CODE:
|
case BACKGROUND_LOCATION_REQUEST_CODE:
|
||||||
if (!hasAllPermissionsGranted(grantResults)) Toast.makeText(this, getString(R.string.msg_loc2_perm), Toast.LENGTH_LONG).show();
|
if (!hasAllPermissionsGranted(grantResults)) {
|
||||||
|
Toast.makeText(this, getString(R.string.msg_loc2_perm), Toast.LENGTH_LONG).show();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case NOTIFICATION_REQUEST_CODE:
|
||||||
|
if (!hasAllPermissionsGranted(grantResults)) {
|
||||||
|
Toast.makeText(this, getString(R.string.msg_notif_perm), Toast.LENGTH_LONG).show();
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
|
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
|
||||||
public boolean hasAllPermissionsGranted (@NonNull int[] grantResults) {
|
public boolean hasAllPermissionsGranted(@NonNull int[] grantResults) {
|
||||||
for (int grantResult : grantResults)
|
for (int grantResult : grantResults) {
|
||||||
if (grantResult == PackageManager.PERMISSION_DENIED)
|
if (grantResult == PackageManager.PERMISSION_DENIED) {
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,9 +19,8 @@ import com.dosse.airpods.utils.PermissionUtils;
|
|||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
public class MainActivity extends AppCompatActivity {
|
public class MainActivity extends AppCompatActivity {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate (Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
setContentView(R.layout.activity_main);
|
setContentView(R.layout.activity_main);
|
||||||
|
|
||||||
@@ -30,6 +29,7 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
if (btAdapter == null || (btAdapter.isEnabled() && btAdapter.getBluetoothLeScanner() == null) || (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE))) {
|
if (btAdapter == null || (btAdapter.isEnabled() && btAdapter.getBluetoothLeScanner() == null) || (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE))) {
|
||||||
startActivity(new Intent(MainActivity.this, NoBTActivity.class));
|
startActivity(new Intent(MainActivity.this, NoBTActivity.class));
|
||||||
finish();
|
finish();
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,18 +44,18 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onCreateOptionsMenu (Menu menu) {
|
public boolean onCreateOptionsMenu(Menu menu) {
|
||||||
getMenuInflater().inflate(R.menu.menu_main, menu);
|
getMenuInflater().inflate(R.menu.menu_main, menu);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onOptionsItemSelected (@NonNull MenuItem item) {
|
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
|
||||||
if (item.getItemId() == R.id.menu_ab_settings) {
|
if (item.getItemId() == R.id.menu_ab_settings) {
|
||||||
startActivity(new Intent(this, SettingsActivity.class)); // Settings icon clicked
|
startActivity(new Intent(this, SettingsActivity.class)); // Settings icon clicked
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,11 +6,9 @@ import androidx.appcompat.app.AppCompatActivity;
|
|||||||
import com.dosse.airpods.R;
|
import com.dosse.airpods.R;
|
||||||
|
|
||||||
public class NoBTActivity extends AppCompatActivity {
|
public class NoBTActivity extends AppCompatActivity {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate (Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
setContentView(R.layout.activity_no_bt);
|
setContentView(R.layout.activity_no_bt);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,9 +11,8 @@ import com.dosse.airpods.R;
|
|||||||
import com.dosse.airpods.receivers.StartupReceiver;
|
import com.dosse.airpods.receivers.StartupReceiver;
|
||||||
|
|
||||||
public class SettingsActivity extends AppCompatActivity {
|
public class SettingsActivity extends AppCompatActivity {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate (Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
setContentView(R.layout.activity_settings);
|
setContentView(R.layout.activity_settings);
|
||||||
getSupportFragmentManager()
|
getSupportFragmentManager()
|
||||||
@@ -23,9 +22,8 @@ public class SettingsActivity extends AppCompatActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static class SettingsFragment extends PreferenceFragmentCompat {
|
public static class SettingsFragment extends PreferenceFragmentCompat {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreatePreferences (Bundle savedInstanceState, String rootKey) {
|
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
|
||||||
setPreferencesFromResource(R.xml.preference_screen, rootKey);
|
setPreferencesFromResource(R.xml.preference_screen, rootKey);
|
||||||
|
|
||||||
getPreference("batterySaver").setOnPreferenceClickListener(preference -> {
|
getPreference("batterySaver").setOnPreferenceClickListener(preference -> {
|
||||||
@@ -46,10 +44,8 @@ public class SettingsActivity extends AppCompatActivity {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private Preference getPreference (String key) {
|
private Preference getPreference(String key) {
|
||||||
return getPreferenceManager().findPreference(key);
|
return getPreferenceManager().findPreference(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,17 +5,19 @@ import android.util.Log;
|
|||||||
import com.dosse.airpods.BuildConfig;
|
import com.dosse.airpods.BuildConfig;
|
||||||
|
|
||||||
public class Logger {
|
public class Logger {
|
||||||
|
|
||||||
// Log is only displayed if this is a debug build, not release
|
// Log is only displayed if this is a debug build, not release
|
||||||
private static final boolean ENABLE_LOGGING = BuildConfig.DEBUG;
|
private static final boolean ENABLE_LOGGING = BuildConfig.DEBUG;
|
||||||
public static final String TAG = "AirPods";
|
public static final String TAG = "AirPods";
|
||||||
|
|
||||||
public static void debug (String msg) {
|
public static void debug(String msg) {
|
||||||
if (ENABLE_LOGGING) Log.d(TAG, msg);
|
if (ENABLE_LOGGING) {
|
||||||
|
Log.d(TAG, msg);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void error (Throwable t) {
|
public static void error(Throwable t) {
|
||||||
if (ENABLE_LOGGING) Log.e(TAG, "ERROR", t);
|
if (ENABLE_LOGGING) {
|
||||||
|
Log.e(TAG, "ERROR", t);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,31 +6,47 @@ import androidx.appcompat.app.AlertDialog;
|
|||||||
|
|
||||||
import com.dosse.airpods.R;
|
import com.dosse.airpods.R;
|
||||||
|
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
|
||||||
public class MIUIWarning {
|
public class MIUIWarning {
|
||||||
|
public static void show(Context context) {
|
||||||
|
Context appContext = context.getApplicationContext();
|
||||||
|
|
||||||
public static void show (Context context) {
|
|
||||||
try {
|
try {
|
||||||
@SuppressLint("PrivateApi") Class<?> c = Class.forName("android.os.SystemProperties");
|
String miuiVersion = getMiuiVersion();
|
||||||
String miuiVersion = (String)c.getMethod("get", String.class).invoke(c, "ro.miui.ui.version.code");
|
if (miuiVersion == null || miuiVersion.isEmpty()) {
|
||||||
if (miuiVersion != null && !miuiVersion.isEmpty()) {
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
context.getApplicationContext().openFileInput("miuiwarn").close();
|
appContext.openFileInput("miuiwarn").close();
|
||||||
} catch (Throwable ignored) {
|
} catch (Throwable ignored) {
|
||||||
|
showAlertDialog(appContext);
|
||||||
|
}
|
||||||
|
} catch (Throwable ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("PrivateApi")
|
||||||
|
private static String getMiuiVersion() throws ReflectiveOperationException {
|
||||||
|
Class<?> systemPropertiesClazz = Class.forName("android.os.SystemProperties");
|
||||||
|
Method systemPropertiesGet = systemPropertiesClazz.getMethod("get", String.class);
|
||||||
|
systemPropertiesGet.setAccessible(true);
|
||||||
|
|
||||||
|
return (String)systemPropertiesGet.invoke(null, "ro.miui.ui.version.code");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void showAlertDialog(Context context) {
|
||||||
AlertDialog.Builder builder = new AlertDialog.Builder(context);
|
AlertDialog.Builder builder = new AlertDialog.Builder(context);
|
||||||
builder.setTitle(R.string.miui_warning);
|
builder.setTitle(R.string.miui_warning);
|
||||||
builder.setMessage(R.string.miui_warning_desc);
|
builder.setMessage(R.string.miui_warning_desc);
|
||||||
builder.setNeutralButton(R.string.miui_warning_continue, (dialog, which) -> dialog.dismiss());
|
builder.setNeutralButton(R.string.miui_warning_continue, (dialog, which) -> dialog.dismiss());
|
||||||
builder.setOnDismissListener(dialog -> {
|
builder.setOnDismissListener(dialog -> {
|
||||||
try {
|
try {
|
||||||
context.getApplicationContext().openFileOutput("miuiwarn", Context.MODE_PRIVATE).close();
|
context.openFileOutput("miuiwarn", Context.MODE_PRIVATE).close();
|
||||||
} catch (Throwable ignored2) {
|
} catch (Throwable ignored2) {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
builder.show();
|
builder.show();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
} catch (Throwable ignored) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,47 +7,50 @@ import android.os.Build;
|
|||||||
import android.os.PowerManager;
|
import android.os.PowerManager;
|
||||||
import androidx.annotation.RequiresApi;
|
import androidx.annotation.RequiresApi;
|
||||||
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
import static androidx.core.content.ContextCompat.checkSelfPermission;
|
import static androidx.core.content.ContextCompat.checkSelfPermission;
|
||||||
|
|
||||||
public class PermissionUtils {
|
public class PermissionUtils {
|
||||||
|
public static boolean getBatteryOptimizationsPermission(Context context) {
|
||||||
public static boolean getBatteryOptimizationsPermission (Context context) {
|
return context.getSystemService(PowerManager.class).isIgnoringBatteryOptimizations(context.getPackageName());
|
||||||
try {
|
|
||||||
return Objects.requireNonNull(context.getSystemService(PowerManager.class)).isIgnoringBatteryOptimizations(context.getPackageName());
|
|
||||||
} catch (Throwable ignored) {
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@RequiresApi(api = Build.VERSION_CODES.TIRAMISU)
|
||||||
|
public static boolean getNotificationPermissions(Context context) {
|
||||||
|
return checkSelfPermission(context, Manifest.permission.POST_NOTIFICATIONS) == PackageManager.PERMISSION_GRANTED;
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequiresApi(api = Build.VERSION_CODES.S)
|
@RequiresApi(api = Build.VERSION_CODES.S)
|
||||||
public static boolean getBluetoothPermissions (Context context) {
|
public static boolean getBluetoothPermissions(Context context) {
|
||||||
return checkSelfPermission(context, Manifest.permission.BLUETOOTH_SCAN) == PackageManager.PERMISSION_GRANTED
|
return checkSelfPermission(context, Manifest.permission.BLUETOOTH_SCAN) == PackageManager.PERMISSION_GRANTED
|
||||||
&& checkSelfPermission(context, Manifest.permission.BLUETOOTH_CONNECT) == PackageManager.PERMISSION_GRANTED;
|
&& checkSelfPermission(context, Manifest.permission.BLUETOOTH_CONNECT) == PackageManager.PERMISSION_GRANTED;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean getFineLocationPermission (Context context) {
|
public static boolean getFineLocationPermission(Context context) {
|
||||||
return checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED;
|
return checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED;
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequiresApi(api = Build.VERSION_CODES.Q)
|
@RequiresApi(api = Build.VERSION_CODES.Q)
|
||||||
public static boolean getBackgroundLocationPermission (Context context) {
|
public static boolean getBackgroundLocationPermission(Context context) {
|
||||||
return checkSelfPermission(context, Manifest.permission.ACCESS_BACKGROUND_LOCATION) == PackageManager.PERMISSION_GRANTED;
|
return checkSelfPermission(context, Manifest.permission.ACCESS_BACKGROUND_LOCATION) == PackageManager.PERMISSION_GRANTED;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean getLocationPermissions (Context context) {
|
public static boolean getLocationPermissions(Context context) {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||||
return getFineLocationPermission(context) && getBackgroundLocationPermission(context);
|
return getFineLocationPermission(context) && getBackgroundLocationPermission(context);
|
||||||
else
|
}
|
||||||
|
|
||||||
return getFineLocationPermission(context);
|
return getFineLocationPermission(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean checkAllPermissions (Context context) {
|
public static boolean checkAllPermissions(Context context) {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S)
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
return getBatteryOptimizationsPermission(context) && getLocationPermissions(context) && getBluetoothPermissions(context);
|
return getNotificationPermissions(context) && getBatteryOptimizationsPermission(context) && getLocationPermissions(context) && getBluetoothPermissions(context);
|
||||||
else
|
}
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
|
||||||
return getBatteryOptimizationsPermission(context) && getLocationPermissions(context);
|
return getBatteryOptimizationsPermission(context) && getLocationPermissions(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return getBatteryOptimizationsPermission(context) && getLocationPermissions(context) && getBluetoothPermissions(context);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,10 +5,8 @@ import android.content.SharedPreferences;
|
|||||||
import androidx.preference.PreferenceManager;
|
import androidx.preference.PreferenceManager;
|
||||||
|
|
||||||
public class SharedPreferencesUtils {
|
public class SharedPreferencesUtils {
|
||||||
|
public static boolean isSavingBattery(Context context) {
|
||||||
public static boolean isSavingBattery (Context context) {
|
|
||||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||||
return prefs.getBoolean("batterySaver", false);
|
return prefs.getBoolean("batterySaver", false);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 151 KiB After Width: | Height: | Size: 130 KiB |
|
Before Width: | Height: | Size: 150 KiB After Width: | Height: | Size: 129 KiB |
|
Before Width: | Height: | Size: 143 KiB After Width: | Height: | Size: 116 KiB |
|
Before Width: | Height: | Size: 142 KiB After Width: | Height: | Size: 115 KiB |
|
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 92 KiB After Width: | Height: | Size: 69 KiB |
|
Before Width: | Height: | Size: 111 KiB After Width: | Height: | Size: 91 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 551 KiB After Width: | Height: | Size: 484 KiB |
|
Before Width: | Height: | Size: 543 KiB After Width: | Height: | Size: 474 KiB |
|
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 85 KiB |
|
After Width: | Height: | Size: 84 KiB |
|
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 40 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 161 KiB After Width: | Height: | Size: 130 KiB |
|
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 161 KiB After Width: | Height: | Size: 130 KiB |
@@ -62,6 +62,16 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="@string/airpodspro" />
|
android:text="@string/airpodspro" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/airpodspro2" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/airpodspro3" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
|||||||
@@ -55,6 +55,16 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="@string/airpodspro" />
|
android:text="@string/airpodspro" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/airpodspro2" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/airpodspro3" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 658 B After Width: | Height: | Size: 541 B |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 341 B After Width: | Height: | Size: 296 B |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 4.8 KiB |
|
Before Width: | Height: | Size: 684 B After Width: | Height: | Size: 531 B |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 8.3 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1007 B |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 1.5 KiB |
@@ -0,0 +1,55 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
|
<string name="bg_noti_channel" translatable="false">Background Notification</string>
|
||||||
|
<string name="bg_noti_title" tools:ignore="MissingTranslation">Běží na pozadí</string>
|
||||||
|
<string name="bg_noti_text" tools:ignore="MissingTranslation">Stisknutím otevřete nastavení oznámení</string>
|
||||||
|
|
||||||
|
<string name="intro_title">Tato aplikace vám umožňuje zkontrolovat stav vašich AirPods na jakémkoli zařízení s Androidem</string>
|
||||||
|
<string name="intro_message">Pro provoz je potřeba povolení k použití Bluetooth LE (místo) a povolení k běhu na pozadí</string>
|
||||||
|
<string name="intro_allow">Povolit</string>
|
||||||
|
<string name="intro_step" tools:ignore="MissingTranslation">Krok</string>
|
||||||
|
<string name="intro_bt_perm" tools:ignore="MissingTranslation">Povolení Bluetooth</string>
|
||||||
|
<string name="intro_bat_perm" tools:ignore="MissingTranslation">Optimalizace baterie</string>
|
||||||
|
<string name="intro_loc1_perm" tools:ignore="MissingTranslation">Povolení polohy</string>
|
||||||
|
<string name="intro_loc2_perm" tools:ignore="MissingTranslation">Povolení polohy v pozadí</string>
|
||||||
|
<string name="intro_notif_perm" tools:ignore="MissingTranslation">Povolení notifikace</string>
|
||||||
|
<string name="msg_bt_perm" tools:ignore="MissingTranslation">Nebylo uděleno povolení pro Bluetooth, povolte jej prosím v nastavení.</string>
|
||||||
|
<string name="msg_loc1_perm" tools:ignore="MissingTranslation">Nebylo uděleno povolení polohy, povolte jej prosím v nastavení.</string>
|
||||||
|
<string name="msg_loc2_perm" tools:ignore="MissingTranslation">Nebylo uděleno povolení polohy na pozadí, povolte jej prosím v nastavení.</string>
|
||||||
|
<string name="msg_notif_perm" tools:ignore="MissingTranslation">Nebylo uděleno povolení notifikace, povolte jej prosím v nastavení.</string>
|
||||||
|
|
||||||
|
<string name="main_description">The app will show a notification when your AirPods are connected</string>
|
||||||
|
<string name="supportedDevicesTitle"><u>Supported devices:</u></string>
|
||||||
|
<string name="airpods1">• Apple AirPods 1st gen</string>
|
||||||
|
<string name="airpods2">• Apple AirPods 2nd gen</string>
|
||||||
|
<string name="airpods3" tools:ignore="MissingTranslation">• Apple AirPods 3rd gen</string>
|
||||||
|
<string name="airpodspro">• Apple AirPods Pro</string>
|
||||||
|
<string name="airpodspro2" tools:ignore="MissingTranslation">• Apple AirPods Pro 2</string>
|
||||||
|
<string name="airpodspro3">• Apple AirPods Pro 3</string>
|
||||||
|
<string name="airpodsmax">• Apple AirPods Max</string>
|
||||||
|
<string name="beatsx">• Beats X</string>
|
||||||
|
<string name="beatsflex">• Beats Flex</string>
|
||||||
|
<string name="beatssolo3">• Beats Solo 3</string>
|
||||||
|
<string name="beatsstudio3">• Beats Studio 3</string>
|
||||||
|
<string name="powerbeats3">• Powerbeats 3</string>
|
||||||
|
<string name="powerbeatspro">• Powerbeats Pro</string>
|
||||||
|
|
||||||
|
<string name="miui_warning" tools:ignore="MissingTranslation">Detekováno MIUI</string>
|
||||||
|
<string name="miui_warning_desc" tools:ignore="MissingTranslation">Na tomto zařízení běží ROM s známými problémy kompatibility. Můžete pokračovat, ale OpenPods nemusí fungovat správně.</string>
|
||||||
|
<string name="miui_warning_continue" tools:ignore="MissingTranslation">Pokračovat</string>
|
||||||
|
|
||||||
|
<string name="noBtText">Toto zařízení nemá Bluetooth LE</string>
|
||||||
|
|
||||||
|
<string name="settings" tools:ignore="MissingTranslation">Nastavení</string>
|
||||||
|
<string name="batterySaver">Spořič baterie (nedoporučeno)</string>
|
||||||
|
<string name="batterySaver_desc">Povolte pokud Bluetooth spotřebovává velké množství baterie</string>
|
||||||
|
<string name="restart" tools:ignore="MissingTranslation">Službu restartujte</string>
|
||||||
|
<string name="restart_desc" tools:ignore="MissingTranslation">Udělejte to v případě, že se oznámení nezobrazuje</string>
|
||||||
|
|
||||||
|
<string name="about">O developerovi</string>
|
||||||
|
<string name="about_dev">Vytvořili společně Federico Dossena a Itai Levin</string>
|
||||||
|
<string name="website">webová stránka</string>
|
||||||
|
<string name="donate">Darujte</string>
|
||||||
|
|
||||||
|
</resources>
|
||||||
@@ -1,14 +1,32 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
|
<string name="bg_noti_channel" translatable="false">Background Notification</string>
|
||||||
|
<string name="bg_noti_title" tools:ignore="MissingTranslation">Läuft im Hintergrund</string>
|
||||||
|
<string name="bg_noti_text" tools:ignore="MissingTranslation">Anklicken, um die Benachrichtigungseinstellungen zu öffnen</string>
|
||||||
|
|
||||||
<string name="intro_title">Diese App erlaubt es dir, den Status deiner AirPods auf einem beliebigen Android-Gerät zu überprüfen</string>
|
<string name="intro_title">Diese App erlaubt es dir, den Status deiner AirPods auf einem beliebigen Android-Gerät zu überprüfen</string>
|
||||||
<string name="intro_message">Für den Dienst benötigen wir die Berechtigung zur Verwendung von Bluetooth LE (Standort) und die Berechtigung zum Betrieb im Hintergrund</string>
|
<string name="intro_message">Für den Dienst benötigen wir die Berechtigung zur Verwendung von Bluetooth LE (Standort) und die Berechtigung zum Betrieb im Hintergrund</string>
|
||||||
<string name="intro_allow">Erlauben</string>
|
<string name="intro_allow">Erlauben</string>
|
||||||
|
<string name="intro_step" tools:ignore="MissingTranslation">Weiter</string>
|
||||||
|
<string name="intro_bt_perm" tools:ignore="MissingTranslation">Bluetooth-Berechtigung</string>
|
||||||
|
<string name="intro_bat_perm" tools:ignore="MissingTranslation">Akku-Optimierung</string>
|
||||||
|
<string name="intro_loc1_perm" tools:ignore="MissingTranslation">Standortberechtigung</string>
|
||||||
|
<string name="intro_loc2_perm" tools:ignore="MissingTranslation">Berechtigung für Standort im Hintergrund</string>
|
||||||
|
<string name="intro_notif_perm" tools:ignore="MissingTranslation">Berechtigung für Benachrichtigung</string>
|
||||||
|
<string name="msg_bt_perm" tools:ignore="MissingTranslation">Bluetooth-Berechtigung nicht erteilt, bitte in den Einstellungen aktivieren.</string>
|
||||||
|
<string name="msg_loc1_perm" tools:ignore="MissingTranslation">Standortberechtigung nicht erteilt, bitte in den Einstellungen aktivieren.</string>
|
||||||
|
<string name="msg_loc2_perm" tools:ignore="MissingTranslation">Berechtigung für Standort im Hintergrund nicht erteilt, bitte in den Einstellungen aktivieren.</string>
|
||||||
|
<string name="msg_notif_perm" tools:ignore="MissingTranslation">Berechtigung für Benachrichtigung nicht erteilt, bitte in den Einstellungen aktivieren.</string>
|
||||||
|
|
||||||
<string name="main_description">Die App zeigt eine Benachrichtigung, wenn deine AirPods verbunden sind</string>
|
<string name="main_description">Die App zeigt eine Benachrichtigung, wenn deine AirPods verbunden sind</string>
|
||||||
<string name="supportedDevicesTitle"><u>Unterstützte Geräte:</u></string>
|
<string name="supportedDevicesTitle"><u>Unterstützte Geräte:</u></string>
|
||||||
<string name="airpods1">• Apple AirPods 1st gen</string>
|
<string name="airpods1">• Apple AirPods 1</string>
|
||||||
<string name="airpods2">• Apple AirPods 2nd gen</string>
|
<string name="airpods2">• Apple AirPods 2</string>
|
||||||
|
<string name="airpods3" tools:ignore="MissingTranslation">• Apple AirPods 3</string>
|
||||||
<string name="airpodspro">• Apple AirPods Pro</string>
|
<string name="airpodspro">• Apple AirPods Pro</string>
|
||||||
|
<string name="airpodspro2" tools:ignore="MissingTranslation">• Apple AirPods Pro 2</string>
|
||||||
|
<string name="airpodspro3">• Apple AirPods Pro 3</string>
|
||||||
<string name="airpodsmax">• Apple AirPods Max</string>
|
<string name="airpodsmax">• Apple AirPods Max</string>
|
||||||
<string name="beatsx">• Beats X</string>
|
<string name="beatsx">• Beats X</string>
|
||||||
<string name="beatsflex">• Beats Flex</string>
|
<string name="beatsflex">• Beats Flex</string>
|
||||||
@@ -18,16 +36,20 @@
|
|||||||
<string name="powerbeatspro">• Powerbeats Pro</string>
|
<string name="powerbeatspro">• Powerbeats Pro</string>
|
||||||
|
|
||||||
<string name="miui_warning">MIUI erkannt</string>
|
<string name="miui_warning">MIUI erkannt</string>
|
||||||
|
<string name="miui_warning_desc" tools:ignore="MissingTranslation">Die ROM, die auf diesem Gerät läuft, hat bekannte Kompatibilitätsprobleme. Du kannst fortfahren, aber OpenPods funktioniert möglicherweise nicht richtig.</string>
|
||||||
<string name="miui_warning_continue">Fortsetzen</string>
|
<string name="miui_warning_continue">Fortsetzen</string>
|
||||||
|
|
||||||
<string name="noBtText">Es sieht so aus, als hätte dieses Gerät kein Bluetooth LE</string>
|
<string name="noBtText">Es sieht so aus, als hätte dieses Gerät kein Bluetooth LE</string>
|
||||||
|
|
||||||
<string name="settings">Einstellungen</string>
|
<string name="settings">Einstellungen</string>
|
||||||
<string name="batterySaver">Stromsparmodus (nicht empfohlen)</string>
|
<string name="batterySaver">Stromsparmodus (nicht empfohlen)</string>
|
||||||
<string name="batterySaver_desc">Aktiviere diesen Modus, wenn Bluetooth zu viel Batterieleistung erfordert</string>
|
<string name="batterySaver_desc">Aktiviere diesen Modus, wenn Bluetooth zu viel Akkuleistung erfordert</string>
|
||||||
|
<string name="restart" tools:ignore="MissingTranslation">Dienst neu starten</string>
|
||||||
|
<string name="restart_desc" tools:ignore="MissingTranslation">Wähle dies, wenn die Benachrichtigung nicht angezeigt wird</string>
|
||||||
|
|
||||||
<string name="about">Über</string>
|
<string name="about">Über</string>
|
||||||
<string name="about_dev">Entwickelt von Federico Dossena und Itai Levin</string>
|
<string name="about_dev">Entwickelt von Federico Dossena und Itai Levin</string>
|
||||||
<string name="website">Webseite</string>
|
<string name="website">Webseite</string>
|
||||||
<string name="donate">Spenden</string>
|
<string name="donate">Spenden</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
<string name="airpods1">• Apple AirPods 1ª generación</string>
|
<string name="airpods1">• Apple AirPods 1ª generación</string>
|
||||||
<string name="airpods2">• Apple AirPods 2ª generación</string>
|
<string name="airpods2">• Apple AirPods 2ª generación</string>
|
||||||
<string name="airpodspro">• Apple AirPods Pro</string>
|
<string name="airpodspro">• Apple AirPods Pro</string>
|
||||||
|
<string name="airpodspro3">• Apple AirPods Pro 3</string>
|
||||||
<string name="airpodsmax">• Apple AirPods Max</string>
|
<string name="airpodsmax">• Apple AirPods Max</string>
|
||||||
<string name="beatsx">• Beats X</string>
|
<string name="beatsx">• Beats X</string>
|
||||||
<string name="beatsflex">• Beats Flex</string>
|
<string name="beatsflex">• Beats Flex</string>
|
||||||
@@ -30,4 +31,5 @@
|
|||||||
<string name="about_dev">Desarrollado por Federico Dossena y Itai Levin</string>
|
<string name="about_dev">Desarrollado por Federico Dossena y Itai Levin</string>
|
||||||
<string name="website">Página web</string>
|
<string name="website">Página web</string>
|
||||||
<string name="donate">Donar</string>
|
<string name="donate">Donar</string>
|
||||||
|
<string name="airpodspro2">• Apple AirPods Pro 2</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -9,6 +9,8 @@
|
|||||||
<string name="airpods1">• Apple AirPods 1re génération</string>
|
<string name="airpods1">• Apple AirPods 1re génération</string>
|
||||||
<string name="airpods2">• Apple AirPods 2e génération</string>
|
<string name="airpods2">• Apple AirPods 2e génération</string>
|
||||||
<string name="airpodspro">• Apple AirPods Pro</string>
|
<string name="airpodspro">• Apple AirPods Pro</string>
|
||||||
|
<string name="airpodspro2">• Apple AirPods Pro 2</string>
|
||||||
|
<string name="airpodspro3">• Apple AirPods Pro 3</string>
|
||||||
<string name="airpodsmax">• Apple AirPods Max</string>
|
<string name="airpodsmax">• Apple AirPods Max</string>
|
||||||
<string name="beatsx">• Beats X</string>
|
<string name="beatsx">• Beats X</string>
|
||||||
<string name="beatsflex">• Beats Flex</string>
|
<string name="beatsflex">• Beats Flex</string>
|
||||||
|
|||||||
@@ -9,6 +9,8 @@
|
|||||||
<string name="airpods1">• Apple AirPods 1st gen</string>
|
<string name="airpods1">• Apple AirPods 1st gen</string>
|
||||||
<string name="airpods2">• Apple AirPods 2nd gen</string>
|
<string name="airpods2">• Apple AirPods 2nd gen</string>
|
||||||
<string name="airpodspro">• Apple AirPods Pro</string>
|
<string name="airpodspro">• Apple AirPods Pro</string>
|
||||||
|
<string name="airpodspro2">• Apple AirPods Pro 2</string>
|
||||||
|
<string name="airpodspro3">• Apple AirPods Pro 3</string>
|
||||||
<string name="airpodsmax">• Apple AirPods Max</string>
|
<string name="airpodsmax">• Apple AirPods Max</string>
|
||||||
<string name="beatsx">• Beats X</string>
|
<string name="beatsx">• Beats X</string>
|
||||||
<string name="beatsflex">• Beats Flex</string>
|
<string name="beatsflex">• Beats Flex</string>
|
||||||
|
|||||||
@@ -8,7 +8,10 @@
|
|||||||
<string name="supportedDevicesTitle"><u>מכשירים נתמכים</u></string>
|
<string name="supportedDevicesTitle"><u>מכשירים נתמכים</u></string>
|
||||||
<string name="airpods1">• אפל איירפודס דור ראשון</string>
|
<string name="airpods1">• אפל איירפודס דור ראשון</string>
|
||||||
<string name="airpods2">• אפל איירפודס דור שני</string>
|
<string name="airpods2">• אפל איירפודס דור שני</string>
|
||||||
|
<string name="airpods3">• אפל איירפודס דור שלישי</string>
|
||||||
<string name="airpodspro">• אפל איירפודס פרו</string>
|
<string name="airpodspro">• אפל איירפודס פרו</string>
|
||||||
|
<string name="airpodspro2">• אפל איירפודס פרו 2</string>
|
||||||
|
<string name="airpodspro3">• אפל איירפודס פרו 3</string>
|
||||||
<string name="airpodsmax">• אפל איירפודס מקס</string>
|
<string name="airpodsmax">• אפל איירפודס מקס</string>
|
||||||
<string name="beatsx">• ביטס אקס</string>
|
<string name="beatsx">• ביטס אקס</string>
|
||||||
<string name="beatsflex">• ביטס פלקס</string>
|
<string name="beatsflex">• ביטס פלקס</string>
|
||||||
@@ -17,8 +20,8 @@
|
|||||||
<string name="powerbeats3">•פווארביטס 3</string>
|
<string name="powerbeats3">•פווארביטס 3</string>
|
||||||
<string name="powerbeatspro">• פווארביטס פרו</string>
|
<string name="powerbeatspro">• פווארביטס פרו</string>
|
||||||
|
|
||||||
<string name="miui_warning">MIUI נמצא</string>
|
<string name="miui_warning">MIUI זוהה</string>
|
||||||
<string name="miui_warning_desc">למערכת ההפעלה על מכשיר זה יש בעיות תאימות ידועות עם האפליקציה. אתה יכול להמשיך להשתמש ב-OpenPods אך לא בטוח שדברים יעבדו כמו שצריך.</string>
|
<string name="miui_warning_desc">למערכת ההפעלה במכשיר זה יש בעיות תאימות ידועות עם האפליקציה. אתה יכול להמשיך להשתמש ב-OpenPods אך לא בטוח שדברים יעבדו כמו שצריך.</string>
|
||||||
<string name="miui_warning_continue">המשך</string>
|
<string name="miui_warning_continue">המשך</string>
|
||||||
|
|
||||||
<string name="noBtText">נראה כי מכשיר זה אינו תומך ב-Bluetooth LE</string>
|
<string name="noBtText">נראה כי מכשיר זה אינו תומך ב-Bluetooth LE</string>
|
||||||
@@ -30,6 +33,5 @@
|
|||||||
<string name="about">אודות</string>
|
<string name="about">אודות</string>
|
||||||
<string name="about_dev">פותח על ידי פדריקו דוסנה ואיתי לוין</string>
|
<string name="about_dev">פותח על ידי פדריקו דוסנה ואיתי לוין</string>
|
||||||
<string name="website">אתר המפתח</string>
|
<string name="website">אתר המפתח</string>
|
||||||
<string name="github">קוד מקור</string>
|
|
||||||
<string name="donate">תרומה</string>
|
<string name="donate">תרומה</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -20,6 +20,8 @@
|
|||||||
<string name="airpods1">• Apple AirPods 1e gen.</string>
|
<string name="airpods1">• Apple AirPods 1e gen.</string>
|
||||||
<string name="airpods2">• Apple AirPods 2e gen.</string>
|
<string name="airpods2">• Apple AirPods 2e gen.</string>
|
||||||
<string name="airpodspro">• Apple AirPods Pro</string>
|
<string name="airpodspro">• Apple AirPods Pro</string>
|
||||||
|
<string name="airpodspro2">• Apple AirPods Pro 2</string>
|
||||||
|
<string name="airpodspro3">• Apple AirPods Pro 3</string>
|
||||||
<string name="airpodsmax">• Apple AirPods Max</string>
|
<string name="airpodsmax">• Apple AirPods Max</string>
|
||||||
<string name="beatsx">• Beats X</string>
|
<string name="beatsx">• Beats X</string>
|
||||||
<string name="beatsflex">• Beats Flex</string>
|
<string name="beatsflex">• Beats Flex</string>
|
||||||
@@ -43,6 +45,5 @@
|
|||||||
<string name="about">Over</string>
|
<string name="about">Over</string>
|
||||||
<string name="about_dev">Gemaakt door Federico Dossena en Itai Levin</string>
|
<string name="about_dev">Gemaakt door Federico Dossena en Itai Levin</string>
|
||||||
<string name="website">Website</string>
|
<string name="website">Website</string>
|
||||||
<string name="github">GitHub</string>
|
|
||||||
<string name="donate">Doneren</string>
|
<string name="donate">Doneren</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -1,14 +1,32 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
|
<string name="bg_noti_channel" translatable="false">Фоновое уведомление</string>
|
||||||
|
<string name="bg_noti_title" tools:ignore="MissingTranslation">Работает в фоновом режиме</string>
|
||||||
|
<string name="bg_noti_text" tools:ignore="MissingTranslation">Нажмите, чтобы войти в настройки уведомлений</string>
|
||||||
|
|
||||||
<string name="intro_title">Это приложение позволяет Вам проверить статус ваших AirPods на любом Android-устройстве</string>
|
<string name="intro_title">Это приложение позволяет Вам проверить статус ваших AirPods на любом Android-устройстве</string>
|
||||||
<string name="intro_message">Для работы нам нужно Bluetooth LE(местоположение) и разрешение на запуск в фоновом режиме</string>
|
<string name="intro_message">Для работы нам нужно Bluetooth LE(местоположение) и разрешение на запуск в фоновом режиме</string>
|
||||||
<string name="intro_allow">Разрешить</string>
|
<string name="intro_allow">Разрешить</string>
|
||||||
|
<string name="intro_step" tools:ignore="MissingTranslation">Шаг</string>
|
||||||
|
<string name="intro_bt_perm" tools:ignore="MissingTranslation">Bluetooth</string>
|
||||||
|
<string name="intro_bat_perm" tools:ignore="MissingTranslation">Оптимизация расхода батареи</string>
|
||||||
|
<string name="intro_loc1_perm" tools:ignore="MissingTranslation">Местоположение</string>
|
||||||
|
<string name="intro_loc2_perm" tools:ignore="MissingTranslation">Местоположение в фоновом режиме</string>
|
||||||
|
<string name="intro_notif_perm" tools:ignore="MissingTranslation">Уведомления</string>
|
||||||
|
<string name="msg_bt_perm" tools:ignore="MissingTranslation">Разрешение на использование Bluetooth не получено. Пожалуйста, выставьте его вручную в настройках приложения.</string>
|
||||||
|
<string name="msg_loc1_perm" tools:ignore="MissingTranslation">Разрешение на использование местоположения не получено. Пожалуйста, выставьте его вручную в настройках приложения.</string>
|
||||||
|
<string name="msg_loc2_perm" tools:ignore="MissingTranslation">Разрешение на использование местоположения в фоновом режиме не получено. Пожалуйста, выставьте его вручную в настройках приложения.</string>
|
||||||
|
<string name="msg_notif_perm" tools:ignore="MissingTranslation">Разрешение на отображение уведомлений не получено. Пожалуйста, выставьте его вручную в настройках приложения.</string>
|
||||||
|
|
||||||
<string name="main_description">Приложение покажет уведомление, когда будет установлено соединение с AirPods</string>
|
<string name="main_description">Приложение покажет уведомление, когда будет установлено соединение с AirPods</string>
|
||||||
<string name="supportedDevicesTitle"><u>Поддерживаемые устройства:</u></string>
|
<string name="supportedDevicesTitle"><u>Поддерживаемые устройства:</u></string>
|
||||||
<string name="airpods1">• Apple AirPods 1st gen</string>
|
<string name="airpods1">• Apple AirPods 1st gen</string>
|
||||||
<string name="airpods2">• Apple AirPods 2nd gen</string>
|
<string name="airpods2">• Apple AirPods 2nd gen</string>
|
||||||
|
<string name="airpods3" tools:ignore="MissingTranslation">• Apple AirPods 3rd gen</string>
|
||||||
<string name="airpodspro">• Apple AirPods Pro</string>
|
<string name="airpodspro">• Apple AirPods Pro</string>
|
||||||
|
<string name="airpodspro2" tools:ignore="MissingTranslation">• Apple AirPods Pro 2</string>
|
||||||
|
<string name="airpodspro3">• Apple AirPods Pro 3</string>
|
||||||
<string name="airpodsmax">• Apple AirPods Max</string>
|
<string name="airpodsmax">• Apple AirPods Max</string>
|
||||||
<string name="beatsx">• Beats X</string>
|
<string name="beatsx">• Beats X</string>
|
||||||
<string name="beatsflex">• Beats Flex</string>
|
<string name="beatsflex">• Beats Flex</string>
|
||||||
@@ -18,13 +36,16 @@
|
|||||||
<string name="powerbeatspro">• Powerbeats Pro</string>
|
<string name="powerbeatspro">• Powerbeats Pro</string>
|
||||||
|
|
||||||
<string name="miui_warning">MIUI обнаружено</string>
|
<string name="miui_warning">MIUI обнаружено</string>
|
||||||
<string name="miui_warning_continue">Продолжать</string>
|
<string name="miui_warning_desc">Прошивка, запущенная на вашем устройстве имеет известные неполадки с совместимостью с некоторыми программами. Вы можете продолжить, однако правильная работа OpenPods не гарантируется.</string>
|
||||||
|
<string name="miui_warning_continue">Продолжить</string>
|
||||||
|
|
||||||
<string name="noBtText">Похоже, что Bluetooth LE не поддерживается на Вашем устройстве</string>
|
<string name="noBtText">Похоже, что Bluetooth LE не поддерживается на Вашем устройстве.</string>
|
||||||
|
|
||||||
<string name="settings">Настройки</string>
|
<string name="settings">Настройки</string>
|
||||||
<string name="batterySaver">Режим энергосбережения (Не рекомендуется)</string>
|
<string name="batterySaver">Режим энергосбережения (не рекомендуется)</string>
|
||||||
<string name="batterySaver_desc">Включите, если Bluetooth использует слишком много ресурсов</string>
|
<string name="batterySaver_desc">Включите, если Bluetooth использует слишком много ресурсов</string>
|
||||||
|
<string name="restart" tools:ignore="MissingTranslation">Перезапуск службы</string>
|
||||||
|
<string name="restart_desc" tools:ignore="MissingTranslation">На случай если уведомление не показывается</string>
|
||||||
|
|
||||||
<string name="about">О приложении</string>
|
<string name="about">О приложении</string>
|
||||||
<string name="about_dev">Разработано Federico Dossena и Itai Levin</string>
|
<string name="about_dev">Разработано Federico Dossena и Itai Levin</string>
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
<string name="airpods1">• Apple AirPods 1st gen</string>
|
<string name="airpods1">• Apple AirPods 1st gen</string>
|
||||||
<string name="airpods2">• Apple AirPods 2nd gen</string>
|
<string name="airpods2">• Apple AirPods 2nd gen</string>
|
||||||
<string name="airpodspro">• Apple AirPods Pro</string>
|
<string name="airpodspro">• Apple AirPods Pro</string>
|
||||||
|
<string name="airpodspro3">• Apple AirPods Pro 3</string>
|
||||||
<string name="airpodsmax">• Apple AirPods Max</string>
|
<string name="airpodsmax">• Apple AirPods Max</string>
|
||||||
<string name="beatsx">• Beats X</string>
|
<string name="beatsx">• Beats X</string>
|
||||||
<string name="beatsflex">• Beats Flex</string>
|
<string name="beatsflex">• Beats Flex</string>
|
||||||
@@ -30,4 +31,5 @@
|
|||||||
<string name="about_dev">Розроблено Federico Dossena й Itai Levin</string>
|
<string name="about_dev">Розроблено Federico Dossena й Itai Levin</string>
|
||||||
<string name="website">Сайт</string>
|
<string name="website">Сайт</string>
|
||||||
<string name="donate">Підтримати</string>
|
<string name="donate">Підтримати</string>
|
||||||
|
<string name="airpodspro2">• Apple AirPods Pro 2</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
<string name="airpods1">• Apple AirPods 一代</string>
|
<string name="airpods1">• Apple AirPods 一代</string>
|
||||||
<string name="airpods2">• Apple AirPods 二代</string>
|
<string name="airpods2">• Apple AirPods 二代</string>
|
||||||
<string name="airpodspro">• Apple AirPods Pro</string>
|
<string name="airpodspro">• Apple AirPods Pro</string>
|
||||||
|
<string name="airpodspro3">• Apple AirPods Pro 3</string>
|
||||||
<string name="airpodsmax">• Apple AirPods Max</string>
|
<string name="airpodsmax">• Apple AirPods Max</string>
|
||||||
<string name="beatsx">• Beats X</string>
|
<string name="beatsx">• Beats X</string>
|
||||||
<string name="beatsflex">• Beats Flex</string>
|
<string name="beatsflex">• Beats Flex</string>
|
||||||
@@ -26,4 +27,5 @@
|
|||||||
<string name="about_dev">由 Federico Dossena 和 Itai Levin 开发</string>
|
<string name="about_dev">由 Federico Dossena 和 Itai Levin 开发</string>
|
||||||
<string name="website">网站</string>
|
<string name="website">网站</string>
|
||||||
<string name="donate">捐赠</string>
|
<string name="donate">捐赠</string>
|
||||||
|
<string name="airpodspro2">• Apple AirPods Pro 2</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -0,0 +1,55 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
|
||||||
|
<string name="bg_noti_channel" translatable="false">Background Notification</string>
|
||||||
|
<string name="bg_noti_title">背景運作中</string>
|
||||||
|
<string name="bg_noti_text">點選以開啟通知設定</string>
|
||||||
|
|
||||||
|
<string name="intro_title">此應用讓您在任何 Android 裝置上檢查您的 AirPods 狀態</string>
|
||||||
|
<string name="intro_message">為了正常運作,我們需要您開放藍牙低功耗(位置)以及背景運作的權限</string>
|
||||||
|
<string name="intro_allow">允許</string>
|
||||||
|
<string name="intro_step">步驟</string>
|
||||||
|
<string name="intro_bt_perm">藍牙權限</string>
|
||||||
|
<string name="intro_bat_perm">電池最佳化</string>
|
||||||
|
<string name="intro_loc1_perm">位置權限</string>
|
||||||
|
<string name="intro_loc2_perm">背景位置權限</string>
|
||||||
|
<string name="intro_notif_perm">通知權限</string>
|
||||||
|
<string name="msg_bt_perm">藍牙權限尚未開啟,請至設定中啟用。</string>
|
||||||
|
<string name="msg_loc1_perm">位置權限尚未開啟,請至設定中啟用。</string>
|
||||||
|
<string name="msg_loc2_perm">背景位置權限尚未開啟,請至設定中啟用。</string>
|
||||||
|
<string name="msg_notif_perm">通知權限尚未開啟,請至設定中啟用。</string>
|
||||||
|
|
||||||
|
<string name="main_description">當您的 AirPods 連接時,此應用會顯示一個通知</string>
|
||||||
|
<string name="supportedDevicesTitle"><u>支援的裝置:</u></string>
|
||||||
|
<string name="airpods1">• Apple AirPods 第一代</string>
|
||||||
|
<string name="airpods2">• Apple AirPods 第二代</string>
|
||||||
|
<string name="airpods3">• Apple AirPods 第三代</string>
|
||||||
|
<string name="airpodspro">• Apple AirPods Pro</string>
|
||||||
|
<string name="airpodspro2">• Apple AirPods Pro 2</string>
|
||||||
|
<string name="airpodspro3">• Apple AirPods Pro 3</string>
|
||||||
|
<string name="airpodsmax">• Apple AirPods Max</string>
|
||||||
|
<string name="beatsx">• Beats X</string>
|
||||||
|
<string name="beatsflex">• Beats Flex</string>
|
||||||
|
<string name="beatssolo3">• Beats Solo 3</string>
|
||||||
|
<string name="beatsstudio3">• Beats Studio 3</string>
|
||||||
|
<string name="powerbeats3">• Powerbeats 3</string>
|
||||||
|
<string name="powerbeatspro">• Powerbeats Pro</string>
|
||||||
|
|
||||||
|
<string name="miui_warning">偵測到 MIUI</string>
|
||||||
|
<string name="miui_warning_desc">此裝置使用的 ROM 存在已知的相容性問題。您可以繼續使用,但 OpenPods 可能無法正常運作。</string>
|
||||||
|
<string name="miui_warning_continue">繼續</string>
|
||||||
|
|
||||||
|
<string name="noBtText">此裝置似乎沒有藍牙低功耗功能</string>
|
||||||
|
|
||||||
|
<string name="settings">設定</string>
|
||||||
|
<string name="batterySaver">省電模式(不建議)</string>
|
||||||
|
<string name="batterySaver_desc">如果藍牙使用過多電量,請啟用此功能</string>
|
||||||
|
<string name="restart">重新啟動服務</string>
|
||||||
|
<string name="restart_desc">如果通知未正常顯示,請執行此操作</string>
|
||||||
|
|
||||||
|
<string name="about">關於</string>
|
||||||
|
<string name="about_dev">由 Federico Dossena 和 Itai Levin 開發</string>
|
||||||
|
<string name="website">網站</string>
|
||||||
|
<string name="donate">贊助</string>
|
||||||
|
|
||||||
|
</resources>
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||||
<string name="app_name" translatable="false">OpenPods</string>
|
<string name="app_name" translatable="false">OpenPods</string>
|
||||||
|
|
||||||
<string name="bg_noti_channel" translatable="false">Background Notification</string>
|
<string name="bg_noti_channel" tools:ignore="MissingTranslation">Background Notification</string>
|
||||||
<string name="bg_noti_title" tools:ignore="MissingTranslation">Running in the background</string>
|
<string name="bg_noti_title" tools:ignore="MissingTranslation">Running in the background</string>
|
||||||
<string name="bg_noti_text" tools:ignore="MissingTranslation">Click to open notification settings</string>
|
<string name="bg_noti_text" tools:ignore="MissingTranslation">Click to open notification settings</string>
|
||||||
|
|
||||||
@@ -14,16 +14,20 @@
|
|||||||
<string name="intro_bat_perm" tools:ignore="MissingTranslation">Battery Optimization</string>
|
<string name="intro_bat_perm" tools:ignore="MissingTranslation">Battery Optimization</string>
|
||||||
<string name="intro_loc1_perm" tools:ignore="MissingTranslation">Location Permission</string>
|
<string name="intro_loc1_perm" tools:ignore="MissingTranslation">Location Permission</string>
|
||||||
<string name="intro_loc2_perm" tools:ignore="MissingTranslation">Background Location Permission</string>
|
<string name="intro_loc2_perm" tools:ignore="MissingTranslation">Background Location Permission</string>
|
||||||
|
<string name="intro_notif_perm" tools:ignore="MissingTranslation">Notification Permission</string>
|
||||||
<string name="msg_bt_perm" tools:ignore="MissingTranslation">Bluetooth permissions not granted, please enable them in settings.</string>
|
<string name="msg_bt_perm" tools:ignore="MissingTranslation">Bluetooth permissions not granted, please enable them in settings.</string>
|
||||||
<string name="msg_loc1_perm" tools:ignore="MissingTranslation">Location permission not granted, please enable it in settings.</string>
|
<string name="msg_loc1_perm" tools:ignore="MissingTranslation">Location permission not granted, please enable it in settings.</string>
|
||||||
<string name="msg_loc2_perm" tools:ignore="MissingTranslation">Background Location permission not granted, please enable it in settings.</string>
|
<string name="msg_loc2_perm" tools:ignore="MissingTranslation">Background Location permission not granted, please enable it in settings.</string>
|
||||||
|
<string name="msg_notif_perm" tools:ignore="MissingTranslation">Notification Permission not granted, please enable it in settings.</string>
|
||||||
|
|
||||||
<string name="main_description">The app will show a notification when your AirPods are connected</string>
|
<string name="main_description">The app will show a notification when your AirPods are connected</string>
|
||||||
<string name="supportedDevicesTitle"><u>Supported devices:</u></string>
|
<string name="supportedDevicesTitle"><u>Supported devices:</u></string>
|
||||||
<string name="airpods1">• Apple AirPods 1st gen</string>
|
<string name="airpods1">• Apple AirPods 1st gen</string>
|
||||||
<string name="airpods2">• Apple AirPods 2nd gen</string>
|
<string name="airpods2">• Apple AirPods 2nd gen</string>
|
||||||
<string name="airpods3">• Apple AirPods 3rd gen</string>
|
<string name="airpods3" tools:ignore="MissingTranslation">• Apple AirPods 3rd gen</string>
|
||||||
<string name="airpodspro">• Apple AirPods Pro</string>
|
<string name="airpodspro">• Apple AirPods Pro</string>
|
||||||
|
<string name="airpodspro2" tools:ignore="MissingTranslation">• Apple AirPods Pro 2</string>
|
||||||
|
<string name="airpodspro3" tools:ignore="MissingTranslation">• Apple AirPods Pro 3</string>
|
||||||
<string name="airpodsmax">• Apple AirPods Max</string>
|
<string name="airpodsmax">• Apple AirPods Max</string>
|
||||||
<string name="beatsx">• Beats X</string>
|
<string name="beatsx">• Beats X</string>
|
||||||
<string name="beatsflex">• Beats Flex</string>
|
<string name="beatsflex">• Beats Flex</string>
|
||||||
@@ -48,7 +52,7 @@
|
|||||||
<string name="about_dev">Developed by Federico Dossena and Itai Levin</string>
|
<string name="about_dev">Developed by Federico Dossena and Itai Levin</string>
|
||||||
<string name="fdroid" translatable="false">F-Droid</string>
|
<string name="fdroid" translatable="false">F-Droid</string>
|
||||||
<string name="website">Website</string>
|
<string name="website">Website</string>
|
||||||
<string name="github" tools:ignore="MissingTranslation">Github</string>
|
<string name="github" translatable="false">Github</string>
|
||||||
<string name="donate">Donate</string>
|
<string name="donate">Donate</string>
|
||||||
|
|
||||||
<string name="fdroid_url" translatable="false">https://f-droid.org/packages/com.dosse.airpods/</string>
|
<string name="fdroid_url" translatable="false">https://f-droid.org/packages/com.dosse.airpods/</string>
|
||||||
|
|||||||
@@ -1,27 +1,4 @@
|
|||||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||||
|
plugins {
|
||||||
buildscript {
|
alias(libs.plugins.androidApplication) apply false
|
||||||
repositories {
|
|
||||||
google()
|
|
||||||
mavenCentral()
|
|
||||||
|
|
||||||
}
|
|
||||||
dependencies {
|
|
||||||
classpath 'com.android.tools.build:gradle:7.2.2'
|
|
||||||
|
|
||||||
// NOTE: Do not place your application dependencies here; they belong
|
|
||||||
// in the individual module build.gradle files
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
allprojects {
|
|
||||||
repositories {
|
|
||||||
google()
|
|
||||||
mavenCentral()
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
task clean(type: Delete) {
|
|
||||||
delete rootProject.buildDir
|
|
||||||
}
|
}
|
||||||
@@ -6,12 +6,17 @@
|
|||||||
# http://www.gradle.org/docs/current/userguide/build_environment.html
|
# http://www.gradle.org/docs/current/userguide/build_environment.html
|
||||||
# Specifies the JVM arguments used for the daemon process.
|
# Specifies the JVM arguments used for the daemon process.
|
||||||
# The setting is particularly useful for tweaking memory settings.
|
# The setting is particularly useful for tweaking memory settings.
|
||||||
android.enableJetifier=true
|
org.gradle.jvmargs=-Xmx4096m -Dfile.encoding=UTF-8
|
||||||
android.useAndroidX=true
|
|
||||||
org.gradle.jvmargs=-Xmx1536m
|
|
||||||
# When configured, Gradle will run in incubating parallel mode.
|
# When configured, Gradle will run in incubating parallel mode.
|
||||||
# This option should only be used with decoupled projects. More details, visit
|
# This option should only be used with decoupled projects. For more details, visit
|
||||||
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
# https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects
|
||||||
# org.gradle.parallel=true
|
# org.gradle.parallel=true
|
||||||
|
# AndroidX package structure to make it clearer which packages are bundled with the
|
||||||
|
# Android operating system, and which are packaged with your app's APK
|
||||||
|
# https://developer.android.com/topic/libraries/support-library/androidx-rn
|
||||||
|
android.useAndroidX=true
|
||||||
|
android.enableJetifier=true
|
||||||
|
# Enables namespacing of each library's R class so that its R class includes only the
|
||||||
|
# resources declared in the library itself and none from the library's dependencies,
|
||||||
|
# thereby reducing the size of the R class for that library
|
||||||
|
android.nonTransitiveRClass=true
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
[versions]
|
||||||
|
agp = "8.13.0"
|
||||||
|
appcompat = "1.6.1"
|
||||||
|
preference = "1.2.1"
|
||||||
|
|
||||||
|
[libraries]
|
||||||
|
androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
|
||||||
|
androidx-preference = { group = "androidx.preference", name = "preference", version.ref = "preference" }
|
||||||
|
|
||||||
|
[plugins]
|
||||||
|
androidApplication = { id = "com.android.application", version.ref = "agp" }
|
||||||
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
#Wed May 18 00:26:39 IDT 2022
|
#Thu Sep 29 21:38:30 IDT 2022
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
|||||||
@@ -1 +1,23 @@
|
|||||||
|
pluginManagement {
|
||||||
|
repositories {
|
||||||
|
google {
|
||||||
|
content {
|
||||||
|
includeGroupByRegex("com\\.android.*")
|
||||||
|
includeGroupByRegex("com\\.google.*")
|
||||||
|
includeGroupByRegex("androidx.*")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mavenCentral()
|
||||||
|
gradlePluginPortal()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dependencyResolutionManagement {
|
||||||
|
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
|
||||||
|
repositories {
|
||||||
|
google()
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rootProject.name = "OpenPods"
|
||||||
include ':app'
|
include ':app'
|
||||||
|
|||||||