Skip to content

Commit 58c71c1

Browse files
committed
v2.3.3
1 parent 757fa9c commit 58c71c1

File tree

12 files changed

+267
-117
lines changed

12 files changed

+267
-117
lines changed

build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,5 +22,5 @@ ext {
2222
compileSdkVersion = 28
2323
hwSdkIncludeAsSubmodule = false
2424
hwSdkVersionCode = 8
25-
hwSdkVersionName = '2.2.3'
25+
hwSdkVersionName = '2.3.3'
2626
}
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
package de.cotech.hw.fido.internal;
2+
3+
import android.content.Context;
4+
import android.graphics.drawable.Animatable;
5+
import android.graphics.drawable.Drawable;
6+
import android.os.Build;
7+
import android.os.Handler;
8+
import android.os.Looper;
9+
import android.widget.ImageView;
10+
import androidx.annotation.NonNull;
11+
import androidx.annotation.RestrictTo;
12+
import androidx.core.view.ViewCompat;
13+
import androidx.vectordrawable.graphics.drawable.Animatable2Compat;
14+
import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat;
15+
16+
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
17+
public class AnimatedVectorDrawableHelper {
18+
19+
public static void startAnimation(Context context, ImageView imageView, int resId) {
20+
startAnimation(context, imageView, resId, null);
21+
}
22+
23+
public static void startAnimation(Context context, ImageView imageView, int resId, Animatable2Compat.AnimationCallback animationCallback) {
24+
if (Build.VERSION.SDK_INT <= 24) {
25+
AnimatedVectorDrawableCompat avdCompat = setAndStartAnimatedVectorDrawableSdk24(context, imageView, resId);
26+
if (animationCallback != null) {
27+
avdCompat.registerAnimationCallback(animationCallback);
28+
}
29+
} else {
30+
if (animationCallback != null) {
31+
AnimatedVectorDrawableCompat.registerAnimationCallback(imageView.getDrawable(), animationCallback);
32+
}
33+
Animatable animatable = (Animatable) imageView.getDrawable();
34+
animatable.start();
35+
}
36+
}
37+
38+
public static void startAndLoopAnimation(Context context, ImageView imageView, int resId) {
39+
Animatable2Compat.AnimationCallback animationCallback = new Animatable2Compat.AnimationCallback() {
40+
@NonNull
41+
private final Handler fHandler = new Handler(Looper.getMainLooper());
42+
43+
@Override
44+
public void onAnimationEnd(@NonNull Drawable drawable) {
45+
if (!ViewCompat.isAttachedToWindow(imageView)) {
46+
return;
47+
}
48+
49+
fHandler.post(() -> {
50+
if (Build.VERSION.SDK_INT <= 24) {
51+
AnimatedVectorDrawableCompat avdCompat = setAndStartAnimatedVectorDrawableSdk24(context, imageView, resId);
52+
avdCompat.registerAnimationCallback(this);
53+
} else {
54+
((Animatable) drawable).start();
55+
}
56+
});
57+
}
58+
};
59+
60+
if (Build.VERSION.SDK_INT <= 24) {
61+
AnimatedVectorDrawableCompat avdCompat = setAndStartAnimatedVectorDrawableSdk24(context, imageView, resId);
62+
avdCompat.registerAnimationCallback(animationCallback);
63+
} else {
64+
imageView.setImageResource(resId);
65+
AnimatedVectorDrawableCompat.registerAnimationCallback(imageView.getDrawable(), animationCallback);
66+
Animatable animatable = (Animatable) imageView.getDrawable();
67+
animatable.start();
68+
}
69+
}
70+
71+
private static AnimatedVectorDrawableCompat setAndStartAnimatedVectorDrawableSdk24(Context context, ImageView imageView, int resId) {
72+
AnimatedVectorDrawableCompat avdCompat = AnimatedVectorDrawableCompat.create(context, resId);
73+
74+
// on SDK <= 24, the alphaFill values are not resetted properly to their initial state
75+
// The states of AnimatedVectorDrawables are stored centrally per resource.
76+
// Thus, making the drawable mutate allows it to have a completely new state
77+
avdCompat.mutate();
78+
79+
imageView.setImageDrawable(avdCompat);
80+
avdCompat.start();
81+
82+
return avdCompat;
83+
}
84+
}

hwsecurity-fido/src/main/java/de/cotech/hw/fido/internal/FidoU2fAppletConnection.java

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -149,10 +149,10 @@ private byte[] selectFileOrFail(byte[] fileAid) throws IOException {
149149
private ResponseApdu communicate(CommandApdu commandApdu) throws IOException {
150150
ResponseApdu lastResponse;
151151

152-
lastResponse = transceiveWithChaining(commandApdu);
152+
lastResponse = sendWithChaining(commandApdu);
153153
if (lastResponse.getSw1() == RESPONSE_SW1_INCORRECT_LENGTH && lastResponse.getSw2() != 0) {
154154
commandApdu = commandApdu.withNe(lastResponse.getSw2());
155-
lastResponse = transceiveWithChaining(commandApdu);
155+
lastResponse = sendWithChaining(commandApdu);
156156
}
157157
lastResponse = readChainedResponseIfAvailable(lastResponse);
158158

@@ -188,9 +188,8 @@ public ResponseApdu communicateOrThrow(CommandApdu commandApdu) throws IOExcepti
188188

189189
// ISO/IEC 7816-4
190190
@NonNull
191-
private ResponseApdu transceiveWithChaining(CommandApdu commandApdu) throws IOException {
192-
/* If extended length is supported, we use an Lc of 65536 to enforce extended length
193-
* APDUs for the register and authenticate commands.
191+
private ResponseApdu sendWithChaining(CommandApdu commandApdu) throws IOException {
192+
/* We use an Lc of 65536 to enforce extended length APDUs for the register and authenticate commands.
194193
*
195194
* This forces APDU case 4e in CommandApdu:
196195
* apdu[apdu.length - 2] = 0;
@@ -199,16 +198,21 @@ private ResponseApdu transceiveWithChaining(CommandApdu commandApdu) throws IOEx
199198
* see also:
200199
* https://fidoalliance.org/specs/fido-u2f-v1.2-ps-20170411/fido-u2f-hid-protocol-v1.2-ps-20170411.html
201200
* https://docs.oracle.com/javacard/3.0.5/prognotes/extended_apdu_format.htm
201+
*
202+
* Note:
203+
* We do *not* check for `transport.isExtendedLengthSupported()` here! There are phones (including
204+
* the Nexus 5X) that return "false" to this, but some Security Keys (like Yubikey Neo) still
205+
* require us to send extended APDUs. So what we do is, send an extended APDU, and if that doesn't
206+
* work, fall back to a short one.
202207
*/
203-
if (transport.isExtendedLengthSupported() && commandFactory.isSuitableForExtendedApdu(commandApdu)) {
208+
if (commandFactory.isSuitableForExtendedApdu(commandApdu)) {
204209
CommandApdu extendedLengthApdu = commandApdu.withNe(65536);
205210
ResponseApdu response = transport.transceive(extendedLengthApdu);
206-
if (response.getSw() == WrongRequestLengthException.SW_WRONG_REQUEST_LENGTH) {
211+
if (response.getSw() != WrongRequestLengthException.SW_WRONG_REQUEST_LENGTH) {
212+
return response;
213+
} else {
207214
Timber.d("Received WRONG_REQUEST_LENGTH error. Retrying with compatibility workaround");
208-
CommandApdu shortApdu = commandFactory.createShortApdu(commandApdu);
209-
return transport.transceive(shortApdu);
210215
}
211-
return response;
212216
}
213217

214218
if (commandFactory.isSuitableForShortApdu(commandApdu)) {

hwsecurity-fido/src/main/java/de/cotech/hw/fido/ui/FidoDialogFragment.java

Lines changed: 20 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@
9595
import de.cotech.hw.fido.FidoSecurityKeyConnectionMode;
9696
import de.cotech.hw.fido.R;
9797
import de.cotech.hw.fido.exceptions.FidoWrongKeyHandleException;
98+
import de.cotech.hw.fido.internal.AnimatedVectorDrawableHelper;
9899
import de.cotech.hw.util.NfcStatusObserver;
99100
import de.cotech.sweetspot.NfcSweetspotData;
100101
import timber.log.Timber;
@@ -234,12 +235,14 @@ public Dialog onCreateDialog(Bundle savedInstanceState) {
234235
coordinator = bottomSheetDialog.findViewById(com.google.android.material.R.id.coordinator);
235236
bottomSheet = bottomSheetDialog.findViewById(com.google.android.material.R.id.design_bottom_sheet);
236237

237-
// never just "peek", always fully expand the bottom sheet
238-
if (bottomSheet != null) {
239-
BottomSheetBehavior bottomSheetBehavior = BottomSheetBehavior.from(bottomSheet);
240-
bottomSheetBehavior.setSkipCollapsed(true);
241-
bottomSheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED);
238+
if (bottomSheet == null) {
239+
throw new IllegalStateException("bottomSheet is null");
242240
}
241+
242+
// never just "peek", always fully expand the bottom sheet
243+
BottomSheetBehavior bottomSheetBehavior = BottomSheetBehavior.from(bottomSheet);
244+
bottomSheetBehavior.setSkipCollapsed(true);
245+
bottomSheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED);
243246
});
244247

245248
return dialog;
@@ -302,11 +305,6 @@ private void initTimeout(long timeoutSeconds) {
302305
final Handler handler = new Handler();
303306
handler.postDelayed(() -> {
304307
Timber.d("Timeout after %s seconds.", timeoutSeconds);
305-
if (fidoAuthenticateRequest != null) {
306-
fidoAuthenticateCallback.onFidoAuthenticateTimeout(fidoAuthenticateRequest);
307-
} else if (fidoRegisterRequest != null) {
308-
fidoRegisterCallback.onFidoRegisterTimeout(fidoRegisterRequest);
309-
}
310308

311309
textError.setText(R.string.hwsecurity_error_timeout);
312310
gotoState(State.ERROR);
@@ -315,6 +313,12 @@ private void initTimeout(long timeoutSeconds) {
315313
return;
316314
}
317315
dismiss();
316+
317+
if (fidoAuthenticateRequest != null) {
318+
fidoAuthenticateCallback.onFidoAuthenticateTimeout(fidoAuthenticateRequest);
319+
} else if (fidoRegisterRequest != null) {
320+
fidoRegisterCallback.onFidoRegisterTimeout(fidoRegisterRequest);
321+
}
318322
}, TIME_DELAYED_STATE_CHANGE);
319323
}, timeoutSeconds * 1000);
320324
}
@@ -630,14 +634,7 @@ public void onAnimationEnd(Drawable drawable) {
630634
}
631635
};
632636

633-
if (Build.VERSION.SDK_INT <= 24) {
634-
AnimatedVectorDrawableCompat avdCompat = setAndStartAnimatedVectorDrawableSdk24(imageNfcFullscreen, R.drawable.nfc_handling);
635-
avdCompat.registerAnimationCallback(animationCallback);
636-
} else {
637-
AnimatedVectorDrawableCompat.registerAnimationCallback(imageNfcFullscreen.getDrawable(), animationCallback);
638-
Animatable animatable = (Animatable) imageNfcFullscreen.getDrawable();
639-
animatable.start();
640-
}
637+
AnimatedVectorDrawableHelper.startAnimation(getActivity(), imageNfcFullscreen, R.drawable.nfc_handling, animationCallback);
641638
}
642639

643640
private void fadeToNfcSweetSpot() {
@@ -713,7 +710,7 @@ private void showNfcSweetSpot() {
713710
textError.setVisibility(View.GONE);
714711
});
715712

716-
startAndLoopAnimation(sweetspotIndicator, R.drawable.nfc_sweet_spot_a);
713+
AnimatedVectorDrawableHelper.startAndLoopAnimation(getActivity(), sweetspotIndicator, R.drawable.nfc_sweet_spot_a);
717714
}
718715

719716
private void animateSelectUsb() {
@@ -726,7 +723,7 @@ public void onTransitionStart(@NonNull Transition transition) {
726723

727724
@Override
728725
public void onTransitionEnd(@NonNull Transition transition) {
729-
startAndLoopAnimation(imageUsb, R.drawable.usb_handling_a);
726+
AnimatedVectorDrawableHelper.startAndLoopAnimation(getActivity(), imageUsb, R.drawable.usb_handling_a);
730727
}
731728

732729
@Override
@@ -764,7 +761,7 @@ public void onTransitionStart(@NonNull Transition transition) {
764761

765762
@Override
766763
public void onTransitionEnd(@NonNull Transition transition) {
767-
startAndLoopAnimation(imageUsb, R.drawable.usb_handling_b);
764+
AnimatedVectorDrawableHelper.startAndLoopAnimation(getActivity(), imageUsb, R.drawable.usb_handling_b);
768765
}
769766

770767
@Override
@@ -795,7 +792,7 @@ public void onTransitionResume(@NonNull Transition transition) {
795792
private void animateUsbPressButton() {
796793
TransitionManager.beginDelayedTransition(innerBottomSheet);
797794
textTitle.setText(R.string.hwsecurity_title_usb_button);
798-
startAndLoopAnimation(imageUsb, R.drawable.usb_handling_b);
795+
AnimatedVectorDrawableHelper.startAndLoopAnimation(getActivity(), imageUsb, R.drawable.usb_handling_b);
799796
}
800797

801798
private void animateError() {
@@ -813,59 +810,7 @@ private void animateError() {
813810
textError.setVisibility(View.VISIBLE);
814811
imageError.setVisibility(View.VISIBLE);
815812

816-
if (Build.VERSION.SDK_INT <= 24) {
817-
setAndStartAnimatedVectorDrawableSdk24(imageError, R.drawable.error);
818-
} else {
819-
Animatable animatable = (Animatable) imageError.getDrawable();
820-
animatable.start();
821-
}
822-
}
823-
824-
private void startAndLoopAnimation(ImageView imageView, int resId) {
825-
Animatable2Compat.AnimationCallback animationCallback = new Animatable2Compat.AnimationCallback() {
826-
@NonNull
827-
private final Handler fHandler = new Handler(Looper.getMainLooper());
828-
829-
@Override
830-
public void onAnimationEnd(@NonNull Drawable drawable) {
831-
if (!ViewCompat.isAttachedToWindow(imageView)) {
832-
return;
833-
}
834-
835-
fHandler.post(() -> {
836-
if (Build.VERSION.SDK_INT <= 24) {
837-
AnimatedVectorDrawableCompat avdCompat = setAndStartAnimatedVectorDrawableSdk24(imageView, resId);
838-
avdCompat.registerAnimationCallback(this);
839-
} else {
840-
((Animatable) drawable).start();
841-
}
842-
});
843-
}
844-
};
845-
846-
if (Build.VERSION.SDK_INT <= 24) {
847-
AnimatedVectorDrawableCompat avdCompat = setAndStartAnimatedVectorDrawableSdk24(imageView, resId);
848-
avdCompat.registerAnimationCallback(animationCallback);
849-
} else {
850-
imageView.setImageResource(resId);
851-
AnimatedVectorDrawableCompat.registerAnimationCallback(imageView.getDrawable(), animationCallback);
852-
Animatable animatable = (Animatable) imageView.getDrawable();
853-
animatable.start();
854-
}
855-
}
856-
857-
private AnimatedVectorDrawableCompat setAndStartAnimatedVectorDrawableSdk24(ImageView imageView, int resId) {
858-
AnimatedVectorDrawableCompat avdCompat = AnimatedVectorDrawableCompat.create(getContext(), resId);
859-
860-
// on SDK <= 24, the alphaFill values are not resetted properly to their initial state
861-
// The states of AnimatedVectorDrawables are stored centrally per resource.
862-
// Thus, making the drawable mutate allows it to have a completely new state
863-
avdCompat.mutate();
864-
865-
imageView.setImageDrawable(avdCompat);
866-
avdCompat.start();
867-
868-
return avdCompat;
813+
AnimatedVectorDrawableHelper.startAnimation(getActivity(), imageError, R.drawable.error);
869814
}
870815

871816
@UiThread

hwsecurity/src/main/java/de/cotech/hw/SecurityKeyAuthenticator.java

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -76,8 +76,5 @@ public interface SecurityKeyAuthenticator {
7676
* @param hashAlgo the algorithm to digest the challenge with before signing
7777
*/
7878
@WorkerThread
79-
default byte[] authenticateWithDigest(byte[] challenge, String hashAlgo) throws IOException, NoSuchAlgorithmException {
80-
byte[] digest = MessageDigest.getInstance(hashAlgo).digest(challenge);
81-
return authenticatePresignedDigest(digest, hashAlgo);
82-
}
79+
byte[] authenticateWithDigest(byte[] challenge, String hashAlgo) throws IOException, NoSuchAlgorithmException;
8380
}

hwsecurity/src/main/java/de/cotech/hw/SecurityKeyManager.java

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -272,7 +272,7 @@ public void onActivityResumed(Activity activity) {
272272
if (activeNfcDispatcher != null) {
273273
activeNfcDispatcher.onResume();
274274
}
275-
postTriggerCallacksActively();
275+
postTriggerCallbacksActively();
276276
}
277277

278278
@Override
@@ -358,7 +358,7 @@ private void transportConnectAndDeliverOrPostponeOrFail(Transport transport) {
358358
}
359359

360360
@AnyThread
361-
private void postTriggerCallacksActively() {
361+
private void postTriggerCallbacksActively() {
362362
if (callbackDedup.getAndSet(true)) {
363363
return;
364364
}
@@ -406,7 +406,7 @@ public <T extends SecurityKey> void registerCallback(SecurityKeyConnectionMode<T
406406
lifecycleOwner.getLifecycle().addObserver(registeredConnectionMode);
407407
registeredCallbacks.add(0, registeredConnectionMode);
408408

409-
postTriggerCallacksActively();
409+
postTriggerCallbacksActively();
410410
}
411411

412412
/**
@@ -439,6 +439,11 @@ public <T> List<T> getConnectedPersistentSecurityKeys(Class<T> clazz) {
439439
return Collections.unmodifiableList(result);
440440
}
441441

442+
@AnyThread
443+
public void rediscoverConnectedSecurityKeys() {
444+
postTriggerCallbacksActively();
445+
}
446+
442447
/**
443448
* Returns true if USB host mode is available.
444449
*

hwsecurity/src/main/java/de/cotech/hw/internal/transport/SecurityKeyInfo.java

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,14 @@
2525
package de.cotech.hw.internal.transport;
2626

2727

28+
import android.os.Parcelable;
29+
30+
import androidx.annotation.Nullable;
31+
import androidx.annotation.RestrictTo;
32+
import androidx.annotation.RestrictTo.Scope;
33+
34+
import com.google.auto.value.AutoValue;
35+
2836
import java.util.ArrayList;
2937
import java.util.Arrays;
3038
import java.util.Collections;
@@ -34,13 +42,6 @@
3442
import java.util.regex.Matcher;
3543
import java.util.regex.Pattern;
3644

37-
import android.os.Parcelable;
38-
39-
import androidx.annotation.Nullable;
40-
import androidx.annotation.RestrictTo;
41-
import androidx.annotation.RestrictTo.Scope;
42-
import com.google.auto.value.AutoValue;
43-
4445

4546
@AutoValue
4647
@RestrictTo(Scope.LIBRARY_GROUP)
@@ -140,9 +141,4 @@ public static Version parseGnukVersionString(String serialNo) {
140141
return Version.create(matcher.group(1));
141142
}
142143

143-
// public Version getOpenPgpVersion() {
144-
// OpenPgpAid aid = OpenPgpAid.create(getAid());
145-
// return aid.getOpenPgpSpecVersion();
146-
// }
147-
148144
}

0 commit comments

Comments
 (0)