浏览代码

Added Fingerprint Support

Flole 8 年之前
父节点
当前提交
99bf76657a

二进制
fingerprint_icon-web.png


文件差异内容过多而无法显示
+ 7 - 0
res/drawable/ic_fingerprint.xml


+ 67 - 0
res/layout/fingerprintlock.xml

@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ownCloud Android client application
+
+  Copyright (C) 2017 Flole Systems
+
+  This program is free software: you can redistribute it and/or modify
+  it under the terms of the GNU General Public License version 2,
+  as published by the Free Software Foundation.
+
+  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/>.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    android:gravity="center_horizontal"
+    android:orientation="vertical"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+
+    >
+
+    <android.support.v7.widget.Toolbar
+        android:id="@+id/toolbar"
+        android:layout_width="match_parent"
+        android:layout_height="?attr/actionBarSize"
+        android:background="?attr/colorPrimary"
+        app:title="Nextcloud"
+        app:titleTextColor="@color/white"
+        app:layout_scrollFlags="scroll|enterAlways|snap"
+        app:popupTheme="@style/ThemeOverlay.AppCompat.Light"/>
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    android:gravity="center_horizontal"
+    android:orientation="vertical"
+    android:padding="@dimen/standard_padding" >
+
+
+    <TextView
+        android:id="@+id/scanfingerprinttext"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/fingerprint_scan_finger"
+        android:textColor="@android:color/black"
+        android:gravity="center_horizontal"
+        android:padding="8dp"
+        android:textSize="32dp"
+         />
+    <ImageView
+        android:id="@+id/fingerprinticon"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:padding="16dp"
+        android:src="@drawable/ic_fingerprint"
+        android:scaleType="fitCenter"
+        android:tint="@color/actionbar_start_color"/>
+
+
+</LinearLayout>
+</LinearLayout>

+ 383 - 0
src/com/owncloud/android/ui/activity/FingerprintActivity.java

@@ -0,0 +1,383 @@
+/**
+ *   ownCloud Android client application
+ *
+ *   @author Florian Lentz
+ *   Copyright (C) 2017 Florian Lentz
+ *
+ *   This program is free software: you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License version 2,
+ *   as published by the Free Software Foundation.
+ *
+ *   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.owncloud.android.ui.activity;
+
+import android.Manifest;
+import android.annotation.SuppressLint;
+import android.annotation.TargetApi;
+import android.app.KeyguardManager;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.res.ColorStateList;
+import android.graphics.Color;
+import android.graphics.drawable.Drawable;
+import android.hardware.fingerprint.FingerprintManager;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.CancellationSignal;
+import android.security.keystore.KeyGenParameterSpec;
+import android.security.keystore.KeyPermanentlyInvalidatedException;
+import android.security.keystore.KeyProperties;
+import android.support.v4.app.ActivityCompat;
+import android.support.v4.graphics.drawable.DrawableCompat;
+import android.support.v7.app.AppCompatActivity;
+import android.view.KeyEvent;
+import android.widget.ImageView;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.owncloud.android.MainApp;
+import com.owncloud.android.R;
+
+import java.io.IOException;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.UnrecoverableKeyException;
+import java.security.cert.CertificateException;
+
+import javax.crypto.Cipher;
+import javax.crypto.KeyGenerator;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.SecretKey;
+
+public class FingerprintActivity extends AppCompatActivity {
+
+    private static final String TAG = FingerprintActivity.class.getSimpleName();
+
+    public final static String KEY_CHECK_RESULT = "KEY_CHECK_RESULT";
+
+    public final static String PREFERENCE_USE_FINGERPRINT = "use_fingerprint";
+
+    private KeyStore keyStore;
+    // Variable used for storing the key in the Android Keystore container
+    private static final String KEY_NAME = "Nextcloud";
+    private Cipher cipher;
+
+    FingerprintManager fingerprintManager;
+
+    private CancellationSignal mCancellationSignal;
+
+    private boolean mSelfCancelled;
+
+    private TextView fingerprinttext;
+
+    FingerprintHandler helper;
+
+    FingerprintManager.CryptoObject cryptoObject;
+
+    CancellationSignal cancellationSignal;
+
+    /**
+     * Initializes the activity.
+     *
+     * @param savedInstanceState    Previously saved state - irrelevant in this case
+     */
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.fingerprintlock);
+    }
+
+    private void startFingerprint() {
+        fingerprinttext = (TextView) findViewById(R.id.scanfingerprinttext);
+
+        fingerprintManager = (FingerprintManager) MainApp.getAppContext().getSystemService(Context.FINGERPRINT_SERVICE);
+
+        mCancellationSignal = new CancellationSignal();
+        mSelfCancelled = false;
+        // The line below prevents the false positive inspection from Android Studio
+        // noinspection ResourceType
+
+        if (ActivityCompat.checkSelfPermission(this, Manifest.permission.USE_FINGERPRINT) != PackageManager.PERMISSION_GRANTED) {
+            return;
+        }
+        KeyguardManager keyguardManager = (KeyguardManager) getSystemService(KEYGUARD_SERVICE);
+
+        if (!keyguardManager.isKeyguardSecure()) {
+            return;
+        }else{
+            generateKey();
+
+
+            if (cipherInit()) {
+                cryptoObject = new FingerprintManager.CryptoObject(cipher);
+                FingerprintHandler.Callback callback = new FingerprintHandler.Callback() {
+                    @Override
+                    public void onAuthenticated() {
+                        fingerprintresult(true);
+                    }
+
+                    @Override
+                    public void onFailed(String error) {
+                        Toast.makeText(
+                                MainApp.getAppContext(),
+                                error,
+                                Toast.LENGTH_LONG)
+                                .show();
+                        ImageView imageView = (ImageView)findViewById(R.id.fingerprinticon);
+                        int[][] states = new int[][] { new int[] { android.R.attr.state_activated }, new int[] { -android.R.attr.state_activated } };
+                        int[] colors = new int[] { Color.parseColor("#FF0000"), Color.RED };
+                        ColorStateList csl = new ColorStateList(states, colors);
+                        Drawable drawable = DrawableCompat.wrap(imageView.getDrawable());
+                        DrawableCompat.setTintList(drawable, csl);
+                        imageView.setImageDrawable(drawable);
+                    }
+                };
+
+                helper = new FingerprintHandler(this, fingerprinttext, callback);
+                cancellationSignal = new CancellationSignal();
+                if (ActivityCompat.checkSelfPermission(MainApp.getAppContext(), Manifest.permission.USE_FINGERPRINT) != PackageManager.PERMISSION_GRANTED) {
+                    return;
+                }
+                fingerprintManager.authenticate(cryptoObject, cancellationSignal, 0, helper, null);
+            }
+        }
+    }
+
+    @Override
+    public void onResume(){
+        super.onResume();
+        startFingerprint();
+        ImageView imageView = (ImageView)findViewById(R.id.fingerprinticon);
+        imageView.setImageDrawable(getDrawable(R.drawable.ic_fingerprint));
+    }
+
+
+    @Override
+    public void onStop(){
+        super.onStop();
+        cancellationSignal.cancel();
+    }
+
+    /**
+     * Overrides click on the BACK arrow to prevent fingerprint from being worked around.
+     *
+     * @param keyCode       Key code of the key that triggered the down event.
+     * @param event         Event triggered.
+     * @return              'True' when the key event was processed by this method.
+     */
+    @Override
+    public boolean onKeyDown(int keyCode, KeyEvent event){
+        if (keyCode == KeyEvent.KEYCODE_BACK && event.getRepeatCount()== 0){
+            return true;
+        }
+        return super.onKeyDown(keyCode, event);
+    }
+
+    @TargetApi(Build.VERSION_CODES.M)
+    protected void generateKey() {
+        try {
+            keyStore = KeyStore.getInstance("AndroidKeyStore");
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+
+
+        KeyGenerator keyGenerator;
+        try {
+            keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore");
+        } catch (NoSuchAlgorithmException | NoSuchProviderException e) {
+            throw new RuntimeException("Failed to get KeyGenerator instance", e);
+        }
+
+
+        try {
+            keyStore.load(null);
+            keyGenerator.init(new
+                    KeyGenParameterSpec.Builder(KEY_NAME,
+                    KeyProperties.PURPOSE_ENCRYPT |
+                            KeyProperties.PURPOSE_DECRYPT)
+                    .setBlockModes(KeyProperties.BLOCK_MODE_CBC)
+                    .setUserAuthenticationRequired(true)
+                    .setEncryptionPaddings(
+                            KeyProperties.ENCRYPTION_PADDING_PKCS7)
+                    .build());
+            keyGenerator.generateKey();
+        } catch (NoSuchAlgorithmException |
+                InvalidAlgorithmParameterException
+                | CertificateException | IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+
+    @TargetApi(Build.VERSION_CODES.M)
+    public boolean cipherInit() {
+        try {
+            cipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/" + KeyProperties.BLOCK_MODE_CBC + "/" + KeyProperties.ENCRYPTION_PADDING_PKCS7);
+        } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
+            throw new RuntimeException("Failed to get Cipher", e);
+        }
+
+
+        try {
+            keyStore.load(null);
+            SecretKey key = (SecretKey) keyStore.getKey(KEY_NAME,
+                    null);
+            cipher.init(Cipher.ENCRYPT_MODE, key);
+            return true;
+        } catch (KeyPermanentlyInvalidatedException e) {
+            return false;
+        } catch (KeyStoreException | CertificateException | UnrecoverableKeyException | IOException | NoSuchAlgorithmException | InvalidKeyException e) {
+            throw new RuntimeException("Failed to init Cipher", e);
+        }
+    }
+
+
+    private void fingerprintresult(boolean fingerok) {
+
+            if (fingerok) {
+                Intent resultIntent = new Intent();
+                resultIntent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
+                resultIntent.putExtra(KEY_CHECK_RESULT, true);
+                setResult(RESULT_OK, resultIntent);
+                finish();
+            } else {
+                showErrorAndRestart(R.string.fingerprint_unknown);
+            }
+
+
+    }
+
+
+    private void showErrorAndRestart(int errorMessage) {
+        CharSequence errorSeq = getString(errorMessage);
+        Toast.makeText(this, errorSeq, Toast.LENGTH_LONG).show();
+    }
+
+
+
+    final static public boolean isFingerprintCapable(Context context) {
+        FingerprintManager fingerprintManager = (FingerprintManager) context.getSystemService(Context.FINGERPRINT_SERVICE);
+
+        if (ActivityCompat.checkSelfPermission(context, Manifest.permission.USE_FINGERPRINT) != PackageManager.PERMISSION_GRANTED) {
+            return false;
+        }
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+            if (!fingerprintManager.isHardwareDetected()) {
+                // Device doesn't support fingerprint authentication
+                return false;
+            } else if (!fingerprintManager.hasEnrolledFingerprints()) {
+                // User hasn't enrolled any fingerprints to authenticate with
+                return true;
+            } else {
+                // Everything is ready for fingerprint authentication
+                return true;
+            }
+        }
+        return false;
+    }
+
+    final static public boolean isFingerprintReady(Context context) {
+        FingerprintManager fingerprintManager = (FingerprintManager) context.getSystemService(Context.FINGERPRINT_SERVICE);
+
+        if (ActivityCompat.checkSelfPermission(context, Manifest.permission.USE_FINGERPRINT) != PackageManager.PERMISSION_GRANTED) {
+            return false;
+        }
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+            if (!fingerprintManager.isHardwareDetected()) {
+                // Device doesn't support fingerprint authentication
+                return false;
+            } else if (!fingerprintManager.hasEnrolledFingerprints()) {
+                // User hasn't enrolled any fingerprints to authenticate with
+                return false;
+            } else {
+                // Everything is ready for fingerprint authentication
+                return true;
+            }
+        }
+        return false;
+    }
+
+
+
+}
+@SuppressLint("NewApi")
+class FingerprintHandler extends FingerprintManager.AuthenticationCallback {
+
+
+    private Context context;
+    private TextView text;
+    private Callback callback;
+
+
+    // Constructor
+    public FingerprintHandler(Context mContext, TextView mtext, Callback mcallback) {
+        context = mContext;
+        text = mtext;
+        callback = mcallback;
+    }
+
+
+
+    @Override
+    public void onAuthenticationError(int errMsgId, CharSequence errString) {
+  //      this.update(String.valueOf(errString), false);
+    }
+
+
+    @Override
+    public void onAuthenticationHelp(int helpMsgId, CharSequence helpString) {
+        this.update(String.valueOf(helpString), false);
+    }
+
+
+    @Override
+    public void onAuthenticationFailed() {
+        this.update(MainApp.getAppContext().getString(R.string.fingerprint_unknown), false);
+    }
+
+
+    @Override
+    public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) {
+        this.update("Fingerprint Authentication succeeded.", true);
+    }
+
+
+    public void update(final String e, Boolean success) {
+        if(success) {
+            text.postDelayed(new Runnable() {
+                @Override
+                public void run() {
+                    callback.onAuthenticated();
+                }
+            }, 0);
+        } else {
+            text.postDelayed(new Runnable() {
+                @Override
+                public void run() {
+                    callback.onFailed(e);
+                }
+            }, 0);
+        }
+    }
+
+    public interface Callback {
+
+        void onAuthenticated();
+
+        void onFailed(String error);
+
+    }
+}

+ 2 - 0
src/main/AndroidManifest.xml

@@ -44,6 +44,7 @@
 
     <!-- WRITE_EXTERNAL_STORAGE may be enabled or disabled by the user after installation in
         API >= 23; the app needs to handle this -->
+    <uses-permission android:name="android.permission.USE_FINGERPRINT" />
     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
 
     <!-- Next permissions are always approved in installation time,
@@ -204,6 +205,7 @@
         <service android:name=".media.MediaService" />
 
         <activity android:name=".ui.activity.PassCodeActivity" />
+        <activity android:name=".ui.activity.FingerprintActivity"/>
         <activity android:name=".ui.activity.ConflictsResolveActivity"/>
         <activity android:name=".ui.activity.GenericExplanationActivity"/>
         <activity android:name=".ui.activity.ErrorsWhileCopyingHandlerActivity"/>

+ 48 - 1
src/main/java/com/owncloud/android/ui/activity/Preferences.java

@@ -93,6 +93,7 @@ public class Preferences extends PreferenceActivity
     private Uri mUri;
 
     private CheckBoxPreference pCode;
+    private CheckBoxPreference fPrint;
     private CheckBoxPreference mShowHiddenFiles;
     private Preference pAboutApp;
     private AppCompatDelegate mDelegate;
@@ -156,6 +157,9 @@ public class Preferences extends PreferenceActivity
         // Register context menu for list of preferences.
         registerForContextMenu(getListView());
 
+        PreferenceCategory preferenceCategory = (PreferenceCategory) findPreference("details");
+
+
         pCode = (CheckBoxPreference) findPreference(PassCodeActivity.PREFERENCE_SET_PASSCODE);
         if (pCode != null) {
             pCode.setOnPreferenceChangeListener(new OnPreferenceChangeListener() {
@@ -178,6 +182,49 @@ public class Preferences extends PreferenceActivity
             });
         }
 
+        boolean fPrintEnabled = getResources().getBoolean(R.bool.fingerprint_enabled);
+        fPrint = (CheckBoxPreference) findPreference(FingerprintActivity.PREFERENCE_USE_FINGERPRINT);
+        if (fPrint != null) {
+            if(FingerprintActivity.isFingerprintCapable(MainApp.getAppContext()) && fPrintEnabled) {
+                fPrint.setOnPreferenceChangeListener(new OnPreferenceChangeListener() {
+                    @Override
+                    public boolean onPreferenceChange(Preference preference, Object newValue) {
+                        Boolean incoming = (Boolean) newValue;
+
+                        if(FingerprintActivity.isFingerprintReady(MainApp.getAppContext())) {
+                            SharedPreferences appPrefs =
+                                    PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
+                            SharedPreferences.Editor editor = appPrefs.edit();
+                            editor.putBoolean("use_fingerprint", incoming);
+                            editor.commit();
+                            return true;
+                        } else {
+                            if(incoming) {
+                                Toast.makeText(
+                                        MainApp.getAppContext(),
+                                        R.string.prefs_fingerprint_notsetup,
+                                        Toast.LENGTH_LONG)
+                                        .show();
+                                fPrint.setChecked(false);
+                            }
+                            SharedPreferences appPrefs =
+                                    PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
+                            SharedPreferences.Editor editor = appPrefs.edit();
+                            editor.putBoolean("use_fingerprint", false);
+                            editor.commit();
+                            return false;
+                        }
+                    }
+                });
+                if(!FingerprintActivity.isFingerprintReady(MainApp.getAppContext())) {
+                    fPrint.setChecked(false);
+                }
+
+            } else {
+               preferenceCategory.removePreference(fPrint);
+            }
+        }
+
         mShowHiddenFiles = (CheckBoxPreference) findPreference("show_hidden_files");
         mShowHiddenFiles.setOnPreferenceClickListener(new OnPreferenceClickListener() {
             @Override
@@ -191,7 +238,7 @@ public class Preferences extends PreferenceActivity
             }
         });
 
-        PreferenceCategory preferenceCategory = (PreferenceCategory) findPreference("more");
+        preferenceCategory = (PreferenceCategory) findPreference("more");
 
         boolean calendarContactsEnabled = getResources().getBoolean(R.bool.calendar_contacts_enabled);
         Preference pCalendarContacts = findPreference("calendar_contacts");

+ 2 - 0
src/main/res/values-de-rDE/strings.xml

@@ -42,6 +42,8 @@
     <string name="prefs_accounts">Konten</string>
     <string name="prefs_manage_accounts">Konten verwalten</string>
     <string name="prefs_passcode">PIN gesperrt</string>
+    <string name="prefs_fingerprint">Fingerabdruck gesperrt</string>
+    <string name="prefs_fingerprint_notsetup">Es sind keine Fingerabdrücke eingerichtet.</string>
     <string name="prefs_show_hidden_files">Versteckte Dateien anzeigen</string>
     <string name="prefs_instant_upload">Sofortiger Bilderupload</string>
     <string name="prefs_instant_upload_summary">Fotos von der Kamera sofort hochladen</string>

+ 4 - 0
src/main/res/values/strings.xml

@@ -43,6 +43,8 @@
     <string name="prefs_accounts">Accounts</string>
     <string name="prefs_manage_accounts">Manage accounts</string>
     <string name="prefs_passcode">Passcode lock</string>
+    <string name="prefs_fingerprint">Fingerprint lock</string>
+    <string name="prefs_fingerprint_notsetup">No fingerprints have been set up.</string>
     <string name="prefs_show_hidden_files">Show hidden files</string>
     <string name="prefs_instant_upload">Instant picture uploads</string>
     <string name="prefs_instant_upload_summary">Instantly upload pictures taken by camera</string>
@@ -611,6 +613,8 @@
     <string name="welcome_feature_3_text">Keep your photos safe</string>
 
     <string name="whats_new_skip">Skip</string>
+    <string name="fingerprint_scan_finger">Please scan your finger</string>
+    <string name="fingerprint_unknown">Finger not recognized</string>
 
     <!-- User information -->
     <string name="user_info_full_name">Full name</string>

+ 2 - 1
src/main/res/xml/preferences.xml

@@ -78,8 +78,9 @@
                         
     </PreferenceCategory>
 
-	<PreferenceCategory android:title="@string/prefs_category_details">
+	<PreferenceCategory android:title="@string/prefs_category_details" android:key="details">
 		<android.preference.CheckBoxPreference android:title="@string/prefs_passcode" android:key="set_pincode" />
+		<android.preference.CheckBoxPreference android:title="@string/prefs_fingerprint" android:key="use_fingerprint" />
 		<android.preference.CheckBoxPreference android:title="@string/prefs_show_hidden_files" android:key="show_hidden_files" />
 	</PreferenceCategory>
 

+ 1 - 0
src/modified/res/values/setup.xml

@@ -78,6 +78,7 @@
     <bool name="bottom_toolbar_enabled">true</bool>
 
     <!-- Help, imprint and feedback -->
+    <bool name="fingerprint_enabled">true</bool>
     <bool name="calendar_contacts_enabled">true</bool>
     <bool name="help_enabled">true</bool>
     <bool name="imprint_enabled">false</bool> 

部分文件因为文件数量过多而无法显示