diff --git a/app/build.gradle b/app/build.gradle index d364fda5..3ec0009a 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -35,9 +35,10 @@ android { dependencies { implementation fileTree(include: ['*.jar'], dir: 'libs') + implementation 'androidx.biometric:biometric:1.0.0-alpha03' implementation 'androidx.legacy:legacy-support-core-ui:1.0.0' implementation 'androidx.fragment:fragment:1.0.0' - implementation 'androidx.appcompat:appcompat:1.0.0' + implementation 'androidx.appcompat:appcompat:1.0.2' implementation 'androidx.preference:preference:1.0.0' implementation 'androidx.constraintlayout:constraintlayout:2.0.0-alpha2' implementation 'com.google.android.material:material:1.0.0' diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index a3a6fd4d..b2c388c2 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -4,8 +4,10 @@ + + @@ -40,7 +42,7 @@ + android:theme="@style/Theme.AppCompat.Light.NoActionBar"> diff --git a/app/src/main/java/net/typeblog/shelter/ui/DummyActivity.java b/app/src/main/java/net/typeblog/shelter/ui/DummyActivity.java index 36e1653f..bde0e217 100644 --- a/app/src/main/java/net/typeblog/shelter/ui/DummyActivity.java +++ b/app/src/main/java/net/typeblog/shelter/ui/DummyActivity.java @@ -1,7 +1,6 @@ package net.typeblog.shelter.ui; import android.Manifest; -import android.app.Activity; import android.app.admin.DevicePolicyManager; import android.content.ComponentName; import android.content.Intent; @@ -17,6 +16,8 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.appcompat.app.AppCompatActivity; +import androidx.biometric.BiometricPrompt; import net.typeblog.shelter.R; import net.typeblog.shelter.ShelterApplication; @@ -35,13 +36,15 @@ import java.util.Arrays; import java.util.Date; import java.util.List; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; // DummyActivity does nothing about presenting any UI // It is a wrapper over various different operations // that might be required to perform across user profiles // which is only possible through Intents that are in // the crossProfileIntentFilter -public class DummyActivity extends Activity { +public class DummyActivity extends AppCompatActivity { public static final String FINALIZE_PROVISION = "net.typeblog.shelter.action.FINALIZE_PROVISION"; public static final String START_SERVICE = "net.typeblog.shelter.action.START_SERVICE"; public static final String TRY_START_SERVICE = "net.typeblog.shelter.action.TRY_START_SERVICE"; @@ -73,7 +76,7 @@ public class DummyActivity extends Activity { UNFREEZE_AND_LAUNCH); private static final int REQUEST_INSTALL_PACKAGE = 1; - private static final int REQUEST_PERMISSION_EXTERNAL_STORAGE= 2; + private static final int REQUEST_PERMISSION_EXTERNAL_STORAGE = 2; // A state variable to record the last time DummyActivity was informed that someone // in the same process needs to call an action without signature @@ -151,7 +154,14 @@ protected void onCreate(@Nullable Bundle savedInstanceState) { } else if (FINALIZE_PROVISION.equals(intent.getAction())) { actionFinalizeProvision(); } else if (UNFREEZE_AND_LAUNCH.equals(intent.getAction()) || PUBLIC_UNFREEZE_AND_LAUNCH.equals(intent.getAction())) { - actionUnfreezeAndLaunch(); + if ((SettingsManager.getInstance().getAutoFreezeServiceEnabled() && SettingsManager.getInstance().getFingerprintAuthEnabled())) { + //We check to see if the app has already been authorized on the main profile + if (!intent.getBooleanExtra("authorized", false)) + auth(); + else + actionUnfreezeAndLaunch(); + } else + actionUnfreezeAndLaunch(); } else if (PUBLIC_FREEZE_ALL.equals(intent.getAction())) { actionPublicFreezeAll(); } else if (FREEZE_ALL_IN_LIST.equals(intent.getAction())) { @@ -317,7 +327,7 @@ private void actionUnfreezeAndLaunch() { intent.putExtra("shouldFreeze", SettingsManager.getInstance().getAutoFreezeServiceEnabled() && LocalStorageManager.getInstance() - .stringListContains(LocalStorageManager.PREF_AUTO_FREEZE_LIST_WORK_PROFILE, packageName)); + .stringListContains(LocalStorageManager.PREF_AUTO_FREEZE_LIST_WORK_PROFILE, packageName)); if (getIntent().hasExtra("linkedPackages")) { // Multiple apps should be unfrozen here String[] packages = getIntent().getStringExtra("linkedPackages").split(","); @@ -333,6 +343,9 @@ private void actionUnfreezeAndLaunch() { intent.putExtra("linkedPackages", packages); intent.putExtra("linkedPackagesShouldFreeze", packagesShouldFreeze); } + + //We use this to for the fingerprint authentication to check if the app has already been authorized on the main profile + intent.putExtra("authorized", true); Utility.transferIntentToProfile(this, intent); startActivity(intent); finish(); @@ -460,4 +473,36 @@ private void actionSynchronizePreference() { SettingsManager.getInstance().applyAll(); finish(); } + + private void auth() { + Executor executor = Executors.newSingleThreadExecutor(); + + final BiometricPrompt biometricPrompt = new BiometricPrompt(this, executor, new BiometricPrompt.AuthenticationCallback() { + @Override + public void onAuthenticationError(int errorCode, @NonNull CharSequence errString) { + super.onAuthenticationError(errorCode, errString); + if (errorCode == BiometricPrompt.ERROR_NEGATIVE_BUTTON) { + // user clicked negative button + } + finish(); + } + + @Override + public void onAuthenticationSucceeded(@NonNull BiometricPrompt.AuthenticationResult result) { + super.onAuthenticationSucceeded(result); + actionUnfreezeAndLaunch(); + } + + @Override + public void onAuthenticationFailed() { + super.onAuthenticationFailed(); + //Called when a biometric is valid but not recognized. + } + }); + final BiometricPrompt.PromptInfo promptInfo = new BiometricPrompt.PromptInfo.Builder() + .setTitle(getString(R.string.settings_fingerprint_auth_service)) + .setNegativeButtonText(getString(R.string.fingerprint_auth_negative)) + .build(); + biometricPrompt.authenticate(promptInfo); + } } diff --git a/app/src/main/java/net/typeblog/shelter/ui/SettingsFragment.java b/app/src/main/java/net/typeblog/shelter/ui/SettingsFragment.java index 4ba3da09..890e5f5d 100644 --- a/app/src/main/java/net/typeblog/shelter/ui/SettingsFragment.java +++ b/app/src/main/java/net/typeblog/shelter/ui/SettingsFragment.java @@ -14,6 +14,7 @@ import net.typeblog.shelter.R; import net.typeblog.shelter.services.IShelterService; +import net.typeblog.shelter.util.BiometricUtils; import net.typeblog.shelter.util.SettingsManager; import net.typeblog.shelter.util.Utility; @@ -31,6 +32,7 @@ public class SettingsFragment extends PreferenceFragmentCompat implements Prefer private static final String SETTINGS_AUTO_FREEZE_SERVICE = "settings_auto_freeze_service"; private static final String SETTINGS_AUTO_FREEZE_DELAY = "settings_auto_freeze_delay"; private static final String SETTINGS_SKIP_FOREGROUND = "settings_dont_freeze_foreground"; + private static final String SETTINGS_FINGERPRINT_AUTH = "settings_fingerprint_auth_service"; private SettingsManager mManager = SettingsManager.getInstance(); private IShelterService mServiceWork = null; @@ -39,6 +41,7 @@ public class SettingsFragment extends PreferenceFragmentCompat implements Prefer private CheckBoxPreference mPrefCameraProxy = null; private CheckBoxPreference mPrefAutoFreezeService = null; private CheckBoxPreference mPrefSkipForeground = null; + private CheckBoxPreference mPrefFingerprintAuth = null; private Preference mPrefAutoFreezeDelay = null; @@ -83,6 +86,10 @@ public void onCreatePreferences(Bundle bundle, String s) { mPrefSkipForeground = (CheckBoxPreference) findPreference(SETTINGS_SKIP_FOREGROUND); mPrefSkipForeground.setChecked(mManager.getSkipForegroundEnabled()); mPrefSkipForeground.setOnPreferenceChangeListener(this); + + mPrefFingerprintAuth = (CheckBoxPreference) findPreference(SETTINGS_FINGERPRINT_AUTH); + mPrefFingerprintAuth.setChecked(mManager.getFingerprintAuthEnabled()); + mPrefFingerprintAuth.setOnPreferenceChangeListener(this); } @Override @@ -144,6 +151,13 @@ public boolean onPreferenceChange(Preference preference, Object newState) { .show(); return false; } + } else if (preference == mPrefFingerprintAuth) { + if ((BiometricUtils.isPermissionGranted(getContext()) || BiometricUtils.isBiometricPromptEnabled(getContext())) && + BiometricUtils.isHardwareSupported(getContext()) && BiometricUtils.isFingerprintAvailable(getContext())) + mManager.setFingerprintAuthEnabled((boolean) newState); + else + mManager.setFingerprintAuthEnabled(false); + return true; } else { return false; } diff --git a/app/src/main/java/net/typeblog/shelter/util/BiometricUtils.java b/app/src/main/java/net/typeblog/shelter/util/BiometricUtils.java new file mode 100644 index 00000000..b62dedb3 --- /dev/null +++ b/app/src/main/java/net/typeblog/shelter/util/BiometricUtils.java @@ -0,0 +1,41 @@ +package net.typeblog.shelter.util; + +import android.Manifest; +import android.annotation.TargetApi; +import android.content.Context; +import android.content.pm.PackageManager; +import android.os.Build; + +import androidx.core.app.ActivityCompat; +import androidx.core.hardware.fingerprint.FingerprintManagerCompat; + +public class BiometricUtils { + + public static boolean isBiometricPromptEnabled(Context context) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) + return ActivityCompat.checkSelfPermission(context, Manifest.permission.USE_BIOMETRIC) == PackageManager.PERMISSION_GRANTED; + return false; + } + + /* Condition 1: Check if the device has fingerprint sensors. */ + public static boolean isHardwareSupported(Context context) { + FingerprintManagerCompat fingerprintManager = FingerprintManagerCompat.from(context); + return fingerprintManager.isHardwareDetected(); + } + + /* Condition 2: Fingerprint authentication can be matched with a + * registered fingerprint of the user. So we need to perform this check + * in order to enable fingerprint authentication*/ + public static boolean isFingerprintAvailable(Context context) { + FingerprintManagerCompat fingerprintManager = FingerprintManagerCompat.from(context); + return fingerprintManager.hasEnrolledFingerprints(); + } + + /* Condition 3: Check if the permission has been added to + * the app. This permission will be granted as soon as the user + * installs the app on their device.*/ + public static boolean isPermissionGranted(Context context) { + return ActivityCompat.checkSelfPermission(context, Manifest.permission.USE_FINGERPRINT) == + PackageManager.PERMISSION_GRANTED; + } +} diff --git a/app/src/main/java/net/typeblog/shelter/util/LocalStorageManager.java b/app/src/main/java/net/typeblog/shelter/util/LocalStorageManager.java index 53186849..3948e511 100644 --- a/app/src/main/java/net/typeblog/shelter/util/LocalStorageManager.java +++ b/app/src/main/java/net/typeblog/shelter/util/LocalStorageManager.java @@ -17,6 +17,7 @@ public class LocalStorageManager { public static final String PREF_AUTO_FREEZE_SERVICE = "auto_freeze_service"; public static final String PREF_DONT_FREEZE_FOREGROUND = "dont_freeze_foreground"; public static final String PREF_AUTO_FREEZE_DELAY = "auto_freeze_delay"; + public static final String PREF_FINGERPRINT_AUTH = "fingerprint_auth"; public static final String PREF_CAMERA_PROXY = "camera_proxy"; private static final String LIST_DIVIDER = ","; diff --git a/app/src/main/java/net/typeblog/shelter/util/SettingsManager.java b/app/src/main/java/net/typeblog/shelter/util/SettingsManager.java index 88bc63fe..91783cb2 100644 --- a/app/src/main/java/net/typeblog/shelter/util/SettingsManager.java +++ b/app/src/main/java/net/typeblog/shelter/util/SettingsManager.java @@ -129,4 +129,15 @@ public void setSkipForegroundEnabled(boolean enabled) { public boolean getSkipForegroundEnabled() { return mStorage.getBoolean(LocalStorageManager.PREF_DONT_FREEZE_FOREGROUND); } + + // Set the enabled state of "Fingerprint Authentication" + // This does NOT need to be synchronized nor applied across profile + public void setFingerprintAuthEnabled(boolean enabled) { + mStorage.setBoolean(LocalStorageManager.PREF_FINGERPRINT_AUTH, enabled); + } + + // Get the enabled state of "Fingerprint Authentication" + public boolean getFingerprintAuthEnabled() { + return mStorage.getBoolean(LocalStorageManager.PREF_FINGERPRINT_AUTH); + } } diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index 7f078522..32551dbf 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -63,4 +63,11 @@ لا تقم بتجميد التطبيقات المفتوحه (مع نشاط مرئي) عند قفل الشاشة. يمكن أن يكون هذا مفيدًا للتطبيقات مثل مشغل الموسيقى ، ولكن ستحتاج إلى تجميدها يدويًا من خلال \"اختصار تجميد دفعة\" بعد ذلك. اكمل على أي حال يحتاج العازل إلى إذن إحصائيات الاستخدام للقيام بذلك. الرجاء تمكين الإذن لكلي التطبيقين بعد الضغط على "موافق". سيؤدي عدم القيام بذلك إلى عدم عمل هذه الميزة بشكل صحيح. + السماح للأدوات في الملف الرئيسي + إظهار كل التطبيقات + قد يتسبب التلاعب بالتطبيقات المخفية من القائمة في حدوث أعطال وكل أنواع السلوك غير المتوقع. على الرغم من ذلك ، يمكن أن تكون هذه الميزة مفيدة عندما لا يقوم ROM المخصص بتمكين جميع تطبيقات النظام الضرورية في الملف العمل بشكل افتراضي. إذا تابعت ، فهذا علي مسؤليتك الخاصه. + تأخير التجميد التلقائي + بصمة الإصبع + استخدم بصمتك لالغاء تجميد و بدأ التطبيقات + الغاء \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index b1e86477..6c64e366 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -54,6 +54,9 @@ Auto Freeze Delay Skip Foreground Apps Do NOT freeze foreground apps (with visible activity) when you lock your screen. This can be useful for apps like music players, but you\'ll need to manually freeze them through \"Batch Freeze Shortcut\" afterwards. + Fingerprint Authentication + Use your fingerprint to unfreeze and launch apps + Cancel About Version Source Code diff --git a/app/src/main/res/xml/preferences_settings.xml b/app/src/main/res/xml/preferences_settings.xml index 066f8752..4e863bd2 100644 --- a/app/src/main/res/xml/preferences_settings.xml +++ b/app/src/main/res/xml/preferences_settings.xml @@ -36,6 +36,12 @@ android:title="@string/settings_dont_freeze_foreground" android:summary="@string/settings_dont_freeze_foreground_desc" /> +