瀏覽代碼

Add shizuku installer provider

世界 3 年之前
父節點
當前提交
5c72297186
共有 28 個文件被更改,包括 800 次插入27 次删除
  1. 1 0
      .idea/dictionaries/sekai.xml
  2. 5 0
      app/build.gradle.kts
  3. 11 1
      app/src/main/AndroidManifest.xml
  4. 7 0
      app/src/main/java/io/nekohasekai/sagernet/Constants.kt
  5. 4 1
      app/src/main/java/io/nekohasekai/sagernet/SagerNet.kt
  6. 1 0
      app/src/main/java/io/nekohasekai/sagernet/database/DataStore.kt
  7. 24 0
      app/src/main/java/io/nekohasekai/sagernet/ui/SettingsPreferenceFragment.kt
  8. 10 9
      app/src/main/java/io/nekohasekai/sagernet/ui/ThemedActivity.kt
  9. 90 0
      app/src/main/java/io/nekohasekai/sagernet/ui/sai/ShizukuPackageManager.kt
  10. 82 12
      app/src/main/java/io/nekohasekai/sagernet/ui/sai/SplitAPKsInstallerActivity.kt
  11. 6 2
      app/src/main/java/io/nekohasekai/sagernet/ui/sai/SplitAPKsInstallerService.kt
  12. 34 0
      app/src/main/java/io/nekohasekai/sagernet/utils/Theme.kt
  13. 5 0
      app/src/main/res/values/arrays.xml
  14. 2 0
      app/src/main/res/values/strings.xml
  15. 196 2
      app/src/main/res/values/themes.xml
  16. 8 0
      app/src/main/res/xml/global_preferences.xml
  17. 38 0
      library/include/src/main/java/android/annotation/NonNull.java
  18. 44 0
      library/include/src/main/java/android/annotation/Nullable.java
  19. 15 0
      library/include/src/main/java/android/content/IIntentReceiver.java
  20. 34 0
      library/include/src/main/java/android/content/IIntentSender.java
  21. 10 0
      library/include/src/main/java/android/content/pm/BaseParceledListSlice.java
  22. 25 0
      library/include/src/main/java/android/content/pm/IPackageInstaller.java
  23. 15 0
      library/include/src/main/java/android/content/pm/IPackageInstallerSession.java
  24. 19 0
      library/include/src/main/java/android/content/pm/IPackageManager.java
  25. 4 0
      library/include/src/main/java/android/content/pm/ParceledListSlice.java
  26. 6 0
      library/include/src/main/java/android/content/pm/UserInfo.java
  27. 51 0
      library/include/src/main/java/annotation/IntRange.java
  28. 53 0
      library/include/src/main/java/annotation/RequiresApi.java

+ 1 - 0
.idea/dictionaries/sekai.xml

@@ -33,6 +33,7 @@
       <w>sagernet</w>
       <w>shadowsocks</w>
       <w>shadowsocksr</w>
+      <w>shizuku</w>
       <w>snackbar</w>
       <w>thiz</w>
       <w>tproxy</w>

+ 5 - 0
app/build.gradle.kts

@@ -70,6 +70,11 @@ dependencies {
     implementation("io.noties.markwon:core:4.6.2")
     implementation("com.twofortyfouram:android-plugin-api-for-locale:1.0.4")
 
+    val shizuku_version = "12.1.0"
+    implementation("dev.rikka.shizuku:api:$shizuku_version")
+    implementation("dev.rikka.shizuku:provider:$shizuku_version")
+    implementation("org.lsposed.hiddenapibypass:hiddenapibypass:2.0")
+
     implementation("com.simplecityapps:recyclerview-fastscroll:2.0.1") {
         exclude(group = "androidx.recyclerview")
         exclude(group = "androidx.appcompat")

+ 11 - 1
app/src/main/AndroidManifest.xml

@@ -4,7 +4,7 @@
     package="io.nekohasekai.sagernet"
     android:installLocation="internalOnly">
 
-    <uses-sdk tools:overrideLibrary="com.google.zxing.client.android" />
+    <uses-sdk tools:overrideLibrary="com.google.zxing.client.android|rikka.shizuku.api|rikka.shizuku.provider|rikka.shizuku.shared|rikka.shizuku.aidl" />
 
     <permission
         android:name="${applicationId}.SERVICE"
@@ -372,9 +372,11 @@
 
         <activity
             android:name="io.nekohasekai.sagernet.ui.sai.SplitAPKsInstallerActivity"
+            android:configChanges="uiMode"
             android:excludeFromRecents="true"
             android:exported="true"
             android:label="@string/sai"
+            android:launchMode="singleInstance"
             android:theme="@style/Theme.SagerNet.Translucent">
 
             <intent-filter>
@@ -501,6 +503,14 @@
             android:name=".ui.sai.SplitAPKsInstallerService"
             android:exported="false" />
 
+        <provider
+            android:name="rikka.shizuku.ShizukuProvider"
+            android:authorities="${applicationId}.shizuku"
+            android:enabled="true"
+            android:exported="true"
+            android:multiprocess="false"
+            android:permission="android.permission.INTERACT_ACROSS_USERS_FULL" />
+
     </application>
 
 </manifest>

+ 7 - 0
app/src/main/java/io/nekohasekai/sagernet/Constants.kt

@@ -88,6 +88,7 @@ object Key {
 
     const val PROVIDER_TROJAN = "providerTrojan"
     const val PROVIDER_ROOT_CA = "providerRootCA"
+    const val PROVIDER_INSTALLER = "providerInstaller"
 
     const val TUN_IMPLEMENTATION = "tunImplementation"
     const val ENABLE_PCAP = "enablePcap"
@@ -220,6 +221,12 @@ object RootCAProvider {
     const val SYSTEM = 1
 }
 
+
+object InstallerProvider {
+    const val SYSTEM = 0
+    const val SHIZUKU = 1
+}
+
 object IPv6Mode {
     const val DISABLE = 0
     const val ENABLE = 1

+ 4 - 1
app/src/main/java/io/nekohasekai/sagernet/SagerNet.kt

@@ -62,6 +62,7 @@ import kotlinx.coroutines.DEBUG_PROPERTY_VALUE_ON
 import libcore.Libcore
 import libcore.UidDumper
 import libcore.UidInfo
+import org.lsposed.hiddenapibypass.HiddenApiBypass
 import java.net.InetSocketAddress
 import androidx.work.Configuration as WorkConfiguration
 
@@ -71,7 +72,9 @@ class SagerNet : Application(),
 
     override fun attachBaseContext(base: Context) {
         super.attachBaseContext(base)
-
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
+            HiddenApiBypass.addHiddenApiExemptions("L")
+        }
         application = this
     }
 

+ 1 - 0
app/src/main/java/io/nekohasekai/sagernet/database/DataStore.kt

@@ -214,6 +214,7 @@ object DataStore : OnPreferenceDataStoreChangeListener {
 
     var providerTrojan by configurationStore.stringToInt(Key.PROVIDER_TROJAN)
     var providerRootCA by configurationStore.stringToInt(Key.PROVIDER_ROOT_CA)
+    var providerInstaller by configurationStore.stringToInt(Key.PROVIDER_INSTALLER)
 
     // cache
 

+ 24 - 0
app/src/main/java/io/nekohasekai/sagernet/ui/SettingsPreferenceFragment.kt

@@ -20,6 +20,7 @@
 package io.nekohasekai.sagernet.ui
 
 import android.content.Intent
+import android.content.pm.PackageManager
 import android.os.Build
 import android.os.Bundle
 import android.view.View
@@ -38,6 +39,7 @@ import io.nekohasekai.sagernet.utils.Theme
 import io.nekohasekai.sagernet.widget.ColorPickerPreference
 import kotlinx.coroutines.delay
 import libcore.Libcore
+import rikka.shizuku.Shizuku
 import java.io.File
 
 class SettingsPreferenceFragment : PreferenceFragmentCompat() {
@@ -233,6 +235,15 @@ class SettingsPreferenceFragment : PreferenceFragmentCompat() {
 
         val acquireWakeLock = findPreference<SwitchPreference>(Key.ACQUIRE_WAKE_LOCK)!!
 
+        val installerProvider = findPreference<SimpleMenuPreference>(Key.PROVIDER_INSTALLER)!!
+        installerProvider.setOnPreferenceChangeListener { _, newValue ->
+            if (newValue == "${InstallerProvider.SHIZUKU}") {
+                checkShizuku()
+            } else {
+                true
+            }
+        }
+
         speedInterval.onPreferenceChangeListener = reloadListener
         portSocks5.onPreferenceChangeListener = reloadListener
         portHttp.onPreferenceChangeListener = reloadListener
@@ -291,6 +302,19 @@ class SettingsPreferenceFragment : PreferenceFragmentCompat() {
 
     }
 
+    fun checkShizuku(): Boolean {
+        val permission = try {
+            Shizuku.checkSelfPermission()
+        } catch (e: Exception) {
+            Logs.w(e)
+            return false
+        }
+        if (permission != PackageManager.PERMISSION_GRANTED) {
+            Shizuku.requestPermission(0)
+        }
+        return true
+    }
+
     override fun onResume() {
         super.onResume()
 

+ 10 - 9
app/src/main/java/io/nekohasekai/sagernet/ui/ThemedActivity.kt

@@ -45,17 +45,18 @@ abstract class ThemedActivity : AppCompatActivity {
     var uiMode = 0
 
     override fun onCreate(savedInstanceState: Bundle?) {
-        if (type != Type.Translucent) {
-            when (type) {
-                Type.Default -> {
-                    Theme.apply(this)
-                }
-                Type.Dialog -> {
-                    Theme.applyDialog(this)
-                }
+        when (type) {
+            Type.Default -> {
+                Theme.apply(this)
+            }
+            Type.Dialog -> {
+                Theme.applyDialog(this)
+            }
+            Type.Translucent -> {
+                Theme.applyTranslucent(this)
             }
-            Theme.applyNightTheme()
         }
+        Theme.applyNightTheme()
 
         super.onCreate(savedInstanceState)
         uiMode = resources.configuration.uiMode

+ 90 - 0
app/src/main/java/io/nekohasekai/sagernet/ui/sai/ShizukuPackageManager.kt

@@ -0,0 +1,90 @@
+/******************************************************************************
+ * Copyright (C) 2022 by nekohasekai <[email protected]>                  *
+ *                                                                            *
+ * This program is free software: you can redistribute it and/or modify       *
+ * it under the terms of the GNU General Public License as published by       *
+ * the Free Software Foundation, either version 3 of the License, or          *
+ *  (at your option) any later version.                                       *
+ *                                                                            *
+ * This program is distributed in the hope that it will be useful,            *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of             *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the              *
+ * GNU General Public License for more details.                               *
+ *                                                                            *
+ * You should have received a copy of the GNU General Public License          *
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.       *
+ *                                                                            *
+ ******************************************************************************/
+
+package io.nekohasekai.sagernet.ui.sai
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.content.pm.*
+import android.os.Build
+import android.os.Process
+import io.nekohasekai.sagernet.ktx.app
+import rikka.shizuku.Shizuku
+import rikka.shizuku.ShizukuBinderWrapper
+import rikka.shizuku.SystemServiceHelper
+
+object ShizukuPackageManager {
+
+    fun createPackageInstaller(): PackageInstaller {
+        val isRoot = Shizuku.getUid() == 0
+        return createPackageInstaller(
+            packageInstaller,
+            if (isRoot) app.packageName else "com.android.shell",
+            Process.myUserHandle().hashCode()
+        )
+    }
+
+    fun openSession(sessionId: Int) : PackageInstaller.Session {
+        val binder = ShizukuBinderWrapper(packageInstaller.openSession(sessionId).asBinder())
+        val session = IPackageInstallerSession.Stub.asInterface(binder)
+        return PackageInstaller.Session::class.java.getConstructor(
+            IPackageInstallerSession::class.java
+        ).newInstance(session)
+    }
+
+    @SuppressLint("PrivateApi")
+    fun getInstallFlags(params: PackageInstaller.SessionParams): Int {
+        return PackageInstaller.SessionParams::class.java.getDeclaredField("installFlags")[params] as Int
+    }
+
+    @SuppressLint("PrivateApi")
+    fun setInstallFlags(params: PackageInstaller.SessionParams, newValue: Int) {
+        PackageInstaller.SessionParams::class.java.getDeclaredField("installFlags")[params] = newValue
+    }
+
+    private val packageManager by lazy {
+        val binder = ShizukuBinderWrapper(SystemServiceHelper.getSystemService("package"))
+        IPackageManager.Stub.asInterface(binder)
+    }
+
+    private val packageInstaller by lazy {
+        IPackageInstaller.Stub.asInterface(ShizukuBinderWrapper(packageManager.packageInstaller.asBinder()))
+    }
+
+    private fun createPackageInstaller(
+        installer: IPackageInstaller, installerPackageName: String, userId: Int
+    ): PackageInstaller {
+        return if (Build.VERSION.SDK_INT >= 26) {
+            PackageInstaller::class.java.getConstructor(
+                IPackageInstaller::class.java, String::class.java, Int::class.javaPrimitiveType
+            ).newInstance(installer, installerPackageName, userId)
+        } else {
+            PackageInstaller::class.java.getConstructor(
+                Context::class.java,
+                PackageManager::class.java,
+                IPackageInstaller::class.java,
+                String::class.java,
+                Int::class.javaPrimitiveType
+            ).newInstance(
+                app, app.packageManager, installer, installerPackageName, userId
+            )
+        }
+    }
+
+
+}

+ 82 - 12
app/src/main/java/io/nekohasekai/sagernet/ui/sai/SplitAPKsInstallerActivity.kt

@@ -29,10 +29,14 @@ import android.os.Build
 import android.os.Bundle
 import androidx.documentfile.provider.DocumentFile
 import com.google.android.material.dialog.MaterialAlertDialogBuilder
+import io.nekohasekai.sagernet.InstallerProvider
 import io.nekohasekai.sagernet.R
+import io.nekohasekai.sagernet.database.DataStore
 import io.nekohasekai.sagernet.ktx.Logs
+import io.nekohasekai.sagernet.ktx.launchCustomTab
 import io.nekohasekai.sagernet.ktx.readableMessage
 import io.nekohasekai.sagernet.ui.ThemedActivity
+import rikka.shizuku.Shizuku
 import java.io.File
 import java.io.InputStream
 import java.util.zip.ZipEntry
@@ -42,6 +46,9 @@ class SplitAPKsInstallerActivity : ThemedActivity() {
 
     override val type = Type.Translucent
 
+    lateinit var pendingUri: Uri
+    val useShizuku by lazy { DataStore.providerInstaller == InstallerProvider.SHIZUKU }
+
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
 
@@ -50,11 +57,66 @@ class SplitAPKsInstallerActivity : ThemedActivity() {
             finish()
             return
         }
-        handleUri(uri)
+        pendingUri = uri
+
+        if (useShizuku) {
+            Shizuku.addRequestPermissionResultListener(this::onRequestPermissionResult)
+
+            val permission = try {
+                Shizuku.checkSelfPermission()
+            } catch (e: Exception) {
+                Logs.w(e)
+                shizukuError()
+                return
+            }
+
+            if (permission != PackageManager.PERMISSION_GRANTED) {
+                Shizuku.requestPermission(0)
+            } else {
+                if (!Shizuku.pingBinder()) {
+                    shizukuError()
+                    return
+                }
+                handleUri()
+            }
+        } else {
+            handleUri()
+        }
+    }
+
+    private fun shizukuError() {
+        MaterialAlertDialogBuilder(this).setTitle(R.string.error_title)
+            .setMessage(R.string.shizuku_unavailable)
+            .setPositiveButton(R.string.action_learn_more) { _, _ ->
+                launchCustomTab("https://shizuku.rikka.app/")
+                finish()
+            }
+            .setNegativeButton(android.R.string.cancel) { _, _ ->
+                finish()
+            }
+            .setOnCancelListener {
+                finish()
+            }
+            .show()
+    }
+
+    override fun onDestroy() {
+        super.onDestroy()
+        if (useShizuku) {
+            Shizuku.removeRequestPermissionResultListener(this::onRequestPermissionResult)
+        }
+    }
+
+    private fun onRequestPermissionResult(requestCode: Int, grantResult: Int) {
+        if (grantResult == PackageManager.PERMISSION_GRANTED) {
+            handleUri()
+        } else {
+            finish()
+        }
     }
 
-    fun handleUri(contentUri: Uri) {
-        val document = getDocument(contentUri)
+    fun handleUri() {
+        val document = getDocument(pendingUri)
         if (document == null) {
             returnError("Failed to get document")
             return
@@ -72,15 +134,9 @@ class SplitAPKsInstallerActivity : ThemedActivity() {
             return
         }
         runCatching {
-            contentResolver.openInputStream(contentUri)!!.use {
+            contentResolver.openInputStream(pendingUri)!!.use {
                 handleInputStream(it)
             }
-            /* }.recoverCatching { ex ->
-                 if (ex.message == "only DEFLATED entries can have EXT descriptor") {
-                     contentResolver.openInputStream(contentUri)!!.use {
-                         copyAndHandleInputStream(it)
-                     }
-                 } else throw ex*/
         }.onFailure {
             Logs.w(it)
             returnError(it.readableMessage)
@@ -91,15 +147,25 @@ class SplitAPKsInstallerActivity : ThemedActivity() {
         MaterialAlertDialogBuilder(this).setTitle(R.string.error_title)
             .setMessage(message)
             .setPositiveButton(android.R.string.ok) { _, _ -> finish() }
+            .setOnCancelListener { finish() }
             .show()
     }
 
     fun handleInputStream(inputStream: InputStream) {
-        val packageInstaller = packageManager.packageInstaller
+        val packageInstaller = if (useShizuku) ShizukuPackageManager.createPackageInstaller() else packageManager.packageInstaller
         val sessionParams = PackageInstaller.SessionParams(PackageInstaller.SessionParams.MODE_FULL_INSTALL)
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
             sessionParams.setInstallReason(PackageManager.INSTALL_REASON_USER)
         }
+        if (useShizuku) {
+            try {
+                var installFlags = ShizukuPackageManager.getInstallFlags(sessionParams)
+                installFlags = installFlags or (0x00000004 /*PackageManager.INSTALL_ALLOW_TEST*/ or 0x00000002) /*PackageManager.INSTALL_REPLACE_EXISTING*/
+                ShizukuPackageManager.setInstallFlags(sessionParams, installFlags)
+            } catch (e: Exception) {
+                Logs.w("Failed to set install flags", e)
+            }
+        }
         val sessionId = try {
             packageInstaller.createSession(sessionParams)
         } catch (e: IllegalStateException) {
@@ -109,7 +175,11 @@ class SplitAPKsInstallerActivity : ThemedActivity() {
             packageInstaller.createSession(sessionParams)
         }
         Logs.d("Create install session $sessionId")
-        val session = packageInstaller.openSession(sessionId)
+        val session = if (!useShizuku) {
+            packageInstaller.openSession(sessionId)
+        } else {
+            ShizukuPackageManager.openSession(sessionId)
+        }
         ZipInputStream(inputStream).use { zip ->
             var currentZipEntry: ZipEntry
             while (true) {

+ 6 - 2
app/src/main/java/io/nekohasekai/sagernet/ui/sai/SplitAPKsInstallerService.kt

@@ -23,7 +23,9 @@ import android.content.Intent
 import android.content.pm.PackageInstaller
 import android.os.IBinder
 import android.widget.Toast
+import io.nekohasekai.sagernet.InstallerProvider
 import io.nekohasekai.sagernet.R
+import io.nekohasekai.sagernet.database.DataStore
 import io.nekohasekai.sagernet.ktx.Logs
 
 class SplitAPKsInstallerService : Service() {
@@ -70,8 +72,10 @@ class SplitAPKsInstallerService : Service() {
                 val sessionId = intent.getIntExtra(PackageInstaller.EXTRA_SESSION_ID, -1)
                 if (sessionId != -1) {
                     try {
-                        packageManager.packageInstaller.abandonSession(sessionId)
-                    } catch (ignored: SecurityException) {
+                        val useShizuku = DataStore.providerInstaller == InstallerProvider.SHIZUKU
+                        val packageInstaller = if (useShizuku) ShizukuPackageManager.createPackageInstaller() else packageManager.packageInstaller
+                        packageInstaller.abandonSession(sessionId)
+                    } catch (ignored: Exception) {
                     }
                 }
             }

+ 34 - 0
app/src/main/java/io/nekohasekai/sagernet/utils/Theme.kt

@@ -61,6 +61,10 @@ object Theme {
         context.setTheme(getDialogTheme())
     }
 
+    fun applyTranslucent(context: Context) {
+        context.setTheme(getTranslucentTheme())
+    }
+
     fun getTheme(): Int {
         return getTheme(if (isExpert) DataStore.appTheme else defaultTheme())
     }
@@ -69,6 +73,10 @@ object Theme {
         return getDialogTheme(if (isExpert) DataStore.appTheme else defaultTheme())
     }
 
+    fun getTranslucentTheme(): Int {
+        return getTranslucentTheme(if (isExpert) DataStore.appTheme else defaultTheme())
+    }
+
     fun getTheme(theme: Int): Int {
         return when (theme) {
             RED -> R.style.Theme_SagerNet_Red
@@ -121,6 +129,32 @@ object Theme {
         }
     }
 
+    fun getTranslucentTheme(theme: Int): Int {
+        return when (theme) {
+            RED -> R.style.Theme_SagerNet_Translucent_Red
+            PINK -> R.style.Theme_SagerNet_Translucent
+            PURPLE -> R.style.Theme_SagerNet_Translucent_Purple
+            DEEP_PURPLE -> R.style.Theme_SagerNet_Translucent_DeepPurple
+            INDIGO -> R.style.Theme_SagerNet_Translucent_Indigo
+            BLUE -> R.style.Theme_SagerNet_Translucent_Blue
+            LIGHT_BLUE -> R.style.Theme_SagerNet_Translucent_LightBlue
+            CYAN -> R.style.Theme_SagerNet_Translucent_Cyan
+            TEAL -> R.style.Theme_SagerNet_Translucent_Teal
+            GREEN -> R.style.Theme_SagerNet_Translucent_Green
+            LIGHT_GREEN -> R.style.Theme_SagerNet_Translucent_LightGreen
+            LIME -> R.style.Theme_SagerNet_Translucent_Lime
+            YELLOW -> R.style.Theme_SagerNet_Translucent_Yellow
+            AMBER -> R.style.Theme_SagerNet_Translucent_Amber
+            ORANGE -> R.style.Theme_SagerNet_Translucent_Orange
+            DEEP_ORANGE -> R.style.Theme_SagerNet_Translucent_DeepOrange
+            BROWN -> R.style.Theme_SagerNet_Translucent_Brown
+            GREY -> R.style.Theme_SagerNet_Translucent_Grey
+            BLUE_GREY -> R.style.Theme_SagerNet_Translucent_BlueGrey
+            BLACK -> if (usingNightMode()) R.style.Theme_SagerNet_Translucent_Black else R.style.Theme_SagerNet_Translucent_LightBlack
+            else -> getTranslucentTheme(defaultTheme())
+        }
+    }
+
     var currentNightMode = -1
     fun getNightMode(): Int {
         if (currentNightMode == -1) {

+ 5 - 0
app/src/main/res/values/arrays.xml

@@ -643,4 +643,9 @@
         <item>XUDP</item>
     </string-array>
 
+    <string-array name="installer_provider_entry">
+        <item>@string/root_ca_system</item>
+        <item>Shizuku</item>
+    </string-array>
+
 </resources>

+ 2 - 0
app/src/main/res/values/strings.xml

@@ -523,5 +523,7 @@
     <string name="sai_error_incompatible">Application is incompatible with this device</string>
     <string name="sai_error_invalid">Invalid APKs</string>
     <string name="sai_error_storage">Not enough storage space to install the app</string>
+    <string name="installer_provider">Installer Provider</string>
+    <string name="shizuku_unavailable">Shizuku unavailable</string>
 
 </resources>

+ 196 - 2
app/src/main/res/values/themes.xml

@@ -48,8 +48,8 @@
 
     </style>
 
-    <style name="Theme.SagerNet.Translucent">
-        <item name="android:background">#33000000</item>
+    <style name="Theme.SagerNet.Translucent" parent="Theme.SagerNet.Dialog">
+        <item name="android:background">@android:color/transparent</item>
         <item name="android:windowNoTitle">true</item>
         <item name="android:windowBackground">@android:color/transparent</item>
         <item name="android:colorBackgroundCacheHint">@null</item>
@@ -529,6 +529,200 @@
         <item name="colorOnPrimarySurface">@color/black</item>
     </style>
 
+    <style name="Theme.SagerNet.Translucent.Black">
+        <item name="colorPrimary">@color/material_light_black</item>
+        <item name="colorPrimaryDark">@color/black</item>
+        <item name="colorAccent">@color/white</item>
+        <item name="colorMaterial100">@color/fab_color_progress</item>
+        <item name="colorMaterial300">@color/material_light_black</item>
+
+        <item name="pref_categoryColor">?android:textColorPrimary</item>
+        <item name="android:textColorLink">?attr/accentOrTextSecondary</item>
+        <item name="fabColorBackground">?colorPrimaryDark</item>
+        <item name="selectedColorPrimary">@color/white</item>
+        <item name="itemShapeFillColor">#616161</item>
+
+        <item name="accentOrTextSecondary">?android:textColorSecondary</item>
+        <item name="accentOrTextPrimary">?android:textColorPrimary</item>
+        <item name="primaryOrTextSecondary">?android:textColorSecondary</item>
+        <item name="primaryOrTextPrimary">?android:textColorPrimary</item>
+    </style>
+
+
+    <style name="Theme.SagerNet.Translucent.LightBlack">
+        <item name="colorPrimary">@color/white</item>
+        <item name="colorPrimaryDark">@color/material_light_white</item>
+        <item name="colorAccent">@color/black</item>
+        <item name="colorMaterial300">@color/white</item>
+
+        <item name="actionBarTheme">@style/ThemeOverlay.AppCompat.ActionBar</item>
+        <item name="selectedColorPrimary">@color/black</item>
+        <item name="whiteOrTextPrimary">?android:textColorPrimary</item>
+        <item name="fabColorBackground">@color/white</item>
+
+        <item name="tabTextColor">?android:textColorSecondary</item>
+        <item name="tabSelectedTextColor">?android:textColorPrimary</item>
+        <item name="tabIndicatorColor">@color/black</item>
+        <item name="tabRippleColor">@android:color/transparent</item>
+
+        <item name="android:windowLightStatusBar" tools:targetApi="m">true</item>
+        <item name="itemShapeFillColor">#E0E0E0</item>
+
+        <item name="colorMaterial100">@color/fab_color_progress</item>
+        <item name="pref_categoryColor">?android:textColorPrimary</item>
+        <item name="android:textColorLink">?attr/accentOrTextSecondary</item>
+
+        <item name="accentOrTextSecondary">?android:textColorSecondary</item>
+        <item name="accentOrTextPrimary">?android:textColorPrimary</item>
+        <item name="primaryOrTextSecondary">?android:textColorSecondary</item>
+        <item name="primaryOrTextPrimary">?android:textColorPrimary</item>
+    </style>
+
+    <style name="Theme.SagerNet.Translucent.Amber">
+        <item name="colorPrimary">@color/material_amber_500</item>
+        <item name="colorPrimaryDark">@color/material_amber_700</item>
+        <item name="colorAccent">@color/material_amber_accent_200</item>
+        <item name="colorMaterial100">@color/material_amber_100</item>
+        <item name="colorMaterial300">@color/material_amber_300</item>
+    </style>
+
+    <style name="Theme.SagerNet.Translucent.Blue">
+        <item name="colorPrimary">@color/material_blue_500</item>
+        <item name="colorPrimaryDark">@color/material_blue_700</item>
+        <item name="colorAccent">@color/material_blue_accent_200</item>
+        <item name="colorMaterial100">@color/material_blue_100</item>
+        <item name="colorMaterial300">@color/material_blue_300</item>
+    </style>
+
+    <style name="Theme.SagerNet.Translucent.BlueGrey">
+        <item name="colorPrimary">@color/material_blue_grey_500</item>
+        <item name="colorPrimaryDark">@color/material_blue_grey_700</item>
+        <item name="colorAccent">@color/material_blue_grey_200</item>
+        <item name="colorMaterial100">@color/material_blue_grey_100</item>
+        <item name="colorMaterial300">@color/material_blue_grey_300</item>
+    </style>
+
+    <style name="Theme.SagerNet.Translucent.Brown">
+        <item name="colorPrimary">@color/material_brown_500</item>
+        <item name="colorPrimaryDark">@color/material_brown_700</item>
+        <item name="colorAccent">@color/material_brown_200</item>
+        <item name="colorMaterial100">@color/material_brown_100</item>
+        <item name="colorMaterial300">@color/material_brown_300</item>
+    </style>
+
+    <style name="Theme.SagerNet.Translucent.Cyan">
+        <item name="colorPrimary">@color/material_cyan_500</item>
+        <item name="colorPrimaryDark">@color/material_cyan_700</item>
+        <item name="colorAccent">@color/material_cyan_accent_200</item>
+        <item name="colorMaterial100">@color/material_cyan_100</item>
+        <item name="colorMaterial300">@color/material_cyan_300</item>
+    </style>
+
+    <style name="Theme.SagerNet.Translucent.DeepOrange">
+        <item name="colorPrimary">@color/material_deep_orange_500</item>
+        <item name="colorPrimaryDark">@color/material_deep_orange_700</item>
+        <item name="colorAccent">@color/material_deep_orange_accent_200</item>
+        <item name="colorMaterial100">@color/material_deep_orange_100</item>
+        <item name="colorMaterial300">@color/material_deep_orange_300</item>
+    </style>
+
+    <style name="Theme.SagerNet.Translucent.DeepPurple">
+        <item name="colorPrimary">@color/material_deep_purple_500</item>
+        <item name="colorPrimaryDark">@color/material_deep_purple_700</item>
+        <item name="colorAccent">@color/material_deep_purple_accent_200</item>
+        <item name="colorMaterial100">@color/material_deep_purple_100</item>
+        <item name="colorMaterial300">@color/material_deep_purple_300</item>
+    </style>
+
+    <style name="Theme.SagerNet.Translucent.Green">
+        <item name="colorPrimary">@color/material_green_500</item>
+        <item name="colorPrimaryDark">@color/material_green_700</item>
+        <item name="colorAccent">@color/material_green_accent_200</item>
+        <item name="colorMaterial100">@color/material_green_100</item>
+        <item name="colorMaterial300">@color/material_green_300</item>
+    </style>
+
+    <style name="Theme.SagerNet.Translucent.Grey">
+        <item name="colorPrimary">@color/material_grey_500</item>
+        <item name="colorPrimaryDark">@color/material_grey_700</item>
+        <item name="colorAccent">@color/material_grey_200</item>
+        <item name="colorMaterial100">@color/material_grey_100</item>
+        <item name="colorMaterial300">@color/material_grey_300</item>
+    </style>
+
+    <style name="Theme.SagerNet.Translucent.Indigo">
+        <item name="colorPrimary">@color/material_indigo_500</item>
+        <item name="colorPrimaryDark">@color/material_indigo_700</item>
+        <item name="colorAccent">@color/material_indigo_accent_200</item>
+        <item name="colorMaterial100">@color/material_indigo_100</item>
+        <item name="colorMaterial300">@color/material_indigo_300</item>
+    </style>
+
+    <style name="Theme.SagerNet.Translucent.LightBlue">
+        <item name="colorPrimary">@color/material_light_blue_500</item>
+        <item name="colorPrimaryDark">@color/material_light_blue_700</item>
+        <item name="colorAccent">@color/material_light_blue_accent_200</item>
+        <item name="colorMaterial100">@color/material_light_blue_100</item>
+        <item name="colorMaterial300">@color/material_light_blue_300</item>
+    </style>
+
+    <style name="Theme.SagerNet.Translucent.LightGreen">
+        <item name="colorPrimary">@color/material_light_green_500</item>
+        <item name="colorPrimaryDark">@color/material_light_green_700</item>
+        <item name="colorAccent">@color/material_light_green_accent_200</item>
+        <item name="colorMaterial100">@color/material_light_green_100</item>
+        <item name="colorMaterial300">@color/material_light_green_300</item>
+    </style>
+
+    <style name="Theme.SagerNet.Translucent.Lime">
+        <item name="colorPrimary">@color/material_lime_500</item>
+        <item name="colorPrimaryDark">@color/material_lime_700</item>
+        <item name="colorAccent">@color/material_lime_accent_200</item>
+        <item name="colorMaterial100">@color/material_lime_100</item>
+        <item name="colorMaterial300">@color/material_lime_300</item>
+    </style>
+
+    <style name="Theme.SagerNet.Translucent.Orange">
+        <item name="colorPrimary">@color/material_orange_500</item>
+        <item name="colorPrimaryDark">@color/material_orange_700</item>
+        <item name="colorAccent">@color/material_orange_accent_200</item>
+        <item name="colorMaterial100">@color/material_orange_100</item>
+        <item name="colorMaterial300">@color/material_orange_300</item>
+    </style>
+
+    <style name="Theme.SagerNet.Translucent.Purple">
+        <item name="colorPrimary">@color/material_purple_500</item>
+        <item name="colorPrimaryDark">@color/material_purple_700</item>
+        <item name="colorAccent">@color/material_purple_accent_200</item>
+        <item name="colorMaterial100">@color/material_purple_100</item>
+        <item name="colorMaterial300">@color/material_purple_300</item>
+    </style>
+
+    <style name="Theme.SagerNet.Translucent.Red">
+        <item name="colorPrimary">@color/material_red_500</item>
+        <item name="colorPrimaryDark">@color/material_red_700</item>
+        <item name="colorAccent">@color/material_red_accent_200</item>
+        <item name="colorMaterial100">@color/material_red_100</item>
+        <item name="colorMaterial300">@color/material_red_300</item>
+    </style>
+
+    <style name="Theme.SagerNet.Translucent.Teal">
+        <item name="colorPrimary">@color/material_teal_500</item>
+        <item name="colorPrimaryDark">@color/material_teal_700</item>
+        <item name="colorAccent">@color/material_teal_accent_200</item>
+        <item name="colorMaterial100">@color/material_teal_100</item>
+        <item name="colorMaterial300">@color/material_teal_300</item>
+    </style>
+
+    <style name="Theme.SagerNet.Translucent.Yellow">
+        <item name="colorPrimary">@color/material_yellow_500</item>
+        <item name="colorPrimaryDark">@color/material_yellow_700</item>
+        <item name="colorAccent">@color/material_yellow_accent_200</item>
+        <item name="colorMaterial100">@color/material_yellow_100</item>
+        <item name="colorMaterial300">@color/material_yellow_300</item>
+        <item name="colorOnPrimarySurface">@color/black</item>
+    </style>
+
     <style name="Theme.SagerNet.Start" parent="Theme.MaterialComponents.Light.NoActionBar">
         <item name="android:windowBackground">@android:color/transparent</item>
         <item name="android:navigationBarColor">@android:color/transparent</item>

+ 8 - 0
app/src/main/res/xml/global_preferences.xml

@@ -181,6 +181,14 @@
             app:key="providerRootCA"
             app:title="@string/root_ca_provider"
             app:useSimpleSummaryProvider="true" />
+        <com.takisoft.preferencex.SimpleMenuPreference
+            android:icon="@drawable/baseline_construction_24"
+            app:defaultValue="0"
+            app:entries="@array/installer_provider_entry"
+            app:entryValues="@array/int_array_2"
+            app:key="providerInstaller"
+            app:title="@string/installer_provider"
+            app:useSimpleSummaryProvider="true" />
     </PreferenceCategory>
 
     <PreferenceCategory app:title="@string/cag_dns">

+ 38 - 0
library/include/src/main/java/android/annotation/NonNull.java

@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.annotation;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * Denotes that a parameter, field or method return value can never be null.
+ * <p>
+ * This is a marker annotation and it has no specific attributes.
+ *
+ * @paramDoc This value must never be {@code null}.
+ * @returnDoc This value will never be {@code null}.
+ * @hide
+ */
+@Retention(SOURCE)
+@Target({METHOD, PARAMETER, FIELD})
+public @interface NonNull {
+}

+ 44 - 0
library/include/src/main/java/android/annotation/Nullable.java

@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.annotation;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * Denotes that a parameter, field or method return value can be null.
+ * <p>
+ * When decorating a method call parameter, this denotes that the parameter can
+ * legitimately be null and the method will gracefully deal with it. Typically
+ * used on optional parameters.
+ * <p>
+ * When decorating a method, this denotes the method might legitimately return
+ * null.
+ * <p>
+ * This is a marker annotation and it has no specific attributes.
+ *
+ * @paramDoc This value may be {@code null}.
+ * @returnDoc This value may be {@code null}.
+ */
+@Retention(SOURCE)
+@Target({METHOD, PARAMETER, FIELD})
+public @interface Nullable {
+}

+ 15 - 0
library/include/src/main/java/android/content/IIntentReceiver.java

@@ -0,0 +1,15 @@
+package android.content;
+
+import android.os.Binder;
+import android.os.Bundle;
+
+public interface IIntentReceiver {
+
+    void performReceive(Intent intent, int resultCode, String data, Bundle extras,
+                        boolean ordered, boolean sticky, int sendingUser)
+            throws android.os.RemoteException;
+
+    abstract class Stub extends Binder implements IIntentReceiver {
+
+    }
+}

+ 34 - 0
library/include/src/main/java/android/content/IIntentSender.java

@@ -0,0 +1,34 @@
+package android.content;
+
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.IInterface;
+
+import androidx.annotation.RequiresApi;
+
+public interface IIntentSender extends IInterface {
+
+    int send(int code, Intent intent, String resolvedType,
+             IIntentReceiver finishedReceiver, String requiredPermission, Bundle options);
+
+    @RequiresApi(26)
+    void send(int code, Intent intent, String resolvedType, IBinder whitelistToken,
+              IIntentReceiver finishedReceiver, String requiredPermission, Bundle options);
+
+    abstract class Stub extends Binder implements IIntentSender {
+
+        public Stub() {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public IBinder asBinder() {
+            throw new UnsupportedOperationException();
+        }
+
+        public static IIntentSender asInterface(IBinder binder) {
+            throw new UnsupportedOperationException();
+        }
+    }
+}

+ 10 - 0
library/include/src/main/java/android/content/pm/BaseParceledListSlice.java

@@ -0,0 +1,10 @@
+package android.content.pm;
+
+import java.util.List;
+
+abstract class BaseParceledListSlice<T> {
+
+    public List<T> getList() {
+        throw new RuntimeException("STUB");
+    }
+}

+ 25 - 0
library/include/src/main/java/android/content/pm/IPackageInstaller.java

@@ -0,0 +1,25 @@
+package android.content.pm;
+
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.IInterface;
+import android.os.RemoteException;
+
+public interface IPackageInstaller extends IInterface {
+
+    void abandonSession(int sessionId)
+            throws RemoteException;
+
+    IPackageInstallerSession openSession(int sessionId)
+            throws RemoteException;
+
+    ParceledListSlice<PackageInstaller.SessionInfo> getMySessions(String installerPackageName, int userId)
+            throws RemoteException;
+
+    abstract class Stub extends Binder implements IPackageInstaller {
+
+        public static IPackageInstaller asInterface(IBinder binder) {
+            throw new UnsupportedOperationException();
+        }
+    }
+}

+ 15 - 0
library/include/src/main/java/android/content/pm/IPackageInstallerSession.java

@@ -0,0 +1,15 @@
+package android.content.pm;
+
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.IInterface;
+
+public interface IPackageInstallerSession extends IInterface {
+
+    abstract class Stub extends Binder implements IPackageInstallerSession {
+
+        public static IPackageInstallerSession asInterface(IBinder binder) {
+            throw new UnsupportedOperationException();
+        }
+    }
+}

+ 19 - 0
library/include/src/main/java/android/content/pm/IPackageManager.java

@@ -0,0 +1,19 @@
+package android.content.pm;
+
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.IInterface;
+import android.os.RemoteException;
+
+public interface IPackageManager extends IInterface {
+
+    IPackageInstaller getPackageInstaller()
+            throws RemoteException;
+
+    abstract class Stub extends Binder implements IPackageManager {
+
+        public static IPackageManager asInterface(IBinder obj) {
+            throw new UnsupportedOperationException();
+        }
+    }
+}

+ 4 - 0
library/include/src/main/java/android/content/pm/ParceledListSlice.java

@@ -0,0 +1,4 @@
+package android.content.pm;
+
+public class ParceledListSlice<T> extends BaseParceledListSlice<T> {
+}

+ 6 - 0
library/include/src/main/java/android/content/pm/UserInfo.java

@@ -0,0 +1,6 @@
+package android.content.pm;
+
+public class UserInfo {
+
+    public int id;
+}

+ 51 - 0
library/include/src/main/java/annotation/IntRange.java

@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package annotation;
+
+import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.CLASS;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * Denotes that the annotated element should be an int or long in the given range
+ * <p>
+ * Example:
+ * <pre><code>
+ *  &#64;IntRange(from=0,to=255)
+ *  public int getAlpha() {
+ *      ...
+ *  }
+ * </code></pre>
+ */
+@Retention(CLASS)
+@Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE, ANNOTATION_TYPE})
+public @interface IntRange {
+    /**
+     * Smallest value, inclusive
+     */
+    long from() default Long.MIN_VALUE;
+
+    /**
+     * Largest value, inclusive
+     */
+    long to() default Long.MAX_VALUE;
+}

+ 53 - 0
library/include/src/main/java/annotation/RequiresApi.java

@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package annotation;
+
+import static java.lang.annotation.ElementType.CONSTRUCTOR;
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import androidx.annotation.IntRange;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * Denotes that the annotated element should only be called on the given API level
+ * or higher.
+ * <p>
+ * This is similar in purpose to the older {@code @TargetApi} annotation, but more
+ * clearly expresses that this is a requirement on the caller, rather than being
+ * used to "suppress" warnings within the method that exceed the {@code minSdkVersion}.
+ */
+@Retention(SOURCE)
+@Target({TYPE, METHOD, CONSTRUCTOR, FIELD})
+public @interface RequiresApi {
+
+    /**
+     * The API level to require. Alias for {@link #api} which allows you to leave out the
+     * {@code api=} part.
+     */
+    @androidx.annotation.IntRange(from = 1)
+    int value() default 1;
+
+    /**
+     * The API level to require
+     */
+    @IntRange(from = 1)
+    int api() default 1;
+}