Эх сурвалжийг харах

Implement biometric support

Signed-off-by: Mario Danic <mario@lovelyhq.com>
Mario Danic 6 жил өмнө
parent
commit
f7151d8f15

+ 0 - 68
app/src/main/java/com/nextcloud/talk/activities/BaseActivity.java

@@ -21,24 +21,18 @@
 package com.nextcloud.talk.activities;
 package com.nextcloud.talk.activities;
 
 
 import android.annotation.SuppressLint;
 import android.annotation.SuppressLint;
-import android.app.KeyguardManager;
 import android.content.Context;
 import android.content.Context;
-import android.content.Intent;
 import android.os.Build;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.Bundle;
-import android.os.Handler;
 import android.util.Log;
 import android.util.Log;
 import android.view.WindowManager;
 import android.view.WindowManager;
 import android.webkit.SslErrorHandler;
 import android.webkit.SslErrorHandler;
 import androidx.annotation.Nullable;
 import androidx.annotation.Nullable;
-import androidx.annotation.RequiresApi;
 import androidx.appcompat.app.AppCompatActivity;
 import androidx.appcompat.app.AppCompatActivity;
-import androidx.biometric.BiometricPrompt;
 import autodagger.AutoInjector;
 import autodagger.AutoInjector;
 import com.nextcloud.talk.R;
 import com.nextcloud.talk.R;
 import com.nextcloud.talk.application.NextcloudTalkApplication;
 import com.nextcloud.talk.application.NextcloudTalkApplication;
 import com.nextcloud.talk.events.CertificateEvent;
 import com.nextcloud.talk.events.CertificateEvent;
-import com.nextcloud.talk.utils.HandlerExecutor;
 import com.nextcloud.talk.utils.SecurityUtils;
 import com.nextcloud.talk.utils.SecurityUtils;
 import com.nextcloud.talk.utils.preferences.AppPreferences;
 import com.nextcloud.talk.utils.preferences.AppPreferences;
 import com.nextcloud.talk.utils.ssl.MagicTrustManager;
 import com.nextcloud.talk.utils.ssl.MagicTrustManager;
@@ -52,12 +46,10 @@ import java.security.cert.CertificateParsingException;
 import java.security.cert.X509Certificate;
 import java.security.cert.X509Certificate;
 import java.text.DateFormat;
 import java.text.DateFormat;
 import java.util.List;
 import java.util.List;
-import java.util.concurrent.Executor;
 
 
 @AutoInjector(NextcloudTalkApplication.class)
 @AutoInjector(NextcloudTalkApplication.class)
 public class BaseActivity extends AppCompatActivity {
 public class BaseActivity extends AppCompatActivity {
     private static final String TAG = "BaseActivity";
     private static final String TAG = "BaseActivity";
-    private static final int REQUEST_CODE_CONFIRM_DEVICE_CREDENTIALS = 112;
 
 
     @Inject
     @Inject
     EventBus eventBus;
     EventBus eventBus;
@@ -68,8 +60,6 @@ public class BaseActivity extends AppCompatActivity {
     @Inject
     @Inject
     Context context;
     Context context;
 
 
-    private KeyguardManager keyguardManager;
-
     @Override
     @Override
     protected void onCreate(Bundle savedInstanceState) {
     protected void onCreate(Bundle savedInstanceState) {
         NextcloudTalkApplication.getSharedApplication().getComponentApplication().inject(this);
         NextcloudTalkApplication.getSharedApplication().getComponentApplication().inject(this);
@@ -89,64 +79,6 @@ public class BaseActivity extends AppCompatActivity {
         } else {
         } else {
             getWindow().clearFlags(WindowManager.LayoutParams.FLAG_SECURE);
             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,
     public void showCertificateDialog(X509Certificate cert, MagicTrustManager magicTrustManager,

+ 35 - 5
app/src/main/java/com/nextcloud/talk/activities/MainActivity.java

@@ -20,9 +20,13 @@
  */
  */
 package com.nextcloud.talk.activities;
 package com.nextcloud.talk.activities;
 
 
+import android.app.KeyguardManager;
+import android.content.Context;
 import android.content.Intent;
 import android.content.Intent;
+import android.os.Build;
 import android.os.Bundle;
 import android.os.Bundle;
 import android.view.ViewGroup;
 import android.view.ViewGroup;
+import androidx.annotation.RequiresApi;
 import androidx.appcompat.widget.Toolbar;
 import androidx.appcompat.widget.Toolbar;
 import autodagger.AutoInjector;
 import autodagger.AutoInjector;
 import butterknife.BindView;
 import butterknife.BindView;
@@ -31,13 +35,12 @@ import com.bluelinelabs.conductor.Conductor;
 import com.bluelinelabs.conductor.Router;
 import com.bluelinelabs.conductor.Router;
 import com.bluelinelabs.conductor.RouterTransaction;
 import com.bluelinelabs.conductor.RouterTransaction;
 import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler;
 import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler;
+import com.bluelinelabs.conductor.changehandler.VerticalChangeHandler;
 import com.nextcloud.talk.R;
 import com.nextcloud.talk.R;
 import com.nextcloud.talk.application.NextcloudTalkApplication;
 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.controllers.base.providers.ActionBarProvider;
+import com.nextcloud.talk.utils.SecurityUtils;
 import com.nextcloud.talk.utils.bundle.BundleKeys;
 import com.nextcloud.talk.utils.bundle.BundleKeys;
 import com.nextcloud.talk.utils.database.user.UserUtils;
 import com.nextcloud.talk.utils.database.user.UserUtils;
 import io.requery.Persistable;
 import io.requery.Persistable;
@@ -48,7 +51,6 @@ import javax.inject.Inject;
 
 
 @AutoInjector(NextcloudTalkApplication.class)
 @AutoInjector(NextcloudTalkApplication.class)
 public final class MainActivity extends BaseActivity implements ActionBarProvider {
 public final class MainActivity extends BaseActivity implements ActionBarProvider {
-
     private static final String TAG = "MainActivity";
     private static final String TAG = "MainActivity";
 
 
     @BindView(R.id.toolbar)
     @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
     @Override
     protected void onNewIntent(Intent intent) {
     protected void onNewIntent(Intent intent) {
@@ -133,6 +159,10 @@ public final class MainActivity extends BaseActivity implements ActionBarProvide
 
 
     @Override
     @Override
     public void onBackPressed() {
     public void onBackPressed() {
+        if (router.getControllerWithTag(LockedController.TAG) != null) {
+            return;
+        }
+
         if (!router.handleBack()) {
         if (!router.handleBack()) {
             super.onBackPressed();
             super.onBackPressed();
         }
         }

+ 0 - 1
app/src/main/java/com/nextcloud/talk/controllers/AccountVerificationController.java

@@ -153,7 +153,6 @@ public class AccountVerificationController extends BaseController {
         } else {
         } else {
             checkEverything();
             checkEverything();
         }
         }
-
     }
     }
 
 
     private void checkEverything() {
     private void checkEverything() {

+ 157 - 0
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 <mario@lovelyhq.com>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+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");
+            }
+        }
+    }
+}

+ 0 - 41
app/src/main/java/com/nextcloud/talk/utils/HandlerExecutor.java

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

+ 0 - 7
app/src/main/java/com/nextcloud/talk/utils/SecurityUtils.java

@@ -20,7 +20,6 @@
 
 
 package com.nextcloud.talk.utils;
 package com.nextcloud.talk.utils;
 
 
-import android.content.Context;
 import android.content.res.Resources;
 import android.content.res.Resources;
 import android.os.Build;
 import android.os.Build;
 import android.security.keystore.KeyGenParameterSpec;
 import android.security.keystore.KeyGenParameterSpec;
@@ -30,7 +29,6 @@ import android.security.keystore.UserNotAuthenticatedException;
 import android.util.Log;
 import android.util.Log;
 import androidx.annotation.RequiresApi;
 import androidx.annotation.RequiresApi;
 import androidx.biometric.BiometricPrompt;
 import androidx.biometric.BiometricPrompt;
-import androidx.core.hardware.fingerprint.FingerprintManagerCompat;
 import com.nextcloud.talk.R;
 import com.nextcloud.talk.R;
 import com.nextcloud.talk.application.NextcloudTalkApplication;
 import com.nextcloud.talk.application.NextcloudTalkApplication;
 
 
@@ -130,9 +128,4 @@ public class SecurityUtils {
         int indexOfValidity = entryValues.indexOf(validity);
         int indexOfValidity = entryValues.indexOf(validity);
         return entryIntValues[indexOfValidity];
         return entryIntValues[indexOfValidity];
     }
     }
-
-    public static boolean isFingerprintAvailable(Context context) {
-        FingerprintManagerCompat fingerprintManager = FingerprintManagerCompat.from(context);
-        return fingerprintManager.isHardwareDetected() && fingerprintManager.hasEnrolledFingerprints();
-    }
 }
 }

+ 53 - 0
app/src/main/res/layout/controller_locked.xml

@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Nextcloud Talk application
+  ~
+  ~ @author Mario Danic
+  ~ Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
+  ~
+  ~ 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 <http://www.gnu.org/licenses/>.
+  -->
+
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:background="@drawable/ic_launcher_background"
+    android:orientation="vertical">
+
+    <ImageView
+        android:id="@+id/appLogo"
+        android:layout_width="92dp"
+        android:layout_height="92dp"
+        android:layout_centerInParent="true"
+        android:scaleType="center"
+        android:src="@drawable/ic_launcher_foreground" />
+
+    <ImageView
+        android:layout_width="24dp"
+        android:layout_height="24dp"
+        android:layout_alignTop="@id/appLogo"
+        android:layout_alignEnd="@id/appLogo"
+        android:src="@drawable/ic_lock_white_24px" />
+
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_below="@id/appLogo"
+        android:layout_centerHorizontal="true"
+        android:layout_marginTop="@dimen/margin_between_elements"
+        android:padding="16dp"
+        android:id="@+id/unlockTextView"
+        android:text="@string/nc_locked"
+        android:textAlignment="center"
+        android:textColor="@color/white" />
+</RelativeLayout>

+ 1 - 0
app/src/main/res/values/strings.xml

@@ -102,6 +102,7 @@
     <string name="nc_settings_link_previews_title">Show link previews</string>
     <string name="nc_settings_link_previews_title">Show link previews</string>
     <string name="nc_settings_link_previews_desc">Allows previews of content from received links for supported services</string>
     <string name="nc_settings_link_previews_desc">Allows previews of content from received links for supported services</string>
     <string name="nc_settings_link_previews_key" translatable="false">link_previews</string>
     <string name="nc_settings_link_previews_key" translatable="false">link_previews</string>
+    <string name="nc_locked">Tap to unlock</string>
 
 
     <string name="nc_screen_lock_timeout_30">30 seconds</string>
     <string name="nc_screen_lock_timeout_30">30 seconds</string>
     <string name="nc_screen_lock_timeout_60">1 minute</string>
     <string name="nc_screen_lock_timeout_60">1 minute</string>