Started migration to AndroidX

This commit is contained in:
Electric
2020-05-10 19:54:52 +03:00
parent 09da00294e
commit f4ac967636
31 changed files with 756 additions and 558 deletions
+8
View File
@@ -0,0 +1,8 @@
<component name="ProjectDictionaryState">
<dictionary name="Electric">
<words>
<w>airpod</w>
<w>airpods</w>
</words>
</dictionary>
</component>
+1 -1
View File
@@ -5,7 +5,7 @@
<configuration PROFILE_NAME="Debug" CONFIG_NAME="Debug" />
</configurations>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_7" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">
+6
View File
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RenderSettings">
<option name="showDecorations" value="true" />
</component>
</project>
+13 -4
View File
@@ -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
targetSdkVersion 29
versionCode 13
versionName '1.3'
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
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"
}
+22 -13
View File
@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.dosse.airpods">
<uses-permission android:name="android.permission.BLUETOOTH" />
@@ -16,14 +17,28 @@
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".AboutActivity"
android:theme="@style/AppTheme.NoActionBar"></activity>
<activity android:name=".SettingsActivity"/>
android:supportsRtl="false"
android:theme="@style/AppTheme"
tools:ignore="AllowBackup">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".AboutActivity"
android:theme="@style/AppTheme.NoActionBar" />
<activity
android:name=".SettingsActivity" />
<activity
android:name=".NoBTActivity"
android:label="@string/app_name" />
<activity
android:name=".IntroActivity"
android:label="@string/app_name"
@@ -44,15 +59,9 @@
<service
android:name=".PodsService"
android:enabled="true"
android:exported="true" />
android:exported="true"
tools:ignore="ExportedService" />
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
@@ -2,21 +2,24 @@ 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/i.frag";
final String donateURL = "https://paypal.me/sineisochronic";
@Override
protected void onCreate(Bundle savedInstanceState) {
protected void onCreate (Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_about);
/**
/*
* LEGAL DANGER ZONE!
*
* The following actions are a violation of the GNU GPLv3 License:
@@ -32,26 +35,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
}
}
@@ -1,69 +1,78 @@
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;
@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();
}
}
@@ -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;
}
}
@@ -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);
}
}
@@ -26,29 +26,31 @@ 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 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,7 +58,7 @@ 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
*
* <p>
* 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.
* What we did to workaround this issue is this:
@@ -64,7 +66,7 @@ public class PodsService extends Service {
* - 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...
*
* <p>
* 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 +75,134 @@ 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)
*
* <p>
* 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<ScanResult> recentBeacons=new ArrayList<>();
private static final long RECENT_BEACONS_MAX_T_NS=10000000000L; //10s
private void startAirPodsScanner() {
private static ArrayList<ScanResult> 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 = getSharedPreferences("openpods", MODE_PRIVATE);
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<ScanFilter> 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<ScanResult> scanResults) {
for (ScanResult result : scanResults) onScanResult(-1, result);
super.onBatchScanResults(scanResults);
}
@Override
public void onBatchScanResults (List<ScanResult> 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;i<recentBeacons.size();i++){
if(SystemClock.elapsedRealtimeNanos()-recentBeacons.get(i).getTimestampNanos()>RECENT_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)));
String leftAirpodStr; // Left airpod (0-10 batt; 15=disconnected)
String rightAirpodStr; // Right airpod (0-10 batt; 15=disconnected)
if (isFlipped(a)) {
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);
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()<recentBeacons.get(i).getRssi()) strongestBeacon=recentBeacons.get(i);
}
if(strongestBeacon!=null&&strongestBeacon.getDevice().getAddress().equals(result.getDevice().getAddress())) strongestBeacon=result;
result=strongestBeacon;
if(result.getRssi()<-60) return;
String a=decodeHex(result.getScanRecord().getManufacturerSpecificData(76));
String str = ""; //left airpod (0-10 batt; 15=disconnected)
String str2 = ""; //right airpod (0-10 batt; 15=disconnected)
if (isFlipped(a)) {
str = "" + a.charAt(12);
str2 = "" + a.charAt(13);
} else {
str = "" + a.charAt(13);
str2 = "" + a.charAt(12);
}
String str3 = "" + a.charAt(15); //case (0-10 batt; 15=disconnected)
String str4 = "" + a.charAt(14); //charge status (bit 0=left; bit 1=right; bit 2=case)
leftStatus = Integer.parseInt(str, 16);
rightStatus = Integer.parseInt(str2, 16);
caseStatus = Integer.parseInt(str3, 16);
int chargeStatus = Integer.parseInt(str4, 16);
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);
}
}
});
});
} catch (Throwable t) {
if(ENABLE_LOGGING) Log.d(TAG, "" + t);
if (ENABLE_LOGGING)
Log.d(TAG, "" + t);
}
}
private List<ScanFilter> getScanFilters() {
private List<ScanFilter> getScanFilters () {
byte[] manufacturerData = new byte[27];
byte[] manufacturerDataMask = new byte[27];
@@ -178,66 +214,81 @@ 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.
*
* <p>
* 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)
*
* <p>
* 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);
@SuppressWarnings("WeakerAccess")
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
NotificationChannel channel = new NotificationChannel(TAG, TAG, NotificationManager.IMPORTANCE_LOW);
channel.enableVibration(false);
@@ -247,48 +298,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;
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<TIMEOUT_CONNECTED*/){
if(!notificationShowing){
if(ENABLE_LOGGING) Log.d(TAG,"Creating notification");
notificationShowing=true;
mNotifyManager.notify(1,mBuilder.build());
//noinspection InfiniteLoopStatement
for (; ; ) {
/*&&System.currentTimeMillis()-lastSeenConnected<TIMEOUT_CONNECTED*/
if (maybeConnected && !(leftStatus == 15 && rightStatus == 15 && caseStatus == 15)) {
if (!notificationShowing) {
if (ENABLE_LOGGING)
Log.d(TAG, "Creating notification");
notificationShowing = true;
mNotifyManager.notify(1, mBuilder.build());
}
}else{
if(notificationShowing){
if(ENABLE_LOGGING) Log.d(TAG,"Removing notification");
notificationShowing=false;
} else {
if (notificationShowing) {
if (ENABLE_LOGGING)
Log.d(TAG, "Removing notification");
notificationShowing = false;
continue;
}
mNotifyManager.cancel(1);
}
if(isLocationEnabled()||Build.VERSION.SDK_INT>=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,7 +360,8 @@ 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<TIMEOUT_CONNECTED) {
if (System.currentTimeMillis() - lastSeenConnected < TIMEOUT_CONNECTED) {
notificationBig.setViewVisibility(R.id.leftPodText, View.VISIBLE);
notificationBig.setViewVisibility(R.id.rightPodText, View.VISIBLE);
notificationBig.setViewVisibility(R.id.podCaseText, View.VISIBLE);
@@ -309,13 +374,13 @@ public class PodsService extends Service {
notificationSmall.setViewVisibility(R.id.leftPodUpdating, View.INVISIBLE);
notificationSmall.setViewVisibility(R.id.rightPodUpdating, View.INVISIBLE);
notificationSmall.setViewVisibility(R.id.podCaseUpdating, View.INVISIBLE);
notificationBig.setTextViewText(R.id.leftPodText, (leftStatus==10?"100%":leftStatus<10?(((leftStatus)*10+5)+"%"):"") + ((chargeL && leftStatus < 10) ? "+" : ""));
notificationBig.setTextViewText(R.id.rightPodText, (rightStatus==10?"100%":rightStatus<10?(((rightStatus)*10+5)+"%"):"") + ((chargeR && rightStatus < 10) ? "+" : ""));
notificationBig.setTextViewText(R.id.podCaseText, (caseStatus==10?"100%":caseStatus<10?(((caseStatus)*10+5)+"%"):"") + ((chargeCase && caseStatus < 10) ? "+" : ""));
notificationSmall.setTextViewText(R.id.leftPodText, (leftStatus==10?"100%":leftStatus<10?(((leftStatus)*10+5)+"%"):"") + ((chargeL && leftStatus < 10) ? "+" : ""));
notificationSmall.setTextViewText(R.id.rightPodText, (rightStatus==10?"100%":rightStatus<10?(((rightStatus)*10+5)+"%"):"") + ((chargeR && rightStatus < 10) ? "+" : ""));
notificationSmall.setTextViewText(R.id.podCaseText, (caseStatus==10?"100%":caseStatus<10?(((caseStatus)*10+5)+"%"):"") + ((chargeCase && caseStatus < 10) ? "+" : ""));
}else{
notificationBig.setTextViewText(R.id.leftPodText, (leftStatus == 10 ? "100%" : leftStatus < 10 ? (((leftStatus) * 10 + 5) + "%") : "") + ((chargeL && leftStatus < 10) ? "+" : ""));
notificationBig.setTextViewText(R.id.rightPodText, (rightStatus == 10 ? "100%" : rightStatus < 10 ? (((rightStatus) * 10 + 5) + "%") : "") + ((chargeR && rightStatus < 10) ? "+" : ""));
notificationBig.setTextViewText(R.id.podCaseText, (caseStatus == 10 ? "100%" : caseStatus < 10 ? (((caseStatus) * 10 + 5) + "%") : "") + ((chargeCase && caseStatus < 10) ? "+" : ""));
notificationSmall.setTextViewText(R.id.leftPodText, (leftStatus == 10 ? "100%" : leftStatus < 10 ? (((leftStatus) * 10 + 5) + "%") : "") + ((chargeL && leftStatus < 10) ? "+" : ""));
notificationSmall.setTextViewText(R.id.rightPodText, (rightStatus == 10 ? "100%" : rightStatus < 10 ? (((rightStatus) * 10 + 5) + "%") : "") + ((chargeR && rightStatus < 10) ? "+" : ""));
notificationSmall.setTextViewText(R.id.podCaseText, (caseStatus == 10 ? "100%" : caseStatus < 10 ? (((caseStatus) * 10 + 5) + "%") : "") + ((chargeCase && caseStatus < 10) ? "+" : ""));
} else {
notificationBig.setViewVisibility(R.id.leftPodText, View.INVISIBLE);
notificationBig.setViewVisibility(R.id.rightPodText, View.INVISIBLE);
notificationBig.setViewVisibility(R.id.podCaseText, View.INVISIBLE);
@@ -329,32 +394,36 @@ public class PodsService extends Service {
notificationSmall.setViewVisibility(R.id.rightPodUpdating, View.VISIBLE);
notificationSmall.setViewVisibility(R.id.podCaseUpdating, View.VISIBLE);
}
mNotifyManager.notify(1,mBuilder.build());
mNotifyManager.notify(1, mBuilder.build());
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) { }
} catch (InterruptedException ignored) {
}
}
}
}
public PodsService() {
public PodsService () {
}
@Override
public IBinder onBind(Intent intent) {
public IBinder onBind (Intent intent) {
return null;
}
private BroadcastReceiver btReceiver=null, screenReceiver=null;
private BroadcastReceiver btReceiver = null, screenReceiver = null;
/**
* 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.
*/
@Override
public void onCreate() {
public void onCreate () {
super.onCreate();
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction("android.bluetooth.device.action.ACL_CONNECTED");
intentFilter.addAction("android.bluetooth.device.action.ACL_DISCONNECTED");
@@ -367,126 +436,163 @@ public class PodsService extends Service {
intentFilter.addAction("android.bluetooth.a2dp.profile.action.CONNECTION_STATE_CHANGED");
intentFilter.addAction("android.bluetooth.a2dp.profile.action.PLAYING_STATE_CHANGED");
intentFilter.addCategory("android.bluetooth.headset.intent.category.companyid.76");
try{
try {
unregisterReceiver(btReceiver);
}catch (Throwable t){}
btReceiver=new BroadcastReceiver() {
} catch (Throwable ignored) {
}
btReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
BluetoothDevice bluetoothDevice = (BluetoothDevice) intent.getParcelableExtra("android.bluetooth.device.extra.DEVICE");
public void onReceive (Context context, Intent intent) {
BluetoothDevice bluetoothDevice = intent.getParcelableExtra("android.bluetooth.device.extra.DEVICE");
String action = intent.getAction();
if(action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)){
int state= intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR);
if(state==BluetoothAdapter.STATE_OFF||state==BluetoothAdapter.STATE_TURNING_OFF){ //bluetooth turned off, stop scanner and remove notification
if(ENABLE_LOGGING) Log.d(TAG,"BT OFF");
maybeConnected =false;
assert action != null;
if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR);
// Bluetooth turned off, stop scanner and remove notification.
if (state == BluetoothAdapter.STATE_OFF || state == BluetoothAdapter.STATE_TURNING_OFF) {
if (ENABLE_LOGGING)
Log.d(TAG, "BT OFF");
maybeConnected = false;
stopAirPodsScanner();
recentBeacons.clear();
}
if(state==BluetoothAdapter.STATE_ON){ //bluetooth turned on, start/restart scanner
if(ENABLE_LOGGING) Log.d(TAG,"BT ON");
// Bluetooth turned on, start/restart scanner.
if (state == BluetoothAdapter.STATE_ON) {
if (ENABLE_LOGGING)
Log.d(TAG, "BT ON");
startAirPodsScanner();
}
}
if (bluetoothDevice != null && action != null && !action.isEmpty()&&checkUUID(bluetoothDevice)){ //airpods filter
if(action.equals(BluetoothDevice.ACTION_ACL_CONNECTED)){ //airpods connected, show notification
if(ENABLE_LOGGING) Log.d(TAG,"ACL CONNECTED");
maybeConnected =true;
// Airpods filter
if (bluetoothDevice != null && !action.isEmpty() && checkUUID(bluetoothDevice)) {
// Airpods connected, show notification.
if (action.equals(BluetoothDevice.ACTION_ACL_CONNECTED)) {
if (ENABLE_LOGGING)
Log.d(TAG, "ACL CONNECTED");
maybeConnected = true;
}
if(action.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED)||action.equals(BluetoothDevice.ACTION_ACL_DISCONNECT_REQUESTED)){ //airpods disconnected, remove notification but leave the scanner going
if(ENABLE_LOGGING) Log.d(TAG,"ACL DISCONNECTED");
maybeConnected =false;
// Airpods disconnected, remove notification but leave the scanner going.
if (action.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED) || action.equals(BluetoothDevice.ACTION_ACL_DISCONNECT_REQUESTED)) {
if (ENABLE_LOGGING)
Log.d(TAG, "ACL DISCONNECTED");
maybeConnected = false;
recentBeacons.clear();
}
}
}
};
try{
registerReceiver(btReceiver,intentFilter);
}catch(Throwable t){}
//this BT Profile Proxy allows us to know if airpods are already connected when the app is started. It also fires an event when BT is turned off, in case the BroadcastReceiver doesn't do its job
BluetoothAdapter ba=((BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE)).getAdapter();
try {
registerReceiver(btReceiver, intentFilter);
} catch (Throwable ignored) {
}
// This BT Profile Proxy allows us to know if airpods are already connected when the app is started.
// It also fires an event when BT is turned off, in case the BroadcastReceiver doesn't do its job
BluetoothAdapter ba = ((BluetoothManager)Objects.requireNonNull(getSystemService(Context.BLUETOOTH_SERVICE))).getAdapter();
ba.getProfileProxy(getApplicationContext(), new BluetoothProfile.ServiceListener() {
@Override
public void onServiceConnected(int i, BluetoothProfile bluetoothProfile) {
if(i==BluetoothProfile.HEADSET){
if(ENABLE_LOGGING) Log.d(TAG,"BT PROXY SERVICE CONNECTED");
BluetoothHeadset h=(BluetoothHeadset)bluetoothProfile;
for(BluetoothDevice d:h.getConnectedDevices()){
if(checkUUID(d)){
if(ENABLE_LOGGING) Log.d(TAG,"BT PROXY: AIRPODS ALREADY CONNECTED");
maybeConnected=true;
public void onServiceConnected (int i, BluetoothProfile bluetoothProfile) {
if (i == BluetoothProfile.HEADSET) {
if (ENABLE_LOGGING)
Log.d(TAG, "BT PROXY SERVICE CONNECTED");
BluetoothHeadset h = (BluetoothHeadset)bluetoothProfile;
for (BluetoothDevice d : h.getConnectedDevices())
if (checkUUID(d)) {
if (ENABLE_LOGGING)
Log.d(TAG, "BT PROXY: AIRPODS ALREADY CONNECTED");
maybeConnected = true;
break;
}
}
}
}
@Override
public void onServiceDisconnected(int i) {
if(i==BluetoothProfile.HEADSET){
if(ENABLE_LOGGING) Log.d(TAG,"BT PROXY SERVICE DISCONNECTED ");
maybeConnected=false;
public void onServiceDisconnected (int i) {
if (i == BluetoothProfile.HEADSET) {
if (ENABLE_LOGGING)
Log.d(TAG, "BT PROXY SERVICE DISCONNECTED ");
maybeConnected = false;
}
}
},BluetoothProfile.HEADSET);
if(ba.isEnabled())startAirPodsScanner(); //if BT is already on when the app is started, start the scanner without waiting for an event to happen
//Screen on/off listener to suspend scanning when the screen is off, to save battery
try{
}, BluetoothProfile.HEADSET);
if (ba.isEnabled())
startAirPodsScanner(); // If BT is already on when the app is started, start the scanner without waiting for an event to happen
// Screen on/off listener to suspend scanning when the screen is off, to save battery
try {
unregisterReceiver(screenReceiver);
}catch (Throwable t){}
SharedPreferences prefs=getSharedPreferences("openpods",MODE_PRIVATE);
if(prefs.getBoolean("batterySaver",false)) {
} catch (Throwable ignored) {
}
SharedPreferences prefs = getSharedPreferences("openpods", MODE_PRIVATE);
if (prefs.getBoolean("batterySaver", false)) {
IntentFilter screenIntentFilter = new IntentFilter();
screenIntentFilter.addAction(Intent.ACTION_SCREEN_ON);
screenIntentFilter.addAction(Intent.ACTION_SCREEN_OFF);
screenReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction() == Intent.ACTION_SCREEN_OFF) {
if (ENABLE_LOGGING) Log.d(TAG, "SCREEN OFF");
public void onReceive (Context context, Intent intent) {
if (Objects.equals(intent.getAction(), Intent.ACTION_SCREEN_OFF)) {
if (ENABLE_LOGGING)
Log.d(TAG, "SCREEN OFF");
stopAirPodsScanner();
} else if (intent.getAction() == Intent.ACTION_SCREEN_ON) {
if (ENABLE_LOGGING) Log.d(TAG, "SCREEN ON");
BluetoothAdapter ba = ((BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE)).getAdapter();
if (ba.isEnabled()) startAirPodsScanner();
} else if (Objects.equals(intent.getAction(), Intent.ACTION_SCREEN_ON)) {
if (ENABLE_LOGGING)
Log.d(TAG, "SCREEN ON");
BluetoothAdapter ba = ((BluetoothManager)Objects.requireNonNull(getSystemService(Context.BLUETOOTH_SERVICE))).getAdapter();
if (ba.isEnabled())
startAirPodsScanner();
}
}
};
try {
registerReceiver(screenReceiver, screenIntentFilter);
} catch (Throwable t) {
} catch (Throwable ignored) {
}
}
}
private boolean checkUUID(BluetoothDevice bluetoothDevice){
ParcelUuid[] AIRPODS_UUIDS= {
private boolean checkUUID (BluetoothDevice bluetoothDevice) {
ParcelUuid[] AIRPODS_UUIDS = {
ParcelUuid.fromString("74ec2172-0bad-4d01-8f77-997b2be0722a"),
ParcelUuid.fromString("2a72e02b-7b99-778f-014d-ad0b7221ec74")
};
ParcelUuid[] uuids = bluetoothDevice.getUuids();
if(uuids==null) return false;
for(ParcelUuid u:uuids){
for(ParcelUuid v:AIRPODS_UUIDS){
if(u.equals(v)) return true;
}
}
if (uuids == null)
return false;
for (ParcelUuid u : uuids)
for (ParcelUuid v : AIRPODS_UUIDS)
if (u.equals(v)) return true;
return false;
}
@Override
public void onDestroy() {
public void onDestroy () {
super.onDestroy();
if(btReceiver!=null) unregisterReceiver(btReceiver);
if(screenReceiver!=null) unregisterReceiver(screenReceiver);
if (btReceiver != null) unregisterReceiver(btReceiver);
if (screenReceiver != null) unregisterReceiver(screenReceiver);
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if(n==null||!n.isAlive()){
n=new NotificationThread();
public int onStartCommand (Intent intent, int flags, int startId) {
if (n == null || !n.isAlive()) {
n = new NotificationThread();
n.start();
}
return START_STICKY;
@@ -1,62 +1,31 @@
package com.dosse.airpods;
import android.content.ComponentName;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.preference.Preference;
import android.preference.PreferenceActivity;
import android.util.Log;
import android.widget.Toast;
public class SettingsActivity extends PreferenceActivity implements SharedPreferences.OnSharedPreferenceChangeListener{
import androidx.appcompat.app.AppCompatActivity;
import androidx.preference.PreferenceManager;
public class SettingsActivity extends AppCompatActivity implements SharedPreferences.OnSharedPreferenceChangeListener {
@Override
protected void onCreate(Bundle icicle) {
super.onCreate(icicle);
getPreferenceManager().setSharedPreferencesName("openpods");
addPreferencesFromResource(R.xml.pref_general);
getPreferenceManager().getSharedPreferences().registerOnSharedPreferenceChangeListener(this);
protected void onCreate (Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_settings);
getSupportFragmentManager()
.beginTransaction()
.replace(R.id.settings_container, new SettingsFragment())
.commit();
//hide app listener
((Preference)findPreference("hideApp")).setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
PackageManager p = getPackageManager();
p.setComponentEnabledSetting(new ComponentName(SettingsActivity.this,MainActivity.class), PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP);
Toast.makeText(getApplicationContext(),getString(R.string.hideClicked), Toast.LENGTH_LONG).show();
try{getApplicationContext().openFileOutput("hidden",MODE_PRIVATE).close();}catch(Throwable t){}
enableDisableOptions();
finish();
return true;
}
});
//about listener. Removing or hiding this is a violation of the GPL license
((Preference)findPreference("about")).setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
Intent i=new Intent(SettingsActivity.this,AboutActivity.class);
startActivity(i);
return true;
}
});
enableDisableOptions();
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
prefs.registerOnSharedPreferenceChangeListener(this);
}
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
if(key.equalsIgnoreCase("batterySaver")){
public void onSharedPreferenceChanged (SharedPreferences sharedPreferences, String key) {
if (key.equalsIgnoreCase("batterySaver"))
Starter.restartPodsService(getApplicationContext());
}
}
private void enableDisableOptions(){
try{
getApplicationContext().openFileInput("hidden").close();
((Preference)findPreference("hideApp")).setEnabled(false);
}catch (Throwable t){}
}
}
@@ -0,0 +1,60 @@
package com.dosse.airpods;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.widget.Toast;
import androidx.preference.Preference;
import androidx.preference.PreferenceFragmentCompat;
import java.util.Objects;
public class SettingsFragment extends PreferenceFragmentCompat {
private Context context;
@SuppressWarnings("FieldCanBeLocal")
private Preference mAboutPreference, mHideAppPrefernce;
@Override
public void onCreatePreferences (Bundle savedInstanceState, String rootKey) {
setPreferencesFromResource(R.xml.preference_screen, rootKey);
context = getContext();
mAboutPreference = getPreferenceManager().findPreference("about");
Objects.requireNonNull(mAboutPreference).setOnPreferenceClickListener(preference -> {
startActivity(new Intent(context, AboutActivity.class));
return true;
});
mHideAppPrefernce = getPreferenceManager().findPreference("hideApp");
Objects.requireNonNull(mHideAppPrefernce).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();
mHideAppPrefernce.setEnabled(false);
} catch (Throwable ignored) {
}
}
}
@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="48dp"
android:height="48dp"
android:tint="#737373"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M9,16.17L4.83,12l-1.42,1.41L9,19 21,7l-1.41,-1.41z" />
</vector>
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="30dp"
android:height="30dp"
android:tint="#FFFFFF"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M19.1,12.9a2.8,2.8 0,0 0,0.1 -0.9,2.8 2.8,0 0,0 -0.1,-0.9l2.1,-1.6a0.7,0.7 0,0 0,0.1 -0.6L19.4,5.5a0.7,0.7 0,0 0,-0.6 -0.2l-2.4,1a6.5,6.5 0,0 0,-1.6 -0.9l-0.4,-2.6a0.5,0.5 0,0 0,-0.5 -0.4H10.1a0.5,0.5 0,0 0,-0.5 0.4L9.3,5.4a5.6,5.6 0,0 0,-1.7 0.9l-2.4,-1a0.4,0.4 0,0 0,-0.5 0.2l-2,3.4c-0.1,0.2 0,0.4 0.2,0.6l2,1.6a2.8,2.8 0,0 0,-0.1 0.9,2.8 2.8,0 0,0 0.1,0.9L2.8,14.5a0.7,0.7 0,0 0,-0.1 0.6l1.9,3.4a0.7,0.7 0,0 0,0.6 0.2l2.4,-1a6.5,6.5 0,0 0,1.6 0.9l0.4,2.6a0.5,0.5 0,0 0,0.5 0.4h3.8a0.5,0.5 0,0 0,0.5 -0.4l0.3,-2.6a5.6,5.6 0,0 0,1.7 -0.9l2.4,1a0.4,0.4 0,0 0,0.5 -0.2l2,-3.4c0.1,-0.2 0,-0.4 -0.2,-0.6ZM12,15.6A3.6,3.6 0,1 1,15.6 12,3.6 3.6,0 0,1 12,15.6Z" />
</vector>
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="48dp"
android:height="48dp"
android:tint="#737373"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M24 20.188l-8.315-8.209 8.2-8.282-3.697-3.697-8.212 8.318-8.31-8.203-3.666 3.666 8.321 8.24-8.206 8.313 3.666 3.666 8.237-8.318 8.285 8.203z" />
</vector>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

@@ -3,17 +3,17 @@
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:padding="12dp"
tools:context=".AboutActivity"
tools:ignore="ContentDescription,ButtonStyle">
<ImageView
android:id="@+id/logo"
android:layout_width="match_parent"
android:layout_height="90dp"
android:src="@drawable/logo"
android:id="@+id/logo"
android:layout_marginBottom="16dp"/>
android:layout_marginBottom="16dp"
android:src="@drawable/logo" />
<!-- LEGAL DANGER ZONE
The following actions are a violation of the GNU GPLv3 License:
@@ -29,48 +29,50 @@
-->
<TextView
android:id="@+id/about1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/about1"
android:layout_below="@id/logo"
android:id="@+id/about1"
android:gravity="center"
android:layout_marginBottom="16dp"
android:textColor="#404040"
/>
android:gravity="center"
android:text="@string/about1"
android:textColor="#404040" />
<LinearLayout
android:id="@+id/buttons"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/about1"
android:gravity="center"
android:layout_centerHorizontal="true"
android:layout_marginBottom="12dp"
android:id="@+id/buttons">
android:gravity="center">
<Button
android:id="@+id/website"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/website"
android:id="@+id/website"/>
android:text="@string/website" />
<Button
android:id="@+id/github"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/github"
android:id="@+id/github"/>
android:text="@string/github" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/donate"
android:id="@+id/donate"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:backgroundTint="#EABF80"
/>
android:text="@string/donate" />
</LinearLayout>
<WebView
android:id="@+id/license"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@id/buttons"
android:id="@+id/license"></WebView>
android:layout_below="@id/buttons" />
<!-- END OF LEGAL DANGER ZONE -->
@@ -1,59 +1,51 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="match_parent"
android:gravity="center"
>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:padding="12dp"
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:gravity="center"
>
android:orientation="vertical"
android:padding="12dp">
<ImageView
android:id="@+id/introImg1"
android:layout_width="match_parent"
android:layout_height="120dp"
android:src="@drawable/left_pod"
android:layout_alignParentTop="true"
android:id="@+id/introImg1"
android:layout_marginBottom="24dp"
/>
android:src="@drawable/left_pod"
tools:ignore="ContentDescription" />
<TextView
android:id="@+id/intro1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/introImg1"
android:layout_marginBottom="24dp"
android:gravity="center"
android:text="@string/intro1"
android:textAppearance="@android:style/TextAppearance.DeviceDefault.Large" />
<TextView
android:id="@+id/intro2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/intro2"
android:id="@+id/intro2"
android:layout_below="@id/intro1"
android:layout_marginBottom="24dp"
android:gravity="center"
/>
android:text="@string/intro2" />
<Button
android:id="@+id/allowBtn"
style="@style/Widget.AppCompat.Button.Colored"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/intro2"
android:layout_centerHorizontal="true"
android:text="@string/introAllow"
android:backgroundTint="@color/colorAccent"
/>
android:text="@string/introAllow" />
</RelativeLayout>
</LinearLayout>
</RelativeLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
@@ -1,58 +1,49 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
>
tools:ignore="ContentDescription">
<RelativeLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:padding="12dp"
android:gravity="center"
>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingLeft="16dp"
android:paddingTop="36dp"
android:paddingRight="16dp"
android:paddingBottom="16dp">
<ImageView
android:layout_width="match_parent"
android:layout_height="120dp"
android:src="@drawable/tick"
android:layout_alignParentTop="true"
android:id="@+id/tick"
android:layout_marginBottom="24dp"
/>
android:scaleType="fitCenter"
android:scaleX="1.1"
android:scaleY="1.1"
android:src="@drawable/ic_check_dgrey_48dp" />
<TextView
android:id="@+id/main1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="24dp"
android:text="@string/main1"
android:textAppearance="@android:style/TextAppearance.DeviceDefault.Large"
android:layout_below="@id/tick"
/>
android:textAppearance="@android:style/TextAppearance.DeviceDefault.Large" />
<TextView
android:id="@+id/supportedDevices"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="60dp"
android:layout_below="@id/main1"
android:layout_marginBottom="48dp"
android:text="@string/supportedDevices" />
<TextView
android:id="@+id/main2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="24dp"
android:layout_below="@id/supportedDevices"
android:text="@string/main2" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/settings"
android:layout_below="@id/main2"
android:id="@+id/settings"
android:layout_alignParentEnd="true"
/>
android:text="@string/main2"
android:textStyle="italic" />
</RelativeLayout>
</LinearLayout>
</RelativeLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
@@ -1,32 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="match_parent"
android:gravity="center"
>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<RelativeLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:padding="12dp"
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:gravity="center"
>
android:orientation="vertical"
android:padding="12dp">
<ImageView
android:layout_width="wrap_content"
android:layout_height="120dp"
android:src="@drawable/x"
android:id="@+id/btErrImg"
/>
android:scaleType="fitCenter"
android:scaleX="1.6"
android:scaleY="1.6"
android:src="@drawable/ic_x_dgrey_48dp"
tools:ignore="ContentDescription" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/noBtText"
android:layout_below="@id/btErrImg"
android:gravity="center"
/>
android:text="@string/noBtText"
android:textColor="#000"
android:textSize="15sp"
android:textStyle="" />
</RelativeLayout>
</LinearLayout>
</RelativeLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
@@ -1,8 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/settings_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".SettingsActivity">
</RelativeLayout>
tools:context=".SettingsActivity" />
@@ -1,18 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_width="match_parent"
android:layout_height="120dp"
android:padding="12dp"
android:gravity="center"
android:background="@color/white"
>
android:gravity="center"
android:padding="12dp">
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:text="@string/locationDisabledText"
android:textColor="@color/black"
/>
android:textColor="@color/black" />
</RelativeLayout>
@@ -1,18 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_width="match_parent"
android:layout_height="70dp"
android:padding="12dp"
android:gravity="center"
android:background="@color/white"
>
android:gravity="center"
android:padding="12dp">
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:text="@string/locationDisabledText"
android:textColor="@color/black"
/>
android:textColor="@color/black" />
</RelativeLayout>
+20 -30
View File
@@ -1,39 +1,36 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="12dp"
android:gravity="center"
android:background="@color/white"
>
android:gravity="center"
android:padding="12dp"
tools:ignore="RtlHardcoded,ContentDescription">
<RelativeLayout
android:id="@+id/leftPod"
android:layout_width="60dp"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:layout_alignParentTop="true"
android:id="@+id/leftPod"
>
android:layout_alignParentTop="true">
<ImageView
android:id="@+id/leftPodImg"
android:layout_width="40dp"
android:layout_height="90dp"
android:src="@drawable/left_pod"
android:id="@+id/leftPodImg"
android:layout_centerHorizontal="true"
android:layout_marginBottom="6dp"
/>
android:src="@drawable/left_pod" />
<TextView
android:id="@+id/leftPodText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/leftPodImg"
android:gravity="center"
android:text="100%+"
android:id="@+id/leftPodText"
android:textColor="@color/black"
/>
android:textColor="@color/black" />
<ProgressBar
android:id="@+id/leftPodUpdating"
@@ -42,8 +39,7 @@
android:layout_height="18dp"
android:layout_below="@id/leftPodImg"
android:gravity="center"
android:indeterminate="true"
/>
android:indeterminate="true" />
</RelativeLayout>
@@ -59,19 +55,17 @@
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" />
<TextView
android:id="@+id/rightPodText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/rightPodImg"
android:gravity="center"
android:text="100%+"
android:id="@+id/rightPodText"
android:textColor="@color/black"
/>
android:textColor="@color/black" />
<ProgressBar
android:id="@+id/rightPodUpdating"
@@ -80,8 +74,7 @@
android:layout_height="18dp"
android:layout_below="@id/rightPodImg"
android:gravity="center"
android:indeterminate="true"
/>
android:indeterminate="true" />
</RelativeLayout>
@@ -97,19 +90,17 @@
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" />
<TextView
android:id="@+id/podCaseText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/podCaseImg"
android:gravity="center"
android:text="100%+"
android:id="@+id/podCaseText"
android:textColor="@color/black"
/>
android:textColor="@color/black" />
<ProgressBar
android:id="@+id/podCaseUpdating"
@@ -118,8 +109,7 @@
android:layout_height="18dp"
android:layout_below="@id/podCaseImg"
android:gravity="center"
android:indeterminate="true"
/>
android:indeterminate="true" />
</RelativeLayout>
@@ -1,37 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:padding="12dp"
android:gravity="center"
android:background="@color/white"
>
android:gravity="center"
android:padding="12dp"
tools:ignore="RtlHardcoded,ContentDescription">
<RelativeLayout
android:id="@+id/leftPod"
android:layout_width="80dp"
android:layout_height="30dp"
android:layout_alignParentStart="true"
android:layout_alignParentTop="true"
android:id="@+id/leftPod"
>
android:layout_alignParentTop="true">
<ImageView
android:id="@+id/leftPodImg"
android:layout_width="18dp"
android:layout_height="30dp"
android:src="@drawable/left_pod"
android:id="@+id/leftPodImg"
/>
android:src="@drawable/left_pod" />
<TextView
android:id="@+id/leftPodText"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_marginLeft="0dp"
android:layout_toRightOf="@id/leftPodImg"
android:gravity="center_vertical"
android:text="100%+"
android:id="@+id/leftPodText"
android:textColor="@color/black"
/>
android:textColor="@color/black" />
<ProgressBar
android:id="@+id/leftPodUpdating"
@@ -39,36 +37,32 @@
android:layout_width="18dp"
android:layout_height="30dp"
android:layout_toRightOf="@id/leftPodImg"
android:indeterminate="true"
/>
android:indeterminate="true" />
</RelativeLayout>
<RelativeLayout
android:id="@+id/rightPod"
android:layout_width="80dp"
android:layout_height="30dp"
android:layout_alignParentTop="true"
android:layout_toRightOf="@id/leftPod"
android:id="@+id/rightPod"
>
android:layout_toRightOf="@id/leftPod">
<ImageView
android:id="@+id/rightPodImg"
android:layout_width="18dp"
android:layout_height="30dp"
android:src="@drawable/right_pod"
android:id="@+id/rightPodImg"
/>
android:src="@drawable/right_pod" />
<TextView
android:id="@+id/rightPodText"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_marginLeft="0dp"
android:layout_toRightOf="@id/rightPodImg"
android:gravity="center_vertical"
android:text="100%+"
android:id="@+id/rightPodText"
android:textColor="@color/black"
/>
android:textColor="@color/black" />
<ProgressBar
android:id="@+id/rightPodUpdating"
@@ -76,36 +70,32 @@
android:layout_width="18dp"
android:layout_height="30dp"
android:layout_toRightOf="@id/rightPodImg"
android:indeterminate="true"
/>
android:indeterminate="true" />
</RelativeLayout>
<RelativeLayout
android:id="@+id/podCase"
android:layout_width="80dp"
android:layout_height="30dp"
android:layout_alignParentTop="true"
android:layout_toRightOf="@id/rightPod"
android:id="@+id/podCase"
>
android:layout_toRightOf="@id/rightPod">
<ImageView
android:id="@+id/podCaseImg"
android:layout_width="25dp"
android:layout_height="30dp"
android:src="@drawable/pod_case"
android:id="@+id/podCaseImg"
/>
android:src="@drawable/pod_case" />
<TextView
android:id="@+id/podCaseText"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_marginLeft="0dp"
android:layout_toRightOf="@id/podCaseImg"
android:gravity="center_vertical"
android:text="100%+"
android:id="@+id/podCaseText"
android:textColor="@color/black"
/>
android:textColor="@color/black" />
<ProgressBar
android:id="@+id/podCaseUpdating"
@@ -113,8 +103,7 @@
android:layout_width="18dp"
android:layout_height="30dp"
android:layout_toRightOf="@id/podCaseImg"
android:indeterminate="true"
/>
android:indeterminate="true" />
</RelativeLayout>
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
tools:ignore="AlwaysShowAction">
<item
android:id="@+id/menu_ab_settings"
android:icon="@drawable/ic_menu_settings_white_30dp"
android:title="@string/settings"
app:showAsAction="always" />
</menu>
@@ -1,17 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<CheckBoxPreference
android:key="batterySaver"
android:title="@string/batterySaver"
android:summary="@string/batterySaver_desc"
android:defaultValue="false"
/>
android:key="batterySaver"
android:summary="@string/batterySaver_desc"
android:title="@string/batterySaver" />
<Preference
android:key="hideApp"
android:title="@string/hide"
/>
android:title="@string/hide" />
<Preference
android:key="about"
android:title="@string/about"
/>
android:title="@string/about" />
</PreferenceScreen>
+1 -1
View File
@@ -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
+2
View File
@@ -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
+1 -1
View File
@@ -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