From f7151d8f158802f740bacfe2d5e2e5ede95ecd40 Mon Sep 17 00:00:00 2001 From: Mario Danic Date: Mon, 18 Feb 2019 14:02:49 +0100 Subject: [PATCH] Implement biometric support Signed-off-by: Mario Danic --- .../talk/activities/BaseActivity.java | 68 -------- .../talk/activities/MainActivity.java | 40 ++++- .../AccountVerificationController.java | 1 - .../talk/controllers/LockedController.java | 157 ++++++++++++++++++ .../nextcloud/talk/utils/HandlerExecutor.java | 41 ----- .../nextcloud/talk/utils/SecurityUtils.java | 7 - app/src/main/res/layout/controller_locked.xml | 53 ++++++ app/src/main/res/values/strings.xml | 1 + 8 files changed, 246 insertions(+), 122 deletions(-) create mode 100644 app/src/main/java/com/nextcloud/talk/controllers/LockedController.java delete mode 100644 app/src/main/java/com/nextcloud/talk/utils/HandlerExecutor.java create mode 100644 app/src/main/res/layout/controller_locked.xml diff --git a/app/src/main/java/com/nextcloud/talk/activities/BaseActivity.java b/app/src/main/java/com/nextcloud/talk/activities/BaseActivity.java index 5739f82b0..f10ad4c95 100644 --- a/app/src/main/java/com/nextcloud/talk/activities/BaseActivity.java +++ b/app/src/main/java/com/nextcloud/talk/activities/BaseActivity.java @@ -21,24 +21,18 @@ package com.nextcloud.talk.activities; import android.annotation.SuppressLint; -import android.app.KeyguardManager; import android.content.Context; -import android.content.Intent; import android.os.Build; import android.os.Bundle; -import android.os.Handler; import android.util.Log; import android.view.WindowManager; import android.webkit.SslErrorHandler; import androidx.annotation.Nullable; -import androidx.annotation.RequiresApi; import androidx.appcompat.app.AppCompatActivity; -import androidx.biometric.BiometricPrompt; import autodagger.AutoInjector; import com.nextcloud.talk.R; import com.nextcloud.talk.application.NextcloudTalkApplication; import com.nextcloud.talk.events.CertificateEvent; -import com.nextcloud.talk.utils.HandlerExecutor; import com.nextcloud.talk.utils.SecurityUtils; import com.nextcloud.talk.utils.preferences.AppPreferences; import com.nextcloud.talk.utils.ssl.MagicTrustManager; @@ -52,12 +46,10 @@ import java.security.cert.CertificateParsingException; import java.security.cert.X509Certificate; import java.text.DateFormat; import java.util.List; -import java.util.concurrent.Executor; @AutoInjector(NextcloudTalkApplication.class) public class BaseActivity extends AppCompatActivity { private static final String TAG = "BaseActivity"; - private static final int REQUEST_CODE_CONFIRM_DEVICE_CREDENTIALS = 112; @Inject EventBus eventBus; @@ -68,8 +60,6 @@ public class BaseActivity extends AppCompatActivity { @Inject Context context; - private KeyguardManager keyguardManager; - @Override protected void onCreate(Bundle savedInstanceState) { NextcloudTalkApplication.getSharedApplication().getComponentApplication().inject(this); @@ -89,64 +79,6 @@ public class BaseActivity extends AppCompatActivity { } else { getWindow().clearFlags(WindowManager.LayoutParams.FLAG_SECURE); } - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - checkIfWeAreSecure(); - } - } - - @RequiresApi(api = Build.VERSION_CODES.M) - private void checkIfWeAreSecure() { - keyguardManager = (KeyguardManager) getSystemService(Context.KEYGUARD_SERVICE); - if (keyguardManager != null && keyguardManager.isKeyguardSecure() && appPreferences.getIsScreenLocked()) { - if (!SecurityUtils.checkIfWeAreAuthenticated(appPreferences.getScreenLockTimeout())) { - if (SecurityUtils.isFingerprintAvailable(context)) { - showBiometricDialog(); - } else { - showAuthenticationScreen(); - } - } - } - } - - @RequiresApi(api = Build.VERSION_CODES.M) - private void showBiometricDialog() { - final BiometricPrompt.PromptInfo promptInfo = new BiometricPrompt.PromptInfo.Builder() - .setTitle(String.format(context.getString(R.string.nc_biometric_unlock), context.getString(R.string.nc_app_name))) - .setNegativeButtonText(context.getString(R.string.nc_cancel)) - .build(); - - Executor mainExecutor = new HandlerExecutor(new Handler(getMainLooper())); - - final BiometricPrompt biometricPrompt = new BiometricPrompt(this, mainExecutor, - new BiometricPrompt.AuthenticationCallback() { - - } - ); - - biometricPrompt.authenticate(promptInfo, SecurityUtils.getCryptoObject()); - } - - private void showAuthenticationScreen() { - Intent intent = keyguardManager.createConfirmDeviceCredentialIntent(null, null); - if (intent != null) { - startActivityForResult(intent, REQUEST_CODE_CONFIRM_DEVICE_CREDENTIALS); - } - } - - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - if (requestCode == REQUEST_CODE_CONFIRM_DEVICE_CREDENTIALS) { - if (resultCode == RESULT_OK) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - if (SecurityUtils.checkIfWeAreAuthenticated(appPreferences.getScreenLockTimeout())) { - // all went well - } - } - } else { - // we didnt auth - } - } } public void showCertificateDialog(X509Certificate cert, MagicTrustManager magicTrustManager, diff --git a/app/src/main/java/com/nextcloud/talk/activities/MainActivity.java b/app/src/main/java/com/nextcloud/talk/activities/MainActivity.java index 415bae6c4..4797a0ed9 100644 --- a/app/src/main/java/com/nextcloud/talk/activities/MainActivity.java +++ b/app/src/main/java/com/nextcloud/talk/activities/MainActivity.java @@ -20,9 +20,13 @@ */ package com.nextcloud.talk.activities; +import android.app.KeyguardManager; +import android.content.Context; import android.content.Intent; +import android.os.Build; import android.os.Bundle; import android.view.ViewGroup; +import androidx.annotation.RequiresApi; import androidx.appcompat.widget.Toolbar; import autodagger.AutoInjector; import butterknife.BindView; @@ -31,13 +35,12 @@ import com.bluelinelabs.conductor.Conductor; import com.bluelinelabs.conductor.Router; import com.bluelinelabs.conductor.RouterTransaction; import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler; +import com.bluelinelabs.conductor.changehandler.VerticalChangeHandler; import com.nextcloud.talk.R; import com.nextcloud.talk.application.NextcloudTalkApplication; -import com.nextcloud.talk.controllers.CallNotificationController; -import com.nextcloud.talk.controllers.ChatController; -import com.nextcloud.talk.controllers.ConversationsListController; -import com.nextcloud.talk.controllers.ServerSelectionController; +import com.nextcloud.talk.controllers.*; import com.nextcloud.talk.controllers.base.providers.ActionBarProvider; +import com.nextcloud.talk.utils.SecurityUtils; import com.nextcloud.talk.utils.bundle.BundleKeys; import com.nextcloud.talk.utils.database.user.UserUtils; import io.requery.Persistable; @@ -48,7 +51,6 @@ import javax.inject.Inject; @AutoInjector(NextcloudTalkApplication.class) public final class MainActivity extends BaseActivity implements ActionBarProvider { - private static final String TAG = "MainActivity"; @BindView(R.id.toolbar) @@ -113,6 +115,30 @@ public final class MainActivity extends BaseActivity implements ActionBarProvide } } + @Override + public void onResume() { + super.onResume(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + checkIfWeAreSecure(); + } + } + + + @RequiresApi(api = Build.VERSION_CODES.M) + public void checkIfWeAreSecure() { + KeyguardManager keyguardManager = (KeyguardManager) getSystemService(Context.KEYGUARD_SERVICE); + if (keyguardManager != null && keyguardManager.isKeyguardSecure() && appPreferences.getIsScreenLocked()) { + if (!SecurityUtils.checkIfWeAreAuthenticated(appPreferences.getScreenLockTimeout())) { + if (router != null && router.getControllerWithTag(LockedController.TAG) == null) { + router.pushController(RouterTransaction.with(new LockedController()) + .pushChangeHandler(new VerticalChangeHandler()) + .popChangeHandler(new VerticalChangeHandler()) + .tag(LockedController.TAG)); + } + } + } + } + @Override protected void onNewIntent(Intent intent) { @@ -133,6 +159,10 @@ public final class MainActivity extends BaseActivity implements ActionBarProvide @Override public void onBackPressed() { + if (router.getControllerWithTag(LockedController.TAG) != null) { + return; + } + if (!router.handleBack()) { super.onBackPressed(); } diff --git a/app/src/main/java/com/nextcloud/talk/controllers/AccountVerificationController.java b/app/src/main/java/com/nextcloud/talk/controllers/AccountVerificationController.java index e04218616..bfd45c736 100644 --- a/app/src/main/java/com/nextcloud/talk/controllers/AccountVerificationController.java +++ b/app/src/main/java/com/nextcloud/talk/controllers/AccountVerificationController.java @@ -153,7 +153,6 @@ public class AccountVerificationController extends BaseController { } else { checkEverything(); } - } private void checkEverything() { diff --git a/app/src/main/java/com/nextcloud/talk/controllers/LockedController.java b/app/src/main/java/com/nextcloud/talk/controllers/LockedController.java new file mode 100644 index 000000000..903fb3810 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/controllers/LockedController.java @@ -0,0 +1,157 @@ +/* + * Nextcloud Talk application + * + * @author Mario Danic + * Copyright (C) 2017-2018 Mario Danic + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.nextcloud.talk.controllers; + +import android.app.Activity; +import android.app.KeyguardManager; +import android.content.Context; +import android.content.Intent; +import android.os.Build; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import androidx.annotation.NonNull; +import androidx.annotation.RequiresApi; +import androidx.biometric.BiometricPrompt; +import autodagger.AutoInjector; +import butterknife.OnClick; +import com.nextcloud.talk.R; +import com.nextcloud.talk.activities.MainActivity; +import com.nextcloud.talk.application.NextcloudTalkApplication; +import com.nextcloud.talk.controllers.base.BaseController; +import com.nextcloud.talk.utils.SecurityUtils; +import com.nextcloud.talk.utils.preferences.AppPreferences; + +import javax.inject.Inject; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; + +@AutoInjector(NextcloudTalkApplication.class) +public class LockedController extends BaseController { + public static final String TAG = "LockedController"; + private static final int REQUEST_CODE_CONFIRM_DEVICE_CREDENTIALS = 112; + + @Inject + AppPreferences appPreferences; + + @Override + protected View inflateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container) { + return inflater.inflate(R.layout.controller_locked, container, false); + } + + @RequiresApi(api = Build.VERSION_CODES.M) + @Override + protected void onViewBound(@NonNull View view) { + super.onViewBound(view); + NextcloudTalkApplication.getSharedApplication().getComponentApplication().inject(this); + if (getActionBar() != null) { + getActionBar().hide(); + } + + showBiometricDialog(); + } + + @RequiresApi(api = Build.VERSION_CODES.M) + @OnClick(R.id.unlockTextView) + void unlock() { + checkIfWeAreSecure(); + } + + @RequiresApi(api = Build.VERSION_CODES.M) + private void showBiometricDialog() { + Context context = getActivity(); + + if (context != null) { + final BiometricPrompt.PromptInfo promptInfo = new BiometricPrompt.PromptInfo.Builder() + .setTitle(String.format(context.getString(R.string.nc_biometric_unlock), context.getString(R.string.nc_app_name))) + .setNegativeButtonText(context.getString(R.string.nc_cancel)) + .build(); + + Executor executor = Executors.newSingleThreadExecutor(); + + final BiometricPrompt biometricPrompt = new BiometricPrompt((MainActivity) context, executor, + new BiometricPrompt.AuthenticationCallback() { + @Override + public void onAuthenticationSucceeded(@NonNull BiometricPrompt.AuthenticationResult result) { + super.onAuthenticationSucceeded(result); + Log.d(TAG, "Fingerprint recognised successfully"); + getRouter().popCurrentController(); + } + + @Override + public void onAuthenticationFailed() { + super.onAuthenticationFailed(); + Log.d(TAG, "Fingerprint not recognised"); + } + + @Override + public void onAuthenticationError(int errorCode, @NonNull CharSequence errString) { + super.onAuthenticationError(errorCode, errString); + showAuthenticationScreen(); + } + } + ); + + biometricPrompt.authenticate(promptInfo, SecurityUtils.getCryptoObject()); + } + } + + @RequiresApi(api = Build.VERSION_CODES.M) + private void checkIfWeAreSecure() { + if (getActivity() != null) { + KeyguardManager keyguardManager = (KeyguardManager) getActivity().getSystemService(Context.KEYGUARD_SERVICE); + if (keyguardManager != null && keyguardManager.isKeyguardSecure() && appPreferences.getIsScreenLocked()) { + if (!SecurityUtils.checkIfWeAreAuthenticated(appPreferences.getScreenLockTimeout())) { + showBiometricDialog(); + } + } + } + } + + private void showAuthenticationScreen() { + if (getActivity() != null) { + KeyguardManager keyguardManager = (KeyguardManager) getActivity().getSystemService(Context.KEYGUARD_SERVICE); + Intent intent = keyguardManager.createConfirmDeviceCredentialIntent(null, null); + if (intent != null) { + startActivityForResult(intent, REQUEST_CODE_CONFIRM_DEVICE_CREDENTIALS); + } + } + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + + if (requestCode == REQUEST_CODE_CONFIRM_DEVICE_CREDENTIALS ) { + if (resultCode == Activity.RESULT_OK) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + if (SecurityUtils.checkIfWeAreAuthenticated(appPreferences.getScreenLockTimeout())) { + Log.d(TAG, "All went well, dismiss locked controller"); + getRouter().popCurrentController(); + } + } + } else { + Log.d(TAG, "Authorization failed"); + } + } + } +} diff --git a/app/src/main/java/com/nextcloud/talk/utils/HandlerExecutor.java b/app/src/main/java/com/nextcloud/talk/utils/HandlerExecutor.java deleted file mode 100644 index 89521f659..000000000 --- a/app/src/main/java/com/nextcloud/talk/utils/HandlerExecutor.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.nextcloud.talk.utils; -import android.os.Handler; -import androidx.annotation.NonNull; - -import java.util.concurrent.Executor; -import java.util.concurrent.RejectedExecutionException; -/** - * An adapter {@link Executor} that posts all executed tasks onto the given - * {@link Handler}. - * - * @hide - */ -public class HandlerExecutor implements Executor { - private final Handler mHandler; - - public HandlerExecutor(@NonNull Handler handler) { - mHandler = handler; - } - - @Override - public void execute(Runnable command) { - if (!mHandler.post(command)) { - throw new RejectedExecutionException(mHandler + " is shutting down"); - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/nextcloud/talk/utils/SecurityUtils.java b/app/src/main/java/com/nextcloud/talk/utils/SecurityUtils.java index 0074c8863..cea95d4d6 100644 --- a/app/src/main/java/com/nextcloud/talk/utils/SecurityUtils.java +++ b/app/src/main/java/com/nextcloud/talk/utils/SecurityUtils.java @@ -20,7 +20,6 @@ package com.nextcloud.talk.utils; -import android.content.Context; import android.content.res.Resources; import android.os.Build; import android.security.keystore.KeyGenParameterSpec; @@ -30,7 +29,6 @@ import android.security.keystore.UserNotAuthenticatedException; import android.util.Log; import androidx.annotation.RequiresApi; import androidx.biometric.BiometricPrompt; -import androidx.core.hardware.fingerprint.FingerprintManagerCompat; import com.nextcloud.talk.R; import com.nextcloud.talk.application.NextcloudTalkApplication; @@ -130,9 +128,4 @@ public class SecurityUtils { int indexOfValidity = entryValues.indexOf(validity); return entryIntValues[indexOfValidity]; } - - public static boolean isFingerprintAvailable(Context context) { - FingerprintManagerCompat fingerprintManager = FingerprintManagerCompat.from(context); - return fingerprintManager.isHardwareDetected() && fingerprintManager.hasEnrolledFingerprints(); - } } diff --git a/app/src/main/res/layout/controller_locked.xml b/app/src/main/res/layout/controller_locked.xml new file mode 100644 index 000000000..5df9f7ea1 --- /dev/null +++ b/app/src/main/res/layout/controller_locked.xml @@ -0,0 +1,53 @@ + + + + + + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index bf760c1c1..61b1cb9c5 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -102,6 +102,7 @@ Show link previews Allows previews of content from received links for supported services link_previews + Tap to unlock 30 seconds 1 minute