├── .gitignore
├── .idea
├── .gitignore
├── .name
├── AndroidProjectSystem.xml
├── compiler.xml
├── deploymentTargetDropDown.xml
├── gradle.xml
├── icon.png
├── inspectionProfiles
│ └── Project_Default.xml
├── jarRepositories.xml
├── kotlinc.xml
├── migrations.xml
├── misc.xml
├── runConfigurations.xml
└── vcs.xml
├── PRIVACY.md
├── README.md
├── app
├── .gitignore
├── FeatureImage.afdesign
├── FeatureImage.png
├── LauncherBackground.afphoto
├── LauncherBackground.png
├── LauncherBackground.svg
├── LauncherForeground.afdesign
├── LauncherForeground.svg
├── build.gradle.kts
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── ic_launcher-playstore.png
│ ├── java
│ └── tk
│ │ └── zwander
│ │ └── rootactivitylauncher
│ │ ├── App.kt
│ │ ├── MainActivity.kt
│ │ ├── activities
│ │ ├── ShortcutLaunchActivity.kt
│ │ └── tasker
│ │ │ └── TaskerLaunchComponentConfigureActivity.kt
│ │ ├── data
│ │ ├── BaseOption.kt
│ │ ├── ComponentActionButton.kt
│ │ ├── ExtraInfo.kt
│ │ ├── FilterMode.kt
│ │ ├── PrefManager.kt
│ │ ├── Query.kt
│ │ ├── SortMode.kt
│ │ ├── SortOrder.kt
│ │ ├── component
│ │ │ ├── ActivityInfo.kt
│ │ │ ├── Availability.kt
│ │ │ ├── BaseComponentInfo.kt
│ │ │ ├── ComponentType.kt
│ │ │ ├── ReceiverInfo.kt
│ │ │ └── ServiceInfo.kt
│ │ ├── model
│ │ │ ├── AppModel.kt
│ │ │ ├── BaseInfoModel.kt
│ │ │ ├── ExtrasDialogModel.kt
│ │ │ ├── FavoriteModel.kt
│ │ │ └── MainModel.kt
│ │ └── tasker
│ │ │ └── TaskerLaunchComponentInfo.kt
│ │ ├── util
│ │ ├── AdvancedSearcher.kt
│ │ ├── CollectionUtils.kt
│ │ ├── ComponentInfoUtils.kt
│ │ ├── ComponentUtils.kt
│ │ ├── CompositionLocals.kt
│ │ ├── DhizukuUtils.kt
│ │ ├── EnabledUtils.kt
│ │ ├── InfoGetter.kt
│ │ ├── PackageUtils.kt
│ │ ├── PermissionResultListener.kt
│ │ ├── PermissionUtils.kt
│ │ ├── UIUtils.kt
│ │ ├── Utils.kt
│ │ ├── launch
│ │ │ ├── LaunchArgs.kt
│ │ │ ├── LaunchStrategy.kt
│ │ │ ├── LaunchStrategyRegistry.kt
│ │ │ └── LaunchUtils.kt
│ │ ├── receiver
│ │ │ ├── AdminReceiver.kt
│ │ │ └── KnoxLicenseReceiver.kt
│ │ └── tasker
│ │ │ ├── TaskerLaunchComponentHelper.kt
│ │ │ └── TaskerLaunchComponentRunner.kt
│ │ └── views
│ │ ├── MainView.kt
│ │ ├── ScrimView.kt
│ │ ├── components
│ │ ├── AppItem.kt
│ │ ├── AppList.kt
│ │ ├── BottomBar.kt
│ │ ├── ComponentBar.kt
│ │ ├── ComponentGroup.kt
│ │ ├── ComponentItem.kt
│ │ ├── Menu.kt
│ │ ├── OptionGroup.kt
│ │ ├── SearchComponent.kt
│ │ └── SelectableCard.kt
│ │ ├── dialogs
│ │ ├── AdvancedUsageDialog.kt
│ │ ├── BaseAlertDialog.kt
│ │ ├── ComponentInfoDialog.kt
│ │ ├── ExtraTypeDialog.kt
│ │ ├── ExtrasDialog.kt
│ │ ├── FilterDialog.kt
│ │ ├── PatreonSupportersDialog.kt
│ │ └── SortDialog.kt
│ │ └── theme
│ │ └── Theme.kt
│ └── res
│ ├── drawable
│ ├── baseline_favorite_24.xml
│ ├── ic_baseline_filter_list_24.xml
│ ├── ic_baseline_help_outline_24.xml
│ ├── ic_baseline_link_24.xml
│ ├── ic_baseline_open_in_new_24.xml
│ ├── ic_launcher_background.xml
│ ├── ic_launcher_foreground.xml
│ ├── save.xml
│ ├── scroll_to_bottom.xml
│ ├── scroll_to_top.xml
│ ├── sort.xml
│ └── tune.xml
│ ├── mipmap-anydpi-v26
│ ├── ic_launcher.xml
│ └── ic_launcher_round.xml
│ ├── mipmap-hdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── mipmap-mdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── mipmap-xhdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── mipmap-xxhdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── mipmap-xxxhdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── values-v31
│ └── colors.xml
│ ├── values
│ ├── colors.xml
│ ├── strings.xml
│ └── styles.xml
│ └── xml
│ └── device_admin.xml
├── 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 |
--------------------------------------------------------------------------------
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 | /deploymentTargetSelector.xml
5 | /other.xml
6 | /codeStyles/Project.xml
7 | /appInsightsSettings.xml
8 |
--------------------------------------------------------------------------------
/.idea/.name:
--------------------------------------------------------------------------------
1 | Root Activity Launcher
--------------------------------------------------------------------------------
/.idea/AndroidProjectSystem.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/deploymentTargetDropDown.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
18 |
19 |
--------------------------------------------------------------------------------
/.idea/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zacharee/RootActivityLauncher/58c903ddabdf33bab83c2c730dd06a19ad9088a1/.idea/icon.png
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/Project_Default.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
--------------------------------------------------------------------------------
/.idea/jarRepositories.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/.idea/kotlinc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/migrations.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
--------------------------------------------------------------------------------
/.idea/runConfigurations.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/PRIVACY.md:
--------------------------------------------------------------------------------
1 | # Privacy Policy
2 |
3 | Root Activity Launcher collects only the data needed for crash logs, along with the [default Firebase analytics events](https://support.google.com/firebase/answer/9234069?hl=en).
4 |
5 | No personally identifying information is collected, and no data is shared beyond the scope of Firebase.
6 |
7 | ## Permissions
8 |
9 | - com.android.launcher.permission.INSTALL_SHORTCUT
10 | - Used to allow Root Activity Launcher to add shortcuts to the home screen.
11 | - android.permission.QUERY_ALL_PACKAGES
12 | - Used to allow Root Activity Launcher to display all installed apps.
13 | - android.permission.FOREGROUND_SERVICE
14 | - Used to allow Root Activity Launcher to start app Services in the foreground.
15 | - moe.shizuku.manager.permission.API_V23
16 | - Used to allow Root Activity Launcher to use [Shizuku](https://shizuku.rikka.app) to access privileged APIs.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Root Activity Launcher
2 | A utility app to launch and modify app components with potentially privileged access.
3 |
4 | ## Download
5 | https://play.google.com/store/apps/details?id=tk.zwander.rootactivitylauncher
6 |
7 | ## Error Reporting
8 | Root Activity Launcher uses Bugsnag for error reporting as of version 28. Previous versions use Firebase Crashlytics.
9 |
10 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 | /release
--------------------------------------------------------------------------------
/app/FeatureImage.afdesign:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zacharee/RootActivityLauncher/58c903ddabdf33bab83c2c730dd06a19ad9088a1/app/FeatureImage.afdesign
--------------------------------------------------------------------------------
/app/FeatureImage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zacharee/RootActivityLauncher/58c903ddabdf33bab83c2c730dd06a19ad9088a1/app/FeatureImage.png
--------------------------------------------------------------------------------
/app/LauncherBackground.afphoto:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zacharee/RootActivityLauncher/58c903ddabdf33bab83c2c730dd06a19ad9088a1/app/LauncherBackground.afphoto
--------------------------------------------------------------------------------
/app/LauncherBackground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zacharee/RootActivityLauncher/58c903ddabdf33bab83c2c730dd06a19ad9088a1/app/LauncherBackground.png
--------------------------------------------------------------------------------
/app/LauncherBackground.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
--------------------------------------------------------------------------------
/app/LauncherForeground.afdesign:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zacharee/RootActivityLauncher/58c903ddabdf33bab83c2c730dd06a19ad9088a1/app/LauncherForeground.afdesign
--------------------------------------------------------------------------------
/app/LauncherForeground.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
13 |
--------------------------------------------------------------------------------
/app/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | alias(libs.plugins.android.application)
3 | alias(libs.plugins.kotlin.android)
4 | alias(libs.plugins.kotlin.parcelize)
5 | alias(libs.plugins.kotlin.atomicfu)
6 | alias(libs.plugins.kotlin.compose)
7 | alias(libs.plugins.bugsnag.android)
8 | }
9 |
10 | android {
11 | compileSdk = 35
12 |
13 | defaultConfig {
14 | applicationId = "tk.zwander.rootactivitylauncher"
15 | namespace = "tk.zwander.rootactivitylauncher"
16 | minSdk = 21
17 | targetSdk = 35
18 | versionCode = 33
19 | versionName = versionCode.toString()
20 |
21 | extensions.getByType(BasePluginExtension::class.java).archivesName.set("RootActivityLauncher_${versionCode}")
22 | }
23 |
24 | buildTypes {
25 | release {
26 | signingConfig = signingConfigs["debug"]
27 | }
28 | }
29 |
30 | compileOptions {
31 | sourceCompatibility = JavaVersion.VERSION_11
32 | targetCompatibility = JavaVersion.VERSION_11
33 | }
34 |
35 | kotlinOptions {
36 | jvmTarget = "11"
37 | }
38 |
39 | buildFeatures {
40 | viewBinding = true
41 | compose = true
42 | }
43 | }
44 |
45 | dependencies {
46 | implementation(fileTree("libs") { include("*.jar") })
47 | implementation(libs.kotlin.stdlib)
48 | implementation(libs.kotlin.reflect)
49 | implementation(libs.atomicfu)
50 |
51 | implementation(libs.core.ktx)
52 | implementation(libs.appcompat)
53 | implementation(libs.preference.ktx)
54 |
55 | implementation(libs.compose.material3)
56 | implementation(libs.compose.ui.tooling.preview)
57 | implementation(libs.compose.runtime)
58 | implementation(libs.compose.material)
59 | implementation(libs.activity.compose)
60 | implementation(libs.compose.theme.adapter3)
61 | implementation(libs.datastore.preferences)
62 |
63 | implementation(libs.coil)
64 | implementation(libs.coil.compose)
65 | implementation(libs.composetooltip)
66 |
67 | implementation(libs.google.material)
68 | implementation(libs.gson)
69 |
70 | implementation(libs.shizuku.api)
71 | implementation(libs.shizuku.provider)
72 | implementation(libs.hiddenapibypass)
73 | implementation(libs.patreonSupportersRetrieval)
74 | implementation(libs.taskerpluginlibrary)
75 |
76 | implementation(libs.kotlinx.coroutines.core)
77 | implementation(libs.kotlinx.coroutines.android)
78 | implementation(libs.libsuperuser)
79 | implementation(libs.progressCircula)
80 |
81 | implementation(libs.bugsnag.android)
82 | implementation(libs.dhizuku.api)
83 | }
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
8 |
9 |
10 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
47 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
63 |
66 |
67 |
68 |
69 |
70 |
71 |
78 |
79 |
85 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
101 |
103 |
106 |
107 |
--------------------------------------------------------------------------------
/app/src/main/ic_launcher-playstore.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zacharee/RootActivityLauncher/58c903ddabdf33bab83c2c730dd06a19ad9088a1/app/src/main/ic_launcher-playstore.png
--------------------------------------------------------------------------------
/app/src/main/java/tk/zwander/rootactivitylauncher/App.kt:
--------------------------------------------------------------------------------
1 | package tk.zwander.rootactivitylauncher
2 |
3 | import android.app.Application
4 | import android.os.Build
5 | import com.bugsnag.android.Bugsnag
6 | import org.lsposed.hiddenapibypass.HiddenApiBypass
7 |
8 | class App : Application() {
9 | override fun onCreate() {
10 | super.onCreate()
11 |
12 | Bugsnag.start(this)
13 |
14 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
15 | HiddenApiBypass.setHiddenApiExemptions("L")
16 | }
17 | }
18 | }
--------------------------------------------------------------------------------
/app/src/main/java/tk/zwander/rootactivitylauncher/activities/ShortcutLaunchActivity.kt:
--------------------------------------------------------------------------------
1 | package tk.zwander.rootactivitylauncher.activities
2 |
3 | import android.content.Context
4 | import android.content.Intent
5 | import android.os.Bundle
6 | import androidx.appcompat.app.AppCompatActivity
7 | import androidx.core.content.pm.ShortcutInfoCompat
8 | import androidx.core.content.pm.ShortcutManagerCompat
9 | import androidx.core.graphics.drawable.IconCompat
10 | import kotlinx.coroutines.Dispatchers
11 | import kotlinx.coroutines.runBlocking
12 | import tk.zwander.rootactivitylauncher.data.component.ComponentType
13 | import tk.zwander.rootactivitylauncher.util.determineComponentNamePackage
14 | import tk.zwander.rootactivitylauncher.util.findExtrasForComponent
15 | import tk.zwander.rootactivitylauncher.util.launch.launch
16 |
17 | class ShortcutLaunchActivity : AppCompatActivity() {
18 | companion object {
19 | const val EXTRA_COMPONENT_KEY = "component_key"
20 | const val EXTRA_COMPONENT_TYPE = "component_type"
21 |
22 | fun createShortcut(
23 | context: Context,
24 | label: CharSequence,
25 | icon: IconCompat?,
26 | componentKey: String,
27 | componentType: ComponentType
28 | ) {
29 | with(context) {
30 | val shortcut = Intent(this, ShortcutLaunchActivity::class.java)
31 | shortcut.action = Intent.ACTION_MAIN
32 | shortcut.putExtra(EXTRA_COMPONENT_KEY, componentKey)
33 | shortcut.putExtra(EXTRA_COMPONENT_TYPE, componentType.serialize())
34 |
35 | val info = ShortcutInfoCompat.Builder(this, componentKey)
36 | .setIcon(icon)
37 | .setShortLabel(label.takeIf { it.isNotBlank() } ?: componentKey)
38 | .setLongLabel("$label: $componentKey")
39 | .setIntent(shortcut)
40 | .build()
41 |
42 | ShortcutManagerCompat.requestPinShortcut(
43 | this,
44 | info,
45 | null
46 | )
47 | }
48 | }
49 | }
50 |
51 | private val componentKey by lazy { intent.getStringExtra(EXTRA_COMPONENT_KEY) }
52 | private val componentType by lazy {
53 | ComponentType.deserialize(
54 | intent.getStringExtra(
55 | EXTRA_COMPONENT_TYPE
56 | )
57 | )
58 | }
59 |
60 | override fun onCreate(savedInstanceState: Bundle?) {
61 | super.onCreate(savedInstanceState)
62 |
63 | if (componentKey.isNullOrBlank() || componentType == null) {
64 | finish()
65 | return
66 | }
67 |
68 | val extras = findExtrasForComponent(componentKey!!)
69 | val globalExtras = findExtrasForComponent(determineComponentNamePackage(componentKey!!))
70 |
71 | runBlocking(Dispatchers.IO) {
72 | launch(
73 | componentType!!,
74 | globalExtras + extras,
75 | componentKey!!
76 | )
77 | }
78 |
79 | finish()
80 | }
81 | }
--------------------------------------------------------------------------------
/app/src/main/java/tk/zwander/rootactivitylauncher/activities/tasker/TaskerLaunchComponentConfigureActivity.kt:
--------------------------------------------------------------------------------
1 | package tk.zwander.rootactivitylauncher.activities.tasker
2 |
3 | import android.content.ComponentName
4 | import android.content.Context
5 | import com.joaomgcd.taskerpluginlibrary.config.TaskerPluginConfig
6 | import com.joaomgcd.taskerpluginlibrary.input.TaskerInput
7 | import tk.zwander.rootactivitylauncher.MainActivity
8 | import tk.zwander.rootactivitylauncher.data.component.ComponentType
9 | import tk.zwander.rootactivitylauncher.data.tasker.TaskerLaunchComponentInfo
10 | import tk.zwander.rootactivitylauncher.util.tasker.TaskerLaunchComponentHelper
11 |
12 | class TaskerLaunchComponentConfigureActivity : MainActivity(), TaskerPluginConfig {
13 | override val context: Context
14 | get() = this
15 | override val inputForTasker: TaskerInput
16 | get() = TaskerInput(TaskerLaunchComponentInfo(selectedItem?.first?.serialize(), selectedItem?.second?.flattenToString()))
17 |
18 | override val isForTasker = true
19 | override var selectedItem: Pair? = null
20 | set(value) {
21 | field = value
22 |
23 | if (value != null) {
24 | TaskerLaunchComponentHelper(this).finishForTasker()
25 | }
26 | }
27 |
28 | override fun assignFromInput(input: TaskerInput) {
29 | val type = ComponentType.deserialize(input.regular.type)
30 | val cmp = ComponentName.unflattenFromString(input.regular.component ?: "")
31 |
32 | selectedItem = if (type == null || cmp == null) {
33 | null
34 | } else {
35 | type to cmp
36 | }
37 | }
38 | }
--------------------------------------------------------------------------------
/app/src/main/java/tk/zwander/rootactivitylauncher/data/BaseOption.kt:
--------------------------------------------------------------------------------
1 | package tk.zwander.rootactivitylauncher.data
2 |
3 | import androidx.annotation.StringRes
4 |
5 | abstract class BaseOption(@StringRes val labelRes: Int)
6 |
--------------------------------------------------------------------------------
/app/src/main/java/tk/zwander/rootactivitylauncher/data/ExtraInfo.kt:
--------------------------------------------------------------------------------
1 | package tk.zwander.rootactivitylauncher.data
2 |
3 | import android.content.ComponentName
4 | import android.content.Intent
5 | import android.os.Parcelable
6 | import androidx.core.net.toUri
7 | import kotlinx.parcelize.Parcelize
8 | import tk.zwander.rootactivitylauncher.R
9 |
10 | @Parcelize
11 | data class ExtraInfo(
12 | var key: String,
13 | var value: String,
14 | var type: ExtraType? = ExtraType.STRING
15 | ) : Parcelable {
16 | val safeType: ExtraType
17 | get() = type ?: ExtraType.STRING
18 | }
19 |
20 | enum class ExtraType(val value: String, val nameRes: Int) {
21 | INTEGER("integer", R.string.integer) {
22 | override val shellArgName: String
23 | get() = "ei"
24 |
25 | override fun putExtra(intent: Intent, key: String, value: String) {
26 | intent.putExtra(key, value.toIntOrNull())
27 | }
28 | },
29 | BOOLEAN("boolean", R.string.bool) {
30 | override val shellArgName: String
31 | get() = "ez"
32 |
33 | override fun putExtra(intent: Intent, key: String, value: String) {
34 | intent.putExtra(key, value.toBooleanStrictOrNull())
35 | }
36 | },
37 | STRING("string", R.string.str) {
38 | override val shellArgName: String
39 | get() = "es"
40 |
41 | override fun putExtra(intent: Intent, key: String, value: String) {
42 | intent.putExtra(key, value)
43 | }
44 | },
45 | BYTE("byte", R.string.byte_name) {
46 | override val shellArgName: String
47 | get() = "ei"
48 |
49 | override fun putExtra(intent: Intent, key: String, value: String) {
50 | intent.putExtra(key, value.toByteOrNull())
51 | }
52 | },
53 | CHAR("char", R.string.char_name) {
54 | override val shellArgName: String
55 | get() = "ei"
56 |
57 | override fun putExtra(intent: Intent, key: String, value: String) {
58 | intent.putExtra(key, value.toIntOrNull())
59 | }
60 | },
61 | LONG("long", R.string.long_name) {
62 | override val shellArgName: String
63 | get() = "el"
64 |
65 | override fun putExtra(intent: Intent, key: String, value: String) {
66 | intent.putExtra(key, value.toLongOrNull())
67 | }
68 | },
69 | FLOAT("float", R.string.float_name) {
70 | override val shellArgName: String
71 | get() = "ef"
72 |
73 | override fun putExtra(intent: Intent, key: String, value: String) {
74 | intent.putExtra(key, value.toFloatOrNull())
75 | }
76 | },
77 | DOUBLE("double", R.string.double_name) {
78 | override val shellArgName: String
79 | get() = "ed"
80 |
81 | override fun putExtra(intent: Intent, key: String, value: String) {
82 | intent.putExtra(key, value.toDoubleOrNull())
83 | }
84 | },
85 | SHORT("short", R.string.short_name) {
86 | override val shellArgName: String
87 | get() = "ei"
88 |
89 | override fun putExtra(intent: Intent, key: String, value: String) {
90 | intent.putExtra(key, value.toShortOrNull())
91 | }
92 | },
93 | INT_LIST("int_list", R.string.int_al) {
94 | override val shellArgName: String
95 | get() = "eial"
96 |
97 | override fun putExtra(intent: Intent, key: String, value: String) {
98 | intent.putExtra(key, ArrayList(value.split(",").mapNotNull { it.toIntOrNull() }))
99 | }
100 | },
101 | STRING_LIST("string_list", R.string.string_al) {
102 | override val shellArgName: String
103 | get() = "esal"
104 |
105 | override fun putExtra(intent: Intent, key: String, value: String) {
106 | intent.putExtra(
107 | key,
108 | ArrayList(value.split(Regex("(?>
47 | get() = gson.fromJson(
48 | prefs.getString(KEY_EXTRAS, ""),
49 | object : TypeToken>>() {}.type
50 | ) ?: HashMap()
51 | set(value) {
52 | prefs.edit {
53 | putString(
54 | KEY_EXTRAS,
55 | gson.toJson(value)
56 | )
57 | }
58 | }
59 | var actions: HashMap
60 | get() = gson.fromJson(
61 | prefs.getString(KEY_ACTIONS, ""),
62 | object : TypeToken>() {}.type
63 | ) ?: HashMap()
64 | set(value) {
65 | prefs.edit {
66 | putString(
67 | KEY_ACTIONS,
68 | gson.toJson(value)
69 | )
70 | }
71 | }
72 | var datas: HashMap
73 | get() = gson.fromJson(
74 | prefs.getString(KEY_DATAS, ""),
75 | object : TypeToken>() {}.type
76 | ) ?: HashMap()
77 | set(value) {
78 | prefs.edit {
79 | putString(
80 | KEY_DATAS,
81 | gson.toJson(value)
82 | )
83 | }
84 | }
85 | var categories: HashMap>
86 | get() = gson.fromJson(
87 | prefs.getString(KEY_CATEGORIES, ""),
88 | object : TypeToken>>() {}.type
89 | ) ?: HashMap()
90 | set(value) {
91 | prefs.edit {
92 | putString(
93 | KEY_CATEGORIES,
94 | gson.toJson(value)
95 | )
96 | }
97 | }
98 |
99 | val favoriteActivities: Flow>
100 | get() = store.data.map { ArrayList(it[KEY_FAVORITE_ACTIVITIES] ?: setOf()) }
101 | val favoriteReceivers: Flow>
102 | get() = store.data.map { ArrayList(it[KEY_FAVORITE_RECEIVERS] ?: setOf()) }
103 | val favoriteServices: Flow>
104 | get() = store.data.map { ArrayList(it[KEY_FAVORITE_SERVICES] ?: setOf()) }
105 |
106 | suspend fun updateFavoriteActivities(activities: List) {
107 | updateFavorites(KEY_FAVORITE_ACTIVITIES, activities)
108 | }
109 |
110 | suspend fun updateFavoriteReceivers(receivers: List) {
111 | updateFavorites(KEY_FAVORITE_RECEIVERS, receivers)
112 | }
113 |
114 | suspend fun updateFavoriteServices(services: List) {
115 | updateFavorites(KEY_FAVORITE_SERVICES, services)
116 | }
117 |
118 | private suspend fun updateFavorites(key: Preferences.Key>, items: List) {
119 | store.edit {
120 | it[key] = items.toSet()
121 | }
122 | }
123 | }
--------------------------------------------------------------------------------
/app/src/main/java/tk/zwander/rootactivitylauncher/data/Query.kt:
--------------------------------------------------------------------------------
1 | package tk.zwander.rootactivitylauncher.data
2 |
3 | import android.content.Context
4 | import tk.zwander.rootactivitylauncher.data.component.BaseComponentInfo
5 |
6 | sealed class Query(val raw: String) {
7 | val isBlank: Boolean
8 | get() = raw.isBlank()
9 |
10 | abstract fun checkMatch(context: Context, data: BaseComponentInfo, advancedMatch: Boolean): Boolean
11 |
12 | data class StringQuery(val query: String) : Query(query) {
13 | override fun checkMatch(context: Context, data: BaseComponentInfo, advancedMatch: Boolean): Boolean {
14 | return data.info.name.contains(query, true)
15 | || (data.label.contains(query, true))
16 | || advancedMatch
17 | }
18 | }
19 |
20 | data class RegexQuery(val query: Regex) : Query(query.pattern) {
21 | override fun checkMatch(
22 | context: Context,
23 | data: BaseComponentInfo,
24 | advancedMatch: Boolean
25 | ): Boolean {
26 | return query.run {
27 | containsMatchIn(data.info.name)
28 | || containsMatchIn(data.label)
29 | } || advancedMatch
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/app/src/main/java/tk/zwander/rootactivitylauncher/data/SortMode.kt:
--------------------------------------------------------------------------------
1 | package tk.zwander.rootactivitylauncher.data
2 |
3 | import tk.zwander.rootactivitylauncher.R
4 |
5 | sealed class SortMode(labelRes: Int) : BaseOption(labelRes) {
6 | data object SortByName : SortMode(R.string.sort_by_name)
7 | data object SortByInstalledDate : SortMode(R.string.sort_by_installed)
8 | data object SortByUpdatedDate : SortMode(R.string.sort_by_updated)
9 | }
--------------------------------------------------------------------------------
/app/src/main/java/tk/zwander/rootactivitylauncher/data/SortOrder.kt:
--------------------------------------------------------------------------------
1 | package tk.zwander.rootactivitylauncher.data
2 |
3 | import tk.zwander.rootactivitylauncher.R
4 |
5 | sealed class SortOrder(labelRes: Int) : BaseOption(labelRes) {
6 | abstract fun applyOrder(input: Int): Int
7 |
8 | data object Ascending : SortOrder(R.string.sort_ascending) {
9 | override fun applyOrder(input: Int): Int {
10 | return input
11 | }
12 | }
13 | data object Descending : SortOrder(R.string.sort_descending) {
14 | override fun applyOrder(input: Int): Int {
15 | return -input
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/app/src/main/java/tk/zwander/rootactivitylauncher/data/component/ActivityInfo.kt:
--------------------------------------------------------------------------------
1 | package tk.zwander.rootactivitylauncher.data.component
2 |
3 | import android.content.pm.ActivityInfo
4 |
5 | data class ActivityInfo(
6 | override val info: ActivityInfo,
7 | override val label: CharSequence
8 | ) : BaseComponentInfo(info, label)
--------------------------------------------------------------------------------
/app/src/main/java/tk/zwander/rootactivitylauncher/data/component/Availability.kt:
--------------------------------------------------------------------------------
1 | package tk.zwander.rootactivitylauncher.data.component
2 |
3 | import tk.zwander.rootactivitylauncher.R
4 |
5 | enum class Availability(val labelRes: Int, val tintRes: Int) {
6 | EXPORTED(R.string.exported, R.color.colorExported),
7 | PERMISSION_REQUIRED(R.string.permission_required, R.color.colorNeedsPermission),
8 | UNEXPORTED(R.string.unexported, R.color.colorUnexported),
9 | NA(0, 0)
10 | }
--------------------------------------------------------------------------------
/app/src/main/java/tk/zwander/rootactivitylauncher/data/component/BaseComponentInfo.kt:
--------------------------------------------------------------------------------
1 | package tk.zwander.rootactivitylauncher.data.component
2 |
3 | import android.content.ComponentName
4 | import android.content.pm.ComponentInfo
5 | import tk.zwander.rootactivitylauncher.util.constructComponentKey
6 | import java.util.Objects
7 |
8 | sealed class BaseComponentInfo(
9 | open val info: ComponentInfo,
10 | open val label: CharSequence
11 | ) : Comparable {
12 | val component by lazy { ComponentName(info.packageName, info.name) }
13 |
14 | fun type(): ComponentType {
15 | return when(this) {
16 | is ActivityInfo -> ComponentType.ACTIVITY
17 | is ReceiverInfo -> ComponentType.RECEIVER
18 | is ServiceInfo -> ComponentType.SERVICE
19 | }
20 | }
21 |
22 | override fun compareTo(other: BaseComponentInfo): Int {
23 | return label.toString().compareTo(other.label.toString(), true).run {
24 | if (this == 0) constructComponentKey(info).compareTo(constructComponentKey(other.info), true)
25 | else this
26 | }
27 | }
28 |
29 | override fun equals(other: Any?): Boolean {
30 | return other is BaseComponentInfo
31 | && component == other.component
32 | && label == other.label
33 | }
34 |
35 | override fun hashCode(): Int {
36 | return Objects.hash(component, label)
37 | }
38 | }
--------------------------------------------------------------------------------
/app/src/main/java/tk/zwander/rootactivitylauncher/data/component/ComponentType.kt:
--------------------------------------------------------------------------------
1 | package tk.zwander.rootactivitylauncher.data.component
2 |
3 | enum class ComponentType {
4 | ACTIVITY,
5 | SERVICE,
6 | RECEIVER;
7 |
8 | fun serialize(): String = name
9 |
10 | companion object {
11 | fun deserialize(name: String?) = if (name != null) valueOf(name) else null
12 | }
13 | }
--------------------------------------------------------------------------------
/app/src/main/java/tk/zwander/rootactivitylauncher/data/component/ReceiverInfo.kt:
--------------------------------------------------------------------------------
1 | package tk.zwander.rootactivitylauncher.data.component
2 |
3 | import android.content.pm.ActivityInfo
4 |
5 | data class ReceiverInfo(
6 | override val info: ActivityInfo,
7 | override val label: CharSequence
8 | ) : BaseComponentInfo(info, label)
--------------------------------------------------------------------------------
/app/src/main/java/tk/zwander/rootactivitylauncher/data/component/ServiceInfo.kt:
--------------------------------------------------------------------------------
1 | package tk.zwander.rootactivitylauncher.data.component
2 |
3 | import android.content.pm.ServiceInfo
4 |
5 | data class ServiceInfo(
6 | override val info: ServiceInfo,
7 | override val label: CharSequence
8 | ) : BaseComponentInfo(info, label)
--------------------------------------------------------------------------------
/app/src/main/java/tk/zwander/rootactivitylauncher/data/model/AppModel.kt:
--------------------------------------------------------------------------------
1 | package tk.zwander.rootactivitylauncher.data.model
2 |
3 | import android.content.Context
4 | import android.content.pm.ApplicationInfo
5 | import android.content.pm.PackageInfo
6 | import kotlinx.coroutines.CoroutineScope
7 | import kotlinx.coroutines.flow.MutableStateFlow
8 | import tk.zwander.rootactivitylauncher.data.component.ActivityInfo
9 | import tk.zwander.rootactivitylauncher.data.component.ReceiverInfo
10 | import tk.zwander.rootactivitylauncher.data.component.ServiceInfo
11 | import java.util.Objects
12 |
13 | data class AppModel(
14 | val pInfo: PackageInfo,
15 | val info: ApplicationInfo? = pInfo.applicationInfo,
16 | val label: CharSequence,
17 | override val mainModel: MainModel,
18 | override val scope: CoroutineScope,
19 | override val context: Context,
20 | ) : BaseInfoModel(context, scope, mainModel) {
21 | override val initialActivitiesSize = MutableStateFlow(pInfo.activities?.size ?: 0)
22 | override val initialServicesSize = MutableStateFlow(pInfo.services?.size ?: 0)
23 | override val initialReceiversSize = MutableStateFlow(pInfo.receivers?.size ?: 0)
24 |
25 | override fun equals(other: Any?): Boolean {
26 | return other is AppModel
27 | && pInfo.packageName == other.pInfo.packageName
28 | && super.equals(other)
29 | && activitiesSize.value == other.activitiesSize.value
30 | && servicesSize.value == other.servicesSize.value
31 | && receiversSize.value == other.receiversSize.value
32 | && filteredActivities.value == other.filteredActivities.value
33 | && filteredServices.value == other.filteredServices.value
34 | && filteredReceivers.value == other.filteredReceivers.value
35 | }
36 |
37 | override fun hashCode(): Int {
38 | return info?.packageName.hashCode() +
39 | 31 * super.hashCode() +
40 | Objects.hash(
41 | activitiesSize.value,
42 | servicesSize.value,
43 | receiversSize.value,
44 | filteredActivities.value,
45 | filteredServices.value,
46 | filteredReceivers.value
47 | )
48 | }
49 |
50 | override suspend fun performActivityLoad(progress: (suspend () -> Unit)?): Collection {
51 | return pInfo.activities.loadItems(
52 | progress = progress
53 | ) { input, label -> ActivityInfo(input, label.ifBlank { this.label }) }
54 | .toSortedSet()
55 | }
56 |
57 | override suspend fun performServiceLoad(progress: (suspend () -> Unit)?): Collection {
58 | return pInfo.services.loadItems(
59 | progress = progress
60 | ) { input, label -> ServiceInfo(input, label.ifBlank { this.label }) }
61 | .toSortedSet()
62 | }
63 |
64 | override suspend fun performReceiverLoad(progress: (suspend () -> Unit)?): Collection {
65 | return pInfo.receivers.loadItems(
66 | progress = progress
67 | ) { input, label -> ReceiverInfo(input, label.ifBlank { this.label }) }
68 | .toSortedSet()
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/app/src/main/java/tk/zwander/rootactivitylauncher/data/model/ExtrasDialogModel.kt:
--------------------------------------------------------------------------------
1 | package tk.zwander.rootactivitylauncher.data.model
2 |
3 | import android.content.Context
4 | import kotlinx.coroutines.flow.MutableStateFlow
5 | import tk.zwander.rootactivitylauncher.data.ExtraInfo
6 | import tk.zwander.rootactivitylauncher.util.findActionForComponent
7 | import tk.zwander.rootactivitylauncher.util.findCategoriesForComponent
8 | import tk.zwander.rootactivitylauncher.util.findDataForComponent
9 | import tk.zwander.rootactivitylauncher.util.findExtrasForComponent
10 | import java.util.UUID
11 |
12 | class ExtrasDialogModel(private val context: Context, private val componentKey: String) {
13 | val extras = MutableStateFlow>>(
14 | ArrayList>().apply {
15 | addAll(context.findExtrasForComponent(componentKey).map {
16 | UUID.randomUUID() to it
17 | })
18 |
19 | add(UUID.randomUUID() to ExtraInfo("", ""))
20 | }
21 | )
22 |
23 | val categories = MutableStateFlow>>(
24 | ArrayList>().apply {
25 | addAll(context.findCategoriesForComponent(componentKey).map {
26 | UUID.randomUUID() to it
27 | })
28 |
29 | add(UUID.randomUUID() to "")
30 | }
31 | )
32 |
33 | val action = MutableStateFlow(context.findActionForComponent(componentKey))
34 | val data = MutableStateFlow(context.findDataForComponent(componentKey))
35 | }
--------------------------------------------------------------------------------
/app/src/main/java/tk/zwander/rootactivitylauncher/data/model/FavoriteModel.kt:
--------------------------------------------------------------------------------
1 | package tk.zwander.rootactivitylauncher.data.model
2 |
3 | import android.annotation.SuppressLint
4 | import android.content.ComponentName
5 | import android.content.Context
6 | import android.content.pm.PackageItemInfo
7 | import android.content.pm.PackageManager
8 | import android.content.pm.PackageManager.NameNotFoundException
9 | import kotlinx.coroutines.CoroutineScope
10 | import kotlinx.coroutines.flow.Flow
11 | import kotlinx.coroutines.flow.MutableStateFlow
12 | import kotlinx.coroutines.flow.first
13 | import kotlinx.coroutines.launch
14 | import tk.zwander.rootactivitylauncher.data.component.ActivityInfo
15 | import tk.zwander.rootactivitylauncher.data.component.ReceiverInfo
16 | import tk.zwander.rootactivitylauncher.data.component.ServiceInfo
17 | import tk.zwander.rootactivitylauncher.util.getActivityInfoCompat
18 | import tk.zwander.rootactivitylauncher.util.getReceiverInfoCompat
19 | import tk.zwander.rootactivitylauncher.util.getServiceInfoCompat
20 |
21 | data class FavoriteModel(
22 | val activityKeys: Flow>,
23 | val serviceKeys: Flow>,
24 | val receiverKeys: Flow>,
25 | override val context: Context,
26 | override val scope: CoroutineScope,
27 | override val mainModel: MainModel
28 | ) : BaseInfoModel(context, scope, mainModel) {
29 | override val initialActivitiesSize = MutableStateFlow(0)
30 | override val initialServicesSize = MutableStateFlow(0)
31 | override val initialReceiversSize = MutableStateFlow(0)
32 |
33 | init {
34 | scope.launch {
35 | activityKeys.collect {
36 | _loadedActivities.clear()
37 | initialActivitiesSize.value = it.size
38 | hasLoadedActivities.value = false
39 | }
40 | }
41 |
42 | scope.launch {
43 | serviceKeys.collect {
44 | _loadedServices.clear()
45 | initialServicesSize.value = it.size
46 | hasLoadedServices.value = false
47 | }
48 | }
49 |
50 | scope.launch {
51 | receiverKeys.collect {
52 | _loadedReceivers.clear()
53 | initialReceiversSize.value = it.size
54 | hasLoadedReceivers.value = false
55 | }
56 | }
57 | }
58 |
59 | @SuppressLint("InlinedApi")
60 | override suspend fun performActivityLoad(progress: (suspend () -> Unit)?): Collection {
61 | val activityInfos = activityKeys.first().mapNotNull { key ->
62 | context.getInfoFromKey(key) {
63 | packageManager.getActivityInfoCompat(it, PackageManager.MATCH_DISABLED_COMPONENTS)
64 | }
65 | }.toTypedArray()
66 |
67 | return activityInfos.loadItems(progress) { input, label ->
68 | ActivityInfo(input, label.ifBlank {
69 | packageManager.getApplicationLabel(input.applicationInfo)
70 | })
71 | }.sortedBy { it.label.toString().lowercase() }
72 | }
73 |
74 | @SuppressLint("InlinedApi")
75 | override suspend fun performServiceLoad(progress: (suspend () -> Unit)?): Collection {
76 | val activityInfos = serviceKeys.first().mapNotNull { key ->
77 | context.getInfoFromKey(key) {
78 | packageManager.getServiceInfoCompat(it, PackageManager.MATCH_DISABLED_COMPONENTS)
79 | }
80 | }.toTypedArray()
81 |
82 | return activityInfos.loadItems(progress) { input, label ->
83 | ServiceInfo(input, label.ifBlank {
84 | packageManager.getApplicationLabel(input.applicationInfo)
85 | })
86 | }.sortedBy { it.label.toString().lowercase() }
87 | }
88 |
89 | @SuppressLint("InlinedApi")
90 | override suspend fun performReceiverLoad(progress: (suspend () -> Unit)?): Collection {
91 | val activityInfos = receiverKeys.first().mapNotNull { key ->
92 | context.getInfoFromKey(key) {
93 | packageManager.getReceiverInfoCompat(it, PackageManager.MATCH_DISABLED_COMPONENTS)
94 | }
95 | }.toTypedArray()
96 |
97 | return activityInfos.loadItems(progress) { input, label ->
98 | ReceiverInfo(input, label.ifBlank {
99 | packageManager.getApplicationLabel(input.applicationInfo)
100 | })
101 | }.sortedBy { it.label.toString().lowercase() }
102 | }
103 |
104 | private fun Context.getInfoFromKey(key: String, getter: Context.(ComponentName) -> T): T? {
105 | val componentName = ComponentName.unflattenFromString(key)!!
106 |
107 | return try {
108 | getter(componentName)
109 | } catch (e: NameNotFoundException) {
110 | if (componentName.className.contains(".")) {
111 | val lastIndex = key.lastIndexOf(".")
112 | getInfoFromKey(key.replaceRange(lastIndex..lastIndex, "$"), getter)
113 | } else {
114 | null
115 | }
116 | }
117 | }
118 | }
--------------------------------------------------------------------------------
/app/src/main/java/tk/zwander/rootactivitylauncher/data/tasker/TaskerLaunchComponentInfo.kt:
--------------------------------------------------------------------------------
1 | package tk.zwander.rootactivitylauncher.data.tasker
2 |
3 | import com.joaomgcd.taskerpluginlibrary.input.TaskerInputField
4 | import com.joaomgcd.taskerpluginlibrary.input.TaskerInputRoot
5 |
6 | @TaskerInputRoot
7 | class TaskerLaunchComponentInfo @JvmOverloads constructor(
8 | @field:TaskerInputField("type") var type: String? = null,
9 | @field:TaskerInputField("component") var component: String? = null
10 | )
--------------------------------------------------------------------------------
/app/src/main/java/tk/zwander/rootactivitylauncher/util/AdvancedSearcher.kt:
--------------------------------------------------------------------------------
1 | package tk.zwander.rootactivitylauncher.util
2 |
3 | import android.content.pm.ActivityInfo
4 | import android.content.pm.ComponentInfo
5 | import android.content.pm.ServiceInfo
6 | import tk.zwander.rootactivitylauncher.data.model.AppModel
7 |
8 | object AdvancedSearcher {
9 | enum class LogicMode {
10 | AND,
11 | OR;
12 |
13 | companion object {
14 | fun fromString(source: String, def: LogicMode): LogicMode {
15 | return entries.find {
16 | it.name.lowercase() == source.lowercase()
17 | } ?: def
18 | }
19 | }
20 | }
21 |
22 | private const val MODE_DELIMITER = ";"
23 | private const val ITEM_DELIMITER = ","
24 | private const val EXP_DELIMITER = " "
25 | private const val TYPE_DELIMITER = ":"
26 |
27 | private const val HAS_PERMISSION = "has-permission"
28 | private const val REQUIRES_PERMISSION = "requires-permission"
29 | private const val DECLARES_PERMISSION = "declares-permission"
30 | private const val REQUIRES_FEATURE = "requires-feature"
31 |
32 | /**
33 | * Expects a [query] string in a format like:
34 | * ` [type]:[AND;|OR;]item1,item2,item3,... `.
35 | *
36 | * If neither AND; nor OR; are found, the default mode is [LogicMode.AND].
37 | */
38 | private fun itemMatch(
39 | query: String,
40 | type: String,
41 | checker: (LogicMode, List) -> Boolean
42 | ): Boolean {
43 | if (!query.contains("$type$TYPE_DELIMITER")) return false
44 |
45 | val i = query.substringAfter("$type$TYPE_DELIMITER").substringBefore(EXP_DELIMITER)
46 | val (mode, items) = try {
47 | if (i.contains(MODE_DELIMITER)) {
48 | i.split(MODE_DELIMITER).run {
49 | LogicMode.fromString(this[0], LogicMode.AND) to
50 | this[1].split(ITEM_DELIMITER)
51 | }
52 | } else {
53 | LogicMode.AND to i.split(ITEM_DELIMITER)
54 | }
55 | } catch (_: Exception) {
56 | return false
57 | }
58 |
59 | return checker(mode, items)
60 | }
61 |
62 | private fun logicMatch(mode: LogicMode, items: List, availableItems: List): Boolean {
63 | return if (mode == LogicMode.OR) {
64 | items.any { availableItems.contains(it) }
65 | } else {
66 | items.all { availableItems.contains(it) }
67 | }
68 | }
69 |
70 | fun matchesHasPermission(query: String, data: AppModel): Boolean {
71 | return itemMatch(query, HAS_PERMISSION) { mode, items ->
72 | val p = data.pInfo.requestedPermissions ?: return@itemMatch false
73 |
74 | logicMatch(mode, items, p.toList())
75 | }
76 | }
77 |
78 | fun matchesRequiresPermission(query: String, data: AppModel): Boolean {
79 | return matchesRequiresPermission(query, data.info?.permission)
80 | }
81 |
82 | fun matchesRequiresPermission(query: String, componentInfo: ComponentInfo): Boolean {
83 | val permission = when (componentInfo) {
84 | is ActivityInfo -> componentInfo.permission
85 | is ServiceInfo -> componentInfo.permission
86 | else -> null
87 | }
88 |
89 | return matchesRequiresPermission(query, permission)
90 | }
91 |
92 | private fun matchesRequiresPermission(query: String, requiredPermission: String?): Boolean {
93 | return itemMatch(query, REQUIRES_PERMISSION) { _, items ->
94 | // Always an OR check, so we don't care about any mode given.
95 | items.contains(requiredPermission)
96 | }
97 | }
98 |
99 | fun matchesDeclaresPermission(query: String, data: AppModel): Boolean {
100 | return itemMatch(query, DECLARES_PERMISSION) { mode, items ->
101 | val declaredPermissions = data.pInfo.permissions?.map { it.name } ?: return@itemMatch false
102 |
103 | logicMatch(mode, items, declaredPermissions)
104 | }
105 | }
106 |
107 | fun matchesRequiresFeature(query: String, data: AppModel): Boolean {
108 | return itemMatch(query, REQUIRES_FEATURE) { mode, items ->
109 | val requiredFeatures = data.pInfo.reqFeatures?.map { it.name } ?: return@itemMatch false
110 |
111 | logicMatch(mode, items, requiredFeatures)
112 | }
113 | }
114 | }
--------------------------------------------------------------------------------
/app/src/main/java/tk/zwander/rootactivitylauncher/util/CollectionUtils.kt:
--------------------------------------------------------------------------------
1 | package tk.zwander.rootactivitylauncher.util
2 |
3 | import android.util.SparseArray
4 | import androidx.core.util.forEach
5 | import kotlinx.coroutines.CoroutineScope
6 | import kotlinx.coroutines.Deferred
7 | import kotlinx.coroutines.async
8 | import kotlinx.coroutines.awaitAll
9 | import tk.zwander.rootactivitylauncher.data.model.AppModel
10 | import tk.zwander.rootactivitylauncher.data.model.BaseInfoModel
11 | import kotlin.coroutines.CoroutineContext
12 | import kotlin.coroutines.EmptyCoroutineContext
13 |
14 | fun SparseArray.map(transform: (T) -> R): List {
15 | return mapTo(ArrayList(), transform)
16 | }
17 |
18 | fun > SparseArray.mapTo(destination: C, transform: (T) -> R): C {
19 | forEach { _, any ->
20 | destination.add(transform(any))
21 | }
22 |
23 | return destination
24 | }
25 |
26 | suspend fun Collection.forEachParallel(context: CoroutineContext = EmptyCoroutineContext, scope: CoroutineScope, block: suspend CoroutineScope.(T) -> Unit) {
27 | val jobs = ArrayList>(size)
28 | forEach {
29 | jobs.add(
30 | scope.async(context) {
31 | block(it)
32 | }
33 | )
34 | }
35 | jobs.awaitAll()
36 | }
37 |
38 | suspend fun Array.forEachParallel(context: CoroutineContext = EmptyCoroutineContext, scope: CoroutineScope, block: suspend CoroutineScope.(T) -> Unit) {
39 | val jobs = ArrayList>(size)
40 | forEach {
41 | jobs.add(
42 | scope.async(context) {
43 | block(it)
44 | }
45 | )
46 | }
47 | jobs.awaitAll()
48 | }
49 |
50 | fun Collection.distinctByPackageName(): List {
51 | return distinctBy {
52 | if (it is AppModel) it.pInfo.packageName else "favorite"
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/app/src/main/java/tk/zwander/rootactivitylauncher/util/ComponentUtils.kt:
--------------------------------------------------------------------------------
1 | package tk.zwander.rootactivitylauncher.util
2 |
3 | import android.annotation.SuppressLint
4 | import android.content.ComponentName
5 | import android.content.Context
6 | import android.content.Intent
7 | import android.content.pm.ActivityInfo
8 | import android.content.pm.ApplicationInfo
9 | import android.content.pm.ComponentInfo
10 | import android.content.pm.PackageItemInfo
11 | import android.content.pm.PackageManager
12 | import android.os.Build
13 | import android.provider.Settings
14 | import androidx.core.net.toUri
15 | import tk.zwander.rootactivitylauncher.data.ExtraInfo
16 | import tk.zwander.rootactivitylauncher.data.prefs
17 |
18 | fun determineComponentNamePackage(componentName: String): String {
19 | val component = ComponentName.unflattenFromString(componentName)
20 |
21 | return component?.packageName ?: componentName
22 | }
23 |
24 | fun Context.findExtrasForComponent(componentName: String): List {
25 | val extras = ArrayList()
26 |
27 | prefs.extras[componentName]?.let { extras.addAll(it) }
28 |
29 | return extras
30 | }
31 |
32 | fun Context.updateExtrasForComponent(componentName: String, extras: List) {
33 | val map = prefs.extras
34 |
35 | map[componentName] = extras
36 | prefs.extras = map
37 | }
38 |
39 | fun Context.findActionForComponent(componentName: String): String {
40 | val pkg = determineComponentNamePackage(componentName)
41 |
42 | return prefs.actions[componentName] ?: (prefs.actions[pkg] ?: Intent.ACTION_MAIN)
43 | }
44 |
45 | fun Context.updateActionForComponent(componentName: String, action: String?) {
46 | val map = prefs.actions
47 |
48 | map[componentName] = if (action.isNullOrBlank()) null else action
49 | prefs.actions = map
50 | }
51 |
52 | fun Context.findDataForComponent(componentName: String): String? {
53 | val pkg = determineComponentNamePackage(componentName)
54 |
55 | return prefs.datas[componentName] ?: prefs.datas[pkg]
56 | }
57 |
58 | fun Context.updateDataForComponent(componentName: String, data: String?) {
59 | val map = prefs.datas
60 |
61 | map[componentName] = if (data.isNullOrBlank()) null else data
62 | prefs.datas = map
63 | }
64 |
65 | fun Context.findCategoriesForComponent(componentName: String): MutableList {
66 | val pkg = determineComponentNamePackage(componentName)
67 |
68 | return prefs.categories[componentName] ?: prefs.categories[pkg] ?: mutableListOf()
69 | }
70 |
71 | fun Context.updateCategoriesForComponent(componentName: String, categories: List) {
72 | val filtered = categories.filterNot { it.isNullOrBlank() }
73 | val map = prefs.categories
74 |
75 | map[componentName] = ArrayList(filtered)
76 | prefs.categories = map
77 | }
78 |
79 | fun constructComponentKey(component: PackageItemInfo): String {
80 | return constructComponentKey(component.packageName, component.name)
81 | }
82 |
83 | fun constructComponentKey(packageName: String, componentName: String): String {
84 | return "$packageName/$componentName"
85 | }
86 |
87 | fun Context.openAppInfo(packageName: String) {
88 | val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
89 | intent.data = "package:$packageName".toUri()
90 |
91 | try {
92 | startActivity(intent)
93 | } catch (_: Exception) {}
94 | }
95 |
96 | fun ActivityInfo.persistableModeToString(): String {
97 | return when (persistableMode) {
98 | ActivityInfo.PERSIST_ROOT_ONLY -> "PERSIST_ROOT_ONLY"
99 | ActivityInfo.PERSIST_NEVER -> "PERSIST_NEVER"
100 | ActivityInfo.PERSIST_ACROSS_REBOOTS -> "PERSIST_ACROSS_REBOOTS"
101 | else -> "UNKNOWN=$persistableMode"
102 | }
103 | }
104 |
105 | val ActivityInfo.manifestMinAspectRatioCompat: Float
106 | @SuppressLint("PrivateApi")
107 | get() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
108 | manifestMinAspectRatio
109 | } else {
110 | ActivityInfo::class.java
111 | .getDeclaredField("mMinAspectRatio")
112 | .apply { isAccessible = true }
113 | .getFloat(this)
114 | }
115 |
116 | val ActivityInfo.rMaxAspectRatio: Float
117 | @SuppressLint("PrivateApi")
118 | get() = ActivityInfo::class.java
119 | .getDeclaredField("mMaxAspectRatio")
120 | .apply { isAccessible = true }
121 | .getFloat(this)
122 |
123 | fun ComponentInfo.isActuallyEnabled(context: Context): Boolean {
124 | return applicationInfo.isActuallyEnabled(context) && try {
125 | checkEnabledSetting(
126 | context.packageManager.getComponentEnabledSetting(safeComponentName),
127 | enabled
128 | )
129 | } catch (_: Exception) {
130 | enabled
131 | }
132 | }
133 |
134 | fun ApplicationInfo.isActuallyEnabled(context: Context): Boolean {
135 | return try {
136 | checkEnabledSetting(
137 | context.packageManager.getApplicationEnabledSetting(packageName),
138 | enabled
139 | )
140 | } catch (_: Exception) {
141 | enabled
142 | }
143 | }
144 |
145 | fun ApplicationInfo.isSystemAppCompat(): Boolean {
146 | return (flags and ApplicationInfo.FLAG_SYSTEM) != 0
147 | }
148 |
149 | private fun checkEnabledSetting(setting: Int, default: Boolean): Boolean {
150 | return when (setting) {
151 | PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
152 | PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED -> true
153 | PackageManager.COMPONENT_ENABLED_STATE_DEFAULT -> default
154 | PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
155 | PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER -> false
156 | else -> false
157 | }
158 | }
159 |
160 | val ComponentInfo.safeComponentName: ComponentName
161 | get() = ComponentName(packageName, name)
162 |
--------------------------------------------------------------------------------
/app/src/main/java/tk/zwander/rootactivitylauncher/util/CompositionLocals.kt:
--------------------------------------------------------------------------------
1 | package tk.zwander.rootactivitylauncher.util
2 |
3 | import androidx.compose.runtime.compositionLocalOf
4 | import tk.zwander.rootactivitylauncher.data.model.FavoriteModel
5 | import tk.zwander.rootactivitylauncher.data.model.MainModel
6 |
7 | val LocalMainModel = compositionLocalOf { error("No MainModel specified!") }
8 | val LocalFavoriteModel = compositionLocalOf { error("No FavoriteModel specified!") }
9 |
--------------------------------------------------------------------------------
/app/src/main/java/tk/zwander/rootactivitylauncher/util/DhizukuUtils.kt:
--------------------------------------------------------------------------------
1 | package tk.zwander.rootactivitylauncher.util
2 |
3 | import android.content.pm.PackageManager
4 | import com.rosan.dhizuku.api.Dhizuku
5 | import com.rosan.dhizuku.api.DhizukuRequestPermissionListener
6 | import kotlin.coroutines.resume
7 | import kotlin.coroutines.suspendCoroutine
8 |
9 | object DhizukuUtils {
10 | suspend fun requestDhizukuPermission(): Boolean {
11 | return suspendCoroutine { continuation ->
12 | Dhizuku.requestPermission(object : DhizukuRequestPermissionListener() {
13 | override fun onRequestPermission(grantResult: Int) {
14 | continuation.resume(grantResult == PackageManager.PERMISSION_GRANTED)
15 | }
16 | })
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/app/src/main/java/tk/zwander/rootactivitylauncher/util/InfoGetter.kt:
--------------------------------------------------------------------------------
1 | package tk.zwander.rootactivitylauncher.util
2 |
3 | import android.content.Context
4 | import android.os.IBinder
5 | import android.os.UserHandle
6 | import com.rosan.dhizuku.api.Dhizuku
7 | import rikka.shizuku.Shizuku
8 |
9 | interface BinderWrapper {
10 | suspend fun Context.getUidAndPackage(): Pair
11 | suspend fun wrapBinder(binder: IBinder): IBinder
12 | }
13 |
14 | interface ShizukuBinderWrapper : BinderWrapper {
15 | override suspend fun Context.getUidAndPackage(): Pair {
16 | val uid = try {
17 | Shizuku.getUid()
18 | } catch (e: IllegalStateException) {
19 | // It shouldn't be possible for the Shizuku Binder not to exist at this point, but sometimes it doesn't.
20 | // If that happens, just assume user 2000 (shell).
21 | 2000
22 | }
23 |
24 | return UserHandle.getUserId(uid) to packageManager.getPackagesForUid(uid)?.firstOrNull()
25 | }
26 |
27 | override suspend fun wrapBinder(binder: IBinder): IBinder {
28 | return rikka.shizuku.ShizukuBinderWrapper(binder)
29 | }
30 | }
31 |
32 | interface DhizukuBinderWrapper : BinderWrapper {
33 | override suspend fun Context.getUidAndPackage(): Pair {
34 | val packageName = "com.rosan.dhizuku"
35 |
36 | return UserHandle.getUserId(
37 | packageManager.getApplicationInfo(packageName, 0).uid
38 | ) to packageName
39 | }
40 |
41 | override suspend fun wrapBinder(binder: IBinder): IBinder {
42 | return Dhizuku.binderWrapper(binder)
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/app/src/main/java/tk/zwander/rootactivitylauncher/util/PackageUtils.kt:
--------------------------------------------------------------------------------
1 | package tk.zwander.rootactivitylauncher.util
2 |
3 | import android.content.ComponentName
4 | import android.content.Context
5 | import android.content.IntentFilter
6 | import android.content.pm.ActivityInfo
7 | import android.content.pm.PackageInfo
8 | import android.content.pm.PackageManager
9 | import android.content.pm.ServiceInfo
10 | import android.net.Uri
11 | import android.os.Build
12 | import android.os.DeadObjectException
13 | import android.util.Log
14 | import androidx.documentfile.provider.DocumentFile
15 | import tk.zwander.rootactivitylauncher.R
16 | import tk.zwander.rootactivitylauncher.data.model.AppModel
17 | import java.io.File
18 |
19 | // Newer Android versions say these APIs are NonNull, but older versions can return null.
20 |
21 | fun PackageManager.getInstalledPackagesCompat(flags: Int = 0): List {
22 | return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
23 | getInstalledPackages(PackageManager.PackageInfoFlags.of(flags.toLong()))
24 | } else {
25 | getInstalledPackages(flags)
26 | } ?: listOf()
27 | }
28 |
29 | fun PackageManager.getPackageInfoCompat(pkg: String, flags: Int = 0): PackageInfo? {
30 | return try {
31 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
32 | getPackageInfo(pkg, PackageManager.PackageInfoFlags.of(flags.toLong()))
33 | } else {
34 | getPackageInfo(pkg, flags)
35 | }
36 | } catch (_: PackageManager.NameNotFoundException) {
37 | null
38 | } catch (e: RuntimeException) {
39 | if (e.cause is DeadObjectException) {
40 | null
41 | } else {
42 | throw e
43 | }
44 | }
45 | }
46 |
47 | fun Context.extractApk(result: Uri, info: AppModel): List {
48 | val errors = ArrayList()
49 |
50 | val dir = DocumentFile.fromTreeUri(this, result)
51 |
52 | if (dir == null) {
53 | errors.add(Exception("Unable to get file reference for $result."))
54 | }
55 |
56 | if (info.info == null) {
57 | errors.add(Exception(resources.getString(R.string.no_app_info_error)))
58 | return errors
59 | }
60 |
61 | val baseDir = File(info.info.sourceDir)
62 |
63 | val splits = info.info.splitSourceDirs?.mapIndexed { index, s ->
64 | val splitApk = File(s)
65 | val splitName = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
66 | info.info.splitNames?.get(index) ?: splitApk.nameWithoutExtension
67 | } else splitApk.nameWithoutExtension
68 |
69 | splitName to s
70 | }
71 |
72 | val baseFile = dir?.createFile(
73 | "application/vnd.android.package-archive",
74 | info.pInfo.packageName
75 | )
76 |
77 | if (baseFile == null) {
78 | errors.add(Exception("Unable to create file ${info.pInfo.packageName}."))
79 | } else {
80 | try {
81 | contentResolver.openOutputStream(baseFile.uri).use { writer ->
82 | baseDir.inputStream().use { reader ->
83 | reader.copyTo(writer!!)
84 | }
85 | }
86 | } catch (e: Exception) {
87 | Log.e("RootActivityLauncher", "Extraction failed", e)
88 | errors.add(e)
89 | }
90 | }
91 |
92 | splits?.forEach { split ->
93 | val name = split.first
94 | val path = File(split.second)
95 |
96 | val file = dir?.createFile(
97 | "application/vnd.android.package-archive",
98 | "${info.pInfo.packageName}_$name"
99 | )
100 |
101 | if (file == null) {
102 | errors.add(Exception("Unable to create file ${info.pInfo.packageName}_$name"))
103 | } else {
104 | contentResolver.openOutputStream(file.uri).use { writer ->
105 | try {
106 | path.inputStream().use { reader ->
107 | reader.copyTo(writer!!)
108 | }
109 | } catch (e: Exception) {
110 | Log.e("RootActivityLauncher", "Extraction failed", e)
111 | errors.add(e)
112 | }
113 | }
114 | }
115 | }
116 |
117 | return errors
118 | }
119 |
120 | fun PackageManager.getAllIntentFiltersCompat(packageName: String?): List {
121 | if (packageName == null) {
122 | return listOf()
123 | }
124 |
125 | return try {
126 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
127 | getAllIntentFilters(packageName)
128 | } else {
129 | listOf()
130 | }
131 | } catch (_: Throwable) {
132 | listOf()
133 | }
134 | }
135 |
136 | fun PackageManager.getActivityInfoCompat(
137 | componentName: ComponentName,
138 | flags: Int = 0
139 | ): ActivityInfo {
140 | return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
141 | getActivityInfo(componentName, PackageManager.ComponentInfoFlags.of(flags.toLong()))
142 | } else {
143 | getActivityInfo(componentName, flags)
144 | }
145 | }
146 |
147 | fun PackageManager.getServiceInfoCompat(componentName: ComponentName, flags: Int = 0): ServiceInfo {
148 | return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
149 | getServiceInfo(componentName, PackageManager.ComponentInfoFlags.of(flags.toLong()))
150 | } else {
151 | getServiceInfo(componentName, flags)
152 | }
153 | }
154 |
155 | fun PackageManager.getReceiverInfoCompat(
156 | componentName: ComponentName,
157 | flags: Int = 0
158 | ): ActivityInfo {
159 | return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
160 | getReceiverInfo(componentName, PackageManager.ComponentInfoFlags.of(flags.toLong()))
161 | } else {
162 | getReceiverInfo(componentName, flags)
163 | }
164 | }
165 |
--------------------------------------------------------------------------------
/app/src/main/java/tk/zwander/rootactivitylauncher/util/PermissionResultListener.kt:
--------------------------------------------------------------------------------
1 | package tk.zwander.rootactivitylauncher.util
2 |
3 | interface PermissionResultListener {
4 | fun onPermissionResult(granted: Boolean)
5 | }
--------------------------------------------------------------------------------
/app/src/main/java/tk/zwander/rootactivitylauncher/util/PermissionUtils.kt:
--------------------------------------------------------------------------------
1 | package tk.zwander.rootactivitylauncher.util
2 |
3 | import android.content.Context
4 | import android.content.pm.PackageManager
5 | import android.os.Build
6 | import rikka.shizuku.Shizuku
7 | import rikka.shizuku.ShizukuProvider
8 | import kotlin.coroutines.resume
9 | import kotlin.coroutines.suspendCoroutine
10 | import kotlin.random.Random
11 |
12 | val Context.hasShizukuPermission: Boolean
13 | get() {
14 | if (!Shizuku.pingBinder()) {
15 | return false
16 | }
17 |
18 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
19 | return true
20 | }
21 |
22 | return try {
23 | if (Shizuku.isPreV11() || Shizuku.getVersion() < 11) {
24 | checkCallingOrSelfPermission(ShizukuProvider.PERMISSION) == PackageManager.PERMISSION_GRANTED
25 | } else {
26 | Shizuku.checkSelfPermission() == PackageManager.PERMISSION_GRANTED
27 | }
28 | } catch (e: IllegalStateException) {
29 | false
30 | }
31 | }
32 |
33 | suspend fun requestShizukuPermission(): Boolean {
34 | return suspendCoroutine { coroutine ->
35 | requestShizukuPermission { granted ->
36 | coroutine.resume(granted)
37 | }
38 | }
39 | }
40 |
41 | fun requestShizukuPermission(resultListener: (Boolean) -> Unit): Boolean {
42 | return if (Shizuku.isPreV11() || Shizuku.getVersion() < 11) {
43 | false
44 | } else {
45 | val code = Random(System.currentTimeMillis()).nextInt()
46 | val listener = object : Shizuku.OnRequestPermissionResultListener {
47 | override fun onRequestPermissionResult(requestCode: Int, grantResult: Int) {
48 | resultListener(grantResult == PackageManager.PERMISSION_GRANTED)
49 | Shizuku.removeRequestPermissionResultListener(this)
50 | }
51 | }
52 |
53 | try {
54 | Shizuku.addRequestPermissionResultListener(listener)
55 | Shizuku.requestPermission(code)
56 | true
57 | } catch (e: IllegalStateException) {
58 | false
59 | }
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/app/src/main/java/tk/zwander/rootactivitylauncher/util/UIUtils.kt:
--------------------------------------------------------------------------------
1 | package tk.zwander.rootactivitylauncher.util
2 |
3 | import android.content.pm.ApplicationInfo
4 | import android.net.Uri
5 | import androidx.compose.foundation.layout.PaddingValues
6 | import androidx.compose.foundation.layout.calculateEndPadding
7 | import androidx.compose.foundation.layout.calculateStartPadding
8 | import androidx.compose.ui.unit.LayoutDirection
9 | import androidx.core.net.toUri
10 | import com.android.internal.R
11 | import tk.zwander.rootactivitylauncher.data.component.BaseComponentInfo
12 |
13 | fun BaseComponentInfo.getIconResourceId(): Pair {
14 | val res = info.iconResource
15 |
16 | return if (res != 0) {
17 | info.packageName to res
18 | } else {
19 | info.applicationInfo.getIconResourceId()
20 | }
21 | }
22 |
23 | fun ApplicationInfo.getIconResourceId(): Pair {
24 | val res = icon
25 |
26 | return if (res != 0) {
27 | packageName to res
28 | } else {
29 | "android" to R.drawable.sym_def_app_icon
30 | }
31 | }
32 |
33 | fun BaseComponentInfo.getCoilData(): Uri {
34 | val id = getIconResourceId()
35 |
36 | return "android.resource://${id.first}/${id.second}".toUri()
37 | }
38 |
39 | fun ApplicationInfo.getCoilData(): Uri {
40 | val id = getIconResourceId()
41 |
42 | return "android.resource://${id.first}/${id.second}".toUri()
43 | }
44 |
45 | operator fun PaddingValues.plus(other: PaddingValues): PaddingValues = PaddingValues(
46 | start = this.calculateStartPadding(LayoutDirection.Ltr) +
47 | other.calculateStartPadding(LayoutDirection.Ltr),
48 | top = this.calculateTopPadding() + other.calculateTopPadding(),
49 | end = this.calculateEndPadding(LayoutDirection.Ltr) +
50 | other.calculateEndPadding(LayoutDirection.Ltr),
51 | bottom = this.calculateBottomPadding() + other.calculateBottomPadding(),
52 | )
53 |
--------------------------------------------------------------------------------
/app/src/main/java/tk/zwander/rootactivitylauncher/util/Utils.kt:
--------------------------------------------------------------------------------
1 | package tk.zwander.rootactivitylauncher.util
2 |
3 | import android.content.Context
4 | import android.content.Intent
5 | import androidx.core.net.toUri
6 | import kotlinx.atomicfu.AtomicInt
7 | import kotlinx.atomicfu.AtomicLong
8 |
9 | val Int.hexString: String
10 | get() = Integer.toHexString(this)
11 |
12 | fun Context.launchUrl(url: String) {
13 | try {
14 | val browserIntent =
15 | Intent(Intent.ACTION_VIEW, url.toUri())
16 | startActivity(browserIntent)
17 | } catch (_: Exception) {}
18 | }
19 |
20 | val Context.isTouchWiz: Boolean
21 | get() = packageManager.hasSystemFeature("com.samsung.feature.samsung_experience_mobile")
22 |
23 | fun updateProgress(
24 | current: AtomicInt,
25 | lastUpdateTime: AtomicLong,
26 | total: Int,
27 | setter: (newProgress: Float) -> Unit
28 | ) {
29 | val oldCurrent = current.value
30 | val newCurrent = current.incrementAndGet()
31 |
32 | val oldProgress = (oldCurrent / total.toFloat() * 100f).toInt() / 100f
33 | val newProgress = (newCurrent / total.toFloat() * 100f).toInt() / 100f
34 |
35 | val oldUpdateTime = lastUpdateTime.value
36 | val newUpdateTime = System.currentTimeMillis()
37 |
38 | if (newProgress > oldProgress && newUpdateTime > oldUpdateTime) {
39 | lastUpdateTime.value = newUpdateTime
40 |
41 | setter(newProgress)
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/app/src/main/java/tk/zwander/rootactivitylauncher/util/launch/LaunchArgs.kt:
--------------------------------------------------------------------------------
1 | package tk.zwander.rootactivitylauncher.util.launch
2 |
3 | import android.content.Intent
4 | import android.content.IntentFilter
5 | import tk.zwander.rootactivitylauncher.data.ExtraInfo
6 |
7 | data class LaunchArgs(val intent: Intent, val extras: List, val filters: List)
--------------------------------------------------------------------------------
/app/src/main/java/tk/zwander/rootactivitylauncher/util/launch/LaunchUtils.kt:
--------------------------------------------------------------------------------
1 | package tk.zwander.rootactivitylauncher.util.launch
2 |
3 | import android.content.ComponentName
4 | import android.content.Context
5 | import android.content.Intent
6 | import android.util.Log
7 | import androidx.core.net.toUri
8 | import tk.zwander.rootactivitylauncher.R
9 | import tk.zwander.rootactivitylauncher.data.ExtraInfo
10 | import tk.zwander.rootactivitylauncher.data.component.ComponentType
11 | import tk.zwander.rootactivitylauncher.data.prefs
12 | import tk.zwander.rootactivitylauncher.util.findActionForComponent
13 | import tk.zwander.rootactivitylauncher.util.findCategoriesForComponent
14 | import tk.zwander.rootactivitylauncher.util.findDataForComponent
15 | import tk.zwander.rootactivitylauncher.util.getAllIntentFiltersCompat
16 |
17 | private fun Context.createLaunchArgs(extras: List, componentKey: String): LaunchArgs {
18 | val intent = Intent(prefs.findActionForComponent(componentKey))
19 | intent.component = ComponentName.unflattenFromString(componentKey)
20 | intent.data = prefs.findDataForComponent(componentKey)?.toUri()
21 |
22 | val filters = packageManager.getAllIntentFiltersCompat(intent.component?.packageName)
23 |
24 | prefs.findCategoriesForComponent(componentKey).forEach { category ->
25 | intent.addCategory(category)
26 | }
27 |
28 | if (extras.isNotEmpty()) {
29 | extras.forEach { extra ->
30 | extra.safeType.putExtra(intent, extra.key, extra.value)
31 | }
32 | }
33 |
34 | return LaunchArgs(intent, extras, filters)
35 | }
36 |
37 | private suspend inline fun Context.performLaunch(args: LaunchArgs): List> {
38 | val errors = mutableListOf>()
39 |
40 | T::class.sealedSubclasses
41 | .mapNotNull { it.objectInstance?.let { obj -> obj to it } }
42 | .sortedByDescending { (obj, _) -> obj.priority }
43 | .forEach { (obj, clazz) ->
44 | with (obj) {
45 | if (canRun(args)) {
46 | val latestResult = tryLaunch(args)
47 |
48 | Log.e("RootActivityLauncher", "$clazz $latestResult")
49 |
50 | if (latestResult.isEmpty()) {
51 | return listOf()
52 | } else {
53 | errors.addAll(latestResult.map { r -> clazz.simpleName!! to r })
54 | }
55 | }
56 | }
57 | }
58 |
59 | return errors.ifEmpty {
60 | listOf(
61 | "Unknown" to Exception(
62 | resources.getString(
63 | R.string.unknown_launch_error,
64 | args.intent.component?.flattenToString()
65 | )
66 | )
67 | )
68 | }
69 | }
70 |
71 | suspend fun Context.launch(
72 | type: ComponentType,
73 | extras: List,
74 | componentKey: String
75 | ): List> {
76 | val args = createLaunchArgs(extras, componentKey)
77 |
78 | return when (type) {
79 | ComponentType.ACTIVITY -> performLaunch(args)
80 | ComponentType.SERVICE -> performLaunch(args)
81 | ComponentType.RECEIVER -> performLaunch(args)
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/app/src/main/java/tk/zwander/rootactivitylauncher/util/receiver/AdminReceiver.kt:
--------------------------------------------------------------------------------
1 | package tk.zwander.rootactivitylauncher.util.receiver
2 |
3 | import android.app.admin.DeviceAdminReceiver
4 |
5 | class AdminReceiver : DeviceAdminReceiver()
--------------------------------------------------------------------------------
/app/src/main/java/tk/zwander/rootactivitylauncher/util/receiver/KnoxLicenseReceiver.kt:
--------------------------------------------------------------------------------
1 | package tk.zwander.rootactivitylauncher.util.receiver
2 |
3 | import android.content.BroadcastReceiver
4 | import android.content.Context
5 | import android.content.Intent
6 | import android.util.Log
7 |
8 | class KnoxLicenseReceiver : BroadcastReceiver() {
9 | override fun onReceive(context: Context?, intent: Intent) {
10 | Log.e("RootActivityLauncher", "${intent.action}")
11 |
12 | intent.extras?.keySet()?.forEach {
13 | Log.e("RootActivityLauncher", "$it : ${intent.extras?.get(it)}")
14 | }
15 | }
16 | }
--------------------------------------------------------------------------------
/app/src/main/java/tk/zwander/rootactivitylauncher/util/tasker/TaskerLaunchComponentHelper.kt:
--------------------------------------------------------------------------------
1 | package tk.zwander.rootactivitylauncher.util.tasker
2 |
3 | import com.joaomgcd.taskerpluginlibrary.config.TaskerPluginConfig
4 | import com.joaomgcd.taskerpluginlibrary.config.TaskerPluginConfigHelperNoOutput
5 | import tk.zwander.rootactivitylauncher.data.tasker.TaskerLaunchComponentInfo
6 |
7 | class TaskerLaunchComponentHelper(config: TaskerPluginConfig) : TaskerPluginConfigHelperNoOutput(config) {
8 | override val runnerClass = TaskerLaunchComponentRunner::class.java
9 | override val inputClass = TaskerLaunchComponentInfo::class.java
10 | }
--------------------------------------------------------------------------------
/app/src/main/java/tk/zwander/rootactivitylauncher/util/tasker/TaskerLaunchComponentRunner.kt:
--------------------------------------------------------------------------------
1 | package tk.zwander.rootactivitylauncher.util.tasker
2 |
3 | import android.content.ComponentName
4 | import android.content.Context
5 | import com.joaomgcd.taskerpluginlibrary.action.TaskerPluginRunnerActionNoOutput
6 | import com.joaomgcd.taskerpluginlibrary.input.TaskerInput
7 | import com.joaomgcd.taskerpluginlibrary.runner.TaskerPluginResult
8 | import com.joaomgcd.taskerpluginlibrary.runner.TaskerPluginResultError
9 | import com.joaomgcd.taskerpluginlibrary.runner.TaskerPluginResultSucess
10 | import kotlinx.coroutines.runBlocking
11 | import tk.zwander.rootactivitylauncher.data.component.ComponentType
12 | import tk.zwander.rootactivitylauncher.data.tasker.TaskerLaunchComponentInfo
13 | import tk.zwander.rootactivitylauncher.util.findExtrasForComponent
14 | import tk.zwander.rootactivitylauncher.util.launch.launch
15 |
16 | class TaskerLaunchComponentRunner : TaskerPluginRunnerActionNoOutput() {
17 | override fun run(
18 | context: Context,
19 | input: TaskerInput
20 | ): TaskerPluginResult {
21 | val type = input.regular.type
22 | val component = input.regular.component
23 |
24 | if (type == null) {
25 | return TaskerPluginResultError(10, "The component type cannot be null!")
26 | }
27 |
28 | if (component == null) {
29 | return TaskerPluginResultError(11, "The component cannot be null!")
30 | }
31 |
32 | val componentObj = ComponentName.unflattenFromString(component)
33 | val extras =
34 | (componentObj?.let { context.findExtrasForComponent(componentObj.packageName) } ?: listOf()) +
35 | context.findExtrasForComponent(component)
36 | var result: TaskerPluginResult = TaskerPluginResultError(
37 | -1,
38 | "Unable to launch component: $type, $component. You may need root or Shizuku."
39 | )
40 |
41 | runBlocking {
42 | val componentType = try {
43 | ComponentType.valueOf(type)
44 | } catch (_: Exception) {
45 | result = TaskerPluginResultError(12, "Invalid component type $type")
46 | null
47 | }
48 |
49 | if (context.launch(
50 | componentType ?: return@runBlocking,
51 | extras,
52 | component
53 | ).isEmpty()
54 | ) {
55 | result = TaskerPluginResultSucess()
56 | }
57 | }
58 |
59 | return result
60 | }
61 | }
--------------------------------------------------------------------------------
/app/src/main/java/tk/zwander/rootactivitylauncher/views/ScrimView.kt:
--------------------------------------------------------------------------------
1 | package tk.zwander.rootactivitylauncher.views
2 |
3 | import androidx.compose.animation.AnimatedVisibility
4 | import androidx.compose.animation.fadeIn
5 | import androidx.compose.animation.fadeOut
6 | import androidx.compose.foundation.background
7 | import androidx.compose.foundation.clickable
8 | import androidx.compose.foundation.interaction.MutableInteractionSource
9 | import androidx.compose.foundation.layout.Box
10 | import androidx.compose.foundation.layout.fillMaxSize
11 | import androidx.compose.foundation.layout.size
12 | import androidx.compose.material3.MaterialTheme
13 | import androidx.compose.runtime.Composable
14 | import androidx.compose.runtime.LaunchedEffect
15 | import androidx.compose.runtime.getValue
16 | import androidx.compose.runtime.mutableFloatStateOf
17 | import androidx.compose.runtime.remember
18 | import androidx.compose.runtime.setValue
19 | import androidx.compose.ui.Alignment
20 | import androidx.compose.ui.Modifier
21 | import androidx.compose.ui.graphics.Color
22 | import androidx.compose.ui.graphics.toArgb
23 | import androidx.compose.ui.platform.LocalDensity
24 | import androidx.compose.ui.unit.dp
25 | import androidx.compose.ui.viewinterop.AndroidView
26 | import com.hmomeni.progresscircula.ProgressCircula
27 | import kotlin.math.roundToInt
28 |
29 | @Composable
30 | fun ScrimView(
31 | progress: Float?,
32 | modifier: Modifier = Modifier
33 | ) {
34 | var actualProgress by remember {
35 | mutableFloatStateOf(0f)
36 | }
37 |
38 | LaunchedEffect(progress) {
39 | if (progress != null) {
40 | actualProgress = progress
41 | }
42 | }
43 |
44 | AnimatedVisibility(
45 | visible = progress != null,
46 | modifier = modifier,
47 | enter = fadeIn(),
48 | exit = fadeOut()
49 | ) {
50 | Box(
51 | modifier = Modifier
52 | .fillMaxSize()
53 | .background(Color.Black.copy(alpha = 0.5f))
54 | .clickable(
55 | interactionSource = remember {
56 | MutableInteractionSource()
57 | },
58 | indication = null,
59 | ) {},
60 | contentAlignment = Alignment.Center
61 | ) {
62 | val accentColor = MaterialTheme.colorScheme.primary
63 | val textColor = Color.White
64 | val rimWidth = with(LocalDensity.current) {
65 | 8.dp.toPx()
66 | }
67 |
68 | AndroidView(
69 | factory = {
70 | ProgressCircula(context = it).apply {
71 | indeterminate = false
72 | rimColor = accentColor.toArgb()
73 | showProgress = true
74 | speed = 0.5f
75 | this.rimWidth = rimWidth
76 | this.textColor = textColor.toArgb()
77 | }
78 | },
79 | modifier = Modifier.size(200.dp),
80 | update = {
81 | it.progress = (actualProgress * 100).roundToInt()
82 | }
83 | )
84 | }
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/app/src/main/java/tk/zwander/rootactivitylauncher/views/components/AppItem.kt:
--------------------------------------------------------------------------------
1 | package tk.zwander.rootactivitylauncher.views.components
2 |
3 | import android.annotation.SuppressLint
4 | import androidx.compose.foundation.layout.Arrangement
5 | import androidx.compose.foundation.layout.Column
6 | import androidx.compose.foundation.layout.fillMaxWidth
7 | import androidx.compose.foundation.layout.padding
8 | import androidx.compose.material3.ElevatedCard
9 | import androidx.compose.runtime.Composable
10 | import androidx.compose.runtime.LaunchedEffect
11 | import androidx.compose.runtime.collectAsState
12 | import androidx.compose.runtime.getValue
13 | import androidx.compose.runtime.mutableStateOf
14 | import androidx.compose.runtime.remember
15 | import androidx.compose.runtime.saveable.rememberSaveable
16 | import androidx.compose.runtime.setValue
17 | import androidx.compose.ui.Modifier
18 | import androidx.compose.ui.platform.LocalContext
19 | import androidx.compose.ui.res.stringResource
20 | import androidx.compose.ui.unit.dp
21 | import kotlinx.coroutines.Dispatchers
22 | import kotlinx.coroutines.withContext
23 | import tk.zwander.rootactivitylauncher.R
24 | import tk.zwander.rootactivitylauncher.data.ComponentActionButton
25 | import tk.zwander.rootactivitylauncher.data.component.BaseComponentInfo
26 | import tk.zwander.rootactivitylauncher.data.model.AppModel
27 | import tk.zwander.rootactivitylauncher.data.model.BaseInfoModel
28 | import tk.zwander.rootactivitylauncher.util.getCoilData
29 | import tk.zwander.rootactivitylauncher.util.isActuallyEnabled
30 | import tk.zwander.rootactivitylauncher.views.dialogs.ComponentInfoDialog
31 | import tk.zwander.rootactivitylauncher.views.dialogs.ExtrasDialog
32 |
33 | @SuppressLint("StateFlowValueCalledInComposition")
34 | @Composable
35 | fun AppItem(
36 | info: BaseInfoModel,
37 | isForTasker: Boolean,
38 | selectionCallback: (BaseComponentInfo) -> Unit,
39 | extractCallback: (AppModel) -> Unit,
40 | modifier: Modifier = Modifier
41 | ) {
42 | var showingIntentDialog by remember {
43 | mutableStateOf(false)
44 | }
45 | var showingComponentInfo by remember {
46 | mutableStateOf(false)
47 | }
48 | var enabled by rememberSaveable(if (info is AppModel) info.pInfo.packageName else null) {
49 | mutableStateOf(true)
50 | }
51 |
52 | val context = LocalContext.current
53 |
54 | val filteredActivities by info.filteredActivities.collectAsState()
55 | val filteredServices by info.filteredServices.collectAsState()
56 | val filteredReceivers by info.filteredReceivers.collectAsState()
57 |
58 | val activityCount by info.activitiesSize.collectAsState(info.initialActivitiesSize.value)
59 | val servicesCount by info.servicesSize.collectAsState(info.initialServicesSize.value)
60 | val receiversCount by info.receiversSize.collectAsState(info.initialReceiversSize.value)
61 |
62 | val activitiesExpanded by info.activitiesExpanded.collectAsState()
63 | val servicesExpanded by info.servicesExpanded.collectAsState()
64 | val receiversExpanded by info.receiversExpanded.collectAsState()
65 |
66 | val activitiesLoading by info.activitiesLoading.collectAsState()
67 | val servicesLoading by info.servicesLoading.collectAsState()
68 | val receiversLoading by info.receiversLoading.collectAsState()
69 |
70 | if (info is AppModel) {
71 | LaunchedEffect(info.pInfo.packageName) {
72 | enabled = withContext(Dispatchers.IO) {
73 | info.info?.isActuallyEnabled(context) == true
74 | }
75 | }
76 | }
77 |
78 | ElevatedCard(
79 | modifier = modifier,
80 | ) {
81 | Column(
82 | modifier = Modifier.fillMaxWidth(),
83 | verticalArrangement = Arrangement.spacedBy(8.dp),
84 | ) {
85 | AppBar(
86 | icon = remember(info is AppModel) {
87 | if (info is AppModel) info.info?.getCoilData() else R.drawable.baseline_favorite_24
88 | },
89 | name = if (info is AppModel) info.label.toString() else stringResource(id = R.string.favorites),
90 | showActions = !isForTasker && info is AppModel,
91 | modifier = Modifier
92 | .fillMaxWidth()
93 | .padding(start = 8.dp, top = 8.dp, end = 8.dp, bottom = 8.dp),
94 | app = info,
95 | whichButtons = remember(info is AppModel) {
96 | if (info is AppModel) {
97 | listOf(
98 | ComponentActionButton.ComponentInfoButton(info.pInfo) {
99 | showingComponentInfo = true
100 | },
101 | ComponentActionButton.IntentDialogButton(info.pInfo.packageName) {
102 | showingIntentDialog = true
103 | },
104 | ComponentActionButton.AppInfoButton(info.pInfo.packageName),
105 | ComponentActionButton.SaveApkButton(info, extractCallback)
106 | )
107 | } else {
108 | listOf()
109 | }
110 | },
111 | enabled = enabled,
112 | onEnabledChanged = {
113 | enabled = it
114 | }
115 | )
116 |
117 | ComponentGroup(
118 | titleRes = R.string.activities,
119 | items = filteredActivities,
120 | forTasker = isForTasker,
121 | expanded = activitiesExpanded,
122 | appEnabled = enabled,
123 | loading = activitiesLoading,
124 | onExpandChange = {
125 | info.activitiesExpanded.value = it
126 | },
127 | onItemSelected = selectionCallback,
128 | modifier = Modifier.fillMaxWidth(),
129 | count = activityCount
130 | )
131 |
132 | ComponentGroup(
133 | titleRes = R.string.services,
134 | items = filteredServices,
135 | forTasker = isForTasker,
136 | expanded = servicesExpanded,
137 | appEnabled = enabled,
138 | loading = servicesLoading,
139 | onExpandChange = {
140 | info.servicesExpanded.value = it
141 | },
142 | onItemSelected = selectionCallback,
143 | modifier = Modifier.fillMaxWidth(),
144 | count = servicesCount
145 | )
146 |
147 | ComponentGroup(
148 | titleRes = R.string.receivers,
149 | items = filteredReceivers,
150 | forTasker = isForTasker,
151 | expanded = receiversExpanded,
152 | appEnabled = enabled,
153 | loading = receiversLoading,
154 | onExpandChange = {
155 | info.receiversExpanded.value = it
156 | },
157 | onItemSelected = selectionCallback,
158 | modifier = Modifier.fillMaxWidth(),
159 | count = receiversCount
160 | )
161 | }
162 | }
163 |
164 | if (info is AppModel) {
165 | ExtrasDialog(
166 | showing = showingIntentDialog,
167 | componentKey = info.pInfo.packageName,
168 | ) { showingIntentDialog = false }
169 |
170 | ComponentInfoDialog(
171 | info = info.pInfo,
172 | showing = showingComponentInfo
173 | ) { showingComponentInfo = false }
174 | }
175 | }
176 |
--------------------------------------------------------------------------------
/app/src/main/java/tk/zwander/rootactivitylauncher/views/components/AppList.kt:
--------------------------------------------------------------------------------
1 | package tk.zwander.rootactivitylauncher.views.components
2 |
3 | import androidx.compose.foundation.layout.Arrangement
4 | import androidx.compose.foundation.layout.PaddingValues
5 | import androidx.compose.foundation.layout.fillMaxWidth
6 | import androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridState
7 | import androidx.compose.foundation.lazy.staggeredgrid.LazyVerticalStaggeredGrid
8 | import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridCells
9 | import androidx.compose.foundation.lazy.staggeredgrid.items
10 | import androidx.compose.runtime.Composable
11 | import androidx.compose.runtime.collectAsState
12 | import androidx.compose.runtime.getValue
13 | import androidx.compose.runtime.remember
14 | import androidx.compose.ui.Modifier
15 | import androidx.compose.ui.unit.dp
16 | import tk.zwander.rootactivitylauncher.data.component.BaseComponentInfo
17 | import tk.zwander.rootactivitylauncher.data.model.AppModel
18 | import tk.zwander.rootactivitylauncher.data.model.BaseInfoModel
19 | import tk.zwander.rootactivitylauncher.util.LocalFavoriteModel
20 | import tk.zwander.rootactivitylauncher.util.plus
21 |
22 | @Composable
23 | fun AppList(
24 | appListState: LazyStaggeredGridState,
25 | filteredApps: List,
26 | isForTasker: Boolean,
27 | onItemSelected: (BaseComponentInfo) -> Unit,
28 | extractCallback: (AppModel) -> Unit,
29 | modifier: Modifier = Modifier,
30 | contentPaddingValues: PaddingValues = PaddingValues(0.dp),
31 | ) {
32 | val favoriteModel = LocalFavoriteModel.current
33 | val favoriteSize by favoriteModel.totalInitialSize.collectAsState()
34 |
35 | val actualFilteredApps = remember(favoriteSize, filteredApps.toList()) {
36 | if (favoriteSize == 0) filteredApps.filterNot { it == favoriteModel }
37 | else filteredApps
38 | }
39 |
40 | LazyVerticalStaggeredGrid(
41 | columns = StaggeredGridCells.Adaptive(400.dp),
42 | modifier = modifier,
43 | contentPadding = contentPaddingValues + PaddingValues(8.dp),
44 | horizontalArrangement = Arrangement.spacedBy(8.dp),
45 | verticalItemSpacing = 8.dp,
46 | state = appListState,
47 | ) {
48 | items(items = actualFilteredApps, key = { if (it is AppModel) it.pInfo.packageName else "favorite_item" }) { info ->
49 | AppItem(
50 | info = info,
51 | isForTasker = isForTasker,
52 | selectionCallback = {
53 | onItemSelected(it)
54 | },
55 | extractCallback = extractCallback,
56 | modifier = Modifier.fillMaxWidth()
57 | .animateItem(),
58 | )
59 | }
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/app/src/main/java/tk/zwander/rootactivitylauncher/views/components/ComponentGroup.kt:
--------------------------------------------------------------------------------
1 | package tk.zwander.rootactivitylauncher.views.components
2 |
3 | import androidx.compose.animation.AnimatedVisibility
4 | import androidx.compose.animation.animateContentSize
5 | import androidx.compose.animation.core.animateFloatAsState
6 | import androidx.compose.animation.expandVertically
7 | import androidx.compose.animation.fadeIn
8 | import androidx.compose.animation.fadeOut
9 | import androidx.compose.animation.shrinkVertically
10 | import androidx.compose.foundation.clickable
11 | import androidx.compose.foundation.interaction.MutableInteractionSource
12 | import androidx.compose.foundation.layout.Arrangement
13 | import androidx.compose.foundation.layout.Column
14 | import androidx.compose.foundation.layout.PaddingValues
15 | import androidx.compose.foundation.layout.Row
16 | import androidx.compose.foundation.layout.Spacer
17 | import androidx.compose.foundation.layout.fillMaxWidth
18 | import androidx.compose.foundation.layout.heightIn
19 | import androidx.compose.foundation.layout.padding
20 | import androidx.compose.foundation.layout.size
21 | import androidx.compose.foundation.lazy.LazyColumn
22 | import androidx.compose.foundation.lazy.items
23 | import androidx.compose.material.icons.Icons
24 | import androidx.compose.material.icons.filled.KeyboardArrowDown
25 | import androidx.compose.material3.CircularProgressIndicator
26 | import androidx.compose.material3.Icon
27 | import androidx.compose.material3.Text
28 | import androidx.compose.material3.ripple
29 | import androidx.compose.runtime.Composable
30 | import androidx.compose.runtime.getValue
31 | import androidx.compose.runtime.remember
32 | import androidx.compose.ui.Alignment
33 | import androidx.compose.ui.Modifier
34 | import androidx.compose.ui.draw.rotate
35 | import androidx.compose.ui.res.stringResource
36 | import androidx.compose.ui.unit.dp
37 | import androidx.compose.ui.unit.sp
38 | import tk.zwander.rootactivitylauncher.data.component.BaseComponentInfo
39 |
40 | @Composable
41 | fun ComponentGroup(
42 | titleRes: Int,
43 | items: List,
44 | forTasker: Boolean,
45 | expanded: Boolean,
46 | appEnabled: Boolean,
47 | loading: Boolean,
48 | onExpandChange: (Boolean) -> Unit,
49 | onItemSelected: (BaseComponentInfo) -> Unit,
50 | modifier: Modifier = Modifier,
51 | count: Int = items.size,
52 | ) {
53 | val rotation by animateFloatAsState(
54 | targetValue = if (expanded) 180f else 0f,
55 | label = "Expanded_${stringResource(titleRes, count)}",
56 | )
57 |
58 | AnimatedVisibility(visible = count > 0) {
59 | Column(
60 | modifier = modifier
61 | ) {
62 | Row(
63 | modifier = Modifier
64 | .fillMaxWidth()
65 | .heightIn(min = 48.dp)
66 | .clickable(
67 | interactionSource = remember {
68 | MutableInteractionSource()
69 | },
70 | indication = ripple()
71 | ) {
72 | onExpandChange(!expanded)
73 | }
74 | .padding(8.dp),
75 | verticalAlignment = Alignment.CenterVertically,
76 | horizontalArrangement = Arrangement.spacedBy(8.dp),
77 | ) {
78 | Text(
79 | text = stringResource(id = titleRes, count),
80 | fontSize = 18.sp,
81 | )
82 |
83 | Spacer(Modifier.weight(1f))
84 |
85 | AnimatedVisibility(visible = loading) {
86 | CircularProgressIndicator(
87 | modifier = Modifier.size(24.dp),
88 | )
89 | }
90 |
91 | Icon(
92 | imageVector = Icons.Default.KeyboardArrowDown,
93 | contentDescription = null,
94 | modifier = Modifier.rotate(rotation),
95 | )
96 | }
97 |
98 | AnimatedVisibility(
99 | modifier = Modifier.fillMaxWidth(),
100 | visible = expanded && items.isNotEmpty(),
101 | enter = fadeIn() + expandVertically(),
102 | exit = fadeOut() + shrinkVertically(),
103 | ) {
104 | LazyColumn(
105 | modifier = Modifier
106 | .fillMaxWidth()
107 | .heightIn(max = 500.dp)
108 | .animateContentSize(),
109 | verticalArrangement = Arrangement.spacedBy(8.dp),
110 | contentPadding = PaddingValues(
111 | top = 8.dp,
112 | bottom = 8.dp
113 | )
114 | ) {
115 | items(items = items, key = { it.hashCode() }) {
116 | ComponentItem(
117 | forTasker = forTasker,
118 | component = it,
119 | appEnabled = appEnabled,
120 | modifier = Modifier
121 | .fillMaxWidth()
122 | .heightIn(min = 56.dp)
123 | .then(if (forTasker) {
124 | Modifier.clickable {
125 | onItemSelected(it)
126 | }
127 | } else Modifier)
128 | .padding(
129 | start = 32.dp,
130 | end = 8.dp,
131 | )
132 | )
133 | }
134 | }
135 | }
136 | }
137 | }
138 | }
139 |
--------------------------------------------------------------------------------
/app/src/main/java/tk/zwander/rootactivitylauncher/views/components/Menu.kt:
--------------------------------------------------------------------------------
1 | package tk.zwander.rootactivitylauncher.views.components
2 |
3 | import android.os.Parcelable
4 | import androidx.annotation.StringRes
5 | import androidx.compose.foundation.layout.Box
6 | import androidx.compose.material.icons.Icons
7 | import androidx.compose.material.icons.filled.MoreVert
8 | import androidx.compose.material3.DropdownMenu
9 | import androidx.compose.material3.DropdownMenuItem
10 | import androidx.compose.material3.Icon
11 | import androidx.compose.material3.IconButton
12 | import androidx.compose.material3.Text
13 | import androidx.compose.runtime.Composable
14 | import androidx.compose.runtime.getValue
15 | import androidx.compose.runtime.mutableStateOf
16 | import androidx.compose.runtime.remember
17 | import androidx.compose.runtime.setValue
18 | import androidx.compose.ui.Modifier
19 | import androidx.compose.ui.platform.LocalContext
20 | import androidx.compose.ui.res.stringResource
21 | import androidx.compose.ui.unit.DpOffset
22 | import androidx.compose.ui.unit.dp
23 | import kotlinx.parcelize.Parcelize
24 | import tk.zwander.rootactivitylauncher.R
25 | import tk.zwander.rootactivitylauncher.util.launchUrl
26 | import tk.zwander.rootactivitylauncher.views.dialogs.PatreonSupportersDialog
27 |
28 | @Parcelize
29 | private data class MenuItem(
30 | val url: String,
31 | @StringRes val nameRes: Int,
32 | ) : Parcelable
33 |
34 | @Composable
35 | fun Menu(
36 | modifier: Modifier = Modifier
37 | ) {
38 | val context = LocalContext.current
39 | val items = remember {
40 | listOf(
41 | MenuItem(
42 | "https://androiddev.social/@wander1236",
43 | R.string.mastodon
44 | ),
45 | MenuItem(
46 | "https://zwander.dev",
47 | R.string.website
48 | ),
49 | MenuItem(
50 | "https://github.com/zacharee",
51 | R.string.github
52 | ),
53 | MenuItem(
54 | "mailto:zachary@zwander.dev?subject=${
55 | context.resources.getString(R.string.app_name)
56 | .replace(" ", "%20")
57 | }",
58 | R.string.email
59 | ),
60 | MenuItem(
61 | "https://bit.ly/ZachareeTG",
62 | R.string.telegram
63 | ),
64 | MenuItem(
65 | "https://bit.ly/zwanderDiscord",
66 | R.string.discord
67 | ),
68 | MenuItem(
69 | "https://bit.ly/zwanderPatreon",
70 | R.string.patreon
71 | )
72 | )
73 | }
74 |
75 | var showingMenu by remember {
76 | mutableStateOf(false)
77 | }
78 | var showingSupportersDialog by remember {
79 | mutableStateOf(false)
80 | }
81 |
82 | Box(modifier = modifier) {
83 | IconButton(
84 | onClick = {
85 | showingMenu = true
86 | }
87 | ) {
88 | Icon(
89 | imageVector = Icons.Default.MoreVert,
90 | contentDescription = stringResource(id = R.string.menu)
91 | )
92 | }
93 |
94 | DropdownMenu(
95 | expanded = showingMenu,
96 | onDismissRequest = { showingMenu = false },
97 | offset = DpOffset(16.dp, 0.dp),
98 | ) {
99 | items.forEach { item ->
100 | DropdownMenuItem(
101 | text = {
102 | Text(text = stringResource(id = item.nameRes))
103 | },
104 | onClick = {
105 | context.launchUrl(item.url)
106 | }
107 | )
108 | }
109 |
110 | DropdownMenuItem(
111 | text = {
112 | Text(text = stringResource(id = R.string.supporters))
113 | },
114 | onClick = {
115 | showingSupportersDialog = true
116 | }
117 | )
118 | }
119 | }
120 |
121 | PatreonSupportersDialog(showing = showingSupportersDialog) {
122 | showingSupportersDialog = false
123 | }
124 | }
125 |
--------------------------------------------------------------------------------
/app/src/main/java/tk/zwander/rootactivitylauncher/views/components/OptionGroup.kt:
--------------------------------------------------------------------------------
1 | package tk.zwander.rootactivitylauncher.views.components
2 |
3 | import androidx.compose.foundation.layout.Arrangement
4 | import androidx.compose.foundation.layout.Box
5 | import androidx.compose.foundation.layout.Column
6 | import androidx.compose.foundation.layout.fillMaxWidth
7 | import androidx.compose.foundation.layout.heightIn
8 | import androidx.compose.material3.Text
9 | import androidx.compose.runtime.Composable
10 | import androidx.compose.ui.Alignment
11 | import androidx.compose.ui.Modifier
12 | import androidx.compose.ui.res.stringResource
13 | import androidx.compose.ui.unit.dp
14 | import androidx.compose.ui.unit.sp
15 | import tk.zwander.rootactivitylauncher.data.BaseOption
16 |
17 | @Composable
18 | fun OptionGroup(
19 | name: String,
20 | modes: Array,
21 | selectedMode: T,
22 | onModeSelected: (T) -> Unit,
23 | ) {
24 | Column(
25 | modifier = Modifier.fillMaxWidth(),
26 | verticalArrangement = Arrangement.spacedBy(8.dp)
27 | ) {
28 | Text(
29 | text = name,
30 | fontSize = 18.sp,
31 | )
32 |
33 | Column(
34 | modifier = Modifier.fillMaxWidth(),
35 | verticalArrangement = Arrangement.spacedBy(4.dp),
36 | ) {
37 | modes.forEach { mode ->
38 | SelectableCard(
39 | selected = mode.labelRes == selectedMode.labelRes,
40 | onClick = { onModeSelected(mode) },
41 | ) {
42 | Box(
43 | modifier = Modifier
44 | .fillMaxWidth()
45 | .heightIn(min = 48.dp),
46 | contentAlignment = Alignment.Center,
47 | ) {
48 | Text(text = stringResource(id = mode.labelRes))
49 | }
50 | }
51 | }
52 | }
53 | }
54 | }
--------------------------------------------------------------------------------
/app/src/main/java/tk/zwander/rootactivitylauncher/views/components/SearchComponent.kt:
--------------------------------------------------------------------------------
1 | package tk.zwander.rootactivitylauncher.views.components
2 |
3 | import androidx.compose.animation.AnimatedVisibility
4 | import androidx.compose.animation.expandHorizontally
5 | import androidx.compose.animation.fadeIn
6 | import androidx.compose.animation.fadeOut
7 | import androidx.compose.animation.shrinkHorizontally
8 | import androidx.compose.foundation.layout.Box
9 | import androidx.compose.foundation.layout.ExperimentalLayoutApi
10 | import androidx.compose.foundation.layout.WindowInsets
11 | import androidx.compose.foundation.layout.fillMaxWidth
12 | import androidx.compose.foundation.layout.isImeVisible
13 | import androidx.compose.foundation.text.KeyboardOptions
14 | import androidx.compose.material.icons.Icons
15 | import androidx.compose.material.icons.filled.Search
16 | import androidx.compose.material.icons.outlined.Close
17 | import androidx.compose.material3.ExperimentalMaterial3Api
18 | import androidx.compose.material3.Icon
19 | import androidx.compose.material3.IconButton
20 | import androidx.compose.material3.Text
21 | import androidx.compose.material3.TextField
22 | import androidx.compose.material3.TextFieldDefaults
23 | import androidx.compose.runtime.Composable
24 | import androidx.compose.runtime.LaunchedEffect
25 | import androidx.compose.runtime.getValue
26 | import androidx.compose.runtime.mutableStateOf
27 | import androidx.compose.runtime.remember
28 | import androidx.compose.runtime.setValue
29 | import androidx.compose.ui.Alignment
30 | import androidx.compose.ui.Modifier
31 | import androidx.compose.ui.composed
32 | import androidx.compose.ui.focus.FocusRequester
33 | import androidx.compose.ui.focus.focusRequester
34 | import androidx.compose.ui.focus.onFocusEvent
35 | import androidx.compose.ui.graphics.Color
36 | import androidx.compose.ui.graphics.vector.rememberVectorPainter
37 | import androidx.compose.ui.platform.LocalFocusManager
38 | import androidx.compose.ui.platform.LocalSoftwareKeyboardController
39 | import androidx.compose.ui.res.painterResource
40 | import androidx.compose.ui.res.stringResource
41 | import tk.zwander.rootactivitylauncher.R
42 | import tk.zwander.rootactivitylauncher.views.dialogs.AdvancedUsageDialog
43 |
44 | @OptIn(ExperimentalMaterial3Api::class)
45 | @Composable
46 | fun SearchComponent(
47 | expanded: Boolean,
48 | query: String,
49 | onExpandChange: (Boolean) -> Unit,
50 | onQueryChange: (String) -> Unit,
51 | modifier: Modifier = Modifier,
52 | ) {
53 | var showingAdvancedUsage by remember {
54 | mutableStateOf(false)
55 | }
56 |
57 | val focusRequester = remember { FocusRequester() }
58 |
59 | val softwareKeyboardController = LocalSoftwareKeyboardController.current
60 |
61 | Box(
62 | modifier = modifier,
63 | contentAlignment = Alignment.CenterEnd
64 | ) {
65 | AnimatedVisibility(
66 | visible = expanded,
67 | enter = fadeIn() + expandHorizontally(expandFrom = Alignment.End),
68 | exit = fadeOut() + shrinkHorizontally(shrinkTowards = Alignment.End),
69 | ) {
70 | LaunchedEffect(null) {
71 | focusRequester.requestFocus()
72 | }
73 |
74 | TextField(
75 | value = query,
76 | onValueChange = onQueryChange,
77 | modifier = Modifier.fillMaxWidth()
78 | .clearFocusOnKeyboardDismiss()
79 | .focusRequester(focusRequester),
80 | colors = TextFieldDefaults.colors(
81 | focusedContainerColor = Color.Transparent,
82 | unfocusedContainerColor = Color.Transparent,
83 | ),
84 | leadingIcon = {
85 | IconButton(
86 | onClick = {
87 | showingAdvancedUsage = true
88 | }
89 | ) {
90 | Icon(
91 | painter = painterResource(id = R.drawable.ic_baseline_help_outline_24),
92 | contentDescription = stringResource(id = R.string.usage_advanced_search)
93 | )
94 | }
95 | },
96 | trailingIcon = {
97 | IconButton(
98 | onClick = {
99 | if (query.isNotBlank()) {
100 | onQueryChange("")
101 | } else {
102 | softwareKeyboardController?.hide()
103 | onExpandChange(false)
104 | }
105 | }
106 | ) {
107 | Icon(
108 | painter = rememberVectorPainter(Icons.Outlined.Close),
109 | contentDescription = stringResource(
110 | id = if (query.isNotBlank()) {
111 | R.string.clear
112 | } else {
113 | R.string.close
114 | }
115 | )
116 | )
117 | }
118 | },
119 | keyboardOptions = KeyboardOptions.Default.copy(autoCorrectEnabled = false),
120 | placeholder = {
121 | Text(text = stringResource(R.string.search))
122 | },
123 | )
124 | }
125 |
126 | AnimatedVisibility(
127 | visible = !expanded,
128 | enter = fadeIn(),
129 | exit = fadeOut()
130 | ) {
131 | IconButton(onClick = { onExpandChange(true) }) {
132 | Icon(
133 | imageVector = Icons.Default.Search,
134 | contentDescription = stringResource(id = R.string.search)
135 | )
136 | }
137 | }
138 | }
139 |
140 | AdvancedUsageDialog(showing = showingAdvancedUsage) {
141 | showingAdvancedUsage = false
142 | }
143 | }
144 |
145 | @OptIn(ExperimentalLayoutApi::class)
146 | fun Modifier.clearFocusOnKeyboardDismiss(): Modifier = composed {
147 | var isFocused by remember { mutableStateOf(false) }
148 | var keyboardAppearedSinceLastFocused by remember { mutableStateOf(false) }
149 | if (isFocused) {
150 | val imeIsVisible = WindowInsets.isImeVisible
151 | val focusManager = LocalFocusManager.current
152 | LaunchedEffect(imeIsVisible) {
153 | if (imeIsVisible) {
154 | keyboardAppearedSinceLastFocused = true
155 | } else if (keyboardAppearedSinceLastFocused) {
156 | focusManager.clearFocus()
157 | }
158 | }
159 | }
160 | onFocusEvent {
161 | if (isFocused != it.isFocused) {
162 | isFocused = it.isFocused
163 | if (isFocused) {
164 | keyboardAppearedSinceLastFocused = false
165 | }
166 | }
167 | }
168 | }
169 |
--------------------------------------------------------------------------------
/app/src/main/java/tk/zwander/rootactivitylauncher/views/components/SelectableCard.kt:
--------------------------------------------------------------------------------
1 | package tk.zwander.rootactivitylauncher.views.components
2 |
3 | import androidx.compose.animation.animateColorAsState
4 | import androidx.compose.foundation.BorderStroke
5 | import androidx.compose.foundation.layout.ColumnScope
6 | import androidx.compose.material3.AlertDialogDefaults
7 | import androidx.compose.material3.CardDefaults
8 | import androidx.compose.material3.MaterialTheme
9 | import androidx.compose.material3.OutlinedCard
10 | import androidx.compose.runtime.Composable
11 | import androidx.compose.runtime.getValue
12 | import androidx.compose.runtime.remember
13 | import androidx.compose.ui.Modifier
14 | import androidx.compose.ui.graphics.Color
15 | import androidx.compose.ui.unit.dp
16 |
17 | @Composable
18 | fun SelectableCard(
19 | selected: Boolean,
20 | onClick: () -> Unit,
21 | modifier: Modifier = Modifier,
22 | selectedColor: Color = MaterialTheme.colorScheme.primaryContainer,
23 | unselectedColor: Color = AlertDialogDefaults.containerColor,
24 | content: @Composable ColumnScope.() -> Unit,
25 | ) {
26 | val outlineColor = MaterialTheme.colorScheme.outline
27 |
28 | val color by animateColorAsState(
29 | targetValue = if (selected) {
30 | selectedColor
31 | } else {
32 | unselectedColor
33 | },
34 | label = "SelectableCardColor",
35 | )
36 |
37 | OutlinedCard(
38 | modifier = modifier,
39 | colors = CardDefaults.outlinedCardColors(
40 | containerColor = color,
41 | ),
42 | onClick = onClick,
43 | content = content,
44 | border = remember {
45 | BorderStroke(1.dp, outlineColor)
46 | }
47 | )
48 | }
49 |
--------------------------------------------------------------------------------
/app/src/main/java/tk/zwander/rootactivitylauncher/views/dialogs/AdvancedUsageDialog.kt:
--------------------------------------------------------------------------------
1 | package tk.zwander.rootactivitylauncher.views.dialogs
2 |
3 | import androidx.compose.foundation.layout.Arrangement
4 | import androidx.compose.foundation.layout.Column
5 | import androidx.compose.foundation.layout.fillMaxWidth
6 | import androidx.compose.foundation.layout.padding
7 | import androidx.compose.foundation.lazy.LazyColumn
8 | import androidx.compose.foundation.lazy.items
9 | import androidx.compose.material3.OutlinedCard
10 | import androidx.compose.material3.Text
11 | import androidx.compose.material3.TextButton
12 | import androidx.compose.runtime.Composable
13 | import androidx.compose.ui.Modifier
14 | import androidx.compose.ui.res.stringResource
15 | import androidx.compose.ui.text.font.FontWeight
16 | import androidx.compose.ui.unit.dp
17 | import androidx.compose.ui.unit.sp
18 | import tk.zwander.rootactivitylauncher.R
19 |
20 | private val items = listOf(
21 | R.string.usage_advanced_search_has_permission to R.string.usage_advanced_search_has_permission_desc,
22 | R.string.usage_advanced_search_declares_permission to R.string.usage_advanced_search_declares_permission_desc,
23 | R.string.usage_advanced_search_requires_permission to R.string.usage_advanced_search_requires_permission_desc,
24 | R.string.usage_advanced_search_requires_feature to R.string.usage_advanced_search_requires_feature_desc
25 | )
26 |
27 | @Composable
28 | fun AdvancedUsageDialog(
29 | showing: Boolean,
30 | onDismissRequest: () -> Unit
31 | ) {
32 | if (showing) {
33 | BaseAlertDialog(
34 | onDismissRequest = onDismissRequest,
35 | title = {
36 | Text(text = stringResource(id = R.string.usage_advanced_search))
37 | },
38 | text = {
39 | LazyColumn(
40 | verticalArrangement = Arrangement.spacedBy(8.dp)
41 | ) {
42 | items(items = items, key = { it }) {
43 | OutlinedCard {
44 | Column(
45 | modifier = Modifier.fillMaxWidth()
46 | .padding(8.dp),
47 | verticalArrangement = Arrangement.spacedBy(8.dp)
48 | ) {
49 | Text(
50 | text = stringResource(id = it.first),
51 | fontWeight = FontWeight.Bold,
52 | fontSize = 16.sp
53 | )
54 |
55 | Text(
56 | text = stringResource(id = it.second)
57 | )
58 | }
59 | }
60 | }
61 | }
62 | },
63 | confirmButton = {
64 | TextButton(onClick = onDismissRequest) {
65 | Text(text = stringResource(id = android.R.string.ok))
66 | }
67 | }
68 | )
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/app/src/main/java/tk/zwander/rootactivitylauncher/views/dialogs/BaseAlertDialog.kt:
--------------------------------------------------------------------------------
1 | package tk.zwander.rootactivitylauncher.views.dialogs
2 |
3 | import androidx.compose.foundation.layout.fillMaxWidth
4 | import androidx.compose.material3.AlertDialog
5 | import androidx.compose.material3.AlertDialogDefaults
6 | import androidx.compose.runtime.Composable
7 | import androidx.compose.ui.Modifier
8 | import androidx.compose.ui.graphics.Color
9 | import androidx.compose.ui.graphics.Shape
10 | import androidx.compose.ui.unit.Dp
11 | import androidx.compose.ui.window.DialogProperties
12 |
13 | @Composable
14 | fun BaseAlertDialog(
15 | onDismissRequest: () -> Unit,
16 | confirmButton: @Composable () -> Unit,
17 | modifier: Modifier = Modifier,
18 | dismissButton: @Composable (() -> Unit)? = null,
19 | icon: @Composable (() -> Unit)? = null,
20 | title: @Composable (() -> Unit)? = null,
21 | text: @Composable (() -> Unit)? = null,
22 | shape: Shape = AlertDialogDefaults.shape,
23 | containerColor: Color = AlertDialogDefaults.containerColor,
24 | iconContentColor: Color = AlertDialogDefaults.iconContentColor,
25 | titleContentColor: Color = AlertDialogDefaults.titleContentColor,
26 | textContentColor: Color = AlertDialogDefaults.textContentColor,
27 | tonalElevation: Dp = AlertDialogDefaults.TonalElevation,
28 | properties: DialogProperties = DialogProperties()
29 | ) {
30 | val actualProperties = DialogProperties(
31 | usePlatformDefaultWidth = false,
32 | decorFitsSystemWindows = properties.decorFitsSystemWindows,
33 | dismissOnBackPress = properties.dismissOnBackPress,
34 | dismissOnClickOutside = properties.dismissOnClickOutside,
35 | securePolicy = properties.securePolicy
36 | )
37 | val actualModifier = modifier.fillMaxWidth(0.85f)
38 |
39 | AlertDialog(
40 | onDismissRequest = onDismissRequest,
41 | confirmButton = confirmButton,
42 | modifier = actualModifier,
43 | dismissButton = dismissButton,
44 | icon = icon,
45 | title = title,
46 | text = text,
47 | shape = shape,
48 | containerColor = containerColor,
49 | iconContentColor = iconContentColor,
50 | titleContentColor = titleContentColor,
51 | textContentColor = textContentColor,
52 | tonalElevation = tonalElevation,
53 | properties = actualProperties
54 | )
55 | }
56 |
--------------------------------------------------------------------------------
/app/src/main/java/tk/zwander/rootactivitylauncher/views/dialogs/ComponentInfoDialog.kt:
--------------------------------------------------------------------------------
1 | package tk.zwander.rootactivitylauncher.views.dialogs
2 |
3 | import android.content.ClipData
4 | import android.content.pm.ActivityInfo
5 | import android.content.pm.PackageInfo
6 | import android.content.pm.ServiceInfo
7 | import androidx.compose.animation.Crossfade
8 | import androidx.compose.animation.animateContentSize
9 | import androidx.compose.foundation.layout.Arrangement
10 | import androidx.compose.foundation.layout.Box
11 | import androidx.compose.foundation.layout.Column
12 | import androidx.compose.foundation.layout.fillMaxWidth
13 | import androidx.compose.foundation.lazy.LazyColumn
14 | import androidx.compose.foundation.lazy.itemsIndexed
15 | import androidx.compose.foundation.text.KeyboardOptions
16 | import androidx.compose.foundation.text.selection.SelectionContainer
17 | import androidx.compose.material3.CircularProgressIndicator
18 | import androidx.compose.material3.ExperimentalMaterial3Api
19 | import androidx.compose.material3.MaterialTheme
20 | import androidx.compose.material3.OutlinedTextField
21 | import androidx.compose.material3.Text
22 | import androidx.compose.material3.TextButton
23 | import androidx.compose.runtime.Composable
24 | import androidx.compose.runtime.LaunchedEffect
25 | import androidx.compose.runtime.getValue
26 | import androidx.compose.runtime.mutableStateListOf
27 | import androidx.compose.runtime.mutableStateOf
28 | import androidx.compose.runtime.remember
29 | import androidx.compose.runtime.rememberCoroutineScope
30 | import androidx.compose.runtime.setValue
31 | import androidx.compose.ui.Alignment
32 | import androidx.compose.ui.Modifier
33 | import androidx.compose.ui.platform.ClipEntry
34 | import androidx.compose.ui.platform.LocalClipboard
35 | import androidx.compose.ui.res.stringResource
36 | import androidx.compose.ui.text.AnnotatedString
37 | import androidx.compose.ui.unit.dp
38 | import kotlinx.coroutines.Dispatchers
39 | import kotlinx.coroutines.launch
40 | import kotlinx.coroutines.withContext
41 | import tk.zwander.rootactivitylauncher.R
42 | import tk.zwander.rootactivitylauncher.util.applyQuery
43 | import tk.zwander.rootactivitylauncher.util.processActivityInfo
44 | import tk.zwander.rootactivitylauncher.util.processPackageInfo
45 | import tk.zwander.rootactivitylauncher.util.processServiceInfo
46 |
47 | @Composable
48 | fun ComponentInfoDialog(
49 | info: T,
50 | showing: Boolean,
51 | onDismissRequest: () -> Unit,
52 | ) {
53 | if (showing) {
54 | val clipboardManager = LocalClipboard.current
55 | val highlightColor = MaterialTheme.colorScheme.primary
56 | val scope = rememberCoroutineScope()
57 |
58 | var query by remember {
59 | mutableStateOf("")
60 | }
61 |
62 | val dump = remember {
63 | mutableStateListOf()
64 | }
65 |
66 | LaunchedEffect(query) {
67 | val d = if (dump.isEmpty()) {
68 | withContext(Dispatchers.IO) {
69 | when (info) {
70 | is ActivityInfo -> processActivityInfo(info)
71 | is ServiceInfo -> processServiceInfo(info)
72 | is PackageInfo -> processPackageInfo(info)
73 | else -> listOf()
74 | }
75 | }
76 | } else dump
77 |
78 | val q = withContext(Dispatchers.IO) {
79 | applyQuery(highlightColor, d, query, dump.isEmpty())
80 | }
81 |
82 | dump.clear()
83 | dump.addAll(q)
84 | }
85 |
86 | BaseAlertDialog(
87 | title = {
88 | Text(text = stringResource(id = R.string.component_info))
89 | },
90 | onDismissRequest = onDismissRequest,
91 | confirmButton = {
92 | TextButton(onClick = onDismissRequest) {
93 | Text(text = stringResource(id = android.R.string.ok))
94 | }
95 | },
96 | dismissButton = {
97 | TextButton(
98 | onClick = {
99 | scope.launch {
100 | clipboardManager.setClipEntry(
101 | ClipEntry(ClipData.newPlainText(null, dump.joinToString("\n")))
102 | )
103 | }
104 | },
105 | ) {
106 | Text(text = stringResource(id = tk.zwander.patreonsupportersretrieval.R.string.copy))
107 | }
108 | },
109 | text = {
110 | ComponentInfoContents(
111 | query = query,
112 | onQueryChanged = { query = it },
113 | content = dump,
114 | modifier = Modifier.animateContentSize()
115 | )
116 | }
117 | )
118 | }
119 | }
120 |
121 | @OptIn(ExperimentalMaterial3Api::class)
122 | @Composable
123 | private fun ComponentInfoContents(
124 | query: String,
125 | onQueryChanged: (String) -> Unit,
126 | content: List,
127 | modifier: Modifier = Modifier
128 | ) {
129 | Crossfade(
130 | targetState = content.isEmpty(),
131 | modifier = modifier
132 | ) {
133 | if (it) {
134 | Box(
135 | modifier = Modifier.fillMaxWidth(),
136 | contentAlignment = Alignment.Center
137 | ) {
138 | CircularProgressIndicator()
139 | }
140 | } else {
141 | Column(
142 | modifier = Modifier.fillMaxWidth(),
143 | verticalArrangement = Arrangement.spacedBy(8.dp)
144 | ) {
145 | OutlinedTextField(
146 | value = query,
147 | onValueChange = onQueryChanged,
148 | modifier = Modifier.fillMaxWidth(),
149 | label = {
150 | Text(text = stringResource(id = R.string.search))
151 | },
152 | keyboardOptions = KeyboardOptions.Default.copy(autoCorrectEnabled = false),
153 | )
154 |
155 | SelectionContainer {
156 | LazyColumn(
157 | modifier = Modifier.fillMaxWidth()
158 | ) {
159 | itemsIndexed(items = content, key = { index, item -> item.toString() + index }) { _, item ->
160 | Text(text = item)
161 | }
162 | }
163 | }
164 | }
165 | }
166 | }
167 | }
168 |
--------------------------------------------------------------------------------
/app/src/main/java/tk/zwander/rootactivitylauncher/views/dialogs/ExtraTypeDialog.kt:
--------------------------------------------------------------------------------
1 | package tk.zwander.rootactivitylauncher.views.dialogs
2 |
3 | import androidx.compose.foundation.layout.Arrangement
4 | import androidx.compose.foundation.layout.Box
5 | import androidx.compose.foundation.layout.fillMaxWidth
6 | import androidx.compose.foundation.layout.heightIn
7 | import androidx.compose.foundation.lazy.LazyColumn
8 | import androidx.compose.foundation.lazy.items
9 | import androidx.compose.foundation.lazy.rememberLazyListState
10 | import androidx.compose.material3.Text
11 | import androidx.compose.material3.TextButton
12 | import androidx.compose.runtime.Composable
13 | import androidx.compose.runtime.LaunchedEffect
14 | import androidx.compose.runtime.remember
15 | import androidx.compose.ui.Alignment
16 | import androidx.compose.ui.Modifier
17 | import androidx.compose.ui.platform.LocalContext
18 | import androidx.compose.ui.res.stringResource
19 | import androidx.compose.ui.unit.dp
20 | import tk.zwander.rootactivitylauncher.R
21 | import tk.zwander.rootactivitylauncher.data.ExtraInfo
22 | import tk.zwander.rootactivitylauncher.data.ExtraType
23 | import tk.zwander.rootactivitylauncher.views.components.SelectableCard
24 |
25 | @Composable
26 | fun ExtrasTypeDialog(
27 | showing: Boolean,
28 | extraInfo: ExtraInfo,
29 | onDismissRequest: () -> Unit,
30 | onTypeSelected: (ExtraType) -> Unit,
31 | ) {
32 | if (showing) {
33 | BaseAlertDialog(
34 | onDismissRequest = {
35 | onDismissRequest()
36 | },
37 | title = {
38 | Text(text = stringResource(id = R.string.type))
39 | },
40 | text = {
41 | ExtrasTypeDialogContents(
42 | initial = extraInfo.safeType,
43 | onTypeSelected = {
44 | onTypeSelected(it)
45 | onDismissRequest()
46 | },
47 | modifier = Modifier.fillMaxWidth()
48 | )
49 | },
50 | confirmButton = {
51 | TextButton(onClick = { onDismissRequest() }) {
52 | Text(text = stringResource(id = android.R.string.cancel))
53 | }
54 | },
55 | )
56 | }
57 | }
58 |
59 | @Composable
60 | fun ExtrasTypeDialogContents(
61 | initial: ExtraType,
62 | onTypeSelected: (ExtraType) -> Unit,
63 | modifier: Modifier = Modifier
64 | ) {
65 | val context = LocalContext.current
66 | val sortedTypes = remember {
67 | ExtraType.entries.sortedBy {
68 | context.resources.getString(it.nameRes)
69 | }
70 | }
71 | val state = rememberLazyListState()
72 |
73 | LaunchedEffect(initial) {
74 | state.scrollToItem(sortedTypes.indexOf(initial))
75 | }
76 |
77 | LazyColumn(
78 | modifier = modifier,
79 | verticalArrangement = Arrangement.spacedBy(8.dp),
80 | state = state
81 | ) {
82 | items(items = sortedTypes, key = { it.value }) { type ->
83 | SelectableCard(
84 | modifier = Modifier.fillMaxWidth(),
85 | selected = initial == type,
86 | onClick = {
87 | onTypeSelected(type)
88 | },
89 | ) {
90 | Box(
91 | modifier = Modifier
92 | .fillMaxWidth()
93 | .heightIn(min = 56.dp),
94 | contentAlignment = Alignment.Center,
95 | ) {
96 | Text(text = stringResource(id = type.nameRes))
97 | }
98 | }
99 | }
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/app/src/main/java/tk/zwander/rootactivitylauncher/views/dialogs/FilterDialog.kt:
--------------------------------------------------------------------------------
1 | package tk.zwander.rootactivitylauncher.views.dialogs
2 |
3 | import androidx.compose.foundation.layout.Arrangement
4 | import androidx.compose.foundation.layout.Column
5 | import androidx.compose.foundation.layout.fillMaxWidth
6 | import androidx.compose.foundation.rememberScrollState
7 | import androidx.compose.foundation.verticalScroll
8 | import androidx.compose.material3.Text
9 | import androidx.compose.material3.TextButton
10 | import androidx.compose.runtime.Composable
11 | import androidx.compose.runtime.getValue
12 | import androidx.compose.runtime.mutableStateOf
13 | import androidx.compose.runtime.remember
14 | import androidx.compose.runtime.setValue
15 | import androidx.compose.ui.Modifier
16 | import androidx.compose.ui.res.stringResource
17 | import androidx.compose.ui.unit.dp
18 | import tk.zwander.rootactivitylauncher.R
19 | import tk.zwander.rootactivitylauncher.data.FilterMode
20 | import tk.zwander.rootactivitylauncher.views.components.OptionGroup
21 |
22 | @Composable
23 | fun FilterDialog(
24 | showing: Boolean,
25 | initialEnabledMode: FilterMode.EnabledFilterMode,
26 | initialExportedMode: FilterMode.ExportedFilterMode,
27 | initialPermissionMode: FilterMode.PermissionFilterMode,
28 | initialComponentMode: FilterMode.HasComponentsFilterMode,
29 | initialSystemAppsMode: FilterMode.SystemAppFilterMode,
30 | onDismissRequest: (
31 | FilterMode.EnabledFilterMode,
32 | FilterMode.ExportedFilterMode,
33 | FilterMode.PermissionFilterMode,
34 | FilterMode.HasComponentsFilterMode,
35 | FilterMode.SystemAppFilterMode,
36 | ) -> Unit,
37 | ) {
38 | if (showing) {
39 | var enabledMode by remember {
40 | mutableStateOf(initialEnabledMode)
41 | }
42 | var exportedMode by remember {
43 | mutableStateOf(initialExportedMode)
44 | }
45 | var permissionMode by remember {
46 | mutableStateOf(initialPermissionMode)
47 | }
48 | var componentMode by remember {
49 | mutableStateOf(initialComponentMode)
50 | }
51 | var systemAppsMode by remember {
52 | mutableStateOf(initialSystemAppsMode)
53 | }
54 |
55 | BaseAlertDialog(
56 | onDismissRequest = {
57 | onDismissRequest(
58 | initialEnabledMode,
59 | initialExportedMode,
60 | initialPermissionMode,
61 | initialComponentMode,
62 | initialSystemAppsMode,
63 | )
64 | },
65 | title = {
66 | Text(text = stringResource(id = R.string.filter))
67 | },
68 | confirmButton = {
69 | TextButton(
70 | onClick = {
71 | onDismissRequest(enabledMode, exportedMode, permissionMode, componentMode, systemAppsMode)
72 | }
73 | ) {
74 | Text(text = stringResource(id = android.R.string.ok))
75 | }
76 | },
77 | dismissButton = {
78 | TextButton(
79 | onClick = {
80 | onDismissRequest(
81 | initialEnabledMode,
82 | initialExportedMode,
83 | initialPermissionMode,
84 | initialComponentMode,
85 | initialSystemAppsMode,
86 | )
87 | }
88 | ) {
89 | Text(text = stringResource(id = android.R.string.cancel))
90 | }
91 | },
92 | text = {
93 | Column(
94 | verticalArrangement = Arrangement.spacedBy(16.dp),
95 | modifier = Modifier
96 | .fillMaxWidth()
97 | .verticalScroll(rememberScrollState()),
98 | ) {
99 | OptionGroup(
100 | name = stringResource(id = R.string.enabled_filter),
101 | modes = arrayOf(
102 | FilterMode.EnabledFilterMode.ShowAll,
103 | FilterMode.EnabledFilterMode.ShowEnabled,
104 | FilterMode.EnabledFilterMode.ShowDisabled,
105 | ),
106 | onModeSelected = {
107 | enabledMode = it
108 | },
109 | selectedMode = enabledMode
110 | )
111 |
112 | OptionGroup(
113 | name = stringResource(id = R.string.exported_filter),
114 | modes = arrayOf(
115 | FilterMode.ExportedFilterMode.ShowAll,
116 | FilterMode.ExportedFilterMode.ShowExported,
117 | FilterMode.ExportedFilterMode.ShowUnexported,
118 | ),
119 | onModeSelected = {
120 | exportedMode = it
121 | },
122 | selectedMode = exportedMode,
123 | )
124 |
125 | OptionGroup(
126 | name = stringResource(id = R.string.permission_filter),
127 | modes = arrayOf(
128 | FilterMode.PermissionFilterMode.ShowAll,
129 | FilterMode.PermissionFilterMode.ShowRequiresPermission,
130 | FilterMode.PermissionFilterMode.ShowNoPermissionRequired,
131 | ),
132 | onModeSelected = {
133 | permissionMode = it
134 | },
135 | selectedMode = permissionMode,
136 | )
137 |
138 | OptionGroup(
139 | name = stringResource(id = R.string.component_filter),
140 | modes = arrayOf(
141 | FilterMode.HasComponentsFilterMode.ShowAll,
142 | FilterMode.HasComponentsFilterMode.ShowHasComponents,
143 | FilterMode.HasComponentsFilterMode.ShowHasNoComponents,
144 | ),
145 | onModeSelected = {
146 | componentMode = it
147 | },
148 | selectedMode = componentMode,
149 | )
150 |
151 | OptionGroup(
152 | name = stringResource(R.string.system_app_filter),
153 | modes = arrayOf(
154 | FilterMode.SystemAppFilterMode.ShowAll,
155 | FilterMode.SystemAppFilterMode.ShowSystemApps,
156 | FilterMode.SystemAppFilterMode.ShowNonSystemApps,
157 | ),
158 | onModeSelected = {
159 | systemAppsMode = it
160 | },
161 | selectedMode = systemAppsMode,
162 | )
163 | }
164 | },
165 | )
166 | }
167 | }
168 |
--------------------------------------------------------------------------------
/app/src/main/java/tk/zwander/rootactivitylauncher/views/dialogs/PatreonSupportersDialog.kt:
--------------------------------------------------------------------------------
1 | package tk.zwander.rootactivitylauncher.views.dialogs
2 |
3 | import androidx.compose.animation.Crossfade
4 | import androidx.compose.foundation.layout.Box
5 | import androidx.compose.foundation.layout.Column
6 | import androidx.compose.foundation.layout.Spacer
7 | import androidx.compose.foundation.layout.fillMaxWidth
8 | import androidx.compose.foundation.layout.heightIn
9 | import androidx.compose.foundation.layout.size
10 | import androidx.compose.foundation.lazy.LazyColumn
11 | import androidx.compose.foundation.lazy.items
12 | import androidx.compose.material3.CircularProgressIndicator
13 | import androidx.compose.material3.ExperimentalMaterial3Api
14 | import androidx.compose.material3.OutlinedCard
15 | import androidx.compose.material3.Text
16 | import androidx.compose.material3.TextButton
17 | import androidx.compose.runtime.Composable
18 | import androidx.compose.runtime.LaunchedEffect
19 | import androidx.compose.runtime.mutableStateListOf
20 | import androidx.compose.runtime.remember
21 | import androidx.compose.ui.Alignment
22 | import androidx.compose.ui.Modifier
23 | import androidx.compose.ui.platform.LocalContext
24 | import androidx.compose.ui.res.stringResource
25 | import androidx.compose.ui.unit.dp
26 | import tk.zwander.patreonsupportersretrieval.data.SupporterInfo
27 | import tk.zwander.patreonsupportersretrieval.util.DataParser
28 | import tk.zwander.rootactivitylauncher.R
29 | import tk.zwander.rootactivitylauncher.util.launchUrl
30 |
31 | @OptIn(ExperimentalMaterial3Api::class)
32 | @Composable
33 | fun PatreonSupportersDialog(
34 | showing: Boolean,
35 | onDismissRequest: () -> Unit
36 | ) {
37 | val context = LocalContext.current
38 | val supporters = remember {
39 | mutableStateListOf()
40 | }
41 |
42 | if (showing) {
43 | LaunchedEffect(null) {
44 | supporters.clear()
45 | supporters.addAll(DataParser.getInstance(context).parseSupporters())
46 | }
47 |
48 | BaseAlertDialog(
49 | onDismissRequest = onDismissRequest,
50 | title = {
51 | Text(text = stringResource(id = R.string.supporters))
52 | },
53 | confirmButton = {
54 | TextButton(onClick = onDismissRequest) {
55 | Text(text = stringResource(id = android.R.string.ok))
56 | }
57 | },
58 | text = {
59 | Column(
60 | horizontalAlignment = Alignment.CenterHorizontally,
61 | modifier = Modifier.fillMaxWidth()
62 | ) {
63 | Text(text = stringResource(id = tk.zwander.patreonsupportersretrieval.R.string.supporters_desc))
64 |
65 | Spacer(modifier = Modifier.size(8.dp))
66 |
67 | Crossfade(targetState = supporters.isEmpty()) { empty ->
68 | if (empty) {
69 | Box(
70 | modifier = Modifier.fillMaxWidth(),
71 | contentAlignment = Alignment.Center
72 | ) {
73 | CircularProgressIndicator()
74 | }
75 | } else {
76 | LazyColumn(
77 | modifier = Modifier.fillMaxWidth()
78 | ) {
79 | items(items = supporters, key = { it }) {
80 | OutlinedCard(
81 | onClick = {
82 | context.launchUrl(it.link)
83 | },
84 | ) {
85 | Box(
86 | modifier = Modifier
87 | .fillMaxWidth()
88 | .heightIn(min = 48.dp),
89 | contentAlignment = Alignment.Center
90 | ) {
91 | Text(text = it.name)
92 | }
93 | }
94 | }
95 | }
96 | }
97 | }
98 | }
99 | }
100 | )
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/app/src/main/java/tk/zwander/rootactivitylauncher/views/dialogs/SortDialog.kt:
--------------------------------------------------------------------------------
1 | package tk.zwander.rootactivitylauncher.views.dialogs
2 |
3 | import androidx.compose.foundation.layout.Arrangement
4 | import androidx.compose.foundation.layout.Column
5 | import androidx.compose.foundation.layout.fillMaxWidth
6 | import androidx.compose.foundation.rememberScrollState
7 | import androidx.compose.foundation.verticalScroll
8 | import androidx.compose.material3.Text
9 | import androidx.compose.material3.TextButton
10 | import androidx.compose.runtime.Composable
11 | import androidx.compose.runtime.getValue
12 | import androidx.compose.runtime.mutableStateOf
13 | import androidx.compose.runtime.remember
14 | import androidx.compose.runtime.setValue
15 | import androidx.compose.ui.Modifier
16 | import androidx.compose.ui.res.stringResource
17 | import androidx.compose.ui.unit.dp
18 | import tk.zwander.rootactivitylauncher.R
19 | import tk.zwander.rootactivitylauncher.data.SortMode
20 | import tk.zwander.rootactivitylauncher.data.SortOrder
21 | import tk.zwander.rootactivitylauncher.views.components.OptionGroup
22 |
23 | @Composable
24 | fun SortDialog(
25 | showing: Boolean,
26 | initialSortBy: SortMode,
27 | initialSortOrder: SortOrder,
28 | onDismissRequest: (SortMode, SortOrder) -> Unit,
29 | ) {
30 | if (showing) {
31 | var sortBy by remember {
32 | mutableStateOf(initialSortBy)
33 | }
34 | var sortOrder by remember {
35 | mutableStateOf(initialSortOrder)
36 | }
37 |
38 | BaseAlertDialog(
39 | onDismissRequest = {
40 | onDismissRequest(initialSortBy, initialSortOrder)
41 | },
42 | title = {
43 | Text(text = stringResource(id = R.string.sort_apps))
44 | },
45 | confirmButton = {
46 | TextButton(
47 | onClick = {
48 | onDismissRequest(sortBy, sortOrder)
49 | }
50 | ) {
51 | Text(text = stringResource(id = android.R.string.ok))
52 | }
53 | },
54 | dismissButton = {
55 | TextButton(
56 | onClick = {
57 | onDismissRequest(initialSortBy, initialSortOrder)
58 | }
59 | ) {
60 | Text(text = stringResource(id = android.R.string.cancel))
61 | }
62 | },
63 | text = {
64 | Column(
65 | verticalArrangement = Arrangement.spacedBy(16.dp),
66 | modifier = Modifier
67 | .fillMaxWidth()
68 | .verticalScroll(rememberScrollState())
69 | ) {
70 | OptionGroup(
71 | name = stringResource(id = R.string.sort_by),
72 | modes = arrayOf(
73 | SortMode.SortByName,
74 | SortMode.SortByUpdatedDate,
75 | SortMode.SortByInstalledDate,
76 | ),
77 | onModeSelected = {
78 | sortBy = it
79 | },
80 | selectedMode = sortBy,
81 | )
82 |
83 | OptionGroup(
84 | name = stringResource(id = R.string.sort_order),
85 | modes = arrayOf(
86 | SortOrder.Ascending,
87 | SortOrder.Descending,
88 | ),
89 | onModeSelected = {
90 | sortOrder = it
91 | },
92 | selectedMode = sortOrder,
93 | )
94 | }
95 | },
96 | )
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/app/src/main/java/tk/zwander/rootactivitylauncher/views/theme/Theme.kt:
--------------------------------------------------------------------------------
1 | package tk.zwander.rootactivitylauncher.views.theme
2 |
3 | import android.os.Build
4 | import androidx.compose.foundation.isSystemInDarkTheme
5 | import androidx.compose.material3.MaterialTheme
6 | import androidx.compose.material3.darkColorScheme
7 | import androidx.compose.material3.dynamicDarkColorScheme
8 | import androidx.compose.material3.dynamicLightColorScheme
9 | import androidx.compose.material3.lightColorScheme
10 | import androidx.compose.runtime.Composable
11 | import androidx.compose.ui.platform.LocalContext
12 | import androidx.compose.ui.res.colorResource
13 | import tk.zwander.rootactivitylauncher.R
14 |
15 | @Composable
16 | fun Theme(
17 | dark: Boolean = isSystemInDarkTheme(),
18 | content: @Composable () -> Unit,
19 | ) {
20 | val context = LocalContext.current
21 | val isAndroid12 = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S
22 |
23 | val colorPrimary = colorResource(R.color.colorPrimary)
24 | val colorPrimaryDark = colorResource(R.color.colorPrimaryDark)
25 | val colorAccent = colorResource(R.color.colorAccent)
26 |
27 | MaterialTheme(
28 | colorScheme = if (dark) {
29 | if (isAndroid12) dynamicDarkColorScheme(context) else darkColorScheme(
30 | primary = colorPrimary,
31 | secondary = colorAccent,
32 | tertiary = colorPrimaryDark,
33 | )
34 | } else {
35 | if (isAndroid12) dynamicLightColorScheme(context) else lightColorScheme(
36 | primary = colorPrimary,
37 | secondary = colorAccent,
38 | tertiary = colorPrimaryDark,
39 | )
40 | },
41 | content = content,
42 | )
43 | }
44 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/baseline_favorite_24.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_filter_list_24.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_help_outline_24.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_link_24.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_open_in_new_24.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
12 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
6 |
10 |
14 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/save.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/scroll_to_bottom.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/scroll_to_top.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/sort.xml:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/tune.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zacharee/RootActivityLauncher/58c903ddabdf33bab83c2c730dd06a19ad9088a1/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zacharee/RootActivityLauncher/58c903ddabdf33bab83c2c730dd06a19ad9088a1/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zacharee/RootActivityLauncher/58c903ddabdf33bab83c2c730dd06a19ad9088a1/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zacharee/RootActivityLauncher/58c903ddabdf33bab83c2c730dd06a19ad9088a1/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zacharee/RootActivityLauncher/58c903ddabdf33bab83c2c730dd06a19ad9088a1/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zacharee/RootActivityLauncher/58c903ddabdf33bab83c2c730dd06a19ad9088a1/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zacharee/RootActivityLauncher/58c903ddabdf33bab83c2c730dd06a19ad9088a1/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zacharee/RootActivityLauncher/58c903ddabdf33bab83c2c730dd06a19ad9088a1/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zacharee/RootActivityLauncher/58c903ddabdf33bab83c2c730dd06a19ad9088a1/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zacharee/RootActivityLauncher/58c903ddabdf33bab83c2c730dd06a19ad9088a1/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/values-v31/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | @android:color/system_neutral1_800
4 | @android:color/system_accent1_100
5 | @color/iconBackgroundStart
6 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #F44336
4 | #D32F2F
5 | #7C4DFF
6 |
7 | #00ff00
8 | #ffff00
9 | #ff0000
10 |
11 | #FFFFFF
12 | @color/colorPrimaryDark
13 | @color/colorAccent
14 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
13 |
14 |
25 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/device_admin.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | alias(libs.plugins.android.application) apply false
3 | alias(libs.plugins.kotlin.android) apply false
4 | alias(libs.plugins.kotlin.parcelize) apply false
5 | alias(libs.plugins.kotlin.atomicfu) apply false
6 | alias(libs.plugins.kotlin.compose) apply false
7 | alias(libs.plugins.bugsnag.android) apply false
8 | }
9 |
10 | tasks.register("clean") {
11 | delete(rootProject.layout.buildDirectory)
12 | }
13 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx2048m
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app"s APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | # Automatically convert third-party libraries to use AndroidX
19 | android.enableJetifier=true
20 | # Kotlin code style for this project: "official" or "obsolete":
21 | kotlin.code.style=official
22 | kotlinx.atomicfu.enableJvmIrTransformation=true
--------------------------------------------------------------------------------
/gradle/libs.versions.toml:
--------------------------------------------------------------------------------
1 | [versions]
2 | activityCompose = "1.10.1"
3 | androidGradlePlugin = "8.10.1"
4 | appcompat = "1.7.0"
5 | atomicfu = "0.27.0"
6 | bugsnagAndroid = "6.13.0"
7 | bugsnagAndroidGradle = "8.1.0"
8 | coil = "2.7.0"
9 | compose = "1.8.2"
10 | composeThemeAdapter3 = "1.1.1"
11 | composetooltip = "0.2.0"
12 | coreKtx = "1.16.0"
13 | datastorePreferences = "1.1.7"
14 | dhizukuApi = "2.5.3"
15 | gson = "2.12.1"
16 | hiddenapibypass = "6.1"
17 | kotlin = "2.1.21"
18 | kotlinxCoroutines = "1.10.2"
19 | libsuperuser = "1.1.1"
20 | material3 = "1.3.2"
21 | materialVersion = "1.12.0"
22 | patreonsupportersretrieval = "8436bbdbdc"
23 | preferenceKtx = "1.2.1"
24 | progresscircula = "0.2.1"
25 | shizuku = "13.1.5"
26 | taskerpluginlibrary = "0.4.10"
27 |
28 | [libraries]
29 | activity-compose = { module = "androidx.activity:activity-compose", version.ref = "activityCompose" }
30 | appcompat = { module = "androidx.appcompat:appcompat", version.ref = "appcompat" }
31 | atomicfu = { module = "org.jetbrains.kotlinx:atomicfu", version.ref = "atomicfu" }
32 | bugsnag-android = { module = "com.bugsnag:bugsnag-android", version.ref = "bugsnagAndroid" }
33 | coil = { module = "io.coil-kt:coil", version.ref = "coil" }
34 | coil-compose = { module = "io.coil-kt:coil-compose", version.ref = "coil" }
35 | compose-material = { module = "androidx.compose.material:material", version.ref = "compose" }
36 | compose-material3 = { module = "androidx.compose.material3:material3", version.ref = "material3" }
37 | compose-runtime = { module = "androidx.compose.runtime:runtime", version.ref = "compose" }
38 | compose-theme-adapter3 = { module = "com.google.android.material:compose-theme-adapter-3", version.ref = "composeThemeAdapter3" }
39 | compose-ui-tooling-preview = { module = "androidx.compose.ui:ui-tooling-preview", version.ref = "compose" }
40 | composetooltip = { module = "com.github.skgmn:composetooltip", version.ref = "composetooltip" }
41 | core-ktx = { module = "androidx.core:core-ktx", version.ref = "coreKtx" }
42 | datastore-preferences = { module = "androidx.datastore:datastore-preferences", version.ref = "datastorePreferences" }
43 | dhizuku-api = { module = "io.github.iamr0s:Dhizuku-API", version.ref = "dhizukuApi" }
44 | google-material = { module = "com.google.android.material:material", version.ref = "materialVersion" }
45 | gson = { module = "com.google.code.gson:gson", version.ref = "gson" }
46 | hiddenapibypass = { module = "org.lsposed.hiddenapibypass:hiddenapibypass", version.ref = "hiddenapibypass" }
47 | kotlin-reflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = "kotlin" }
48 | kotlin-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = "kotlin" }
49 | kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "kotlinxCoroutines" }
50 | kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinxCoroutines" }
51 | libsuperuser = { module = "com.github.Chainfire:libsuperuser", version.ref = "libsuperuser" }
52 | patreonSupportersRetrieval = { module = "com.github.zacharee:PatreonSupportersRetrieval", version.ref = "patreonsupportersretrieval" }
53 | preference-ktx = { module = "androidx.preference:preference-ktx", version.ref = "preferenceKtx" }
54 | progressCircula = { module = "com.github.2hamed:ProgressCircula", version.ref = "progresscircula" }
55 | shizuku-api = { module = "dev.rikka.shizuku:api", version.ref = "shizuku" }
56 | shizuku-provider = { module = "dev.rikka.shizuku:provider", version.ref = "shizuku" }
57 | taskerpluginlibrary = { module = "com.joaomgcd:taskerpluginlibrary", version.ref = "taskerpluginlibrary" }
58 |
59 | [plugins]
60 | android-application = { id = "com.android.application", version.ref = "androidGradlePlugin" }
61 | bugsnag-android = { id = "com.bugsnag.android.gradle", version.ref = "bugsnagAndroidGradle" }
62 | kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
63 | kotlin-atomicfu = { id = "org.jetbrains.kotlin.plugin.atomicfu", version.ref = "kotlin" }
64 | kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
65 | kotlin-parcelize = { id = "org.jetbrains.kotlin.plugin.parcelize", version.ref = "kotlin" }
66 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zacharee/RootActivityLauncher/58c903ddabdf33bab83c2c730dd06a19ad9088a1/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Wed May 28 23:03:07 EDT 2025
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.1-bin.zip
5 | zipStoreBase=GRADLE_USER_HOME
6 | zipStorePath=wrapper/dists
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Attempt to set APP_HOME
10 | # Resolve links: $0 may be a link
11 | PRG="$0"
12 | # Need this for relative symlinks.
13 | while [ -h "$PRG" ] ; do
14 | ls=`ls -ld "$PRG"`
15 | link=`expr "$ls" : '.*-> \(.*\)$'`
16 | if expr "$link" : '/.*' > /dev/null; then
17 | PRG="$link"
18 | else
19 | PRG=`dirname "$PRG"`"/$link"
20 | fi
21 | done
22 | SAVED="`pwd`"
23 | cd "`dirname \"$PRG\"`/" >/dev/null
24 | APP_HOME="`pwd -P`"
25 | cd "$SAVED" >/dev/null
26 |
27 | APP_NAME="Gradle"
28 | APP_BASE_NAME=`basename "$0"`
29 |
30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
31 | DEFAULT_JVM_OPTS=""
32 |
33 | # Use the maximum available, or set MAX_FD != -1 to use that value.
34 | MAX_FD="maximum"
35 |
36 | warn () {
37 | echo "$*"
38 | }
39 |
40 | die () {
41 | echo
42 | echo "$*"
43 | echo
44 | exit 1
45 | }
46 |
47 | # OS specific support (must be 'true' or 'false').
48 | cygwin=false
49 | msys=false
50 | darwin=false
51 | nonstop=false
52 | case "`uname`" in
53 | CYGWIN* )
54 | cygwin=true
55 | ;;
56 | Darwin* )
57 | darwin=true
58 | ;;
59 | MINGW* )
60 | msys=true
61 | ;;
62 | NONSTOP* )
63 | nonstop=true
64 | ;;
65 | esac
66 |
67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
68 |
69 | # Determine the Java command to use to start the JVM.
70 | if [ -n "$JAVA_HOME" ] ; then
71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
72 | # IBM's JDK on AIX uses strange locations for the executables
73 | JAVACMD="$JAVA_HOME/jre/sh/java"
74 | else
75 | JAVACMD="$JAVA_HOME/bin/java"
76 | fi
77 | if [ ! -x "$JAVACMD" ] ; then
78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
79 |
80 | Please set the JAVA_HOME variable in your environment to match the
81 | location of your Java installation."
82 | fi
83 | else
84 | JAVACMD="java"
85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
86 |
87 | Please set the JAVA_HOME variable in your environment to match the
88 | location of your Java installation."
89 | fi
90 |
91 | # Increase the maximum file descriptors if we can.
92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
93 | MAX_FD_LIMIT=`ulimit -H -n`
94 | if [ $? -eq 0 ] ; then
95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
96 | MAX_FD="$MAX_FD_LIMIT"
97 | fi
98 | ulimit -n $MAX_FD
99 | if [ $? -ne 0 ] ; then
100 | warn "Could not set maximum file descriptor limit: $MAX_FD"
101 | fi
102 | else
103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
104 | fi
105 | fi
106 |
107 | # For Darwin, add options to specify how the application appears in the dock
108 | if $darwin; then
109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
110 | fi
111 |
112 | # For Cygwin, switch paths to Windows format before running java
113 | if $cygwin ; then
114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
116 | JAVACMD=`cygpath --unix "$JAVACMD"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Escape application args
158 | save () {
159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
160 | echo " "
161 | }
162 | APP_ARGS=$(save "$@")
163 |
164 | # Collect all arguments for the java command, following the shell quoting and substitution rules
165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
166 |
167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
169 | cd "$(dirname "$0")"
170 | fi
171 |
172 | exec "$JAVACMD" "$@"
173 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | @file:Suppress("UnstableApiUsage")
2 |
3 | pluginManagement {
4 | repositories {
5 | google {
6 | content {
7 | includeGroupByRegex("com\\.android.*")
8 | includeGroupByRegex("com\\.google.*")
9 | includeGroupByRegex("androidx.*")
10 | }
11 | }
12 | mavenCentral()
13 | gradlePluginPortal()
14 | }
15 | }
16 | dependencyResolutionManagement {
17 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
18 | repositories {
19 | google()
20 | mavenCentral()
21 | maven(url = "https://jitpack.io")
22 | }
23 | }
24 |
25 | include(":app")
26 | rootProject.name = "Root Activity Launcher"
--------------------------------------------------------------------------------