diff --git a/OpenPods/.gitignore b/OpenPods/.gitignore
index 2b75303..6f9fc07 100644
--- a/OpenPods/.gitignore
+++ b/OpenPods/.gitignore
@@ -2,6 +2,7 @@
.gradle
/local.properties
/.idea/caches
+/.idea/dictionaries
/.idea/libraries
/.idea/modules.xml
/.idea/workspace.xml
diff --git a/OpenPods/.idea/misc.xml b/OpenPods/.idea/misc.xml
index af0bbdd..703e5d4 100644
--- a/OpenPods/.idea/misc.xml
+++ b/OpenPods/.idea/misc.xml
@@ -5,7 +5,7 @@
-
+
diff --git a/OpenPods/.idea/render.experimental.xml b/OpenPods/.idea/render.experimental.xml
new file mode 100644
index 0000000..8ec256a
--- /dev/null
+++ b/OpenPods/.idea/render.experimental.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/OpenPods/app/build.gradle b/OpenPods/app/build.gradle
index f201df8..4f1d9a2 100644
--- a/OpenPods/app/build.gradle
+++ b/OpenPods/app/build.gradle
@@ -1,15 +1,19 @@
apply plugin: 'com.android.application'
android {
- compileSdkVersion 28
+ compileSdkVersion 29
+ buildToolsVersion "29.0.3"
+
defaultConfig {
applicationId "com.dosse.airpods"
minSdkVersion 23
- targetSdkVersion 23
- versionCode 13
- versionName '1.3'
- testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+ targetSdkVersion 29
+ versionCode 14
+ versionName '1.4'
+
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
+
buildTypes {
release {
minifyEnabled false
@@ -21,9 +25,14 @@ android {
lintOptions {
abortOnError false
}
+ compileOptions {
+ sourceCompatibility = 1.8
+ targetCompatibility = 1.8
+ }
}
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
- implementation 'com.android.support:appcompat-v7:28.0.0'
+ implementation 'androidx.appcompat:appcompat:1.1.0'
+ implementation "androidx.preference:preference:1.1.1"
}
diff --git a/OpenPods/app/src/main/AndroidManifest.xml b/OpenPods/app/src/main/AndroidManifest.xml
index 21a6422..016e262 100644
--- a/OpenPods/app/src/main/AndroidManifest.xml
+++ b/OpenPods/app/src/main/AndroidManifest.xml
@@ -1,5 +1,6 @@
@@ -7,6 +8,7 @@
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+ android:exported="true"
+ tools:ignore="ExportedService" />
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/OpenPods/app/src/main/java/com/dosse/airpods/AboutActivity.java b/OpenPods/app/src/main/java/com/dosse/airpods/AboutActivity.java
index e5293a2..74fd9aa 100644
--- a/OpenPods/app/src/main/java/com/dosse/airpods/AboutActivity.java
+++ b/OpenPods/app/src/main/java/com/dosse/airpods/AboutActivity.java
@@ -2,17 +2,21 @@ package com.dosse.airpods;
import android.content.Intent;
import android.net.Uri;
-import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
-import android.view.View;
import android.webkit.WebView;
-import android.widget.Button;
import android.widget.Toast;
+import androidx.appcompat.app.AppCompatActivity;
+
public class AboutActivity extends AppCompatActivity {
+ final String githubURL = "https://github.com/adolfintel/OpenPods";
+ final String websiteURL = "https://fdossena.com/?p=openPods/index.frag";
+ final String donateURL = "https://paypal.me/sineisochronic";
+
+ @SuppressWarnings("DanglingJavadoc")
@Override
- protected void onCreate(Bundle savedInstanceState) {
+ protected void onCreate (Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_about);
@@ -32,26 +36,14 @@ public class AboutActivity extends AppCompatActivity {
((WebView)(findViewById(R.id.license))).loadUrl("file:///android_asset/license.html");
- ((Button)(findViewById(R.id.github))).setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("https://github.com/adolfintel/OpenPods")));
- }
- });
- ((Button)(findViewById(R.id.website))).setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("https://fdossena.com/?p=openPods/i.frag")));
- }
- });
- ((Button)(findViewById(R.id.donate))).setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("https://paypal.me/sineisochronic")));
- Toast.makeText(AboutActivity.this, "❤️", Toast.LENGTH_SHORT).show();
- }
+ findViewById(R.id.github).setOnClickListener(v -> startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(githubURL))));
+ findViewById(R.id.website).setOnClickListener(v -> startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(websiteURL))));
+ findViewById(R.id.donate).setOnClickListener(v -> {
+ startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(donateURL)));
+ Toast.makeText(AboutActivity.this, "❤️", Toast.LENGTH_SHORT).show();
});
//END OF LEGAL DANGER ZONE
}
+
}
diff --git a/OpenPods/app/src/main/java/com/dosse/airpods/IntroActivity.java b/OpenPods/app/src/main/java/com/dosse/airpods/IntroActivity.java
index b796db4..25d1fa7 100644
--- a/OpenPods/app/src/main/java/com/dosse/airpods/IntroActivity.java
+++ b/OpenPods/app/src/main/java/com/dosse/airpods/IntroActivity.java
@@ -1,69 +1,81 @@
package com.dosse.airpods;
import android.Manifest;
+import android.annotation.SuppressLint;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
-import android.os.Build;
import android.os.Bundle;
import android.os.PowerManager;
import android.provider.Settings;
-import android.support.v4.content.ContextCompat;
-import android.support.v7.app.AppCompatActivity;
-import android.view.View;
-import android.widget.Button;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.core.content.ContextCompat;
+
+import java.util.Objects;
import java.util.Timer;
import java.util.TimerTask;
public class IntroActivity extends AppCompatActivity {
- private Timer t;
+ private Timer timer;
+
+ @SuppressLint("BatteryLife")
@Override
- protected void onCreate(Bundle savedInstanceState) {
+ protected void onCreate (Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_intro);
- ((Button)findViewById(R.id.allowBtn)).setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View view) { //allow button clicked, ask for permissions
- requestPermissions(new String[]{Manifest.permission.ACCESS_COARSE_LOCATION}, 1); //location (for BLE)
- try { //run in background
- if(!getSystemService(PowerManager.class).isIgnoringBatteryOptimizations(getPackageName())) {
- Intent intent = new Intent();
- PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
- intent.setAction(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
- intent.setData(Uri.parse("package:" + getPackageName()));
- startActivity(intent);
- }
- } catch (Throwable t) {
+
+ // Allow button clicked, ask for permissions
+ findViewById(R.id.allowBtn).setOnClickListener(view -> {
+ requestPermissions(new String[] {Manifest.permission.ACCESS_COARSE_LOCATION}, 1); // Location (for BLE)
+ // Run in background
+ try {
+ if (!Objects.requireNonNull(getSystemService(PowerManager.class)).isIgnoringBatteryOptimizations(getPackageName())) {
+ Intent intent = new Intent();
+ getSystemService(Context.POWER_SERVICE);
+ intent.setAction(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
+ intent.setData(Uri.parse("package:" + getPackageName()));
+ startActivity(intent);
}
+ } catch (Throwable ignored) {
}
});
- //wait for permissions to be granted. When they are granted, go to MainActivity
- t= new Timer();
- t.scheduleAtFixedRate(new TimerTask() {
+
+ // Wait for permissions to be granted.
+ // When they are granted, go to MainActivity.
+ timer = new Timer();
+ timer.scheduleAtFixedRate(new TimerTask() {
@Override
- public void run() {
- boolean ok=true;
+ public void run () {
+ boolean ok = true;
+
try {
- if (!getSystemService(PowerManager.class).isIgnoringBatteryOptimizations(getPackageName())) ok = false;
- }catch(Throwable t){}
- if (ContextCompat.checkSelfPermission(IntroActivity.this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) ok=false;
- if(ok){
- t.cancel();
- Intent i=new Intent(IntroActivity.this,MainActivity.class);
- startActivity(i);
+ if (!Objects.requireNonNull(getSystemService(PowerManager.class)).isIgnoringBatteryOptimizations(getPackageName()))
+ ok = false;
+ } catch (Throwable ignored) {
+ }
+
+ if (ContextCompat.checkSelfPermission(IntroActivity.this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED)
+ ok = false;
+
+ if (ok) {
+ timer.cancel();
+ startActivity(new Intent(IntroActivity.this, MainActivity.class));
finish();
}
}
- },0,100);
+ }, 0, 100);
}
+ // Activity destroyed (or screen rotated). destroy the timer too
@Override
- protected void onDestroy() { //activity destroyed (or screen rotated). destroy the timer too
+ protected void onDestroy () {
super.onDestroy();
- if(t!=null) t.cancel();
+ if (timer != null)
+ timer.cancel();
}
+
}
diff --git a/OpenPods/app/src/main/java/com/dosse/airpods/MainActivity.java b/OpenPods/app/src/main/java/com/dosse/airpods/MainActivity.java
index 2b34c85..149635d 100644
--- a/OpenPods/app/src/main/java/com/dosse/airpods/MainActivity.java
+++ b/OpenPods/app/src/main/java/com/dosse/airpods/MainActivity.java
@@ -8,54 +8,73 @@ import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.PowerManager;
-import android.support.v4.content.ContextCompat;
-import android.support.v7.app.AppCompatActivity;
-import android.util.Log;
-import android.view.View;
-import android.widget.Button;
+import android.view.Menu;
+import android.view.MenuItem;
+
+import androidx.annotation.NonNull;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.core.content.ContextCompat;
+
+import java.util.Objects;
public class MainActivity extends AppCompatActivity {
@Override
- protected void onCreate(Bundle savedInstanceState) {
+ protected void onCreate (Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
- //check if Bluetooth LE is available on this device. If not, show an error
- BluetoothAdapter btAdapter=((BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE)).getAdapter();
- if(btAdapter==null||(btAdapter.isEnabled()&&btAdapter.getBluetoothLeScanner()==null)||(!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE))){
- Intent i=new Intent(this,NoBTActivity.class);
- startActivity(i);
+
+ // Check if Bluetooth LE is available on this device. If not, show an error
+ BluetoothAdapter btAdapter = ((BluetoothManager)Objects.requireNonNull(getSystemService(Context.BLUETOOTH_SERVICE))).getAdapter();
+ if (btAdapter == null || (btAdapter.isEnabled() && btAdapter.getBluetoothLeScanner() == null) || (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE))) {
+ startActivity(new Intent(MainActivity.this, NoBTActivity.class));
finish();
return;
}
- //check if all permissions have been granted
- boolean ok=true;
+
+ // Check if all permissions have been granted
+ boolean ok = true;
+
try {
- if (!getSystemService(PowerManager.class).isIgnoringBatteryOptimizations(getPackageName())) ok = false;
- }catch(Throwable t){}
- if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) ok=false;
- if(ok){
+ if (!Objects.requireNonNull(getSystemService(PowerManager.class)).isIgnoringBatteryOptimizations(getPackageName()))
+ ok = false;
+ } catch (Throwable ignored) {
+ }
+
+ if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED)
+ ok = false;
+
+ if (ok) {
Starter.startPodsService(getApplicationContext());
- }else{
- Intent i=new Intent(this,IntroActivity.class);
- startActivity(i);
+ } else {
+ startActivity(new Intent(MainActivity.this, IntroActivity.class));
finish();
}
- ((Button)(findViewById(R.id.settings))).setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View view) { //settings clicked
- Intent i=new Intent(MainActivity.this, SettingsActivity.class);
- startActivity(i);
- }
- });
}
@Override
- protected void onResume() {
+ protected void onResume () {
super.onResume();
- try{
+ try {
getApplicationContext().openFileInput("hidden").close();
finish();
- }catch (Throwable t){}
+ } catch (Throwable ignored) {
+ }
}
+
+ @Override
+ public boolean onCreateOptionsMenu (Menu menu) {
+ getMenuInflater().inflate(R.menu.menu_main, menu);
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected (@NonNull MenuItem item) {
+ if (item.getItemId() == R.id.menu_ab_settings) {
+ startActivity(new Intent(this, SettingsActivity.class)); // Settings icon clicked
+ return true;
+ }
+ return false;
+ }
+
}
diff --git a/OpenPods/app/src/main/java/com/dosse/airpods/NoBTActivity.java b/OpenPods/app/src/main/java/com/dosse/airpods/NoBTActivity.java
index 61d7f9f..6c4f353 100644
--- a/OpenPods/app/src/main/java/com/dosse/airpods/NoBTActivity.java
+++ b/OpenPods/app/src/main/java/com/dosse/airpods/NoBTActivity.java
@@ -1,13 +1,15 @@
package com.dosse.airpods;
-import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
+import androidx.appcompat.app.AppCompatActivity;
+
public class NoBTActivity extends AppCompatActivity {
@Override
- protected void onCreate(Bundle savedInstanceState) {
+ protected void onCreate (Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_no_bt);
}
+
}
diff --git a/OpenPods/app/src/main/java/com/dosse/airpods/PodsService.java b/OpenPods/app/src/main/java/com/dosse/airpods/PodsService.java
index 93cda2b..5536cca 100644
--- a/OpenPods/app/src/main/java/com/dosse/airpods/PodsService.java
+++ b/OpenPods/app/src/main/java/com/dosse/airpods/PodsService.java
@@ -26,29 +26,32 @@ import android.os.IBinder;
import android.os.ParcelUuid;
import android.os.SystemClock;
import android.provider.Settings;
-import android.support.v4.app.NotificationCompat;
import android.util.Log;
import android.view.View;
import android.widget.RemoteViews;
+
+import androidx.core.app.NotificationCompat;
+import androidx.preference.PreferenceManager;
+
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import java.util.Objects;
/**
* This is the class that does most of the work. It has 3 functions:
* - Detect when AirPods are detected
* - Receive beacons from AirPods and decode them (easier said than done thanks to google's autism)
* - Display the notification with the status
- *
*/
public class PodsService extends Service {
- private static final boolean ENABLE_LOGGING=BuildConfig.DEBUG; //Log is only displayed if this is a debug build, not release
+ private static final boolean ENABLE_LOGGING = BuildConfig.DEBUG; // Log is only displayed if this is a debug build, not release
private static BluetoothLeScanner btScanner;
- private static int leftStatus=15, rightStatus=15, caseStatus=15;
- private static boolean chargeL=false, chargeR=false, chargeCase=false;
- private static final String MODEL_AIRPODS_NORMAL="airpods12", MODEL_AIRPODS_PRO="airpodspro";
- private static String model=MODEL_AIRPODS_NORMAL;
+ private static int leftStatus = 15, rightStatus = 15, caseStatus = 15;
+ private static boolean chargeL = false, chargeR = false, chargeCase = false;
+ private static final String MODEL_AIRPODS_NORMAL = "airpods12", MODEL_AIRPODS_PRO = "airpodspro";
+ private static String model = MODEL_AIRPODS_NORMAL;
/**
* The following method (startAirPodsScanner) creates a bluetoth LE scanner.
@@ -56,15 +59,16 @@ public class PodsService extends Service {
* - Check that the beacon comes from something that looks like a pair of AirPods
* - Make sure that it is YOUR pair of AirPods
* - Decode the beacon to get the status
- *
+ *
* On a normal OS, we would use the bluetooth address of the device to filter out beacons from other devices.
- * UNFORTUNATELY, someone at google was so concerned about privacy (yea, as if they give a shit) that he decided it was a good idea to not allow access to the bluetooth address of incoming BLE beacons. As a result, we have no reliable way to make sure that the beacon comes from YOUR airpods and not the guy sitting next to you on the bus.
+ * UNFORTUNATELY, someone at google was so concerned about privacy (yea, as if they give a shit) that he decided it was a good idea to not allow access to the bluetooth address of incoming BLE beacons.
+ * As a result, we have no reliable way to make sure that the beacon comes from YOUR airpods and not the guy sitting next to you on the bus.
* What we did to workaround this issue is this:
* - When a beacon arrives that looks like a pair of AirPods, look at the other beacons received in the last 10 seconds and get the strongest one
* - If the strongest beacon's fake address is the same as this, use this beacon; otherwise use the strongest beacon
* - Filter for signals stronger than -60db
* - Decode...
- *
+ *
* Decoding the beacon:
* This was done through reverse engineering. Hopefully it's correct.
* - The beacon coming from a pair of AirPods contains a manufacturer specific data field n°76 of 27 bytes
@@ -73,100 +77,141 @@ public class PodsService extends Service {
* - The 15th character in the string represents the charge of the case. Values between 0 and 10 are battery 0-100%; Value 15 means it's disconnected
* - The 14th character in the string represents the "in charge" status. Bit 0 (LSB) is the left pod; Bit 1 is the right pod; Bit 2 is the case. Bit 3 might be case open/closed but I'm not sure and it's not used
* - The 7th character in the string represents the AirPods model (E=AirPods pro)
- *
+ *
* After decoding a beacon, the status is written to leftStatus, rightStatus, caseStatus, chargeL, chargeR, chargeCase so that the NotificationThread can use the information
- *
*/
- private static ArrayList recentBeacons=new ArrayList<>();
- private static final long RECENT_BEACONS_MAX_T_NS=10000000000L; //10s
- private void startAirPodsScanner() {
+ private static ArrayList recentBeacons = new ArrayList<>();
+ private static final long RECENT_BEACONS_MAX_T_NS = 10000000000L; //10s
+
+ private void startAirPodsScanner () {
try {
- if(ENABLE_LOGGING) Log.d(TAG,"START SCANNER");
- SharedPreferences prefs=getSharedPreferences("openpods",MODE_PRIVATE);
- BluetoothManager btManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
+ if (ENABLE_LOGGING)
+ Log.d(TAG, "START SCANNER");
+
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
+ BluetoothManager btManager = (BluetoothManager)getSystemService(Context.BLUETOOTH_SERVICE);
+ assert btManager != null;
BluetoothAdapter btAdapter = btManager.getAdapter();
- if(prefs.getBoolean("batterySaver",false)) {
+
+ if (prefs.getBoolean("batterySaver", false)) {
if (btScanner != null) {
btScanner.stopScan(new ScanCallback() {
@Override
- public void onScanResult(int callbackType, ScanResult result) {
+ public void onScanResult (int callbackType, ScanResult result) {
}
});
}
}
+
btScanner = btAdapter.getBluetoothLeScanner();
- if (btAdapter == null) throw new Exception("No BT");
- if (!btAdapter.isEnabled()) throw new Exception("BT Off");
+
+ if (!btAdapter.isEnabled())
+ throw new Exception("BT Off");
List filters = getScanFilters();
ScanSettings settings;
- if(prefs.getBoolean("batterySaver",false)) {
+
+ if (prefs.getBoolean("batterySaver", false))
settings = new ScanSettings.Builder().setScanMode(0).setReportDelay(0).build();
- }else{
+ else
settings = new ScanSettings.Builder().setScanMode(2).setReportDelay(2).build();
- }
btScanner.startScan(
filters,
settings,
new ScanCallback() {
- @Override
- public void onBatchScanResults(List scanResults) {
- for (ScanResult result : scanResults) onScanResult(-1, result);
- super.onBatchScanResults(scanResults);
- }
+ @Override
+ public void onBatchScanResults (List scanResults) {
+ for (ScanResult result : scanResults)
+ onScanResult(-1, result);
+ super.onBatchScanResults(scanResults);
+ }
- @Override
- public void onScanResult(int callbackType, ScanResult result) {
- try {
- byte[] data = result.getScanRecord().getManufacturerSpecificData(76);
- if (data == null||data.length!=27) return;
- recentBeacons.add(result);
- if(ENABLE_LOGGING) Log.d(TAG,""+result.getRssi()+"db");
- if(ENABLE_LOGGING) Log.d(TAG, decodeHex(data));
- ScanResult strongestBeacon=null;
- for(int i=0;iRECENT_BEACONS_MAX_T_NS){
- recentBeacons.remove(i--);
- continue;
+ @Override
+ public void onScanResult (int callbackType, ScanResult result) {
+ try {
+ byte[] data = Objects.requireNonNull(result.getScanRecord()).getManufacturerSpecificData(76);
+
+ if (data == null || data.length != 27)
+ return;
+
+ recentBeacons.add(result);
+
+ if (ENABLE_LOGGING) {
+ Log.d(TAG, "" + result.getRssi() + "db");
+ Log.d(TAG, decodeHex(data));
+ }
+
+ ScanResult strongestBeacon = null;
+ for (int i = 0; i < recentBeacons.size(); i++) {
+ if (SystemClock.elapsedRealtimeNanos() - recentBeacons.get(i).getTimestampNanos() > RECENT_BEACONS_MAX_T_NS) {
+ recentBeacons.remove(i--);
+ continue;
+ }
+ if (strongestBeacon == null || strongestBeacon.getRssi() < recentBeacons.get(i).getRssi())
+ strongestBeacon = recentBeacons.get(i);
+ }
+
+ if (strongestBeacon != null && strongestBeacon.getDevice().getAddress().equals(result.getDevice().getAddress()))
+ strongestBeacon = result;
+
+ result = strongestBeacon;
+ assert result != null;
+ if (result.getRssi() < -60)
+ return;
+
+ String a = decodeHex(Objects.requireNonNull(Objects.requireNonNull(result.getScanRecord()).getManufacturerSpecificData(76)));
+ boolean flip = isFlipped(a);
+
+ String leftAirpodStr; // Left airpod (0-10 batt; 15=disconnected)
+ String rightAirpodStr; // Right airpod (0-10 batt; 15=disconnected)
+
+ if (flip) {
+ leftAirpodStr = "" + a.charAt(12);
+ rightAirpodStr = "" + a.charAt(13);
+ } else {
+ leftAirpodStr = "" + a.charAt(13);
+ rightAirpodStr = "" + a.charAt(12);
+ }
+
+ String caseStr = "" + a.charAt(15); // Case (0-10 batt; 15=disconnected)
+ String chargeStatusStr = "" + a.charAt(14); // Charge status (bit 0=left; bit 1=right; bit 2=case)
+
+ leftStatus = Integer.parseInt(leftAirpodStr, 16);
+ rightStatus = Integer.parseInt(rightAirpodStr, 16);
+ caseStatus = Integer.parseInt(caseStr, 16);
+
+ int chargeStatus = Integer.parseInt(chargeStatusStr, 16);
+
+ if (flip) {
+ chargeL = (chargeStatus & 0b00000010) != 0;
+ chargeR = (chargeStatus & 0b00000001) != 0;
+ } else {
+ chargeL = (chargeStatus & 0b00000001) != 0;
+ chargeR = (chargeStatus & 0b00000010) != 0;
+ }
+
+ chargeCase = (chargeStatus & 0b00000100) != 0;
+
+ if (a.charAt(7) == 'E')
+ model = MODEL_AIRPODS_PRO;
+ else
+ model = MODEL_AIRPODS_NORMAL; // Detect if these are AirPods pro or regular ones
+
+ lastSeenConnected = System.currentTimeMillis();
+ } catch (Throwable t) {
+ if (ENABLE_LOGGING)
+ Log.d(TAG, "" + t);
}
- if(strongestBeacon==null||strongestBeacon.getRssi() getScanFilters() {
+ private List getScanFilters () {
byte[] manufacturerData = new byte[27];
byte[] manufacturerDataMask = new byte[27];
@@ -178,68 +223,86 @@ public class PodsService extends Service {
Builder builder = new Builder();
builder.setManufacturerData(76, manufacturerData, manufacturerDataMask);
+
return Collections.singletonList(builder.build());
}
- private void stopAirPodsScanner(){
- try{
- if(btScanner!=null){
- if(ENABLE_LOGGING) Log.d(TAG,"STOP SCANNER");
+ private void stopAirPodsScanner () {
+ try {
+ if (btScanner != null) {
+ if (ENABLE_LOGGING)
+ Log.d(TAG, "STOP SCANNER");
+
btScanner.stopScan(new ScanCallback() {
@Override
- public void onScanResult(int callbackType, ScanResult result) {}});
+ public void onScanResult (int callbackType, ScanResult result) {
+ }
+ });
}
- leftStatus=15; rightStatus=15; caseStatus=15;
- }catch (Throwable t){}
+ leftStatus = 15;
+ rightStatus = 15;
+ caseStatus = 15;
+ } catch (Throwable ignored) {
+ }
}
- private final char[] hexCharset = {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};
- private String decodeHex(byte[] bArr) {
+ private final char[] hexCharset = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
+
+ private String decodeHex (byte[] bArr) {
char[] ret = new char[bArr.length * 2];
+
for (int i = 0; i < bArr.length; i++) {
int b = bArr[i] & 0xFF;
- ret[i*2] = hexCharset[b >>> 4];
- ret[i*2+1] = hexCharset[b & 0x0F];
+ ret[i * 2] = hexCharset[b >>> 4];
+ ret[i * 2 + 1] = hexCharset[b & 0x0F];
}
+
return new String(ret);
}
- private boolean isFlipped(String str) {
- return (Integer.toString(Integer.parseInt(""+str.charAt(10),16)+0x10,2)).charAt(3)=='0';
+ private boolean isFlipped (String str) {
+ return (Integer.toString(Integer.parseInt("" + str.charAt(10), 16) + 0x10, 2)).charAt(3) == '0';
}
/**
* The following class is a thread that manages the notification while your AirPods are connected.
- *
+ *
* 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. Battery% is hidden if we didn't receive a beacon for 30 seconds (screen off for a while)
- *
+ *
* 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.
- *
*/
- private static NotificationThread n=null;
- private static final String TAG="AirPods";
- private static long lastSeenConnected=0;
- private static final long TIMEOUT_CONNECTED=30000;
- private static boolean maybeConnected =false;
- private class NotificationThread extends Thread{
- private boolean isLocationEnabled(){
- if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.P){
- LocationManager service = (LocationManager) getSystemService(getApplicationContext().LOCATION_SERVICE);
- return service!=null&&service.isLocationEnabled();
- }else{
+ private static NotificationThread n = null;
+ private static final String TAG = "AirPods";
+ private static long lastSeenConnected = 0;
+ private static final long TIMEOUT_CONNECTED = 30000;
+ private static boolean maybeConnected = false;
+
+ private class NotificationThread extends Thread {
+
+ private boolean isLocationEnabled () {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
+ getApplicationContext();
+ LocationManager service = (LocationManager)getSystemService(LOCATION_SERVICE);
+ return service != null && service.isLocationEnabled();
+ } else {
try {
return Settings.Secure.getInt(getContentResolver(), Settings.Secure.LOCATION_MODE) != Settings.Secure.LOCATION_MODE_OFF;
- }catch(Throwable t){
+ } catch (Throwable t) {
return true;
}
}
}
+
private NotificationManager mNotifyManager;
- public NotificationThread(){
- mNotifyManager=(NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { //on oreo and newer, create a notification channel
+
+ @SuppressWarnings("WeakerAccess")
+ public NotificationThread () {
+ mNotifyManager = (NotificationManager)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);
+ channel.setSound(null, null);
channel.enableVibration(false);
channel.enableLights(false);
channel.setShowBadge(true);
@@ -247,48 +310,61 @@ public class PodsService extends Service {
mNotifyManager.createNotificationChannel(channel);
}
}
- public void run(){
- boolean notificationShowing=false;
- RemoteViews notificationBig=new RemoteViews(getPackageName(),R.layout.status_big);
- RemoteViews notificationSmall=new RemoteViews(getPackageName(),R.layout.status_small);
- RemoteViews locationDisabledBig=new RemoteViews(getPackageName(),R.layout.location_disabled_big);
- RemoteViews locationDisabledSmall=new RemoteViews(getPackageName(),R.layout.location_disabled_small);
- NotificationCompat.Builder mBuilder=new NotificationCompat.Builder(PodsService.this,TAG);
+
+ public void run () {
+ boolean notificationShowing = false;
+ String compat = getPackageManager().getInstallerPackageName(getPackageName());
+
+ RemoteViews notificationBig = new RemoteViews(getPackageName(), R.layout.status_big);
+ RemoteViews notificationSmall = new RemoteViews(getPackageName(), R.layout.status_small);
+ RemoteViews locationDisabledBig = new RemoteViews(getPackageName(), R.layout.location_disabled_big);
+ RemoteViews locationDisabledSmall = new RemoteViews(getPackageName(), R.layout.location_disabled_small);
+
+ NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(PodsService.this, TAG);
mBuilder.setShowWhen(false);
mBuilder.setOngoing(true);
mBuilder.setSmallIcon(R.mipmap.notification_icon);
- for(;;){
- if(maybeConnected &&!(leftStatus==15&&rightStatus==15&&caseStatus==15)/*&&System.currentTimeMillis()-lastSeenConnected=29) { //apparently this restriction was removed in android Q
+
+ // Apparently this restriction was removed in android Q
+ if (isLocationEnabled() || Build.VERSION.SDK_INT >= 29) {
mBuilder.setCustomContentView(notificationSmall);
mBuilder.setCustomBigContentView(notificationBig);
- }else{
+ } else {
mBuilder.setCustomContentView(locationDisabledSmall);
mBuilder.setCustomBigContentView(locationDisabledBig);
}
- if(notificationShowing){
- if(ENABLE_LOGGING) Log.d(TAG,"Left: "+leftStatus+(chargeL?"+":"")+" "+"Right: "+rightStatus+(chargeR?"+":"")+" "+"Case: "+caseStatus+(chargeCase?"+":"")+" "+"Model: "+model);
- if(model.equals(MODEL_AIRPODS_NORMAL)){
+
+ if (notificationShowing) {
+ if (ENABLE_LOGGING)
+ Log.d(TAG, "Left: " + leftStatus + (chargeL ? "+" : "") + " Right: " + rightStatus + (chargeR ? "+" : "") + " Case: " + caseStatus + (chargeCase ? "+" : "") + " Model: " + model);
+
+ if (model.equals(MODEL_AIRPODS_NORMAL)) {
notificationBig.setImageViewResource(R.id.leftPodImg, leftStatus <= 10 ? R.drawable.left_pod : R.drawable.left_pod_disconnected);
notificationBig.setImageViewResource(R.id.rightPodImg, rightStatus <= 10 ? R.drawable.right_pod : R.drawable.right_pod_disconnected);
notificationBig.setImageViewResource(R.id.podCaseImg, caseStatus <= 10 ? R.drawable.pod_case : R.drawable.pod_case_disconnected);
notificationSmall.setImageViewResource(R.id.leftPodImg, leftStatus <= 10 ? R.drawable.left_pod : R.drawable.left_pod_disconnected);
notificationSmall.setImageViewResource(R.id.rightPodImg, rightStatus <= 10 ? R.drawable.right_pod : R.drawable.right_pod_disconnected);
notificationSmall.setImageViewResource(R.id.podCaseImg, caseStatus <= 10 ? R.drawable.pod_case : R.drawable.pod_case_disconnected);
- }else if(model.equals(MODEL_AIRPODS_PRO)){
+ } else if (model.equals(MODEL_AIRPODS_PRO)) {
notificationBig.setImageViewResource(R.id.leftPodImg, leftStatus <= 10 ? R.drawable.left_podpro : R.drawable.left_podpro_disconnected);
notificationBig.setImageViewResource(R.id.rightPodImg, rightStatus <= 10 ? R.drawable.right_podpro : R.drawable.right_podpro_disconnected);
notificationBig.setImageViewResource(R.id.podCaseImg, caseStatus <= 10 ? R.drawable.podpro_case : R.drawable.podpro_case_disconnected);
@@ -296,65 +372,106 @@ public class PodsService extends Service {
notificationSmall.setImageViewResource(R.id.rightPodImg, rightStatus <= 10 ? R.drawable.right_podpro : R.drawable.right_podpro_disconnected);
notificationSmall.setImageViewResource(R.id.podCaseImg, caseStatus <= 10 ? R.drawable.podpro_case : R.drawable.podpro_case_disconnected);
}
- if(System.currentTimeMillis()-lastSeenConnected {
+ startActivity(new Intent(context, AboutActivity.class));
+ return true;
+ });
+
+ mHideAppPreference = getPreferenceManager().findPreference("hideApp");
+ Objects.requireNonNull(mHideAppPreference).setOnPreferenceClickListener(preference -> {
+ PackageManager p = requireContext().getPackageManager();
+ p.setComponentEnabledSetting(new ComponentName(context, MainActivity.class), PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP);
+ Toast.makeText(context, getString(R.string.hideClicked), Toast.LENGTH_LONG).show();
+
+ try {
+ context.openFileOutput("hidden", Context.MODE_PRIVATE).close();
+ } catch (Throwable ignored) {
+ }
+
+ enableDisableOptions();
+ requireActivity().finish();
+ return true;
+ });
+
+ enableDisableOptions();
+ }
+
+ private void enableDisableOptions () {
+ try {
+ context.openFileInput("hidden").close();
+ mHideAppPreference.setEnabled(false);
+ } catch (Throwable ignored) {
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/OpenPods/app/src/main/java/com/dosse/airpods/Starter.java b/OpenPods/app/src/main/java/com/dosse/airpods/Starter.java
index 73a01e3..1d490b3 100644
--- a/OpenPods/app/src/main/java/com/dosse/airpods/Starter.java
+++ b/OpenPods/app/src/main/java/com/dosse/airpods/Starter.java
@@ -3,25 +3,35 @@ package com.dosse.airpods;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
-import android.os.Build;
-import android.util.Log;
+
+import java.util.Objects;
/**
* A simple starter class that starts the service when the device is booted, or after an update
*/
public class Starter extends BroadcastReceiver {
+
@Override
- public void onReceive(Context context, Intent intent) {
- startPodsService(context);
+ public void onReceive (Context context, Intent intent) {
+ switch (Objects.requireNonNull(intent.getAction())) {
+ case Intent.ACTION_MY_PACKAGE_REPLACED:
+ case Intent.ACTION_BOOT_COMPLETED:
+ startPodsService(context);
+ break;
+ }
}
- public static final void startPodsService(Context context){
+ public static void startPodsService (Context context) {
context.startService(new Intent(context, PodsService.class));
}
- public static final void restartPodsService(Context context){
+ public static void restartPodsService (Context context) {
context.stopService(new Intent(context, PodsService.class));
- try{Thread.sleep(500);}catch(Throwable t){}
+ try {
+ Thread.sleep(500);
+ } catch (Throwable ignored) {
+ }
context.startService(new Intent(context, PodsService.class));
}
+
}
diff --git a/OpenPods/app/src/main/res/drawable/ic_battery_alert_red_24dp.xml b/OpenPods/app/src/main/res/drawable/ic_battery_alert_red_24dp.xml
new file mode 100644
index 0000000..aa69d6b
--- /dev/null
+++ b/OpenPods/app/src/main/res/drawable/ic_battery_alert_red_24dp.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/OpenPods/app/src/main/res/drawable/ic_battery_charging_full_green_24dp.xml b/OpenPods/app/src/main/res/drawable/ic_battery_charging_full_green_24dp.xml
new file mode 100644
index 0000000..3d6a9a5
--- /dev/null
+++ b/OpenPods/app/src/main/res/drawable/ic_battery_charging_full_green_24dp.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/OpenPods/app/src/main/res/drawable/ic_check_dgrey_48dp.xml b/OpenPods/app/src/main/res/drawable/ic_check_dgrey_48dp.xml
new file mode 100644
index 0000000..13c3931
--- /dev/null
+++ b/OpenPods/app/src/main/res/drawable/ic_check_dgrey_48dp.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/OpenPods/app/src/main/res/drawable/ic_menu_settings_white_30dp.xml b/OpenPods/app/src/main/res/drawable/ic_menu_settings_white_30dp.xml
new file mode 100644
index 0000000..02be8c7
--- /dev/null
+++ b/OpenPods/app/src/main/res/drawable/ic_menu_settings_white_30dp.xml
@@ -0,0 +1,11 @@
+
+
+
+
diff --git a/OpenPods/app/src/main/res/drawable/ic_x_dgrey_48dp.xml b/OpenPods/app/src/main/res/drawable/ic_x_dgrey_48dp.xml
new file mode 100644
index 0000000..20ea726
--- /dev/null
+++ b/OpenPods/app/src/main/res/drawable/ic_x_dgrey_48dp.xml
@@ -0,0 +1,11 @@
+
+
+
+
\ No newline at end of file
diff --git a/OpenPods/app/src/main/res/drawable/icon.png b/OpenPods/app/src/main/res/drawable/icon.png
new file mode 100644
index 0000000..1e81c96
Binary files /dev/null and b/OpenPods/app/src/main/res/drawable/icon.png differ
diff --git a/OpenPods/app/src/main/res/drawable/tick.png b/OpenPods/app/src/main/res/drawable/tick.png
deleted file mode 100644
index 27ee5a8..0000000
Binary files a/OpenPods/app/src/main/res/drawable/tick.png and /dev/null differ
diff --git a/OpenPods/app/src/main/res/drawable/x.png b/OpenPods/app/src/main/res/drawable/x.png
deleted file mode 100644
index 6ad6fa0..0000000
Binary files a/OpenPods/app/src/main/res/drawable/x.png and /dev/null differ
diff --git a/OpenPods/app/src/main/res/layout-land/activity_about.xml b/OpenPods/app/src/main/res/layout-land/activity_about.xml
new file mode 100644
index 0000000..09808d7
--- /dev/null
+++ b/OpenPods/app/src/main/res/layout-land/activity_about.xml
@@ -0,0 +1,85 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/OpenPods/app/src/main/res/layout-land/activity_main.xml b/OpenPods/app/src/main/res/layout-land/activity_main.xml
new file mode 100644
index 0000000..0850710
--- /dev/null
+++ b/OpenPods/app/src/main/res/layout-land/activity_main.xml
@@ -0,0 +1,54 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/OpenPods/app/src/main/res/layout/activity_about.xml b/OpenPods/app/src/main/res/layout/activity_about.xml
index 48c4a23..67df7da 100644
--- a/OpenPods/app/src/main/res/layout/activity_about.xml
+++ b/OpenPods/app/src/main/res/layout/activity_about.xml
@@ -3,17 +3,19 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
- tools:context=".AboutActivity"
- android:padding="12dp"
android:background="#FFFFFF"
- >
+ android:paddingLeft="12dp"
+ android:paddingTop="12dp"
+ android:paddingRight="12dp"
+ tools:context=".AboutActivity"
+ tools:ignore="ContentDescription,ButtonStyle">
+ android:layout_marginBottom="16dp"
+ android:src="@drawable/logo" />
+ android:gravity="center"
+ android:text="@string/about_dev"
+ android:textColor="#404040" />
+ android:gravity="center">
+
+ android:text="@string/website" />
+
+ android:text="@string/github" />
+
+ android:text="@string/donate" />
+
+ android:layout_below="@id/buttons" />
diff --git a/OpenPods/app/src/main/res/layout/activity_intro.xml b/OpenPods/app/src/main/res/layout/activity_intro.xml
index a030b46..395827f 100644
--- a/OpenPods/app/src/main/res/layout/activity_intro.xml
+++ b/OpenPods/app/src/main/res/layout/activity_intro.xml
@@ -1,59 +1,51 @@
-
+
-
-
+ android:orientation="vertical"
+ android:padding="12dp">
+ android:src="@drawable/icon"
+ tools:ignore="ContentDescription" />
+
+ android:text="@string/intro2" />
+ android:text="@string/introAllow" />
-
+
-
-
+
diff --git a/OpenPods/app/src/main/res/layout/activity_main.xml b/OpenPods/app/src/main/res/layout/activity_main.xml
index df4c551..5c178ce 100644
--- a/OpenPods/app/src/main/res/layout/activity_main.xml
+++ b/OpenPods/app/src/main/res/layout/activity_main.xml
@@ -1,58 +1,54 @@
-
+ android:padding="16dp"
+ tools:ignore="ContentDescription">
-
+
+ android:scaleType="fitCenter"
+ android:scaleX="1.1"
+ android:scaleY="1.1"
+ android:src="@drawable/ic_check_dgrey_48dp" />
+
+ android:layout_marginBottom="36dp"
+ android:text="@string/main_description"
+ android:textAppearance="@android:style/TextAppearance.DeviceDefault.Large" />
+
+ android:layout_marginBottom="8dp"
+ android:text="@string/supportedDevicesTitle"
+ android:textSize="17sp" />
+
-
+ android:text="@string/supportedDevicesItems" />
-
+
-
+
+
+
diff --git a/OpenPods/app/src/main/res/layout/activity_no_bt.xml b/OpenPods/app/src/main/res/layout/activity_no_bt.xml
index eafc999..4d56606 100644
--- a/OpenPods/app/src/main/res/layout/activity_no_bt.xml
+++ b/OpenPods/app/src/main/res/layout/activity_no_bt.xml
@@ -1,32 +1,35 @@
-
+
-
+ android:orientation="vertical"
+ android:padding="12dp">
+ android:scaleType="fitCenter"
+ android:scaleX="1.6"
+ android:scaleY="1.6"
+ android:src="@drawable/ic_x_dgrey_48dp"
+ tools:ignore="ContentDescription" />
+ android:text="@string/noBtText"
+ android:textColor="#000"
+ android:textSize="15sp"
+ android:textStyle="" />
-
+
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/OpenPods/app/src/main/res/layout/activity_settings.xml b/OpenPods/app/src/main/res/layout/activity_settings.xml
index 33cc36a..2d41426 100644
--- a/OpenPods/app/src/main/res/layout/activity_settings.xml
+++ b/OpenPods/app/src/main/res/layout/activity_settings.xml
@@ -1,8 +1,7 @@
-
-
-
\ No newline at end of file
+ tools:context=".SettingsActivity" />
\ No newline at end of file
diff --git a/OpenPods/app/src/main/res/layout/location_disabled_big.xml b/OpenPods/app/src/main/res/layout/location_disabled_big.xml
index 10ba2a6..ef3cbd7 100644
--- a/OpenPods/app/src/main/res/layout/location_disabled_big.xml
+++ b/OpenPods/app/src/main/res/layout/location_disabled_big.xml
@@ -1,18 +1,15 @@
+ android:padding="12dp">
+ android:textColor="@color/textColorPrimary" />
diff --git a/OpenPods/app/src/main/res/layout/location_disabled_small.xml b/OpenPods/app/src/main/res/layout/location_disabled_small.xml
index b623f52..bd4c2bb 100644
--- a/OpenPods/app/src/main/res/layout/location_disabled_small.xml
+++ b/OpenPods/app/src/main/res/layout/location_disabled_small.xml
@@ -1,18 +1,15 @@
+ android:padding="12dp">
+ android:textColor="@color/textColorPrimary" />
diff --git a/OpenPods/app/src/main/res/layout/status_big.xml b/OpenPods/app/src/main/res/layout/status_big.xml
index 36072bb..b245565 100644
--- a/OpenPods/app/src/main/res/layout/status_big.xml
+++ b/OpenPods/app/src/main/res/layout/status_big.xml
@@ -1,39 +1,43 @@
-
+ android:padding="12dp"
+ tools:ignore="RtlHardcoded,ContentDescription">
+ android:layout_alignParentLeft="true"
+ android:layout_alignParentTop="true">
+ android:src="@drawable/left_pod" />
+ android:textColor="@color/textColorPrimary" />
+
+
+ android:indeterminate="true" />
@@ -59,19 +62,25 @@
android:layout_width="40dp"
android:layout_height="90dp"
android:layout_centerHorizontal="true"
- android:src="@drawable/right_pod"
android:layout_marginBottom="6dp"
- />
+ android:src="@drawable/right_pod" />
+ android:textColor="@color/textColorPrimary" />
+
+
+ android:indeterminate="true" />
@@ -97,19 +105,25 @@
android:layout_width="80dp"
android:layout_height="90dp"
android:layout_centerHorizontal="true"
- android:src="@drawable/pod_case"
android:layout_marginBottom="6dp"
- />
+ android:src="@drawable/pod_case" />
+ android:textColor="@color/textColorPrimary" />
+
+
+ android:indeterminate="true" />
diff --git a/OpenPods/app/src/main/res/layout/status_small.xml b/OpenPods/app/src/main/res/layout/status_small.xml
index b513de4..2efc4d3 100644
--- a/OpenPods/app/src/main/res/layout/status_small.xml
+++ b/OpenPods/app/src/main/res/layout/status_small.xml
@@ -1,37 +1,40 @@
+ android:padding="12dp"
+ tools:ignore="RtlHardcoded,ContentDescription">
+ android:layout_alignParentLeft="true"
+ android:layout_alignParentTop="true">
+ android:src="@drawable/left_pod" />
+ android:textColor="@color/textColorPrimary" />
+
+
+ android:indeterminate="true" />
+ android:layout_toRightOf="@id/leftPod">
+ android:src="@drawable/right_pod" />
+ android:textColor="@color/textColorPrimary" />
+
+
+ android:indeterminate="true" />
+ android:layout_toRightOf="@id/rightPod">
+ android:src="@drawable/pod_case" />
+ android:textColor="@color/textColorPrimary" />
+
+
+ android:indeterminate="true" />
-
diff --git a/OpenPods/app/src/main/res/menu/menu_main.xml b/OpenPods/app/src/main/res/menu/menu_main.xml
new file mode 100644
index 0000000..46ab068
--- /dev/null
+++ b/OpenPods/app/src/main/res/menu/menu_main.xml
@@ -0,0 +1,13 @@
+
+
\ No newline at end of file
diff --git a/OpenPods/app/src/main/res/values-de/strings.xml b/OpenPods/app/src/main/res/values-de/strings.xml
index e69d692..e204964 100644
--- a/OpenPods/app/src/main/res/values-de/strings.xml
+++ b/OpenPods/app/src/main/res/values-de/strings.xml
@@ -3,21 +3,22 @@
Diese App erlaubt es dir, den Status deiner AirPods auf einem beliebigen Android-Gerät zu überprüfenFür den Dienst benötigen wir die Berechtigung zur Verwendung von Bluetooth LE (Standort) und die Berechtigung zum Betrieb im HintergrundErlauben
- Die App zeigt eine Benachrichtigung, wenn deine AirPods verbunden sind
- Du kannst die App jetzt verstecken, wenn du möchtest
+ Die App zeigt eine Benachrichtigung, wenn deine AirPods verbunden sind
+ Du kannst die App jetzt verstecken, wenn du möchtestEinstellungenEs sieht so aus, als hätte dieses Gerät kein Bluetooth LEDu musst Standortinformationen aktivieren, damit OpenPods den Status deiner AirPods auslesen kann
- Unterstützte Geräte:\n• Apple AirPods 1\n• Apple AirPods 2\n• Apple AirPods Pro
+ Unterstützte Geräte:
+ • Apple AirPods 1st gen\n• Apple AirPods 2nd gen\n• Apple AirPods Pro
- App verstecken
- Das Icon wird bald verschwindenStromsparmodus (nicht empfohlen)Aktiviere diesen Modus, wenn Bluetooth zu viel Batterieleistung erfordert
+ App verstecken
+ Das Icon wird bald verschwindenÜber
- Entwickelt von Federico Dossena
+ Entwickelt von Federico DossenaWebseiteGithubSpenden
diff --git a/OpenPods/app/src/main/res/values-es/strings.xml b/OpenPods/app/src/main/res/values-es/strings.xml
index 3b66438..230b8ff 100644
--- a/OpenPods/app/src/main/res/values-es/strings.xml
+++ b/OpenPods/app/src/main/res/values-es/strings.xml
@@ -1,22 +1,24 @@
+
Esta aplicación te permite ver el estado de tus AirPods en cualquier dispositivo Android.Para operar necesitamos permiso para usar Bluetooth LE (Localización) y permiso para ejecutar la app en segundo plano.Permitir
- Esta aplicación mostrará una notificación cuando tus AirPods estén conectados.
- Puedes ocultar la app si lo prefieres.
+ Esta aplicación mostrará una notificación cuando tus AirPods estén conectados.
+ Puedes ocultar la app si lo prefieres.AjustesParece que éste dispositivo no tiene Bluetooth LE.Necesitas activar la Localización para permitir que OpenPods lea el estado.
- Dispositivos compatibles:\n• Apple AirPods 1ª generación\n• Apple AirPods 2ª generación\n• Apple AirPods Pro
+ Dispositivos compatibles:
+ • Apple AirPods 1ª generación\n• Apple AirPods 2ª generación\n• Apple AirPods Pro
- Ocultar app
- El icono desaparecerá prontoAhorro de energía (no recomendado)Activa ésta opción si el Bluetooth consume mucha batería.
+ Ocultar app
+ El icono desaparecerá prontoAcerca de
- Desarrollado por Federico Dossena
+ Desarrollado por Federico DossenaPágina webGithubDonar
diff --git a/OpenPods/app/src/main/res/values-it/strings.xml b/OpenPods/app/src/main/res/values-it/strings.xml
index 394cd05..646ffc5 100644
--- a/OpenPods/app/src/main/res/values-it/strings.xml
+++ b/OpenPods/app/src/main/res/values-it/strings.xml
@@ -3,21 +3,22 @@
Questa app consente di monitorare lo stato delle tue AirPods su AndroidPer funzionare, abbiamo bisogno dei permessi per Bluetooth LE (Posizione) e di esecuzione in backgroundConsenti
- L\'app mostrerà una notifica quando le tue AirPods sono connesse
- Puoi nascondere l\'app se vuoi
+ L\'app mostrerà una notifica quando le tue AirPods sono connesse
+ Puoi nascondere l\'app se vuoiImpostazioniQuesto dispositivo non supporta Bluetooth LEAttiva la posizione per permttere a OpenPods di leggere lo stato
- Dispositivi supportati:\n• Apple AirPods 1st gen\n• Apple AirPods 2nd gen\n• Apple AirPods Pro
+ Dispositivi supportati:
+ • Apple AirPods 1st gen\n• Apple AirPods 2nd gen\n• Apple AirPods Pro
- Nascondi app
- L\'icona sarà rimossa prestoRisparmio batteria (sconsigliato)Attiva se il Bluetooth usa molta batteria
+ Nascondi app
+ L\'icona sarà rimossa prestoInformazioni su
- Sviluppata da Federico Dossena
+ Sviluppata da Federico DossenaSito webGithubDonazioni
diff --git a/OpenPods/app/src/main/res/values-iw-rIL/strings.xml b/OpenPods/app/src/main/res/values-iw-rIL/strings.xml
new file mode 100644
index 0000000..4397fe7
--- /dev/null
+++ b/OpenPods/app/src/main/res/values-iw-rIL/strings.xml
@@ -0,0 +1,25 @@
+
+
+ אפליקציה זו מאפשרת לך לבדוק את המצב של האיירפודס שלך מכל מכשיר אנדרואיד
+ כדי שאפליקציה זו תוכל לתפקד, הינך נדרש לאפשר גישה ל-Bluetooth LE (מיקום) ולריצה ברקע
+ אפשר
+ האפליקציה תשלח התראה כאשר האיירפודס מחוברות
+ אתה יכול להסתיר אפליקציה זו
+ הגדרות
+
+ נראה כי מכשיר זה אינו תומך ב-Bluetooth LE
+ אתה צריך להפעיל שיתוף מיקום בכדי לאפשר ל-OpenPods לקרוא את סטטוס האוזניות
+ מכשירים נתמכים
+ • אפל איירפודס דור ראשון \n• אפל איירפודס דור שני \n• אפל איירפודס פרו
+
+ חיסכון בסוללה (לא מומלץ)
+ הפעל אם Bluetooth משתמש בהרבה סוללה
+ הסתר אפליקציה
+ האפליקציה תוסתר בקרוב
+ אודות
+
+ פותח על ידי פדריקו דוסנה
+ אתר המפתח
+ Github
+ תרומה
+
diff --git a/OpenPods/app/src/main/res/values-night/colors.xml b/OpenPods/app/src/main/res/values-night/colors.xml
new file mode 100644
index 0000000..bc34083
--- /dev/null
+++ b/OpenPods/app/src/main/res/values-night/colors.xml
@@ -0,0 +1,11 @@
+
+
+ #448AFF
+ #0D47A1
+ #448AFF
+ #FFFFFF
+
+ #FFFFFF
+ #000000
+ @android:color/darker_gray
+
diff --git a/OpenPods/app/src/main/res/values-nl/strings.xml b/OpenPods/app/src/main/res/values-nl/strings.xml
index a652225..56a5a09 100644
--- a/OpenPods/app/src/main/res/values-nl/strings.xml
+++ b/OpenPods/app/src/main/res/values-nl/strings.xml
@@ -1,25 +1,25 @@
+
- OpenPodsMet deze app kun je de status van je AirPods bekijken op elk Android-apparaatDe app heeft toegang nodig tot Bluetooth LE (locatie), en moet op de achtergrond worden uitgevoerdToestaan
- De app toont een melding zodra je je AirPods verbindt
- Als je wilt, kun je deze app verbergen
+ De app toont een melding zodra je je AirPods verbindt
+ Als je wilt, kun je deze app verbergenInstellingenHet lijkt erop dat dit apparaat niet beschikt over Bluetooth LESchakel je locatie in zodat OpenPods de status kan uitlezen
- Ondersteunde apparaten:\n• Apple AirPods 1e gen.\n• Apple AirPods 2e gen.\n• Apple AirPods Pro
+ Ondersteunde apparaten:
+ • Apple AirPods 1e gen\n• Apple AirPods 2e gen\n• Apple AirPods Pro
- App verbergen
- Het pictogram verdwijnt spoedigAccu-optimalisatie (niet aanbevolen)Schakel dit in als je merkt dat het accuverbruik hoger is door Bluetooth
+ App verbergen
+ Het pictogram verdwijnt spoedigOver
- Gemaakt door Federico Dossena
+ Gemaakt door Federico DossenaWebsiteGitHubDoneren
-
diff --git a/OpenPods/app/src/main/res/values-ru/strings.xml b/OpenPods/app/src/main/res/values-ru/strings.xml
index bcc5080..d9abd5c 100644
--- a/OpenPods/app/src/main/res/values-ru/strings.xml
+++ b/OpenPods/app/src/main/res/values-ru/strings.xml
@@ -1,24 +1,25 @@
+
Это приложение позволяет Вам проверить статус ваших AirPods на любом Android-устройствеДля работы нам нужно Bluetooth LE(местоположение) и разрешение на запуск в фоновом режимеРазрешить
- Приложение покажет уведомление, когда будет установлено соединение с AirPods
- Вы можете скрыть это приложение, если хотите
+ Приложение покажет уведомление, когда будет установлено соединение с AirPods
+ Вы можете скрыть это приложение, если хотитеНастройкиПохоже, что Bluetooth LE не поддерживается на Вашем устройствеВам нужно включить местоположение, чтобы разрешить OpenPods читать статус
- Поддерживаемые устройства:\n• Apple AirPods 1st gen\n• Apple AirPods 2nd gen\n• Apple AirPods Pro
+ Поддерживаемые устройства:
+ • Apple AirPods 1st gen\n• Apple AirPods 2nd gen\n• Apple AirPods Pro
- Скрыть приложение
- Иконка скоро исчезнетРежим энергосбережения (Не рекомендуется)Включите, если Bluetooth использует слишком много ресурсов
+ Скрыть приложение
+ Иконка скоро исчезнетО приложении
- Разработано Federico Dossena
+ Разработано Federico DossenaСайтGithubПоддержать
-
diff --git a/OpenPods/app/src/main/res/values-uk/strings.xml b/OpenPods/app/src/main/res/values-uk/strings.xml
index a8f3a96..5175352 100644
--- a/OpenPods/app/src/main/res/values-uk/strings.xml
+++ b/OpenPods/app/src/main/res/values-uk/strings.xml
@@ -1,26 +1,25 @@
- OpenPodsЦей додаток дозволяє Вам перевірити статус ваших AirPods на будь-якому Android-пристроїДля роботи ми використовуємо Bluetooth LE (місцезнаходження) та дозвіл на роботу додатку в фоновому режиміДозволити
- Цей додаток покаже сповіщення, коли Ваші AirPods будуть підключені
- Ви можете сховати цей додаток, якщо бажаєте
+ Цей додаток покаже сповіщення, коли Ваші AirPods будуть підключені
+ Ви можете сховати цей додаток, якщо бажаєтеНалаштуванняСхоже, що цей пристрій не підтримує Bluetooth LEВам потрібно ввімкнути місцезнаходження, для того, щоб дозволити OpenPods читати статус
- Supported devices:\n• Apple AirPods 1st gen\n• Apple AirPods 2nd gen\n• Apple AirPods Pro
+ Supported devices:
+ • Apple AirPods 1st gen\n• Apple AirPods 2nd gen\n• Apple AirPods Pro
- Сховати додаток
- Іконка скоро зникнеРежим збереження енергії (Не рекомендовано)Увімкніть, якщо Bluetooth використовує багато ресурсів
+ Сховати додаток
+ Іконка скоро зникнеПро програму
- Розроблено Federico Dossena
+ Розроблено Federico DossenaСайтGithubПідтримати
-
diff --git a/OpenPods/app/src/main/res/values-zh-rCN/strings.xml b/OpenPods/app/src/main/res/values-zh-rCN/strings.xml
index 701a63b..2a6b82f 100644
--- a/OpenPods/app/src/main/res/values-zh-rCN/strings.xml
+++ b/OpenPods/app/src/main/res/values-zh-rCN/strings.xml
@@ -3,21 +3,22 @@
此应用可以让您在任何安卓设备上查看 AirPods 的状态我们需要蓝牙、定位以及在后台运行的权限来正常运作允许
- 当您的 AirPods 接入时,我们会在通知栏显示通知
- 您可以隐藏此应用
+ 当您的 AirPods 接入时,我们会在通知栏显示通知
+ 您可以隐藏此应用Settings看起来您的设备并不支持蓝牙您需要开启定位来让 OpenPods 读取状态
- 支持的设备:\n• Apple AirPods 一代\n• Apple AirPods 二代\n• Apple AirPods Pro
+ 支持的设备:
+ • Apple AirPods 一代\n• Apple AirPods 二代\n• Apple AirPods Pro
- 隐藏
- 桌面图标很快就会被隐藏掉省电模式 (不推荐)可在蓝牙消耗过多电量时开启
+ 隐藏
+ 桌面图标很快就会被隐藏掉关于
- 由 Federico Dossena 开发
+ 由 Federico Dossena 开发网站Github捐赠
diff --git a/OpenPods/app/src/main/res/values/colors.xml b/OpenPods/app/src/main/res/values/colors.xml
index cbb24a6..bfbf423 100644
--- a/OpenPods/app/src/main/res/values/colors.xml
+++ b/OpenPods/app/src/main/res/values/colors.xml
@@ -1,8 +1,11 @@
- #448aff
- #0d47a1
- #448aff
- #ffffff
+ #448AFF
+ #0D47A1
+ #448AFF
+ #000000
+
+ #FFFFFF#000000
+ #737373
diff --git a/OpenPods/app/src/main/res/values/dimens.xml b/OpenPods/app/src/main/res/values/dimens.xml
deleted file mode 100644
index 59a0b0c..0000000
--- a/OpenPods/app/src/main/res/values/dimens.xml
+++ /dev/null
@@ -1,3 +0,0 @@
-
- 16dp
-
diff --git a/OpenPods/app/src/main/res/values/strings.xml b/OpenPods/app/src/main/res/values/strings.xml
index 8edbd5a..3222712 100644
--- a/OpenPods/app/src/main/res/values/strings.xml
+++ b/OpenPods/app/src/main/res/values/strings.xml
@@ -1,25 +1,27 @@
+
OpenPods
+
This app lets you check the status of your AirPods on any Android deviceTo operate, we need permission to use Bluetooth LE (Location) and permission to run in backgroundAllow
- The app will show a notification when your AirPods are connected
- You can hide this app if you want
+ The app will show a notification when your AirPods are connected
+ You can hide this app if you wantSettingsIt looks like this device doesn\'t have Bluetooth LEYou need to turn on Location to allow OpenPods to read the status
- Supported devices:\n• Apple AirPods 1st gen\n• Apple AirPods 2nd gen\n• Apple AirPods Pro
+ Supported devices:
+ • Apple AirPods 1st gen\n• Apple AirPods 2nd gen\n• Apple AirPods Pro
- Hide app
- The icon will disappear soonBattery saver (Not recommended)Enable this if Bluetooth uses a lot of battery
+ Hide app
+ The icon will disappear soonAbout
- Developed by Federico Dossena
+ Developed by Federico DossenaWebsiteGithubDonate
-
diff --git a/OpenPods/app/src/main/res/values/styles.xml b/OpenPods/app/src/main/res/values/styles.xml
index 545b9c6..270a02f 100644
--- a/OpenPods/app/src/main/res/values/styles.xml
+++ b/OpenPods/app/src/main/res/values/styles.xml
@@ -1,11 +1,9 @@
-
-
-
-
-
-
diff --git a/OpenPods/app/src/main/res/xml/backup_descriptor.xml b/OpenPods/app/src/main/res/xml/backup_descriptor.xml
new file mode 100644
index 0000000..0968891
--- /dev/null
+++ b/OpenPods/app/src/main/res/xml/backup_descriptor.xml
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/OpenPods/app/src/main/res/xml/pref_general.xml b/OpenPods/app/src/main/res/xml/preference_screen.xml
similarity index 71%
rename from OpenPods/app/src/main/res/xml/pref_general.xml
rename to OpenPods/app/src/main/res/xml/preference_screen.xml
index 98db7d7..4b09ed9 100644
--- a/OpenPods/app/src/main/res/xml/pref_general.xml
+++ b/OpenPods/app/src/main/res/xml/preference_screen.xml
@@ -1,17 +1,18 @@
+
+ android:key="batterySaver"
+ android:summary="@string/batterySaver_desc"
+ android:title="@string/batterySaver" />
+
+ android:title="@string/hide" />
+
+ android:title="@string/about" />
+
diff --git a/OpenPods/build.gradle b/OpenPods/build.gradle
index 2afeee3..d29fea0 100644
--- a/OpenPods/build.gradle
+++ b/OpenPods/build.gradle
@@ -7,7 +7,7 @@ buildscript {
}
dependencies {
- classpath 'com.android.tools.build:gradle:3.6.0'
+ classpath 'com.android.tools.build:gradle:3.6.3'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
diff --git a/OpenPods/gradle.properties b/OpenPods/gradle.properties
index 82618ce..d546dea 100644
--- a/OpenPods/gradle.properties
+++ b/OpenPods/gradle.properties
@@ -6,6 +6,8 @@
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
+android.enableJetifier=true
+android.useAndroidX=true
org.gradle.jvmargs=-Xmx1536m
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
diff --git a/OpenPods/gradle/wrapper/gradle-wrapper.properties b/OpenPods/gradle/wrapper/gradle-wrapper.properties
index 9bf543c..f351fcf 100644
--- a/OpenPods/gradle/wrapper/gradle-wrapper.properties
+++ b/OpenPods/gradle/wrapper/gradle-wrapper.properties
@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-6.3-all.zip
diff --git a/README.md b/README.md
index 58fe704..217e990 100644
--- a/README.md
+++ b/README.md
@@ -12,6 +12,7 @@ The Free and Open Source app for monitoring your AirPods on Android
* Shows a notification with AirPods status when they are connected
* Respects your privacy
* Free and Open Source software
+* Supports Dark Theme
## Supported devices
* Apple AirPods 1st gen
@@ -27,10 +28,7 @@ I had to do this because several people were redistributing modified proprietary
[Donate with PayPal](https://www.paypal.me/sineisochronic)
## Screenshots
-
-
-
-
+
## License
Copyright (C) 2019 Federico Dossena
diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/screen3.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/screen3.png
index 16be31f..d4680bc 100644
Binary files a/fastlane/metadata/android/en-US/images/phoneScreenshots/screen3.png and b/fastlane/metadata/android/en-US/images/phoneScreenshots/screen3.png differ
diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/screen4.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/screen4.png
index e8eb820..2e39359 100644
Binary files a/fastlane/metadata/android/en-US/images/phoneScreenshots/screen4.png and b/fastlane/metadata/android/en-US/images/phoneScreenshots/screen4.png differ