mirror of
https://github.com/topjohnwu/Magisk.git
synced 2024-12-25 08:17:40 +00:00
Several changes for using MediaStore
- Change config key name so default downloads to folder 'Download' - Always use getFile as we do not need existing file deleted - Fallback to use File based I/O pre API 29 as officially MediaStore APIs do not support general purpose usage. And also, it was working fine on all devices before. If it ain't broke, don't fix it - Show full download path in settings to make it more clear to the user - Close streams after using them
This commit is contained in:
parent
9e81db8692
commit
14a2f63b8b
@ -49,7 +49,7 @@ object Config : PreferenceModel, DBConfig {
|
|||||||
const val DARK_THEME = "dark_theme_extended"
|
const val DARK_THEME = "dark_theme_extended"
|
||||||
const val REPO_ORDER = "repo_order"
|
const val REPO_ORDER = "repo_order"
|
||||||
const val SHOW_SYSTEM_APP = "show_system"
|
const val SHOW_SYSTEM_APP = "show_system"
|
||||||
const val DOWNLOAD_PATH = "download_path"
|
const val DOWNLOAD_DIR = "download_dir"
|
||||||
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"
|
||||||
@ -109,7 +109,7 @@ object Config : PreferenceModel, DBConfig {
|
|||||||
var bootId by preference(Key.BOOT_ID, "")
|
var bootId by preference(Key.BOOT_ID, "")
|
||||||
var askedHome by preference(Key.ASKED_HOME, false)
|
var askedHome by preference(Key.ASKED_HOME, false)
|
||||||
|
|
||||||
var downloadPath by preference(Key.DOWNLOAD_PATH, "Magisk Manager")
|
var downloadDir by preference(Key.DOWNLOAD_DIR, "")
|
||||||
var repoOrder by preference(Key.REPO_ORDER, Value.ORDER_DATE)
|
var repoOrder by preference(Key.REPO_ORDER, Value.ORDER_DATE)
|
||||||
|
|
||||||
var suDefaultTimeout by preferenceStrInt(Key.SU_REQUEST_TIMEOUT, 10)
|
var suDefaultTimeout by preferenceStrInt(Key.SU_REQUEST_TIMEOUT, 10)
|
||||||
|
@ -8,11 +8,12 @@ import androidx.lifecycle.MutableLiveData
|
|||||||
import com.topjohnwu.magisk.R
|
import com.topjohnwu.magisk.R
|
||||||
import com.topjohnwu.magisk.core.ForegroundTracker
|
import com.topjohnwu.magisk.core.ForegroundTracker
|
||||||
import com.topjohnwu.magisk.core.base.BaseService
|
import com.topjohnwu.magisk.core.base.BaseService
|
||||||
|
import com.topjohnwu.magisk.core.utils.MediaStoreUtils.checkSum
|
||||||
|
import com.topjohnwu.magisk.core.utils.MediaStoreUtils.outputStream
|
||||||
import com.topjohnwu.magisk.core.utils.ProgressInputStream
|
import com.topjohnwu.magisk.core.utils.ProgressInputStream
|
||||||
import com.topjohnwu.magisk.data.network.GithubRawServices
|
import com.topjohnwu.magisk.data.network.GithubRawServices
|
||||||
|
import com.topjohnwu.magisk.ktx.withStreams
|
||||||
import com.topjohnwu.magisk.ktx.writeTo
|
import com.topjohnwu.magisk.ktx.writeTo
|
||||||
import com.topjohnwu.magisk.utils.MediaStoreUtils.checkSum
|
|
||||||
import com.topjohnwu.magisk.utils.MediaStoreUtils.outputStream
|
|
||||||
import com.topjohnwu.magisk.view.Notifications
|
import com.topjohnwu.magisk.view.Notifications
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
@ -76,7 +77,7 @@ abstract class BaseDownloadService : BaseService(), KoinComponent {
|
|||||||
is Subject.Module -> // Download and process on-the-fly
|
is Subject.Module -> // Download and process on-the-fly
|
||||||
stream.toModule(file, service.fetchInstaller().byteStream())
|
stream.toModule(file, service.fetchInstaller().byteStream())
|
||||||
else ->
|
else ->
|
||||||
stream.copyTo(file.outputStream())
|
withStreams(stream, file.outputStream()) { it, out -> it.copyTo(out) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val newId = notifyFinish(this)
|
val newId = notifyFinish(this)
|
||||||
|
@ -5,7 +5,6 @@ import android.app.Notification
|
|||||||
import android.app.PendingIntent
|
import android.app.PendingIntent
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.net.Uri
|
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import androidx.core.net.toFile
|
import androidx.core.net.toFile
|
||||||
import com.topjohnwu.magisk.core.download.Action.*
|
import com.topjohnwu.magisk.core.download.Action.*
|
||||||
|
@ -2,7 +2,7 @@ package com.topjohnwu.magisk.core.download
|
|||||||
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import com.topjohnwu.magisk.ktx.withStreams
|
import com.topjohnwu.magisk.ktx.withStreams
|
||||||
import com.topjohnwu.magisk.utils.MediaStoreUtils.outputStream
|
import com.topjohnwu.magisk.core.utils.MediaStoreUtils.outputStream
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import java.util.zip.ZipEntry
|
import java.util.zip.ZipEntry
|
||||||
import java.util.zip.ZipInputStream
|
import java.util.zip.ZipInputStream
|
||||||
|
@ -10,7 +10,7 @@ import com.topjohnwu.magisk.core.model.ManagerJson
|
|||||||
import com.topjohnwu.magisk.core.model.module.Repo
|
import com.topjohnwu.magisk.core.model.module.Repo
|
||||||
import com.topjohnwu.magisk.ktx.cachedFile
|
import com.topjohnwu.magisk.ktx.cachedFile
|
||||||
import com.topjohnwu.magisk.ktx.get
|
import com.topjohnwu.magisk.ktx.get
|
||||||
import com.topjohnwu.magisk.utils.MediaStoreUtils
|
import com.topjohnwu.magisk.core.utils.MediaStoreUtils
|
||||||
import kotlinx.android.parcel.IgnoredOnParcel
|
import kotlinx.android.parcel.IgnoredOnParcel
|
||||||
import kotlinx.android.parcel.Parcelize
|
import kotlinx.android.parcel.Parcelize
|
||||||
|
|
||||||
@ -31,7 +31,7 @@ sealed class Subject : Parcelable {
|
|||||||
|
|
||||||
@IgnoredOnParcel
|
@IgnoredOnParcel
|
||||||
override val file by lazy {
|
override val file by lazy {
|
||||||
MediaStoreUtils.newFile(title).uri
|
MediaStoreUtils.getFile(title).uri
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,9 +4,10 @@ import android.content.Context
|
|||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import androidx.core.os.postDelayed
|
import androidx.core.os.postDelayed
|
||||||
import com.topjohnwu.magisk.core.Const
|
import com.topjohnwu.magisk.core.Const
|
||||||
|
import com.topjohnwu.magisk.core.utils.MediaStoreUtils.displayName
|
||||||
import com.topjohnwu.magisk.core.utils.unzip
|
import com.topjohnwu.magisk.core.utils.unzip
|
||||||
import com.topjohnwu.magisk.utils.MediaStoreUtils.getDisplayName
|
import com.topjohnwu.magisk.core.utils.MediaStoreUtils.inputStream
|
||||||
import com.topjohnwu.magisk.utils.MediaStoreUtils.inputStream
|
import com.topjohnwu.magisk.ktx.writeTo
|
||||||
import com.topjohnwu.superuser.Shell
|
import com.topjohnwu.superuser.Shell
|
||||||
import com.topjohnwu.superuser.internal.UiThreadHandler
|
import com.topjohnwu.superuser.internal.UiThreadHandler
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
@ -47,9 +48,7 @@ open class FlashZip(
|
|||||||
console.add("- Copying zip to temp directory")
|
console.add("- Copying zip to temp directory")
|
||||||
|
|
||||||
runCatching {
|
runCatching {
|
||||||
mUri.inputStream().use { input ->
|
mUri.inputStream().writeTo(tmpFile)
|
||||||
tmpFile.outputStream().use { out -> input.copyTo(out) }
|
|
||||||
}
|
|
||||||
}.getOrElse {
|
}.getOrElse {
|
||||||
when (it) {
|
when (it) {
|
||||||
is FileNotFoundException -> console.add("! Invalid Uri")
|
is FileNotFoundException -> console.add("! Invalid Uri")
|
||||||
@ -70,7 +69,7 @@ open class FlashZip(
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
console.add("- Installing ${mUri.getDisplayName()}")
|
console.add("- Installing ${mUri.displayName}")
|
||||||
|
|
||||||
val parentFile = tmpFile.parent ?: return false
|
val parentFile = tmpFile.parent ?: return false
|
||||||
|
|
||||||
|
@ -15,9 +15,9 @@ import com.topjohnwu.magisk.di.Protected
|
|||||||
import com.topjohnwu.magisk.events.dialog.EnvFixDialog
|
import com.topjohnwu.magisk.events.dialog.EnvFixDialog
|
||||||
import com.topjohnwu.magisk.ktx.reboot
|
import com.topjohnwu.magisk.ktx.reboot
|
||||||
import com.topjohnwu.magisk.ktx.withStreams
|
import com.topjohnwu.magisk.ktx.withStreams
|
||||||
import com.topjohnwu.magisk.utils.MediaStoreUtils
|
import com.topjohnwu.magisk.core.utils.MediaStoreUtils
|
||||||
import com.topjohnwu.magisk.utils.MediaStoreUtils.inputStream
|
import com.topjohnwu.magisk.core.utils.MediaStoreUtils.inputStream
|
||||||
import com.topjohnwu.magisk.utils.MediaStoreUtils.outputStream
|
import com.topjohnwu.magisk.core.utils.MediaStoreUtils.outputStream
|
||||||
import com.topjohnwu.magisk.utils.Utils
|
import com.topjohnwu.magisk.utils.Utils
|
||||||
import com.topjohnwu.signing.SignBoot
|
import com.topjohnwu.signing.SignBoot
|
||||||
import com.topjohnwu.superuser.Shell
|
import com.topjohnwu.superuser.Shell
|
||||||
@ -49,7 +49,7 @@ abstract class MagiskInstallImpl : KoinComponent {
|
|||||||
|
|
||||||
protected lateinit var installDir: File
|
protected lateinit var installDir: File
|
||||||
private lateinit var srcBoot: String
|
private lateinit var srcBoot: String
|
||||||
private lateinit var destFile: MediaStoreUtils.MediaStoreFile
|
private lateinit var destFile: MediaStoreUtils.UriFile
|
||||||
private lateinit var zipUri: Uri
|
private lateinit var zipUri: Uri
|
||||||
|
|
||||||
protected val console: MutableList<String>
|
protected val console: MutableList<String>
|
||||||
@ -233,12 +233,12 @@ abstract class MagiskInstallImpl : KoinComponent {
|
|||||||
}
|
}
|
||||||
it.reset()
|
it.reset()
|
||||||
if (magic.contentEquals("ustar".toByteArray())) {
|
if (magic.contentEquals("ustar".toByteArray())) {
|
||||||
destFile = MediaStoreUtils.newFile("magisk_patched.tar")
|
destFile = MediaStoreUtils.getFile("magisk_patched.tar")
|
||||||
handleTar(it)
|
handleTar(it)
|
||||||
} else {
|
} else {
|
||||||
// Raw image
|
// Raw image
|
||||||
srcBoot = File(installDir, "boot.img").path
|
srcBoot = File(installDir, "boot.img").path
|
||||||
destFile = MediaStoreUtils.newFile("magisk_patched.img")
|
destFile = MediaStoreUtils.getFile("magisk_patched.img")
|
||||||
console.add("- Copying image to cache")
|
console.add("- Copying image to cache")
|
||||||
FileOutputStream(srcBoot).use { out -> it.copyTo(out) }
|
FileOutputStream(srcBoot).use { out -> it.copyTo(out) }
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package com.topjohnwu.magisk.utils
|
package com.topjohnwu.magisk.core.utils
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.content.ContentResolver
|
import android.content.ContentResolver
|
||||||
@ -10,6 +10,9 @@ import android.os.Build
|
|||||||
import android.os.Environment
|
import android.os.Environment
|
||||||
import android.provider.MediaStore
|
import android.provider.MediaStore
|
||||||
import android.provider.OpenableColumns
|
import android.provider.OpenableColumns
|
||||||
|
import androidx.annotation.RequiresApi
|
||||||
|
import androidx.core.net.toFile
|
||||||
|
import androidx.core.net.toUri
|
||||||
import com.topjohnwu.magisk.core.Config
|
import com.topjohnwu.magisk.core.Config
|
||||||
import com.topjohnwu.magisk.ktx.get
|
import com.topjohnwu.magisk.ktx.get
|
||||||
import java.io.File
|
import java.io.File
|
||||||
@ -24,32 +27,24 @@ object MediaStoreUtils {
|
|||||||
|
|
||||||
private val cr: ContentResolver by lazy { get<Context>().contentResolver }
|
private val cr: ContentResolver by lazy { get<Context>().contentResolver }
|
||||||
|
|
||||||
@SuppressLint("InlinedApi")
|
@get:RequiresApi(api = 29)
|
||||||
private val tableUri: Uri = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
private val tableUri
|
||||||
MediaStore.Downloads.EXTERNAL_CONTENT_URI
|
get() = MediaStore.Downloads.EXTERNAL_CONTENT_URI
|
||||||
} else {
|
|
||||||
MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun relativePath(appName: String): String {
|
private fun relativePath(name: String) =
|
||||||
var path = Environment.DIRECTORY_DOWNLOADS
|
if (name.isEmpty()) Environment.DIRECTORY_DOWNLOADS
|
||||||
if (appName.isNotEmpty()) {
|
else Environment.DIRECTORY_DOWNLOADS + File.separator + name
|
||||||
path += File.separator + appName
|
|
||||||
}
|
|
||||||
return path
|
|
||||||
}
|
|
||||||
|
|
||||||
|
fun fullPath(name: String): String =
|
||||||
|
File(Environment.getExternalStorageDirectory(), relativePath(name)).canonicalPath
|
||||||
|
|
||||||
|
private val relativePath get() = relativePath(Config.downloadDir)
|
||||||
|
|
||||||
|
@RequiresApi(api = 29)
|
||||||
@Throws(IOException::class)
|
@Throws(IOException::class)
|
||||||
private fun insertFile(displayName: String): MediaStoreFile {
|
private fun insertFile(displayName: String): MediaStoreFile {
|
||||||
val values = ContentValues()
|
val values = ContentValues()
|
||||||
val relativePath = relativePath(Config.downloadPath)
|
values.put(MediaStore.MediaColumns.RELATIVE_PATH, relativePath)
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
|
||||||
values.put(MediaStore.MediaColumns.RELATIVE_PATH, relativePath)
|
|
||||||
} else {
|
|
||||||
val parent = File(Environment.getExternalStorageDirectory(), relativePath)
|
|
||||||
values.put(MediaStore.MediaColumns.DATA, File(parent, displayName).path)
|
|
||||||
parent.mkdirs()
|
|
||||||
}
|
|
||||||
values.put(MediaStore.MediaColumns.DISPLAY_NAME, displayName)
|
values.put(MediaStore.MediaColumns.DISPLAY_NAME, displayName)
|
||||||
|
|
||||||
// before Android 11, MediaStore can not rename new file when file exists,
|
// before Android 11, MediaStore can not rename new file when file exists,
|
||||||
@ -70,7 +65,14 @@ object MediaStoreUtils {
|
|||||||
throw IOException("Can't insert $displayName.")
|
throw IOException("Can't insert $displayName.")
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun queryFile(displayName: String): MediaStoreFile? {
|
private fun queryFile(displayName: String): UriFile? {
|
||||||
|
if (Build.VERSION.SDK_INT < 29) {
|
||||||
|
// Before official general purpose MediaStore API exists, fallback to file based I/O
|
||||||
|
val parent = File(Environment.getExternalStorageDirectory(), relativePath)
|
||||||
|
parent.mkdirs()
|
||||||
|
return LegacyUriFile(File(parent, displayName))
|
||||||
|
}
|
||||||
|
|
||||||
val projection = arrayOf(MediaStore.MediaColumns._ID, MediaStore.MediaColumns.DATA)
|
val projection = arrayOf(MediaStore.MediaColumns._ID, MediaStore.MediaColumns.DATA)
|
||||||
// Before Android 10, we wrote the DISPLAY_NAME field when insert, so it can be used.
|
// Before Android 10, we wrote the DISPLAY_NAME field when insert, so it can be used.
|
||||||
val selection = "${MediaStore.MediaColumns.DISPLAY_NAME} == ?"
|
val selection = "${MediaStore.MediaColumns.DISPLAY_NAME} == ?"
|
||||||
@ -82,7 +84,6 @@ object MediaStoreUtils {
|
|||||||
while (cursor.moveToNext()) {
|
while (cursor.moveToNext()) {
|
||||||
val id = cursor.getLong(idColumn)
|
val id = cursor.getLong(idColumn)
|
||||||
val data = cursor.getString(dataColumn)
|
val data = cursor.getString(dataColumn)
|
||||||
val relativePath = relativePath(Config.downloadPath)
|
|
||||||
if (data.endsWith(relativePath + File.separator + displayName)) {
|
if (data.endsWith(relativePath + File.separator + displayName)) {
|
||||||
return MediaStoreFile(id, data)
|
return MediaStoreFile(id, data)
|
||||||
}
|
}
|
||||||
@ -91,26 +92,22 @@ object MediaStoreUtils {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressLint("NewApi")
|
||||||
@Throws(IOException::class)
|
@Throws(IOException::class)
|
||||||
fun newFile(displayName: String): MediaStoreFile {
|
fun getFile(displayName: String): UriFile {
|
||||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
return queryFile(displayName) ?:
|
||||||
insertFile(displayName)
|
/* this code path will never happen pre 29 */ insertFile(displayName)
|
||||||
} else {
|
|
||||||
queryFile(displayName)?.delete()
|
|
||||||
insertFile(displayName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Throws(IOException::class)
|
|
||||||
fun getFile(displayName: String): MediaStoreFile {
|
|
||||||
return queryFile(displayName) ?: insertFile(displayName)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Uri.inputStream() = cr.openInputStream(this) ?: throw FileNotFoundException()
|
fun Uri.inputStream() = cr.openInputStream(this) ?: throw FileNotFoundException()
|
||||||
|
|
||||||
fun Uri.outputStream() = cr.openOutputStream(this) ?: throw FileNotFoundException()
|
fun Uri.outputStream() = cr.openOutputStream(this) ?: throw FileNotFoundException()
|
||||||
|
|
||||||
fun Uri.getDisplayName(): String {
|
val Uri.displayName: String get() {
|
||||||
|
if (scheme == "file") {
|
||||||
|
// Simple uri wrapper over file, directly get file name
|
||||||
|
return toFile().name
|
||||||
|
}
|
||||||
require(scheme == "content") { "Uri lacks 'content' scheme: $this" }
|
require(scheme == "content") { "Uri lacks 'content' scheme: $this" }
|
||||||
val projection = arrayOf(OpenableColumns.DISPLAY_NAME)
|
val projection = arrayOf(OpenableColumns.DISPLAY_NAME)
|
||||||
cr.query(this, projection, null, null, null)?.use { cursor ->
|
cr.query(this, projection, null, null, null)?.use { cursor ->
|
||||||
@ -140,11 +137,22 @@ object MediaStoreUtils {
|
|||||||
}
|
}
|
||||||
}.getOrElse { false }
|
}.getOrElse { false }
|
||||||
|
|
||||||
data class MediaStoreFile(val id: Long, private val data: String) {
|
interface UriFile {
|
||||||
val uri: Uri = ContentUris.withAppendedId(tableUri, id)
|
val uri: Uri
|
||||||
override fun toString() = data
|
fun delete(): Boolean
|
||||||
|
}
|
||||||
|
|
||||||
fun delete(): Boolean {
|
private class LegacyUriFile(private val file: File) : UriFile {
|
||||||
|
override val uri = file.toUri()
|
||||||
|
override fun delete() = file.delete()
|
||||||
|
override fun toString() = file.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequiresApi(api = 29)
|
||||||
|
private class MediaStoreFile(private val id: Long, private val data: String) : UriFile {
|
||||||
|
override val uri = ContentUris.withAppendedId(tableUri, id)
|
||||||
|
override fun toString() = data
|
||||||
|
override fun delete(): Boolean {
|
||||||
val selection = "${MediaStore.MediaColumns._ID} == ?"
|
val selection = "${MediaStore.MediaColumns._ID} == ?"
|
||||||
val selectionArgs = arrayOf(id.toString())
|
val selectionArgs = arrayOf(id.toString())
|
||||||
return cr.delete(uri, selection, selectionArgs) == 1
|
return cr.delete(uri, selection, selectionArgs) == 1
|
@ -21,7 +21,8 @@ fun ZipInputStream.forEach(callback: (ZipEntry) -> Unit) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun InputStream.writeTo(file: File) = this.copyTo(file.outputStream())
|
fun InputStream.writeTo(file: File) =
|
||||||
|
withStreams(this, file.outputStream()) { reader, writer -> reader.copyTo(writer) }
|
||||||
|
|
||||||
inline fun <In : InputStream, Out : OutputStream> withStreams(
|
inline fun <In : InputStream, Out : OutputStream> withStreams(
|
||||||
inStream: In,
|
inStream: In,
|
||||||
|
@ -16,8 +16,8 @@ import com.topjohnwu.magisk.core.tasks.MagiskInstaller
|
|||||||
import com.topjohnwu.magisk.databinding.RvBindingAdapter
|
import com.topjohnwu.magisk.databinding.RvBindingAdapter
|
||||||
import com.topjohnwu.magisk.events.SnackbarEvent
|
import com.topjohnwu.magisk.events.SnackbarEvent
|
||||||
import com.topjohnwu.magisk.ktx.*
|
import com.topjohnwu.magisk.ktx.*
|
||||||
import com.topjohnwu.magisk.utils.MediaStoreUtils
|
import com.topjohnwu.magisk.core.utils.MediaStoreUtils
|
||||||
import com.topjohnwu.magisk.utils.MediaStoreUtils.outputStream
|
import com.topjohnwu.magisk.core.utils.MediaStoreUtils.outputStream
|
||||||
import com.topjohnwu.magisk.utils.set
|
import com.topjohnwu.magisk.utils.set
|
||||||
import com.topjohnwu.magisk.view.Notifications
|
import com.topjohnwu.magisk.view.Notifications
|
||||||
import com.topjohnwu.superuser.CallbackList
|
import com.topjohnwu.superuser.CallbackList
|
||||||
@ -109,7 +109,7 @@ class FlashViewModel(
|
|||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
val name = Const.MAGISK_INSTALL_LOG_FILENAME.format(now.toTime(timeFormatStandard))
|
val name = Const.MAGISK_INSTALL_LOG_FILENAME.format(now.toTime(timeFormatStandard))
|
||||||
val file = MediaStoreUtils.newFile(name)
|
val file = MediaStoreUtils.getFile(name)
|
||||||
file.uri.outputStream().bufferedWriter().use { writer ->
|
file.uri.outputStream().bufferedWriter().use { writer ->
|
||||||
logItems.forEach {
|
logItems.forEach {
|
||||||
writer.write(it)
|
writer.write(it)
|
||||||
|
@ -9,8 +9,8 @@ import com.topjohnwu.magisk.arch.diffListOf
|
|||||||
import com.topjohnwu.magisk.arch.itemBindingOf
|
import com.topjohnwu.magisk.arch.itemBindingOf
|
||||||
import com.topjohnwu.magisk.data.repository.LogRepository
|
import com.topjohnwu.magisk.data.repository.LogRepository
|
||||||
import com.topjohnwu.magisk.events.SnackbarEvent
|
import com.topjohnwu.magisk.events.SnackbarEvent
|
||||||
import com.topjohnwu.magisk.utils.MediaStoreUtils
|
import com.topjohnwu.magisk.core.utils.MediaStoreUtils
|
||||||
import com.topjohnwu.magisk.utils.MediaStoreUtils.outputStream
|
import com.topjohnwu.magisk.core.utils.MediaStoreUtils.outputStream
|
||||||
import com.topjohnwu.magisk.utils.set
|
import com.topjohnwu.magisk.utils.set
|
||||||
import com.topjohnwu.magisk.view.TextItem
|
import com.topjohnwu.magisk.view.TextItem
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
@ -53,18 +53,16 @@ class LogViewModel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun saveMagiskLog() = withExternalRW {
|
fun saveMagiskLog() = withExternalRW {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
withContext(Dispatchers.IO) {
|
val now = Calendar.getInstance()
|
||||||
val now = Calendar.getInstance()
|
val filename = "magisk_log_%04d%02d%02d_%02d%02d%02d.log".format(
|
||||||
val filename = "magisk_log_%04d%02d%02d_%02d%02d%02d.log".format(
|
now.get(Calendar.YEAR), now.get(Calendar.MONTH) + 1,
|
||||||
now.get(Calendar.YEAR), now.get(Calendar.MONTH) + 1,
|
now.get(Calendar.DAY_OF_MONTH), now.get(Calendar.HOUR_OF_DAY),
|
||||||
now.get(Calendar.DAY_OF_MONTH), now.get(Calendar.HOUR_OF_DAY),
|
now.get(Calendar.MINUTE), now.get(Calendar.SECOND)
|
||||||
now.get(Calendar.MINUTE), now.get(Calendar.SECOND)
|
)
|
||||||
)
|
val logFile = MediaStoreUtils.getFile(filename)
|
||||||
val logFile = MediaStoreUtils.newFile(filename)
|
logFile.uri.outputStream().writer().use { it.write(consoleText) }
|
||||||
logFile.uri.outputStream().writer().use { it.write(consoleText) }
|
SnackbarEvent(logFile.toString()).publish()
|
||||||
SnackbarEvent(logFile.toString()).publish()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,13 +12,13 @@ import com.topjohnwu.magisk.core.Const
|
|||||||
import com.topjohnwu.magisk.core.Info
|
import com.topjohnwu.magisk.core.Info
|
||||||
import com.topjohnwu.magisk.core.UpdateCheckService
|
import com.topjohnwu.magisk.core.UpdateCheckService
|
||||||
import com.topjohnwu.magisk.core.utils.BiometricHelper
|
import com.topjohnwu.magisk.core.utils.BiometricHelper
|
||||||
|
import com.topjohnwu.magisk.core.utils.MediaStoreUtils
|
||||||
import com.topjohnwu.magisk.core.utils.availableLocales
|
import com.topjohnwu.magisk.core.utils.availableLocales
|
||||||
import com.topjohnwu.magisk.core.utils.currentLocale
|
import com.topjohnwu.magisk.core.utils.currentLocale
|
||||||
import com.topjohnwu.magisk.databinding.DialogSettingsAppNameBinding
|
import com.topjohnwu.magisk.databinding.DialogSettingsAppNameBinding
|
||||||
import com.topjohnwu.magisk.databinding.DialogSettingsDownloadPathBinding
|
import com.topjohnwu.magisk.databinding.DialogSettingsDownloadPathBinding
|
||||||
import com.topjohnwu.magisk.databinding.DialogSettingsUpdateChannelBinding
|
import com.topjohnwu.magisk.databinding.DialogSettingsUpdateChannelBinding
|
||||||
import com.topjohnwu.magisk.ktx.get
|
import com.topjohnwu.magisk.ktx.get
|
||||||
import com.topjohnwu.magisk.utils.MediaStoreUtils
|
|
||||||
import com.topjohnwu.magisk.utils.Utils
|
import com.topjohnwu.magisk.utils.Utils
|
||||||
import com.topjohnwu.magisk.utils.asTransitive
|
import com.topjohnwu.magisk.utils.asTransitive
|
||||||
import com.topjohnwu.magisk.utils.set
|
import com.topjohnwu.magisk.utils.set
|
||||||
@ -108,8 +108,8 @@ object AddShortcut : BaseSettingsItem.Blank() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
object DownloadPath : BaseSettingsItem.Input() {
|
object DownloadPath : BaseSettingsItem.Input() {
|
||||||
override var value = Config.downloadPath
|
override var value = Config.downloadDir
|
||||||
set(value) = setV(value, field, { field = it }) { Config.downloadPath = it }
|
set(value) = setV(value, field, { field = it }) { Config.downloadDir = it }
|
||||||
|
|
||||||
override val title = R.string.settings_download_path_title.asTransitive()
|
override val title = R.string.settings_download_path_title.asTransitive()
|
||||||
override val description get() = path.asTransitive()
|
override val description get() = path.asTransitive()
|
||||||
@ -122,7 +122,7 @@ object DownloadPath : BaseSettingsItem.Input() {
|
|||||||
|
|
||||||
@get:Bindable
|
@get:Bindable
|
||||||
val path
|
val path
|
||||||
get() = MediaStoreUtils.relativePath(result)
|
get() = MediaStoreUtils.fullPath(result)
|
||||||
|
|
||||||
override fun getView(context: Context) = DialogSettingsDownloadPathBinding
|
override fun getView(context: Context) = DialogSettingsDownloadPathBinding
|
||||||
.inflate(LayoutInflater.from(context)).also { it.data = this }.root
|
.inflate(LayoutInflater.from(context)).also { it.data = this }.root
|
||||||
|
Loading…
x
Reference in New Issue
Block a user