Add fingerprint authentication

This commit is contained in:
topjohnwu 2018-01-12 01:53:49 +08:00
parent 1b27e69e40
commit 2ef7146642
12 changed files with 193 additions and 25 deletions

View File

@ -9,6 +9,7 @@
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.VIBRATE" /> <uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" /> <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
<application <application
android:name=".MagiskManager" android:name=".MagiskManager"

View File

@ -62,8 +62,6 @@ public class MagiskManager extends Application {
public boolean magiskHide; public boolean magiskHide;
public boolean isDarkTheme; public boolean isDarkTheme;
public boolean updateNotification;
public boolean suReauth;
public int suRequestTimeout; public int suRequestTimeout;
public int suLogTimeout = 14; public int suLogTimeout = 14;
public int suAccessState; public int suAccessState;
@ -74,7 +72,6 @@ public class MagiskManager extends Application {
public String localeConfig; public String localeConfig;
public int updateChannel; public int updateChannel;
public String bootFormat; public String bootFormat;
public String customChannelUrl;
public int repoOrder; public int repoOrder;
// Global resources // Global resources
@ -135,17 +132,14 @@ public class MagiskManager extends Application {
suRequestTimeout = Utils.getPrefsInt(prefs, Const.Key.SU_REQUEST_TIMEOUT, Const.Value.timeoutList[2]); 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); 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); 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); 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); 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); suNamespaceMode = suDB.getSettings(Const.Key.SU_MNT_NS, Const.Value.NAMESPACE_MODE_REQUESTER);
// config // config
isDarkTheme = prefs.getBoolean(Const.Key.DARK_THEME, false); 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); updateChannel = Utils.getPrefsInt(prefs, Const.Key.UPDATE_CHANNEL, Const.Value.STABLE_CHANNEL);
bootFormat = prefs.getString(Const.Key.BOOT_FORMAT, ".img"); bootFormat = prefs.getString(Const.Key.BOOT_FORMAT, ".img");
customChannelUrl = prefs.getString(Const.Key.CUSTOM_CHANNEL, "");
repoOrder = prefs.getInt(Const.Key.REPO_ORDER, Const.Value.ORDER_NAME); repoOrder = prefs.getInt(Const.Key.REPO_ORDER, Const.Value.ORDER_NAME);
} }
@ -153,10 +147,8 @@ public class MagiskManager extends Application {
prefs.edit() prefs.edit()
.putBoolean(Const.Key.DARK_THEME, isDarkTheme) .putBoolean(Const.Key.DARK_THEME, isDarkTheme)
.putBoolean(Const.Key.MAGISKHIDE, magiskHide) .putBoolean(Const.Key.MAGISKHIDE, magiskHide)
.putBoolean(Const.Key.UPDATE_NOTIFICATION, updateNotification)
.putBoolean(Const.Key.HOSTS, Utils.itemExist(Const.MAGISK_HOST_FILE())) .putBoolean(Const.Key.HOSTS, Utils.itemExist(Const.MAGISK_HOST_FILE()))
.putBoolean(Const.Key.COREONLY, Utils.itemExist(Const.MAGISK_DISABLE_FILE)) .putBoolean(Const.Key.COREONLY, Utils.itemExist(Const.MAGISK_DISABLE_FILE))
.putBoolean(Const.Key.SU_REAUTH, suReauth)
.putString(Const.Key.SU_REQUEST_TIMEOUT, String.valueOf(suRequestTimeout)) .putString(Const.Key.SU_REQUEST_TIMEOUT, String.valueOf(suRequestTimeout))
.putString(Const.Key.SU_AUTO_RESPONSE, String.valueOf(suResponseType)) .putString(Const.Key.SU_AUTO_RESPONSE, String.valueOf(suResponseType))
.putString(Const.Key.SU_NOTIFICATION, String.valueOf(suNotificationType)) .putString(Const.Key.SU_NOTIFICATION, String.valueOf(suNotificationType))
@ -171,7 +163,7 @@ public class MagiskManager extends Application {
.apply(); .apply();
} }
public static void toast(String msg, int duration) { public static void toast(CharSequence msg, int duration) {
mHandler.post(() -> Toast.makeText(weakSelf.get(), msg, duration).show()); mHandler.post(() -> Toast.makeText(weakSelf.get(), msg, duration).show());
} }

View File

@ -22,6 +22,7 @@ import com.topjohnwu.magisk.asyncs.CheckUpdates;
import com.topjohnwu.magisk.asyncs.HideManager; import com.topjohnwu.magisk.asyncs.HideManager;
import com.topjohnwu.magisk.components.Activity; import com.topjohnwu.magisk.components.Activity;
import com.topjohnwu.magisk.utils.Const; import com.topjohnwu.magisk.utils.Const;
import com.topjohnwu.magisk.utils.FingerprintHelper;
import com.topjohnwu.magisk.utils.Shell; import com.topjohnwu.magisk.utils.Shell;
import com.topjohnwu.magisk.utils.Topic; import com.topjohnwu.magisk.utils.Topic;
import com.topjohnwu.magisk.utils.Utils; 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); multiuserMode = (ListPreference) findPreference(Const.Key.SU_MULTIUSER_MODE);
namespaceMode = (ListPreference) findPreference(Const.Key.SU_MNT_NS); namespaceMode = (ListPreference) findPreference(Const.Key.SU_MNT_NS);
SwitchPreference reauth = (SwitchPreference) findPreference(Const.Key.SU_REAUTH); SwitchPreference reauth = (SwitchPreference) findPreference(Const.Key.SU_REAUTH);
SwitchPreference fingerprint = (SwitchPreference) findPreference(Const.Key.SU_FINGERPRINT);
updateChannel.setOnPreferenceChangeListener((pref, o) -> { updateChannel.setOnPreferenceChangeListener((pref, o) -> {
mm.updateChannel = Integer.parseInt((String) o); mm.updateChannel = Integer.parseInt((String) o);
if (mm.updateChannel == Const.Value.CUSTOM_CHANNEL) { if (mm.updateChannel == Const.Value.CUSTOM_CHANNEL) {
View v = LayoutInflater.from(getActivity()).inflate(R.layout.custom_channel_dialog, null); View v = LayoutInflater.from(getActivity()).inflate(R.layout.custom_channel_dialog, null);
EditText url = v.findViewById(R.id.custom_url); 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()) new AlertDialog.Builder(getActivity())
.setTitle(R.string.settings_update_custom) .setTitle(R.string.settings_update_custom)
.setView(v) .setView(v)
@ -144,6 +146,11 @@ public class SettingsActivity extends Activity implements Topic.Subscriber {
reauth.setSummary(R.string.android_o_not_support); 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) { if (mm.getPackageName().equals(Const.ORIG_PKG_NAME) && mm.magiskVersionCode >= 1440) {
hideManager.setOnPreferenceClickListener((pref) -> { hideManager.setOnPreferenceClickListener((pref) -> {
Utils.runWithPermission(getActivity(), Utils.runWithPermission(getActivity(),

View File

@ -33,7 +33,7 @@ public class CheckUpdates extends ParallelTask<Void, Void, Void> {
jsonStr = WebService.getString(Const.Url.BETA_URL); jsonStr = WebService.getString(Const.Url.BETA_URL);
break; break;
case Const.Value.CUSTOM_CHANNEL: case Const.Value.CUSTOM_CHANNEL:
jsonStr = WebService.getString(mm.customChannelUrl); jsonStr = WebService.getString(mm.prefs.getString(Const.Key.CUSTOM_CHANNEL, ""));
break; break;
} }
try { try {
@ -54,7 +54,7 @@ public class CheckUpdates extends ParallelTask<Void, Void, Void> {
@Override @Override
protected void onPostExecute(Void v) { protected void onPostExecute(Void v) {
MagiskManager mm = MagiskManager.get(); MagiskManager mm = MagiskManager.get();
if (showNotification && mm.updateNotification) { if (showNotification && mm.prefs.getBoolean(Const.Key.UPDATE_NOTIFICATION, true)) {
if (BuildConfig.VERSION_CODE < mm.remoteManagerVersionCode) { if (BuildConfig.VERSION_CODE < mm.remoteManagerVersionCode) {
ShowUI.managerUpdateNotification(); ShowUI.managerUpdateNotification();
} else if (mm.magiskVersionCode < mm.remoteMagiskVersionCode) { } else if (mm.magiskVersionCode < mm.remoteMagiskVersionCode) {

View File

@ -5,6 +5,7 @@ import android.content.Context;
import android.content.Intent; import android.content.Intent;
import com.topjohnwu.magisk.MagiskManager; import com.topjohnwu.magisk.MagiskManager;
import com.topjohnwu.magisk.utils.Const;
import com.topjohnwu.magisk.utils.Shell; import com.topjohnwu.magisk.utils.Shell;
import com.topjohnwu.magisk.utils.Utils; import com.topjohnwu.magisk.utils.Utils;
@ -18,7 +19,7 @@ public class PackageReceiver extends BroadcastReceiver {
switch (intent.getAction()) { switch (intent.getAction()) {
case Intent.ACTION_PACKAGE_REPLACED: case Intent.ACTION_PACKAGE_REPLACED:
// This will only work pre-O // This will only work pre-O
if (mm.suReauth) { if (mm.prefs.getBoolean(Const.Key.SU_REAUTH, false)) {
mm.suDB.deletePolicy(pkg); mm.suDB.deletePolicy(pkg);
} }
break; break;

View File

@ -3,12 +3,14 @@ package com.topjohnwu.magisk.superuser;
import android.content.ContentValues; import android.content.ContentValues;
import android.content.Intent; import android.content.Intent;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.hardware.fingerprint.FingerprintManager;
import android.net.LocalSocket; import android.net.LocalSocket;
import android.net.LocalSocketAddress; import android.net.LocalSocketAddress;
import android.os.Bundle; import android.os.Bundle;
import android.os.CountDownTimer; import android.os.CountDownTimer;
import android.os.FileObserver; import android.os.FileObserver;
import android.text.TextUtils; import android.text.TextUtils;
import android.view.View;
import android.view.Window; import android.view.Window;
import android.widget.ArrayAdapter; import android.widget.ArrayAdapter;
import android.widget.Button; import android.widget.Button;
@ -23,6 +25,7 @@ import com.topjohnwu.magisk.asyncs.ParallelTask;
import com.topjohnwu.magisk.components.Activity; import com.topjohnwu.magisk.components.Activity;
import com.topjohnwu.magisk.container.Policy; import com.topjohnwu.magisk.container.Policy;
import com.topjohnwu.magisk.utils.Const; import com.topjohnwu.magisk.utils.Const;
import com.topjohnwu.magisk.utils.FingerprintHelper;
import com.topjohnwu.magisk.utils.Utils; import com.topjohnwu.magisk.utils.Utils;
import java.io.DataInputStream; import java.io.DataInputStream;
@ -40,6 +43,8 @@ public class RequestActivity extends Activity {
@BindView(R.id.package_name) TextView packageNameView; @BindView(R.id.package_name) TextView packageNameView;
@BindView(R.id.grant_btn) Button grant_btn; @BindView(R.id.grant_btn) Button grant_btn;
@BindView(R.id.deny_btn) Button deny_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 String socketPath;
private LocalSocket socket; private LocalSocket socket;
@ -49,6 +54,7 @@ public class RequestActivity extends Activity {
private boolean hasTimeout; private boolean hasTimeout;
private Policy policy; private Policy policy;
private CountDownTimer timer; private CountDownTimer timer;
private FingerprintHelper fingerprintHelper;
@Override @Override
public int getDarkTheme() { public int getDarkTheme() {
@ -80,6 +86,15 @@ public class RequestActivity extends Activity {
new SocketManager(this).exec(); new SocketManager(this).exec();
} }
@Override
public void finish() {
if (timer != null)
timer.cancel();
if (fingerprintHelper != null)
fingerprintHelper.cancel();
super.finish();
}
private boolean cancelTimeout() { private boolean cancelTimeout() {
timer.cancel(); timer.cancel();
deny_btn.setText(getString(R.string.deny)); deny_btn.setText(getString(R.string.deny));
@ -128,11 +143,49 @@ public class RequestActivity extends Activity {
} }
}; };
grant_btn.setOnClickListener(v -> { boolean useFingerprint = mm.prefs.getBoolean(Const.Key.SU_FINGERPRINT, false) && FingerprintHelper.canUseFingerprint();
handleAction(Policy.ALLOW);
timer.cancel(); if (useFingerprint) {
}); try {
grant_btn.requestFocus(); 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 -> { deny_btn.setOnClickListener(v -> {
handleAction(Policy.DENY); handleAction(Policy.DENY);
timer.cancel(); timer.cancel();
@ -151,8 +204,9 @@ public class RequestActivity extends Activity {
public void onBackPressed() { public void onBackPressed() {
if (policy != null) { if (policy != null) {
handleAction(Policy.DENY); handleAction(Policy.DENY);
} else {
finish();
} }
finish();
} }
void handleAction() { void handleAction() {

View File

@ -23,6 +23,8 @@ public class Const {
public static final String UTIL_FUNCTIONS= "util_functions.sh"; public static final String UTIL_FUNCTIONS= "util_functions.sh";
public static final String ANDROID_MANIFEST = "AndroidManifest.xml"; public static final String ANDROID_MANIFEST = "AndroidManifest.xml";
public static final String SU_KEYSTORE_KEY = "su_key";
// Paths // Paths
public static final String MAGISK_DISABLE_FILE = "/cache/.disable_magisk"; public static final String MAGISK_DISABLE_FILE = "/cache/.disable_magisk";
public static final String TMP_FOLDER_PATH = "/dev/tmp"; public static final String TMP_FOLDER_PATH = "/dev/tmp";
@ -109,6 +111,7 @@ public class Const {
public static final String SU_AUTO_RESPONSE = "su_auto_response"; public static final String SU_AUTO_RESPONSE = "su_auto_response";
public static final String SU_NOTIFICATION = "su_notification"; public static final String SU_NOTIFICATION = "su_notification";
public static final String SU_REAUTH = "su_reauth"; public static final String SU_REAUTH = "su_reauth";
public static final String SU_FINGERPRINT = "su_fingerprint";
// intents // intents
public static final String OPEN_SECTION = "section"; public static final String OPEN_SECTION = "section";

View File

@ -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();
}
}

View 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="#FF000000"
android:pathData="M17.81,4.47c-0.08,0 -0.16,-0.02 -0.23,-0.06C15.66,3.42 14,3 12.01,3c-1.98,0 -3.86,0.47 -5.57,1.41 -0.24,0.13 -0.54,0.04 -0.68,-0.2 -0.13,-0.24 -0.04,-0.55 0.2,-0.68C7.82,2.52 9.86,2 12.01,2c2.13,0 3.99,0.47 6.03,1.52 0.25,0.13 0.34,0.43 0.21,0.67 -0.09,0.18 -0.26,0.28 -0.44,0.28zM3.5,9.72c-0.1,0 -0.2,-0.03 -0.29,-0.09 -0.23,-0.16 -0.28,-0.47 -0.12,-0.7 0.99,-1.4 2.25,-2.5 3.75,-3.27C9.98,4.04 14,4.03 17.15,5.65c1.5,0.77 2.76,1.86 3.75,3.25 0.16,0.22 0.11,0.54 -0.12,0.7 -0.23,0.16 -0.54,0.11 -0.7,-0.12 -0.9,-1.26 -2.04,-2.25 -3.39,-2.94 -2.87,-1.47 -6.54,-1.47 -9.4,0.01 -1.36,0.7 -2.5,1.7 -3.4,2.96 -0.08,0.14 -0.23,0.21 -0.39,0.21zM9.75,21.79c-0.13,0 -0.26,-0.05 -0.35,-0.15 -0.87,-0.87 -1.34,-1.43 -2.01,-2.64 -0.69,-1.23 -1.05,-2.73 -1.05,-4.34 0,-2.97 2.54,-5.39 5.66,-5.39s5.66,2.42 5.66,5.39c0,0.28 -0.22,0.5 -0.5,0.5s-0.5,-0.22 -0.5,-0.5c0,-2.42 -2.09,-4.39 -4.66,-4.39 -2.57,0 -4.66,1.97 -4.66,4.39 0,1.44 0.32,2.77 0.93,3.85 0.64,1.15 1.08,1.64 1.85,2.42 0.19,0.2 0.19,0.51 0,0.71 -0.11,0.1 -0.24,0.15 -0.37,0.15zM16.92,19.94c-1.19,0 -2.24,-0.3 -3.1,-0.89 -1.49,-1.01 -2.38,-2.65 -2.38,-4.39 0,-0.28 0.22,-0.5 0.5,-0.5s0.5,0.22 0.5,0.5c0,1.41 0.72,2.74 1.94,3.56 0.71,0.48 1.54,0.71 2.54,0.71 0.24,0 0.64,-0.03 1.04,-0.1 0.27,-0.05 0.53,0.13 0.58,0.41 0.05,0.27 -0.13,0.53 -0.41,0.58 -0.57,0.11 -1.07,0.12 -1.21,0.12zM14.91,22c-0.04,0 -0.09,-0.01 -0.13,-0.02 -1.59,-0.44 -2.63,-1.03 -3.72,-2.1 -1.4,-1.39 -2.17,-3.24 -2.17,-5.22 0,-1.62 1.38,-2.94 3.08,-2.94 1.7,0 3.08,1.32 3.08,2.94 0,1.07 0.93,1.94 2.08,1.94s2.08,-0.87 2.08,-1.94c0,-3.77 -3.25,-6.83 -7.25,-6.83 -2.84,0 -5.44,1.58 -6.61,4.03 -0.39,0.81 -0.59,1.76 -0.59,2.8 0,0.78 0.07,2.01 0.67,3.61 0.1,0.26 -0.03,0.55 -0.29,0.64 -0.26,0.1 -0.55,-0.04 -0.64,-0.29 -0.49,-1.31 -0.73,-2.61 -0.73,-3.96 0,-1.2 0.23,-2.29 0.68,-3.24 1.33,-2.79 4.28,-4.6 7.51,-4.6 4.55,0 8.25,3.51 8.25,7.83 0,1.62 -1.38,2.94 -3.08,2.94s-3.08,-1.32 -3.08,-2.94c0,-1.07 -0.93,-1.94 -2.08,-1.94s-2.08,0.87 -2.08,1.94c0,1.71 0.66,3.31 1.87,4.51 0.95,0.94 1.86,1.46 3.27,1.85 0.27,0.07 0.42,0.35 0.35,0.61 -0.05,0.23 -0.26,0.38 -0.47,0.38z"/>
</vector>

View File

@ -101,18 +101,27 @@
<Button <Button
style="?android:buttonBarButtonStyle" style="?android:buttonBarButtonStyle"
android:text="@string/deny_with_str" android:text="@string/deny_with_str"
android:layout_width="match_parent" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:id="@+id/deny_btn" android:id="@+id/deny_btn"
android:layout_weight="1" /> android:layout_weight="1" />
<Button <Button
style="?android:buttonBarButtonStyle"
android:text="@string/grant"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/grant_btn" 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>
</LinearLayout> </LinearLayout>

View File

@ -166,6 +166,8 @@
<string name="request_timeout_summary">%1$s seconds</string> <string name="request_timeout_summary">%1$s seconds</string>
<string name="settings_su_reauth_title">Re-authenticate after upgrade</string> <string name="settings_su_reauth_title">Re-authenticate after upgrade</string>
<string name="settings_su_reauth_summary">Re-authenticate superuser permissions after an application upgrades</string> <string name="settings_su_reauth_summary">Re-authenticate superuser permissions after an application upgrades</string>
<string name="settings_su_fingerprint_title">Enable Fingerprint Authentication</string>
<string name="settings_su_fingerprint_summary">Use fingerprint scanner to allow superuser requests</string>
<string name="multiuser_mode">Multiuser Mode</string> <string name="multiuser_mode">Multiuser Mode</string>
<string name="settings_owner_only">Device Owner Only</string> <string name="settings_owner_only">Device Owner Only</string>
@ -212,6 +214,7 @@
<string name="su_revoke_msg">Confirm to revoke %1$s rights?</string> <string name="su_revoke_msg">Confirm to revoke %1$s rights?</string>
<string name="toast">Toast</string> <string name="toast">Toast</string>
<string name="none">None</string> <string name="none">None</string>
<string name="auth_fail">Authentication Failed</string>
<!--Superuser logs--> <!--Superuser logs-->
<string name="pid">PID:\u0020</string> <string name="pid">PID:\u0020</string>

View File

@ -107,6 +107,11 @@
android:entries="@array/su_notification" android:entries="@array/su_notification"
android:entryValues="@array/value_array" /> android:entryValues="@array/value_array" />
<SwitchPreference
android:key="su_fingerprint"
android:title="@string/settings_su_fingerprint_title"
android:summary="@string/settings_su_fingerprint_summary"/>
<SwitchPreference <SwitchPreference
android:key="su_reauth" android:key="su_reauth"
android:title="@string/settings_su_reauth_title" android:title="@string/settings_su_reauth_title"