Compare commits

..

18 Commits

Author SHA1 Message Date
topjohnwu
120bd6cd68 v7.3.2 Changelog 2019-06-21 01:12:50 -07:00
JoanVC100
9aef06d1b8 Fix traductors 2019-06-21 04:00:54 -04:00
topjohnwu
e6e9dd751c Upgrade Kotlin 2019-06-21 00:53:40 -07:00
Viktor De Pasquale
5dd677756f Fixed multiple fetch tasks running at once
Disposing wouldn't help since the shell doesn't appear to handle concurrency well
2019-06-21 00:36:37 -07:00
Viktor De Pasquale
b77c590910 Fixed the searchView being collapsed after searching through it
Now they have their state synced with viewModel to allow continuity
2019-06-21 00:36:37 -07:00
Viktor De Pasquale
7e5f2822ae Fix superuser fragment crashes
Fix superuser screen encountering inconsistencies when refreshing the data rapidly
2019-06-21 00:36:01 -07:00
topjohnwu
12bbc7fd6b Update v7.3.1 changelog 2019-06-17 22:15:38 -07:00
topjohnwu
bf9ac8252b Cleanup UpdateInfo 2019-06-16 16:47:30 -07:00
topjohnwu
4a3f5dc619 Cleanups 2019-06-16 14:35:51 -07:00
Viktor De Pasquale
ca156befbd Fixed mapping generic pairs to policy crashing when no policy is found
The policy (app) is now deleted when found invalid (uninstalled)
2019-06-16 16:50:08 -04:00
Viktor De Pasquale
4db41e2ac4 Added attempted fix for parsing data off default thread 2019-06-16 16:50:08 -04:00
Viktor De Pasquale
982a43fce1 Moved diff computation of policy list to the background thread 2019-06-16 16:50:08 -04:00
Viktor De Pasquale
dd76a74e1c Fixed fast scroll button crashing while scrolling to undefined position 2019-06-16 16:50:08 -04:00
Viktor De Pasquale
70cb52b2c7 Fixed fast scroll button being visible when log is empty 2019-06-16 16:50:08 -04:00
topjohnwu
5c7f69acaa Separate SAR and legacy implementation 2019-06-16 12:45:32 -07:00
topjohnwu
f1d9015e5f Move load kernel info out of class 2019-06-15 22:25:09 -07:00
topjohnwu
e8d900c58e Fix typo 2019-06-15 18:12:12 -07:00
topjohnwu
a6241ae912 Fix magiskboot unpack option parsing 2019-06-15 16:15:12 -07:00
37 changed files with 357 additions and 306 deletions

View File

@@ -1,26 +1,20 @@
package com.topjohnwu.magisk;
import androidx.annotation.NonNull;
import com.topjohnwu.magisk.model.entity.UpdateInfo;
import com.topjohnwu.superuser.Shell;
import com.topjohnwu.superuser.ShellUtils;
public final class Info {
public static int magiskVersionCode = -1;
// Current status
public static String magiskVersionString = "";
// Update Info
public static String remoteMagiskVersionString = "";
public static int remoteMagiskVersionCode = -1;
public static String magiskLink = "";
public static String magiskNoteLink = "";
public static String magiskMD5 = "";
public static String remoteManagerVersionString = "";
public static int remoteManagerVersionCode = -1;
public static String managerLink = "";
public static String managerNoteLink = "";
public static String uninstallerLink = "";
// Install flags
@NonNull
public static String magiskVersionString = "";
public static UpdateInfo remote = new UpdateInfo();
public static boolean keepVerity = false;
public static boolean keepEnc = false;
public static boolean recovery = false;

View File

@@ -8,6 +8,7 @@ import com.topjohnwu.magisk.model.entity.MagiskPolicy
import com.topjohnwu.magisk.model.entity.toMap
import com.topjohnwu.magisk.model.entity.toPolicy
import com.topjohnwu.magisk.utils.now
import timber.log.Timber
import java.util.concurrent.TimeUnit
@@ -47,12 +48,7 @@ class PolicyDao(
condition {
equals("uid", uid)
}
}.map { it.firstOrNull()?.toPolicy(context.packageManager) }
.doOnError {
if (it is PackageManager.NameNotFoundException) {
delete(uid).subscribe()
}
}
}.map { it.first().toPolicySafe() }
fun update(policy: MagiskPolicy) = query<Replace> {
values(policy.toMap())
@@ -62,8 +58,24 @@ class PolicyDao(
condition {
equals("uid/100000", Const.USER_ID)
}
}.flattenAsFlowable { it }
.map { it.toPolicy(context.packageManager) }
.toList()
}.map { it.mapNotNull { it.toPolicySafe() } }
private fun Map<String, String>.toPolicySafe(): MagiskPolicy? {
val taskResult = runCatching { toPolicy(context.packageManager) }
val result = taskResult.getOrNull()
val exception = taskResult.exceptionOrNull()
Timber.e(exception)
when (exception) {
is PackageManager.NameNotFoundException -> {
val uid = getOrElse("uid") { null } ?: return null
delete(uid).subscribe()
}
}
return result
}
}

View File

@@ -1,7 +1,7 @@
package com.topjohnwu.magisk.data.network
import com.topjohnwu.magisk.Const
import com.topjohnwu.magisk.model.entity.MagiskConfig
import com.topjohnwu.magisk.model.entity.UpdateInfo
import io.reactivex.Single
import okhttp3.ResponseBody
import retrofit2.http.GET
@@ -15,19 +15,19 @@ interface GithubRawApiServices {
//region topjohnwu/magisk_files
@GET("$MAGISK_FILES/master/stable.json")
fun fetchStableUpdate(): Single<MagiskConfig>
fun fetchStableUpdate(): Single<UpdateInfo>
@GET("$MAGISK_FILES/master/beta.json")
fun fetchBetaUpdate(): Single<MagiskConfig>
fun fetchBetaUpdate(): Single<UpdateInfo>
@GET("$MAGISK_FILES/master/canary_builds/release.json")
fun fetchCanaryUpdate(): Single<MagiskConfig>
fun fetchCanaryUpdate(): Single<UpdateInfo>
@GET("$MAGISK_FILES/master/canary_builds/canary.json")
fun fetchCanaryDebugUpdate(): Single<MagiskConfig>
fun fetchCanaryDebugUpdate(): Single<UpdateInfo>
@GET
fun fetchCustomUpdate(@Url url: String): Single<MagiskConfig>
fun fetchCustomUpdate(@Url url: String): Single<UpdateInfo>
@GET("$MAGISK_FILES/{$REVISION}/snet.apk")
@Streaming

View File

@@ -7,7 +7,6 @@ import com.topjohnwu.magisk.model.entity.MagiskLog
import com.topjohnwu.magisk.model.entity.WrappedMagiskLog
import com.topjohnwu.magisk.utils.toSingle
import com.topjohnwu.superuser.Shell
import timber.log.Timber
import java.util.concurrent.TimeUnit
@@ -21,7 +20,6 @@ class LogRepository(
fun fetchMagiskLogs() = "tail -n 5000 ${Const.MAGISK_LOG}".suRaw()
.filter { it.isNotEmpty() }
.map { Timber.i(it.toString()); it }
fun clearLogs() = logDao.deleteAll()
fun clearOutdated() = logDao.deleteOutdated()

View File

@@ -50,27 +50,14 @@ class MagiskRepository(
else -> throw IllegalArgumentException()
}.flatMap {
// If remote version is lower than current installed, try switching to beta
if (it.magisk.versionCode.toIntOrNull() ?: -1 < Info.magiskVersionCode
if (it.magisk.versionCode < Info.magiskVersionCode
&& Config.updateChannel == Config.Value.DEFAULT_CHANNEL) {
Config.updateChannel = Config.Value.BETA_CHANNEL
apiRaw.fetchBetaUpdate()
} else {
Single.just(it)
}
}.doOnSuccess {
Info.remoteMagiskVersionString = it.magisk.version
Info.remoteMagiskVersionCode = it.magisk.versionCode.toIntOrNull() ?: -1
Info.magiskLink = it.magisk.link
Info.magiskNoteLink = it.magisk.note
Info.magiskMD5 = it.magisk.hash
Info.remoteManagerVersionString = it.app.version
Info.remoteManagerVersionCode = it.app.versionCode.toIntOrNull() ?: -1
Info.managerLink = it.app.link
Info.managerNoteLink = it.app.note
Info.uninstallerLink = it.uninstaller.link
}
}.map { Info.remote = it; it }
fun fetchApps() =
Single.fromCallable { packageManager.getInstalledApplications(0) }

View File

@@ -1,11 +0,0 @@
package com.topjohnwu.magisk.model.entity
import se.ansman.kotshi.JsonSerializable
@JsonSerializable
data class MagiskApp(
val version: String,
val versionCode: String,
val link: String,
val note: String
)

View File

@@ -1,10 +0,0 @@
package com.topjohnwu.magisk.model.entity
import se.ansman.kotshi.JsonSerializable
@JsonSerializable
data class MagiskConfig(
val app: MagiskApp,
val uninstaller: MagiskLink,
val magisk: MagiskFlashable
)

View File

@@ -1,13 +0,0 @@
package com.topjohnwu.magisk.model.entity
import com.squareup.moshi.Json
import se.ansman.kotshi.JsonSerializable
@JsonSerializable
data class MagiskFlashable(
val version: String,
val versionCode: String,
val link: String,
val note: String,
@Json(name = "md5") val hash: String
)

View File

@@ -1,8 +0,0 @@
package com.topjohnwu.magisk.model.entity
import se.ansman.kotshi.JsonSerializable
@JsonSerializable
data class MagiskLink(
val link: String
)

View File

@@ -0,0 +1,33 @@
package com.topjohnwu.magisk.model.entity
import com.squareup.moshi.Json
import se.ansman.kotshi.JsonSerializable
@JsonSerializable
data class UpdateInfo(
val app: ManagerJson = ManagerJson(),
val uninstaller: UninstallerJson = UninstallerJson(),
val magisk: MagiskJson = MagiskJson()
)
@JsonSerializable
data class UninstallerJson(
val link: String = ""
)
@JsonSerializable
data class MagiskJson(
val version: String = "",
val versionCode: Int = -1,
val link: String = "",
val note: String = "",
@Json(name = "md5") val hash: String = ""
)
@JsonSerializable
data class ManagerJson(
val version: String = "",
val versionCode: Int = -1,
val link: String = "",
val note: String = ""
)

View File

@@ -73,7 +73,8 @@ open class GeneralReceiver : BroadcastReceiver() {
}
Intent.ACTION_LOCALE_CHANGED -> Shortcuts.setup(context)
Const.Key.BROADCAST_MANAGER_UPDATE -> {
Info.managerLink = intent.getStringExtra(Const.Key.INTENT_SET_LINK)
Info.remote = Info.remote.copy(app = Info.remote.app.copy(
link = intent.getStringExtra(Const.Key.INTENT_SET_LINK) ?: ""))
DownloadApp.upgrade(intent.getStringExtra(Const.Key.INTENT_SET_NAME))
}
Const.Key.BROADCAST_REBOOT -> reboot()

View File

@@ -18,9 +18,9 @@ class UpdateCheckService : DelegateWorker() {
Shell.getShell()
return runCatching {
magiskRepo.fetchUpdate().blockingGet()
if (BuildConfig.VERSION_CODE < Info.remoteManagerVersionCode)
if (BuildConfig.VERSION_CODE < Info.remote.app.versionCode)
Notifications.managerUpdate()
else if (Info.magiskVersionCode < Info.remoteMagiskVersionCode)
else if (Info.magiskVersionCode < Info.remote.magisk.versionCode)
Notifications.magiskUpdate()
ListenableWorker.Result.success()
}.getOrElse {

View File

@@ -123,9 +123,9 @@ public abstract class MagiskInstaller {
File zip = new File(App.self.getCacheDir(), "magisk.zip");
if (!ShellUtils.checkSum("MD5", zip, Info.magiskMD5)) {
if (!ShellUtils.checkSum("MD5", zip, Info.remote.getMagisk().getHash())) {
console.add("- Downloading zip");
Networking.get(Info.magiskLink)
Networking.get(Info.remote.getMagisk().getLink())
.setDownloadProgressListener(new ProgressLog())
.execForFile(zip);
} else {

View File

@@ -3,8 +3,6 @@ package com.topjohnwu.magisk.tasks;
import android.database.Cursor;
import android.util.Pair;
import androidx.annotation.NonNull;
import com.topjohnwu.magisk.App;
import com.topjohnwu.magisk.Config;
import com.topjohnwu.magisk.Const;
@@ -20,7 +18,6 @@ import org.json.JSONException;
import org.json.JSONObject;
import java.net.HttpURLConnection;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Collections;
@@ -33,16 +30,11 @@ import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import androidx.annotation.NonNull;
import io.reactivex.Single;
@Deprecated
public class UpdateRepos {
private static final DateFormat DATE_FORMAT;
static {
DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US);
DATE_FORMAT.setTimeZone(TimeZone.getTimeZone("UTC"));
}
@NonNull
private final RepoDatabaseHelper repoDB;
@@ -71,6 +63,17 @@ public class UpdateRepos {
}
}
/**
* Static instance of (Simple)DateFormat is not threadsafe so in order to make it safe it needs
* to be created beforehand on the same thread where it'll be used.
* See https://stackoverflow.com/a/18383395
*/
private static SimpleDateFormat getDateFormat() {
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US);
format.setTimeZone(TimeZone.getTimeZone("UTC"));
return format;
}
/* We sort repos by last push, which means that we only need to check whether the
* first page is updated to determine whether the online repo database is changed
*/
@@ -95,10 +98,12 @@ public class UpdateRepos {
return true;
try {
SimpleDateFormat dateFormat = getDateFormat();
for (int i = 0; i < res.getResult().length(); i++) {
JSONObject rawRepo = res.getResult().getJSONObject(i);
String id = rawRepo.getString("name");
Date date = DATE_FORMAT.parse(rawRepo.getString("pushed_at"));
Date date = dateFormat.parse(rawRepo.getString("pushed_at"));
moduleQueue.offer(new Pair<>(id, date));
}
} catch (JSONException | ParseException e) {

View File

@@ -5,14 +5,11 @@ import android.os.Bundle
import android.text.TextUtils
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import com.skoumal.teanity.extensions.subscribeK
import com.topjohnwu.magisk.*
import com.topjohnwu.magisk.data.database.SettingsDao
import com.topjohnwu.magisk.tasks.UpdateRepos
import com.topjohnwu.magisk.utils.Utils
import com.topjohnwu.magisk.view.Notifications
import com.topjohnwu.magisk.view.Shortcuts
import com.topjohnwu.net.Networking
import com.topjohnwu.superuser.Shell
import org.koin.android.ext.android.get
@@ -61,14 +58,6 @@ open class SplashActivity : AppCompatActivity() {
// Setup shortcuts
Shortcuts.setup(this)
// Magisk working as expected
if (Shell.rootAccess() && Info.magiskVersionCode > 0) {
// Load repos
if (Networking.checkNetworkStatus(this)) {
get<UpdateRepos>().exec().subscribeK()
}
}
val intent = Intent(this, ClassMap[MainActivity::class.java])
intent.putExtra(Const.Key.OPEN_SECTION, getIntent().getStringExtra(Const.Key.OPEN_SECTION))
DONE = true

View File

@@ -25,8 +25,22 @@ class MagiskHideFragment : MagiskFragment<HideViewModel, FragmentMagiskHideBindi
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.menu_magiskhide, menu)
menu.apply {
(findItem(R.id.app_search).actionView as? SearchView)
?.setOnQueryTextListener(this@MagiskHideFragment)
val query = viewModel.query.value
val searchItem = menu.findItem(R.id.app_search)
val searchView = searchItem.actionView as? SearchView
searchView?.run {
setOnQueryTextListener(this@MagiskHideFragment)
setQuery(query, false)
}
if (query.isNotBlank()) {
searchItem.expandActionView()
searchView?.isIconified = false
} else {
searchItem.collapseActionView()
searchView?.isIconified = true
}
val showSystem = Config.showSystemApp

View File

@@ -56,7 +56,7 @@ class HomeFragment : MagiskFragment<HomeViewModel, FragmentMagiskBinding>(),
private fun installMagisk() {
// Show Manager update first
if (Info.remoteManagerVersionCode > BuildConfig.VERSION_CODE) {
if (Info.remote.app.versionCode > BuildConfig.VERSION_CODE) {
installManager()
return
}

View File

@@ -172,7 +172,7 @@ class HomeViewModel(
state = State.LOADED
magiskState.value = when (Info.magiskVersionCode) {
in Int.MIN_VALUE until 0 -> MagiskState.NOT_INSTALLED
!in Info.remoteMagiskVersionCode..Int.MAX_VALUE -> MagiskState.OBSOLETE
!in Info.remote.magisk.versionCode..Int.MAX_VALUE -> MagiskState.OBSOLETE
else -> MagiskState.UP_TO_DATE
}
@@ -183,9 +183,9 @@ class HomeViewModel(
}
magiskLatestVersion.value = version
.format(Info.remoteMagiskVersionString, Info.remoteMagiskVersionCode)
.format(Info.remote.magisk.version, Info.remote.magisk.versionCode)
managerState.value = when (Info.remoteManagerVersionCode) {
managerState.value = when (Info.remote.app.versionCode) {
in Int.MIN_VALUE until 0 -> MagiskState.NOT_INSTALLED //wrong update channel
in (BuildConfig.VERSION_CODE + 1)..Int.MAX_VALUE -> MagiskState.OBSOLETE
else -> MagiskState.UP_TO_DATE
@@ -195,7 +195,7 @@ class HomeViewModel(
.format(BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE)
managerLatestVersion.value = version
.format(Info.remoteManagerVersionString, Info.remoteManagerVersionCode)
.format(Info.remote.app.version, Info.remote.app.versionCode)
}
private fun ensureEnv() {

View File

@@ -43,8 +43,23 @@ class ReposFragment : MagiskFragment<ModuleViewModel, FragmentReposBinding>(),
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.menu_repo, menu)
(menu.findItem(R.id.repo_search).actionView as? SearchView)
?.setOnQueryTextListener(this)
val query = viewModel.query.value
val searchItem = menu.findItem(R.id.repo_search)
val searchView = searchItem.actionView as? SearchView
searchView?.run {
setOnQueryTextListener(this@ReposFragment)
setQuery(query, false)
}
if (query.isNotBlank()) {
searchItem.expandActionView()
searchView?.isIconified = false
} else {
searchItem.collapseActionView()
searchView?.isIconified = true
}
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
@@ -84,7 +99,7 @@ class ReposFragment : MagiskFragment<ModuleViewModel, FragmentReposBinding>(),
context.withExternalRW {
onSuccess {
val intent = Intent(activity, ClassMap[DownloadModuleService::class.java])
.putExtra("repo", item).putExtra("install", install)
.putExtra("repo", item).putExtra("install", install)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
context.startForegroundService(intent)
} else {

View File

@@ -22,6 +22,7 @@ import com.topjohnwu.magisk.utils.toggle
import com.topjohnwu.magisk.view.dialogs.CustomAlertDialog
import com.topjohnwu.magisk.view.dialogs.FingerprintAuthDialog
import io.reactivex.Single
import io.reactivex.disposables.Disposable
import me.tatarka.bindingcollectionadapter2.ItemBinding
class SuperuserViewModel(
@@ -38,6 +39,7 @@ class SuperuserViewModel(
}
private var ignoreNext: PolicyRvItem? = null
private var fetchTask: Disposable? = null
init {
rxBus.register<PolicyEnableEvent>()
@@ -51,7 +53,8 @@ class SuperuserViewModel(
}
fun updatePolicies() {
appRepo.fetchAll()
if (fetchTask?.isDisposed?.not() == true) return
fetchTask = appRepo.fetchAll()
.flattenAsFlowable { it }
.map { PolicyRvItem(it, it.applicationInfo.loadIcon(packageManager)) }
.toList()
@@ -61,10 +64,10 @@ class SuperuserViewModel(
{ it.item.packageName }
))
}
.map { it to items.calculateDiff(it) }
.applySchedulers()
.applyViewModel(this)
.subscribeK { items.update(it) }
.add()
.subscribeK { items.update(it.first, it.second) }
}
fun deletePressed(item: PolicyRvItem) {

View File

@@ -186,9 +186,17 @@ fun setHidden(view: FloatingActionButton, hide: Boolean) {
}
@BindingAdapter("scrollPosition", "scrollPositionSmooth", requireAll = false)
fun setScrollPosition(view: RecyclerView, position: Int, smoothScroll: Boolean) = when {
smoothScroll -> view.smoothScrollToPosition(position)
else -> view.scrollToPosition(position)
fun setScrollPosition(view: RecyclerView, position: Int, smoothScroll: Boolean) {
val adapterItemCount = view.adapter?.itemCount ?: -1
if (position !in 0 until adapterItemCount) {
// the position is not in adapter bounds, adapter will throw exception for invalid positions
return
}
when {
smoothScroll -> view.smoothScrollToPosition(position)
else -> view.scrollToPosition(position)
}
}
@BindingAdapter("recyclerScrollEvent")

View File

@@ -26,7 +26,7 @@ public class DownloadApp {
public static void restore() {
String name = Utils.INSTANCE.fmt("MagiskManager v%s(%d)",
Info.remoteManagerVersionString, Info.remoteManagerVersionCode);
Info.remote.getApp().getVersion(), Info.remote.getApp().getVersionCode());
dlInstall(name, new RestoreManager());
}
@@ -34,7 +34,7 @@ public class DownloadApp {
File apk = new File(App.self.getCacheDir(), "manager.apk");
ProgressNotification progress = new ProgressNotification(name);
listener.progress = progress;
Networking.get(Info.managerLink)
Networking.get(Info.remote.getApp().getLink())
.setExecutor(App.THREAD_POOL)
.setDownloadProgressListener(progress)
.setErrorHandler((conn, e) -> progress.dlFail())

View File

@@ -63,11 +63,11 @@ public class Notifications {
public static void managerUpdate() {
App app = App.self;
String name = Utils.INSTANCE.fmt("MagiskManager v%s(%d)",
Info.remoteManagerVersionString, Info.remoteManagerVersionCode);
Info.remote.getApp().getVersion(), Info.remote.getApp().getVersionCode());
Intent intent = new Intent(app, ClassMap.get(GeneralReceiver.class));
intent.setAction(Const.Key.BROADCAST_MANAGER_UPDATE);
intent.putExtra(Const.Key.INTENT_SET_LINK, Info.managerLink);
intent.putExtra(Const.Key.INTENT_SET_LINK, Info.remote.getApp().getLink());
intent.putExtra(Const.Key.INTENT_SET_NAME, name);
PendingIntent pendingIntent = PendingIntent.getBroadcast(app,
Const.ID.APK_UPDATE_NOTIFICATION_ID, intent, PendingIntent.FLAG_UPDATE_CURRENT);

View File

@@ -57,11 +57,11 @@ internal class InstallMethodDialog(activity: MagiskActivity<*, *>, options: List
private fun downloadOnly(activity: MagiskActivity<*, *>) {
activity.withExternalRW {
onSuccess {
val filename = "Magisk-v${Info.remoteMagiskVersionString}" +
"(${Info.remoteMagiskVersionCode}).zip"
val filename = "Magisk-v${Info.remote.magisk.version}" +
"(${Info.remote.magisk.versionCode}).zip"
val zip = File(Const.EXTERNAL_PATH, filename)
val progress = ProgressNotification(filename)
Networking.get(Info.magiskLink)
Networking.get(Info.remote.magisk.link)
.setDownloadProgressListener(progress)
.setErrorHandler { _, _ -> progress.dlFail() }
.getAsFile(zip) {

View File

@@ -1,7 +1,6 @@
package com.topjohnwu.magisk.view.dialogs
import android.net.Uri
import android.text.TextUtils
import com.topjohnwu.magisk.Info
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.ui.base.MagiskActivity
@@ -13,8 +12,8 @@ import java.util.*
class MagiskInstallDialog(a: MagiskActivity<*, *>) : CustomAlertDialog(a) {
init {
val filename = "Magisk v${Info.remoteMagiskVersionString}" +
"(${Info.remoteMagiskVersionCode})"
val filename = "Magisk v${Info.remote.magisk.version}" +
"(${Info.remote.magisk.versionCode})"
setTitle(a.getString(R.string.repo_install_title, a.getString(R.string.magisk)))
setMessage(a.getString(R.string.repo_install_msg, filename))
setCancelable(true)
@@ -31,13 +30,13 @@ class MagiskInstallDialog(a: MagiskActivity<*, *>) : CustomAlertDialog(a) {
}
InstallMethodDialog(a, options).show()
}
if (!TextUtils.isEmpty(Info.magiskNoteLink)) {
if (Info.remote.magisk.note.isNotEmpty()) {
setNeutralButton(R.string.release_notes) { _, _ ->
if (Info.magiskNoteLink.contains("forum.xda-developers")) {
if (Info.remote.magisk.note.contains("forum.xda-developers")) {
// Open forum links in browser
Utils.openLink(a, Uri.parse(Info.magiskNoteLink))
Utils.openLink(a, Uri.parse(Info.remote.magisk.note))
} else {
MarkDownWindow.show(a, null, Info.magiskNoteLink)
MarkDownWindow.show(a, null, Info.remote.magisk.note)
}
}
}

View File

@@ -9,15 +9,15 @@ import com.topjohnwu.magisk.view.MarkDownWindow
class ManagerInstallDialog(a: Activity) : CustomAlertDialog(a) {
init {
val name = "MagiskManager v${Info.remoteManagerVersionString}" +
"(${Info.remoteManagerVersionCode})"
val name = "MagiskManager v${Info.remote.app.version}" +
"(${Info.remote.app.versionCode})"
setTitle(a.getString(R.string.repo_install_title, a.getString(R.string.app_name)))
setMessage(a.getString(R.string.repo_install_msg, name))
setCancelable(true)
setPositiveButton(R.string.install) { _, _ -> DownloadApp.upgrade(name) }
if (Info.managerNoteLink.isNotEmpty()) {
if (Info.remote.app.note.isNotEmpty()) {
setNeutralButton(R.string.app_changelog) { _, _ ->
MarkDownWindow.show(a, null, Info.managerNoteLink) }
MarkDownWindow.show(a, null, Info.remote.app.note) }
}
}
}

View File

@@ -4,9 +4,7 @@ import android.app.Activity
import android.app.ProgressDialog
import android.content.Intent
import android.net.Uri
import android.text.TextUtils
import android.widget.Toast
import com.topjohnwu.magisk.ClassMap
import com.topjohnwu.magisk.Const
import com.topjohnwu.magisk.Info
@@ -16,7 +14,6 @@ import com.topjohnwu.magisk.utils.Utils
import com.topjohnwu.magisk.view.ProgressNotification
import com.topjohnwu.net.Networking
import com.topjohnwu.superuser.Shell
import java.io.File
class UninstallDialog(activity: Activity) : CustomAlertDialog(activity) {
@@ -37,11 +34,11 @@ class UninstallDialog(activity: Activity) : CustomAlertDialog(activity) {
}
}
}
if (!TextUtils.isEmpty(Info.uninstallerLink)) {
if (Info.remote.uninstaller.link.isNotEmpty()) {
setPositiveButton(R.string.complete_uninstall) { d, i ->
val zip = File(activity.filesDir, "uninstaller.zip")
val progress = ProgressNotification(zip.name)
Networking.get(Info.uninstallerLink)
Networking.get(Info.remote.uninstaller.link)
.setDownloadProgressListener(progress)
.setErrorHandler { _, _ -> progress.dlFail() }
.getAsFile(zip) { f ->

View File

@@ -38,7 +38,7 @@
</HorizontalScrollView>
<com.google.android.material.floatingactionbutton.FloatingActionButton
hide="@{viewModel.scrollPosition == item.items.size - 1}"
hide="@{viewModel.scrollPosition == item.items.size - 1 || item.items.size == 0}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"

View File

@@ -1,4 +1,3 @@
# v7.3.0
- **HUGE** code base modernization, thanks @diareuse!
- Reboot device using proper API (no more abrupt reboot)
- New floating button in Magisk logs to go to bottom
# v7.3.2
- Fix potential crash in superuser fragment
- Preserve searched state in repo fragment

View File

@@ -63,8 +63,8 @@
<!--About Activity-->
<string name="app_changelog">Registre de canvis</string>
<string name="translators" />
<string name="app_translators">JoanVC100, QuitusAnbu27</string>
<string name="translators">JoanVC100, QuitusAnbu27</string>
<string name="app_translators">Traductors</string>
<!-- System Components, Notifications -->
<string name="update_channel">Actualització de Magisk</string>

View File

@@ -16,7 +16,7 @@ buildscript {
}
dependencies {
classpath 'com.android.tools.build:gradle:3.5.0-beta04'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.31"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.40"
// NOTE: Do not place your application dependencies here; they belong

View File

@@ -5,6 +5,7 @@
#include <utils.h>
#include <logging.h>
#include <selinux.h>
#include "init.h"
@@ -73,17 +74,17 @@ static void setup_block(const char *partname, char *block_dev) {
}
}
bool MagiskInit::read_dt_fstab(const char *name, char *partname, char *fstype) {
bool BaseInit::read_dt_fstab(const char *name, char *partname, char *fstype) {
char path[128];
int fd;
sprintf(path, "%s/fstab/%s/dev", cmd.dt_dir, name);
sprintf(path, "%s/fstab/%s/dev", cmd->dt_dir, name);
if ((fd = xopen(path, O_RDONLY | O_CLOEXEC)) >= 0) {
read(fd, path, sizeof(path));
close(fd);
// Some custom treble use different names, so use what we read
char *part = rtrim(strrchr(path, '/') + 1);
sprintf(partname, "%s%s", part, strend(part, cmd.slot) ? cmd.slot : "");
sprintf(path, "%s/fstab/%s/type", cmd.dt_dir, name);
sprintf(partname, "%s%s", part, strend(part, cmd->slot) ? cmd->slot : "");
sprintf(path, "%s/fstab/%s/type", cmd->dt_dir, name);
if ((fd = xopen(path, O_RDONLY | O_CLOEXEC)) >= 0) {
read(fd, fstype, 32);
close(fd);
@@ -106,38 +107,57 @@ if (!is_lnk("/" #name) && read_dt_fstab(#name, partname, fstype)) { \
mnt_##name = true; \
}
void MagiskInit::early_mount() {
void LegacyInit::early_mount() {
char partname[32];
char fstype[32];
char block_dev[64];
if (cmd.system_as_root) {
LOGD("Early mount system_root\n");
sprintf(partname, "system%s", cmd.slot);
setup_block(partname, block_dev);
xmkdir("/system_root", 0755);
if (xmount(block_dev, "/system_root", "ext4", MS_RDONLY, nullptr))
xmount(block_dev, "/system_root", "erofs", MS_RDONLY, nullptr);
xmkdir("/system", 0755);
xmount("/system_root/system", "/system", nullptr, MS_BIND, nullptr);
// Android Q
if (is_lnk("/system_root/init"))
load_sepol = true;
// System-as-root with monolithic sepolicy
if (access("/system_root/sepolicy", F_OK) == 0)
cp_afc("/system_root/sepolicy", "/sepolicy");
// Copy if these partitions are symlinks
link_root("/vendor");
link_root("/product");
link_root("/odm");
} else {
mount_root(system);
}
mount_root(system);
mount_root(vendor);
mount_root(product);
mount_root(odm);
}
void SARInit::early_mount() {
char partname[32];
char fstype[32];
char block_dev[64];
LOGD("Early mount system_root\n");
sprintf(partname, "system%s", cmd->slot);
setup_block(partname, block_dev);
xmkdir("/system_root", 0755);
if (xmount(block_dev, "/system_root", "ext4", MS_RDONLY, nullptr))
xmount(block_dev, "/system_root", "erofs", MS_RDONLY, nullptr);
xmkdir("/system", 0755);
xmount("/system_root/system", "/system", nullptr, MS_BIND, nullptr);
// Android Q
if (is_lnk("/system_root/init"))
load_sepol = true;
// System-as-root with monolithic sepolicy
if (access("/system_root/sepolicy", F_OK) == 0)
cp_afc("/system_root/sepolicy", "/sepolicy");
link_root("/vendor");
link_root("/product");
link_root("/odm");
mount_root(vendor);
mount_root(product);
mount_root(odm);
}
#define umount_root(name) \
if (mnt_##name) \
umount("/" #name);
void BaseInit::cleanup() {
umount(SELINUX_MNT);
umount("/sys");
umount("/proc");
umount_root(system);
umount_root(vendor);
umount_root(product);
umount_root(odm);
}

View File

@@ -92,7 +92,7 @@ static bool check_key_combo() {
return false;
}
void MagiskInit::load_kernel_info() {
void load_kernel_info(cmdline *cmd) {
// Communicate with kernel using procfs and sysfs
xmkdir("/proc", 0755);
xmount("proc", "/proc", "proc", 0, nullptr);
@@ -106,14 +106,14 @@ void MagiskInit::load_kernel_info() {
parse_cmdline([&](auto key, auto value) -> void {
LOGD("cmdline: [%s]=[%s]\n", key.data(), value);
if (key == "androidboot.slot_suffix") {
strcpy(cmd.slot, value);
strcpy(cmd->slot, value);
} else if (key == "androidboot.slot") {
cmd.slot[0] = '_';
strcpy(cmd.slot + 1, value);
cmd->slot[0] = '_';
strcpy(cmd->slot + 1, value);
} else if (key == "skip_initramfs") {
cmd.system_as_root = true;
cmd->system_as_root = true;
} else if (key == "androidboot.android_dt_dir") {
strcpy(cmd.dt_dir, value);
strcpy(cmd->dt_dir, value);
} else if (key == "enter_recovery") {
enter_recovery = value[0] == '1';
} else if (key == "androidboot.hardware") {
@@ -140,13 +140,13 @@ void MagiskInit::load_kernel_info() {
if (recovery_mode) {
LOGD("Running in recovery mode, waiting for key...\n");
cmd.system_as_root = !check_key_combo();
cmd->system_as_root = !check_key_combo();
}
if (cmd.dt_dir[0] == '\0')
strcpy(cmd.dt_dir, DEFAULT_DT_DIR);
if (cmd->dt_dir[0] == '\0')
strcpy(cmd->dt_dir, DEFAULT_DT_DIR);
LOGD("system_as_root=[%d]\n", cmd.system_as_root);
LOGD("slot=[%s]\n", cmd.slot);
LOGD("dt_dir=[%s]\n", cmd.dt_dir);
LOGD("system_as_root=[%d]\n", cmd->system_as_root);
LOGD("slot=[%s]\n", cmd->slot);
LOGD("dt_dir=[%s]\n", cmd->dt_dir);
}

View File

@@ -6,13 +6,11 @@
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <dirent.h>
#include <fcntl.h>
#include <libgen.h>
#include <xz.h>
#include <magisk.h>
#include <selinux.h>
#include <cpio.h>
#include <utils.h>
#include <flags.h>
@@ -48,12 +46,10 @@ static void setup_klog() {
#define setup_klog(...)
#endif
static int test_main(int argc, char *argv[]);
constexpr const char *init_applet[] =
{ "magiskpolicy", "supolicy", "init_test", nullptr };
{ "magiskpolicy", "supolicy", nullptr };
constexpr int (*init_applet_main[])(int, char *[]) =
{ magiskpolicy_main, magiskpolicy_main, test_main, nullptr };
{ magiskpolicy_main, magiskpolicy_main, nullptr };
static bool unxz(int fd, const uint8_t *buf, size_t size) {
uint8_t out[8192];
@@ -117,50 +113,27 @@ static int dump_manager(const char *path, mode_t mode) {
return 0;
}
void MagiskInit::preset() {
root = open("/", O_RDONLY | O_CLOEXEC);
if (cmd.system_as_root) {
// Clear rootfs
LOGD("Cleaning rootfs\n");
frm_rf(root, { "overlay", "proc", "sys" });
} else {
decompress_ramdisk();
// Revert original init binary
rename("/.backup/init", "/init");
rm_rf("/.backup");
// Do not go further if device is booting into recovery
if (access("/sbin/recovery", F_OK) == 0) {
LOGD("Ramdisk is recovery, abort\n");
re_exec_init();
}
}
}
#define umount_root(name) \
if (mnt_##name) \
umount("/" #name);
void MagiskInit::cleanup() {
umount(SELINUX_MNT);
umount("/sys");
umount("/proc");
umount_root(system);
umount_root(vendor);
umount_root(product);
umount_root(odm);
}
void MagiskInit::re_exec_init() {
void BaseInit::re_exec_init() {
LOGD("Re-exec /init\n");
cleanup();
execv("/init", argv);
exit(1);
}
void MagiskInit::start() {
void LegacyInit::preset() {
LOGD("Reverting /init\n");
root = open("/", O_RDONLY | O_CLOEXEC);
rename("/.backup/init", "/init");
rm_rf("/.backup");
}
void SARInit::preset() {
LOGD("Cleaning rootfs\n");
root = open("/", O_RDONLY | O_CLOEXEC);
frm_rf(root, { "overlay", "proc", "sys" });
}
void BaseInit::start() {
// Prevent file descriptor confusion
mknod("/null", S_IFCHR | 0666, makedev(1, 3));
int null = open("/null", O_RDWR | O_CLOEXEC);
@@ -171,10 +144,6 @@ void MagiskInit::start() {
if (null > STDERR_FILENO)
close(null);
setup_klog();
load_kernel_info();
full_read("/init", &self.buf, &self.sz);
full_read("/.backup/.magisk", &config.buf, &config.sz);
@@ -184,26 +153,27 @@ void MagiskInit::start() {
re_exec_init();
}
void MagiskInit::test() {
cmdline_logging();
log_cb.ex = nop_ex;
class RecoveryInit : public BaseInit {
public:
RecoveryInit(char *argv[], cmdline *cmd) : BaseInit(argv, cmd) {};
void start() override {
LOGD("Ramdisk is recovery, abort\n");
rename("/.backup/init", "/init");
rm_rf("/.backup");
re_exec_init();
}
};
chdir(dirname(argv[0]));
chroot(".");
chdir("/");
load_kernel_info();
preset();
early_mount();
setup_rootfs();
cleanup();
}
static int test_main(int, char *argv[]) {
MagiskInit init(argv);
init.test();
return 0;
}
class TestInit : public SARInit {
public:
TestInit(char *argv[], cmdline *cmd) : SARInit(argv, cmd) {};
void start() override {
preset();
early_mount();
setup_rootfs();
cleanup();
}
};
int main(int argc, char *argv[]) {
umask(0);
@@ -220,11 +190,40 @@ int main(int argc, char *argv[]) {
return dump_manager(argv[3], 0644);
}
if (getpid() != 1)
return 1;
#ifdef MAGISK_DEBUG
bool run_test = getenv("INIT_TEST") != nullptr;
#else
constexpr bool run_test = false;
#endif
MagiskInit init(argv);
if (run_test) {
chdir(dirname(argv[0]));
chroot(".");
chdir("/");
cmdline_logging();
log_cb.ex = nop_ex;
} else {
if (getpid() != 1)
return 1;
setup_klog();
}
cmdline cmd{};
load_kernel_info(&cmd);
unique_ptr<BaseInit> init;
if (run_test) {
init = make_unique<TestInit>(argv, &cmd);
} else if (cmd.system_as_root) {
init = make_unique<SARInit>(argv, &cmd);
} else {
decompress_ramdisk();
if (access("/sbin/recovery", F_OK) == 0)
init = make_unique<RecoveryInit>(argv, &cmd);
else
init = make_unique<LegacyInit>(argv, &cmd);
}
// Run the main routine
init.start();
init->start();
}

View File

@@ -11,22 +11,21 @@ struct raw_data {
size_t sz;
};
class MagiskInit {
private:
cmdline cmd{};
class BaseInit {
protected:
cmdline *cmd;
raw_data self{};
raw_data config{};
int root = -1;
char **argv;
int root = -1;
bool load_sepol = false;
bool mnt_system = false;
bool mnt_vendor = false;
bool mnt_product = false;
bool mnt_odm = false;
void load_kernel_info();
void preset();
void early_mount();
virtual void preset() {};
virtual void early_mount() {}
void setup_rootfs();
bool read_dt_fstab(const char *name, char *partname, char *fstype);
bool patch_sepolicy();
@@ -34,9 +33,25 @@ private:
void re_exec_init();
public:
explicit MagiskInit(char *argv[]) : argv(argv) {}
void start();
void test();
BaseInit(char *argv[], cmdline *cmd) : cmd(cmd), argv(argv) {}
virtual ~BaseInit() = default;
virtual void start();
};
class LegacyInit : public BaseInit {
protected:
void preset() override;
void early_mount() override;
public:
LegacyInit(char *argv[], cmdline *cmd) : BaseInit(argv, cmd) {};
};
class SARInit : public BaseInit {
protected:
void preset() override;
void early_mount() override;
public:
SARInit(char *argv[], cmdline *cmd) : BaseInit(argv, cmd) {};
};
static inline bool is_lnk(const char *name) {
@@ -46,4 +61,5 @@ static inline bool is_lnk(const char *name) {
return S_ISLNK(st.st_mode);
}
void load_kernel_info(cmdline *cmd);
int dump_magisk(const char *path, mode_t mode);

View File

@@ -37,10 +37,10 @@ constexpr const char wrapper[] =
"exec /sbin/magisk.bin \"$0\" \"$@\"\n"
;
void MagiskInit::setup_rootfs() {
void BaseInit::setup_rootfs() {
bool patch_init = patch_sepolicy();
if (cmd.system_as_root) {
if (cmd->system_as_root) {
// Clone rootfs
LOGD("Clone root dir from system to rootfs\n");
int system_root = xopen("/system_root", O_RDONLY | O_CLOEXEC);
@@ -173,7 +173,7 @@ void MagiskInit::setup_rootfs() {
close(sbin);
}
bool MagiskInit::patch_sepolicy() {
bool BaseInit::patch_sepolicy() {
bool patch_init = false;
if (access(SPLIT_PLAT_CIL, R_OK) == 0) {

View File

@@ -12,6 +12,8 @@
#include "magiskboot.h"
#include "compress.h"
using namespace std;
static void usage(char *arg0) {
fprintf(stderr,
FULL_VER(MagiskBoot) " - Boot Image Modification Tool\n"
@@ -114,10 +116,11 @@ int main(int argc, char *argv[]) {
usage(argv[0]);
// Skip '--' for backwards compatibility
if (strncmp(argv[1], "--", 2) == 0)
argv[1] += 2;
string_view action(argv[1]);
if (str_starts(action, "--"))
action = argv[1] + 2;
if (strcmp(argv[1], "cleanup") == 0) {
if (action == "cleanup") {
fprintf(stderr, "Cleaning up...\n");
unlink(HEADER_FILE);
unlink(KERNEL_FILE);
@@ -127,7 +130,7 @@ int main(int argc, char *argv[]) {
unlink(EXTRA_FILE);
unlink(RECV_DTBO_FILE);
unlink(DTB_FILE);
} else if (argc > 2 && strcmp(argv[1], "sha1") == 0) {
} else if (argc > 2 && action == "sha1") {
uint8_t sha1[SHA_DIGEST_SIZE];
void *buf;
size_t size;
@@ -137,34 +140,35 @@ int main(int argc, char *argv[]) {
printf("%02x", i);
printf("\n");
munmap(buf, size);
} else if (argc > 2 && strcmp(argv[1], "unpack") == 0) {
if (strcmp(argv[2], "-h") == 0) {
} else if (argc > 2 && action == "unpack") {
if (argv[2] == "-h"sv) {
if (argc == 3)
usage(argv[0]);
return unpack(argv[3], true);
} else {
return unpack(argv[2]);
}
} else if (argc > 2 && strcmp(argv[1], "repack") == 0) {
if (strcmp(argv[2], "-n") == 0) {
if (argc == 4)
} else if (argc > 2 && action == "repack") {
if (argv[2] == "-n"sv) {
if (argc == 3)
usage(argv[0]);
repack(argv[3], argv[4] ? argv[4] : NEW_BOOT, true);
} else {
repack(argv[2], argv[3] ? argv[3] : NEW_BOOT);
}
} else if (argc > 2 && strcmp(argv[1], "decompress") == 0) {
} else if (argc > 2 && action == "decompress") {
decompress(argv[2], argv[3]);
} else if (argc > 2 && strncmp(argv[1], "compress", 8) == 0) {
compress(argv[1][8] == '=' ? &argv[1][9] : "gzip", argv[2], argv[3]);
} else if (argc > 4 && strcmp(argv[1], "hexpatch") == 0) {
} else if (argc > 2 && str_starts(action, "compress")) {
compress(action[8] == '=' ? &action[9] : "gzip", argv[2], argv[3]);
} else if (argc > 4 && action == "hexpatch") {
return hexpatch(argv[2], argv[3], argv[4]);
} else if (argc > 2 && strcmp(argv[1], "cpio") == 0) {
if (cpio_commands(argc - 2, argv + 2)) usage(argv[0]);
} else if (argc > 2 && strncmp(argv[1], "dtb", 3) == 0) {
if (argv[1][3] != '-')
} else if (argc > 2 && action == "cpio"sv) {
if (cpio_commands(argc - 2, argv + 2))
usage(argv[0]);
if (dtb_commands(&argv[1][4], argc - 2, argv + 2))
} else if (argc > 2 && str_starts(action, "dtb")) {
if (action[3] != '-')
usage(argv[0]);
if (dtb_commands(&action[4], argc - 2, argv + 2))
usage(argv[0]);
} else {
usage(argv[0]);