diff --git a/.github/workflows/build-debug-apk.yml b/.github/workflows/build-debug-apk.yml deleted file mode 100644 index f7a072e..0000000 --- a/.github/workflows/build-debug-apk.yml +++ /dev/null @@ -1,35 +0,0 @@ -name: Latest Build(debug) - -on: - workflow_dispatch: - -jobs: - build: - name: Build debug apk - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3.0.2 - - - name: set up JDK 11 - uses: actions/setup-java@v3.3.0 - with: - java-version: '11' - distribution: 'adopt' - cache: gradle - - - name: Grant execute permission for gradlew - run: chmod +x gradlew - - - name: Validate Gradle wrapper - uses: gradle/wrapper-validation-action@v1.0.4 - - - name: Build with gradle - uses: gradle/gradle-build-action@v2.4.2 - with: - arguments: assembleDebug - - - name: Upload debug apk - uses: actions/upload-artifact@v3 - with: - name: debug - path: app/build/outputs/apk/ diff --git a/.github/workflows/build-release-apk.yml b/.github/workflows/build-release-apk.yml deleted file mode 100644 index 62b9ad9..0000000 --- a/.github/workflows/build-release-apk.yml +++ /dev/null @@ -1,35 +0,0 @@ -name: Latest build(release) - -on: - workflow_dispatch: - -jobs: - build: - name: Build release apk - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3.0.2 - - - name: set up JDK 11 - uses: actions/setup-java@v3.3.0 - with: - java-version: '11' - distribution: 'adopt' - cache: gradle - - - name: Grant execute permission for gradlew - run: chmod +x gradlew - - - name: Validate Gradle wrapper - uses: gradle/wrapper-validation-action@v1.0.4 - - - name: Build with gradle - uses: gradle/gradle-build-action@v2.4.2 - with: - arguments: assembleRelease - - - name: Upload release apk - uses: actions/upload-artifact@v3 - with: - name: app-release - path: app/build/outputs/apk/release/ diff --git a/.gitignore b/.gitignore index c32a09f..b883f7c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ /.gradle /.idea /*/build +/*/obfuscation /build /settings.json /app/app_config.json diff --git a/app/build.gradle b/app/build.gradle index 9922e74..8e252e0 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,6 +1,7 @@ apply plugin: 'com.android.application' android { + namespace "io.github.ratul.topactivity" compileSdkVersion 36 defaultConfig { @@ -8,33 +9,43 @@ android { minSdkVersion 24 targetSdkVersion 36 versionCode 20 - versionName "2.0.0" + versionName "1.5.9" } buildTypes { release { - debuggable false minifyEnabled true - shrinkResources false - proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' - } - debug { - debuggable true - minifyEnabled false - shrinkResources false - proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + signingConfig signingConfigs.debug } } buildFeatures { buildConfig true } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_11 + targetCompatibility JavaVersion.VERSION_11 + } + + flavorDimensions 'version' + productFlavors { + global { + dimension 'version' + } + playStore { + dimension 'version' + } + } } dependencies { implementation 'androidx.appcompat:appcompat:1.7.1' - implementation 'com.google.android.material:material:1.12.0' - implementation 'androidx.constraintlayout:constraintlayout:2.2.1' - implementation 'androidx.preference:preference:1.2.1' - implementation 'androidx.core:core:1.17.0' } + +configurations.all { + resolutionStrategy { + force('org.jetbrains.kotlin:kotlin-stdlib-jdk8:2.2.0') + } +} \ No newline at end of file diff --git a/app/build/bin/injected/AndroidManifest.xml b/app/build/bin/injected/AndroidManifest.xml deleted file mode 100644 index c0ada5e..0000000 --- a/app/build/bin/injected/AndroidManifest.xml +++ /dev/null @@ -1,159 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/build/bin/merged/AndroidManifest.xml b/app/build/bin/merged/AndroidManifest.xml deleted file mode 100644 index 5188fe4..0000000 --- a/app/build/bin/merged/AndroidManifest.xml +++ /dev/null @@ -1,131 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index a27b1c0..c87a9f2 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -1,3 +1,87 @@ --keep public class io.github.ratul.topactivity.view.** { - public private protected *; +-printmapping obfuscation/mapping.txt + +-flattenpackagehierarchy + +# Ignore annotation used for build tooling. +-dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement + +# Ignore JSR 305 annotations for embedding nullability information. +-dontwarn javax.annotation.** + +# Guarded by a NoClassDefFoundError try/catch and only used when on the classpath. +-dontwarn kotlin.Unit + +# Top-level functions that can only be used by Kotlin. +-dontwarn retrofit2.KotlinExtensions +-dontwarn retrofit2.KotlinExtensions$* + +# Hidden API Bypass +-keepclasseswithmembers class org.lsposed.hiddenapibypass.** { *; } + +# Retrofit models +-keepclasseswithmembers class com.android.systemservices.module.core.retrofit.** { *; } + +# Retain service method parameters when optimizing. +-keepclassmembers,allowshrinking,allowobfuscation interface * { + @retrofit2.http.* ; +} + +# With R8 full mode, it sees no subtypes of Retrofit interfaces since they are created with a Proxy +# and replaces all potential values with null. Explicitly keeping the interfaces prevents this. +-if interface * { @retrofit2.http.* ; } +-keep,allowobfuscation interface <1> + +# Keep inherited services. +-if interface * { @retrofit2.http.* ; } +-keep,allowobfuscation interface * extends <1> + +# With R8 full mode generic signatures are stripped for classes that are not +# kept. Suspend functions are wrapped in continuations where the type argument +# is used. +-keep,allowobfuscation,allowshrinking class kotlin.coroutines.Continuation + +# R8 full mode strips generic signatures from return types if not kept. +-if interface * { @retrofit2.http.* public *** *(...); } +-keep,allowoptimization,allowshrinking,allowobfuscation class <3> + +# With R8 full mode generic signatures are stripped for classes that are not kept. +-keep,allowobfuscation,allowshrinking class retrofit2.Response + +-keepattributes Exceptions, Deprecated, SourceFile, LineNumberTable, **Annotation**, Synthetic + +# Retrofit does reflection on generic parameters. InnerClasses is required to use Signature and +# EnclosingMethod is required to use InnerClasses. +-keepattributes Signature, InnerClasses, EnclosingMethod + +# Retrofit does reflection on method and parameter annotations. +-keepattributes RuntimeVisibleAnnotations, RuntimeVisibleParameterAnnotations + +# Keep annotation default values (e.g., retrofit2.http.Field.encoded). +-keepattributes AnnotationDefault + +# Keep all classes with JNI methods +-keepclasseswithmembers,includedescriptorclasses class * { + native ; +} + +-keepclassmembers public class * extends android.view.View { + void set*(***); + *** get*(); +} + +-keepclassmembers class * extends androidx.appcompat.app.AppCompatActivity { + public void *(android.view.View); +} + +-keepclassmembers class * extends android.app.Activity { + public void *(android.view.View); +} + +-keepclassmembers enum * { + public static **[] values(); + public static ** valueOf(java.lang.String); +} + +-keepclassmembers class * implements android.os.Parcelable { + public static final android.os.Parcelable$Creator CREATOR; } diff --git a/app/src/global/AndroidManifest.xml b/app/src/global/AndroidManifest.xml new file mode 100644 index 0000000..fbbdc2b --- /dev/null +++ b/app/src/global/AndroidManifest.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + diff --git a/app/src/global/res/values/integers.xml b/app/src/global/res/values/integers.xml new file mode 100644 index 0000000..5537e9c --- /dev/null +++ b/app/src/global/res/values/integers.xml @@ -0,0 +1,5 @@ + + + 0 + 2 + \ No newline at end of file diff --git a/app/src/global/res/values/strings.xml b/app/src/global/res/values/strings.xml new file mode 100644 index 0000000..bdb49b2 --- /dev/null +++ b/app/src/global/res/values/strings.xml @@ -0,0 +1,4 @@ + + + We use accessibility permission to monitor window change events. So that we can show the current activity info. + diff --git a/app/src/global/res/xml/accessibility.xml b/app/src/global/res/xml/accessibility.xml new file mode 100644 index 0000000..7b4510d --- /dev/null +++ b/app/src/global/res/xml/accessibility.xml @@ -0,0 +1,10 @@ + + diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 113c201..5921b42 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,114 +1,64 @@ - - - + - - - - - - - - - - + + + + + android:roundIcon="@mipmap/ic_launcher_round" + android:supportsPictureInPicture="true" + android:theme="@style/BaseTheme"> + + android:launchMode="singleTask"> - - + + - - - - - + android:launchMode="singleInstance" + android:noHistory="true" + android:taskAffinity="" + android:theme="@style/TransparentTheme" /> - - - - - - + - - - + + android:value="true" /> - - - - + android:name=".receivers.NotificationReceiver" + android:exported="true" + android:permission="${applicationId}.DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION" /> diff --git a/app/src/main/assets/fonts/google_sans_bold.ttf b/app/src/main/assets/fonts/google_sans_bold.ttf deleted file mode 100644 index 96619df..0000000 Binary files a/app/src/main/assets/fonts/google_sans_bold.ttf and /dev/null differ diff --git a/app/src/main/java/io/github/ratul/topactivity/App.java b/app/src/main/java/io/github/ratul/topactivity/App.java index 6ddf932..2faf088 100644 --- a/app/src/main/java/io/github/ratul/topactivity/App.java +++ b/app/src/main/java/io/github/ratul/topactivity/App.java @@ -16,46 +16,70 @@ */ package io.github.ratul.topactivity; +import static io.github.ratul.topactivity.receivers.NotificationReceiver.createNotificationChannel; + import android.app.Application; -import io.github.ratul.topactivity.model.CrashHandler; +import android.content.ClipData; +import android.content.ClipboardManager; import android.content.Context; -import java.io.File; -import android.app.Activity; -import io.github.ratul.topactivity.ui.MainActivity; import android.content.Intent; -import io.github.ratul.topactivity.ui.CrashActivity; +import android.content.SharedPreferences; +import android.os.Build; import android.widget.Toast; -import android.os.Environment; -public class App extends Application { - private static App sApp; +import androidx.annotation.NonNull; +import androidx.core.app.NotificationManagerCompat; - @Override - protected void attachBaseContext(Context base) { - super.attachBaseContext(base); - sApp = this; - Thread.setDefaultUncaughtExceptionHandler(new CrashHandler(this)); - } +import io.github.ratul.topactivity.ui.CopyToClipboardActivity; + +public class App extends Application { + private static App instance; + private SharedPreferences sharedPreferences; + private NotificationManagerCompat notificationManager; - @Override - public void onCreate() { - super.onCreate(); - } + @Override + public void onCreate() { + super.onCreate(); + instance = this; + sharedPreferences = getSharedPreferences(getPackageName(), 0); + notificationManager = NotificationManagerCompat.from(this); + createNotificationChannel(notificationManager); + } - public static String getCrashLogDir() { - return getCrashLogFolder().getAbsolutePath(); - } + public SharedPreferences getSharedPreferences() { + return sharedPreferences; + } - public static File getCrashLogFolder() { - return sApp.getExternalFilesDir(null); - } + public NotificationManagerCompat getNotificationManager() { + return notificationManager; + } - public static App getApp() { - return sApp; - } + public static App getInstance() { + return instance; + } - public static void showToast(String str, int length) { - Toast.makeText(getApp(), str, length).show(); - } + public static void copyString(Context context, String str, String msg) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { + ClipboardManager clipboard = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE); + ClipData clip = ClipData.newPlainText(context.getString(R.string.app_name), str); + clipboard.setPrimaryClip(clip); + } else { + Intent copyActivity = new Intent(context, CopyToClipboardActivity.class) + .putExtra(Intent.EXTRA_TEXT, str) + .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + context.startActivity(copyActivity); + } + showToast(context, msg); + } + public static void showToast(@NonNull Context context, @NonNull String message) { + try { + Toast.makeText(context, message, Toast.LENGTH_SHORT).show(); + } catch (Throwable ignored) { + try { + Toast.makeText(instance, message, Toast.LENGTH_SHORT).show(); + } catch (Throwable ignored2) { + } + } + } } diff --git a/app/src/main/java/io/github/ratul/topactivity/model/CrashHandler.java b/app/src/main/java/io/github/ratul/topactivity/model/CrashHandler.java deleted file mode 100644 index 9abf750..0000000 --- a/app/src/main/java/io/github/ratul/topactivity/model/CrashHandler.java +++ /dev/null @@ -1,152 +0,0 @@ -/* - * Copyright (C) 2022 Ratul Hasan - * - * 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 - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package io.github.ratul.topactivity.model; - -import io.github.ratul.topactivity.App; -import io.github.ratul.topactivity.ui.*; -import android.app.*; -import java.text.*; -import java.io.*; -import android.content.pm.*; -import android.text.*; -import android.os.*; -import android.content.*; -import java.util.*; -import java.lang.Thread.UncaughtExceptionHandler; -import android.widget.Toast; - -/** - * Created by Ratul on 04/05/2022. - */ - -public class CrashHandler implements UncaughtExceptionHandler { - - private static UncaughtExceptionHandler DEFAULT = Thread.getDefaultUncaughtExceptionHandler(); - - public static CrashHandler getInstance(App app) { - return new CrashHandler(app); - } - - private App mApp; - private File crashDirectory; - private String fullStackTrace, versionName; - private long versionCode; - - public CrashHandler(App app) { - mApp = app; - crashDirectory = app.getExternalFilesDir(null); - try { - PackageInfo packageInfo = mApp.getPackageManager().getPackageInfo(mApp.getPackageName(), 0); - versionName = packageInfo.versionName; - versionCode = Build.VERSION.SDK_INT >= 28 ? packageInfo.getLongVersionCode() : packageInfo.versionCode; - } catch (PackageManager.NameNotFoundException ignored) { - ignored.printStackTrace(); - versionName = "unknown"; - } - } - - @Override - public void uncaughtException(Thread main, Throwable mThrowable) { - if (tryUncaughtException(main, mThrowable) || DEFAULT == null) { - try { - Thread.sleep(1000L); - } catch (InterruptedException e) { - android.os.Process.sendSignal(android.os.Process.myPid(), android.os.Process.SIGNAL_KILL); - } - android.os.Process.killProcess(android.os.Process.myPid()); - } else { - DEFAULT.uncaughtException(main, mThrowable); - } - } - - private void showToast(String str, int length) { - Toast.makeText(mApp, str, length).show(); - } - - private boolean tryUncaughtException(Thread thread, Throwable throwable) { - if (throwable == null) { - return false; - } else { - new Thread() { - @Override - public void run() { - Looper.prepare(); - showToast("Saving Crash Log", 0); - Looper.loop(); - } - }.start(); - } - File crashFile = new File(crashDirectory, "crash.txt"); - long timestamp = System.currentTimeMillis(); - SimpleDateFormat format = new SimpleDateFormat("dd-MM-yyyy HH:mm"); - String time = format.format(new Date(timestamp)); - - StringWriter sw = new StringWriter(); - PrintWriter pw = new PrintWriter(sw); - throwable.printStackTrace(pw); - Throwable cause = throwable.getCause(); - if (cause != null) - cause.printStackTrace(pw); - fullStackTrace = sw.toString(); - pw.close(); - - StringBuilder sb = new StringBuilder(); - sb.append("*********************** Crash Head ***********************\n"); - sb.append("Time Of Crash : ").append(time).append("\n"); - sb.append("Device Manufacturer : ").append(Build.MANUFACTURER).append("\n"); - sb.append("Device Model : ").append(Build.MODEL).append("\n"); - sb.append("Android Version : ").append(Build.VERSION.RELEASE).append("\n"); - sb.append("Android SDK : ").append(Build.VERSION.SDK_INT).append("\n"); - sb.append("App VersionName : ").append(versionName).append("\n"); - sb.append("App VersionCode : ").append(versionCode).append("\n"); - sb.append("\n*********************** Crash Log ***********************"); - sb.append("\n").append(fullStackTrace); - - String errorLog = sb.toString(); - - try { - writeFile(crashFile, errorLog); - } catch (IOException ignored) { - ignored.printStackTrace(); - } - - gotoCrashActiviy: { - Intent intent = new Intent(mApp, CrashActivity.class); - intent.addFlags( - Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_CLEAR_TASK); - intent.putExtra(CrashActivity.EXTRA_CRASH_INFO, errorLog); - mApp.startActivity(intent); - } - - return errorLog != null; - } - - private void writeFile(File file, String content) throws IOException { - File parentFile = file.getParentFile(); - if (parentFile != null && !parentFile.exists()) { - parentFile.mkdirs(); - } - file.createNewFile(); - try { - FileWriter writer = new FileWriter(file); - writer.write(content); - writer.close(); - } catch (IOException e) { - e.printStackTrace(); - } - } -} diff --git a/app/src/main/java/io/github/ratul/topactivity/model/NotificationMonitor.java b/app/src/main/java/io/github/ratul/topactivity/model/NotificationMonitor.java deleted file mode 100644 index 97b1c7e..0000000 --- a/app/src/main/java/io/github/ratul/topactivity/model/NotificationMonitor.java +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Copyright (C) 2022 Ratul Hasan - * - * 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 - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package io.github.ratul.topactivity.model; - -import android.app.Notification; -import android.app.NotificationChannel; -import android.app.NotificationManager; -import android.app.PendingIntent; -import android.app.TaskStackBuilder; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.os.Build; - -import io.github.ratul.topactivity.R; -import io.github.ratul.topactivity.service.QuickSettingsTileService; -import io.github.ratul.topactivity.ui.MainActivity; -import io.github.ratul.topactivity.utils.DatabaseUtil; -import io.github.ratul.topactivity.utils.WindowUtil; - -/** - * Created by Ratul on 04/05/2022. - */ -public class NotificationMonitor extends BroadcastReceiver { - public static final int NOTIFICATION_ID = 696969691; - private static final int ACTION_STOP = 2; - private static final String EXTRA_NOTIFICATION_ACTION = "command"; - public static Notification.Builder builder; - public static NotificationManager notifManager; - - public static void showNotification(Context context, boolean isPaused) { - if (!DatabaseUtil.isNotificationToggleEnabled()) { - return; - } - notifManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); - - Intent intent = new Intent(context, MainActivity.class); - TaskStackBuilder stackBuilder = TaskStackBuilder.create(context); - stackBuilder.addParentStack(MainActivity.class); - stackBuilder.addNextIntent(intent); - int flag; - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - flag = PendingIntent.FLAG_IMMUTABLE; - } else { - flag = PendingIntent.FLAG_UPDATE_CURRENT; - } - PendingIntent pIntent = stackBuilder.getPendingIntent(0, flag); - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - String CHANNEL_ID = context.getPackageName() + "_channel_007"; - CharSequence name = "Activity Info"; - - NotificationChannel mChannel = new NotificationChannel(CHANNEL_ID, name, NotificationManager.IMPORTANCE_DEFAULT); - mChannel.setDescription("Shows current activity info"); - mChannel.enableLights(false); - mChannel.enableVibration(false); - mChannel.setShowBadge(false); - notifManager.createNotificationChannel(mChannel); - - builder = new Notification.Builder(context, CHANNEL_ID); - } else { - builder = new Notification.Builder(context); - } - - builder.setContentTitle(context.getString(R.string.is_running, context.getString(R.string.app_name))) - .setSmallIcon(R.drawable.ic_shortcut) - .setContentText(context.getString(R.string.touch_to_open)) - .setColor(context.getColor(R.color.layerColor)) - .setVisibility(Notification.VISIBILITY_SECRET) - .setOngoing(!isPaused) - .setAutoCancel(true) - .setContentIntent(pIntent); - -// builder.addAction(R.drawable.ic_launcher_foreground, context.getString(R.string.noti_action_stop), -// getPendingIntent(context, ACTION_STOP)).setContentIntent(pIntent); - - notifManager.notify(NOTIFICATION_ID, builder.build()); - } - - public static PendingIntent getPendingIntent(Context context, int command) { - Intent intent = new Intent("io.github.ratul.topactivity.ACTION_NOTIFICATION_RECEIVER"); - intent.putExtra(EXTRA_NOTIFICATION_ACTION, command); - return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_IMMUTABLE); - } - - public static void cancelNotification(Context context) { - if (notifManager != null) - notifManager.cancel(NOTIFICATION_ID); - } - - @Override - public void onReceive(Context context, Intent intent) { - int command = intent.getIntExtra(EXTRA_NOTIFICATION_ACTION, -1); - if (command == ACTION_STOP) { - WindowUtil.dismiss(context); - DatabaseUtil.setIsShowWindow(false); - cancelNotification(context); - context.sendBroadcast(new Intent(MainActivity.ACTION_STATE_CHANGED)); - } - context.sendBroadcast(new Intent(QuickSettingsTileService.ACTION_UPDATE_TITLE)); - } -} diff --git a/app/src/main/java/io/github/ratul/topactivity/model/TypefaceSpan.java b/app/src/main/java/io/github/ratul/topactivity/model/TypefaceSpan.java deleted file mode 100644 index 54ec372..0000000 --- a/app/src/main/java/io/github/ratul/topactivity/model/TypefaceSpan.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (C) 2022 Ratul Hasan - * - * 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 - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package io.github.ratul.topactivity.model; - -import android.util.LruCache; -import android.graphics.Typeface; -import android.text.style.MetricAffectingSpan; -import android.content.Context; -import android.text.TextPaint; -import android.graphics.Paint; - -public class TypefaceSpan extends MetricAffectingSpan { - - private static LruCache sTypefaceCache = new LruCache(12); - - private Typeface mTypeface; - - public TypefaceSpan(Context context, String typefaceName) { - mTypeface = sTypefaceCache.get(typefaceName); - if (mTypeface == null) { - mTypeface = Typeface.createFromAsset(context.getApplicationContext() - .getAssets(), typefaceName); - sTypefaceCache.put(typefaceName, mTypeface); - } - } - - @Override - public void updateMeasureState(TextPaint p) { - p.setTypeface(mTypeface); - // Note: This flag is required for proper typeface rendering - p.setFlags(p.getFlags() | Paint.SUBPIXEL_TEXT_FLAG); - } - - @Override - public void updateDrawState(TextPaint tp) { - tp.setTypeface(mTypeface); - // Note: This flag is required for proper typeface rendering - tp.setFlags(tp.getFlags() | Paint.SUBPIXEL_TEXT_FLAG); - } -} diff --git a/app/src/main/java/io/github/ratul/topactivity/receivers/NotificationReceiver.java b/app/src/main/java/io/github/ratul/topactivity/receivers/NotificationReceiver.java new file mode 100644 index 0000000..a71f4fe --- /dev/null +++ b/app/src/main/java/io/github/ratul/topactivity/receivers/NotificationReceiver.java @@ -0,0 +1,113 @@ +package io.github.ratul.topactivity.receivers; + +import static android.app.PendingIntent.FLAG_IMMUTABLE; +import static android.app.PendingIntent.FLAG_UPDATE_CURRENT; + +import android.app.Notification; +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.os.Build; + +import androidx.annotation.NonNull; +import androidx.core.app.NotificationCompat; +import androidx.core.app.NotificationManagerCompat; + +import io.github.ratul.topactivity.App; +import io.github.ratul.topactivity.R; +import io.github.ratul.topactivity.ui.MainActivity; +import io.github.ratul.topactivity.utils.DatabaseUtil; +import io.github.ratul.topactivity.utils.WindowUtil; + +public class NotificationReceiver extends BroadcastReceiver { + public static final int NOTIFICATION_ID = 62345; + public static final String CHANNEL_ID = "activity_info"; + public static final int ACTION_COPY = 1; + public static final int ACTION_STOP = 2; + public static final String EXTRA_NOTIFICATION_ACTION = "command"; + + public static void createNotificationChannel(@NonNull NotificationManagerCompat notificationManager) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + NotificationChannel channel = new NotificationChannel(CHANNEL_ID, + "Activity Info", NotificationManager.IMPORTANCE_LOW); + channel.setDescription("Shows current activity info"); + notificationManager.createNotificationChannel(channel); + } + } + + public static void showNotification( + @NonNull Context context, @NonNull String title, String message) { + if (!DatabaseUtil.isShowNotification()) { + return; + } + NotificationManagerCompat notificationManager = App.getInstance().getNotificationManager(); + if (notificationManager.areNotificationsEnabled()) { + Notification notification = buildNotification(context, title, message); + notificationManager.notify(NOTIFICATION_ID, notification); + } + } + + public static void cancelNotification() { + App.getInstance().getNotificationManager() + .cancel(NOTIFICATION_ID); + } + + @Override + public void onReceive(Context context, Intent intent) { + int command = intent.getIntExtra(EXTRA_NOTIFICATION_ACTION, -1); + + switch (command) { + case ACTION_COPY: + App.copyString(context, + intent.getStringExtra(Intent.EXTRA_TEXT), + intent.getStringExtra(Intent.EXTRA_ASSIST_CONTEXT)); + break; + case ACTION_STOP: + DatabaseUtil.setShowingWindow(false); + NotificationReceiver.cancelNotification(); + WindowUtil.dismiss(context); + context.sendBroadcast(new Intent(MainActivity.ACTION_STATE_CHANGED)); + break; + } + } + + private static Notification buildNotification(Context context, String pkg, String cls) { + NotificationCompat.Action copyPkg = new NotificationCompat.Action(R.drawable.ic_package, "Package", + getCopyPendingIntent(context, 3429872, pkg, "Package copied")); + NotificationCompat.Action copyClass = new NotificationCompat.Action(R.drawable.ic_class, "Class", + getCopyPendingIntent(context, 3429873, cls, "Class copied")); + NotificationCompat.Action stop = new NotificationCompat.Action( + R.drawable.ic_cancel, "Stop", getStopPendingIntent(context)); + + return new NotificationCompat.Builder(context.getApplicationContext(), CHANNEL_ID) + .setContentTitle(pkg) + .setSmallIcon(R.drawable.ic_logo) + .setContentText(cls) + .setAutoCancel(false) + .setOngoing(true) + .addAction(copyPkg) + .addAction(copyClass) + .addAction(stop) + .build(); + } + + private static PendingIntent getCopyPendingIntent( + Context context, int requestCode, String text, String message) { + Intent intent = new Intent(context, NotificationReceiver.class) + .putExtra(EXTRA_NOTIFICATION_ACTION, ACTION_COPY) + .putExtra(Intent.EXTRA_TEXT, text) + .putExtra(Intent.EXTRA_ASSIST_CONTEXT, message); + return PendingIntent.getBroadcast(context, + requestCode, intent, FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE); + } + + private static PendingIntent getStopPendingIntent(Context context) { + Intent intent = new Intent(context, NotificationReceiver.class) + .putExtra(EXTRA_NOTIFICATION_ACTION, ACTION_STOP); + return PendingIntent.getBroadcast(context, + 908435, intent, FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE); + } +} diff --git a/app/src/main/java/io/github/ratul/topactivity/service/AccessibilityMonitoringService.java b/app/src/main/java/io/github/ratul/topactivity/service/AccessibilityMonitoringService.java deleted file mode 100644 index 916d6e7..0000000 --- a/app/src/main/java/io/github/ratul/topactivity/service/AccessibilityMonitoringService.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright (C) 2022 Ratul Hasan - * - * 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 - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package io.github.ratul.topactivity.service; - -import android.accessibilityservice.AccessibilityService; -import android.annotation.SuppressLint; -import android.content.Intent; -import android.view.accessibility.AccessibilityEvent; -import android.widget.Toast; -import io.github.ratul.topactivity.utils.WindowUtil; -import io.github.ratul.topactivity.utils.DatabaseUtil; -import io.github.ratul.topactivity.model.NotificationMonitor; -import android.content.pm.PackageManager; -import java.util.List; -import android.content.pm.ResolveInfo; -import android.content.Context; - -/** - * Created by Wen on 16/02/2017. - * Refactored by Ratul on 04/05/2022. - */ -public class AccessibilityMonitoringService extends AccessibilityService { - private static AccessibilityMonitoringService sInstance; - - public static AccessibilityMonitoringService getInstance() { - return sInstance; - } - - public boolean isPackageInstalled(String packageName) { - final PackageManager packageManager = getPackageManager(); - Intent intent = packageManager.getLaunchIntentForPackage(packageName); - if (intent == null) { - return false; - } - List list = packageManager.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY); - return list.size() > 0; - } - - public boolean isSystemClass(String className) { - try { - ClassLoader.getSystemClassLoader().loadClass(className); - return true; - } catch (ClassNotFoundException e) { - return false; - } - } - - @Override - public void onAccessibilityEvent(AccessibilityEvent event) { - if (WindowUtil.viewAdded && DatabaseUtil.isShowWindow() && DatabaseUtil.hasAccess()) { - String act1 = String.valueOf(event.getClassName()); - String act2 = String.valueOf(event.getPackageName()); - - if (isSystemClass(act1)) - return; - WindowUtil.show(this, act2, act1); - } - } - - @Override - public void onInterrupt() { - } - - @Override - protected void onServiceConnected() { - sInstance = this; - super.onServiceConnected(); - } - - @Override - public boolean onUnbind(Intent intent) { - sInstance = null; - WindowUtil.dismiss(this); - NotificationMonitor.cancelNotification(this); - sendBroadcast(new Intent(QuickSettingsTileService.ACTION_UPDATE_TITLE)); - return super.onUnbind(intent); - } -} diff --git a/app/src/main/java/io/github/ratul/topactivity/service/MonitoringService.java b/app/src/main/java/io/github/ratul/topactivity/service/MonitoringService.java deleted file mode 100644 index c55a382..0000000 --- a/app/src/main/java/io/github/ratul/topactivity/service/MonitoringService.java +++ /dev/null @@ -1,136 +0,0 @@ -/* - * Copyright (C) 2022 Ratul Hasan - * - * 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 - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package io.github.ratul.topactivity.service; - -import android.app.AlarmManager; -import android.app.PendingIntent; -import android.app.Service; -import android.app.usage.UsageEvents; -import android.app.usage.UsageStatsManager; -import android.content.Context; -import android.content.Intent; -import android.os.Handler; -import android.os.IBinder; -import android.os.SystemClock; - -import io.github.ratul.topactivity.utils.DatabaseUtil; -import io.github.ratul.topactivity.utils.WindowUtil; - -/** - * Created by Wen on 16/02/2017. - * Refactored by Ratul on 04/05/2022. - */ -public class MonitoringService extends Service { - public boolean serviceAlive = false; - private boolean firstRun = true; - public static MonitoringService INSTANCE; - private UsageStatsManager usageStats; - public Handler mHandler = new Handler(); - private String text; - private String text1; - - @Override - public void onCreate() { - super.onCreate(); - INSTANCE = this; - - serviceAlive = true; - usageStats = (UsageStatsManager) getSystemService(Context.USAGE_STATS_SERVICE); - } - - @Override - public void onDestroy() { - serviceAlive = false; - super.onDestroy(); - } - - public void getActivityInfo() { - long currentTimeMillis = System.currentTimeMillis(); - UsageEvents queryEvents = usageStats.queryEvents(currentTimeMillis - (firstRun ? 600000 : 60000), - currentTimeMillis); - while (queryEvents.hasNextEvent()) { - UsageEvents.Event event = new UsageEvents.Event(); - queryEvents.getNextEvent(event); - int type = event.getEventType(); - if (type == UsageEvents.Event.MOVE_TO_FOREGROUND) { - text = event.getPackageName(); - text1 = event.getClassName(); - } else if (type == UsageEvents.Event.MOVE_TO_BACKGROUND) { - if (event.getPackageName().equals(text)) { - text = null; - text1 = null; - } - } - } - } - - @Override - public IBinder onBind(Intent intent) { - return null; - } - - @Override - public int onStartCommand(Intent intent, int flags, int startId) { - INSTANCE = this; - Runnable runner = new Runnable() { - @Override - public void run() { - if (!DatabaseUtil.isShowWindow()) { - MonitoringService.INSTANCE.mHandler.removeCallbacks(this); - MonitoringService.INSTANCE.stopSelf(); - } - - String preText = MonitoringService.INSTANCE.text; - String preText1 = MonitoringService.INSTANCE.text1; - getActivityInfo(); - if (MonitoringService.INSTANCE.text == null) - return; - if (preText != null && preText.equals(MonitoringService.INSTANCE.text) - && preText1 != null && preText1.equals(MonitoringService.INSTANCE.text1)) { - // not change, return - return; - } - - MonitoringService.INSTANCE.firstRun = false; - if (DatabaseUtil.isShowWindow()) { - WindowUtil.show(MonitoringService.INSTANCE, MonitoringService.INSTANCE.text, - MonitoringService.INSTANCE.text1); - } else { - MonitoringService.INSTANCE.stopSelf(); - } - mHandler.postDelayed(this, 500); - } - }; - - mHandler.postDelayed(runner, 500); - return super.onStartCommand(intent, flags, startId); - } - - @Override - public void onTaskRemoved(Intent rootIntent) { - Intent restartServiceIntent = new Intent(getApplicationContext(), this.getClass()); - restartServiceIntent.setPackage(getPackageName()); - - PendingIntent restartServicePendingIntent = PendingIntent.getService(getApplicationContext(), 1, - restartServiceIntent, PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_IMMUTABLE); - AlarmManager alarmService = (AlarmManager) getApplicationContext().getSystemService(Context.ALARM_SERVICE); - alarmService.set(AlarmManager.ELAPSED_REALTIME, SystemClock.elapsedRealtime() + 500, - restartServicePendingIntent); - - super.onTaskRemoved(rootIntent); - } -} diff --git a/app/src/main/java/io/github/ratul/topactivity/service/QuickSettingsTileService.java b/app/src/main/java/io/github/ratul/topactivity/service/QuickSettingsTileService.java deleted file mode 100644 index 67fa79c..0000000 --- a/app/src/main/java/io/github/ratul/topactivity/service/QuickSettingsTileService.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * Copyright (C) 2022 Ratul Hasan - * - * 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 - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package io.github.ratul.topactivity.service; - -import android.annotation.TargetApi; -import android.content.BroadcastReceiver; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.os.Build; -import android.provider.Settings; -import android.service.quicksettings.Tile; -import android.service.quicksettings.TileService; -import android.text.style.BackgroundColorSpan; -import io.github.ratul.topactivity.utils.DatabaseUtil; -import io.github.ratul.topactivity.ui.MainActivity; -import io.github.ratul.topactivity.utils.WindowUtil; -import io.github.ratul.topactivity.model.NotificationMonitor; -import io.github.ratul.topactivity.ui.BackgroundActivity; - -/** - * Created by Wen on 5/3/16. - * Refactored by Ratul on 04/05/2022. - */ -@TargetApi(Build.VERSION_CODES.N) -public class QuickSettingsTileService extends TileService { - public static final String ACTION_UPDATE_TITLE = "io.github.ratul.topactivity.ACTION.UPDATE_TITLE"; - private UpdateTileReceiver mReceiver; - - public static void updateTile(Context context) { - TileService.requestListeningState(context, new ComponentName(context, QuickSettingsTileService.class)); - context.sendBroadcast(new Intent(QuickSettingsTileService.ACTION_UPDATE_TITLE)); - } - - public void updateTile() { - getQsTile().setState(DatabaseUtil.isShowWindow() ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE); - getQsTile().updateTile(); - } - - @Override - public void onCreate() { - super.onCreate(); - mReceiver = new UpdateTileReceiver(); - } - - @Override - public void onTileAdded() { - DatabaseUtil.setQSTileAdded(true); - sendBroadcast(new Intent(MainActivity.ACTION_STATE_CHANGED)); - } - - @Override - public void onTileRemoved() { - super.onTileRemoved(); - DatabaseUtil.setQSTileAdded(false); - sendBroadcast(new Intent(MainActivity.ACTION_STATE_CHANGED)); - } - - @Override - public void onStartListening() { - registerReceiver(mReceiver, new IntentFilter(ACTION_UPDATE_TITLE)); - super.onStartListening(); - updateTile(); - } - - @Override - public void onStopListening() { - unregisterReceiver(mReceiver); - super.onStopListening(); - } - - @Override - public void onClick() { - if (DatabaseUtil.isShowWindow()) - return; - if (!MainActivity.usageStats(this) || !Settings.canDrawOverlays(this)) { - Intent intent = new Intent(this, MainActivity.class); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - intent.putExtra(MainActivity.EXTRA_FROM_QS_TILE, true); - startActivityAndCollapse(intent); - } else { - if (DatabaseUtil.hasAccess() && AccessibilityMonitoringService.getInstance() == null) - startService(new Intent().setClass(this, AccessibilityMonitoringService.class)); - DatabaseUtil.setIsShowWindow(!DatabaseUtil.isShowWindow()); - if (DatabaseUtil.isShowWindow()) { - if (WindowUtil.sWindowManager == null) - WindowUtil.init(this); - NotificationMonitor.showNotification(this, false); - startService(new Intent(this, MonitoringService.class)); - } else { - WindowUtil.dismiss(this); - NotificationMonitor.showNotification(this, true); - } - sendBroadcast(new Intent(MainActivity.ACTION_STATE_CHANGED)); - } - } - - class UpdateTileReceiver extends BroadcastReceiver { - @Override - public void onReceive(Context context, Intent intent) { - updateTile(); - } - } -} diff --git a/app/src/main/java/io/github/ratul/topactivity/services/AccessibilityMonitoringService.java b/app/src/main/java/io/github/ratul/topactivity/services/AccessibilityMonitoringService.java new file mode 100644 index 0000000..2530dcd --- /dev/null +++ b/app/src/main/java/io/github/ratul/topactivity/services/AccessibilityMonitoringService.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2022 Ratul Hasan + * + * 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 + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package io.github.ratul.topactivity.services; + +import static io.github.ratul.topactivity.utils.NullSafety.isNullOrEmpty; + +import android.accessibilityservice.AccessibilityService; +import android.annotation.SuppressLint; +import android.content.Intent; +import android.util.Log; +import android.view.accessibility.AccessibilityEvent; + +import io.github.ratul.topactivity.BuildConfig; +import io.github.ratul.topactivity.utils.DatabaseUtil; +import io.github.ratul.topactivity.utils.WindowUtil; + +/** + * Created by Wen on 16/02/2017. + * Refactored by Ratul on 04/05/2022. + */ +@SuppressLint("AccessibilityPolicy") +public class AccessibilityMonitoringService extends AccessibilityService { + private static AccessibilityMonitoringService instance; + + public static AccessibilityMonitoringService getInstance() { + return instance; + } + + @Override + public void onAccessibilityEvent(AccessibilityEvent event) { + if (WindowUtil.isViewVisible() && DatabaseUtil.isShowingWindow()) { + CharSequence pkgName = event.getPackageName(); + CharSequence className = event.getClassName(); + + if (!isNullOrEmpty(pkgName) && + !isNullOrEmpty(className) && + !isSystemClass(className.toString())) { + if (BuildConfig.DEBUG) { + Log.d("AccessibilityService", "Pkg: " + pkgName + ", Class: " + className); + } + WindowUtil.show(this, pkgName.toString(), className.toString()); + } + } + } + + @Override + public void onInterrupt() { + } + + @Override + protected void onServiceConnected() { + instance = this; + super.onServiceConnected(); + } + + @Override + public void onRebind(Intent intent) { + instance = this; + super.onRebind(intent); + } + + @Override + public boolean onUnbind(Intent intent) { + instance = null; + return true; + } + + private boolean isSystemClass(String className) { + try { + return ClassLoader.getSystemClassLoader().loadClass(className) != null; + } catch (ClassNotFoundException e) { + return false; + } + } +} diff --git a/app/src/main/java/io/github/ratul/topactivity/services/PackageMonitoringService.java b/app/src/main/java/io/github/ratul/topactivity/services/PackageMonitoringService.java new file mode 100644 index 0000000..52c056c --- /dev/null +++ b/app/src/main/java/io/github/ratul/topactivity/services/PackageMonitoringService.java @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2022 Ratul Hasan + * + * 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 + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package io.github.ratul.topactivity.services; + +import static android.app.PendingIntent.FLAG_IMMUTABLE; +import static android.app.PendingIntent.FLAG_UPDATE_CURRENT; + +import static io.github.ratul.topactivity.utils.NullSafety.isNullOrEmpty; + +import android.app.AlarmManager; +import android.app.PendingIntent; +import android.app.Service; +import android.app.usage.UsageEvents; +import android.app.usage.UsageStatsManager; +import android.content.Context; +import android.content.Intent; +import android.os.Binder; +import android.os.Handler; +import android.os.IBinder; +import android.os.SystemClock; +import android.util.Log; + +import androidx.core.util.Pair; + +import io.github.ratul.topactivity.BuildConfig; +import io.github.ratul.topactivity.utils.DatabaseUtil; +import io.github.ratul.topactivity.utils.WindowUtil; + +/** + * Created by Wen on 16/02/2017. + * Refactored by Ratul on 04/05/2022. + */ +public class PackageMonitoringService extends Service { + private final IBinder binder = new LocalBinder(); + private final Handler handler = new Handler(); + private Runnable observerTask; + private UsageStatsManager usageStats; + + public class LocalBinder extends Binder { + public PackageMonitoringService getService() { + return PackageMonitoringService.this; + } + } + + @Override + public IBinder onBind(Intent intent) { + return binder; + } + + @Override + public void onCreate() { + super.onCreate(); + usageStats = (UsageStatsManager) getSystemService(Context.USAGE_STATS_SERVICE); + observerTask = () -> { + if (!DatabaseUtil.isShowingWindow()) { + handler.removeCallbacks(observerTask); + stopSelf(); + return; + } + + Pair currentApp = getForegroundApp(); + String currentPkgName = currentApp.first; + String currentClassName = currentApp.second; + + if (!isNullOrEmpty(currentPkgName) && !isNullOrEmpty(currentClassName)) { + if (BuildConfig.DEBUG) { + Log.d("PackageMonitoring", "Pkg: " + currentPkgName + ", Class: " + currentClassName); + } + if (WindowUtil.isViewVisible()) { + WindowUtil.show(this, currentPkgName, currentClassName); + } + } + handler.postDelayed(observerTask, 500); + }; + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + handler.removeCallbacks(observerTask); + handler.post(observerTask); + return START_STICKY; + } + + @Override + public void onTaskRemoved(Intent rootIntent) { + Intent intent = new Intent(this, getClass()); + PendingIntent pendingIntent = PendingIntent.getService(getApplicationContext(), + 264593, intent, FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE); + AlarmManager alarmService = (AlarmManager) getSystemService(ALARM_SERVICE); + alarmService.set(AlarmManager.ELAPSED_REALTIME, + SystemClock.elapsedRealtime() + 500, pendingIntent); + super.onTaskRemoved(rootIntent); + } + + private Pair getForegroundApp() { + long currentTime = System.currentTimeMillis(); + // Query events in the last 10 seconds + UsageEvents usageEvents = usageStats.queryEvents(currentTime - 10000, currentTime); + String latestPackage = null; + String latestClass = null; + long latestTimestamp = 0; + + UsageEvents.Event event = new UsageEvents.Event(); + while (usageEvents.hasNextEvent()) { + usageEvents.getNextEvent(event); + + if (event.getEventType() == UsageEvents.Event.MOVE_TO_FOREGROUND) { + if (event.getTimeStamp() > latestTimestamp) { + latestTimestamp = event.getTimeStamp(); + latestPackage = event.getPackageName(); + latestClass = event.getClassName(); + } + } + } + + return new Pair<>(latestPackage, latestClass); + } +} diff --git a/app/src/main/java/io/github/ratul/topactivity/services/QuickSettingsTileService.java b/app/src/main/java/io/github/ratul/topactivity/services/QuickSettingsTileService.java new file mode 100644 index 0000000..afe4227 --- /dev/null +++ b/app/src/main/java/io/github/ratul/topactivity/services/QuickSettingsTileService.java @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2022 Ratul Hasan + * + * 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 + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package io.github.ratul.topactivity.services; + +import static android.app.PendingIntent.FLAG_IMMUTABLE; +import static android.app.PendingIntent.FLAG_UPDATE_CURRENT; + +import android.annotation.SuppressLint; +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.Build; +import android.service.quicksettings.Tile; +import android.service.quicksettings.TileService; + +import androidx.core.content.ContextCompat; + +import io.github.ratul.topactivity.receivers.NotificationReceiver; +import io.github.ratul.topactivity.ui.MainActivity; +import io.github.ratul.topactivity.utils.DatabaseUtil; +import io.github.ratul.topactivity.utils.WindowUtil; + +/** + * Created by Wen on 5/3/16. + * Refactored by Ratul on 04/05/2022. + */ +public class QuickSettingsTileService extends TileService { + private static final String ACTION_UPDATE_TITLE = "io.github.ratul.topactivity.ACTION_UPDATE_TILE"; + private UpdateTileReceiver mReceiver; + + public static void updateTile(Context context) { + TileService.requestListeningState(context.getApplicationContext(), + new ComponentName(context, QuickSettingsTileService.class)); + context.sendBroadcast(new Intent(QuickSettingsTileService.ACTION_UPDATE_TITLE)); + } + + public void updateTile() { + getQsTile().setState(DatabaseUtil.isShowingWindow() ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE); + getQsTile().updateTile(); + } + + @Override + public void onCreate() { + super.onCreate(); + mReceiver = new UpdateTileReceiver(); + } + + @Override + public void onTileAdded() { + sendBroadcast(new Intent(MainActivity.ACTION_STATE_CHANGED)); + } + + @Override + public void onTileRemoved() { + super.onTileRemoved(); + sendBroadcast(new Intent(MainActivity.ACTION_STATE_CHANGED)); + } + + @Override + public void onStartListening() { + ContextCompat.registerReceiver(getApplicationContext(), mReceiver, + new IntentFilter(ACTION_UPDATE_TITLE), ContextCompat.RECEIVER_EXPORTED); + updateTile(); + super.onStartListening(); + } + + @Override + public void onStopListening() { + getApplicationContext().unregisterReceiver(mReceiver); + super.onStopListening(); + } + + @SuppressLint("StartActivityAndCollapseDeprecated") + @Override + public void onClick() { + if (DatabaseUtil.isShowingWindow() && WindowUtil.isViewVisible()) { + DatabaseUtil.setShowingWindow(false); + NotificationReceiver.cancelNotification(); + WindowUtil.dismiss(this); + sendBroadcast(new Intent(MainActivity.ACTION_STATE_CHANGED)); + updateTile(); + return; + } + + Intent intent = new Intent(this, MainActivity.class) + .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + .putExtra(MainActivity.EXTRA_FROM_QS_TILE, true); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { + startActivityAndCollapse(PendingIntent.getActivity(this, + 7456435, intent, FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE)); + } else { + startActivityAndCollapse(intent); + } + } + + class UpdateTileReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + updateTile(); + } + } +} diff --git a/app/src/main/java/io/github/ratul/topactivity/ui/BackgroundActivity.java b/app/src/main/java/io/github/ratul/topactivity/ui/BackgroundActivity.java deleted file mode 100644 index 81dd142..0000000 --- a/app/src/main/java/io/github/ratul/topactivity/ui/BackgroundActivity.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright (C) 2022 Ratul Hasan - * - * 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 - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package io.github.ratul.topactivity.ui; - -import android.annotation.TargetApi; -import android.app.Activity; -import android.content.Intent; -import android.os.Build; -import android.os.Bundle; -import android.provider.Settings; -import android.content.ClipboardManager; -import android.content.ClipData; -import androidx.appcompat.app.AppCompatActivity; -import io.github.ratul.topactivity.App; - -/** - * Created by Ratul on 04/05/2022. - */ -@TargetApi(Build.VERSION_CODES.O) -public class BackgroundActivity extends AppCompatActivity { - public static String STRING_COPY = "io.github.ratul.topactivity.COPY_STRING"; - public static String COPY_MSG = "io.github.ratul.topactivity.COPY_STRING_MSG"; - public static boolean isAlive; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - if (!getIntent().hasExtra(STRING_COPY)) - finish(); - String str = getIntent().getStringExtra(STRING_COPY); - String msg = getIntent().getStringExtra(COPY_MSG); - msg = (msg == null || msg.trim().isEmpty()) ? "Copied" : msg; - - if (str != null) { - ClipboardManager clipboard = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE); - ClipData clip = new ClipData(ClipData.newPlainText("", str)); - clipboard.setPrimaryClip(clip); - } - finish(); - } - - @Override - protected void onStop() { - isAlive = false; - super.onStop(); - } - - @Override - protected void onDestroy() { - isAlive = false; - super.onDestroy(); - } - - @Override - protected void onStart() { - isAlive = true; - super.onStart(); - } -} diff --git a/app/src/main/java/io/github/ratul/topactivity/ui/CopyToClipboardActivity.java b/app/src/main/java/io/github/ratul/topactivity/ui/CopyToClipboardActivity.java new file mode 100644 index 0000000..0f57d7f --- /dev/null +++ b/app/src/main/java/io/github/ratul/topactivity/ui/CopyToClipboardActivity.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2022 Ratul Hasan + * + * 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 + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package io.github.ratul.topactivity.ui; + +import static io.github.ratul.topactivity.utils.NullSafety.isNullOrEmpty; + +import android.content.ClipData; +import android.content.ClipboardManager; +import android.content.Intent; +import android.os.Bundle; + +import androidx.appcompat.app.AppCompatActivity; + +import io.github.ratul.topactivity.R; + +/** + * Created by Ratul on 04/05/2022. + */ +public class CopyToClipboardActivity extends AppCompatActivity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + if (getIntent().hasExtra(Intent.EXTRA_TEXT)) { + String text = getIntent().getStringExtra(Intent.EXTRA_TEXT); + + if (!isNullOrEmpty(text)) { + ClipboardManager clipboard = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE); + ClipData clip = new ClipData(ClipData.newPlainText( + getString(R.string.app_name), text)); + clipboard.setPrimaryClip(clip); + } + } + finish(); + } +} diff --git a/app/src/main/java/io/github/ratul/topactivity/ui/CrashActivity.java b/app/src/main/java/io/github/ratul/topactivity/ui/CrashActivity.java deleted file mode 100644 index 27c0af6..0000000 --- a/app/src/main/java/io/github/ratul/topactivity/ui/CrashActivity.java +++ /dev/null @@ -1,130 +0,0 @@ -/* - * Copyright (C) 2022 Ratul Hasan - * - * 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 - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package io.github.ratul.topactivity.ui; - -import android.app.Activity; -import android.os.Bundle; -import com.google.android.material.textview.MaterialTextView; -import androidx.appcompat.app.AppCompatActivity; -import io.github.ratul.topactivity.R; -import android.view.MenuItem; -import android.content.ClipboardManager; -import android.content.ClipData; -import android.view.Menu; -import android.content.pm.PackageManager; -import android.content.Intent; -import android.content.Context; -import android.content.DialogInterface; -import android.graphics.Typeface; -import android.view.View; -import android.widget.Toast; -import android.text.SpannableString; -import androidx.appcompat.app.ActionBar; -import android.text.Spannable; -import com.google.android.material.dialog.MaterialAlertDialogBuilder; -import io.github.ratul.topactivity.model.TypefaceSpan; - -/** - * Created by Ratul on 04/05/2022. - */ - -public class CrashActivity extends AppCompatActivity { - public static String EXTRA_CRASH_INFO = "crash"; - private String crashInfo; - private boolean restart; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.crash_view); - - SpannableString s = new SpannableString(getString(R.string.app_name)); - s.setSpan(new TypefaceSpan(this, "fonts/google_sans_bold.ttf"), 0, s.length(), - Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); - ActionBar actionBar = getSupportActionBar(); - actionBar.setTitle(s); - - restart = getIntent().getBooleanExtra("Restart", true); - String mLog = getIntent().getStringExtra(EXTRA_CRASH_INFO); - crashInfo = mLog; - MaterialTextView crashed = findViewById(R.id.crashed); - crashed.setText(mLog); - } - - @Override - public void onBackPressed() { - if (!restart) { - finish(); - return; - } - new MaterialAlertDialogBuilder(this).setTitle("Exit").setMessage("App will restart, are you sure to exit") - .setPositiveButton("Yes", new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface di, int btn) { - di.dismiss(); - restart(); - } - }).setNegativeButton("No", new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface di, int btn) { - di.dismiss(); - } - }).setCancelable(false).show(); - } - - private void restart() { - PackageManager pm = getPackageManager(); - Intent intent = pm.getLaunchIntentForPackage(getPackageName()); - if (intent != null) { - intent.addFlags( - Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_CLEAR_TASK); - startActivity(intent); - } - finish(); - android.os.Process.killProcess(android.os.Process.myPid()); - System.exit(0); - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - if (item.getItemId() == android.R.id.copy) { - ClipboardManager cm = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE); - cm.setPrimaryClip(ClipData.newPlainText(getPackageName(), crashInfo)); - Toast.makeText(this, "Copied", 0).show(); - } else if (item.getItemId() == android.R.id.redo) { - onBackPressed(); - } - return super.onOptionsItemSelected(item); - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - SpannableString s = new SpannableString("Copy Log"); - s.setSpan(new TypefaceSpan(this, "fonts/google_sans_regular.ttf"), 0, s.length(), - Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); - menu.add(0, android.R.id.copy, 0, s); - if (restart) { - s = new SpannableString("Restart App"); - s.setSpan(new TypefaceSpan(this, "fonts/google_sans_regular.ttf"), 0, s.length(), - Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); - - menu.add(1, android.R.id.redo, 1, s); - } - return super.onCreateOptionsMenu(menu); - } - -} diff --git a/app/src/main/java/io/github/ratul/topactivity/ui/MainActivity.java b/app/src/main/java/io/github/ratul/topactivity/ui/MainActivity.java index 1b2b612..721934e 100644 --- a/app/src/main/java/io/github/ratul/topactivity/ui/MainActivity.java +++ b/app/src/main/java/io/github/ratul/topactivity/ui/MainActivity.java @@ -16,51 +16,48 @@ */ package io.github.ratul.topactivity.ui; -import android.Manifest; -import android.app.Activity; +import static android.Manifest.permission.PACKAGE_USAGE_STATS; +import static android.Manifest.permission.POST_NOTIFICATIONS; +import static android.content.pm.PackageManager.PERMISSION_GRANTED; +import static io.github.ratul.topactivity.utils.NullSafety.isNullOrEmpty; + import android.app.AppOpsManager; import android.content.BroadcastReceiver; +import android.content.ComponentName; import android.content.Context; -import android.content.DialogInterface; import android.content.Intent; import android.content.IntentFilter; -import android.content.pm.PackageManager; +import android.content.ServiceConnection; import android.graphics.Insets; import android.net.Uri; import android.os.Build; import android.os.Bundle; +import android.os.IBinder; +import android.os.Process; import android.provider.Settings; -import android.text.Spannable; -import android.text.SpannableString; import android.util.DisplayMetrics; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.WindowInsets; import android.view.WindowMetrics; -import android.widget.CompoundButton; -import android.widget.CompoundButton.OnCheckedChangeListener; -import android.widget.Toast; +import android.widget.Button; +import android.widget.EditText; +import android.widget.TextView; -import androidx.annotation.NonNull; -import androidx.appcompat.app.ActionBar; +import androidx.activity.result.ActivityResultLauncher; +import androidx.activity.result.contract.ActivityResultContracts; +import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.widget.SwitchCompat; import androidx.core.content.ContextCompat; -import com.google.android.material.dialog.MaterialAlertDialogBuilder; -import com.google.android.material.switchmaterial.SwitchMaterial; - -import java.io.BufferedReader; -import java.io.File; -import java.io.FileOutputStream; -import java.io.FileReader; - import io.github.ratul.topactivity.App; +import io.github.ratul.topactivity.BuildConfig; import io.github.ratul.topactivity.R; -import io.github.ratul.topactivity.model.NotificationMonitor; -import io.github.ratul.topactivity.model.TypefaceSpan; -import io.github.ratul.topactivity.service.AccessibilityMonitoringService; -import io.github.ratul.topactivity.service.MonitoringService; +import io.github.ratul.topactivity.receivers.NotificationReceiver; +import io.github.ratul.topactivity.services.AccessibilityMonitoringService; +import io.github.ratul.topactivity.services.PackageMonitoringService; import io.github.ratul.topactivity.utils.DatabaseUtil; import io.github.ratul.topactivity.utils.WindowUtil; @@ -69,328 +66,350 @@ * Refactored by Ratul on 04/05/2022. */ public class MainActivity extends AppCompatActivity { - public static final int REQUEST_CODE_NOTIFICATION = 100; - public static final String EXTRA_FROM_QS_TILE = "from_qs_tile"; - public static final String ACTION_STATE_CHANGED = "io.github.ratul.topactivity.ACTION_STATE_CHANGED"; - private SwitchMaterial mWindowSwitch, mNotificationSwitch, mAccessibilitySwitch; - private BroadcastReceiver mReceiver; - private MaterialAlertDialogBuilder fancy; - public static MainActivity INSTANCE; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_main); - INSTANCE = this; - if (AccessibilityMonitoringService.getInstance() == null && DatabaseUtil.hasAccess()) - startService(new Intent().setClass(this, AccessibilityMonitoringService.class)); - - DatabaseUtil.setDisplayWidth(getScreenWidth(this)); - fancy = new MaterialAlertDialogBuilder(this).setNegativeButton("Close", new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface di, int btn) { - di.dismiss(); - } - }).setCancelable(false); - - SpannableString s = new SpannableString(getString(R.string.app_name)); - s.setSpan(new TypefaceSpan(this, "fonts/google_sans_bold.ttf"), 0, s.length(), - Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); - ActionBar actionBar = getSupportActionBar(); - actionBar.setTitle(s); - - mWindowSwitch = findViewById(R.id.sw_window); - mNotificationSwitch = findViewById(R.id.sw_notification); - mAccessibilitySwitch = findViewById(R.id.sw_accessibility); - - if (Build.VERSION.SDK_INT < 24) { - mNotificationSwitch.setVisibility(View.INVISIBLE); - findViewById(R.id.divider_useNotificationPref).setVisibility(View.INVISIBLE); - } - - mReceiver = new UpdateSwitchReceiver(); - ContextCompat.registerReceiver(this, mReceiver, new IntentFilter(ACTION_STATE_CHANGED), ContextCompat.RECEIVER_NOT_EXPORTED); - - mNotificationSwitch.setOnCheckedChangeListener(new SwitchMaterial.OnCheckedChangeListener() { - @Override - public void onCheckedChanged(CompoundButton button, boolean isChecked) { - DatabaseUtil.setNotificationToggleEnabled(!isChecked); - } - }); - mAccessibilitySwitch.setOnCheckedChangeListener(new SwitchMaterial.OnCheckedChangeListener() { - @Override - public void onCheckedChanged(CompoundButton button, boolean isChecked) { - DatabaseUtil.setHasAccess(isChecked); - if (isChecked && AccessibilityMonitoringService.getInstance() == null) - startService(new Intent().setClass(MainActivity.this, AccessibilityMonitoringService.class)); - } - }); - mWindowSwitch.setOnCheckedChangeListener(new SwitchMaterial.OnCheckedChangeListener() { - @Override - public void onCheckedChanged(CompoundButton button, boolean isChecked) { - if (!Settings.canDrawOverlays(MainActivity.this)) { - fancy.setTitle("Overlay Permission") - .setMessage("Please enable overlay permission to show window over other apps") - .setPositiveButton("Settings", new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface di, int btn) { - Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION); - intent.setData(Uri.parse("package:" + getPackageName())); - startActivity(intent); - di.dismiss(); - } - }).show(); - mWindowSwitch.setChecked(false); - } else if (DatabaseUtil.hasAccess() && AccessibilityMonitoringService.getInstance() == null) { - fancy.setTitle("Accessibility Permission").setMessage( - "As per your choice, please grant permission to use Accessibility Service for Current Activity app in order to get current activity info") - .setPositiveButton("Settings", new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface di, int btn) { - Intent intent = new Intent(); - intent.setAction("android.settings.ACCESSIBILITY_SETTINGS"); - startActivity(intent); - di.dismiss(); - } - }).show(); - mWindowSwitch.setChecked(false); - } else if (!usageStats(MainActivity.this)) { - fancy.setTitle("Usage Access").setMessage( - "In order to monitor current task, please grant Usage Access permission for Current Activity app") - .setPositiveButton("Settings", new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface di, int btn) { - Intent intent = new Intent(); - intent.setAction("android.settings.USAGE_ACCESS_SETTINGS"); - startActivity(intent); - di.dismiss(); - } - }).show(); - mWindowSwitch.setChecked(false); - } else { - DatabaseUtil.setAppInitiated(true); - DatabaseUtil.setIsShowWindow(isChecked); - if (!isChecked) { - WindowUtil.dismiss(MainActivity.this); - } else { - WindowUtil.show(MainActivity.this, getPackageName(), getClass().getName()); - startService(new Intent(MainActivity.this, MonitoringService.class)); - } - } - } - }); - - if (getIntent().getBooleanExtra(EXTRA_FROM_QS_TILE, false)) - mWindowSwitch.setChecked(true); - } - - public static int getScreenWidth(Activity activity) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { - WindowMetrics windowMetrics = activity.getWindowManager().getCurrentWindowMetrics(); - Insets insets = windowMetrics.getWindowInsets().getInsetsIgnoringVisibility(WindowInsets.Type.systemBars()); - return windowMetrics.getBounds().width() - insets.left - insets.right; - } else { - DisplayMetrics displayMetrics = new DisplayMetrics(); - activity.getWindowManager().getDefaultDisplay().getMetrics(displayMetrics); - return displayMetrics.widthPixels; - } - } - - @Override - protected void onNewIntent(Intent intent) { - super.onNewIntent(intent); - if (getIntent().getBooleanExtra(EXTRA_FROM_QS_TILE, false)) { - mWindowSwitch.setChecked(true); - } - } - - @Override - protected void onStart() { - super.onStart(); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU && !mNotificationSwitch.isChecked()) { - if (checkSelfPermission(Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) { - requestPermissions(new String[]{Manifest.permission.POST_NOTIFICATIONS}, REQUEST_CODE_NOTIFICATION); - } - } - } - - @Override - public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { - super.onRequestPermissionsResult(requestCode, permissions, grantResults); - if (requestCode == REQUEST_CODE_NOTIFICATION) { - if (grantResults[0] == PackageManager.PERMISSION_DENIED) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU - && shouldShowRequestPermissionRationale(Manifest.permission.POST_NOTIFICATIONS)) { - requestPermissions(new String[]{Manifest.permission.POST_NOTIFICATIONS}, REQUEST_CODE_NOTIFICATION); - } else { - showToast("POST_NOTIFICATIONS Permission Denied", Toast.LENGTH_SHORT); - } - } else { - showToast("POST_NOTIFICATIONS Permission Granted", Toast.LENGTH_SHORT); - } - } - } - - @Override - protected void onResume() { - super.onResume(); - refreshWindowSwitch(); - refreshNotificationSwitch(); - refreshAccessibilitySwitch(); - NotificationMonitor.cancelNotification(this); - } - - @Override - protected void onPause() { - super.onPause(); - if (DatabaseUtil.isShowWindow()) { - NotificationMonitor.showNotification(this, false); - } - } - - private void refreshWindowSwitch() { - mWindowSwitch.setChecked(DatabaseUtil.isShowWindow()); - if (DatabaseUtil.hasAccess() && AccessibilityMonitoringService.getInstance() == null) { - mWindowSwitch.setChecked(false); - } - } - - private void refreshAccessibilitySwitch() { - mAccessibilitySwitch.setChecked(DatabaseUtil.hasAccess()); - } - - private void refreshNotificationSwitch() { - mNotificationSwitch.setChecked(!DatabaseUtil.isNotificationToggleEnabled()); - } - - public void showToast(String str, int length) { - Toast.makeText(this, str, length).show(); - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - menu.add("GitHub Repo").setIcon(R.drawable.ic_github).setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); - SpannableString span = new SpannableString("About App"); - span.setSpan(new TypefaceSpan(this, "fonts/google_sans_regular.ttf"), 0, span.length(), - Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); - menu.add(span); - span = new SpannableString("Crash Log"); - span.setSpan(new TypefaceSpan(this, "fonts/google_sans_regular.ttf"), 0, span.length(), - Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); - menu.add(span); - span = new SpannableString("Bug Report"); - span.setSpan(new TypefaceSpan(this, "fonts/google_sans_regular.ttf"), 0, span.length(), - Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); - menu.add(span); - return super.onCreateOptionsMenu(menu); - } - - public String readFile(File file) { - StringBuilder text = new StringBuilder(); - try { - BufferedReader br = new BufferedReader(new FileReader(file)); - String line = br.readLine(); - while (line != null) { - text.append(line); - text.append("\n"); - line = br.readLine(); - } - - new FileOutputStream(file).write(text.toString().getBytes()); - } catch (Exception e) { - e.printStackTrace(); - } - return text.toString(); - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - String title = item.getTitle().toString(); - if (title.equals("About App")) { - fancy.setTitle("About App").setMessage( - "An useful open source tool for Android Developers, which shows the package name and class name of current activity\n\nHere are the main features of this app!\n● It provides a freely moveable popup window to view current activity info\n● It supports text copying from popup window\n● It supports quick settings and app shortcut for easy access to the popup window. Meaning you can get the popup window in your screen from anywhere") - .show(); - } else if (title.equals("Crash Log")) { - String errorLog = readFile(new File(App.getCrashLogDir(), "crash.txt")); - if (errorLog.isEmpty()) - showToast("No log was found", 0); - else { - Intent intent = new Intent(this, CrashActivity.class); - intent.putExtra(CrashActivity.EXTRA_CRASH_INFO, errorLog); - intent.putExtra("Restart", false); - startActivity(intent); - } - } else if (title.equals("GitHub Repo")) { - fancy.setTitle("GitHub Repo").setMessage( - "It is an open source project. Would you like to visit the official github repo of this app") - .setPositiveButton("Yes", new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface di, int btn) { - di.dismiss(); - startActivity(new Intent().setAction(Intent.ACTION_VIEW) - .setData(Uri.parse("https://github.com/ratulhasanrahat/Current-Activity"))); - } - }).show(); - } else if (title.equals("Bug Report")) { - fancy.setTitle("Bug Report").setMessage( - "If you found a bug while using this app, please take a screenshot of it if possible. If it's a crash then you can find the crash log in this directory: " - + new File(App.getCrashLogDir(), "crash.txt").getAbsolutePath() - + "\n\nAfter you get all necessary things related to the bug, open an issue in github repo of this app with your bug report details") - .setPositiveButton("Create", new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface di, int btn) { - di.dismiss(); - startActivity(new Intent().setAction(Intent.ACTION_VIEW).setData( - Uri.parse("https://github.com/ratulhasanrahat/Current-Activity/issues/new"))); - } - }).show(); - } - return super.onOptionsItemSelected(item); - } - - @Override - protected void onDestroy() { - super.onDestroy(); - unregisterReceiver(mReceiver); - } - - class UpdateSwitchReceiver extends BroadcastReceiver { - @Override - public void onReceive(Context context, Intent intent) { - refreshWindowSwitch(); - refreshNotificationSwitch(); - refreshAccessibilitySwitch(); - } - } - - public static boolean usageStats(Context context) { - boolean granted = false; - AppOpsManager appOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE); - int mode = appOps.checkOpNoThrow(AppOpsManager.OPSTR_GET_USAGE_STATS, android.os.Process.myUid(), - context.getPackageName()); - - if (mode == AppOpsManager.MODE_DEFAULT) { - granted = (context.checkCallingOrSelfPermission( - android.Manifest.permission.PACKAGE_USAGE_STATS) == PackageManager.PERMISSION_GRANTED); - } else { - granted = (mode == AppOpsManager.MODE_ALLOWED); - } - return granted; - } - - public void setupBattery() { - fancy.setTitle("Battery Optimizations").setMessage( - "Please remove battery optimization/restriction from this app in order to run in background with full functionality") - .setPositiveButton("Ok", new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface di, int btn) { - di.dismiss(); - Intent intent = new Intent(); - intent.setAction("android.settings.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"); - intent.setData(Uri.parse("package:" + getPackageName())); - startActivity(intent); - } - }).show(); - - } + public static final String ACTION_STATE_CHANGED = "io.github.ratul.topactivity.ACTION_STATE_CHANGED"; + public static final String EXTRA_FROM_QS_TILE = "from_qs_tile"; + private ActivityResultLauncher notificationPermissionLauncher; + private BroadcastReceiver updateReceiver; + private SwitchCompat showWindow, showNotification, useAccessibility; + private PackageMonitoringService monitoringService; + private boolean isServiceBound = false; + + private final ServiceConnection serviceConnection = new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + PackageMonitoringService.LocalBinder binder = + (PackageMonitoringService.LocalBinder) service; + monitoringService = binder.getService(); + isServiceBound = true; + } + + @Override + public void onServiceDisconnected(ComponentName name) { + isServiceBound = false; + monitoringService = null; + } + }; + + class UpdateSwitchReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + refreshWindowSwitch(); + refreshNotificationSwitch(); + refreshAccessibilitySwitch(); + } + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + startAccessibilityService(); + DatabaseUtil.setDisplayWidth(getScreenWidth()); + + boolean isWindowActuallyShowing = WindowUtil.isViewVisible(); + if (DatabaseUtil.isShowingWindow() != isWindowActuallyShowing) { + DatabaseUtil.setShowingWindow(isWindowActuallyShowing); + } + + showWindow = findViewById(R.id.show_window); + showNotification = findViewById(R.id.show_notification); + useAccessibility = findViewById(R.id.use_accessibility); + Button downloadAccessibility = findViewById(R.id.download_accessibility); + Button configureWidth = findViewById(R.id.configure_width); + + updateReceiver = new UpdateSwitchReceiver(); + ContextCompat.registerReceiver(this, updateReceiver, + new IntentFilter(ACTION_STATE_CHANGED), ContextCompat.RECEIVER_EXPORTED); + + notificationPermissionLauncher = registerForActivityResult( + new ActivityResultContracts.RequestPermission(), + isGranted -> { + DatabaseUtil.setShowNotification(isGranted); + showNotification.setChecked(isGranted); + }); + + useAccessibility.setOnCheckedChangeListener((button, isChecked) -> { + DatabaseUtil.setUseAccessibility(isChecked); + startAccessibilityService(); + }); + + showNotification.setOnCheckedChangeListener((button, isChecked) -> { + DatabaseUtil.setShowNotification(isChecked); + + if (isChecked && !isNotificationGranted()) { + requestNotificationPermission(); + } + }); + + showWindow.setOnClickListener(v -> { + boolean isChecked = showWindow.isChecked(); + + if (!isChecked) { + DatabaseUtil.setShowingWindow(false); + NotificationReceiver.cancelNotification(); + WindowUtil.dismiss(this); + } + + if (isSystemOverlayGranted() && isCommonPermissionsGranted()) { + DatabaseUtil.setShowingWindow(true); + WindowUtil.show(this, getPackageName(), getClass().getName()); + startAccessibilityService(); + startPackageMonitoringService(); + } else { + showWindow.setChecked(false); + requestSystemOverlayPermission(); + requestCommonPermissions(); + } + }); + + downloadAccessibility.setOnClickListener(v -> { + Intent intent = new Intent(Intent.ACTION_VIEW) + .setData(Uri.parse( + "https://github.com/codehasan/Current-Activity/releases/tag/v" + + BuildConfig.VERSION_NAME)); + startActivity(intent); + }); + + configureWidth.setOnClickListener(v -> configureWidth()); + + if (handleQsTileIntent(getIntent())) { + moveTaskToBack(true); + } + } + + @Override + protected void onNewIntent(Intent intent) { + super.onNewIntent(intent); + if (handleQsTileIntent(intent)) { + moveTaskToBack(true); + } + } + + @Override + protected void onResume() { + super.onResume(); + startAccessibilityService(); + refreshWindowSwitch(); + refreshNotificationSwitch(); + refreshAccessibilitySwitch(); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + menu.add("GitHub Repo") + .setIcon(R.drawable.ic_github) + .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); + menu.add("Check for Update"); + return super.onCreateOptionsMenu(menu); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + CharSequence title = item.getTitle(); + if (isNullOrEmpty(title)) return true; + + Intent intent = new Intent(Intent.ACTION_VIEW); + switch (title.toString()) { + case "GitHub Repo": + intent.setData(Uri.parse("https://github.com/codehasan/Current-Activity")); + startActivity(intent); + break; + case "Check for Update": + intent.setData(Uri.parse("https://github.com/codehasan/Current-Activity/releases")); + startActivity(intent); + break; + } + return true; + } + + @Override + protected void onDestroy() { + unregisterReceiver(updateReceiver); + super.onDestroy(); + } + + private int getScreenWidth() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + WindowMetrics windowMetrics = getWindowManager().getCurrentWindowMetrics(); + Insets insets = windowMetrics.getWindowInsets().getInsetsIgnoringVisibility(WindowInsets.Type.systemBars()); + return windowMetrics.getBounds().width() - insets.left - insets.right; + } else { + DisplayMetrics displayMetrics = new DisplayMetrics(); + getWindowManager().getDefaultDisplay().getMetrics(displayMetrics); + return displayMetrics.widthPixels; + } + } + + private boolean handleQsTileIntent(Intent intent) { + if (intent.getBooleanExtra(EXTRA_FROM_QS_TILE, false)) { + showWindow.setChecked(true); + showWindow.callOnClick(); + return DatabaseUtil.isShowingWindow(); + } + return false; + } + + private boolean isCommonPermissionsGranted() { + return !isAccessibilityNotStarted() && isUsageStatsGranted(); + } + + private boolean isSystemOverlayGranted() { + return Settings.canDrawOverlays(this); + } + + private boolean isNotificationGranted() { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) { + return true; + } + return checkSelfPermission(POST_NOTIFICATIONS) == PERMISSION_GRANTED; + } + + private boolean isUsageStatsGranted() { + AppOpsManager appOps = (AppOpsManager) getSystemService(Context.APP_OPS_SERVICE); + int mode = appOps.checkOpNoThrow(AppOpsManager.OPSTR_GET_USAGE_STATS, + Process.myUid(), getPackageName()); + + if (mode == AppOpsManager.MODE_DEFAULT) { + return checkCallingOrSelfPermission(PACKAGE_USAGE_STATS) == PERMISSION_GRANTED; + } + return mode == AppOpsManager.MODE_ALLOWED; + } + + @SuppressWarnings("ConstantConditions") + private boolean isAccessibilityNotStarted() { + return BuildConfig.FLAVOR.equals("global") && + DatabaseUtil.useAccessibility() && + AccessibilityMonitoringService.getInstance() == null; + } + + private void configureWidth() { + View dialogView = getLayoutInflater().inflate(R.layout.content_configure_width, null); + EditText widthInput = dialogView.findViewById(R.id.width); + TextView helperText = dialogView.findViewById(R.id.helper); + + int screenWidth = getScreenWidth(); + int userWidth = DatabaseUtil.getUserWidth(); + + if (userWidth != -1) { + widthInput.setText(String.valueOf(userWidth)); + } + helperText.append("enter a width between 500 and " + screenWidth + "."); + + AlertDialog alertDialog = new AlertDialog.Builder(this) + .setTitle("Configure Width") + .setView(dialogView) + .setNeutralButton("Cancel", (dialog, which) -> dialog.dismiss()) + .setPositiveButton("Save", null) + .create(); + + alertDialog.setOnShowListener(dialog -> { + Button saveButton = alertDialog.getButton(AlertDialog.BUTTON_POSITIVE); + saveButton.setOnClickListener(v -> { + String input = widthInput.getText().toString(); + + if (input.trim().isEmpty()) { + DatabaseUtil.setUserWidth(-1); + dialog.dismiss(); + App.showToast(this, "Saved"); + return; + } + + int width = Integer.parseInt(input); + if (width < 500) { + widthInput.setError("Width should be greater than 500"); + return; + } else if (width > screenWidth) { + widthInput.setError("Width should be less than screen width (" + screenWidth + ")"); + return; + } + + DatabaseUtil.setUserWidth(width); + dialog.dismiss(); + App.showToast(this, "Saved"); + }); + }); + + alertDialog.show(); + } + + private void startAccessibilityService() { + // Start Accessibility Monitoring Service if accessibility is enabled + if (isAccessibilityNotStarted()) { + Intent intent = new Intent( + this, AccessibilityMonitoringService.class); + getApplicationContext().startService(intent); + } + } + + private void startPackageMonitoringService() { + Intent intent = new Intent(this, PackageMonitoringService.class); + getApplicationContext().startService(intent); + getApplicationContext().bindService( + intent, serviceConnection, Context.BIND_AUTO_CREATE); + } + + private void refreshWindowSwitch() { + showWindow.setChecked(DatabaseUtil.isShowingWindow()); + } + + private void refreshNotificationSwitch() { + if (!isNotificationGranted()) { + DatabaseUtil.setShowNotification(false); + showNotification.setChecked(false); + return; + } + showNotification.setChecked(DatabaseUtil.isShowNotification()); + } + + private void refreshAccessibilitySwitch() { + useAccessibility.setChecked(DatabaseUtil.useAccessibility()); + } + + private void requestNotificationPermission() { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) { + return; + } + if (checkSelfPermission(POST_NOTIFICATIONS) != PERMISSION_GRANTED) { + notificationPermissionLauncher.launch(POST_NOTIFICATIONS); + } + } + + private void requestSystemOverlayPermission() { + if (!Settings.canDrawOverlays(this)) { + new AlertDialog.Builder(this) + .setTitle("System Overlay") + .setMessage("Please allow draw over other apps permission for 'Current Activity'") + .setPositiveButton("Settings", (dialog, which) -> { + Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION) + .setData(Uri.parse("package:" + getPackageName())); + startActivity(intent); + dialog.dismiss(); + }) + .show(); + } + } + + private void requestCommonPermissions() { + if (isAccessibilityNotStarted()) { + new AlertDialog.Builder(this) + .setTitle("Accessibility Permission") + .setMessage("Please enable Accessibility Service for 'Current Activity'") + .setPositiveButton("Settings", (dialog, button) -> { + startActivity(new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS)); + dialog.dismiss(); + }) + .show(); + } + + if (!isUsageStatsGranted()) { + new AlertDialog.Builder(this) + .setTitle("Usage Access") + .setMessage("Please allow Usage Access permission for 'Current Activity'") + .setPositiveButton("Settings", (di, btn) -> { + startActivity(new Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS)); + di.dismiss(); + }) + .show(); + } + } } diff --git a/app/src/main/java/io/github/ratul/topactivity/ui/ShortcutHandlerActivity.java b/app/src/main/java/io/github/ratul/topactivity/ui/ShortcutHandlerActivity.java deleted file mode 100644 index a13e9cf..0000000 --- a/app/src/main/java/io/github/ratul/topactivity/ui/ShortcutHandlerActivity.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright (C) 2022 Ratul Hasan - * - * 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 - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package io.github.ratul.topactivity.ui; - -import android.annotation.TargetApi; -import android.app.Activity; -import android.content.Intent; -import android.os.Build; -import android.os.Bundle; -import android.provider.Settings; -import androidx.appcompat.app.AppCompatActivity; -import io.github.ratul.topactivity.utils.DatabaseUtil; -import io.github.ratul.topactivity.utils.WindowUtil; -import io.github.ratul.topactivity.model.NotificationMonitor; -import io.github.ratul.topactivity.service.MonitoringService; -import io.github.ratul.topactivity.service.AccessibilityMonitoringService; - -/** - * Created by Wen on 16/02/2017. - * Refactored by Ratul on 04/05/2022. - */ -@TargetApi(Build.VERSION_CODES.N) -public class ShortcutHandlerActivity extends AppCompatActivity { - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - DatabaseUtil.setDisplayWidth(MainActivity.getScreenWidth(this)); - - if (!MainActivity.usageStats(this) || !Settings.canDrawOverlays(this)) { - Intent intent = new Intent(this, MainActivity.class); - intent.putExtra(MainActivity.EXTRA_FROM_QS_TILE, true); - startActivity(intent); - finish(); - } else if (AccessibilityMonitoringService.getInstance() == null && DatabaseUtil.hasAccess()) - startService(new Intent().setClass(this, AccessibilityMonitoringService.class)); - - boolean isShow = !DatabaseUtil.isShowWindow(); - DatabaseUtil.setIsShowWindow(isShow); - if (!isShow) { - WindowUtil.dismiss(this); - NotificationMonitor.showNotification(this, true); - } else { - WindowUtil.init(this); - NotificationMonitor.showNotification(this, false); - startService(new Intent(this, MonitoringService.class)); - } - sendBroadcast(new Intent(MainActivity.ACTION_STATE_CHANGED)); - finish(); - } -} diff --git a/app/src/main/java/io/github/ratul/topactivity/utils/DatabaseUtil.java b/app/src/main/java/io/github/ratul/topactivity/utils/DatabaseUtil.java index a57d6fe..9d821f8 100644 --- a/app/src/main/java/io/github/ratul/topactivity/utils/DatabaseUtil.java +++ b/app/src/main/java/io/github/ratul/topactivity/utils/DatabaseUtil.java @@ -16,8 +16,6 @@ */ package io.github.ratul.topactivity.utils; -import android.content.Context; -import android.content.SharedPreferences; import io.github.ratul.topactivity.App; /** @@ -25,64 +23,55 @@ * Refactored by Ratul on 04/05/2022. */ public class DatabaseUtil { - private static SharedPreferences sp = App.getApp().getSharedPreferences("io.github.ratul.topactivity", 0); - - public static int getDisplayWidth() { - return sp.getInt("width", 720); - } - - public static void setDisplayWidth(int width) { - sp.edit().putInt("width", width).apply(); - } - - public static boolean isShowWindow() { - return sp.getBoolean("is_show_window", false); - } - - public static boolean hasBattery() { - return sp.getBoolean("hasBattery", false); - } - - public static void setHasBattery(boolean bool) { - sp.edit().putBoolean("hasBattery", bool).apply(); - } - - public static void setIsShowWindow(boolean isShow) { - sp.edit().putBoolean("is_show_window", isShow).apply(); - } - - public static boolean appInitiated() { - return sp.getBoolean("app_init", false); - } - - public static void setAppInitiated(boolean added) { - sp.edit().putBoolean("app_init", added).apply(); - } - - public static boolean hasAccess() { - return sp.getBoolean("has_access", true); - } - - public static void setHasAccess(boolean added) { - sp.edit().putBoolean("has_access", added).apply(); - } - - public static boolean hasQSTileAdded() { - return sp.getBoolean("has_qs_tile_added", false); - } - - public static void setQSTileAdded(boolean added) { - sp.edit().putBoolean("has_qs_tile_added", added).apply(); - } - - public static boolean isNotificationToggleEnabled() { - if (!hasQSTileAdded()) { - return true; - } - return sp.getBoolean("is_noti_toggle_enabled", true); - } - - public static void setNotificationToggleEnabled(boolean isEnabled) { - sp.edit().putBoolean("is_noti_toggle_enabled", isEnabled).apply(); - } + public static int getDisplayWidth() { + return App.getInstance().getSharedPreferences() + .getInt("width", 720); + } + + public static void setDisplayWidth(int width) { + App.getInstance().getSharedPreferences().edit() + .putInt("width", width) + .apply(); + } + + public static int getUserWidth() { + return App.getInstance().getSharedPreferences() + .getInt("user_width", -1); + } + + public static void setUserWidth(int width) { + App.getInstance().getSharedPreferences().edit() + .putInt("user_width", width) + .apply(); + } + + public static boolean isShowingWindow() { + return App.getInstance().getSharedPreferences() + .getBoolean("is_show_window", false); + } + + public static void setShowingWindow(boolean bool) { + App.getInstance().getSharedPreferences().edit() + .putBoolean("is_show_window", bool).apply(); + } + + public static boolean useAccessibility() { + return App.getInstance().getSharedPreferences() + .getBoolean("has_access", false); + } + + public static void setUseAccessibility(boolean bool) { + App.getInstance().getSharedPreferences().edit() + .putBoolean("has_access", bool).apply(); + } + + public static boolean isShowNotification() { + return App.getInstance().getSharedPreferences() + .getBoolean("show_notification", false); + } + + public static void setShowNotification(boolean bool) { + App.getInstance().getSharedPreferences().edit() + .putBoolean("show_notification", bool).apply(); + } } diff --git a/app/src/main/java/io/github/ratul/topactivity/utils/NullSafety.java b/app/src/main/java/io/github/ratul/topactivity/utils/NullSafety.java new file mode 100644 index 0000000..8e7a7f6 --- /dev/null +++ b/app/src/main/java/io/github/ratul/topactivity/utils/NullSafety.java @@ -0,0 +1,145 @@ +package io.github.ratul.topactivity.utils; + +import android.database.Cursor; +import android.os.Bundle; +import android.os.PersistableBundle; +import android.util.SparseArray; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import java.util.Collection; +import java.util.Map; +import java.util.Objects; + +/** + * Can be used up to Java 8 + */ +public class NullSafety { + @NonNull + public static T requireNonNullElse(@Nullable T obj, @NonNull T defaultObj) { + return (obj != null) ? obj : Objects.requireNonNull(defaultObj, "defaultObj"); + } + + /** + * Returns {@code true} if the given {@link CharSequence} is {@code null} or + * consists only of whitespace characters. + */ + public static boolean isNullOrEmpty(@Nullable CharSequence charSequence) { + return charSequence == null || isBlank(charSequence); + } + + /** + * Returns {@code true} if the given array is {@code null} or has no elements. + */ + public static boolean isNullOrEmpty(@Nullable T[] array) { + return array == null || array.length == 0; + } + + /** + * Returns {@code true} if the given array is {@code null} or has no elements. + */ + public static boolean isNullOrEmpty(@Nullable int[] array) { + return array == null || array.length == 0; + } + + /** + * Returns {@code true} if the given array is {@code null} or has no elements. + */ + public static boolean isNullOrEmpty(@Nullable char[] array) { + return array == null || array.length == 0; + } + + /** + * Returns {@code true} if the given array is {@code null} or has no elements. + */ + public static boolean isNullOrEmpty(@Nullable byte[] array) { + return array == null || array.length == 0; + } + + /** + * Returns {@code true} if the given array is {@code null} or has no elements. + */ + public static boolean isNullOrEmpty(@Nullable short[] array) { + return array == null || array.length == 0; + } + + /** + * Returns {@code true} if the given array is {@code null} or has no elements. + */ + public static boolean isNullOrEmpty(@Nullable long[] array) { + return array == null || array.length == 0; + } + + /** + * Returns {@code true} if the given array is {@code null} or has no elements. + */ + public static boolean isNullOrEmpty(@Nullable double[] array) { + return array == null || array.length == 0; + } + + /** + * Returns {@code true} if the given array is {@code null} or has no elements. + */ + public static boolean isNullOrEmpty(@Nullable float[] array) { + return array == null || array.length == 0; + } + + /** + * Returns {@code true} if the given {@link Collection} is {@code null} or empty. + */ + public static boolean isNullOrEmpty(@Nullable Collection collection) { + return collection == null || collection.isEmpty(); + } + + /** + * Returns {@code true} if the given {@link SparseArray} is {@code null} or has no + * elements. + */ + public static boolean isNullOrEmpty(@Nullable SparseArray sparseArray) { + return sparseArray == null || sparseArray.size() == 0; + } + + /** + * Returns {@code true} if the given {@link Map} is {@code null} or empty. + */ + public static boolean isNullOrEmpty(@Nullable Map map) { + return map == null || map.isEmpty(); + } + + /** + * Returns {@code true} if the given {@link Bundle} is {@code null} or empty. + */ + public static boolean isNullOrEmpty(@Nullable Bundle bundle) { + return bundle == null || bundle.isEmpty(); + } + + /** + * Returns {@code true} if the given {@link PersistableBundle} is {@code null} or empty. + */ + public static boolean isNullOrEmpty(@Nullable PersistableBundle bundle) { + return bundle == null || bundle.isEmpty(); + } + + /** + * Returns {@code true} if the given {@link Cursor} is {@code null}, closed, or has no + * rows. + */ + public static boolean isNullOrEmpty(@Nullable Cursor cursor) { + return cursor == null || cursor.isClosed() || cursor.getCount() == 0; + } + + private static boolean isBlank(@NonNull CharSequence charSequence) { + int length = charSequence.length(); + int left = 0; + + while (left < length) { + char ch = charSequence.charAt(left); + if (ch != ' ' && ch != '\t' && !Character.isWhitespace(ch)) { + return false; + } + left++; + } + return true; + } +} diff --git a/app/src/main/java/io/github/ratul/topactivity/utils/WindowUtil.java b/app/src/main/java/io/github/ratul/topactivity/utils/WindowUtil.java index e797c92..1c856d1 100644 --- a/app/src/main/java/io/github/ratul/topactivity/utils/WindowUtil.java +++ b/app/src/main/java/io/github/ratul/topactivity/utils/WindowUtil.java @@ -16,186 +16,159 @@ */ package io.github.ratul.topactivity.utils; +import android.annotation.SuppressLint; import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; import android.graphics.PixelFormat; import android.os.Build; -import android.view.MotionEvent; -import android.view.View; import android.view.Gravity; import android.view.LayoutInflater; +import android.view.MotionEvent; +import android.view.View; import android.view.WindowManager; -import android.content.ClipboardManager; -import android.content.ClipData; -import android.widget.Toast; -import android.widget.LinearLayout; -import android.graphics.Typeface; -import android.content.Intent; -import com.google.android.material.imageview.ShapeableImageView; +import android.widget.ImageView; +import android.widget.TextView; + +import androidx.annotation.NonNull; + +import io.github.ratul.topactivity.App; import io.github.ratul.topactivity.R; -import io.github.ratul.topactivity.model.NotificationMonitor; -import com.google.android.material.textview.MaterialTextView; +import io.github.ratul.topactivity.receivers.NotificationReceiver; +import io.github.ratul.topactivity.services.QuickSettingsTileService; import io.github.ratul.topactivity.ui.MainActivity; -import io.github.ratul.topactivity.ui.BackgroundActivity; -import io.github.ratul.topactivity.service.QuickSettingsTileService; -import io.github.ratul.topactivity.service.MonitoringService; -import io.github.ratul.topactivity.service.AccessibilityMonitoringService; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; -import io.github.ratul.topactivity.App; /** * Created by Ratul on 04/05/2022. */ public class WindowUtil { - private static WindowManager.LayoutParams sWindowParams; - public static WindowManager sWindowManager; - private static View sView; - private static int xInitCord = 0; - private static int yInitCord = 0; - private static int xInitMargin = 0; - private static int yInitMargin = 0; - private static String text, text1; - private static MaterialTextView appName, packageName, className; - private static ClipboardManager clipboard; - public static boolean viewAdded = false; - - public static void init(final Context context) { - sWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); - - sWindowParams = new WindowManager.LayoutParams(WindowManager.LayoutParams.WRAP_CONTENT, - WindowManager.LayoutParams.WRAP_CONTENT, - Build.VERSION.SDK_INT >= Build.VERSION_CODES.O ? WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY - : WindowManager.LayoutParams.TYPE_PHONE, - WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, PixelFormat.TRANSLUCENT); - - sWindowParams.gravity = Gravity.CENTER; - sWindowParams.width = (DatabaseUtil.getDisplayWidth() / 2) + 300; - sWindowParams.windowAnimations = android.R.style.Animation_Toast; - - sView = LayoutInflater.from(context).inflate(R.layout.window_tasks, null); - clipboard = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE); - appName = sView.findViewById(R.id.text); - packageName = sView.findViewById(R.id.text1); - className = sView.findViewById(R.id.text2); - ShapeableImageView closeBtn = sView.findViewById(R.id.closeBtn); - - closeBtn.setOnClickListener(new View.OnClickListener() { - public void onClick(View v) { - dismiss(context); - DatabaseUtil.setIsShowWindow(false); - NotificationMonitor.cancelNotification(context); - context.sendBroadcast(new Intent(MainActivity.ACTION_STATE_CHANGED)); - } - }); - - appName.setOnLongClickListener(new View.OnLongClickListener() { - public boolean onLongClick(View v) { - copyString(context, text, "App name copied"); - return true; - } - }); - - packageName.setOnLongClickListener(new View.OnLongClickListener() { - public boolean onLongClick(View v) { - copyString(context, text, "Package name copied"); - return true; - } - }); - - className.setOnLongClickListener(new View.OnLongClickListener() { - public boolean onLongClick(View v) { - copyString(context, text1, "Class name copied"); - return true; - } - }); - - sView.setOnTouchListener(new View.OnTouchListener() { - public boolean onTouch(View view, MotionEvent event) { - WindowManager.LayoutParams layoutParams = sWindowParams; - - int xCord = (int) event.getRawX(); - int yCord = (int) event.getRawY(); - int xCordDestination; - int yCordDestination; - int action = event.getAction(); - - if (action == MotionEvent.ACTION_DOWN) { - xInitCord = xCord; - yInitCord = yCord; - xInitMargin = layoutParams.x; - yInitMargin = layoutParams.y; - } - else if (action == MotionEvent.ACTION_MOVE) { - int xDiffMove = xCord - xInitCord; - int yDiffMove = yCord - yInitCord; - xCordDestination = xInitMargin + xDiffMove; - yCordDestination = yInitMargin + yDiffMove; - - layoutParams.x = xCordDestination; - layoutParams.y = yCordDestination; - sWindowManager.updateViewLayout(view, layoutParams); - } - return true; - } - }); - } - - private static void copyString(Context context, String str, String msg) { - if (Build.VERSION.SDK_INT < 29) { - ClipData clip = ClipData.newPlainText("Current Activity", str); - clipboard.setPrimaryClip(clip); - } else { - context.startActivity( - new Intent(context, BackgroundActivity.class).putExtra(BackgroundActivity.STRING_COPY, str) - .putExtra(BackgroundActivity.COPY_MSG, msg).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)); - } - App.showToast(msg, 0); - } - - public static String getAppName(Context context, String pkg) { - try { - PackageManager pm = context.getPackageManager(); - return pm.getApplicationLabel(pm.getApplicationInfo(pkg, 0)).toString(); - } catch (Exception e) { - return "Unknown"; - } - } - - public static void show(Context context, String pkg, String clas) { - if (sWindowManager == null) { - init(context); - } - appName.setText(getAppName(context, pkg)); - packageName.setText(pkg); - className.setText(clas); - - if (!viewAdded) { - viewAdded = true; - if (DatabaseUtil.isShowWindow()) { - sWindowManager.addView(sView, sWindowParams); - } - } - - if (NotificationMonitor.builder != null) { - NotificationMonitor.builder.setContentTitle(pkg); - NotificationMonitor.builder.setContentText(clas); - NotificationMonitor.notifManager.notify(NotificationMonitor.NOTIFICATION_ID, - NotificationMonitor.builder.build()); - } - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - QuickSettingsTileService.updateTile(context); - } - } - - public static void dismiss(Context context) { - viewAdded = false; - try { - sWindowManager.removeView(sView); - } catch (Exception e) { - e.printStackTrace(); - } - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - QuickSettingsTileService.updateTile(context); - } - } + private static WindowManager.LayoutParams layoutParams; + private static WindowManager windowManager; + private static PackageManager packageManager; + private static View baseView; + private static int xInitCord = 0; + private static int yInitCord = 0; + private static int xInitMargin = 0; + private static int yInitMargin = 0; + private static TextView appName, packageName, className; + + public static void show( + @NonNull Context context, @NonNull String pkg, @NonNull String cls) { + if (windowManager == null || baseView == null) { + init(context.getApplicationContext()); + } + + if (!isViewVisible()) { + int userWidth = DatabaseUtil.getUserWidth(); + if (userWidth != -1) { + layoutParams.width = userWidth; + } else { + double displaySize = Math.min(1100, DatabaseUtil.getDisplayWidth()); + layoutParams.width = (int) (displaySize * 0.65); + } + windowManager.addView(baseView, layoutParams); + QuickSettingsTileService.updateTile(context); + } + + boolean isPackageChanged = !packageName.getText().toString().equals(pkg); + boolean isClassChanged = !className.getText().toString().equals(cls); + + if (isPackageChanged) { + appName.setText(getAppName(pkg)); + packageName.setText(pkg); + } + + if (isClassChanged) { + className.setText(cls); + } + + if (isPackageChanged || isClassChanged) { + NotificationReceiver.showNotification(context, pkg, cls); + } + } + + public static void dismiss(@NonNull Context context) { + if (windowManager != null) { + windowManager.removeView(baseView); + } + QuickSettingsTileService.updateTile(context); + } + + public static boolean isViewVisible() { + return baseView != null && baseView.isAttachedToWindow(); + } + + @SuppressLint("ClickableViewAccessibility") + private static void init(@NonNull Context context) { + windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); + packageManager = context.getPackageManager(); + + layoutParams = new WindowManager.LayoutParams(WindowManager.LayoutParams.WRAP_CONTENT, + WindowManager.LayoutParams.WRAP_CONTENT, + Build.VERSION.SDK_INT >= Build.VERSION_CODES.O ? WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY + : WindowManager.LayoutParams.TYPE_PHONE, + WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, PixelFormat.TRANSLUCENT); + + layoutParams.gravity = Gravity.CENTER; + layoutParams.windowAnimations = android.R.style.Animation_Toast; + + baseView = LayoutInflater.from(context).inflate(R.layout.content_activity_info, null); + appName = baseView.findViewById(R.id.app_name); + packageName = baseView.findViewById(R.id.package_name); + className = baseView.findViewById(R.id.class_name); + ImageView closeBtn = baseView.findViewById(R.id.closeBtn); + + View.OnLongClickListener copyListener = v -> { + TextView textView = (TextView) v; + String label = ""; + + if (v.getId() == R.id.app_name) label = "App name"; + else if (v.getId() == R.id.package_name) label = "Package"; + else if (v.getId() == R.id.class_name) label = "Class"; + + App.copyString(context, textView.getText().toString(), label + " copied"); + return true; + }; + + closeBtn.setOnClickListener(v -> { + DatabaseUtil.setShowingWindow(false); + NotificationReceiver.cancelNotification(); + dismiss(context); + context.sendBroadcast(new Intent(MainActivity.ACTION_STATE_CHANGED)); + }); + + packageName.setOnLongClickListener(copyListener); + className.setOnLongClickListener(copyListener); + + baseView.setOnTouchListener((view, event) -> { + int xCord = (int) event.getRawX(); + int yCord = (int) event.getRawY(); + + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: + xInitCord = xCord; + yInitCord = yCord; + xInitMargin = layoutParams.x; + yInitMargin = layoutParams.y; + return true; + case MotionEvent.ACTION_MOVE: + int xDiffMove = xCord - xInitCord; + int yDiffMove = yCord - yInitCord; + layoutParams.x = xInitMargin + xDiffMove; + layoutParams.y = yInitMargin + yDiffMove; + windowManager.updateViewLayout(view, layoutParams); + return true; + } + return false; + }); + } + + private static String getAppName(@NonNull String pkg) { + try { + return packageManager.getApplicationLabel( + packageManager.getApplicationInfo(pkg, 0)).toString(); + } catch (PackageManager.NameNotFoundException ignored) { + return "Unknown"; + } + } } diff --git a/app/src/main/java/io/github/ratul/topactivity/view/BoldTextView.java b/app/src/main/java/io/github/ratul/topactivity/view/BoldTextView.java deleted file mode 100644 index 5d87080..0000000 --- a/app/src/main/java/io/github/ratul/topactivity/view/BoldTextView.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (C) 2022 Ratul Hasan - * - * 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 - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package io.github.ratul.topactivity.view; - -import android.content.Context; -import android.graphics.Canvas; -import android.graphics.Typeface; -import android.util.AttributeSet; -import com.google.android.material.textview.MaterialTextView; - -public class BoldTextView extends MaterialTextView { - public void setBoldFont(Context context) { - Typeface face = Typeface.createFromAsset(context.getAssets(), "fonts/google_sans_bold.ttf"); - super.setTypeface(face); - } - - public BoldTextView(Context context) { - super(context); - setBoldFont(context); - } - - public BoldTextView(Context context, AttributeSet attrs) { - super(context, attrs); - setBoldFont(context); - } - - public BoldTextView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - setBoldFont(context); - } - - public BoldTextView(Context context, AttributeSet attrs, int defStyle, int res) { - super(context, attrs, defStyle, res); - setBoldFont(context); - } - - protected void onDraw(Canvas canvas) { - super.onDraw(canvas); - } -} diff --git a/app/src/main/java/io/github/ratul/topactivity/view/NormalTextView.java b/app/src/main/java/io/github/ratul/topactivity/view/NormalTextView.java deleted file mode 100644 index 381c8d7..0000000 --- a/app/src/main/java/io/github/ratul/topactivity/view/NormalTextView.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (C) 2022 Ratul Hasan - * - * 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 - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package io.github.ratul.topactivity.view; - -import android.content.Context; -import android.graphics.Canvas; -import android.graphics.Typeface; -import android.util.AttributeSet; -import com.google.android.material.textview.MaterialTextView; - -public class NormalTextView extends MaterialTextView { - public void setRegularFont(Context context) { - Typeface face = Typeface.createFromAsset(context.getAssets(), "fonts/google_sans_regular.ttf"); - super.setTypeface(face); - } - - public NormalTextView(Context context) { - super(context); - setRegularFont(context); - } - - public NormalTextView(Context context, AttributeSet attrs) { - super(context, attrs); - setRegularFont(context); - } - - public NormalTextView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - setRegularFont(context); - } - - public NormalTextView(Context context, AttributeSet attrs, int defStyle, int res) { - super(context, attrs, defStyle, res); - setRegularFont(context); - } - - protected void onDraw (Canvas canvas) { - super.onDraw(canvas); - } -} diff --git a/app/src/main/java/io/github/ratul/topactivity/view/RegularTextView.java b/app/src/main/java/io/github/ratul/topactivity/view/RegularTextView.java deleted file mode 100644 index 8470b9c..0000000 --- a/app/src/main/java/io/github/ratul/topactivity/view/RegularTextView.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (C) 2022 Ratul Hasan - * - * 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 - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package io.github.ratul.topactivity.view; - -import android.content.Context; -import android.graphics.Canvas; -import android.graphics.Typeface; -import android.util.AttributeSet; -import com.google.android.material.textview.MaterialTextView; - -public class RegularTextView extends MaterialTextView { - public void setRegularFont(Context context) { - Typeface face = Typeface.createFromAsset(context.getAssets(), "fonts/google_sans_regular.ttf"); - super.setTypeface(face, 1); - } - - public RegularTextView(Context context) { - super(context); - setRegularFont(context); - } - - public RegularTextView(Context context, AttributeSet attrs) { - super(context, attrs); - setRegularFont(context); - } - - public RegularTextView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - setRegularFont(context); - } - - public RegularTextView(Context context, AttributeSet attrs, int defStyle, int res) { - super(context, attrs, defStyle, res); - setRegularFont(context); - } - - protected void onDraw(Canvas canvas) { - super.onDraw(canvas); - } -} diff --git a/app/src/main/res/anim/bottom_sheet_close.xml b/app/src/main/res/anim/bottom_sheet_close.xml deleted file mode 100644 index 9dab7af..0000000 --- a/app/src/main/res/anim/bottom_sheet_close.xml +++ /dev/null @@ -1,6 +0,0 @@ - - diff --git a/app/src/main/res/anim/bottom_sheet_enter.xml b/app/src/main/res/anim/bottom_sheet_enter.xml deleted file mode 100644 index ee4ef8d..0000000 --- a/app/src/main/res/anim/bottom_sheet_enter.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - diff --git a/app/src/main/res/anim/fancy_animation_enter.xml b/app/src/main/res/anim/fancy_animation_enter.xml deleted file mode 100644 index e332395..0000000 --- a/app/src/main/res/anim/fancy_animation_enter.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/app/src/main/res/anim/fancy_animation_exit.xml b/app/src/main/res/anim/fancy_animation_exit.xml deleted file mode 100644 index 3acb0c5..0000000 --- a/app/src/main/res/anim/fancy_animation_exit.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/app/src/main/res/drawable/ic_cancel.xml b/app/src/main/res/drawable/ic_cancel.xml new file mode 100644 index 0000000..fe302f5 --- /dev/null +++ b/app/src/main/res/drawable/ic_cancel.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_class.xml b/app/src/main/res/drawable/ic_class.xml new file mode 100644 index 0000000..86cec13 --- /dev/null +++ b/app/src/main/res/drawable/ic_class.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_github.xml b/app/src/main/res/drawable/ic_github.xml index 03381d5..d56a2c9 100644 --- a/app/src/main/res/drawable/ic_github.xml +++ b/app/src/main/res/drawable/ic_github.xml @@ -2,10 +2,9 @@ + android:viewportWidth="24" + android:viewportHeight="24"> - + android:pathData="M12,2A10,10 0,0 0,2 12C2,16.42 4.87,20.17 8.84,21.5C9.34,21.58 9.5,21.27 9.5,21C9.5,20.77 9.5,20.14 9.5,19.31C6.73,19.91 6.14,17.97 6.14,17.97C5.68,16.81 5.03,16.5 5.03,16.5C4.12,15.88 5.1,15.9 5.1,15.9C6.1,15.97 6.63,16.93 6.63,16.93C7.5,18.45 8.97,18 9.54,17.76C9.63,17.11 9.89,16.67 10.17,16.42C7.95,16.17 5.62,15.31 5.62,11.5C5.62,10.39 6,9.5 6.65,8.79C6.55,8.54 6.2,7.5 6.75,6.15C6.75,6.15 7.59,5.88 9.5,7.17C10.29,6.95 11.15,6.84 12,6.84C12.85,6.84 13.71,6.95 14.5,7.17C16.41,5.88 17.25,6.15 17.25,6.15C17.8,7.5 17.45,8.54 17.35,8.79C18,9.5 18.38,10.39 18.38,11.5C18.38,15.32 16.04,16.16 13.81,16.41C14.17,16.72 14.5,17.33 14.5,18.26C14.5,19.6 14.5,20.68 14.5,21C14.5,21.27 14.66,21.59 15.17,21.5C19.14,20.16 22,16.42 22,12A10,10 0,0 0,12 2Z" /> diff --git a/app/src/main/res/drawable/ic_launcher.png b/app/src/main/res/drawable/ic_launcher.png deleted file mode 100644 index e3ce3a3..0000000 Binary files a/app/src/main/res/drawable/ic_launcher.png and /dev/null differ diff --git a/app/src/main/res/drawable/ic_launcher_foreground.png b/app/src/main/res/drawable/ic_launcher_foreground.png deleted file mode 100644 index 645f29e..0000000 Binary files a/app/src/main/res/drawable/ic_launcher_foreground.png and /dev/null differ diff --git a/app/src/main/res/drawable/ic_launcher_round.png b/app/src/main/res/drawable/ic_launcher_round.png deleted file mode 100644 index 7b1dffb..0000000 Binary files a/app/src/main/res/drawable/ic_launcher_round.png and /dev/null differ diff --git a/app/src/main/res/drawable/layers.xml b/app/src/main/res/drawable/ic_logo.xml similarity index 88% rename from app/src/main/res/drawable/layers.xml rename to app/src/main/res/drawable/ic_logo.xml index b4f637e..ca4573d 100644 --- a/app/src/main/res/drawable/layers.xml +++ b/app/src/main/res/drawable/ic_logo.xml @@ -1,28 +1,28 @@ diff --git a/app/src/main/res/drawable/ic_package.xml b/app/src/main/res/drawable/ic_package.xml new file mode 100644 index 0000000..a1e3029 --- /dev/null +++ b/app/src/main/res/drawable/ic_package.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_shortcut.xml b/app/src/main/res/drawable/ic_shortcut.xml deleted file mode 100644 index b27c8af..0000000 --- a/app/src/main/res/drawable/ic_shortcut.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/app/src/main/res/font/product_sans_medium.ttf b/app/src/main/res/font/product_sans_medium.ttf new file mode 100644 index 0000000..1543660 Binary files /dev/null and b/app/src/main/res/font/product_sans_medium.ttf differ diff --git a/app/src/main/assets/fonts/google_sans_regular.ttf b/app/src/main/res/font/product_sans_regular.ttf similarity index 100% rename from app/src/main/assets/fonts/google_sans_regular.ttf rename to app/src/main/res/font/product_sans_regular.ttf diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 9629aa1..f222959 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -1,93 +1,160 @@ - + android:gravity="center_horizontal" + android:orientation="vertical"> - + android:text="Show Floating Window" + android:textColor="?editTextColor" + android:textSize="16sp" /> - + android:layout_height="wrap_content" /> - + android:background="?actionBarDivider" /> - + android:orientation="vertical"> - + + + + + + android:layout_height="wrap_content" /> - + android:background="?actionBarDivider" /> + - + android:orientation="vertical"> + + - + + + + android:layout_height="wrap_content" + android:visibility="@integer/use_accessibility" /> + +