Commit 4b238a9c authored by topjohnwu's avatar topjohnwu

Add feature to create launch shortcuts

parent f200d472
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
<uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.VIBRATE" /> <uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="com.android.launcher.permission.INSTALL_SHORTCUT" />
<application <application
android:icon="@drawable/ic_launcher" android:icon="@drawable/ic_launcher"
......
...@@ -55,6 +55,7 @@ object Config : PreferenceModel, DBConfig { ...@@ -55,6 +55,7 @@ object Config : PreferenceModel, DBConfig {
const val SAFETY = "safety_notice" const val SAFETY = "safety_notice"
const val THEME_ORDINAL = "theme_ordinal" const val THEME_ORDINAL = "theme_ordinal"
const val BOOT_ID = "boot_id" const val BOOT_ID = "boot_id"
const val ASKED_HOME = "asked_home"
// system state // system state
const val MAGISKHIDE = "magiskhide" const val MAGISKHIDE = "magiskhide"
...@@ -108,6 +109,7 @@ object Config : PreferenceModel, DBConfig { ...@@ -108,6 +109,7 @@ object Config : PreferenceModel, DBConfig {
Value.DEFAULT_CHANNEL Value.DEFAULT_CHANNEL
var bootId by preference(Key.BOOT_ID, "") var bootId by preference(Key.BOOT_ID, "")
var askedHome by preference(Key.ASKED_HOME, false)
var downloadPath by preference(Key.DOWNLOAD_PATH, Environment.DIRECTORY_DOWNLOADS) var downloadPath by preference(Key.DOWNLOAD_PATH, Environment.DIRECTORY_DOWNLOADS)
var repoOrder by preference(Key.REPO_ORDER, Value.ORDER_DATE) var repoOrder by preference(Key.REPO_ORDER, Value.ORDER_DATE)
...@@ -223,7 +225,9 @@ object Config : PreferenceModel, DBConfig { ...@@ -223,7 +225,9 @@ object Config : PreferenceModel, DBConfig {
fun export() { fun export() {
// Flush prefs to disk // Flush prefs to disk
prefs.edit().commit() prefs.edit().apply {
remove(Key.ASKED_HOME)
}.commit()
val context = get<Context>(Protected) val context = get<Context>(Protected)
val xml = File( val xml = File(
"${context.filesDir.parent}/shared_prefs", "${context.filesDir.parent}/shared_prefs",
......
...@@ -83,6 +83,7 @@ object Const { ...@@ -83,6 +83,7 @@ object Const {
} }
object Nav { object Nav {
const val HOME = "home"
const val SETTINGS = "settings" const val SETTINGS = "settings"
const val HIDE = "hide" const val HIDE = "hide"
const val MODULES = "modules" const val MODULES = "modules"
......
...@@ -45,7 +45,7 @@ open class GeneralReceiver : BaseReceiver() { ...@@ -45,7 +45,7 @@ open class GeneralReceiver : BaseReceiver() {
rmPolicy(pkg) rmPolicy(pkg)
Shell.su("magiskhide --rm $pkg").submit() Shell.su("magiskhide --rm $pkg").submit()
} }
Intent.ACTION_LOCALE_CHANGED -> Shortcuts.setup(context) Intent.ACTION_LOCALE_CHANGED -> Shortcuts.setupDynamic(context)
Const.Key.BROADCAST_MANAGER_UPDATE -> { Const.Key.BROADCAST_MANAGER_UPDATE -> {
intent.getParcelableExtra<ManagerJson>(Const.Key.INTENT_SET_APP)?.let { intent.getParcelableExtra<ManagerJson>(Const.Key.INTENT_SET_APP)?.let {
Info.remote = Info.remote.copy(app = it) Info.remote = Info.remote.copy(app = it)
......
...@@ -52,7 +52,7 @@ open class SplashActivity : Activity() { ...@@ -52,7 +52,7 @@ open class SplashActivity : Activity() {
handleRepackage() handleRepackage()
Notifications.setup(this) Notifications.setup(this)
UpdateCheckService.schedule(this) UpdateCheckService.schedule(this)
Shortcuts.setup(this) Shortcuts.setupDynamic(this)
// Pre-fetch network stuffs // Pre-fetch network stuffs
get<GithubRawServices>() get<GithubRawServices>()
......
...@@ -8,6 +8,7 @@ import com.topjohnwu.magisk.arch.* ...@@ -8,6 +8,7 @@ import com.topjohnwu.magisk.arch.*
import com.topjohnwu.magisk.core.base.BaseActivity import com.topjohnwu.magisk.core.base.BaseActivity
import com.topjohnwu.magisk.core.model.module.Repo import com.topjohnwu.magisk.core.model.module.Repo
import com.topjohnwu.magisk.view.MarkDownWindow import com.topjohnwu.magisk.view.MarkDownWindow
import com.topjohnwu.magisk.view.Shortcuts
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
class ViewActionEvent(val action: BaseActivity.() -> Unit) : ViewEvent(), ActivityExecutor { class ViewActionEvent(val action: BaseActivity.() -> Unit) : ViewEvent(), ActivityExecutor {
...@@ -82,3 +83,9 @@ class NavigationEvent( ...@@ -82,3 +83,9 @@ class NavigationEvent(
} }
} }
} }
class AddHomeIconEvent : ViewEvent(), ContextExecutor {
override fun invoke(context: Context) {
Shortcuts.addHomeIcon(context)
}
}
...@@ -8,20 +8,19 @@ import android.view.View ...@@ -8,20 +8,19 @@ import android.view.View
import android.view.ViewTreeObserver import android.view.ViewTreeObserver
import android.view.WindowManager import android.view.WindowManager
import androidx.coordinatorlayout.widget.CoordinatorLayout import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.content.pm.ShortcutManagerCompat
import androidx.core.view.forEach import androidx.core.view.forEach
import androidx.core.view.setPadding import androidx.core.view.setPadding
import androidx.core.view.updateLayoutParams import androidx.core.view.updateLayoutParams
import androidx.navigation.NavDirections import androidx.navigation.NavDirections
import com.google.android.material.card.MaterialCardView import com.google.android.material.card.MaterialCardView
import com.topjohnwu.magisk.BuildConfig
import com.topjohnwu.magisk.MainDirections import com.topjohnwu.magisk.MainDirections
import com.topjohnwu.magisk.R import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.arch.BaseUIActivity import com.topjohnwu.magisk.arch.BaseUIActivity
import com.topjohnwu.magisk.arch.BaseViewModel import com.topjohnwu.magisk.arch.BaseViewModel
import com.topjohnwu.magisk.arch.ReselectionTarget import com.topjohnwu.magisk.arch.ReselectionTarget
import com.topjohnwu.magisk.core.Const import com.topjohnwu.magisk.core.*
import com.topjohnwu.magisk.core.Info
import com.topjohnwu.magisk.core.SplashActivity
import com.topjohnwu.magisk.core.redirect
import com.topjohnwu.magisk.databinding.ActivityMainMd2Binding import com.topjohnwu.magisk.databinding.ActivityMainMd2Binding
import com.topjohnwu.magisk.ktx.startAnimations import com.topjohnwu.magisk.ktx.startAnimations
import com.topjohnwu.magisk.ui.home.HomeFragmentDirections import com.topjohnwu.magisk.ui.home.HomeFragmentDirections
...@@ -29,6 +28,7 @@ import com.topjohnwu.magisk.utils.HideBottomViewOnScrollBehavior ...@@ -29,6 +28,7 @@ import com.topjohnwu.magisk.utils.HideBottomViewOnScrollBehavior
import com.topjohnwu.magisk.utils.HideTopViewOnScrollBehavior import com.topjohnwu.magisk.utils.HideTopViewOnScrollBehavior
import com.topjohnwu.magisk.utils.HideableBehavior import com.topjohnwu.magisk.utils.HideableBehavior
import com.topjohnwu.magisk.view.MagiskDialog import com.topjohnwu.magisk.view.MagiskDialog
import com.topjohnwu.magisk.view.Shortcuts
import com.topjohnwu.superuser.Shell import com.topjohnwu.superuser.Shell
import org.koin.androidx.viewmodel.ext.android.viewModel import org.koin.androidx.viewmodel.ext.android.viewModel
...@@ -60,14 +60,8 @@ open class MainActivity : BaseUIActivity<MainViewModel, ActivityMainMd2Binding>( ...@@ -60,14 +60,8 @@ open class MainActivity : BaseUIActivity<MainViewModel, ActivityMainMd2Binding>(
return return
} }
if (Info.env.isUnsupported) { showUnsupportedMessage()
MagiskDialog(this) askForHomeShortcut()
.applyTitle(R.string.unsupport_magisk_title)
.applyMessage(R.string.unsupport_magisk_msg, Const.Version.MIN_VERSION)
.applyButton(MagiskDialog.ButtonType.POSITIVE) { titleRes = android.R.string.ok }
.cancellable(true)
.reveal()
}
window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE) window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE)
...@@ -189,6 +183,40 @@ open class MainActivity : BaseUIActivity<MainViewModel, ActivityMainMd2Binding>( ...@@ -189,6 +183,40 @@ open class MainActivity : BaseUIActivity<MainViewModel, ActivityMainMd2Binding>(
} }
} }
private fun showUnsupportedMessage() {
if (Info.env.isUnsupported) {
MagiskDialog(this)
.applyTitle(R.string.unsupport_magisk_title)
.applyMessage(R.string.unsupport_magisk_msg, Const.Version.MIN_VERSION)
.applyButton(MagiskDialog.ButtonType.POSITIVE) { titleRes = android.R.string.ok }
.cancellable(true)
.reveal()
}
}
private fun askForHomeShortcut() {
// Don't bother if we are not hidden
if (packageName == BuildConfig.APPLICATION_ID)
return
if (!Config.askedHome && ShortcutManagerCompat.isRequestPinShortcutSupported(this)) {
// Ask and show dialog
Config.askedHome = true
MagiskDialog(this)
.applyTitle(R.string.add_shortcut_title)
.applyMessage(R.string.add_shortcut_msg)
.applyButton(MagiskDialog.ButtonType.NEGATIVE) {
titleRes = R.string.no
}.applyButton(MagiskDialog.ButtonType.POSITIVE) {
titleRes = R.string.yes
onClick {
Shortcuts.addHomeIcon(this@MainActivity)
}
}.cancellable(true)
.reveal()
}
}
companion object { companion object {
private val ACTION_APPLICATION_PREFERENCES get() = private val ACTION_APPLICATION_PREFERENCES get() =
if (Build.VERSION.SDK_INT >= 24) Intent.ACTION_APPLICATION_PREFERENCES if (Build.VERSION.SDK_INT >= 24) Intent.ACTION_APPLICATION_PREFERENCES
......
...@@ -103,9 +103,10 @@ object Restore : BaseSettingsItem.Blank() { ...@@ -103,9 +103,10 @@ object Restore : BaseSettingsItem.Blank() {
override val description = R.string.settings_restore_manager_summary.asTransitive() override val description = R.string.settings_restore_manager_summary.asTransitive()
} }
@Suppress("FunctionName") object AddShortcut : BaseSettingsItem.Blank() {
fun HideOrRestore() = override val title = R.string.add_shortcut_title.asTransitive()
if (get<Context>().packageName == BuildConfig.APPLICATION_ID) Hide else Restore override val description = R.string.setting_add_shortcut_summary.asTransitive()
}
object DownloadPath : BaseSettingsItem.Input() { object DownloadPath : BaseSettingsItem.Input() {
override var value = Config.downloadPath override var value = Config.downloadPath
......
package com.topjohnwu.magisk.ui.settings package com.topjohnwu.magisk.ui.settings
import android.content.Context
import android.os.Build import android.os.Build
import android.view.View import android.view.View
import android.widget.Toast import android.widget.Toast
import androidx.core.content.pm.ShortcutManagerCompat
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.topjohnwu.magisk.BR import com.topjohnwu.magisk.BR
import com.topjohnwu.magisk.BuildConfig
import com.topjohnwu.magisk.R import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.arch.BaseViewModel import com.topjohnwu.magisk.arch.BaseViewModel
import com.topjohnwu.magisk.arch.adapterOf import com.topjohnwu.magisk.arch.adapterOf
...@@ -17,6 +20,7 @@ import com.topjohnwu.magisk.core.download.DownloadService ...@@ -17,6 +20,7 @@ import com.topjohnwu.magisk.core.download.DownloadService
import com.topjohnwu.magisk.core.download.DownloadSubject import com.topjohnwu.magisk.core.download.DownloadSubject
import com.topjohnwu.magisk.core.utils.PatchAPK import com.topjohnwu.magisk.core.utils.PatchAPK
import com.topjohnwu.magisk.data.database.RepoDao import com.topjohnwu.magisk.data.database.RepoDao
import com.topjohnwu.magisk.events.AddHomeIconEvent
import com.topjohnwu.magisk.events.RecreateEvent import com.topjohnwu.magisk.events.RecreateEvent
import com.topjohnwu.magisk.events.dialog.BiometricDialog import com.topjohnwu.magisk.events.dialog.BiometricDialog
import com.topjohnwu.magisk.utils.Utils import com.topjohnwu.magisk.utils.Utils
...@@ -39,6 +43,9 @@ class SettingsViewModel( ...@@ -39,6 +43,9 @@ class SettingsViewModel(
} }
private fun createItems(): List<BaseSettingsItem> { private fun createItems(): List<BaseSettingsItem> {
val context = get<Context>()
val hidden = context.packageName != BuildConfig.APPLICATION_ID
// Customization // Customization
val list = mutableListOf( val list = mutableListOf(
Customization, Customization,
...@@ -49,6 +56,8 @@ class SettingsViewModel( ...@@ -49,6 +56,8 @@ class SettingsViewModel(
// making theming a pain in the ass. Just forget about it // making theming a pain in the ass. Just forget about it
list.remove(Theme) list.remove(Theme)
} }
if (hidden && ShortcutManagerCompat.isRequestPinShortcutSupported(context))
list.add(AddShortcut)
// Manager // Manager
list.addAll(listOf( list.addAll(listOf(
...@@ -58,7 +67,7 @@ class SettingsViewModel( ...@@ -58,7 +67,7 @@ class SettingsViewModel(
if (Info.env.isActive) { if (Info.env.isActive) {
list.add(ClearRepoCache) list.add(ClearRepoCache)
if (Const.USER_ID == 0 && Info.isConnected.get()) if (Const.USER_ID == 0 && Info.isConnected.get())
list.add(HideOrRestore()) list.add(if (hidden) Restore else Hide)
} }
// Magisk // Magisk
...@@ -96,6 +105,7 @@ class SettingsViewModel( ...@@ -96,6 +105,7 @@ class SettingsViewModel(
is ClearRepoCache -> clearRepoCache() is ClearRepoCache -> clearRepoCache()
is SystemlessHosts -> createHosts() is SystemlessHosts -> createHosts()
is Restore -> restoreManager() is Restore -> restoreManager()
is AddShortcut -> AddHomeIconEvent().publish()
else -> callback() else -> callback()
} }
......
...@@ -4,53 +4,62 @@ import android.content.Context ...@@ -4,53 +4,62 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.pm.ShortcutInfo import android.content.pm.ShortcutInfo
import android.content.pm.ShortcutManager import android.content.pm.ShortcutManager
import android.graphics.drawable.Icon
import android.os.Build import android.os.Build
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import androidx.core.content.getSystemService import androidx.core.content.getSystemService
import androidx.core.graphics.drawable.toAdaptiveIcon import androidx.core.content.pm.ShortcutInfoCompat
import androidx.core.graphics.drawable.toIcon import androidx.core.content.pm.ShortcutManagerCompat
import androidx.core.graphics.drawable.IconCompat
import com.topjohnwu.magisk.R import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.Const import com.topjohnwu.magisk.core.Const
import com.topjohnwu.magisk.core.Info import com.topjohnwu.magisk.core.Info
import com.topjohnwu.magisk.core.SplashActivity
import com.topjohnwu.magisk.core.intent
import com.topjohnwu.magisk.ktx.getBitmap import com.topjohnwu.magisk.ktx.getBitmap
import com.topjohnwu.magisk.utils.Utils import com.topjohnwu.magisk.utils.Utils
object Shortcuts { object Shortcuts {
fun setup(context: Context) { fun setupDynamic(context: Context) {
if (Build.VERSION.SDK_INT >= 25) { if (Build.VERSION.SDK_INT >= 25) {
val manager = context.getSystemService<ShortcutManager>() val manager = context.getSystemService<ShortcutManager>() ?: return
manager?.dynamicShortcuts = manager.dynamicShortcuts = getShortCuts(context)
getShortCuts(context)
} }
} }
fun addHomeIcon(context: Context) {
val intent = context.packageManager.getLaunchIntentForPackage(context.packageName) ?: return
val info = ShortcutInfoCompat.Builder(context, Const.Nav.HOME)
.setShortLabel(context.getString(R.string.app_name))
.setIntent(intent)
.setIcon(context.getIconCompat(R.drawable.ic_launcher))
.build()
ShortcutManagerCompat.requestPinShortcut(context, info, null)
}
private fun Context.getIconCompat(id: Int): IconCompat {
return if (Build.VERSION.SDK_INT >= 26)
IconCompat.createWithAdaptiveBitmap(getBitmap(id))
else
IconCompat.createWithBitmap(getBitmap(id))
}
@RequiresApi(api = 23)
private fun Context.getIcon(id: Int) = getIconCompat(id).toIcon(this)
@RequiresApi(api = 25) @RequiresApi(api = 25)
private fun getShortCuts(context: Context): List<ShortcutInfo> { private fun getShortCuts(context: Context): List<ShortcutInfo> {
val shortCuts = mutableListOf<ShortcutInfo>() val intent = context.packageManager.getLaunchIntentForPackage(context.packageName)
val intent = context.intent<SplashActivity>() ?: return emptyList()
fun getIcon(id: Int): Icon { val shortCuts = mutableListOf<ShortcutInfo>()
return if (Build.VERSION.SDK_INT >= 26)
context.getBitmap(id).toAdaptiveIcon()
else
context.getBitmap(id).toIcon()
}
if (Utils.showSuperUser()) { if (Utils.showSuperUser()) {
shortCuts.add( shortCuts.add(
ShortcutInfo.Builder(context, Const.Nav.SUPERUSER) ShortcutInfo.Builder(context, Const.Nav.SUPERUSER)
.setShortLabel(context.getString(R.string.superuser)) .setShortLabel(context.getString(R.string.superuser))
.setIntent( .setIntent(
Intent(intent) Intent(intent).putExtra(Const.Key.OPEN_SECTION, Const.Nav.SUPERUSER)
.putExtra(Const.Key.OPEN_SECTION, Const.Nav.SUPERUSER)
.setAction(Intent.ACTION_VIEW)
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
) )
.setIcon(getIcon(R.drawable.sc_superuser)) .setIcon(context.getIcon(R.drawable.sc_superuser))
.setRank(0) .setRank(0)
.build() .build()
) )
...@@ -60,12 +69,9 @@ object Shortcuts { ...@@ -60,12 +69,9 @@ object Shortcuts {
ShortcutInfo.Builder(context, Const.Nav.HIDE) ShortcutInfo.Builder(context, Const.Nav.HIDE)
.setShortLabel(context.getString(R.string.magiskhide)) .setShortLabel(context.getString(R.string.magiskhide))
.setIntent( .setIntent(
Intent(intent) Intent(intent).putExtra(Const.Key.OPEN_SECTION, Const.Nav.HIDE)
.putExtra(Const.Key.OPEN_SECTION, Const.Nav.HIDE)
.setAction(Intent.ACTION_VIEW)
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
) )
.setIcon(getIcon(R.drawable.sc_magiskhide)) .setIcon(context.getIcon(R.drawable.sc_magiskhide))
.setRank(1) .setRank(1)
.build() .build()
) )
...@@ -75,12 +81,9 @@ object Shortcuts { ...@@ -75,12 +81,9 @@ object Shortcuts {
ShortcutInfo.Builder(context, Const.Nav.MODULES) ShortcutInfo.Builder(context, Const.Nav.MODULES)
.setShortLabel(context.getString(R.string.modules)) .setShortLabel(context.getString(R.string.modules))
.setIntent( .setIntent(
Intent(intent) Intent(intent).putExtra(Const.Key.OPEN_SECTION, Const.Nav.MODULES)
.putExtra(Const.Key.OPEN_SECTION, Const.Nav.MODULES)
.setAction(Intent.ACTION_VIEW)
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
) )
.setIcon(getIcon(R.drawable.sc_extension)) .setIcon(context.getIcon(R.drawable.sc_extension))
.setRank(2) .setRank(2)
.build() .build()
) )
......
...@@ -174,6 +174,7 @@ ...@@ -174,6 +174,7 @@
<string name="settings_su_biometric_summary">Use biometric authentication to allow superuser requests</string> <string name="settings_su_biometric_summary">Use biometric authentication to allow superuser requests</string>
<string name="no_biometric">Unsupported device or no biometric settings are enabled</string> <string name="no_biometric">Unsupported device or no biometric settings are enabled</string>
<string name="settings_customization">Customization</string> <string name="settings_customization">Customization</string>
<string name="setting_add_shortcut_summary">Add a pretty shortcut in the home screen in case the name and icon are difficult to recognize after hiding the app</string>
<string name="multiuser_mode">Multiuser Mode</string> <string name="multiuser_mode">Multiuser Mode</string>
<string name="settings_owner_only">Device Owner Only</string> <string name="settings_owner_only">Device Owner Only</string>
...@@ -231,5 +232,7 @@ ...@@ -231,5 +232,7 @@
<string name="unsupport_magisk_title">Unsupported Magisk Version</string> <string name="unsupport_magisk_title">Unsupported Magisk Version</string>
<string name="unsupport_magisk_msg">This version of Magisk Manager does not support Magisk version lower than %1$s.\n\nThe app will behave as if no Magisk is installed, please upgrade Magisk as soon as possible.</string> <string name="unsupport_magisk_msg">This version of Magisk Manager does not support Magisk version lower than %1$s.\n\nThe app will behave as if no Magisk is installed, please upgrade Magisk as soon as possible.</string>
<string name="external_rw_permission_denied">Grant storage permission to enable this functionality</string> <string name="external_rw_permission_denied">Grant storage permission to enable this functionality</string>
<string name="add_shortcut_title">Add shortcut to home screen</string>
<string name="add_shortcut_msg">After hiding Magisk Manager, its name and icon might become difficult to recognize. Do you want to add a pretty shortcut to the home screen?</string>
</resources> </resources>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment