mirror of
https://github.com/topjohnwu/Magisk
synced 2025-10-27 04:02:14 +01:00
Compare commits
24 Commits
manager-v5
...
manager-v5
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8d139e156e | ||
|
|
7c2849356a | ||
|
|
0025ffd1c0 | ||
|
|
2ef7146642 | ||
|
|
1b27e69e40 | ||
|
|
8e7b757efd | ||
|
|
1ab543cea1 | ||
|
|
a3f86903e4 | ||
|
|
c239c305ab | ||
|
|
2e02af994e | ||
|
|
836d9afe17 | ||
|
|
007a352742 | ||
|
|
e526e5659e | ||
|
|
4a5227c7bf | ||
|
|
c2c151ec4c | ||
|
|
452096e7e4 | ||
|
|
50c2a9859e | ||
|
|
677b667307 | ||
|
|
1adf331268 | ||
|
|
349b3e961b | ||
|
|
96650c06f0 | ||
|
|
26038a0a07 | ||
|
|
6a148b5dd9 | ||
|
|
0e109ef979 |
@@ -2,14 +2,14 @@ apply plugin: 'com.android.application'
|
||||
|
||||
android {
|
||||
compileSdkVersion 27
|
||||
buildToolsVersion "27.0.2"
|
||||
buildToolsVersion "27.0.3"
|
||||
|
||||
defaultConfig {
|
||||
applicationId "com.topjohnwu.magisk"
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 27
|
||||
versionCode 82
|
||||
versionName "5.5.1"
|
||||
versionCode 90
|
||||
versionName "5.5.4"
|
||||
ndk {
|
||||
moduleName 'zipadjust'
|
||||
abiFilters 'x86', 'x86_64', 'armeabi-v7a', 'arm64-v8a'
|
||||
|
||||
3
proguard-rules.pro
vendored
3
proguard-rules.pro
vendored
@@ -22,3 +22,6 @@
|
||||
# BouncyCastle
|
||||
-keep class org.bouncycastle.jcajce.provider.** { *; }
|
||||
-dontwarn javax.naming.**
|
||||
|
||||
# Gson
|
||||
-keepattributes Signature
|
||||
@@ -9,6 +9,7 @@
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||
<uses-permission android:name="android.permission.VIBRATE" />
|
||||
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
|
||||
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
|
||||
|
||||
<application
|
||||
android:name=".MagiskManager"
|
||||
|
||||
@@ -1,12 +1,3 @@
|
||||
### v5.5.1
|
||||
- Fix an issue in setting up superuser database
|
||||
|
||||
### v5.5.0
|
||||
- Fix dynamic resource loading, prevent crashes when checking SafetyNet
|
||||
- Update SignAPK to use very little RAM for supporting old devices
|
||||
- Support settings migration after hiding Magisk Manager
|
||||
- Add reboot menu in modules section
|
||||
- Add dark theme to superuser request dialogs
|
||||
- Properly handle new `HIGHCOMP` and add recommended `KEEPVERITY` and `KEEPFORCEENCRYPT` flags for installation
|
||||
- Support new paths for v14.6
|
||||
- Massive improvements in repackaging Magisk Manager
|
||||
### v5.5.4
|
||||
- Fix on-boot dtbo detection
|
||||
- Add fingerprint authentication for Superuser requests
|
||||
|
||||
@@ -91,7 +91,7 @@ public class MagiskFragment extends Fragment
|
||||
new CheckSafetyNet(getActivity()).exec();
|
||||
collapse();
|
||||
};
|
||||
if (mm.snetVersion < 0) {
|
||||
if (!CheckSafetyNet.dexPath.exists()) {
|
||||
// Show dialog
|
||||
new AlertDialogBuilder(getActivity())
|
||||
.setTitle(R.string.proprietary_title)
|
||||
@@ -138,13 +138,9 @@ public class MagiskFragment extends Fragment
|
||||
setupExpandable();
|
||||
|
||||
keepVerityChkbox.setChecked(mm.keepVerity);
|
||||
keepVerityChkbox.setOnCheckedChangeListener((view, isChecked) -> {
|
||||
mm.keepVerity = isChecked;
|
||||
});
|
||||
keepVerityChkbox.setOnCheckedChangeListener((view, checked) -> mm.keepVerity = checked);
|
||||
keepEncChkbox.setChecked(mm.keepEnc);
|
||||
keepEncChkbox.setOnCheckedChangeListener((view, isChecked) -> {
|
||||
mm.keepEnc = isChecked;
|
||||
});
|
||||
keepEncChkbox.setOnCheckedChangeListener((view, checked) -> mm.keepEnc = checked);
|
||||
|
||||
mSwipeRefreshLayout.setOnRefreshListener(this);
|
||||
updateUI();
|
||||
|
||||
@@ -49,7 +49,6 @@ public class MagiskManager extends Application {
|
||||
public int remoteManagerVersionCode = -1;
|
||||
public String managerLink;
|
||||
public String bootBlock = null;
|
||||
public int snetVersion;
|
||||
public boolean keepVerity = false;
|
||||
public boolean keepEnc = false;
|
||||
|
||||
@@ -63,8 +62,6 @@ public class MagiskManager extends Application {
|
||||
|
||||
public boolean magiskHide;
|
||||
public boolean isDarkTheme;
|
||||
public boolean updateNotification;
|
||||
public boolean suReauth;
|
||||
public int suRequestTimeout;
|
||||
public int suLogTimeout = 14;
|
||||
public int suAccessState;
|
||||
@@ -75,7 +72,7 @@ public class MagiskManager extends Application {
|
||||
public String localeConfig;
|
||||
public int updateChannel;
|
||||
public String bootFormat;
|
||||
public String customChannelUrl;
|
||||
public int repoOrder;
|
||||
|
||||
// Global resources
|
||||
public SharedPreferences prefs;
|
||||
@@ -106,7 +103,7 @@ public class MagiskManager extends Application {
|
||||
} catch (PackageManager.NameNotFoundException ignored) { /* Expected */ }
|
||||
}
|
||||
|
||||
suDB = new SuDatabaseHelper(false);
|
||||
suDB = SuDatabaseHelper.getSuDB(false);
|
||||
repoDB = new RepoDatabaseHelper(this);
|
||||
defaultLocale = Locale.getDefault();
|
||||
setLocale();
|
||||
@@ -135,21 +132,38 @@ public class MagiskManager extends Application {
|
||||
suRequestTimeout = Utils.getPrefsInt(prefs, Const.Key.SU_REQUEST_TIMEOUT, Const.Value.timeoutList[2]);
|
||||
suResponseType = Utils.getPrefsInt(prefs, Const.Key.SU_AUTO_RESPONSE, Const.Value.SU_PROMPT);
|
||||
suNotificationType = Utils.getPrefsInt(prefs, Const.Key.SU_NOTIFICATION, Const.Value.NOTIFICATION_TOAST);
|
||||
suReauth = prefs.getBoolean(Const.Key.SU_REAUTH, false);
|
||||
suAccessState = suDB.getSettings(Const.Key.ROOT_ACCESS, Const.Value.ROOT_ACCESS_APPS_AND_ADB);
|
||||
multiuserMode = suDB.getSettings(Const.Key.SU_MULTIUSER_MODE, Const.Value.MULTIUSER_MODE_OWNER_ONLY);
|
||||
suNamespaceMode = suDB.getSettings(Const.Key.SU_MNT_NS, Const.Value.NAMESPACE_MODE_REQUESTER);
|
||||
|
||||
// config
|
||||
isDarkTheme = prefs.getBoolean(Const.Key.DARK_THEME, false);
|
||||
updateNotification = prefs.getBoolean(Const.Key.UPDATE_NOTIFICATION, true);
|
||||
updateChannel = Utils.getPrefsInt(prefs, Const.Key.UPDATE_CHANNEL, Const.Value.STABLE_CHANNEL);
|
||||
bootFormat = prefs.getString(Const.Key.BOOT_FORMAT, ".img");
|
||||
snetVersion = prefs.getInt(Const.Key.SNET_VER, -1);
|
||||
customChannelUrl = prefs.getString(Const.Key.CUSTOM_CHANNEL, "");
|
||||
repoOrder = prefs.getInt(Const.Key.REPO_ORDER, Const.Value.ORDER_NAME);
|
||||
}
|
||||
|
||||
public static void toast(String msg, int duration) {
|
||||
public void writeConfig() {
|
||||
prefs.edit()
|
||||
.putBoolean(Const.Key.DARK_THEME, isDarkTheme)
|
||||
.putBoolean(Const.Key.MAGISKHIDE, magiskHide)
|
||||
.putBoolean(Const.Key.HOSTS, Utils.itemExist(Const.MAGISK_HOST_FILE()))
|
||||
.putBoolean(Const.Key.COREONLY, Utils.itemExist(Const.MAGISK_DISABLE_FILE))
|
||||
.putString(Const.Key.SU_REQUEST_TIMEOUT, String.valueOf(suRequestTimeout))
|
||||
.putString(Const.Key.SU_AUTO_RESPONSE, String.valueOf(suResponseType))
|
||||
.putString(Const.Key.SU_NOTIFICATION, String.valueOf(suNotificationType))
|
||||
.putString(Const.Key.ROOT_ACCESS, String.valueOf(suAccessState))
|
||||
.putString(Const.Key.SU_MULTIUSER_MODE, String.valueOf(multiuserMode))
|
||||
.putString(Const.Key.SU_MNT_NS, String.valueOf(suNamespaceMode))
|
||||
.putString(Const.Key.UPDATE_CHANNEL, String.valueOf(updateChannel))
|
||||
.putString(Const.Key.LOCALE, localeConfig)
|
||||
.putString(Const.Key.BOOT_FORMAT, bootFormat)
|
||||
.putInt(Const.Key.UPDATE_SERVICE_VER, Const.UPDATE_SERVICE_VER)
|
||||
.putInt(Const.Key.REPO_ORDER, repoOrder)
|
||||
.apply();
|
||||
}
|
||||
|
||||
public static void toast(CharSequence msg, int duration) {
|
||||
mHandler.post(() -> Toast.makeText(weakSelf.get(), msg, duration).show());
|
||||
}
|
||||
|
||||
@@ -190,6 +204,14 @@ public class MagiskManager extends Application {
|
||||
if (Utils.isValidShellResponse(ret))
|
||||
bootBlock = ret.get(0);
|
||||
|
||||
if (suDB != null && !SuDatabaseHelper.verified) {
|
||||
suDB.close();
|
||||
suDB = SuDatabaseHelper.getSuDB(true);
|
||||
}
|
||||
}
|
||||
|
||||
public void getDefaultInstallFlags() {
|
||||
List<String> ret;
|
||||
ret = Shell.su("echo \"$DTBOIMAGE\"");
|
||||
if (Utils.isValidShellResponse(ret))
|
||||
keepVerity = true;
|
||||
@@ -213,11 +235,6 @@ public class MagiskManager extends Application {
|
||||
if (Utils.isValidShellResponse(ret))
|
||||
keepEnc = Boolean.parseBoolean(ret.get(0));
|
||||
} catch (NumberFormatException ignored) {}
|
||||
|
||||
if (suDB != null && !SuDatabaseHelper.verified) {
|
||||
suDB.close();
|
||||
suDB = new SuDatabaseHelper();
|
||||
}
|
||||
}
|
||||
|
||||
public void setPermissionGrantCallback(Runnable callback) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.topjohnwu.magisk;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.widget.SwipeRefreshLayout;
|
||||
@@ -7,6 +8,7 @@ import android.support.v7.widget.RecyclerView;
|
||||
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;
|
||||
@@ -15,6 +17,7 @@ import android.widget.TextView;
|
||||
import com.topjohnwu.magisk.adapters.ReposAdapter;
|
||||
import com.topjohnwu.magisk.asyncs.UpdateRepos;
|
||||
import com.topjohnwu.magisk.components.Fragment;
|
||||
import com.topjohnwu.magisk.utils.Const;
|
||||
import com.topjohnwu.magisk.utils.Topic;
|
||||
|
||||
import butterknife.BindView;
|
||||
@@ -98,6 +101,22 @@ public class ReposFragment extends Fragment implements Topic.Subscriber {
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
MagiskManager mm = getApplication();
|
||||
if (item.getItemId() == R.id.repo_sort) {
|
||||
new AlertDialog.Builder(getActivity())
|
||||
.setTitle(R.string.sorting_order)
|
||||
.setSingleChoiceItems(R.array.sorting_orders, mm.repoOrder, (d, which) -> {
|
||||
mm.repoOrder = which;
|
||||
mm.prefs.edit().putInt(Const.Key.REPO_ORDER, mm.repoOrder).apply();
|
||||
adapter.notifyDBChanged();
|
||||
d.dismiss();
|
||||
}).show();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
|
||||
@@ -22,6 +22,7 @@ import com.topjohnwu.magisk.asyncs.CheckUpdates;
|
||||
import com.topjohnwu.magisk.asyncs.HideManager;
|
||||
import com.topjohnwu.magisk.components.Activity;
|
||||
import com.topjohnwu.magisk.utils.Const;
|
||||
import com.topjohnwu.magisk.utils.FingerprintHelper;
|
||||
import com.topjohnwu.magisk.utils.Shell;
|
||||
import com.topjohnwu.magisk.utils.Topic;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
@@ -112,13 +113,14 @@ public class SettingsActivity extends Activity implements Topic.Subscriber {
|
||||
multiuserMode = (ListPreference) findPreference(Const.Key.SU_MULTIUSER_MODE);
|
||||
namespaceMode = (ListPreference) findPreference(Const.Key.SU_MNT_NS);
|
||||
SwitchPreference reauth = (SwitchPreference) findPreference(Const.Key.SU_REAUTH);
|
||||
SwitchPreference fingerprint = (SwitchPreference) findPreference(Const.Key.SU_FINGERPRINT);
|
||||
|
||||
updateChannel.setOnPreferenceChangeListener((pref, o) -> {
|
||||
mm.updateChannel = Integer.parseInt((String) o);
|
||||
if (mm.updateChannel == Const.Value.CUSTOM_CHANNEL) {
|
||||
View v = LayoutInflater.from(getActivity()).inflate(R.layout.custom_channel_dialog, null);
|
||||
EditText url = v.findViewById(R.id.custom_url);
|
||||
url.setText(mm.customChannelUrl);
|
||||
url.setText(mm.prefs.getString(Const.Key.CUSTOM_CHANNEL, ""));
|
||||
new AlertDialog.Builder(getActivity())
|
||||
.setTitle(R.string.settings_update_custom)
|
||||
.setView(v)
|
||||
@@ -144,6 +146,11 @@ public class SettingsActivity extends Activity implements Topic.Subscriber {
|
||||
reauth.setSummary(R.string.android_o_not_support);
|
||||
}
|
||||
|
||||
// Remove fingerprint option if not possible
|
||||
if (!FingerprintHelper.canUseFingerprint()) {
|
||||
suCategory.removePreference(fingerprint);
|
||||
}
|
||||
|
||||
if (mm.getPackageName().equals(Const.ORIG_PKG_NAME) && mm.magiskVersionCode >= 1440) {
|
||||
hideManager.setOnPreferenceClickListener((pref) -> {
|
||||
Utils.runWithPermission(getActivity(),
|
||||
|
||||
@@ -34,6 +34,7 @@ public class SplashActivity extends Activity {
|
||||
MagiskManager mm = getMagiskManager();
|
||||
|
||||
mm.loadMagiskInfo();
|
||||
mm.getDefaultInstallFlags();
|
||||
Utils.loadPrefs();
|
||||
|
||||
// Dynamic detect all locales
|
||||
@@ -61,7 +62,7 @@ public class SplashActivity extends Activity {
|
||||
if (Shell.rootAccess() && mm.magiskVersionCode > 0) {
|
||||
|
||||
// Add update checking service
|
||||
if (Const.Value.UPDATE_SERVICE_VER > mm.prefs.getInt(Const.Key.UPDATE_SERVICE_VER, -1)) {
|
||||
if (Const.UPDATE_SERVICE_VER > mm.prefs.getInt(Const.Key.UPDATE_SERVICE_VER, -1)) {
|
||||
ComponentName service = new ComponentName(this, UpdateCheckService.class);
|
||||
JobInfo info = new JobInfo.Builder(Const.ID.UPDATE_SERVICE_ID, service)
|
||||
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
|
||||
@@ -79,24 +80,7 @@ public class SplashActivity extends Activity {
|
||||
}
|
||||
|
||||
// Write back default values
|
||||
mm.prefs.edit()
|
||||
.putBoolean(Const.Key.DARK_THEME, mm.isDarkTheme)
|
||||
.putBoolean(Const.Key.MAGISKHIDE, mm.magiskHide)
|
||||
.putBoolean(Const.Key.UPDATE_NOTIFICATION, mm.updateNotification)
|
||||
.putBoolean(Const.Key.HOSTS, Utils.itemExist(Const.MAGISK_HOST_FILE()))
|
||||
.putBoolean(Const.Key.COREONLY, Utils.itemExist(Const.MAGISK_DISABLE_FILE))
|
||||
.putBoolean(Const.Key.SU_REAUTH, mm.suReauth)
|
||||
.putString(Const.Key.SU_REQUEST_TIMEOUT, String.valueOf(mm.suRequestTimeout))
|
||||
.putString(Const.Key.SU_AUTO_RESPONSE, String.valueOf(mm.suResponseType))
|
||||
.putString(Const.Key.SU_NOTIFICATION, String.valueOf(mm.suNotificationType))
|
||||
.putString(Const.Key.ROOT_ACCESS, String.valueOf(mm.suAccessState))
|
||||
.putString(Const.Key.SU_MULTIUSER_MODE, String.valueOf(mm.multiuserMode))
|
||||
.putString(Const.Key.SU_MNT_NS, String.valueOf(mm.suNamespaceMode))
|
||||
.putString(Const.Key.UPDATE_CHANNEL, String.valueOf(mm.updateChannel))
|
||||
.putString(Const.Key.LOCALE, mm.localeConfig)
|
||||
.putString(Const.Key.BOOT_FORMAT, mm.bootFormat)
|
||||
.putInt(Const.Key.UPDATE_SERVICE_VER, Const.Value.UPDATE_SERVICE_VER)
|
||||
.apply();
|
||||
mm.writeConfig();
|
||||
|
||||
mm.hasInit = true;
|
||||
|
||||
|
||||
@@ -108,7 +108,7 @@ public class ApplicationAdapter extends RecyclerView.Adapter<ApplicationAdapter.
|
||||
|
||||
@BindView(R.id.app_icon) ImageView appIcon;
|
||||
@BindView(R.id.app_name) TextView appName;
|
||||
@BindView(R.id.app_package) TextView appPackage;
|
||||
@BindView(R.id.package_name) TextView appPackage;
|
||||
@BindView(R.id.checkbox) CheckBox checkBox;
|
||||
|
||||
ViewHolder(View itemView) {
|
||||
|
||||
@@ -95,6 +95,7 @@ public class ReposAdapter extends SectionedAdapter<ReposAdapter.SectionHolder, R
|
||||
String author = repo.getAuthor();
|
||||
holder.author.setText(TextUtils.isEmpty(author) ? null : context.getString(R.string.author, author));
|
||||
holder.description.setText(repo.getDescription());
|
||||
holder.updateTime.setText(context.getString(R.string.updated_on, repo.getLastUpdateString()));
|
||||
|
||||
holder.infoLayout.setOnClickListener(v ->
|
||||
new MarkDownWindow((Activity) context, null, repo.getDetailUrl()).exec());
|
||||
@@ -180,6 +181,7 @@ public class ReposAdapter extends SectionedAdapter<ReposAdapter.SectionHolder, R
|
||||
@BindView(R.id.author) TextView author;
|
||||
@BindView(R.id.info_layout) LinearLayout infoLayout;
|
||||
@BindView(R.id.download) ImageView downloadImage;
|
||||
@BindView(R.id.update_time) TextView updateTime;
|
||||
|
||||
RepoHolder(View itemView) {
|
||||
super(itemView);
|
||||
|
||||
@@ -22,13 +22,13 @@ import dalvik.system.DexClassLoader;
|
||||
|
||||
public class CheckSafetyNet extends ParallelTask<Void, Void, Exception> {
|
||||
|
||||
private File dexPath;
|
||||
public static final File dexPath =
|
||||
new File(MagiskManager.get().getFilesDir().getParent() + "/snet", "snet.apk");
|
||||
private DexClassLoader loader;
|
||||
private Class<?> helperClazz, callbackClazz;
|
||||
|
||||
public CheckSafetyNet(Activity activity) {
|
||||
super(activity);
|
||||
dexPath = new File(activity.getCacheDir().getParent() + "/snet", "snet.apk");
|
||||
}
|
||||
|
||||
private void dlSnet() throws IOException {
|
||||
@@ -65,7 +65,7 @@ public class CheckSafetyNet extends ParallelTask<Void, Void, Exception> {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
if (snet_ver != Const.Value.SNET_VER) {
|
||||
if (snet_ver != Const.SNET_VER) {
|
||||
dlSnet();
|
||||
loadClasses();
|
||||
}
|
||||
@@ -79,8 +79,6 @@ public class CheckSafetyNet extends ParallelTask<Void, Void, Exception> {
|
||||
@Override
|
||||
protected void onPostExecute(Exception err) {
|
||||
MagiskManager mm = MagiskManager.get();
|
||||
mm.snetVersion = Const.Value.SNET_VER;
|
||||
mm.prefs.edit().putInt(Const.Key.SNET_VER, Const.Value.SNET_VER).apply();
|
||||
try {
|
||||
if (err != null) throw err;
|
||||
Object helper = helperClazz.getConstructors()[0].newInstance(
|
||||
|
||||
@@ -33,7 +33,7 @@ public class CheckUpdates extends ParallelTask<Void, Void, Void> {
|
||||
jsonStr = WebService.getString(Const.Url.BETA_URL);
|
||||
break;
|
||||
case Const.Value.CUSTOM_CHANNEL:
|
||||
jsonStr = WebService.getString(mm.customChannelUrl);
|
||||
jsonStr = WebService.getString(mm.prefs.getString(Const.Key.CUSTOM_CHANNEL, ""));
|
||||
break;
|
||||
}
|
||||
try {
|
||||
@@ -54,7 +54,7 @@ public class CheckUpdates extends ParallelTask<Void, Void, Void> {
|
||||
@Override
|
||||
protected void onPostExecute(Void v) {
|
||||
MagiskManager mm = MagiskManager.get();
|
||||
if (showNotification && mm.updateNotification) {
|
||||
if (showNotification && mm.prefs.getBoolean(Const.Key.UPDATE_NOTIFICATION, true)) {
|
||||
if (BuildConfig.VERSION_CODE < mm.remoteManagerVersionCode) {
|
||||
ShowUI.managerUpdateNotification();
|
||||
} else if (mm.magiskVersionCode < mm.remoteMagiskVersionCode) {
|
||||
|
||||
@@ -3,9 +3,11 @@ package com.topjohnwu.magisk.container;
|
||||
import android.content.ContentValues;
|
||||
import android.database.Cursor;
|
||||
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.magisk.utils.Const;
|
||||
import com.topjohnwu.magisk.utils.WebService;
|
||||
|
||||
import java.text.DateFormat;
|
||||
import java.util.Date;
|
||||
|
||||
public class Repo extends BaseModule {
|
||||
@@ -40,7 +42,7 @@ public class Repo extends BaseModule {
|
||||
if (getVersionCode() < 0) {
|
||||
throw new IllegalRepoException("Repo [" + repoName + "] does not contain versionCode");
|
||||
}
|
||||
if (getMinMagiskVersion() < Const.Value.MIN_MODULE_VER) {
|
||||
if (getMinMagiskVersion() < Const.MIN_MODULE_VER) {
|
||||
throw new IllegalRepoException("Repo [" + repoName + "] is outdated");
|
||||
}
|
||||
}
|
||||
@@ -78,6 +80,11 @@ public class Repo extends BaseModule {
|
||||
return String.format(Const.Url.FILE_URL, repoName, "README.md");
|
||||
}
|
||||
|
||||
public String getLastUpdateString() {
|
||||
return DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.MEDIUM,
|
||||
MagiskManager.locale).format(mLastUpdate);
|
||||
}
|
||||
|
||||
public Date getLastUpdate() {
|
||||
return mLastUpdate;
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ public class RepoDatabaseHelper extends SQLiteOpenHelper {
|
||||
|
||||
// Clear bad repos
|
||||
mDb.delete(TABLE_NAME, "minMagisk<?",
|
||||
new String[] { String.valueOf(Const.Value.MIN_MODULE_VER) });
|
||||
new String[] { String.valueOf(Const.MIN_MODULE_VER) });
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -95,9 +95,17 @@ public class RepoDatabaseHelper extends SQLiteOpenHelper {
|
||||
}
|
||||
|
||||
public Cursor getRepoCursor() {
|
||||
String orderBy = null;
|
||||
switch (mm.repoOrder) {
|
||||
case Const.Value.ORDER_NAME:
|
||||
orderBy = "name COLLATE NOCASE";
|
||||
break;
|
||||
case Const.Value.ORDER_DATE:
|
||||
orderBy = "last_update DESC";
|
||||
}
|
||||
return mDb.query(TABLE_NAME, null, "minMagisk<=?",
|
||||
new String[] { String.valueOf(mm.magiskVersionCode) },
|
||||
null, null, "name COLLATE NOCASE");
|
||||
null, null, orderBy);
|
||||
}
|
||||
|
||||
public List<String> getRepoIDList() {
|
||||
|
||||
@@ -62,7 +62,7 @@ public class SuDatabaseHelper extends SQLiteOpenHelper {
|
||||
|
||||
File db = Utils.getDB(context, DB_NAME);
|
||||
if (!verify) {
|
||||
if (db.length() == 0) {
|
||||
if (db.exists() && db.length() == 0) {
|
||||
ce.loadMagiskInfo();
|
||||
// Continue verification
|
||||
} else {
|
||||
@@ -108,7 +108,7 @@ public class SuDatabaseHelper extends SQLiteOpenHelper {
|
||||
// New global su db
|
||||
Shell.su(Utils.fmt("mkdir %s 2>/dev/null; chmod 700 %s", GLOBAL_DB.getParent(), GLOBAL_DB.getParent()));
|
||||
if (!Utils.itemExist(GLOBAL_DB)) {
|
||||
Utils.javaCreateFile(db);
|
||||
context.openOrCreateDatabase(DB_NAME, 0, null).close();
|
||||
Shell.su(Utils.fmt("cp -af %s %s; rm -f %s*", db, GLOBAL_DB, db));
|
||||
}
|
||||
verified = TextUtils.equals(Utils.checkInode(GLOBAL_DB), Utils.checkInode(db));
|
||||
@@ -125,12 +125,17 @@ public class SuDatabaseHelper extends SQLiteOpenHelper {
|
||||
return context;
|
||||
}
|
||||
|
||||
public SuDatabaseHelper() {
|
||||
this(true);
|
||||
}
|
||||
|
||||
public SuDatabaseHelper(boolean verify) {
|
||||
this(initDB(verify));
|
||||
public static SuDatabaseHelper getSuDB(boolean verify) {
|
||||
try {
|
||||
return new SuDatabaseHelper(initDB(verify));
|
||||
} catch(Exception e) {
|
||||
// Try to catch runtime exceptions and remove all db for retry
|
||||
unmntDB();
|
||||
Shell.su(Utils.fmt("rm -rf /data/user*/*/magisk.db /data/adb/magisk.db /data/user*/*/%s/databases",
|
||||
MagiskManager.get().getPackageName()));
|
||||
e.printStackTrace();
|
||||
return new SuDatabaseHelper(initDB(false));
|
||||
}
|
||||
}
|
||||
|
||||
private SuDatabaseHelper(Context context) {
|
||||
|
||||
@@ -5,6 +5,7 @@ import android.content.Context;
|
||||
import android.content.Intent;
|
||||
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.magisk.utils.Const;
|
||||
import com.topjohnwu.magisk.utils.Shell;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
|
||||
@@ -18,7 +19,7 @@ public class PackageReceiver extends BroadcastReceiver {
|
||||
switch (intent.getAction()) {
|
||||
case Intent.ACTION_PACKAGE_REPLACED:
|
||||
// This will only work pre-O
|
||||
if (mm.suReauth) {
|
||||
if (mm.prefs.getBoolean(Const.Key.SU_REAUTH, false)) {
|
||||
mm.suDB.deletePolicy(pkg);
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -41,6 +41,7 @@ public class OnBootIntentService extends IntentService {
|
||||
* */
|
||||
MagiskManager mm = Utils.getMagiskManager(this);
|
||||
mm.loadMagiskInfo();
|
||||
mm.getDefaultInstallFlags();
|
||||
if (Shell.rootAccess()) {
|
||||
Utils.patchDTBO();
|
||||
}
|
||||
|
||||
@@ -3,12 +3,14 @@ package com.topjohnwu.magisk.superuser;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.hardware.fingerprint.FingerprintManager;
|
||||
import android.net.LocalSocket;
|
||||
import android.net.LocalSocketAddress;
|
||||
import android.os.Bundle;
|
||||
import android.os.CountDownTimer;
|
||||
import android.os.FileObserver;
|
||||
import android.text.TextUtils;
|
||||
import android.view.View;
|
||||
import android.view.Window;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.Button;
|
||||
@@ -23,6 +25,7 @@ import com.topjohnwu.magisk.asyncs.ParallelTask;
|
||||
import com.topjohnwu.magisk.components.Activity;
|
||||
import com.topjohnwu.magisk.container.Policy;
|
||||
import com.topjohnwu.magisk.utils.Const;
|
||||
import com.topjohnwu.magisk.utils.FingerprintHelper;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
@@ -40,6 +43,8 @@ public class RequestActivity extends Activity {
|
||||
@BindView(R.id.package_name) TextView packageNameView;
|
||||
@BindView(R.id.grant_btn) Button grant_btn;
|
||||
@BindView(R.id.deny_btn) Button deny_btn;
|
||||
@BindView(R.id.fingerprint) ImageView fingerprintImg;
|
||||
@BindView(R.id.warning) TextView warning;
|
||||
|
||||
private String socketPath;
|
||||
private LocalSocket socket;
|
||||
@@ -49,6 +54,7 @@ public class RequestActivity extends Activity {
|
||||
private boolean hasTimeout;
|
||||
private Policy policy;
|
||||
private CountDownTimer timer;
|
||||
private FingerprintHelper fingerprintHelper;
|
||||
|
||||
@Override
|
||||
public int getDarkTheme() {
|
||||
@@ -80,6 +86,15 @@ public class RequestActivity extends Activity {
|
||||
new SocketManager(this).exec();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void finish() {
|
||||
if (timer != null)
|
||||
timer.cancel();
|
||||
if (fingerprintHelper != null)
|
||||
fingerprintHelper.cancel();
|
||||
super.finish();
|
||||
}
|
||||
|
||||
private boolean cancelTimeout() {
|
||||
timer.cancel();
|
||||
deny_btn.setText(getString(R.string.deny));
|
||||
@@ -128,11 +143,49 @@ public class RequestActivity extends Activity {
|
||||
}
|
||||
};
|
||||
|
||||
grant_btn.setOnClickListener(v -> {
|
||||
handleAction(Policy.ALLOW);
|
||||
timer.cancel();
|
||||
});
|
||||
grant_btn.requestFocus();
|
||||
boolean useFingerprint = mm.prefs.getBoolean(Const.Key.SU_FINGERPRINT, false) && FingerprintHelper.canUseFingerprint();
|
||||
|
||||
if (useFingerprint) {
|
||||
try {
|
||||
fingerprintHelper = new FingerprintHelper() {
|
||||
@Override
|
||||
public void onAuthenticationError(int errorCode, CharSequence errString) {
|
||||
warning.setText(errString);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAuthenticationHelp(int helpCode, CharSequence helpString) {
|
||||
warning.setText(helpString);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) {
|
||||
handleAction(Policy.ALLOW);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAuthenticationFailed() {
|
||||
warning.setText(R.string.auth_fail);
|
||||
}
|
||||
};
|
||||
fingerprintHelper.startAuth();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
useFingerprint = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!useFingerprint) {
|
||||
grant_btn.setOnClickListener(v -> {
|
||||
handleAction(Policy.ALLOW);
|
||||
timer.cancel();
|
||||
});
|
||||
grant_btn.requestFocus();
|
||||
}
|
||||
|
||||
grant_btn.setVisibility(useFingerprint ? View.GONE : View.VISIBLE);
|
||||
fingerprintImg.setVisibility(useFingerprint ? View.VISIBLE : View.GONE);
|
||||
|
||||
deny_btn.setOnClickListener(v -> {
|
||||
handleAction(Policy.DENY);
|
||||
timer.cancel();
|
||||
@@ -151,8 +204,9 @@ public class RequestActivity extends Activity {
|
||||
public void onBackPressed() {
|
||||
if (policy != null) {
|
||||
handleAction(Policy.DENY);
|
||||
} else {
|
||||
finish();
|
||||
}
|
||||
finish();
|
||||
}
|
||||
|
||||
void handleAction() {
|
||||
|
||||
@@ -23,6 +23,8 @@ public class Const {
|
||||
public static final String UTIL_FUNCTIONS= "util_functions.sh";
|
||||
public static final String ANDROID_MANIFEST = "AndroidManifest.xml";
|
||||
|
||||
public static final String SU_KEYSTORE_KEY = "su_key";
|
||||
|
||||
// Paths
|
||||
public static final String MAGISK_DISABLE_FILE = "/cache/.disable_magisk";
|
||||
public static final String TMP_FOLDER_PATH = "/dev/tmp";
|
||||
@@ -30,6 +32,11 @@ public class Const {
|
||||
public static final File EXTERNAL_PATH = new File(Environment.getExternalStorageDirectory(), "MagiskManager");
|
||||
public static final String MANAGER_CONFIGS = ".tmp.magisk.config";
|
||||
|
||||
// Versions
|
||||
public static final int UPDATE_SERVICE_VER = 1;
|
||||
public static final int SNET_VER = 7;
|
||||
public static final int MIN_MODULE_VER = 1400;
|
||||
|
||||
public static String BUSYBOX_PATH() {
|
||||
if (Utils.itemExist("/sbin/.core/busybox/busybox")) {
|
||||
return "/sbin/.core/busybox";
|
||||
@@ -104,6 +111,7 @@ public class Const {
|
||||
public static final String SU_AUTO_RESPONSE = "su_auto_response";
|
||||
public static final String SU_NOTIFICATION = "su_notification";
|
||||
public static final String SU_REAUTH = "su_reauth";
|
||||
public static final String SU_FINGERPRINT = "su_fingerprint";
|
||||
|
||||
// intents
|
||||
public static final String OPEN_SECTION = "section";
|
||||
@@ -118,7 +126,6 @@ public class Const {
|
||||
public static final String UPDATE_CHANNEL = "update_channel";
|
||||
public static final String CUSTOM_CHANNEL = "custom_channel";
|
||||
public static final String BOOT_FORMAT = "boot_format";
|
||||
public static final String SNET_VER = "snet_version";
|
||||
public static final String UPDATE_SERVICE_VER = "update_service_version";
|
||||
public static final String APP_VER = "app_version";
|
||||
public static final String MAGISKHIDE = "magiskhide";
|
||||
@@ -129,6 +136,7 @@ public class Const {
|
||||
public static final String ETAG_KEY = "ETag";
|
||||
public static final String LINK_KEY = "Link";
|
||||
public static final String IF_NONE_MATCH = "If-None-Match";
|
||||
public static final String REPO_ORDER = "repo_order";
|
||||
}
|
||||
|
||||
|
||||
@@ -158,8 +166,7 @@ public class Const {
|
||||
public static final String PATCH_BOOT = "patch";
|
||||
public static final String FLASH_MAGISK = "magisk";
|
||||
public static final int[] timeoutList = {0, -1, 10, 20, 30, 60};
|
||||
public static final int UPDATE_SERVICE_VER = 1;
|
||||
public static final int SNET_VER = 7;
|
||||
public static final int MIN_MODULE_VER = 1400;
|
||||
public static final int ORDER_NAME = 0;
|
||||
public static final int ORDER_DATE = 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
package com.topjohnwu.magisk.utils;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.KeyguardManager;
|
||||
import android.hardware.fingerprint.FingerprintManager;
|
||||
import android.os.Build;
|
||||
import android.os.CancellationSignal;
|
||||
import android.security.keystore.KeyGenParameterSpec;
|
||||
import android.security.keystore.KeyPermanentlyInvalidatedException;
|
||||
import android.security.keystore.KeyProperties;
|
||||
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
|
||||
import java.security.KeyStore;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.KeyGenerator;
|
||||
import javax.crypto.SecretKey;
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.M)
|
||||
public abstract class FingerprintHelper extends FingerprintManager.AuthenticationCallback {
|
||||
|
||||
private FingerprintManager manager;
|
||||
private Cipher cipher;
|
||||
private CancellationSignal cancel;
|
||||
|
||||
public static boolean canUseFingerprint() {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M)
|
||||
return false;
|
||||
MagiskManager mm = MagiskManager.get();
|
||||
KeyguardManager km = mm.getSystemService(KeyguardManager.class);
|
||||
FingerprintManager fm = mm.getSystemService(FingerprintManager.class);
|
||||
return km.isKeyguardSecure() && fm.isHardwareDetected() && fm.hasEnrolledFingerprints();
|
||||
}
|
||||
|
||||
protected FingerprintHelper() throws Exception {
|
||||
MagiskManager mm = MagiskManager.get();
|
||||
KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
|
||||
manager = mm.getSystemService(FingerprintManager.class);
|
||||
cipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/"
|
||||
+ KeyProperties.BLOCK_MODE_CBC + "/"
|
||||
+ KeyProperties.ENCRYPTION_PADDING_PKCS7);
|
||||
keyStore.load(null);
|
||||
SecretKey key = (SecretKey) keyStore.getKey(Const.SU_KEYSTORE_KEY, null);
|
||||
if (key == null) {
|
||||
key = generateKey();
|
||||
}
|
||||
try {
|
||||
cipher.init(Cipher.ENCRYPT_MODE, key);
|
||||
} catch (KeyPermanentlyInvalidatedException e) {
|
||||
// Only happens on Marshmallow
|
||||
key = generateKey();
|
||||
cipher.init(Cipher.ENCRYPT_MODE, key);
|
||||
}
|
||||
}
|
||||
|
||||
public void startAuth() {
|
||||
cancel = new CancellationSignal();
|
||||
FingerprintManager.CryptoObject cryptoObject = new FingerprintManager.CryptoObject(cipher);
|
||||
manager.authenticate(cryptoObject, cancel, 0, this, null);
|
||||
}
|
||||
|
||||
public void cancel() {
|
||||
if (cancel != null)
|
||||
cancel.cancel();
|
||||
}
|
||||
|
||||
private SecretKey generateKey() throws Exception {
|
||||
KeyGenerator keygen = KeyGenerator
|
||||
.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore");
|
||||
KeyGenParameterSpec.Builder builder = new KeyGenParameterSpec.Builder(
|
||||
Const.SU_KEYSTORE_KEY,
|
||||
KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
|
||||
.setBlockModes(KeyProperties.BLOCK_MODE_CBC)
|
||||
.setUserAuthenticationRequired(true)
|
||||
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
builder.setInvalidatedByBiometricEnrollment(false);
|
||||
}
|
||||
keygen.init(builder.build());
|
||||
return keygen.generateKey();
|
||||
}
|
||||
}
|
||||
@@ -89,13 +89,10 @@ public class Shell {
|
||||
}
|
||||
|
||||
// Root shell initialization
|
||||
String bbpath = Const.BUSYBOX_PATH();
|
||||
mm.shell.run_raw(false, false,
|
||||
"export PATH=" + bbpath + ":$PATH",
|
||||
"export PATH=" + Const.BUSYBOX_PATH() + ":$PATH",
|
||||
"mount_partitions",
|
||||
"find_boot_image",
|
||||
"find_dtbo_image",
|
||||
"migrate_boot_backup");
|
||||
"run_migrations");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,8 +11,8 @@ import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.InputStream;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarInputStream;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipInputStream;
|
||||
|
||||
public class ZipUtils {
|
||||
|
||||
@@ -30,9 +30,9 @@ public class ZipUtils {
|
||||
|
||||
public static void unzip(InputStream zip, File folder, String path, boolean junkPath) throws Exception {
|
||||
try {
|
||||
JarInputStream zipfile = new JarInputStream(zip);
|
||||
JarEntry entry;
|
||||
while ((entry = zipfile.getNextJarEntry()) != null) {
|
||||
ZipInputStream zipfile = new ZipInputStream(zip);
|
||||
ZipEntry entry;
|
||||
while ((entry = zipfile.getNextEntry()) != null) {
|
||||
if (!entry.getName().startsWith(path) || entry.isDirectory()){
|
||||
// Ignore directories, only create files
|
||||
continue;
|
||||
|
||||
9
src/main/res/drawable/ic_fingerprint.xml
Normal file
9
src/main/res/drawable/ic_fingerprint.xml
Normal file
File diff suppressed because one or more lines are too long
9
src/main/res/drawable/ic_sort.xml
Normal file
9
src/main/res/drawable/ic_sort.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:fillColor="?attr/imageColorTint"
|
||||
android:pathData="M3,18h6v-2L3,16v2zM3,6v2h18L21,6L3,6zM3,13h12v-2L3,11v2z"/>
|
||||
</vector>
|
||||
@@ -101,18 +101,27 @@
|
||||
<Button
|
||||
style="?android:buttonBarButtonStyle"
|
||||
android:text="@string/deny_with_str"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/deny_btn"
|
||||
android:layout_weight="1" />
|
||||
|
||||
<Button
|
||||
style="?android:buttonBarButtonStyle"
|
||||
android:text="@string/grant"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/grant_btn"
|
||||
android:layout_weight="1" />
|
||||
style="?android:buttonBarButtonStyle"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/grant" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/fingerprint"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:padding="7dp"
|
||||
android:tint="?attr/colorAccent"
|
||||
android:src="@drawable/ic_fingerprint" />
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
@@ -15,71 +15,63 @@
|
||||
card_view:cardCornerRadius="@dimen/card_corner_radius"
|
||||
card_view:cardElevation="@dimen/card_elevation">
|
||||
|
||||
<RelativeLayout
|
||||
<LinearLayout
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:padding="@dimen/card_layout_padding">
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/info_layout"
|
||||
android:paddingStart="10dp"
|
||||
android:paddingEnd="10dp"
|
||||
android:paddingTop="5dp"
|
||||
android:paddingBottom="5dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/app_icon"
|
||||
android:layout_width="@dimen/card_appicon_size"
|
||||
android:layout_height="@dimen/card_appicon_size"
|
||||
android:layout_centerVertical="true"
|
||||
android:scaleType="centerCrop"/>
|
||||
android:layout_width="50dp"
|
||||
android:layout_height="50dp"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:gravity="end" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:layout_alignBottom="@+id/app_icon"
|
||||
android:gravity="center_horizontal"
|
||||
android:orientation="vertical"
|
||||
android:paddingEnd="@dimen/card_appicon_size"
|
||||
android:paddingStart="65dp"
|
||||
android:weightSum="1">
|
||||
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginStart="5dp"
|
||||
android:layout_marginEnd="5dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/app_name"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:maxLines="1"
|
||||
android:paddingEnd="3dp"
|
||||
android:paddingStart="3dp"
|
||||
android:textStyle="bold"/>
|
||||
android:ellipsize="end"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
android:textIsSelectable="false"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/app_package"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="25dp"
|
||||
android:ellipsize="marquee"
|
||||
android:gravity="center_vertical"
|
||||
android:marqueeRepeatLimit="marquee_forever"
|
||||
android:paddingEnd="3dp"
|
||||
android:paddingStart="3dp"
|
||||
android:singleLine="true"/>
|
||||
android:id="@+id/package_name"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:maxLines="1"
|
||||
android:ellipsize="end"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||
android:textColor="@android:color/tertiary_text_dark"
|
||||
android:textIsSelectable="false" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
<CheckBox
|
||||
android:id="@+id/checkbox"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_centerVertical="true">
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/checkbox"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:focusable="false"
|
||||
android:gravity="center"
|
||||
android:src="@drawable/ic_menu_overflow_material"
|
||||
android:checked="false" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</RelativeLayout>
|
||||
android:focusable="false"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:src="@drawable/ic_menu_overflow_material"
|
||||
android:checked="false" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</android.support.v7.widget.CardView>
|
||||
|
||||
|
||||
@@ -52,7 +52,6 @@
|
||||
android:maxLines="1"
|
||||
android:ellipsize="end"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
android:textIsSelectable="false"/>
|
||||
|
||||
<TextView
|
||||
|
||||
@@ -67,6 +67,15 @@
|
||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||
android:textIsSelectable="false" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/update_time"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||
android:textColor="@android:color/tertiary_text_dark"
|
||||
android:textIsSelectable="false"
|
||||
android:textStyle="bold|italic" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<ImageView
|
||||
|
||||
@@ -2,9 +2,15 @@
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<item
|
||||
android:id="@+id/repo_sort"
|
||||
android:icon="@drawable/ic_sort"
|
||||
app:showAsAction="always"/>
|
||||
|
||||
<item
|
||||
android:id="@+id/repo_search"
|
||||
android:title=""
|
||||
app:actionViewClass="android.widget.SearchView"
|
||||
app:showAsAction="always"/>
|
||||
|
||||
</menu>
|
||||
@@ -53,6 +53,10 @@
|
||||
<string name="update_available">Aktualisierung verfügbar</string>
|
||||
<string name="installed">Installiert</string>
|
||||
<string name="not_installed">Nicht installiert</string>
|
||||
<string name="updated_on">Zuletzt aktualisiert: %1$s</string>
|
||||
<string name="sorting_order">Sortierung</string>
|
||||
<string name="sort_by_name">Nach Namen sortiert</string>
|
||||
<string name="sort_by_update">Nach Aktualisierung sortiert</string>
|
||||
|
||||
<!--Log Fragment-->
|
||||
<string name="menuSaveLog">Protokoll speichern</string>
|
||||
@@ -179,6 +183,7 @@
|
||||
<string name="global_summary">Alle Root-Sitzungen benutzen den global angelegten Namensraum</string>
|
||||
<string name="requester_summary">Root-Sitzungen erben den Namensraum des Abfragenden</string>
|
||||
<string name="isolate_summary">Jede Root-Sitzung hat ihren isolierten Namensraum</string>
|
||||
<string name="android_o_not_support">Android 8.0+ wird nicht unterstützt</string>
|
||||
|
||||
<!--Superuser-->
|
||||
<string name="su_request_title">Superuser-Anfrage</string>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user