Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Contribute to GitLab
Sign in / Register
Toggle navigation
M
Magisk
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Administrator
Magisk
Commits
9e81db86
Commit
9e81db86
authored
Aug 21, 2020
by
vvb2060
Committed by
John Wu
Aug 22, 2020
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Support scoped storage
parent
1ed67eed
Hide whitespace changes
Inline
Side-by-side
Showing
18 changed files
with
248 additions
and
166 deletions
+248
-166
AndroidManifest.xml
app/shared/src/main/AndroidManifest.xml
+3
-1
BaseViewModel.kt
app/src/main/java/com/topjohnwu/magisk/arch/BaseViewModel.kt
+5
-0
Config.kt
app/src/main/java/com/topjohnwu/magisk/core/Config.kt
+1
-7
BaseDownloadService.kt
...com/topjohnwu/magisk/core/download/BaseDownloadService.kt
+4
-3
DownloadService.kt
...ava/com/topjohnwu/magisk/core/download/DownloadService.kt
+3
-1
ManagerUpgrade.kt
...java/com/topjohnwu/magisk/core/download/ManagerUpgrade.kt
+3
-4
ModuleProcessor.kt
...ava/com/topjohnwu/magisk/core/download/ModuleProcessor.kt
+3
-2
Subject.kt
...c/main/java/com/topjohnwu/magisk/core/download/Subject.kt
+14
-9
FlashZip.kt
...src/main/java/com/topjohnwu/magisk/core/tasks/FlashZip.kt
+4
-4
MagiskInstaller.kt
...n/java/com/topjohnwu/magisk/core/tasks/MagiskInstaller.kt
+14
-16
XAndroid.kt
app/src/main/java/com/topjohnwu/magisk/ktx/XAndroid.kt
+0
-37
XJava.kt
app/src/main/java/com/topjohnwu/magisk/ktx/XJava.kt
+1
-21
FlashFragment.kt
.../main/java/com/topjohnwu/magisk/ui/flash/FlashFragment.kt
+16
-18
FlashViewModel.kt
...main/java/com/topjohnwu/magisk/ui/flash/FlashViewModel.kt
+6
-6
LogViewModel.kt
...src/main/java/com/topjohnwu/magisk/ui/log/LogViewModel.kt
+15
-24
SettingsItems.kt
...in/java/com/topjohnwu/magisk/ui/settings/SettingsItems.kt
+3
-5
MediaStoreUtils.kt
...c/main/java/com/topjohnwu/magisk/utils/MediaStoreUtils.kt
+153
-0
Utils.kt
app/src/main/java/com/topjohnwu/magisk/utils/Utils.kt
+0
-8
No files found.
app/shared/src/main/AndroidManifest.xml
View file @
9e81db86
...
...
@@ -4,7 +4,9 @@
<uses-permission
android:name=
"android.permission.ACCESS_NETWORK_STATE"
/>
<uses-permission
android:name=
"android.permission.REQUEST_INSTALL_PACKAGES"
/>
<uses-permission
android:name=
"android.permission.WRITE_EXTERNAL_STORAGE"
/>
<uses-permission
android:name=
"android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion=
"28"
/>
<application
android:label=
"Magisk Manager"
...
...
app/src/main/java/com/topjohnwu/magisk/arch/BaseViewModel.kt
View file @
9e81db86
package
com.topjohnwu.magisk.arch
import
android.Manifest
import
android.os.Build
import
androidx.annotation.CallSuper
import
androidx.core.graphics.Insets
import
androidx.databinding.Bindable
...
...
@@ -86,6 +87,10 @@ abstract class BaseViewModel(
}
fun
withExternalRW
(
callback
:
()
->
Unit
)
{
if
(
Build
.
VERSION
.
SDK_INT
>=
Build
.
VERSION_CODES
.
Q
)
{
callback
()
return
}
withPermissions
(
Manifest
.
permission
.
WRITE_EXTERNAL_STORAGE
)
{
if
(!
it
)
{
SnackbarEvent
(
R
.
string
.
external_rw_permission_denied
).
publish
()
...
...
app/src/main/java/com/topjohnwu/magisk/core/Config.kt
View file @
9e81db86
...
...
@@ -2,7 +2,6 @@ package com.topjohnwu.magisk.core
import
android.content.Context
import
android.content.SharedPreferences
import
android.os.Environment
import
android.util.Xml
import
androidx.appcompat.app.AppCompatDelegate
import
androidx.core.content.edit
...
...
@@ -17,7 +16,6 @@ import com.topjohnwu.magisk.di.Protected
import
com.topjohnwu.magisk.ktx.get
import
com.topjohnwu.magisk.ktx.inject
import
com.topjohnwu.magisk.ui.theme.Theme
import
com.topjohnwu.magisk.utils.Utils
import
com.topjohnwu.superuser.Shell
import
com.topjohnwu.superuser.io.SuFile
import
com.topjohnwu.superuser.io.SuFileInputStream
...
...
@@ -111,7 +109,7 @@ object Config : PreferenceModel, DBConfig {
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
,
"Magisk Manager"
)
var
repoOrder
by
preference
(
Key
.
REPO_ORDER
,
Value
.
ORDER_DATE
)
var
suDefaultTimeout
by
preferenceStrInt
(
Key
.
SU_REQUEST_TIMEOUT
,
10
)
...
...
@@ -143,10 +141,6 @@ object Config : PreferenceModel, DBConfig {
var
suManager
by
dbStrings
(
Key
.
SU_MANAGER
,
""
,
true
)
var
keyStoreRaw
by
dbStrings
(
Key
.
KEYSTORE
,
""
,
true
)
// Always return a path in external storage where we can write
val
downloadDirectory
get
()
=
Utils
.
ensureDownloadPath
(
downloadPath
)
?:
get
<
Context
>().
getExternalFilesDir
(
null
)
!!
private
const
val
SU_FINGERPRINT
=
"su_fingerprint"
fun
initialize
()
{
...
...
app/src/main/java/com/topjohnwu/magisk/core/download/BaseDownloadService.kt
View file @
9e81db86
...
...
@@ -10,8 +10,9 @@ import com.topjohnwu.magisk.core.ForegroundTracker
import
com.topjohnwu.magisk.core.base.BaseService
import
com.topjohnwu.magisk.core.utils.ProgressInputStream
import
com.topjohnwu.magisk.data.network.GithubRawServices
import
com.topjohnwu.magisk.ktx.checkSum
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
kotlinx.coroutines.CoroutineScope
import
kotlinx.coroutines.Dispatchers
...
...
@@ -68,14 +69,14 @@ abstract class BaseDownloadService : BaseService(), KoinComponent {
// -- Download logic
private
suspend
fun
Subject
.
startDownload
()
{
val
skip
=
this
is
Subject
.
Magisk
&&
file
.
exists
()
&&
file
.
checkSum
(
"MD5"
,
magisk
.
md5
)
val
skip
=
this
is
Subject
.
Magisk
&&
file
.
checkSum
(
"MD5"
,
magisk
.
md5
)
if
(!
skip
)
{
val
stream
=
service
.
fetchFile
(
url
).
toProgressStream
(
this
)
when
(
this
)
{
is
Subject
.
Module
->
// Download and process on-the-fly
stream
.
toModule
(
file
,
service
.
fetchInstaller
().
byteStream
())
else
->
stream
.
writeTo
(
file
)
stream
.
copyTo
(
file
.
outputStream
()
)
}
}
val
newId
=
notifyFinish
(
this
)
...
...
app/src/main/java/com/topjohnwu/magisk/core/download/DownloadService.kt
View file @
9e81db86
...
...
@@ -5,7 +5,9 @@ import android.app.Notification
import
android.app.PendingIntent
import
android.content.Context
import
android.content.Intent
import
android.net.Uri
import
android.os.Build
import
androidx.core.net.toFile
import
com.topjohnwu.magisk.core.download.Action.*
import
com.topjohnwu.magisk.core.download.Action.Flash.Secondary
import
com.topjohnwu.magisk.core.download.Subject.*
...
...
@@ -73,7 +75,7 @@ open class DownloadService : BaseDownloadService() {
private
fun
Notification
.
Builder
.
setIntent
(
subject
:
Manager
)
=
when
(
subject
.
action
)
{
APK
.
Upgrade
->
setContentIntent
(
APKInstall
.
installIntent
(
context
,
subject
.
file
))
APK
.
Upgrade
->
setContentIntent
(
APKInstall
.
installIntent
(
context
,
subject
.
file
.
toFile
()
))
else
->
setContentIntent
(
Intent
())
}
...
...
app/src/main/java/com/topjohnwu/magisk/core/download/ManagerUpgrade.kt
View file @
9e81db86
package
com.topjohnwu.magisk.core.download
import
androidx.core.net.toFile
import
com.topjohnwu.magisk.BuildConfig
import
com.topjohnwu.magisk.DynAPK
import
com.topjohnwu.magisk.ProcessPhoenix
...
...
@@ -61,13 +62,11 @@ private fun DownloadService.restore(apk: File, id: Int) {
.
setContentText
(
""
)
}
Config
.
export
()
// Make it world readable
apk
.
setReadable
(
true
,
false
)
Shell
.
su
(
"pm install $apk && pm uninstall $packageName"
).
exec
()
}
suspend
fun
DownloadService
.
handleAPK
(
subject
:
Subject
.
Manager
)
=
when
(
subject
.
action
)
{
is
Upgrade
->
upgrade
(
subject
.
file
,
subject
.
notifyID
())
is
Restore
->
restore
(
subject
.
file
,
subject
.
notifyID
())
is
Upgrade
->
upgrade
(
subject
.
file
.
toFile
()
,
subject
.
notifyID
())
is
Restore
->
restore
(
subject
.
file
.
toFile
()
,
subject
.
notifyID
())
}
app/src/main/java/com/topjohnwu/magisk/core/download/ModuleProcessor.kt
View file @
9e81db86
package
com.topjohnwu.magisk.core.download
import
android.net.Uri
import
com.topjohnwu.magisk.ktx.withStreams
import
java.io.File
import
com.topjohnwu.magisk.utils.MediaStoreUtils.outputStream
import
java.io.InputStream
import
java.util.zip.ZipEntry
import
java.util.zip.ZipInputStream
import
java.util.zip.ZipOutputStream
fun
InputStream
.
toModule
(
file
:
File
,
installer
:
InputStream
)
{
fun
InputStream
.
toModule
(
file
:
Uri
,
installer
:
InputStream
)
{
val
input
=
ZipInputStream
(
buffered
())
val
output
=
ZipOutputStream
(
file
.
outputStream
().
buffered
())
...
...
app/src/main/java/com/topjohnwu/magisk/core/download/Subject.kt
View file @
9e81db86
package
com.topjohnwu.magisk.core.download
import
android.content.Context
import
android.net.Uri
import
android.os.Parcelable
import
com.topjohnwu.magisk.core.Config
import
androidx.core.net.toUri
import
com.topjohnwu.magisk.core.Info
import
com.topjohnwu.magisk.core.model.MagiskJson
import
com.topjohnwu.magisk.core.model.ManagerJson
import
com.topjohnwu.magisk.core.model.module.Repo
import
com.topjohnwu.magisk.ktx.cachedFile
import
com.topjohnwu.magisk.ktx.get
import
com.topjohnwu.magisk.utils.MediaStoreUtils
import
kotlinx.android.parcel.IgnoredOnParcel
import
kotlinx.android.parcel.Parcelize
import
java.io.File
sealed
class
Subject
:
Parcelable
{
abstract
val
url
:
String
abstract
val
file
:
File
abstract
val
file
:
Uri
abstract
val
action
:
Action
open
val
title
:
String
get
()
=
file
.
name
abstract
val
title
:
String
@Parcelize
class
Module
(
...
...
@@ -26,10 +27,11 @@ sealed class Subject : Parcelable {
override
val
action
:
Action
)
:
Subject
()
{
override
val
url
:
String
get
()
=
module
.
zipUrl
override
val
title
:
String
get
()
=
module
.
downloadFilename
@IgnoredOnParcel
override
val
file
by
lazy
{
File
(
Config
.
downloadDirectory
,
module
.
downloadFilename
)
MediaStoreUtils
.
newFile
(
title
).
uri
}
}
...
...
@@ -49,7 +51,7 @@ sealed class Subject : Parcelable {
@IgnoredOnParcel
override
val
file
by
lazy
{
get
<
Context
>().
cachedFile
(
"manager.apk"
)
get
<
Context
>().
cachedFile
(
"manager.apk"
)
.
toUri
()
}
}
...
...
@@ -67,7 +69,7 @@ sealed class Subject : Parcelable {
@IgnoredOnParcel
override
val
file
by
lazy
{
get
<
Context
>().
cachedFile
(
"magisk.zip"
)
get
<
Context
>().
cachedFile
(
"magisk.zip"
)
.
toUri
()
}
}
...
...
@@ -75,21 +77,24 @@ sealed class Subject : Parcelable {
private
class
Uninstall
:
Magisk
()
{
override
val
action
get
()
=
Action
.
Uninstall
override
val
url
:
String
get
()
=
Info
.
remote
.
uninstaller
.
link
override
val
title
:
String
get
()
=
"uninstall.zip"
@IgnoredOnParcel
override
val
file
by
lazy
{
get
<
Context
>().
cachedFile
(
"uninstall.zip"
)
get
<
Context
>().
cachedFile
(
title
).
toUri
(
)
}
}
@Parcelize
private
class
Download
:
Magisk
()
{
override
val
action
get
()
=
Action
.
Download
override
val
url
:
String
get
()
=
magisk
.
link
override
val
title
:
String
get
()
=
"Magisk-${magisk.version}(${magisk.versionCode}).zip"
@IgnoredOnParcel
override
val
file
by
lazy
{
File
(
Config
.
downloadDirectory
,
"Magisk-${magisk.version}(${magisk.versionCode}).zip"
)
MediaStoreUtils
.
getFile
(
title
).
uri
}
}
...
...
app/src/main/java/com/topjohnwu/magisk/core/tasks/FlashZip.kt
View file @
9e81db86
...
...
@@ -5,8 +5,8 @@ import android.net.Uri
import
androidx.core.os.postDelayed
import
com.topjohnwu.magisk.core.Const
import
com.topjohnwu.magisk.core.utils.unzip
import
com.topjohnwu.magisk.
ktx.file
Name
import
com.topjohnwu.magisk.
ktx.readUri
import
com.topjohnwu.magisk.
utils.MediaStoreUtils.getDisplay
Name
import
com.topjohnwu.magisk.
utils.MediaStoreUtils.inputStream
import
com.topjohnwu.superuser.Shell
import
com.topjohnwu.superuser.internal.UiThreadHandler
import
kotlinx.coroutines.Dispatchers
...
...
@@ -47,7 +47,7 @@ open class FlashZip(
console
.
add
(
"- Copying zip to temp directory"
)
runCatching
{
context
.
readUri
(
mUri
).
use
{
input
->
mUri
.
inputStream
(
).
use
{
input
->
tmpFile
.
outputStream
().
use
{
out
->
input
.
copyTo
(
out
)
}
}
}.
getOrElse
{
...
...
@@ -70,7 +70,7 @@ open class FlashZip(
return
false
}
console
.
add
(
"- Installing ${mUri.
fileName
}"
)
console
.
add
(
"- Installing ${mUri.
getDisplayName()
}"
)
val
parentFile
=
tmpFile
.
parent
?:
return
false
...
...
app/src/main/java/com/topjohnwu/magisk/core/tasks/MagiskInstaller.kt
View file @
9e81db86
...
...
@@ -6,18 +6,18 @@ import android.net.Uri
import
android.os.Build
import
android.widget.Toast
import
androidx.annotation.WorkerThread
import
androidx.core.net.toUri
import
androidx.core.os.postDelayed
import
androidx.localbroadcastmanager.content.LocalBroadcastManager
import
com.topjohnwu.magisk.R
import
com.topjohnwu.magisk.core.Config
import
com.topjohnwu.magisk.core.Info
import
com.topjohnwu.magisk.data.network.GithubRawServices
import
com.topjohnwu.magisk.di.Protected
import
com.topjohnwu.magisk.events.dialog.EnvFixDialog
import
com.topjohnwu.magisk.ktx.readUri
import
com.topjohnwu.magisk.ktx.reboot
import
com.topjohnwu.magisk.ktx.withStreams
import
com.topjohnwu.magisk.utils.MediaStoreUtils
import
com.topjohnwu.magisk.utils.MediaStoreUtils.inputStream
import
com.topjohnwu.magisk.utils.MediaStoreUtils.outputStream
import
com.topjohnwu.magisk.utils.Utils
import
com.topjohnwu.signing.SignBoot
import
com.topjohnwu.superuser.Shell
...
...
@@ -49,7 +49,7 @@ abstract class MagiskInstallImpl : KoinComponent {
protected
lateinit
var
installDir
:
File
private
lateinit
var
srcBoot
:
String
private
lateinit
var
destFile
:
File
private
lateinit
var
destFile
:
MediaStoreUtils
.
MediaStore
File
private
lateinit
var
zipUri
:
Uri
protected
val
console
:
MutableList
<
String
>
...
...
@@ -113,7 +113,7 @@ abstract class MagiskInstallImpl : KoinComponent {
console
.
add
(
"- Device platform: "
+
Build
.
CPU_ABI
)
try
{
ZipInputStream
(
context
.
readUri
(
zipUri
).
buffered
()).
use
{
zi
->
ZipInputStream
(
zipUri
.
inputStream
(
).
buffered
()).
use
{
zi
->
lateinit
var
ze
:
ZipEntry
while
(
zi
.
nextEntry
?.
let
{
ze
=
it
}
!=
null
)
{
if
(
ze
.
isDirectory
)
...
...
@@ -166,7 +166,7 @@ abstract class MagiskInstallImpl : KoinComponent {
private
fun
handleTar
(
input
:
InputStream
)
{
console
.
add
(
"- Processing tar file"
)
var
vbmeta
=
false
val
tarOut
=
TarOutputStream
(
destFile
)
val
tarOut
=
TarOutputStream
(
destFile
.
uri
.
outputStream
()
)
this
.
tarOut
=
tarOut
TarInputStream
(
input
).
use
{
tarIn
->
lateinit
var
entry
:
TarEntry
...
...
@@ -224,7 +224,7 @@ abstract class MagiskInstallImpl : KoinComponent {
private
fun
handleFile
(
uri
:
Uri
):
Boolean
{
try
{
context
.
readUri
(
uri
).
buffered
().
use
{
uri
.
inputStream
(
).
buffered
().
use
{
it
.
mark
(
500
)
val
magic
=
ByteArray
(
5
)
if
(
it
.
skip
(
257
)
!=
257L
||
it
.
read
(
magic
)
!=
magic
.
size
)
{
...
...
@@ -233,12 +233,12 @@ abstract class MagiskInstallImpl : KoinComponent {
}
it
.
reset
()
if
(
magic
.
contentEquals
(
"ustar"
.
toByteArray
()))
{
destFile
=
File
(
Config
.
downloadDirectory
,
"magisk_patched.tar"
)
destFile
=
MediaStoreUtils
.
newFile
(
"magisk_patched.tar"
)
handleTar
(
it
)
}
else
{
// Raw image
srcBoot
=
File
(
installDir
,
"boot.img"
).
path
destFile
=
File
(
Config
.
downloadDirectory
,
"magisk_patched.img"
)
destFile
=
MediaStoreUtils
.
newFile
(
"magisk_patched.img"
)
console
.
add
(
"- Copying image to cache"
)
FileOutputStream
(
srcBoot
).
use
{
out
->
it
.
copyTo
(
out
)
}
}
...
...
@@ -324,7 +324,7 @@ abstract class MagiskInstallImpl : KoinComponent {
patched
.
length
()))
tarOut
=
null
it
}
?:
destFile
.
outputStream
()
}
?:
destFile
.
uri
.
outputStream
()
SuFileInputStream
(
patched
).
use
{
it
.
copyTo
(
os
);
os
.
close
()
}
}
catch
(
e
:
IOException
)
{
console
.
add
(
"! Failed to output to $destFile"
)
...
...
@@ -344,9 +344,7 @@ abstract class MagiskInstallImpl : KoinComponent {
private
suspend
fun
postOTA
():
Boolean
{
val
bootctl
=
SuFile
(
"/data/adb/bootctl"
)
try
{
withStreams
(
service
.
fetchBootctl
().
byteStream
(),
SuFileOutputStream
(
bootctl
))
{
input
,
out
->
input
.
copyTo
(
out
)
}
service
.
fetchBootctl
().
byteStream
().
copyTo
(
SuFileOutputStream
(
bootctl
))
}
catch
(
e
:
IOException
)
{
console
.
add
(
"! Unable to download bootctl"
)
Timber
.
e
(
e
)
...
...
@@ -374,10 +372,10 @@ abstract class MagiskInstallImpl : KoinComponent {
protected
suspend
fun
secondSlot
()
=
findSecondaryImage
()
&&
extractZip
()
&&
patchBoot
()
&&
flashBoot
()
&&
postOTA
()
protected
fun
fixEnv
(
zip
:
File
):
Boolean
{
protected
fun
fixEnv
(
zip
:
Uri
):
Boolean
{
installDir
=
SuFile
(
"/data/adb/magisk"
)
Shell
.
su
(
"rm -rf /data/adb/magisk/*"
).
exec
()
zipUri
=
zip
.
toUri
()
zipUri
=
zip
return
extractZip
()
&&
Shell
.
su
(
"fix_env"
).
exec
().
isSuccess
}
...
...
@@ -432,7 +430,7 @@ sealed class MagiskInstaller(
}
class
EnvFixTask
(
private
val
zip
:
File
private
val
zip
:
Uri
)
:
MagiskInstallImpl
()
{
override
suspend
fun
operations
()
=
fixEnv
(
zip
)
...
...
app/src/main/java/com/topjohnwu/magisk/ktx/XAndroid.kt
View file @
9e81db86
...
...
@@ -21,7 +21,6 @@ import android.graphics.drawable.LayerDrawable
import
android.net.Uri
import
android.os.Build
import
android.os.Build.VERSION.SDK_INT
import
android.provider.OpenableColumns
import
android.text.PrecomputedText
import
android.view.View
import
android.view.ViewGroup
...
...
@@ -33,7 +32,6 @@ import androidx.annotation.DrawableRes
import
androidx.appcompat.content.res.AppCompatResources
import
androidx.core.content.ContextCompat
import
androidx.core.content.getSystemService
import
androidx.core.net.toFile
import
androidx.core.net.toUri
import
androidx.core.text.PrecomputedTextCompat
import
androidx.core.view.isGone
...
...
@@ -43,7 +41,6 @@ import androidx.fragment.app.Fragment
import
androidx.interpolator.view.animation.FastOutSlowInInterpolator
import
androidx.transition.AutoTransition
import
androidx.transition.TransitionManager
import
com.topjohnwu.magisk.FileProvider
import
com.topjohnwu.magisk.R
import
com.topjohnwu.magisk.core.Const
import
com.topjohnwu.magisk.core.ResMgr
...
...
@@ -56,7 +53,6 @@ import kotlinx.coroutines.Dispatchers
import
kotlinx.coroutines.GlobalScope
import
kotlinx.coroutines.launch
import
java.io.File
import
java.io.FileNotFoundException
import
java.lang.reflect.Array
as
JArray
val
packageName
:
String
get
()
=
get
<
Context
>().
packageName
...
...
@@ -92,23 +88,6 @@ val ApplicationInfo.packageInfo: PackageInfo?
}
}
val
Uri
.
fileName
:
String
get
()
{
var
name
:
String
?
=
null
get
<
Context
>().
contentResolver
.
query
(
this
,
null
,
null
,
null
,
null
)
?.
use
{
c
->
val
nameIndex
=
c
.
getColumnIndex
(
OpenableColumns
.
DISPLAY_NAME
)
if
(
nameIndex
!=
-
1
)
{
c
.
moveToFirst
()
name
=
c
.
getString
(
nameIndex
)
}
}
if
(
name
==
null
&&
path
!=
null
)
{
val
idx
=
path
!!
.
lastIndexOf
(
'/'
)
name
=
path
!!
.
substring
(
idx
+
1
)
}
return
name
.
orEmpty
()
}
fun
PackageManager
.
activities
(
packageName
:
String
)
=
getPackageInfo
(
packageName
,
GET_ACTIVITIES
)
...
...
@@ -123,9 +102,6 @@ fun PackageManager.providers(packageName: String) =
fun
Context
.
rawResource
(
id
:
Int
)
=
resources
.
openRawResource
(
id
)
fun
Context
.
readUri
(
uri
:
Uri
)
=
contentResolver
.
openInputStream
(
uri
)
?:
throw
FileNotFoundException
()
fun
Context
.
getBitmap
(
id
:
Int
):
Bitmap
{
var
drawable
=
AppCompatResources
.
getDrawable
(
this
,
id
)
!!
if
(
drawable
is
BitmapDrawable
)
...
...
@@ -249,17 +225,6 @@ fun Intent.toCommand(args: MutableList<String> = mutableListOf()): MutableList<S
return
args
}
fun
File
.
provide
(
context
:
Context
=
get
()):
Uri
{
return
FileProvider
.
getUriForFile
(
context
,
context
.
packageName
+
".provider"
,
this
)
}
fun
File
.
mv
(
destination
:
File
)
{
inputStream
().
writeTo
(
destination
)
deleteRecursively
()
}
fun
String
.
toFile
()
=
File
(
this
)
fun
Intent
.
chooser
(
title
:
String
=
"Pick an app"
)
=
Intent
.
createChooser
(
this
,
title
)
fun
Context
.
cachedFile
(
name
:
String
)
=
File
(
cacheDir
,
name
)
...
...
@@ -329,8 +294,6 @@ fun Context.unwrap(): Context {
return
context
}
fun
Uri
.
writeTo
(
file
:
File
)
=
toFile
().
copyTo
(
file
)
fun
Context
.
hasPermissions
(
vararg
permissions
:
String
)
=
permissions
.
all
{
ContextCompat
.
checkSelfPermission
(
this
,
it
)
==
PERMISSION_GRANTED
}
...
...
app/src/main/java/com/topjohnwu/magisk/ktx/XJava.kt
View file @
9e81db86
...
...
@@ -8,12 +8,10 @@ import java.io.InputStream
import
java.io.OutputStream
import
java.lang.reflect.Field
import
java.lang.reflect.Method
import
java.security.MessageDigest
import
java.text.SimpleDateFormat
import
java.util.*
import
java.util.zip.ZipEntry
import
java.util.zip.ZipInputStream
import
kotlin.experimental.and
fun
ZipInputStream
.
forEach
(
callback
:
(
ZipEntry
)
->
Unit
)
{
var
entry
:
ZipEntry
?
=
nextEntry
...
...
@@ -23,25 +21,7 @@ fun ZipInputStream.forEach(callback: (ZipEntry) -> Unit) {
}
}
fun
InputStream
.
writeTo
(
file
:
File
)
=
withStreams
(
this
,
file
.
outputStream
())
{
reader
,
writer
->
reader
.
copyTo
(
writer
)
}
fun
File
.
checkSum
(
alg
:
String
,
reference
:
String
)
=
runCatching
{
inputStream
().
use
{
val
digest
=
MessageDigest
.
getInstance
(
alg
)
it
.
copyTo
(
object
:
OutputStream
()
{
override
fun
write
(
b
:
Int
)
{
digest
.
update
(
b
.
toByte
())
}
override
fun
write
(
b
:
ByteArray
,
off
:
Int
,
len
:
Int
)
{
digest
.
update
(
b
,
off
,
len
)
}
})
val
sb
=
StringBuilder
()
digest
.
digest
().
forEach
{
b
->
sb
.
append
(
"%02x"
.
format
(
b
and
0
xff
.
toByte
()))
}
sb
.
toString
()
==
reference
}
}.
getOrElse
{
false
}
fun
InputStream
.
writeTo
(
file
:
File
)
=
this
.
copyTo
(
file
.
outputStream
())
inline
fun
<
In
:
InputStream
,
Out
:
OutputStream
>
withStreams
(
inStream
:
In
,
...
...
app/src/main/java/com/topjohnwu/magisk/ui/flash/FlashFragment.kt
View file @
9e81db86
...
...
@@ -6,7 +6,6 @@ import android.content.pm.ActivityInfo
import
android.net.Uri
import
android.os.Bundle
import
android.view.*
import
androidx.core.net.toUri
import
androidx.navigation.NavDeepLinkBuilder
import
com.topjohnwu.magisk.R
import
com.topjohnwu.magisk.arch.BaseUIActivity
...
...
@@ -17,7 +16,6 @@ import com.topjohnwu.magisk.databinding.FragmentFlashMd2Binding
import
com.topjohnwu.magisk.ui.MainActivity
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
...
...
@@ -89,29 +87,29 @@ class FlashFragment : BaseUIFragment<FlashViewModel, FragmentFlashMd2Binding>()
/* Flashing is understood as installing / flashing magisk itself */
fun
flashIntent
(
context
:
Context
,
file
:
File
,
isSecondSlot
:
Boolean
,
id
:
Int
=
-
1
)
=
args
(
installer
=
file
.
toUri
()
,
fun
flashIntent
(
context
:
Context
,
file
:
Uri
,
isSecondSlot
:
Boolean
,
id
:
Int
=
-
1
)
=
args
(
installer
=
file
,
action
=
flashType
(
isSecondSlot
),
dismissId
=
id
).
let
{
createIntent
(
context
,
it
)
}
fun
flash
(
file
:
File
,
isSecondSlot
:
Boolean
,
id
:
Int
)
=
toFlash
(
installer
=
file
.
toUri
()
,
fun
flash
(
file
:
Uri
,
isSecondSlot
:
Boolean
,
id
:
Int
)
=
toFlash
(
installer
=
file
,
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
()
,
fun
patchIntent
(
context
:
Context
,
file
:
Uri
,
uri
:
Uri
,
id
:
Int
=
-
1
)
=
args
(
installer
=
file
,
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
()
,
fun
patch
(
file
:
Uri
,
uri
:
Uri
,
id
:
Int
)
=
toFlash
(
installer
=
file
,
action
=
Const
.
Value
.
PATCH_FILE
,
additionalData
=
uri
,
dismissId
=
id
...
...
@@ -119,28 +117,28 @@ class FlashFragment : BaseUIFragment<FlashViewModel, FragmentFlashMd2Binding>()
/* Uninstalling is understood as removing magisk entirely */
fun
uninstallIntent
(
context
:
Context
,
file
:
File
,
id
:
Int
=
-
1
)
=
args
(
installer
=
file
.
toUri
()
,
fun
uninstallIntent
(
context
:
Context
,
file
:
Uri
,
id
:
Int
=
-
1
)
=
args
(
installer
=
file
,
action
=
Const
.
Value
.
UNINSTALL
,
dismissId
=
id
).
let
{
createIntent
(
context
,
it
)
}
fun
uninstall
(
file
:
File
,
id
:
Int
)
=
toFlash
(
installer
=
file
.
toUri
()
,
fun
uninstall
(
file
:
Uri
,
id
:
Int
)
=
toFlash
(
installer
=
file
,
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
()
,
fun
installIntent
(
context
:
Context
,
file
:
Uri
,
id
:
Int
=
-
1
)
=
args
(
installer
=
file
,
action
=
Const
.
Value
.
FLASH_ZIP
,
dismissId
=
id
).
let
{
createIntent
(
context
,
it
)
}
fun
install
(
file
:
File
,
id
:
Int
)
=
toFlash
(
installer
=
file
.
toUri
()
,
fun
install
(
file
:
Uri
,
id
:
Int
)
=
toFlash
(
installer
=
file
,
action
=
Const
.
Value
.
FLASH_ZIP
,
dismissId
=
id
).
let
{
BaseUIActivity
.
postDirections
(
it
)
}
...
...
app/src/main/java/com/topjohnwu/magisk/ui/flash/FlashViewModel.kt
View file @
9e81db86
...
...
@@ -10,13 +10,14 @@ import com.topjohnwu.magisk.R
import
com.topjohnwu.magisk.arch.BaseViewModel
import
com.topjohnwu.magisk.arch.diffListOf
import
com.topjohnwu.magisk.arch.itemBindingOf
import
com.topjohnwu.magisk.core.Config
import
com.topjohnwu.magisk.core.Const
import
com.topjohnwu.magisk.core.tasks.FlashZip
import
com.topjohnwu.magisk.core.tasks.MagiskInstaller
import
com.topjohnwu.magisk.databinding.RvBindingAdapter
import
com.topjohnwu.magisk.events.SnackbarEvent
import
com.topjohnwu.magisk.ktx.*
import
com.topjohnwu.magisk.utils.MediaStoreUtils
import
com.topjohnwu.magisk.utils.MediaStoreUtils.outputStream
import
com.topjohnwu.magisk.utils.set
import
com.topjohnwu.magisk.view.Notifications
import
com.topjohnwu.superuser.CallbackList
...
...
@@ -24,7 +25,6 @@ import com.topjohnwu.superuser.Shell
import
kotlinx.coroutines.Dispatchers
import
kotlinx.coroutines.launch
import
kotlinx.coroutines.withContext
import
java.io.File
class
FlashViewModel
(
args
:
FlashFragmentArgs
,
...
...
@@ -107,17 +107,17 @@ class FlashViewModel(
private
fun
savePressed
()
=
withExternalRW
{
viewModelScope
.
launch
{
val
name
=
Const
.
MAGISK_INSTALL_LOG_FILENAME
.
format
(
now
.
toTime
(
timeFormatStandard
))
val
file
=
File
(
Config
.
downloadDirectory
,
name
)
withContext
(
Dispatchers
.
IO
)
{
file
.
bufferedWriter
().
use
{
writer
->
val
name
=
Const
.
MAGISK_INSTALL_LOG_FILENAME
.
format
(
now
.
toTime
(
timeFormatStandard
))
val
file
=
MediaStoreUtils
.
newFile
(
name
)
file
.
uri
.
outputStream
().
bufferedWriter
().
use
{
writer
->
logItems
.
forEach
{
writer
.
write
(
it
)
writer
.
newLine
()
}
}
SnackbarEvent
(
file
.
toString
()).
publish
()
}
SnackbarEvent
(
file
.
path
).
publish
()
}
}
...
...
app/src/main/java/com/topjohnwu/magisk/ui/log/LogViewModel.kt
View file @
9e81db86
...
...
@@ -7,19 +7,15 @@ import com.topjohnwu.magisk.R
import
com.topjohnwu.magisk.arch.BaseViewModel
import
com.topjohnwu.magisk.arch.diffListOf
import
com.topjohnwu.magisk.arch.itemBindingOf
import
com.topjohnwu.magisk.core.Config
import
com.topjohnwu.magisk.core.Const
import
com.topjohnwu.magisk.data.repository.LogRepository
import
com.topjohnwu.magisk.events.SnackbarEvent
import
com.topjohnwu.magisk.utils.MediaStoreUtils
import
com.topjohnwu.magisk.utils.MediaStoreUtils.outputStream
import
com.topjohnwu.magisk.utils.set
import
com.topjohnwu.magisk.view.TextItem
import
com.topjohnwu.superuser.Shell
import
kotlinx.coroutines.Dispatchers
import
kotlinx.coroutines.launch
import
kotlinx.coroutines.withContext
import
timber.log.Timber
import
java.io.File
import
java.io.IOException
import
java.util.*
class
LogViewModel
(
...
...
@@ -40,7 +36,7 @@ class LogViewModel(
// --- magisk log
@get
:
Bindable
var
consoleText
=
" "
var
consoleText
=
" "
set
(
value
)
=
set
(
value
,
field
,
{
field
=
it
},
BR
.
consoleText
)
override
fun
refresh
()
=
viewModelScope
.
launch
{
...
...
@@ -57,23 +53,18 @@ class LogViewModel(
}
fun
saveMagiskLog
()
=
withExternalRW
{
val
now
=
Calendar
.
getInstance
()
val
filename
=
"magisk_log_%04d%02d%02d_%02d%02d%02d.log"
.
format
(
now
.
get
(
Calendar
.
YEAR
),
now
.
get
(
Calendar
.
MONTH
)
+
1
,
now
.
get
(
Calendar
.
DAY_OF_MONTH
),
now
.
get
(
Calendar
.
HOUR_OF_DAY
),
now
.
get
(
Calendar
.
MINUTE
),
now
.
get
(
Calendar
.
SECOND
)
)
val
logFile
=
File
(
Config
.
downloadDirectory
,
filename
)
try
{
logFile
.
createNewFile
()
}
catch
(
e
:
IOException
)
{
Timber
.
e
(
e
)
return
@withExternalRW
}
Shell
.
su
(
"cat ${Const.MAGISK_LOG} > $logFile"
).
submit
{
SnackbarEvent
(
logFile
.
path
).
publish
()
viewModelScope
.
launch
{
withContext
(
Dispatchers
.
IO
)
{
val
now
=
Calendar
.
getInstance
()
val
filename
=
"magisk_log_%04d%02d%02d_%02d%02d%02d.log"
.
format
(
now
.
get
(
Calendar
.
YEAR
),
now
.
get
(
Calendar
.
MONTH
)
+
1
,
now
.
get
(
Calendar
.
DAY_OF_MONTH
),
now
.
get
(
Calendar
.
HOUR_OF_DAY
),
now
.
get
(
Calendar
.
MINUTE
),
now
.
get
(
Calendar
.
SECOND
)
)
val
logFile
=
MediaStoreUtils
.
newFile
(
filename
)
logFile
.
uri
.
outputStream
().
writer
().
use
{
it
.
write
(
consoleText
)
}
SnackbarEvent
(
logFile
.
toString
()).
publish
()
}
}
}
...
...
app/src/main/java/com/topjohnwu/magisk/ui/settings/SettingsItems.kt
View file @
9e81db86
...
...
@@ -2,7 +2,6 @@ package com.topjohnwu.magisk.ui.settings
import
android.content.Context
import
android.os.Build
import
android.os.Environment
import
android.view.LayoutInflater
import
androidx.databinding.Bindable
import
com.topjohnwu.magisk.BR
...
...
@@ -19,13 +18,13 @@ import com.topjohnwu.magisk.databinding.DialogSettingsAppNameBinding
import
com.topjohnwu.magisk.databinding.DialogSettingsDownloadPathBinding
import
com.topjohnwu.magisk.databinding.DialogSettingsUpdateChannelBinding
import
com.topjohnwu.magisk.ktx.get
import
com.topjohnwu.magisk.utils.MediaStoreUtils
import
com.topjohnwu.magisk.utils.Utils
import
com.topjohnwu.magisk.utils.asTransitive
import
com.topjohnwu.magisk.utils.set
import
com.topjohnwu.superuser.Shell
import
kotlinx.coroutines.CoroutineScope
import
kotlinx.coroutines.launch
import
java.io.File
// --- Customization
...
...
@@ -115,8 +114,7 @@ object DownloadPath : BaseSettingsItem.Input() {
override
val
title
=
R
.
string
.
settings_download_path_title
.
asTransitive
()
override
val
description
get
()
=
path
.
asTransitive
()
override
val
inputResult
:
String
?
get
()
=
if
(
Utils
.
ensureDownloadPath
(
result
)
!=
null
)
result
else
null
override
val
inputResult
:
String
get
()
=
result
@get
:
Bindable
var
result
=
value
...
...
@@ -124,7 +122,7 @@ object DownloadPath : BaseSettingsItem.Input() {
@get
:
Bindable
val
path
get
()
=
File
(
Environment
.
getExternalStorageDirectory
(),
result
).
absolutePath
.
orEmpty
(
)
get
()
=
MediaStoreUtils
.
relativePath
(
result
)
override
fun
getView
(
context
:
Context
)
=
DialogSettingsDownloadPathBinding
.
inflate
(
LayoutInflater
.
from
(
context
)).
also
{
it
.
data
=
this
}.
root
...
...
app/src/main/java/com/topjohnwu/magisk/utils/MediaStoreUtils.kt
0 → 100644
View file @
9e81db86
package
com.topjohnwu.magisk.utils
import
android.annotation.SuppressLint
import
android.content.ContentResolver
import
android.content.ContentUris
import
android.content.ContentValues
import
android.content.Context
import
android.net.Uri
import
android.os.Build
import
android.os.Environment
import
android.provider.MediaStore
import
android.provider.OpenableColumns
import
com.topjohnwu.magisk.core.Config
import
com.topjohnwu.magisk.ktx.get
import
java.io.File
import
java.io.FileNotFoundException
import
java.io.IOException
import
java.io.OutputStream
import
java.security.MessageDigest
import
kotlin.experimental.and
@Suppress
(
"DEPRECATION"
)
object
MediaStoreUtils
{
private
val
cr
:
ContentResolver
by
lazy
{
get
<
Context
>().
contentResolver
}
@SuppressLint
(
"InlinedApi"
)
private
val
tableUri
:
Uri
=
if
(
Build
.
VERSION
.
SDK_INT
>=
Build
.
VERSION_CODES
.
Q
)
{
MediaStore
.
Downloads
.
EXTERNAL_CONTENT_URI
}
else
{
MediaStore
.
Files
.
getContentUri
(
MediaStore
.
VOLUME_EXTERNAL
)
}
fun
relativePath
(
appName
:
String
):
String
{
var
path
=
Environment
.
DIRECTORY_DOWNLOADS
if
(
appName
.
isNotEmpty
())
{
path
+=
File
.
separator
+
appName
}
return
path
}
@Throws
(
IOException
::
class
)
private
fun
insertFile
(
displayName
:
String
):
MediaStoreFile
{
val
values
=
ContentValues
()
val
relativePath
=
relativePath
(
Config
.
downloadPath
)
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
)
// before Android 11, MediaStore can not rename new file when file exists,
// insert will return null. use newFile() instead.
val
fileUri
=
cr
.
insert
(
tableUri
,
values
)
?:
throw
IOException
(
"Can't insert $displayName."
)
val
projection
=
arrayOf
(
MediaStore
.
MediaColumns
.
_ID
,
MediaStore
.
MediaColumns
.
DATA
)
cr
.
query
(
fileUri
,
projection
,
null
,
null
,
null
)
?.
use
{
cursor
->
val
idIndex
=
cursor
.
getColumnIndexOrThrow
(
MediaStore
.
MediaColumns
.
_ID
)
val
dataColumn
=
cursor
.
getColumnIndexOrThrow
(
MediaStore
.
MediaColumns
.
DATA
)
if
(
cursor
.
moveToFirst
())
{
val
id
=
cursor
.
getLong
(
idIndex
)
val
data
=
cursor
.
getString
(
dataColumn
)
return
MediaStoreFile
(
id
,
data
)
}
}
throw
IOException
(
"Can't insert $displayName."
)
}
private
fun
queryFile
(
displayName
:
String
):
MediaStoreFile
?
{
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.
val
selection
=
"${MediaStore.MediaColumns.DISPLAY_NAME} == ?"
val
selectionArgs
=
arrayOf
(
displayName
)
val
sortOrder
=
"${MediaStore.MediaColumns.DATE_ADDED} DESC"
cr
.
query
(
tableUri
,
projection
,
selection
,
selectionArgs
,
sortOrder
)
?.
use
{
cursor
->
val
idColumn
=
cursor
.
getColumnIndexOrThrow
(
MediaStore
.
MediaColumns
.
_ID
)
val
dataColumn
=
cursor
.
getColumnIndexOrThrow
(
MediaStore
.
MediaColumns
.
DATA
)
while
(
cursor
.
moveToNext
())
{
val
id
=
cursor
.
getLong
(
idColumn
)
val
data
=
cursor
.
getString
(
dataColumn
)
val
relativePath
=
relativePath
(
Config
.
downloadPath
)
if
(
data
.
endsWith
(
relativePath
+
File
.
separator
+
displayName
))
{
return
MediaStoreFile
(
id
,
data
)
}
}
}
return
null
}
@Throws
(
IOException
::
class
)
fun
newFile
(
displayName
:
String
):
MediaStoreFile
{
return
if
(
Build
.
VERSION
.
SDK_INT
>=
Build
.
VERSION_CODES
.
R
)
{
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
.
outputStream
()
=
cr
.
openOutputStream
(
this
)
?:
throw
FileNotFoundException
()
fun
Uri
.
getDisplayName
():
String
{
require
(
scheme
==
"content"
)
{
"Uri lacks 'content' scheme: $this"
}
val
projection
=
arrayOf
(
OpenableColumns
.
DISPLAY_NAME
)
cr
.
query
(
this
,
projection
,
null
,
null
,
null
)
?.
use
{
cursor
->
val
displayNameColumn
=
cursor
.
getColumnIndexOrThrow
(
OpenableColumns
.
DISPLAY_NAME
)
if
(
cursor
.
moveToFirst
())
{
return
cursor
.
getString
(
displayNameColumn
)
}
}
return
this
.
toString
()
}
fun
Uri
.
checkSum
(
alg
:
String
,
reference
:
String
)
=
runCatching
{
this
.
inputStream
().
use
{
val
digest
=
MessageDigest
.
getInstance
(
alg
)
it
.
copyTo
(
object
:
OutputStream
()
{
override
fun
write
(
b
:
Int
)
{
digest
.
update
(
b
.
toByte
())
}
override
fun
write
(
b
:
ByteArray
,
off
:
Int
,
len
:
Int
)
{
digest
.
update
(
b
,
off
,
len
)
}
})
val
sb
=
StringBuilder
()
digest
.
digest
().
forEach
{
b
->
sb
.
append
(
"%02x"
.
format
(
b
and
0
xff
.
toByte
()))
}
sb
.
toString
()
==
reference
}
}.
getOrElse
{
false
}
data class
MediaStoreFile
(
val
id
:
Long
,
private
val
data
:
String
)
{
val
uri
:
Uri
=
ContentUris
.
withAppendedId
(
tableUri
,
id
)
override
fun
toString
()
=
data
fun
delete
():
Boolean
{
val
selection
=
"${MediaStore.MediaColumns._ID} == ?"
val
selectionArgs
=
arrayOf
(
id
.
toString
())
return
cr
.
delete
(
uri
,
selection
,
selectionArgs
)
==
1
}
}
}
app/src/main/java/com/topjohnwu/magisk/utils/Utils.kt
View file @
9e81db86
...
...
@@ -3,7 +3,6 @@ package com.topjohnwu.magisk.utils
import
android.content.Context
import
android.content.Intent
import
android.net.Uri
import
android.os.Environment
import
android.widget.Toast
import
com.topjohnwu.magisk.R
import
com.topjohnwu.magisk.core.Config
...
...
@@ -11,7 +10,6 @@ import com.topjohnwu.magisk.core.Const
import
com.topjohnwu.magisk.core.Info
import
com.topjohnwu.magisk.ktx.get
import
com.topjohnwu.superuser.internal.UiThreadHandler
import
java.io.File
object
Utils
{
...
...
@@ -40,10 +38,4 @@ object Utils {
)
}
}
fun
ensureDownloadPath
(
path
:
String
)
=
File
(
Environment
.
getExternalStorageDirectory
(),
path
).
run
{
if
((
exists
()
&&
isDirectory
)
||
mkdirs
())
this
else
null
}
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment