├── .gitignore ├── .idea ├── .gitignore ├── AndroidProjectSystem.xml ├── codeStyles │ ├── Project.xml │ └── codeStyleConfig.xml ├── compiler.xml ├── deploymentTargetSelector.xml ├── gradle.xml ├── kotlinc.xml ├── migrations.xml ├── misc.xml ├── runConfigurations.xml └── vcs.xml ├── .kotlin └── errors │ └── errors-1744377698457.log ├── README.md ├── app ├── .gitignore ├── build.gradle.kts ├── proguard-rules.pro ├── release │ └── output-metadata.json └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── tpk │ │ └── widgetspro │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── ic_launcher-playstore.png │ ├── java │ │ └── com │ │ │ └── tpk │ │ │ └── widgetspro │ │ │ ├── MainActivity.kt │ │ │ ├── PermissionActivity.kt │ │ │ ├── api │ │ │ ├── GoogleSearchApiService.kt │ │ │ └── ImageApiClient.kt │ │ │ ├── base │ │ │ ├── BaseDottedGraphView.kt │ │ │ └── BaseWidgetProvider.kt │ │ │ ├── models │ │ │ └── GoogleSearchResponse.kt │ │ │ ├── services │ │ │ ├── BaseMonitorService.kt │ │ │ ├── LauncherStateAccessibilityService.kt │ │ │ ├── analogclock │ │ │ │ ├── AnalogClockUpdateService_1.kt │ │ │ │ └── AnalogClockUpdateService_2.kt │ │ │ ├── battery │ │ │ │ └── BatteryMonitorService.kt │ │ │ ├── caffeine │ │ │ │ └── CaffeineService.kt │ │ │ ├── cpu │ │ │ │ └── CpuMonitorService.kt │ │ │ ├── gif │ │ │ │ └── AnimationService.kt │ │ │ ├── music │ │ │ │ └── MediaMonitorService.kt │ │ │ ├── networkusage │ │ │ │ ├── BaseNetworkSpeedWidgetService.kt │ │ │ │ ├── BaseSimDataUsageWidgetService.kt │ │ │ │ ├── BaseUsageWidgetUpdateService.kt │ │ │ │ └── BaseWifiDataUsageWidgetService.kt │ │ │ ├── notes │ │ │ │ └── NoteWidgetInputService.kt │ │ │ └── sun │ │ │ │ └── SunSyncService.kt │ │ │ ├── ui │ │ │ ├── addwidgets │ │ │ │ └── AddWidgetsFragment.kt │ │ │ ├── help │ │ │ │ └── HelpFragment.kt │ │ │ ├── permission │ │ │ │ ├── PermissionPage1Fragment.kt │ │ │ │ └── PermissionPage2Fragment.kt │ │ │ └── settings │ │ │ │ └── SettingsFragment.kt │ │ │ ├── utils │ │ │ ├── BitmapCacheManager.kt │ │ │ ├── BootReceiver.kt │ │ │ ├── CommonUtils.kt │ │ │ ├── CryptoUtils.kt │ │ │ ├── ImageLoader.kt │ │ │ ├── NetworkStatsHelper.kt │ │ │ ├── PermissionUtils.kt │ │ │ └── UpdateReceiver.kt │ │ │ └── widgets │ │ │ ├── analogclock │ │ │ ├── AnalogClockWidgetProvider_1.kt │ │ │ └── AnalogClockWidgetProvider_2.kt │ │ │ ├── battery │ │ │ ├── BatteryDottedView.kt │ │ │ ├── BatteryMonitor.kt │ │ │ └── BatteryWidgetProvider.kt │ │ │ ├── bluetooth │ │ │ ├── BluetoothReceiver.kt │ │ │ ├── BluetoothWidgetConfigActivity.kt │ │ │ └── BluetoothWidgetProvider.kt │ │ │ ├── caffeine │ │ │ ├── CaffeineToggleReceiver.kt │ │ │ └── CaffeineWidget.kt │ │ │ ├── cpu │ │ │ ├── CpuMonitor.kt │ │ │ ├── CpuWidgetProvider.kt │ │ │ └── DottedGraphView.kt │ │ │ ├── gif │ │ │ └── GifWidgetProvider.kt │ │ │ ├── music │ │ │ ├── MusicSimpleWidgetProvider.kt │ │ │ └── MusicVisualizerDrawer.kt │ │ │ ├── networkusage │ │ │ ├── BaseNetworkSpeedWidgetProvider.kt │ │ │ ├── BaseSimDataUsageWidgetProvider.kt │ │ │ ├── BaseWifiDataUsageWidgetProvider.kt │ │ │ ├── NetworkSpeedWidgetProviderCircle.kt │ │ │ ├── NetworkSpeedWidgetProviderPill.kt │ │ │ ├── SimDataUsageWidgetProviderCircle.kt │ │ │ ├── SimDataUsageWidgetProviderPill.kt │ │ │ ├── WifiDataUsageWidgetProviderCircle.kt │ │ │ └── WifiDataUsageWidgetProviderPill.kt │ │ │ ├── notes │ │ │ └── NoteWidgetProvider.kt │ │ │ └── sun │ │ │ └── SunTrackerWidget.kt │ └── res │ │ ├── drawable │ │ ├── analog_1_bg.png │ │ ├── analog_1_dial_dark.png │ │ ├── analog_1_dial_light.png │ │ ├── analog_1_hour.png │ │ ├── analog_1_min.png │ │ ├── analog_1_secs.png │ │ ├── analog_2_bg.png │ │ ├── analog_2_dial_dark.png │ │ ├── analog_2_dial_light.png │ │ ├── analog_2_hour.png │ │ ├── analog_2_min.png │ │ ├── analog_2_secs.png │ │ ├── arrow_down.xml │ │ ├── dialog_background.xml │ │ ├── gif_widget_bg.xml │ │ ├── ic_bluetooth_placeholder.xml │ │ ├── ic_coffee_active.xml │ │ ├── ic_coffee_inactive.xml │ │ ├── ic_default_album_art.xml │ │ ├── ic_help.xml │ │ ├── ic_home.xml │ │ ├── ic_launcher_background.xml │ │ ├── ic_launcher_foreground.xml │ │ ├── ic_media_next.xml │ │ ├── ic_media_previous.xml │ │ ├── ic_music_note.xml │ │ ├── ic_notification.xml │ │ ├── ic_pause.xml │ │ ├── ic_play_arrow.xml │ │ ├── ic_refresh.xml │ │ ├── ic_settings.xml │ │ ├── moon_orb.xml │ │ ├── network_speed_widget_icon.xml │ │ ├── preview_shape_circle.xml │ │ ├── preview_shape_pill.xml │ │ ├── round_layout.xml │ │ ├── round_layout_outline_bg.xml │ │ ├── rounded_layout_bg.xml │ │ ├── rounded_layout_bg_alt.xml │ │ ├── sim_data_usage_icon.xml │ │ ├── sun_orb.xml │ │ └── wifi_data_usage_icon.xml │ │ ├── font │ │ ├── my_custom_font.ttf │ │ └── ndot.otf │ │ ├── layout │ │ ├── activity_main.xml │ │ ├── activity_note_input.xml │ │ ├── activity_permission.xml │ │ ├── analog_1_widget.xml │ │ ├── analog_1_widget_preview_static.xml │ │ ├── analog_2_widget.xml │ │ ├── analog_2_widget_preview_static.xml │ │ ├── battery_widget_layout.xml │ │ ├── battery_widget_preview_static.xml │ │ ├── bluetooth_widget_layout.xml │ │ ├── bluetooth_widget_preview_static.xml │ │ ├── caffeine_widget_layout.xml │ │ ├── caffeine_widget_preview_static.xml │ │ ├── cpu_widget_layout.xml │ │ ├── cpu_widget_preview_static.xml │ │ ├── custom_simple_list_item.xml │ │ ├── fragment_add_widgets.xml │ │ ├── fragment_help.xml │ │ ├── fragment_permission_page1.xml │ │ ├── fragment_permission_page2.xml │ │ ├── fragment_settings.xml │ │ ├── gif_widget_layout.xml │ │ ├── gif_widget_preview_static.xml │ │ ├── item_music_app.xml │ │ ├── music_simple_widget.xml │ │ ├── music_widget_preview_static.xml │ │ ├── network_speed_widget_circle.xml │ │ ├── network_speed_widget_circle_preview_static.xml │ │ ├── network_speed_widget_pill.xml │ │ ├── network_speed_widget_pill_preview_static.xml │ │ ├── notes_widget_layout.xml │ │ ├── notes_widget_preview_static.xml │ │ ├── sim_data_usage_widget_circle.xml │ │ ├── sim_data_usage_widget_circle_preview_static.xml │ │ ├── sim_data_usage_widget_pill.xml │ │ ├── sim_data_usage_widget_pill_preview_static.xml │ │ ├── sun_tracker_widget_preview_static.xml │ │ ├── widget_sun_tracker.xml │ │ ├── wifi_data_usage_widget_circle.xml │ │ ├── wifi_data_usage_widget_circle_preview_static.xml │ │ ├── wifi_data_usage_widget_pill.xml │ │ └── wifi_data_usage_widget_pill_preview_static.xml │ │ ├── menu │ │ └── bottom_nav_menu.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── navigation │ │ └── nav_graph.xml │ │ ├── values-night │ │ ├── colors.xml │ │ └── themes.xml │ │ ├── values │ │ ├── attrs.xml │ │ ├── colors.xml │ │ ├── ic_launcher_background.xml │ │ ├── strings.xml │ │ └── themes.xml │ │ └── xml │ │ ├── accessibility_service_config.xml │ │ ├── analog_1_widget_info.xml │ │ ├── analog_2_widget_info.xml │ │ ├── backup_rules.xml │ │ ├── battery_widget_info.xml │ │ ├── bluetooth_widget_info.xml │ │ ├── caffeine_widget_info.xml │ │ ├── cpu_widget_info.xml │ │ ├── data_extraction_rules.xml │ │ ├── gif_widget_info.xml │ │ ├── music_simple_widget_info.xml │ │ ├── network_speed_widget_circle_info.xml │ │ ├── network_speed_widget_pill_info.xml │ │ ├── note_widget_info.xml │ │ ├── preferences.xml │ │ ├── sim_data_usage_widget_circle_info.xml │ │ ├── sim_data_usage_widget_pill_info.xml │ │ ├── sun_tracker_info.xml │ │ ├── wifi_data_usage_widget_circle_info.xml │ │ └── wifi_data_usage_widget_pill_info.xml │ └── test │ └── java │ └── com │ └── tpk │ └── widgetspro │ └── ExampleUnitTest.kt ├── build.gradle.kts ├── gradle.properties ├── gradle ├── libs.versions.toml └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle.kts /.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 | /app/release/app-release.aab 17 | /app/release/Widgets-Pro-release-v1.1.9.apk 18 | /app/release/baselineProfiles/0/Widgets-Pro-release-v1.1.9.dm 19 | /app/release/baselineProfiles/1/Widgets-Pro-release-v1.1.9.dm 20 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | -------------------------------------------------------------------------------- /.idea/AndroidProjectSystem.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 119 | 120 | 122 | 123 | -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/deploymentTargetSelector.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 18 | 19 | -------------------------------------------------------------------------------- /.idea/kotlinc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /.idea/migrations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 17 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.kotlin/errors/errors-1744377698457.log: -------------------------------------------------------------------------------- 1 | kotlin version: 2.0.20 2 | error message: The daemon has terminated unexpectedly on startup attempt #1 with error code: 0. The daemon process output: 3 | 1. Kotlin compile daemon is ready 4 | 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Inspired by a concept and newthings, I made this with Al's help to follow the same design language like nothing, because I daily drive it. I'm noob. 2 | Took drawable resources from various apps 3 | 4 | For support and discussion please use - https://t.me/widgets_pro 5 | 6 | Currently the app support Android 14 and above. Will soon lower the android requirements. 7 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | /release -------------------------------------------------------------------------------- /app/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import java.util.Properties 2 | import java.io.FileInputStream 3 | 4 | val localProperties = Properties() 5 | val localPropertiesFile = rootProject.file("local.properties") 6 | if (localPropertiesFile.exists()) { 7 | localProperties.load(FileInputStream(localPropertiesFile)) 8 | } 9 | 10 | val fernetKey: String = localProperties.getProperty("fernetKey") ?: "" 11 | val encryptedApiKey1: String = localProperties.getProperty("encryptedApiKey1") ?: "" 12 | val encryptedApiKey2: String = localProperties.getProperty("encryptedApiKey2") ?: "" 13 | 14 | 15 | plugins { 16 | alias(libs.plugins.android.application) 17 | alias(libs.plugins.kotlin.android) 18 | } 19 | 20 | android { 21 | namespace = "com.tpk.widgetspro" 22 | compileSdk = 35 23 | 24 | defaultConfig { 25 | applicationId = "com.tpk.widgetspro" 26 | minSdk = 31 27 | targetSdk = 35 28 | versionCode = 5 29 | versionName = "1.2.3" 30 | 31 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" 32 | 33 | buildConfigField("String", "API_KEY_1", "\"$fernetKey\"") 34 | buildConfigField("String", "API_KEY_2", "\"$encryptedApiKey1\"") 35 | buildConfigField("String", "API_KEY_3", "\"$encryptedApiKey2\"") 36 | } 37 | 38 | buildTypes { 39 | release { 40 | isMinifyEnabled = false 41 | isShrinkResources = false 42 | proguardFiles( 43 | getDefaultProguardFile("proguard-android-optimize.txt"), 44 | "proguard-rules.pro" 45 | ) 46 | } 47 | debug { 48 | isMinifyEnabled = false 49 | isShrinkResources = false 50 | } 51 | applicationVariants.configureEach { 52 | outputs.configureEach { 53 | (this as? com.android.build.gradle.internal.api.ApkVariantOutputImpl)?.outputFileName = 54 | "Widgets-Pro-${buildType.name}-v$versionName.apk" 55 | } 56 | } 57 | } 58 | compileOptions { 59 | sourceCompatibility = JavaVersion.VERSION_11 60 | targetCompatibility = JavaVersion.VERSION_11 61 | } 62 | kotlinOptions { 63 | jvmTarget = "11" 64 | } 65 | buildFeatures { 66 | viewBinding = true 67 | buildConfig = true 68 | } 69 | } 70 | 71 | dependencies { 72 | implementation(libs.androidx.core.ktx) 73 | implementation(libs.androidx.appcompat) 74 | implementation(libs.material) 75 | implementation(libs.androidx.work.runtime.ktx) 76 | implementation(libs.androidx.navigation.fragment.ktx) 77 | implementation(libs.androidx.navigation.ui.ktx) 78 | testImplementation(libs.junit) 79 | androidTestImplementation(libs.androidx.junit) 80 | androidTestImplementation(libs.androidx.espresso.core) 81 | implementation(libs.api) 82 | implementation(libs.provider) 83 | implementation(libs.retrofit) 84 | implementation(libs.converter.gson) 85 | implementation(libs.logging.interceptor) 86 | implementation(libs.gson) 87 | implementation (libs.androidx.security.crypto) 88 | implementation(libs.lazysodium.android) 89 | implementation(libs.jna) 90 | implementation(libs.kotlinx.coroutines.android) 91 | implementation(libs.picasso) 92 | implementation(libs.androidx.preference.ktx) 93 | implementation("pl.droidsonroids.gif:android-gif-drawable:1.2.29") 94 | implementation("androidx.media:media:1.7.0") 95 | } -------------------------------------------------------------------------------- /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/release/output-metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "artifactType": { 4 | "type": "APK", 5 | "kind": "Directory" 6 | }, 7 | "applicationId": "com.tpk.widgetspro", 8 | "variantName": "release", 9 | "elements": [ 10 | { 11 | "type": "SINGLE", 12 | "filters": [], 13 | "attributes": [], 14 | "versionCode": 1, 15 | "versionName": "1.2.1", 16 | "outputFile": "Widgets-Pro-release-v1.2.1.apk" 17 | } 18 | ], 19 | "elementType": "File", 20 | "baselineProfiles": [ 21 | { 22 | "minApi": 28, 23 | "maxApi": 30, 24 | "baselineProfiles": [ 25 | "baselineProfiles/1/Widgets-Pro-release-v1.2.1.dm" 26 | ] 27 | }, 28 | { 29 | "minApi": 31, 30 | "maxApi": 2147483647, 31 | "baselineProfiles": [ 32 | "baselineProfiles/0/Widgets-Pro-release-v1.2.1.dm" 33 | ] 34 | } 35 | ], 36 | "minSdkVersionForDexing": 31 37 | } -------------------------------------------------------------------------------- /app/src/androidTest/java/com/tpk/widgetspro/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.tpk.widgetspro 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("com.tpk.widgetspro", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /app/src/main/ic_launcher-playstore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/preethamkmr3/WidgetsPro/af0c99f2d44ec46c043f66c3418d73874099bd0b/app/src/main/ic_launcher-playstore.png -------------------------------------------------------------------------------- /app/src/main/java/com/tpk/widgetspro/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.tpk.widgetspro 2 | 3 | import android.appwidget.AppWidgetManager 4 | import android.content.ComponentName 5 | import android.content.Intent 6 | import android.content.pm.PackageManager 7 | import android.os.Bundle 8 | import android.widget.Toast 9 | import androidx.appcompat.app.AppCompatActivity 10 | import com.tpk.widgetspro.services.cpu.CpuMonitorService 11 | import com.tpk.widgetspro.utils.BitmapCacheManager 12 | import com.tpk.widgetspro.widgets.battery.BatteryWidgetProvider 13 | import com.tpk.widgetspro.widgets.bluetooth.BluetoothWidgetProvider 14 | import com.tpk.widgetspro.widgets.caffeine.CaffeineWidget 15 | import com.tpk.widgetspro.widgets.cpu.CpuWidgetProvider 16 | import com.tpk.widgetspro.widgets.networkusage.NetworkSpeedWidgetProviderCircle 17 | import com.tpk.widgetspro.widgets.networkusage.NetworkSpeedWidgetProviderPill 18 | import com.tpk.widgetspro.widgets.networkusage.SimDataUsageWidgetProviderCircle 19 | import com.tpk.widgetspro.widgets.networkusage.SimDataUsageWidgetProviderPill 20 | import com.tpk.widgetspro.widgets.networkusage.WifiDataUsageWidgetProviderCircle 21 | import com.tpk.widgetspro.widgets.networkusage.WifiDataUsageWidgetProviderPill 22 | import com.tpk.widgetspro.widgets.notes.NoteWidgetProvider 23 | import com.tpk.widgetspro.widgets.sun.SunTrackerWidget 24 | import com.tpk.widgetspro.widgets.analogclock.AnalogClockWidgetProvider_1 25 | import com.tpk.widgetspro.widgets.analogclock.AnalogClockWidgetProvider_2 26 | import androidx.navigation.findNavController 27 | import androidx.navigation.ui.setupWithNavController 28 | import com.google.android.material.bottomnavigation.BottomNavigationView 29 | import com.tpk.widgetspro.services.BaseMonitorService 30 | import com.tpk.widgetspro.widgets.music.MusicSimpleWidgetProvider 31 | 32 | class MainActivity : AppCompatActivity() { 33 | internal val SHIZUKU_REQUEST_CODE = 1001 34 | 35 | override fun onCreate(savedInstanceState: Bundle?) { 36 | applyTheme() 37 | super.onCreate(savedInstanceState) 38 | setContentView(R.layout.activity_main) 39 | val navView: BottomNavigationView = findViewById(R.id.nav_view) 40 | val navController = findNavController(R.id.nav_host_fragment_activity_main) 41 | 42 | navView.setupWithNavController(navController) 43 | BitmapCacheManager.clearExpiredCache(this) 44 | } 45 | 46 | private fun applyTheme() { 47 | val prefs = getSharedPreferences("theme_prefs", MODE_PRIVATE) 48 | val isDarkTheme = prefs.getBoolean("dark_theme", false) 49 | val isRedAccent = prefs.getBoolean("red_accent", false) 50 | setTheme( 51 | when { 52 | isDarkTheme && isRedAccent -> R.style.Theme_WidgetsPro_Red_Dark 53 | isDarkTheme -> R.style.Theme_WidgetsPro 54 | isRedAccent -> R.style.Theme_WidgetsPro_Red_Light 55 | else -> R.style.Theme_WidgetsPro 56 | } 57 | ) 58 | } 59 | 60 | override fun onRequestPermissionsResult( 61 | requestCode: Int, 62 | permissions: Array, 63 | grantResults: IntArray 64 | ) { 65 | super.onRequestPermissionsResult(requestCode, permissions, grantResults) 66 | when (requestCode) { 67 | SHIZUKU_REQUEST_CODE -> { 68 | if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) 69 | startServiceAndFinish(useRoot = false) 70 | else Toast.makeText(this, R.string.shizuku_toast_fail, Toast.LENGTH_SHORT).show() 71 | } 72 | } 73 | } 74 | 75 | fun startServiceAndFinish(useRoot: Boolean) { 76 | startForegroundService( 77 | Intent(this, CpuMonitorService::class.java).putExtra("use_root", useRoot) 78 | ) 79 | } 80 | 81 | fun switchTheme() { 82 | val prefs = getSharedPreferences("theme_prefs", MODE_PRIVATE) 83 | val isDarkTheme = prefs.getBoolean("dark_theme", false) 84 | val isRedAccent = prefs.getBoolean("red_accent", false) 85 | prefs.edit().apply { 86 | putBoolean("dark_theme", !isDarkTheme) 87 | putBoolean("red_accent", !isRedAccent) 88 | apply() 89 | } 90 | val appWidgetManager = AppWidgetManager.getInstance(this) 91 | val providers = arrayOf( 92 | CpuWidgetProvider::class.java, 93 | BatteryWidgetProvider::class.java, 94 | BluetoothWidgetProvider::class.java, 95 | CaffeineWidget::class.java, 96 | SunTrackerWidget::class.java, 97 | NetworkSpeedWidgetProviderCircle::class.java, 98 | NetworkSpeedWidgetProviderPill::class.java, 99 | WifiDataUsageWidgetProviderCircle::class.java, 100 | WifiDataUsageWidgetProviderPill::class.java, 101 | SimDataUsageWidgetProviderCircle::class.java, 102 | SimDataUsageWidgetProviderPill::class.java, 103 | NoteWidgetProvider::class.java, 104 | AnalogClockWidgetProvider_1::class.java, 105 | AnalogClockWidgetProvider_2::class.java, 106 | MusicSimpleWidgetProvider::class.java 107 | ) 108 | val intent = Intent(BaseMonitorService.ACTION_THEME_CHANGED) 109 | sendBroadcast(intent) 110 | providers.forEach { provider -> 111 | val componentName = ComponentName(this, provider) 112 | val appWidgetIds = appWidgetManager.getAppWidgetIds(componentName) 113 | if (appWidgetIds.isNotEmpty()) { 114 | val updateIntent = Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE).apply { 115 | putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds) 116 | component = componentName 117 | } 118 | sendBroadcast(updateIntent) 119 | } 120 | } 121 | recreate() 122 | } 123 | } -------------------------------------------------------------------------------- /app/src/main/java/com/tpk/widgetspro/PermissionActivity.kt: -------------------------------------------------------------------------------- 1 | package com.tpk.widgetspro 2 | 3 | import android.app.AppOpsManager 4 | import android.content.* 5 | import android.content.pm.PackageManager 6 | import android.os.Build 7 | import android.os.Bundle 8 | import android.os.PowerManager 9 | import android.provider.Settings 10 | import androidx.appcompat.app.AppCompatActivity 11 | import androidx.core.content.ContextCompat 12 | import androidx.fragment.app.Fragment 13 | import androidx.viewpager2.adapter.FragmentStateAdapter 14 | import androidx.viewpager2.widget.ViewPager2 15 | import com.google.android.material.tabs.TabLayout 16 | import com.google.android.material.tabs.TabLayoutMediator 17 | import com.tpk.widgetspro.ui.permission.PermissionPage1Fragment 18 | import com.tpk.widgetspro.ui.permission.PermissionPage2Fragment 19 | 20 | class PermissionActivity : AppCompatActivity() { 21 | private lateinit var viewPager: ViewPager2 22 | private lateinit var tabLayout: TabLayout 23 | private lateinit var prefs: SharedPreferences 24 | 25 | override fun onCreate(savedInstanceState: Bundle?) { 26 | super.onCreate(savedInstanceState) 27 | 28 | prefs = getSharedPreferences("app_prefs", MODE_PRIVATE) 29 | 30 | if (prefs.getBoolean("optional_permissions_skipped_via_next", false)) { 31 | val intent = Intent(this, MainActivity::class.java) 32 | startActivity(intent) 33 | finish() 34 | return 35 | } 36 | 37 | setContentView(R.layout.activity_permission) 38 | 39 | viewPager = findViewById(R.id.permission_viewpager) 40 | tabLayout = findViewById(R.id.permission_tab_layout) 41 | 42 | val pagerAdapter = PermissionPagerAdapter(this) 43 | viewPager.adapter = pagerAdapter 44 | 45 | TabLayoutMediator(tabLayout, viewPager) { tab, position -> 46 | tab.text = when (position) { 47 | 0 -> "Required" 48 | 1 -> "Optional" 49 | else -> null 50 | } 51 | }.attach() 52 | 53 | checkCurrentPermissionStateAndSetPage() 54 | } 55 | 56 | fun checkCurrentPermissionStateAndSetPage() { 57 | if (!areBasicPermissionsGrantedInPage1()) { 58 | viewPager.currentItem = 0 59 | } else { 60 | viewPager.currentItem = 1 61 | } 62 | } 63 | 64 | fun areBasicPermissionsGrantedInPage1(): Boolean { 65 | val powerManager = getSystemService(POWER_SERVICE) as PowerManager 66 | val isIgnoringBatteryOptimizations = powerManager.isIgnoringBatteryOptimizations(packageName) 67 | 68 | val hasNotificationPermission = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { 69 | ContextCompat.checkSelfPermission( 70 | this, 71 | android.Manifest.permission.POST_NOTIFICATIONS 72 | ) == PackageManager.PERMISSION_GRANTED 73 | } else { 74 | true 75 | } 76 | return isIgnoringBatteryOptimizations && hasNotificationPermission 77 | } 78 | 79 | fun areAllPermissionsGrantedInPage2(): Boolean { 80 | val appOps = getSystemService(APP_OPS_SERVICE) as AppOpsManager 81 | val mode = appOps.checkOpNoThrow( 82 | AppOpsManager.OPSTR_GET_USAGE_STATS, 83 | android.os.Process.myUid(), 84 | packageName 85 | ) 86 | val hasUsageAccessPermission = mode == AppOpsManager.MODE_ALLOWED 87 | 88 | val isAccessibilityEnabled = isAccessibilityServiceEnabled() 89 | 90 | if (hasUsageAccessPermission && isAccessibilityEnabled) { 91 | if (!prefs.getBoolean("optional_permissions_interacted", false)) { 92 | prefs.edit().putBoolean("optional_permissions_interacted", true).apply() 93 | } 94 | } 95 | return hasUsageAccessPermission && isAccessibilityEnabled 96 | } 97 | 98 | fun isAccessibilityServiceEnabled(): Boolean { 99 | val serviceName = ComponentName(this, com.tpk.widgetspro.services.LauncherStateAccessibilityService::class.java) 100 | val enabledServices = Settings.Secure.getString( 101 | contentResolver, 102 | Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES 103 | ) ?: return false 104 | return enabledServices.contains(serviceName.flattenToString()) 105 | } 106 | 107 | private inner class PermissionPagerAdapter(activity: AppCompatActivity) : FragmentStateAdapter(activity) { 108 | override fun getItemCount(): Int = 2 109 | 110 | override fun createFragment(position: Int): Fragment { 111 | return when (position) { 112 | 0 -> PermissionPage1Fragment() 113 | 1 -> PermissionPage2Fragment() 114 | else -> throw IllegalStateException("Invalid position $position") 115 | } 116 | } 117 | } 118 | } -------------------------------------------------------------------------------- /app/src/main/java/com/tpk/widgetspro/api/GoogleSearchApiService.kt: -------------------------------------------------------------------------------- 1 | package com.tpk.widgetspro.api 2 | 3 | import com.tpk.widgetspro.models.GoogleSearchResponse 4 | import retrofit2.Call 5 | import retrofit2.http.GET 6 | import retrofit2.http.Query 7 | 8 | interface GoogleSearchApiService { 9 | @GET("customsearch/v1") 10 | fun searchImages( 11 | @Query("key") apiKey: String, 12 | @Query("cx") searchEngineId: String, 13 | @Query("q") query: String, 14 | @Query("searchType") searchType: String, 15 | @Query("fileType") fileType: String? = null 16 | ): Call 17 | } -------------------------------------------------------------------------------- /app/src/main/java/com/tpk/widgetspro/base/BaseDottedGraphView.kt: -------------------------------------------------------------------------------- 1 | package com.tpk.widgetspro.base 2 | 3 | import android.content.Context 4 | import android.graphics.Canvas 5 | import android.graphics.Paint 6 | import android.view.View 7 | import androidx.core.content.ContextCompat 8 | import com.tpk.widgetspro.R 9 | import com.tpk.widgetspro.utils.CommonUtils 10 | 11 | abstract class BaseDottedGraphView(context: Context) : View(context) { 12 | protected data class Dot(val x: Float, val y: Float) 13 | protected val greyDots = mutableListOf() 14 | protected val dotPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply { 15 | color = CommonUtils.getAccentColor(context) 16 | strokeWidth = 1.5f 17 | strokeCap = Paint.Cap.ROUND 18 | style = Paint.Style.FILL 19 | } 20 | protected val greyDotPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply { 21 | color = ContextCompat.getColor(context, R.color.battery_grey) 22 | strokeWidth = 1.5f 23 | strokeCap = Paint.Cap.ROUND 24 | style = Paint.Style.FILL 25 | } 26 | protected val fillAreaTopOffset = 5f 27 | protected val fillAreaBottomOffset = 5f 28 | protected var dotRadius = 0f 29 | 30 | override fun onDraw(canvas: Canvas) { 31 | super.onDraw(canvas) 32 | greyDots.forEach { canvas.drawCircle(it.x, it.y, dotRadius, greyDotPaint) } 33 | } 34 | 35 | protected fun updateGreyDots(width: Int, height: Int, columns: Int, rows: Int, spacingHorizontal: Float = width.toFloat() / columns) { 36 | greyDots.clear() 37 | val barWidth = spacingHorizontal 38 | val availableHeight = height - fillAreaTopOffset - fillAreaBottomOffset 39 | val rowSpacing = availableHeight / (rows - 1) 40 | dotRadius = barWidth / 4 41 | 42 | for (col in 0 until columns) { 43 | for (row in 0 until rows) { 44 | val x = col * barWidth + barWidth / 2 45 | val y = fillAreaTopOffset + row * rowSpacing 46 | greyDots.add(Dot(x, y)) 47 | } 48 | } 49 | } 50 | } -------------------------------------------------------------------------------- /app/src/main/java/com/tpk/widgetspro/base/BaseWidgetProvider.kt: -------------------------------------------------------------------------------- 1 | package com.tpk.widgetspro.base 2 | 3 | import android.appwidget.AppWidgetManager 4 | import android.appwidget.AppWidgetProvider 5 | import android.content.ComponentName 6 | import android.content.Context 7 | import android.view.View 8 | import android.widget.RemoteViews 9 | import com.tpk.widgetspro.R 10 | import com.tpk.widgetspro.utils.CommonUtils 11 | 12 | abstract class BaseWidgetProvider : AppWidgetProvider() { 13 | protected abstract val layoutId: Int 14 | protected abstract val setupText: String 15 | protected abstract val setupDestination: Class<*> 16 | 17 | override fun onEnabled(context: Context) { 18 | super.onEnabled(context) 19 | if (!hasRequiredPermissions(context)) updateWidgetSetupRequired(context) 20 | } 21 | 22 | override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray) { 23 | if (!hasRequiredPermissions(context)) updateWidgetSetupRequired(context, appWidgetIds) 24 | else appWidgetIds.forEach { updateNormalWidgetView(context, appWidgetManager, it) } 25 | } 26 | 27 | protected open fun hasRequiredPermissions(context: Context): Boolean = true 28 | 29 | protected abstract fun updateNormalWidgetView(context: Context, appWidgetManager: AppWidgetManager, appWidgetId: Int) 30 | 31 | protected fun updateWidgetSetupRequired(context: Context, appWidgetIds: IntArray? = null) { 32 | val appWidgetManager = AppWidgetManager.getInstance(context) 33 | val componentName = ComponentName(context, this::class.java) 34 | val ids = appWidgetIds ?: appWidgetManager.getAppWidgetIds(componentName) 35 | val typeface = CommonUtils.getTypeface(context) 36 | val setupBitmap = CommonUtils.createTextBitmap(context, setupText, 20f, typeface) 37 | 38 | ids.forEach { appWidgetId -> 39 | val views = RemoteViews(context.packageName, layoutId).apply { 40 | setImageViewBitmap(R.id.setupView, setupBitmap) 41 | setViewVisibility(R.id.setupView, View.VISIBLE) 42 | setOnClickPendingIntent(R.id.setupView, CommonUtils.getPendingIntent(context, appWidgetId, setupDestination)) 43 | } 44 | appWidgetManager.updateAppWidget(appWidgetId, views) 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /app/src/main/java/com/tpk/widgetspro/models/GoogleSearchResponse.kt: -------------------------------------------------------------------------------- 1 | package com.tpk.widgetspro.models 2 | 3 | import com.google.gson.annotations.SerializedName 4 | 5 | data class GoogleSearchResponse( 6 | @SerializedName("items") val items: List? 7 | ) { 8 | data class GoogleSearchItem( 9 | @SerializedName("link") val link: String 10 | ) 11 | } -------------------------------------------------------------------------------- /app/src/main/java/com/tpk/widgetspro/services/LauncherStateAccessibilityService.kt: -------------------------------------------------------------------------------- 1 | package com.tpk.widgetspro.services 2 | 3 | import android.accessibilityservice.AccessibilityService 4 | import android.accessibilityservice.AccessibilityServiceInfo 5 | import android.content.Intent 6 | import android.view.accessibility.AccessibilityEvent 7 | import android.content.pm.PackageManager 8 | import androidx.localbroadcastmanager.content.LocalBroadcastManager 9 | 10 | class LauncherStateAccessibilityService : AccessibilityService() { 11 | companion object { 12 | const val ACTION_LAUNCHER_STATE_CHANGED = "com.tpk.widgetspro.LAUNCHER_STATE_CHANGED" 13 | const val EXTRA_IS_ACTIVE = "is_active" 14 | } 15 | 16 | private var defaultLauncherPackage: String? = null 17 | 18 | override fun onServiceConnected() { 19 | super.onServiceConnected() 20 | val info = AccessibilityServiceInfo().apply { 21 | eventTypes = AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED 22 | feedbackType = AccessibilityServiceInfo.FEEDBACK_GENERIC 23 | flags = AccessibilityServiceInfo.DEFAULT 24 | } 25 | this.serviceInfo = info 26 | 27 | 28 | val intent = Intent(Intent.ACTION_MAIN).apply { 29 | addCategory(Intent.CATEGORY_HOME) 30 | } 31 | defaultLauncherPackage = packageManager.resolveActivity( 32 | intent, 33 | PackageManager.MATCH_DEFAULT_ONLY 34 | )?.activityInfo?.packageName 35 | 36 | 37 | checkCurrentWindowState() 38 | } 39 | 40 | override fun onAccessibilityEvent(event: AccessibilityEvent?) { 41 | event?.let { 42 | if (it.eventType == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) { 43 | checkCurrentWindowState() 44 | } 45 | } 46 | } 47 | 48 | private fun checkCurrentWindowState() { 49 | val rootNode = rootInActiveWindow ?: return 50 | val currentPackage = rootNode.packageName?.toString() 51 | val isLauncherActive = currentPackage == defaultLauncherPackage 52 | sendLauncherStateUpdate(isLauncherActive) 53 | } 54 | 55 | private fun sendLauncherStateUpdate(isActive: Boolean) { 56 | LocalBroadcastManager.getInstance(this).sendBroadcast( 57 | Intent(ACTION_LAUNCHER_STATE_CHANGED).apply { 58 | putExtra(EXTRA_IS_ACTIVE, isActive) 59 | } 60 | ) 61 | } 62 | 63 | override fun onInterrupt() {} 64 | } -------------------------------------------------------------------------------- /app/src/main/java/com/tpk/widgetspro/services/caffeine/CaffeineService.kt: -------------------------------------------------------------------------------- 1 | package com.tpk.widgetspro.services.caffeine 2 | 3 | import android.content.Intent 4 | import android.os.IBinder 5 | import android.os.PowerManager 6 | import com.tpk.widgetspro.services.BaseMonitorService 7 | 8 | class CaffeineService : BaseMonitorService() { 9 | private var wakeLock: PowerManager.WakeLock? = null 10 | 11 | override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { 12 | wakeLock = (getSystemService(POWER_SERVICE) as PowerManager) 13 | .newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK or PowerManager.ON_AFTER_RELEASE, "Caffeine::WakeLock") 14 | .apply { acquire() } 15 | return super.onStartCommand(intent, flags, startId) 16 | } 17 | 18 | override fun onDestroy() { 19 | wakeLock?.takeIf { it.isHeld }?.release() 20 | wakeLock = null 21 | stopForeground(STOP_FOREGROUND_REMOVE) 22 | super.onDestroy() 23 | } 24 | 25 | override fun onBind(intent: Intent?): IBinder? = null 26 | } -------------------------------------------------------------------------------- /app/src/main/java/com/tpk/widgetspro/services/networkusage/BaseSimDataUsageWidgetService.kt: -------------------------------------------------------------------------------- 1 | package com.tpk.widgetspro.services.networkusage 2 | 3 | import android.appwidget.AppWidgetManager 4 | import android.content.ComponentName 5 | import android.content.Intent 6 | import com.tpk.widgetspro.widgets.networkusage.BaseSimDataUsageWidgetProvider 7 | import com.tpk.widgetspro.widgets.networkusage.SimDataUsageWidgetProviderCircle 8 | import com.tpk.widgetspro.widgets.networkusage.SimDataUsageWidgetProviderPill 9 | 10 | class BaseSimDataUsageWidgetService : BaseUsageWidgetUpdateService() { 11 | override val intervalKey = "sim_data_usage_interval" 12 | override val widgetProviderClass = BaseSimDataUsageWidgetProvider::class.java 13 | 14 | override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { 15 | 16 | val appWidgetManager = AppWidgetManager.getInstance(applicationContext) 17 | val circleWidgetIds = appWidgetManager.getAppWidgetIds(ComponentName(applicationContext, SimDataUsageWidgetProviderCircle::class.java)) 18 | val pillWidgetIds = appWidgetManager.getAppWidgetIds(ComponentName(applicationContext, SimDataUsageWidgetProviderPill::class.java)) 19 | if (circleWidgetIds.isEmpty() && pillWidgetIds.isEmpty()) { 20 | stopSelf() 21 | return START_NOT_STICKY 22 | } 23 | return super.onStartCommand(intent, flags, startId) 24 | } 25 | 26 | override fun updateWidgets() { 27 | val appWidgetManager = AppWidgetManager.getInstance(applicationContext) 28 | val circleWidgetIds = appWidgetManager.getAppWidgetIds(ComponentName(applicationContext, SimDataUsageWidgetProviderCircle::class.java)) 29 | val pillWidgetIds = appWidgetManager.getAppWidgetIds(ComponentName(applicationContext, SimDataUsageWidgetProviderPill::class.java)) 30 | 31 | if (circleWidgetIds.isEmpty() && pillWidgetIds.isEmpty()) { 32 | stopSelf() 33 | return 34 | } 35 | BaseSimDataUsageWidgetProvider.updateAllWidgets(applicationContext, SimDataUsageWidgetProviderCircle::class.java) 36 | BaseSimDataUsageWidgetProvider.updateAllWidgets(applicationContext, SimDataUsageWidgetProviderPill::class.java) 37 | } 38 | } -------------------------------------------------------------------------------- /app/src/main/java/com/tpk/widgetspro/services/networkusage/BaseUsageWidgetUpdateService.kt: -------------------------------------------------------------------------------- 1 | package com.tpk.widgetspro.services.networkusage 2 | 3 | import android.content.BroadcastReceiver 4 | import android.content.Context 5 | import android.content.Intent 6 | import android.content.IntentFilter 7 | import android.content.SharedPreferences 8 | import androidx.localbroadcastmanager.content.LocalBroadcastManager 9 | import com.tpk.widgetspro.services.BaseMonitorService 10 | import kotlinx.coroutines.* 11 | import java.util.concurrent.TimeUnit 12 | 13 | abstract class BaseUsageWidgetUpdateService : BaseMonitorService(), CoroutineScope { 14 | 15 | private val supervisorJob = SupervisorJob() 16 | override val coroutineContext = Dispatchers.Main + supervisorJob 17 | 18 | private var updateJob: Job? = null 19 | private lateinit var prefs: SharedPreferences 20 | protected abstract val intervalKey: String 21 | protected abstract val widgetProviderClass: Class<*> 22 | 23 | private val idleIntervalMs = CHECK_INTERVAL_INACTIVE_MS 24 | private var userIntervalMs = TimeUnit.MINUTES.toMillis(1) 25 | 26 | private val prefListener = SharedPreferences.OnSharedPreferenceChangeListener { _, key -> 27 | if (key == intervalKey) { 28 | updateIntervals() 29 | restartMonitoring() 30 | } 31 | } 32 | 33 | private val visibilityResumedReceiver = object : BroadcastReceiver() { 34 | override fun onReceive(context: Context, intent: Intent) { 35 | if (intent.action == ACTION_VISIBILITY_RESUMED) { 36 | restartMonitoring() 37 | } 38 | } 39 | } 40 | 41 | override fun onCreate() { 42 | super.onCreate() 43 | prefs = getSharedPreferences("widget_prefs", Context.MODE_PRIVATE) 44 | prefs.registerOnSharedPreferenceChangeListener(prefListener) 45 | LocalBroadcastManager.getInstance(this).registerReceiver( 46 | visibilityResumedReceiver, 47 | IntentFilter(ACTION_VISIBILITY_RESUMED) 48 | ) 49 | updateIntervals() 50 | restartMonitoring() 51 | } 52 | 53 | override fun onDestroy() { 54 | updateJob?.cancel() 55 | supervisorJob.cancel() 56 | prefs.unregisterOnSharedPreferenceChangeListener(prefListener) 57 | LocalBroadcastManager.getInstance(this).unregisterReceiver(visibilityResumedReceiver) 58 | super.onDestroy() 59 | } 60 | 61 | private fun updateIntervals() { 62 | val intervalMinutes = prefs.getInt(intervalKey, 60).coerceAtLeast(1) 63 | userIntervalMs = TimeUnit.MINUTES.toMillis(intervalMinutes.toLong()) 64 | } 65 | 66 | private fun restartMonitoring() { 67 | updateJob?.cancel() 68 | updateJob = launch { 69 | while (isActive) { 70 | if (shouldUpdate()) { 71 | updateWidgets() 72 | delay(userIntervalMs) 73 | } else { 74 | delay(idleIntervalMs) 75 | } 76 | } 77 | } 78 | } 79 | 80 | protected abstract fun updateWidgets() 81 | } -------------------------------------------------------------------------------- /app/src/main/java/com/tpk/widgetspro/services/networkusage/BaseWifiDataUsageWidgetService.kt: -------------------------------------------------------------------------------- 1 | package com.tpk.widgetspro.services.networkusage 2 | 3 | import android.appwidget.AppWidgetManager 4 | import android.content.ComponentName 5 | import android.content.Intent 6 | import com.tpk.widgetspro.widgets.networkusage.BaseWifiDataUsageWidgetProvider 7 | import com.tpk.widgetspro.widgets.networkusage.WifiDataUsageWidgetProviderCircle 8 | import com.tpk.widgetspro.widgets.networkusage.WifiDataUsageWidgetProviderPill 9 | 10 | class BaseWifiDataUsageWidgetService : BaseUsageWidgetUpdateService() { 11 | override val intervalKey = "wifi_data_usage_interval" 12 | override val widgetProviderClass = BaseWifiDataUsageWidgetProvider::class.java 13 | 14 | override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { 15 | 16 | val appWidgetManager = AppWidgetManager.getInstance(applicationContext) 17 | val circleWidgetIds = appWidgetManager.getAppWidgetIds(ComponentName(applicationContext, WifiDataUsageWidgetProviderCircle::class.java)) 18 | val pillWidgetIds = appWidgetManager.getAppWidgetIds(ComponentName(applicationContext, WifiDataUsageWidgetProviderPill::class.java)) 19 | if (circleWidgetIds.isEmpty() && pillWidgetIds.isEmpty()) { 20 | stopSelf() 21 | return START_NOT_STICKY 22 | } 23 | return super.onStartCommand(intent, flags, startId) 24 | } 25 | 26 | override fun updateWidgets() { 27 | val appWidgetManager = AppWidgetManager.getInstance(applicationContext) 28 | val circleWidgetIds = appWidgetManager.getAppWidgetIds(ComponentName(applicationContext, WifiDataUsageWidgetProviderCircle::class.java)) 29 | val pillWidgetIds = appWidgetManager.getAppWidgetIds(ComponentName(applicationContext, WifiDataUsageWidgetProviderPill::class.java)) 30 | 31 | if (circleWidgetIds.isEmpty() && pillWidgetIds.isEmpty()) { 32 | stopSelf() 33 | return 34 | } 35 | BaseWifiDataUsageWidgetProvider.updateAllWidgets(applicationContext, WifiDataUsageWidgetProviderCircle::class.java) 36 | BaseWifiDataUsageWidgetProvider.updateAllWidgets(applicationContext, WifiDataUsageWidgetProviderPill::class.java) 37 | } 38 | } -------------------------------------------------------------------------------- /app/src/main/java/com/tpk/widgetspro/ui/help/HelpFragment.kt: -------------------------------------------------------------------------------- 1 | package com.tpk.widgetspro.ui.help 2 | 3 | import android.os.Bundle 4 | import androidx.fragment.app.Fragment 5 | import android.view.LayoutInflater 6 | import android.view.View 7 | import android.view.ViewGroup 8 | import com.tpk.widgetspro.R 9 | 10 | class HelpFragment : Fragment() { 11 | 12 | override fun onCreateView( 13 | inflater: LayoutInflater, container: ViewGroup?, 14 | savedInstanceState: Bundle? 15 | ): View? { 16 | return inflater.inflate(R.layout.fragment_help, container, false) 17 | } 18 | } -------------------------------------------------------------------------------- /app/src/main/java/com/tpk/widgetspro/ui/permission/PermissionPage1Fragment.kt: -------------------------------------------------------------------------------- 1 | package com.tpk.widgetspro.ui.permission 2 | 3 | import android.content.Context 4 | import android.content.Intent 5 | import android.content.pm.PackageManager 6 | import android.net.Uri 7 | import android.os.Build 8 | import android.os.Bundle 9 | import android.os.PowerManager 10 | import android.provider.Settings 11 | import android.view.LayoutInflater 12 | import android.view.View 13 | import android.view.ViewGroup 14 | import android.widget.Button 15 | import androidx.activity.result.contract.ActivityResultContracts 16 | import androidx.core.app.ActivityCompat 17 | import androidx.core.content.ContextCompat 18 | import androidx.fragment.app.Fragment 19 | import com.tpk.widgetspro.PermissionActivity 20 | import com.tpk.widgetspro.R 21 | 22 | class PermissionPage1Fragment : Fragment() { 23 | 24 | private lateinit var btnBatteryOptimizations: Button 25 | private lateinit var btnNotificationPermission: Button 26 | 27 | private val batteryOptimizationsLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { 28 | updateButtonStates() 29 | (activity as? PermissionActivity)?.checkCurrentPermissionStateAndSetPage() 30 | } 31 | 32 | private val notificationPermissionLauncher = registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted -> 33 | updateButtonStates() 34 | (activity as? PermissionActivity)?.checkCurrentPermissionStateAndSetPage() 35 | } 36 | 37 | 38 | override fun onCreateView( 39 | inflater: LayoutInflater, container: ViewGroup?, 40 | savedInstanceState: Bundle? 41 | ): View? { 42 | return inflater.inflate(R.layout.fragment_permission_page1, container, false) 43 | } 44 | 45 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 46 | super.onViewCreated(view, savedInstanceState) 47 | 48 | btnBatteryOptimizations = view.findViewById(R.id.btn_battery_optimizations) 49 | btnNotificationPermission = view.findViewById(R.id.btn_notification_permission) 50 | 51 | btnBatteryOptimizations.setOnClickListener { checkBatteryOptimizations() } 52 | btnNotificationPermission.setOnClickListener { requestNotificationPermission() } 53 | 54 | updateButtonStates() 55 | } 56 | 57 | override fun onResume() { 58 | super.onResume() 59 | updateButtonStates() 60 | (activity as? PermissionActivity)?.checkCurrentPermissionStateAndSetPage() 61 | } 62 | 63 | private fun checkBatteryOptimizations() { 64 | if (!isAdded) return 65 | val powerManager = requireContext().getSystemService(Context.POWER_SERVICE) as PowerManager 66 | if (!powerManager.isIgnoringBatteryOptimizations(requireContext().packageName)) { 67 | val intent = Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS).apply { 68 | data = Uri.parse("package:${requireContext().packageName}") 69 | } 70 | try { 71 | batteryOptimizationsLauncher.launch(intent) 72 | } catch (e: Exception) { 73 | } 74 | } else { 75 | updateButtonStates() 76 | } 77 | } 78 | 79 | private fun requestNotificationPermission() { 80 | if (!isAdded) return 81 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { 82 | if (ContextCompat.checkSelfPermission( 83 | requireContext(), 84 | android.Manifest.permission.POST_NOTIFICATIONS 85 | ) != PackageManager.PERMISSION_GRANTED 86 | ) { 87 | notificationPermissionLauncher.launch(android.Manifest.permission.POST_NOTIFICATIONS) 88 | } else { 89 | updateButtonStates() 90 | } 91 | } else { 92 | updateButtonStates() 93 | } 94 | } 95 | 96 | private fun updateButtonStates() { 97 | if (!isAdded) return 98 | 99 | val powerManager = requireContext().getSystemService(Context.POWER_SERVICE) as PowerManager 100 | val batteryOptGranted = powerManager.isIgnoringBatteryOptimizations(requireContext().packageName) 101 | btnBatteryOptimizations.isEnabled = !batteryOptGranted 102 | 103 | val notificationGranted: Boolean 104 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { 105 | notificationGranted = ContextCompat.checkSelfPermission( 106 | requireContext(), 107 | android.Manifest.permission.POST_NOTIFICATIONS 108 | ) == PackageManager.PERMISSION_GRANTED 109 | btnNotificationPermission.isEnabled = !notificationGranted 110 | btnNotificationPermission.visibility = View.VISIBLE 111 | } else { 112 | notificationGranted = true 113 | btnNotificationPermission.isEnabled = false 114 | btnNotificationPermission.visibility = View.GONE 115 | } 116 | 117 | if (batteryOptGranted && notificationGranted) { 118 | (activity as? PermissionActivity)?.checkCurrentPermissionStateAndSetPage() 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /app/src/main/java/com/tpk/widgetspro/utils/BitmapCacheManager.kt: -------------------------------------------------------------------------------- 1 | package com.tpk.widgetspro.utils 2 | 3 | import android.content.Context 4 | import android.graphics.Bitmap 5 | import android.graphics.BitmapFactory 6 | import java.io.File 7 | import java.io.FileOutputStream 8 | 9 | object BitmapCacheManager { 10 | private const val CACHE_DIR = "device_images" 11 | private const val WEEK_IN_MILLIS = 604_800_000L 12 | 13 | fun getCachedBitmap(context: Context, deviceName: String): Bitmap? { 14 | val file = File(getCacheDir(context), "${deviceName.hashCode()}.png") 15 | return if (file.exists() && System.currentTimeMillis() - file.lastModified() < WEEK_IN_MILLIS) { 16 | BitmapFactory.decodeFile(file.absolutePath) 17 | } else { 18 | file.delete() 19 | null 20 | } 21 | } 22 | 23 | fun cacheBitmap(context: Context, deviceName: String, bitmap: Bitmap) { 24 | val file = File(getCacheDir(context), "${deviceName.hashCode()}.png") 25 | FileOutputStream(file).use { bitmap.compress(Bitmap.CompressFormat.PNG, 100, it) } 26 | } 27 | 28 | private fun getCacheDir(context: Context): File { 29 | val dir = File(context.cacheDir, CACHE_DIR) 30 | if (!dir.exists()) dir.mkdirs() 31 | return dir 32 | } 33 | 34 | fun clearExpiredCache(context: Context) { 35 | getCacheDir(context).listFiles()?.forEach { file -> 36 | if (System.currentTimeMillis() - file.lastModified() > WEEK_IN_MILLIS) file.delete() 37 | } 38 | } 39 | 40 | fun clearBitmapCache(context: Context, deviceName: String) { 41 | File(getCacheDir(context), "${deviceName.hashCode()}.png").delete() 42 | } 43 | } -------------------------------------------------------------------------------- /app/src/main/java/com/tpk/widgetspro/utils/BootReceiver.kt: -------------------------------------------------------------------------------- 1 | package com.tpk.widgetspro.utils 2 | 3 | import android.appwidget.AppWidgetManager 4 | import android.content.BroadcastReceiver 5 | import android.content.ComponentName 6 | import android.content.Context 7 | import android.content.Intent 8 | import com.tpk.widgetspro.widgets.battery.BatteryWidgetProvider 9 | import com.tpk.widgetspro.widgets.bluetooth.BluetoothWidgetProvider 10 | import com.tpk.widgetspro.widgets.caffeine.CaffeineWidget 11 | import com.tpk.widgetspro.widgets.cpu.CpuWidgetProvider 12 | import com.tpk.widgetspro.widgets.notes.NoteWidgetProvider 13 | import com.tpk.widgetspro.widgets.networkusage.NetworkSpeedWidgetProviderCircle 14 | import com.tpk.widgetspro.widgets.networkusage.NetworkSpeedWidgetProviderPill 15 | import com.tpk.widgetspro.widgets.networkusage.SimDataUsageWidgetProviderCircle 16 | import com.tpk.widgetspro.widgets.networkusage.SimDataUsageWidgetProviderPill 17 | import com.tpk.widgetspro.widgets.networkusage.WifiDataUsageWidgetProviderCircle 18 | import com.tpk.widgetspro.widgets.networkusage.WifiDataUsageWidgetProviderPill 19 | import com.tpk.widgetspro.widgets.sun.SunTrackerWidget 20 | import com.tpk.widgetspro.widgets.analogclock.AnalogClockWidgetProvider_1 21 | import com.tpk.widgetspro.widgets.analogclock.AnalogClockWidgetProvider_2 22 | import com.tpk.widgetspro.widgets.gif.GifWidgetProvider 23 | import com.tpk.widgetspro.widgets.music.MusicSimpleWidgetProvider 24 | 25 | class BootReceiver : BroadcastReceiver() { 26 | override fun onReceive(context: Context, intent: Intent) { 27 | if (intent.action == Intent.ACTION_BOOT_COMPLETED) { 28 | val appWidgetManager = AppWidgetManager.getInstance(context) 29 | updateWidgets(context, appWidgetManager, CpuWidgetProvider::class.java) 30 | updateWidgets(context, appWidgetManager, BatteryWidgetProvider::class.java) 31 | updateWidgets(context, appWidgetManager, CaffeineWidget::class.java) 32 | updateWidgets(context, appWidgetManager, BluetoothWidgetProvider::class.java) 33 | updateWidgets(context, appWidgetManager, SunTrackerWidget::class.java) 34 | updateWidgets(context, appWidgetManager, NetworkSpeedWidgetProviderCircle::class.java) 35 | updateWidgets(context, appWidgetManager, NetworkSpeedWidgetProviderPill::class.java) 36 | updateWidgets(context, appWidgetManager, WifiDataUsageWidgetProviderCircle::class.java) 37 | updateWidgets(context, appWidgetManager, WifiDataUsageWidgetProviderPill::class.java) 38 | updateWidgets(context, appWidgetManager, SimDataUsageWidgetProviderCircle::class.java) 39 | updateWidgets(context, appWidgetManager, SimDataUsageWidgetProviderPill::class.java) 40 | updateWidgets(context, appWidgetManager, NoteWidgetProvider::class.java) 41 | updateWidgets(context, appWidgetManager, AnalogClockWidgetProvider_1::class.java) 42 | updateWidgets(context, appWidgetManager, AnalogClockWidgetProvider_2::class.java) 43 | updateWidgets(context, appWidgetManager, GifWidgetProvider::class.java) 44 | updateWidgets(context, appWidgetManager, MusicSimpleWidgetProvider::class.java) 45 | } 46 | } 47 | 48 | private fun updateWidgets(context: Context, manager: AppWidgetManager, providerClass: Class<*>) { 49 | val provider = ComponentName(context, providerClass) 50 | val widgetIds = manager.getAppWidgetIds(provider) 51 | if (widgetIds.isNotEmpty()) { 52 | Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE).apply { 53 | putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, widgetIds) 54 | component = provider 55 | context.sendBroadcast(this) 56 | } 57 | } 58 | } 59 | } -------------------------------------------------------------------------------- /app/src/main/java/com/tpk/widgetspro/utils/CryptoUtils.kt: -------------------------------------------------------------------------------- 1 | package com.tpk.widgetspro.utils 2 | 3 | import android.util.Base64 4 | import javax.crypto.Cipher 5 | import javax.crypto.Mac 6 | import javax.crypto.spec.IvParameterSpec 7 | import javax.crypto.spec.SecretKeySpec 8 | 9 | object CryptoUtils { 10 | fun decryptApiKey(encryptedToken: String, fernetKey: String): String { 11 | val decodedKey = decodeBase64Url(fernetKey) 12 | if (decodedKey.size != 32) throw IllegalArgumentException("Invalid Fernet key") 13 | 14 | val signingKey = decodedKey.copyOfRange(0, 16) 15 | val encryptionKey = decodedKey.copyOfRange(16, 32) 16 | val tokenBytes = decodeBase64Url(encryptedToken) 17 | 18 | if (tokenBytes[0] != 0x80.toByte()) throw IllegalArgumentException("Invalid token version") 19 | 20 | val iv = tokenBytes.copyOfRange(9, 25) 21 | val ciphertext = tokenBytes.copyOfRange(25, tokenBytes.size - 32) 22 | val receivedHmac = tokenBytes.copyOfRange(tokenBytes.size - 32, tokenBytes.size) 23 | val dataToHmac = tokenBytes.copyOfRange(0, tokenBytes.size - 32) 24 | 25 | verifyHmac(dataToHmac, receivedHmac, signingKey) 26 | return decryptAesCbc(ciphertext, encryptionKey, iv) 27 | } 28 | 29 | private fun decodeBase64Url(input: String): ByteArray { 30 | val base64 = input.replace('-', '+').replace('_', '/') 31 | val padding = (4 - base64.length % 4) % 4 32 | return Base64.decode(base64 + "=".repeat(padding), Base64.DEFAULT) 33 | } 34 | 35 | private fun verifyHmac(data: ByteArray, receivedHmac: ByteArray, hmacKey: ByteArray) { 36 | val mac = Mac.getInstance("HmacSHA256") 37 | mac.init(SecretKeySpec(hmacKey, "HmacSHA256")) 38 | if (!mac.doFinal(data).contentEquals(receivedHmac)) throw SecurityException("HMAC verification failed") 39 | } 40 | 41 | private fun decryptAesCbc(ciphertext: ByteArray, key: ByteArray, iv: ByteArray): String { 42 | val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding") 43 | cipher.init(Cipher.DECRYPT_MODE, SecretKeySpec(key, "AES"), IvParameterSpec(iv)) 44 | return String(cipher.doFinal(ciphertext), Charsets.UTF_8) 45 | } 46 | } -------------------------------------------------------------------------------- /app/src/main/java/com/tpk/widgetspro/utils/ImageLoader.kt: -------------------------------------------------------------------------------- 1 | package com.tpk.widgetspro.utils 2 | 3 | import android.appwidget.AppWidgetManager 4 | import android.content.Context 5 | import android.graphics.Bitmap 6 | import android.graphics.BitmapFactory 7 | import android.widget.RemoteViews 8 | import com.tpk.widgetspro.R 9 | import com.tpk.widgetspro.api.ImageApiClient 10 | import kotlinx.coroutines.CoroutineScope 11 | import kotlinx.coroutines.Dispatchers 12 | import kotlinx.coroutines.launch 13 | import kotlinx.coroutines.withContext 14 | import java.net.HttpURLConnection 15 | import java.net.URL 16 | 17 | class ImageLoader( 18 | private val context: Context, 19 | private val appWidgetManager: AppWidgetManager, 20 | private val appWidgetId: Int, 21 | private val views: RemoteViews 22 | ) { 23 | private val scope = CoroutineScope(Dispatchers.IO) 24 | 25 | fun loadImageAsync(device: android.bluetooth.BluetoothDevice) { 26 | scope.launch { 27 | val imageUrl = ImageApiClient.getCachedUrl(context, device.name) 28 | ?: ImageApiClient.getImageUrl(context, device.name).also { 29 | if (it.isNotEmpty()) ImageApiClient.cacheUrl(context, device.name, it) 30 | } 31 | 32 | if (imageUrl.isNotEmpty()) { 33 | downloadBitmap(imageUrl)?.let { bitmap -> 34 | BitmapCacheManager.cacheBitmap(context, device.name, bitmap) 35 | withContext(Dispatchers.Main) { 36 | views.setImageViewBitmap(R.id.device_image, bitmap) 37 | appWidgetManager.updateAppWidget(appWidgetId, views) 38 | } 39 | } 40 | } 41 | } 42 | } 43 | 44 | private fun downloadBitmap(url: String): Bitmap? { 45 | return try { 46 | val connection = URL(url).openConnection() as HttpURLConnection 47 | connection.doInput = true 48 | connection.connect() 49 | BitmapFactory.decodeStream(connection.inputStream) 50 | } catch (e: Exception) { 51 | null 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /app/src/main/java/com/tpk/widgetspro/utils/NetworkStatsHelper.kt: -------------------------------------------------------------------------------- 1 | package com.tpk.widgetspro.utils 2 | 3 | import android.content.Context 4 | import android.net.ConnectivityManager 5 | import android.os.RemoteException 6 | import android.app.usage.NetworkStatsManager 7 | import java.util.Calendar 8 | 9 | object NetworkStatsHelper { 10 | 11 | @Throws(RemoteException::class) 12 | fun getSimDataUsage(context: Context): LongArray { 13 | val (startTime, endTime) = getTimeRange(context) 14 | val networkStatsManager = context.getSystemService(Context.NETWORK_STATS_SERVICE) as NetworkStatsManager 15 | val bucket = networkStatsManager.querySummaryForDevice( 16 | ConnectivityManager.TYPE_MOBILE, 17 | null, 18 | startTime, 19 | endTime 20 | ) 21 | val txBytes = bucket.txBytes 22 | val rxBytes = bucket.rxBytes 23 | val totalBytes = txBytes + rxBytes 24 | return longArrayOf(txBytes, rxBytes, totalBytes) 25 | } 26 | 27 | @Throws(RemoteException::class) 28 | fun getWifiDataUsage(context: Context): LongArray { 29 | val (startTime, endTime) = getTimeRange(context) 30 | val networkStatsManager = context.getSystemService(Context.NETWORK_STATS_SERVICE) as NetworkStatsManager 31 | val bucket = networkStatsManager.querySummaryForDevice( 32 | ConnectivityManager.TYPE_WIFI, 33 | null, 34 | startTime, 35 | endTime 36 | ) 37 | val txBytes = bucket.txBytes 38 | val rxBytes = bucket.rxBytes 39 | val totalBytes = txBytes + rxBytes 40 | return longArrayOf(txBytes, rxBytes, totalBytes) 41 | } 42 | 43 | private fun getTimeRange(context: Context): Pair { 44 | val prefs = context.getSharedPreferences("widget_prefs", Context.MODE_PRIVATE) 45 | val resetMode = prefs.getString("data_usage_reset_mode", "daily") ?: "daily" 46 | 47 | return if (resetMode == "daily") { 48 | val calendar = Calendar.getInstance().apply { 49 | set(Calendar.HOUR_OF_DAY, 0) 50 | set(Calendar.MINUTE, 0) 51 | set(Calendar.SECOND, 0) 52 | } 53 | val startTime = calendar.timeInMillis 54 | val endTime = startTime + 24 * 60 * 60 * 1000 55 | Pair(startTime, endTime) 56 | } else { 57 | val manualResetTime = prefs.getLong("manual_reset_time", System.currentTimeMillis()) 58 | val endTime = System.currentTimeMillis() 59 | Pair(manualResetTime, endTime) 60 | } 61 | } 62 | } -------------------------------------------------------------------------------- /app/src/main/java/com/tpk/widgetspro/utils/PermissionUtils.kt: -------------------------------------------------------------------------------- 1 | package com.tpk.widgetspro.utils 2 | 3 | import java.io.File 4 | import rikka.shizuku.Shizuku 5 | 6 | object PermissionUtils { 7 | private var hasRoot: Boolean? = null 8 | 9 | fun hasRootAccess(): Boolean { 10 | if (hasRoot == null) { 11 | hasRoot = checkSuBinary() 12 | } 13 | return hasRoot!! 14 | } 15 | 16 | private fun checkSuBinary(): Boolean { 17 | val paths = arrayOf( 18 | "/system/bin/su", 19 | "/system/xbin/su", 20 | "/sbin/su", 21 | "/vendor/bin/su" 22 | ) 23 | return paths.any { File(it).exists() } 24 | } 25 | 26 | fun hasShizukuPermission(): Boolean { 27 | return try { 28 | Shizuku.pingBinder() && 29 | Shizuku.checkSelfPermission() == android.content.pm.PackageManager.PERMISSION_GRANTED 30 | } catch (e: Exception) { 31 | false 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /app/src/main/java/com/tpk/widgetspro/widgets/analogclock/AnalogClockWidgetProvider_1.kt: -------------------------------------------------------------------------------- 1 | package com.tpk.widgetspro.widgets.analogclock 2 | 3 | import android.appwidget.AppWidgetManager 4 | import android.appwidget.AppWidgetProvider 5 | import android.content.Context 6 | import android.content.Intent 7 | import com.tpk.widgetspro.services.analogclock.AnalogClockUpdateService_1 8 | 9 | class AnalogClockWidgetProvider_1 : AppWidgetProvider() { 10 | 11 | override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray) { 12 | super.onUpdate(context, appWidgetManager, appWidgetIds) 13 | startService(context) 14 | } 15 | 16 | override fun onEnabled(context: Context) { 17 | startService(context) 18 | } 19 | 20 | override fun onDisabled(context: Context) { 21 | super.onDisabled(context) 22 | context.stopService(Intent(context, AnalogClockUpdateService_1::class.java)) 23 | } 24 | 25 | override fun onReceive(context: Context, intent: Intent) { 26 | super.onReceive(context, intent) 27 | if (intent.action == AppWidgetManager.ACTION_APPWIDGET_UPDATE) { 28 | startService(context) 29 | } 30 | } 31 | 32 | private fun startService(context: Context) { 33 | val intent = Intent(context, AnalogClockUpdateService_1::class.java) 34 | context.startForegroundService(intent) 35 | } 36 | } -------------------------------------------------------------------------------- /app/src/main/java/com/tpk/widgetspro/widgets/analogclock/AnalogClockWidgetProvider_2.kt: -------------------------------------------------------------------------------- 1 | package com.tpk.widgetspro.widgets.analogclock 2 | 3 | import android.appwidget.AppWidgetManager 4 | import android.appwidget.AppWidgetProvider 5 | import android.content.Context 6 | import android.content.Intent 7 | import com.tpk.widgetspro.services.analogclock.AnalogClockUpdateService_2 8 | 9 | class AnalogClockWidgetProvider_2 : AppWidgetProvider() { 10 | 11 | override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray) { 12 | super.onUpdate(context, appWidgetManager, appWidgetIds) 13 | startService(context) 14 | } 15 | 16 | override fun onEnabled(context: Context) { 17 | startService(context) 18 | } 19 | 20 | override fun onDisabled(context: Context) { 21 | super.onDisabled(context) 22 | context.stopService(Intent(context, AnalogClockUpdateService_2::class.java)) 23 | } 24 | 25 | override fun onReceive(context: Context, intent: Intent) { 26 | super.onReceive(context, intent) 27 | if (intent.action == AppWidgetManager.ACTION_APPWIDGET_UPDATE) { 28 | startService(context) 29 | } 30 | } 31 | 32 | private fun startService(context: Context) { 33 | val intent = Intent(context, AnalogClockUpdateService_2::class.java) 34 | context.startForegroundService(intent) 35 | } 36 | } -------------------------------------------------------------------------------- /app/src/main/java/com/tpk/widgetspro/widgets/battery/BatteryDottedView.kt: -------------------------------------------------------------------------------- 1 | package com.tpk.widgetspro.widgets.battery 2 | 3 | import android.content.Context 4 | import android.graphics.Canvas 5 | import com.tpk.widgetspro.base.BaseDottedGraphView 6 | 7 | class BatteryDottedView(context: Context) : BaseDottedGraphView(context) { 8 | private var percentage = 0 9 | private val columns = 50 10 | private val rows = 20 11 | 12 | fun updatePercentage(newPercentage: Int) { 13 | percentage = newPercentage.coerceIn(0, 100) 14 | invalidate() 15 | } 16 | 17 | override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { 18 | super.onSizeChanged(w, h, oldw, oldh) 19 | updateGreyDots(w, h, columns, rows) 20 | } 21 | 22 | override fun onDraw(canvas: Canvas) { 23 | super.onDraw(canvas) 24 | val barWidth = width.toFloat() / columns 25 | val filledColumns = percentage / 100f * columns 26 | val fullColumns = filledColumns.toInt() 27 | val partialRows = ((filledColumns - fullColumns) * rows).toInt() 28 | val availableHeight = height - fillAreaTopOffset - fillAreaBottomOffset 29 | val rowSpacing = availableHeight / (rows - 1) 30 | 31 | for (col in 0 until fullColumns) { 32 | for (row in 0 until rows) { 33 | val x = col * barWidth + barWidth / 2 34 | val y = fillAreaTopOffset + row * rowSpacing 35 | canvas.drawCircle(x, y, dotRadius, dotPaint) 36 | } 37 | } 38 | 39 | if (partialRows > 0 && fullColumns < columns) { 40 | for (row in (rows - partialRows) until rows) { 41 | val x = fullColumns * barWidth + barWidth / 2 42 | val y = fillAreaTopOffset + row * rowSpacing 43 | canvas.drawCircle(x, y, dotRadius, dotPaint) 44 | } 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /app/src/main/java/com/tpk/widgetspro/widgets/battery/BatteryMonitor.kt: -------------------------------------------------------------------------------- 1 | package com.tpk.widgetspro.widgets.battery 2 | 3 | import android.content.Context 4 | import android.content.Intent 5 | import android.content.IntentFilter 6 | import android.os.BatteryManager 7 | import android.os.Build 8 | import java.util.concurrent.Executors 9 | import java.util.concurrent.ScheduledExecutorService 10 | import java.util.concurrent.ScheduledFuture 11 | import java.util.concurrent.TimeUnit 12 | 13 | class BatteryMonitor( 14 | private val context: Context, 15 | private val callback: (Int, Int) -> Unit 16 | ) { 17 | private var executorService: ScheduledExecutorService? = null 18 | private var scheduledFuture: ScheduledFuture<*>? = null 19 | private var currentInterval = 60 20 | 21 | fun startMonitoring(initialInterval: Int) { 22 | currentInterval = initialInterval 23 | executorService = Executors.newSingleThreadScheduledExecutor() 24 | executorService?.execute { 25 | performMonitoring() 26 | scheduleNextRun() 27 | } 28 | } 29 | 30 | private fun performMonitoring() { 31 | val percentage = batteryPercentage() 32 | val health = batteryCycleCount() 33 | callback(percentage, health) 34 | } 35 | 36 | private fun scheduleNextRun() { 37 | scheduledFuture = executorService?.schedule({ 38 | performMonitoring() 39 | scheduleNextRun() 40 | }, currentInterval.toLong(), TimeUnit.MINUTES) 41 | } 42 | 43 | fun updateInterval(newInterval: Int) { 44 | currentInterval = newInterval.coerceAtLeast(1) 45 | scheduledFuture?.cancel(false) 46 | scheduleNextRun() 47 | } 48 | 49 | fun stopMonitoring() { 50 | scheduledFuture?.cancel(false) 51 | executorService?.shutdown() 52 | executorService = null 53 | scheduledFuture = null 54 | } 55 | 56 | private fun batteryPercentage(): Int { 57 | val bm = context.getSystemService(Context.BATTERY_SERVICE) as BatteryManager 58 | return bm.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY) 59 | } 60 | 61 | private fun batteryCycleCount(): Int { 62 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { 63 | val batteryStatus = context.registerReceiver(null, IntentFilter(Intent.ACTION_BATTERY_CHANGED)) 64 | return batteryStatus?.getIntExtra(BatteryManager.EXTRA_CYCLE_COUNT, -1) ?: -1} 65 | else 66 | return 0 67 | } 68 | } -------------------------------------------------------------------------------- /app/src/main/java/com/tpk/widgetspro/widgets/battery/BatteryWidgetProvider.kt: -------------------------------------------------------------------------------- 1 | package com.tpk.widgetspro.widgets.battery 2 | 3 | import android.appwidget.AppWidgetManager 4 | import android.content.Context 5 | import android.content.Intent 6 | import com.tpk.widgetspro.MainActivity 7 | import com.tpk.widgetspro.R 8 | import com.tpk.widgetspro.base.BaseWidgetProvider 9 | import com.tpk.widgetspro.services.battery.BatteryMonitorService 10 | 11 | class BatteryWidgetProvider : BaseWidgetProvider() { 12 | override val layoutId = R.layout.battery_widget_layout 13 | override val setupText = "Tap to setup Battery" 14 | override val setupDestination = MainActivity::class.java 15 | 16 | override fun onEnabled(context: Context) { 17 | super.onEnabled(context) 18 | startService(context) 19 | } 20 | 21 | override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray) { 22 | super.onUpdate(context, appWidgetManager, appWidgetIds) 23 | startService(context) 24 | } 25 | 26 | override fun onDisabled(context: Context) { 27 | super.onDisabled(context) 28 | context.stopService(Intent(context, BatteryMonitorService::class.java)) 29 | } 30 | 31 | override fun updateNormalWidgetView(context: Context, appWidgetManager: AppWidgetManager, appWidgetId: Int) { 32 | startService(context) 33 | } 34 | 35 | private fun startService(context: Context) { 36 | context.startService(Intent(context, BatteryMonitorService::class.java)) 37 | } 38 | } -------------------------------------------------------------------------------- /app/src/main/java/com/tpk/widgetspro/widgets/bluetooth/BluetoothReceiver.kt: -------------------------------------------------------------------------------- 1 | package com.tpk.widgetspro.widgets.bluetooth 2 | 3 | import android.appwidget.AppWidgetManager 4 | import android.bluetooth.BluetoothDevice 5 | import android.content.BroadcastReceiver 6 | import android.content.ComponentName 7 | import android.content.Context 8 | import android.content.Intent 9 | import android.widget.RemoteViews 10 | import com.tpk.widgetspro.R 11 | 12 | class BluetoothReceiver : BroadcastReceiver() { 13 | private val ACTION_BATTERY_LEVEL_CHANGED = "android.bluetooth.device.action.BATTERY_LEVEL_CHANGED" 14 | 15 | override fun onReceive(context: Context, intent: Intent) { 16 | val action = intent.action 17 | if (action == BluetoothDevice.ACTION_ACL_CONNECTED || action == BluetoothDevice.ACTION_ACL_DISCONNECTED || action == ACTION_BATTERY_LEVEL_CHANGED) { 18 | val appWidgetManager = AppWidgetManager.getInstance(context) 19 | val appWidgetIds = appWidgetManager.getAppWidgetIds(ComponentName(context, BluetoothWidgetProvider::class.java)) 20 | appWidgetIds.forEach { appWidgetId -> 21 | val views = RemoteViews(context.packageName, R.layout.bluetooth_widget_layout) 22 | BluetoothWidgetProvider.updateAppWidget(context, appWidgetManager, appWidgetId, views) 23 | appWidgetManager.updateAppWidget(appWidgetId, views) 24 | } 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /app/src/main/java/com/tpk/widgetspro/widgets/bluetooth/BluetoothWidgetConfigActivity.kt: -------------------------------------------------------------------------------- 1 | package com.tpk.widgetspro.widgets.bluetooth 2 | 3 | import android.appwidget.AppWidgetManager 4 | import android.bluetooth.BluetoothAdapter 5 | import android.content.Context 6 | import android.content.Intent 7 | import android.content.pm.PackageManager 8 | import android.os.Bundle 9 | import android.util.TypedValue 10 | import android.view.WindowManager 11 | import android.widget.ArrayAdapter 12 | import android.widget.LinearLayout 13 | import android.widget.ListView 14 | import android.widget.RemoteViews 15 | import android.widget.TextView 16 | import androidx.appcompat.app.AppCompatActivity 17 | import androidx.core.app.ActivityCompat 18 | import androidx.core.content.ContextCompat 19 | import com.tpk.widgetspro.R 20 | 21 | class BluetoothWidgetConfigActivity : AppCompatActivity() { 22 | private var appWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID 23 | private val REQUEST_BLUETOOTH_PERMISSIONS = 100 24 | 25 | override fun onCreate(savedInstanceState: Bundle?) { 26 | super.onCreate(savedInstanceState) 27 | 28 | appWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID) 29 | if (appWidgetId == AppWidgetManager.INVALID_APPWIDGET_ID) { 30 | finish() 31 | return 32 | } 33 | 34 | if (ContextCompat.checkSelfPermission(this, android.Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED) { 35 | ActivityCompat.requestPermissions(this, arrayOf(android.Manifest.permission.BLUETOOTH_CONNECT), REQUEST_BLUETOOTH_PERMISSIONS) 36 | } else { 37 | setupDeviceSelection() 38 | } 39 | } 40 | 41 | override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) { 42 | super.onRequestPermissionsResult(requestCode, permissions, grantResults) 43 | if (requestCode == REQUEST_BLUETOOTH_PERMISSIONS) { 44 | if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) { 45 | setupDeviceSelection() 46 | } else { 47 | setResult(RESULT_CANCELED) 48 | finish() 49 | } 50 | } 51 | } 52 | 53 | private fun setupDeviceSelection() { 54 | val bluetoothAdapter = BluetoothAdapter.getDefaultAdapter() 55 | val pairedDevices = bluetoothAdapter?.bondedDevices ?: emptySet() 56 | if (pairedDevices.isEmpty()) { 57 | setResult(RESULT_CANCELED) 58 | finish() 59 | return 60 | } 61 | 62 | window.apply { 63 | val displayMetrics = resources.displayMetrics 64 | val width = (displayMetrics.widthPixels * 0.8).toInt() 65 | setLayout(width, WindowManager.LayoutParams.WRAP_CONTENT) 66 | addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND) 67 | attributes.dimAmount = 0.5f 68 | } 69 | window.setBackgroundDrawableResource(R.color.transparent) 70 | setFinishOnTouchOutside(true) 71 | 72 | val layout = LinearLayout(this).apply { 73 | orientation = LinearLayout.VERTICAL 74 | setBackgroundResource(R.drawable.round_layout) 75 | setPadding(dpToPx(16), dpToPx(16), dpToPx(16), dpToPx(16)) 76 | } 77 | 78 | val titleTextView = TextView(this).apply { 79 | text = "Select a Bluetooth device" 80 | setTextColor(ContextCompat.getColor(this@BluetoothWidgetConfigActivity, R.color.text_color)) 81 | } 82 | layout.addView(titleTextView) 83 | 84 | val listView = ListView(this) 85 | val deviceNames = pairedDevices.map { it.name } 86 | val adapter = ArrayAdapter(this, R.layout.custom_simple_list_item, deviceNames) 87 | listView.adapter = adapter 88 | 89 | listView.setOnItemClickListener { _, _, position, _ -> 90 | val selectedDevice = pairedDevices.elementAt(position) 91 | saveSelectedDevice(appWidgetId, selectedDevice.address) 92 | updateWidget(appWidgetId) 93 | val resultValue = Intent().putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId) 94 | setResult(RESULT_OK, resultValue) 95 | finish() 96 | } 97 | 98 | layout.addView(listView) 99 | setContentView(layout) 100 | } 101 | 102 | private fun saveSelectedDevice(appWidgetId: Int, deviceAddress: String) { 103 | getSharedPreferences("BluetoothWidgetPrefs", Context.MODE_PRIVATE) 104 | .edit() 105 | .putString("device_address_$appWidgetId", deviceAddress) 106 | .apply() 107 | } 108 | 109 | private fun updateWidget(appWidgetId: Int) { 110 | val appWidgetManager = AppWidgetManager.getInstance(this) 111 | val views = RemoteViews(packageName, R.layout.bluetooth_widget_layout) 112 | BluetoothWidgetProvider.updateAppWidget(this, appWidgetManager, appWidgetId, views) 113 | appWidgetManager.updateAppWidget(appWidgetId, views) 114 | } 115 | 116 | private fun dpToPx(dp: Int): Int { 117 | return TypedValue.applyDimension( 118 | TypedValue.COMPLEX_UNIT_DIP, 119 | dp.toFloat(), 120 | resources.displayMetrics 121 | ).toInt() 122 | } 123 | } -------------------------------------------------------------------------------- /app/src/main/java/com/tpk/widgetspro/widgets/caffeine/CaffeineToggleReceiver.kt: -------------------------------------------------------------------------------- 1 | package com.tpk.widgetspro.widgets.caffeine 2 | 3 | import android.content.BroadcastReceiver 4 | import android.content.Context 5 | import android.content.Intent 6 | import com.tpk.widgetspro.services.caffeine.CaffeineService 7 | 8 | class CaffeineToggleReceiver : BroadcastReceiver() { 9 | override fun onReceive(context: Context, intent: Intent) { 10 | val prefs = context.getSharedPreferences("caffeine", Context.MODE_PRIVATE) 11 | val isActive = !prefs.getBoolean("active", false) 12 | if (toggleCaffeine(context, isActive)) { 13 | prefs.edit().putBoolean("active", isActive).apply() 14 | CaffeineWidget.updateAllWidgets(context) 15 | } 16 | } 17 | 18 | private fun toggleCaffeine(context: Context, enable: Boolean): Boolean { 19 | val serviceIntent = Intent(context, CaffeineService::class.java) 20 | return try { 21 | if (enable) { 22 | context.startForegroundService(serviceIntent) 23 | } else { 24 | context.stopService(serviceIntent) 25 | } 26 | true 27 | } catch (e: SecurityException) { 28 | false 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /app/src/main/java/com/tpk/widgetspro/widgets/caffeine/CaffeineWidget.kt: -------------------------------------------------------------------------------- 1 | package com.tpk.widgetspro.widgets.caffeine 2 | 3 | import android.app.PendingIntent 4 | import android.appwidget.AppWidgetManager 5 | import android.content.Context 6 | import android.content.Intent 7 | import android.graphics.Color 8 | import android.widget.RemoteViews 9 | import androidx.core.content.ContextCompat 10 | import com.tpk.widgetspro.MainActivity 11 | import com.tpk.widgetspro.R 12 | import com.tpk.widgetspro.base.BaseWidgetProvider 13 | import com.tpk.widgetspro.services.caffeine.CaffeineService 14 | import com.tpk.widgetspro.utils.CommonUtils 15 | 16 | class CaffeineWidget : BaseWidgetProvider() { 17 | override val layoutId = R.layout.caffeine_widget_layout 18 | override val setupText = "Tap to setup Caffeine" 19 | override val setupDestination = MainActivity::class.java 20 | 21 | override fun updateNormalWidgetView(context: Context, appWidgetManager: AppWidgetManager, appWidgetId: Int) { 22 | val caffeinePrefs = context.getSharedPreferences("caffeine", Context.MODE_PRIVATE) 23 | val isActive = caffeinePrefs.getBoolean("active", false) 24 | val views = RemoteViews(context.packageName, layoutId).apply { 25 | val imageRes = if (isActive) R.drawable.ic_coffee_active else R.drawable.ic_coffee_inactive 26 | setImageViewResource(R.id.widget_toggle, imageRes) 27 | setOnClickPendingIntent(R.id.widget_toggle, getToggleIntent(context)) 28 | setInt(R.id.widget_toggle, "setColorFilter", if (isActive) CommonUtils.getAccentColor(context) else ContextCompat.getColor(context, R.color.text_color)) 29 | } 30 | appWidgetManager.updateAppWidget(appWidgetId, views) 31 | } 32 | 33 | override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray) { 34 | val caffeinePrefs = context.getSharedPreferences("caffeine", Context.MODE_PRIVATE) 35 | val isActive = caffeinePrefs.getBoolean("active", false) 36 | val views = RemoteViews(context.packageName, layoutId).apply { 37 | val imageRes = if (isActive) R.drawable.ic_coffee_active else R.drawable.ic_coffee_inactive 38 | setImageViewResource(R.id.widget_toggle, imageRes) 39 | setOnClickPendingIntent(R.id.widget_toggle, getToggleIntent(context)) 40 | setInt(R.id.widget_toggle, "setColorFilter", if (isActive) CommonUtils.getAccentColor(context) else ContextCompat.getColor(context, R.color.text_color)) 41 | } 42 | appWidgetManager.updateAppWidget(appWidgetIds, views) 43 | } 44 | 45 | override fun onDisabled(context: Context) { 46 | super.onDisabled(context) 47 | val caffeinePrefs = context.getSharedPreferences("caffeine", Context.MODE_PRIVATE) 48 | if (caffeinePrefs.getBoolean("active", false)) { 49 | context.stopService(Intent(context, CaffeineService::class.java)) 50 | caffeinePrefs.edit().putBoolean("active", false).apply() 51 | } 52 | } 53 | 54 | companion object { 55 | fun getToggleIntent(context: Context) = PendingIntent.getBroadcast( 56 | context, 0, 57 | Intent(context, CaffeineToggleReceiver::class.java).apply { action = "TOGGLE_CAFFEINE" }, 58 | PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT 59 | ) 60 | 61 | fun updateAllWidgets(context: Context) { 62 | CommonUtils.updateAllWidgets(context, CaffeineWidget::class.java) 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /app/src/main/java/com/tpk/widgetspro/widgets/cpu/CpuMonitor.kt: -------------------------------------------------------------------------------- 1 | package com.tpk.widgetspro.widgets.cpu 2 | 3 | import java.io.BufferedReader 4 | import java.io.InputStreamReader 5 | import java.util.concurrent.Executors 6 | import java.util.concurrent.ScheduledExecutorService 7 | import java.util.concurrent.ScheduledFuture 8 | import java.util.concurrent.TimeUnit 9 | 10 | class CpuMonitor(private val useRoot: Boolean, private val callback: (Double, Double) -> Unit) { 11 | private var prevIdleTime: Long = 0 12 | private var prevNonIdleTime: Long = 0 13 | private var prevTotalTime: Long = 0 14 | private var isFirstReading = true 15 | private var executorService: ScheduledExecutorService? = null 16 | private var scheduledFuture: ScheduledFuture<*>? = null 17 | private var currentInterval = 60 18 | private var cpuThermalZone: String? = null 19 | 20 | fun startMonitoring(initialInterval: Int) { 21 | currentInterval = initialInterval 22 | executorService = Executors.newSingleThreadScheduledExecutor() 23 | executorService?.execute { 24 | performMonitoring() 25 | scheduleNextRun() 26 | } 27 | } 28 | 29 | private fun performMonitoring() { 30 | val cpuUsage = calculateCpuUsage() 31 | val cpuTemperature = readCpuTemperature() 32 | callback(cpuUsage, cpuTemperature) 33 | } 34 | 35 | private fun scheduleNextRun() { 36 | scheduledFuture = executorService?.schedule({ 37 | performMonitoring() 38 | scheduleNextRun() 39 | }, currentInterval.toLong(), TimeUnit.SECONDS) 40 | } 41 | 42 | fun updateInterval(newInterval: Int) { 43 | currentInterval = newInterval.coerceAtLeast(1) 44 | scheduledFuture?.cancel(false) 45 | scheduleNextRun() 46 | } 47 | 48 | fun stopMonitoring() { 49 | scheduledFuture?.cancel(false) 50 | executorService?.shutdown() 51 | executorService = null 52 | scheduledFuture = null 53 | } 54 | 55 | private fun calculateCpuUsage(): Double { 56 | val stat = readProcStat() ?: return 0.0 57 | val cpuLine = stat.lines().firstOrNull { it.startsWith("cpu ") } ?: return 0.0 58 | val tokens = cpuLine.split("\\s+".toRegex()).drop(1).mapNotNull { it.toLongOrNull() } 59 | 60 | if (tokens.size >= 10) { 61 | val idleTime = tokens[3] + tokens[4] 62 | val nonIdleTime = tokens[0] + tokens[1] + tokens[2] + tokens[5] + tokens[6] + tokens[7] 63 | val totalTime = idleTime + nonIdleTime 64 | 65 | if (isFirstReading) { 66 | prevIdleTime = idleTime 67 | prevNonIdleTime = nonIdleTime 68 | prevTotalTime = totalTime 69 | isFirstReading = false 70 | return 0.0 71 | } 72 | 73 | val totalDelta = totalTime - prevTotalTime 74 | val nonIdleDelta = nonIdleTime - prevNonIdleTime 75 | 76 | prevIdleTime = idleTime 77 | prevNonIdleTime = nonIdleTime 78 | prevTotalTime = totalTime 79 | 80 | return if (totalDelta > 0) (nonIdleDelta.toDouble() / totalDelta) * 100.0 else 0.0 81 | } 82 | return 0.0 83 | } 84 | 85 | private fun executeCommand(command: Array): Process? = try { 86 | if (useRoot) Runtime.getRuntime().exec(arrayOf("su", "-c", command.joinToString(" "))) 87 | else rikka.shizuku.Shizuku.newProcess(command, null, null) 88 | } catch (e: Exception) { 89 | null 90 | } 91 | 92 | private fun readProcStat(): String? { 93 | val process = executeCommand(arrayOf("cat", "/proc/stat")) ?: return null 94 | return BufferedReader(InputStreamReader(process.inputStream)).use { it.readText() }.also { process.destroy() } 95 | } 96 | 97 | private fun readCpuTemperature(): Double { 98 | if (cpuThermalZone == null) cpuThermalZone = findCpuThermalZone() 99 | return cpuThermalZone?.let { readThermalZoneTemp(it)?.div(1000.0) } ?: 0.0 100 | } 101 | 102 | private fun findCpuThermalZone(): String? { 103 | val zones = getThermalZones() 104 | val preferredTypes = listOf("cpu", "tsens", "processor") 105 | return zones.firstOrNull { zone -> 106 | readThermalZoneType(zone)?.let { type -> preferredTypes.any { type.contains(it, ignoreCase = true) } } == true 107 | } ?: zones.firstOrNull() 108 | } 109 | 110 | private fun getThermalZones(): List { 111 | val process = executeCommand(arrayOf("ls", "/sys/class/thermal/")) ?: return emptyList() 112 | return BufferedReader(InputStreamReader(process.inputStream)).use { it.readText().lines().filter { line -> line.startsWith("thermal_zone") } }.also { process.destroy() } 113 | } 114 | 115 | private fun readThermalZoneType(zone: String): String? { 116 | val process = executeCommand(arrayOf("cat", "/sys/class/thermal/$zone/type")) ?: return null 117 | return BufferedReader(InputStreamReader(process.inputStream)).use { it.readLine() }.also { process.destroy() } 118 | } 119 | 120 | private fun readThermalZoneTemp(zone: String): Double? { 121 | val process = executeCommand(arrayOf("cat", "/sys/class/thermal/$zone/temp")) ?: return null 122 | return BufferedReader(InputStreamReader(process.inputStream)).use { it.readLine()?.toDoubleOrNull() }.also { process.destroy() } 123 | } 124 | } -------------------------------------------------------------------------------- /app/src/main/java/com/tpk/widgetspro/widgets/cpu/CpuWidgetProvider.kt: -------------------------------------------------------------------------------- 1 | package com.tpk.widgetspro.widgets.cpu 2 | 3 | import android.appwidget.AppWidgetManager 4 | import android.content.Context 5 | import android.content.Intent 6 | import com.tpk.widgetspro.MainActivity 7 | import com.tpk.widgetspro.R 8 | import com.tpk.widgetspro.base.BaseWidgetProvider 9 | import com.tpk.widgetspro.services.cpu.CpuMonitorService 10 | import com.tpk.widgetspro.utils.PermissionUtils 11 | 12 | class CpuWidgetProvider : BaseWidgetProvider() { 13 | override val layoutId = R.layout.cpu_widget_layout 14 | override val setupText = "Tap to setup CPU" 15 | override val setupDestination = MainActivity::class.java 16 | 17 | override fun onEnabled(context: Context) { 18 | super.onEnabled(context) 19 | if (hasRequiredPermissions(context)) startService(context) 20 | } 21 | 22 | override fun onUpdate( 23 | context: Context, 24 | appWidgetManager: AppWidgetManager, 25 | appWidgetIds: IntArray 26 | ) { 27 | super.onUpdate(context, appWidgetManager, appWidgetIds) 28 | if (hasRequiredPermissions(context)) startService(context) 29 | } 30 | 31 | override fun onDisabled(context: Context) { 32 | super.onDisabled(context) 33 | context.stopService(Intent(context, CpuMonitorService::class.java)) 34 | } 35 | 36 | override fun updateNormalWidgetView( 37 | context: Context, 38 | appWidgetManager: AppWidgetManager, 39 | appWidgetId: Int 40 | ) { 41 | startService(context) 42 | } 43 | 44 | override fun hasRequiredPermissions(context: Context): Boolean { 45 | return PermissionUtils.hasRootAccess() || PermissionUtils.hasShizukuPermission() 46 | } 47 | 48 | private fun startService(context: Context) { 49 | context.startService(Intent(context, CpuMonitorService::class.java)) 50 | } 51 | } -------------------------------------------------------------------------------- /app/src/main/java/com/tpk/widgetspro/widgets/cpu/DottedGraphView.kt: -------------------------------------------------------------------------------- 1 | package com.tpk.widgetspro.widgets.cpu 2 | 3 | import android.content.Context 4 | import android.graphics.Canvas 5 | import com.tpk.widgetspro.base.BaseDottedGraphView 6 | 7 | class DottedGraphView(context: Context) : BaseDottedGraphView(context) { 8 | private var dataPoints: List = emptyList() 9 | private val dotSpacing = 10f 10 | 11 | fun setDataPoints(dataPoints: List) { 12 | this.dataPoints = dataPoints 13 | invalidate() 14 | } 15 | 16 | override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { 17 | super.onSizeChanged(w, h, oldw, oldh) 18 | if (dataPoints.isNotEmpty()) updateGreyDots(w, h, dataPoints.size, (h / dotSpacing).toInt(), w.toFloat() / dataPoints.size) 19 | } 20 | 21 | override fun onDraw(canvas: Canvas) { 22 | super.onDraw(canvas) 23 | if (dataPoints.isEmpty()) return 24 | 25 | val barWidth = width.toFloat() / dataPoints.size 26 | val maxDataValue = dataPoints.maxOrNull()?.coerceAtLeast(100.0) ?: 100.0 27 | val heightFactor = height.toFloat() / maxDataValue 28 | 29 | dataPoints.forEachIndexed { i, dataPoint -> 30 | val x = i * barWidth + barWidth / 2f 31 | val dataPointY = height - fillAreaBottomOffset - dataPoint * heightFactor 32 | var currentY = height - fillAreaBottomOffset 33 | while (currentY >= dataPointY) { 34 | canvas.drawCircle(x, currentY, dotRadius, dotPaint) 35 | currentY -= dotSpacing 36 | } 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /app/src/main/java/com/tpk/widgetspro/widgets/gif/GifWidgetProvider.kt: -------------------------------------------------------------------------------- 1 | package com.tpk.widgetspro.widgets.gif 2 | 3 | import android.appwidget.AppWidgetManager 4 | import android.appwidget.AppWidgetProvider 5 | import android.content.Context 6 | import android.content.Intent 7 | import com.tpk.widgetspro.services.gif.AnimationService 8 | import android.content.ComponentName 9 | 10 | class GifWidgetProvider : AppWidgetProvider() { 11 | override fun onUpdate( 12 | context: Context, 13 | appWidgetManager: AppWidgetManager, 14 | appWidgetIds: IntArray 15 | ) { 16 | appWidgetIds.forEach { appWidgetId -> 17 | val prefs = context.getSharedPreferences("gif_widget_prefs", Context.MODE_PRIVATE) 18 | val uriString = prefs.getString("file_uri_$appWidgetId", null) 19 | 20 | if (!prefs.contains("widget_index_$appWidgetId")) { 21 | val currentIndices = prefs.all.keys 22 | .filter { it.startsWith("widget_index_") } 23 | .mapNotNull { key -> 24 | try { prefs.getInt(key, 0) } catch (e: ClassCastException) { null } 25 | } 26 | val newIndex = (currentIndices.maxOrNull() ?: 0) + 1 27 | prefs.edit().putInt("widget_index_$appWidgetId", newIndex).apply() 28 | } 29 | 30 | val intent = Intent(context, AnimationService::class.java).apply { 31 | action = "ADD_WIDGET" 32 | putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId) 33 | uriString?.let { putExtra("file_uri", it) } 34 | } 35 | try { 36 | context.startForegroundService(intent) 37 | } catch (e: Exception) { 38 | context.startService(intent) 39 | } 40 | } 41 | } 42 | 43 | override fun onDeleted(context: Context, appWidgetIds: IntArray) { 44 | super.onDeleted(context, appWidgetIds) 45 | appWidgetIds.forEach { appWidgetId -> 46 | val prefs = context.getSharedPreferences("gif_widget_prefs", Context.MODE_PRIVATE) 47 | prefs.edit() 48 | .remove("file_uri_$appWidgetId") 49 | .remove("widget_index_$appWidgetId") 50 | .remove("sync_group_$appWidgetId") 51 | .apply() 52 | 53 | val intent = Intent(context, AnimationService::class.java).apply { 54 | action = "REMOVE_WIDGET" 55 | putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId) 56 | } 57 | try { 58 | context.startForegroundService(intent) 59 | } catch (e: Exception) { 60 | context.startService(intent) 61 | } 62 | } 63 | } 64 | 65 | override fun onDisabled(context: Context) { 66 | super.onDisabled(context) 67 | val appWidgetManager = AppWidgetManager.getInstance(context) 68 | val componentName = ComponentName(context, GifWidgetProvider::class.java) 69 | val appWidgetIds = appWidgetManager.getAppWidgetIds(componentName) 70 | if (appWidgetIds.isEmpty()) { 71 | context.stopService(Intent(context, AnimationService::class.java)) 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /app/src/main/java/com/tpk/widgetspro/widgets/networkusage/BaseNetworkSpeedWidgetProvider.kt: -------------------------------------------------------------------------------- 1 | package com.tpk.widgetspro.widgets.networkusage 2 | 3 | import android.appwidget.AppWidgetManager 4 | import android.appwidget.AppWidgetProvider 5 | import android.content.ComponentName 6 | import android.content.Context 7 | import android.content.Intent 8 | import android.graphics.Bitmap 9 | import android.graphics.Canvas 10 | import android.graphics.drawable.Drawable 11 | import android.widget.RemoteViews 12 | import com.tpk.widgetspro.R 13 | import com.tpk.widgetspro.services.networkusage.BaseNetworkSpeedWidgetService 14 | import com.tpk.widgetspro.utils.CommonUtils 15 | 16 | abstract class BaseNetworkSpeedWidgetProvider : AppWidgetProvider() { 17 | abstract val layoutResId: Int 18 | 19 | override fun onReceive(context: Context, intent: Intent) { 20 | super.onReceive(context, intent) 21 | if (intent.action == ACTION_UPDATE) { 22 | val appWidgetManager = AppWidgetManager.getInstance(context) 23 | val appWidgetIds = appWidgetManager.getAppWidgetIds(ComponentName(context, this::class.java)) 24 | onUpdate(context, appWidgetManager, appWidgetIds) 25 | } 26 | } 27 | 28 | override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray) { 29 | appWidgetIds.forEach { updateAppWidget(context, appWidgetManager, it, layoutResId) } 30 | context.startForegroundService(Intent(context, BaseNetworkSpeedWidgetService::class.java)) 31 | } 32 | 33 | override fun onEnabled(context: Context) { 34 | super.onEnabled(context) 35 | context.startForegroundService(Intent(context, BaseNetworkSpeedWidgetService::class.java)) 36 | } 37 | 38 | override fun onDisabled(context: Context) { 39 | super.onDisabled(context) 40 | val appWidgetManager = AppWidgetManager.getInstance(context) 41 | val circleWidgets = appWidgetManager.getAppWidgetIds(ComponentName(context, NetworkSpeedWidgetProviderCircle::class.java)) 42 | val pillWidgets = appWidgetManager.getAppWidgetIds(ComponentName(context, NetworkSpeedWidgetProviderPill::class.java)) 43 | 44 | if (circleWidgets.isEmpty() && pillWidgets.isEmpty()) { 45 | context.stopService(Intent(context, BaseNetworkSpeedWidgetService::class.java)) 46 | } 47 | } 48 | 49 | companion object { 50 | private const val ACTION_UPDATE = "com.tpk.widgetspro.ACTION_NETWORK_SPEED_UPDATE" 51 | 52 | fun updateAppWidget(context: Context, appWidgetManager: AppWidgetManager, appWidgetId: Int, layoutResId: Int) { 53 | val views = RemoteViews(context.packageName, layoutResId).apply { 54 | if (layoutResId == R.layout.network_speed_widget_circle) { 55 | val iconDrawable = context.getDrawable(R.drawable.network_speed_widget_icon) 56 | val scaledIcon = scaleDrawable(iconDrawable, 0.9f) 57 | setImageViewBitmap(R.id.network_speed_widget_image, scaledIcon) 58 | setInt(R.id.network_speed_widget_image, "setColorFilter", CommonUtils.getAccentColor(context)) 59 | setImageViewBitmap( 60 | R.id.network_speed_widget_text, 61 | CommonUtils.createTextAlternateBitmap(context, "N/A", 14f, CommonUtils.getTypeface(context)) 62 | ) 63 | } else { 64 | setImageViewBitmap( 65 | R.id.network_speed_widget_text, 66 | CommonUtils.createTextAlternateBitmap(context, "N/A", 20f, CommonUtils.getTypeface(context)) 67 | ) 68 | setInt(R.id.network_speed_widget_image, "setColorFilter", CommonUtils.getAccentColor(context)) 69 | } 70 | } 71 | appWidgetManager.updateAppWidget(appWidgetId, views) 72 | } 73 | 74 | private fun scaleDrawable(drawable: Drawable?, scaleFactor: Float): Bitmap? { 75 | if (drawable == null) return null 76 | val width = (drawable.intrinsicWidth * scaleFactor).toInt() 77 | val height = (drawable.intrinsicHeight * scaleFactor).toInt() 78 | drawable.setBounds(0, 0, width, height) 79 | val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888) 80 | val canvas = Canvas(bitmap) 81 | drawable.draw(canvas) 82 | return bitmap 83 | } 84 | 85 | fun updateAllWidgets(context: Context, providerClass: Class<*>) { 86 | val appWidgetManager = AppWidgetManager.getInstance(context) 87 | val appWidgetIds = appWidgetManager.getAppWidgetIds(ComponentName(context, providerClass)) 88 | appWidgetIds.forEach { updateAppWidget(context, appWidgetManager, it, when (providerClass) { 89 | NetworkSpeedWidgetProviderCircle::class.java -> R.layout.network_speed_widget_circle 90 | NetworkSpeedWidgetProviderPill::class.java -> R.layout.network_speed_widget_pill 91 | else -> throw IllegalArgumentException("Unknown provider class: $providerClass") 92 | }) } 93 | } 94 | } 95 | } -------------------------------------------------------------------------------- /app/src/main/java/com/tpk/widgetspro/widgets/networkusage/NetworkSpeedWidgetProviderCircle.kt: -------------------------------------------------------------------------------- 1 | package com.tpk.widgetspro.widgets.networkusage 2 | 3 | import com.tpk.widgetspro.R 4 | 5 | class NetworkSpeedWidgetProviderCircle : BaseNetworkSpeedWidgetProvider() { 6 | override val layoutResId: Int = R.layout.network_speed_widget_circle 7 | } -------------------------------------------------------------------------------- /app/src/main/java/com/tpk/widgetspro/widgets/networkusage/NetworkSpeedWidgetProviderPill.kt: -------------------------------------------------------------------------------- 1 | package com.tpk.widgetspro.widgets.networkusage 2 | 3 | import com.tpk.widgetspro.R 4 | 5 | class NetworkSpeedWidgetProviderPill : BaseNetworkSpeedWidgetProvider() { 6 | override val layoutResId: Int = R.layout.network_speed_widget_pill 7 | } -------------------------------------------------------------------------------- /app/src/main/java/com/tpk/widgetspro/widgets/networkusage/SimDataUsageWidgetProviderCircle.kt: -------------------------------------------------------------------------------- 1 | package com.tpk.widgetspro.widgets.networkusage 2 | 3 | import com.tpk.widgetspro.R 4 | 5 | class SimDataUsageWidgetProviderCircle : BaseSimDataUsageWidgetProvider() { 6 | override val layoutResId: Int = R.layout.sim_data_usage_widget_circle 7 | } -------------------------------------------------------------------------------- /app/src/main/java/com/tpk/widgetspro/widgets/networkusage/SimDataUsageWidgetProviderPill.kt: -------------------------------------------------------------------------------- 1 | package com.tpk.widgetspro.widgets.networkusage 2 | 3 | import com.tpk.widgetspro.R 4 | 5 | class SimDataUsageWidgetProviderPill : BaseSimDataUsageWidgetProvider() { 6 | override val layoutResId: Int = R.layout.sim_data_usage_widget_pill 7 | } -------------------------------------------------------------------------------- /app/src/main/java/com/tpk/widgetspro/widgets/networkusage/WifiDataUsageWidgetProviderCircle.kt: -------------------------------------------------------------------------------- 1 | package com.tpk.widgetspro.widgets.networkusage 2 | 3 | import com.tpk.widgetspro.R 4 | 5 | class WifiDataUsageWidgetProviderCircle : BaseWifiDataUsageWidgetProvider() { 6 | override val layoutResId: Int = R.layout.wifi_data_usage_widget_circle 7 | } -------------------------------------------------------------------------------- /app/src/main/java/com/tpk/widgetspro/widgets/networkusage/WifiDataUsageWidgetProviderPill.kt: -------------------------------------------------------------------------------- 1 | package com.tpk.widgetspro.widgets.networkusage 2 | 3 | import com.tpk.widgetspro.R 4 | 5 | class WifiDataUsageWidgetProviderPill : BaseWifiDataUsageWidgetProvider() { 6 | override val layoutResId: Int = R.layout.wifi_data_usage_widget_pill 7 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable/analog_1_bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/preethamkmr3/WidgetsPro/af0c99f2d44ec46c043f66c3418d73874099bd0b/app/src/main/res/drawable/analog_1_bg.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/analog_1_dial_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/preethamkmr3/WidgetsPro/af0c99f2d44ec46c043f66c3418d73874099bd0b/app/src/main/res/drawable/analog_1_dial_dark.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/analog_1_dial_light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/preethamkmr3/WidgetsPro/af0c99f2d44ec46c043f66c3418d73874099bd0b/app/src/main/res/drawable/analog_1_dial_light.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/analog_1_hour.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/preethamkmr3/WidgetsPro/af0c99f2d44ec46c043f66c3418d73874099bd0b/app/src/main/res/drawable/analog_1_hour.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/analog_1_min.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/preethamkmr3/WidgetsPro/af0c99f2d44ec46c043f66c3418d73874099bd0b/app/src/main/res/drawable/analog_1_min.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/analog_1_secs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/preethamkmr3/WidgetsPro/af0c99f2d44ec46c043f66c3418d73874099bd0b/app/src/main/res/drawable/analog_1_secs.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/analog_2_bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/preethamkmr3/WidgetsPro/af0c99f2d44ec46c043f66c3418d73874099bd0b/app/src/main/res/drawable/analog_2_bg.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/analog_2_dial_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/preethamkmr3/WidgetsPro/af0c99f2d44ec46c043f66c3418d73874099bd0b/app/src/main/res/drawable/analog_2_dial_dark.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/analog_2_dial_light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/preethamkmr3/WidgetsPro/af0c99f2d44ec46c043f66c3418d73874099bd0b/app/src/main/res/drawable/analog_2_dial_light.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/analog_2_hour.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/preethamkmr3/WidgetsPro/af0c99f2d44ec46c043f66c3418d73874099bd0b/app/src/main/res/drawable/analog_2_hour.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/analog_2_min.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/preethamkmr3/WidgetsPro/af0c99f2d44ec46c043f66c3418d73874099bd0b/app/src/main/res/drawable/analog_2_min.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/analog_2_secs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/preethamkmr3/WidgetsPro/af0c99f2d44ec46c043f66c3418d73874099bd0b/app/src/main/res/drawable/analog_2_secs.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/arrow_down.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/dialog_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/gif_widget_bg.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_bluetooth_placeholder.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_coffee_active.xml: -------------------------------------------------------------------------------- 1 | 6 | 11 | 14 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_coffee_inactive.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_default_album_art.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_help.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_home.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 6 | 10 | 14 | 15 | 18 | 19 | 22 | 23 | 26 | 27 | 30 | 31 | 34 | 35 | 38 | 41 | 42 | 45 | 46 | 49 | 50 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_media_next.xml: -------------------------------------------------------------------------------- 1 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_media_previous.xml: -------------------------------------------------------------------------------- 1 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_music_note.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_notification.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 10 | 11 | 16 | 17 | 20 | 21 | 24 | 25 | 28 | 29 | 32 | 33 | 36 | 37 | 40 | 41 | 44 | 45 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_pause.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_play_arrow.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_refresh.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/moon_orb.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/network_speed_widget_icon.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 12 | 15 | 18 | 21 | 24 | 27 | 30 | 33 | 36 | 39 | 40 | 41 | 42 | 45 | 48 | 51 | 54 | 57 | 60 | 63 | 66 | 69 | 72 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/preview_shape_circle.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/preview_shape_pill.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/round_layout.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/round_layout_outline_bg.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/rounded_layout_bg.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/rounded_layout_bg_alt.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/sim_data_usage_icon.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 12 | 15 | 18 | 21 | 24 | 27 | 30 | 33 | 36 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/sun_orb.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/wifi_data_usage_icon.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 12 | 15 | 18 | 21 | 24 | 27 | 30 | 33 | 36 | 39 | 40 | -------------------------------------------------------------------------------- /app/src/main/res/font/my_custom_font.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/preethamkmr3/WidgetsPro/af0c99f2d44ec46c043f66c3418d73874099bd0b/app/src/main/res/font/my_custom_font.ttf -------------------------------------------------------------------------------- /app/src/main/res/font/ndot.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/preethamkmr3/WidgetsPro/af0c99f2d44ec46c043f66c3418d73874099bd0b/app/src/main/res/font/ndot.otf -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 16 | 17 | 28 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_note_input.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 24 | 25 | 31 | 32 | 39 | 40 | 44 | 45 |