10

Ho un DialogFragment che gestisce l'autenticazione di accesso e dell'impronta digitale per la mia applicazione. Questo frammento utilizza due classi che sono esclusive per l'API 23, KeyGenParameterSpec e KeyPermanentlyInvalidatedException. Avevo avuto l'impressione che ho potuto usare queste classi, fino a quando verifico la versione build prima che io cerco di inizializzare le classi (delineato here):Come utilizzare Eccezione non supportata per piattaforma inferiore Versione

if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { 
    ... 
} else { 
    ... 
} 

ma sembra che questo non è il caso. Se provo a eseguire questo codice su una versione precedente all'API 20, il Dalvik VM rifiuta l'intera classe e genera un VerifyError. Tuttavia, il codice funziona per API 20 e versioni successive. Come posso utilizzare questi metodi nel mio codice pur continuando a consentire l'utilizzo del codice per i precedenti livelli API?

La traccia di stack completo è il seguente:

05-31 14:35:50.924 11941-11941/com.example.app E/dalvikvm: Could not find class 'android.security.keystore.KeyGenParameterSpec$Builder', referenced from method com.example.app.ui.fragment.util.LoginFragment.createKeyPair 
05-31 14:35:50.924 11941-11941/com.example.app W/dalvikvm: VFY: unable to resolve new-instance 263 (Landroid/security/keystore/KeyGenParameterSpec$Builder;) in Lcom/example/app/ui/fragment/util/LoginFragment; 
05-31 14:35:50.924 11941-11941/com.example.app D/dalvikvm: VFY: replacing opcode 0x22 at 0x000c 
05-31 14:35:50.924 11941-11941/com.example.app W/dalvikvm: VFY: unable to resolve exception class 265 (Landroid/security/keystore/KeyPermanentlyInvalidatedException;) 
05-31 14:35:50.924 11941-11941/com.example.app W/dalvikvm: VFY: unable to find exception handler at addr 0x3f 
05-31 14:35:50.924 11941-11941/com.example.app W/dalvikvm: VFY: rejected Lcom/example/app/ui/fragment/util/LoginFragment;.initializeCipher (I)Z 
05-31 14:35:50.924 11941-11941/cp W/dalvikvm: VFY: rejecting opcode 0x0d at 0x003f 
05-31 14:35:50.924 11941-11941/com.example.app W/dalvikvm: VFY: rejected Lcom/example/app/ui/fragment/util/LoginFragment;.initializeCipher (I)Z 
05-31 14:35:50.924 11941-11941/com.example.app W/dalvikvm: Verifier rejected class Lcom/example/app/ui/fragment/util/LoginFragment; 
05-31 14:35:50.924 11941-11941/com.example.app D/AndroidRuntime: Shutting down VM 
05-31 14:35:50.924 11941-11941/com.example.app W/dalvikvm: threadid=1: thread exiting with uncaught exception (group=0x9cca9b20) 
05-31 14:35:50.934 11941-11941/com.example.app E/AndroidRuntime: FATAL EXCEPTION: main 
     Process: com.example.app, PID: 11941 java.lang.VerifyError: com/example/app/ui/fragment/util/LoginFragment 
      at com.example.app.util.NetworkUtility.login(NetworkUtility.java:41) 
      at com.example.app.ui.activity.AbstractNavActivity.onOptionsItemSelected(AbstractNavActivity.java:68) 
      at android.app.Activity.onMenuItemSelected(Activity.java:2600) 
      at android.support.v4.app.FragmentActivity.onMenuItemSelected(FragmentActivity.java:403) 
      at android.support.v7.app.AppCompatActivity.onMenuItemSelected(AppCompatActivity.java:189) 
      at android.support.v7.view.WindowCallbackWrapper.onMenuItemSelected(WindowCallbackWrapper.java:100) 
      at android.support.v7.view.WindowCallbackWrapper.onMenuItemSelected(WindowCallbackWrapper.java:100) 
      at android.support.v7.app.ToolbarActionBar$2.onMenuItemClick(ToolbarActionBar.java:69) 
      at android.support.v7.widget.Toolbar$1.onMenuItemClick(Toolbar.java:169) 
      at android.support.v7.widget.ActionMenuView$MenuBuilderCallback.onMenuItemSelected(ActionMenuView.java:760) 
      at android.support.v7.view.menu.MenuBuilder.dispatchMenuItemSelected(MenuBuilder.java:811) 
      at android.support.v7.view.menu.MenuItemImpl.invoke(MenuItemImpl.java:152) 
      at android.support.v7.view.menu.MenuBuilder.performItemAction(MenuBuilder.java:958) 
      at android.support.v7.view.menu.MenuBuilder.performItemAction(MenuBuilder.java:948) 
      at android.support.v7.view.menu.MenuPopupHelper.onItemClick(MenuPopupHelper.java:191) 
      at android.widget.AdapterView.performItemClick(AdapterView.java:299) 
      at android.widget.AbsListView.performItemClick(AbsListView.java:1113) 
      at android.widget.AbsListView$PerformClick.run(AbsListView.java:2904) 
      at android.widget.AbsListView$3.run(AbsListView.java:3638) 
      at android.os.Handler.handleCallback(Handler.java:733) 
      at android.os.Handler.dispatchMessage(Handler.java:95) 
      at android.os.Looper.loop(Looper.java:136) 
      at android.app.ActivityThread.main(ActivityThread.java:5017) 
      at java.lang.reflect.Method.invokeNative(Native Method) 
      at java.lang.reflect.Method.invoke(Method.java:515) 
      at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:779) 
      at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:595) 
      at dalvik.system.NativeStart.main(Native Method) 

aggiornato con codice

Il metodo login() è solo un metodo comodo per avviare il LoginFragment:

public static void login(FragmentManager manager) { 
    manager.beginTransAction().add(LoginFragment.newInstance(), null).commit(); 
} 

Il r codice elevato è nello stesso LoginFragment. In particolare i createKeyPair() e initializeCipher metodi:

public class LoginFragment extends DialogFragment 
     implements TextView.OnEditorActionListener, FingerprintCallback.Callback { 

    ... 

    public static LoginFragment newInstance() { 
     return newInstance(null); 
    } 

    public static LoginFragment newInstance(Intent intent) { 
     LoginFragment fragment = new LoginFragment(); 

     Bundle args = new Bundle(); 
     args.putParcelable(EXTRA_INTENT, intent); 
     fragment.setArguments(args); 

     return fragment; 
    } 

    @Override 
    public void onCreate(Bundle savedInstanceState) { 
     super.onCreate(savedInstanceState); 
     Injector.getContextComponent().inject(this); 
     setStyle(STYLE_NO_TITLE, R.style.DialogTheme); 
     setRetainInstance(true); 
     setCancelable(false); 

     mSaveUsernamePreference = mPreferences.getBoolean(getString(R.string.key_auth_username_retain)); 
     mUseFingerprintPreference = mPreferences.getBoolean(getString(R.string.key_auth_fingerprint)); 
     mUsernamePreference = mPreferences.getString(getString(R.string.key_auth_username)); 
     mPasswordPreference = mPreferences.getString(getString(R.string.key_auth_password)); 
    } 

    @Override 
    public View onCreateView(LayoutInflater inflater, ViewGroup container, 
          Bundle savedInstanceState) { 
     View view = inflater.inflate(R.layout.dialog_login_container, container, false); 
     ButterKnife.bind(this, view); 

     mPasswordView.setOnEditorActionListener(this); 

     if(!mFingerprintManager.isHardwareDetected()) { 
      mUseFingerprintToggle.setVisibility(View.GONE); 
     } else { 
      mGenerated = initializeKeyPair(false); 
     } 

     if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { 
      setStage(isFingerprintAvailable() ? Stage.FINGERPRINT : Stage.CREDENTIALS); 
     } else { 
      setStage(Stage.CREDENTIALS); 
     } 

     return view; 
    } 

    @Override 
    public void onResume() { 
     super.onResume(); 

     ... 

     if(mStage == Stage.FINGERPRINT && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { 
      startListening(initializeCipher(Cipher.DECRYPT_MODE)); 
     } 
    } 

    @Override 
    public void onPause() { 
     super.onPause(); 
     stopListening(); 
    } 

    ... 

    @Override 
    public void onAuthenticationSucceeded(FingerprintManagerCompat.AuthenticationResult result) { 
     Timber.i("Fingerprint succeeded"); 
     showFingerprintSuccess(); 

     mSubscriptions.add(
      mGenerated.subscribeOn(Schedulers.newThread()) 
        .observeOn(AndroidSchedulers.mainThread()) 
        .doOnCompleted(() -> { 
         try { 
          mUsername = mUsernamePreference.get(); 
          mPassword = decryptPassword(result.getCryptoObject().getCipher()); 
          initLoginAttempt(); 
         } catch (IllegalBlockSizeException | BadPaddingException exception) { 
          Timber.e(exception, "Failed to decrypt password"); 
         } 
        }).subscribe()); 
    } 

    @Override 
    public void onAuthenticationHelp(int messageId, CharSequence message) { 
     Timber.i("Fingerprint help id: " + messageId + " message: " + message); 
     showFingerprintError(message); 
    } 

    @Override 
    public void onAuthenticationError(int messageId, CharSequence message) { 
     Timber.i("Fingerprint error id: " + messageId + " message: " + message); 
     if(messageId != 5) { 
      showFingerprintError(message); 
     } 
    } 

    @Override 
    public void onAuthenticationFailed() { 
     Timber.i("Fingerprint failed"); 
     showFingerprintError(getResources().getString(R.string.msg_fingerprint_error_unknown)); 
    } 

    @OnClick(R.id.button_cancel) 
    public void onCancel() { 
     dismiss(); 
    } 

    @OnClick(R.id.button_continue) 
    public void onContinue() { 
     switch (mStage) { 
      case CREDENTIALS: 
       mUsername = mUsernameView.getText().toString(); 
       mPassword = mPasswordView.getText().toString(); 
       initLoginAttempt(); 
       break; 
      case FINGERPRINT: 
       setStage(Stage.CREDENTIALS); 
       break; 
     } 
    } 

    private void showFingerprintSuccess() { 
     int colorAccent = ThemeUtil.getColorAttribute(getContext(), android.R.attr.colorAccent); 
     mFingerprintIcon.setImageResource(R.drawable.ic_done_white_24dp); 
     mFingerprintIcon.setCircleColor(colorAccent); 
     mFingerprintStatus.setText(R.string.msg_fingerprint_success); 
     mFingerprintStatus.setTextColor(colorAccent); 
    } 

    private void showFingerprintError(CharSequence message) { 
     int colorError = ContextCompat.getColor(getContext(), R.color.material_deep_orange_600); 
     mFingerprintIcon.setImageResource(R.drawable.ic_priority_high_white_24dp); 
     mFingerprintIcon.setCircleColor(colorError); 
     mFingerprintStatus.setText(message); 
     mFingerprintStatus.setTextColor(colorError); 
     resetFingerprintStatus(); 
    } 

    private void resetFingerprintStatus() { 
     mSubscriptions.add(Observable.timer(1600, TimeUnit.MILLISECONDS) 
       .subscribeOn(Schedulers.newThread()) 
       .observeOn(AndroidSchedulers.mainThread()) 
       .subscribe(finished -> { 
        mFingerprintIcon.setImageResource(R.drawable.ic_fingerprint_white_24dp); 
        mFingerprintIcon.setCircleColor(ContextCompat 
          .getColor(getContext(), R.color.material_blue_gray_500)); 
        mFingerprintStatus.setText(R.string.msg_fingerprint_input); 
        mFingerprintStatus.setTextColor(ThemeUtil 
          .getColorAttribute(getContext(), android.R.attr.textColorHint)); 
       })); 
    } 

    private void onSaveUsernameChanged(boolean checked) { 
     if(!checked) { 
      mUseFingerprintToggle.setChecked(false); 
     } 
    } 

    private void onUseFingerprintChanged(boolean checked) { 
     if(checked) { 
      mSaveUsernameToggle.setChecked(true); 

      if(!mFingerprintManager.hasEnrolledFingerprints()) { 
       displaySettingsDialog(); 
       mUseFingerprintToggle.setChecked(false); 
      } 
     } 
    } 

    public void setStage(Stage stage) { 
     switch (stage) { 
      case CREDENTIALS: 
       Timber.d("Set stage Credentials"); 
       mPositiveButton.setText(R.string.btn_login); 
       mFingerprintContent.setVisibility(View.GONE); 
       mCredentialContent.setVisibility(View.VISIBLE); 
       setForm(); 
       break; 
      case FINGERPRINT: 
       mPositiveButton.setText(R.string.btn_password); 
       mCredentialContent.setVisibility(View.GONE); 
       mFingerprintContent.setVisibility(View.VISIBLE); 
       break; 
     } mStage = stage; 
    } 

    private void startListening(boolean cipher) { 
     Timber.v("Start listening for fingerprint input"); 
     mCancellationSignal = new CancellationSignal(); 
     if(cipher) { 
      mFingerprintManager.authenticate(new FingerprintManagerCompat.CryptoObject(mCipher), 
        0, mCancellationSignal, new FingerprintCallback(this), null); 
     } else { 
      setStage(Stage.CREDENTIALS); 
     } 
    } 

    private void stopListening() { 
     if(mCancellationSignal != null) { 
      mCancellationSignal.cancel(); 
      mCancellationSignal = null; 
     } 
    } 

    private void setForm() { 
     if(mSaveUsernamePreference.isSet() && mSaveUsernamePreference.get() 
       && mUsernamePreference.isSet()) { 
      mUsernameView.setText(mUsernamePreference.get()); 
      mUsernameView.setSelectAllOnFocus(true); 
      mPasswordView.requestFocus(); 
     } else { 
      mUsernameView.requestFocus(); 
     } 
    } 

    public void initLoginAttempt() { 
     mProgressBar.setVisibility(View.VISIBLE); 
     mAuthenticationService.getLoginForm().subscribeOn(Schedulers.newThread()) 
       .observeOn(AndroidSchedulers.mainThread()) 
       .subscribe(this::onLoginFormResponse, this::onError); 
    } 

    private void onLoginFormResponse(ResponseBody response) { 
     try { 
      attemptLogin(LoginForm.parse(response.string())); 
     } catch (IOException exception) { 
      Timber.w(exception, "Failed to parse login form"); 
     } 
    } 

    private void attemptLogin(LoginForm loginForm) { 
     mAuthenticationService 
       .login(loginForm.getLoginTicket(), loginForm.getExecution(), loginForm.getEventIdentifier(), 
         mUsername, mPassword, loginForm.getSubmitValue()) 
       .subscribeOn(Schedulers.newThread()) 
       .observeOn(AndroidSchedulers.mainThread()) 
       .subscribe(this::onLoginResponse, this::onError); 
    } 

    public void onLoginResponse(ResponseBody response) { 
     Timber.d("LOGIN RESPONSE"); 
     try { 
      Timber.d(response.string()); 
     } catch (IOException exception) { 
      Timber.w(exception, "Failed to retrieve attemptLogin response"); 
     } 

     mSubscriptions.add(NetworkUtility.getAuthentication() 
       .subscribeOn(Schedulers.newThread()) 
       .observeOn(AndroidSchedulers.mainThread()) 
       .subscribe(this::onAuthenticationChanged, this::onError)); 
    } 

    public void onAuthenticationChanged(Boolean authenticated) { 
     if(authenticated) { 
      Timber.d("Authentication success"); 

      if(mStage == Stage.CREDENTIALS) { 
       if (mSaveUsernameToggle.isChecked()) { 
        storeUsername(); 
       } else { 
        clearUsername(); 
       } 

       if (mUseFingerprintToggle.isChecked()) { 
        mGenerated = initializeKeyPair(true); 
        storePassword(); 
       } else { 
        clearPassword(); 
        finishIntent(); 
       } 
      } else { 
       finishIntent(); 
      } 
     } else { 
      Timber.d("Authentication failed"); 
      setStage(Stage.CREDENTIALS); 
      mCaptionView.setTextColor(ContextCompat.getColor(getContext(), R.color.material_deep_orange_600)); 
      mCaptionView.setText(getString(R.string.msg_login_failed)); 
      mPasswordView.setText(""); 
     } 
    } 

    private void finishIntent() { 
     mProgressBar.setVisibility(View.INVISIBLE); 
     Intent intent = getArguments().getParcelable(EXTRA_INTENT); 
     if(intent != null) { 
      startActivity(intent); 
     } dismiss(); 
    } 

    private void onError(Throwable throwable) { 
     Timber.w(throwable, "Login attempt failed"); 
     mProgressBar.setVisibility(View.INVISIBLE); 
     mCaptionView.setTextColor(ContextCompat.getColor(getContext(), R.color.material_deep_orange_600)); 
     mCaptionView.setText("Login attempt failed\nPlease check your internet connection and try again"); 
     mPasswordView.setText(""); 
    } 

    private void storeUsername() { 
     String username = mUsernameView.getText().toString(); 
     mUsernamePreference.set(username); 
     if(mPreferences.getBoolean(getString(R.string.key_auth_push), false).get()) { 
      UAirship.shared().getPushManager().getNamedUser().setId(username); 
     } 
    } 

    private void clearUsername() { 
     UAirship.shared().getPushManager().getNamedUser().setId(null); 
     mUsernamePreference.delete(); 
    } 

    private void storePassword() { 
     Timber.d("STORE PASSWORD"); 
     mSubscriptions.add(mGenerated.subscribeOn(Schedulers.newThread()) 
       .observeOn(AndroidSchedulers.mainThread()) 
       .doOnCompleted(() -> { 
        try { 
         Timber.d("Store password"); 
         initializeCipher(Cipher.ENCRYPT_MODE); 

         String password = mPasswordView.getText().toString(); 
         byte[] bytes = password.getBytes(); 
         byte[] encrypted = mCipher.doFinal(bytes); 
         String encoded = Base64.encodeToString(encrypted, Base64.NO_WRAP); 

         mPasswordPreference.set(encoded); 

         finishIntent(); 

        } catch (IllegalBlockSizeException | BadPaddingException exception) { 
         Timber.e(exception, "Failed to encrypt password"); 
        } 
       }).subscribe()); 
    } 

    private String decryptPassword(Cipher cipher) throws IllegalBlockSizeException, BadPaddingException { 
     String encoded = mPasswordPreference.get(); 

     Timber.d("ENCODED STRING " + encoded); 

     byte[] encrypted = Base64.decode(encoded, Base64.NO_WRAP); 

     byte[] bytes = cipher.doFinal(encrypted); 

     return new String(bytes); 
    } 

    private void clearPassword() { 
     mPasswordPreference.delete(); 
    } 

    private boolean isFingerprintAvailable() { 
     return mUseFingerprintPreference.isSet() && mUseFingerprintPreference.get() 
       && mFingerprintManager.hasEnrolledFingerprints() 
       && mSaveUsernamePreference.isSet() 
       && mPasswordPreference.isSet(); 
    } 

    private void displaySettingsDialog() { 
     new AlertDialog.Builder(getContext()) 
       .setTitle(R.string.title_dialog_secure_lock) 
       .setMessage(R.string.msg_fingerprint_unavailable) 
       .setPositiveButton(R.string.btn_settings, (dialog, which) -> { 
        startActivity(new Intent(android.provider.Settings.ACTION_SECURITY_SETTINGS)); 
        dialog.dismiss(); 
       }).setNegativeButton(R.string.btn_cancel, (dialog, which) -> { 
      dialog.dismiss(); 
     }).create().show(); 
    } 

    @TargetApi(Build.VERSION_CODES.M) 
    private boolean initializeCipher(int opmode) { 
     try { 
      mKeyStore.load(null); 

      /** 
      * A known bug in the Android 6.0 (API Level 23) implementation of Bouncy Castle 
      * RSA OAEP causes the cipher to default to an SHA-1 certificate, making the SHA-256 
      * certificate of the public key incompatible 
      * To work around this issue, explicitly provide a new OAEP specification upon 
      * initialization 
      * @see <a href="https://code.google.com/p/android/issues/detail?id=197719">Issue 197719</a> 
      */ 
      AlgorithmParameterSpec spec = generateOAEPParameterSpec(); 
      Key key; 

      if(opmode == Cipher.ENCRYPT_MODE) { 
       Key publicKey = mKeyStore.getCertificate(CIPHER_KEY_ALIAS).getPublicKey(); 

       /** 
       * A known bug in Android 6.0 (API Level 23) causes user authentication-related 
       * authorizations to be enforced even for public keys 
       * To work around this issue, extract the public key material to use outside of 
       * the Android Keystore 
       * @see <a href="http://developer.android.com/reference/android/security/keystore/KeyGenParameterSpec.html">KeyGenParameterSpec Known Issues</a> 
       */ 
       key = KeyFactory.getInstance(publicKey.getAlgorithm()) 
         .generatePublic(new X509EncodedKeySpec(publicKey.getEncoded())); 
      } else { 
       key = mKeyStore.getKey(CIPHER_KEY_ALIAS, null); 
      } 

      mCipher.init(opmode, key, spec); 
      return true; 
     } catch (KeyPermanentlyInvalidatedException exception) { 
      Timber.w(exception, "Failed to initialize Cipher"); 
      handleKeyPermanentlyInvalidated(); 
      return false; 
     } catch (IOException | KeyStoreException | UnrecoverableEntryException 
       | InvalidKeySpecException | CertificateException | InvalidKeyException 
       | NoSuchAlgorithmException | InvalidAlgorithmParameterException exception) { 
      throw new RuntimeException("Failed to initialize Cipher", exception); 
     } 
    } 

    private OAEPParameterSpec generateOAEPParameterSpec() { 
     return new OAEPParameterSpec("SHA-256", "MGF1", MGF1ParameterSpec.SHA1, PSource.PSpecified.DEFAULT); 
    } 

    private void handleKeyPermanentlyInvalidated() { 
     mCaptionView.setText(getString(R.string.msg_fingerprint_invalidated)); 
     mGenerated = initializeKeyPair(true); 
     clearPassword(); 
    } 

    private Observable<KeyPair> initializeKeyPair(boolean generate) { 
     return Observable.create(subscriber -> { 
      try { 
       mKeyStore.load(null); 

       if(!generate || mKeyStore.containsAlias(CIPHER_KEY_ALIAS)) { 
        PublicKey publicKey = mKeyStore.getCertificate(CIPHER_KEY_ALIAS).getPublicKey(); 
        PrivateKey privateKey = (PrivateKey) mKeyStore.getKey(CIPHER_KEY_ALIAS, null); 
        subscriber.onNext(new KeyPair(publicKey, privateKey)); 
       } else { 
        subscriber.onNext(createKeyPair()); 
       } 

       subscriber.onCompleted(); 
      } catch (IOException | KeyStoreException | UnrecoverableKeyException 
        | CertificateException | NoSuchAlgorithmException 
        | InvalidAlgorithmParameterException exception) { 
       Timber.e(exception, "Failed to generate key pair"); 
       subscriber.onError(exception); 
      } 
     }); 
    } 

    @TargetApi(Build.VERSION_CODES.M) 
    private KeyPair createKeyPair() throws InvalidAlgorithmParameterException { 
     // Set the alias of the entry in Android KeyStore where the key will appear 
     // and the constrains (purposes) in the constructor of the Builder 
     Timber.d("Initialize key pair"); 
     mKeyPairGenerator.initialize(
       new KeyGenParameterSpec.Builder(CIPHER_KEY_ALIAS, KeyProperties.PURPOSE_DECRYPT) 
        .setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA512) 
        .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_OAEP) 
         .setUserAuthenticationRequired(true) 
         .build()); 

     return mKeyPairGenerator.generateKeyPair(); 
    } 

} 

Aggiornamento

Va bene, così ho capito che è il KeyPermanentlyInvalidatedException che causa l'errore. Se commento il blocco catch che gestisce tale eccezione, il codice funziona correttamente su qualsiasi dispositivo. Il problema è che ho bisogno di essere in grado di gestire tale eccezione su dispositivi su API 23+:

catch (KeyPermanentlyInvalidatedException exception) { 
    Timber.w(exception, "A new fingerprint was added to the device"); 
    handleKeyPermanentlyInvalidated(); 
    return false; 
} 
+0

Non possiamo davvero aiutarti con codice redatto. Per favore pubblica un [mcve] che mostri il tuo problema, come l'effettiva implementazione del tuo metodo 'login()' dove stai andando in crash, e il metodo 'initializeCipher()' del tuo 'LoginFragment' (che sembra essere il materiale non riconosciuto bugie). – CommonsWare

+0

@CommonsWare Ho aggiornato la domanda con il codice. – Bryan

risposta

8

La mia ipotesi è che o FingerprintCallback.Callback estende un'interfaccia API livello 23+ o che LoginFragment ha i campi che fanno riferimento API Livello 23 + roba.

La regola per poter chiamare in sicurezza i metodi API Level 23+ all'interno del blocco di versione è corretta. Tuttavia, non è possibile:

  • ereditare dalle classi che non esistono sul dispositivo
  • implementare interfacce che non esistono sul dispositivo
  • hanno non esistono campi i cui tipi sul dispositivo
  • accettare parametri costruttore o metodo i cui tipi non esistono sul dispositivo (dove effettivamente li chiamiamo)
  • hanno valori di ritorno del metodo i cui tipi non esistono sul dispositivo (dove effettivamente li chiamiamo)

In molti casi, non è necessario alcunché, nel qual caso è sufficiente controllare Build.VERSION.SDK_INT prima di chiamare i metodi API Level 23+.

Se è necessario eseguire alcune delle operazioni nell'elenco puntato, va bene, ma è necessario isolarle in classi che si utilizzano solo su dispositivi API Level 23+.

Quindi, ad esempio, facciamo finta che il problema sia che FingerprintCallback.Callback estende un'interfaccia API 23+. Anziché implementare FingerprintCallback.Callback su LoginFragment, è possibile implementarlo come classe interna anonima e solo eseguire il codice creando tale istanza anonima della classe interna se Build.VERSION.SDK_INT è sufficientemente elevato. Quindi, stai solo facendo riferimento a FingerprintCallback.Callback sui nuovi dispositivi e dovresti essere sicuro.

+0

Vedo quello che stai dicendo, anche se non dovrebbe essere 'FingerprintCallback' perché quella è una classe che estende' FingerprintManagerCompat.AuthenticationCallback', che è una parte della [Fingerprint Support Library] (https://developer.android com/riferimento/android/supporto/v4/hardware/impronta/package-summary.html). Inoltre, non vedo campi che fanno riferimento alle classi dall'API 23+. Dovrò controllare il codice per vedere se non soddisfo uno degli altri requisiti. – Bryan

+1

@Bryan: Se finisci per essere stump, dividi 'LoginFragment' in 2-3 classi. O hanno una sottoclasse 'LoginFragment' e una' FingerprintLoginFragment', o una 'LoginFragmentBase' con' LoginFragment' e 'FingerprintLoginFragment'. Metti tutte le impronte digitali in 'FingerprintLoginFragment'. Quindi, quando arriva il momento di aggiungere() 'il frammento, scegliere la classe frammento in base al livello dell'API del dispositivo. Questo è utile anche nei casi in cui si verrebbe a capo altrimenti con un numero di '' Build.VERSION.SDK_INT'', altrimenti 'FingerprintLoginFragment' può usare qualsiasi materiale API 23 livello che desidera. – CommonsWare

+0

Ho trovato l'origine del problema, che è il 'KeyPermanentlyInvalidatedException' nel blocco try/catch di' initializeCipher() '. Ma questo metodo non viene mai chiamato sui dispositivi prima dell'API 23 (altrimenti non penso che il codice funzioni sui dispositivi API 20+). Penso che potrei finire solo facendo due lezioni come hai suggerito, ma qualche idea sul perché questo sarebbe successo? – Bryan

1

Come ha detto il problema è che blocco catch

catch (KeyPermanentlyInvalidatedException exception) { 
    Timber.w(exception, "A new fingerprint was added to the device"); 
    handleKeyPermanentlyInvalidated(); 
    return false; 
} 

Perché si aggiunge tale eccezione su API LIVELLO 23, ma non so il motivo per cui l'errore è gettato verificare in fase di inizializzazione stesso.

In ogni caso è possibile intercettare l'eccezione utilizzando

catch (InvalidKeyExceptionexception) { 
    .... 
    return false; 
} 

dal KeyPermanentlyInvalidatedException estende InvalidKeyExceptionexception

+1

Sì, ho finito per fare questo, ma ho bisogno di catturare il 'KeyPermanentlyInvalidatedException' in particolare. Il [motivo per questo] (https://developer.android.com/reference/android/security/keystore/KeyPermanentlyInvalidatedException.html) è che questa eccezione viene utilizzata per catturare ogni volta che l'utente deve eseguire nuovamente l'autenticazione con una password. Un 'InvalidKeyException' potrebbe essere lanciato in un numero di altri casi, quindi, come @CommonsWare menzionato, controllo se l'eccezione è un' instanceof' del 'KeyPermanentlyInvalidatedException' nel blocco catch, dopo aver controllato prima se l'API ha 23+. – Bryan

4

ho avuto lo stesso errore e risolto nel modo seguente:

catch (Exception e) { 
    if (e instanceof KeyPermanentlyInvalidatedException) { 
     //your error handling goes here 
    } 

isn' t vey nice, ma funziona

+0

Sì, funziona. Confermato. Non sono un fan di farlo ma ho un pesce più grande da friggere. Questo era solo un problema per me in 4.x. Quindi in un paio d'anni posso rimuovere questo brutto codice. – KickingLettuce