43 Commits
Author SHA1 Message Date
Federico 888c6e7d0d Release 1.9.1 2022-10-30 11:13:23 +01:00
Itai Levin 019ef80ec9 Merge pull request #155 from oddlyspaced/master
Properly handle POST_NOTIFICATION permission for Android 13
2022-10-27 03:44:42 +03:00
oddlyspaced 4abb9e5d6b IntroActivity: ask for notification perm on 13 and above 2022-10-27 02:19:44 +05:30
oddlyspaced b15dcfbf9d PermissionUtils: setup methods to check for notification perm on >=13 2022-10-27 02:19:08 +05:30
oddlyspaced 92f64ae2dd strings: add strings related to notification permission 2022-10-27 02:18:53 +05:30
oddlyspaced a39dbc3785 AndroidManifest: add post notification permission to manifest 2022-10-27 02:02:42 +05:30
Electric1447 ee7e75b5e8 Update README.md 2022-09-29 22:22:41 +03:00
Electric1447 b939674a62 Add missing airpods pro 2 text in supported devices list 2022-09-29 22:21:44 +03:00
Electric1447 ade3d0a481 Update localizations 2022-09-29 22:20:06 +03:00
Electric1447 70a05a1848 Add Airpods Pro Gen 2 support 2022-09-29 22:17:32 +03:00
Electric1447 6803ef0691 refactor pod models folders 2022-09-29 22:14:18 +03:00
Electric1447 71f08a4098 renamed ``data` to `types`` 2022-09-29 22:07:56 +03:00
Electric1447 17afa55203 Android 13 support 2022-09-29 22:06:36 +03:00
Electric1447 96ffaf1d90 Update localizations 2022-09-29 22:06:22 +03:00
Electric1447 047b9b373c Update build tools 2022-09-29 22:05:54 +03:00
Federico feb2eac287 Release 1.8 2022-09-10 14:08:46 +02:00
Federico cd20f99747 Increased version to 1.8, updated to latest gradle, updated localization and readme 2022-07-28 15:09:27 +02:00
Itai Levin 535790aab1 Merge pull request #147 from d4rken/feature/detect-model-by-full-identifier
Improve model identification and add AirPods Gen3
2022-07-26 09:50:10 +03:00
Itai Levin d938817669 Merge pull request #142 from iris-mobile-accessibility-repair/master
solve issue of low contrast
2022-07-26 09:48:38 +03:00
darken 31cff2ac0c Change model check to compare against the full model identifier (4 bytes) and add support for AirPods 3rd gen.
Closes #140
2022-07-25 18:20:45 +02:00
Electric1447 104dc3454b Update build tools 2022-05-18 00:46:05 +03:00
iris-mobile-accessibility-repair 83b71cb488 Update styles.xml 2022-03-15 22:22:36 +08:00
Electric1447 97f7359057 Location permission is now required 2021-09-12 16:30:21 +03:00
Electric1447 ec67136d44 Update build tools 2021-09-07 23:37:32 +03:00
Electric1447 4f3f365e62 Update README.md 2021-09-01 18:15:03 +03:00
Itai Levin 67a8a4cde9 Merge pull request #126 from Electric1447/master
Update repository organization
2021-09-01 17:28:38 +03:00
Electric1447 b1a056a00b Merge branch 'master' of https://github.com/adolfintel/OpenPods
merge from upstream
2021-09-01 17:26:32 +03:00
Electric1447 b6941b6686 Update README.md 2021-09-01 17:25:13 +03:00
Electric1447 84272fd7a5 Update README.md 2021-09-01 17:19:43 +03:00
Electric1447 6b7ddd4618 Update README.md 2021-09-01 17:09:56 +03:00
Electric1447 12bc363f45 Update README.md 2021-09-01 16:56:46 +03:00
Federico Dossena cac56e641a Deleted syncthing garbage 2021-09-01 13:44:19 +00:00
dosse91 cd416877f9 Merge branch 'master' of github.com:adolfintel/OpenPods 2021-09-01 15:44:10 +02:00
dosse91 dfb76e7791 Added changelogs for fastlane 2021-09-01 15:43:27 +02:00
Electric1447 e47a1b0eee Update README.md 2021-09-01 16:16:27 +03:00
Electric1447 d4fea733b7 Add fastlane text metadata files 2021-09-01 15:50:36 +03:00
Electric1447 0479652a53 Remove .idea folder 2021-09-01 15:30:31 +03:00
Electric1447 65c053002c Update .gitignore 2021-09-01 15:29:20 +03:00
Electric1447 73befc9e7b Move app files to root folder 2021-09-01 15:24:27 +03:00
Electric1447 cd462fd2bb remove old .gitignore 2021-09-01 15:21:00 +03:00
Electric1447 07047a97ec fix Dutch strings.xml 2021-08-31 23:16:44 +03:00
Federico Dossena 7e48ddd4c5 Merge pull request #125 from Vistaus/master
Updated Dutch translation
2021-08-31 16:00:51 +00:00
Heimen Stoffels e66be09b4b Updated Dutch translation 2021-08-31 17:34:17 +02:00
156 changed files with 426 additions and 288 deletions
+15 -1
View File
@@ -1 +1,15 @@
/_old_ # Android specific
local.properties
# Gradle
.cxx
.externalNativeBuild
.gradle
build
# IntelliJ IDEA
.idea
*.iml
# macOS
.DS_Store
-17
View File
@@ -1,17 +0,0 @@
*.iml
.gradle
/local.properties
/.idea/caches
/.idea/codeStyles
/.idea/dictionaries
/.idea/libraries
/.idea/gradle.xml
/.idea/misc.xml
/.idea/modules.xml
/.idea/workspace.xml
/.idea/navEditor.xml
/.idea/assetWizardSettings.xml
.DS_Store
/build
/captures
.externalNativeBuild
-6
View File
@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<bytecodeTargetLevel target="11" />
</component>
</project>
-4
View File
@@ -1,4 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding" addBOMForNewFiles="with NO BOM" />
</project>
-17
View File
@@ -1,17 +0,0 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="HtmlUnknownAttribute" enabled="true" level="WARNING" enabled_by_default="true">
<option name="myValues">
<value>
<list size="3">
<item index="0" class="java.lang.String" itemvalue="data-wgscriptallow" />
<item index="1" class="java.lang.String" itemvalue="data-cbscriptallow" />
<item index="2" class="java.lang.String" itemvalue="data-acxscriptallow" />
</list>
</value>
</option>
<option name="myCustomValuesEnabled" value="true" />
</inspection_tool>
</profile>
</component>
-30
View File
@@ -1,30 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RemoteRepositoriesConfiguration">
<remote-repository>
<option name="id" value="central" />
<option name="name" value="Maven Central repository" />
<option name="url" value="https://repo1.maven.org/maven2" />
</remote-repository>
<remote-repository>
<option name="id" value="jboss.community" />
<option name="name" value="JBoss Community repository" />
<option name="url" value="https://repository.jboss.org/nexus/content/repositories/public/" />
</remote-repository>
<remote-repository>
<option name="id" value="BintrayJCenter" />
<option name="name" value="BintrayJCenter" />
<option name="url" value="https://jcenter.bintray.com/" />
</remote-repository>
<remote-repository>
<option name="id" value="Google" />
<option name="name" value="Google" />
<option name="url" value="https://dl.google.com/dl/android/maven2/" />
</remote-repository>
<remote-repository>
<option name="id" value="MavenRepo" />
<option name="name" value="MavenRepo" />
<option name="url" value="https://repo.maven.apache.org/maven2/" />
</remote-repository>
</component>
</project>
-6
View File
@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RenderSettings">
<option name="showDecorations" value="true" />
</component>
</project>
-6
View File
@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
</component>
</project>
@@ -1,26 +0,0 @@
package com.dosse.airpods.pods.data;
public interface IPods {
String MODEL_AIRPODS_GEN1 = "airpods1";
String MODEL_AIRPODS_GEN2 = "airpods2";
String MODEL_AIRPODS_PRO = "airpodspro";
String MODEL_AIRPODS_MAX = "airpodsmax";
String MODEL_BEATS_X = "beatsx";
String MODEL_BEATS_FLEX = "beatsflex";
String MODEL_BEATS_SOLO_3 = "beatssolo3";
String MODEL_BEATS_STUDIO_3 = "beatsstudio3";
String MODEL_POWERBEATS_3 = "powerbeats3";
String MODEL_POWERBEATS_PRO = "powerbeatspro";
String MODEL_UNKNOWN = "unknown";
String getModel ();
boolean isSingle ();
boolean isDisconnected ();
String parseStatusForLogger ();
}
@@ -1,16 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="120dp"
android:background="@color/bgColorPrimary"
android:gravity="center"
android:padding="@dimen/padding_default">
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:text="@string/locationDisabledText"
android:textColor="@color/textColorPrimary" />
</RelativeLayout>
@@ -1,16 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="70dp"
android:background="@color/bgColorPrimary"
android:gravity="center"
android:padding="@dimen/padding_default">
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:text="@string/locationDisabledText"
android:textColor="@color/textColorPrimary" />
</RelativeLayout>
+45 -13
View File
@@ -1,23 +1,53 @@
![OpenPods Logo](fastlane/metadata/android/en-US/images/featureGraphic.png) <p align="center"><img src="fastlane/metadata/android/en-US/images/icon.png" width="150"></p>
<h1 align="center"><b>OpenPods</b></h1>
<h4 align="center">The Free and Open Source app for monitoring your AirPods on Android</h4>
<p align="center">
<a href="https://github.com/adolfintel/OpenPods/releases">
<img src="https://img.shields.io/github/release/adolfintel/OpenPods.svg?color=blueviolet" alt="GitHub release">
</a>
<a href="https://www.gnu.org/licenses/gpl-3.0">
<img src="https://img.shields.io/badge/License-GPL%20v3-blue.svg" alt="License: GPLv3">
</a>
<img alt="Minimum SDK" src="https://img.shields.io/badge/API-23%2B-green">
</p>
<p align="center">
<a href="https://f-droid.org/packages/com.dosse.airpods">
<img src="assets/get-it-on-fdroid.png" alt="Get it on F-Droid" height="80">
</a>
<a href="https://downloads.fdossena.com/geth.php?r=openpods-apk">
<img src="assets/get-it-on-openpods.png" alt="Get it on OpenPods" height="80">
</a>
</p>
# OpenPods ## Screenshots
The Free and Open Source app for monitoring your AirPods on Android
## Download
<a href="https://f-droid.org/packages/com.dosse.airpods"><img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png" alt="Get it on F-Droid" height="80"></a>
[Download APK](https://downloads.fdossena.com/geth.php?r=openpods-apk)
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/screen1.png"
alt="Big notification"
width="200">](fastlane/metadata/android/en-US/images/phoneScreenshots/screen1.png)
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/screen2.png"
alt="Small notification"
width="200">](fastlane/metadata/android/en-US/images/phoneScreenshots/screen2.png)
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/screen3.png"
alt="Setup screen"
width="200">](fastlane/metadata/android/en-US/images/phoneScreenshots/screen3.png)
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/screen4.png"
alt="Home screen"
width="200">](fastlane/metadata/android/en-US/images/phoneScreenshots/screen4.png)
## Features ## Features
* Shows a notification with AirPods status when they are connected * Shows a notification with AirPods status when they are connected
* Respects your privacy * Respects your privacy
* Free and Open Source software * Free and Open Source software
* Supports Dark Theme * Supports Dark Theme
## Supported devices ## Supported devices
* Apple AirPods 1st gen * Apple AirPods 1st gen
* Apple AirPods 2nd gen * Apple AirPods 2nd gen
* Apple AirPods 3rd gen
* Apple AirPods Pro * Apple AirPods Pro
* Apple AirPods Pro 2
* Apple AirPods Max * Apple AirPods Max
* Beats X * Beats X
* Beats Flex * Beats Flex
@@ -33,13 +63,10 @@ Legal actions can and will be taken when uploading a compiled version to the Goo
This paragraph was added due to multiple license violations found on the Google Play Store. If you're not sure whether you're allowed to distribute your modified version, please contact the developer. This paragraph was added due to multiple license violations found on the Google Play Store. If you're not sure whether you're allowed to distribute your modified version, please contact the developer.
## Donate ## Donate
[Donate with PayPal](https://www.paypal.me/sineisochronic) <a href="https://www.paypal.me/sineisochronic"><img src="assets/donate-with-paypal.png" alt="Donate with PayPal" height="80"></a>
## Screenshots
<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/screen1.png" width="400"> &nbsp; <img src="fastlane/metadata/android/en-US/images/phoneScreenshots/screen2.png" width="400"> &nbsp; <img src="fastlane/metadata/android/en-US/images/phoneScreenshots/screen3.png" width="400"> &nbsp; <img src="fastlane/metadata/android/en-US/images/phoneScreenshots/screen4.png" width="400">
## License ## License
Copyright (C) 2021 Federico Dossena [![GNU GPLv3 Image](https://www.gnu.org/graphics/gplv3-127x51.png)](https://www.gnu.org/licenses/gpl-3.0.en.html)
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@@ -54,4 +81,9 @@ GNU General Public License for more details.
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
&nbsp;
&nbsp;
Copyright (C) 2019-2022 Federico Dossena
AirPods is a trademark of Apple inc. AirPods is a trademark of Apple inc.
+16 -12
View File
@@ -1,15 +1,17 @@
apply plugin: 'com.android.application' apply plugin: 'com.android.application'
android { android {
compileSdkVersion 31 namespace 'com.dosse.airpods'
buildToolsVersion '30.0.3'
compileSdkVersion 33
buildToolsVersion '33.0.0'
defaultConfig { defaultConfig {
applicationId "com.dosse.airpods" applicationId "com.dosse.airpods"
minSdkVersion 23 minSdkVersion 23
targetSdkVersion 31 targetSdkVersion 33
versionCode 18 versionCode 22
versionName '1.7' versionName '1.9.1'
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
} }
@@ -22,17 +24,19 @@ android {
} }
} }
lintOptions {
abortOnError false
}
compileOptions { compileOptions {
sourceCompatibility = 1.8 sourceCompatibility = 11
targetCompatibility = 1.8 targetCompatibility = 11
}
lint {
abortOnError false
} }
} }
dependencies { dependencies {
implementation 'androidx.appcompat:appcompat:1.3.1' //noinspection GradleDependency 1.5.0/1.5.1 is broken
implementation "androidx.preference:preference:1.1.1" implementation 'androidx.appcompat:appcompat:1.4.2'
implementation 'androidx.preference:preference:1.2.0'
} }
+20
View File
@@ -0,0 +1,20 @@
{
"version": 3,
"artifactType": {
"type": "APK",
"kind": "Directory"
},
"applicationId": "com.dosse.airpods",
"variantName": "release",
"elements": [
{
"type": "SINGLE",
"filters": [],
"attributes": [],
"versionCode": 21,
"versionName": "1.9",
"outputFile": "app-release.apk"
}
],
"elementType": "File"
}
@@ -1,14 +1,18 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.dosse.airpods"> xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.BLUETOOTH" /> <uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" /> <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" /> <uses-permission
android:name="android.permission.BLUETOOTH_SCAN"
android:usesPermissionFlags="neverForLocation"
tools:targetApi="s" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" /> <uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
@@ -23,7 +27,8 @@
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:label="@string/app_name" android:label="@string/app_name"
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/AppTheme"> android:theme="@style/AppTheme"
tools:ignore="DataExtractionRules">
<activity <activity
android:name=".ui.MainActivity" android:name=".ui.MainActivity"
@@ -47,13 +52,10 @@
android:launchMode="singleTop" android:launchMode="singleTop"
android:parentActivityName=".ui.MainActivity" /> android:parentActivityName=".ui.MainActivity" />
<activity <activity android:name=".ui.NoBTActivity" />
android:name=".ui.NoBTActivity"
android:label="@string/app_name" />
<activity <activity
android:name=".ui.IntroActivity" android:name=".ui.IntroActivity"
android:label="@string/app_name"
android:theme="@style/AppTheme.NoActionBar" /> android:theme="@style/AppTheme.NoActionBar" />
<receiver <receiver
@@ -2,51 +2,38 @@ package com.dosse.airpods.notification;
import android.app.Notification; import android.app.Notification;
import android.content.Context; import android.content.Context;
import android.os.Build;
import android.view.View; import android.view.View;
import android.widget.RemoteViews; import android.widget.RemoteViews;
import androidx.core.app.NotificationCompat; import androidx.core.app.NotificationCompat;
import com.dosse.airpods.R; import com.dosse.airpods.R;
import com.dosse.airpods.pods.PodsStatus; import com.dosse.airpods.pods.PodsStatus;
import com.dosse.airpods.pods.data.IPods; import com.dosse.airpods.pods.models.IPods;
import com.dosse.airpods.pods.data.RegularPods; import com.dosse.airpods.pods.models.RegularPods;
import com.dosse.airpods.pods.data.SinglePods; import com.dosse.airpods.pods.models.SinglePods;
import com.dosse.airpods.utils.PermissionUtils;
public class NotificationBuilder { public class NotificationBuilder {
public static final String TAG = "AirPods"; public static final String TAG = "AirPods";
public static final long TIMEOUT_CONNECTED = 30000; public static final long TIMEOUT_CONNECTED = 30000;
public static final int NOTIFICATION_ID = 1; public static final int NOTIFICATION_ID = 1;
private final RemoteViews[] notificationArr, notificationLocation; private final RemoteViews[] notificationArr;
private final Context mContext;
private final NotificationCompat.Builder mBuilder; private final NotificationCompat.Builder mBuilder;
public NotificationBuilder (Context context) { public NotificationBuilder (Context context) {
mContext = context;
notificationArr = new RemoteViews[] {new RemoteViews(context.getPackageName(), R.layout.status_big), new RemoteViews(context.getPackageName(), R.layout.status_small)}; notificationArr = new RemoteViews[] {new RemoteViews(context.getPackageName(), R.layout.status_big), new RemoteViews(context.getPackageName(), R.layout.status_small)};
notificationLocation = new RemoteViews[] {new RemoteViews(context.getPackageName(), R.layout.location_disabled_big), new RemoteViews(context.getPackageName(), R.layout.location_disabled_small)};
mBuilder = new NotificationCompat.Builder(context, TAG); mBuilder = new NotificationCompat.Builder(context, TAG);
mBuilder.setShowWhen(false); mBuilder.setShowWhen(false);
mBuilder.setOngoing(true); mBuilder.setOngoing(true);
mBuilder.setSmallIcon(R.mipmap.notification_icon); mBuilder.setSmallIcon(R.mipmap.notification_icon);
mBuilder.setVisibility(NotificationCompat.VISIBILITY_PUBLIC); mBuilder.setVisibility(NotificationCompat.VISIBILITY_PUBLIC);
mBuilder.setCustomContentView(notificationArr[1]);
mBuilder.setCustomBigContentView(notificationArr[0]);
} }
public Notification build (PodsStatus status) { public Notification build (PodsStatus status) {
// Apparently this restriction was removed ONLY in android Q
if (PermissionUtils.getLocationPermissions(mContext) || Build.VERSION.SDK_INT == Build.VERSION_CODES.Q) {
mBuilder.setCustomContentView(notificationArr[1]);
mBuilder.setCustomBigContentView(notificationArr[0]);
} else {
mBuilder.setCustomContentView(notificationLocation[1]);
mBuilder.setCustomBigContentView(notificationLocation[0]);
}
IPods airpods = status.getAirpods(); IPods airpods = status.getAirpods();
boolean single = airpods.isSingle(); boolean single = airpods.isSingle();
@@ -1,4 +1,4 @@
package com.dosse.airpods.pods.data; package com.dosse.airpods.pods;
import android.view.View; import android.view.View;
@@ -1,5 +1,6 @@
package com.dosse.airpods.pods; package com.dosse.airpods.pods;
import android.annotation.SuppressLint;
import android.app.Notification; import android.app.Notification;
import android.app.NotificationChannel; import android.app.NotificationChannel;
import android.app.NotificationManager; import android.app.NotificationManager;
@@ -59,6 +60,7 @@ public class PodsService extends Service {
* *
* After decoding a beacon, the status is written to PodsStatus so that the NotificationThread can use the information * After decoding a beacon, the status is written to PodsStatus so that the NotificationThread can use the information
*/ */
@SuppressLint("MissingPermission")
private void startAirPodsScanner () { private void startAirPodsScanner () {
try { try {
Logger.debug("START SCANNER"); Logger.debug("START SCANNER");
@@ -71,7 +73,6 @@ public class PodsService extends Service {
return; return;
} }
//if (batterySaver && btScanner != null && scanCallback != null) {
if (btScanner != null && scanCallback != null) { if (btScanner != null && scanCallback != null) {
btScanner.stopScan(scanCallback); btScanner.stopScan(scanCallback);
scanCallback = null; scanCallback = null;
@@ -102,6 +103,7 @@ public class PodsService extends Service {
} }
} }
@SuppressLint("MissingPermission")
private void stopAirPodsScanner () { private void stopAirPodsScanner () {
try { try {
if (btScanner != null && scanCallback != null) { if (btScanner != null && scanCallback != null) {
@@ -132,7 +134,7 @@ public class PodsService extends Service {
public void onCreate () { public void onCreate () {
super.onCreate(); super.onCreate();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
startForeground(101, createBackgroundNotification()); startForeground(101, createBackgroundNotification());
unregisterBtReceiver(); unregisterBtReceiver();
@@ -233,6 +235,7 @@ public class PodsService extends Service {
} }
} }
@SuppressLint("MissingPermission")
private static boolean checkUUID (BluetoothDevice bluetoothDevice) { private static boolean checkUUID (BluetoothDevice bluetoothDevice) {
ParcelUuid[] AIRPODS_UUIDS = { ParcelUuid[] AIRPODS_UUIDS = {
ParcelUuid.fromString("74ec2172-0bad-4d01-8f77-997b2be0722a"), ParcelUuid.fromString("74ec2172-0bad-4d01-8f77-997b2be0722a"),
@@ -1,18 +1,19 @@
package com.dosse.airpods.pods; package com.dosse.airpods.pods;
import com.dosse.airpods.pods.data.AirPods1; import com.dosse.airpods.pods.models.AirPods1;
import com.dosse.airpods.pods.data.AirPods2; import com.dosse.airpods.pods.models.AirPods2;
import com.dosse.airpods.pods.data.AirPodsMax; import com.dosse.airpods.pods.models.AirPods3;
import com.dosse.airpods.pods.data.AirPodsPro; import com.dosse.airpods.pods.models.AirPodsMax;
import com.dosse.airpods.pods.data.BeatsFlex; import com.dosse.airpods.pods.models.AirPodsPro;
import com.dosse.airpods.pods.data.BeatsSolo3; import com.dosse.airpods.pods.models.AirPodsPro2;
import com.dosse.airpods.pods.data.BeatsStudio3; import com.dosse.airpods.pods.models.BeatsFlex;
import com.dosse.airpods.pods.data.BeatsX; import com.dosse.airpods.pods.models.BeatsSolo3;
import com.dosse.airpods.pods.data.IPods; import com.dosse.airpods.pods.models.BeatsStudio3;
import com.dosse.airpods.pods.data.Pod; import com.dosse.airpods.pods.models.BeatsX;
import com.dosse.airpods.pods.data.Powerbeats3; import com.dosse.airpods.pods.models.IPods;
import com.dosse.airpods.pods.data.PowerbeatsPro; import com.dosse.airpods.pods.models.Powerbeats3;
import com.dosse.airpods.pods.data.RegularPods; import com.dosse.airpods.pods.models.PowerbeatsPro;
import com.dosse.airpods.pods.models.RegularPods;
/** /**
* Decoding the beacon: * Decoding the beacon:
@@ -68,29 +69,36 @@ public class PodsStatus {
Pod casePod = new Pod(caseStatus, chargeCase, false); Pod casePod = new Pod(caseStatus, chargeCase, false);
Pod singlePod = new Pod(singleStatus, chargeSingle, false); Pod singlePod = new Pod(singleStatus, chargeSingle, false);
char idSingle = status.charAt(7); // We don't know the full ID for all devices
String idFull = status.substring(6, 10);
// Detect which model // Detect which model
switch (status.charAt(7)) { if ("0220".equals(idFull)) {
case '2': pods = new AirPods1(leftPod, rightPod, casePod); // Airpods 1st gen pods = new AirPods1(leftPod, rightPod, casePod); // Airpods 1st gen
break; } else if ("0F20".equals(idFull)) {
case 'F': pods = new AirPods2(leftPod, rightPod, casePod); // Airpods 2nd gen pods = new AirPods2(leftPod, rightPod, casePod); // Airpods 2nd gen
break; } else if ("1320".equals(idFull)) {
case 'E': pods = new AirPodsPro(leftPod, rightPod, casePod); // Airpods Pro pods = new AirPods3(leftPod, rightPod, casePod); // Airpods 3rd gen
break; } else if ("0E20".equals(idFull)) {
case 'A': pods = new AirPodsMax(singlePod); // Airpods Max pods = new AirPodsPro(leftPod, rightPod, casePod); // Airpods Pro
break; } else if ("1420".equals(idFull)) {
case 'B': pods = new PowerbeatsPro(leftPod, rightPod, casePod); // Powerbeats Pro pods = new AirPodsPro2(leftPod, rightPod, casePod); // Airpods Pro 2
break; } else if ('A' == idSingle) {
case '5': pods = new BeatsX(singlePod); // Beats X pods = new AirPodsMax(singlePod); // Airpods Max
break; } else if ('B' == idSingle) {
case '0': pods = new BeatsFlex(singlePod); // Beats Flex pods = new PowerbeatsPro(leftPod, rightPod, casePod); // Powerbeats Pro
break; } else if ("0520".equals(idFull)) {
case '6': pods = new BeatsSolo3(singlePod); // Beats Solo 3 pods = new BeatsX(singlePod); // Beats X
break; } else if ("1020".equals(idFull)) {
case '9': pods = new BeatsStudio3(singlePod); // Beats Studio 3 pods = new BeatsFlex(singlePod); // Beats Flex
break; } else if ("0620".equals(idFull)) {
case '3': pods = new Powerbeats3(singlePod); // Powerbeats 3 pods = new BeatsSolo3(singlePod); // Beats Solo 3
break; } else if ('9' == idSingle) {
default: pods = new RegularPods(leftPod, rightPod, casePod); // Unknown pods = new BeatsStudio3(singlePod); // Beats Studio 3
} else if ("0320".equals(idFull)) {
pods = new Powerbeats3(singlePod); // Powerbeats 3
} else {
pods = new RegularPods(leftPod, rightPod, casePod); // Unknown
} }
} }
@@ -1,4 +1,6 @@
package com.dosse.airpods.pods.data; package com.dosse.airpods.pods.models;
import com.dosse.airpods.pods.Pod;
public class AirPods1 extends RegularPods { public class AirPods1 extends RegularPods {
@@ -8,7 +10,7 @@ public class AirPods1 extends RegularPods {
@Override @Override
public String getModel () { public String getModel () {
return MODEL_AIRPODS_GEN1; return Constants.MODEL_AIRPODS_GEN1;
} }
} }
@@ -1,4 +1,6 @@
package com.dosse.airpods.pods.data; package com.dosse.airpods.pods.models;
import com.dosse.airpods.pods.Pod;
public class AirPods2 extends RegularPods { public class AirPods2 extends RegularPods {
@@ -8,7 +10,7 @@ public class AirPods2 extends RegularPods {
@Override @Override
public String getModel () { public String getModel () {
return MODEL_AIRPODS_GEN2; return Constants.MODEL_AIRPODS_GEN2;
} }
} }
@@ -0,0 +1,16 @@
package com.dosse.airpods.pods.models;
import com.dosse.airpods.pods.Pod;
public class AirPods3 extends RegularPods {
public AirPods3(Pod leftPod, Pod rightPod, Pod casePod) {
super(leftPod, rightPod, casePod);
}
@Override
public String getModel () {
return Constants.MODEL_AIRPODS_GEN3;
}
}
@@ -1,6 +1,7 @@
package com.dosse.airpods.pods.data; package com.dosse.airpods.pods.models;
import com.dosse.airpods.R; import com.dosse.airpods.R;
import com.dosse.airpods.pods.Pod;
public class AirPodsMax extends SinglePods { public class AirPodsMax extends SinglePods {
@@ -15,7 +16,7 @@ public class AirPodsMax extends SinglePods {
@Override @Override
public String getModel () { public String getModel () {
return MODEL_AIRPODS_MAX; return Constants.MODEL_AIRPODS_MAX;
} }
} }
@@ -1,6 +1,7 @@
package com.dosse.airpods.pods.data; package com.dosse.airpods.pods.models;
import com.dosse.airpods.R; import com.dosse.airpods.R;
import com.dosse.airpods.pods.Pod;
public class AirPodsPro extends RegularPods { public class AirPodsPro extends RegularPods {
@@ -25,7 +26,7 @@ public class AirPodsPro extends RegularPods {
@Override @Override
public String getModel () { public String getModel () {
return MODEL_AIRPODS_PRO; return Constants.MODEL_AIRPODS_PRO;
} }
} }
@@ -0,0 +1,32 @@
package com.dosse.airpods.pods.models;
import com.dosse.airpods.R;
import com.dosse.airpods.pods.Pod;
public class AirPodsPro2 extends RegularPods {
public AirPodsPro2 (Pod leftPod, Pod rightPod, Pod casePod) {
super(leftPod, rightPod, casePod);
}
@Override
public int getLeftDrawable () {
return getPod(LEFT).isConnected() ? R.drawable.podpro : R.drawable.podpro_disconnected;
}
@Override
public int getRightDrawable () {
return getPod(RIGHT).isConnected() ? R.drawable.podpro : R.drawable.podpro_disconnected;
}
@Override
public int getCaseDrawable () {
return getPod(CASE).isConnected() ? R.drawable.podpro_case : R.drawable.podpro_case_disconnected;
}
@Override
public String getModel () {
return Constants.MODEL_AIRPODS_PRO_2;
}
}
@@ -1,6 +1,7 @@
package com.dosse.airpods.pods.data; package com.dosse.airpods.pods.models;
import com.dosse.airpods.R; import com.dosse.airpods.R;
import com.dosse.airpods.pods.Pod;
public class BeatsFlex extends SinglePods { public class BeatsFlex extends SinglePods {
@@ -15,7 +16,7 @@ public class BeatsFlex extends SinglePods {
@Override @Override
public String getModel () { public String getModel () {
return MODEL_BEATS_FLEX; return Constants.MODEL_BEATS_FLEX;
} }
} }
@@ -1,6 +1,7 @@
package com.dosse.airpods.pods.data; package com.dosse.airpods.pods.models;
import com.dosse.airpods.R; import com.dosse.airpods.R;
import com.dosse.airpods.pods.Pod;
public class BeatsSolo3 extends SinglePods { public class BeatsSolo3 extends SinglePods {
@@ -15,7 +16,7 @@ public class BeatsSolo3 extends SinglePods {
@Override @Override
public String getModel () { public String getModel () {
return MODEL_BEATS_SOLO_3; return Constants.MODEL_BEATS_SOLO_3;
} }
} }
@@ -1,6 +1,7 @@
package com.dosse.airpods.pods.data; package com.dosse.airpods.pods.models;
import com.dosse.airpods.R; import com.dosse.airpods.R;
import com.dosse.airpods.pods.Pod;
public class BeatsStudio3 extends SinglePods { public class BeatsStudio3 extends SinglePods {
@@ -15,7 +16,7 @@ public class BeatsStudio3 extends SinglePods {
@Override @Override
public String getModel () { public String getModel () {
return MODEL_BEATS_STUDIO_3; return Constants.MODEL_BEATS_STUDIO_3;
} }
} }
@@ -1,6 +1,7 @@
package com.dosse.airpods.pods.data; package com.dosse.airpods.pods.models;
import com.dosse.airpods.R; import com.dosse.airpods.R;
import com.dosse.airpods.pods.Pod;
public class BeatsX extends SinglePods { public class BeatsX extends SinglePods {
@@ -15,7 +16,7 @@ public class BeatsX extends SinglePods {
@Override @Override
public String getModel () { public String getModel () {
return MODEL_BEATS_X; return Constants.MODEL_BEATS_X;
} }
} }
@@ -0,0 +1,17 @@
package com.dosse.airpods.pods.models;
public final class Constants {
static final String MODEL_AIRPODS_GEN1 = "airpods1";
static final String MODEL_AIRPODS_GEN2 = "airpods2";
static final String MODEL_AIRPODS_GEN3 = "airpods3";
static final String MODEL_AIRPODS_PRO = "airpodspro";
static final String MODEL_AIRPODS_PRO_2 = "airpodspro2";
static final String MODEL_AIRPODS_MAX = "airpodsmax";
static final String MODEL_BEATS_X = "beatsx";
static final String MODEL_BEATS_FLEX = "beatsflex";
static final String MODEL_BEATS_SOLO_3 = "beatssolo3";
static final String MODEL_BEATS_STUDIO_3 = "beatsstudio3";
static final String MODEL_POWERBEATS_3 = "powerbeats3";
static final String MODEL_POWERBEATS_PRO = "powerbeatspro";
static final String MODEL_UNKNOWN = "unknown";
}
@@ -0,0 +1,13 @@
package com.dosse.airpods.pods.models;
public interface IPods {
String getModel ();
boolean isSingle ();
boolean isDisconnected ();
String parseStatusForLogger ();
}
@@ -1,6 +1,7 @@
package com.dosse.airpods.pods.data; package com.dosse.airpods.pods.models;
import com.dosse.airpods.R; import com.dosse.airpods.R;
import com.dosse.airpods.pods.Pod;
public class Powerbeats3 extends SinglePods { public class Powerbeats3 extends SinglePods {
@@ -15,7 +16,7 @@ public class Powerbeats3 extends SinglePods {
@Override @Override
public String getModel () { public String getModel () {
return MODEL_POWERBEATS_3; return Constants.MODEL_POWERBEATS_3;
} }
} }
@@ -1,6 +1,7 @@
package com.dosse.airpods.pods.data; package com.dosse.airpods.pods.models;
import com.dosse.airpods.R; import com.dosse.airpods.R;
import com.dosse.airpods.pods.Pod;
public class PowerbeatsPro extends RegularPods { public class PowerbeatsPro extends RegularPods {
@@ -25,7 +26,7 @@ public class PowerbeatsPro extends RegularPods {
@Override @Override
public String getModel () { public String getModel () {
return MODEL_POWERBEATS_PRO; return Constants.MODEL_POWERBEATS_PRO;
} }
} }
@@ -1,6 +1,7 @@
package com.dosse.airpods.pods.data; package com.dosse.airpods.pods.models;
import com.dosse.airpods.R; import com.dosse.airpods.R;
import com.dosse.airpods.pods.Pod;
import java.util.Locale; import java.util.Locale;
@@ -48,7 +49,7 @@ public class RegularPods implements IPods {
@Override @Override
public String getModel () { public String getModel () {
return MODEL_UNKNOWN; return Constants.MODEL_UNKNOWN;
} }
@Override @Override
@@ -1,4 +1,6 @@
package com.dosse.airpods.pods.data; package com.dosse.airpods.pods.models;
import com.dosse.airpods.pods.Pod;
import java.util.Locale; import java.util.Locale;
@@ -25,7 +25,7 @@ public class StartupReceiver extends BroadcastReceiver {
} }
public static void startPodsService (Context context) { public static void startPodsService (Context context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
context.startForegroundService(new Intent(context, PodsService.class)); context.startForegroundService(new Intent(context, PodsService.class));
else else
context.startService(new Intent(context, PodsService.class)); context.startService(new Intent(context, PodsService.class));
@@ -28,8 +28,8 @@ public class IntroActivity extends AppCompatActivity {
private TextView msg; private TextView msg;
private Button btn; private Button btn;
private static final int STEP_PERMISSION_BLUETOOTH = 1, STEP_PERMISSION_BATTERY_OPTIMIZATION = 2, STEP_PERMISSION_LOCATION = 3, STEP_PERMISSION_BACKGROUND_LOCATION = 4; private static final int STEP_PERMISSION_BLUETOOTH = 1, STEP_PERMISSION_BATTERY_OPTIMIZATION = 2, STEP_PERMISSION_LOCATION = 3, STEP_PERMISSION_BACKGROUND_LOCATION = 4, STEP_PERMISSION_NOTIFICATION = 5;
private static final int BLUETOOTH_REQUEST_CODE = 101, LOCATION_REQUEST_CODE = 102, BACKGROUND_LOCATION_REQUEST_CODE = 103; private static final int BLUETOOTH_REQUEST_CODE = 101, LOCATION_REQUEST_CODE = 102, BACKGROUND_LOCATION_REQUEST_CODE = 103, NOTIFICATION_REQUEST_CODE = 104;
@Override @Override
protected void onCreate (Bundle savedInstanceState) { protected void onCreate (Bundle savedInstanceState) {
@@ -72,16 +72,17 @@ public class IntroActivity extends AppCompatActivity {
return STEP_PERMISSION_BATTERY_OPTIMIZATION; return STEP_PERMISSION_BATTERY_OPTIMIZATION;
if (!PermissionUtils.getFineLocationPermission(this)) if (!PermissionUtils.getFineLocationPermission(this))
return STEP_PERMISSION_LOCATION; return STEP_PERMISSION_LOCATION;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) if (!PermissionUtils.getBackgroundLocationPermission(this)) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) if (!PermissionUtils.getBackgroundLocationPermission(this))
return STEP_PERMISSION_BACKGROUND_LOCATION; return STEP_PERMISSION_BACKGROUND_LOCATION;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) if (!PermissionUtils.getNotificationPermissions(this))
return STEP_PERMISSION_NOTIFICATION;
return 0; return 0;
} }
@SuppressLint("BatteryLife") @SuppressLint("BatteryLife")
private void initScreen () { private void initScreen () {
int currentStep = getPermissionState() - ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) ? 0 : 1); int currentStep = getPermissionState() - ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) ? 0 : 1);
int numOfSteps = (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) ? ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) ? 4 : 3) : 2; int numOfSteps = (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) ? 5 : (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) ? ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) ? 4 : 3) : 2;
runOnUiThread(() -> { runOnUiThread(() -> {
switch (getPermissionState()) { switch (getPermissionState()) {
@@ -109,9 +110,14 @@ public class IntroActivity extends AppCompatActivity {
case STEP_PERMISSION_BACKGROUND_LOCATION: case STEP_PERMISSION_BACKGROUND_LOCATION:
msg.setText(String.format(Locale.getDefault(), "%s %d/%d: %s", getString(R.string.intro_step), currentStep, numOfSteps, getString(R.string.intro_loc2_perm))); msg.setText(String.format(Locale.getDefault(), "%s %d/%d: %s", getString(R.string.intro_step), currentStep, numOfSteps, getString(R.string.intro_loc2_perm)));
btn.setOnClickListener(view -> { btn.setOnClickListener(view -> {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) requestPermissions(new String[] {Manifest.permission.ACCESS_BACKGROUND_LOCATION}, BACKGROUND_LOCATION_REQUEST_CODE); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) requestPermissions(new String[] {Manifest.permission.ACCESS_BACKGROUND_LOCATION}, BACKGROUND_LOCATION_REQUEST_CODE);
}); });
break; break;
case STEP_PERMISSION_NOTIFICATION:
msg.setText(String.format(Locale.getDefault(), "%s %d/%d: %s", getString(R.string.intro_step), currentStep, numOfSteps, getString(R.string.intro_notif_perm)));
btn.setOnClickListener(view -> {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) requestPermissions(new String[] {Manifest.permission.POST_NOTIFICATIONS}, NOTIFICATION_REQUEST_CODE);
});
} }
btn.setText(String.format(Locale.getDefault(), "%s (%d/%d)", getString(R.string.intro_allow), currentStep, numOfSteps)); btn.setText(String.format(Locale.getDefault(), "%s (%d/%d)", getString(R.string.intro_allow), currentStep, numOfSteps));
}); });
@@ -130,6 +136,9 @@ public class IntroActivity extends AppCompatActivity {
case BACKGROUND_LOCATION_REQUEST_CODE: case BACKGROUND_LOCATION_REQUEST_CODE:
if (!hasAllPermissionsGranted(grantResults)) Toast.makeText(this, getString(R.string.msg_loc2_perm), Toast.LENGTH_LONG).show(); if (!hasAllPermissionsGranted(grantResults)) Toast.makeText(this, getString(R.string.msg_loc2_perm), Toast.LENGTH_LONG).show();
break; break;
case NOTIFICATION_REQUEST_CODE:
if (!hasAllPermissionsGranted(grantResults)) Toast.makeText(this, getString(R.string.msg_notif_perm), Toast.LENGTH_LONG).show();
break;
} }
} }
@@ -21,6 +21,11 @@ public class PermissionUtils {
} }
} }
@RequiresApi(api = Build.VERSION_CODES.TIRAMISU)
public static boolean getNotificationPermissions (Context context) {
return checkSelfPermission(context, Manifest.permission.POST_NOTIFICATIONS) == PackageManager.PERMISSION_GRANTED;
}
@RequiresApi(api = Build.VERSION_CODES.S) @RequiresApi(api = Build.VERSION_CODES.S)
public static boolean getBluetoothPermissions (Context context) { public static boolean getBluetoothPermissions (Context context) {
return checkSelfPermission(context, Manifest.permission.BLUETOOTH_SCAN) == PackageManager.PERMISSION_GRANTED return checkSelfPermission(context, Manifest.permission.BLUETOOTH_SCAN) == PackageManager.PERMISSION_GRANTED
@@ -37,13 +42,15 @@ public class PermissionUtils {
} }
public static boolean getLocationPermissions (Context context) { public static boolean getLocationPermissions (Context context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
return getFineLocationPermission(context) && getBackgroundLocationPermission(context); return getFineLocationPermission(context) && getBackgroundLocationPermission(context);
else else
return getFineLocationPermission(context); return getFineLocationPermission(context);
} }
public static boolean checkAllPermissions (Context context) { public static boolean checkAllPermissions (Context context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU)
return getNotificationPermissions(context) && getBatteryOptimizationsPermission(context) && getLocationPermissions(context) && getBluetoothPermissions(context);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S)
return getBatteryOptimizationsPermission(context) && getLocationPermissions(context) && getBluetoothPermissions(context); return getBatteryOptimizationsPermission(context) && getLocationPermissions(context) && getBluetoothPermissions(context);
else else

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 46 KiB

Before

Width:  |  Height:  |  Size: 45 KiB

After

Width:  |  Height:  |  Size: 45 KiB

Before

Width:  |  Height:  |  Size: 151 KiB

After

Width:  |  Height:  |  Size: 151 KiB

Before

Width:  |  Height:  |  Size: 150 KiB

After

Width:  |  Height:  |  Size: 150 KiB

Before

Width:  |  Height:  |  Size: 143 KiB

After

Width:  |  Height:  |  Size: 143 KiB

Before

Width:  |  Height:  |  Size: 142 KiB

After

Width:  |  Height:  |  Size: 142 KiB

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 46 KiB

Before

Width:  |  Height:  |  Size: 45 KiB

After

Width:  |  Height:  |  Size: 45 KiB

Before

Width:  |  Height:  |  Size: 92 KiB

After

Width:  |  Height:  |  Size: 92 KiB

Before

Width:  |  Height:  |  Size: 111 KiB

After

Width:  |  Height:  |  Size: 111 KiB

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Before

Width:  |  Height:  |  Size: 551 KiB

After

Width:  |  Height:  |  Size: 551 KiB

Before

Width:  |  Height:  |  Size: 543 KiB

After

Width:  |  Height:  |  Size: 543 KiB

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 29 KiB

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 40 KiB

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 40 KiB

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Before

Width:  |  Height:  |  Size: 161 KiB

After

Width:  |  Height:  |  Size: 161 KiB

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 46 KiB

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 44 KiB

Before

Width:  |  Height:  |  Size: 161 KiB

After

Width:  |  Height:  |  Size: 161 KiB

@@ -52,6 +52,11 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/airpods1" /> android:text="@string/airpods1" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/airpods3" />
<TextView <TextView
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@@ -89,6 +94,11 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/airpodsmax" /> android:text="@string/airpodsmax" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/airpodspro2" />
<TextView <TextView
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@@ -45,11 +45,21 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/airpods2" /> android:text="@string/airpods2" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/airpods3" />
<TextView <TextView
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/airpodspro" /> android:text="@string/airpodspro" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/airpodspro2" />
<TextView <TextView
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Before

Width:  |  Height:  |  Size: 658 B

After

Width:  |  Height:  |  Size: 658 B

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Before

Width:  |  Height:  |  Size: 341 B

After

Width:  |  Height:  |  Size: 341 B

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Some files were not shown because too many files have changed in this diff Show More