Commit 2813d203 authored by topjohnwu's avatar topjohnwu

Merge branch 'WIP'

parents 4040a024 db218407
apply plugin: 'com.android.application' apply plugin: 'com.android.application'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-android' apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt' apply plugin: 'kotlin-kapt'
kapt { kapt {
...@@ -16,6 +16,7 @@ android { ...@@ -16,6 +16,7 @@ android {
defaultConfig { defaultConfig {
applicationId 'com.topjohnwu.magisk' applicationId 'com.topjohnwu.magisk'
vectorDrawables.useSupportLibrary = true vectorDrawables.useSupportLibrary = true
multiDexEnabled true
versionName configProps['appVersion'] versionName configProps['appVersion']
versionCode configProps['appVersionCode'] as Integer versionCode configProps['appVersionCode'] as Integer
javaCompileOptions { javaCompileOptions {
...@@ -30,6 +31,10 @@ android { ...@@ -30,6 +31,10 @@ android {
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
} }
} }
dataBinding {
enabled = true
}
} }
dependencies { dependencies {
...@@ -37,25 +42,19 @@ dependencies { ...@@ -37,25 +42,19 @@ dependencies {
implementation project(':net') implementation project(':net')
implementation project(':shared') implementation project(':shared')
implementation project(':signing') implementation project(':signing')
implementation 'com.github.topjohnwu:jtar:1.0.0' implementation 'com.github.topjohnwu:jtar:1.0.0'
implementation 'net.sourceforge.streamsupport:android-retrostreams:1.7.0' implementation 'net.sourceforge.streamsupport:android-retrostreams:1.7.0'
implementation 'com.github.sevar83:indeterminate-checkbox:1.0.5' implementation 'com.github.sevar83:indeterminate-checkbox:1.0.5'
implementation 'com.jakewharton.timber:timber:4.7.1'
implementation 'com.github.skoumalcz:teanity:0.3.3'
implementation 'com.ncapdevi:frag-nav:3.2.0'
def markwonVersion = '3.0.0' def markwonVersion = '3.0.0'
implementation "ru.noties.markwon:core:${markwonVersion}" implementation "ru.noties.markwon:core:${markwonVersion}"
implementation "ru.noties.markwon:html:${markwonVersion}" implementation "ru.noties.markwon:html:${markwonVersion}"
implementation "ru.noties.markwon:image-svg:${markwonVersion}" implementation "ru.noties.markwon:image-svg:${markwonVersion}"
def androidXVersion = "1.0.0"
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'androidx.appcompat:appcompat:1.0.2'
implementation "androidx.preference:preference:${androidXVersion}"
implementation "androidx.recyclerview:recyclerview:${androidXVersion}"
implementation "androidx.cardview:cardview:${androidXVersion}"
implementation "com.google.android.material:material:${androidXVersion}"
implementation 'androidx.work:work-runtime:2.0.1'
implementation 'androidx.transition:transition:1.1.0-beta01'
def libsuVersion = '2.5.0' def libsuVersion = '2.5.0'
implementation "com.github.topjohnwu.libsu:core:${libsuVersion}" implementation "com.github.topjohnwu.libsu:core:${libsuVersion}"
implementation "com.github.topjohnwu.libsu:io:${libsuVersion}" implementation "com.github.topjohnwu.libsu:io:${libsuVersion}"
...@@ -63,4 +62,19 @@ dependencies { ...@@ -63,4 +62,19 @@ dependencies {
def butterKnifeVersion = '10.1.0' def butterKnifeVersion = '10.1.0'
implementation "com.jakewharton:butterknife-runtime:${butterKnifeVersion}" implementation "com.jakewharton:butterknife-runtime:${butterKnifeVersion}"
kapt "com.jakewharton:butterknife-compiler:${butterKnifeVersion}" kapt "com.jakewharton:butterknife-compiler:${butterKnifeVersion}"
def koin = "2.0.0-rc-2"
implementation "org.koin:koin-core:${koin}"
implementation "org.koin:koin-android:${koin}"
implementation "org.koin:koin-androidx-viewmodel:${koin}"
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'androidx.appcompat:appcompat:1.0.2'
implementation 'androidx.preference:preference:1.0.0'
implementation 'androidx.recyclerview:recyclerview:1.1.0-alpha04'
implementation 'androidx.cardview:cardview:1.0.0'
implementation 'com.google.android.material:material:1.1.0-alpha05'
implementation 'androidx.work:work-runtime:2.0.1'
implementation 'androidx.transition:transition:1.1.0-beta01'
implementation 'androidx.multidex:multidex:2.0.1'
} }
...@@ -29,6 +29,11 @@ ...@@ -29,6 +29,11 @@
void onResponse(int); void onResponse(int);
} }
# Keep all fragment constructors
-keepclassmembers class * extends androidx.fragment.app.Fragment {
public <init>(...);
}
# DelegateWorker # DelegateWorker
-keep,allowobfuscation class * extends com.topjohnwu.magisk.model.worker.DelegateWorker -keep,allowobfuscation class * extends com.topjohnwu.magisk.model.worker.DelegateWorker
......
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
<application <application
android:allowBackup="true" android:allowBackup="true"
android:name="a.e" android:name="a.e"
android:theme="@style/AppTheme" android:theme="@style/MagiskTheme"
android:usesCleartextTraffic="true" android:usesCleartextTraffic="true"
tools:ignore="UnusedAttribute,GoogleAppIndexingWarning"> tools:ignore="UnusedAttribute,GoogleAppIndexingWarning">
...@@ -35,7 +35,7 @@ ...@@ -35,7 +35,7 @@
android:name="a.f" android:name="a.f"
android:configChanges="keyboardHidden|orientation|screenSize" android:configChanges="keyboardHidden|orientation|screenSize"
android:screenOrientation="nosensor" android:screenOrientation="nosensor"
android:theme="@style/AppTheme.NoDrawer" /> android:theme="@style/MagiskTheme.Flashing" />
<!-- Superuser --> <!-- Superuser -->
...@@ -44,7 +44,7 @@ ...@@ -44,7 +44,7 @@
android:exported="false" android:exported="false"
android:directBootAware="true" android:directBootAware="true"
android:excludeFromRecents="true" android:excludeFromRecents="true"
android:theme="@style/SuRequest" /> android:theme="@style/MagiskTheme.SU" />
<!-- Receiver --> <!-- Receiver -->
......
package com.topjohnwu.magisk;
import android.app.Activity;
import android.app.Application;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.preference.PreferenceManager;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatDelegate;
import com.topjohnwu.magisk.data.database.MagiskDB;
import com.topjohnwu.magisk.data.database.RepoDatabaseHelper;
import com.topjohnwu.magisk.ui.base.BaseActivity;
import com.topjohnwu.magisk.utils.LocaleManager;
import com.topjohnwu.magisk.utils.RootUtils;
import com.topjohnwu.net.Networking;
import com.topjohnwu.superuser.Shell;
import java.util.concurrent.ThreadPoolExecutor;
public class App extends Application implements Application.ActivityLifecycleCallbacks {
public static App self;
public static Context deContext;
public static ThreadPoolExecutor THREAD_POOL;
// Global resources
public SharedPreferences prefs;
public MagiskDB mDB;
public RepoDatabaseHelper repoDB;
private volatile BaseActivity foreground;
static {
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true);
Shell.Config.setFlags(Shell.FLAG_MOUNT_MASTER | Shell.FLAG_USE_MAGISK_BUSYBOX);
Shell.Config.verboseLogging(BuildConfig.DEBUG);
Shell.Config.addInitializers(RootUtils.class);
Shell.Config.setTimeout(2);
THREAD_POOL = (ThreadPoolExecutor) AsyncTask.THREAD_POOL_EXECUTOR;
}
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
self = this;
deContext = base;
registerActivityLifecycleCallbacks(this);
if (Build.VERSION.SDK_INT >= 24) {
deContext = base.createDeviceProtectedStorageContext();
deContext.moveSharedPreferencesFrom(base,
PreferenceManager.getDefaultSharedPreferencesName(base));
}
prefs = PreferenceManager.getDefaultSharedPreferences(deContext);
mDB = new MagiskDB(base);
Networking.init(base);
LocaleManager.setLocale(this);
}
@Override
public void onConfigurationChanged(@NonNull Configuration newConfig) {
super.onConfigurationChanged(newConfig);
LocaleManager.setLocale(this);
}
public static BaseActivity foreground() {
return self.foreground;
}
@Override
public void onActivityCreated(@NonNull Activity activity, @Nullable Bundle bundle) {}
@Override
public void onActivityStarted(@NonNull Activity activity) {}
@Override
public synchronized void onActivityResumed(@NonNull Activity activity) {
foreground = (BaseActivity) activity;
}
@Override
public synchronized void onActivityPaused(@NonNull Activity activity) {
foreground = null;
}
@Override
public void onActivityStopped(@NonNull Activity activity) {}
@Override
public void onActivitySaveInstanceState(@NonNull Activity activity, @NonNull Bundle bundle) {}
@Override
public void onActivityDestroyed(@NonNull Activity activity) {}
}
package com.topjohnwu.magisk
import android.annotation.SuppressLint
import android.app.Activity
import android.app.Application
import android.content.Context
import android.content.SharedPreferences
import android.content.res.Configuration
import android.os.AsyncTask
import android.os.Build
import android.os.Bundle
import androidx.appcompat.app.AppCompatDelegate
import androidx.multidex.MultiDex
import com.topjohnwu.magisk.data.database.MagiskDB
import com.topjohnwu.magisk.data.database.RepoDatabaseHelper
import com.topjohnwu.magisk.di.koinModules
import com.topjohnwu.magisk.utils.LocaleManager
import com.topjohnwu.magisk.utils.RootUtils
import com.topjohnwu.magisk.utils.inject
import com.topjohnwu.net.Networking
import com.topjohnwu.superuser.Shell
import org.koin.android.ext.android.inject
import org.koin.android.ext.koin.androidContext
import org.koin.core.context.startKoin
import timber.log.Timber
import java.util.concurrent.ThreadPoolExecutor
open class App : Application(), Application.ActivityLifecycleCallbacks {
lateinit var protectedContext: Context
@Deprecated("Use dependency injection")
val prefs: SharedPreferences by inject()
@Deprecated("Use dependency injection")
val DB: MagiskDB by inject()
@Deprecated("Use dependency injection")
val repoDB: RepoDatabaseHelper by inject()
@Volatile
private var foreground: Activity? = null
override fun attachBaseContext(base: Context) {
super.attachBaseContext(base)
MultiDex.install(base)
Timber.plant(Timber.DebugTree())
startKoin {
androidContext(this@App)
modules(koinModules)
}
protectedContext = baseContext
self = this
deContext = base
if (Build.VERSION.SDK_INT >= 24) {
protectedContext = base.createDeviceProtectedStorageContext()
deContext = protectedContext
deContext.moveSharedPreferencesFrom(base, base.defaultPrefsName)
}
registerActivityLifecycleCallbacks(this)
Networking.init(base)
LocaleManager.setLocale(this)
}
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
LocaleManager.setLocale(this)
}
//region ActivityLifecycleCallbacks
override fun onActivityCreated(activity: Activity, bundle: Bundle?) {}
override fun onActivityStarted(activity: Activity) {}
@Synchronized
override fun onActivityResumed(activity: Activity) {
foreground = activity
}
@Synchronized
override fun onActivityPaused(activity: Activity) {
foreground = null
}
override fun onActivityStopped(activity: Activity) {}
override fun onActivitySaveInstanceState(activity: Activity, bundle: Bundle) {}
override fun onActivityDestroyed(activity: Activity) {}
//endregion
private val Context.defaultPrefsName get() = "${packageName}_preferences"
companion object {
@SuppressLint("StaticFieldLeak")
@Deprecated("Use dependency injection")
@JvmStatic
lateinit var self: App
@SuppressLint("StaticFieldLeak")
@Deprecated("Use dependency injection; replace with protectedContext")
@JvmStatic
lateinit var deContext: Context
@Deprecated("Use Rx or similar")
@JvmField
var THREAD_POOL: ThreadPoolExecutor
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.setTimeout(2)
THREAD_POOL = AsyncTask.THREAD_POOL_EXECUTOR as ThreadPoolExecutor
}
@Deprecated("")
@JvmStatic
fun foreground(): Activity? {
val app: App by inject()
return app.foreground
}
}
}
...@@ -3,8 +3,6 @@ package com.topjohnwu.magisk; ...@@ -3,8 +3,6 @@ package com.topjohnwu.magisk;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.util.Xml; import android.util.Xml;
import androidx.collection.ArrayMap;
import com.topjohnwu.magisk.utils.Utils; import com.topjohnwu.magisk.utils.Utils;
import com.topjohnwu.superuser.Shell; import com.topjohnwu.superuser.Shell;
import com.topjohnwu.superuser.ShellUtils; import com.topjohnwu.superuser.ShellUtils;
...@@ -17,6 +15,8 @@ import org.xmlpull.v1.XmlPullParserException; ...@@ -17,6 +15,8 @@ import org.xmlpull.v1.XmlPullParserException;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import androidx.collection.ArrayMap;
public class Config { public class Config {
// Current status // Current status
...@@ -109,14 +109,14 @@ public class Config { ...@@ -109,14 +109,14 @@ public class Config {
public static void export() { public static void export() {
// Flush prefs to disk // Flush prefs to disk
App app = App.self; App app = App.self;
app.prefs.edit().commit(); app.getPrefs().edit().commit();
File xml = new File(App.deContext.getFilesDir().getParent() + "/shared_prefs", File xml = new File(App.deContext.getFilesDir().getParent() + "/shared_prefs",
app.getPackageName() + "_preferences.xml"); app.getPackageName() + "_preferences.xml");
Shell.su(Utils.fmt("cat %s > /data/adb/%s", xml, Const.MANAGER_CONFIGS)).exec(); Shell.su(Utils.fmt("cat %s > /data/adb/%s", xml, Const.MANAGER_CONFIGS)).exec();
} }
public static void initialize() { public static void initialize() {
SharedPreferences pref = App.self.prefs; SharedPreferences pref = App.self.getPrefs();
SharedPreferences.Editor editor = pref.edit(); SharedPreferences.Editor editor = pref.edit();
File config = SuFile.open("/data/adb", Const.MANAGER_CONFIGS); File config = SuFile.open("/data/adb", Const.MANAGER_CONFIGS);
if (config.exists()) { if (config.exists()) {
...@@ -238,19 +238,19 @@ public class Config { ...@@ -238,19 +238,19 @@ public class Config {
App app = App.self; App app = App.self;
switch (getConfigType(key)) { switch (getConfigType(key)) {
case PREF_INT: case PREF_INT:
return (T) (Integer) app.prefs.getInt(key, getDef(key)); return (T) (Integer) app.getPrefs().getInt(key, getDef(key));
case PREF_STR_INT: case PREF_STR_INT:
return (T) (Integer) Utils.getPrefsInt(app.prefs, key, getDef(key)); return (T) (Integer) Utils.getPrefsInt(app.getPrefs(), key, getDef(key));
case PREF_BOOL: case PREF_BOOL:
return (T) (Boolean) app.prefs.getBoolean(key, getDef(key)); return (T) (Boolean) app.getPrefs().getBoolean(key, getDef(key));
case PREF_STR: case PREF_STR:
return (T) app.prefs.getString(key, getDef(key)); return (T) app.getPrefs().getString(key, getDef(key));
case DB_INT: case DB_INT:
return (T) (Integer) app.mDB.getSettings(key, getDef(key)); return (T) (Integer) app.getDB().getSettings(key, getDef(key));
case DB_BOOL: case DB_BOOL:
return (T) (Boolean) (app.mDB.getSettings(key, getDef(key) ? 1 : 0) != 0); return (T) (Boolean) (app.getDB().getSettings(key, getDef(key) ? 1 : 0) != 0);
case DB_STR: case DB_STR:
return (T) app.mDB.getStrings(key, getDef(key)); return (T) app.getDB().getStrings(key, getDef(key));
} }
/* Will never get here (IllegalArgumentException in getConfigType) */ /* Will never get here (IllegalArgumentException in getConfigType) */
return null; return null;
...@@ -260,25 +260,25 @@ public class Config { ...@@ -260,25 +260,25 @@ public class Config {
App app = App.self; App app = App.self;
switch (getConfigType(key)) { switch (getConfigType(key)) {
case PREF_INT: case PREF_INT:
app.prefs.edit().putInt(key, (int) val).apply(); app.getPrefs().edit().putInt(key, (int) val).apply();
break; break;
case PREF_STR_INT: case PREF_STR_INT:
app.prefs.edit().putString(key, String.valueOf(val)).apply(); app.getPrefs().edit().putString(key, String.valueOf(val)).apply();
break; break;
case PREF_BOOL: case PREF_BOOL:
app.prefs.edit().putBoolean(key, (boolean) val).apply(); app.getPrefs().edit().putBoolean(key, (boolean) val).apply();
break; break;
case PREF_STR: case PREF_STR:
app.prefs.edit().putString(key, (String) val).apply(); app.getPrefs().edit().putString(key, (String) val).apply();
break; break;
case DB_INT: case DB_INT:
app.mDB.setSettings(key, (int) val); app.getDB().setSettings(key, (int) val);
break; break;
case DB_BOOL: case DB_BOOL:
app.mDB.setSettings(key, (boolean) val ? 1 : 0); app.getDB().setSettings(key, (boolean) val ? 1 : 0);
break; break;
case DB_STR: case DB_STR:
app.mDB.setStrings(key, (String) val); app.getDB().setStrings(key, (String) val);
break; break;
} }
} }
...@@ -290,14 +290,14 @@ public class Config { ...@@ -290,14 +290,14 @@ public class Config {
case PREF_STR_INT: case PREF_STR_INT:
case PREF_BOOL: case PREF_BOOL:
case PREF_STR: case PREF_STR:
app.prefs.edit().remove(key).apply(); app.getPrefs().edit().remove(key).apply();
break; break;
case DB_BOOL: case DB_BOOL:
case DB_INT: case DB_INT:
app.mDB.rmSettings(key); app.getDB().rmSettings(key);
break; break;
case DB_STR: case DB_STR:
app.mDB.setStrings(key, null); app.getDB().setStrings(key, null);
break; break;
} }
} }
...@@ -365,13 +365,13 @@ public class Config { ...@@ -365,13 +365,13 @@ public class Config {
switch (type) { switch (type) {
case DB_INT: case DB_INT:
editor.putString(key, String.valueOf( editor.putString(key, String.valueOf(
app.mDB.getSettings(key, (Integer) defs.get(key)))); app.getDB().getSettings(key, (Integer) defs.get(key))));
continue; continue;
case DB_STR: case DB_STR:
editor.putString(key, app.mDB.getStrings(key, (String) defs.get(key))); editor.putString(key, app.getDB().getStrings(key, (String) defs.get(key)));
continue; continue;
case DB_BOOL: case DB_BOOL:
int bs = app.mDB.getSettings(key, -1); int bs = app.getDB().getSettings(key, -1);
editor.putBoolean(key, bs < 0 ? (Boolean) defs.get(key) : bs != 0); editor.putBoolean(key, bs < 0 ? (Boolean) defs.get(key) : bs != 0);
continue; continue;
} }
......
...@@ -35,6 +35,9 @@ public class Const { ...@@ -35,6 +35,9 @@ public class Const {
public static final int USER_ID = Process.myUid() / 100000; public static final int USER_ID = Process.myUid() / 100000;
// Generic
public static final String MAGISK_INSTALL_LOG_FILENAME = "magisk_install_log_%s.log";
public static final class MAGISK_VER { public static final class MAGISK_VER {
public static final int MIN_SUPPORT = 18000; public static final int MIN_SUPPORT = 18000;
} }
......
package com.topjohnwu.magisk.di
import android.content.Context
import androidx.preference.PreferenceManager
import com.skoumal.teanity.rxbus.RxBus
import com.topjohnwu.magisk.App
import org.koin.dsl.module
val applicationModule = module {
single { RxBus() }
single { get<Context>().resources }
single { get<Context>() as App }
single { get<Context>().packageManager }
single(SUTimeout) {
get<App>().protectedContext
.getSharedPreferences("su_timeout", 0)
}
single { PreferenceManager.getDefaultSharedPreferences(get<App>().protectedContext) }
}
package com.topjohnwu.magisk.di
import com.topjohnwu.magisk.App
import com.topjohnwu.magisk.data.database.MagiskDB
import com.topjohnwu.magisk.data.database.RepoDatabaseHelper
import org.koin.dsl.module
val databaseModule = module {
single { MagiskDB(get<App>().protectedContext) }
single { RepoDatabaseHelper(get()) }
}
package com.topjohnwu.magisk.di
import org.koin.dsl.module
val miscModule = module {
// define miscs here
}
package com.topjohnwu.magisk.di
val koinModules = listOf(
applicationModule,
networkingModule,
databaseModule,
repositoryModule,
viewModelModules,
miscModule
)
package com.topjohnwu.magisk.di
import org.koin.core.qualifier.named
val SUTimeout = named("su_timeout")
\ No newline at end of file
package com.topjohnwu.magisk.di
import org.koin.dsl.module
val networkingModule = module {}
package com.topjohnwu.magisk.di
import org.koin.dsl.module
val repositoryModule = module {}
package com.topjohnwu.magisk.di
import android.content.Intent
import android.net.Uri
import com.topjohnwu.magisk.ui.MainViewModel
import com.topjohnwu.magisk.ui.flash.FlashViewModel
import com.topjohnwu.magisk.ui.hide.HideViewModel
import com.topjohnwu.magisk.ui.home.HomeViewModel
import com.topjohnwu.magisk.ui.log.LogViewModel
import com.topjohnwu.magisk.ui.module.ModuleViewModel
import com.topjohnwu.magisk.ui.superuser.SuperuserViewModel
import com.topjohnwu.magisk.ui.surequest.SuRequestViewModel
import com.topjohnwu.magisk.ui.surequest._SuRequestViewModel
import org.koin.androidx.viewmodel.dsl.viewModel
import org.koin.dsl.module
val viewModelModules = module {
viewModel { MainViewModel() }
viewModel { HomeViewModel(get(), get()) }
viewModel { SuperuserViewModel(get(), get(), get(), get()) }
viewModel { HideViewModel(get(), get()) }
viewModel { ModuleViewModel(get(), get()) }
viewModel { LogViewModel(get(), get()) }
viewModel { (action: String, uri: Uri?) -> FlashViewModel(action, uri, get()) }
viewModel { (intent: Intent, action: String?) ->
_SuRequestViewModel(intent, action.orEmpty(), get(), get())
}
viewModel { SuRequestViewModel(get(), get(), get(SUTimeout), get()) }
}
package com.topjohnwu.magisk.model.adapters;
import android.content.Context;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CheckBox;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.snackbar.Snackbar;
import com.topjohnwu.magisk.R;
import com.topjohnwu.magisk.model.entity.Module;
import com.topjohnwu.magisk.view.SnackbarMaker;
import com.topjohnwu.superuser.Shell;
import java.util.List;
import butterknife.BindView;
public class ModulesAdapter extends RecyclerView.Adapter<ModulesAdapter.ViewHolder> {
private final List<Module> mList;
public ModulesAdapter(List<Module> list) {
mList = list;
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_module, parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(final ViewHolder holder, int position) {
Context context = holder.itemView.getContext();
final Module module = mList.get(position);
String version = module.getVersion();
String author = module.getAuthor();
String description = module.getDescription();
String noInfo = context.getString(R.string.no_info_provided);
holder.title.setText(module.getName());
holder.versionName.setText(TextUtils.isEmpty(version) ? noInfo : version);
holder.author.setText(TextUtils.isEmpty(author) ? noInfo : context.getString(R.string.author, author));
holder.description.setText(TextUtils.isEmpty(description) ? noInfo : description);
holder.checkBox.setOnCheckedChangeListener(null);
holder.checkBox.setChecked(module.isEnabled());
holder.checkBox.setOnCheckedChangeListener((v, isChecked) -> {
int snack;
if (isChecked) {
module.removeDisableFile();
snack = R.string.disable_file_removed;
} else {
module.createDisableFile();
snack = R.string.disable_file_created;
}
SnackbarMaker.make(holder.itemView, snack, Snackbar.LENGTH_SHORT).show();
});
holder.delete.setOnClickListener(v -> {
boolean removed = module.willBeRemoved();
int snack;
if (removed) {
module.deleteRemoveFile();
snack = R.string.remove_file_deleted;
} else {
module.createRemoveFile();
snack = R.string.remove_file_created;
}
SnackbarMaker.make(holder.itemView, snack, Snackbar.LENGTH_SHORT).show();
updateDeleteButton(holder, module);
});
if (module.isUpdated()) {
holder.notice.setVisibility(View.VISIBLE);
holder.notice.setText(R.string.update_file_created);
holder.delete.setEnabled(false);
} else {
updateDeleteButton(holder, module);
}
}
private void updateDeleteButton(ViewHolder holder, Module module) {
holder.notice.setVisibility(module.willBeRemoved() ? View.VISIBLE : View.GONE);
if (module.willBeRemoved()) {
holder.delete.setImageResource(R.drawable.ic_undelete);
} else {
holder.delete.setImageResource(R.drawable.ic_delete);
}
}
@Override
public int getItemCount() {
return mList.size();
}
static class ViewHolder extends RecyclerView.ViewHolder {
@BindView(R.id.title) TextView title;
@BindView(R.id.version_name) TextView versionName;
@BindView(R.id.description) TextView description;
@BindView(R.id.notice) TextView notice;
@BindView(R.id.checkbox) CheckBox checkBox;
@BindView(R.id.author) TextView author;
@BindView(R.id.delete) ImageView delete;
ViewHolder(View itemView) {
super(itemView);
new ModulesAdapter$ViewHolder_ViewBinding(this, itemView);
if (!Shell.rootAccess()) {
checkBox.setEnabled(false);
delete.setEnabled(false);
}
}
}
}
package com.topjohnwu.magisk.model.adapters;
import android.app.Activity;
import android.content.DialogInterface;
import android.content.pm.PackageManager;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.appcompat.widget.SwitchCompat;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.snackbar.Snackbar;
import com.topjohnwu.magisk.R;
import com.topjohnwu.magisk.data.database.MagiskDB;
import com.topjohnwu.magisk.model.entity.Policy;
import com.topjohnwu.magisk.utils.FingerprintHelper;
import com.topjohnwu.magisk.view.ArrowExpandable;
import com.topjohnwu.magisk.view.Expandable;
import com.topjohnwu.magisk.view.ExpandableViewHolder;
import com.topjohnwu.magisk.view.SnackbarMaker;
import com.topjohnwu.magisk.view.dialogs.CustomAlertDialog;
import com.topjohnwu.magisk.view.dialogs.FingerprintAuthDialog;
import java.util.List;
import butterknife.BindView;
public class PolicyAdapter extends RecyclerView.Adapter<PolicyAdapter.ViewHolder> {
private List<Policy> policyList;
private MagiskDB dbHelper;
private PackageManager pm;
private boolean[] expandList;
public PolicyAdapter(List<Policy> list, MagiskDB db, PackageManager pm) {
policyList = list;
expandList = new boolean[policyList.size()];
dbHelper = db;
this.pm = pm;
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_policy, parent, false);
return new ViewHolder(v);
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
Policy policy = policyList.get(position);
holder.settings.setExpanded(expandList[position]);
holder.trigger.setOnClickListener(view -> {
if (holder.settings.isExpanded()) {
holder.settings.collapse();
expandList[position] = false;
} else {
holder.settings.expand();
expandList[position] = true;
}
});
holder.appName.setText(policy.appName);
holder.packageName.setText(policy.packageName);
holder.appIcon.setImageDrawable(policy.info.loadIcon(pm));
holder.notificationSwitch.setOnCheckedChangeListener(null);
holder.loggingSwitch.setOnCheckedChangeListener(null);
holder.masterSwitch.setChecked(policy.policy == Policy.ALLOW);
holder.notificationSwitch.setChecked(policy.notification);
holder.loggingSwitch.setChecked(policy.logging);
holder.masterSwitch.setOnClickListener(v -> {
boolean isChecked = holder.masterSwitch.isChecked();
Runnable r = () -> {
if ((isChecked && policy.policy == Policy.DENY) ||
(!isChecked && policy.policy == Policy.ALLOW)) {
policy.policy = isChecked ? Policy.ALLOW : Policy.DENY;
String message = v.getContext().getString(
isChecked ? R.string.su_snack_grant : R.string.su_snack_deny, policy.appName);
SnackbarMaker.make(holder.itemView, message, Snackbar.LENGTH_SHORT).show();
dbHelper.updatePolicy(policy);
}
};
if (FingerprintHelper.useFingerprint()) {
holder.masterSwitch.setChecked(!isChecked);
new FingerprintAuthDialog((Activity) v.getContext(), () -> {
holder.masterSwitch.setChecked(isChecked);
r.run();
}).show();
} else {
r.run();
}
});
holder.notificationSwitch.setOnCheckedChangeListener((v, isChecked) -> {
if ((isChecked && !policy.notification) ||
(!isChecked && policy.notification)) {
policy.notification = isChecked;
String message = v.getContext().getString(
isChecked ? R.string.su_snack_notif_on : R.string.su_snack_notif_off, policy.appName);
SnackbarMaker.make(holder.itemView, message, Snackbar.LENGTH_SHORT).show();
dbHelper.updatePolicy(policy);
}
});
holder.loggingSwitch.setOnCheckedChangeListener((v, isChecked) -> {
if ((isChecked && !policy.logging) ||
(!isChecked && policy.logging)) {
policy.logging = isChecked;
String message = v.getContext().getString(
isChecked ? R.string.su_snack_log_on : R.string.su_snack_log_off, policy.appName);
SnackbarMaker.make(holder.itemView, message, Snackbar.LENGTH_SHORT).show();
dbHelper.updatePolicy(policy);
}
});
holder.delete.setOnClickListener(v -> {
DialogInterface.OnClickListener l = (dialog, which) -> {
policyList.remove(position);
notifyItemRemoved(position);
notifyItemRangeChanged(position, policyList.size());
SnackbarMaker.make(holder.itemView, v.getContext().getString(R.string.su_snack_revoke, policy.appName),
Snackbar.LENGTH_SHORT).show();
dbHelper.deletePolicy(policy);
};
if (FingerprintHelper.useFingerprint()) {
new FingerprintAuthDialog((Activity) v.getContext(),
() -> l.onClick(null, 0)).show();
} else {
new CustomAlertDialog((Activity) v.getContext())
.setTitle(R.string.su_revoke_title)
.setMessage(v.getContext().getString(R.string.su_revoke_msg, policy.appName))
.setPositiveButton(R.string.yes, l)
.setNegativeButton(R.string.no_thanks, null)
.setCancelable(true)
.show();
}
});
}
@Override
public int getItemCount() {
return policyList.size();
}
static class ViewHolder extends RecyclerView.ViewHolder {
@BindView(R.id.app_name) TextView appName;
@BindView(R.id.package_name) TextView packageName;
@BindView(R.id.app_icon) ImageView appIcon;
@BindView(R.id.master_switch) SwitchCompat masterSwitch;
@BindView(R.id.notification_switch) SwitchCompat notificationSwitch;
@BindView(R.id.logging_switch) SwitchCompat loggingSwitch;
@BindView(R.id.expand_layout) ViewGroup expandLayout;
@BindView(R.id.arrow) ImageView arrow;
@BindView(R.id.trigger) View trigger;
@BindView(R.id.delete) ImageView delete;
@BindView(R.id.more_info) ImageView moreInfo;
Expandable settings;
public ViewHolder(View itemView) {
super(itemView);
new PolicyAdapter$ViewHolder_ViewBinding(this, itemView);
settings = new ArrowExpandable(new ExpandableViewHolder(expandLayout), arrow);
}
}
}
package com.topjohnwu.magisk.model.adapters;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.os.Build;
import android.text.TextUtils;
import android.util.Pair;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.SearchView;
import android.widget.TextView;
import androidx.recyclerview.widget.RecyclerView;
import com.topjohnwu.magisk.App;
import com.topjohnwu.magisk.ClassMap;
import com.topjohnwu.magisk.R;
import com.topjohnwu.magisk.data.database.RepoDatabaseHelper;
import com.topjohnwu.magisk.model.download.DownloadModuleService;
import com.topjohnwu.magisk.model.entity.Module;
import com.topjohnwu.magisk.model.entity.Repo;
import com.topjohnwu.magisk.ui.base.BaseActivity;
import com.topjohnwu.magisk.utils.Event;
import com.topjohnwu.magisk.view.MarkDownWindow;
import com.topjohnwu.magisk.view.dialogs.CustomAlertDialog;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import butterknife.BindView;
import java9.util.stream.StreamSupport;
public class ReposAdapter
extends SectionedAdapter<ReposAdapter.SectionHolder, ReposAdapter.RepoHolder>
implements Event.AutoListener, SearchView.OnQueryTextListener {
private static final int UPDATES = 0;
private static final int INSTALLED = 1;
private static final int OTHERS = 2;
private Map<String, Module> moduleMap;
private RepoDatabaseHelper repoDB;
private List<Pair<Integer, List<Repo>>> repoPairs;
private List<Repo> fullList;
private SearchView mSearch;
public ReposAdapter() {
repoDB = App.self.repoDB;
moduleMap = Collections.emptyMap();
fullList = Collections.emptyList();
repoPairs = new ArrayList<>();
}
@Override
public int getSectionCount() {
return repoPairs.size();
}
@Override
public int getItemCount(int section) {
return repoPairs.get(section).second.size();
}
@Override
public SectionHolder onCreateSectionViewHolder(ViewGroup parent) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.section, parent, false);
return new SectionHolder(v);
}
@Override
public RepoHolder onCreateItemViewHolder(ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_repo, parent, false);
return new RepoHolder(v);
}
@Override
public void onBindSectionViewHolder(SectionHolder holder, int section) {
switch (repoPairs.get(section).first) {
case UPDATES:
holder.sectionText.setText(R.string.update_available);
break;
case INSTALLED:
holder.sectionText.setText(R.string.installed);
break;
case OTHERS:
holder.sectionText.setText(R.string.not_installed);
break;
}
}
@Override
public void onBindItemViewHolder(RepoHolder holder, int section, int position) {
Repo repo = repoPairs.get(section).second.get(position);
Context context = holder.itemView.getContext();
String name = repo.getName();
String version = repo.getVersion();
String author = repo.getAuthor();
String description = repo.getDescription();
String noInfo = context.getString(R.string.no_info_provided);
holder.title.setText(TextUtils.isEmpty(name) ? noInfo : name);
holder.versionName.setText(TextUtils.isEmpty(version) ? noInfo : version);
holder.author.setText(TextUtils.isEmpty(author) ? noInfo : context.getString(R.string.author, author));
holder.description.setText(TextUtils.isEmpty(description) ? noInfo : description);
holder.updateTime.setText(context.getString(R.string.updated_on, repo.getLastUpdateString()));
holder.infoLayout.setOnClickListener(v ->
MarkDownWindow.show((BaseActivity) context, null, repo.getDetailUrl()));
holder.downloadImage.setOnClickListener(v -> {
new CustomAlertDialog((BaseActivity) context)
.setTitle(context.getString(R.string.repo_install_title, repo.getName()))
.setMessage(context.getString(R.string.repo_install_msg, repo.getDownloadFilename()))
.setCancelable(true)
.setPositiveButton(R.string.install, (d, i) ->
startDownload((BaseActivity) context, repo, true))
.setNeutralButton(R.string.download, (d, i) ->
startDownload((BaseActivity) context, repo, false))
.setNegativeButton(R.string.no_thanks, null)
.show();
});
}
private void startDownload(BaseActivity activity, Repo repo, Boolean install) {
activity.runWithExternalRW(() -> {
Intent intent = new Intent(activity, ClassMap.get(DownloadModuleService.class))
.putExtra("repo", repo).putExtra("install", install);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
activity.startForegroundService(intent);
} else {
activity.startService(intent);
}
});
}
private void updateLists() {
if (mSearch != null)
onQueryTextChange(mSearch.getQuery().toString());
else
onQueryTextChange("");
}
private static boolean noCaseContain(String a, String b) {
return a.toLowerCase().contains(b.toLowerCase());
}
public void setSearchView(SearchView view) {
mSearch = view;
mSearch.setOnQueryTextListener(this);
}
public void notifyDBChanged(boolean refresh) {
try (Cursor c = repoDB.getRepoCursor()) {
fullList = new ArrayList<>(c.getCount());
while (c.moveToNext())
fullList.add(new Repo(c));
}
if (refresh)
updateLists();
}
@Override
public void onEvent(int event) {
moduleMap = Event.getResult(event);
updateLists();
}
@Override
public int[] getListeningEvents() {
return new int[] {Event.MODULE_LOAD_DONE};
}
@Override
public boolean onQueryTextSubmit(String query) {
return false;
}
@Override
public boolean onQueryTextChange(String s) {
List<Repo> updates = new ArrayList<>();
List<Repo> installed = new ArrayList<>();
List<Repo> others = new ArrayList<>();
StreamSupport.stream(fullList)
.filter(repo -> noCaseContain(repo.getName(), s)
|| noCaseContain(repo.getAuthor(), s)
|| noCaseContain(repo.getDescription(), s))
.forEach(repo -> {
Module module = moduleMap.get(repo.getId());
if (module != null) {
if (repo.getVersionCode() > module.getVersionCode()) {
// Updates
updates.add(repo);
} else {
installed.add(repo);
}
} else {
others.add(repo);
}
});
repoPairs.clear();
if (!updates.isEmpty())
repoPairs.add(new Pair<>(UPDATES, updates));
if (!installed.isEmpty())
repoPairs.add(new Pair<>(INSTALLED, installed));
if (!others.isEmpty())
repoPairs.add(new Pair<>(OTHERS, others));
notifyDataSetChanged();
return false;
}
static class SectionHolder extends RecyclerView.ViewHolder {
@BindView(R.id.section_text) TextView sectionText;
SectionHolder(View itemView) {
super(itemView);
new ReposAdapter$SectionHolder_ViewBinding(this, itemView);
}
}
static class RepoHolder extends RecyclerView.ViewHolder {
@BindView(R.id.title) TextView title;
@BindView(R.id.version_name) TextView versionName;
@BindView(R.id.description) TextView description;
@BindView(R.id.author) TextView author;
@BindView(R.id.info_layout) View infoLayout;
@BindView(R.id.download) ImageView downloadImage;
@BindView(R.id.update_time) TextView updateTime;
RepoHolder(View itemView) {
super(itemView);
new ReposAdapter$RepoHolder_ViewBinding(this, itemView);
}
}
}
package com.topjohnwu.magisk.model.adapters;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
public abstract class SectionedAdapter<S extends RecyclerView.ViewHolder, C extends RecyclerView.ViewHolder>
extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private static final int SECTION_TYPE = Integer.MIN_VALUE;
@NonNull
@Override
final public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
if (viewType == SECTION_TYPE)
return onCreateSectionViewHolder(parent);
return onCreateItemViewHolder(parent, viewType);
}
@Override
@SuppressWarnings("unchecked")
final public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
PositionInfo info = getPositionInfo(position);
if (info.position == -1)
onBindSectionViewHolder((S) holder, info.section);
else
onBindItemViewHolder((C) holder, info.section, info.position);
}
@Override
final public int getItemCount() {
int size, sec;
size = sec = getSectionCount();
for (int i = 0; i < sec; ++i){
size += getItemCount(i);
}
return size;
}
@Override
final public int getItemViewType(int position) {
PositionInfo info = getPositionInfo(position);
if (info.position == -1)
return SECTION_TYPE;
else
return getItemViewType(info.section, info.position);
}
public int getItemViewType(int section, int position) {
return 0;
}
protected int getSectionPosition(int section) {
return getItemPosition(section, -1);
}
protected int getItemPosition(int section, int position) {
int realPosition = 0;
// Previous sections
for (int i = 0; i < section; ++i) {
realPosition += getItemCount(i) + 1;
}
// Current section
realPosition += position + 1;
return realPosition;
}
private PositionInfo getPositionInfo(int position) {
int section = 0;
while (true) {
if (position == 0)
return new PositionInfo(section, -1);
position -= 1;
if (position < getItemCount(section))
return new PositionInfo(section, position);
position -= getItemCount(section++);
}
}
private static class PositionInfo {
int section;
int position;
PositionInfo(int section, int position) {
this.section = section;
this.position = position;
}
}
public abstract int getSectionCount();
public abstract int getItemCount(int section);
public abstract S onCreateSectionViewHolder(ViewGroup parent);
public abstract C onCreateItemViewHolder(ViewGroup parent, int viewType);
public abstract void onBindSectionViewHolder(S holder, int section);
public abstract void onBindItemViewHolder(C holder, int section, int position);
}
package com.topjohnwu.magisk.model.adapters;
import android.app.Activity;
import android.util.DisplayMetrics;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.IdRes;
import androidx.annotation.LayoutRes;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import java.util.List;
public abstract class StringListAdapter<VH extends StringListAdapter.ViewHolder>
extends RecyclerView.Adapter<VH> {
private RecyclerView rv;
private boolean dynamic;
private int screenWidth;
private int txtWidth = -1;
private int padding;
protected List<String> mList;
public StringListAdapter(List<String> list) {
this(list, false);
}
public StringListAdapter(List<String> list, boolean isDynamic) {
mList = list;
dynamic = isDynamic;
}
@NonNull
@Override
public final VH onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(itemLayoutRes(), parent, false);
VH vh = createViewHolder(v);
if (txtWidth < 0)
onUpdateTextWidth(vh);
return vh;
}
@Override
public void onBindViewHolder(@NonNull VH holder, int position) {
holder.txt.setText(mList.get(position));
holder.txt.getLayoutParams().width = txtWidth;
if (dynamic)
onUpdateTextWidth(holder);
}
protected void onUpdateTextWidth(VH vh) {
if (txtWidth < 0) {
txtWidth = screenWidth - padding;
} else {
vh.txt.measure(0, 0);
int width = vh.txt.getMeasuredWidth();
if (width > txtWidth) {
txtWidth = width;
vh.txt.getLayoutParams().width = txtWidth;
}
}
if (rv.getWidth() != txtWidth + padding)
rv.requestLayout();
}
@Override
public void onAttachedToRecyclerView(@NonNull RecyclerView rv) {
DisplayMetrics displayMetrics = new DisplayMetrics();
((Activity) rv.getContext()).getWindowManager()
.getDefaultDisplay().getMetrics(displayMetrics);
screenWidth = displayMetrics.widthPixels;
padding = rv.getPaddingStart() + rv.getPaddingEnd();
this.rv = rv;
}
@Override
public final int getItemCount() {
return mList.size();
}
@LayoutRes
protected abstract int itemLayoutRes();
@NonNull
public abstract VH createViewHolder(@NonNull View v);
public static abstract class ViewHolder extends RecyclerView.ViewHolder {
public TextView txt;
public ViewHolder(@NonNull View itemView) {
super(itemView);
txt = itemView.findViewById(textViewResId());
}
@IdRes
protected abstract int textViewResId();
}
}
package com.topjohnwu.magisk.model.adapters;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.Animation;
import android.view.animation.RotateAnimation;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.recyclerview.widget.RecyclerView;
import com.topjohnwu.magisk.R;
import com.topjohnwu.magisk.data.database.MagiskDB;
import com.topjohnwu.magisk.model.entity.SuLogEntry;
import com.topjohnwu.magisk.view.ExpandableViewHolder;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import butterknife.BindView;
public class SuLogAdapter extends SectionedAdapter<SuLogAdapter.SectionHolder, SuLogAdapter.LogViewHolder> {
private List<List<SuLogEntry>> logEntries;
private Set<Integer> itemExpanded, sectionExpanded;
private MagiskDB suDB;
public SuLogAdapter(MagiskDB db) {
suDB = db;
logEntries = Collections.emptyList();
sectionExpanded = new HashSet<>();
itemExpanded = new HashSet<>();
}
@Override
public int getSectionCount() {
return logEntries.size();
}
@Override
public int getItemCount(int section) {
return sectionExpanded.contains(section) ? logEntries.get(section).size() : 0;
}
@Override
public SectionHolder onCreateSectionViewHolder(ViewGroup parent) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_sulog_group, parent, false);
return new SectionHolder(v);
}
@Override
public LogViewHolder onCreateItemViewHolder(ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_sulog, parent, false);
return new LogViewHolder(v);
}
@Override
public void onBindSectionViewHolder(SectionHolder holder, int section) {
SuLogEntry entry = logEntries.get(section).get(0);
holder.arrow.setRotation(sectionExpanded.contains(section) ? 180 : 0);
holder.itemView.setOnClickListener(v -> {
RotateAnimation rotate;
if (sectionExpanded.contains(section)) {
holder.arrow.setRotation(0);
rotate = new RotateAnimation(180, 0, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
sectionExpanded.remove(section);
notifyItemRangeRemoved(getItemPosition(section, 0), logEntries.get(section).size());
} else {
rotate = new RotateAnimation(0, 180, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
sectionExpanded.add(section);
notifyItemRangeInserted(getItemPosition(section, 0), logEntries.get(section).size());
}
rotate.setDuration(300);
rotate.setFillAfter(true);
holder.arrow.setAnimation(rotate);
});
holder.date.setText(entry.getDateString());
}
@Override
public void onBindItemViewHolder(LogViewHolder holder, int section, int position) {
SuLogEntry entry = logEntries.get(section).get(position);
int realIdx = getItemPosition(section, position);
holder.expandable.setExpanded(itemExpanded.contains(realIdx));
holder.itemView.setOnClickListener(view -> {
if (holder.expandable.isExpanded()) {
holder.expandable.collapse();
itemExpanded.remove(realIdx);
} else {
holder.expandable.expand();
itemExpanded.add(realIdx);
}
});
Context context = holder.itemView.getContext();
holder.appName.setText(entry.appName);
holder.action.setText(entry.action ? R.string.grant : R.string.deny);
holder.pid.setText(context.getString(R.string.pid, entry.fromPid));
holder.uid.setText(context.getString(R.string.target_uid, entry.toUid));
holder.command.setText(context.getString(R.string.command, entry.command));
holder.time.setText(entry.getTimeString());
}
public void notifyDBChanged() {
logEntries = suDB.getLogs();
itemExpanded.clear();
sectionExpanded.clear();
sectionExpanded.add(0);
notifyDataSetChanged();
}
static class SectionHolder extends RecyclerView.ViewHolder {
@BindView(R.id.date) TextView date;
@BindView(R.id.arrow) ImageView arrow;
SectionHolder(View itemView) {
super(itemView);
new SuLogAdapter$SectionHolder_ViewBinding(this, itemView);
}
}
static class LogViewHolder extends RecyclerView.ViewHolder {
@BindView(R.id.app_name) TextView appName;
@BindView(R.id.action) TextView action;
@BindView(R.id.time) TextView time;
@BindView(R.id.pid) TextView pid;
@BindView(R.id.uid) TextView uid;
@BindView(R.id.cmd) TextView command;
@BindView(R.id.expand_layout) ViewGroup expandLayout;
ExpandableViewHolder expandable;
LogViewHolder(View itemView) {
super(itemView);
new SuLogAdapter$LogViewHolder_ViewBinding(this, itemView);
expandable = new ExpandableViewHolder(expandLayout);
}
}
}
package com.topjohnwu.magisk.model.adapters;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentPagerAdapter;
import java.util.ArrayList;
import java.util.List;
public class TabFragmentAdapter extends FragmentPagerAdapter {
private List<Fragment> fragmentList;
private List<String> titleList;
public TabFragmentAdapter(FragmentManager fm) {
super(fm);
fragmentList = new ArrayList<>();
titleList = new ArrayList<>();
}
@Override
public Fragment getItem(int position) {
return fragmentList.get(position);
}
@Override
public int getCount() {
return fragmentList.size();
}
@Override
public CharSequence getPageTitle(int position) {
return titleList.get(position);
}
public void addTab(Fragment fragment, String title) {
fragmentList.add(fragment);
titleList.add(title);
}
}
...@@ -6,8 +6,6 @@ import android.content.Intent; ...@@ -6,8 +6,6 @@ import android.content.Intent;
import android.net.Uri; import android.net.Uri;
import android.os.IBinder; import android.os.IBinder;
import androidx.annotation.Nullable;
import com.topjohnwu.magisk.App; import com.topjohnwu.magisk.App;
import com.topjohnwu.magisk.ClassMap; import com.topjohnwu.magisk.ClassMap;
import com.topjohnwu.magisk.Const; import com.topjohnwu.magisk.Const;
...@@ -31,6 +29,8 @@ import java.util.zip.ZipEntry; ...@@ -31,6 +29,8 @@ import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream; import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream; import java.util.zip.ZipOutputStream;
import androidx.annotation.Nullable;
public class DownloadModuleService extends Service { public class DownloadModuleService extends Service {
private List<ProgressNotification> notifications; private List<ProgressNotification> notifications;
......
...@@ -5,10 +5,10 @@ import android.database.Cursor; ...@@ -5,10 +5,10 @@ import android.database.Cursor;
import android.os.Parcel; import android.os.Parcel;
import android.os.Parcelable; import android.os.Parcelable;
import androidx.annotation.NonNull;
import java.util.List; import java.util.List;
import androidx.annotation.NonNull;
public abstract class BaseModule implements Comparable<BaseModule>, Parcelable { public abstract class BaseModule implements Comparable<BaseModule>, Parcelable {
private String mId, mName, mVersion, mAuthor, mDescription; private String mId, mName, mVersion, mAuthor, mDescription;
......
package com.topjohnwu.magisk.model.entity
import android.content.pm.ApplicationInfo
import android.graphics.drawable.Drawable
import com.topjohnwu.magisk.utils.packageInfo
import com.topjohnwu.magisk.utils.processes
class HideAppInfo(
val info: ApplicationInfo,
val name: String,
val icon: Drawable
) {
val processes = info.packageInfo?.processes?.distinct() ?: listOf(info.packageName)
}
package com.topjohnwu.magisk.model.entity
class HideTarget(line: String) {
private val split = line.split(Regex("\\|"), 2)
val packageName = split[0]
val process = split.getOrElse(1) { packageName }
}
\ No newline at end of file
...@@ -4,10 +4,10 @@ import android.content.ContentValues; ...@@ -4,10 +4,10 @@ import android.content.ContentValues;
import android.content.pm.ApplicationInfo; import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import androidx.annotation.NonNull;
import com.topjohnwu.magisk.utils.Utils; import com.topjohnwu.magisk.utils.Utils;
import androidx.annotation.NonNull;
public class Policy implements Comparable<Policy>{ public class Policy implements Comparable<Policy>{
public static final int INTERACTIVE = 0; public static final int INTERACTIVE = 0;
......
package com.topjohnwu.magisk.model.entity.recycler
import com.skoumal.teanity.databinding.ComparableRvItem
import com.topjohnwu.magisk.R
class ConsoleRvItem(val item: String) : ComparableRvItem<ConsoleRvItem>() {
override val layoutRes: Int = R.layout.item_console
override fun contentSameAs(other: ConsoleRvItem) = itemSameAs(other)
override fun itemSameAs(other: ConsoleRvItem) = item == other.item
}
\ No newline at end of file
package com.topjohnwu.magisk.model.entity.recycler
import com.skoumal.teanity.databinding.ComparableRvItem
import com.skoumal.teanity.extensions.addOnPropertyChangedCallback
import com.skoumal.teanity.rxbus.RxBus
import com.skoumal.teanity.util.DiffObservableList
import com.skoumal.teanity.util.KObservableField
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.model.entity.HideAppInfo
import com.topjohnwu.magisk.model.entity.HideTarget
import com.topjohnwu.magisk.model.entity.state.IndeterminateState
import com.topjohnwu.magisk.model.events.HideProcessEvent
import com.topjohnwu.magisk.utils.inject
import com.topjohnwu.magisk.utils.toggle
class HideRvItem(val item: HideAppInfo, targets: List<HideTarget>) :
ComparableRvItem<HideRvItem>() {
override val layoutRes: Int = R.layout.item_hide_app
val packageName = item.info.packageName.orEmpty()
val items = DiffObservableList(callback).also {
val items = item.processes.map {
val isHidden = targets.any { target ->
packageName == target.packageName && it == target.process
}
HideProcessRvItem(packageName, it, isHidden)
}
it.update(items)
}
val isHiddenState = KObservableField(currentState)
val isExpanded = KObservableField(false)
private val itemsProcess get() = items.filterIsInstance<HideProcessRvItem>()
private val currentState
get() = when (itemsProcess.count { it.isHidden.value }) {
items.size -> IndeterminateState.CHECKED
in 1 until items.size -> IndeterminateState.INDETERMINATE
else -> IndeterminateState.UNCHECKED
}
init {
itemsProcess.forEach {
it.isHidden.addOnPropertyChangedCallback { isHiddenState.value = currentState }
}
}
fun toggle() {
val desiredState = when (isHiddenState.value) {
IndeterminateState.INDETERMINATE,
IndeterminateState.UNCHECKED -> true
IndeterminateState.CHECKED -> false
}
itemsProcess.forEach { it.isHidden.value = desiredState }
isHiddenState.value = currentState
}
fun toggleExpansion() {
if (items.size <= 1) return
isExpanded.toggle()
}
override fun contentSameAs(other: HideRvItem): Boolean = items.all { other.items.contains(it) }
override fun itemSameAs(other: HideRvItem): Boolean = item.info == other.item.info
}
class HideProcessRvItem(
val packageName: String,
val process: String,
isHidden: Boolean
) : ComparableRvItem<HideProcessRvItem>() {
override val layoutRes: Int = R.layout.item_hide_process
val isHidden = KObservableField(isHidden)
private val rxBus: RxBus by inject()
init {
this.isHidden.addOnPropertyChangedCallback {
rxBus.post(HideProcessEvent(this@HideProcessRvItem))
}
}
fun toggle() = isHidden.toggle()
override fun contentSameAs(other: HideProcessRvItem): Boolean = itemSameAs(other)
override fun itemSameAs(other: HideProcessRvItem): Boolean =
packageName == other.packageName && process == other.process
}
\ No newline at end of file
package com.topjohnwu.magisk.model.entity.recycler
import androidx.databinding.ObservableList
import com.skoumal.teanity.databinding.ComparableRvItem
import com.skoumal.teanity.util.DiffObservableList
import com.skoumal.teanity.util.KObservableField
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.model.entity.SuLogEntry
import com.topjohnwu.magisk.utils.toggle
class LogRvItem : ComparableRvItem<LogRvItem>() {
override val layoutRes: Int = R.layout.item_page_log
val items = DiffObservableList(callback)
fun update(list: List<LogItemRvItem>) {
list.firstOrNull()?.isExpanded?.value = true
items.update(list)
}
//two of these will never be present, safe to assume it's unique
override fun contentSameAs(other: LogRvItem): Boolean = false
override fun itemSameAs(other: LogRvItem): Boolean = false
}
class LogItemRvItem(
val items: ObservableList<ComparableRvItem<*>>
) : ComparableRvItem<LogItemRvItem>() {
override val layoutRes: Int = R.layout.item_superuser_log
val date = items.filterIsInstance<LogItemEntryRvItem>().firstOrNull()
?.item?.dateString.orEmpty()
val isExpanded = KObservableField(false)
fun toggle() = isExpanded.toggle()
override fun contentSameAs(other: LogItemRvItem): Boolean = items
.any { !other.items.contains(it) }
override fun itemSameAs(other: LogItemRvItem): Boolean = date == other.date
}
class LogItemEntryRvItem(val item: SuLogEntry) : ComparableRvItem<LogItemEntryRvItem>() {
override val layoutRes: Int = R.layout.item_superuser_log_entry
val isExpanded = KObservableField(false)
fun toggle() = isExpanded.toggle()
override fun contentSameAs(other: LogItemEntryRvItem) = item.fromUid == other.item.fromUid &&
item.toUid == other.item.toUid &&
item.fromPid == other.item.fromPid &&
item.packageName == other.item.packageName &&
item.command == other.item.command &&
item.action == other.item.action &&
item.date == other.item.date
override fun itemSameAs(other: LogItemEntryRvItem) = item.appName == other.item.appName
}
class MagiskLogRvItem : ComparableRvItem<MagiskLogRvItem>() {
override val layoutRes: Int = R.layout.item_page_magisk_log
val items = DiffObservableList(callback)
fun update(list: List<ConsoleRvItem>) {
items.update(list)
}
//two of these will never be present, safe to assume it's unique
override fun contentSameAs(other: MagiskLogRvItem): Boolean = false
override fun itemSameAs(other: MagiskLogRvItem): Boolean = false
}
\ No newline at end of file
package com.topjohnwu.magisk.model.entity.recycler
import android.content.res.Resources
import androidx.annotation.StringRes
import com.skoumal.teanity.databinding.ComparableRvItem
import com.skoumal.teanity.extensions.addOnPropertyChangedCallback
import com.skoumal.teanity.util.KObservableField
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.model.entity.Module
import com.topjohnwu.magisk.model.entity.Repo
import com.topjohnwu.magisk.utils.get
import com.topjohnwu.magisk.utils.toggle
class ModuleRvItem(val item: Module) : ComparableRvItem<ModuleRvItem>() {
override val layoutRes: Int = R.layout.item_module
val lastActionNotice = KObservableField("")
val isChecked = KObservableField(item.isEnabled)
val isDeletable = KObservableField(item.willBeRemoved())
init {
isChecked.addOnPropertyChangedCallback {
when (it) {
true -> item.removeDisableFile().notice(R.string.disable_file_removed)
false -> item.createDisableFile().notice(R.string.disable_file_created)
}
}
isDeletable.addOnPropertyChangedCallback {
when (it) {
true -> item.createRemoveFile().notice(R.string.remove_file_created)
false -> item.deleteRemoveFile().notice(R.string.remove_file_deleted)
}
}
when {
item.isUpdated -> notice(R.string.update_file_created)
item.willBeRemoved() -> notice(R.string.remove_file_created)
}
}
fun toggle() = isChecked.toggle()
fun toggleDelete() = isDeletable.toggle()
@Suppress("unused")
private fun Any.notice(@StringRes info: Int) {
lastActionNotice.value = get<Resources>().getString(info)
}
override fun contentSameAs(other: ModuleRvItem): Boolean = item.version == other.item.version
&& item.versionCode == other.item.versionCode
&& item.description == other.item.description
override fun itemSameAs(other: ModuleRvItem): Boolean = item.name == other.item.name
}
class RepoRvItem(val item: Repo) : ComparableRvItem<RepoRvItem>() {
override val layoutRes: Int = R.layout.item_repo
override fun contentSameAs(other: RepoRvItem): Boolean = item.version == other.item.version
&& item.lastUpdate == other.item.lastUpdate
&& item.versionCode == other.item.versionCode
&& item.description == other.item.description
override fun itemSameAs(other: RepoRvItem): Boolean = item.detailUrl == other.item.detailUrl
}
\ No newline at end of file
package com.topjohnwu.magisk.model.entity.recycler
import android.graphics.drawable.Drawable
import com.skoumal.teanity.databinding.ComparableRvItem
import com.skoumal.teanity.extensions.addOnPropertyChangedCallback
import com.skoumal.teanity.rxbus.RxBus
import com.skoumal.teanity.util.KObservableField
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.model.entity.Policy
import com.topjohnwu.magisk.model.events.PolicyEnableEvent
import com.topjohnwu.magisk.model.events.PolicyUpdateEvent
import com.topjohnwu.magisk.utils.inject
import com.topjohnwu.magisk.utils.toggle
class PolicyRvItem(val item: Policy, val icon: Drawable) : ComparableRvItem<PolicyRvItem>() {
override val layoutRes: Int = R.layout.item_policy
val isExpanded = KObservableField(false)
val isEnabled = KObservableField(item.policy == Policy.ALLOW)
val shouldNotify = KObservableField(item.notification)
val shouldLog = KObservableField(item.logging)
fun toggle() = isExpanded.toggle()
private val rxBus: RxBus by inject()
init {
isEnabled.addOnPropertyChangedCallback {
it ?: return@addOnPropertyChangedCallback
rxBus.post(PolicyEnableEvent(this@PolicyRvItem, it))
}
shouldNotify.addOnPropertyChangedCallback {
it ?: return@addOnPropertyChangedCallback
item.notification = it
rxBus.post(PolicyUpdateEvent.Notification(this@PolicyRvItem))
}
shouldLog.addOnPropertyChangedCallback {
it ?: return@addOnPropertyChangedCallback
item.logging = it
rxBus.post(PolicyUpdateEvent.Log(this@PolicyRvItem))
}
}
override fun contentSameAs(other: PolicyRvItem): Boolean = itemSameAs(other)
override fun itemSameAs(other: PolicyRvItem): Boolean = item.uid == other.item.uid
}
\ No newline at end of file
package com.topjohnwu.magisk.model.entity.recycler
import com.skoumal.teanity.databinding.ComparableRvItem
import com.topjohnwu.magisk.R
class SectionRvItem(val text: String) : ComparableRvItem<SectionRvItem>() {
override val layoutRes: Int = R.layout.item_section
override fun contentSameAs(other: SectionRvItem) = itemSameAs(other)
override fun itemSameAs(other: SectionRvItem) = text == other.text
}
\ No newline at end of file
package com.topjohnwu.magisk.model.entity.recycler
import com.skoumal.teanity.databinding.ComparableRvItem
import com.topjohnwu.magisk.R
class SpinnerRvItem(val item: String) : ComparableRvItem<SpinnerRvItem>() {
override val layoutRes: Int = R.layout.item_spinner
override fun contentSameAs(other: SpinnerRvItem) = itemSameAs(other)
override fun itemSameAs(other: SpinnerRvItem) = item == other.item
}
\ No newline at end of file
package com.topjohnwu.magisk.model.entity.state
enum class IndeterminateState {
INDETERMINATE, CHECKED, UNCHECKED
}
\ No newline at end of file
package com.topjohnwu.magisk.model.events
import com.skoumal.teanity.rxbus.RxBus
import com.topjohnwu.magisk.model.entity.recycler.HideProcessRvItem
import com.topjohnwu.magisk.model.entity.recycler.ModuleRvItem
import com.topjohnwu.magisk.model.entity.recycler.PolicyRvItem
class HideProcessEvent(val item: HideProcessRvItem) : RxBus.Event
class PolicyEnableEvent(val item: PolicyRvItem, val enable: Boolean) : RxBus.Event
sealed class PolicyUpdateEvent(val item: PolicyRvItem) : RxBus.Event {
class Notification(item: PolicyRvItem) : PolicyUpdateEvent(item)
class Log(item: PolicyRvItem) : PolicyUpdateEvent(item)
}
class ModuleUpdatedEvent(val item: ModuleRvItem) : RxBus.Event
package com.topjohnwu.magisk.model.events
import android.app.Activity
import com.skoumal.teanity.viewevents.ViewEvent
import com.topjohnwu.magisk.model.entity.Policy
import com.topjohnwu.magisk.model.entity.Repo
import io.reactivex.subjects.PublishSubject
data class OpenLinkEvent(val url: String) : ViewEvent()
class ManagerInstallEvent : ViewEvent()
class MagiskInstallEvent : ViewEvent()
class ManagerChangelogEvent : ViewEvent()
class MagiskChangelogEvent : ViewEvent()
class UninstallEvent : ViewEvent()
class EnvFixEvent : ViewEvent()
class UpdateSafetyNetEvent : ViewEvent()
class ViewActionEvent(val action: Activity.() -> Unit) : ViewEvent()
class OpenFilePickerEvent : ViewEvent()
class OpenChangelogEvent(val item: Repo) : ViewEvent()
class InstallModuleEvent(val item: Repo) : ViewEvent()
class PageChangedEvent : ViewEvent()
class PermissionEvent(
val permissions: List<String>,
val callback: PublishSubject<Boolean>
) : ViewEvent()
class BackPressEvent : ViewEvent()
class SuDialogEvent(val policy: Policy) : ViewEvent()
class DieEvent : ViewEvent()
\ No newline at end of file
package com.topjohnwu.magisk.model.flash
interface FlashResultListener {
fun onResult(isSuccess: Boolean)
}
\ No newline at end of file
package com.topjohnwu.magisk.model.flash
import android.content.Context
import android.net.Uri
import androidx.core.os.postDelayed
import com.topjohnwu.magisk.tasks.FlashZip
import com.topjohnwu.magisk.utils.Utils
import com.topjohnwu.magisk.utils.inject
import com.topjohnwu.superuser.Shell
import com.topjohnwu.superuser.internal.UiThreadHandler
sealed class Flashing(
uri: Uri,
private val console: MutableList<String>,
log: MutableList<String>,
private val resultListener: FlashResultListener
) : FlashZip(uri, console, log) {
override fun onResult(success: Boolean) {
if (!success) {
console.add("! Installation failed")
}
resultListener.onResult(success)
}
class Install(
uri: Uri,
console: MutableList<String>,
log: MutableList<String>,
resultListener: FlashResultListener
) : Flashing(uri, console, log, resultListener) {
override fun onResult(success: Boolean) {
if (success) {
Utils.loadModules()
}
super.onResult(success)
}
}
class Uninstall(
uri: Uri,
console: MutableList<String>,
log: MutableList<String>,
resultListener: FlashResultListener
) : Flashing(uri, console, log, resultListener) {
private val context: Context by inject()
override fun onResult(success: Boolean) {
if (success) {
UiThreadHandler.handler.postDelayed(3000) {
Shell.su("pm uninstall " + context.packageName).exec()
}
}
super.onResult(success)
}
}
}
\ No newline at end of file
package com.topjohnwu.magisk.model.flash
import android.net.Uri
import com.topjohnwu.magisk.tasks.MagiskInstaller
import com.topjohnwu.superuser.Shell
sealed class Patching(
private val console: MutableList<String>,
logs: MutableList<String>,
private val resultListener: FlashResultListener
) : MagiskInstaller(console, logs) {
override fun onResult(success: Boolean) {
if (success) {
console.add("- All done!")
} else {
Shell.sh("rm -rf $installDir").submit()
console.add("! Installation failed")
}
resultListener.onResult(success)
}
class File(
private val uri: Uri,
console: MutableList<String>,
logs: MutableList<String>,
resultListener: FlashResultListener
) : Patching(console, logs, resultListener) {
override fun operations() =
extractZip() && handleFile(uri) && patchBoot() && storeBoot()
}
class SecondSlot(
console: MutableList<String>,
logs: MutableList<String>,
resultListener: FlashResultListener
) : Patching(console, logs, resultListener) {
override fun operations() =
findSecondaryImage() && extractZip() && patchBoot() && flashBoot() && postOTA()
}
class Direct(
console: MutableList<String>,
logs: MutableList<String>,
resultListener: FlashResultListener
) : Patching(console, logs, resultListener) {
override fun operations() =
findImage() && extractZip() && patchBoot() && flashBoot()
}
}
\ No newline at end of file
package com.topjohnwu.magisk.model.navigation
import android.os.Bundle
import androidx.annotation.AnimRes
import androidx.annotation.AnimatorRes
import androidx.fragment.app.Fragment
import com.skoumal.teanity.viewevents.NavigationDslMarker
import com.skoumal.teanity.viewevents.ViewEvent
import kotlin.reflect.KClass
class MagiskNavigationEvent(
val navDirections: MagiskNavDirectionsBuilder,
val navOptions: MagiskNavOptions,
val animOptions: MagiskAnimBuilder
) : ViewEvent() {
companion object {
operator fun invoke(builder: Builder.() -> Unit) = Builder().apply(builder).build()
}
@NavigationDslMarker
class Builder {
private var animOptions: MagiskAnimBuilder = MagiskAnimBuilder()
private var navOptions: MagiskNavOptions = MagiskNavOptions()
private val directionsBuilder = MagiskNavDirectionsBuilder()
fun args(builder: Bundle.() -> Unit) = directionsBuilder.args(builder)
fun navAnim(builder: MagiskAnimBuilder.() -> Unit) {
animOptions = MagiskAnimBuilder().apply(builder)
}
fun navOptions(builder: MagiskNavOptions.() -> Unit) {
navOptions = MagiskNavOptions().apply(builder)
}
fun navDirections(builder: MagiskNavDirectionsBuilder.() -> Unit) {
directionsBuilder.apply(builder)
}
internal fun build() = MagiskNavigationEvent(directionsBuilder, navOptions, animOptions)
}
}
@NavigationDslMarker
class MagiskNavDirectionsBuilder {
var destination: KClass<out Fragment>? = null
var isActivity: Boolean = false
val args: Bundle = Bundle()
fun args(builder: Bundle.() -> Unit) = args.apply(builder)
}
@NavigationDslMarker
class MagiskNavOptions {
var popUpTo: KClass<*>? = null
var inclusive: Boolean = false
var clearTask: Boolean = false
var singleTop: Boolean = false
}
@NavigationDslMarker
class MagiskAnimBuilder {
@AnimRes
@AnimatorRes
var enter = 0
@AnimRes
@AnimatorRes
var exit = 0
@AnimRes
@AnimatorRes
var popEnter = 0
@AnimRes
@AnimatorRes
var popExit = 0
val anySet: Boolean get() = enter != 0 || exit != 0 || popEnter != 0 || popExit != 0
}
\ No newline at end of file
package com.topjohnwu.magisk.model.navigation
import com.topjohnwu.magisk.ui.hide.MagiskHideFragment
import com.topjohnwu.magisk.ui.home.MagiskFragment
import com.topjohnwu.magisk.ui.log.LogFragment
import com.topjohnwu.magisk.ui.module.ModulesFragment
import com.topjohnwu.magisk.ui.module.ReposFragment
import com.topjohnwu.magisk.ui.settings.SettingsFragment
import com.topjohnwu.magisk.ui.superuser.SuperuserFragment
object Navigation {
fun home() = MagiskNavigationEvent {
navDirections { destination = MagiskFragment::class }
navOptions { popUpTo = MagiskFragment::class }
}
fun superuser() = MagiskNavigationEvent {
navDirections { destination = SuperuserFragment::class }
}
fun modules() = MagiskNavigationEvent {
navDirections { destination = ModulesFragment::class }
}
fun repos() = MagiskNavigationEvent {
navDirections { destination = ReposFragment::class }
}
fun hide() = MagiskNavigationEvent {
navDirections { destination = MagiskHideFragment::class }
}
fun log() = MagiskNavigationEvent {
navDirections { destination = LogFragment::class }
}
fun settings() = MagiskNavigationEvent {
navDirections { destination = SettingsFragment::class }
}
fun fromSection(section: String) = when (section) {
"superuser" -> superuser()
"modules" -> modules()
"downloads" -> repos()
"magiskhide" -> hide()
"log" -> log()
"settings" -> settings()
else -> home()
}
object Main {
const val OPEN_NAV = 1
}
}
package com.topjohnwu.magisk.model.navigation
import androidx.fragment.app.Fragment
import kotlin.reflect.KClass
interface Navigator {
//TODO Elevate Fragment to MagiskFragment<*,*> once everything is on board with it
val baseFragments: List<KClass<out Fragment>>
fun navigateTo(event: MagiskNavigationEvent)
}
\ No newline at end of file
package com.topjohnwu.magisk.model.observer
import androidx.databinding.Observable
import androidx.databinding.ObservableField
import java.io.Serializable
class Observer<T>(vararg dependencies: Observable, private val observer: () -> T) :
ObservableField<T>(*dependencies), Serializable {
val value: T get() = observer()
@Deprecated(
message = "Use KObservableField.value syntax from code",
replaceWith = ReplaceWith("value")
)
override fun get(): T {
return value
}
@Deprecated(
message = "Observer cannot be set",
level = DeprecationLevel.HIDDEN
)
override fun set(newValue: T) {
}
override fun toString(): String {
return "Observer(value=$value)"
}
}
\ No newline at end of file
package com.topjohnwu.magisk.model.permissions
typealias SimpleCallback = () -> Unit
typealias PermissionRationaleCallback = (List<String>) -> Unit
class PermissionRequestBuilder {
private var onSuccessCallback: SimpleCallback = {}
private var onFailureCallback: SimpleCallback = {}
private var onShowRationaleCallback: PermissionRationaleCallback = {}
fun onSuccess(callback: SimpleCallback) {
onSuccessCallback = callback
}
fun onFailure(callback: SimpleCallback) {
onFailureCallback = callback
}
fun onShowRationale(callback: PermissionRationaleCallback) {
onShowRationaleCallback = callback
}
fun build(): PermissionRequest {
return PermissionRequest(onSuccessCallback, onFailureCallback, onShowRationaleCallback)
}
}
class PermissionRequest(
private val onSuccessCallback: SimpleCallback,
private val onFailureCallback: SimpleCallback,
private val onShowRationaleCallback: PermissionRationaleCallback
) {
fun onSuccess() = onSuccessCallback()
fun onFailure() = onFailureCallback()
fun onShowRationale(permissions: List<String>) = onShowRationaleCallback(permissions)
}
\ No newline at end of file
...@@ -61,12 +61,12 @@ public class GeneralReceiver extends BroadcastReceiver { ...@@ -61,12 +61,12 @@ public class GeneralReceiver extends BroadcastReceiver {
case Intent.ACTION_PACKAGE_REPLACED: case Intent.ACTION_PACKAGE_REPLACED:
// This will only work pre-O // This will only work pre-O
if (Config.get(Config.Key.SU_REAUTH)) { if (Config.get(Config.Key.SU_REAUTH)) {
app.mDB.deletePolicy(getPkg(intent)); app.getDB().deletePolicy(getPkg(intent));
} }
break; break;
case Intent.ACTION_PACKAGE_FULLY_REMOVED: case Intent.ACTION_PACKAGE_FULLY_REMOVED:
String pkg = getPkg(intent); String pkg = getPkg(intent);
app.mDB.deletePolicy(pkg); app.getDB().deletePolicy(pkg);
Shell.su("magiskhide --rm " + pkg).submit(); Shell.su("magiskhide --rm " + pkg).submit();
break; break;
case Intent.ACTION_LOCALE_CHANGED: case Intent.ACTION_LOCALE_CHANGED:
......
...@@ -4,6 +4,12 @@ import android.content.Context; ...@@ -4,6 +4,12 @@ import android.content.Context;
import android.net.Network; import android.net.Network;
import android.net.Uri; import android.net.Uri;
import com.google.common.util.concurrent.ListenableFuture;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import androidx.annotation.MainThread; import androidx.annotation.MainThread;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
...@@ -11,12 +17,6 @@ import androidx.annotation.RequiresApi; ...@@ -11,12 +17,6 @@ import androidx.annotation.RequiresApi;
import androidx.work.Data; import androidx.work.Data;
import androidx.work.ListenableWorker; import androidx.work.ListenableWorker;
import com.google.common.util.concurrent.ListenableFuture;
import java.util.List;
import java.util.Set;
import java.util.UUID;
public abstract class DelegateWorker { public abstract class DelegateWorker {
private ListenableWorker worker; private ListenableWorker worker;
......
...@@ -34,7 +34,7 @@ import java.util.concurrent.Future; ...@@ -34,7 +34,7 @@ import java.util.concurrent.Future;
public class UpdateRepos { public class UpdateRepos {
private static final DateFormat DATE_FORMAT; private static final DateFormat DATE_FORMAT;
private App app = App.self; private final App app = App.self;
private Set<String> cached; private Set<String> cached;
private Queue<Pair<String, Date>> moduleQueue; private Queue<Pair<String, Date>> moduleQueue;
...@@ -116,17 +116,17 @@ public class UpdateRepos { ...@@ -116,17 +116,17 @@ public class UpdateRepos {
Pair<String, Date> pair = moduleQueue.poll(); Pair<String, Date> pair = moduleQueue.poll();
if (pair == null) if (pair == null)
return; return;
Repo repo = app.repoDB.getRepo(pair.first); Repo repo = app.getRepoDB().getRepo(pair.first);
try { try {
if (repo == null) if (repo == null)
repo = new Repo(pair.first); repo = new Repo(pair.first);
else else
cached.remove(pair.first); cached.remove(pair.first);
repo.update(pair.second); repo.update(pair.second);
app.repoDB.addRepo(repo); app.getRepoDB().addRepo(repo);
} catch (Repo.IllegalRepoException e) { } catch (Repo.IllegalRepoException e) {
Logger.debug(e.getMessage()); Logger.debug(e.getMessage());
app.repoDB.removeRepo(pair.first); app.getRepoDB().removeRepo(pair.first);
} }
} }
}); });
...@@ -134,7 +134,7 @@ public class UpdateRepos { ...@@ -134,7 +134,7 @@ public class UpdateRepos {
} }
private void fullReload() { private void fullReload() {
Cursor c = app.repoDB.getRawCursor(); Cursor c = app.getRepoDB().getRawCursor();
runTasks(() -> { runTasks(() -> {
while (true) { while (true) {
Repo repo; Repo repo;
...@@ -145,10 +145,10 @@ public class UpdateRepos { ...@@ -145,10 +145,10 @@ public class UpdateRepos {
} }
try { try {
repo.update(); repo.update();
app.repoDB.addRepo(repo); app.getRepoDB().addRepo(repo);
} catch (Repo.IllegalRepoException e) { } catch (Repo.IllegalRepoException e) {
Logger.debug(e.getMessage()); Logger.debug(e.getMessage());
app.repoDB.removeRepo(repo); app.getRepoDB().removeRepo(repo);
} }
} }
}); });
...@@ -157,12 +157,12 @@ public class UpdateRepos { ...@@ -157,12 +157,12 @@ public class UpdateRepos {
public void exec(boolean force) { public void exec(boolean force) {
Event.reset(Event.REPO_LOAD_DONE); Event.reset(Event.REPO_LOAD_DONE);
App.THREAD_POOL.execute(() -> { App.THREAD_POOL.execute(() -> {
cached = Collections.synchronizedSet(app.repoDB.getRepoIDSet()); cached = Collections.synchronizedSet(app.getRepoDB().getRepoIDSet());
moduleQueue = new ConcurrentLinkedQueue<>(); moduleQueue = new ConcurrentLinkedQueue<>();
if (loadPages()) { if (loadPages()) {
// The leftover cached means they are removed from online repo // The leftover cached means they are removed from online repo
app.repoDB.removeRepo(cached); app.getRepoDB().removeRepo(cached);
} else if (force) { } else if (force) {
fullReload(); fullReload();
} }
......
package com.topjohnwu.magisk.ui;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.appcompat.app.ActionBarDrawerToggle;
import androidx.appcompat.widget.Toolbar;
import androidx.drawerlayout.widget.DrawerLayout;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentTransaction;
import com.google.android.material.navigation.NavigationView;
import com.topjohnwu.magisk.ClassMap;
import com.topjohnwu.magisk.Config;
import com.topjohnwu.magisk.Const;
import com.topjohnwu.magisk.R;
import com.topjohnwu.magisk.ui.base.BaseActivity;
import com.topjohnwu.magisk.ui.hide.MagiskHideFragment;
import com.topjohnwu.magisk.ui.home.MagiskFragment;
import com.topjohnwu.magisk.ui.log.LogFragment;
import com.topjohnwu.magisk.ui.module.ModulesFragment;
import com.topjohnwu.magisk.ui.module.ReposFragment;
import com.topjohnwu.magisk.ui.settings.SettingsFragment;
import com.topjohnwu.magisk.ui.superuser.SuperuserFragment;
import com.topjohnwu.magisk.utils.Utils;
import com.topjohnwu.net.Networking;
import com.topjohnwu.superuser.Shell;
import butterknife.BindView;
public class MainActivity extends BaseActivity
implements NavigationView.OnNavigationItemSelectedListener {
private final Handler mDrawerHandler = new Handler();
private int mDrawerItem;
private static boolean fromShortcut = false;
@BindView(R.id.toolbar) public Toolbar toolbar;
@BindView(R.id.drawer_layout) DrawerLayout drawer;
@BindView(R.id.nav_view) NavigationView navigationView;
private float toolbarElevation;
@Override
public int getDarkTheme() {
return R.style.AppTheme_Dark;
}
@Override
protected void onCreate(final Bundle savedInstanceState) {
if (!SplashActivity.DONE) {
startActivity(new Intent(this, ClassMap.get(SplashActivity.class)));
finish();
}
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
new MainActivity_ViewBinding(this);
checkHideSection();
setSupportActionBar(toolbar);
ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(this, drawer, toolbar, R.string.magisk, R.string.magisk) {
@Override
public void onDrawerOpened(View drawerView) {
super.onDrawerOpened(drawerView);
super.onDrawerSlide(drawerView, 0); // this disables the arrow @ completed tate
}
@Override
public void onDrawerSlide(View drawerView, float slideOffset) {
super.onDrawerSlide(drawerView, 0); // this disables the animation
}
};
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
toolbarElevation = toolbar.getElevation();
}
drawer.addDrawerListener(toggle);
toggle.syncState();
if (savedInstanceState == null) {
String section = getIntent().getStringExtra(Const.Key.OPEN_SECTION);
fromShortcut = section != null;
navigate(section);
}
navigationView.setNavigationItemSelectedListener(this);
}
@Override
public void onBackPressed() {
if (drawer.isDrawerOpen(navigationView)) {
drawer.closeDrawer(navigationView);
} else if (mDrawerItem != R.id.magisk && !fromShortcut) {
navigate(R.id.magisk);
} else {
finish();
}
}
@Override
public boolean onNavigationItemSelected(@NonNull final MenuItem menuItem) {
mDrawerHandler.removeCallbacksAndMessages(null);
mDrawerHandler.postDelayed(() -> navigate(menuItem.getItemId()), 250);
drawer.closeDrawer(navigationView);
return true;
}
public void checkHideSection() {
Menu menu = navigationView.getMenu();
menu.findItem(R.id.magiskhide).setVisible(Shell.rootAccess() &&
(boolean) Config.get(Config.Key.MAGISKHIDE));
menu.findItem(R.id.modules).setVisible(Shell.rootAccess() && Config.magiskVersionCode >= 0);
menu.findItem(R.id.downloads).setVisible(Networking.checkNetworkStatus(this)
&& Shell.rootAccess() && Config.magiskVersionCode >= 0);
menu.findItem(R.id.log).setVisible(Shell.rootAccess());
menu.findItem(R.id.superuser).setVisible(Utils.showSuperUser());
}
public void navigate(String item) {
int itemId = R.id.magisk;
if (item != null) {
switch (item) {
case "superuser":
itemId = R.id.superuser;
break;
case "modules":
itemId = R.id.modules;
break;
case "downloads":
itemId = R.id.downloads;
break;
case "magiskhide":
itemId = R.id.magiskhide;
break;
case "log":
itemId = R.id.log;
break;
case "settings":
itemId = R.id.settings;
break;
}
}
navigate(itemId);
}
public void navigate(int itemId) {
mDrawerItem = itemId;
navigationView.setCheckedItem(itemId);
switch (itemId) {
case R.id.magisk:
fromShortcut = false;
displayFragment(new MagiskFragment(), true);
break;
case R.id.superuser:
displayFragment(new SuperuserFragment(), true);
break;
case R.id.modules:
displayFragment(new ModulesFragment(), true);
break;
case R.id.downloads:
displayFragment(new ReposFragment(), true);
break;
case R.id.magiskhide:
displayFragment(new MagiskHideFragment(), true);
break;
case R.id.log:
displayFragment(new LogFragment(), false);
break;
case R.id.settings:
displayFragment(new SettingsFragment(), true);
break;
}
}
private void displayFragment(@NonNull Fragment navFragment, boolean setElevation) {
supportInvalidateOptionsMenu();
getSupportFragmentManager()
.beginTransaction()
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
.replace(R.id.content_frame, navFragment)
.commitNow();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
toolbar.setElevation(setElevation ? toolbarElevation : 0);
}
}
}
package com.topjohnwu.magisk.ui
import android.content.Intent
import android.os.Bundle
import androidx.core.view.GravityCompat
import androidx.fragment.app.Fragment
import com.topjohnwu.magisk.ClassMap
import com.topjohnwu.magisk.Config
import com.topjohnwu.magisk.Const.Key.OPEN_SECTION
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.databinding.ActivityMainBinding
import com.topjohnwu.magisk.model.navigation.Navigation
import com.topjohnwu.magisk.ui.base.MagiskActivity
import com.topjohnwu.magisk.ui.hide.MagiskHideFragment
import com.topjohnwu.magisk.ui.log.LogFragment
import com.topjohnwu.magisk.ui.module.ModulesFragment
import com.topjohnwu.magisk.ui.module.ReposFragment
import com.topjohnwu.magisk.ui.settings.SettingsFragment
import com.topjohnwu.magisk.ui.superuser.SuperuserFragment
import com.topjohnwu.magisk.utils.Utils
import com.topjohnwu.net.Networking
import com.topjohnwu.superuser.Shell
import org.koin.androidx.viewmodel.ext.android.viewModel
import kotlin.reflect.KClass
import com.topjohnwu.magisk.ui.home.MagiskFragment as HomeFragment
open class MainActivity : MagiskActivity<MainViewModel, ActivityMainBinding>() {
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
override val baseFragments: List<KClass<out Fragment>> = listOf(
HomeFragment::class,
SuperuserFragment::class,
MagiskHideFragment::class,
ModulesFragment::class,
ReposFragment::class,
LogFragment::class,
SettingsFragment::class
)
/*override fun getDarkTheme(): Int {
return R.style.AppTheme_Dark
}*/
override fun onCreate(savedInstanceState: Bundle?) {
if (!SplashActivity.DONE) {
startActivity(Intent(this, ClassMap.get<Any>(SplashActivity::class.java)))
finish()
}
super.onCreate(savedInstanceState)
checkHideSection()
setSupportActionBar(binding.mainInclude.mainToolbar)
if (savedInstanceState == null) {
intent.getStringExtra(OPEN_SECTION)?.let {
onEventDispatched(Navigation.fromSection(it))
}
}
}
override fun setTitle(title: CharSequence?) {
supportActionBar?.title = title
}
override fun setTitle(titleId: Int) {
supportActionBar?.setTitle(titleId)
}
override fun onBackPressed() {
if (binding.drawerLayout.isDrawerOpen(binding.navView)) {
binding.drawerLayout.closeDrawer(binding.navView)
} else {
super.onBackPressed()
}
}
override fun onSimpleEventDispatched(event: Int) {
super.onSimpleEventDispatched(event)
when (event) {
Navigation.Main.OPEN_NAV -> openNav()
}
}
private fun openNav() = binding.drawerLayout.openDrawer(GravityCompat.START)
fun checkHideSection() {
val menu = binding.navView.menu
menu.findItem(R.id.magiskHideFragment).isVisible =
Shell.rootAccess() && Config.get<Any>(Config.Key.MAGISKHIDE) as Boolean
menu.findItem(R.id.modulesFragment).isVisible =
Shell.rootAccess() && Config.magiskVersionCode >= 0
menu.findItem(R.id.reposFragment).isVisible =
(Networking.checkNetworkStatus(this) && Shell.rootAccess() && Config.magiskVersionCode >= 0)
menu.findItem(R.id.logFragment).isVisible =
Shell.rootAccess()
menu.findItem(R.id.superuserFragment).isVisible =
Utils.showSuperUser()
}
}
package com.topjohnwu.magisk.ui
import android.view.MenuItem
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.model.navigation.Navigation
import com.topjohnwu.magisk.ui.base.MagiskViewModel
class MainViewModel : MagiskViewModel() {
fun navPressed() = Navigation.Main.OPEN_NAV.publish()
fun navigationItemPressed(item: MenuItem): Boolean {
when (item.itemId) {
R.id.magiskFragment -> Navigation.home()
R.id.superuserFragment -> Navigation.superuser()
R.id.magiskHideFragment -> Navigation.hide()
R.id.modulesFragment -> Navigation.modules()
R.id.reposFragment -> Navigation.repos()
R.id.logFragment -> Navigation.log()
R.id.settings -> Navigation.settings()
else -> null
}?.publish()?.let { return@navigationItemPressed true }
return false
}
}
package com.topjohnwu.magisk.ui;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.text.TextUtils;
import androidx.appcompat.app.AlertDialog;
import com.topjohnwu.magisk.BuildConfig;
import com.topjohnwu.magisk.ClassMap;
import com.topjohnwu.magisk.Config;
import com.topjohnwu.magisk.Const;
import com.topjohnwu.magisk.R;
import com.topjohnwu.magisk.data.database.RepoDatabaseHelper;
import com.topjohnwu.magisk.tasks.CheckUpdates;
import com.topjohnwu.magisk.tasks.UpdateRepos;
import com.topjohnwu.magisk.ui.base.BaseActivity;
import com.topjohnwu.magisk.utils.LocaleManager;
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;
public class SplashActivity extends BaseActivity {
public static boolean DONE = false;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Shell.getShell(shell -> {
if (Config.magiskVersionCode > 0 &&
Config.magiskVersionCode < Const.MAGISK_VER.MIN_SUPPORT) {
new AlertDialog.Builder(this)
.setTitle(R.string.unsupport_magisk_title)
.setMessage(R.string.unsupport_magisk_message)
.setNegativeButton(R.string.ok, null)
.setOnDismissListener(dialog -> finish())
.show();
} else {
initAndStart();
}
});
}
private void initAndStart() {
String pkg = Config.get(Config.Key.SU_MANAGER);
if (pkg != null && getPackageName().equals(BuildConfig.APPLICATION_ID)) {
Config.remove(Config.Key.SU_MANAGER);
Shell.su("pm uninstall " + pkg).submit();
}
if (TextUtils.equals(pkg, getPackageName())) {
try {
// We are the manager, remove com.topjohnwu.magisk as it could be malware
getPackageManager().getApplicationInfo(BuildConfig.APPLICATION_ID, 0);
Shell.su("pm uninstall " + BuildConfig.APPLICATION_ID).submit();
} catch (PackageManager.NameNotFoundException ignored) {}
}
// Dynamic detect all locales
LocaleManager.loadAvailableLocales(R.string.app_changelog);
// Set default configs
Config.initialize();
// Create notification channel on Android O
Notifications.setup(this);
// Schedule periodic update checks
Utils.scheduleUpdateCheck();
CheckUpdates.check();
// Setup shortcuts
Shortcuts.setup(this);
// Create repo database
app.repoDB = new RepoDatabaseHelper(this);
// Magisk working as expected
if (Shell.rootAccess() && Config.magiskVersionCode > 0) {
// Load modules
Utils.loadModules(false);
// Load repos
if (Networking.checkNetworkStatus(this))
new UpdateRepos().exec();
}
Intent intent = new Intent(this, ClassMap.get(MainActivity.class));
intent.putExtra(Const.Key.OPEN_SECTION, getIntent().getStringExtra(Const.Key.OPEN_SECTION));
DONE = true;
startActivity(intent);
finish();
}
}
package com.topjohnwu.magisk.ui
import android.content.Intent
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.tasks.CheckUpdates
import com.topjohnwu.magisk.tasks.UpdateRepos
import com.topjohnwu.magisk.utils.LocaleManager
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
open class SplashActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Shell.getShell {
if (Config.magiskVersionCode > 0 && Config.magiskVersionCode < Const.MAGISK_VER.MIN_SUPPORT) {
AlertDialog.Builder(this)
.setTitle(R.string.unsupport_magisk_title)
.setMessage(R.string.unsupport_magisk_message)
.setNegativeButton(R.string.ok, null)
.setOnDismissListener { finish() }
.show()
} else {
initAndStart()
}
}
}
private fun initAndStart() {
val pkg = Config.get<String>(Config.Key.SU_MANAGER)
if (pkg != null && packageName == BuildConfig.APPLICATION_ID) {
Config.remove(Config.Key.SU_MANAGER)
Shell.su("pm uninstall $pkg").submit()
}
if (TextUtils.equals(pkg, packageName)) {
runCatching {
// We are the manager, remove com.topjohnwu.magisk as it could be malware
packageManager.getApplicationInfo(BuildConfig.APPLICATION_ID, 0)
Shell.su("pm uninstall " + BuildConfig.APPLICATION_ID).submit()
}
}
// Dynamic detect all locales
LocaleManager.loadAvailableLocales(R.string.app_changelog)
// Set default configs
Config.initialize()
// Create notification channel on Android O
Notifications.setup(this)
// Schedule periodic update checks
Utils.scheduleUpdateCheck()
CheckUpdates.check()
// Setup shortcuts
Shortcuts.setup(this)
// Magisk working as expected
if (Shell.rootAccess() && Config.magiskVersionCode > 0) {
// Load modules
Utils.loadModules(false)
// Load repos
if (Networking.checkNetworkStatus(this))
UpdateRepos().exec()
}
val intent = Intent(this, ClassMap.get<Any>(MainActivity::class.java))
intent.putExtra(Const.Key.OPEN_SECTION, getIntent().getStringExtra(Const.Key.OPEN_SECTION))
DONE = true
startActivity(intent)
finish()
}
companion object {
var DONE = false
}
}
package com.topjohnwu.magisk.ui.base
import android.content.Intent
interface ActivityResultListener {
fun onActivityResult(resultCode: Int, data: Intent?)
}
\ No newline at end of file
package com.topjohnwu.magisk.ui.base;
import android.Manifest;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.WindowManager;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StyleRes;
import androidx.appcompat.app.AppCompatActivity;
import androidx.collection.SparseArrayCompat;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import com.topjohnwu.magisk.App;
import com.topjohnwu.magisk.Config;
import com.topjohnwu.magisk.Const;
import com.topjohnwu.magisk.R;
import com.topjohnwu.magisk.utils.Event;
import com.topjohnwu.magisk.utils.LocaleManager;
public abstract class BaseActivity extends AppCompatActivity implements Event.AutoListener {
static int[] EMPTY_INT_ARRAY = new int[0];
private SparseArrayCompat<ActivityResultListener> resultListeners = new SparseArrayCompat<>();
public App app = App.self;
@Override
public int[] getListeningEvents() {
return EMPTY_INT_ARRAY;
}
@Override
public void onEvent(int event) {}
@StyleRes
public int getDarkTheme() {
return -1;
}
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(LocaleManager.getLocaleContext(base, LocaleManager.locale));
}
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
Event.register(this);
if (getDarkTheme() != -1 && (boolean) Config.get(Config.Key.DARK_THEME)) {
setTheme(getDarkTheme());
}
super.onCreate(savedInstanceState);
}
@Override
protected void onDestroy() {
Event.unregister(this);
super.onDestroy();
}
protected void setFloating() {
boolean isTablet = getResources().getBoolean(R.bool.isTablet);
if (isTablet) {
WindowManager.LayoutParams params = getWindow().getAttributes();
params.height = getResources().getDimensionPixelSize(R.dimen.floating_height);
params.width = getResources().getDimensionPixelSize(R.dimen.floating_width);
params.alpha = 1.0f;
params.dimAmount = 0.6f;
params.flags |= 2;
getWindow().setAttributes(params);
setFinishOnTouchOutside(true);
}
}
protected void lockOrientation() {
if (Build.VERSION.SDK_INT < 18)
setRequestedOrientation(getResources().getConfiguration().orientation);
else
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LOCKED);
}
public void runWithExternalRW(Runnable callback) {
runWithPermissions(new String[] { Manifest.permission.WRITE_EXTERNAL_STORAGE }, callback);
}
public void runWithPermissions(String[] permissions, Runnable callback) {
runWithPermissions(this, permissions, callback);
}
public static void runWithPermissions(Context context, String[] permissions, Runnable callback) {
boolean granted = true;
for (String perm : permissions) {
if (ContextCompat.checkSelfPermission(context, perm) != PackageManager.PERMISSION_GRANTED)
granted = false;
}
if (granted) {
Const.EXTERNAL_PATH.mkdirs();
callback.run();
} else {
// Passed in context should be an activity if not granted, need to show dialog!
if (context instanceof BaseActivity) {
BaseActivity activity = (BaseActivity) context;
int code = callback.hashCode() & 0xFFFF;
activity.resultListeners.put(code, ((i, d) -> callback.run()));
ActivityCompat.requestPermissions(activity, permissions, code);
}
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
onActivityResultListener(requestCode, resultCode, data);
}
private void onActivityResultListener(int requestCode, int resultCode, Intent data) {
ActivityResultListener listener = resultListeners.get(requestCode);
if (listener != null) {
resultListeners.remove(requestCode);
listener.onActivityResult(resultCode, data);
}
}
public void startActivityForResult(Intent intent, int requestCode, ActivityResultListener listener) {
resultListeners.put(requestCode, listener);
super.startActivityForResult(intent, requestCode);
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
boolean grant = true;
for (int result : grantResults) {
if (result != PackageManager.PERMISSION_GRANTED)
grant = false;
}
if (grant)
onActivityResultListener(requestCode, 0, null);
else
resultListeners.remove(requestCode);
}
public interface ActivityResultListener {
void onActivityResult(int resultCode, Intent data);
}
@Override
public SharedPreferences getSharedPreferences(String name, int mode) {
if (TextUtils.equals(name, getPackageName() + "_preferences"))
return app.prefs;
return super.getSharedPreferences(name, mode);
}
}
package com.topjohnwu.magisk.ui.base;
import android.content.Intent;
import androidx.fragment.app.Fragment;
import com.topjohnwu.magisk.App;
import com.topjohnwu.magisk.utils.Event;
import butterknife.Unbinder;
public abstract class BaseFragment extends Fragment implements Event.AutoListener {
public App app = App.self;
protected Unbinder unbinder = null;
@Override
public void onResume() {
super.onResume();
Event.register(this);
}
@Override
public void onPause() {
Event.unregister(this);
super.onPause();
}
@Override
public void onDestroyView() {
super.onDestroyView();
if (unbinder != null)
unbinder.unbind();
}
@Override
public void startActivityForResult(Intent intent, int requestCode) {
startActivityForResult(intent, requestCode, (resultCode, data) ->
onActivityResult(requestCode, resultCode, data));
}
public void startActivityForResult(Intent intent, int requestCode,
BaseActivity.ActivityResultListener listener) {
((BaseActivity) requireActivity()).startActivityForResult(intent, requestCode, listener);
}
protected void runWithExternalRW(Runnable callback) {
((BaseActivity) requireActivity()).runWithExternalRW(callback);
}
@Override
public int[] getListeningEvents() {
return BaseActivity.EMPTY_INT_ARRAY;
}
@Override
public void onEvent(int event) {}
}
...@@ -8,6 +8,10 @@ import android.view.LayoutInflater; ...@@ -8,6 +8,10 @@ import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import com.topjohnwu.magisk.App;
import com.topjohnwu.magisk.R;
import com.topjohnwu.magisk.utils.Event;
import androidx.preference.Preference; import androidx.preference.Preference;
import androidx.preference.PreferenceCategory; import androidx.preference.PreferenceCategory;
import androidx.preference.PreferenceFragmentCompat; import androidx.preference.PreferenceFragmentCompat;
...@@ -16,10 +20,6 @@ import androidx.preference.PreferenceScreen; ...@@ -16,10 +20,6 @@ import androidx.preference.PreferenceScreen;
import androidx.preference.PreferenceViewHolder; import androidx.preference.PreferenceViewHolder;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import com.topjohnwu.magisk.App;
import com.topjohnwu.magisk.R;
import com.topjohnwu.magisk.utils.Event;
public abstract class BasePreferenceFragment extends PreferenceFragmentCompat public abstract class BasePreferenceFragment extends PreferenceFragmentCompat
implements SharedPreferences.OnSharedPreferenceChangeListener, Event.AutoListener { implements SharedPreferences.OnSharedPreferenceChangeListener, Event.AutoListener {
...@@ -28,21 +28,21 @@ public abstract class BasePreferenceFragment extends PreferenceFragmentCompat ...@@ -28,21 +28,21 @@ public abstract class BasePreferenceFragment extends PreferenceFragmentCompat
@Override @Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View v = super.onCreateView(inflater, container, savedInstanceState); View v = super.onCreateView(inflater, container, savedInstanceState);
app.prefs.registerOnSharedPreferenceChangeListener(this); app.getPrefs().registerOnSharedPreferenceChangeListener(this);
Event.register(this); Event.register(this);
return v; return v;
} }
@Override @Override
public void onDestroyView() { public void onDestroyView() {
app.prefs.unregisterOnSharedPreferenceChangeListener(this); app.getPrefs().unregisterOnSharedPreferenceChangeListener(this);
Event.unregister(this); Event.unregister(this);
super.onDestroyView(); super.onDestroyView();
} }
@Override @Override
public int[] getListeningEvents() { public int[] getListeningEvents() {
return BaseActivity.EMPTY_INT_ARRAY; return new int[0];
} }
@Override @Override
......
package com.topjohnwu.magisk.ui.base
import android.content.Intent
interface IBaseLeanback {
fun runWithExternalRW(callback: Runnable)
fun runWithPermissions(vararg permissions: String, callback: Runnable)
fun startActivityForResult(intent: Intent, requestCode: Int, listener: ActivityResultListener)
}
\ No newline at end of file
package com.topjohnwu.magisk.ui.base
import android.content.Intent
import android.os.Bundle
import androidx.annotation.CallSuper
import androidx.appcompat.app.AppCompatDelegate
import androidx.core.net.toUri
import androidx.databinding.ViewDataBinding
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentTransaction
import com.karumi.dexter.Dexter
import com.karumi.dexter.MultiplePermissionsReport
import com.karumi.dexter.PermissionToken
import com.karumi.dexter.listener.PermissionRequest
import com.karumi.dexter.listener.multi.MultiplePermissionsListener
import com.ncapdevi.fragnav.FragNavController
import com.ncapdevi.fragnav.FragNavTransactionOptions
import com.skoumal.teanity.viewevents.ViewEvent
import com.topjohnwu.magisk.Config
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.model.navigation.MagiskAnimBuilder
import com.topjohnwu.magisk.model.navigation.MagiskNavigationEvent
import com.topjohnwu.magisk.model.navigation.Navigator
import com.topjohnwu.magisk.model.permissions.PermissionRequestBuilder
import com.topjohnwu.magisk.utils.Utils
import timber.log.Timber
import kotlin.reflect.KClass
abstract class MagiskActivity<ViewModel : MagiskViewModel, Binding : ViewDataBinding> :
MagiskLeanbackActivity<ViewModel, Binding>(), FragNavController.RootFragmentListener,
Navigator {
override val numberOfRootFragments: Int get() = baseFragments.size
override val baseFragments: List<KClass<out Fragment>> = listOf()
protected open val defaultPosition: Int = 0
protected val navigationController get() = if (navHostId == 0) null else _navigationController
private val _navigationController by lazy {
if (navHostId == 0) throw IllegalStateException("Did you forget to override \"navHostId\"?")
FragNavController(supportFragmentManager, navHostId)
}
init {
val isDarkTheme = Config.get<Boolean>(Config.Key.DARK_THEME)
val theme = if (isDarkTheme) {
AppCompatDelegate.MODE_NIGHT_YES
} else {
AppCompatDelegate.MODE_NIGHT_NO
}
AppCompatDelegate.setDefaultNightMode(theme)
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
navigationController?.apply {
rootFragmentListener = this@MagiskActivity
initialize(defaultPosition, savedInstanceState)
}
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
navigationController?.onSaveInstanceState(outState)
}
@CallSuper
override fun onEventDispatched(event: ViewEvent) {
super.onEventDispatched(event)
when (event) {
is BackPressEvent -> onBackPressed()
is MagiskNavigationEvent -> navigateTo(event)
is ViewActionEvent -> event.action(this)
is PermissionEvent -> withPermissions(*event.permissions.toTypedArray()) {
onSuccess { event.callback.onNext(true) }
onFailure {
event.callback.onNext(false)
event.callback.onError(SecurityException("User refused permissions"))
}
}
}
}
override fun getRootFragment(index: Int) = baseFragments[index].java.newInstance()
override fun navigateTo(event: MagiskNavigationEvent) {
val directions = event.navDirections
navigationController?.defaultTransactionOptions = FragNavTransactionOptions.newBuilder()
.customAnimations(event.animOptions)
.build()
navigationController?.currentStack
?.indexOfFirst { it.javaClass == event.navOptions.popUpTo }
?.let { if (it == -1) null else it } // invalidate if class is not found
?.let { if (event.navOptions.inclusive) it + 1 else it }
?.let { navigationController?.popFragments(it) }
when (directions.isActivity) {
true -> navigateToActivity(event)
else -> navigateToFragment(event)
}
}
private fun navigateToActivity(event: MagiskNavigationEvent) {
val destination = event.navDirections.destination?.java ?: let {
Timber.e("Cannot navigate to null destination")
return
}
val options = event.navOptions
Intent(this, destination)
.putExtras(event.navDirections.args)
.apply {
if (options.singleTop) addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)
if (options.clearTask) addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
}
.let { startActivity(it) }
}
private fun navigateToFragment(event: MagiskNavigationEvent) {
val destination = event.navDirections.destination?.java ?: let {
Timber.e("Cannot navigate to null destination")
return
}
when (val index = baseFragments.indexOfFirst { it.java.name == destination.name }) {
-1 -> destination.newInstance()
.apply { arguments = event.navDirections.args }
.let { navigationController?.pushFragment(it) }
// When it's desired that fragments of same class are put on top of one another edit this
else -> navigationController?.switchTab(index)
}
}
override fun onBackPressed() {
val fragment = navigationController?.currentFrag as? MagiskFragment<*, *>
if (fragment?.onBackPressed() == true) {
return
}
try {
navigationController?.popFragment() ?: throw UnsupportedOperationException()
} catch (e: UnsupportedOperationException) {
when {
navigationController?.currentStackIndex != defaultPosition -> {
val options = FragNavTransactionOptions.newBuilder()
.transition(FragmentTransaction.TRANSIT_FRAGMENT_CLOSE)
.build()
navigationController?.switchTab(defaultPosition, options)
}
else -> super.onBackPressed()
}
}
}
fun openUrl(url: String) = Utils.openLink(this, url.toUri())
fun withPermissions(vararg permissions: String, builder: PermissionRequestBuilder.() -> Unit) {
val request = PermissionRequestBuilder().apply(builder).build()
Dexter.withActivity(this)
.withPermissions(*permissions)
.withListener(object : MultiplePermissionsListener {
override fun onPermissionsChecked(report: MultiplePermissionsReport?) =
if (report?.areAllPermissionsGranted() == true) {
request.onSuccess()
} else {
request.onFailure()
}
override fun onPermissionRationaleShouldBeShown(
permissions: MutableList<PermissionRequest>?,
token: PermissionToken?
) = request.onShowRationale(permissions.orEmpty().map { it.name })
})
.check()
}
private fun FragNavTransactionOptions.Builder.customAnimations(options: MagiskAnimBuilder) =
customAnimations(options.enter, options.exit, options.popEnter, options.popExit).apply {
if (!options.anySet) {
transition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
}
}
}
package com.topjohnwu.magisk.ui.base
import androidx.annotation.CallSuper
import androidx.databinding.ViewDataBinding
import androidx.fragment.app.Fragment
import com.skoumal.teanity.view.TeanityFragment
import com.skoumal.teanity.viewevents.ViewEvent
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.model.navigation.MagiskNavigationEvent
import com.topjohnwu.magisk.model.navigation.Navigator
import com.topjohnwu.magisk.model.permissions.PermissionRequestBuilder
import kotlin.reflect.KClass
abstract class MagiskFragment<ViewModel : MagiskViewModel, Binding : ViewDataBinding> :
TeanityFragment<ViewModel, Binding>(), Navigator {
protected val magiskActivity get() = activity as MagiskActivity<*, *>
// We don't need nested fragments
override val baseFragments: List<KClass<Fragment>> = listOf()
override fun navigateTo(event: MagiskNavigationEvent) = magiskActivity.navigateTo(event)
@CallSuper
override fun onEventDispatched(event: ViewEvent) {
super.onEventDispatched(event)
when (event) {
is BackPressEvent -> magiskActivity.onBackPressed()
is MagiskNavigationEvent -> navigateTo(event)
is ViewActionEvent -> event.action(requireActivity())
is PermissionEvent -> magiskActivity.withPermissions(*event.permissions.toTypedArray()) {
onSuccess { event.callback.onNext(true) }
onFailure {
event.callback.onNext(false)
event.callback.onError(SecurityException("User refused permissions"))
}
}
}
}
fun withPermissions(vararg permissions: String, builder: PermissionRequestBuilder.() -> Unit) {
magiskActivity.withPermissions(*permissions, builder = builder)
}
fun openLink(url: String) = magiskActivity.openUrl(url)
open fun onBackPressed(): Boolean = false
}
package com.topjohnwu.magisk.ui.base
import android.Manifest
import android.content.Intent
import androidx.collection.SparseArrayCompat
import androidx.databinding.ViewDataBinding
import com.karumi.dexter.Dexter
import com.karumi.dexter.MultiplePermissionsReport
import com.karumi.dexter.PermissionToken
import com.karumi.dexter.listener.PermissionRequest
import com.karumi.dexter.listener.multi.MultiplePermissionsListener
import com.skoumal.teanity.view.TeanityActivity
import com.topjohnwu.magisk.Const
abstract class MagiskLeanbackActivity<ViewModel : MagiskViewModel, Binding : ViewDataBinding> :
TeanityActivity<ViewModel, Binding>(), IBaseLeanback {
private val resultListeners = SparseArrayCompat<ActivityResultListener>()
@Deprecated("Permissions will be checked in a different streamlined way")
fun runWithExternalRW(callback: () -> Unit) = runWithExternalRW(Runnable { callback() })
@Deprecated("Permissions will be checked in a different streamlined way")
override fun runWithExternalRW(callback: Runnable) {
runWithPermissions(Manifest.permission.WRITE_EXTERNAL_STORAGE, callback = callback)
}
@Deprecated("Permissions will be checked in a different streamlined way")
override fun runWithPermissions(vararg permissions: String, callback: Runnable) {
Dexter.withActivity(this)
.withPermissions(*permissions)
.withListener(object : MultiplePermissionsListener {
override fun onPermissionsChecked(report: MultiplePermissionsReport?) {
if (report?.areAllPermissionsGranted() == true) {
Const.EXTERNAL_PATH.mkdirs()
callback.run()
}
}
override fun onPermissionRationaleShouldBeShown(
permissions: MutableList<PermissionRequest>?,
token: PermissionToken?
) = Unit
})
.check()
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
resultListeners.get(requestCode)?.apply {
resultListeners.remove(requestCode)
onActivityResult(resultCode, data)
}
}
override fun startActivityForResult(
intent: Intent,
requestCode: Int,
listener: ActivityResultListener
) {
resultListeners.put(requestCode, listener)
startActivityForResult(intent, requestCode)
}
}
\ No newline at end of file
package com.topjohnwu.magisk.ui.base
import android.app.Activity
import com.skoumal.teanity.viewmodel.LoadingViewModel
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.Event
import io.reactivex.Observable
import io.reactivex.subjects.PublishSubject
import timber.log.Timber
abstract class MagiskViewModel : LoadingViewModel(), Event.AutoListener {
override fun onEvent(event: Int) = Timber.i("Event of $event was not handled")
override fun getListeningEvents(): IntArray = intArrayOf()
fun withView(action: Activity.() -> Unit) {
ViewActionEvent(action).publish()
}
fun withPermissions(vararg permissions: String): Observable<Boolean> {
val subject = PublishSubject.create<Boolean>()
return subject.doOnSubscribe { PermissionEvent(permissions.toList(), subject).publish() }
}
fun back() = BackPressEvent().publish()
}
package com.topjohnwu.magisk.ui.flash;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.widget.Toolbar;
import androidx.recyclerview.widget.RecyclerView;
import com.topjohnwu.magisk.Const;
import com.topjohnwu.magisk.R;
import com.topjohnwu.magisk.model.adapters.StringListAdapter;
import com.topjohnwu.magisk.tasks.FlashZip;
import com.topjohnwu.magisk.tasks.MagiskInstaller;
import com.topjohnwu.magisk.ui.base.BaseActivity;
import com.topjohnwu.magisk.utils.RootUtils;
import com.topjohnwu.magisk.utils.Utils;
import com.topjohnwu.superuser.CallbackList;
import com.topjohnwu.superuser.Shell;
import com.topjohnwu.superuser.internal.UiThreadHandler;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import butterknife.BindColor;
import butterknife.BindView;
import butterknife.OnClick;
public class FlashActivity extends BaseActivity {
@BindView(R.id.toolbar) Toolbar toolbar;
@BindView(R.id.button_panel) LinearLayout buttonPanel;
@BindView(R.id.reboot) Button reboot;
@BindView(R.id.recyclerView) RecyclerView rv;
@BindColor(android.R.color.white) int white;
private List<String> console, logs;
@OnClick(R.id.reboot)
void reboot() {
RootUtils.reboot();
}
@OnClick(R.id.save_logs)
void saveLogs() {
runWithExternalRW(() -> {
Calendar now = Calendar.getInstance();
String filename = String.format(Locale.US,
"magisk_install_log_%04d%02d%02d_%02d%02d%02d.log",
now.get(Calendar.YEAR), now.get(Calendar.MONTH) + 1,
now.get(Calendar.DAY_OF_MONTH), now.get(Calendar.HOUR_OF_DAY),
now.get(Calendar.MINUTE), now.get(Calendar.SECOND));
File logFile = new File(Const.EXTERNAL_PATH, filename);
try (FileWriter writer = new FileWriter(logFile)) {
for (String s : logs) {
writer.write(s);
writer.write('\n');
}
} catch (IOException e) {
e.printStackTrace();
return;
}
Utils.toast(logFile.getPath(), Toast.LENGTH_LONG);
});
}
@OnClick(R.id.close)
public void close() {
finish();
}
@Override
public void onBackPressed() {
// Prevent user accidentally press back button
}
@Override
public int getDarkTheme() {
return R.style.AppTheme_NoDrawer_Dark;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_flash);
new FlashActivity_ViewBinding(this);
setSupportActionBar(toolbar);
ActionBar ab = getSupportActionBar();
if (ab != null) {
ab.setTitle(R.string.flashing);
}
setFloating();
setFinishOnTouchOutside(false);
if (!Shell.rootAccess())
reboot.setVisibility(View.GONE);
logs = Collections.synchronizedList(new ArrayList<>());
console = new ConsoleList();
rv.setAdapter(new ConsoleAdapter());
Intent intent = getIntent();
Uri uri = intent.getData();
switch (intent.getStringExtra(Const.Key.FLASH_ACTION)) {
case Const.Value.FLASH_ZIP:
new FlashModule(uri).exec();
break;
case Const.Value.UNINSTALL:
new Uninstall(uri).exec();
break;
case Const.Value.FLASH_MAGISK:
new DirectInstall().exec();
break;
case Const.Value.FLASH_INACTIVE_SLOT:
new SecondSlot().exec();
break;
case Const.Value.PATCH_FILE:
new PatchFile(uri).exec();
break;
}
}
private class ConsoleAdapter extends StringListAdapter<ConsoleAdapter.ViewHolder> {
ConsoleAdapter() {
super(console, true);
}
@Override
protected int itemLayoutRes() {
return R.layout.list_item_console;
}
@NonNull
@Override
public ViewHolder createViewHolder(@NonNull View v) {
return new ViewHolder(v);
}
class ViewHolder extends StringListAdapter.ViewHolder {
public ViewHolder(@NonNull View itemView) {
super(itemView);
txt.setTextColor(white);
}
@Override
protected int textViewResId() {
return R.id.txt;
}
}
}
private class ConsoleList extends CallbackList<String> {
ConsoleList() {
super(new ArrayList<>());
}
private void updateUI() {
rv.getAdapter().notifyItemChanged(size() - 1);
rv.postDelayed(() -> rv.smoothScrollToPosition(size() - 1), 10);
}
@Override
public void onAddElement(String s) {
logs.add(s);
updateUI();
}
@Override
public String set(int i, String s) {
String ret = super.set(i, s);
UiThreadHandler.run(this::updateUI);
return ret;
}
}
private class FlashModule extends FlashZip {
FlashModule(Uri uri) {
super(uri, console, logs);
}
@Override
protected void onResult(boolean success) {
if (success) {
Utils.loadModules();
} else {
console.add("! Installation failed");
reboot.setVisibility(View.GONE);
}
buttonPanel.setVisibility(View.VISIBLE);
}
}
private class Uninstall extends FlashModule {
Uninstall(Uri uri) {
super(uri);
}
@Override
protected void onResult(boolean success) {
if (success)
UiThreadHandler.handler.postDelayed(Shell.su("pm uninstall " + getPackageName())::exec, 3000);
else
super.onResult(false);
}
}
private abstract class BaseInstaller extends MagiskInstaller {
BaseInstaller() {
super(console, logs);
}
@Override
protected void onResult(boolean success) {
if (success) {
console.add("- All done!");
} else {
Shell.sh("rm -rf " + installDir).submit();
console.add("! Installation failed");
reboot.setVisibility(View.GONE);
}
buttonPanel.setVisibility(View.VISIBLE);
}
}
private class DirectInstall extends BaseInstaller {
@Override
protected boolean operations() {
return findImage() && extractZip() && patchBoot() && flashBoot();
}
}
private class SecondSlot extends BaseInstaller {
@Override
protected boolean operations() {
return findSecondaryImage() && extractZip() && patchBoot() && flashBoot() && postOTA();
}
}
private class PatchFile extends BaseInstaller {
private Uri uri;
PatchFile(Uri u) {
uri = u;
}
@Override
protected boolean operations() {
return extractZip() && handleFile(uri) && patchBoot() && storeBoot();
}
}
}
package com.topjohnwu.magisk.ui.flash
import com.topjohnwu.magisk.Const
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.databinding.ActivityFlashBinding
import com.topjohnwu.magisk.ui.base.MagiskActivity
import org.koin.androidx.viewmodel.ext.android.viewModel
import org.koin.core.parameter.parametersOf
open class FlashActivity : MagiskActivity<FlashViewModel, ActivityFlashBinding>() {
override val layoutRes: Int = R.layout.activity_flash
override val viewModel: FlashViewModel by viewModel {
val uri = intent.data
val action = intent.getStringExtra(Const.Key.FLASH_ACTION) ?: let { finish();"" }
parametersOf(action, uri)
}
override fun onBackPressed() {
if (viewModel.loading) return
super.onBackPressed()
}
}
package com.topjohnwu.magisk.ui.flash
import android.Manifest.permission.READ_EXTERNAL_STORAGE
import android.Manifest.permission.WRITE_EXTERNAL_STORAGE
import android.content.res.Resources
import android.net.Uri
import android.os.Handler
import androidx.core.os.postDelayed
import androidx.databinding.ObservableArrayList
import com.skoumal.teanity.databinding.ComparableRvItem
import com.skoumal.teanity.extensions.subscribeK
import com.skoumal.teanity.util.DiffObservableList
import com.skoumal.teanity.util.KObservableField
import com.skoumal.teanity.viewevents.SnackbarEvent
import com.topjohnwu.magisk.BR
import com.topjohnwu.magisk.Const
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.model.entity.recycler.ConsoleRvItem
import com.topjohnwu.magisk.model.flash.FlashResultListener
import com.topjohnwu.magisk.model.flash.Flashing
import com.topjohnwu.magisk.model.flash.Patching
import com.topjohnwu.magisk.ui.base.MagiskViewModel
import com.topjohnwu.magisk.utils.*
import com.topjohnwu.superuser.Shell
import me.tatarka.bindingcollectionadapter2.ItemBinding
import java.io.File
class FlashViewModel(
action: String,
uri: Uri?,
private val resources: Resources
) : MagiskViewModel(), FlashResultListener {
val canShowReboot = Shell.rootAccess()
val showRestartTitle = KObservableField(false)
val behaviorText = KObservableField(resources.getString(R.string.flashing))
val items = DiffObservableList(ComparableRvItem.callback)
val itemBinding = ItemBinding.of<ComparableRvItem<*>> { itemBinding, _, item ->
item.bind(itemBinding)
itemBinding.bindExtra(BR.viewModel, this@FlashViewModel)
}
private val rawItems = ObservableArrayList<String>()
init {
rawItems.sendUpdatesTo(items) { it.map { ConsoleRvItem(it) } }
state = State.LOADING
val uri = uri ?: Uri.EMPTY
when (action) {
Const.Value.FLASH_ZIP -> Flashing
.Install(uri, rawItems, rawItems, this)
.exec()
Const.Value.UNINSTALL -> Flashing
.Uninstall(uri, rawItems, rawItems, this)
.exec()
Const.Value.FLASH_MAGISK -> Patching
.Direct(rawItems, rawItems, this)
.exec()
Const.Value.FLASH_INACTIVE_SLOT -> Patching
.SecondSlot(rawItems, rawItems, this)
.exec()
Const.Value.PATCH_FILE -> Patching
.File(uri, rawItems, rawItems, this)
.exec()
}
}
override fun onResult(isSuccess: Boolean) {
state = if (isSuccess) State.LOADED else State.LOADING_FAILED
behaviorText.value = when {
isSuccess -> resources.getString(R.string.done)
else -> resources.getString(R.string.failure)
}
if (isSuccess) {
Handler().postDelayed(500) {
showRestartTitle.value = true
}
}
}
fun savePressed() = withPermissions(READ_EXTERNAL_STORAGE, WRITE_EXTERNAL_STORAGE)
.map { now }
.map { it.toTime(timeFormatFull) }
.map { Const.MAGISK_INSTALL_LOG_FILENAME.format(it) }
.map { File(Const.EXTERNAL_PATH, it) }
.map { file ->
val log = items.filterIsInstance<ConsoleRvItem>()
.joinToString("\n") { it.item }
file.writeText(log)
file.path
}
.subscribeK { SnackbarEvent(it).publish() }
.add()
fun restartPressed() = RootUtils.reboot()
fun backPressed() = back()
}
\ No newline at end of file
package com.topjohnwu.magisk.ui.hide
import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
import com.skoumal.teanity.databinding.ComparableRvItem
import com.skoumal.teanity.extensions.addOnPropertyChangedCallback
import com.skoumal.teanity.extensions.subscribeK
import com.skoumal.teanity.rxbus.RxBus
import com.skoumal.teanity.util.DiffObservableList
import com.skoumal.teanity.util.KObservableField
import com.topjohnwu.magisk.App
import com.topjohnwu.magisk.BR
import com.topjohnwu.magisk.model.entity.HideAppInfo
import com.topjohnwu.magisk.model.entity.HideTarget
import com.topjohnwu.magisk.model.entity.recycler.HideProcessRvItem
import com.topjohnwu.magisk.model.entity.recycler.HideRvItem
import com.topjohnwu.magisk.model.events.HideProcessEvent
import com.topjohnwu.magisk.ui.base.MagiskViewModel
import com.topjohnwu.magisk.utils.Utils
import com.topjohnwu.magisk.utils.toSingle
import com.topjohnwu.magisk.utils.update
import com.topjohnwu.superuser.Shell
import io.reactivex.Single
import me.tatarka.bindingcollectionadapter2.OnItemBind
import timber.log.Timber
class HideViewModel(
private val packageManager: PackageManager,
rxBus: RxBus
) : MagiskViewModel() {
val query = KObservableField("")
val isShowSystem = KObservableField(false)
private val allItems = mutableListOf<ComparableRvItem<*>>()
val items = DiffObservableList(ComparableRvItem.callback)
val itemBinding = OnItemBind<ComparableRvItem<*>> { itemBinding, _, item ->
item.bind(itemBinding)
itemBinding.bindExtra(BR.viewModel, this@HideViewModel)
}
init {
rxBus.register<HideProcessEvent>()
.subscribeK { toggleItem(it.item) }
.add()
isShowSystem.addOnPropertyChangedCallback { query() }
query.addOnPropertyChangedCallback { query() }
refresh()
}
fun refresh() {
// fetching this for every item is nonsensical, so we add .cache() so the response is all
// the same for every single mapped item, it only actually executes the whole thing the
// first time around.
val hideTargets = Shell.su("magiskhide --ls").toSingle()
.map { it.exec().out }
.flattenAsFlowable { it }
.map { HideTarget(it) }
.toList()
.cache()
Single.fromCallable { packageManager.getInstalledApplications(0) }
.flattenAsFlowable { it }
.filter { it.enabled && !blacklist.contains(it.packageName) }
.map {
val label = Utils.getAppLabel(it, packageManager)
val icon = it.loadIcon(packageManager)
HideAppInfo(it, label, icon)
}
.filter { it.processes.isNotEmpty() }
.map { HideRvItem(it, hideTargets.blockingGet()) }
.toList()
.map { it.sortBy { it.item.info.name }; it }
.doOnSuccess { allItems.update(it) }
.flatMap { queryRaw() }
.applyViewModel(this)
.subscribeK(onError = Timber::e) { items.update(it.first, it.second) }
.add()
}
private fun query() = queryRaw()
.subscribeK { items.update(it.first, it.second) }
.add()
private fun queryRaw(
showSystem: Boolean = isShowSystem.value,
query: String = this.query.value
) = allItems.toSingle()
.map { it.filterIsInstance<HideRvItem>() }
.flattenAsFlowable { it }
.filter {
it.item.name.contains(query, ignoreCase = true) ||
it.item.processes.any { it.contains(query, ignoreCase = true) }
}
.filter { if (showSystem) true else it.item.info.flags and ApplicationInfo.FLAG_SYSTEM == 0 }
.toList()
.map { it to items.calculateDiff(it) }
private fun toggleItem(item: HideProcessRvItem) {
val state = if (item.isHidden.value) "add" else "rm"
"magiskhide --%s %s %s".format(state, item.packageName, item.process)
.let { Shell.su(it) }
.toSingle()
.map { it.submit() }
.subscribeK()
}
companion object {
private val blacklist = listOf(
App.self.packageName,
"android",
"com.android.chrome",
"com.chrome.beta",
"com.chrome.dev",
"com.chrome.canary",
"com.android.webview",
"com.google.android.webview"
)
}
}
\ No newline at end of file
package com.topjohnwu.magisk.ui.hide;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.SearchView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.RecyclerView;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import com.topjohnwu.magisk.Config;
import com.topjohnwu.magisk.R;
import com.topjohnwu.magisk.model.adapters.ApplicationAdapter;
import com.topjohnwu.magisk.ui.base.BaseFragment;
import com.topjohnwu.magisk.utils.Event;
import butterknife.BindView;
public class MagiskHideFragment extends BaseFragment {
@BindView(R.id.swipeRefreshLayout) SwipeRefreshLayout mSwipeRefreshLayout;
@BindView(R.id.recyclerView) RecyclerView recyclerView;
private SearchView search;
private ApplicationAdapter adapter;
private SearchView.OnQueryTextListener searchListener;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_magisk_hide, container, false);
unbinder = new MagiskHideFragment_ViewBinding(this, view);
adapter = new ApplicationAdapter(requireActivity());
recyclerView.setAdapter(adapter);
mSwipeRefreshLayout.setRefreshing(true);
mSwipeRefreshLayout.setOnRefreshListener(adapter::refresh);
searchListener = new SearchView.OnQueryTextListener() {
@Override
public boolean onQueryTextSubmit(String query) {
adapter.filter(query);
return false;
}
@Override
public boolean onQueryTextChange(String newText) {
adapter.filter(newText);
return false;
}
};
requireActivity().setTitle(R.string.magiskhide);
return view;
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.menu_magiskhide, menu);
search = (SearchView) menu.findItem(R.id.app_search).getActionView();
search.setOnQueryTextListener(searchListener);
menu.findItem(R.id.show_system).setChecked(Config.get(Config.Key.SHOW_SYSTEM_APP));
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == R.id.show_system) {
boolean showSystem = !item.isChecked();
item.setChecked(showSystem);
Config.set(Config.Key.SHOW_SYSTEM_APP, showSystem);
adapter.setShowSystem(showSystem);
adapter.filter(search.getQuery().toString());
}
return true;
}
@Override
public int[] getListeningEvents() {
return new int[] {Event.MAGISK_HIDE_DONE};
}
@Override
public void onEvent(int event) {
mSwipeRefreshLayout.setRefreshing(false);
adapter.filter(search.getQuery().toString());
}
}
package com.topjohnwu.magisk.ui.hide
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.widget.SearchView
import com.topjohnwu.magisk.Config
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.databinding.FragmentMagiskHideBinding
import com.topjohnwu.magisk.ui.base.MagiskFragment
import org.koin.androidx.viewmodel.ext.android.viewModel
class MagiskHideFragment : MagiskFragment<HideViewModel, FragmentMagiskHideBinding>(),
SearchView.OnQueryTextListener {
override val layoutRes: Int = R.layout.fragment_magisk_hide
override val viewModel: HideViewModel by viewModel()
override fun onStart() {
super.onStart()
setHasOptionsMenu(true)
requireActivity().setTitle(R.string.magiskhide)
}
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 showSystem = Config.get<Boolean>(Config.Key.SHOW_SYSTEM_APP)
findItem(R.id.show_system).isChecked = showSystem
viewModel.isShowSystem.value = showSystem
}
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (item.itemId == R.id.show_system) {
val showSystem = !item.isChecked
item.isChecked = showSystem
Config.set(Config.Key.SHOW_SYSTEM_APP, showSystem)
viewModel.isShowSystem.value = showSystem
//adapter!!.setShowSystem(showSystem)
//adapter!!.filter(search!!.query.toString())
}
return true
}
override fun onQueryTextSubmit(query: String?): Boolean {
viewModel.query.value = query.orEmpty()
return false
}
override fun onQueryTextChange(query: String?): Boolean {
viewModel.query.value = query.orEmpty()
return false
}
/*override fun onEvent(event: Int) {
//mSwipeRefreshLayout!!.isRefreshing = false
adapter!!.filter(search!!.query.toString())
}*/
}
package com.topjohnwu.magisk.ui.home
import android.content.res.Resources
import com.skoumal.teanity.extensions.addOnPropertyChangedCallback
import com.skoumal.teanity.util.KObservableField
import com.topjohnwu.magisk.*
import com.topjohnwu.magisk.model.events.*
import com.topjohnwu.magisk.model.observer.Observer
import com.topjohnwu.magisk.tasks.CheckUpdates
import com.topjohnwu.magisk.ui.base.MagiskViewModel
import com.topjohnwu.magisk.utils.Event
import com.topjohnwu.magisk.utils.ISafetyNetHelper
import com.topjohnwu.magisk.utils.toggle
import com.topjohnwu.net.Networking
import com.topjohnwu.superuser.Shell
class HomeViewModel(
private val resources: Resources,
private val app: App
) : MagiskViewModel() {
val isAdvancedExpanded = KObservableField(false)
val isForceEncryption = KObservableField(Config.keepEnc)
val isKeepVerity = KObservableField(Config.keepVerity)
val magiskState = KObservableField(MagiskState.LOADING)
val magiskStateText = Observer(magiskState) {
when (magiskState.value) {
MagiskState.NO_ROOT -> TODO()
MagiskState.NOT_INSTALLED -> resources.getString(R.string.magisk_version_error)
MagiskState.UP_TO_DATE -> resources.getString(R.string.magisk_up_to_date)
MagiskState.LOADING -> resources.getString(R.string.checking_for_updates)
MagiskState.OBSOLETE -> resources.getString(R.string.magisk_update_title)
}
}
val magiskCurrentVersion = KObservableField("")
val magiskLatestVersion = KObservableField("")
val magiskAdditionalInfo = Observer(magiskState) {
if (Config.get<Boolean>(Config.Key.COREONLY))
resources.getString(R.string.core_only_enabled)
else
""
}
val managerState = KObservableField(MagiskState.LOADING)
val managerStateText = Observer(managerState) {
when (managerState.value) {
MagiskState.NO_ROOT -> "wtf"
MagiskState.NOT_INSTALLED -> resources.getString(R.string.invalid_update_channel)
MagiskState.UP_TO_DATE -> resources.getString(R.string.manager_up_to_date)
MagiskState.LOADING -> resources.getString(R.string.checking_for_updates)
MagiskState.OBSOLETE -> resources.getString(R.string.manager_update_title)
}
}
val managerCurrentVersion = KObservableField("")
val managerLatestVersion = KObservableField("")
val managerAdditionalInfo = Observer(managerState) {
if (app.packageName != BuildConfig.APPLICATION_ID)
"(${app.packageName})"
else
""
}
val safetyNetTitle = KObservableField(resources.getString(R.string.safetyNet_check_text))
val ctsState = KObservableField(SafetyNetState.IDLE)
val basicIntegrityState = KObservableField(SafetyNetState.IDLE)
val safetyNetState = Observer(ctsState, basicIntegrityState) {
val cts = ctsState.value
val basic = basicIntegrityState.value
val states = listOf(cts, basic)
when {
states.any { it == SafetyNetState.LOADING } -> State.LOADING
states.any { it == SafetyNetState.IDLE } -> State.LOADING
else -> State.LOADED
}
}
val hasRoot = KObservableField(false)
private var shownDialog = false
private val current = resources.getString(R.string.current_installed)
private val latest = resources.getString(R.string.latest_version)
init {
Event.register(this)
isForceEncryption.addOnPropertyChangedCallback {
Config.keepEnc = it ?: return@addOnPropertyChangedCallback
}
isKeepVerity.addOnPropertyChangedCallback {
Config.keepVerity = it ?: return@addOnPropertyChangedCallback
}
refresh()
}
override fun onEvent(event: Int) {
updateSelf()
ensureEnv()
}
override fun getListeningEvents(): IntArray = intArrayOf(Event.UPDATE_CHECK_DONE)
fun paypalPressed() = OpenLinkEvent(Const.Url.PAYPAL_URL).publish()
fun patreonPressed() = OpenLinkEvent(Const.Url.PATREON_URL).publish()
fun twitterPressed() = OpenLinkEvent(Const.Url.TWITTER_URL).publish()
fun githubPressed() = OpenLinkEvent(Const.Url.SOURCE_CODE_URL).publish()
fun xdaPressed() = OpenLinkEvent(Const.Url.XDA_THREAD).publish()
fun uninstallPressed() = UninstallEvent().publish()
fun advancedPressed() = isAdvancedExpanded.toggle()
fun installPressed(item: MagiskItem) = when (item) {
MagiskItem.MANAGER -> ManagerInstallEvent().publish()
MagiskItem.MAGISK -> MagiskInstallEvent().publish()
}
fun cardPressed(item: MagiskItem) = when (item) {
MagiskItem.MANAGER -> ManagerChangelogEvent().publish()
MagiskItem.MAGISK -> MagiskChangelogEvent().publish()
}
fun safetyNetPressed() {
ctsState.value = SafetyNetState.LOADING
basicIntegrityState.value = SafetyNetState.LOADING
safetyNetTitle.value = resources.getString(R.string.checking_safetyNet_status)
UpdateSafetyNetEvent().publish()
}
fun finishSafetyNetCheck(response: Int) = when {
response and 0x0F == 0 -> {
val hasCtsPassed = response and ISafetyNetHelper.CTS_PASS != 0
val hasBasicIntegrityPassed = response and ISafetyNetHelper.BASIC_PASS != 0
safetyNetTitle.value = resources.getString(R.string.safetyNet_check_success)
ctsState.value = if (hasCtsPassed) {
SafetyNetState.PASS
} else {
SafetyNetState.FAILED
}
basicIntegrityState.value = if (hasBasicIntegrityPassed) {
SafetyNetState.PASS
} else {
SafetyNetState.FAILED
}
}
response == -2 -> {
ctsState.value = SafetyNetState.IDLE
basicIntegrityState.value = SafetyNetState.IDLE
}
else -> {
ctsState.value = SafetyNetState.IDLE
basicIntegrityState.value = SafetyNetState.IDLE
val errorString = when (response) {
ISafetyNetHelper.RESPONSE_ERR -> R.string.safetyNet_res_invalid
else -> R.string.safetyNet_api_error
}
safetyNetTitle.value = resources.getString(errorString)
}
}
fun refresh() {
state = State.LOADING
magiskState.value = MagiskState.LOADING
managerState.value = MagiskState.LOADING
Event.reset(this)
Config.remoteMagiskVersionString = null
Config.remoteMagiskVersionCode = -1
hasRoot.value = Shell.rootAccess()
if (Networking.checkNetworkStatus(app)) {
CheckUpdates.check()
} else {
state = State.LOADING_FAILED
}
}
private fun updateSelf() {
state = State.LOADED
magiskState.value = when (Config.magiskVersionCode) {
in Int.MIN_VALUE until 0 -> MagiskState.NOT_INSTALLED
!in Config.remoteMagiskVersionCode..Int.MAX_VALUE -> MagiskState.OBSOLETE
else -> MagiskState.UP_TO_DATE
}
if (magiskState.value != MagiskState.NOT_INSTALLED) {
magiskCurrentVersion.value = version
.format(Config.magiskVersionString, Config.magiskVersionCode)
.let { current.format(it) }
} else {
magiskCurrentVersion.value = ""
}
magiskLatestVersion.value = version
.format(Config.remoteMagiskVersionString, Config.remoteMagiskVersionCode)
.let { latest.format(it) }
managerState.value = when (Config.remoteManagerVersionCode) {
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
}
managerCurrentVersion.value = version
.format(BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE)
.let { current.format(it) }
managerLatestVersion.value = version
.format(Config.remoteManagerVersionString, Config.remoteManagerVersionCode)
.let { latest.format(it) }
}
private fun ensureEnv() {
val invalidStates =
listOf(MagiskState.NOT_INSTALLED, MagiskState.NO_ROOT, MagiskState.LOADING)
// Don't bother checking env when magisk is not installed, loading or already has been shown
if (invalidStates.any { it == magiskState.value } || shownDialog) return
if (!Shell.su("env_check").exec().isSuccess) {
shownDialog = true
EnvFixEvent().publish()
}
}
companion object {
private const val version = "%s (%d)"
}
}
package com.topjohnwu.magisk.ui.home
import com.skoumal.teanity.viewevents.ViewEvent
import com.topjohnwu.magisk.BuildConfig
import com.topjohnwu.magisk.Config
import com.topjohnwu.magisk.Const
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.databinding.FragmentMagiskBinding
import com.topjohnwu.magisk.model.events.*
import com.topjohnwu.magisk.ui.base.MagiskActivity
import com.topjohnwu.magisk.utils.ISafetyNetHelper
import com.topjohnwu.magisk.view.MarkDownWindow
import com.topjohnwu.magisk.view.SafetyNet
import com.topjohnwu.magisk.view.SafetyNet.EXT_APK
import com.topjohnwu.magisk.view.dialogs.*
import com.topjohnwu.net.Networking
import com.topjohnwu.superuser.Shell
import org.koin.androidx.viewmodel.ext.android.viewModel
import com.topjohnwu.magisk.ui.base.MagiskFragment as NewMagiskFragment
class MagiskFragment : NewMagiskFragment<HomeViewModel, FragmentMagiskBinding>(),
ISafetyNetHelper.Callback {
override val layoutRes: Int = R.layout.fragment_magisk
override val viewModel: HomeViewModel by viewModel()
override fun onResponse(responseCode: Int) = viewModel.finishSafetyNetCheck(responseCode)
override fun onEventDispatched(event: ViewEvent) {
super.onEventDispatched(event)
when (event) {
is OpenLinkEvent -> openLink(event.url)
is ManagerInstallEvent -> installManager()
is MagiskInstallEvent -> installMagisk()
is UninstallEvent -> uninstall()
is ManagerChangelogEvent -> changelogManager()
is EnvFixEvent -> fixEnv()
is UpdateSafetyNetEvent -> updateSafetyNet(false)
}
}
override fun onStart() {
super.onStart()
setHasOptionsMenu(true)
requireActivity().setTitle(R.string.magisk)
}
private fun installMagisk() {
// Show Manager update first
if (Config.remoteManagerVersionCode > BuildConfig.VERSION_CODE) {
installManager()
return
}
MagiskInstallDialog(requireActivity() as MagiskActivity<*, *>).show()
}
private fun installManager() = ManagerInstallDialog(requireActivity()).show()
private fun uninstall() = UninstallDialog(requireActivity()).show()
private fun fixEnv() = EnvFixDialog(requireActivity()).show()
private fun changelogManager() = MarkDownWindow
.show(requireActivity(), null, resources.openRawResource(R.raw.changelog))
private fun downloadSafetyNet(requiresUserInput: Boolean = true) {
fun download() = Networking
.get(Const.Url.SNET_URL)
.getAsFile(EXT_APK) { updateSafetyNet(true) }
if (!requiresUserInput) {
download()
return
}
CustomAlertDialog(requireActivity())
.setTitle(R.string.proprietary_title)
.setMessage(R.string.proprietary_notice)
.setCancelable(false)
.setPositiveButton(R.string.yes) { _, _ -> download() }
.setNegativeButton(R.string.no_thanks) { _, _ -> viewModel.finishSafetyNetCheck(-2) }
.show()
}
private fun updateSafetyNet(dieOnError: Boolean) {
try {
SafetyNet.dyRun(requireActivity(), this)
} catch (e: Exception) {
if (dieOnError) {
viewModel.finishSafetyNetCheck(-1)
return
}
Shell.sh("rm -rf " + EXT_APK.parent).exec()
EXT_APK.parentFile?.mkdir()
downloadSafetyNet(!dieOnError)
}
}
}
package com.topjohnwu.magisk.ui.home
enum class MagiskItem {
MANAGER, MAGISK
}
package com.topjohnwu.magisk.ui.home
enum class MagiskState {
NO_ROOT, NOT_INSTALLED, UP_TO_DATE, OBSOLETE, LOADING
}
\ No newline at end of file
package com.topjohnwu.magisk.ui.home
enum class SafetyNetState {
LOADING, PASS, FAILED, IDLE
}
\ No newline at end of file
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment