From ad0599352bdc764e3609532c195657cf7734af1a Mon Sep 17 00:00:00 2001 From: Jason Date: Sun, 26 May 2024 18:09:38 +0800 Subject: [PATCH] + 5.2.0 Alipay Taobao support switch payment method UnionPay support english environment --- app/build.gradle | 2 +- .../plugin/impl/alipay/AlipayBasePlugin.java | 77 ++++++++++++----- .../plugin/impl/taobao/TaobaoBasePlugin.java | 76 +++++++++++++---- .../impl/unionpay/UnionPayBasePlugin.java | 29 +++---- .../fingerprint/util/ViewUtils.java | 84 +++++++++++++++++++ .../fingerprint/view/AlipayPayView.java | 2 - .../fingerprint/view/DialogUtils.java | 18 ++++ .../fingerprint/view/SettingsView.java | 2 +- app/src/main/res/values/ids.xml | 1 + 9 files changed, 229 insertions(+), 62 deletions(-) create mode 100644 app/src/main/java/com/surcumference/fingerprint/view/DialogUtils.java diff --git a/app/build.gradle b/app/build.gradle index 4e05d99..d4e6966 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -8,7 +8,7 @@ android { minSdkVersion 14 targetSdkVersion 33 versionCode 32 - versionName "5.1.0" + versionName "5.2.0" buildConfigField "String", "APP_PRODUCT_NAME", "\"FingerprintPay\"" } diff --git a/app/src/main/java/com/surcumference/fingerprint/plugin/impl/alipay/AlipayBasePlugin.java b/app/src/main/java/com/surcumference/fingerprint/plugin/impl/alipay/AlipayBasePlugin.java index 1e604b3..7978ede 100644 --- a/app/src/main/java/com/surcumference/fingerprint/plugin/impl/alipay/AlipayBasePlugin.java +++ b/app/src/main/java/com/surcumference/fingerprint/plugin/impl/alipay/AlipayBasePlugin.java @@ -47,6 +47,7 @@ import com.surcumference.fingerprint.util.drawable.XDrawable; import com.surcumference.fingerprint.util.log.L; import com.surcumference.fingerprint.view.AlipayPayView; +import com.surcumference.fingerprint.view.DialogUtils; import com.surcumference.fingerprint.view.SettingsView; import com.wei.android.lib.fingerprintidentify.bean.FingerprintIdentifyFailInfo; @@ -317,11 +318,7 @@ public boolean showFingerPrintDialog(final Activity activity) { onCompleteRunnable.run(); }); }).withOnCancelButtonClickListener(target -> { - AlertDialog dialog = target.getDialog(); - if (dialog != null) { - dialog.dismiss(); - } - activity.onBackPressed(); + DialogUtils.dismiss(target.getDialog()); }).withOnDismissListener(v -> { XBiometricIdentify fingerprintIdentify = mFingerprintIdentify; if (fingerprintIdentify != null) { @@ -337,11 +334,51 @@ public boolean showFingerPrintDialog(final Activity activity) { return true; } + private void setupPaymentItemOnClickListener(ViewGroup rootView) { + List paymentMethodsViewList = new ArrayList<>(); + ViewUtils.getChildViewsByRegex(rootView, ".*选中,.+", paymentMethodsViewList); + long paymentMethodsViewListSize = paymentMethodsViewList.size(); + for (int i = 0; i < paymentMethodsViewListSize; i++) { + if (i == paymentMethodsViewListSize - 1) { + // 最后一个 + continue; + } + View paymentMethodView = paymentMethodsViewList.get(i); + L.d("paymentMethodView", ViewUtils.getViewInfo(paymentMethodView)); + // 只取第一次, 防止多次调用造成出错 + View.OnClickListener originPaymentMethodListener = (View.OnClickListener) paymentMethodView.getTag(R.id.alipay_payment_method_item_click_listener); + if (originPaymentMethodListener == null) { + paymentMethodView.setTag(R.id.alipay_payment_method_item_click_listener, ViewUtils.getOnClickListener(paymentMethodView)); + } + paymentMethodView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + try { + AlertDialog dialog = mFingerPrintAlertDialog; + if (dialog == null) { + return; + } + if (!dialog.isShowing()) { + dialog.show(); + } + } finally { + View.OnClickListener originPaymentMethodListener = (View.OnClickListener) v.getTag(R.id.alipay_payment_method_item_click_listener); + if (originPaymentMethodListener != null && originPaymentMethodListener != this) { + originPaymentMethodListener.onClick(v); + } + } + } + }); + } + } + private void reEnteredPayDialogSolution(Activity activity) { int versionCode = getVersionCode(activity); if (versionCode < 1261 /** 10.5.96.8000 */) { return; } + ViewGroup rootView = (ViewGroup)activity.getWindow().getDecorView(); + setupPaymentItemOnClickListener(rootView); // 在10s内寻找密码框 ActivityViewObserver activityViewObserver = new ActivityViewObserver(activity); activityViewObserver.setActivityViewFinder(outViewList -> { @@ -349,7 +386,7 @@ private void reEnteredPayDialogSolution(Activity activity) { if (view != null) { outViewList.add(view); } - View shortPwdView = ViewUtils.findViewByText(activity.getWindow().getDecorView(), "密码共6位,已输入0位"); + View shortPwdView = ViewUtils.findViewByText(rootView, "密码共6位,已输入0位"); if (ViewUtils.isShown(shortPwdView)) { outViewList.add(shortPwdView); } @@ -367,6 +404,10 @@ public void onFocusChange(View v, boolean hasFocus) { try { // 如果失去焦点并且获得新焦点, 通常是切换支付方式, 尝试重新触发识别 if (!lastFocusState && hasFocus) { + // 如果支付方式点了第一项, 页面会刷新, 需要重建OnclickListener + Task.onMain(666, () -> { + setupPaymentItemOnClickListener(rootView); + }); AlertDialog dialog = mFingerPrintAlertDialog; if (dialog == null) { return; @@ -378,7 +419,6 @@ public void onFocusChange(View v, boolean hasFocus) { } finally { lastFocusState = hasFocus; } - } }); @@ -687,13 +727,7 @@ private View findConfirmPasswordBtn(Activity activity) { } ViewGroup rootView = (ViewGroup) activity.getWindow().getDecorView(); List outList = new ArrayList<>(); - ViewUtils.getChildViews(rootView, "付款", outList); - if (outList.isEmpty()) { - ViewUtils.getChildViews(rootView, "Pay", outList); - } - if (outList.isEmpty()) { - ViewUtils.getChildViews(rootView, "确定", outList); - } + ViewUtils.getChildViewsByRegex(rootView, "确定|確定|OK|确认|付款|確認|Pay", outList); int versionCode = getVersionCode(activity); for (View view : outList) { if (view.getId() != -1) { @@ -702,20 +736,21 @@ private View findConfirmPasswordBtn(Activity activity) { if (!view.isShown()) { continue; } - if (versionCode >= 1261 /** 10.5.96.8000 */) { - return view; + // 跳过键盘上的OK + if (view.getParent().toString().contains(":id/key_enter")) { + continue; } - return (View) view.getParent(); + if (versionCode < 1261 /** 10.5.96.8000 */) { + return (View) view.getParent(); + } + return view; } return null; } private void hidePreviousPayDialog() { - AlertDialog dialog = mFingerPrintAlertDialog; L.d("hidePreviousPayDialog", mFingerPrintAlertDialog); - if (dialog != null) { - dialog.dismiss(); - } + DialogUtils.dismiss(mFingerPrintAlertDialog); mFingerPrintAlertDialog = null; } } diff --git a/app/src/main/java/com/surcumference/fingerprint/plugin/impl/taobao/TaobaoBasePlugin.java b/app/src/main/java/com/surcumference/fingerprint/plugin/impl/taobao/TaobaoBasePlugin.java index 64ad9f4..e2d166f 100644 --- a/app/src/main/java/com/surcumference/fingerprint/plugin/impl/taobao/TaobaoBasePlugin.java +++ b/app/src/main/java/com/surcumference/fingerprint/plugin/impl/taobao/TaobaoBasePlugin.java @@ -41,6 +41,7 @@ import com.surcumference.fingerprint.util.drawable.XDrawable; import com.surcumference.fingerprint.util.log.L; import com.surcumference.fingerprint.view.AlipayPayView; +import com.surcumference.fingerprint.view.DialogUtils; import com.surcumference.fingerprint.view.SettingsView; import com.wei.android.lib.fingerprintidentify.bean.FingerprintIdentifyFailInfo; @@ -287,11 +288,7 @@ public void showFingerPrintDialog(final Activity activity) { onCompleteRunnable.run(); }); }).withOnCancelButtonClickListener(target -> { - AlertDialog dialog = target.getDialog(); - if (dialog != null) { - dialog.dismiss(); - } - activity.onBackPressed(); + DialogUtils.dismiss(target.getDialog()); }).withOnDismissListener(v -> { XBiometricIdentify fingerprintIdentify = mFingerprintIdentify; if (fingerprintIdentify != null) { @@ -306,11 +303,51 @@ public void showFingerPrintDialog(final Activity activity) { } } + private void setupPaymentItemOnClickListener(ViewGroup rootView) { + List paymentMethodsViewList = new ArrayList<>(); + ViewUtils.getChildViewsByRegex(rootView, ".*选中,.+", paymentMethodsViewList); + long paymentMethodsViewListSize = paymentMethodsViewList.size(); + for (int i = 0; i < paymentMethodsViewListSize; i++) { + if (i == paymentMethodsViewListSize - 1) { + // 最后一个 + continue; + } + View paymentMethodView = paymentMethodsViewList.get(i); + L.d("paymentMethodView", ViewUtils.getViewInfo(paymentMethodView)); + // 只取第一次, 防止多次调用造成出错 + View.OnClickListener originPaymentMethodListener = (View.OnClickListener) paymentMethodView.getTag(R.id.alipay_payment_method_item_click_listener); + if (originPaymentMethodListener == null) { + paymentMethodView.setTag(R.id.alipay_payment_method_item_click_listener, ViewUtils.getOnClickListener(paymentMethodView)); + } + paymentMethodView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + try { + AlertDialog dialog = mFingerPrintAlertDialog; + if (dialog == null) { + return; + } + if (!dialog.isShowing()) { + dialog.show(); + } + } finally { + View.OnClickListener originPaymentMethodListener = (View.OnClickListener) v.getTag(R.id.alipay_payment_method_item_click_listener); + if (originPaymentMethodListener != null && originPaymentMethodListener != this) { + originPaymentMethodListener.onClick(v); + } + } + } + }); + } + } + private void reEnteredPayDialogSolution(Activity activity) { int versionCode = getVersionCode(activity); if (versionCode < 643 /** 10.36.10 */) { return; } + ViewGroup rootView = (ViewGroup)activity.getWindow().getDecorView(); + setupPaymentItemOnClickListener(rootView); // 在10s内寻找密码框 ActivityViewObserver activityViewObserver = new ActivityViewObserver(activity); activityViewObserver.setActivityViewFinder(outViewList -> { @@ -318,7 +355,7 @@ private void reEnteredPayDialogSolution(Activity activity) { if (view != null) { outViewList.add(view); } - View shortPwdView = ViewUtils.findViewByText(activity.getWindow().getDecorView(), "密码共6位,已输入0位"); + View shortPwdView = ViewUtils.findViewByText(rootView, "密码共6位,已输入0位"); if (ViewUtils.isShown(shortPwdView)) { outViewList.add(shortPwdView); } @@ -336,6 +373,10 @@ public void onFocusChange(View v, boolean hasFocus) { try { // 如果失去焦点并且获得新焦点, 通常是切换支付方式, 尝试重新触发识别 if (!lastFocusState && hasFocus) { + // 如果支付方式点了第一项, 页面会刷新, 需要重建OnclickListener + Task.onMain(666, () -> { + setupPaymentItemOnClickListener(rootView); + }); AlertDialog dialog = mFingerPrintAlertDialog; if (dialog == null) { return; @@ -582,18 +623,10 @@ private View findConfirmPasswordBtn(Activity activity) { } return okView; } - ViewGroup viewGroup = (ViewGroup)activity.getWindow().getDecorView(); + ViewGroup rootView = (ViewGroup)activity.getWindow().getDecorView(); List outList = new ArrayList<>(); - ViewUtils.getChildViews(viewGroup, "确认", outList); - if (outList.isEmpty()) { - ViewUtils.getChildViews(viewGroup, "付款", outList); - } - if (outList.isEmpty()) { - ViewUtils.getChildViews(viewGroup, "確認", outList); - } - if (outList.isEmpty()) { - ViewUtils.getChildViews(viewGroup, "Pay", outList); - } + ViewUtils.getChildViewsByRegex(rootView, "确定|確定|OK|确认|付款|確認|Pay", outList); + int versionCode = getVersionCode(activity); for (View view : outList) { if (view.getId() != -1) { continue; @@ -601,7 +634,14 @@ private View findConfirmPasswordBtn(Activity activity) { if (!view.isShown()) { continue; } - return (View) view.getParent(); + // 跳过键盘上的OK + if (view.getParent().toString().contains(":id/key_enter")) { + continue; + } + if (versionCode < 643 /** 10.36.10 */) { + return (View) view.getParent(); + } + return view; } return null; } diff --git a/app/src/main/java/com/surcumference/fingerprint/plugin/impl/unionpay/UnionPayBasePlugin.java b/app/src/main/java/com/surcumference/fingerprint/plugin/impl/unionpay/UnionPayBasePlugin.java index 143bbd5..74af553 100644 --- a/app/src/main/java/com/surcumference/fingerprint/plugin/impl/unionpay/UnionPayBasePlugin.java +++ b/app/src/main/java/com/surcumference/fingerprint/plugin/impl/unionpay/UnionPayBasePlugin.java @@ -46,6 +46,7 @@ import com.surcumference.fingerprint.util.XBiometricIdentify; import com.surcumference.fingerprint.util.log.L; import com.surcumference.fingerprint.view.AlipayPayView; +import com.surcumference.fingerprint.view.DialogUtils; import com.surcumference.fingerprint.view.SettingsView; import com.wei.android.lib.fingerprintidentify.bean.FingerprintIdentifyFailInfo; @@ -123,7 +124,7 @@ public synchronized void showFingerPrintDialog(@Nullable Activity activity, View ViewTreeObserver.OnGlobalLayoutListener paymentMethodListener = new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { - View selectPaymentMethodView = ViewUtils.findViewByText(rootView, "选择付款方式"); + View selectPaymentMethodView = ViewUtils.findViewByText(rootView, "选择付款方式", "Select payment method"); L.d("selectPaymentMethodView", ViewUtils.getViewInfo(selectPaymentMethodView)); if (selectPaymentMethodView == null) { showFingerPrintDialog(activity, targetView); @@ -153,7 +154,7 @@ public void onGlobalLayout() { View payRootLayout = ViewUtils.findViewByName(rootView, PACKAGE_NAME_UNIONPAY, "fl_container"); L.d("payRootLayout", payRootLayout); if (payRootLayout == null) { - View payTitleTextView = ViewUtils.findViewByText(rootView, "请输入支付密码"); + View payTitleTextView = ViewUtils.findViewByText(rootView, "请输入支付密码", "Input payment password"); payRootLayout = (View)payTitleTextView.getParent().getParent().getParent(); dialogMode = true; } @@ -161,11 +162,8 @@ public void onGlobalLayout() { boolean finalDialogMode = dialogMode; AlipayPayView payView = new AlipayPayView(context) .withOnCancelButtonClickListener(target -> { - AlertDialog dialog = target.getDialog(); - if (dialog != null) { - dialog.dismiss(); - } - if (finalDialogMode) { + DialogUtils.dismiss(target.getDialog()); + if (finalDialogMode) { Task.onBackground(100, () -> { Task.onMain(500, () -> { //窗口消失了 @@ -247,14 +245,14 @@ public void onGlobalLayout() { } } - private void retryWatchPayViewIfPossible(Activity activity, View rootView, int countDown, int delayMsec) { - View payTitleTextView = ViewUtils.findViewByText(rootView, "请输入支付密码"); + private void retryWatchPayViewIfPossible(Activity activity, View rootView, int countDown, int delayMS) { + View payTitleTextView = ViewUtils.findViewByText(rootView, "请输入支付密码", "Input payment password"); if (payTitleTextView == null || !(ViewUtils.isViewVisibleInScreen(payTitleTextView) && ViewUtils.isShownInScreen(payTitleTextView))) { watchPayView(activity); } if (countDown > 0) { - Task.onMain(delayMsec, () -> retryWatchPayViewIfPossible(activity, rootView, countDown - 1, delayMsec)); + Task.onMain(delayMS, () -> retryWatchPayViewIfPossible(activity, rootView, countDown - 1, delayMS)); } } @@ -380,7 +378,7 @@ public void setMockCurrentUser(boolean mock) { private void watchPayView(Activity activity) { ActivityViewObserver activityViewObserver = new ActivityViewObserver(activity); - activityViewObserver.setViewIdentifyText("请输入支付密码"); + activityViewObserver.setViewIdentifyText("请输入支付密码", "Input payment password"); activityViewObserver.setWatchActivityViewOnly(true); ActivityViewObserverHolder.start(ActivityViewObserverHolder.Key.UnionPayPasswordView, activityViewObserver, 100, new ActivityViewObserver.IActivityViewListener() { @@ -558,15 +556,8 @@ protected void doSettingsMenuInject(final Activity activity) { } private void hidePreviousPayDialog() { - AlertDialog dialog = mFingerPrintAlertDialog; L.d("hidePreviousPayDialog", mFingerPrintAlertDialog); - if (dialog != null) { - try { - dialog.dismiss(); - } catch (IllegalArgumentException e) { - //for java.lang.IllegalArgumentException: View=DecorView@4eafdfb[UPActivityPayPasswordSet] not attached to window manager - } - } + DialogUtils.dismiss(mFingerPrintAlertDialog); mFingerPrintAlertDialog = null; } } diff --git a/app/src/main/java/com/surcumference/fingerprint/util/ViewUtils.java b/app/src/main/java/com/surcumference/fingerprint/util/ViewUtils.java index ef90f0d..ac584a7 100644 --- a/app/src/main/java/com/surcumference/fingerprint/util/ViewUtils.java +++ b/app/src/main/java/com/surcumference/fingerprint/util/ViewUtils.java @@ -330,6 +330,35 @@ public static void getChildViews(ViewGroup parent, String text, List outLi } } + public static void getChildViewsByRegex(ViewGroup parent, String regex, List outList) { + for (int i = parent.getChildCount() - 1; i >= 0; i--) { + final View child = parent.getChildAt(i); + if (child == null) { + continue; + } + if (child instanceof EditText) { + if (String.valueOf(((TextView) child).getText()).matches(regex)) { + outList.add(child); + } else if (regex.equals(String.valueOf(((EditText) child).getHint()))) { + outList.add(child); + } + } else if (child instanceof TextView) { + if (String.valueOf(((TextView) child).getText()).matches(regex)) { + outList.add(child); + } + } + if (!outList.contains(child)) { + if (String.valueOf(child.getContentDescription()).matches(regex)) { + outList.add(child); + } + } + if (child instanceof ViewGroup) { + getChildViewsByRegex((ViewGroup) child, regex, outList); + } else { + } + } + } + public static void getChildViews(ViewGroup parent, int id, List outList) { for (int i = parent.getChildCount() - 1; i >= 0; i--) { final View child = parent.getChildAt(i); @@ -585,4 +614,59 @@ public static void setDimAmount(AlertDialog dialog, float value) { } window.setDimAmount(value); } + + public static View.OnClickListener getOnClickListener(View v) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { + return getOnClickListenerV14(v); + } else { + return getOnClickListenerV(v); + } + } + + //Used for APIs lower than ICS (API 14) + private static View.OnClickListener getOnClickListenerV(View view) { + View.OnClickListener retrievedListener = null; + String viewStr = "android.view.View"; + Field field; + try { + //noinspection JavaReflectionMemberAccess + field = Class.forName(viewStr).getDeclaredField("mOnClickListener"); + retrievedListener = (View.OnClickListener) field.get(view); + } catch (NoSuchFieldException ex) { + L.e("Reflection: No Such Field."); + } catch (IllegalAccessException ex) { + L.e("Reflection: Illegal Access."); + } catch (ClassNotFoundException ex) { + L.e("Reflection: Class Not Found."); + } + return retrievedListener; + } + + //Used for new ListenerInfo class structure used beginning with API 14 (ICS) + @SuppressWarnings("JavaReflectionMemberAccess") + @SuppressLint("PrivateApi") + private static View.OnClickListener getOnClickListenerV14(View view) { + View.OnClickListener retrievedListener = null; + String viewStr = "android.view.View"; + String lInfoStr = "android.view.View$ListenerInfo"; + try { + Field listenerField = Class.forName(viewStr).getDeclaredField("mListenerInfo"); + Object listenerInfo = null; + if (listenerField != null) { + listenerField.setAccessible(true); + listenerInfo = listenerField.get(view); + } + Field clickListenerField = Class.forName(lInfoStr).getDeclaredField("mOnClickListener"); + if (clickListenerField != null && listenerInfo != null) { + retrievedListener = (View.OnClickListener) clickListenerField.get(listenerInfo); + } + } catch (NoSuchFieldException ex) { + L.e("Reflection: No Such Field."); + } catch (IllegalAccessException ex) { + L.e("Reflection: Illegal Access."); + } catch (ClassNotFoundException ex) { + L.e("Reflection: Class Not Found."); + } + return retrievedListener; + } } diff --git a/app/src/main/java/com/surcumference/fingerprint/view/AlipayPayView.java b/app/src/main/java/com/surcumference/fingerprint/view/AlipayPayView.java index 3f32b15..9128eb8 100644 --- a/app/src/main/java/com/surcumference/fingerprint/view/AlipayPayView.java +++ b/app/src/main/java/com/surcumference/fingerprint/view/AlipayPayView.java @@ -65,8 +65,6 @@ private void init(Context context) { frameLayoutParams.gravity = Gravity.TOP | Gravity.RIGHT; withNeutralButtonText(Lang.getString(R.id.cancel)); - withPositiveButtonText(Lang.getString(R.id.enter_password)); - withOnPositiveButtonClickListener((dialog, which) -> dialog.dismiss()); this.addView(rootFrameLayout); } diff --git a/app/src/main/java/com/surcumference/fingerprint/view/DialogUtils.java b/app/src/main/java/com/surcumference/fingerprint/view/DialogUtils.java new file mode 100644 index 0000000..e95bb71 --- /dev/null +++ b/app/src/main/java/com/surcumference/fingerprint/view/DialogUtils.java @@ -0,0 +1,18 @@ +package com.surcumference.fingerprint.view; + +import android.app.AlertDialog; + +import androidx.annotation.Nullable; + +public class DialogUtils { + + public static void dismiss(@Nullable AlertDialog dialog) { + if (dialog == null) { + return; + } + try { + dialog.dismiss(); + } catch (IllegalArgumentException e) { + } + } +} diff --git a/app/src/main/java/com/surcumference/fingerprint/view/SettingsView.java b/app/src/main/java/com/surcumference/fingerprint/view/SettingsView.java index fe8adbd..954ea6a 100644 --- a/app/src/main/java/com/surcumference/fingerprint/view/SettingsView.java +++ b/app/src/main/java/com/surcumference/fingerprint/view/SettingsView.java @@ -220,7 +220,7 @@ private void updatePassword(DialogInterface passwordInputDialog, final String pa BizBiometricIdentify fingerprintIdentify = new BizBiometricIdentify(context); AlertDialog fingerprintVerificationDialog = new FingerprintVerificationView(context) .withOnCancelButtonClickListener((target) -> { - target.getDialog().dismiss(); + DialogUtils.dismiss(target.getDialog()); fingerprintIdentify.cancelIdentify(); }).withOnDismissListener(v -> { fingerprintIdentify.cancelIdentify(); diff --git a/app/src/main/res/values/ids.xml b/app/src/main/res/values/ids.xml index 0809fc1..c4ebbd7 100644 --- a/app/src/main/res/values/ids.xml +++ b/app/src/main/res/values/ids.xml @@ -75,6 +75,7 @@ +