├── .deepsource.toml ├── .github └── workflows │ ├── build.yml │ └── release.yml ├── .gitignore ├── .idea ├── .gitignore ├── .name ├── compiler.xml ├── deploymentTargetDropDown.xml ├── discord.xml ├── gradle.xml ├── misc.xml └── vcs.xml ├── LICENSE ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── abh80 │ │ └── smartedge │ │ └── ExampleInstrumentedTest.java │ ├── debug │ └── AndroidManifest.xml │ ├── github │ └── AndroidManifest.xml │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── abh80 │ │ │ └── smartedge │ │ │ ├── App.java │ │ │ ├── activities │ │ │ ├── AppearanceActivity.java │ │ │ ├── MainActivity.java │ │ │ ├── NotificationManageActivity.java │ │ │ ├── OverlayLayoutSettingActivity.java │ │ │ └── PermissionActivity.java │ │ │ ├── plugins │ │ │ ├── BasePlugin.java │ │ │ ├── BatteryPlugin │ │ │ │ └── BatteryPlugin.java │ │ │ ├── ExportedPlugins.java │ │ │ ├── MediaSession │ │ │ │ ├── MediaCallback.java │ │ │ │ ├── MediaSessionPlugin.java │ │ │ │ ├── SongVisualizer.java │ │ │ │ └── UpdateQueueStruct.java │ │ │ └── Notification │ │ │ │ ├── NotificationMeta.java │ │ │ │ ├── NotificationPlugin.java │ │ │ │ └── NotificationView.java │ │ │ ├── services │ │ │ ├── NotiService.java │ │ │ ├── OverlayService.java │ │ │ └── UpdaterService.java │ │ │ ├── utils │ │ │ ├── CallBack.java │ │ │ ├── NotificationAppMeta.java │ │ │ ├── NotificationHolderClass.java │ │ │ ├── SettingStruct.java │ │ │ └── adapters │ │ │ │ ├── NotificationManageAppsAdapter.java │ │ │ │ ├── NotificationViewPagerAdapter.java │ │ │ │ └── RecylerViewSettingsAdapter.java │ │ │ └── views │ │ │ ├── BatteryImageView.java │ │ │ └── ViewPagerOnClickable.java │ └── res │ │ ├── drawable-v24 │ │ ├── avd_pause_to_play.xml │ │ ├── avd_play_to_pause.xml │ │ ├── fast_forward.xml │ │ ├── fast_rewind.xml │ │ ├── launcher_foreground.png │ │ ├── pause.xml │ │ └── play.xml │ │ ├── drawable │ │ ├── custom_progress_bar_horizontal.xml │ │ ├── default_dot.xml │ │ ├── ic_baseline_add_24.xml │ │ ├── ic_baseline_arrow_back_ios_24.xml │ │ ├── ic_baseline_fast_forward_24.xml │ │ ├── ic_baseline_fast_rewind_24.xml │ │ ├── ic_baseline_message_24.xml │ │ ├── ic_baseline_open_in_new_24.xml │ │ ├── ic_baseline_pause_24.xml │ │ ├── ic_baseline_play_arrow_24.xml │ │ ├── ic_baseline_remove_24.xml │ │ ├── ic_outline_refresh_24.xml │ │ ├── ripple.xml │ │ ├── rounded_corner.xml │ │ ├── rounded_corner_setting_bottom.xml │ │ ├── rounded_corner_setting_top.xml │ │ ├── selected_dot.xml │ │ └── tab_selector.xml │ │ ├── layout │ │ ├── activity_main.xml │ │ ├── activity_permission.xml │ │ ├── appearence_layout.xml │ │ ├── battery_layout.xml │ │ ├── manage_notification_layout.xml │ │ ├── media_session_layout.xml │ │ ├── notification_layout.xml │ │ ├── overlay_layout.xml │ │ ├── overlay_layout_setting_activity.xml │ │ ├── setting_custom_layout.xml │ │ ├── single_notification.xml │ │ ├── single_notification_layout_app.xml │ │ ├── toggle_setting_layout.xml │ │ └── toggle_setting_null_layout.xml │ │ ├── menu │ │ └── notifications_top_bar_menu.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── ids.xml │ │ ├── strings.xml │ │ ├── styles.xml │ │ └── themes.xml │ │ └── xml │ │ ├── backup_rules.xml │ │ ├── data_extraction_rules.xml │ │ ├── global_action_bar_service.xml │ │ ├── overlay_layout_scene.xml │ │ └── provider_paths.xml │ └── test │ └── java │ └── com │ └── abh80 │ └── smartedge │ └── ExampleUnitTest.java ├── build.gradle ├── fastlane └── metadata │ └── android │ └── en-US │ ├── changelogs │ ├── 20202.txt │ ├── 20203.txt │ └── 20204.txt │ ├── full_description.txt │ ├── images │ ├── icon.png │ └── phoneScreenshots │ │ └── screenshot.png │ └── short_description.txt ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── package-lock.json ├── package.json ├── scripts └── release.js ├── settings.gradle └── yarn.lock /.deepsource.toml: -------------------------------------------------------------------------------- 1 | version = 1 2 | 3 | [[analyzers]] 4 | name = "java" 5 | enabled = true 6 | 7 | [analyzers.meta] 8 | runtime_version = "13" -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build APK 2 | 3 | on: 4 | pull_request: 5 | types: 6 | - opened 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v1 13 | - name: set up JDK 11 14 | uses: actions/setup-java@v1 15 | with: 16 | java-version: 11 17 | - name: Make Gradle executable 18 | run: chmod +x ./gradlew 19 | - name: Build with Gradle 20 | run: ./gradlew build 21 | - name: Build Release APK 22 | run: ./gradlew assembleRelease -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Build & Publish APK 2 | 3 | on: workflow_dispatch 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | strategy: 9 | matrix: 10 | node-version: [15.x] 11 | steps: 12 | - uses: actions/checkout@v1 13 | - name: set up JDK 11 14 | uses: actions/setup-java@v1 15 | with: 16 | java-version: 11 17 | - name: Decode Keystore 18 | uses: timheuer/base64-to-file@v1 19 | with: 20 | fileName: "keystore/keystore.jks" 21 | encodedString: ${{ secrets.ENCODED_KEYSTORE }} 22 | - name: Make Gradle executable 23 | run: chmod +x ./gradlew 24 | - name: Build with Gradle 25 | run: ./gradlew build 26 | env: 27 | SIGNING_KEY_PASSWORD: ${{ secrets.SIGNING_KEY_PASSWORD }} 28 | SIGNING_KEY_ALIAS: ${{ secrets.SIGNING_KEY_ALIAS }} 29 | - name: Build Release APK 30 | run: ./gradlew assembleGithub 31 | env: 32 | SIGNING_KEY_PASSWORD: ${{ secrets.SIGNING_KEY_PASSWORD }} 33 | SIGNING_KEY_ALIAS: ${{ secrets.SIGNING_KEY_ALIAS }} 34 | - name: Use Node.js ${{ matrix.node-version }} 35 | uses: actions/setup-node@v3 36 | with: 37 | node-version: ${{ matrix.node-version }} 38 | - name: Install Node Modules 39 | run: npm install 40 | - name: Publish Release 41 | run: node scripts/release.js 42 | env: 43 | TOKEN: ${{ secrets.TOKEN }} 44 | - name: Commit 45 | run: | 46 | git config --global user.name 'Github Actions' 47 | git config --global user.email 'action@github.com' 48 | git add . 49 | git commit -m "publish new build" 50 | - name: Push to Github 51 | uses: ad-m/github-push-action@master 52 | with: 53 | github_token: ${{ secrets.TOKEN }} 54 | force: true 55 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | .externalNativeBuild 14 | .cxx 15 | local.properties 16 | node_modules/ 17 | keystore/ 18 | .fleet -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | -------------------------------------------------------------------------------- /.idea/.name: -------------------------------------------------------------------------------- 1 | Smart Edge -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/deploymentTargetDropDown.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /.idea/discord.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 20 | 21 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 55 | 56 | 57 | 58 | 59 | 60 | 62 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Year abh80 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Smart Edge (Early Access) [![Build & Publish Debug APK](https://github.com/abh80/smart-edge/actions/workflows/release.yml/badge.svg)](https://github.com/abh80/smart-edge/actions/workflows/release.yml) 2 | Alternative to dynamic island for android. 3 | # Downloads 4 | 5 | [![Download Button](https://img.shields.io/github/v/release/abh80/smart-edge?color=7885FF&label=Android-Apk&logo=android&style=for-the-badge)](https://github.com/abh80/smart-edge/releases/download/20204/release.apk) 6 | # Donations 7 | Help support the project by donating ❤️ 8 | 9 | 10 | Donate with PayPal 11 | 12 | 13 | # Previews 14 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.application' 3 | } 4 | 5 | android { 6 | compileSdk 32 7 | sourceSets { 8 | github { 9 | manifest.srcFile 'github/AndroidManifest.xml' 10 | } 11 | } 12 | defaultConfig { 13 | applicationId "com.abh80.smartedge" 14 | minSdk 26 15 | targetSdk 32 16 | versionCode 20204 17 | versionName "2.2.4" 18 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 19 | } 20 | signingConfigs { 21 | github { 22 | def tmpFilePath = System.getProperty("user.home") + "/work/_temp/keystore/keystore.jks" 23 | storeFile = file(tmpFilePath) 24 | storePassword System.getenv("SIGNING_KEY_PASSWORD") 25 | keyAlias System.getenv("SIGNING_KEY_ALIAS") 26 | keyPassword System.getenv("SIGNING_KEY_PASSWORD") 27 | } 28 | } 29 | 30 | buildTypes { 31 | github { 32 | minifyEnabled false 33 | buildConfigField "Boolean", "AUTO_UPDATE", "true" 34 | signingConfig signingConfigs.github 35 | } 36 | release { 37 | minifyEnabled false 38 | buildConfigField "Boolean", "AUTO_UPDATE", "false" 39 | } 40 | debug { 41 | minifyEnabled false 42 | buildConfigField "Boolean", "AUTO_UPDATE", "true" 43 | } 44 | } 45 | compileOptions { 46 | sourceCompatibility JavaVersion.VERSION_1_8 47 | targetCompatibility JavaVersion.VERSION_1_8 48 | } 49 | } 50 | 51 | dependencies { 52 | 53 | implementation 'androidx.appcompat:appcompat:1.5.0' 54 | implementation 'com.google.android.material:material:1.8.0-alpha01' 55 | testImplementation 'junit:junit:4.13.2' 56 | androidTestImplementation 'androidx.test.ext:junit:1.1.3' 57 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' 58 | implementation 'org.apache.commons:commons-lang3:3.12.0' 59 | implementation 'com.android.volley:volley:1.2.1' 60 | implementation 'org.ocpsoft.prettytime:prettytime:5.0.2.Final' 61 | } 62 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /app/src/androidTest/java/com/abh80/smartedge/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.abh80.smartedge; 2 | 3 | import android.content.Context; 4 | 5 | import androidx.test.platform.app.InstrumentationRegistry; 6 | import androidx.test.ext.junit.runners.AndroidJUnit4; 7 | 8 | import org.junit.Test; 9 | import org.junit.runner.RunWith; 10 | 11 | import static org.junit.Assert.*; 12 | 13 | /** 14 | * Instrumented test, which will execute on an Android device. 15 | * 16 | * @see Testing documentation 17 | */ 18 | @RunWith(AndroidJUnit4.class) 19 | public class ExampleInstrumentedTest { 20 | @Test 21 | public void useAppContext() { 22 | // Context of the app under test. 23 | Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); 24 | assertEquals("com.abh80.smartedge", appContext.getPackageName()); 25 | } 26 | } -------------------------------------------------------------------------------- /app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/github/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 13 | 14 | 27 | 32 | 35 | 36 | 37 | 40 | 41 | 45 | 46 | 47 | 48 | 49 | 50 | 54 | 58 | 62 | 63 | 68 | 69 | 70 | 71 | 72 | 75 | 81 | 82 | 83 | 84 | 87 | 88 | 89 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /app/src/main/java/com/abh80/smartedge/App.java: -------------------------------------------------------------------------------- 1 | package com.abh80.smartedge; 2 | 3 | import android.app.Application; 4 | 5 | import com.google.android.material.color.DynamicColors; 6 | 7 | public class App extends Application { 8 | @Override 9 | public void onCreate() { 10 | super.onCreate(); 11 | DynamicColors.applyToActivitiesIfAvailable(this); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /app/src/main/java/com/abh80/smartedge/activities/AppearanceActivity.java: -------------------------------------------------------------------------------- 1 | package com.abh80.smartedge.activities; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.content.Context; 5 | import android.content.Intent; 6 | import android.content.SharedPreferences; 7 | import android.graphics.Color; 8 | import android.os.Bundle; 9 | import android.util.Log; 10 | import android.view.MenuItem; 11 | import android.view.inputmethod.InputMethodManager; 12 | 13 | import androidx.annotation.NonNull; 14 | import androidx.annotation.Nullable; 15 | import androidx.appcompat.app.AppCompatActivity; 16 | 17 | import com.abh80.smartedge.R; 18 | import com.google.android.material.snackbar.Snackbar; 19 | import com.google.android.material.textfield.TextInputLayout; 20 | 21 | import java.util.Objects; 22 | import java.util.regex.Matcher; 23 | import java.util.regex.Pattern; 24 | 25 | public class AppearanceActivity extends AppCompatActivity { 26 | private String intToHex(int i) { 27 | return Integer.toHexString(i); 28 | } 29 | 30 | @SuppressLint("RestrictedApi") 31 | @Override 32 | protected void onCreate(@Nullable Bundle savedInstanceState) { 33 | super.onCreate(savedInstanceState); 34 | setContentView(R.layout.appearence_layout); 35 | setSupportActionBar(findViewById(R.id.toolbar)); 36 | Objects.requireNonNull(getSupportActionBar()).setDefaultDisplayHomeAsUpEnabled(true); 37 | SharedPreferences sharedPreferences = getSharedPreferences(getPackageName(), MODE_PRIVATE); 38 | TextInputLayout t = findViewById(R.id.textField); 39 | Objects.requireNonNull(t.getEditText()).setText(intToHex(sharedPreferences.getInt("color", getColor(R.color.black)))); 40 | findViewById(R.id.apply_btn).setOnClickListener(l -> { 41 | String value = null; 42 | if (Objects.requireNonNull(t.getEditText()).getText() != null) 43 | value = "#" + t.getEditText().getText().toString(); 44 | if (value != null) { 45 | if (isValidColor(value)) { 46 | t.setError(null); 47 | t.setErrorEnabled(false); 48 | try { 49 | InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); 50 | imm.hideSoftInputFromWindow(findViewById(R.id.textField).getWindowToken(), 0); 51 | } catch (Exception ignored) { 52 | } 53 | try { 54 | int color = Color.parseColor(value); 55 | sharedPreferences.edit().putInt("color", color).apply(); 56 | Snackbar.make(this, findViewById(R.id.textField), "Successfully updated color", Snackbar.LENGTH_SHORT).show(); 57 | Intent intent = new Intent(getPackageName() + ".COLOR_CHANGED"); 58 | intent.putExtra("color", color); 59 | sendBroadcast(intent); 60 | } catch (Exception e) { 61 | t.setErrorEnabled(true); 62 | t.setError("Invalid hexadecimal value"); 63 | } 64 | } else { 65 | t.setErrorEnabled(true); 66 | t.setError("Please provide a valid hexadecimal value"); 67 | } 68 | 69 | } else { 70 | t.setErrorEnabled(true); 71 | t.setError("Please provide a hexadecimal value"); 72 | } 73 | }); 74 | } 75 | 76 | private boolean isValidColor(String value) { 77 | // Source : https://stackoverflow.com/a/23155867/14200419 78 | Pattern colorPattern = Pattern.compile("#([0-9a-f]{6}|[0-9a-f]{8})"); 79 | Matcher m = colorPattern.matcher(value); 80 | return m.matches(); 81 | } 82 | 83 | @Override 84 | public boolean onOptionsItemSelected(@NonNull MenuItem item) { 85 | if (item.getItemId() == android.R.id.home) finish(); 86 | return super.onOptionsItemSelected(item); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /app/src/main/java/com/abh80/smartedge/activities/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.abh80.smartedge.activities; 2 | 3 | import android.Manifest; 4 | import android.annotation.SuppressLint; 5 | import android.app.Notification; 6 | import android.app.NotificationChannel; 7 | import android.app.NotificationManager; 8 | import android.content.BroadcastReceiver; 9 | import android.content.ClipData; 10 | import android.content.ClipboardManager; 11 | import android.content.Context; 12 | import android.content.Intent; 13 | import android.content.IntentFilter; 14 | import android.content.SharedPreferences; 15 | import android.content.pm.PackageManager; 16 | import android.graphics.Canvas; 17 | import android.graphics.Paint; 18 | import android.graphics.Rect; 19 | import android.graphics.drawable.Drawable; 20 | import android.net.Uri; 21 | import android.os.Bundle; 22 | import android.provider.Settings; 23 | import android.util.TypedValue; 24 | import android.view.View; 25 | import android.widget.Toast; 26 | 27 | import androidx.annotation.NonNull; 28 | import androidx.annotation.Nullable; 29 | import androidx.appcompat.app.AppCompatActivity; 30 | import androidx.core.app.ActivityCompat; 31 | import androidx.core.app.NotificationCompat; 32 | import androidx.recyclerview.widget.LinearLayoutManager; 33 | import androidx.recyclerview.widget.RecyclerView; 34 | 35 | import com.abh80.smartedge.BuildConfig; 36 | import com.abh80.smartedge.R; 37 | import com.abh80.smartedge.plugins.ExportedPlugins; 38 | import com.abh80.smartedge.services.UpdaterService; 39 | import com.abh80.smartedge.utils.adapters.RecylerViewSettingsAdapter; 40 | import com.abh80.smartedge.utils.SettingStruct; 41 | import com.google.android.material.bottomsheet.BottomSheetDialog; 42 | import com.google.android.material.card.MaterialCardView; 43 | import com.google.android.material.color.MaterialColors; 44 | import com.google.android.material.dialog.MaterialAlertDialogBuilder; 45 | 46 | import java.util.ArrayList; 47 | import java.util.Arrays; 48 | 49 | public class MainActivity extends AppCompatActivity implements SharedPreferences.OnSharedPreferenceChangeListener { 50 | private SharedPreferences sharedPreferences; 51 | private final ArrayList settings = new ArrayList<>(); 52 | 53 | @Override 54 | protected void onCreate(@Nullable Bundle savedInstanceState) { 55 | Thread.setDefaultUncaughtExceptionHandler((thread, throwable) -> { 56 | if (sharedPreferences.getBoolean("clip_copy_enabled", true)) { 57 | ClipboardManager clipboard = (ClipboardManager) 58 | getSystemService(Context.CLIPBOARD_SERVICE); 59 | ClipData clip = ClipData.newPlainText("smart edge error log", throwable.getMessage() + " : " + Arrays.toString(throwable.getStackTrace())); 60 | clipboard.setPrimaryClip(clip); 61 | sendCrashNotification(); 62 | } 63 | Runtime.getRuntime().exit(0); 64 | }); 65 | init(); 66 | super.onCreate(savedInstanceState); 67 | setContentView(R.layout.activity_main); 68 | if ((Settings.Secure.getString(this.getContentResolver(), "enabled_notification_listeners") != null && !Settings.Secure.getString(this.getContentResolver(), "enabled_notification_listeners").contains(getApplicationContext().getPackageName()) 69 | ) || ActivityCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) { 70 | startActivity(new Intent(this, PermissionActivity.class)); 71 | } 72 | MaterialCardView enable_btn = findViewById(R.id.enable_switch); 73 | enable_btn.setOnClickListener(l -> { 74 | Intent intent = new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS); 75 | startActivity(intent); 76 | Toast.makeText(this, "Installed Apps -> Smart Edge", Toast.LENGTH_SHORT).show(); 77 | }); 78 | settings.add(new SettingStruct("Manage Overlay Layout", "App Settings", SettingStruct.TYPE_CUSTOM) { 79 | @Override 80 | public void onClick(Context c) { 81 | startActivity(new Intent(MainActivity.this, OverlayLayoutSettingActivity.class)); 82 | } 83 | }); 84 | settings.add(new SettingStruct("Overlay color", "App Settings", SettingStruct.TYPE_CUSTOM) { 85 | @Override 86 | public void onClick(Context ctx) { 87 | startActivity(new Intent(MainActivity.this, AppearanceActivity.class)); 88 | } 89 | }); 90 | if (BuildConfig.AUTO_UPDATE) 91 | settings.add(new SettingStruct("Enable auto update checking", "App Settings", SettingStruct.TYPE_TOGGLE) { 92 | @Override 93 | public boolean onAttach(Context ctx) { 94 | return sharedPreferences.getBoolean("update_enabled", true); 95 | } 96 | 97 | @Override 98 | public void onCheckChanged(boolean checked, Context ctx) { 99 | sharedPreferences.edit().putBoolean("update_enabled", checked).apply(); 100 | } 101 | }); 102 | settings.add(new SettingStruct("Invert long press and click functions", "App Settings", SettingStruct.TYPE_TOGGLE) { 103 | @Override 104 | public void onCheckChanged(boolean checked, Context ctx) { 105 | sharedPreferences.edit().putBoolean("invert_click", checked).apply(); 106 | } 107 | 108 | @Override 109 | public boolean onAttach(Context ctx) { 110 | return sharedPreferences.getBoolean("invert_click", false); 111 | } 112 | }); 113 | settings.add(new SettingStruct("Enable on lockscreen", "App Settings", SettingStruct.TYPE_TOGGLE) { 114 | @Override 115 | public boolean onAttach(Context ctx) { 116 | return sharedPreferences.getBoolean("enable_on_lockscreen", false); 117 | } 118 | 119 | @Override 120 | public void onCheckChanged(boolean checked, Context ctx) { 121 | sharedPreferences.edit().putBoolean("enable_on_lockscreen", checked).apply(); 122 | } 123 | }); 124 | settings.add(new SettingStruct("Copy crash logs to clipboard", "App Settings") { 125 | @Override 126 | public boolean onAttach(Context ctx) { 127 | return sharedPreferences.getBoolean("clip_copy_enabled", true); 128 | } 129 | 130 | @Override 131 | public void onCheckChanged(boolean checked, Context ctx) { 132 | sharedPreferences.edit().putBoolean("clip_copy_enabled", checked).apply(); 133 | } 134 | }); 135 | settings.add(null); 136 | ExportedPlugins.getPlugins().forEach(x -> { 137 | settings.add(new SettingStruct("Enable " + x.getName() + " Plugin", x.getName() + " Plugin Settings") { 138 | @Override 139 | public boolean onAttach(Context ctx) { 140 | return sharedPreferences.getBoolean(x.getID() + "_enabled", true); 141 | } 142 | 143 | @Override 144 | public void onCheckChanged(boolean checked, Context ctx) { 145 | sharedPreferences.edit().putBoolean(x.getID() + "_enabled", checked).apply(); 146 | } 147 | } 148 | ); 149 | if (x.getSettings() != null) { 150 | settings.addAll(x.getSettings()); 151 | } 152 | settings.add(null); 153 | }); 154 | RecylerViewSettingsAdapter adapter = new RecylerViewSettingsAdapter(this, settings); 155 | recyclerView = findViewById(R.id.recycler_view); 156 | recyclerView.setLayoutManager(new LinearLayoutManager(this)); 157 | recyclerView.setAdapter(adapter); 158 | recyclerView.addItemDecoration(new ItemDecoration()); 159 | recyclerView.invalidateItemDecorations(); 160 | if (sharedPreferences.getBoolean("update_enabled", true) && BuildConfig.AUTO_UPDATE) 161 | startService(new Intent(this, UpdaterService.class)); 162 | if (BuildConfig.AUTO_UPDATE) 163 | registerReceiver(broadcastReceiver, new IntentFilter(getPackageName() + ".UPDATE_AVAIL")); 164 | 165 | } 166 | 167 | private RecyclerView recyclerView; 168 | 169 | @Override 170 | protected void onResume() { 171 | super.onResume(); 172 | sharedPreferences.registerOnSharedPreferenceChangeListener(this); 173 | } 174 | 175 | @Override 176 | protected void onPause() { 177 | super.onPause(); 178 | sharedPreferences.unregisterOnSharedPreferenceChangeListener(this); 179 | 180 | } 181 | 182 | private void init() { 183 | if (sharedPreferences == null) { 184 | 185 | sharedPreferences = getSharedPreferences(getPackageName(), MODE_PRIVATE); 186 | } 187 | } 188 | 189 | @Override 190 | protected void onDestroy() { 191 | super.onDestroy(); 192 | sharedPreferences.unregisterOnSharedPreferenceChangeListener(this); 193 | try { 194 | unregisterReceiver(broadcastReceiver); 195 | } catch (Exception ignored) { 196 | } 197 | } 198 | 199 | @Override 200 | public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String s) { 201 | Intent intent = new Intent(getPackageName() + ".SETTINGS_CHANGED"); 202 | Bundle b = new Bundle(); 203 | sharedPreferences.getAll().forEach((key, value) -> { 204 | if (value instanceof Boolean) { 205 | b.putBoolean(key, (boolean) value); 206 | 207 | } 208 | }); 209 | intent.putExtra("settings", b); 210 | sendBroadcast(intent); 211 | } 212 | 213 | private final BroadcastReceiver broadcastReceiver = new BroadcastReceiver() { 214 | @Override 215 | public void onReceive(Context context, Intent intent) { 216 | if (intent.getAction().equals(getPackageName() + ".UPDATE_AVAIL")) { 217 | new MaterialAlertDialogBuilder(MainActivity.this).setTitle("New Update Available") 218 | .setMessage("We would like to update this app from " + BuildConfig.VERSION_NAME + " --> " + intent.getExtras().getString("version") + 219 | ".\n\nUpdating app generally means better and more stable experience.") 220 | .setCancelable(false) 221 | .setNegativeButton("Later", (dialogInterface, i) -> { 222 | dialogInterface.dismiss(); 223 | }) 224 | .setPositiveButton("Update Now", ((dialogInterface, i) -> { 225 | if (ActivityCompat.checkSelfPermission(MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { 226 | MainActivity.this.sendBroadcast(new Intent(getPackageName() + ".START_UPDATE")); 227 | Toast.makeText(MainActivity.this, "Updating in background! Please don't kill the app", Toast.LENGTH_SHORT).show(); 228 | } else 229 | ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE}, 102); 230 | dialogInterface.dismiss(); 231 | if (!getPackageManager().canRequestPackageInstalls()) { 232 | startActivityForResult(new Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES) 233 | .setData(Uri.parse(String.format("package:%s", getPackageName()))), 103); 234 | Toast.makeText(MainActivity.this, "Please provide install access to update the application.", Toast.LENGTH_SHORT).show(); 235 | } 236 | })) 237 | .show(); 238 | } 239 | } 240 | }; 241 | 242 | @Override 243 | public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { 244 | super.onRequestPermissionsResult(requestCode, permissions, grantResults); 245 | if (requestCode == 102 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { 246 | MainActivity.this.sendBroadcast(new Intent(getPackageName() + ".START_UPDATE")); 247 | Toast.makeText(MainActivity.this, "Updating in background! Please don't kill the app", Toast.LENGTH_SHORT).show(); 248 | } 249 | } 250 | 251 | private int dpToInt(int v) { 252 | return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, v, getResources().getDisplayMetrics()); 253 | } 254 | 255 | public class ItemDecoration extends RecyclerView.ItemDecoration { 256 | @SuppressLint("UseCompatLoadingForDrawables") 257 | @Override 258 | public void onDraw(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) { 259 | int color = MaterialColors.getColor(MainActivity.this, com.google.android.material.R.attr.colorOnSecondary, getColor(R.color.md_theme_dark_secondary)); 260 | super.onDraw(c, parent, state); 261 | int childCount = recyclerView.getChildCount(); 262 | int width = recyclerView.getWidth(); 263 | Rect cornerBounds = new Rect(); 264 | c.getClipBounds(cornerBounds); 265 | for (int i = 0; i < childCount; i++) { 266 | View childAt = recyclerView.getChildAt(i); 267 | RecylerViewSettingsAdapter.ViewHolder viewHolder = (RecylerViewSettingsAdapter.ViewHolder) recyclerView.getChildViewHolder(childAt); 268 | int vo = recyclerView.getChildAdapterPosition(childAt); 269 | if (!viewHolder.isItem) { 270 | if (settings.size() >= vo + 2 && settings.get(vo + 1) != null) { 271 | c.drawText(settings.get(vo + 1).category, cornerBounds.left + dpToInt(10), childAt.getBottom() - dpToInt(30), new Paint() { 272 | { 273 | setColor(MainActivity.this.getColor(R.color.quite_white)); 274 | setTextSize(dpToInt(16)); 275 | } 276 | }); 277 | } 278 | Drawable cornerBottom = getDrawable(R.drawable.rounded_corner_setting_bottom); 279 | Drawable cornerTop = getDrawable(R.drawable.rounded_corner_setting_top); 280 | if (recyclerView.getChildAt(i + 1) != null) { 281 | View v = recyclerView.getChildAt(i + 1); 282 | cornerTop.setBounds(cornerBounds.left, (int) v.getY() - dpToInt(20), cornerBounds.right, (int) v.getY()); 283 | cornerTop.draw(c); 284 | } 285 | if (recyclerView.getChildAt(i - 1) != null) { 286 | View v = recyclerView.getChildAt(i - 1); 287 | cornerBottom.setBounds(cornerBounds.left, v.getBottom(), cornerBounds.right, v.getBottom() + dpToInt(20)); 288 | cornerBottom.draw(c); 289 | } 290 | } else { 291 | Rect bounds = new Rect(cornerBounds.left, (int) childAt.getY(), cornerBounds.right, childAt.getBottom()); 292 | c.drawRect(bounds, new Paint() { 293 | { 294 | setColor(color); 295 | } 296 | }); 297 | } 298 | 299 | } 300 | if (recyclerView.getChildCount() < 1) return; 301 | if (((RecylerViewSettingsAdapter.ViewHolder) recyclerView.getChildViewHolder(recyclerView.getChildAt(0))).isItem) { 302 | Drawable roundedCornerTop = getDrawable(R.drawable.rounded_corner_setting_top); 303 | roundedCornerTop.setBounds(cornerBounds.left, cornerBounds.top, cornerBounds.right, (int) recyclerView.getChildAt(0).getY()); 304 | roundedCornerTop.draw(c); 305 | } 306 | if (((RecylerViewSettingsAdapter.ViewHolder) recyclerView.getChildViewHolder(recyclerView.getChildAt(recyclerView.getChildCount() - 1))).isItem) { 307 | Drawable roundedCornerBottom = getDrawable(R.drawable.rounded_corner_setting_bottom); 308 | roundedCornerBottom.setBounds(cornerBounds.left, 309 | recyclerView.getChildAt(recyclerView.getChildCount() - 1).getBottom(), 310 | cornerBounds.right, 311 | recyclerView.getChildAt(recyclerView.getChildCount() - 1).getBottom() + dpToInt(20)); 312 | roundedCornerBottom.draw(c); 313 | } 314 | } 315 | } 316 | 317 | private void sendCrashNotification() { 318 | NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); 319 | final String NOTIFICATION_CHANNEL_ID = getPackageName() + ".updater_channel"; 320 | String channelName = "Updater Service"; 321 | NotificationChannel chan = new NotificationChannel(NOTIFICATION_CHANNEL_ID, channelName, NotificationManager.IMPORTANCE_MIN); 322 | manager.createNotificationChannel(chan); 323 | 324 | NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID); 325 | Notification notification = notificationBuilder.setOngoing(false) 326 | .setContentTitle("Smart Edge Crashed") 327 | .setContentText("Crash Log copied to clipboard") 328 | .setSmallIcon(R.drawable.launcher_foreground) 329 | .setPriority(NotificationManager.IMPORTANCE_MAX) 330 | .setCategory(Notification.CATEGORY_ERROR) 331 | .build(); 332 | manager.notify(100, notification); 333 | } 334 | } 335 | -------------------------------------------------------------------------------- /app/src/main/java/com/abh80/smartedge/activities/NotificationManageActivity.java: -------------------------------------------------------------------------------- 1 | package com.abh80.smartedge.activities; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.content.Intent; 5 | import android.content.SharedPreferences; 6 | import android.content.pm.ApplicationInfo; 7 | import android.content.pm.PackageManager; 8 | import android.os.Bundle; 9 | import android.view.LayoutInflater; 10 | import android.view.Menu; 11 | import android.view.MenuItem; 12 | 13 | import androidx.annotation.NonNull; 14 | import androidx.annotation.Nullable; 15 | import androidx.appcompat.app.AppCompatActivity; 16 | import androidx.recyclerview.widget.LinearLayoutManager; 17 | import androidx.recyclerview.widget.RecyclerView; 18 | 19 | import com.abh80.smartedge.R; 20 | import com.abh80.smartedge.utils.NotificationAppMeta; 21 | import com.abh80.smartedge.utils.adapters.NotificationManageAppsAdapter; 22 | import com.google.android.material.appbar.MaterialToolbar; 23 | 24 | import org.apache.commons.lang3.StringUtils; 25 | 26 | import java.util.ArrayList; 27 | import java.util.List; 28 | 29 | public class NotificationManageActivity extends AppCompatActivity implements SharedPreferences.OnSharedPreferenceChangeListener { 30 | private NotificationManageAppsAdapter adapter; 31 | private SharedPreferences sharedPreferences; 32 | 33 | @Override 34 | public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String s) { 35 | Intent intent = new Intent(getPackageName() + ".NOTIFICATION_APPS_UPDATE"); 36 | intent.putExtra("apps", sharedPreferences.getString("notifications_apps", "")); 37 | sendBroadcast(intent); 38 | } 39 | 40 | @SuppressLint("RestrictedApi") 41 | @Override 42 | protected void onCreate(@Nullable Bundle savedInstanceState) { 43 | super.onCreate(savedInstanceState); 44 | setContentView(R.layout.manage_notification_layout); 45 | MaterialToolbar toolbar = findViewById(R.id.toolbar); 46 | setSupportActionBar(toolbar); 47 | sharedPreferences = getSharedPreferences(getPackageName(), MODE_PRIVATE); 48 | getSupportActionBar().setDefaultDisplayHomeAsUpEnabled(true); 49 | RecyclerView recyclerView = findViewById(R.id.recycler_view); 50 | recyclerView.setLayoutManager(new LinearLayoutManager(this)); 51 | ArrayList apps = new ArrayList<>(); 52 | ArrayList apps_enabled = NotificationManageAppsAdapter.parseEnabledApps(sharedPreferences.getString("notifications_apps", "")); 53 | // avoid blocking ui 54 | new Thread(() -> { 55 | List infos = getPackageManager().getInstalledApplications(PackageManager.GET_META_DATA); 56 | for (ApplicationInfo info : infos) { 57 | if (((info.flags & ApplicationInfo.FLAG_SYSTEM) == 0) || ((info.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0)) { 58 | NotificationAppMeta meta = new NotificationAppMeta(getPackageManager().getApplicationLabel(info).toString(), info.packageName, info.loadIcon(getPackageManager())); 59 | meta.enabled = apps_enabled.contains("all") || apps_enabled.contains(info.packageName); 60 | apps.add(meta); 61 | } 62 | } 63 | runOnUiThread(() -> { 64 | adapter = new NotificationManageAppsAdapter(sharedPreferences, apps); 65 | recyclerView.setAdapter(adapter); 66 | }); 67 | }).start(); 68 | sharedPreferences.registerOnSharedPreferenceChangeListener(this); 69 | } 70 | 71 | @Override 72 | protected void onDestroy() { 73 | super.onDestroy(); 74 | adapter = null; 75 | sharedPreferences.unregisterOnSharedPreferenceChangeListener(this); 76 | } 77 | 78 | @Override 79 | public boolean onPrepareOptionsMenu(@NonNull Menu menu) { 80 | getMenuInflater().inflate(R.menu.notifications_top_bar_menu, menu); 81 | return super.onPrepareOptionsMenu(menu); 82 | } 83 | 84 | @SuppressLint("NotifyDataSetChanged") 85 | @Override 86 | public boolean onOptionsItemSelected(@NonNull MenuItem item) { 87 | if (item.getItemId() == android.R.id.home) { 88 | finish(); 89 | return true; 90 | } else { 91 | if (adapter.apps != null) { 92 | if (adapter.apps.stream().anyMatch(x -> x.enabled)) { 93 | for (NotificationAppMeta app : adapter.apps) { 94 | app.enabled = false; 95 | } 96 | sharedPreferences.edit().putString("notifications_apps", "").apply(); 97 | } else { 98 | for (NotificationAppMeta app : adapter.apps) { 99 | app.enabled = true; 100 | } 101 | sharedPreferences.edit().putString("notifications_apps", StringUtils.join(adapter.apps.stream().map(x -> x.package_name).toArray(), ",")).apply(); 102 | } 103 | adapter.notifyDataSetChanged(); 104 | } 105 | } 106 | return super.onOptionsItemSelected(item); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /app/src/main/java/com/abh80/smartedge/activities/OverlayLayoutSettingActivity.java: -------------------------------------------------------------------------------- 1 | package com.abh80.smartedge.activities; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.content.Intent; 5 | import android.content.SharedPreferences; 6 | import android.content.res.Resources; 7 | import android.os.Bundle; 8 | import android.util.DisplayMetrics; 9 | import android.util.TypedValue; 10 | import android.view.MenuItem; 11 | import android.widget.TextView; 12 | 13 | import androidx.annotation.NonNull; 14 | import androidx.annotation.Nullable; 15 | import androidx.appcompat.app.AppCompatActivity; 16 | 17 | import com.abh80.smartedge.R; 18 | import com.google.android.material.imageview.ShapeableImageView; 19 | import com.google.android.material.slider.Slider; 20 | 21 | import java.math.RoundingMode; 22 | import java.text.DecimalFormat; 23 | 24 | public class OverlayLayoutSettingActivity extends AppCompatActivity { 25 | SharedPreferences sharedPreferences; 26 | Slider h; 27 | TextView val_h; 28 | ShapeableImageView add_h; 29 | ShapeableImageView sub_h; 30 | 31 | Slider gap; 32 | TextView val_gap; 33 | ShapeableImageView add_gap; 34 | ShapeableImageView sub_gap; 35 | 36 | Slider x; 37 | TextView val_x; 38 | ShapeableImageView add_x; 39 | ShapeableImageView sub_x; 40 | 41 | Slider w; 42 | ShapeableImageView add_w; 43 | ShapeableImageView sub_w; 44 | 45 | Slider y; 46 | TextView val_y; 47 | ShapeableImageView add_y; 48 | ShapeableImageView sub_y; 49 | 50 | @SuppressLint("RestrictedApi") 51 | @Override 52 | protected void onCreate(@Nullable Bundle savedInstanceState) { 53 | DisplayMetrics metrics = getResources().getDisplayMetrics(); 54 | super.onCreate(savedInstanceState); 55 | setContentView(R.layout.overlay_layout_setting_activity); 56 | setSupportActionBar(findViewById(R.id.toolbar)); 57 | assert getSupportActionBar() != null; 58 | getSupportActionBar().setDefaultDisplayHomeAsUpEnabled(true); 59 | sharedPreferences = getSharedPreferences(getPackageName(), MODE_PRIVATE); 60 | h = findViewById(R.id.seekbar_h); 61 | val_h = findViewById(R.id.val_h); 62 | add_h = findViewById(R.id.add_h); 63 | sub_h = findViewById(R.id.sub_h); 64 | 65 | gap = findViewById(R.id.seekbar_gap); 66 | val_gap = findViewById(R.id.val_gap); 67 | add_gap = findViewById(R.id.add_gap); 68 | sub_gap = findViewById(R.id.sub_gap); 69 | 70 | x = findViewById(R.id.seekbar_x); 71 | val_x = findViewById(R.id.val_x); 72 | add_x = findViewById(R.id.add_x); 73 | sub_x = findViewById(R.id.sub_x); 74 | 75 | w = findViewById(R.id.seekbar_w); 76 | add_w = findViewById(R.id.add_w); 77 | sub_w = findViewById(R.id.sub_w); 78 | val_w = findViewById(R.id.val_w); 79 | 80 | y = findViewById(R.id.seekbar_y); 81 | val_y = findViewById(R.id.val_y); 82 | 83 | add_w.setOnClickListener(l -> { 84 | int v = (int) w.getValue(); 85 | v += 1; 86 | if (v >= w.getValueTo()) v = (int) w.getValueTo(); 87 | if (v <= w.getValueFrom()) v = (int) w.getValueFrom(); 88 | w.setValue(v); 89 | onChange(); 90 | }); 91 | sub_w.setOnClickListener(l -> { 92 | int v = (int) w.getValue(); 93 | v -= 1; 94 | if (v >= w.getValueTo()) v = (int) w.getValueTo(); 95 | if (v <= w.getValueFrom()) v = (int) w.getValueFrom(); 96 | w.setValue(v); 97 | onChange(); 98 | }); 99 | add_h.setOnClickListener(l -> { 100 | int v = (int) h.getValue(); 101 | v += 1; 102 | if (v >= h.getValueTo()) v = (int) h.getValueTo(); 103 | if (v <= h.getValueFrom()) v = (int) h.getValueFrom(); 104 | h.setValue(v); 105 | onChange(); 106 | }); 107 | sub_h.setOnClickListener(l -> { 108 | int v = (int) h.getValue(); 109 | v -= 1; 110 | if (v >= h.getValueTo()) v = (int) h.getValueTo(); 111 | if (v <= h.getValueFrom()) v = (int) h.getValueFrom(); 112 | h.setValue(v); 113 | onChange(); 114 | }); 115 | add_gap.setOnClickListener(l -> { 116 | int v = (int) gap.getValue(); 117 | v += 1; 118 | if (v >= gap.getValueTo()) v = (int) gap.getValueTo(); 119 | if (v <= gap.getValueFrom()) v = (int) gap.getValueFrom(); 120 | gap.setValue(v); 121 | onChange(); 122 | }); 123 | sub_gap.setOnClickListener(l -> { 124 | int v = (int) gap.getValue(); 125 | v -= 1; 126 | if (v >= gap.getValueTo()) v = (int) gap.getValueTo(); 127 | if (v <= gap.getValueFrom()) v = (int) gap.getValueFrom(); 128 | gap.setValue(v); 129 | onChange(); 130 | }); 131 | add_y = findViewById(R.id.add_y); 132 | sub_y = findViewById(R.id.sub_y); 133 | 134 | add_y.setOnClickListener(l -> { 135 | float v = y.getValue(); 136 | v += 0.1; 137 | if (v >= y.getValueTo()) v = y.getValueTo(); 138 | if (v <= y.getValueFrom()) v = y.getValueFrom(); 139 | y.setValue(v); 140 | onChange(); 141 | }); 142 | sub_y.setOnClickListener(l -> { 143 | float v = y.getValue(); 144 | v -= 0.1; 145 | if (v >= y.getValueTo()) v = y.getValueTo(); 146 | if (v <= y.getValueFrom()) v = y.getValueFrom(); 147 | y.setValue(v); 148 | onChange(); 149 | }); 150 | add_x.setOnClickListener(l -> { 151 | float v = x.getValue(); 152 | v += 0.1; 153 | if (Math.abs(v) < 0.1) v = 0; 154 | if (v >= x.getValueTo()) v = x.getValueTo(); 155 | if (v <= x.getValueFrom()) v = x.getValueFrom(); 156 | x.setValue(v); 157 | onChange(); 158 | }); 159 | sub_x.setOnClickListener(l -> { 160 | float v = x.getValue(); 161 | v -= 0.1; 162 | if (v >= x.getValueTo()) v = x.getValueTo(); 163 | if (v <= x.getValueFrom()) v = x.getValueFrom(); 164 | x.setValue(v); 165 | onChange(); 166 | }); 167 | 168 | y.addOnChangeListener((slider, value, fromUser) -> { 169 | if (fromUser) onChange(); 170 | }); 171 | x.addOnChangeListener((slider, value, fromUser) -> { 172 | if (fromUser) onChange(); 173 | }); 174 | h.addOnChangeListener((slider, value, fromUser) -> { 175 | if (fromUser) onChange(); 176 | }); 177 | w.addOnChangeListener((slider, value, fromUser) -> { 178 | if (fromUser) onChange(); 179 | }); 180 | gap.addOnChangeListener((slider, value, fromUser) -> { 181 | if (fromUser) onChange(); 182 | }); 183 | 184 | gap.setValue(sharedPreferences.getFloat("overlay_gap", 50)); 185 | w.setValue(sharedPreferences.getFloat("overlay_w", 83)); 186 | h.setValue(sharedPreferences.getFloat("overlay_h", 40)); 187 | x.setValue(sharedPreferences.getFloat("overlay_x", 0)); 188 | y.setValue(sharedPreferences.getFloat("overlay_y", 0.67f)); 189 | updateTexts(); 190 | findViewById(R.id.reset_btn).setOnClickListener(l -> { 191 | gap.setValue(40); 192 | w.setValue(100); 193 | h.setValue(40); 194 | x.setValue(0); 195 | y.setValue(0.1f); 196 | onChange(); 197 | }); 198 | 199 | } 200 | 201 | TextView val_w; 202 | 203 | @SuppressLint("SetTextI18n") 204 | private void updateTexts() { 205 | DecimalFormat df = new DecimalFormat("##.##"); 206 | df.setRoundingMode(RoundingMode.DOWN); 207 | val_gap.setText(df.format(gap.getValue()) + " dp"); 208 | val_x.setText(df.format(x.getValue()) + " %"); 209 | val_y.setText(df.format(y.getValue()) + " %"); 210 | val_h.setText(df.format(h.getValue()) + " dp"); 211 | val_w.setText(df.format(w.getValue()) + " dp"); 212 | } 213 | 214 | private void onChange() { 215 | sharedPreferences.edit().putFloat("overlay_w", w.getValue()).apply(); 216 | sharedPreferences.edit().putFloat("overlay_h", h.getValue()).apply(); 217 | sharedPreferences.edit().putFloat("overlay_gap", gap.getValue()).apply(); 218 | sharedPreferences.edit().putFloat("overlay_x", x.getValue()).apply(); 219 | sharedPreferences.edit().putFloat("overlay_y", y.getValue()).apply(); 220 | updateTexts(); 221 | Intent intent = new Intent(getPackageName() + ".OVERLAY_LAYOUT_CHANGE"); 222 | Bundle b = new Bundle(); 223 | sharedPreferences.getAll().forEach((key, val) -> { 224 | if (val instanceof Float) { 225 | b.putFloat(key, (float) val); 226 | } 227 | }); 228 | intent.putExtra("settings", b); 229 | sendBroadcast(intent); 230 | } 231 | 232 | private float pxToDp(int x) { 233 | DisplayMetrics displayMetrics = getResources().getDisplayMetrics(); 234 | return x / (displayMetrics.xdpi / DisplayMetrics.DENSITY_DEFAULT); 235 | } 236 | 237 | @Override 238 | public boolean onOptionsItemSelected(@NonNull MenuItem item) { 239 | if (item.getItemId() == android.R.id.home) { 240 | finish(); 241 | return true; 242 | } 243 | return super.onOptionsItemSelected(item); 244 | } 245 | } 246 | -------------------------------------------------------------------------------- /app/src/main/java/com/abh80/smartedge/activities/PermissionActivity.java: -------------------------------------------------------------------------------- 1 | package com.abh80.smartedge.activities; 2 | 3 | import android.Manifest; 4 | import android.content.Intent; 5 | import android.content.pm.PackageManager; 6 | import android.net.Uri; 7 | import android.os.Bundle; 8 | import android.provider.Settings; 9 | import android.widget.CheckBox; 10 | import android.widget.Toast; 11 | 12 | import androidx.annotation.NonNull; 13 | import androidx.annotation.Nullable; 14 | import androidx.appcompat.app.AppCompatActivity; 15 | import androidx.core.app.ActivityCompat; 16 | 17 | import com.abh80.smartedge.R; 18 | 19 | public class PermissionActivity extends AppCompatActivity { 20 | @Override 21 | protected void onCreate(@Nullable Bundle savedInstanceState) { 22 | super.onCreate(savedInstanceState); 23 | setContentView(R.layout.activity_permission); 24 | findViewById(R.id.notification_access).setOnClickListener(l -> { 25 | if (Settings.Secure.getString(this.getContentResolver(), "enabled_notification_listeners") != null && Settings.Secure.getString(this.getContentResolver(), "enabled_notification_listeners").contains(getApplicationContext().getPackageName())) { 26 | Toast.makeText(this, "This permission is already enabled", Toast.LENGTH_LONG).show(); 27 | return; 28 | } 29 | startActivity(new Intent( 30 | "android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS")); 31 | Toast.makeText(this, "Please select Smart Edge", Toast.LENGTH_LONG).show(); 32 | }); 33 | findViewById(R.id.record_audio).setOnClickListener(l -> { 34 | if (ActivityCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED) { 35 | Toast.makeText(this, "This permission is already enabled", Toast.LENGTH_LONG).show(); 36 | return; 37 | } 38 | ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.RECORD_AUDIO}, 101); 39 | }); 40 | } 41 | 42 | @Override 43 | public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { 44 | super.onRequestPermissionsResult(requestCode, permissions, grantResults); 45 | onResume(); 46 | } 47 | 48 | @Override 49 | protected void onResume() { 50 | super.onResume(); 51 | int checks = 0; 52 | if (Settings.Secure.getString(this.getContentResolver(), "enabled_notification_listeners") != null && Settings.Secure.getString(this.getContentResolver(), "enabled_notification_listeners").contains(getApplicationContext().getPackageName())) { 53 | ((CheckBox) findViewById(R.id.notification_access_checkbox)).setChecked(true); 54 | checks++; 55 | } 56 | if (ActivityCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED) { 57 | ((CheckBox) findViewById(R.id.record_audio_checkbox)).setChecked(true); 58 | checks++; 59 | } 60 | if (checks >= 2) { 61 | finish(); 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /app/src/main/java/com/abh80/smartedge/plugins/BasePlugin.java: -------------------------------------------------------------------------------- 1 | package com.abh80.smartedge.plugins; 2 | 3 | import android.view.View; 4 | import android.view.accessibility.AccessibilityEvent; 5 | 6 | import com.abh80.smartedge.services.OverlayService; 7 | import com.abh80.smartedge.utils.SettingStruct; 8 | 9 | import java.util.ArrayList; 10 | 11 | public abstract class BasePlugin { 12 | public abstract String getID(); 13 | 14 | public abstract String getName(); 15 | 16 | public abstract void onCreate(OverlayService context); 17 | 18 | public abstract View onBind(); 19 | 20 | public abstract void onUnbind(); 21 | 22 | public abstract void onDestroy(); 23 | 24 | public abstract void onExpand(); 25 | 26 | public abstract void onCollapse(); 27 | 28 | public abstract void onClick(); 29 | 30 | public void onTextColorChange() { 31 | } 32 | 33 | public abstract String[] permissionsRequired(); 34 | 35 | public void onEvent(AccessibilityEvent event) { 36 | } 37 | 38 | public abstract ArrayList getSettings(); 39 | 40 | public void onBindComplete() { 41 | } 42 | 43 | public void onRightSwipe() { 44 | } 45 | 46 | public void onLeftSwipe() { 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /app/src/main/java/com/abh80/smartedge/plugins/BatteryPlugin/BatteryPlugin.java: -------------------------------------------------------------------------------- 1 | package com.abh80.smartedge.plugins.BatteryPlugin; 2 | 3 | import android.animation.Animator; 4 | import android.animation.AnimatorListenerAdapter; 5 | import android.animation.ValueAnimator; 6 | import android.content.BroadcastReceiver; 7 | import android.content.Context; 8 | import android.content.Intent; 9 | import android.content.IntentFilter; 10 | import android.graphics.Color; 11 | import android.os.BatteryManager; 12 | import android.view.LayoutInflater; 13 | import android.view.View; 14 | import android.view.ViewGroup; 15 | import android.widget.TextView; 16 | 17 | import com.abh80.smartedge.R; 18 | import com.abh80.smartedge.plugins.BasePlugin; 19 | import com.abh80.smartedge.services.OverlayService; 20 | import com.abh80.smartedge.utils.SettingStruct; 21 | import com.abh80.smartedge.views.BatteryImageView; 22 | 23 | import java.util.ArrayList; 24 | 25 | public class BatteryPlugin extends BasePlugin { 26 | @Override 27 | public String getID() { 28 | return "BatteryPlugin"; 29 | } 30 | 31 | @Override 32 | public String getName() { 33 | return "Battery Plugin"; 34 | } 35 | 36 | private OverlayService ctx; 37 | 38 | @Override 39 | public void onCreate(OverlayService context) { 40 | ctx = context; 41 | ctx.registerReceiver(mBroadcastReceiver, new IntentFilter(Intent.ACTION_BATTERY_CHANGED)); 42 | } 43 | 44 | private View mView; 45 | 46 | @Override 47 | public View onBind() { 48 | mView = LayoutInflater.from(ctx).inflate(R.layout.battery_layout, null); 49 | init(); 50 | return mView; 51 | } 52 | 53 | private void init() { 54 | tv = mView.findViewById(R.id.text_percent); 55 | batteryImageView = mView.findViewById(R.id.cover); 56 | updateView(); 57 | } 58 | 59 | float batteryPercent; 60 | private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { 61 | @Override 62 | public void onReceive(Context context, Intent intent) { 63 | int status = intent.getExtras().getInt(BatteryManager.EXTRA_STATUS); 64 | boolean isCharging = status == BatteryManager.BATTERY_STATUS_CHARGING; 65 | if (isCharging) { 66 | ctx.enqueue(BatteryPlugin.this); 67 | int level = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1); 68 | int scale = intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1); 69 | batteryPercent = level * 100 / (float) scale; 70 | updateView(); 71 | } else { 72 | ctx.dequeue(BatteryPlugin.this); 73 | if (tv != null && batteryImageView != null) { 74 | ValueAnimator valueAnimator = ValueAnimator.ofInt(0, ctx.dpToInt(0)); 75 | valueAnimator.setDuration(300); 76 | valueAnimator.addUpdateListener(valueAnimator1 -> { 77 | ViewGroup.LayoutParams p = batteryImageView.getLayoutParams(); 78 | p.width = (int) valueAnimator1.getAnimatedValue(); 79 | p.height = (int) valueAnimator1.getAnimatedValue(); 80 | batteryImageView.setLayoutParams(p); 81 | }); 82 | valueAnimator.addListener(new AnimatorListenerAdapter() { 83 | @Override 84 | public void onAnimationStart(Animator animation) { 85 | super.onAnimationEnd(animation); 86 | tv.setVisibility(View.INVISIBLE); 87 | } 88 | }); 89 | valueAnimator.start(); 90 | } 91 | } 92 | } 93 | }; 94 | private TextView tv; 95 | private BatteryImageView batteryImageView; 96 | 97 | private void updateView() { 98 | if (mView != null) { 99 | tv.setText((int) batteryPercent + "%"); 100 | batteryImageView.updateBatteryPercent(batteryPercent); 101 | if (batteryPercent > 80) { 102 | batteryImageView.setStrokeColor(Color.GREEN); 103 | tv.setTextColor(Color.GREEN); 104 | } else if (batteryPercent < 80 && batteryPercent > 20) { 105 | batteryImageView.setStrokeColor(Color.YELLOW); 106 | tv.setTextColor(Color.YELLOW); 107 | } else { 108 | batteryImageView.setStrokeColor(Color.RED); 109 | tv.setTextColor(Color.RED); 110 | } 111 | } 112 | } 113 | 114 | @Override 115 | public void onUnbind() { 116 | 117 | mView = null; 118 | } 119 | 120 | @Override 121 | public void onBindComplete() { 122 | if (mView == null) return; 123 | ValueAnimator valueAnimator = ValueAnimator.ofInt(0, ctx.dpToInt(ctx.minHeight / 4)); 124 | valueAnimator.setDuration(300); 125 | valueAnimator.addUpdateListener(valueAnimator1 -> { 126 | ViewGroup.LayoutParams p = batteryImageView.getLayoutParams(); 127 | p.width = (int) valueAnimator1.getAnimatedValue(); 128 | p.height = (int) valueAnimator1.getAnimatedValue(); 129 | batteryImageView.setLayoutParams(p); 130 | }); 131 | valueAnimator.addListener(new AnimatorListenerAdapter() { 132 | @Override 133 | public void onAnimationEnd(Animator animation) { 134 | super.onAnimationEnd(animation); 135 | tv.setVisibility(View.VISIBLE); 136 | batteryImageView.requestLayout(); 137 | batteryImageView.updateBatteryPercent(batteryPercent); 138 | } 139 | }); 140 | valueAnimator.start(); 141 | } 142 | 143 | @Override 144 | public void onDestroy() { 145 | ctx.unregisterReceiver(mBroadcastReceiver); 146 | } 147 | 148 | @Override 149 | public void onExpand() { 150 | 151 | } 152 | 153 | @Override 154 | public void onCollapse() { 155 | 156 | } 157 | 158 | @Override 159 | public void onClick() { 160 | 161 | } 162 | 163 | @Override 164 | public String[] permissionsRequired() { 165 | return null; 166 | } 167 | 168 | @Override 169 | public ArrayList getSettings() { 170 | return null; 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /app/src/main/java/com/abh80/smartedge/plugins/ExportedPlugins.java: -------------------------------------------------------------------------------- 1 | package com.abh80.smartedge.plugins; 2 | 3 | import com.abh80.smartedge.plugins.BatteryPlugin.BatteryPlugin; 4 | import com.abh80.smartedge.plugins.MediaSession.MediaSessionPlugin; 5 | import com.abh80.smartedge.plugins.Notification.NotificationPlugin; 6 | 7 | import java.util.ArrayList; 8 | 9 | public class ExportedPlugins { 10 | public static ArrayList getPlugins() { 11 | ArrayList plugins = new ArrayList<>(); 12 | plugins.add(new MediaSessionPlugin()); 13 | plugins.add(new NotificationPlugin()); 14 | plugins.add(new BatteryPlugin()); 15 | return plugins; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /app/src/main/java/com/abh80/smartedge/plugins/MediaSession/MediaCallback.java: -------------------------------------------------------------------------------- 1 | package com.abh80.smartedge.plugins.MediaSession; 2 | 3 | import android.graphics.Bitmap; 4 | import android.media.MediaMetadata; 5 | import android.media.session.MediaController; 6 | import android.media.session.PlaybackState; 7 | 8 | import androidx.annotation.Nullable; 9 | 10 | import com.abh80.smartedge.utils.CallBack; 11 | 12 | public class MediaCallback extends MediaController.Callback { 13 | public MediaCallback(MediaController mCurrent, MediaSessionPlugin context) { 14 | this.mCurrent = mCurrent; 15 | this.ctx = context; 16 | try { 17 | isPlaying = mCurrent.getPlaybackState().getState() == PlaybackState.STATE_PLAYING; 18 | mediaMetadata = mCurrent.getMetadata(); 19 | updateView(); 20 | } catch (Exception e) { 21 | // do nothing lol 22 | } 23 | } 24 | 25 | private final MediaSessionPlugin ctx; 26 | private final MediaController mCurrent; 27 | private MediaMetadata mediaMetadata; 28 | private boolean isPlaying = true; 29 | 30 | private void updateView() { 31 | if (!isPlaying) return; 32 | if (mCurrent.getMetadata() == null) return; 33 | Bitmap b = mediaMetadata.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART); 34 | if (b == null) { 35 | return; 36 | } 37 | String title = mediaMetadata.getText(MediaMetadata.METADATA_KEY_TITLE).toString(); 38 | String artist = mediaMetadata.getString(MediaMetadata.METADATA_KEY_ARTIST); 39 | ctx.queueUpdate(new UpdateQueueStruct(artist, title, b)); 40 | ctx.openOverlay(mCurrent.getPackageName()); 41 | ctx.mCurrent = mCurrent; 42 | ctx.onPlayerResume(false); 43 | } 44 | 45 | 46 | @Override 47 | public void onPlaybackStateChanged(@Nullable PlaybackState state) { 48 | super.onPlaybackStateChanged(state); 49 | try { 50 | if (state == null || mCurrent.getMetadata() == null) return; 51 | MediaMetadata targetMetada = mCurrent.getMetadata(); 52 | boolean isPlaying2 = state.getState() == PlaybackState.STATE_PLAYING; 53 | if (mediaMetadata != null && mediaMetadata.getString(MediaMetadata.METADATA_KEY_TITLE) != null 54 | && mediaMetadata.getString(MediaMetadata.METADATA_KEY_TITLE).equals(targetMetada.getString(MediaMetadata.METADATA_KEY_TITLE)) && ctx.overlayOpen()) { 55 | if (ctx.mCurrent != null && ctx.mCurrent.getPackageName().equals(mCurrent.getPackageName())) { 56 | if (!isPlaying2) ctx.onPlayerPaused(true); 57 | else ctx.onPlayerResume(true); 58 | } 59 | isPlaying = isPlaying2; 60 | return; 61 | } 62 | 63 | isPlaying = isPlaying2; 64 | if (ctx.mCurrent != null && ctx.mCurrent.getPackageName().equals(mCurrent.getPackageName())) { 65 | if (!isPlaying) ctx.onPlayerPaused(false); 66 | else ctx.onPlayerResume(false); 67 | } 68 | if (!isPlaying) return; 69 | mediaMetadata = targetMetada; 70 | ctx.mCurrent = mCurrent; 71 | if (ctx.expanded) { 72 | updateView(); 73 | return; 74 | } 75 | ctx.closeOverlay(new CallBack() { 76 | @Override 77 | public void onFinish() { 78 | super.onFinish(); 79 | updateView(); 80 | } 81 | }); 82 | } catch (Exception e) { 83 | e.printStackTrace(); 84 | ctx.closeOverlay(); 85 | } 86 | } 87 | 88 | @Override 89 | public void onSessionDestroyed() { 90 | super.onSessionDestroyed(); 91 | if (mCurrent != null) { 92 | mCurrent.unregisterCallback(this); 93 | ctx.callbackMap.remove(mCurrent.getPackageName()); 94 | ctx.mCurrent = null; 95 | } 96 | ctx.closeOverlay(); 97 | 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /app/src/main/java/com/abh80/smartedge/plugins/MediaSession/SongVisualizer.java: -------------------------------------------------------------------------------- 1 | package com.abh80.smartedge.plugins.MediaSession; 2 | 3 | import android.content.Context; 4 | import android.graphics.Canvas; 5 | import android.graphics.Color; 6 | import android.graphics.Paint; 7 | import android.media.audiofx.Visualizer; 8 | import android.util.AttributeSet; 9 | import android.view.View; 10 | 11 | import androidx.annotation.Nullable; 12 | 13 | public class SongVisualizer extends View { 14 | Visualizer visualizer; 15 | 16 | public SongVisualizer(Context context) { 17 | super(context); 18 | init(); 19 | } 20 | 21 | public SongVisualizer(Context context, @Nullable AttributeSet attrs) { 22 | super(context, attrs); 23 | init(); 24 | } 25 | 26 | public SongVisualizer(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { 27 | super(context, attrs, defStyleAttr); 28 | init(); 29 | } 30 | 31 | public boolean paused; 32 | private byte[] bytes; 33 | 34 | public void setPlayerId(int sessionID) { 35 | try { 36 | if (visualizer != null) { 37 | release(); 38 | visualizer = null; 39 | } 40 | visualizer = new Visualizer(sessionID); 41 | visualizer.setEnabled(false); 42 | visualizer.setScalingMode(Visualizer.SCALING_MODE_NORMALIZED); 43 | visualizer.setCaptureSize(Visualizer.getCaptureSizeRange()[1]); 44 | visualizer.setDataCaptureListener(new Visualizer.OnDataCaptureListener() { 45 | @Override 46 | public void onWaveFormDataCapture(Visualizer visualizer, byte[] bytes, 47 | int samplingRate) { 48 | SongVisualizer.this.bytes = bytes; 49 | if (!paused) invalidate(); 50 | } 51 | 52 | @Override 53 | public void onFftDataCapture(Visualizer visualizer, byte[] bytes, 54 | int samplingRate) { 55 | } 56 | }, Visualizer.getMaxCaptureRate() / 2, true, false); 57 | 58 | 59 | visualizer.setEnabled(true); 60 | } catch (Exception e) { 61 | // do nothing lol 62 | } 63 | } 64 | 65 | public void release() { 66 | //will be null if setPlayer hasn't yet been called 67 | if (visualizer == null) 68 | return; 69 | 70 | visualizer.release(); 71 | bytes = null; 72 | invalidate(); 73 | } 74 | 75 | private void init() { 76 | setColor(Color.BLUE); 77 | paint.setStyle(Paint.Style.FILL); 78 | paint.setStrokeCap(Paint.Cap.ROUND); 79 | } 80 | 81 | public void setColor(int Color) { 82 | paint.setColor(Color); 83 | } 84 | 85 | private final Paint paint = new Paint(); 86 | 87 | @Override 88 | protected void onDraw(Canvas canvas) { 89 | float density = 8; 90 | 91 | if (bytes != null) { 92 | float barWidth = getWidth() / density; 93 | float div = bytes.length / density; 94 | paint.setStrokeWidth(barWidth - 4); 95 | 96 | for (int i = 0; i < density; i++) { 97 | int bytePosition = (int) Math.ceil(i * div); 98 | float barX = (i * barWidth) + (barWidth / 2); 99 | if (bytes[bytePosition] == 0 || bytes[bytePosition] + 128 == 0) { 100 | canvas.drawLine(barX, (getHeight() / 2f), barX, (getHeight() / 2f), paint); 101 | } else { 102 | int top = (getHeight() - 20) + 103 | ((byte) (Math.abs(bytes[bytePosition]) + 128)) * (getHeight() - 20) / 128; 104 | canvas.drawLine(barX, ((getHeight() + 20) - top) / 2f, barX, top, paint); 105 | } 106 | } 107 | super.onDraw(canvas); 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /app/src/main/java/com/abh80/smartedge/plugins/MediaSession/UpdateQueueStruct.java: -------------------------------------------------------------------------------- 1 | package com.abh80.smartedge.plugins.MediaSession; 2 | 3 | import android.graphics.Bitmap; 4 | 5 | public class UpdateQueueStruct { 6 | private String artist; 7 | private String title; 8 | private Bitmap cover; 9 | 10 | public UpdateQueueStruct(String artist, String title, Bitmap cover) { 11 | this.artist = artist; 12 | this.title = title; 13 | this.cover = cover; 14 | } 15 | 16 | public String getArtist() { 17 | return artist; 18 | } 19 | 20 | public void setArtist(String artist) { 21 | this.artist = artist; 22 | } 23 | 24 | public String getTitle() { 25 | return title; 26 | } 27 | 28 | public void setTitle(String title) { 29 | this.title = title; 30 | } 31 | 32 | public Bitmap getCover() { 33 | return cover; 34 | } 35 | 36 | public void setCover(Bitmap cover) { 37 | this.cover = cover; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /app/src/main/java/com/abh80/smartedge/plugins/Notification/NotificationMeta.java: -------------------------------------------------------------------------------- 1 | package com.abh80.smartedge.plugins.Notification; 2 | 3 | import android.graphics.Bitmap; 4 | import android.graphics.drawable.Drawable; 5 | import android.graphics.drawable.Icon; 6 | import android.os.Bundle; 7 | 8 | public class NotificationMeta { 9 | private String title; 10 | private String description; 11 | private Icon icon; 12 | private Drawable icon_drawable; 13 | private Bundle all; 14 | 15 | public NotificationMeta(String title, String description, int id, Drawable icon , Bundle all) { 16 | this.title = title; 17 | this.description = description; 18 | this.icon_drawable = icon; 19 | this.id = id; 20 | this.all = all; 21 | } 22 | 23 | public Bundle getAll() { 24 | return all; 25 | } 26 | 27 | public void setAll(Bundle all) { 28 | this.all = all; 29 | } 30 | 31 | private int id; 32 | 33 | public int getId() { 34 | return id; 35 | } 36 | 37 | public void setId(int id) { 38 | this.id = id; 39 | } 40 | 41 | public Drawable getIcon_drawable() { 42 | return icon_drawable; 43 | } 44 | 45 | public void setIcon_drawable(Drawable icon_drawable) { 46 | this.icon_drawable = icon_drawable; 47 | } 48 | 49 | public String getTitle() { 50 | return title; 51 | } 52 | 53 | public void setTitle(String title) { 54 | this.title = title; 55 | } 56 | 57 | public String getDescription() { 58 | return description; 59 | } 60 | 61 | public void setDescription(String description) { 62 | this.description = description; 63 | } 64 | 65 | public Icon getIcon() { 66 | return icon; 67 | } 68 | 69 | public void setIcon(Icon icon) { 70 | this.icon = icon; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /app/src/main/java/com/abh80/smartedge/plugins/Notification/NotificationView.java: -------------------------------------------------------------------------------- 1 | package com.abh80.smartedge.plugins.Notification; 2 | 3 | import android.content.Context; 4 | import android.graphics.Canvas; 5 | import android.util.AttributeSet; 6 | import android.view.View; 7 | 8 | import androidx.annotation.Nullable; 9 | 10 | public class NotificationView extends View { 11 | public NotificationView(Context context) { 12 | super(context); 13 | } 14 | 15 | public NotificationView(Context context, @Nullable AttributeSet attrs) { 16 | super(context, attrs); 17 | } 18 | 19 | public NotificationView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { 20 | super(context, attrs, defStyleAttr); 21 | } 22 | 23 | public NotificationView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { 24 | super(context, attrs, defStyleAttr, defStyleRes); 25 | } 26 | 27 | @Override 28 | protected void onDraw(Canvas canvas) { 29 | 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/src/main/java/com/abh80/smartedge/services/NotiService.java: -------------------------------------------------------------------------------- 1 | package com.abh80.smartedge.services; 2 | 3 | import android.app.Notification; 4 | import android.app.NotificationChannel; 5 | import android.app.NotificationManager; 6 | import android.content.BroadcastReceiver; 7 | import android.content.Context; 8 | import android.content.Intent; 9 | import android.content.IntentFilter; 10 | import android.service.notification.NotificationListenerService; 11 | import android.service.notification.StatusBarNotification; 12 | import android.util.Log; 13 | 14 | import java.util.ArrayList; 15 | import java.util.Arrays; 16 | import java.util.List; 17 | import java.util.Optional; 18 | 19 | 20 | public class NotiService extends NotificationListenerService { 21 | 22 | @Override 23 | public void onCreate() { 24 | super.onCreate(); 25 | IntentFilter filter = new IntentFilter(getPackageName() + ".ACTION_OPEN_CLOSE"); 26 | filter.addAction(getPackageName() + ".ACTION_CLOSE"); 27 | registerReceiver(receiver, filter); 28 | } 29 | 30 | private final ArrayList notifications = new ArrayList<>(); 31 | 32 | @Override 33 | public void onDestroy() { 34 | super.onDestroy(); 35 | unregisterReceiver(receiver); 36 | } 37 | 38 | @Override 39 | public void onNotificationPosted(StatusBarNotification sbn) { 40 | super.onNotificationRemoved(sbn); 41 | Intent intent = new Intent(getPackageName() + ".NOTIFICATION_POSTED"); 42 | Notification notification = sbn.getNotification(); 43 | intent.putExtra("package_name", sbn.getPackageName()); 44 | intent.putExtra("id", sbn.getId()); 45 | intent.putExtra("time", sbn.getPostTime()); 46 | intent.putExtra("icon_large", sbn.getNotification().getLargeIcon()); 47 | intent.putExtra("icon_small", sbn.getNotification().getSmallIcon()); 48 | intent.putExtra("category", sbn.getNotification().category); 49 | try { 50 | intent.putExtra("title", notification.extras.getString("android.title")); 51 | intent.putExtra("body", notification.extras.getString("android.text")); 52 | } catch (Exception e) { 53 | //ignore 54 | } 55 | notifications.add(sbn); 56 | sendBroadcast(intent); 57 | } 58 | 59 | @Override 60 | public void onNotificationRemoved(StatusBarNotification sbn) { 61 | super.onNotificationPosted(sbn); 62 | Intent intent = new Intent(getPackageName() + ".NOTIFICATION_REMOVED"); 63 | intent.putExtra("id", sbn.getId()); 64 | notifications.remove(sbn); 65 | sendBroadcast(intent); 66 | } 67 | 68 | private final BroadcastReceiver receiver = new BroadcastReceiver() { 69 | @Override 70 | public void onReceive(Context context, Intent intent) { 71 | if (intent.getAction().equals(context.getPackageName() + ".ACTION_OPEN_CLOSE")) { 72 | Optional n = notifications.stream().filter(x -> x.getId() == intent.getExtras().getInt("id")).findFirst(); 73 | n.ifPresent(x -> { 74 | try { 75 | if (x.getNotification().deleteIntent != null) { 76 | x.getNotification().deleteIntent.send(); 77 | } else { 78 | cancelNotification(x.getKey()); 79 | } 80 | if (x.getNotification().contentIntent != null) { 81 | x.getNotification().contentIntent.send(); 82 | } 83 | } catch (Exception ignored) { 84 | } 85 | }); 86 | } 87 | if (intent.getAction().equals(context.getPackageName() + ".ACTION_CLOSE")) { 88 | Optional n = notifications.stream().filter(x -> x.getId() == intent.getExtras().getInt("id")).findFirst(); 89 | n.ifPresent(x -> { 90 | try { 91 | if (x.getNotification().deleteIntent != null) { 92 | x.getNotification().deleteIntent.send(); 93 | } else { 94 | cancelNotification(x.getKey()); 95 | } 96 | } catch (Exception ignored) { 97 | } 98 | }); 99 | } 100 | } 101 | }; 102 | } 103 | -------------------------------------------------------------------------------- /app/src/main/java/com/abh80/smartedge/services/UpdaterService.java: -------------------------------------------------------------------------------- 1 | package com.abh80.smartedge.services; 2 | 3 | import android.app.Notification; 4 | import android.app.NotificationChannel; 5 | import android.app.NotificationManager; 6 | import android.app.Service; 7 | import android.content.ActivityNotFoundException; 8 | import android.content.BroadcastReceiver; 9 | import android.content.Context; 10 | import android.content.Intent; 11 | import android.content.IntentFilter; 12 | import android.net.Uri; 13 | import android.os.AsyncTask; 14 | import android.os.Build; 15 | import android.os.Environment; 16 | import android.os.IBinder; 17 | import android.provider.Settings; 18 | import android.util.Log; 19 | import android.widget.Toast; 20 | 21 | import androidx.annotation.Nullable; 22 | import androidx.core.app.NotificationCompat; 23 | import androidx.core.content.FileProvider; 24 | 25 | import com.abh80.smartedge.BuildConfig; 26 | import com.abh80.smartedge.R; 27 | import com.android.volley.Request; 28 | import com.android.volley.RequestQueue; 29 | import com.android.volley.Response; 30 | import com.android.volley.VolleyError; 31 | import com.android.volley.toolbox.JsonArrayRequest; 32 | import com.android.volley.toolbox.JsonObjectRequest; 33 | import com.android.volley.toolbox.Volley; 34 | 35 | import org.json.JSONArray; 36 | import org.json.JSONException; 37 | import org.json.JSONObject; 38 | 39 | import java.io.BufferedInputStream; 40 | import java.io.File; 41 | import java.io.FileOutputStream; 42 | import java.io.InputStream; 43 | import java.io.OutputStream; 44 | import java.net.URL; 45 | import java.net.URLConnection; 46 | 47 | public class UpdaterService extends Service { 48 | 49 | @Nullable 50 | @Override 51 | public IBinder onBind(Intent intent) { 52 | return null; 53 | } 54 | 55 | private final BroadcastReceiver broadcastReceiver = new BroadcastReceiver() { 56 | @Override 57 | public void onReceive(Context context, Intent intent) { 58 | if (intent.getAction().equals(getPackageName() + ".START_UPDATE")) { 59 | if (download_url != null) { 60 | new DownloadFileFromURL().execute(download_url); 61 | } else { 62 | sendNotification("Cannot update app, please report the problem to developer.", NotificationManager.IMPORTANCE_HIGH, false); 63 | } 64 | } 65 | } 66 | }; 67 | private String download_url; 68 | 69 | @Override 70 | public void onCreate() { 71 | super.onCreate(); 72 | manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); 73 | RequestQueue queue = Volley.newRequestQueue(this); 74 | sendNotification("Checking for updates", NotificationManager.IMPORTANCE_MIN, true); 75 | registerReceiver(broadcastReceiver, new IntentFilter(getPackageName() + ".START_UPDATE")); 76 | int VERSION_CODE = BuildConfig.VERSION_CODE; 77 | String baseUrl = "https://api.github.com/"; 78 | JsonArrayRequest jsonArrayRequest = new JsonArrayRequest(Request.Method.GET, baseUrl + "repos/abh80/smart-edge/releases", 79 | null, response -> { 80 | if (response.length() > 0) { 81 | try { 82 | JSONObject object = (JSONObject) response.get(0); 83 | try { 84 | if (Integer.parseInt(object.getString("tag_name")) > VERSION_CODE) { 85 | JSONArray o = object.getJSONArray("assets"); 86 | download_url = ((JSONObject) o.get(0)).getString("browser_download_url"); 87 | Intent intent = new Intent(getPackageName() + ".UPDATE_AVAIL"); 88 | intent.putExtra("version", object.getString("name")); 89 | sendBroadcast(new Intent(intent)); 90 | sendNotification("Update available", NotificationManager.IMPORTANCE_MAX, false); 91 | } else { 92 | stopSelf(); 93 | } 94 | } catch (Exception e) { 95 | // do nothing lol 96 | stopSelf(); 97 | } 98 | 99 | } catch (JSONException e) { 100 | e.printStackTrace(); 101 | stopSelf(); 102 | } 103 | 104 | } else { 105 | stopSelf(); 106 | } 107 | }, error -> 108 | 109 | { 110 | }); 111 | queue.add(jsonArrayRequest); 112 | } 113 | 114 | @Override 115 | public void onDestroy() { 116 | super.onDestroy(); 117 | unregisterReceiver(broadcastReceiver); 118 | manager.cancel(1001); 119 | } 120 | 121 | NotificationManager manager; 122 | 123 | 124 | private void sendNotification(String text, int priority, boolean ongoing) { 125 | final String NOTIFICATION_CHANNEL_ID = getPackageName() + ".updater_channel"; 126 | String channelName = "Updater Service"; 127 | NotificationChannel chan = new NotificationChannel(NOTIFICATION_CHANNEL_ID, channelName, NotificationManager.IMPORTANCE_MIN); 128 | manager.createNotificationChannel(chan); 129 | 130 | NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID); 131 | Notification notification = notificationBuilder.setOngoing(ongoing) 132 | .setContentTitle("Smart Edge") 133 | .setContentText(text) 134 | .setSmallIcon(R.drawable.launcher_foreground) 135 | .setPriority(priority) 136 | .setCategory(Notification.CATEGORY_SERVICE) 137 | .build(); 138 | manager.notify(1001, notification); 139 | } 140 | class DownloadFileFromURL extends AsyncTask { 141 | 142 | /** 143 | * Before starting background thread Show Progress Bar Dialog 144 | */ 145 | @Override 146 | protected void onPreExecute() { 147 | super.onPreExecute(); 148 | sendNotification("Starting download", NotificationManager.IMPORTANCE_MIN, true); 149 | } 150 | 151 | /** 152 | * Downloading file in background thread 153 | */ 154 | @Override 155 | protected String doInBackground(String... f_url) { 156 | int count; 157 | try { 158 | URL url = new URL(f_url[0]); 159 | URLConnection connection = url.openConnection(); 160 | connection.connect(); 161 | 162 | // this will be useful so that you can show a tipical 0-100% 163 | // progress bar 164 | int lenghtOfFile = connection.getContentLength(); 165 | 166 | // download the file 167 | InputStream input = new BufferedInputStream(url.openStream(), 168 | 8192); 169 | 170 | // Output stream 171 | OutputStream output = new FileOutputStream(getExternalFilesDir(null).getAbsolutePath() + "/output.apk"); 172 | 173 | byte data[] = new byte[1024]; 174 | 175 | long total = 0; 176 | 177 | while ((count = input.read(data)) != -1) { 178 | total += count; 179 | // publishing the progress.... 180 | // After this onProgressUpdate will be called 181 | publishProgress("" + (int) ((total * 100) / lenghtOfFile)); 182 | 183 | // writing data to file 184 | output.write(data, 0, count); 185 | } 186 | 187 | // flushing output 188 | output.flush(); 189 | 190 | // closing streams 191 | output.close(); 192 | input.close(); 193 | 194 | } catch (Exception e) { 195 | Log.e("Error: ", e.getMessage()); 196 | } 197 | 198 | return null; 199 | } 200 | 201 | protected void onProgressUpdate(String... progress) { 202 | final String NOTIFICATION_CHANNEL_ID = getPackageName() + ".updater_channel"; 203 | NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(UpdaterService.this, NOTIFICATION_CHANNEL_ID); 204 | notificationBuilder.setOngoing(true) 205 | .setSmallIcon(R.drawable.launcher_foreground) 206 | .setPriority(NotificationManager.IMPORTANCE_MIN) 207 | .setCategory(Notification.CATEGORY_PROGRESS) 208 | .setContentTitle("Smart Edge") 209 | .setContentText("Downloading update") 210 | .setProgress(100, Integer.parseInt(String.valueOf(progress[0])), false); 211 | 212 | Notification notification = notificationBuilder.build(); 213 | manager.notify(1001, notification); 214 | } 215 | 216 | // Source for below codes : https://medium.com/@vishtech36/installing-apps-programmatically-in-android-10-7e39cfe22b86 217 | @Override 218 | protected void onPostExecute(String file_url) { 219 | String PATH = getExternalFilesDir(null).getAbsolutePath() + "/output.apk"; 220 | File file = new File(PATH); 221 | if (file.exists()) { 222 | Intent intent = new Intent(Intent.ACTION_VIEW); 223 | intent.setDataAndType(uriFromFile(getApplicationContext(), new File(PATH)), "application/vnd.android.package-archive"); 224 | intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 225 | intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); 226 | try { 227 | getApplicationContext().startActivity(intent); 228 | } catch (ActivityNotFoundException e) { 229 | e.printStackTrace(); 230 | Log.e("TAG", "Error in opening the file!"); 231 | } 232 | } else { 233 | Toast.makeText(getApplicationContext(), "installing", Toast.LENGTH_LONG).show(); 234 | } 235 | 236 | 237 | UpdaterService.this. 238 | 239 | stopSelf(); 240 | } 241 | 242 | Uri uriFromFile(Context context, File file) { 243 | return FileProvider.getUriForFile(context, BuildConfig.APPLICATION_ID + ".provider", file); 244 | 245 | } 246 | } 247 | } 248 | -------------------------------------------------------------------------------- /app/src/main/java/com/abh80/smartedge/utils/CallBack.java: -------------------------------------------------------------------------------- 1 | package com.abh80.smartedge.utils; 2 | 3 | public class CallBack { 4 | 5 | public void onFinish(){ 6 | // do something 7 | } 8 | public void onChange(float p){ 9 | 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /app/src/main/java/com/abh80/smartedge/utils/NotificationAppMeta.java: -------------------------------------------------------------------------------- 1 | package com.abh80.smartedge.utils; 2 | 3 | import android.graphics.drawable.Drawable; 4 | 5 | public class NotificationAppMeta { 6 | public String name; 7 | public String package_name; 8 | public Drawable app_icon; 9 | public boolean enabled; 10 | 11 | public NotificationAppMeta(String name, String package_name, Drawable app_icon) { 12 | this.name = name; 13 | this.package_name = package_name; 14 | this.app_icon = app_icon; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /app/src/main/java/com/abh80/smartedge/utils/NotificationHolderClass.java: -------------------------------------------------------------------------------- 1 | package com.abh80.smartedge.utils; 2 | 3 | import android.service.notification.StatusBarNotification; 4 | 5 | import java.io.Serializable; 6 | 7 | public class NotificationHolderClass implements Serializable { 8 | public StatusBarNotification notification; 9 | 10 | public NotificationHolderClass(StatusBarNotification notification) { 11 | this.notification = notification; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /app/src/main/java/com/abh80/smartedge/utils/SettingStruct.java: -------------------------------------------------------------------------------- 1 | package com.abh80.smartedge.utils; 2 | 3 | import android.content.Context; 4 | 5 | public class SettingStruct { 6 | public static int TYPE_TOGGLE = 1; 7 | public static int TYPE_CUSTOM = 2; 8 | 9 | public String text; 10 | public String category; 11 | public int type; 12 | 13 | public SettingStruct(String text, String cat) { 14 | this.text = text; 15 | this.category = cat; 16 | type = TYPE_TOGGLE; 17 | } 18 | 19 | public SettingStruct(String text, String category, int type) { 20 | this.text = text; 21 | this.category = category; 22 | this.type = type; 23 | } 24 | 25 | public void onCheckChanged(boolean checked , Context ctx) { 26 | } 27 | 28 | public void onClick(Context ctx) { 29 | } 30 | 31 | public boolean onAttach(Context ctx) { 32 | return false; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /app/src/main/java/com/abh80/smartedge/utils/adapters/NotificationManageAppsAdapter.java: -------------------------------------------------------------------------------- 1 | package com.abh80.smartedge.utils.adapters; 2 | 3 | import android.content.Context; 4 | import android.content.SharedPreferences; 5 | import android.util.Log; 6 | import android.view.LayoutInflater; 7 | import android.view.View; 8 | import android.view.ViewGroup; 9 | import android.widget.CompoundButton; 10 | import android.widget.TextView; 11 | 12 | import androidx.annotation.NonNull; 13 | import androidx.recyclerview.widget.RecyclerView; 14 | 15 | import com.abh80.smartedge.R; 16 | import com.abh80.smartedge.utils.NotificationAppMeta; 17 | import com.google.android.material.imageview.ShapeableImageView; 18 | import com.google.android.material.materialswitch.MaterialSwitch; 19 | 20 | import org.apache.commons.lang3.StringUtils; 21 | 22 | import java.util.ArrayList; 23 | import java.util.Arrays; 24 | import java.util.List; 25 | 26 | public class NotificationManageAppsAdapter extends RecyclerView.Adapter { 27 | public ArrayList apps; 28 | private SharedPreferences sharedPreferences; 29 | 30 | public NotificationManageAppsAdapter(SharedPreferences sharedPreferences, ArrayList apps) { 31 | this.apps = apps; 32 | this.sharedPreferences = sharedPreferences; 33 | } 34 | 35 | @NonNull 36 | @Override 37 | public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { 38 | return new ViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.single_notification_layout_app, parent, false)); 39 | } 40 | 41 | @Override 42 | public void onBindViewHolder(@NonNull ViewHolder holder, int position) { 43 | holder.imageView.setImageDrawable(apps.get(position).app_icon); 44 | holder.name.setText(apps.get(position).name); 45 | holder.switchBtn.setChecked(apps.get(position).enabled); 46 | holder.switchBtn.setOnClickListener(l -> { 47 | NotificationAppMeta app = apps.get(position); 48 | app.enabled = holder.switchBtn.isChecked(); 49 | holder.switchBtn.setChecked(holder.switchBtn.isChecked()); 50 | ArrayList p = parseEnabledApps(sharedPreferences.getString("notifications_apps", "")); 51 | if (app.enabled) { 52 | p.add(app.package_name); 53 | } else { 54 | p.remove(app.package_name); 55 | } 56 | sharedPreferences.edit().putString("notifications_apps", StringUtils.join(p, ",")).apply(); 57 | }); 58 | } 59 | 60 | public static ArrayList parseEnabledApps(String s) { 61 | return new ArrayList<>(Arrays.asList(s.split(","))); 62 | } 63 | 64 | @Override 65 | public int getItemCount() { 66 | return apps.size(); 67 | } 68 | 69 | public static class ViewHolder extends RecyclerView.ViewHolder { 70 | public TextView name; 71 | public MaterialSwitch switchBtn; 72 | public ShapeableImageView imageView; 73 | 74 | public ViewHolder(@NonNull View itemView) { 75 | super(itemView); 76 | name = itemView.findViewById(R.id.app_title); 77 | switchBtn = itemView.findViewById(R.id.app_switch); 78 | imageView = itemView.findViewById(R.id.app_image); 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /app/src/main/java/com/abh80/smartedge/utils/adapters/NotificationViewPagerAdapter.java: -------------------------------------------------------------------------------- 1 | package com.abh80.smartedge.utils.adapters; 2 | 3 | import android.content.Context; 4 | import android.view.LayoutInflater; 5 | import android.view.View; 6 | import android.view.ViewGroup; 7 | import android.widget.TextView; 8 | 9 | import androidx.annotation.NonNull; 10 | import androidx.viewpager.widget.PagerAdapter; 11 | import androidx.viewpager.widget.ViewPager; 12 | 13 | import com.abh80.smartedge.R; 14 | import com.abh80.smartedge.plugins.Notification.NotificationMeta; 15 | import com.abh80.smartedge.services.OverlayService; 16 | 17 | import org.ocpsoft.prettytime.PrettyTime; 18 | 19 | import java.util.ArrayList; 20 | import java.util.Date; 21 | 22 | public class NotificationViewPagerAdapter extends PagerAdapter { 23 | private ArrayList notifications; 24 | private OverlayService ctx; 25 | 26 | public NotificationViewPagerAdapter(ArrayList metas, OverlayService context) { 27 | ctx = context; 28 | notifications = metas; 29 | } 30 | 31 | public void updateNotifications(ArrayList metas) { 32 | notifications = metas; 33 | } 34 | 35 | @NonNull 36 | @Override 37 | public Object instantiateItem(@NonNull ViewGroup container, int position) { 38 | NotificationMeta meta = notifications.get(position); 39 | View mView = LayoutInflater.from(container.getContext()).inflate(R.layout.single_notification, null); 40 | ((TextView) mView.findViewById(R.id.title)).setText(meta.getTitle()); 41 | ((TextView) mView.findViewById(R.id.text_description)).setText(meta.getDescription()); 42 | StringBuilder stringBuilder = new StringBuilder(); 43 | String name = meta.getAll().getString("name"); 44 | if (name != null) stringBuilder.append(name).append(" • "); 45 | long since = meta.getAll().getLong("time"); 46 | PrettyTime p = new PrettyTime(); 47 | stringBuilder.append(p.format(new Date(since))); 48 | ((TextView) mView.findViewById(R.id.author)).setText(stringBuilder.toString()); 49 | int value = ctx.getAttr(androidx.appcompat.R.attr.colorPrimary); 50 | ((TextView) mView.findViewById(R.id.author)).setTextColor(value); 51 | mView.findViewById(R.id.title).setSelected(true); 52 | mView.findViewById(R.id.text_description).setSelected(true); 53 | ((TextView) mView.findViewById(R.id.title)).setTextColor(ctx.textColor); 54 | ((TextView) mView.findViewById(R.id.text_description)).setTextColor(ctx.textColor); 55 | container.addView(mView); 56 | mView.setTag("mv_" + position); 57 | return mView; 58 | } 59 | 60 | @Override 61 | public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) { 62 | container.removeView((View) object); 63 | } 64 | 65 | @Override 66 | public int getItemPosition(@NonNull Object object) { 67 | return POSITION_NONE; 68 | } 69 | 70 | @Override 71 | public int getCount() { 72 | return notifications.size(); 73 | } 74 | 75 | @Override 76 | public boolean isViewFromObject(@NonNull View view, @NonNull Object object) { 77 | return view == object; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /app/src/main/java/com/abh80/smartedge/utils/adapters/RecylerViewSettingsAdapter.java: -------------------------------------------------------------------------------- 1 | package com.abh80.smartedge.utils.adapters; 2 | 3 | import android.content.Context; 4 | import android.view.LayoutInflater; 5 | import android.view.View; 6 | import android.view.ViewGroup; 7 | import android.widget.TextView; 8 | 9 | import androidx.annotation.NonNull; 10 | import androidx.recyclerview.widget.RecyclerView; 11 | 12 | import com.abh80.smartedge.R; 13 | import com.abh80.smartedge.utils.SettingStruct; 14 | import com.google.android.material.materialswitch.MaterialSwitch; 15 | 16 | import java.util.ArrayList; 17 | 18 | public class RecylerViewSettingsAdapter extends RecyclerView.Adapter { 19 | private final Context context; 20 | private final ArrayList settings; 21 | 22 | public RecylerViewSettingsAdapter(Context context, ArrayList settings) { 23 | this.context = context; 24 | this.settings = settings; 25 | settings.add(0, null); 26 | } 27 | 28 | @NonNull 29 | @Override 30 | public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { 31 | View itemView; 32 | if (viewType == SettingStruct.TYPE_TOGGLE) { 33 | itemView = LayoutInflater.from(context).inflate(R.layout.toggle_setting_layout, parent, false); 34 | } else if (viewType == SettingStruct.TYPE_CUSTOM) { 35 | itemView = LayoutInflater.from(context).inflate(R.layout.setting_custom_layout, parent, false); 36 | } else { 37 | itemView = LayoutInflater.from(context).inflate(R.layout.toggle_setting_null_layout, parent, false); 38 | } 39 | return new ViewHolder(itemView, viewType); 40 | } 41 | 42 | @Override 43 | public int getItemViewType(final int position) { 44 | if (settings.get(position) == null) return 0; 45 | return settings.get(position).type; 46 | } 47 | 48 | @Override 49 | public void onBindViewHolder(@NonNull ViewHolder holder, int position) { 50 | if (!holder.isItem) return; 51 | holder.textView.setText(settings.get(position).text); 52 | if (holder.ViewType == SettingStruct.TYPE_CUSTOM) { 53 | holder.itemView.setOnClickListener(l -> settings.get(position).onClick(context)); 54 | } 55 | if (holder.ViewType == SettingStruct.TYPE_TOGGLE) { 56 | holder.switchBtn.setOnCheckedChangeListener((compoundButton, b) -> { 57 | settings.get(position).onCheckChanged(b, context); 58 | }); 59 | holder.switchBtn.setChecked(settings.get(position).onAttach(context)); 60 | } 61 | } 62 | 63 | @Override 64 | public int getItemCount() { 65 | return settings.size(); 66 | } 67 | 68 | public static class ViewHolder extends RecyclerView.ViewHolder { 69 | public TextView textView; 70 | public MaterialSwitch switchBtn; 71 | public boolean isItem; 72 | public View itemView; 73 | public int ViewType; 74 | 75 | public ViewHolder(@NonNull View itemView, int itemViewType) { 76 | super(itemView); 77 | this.itemView = itemView; 78 | ViewType = itemViewType; 79 | isItem = itemViewType != 0; 80 | if (!isItem) { 81 | textView = itemView.findViewById(R.id.cat_text); 82 | return; 83 | } 84 | textView = itemView.findViewById(R.id.enable_text); 85 | if (itemViewType == SettingStruct.TYPE_TOGGLE) { 86 | switchBtn = itemView.findViewById(R.id.enable_switch2); 87 | } 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /app/src/main/java/com/abh80/smartedge/views/BatteryImageView.java: -------------------------------------------------------------------------------- 1 | package com.abh80.smartedge.views; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.content.Context; 5 | import android.graphics.Canvas; 6 | import android.graphics.Color; 7 | import android.graphics.Paint; 8 | import android.graphics.Path; 9 | import android.graphics.RectF; 10 | import android.util.AttributeSet; 11 | import android.util.Log; 12 | import android.view.View; 13 | 14 | import androidx.annotation.Nullable; 15 | 16 | public class BatteryImageView extends View { 17 | public BatteryImageView(Context context) { 18 | super(context); 19 | init(); 20 | } 21 | 22 | private void init() { 23 | paint = new Paint() { 24 | { 25 | setAntiAlias(true); 26 | setColor(Color.GREEN); 27 | setStrokeWidth(8); 28 | setStyle(Style.STROKE); 29 | setStrokeJoin(Join.MITER); 30 | setDither(true); 31 | } 32 | }; 33 | } 34 | 35 | public BatteryImageView(Context context, @Nullable AttributeSet attrs) { 36 | super(context, attrs); 37 | init(); 38 | } 39 | 40 | public BatteryImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { 41 | super(context, attrs, defStyleAttr); 42 | init(); 43 | } 44 | 45 | Paint paint; 46 | private float batterPercent; 47 | 48 | public void updateBatteryPercent(float p) { 49 | batterPercent = p; 50 | invalidate(); 51 | } 52 | 53 | public void setStrokeColor(int color) { 54 | if (paint != null) paint.setColor(color); 55 | invalidate(); 56 | } 57 | 58 | @SuppressLint("DrawAllocation") 59 | @Override 60 | protected void onDraw(Canvas canvas) { 61 | super.onDraw(canvas); 62 | if (paint == null) { 63 | init(); 64 | } 65 | float margin = 5; 66 | RectF rectangle = new RectF(margin, margin, getWidth() - margin, getHeight() - margin); 67 | canvas.drawArc(rectangle, 270f, (batterPercent / 100f) * 360f, false, paint); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /app/src/main/java/com/abh80/smartedge/views/ViewPagerOnClickable.java: -------------------------------------------------------------------------------- 1 | package com.abh80.smartedge.views; 2 | 3 | import android.content.Context; 4 | import android.util.AttributeSet; 5 | import android.view.MotionEvent; 6 | 7 | import androidx.annotation.NonNull; 8 | import androidx.annotation.Nullable; 9 | import androidx.viewpager.widget.ViewPager; 10 | 11 | public class ViewPagerOnClickable extends ViewPager { 12 | public ViewPagerOnClickable(@NonNull Context context) { 13 | super(context); 14 | } 15 | 16 | public ViewPagerOnClickable(@NonNull Context context, @Nullable AttributeSet attrs) { 17 | super(context, attrs); 18 | } 19 | 20 | @Override 21 | public boolean onInterceptTouchEvent(MotionEvent ev) { 22 | onTouchEvent(ev); 23 | return false; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/avd_pause_to_play.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 11 | 15 | 16 | 17 | 18 | 19 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/avd_play_to_pause.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 11 | 15 | 16 | 17 | 18 | 19 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/fast_forward.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/fast_rewind.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abh80/smart-edge/0a4dcd81506662ecd52c91b3a419f888446e4baa/app/src/main/res/drawable-v24/launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/pause.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/play.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/custom_progress_bar_horizontal.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 14 | 15 | 16 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/default_dot.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_add_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_arrow_back_ios_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_fast_forward_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_fast_rewind_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_message_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_open_in_new_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_pause_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_play_arrow_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_remove_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_outline_refresh_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ripple.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/rounded_corner.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/rounded_corner_setting_bottom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/rounded_corner_setting_top.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/selected_dot.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/tab_selector.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 14 | 15 | 25 | 26 | 32 | 33 | 41 | 42 | 43 | 44 | 51 | 52 | 53 | 54 | 57 | 58 | 66 | 67 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_permission.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 18 | 19 | 29 | 30 | 31 | 41 | 42 | 43 | 47 | 48 | 54 | 55 | 59 | 60 | 68 | 77 | 78 | 79 | 80 | 81 | 82 | 93 | 94 | 99 | 100 | 106 | 107 | 111 | 112 | 120 | 129 | 130 | 131 | 132 | -------------------------------------------------------------------------------- /app/src/main/res/layout/appearence_layout.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 14 | 15 | 19 | 20 | 21 | 28 | 29 | 30 | 33 | 34 | 35 |