Updated flash screen so it's a fragment

The FlashActivity has been removed and all of it's functionality has been transferred to the FlashFragment.
The FlashFragment needs to be however launched in a different way than the activity using the MainActivity's stub and so seemingly massive changes had to be made.

Notably the RemoteFileService didn't seem to be calling Service.startForeground(), which has been crashing the application due to the system requirements, so that's been fixed.
This commit is contained in:
Viktor De Pasquale 2020-03-24 18:07:20 +01:00 committed by John Wu
parent fc2d0246e6
commit 6907651756
19 changed files with 435 additions and 369 deletions

View File

@ -29,9 +29,6 @@
<!-- Main --> <!-- Main -->
<activity android:name="a.b" /> <activity android:name="a.b" />
<!-- Flashing -->
<activity android:name="a.f" />
<!-- Superuser --> <!-- Superuser -->
<activity <activity
android:name="a.m" android:name="a.m"

View File

@ -4,7 +4,6 @@ import com.topjohnwu.magisk.core.App
import com.topjohnwu.magisk.core.GeneralReceiver import com.topjohnwu.magisk.core.GeneralReceiver
import com.topjohnwu.magisk.core.SplashActivity import com.topjohnwu.magisk.core.SplashActivity
import com.topjohnwu.magisk.core.download.DownloadService import com.topjohnwu.magisk.core.download.DownloadService
import com.topjohnwu.magisk.legacy.flash.FlashActivity
import com.topjohnwu.magisk.legacy.surequest.SuRequestActivity import com.topjohnwu.magisk.legacy.surequest.SuRequestActivity
import com.topjohnwu.magisk.ui.MainActivity import com.topjohnwu.magisk.ui.MainActivity
@ -17,8 +16,6 @@ class e : App {
constructor(o: Any) : super(o) constructor(o: Any) : super(o)
} }
class f : FlashActivity()
class h : GeneralReceiver() class h : GeneralReceiver()
class j : DownloadService() class j : DownloadService()

View File

@ -64,6 +64,7 @@ object Const {
const val OPEN_SECTION = "section" const val OPEN_SECTION = "section"
const val OPEN_SETTINGS = "settings" const val OPEN_SETTINGS = "settings"
const val INTENT_SET_APP = "app_json" const val INTENT_SET_APP = "app_json"
const val FLASH_INSTALLER = "installer"
const val FLASH_ACTION = "action" const val FLASH_ACTION = "action"
const val FLASH_DATA = "additional_data" const val FLASH_DATA = "additional_data"
const val DISMISS_ID = "dismiss_id" const val DISMISS_ID = "dismiss_id"

View File

@ -21,7 +21,6 @@ import com.topjohnwu.magisk.core.download.DownloadService
import com.topjohnwu.magisk.core.utils.refreshLocale import com.topjohnwu.magisk.core.utils.refreshLocale
import com.topjohnwu.magisk.core.utils.updateConfig import com.topjohnwu.magisk.core.utils.updateConfig
import com.topjohnwu.magisk.extensions.forceGetDeclaredField import com.topjohnwu.magisk.extensions.forceGetDeclaredField
import com.topjohnwu.magisk.legacy.flash.FlashActivity
import com.topjohnwu.magisk.legacy.surequest.SuRequestActivity import com.topjohnwu.magisk.legacy.surequest.SuRequestActivity
import com.topjohnwu.magisk.ui.MainActivity import com.topjohnwu.magisk.ui.MainActivity
@ -146,7 +145,6 @@ object ClassMap {
App::class.java to a.e::class.java, App::class.java to a.e::class.java,
MainActivity::class.java to a.b::class.java, MainActivity::class.java to a.b::class.java,
SplashActivity::class.java to a.c::class.java, SplashActivity::class.java to a.c::class.java,
FlashActivity::class.java to a.f::class.java,
GeneralReceiver::class.java to a.h::class.java, GeneralReceiver::class.java to a.h::class.java,
DownloadService::class.java to a.j::class.java, DownloadService::class.java to a.j::class.java,
SuRequestActivity::class.java to a.m::class.java, SuRequestActivity::class.java to a.m::class.java,

View File

@ -14,11 +14,11 @@ import com.topjohnwu.magisk.extensions.chooser
import com.topjohnwu.magisk.extensions.exists import com.topjohnwu.magisk.extensions.exists
import com.topjohnwu.magisk.extensions.provide import com.topjohnwu.magisk.extensions.provide
import com.topjohnwu.magisk.extensions.subscribeK import com.topjohnwu.magisk.extensions.subscribeK
import com.topjohnwu.magisk.legacy.flash.FlashActivity
import com.topjohnwu.magisk.model.entity.internal.Configuration.* import com.topjohnwu.magisk.model.entity.internal.Configuration.*
import com.topjohnwu.magisk.model.entity.internal.Configuration.Flash.Secondary import com.topjohnwu.magisk.model.entity.internal.Configuration.Flash.Secondary
import com.topjohnwu.magisk.model.entity.internal.DownloadSubject import com.topjohnwu.magisk.model.entity.internal.DownloadSubject
import com.topjohnwu.magisk.model.entity.internal.DownloadSubject.* import com.topjohnwu.magisk.model.entity.internal.DownloadSubject.*
import com.topjohnwu.magisk.ui.flash.FlashFragment
import com.topjohnwu.magisk.utils.APKInstall import com.topjohnwu.magisk.utils.APKInstall
import io.reactivex.Completable import io.reactivex.Completable
import org.koin.core.get import org.koin.core.get
@ -45,10 +45,12 @@ open class DownloadService : RemoteFileService() {
subject: Magisk, subject: Magisk,
id: Int id: Int
) = when (val conf = subject.configuration) { ) = when (val conf = subject.configuration) {
Uninstall -> FlashActivity.uninstall(this, subject.file, id) Uninstall -> FlashFragment.uninstall(subject.file, id)
EnvFix -> { remove(id); EnvFixTask(subject.file).exec() } EnvFix -> {
is Patch -> FlashActivity.patch(this, subject.file, conf.fileUri, id) remove(id); EnvFixTask(subject.file).exec()
is Flash -> FlashActivity.flash(this, subject.file, conf is Secondary, id) }
is Patch -> FlashFragment.patch(subject.file, conf.fileUri, id)
is Flash -> FlashFragment.flash(subject.file, conf is Secondary, id)
else -> Unit else -> Unit
} }
@ -56,7 +58,7 @@ open class DownloadService : RemoteFileService() {
subject: Module, subject: Module,
id: Int id: Int
) = when (subject.configuration) { ) = when (subject.configuration) {
is Flash -> FlashActivity.install(this, subject.file, id) is Flash -> FlashFragment.install(subject.file, id)
else -> Unit else -> Unit
} }
@ -94,9 +96,15 @@ open class DownloadService : RemoteFileService() {
.takeIf { it.exists(get()) } .takeIf { it.exists(get()) }
?.let { addAction(0, R.string.download_open_self, it.chooser()) } ?.let { addAction(0, R.string.download_open_self, it.chooser()) }
} }
Uninstall -> setContentIntent(FlashActivity.uninstallIntent(context, subject.file)) Uninstall -> setContentIntent(FlashFragment.uninstallIntent(context, subject.file))
is Flash -> setContentIntent(FlashActivity.flashIntent(context, subject.file, conf is Secondary)) is Flash -> setContentIntent(
is Patch -> setContentIntent(FlashActivity.patchIntent(context, subject.file, conf.fileUri)) FlashFragment.flashIntent(
context,
subject.file,
conf is Secondary
)
)
is Patch -> setContentIntent(FlashFragment.patchIntent(context, subject.file, conf.fileUri))
else -> this else -> this
} }
@ -110,7 +118,7 @@ open class DownloadService : RemoteFileService() {
.takeIf { it.exists(get()) } .takeIf { it.exists(get()) }
?.let { addAction(0, R.string.download_open_self, it.chooser()) } ?.let { addAction(0, R.string.download_open_self, it.chooser()) }
} }
is Flash -> setContentIntent(FlashActivity.installIntent(context, subject.file)) is Flash -> setContentIntent(FlashFragment.installIntent(context, subject.file))
else -> this else -> this
} }

View File

@ -29,7 +29,10 @@ abstract class RemoteFileService : NotificationService() {
val service: GithubRawServices by inject() val service: GithubRawServices by inject()
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
intent?.getParcelableExtra<DownloadSubject>(ARG_URL)?.let { start(it) } intent?.getParcelableExtra<DownloadSubject>(ARG_URL)?.let {
update(it.hashCode())
start(it)
}
return START_REDELIVER_INTENT return START_REDELIVER_INTENT
} }

View File

@ -1,8 +1,9 @@
package com.topjohnwu.magisk.di package com.topjohnwu.magisk.di
import com.topjohnwu.magisk.legacy.flash.FlashViewModel
import com.topjohnwu.magisk.legacy.surequest.SuRequestViewModel import com.topjohnwu.magisk.legacy.surequest.SuRequestViewModel
import com.topjohnwu.magisk.ui.MainViewModel import com.topjohnwu.magisk.ui.MainViewModel
import com.topjohnwu.magisk.ui.flash.FlashFragmentArgs
import com.topjohnwu.magisk.ui.flash.FlashViewModel
import com.topjohnwu.magisk.ui.hide.HideViewModel import com.topjohnwu.magisk.ui.hide.HideViewModel
import com.topjohnwu.magisk.ui.home.HomeViewModel import com.topjohnwu.magisk.ui.home.HomeViewModel
import com.topjohnwu.magisk.ui.install.InstallViewModel import com.topjohnwu.magisk.ui.install.InstallViewModel
@ -30,6 +31,6 @@ val viewModelModules = module {
viewModel { MainViewModel() } viewModel { MainViewModel() }
// Legacy // Legacy
viewModel { FlashViewModel(get()) } viewModel { (args: FlashFragmentArgs) -> FlashViewModel(args, get()) }
viewModel { SuRequestViewModel(get(), get(), get(SUTimeout), get()) } viewModel { SuRequestViewModel(get(), get(), get(SUTimeout), get()) }
} }

View File

@ -1,110 +0,0 @@
package com.topjohnwu.magisk.legacy.flash
import android.content.Context
import android.content.Intent
import android.content.pm.ActivityInfo
import android.net.Uri
import android.os.Bundle
import androidx.core.net.toUri
import androidx.navigation.NavController
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.Const
import com.topjohnwu.magisk.core.intent
import com.topjohnwu.magisk.core.view.Notifications
import com.topjohnwu.magisk.databinding.ActivityFlashBinding
import com.topjohnwu.magisk.extensions.snackbar
import com.topjohnwu.magisk.model.events.PermissionEvent
import com.topjohnwu.magisk.model.events.SnackbarEvent
import com.topjohnwu.magisk.model.events.ViewEvent
import com.topjohnwu.magisk.ui.base.BaseUIActivity
import org.koin.androidx.viewmodel.ext.android.viewModel
import java.io.File
open class FlashActivity : BaseUIActivity<FlashViewModel, ActivityFlashBinding>() {
override val layoutRes: Int = R.layout.activity_flash
override val viewModel: FlashViewModel by viewModel()
override val navigation: NavController? = null
override fun onCreate(savedInstanceState: Bundle?) {
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_NOSENSOR
super.onCreate(savedInstanceState)
val id = intent.getIntExtra(Const.Key.DISMISS_ID, -1)
if (id != -1)
Notifications.mgr.cancel(id)
viewModel.startFlashing(intent)
}
override fun onBackPressed() {
if (viewModel.loading) return
super.onBackPressed()
}
override fun onEventDispatched(event: ViewEvent) {
super.onEventDispatched(event)
when (event) {
is SnackbarEvent -> snackbar(snackbarView, event.message(this), event.length, event.f)
is PermissionEvent -> withPermissions(*event.permissions.toTypedArray()) {
onSuccess { event.callback.onNext(true) }
onFailure {
event.callback.onNext(false)
event.callback.onError(SecurityException("User refused permissions"))
}
}
}
}
companion object {
private fun intent(context: Context) = context.intent<FlashActivity>()
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
private fun intent(context: Context, file: File) = intent(context).setData(file.toUri())
private fun flashType(isSecondSlot: Boolean) =
if (isSecondSlot) Const.Value.FLASH_INACTIVE_SLOT else Const.Value.FLASH_MAGISK
/* Flashing is understood as installing / flashing magisk itself */
fun flashIntent(context: Context, file: File, isSecondSlot: Boolean, id : Int = -1)
= intent(context, file)
.putExtra(Const.Key.FLASH_ACTION, flashType(isSecondSlot))
.putExtra(Const.Key.DISMISS_ID, id)
fun flash(context: Context, file: File, isSecondSlot: Boolean, id: Int) =
context.startActivity(flashIntent(context, file, isSecondSlot, id))
/* Patching is understood as injecting img files with magisk */
fun patchIntent(context: Context, file: File, uri: Uri, id : Int = -1)
= intent(context, file)
.putExtra(Const.Key.FLASH_DATA, uri)
.putExtra(Const.Key.FLASH_ACTION, Const.Value.PATCH_FILE)
.putExtra(Const.Key.DISMISS_ID, id)
fun patch(context: Context, file: File, uri: Uri, id: Int) =
context.startActivity(patchIntent(context, file, uri, id))
/* Uninstalling is understood as removing magisk entirely */
fun uninstallIntent(context: Context, file: File, id : Int = -1)
= intent(context, file)
.putExtra(Const.Key.FLASH_ACTION, Const.Value.UNINSTALL)
.putExtra(Const.Key.DISMISS_ID, id)
fun uninstall(context: Context, file: File, id: Int) =
context.startActivity(uninstallIntent(context, file, id))
/* Installing is understood as flashing modules / zips */
fun installIntent(context: Context, file: File, id : Int = -1)
= intent(context, file)
.putExtra(Const.Key.FLASH_ACTION, Const.Value.FLASH_ZIP)
.putExtra(Const.Key.DISMISS_ID, id)
fun install(context: Context, file: File, id: Int) =
context.startActivity(installIntent(context, file, id))
}
}

View File

@ -1,117 +0,0 @@
package com.topjohnwu.magisk.legacy.flash
import android.Manifest.permission.READ_EXTERNAL_STORAGE
import android.Manifest.permission.WRITE_EXTERNAL_STORAGE
import android.content.Intent
import android.content.res.Resources
import android.net.Uri
import android.os.Handler
import android.view.MenuItem
import androidx.core.os.postDelayed
import androidx.databinding.ObservableArrayList
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.Config
import com.topjohnwu.magisk.core.Const
import com.topjohnwu.magisk.core.tasks.FlashResultListener
import com.topjohnwu.magisk.core.tasks.Flashing
import com.topjohnwu.magisk.core.tasks.MagiskInstaller
import com.topjohnwu.magisk.extensions.*
import com.topjohnwu.magisk.model.binding.BindingAdapter
import com.topjohnwu.magisk.model.entity.recycler.ConsoleItem
import com.topjohnwu.magisk.model.events.SnackbarEvent
import com.topjohnwu.magisk.ui.base.BaseViewModel
import com.topjohnwu.magisk.ui.base.diffListOf
import com.topjohnwu.magisk.ui.base.itemBindingOf
import com.topjohnwu.magisk.utils.KObservableField
import com.topjohnwu.superuser.Shell
import java.io.File
import java.util.*
class FlashViewModel(
private val resources: Resources
) : BaseViewModel(), FlashResultListener {
val canShowReboot = Shell.rootAccess()
val showRestartTitle = KObservableField(false)
val behaviorText = KObservableField(resources.getString(R.string.flashing))
val adapter = BindingAdapter<ConsoleItem>()
val items = diffListOf<ConsoleItem>()
val itemBinding = itemBindingOf<ConsoleItem>()
private val outItems = ObservableArrayList<String>()
private val logItems = Collections.synchronizedList(mutableListOf<String>())
init {
outItems.sendUpdatesTo(items) { it.map { ConsoleItem(it) } }
outItems.copyNewInputInto(logItems)
}
fun startFlashing(intent: Intent) {
val installer = intent.data ?: return
val uri: Uri? = intent.getParcelableExtra(Const.Key.FLASH_DATA)
val action = intent.getStringExtra(Const.Key.FLASH_ACTION) ?: return
when (action) {
Const.Value.FLASH_ZIP -> Flashing
.Install(installer, outItems, logItems, this)
.exec()
Const.Value.UNINSTALL -> Flashing
.Uninstall(installer, outItems, logItems, this)
.exec()
Const.Value.FLASH_MAGISK -> MagiskInstaller
.Direct(installer, outItems, logItems, this)
.exec()
Const.Value.FLASH_INACTIVE_SLOT -> MagiskInstaller
.SecondSlot(installer, outItems, logItems, this)
.exec()
Const.Value.PATCH_FILE -> MagiskInstaller
.Patch(installer, uri ?: return, outItems, logItems, this)
.exec()
}
}
override fun onResult(success: Boolean) {
state = if (success) State.LOADED else State.LOADING_FAILED
behaviorText.value = when {
success -> resources.getString(R.string.done)
else -> resources.getString(R.string.failure)
}
if (success) {
Handler().postDelayed(500) {
showRestartTitle.value = true
}
}
}
fun onMenuItemClicked(item: MenuItem): Boolean {
when (item.itemId) {
R.id.action_save -> savePressed()
}
return true
}
private fun savePressed() = withPermissions(READ_EXTERNAL_STORAGE, WRITE_EXTERNAL_STORAGE)
.map { now }
.map { it.toTime(timeFormatStandard) }
.map { Const.MAGISK_INSTALL_LOG_FILENAME.format(it) }
.map { File(Config.downloadDirectory, it) }
.map { file ->
file.bufferedWriter().use { writer ->
logItems.forEach {
writer.write(it)
writer.newLine()
}
}
file.path
}
.subscribeK { SnackbarEvent(it).publish() }
.add()
fun restartPressed() = reboot()
fun backPressed() = back()
}

View File

@ -2,13 +2,12 @@ package com.topjohnwu.magisk.model.events
import android.Manifest import android.Manifest
import android.app.Activity import android.app.Activity
import android.content.Context
import android.content.Intent import android.content.Intent
import androidx.annotation.RequiresPermission import androidx.annotation.RequiresPermission
import androidx.navigation.NavDirections
import com.topjohnwu.magisk.MainDirections
import com.topjohnwu.magisk.core.Const import com.topjohnwu.magisk.core.Const
import com.topjohnwu.magisk.core.base.BaseActivity import com.topjohnwu.magisk.core.base.BaseActivity
import com.topjohnwu.magisk.core.intent
import com.topjohnwu.magisk.legacy.flash.FlashActivity
class InstallExternalModuleEvent : ViewEvent(), ActivityExecutor { class InstallExternalModuleEvent : ViewEvent(), ActivityExecutor {
@ -21,14 +20,15 @@ class InstallExternalModuleEvent : ViewEvent(), ActivityExecutor {
companion object { companion object {
fun onActivityResult(context: Context, requestCode: Int, resultCode: Int, data: Intent?) { fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?): NavDirections? {
if (requestCode == Const.ID.FETCH_ZIP && resultCode == Activity.RESULT_OK && data != null) { if (requestCode == Const.ID.FETCH_ZIP && resultCode == Activity.RESULT_OK && data != null) {
// Get the URI of the selected file val data = data.data
val intent = context.intent<FlashActivity>() if (data != null) {
intent.setData(data.data).putExtra(Const.Key.FLASH_ACTION, Const.Value.FLASH_ZIP) return MainDirections.actionFlashFragment(data, Const.Key.FLASH_ACTION)
context.startActivity(intent)
} }
} }
return null
}
} }

View File

@ -9,6 +9,8 @@ import androidx.core.graphics.Insets
import androidx.databinding.DataBindingUtil import androidx.databinding.DataBindingUtil
import androidx.databinding.OnRebindCallback import androidx.databinding.OnRebindCallback
import androidx.databinding.ViewDataBinding import androidx.databinding.ViewDataBinding
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Observer
import androidx.navigation.NavDirections import androidx.navigation.NavDirections
import androidx.navigation.findNavController import androidx.navigation.findNavController
import com.topjohnwu.magisk.BR import com.topjohnwu.magisk.BR
@ -71,6 +73,14 @@ abstract class BaseUIActivity<ViewModel : BaseViewModel, Binding : ViewDataBindi
}) })
delegate.onCreate() delegate.onCreate()
directionsDispatcher.observe(this, Observer {
it?.navigate()
// we don't want the directions to be re-dispatched, so we preemptively set them to null
if (it != null) {
directionsDispatcher.value = null
}
})
} }
override fun onResume() { override fun onResume() {
@ -101,4 +111,13 @@ abstract class BaseUIActivity<ViewModel : BaseViewModel, Binding : ViewDataBindi
navigation?.navigate(this) navigation?.navigate(this)
} }
companion object {
private val directionsDispatcher = MutableLiveData<NavDirections?>()
fun postDirections(navDirections: NavDirections) =
directionsDispatcher.postValue(navDirections)
}
} }

View File

@ -1,13 +1,147 @@
package com.topjohnwu.magisk.ui.flash package com.topjohnwu.magisk.ui.flash
import android.annotation.SuppressLint
import android.app.Activity
import android.app.PendingIntent
import android.content.Context
import android.content.pm.ActivityInfo
import android.net.Uri
import android.os.Bundle
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import androidx.core.net.toUri
import androidx.navigation.NavDeepLinkBuilder
import com.topjohnwu.magisk.R import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.ClassMap
import com.topjohnwu.magisk.core.Const
import com.topjohnwu.magisk.databinding.FragmentFlashMd2Binding import com.topjohnwu.magisk.databinding.FragmentFlashMd2Binding
import com.topjohnwu.magisk.ui.MainActivity
import com.topjohnwu.magisk.ui.base.BaseUIActivity
import com.topjohnwu.magisk.ui.base.BaseUIFragment import com.topjohnwu.magisk.ui.base.BaseUIFragment
import org.koin.androidx.viewmodel.ext.android.viewModel import org.koin.androidx.viewmodel.ext.android.viewModel
import org.koin.core.parameter.parametersOf
import java.io.File
import com.topjohnwu.magisk.MainDirections.Companion.actionFlashFragment as toFlash
import com.topjohnwu.magisk.ui.flash.FlashFragmentArgs as args
class FlashFragment : BaseUIFragment<FlashViewModel, FragmentFlashMd2Binding>() { class FlashFragment : BaseUIFragment<FlashViewModel, FragmentFlashMd2Binding>() {
override val layoutRes = R.layout.fragment_flash_md2 override val layoutRes = R.layout.fragment_flash_md2
override val viewModel by viewModel<FlashViewModel>() override val viewModel by viewModel<FlashViewModel> {
parametersOf(args.fromBundle(requireArguments()))
}
private var defaultOrientation = -1
override fun onStart() {
super.onStart()
setHasOptionsMenu(true)
activity.setTitle(R.string.flash_screen_title)
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.menu_flash, menu)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return viewModel.onMenuItemClicked(item)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
defaultOrientation = activity.requestedOrientation
activity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_NOSENSOR
}
@SuppressLint("WrongConstant")
override fun onDestroyView() {
if (defaultOrientation != -1) {
activity.requestedOrientation = defaultOrientation
}
super.onDestroyView()
}
override fun onBackPressed(): Boolean {
if (viewModel.loading) return true
return super.onBackPressed()
}
override fun onPreBind(binding: FragmentFlashMd2Binding) = Unit
companion object {
private fun createIntent(context: Context, args: args): PendingIntent {
return NavDeepLinkBuilder(context)
.setGraph(R.navigation.main)
.setComponentName(ClassMap[MainActivity::class.java] as Class<out Activity>)
.setDestination(R.id.flashFragment)
.setArguments(args.toBundle())
.createPendingIntent()
}
private fun flashType(isSecondSlot: Boolean) =
if (isSecondSlot) Const.Value.FLASH_INACTIVE_SLOT else Const.Value.FLASH_MAGISK
/* Flashing is understood as installing / flashing magisk itself */
fun flashIntent(context: Context, file: File, isSecondSlot: Boolean, id: Int = -1) = args(
installer = file.toUri(),
action = flashType(isSecondSlot),
dismissId = id
).let { createIntent(context, it) }
fun flash(file: File, isSecondSlot: Boolean, id: Int) = toFlash(
installer = file.toUri(),
action = flashType(isSecondSlot),
dismissId = id
).let { BaseUIActivity.postDirections(it) }
/* Patching is understood as injecting img files with magisk */
fun patchIntent(context: Context, file: File, uri: Uri, id: Int = -1) = args(
installer = file.toUri(),
action = Const.Value.PATCH_FILE,
additionalData = uri,
dismissId = id
).let { createIntent(context, it) }
fun patch(file: File, uri: Uri, id: Int) = toFlash(
installer = file.toUri(),
action = Const.Value.PATCH_FILE,
additionalData = uri,
dismissId = id
).let { BaseUIActivity.postDirections(it) }
/* Uninstalling is understood as removing magisk entirely */
fun uninstallIntent(context: Context, file: File, id: Int = -1) = args(
installer = file.toUri(),
action = Const.Value.UNINSTALL,
dismissId = id
).let { createIntent(context, it) }
fun uninstall(file: File, id: Int) = toFlash(
installer = file.toUri(),
action = Const.Value.UNINSTALL,
dismissId = id
).let { BaseUIActivity.postDirections(it) }
/* Installing is understood as flashing modules / zips */
fun installIntent(context: Context, file: File, id: Int = -1) = args(
installer = file.toUri(),
action = Const.Value.FLASH_ZIP,
dismissId = id
).let { createIntent(context, it) }
fun install(file: File, id: Int) = toFlash(
installer = file.toUri(),
action = Const.Value.FLASH_ZIP,
dismissId = id
).let { BaseUIActivity.postDirections(it) }
}
} }

View File

@ -1,5 +1,141 @@
package com.topjohnwu.magisk.ui.flash package com.topjohnwu.magisk.ui.flash
import android.Manifest
import android.content.res.Resources
import android.net.Uri
import android.os.Handler
import android.view.MenuItem
import androidx.core.os.postDelayed
import androidx.databinding.ObservableArrayList
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.Config
import com.topjohnwu.magisk.core.Const
import com.topjohnwu.magisk.core.tasks.FlashResultListener
import com.topjohnwu.magisk.core.tasks.Flashing
import com.topjohnwu.magisk.core.tasks.MagiskInstaller
import com.topjohnwu.magisk.core.view.Notifications
import com.topjohnwu.magisk.extensions.*
import com.topjohnwu.magisk.model.binding.BindingAdapter
import com.topjohnwu.magisk.model.entity.recycler.ConsoleItem
import com.topjohnwu.magisk.model.events.SnackbarEvent
import com.topjohnwu.magisk.ui.base.BaseViewModel import com.topjohnwu.magisk.ui.base.BaseViewModel
import com.topjohnwu.magisk.ui.base.diffListOf
import com.topjohnwu.magisk.ui.base.itemBindingOf
import com.topjohnwu.magisk.utils.KObservableField
import com.topjohnwu.superuser.Shell
import java.io.File
import java.util.*
class FlashViewModel : BaseViewModel() class FlashViewModel(
private val args: FlashFragmentArgs,
private val resources: Resources
) : BaseViewModel(),
FlashResultListener {
val canShowReboot = Shell.rootAccess()
val showRestartTitle = KObservableField(false)
val behaviorText = KObservableField(resources.getString(R.string.flashing))
val adapter = BindingAdapter<ConsoleItem>()
val items = diffListOf<ConsoleItem>()
val itemBinding = itemBindingOf<ConsoleItem>()
private val outItems = ObservableArrayList<String>()
private val logItems =
Collections.synchronizedList(mutableListOf<String>())
init {
outItems.sendUpdatesTo(items) { it.map { ConsoleItem(it) } }
outItems.copyNewInputInto(logItems)
args.dismissId.takeIf { it != -1 }?.also {
Notifications.mgr.cancel(it)
}
val (installer, action, uri) = args
startFlashing(installer, uri, action)
}
private fun startFlashing(installer: Uri, uri: Uri?, action: String) {
when (action) {
Const.Value.FLASH_ZIP -> Flashing.Install(
installer,
outItems,
logItems,
this
).exec()
Const.Value.UNINSTALL -> Flashing.Uninstall(
installer,
outItems,
logItems,
this
).exec()
Const.Value.FLASH_MAGISK -> MagiskInstaller.Direct(
installer,
outItems,
logItems,
this
).exec()
Const.Value.FLASH_INACTIVE_SLOT -> MagiskInstaller.SecondSlot(
installer,
outItems,
logItems,
this
).exec()
Const.Value.PATCH_FILE -> MagiskInstaller.Patch(
installer,
uri ?: return,
outItems,
logItems,
this
).exec()
else -> backPressed()
}
}
override fun onResult(success: Boolean) {
state = if (success) State.LOADED else State.LOADING_FAILED
behaviorText.value = when {
success -> resources.getString(R.string.done)
else -> resources.getString(R.string.failure)
}
if (success) {
Handler().postDelayed(500) {
showRestartTitle.value = true
}
}
}
fun onMenuItemClicked(item: MenuItem): Boolean {
when (item.itemId) {
R.id.action_save -> savePressed()
}
return true
}
private fun savePressed() = withPermissions(
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE
)
.map { now }
.map { it.toTime(timeFormatStandard) }
.map { Const.MAGISK_INSTALL_LOG_FILENAME.format(it) }
.map { File(Config.downloadDirectory, it) }
.map { file ->
file.bufferedWriter().use { writer ->
logItems.forEach {
writer.write(it)
writer.newLine()
}
}
file.path
}
.subscribeK { SnackbarEvent(it).publish() }
.add()
fun restartPressed() = reboot()
fun backPressed() = back()
}

View File

@ -40,7 +40,7 @@ class InstallViewModel : BaseViewModel(State.LOADED) {
this.progress.value = progress.times(100).roundToInt() this.progress.value = progress.times(100).roundToInt()
if (this.progress.value >= 100) { if (this.progress.value >= 100) {
// this might cause issues if the flash activity launches on top of this sooner // this might cause issues if the flash activity launches on top of this sooner
back() // back()
} }
} }
method.addOnPropertyChangedCallback { method.addOnPropertyChangedCallback {

View File

@ -42,7 +42,9 @@ class ModuleFragment : BaseUIFragment<ModuleViewModel, FragmentModuleMd2Binding>
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data) super.onActivityResult(requestCode, resultCode, data)
InstallExternalModuleEvent.onActivityResult(requireContext(), requestCode, resultCode, data) InstallExternalModuleEvent.onActivityResult(requestCode, resultCode, data)?.also {
it.navigate()
}
} }
override fun onStart() { override fun onStart() {

View File

@ -1,102 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="viewModel"
type="com.topjohnwu.magisk.legacy.flash.FlashViewModel" />
</data>
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.appbar.AppBarLayout
style="@style/WidgetFoundation.Appbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="@{viewModel.insets.left}"
android:paddingRight="@{viewModel.insets.right}">
<com.google.android.material.appbar.MaterialToolbar
style="@style/WidgetFoundation.Toolbar"
onMenuClick="@{(item) -> viewModel.onMenuItemClicked(item)}"
onNavigationClick="@{() -> viewModel.backPressed()}"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="?attr/actionBarSize"
android:paddingTop="@{viewModel.insets.top}"
app:contentInsetLeft="0dp"
app:contentInsetStart="0dp"
app:menu="@menu/menu_flash"
app:navigationIcon="@drawable/ic_back_md2">
<androidx.appcompat.widget.AppCompatTextView
movieBehavior="@{viewModel.loading}"
movieBehaviorText="@{viewModel.behaviorText}"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:textAppearance="@style/AppearanceFoundation.Body"
android:textStyle="bold"
android:fontFamily="monospace"
android:gravity="center"
android:textColor="?colorOnSurface"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="Flashing..." />
</com.google.android.material.appbar.MaterialToolbar>
</com.google.android.material.appbar.AppBarLayout>
<HorizontalScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="@{viewModel.insets.top + (int) @dimen/internal_action_bar_size}"
tools:layout_marginTop="@dimen/internal_action_bar_size">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/flash_content"
adapter="@{viewModel.adapter}"
itemBinding="@{viewModel.itemBinding}"
items="@{viewModel.items}"
scrollToLast="@{true}"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:clipToPadding="false"
android:orientation="vertical"
android:paddingLeft="@{viewModel.insets.left}"
android:paddingRight="@{viewModel.insets.right}"
android:paddingBottom="@{viewModel.insets.bottom}"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:listitem="@layout/item_console_md2" />
</HorizontalScrollView>
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
gone="@{!viewModel.loaded || !viewModel.canShowReboot}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="@dimen/l1"
android:layout_marginBottom="@{(int) @dimen/l1 + viewModel.insets.bottom}"
android:onClick="@{() -> viewModel.restartPressed()}"
android:text="@string/reboot"
android:textAllCaps="false"
android:textColor="?colorOnPrimary"
android:textStyle="bold"
app:backgroundTint="?colorPrimary"
app:icon="@drawable/ic_restart"
app:tint="?colorOnPrimary" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</layout>

View File

@ -1,5 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"> <layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data> <data>
@ -9,15 +11,74 @@
</data> </data>
<androidx.core.widget.NestedScrollView <androidx.coordinatorlayout.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<HorizontalScrollView
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:fillViewport="true"> android:layout_marginTop="@{viewModel.insets.top + (int) @dimen/internal_action_bar_size}"
tools:layout_marginTop="@dimen/internal_action_bar_size">
<androidx.constraintlayout.widget.ConstraintLayout <androidx.recyclerview.widget.RecyclerView
android:layout_width="match_parent" android:id="@+id/flash_content"
android:layout_height="wrap_content" /> adapter="@{viewModel.adapter}"
itemBinding="@{viewModel.itemBinding}"
items="@{viewModel.items}"
scrollToLast="@{true}"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:clipToPadding="false"
android:orientation="vertical"
android:paddingLeft="@{viewModel.insets.left}"
android:paddingRight="@{viewModel.insets.right}"
android:paddingBottom="@{viewModel.insets.bottom}"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:listitem="@layout/item_console_md2" />
</androidx.core.widget.NestedScrollView> </HorizontalScrollView>
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
gone="@{!viewModel.loaded || !viewModel.canShowReboot}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="@dimen/l1"
android:layout_marginBottom="@{(int) @dimen/l1 + viewModel.insets.bottom}"
android:onClick="@{() -> viewModel.restartPressed()}"
android:text="@string/reboot"
android:textAllCaps="false"
android:textColor="?colorOnPrimary"
android:textStyle="bold"
app:backgroundTint="?colorPrimary"
app:icon="@drawable/ic_restart"
app:iconTint="?colorOnPrimary" />
<com.google.android.material.card.MaterialCardView
style="@style/WidgetFoundation.Card.Elevated"
goneUnless="@{viewModel.loading}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
app:contentPadding="@dimen/l1">
<androidx.appcompat.widget.AppCompatTextView
movieBehavior="@{viewModel.loading}"
movieBehaviorText="@{viewModel.behaviorText}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:fontFamily="monospace"
android:gravity="center"
android:textAppearance="@style/AppearanceFoundation.Body"
android:textColor="?colorOnSurface"
android:textStyle="bold"
tools:text="Flashing..." />
</com.google.android.material.card.MaterialCardView>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</layout> </layout>

View File

@ -35,6 +35,33 @@
</fragment> </fragment>
<fragment
android:id="@+id/flashFragment"
android:name="com.topjohnwu.magisk.ui.flash.FlashFragment"
android:label="FlashFragment"
tools:layout="@layout/fragment_flash_md2">
<argument
android:name="installer"
app:argType="android.net.Uri" />
<argument
android:name="action"
app:argType="string" />
<argument
android:name="additional_data"
android:defaultValue="@null"
app:argType="android.net.Uri"
app:nullable="true" />
<argument
android:name="dismiss_id"
android:defaultValue="-1"
app:argType="integer" />
</fragment>
<fragment <fragment
android:id="@+id/installFragment" android:id="@+id/installFragment"
android:name="com.topjohnwu.magisk.ui.install.InstallFragment" android:name="com.topjohnwu.magisk.ui.install.InstallFragment"
@ -145,4 +172,14 @@
app:popUpTo="@id/modulesFragment" app:popUpTo="@id/modulesFragment"
app:popUpToInclusive="true" /> app:popUpToInclusive="true" />
<action
android:id="@+id/action_flashFragment"
app:destination="@id/flashFragment"
app:enterAnim="@anim/fragment_enter"
app:exitAnim="@anim/fragment_exit"
app:popEnterAnim="@anim/fragment_enter_pop"
app:popExitAnim="@anim/fragment_exit_pop"
app:popUpTo="@id/installFragment"
app:popUpToInclusive="true" />
</navigation> </navigation>

View File

@ -61,6 +61,7 @@
<string name="select_patch_file">Select and Patch a File</string> <string name="select_patch_file">Select and Patch a File</string>
<string name="patch_file_msg">Select a raw image (*.img) or an ODIN tarfile (*.tar)</string> <string name="patch_file_msg">Select a raw image (*.img) or an ODIN tarfile (*.tar)</string>
<string name="reboot_delay_toast">Rebooting in 5 seconds…</string> <string name="reboot_delay_toast">Rebooting in 5 seconds…</string>
<string name="flash_screen_title">Installation</string>
<!--Superuser--> <!--Superuser-->
<string name="su_request_title">Superuser Request</string> <string name="su_request_title">Superuser Request</string>