From d72001c6907ff50890673cd91b266c34c73ec84b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Sch=C3=BCrmann?= Date: Sat, 30 Apr 2016 15:24:09 +0200 Subject: Fix animation in manage keys for security tokens --- .../keychain/securitytoken/NfcTransport.java | 4 +- .../securitytoken/SecurityTokenHelper.java | 1 - .../keychain/ui/CreateKeyActivity.java | 13 +- .../ui/CreateSecurityTokenWaitFragment.java | 29 +- .../keychain/ui/MainActivity.java | 4 +- .../ui/SecurityTokenOperationActivity.java | 4 +- .../keychain/ui/ViewKeyActivity.java | 4 +- .../ui/base/BaseSecurityTokenActivity.java | 488 +++++++++++++++++++++ .../ui/base/BaseSecurityTokenNfcActivity.java | 488 --------------------- .../keychain/util/UsbConnectionDispatcher.java | 6 +- 10 files changed, 529 insertions(+), 512 deletions(-) create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenActivity.java delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenNfcActivity.java (limited to 'OpenKeychain/src/main/java') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/NfcTransport.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/NfcTransport.java index 3b2dd838d..ba36ebdf0 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/NfcTransport.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/NfcTransport.java @@ -19,7 +19,7 @@ package org.sufficientlysecure.keychain.securitytoken; import android.nfc.Tag; -import org.sufficientlysecure.keychain.ui.base.BaseSecurityTokenNfcActivity; +import org.sufficientlysecure.keychain.ui.base.BaseSecurityTokenActivity; import java.io.IOException; @@ -83,7 +83,7 @@ public class NfcTransport implements Transport { public void connect() throws IOException { mIsoCard = AndroidCard.get(mTag); if (mIsoCard == null) { - throw new BaseSecurityTokenNfcActivity.IsoDepNotSupportedException("Tag does not support ISO-DEP (ISO 14443-4)"); + throw new BaseSecurityTokenActivity.IsoDepNotSupportedException("Tag does not support ISO-DEP (ISO 14443-4)"); } mIsoCard.setTimeout(TIMEOUT); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/SecurityTokenHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/SecurityTokenHelper.java index 0040d6958..30893afb6 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/SecurityTokenHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/SecurityTokenHelper.java @@ -341,7 +341,6 @@ public class SecurityTokenHelper { } } - /** * Puts a key on the token in the given slot. * diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java index 44b185c52..2210a23dd 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java @@ -21,6 +21,7 @@ import android.content.Intent; import android.nfc.NfcAdapter; import android.os.Bundle; import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentTransaction; import org.sufficientlysecure.keychain.R; @@ -28,7 +29,7 @@ import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.ProviderHelper; -import org.sufficientlysecure.keychain.ui.base.BaseSecurityTokenNfcActivity; +import org.sufficientlysecure.keychain.ui.base.BaseSecurityTokenActivity; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.util.Passphrase; import org.sufficientlysecure.keychain.util.Preferences; @@ -36,7 +37,7 @@ import org.sufficientlysecure.keychain.util.Preferences; import java.io.IOException; import java.util.ArrayList; -public class CreateKeyActivity extends BaseSecurityTokenNfcActivity { +public class CreateKeyActivity extends BaseSecurityTokenActivity { public static final String EXTRA_NAME = "name"; public static final String EXTRA_EMAIL = "email"; @@ -77,7 +78,7 @@ public class CreateKeyActivity extends BaseSecurityTokenNfcActivity { // NOTE: ACTION_NDEF_DISCOVERED and not ACTION_TAG_DISCOVERED like in BaseNfcActivity if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(getIntent().getAction())) { - mTagDispatcher.interceptIntent(getIntent()); + mNfcTagDispatcher.interceptIntent(getIntent()); setTitle(R.string.title_manage_my_keys); @@ -163,7 +164,10 @@ public class CreateKeyActivity extends BaseSecurityTokenNfcActivity { // We don't want get back to wait activity mainly because it looks weird with otg token if (mCurrentFragment instanceof CreateSecurityTokenWaitFragment) { - getSupportFragmentManager().popBackStackImmediate(); + // hack from http://stackoverflow.com/a/11253987 + CreateSecurityTokenWaitFragment.sDisableFragmentAnimations = true; + getSupportFragmentManager().popBackStackImmediate(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);// getSupportFragmentManager(). + CreateSecurityTokenWaitFragment.sDisableFragmentAnimations = false; } if (containsKeys(mScannedFingerprints)) { @@ -257,7 +261,6 @@ public class CreateKeyActivity extends BaseSecurityTokenNfcActivity { // do it immediately! getSupportFragmentManager().executePendingTransactions(); - } interface SecurityTokenListenerFragment { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenWaitFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenWaitFragment.java index 5dc2c478b..d5f4cc7af 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenWaitFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenWaitFragment.java @@ -17,21 +17,23 @@ package org.sufficientlysecure.keychain.ui; -import android.app.Activity; +import android.content.Context; import android.os.Bundle; import android.support.annotation.Nullable; import android.support.v4.app.Fragment; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.view.animation.Animation; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.ui.CreateKeyActivity.FragAction; -import org.sufficientlysecure.keychain.ui.base.BaseSecurityTokenNfcActivity; - +import org.sufficientlysecure.keychain.ui.base.BaseSecurityTokenActivity; public class CreateSecurityTokenWaitFragment extends Fragment { + public static boolean sDisableFragmentAnimations = false; + CreateKeyActivity mCreateKeyActivity; View mBackButton; @@ -39,8 +41,8 @@ public class CreateSecurityTokenWaitFragment extends Fragment { public void onCreate(@Nullable final Bundle savedInstanceState) { super.onCreate(savedInstanceState); - if (this.getActivity() instanceof BaseSecurityTokenNfcActivity) { - ((BaseSecurityTokenNfcActivity) this.getActivity()).checkDeviceConnection(); + if (this.getActivity() instanceof BaseSecurityTokenActivity) { + ((BaseSecurityTokenActivity) this.getActivity()).checkDeviceConnection(); } } @@ -61,9 +63,22 @@ public class CreateSecurityTokenWaitFragment extends Fragment { } @Override - public void onAttach(Activity activity) { - super.onAttach(activity); + public void onAttach(Context context) { + super.onAttach(context); mCreateKeyActivity = (CreateKeyActivity) getActivity(); } + /** + * hack from http://stackoverflow.com/a/11253987 + */ + @Override + public Animation onCreateAnimation(int transit, boolean enter, int nextAnim) { + if (sDisableFragmentAnimations) { + Animation a = new Animation() {}; + a.setDuration(0); + return a; + } + return super.onCreateAnimation(transit, enter, nextAnim); + } + } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/MainActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/MainActivity.java index 37e01e98f..13df0b539 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/MainActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/MainActivity.java @@ -40,11 +40,11 @@ import com.mikepenz.materialdrawer.model.interfaces.IDrawerItem; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.operations.results.OperationResult; import org.sufficientlysecure.keychain.remote.ui.AppsListFragment; -import org.sufficientlysecure.keychain.ui.base.BaseSecurityTokenNfcActivity; +import org.sufficientlysecure.keychain.ui.base.BaseSecurityTokenActivity; import org.sufficientlysecure.keychain.util.FabContainer; import org.sufficientlysecure.keychain.util.Preferences; -public class MainActivity extends BaseSecurityTokenNfcActivity implements FabContainer, OnBackStackChangedListener { +public class MainActivity extends BaseSecurityTokenActivity implements FabContainer, OnBackStackChangedListener { static final int ID_KEYS = 1; static final int ID_ENCRYPT_DECRYPT = 2; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java index 925ad19d4..4d07025e6 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java @@ -40,7 +40,7 @@ import org.sufficientlysecure.keychain.securitytoken.KeyType; import org.sufficientlysecure.keychain.service.PassphraseCacheService; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; -import org.sufficientlysecure.keychain.ui.base.BaseSecurityTokenNfcActivity; +import org.sufficientlysecure.keychain.ui.base.BaseSecurityTokenActivity; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.ui.util.ThemeChanger; import org.sufficientlysecure.keychain.util.Log; @@ -58,7 +58,7 @@ import nordpol.android.NfcGuideView; * NFC devices. * For the full specs, see http://g10code.com/docs/openpgp-card-2.0.pdf */ -public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity { +public class SecurityTokenOperationActivity extends BaseSecurityTokenActivity { public static final String EXTRA_REQUIRED_INPUT = "required_input"; public static final String EXTRA_CRYPTO_INPUT = "crypto_input"; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java index e47ca1db9..dea4b4eef 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java @@ -85,7 +85,7 @@ import org.sufficientlysecure.keychain.service.ImportKeyringParcel; import org.sufficientlysecure.keychain.service.SaveKeyringParcel; import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; import org.sufficientlysecure.keychain.ui.ViewKeyFragment.PostponeType; -import org.sufficientlysecure.keychain.ui.base.BaseSecurityTokenNfcActivity; +import org.sufficientlysecure.keychain.ui.base.BaseSecurityTokenActivity; import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper; import org.sufficientlysecure.keychain.ui.dialog.SetPassphraseDialogFragment; import org.sufficientlysecure.keychain.ui.util.FormattingUtils; @@ -103,7 +103,7 @@ import org.sufficientlysecure.keychain.util.Passphrase; import org.sufficientlysecure.keychain.util.Preferences; -public class ViewKeyActivity extends BaseSecurityTokenNfcActivity implements +public class ViewKeyActivity extends BaseSecurityTokenActivity implements LoaderManager.LoaderCallbacks, CryptoOperationHelper.Callback { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenActivity.java new file mode 100644 index 000000000..75e10055d --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenActivity.java @@ -0,0 +1,488 @@ +/* + * Copyright (C) 2015 Dominik Schürmann + * Copyright (C) 2015 Vincent Breitmoser + * Copyright (C) 2013-2014 Signe Rüsch + * Copyright (C) 2013-2014 Philipp Jakubeit + * Copyright (C) 2016 Nikita Mikhailov + * + * 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 org.sufficientlysecure.keychain.ui.base; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.hardware.usb.UsbDevice; +import android.hardware.usb.UsbManager; +import android.nfc.NfcAdapter; +import android.nfc.Tag; +import android.nfc.TagLostException; +import android.os.AsyncTask; +import android.os.Bundle; + +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; +import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing; +import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; +import org.sufficientlysecure.keychain.provider.ProviderHelper; +import org.sufficientlysecure.keychain.service.PassphraseCacheService; +import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; +import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; +import org.sufficientlysecure.keychain.securitytoken.CardException; +import org.sufficientlysecure.keychain.securitytoken.NfcTransport; +import org.sufficientlysecure.keychain.securitytoken.SecurityTokenHelper; +import org.sufficientlysecure.keychain.securitytoken.Transport; +import org.sufficientlysecure.keychain.util.UsbConnectionDispatcher; +import org.sufficientlysecure.keychain.securitytoken.UsbTransport; +import org.sufficientlysecure.keychain.ui.CreateKeyActivity; +import org.sufficientlysecure.keychain.ui.PassphraseDialogActivity; +import org.sufficientlysecure.keychain.ui.ViewKeyActivity; +import org.sufficientlysecure.keychain.ui.dialog.FidesmoInstallDialog; +import org.sufficientlysecure.keychain.ui.dialog.FidesmoPgpInstallDialog; +import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; +import org.sufficientlysecure.keychain.ui.util.Notify; +import org.sufficientlysecure.keychain.ui.util.Notify.Style; +import org.sufficientlysecure.keychain.util.Log; +import org.sufficientlysecure.keychain.util.Passphrase; + +import java.io.IOException; + +import nordpol.android.OnDiscoveredTagListener; +import nordpol.android.TagDispatcher; + +public abstract class BaseSecurityTokenActivity extends BaseActivity + implements OnDiscoveredTagListener, UsbConnectionDispatcher.OnDiscoveredUsbDeviceListener { + public static final int REQUEST_CODE_PIN = 1; + + public static final String EXTRA_TAG_HANDLING_ENABLED = "tag_handling_enabled"; + + private static final String FIDESMO_APP_PACKAGE = "com.fidesmo.sec.android"; + + protected SecurityTokenHelper mSecurityTokenHelper = SecurityTokenHelper.getInstance(); + protected TagDispatcher mNfcTagDispatcher; + protected UsbConnectionDispatcher mUsbDispatcher; + private boolean mTagHandlingEnabled; + + private byte[] mSecurityTokenFingerprints; + private String mSecurityTokenUserId; + private byte[] mSecurityTokenAid; + + /** + * Override to change UI before SecurityToken handling (UI thread) + */ + protected void onSecurityTokenPreExecute() { + } + + /** + * Override to implement SecurityToken operations (background thread) + */ + protected void doSecurityTokenInBackground() throws IOException { + mSecurityTokenFingerprints = mSecurityTokenHelper.getFingerprints(); + mSecurityTokenUserId = mSecurityTokenHelper.getUserId(); + mSecurityTokenAid = mSecurityTokenHelper.getAid(); + } + + /** + * Override to handle result of SecurityToken operations (UI thread) + */ + protected void onSecurityTokenPostExecute() { + + final long subKeyId = KeyFormattingUtils.getKeyIdFromFingerprint(mSecurityTokenFingerprints); + + try { + CachedPublicKeyRing ring = new ProviderHelper(this).getCachedPublicKeyRing( + KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(subKeyId)); + long masterKeyId = ring.getMasterKeyId(); + + Intent intent = new Intent(this, ViewKeyActivity.class); + intent.setData(KeyRings.buildGenericKeyRingUri(masterKeyId)); + intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_AID, mSecurityTokenAid); + intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_USER_ID, mSecurityTokenUserId); + intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_FINGERPRINTS, mSecurityTokenFingerprints); + startActivity(intent); + } catch (PgpKeyNotFoundException e) { + Intent intent = new Intent(this, CreateKeyActivity.class); + intent.putExtra(CreateKeyActivity.EXTRA_SECURITY_TOKEN_AID, mSecurityTokenAid); + intent.putExtra(CreateKeyActivity.EXTRA_SECURITY_TOKEN_USER_ID, mSecurityTokenUserId); + intent.putExtra(CreateKeyActivity.EXTRA_SECURITY_FINGERPRINTS, mSecurityTokenFingerprints); + startActivity(intent); + } + } + + /** + * Override to use something different than Notify (UI thread) + */ + protected void onSecurityTokenError(String error) { + Notify.create(this, error, Style.WARN).show(); + } + + /** + * Override to do something when PIN is wrong, e.g., clear passphrases (UI thread) + */ + protected void onSecurityTokenPinError(String error) { + onSecurityTokenError(error); + } + + public void tagDiscovered(final Tag tag) { + // Actual NFC operations are executed in doInBackground to not block the UI thread + if (!mTagHandlingEnabled) + return; + + securityTokenDiscovered(new NfcTransport(tag)); + } + + public void usbDeviceDiscovered(final UsbDevice usbDevice) { + // Actual USB operations are executed in doInBackground to not block the UI thread + if (!mTagHandlingEnabled) + return; + + UsbManager usbManager = (UsbManager) getSystemService(Context.USB_SERVICE); + securityTokenDiscovered(new UsbTransport(usbDevice, usbManager)); + } + + public void securityTokenDiscovered(final Transport transport) { + // Actual Security Token operations are executed in doInBackground to not block the UI thread + if (!mTagHandlingEnabled) + return; + new AsyncTask() { + @Override + protected void onPreExecute() { + super.onPreExecute(); + onSecurityTokenPreExecute(); + } + + @Override + protected IOException doInBackground(Void... params) { + try { + handleSecurityToken(transport); + } catch (IOException e) { + return e; + } + + return null; + } + + @Override + protected void onPostExecute(IOException exception) { + super.onPostExecute(exception); + + if (exception != null) { + handleSecurityTokenError(exception); + return; + } + + onSecurityTokenPostExecute(); + } + }.execute(); + } + + protected void pauseTagHandling() { + mTagHandlingEnabled = false; + } + + protected void resumeTagHandling() { + mTagHandlingEnabled = true; + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + mNfcTagDispatcher = TagDispatcher.get(this, this, false, false, true, false); + mUsbDispatcher = new UsbConnectionDispatcher(this, this); + + // Check whether we're recreating a previously destroyed instance + if (savedInstanceState != null) { + // Restore value of members from saved state + mTagHandlingEnabled = savedInstanceState.getBoolean(EXTRA_TAG_HANDLING_ENABLED); + } else { + mTagHandlingEnabled = true; + } + + Intent intent = getIntent(); + String action = intent.getAction(); + if (NfcAdapter.ACTION_TAG_DISCOVERED.equals(action)) { + throw new AssertionError("should not happen: NfcOperationActivity.onCreate is called instead of onNewIntent!"); + } + + } + + @Override + protected void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + + outState.putBoolean(EXTRA_TAG_HANDLING_ENABLED, mTagHandlingEnabled); + } + + /** + * This activity is started as a singleTop activity. + * All new NFC Intents which are delivered to this activity are handled here + */ + @Override + public void onNewIntent(final Intent intent) { + mNfcTagDispatcher.interceptIntent(intent); + } + + private void handleSecurityTokenError(IOException e) { + + if (e instanceof TagLostException) { + onSecurityTokenError(getString(R.string.security_token_error_tag_lost)); + return; + } + + if (e instanceof IsoDepNotSupportedException) { + onSecurityTokenError(getString(R.string.security_token_error_iso_dep_not_supported)); + return; + } + + short status; + if (e instanceof CardException) { + status = ((CardException) e).getResponseCode(); + } else { + status = -1; + } + + // Wrong PIN, a status of 63CX indicates X attempts remaining. + if ((status & (short) 0xFFF0) == 0x63C0) { + int tries = status & 0x000F; + // hook to do something different when PIN is wrong + onSecurityTokenPinError(getResources().getQuantityString(R.plurals.security_token_error_pin, tries, tries)); + return; + } + + // Otherwise, all status codes are fixed values. + switch (status) { + // These errors should not occur in everyday use; if they are returned, it means we + // made a mistake sending data to the token, or the token is misbehaving. + case 0x6A80: { + onSecurityTokenError(getString(R.string.security_token_error_bad_data)); + break; + } + case 0x6883: { + onSecurityTokenError(getString(R.string.security_token_error_chaining_error)); + break; + } + case 0x6B00: { + onSecurityTokenError(getString(R.string.security_token_error_header, "P1/P2")); + break; + } + case 0x6D00: { + onSecurityTokenError(getString(R.string.security_token_error_header, "INS")); + break; + } + case 0x6E00: { + onSecurityTokenError(getString(R.string.security_token_error_header, "CLA")); + break; + } + // These error conditions are more likely to be experienced by an end user. + case 0x6285: { + onSecurityTokenError(getString(R.string.security_token_error_terminated)); + break; + } + case 0x6700: { + onSecurityTokenPinError(getString(R.string.security_token_error_wrong_length)); + break; + } + case 0x6982: { + onSecurityTokenError(getString(R.string.security_token_error_security_not_satisfied)); + break; + } + case 0x6983: { + onSecurityTokenError(getString(R.string.security_token_error_authentication_blocked)); + break; + } + case 0x6985: { + onSecurityTokenError(getString(R.string.security_token_error_conditions_not_satisfied)); + break; + } + // 6A88 is "Not Found" in the spec, but Yubikey also returns 6A83 for this in some cases. + case 0x6A88: + case 0x6A83: { + onSecurityTokenError(getString(R.string.security_token_error_data_not_found)); + break; + } + // 6F00 is a JavaCard proprietary status code, SW_UNKNOWN, and usually represents an + // unhandled exception on the security token. + case 0x6F00: { + onSecurityTokenError(getString(R.string.security_token_error_unknown)); + break; + } + // 6A82 app not installed on security token! + case 0x6A82: { + if (mSecurityTokenHelper.isFidesmoToken()) { + // Check if the Fidesmo app is installed + if (isAndroidAppInstalled(FIDESMO_APP_PACKAGE)) { + promptFidesmoPgpInstall(); + } else { + promptFidesmoAppInstall(); + } + } else { // Other (possibly) compatible hardware + onSecurityTokenError(getString(R.string.security_token_error_pgp_app_not_installed)); + } + break; + } + default: { + onSecurityTokenError(getString(R.string.security_token_error, e.getMessage())); + break; + } + } + + } + + /** + * Called when the system is about to start resuming a previous activity, + * disables NFC Foreground Dispatch + */ + public void onPause() { + super.onPause(); + Log.d(Constants.TAG, "BaseNfcActivity.onPause"); + + mNfcTagDispatcher.disableExclusiveNfc(); + } + + /** + * Called when the activity will start interacting with the user, + * enables NFC Foreground Dispatch + */ + public void onResume() { + super.onResume(); + Log.d(Constants.TAG, "BaseNfcActivity.onResume"); + mNfcTagDispatcher.enableExclusiveNfc(); + } + + protected void obtainSecurityTokenPin(RequiredInputParcel requiredInput) { + + try { + Passphrase passphrase = PassphraseCacheService.getCachedPassphrase(this, + requiredInput.getMasterKeyId(), requiredInput.getSubKeyId()); + if (passphrase != null) { + mSecurityTokenHelper.setPin(passphrase); + return; + } + + Intent intent = new Intent(this, PassphraseDialogActivity.class); + intent.putExtra(PassphraseDialogActivity.EXTRA_REQUIRED_INPUT, + RequiredInputParcel.createRequiredPassphrase(requiredInput)); + startActivityForResult(intent, REQUEST_CODE_PIN); + } catch (PassphraseCacheService.KeyNotFoundException e) { + throw new AssertionError( + "tried to find passphrase for non-existing key. this is a programming error!"); + } + + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + switch (requestCode) { + case REQUEST_CODE_PIN: { + if (resultCode != Activity.RESULT_OK) { + setResult(resultCode); + finish(); + return; + } + CryptoInputParcel input = data.getParcelableExtra(PassphraseDialogActivity.RESULT_CRYPTO_INPUT); + mSecurityTokenHelper.setPin(input.getPassphrase()); + break; + } + default: + super.onActivityResult(requestCode, resultCode, data); + } + } + + protected void handleSecurityToken(Transport transport) throws IOException { + // Don't reconnect if device was already connected + if (!(mSecurityTokenHelper.isPersistentConnectionAllowed() + && mSecurityTokenHelper.isConnected() + && mSecurityTokenHelper.getTransport().equals(transport))) { + mSecurityTokenHelper.setTransport(transport); + mSecurityTokenHelper.connectToDevice(); + } + doSecurityTokenInBackground(); + } + + public boolean isSecurityTokenConnected() { + return mSecurityTokenHelper.isConnected(); + } + + public static class IsoDepNotSupportedException extends IOException { + + public IsoDepNotSupportedException(String detailMessage) { + super(detailMessage); + } + + } + + /** + * Ask user if she wants to install PGP onto her Fidesmo token + */ + private void promptFidesmoPgpInstall() { + FidesmoPgpInstallDialog fidesmoPgpInstallDialog = new FidesmoPgpInstallDialog(); + fidesmoPgpInstallDialog.show(getSupportFragmentManager(), "fidesmoPgpInstallDialog"); + } + + /** + * Show a Dialog to the user informing that Fidesmo App must be installed and with option + * to launch the Google Play store. + */ + private void promptFidesmoAppInstall() { + FidesmoInstallDialog fidesmoInstallDialog = new FidesmoInstallDialog(); + fidesmoInstallDialog.show(getSupportFragmentManager(), "fidesmoInstallDialog"); + } + + /** + * Use the package manager to detect if an application is installed on the phone + * + * @param uri an URI identifying the application's package + * @return 'true' if the app is installed + */ + private boolean isAndroidAppInstalled(String uri) { + PackageManager mPackageManager = getPackageManager(); + boolean mAppInstalled; + try { + mPackageManager.getPackageInfo(uri, PackageManager.GET_ACTIVITIES); + mAppInstalled = true; + } catch (PackageManager.NameNotFoundException e) { + Log.e(Constants.TAG, "App not installed on Android device"); + mAppInstalled = false; + } + return mAppInstalled; + } + + @Override + protected void onStop() { + super.onStop(); + mUsbDispatcher.onStop(); + } + + @Override + protected void onStart() { + super.onStart(); + mUsbDispatcher.onStart(); + } + + public SecurityTokenHelper getSecurityTokenHelper() { + return mSecurityTokenHelper; + } + + /** + * Run Security Token routines if last used token is connected and supports + * persistent connections + */ + public void checkDeviceConnection() { + mUsbDispatcher.rescanDevices(); + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenNfcActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenNfcActivity.java deleted file mode 100644 index f4c0a9365..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenNfcActivity.java +++ /dev/null @@ -1,488 +0,0 @@ -/* - * Copyright (C) 2015 Dominik Schürmann - * Copyright (C) 2015 Vincent Breitmoser - * Copyright (C) 2013-2014 Signe Rüsch - * Copyright (C) 2013-2014 Philipp Jakubeit - * Copyright (C) 2016 Nikita Mikhailov - * - * 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 org.sufficientlysecure.keychain.ui.base; - -import android.app.Activity; -import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.hardware.usb.UsbDevice; -import android.hardware.usb.UsbManager; -import android.nfc.NfcAdapter; -import android.nfc.Tag; -import android.nfc.TagLostException; -import android.os.AsyncTask; -import android.os.Bundle; - -import org.sufficientlysecure.keychain.Constants; -import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; -import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing; -import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; -import org.sufficientlysecure.keychain.provider.ProviderHelper; -import org.sufficientlysecure.keychain.service.PassphraseCacheService; -import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; -import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; -import org.sufficientlysecure.keychain.securitytoken.CardException; -import org.sufficientlysecure.keychain.securitytoken.NfcTransport; -import org.sufficientlysecure.keychain.securitytoken.SecurityTokenHelper; -import org.sufficientlysecure.keychain.securitytoken.Transport; -import org.sufficientlysecure.keychain.util.UsbConnectionDispatcher; -import org.sufficientlysecure.keychain.securitytoken.UsbTransport; -import org.sufficientlysecure.keychain.ui.CreateKeyActivity; -import org.sufficientlysecure.keychain.ui.PassphraseDialogActivity; -import org.sufficientlysecure.keychain.ui.ViewKeyActivity; -import org.sufficientlysecure.keychain.ui.dialog.FidesmoInstallDialog; -import org.sufficientlysecure.keychain.ui.dialog.FidesmoPgpInstallDialog; -import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; -import org.sufficientlysecure.keychain.ui.util.Notify; -import org.sufficientlysecure.keychain.ui.util.Notify.Style; -import org.sufficientlysecure.keychain.util.Log; -import org.sufficientlysecure.keychain.util.Passphrase; - -import java.io.IOException; - -import nordpol.android.OnDiscoveredTagListener; -import nordpol.android.TagDispatcher; - -public abstract class BaseSecurityTokenNfcActivity extends BaseActivity - implements OnDiscoveredTagListener, UsbConnectionDispatcher.OnDiscoveredUsbDeviceListener { - public static final int REQUEST_CODE_PIN = 1; - - public static final String EXTRA_TAG_HANDLING_ENABLED = "tag_handling_enabled"; - - private static final String FIDESMO_APP_PACKAGE = "com.fidesmo.sec.android"; - - protected SecurityTokenHelper mSecurityTokenHelper = SecurityTokenHelper.getInstance(); - protected TagDispatcher mTagDispatcher; - protected UsbConnectionDispatcher mUsbDispatcher; - private boolean mTagHandlingEnabled; - - private byte[] mSecurityTokenFingerprints; - private String mSecurityTokenUserId; - private byte[] mSecurityTokenAid; - - /** - * Override to change UI before SecurityToken handling (UI thread) - */ - protected void onSecurityTokenPreExecute() { - } - - /** - * Override to implement SecurityToken operations (background thread) - */ - protected void doSecurityTokenInBackground() throws IOException { - mSecurityTokenFingerprints = mSecurityTokenHelper.getFingerprints(); - mSecurityTokenUserId = mSecurityTokenHelper.getUserId(); - mSecurityTokenAid = mSecurityTokenHelper.getAid(); - } - - /** - * Override to handle result of SecurityToken operations (UI thread) - */ - protected void onSecurityTokenPostExecute() { - - final long subKeyId = KeyFormattingUtils.getKeyIdFromFingerprint(mSecurityTokenFingerprints); - - try { - CachedPublicKeyRing ring = new ProviderHelper(this).getCachedPublicKeyRing( - KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(subKeyId)); - long masterKeyId = ring.getMasterKeyId(); - - Intent intent = new Intent(this, ViewKeyActivity.class); - intent.setData(KeyRings.buildGenericKeyRingUri(masterKeyId)); - intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_AID, mSecurityTokenAid); - intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_USER_ID, mSecurityTokenUserId); - intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_FINGERPRINTS, mSecurityTokenFingerprints); - startActivity(intent); - } catch (PgpKeyNotFoundException e) { - Intent intent = new Intent(this, CreateKeyActivity.class); - intent.putExtra(CreateKeyActivity.EXTRA_SECURITY_TOKEN_AID, mSecurityTokenAid); - intent.putExtra(CreateKeyActivity.EXTRA_SECURITY_TOKEN_USER_ID, mSecurityTokenUserId); - intent.putExtra(CreateKeyActivity.EXTRA_SECURITY_FINGERPRINTS, mSecurityTokenFingerprints); - startActivity(intent); - } - } - - /** - * Override to use something different than Notify (UI thread) - */ - protected void onSecurityTokenError(String error) { - Notify.create(this, error, Style.WARN).show(); - } - - /** - * Override to do something when PIN is wrong, e.g., clear passphrases (UI thread) - */ - protected void onSecurityTokenPinError(String error) { - onSecurityTokenError(error); - } - - public void tagDiscovered(final Tag tag) { - // Actual NFC operations are executed in doInBackground to not block the UI thread - if (!mTagHandlingEnabled) - return; - - securityTokenDiscovered(new NfcTransport(tag)); - } - - public void usbDeviceDiscovered(final UsbDevice usbDevice) { - // Actual USB operations are executed in doInBackground to not block the UI thread - if (!mTagHandlingEnabled) - return; - - UsbManager usbManager = (UsbManager) getSystemService(Context.USB_SERVICE); - securityTokenDiscovered(new UsbTransport(usbDevice, usbManager)); - } - - public void securityTokenDiscovered(final Transport transport) { - // Actual Security Token operations are executed in doInBackground to not block the UI thread - if (!mTagHandlingEnabled) - return; - new AsyncTask() { - @Override - protected void onPreExecute() { - super.onPreExecute(); - onSecurityTokenPreExecute(); - } - - @Override - protected IOException doInBackground(Void... params) { - try { - handleSecurityToken(transport); - } catch (IOException e) { - return e; - } - - return null; - } - - @Override - protected void onPostExecute(IOException exception) { - super.onPostExecute(exception); - - if (exception != null) { - handleSecurityTokenError(exception); - return; - } - - onSecurityTokenPostExecute(); - } - }.execute(); - } - - protected void pauseTagHandling() { - mTagHandlingEnabled = false; - } - - protected void resumeTagHandling() { - mTagHandlingEnabled = true; - } - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - mTagDispatcher = TagDispatcher.get(this, this, false, false, true, false); - mUsbDispatcher = new UsbConnectionDispatcher(this, this); - - // Check whether we're recreating a previously destroyed instance - if (savedInstanceState != null) { - // Restore value of members from saved state - mTagHandlingEnabled = savedInstanceState.getBoolean(EXTRA_TAG_HANDLING_ENABLED); - } else { - mTagHandlingEnabled = true; - } - - Intent intent = getIntent(); - String action = intent.getAction(); - if (NfcAdapter.ACTION_TAG_DISCOVERED.equals(action)) { - throw new AssertionError("should not happen: NfcOperationActivity.onCreate is called instead of onNewIntent!"); - } - - } - - @Override - protected void onSaveInstanceState(Bundle outState) { - super.onSaveInstanceState(outState); - - outState.putBoolean(EXTRA_TAG_HANDLING_ENABLED, mTagHandlingEnabled); - } - - /** - * This activity is started as a singleTop activity. - * All new NFC Intents which are delivered to this activity are handled here - */ - @Override - public void onNewIntent(final Intent intent) { - mTagDispatcher.interceptIntent(intent); - } - - private void handleSecurityTokenError(IOException e) { - - if (e instanceof TagLostException) { - onSecurityTokenError(getString(R.string.security_token_error_tag_lost)); - return; - } - - if (e instanceof IsoDepNotSupportedException) { - onSecurityTokenError(getString(R.string.security_token_error_iso_dep_not_supported)); - return; - } - - short status; - if (e instanceof CardException) { - status = ((CardException) e).getResponseCode(); - } else { - status = -1; - } - - // Wrong PIN, a status of 63CX indicates X attempts remaining. - if ((status & (short) 0xFFF0) == 0x63C0) { - int tries = status & 0x000F; - // hook to do something different when PIN is wrong - onSecurityTokenPinError(getResources().getQuantityString(R.plurals.security_token_error_pin, tries, tries)); - return; - } - - // Otherwise, all status codes are fixed values. - switch (status) { - // These errors should not occur in everyday use; if they are returned, it means we - // made a mistake sending data to the token, or the token is misbehaving. - case 0x6A80: { - onSecurityTokenError(getString(R.string.security_token_error_bad_data)); - break; - } - case 0x6883: { - onSecurityTokenError(getString(R.string.security_token_error_chaining_error)); - break; - } - case 0x6B00: { - onSecurityTokenError(getString(R.string.security_token_error_header, "P1/P2")); - break; - } - case 0x6D00: { - onSecurityTokenError(getString(R.string.security_token_error_header, "INS")); - break; - } - case 0x6E00: { - onSecurityTokenError(getString(R.string.security_token_error_header, "CLA")); - break; - } - // These error conditions are more likely to be experienced by an end user. - case 0x6285: { - onSecurityTokenError(getString(R.string.security_token_error_terminated)); - break; - } - case 0x6700: { - onSecurityTokenPinError(getString(R.string.security_token_error_wrong_length)); - break; - } - case 0x6982: { - onSecurityTokenError(getString(R.string.security_token_error_security_not_satisfied)); - break; - } - case 0x6983: { - onSecurityTokenError(getString(R.string.security_token_error_authentication_blocked)); - break; - } - case 0x6985: { - onSecurityTokenError(getString(R.string.security_token_error_conditions_not_satisfied)); - break; - } - // 6A88 is "Not Found" in the spec, but Yubikey also returns 6A83 for this in some cases. - case 0x6A88: - case 0x6A83: { - onSecurityTokenError(getString(R.string.security_token_error_data_not_found)); - break; - } - // 6F00 is a JavaCard proprietary status code, SW_UNKNOWN, and usually represents an - // unhandled exception on the security token. - case 0x6F00: { - onSecurityTokenError(getString(R.string.security_token_error_unknown)); - break; - } - // 6A82 app not installed on security token! - case 0x6A82: { - if (mSecurityTokenHelper.isFidesmoToken()) { - // Check if the Fidesmo app is installed - if (isAndroidAppInstalled(FIDESMO_APP_PACKAGE)) { - promptFidesmoPgpInstall(); - } else { - promptFidesmoAppInstall(); - } - } else { // Other (possibly) compatible hardware - onSecurityTokenError(getString(R.string.security_token_error_pgp_app_not_installed)); - } - break; - } - default: { - onSecurityTokenError(getString(R.string.security_token_error, e.getMessage())); - break; - } - } - - } - - /** - * Called when the system is about to start resuming a previous activity, - * disables NFC Foreground Dispatch - */ - public void onPause() { - super.onPause(); - Log.d(Constants.TAG, "BaseNfcActivity.onPause"); - - mTagDispatcher.disableExclusiveNfc(); - } - - /** - * Called when the activity will start interacting with the user, - * enables NFC Foreground Dispatch - */ - public void onResume() { - super.onResume(); - Log.d(Constants.TAG, "BaseNfcActivity.onResume"); - mTagDispatcher.enableExclusiveNfc(); - } - - protected void obtainSecurityTokenPin(RequiredInputParcel requiredInput) { - - try { - Passphrase passphrase = PassphraseCacheService.getCachedPassphrase(this, - requiredInput.getMasterKeyId(), requiredInput.getSubKeyId()); - if (passphrase != null) { - mSecurityTokenHelper.setPin(passphrase); - return; - } - - Intent intent = new Intent(this, PassphraseDialogActivity.class); - intent.putExtra(PassphraseDialogActivity.EXTRA_REQUIRED_INPUT, - RequiredInputParcel.createRequiredPassphrase(requiredInput)); - startActivityForResult(intent, REQUEST_CODE_PIN); - } catch (PassphraseCacheService.KeyNotFoundException e) { - throw new AssertionError( - "tried to find passphrase for non-existing key. this is a programming error!"); - } - - } - - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - switch (requestCode) { - case REQUEST_CODE_PIN: { - if (resultCode != Activity.RESULT_OK) { - setResult(resultCode); - finish(); - return; - } - CryptoInputParcel input = data.getParcelableExtra(PassphraseDialogActivity.RESULT_CRYPTO_INPUT); - mSecurityTokenHelper.setPin(input.getPassphrase()); - break; - } - default: - super.onActivityResult(requestCode, resultCode, data); - } - } - - protected void handleSecurityToken(Transport transport) throws IOException { - // Don't reconnect if device was already connected - if (!(mSecurityTokenHelper.isPersistentConnectionAllowed() - && mSecurityTokenHelper.isConnected() - && mSecurityTokenHelper.getTransport().equals(transport))) { - mSecurityTokenHelper.setTransport(transport); - mSecurityTokenHelper.connectToDevice(); - } - doSecurityTokenInBackground(); - } - - public boolean isSecurityTokenConnected() { - return mSecurityTokenHelper.isConnected(); - } - - public static class IsoDepNotSupportedException extends IOException { - - public IsoDepNotSupportedException(String detailMessage) { - super(detailMessage); - } - - } - - /** - * Ask user if she wants to install PGP onto her Fidesmo token - */ - private void promptFidesmoPgpInstall() { - FidesmoPgpInstallDialog fidesmoPgpInstallDialog = new FidesmoPgpInstallDialog(); - fidesmoPgpInstallDialog.show(getSupportFragmentManager(), "fidesmoPgpInstallDialog"); - } - - /** - * Show a Dialog to the user informing that Fidesmo App must be installed and with option - * to launch the Google Play store. - */ - private void promptFidesmoAppInstall() { - FidesmoInstallDialog fidesmoInstallDialog = new FidesmoInstallDialog(); - fidesmoInstallDialog.show(getSupportFragmentManager(), "fidesmoInstallDialog"); - } - - /** - * Use the package manager to detect if an application is installed on the phone - * - * @param uri an URI identifying the application's package - * @return 'true' if the app is installed - */ - private boolean isAndroidAppInstalled(String uri) { - PackageManager mPackageManager = getPackageManager(); - boolean mAppInstalled; - try { - mPackageManager.getPackageInfo(uri, PackageManager.GET_ACTIVITIES); - mAppInstalled = true; - } catch (PackageManager.NameNotFoundException e) { - Log.e(Constants.TAG, "App not installed on Android device"); - mAppInstalled = false; - } - return mAppInstalled; - } - - @Override - protected void onStop() { - super.onStop(); - mUsbDispatcher.onStop(); - } - - @Override - protected void onStart() { - super.onStart(); - mUsbDispatcher.onStart(); - } - - public SecurityTokenHelper getSecurityTokenHelper() { - return mSecurityTokenHelper; - } - - /** - * Run Security Token routines if last used token is connected and supports - * persistent connections - */ - public void checkDeviceConnection() { - mUsbDispatcher.rescanDevices(); - } -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/UsbConnectionDispatcher.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/UsbConnectionDispatcher.java index 60fc84dba..7a8e65ae4 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/UsbConnectionDispatcher.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/UsbConnectionDispatcher.java @@ -26,7 +26,6 @@ import android.hardware.usb.UsbDevice; import android.hardware.usb.UsbManager; import org.sufficientlysecure.keychain.Constants; -import org.sufficientlysecure.keychain.securitytoken.UsbTransport; import org.sufficientlysecure.keychain.ui.UsbEventReceiverActivity; public class UsbConnectionDispatcher { @@ -34,6 +33,7 @@ public class UsbConnectionDispatcher { private OnDiscoveredUsbDeviceListener mListener; private UsbManager mUsbManager; + /** * Receives broadcast when a supported USB device get permission. */ @@ -44,7 +44,7 @@ public class UsbConnectionDispatcher { switch (action) { case UsbEventReceiverActivity.ACTION_USB_PERMISSION: { - android.hardware.usb.UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); + UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); boolean permission = intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false); if (permission) { @@ -79,7 +79,7 @@ public class UsbConnectionDispatcher { */ public void rescanDevices() { // Note: we don't check devices VID/PID because - // we check for permisssion instead. + // we check for permission instead. // We should have permission only for matching devices for (UsbDevice device : mUsbManager.getDeviceList().values()) { if (mUsbManager.hasPermission(device)) { -- cgit v1.2.3