mirror of
				https://github.com/topjohnwu/Magisk
				synced 2025-11-03 15:52:30 +01:00 
			
		
		
		
	Compare commits
	
		
			91 Commits
		
	
	
		
			manager-v7
			...
			v20.1
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					7da97489cc | ||
| 
						 | 
					a9f11b28c8 | ||
| 
						 | 
					b31d986c8d | ||
| 
						 | 
					2dad751889 | ||
| 
						 | 
					c85b1c56af | ||
| 
						 | 
					6dd34aec47 | ||
| 
						 | 
					4cd154675f | ||
| 
						 | 
					d8d72f92b3 | ||
| 
						 | 
					a30f5b175f | ||
| 
						 | 
					8277896ca1 | ||
| 
						 | 
					493068c073 | ||
| 
						 | 
					f4299fbea8 | ||
| 
						 | 
					10ce11d671 | ||
| 
						 | 
					0f34457a10 | ||
| 
						 | 
					34c65e13bc | ||
| 
						 | 
					17a77e2577 | ||
| 
						 | 
					0f219e5ae6 | ||
| 
						 | 
					353c3c7d81 | ||
| 
						 | 
					0a89edf3b0 | ||
| 
						 | 
					e7155837d7 | ||
| 
						 | 
					31e003bda5 | ||
| 
						 | 
					490e4d3180 | ||
| 
						 | 
					dc9f69bab0 | ||
| 
						 | 
					fdf04f77f2 | ||
| 
						 | 
					5e87483f34 | ||
| 
						 | 
					f7aa451591 | ||
| 
						 | 
					321d11c2c6 | ||
| 
						 | 
					ee447bc4ce | ||
| 
						 | 
					31153e4366 | ||
| 
						 | 
					7693024c29 | ||
| 
						 | 
					9628700a2f | ||
| 
						 | 
					38576173cb | ||
| 
						 | 
					19a769c12e | ||
| 
						 | 
					3c1db7d2f7 | ||
| 
						 | 
					626507093a | ||
| 
						 | 
					588b3d14a3 | ||
| 
						 | 
					815efa7791 | ||
| 
						 | 
					97a691ce2f | ||
| 
						 | 
					9d948f2c2b | ||
| 
						 | 
					0b87108174 | ||
| 
						 | 
					7fc7809cfc | ||
| 
						 | 
					c30be20e49 | ||
| 
						 | 
					25c64db0a1 | ||
| 
						 | 
					676e9c6593 | ||
| 
						 | 
					d459859361 | ||
| 
						 | 
					2be0cef446 | ||
| 
						 | 
					294db93fde | ||
| 
						 | 
					7f971f7173 | ||
| 
						 | 
					5c7b59524d | ||
| 
						 | 
					5133e5910e | ||
| 
						 | 
					1512c350df | ||
| 
						 | 
					a5fc7891a6 | ||
| 
						 | 
					3eb9633231 | ||
| 
						 | 
					ac67b48247 | ||
| 
						 | 
					81b65ea646 | ||
| 
						 | 
					45c1f6bc27 | ||
| 
						 | 
					0d31e5c8b1 | ||
| 
						 | 
					6378abf454 | ||
| 
						 | 
					f8fcaadb5b | ||
| 
						 | 
					0b5fd3ee76 | ||
| 
						 | 
					d010cb7e42 | ||
| 
						 | 
					71136d7347 | ||
| 
						 | 
					a18c552ddf | ||
| 
						 | 
					9656878ef3 | ||
| 
						 | 
					7ded7de39a | ||
| 
						 | 
					0f74e89b44 | ||
| 
						 | 
					953c40b083 | ||
| 
						 | 
					271b0287d8 | ||
| 
						 | 
					96a8a2a8b8 | ||
| 
						 | 
					75306f658f | ||
| 
						 | 
					325d9a0b86 | ||
| 
						 | 
					a02493fbaa | ||
| 
						 | 
					9c27d691dd | ||
| 
						 | 
					935bd01f59 | ||
| 
						 | 
					eeb5d669f6 | ||
| 
						 | 
					78daa2eb62 | ||
| 
						 | 
					40eda05a30 | ||
| 
						 | 
					9f9de8c43b | ||
| 
						 | 
					a910c8ccd8 | ||
| 
						 | 
					43bda2d4a4 | ||
| 
						 | 
					c7033dd757 | ||
| 
						 | 
					5673a9bace | ||
| 
						 | 
					34ff764515 | ||
| 
						 | 
					1b3a009da7 | ||
| 
						 | 
					a49002bb2c | ||
| 
						 | 
					7342fc2307 | ||
| 
						 | 
					9867a3bd60 | ||
| 
						 | 
					5ffb9eaa5b | ||
| 
						 | 
					b05b688267 | ||
| 
						 | 
					f3d7f85063 | ||
| 
						 | 
					de969a9dab | 
							
								
								
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -2,8 +2,8 @@ out
 | 
			
		||||
*.zip
 | 
			
		||||
*.jks
 | 
			
		||||
*.apk
 | 
			
		||||
config.prop
 | 
			
		||||
update.sh
 | 
			
		||||
/config.prop
 | 
			
		||||
/update.sh
 | 
			
		||||
 | 
			
		||||
# Built binaries
 | 
			
		||||
native/out
 | 
			
		||||
 
 | 
			
		||||
@@ -30,13 +30,12 @@ Furthermore, Magisk provides a **Systemless Interface** to alter the system (or
 | 
			
		||||
 | 
			
		||||
## Translations
 | 
			
		||||
 | 
			
		||||
Default string resources for Magisk Manager are scattered throughout
 | 
			
		||||
Default string resources for Magisk Manager and its stub APK are located here:
 | 
			
		||||
 | 
			
		||||
- `app/src/main/res/values/strings.xml`
 | 
			
		||||
- `stub/src/main/res/values/strings.xml`
 | 
			
		||||
- `shared/src/main/res/values/strings.xml`
 | 
			
		||||
 | 
			
		||||
Translate each and place them in the respective locations (`<module>/src/main/res/values-<lang>/strings.xml`).
 | 
			
		||||
Translate each and place them in the respective locations (`[module]/src/main/res/values-[lang]/strings.xml`).
 | 
			
		||||
 | 
			
		||||
## Signature Verification
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -75,7 +75,7 @@ dependencies {
 | 
			
		||||
    implementation "${bindingAdapter}:${vBAdapt}"
 | 
			
		||||
    implementation "${bindingAdapter}-recyclerview:${vBAdapt}"
 | 
			
		||||
 | 
			
		||||
    def vMarkwon = '4.1.1'
 | 
			
		||||
    def vMarkwon = '4.1.2'
 | 
			
		||||
    implementation "io.noties.markwon:core:${vMarkwon}"
 | 
			
		||||
    implementation "io.noties.markwon:html:${vMarkwon}"
 | 
			
		||||
    implementation "io.noties.markwon:image:${vMarkwon}"
 | 
			
		||||
@@ -112,7 +112,7 @@ dependencies {
 | 
			
		||||
            replacedBy('com.github.topjohnwu:room-runtime')
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    def vRoom = "2.2.0"
 | 
			
		||||
    def vRoom = "2.2.1"
 | 
			
		||||
    implementation "com.github.topjohnwu:room-runtime:${vRoom}"
 | 
			
		||||
    kapt "androidx.room:room-compiler:${vRoom}"
 | 
			
		||||
 | 
			
		||||
@@ -123,11 +123,12 @@ dependencies {
 | 
			
		||||
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
 | 
			
		||||
    implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0-alpha03'
 | 
			
		||||
    implementation 'androidx.preference:preference:1.1.0'
 | 
			
		||||
    implementation 'androidx.recyclerview:recyclerview:1.1.0-beta05'
 | 
			
		||||
    implementation 'androidx.recyclerview:recyclerview:1.1.0-rc01'
 | 
			
		||||
    implementation 'androidx.fragment:fragment-ktx:1.2.0-rc01'
 | 
			
		||||
    implementation 'androidx.cardview:cardview:1.0.0'
 | 
			
		||||
    implementation 'androidx.work:work-runtime:2.2.0'
 | 
			
		||||
    implementation 'androidx.transition:transition:1.2.0'
 | 
			
		||||
    implementation 'androidx.transition:transition:1.3.0-rc01'
 | 
			
		||||
    implementation 'androidx.multidex:multidex:2.0.1'
 | 
			
		||||
    implementation 'androidx.core:core-ktx:1.1.0'
 | 
			
		||||
    implementation 'com.google.android.material:material:1.1.0-beta01'
 | 
			
		||||
    implementation 'com.google.android.material:material:1.2.0-alpha01'
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										6
									
								
								app/proguard-rules.pro
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								app/proguard-rules.pro
									
									
									
									
										vendored
									
									
								
							@@ -32,7 +32,11 @@
 | 
			
		||||
-keep,allowobfuscation class * extends com.topjohnwu.magisk.base.DelegateWorker
 | 
			
		||||
 | 
			
		||||
# BootSigner
 | 
			
		||||
-keepclassmembers class com.topjohnwu.signing.BootSigner { *; }
 | 
			
		||||
-keep class a.a { *; }
 | 
			
		||||
 | 
			
		||||
# Workaround R8 bug
 | 
			
		||||
-keep,allowobfuscation class com.topjohnwu.magisk.model.receiver.GeneralReceiver
 | 
			
		||||
-keepclassmembers class a.e { *; }
 | 
			
		||||
 | 
			
		||||
# Strip logging
 | 
			
		||||
-assumenosideeffects class timber.log.Timber.Tree { *; }
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										5
									
								
								app/res-ids.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								app/res-ids.txt
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,5 @@
 | 
			
		||||
com.topjohnwu.magisk:color/xxxxxxxx = 0x7f010000
 | 
			
		||||
com.topjohnwu.magisk:drawable/xxxxxxxx = 0x7f020000
 | 
			
		||||
com.topjohnwu.magisk:string/xxxxxxxx = 0x7f030000
 | 
			
		||||
com.topjohnwu.magisk:style/xxxxxxxx = 0x7f040000
 | 
			
		||||
com.topjohnwu.magisk:xml/xxxxxxxx = 0x7f050000
 | 
			
		||||
@@ -1,4 +1,22 @@
 | 
			
		||||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
 | 
			
		||||
<!--
 | 
			
		||||
    ** Special Requirements **
 | 
			
		||||
 | 
			
		||||
    This AndroidManifest.xml will be copied into the stub
 | 
			
		||||
    APK to allow APK delegation. This is why these special
 | 
			
		||||
    requirements exist.
 | 
			
		||||
 | 
			
		||||
    * Class names *
 | 
			
		||||
    Class names a.a, a.c, a.e should not be changed as they are used
 | 
			
		||||
    externally. All other class names can be changed.
 | 
			
		||||
 | 
			
		||||
    * Resource IDs *
 | 
			
		||||
    All resource IDs referred in AndroidManifest.xml is required to be
 | 
			
		||||
    included into the "shared" module to make the ID match with stub.
 | 
			
		||||
 | 
			
		||||
-->
 | 
			
		||||
 | 
			
		||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
 | 
			
		||||
    xmlns:tools="http://schemas.android.com/tools"
 | 
			
		||||
    package="com.topjohnwu.magisk">
 | 
			
		||||
@@ -11,41 +29,46 @@
 | 
			
		||||
 | 
			
		||||
    <application
 | 
			
		||||
        android:name="a.e"
 | 
			
		||||
        android:appComponentFactory="a.a"
 | 
			
		||||
        android:allowBackup="true"
 | 
			
		||||
        android:theme="@style/MagiskTheme"
 | 
			
		||||
        android:usesCleartextTraffic="true"
 | 
			
		||||
        tools:ignore="UnusedAttribute,GoogleAppIndexingWarning">
 | 
			
		||||
        tools:ignore="UnusedAttribute,GoogleAppIndexingWarning"
 | 
			
		||||
        tools:replace="android:appComponentFactory">
 | 
			
		||||
 | 
			
		||||
        <!-- Activities -->
 | 
			
		||||
        <!-- Splash -->
 | 
			
		||||
 | 
			
		||||
        <activity
 | 
			
		||||
            android:name="a.b"
 | 
			
		||||
            android:configChanges="orientation|screenSize"
 | 
			
		||||
            android:exported="true" />
 | 
			
		||||
        <activity
 | 
			
		||||
            android:name="a.c"
 | 
			
		||||
            android:configChanges="orientation|screenSize"
 | 
			
		||||
            android:exported="true"
 | 
			
		||||
            android:theme="@style/SplashTheme">
 | 
			
		||||
            <intent-filter>
 | 
			
		||||
                <action android:name="android.intent.action.MAIN" />
 | 
			
		||||
                <category android:name="android.intent.category.LAUNCHER" />
 | 
			
		||||
            </intent-filter>
 | 
			
		||||
        </activity>
 | 
			
		||||
 | 
			
		||||
        <!-- Main -->
 | 
			
		||||
 | 
			
		||||
        <activity
 | 
			
		||||
            android:name="a.b"
 | 
			
		||||
            android:configChanges="orientation|screenSize"
 | 
			
		||||
            android:exported="true" />
 | 
			
		||||
 | 
			
		||||
        <!-- Flashing  -->
 | 
			
		||||
 | 
			
		||||
        <activity
 | 
			
		||||
            android:name="a.f"
 | 
			
		||||
            android:configChanges="keyboardHidden|orientation|screenSize"
 | 
			
		||||
            android:screenOrientation="nosensor"
 | 
			
		||||
            android:theme="@style/MagiskTheme.Flashing" />
 | 
			
		||||
            android:screenOrientation="nosensor" />
 | 
			
		||||
 | 
			
		||||
        <!-- Superuser -->
 | 
			
		||||
 | 
			
		||||
        <activity
 | 
			
		||||
            android:name="a.m"
 | 
			
		||||
            android:theme="@android:style/Theme.Translucent.NoTitleBar"
 | 
			
		||||
            android:directBootAware="true"
 | 
			
		||||
            android:excludeFromRecents="true"
 | 
			
		||||
            android:exported="false"
 | 
			
		||||
            android:theme="@style/MagiskTheme.SU" />
 | 
			
		||||
            android:exported="false" />
 | 
			
		||||
 | 
			
		||||
        <!-- Receiver -->
 | 
			
		||||
 | 
			
		||||
@@ -53,6 +76,7 @@
 | 
			
		||||
            android:name="a.h"
 | 
			
		||||
            android:directBootAware="true">
 | 
			
		||||
            <intent-filter>
 | 
			
		||||
                <action android:name="android.intent.action.REBOOT" />
 | 
			
		||||
                <action android:name="android.intent.action.BOOT_COMPLETED" />
 | 
			
		||||
                <action android:name="android.intent.action.LOCALE_CHANGED" />
 | 
			
		||||
            </intent-filter>
 | 
			
		||||
@@ -64,9 +88,10 @@
 | 
			
		||||
            </intent-filter>
 | 
			
		||||
        </receiver>
 | 
			
		||||
 | 
			
		||||
        <!-- Service -->
 | 
			
		||||
        <!-- DownloadService -->
 | 
			
		||||
 | 
			
		||||
        <service android:name="a.j"
 | 
			
		||||
        <service
 | 
			
		||||
            android:name="a.j"
 | 
			
		||||
            android:exported="false" />
 | 
			
		||||
 | 
			
		||||
        <!-- Hardcode GMS version -->
 | 
			
		||||
@@ -74,6 +99,12 @@
 | 
			
		||||
            android:name="com.google.android.gms.version"
 | 
			
		||||
            android:value="12451000" />
 | 
			
		||||
 | 
			
		||||
        <!-- Initialize WorkManager on-demand -->
 | 
			
		||||
        <provider
 | 
			
		||||
            android:name="androidx.work.impl.WorkManagerInitializer"
 | 
			
		||||
            android:authorities="${applicationId}.workmanager-init"
 | 
			
		||||
            tools:node="remove" />
 | 
			
		||||
 | 
			
		||||
    </application>
 | 
			
		||||
 | 
			
		||||
</manifest>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,13 +1,22 @@
 | 
			
		||||
package a;
 | 
			
		||||
 | 
			
		||||
import androidx.core.app.AppComponentFactory;
 | 
			
		||||
 | 
			
		||||
import com.topjohnwu.magisk.utils.PatchAPK;
 | 
			
		||||
import com.topjohnwu.signing.BootSigner;
 | 
			
		||||
 | 
			
		||||
import androidx.annotation.Keep;
 | 
			
		||||
public class a extends AppComponentFactory {
 | 
			
		||||
 | 
			
		||||
@Keep
 | 
			
		||||
public class a extends BootSigner {
 | 
			
		||||
    @Deprecated
 | 
			
		||||
    public static boolean patchAPK(String in, String out, String pkg) {
 | 
			
		||||
        return PatchAPK.patch(in, out, pkg);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static boolean patchAPK(String in, String out, String pkg, String label) {
 | 
			
		||||
        return PatchAPK.patch(in, out, pkg, label);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static void main(String[] args) throws Exception {
 | 
			
		||||
        BootSigner.main(args);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -3,5 +3,11 @@ package a;
 | 
			
		||||
import com.topjohnwu.magisk.App;
 | 
			
		||||
 | 
			
		||||
public class e extends App {
 | 
			
		||||
    /* stub */
 | 
			
		||||
    public e() {
 | 
			
		||||
        super();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public e(Object o) {
 | 
			
		||||
        super(o);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -6,6 +6,7 @@ import android.content.res.Configuration
 | 
			
		||||
import androidx.appcompat.app.AppCompatDelegate
 | 
			
		||||
import androidx.multidex.MultiDex
 | 
			
		||||
import androidx.room.Room
 | 
			
		||||
import androidx.work.WorkManager
 | 
			
		||||
import androidx.work.impl.WorkDatabase
 | 
			
		||||
import androidx.work.impl.WorkDatabase_Impl
 | 
			
		||||
import com.topjohnwu.magisk.data.database.RepoDatabase
 | 
			
		||||
@@ -13,20 +14,25 @@ import com.topjohnwu.magisk.data.database.RepoDatabase_Impl
 | 
			
		||||
import com.topjohnwu.magisk.di.ActivityTracker
 | 
			
		||||
import com.topjohnwu.magisk.di.koinModules
 | 
			
		||||
import com.topjohnwu.magisk.extensions.get
 | 
			
		||||
import com.topjohnwu.magisk.utils.LocaleManager
 | 
			
		||||
import com.topjohnwu.magisk.utils.RootUtils
 | 
			
		||||
import com.topjohnwu.magisk.extensions.unwrap
 | 
			
		||||
import com.topjohnwu.magisk.utils.RootInit
 | 
			
		||||
import com.topjohnwu.magisk.utils.updateConfig
 | 
			
		||||
import com.topjohnwu.superuser.Shell
 | 
			
		||||
import org.koin.android.ext.koin.androidContext
 | 
			
		||||
import org.koin.core.context.startKoin
 | 
			
		||||
import timber.log.Timber
 | 
			
		||||
 | 
			
		||||
open class App : Application() {
 | 
			
		||||
open class App() : Application() {
 | 
			
		||||
 | 
			
		||||
    constructor(o: Any) : this() {
 | 
			
		||||
        Info.stub = DynAPK.load(o)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    init {
 | 
			
		||||
        AppCompatDelegate.setCompatVectorFromResourcesEnabled(true)
 | 
			
		||||
        Shell.Config.setFlags(Shell.FLAG_MOUNT_MASTER or Shell.FLAG_USE_MAGISK_BUSYBOX)
 | 
			
		||||
        Shell.Config.verboseLogging(BuildConfig.DEBUG)
 | 
			
		||||
        Shell.Config.addInitializers(RootUtils::class.java)
 | 
			
		||||
        Shell.Config.addInitializers(RootInit::class.java)
 | 
			
		||||
        Shell.Config.setTimeout(2)
 | 
			
		||||
        Room.setFactory {
 | 
			
		||||
            when (it) {
 | 
			
		||||
@@ -38,22 +44,42 @@ open class App : Application() {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun attachBaseContext(base: Context) {
 | 
			
		||||
        super.attachBaseContext(base)
 | 
			
		||||
        // Basic setup
 | 
			
		||||
        if (BuildConfig.DEBUG)
 | 
			
		||||
            MultiDex.install(base)
 | 
			
		||||
        Timber.plant(Timber.DebugTree())
 | 
			
		||||
 | 
			
		||||
        // Some context magic
 | 
			
		||||
        val app: Application
 | 
			
		||||
        val impl: Context
 | 
			
		||||
        if (base is Application) {
 | 
			
		||||
            app = base
 | 
			
		||||
            impl = base.baseContext
 | 
			
		||||
        } else {
 | 
			
		||||
            app = this
 | 
			
		||||
            impl = base
 | 
			
		||||
        }
 | 
			
		||||
        val wrapped = impl.wrap()
 | 
			
		||||
        super.attachBaseContext(wrapped)
 | 
			
		||||
 | 
			
		||||
        // Normal startup
 | 
			
		||||
        startKoin {
 | 
			
		||||
            androidContext(this@App)
 | 
			
		||||
            androidContext(wrapped)
 | 
			
		||||
            modules(koinModules)
 | 
			
		||||
        }
 | 
			
		||||
        ResourceMgr.init(impl)
 | 
			
		||||
        app.registerActivityLifecycleCallbacks(get<ActivityTracker>())
 | 
			
		||||
        WorkManager.initialize(impl.wrapJob(), androidx.work.Configuration.Builder().build())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
        registerActivityLifecycleCallbacks(get<ActivityTracker>())
 | 
			
		||||
        LocaleManager.setLocale(this)
 | 
			
		||||
    // This is required as some platforms expect ContextImpl
 | 
			
		||||
    override fun getBaseContext(): Context {
 | 
			
		||||
        return super.getBaseContext().unwrap()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onConfigurationChanged(newConfig: Configuration) {
 | 
			
		||||
        super.onConfigurationChanged(newConfig)
 | 
			
		||||
        LocaleManager.setLocale(this)
 | 
			
		||||
        resources.updateConfig(newConfig)
 | 
			
		||||
        if (!isRunningAsStub)
 | 
			
		||||
            super.onConfigurationChanged(newConfig)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,26 +0,0 @@
 | 
			
		||||
package com.topjohnwu.magisk
 | 
			
		||||
 | 
			
		||||
import com.topjohnwu.magisk.model.download.DownloadService
 | 
			
		||||
import com.topjohnwu.magisk.model.receiver.GeneralReceiver
 | 
			
		||||
import com.topjohnwu.magisk.model.update.UpdateCheckService
 | 
			
		||||
import com.topjohnwu.magisk.ui.MainActivity
 | 
			
		||||
import com.topjohnwu.magisk.ui.SplashActivity
 | 
			
		||||
import com.topjohnwu.magisk.ui.flash.FlashActivity
 | 
			
		||||
import com.topjohnwu.magisk.ui.surequest.SuRequestActivity
 | 
			
		||||
 | 
			
		||||
object ClassMap {
 | 
			
		||||
    private val map = mapOf(
 | 
			
		||||
        App::class.java to a.e::class.java,
 | 
			
		||||
        MainActivity::class.java to a.b::class.java,
 | 
			
		||||
        SplashActivity::class.java to a.c::class.java,
 | 
			
		||||
        FlashActivity::class.java to a.f::class.java,
 | 
			
		||||
        UpdateCheckService::class.java to a.g::class.java,
 | 
			
		||||
        GeneralReceiver::class.java to a.h::class.java,
 | 
			
		||||
        DownloadService::class.java to a.j::class.java,
 | 
			
		||||
        SuRequestActivity::class.java to a.m::class.java
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    operator fun <T : Class<*>>get(c: Class<*>): T {
 | 
			
		||||
        return map.getOrElse(c) { throw IllegalArgumentException() } as T
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -32,8 +32,9 @@ object Config : PreferenceModel, DBConfig {
 | 
			
		||||
        const val ROOT_ACCESS = "root_access"
 | 
			
		||||
        const val SU_MULTIUSER_MODE = "multiuser_mode"
 | 
			
		||||
        const val SU_MNT_NS = "mnt_ns"
 | 
			
		||||
        const val SU_MANAGER = "requester"
 | 
			
		||||
        const val SU_FINGERPRINT = "su_fingerprint"
 | 
			
		||||
        const val SU_MANAGER = "requester"
 | 
			
		||||
        const val KEYSTORE = "keystore"
 | 
			
		||||
 | 
			
		||||
        // prefs
 | 
			
		||||
        const val SU_REQUEST_TIMEOUT = "su_request_timeout"
 | 
			
		||||
@@ -97,7 +98,12 @@ object Config : PreferenceModel, DBConfig {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private val defaultChannel =
 | 
			
		||||
        if (Utils.isCanary) Value.CANARY_DEBUG_CHANNEL
 | 
			
		||||
        if (Utils.isCanary) {
 | 
			
		||||
            if (BuildConfig.DEBUG)
 | 
			
		||||
                Value.CANARY_DEBUG_CHANNEL
 | 
			
		||||
            else
 | 
			
		||||
                Value.CANARY_CHANNEL
 | 
			
		||||
        }
 | 
			
		||||
        else Value.DEFAULT_CHANNEL
 | 
			
		||||
 | 
			
		||||
    var downloadPath by preference(Key.DOWNLOAD_PATH, Environment.DIRECTORY_DOWNLOADS)
 | 
			
		||||
@@ -123,6 +129,7 @@ object Config : PreferenceModel, DBConfig {
 | 
			
		||||
    var suMultiuserMode by dbSettings(Key.SU_MULTIUSER_MODE, Value.MULTIUSER_MODE_OWNER_ONLY)
 | 
			
		||||
    var suFingerprint by dbSettings(Key.SU_FINGERPRINT, false)
 | 
			
		||||
    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() =
 | 
			
		||||
@@ -131,9 +138,6 @@ object Config : PreferenceModel, DBConfig {
 | 
			
		||||
    fun initialize() = prefs.edit {
 | 
			
		||||
        parsePrefs(this)
 | 
			
		||||
 | 
			
		||||
        if (!prefs.contains(Key.UPDATE_CHANNEL))
 | 
			
		||||
            putString(Key.UPDATE_CHANNEL, defaultChannel.toString())
 | 
			
		||||
 | 
			
		||||
        // Get actual state
 | 
			
		||||
        putBoolean(Key.COREONLY, Const.MAGISK_DISABLE_FILE.exists())
 | 
			
		||||
 | 
			
		||||
@@ -142,6 +146,9 @@ object Config : PreferenceModel, DBConfig {
 | 
			
		||||
        putString(Key.SU_MNT_NS, suMntNamespaceMode.toString())
 | 
			
		||||
        putString(Key.SU_MULTIUSER_MODE, suMultiuserMode.toString())
 | 
			
		||||
        putBoolean(Key.SU_FINGERPRINT, FingerprintHelper.useFingerprint())
 | 
			
		||||
    }.also {
 | 
			
		||||
        if (!prefs.contains(Key.UPDATE_CHANNEL))
 | 
			
		||||
            prefs.edit().putString(Key.UPDATE_CHANNEL, defaultChannel.toString()).apply()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun parsePrefs(editor: SharedPreferences.Editor) = editor.apply {
 | 
			
		||||
@@ -205,4 +212,4 @@ object Config : PreferenceModel, DBConfig {
 | 
			
		||||
        Shell.su("cat $xml > /data/adb/${Const.MANAGER_CONFIGS}").exec()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -22,8 +22,9 @@ object Const {
 | 
			
		||||
    const val MANAGER_CONFIGS = ".tmp.magisk.config"
 | 
			
		||||
    val USER_ID = Process.myUid() / 100000
 | 
			
		||||
 | 
			
		||||
    object MagiskVersion {
 | 
			
		||||
    object Version {
 | 
			
		||||
        const val MIN_SUPPORT = 18000
 | 
			
		||||
        const val CONNECT_MODE = 20002
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    object ID {
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										199
									
								
								app/src/main/java/com/topjohnwu/magisk/Hacks.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										199
									
								
								app/src/main/java/com/topjohnwu/magisk/Hacks.kt
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,199 @@
 | 
			
		||||
@file:Suppress("DEPRECATION")
 | 
			
		||||
 | 
			
		||||
package com.topjohnwu.magisk
 | 
			
		||||
 | 
			
		||||
import android.annotation.SuppressLint
 | 
			
		||||
import android.app.job.JobInfo
 | 
			
		||||
import android.app.job.JobScheduler
 | 
			
		||||
import android.app.job.JobWorkItem
 | 
			
		||||
import android.content.ComponentName
 | 
			
		||||
import android.content.Context
 | 
			
		||||
import android.content.ContextWrapper
 | 
			
		||||
import android.content.Intent
 | 
			
		||||
import android.content.res.AssetManager
 | 
			
		||||
import android.content.res.Configuration
 | 
			
		||||
import android.content.res.Resources
 | 
			
		||||
import androidx.annotation.RequiresApi
 | 
			
		||||
import androidx.annotation.StringRes
 | 
			
		||||
import com.topjohnwu.magisk.extensions.langTagToLocale
 | 
			
		||||
import com.topjohnwu.magisk.model.download.DownloadService
 | 
			
		||||
import com.topjohnwu.magisk.model.receiver.GeneralReceiver
 | 
			
		||||
import com.topjohnwu.magisk.model.update.UpdateCheckService
 | 
			
		||||
import com.topjohnwu.magisk.ui.MainActivity
 | 
			
		||||
import com.topjohnwu.magisk.ui.SplashActivity
 | 
			
		||||
import com.topjohnwu.magisk.ui.flash.FlashActivity
 | 
			
		||||
import com.topjohnwu.magisk.ui.surequest.SuRequestActivity
 | 
			
		||||
import com.topjohnwu.magisk.utils.currentLocale
 | 
			
		||||
import com.topjohnwu.magisk.utils.defaultLocale
 | 
			
		||||
import com.topjohnwu.magisk.utils.refreshLocale
 | 
			
		||||
import com.topjohnwu.magisk.utils.updateConfig
 | 
			
		||||
import java.util.*
 | 
			
		||||
 | 
			
		||||
fun AssetManager.addAssetPath(path: String) {
 | 
			
		||||
    DynAPK.addAssetPath(this, path)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fun Context.wrap(global: Boolean = true): Context
 | 
			
		||||
        = if (global) GlobalResContext(this) else ResContext(this)
 | 
			
		||||
 | 
			
		||||
fun Context.wrapJob(): Context = object : GlobalResContext(this) {
 | 
			
		||||
 | 
			
		||||
    override fun getApplicationContext(): Context {
 | 
			
		||||
        return this
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @SuppressLint("NewApi")
 | 
			
		||||
    override fun getSystemService(name: String): Any? {
 | 
			
		||||
        return if (!isRunningAsStub) super.getSystemService(name) else
 | 
			
		||||
            when (name) {
 | 
			
		||||
                Context.JOB_SCHEDULER_SERVICE ->
 | 
			
		||||
                    JobSchedulerWrapper(super.getSystemService(name) as JobScheduler)
 | 
			
		||||
                else -> super.getSystemService(name)
 | 
			
		||||
            }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fun Class<*>.cmp(pkg: String = BuildConfig.APPLICATION_ID): ComponentName {
 | 
			
		||||
    val name = ClassMap[this].name
 | 
			
		||||
    return ComponentName(pkg, Info.stub?.componentMap?.get(name) ?: name)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fun Context.intent(c: Class<*>): Intent {
 | 
			
		||||
    val cls = ClassMap[c]
 | 
			
		||||
    return Info.stub?.let {
 | 
			
		||||
        val className = it.componentMap.getOrElse(cls.name) { cls.name }
 | 
			
		||||
        Intent().setComponent(ComponentName(this, className))
 | 
			
		||||
    } ?: Intent(this, cls)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
private open class GlobalResContext(base: Context) : ContextWrapper(base) {
 | 
			
		||||
    open val mRes: Resources get() = ResourceMgr.resource
 | 
			
		||||
    private val loader by lazy { javaClass.classLoader!! }
 | 
			
		||||
 | 
			
		||||
    override fun getResources(): Resources {
 | 
			
		||||
        return mRes
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun getClassLoader(): ClassLoader {
 | 
			
		||||
        return loader
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun createConfigurationContext(config: Configuration): Context {
 | 
			
		||||
        return ResContext(super.createConfigurationContext(config))
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
private class ResContext(base: Context) : GlobalResContext(base) {
 | 
			
		||||
    override val mRes by lazy { base.resources.patch() }
 | 
			
		||||
 | 
			
		||||
    private fun Resources.patch(): Resources {
 | 
			
		||||
        updateConfig()
 | 
			
		||||
        if (isRunningAsStub)
 | 
			
		||||
            assets.addAssetPath(ResourceMgr.resApk)
 | 
			
		||||
        return this
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
object ResourceMgr {
 | 
			
		||||
 | 
			
		||||
    lateinit var resource: Resources
 | 
			
		||||
    lateinit var resApk: String
 | 
			
		||||
 | 
			
		||||
    fun init(context: Context) {
 | 
			
		||||
        resource = context.resources
 | 
			
		||||
        refreshLocale()
 | 
			
		||||
        if (isRunningAsStub) {
 | 
			
		||||
            resApk = DynAPK.current(context).path
 | 
			
		||||
            resource.assets.addAssetPath(resApk)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@RequiresApi(api = 28)
 | 
			
		||||
private class JobSchedulerWrapper(private val base: JobScheduler) : JobScheduler() {
 | 
			
		||||
 | 
			
		||||
    override fun schedule(job: JobInfo): Int {
 | 
			
		||||
        return base.schedule(job.patch())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun enqueue(job: JobInfo, work: JobWorkItem): Int {
 | 
			
		||||
        return base.enqueue(job.patch(), work)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun cancel(jobId: Int) {
 | 
			
		||||
        base.cancel(jobId)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun cancelAll() {
 | 
			
		||||
        base.cancelAll()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun getAllPendingJobs(): List<JobInfo> {
 | 
			
		||||
        return base.allPendingJobs
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun getPendingJob(jobId: Int): JobInfo? {
 | 
			
		||||
        return base.getPendingJob(jobId)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun JobInfo.patch(): JobInfo {
 | 
			
		||||
        // We need to patch the component of JobInfo to access WorkManager SystemJobService
 | 
			
		||||
 | 
			
		||||
        val name = service.className
 | 
			
		||||
        val component = ComponentName(
 | 
			
		||||
            service.packageName,
 | 
			
		||||
            Info.stub!!.componentMap[name] ?: name)
 | 
			
		||||
 | 
			
		||||
        // Clone the JobInfo except component
 | 
			
		||||
        val builder = JobInfo.Builder(id, component)
 | 
			
		||||
            .setExtras(extras)
 | 
			
		||||
            .setTransientExtras(transientExtras)
 | 
			
		||||
            .setClipData(clipData, clipGrantFlags)
 | 
			
		||||
            .setRequiredNetwork(requiredNetwork)
 | 
			
		||||
            .setEstimatedNetworkBytes(estimatedNetworkDownloadBytes, estimatedNetworkUploadBytes)
 | 
			
		||||
            .setRequiresCharging(isRequireCharging)
 | 
			
		||||
            .setRequiresDeviceIdle(isRequireDeviceIdle)
 | 
			
		||||
            .setRequiresBatteryNotLow(isRequireBatteryNotLow)
 | 
			
		||||
            .setRequiresStorageNotLow(isRequireStorageNotLow)
 | 
			
		||||
            .also {
 | 
			
		||||
                triggerContentUris?.let { uris ->
 | 
			
		||||
                    for (uri in uris)
 | 
			
		||||
                        it.addTriggerContentUri(uri)
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            .setTriggerContentUpdateDelay(triggerContentUpdateDelay)
 | 
			
		||||
            .setTriggerContentMaxDelay(triggerContentMaxDelay)
 | 
			
		||||
            .setImportantWhileForeground(isImportantWhileForeground)
 | 
			
		||||
            .setPrefetch(isPrefetch)
 | 
			
		||||
            .setPersisted(isPersisted)
 | 
			
		||||
 | 
			
		||||
        if (isPeriodic) {
 | 
			
		||||
            builder.setPeriodic(intervalMillis, flexMillis)
 | 
			
		||||
        } else {
 | 
			
		||||
            if (minLatencyMillis > 0)
 | 
			
		||||
                builder.setMinimumLatency(minLatencyMillis)
 | 
			
		||||
            if (maxExecutionDelayMillis > 0)
 | 
			
		||||
                builder.setOverrideDeadline(maxExecutionDelayMillis)
 | 
			
		||||
        }
 | 
			
		||||
        if (!isRequireDeviceIdle)
 | 
			
		||||
            builder.setBackoffCriteria(initialBackoffMillis, backoffPolicy)
 | 
			
		||||
 | 
			
		||||
        return builder.build()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
object ClassMap {
 | 
			
		||||
 | 
			
		||||
    private val map = mapOf(
 | 
			
		||||
        App::class.java to a.e::class.java,
 | 
			
		||||
        MainActivity::class.java to a.b::class.java,
 | 
			
		||||
        SplashActivity::class.java to a.c::class.java,
 | 
			
		||||
        FlashActivity::class.java to a.f::class.java,
 | 
			
		||||
        UpdateCheckService::class.java to a.g::class.java,
 | 
			
		||||
        GeneralReceiver::class.java to a.h::class.java,
 | 
			
		||||
        DownloadService::class.java to a.j::class.java,
 | 
			
		||||
        SuRequestActivity::class.java to a.m::class.java
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    operator fun get(c: Class<*>) = map.getOrElse(c) { throw IllegalArgumentException() }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,26 +1,62 @@
 | 
			
		||||
package com.topjohnwu.magisk
 | 
			
		||||
 | 
			
		||||
import com.github.pwittchen.reactivenetwork.library.rx2.ReactiveNetwork
 | 
			
		||||
import com.topjohnwu.magisk.extensions.get
 | 
			
		||||
import com.topjohnwu.magisk.extensions.subscribeK
 | 
			
		||||
import com.topjohnwu.magisk.model.entity.UpdateInfo
 | 
			
		||||
import com.topjohnwu.magisk.utils.CachedValue
 | 
			
		||||
import com.topjohnwu.magisk.utils.KObservableField
 | 
			
		||||
import com.topjohnwu.superuser.Shell
 | 
			
		||||
import com.topjohnwu.superuser.ShellUtils
 | 
			
		||||
 | 
			
		||||
val isRunningAsStub get() = Info.stub != null
 | 
			
		||||
 | 
			
		||||
object Info {
 | 
			
		||||
 | 
			
		||||
    var magiskVersionCode = -1
 | 
			
		||||
    val envRef = CachedValue { loadState() }
 | 
			
		||||
 | 
			
		||||
    var magiskVersionString = ""
 | 
			
		||||
 | 
			
		||||
    var remote = UpdateInfo()
 | 
			
		||||
    val env by envRef              // Local
 | 
			
		||||
    var remote = UpdateInfo()      // Remote
 | 
			
		||||
    var stub: DynAPK.Data? = null  // Stub
 | 
			
		||||
 | 
			
		||||
    var keepVerity = false
 | 
			
		||||
    var keepEnc = false
 | 
			
		||||
    var recovery = false
 | 
			
		||||
 | 
			
		||||
    fun loadMagiskInfo() {
 | 
			
		||||
        runCatching {
 | 
			
		||||
            magiskVersionString = ShellUtils.fastCmd("magisk -v").split(":".toRegex())[0]
 | 
			
		||||
            magiskVersionCode = ShellUtils.fastCmd("magisk -V").toInt()
 | 
			
		||||
            Config.magiskHide = Shell.su("magiskhide --status").exec().isSuccess
 | 
			
		||||
    val isConnected by lazy {
 | 
			
		||||
        KObservableField(false).also { field ->
 | 
			
		||||
            ReactiveNetwork.observeNetworkConnectivity(get())
 | 
			
		||||
                .subscribeK {
 | 
			
		||||
                    field.value = it.available()
 | 
			
		||||
                }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun loadState() = runCatching {
 | 
			
		||||
        val str = ShellUtils.fastCmd("magisk -v").split(":".toRegex())[0]
 | 
			
		||||
        val code = ShellUtils.fastCmd("magisk -V").toInt()
 | 
			
		||||
        val hide = Shell.su("magiskhide --status").exec().isSuccess
 | 
			
		||||
        var mode = -1
 | 
			
		||||
        if (code >= Const.Version.CONNECT_MODE) {
 | 
			
		||||
            mode = Shell.su("magisk --connect-mode").exec().code
 | 
			
		||||
            if (mode == 0) {
 | 
			
		||||
                // Manually trigger broadcast test
 | 
			
		||||
                Shell.su("magisk --broadcast-test").exec()
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        Env(code, str, hide, mode)
 | 
			
		||||
    }.getOrElse { Env() }
 | 
			
		||||
 | 
			
		||||
    class Env(
 | 
			
		||||
        val magiskVersionCode: Int = -1,
 | 
			
		||||
        val magiskVersionString: String = "",
 | 
			
		||||
        hide: Boolean = false,
 | 
			
		||||
        var connectionMode: Int = -1
 | 
			
		||||
    ) {
 | 
			
		||||
        val magiskHide get() = Config.magiskHide
 | 
			
		||||
 | 
			
		||||
        init {
 | 
			
		||||
            Config.magiskHide = hide
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -15,12 +15,13 @@ import androidx.databinding.DataBindingUtil
 | 
			
		||||
import androidx.databinding.ViewDataBinding
 | 
			
		||||
import com.topjohnwu.magisk.BR
 | 
			
		||||
import com.topjohnwu.magisk.Config
 | 
			
		||||
import com.topjohnwu.magisk.R
 | 
			
		||||
import com.topjohnwu.magisk.base.viewmodel.BaseViewModel
 | 
			
		||||
import com.topjohnwu.magisk.extensions.set
 | 
			
		||||
import com.topjohnwu.magisk.model.events.EventHandler
 | 
			
		||||
import com.topjohnwu.magisk.model.permissions.PermissionRequestBuilder
 | 
			
		||||
import com.topjohnwu.magisk.utils.LocaleManager
 | 
			
		||||
import com.topjohnwu.magisk.utils.currentLocale
 | 
			
		||||
import com.topjohnwu.magisk.wrap
 | 
			
		||||
import kotlin.random.Random
 | 
			
		||||
 | 
			
		||||
typealias RequestCallback = BaseActivity<*, *>.(Int, Intent?) -> Unit
 | 
			
		||||
@@ -31,9 +32,8 @@ abstract class BaseActivity<ViewModel : BaseViewModel, Binding : ViewDataBinding
 | 
			
		||||
    protected lateinit var binding: Binding
 | 
			
		||||
    protected abstract val layoutRes: Int
 | 
			
		||||
    protected abstract val viewModel: ViewModel
 | 
			
		||||
    protected open val themeRes: Int = R.style.MagiskTheme
 | 
			
		||||
    protected open val snackbarView get() = binding.root
 | 
			
		||||
    protected open val navHostId: Int = 0
 | 
			
		||||
    protected open val defaultPosition: Int = 0
 | 
			
		||||
 | 
			
		||||
    private val resultCallbacks by lazy { SparseArrayCompat<RequestCallback>() }
 | 
			
		||||
 | 
			
		||||
@@ -53,10 +53,11 @@ abstract class BaseActivity<ViewModel : BaseViewModel, Binding : ViewDataBinding
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun attachBaseContext(base: Context) {
 | 
			
		||||
        super.attachBaseContext(LocaleManager.getLocaleContext(base))
 | 
			
		||||
        super.attachBaseContext(base.wrap(false))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onCreate(savedInstanceState: Bundle?) {
 | 
			
		||||
        setTheme(themeRes)
 | 
			
		||||
        super.onCreate(savedInstanceState)
 | 
			
		||||
 | 
			
		||||
        viewModel.viewEvents.observe(this, viewEventObserver)
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										17
									
								
								app/src/main/java/com/topjohnwu/magisk/base/BaseReceiver.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								app/src/main/java/com/topjohnwu/magisk/base/BaseReceiver.kt
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,17 @@
 | 
			
		||||
package com.topjohnwu.magisk.base
 | 
			
		||||
 | 
			
		||||
import android.content.BroadcastReceiver
 | 
			
		||||
import android.content.Context
 | 
			
		||||
import android.content.ContextWrapper
 | 
			
		||||
import android.content.Intent
 | 
			
		||||
import com.topjohnwu.magisk.wrap
 | 
			
		||||
import org.koin.core.KoinComponent
 | 
			
		||||
 | 
			
		||||
abstract class BaseReceiver : BroadcastReceiver(), KoinComponent {
 | 
			
		||||
 | 
			
		||||
    final override fun onReceive(context: Context, intent: Intent?) {
 | 
			
		||||
        onReceive(context.wrap() as ContextWrapper, intent)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    abstract fun onReceive(context: ContextWrapper, intent: Intent?)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										12
									
								
								app/src/main/java/com/topjohnwu/magisk/base/BaseService.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								app/src/main/java/com/topjohnwu/magisk/base/BaseService.kt
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,12 @@
 | 
			
		||||
package com.topjohnwu.magisk.base
 | 
			
		||||
 | 
			
		||||
import android.app.Service
 | 
			
		||||
import android.content.Context
 | 
			
		||||
import com.topjohnwu.magisk.wrap
 | 
			
		||||
import org.koin.core.KoinComponent
 | 
			
		||||
 | 
			
		||||
abstract class BaseService : Service(), KoinComponent {
 | 
			
		||||
    override fun attachBaseContext(base: Context) {
 | 
			
		||||
        super.attachBaseContext(base.wrap())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,28 +1,24 @@
 | 
			
		||||
package com.topjohnwu.magisk.base.viewmodel
 | 
			
		||||
 | 
			
		||||
import android.app.Activity
 | 
			
		||||
import com.github.pwittchen.reactivenetwork.library.rx2.ReactiveNetwork
 | 
			
		||||
import com.topjohnwu.magisk.extensions.doOnSubscribeUi
 | 
			
		||||
import com.topjohnwu.magisk.extensions.get
 | 
			
		||||
import com.topjohnwu.magisk.extensions.subscribeK
 | 
			
		||||
import com.topjohnwu.magisk.model.events.BackPressEvent
 | 
			
		||||
import com.topjohnwu.magisk.model.events.PermissionEvent
 | 
			
		||||
import com.topjohnwu.magisk.model.events.ViewActionEvent
 | 
			
		||||
import com.topjohnwu.magisk.utils.KObservableField
 | 
			
		||||
import io.reactivex.Observable
 | 
			
		||||
import io.reactivex.subjects.PublishSubject
 | 
			
		||||
import com.topjohnwu.magisk.Info.isConnected as gIsConnected
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
abstract class BaseViewModel(
 | 
			
		||||
    initialState: State = State.LOADING
 | 
			
		||||
) : LoadingViewModel(initialState) {
 | 
			
		||||
 | 
			
		||||
    val isConnected = KObservableField(false)
 | 
			
		||||
 | 
			
		||||
    init {
 | 
			
		||||
        ReactiveNetwork.observeNetworkConnectivity(get())
 | 
			
		||||
            .subscribeK { isConnected.value = it.available() }
 | 
			
		||||
            .add()
 | 
			
		||||
    val isConnected = object : KObservableField<Boolean>(gIsConnected.value, gIsConnected) {
 | 
			
		||||
        override fun get(): Boolean {
 | 
			
		||||
            return gIsConnected.value
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun withView(action: Activity.() -> Unit) {
 | 
			
		||||
 
 | 
			
		||||
@@ -29,7 +29,7 @@ class MagiskRepository(
 | 
			
		||||
        else -> throw IllegalArgumentException()
 | 
			
		||||
    }.flatMap {
 | 
			
		||||
        // If remote version is lower than current installed, try switching to beta
 | 
			
		||||
        if (it.magisk.versionCode < Info.magiskVersionCode
 | 
			
		||||
        if (it.magisk.versionCode < Info.env.magiskVersionCode
 | 
			
		||||
                && Config.updateChannel == Config.Value.DEFAULT_CHANNEL) {
 | 
			
		||||
            Config.updateChannel = Config.Value.BETA_CHANNEL
 | 
			
		||||
            apiRaw.fetchBetaUpdate()
 | 
			
		||||
@@ -74,4 +74,4 @@ class MagiskRepository(
 | 
			
		||||
        ) }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,8 @@
 | 
			
		||||
package com.topjohnwu.magisk.extensions
 | 
			
		||||
 | 
			
		||||
import android.content.ComponentName
 | 
			
		||||
import android.content.Context
 | 
			
		||||
import android.content.ContextWrapper
 | 
			
		||||
import android.content.Intent
 | 
			
		||||
import android.content.pm.ApplicationInfo
 | 
			
		||||
import android.content.pm.ComponentInfo
 | 
			
		||||
@@ -10,19 +12,30 @@ import android.content.pm.PackageManager.*
 | 
			
		||||
import android.content.res.Configuration
 | 
			
		||||
import android.content.res.Resources
 | 
			
		||||
import android.database.Cursor
 | 
			
		||||
import android.graphics.Bitmap
 | 
			
		||||
import android.graphics.Canvas
 | 
			
		||||
import android.graphics.drawable.AdaptiveIconDrawable
 | 
			
		||||
import android.graphics.drawable.BitmapDrawable
 | 
			
		||||
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.view.View
 | 
			
		||||
import androidx.annotation.ColorRes
 | 
			
		||||
import androidx.annotation.DrawableRes
 | 
			
		||||
import androidx.appcompat.content.res.AppCompatResources
 | 
			
		||||
import androidx.core.content.ContextCompat
 | 
			
		||||
import androidx.core.net.toUri
 | 
			
		||||
import com.topjohnwu.magisk.utils.FileProvider
 | 
			
		||||
import com.topjohnwu.magisk.Const
 | 
			
		||||
import com.topjohnwu.magisk.FileProvider
 | 
			
		||||
import com.topjohnwu.magisk.utils.DynamicClassLoader
 | 
			
		||||
import com.topjohnwu.magisk.utils.Utils
 | 
			
		||||
import com.topjohnwu.magisk.utils.currentLocale
 | 
			
		||||
import com.topjohnwu.superuser.Shell
 | 
			
		||||
import java.io.File
 | 
			
		||||
import java.io.FileNotFoundException
 | 
			
		||||
import java.lang.reflect.Array as JArray
 | 
			
		||||
 | 
			
		||||
val packageName: String get() = get<Context>().packageName
 | 
			
		||||
 | 
			
		||||
@@ -91,8 +104,129 @@ 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)
 | 
			
		||||
        return drawable.bitmap
 | 
			
		||||
    if (SDK_INT >= 26 && drawable is AdaptiveIconDrawable) {
 | 
			
		||||
        drawable = LayerDrawable(arrayOf(drawable.background, drawable.foreground))
 | 
			
		||||
    }
 | 
			
		||||
    val bitmap = Bitmap.createBitmap(
 | 
			
		||||
        drawable.intrinsicWidth, drawable.intrinsicHeight,
 | 
			
		||||
        Bitmap.Config.ARGB_8888
 | 
			
		||||
    )
 | 
			
		||||
    val canvas = Canvas(bitmap)
 | 
			
		||||
    drawable.setBounds(0, 0, canvas.width, canvas.height)
 | 
			
		||||
    drawable.draw(canvas)
 | 
			
		||||
    return bitmap
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fun Intent.startActivity(context: Context) = context.startActivity(this)
 | 
			
		||||
 | 
			
		||||
fun Intent.startActivityWithRoot() {
 | 
			
		||||
    val args = mutableListOf("am", "start", "--user", Const.USER_ID.toString())
 | 
			
		||||
    val cmd = toCommand(args).joinToString(" ")
 | 
			
		||||
    Shell.su(cmd).submit()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fun Intent.toCommand(args: MutableList<String> = mutableListOf()): MutableList<String> {
 | 
			
		||||
    action?.also {
 | 
			
		||||
        args.add("-a")
 | 
			
		||||
        args.add(it)
 | 
			
		||||
    }
 | 
			
		||||
    component?.also {
 | 
			
		||||
        args.add("-n")
 | 
			
		||||
        args.add(it.flattenToString())
 | 
			
		||||
    }
 | 
			
		||||
    data?.also {
 | 
			
		||||
        args.add("-d")
 | 
			
		||||
        args.add(it.toString())
 | 
			
		||||
    }
 | 
			
		||||
    categories?.also {
 | 
			
		||||
        for (cat in it) {
 | 
			
		||||
            args.add("-c")
 | 
			
		||||
            args.add(cat)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    type?.also {
 | 
			
		||||
        args.add("-t")
 | 
			
		||||
        args.add(it)
 | 
			
		||||
    }
 | 
			
		||||
    extras?.also {
 | 
			
		||||
        loop@ for (key in it.keySet()) {
 | 
			
		||||
            val v = it[key] ?: continue
 | 
			
		||||
            var value: Any = v
 | 
			
		||||
            val arg: String
 | 
			
		||||
            when {
 | 
			
		||||
                v is String -> arg = "--es"
 | 
			
		||||
                v is Boolean -> arg = "--ez"
 | 
			
		||||
                v is Int -> arg = "--ei"
 | 
			
		||||
                v is Long -> arg = "--el"
 | 
			
		||||
                v is Float -> arg = "--ef"
 | 
			
		||||
                v is Uri -> arg = "--eu"
 | 
			
		||||
                v is ComponentName -> {
 | 
			
		||||
                    arg = "--ecn"
 | 
			
		||||
                    value = v.flattenToString()
 | 
			
		||||
                }
 | 
			
		||||
                v is List<*> -> {
 | 
			
		||||
                    if (v.isEmpty())
 | 
			
		||||
                        continue@loop
 | 
			
		||||
 | 
			
		||||
                    arg = if (v[0] is Int)
 | 
			
		||||
                        "--eial"
 | 
			
		||||
                    else if (v[0] is Long)
 | 
			
		||||
                        "--elal"
 | 
			
		||||
                    else if (v[0] is Float)
 | 
			
		||||
                        "--efal"
 | 
			
		||||
                    else if (v[0] is String)
 | 
			
		||||
                        "--esal"
 | 
			
		||||
                    else
 | 
			
		||||
                        continue@loop  /* Unsupported */
 | 
			
		||||
 | 
			
		||||
                    val sb = StringBuilder()
 | 
			
		||||
                    for (o in v) {
 | 
			
		||||
                        sb.append(o.toString().replace(",", "\\,"))
 | 
			
		||||
                        sb.append(',')
 | 
			
		||||
                    }
 | 
			
		||||
                    // Remove trailing comma
 | 
			
		||||
                    sb.deleteCharAt(sb.length - 1)
 | 
			
		||||
                    value = sb
 | 
			
		||||
                }
 | 
			
		||||
                v.javaClass.isArray -> {
 | 
			
		||||
                    arg = if (v is IntArray)
 | 
			
		||||
                        "--eia"
 | 
			
		||||
                    else if (v is LongArray)
 | 
			
		||||
                        "--ela"
 | 
			
		||||
                    else if (v is FloatArray)
 | 
			
		||||
                        "--efa"
 | 
			
		||||
                    else if (v is Array<*> && v.isArrayOf<String>())
 | 
			
		||||
                        "--esa"
 | 
			
		||||
                    else
 | 
			
		||||
                        continue@loop  /* Unsupported */
 | 
			
		||||
 | 
			
		||||
                    val sb = StringBuilder()
 | 
			
		||||
                    val len = JArray.getLength(v)
 | 
			
		||||
                    for (i in 0 until len) {
 | 
			
		||||
                        sb.append(JArray.get(v, i)!!.toString().replace(",", "\\,"))
 | 
			
		||||
                        sb.append(',')
 | 
			
		||||
                    }
 | 
			
		||||
                    // Remove trailing comma
 | 
			
		||||
                    sb.deleteCharAt(sb.length - 1)
 | 
			
		||||
                    value = sb
 | 
			
		||||
                }
 | 
			
		||||
                else -> continue@loop
 | 
			
		||||
            }  /* Unsupported */
 | 
			
		||||
 | 
			
		||||
            args.add(arg)
 | 
			
		||||
            args.add(key)
 | 
			
		||||
            args.add(value.toString())
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    args.add("-f")
 | 
			
		||||
    args.add(flags.toString())
 | 
			
		||||
    return args
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fun File.provide(context: Context = get()): Uri {
 | 
			
		||||
    return FileProvider.getUriForFile(context, context.packageName + ".provider", this)
 | 
			
		||||
}
 | 
			
		||||
@@ -157,3 +291,18 @@ fun Context.startEndToLeftRight(start: Int, end: Int): Pair<Int, Int> {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fun Context.openUrl(url: String) = Utils.openLink(this, url.toUri())
 | 
			
		||||
 | 
			
		||||
@Suppress("FunctionName")
 | 
			
		||||
inline fun <reified T> T.DynamicClassLoader(apk: File)
 | 
			
		||||
        = DynamicClassLoader(apk, T::class.java.classLoader)
 | 
			
		||||
 | 
			
		||||
fun Context.unwrap() : Context {
 | 
			
		||||
    var context = this
 | 
			
		||||
    while (true) {
 | 
			
		||||
        if (context is ContextWrapper)
 | 
			
		||||
            context = context.baseContext
 | 
			
		||||
        else
 | 
			
		||||
            break
 | 
			
		||||
    }
 | 
			
		||||
    return context
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -25,3 +25,5 @@ fun String.trimEmptyToNull(): String? = if (isBlank()) null else this
 | 
			
		||||
fun String.legalFilename() = replace(" ", "_").replace("'", "").replace("\"", "")
 | 
			
		||||
        .replace("$", "").replace("`", "").replace("*", "").replace("/", "_")
 | 
			
		||||
        .replace("#", "").replace("@", "").replace("\\", "_")
 | 
			
		||||
 | 
			
		||||
fun String.isEmptyInternal() = isNullOrBlank()
 | 
			
		||||
@@ -1,17 +1,17 @@
 | 
			
		||||
package com.topjohnwu.magisk.model.download
 | 
			
		||||
 | 
			
		||||
import android.annotation.SuppressLint
 | 
			
		||||
import android.app.Notification
 | 
			
		||||
import android.app.PendingIntent
 | 
			
		||||
import android.content.Context
 | 
			
		||||
import android.content.Intent
 | 
			
		||||
import android.os.Build
 | 
			
		||||
import android.webkit.MimeTypeMap
 | 
			
		||||
import androidx.core.app.NotificationCompat
 | 
			
		||||
import com.topjohnwu.magisk.ClassMap
 | 
			
		||||
import com.topjohnwu.magisk.R
 | 
			
		||||
import com.topjohnwu.magisk.extensions.chooser
 | 
			
		||||
import com.topjohnwu.magisk.extensions.exists
 | 
			
		||||
import com.topjohnwu.magisk.extensions.provide
 | 
			
		||||
import com.topjohnwu.magisk.intent
 | 
			
		||||
import com.topjohnwu.magisk.model.entity.internal.Configuration.*
 | 
			
		||||
import com.topjohnwu.magisk.model.entity.internal.Configuration.Flash.Secondary
 | 
			
		||||
import com.topjohnwu.magisk.model.entity.internal.DownloadSubject
 | 
			
		||||
@@ -63,20 +63,20 @@ open class DownloadService : RemoteFileService() {
 | 
			
		||||
        remove(id)
 | 
			
		||||
        when (subject.configuration)  {
 | 
			
		||||
            is APK.Upgrade -> APKInstall.install(this, subject.file)
 | 
			
		||||
            else -> Unit
 | 
			
		||||
            is APK.Restore -> Unit
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // ---
 | 
			
		||||
 | 
			
		||||
    override fun NotificationCompat.Builder.addActions(subject: DownloadSubject)
 | 
			
		||||
    override fun Notification.Builder.addActions(subject: DownloadSubject)
 | 
			
		||||
    = when (subject) {
 | 
			
		||||
        is Magisk -> addActionsInternal(subject)
 | 
			
		||||
        is Module -> addActionsInternal(subject)
 | 
			
		||||
        is Manager -> addActionsInternal(subject)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun NotificationCompat.Builder.addActionsInternal(subject: Magisk)
 | 
			
		||||
    private fun Notification.Builder.addActionsInternal(subject: Magisk)
 | 
			
		||||
    = when (val conf = subject.configuration) {
 | 
			
		||||
        Download -> this.apply {
 | 
			
		||||
            fileIntent(subject.file.parentFile!!)
 | 
			
		||||
@@ -92,7 +92,7 @@ open class DownloadService : RemoteFileService() {
 | 
			
		||||
        else -> this
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun NotificationCompat.Builder.addActionsInternal(subject: Module)
 | 
			
		||||
    private fun Notification.Builder.addActionsInternal(subject: Module)
 | 
			
		||||
    = when (subject.configuration) {
 | 
			
		||||
        Download -> this.apply {
 | 
			
		||||
            fileIntent(subject.file.parentFile!!)
 | 
			
		||||
@@ -106,19 +106,19 @@ open class DownloadService : RemoteFileService() {
 | 
			
		||||
        else -> this
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun NotificationCompat.Builder.addActionsInternal(subject: Manager)
 | 
			
		||||
    private fun Notification.Builder.addActionsInternal(subject: Manager)
 | 
			
		||||
    = when (subject.configuration) {
 | 
			
		||||
        APK.Upgrade -> setContentIntent(APKInstall.installIntent(context, subject.file))
 | 
			
		||||
        else -> this
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Suppress("ReplaceSingleLineLet")
 | 
			
		||||
    private fun NotificationCompat.Builder.setContentIntent(intent: Intent) =
 | 
			
		||||
    private fun Notification.Builder.setContentIntent(intent: Intent) =
 | 
			
		||||
        PendingIntent.getActivity(context, nextInt(), intent, PendingIntent.FLAG_ONE_SHOT)
 | 
			
		||||
            .let { setContentIntent(it) }
 | 
			
		||||
 | 
			
		||||
    @Suppress("ReplaceSingleLineLet")
 | 
			
		||||
    private fun NotificationCompat.Builder.addAction(icon: Int, title: Int, intent: Intent) =
 | 
			
		||||
    private fun Notification.Builder.addAction(icon: Int, title: Int, intent: Intent) =
 | 
			
		||||
        PendingIntent.getActivity(context, nextInt(), intent, PendingIntent.FLAG_ONE_SHOT)
 | 
			
		||||
            .let { addAction(icon, getString(title), it) }
 | 
			
		||||
 | 
			
		||||
@@ -140,8 +140,7 @@ open class DownloadService : RemoteFileService() {
 | 
			
		||||
        inline operator fun invoke(context: Context, argBuilder: Builder.() -> Unit) {
 | 
			
		||||
            val app = context.applicationContext
 | 
			
		||||
            val builder = Builder().apply(argBuilder)
 | 
			
		||||
            val intent = Intent(app, ClassMap[DownloadService::class.java])
 | 
			
		||||
                .putExtra(ARG_URL, builder.subject)
 | 
			
		||||
            val intent = app.intent(DownloadService::class.java).putExtra(ARG_URL, builder.subject)
 | 
			
		||||
 | 
			
		||||
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
 | 
			
		||||
                app.startForegroundService(intent)
 | 
			
		||||
@@ -152,4 +151,4 @@ open class DownloadService : RemoteFileService() {
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,43 +1,47 @@
 | 
			
		||||
package com.topjohnwu.magisk.model.download
 | 
			
		||||
 | 
			
		||||
import android.content.ComponentName
 | 
			
		||||
import com.topjohnwu.magisk.BuildConfig
 | 
			
		||||
import com.topjohnwu.magisk.ClassMap
 | 
			
		||||
import com.topjohnwu.magisk.Config
 | 
			
		||||
import com.topjohnwu.magisk.R
 | 
			
		||||
import com.topjohnwu.magisk.*
 | 
			
		||||
import com.topjohnwu.magisk.extensions.writeTo
 | 
			
		||||
import com.topjohnwu.magisk.model.entity.internal.Configuration.APK.Restore
 | 
			
		||||
import com.topjohnwu.magisk.model.entity.internal.Configuration.APK.Upgrade
 | 
			
		||||
import com.topjohnwu.magisk.model.entity.internal.DownloadSubject
 | 
			
		||||
import com.topjohnwu.magisk.ui.SplashActivity
 | 
			
		||||
import com.topjohnwu.magisk.utils.DynamicClassLoader
 | 
			
		||||
import com.topjohnwu.magisk.utils.PatchAPK
 | 
			
		||||
import com.topjohnwu.magisk.utils.RootUtils
 | 
			
		||||
import com.topjohnwu.superuser.Shell
 | 
			
		||||
import timber.log.Timber
 | 
			
		||||
import java.io.File
 | 
			
		||||
 | 
			
		||||
private fun RemoteFileService.patchPackage(apk: File, id: Int) {
 | 
			
		||||
    if (packageName != BuildConfig.APPLICATION_ID) {
 | 
			
		||||
        update(id) { notification ->
 | 
			
		||||
            notification.setProgress(0, 0, true)
 | 
			
		||||
                    .setProgress(0, 0, true)
 | 
			
		||||
                    .setContentTitle(getString(R.string.hide_manager_title))
 | 
			
		||||
                    .setContentText("")
 | 
			
		||||
        }
 | 
			
		||||
        val patched = File(apk.parent, "patched.apk")
 | 
			
		||||
        try {
 | 
			
		||||
            // Try using the new APK to patch itself
 | 
			
		||||
            val loader = DynamicClassLoader(apk)
 | 
			
		||||
            loader.loadClass("a.a")
 | 
			
		||||
                    .getMethod("patchAPK", String::class.java, String::class.java, String::class.java)
 | 
			
		||||
                    .invoke(null, apk.path, patched.path, packageName)
 | 
			
		||||
        } catch (e: Exception) {
 | 
			
		||||
            Timber.e(e)
 | 
			
		||||
            // Fallback to use the current implementation
 | 
			
		||||
            PatchAPK.patch(apk.path, patched.path, packageName)
 | 
			
		||||
        }
 | 
			
		||||
private fun RemoteFileService.patch(apk: File, id: Int) {
 | 
			
		||||
    if (packageName == BuildConfig.APPLICATION_ID)
 | 
			
		||||
        return
 | 
			
		||||
 | 
			
		||||
    update(id) { notification ->
 | 
			
		||||
        notification.setProgress(0, 0, true)
 | 
			
		||||
            .setProgress(0, 0, true)
 | 
			
		||||
            .setContentTitle(getString(R.string.hide_manager_title))
 | 
			
		||||
            .setContentText("")
 | 
			
		||||
    }
 | 
			
		||||
    val patched = File(apk.parent, "patched.apk")
 | 
			
		||||
    PatchAPK.patch(apk, patched, packageName, applicationInfo.nonLocalizedLabel.toString())
 | 
			
		||||
    apk.delete()
 | 
			
		||||
    patched.renameTo(apk)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
private fun RemoteFileService.upgrade(apk: File, id: Int) {
 | 
			
		||||
    if (isRunningAsStub) {
 | 
			
		||||
        // Move to upgrade location
 | 
			
		||||
        apk.copyTo(DynAPK.update(this), overwrite = true)
 | 
			
		||||
        apk.delete()
 | 
			
		||||
        patched.renameTo(apk)
 | 
			
		||||
        if (Info.stub!!.version < Info.remote.stub.versionCode) {
 | 
			
		||||
            // We also want to upgrade stub
 | 
			
		||||
            service.fetchFile(Info.remote.stub.link).blockingGet().byteStream().use {
 | 
			
		||||
                it.writeTo(apk)
 | 
			
		||||
            }
 | 
			
		||||
            patch(apk, id)
 | 
			
		||||
        } else {
 | 
			
		||||
            // Simply relaunch the app
 | 
			
		||||
            ProcessPhoenix.triggerRebirth(this)
 | 
			
		||||
        }
 | 
			
		||||
    } else {
 | 
			
		||||
        patch(apk, id)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -51,15 +55,11 @@ private fun RemoteFileService.restore(apk: File, id: Int) {
 | 
			
		||||
    Config.export()
 | 
			
		||||
    // Make it world readable
 | 
			
		||||
    apk.setReadable(true, false)
 | 
			
		||||
    if (Shell.su("pm install $apk").exec().isSuccess) {
 | 
			
		||||
        val component = ComponentName(BuildConfig.APPLICATION_ID,
 | 
			
		||||
                ClassMap.get<Class<*>>(SplashActivity::class.java).name)
 | 
			
		||||
        RootUtils.rmAndLaunch(packageName, component)
 | 
			
		||||
    }
 | 
			
		||||
    Shell.su("pm install $apk && pm uninstall $packageName").exec()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fun RemoteFileService.handleAPK(subject: DownloadSubject.Manager)
 | 
			
		||||
    = when (subject.configuration) {
 | 
			
		||||
        is Upgrade -> patchPackage(subject.file, subject.hashCode())
 | 
			
		||||
        is Upgrade -> upgrade(subject.file, subject.hashCode())
 | 
			
		||||
        is Restore -> restore(subject.file, subject.hashCode())
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -1,24 +1,22 @@
 | 
			
		||||
package com.topjohnwu.magisk.model.download
 | 
			
		||||
 | 
			
		||||
import android.app.Notification
 | 
			
		||||
import android.app.Service
 | 
			
		||||
import android.content.Intent
 | 
			
		||||
import android.os.IBinder
 | 
			
		||||
import androidx.core.app.NotificationCompat
 | 
			
		||||
import androidx.core.app.NotificationManagerCompat
 | 
			
		||||
import com.topjohnwu.magisk.base.BaseService
 | 
			
		||||
import com.topjohnwu.magisk.view.Notifications
 | 
			
		||||
import org.koin.core.KoinComponent
 | 
			
		||||
import java.util.*
 | 
			
		||||
import kotlin.random.Random.Default.nextInt
 | 
			
		||||
 | 
			
		||||
abstract class NotificationService : Service(), KoinComponent {
 | 
			
		||||
abstract class NotificationService : BaseService(), KoinComponent {
 | 
			
		||||
 | 
			
		||||
    abstract val defaultNotification: NotificationCompat.Builder
 | 
			
		||||
    abstract val defaultNotification: Notification.Builder
 | 
			
		||||
 | 
			
		||||
    private val manager by lazy { NotificationManagerCompat.from(this) }
 | 
			
		||||
    private val hasNotifications get() = notifications.isNotEmpty()
 | 
			
		||||
 | 
			
		||||
    private val notifications =
 | 
			
		||||
        Collections.synchronizedMap(mutableMapOf<Int, NotificationCompat.Builder>())
 | 
			
		||||
        Collections.synchronizedMap(mutableMapOf<Int, Notification.Builder>())
 | 
			
		||||
 | 
			
		||||
    override fun onTaskRemoved(rootIntent: Intent?) {
 | 
			
		||||
        super.onTaskRemoved(rootIntent)
 | 
			
		||||
@@ -30,7 +28,7 @@ abstract class NotificationService : Service(), KoinComponent {
 | 
			
		||||
 | 
			
		||||
    fun update(
 | 
			
		||||
        id: Int,
 | 
			
		||||
        body: (NotificationCompat.Builder) -> Unit = {}
 | 
			
		||||
        body: (Notification.Builder) -> Unit = {}
 | 
			
		||||
    ) {
 | 
			
		||||
        val notification = notifications.getOrPut(id) { defaultNotification }
 | 
			
		||||
 | 
			
		||||
@@ -43,7 +41,7 @@ abstract class NotificationService : Service(), KoinComponent {
 | 
			
		||||
 | 
			
		||||
    protected fun finishNotify(
 | 
			
		||||
        id: Int,
 | 
			
		||||
        editBody: (NotificationCompat.Builder) -> NotificationCompat.Builder? = { null }
 | 
			
		||||
        editBody: (Notification.Builder) -> Notification.Builder? = { null }
 | 
			
		||||
    ) : Int {
 | 
			
		||||
        val currentNotification = remove(id)?.run(editBody)
 | 
			
		||||
 | 
			
		||||
@@ -62,11 +60,11 @@ abstract class NotificationService : Service(), KoinComponent {
 | 
			
		||||
    // ---
 | 
			
		||||
 | 
			
		||||
    private fun notify(id: Int, notification: Notification) {
 | 
			
		||||
        manager.notify(id, notification)
 | 
			
		||||
        Notifications.mgr.notify(id, notification)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun cancel(id: Int) {
 | 
			
		||||
        manager.cancel(id)
 | 
			
		||||
        Notifications.mgr.cancel(id)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected fun remove(id: Int) = notifications.remove(id).also {
 | 
			
		||||
@@ -84,4 +82,4 @@ abstract class NotificationService : Service(), KoinComponent {
 | 
			
		||||
    // --
 | 
			
		||||
 | 
			
		||||
    override fun onBind(p0: Intent?): IBinder? = null
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,8 @@
 | 
			
		||||
package com.topjohnwu.magisk.model.download
 | 
			
		||||
 | 
			
		||||
import android.app.Activity
 | 
			
		||||
import android.app.Notification
 | 
			
		||||
import android.content.Intent
 | 
			
		||||
import androidx.core.app.NotificationCompat
 | 
			
		||||
import com.topjohnwu.magisk.R
 | 
			
		||||
import com.topjohnwu.magisk.data.network.GithubRawServices
 | 
			
		||||
import com.topjohnwu.magisk.di.NullActivity
 | 
			
		||||
@@ -22,9 +22,9 @@ import java.io.InputStream
 | 
			
		||||
 | 
			
		||||
abstract class RemoteFileService : NotificationService() {
 | 
			
		||||
 | 
			
		||||
    private val service: GithubRawServices by inject()
 | 
			
		||||
    val service: GithubRawServices by inject()
 | 
			
		||||
 | 
			
		||||
    override val defaultNotification: NotificationCompat.Builder
 | 
			
		||||
    override val defaultNotification
 | 
			
		||||
        get() = Notifications.progress(this, "")
 | 
			
		||||
 | 
			
		||||
    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
 | 
			
		||||
@@ -108,11 +108,11 @@ abstract class RemoteFileService : NotificationService() {
 | 
			
		||||
    @Throws(Throwable::class)
 | 
			
		||||
    protected abstract fun onFinished(subject: DownloadSubject, id: Int)
 | 
			
		||||
 | 
			
		||||
    protected abstract fun NotificationCompat.Builder.addActions(subject: DownloadSubject)
 | 
			
		||||
            : NotificationCompat.Builder
 | 
			
		||||
    protected abstract fun Notification.Builder.addActions(subject: DownloadSubject)
 | 
			
		||||
            : Notification.Builder
 | 
			
		||||
 | 
			
		||||
    companion object {
 | 
			
		||||
        const val ARG_URL = "arg_url"
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -8,7 +8,8 @@ import se.ansman.kotshi.JsonSerializable
 | 
			
		||||
data class UpdateInfo(
 | 
			
		||||
    val app: ManagerJson = ManagerJson(),
 | 
			
		||||
    val uninstaller: UninstallerJson = UninstallerJson(),
 | 
			
		||||
    val magisk: MagiskJson = MagiskJson()
 | 
			
		||||
    val magisk: MagiskJson = MagiskJson(),
 | 
			
		||||
    val stub: StubJson = StubJson()
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@JsonSerializable
 | 
			
		||||
@@ -33,3 +34,9 @@ data class ManagerJson(
 | 
			
		||||
    val link: String = "",
 | 
			
		||||
    val note: String = ""
 | 
			
		||||
) : Parcelable
 | 
			
		||||
 | 
			
		||||
@JsonSerializable
 | 
			
		||||
data class StubJson(
 | 
			
		||||
    val versionCode: Int = -1,
 | 
			
		||||
    val link: String = ""
 | 
			
		||||
)
 | 
			
		||||
 
 | 
			
		||||
@@ -1,16 +1,15 @@
 | 
			
		||||
package com.topjohnwu.magisk.model.receiver
 | 
			
		||||
 | 
			
		||||
import android.content.BroadcastReceiver
 | 
			
		||||
import android.content.Context
 | 
			
		||||
import android.content.ContextWrapper
 | 
			
		||||
import android.content.Intent
 | 
			
		||||
import com.topjohnwu.magisk.ClassMap
 | 
			
		||||
import com.topjohnwu.magisk.Config
 | 
			
		||||
import com.topjohnwu.magisk.Const
 | 
			
		||||
import com.topjohnwu.magisk.Info
 | 
			
		||||
import android.os.Build.VERSION.SDK_INT
 | 
			
		||||
import com.topjohnwu.magisk.*
 | 
			
		||||
import com.topjohnwu.magisk.base.BaseReceiver
 | 
			
		||||
import com.topjohnwu.magisk.data.database.PolicyDao
 | 
			
		||||
import com.topjohnwu.magisk.data.database.base.su
 | 
			
		||||
import com.topjohnwu.magisk.extensions.inject
 | 
			
		||||
import com.topjohnwu.magisk.extensions.reboot
 | 
			
		||||
import com.topjohnwu.magisk.extensions.startActivity
 | 
			
		||||
import com.topjohnwu.magisk.extensions.startActivityWithRoot
 | 
			
		||||
import com.topjohnwu.magisk.model.download.DownloadService
 | 
			
		||||
import com.topjohnwu.magisk.model.entity.ManagerJson
 | 
			
		||||
import com.topjohnwu.magisk.model.entity.internal.Configuration
 | 
			
		||||
@@ -20,8 +19,10 @@ import com.topjohnwu.magisk.utils.SuLogger
 | 
			
		||||
import com.topjohnwu.magisk.view.Notifications
 | 
			
		||||
import com.topjohnwu.magisk.view.Shortcuts
 | 
			
		||||
import com.topjohnwu.superuser.Shell
 | 
			
		||||
import org.koin.core.inject
 | 
			
		||||
import timber.log.Timber
 | 
			
		||||
 | 
			
		||||
open class GeneralReceiver : BroadcastReceiver() {
 | 
			
		||||
open class GeneralReceiver : BaseReceiver() {
 | 
			
		||||
 | 
			
		||||
    private val policyDB: PolicyDao by inject()
 | 
			
		||||
 | 
			
		||||
@@ -36,8 +37,19 @@ open class GeneralReceiver : BroadcastReceiver() {
 | 
			
		||||
        return intent.data?.encodedSchemeSpecificPart.orEmpty()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onReceive(context: Context, intent: Intent?) {
 | 
			
		||||
    override fun onReceive(context: ContextWrapper, intent: Intent?) {
 | 
			
		||||
        intent ?: return
 | 
			
		||||
 | 
			
		||||
        // Debug messages
 | 
			
		||||
        if (BuildConfig.DEBUG) {
 | 
			
		||||
            Timber.d(intent.action)
 | 
			
		||||
            intent.extras?.let { bundle ->
 | 
			
		||||
                bundle.keySet().forEach {
 | 
			
		||||
                    Timber.d("[%s]=[%s]", it, bundle[it])
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        when (intent.action ?: return) {
 | 
			
		||||
            Intent.ACTION_REBOOT, Intent.ACTION_BOOT_COMPLETED -> {
 | 
			
		||||
                val action = intent.getStringExtra("action")
 | 
			
		||||
@@ -51,16 +63,26 @@ open class GeneralReceiver : BroadcastReceiver() {
 | 
			
		||||
                }
 | 
			
		||||
                when (action) {
 | 
			
		||||
                    REQUEST -> {
 | 
			
		||||
                        val i = Intent(context, ClassMap[SuRequestActivity::class.java])
 | 
			
		||||
                        val i = context.intent(SuRequestActivity::class.java)
 | 
			
		||||
                                .setAction(action)
 | 
			
		||||
                                .putExtra("socket", intent.getStringExtra("socket"))
 | 
			
		||||
                                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
 | 
			
		||||
                                .addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK)
 | 
			
		||||
                        context.startActivity(i)
 | 
			
		||||
                        if (SDK_INT >= 29) {
 | 
			
		||||
                            // Android Q does not allow starting activity from background
 | 
			
		||||
                            i.startActivityWithRoot()
 | 
			
		||||
                        } else {
 | 
			
		||||
                            i.startActivity(context)
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    LOG -> SuLogger.handleLogs(context, intent)
 | 
			
		||||
                    NOTIFY -> SuLogger.handleNotify(context, intent)
 | 
			
		||||
                    TEST -> {
 | 
			
		||||
                        val mode = intent.getIntExtra("mode", 1 shl 1)
 | 
			
		||||
                        if (mode > Info.env.connectionMode)
 | 
			
		||||
                            Info.env.connectionMode = mode
 | 
			
		||||
                        Shell.su("magisk --connect-mode $mode").submit()
 | 
			
		||||
                    }
 | 
			
		||||
                    LOG -> SuLogger.handleLogs(intent)
 | 
			
		||||
                    NOTIFY -> SuLogger.handleNotify(intent)
 | 
			
		||||
                    TEST -> Shell.su("magisk --use-broadcast").submit()
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            Intent.ACTION_PACKAGE_REPLACED ->
 | 
			
		||||
 
 | 
			
		||||
@@ -20,7 +20,7 @@ class UpdateCheckService : DelegateWorker() {
 | 
			
		||||
            magiskRepo.fetchUpdate().blockingGet()
 | 
			
		||||
            if (BuildConfig.VERSION_CODE < Info.remote.app.versionCode)
 | 
			
		||||
                Notifications.managerUpdate(applicationContext)
 | 
			
		||||
            else if (Info.magiskVersionCode < Info.remote.magisk.versionCode)
 | 
			
		||||
            else if (Info.env.magiskVersionCode < Info.remote.magisk.versionCode)
 | 
			
		||||
                Notifications.magiskUpdate(applicationContext)
 | 
			
		||||
            ListenableWorker.Result.success()
 | 
			
		||||
        }.getOrElse {
 | 
			
		||||
 
 | 
			
		||||
@@ -266,7 +266,7 @@ abstract class MagiskInstaller {
 | 
			
		||||
 | 
			
		||||
        val patched = File(installDir, "new-boot.img")
 | 
			
		||||
        if (isSigned) {
 | 
			
		||||
            console.add("- Signing boot image with test keys")
 | 
			
		||||
            console.add("- Signing boot image with verity keys")
 | 
			
		||||
            val signed = File(installDir, "signed.img")
 | 
			
		||||
            try {
 | 
			
		||||
                withStreams(SuFileInputStream(patched), signed.outputStream().buffered()) {
 | 
			
		||||
 
 | 
			
		||||
@@ -7,8 +7,6 @@ import androidx.fragment.app.Fragment
 | 
			
		||||
import androidx.fragment.app.FragmentTransaction
 | 
			
		||||
import com.ncapdevi.fragnav.FragNavController
 | 
			
		||||
import com.ncapdevi.fragnav.FragNavTransactionOptions
 | 
			
		||||
import com.topjohnwu.magisk.ClassMap
 | 
			
		||||
import com.topjohnwu.magisk.Config
 | 
			
		||||
import com.topjohnwu.magisk.Const.Key.OPEN_SECTION
 | 
			
		||||
import com.topjohnwu.magisk.Info
 | 
			
		||||
import com.topjohnwu.magisk.R
 | 
			
		||||
@@ -17,6 +15,7 @@ import com.topjohnwu.magisk.base.BaseFragment
 | 
			
		||||
import com.topjohnwu.magisk.databinding.ActivityMainBinding
 | 
			
		||||
import com.topjohnwu.magisk.extensions.addOnPropertyChangedCallback
 | 
			
		||||
import com.topjohnwu.magisk.extensions.snackbar
 | 
			
		||||
import com.topjohnwu.magisk.intent
 | 
			
		||||
import com.topjohnwu.magisk.model.events.*
 | 
			
		||||
import com.topjohnwu.magisk.model.navigation.MagiskAnimBuilder
 | 
			
		||||
import com.topjohnwu.magisk.model.navigation.MagiskNavigationEvent
 | 
			
		||||
@@ -40,8 +39,8 @@ open class MainActivity : BaseActivity<MainViewModel, ActivityMainBinding>(), Na
 | 
			
		||||
 | 
			
		||||
    override val layoutRes: Int = R.layout.activity_main
 | 
			
		||||
    override val viewModel: MainViewModel by viewModel()
 | 
			
		||||
    override val navHostId: Int = R.id.main_nav_host
 | 
			
		||||
    override val defaultPosition: Int = 0
 | 
			
		||||
    private val navHostId: Int = R.id.main_nav_host
 | 
			
		||||
    private val defaultPosition: Int = 0
 | 
			
		||||
 | 
			
		||||
    private val navigationController by lazy {
 | 
			
		||||
        FragNavController(supportFragmentManager, navHostId)
 | 
			
		||||
@@ -61,7 +60,7 @@ open class MainActivity : BaseActivity<MainViewModel, ActivityMainBinding>(), Na
 | 
			
		||||
 | 
			
		||||
    override fun onCreate(savedInstanceState: Bundle?) {
 | 
			
		||||
        if (!SplashActivity.DONE) {
 | 
			
		||||
            startActivity(Intent(this, ClassMap[SplashActivity::class.java]))
 | 
			
		||||
            startActivity(intent(SplashActivity::class.java))
 | 
			
		||||
            finish()
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -155,11 +154,11 @@ open class MainActivity : BaseActivity<MainViewModel, ActivityMainBinding>(), Na
 | 
			
		||||
    private fun checkHideSection() {
 | 
			
		||||
        val menu = binding.navView.menu
 | 
			
		||||
        menu.findItem(R.id.magiskHideFragment).isVisible =
 | 
			
		||||
            Shell.rootAccess() && Config.magiskHide
 | 
			
		||||
            Shell.rootAccess() && Info.env.magiskHide
 | 
			
		||||
        menu.findItem(R.id.modulesFragment).isVisible =
 | 
			
		||||
            Shell.rootAccess() && Info.magiskVersionCode >= 0
 | 
			
		||||
            Shell.rootAccess() && Info.env.magiskVersionCode >= 0
 | 
			
		||||
        menu.findItem(R.id.reposFragment).isVisible =
 | 
			
		||||
            (viewModel.isConnected.value && Shell.rootAccess() && Info.magiskVersionCode >= 0)
 | 
			
		||||
            (viewModel.isConnected.value && Shell.rootAccess() && Info.env.magiskVersionCode >= 0)
 | 
			
		||||
        menu.findItem(R.id.logFragment).isVisible =
 | 
			
		||||
            Shell.rootAccess()
 | 
			
		||||
        menu.findItem(R.id.superuserFragment).isVisible =
 | 
			
		||||
 
 | 
			
		||||
@@ -1,27 +1,31 @@
 | 
			
		||||
package com.topjohnwu.magisk.ui
 | 
			
		||||
 | 
			
		||||
import android.content.Intent
 | 
			
		||||
import android.app.Activity
 | 
			
		||||
import android.content.Context
 | 
			
		||||
import android.os.Bundle
 | 
			
		||||
import android.text.TextUtils
 | 
			
		||||
import androidx.appcompat.app.AlertDialog
 | 
			
		||||
import androidx.appcompat.app.AppCompatActivity
 | 
			
		||||
import com.topjohnwu.magisk.*
 | 
			
		||||
import com.topjohnwu.magisk.utils.Utils
 | 
			
		||||
import com.topjohnwu.magisk.view.Notifications
 | 
			
		||||
import com.topjohnwu.magisk.view.Shortcuts
 | 
			
		||||
import com.topjohnwu.superuser.Shell
 | 
			
		||||
 | 
			
		||||
open class SplashActivity : AppCompatActivity() {
 | 
			
		||||
open class SplashActivity : Activity() {
 | 
			
		||||
 | 
			
		||||
    override fun attachBaseContext(base: Context) {
 | 
			
		||||
        super.attachBaseContext(base.wrap())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onCreate(savedInstanceState: Bundle?) {
 | 
			
		||||
        super.onCreate(savedInstanceState)
 | 
			
		||||
 | 
			
		||||
        Shell.getShell {
 | 
			
		||||
            if (Info.magiskVersionCode > 0 && Info.magiskVersionCode < Const.MagiskVersion.MIN_SUPPORT) {
 | 
			
		||||
            if (Info.env.magiskVersionCode > 0 && Info.env.magiskVersionCode < Const.Version.MIN_SUPPORT) {
 | 
			
		||||
                AlertDialog.Builder(this)
 | 
			
		||||
                    .setTitle(R.string.unsupport_magisk_title)
 | 
			
		||||
                    .setMessage(R.string.unsupport_magisk_message)
 | 
			
		||||
                    .setNegativeButton(R.string.ok, null)
 | 
			
		||||
                    .setNegativeButton(android.R.string.ok, null)
 | 
			
		||||
                    .setOnDismissListener { finish() }
 | 
			
		||||
                    .show()
 | 
			
		||||
            } else {
 | 
			
		||||
@@ -56,7 +60,7 @@ open class SplashActivity : AppCompatActivity() {
 | 
			
		||||
        // Setup shortcuts
 | 
			
		||||
        Shortcuts.setup(this)
 | 
			
		||||
 | 
			
		||||
        val intent = Intent(this, ClassMap[MainActivity::class.java])
 | 
			
		||||
        val intent = intent(MainActivity::class.java)
 | 
			
		||||
        intent.putExtra(Const.Key.OPEN_SECTION, getIntent().getStringExtra(Const.Key.OPEN_SECTION))
 | 
			
		||||
        DONE = true
 | 
			
		||||
        startActivity(intent)
 | 
			
		||||
 
 | 
			
		||||
@@ -6,12 +6,12 @@ import android.net.Uri
 | 
			
		||||
import android.os.Bundle
 | 
			
		||||
import androidx.core.app.NotificationManagerCompat
 | 
			
		||||
import androidx.core.net.toUri
 | 
			
		||||
import com.topjohnwu.magisk.ClassMap
 | 
			
		||||
import com.topjohnwu.magisk.Const
 | 
			
		||||
import com.topjohnwu.magisk.R
 | 
			
		||||
import com.topjohnwu.magisk.base.BaseActivity
 | 
			
		||||
import com.topjohnwu.magisk.databinding.ActivityFlashBinding
 | 
			
		||||
import com.topjohnwu.magisk.extensions.snackbar
 | 
			
		||||
import com.topjohnwu.magisk.intent
 | 
			
		||||
import com.topjohnwu.magisk.model.events.BackPressEvent
 | 
			
		||||
import com.topjohnwu.magisk.model.events.PermissionEvent
 | 
			
		||||
import com.topjohnwu.magisk.model.events.SnackbarEvent
 | 
			
		||||
@@ -23,6 +23,7 @@ import java.io.File
 | 
			
		||||
open class FlashActivity : BaseActivity<FlashViewModel, ActivityFlashBinding>() {
 | 
			
		||||
 | 
			
		||||
    override val layoutRes: Int = R.layout.activity_flash
 | 
			
		||||
    override val themeRes: Int = R.style.MagiskTheme_Flashing
 | 
			
		||||
    override val viewModel: FlashViewModel by viewModel {
 | 
			
		||||
        val uri = intent.data ?: let { finish(); Uri.EMPTY }
 | 
			
		||||
        val additionalUri = intent.getParcelableExtra(Const.Key.FLASH_DATA) ?: uri
 | 
			
		||||
@@ -59,7 +60,7 @@ open class FlashActivity : BaseActivity<FlashViewModel, ActivityFlashBinding>()
 | 
			
		||||
 | 
			
		||||
    companion object {
 | 
			
		||||
 | 
			
		||||
        private fun intent(context: Context) = Intent(context, ClassMap[FlashActivity::class.java])
 | 
			
		||||
        private fun intent(context: Context) = context.intent(FlashActivity::class.java)
 | 
			
		||||
            .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
 | 
			
		||||
        private fun intent(context: Context, file: File) = intent(context).setData(file.toUri())
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user