Added (ported back) features from initial design [retrofit,moshi,kotpref]
Marked most of the old classes using Networking as deprecated to clearly visualise their future removal
This commit is contained in:
parent
5d632d0d90
commit
b018124226
@ -47,6 +47,10 @@ android {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
androidExtensions {
|
||||||
|
experimental = true
|
||||||
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation fileTree(include: ['*.jar'], dir: 'libs')
|
implementation fileTree(include: ['*.jar'], dir: 'libs')
|
||||||
implementation project(':net')
|
implementation project(':net')
|
||||||
@ -72,8 +76,23 @@ dependencies {
|
|||||||
implementation "org.koin:koin-android:${koin}"
|
implementation "org.koin:koin-android:${koin}"
|
||||||
implementation "org.koin:koin-androidx-viewmodel:${koin}"
|
implementation "org.koin:koin-androidx-viewmodel:${koin}"
|
||||||
|
|
||||||
|
def vRetrofit = "2.5.0"
|
||||||
|
def vOkHttp = "3.12.0"
|
||||||
|
def vMoshi = "1.8.0"
|
||||||
|
implementation "com.squareup.retrofit2:retrofit:${vRetrofit}"
|
||||||
|
implementation "com.squareup.retrofit2:converter-moshi:${vRetrofit}"
|
||||||
|
implementation "com.squareup.retrofit2:adapter-rxjava2:${vRetrofit}"
|
||||||
|
implementation "com.squareup.okhttp3:okhttp:${vOkHttp}"
|
||||||
|
implementation "com.squareup.okhttp3:logging-interceptor:${vOkHttp}"
|
||||||
|
implementation "com.squareup.moshi:moshi:${vMoshi}"
|
||||||
|
implementation "com.squareup.moshi:moshi-kotlin:${vMoshi}"
|
||||||
|
|
||||||
|
def vKotpref = "2.8.0"
|
||||||
|
implementation "com.chibatching.kotpref:kotpref:${vKotpref}"
|
||||||
|
|
||||||
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
||||||
implementation 'androidx.appcompat:appcompat:1.0.2'
|
implementation 'androidx.appcompat:appcompat:1.0.2'
|
||||||
|
implementation 'androidx.browser:browser:1.0.0'
|
||||||
implementation 'androidx.preference:preference:1.0.0'
|
implementation 'androidx.preference:preference:1.0.0'
|
||||||
implementation 'androidx.recyclerview:recyclerview:1.1.0-alpha04'
|
implementation 'androidx.recyclerview:recyclerview:1.1.0-alpha04'
|
||||||
implementation 'androidx.cardview:cardview:1.0.0'
|
implementation 'androidx.cardview:cardview:1.0.0'
|
||||||
|
20
app/src/main/java/com/topjohnwu/magisk/Constants.kt
Normal file
20
app/src/main/java/com/topjohnwu/magisk/Constants.kt
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
package com.topjohnwu.magisk
|
||||||
|
|
||||||
|
import android.os.Process
|
||||||
|
|
||||||
|
object Constants {
|
||||||
|
|
||||||
|
// Paths
|
||||||
|
val MAGISK_PATH = "/sbin/.magisk/img"
|
||||||
|
val MAGISK_LOG = "/cache/magisk.log"
|
||||||
|
|
||||||
|
val USER_ID = Process.myUid() / 100000
|
||||||
|
|
||||||
|
const val SNET_REVISION = "b66b1a914978e5f4c4bbfd74a59f4ad371bac107"
|
||||||
|
const val BOOTCTL_REVISION = "9c5dfc1b8245c0b5b524901ef0ff0f8335757b77"
|
||||||
|
|
||||||
|
const val GITHUB_URL = "https://github.com/"
|
||||||
|
const val GITHUB_API_URL = "https://api.github.com/"
|
||||||
|
const val GITHUB_RAW_API_URL = "https://raw.githubusercontent.com/"
|
||||||
|
|
||||||
|
}
|
36
app/src/main/java/com/topjohnwu/magisk/KConfig.kt
Normal file
36
app/src/main/java/com/topjohnwu/magisk/KConfig.kt
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
package com.topjohnwu.magisk
|
||||||
|
|
||||||
|
import androidx.appcompat.app.AppCompatDelegate
|
||||||
|
import com.chibatching.kotpref.KotprefModel
|
||||||
|
import com.topjohnwu.magisk.KConfig.UpdateChannel.*
|
||||||
|
|
||||||
|
object KConfig : KotprefModel() {
|
||||||
|
override val kotprefName: String = "${context.packageName}_preferences"
|
||||||
|
|
||||||
|
var darkMode by intPref(default = AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM, key = "darkMode")
|
||||||
|
var magiskChecksum by stringPref("", "magiskChecksum")
|
||||||
|
var forceEncrypt by booleanPref(false, "forceEncryption")
|
||||||
|
var keepVerity by booleanPref(false, "keepVerity")
|
||||||
|
var bootFormat by stringPref("img", "bootFormat")
|
||||||
|
var suLogTimeout by longPref(0, "suLogTimeout")
|
||||||
|
private var internalUpdateChannel by stringPref(
|
||||||
|
KConfig.UpdateChannel.STABLE.toString(),
|
||||||
|
"updateChannel"
|
||||||
|
)
|
||||||
|
var useCustomTabs by booleanPref(true, "useCustomTabs")
|
||||||
|
|
||||||
|
var updateChannel: UpdateChannel
|
||||||
|
get() = valueOf(internalUpdateChannel)
|
||||||
|
set(value) {
|
||||||
|
internalUpdateChannel = value.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
val isStable get() = !(isCanary || isBeta)
|
||||||
|
val isCanary get() = updateChannel == CANARY || updateChannel == CANARY_DEBUG
|
||||||
|
val isBeta get() = updateChannel == BETA
|
||||||
|
|
||||||
|
|
||||||
|
enum class UpdateChannel {
|
||||||
|
STABLE, BETA, CANARY, CANARY_DEBUG
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
package com.topjohnwu.magisk.data.database
|
||||||
|
|
||||||
|
import androidx.room.Database
|
||||||
|
import androidx.room.RoomDatabase
|
||||||
|
import com.topjohnwu.magisk.model.entity.Repository
|
||||||
|
|
||||||
|
@Database(
|
||||||
|
version = 1,
|
||||||
|
entities = [Repository::class]
|
||||||
|
)
|
||||||
|
abstract class AppDatabase : RoomDatabase() {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val NAME = "database"
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract fun repoDao(): RepositoryDao
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,34 @@
|
|||||||
|
package com.topjohnwu.magisk.data.database
|
||||||
|
|
||||||
|
import com.topjohnwu.magisk.Config
|
||||||
|
import com.topjohnwu.magisk.data.database.base.*
|
||||||
|
import com.topjohnwu.magisk.model.entity.MagiskLog
|
||||||
|
import com.topjohnwu.magisk.model.entity.toLog
|
||||||
|
import com.topjohnwu.magisk.model.entity.toMap
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
|
class LogDao : BaseDao() {
|
||||||
|
|
||||||
|
override val table = DatabaseDefinition.Table.LOG
|
||||||
|
|
||||||
|
fun deleteOutdated(
|
||||||
|
suTimeout: Long = Config.suLogTimeout * TimeUnit.DAYS.toMillis(1)
|
||||||
|
) = query<Delete> {
|
||||||
|
condition {
|
||||||
|
lessThan("time", suTimeout.toString())
|
||||||
|
}
|
||||||
|
}.ignoreElement()
|
||||||
|
|
||||||
|
fun deleteAll() = query<Delete> {}.ignoreElement()
|
||||||
|
|
||||||
|
fun fetchAll() = query<Select> {
|
||||||
|
orderBy("time", Order.DESC)
|
||||||
|
}.flattenAsFlowable { it }
|
||||||
|
.map { it.toLog() }
|
||||||
|
.toList()
|
||||||
|
|
||||||
|
fun put(log: MagiskLog) = query<Insert> {
|
||||||
|
values(log.toMap())
|
||||||
|
}.ignoreElement()
|
||||||
|
|
||||||
|
}
|
@ -20,6 +20,7 @@ import java.util.Date;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
public class MagiskDB {
|
public class MagiskDB {
|
||||||
|
|
||||||
private static final String POLICY_TABLE = "policies";
|
private static final String POLICY_TABLE = "policies";
|
||||||
@ -27,20 +28,24 @@ public class MagiskDB {
|
|||||||
private static final String SETTINGS_TABLE = "settings";
|
private static final String SETTINGS_TABLE = "settings";
|
||||||
private static final String STRINGS_TABLE = "strings";
|
private static final String STRINGS_TABLE = "strings";
|
||||||
|
|
||||||
private PackageManager pm;
|
private final PackageManager pm;
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
public MagiskDB(Context context) {
|
public MagiskDB(Context context) {
|
||||||
pm = context.getPackageManager();
|
pm = context.getPackageManager();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
public void deletePolicy(Policy policy) {
|
public void deletePolicy(Policy policy) {
|
||||||
deletePolicy(policy.uid);
|
deletePolicy(policy.uid);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
private List<String> rawSQL(String fmt, Object... args) {
|
private List<String> rawSQL(String fmt, Object... args) {
|
||||||
return Shell.su("magisk --sqlite '" + Utils.fmt(fmt, args) + "'").exec().getOut();
|
return Shell.su("magisk --sqlite '" + Utils.fmt(fmt, args) + "'").exec().getOut();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
private List<ContentValues> SQL(String fmt, Object... args) {
|
private List<ContentValues> SQL(String fmt, Object... args) {
|
||||||
List<ContentValues> list = new ArrayList<>();
|
List<ContentValues> list = new ArrayList<>();
|
||||||
for (String raw : rawSQL(fmt, args)) {
|
for (String raw : rawSQL(fmt, args)) {
|
||||||
@ -57,6 +62,7 @@ public class MagiskDB {
|
|||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
private String toSQL(ContentValues values) {
|
private String toSQL(ContentValues values) {
|
||||||
StringBuilder keys = new StringBuilder(), vals = new StringBuilder();
|
StringBuilder keys = new StringBuilder(), vals = new StringBuilder();
|
||||||
keys.append('(');
|
keys.append('(');
|
||||||
@ -80,6 +86,7 @@ public class MagiskDB {
|
|||||||
return keys.toString();
|
return keys.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
public void clearOutdated() {
|
public void clearOutdated() {
|
||||||
rawSQL(
|
rawSQL(
|
||||||
"DELETE FROM %s WHERE until > 0 AND until < %d;" +
|
"DELETE FROM %s WHERE until > 0 AND until < %d;" +
|
||||||
@ -89,14 +96,17 @@ public class MagiskDB {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
public void deletePolicy(String pkg) {
|
public void deletePolicy(String pkg) {
|
||||||
rawSQL("DELETE FROM %s WHERE package_name=\"%s\"", POLICY_TABLE, pkg);
|
rawSQL("DELETE FROM %s WHERE package_name=\"%s\"", POLICY_TABLE, pkg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
public void deletePolicy(int uid) {
|
public void deletePolicy(int uid) {
|
||||||
rawSQL("DELETE FROM %s WHERE uid=%d", POLICY_TABLE, uid);
|
rawSQL("DELETE FROM %s WHERE uid=%d", POLICY_TABLE, uid);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
public Policy getPolicy(int uid) {
|
public Policy getPolicy(int uid) {
|
||||||
List<ContentValues> res =
|
List<ContentValues> res =
|
||||||
SQL("SELECT * FROM %s WHERE uid=%d", POLICY_TABLE, uid);
|
SQL("SELECT * FROM %s WHERE uid=%d", POLICY_TABLE, uid);
|
||||||
@ -110,10 +120,12 @@ public class MagiskDB {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
public void updatePolicy(Policy policy) {
|
public void updatePolicy(Policy policy) {
|
||||||
rawSQL("REPLACE INTO %s %s", POLICY_TABLE, toSQL(policy.getContentValues()));
|
rawSQL("REPLACE INTO %s %s", POLICY_TABLE, toSQL(policy.getContentValues()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
public List<Policy> getPolicyList() {
|
public List<Policy> getPolicyList() {
|
||||||
List<Policy> list = new ArrayList<>();
|
List<Policy> list = new ArrayList<>();
|
||||||
for (ContentValues values : SQL("SELECT * FROM %s WHERE uid/100000=%d", POLICY_TABLE, Const.USER_ID)) {
|
for (ContentValues values : SQL("SELECT * FROM %s WHERE uid/100000=%d", POLICY_TABLE, Const.USER_ID)) {
|
||||||
@ -127,6 +139,7 @@ public class MagiskDB {
|
|||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
public List<List<SuLogEntry>> getLogs() {
|
public List<List<SuLogEntry>> getLogs() {
|
||||||
List<List<SuLogEntry>> ret = new ArrayList<>();
|
List<List<SuLogEntry>> ret = new ArrayList<>();
|
||||||
List<SuLogEntry> list = null;
|
List<SuLogEntry> list = null;
|
||||||
@ -144,18 +157,22 @@ public class MagiskDB {
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
public void addLog(SuLogEntry log) {
|
public void addLog(SuLogEntry log) {
|
||||||
rawSQL("INSERT INTO %s %s", LOG_TABLE, toSQL(log.getContentValues()));
|
rawSQL("INSERT INTO %s %s", LOG_TABLE, toSQL(log.getContentValues()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
public void clearLogs() {
|
public void clearLogs() {
|
||||||
rawSQL("DELETE FROM %s", LOG_TABLE);
|
rawSQL("DELETE FROM %s", LOG_TABLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
public void rmSettings(String key) {
|
public void rmSettings(String key) {
|
||||||
rawSQL("DELETE FROM %s WHERE key=\"%s\"", SETTINGS_TABLE, key);
|
rawSQL("DELETE FROM %s WHERE key=\"%s\"", SETTINGS_TABLE, key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
public void setSettings(String key, int value) {
|
public void setSettings(String key, int value) {
|
||||||
ContentValues data = new ContentValues();
|
ContentValues data = new ContentValues();
|
||||||
data.put("key", key);
|
data.put("key", key);
|
||||||
@ -163,6 +180,7 @@ public class MagiskDB {
|
|||||||
rawSQL("REPLACE INTO %s %s", SETTINGS_TABLE, toSQL(data));
|
rawSQL("REPLACE INTO %s %s", SETTINGS_TABLE, toSQL(data));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
public int getSettings(String key, int defaultValue) {
|
public int getSettings(String key, int defaultValue) {
|
||||||
List<ContentValues> res = SQL("SELECT value FROM %s WHERE key=\"%s\"", SETTINGS_TABLE, key);
|
List<ContentValues> res = SQL("SELECT value FROM %s WHERE key=\"%s\"", SETTINGS_TABLE, key);
|
||||||
if (res.isEmpty())
|
if (res.isEmpty())
|
||||||
@ -170,6 +188,7 @@ public class MagiskDB {
|
|||||||
return res.get(0).getAsInteger("value");
|
return res.get(0).getAsInteger("value");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
public void setStrings(String key, String value) {
|
public void setStrings(String key, String value) {
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
rawSQL("DELETE FROM %s WHERE key=\"%s\"", STRINGS_TABLE, key);
|
rawSQL("DELETE FROM %s WHERE key=\"%s\"", STRINGS_TABLE, key);
|
||||||
@ -181,6 +200,7 @@ public class MagiskDB {
|
|||||||
rawSQL("REPLACE INTO %s %s", STRINGS_TABLE, toSQL(data));
|
rawSQL("REPLACE INTO %s %s", STRINGS_TABLE, toSQL(data));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
public String getStrings(String key, String defaultValue) {
|
public String getStrings(String key, String defaultValue) {
|
||||||
List<ContentValues> res = SQL("SELECT value FROM %s WHERE key=\"%s\"", STRINGS_TABLE, key);
|
List<ContentValues> res = SQL("SELECT value FROM %s WHERE key=\"%s\"", STRINGS_TABLE, key);
|
||||||
if (res.isEmpty())
|
if (res.isEmpty())
|
||||||
|
@ -0,0 +1,67 @@
|
|||||||
|
package com.topjohnwu.magisk.data.database
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.pm.PackageManager
|
||||||
|
import com.topjohnwu.magisk.Constants
|
||||||
|
import com.topjohnwu.magisk.data.database.base.*
|
||||||
|
import com.topjohnwu.magisk.model.entity.MagiskPolicy
|
||||||
|
import com.topjohnwu.magisk.model.entity.toMap
|
||||||
|
import com.topjohnwu.magisk.model.entity.toPolicy
|
||||||
|
import com.topjohnwu.magisk.utils.now
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
|
|
||||||
|
class PolicyDao(
|
||||||
|
private val context: Context
|
||||||
|
) : BaseDao() {
|
||||||
|
|
||||||
|
override val table: String = DatabaseDefinition.Table.POLICY
|
||||||
|
|
||||||
|
fun deleteOutdated(
|
||||||
|
nowSeconds: Long = TimeUnit.MILLISECONDS.toSeconds(now)
|
||||||
|
) = query<Delete> {
|
||||||
|
condition {
|
||||||
|
greaterThan("until", "0")
|
||||||
|
and {
|
||||||
|
lessThan("until", nowSeconds.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.ignoreElement()
|
||||||
|
|
||||||
|
fun delete(packageName: String) = query<Delete> {
|
||||||
|
condition {
|
||||||
|
equals("package_name", packageName)
|
||||||
|
}
|
||||||
|
}.ignoreElement()
|
||||||
|
|
||||||
|
fun delete(uid: Int) = query<Delete> {
|
||||||
|
condition {
|
||||||
|
equals("uid", uid.toString())
|
||||||
|
}
|
||||||
|
}.ignoreElement()
|
||||||
|
|
||||||
|
fun fetch(uid: Int) = query<Select> {
|
||||||
|
condition {
|
||||||
|
equals("uid", uid.toString())
|
||||||
|
}
|
||||||
|
}.map { it.first().toPolicy(context.packageManager) }
|
||||||
|
.doOnError {
|
||||||
|
if (it is PackageManager.NameNotFoundException) {
|
||||||
|
delete(uid).subscribe()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun update(policy: MagiskPolicy) = query<Replace> {
|
||||||
|
|
||||||
|
values(policy.toMap())
|
||||||
|
}.ignoreElement()
|
||||||
|
|
||||||
|
fun fetchAll() = query<Select> {
|
||||||
|
condition {
|
||||||
|
equals("uid/100000", Constants.USER_ID.toString())
|
||||||
|
}
|
||||||
|
}.flattenAsFlowable { it }
|
||||||
|
.map { it.toPolicy(context.packageManager) }
|
||||||
|
.toList()
|
||||||
|
|
||||||
|
}
|
@ -11,13 +11,15 @@ import com.topjohnwu.magisk.model.entity.Repo;
|
|||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
public class RepoDatabaseHelper extends SQLiteOpenHelper {
|
public class RepoDatabaseHelper extends SQLiteOpenHelper {
|
||||||
|
|
||||||
private static final int DATABASE_VER = 5;
|
private static final int DATABASE_VER = 5;
|
||||||
private static final String TABLE_NAME = "repos";
|
private static final String TABLE_NAME = "repos";
|
||||||
|
|
||||||
private SQLiteDatabase mDb;
|
private final SQLiteDatabase mDb;
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
public RepoDatabaseHelper(Context context) {
|
public RepoDatabaseHelper(Context context) {
|
||||||
super(context, "repo.db", null, DATABASE_VER);
|
super(context, "repo.db", null, DATABASE_VER);
|
||||||
mDb = getWritableDatabase();
|
mDb = getWritableDatabase();
|
||||||
@ -46,19 +48,23 @@ public class RepoDatabaseHelper extends SQLiteOpenHelper {
|
|||||||
onUpgrade(db, 0, DATABASE_VER);
|
onUpgrade(db, 0, DATABASE_VER);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
public void clearRepo() {
|
public void clearRepo() {
|
||||||
mDb.delete(TABLE_NAME, null, null);
|
mDb.delete(TABLE_NAME, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
public void removeRepo(String id) {
|
public void removeRepo(String id) {
|
||||||
mDb.delete(TABLE_NAME, "id=?", new String[] { id });
|
mDb.delete(TABLE_NAME, "id=?", new String[] { id });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
public void removeRepo(Repo repo) {
|
public void removeRepo(Repo repo) {
|
||||||
removeRepo(repo.getId());
|
removeRepo(repo.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
public void removeRepo(Iterable<String> list) {
|
public void removeRepo(Iterable<String> list) {
|
||||||
for (String id : list) {
|
for (String id : list) {
|
||||||
if (id == null) continue;
|
if (id == null) continue;
|
||||||
@ -66,10 +72,12 @@ public class RepoDatabaseHelper extends SQLiteOpenHelper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
public void addRepo(Repo repo) {
|
public void addRepo(Repo repo) {
|
||||||
mDb.replace(TABLE_NAME, null, repo.getContentValues());
|
mDb.replace(TABLE_NAME, null, repo.getContentValues());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
public Repo getRepo(String id) {
|
public Repo getRepo(String id) {
|
||||||
try (Cursor c = mDb.query(TABLE_NAME, null, "id=?", new String[] { id }, null, null, null)) {
|
try (Cursor c = mDb.query(TABLE_NAME, null, "id=?", new String[] { id }, null, null, null)) {
|
||||||
if (c.moveToNext()) {
|
if (c.moveToNext()) {
|
||||||
@ -79,10 +87,12 @@ public class RepoDatabaseHelper extends SQLiteOpenHelper {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
public Cursor getRawCursor() {
|
public Cursor getRawCursor() {
|
||||||
return mDb.query(TABLE_NAME, null, null, null, null, null, null);
|
return mDb.query(TABLE_NAME, null, null, null, null, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
public Cursor getRepoCursor() {
|
public Cursor getRepoCursor() {
|
||||||
String orderBy = null;
|
String orderBy = null;
|
||||||
switch ((int) Config.get(Config.Key.REPO_ORDER)) {
|
switch ((int) Config.get(Config.Key.REPO_ORDER)) {
|
||||||
@ -95,6 +105,7 @@ public class RepoDatabaseHelper extends SQLiteOpenHelper {
|
|||||||
return mDb.query(TABLE_NAME, null, null, null, null, null, orderBy);
|
return mDb.query(TABLE_NAME, null, null, null, null, null, orderBy);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
public Set<String> getRepoIDSet() {
|
public Set<String> getRepoIDSet() {
|
||||||
HashSet<String> set = new HashSet<>(300);
|
HashSet<String> set = new HashSet<>(300);
|
||||||
try (Cursor c = mDb.query(TABLE_NAME, null, null, null, null, null, null)) {
|
try (Cursor c = mDb.query(TABLE_NAME, null, null, null, null, null, null)) {
|
||||||
|
@ -0,0 +1,17 @@
|
|||||||
|
package com.topjohnwu.magisk.data.database
|
||||||
|
|
||||||
|
import androidx.room.Dao
|
||||||
|
import androidx.room.Query
|
||||||
|
import com.skoumal.teanity.database.BaseDao
|
||||||
|
import com.topjohnwu.magisk.model.entity.Repository
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
interface RepositoryDao : BaseDao<Repository> {
|
||||||
|
|
||||||
|
@Query("DELETE FROM repos")
|
||||||
|
override fun deleteAll()
|
||||||
|
|
||||||
|
@Query("SELECT * FROM repos")
|
||||||
|
override fun fetchAll(): List<Repository>
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
package com.topjohnwu.magisk.data.database
|
||||||
|
|
||||||
|
import com.topjohnwu.magisk.data.database.base.*
|
||||||
|
|
||||||
|
class SettingsDao : BaseDao() {
|
||||||
|
|
||||||
|
override val table = DatabaseDefinition.Table.SETTINGS
|
||||||
|
|
||||||
|
fun delete(key: String) = query<Delete> {
|
||||||
|
condition { equals("key", key) }
|
||||||
|
}.ignoreElement()
|
||||||
|
|
||||||
|
fun put(key: String, value: Int) = query<Insert> {
|
||||||
|
values(key to value.toString())
|
||||||
|
}.ignoreElement()
|
||||||
|
|
||||||
|
fun fetch(key: String) = query<Select> {
|
||||||
|
condition { equals("key", key) }
|
||||||
|
}.map { it.first().values.first().toIntOrNull() ?: -1 }
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,22 @@
|
|||||||
|
package com.topjohnwu.magisk.data.database
|
||||||
|
|
||||||
|
import com.topjohnwu.magisk.data.database.base.*
|
||||||
|
|
||||||
|
class StringsDao : BaseDao() {
|
||||||
|
|
||||||
|
override val table = DatabaseDefinition.Table.STRINGS
|
||||||
|
|
||||||
|
fun delete(key: String) = query<Delete> {
|
||||||
|
condition { equals("key", key) }
|
||||||
|
}.ignoreElement()
|
||||||
|
|
||||||
|
fun put(key: String, value: String) = query<Insert> {
|
||||||
|
values(key to value)
|
||||||
|
}.ignoreElement()
|
||||||
|
|
||||||
|
fun fetch(key: String, default: String = "") = query<Select> {
|
||||||
|
fields("value")
|
||||||
|
condition { equals("key", key) }
|
||||||
|
}.map { it.firstOrNull()?.values?.firstOrNull() ?: default }
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,15 @@
|
|||||||
|
package com.topjohnwu.magisk.data.database.base
|
||||||
|
|
||||||
|
abstract class BaseDao {
|
||||||
|
|
||||||
|
abstract val table: String
|
||||||
|
|
||||||
|
inline fun <reified Builder : MagiskQueryBuilder> query(builder: Builder.() -> Unit) =
|
||||||
|
Builder::class.java.newInstance()
|
||||||
|
.apply { table = this@BaseDao.table }
|
||||||
|
.apply(builder)
|
||||||
|
.toString()
|
||||||
|
.let { MagiskQuery(it) }
|
||||||
|
.query()
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,33 @@
|
|||||||
|
package com.topjohnwu.magisk.data.database.base
|
||||||
|
|
||||||
|
import androidx.annotation.AnyThread
|
||||||
|
import com.topjohnwu.superuser.Shell
|
||||||
|
import io.reactivex.Single
|
||||||
|
|
||||||
|
object DatabaseDefinition {
|
||||||
|
|
||||||
|
object Table {
|
||||||
|
const val POLICY = "policies"
|
||||||
|
const val LOG = "logs"
|
||||||
|
const val SETTINGS = "settings"
|
||||||
|
const val STRINGS = "strings"
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@AnyThread
|
||||||
|
fun MagiskQuery.query() = query.su()
|
||||||
|
|
||||||
|
fun String.suRaw() = Single.just(Shell.su(this))
|
||||||
|
.map { it.exec().out }
|
||||||
|
|
||||||
|
fun String.su() = suRaw()
|
||||||
|
.map { it.toMap() }
|
||||||
|
|
||||||
|
fun List<String>.toMap() = map { it.split(Regex("\\|")) }
|
||||||
|
.map { it.toMapInternal() }
|
||||||
|
|
||||||
|
private fun List<String>.toMapInternal() = map { it.split("=", limit = 2) }
|
||||||
|
.filter { it.size == 2 }
|
||||||
|
.map { Pair(it[0], it[1]) }
|
||||||
|
.toMap()
|
@ -0,0 +1,5 @@
|
|||||||
|
package com.topjohnwu.magisk.data.database.base
|
||||||
|
|
||||||
|
data class MagiskQuery(private val _query: String) {
|
||||||
|
val query = "magisk --sqlite $_query"
|
||||||
|
}
|
@ -0,0 +1,157 @@
|
|||||||
|
package com.topjohnwu.magisk.data.database.base
|
||||||
|
|
||||||
|
import androidx.annotation.StringDef
|
||||||
|
import com.topjohnwu.magisk.data.database.base.Order.Companion.ASC
|
||||||
|
import com.topjohnwu.magisk.data.database.base.Order.Companion.DESC
|
||||||
|
|
||||||
|
interface MagiskQueryBuilder {
|
||||||
|
|
||||||
|
val requestType: String
|
||||||
|
var table: String
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
inline operator fun <reified Builder : MagiskQueryBuilder> invoke(builder: Builder.() -> Unit): MagiskQuery =
|
||||||
|
Builder::class.java.newInstance()
|
||||||
|
.apply(builder)
|
||||||
|
.toString()
|
||||||
|
.let { MagiskQuery(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class Delete : MagiskQueryBuilder {
|
||||||
|
override val requestType: String = "DELETE FROM"
|
||||||
|
override var table = ""
|
||||||
|
|
||||||
|
private var condition = ""
|
||||||
|
|
||||||
|
fun condition(builder: Condition.() -> Unit) {
|
||||||
|
condition = Condition().apply(builder).toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return StringBuilder()
|
||||||
|
.appendln(requestType)
|
||||||
|
.appendln(table)
|
||||||
|
.appendln(condition)
|
||||||
|
.toString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Select : MagiskQueryBuilder {
|
||||||
|
override val requestType: String get() = "SELECT $fields FROM"
|
||||||
|
override lateinit var table: String
|
||||||
|
|
||||||
|
private var fields = "*"
|
||||||
|
private var condition = ""
|
||||||
|
private var orderField = ""
|
||||||
|
|
||||||
|
fun fields(vararg newFields: String) {
|
||||||
|
if (newFields.isEmpty()) {
|
||||||
|
fields = "*"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fields = newFields.joinToString(", ")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun condition(builder: Condition.() -> Unit) {
|
||||||
|
condition = Condition().apply(builder).toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun orderBy(field: String, @OrderStrict order: String) {
|
||||||
|
orderField = "ORDER BY $field $order"
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return StringBuilder()
|
||||||
|
.appendln(requestType)
|
||||||
|
.appendln(table)
|
||||||
|
.appendln(condition)
|
||||||
|
.appendln(orderField)
|
||||||
|
.toString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Replace : Insert() {
|
||||||
|
override val requestType: String = "REPLACE INTO"
|
||||||
|
}
|
||||||
|
|
||||||
|
open class Insert : MagiskQueryBuilder {
|
||||||
|
override val requestType: String = "INSERT INTO"
|
||||||
|
override lateinit var table: String
|
||||||
|
|
||||||
|
private val keys get() = _values.keys.joinToString(",")
|
||||||
|
private val values get() = _values.values.joinToString(",")
|
||||||
|
private var _values: Map<String, String> = mapOf()
|
||||||
|
|
||||||
|
fun values(vararg pairs: Pair<String, String>) {
|
||||||
|
_values = pairs.toMap()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun values(values: Map<String, String>) {
|
||||||
|
_values = values
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return StringBuilder()
|
||||||
|
.appendln(requestType)
|
||||||
|
.appendln(table)
|
||||||
|
.appendln("($keys) VALUES($values)")
|
||||||
|
.toString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Condition {
|
||||||
|
|
||||||
|
private val conditionWord = "WHERE %s"
|
||||||
|
private var condition: String = ""
|
||||||
|
|
||||||
|
fun equals(field: String, value: String) {
|
||||||
|
condition = "$field=\"$value\""
|
||||||
|
}
|
||||||
|
|
||||||
|
fun greaterThan(field: String, value: String) {
|
||||||
|
condition = "$field > $value"
|
||||||
|
}
|
||||||
|
|
||||||
|
fun lessThan(field: String, value: String) {
|
||||||
|
condition = "$field < $value"
|
||||||
|
}
|
||||||
|
|
||||||
|
fun greaterOrEqualTo(field: String, value: String) {
|
||||||
|
condition = "$field >= $value"
|
||||||
|
}
|
||||||
|
|
||||||
|
fun lessOrEqualTo(field: String, value: String) {
|
||||||
|
condition = "$field <= $value"
|
||||||
|
}
|
||||||
|
|
||||||
|
fun and(builder: Condition.() -> Unit) {
|
||||||
|
condition += " " + Condition().apply(builder).condition
|
||||||
|
}
|
||||||
|
|
||||||
|
fun or(builder: Condition.() -> Unit) {
|
||||||
|
condition += " " + Condition().apply(builder).condition
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return conditionWord.format(condition)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Order {
|
||||||
|
|
||||||
|
@set:OrderStrict
|
||||||
|
var order = DESC
|
||||||
|
var field = ""
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val ASC = "ASC"
|
||||||
|
const val DESC = "DESC"
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@StringDef(ASC, DESC)
|
||||||
|
@Retention(AnnotationRetention.SOURCE)
|
||||||
|
annotation class OrderStrict
|
@ -0,0 +1,22 @@
|
|||||||
|
package com.topjohnwu.magisk.data.network
|
||||||
|
|
||||||
|
import com.topjohnwu.magisk.model.entity.GithubRepo
|
||||||
|
import io.reactivex.Single
|
||||||
|
import retrofit2.http.GET
|
||||||
|
import retrofit2.http.Query
|
||||||
|
|
||||||
|
|
||||||
|
interface GithubApiServices {
|
||||||
|
|
||||||
|
@GET("users/Magisk-Modules-Repo/repos")
|
||||||
|
fun fetchRepos(
|
||||||
|
@Query("page") page: Int,
|
||||||
|
@Query("per_page") count: Int = REPOS_PER_PAGE,
|
||||||
|
@Query("sort") sortOrder: String = "pushed"
|
||||||
|
): Single<List<GithubRepo>>
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val REPOS_PER_PAGE = 100
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,75 @@
|
|||||||
|
package com.topjohnwu.magisk.data.network
|
||||||
|
|
||||||
|
import com.topjohnwu.magisk.Constants
|
||||||
|
import com.topjohnwu.magisk.model.entity.MagiskConfig
|
||||||
|
import io.reactivex.Single
|
||||||
|
import okhttp3.ResponseBody
|
||||||
|
import retrofit2.http.GET
|
||||||
|
import retrofit2.http.Path
|
||||||
|
import retrofit2.http.Streaming
|
||||||
|
import retrofit2.http.Url
|
||||||
|
|
||||||
|
|
||||||
|
interface GithubRawApiServices {
|
||||||
|
|
||||||
|
//region topjohnwu/magisk_files
|
||||||
|
|
||||||
|
@GET("$MAGISK_FILES/master/stable.json")
|
||||||
|
fun fetchConfig(): Single<MagiskConfig>
|
||||||
|
|
||||||
|
@GET("$MAGISK_FILES/master/beta.json")
|
||||||
|
fun fetchBetaConfig(): Single<MagiskConfig>
|
||||||
|
|
||||||
|
@GET("$MAGISK_FILES/master/canary_builds/release.json")
|
||||||
|
fun fetchCanaryConfig(): Single<MagiskConfig>
|
||||||
|
|
||||||
|
@GET("$MAGISK_FILES/master/canary_builds/canary.json")
|
||||||
|
fun fetchCanaryDebugConfig(): Single<MagiskConfig>
|
||||||
|
|
||||||
|
@GET("$MAGISK_FILES/{$REVISION}/snet.apk")
|
||||||
|
@Streaming
|
||||||
|
fun fetchSafetynet(@Path(REVISION) revision: String = Constants.SNET_REVISION): Single<ResponseBody>
|
||||||
|
|
||||||
|
@GET("$MAGISK_FILES/{$REVISION}/bootctl")
|
||||||
|
@Streaming
|
||||||
|
fun fetchBootctl(@Path(REVISION) revision: String = Constants.BOOTCTL_REVISION): Single<ResponseBody>
|
||||||
|
|
||||||
|
//endregion
|
||||||
|
|
||||||
|
//region topjohnwu/Magisk/master
|
||||||
|
|
||||||
|
@GET("$MAGISK_MASTER/scripts/module_installer.sh")
|
||||||
|
@Streaming
|
||||||
|
fun fetchModuleInstaller(): Single<ResponseBody>
|
||||||
|
|
||||||
|
//endregion
|
||||||
|
|
||||||
|
//region Magisk-Modules-Repo
|
||||||
|
|
||||||
|
@GET("$MAGISK_MODULES/{$MODULE}/master/{$FILE}")
|
||||||
|
@Streaming
|
||||||
|
fun fetchFile(id: String, file: String): Single<ResponseBody>
|
||||||
|
|
||||||
|
//endregion
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method shall be used exclusively for fetching files from urls from previous requests.
|
||||||
|
* Him, who uses it in a wrong way, shall die in an eternal flame.
|
||||||
|
* */
|
||||||
|
@GET
|
||||||
|
@Streaming
|
||||||
|
fun fetchFile(@Url url: String): Single<ResponseBody>
|
||||||
|
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val REVISION = "revision"
|
||||||
|
private const val MODULE = "module"
|
||||||
|
private const val FILE = "file"
|
||||||
|
|
||||||
|
|
||||||
|
private const val MAGISK_FILES = "topjohnwu/magisk_files"
|
||||||
|
private const val MAGISK_MASTER = "topjohnwu/Magisk/master"
|
||||||
|
private const val MAGISK_MODULES = "Magisk-Modules-Repo"
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
package com.topjohnwu.magisk.data.network
|
||||||
|
|
||||||
|
import io.reactivex.Single
|
||||||
|
import okhttp3.ResponseBody
|
||||||
|
import retrofit2.http.GET
|
||||||
|
import retrofit2.http.Path
|
||||||
|
import retrofit2.http.Streaming
|
||||||
|
|
||||||
|
|
||||||
|
interface GithubServices {
|
||||||
|
|
||||||
|
@GET("Magisk-Modules-Repo/{$MODULE}/archive/master.zip")
|
||||||
|
@Streaming
|
||||||
|
fun fetchModuleZip(@Path(MODULE) module: String): Single<ResponseBody>
|
||||||
|
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val MODULE = "module"
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,45 @@
|
|||||||
|
package com.topjohnwu.magisk.data.repository
|
||||||
|
|
||||||
|
import com.topjohnwu.magisk.Constants
|
||||||
|
import com.topjohnwu.magisk.data.database.LogDao
|
||||||
|
import com.topjohnwu.magisk.data.database.base.suRaw
|
||||||
|
import com.topjohnwu.magisk.model.entity.MagiskLog
|
||||||
|
import com.topjohnwu.magisk.model.entity.WrappedMagiskLog
|
||||||
|
import timber.log.Timber
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
|
|
||||||
|
class LogRepository(
|
||||||
|
private val logDao: LogDao
|
||||||
|
) {
|
||||||
|
|
||||||
|
fun fetchLogs() = logDao.fetchAll()
|
||||||
|
.map { it.sortByDescending { it.date.time }; it }
|
||||||
|
.map { it.wrap() }
|
||||||
|
|
||||||
|
fun fetchMagiskLogs() = "tail -n 5000 ${Constants.MAGISK_LOG}".suRaw()
|
||||||
|
.filter { it.isNotEmpty() }
|
||||||
|
.map { Timber.i(it.toString()); it }
|
||||||
|
|
||||||
|
private fun List<MagiskLog>.wrap(): List<WrappedMagiskLog> {
|
||||||
|
val day = TimeUnit.DAYS.toMillis(1)
|
||||||
|
var currentDay = firstOrNull()?.date?.time ?: return listOf()
|
||||||
|
var tempList = this
|
||||||
|
val outList = mutableListOf<WrappedMagiskLog>()
|
||||||
|
|
||||||
|
while (tempList.isNotEmpty()) {
|
||||||
|
val logsGivenDay = takeWhile { it.date.time / day == currentDay / day }
|
||||||
|
currentDay = tempList.firstOrNull()?.date?.time ?: currentDay + day
|
||||||
|
|
||||||
|
if (logsGivenDay.isEmpty())
|
||||||
|
continue
|
||||||
|
|
||||||
|
outList.add(WrappedMagiskLog(currentDay / day * day, logsGivenDay))
|
||||||
|
tempList = tempList.subList(logsGivenDay.size, tempList.size)
|
||||||
|
}
|
||||||
|
|
||||||
|
return outList
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,78 @@
|
|||||||
|
package com.topjohnwu.magisk.data.repository
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import com.topjohnwu.magisk.KConfig
|
||||||
|
import com.topjohnwu.magisk.data.database.base.suRaw
|
||||||
|
import com.topjohnwu.magisk.data.network.GithubRawApiServices
|
||||||
|
import com.topjohnwu.magisk.model.entity.Version
|
||||||
|
import com.topjohnwu.magisk.utils.writeToFile
|
||||||
|
import io.reactivex.Single
|
||||||
|
import io.reactivex.functions.BiFunction
|
||||||
|
|
||||||
|
class MagiskRepository(
|
||||||
|
private val context: Context,
|
||||||
|
private val apiRaw: GithubRawApiServices
|
||||||
|
) {
|
||||||
|
|
||||||
|
private val config = apiRaw.fetchConfig()
|
||||||
|
private val configBeta = apiRaw.fetchBetaConfig()
|
||||||
|
private val configCanary = apiRaw.fetchCanaryConfig()
|
||||||
|
private val configCanaryDebug = apiRaw.fetchCanaryDebugConfig()
|
||||||
|
|
||||||
|
|
||||||
|
fun fetchMagisk() = fetchConfig()
|
||||||
|
.flatMap { apiRaw.fetchFile(it.magisk.link) }
|
||||||
|
.map { it.writeToFile(context, FILE_MAGISK_ZIP) }
|
||||||
|
|
||||||
|
fun fetchManager() = fetchConfig()
|
||||||
|
.flatMap { apiRaw.fetchFile(it.app.link) }
|
||||||
|
.map { it.writeToFile(context, FILE_MAGISK_APK) }
|
||||||
|
|
||||||
|
fun fetchUninstaller() = fetchConfig()
|
||||||
|
.flatMap { apiRaw.fetchFile(it.uninstaller.link) }
|
||||||
|
.map { it.writeToFile(context, FILE_UNINSTALLER_ZIP) }
|
||||||
|
|
||||||
|
fun fetchSafetynet() = apiRaw
|
||||||
|
.fetchSafetynet()
|
||||||
|
.map { it.writeToFile(context, FILE_SAFETY_NET_APK) }
|
||||||
|
|
||||||
|
fun fetchBootctl() = apiRaw
|
||||||
|
.fetchBootctl()
|
||||||
|
.map { it.writeToFile(context, FILE_BOOTCTL_SH) }
|
||||||
|
|
||||||
|
|
||||||
|
fun fetchConfig() = when (KConfig.updateChannel) {
|
||||||
|
KConfig.UpdateChannel.STABLE -> config
|
||||||
|
KConfig.UpdateChannel.BETA -> configBeta
|
||||||
|
KConfig.UpdateChannel.CANARY -> configCanary
|
||||||
|
KConfig.UpdateChannel.CANARY_DEBUG -> configCanaryDebug
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun fetchMagiskVersion(): Single<Version> = Single.zip(
|
||||||
|
fetchMagiskVersionName(),
|
||||||
|
fetchMagiskVersionCode(),
|
||||||
|
BiFunction { versionName, versionCode ->
|
||||||
|
Version(versionName, versionCode)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun fetchMagiskVersionName() = "magisk -v".suRaw()
|
||||||
|
.map { it.first() }
|
||||||
|
.map { it.substring(0 until it.indexOf(":")) }
|
||||||
|
.onErrorReturn { "Unknown" }
|
||||||
|
|
||||||
|
private fun fetchMagiskVersionCode() = "magisk -V".suRaw()
|
||||||
|
.map { it.first() }
|
||||||
|
.map { it.toIntOrNull() ?: -1 }
|
||||||
|
.onErrorReturn { -1 }
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val FILE_MAGISK_ZIP = "magisk.zip"
|
||||||
|
const val FILE_MAGISK_APK = "magisk.apk"
|
||||||
|
const val FILE_UNINSTALLER_ZIP = "uninstaller.zip"
|
||||||
|
const val FILE_SAFETY_NET_APK = "safetynet.apk"
|
||||||
|
const val FILE_BOOTCTL_SH = "bootctl"
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,67 @@
|
|||||||
|
package com.topjohnwu.magisk.data.repository
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import com.topjohnwu.magisk.data.network.GithubApiServices
|
||||||
|
import com.topjohnwu.magisk.data.network.GithubRawApiServices
|
||||||
|
import com.topjohnwu.magisk.data.network.GithubServices
|
||||||
|
import com.topjohnwu.magisk.model.entity.GithubRepo
|
||||||
|
import com.topjohnwu.magisk.model.entity.toRepository
|
||||||
|
import com.topjohnwu.magisk.utils.writeToFile
|
||||||
|
import com.topjohnwu.magisk.utils.writeToString
|
||||||
|
import io.reactivex.Single
|
||||||
|
|
||||||
|
class ModuleRepository(
|
||||||
|
private val context: Context,
|
||||||
|
private val apiRaw: GithubRawApiServices,
|
||||||
|
private val api: GithubApiServices,
|
||||||
|
private val apiWeb: GithubServices
|
||||||
|
) {
|
||||||
|
|
||||||
|
fun fetchModules() = fetchAllRepos()
|
||||||
|
.flattenAsFlowable { it }
|
||||||
|
.flatMapSingle { fetchProperties(it.name, it.updatedAtMillis) }
|
||||||
|
.toList()
|
||||||
|
|
||||||
|
fun fetchInstallFile(module: String) = apiRaw
|
||||||
|
.fetchFile(module, FILE_INSTALL_SH)
|
||||||
|
.map { it.writeToFile(context, FILE_INSTALL_SH) }
|
||||||
|
|
||||||
|
fun fetchReadme(module: String) = apiRaw
|
||||||
|
.fetchFile(module, FILE_README_MD)
|
||||||
|
.map { it.writeToString() }
|
||||||
|
|
||||||
|
fun fetchConfig(module: String) = apiRaw
|
||||||
|
.fetchFile(module, FILE_CONFIG_SH)
|
||||||
|
.map { it.writeToFile(context, FILE_CONFIG_SH) }
|
||||||
|
|
||||||
|
fun fetchInstallZip(module: String) = apiWeb
|
||||||
|
.fetchModuleZip(module)
|
||||||
|
.map { it.writeToFile(context, FILE_INSTALL_ZIP) }
|
||||||
|
|
||||||
|
fun fetchInstaller() = apiRaw
|
||||||
|
.fetchModuleInstaller()
|
||||||
|
.map { it.writeToFile(context, FILE_MODULE_INSTALLER_SH) }
|
||||||
|
|
||||||
|
|
||||||
|
private fun fetchProperties(module: String, lastChanged: Long) = apiRaw
|
||||||
|
.fetchFile(module, "module.prop")
|
||||||
|
.map { it.toRepository(lastChanged) }
|
||||||
|
|
||||||
|
private fun fetchAllRepos(page: Int = 0): Single<List<GithubRepo>> = api.fetchRepos(page)
|
||||||
|
.flatMap {
|
||||||
|
if (it.size == GithubApiServices.REPOS_PER_PAGE) {
|
||||||
|
fetchAllRepos(page + 1).map { newList -> it + newList }
|
||||||
|
} else {
|
||||||
|
Single.just(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val FILE_INSTALL_SH = "install.sh"
|
||||||
|
const val FILE_README_MD = "README.md"
|
||||||
|
const val FILE_CONFIG_SH = "config.sh"
|
||||||
|
const val FILE_INSTALL_ZIP = "install.zip"
|
||||||
|
const val FILE_MODULE_INSTALLER_SH = "module_installer.sh"
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,6 +1,65 @@
|
|||||||
package com.topjohnwu.magisk.di
|
package com.topjohnwu.magisk.di
|
||||||
|
|
||||||
|
import com.squareup.moshi.Moshi
|
||||||
|
import com.topjohnwu.magisk.Constants
|
||||||
|
import com.topjohnwu.magisk.data.network.GithubApiServices
|
||||||
|
import com.topjohnwu.magisk.data.network.GithubRawApiServices
|
||||||
|
import com.topjohnwu.magisk.data.network.GithubServices
|
||||||
|
import okhttp3.OkHttpClient
|
||||||
|
import okhttp3.logging.HttpLoggingInterceptor
|
||||||
import org.koin.dsl.module
|
import org.koin.dsl.module
|
||||||
|
import retrofit2.CallAdapter
|
||||||
|
import retrofit2.Converter
|
||||||
|
import retrofit2.Retrofit
|
||||||
|
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory
|
||||||
|
import retrofit2.converter.moshi.MoshiConverterFactory
|
||||||
|
|
||||||
|
val networkingModule = module {
|
||||||
|
single { createOkHttpClient() }
|
||||||
|
|
||||||
val networkingModule = module {}
|
single { createConverterFactory() }
|
||||||
|
single { createCallAdapterFactory() }
|
||||||
|
|
||||||
|
single { createRetrofit(get(), get(), get()) }
|
||||||
|
|
||||||
|
single { createApiService<GithubServices>(get(), Constants.GITHUB_URL) }
|
||||||
|
single { createApiService<GithubApiServices>(get(), Constants.GITHUB_API_URL) }
|
||||||
|
single { createApiService<GithubRawApiServices>(get(), Constants.GITHUB_RAW_API_URL) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun createOkHttpClient(): OkHttpClient {
|
||||||
|
val httpLoggingInterceptor = HttpLoggingInterceptor().apply {
|
||||||
|
level = HttpLoggingInterceptor.Level.BODY
|
||||||
|
}
|
||||||
|
|
||||||
|
return OkHttpClient.Builder()
|
||||||
|
.addInterceptor(httpLoggingInterceptor)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun createConverterFactory(): Converter.Factory {
|
||||||
|
val moshi = Moshi.Builder().build()
|
||||||
|
return MoshiConverterFactory.create(moshi)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun createCallAdapterFactory(): CallAdapter.Factory {
|
||||||
|
return RxJava2CallAdapterFactory.create()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun createRetrofit(
|
||||||
|
okHttpClient: OkHttpClient,
|
||||||
|
converterFactory: Converter.Factory,
|
||||||
|
callAdapterFactory: CallAdapter.Factory
|
||||||
|
): Retrofit.Builder {
|
||||||
|
return Retrofit.Builder()
|
||||||
|
.addConverterFactory(converterFactory)
|
||||||
|
.addCallAdapterFactory(callAdapterFactory)
|
||||||
|
.client(okHttpClient)
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun <reified T> createApiService(retrofitBuilder: Retrofit.Builder, baseUrl: String): T {
|
||||||
|
return retrofitBuilder
|
||||||
|
.baseUrl(baseUrl)
|
||||||
|
.build()
|
||||||
|
.create(T::class.java)
|
||||||
|
}
|
@ -0,0 +1,12 @@
|
|||||||
|
package com.topjohnwu.magisk.model.entity
|
||||||
|
|
||||||
|
import com.squareup.moshi.Json
|
||||||
|
import com.topjohnwu.magisk.utils.timeFormatStandard
|
||||||
|
import com.topjohnwu.magisk.utils.toTime
|
||||||
|
|
||||||
|
data class GithubRepo(
|
||||||
|
@Json(name = "name") val name: String,
|
||||||
|
@Json(name = "updated_at") val updatedAt: String
|
||||||
|
) {
|
||||||
|
val updatedAtMillis by lazy { updatedAt.toTime(timeFormatStandard) }
|
||||||
|
}
|
@ -0,0 +1,32 @@
|
|||||||
|
package com.topjohnwu.magisk.model.entity
|
||||||
|
|
||||||
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
|
data class MagiskConfig(
|
||||||
|
val app: MagiskApp,
|
||||||
|
val uninstaller: MagiskLink,
|
||||||
|
val magisk: MagiskFlashable
|
||||||
|
)
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
data class MagiskApp(
|
||||||
|
val version: String,
|
||||||
|
val versionCode: String,
|
||||||
|
val link: String,
|
||||||
|
val note: String
|
||||||
|
)
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
data class MagiskLink(
|
||||||
|
val link: String
|
||||||
|
)
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
data class MagiskFlashable(
|
||||||
|
val version: String,
|
||||||
|
val versionCode: String,
|
||||||
|
val link: String,
|
||||||
|
val note: String,
|
||||||
|
@Json(name = "md5") val hash: String
|
||||||
|
)
|
@ -0,0 +1,46 @@
|
|||||||
|
package com.topjohnwu.magisk.model.entity
|
||||||
|
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
data class MagiskLog(
|
||||||
|
val fromUid: Int,
|
||||||
|
val toUid: Int,
|
||||||
|
val fromPid: Int,
|
||||||
|
val packageName: String,
|
||||||
|
val appName: String,
|
||||||
|
val command: String,
|
||||||
|
val action: Boolean,
|
||||||
|
val date: Date
|
||||||
|
)
|
||||||
|
|
||||||
|
data class WrappedMagiskLog(
|
||||||
|
val time: Long,
|
||||||
|
val items: List<MagiskLog>
|
||||||
|
)
|
||||||
|
|
||||||
|
fun Map<String, String>.toLog(): MagiskLog {
|
||||||
|
return MagiskLog(
|
||||||
|
fromUid = get("from_uid")?.toIntOrNull() ?: -1,
|
||||||
|
toUid = get("to_uid")?.toIntOrNull() ?: -1,
|
||||||
|
fromPid = get("from_pid")?.toIntOrNull() ?: -1,
|
||||||
|
packageName = get("package_name").orEmpty(),
|
||||||
|
appName = get("app_name").orEmpty(),
|
||||||
|
command = get("command").orEmpty(),
|
||||||
|
action = get("action")?.toIntOrNull() != 0,
|
||||||
|
date = get("time")?.toLongOrNull()?.toDate() ?: Date()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Long.toDate() = Date(this)
|
||||||
|
|
||||||
|
|
||||||
|
fun MagiskLog.toMap() = mapOf(
|
||||||
|
"from_uid" to fromUid,
|
||||||
|
"to_uid" to toUid,
|
||||||
|
"from_pid" to fromPid,
|
||||||
|
"package_name" to packageName,
|
||||||
|
"app_name" to appName,
|
||||||
|
"command" to command,
|
||||||
|
"action" to action,
|
||||||
|
"time" to date
|
||||||
|
).mapValues { it.toString() }
|
@ -0,0 +1,81 @@
|
|||||||
|
package com.topjohnwu.magisk.model.entity
|
||||||
|
|
||||||
|
import android.os.Parcelable
|
||||||
|
import androidx.annotation.AnyThread
|
||||||
|
import androidx.annotation.NonNull
|
||||||
|
import androidx.annotation.WorkerThread
|
||||||
|
import androidx.room.Entity
|
||||||
|
import androidx.room.PrimaryKey
|
||||||
|
import com.topjohnwu.magisk.Constants
|
||||||
|
import com.topjohnwu.magisk.data.database.base.su
|
||||||
|
import io.reactivex.Single
|
||||||
|
import kotlinx.android.parcel.Parcelize
|
||||||
|
import okhttp3.ResponseBody
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
interface MagiskModule : Parcelable {
|
||||||
|
val id: String
|
||||||
|
val name: String
|
||||||
|
val author: String
|
||||||
|
val version: String
|
||||||
|
val versionCode: String
|
||||||
|
}
|
||||||
|
|
||||||
|
@Entity(tableName = "repos")
|
||||||
|
@Parcelize
|
||||||
|
data class Repository(
|
||||||
|
@PrimaryKey @NonNull
|
||||||
|
override val id: String,
|
||||||
|
override val name: String,
|
||||||
|
override val author: String,
|
||||||
|
override val version: String,
|
||||||
|
override val versionCode: String,
|
||||||
|
val lastUpdate: Long
|
||||||
|
) : MagiskModule
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
data class Module(
|
||||||
|
override val id: String,
|
||||||
|
override val name: String,
|
||||||
|
override val author: String,
|
||||||
|
override val version: String,
|
||||||
|
override val versionCode: String,
|
||||||
|
val path: String
|
||||||
|
) : MagiskModule
|
||||||
|
|
||||||
|
@AnyThread
|
||||||
|
fun File.toModule(): Single<Module> {
|
||||||
|
val path = "${Constants.MAGISK_PATH}/$name"
|
||||||
|
return "dos2unix < $path/module.prop".su()
|
||||||
|
.map { it.first().toModule(path) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Map<String, String>.toModule(path: String): Module {
|
||||||
|
return Module(
|
||||||
|
id = get("id").orEmpty(),
|
||||||
|
name = get("name").orEmpty(),
|
||||||
|
author = get("author").orEmpty(),
|
||||||
|
version = get("version").orEmpty(),
|
||||||
|
versionCode = get("versionCode").orEmpty(),
|
||||||
|
path = path
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@WorkerThread
|
||||||
|
fun ResponseBody.toRepository(lastUpdate: Long) = string()
|
||||||
|
.split(Regex("\\n"))
|
||||||
|
.map { it.split("=", limit = 2) }
|
||||||
|
.filter { it.size == 2 }
|
||||||
|
.map { Pair(it[0], it[1]) }
|
||||||
|
.toMap()
|
||||||
|
.toRepository(lastUpdate)
|
||||||
|
|
||||||
|
@AnyThread
|
||||||
|
fun Map<String, String>.toRepository(lastUpdate: Long) = Repository(
|
||||||
|
id = get("id").orEmpty(),
|
||||||
|
name = get("name").orEmpty(),
|
||||||
|
author = get("author").orEmpty(),
|
||||||
|
version = get("version").orEmpty(),
|
||||||
|
versionCode = get("versionCode").orEmpty(),
|
||||||
|
lastUpdate = lastUpdate
|
||||||
|
)
|
@ -0,0 +1,74 @@
|
|||||||
|
package com.topjohnwu.magisk.model.entity
|
||||||
|
|
||||||
|
import android.content.pm.ApplicationInfo
|
||||||
|
import android.content.pm.PackageManager
|
||||||
|
|
||||||
|
|
||||||
|
data class MagiskPolicy(
|
||||||
|
val uid: Int,
|
||||||
|
val packageName: String,
|
||||||
|
val appName: String,
|
||||||
|
val policy: Int,
|
||||||
|
val until: Long,
|
||||||
|
val logging: Boolean,
|
||||||
|
val notification: Boolean,
|
||||||
|
val applicationInfo: ApplicationInfo
|
||||||
|
)
|
||||||
|
|
||||||
|
/*@Throws(PackageManager.NameNotFoundException::class)
|
||||||
|
fun ContentValues.toPolicy(pm: PackageManager): MagiskPolicy {
|
||||||
|
val uid = getAsInteger("uid")
|
||||||
|
val packageName = getAsString("package_name")
|
||||||
|
val info = pm.getApplicationInfo(packageName, 0)
|
||||||
|
if (info.uid != uid)
|
||||||
|
throw PackageManager.NameNotFoundException()
|
||||||
|
return MagiskPolicy(
|
||||||
|
uid = uid,
|
||||||
|
packageName = packageName,
|
||||||
|
policy = getAsInteger("policy"),
|
||||||
|
until = getAsInteger("until").toLong(),
|
||||||
|
logging = getAsInteger("logging") != 0,
|
||||||
|
notification = getAsInteger("notification") != 0,
|
||||||
|
applicationInfo = info,
|
||||||
|
appName = info.loadLabel(pm).toString()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
fun MagiskPolicy.toContentValues() = ContentValues().apply {
|
||||||
|
put("uid", uid)
|
||||||
|
put("uid", uid)
|
||||||
|
put("package_name", packageName)
|
||||||
|
put("policy", policy)
|
||||||
|
put("until", until)
|
||||||
|
put("logging", if (logging) 1 else 0)
|
||||||
|
put("notification", if (notification) 1 else 0)
|
||||||
|
}*/
|
||||||
|
|
||||||
|
fun MagiskPolicy.toMap() = mapOf(
|
||||||
|
"uid" to uid,
|
||||||
|
"package_name" to packageName,
|
||||||
|
"policy" to policy,
|
||||||
|
"until" to until,
|
||||||
|
"logging" to if (logging) 1 else 0,
|
||||||
|
"notification" to if (notification) 1 else 0
|
||||||
|
).mapValues { it.toString() }
|
||||||
|
|
||||||
|
@Throws(PackageManager.NameNotFoundException::class)
|
||||||
|
fun Map<String, String>.toPolicy(pm: PackageManager): MagiskPolicy {
|
||||||
|
val uid = get("uid")?.toIntOrNull() ?: -1
|
||||||
|
val packageName = get("package_name").orEmpty()
|
||||||
|
val info = pm.getApplicationInfo(packageName, 0)
|
||||||
|
|
||||||
|
if (info.uid != uid)
|
||||||
|
throw PackageManager.NameNotFoundException()
|
||||||
|
|
||||||
|
return MagiskPolicy(
|
||||||
|
uid = uid,
|
||||||
|
packageName = packageName,
|
||||||
|
policy = get("policy")?.toIntOrNull() ?: -1,
|
||||||
|
until = get("until")?.toLongOrNull() ?: -1L,
|
||||||
|
logging = get("logging")?.toIntOrNull() != 0,
|
||||||
|
notification = get("notification")?.toIntOrNull() != 0,
|
||||||
|
applicationInfo = info,
|
||||||
|
appName = info.loadLabel(pm).toString()
|
||||||
|
)
|
||||||
|
}
|
@ -0,0 +1,3 @@
|
|||||||
|
package com.topjohnwu.magisk.model.entity
|
||||||
|
|
||||||
|
data class Version(val version: String, val versionCode: Int)
|
75
app/src/main/java/com/topjohnwu/magisk/model/zip/Zip.kt
Normal file
75
app/src/main/java/com/topjohnwu/magisk/model/zip/Zip.kt
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
package com.topjohnwu.magisk.model.zip
|
||||||
|
|
||||||
|
import com.topjohnwu.magisk.utils.forEach
|
||||||
|
import com.topjohnwu.magisk.utils.withStreams
|
||||||
|
import com.topjohnwu.superuser.io.SuFile
|
||||||
|
import java.io.File
|
||||||
|
import java.util.zip.ZipInputStream
|
||||||
|
|
||||||
|
|
||||||
|
class Zip private constructor(private val values: Builder) {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
operator fun invoke(builder: Builder.() -> Unit): Zip {
|
||||||
|
return Zip(Builder().apply(builder))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Builder {
|
||||||
|
lateinit var zip: File
|
||||||
|
lateinit var destination: File
|
||||||
|
var excludeDirs = true
|
||||||
|
}
|
||||||
|
|
||||||
|
data class Path(val path: String, val pullFromDir: Boolean = true)
|
||||||
|
|
||||||
|
fun unzip(vararg paths: Pair<String, Boolean>) =
|
||||||
|
unzip(*paths.map { Path(it.first, it.second) }.toTypedArray())
|
||||||
|
|
||||||
|
@Suppress("RedundantLambdaArrow")
|
||||||
|
fun unzip(vararg paths: Path) {
|
||||||
|
ensureRequiredParams()
|
||||||
|
|
||||||
|
values.zip.zipStream().use {
|
||||||
|
it.forEach { e ->
|
||||||
|
val currentPath = paths.firstOrNull { e.name.startsWith(it.path) }
|
||||||
|
val isDirectory = values.excludeDirs && e.isDirectory
|
||||||
|
if (currentPath == null || isDirectory) {
|
||||||
|
// Ignore directories, only create files
|
||||||
|
return@forEach
|
||||||
|
}
|
||||||
|
|
||||||
|
val name = if (currentPath.pullFromDir) {
|
||||||
|
e.name.substring(e.name.lastIndexOf('/') + 1)
|
||||||
|
} else {
|
||||||
|
e.name
|
||||||
|
}
|
||||||
|
|
||||||
|
val out = File(values.destination, name)
|
||||||
|
.ensureExists()
|
||||||
|
.outputStream()
|
||||||
|
//.suOutputStream()
|
||||||
|
|
||||||
|
withStreams(it, out) { reader, writer ->
|
||||||
|
reader.copyTo(writer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun ensureRequiredParams() {
|
||||||
|
if (!values.zip.exists()) {
|
||||||
|
throw RuntimeException("Zip file does not exist")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun File.ensureExists() =
|
||||||
|
if ((!parentFile.exists() && !parentFile.mkdirs()) || parentFile is SuFile) {
|
||||||
|
SuFile(parentFile, name).apply { parentFile.mkdirs() }
|
||||||
|
} else {
|
||||||
|
this
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun File.zipStream() = ZipInputStream(inputStream())
|
||||||
|
|
||||||
|
}
|
@ -14,6 +14,7 @@ import com.topjohnwu.superuser.internal.UiThreadHandler;
|
|||||||
import org.json.JSONException;
|
import org.json.JSONException;
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
public class CheckUpdates {
|
public class CheckUpdates {
|
||||||
|
|
||||||
private static Request getRequest() {
|
private static Request getRequest() {
|
||||||
@ -56,8 +57,8 @@ public class CheckUpdates {
|
|||||||
|
|
||||||
private static class UpdateListener implements ResponseListener<JSONObject> {
|
private static class UpdateListener implements ResponseListener<JSONObject> {
|
||||||
|
|
||||||
private Runnable cb;
|
private final Runnable cb;
|
||||||
private long start;
|
private final long start;
|
||||||
|
|
||||||
UpdateListener(Runnable callback) {
|
UpdateListener(Runnable callback) {
|
||||||
cb = callback;
|
cb = callback;
|
||||||
|
@ -31,6 +31,7 @@ import java.util.concurrent.ConcurrentLinkedQueue;
|
|||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
import java.util.concurrent.Future;
|
import java.util.concurrent.Future;
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
public class UpdateRepos {
|
public class UpdateRepos {
|
||||||
private static final DateFormat DATE_FORMAT;
|
private static final DateFormat DATE_FORMAT;
|
||||||
|
|
||||||
|
@ -1,14 +1,15 @@
|
|||||||
package com.topjohnwu.magisk.utils;
|
package com.topjohnwu.magisk.utils;
|
||||||
|
|
||||||
import androidx.annotation.IntDef;
|
|
||||||
import androidx.collection.ArraySet;
|
|
||||||
|
|
||||||
import com.topjohnwu.superuser.internal.UiThreadHandler;
|
import com.topjohnwu.superuser.internal.UiThreadHandler;
|
||||||
|
|
||||||
import java.lang.annotation.Retention;
|
import java.lang.annotation.Retention;
|
||||||
import java.lang.annotation.RetentionPolicy;
|
import java.lang.annotation.RetentionPolicy;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
import androidx.annotation.IntDef;
|
||||||
|
import androidx.collection.ArraySet;
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
public class Event {
|
public class Event {
|
||||||
|
|
||||||
public static final int MAGISK_HIDE_DONE = 0;
|
public static final int MAGISK_HIDE_DONE = 0;
|
||||||
@ -23,8 +24,9 @@ public class Event {
|
|||||||
public @interface EventID {}
|
public @interface EventID {}
|
||||||
|
|
||||||
// We will not dynamically add topics, so use arrays instead of hash tables
|
// We will not dynamically add topics, so use arrays instead of hash tables
|
||||||
private static Store[] eventList = new Store[5];
|
private static final Store[] eventList = new Store[5];
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
public static void register(Listener listener, @EventID int... events) {
|
public static void register(Listener listener, @EventID int... events) {
|
||||||
for (int event : events) {
|
for (int event : events) {
|
||||||
if (eventList[event] == null)
|
if (eventList[event] == null)
|
||||||
@ -36,10 +38,12 @@ public class Event {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
public static void register(AutoListener listener) {
|
public static void register(AutoListener listener) {
|
||||||
register(listener, listener.getListeningEvents());
|
register(listener, listener.getListeningEvents());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
public static void unregister(Listener listener, @EventID int... events) {
|
public static void unregister(Listener listener, @EventID int... events) {
|
||||||
for (int event : events) {
|
for (int event : events) {
|
||||||
if (eventList[event] == null)
|
if (eventList[event] == null)
|
||||||
@ -48,22 +52,27 @@ public class Event {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
public static void unregister(AutoListener listener) {
|
public static void unregister(AutoListener listener) {
|
||||||
unregister(listener, listener.getListeningEvents());
|
unregister(listener, listener.getListeningEvents());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
public static void trigger(@EventID int event) {
|
public static void trigger(@EventID int event) {
|
||||||
trigger(true, event, null);
|
trigger(true, event, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
public static void trigger(@EventID int event, Object result) {
|
public static void trigger(@EventID int event, Object result) {
|
||||||
trigger(true, event, result);
|
trigger(true, event, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
public static void trigger(boolean perm, @EventID int event) {
|
public static void trigger(boolean perm, @EventID int event) {
|
||||||
trigger(perm, event, null);
|
trigger(perm, event, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
public static void trigger(boolean perm, @EventID int event, Object result) {
|
public static void trigger(boolean perm, @EventID int event, Object result) {
|
||||||
if (eventList[event] == null)
|
if (eventList[event] == null)
|
||||||
eventList[event] = new Store();
|
eventList[event] = new Store();
|
||||||
@ -76,6 +85,7 @@ public class Event {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
public static void reset(@EventID int event) {
|
public static void reset(@EventID int event) {
|
||||||
if (eventList[event] == null)
|
if (eventList[event] == null)
|
||||||
return;
|
return;
|
||||||
@ -83,17 +93,20 @@ public class Event {
|
|||||||
eventList[event].result = null;
|
eventList[event].result = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
public static void reset(AutoListener listener) {
|
public static void reset(AutoListener listener) {
|
||||||
for (int event : listener.getListeningEvents())
|
for (int event : listener.getListeningEvents())
|
||||||
reset(event);
|
reset(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
public static boolean isTriggered(@EventID int event) {
|
public static boolean isTriggered(@EventID int event) {
|
||||||
if (eventList[event] == null)
|
if (eventList[event] == null)
|
||||||
return false;
|
return false;
|
||||||
return eventList[event].triggered;
|
return eventList[event].triggered;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
public static boolean isTriggered(AutoListener listener) {
|
public static boolean isTriggered(AutoListener listener) {
|
||||||
for (int event : listener.getListeningEvents()) {
|
for (int event : listener.getListeningEvents()) {
|
||||||
if (!isTriggered(event))
|
if (!isTriggered(event))
|
||||||
@ -102,22 +115,26 @@ public class Event {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
public static <T> T getResult(@EventID int event) {
|
public static <T> T getResult(@EventID int event) {
|
||||||
return (T) eventList[event].result;
|
return (T) eventList[event].result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
|
public interface Listener {
|
||||||
|
void onEvent(int event);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
|
public interface AutoListener extends Listener {
|
||||||
|
@EventID
|
||||||
|
int[] getListeningEvents();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
private static class Store {
|
private static class Store {
|
||||||
boolean triggered = false;
|
boolean triggered = false;
|
||||||
Set<Listener> listeners = new ArraySet<>();
|
Set<Listener> listeners = new ArraySet<>();
|
||||||
Object result;
|
Object result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface Listener {
|
|
||||||
void onEvent(int event);
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface AutoListener extends Listener {
|
|
||||||
@EventID
|
|
||||||
int[] getListeningEvents();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package com.topjohnwu.magisk.utils
|
package com.topjohnwu.magisk.utils
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
import android.content.pm.ApplicationInfo
|
import android.content.pm.ApplicationInfo
|
||||||
import android.content.pm.ComponentInfo
|
import android.content.pm.ComponentInfo
|
||||||
import android.content.pm.PackageInfo
|
import android.content.pm.PackageInfo
|
||||||
@ -9,6 +10,7 @@ import android.content.pm.PackageManager.*
|
|||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.provider.OpenableColumns
|
import android.provider.OpenableColumns
|
||||||
import com.topjohnwu.magisk.App
|
import com.topjohnwu.magisk.App
|
||||||
|
import java.io.File
|
||||||
import java.io.FileNotFoundException
|
import java.io.FileNotFoundException
|
||||||
|
|
||||||
val PackageInfo.processes
|
val PackageInfo.processes
|
||||||
@ -74,3 +76,22 @@ fun Context.rawResource(id: Int) = resources.openRawResource(id)
|
|||||||
|
|
||||||
fun Context.readUri(uri: Uri) = contentResolver.openInputStream(uri) ?: throw FileNotFoundException()
|
fun Context.readUri(uri: Uri) = contentResolver.openInputStream(uri) ?: throw FileNotFoundException()
|
||||||
|
|
||||||
|
fun ApplicationInfo.findAppLabel(pm: PackageManager): String {
|
||||||
|
return pm.getApplicationLabel(this)?.toString().orEmpty()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Intent.startActivity(context: Context) = context.startActivity(this)
|
||||||
|
|
||||||
|
fun File.provide(): Uri {
|
||||||
|
val context: Context by inject()
|
||||||
|
return FileProvider.getUriForFile(context, "com.topjohnwu.magisk.fileprovider", this)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun File.mv(destination: File) {
|
||||||
|
inputStream().copyTo(destination)
|
||||||
|
deleteRecursively()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun String.toFile() = File(this)
|
||||||
|
|
||||||
|
fun Intent.chooser(title: String = "Pick an app") = Intent.createChooser(this, title)
|
38
app/src/main/java/com/topjohnwu/magisk/utils/XJava.kt
Normal file
38
app/src/main/java/com/topjohnwu/magisk/utils/XJava.kt
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
package com.topjohnwu.magisk.utils
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
|
import androidx.core.net.toFile
|
||||||
|
import org.kamranzafar.jtar.TarInputStream
|
||||||
|
import org.kamranzafar.jtar.TarOutputStream
|
||||||
|
import java.io.File
|
||||||
|
import java.io.InputStream
|
||||||
|
import java.io.OutputStream
|
||||||
|
import java.util.zip.ZipEntry
|
||||||
|
import java.util.zip.ZipInputStream
|
||||||
|
|
||||||
|
fun ZipInputStream.forEach(callback: (ZipEntry) -> Unit) {
|
||||||
|
var entry: ZipEntry? = nextEntry
|
||||||
|
while (entry != null) {
|
||||||
|
callback(entry)
|
||||||
|
entry = nextEntry
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Uri.copyTo(file: File) = toFile().copyTo(file)
|
||||||
|
fun InputStream.copyTo(file: File) =
|
||||||
|
withStreams(this, file.outputStream()) { reader, writer -> reader.copyTo(writer) }
|
||||||
|
|
||||||
|
fun File.tarInputStream() = TarInputStream(inputStream())
|
||||||
|
fun File.tarOutputStream() = TarOutputStream(this)
|
||||||
|
|
||||||
|
inline fun <In : InputStream, Out : OutputStream> withStreams(
|
||||||
|
inStream: In,
|
||||||
|
outStream: Out,
|
||||||
|
withBoth: (In, Out) -> Unit
|
||||||
|
) {
|
||||||
|
inStream.use { reader ->
|
||||||
|
outStream.use { writer ->
|
||||||
|
withBoth(reader, writer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
54
app/src/main/java/com/topjohnwu/magisk/utils/XNetwork.kt
Normal file
54
app/src/main/java/com/topjohnwu/magisk/utils/XNetwork.kt
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
package com.topjohnwu.magisk.utils
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import androidx.browser.customtabs.CustomTabsIntent
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.core.net.toUri
|
||||||
|
import com.topjohnwu.magisk.Config
|
||||||
|
import com.topjohnwu.magisk.R
|
||||||
|
import okhttp3.ResponseBody
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
fun ResponseBody.writeToFile(context: Context, fileName: String): File {
|
||||||
|
val file = File(context.cacheDir, fileName)
|
||||||
|
withStreams(byteStream(), file.outputStream()) { inStream, outStream ->
|
||||||
|
inStream.copyTo(outStream)
|
||||||
|
}
|
||||||
|
return file
|
||||||
|
}
|
||||||
|
|
||||||
|
fun ResponseBody.writeToString() = string()
|
||||||
|
|
||||||
|
fun String.launch() = if (Config.useCustomTabs) {
|
||||||
|
launchWithCustomTabs()
|
||||||
|
} else {
|
||||||
|
launchWithIntent()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun String.launchWithCustomTabs() {
|
||||||
|
val context: Context by inject()
|
||||||
|
val primaryColor = ContextCompat.getColor(context, R.color.colorPrimary)
|
||||||
|
val secondaryColor = ContextCompat.getColor(context, R.color.colorSecondary)
|
||||||
|
|
||||||
|
CustomTabsIntent.Builder()
|
||||||
|
.enableUrlBarHiding()
|
||||||
|
.setShowTitle(true)
|
||||||
|
.setToolbarColor(primaryColor)
|
||||||
|
.setSecondaryToolbarColor(secondaryColor)
|
||||||
|
.build()
|
||||||
|
.apply { intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK }
|
||||||
|
.launchUrl(context, this.toUri())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun String.launchWithIntent() {
|
||||||
|
val context: Context by inject()
|
||||||
|
|
||||||
|
Intent(Intent.ACTION_VIEW)
|
||||||
|
.apply {
|
||||||
|
data = toUri()
|
||||||
|
flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||||
|
}
|
||||||
|
.startActivity(context)
|
||||||
|
}
|
19
app/src/main/java/com/topjohnwu/magisk/utils/XSU.kt
Normal file
19
app/src/main/java/com/topjohnwu/magisk/utils/XSU.kt
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
package com.topjohnwu.magisk.utils
|
||||||
|
|
||||||
|
import com.topjohnwu.superuser.Shell
|
||||||
|
import com.topjohnwu.superuser.io.SuFileInputStream
|
||||||
|
import com.topjohnwu.superuser.io.SuFileOutputStream
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
fun reboot(recovery: Boolean = false): Shell.Result {
|
||||||
|
val command = StringBuilder("/system/bin/reboot")
|
||||||
|
.appendIf(recovery) {
|
||||||
|
append(" recovery")
|
||||||
|
}
|
||||||
|
.toString()
|
||||||
|
|
||||||
|
return Shell.su(command).exec()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun File.suOutputStream() = SuFileOutputStream(this)
|
||||||
|
fun File.suInputStream() = SuFileInputStream(this)
|
@ -9,3 +9,6 @@ fun String.replaceRandomWithSpecial(): String {
|
|||||||
} while (random == '.')
|
} while (random == '.')
|
||||||
return replace(random, specialChars.random())
|
return replace(random, specialChars.random())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun StringBuilder.appendIf(condition: Boolean, builder: StringBuilder.() -> Unit) =
|
||||||
|
if (condition) apply(builder) else this
|
14
app/src/main/java/com/topjohnwu/magisk/utils/XView.kt
Normal file
14
app/src/main/java/com/topjohnwu/magisk/utils/XView.kt
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
package com.topjohnwu.magisk.utils
|
||||||
|
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewTreeObserver
|
||||||
|
|
||||||
|
fun View.setOnViewReadyListener(callback: () -> Unit) = addOnGlobalLayoutListener(true, callback)
|
||||||
|
|
||||||
|
fun View.addOnGlobalLayoutListener(oneShot: Boolean = false, callback: () -> Unit) =
|
||||||
|
viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
|
||||||
|
override fun onGlobalLayout() {
|
||||||
|
if (oneShot) viewTreeObserver.removeOnGlobalLayoutListener(this)
|
||||||
|
callback()
|
||||||
|
}
|
||||||
|
})
|
@ -13,6 +13,7 @@ import java.net.URL;
|
|||||||
|
|
||||||
import javax.net.ssl.HttpsURLConnection;
|
import javax.net.ssl.HttpsURLConnection;
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
public class Networking {
|
public class Networking {
|
||||||
|
|
||||||
private static final int READ_TIMEOUT = 15000;
|
private static final int READ_TIMEOUT = 15000;
|
||||||
|
Loading…
Reference in New Issue
Block a user