require external storage

This commit is contained in:
X1nto 2021-04-13 14:18:53 +04:00
parent 1c3ab835a6
commit e73b56ed4d
12 changed files with 188 additions and 89 deletions

View File

@ -8,12 +8,18 @@
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<!-- is required for some Android 5.x devices -->
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
tools:ignore="ScopedStorage" />
<uses-permission
android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
tools:ignore="ScopedStorage"/>
<queries>
<package android:name="com.vanced.android.youtube" />
<package android:name="com.google.android.youtube" />
@ -32,7 +38,9 @@
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
tools:ignore="UnusedAttribute">
tools:ignore="UnusedAttribute"
android:requestLegacyExternalStorage="true">
<!-- Only because MANAGE_EXTERNAL_STORAGE is not available in android Q -->
<activity
android:name=".ui.core.SplashScreenActivity"
@ -51,7 +59,7 @@
<activity
android:name=".ui.WelcomeActivity"
android:theme="@style/DarkTheme"/>
android:theme="@style/DarkTheme" />
<activity
android:name=".ui.MainActivity"
@ -62,12 +70,14 @@
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:scheme="https"
android:host="vancedapp.com"
android:pathPrefix="/downloads"/>
android:pathPrefix="/downloads"
android:scheme="https" />
</intent-filter>

View File

@ -24,6 +24,6 @@ object MicrogDownloader {
fun startMicrogInstall(context: Context) {
installing.postValue(true)
postReset()
install("${context.getExternalFilesDir(folderName)}/$fileName", context)
install("$folderName/$fileName".managerFilepath, context)
}
}

View File

@ -68,6 +68,6 @@ object MusicDownloader {
if (variant == "root")
installMusicRoot(context)
else
install("${context.getExternalFilesDir("music/nonroot")}/nonroot.apk", context)
install("music/nonroot/nonroot.apk".managerFilepath, context)
}
}

View File

@ -40,6 +40,10 @@ class MainActivity : AppCompatActivity() {
lateinit var binding: ActivityMainBinding
private val navHost by lazy { findNavController(R.id.nav_host) }
companion object {
const val REQUEST_CODE = 69
}
private val loadingObserver = object : LoadingStateListener {
val tag = "VMLocalisation"
override fun onDataChanged() {
@ -98,6 +102,9 @@ class MainActivity : AppCompatActivity() {
setFinalTheme()
super.onResume()
Crowdin.registerDataLoadingObserver(loadingObserver)
if (!canAccessStorage(this)) {
DialogContainer.storageDialog(this)
}
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {

View File

@ -1,13 +1,16 @@
package com.vanced.manager.ui.dialogs
import android.content.Context
import android.os.Build
import androidx.core.content.edit
import androidx.fragment.app.FragmentActivity
import androidx.preference.PreferenceManager
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.vanced.manager.R
import com.vanced.manager.utils.showWithAccent
import com.vanced.manager.utils.isMiuiOptimizationsEnabled
import com.vanced.manager.utils.openUrl
import com.vanced.manager.utils.requestStoragePerms
object DialogContainer {
@ -30,6 +33,23 @@ object DialogContainer {
prefs.edit { putBoolean("firstLaunch", false) }
}
fun storageDialog(activity: FragmentActivity) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M)
return
MaterialAlertDialogBuilder(activity).apply {
setTitle(R.string.storage_access_required)
setMessage(R.string.storage_access_required_summary)
setPositiveButton(R.string.auth_dialog_ok) { dialog, _ ->
dialog.cancel()
requestStoragePerms(activity)
}
setCancelable(false)
create()
showWithAccent()
}
}
fun miuiDialog(context: Context) {
MaterialAlertDialogBuilder(context).apply {
setTitle(context.getString(R.string.miui_one_title))

View File

@ -8,7 +8,10 @@ import android.widget.Toast
import com.vanced.manager.R
import com.vanced.manager.core.ui.base.BindingFragment
import com.vanced.manager.databinding.FragmentLogBinding
import com.vanced.manager.utils.AppUtils
import com.vanced.manager.utils.AppUtils.logs
import com.vanced.manager.utils.managerFilepath
import com.vanced.manager.utils.performStorageAction
import java.io.File
import java.io.FileWriter
import java.io.IOException
@ -30,23 +33,30 @@ class LogFragment : BindingFragment<FragmentLogBinding>() {
val logs = TextUtils.concat(*logs.toTypedArray())
logText.text = logs
logSave.setOnClickListener {
val calendar = Calendar.getInstance()
val year = calendar.get(Calendar.YEAR)
val month = calendar.get(Calendar.MONTH)
val day = calendar.get(Calendar.DAY_OF_MONTH)
val hour = calendar.get(Calendar.HOUR_OF_DAY)
val minute = calendar.get(Calendar.MINUTE)
val second = calendar.get(Calendar.SECOND)
try {
val calendar = Calendar.getInstance()
val year = calendar.get(Calendar.YEAR)
val month = calendar.get(Calendar.MONTH)
val day = calendar.get(Calendar.DAY_OF_MONTH)
val hour = calendar.get(Calendar.HOUR_OF_DAY)
val minute = calendar.get(Calendar.MINUTE)
val second = calendar.get(Calendar.SECOND)
val log = File(requireActivity().getExternalFilesDir("logs")?.path + "/$year$month${day}_$hour$minute$second.log")
FileWriter(log).apply {
append(logs)
flush()
close()
performStorageAction(requireActivity()) {
val logPath = File("logs".managerFilepath).apply {
if (!exists()) {
mkdirs()
}
}.path
FileWriter(File(logPath, "$year$month${day}_$hour$minute$second.log")).apply {
append(logs)
flush()
close()
}
}
Toast.makeText(requireActivity(), R.string.logs_saved, Toast.LENGTH_SHORT).show()
} catch (e: IOException) {
Toast.makeText(requireActivity(), R.string.logs_not_saved, Toast.LENGTH_SHORT).show()
AppUtils.log("VMIO", "Could not save logs: $e")
}
}
}

View File

@ -51,7 +51,7 @@ class HomeViewModel(private val activity: FragmentActivity): ViewModel() {
val microgModel = MutableLiveData<DataModel>()
val musicModel = MutableLiveData<DataModel>()
val musicRootModel = MutableLiveData<RootDataModel>()
val managerModel = MutableLiveData<DataModel>()
private val managerModel = MutableLiveData<DataModel>()
fun fetchData() {
viewModelScope.launch {
@ -110,7 +110,7 @@ class HomeViewModel(private val activity: FragmentActivity): ViewModel() {
activity.getString(R.string.vanced) -> {
when (variant) {
"nonroot" -> {
if (vancedInstallFilesExist(activity)) {
if (vancedInstallFilesExist()) {
InstallationFilesDetectedDialog.newInstance(app).show(activity)
} else {
VancedPreferencesDialog().show(activity)
@ -124,7 +124,7 @@ class HomeViewModel(private val activity: FragmentActivity): ViewModel() {
activity.getString(R.string.music) -> {
when (variant) {
"nonroot" -> {
if (musicApkExists(activity)) {
if (musicApkExists()) {
InstallationFilesDetectedDialog.newInstance(app).show(activity)
} else {
MusicPreferencesDialog().show(activity)
@ -136,7 +136,7 @@ class HomeViewModel(private val activity: FragmentActivity): ViewModel() {
}
}
activity.getString(R.string.microg) -> {
if (apkExist(activity, "microg.apk")) {
if (apkExist("microg.apk")) {
InstallationFilesDetectedDialog.newInstance(app).show(activity)
} else {
AppDownloadDialog.newInstance(app).show(activity)

View File

@ -49,13 +49,13 @@ object DownloadHelper : CoroutineScope by CoroutineScope(Dispatchers.IO) {
override fun onResponse(call: Call<ResponseBody>, response: Response<ResponseBody>) {
if (response.isSuccessful) {
launch {
if (response.body()?.let { writeFile(it, context.getExternalFilesDir(fileFolder)?.path + "/" + fileName) } == true) {
if (response.body()?.let { writeFile(it, fileFolder, fileName) } == true) {
onDownloadComplete()
} else {
onError("Could not save file")
downloadProgress.postValue(0)
onError(response.errorBody().toString())
log("VMDownloader", "Failed to save file: $url\n${response.errorBody()}")
}
downloadProgress.postValue(0)
}
} else {
val errorBody = response.errorBody().toString()
@ -68,76 +68,78 @@ object DownloadHelper : CoroutineScope by CoroutineScope(Dispatchers.IO) {
override fun onFailure(call: Call<ResponseBody>, t: Throwable) {
if (call.isCanceled) {
log("VMDownloader", "Download canceled")
downloadProgress.postValue(0)
} else {
onError(t.stackTraceToString())
downloadProgress.postValue(0)
log("VMDownloader", "Failed to download file: $url")
}
downloadProgress.postValue(0)
}
})
}
fun writeFile(body: ResponseBody, filePath: String): Boolean {
return try {
val file = File(filePath)
fun writeFile(body: ResponseBody, folderName: String, fileName: String): Boolean =
try {
val totalBytes = body.contentLength()
var inputStream: InputStream? = null
var outputStream: OutputStream? = null
try {
val fileReader = ByteArray(4096)
var downloadedBytes: Long = 0
inputStream = body.byteStream()
outputStream = FileOutputStream(file)
var read: Int
while (inputStream.read(fileReader).also { read = it } != -1) {
outputStream.write(fileReader, 0, read)
downloadedBytes += read.toLong()
downloadProgress.postValue((downloadedBytes * 100 / totalBytes).toInt())
val fileReader = ByteArray(8096)
var downloadedBytes: Long = 0
val dir = File(managerPath, folderName).apply {
if (!exists()) {
mkdirs()
}
outputStream.flush()
true
} catch (e: IOException) {
false
} finally {
inputStream?.close()
outputStream?.close()
}
val inputStream: InputStream = body.byteStream()
val outputStream: OutputStream = FileOutputStream("${dir.path}/$fileName")
var read: Int
while (inputStream.read(fileReader).also { read = it } != -1) {
outputStream.write(fileReader, 0, read)
downloadedBytes += read.toLong()
downloadProgress.postValue((downloadedBytes * 100 / totalBytes).toInt())
}
outputStream.flush()
inputStream.close()
outputStream.close()
true
} catch (e: IOException) {
log("VMIO", "Failed to save file: $e")
false
}
}
fun downloadManager(context: Context) {
val url = "https://github.com/YTVanced/VancedManager/releases/latest/download/manager.apk"
download(url,"https://github.com/YTVanced/VancedManager/", "manager", "manager.apk", context, onDownloadComplete = {
val apk = File("${context.getExternalFilesDir("manager")?.path}/manager.apk")
val uri =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
FileProvider.getUriForFile(context, "${context.packageName}.provider", apk)
else
Uri.fromFile(apk)
download(
url,
"https://github.com/YTVanced/VancedManager/",
"manager",
"manager.apk",
context,
onDownloadComplete = {
val apk = File("manager/manager.apk".managerFilepath)
val uri =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
FileProvider.getUriForFile(context, "${context.packageName}.provider", apk)
else
Uri.fromFile(apk)
val intent = Intent(Intent.ACTION_VIEW)
intent.setDataAndType(uri, "application/vnd.android.package-archive")
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
try {
context.startActivity(intent)
} catch (e: ActivityNotFoundException) {
log("VMDownloader", e.stackTraceToString())
} finally {
sendCloseDialog(context)
}
}, onError = {
val intent = Intent(Intent.ACTION_VIEW)
intent.setDataAndType(uri, "application/vnd.android.package-archive")
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
try {
context.startActivity(intent)
} catch (e: ActivityNotFoundException) {
log("VMDownloader", e.stackTraceToString())
} finally {
sendCloseDialog(context)
}
}) {
downloadingFile.postValue(
context.getString(
R.string.error_downloading,
"manager.apk"
)
)
})
}
}
}

View File

@ -19,6 +19,8 @@ import java.util.*
val RadioGroup.checkedButtonTag: String? get() = findViewById<MaterialRadioButton>(checkedRadioButtonId)?.tag?.toString()
val String.managerFilepath get() = "$managerPath/$this"
fun DialogFragment.show(activity: FragmentActivity) {
try {
show(activity.supportFragmentManager, "")

View File

@ -100,16 +100,16 @@ object PackageHelper {
}
}
fun apkExist(context: Context, apk: String): Boolean {
val apkPath = File(context.getExternalFilesDir(apk.substring(0, apk.length - 4))?.path, apk)
fun apkExist(apk: String): Boolean {
val apkPath = File("${apk.removeSuffix(".apk")}/$apk".managerFilepath)
if (apkPath.exists())
return true
return false
}
fun musicApkExists(context: Context): Boolean {
val apkPath = File(context.getExternalFilesDir("music/nonroot")?.path, "nonroot.apk")
fun musicApkExists(): Boolean {
val apkPath = File("music/nonroot/nonroot.apk".managerFilepath)
if (apkPath.exists()) {
return true
}
@ -117,8 +117,8 @@ object PackageHelper {
return false
}
fun vancedInstallFilesExist(context: Context): Boolean {
val apksPath = File(context.getExternalFilesDir("vanced/nonroot")?.path.toString())
fun vancedInstallFilesExist(): Boolean {
val apksPath = File("vanced/nonroot".managerFilepath)
val splitFiles = mutableListOf<String>()
if (apksPath.exists()) {
val files = apksPath.listFiles()
@ -212,8 +212,8 @@ object PackageHelper {
private fun installRootApp(context: Context, app: String, appVerCode: Int?, pkg: String, modApkBool: (fileName: String) -> Boolean) = CoroutineScope(Dispatchers.IO).launch {
Shell.getShell {
val apkFilesPath = context.getExternalFilesDir("$app/root")?.path
val files = File(apkFilesPath.toString()).listFiles()?.toList()
val apkFilesPath = "$app/root".managerFilepath
val files = File(apkFilesPath).listFiles()?.toList()
if (files != null) {
val modApk: File? = files.lastOrNull { modApkBool(it.name) }
if (modApk != null) {
@ -268,7 +268,7 @@ object PackageHelper {
appName: String
) {
val packageInstaller = context.packageManager.packageInstaller
val folder = File(context.getExternalFilesDir("$appName/nonroot")?.path.toString())
val folder = File("$appName/nonroot".managerFilepath)
var session: PackageInstaller.Session? = null
val sessionId: Int
val sessionParams = PackageInstaller.SessionParams(PackageInstaller.SessionParams.MODE_FULL_INSTALL)
@ -299,17 +299,10 @@ object PackageHelper {
}
}
private fun installSplitApkFilesRoot(apkFiles: List<File>?, context: Context): Boolean {
private fun installSplitApkFilesRoot(apkFiles: List<File>?, context: Context) : Boolean {
val filenames = arrayOf("black.apk", "dark.apk", "blue.apk", "pink.apk", "hash.json")
log(INSTALLER_TAG, "installing split apk files: ${apkFiles?.map { it.name }}")
val sessionId = Shell.su("pm install-create -r").exec().out.joinToString(" ").filter { it.isDigit() }.toIntOrNull()
if (sessionId == null) {
sendFailure("Session ID is null", context)
sendCloseDialog(context)
return false
}
val sessionId = Shell.su("pm install-create").exec().out.joinToString(" ").filter { it.isDigit() }.toInt()
apkFiles?.filter { !filenames.contains(it.name) }?.forEach { apkFile ->
val apkName = apkFile.name
log(INSTALLER_TAG, "installing APK: $apkName")

View File

@ -0,0 +1,53 @@
package com.vanced.manager.utils
import android.Manifest
import android.content.Intent
import android.content.pm.PackageManager
import android.os.Build
import android.os.Environment
import android.provider.Settings
import androidx.fragment.app.FragmentActivity
import com.vanced.manager.ui.MainActivity
import com.vanced.manager.ui.dialogs.DialogContainer.storageDialog
@Suppress("DEPRECATION")
val managerPath get() = "${Environment.getExternalStorageDirectory().path}/Vanced Manager"
inline fun performStorageAction(activity: FragmentActivity, action: () -> Unit) {
if (canAccessStorage(activity)) {
action()
} else {
storageDialog(activity)
}
}
fun canAccessStorage(activity: FragmentActivity): Boolean = when {
Build.VERSION.SDK_INT >= Build.VERSION_CODES.R -> Environment.isExternalStorageManager()
Build.VERSION.SDK_INT >= Build.VERSION_CODES.M -> {
listOf(
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE
).all {
activity.checkSelfPermission(it) == PackageManager.PERMISSION_GRANTED
}
}
else -> true
}
fun requestStoragePerms(activity: FragmentActivity) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
activity.startActivity(
Intent(
Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION
)
)
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
activity.requestPermissions(
arrayOf(
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE
),
MainActivity.REQUEST_CODE
)
}
}

View File

@ -94,6 +94,8 @@
<string name="microg_bug_summary_music">Due to a bug in the original microG, installing Music v4.11+ first requires you to install v4.07.51, open it, then login and only then can you install v4.11 and higher. Do you want to proceed with the installation of v4.07.51?</string>
<string name="please_be_patient">Please do NOT exit the app during this process!</string>
<string name="welcome">Welcome</string>
<string name="storage_access_required">Storage access required</string>
<string name="storage_access_required_summary">In order for Vanced Manager to work, you must grant the storage permission</string>
<!-- Install Page -->
<string name="choose_preferred_language">Choose your preferred language(s) for Vanced</string>