commit 0cf6e520a7ce58330c408122ebb5fe7b7a5824bb Author: adolfintel Date: Tue Mar 12 09:13:39 2019 +0100 Initial commit diff --git a/.github/logo.png b/.github/logo.png new file mode 100644 index 0000000..826eabd Binary files /dev/null and b/.github/logo.png differ diff --git a/.github/screen1.png b/.github/screen1.png new file mode 100644 index 0000000..53c4884 Binary files /dev/null and b/.github/screen1.png differ diff --git a/.github/screen2.png b/.github/screen2.png new file mode 100644 index 0000000..ffbb012 Binary files /dev/null and b/.github/screen2.png differ diff --git a/.github/screen3.png b/.github/screen3.png new file mode 100644 index 0000000..16be31f Binary files /dev/null and b/.github/screen3.png differ diff --git a/.github/screen4.png b/.github/screen4.png new file mode 100644 index 0000000..a96a202 Binary files /dev/null and b/.github/screen4.png differ diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..eb3a2f7 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/_old_ diff --git a/OpenPods/.gitignore b/OpenPods/.gitignore new file mode 100644 index 0000000..2b75303 --- /dev/null +++ b/OpenPods/.gitignore @@ -0,0 +1,13 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild diff --git a/OpenPods/.idea/codeStyles/Project.xml b/OpenPods/.idea/codeStyles/Project.xml new file mode 100644 index 0000000..30aa626 --- /dev/null +++ b/OpenPods/.idea/codeStyles/Project.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/OpenPods/.idea/gradle.xml b/OpenPods/.idea/gradle.xml new file mode 100644 index 0000000..2996d53 --- /dev/null +++ b/OpenPods/.idea/gradle.xml @@ -0,0 +1,15 @@ + + + + + + \ No newline at end of file diff --git a/OpenPods/.idea/misc.xml b/OpenPods/.idea/misc.xml new file mode 100644 index 0000000..af0bbdd --- /dev/null +++ b/OpenPods/.idea/misc.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/OpenPods/.idea/runConfigurations.xml b/OpenPods/.idea/runConfigurations.xml new file mode 100644 index 0000000..7f68460 --- /dev/null +++ b/OpenPods/.idea/runConfigurations.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/OpenPods/app/.gitignore b/OpenPods/app/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/OpenPods/app/.gitignore @@ -0,0 +1 @@ +/build diff --git a/OpenPods/app/build.gradle b/OpenPods/app/build.gradle new file mode 100644 index 0000000..5159a7e --- /dev/null +++ b/OpenPods/app/build.gradle @@ -0,0 +1,24 @@ +apply plugin: 'com.android.application' + +android { + compileSdkVersion 28 + defaultConfig { + applicationId "com.dosse.airpods" + minSdkVersion 23 + targetSdkVersion 28 + versionCode 1 + versionName "1.0" + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } +} + +dependencies { + implementation fileTree(include: ['*.jar'], dir: 'libs') + implementation 'com.android.support:appcompat-v7:28.0.0' +} diff --git a/OpenPods/app/proguard-rules.pro b/OpenPods/app/proguard-rules.pro new file mode 100644 index 0000000..f1b4245 --- /dev/null +++ b/OpenPods/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/OpenPods/app/src/main/AndroidManifest.xml b/OpenPods/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..6a34de2 --- /dev/null +++ b/OpenPods/app/src/main/AndroidManifest.xml @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/OpenPods/app/src/main/java/com/dosse/airpods/IntroActivity.java b/OpenPods/app/src/main/java/com/dosse/airpods/IntroActivity.java new file mode 100644 index 0000000..24f86fb --- /dev/null +++ b/OpenPods/app/src/main/java/com/dosse/airpods/IntroActivity.java @@ -0,0 +1,68 @@ +package com.dosse.airpods; + +import android.Manifest; +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 java.util.Timer; +import java.util.TimerTask; + +public class IntroActivity extends AppCompatActivity { + + private Timer t; + @Override + 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) { + requestPermissions(new String[]{Manifest.permission.ACCESS_COARSE_LOCATION}, 1); + try { + 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) { + } + } + }); + t= new Timer(); + t.scheduleAtFixedRate(new TimerTask() { + @Override + public void run() { + boolean ok=true; + if (Build.VERSION.SDK_INT>= Build.VERSION_CODES.O) { + if(!getSystemService(PowerManager.class).isIgnoringBatteryOptimizations(getPackageName())) ok=false; + } + 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); + finish(); + } + } + },0,100); + + } + + @Override + protected void onDestroy() { + super.onDestroy(); + if(t!=null) t.cancel(); + } +} diff --git a/OpenPods/app/src/main/java/com/dosse/airpods/MainActivity.java b/OpenPods/app/src/main/java/com/dosse/airpods/MainActivity.java new file mode 100644 index 0000000..788ed58 --- /dev/null +++ b/OpenPods/app/src/main/java/com/dosse/airpods/MainActivity.java @@ -0,0 +1,55 @@ +package com.dosse.airpods; + +import android.Manifest; +import android.bluetooth.BluetoothManager; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.os.Build; +import android.os.PowerManager; +import android.support.v4.content.ContextCompat; +import android.support.v7.app.AppCompatActivity; +import android.os.Bundle; +import android.view.View; +import android.widget.Button; +import android.widget.Toast; + +public class MainActivity extends AppCompatActivity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + if(((BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE)).getAdapter()==null){ + Intent i=new Intent(this,NoBTActivity.class); + startActivity(i); + finish(); + return; + } + 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){ + startService(new Intent(getApplicationContext(),PodsService.class)); + }else{ + Intent i=new Intent(this,IntroActivity.class); + startActivity(i); + finish(); + } + ((Button)(findViewById(R.id.mainHide))).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + PackageManager p = getPackageManager(); + p.setComponentEnabledSetting(new ComponentName(MainActivity.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){} + } + }); + } + + + +} diff --git a/OpenPods/app/src/main/java/com/dosse/airpods/NoBTActivity.java b/OpenPods/app/src/main/java/com/dosse/airpods/NoBTActivity.java new file mode 100644 index 0000000..61d7f9f --- /dev/null +++ b/OpenPods/app/src/main/java/com/dosse/airpods/NoBTActivity.java @@ -0,0 +1,13 @@ +package com.dosse.airpods; + +import android.support.v7.app.AppCompatActivity; +import android.os.Bundle; + +public class NoBTActivity extends AppCompatActivity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_no_bt); + } +} diff --git a/OpenPods/app/src/main/java/com/dosse/airpods/PodsService.java b/OpenPods/app/src/main/java/com/dosse/airpods/PodsService.java new file mode 100644 index 0000000..e1104c0 --- /dev/null +++ b/OpenPods/app/src/main/java/com/dosse/airpods/PodsService.java @@ -0,0 +1,251 @@ +package com.dosse.airpods; + +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.app.Service; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothManager; +import android.bluetooth.le.BluetoothLeScanner; +import android.bluetooth.le.ScanCallback; +import android.bluetooth.le.ScanResult; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.Build; +import android.os.IBinder; +import android.os.ParcelUuid; +import android.support.v4.app.NotificationCompat; +import android.util.Log; +import android.widget.RemoteViews; + +public class PodsService extends Service { + + private static NotificationThread n=null; + private static BluetoothLeScanner btScanner; + private static int leftStatus=15, rightStatus=15, caseStatus=15; + private static boolean chargeL=false, chargeR=false, chargeCase=false; + private static long lastSeenConnected=0; + private static final long TIMEOUT_CONNECTED=30000; + private static boolean maybeConnected =true; + + private void startAirPodsScanner() { + try { + BluetoothManager btManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE); + BluetoothAdapter btAdapter = btManager.getAdapter(); + btScanner = btAdapter.getBluetoothLeScanner(); + if (btAdapter == null) throw new Exception("No BT"); + if (!btAdapter.isEnabled()) throw new Exception("BT Off"); + btScanner.startScan(new ScanCallback() { + @Override + public void onScanResult(int callbackType, ScanResult result) { + try { + byte[] data = result.getScanRecord().getManufacturerSpecificData(76); + if (data == null) return; + String a = decodeHex(data); + if (a.length() < 16) return; + 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; + lastSeenConnected = System.currentTimeMillis(); + } catch (Throwable t) { + Log.d("PODS", "" + t); + } + } + }); + } catch (Throwable t) { + Log.d("PODS", "" + t); + } + } + + 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]; + } + return new String(ret); + } + + private boolean isFlipped(String str) { + return (Integer.toString(Integer.parseInt(""+str.charAt(10),16)+0x10,2)).charAt(3)=='0'; + } + + private static final String TAG="AirPods"; + private class NotificationThread extends Thread{ + public void run(){ + boolean notificationShowing=false; + RemoteViews notificationBig=new RemoteViews(getPackageName(),R.layout.status_big); + RemoteViews notificationSmall=new RemoteViews(getPackageName(),R.layout.status_small); + NotificationManager mNotifyManager=(NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); + NotificationCompat.Builder mBuilder=new NotificationCompat.Builder(PodsService.this,TAG); + 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); + channel.enableLights(false); + channel.setShowBadge(true); + mNotifyManager.createNotificationChannel(channel); + } + mBuilder.setCustomContentView(notificationSmall); + mBuilder.setCustomBigContentView(notificationBig); + mBuilder.setShowWhen(false); + mBuilder.setOngoing(true); + mBuilder.setSmallIcon(R.drawable.left_pod); + for(;;){ + if(maybeConnected &&!(leftStatus==15&&rightStatus==15&&caseStatus==15)){ + if(!notificationShowing){ + Log.d("PODS","Creating notification"); + notificationShowing=true; + mNotifyManager.notify(1,mBuilder.build()); + } + }else{ + if(notificationShowing){ + Log.d("PODS","Removing notification"); + notificationShowing=false; + mNotifyManager.cancel(1); + } + } + if(notificationShowing){ + Log.d("PODS","Left: "+leftStatus+(chargeL?"+":"")+" "+"Right: "+rightStatus+(chargeR?"+":"")+" "+"Case: "+caseStatus+(chargeCase?"+":"")); + 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); + if(System.currentTimeMillis()-lastSeenConnected + + + + + + + + + + +