Update app to target API 33

Close #6206
This commit is contained in:
topjohnwu 2022-08-23 03:59:09 -07:00
parent 3f7f6e619a
commit 928a16d8cc
20 changed files with 114 additions and 64 deletions

View File

@ -9,6 +9,7 @@
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<uses-permission android:name="android.permission.HIDE_OVERLAY_WINDOWS" />
<uses-permission android:name="android.permission.UPDATE_PACKAGES_WITHOUT_USER_ACTION" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="29"

View File

@ -39,6 +39,16 @@ public final class APKInstall {
}
}
public static void registerReceiver(
Context context, BroadcastReceiver receiver, IntentFilter filter) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
// noinspection InlinedApi
context.registerReceiver(receiver, filter, Context.RECEIVER_NOT_EXPORTED);
} else {
context.registerReceiver(receiver, filter);
}
}
public static Session startSession(Context context) {
return startSession(context, null, null, null);
}
@ -51,9 +61,9 @@ public final class APKInstall {
// If pkg is not null, look for package added event
var filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
filter.addDataScheme("package");
context.registerReceiver(receiver, filter);
registerReceiver(context, receiver, filter);
}
context.registerReceiver(receiver, new IntentFilter(receiver.sessionId));
registerReceiver(context, receiver, new IntentFilter(receiver.sessionId));
return receiver;
}

View File

@ -1,7 +1,6 @@
package com.topjohnwu.magisk.arch
import android.Manifest.permission.REQUEST_INSTALL_PACKAGES
import android.Manifest.permission.WRITE_EXTERNAL_STORAGE
import android.Manifest.permission.*
import android.annotation.SuppressLint
import android.os.Bundle
import androidx.databinding.PropertyChangeRegistry
@ -53,6 +52,17 @@ abstract class BaseViewModel : ViewModel(), ObservableHost {
}
}
@SuppressLint("InlinedApi")
inline fun withPostNotificationPermission(crossinline callback: () -> Unit) {
withPermission(POST_NOTIFICATIONS) {
if (!it) {
SnackbarEvent(R.string.post_notifications_denied).publish()
} else {
callback()
}
}
}
fun back() = BackPressEvent().publish()
fun <Event : ViewEvent> Event.publish() {

View File

@ -1,7 +1,6 @@
package com.topjohnwu.magisk.core.base
import android.Manifest.permission.REQUEST_INSTALL_PACKAGES
import android.Manifest.permission.WRITE_EXTERNAL_STORAGE
import android.Manifest.permission.*
import android.app.Activity
import android.content.ActivityNotFoundException
import android.content.Context
@ -82,12 +81,18 @@ abstract class BaseActivity : AppCompatActivity() {
}
fun withPermission(permission: String, callback: (Boolean) -> Unit) {
if (permission == WRITE_EXTERNAL_STORAGE &&
Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R &&
permission == WRITE_EXTERNAL_STORAGE) {
// We do not need external rw on R+
callback(true)
return
}
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU &&
permission == POST_NOTIFICATIONS) {
// All apps have notification permissions before T
callback(true)
return
}
permissionCallback = callback
if (permission == REQUEST_INSTALL_PACKAGES) {
requestInstall.launch(Unit)

View File

@ -1,5 +1,6 @@
package com.topjohnwu.magisk.core.download
import android.Manifest
import android.annotation.SuppressLint
import android.app.PendingIntent
import android.app.PendingIntent.*
@ -13,17 +14,14 @@ import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.StubApk
import com.topjohnwu.magisk.core.ActivityTracker
import com.topjohnwu.magisk.core.Info
import com.topjohnwu.magisk.core.base.BaseActivity
import com.topjohnwu.magisk.core.intent
import com.topjohnwu.magisk.core.isRunningAsStub
import com.topjohnwu.magisk.core.tasks.HideAPK
import com.topjohnwu.magisk.core.utils.MediaStoreUtils
import com.topjohnwu.magisk.core.utils.MediaStoreUtils.outputStream
import com.topjohnwu.magisk.ktx.copyAndClose
import com.topjohnwu.magisk.ktx.forEach
import com.topjohnwu.magisk.ktx.withStreams
import com.topjohnwu.magisk.ktx.writeTo
import com.topjohnwu.magisk.ktx.*
import com.topjohnwu.magisk.utils.APKInstall
import com.topjohnwu.magisk.view.Notifications
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
@ -51,7 +49,7 @@ class DownloadService : NotificationService() {
}
private fun download(subject: Subject) {
update(subject.notifyId)
notifyUpdate(subject.notifyId)
val coroutineScope = CoroutineScope(job + Dispatchers.IO)
coroutineScope.launch {
try {
@ -62,7 +60,7 @@ class DownloadService : NotificationService() {
}
val activity = ActivityTracker.foreground
if (activity != null && subject.autoLaunch) {
remove(subject.notifyId)
notifyRemove(subject.notifyId)
subject.pendingIntent(activity)?.send()
} else {
notifyFinish(subject)
@ -92,7 +90,7 @@ class DownloadService : NotificationService() {
if (Info.stub!!.version < subject.stub.versionCode) {
// Also upgrade stub
update(subject.notifyId) {
notifyUpdate(subject.notifyId) {
it.setProgress(0, 0, true)
.setContentTitle(getString(R.string.hide_app_title))
.setContentText("")
@ -118,7 +116,7 @@ class DownloadService : NotificationService() {
StubApk.restartProcess(it)
} ?: run {
// Or else kill the current process after posting notification
subject.intent = Notifications.selfLaunchIntent(this)
subject.intent = selfLaunchIntent()
subject.postDownload = { Runtime.getRuntime().exit(0) }
}
return
@ -206,12 +204,16 @@ class DownloadService : NotificationService() {
}
}
fun start(context: Context, subject: Subject) {
val app = context.applicationContext
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
app.startForegroundService(intent(app, subject))
} else {
app.startService(intent(app, subject))
@SuppressLint("InlinedApi")
fun start(activity: BaseActivity, subject: Subject) {
activity.withPermission(Manifest.permission.POST_NOTIFICATIONS) {
// Always download regardless of notification permission status
val app = activity.applicationContext
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
app.startForegroundService(intent(app, subject))
} else {
app.startService(intent(app, subject))
}
}
}
}

View File

@ -2,6 +2,7 @@ package com.topjohnwu.magisk.core.download
import android.app.Notification
import android.content.Intent
import android.os.Build
import androidx.lifecycle.MutableLiveData
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.base.BaseService
@ -30,11 +31,11 @@ open class NotificationService : BaseService() {
val total = max.toFloat() / 1048576
val id = subject.notifyId
update(id) { it.setContentTitle(subject.title) }
notifyUpdate(id) { it.setContentTitle(subject.title) }
return ProgressInputStream(byteStream()) {
val progress = it.toFloat() / 1048576
update(id) { notification ->
notifyUpdate(id) { notification ->
if (max > 0) {
broadcast(progress / total, subject)
notification
@ -49,7 +50,7 @@ open class NotificationService : BaseService() {
}
private fun finalNotify(id: Int, editor: (Notification.Builder) -> Unit): Int {
val notification = remove(id)?.also(editor) ?: return -1
val notification = notifyRemove(id)?.also(editor) ?: return -1
val newId = Notifications.nextId()
Notifications.mgr.notify(newId, notification.build())
return newId
@ -73,28 +74,31 @@ open class NotificationService : BaseService() {
subject.pendingIntent(this)?.let { intent -> it.setContentIntent(intent) }
}
private fun create() = Notifications.progress(this, "")
private fun updateForeground() {
private fun updateForegroundState() {
if (hasNotifications) {
val (id, notification) = notifications.entries.first()
startForeground(id, notification.build())
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
stopForeground(STOP_FOREGROUND_DETACH)
} else {
@Suppress("DEPRECATION")
stopForeground(false)
}
}
protected fun update(id: Int, editor: (Notification.Builder) -> Unit = {}) {
protected fun notifyUpdate(id: Int, editor: (Notification.Builder) -> Unit = {}) {
fun create() = Notifications.startProgress(this, "")
val wasEmpty = !hasNotifications
val notification = notifications.getOrPut(id, ::create).also(editor)
if (wasEmpty)
updateForeground()
updateForegroundState()
else
Notifications.mgr.notify(id, notification.build())
}
protected fun remove(id: Int): Notification.Builder? {
val n = notifications.remove(id)?.also { updateForeground() }
protected fun notifyRemove(id: Int): Notification.Builder? {
val n = notifications.remove(id)?.also { updateForegroundState() }
Notifications.mgr.cancel(id)
return n
}

View File

@ -10,6 +10,7 @@ import android.content.IntentFilter
import android.net.NetworkCapabilities
import android.os.PowerManager
import androidx.core.content.getSystemService
import com.topjohnwu.magisk.ktx.registerRuntimeReceiver
@TargetApi(23)
class MarshmallowNetworkObserver(
@ -21,7 +22,7 @@ class MarshmallowNetworkObserver(
init {
val filter = IntentFilter(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED)
app.registerReceiver(receiver, filter)
app.registerRuntimeReceiver(receiver, filter)
}
override fun stopObserving() {

View File

@ -8,9 +8,7 @@ import com.topjohnwu.magisk.view.MagiskDialog
abstract class DialogEvent : ViewEvent(), ActivityExecutor {
override fun invoke(activity: UIActivity<*>) {
MagiskDialog(activity)
.apply { setOwnerActivity(activity) }
.apply(this::build).show()
MagiskDialog(activity).apply(this::build).show()
}
abstract fun build(dialog: MagiskDialog)

View File

@ -29,7 +29,7 @@ class ManagerInstallDialog : MarkDownDialog() {
setCancelable(true)
setButton(MagiskDialog.ButtonType.POSITIVE) {
text = R.string.install
onClick { DownloadService.start(context, Subject.App()) }
onClick { DownloadService.start(activity, Subject.App()) }
}
setButton(MagiskDialog.ButtonType.NEGATIVE) {
text = android.R.string.cancel

View File

@ -5,7 +5,6 @@ import android.widget.TextView
import androidx.annotation.CallSuper
import androidx.lifecycle.lifecycleScope
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.base.BaseActivity
import com.topjohnwu.magisk.core.di.ServiceLocator
import com.topjohnwu.magisk.view.MagiskDialog
import kotlinx.coroutines.Dispatchers
@ -24,7 +23,7 @@ abstract class MarkDownDialog : DialogEvent() {
val view = LayoutInflater.from(context).inflate(R.layout.markdown_window_md2, null)
setView(view)
val tv = view.findViewById<TextView>(R.id.md_txt)
(ownerActivity as BaseActivity).lifecycleScope.launch {
activity.lifecycleScope.launch {
try {
val text = withContext(Dispatchers.IO) { getMarkdownText() }
ServiceLocator.markwon.setMarkdown(tv, text)

View File

@ -24,7 +24,7 @@ class ModuleInstallDialog(private val item: OnlineModule) : MarkDownDialog() {
fun download(install: Boolean) {
val action = if (install) Action.Flash else Action.Download
val subject = Subject.Module(item, action)
DownloadService.start(context, subject)
DownloadService.start(activity, subject)
}
val title = context.getString(R.string.repo_install_title,

View File

@ -2,10 +2,7 @@ package com.topjohnwu.magisk.ktx
import android.annotation.SuppressLint
import android.app.Activity
import android.content.ComponentName
import android.content.Context
import android.content.ContextWrapper
import android.content.Intent
import android.content.*
import android.content.pm.ApplicationInfo
import android.content.pm.PackageInfo
import android.content.pm.PackageManager
@ -33,6 +30,7 @@ import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.Const
import com.topjohnwu.magisk.core.utils.RootUtils
import com.topjohnwu.magisk.core.utils.currentLocale
import com.topjohnwu.magisk.utils.APKInstall
import com.topjohnwu.superuser.Shell
import java.io.File
import kotlin.Array
@ -266,3 +264,14 @@ fun PackageManager.getPackageInfo(uid: Int, pid: Int): PackageInfo? {
}
throw PackageManager.NameNotFoundException()
}
fun Context.registerRuntimeReceiver(receiver: BroadcastReceiver, filter: IntentFilter) {
APKInstall.registerReceiver(this, receiver, filter)
}
fun Context.selfLaunchIntent(): Intent {
val pm = packageManager
val intent = pm.getLaunchIntentForPackage(packageName)!!
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
return intent
}

View File

@ -1,5 +1,7 @@
package com.topjohnwu.magisk.ui
import android.Manifest
import android.annotation.SuppressLint
import android.content.Intent
import android.content.pm.ApplicationInfo
import android.os.Bundle
@ -52,11 +54,19 @@ class MainActivity : SplashActivity<ActivityMainMd2Binding>() {
private var isRootFragment = true
@SuppressLint("InlinedApi")
override fun showMainUI(savedInstanceState: Bundle?) {
setContentView()
showUnsupportedMessage()
askForHomeShortcut()
// Ask permission to post notifications for background update check
if (Config.checkUpdate) {
withPermission(Manifest.permission.POST_NOTIFICATIONS) {
Config.checkUpdate = it
}
}
window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE)
navigation.addOnDestinationChangedListener { _, destination, _ ->

View File

@ -8,6 +8,7 @@ import com.topjohnwu.magisk.BR
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.databinding.ObservableRvItem
import com.topjohnwu.magisk.databinding.set
import com.topjohnwu.magisk.ktx.activity
import com.topjohnwu.magisk.utils.TextHolder
import com.topjohnwu.magisk.view.MagiskDialog
@ -57,6 +58,8 @@ sealed class BaseSettingsItem : ObservableRvItem() {
set(checked, value, { onPressed(view, handler) })
override fun onPressed(view: View, handler: Handler) {
// Make sure the checked state is synced
notifyPropertyChanged(BR.checked)
handler.onItemPressed(view, this) {
value = !value
notifyPropertyChanged(BR.checked)
@ -72,7 +75,7 @@ sealed class BaseSettingsItem : ObservableRvItem() {
override fun onPressed(view: View, handler: Handler) {
handler.onItemPressed(view, this) {
MagiskDialog(view.context).apply {
MagiskDialog(view.activity).apply {
setTitle(title.getText(view.resources))
setView(getView(view.context))
setButton(MagiskDialog.ButtonType.POSITIVE) {
@ -115,7 +118,7 @@ sealed class BaseSettingsItem : ObservableRvItem() {
override fun onPressed(view: View, handler: Handler) {
handler.onItemPressed(view, this) {
MagiskDialog(view.context).apply {
MagiskDialog(view.activity).apply {
setTitle(title.getText(view.resources))
setButton(MagiskDialog.ButtonType.NEGATIVE) {
text = android.R.string.cancel

View File

@ -23,6 +23,7 @@ import com.topjohnwu.magisk.databinding.DialogSettingsAppNameBinding
import com.topjohnwu.magisk.databinding.DialogSettingsDownloadPathBinding
import com.topjohnwu.magisk.databinding.DialogSettingsUpdateChannelBinding
import com.topjohnwu.magisk.databinding.set
import com.topjohnwu.magisk.ktx.activity
import com.topjohnwu.magisk.utils.Utils
import com.topjohnwu.magisk.utils.asText
import com.topjohnwu.magisk.view.MagiskDialog
@ -111,7 +112,7 @@ object Restore : BaseSettingsItem.Blank() {
override fun onPressed(view: View, handler: Handler) {
handler.onItemPressed(view, this) {
MagiskDialog(view.context).apply {
MagiskDialog(view.activity).apply {
setTitle(R.string.settings_restore_app_title)
setMessage(R.string.restore_app_confirmation)
setButton(MagiskDialog.ButtonType.POSITIVE) {

View File

@ -96,6 +96,7 @@ class SettingsViewModel : BaseViewModel(), BaseSettingsItem.Handler {
override fun onItemPressed(view: View, item: BaseSettingsItem, andThen: () -> Unit) {
when (item) {
DownloadPath -> withExternalRW(andThen)
UpdateChecker -> withPostNotificationPermission(andThen)
Biometrics -> authenticate(andThen)
Theme -> SettingsFragmentDirections.actionSettingsFragmentToThemeFragment().navigate()
DenyListConfig -> SettingsFragmentDirections.actionSettingsFragmentToDenyFragment().navigate()

View File

@ -1,6 +1,6 @@
package com.topjohnwu.magisk.view
import android.content.Context
import android.app.Activity
import android.content.DialogInterface
import android.content.res.ColorStateList
import android.graphics.drawable.Drawable
@ -21,22 +21,26 @@ import com.google.android.material.color.MaterialColors
import com.google.android.material.shape.MaterialShapeDrawable
import com.topjohnwu.magisk.BR
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.base.BaseActivity
import com.topjohnwu.magisk.databinding.*
import com.topjohnwu.magisk.view.MagiskDialog.DialogClickListener
typealias DialogButtonClickListener = (DialogInterface) -> Unit
class MagiskDialog(
context: Context, theme: Int = 0
context: Activity, theme: Int = 0
) : AppCompatDialog(context, theme) {
private val binding: DialogMagiskBaseBinding =
DialogMagiskBaseBinding.inflate(LayoutInflater.from(context))
private val data = Data()
val activity: BaseActivity get() = ownerActivity as BaseActivity
init {
binding.setVariable(BR.data, data)
setCancelable(true)
setOwnerActivity(context)
}
inner class Data : ObservableHost {

View File

@ -6,7 +6,6 @@ import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.os.Build
import android.os.Build.VERSION.SDK_INT
import androidx.core.content.getSystemService
@ -16,6 +15,7 @@ import com.topjohnwu.magisk.core.di.AppContext
import com.topjohnwu.magisk.core.download.DownloadService
import com.topjohnwu.magisk.core.download.Subject
import com.topjohnwu.magisk.ktx.getBitmap
import com.topjohnwu.magisk.ktx.selfLaunchIntent
import java.util.concurrent.atomic.AtomicInteger
@Suppress("DEPRECATION")
@ -44,18 +44,10 @@ object Notifications {
}
}
fun selfLaunchIntent(context: Context): Intent {
val pm = context.packageManager
val intent = pm.getLaunchIntentForPackage(context.packageName)!!
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
return intent
}
@SuppressLint("InlinedApi")
fun updateDone(context: Context) {
setup(context)
val flag = PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
val pending = PendingIntent.getActivity(context, 0, selfLaunchIntent(context), flag)
val pending = PendingIntent.getActivity(context, 0, context.selfLaunchIntent(), flag)
val builder = if (SDK_INT >= Build.VERSION_CODES.O) {
Notification.Builder(context, UPDATED_CHANNEL)
.setSmallIcon(context.getBitmap(R.drawable.ic_magisk_outline).toIcon())
@ -72,7 +64,6 @@ object Notifications {
fun updateAvailable(context: Context) {
val intent = DownloadService.getPendingIntent(context, Subject.App())
val bitmap = context.getBitmap(R.drawable.ic_magisk_outline)
val builder = if (SDK_INT >= Build.VERSION_CODES.O) {
Notification.Builder(context, UPDATE_CHANNEL)
@ -90,7 +81,7 @@ object Notifications {
mgr.notify(APP_UPDATE_NOTIFICATION_ID, builder.build())
}
fun progress(context: Context, title: CharSequence): Notification.Builder {
fun startProgress(context: Context, title: CharSequence): Notification.Builder {
val builder = if (SDK_INT >= Build.VERSION_CODES.O) {
Notification.Builder(context, PROGRESS_CHANNEL)
} else {

View File

@ -230,6 +230,7 @@
<string name="unsupport_nonroot_stub_msg">The hidden Magisk app cannot continue to work because root was lost. Please restore the original APK.</string>
<string name="unsupport_nonroot_stub_title">@string/settings_restore_app_title</string>
<string name="external_rw_permission_denied">Grant storage permission to enable this functionality</string>
<string name="post_notifications_denied">Grant notifications permission to enable this functionality</string>
<string name="install_unknown_denied">Allow "install unknown apps" to enable this functionality</string>
<string name="add_shortcut_title">Add shortcut to home screen</string>
<string name="add_shortcut_msg">After hiding this app, its name and icon might become difficult to recognize. Do you want to add a pretty shortcut to the home screen?</string>

View File

@ -43,13 +43,13 @@ private val Project.android: BaseAppModuleExtension
fun Project.setupCommon() {
androidBase {
compileSdkVersion(32)
compileSdkVersion(33)
buildToolsVersion = "32.0.0"
ndkPath = "$sdkDirectory/ndk/magisk"
defaultConfig {
minSdk = 21
targetSdk = 32
targetSdk = 33
}
compileOptions {