diff --git a/core/runtime/android/runtime/src/main/java/org/hapjs/component/Component.java b/core/runtime/android/runtime/src/main/java/org/hapjs/component/Component.java index 8793ea20..083c29f9 100755 --- a/core/runtime/android/runtime/src/main/java/org/hapjs/component/Component.java +++ b/core/runtime/android/runtime/src/main/java/org/hapjs/component/Component.java @@ -28,6 +28,7 @@ import android.view.ViewGroup; import android.view.ViewParent; import android.view.ViewTreeObserver; +import android.view.accessibility.AccessibilityEvent; import android.widget.RelativeLayout; import androidx.annotation.NonNull; import androidx.collection.ArraySet; @@ -100,6 +101,7 @@ import org.hapjs.runtime.ProviderManager; import org.hapjs.runtime.RuntimeActivity; import org.hapjs.system.SysOpProvider; +import org.hapjs.system.utils.TalkBackUtils; import org.json.JSONException; import org.json.JSONObject; @@ -108,6 +110,8 @@ public abstract class Component public static final int INVALID_PAGE_ID = -1; public static final String METHOD_FOCUS = "focus"; + public static final String METHOD_TALKBACK_FOCUS = "requestTalkBackFocus"; + public static final String METHOD_TALKBACK_ANNOUNCE = "announceForTalkBack"; public static final String METHOD_ANIMATE = "animate"; public static final String METHOD_REQUEST_FULLSCREEN = "requestFullscreen"; public static final String METHOD_GET_BOUNDING_CLIENT_RECT = "getBoundingClientRect"; @@ -990,10 +994,12 @@ protected boolean setAttribute(String key, Object attribute) { setFocusable(focusable); return true; case Attributes.Style.ARIA_LABEL: + case Attributes.Style.ARIA_LABEL_LOWER: String ariaLabel = Attributes.getString(attribute); setAriaLabel(ariaLabel); return true; case Attributes.Style.ARIA_UNFOCUSABLE: + case Attributes.Style.ARIA_UNFOCUSABLE_LOWER: boolean ariaUnfocusable = Attributes.getBoolean(attribute, true); setAriaUnfocusable(ariaUnfocusable); return true; @@ -2933,6 +2939,14 @@ public void invokeMethod(String methodName, final Map args) { focus = Attributes.getBoolean(args.get("focus"), true); } focus(focus); + } else if (METHOD_TALKBACK_FOCUS.equals(methodName)) { + requestTalkBackFocus(); + } else if (METHOD_TALKBACK_ANNOUNCE.equals(methodName)) { + String content = null; + if (args != null && args.get("content") != null) { + content = Attributes.getString(args.get("content"), ""); + } + announceForTalkBack(content); } else if (METHOD_REQUEST_FULLSCREEN.equals(methodName)) { int screenOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; if (args != null) { @@ -4342,4 +4356,56 @@ public float getMinShowLevel() { public float getMaxShowLevel() { return mMaxShowLevel; } + + public void requestTalkBackFocus() { + if (isEnableTalkBack() && mHost != null) { + if (mHost.isAttachedToWindow()) { + mHost.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_HOVER_ENTER); + } else { + Log.w(TAG, "requestTalkBackFocus is not valid."); + mHost.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() { + @Override + public void onViewAttachedToWindow(View v) { + mHost.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_HOVER_ENTER); + mHost.removeOnAttachStateChangeListener(this); + } + + @Override + public void onViewDetachedFromWindow(View v) { + + } + }); + } + } + } + + public void announceForTalkBack(String content) { + if (isEnableTalkBack() && mHost != null && !TextUtils.isEmpty(content)) { + if (mHost.isAttachedToWindow()) { + mHost.announceForAccessibility(content); + } else { + Log.w(TAG, "announceForTalkBack is not valid."); + mHost.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() { + @Override + public void onViewAttachedToWindow(View v) { + mHost.announceForAccessibility(content); + mHost.removeOnAttachStateChangeListener(this); + } + + @Override + public void onViewDetachedFromWindow(View v) { + + } + }); + } + } + } + + public boolean isEnableTalkBack() { + return TalkBackUtils.isEnableTalkBack(mContext, false); + } + + public void performComponentClick(MotionEvent event) { + + } } diff --git a/core/runtime/android/runtime/src/main/java/org/hapjs/component/RecyclerDataItem.java b/core/runtime/android/runtime/src/main/java/org/hapjs/component/RecyclerDataItem.java index 43147495..550a8129 100644 --- a/core/runtime/android/runtime/src/main/java/org/hapjs/component/RecyclerDataItem.java +++ b/core/runtime/android/runtime/src/main/java/org/hapjs/component/RecyclerDataItem.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, the hapjs-platform Project Contributors + * Copyright (c) 2021-present, the hapjs-platform Project Contributors * SPDX-License-Identifier: Apache-2.0 */ @@ -25,7 +25,7 @@ public abstract class RecyclerDataItem implements ComponentDataHolder { private final CombinedMap mStyleDomData = new CombinedMap<>(); private final CombinedMap mAttrsDomData = new CombinedMap<>(); private final CombinedMap mEventDomData = new CombinedMap<>(); - private final ComponentCreator mComponentCreator; + protected final ComponentCreator mComponentCreator; protected Node mCssNode; private Container.RecyclerItem mParent; private Component mBoundRecycleComponent; @@ -384,6 +384,10 @@ public static class ComponentCreator { private RenderEventCallback mCallback; private Widget mWidget; + public Context getContext() { + return mContext; + } + public ComponentCreator( HapEngine hapEngine, Context context, RenderEventCallback callback, Widget widget) { mHapEngine = hapEngine; diff --git a/core/runtime/android/runtime/src/main/java/org/hapjs/component/constants/Attributes.java b/core/runtime/android/runtime/src/main/java/org/hapjs/component/constants/Attributes.java index aec8d3ca..8fa59407 100755 --- a/core/runtime/android/runtime/src/main/java/org/hapjs/component/constants/Attributes.java +++ b/core/runtime/android/runtime/src/main/java/org/hapjs/component/constants/Attributes.java @@ -591,7 +591,9 @@ public interface Style { String MODE = "mode"; String ARIA_LABEL = "ariaLabel"; + String ARIA_LABEL_LOWER = "arialabel"; String ARIA_UNFOCUSABLE = "ariaUnfocusable"; + String ARIA_UNFOCUSABLE_LOWER = "ariaunfocusable"; String FORCE_DARK = "forcedark"; diff --git a/core/runtime/android/runtime/src/main/java/org/hapjs/component/view/flexbox/PercentFlexboxLayout.java b/core/runtime/android/runtime/src/main/java/org/hapjs/component/view/flexbox/PercentFlexboxLayout.java index bb77b093..a53c7e70 100644 --- a/core/runtime/android/runtime/src/main/java/org/hapjs/component/view/flexbox/PercentFlexboxLayout.java +++ b/core/runtime/android/runtime/src/main/java/org/hapjs/component/view/flexbox/PercentFlexboxLayout.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, the hapjs-platform Project Contributors + * Copyright (c) 2021-present, the hapjs-platform Project Contributors * SPDX-License-Identifier: Apache-2.0 */ @@ -29,6 +29,7 @@ import org.hapjs.component.view.helper.StateHelper; import org.hapjs.component.view.keyevent.KeyEventDelegate; import org.hapjs.render.vdom.DocComponent; +import org.hapjs.system.utils.TalkBackUtils; public class PercentFlexboxLayout extends YogaLayout implements ComponentHost, GestureHost { @@ -38,13 +39,40 @@ public class PercentFlexboxLayout extends YogaLayout implements ComponentHost, G private List mPositionArray; private boolean mDisallowIntercept = false; + private boolean mIsEnableTalkBack; + private MotionEvent mLastMotionEvent = null; public PercentFlexboxLayout(Context context) { super(context); + mIsEnableTalkBack = TalkBackUtils.isEnableTalkBack(context, false); getYogaNode().setFlexDirection(YogaFlexDirection.ROW); getYogaNode().setFlexShrink(1f); } + @Override + public boolean performClick() { + boolean isConsume = super.performClick(); + if (mIsEnableTalkBack) { + if (null != mComponent) { + mComponent.performComponentClick(mLastMotionEvent); + } + } + return isConsume; + } + + + @Override + protected boolean dispatchHoverEvent(MotionEvent event) { + mLastMotionEvent = event; + return super.dispatchHoverEvent(event); + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + mLastMotionEvent = null; + } + @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { if (!(getParent() instanceof YogaLayout)) { diff --git a/core/runtime/android/runtime/src/main/java/org/hapjs/runtime/RuntimeActivity.java b/core/runtime/android/runtime/src/main/java/org/hapjs/runtime/RuntimeActivity.java index ff4e56e0..740d2e02 100755 --- a/core/runtime/android/runtime/src/main/java/org/hapjs/runtime/RuntimeActivity.java +++ b/core/runtime/android/runtime/src/main/java/org/hapjs/runtime/RuntimeActivity.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, the hapjs-platform Project Contributors + * Copyright (c) 2021-present, the hapjs-platform Project Contributors * SPDX-License-Identifier: Apache-2.0 */ @@ -32,6 +32,7 @@ import org.hapjs.model.videodata.VideoCacheManager; import org.hapjs.render.RootView; import org.hapjs.render.jsruntime.JsThread; +import org.hapjs.system.utils.TalkBackUtils; public class RuntimeActivity extends AppCompatActivity { public static final String PROP_APP = "runtime.app"; @@ -418,6 +419,7 @@ protected void onResume() { if (mHybridView != null) { mHybridView.getHybridManager().onResume(); } + TalkBackUtils.isEnableTalkBack(RuntimeActivity.this,true); } @Override diff --git a/core/runtime/android/runtime/src/main/java/org/hapjs/system/DefaultSysOpProviderImpl.java b/core/runtime/android/runtime/src/main/java/org/hapjs/system/DefaultSysOpProviderImpl.java index 101e05f1..794f016b 100644 --- a/core/runtime/android/runtime/src/main/java/org/hapjs/system/DefaultSysOpProviderImpl.java +++ b/core/runtime/android/runtime/src/main/java/org/hapjs/system/DefaultSysOpProviderImpl.java @@ -571,4 +571,8 @@ public void updateConfiguration(Context context, Configuration configuration) { } + public boolean isEnableTalkBack(Context context) { + return false; + } + } \ No newline at end of file diff --git a/core/runtime/android/runtime/src/main/java/org/hapjs/system/SysOpProvider.java b/core/runtime/android/runtime/src/main/java/org/hapjs/system/SysOpProvider.java index a2ccb3fd..0f266190 100644 --- a/core/runtime/android/runtime/src/main/java/org/hapjs/system/SysOpProvider.java +++ b/core/runtime/android/runtime/src/main/java/org/hapjs/system/SysOpProvider.java @@ -190,4 +190,6 @@ interface OnUpdateMenubarDataCallback { float getScaleShowLevel(Context context); void updateConfiguration(Context context, Configuration configuration); + + boolean isEnableTalkBack(Context context); } diff --git a/core/runtime/android/runtime/src/main/java/org/hapjs/system/utils/TalkBackUtils.java b/core/runtime/android/runtime/src/main/java/org/hapjs/system/utils/TalkBackUtils.java new file mode 100644 index 00000000..83ffdbf7 --- /dev/null +++ b/core/runtime/android/runtime/src/main/java/org/hapjs/system/utils/TalkBackUtils.java @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2023-present, the hapjs-platform Project Contributors + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hapjs.system.utils; + +import android.content.ComponentName; +import android.content.Context; +import android.provider.Settings; +import android.text.TextUtils; +import android.util.Log; + +import org.hapjs.runtime.ProviderManager; +import org.hapjs.system.SysOpProvider; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +public class TalkBackUtils { + private final static String TAG = "TalkBackUtils"; + /* + * 获取当前开启的所有辅助功能服务 + */ + static int sTalkBackEnable = -1; + static volatile boolean sIsForceCheck; + static volatile boolean sIsChecking; + static final int TALKBACK_ENABLE = 1; + static final int TALKBACK_DISABLE = 0; + static final int TALKBACK_DEFAULT = -1; + static final char ENABLED_ACCESSIBILITY_SERVICES_SEPARATOR = ':'; + static final ComponentName TALKBACK_SERVICE_ENABLE = ComponentName.unflattenFromString("com.google.android.marvin.talkback/com.google.android.marvin.talkback.TalkBackService"); + final static TextUtils.SimpleStringSplitter sStringColonSplitter = + new TextUtils.SimpleStringSplitter(ENABLED_ACCESSIBILITY_SERVICES_SEPARATOR); + + private static Set getEnabledServicesFromSettings(Context context) { + Set enabledServices = null; + if (null == context) { + return enabledServices; + } + try { + String enabledServicesSetting = Settings.Secure.getString(context.getContentResolver(), "enabled_accessibility_services"); + if (TextUtils.isEmpty(enabledServicesSetting)) { + return Collections.emptySet(); + } else { + enabledServices = new HashSet(); + TextUtils.SimpleStringSplitter colonSplitter = sStringColonSplitter; + colonSplitter.setString(enabledServicesSetting); + while (colonSplitter.hasNext()) { + String componentNameString = colonSplitter.next(); + ComponentName enabledService = ComponentName.unflattenFromString(componentNameString); + if (enabledService != null) { + enabledServices.add(enabledService); + } + } + return enabledServices; + } + } catch (Exception e) { + Log.e(TAG, "getEnabledServicesFromSettings error : " + e.getMessage()); + } + return enabledServices; + } + + public static void setIsForceCheck(boolean isForceCheck) { + TalkBackUtils.sIsForceCheck = isForceCheck; + } + + public static boolean isEnableTalkBack(Context context, boolean forceCheck) { + if (sIsChecking) { + return false; + } + if ((sIsForceCheck || forceCheck || sTalkBackEnable == TALKBACK_DEFAULT) && null != context) { + sIsForceCheck = false; + sIsChecking = true; + boolean isEnable = false; + SysOpProvider provider = ProviderManager.getDefault().getProvider(SysOpProvider.NAME); + if (null != provider) { + isEnable = provider.isEnableTalkBack(context); + } + if (isEnable) { + Set componentNames = getEnabledServicesFromSettings(context); + if (null != componentNames && componentNames.contains(TALKBACK_SERVICE_ENABLE)) { + sTalkBackEnable = TALKBACK_ENABLE; + sIsChecking = false; + return true; + } + } + sTalkBackEnable = TALKBACK_DISABLE; + sIsChecking = false; + return false; + } else { + return sTalkBackEnable == TALKBACK_ENABLE; + } + } +} diff --git a/core/runtime/android/runtime/src/main/res/layout/titlebar_view.xml b/core/runtime/android/runtime/src/main/res/layout/titlebar_view.xml index d224bb1f..51d8a9a4 100644 --- a/core/runtime/android/runtime/src/main/res/layout/titlebar_view.xml +++ b/core/runtime/android/runtime/src/main/res/layout/titlebar_view.xml @@ -1,7 +1,7 @@ @@ -46,7 +46,8 @@ android:layout_width="16dp" android:layout_height="16dp" android:layout_gravity="center" - android:src="@drawable/menu_dot" /> + android:src="@drawable/menu_dot" + android:contentDescription="@string/talkback_titlebar_left_menu_iv"/> + android:src="@drawable/menu_close" + android:contentDescription="@string/talkback_close"/> diff --git a/core/runtime/android/runtime/src/main/res/values-zh-rCN/strings.xml b/core/runtime/android/runtime/src/main/res/values-zh-rCN/strings.xml index 6e796a90..d3509503 100644 --- a/core/runtime/android/runtime/src/main/res/values-zh-rCN/strings.xml +++ b/core/runtime/android/runtime/src/main/res/values-zh-rCN/strings.xml @@ -104,4 +104,10 @@ 加桌 桌面 添加到桌面 + + + 更多-按钮 + 弹出式窗口 + 关闭-按钮 + 按钮 \ No newline at end of file diff --git a/core/runtime/android/runtime/src/main/res/values/strings.xml b/core/runtime/android/runtime/src/main/res/values/strings.xml index d1aabd2f..0d7f5f06 100644 --- a/core/runtime/android/runtime/src/main/res/values/strings.xml +++ b/core/runtime/android/runtime/src/main/res/values/strings.xml @@ -143,4 +143,9 @@ Add to desktop Add to desktop Add to desktop + + more-button + pop-up window + close-button + button diff --git a/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/A.java b/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/A.java index d5aa6060..274490a3 100755 --- a/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/A.java +++ b/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/A.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021-2022, the hapjs-platform Project Contributors + * Copyright (c) 2021-present, the hapjs-platform Project Contributors * SPDX-License-Identifier: Apache-2.0 */ @@ -34,7 +34,9 @@ Component.METHOD_ANIMATE, Component.METHOD_GET_BOUNDING_CLIENT_RECT, Component.METHOD_TO_TEMP_FILE_PATH, - Component.METHOD_FOCUS + Component.METHOD_FOCUS, + Component.METHOD_TALKBACK_FOCUS, + Component.METHOD_TALKBACK_ANNOUNCE }) public class A extends Text { diff --git a/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/Camera.java b/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/Camera.java index ed08d95e..de5c8e9f 100644 --- a/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/Camera.java +++ b/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/Camera.java @@ -46,7 +46,9 @@ Camera.METHOD_GET_EXPOSURE_VALUE, Camera.METHOD_GET_FPS_RANGE, Camera.METHOD_START_RECORD, - Camera.METHOD_STOP_RECORD + Camera.METHOD_STOP_RECORD, + Component.METHOD_TALKBACK_FOCUS, + Component.METHOD_TALKBACK_ANNOUNCE }) public class Camera extends Component { diff --git a/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/Div.java b/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/Div.java index c6da9fd5..28cf41d7 100755 --- a/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/Div.java +++ b/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/Div.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, the hapjs-platform Project Contributors + * Copyright (c) 2021-present, the hapjs-platform Project Contributors * SPDX-License-Identifier: Apache-2.0 */ @@ -21,7 +21,9 @@ Component.METHOD_ANIMATE, Component.METHOD_GET_BOUNDING_CLIENT_RECT, Component.METHOD_TO_TEMP_FILE_PATH, - Component.METHOD_FOCUS + Component.METHOD_FOCUS, + Component.METHOD_TALKBACK_FOCUS, + Component.METHOD_TALKBACK_ANNOUNCE }) public class Div extends Container { diff --git a/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/Image.java b/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/Image.java index cab998e7..a3cfbc2e 100755 --- a/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/Image.java +++ b/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/Image.java @@ -56,7 +56,9 @@ Component.METHOD_TO_TEMP_FILE_PATH, Component.METHOD_FOCUS, Image.METHOD_START_ANIMATION, - Image.METHOD_STOP_ANIMAION + Image.METHOD_STOP_ANIMAION, + Component.METHOD_TALKBACK_FOCUS, + Component.METHOD_TALKBACK_ANNOUNCE } ) public class Image extends Component implements Autoplay, InnerSpannable { diff --git a/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/Popup.java b/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/Popup.java index b1e65a79..211c5c90 100755 --- a/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/Popup.java +++ b/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/Popup.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021-2022, the hapjs-platform Project Contributors + * Copyright (c) 2021-present, the hapjs-platform Project Contributors * SPDX-License-Identifier: Apache-2.0 */ @@ -43,7 +43,9 @@ Component.METHOD_ANIMATE, Component.METHOD_TO_TEMP_FILE_PATH, Component.METHOD_FOCUS, - Component.METHOD_GET_BOUNDING_CLIENT_RECT + Component.METHOD_GET_BOUNDING_CLIENT_RECT, + Component.METHOD_TALKBACK_FOCUS, + Component.METHOD_TALKBACK_ANNOUNCE }) public class Popup extends Container implements Floating { diff --git a/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/Rating.java b/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/Rating.java index 8397de47..b95dd424 100755 --- a/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/Rating.java +++ b/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/Rating.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, the hapjs-platform Project Contributors + * Copyright (c) 2021-present, the hapjs-platform Project Contributors * SPDX-License-Identifier: Apache-2.0 */ @@ -29,7 +29,9 @@ Component.METHOD_ANIMATE, Component.METHOD_GET_BOUNDING_CLIENT_RECT, Component.METHOD_TO_TEMP_FILE_PATH, - Component.METHOD_FOCUS + Component.METHOD_FOCUS, + Component.METHOD_TALKBACK_FOCUS, + Component.METHOD_TALKBACK_ANNOUNCE }) public class Rating extends Component implements SwipeObserver { diff --git a/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/ReaderDiv.java b/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/ReaderDiv.java index 2683d042..4f3fb004 100644 --- a/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/ReaderDiv.java +++ b/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/ReaderDiv.java @@ -55,7 +55,9 @@ ReaderDiv.METHOD_PRELOAD_CONTENT, ReaderDiv.METHOD_SET_PAGE_LINE_SPACE, ReaderDiv.METHOD_SET_PAGE_COLOR, - ReaderDiv.METHOD_GET_PAGE_CONTENT + ReaderDiv.METHOD_GET_PAGE_CONTENT, + Component.METHOD_TALKBACK_FOCUS, + Component.METHOD_TALKBACK_ANNOUNCE } ) public class ReaderDiv extends Container { diff --git a/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/Refresh.java b/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/Refresh.java index 13a39ed1..407be06e 100755 --- a/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/Refresh.java +++ b/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/Refresh.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, the hapjs-platform Project Contributors + * Copyright (c) 2021-present, the hapjs-platform Project Contributors * SPDX-License-Identifier: Apache-2.0 */ @@ -29,7 +29,9 @@ Component.METHOD_ANIMATE, Component.METHOD_GET_BOUNDING_CLIENT_RECT, Component.METHOD_TO_TEMP_FILE_PATH, - Component.METHOD_FOCUS + Component.METHOD_FOCUS, + Component.METHOD_TALKBACK_FOCUS, + Component.METHOD_TALKBACK_ANNOUNCE }) public class Refresh extends Container { diff --git a/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/Select.java b/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/Select.java index 0ffed860..560e8c0d 100755 --- a/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/Select.java +++ b/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/Select.java @@ -47,7 +47,9 @@ Component.METHOD_ANIMATE, Component.METHOD_GET_BOUNDING_CLIENT_RECT, Component.METHOD_TO_TEMP_FILE_PATH, - Component.METHOD_FOCUS + Component.METHOD_FOCUS, + Component.METHOD_TALKBACK_FOCUS, + Component.METHOD_TALKBACK_ANNOUNCE }) public class Select extends Container { diff --git a/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/ShareButton.java b/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/ShareButton.java index 605bccbb..9b768801 100644 --- a/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/ShareButton.java +++ b/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/ShareButton.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, the hapjs-platform Project Contributors + * Copyright (c) 2022-present, the hapjs-platform Project Contributors * SPDX-License-Identifier: Apache-2.0 */ @@ -44,7 +44,9 @@ Component.METHOD_FOCUS, Component.METHOD_ANIMATE, Component.METHOD_GET_BOUNDING_CLIENT_RECT, - Component.METHOD_TO_TEMP_FILE_PATH + Component.METHOD_TO_TEMP_FILE_PATH, + Component.METHOD_TALKBACK_FOCUS, + Component.METHOD_TALKBACK_ANNOUNCE } ) diff --git a/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/ShortcutButton.java b/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/ShortcutButton.java index a09c46b1..c4af6082 100644 --- a/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/ShortcutButton.java +++ b/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/ShortcutButton.java @@ -57,7 +57,9 @@ Component.METHOD_FOCUS, Component.METHOD_ANIMATE, Component.METHOD_GET_BOUNDING_CLIENT_RECT, - Component.METHOD_TO_TEMP_FILE_PATH + Component.METHOD_TO_TEMP_FILE_PATH, + Component.METHOD_TALKBACK_FOCUS, + Component.METHOD_TALKBACK_ANNOUNCE } ) diff --git a/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/Slider.java b/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/Slider.java index 40910746..c5b2543d 100755 --- a/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/Slider.java +++ b/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/Slider.java @@ -27,7 +27,9 @@ Component.METHOD_ANIMATE, Component.METHOD_GET_BOUNDING_CLIENT_RECT, Component.METHOD_TO_TEMP_FILE_PATH, - Component.METHOD_FOCUS + Component.METHOD_FOCUS, + Component.METHOD_TALKBACK_FOCUS, + Component.METHOD_TALKBACK_ANNOUNCE }) public class Slider extends Component implements SwipeObserver { diff --git a/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/Stack.java b/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/Stack.java index 228cb1ac..a3b991ab 100755 --- a/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/Stack.java +++ b/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/Stack.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, the hapjs-platform Project Contributors + * Copyright (c) 2021-present, the hapjs-platform Project Contributors * SPDX-License-Identifier: Apache-2.0 */ @@ -31,7 +31,9 @@ Component.METHOD_REQUEST_FULLSCREEN, Component.METHOD_GET_BOUNDING_CLIENT_RECT, Component.METHOD_TO_TEMP_FILE_PATH, - Component.METHOD_FOCUS + Component.METHOD_FOCUS, + Component.METHOD_TALKBACK_FOCUS, + Component.METHOD_TALKBACK_ANNOUNCE }) public class Stack extends Container { diff --git a/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/Swiper.java b/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/Swiper.java index 67e0a55b..41e8bedf 100755 --- a/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/Swiper.java +++ b/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/Swiper.java @@ -45,7 +45,9 @@ Component.METHOD_ANIMATE, Component.METHOD_GET_BOUNDING_CLIENT_RECT, Component.METHOD_TO_TEMP_FILE_PATH, - Component.METHOD_FOCUS + Component.METHOD_FOCUS, + Component.METHOD_TALKBACK_FOCUS, + Component.METHOD_TALKBACK_ANNOUNCE }) public class Swiper extends AbstractScrollable implements Recycler, HScrollable, ViewTreeObserver.OnGlobalLayoutListener { @@ -442,7 +444,7 @@ public void setInterval(long interval) { } public void setAutoScroll(String autoScrollStr) { - if (TextUtils.isEmpty(autoScrollStr) || mViewPager == null) { + if (TextUtils.isEmpty(autoScrollStr) || mViewPager == null || isEnableTalkBack()) { return; } diff --git a/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/Web.java b/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/Web.java index 537f12c3..a3f5e791 100755 --- a/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/Web.java +++ b/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/Web.java @@ -56,7 +56,9 @@ Web.METHOD_SET_COOKIE, Component.METHOD_GET_BOUNDING_CLIENT_RECT, Component.METHOD_TO_TEMP_FILE_PATH, - Component.METHOD_FOCUS + Component.METHOD_FOCUS, + Component.METHOD_TALKBACK_FOCUS, + Component.METHOD_TALKBACK_ANNOUNCE }) public class Web extends Component implements SwipeObserver { private static final String TAG = "Web"; diff --git a/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/canvas/Canvas.java b/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/canvas/Canvas.java index 40985964..bbabe5c5 100755 --- a/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/canvas/Canvas.java +++ b/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/canvas/Canvas.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, the hapjs-platform Project Contributors + * Copyright (c) 2021-present, the hapjs-platform Project Contributors * SPDX-License-Identifier: Apache-2.0 */ @@ -43,7 +43,9 @@ methods = { Canvas.METHOD_TO_TEMP_FILE_PATH, Component.METHOD_GET_BOUNDING_CLIENT_RECT, - Component.METHOD_FOCUS + Component.METHOD_FOCUS, + Component.METHOD_TALKBACK_FOCUS, + Component.METHOD_TALKBACK_ANNOUNCE }) public class Canvas extends Component { diff --git a/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/drawer/Drawer.java b/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/drawer/Drawer.java index 1e9a2070..23c7ab96 100644 --- a/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/drawer/Drawer.java +++ b/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/drawer/Drawer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, the hapjs-platform Project Contributors + * Copyright (c) 2021-present, the hapjs-platform Project Contributors * SPDX-License-Identifier: Apache-2.0 */ @@ -36,7 +36,9 @@ Component.METHOD_ANIMATE, Component.METHOD_GET_BOUNDING_CLIENT_RECT, Component.METHOD_TO_TEMP_FILE_PATH, - Component.METHOD_FOCUS + Component.METHOD_FOCUS, + Component.METHOD_TALKBACK_FOCUS, + Component.METHOD_TALKBACK_ANNOUNCE }) public class Drawer extends Container { protected static final String WIDGET_NAME = "drawer"; diff --git a/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/drawer/DrawerNavigation.java b/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/drawer/DrawerNavigation.java index 865dd64d..0d5780c8 100644 --- a/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/drawer/DrawerNavigation.java +++ b/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/drawer/DrawerNavigation.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, the hapjs-platform Project Contributors + * Copyright (c) 2021-present, the hapjs-platform Project Contributors * SPDX-License-Identifier: Apache-2.0 */ @@ -26,7 +26,9 @@ Component.METHOD_ANIMATE, Component.METHOD_GET_BOUNDING_CLIENT_RECT, Component.METHOD_TO_TEMP_FILE_PATH, - Component.METHOD_FOCUS + Component.METHOD_FOCUS, + Component.METHOD_TALKBACK_FOCUS, + Component.METHOD_TALKBACK_ANNOUNCE }) public class DrawerNavigation extends Container { protected static final String WIDGET_NAME = "drawer-navigation"; diff --git a/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/input/Button.java b/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/input/Button.java index e5df02a9..fb102f77 100755 --- a/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/input/Button.java +++ b/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/input/Button.java @@ -29,7 +29,9 @@ Component.METHOD_FOCUS, Component.METHOD_ANIMATE, Component.METHOD_GET_BOUNDING_CLIENT_RECT, - Component.METHOD_TO_TEMP_FILE_PATH + Component.METHOD_TO_TEMP_FILE_PATH, + Component.METHOD_TALKBACK_FOCUS, + Component.METHOD_TALKBACK_ANNOUNCE }) public class Button extends Edit { diff --git a/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/input/CheckBox.java b/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/input/CheckBox.java index 8371dad1..8150c895 100755 --- a/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/input/CheckBox.java +++ b/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/input/CheckBox.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, the hapjs-platform Project Contributors + * Copyright (c) 2021-present, the hapjs-platform Project Contributors * SPDX-License-Identifier: Apache-2.0 */ @@ -27,7 +27,9 @@ Component.METHOD_FOCUS, Component.METHOD_ANIMATE, Component.METHOD_GET_BOUNDING_CLIENT_RECT, - Component.METHOD_TO_TEMP_FILE_PATH + Component.METHOD_TO_TEMP_FILE_PATH, + Component.METHOD_TALKBACK_FOCUS, + Component.METHOD_TALKBACK_ANNOUNCE }) public class CheckBox extends Button { @@ -49,7 +51,7 @@ public CheckBox( @Override protected TextView createViewImpl() { - FlexCheckBox checkBox = new FlexCheckBox(mContext); + FlexCheckBox checkBox = new FlexCheckBox(mContext,isEnableTalkBack()); checkBox.setComponent(this); initDefaultView(checkBox); initOnCheckedListener(checkBox); @@ -93,6 +95,9 @@ protected boolean setAttribute(String key, Object attribute) { return true; case Attributes.Style.VALUE: mValue = Attributes.getString(attribute, null); + if(isEnableTalkBack() && (mHost instanceof FlexCheckBox)){ + ((FlexCheckBox)mHost).setValue(mValue); + } return true; default: break; diff --git a/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/input/Edit.java b/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/input/Edit.java index bacad9cc..5a26df87 100755 --- a/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/input/Edit.java +++ b/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/input/Edit.java @@ -83,7 +83,9 @@ Component.METHOD_TO_TEMP_FILE_PATH, Edit.METHOD_SELECT, Edit.METHOD_SET_SELECTION_RANGE, - Edit.METHOD_GET_SELECTION_RANGE + Edit.METHOD_GET_SELECTION_RANGE, + Component.METHOD_TALKBACK_FOCUS, + Component.METHOD_TALKBACK_ANNOUNCE }) public class Edit extends Component implements SwipeObserver { protected static final String WIDGET_NAME = "input"; @@ -139,7 +141,7 @@ public Edit( @Override protected TextView createViewImpl() { - FlexEditText editText = new FlexEditText(mContext); + FlexEditText editText = new FlexEditText(mContext,isEnableTalkBack()); editText.setComponent(this); initDefaultView(editText); diff --git a/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/input/EventButton.java b/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/input/EventButton.java index 85ff75c6..6333811a 100644 --- a/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/input/EventButton.java +++ b/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/input/EventButton.java @@ -46,7 +46,9 @@ Component.METHOD_FOCUS, Component.METHOD_ANIMATE, Component.METHOD_GET_BOUNDING_CLIENT_RECT, - Component.METHOD_TO_TEMP_FILE_PATH + Component.METHOD_TO_TEMP_FILE_PATH, + Component.METHOD_TALKBACK_FOCUS, + Component.METHOD_TALKBACK_ANNOUNCE } ) public class EventButton extends Button { diff --git a/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/input/Label.java b/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/input/Label.java index 39f8866f..1f6e6c2b 100755 --- a/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/input/Label.java +++ b/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/input/Label.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, the hapjs-platform Project Contributors + * Copyright (c) 2021-present, the hapjs-platform Project Contributors * SPDX-License-Identifier: Apache-2.0 */ @@ -24,7 +24,9 @@ Component.METHOD_ANIMATE, Component.METHOD_GET_BOUNDING_CLIENT_RECT, Component.METHOD_FOCUS, - Component.METHOD_TO_TEMP_FILE_PATH + Component.METHOD_TO_TEMP_FILE_PATH, + Component.METHOD_TALKBACK_FOCUS, + Component.METHOD_TALKBACK_ANNOUNCE }) public class Label extends Text { diff --git a/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/input/Radio.java b/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/input/Radio.java index face2256..4ccd1af8 100755 --- a/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/input/Radio.java +++ b/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/input/Radio.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, the hapjs-platform Project Contributors + * Copyright (c) 2021-present, the hapjs-platform Project Contributors * SPDX-License-Identifier: Apache-2.0 */ @@ -28,7 +28,9 @@ Component.METHOD_FOCUS, Component.METHOD_ANIMATE, Component.METHOD_GET_BOUNDING_CLIENT_RECT, - Component.METHOD_TO_TEMP_FILE_PATH + Component.METHOD_TO_TEMP_FILE_PATH, + Component.METHOD_TALKBACK_FOCUS, + Component.METHOD_TALKBACK_ANNOUNCE }) public class Radio extends Button implements SingleChoice { static final String TYPE_RADIO = "radio"; @@ -49,7 +51,7 @@ public Radio( @Override protected TextView createViewImpl() { - FlexRadioButton radioButton = new FlexRadioButton(mContext); + FlexRadioButton radioButton = new FlexRadioButton(mContext,isEnableTalkBack()); radioButton.setComponent(this); initOnCheckedListener(radioButton); return radioButton; @@ -92,6 +94,9 @@ protected boolean setAttribute(String key, Object attribute) { return true; case Attributes.Style.VALUE: mValue = Attributes.getString(attribute, null); + if(isEnableTalkBack() && (mHost instanceof FlexRadioButton)){ + ((FlexRadioButton)mHost).setValue(mValue); + } return true; default: break; diff --git a/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/input/Switch.java b/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/input/Switch.java index b302f859..2b765df6 100755 --- a/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/input/Switch.java +++ b/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/input/Switch.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, the hapjs-platform Project Contributors + * Copyright (c) 2021-present, the hapjs-platform Project Contributors * SPDX-License-Identifier: Apache-2.0 */ @@ -28,7 +28,9 @@ Component.METHOD_ANIMATE, Component.METHOD_GET_BOUNDING_CLIENT_RECT, Component.METHOD_TO_TEMP_FILE_PATH, - Component.METHOD_FOCUS + Component.METHOD_FOCUS, + Component.METHOD_TALKBACK_FOCUS, + Component.METHOD_TALKBACK_ANNOUNCE }) public class Switch extends Button { diff --git a/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/input/TextArea.java b/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/input/TextArea.java index f3bf89b4..d09f3bb4 100755 --- a/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/input/TextArea.java +++ b/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/input/TextArea.java @@ -35,6 +35,8 @@ Edit.METHOD_SELECT, Edit.METHOD_SET_SELECTION_RANGE, Edit.METHOD_GET_SELECTION_RANGE, + Component.METHOD_TALKBACK_FOCUS, + Component.METHOD_TALKBACK_ANNOUNCE }) public class TextArea extends Edit { diff --git a/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/list/List.java b/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/list/List.java index 30c229ba..6b691ae2 100755 --- a/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/list/List.java +++ b/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/list/List.java @@ -37,6 +37,7 @@ import org.hapjs.component.utils.YogaUtil; import org.hapjs.component.view.ScrollView; import org.hapjs.runtime.HapEngine; +import org.hapjs.widgets.R; import org.hapjs.widgets.RecyclerDataItemFactory; import org.hapjs.widgets.view.list.FlexGridLayoutManager; import org.hapjs.widgets.view.list.FlexLayoutManager; @@ -51,7 +52,9 @@ Component.METHOD_ANIMATE, Component.METHOD_GET_BOUNDING_CLIENT_RECT, Component.METHOD_TO_TEMP_FILE_PATH, - Component.METHOD_FOCUS + Component.METHOD_FOCUS, + Component.METHOD_TALKBACK_FOCUS, + Component.METHOD_TALKBACK_ANNOUNCE }) public class List extends AbstractScrollable implements Recycler, SwipeObserver { protected static final String WIDGET_NAME = "list"; @@ -85,6 +88,7 @@ public class List extends AbstractScrollable implements Recycler, private boolean mIsScrollPage = false; private int mOrientation = OrientationHelper.VERTICAL; private boolean mIsReverse = false; + private boolean mIsEnableTalkBack; public List( HapEngine hapEngine, @@ -94,6 +98,7 @@ public List( RenderEventCallback callback, Map savedState) { super(hapEngine, context, parent, elId, callback, savedState); + mIsEnableTalkBack = isEnableTalkBack(); } @Override @@ -778,6 +783,15 @@ private class Holder extends RecyclerView.ViewHolder { private Component mRecycleComponent; private RecyclerDataItem mItem; + public void setContentDescription(String description) { + if (!TextUtils.isEmpty(description) && null != mRecycleComponent) { + View hostView = mRecycleComponent.getHostView(); + if (null != hostView) { + hostView.setContentDescription(description); + } + } + } + Holder(Component instance) { super(instance.getHostView()); mRecycleComponent = instance; @@ -858,6 +872,16 @@ public Holder onCreateViewHolder(ViewGroup parent, int viewType) { public void onBindViewHolder(Holder holder, int position) { ListItem.RecyclerItem item = getItem(position); mRecyclerItem.attachToTemplate(item); + if (mIsEnableTalkBack) { + String description = item.getViewDescription(); + if (TextUtils.isEmpty(description)) { + if (null != mContext) { + holder.setContentDescription(mContext.getResources().getString(R.string.talkback_notitle_defaultstr)); + } + } else { + holder.setContentDescription(description); + } + } holder.bind(item); Component component = holder.getRecycleComponent(); lazySetAppearanceWatch(component); diff --git a/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/list/ListItem.java b/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/list/ListItem.java index d60e2ae9..679e5d3a 100755 --- a/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/list/ListItem.java +++ b/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/list/ListItem.java @@ -1,11 +1,13 @@ /* - * Copyright (c) 2021, the hapjs-platform Project Contributors + * Copyright (c) 2021-present, the hapjs-platform Project Contributors * SPDX-License-Identifier: Apache-2.0 */ package org.hapjs.widgets.list; import android.content.Context; +import android.view.MotionEvent; +import android.view.View; import android.view.ViewGroup; import java.util.Map; import org.hapjs.bridge.annotation.WidgetAnnotation; @@ -14,9 +16,13 @@ import org.hapjs.component.bridge.RenderEventCallback; import org.hapjs.component.constants.Attributes; import org.hapjs.component.view.flexbox.PercentFlexboxLayout; +import org.hapjs.component.view.gesture.GestureDelegate; +import org.hapjs.component.view.gesture.GestureHost; +import org.hapjs.component.view.gesture.IGesture; import org.hapjs.component.view.state.State; import org.hapjs.render.css.value.CSSValues; import org.hapjs.runtime.HapEngine; +import org.hapjs.system.utils.TalkBackUtils; import org.hapjs.widgets.view.list.FlexLayoutManager; @WidgetAnnotation( @@ -25,11 +31,14 @@ Component.METHOD_ANIMATE, Component.METHOD_TO_TEMP_FILE_PATH, Component.METHOD_FOCUS, - Component.METHOD_GET_BOUNDING_CLIENT_RECT + Component.METHOD_GET_BOUNDING_CLIENT_RECT, + Component.METHOD_TALKBACK_FOCUS, + Component.METHOD_TALKBACK_ANNOUNCE }) public class ListItem extends Container { protected static final String WIDGET_NAME = "list-item"; + private boolean mIsEnableTalkBack; public ListItem( HapEngine hapEngine, @@ -39,6 +48,7 @@ public ListItem( RenderEventCallback callback, Map savedState) { super(hapEngine, context, parent, ref, callback, savedState); + mIsEnableTalkBack = isEnableTalkBack(); } @Override @@ -70,6 +80,9 @@ protected boolean setAttribute(String key, Object attribute) { boolean isDisallow = Attributes.getBoolean(attribute, false); disallowIntercept(isDisallow); return true; + case Attributes.Style.ARIA_LABEL: + case Attributes.Style.ARIA_LABEL_LOWER: + return true; default: break; } @@ -84,6 +97,7 @@ private void disallowIntercept(boolean disallow) { public static class RecyclerItem extends Container.RecyclerItem { private int mAttrType = -1; + private String mAttrDescription; public RecyclerItem(int ref, ComponentCreator componentCreator) { super(ref, componentCreator); @@ -94,13 +108,35 @@ public void bindAttrs(Map attrs) { super.bindAttrs(attrs); Object type = getAttrsDomData().get(Attributes.Style.TYPE); + boolean isNeedNotify = false; if (type != null) { int attrType = type.toString().trim().hashCode(); if (attrType != mAttrType) { mAttrType = attrType; - notifyTypeChanged(); + isNeedNotify = true; + } + } + if (null != mComponentCreator) { + Context context = mComponentCreator.getContext(); + boolean isEnableTalkBack = false; + if (null != context) { + isEnableTalkBack = TalkBackUtils.isEnableTalkBack(context, false); + } + if (isEnableTalkBack) { + Object description = getAttrsDomData().get(Attributes.Style.ARIA_LABEL_LOWER); + if (description instanceof String) { + String realDescription = ((String) description); + if (null != realDescription && !realDescription.equals(mAttrDescription)) { + mAttrDescription = realDescription; + isNeedNotify = true; + } + } } } + + if (isNeedNotify) { + notifyTypeChanged(); + } } int getViewType() { @@ -110,6 +146,10 @@ int getViewType() { return mAttrType + getParent().hashCode(); } + String getViewDescription() { + return mAttrDescription; + } + int getColumnSpan() { if (getBoundComponent() != null) { return getBoundComponent() @@ -147,4 +187,27 @@ protected void requestBindTemplate() { notifyItemChanged(); } } + + @Override + public void performComponentClick(MotionEvent event) { + if (mIsEnableTalkBack) { + int allChildCount = getChildCount(); + if (allChildCount > 0) { + Component topChild = getChildAt(0); + if (null != topChild) { + View topHostView = topChild.getHostView(); + if (topHostView instanceof GestureHost) { + GestureHost gestureHost = (GestureHost) topHostView; + IGesture iGesture = null; + if (null != gestureHost) { + iGesture = gestureHost.getGesture(); + } + if (iGesture instanceof GestureDelegate) { + ((GestureDelegate) iGesture).fireClickEvent(event, true); + } + } + } + } + } + } } diff --git a/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/map/CustomMarker.java b/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/map/CustomMarker.java index 4f299633..233b1c02 100644 --- a/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/map/CustomMarker.java +++ b/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/map/CustomMarker.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, the hapjs-platform Project Contributors + * Copyright (c) 2021-present, the hapjs-platform Project Contributors * SPDX-License-Identifier: Apache-2.0 */ @@ -24,7 +24,9 @@ methods = { Component.METHOD_GET_BOUNDING_CLIENT_RECT, Component.METHOD_FOCUS, - Component.METHOD_TO_TEMP_FILE_PATH + Component.METHOD_TO_TEMP_FILE_PATH, + Component.METHOD_TALKBACK_FOCUS, + Component.METHOD_TALKBACK_ANNOUNCE }) public class CustomMarker extends Div { diff --git a/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/map/Map.java b/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/map/Map.java index 4aac8c4d..1ac7f507 100755 --- a/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/map/Map.java +++ b/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/map/Map.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021-2022, the hapjs-platform Project Contributors + * Copyright (c) 2021-present, the hapjs-platform Project Contributors * SPDX-License-Identifier: Apache-2.0 */ @@ -66,7 +66,9 @@ Map.METHOD_SET_MAX_AND_MIN_SCALE_LEVEL, Component.METHOD_GET_BOUNDING_CLIENT_RECT, Component.METHOD_TO_TEMP_FILE_PATH, - Component.METHOD_FOCUS + Component.METHOD_FOCUS, + Component.METHOD_TALKBACK_FOCUS, + Component.METHOD_TALKBACK_ANNOUNCE }) public class Map extends Container { diff --git a/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/picker/DatePicker.java b/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/picker/DatePicker.java index c2347ffe..367682d8 100755 --- a/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/picker/DatePicker.java +++ b/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/picker/DatePicker.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, the hapjs-platform Project Contributors + * Copyright (c) 2021-present, the hapjs-platform Project Contributors * SPDX-License-Identifier: Apache-2.0 */ @@ -36,7 +36,9 @@ Component.METHOD_ANIMATE, Component.METHOD_FOCUS, Component.METHOD_TO_TEMP_FILE_PATH, - Component.METHOD_GET_BOUNDING_CLIENT_RECT + Component.METHOD_GET_BOUNDING_CLIENT_RECT, + Component.METHOD_TALKBACK_FOCUS, + Component.METHOD_TALKBACK_ANNOUNCE }) public class DatePicker extends Picker { diff --git a/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/picker/MultiPicker.java b/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/picker/MultiPicker.java index b0f9f1d4..3656f11c 100755 --- a/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/picker/MultiPicker.java +++ b/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/picker/MultiPicker.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, the hapjs-platform Project Contributors + * Copyright (c) 2021-present, the hapjs-platform Project Contributors * SPDX-License-Identifier: Apache-2.0 */ @@ -42,7 +42,9 @@ Component.METHOD_ANIMATE, Component.METHOD_FOCUS, Component.METHOD_TO_TEMP_FILE_PATH, - Component.METHOD_GET_BOUNDING_CLIENT_RECT + Component.METHOD_GET_BOUNDING_CLIENT_RECT, + Component.METHOD_TALKBACK_FOCUS, + Component.METHOD_TALKBACK_ANNOUNCE }) public class MultiPicker extends Picker { diff --git a/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/picker/TextPicker.java b/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/picker/TextPicker.java index b4f56a51..f135dc53 100755 --- a/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/picker/TextPicker.java +++ b/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/picker/TextPicker.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, the hapjs-platform Project Contributors + * Copyright (c) 2021-present, the hapjs-platform Project Contributors * SPDX-License-Identifier: Apache-2.0 */ @@ -32,7 +32,9 @@ Component.METHOD_ANIMATE, Component.METHOD_FOCUS, Component.METHOD_TO_TEMP_FILE_PATH, - Component.METHOD_GET_BOUNDING_CLIENT_RECT + Component.METHOD_GET_BOUNDING_CLIENT_RECT, + Component.METHOD_TALKBACK_FOCUS, + Component.METHOD_TALKBACK_ANNOUNCE }) public class TextPicker extends Picker { diff --git a/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/picker/TimePicker.java b/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/picker/TimePicker.java index d4b27ace..5faf947f 100755 --- a/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/picker/TimePicker.java +++ b/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/picker/TimePicker.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, the hapjs-platform Project Contributors + * Copyright (c) 2021-present, the hapjs-platform Project Contributors * SPDX-License-Identifier: Apache-2.0 */ @@ -36,7 +36,9 @@ Component.METHOD_ANIMATE, Component.METHOD_FOCUS, Component.METHOD_TO_TEMP_FILE_PATH, - Component.METHOD_GET_BOUNDING_CLIENT_RECT + Component.METHOD_GET_BOUNDING_CLIENT_RECT, + Component.METHOD_TALKBACK_FOCUS, + Component.METHOD_TALKBACK_ANNOUNCE }) public class TimePicker extends Picker { diff --git a/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/progress/CircularProgress.java b/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/progress/CircularProgress.java index 95020795..c7763b29 100755 --- a/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/progress/CircularProgress.java +++ b/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/progress/CircularProgress.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, the hapjs-platform Project Contributors + * Copyright (c) 2021-present, the hapjs-platform Project Contributors * SPDX-License-Identifier: Apache-2.0 */ @@ -12,6 +12,7 @@ import android.view.KeyEvent; import android.view.MotionEvent; import android.view.ViewGroup; +import android.view.accessibility.AccessibilityNodeInfo; import android.widget.ProgressBar; import java.util.Map; import org.hapjs.bridge.annotation.TypeAnnotation; @@ -26,6 +27,7 @@ import org.hapjs.component.view.gesture.IGesture; import org.hapjs.component.view.keyevent.KeyEventDelegate; import org.hapjs.runtime.HapEngine; +import org.hapjs.widgets.R; @WidgetAnnotation( name = Progress.WIDGET_NAME, @@ -33,7 +35,9 @@ Component.METHOD_ANIMATE, Component.METHOD_GET_BOUNDING_CLIENT_RECT, Component.METHOD_TO_TEMP_FILE_PATH, - Component.METHOD_FOCUS + Component.METHOD_FOCUS, + Component.METHOD_TALKBACK_FOCUS, + Component.METHOD_TALKBACK_ANNOUNCE }, types = {@TypeAnnotation(name = CircularProgress.TYPE_CIRCULAR)}) public class CircularProgress extends Progress { @@ -155,5 +159,19 @@ private boolean onKey(int keyAction, int keyCode, KeyEvent event, boolean result result |= mKeyEventDelegate.onKey(keyAction, keyCode, event); return result; } + + @Override + public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfo(info); + info.setClassName(""); + info.setClickable(false); + info.removeAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK); + if (getProgress() >= 0) { + info.setText(mContext.getResources().getString(R.string.talkback_progress_percent) + + getProgress() + + " " + + mContext.getResources().getString(R.string.talkback_progress)); + } + } } } diff --git a/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/progress/HorizontalProgress.java b/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/progress/HorizontalProgress.java index 58e73b83..0043415f 100755 --- a/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/progress/HorizontalProgress.java +++ b/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/progress/HorizontalProgress.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, the hapjs-platform Project Contributors + * Copyright (c) 2021-present, the hapjs-platform Project Contributors * SPDX-License-Identifier: Apache-2.0 */ @@ -10,11 +10,17 @@ import android.graphics.drawable.Drawable; import android.graphics.drawable.GradientDrawable; import android.graphics.drawable.LayerDrawable; +import android.os.Build; import android.text.TextUtils; import android.view.Gravity; +import android.view.View; import android.view.ViewGroup; +import android.view.accessibility.AccessibilityNodeInfo; import android.widget.FrameLayout; import android.widget.ProgressBar; + +import androidx.annotation.RequiresApi; + import java.util.Map; import org.hapjs.bridge.annotation.TypeAnnotation; import org.hapjs.bridge.annotation.WidgetAnnotation; @@ -25,6 +31,7 @@ import org.hapjs.component.constants.Attributes; import org.hapjs.component.view.GestureFrameLayout; import org.hapjs.runtime.HapEngine; +import org.hapjs.widgets.R; @WidgetAnnotation( name = Progress.WIDGET_NAME, @@ -32,7 +39,9 @@ Component.METHOD_ANIMATE, Component.METHOD_GET_BOUNDING_CLIENT_RECT, Component.METHOD_TO_TEMP_FILE_PATH, - Component.METHOD_FOCUS + Component.METHOD_FOCUS, + Component.METHOD_TALKBACK_FOCUS, + Component.METHOD_TALKBACK_ANNOUNCE }, types = {@TypeAnnotation(name = HorizontalProgress.TYPE_HORIZONTAL, isDefault = true)}) public class HorizontalProgress extends Progress { @@ -79,6 +88,7 @@ protected FrameLayout createViewImpl() { layer.setId(1, android.R.id.progress); mProgressBar = new ProgressBar(mContext, null, android.R.attr.progressBarStyleHorizontal); + initTalkBack(); mProgressBar.setProgressDrawable(layer); FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams( @@ -91,6 +101,40 @@ protected FrameLayout createViewImpl() { return frameLayout; } + private void initTalkBack() { + if (isEnableTalkBack() && null != mProgressBar) { + mProgressBar.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() { + @Override + public void onViewAttachedToWindow(View v) { + mProgressBar.setAccessibilityDelegate(new View.AccessibilityDelegate() { + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + @Override + public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfo(host, info); + info.setClassName(""); + info.setClickable(false); + info.removeAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK); + if (null != mProgressBar && mProgressBar.getProgress() >= 0 && null != mContext) { + info.setText(mContext.getResources().getString(R.string.talkback_progress_percent) + + mProgressBar.getProgress() + + " " + + mContext.getResources().getString(R.string.talkback_progress)); + } + } + }); + } + + @Override + public void onViewDetachedFromWindow(View v) { + if (null != mProgressBar) { + mProgressBar.setAccessibilityDelegate(null); + mProgressBar.removeOnAttachStateChangeListener(this); + } + } + }); + } + } + @Override protected boolean setAttribute(String key, Object attribute) { switch (key) { diff --git a/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/refresh/Refresh2.java b/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/refresh/Refresh2.java index aa89edff..c0a6f39c 100644 --- a/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/refresh/Refresh2.java +++ b/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/refresh/Refresh2.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, the hapjs-platform Project Contributors + * Copyright (c) 2021-present, the hapjs-platform Project Contributors * SPDX-License-Identifier: Apache-2.0 */ @@ -47,7 +47,9 @@ Refresh2.METHOD_START_PULLDOWN_REFRESH, Refresh2.METHOD_START_PULLUP_REFRESH, Refresh2.METHOD_STOP_PULLDOWN_REFRESH, - Refresh2.METHOD_STOP_PULLUP_REFRESH + Refresh2.METHOD_STOP_PULLUP_REFRESH, + Component.METHOD_TALKBACK_FOCUS, + Component.METHOD_TALKBACK_ANNOUNCE }) public class Refresh2 extends Container { diff --git a/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/refresh/RefreshFooter.java b/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/refresh/RefreshFooter.java index 4cf9f6d1..b2213862 100644 --- a/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/refresh/RefreshFooter.java +++ b/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/refresh/RefreshFooter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, the hapjs-platform Project Contributors + * Copyright (c) 2021-present, the hapjs-platform Project Contributors * SPDX-License-Identifier: Apache-2.0 */ @@ -19,7 +19,9 @@ Component.METHOD_ANIMATE, Component.METHOD_GET_BOUNDING_CLIENT_RECT, Component.METHOD_TO_TEMP_FILE_PATH, - Component.METHOD_FOCUS + Component.METHOD_FOCUS, + Component.METHOD_TALKBACK_FOCUS, + Component.METHOD_TALKBACK_ANNOUNCE }) public class RefreshFooter extends ExtensionBase { protected static final String WIDGET_NAME = "refresh-footer"; diff --git a/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/refresh/RefreshHeader.java b/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/refresh/RefreshHeader.java index d673d567..09a5783c 100644 --- a/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/refresh/RefreshHeader.java +++ b/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/refresh/RefreshHeader.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, the hapjs-platform Project Contributors + * Copyright (c) 2021-present, the hapjs-platform Project Contributors * SPDX-License-Identifier: Apache-2.0 */ @@ -19,7 +19,9 @@ Component.METHOD_ANIMATE, Component.METHOD_GET_BOUNDING_CLIENT_RECT, Component.METHOD_TO_TEMP_FILE_PATH, - Component.METHOD_FOCUS + Component.METHOD_FOCUS, + Component.METHOD_TALKBACK_FOCUS, + Component.METHOD_TALKBACK_ANNOUNCE }) public class RefreshHeader extends ExtensionBase { diff --git a/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/sectionlist/SectionGroup.java b/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/sectionlist/SectionGroup.java index 8fe6be01..70ca5beb 100644 --- a/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/sectionlist/SectionGroup.java +++ b/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/sectionlist/SectionGroup.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, the hapjs-platform Project Contributors + * Copyright (c) 2021-present, the hapjs-platform Project Contributors * SPDX-License-Identifier: Apache-2.0 */ @@ -39,7 +39,9 @@ Component.METHOD_TO_TEMP_FILE_PATH, Component.METHOD_FOCUS, SectionGroup.METHOD_EXPAND, - SectionGroup.METHOD_SCROLL_TO + SectionGroup.METHOD_SCROLL_TO, + Component.METHOD_TALKBACK_FOCUS, + Component.METHOD_TALKBACK_ANNOUNCE }) public class SectionGroup extends AbstractScrollable { diff --git a/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/sectionlist/SectionHeader.java b/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/sectionlist/SectionHeader.java index a23f0142..dbed22f5 100644 --- a/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/sectionlist/SectionHeader.java +++ b/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/sectionlist/SectionHeader.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, the hapjs-platform Project Contributors + * Copyright (c) 2021-present, the hapjs-platform Project Contributors * SPDX-License-Identifier: Apache-2.0 */ @@ -27,7 +27,9 @@ Component.METHOD_ANIMATE, Component.METHOD_GET_BOUNDING_CLIENT_RECT, Component.METHOD_TO_TEMP_FILE_PATH, - Component.METHOD_FOCUS + Component.METHOD_FOCUS, + Component.METHOD_TALKBACK_FOCUS, + Component.METHOD_TALKBACK_ANNOUNCE }) public class SectionHeader extends Container { diff --git a/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/sectionlist/SectionItem.java b/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/sectionlist/SectionItem.java index a9c0b127..1a3c85ac 100644 --- a/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/sectionlist/SectionItem.java +++ b/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/sectionlist/SectionItem.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, the hapjs-platform Project Contributors + * Copyright (c) 2021-present, the hapjs-platform Project Contributors * SPDX-License-Identifier: Apache-2.0 */ @@ -23,7 +23,9 @@ Component.METHOD_ANIMATE, Component.METHOD_GET_BOUNDING_CLIENT_RECT, Component.METHOD_TO_TEMP_FILE_PATH, - Component.METHOD_FOCUS + Component.METHOD_FOCUS, + Component.METHOD_TALKBACK_FOCUS, + Component.METHOD_TALKBACK_ANNOUNCE }) public class SectionItem extends Container { diff --git a/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/sectionlist/SectionList.java b/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/sectionlist/SectionList.java index 082b8cf0..343dab8d 100644 --- a/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/sectionlist/SectionList.java +++ b/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/sectionlist/SectionList.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, the hapjs-platform Project Contributors + * Copyright (c) 2021-present, the hapjs-platform Project Contributors * SPDX-License-Identifier: Apache-2.0 */ @@ -42,7 +42,9 @@ Component.METHOD_GET_BOUNDING_CLIENT_RECT, Component.METHOD_TO_TEMP_FILE_PATH, Component.METHOD_FOCUS, - SectionList.METHOD_SCROLL_TO + SectionList.METHOD_SCROLL_TO, + Component.METHOD_TALKBACK_FOCUS, + Component.METHOD_TALKBACK_ANNOUNCE }) public class SectionList extends AbstractScrollable implements Recycler, SwipeObserver { diff --git a/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/tab/TabBar.java b/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/tab/TabBar.java index a24af229..e5c06b48 100755 --- a/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/tab/TabBar.java +++ b/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/tab/TabBar.java @@ -6,6 +6,7 @@ package org.hapjs.widgets.tab; import android.content.Context; +import android.text.TextUtils; import android.util.AttributeSet; import android.util.Log; import android.view.Gravity; @@ -15,6 +16,9 @@ import android.widget.LinearLayout; import com.facebook.yoga.YogaNode; import com.google.android.material.tabs.TabLayout; + +import java.util.ArrayList; +import java.util.List; import java.util.Map; import org.hapjs.bridge.annotation.WidgetAnnotation; import org.hapjs.component.AbstractScrollable; @@ -31,7 +35,10 @@ import org.hapjs.component.view.helper.StateHelper; import org.hapjs.component.view.state.State; import org.hapjs.runtime.HapEngine; +import org.hapjs.widgets.R; import org.hapjs.widgets.view.PercentTabLayout; +import org.json.JSONArray; +import org.json.JSONException; @WidgetAnnotation( name = TabBar.WIDGET_NAME, @@ -39,13 +46,18 @@ Component.METHOD_ANIMATE, Component.METHOD_GET_BOUNDING_CLIENT_RECT, Component.METHOD_TO_TEMP_FILE_PATH, - Component.METHOD_FOCUS + Component.METHOD_FOCUS, + Component.METHOD_TALKBACK_FOCUS, + Component.METHOD_TALKBACK_ANNOUNCE }) public class TabBar extends AbstractScrollable implements OnDomTreeChangeListener, HScrollable { protected static final String WIDGET_NAME = "tab-bar"; private static final String TAG = "TabBar"; private int mDomIndex; + private static final String TALKBACK_TABS_LABELS = "arialabels"; + private List mDescriptions = null; + public TabBar( HapEngine hapEngine, @@ -114,6 +126,9 @@ protected boolean setAttribute(String key, Object attribute) { String mode = Attributes.getString(attribute, Attributes.Mode.FIXED); setMode(mode); return true; + case TALKBACK_TABS_LABELS: + initTabsDesp(attribute); + return true; default: break; } @@ -121,6 +136,24 @@ protected boolean setAttribute(String key, Object attribute) { return super.setAttribute(key, attribute); } + private void initTabsDesp(Object attribute) { + boolean isTalkBackEnable = isEnableTalkBack(); + if (isTalkBackEnable && attribute instanceof JSONArray) { + mDescriptions = new ArrayList<>(); + JSONArray jsonArray = (JSONArray) attribute; + for (int i = 0; i < jsonArray.length(); i++) { + try { + Object description = jsonArray.get(i); + if (description instanceof String) { + mDescriptions.add((String) description); + } + } catch (JSONException e) { + Log.e(TAG, "initTabsDesp failed ", e); + } + } + } + } + private void setMode(String modeStr) { if (mHost == null) { return; @@ -186,7 +219,13 @@ public void addView(View childView, final int index) { ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); wrapper.setLayoutParams(layoutParams); wrapper.addView(childView); - + if (isEnableTalkBack()) { + String description = null; + if (null != mDescriptions && index < mDescriptions.size()) { + description = mDescriptions.get(index); + } + wrapper.initTalkBack(childView, index, description); + } tab.setCustomView(wrapper); mHost.addTab(tab, index, false); @@ -309,6 +348,7 @@ private static class TabBarLayoutWrapper extends LinearLayout { public TabBarLayoutWrapper(Context context) { super(context); + setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES); } public TabBarLayoutWrapper(Context context, AttributeSet attrs) { @@ -319,6 +359,16 @@ public TabBarLayoutWrapper(Context context, AttributeSet attrs, int defStyleAttr super(context, attrs, defStyleAttr); } + public void initTalkBack(View childView, int index, String description) { + if (null != childView && index >= 0) { + if (TextUtils.isEmpty(description)) { + setContentDescription(childView.getResources().getString(R.string.talkback_notitle_defaultstr)); + } else { + setContentDescription(description); + } + } + } + @Override public boolean dispatchTouchEvent(MotionEvent ev) { super.dispatchTouchEvent(ev); diff --git a/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/tab/TabContent.java b/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/tab/TabContent.java index 1cfd2093..1f717d9c 100644 --- a/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/tab/TabContent.java +++ b/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/tab/TabContent.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, the hapjs-platform Project Contributors + * Copyright (c) 2021-present, the hapjs-platform Project Contributors * SPDX-License-Identifier: Apache-2.0 */ @@ -33,7 +33,9 @@ Component.METHOD_ANIMATE, Component.METHOD_GET_BOUNDING_CLIENT_RECT, Component.METHOD_TO_TEMP_FILE_PATH, - Component.METHOD_FOCUS + Component.METHOD_FOCUS, + Component.METHOD_TALKBACK_FOCUS, + Component.METHOD_TALKBACK_ANNOUNCE }) public class TabContent extends AbstractScrollable implements SwipeObserver, HScrollable { diff --git a/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/tab/Tabs.java b/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/tab/Tabs.java index 2d9e13a4..3e176414 100755 --- a/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/tab/Tabs.java +++ b/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/tab/Tabs.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, the hapjs-platform Project Contributors + * Copyright (c) 2021-present, the hapjs-platform Project Contributors * SPDX-License-Identifier: Apache-2.0 */ @@ -34,7 +34,9 @@ Component.METHOD_ANIMATE, Component.METHOD_GET_BOUNDING_CLIENT_RECT, Component.METHOD_TO_TEMP_FILE_PATH, - Component.METHOD_FOCUS + Component.METHOD_FOCUS, + Component.METHOD_TALKBACK_FOCUS, + Component.METHOD_TALKBACK_ANNOUNCE }) public class Tabs extends Container { diff --git a/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/text/HtmlText.java b/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/text/HtmlText.java index 7e17d129..97f6d197 100755 --- a/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/text/HtmlText.java +++ b/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/text/HtmlText.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, the hapjs-platform Project Contributors + * Copyright (c) 2021-present, the hapjs-platform Project Contributors * SPDX-License-Identifier: Apache-2.0 */ @@ -45,7 +45,9 @@ Component.METHOD_ANIMATE, Component.METHOD_GET_BOUNDING_CLIENT_RECT, Component.METHOD_TO_TEMP_FILE_PATH, - Component.METHOD_FOCUS + Component.METHOD_FOCUS, + Component.METHOD_TALKBACK_FOCUS, + Component.METHOD_TALKBACK_ANNOUNCE }, types = {@TypeAnnotation(name = HtmlText.TYPE_HTML)}) public class HtmlText extends AbstractText { diff --git a/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/text/Marquee.java b/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/text/Marquee.java index eacf8138..206342b9 100644 --- a/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/text/Marquee.java +++ b/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/text/Marquee.java @@ -39,7 +39,9 @@ Component.METHOD_ANIMATE, Component.METHOD_GET_BOUNDING_CLIENT_RECT, Component.METHOD_FOCUS, - Component.METHOD_TO_TEMP_FILE_PATH + Component.METHOD_TO_TEMP_FILE_PATH, + Component.METHOD_TALKBACK_FOCUS, + Component.METHOD_TALKBACK_ANNOUNCE }) public class Marquee extends Container { // attribute diff --git a/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/text/RichText.java b/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/text/RichText.java index 55d6fcd8..0f2b67ff 100644 --- a/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/text/RichText.java +++ b/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/text/RichText.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021-2022, the hapjs-platform Project Contributors + * Copyright (c) 2021-present, the hapjs-platform Project Contributors * SPDX-License-Identifier: Apache-2.0 */ @@ -42,7 +42,9 @@ Component.METHOD_GET_BOUNDING_CLIENT_RECT, Component.METHOD_ANIMATE, Component.METHOD_TO_TEMP_FILE_PATH, - Component.METHOD_FOCUS + Component.METHOD_FOCUS, + Component.METHOD_TALKBACK_FOCUS, + Component.METHOD_TALKBACK_ANNOUNCE }) public class RichText extends Container { protected static final String WIDGET_NAME = "richtext"; diff --git a/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/text/Text.java b/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/text/Text.java index 26fe28b3..d4b13599 100755 --- a/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/text/Text.java +++ b/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/text/Text.java @@ -48,7 +48,9 @@ Component.METHOD_ANIMATE, Component.METHOD_GET_BOUNDING_CLIENT_RECT, Component.METHOD_TO_TEMP_FILE_PATH, - Component.METHOD_FOCUS + Component.METHOD_FOCUS, + Component.METHOD_TALKBACK_FOCUS, + Component.METHOD_TALKBACK_ANNOUNCE }, types = {@TypeAnnotation(name = Text.TYPE_TEXT, isDefault = true)}) public class Text extends AbstractText implements SwipeObserver { diff --git a/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/video/Video.java b/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/video/Video.java index 128c2529..19b88d53 100644 --- a/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/video/Video.java +++ b/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/video/Video.java @@ -62,7 +62,9 @@ Video.METHOD_SNAP_SHOT, Component.METHOD_REQUEST_FULLSCREEN, Component.METHOD_GET_BOUNDING_CLIENT_RECT, - Component.METHOD_FOCUS + Component.METHOD_FOCUS, + Component.METHOD_TALKBACK_FOCUS, + Component.METHOD_TALKBACK_ANNOUNCE }) public class Video extends Component implements SwipeObserver { diff --git a/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/view/image/FlexImageView.java b/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/view/image/FlexImageView.java index 729150b1..8d4967fb 100755 --- a/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/view/image/FlexImageView.java +++ b/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/view/image/FlexImageView.java @@ -28,10 +28,12 @@ import android.view.View; import android.view.ViewGroup; import android.view.ViewTreeObserver; +import android.view.accessibility.AccessibilityNodeInfo; import android.widget.ImageView; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.annotation.RequiresApi; import com.facebook.common.executors.UiThreadImmediateExecutorService; import com.facebook.common.util.UriUtil; @@ -1361,6 +1363,15 @@ public void process(Bitmap output, Bitmap source) { } } + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + @Override + public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfo(info); + info.setClassName(""); + info.setClickable(false); + info.removeAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK); + } + private class ImageSizeDetectRequestListener extends BaseRequestListener { @Override public void onProducerFinishWithSuccess(String requestId, String producerName, @javax.annotation.Nullable Map extraMap) { diff --git a/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/view/lottie/Lottie.java b/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/view/lottie/Lottie.java index 718ec3e4..687a9a3c 100644 --- a/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/view/lottie/Lottie.java +++ b/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/view/lottie/Lottie.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021-2022, the hapjs-platform Project Contributors + * Copyright (c) 2021-present, the hapjs-platform Project Contributors * SPDX-License-Identifier: Apache-2.0 */ package org.hapjs.widgets.view.lottie; @@ -31,7 +31,9 @@ Lottie.METHOD_RESUME, Lottie.METHOD_RESET, Component.METHOD_GET_BOUNDING_CLIENT_RECT, - Component.METHOD_FOCUS + Component.METHOD_FOCUS, + Component.METHOD_TALKBACK_FOCUS, + Component.METHOD_TALKBACK_ANNOUNCE } ) public class Lottie extends Component { diff --git a/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/view/swiper/LoopPagerAdapter.java b/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/view/swiper/LoopPagerAdapter.java index 4f3c2c64..79a0978a 100644 --- a/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/view/swiper/LoopPagerAdapter.java +++ b/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/view/swiper/LoopPagerAdapter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, the hapjs-platform Project Contributors + * Copyright (c) 2021-present, the hapjs-platform Project Contributors * SPDX-License-Identifier: Apache-2.0 */ @@ -7,6 +7,7 @@ import android.os.Handler; import android.os.Looper; +import android.text.TextUtils; import android.util.ArrayMap; import android.util.Log; import android.view.View; @@ -18,6 +19,8 @@ import org.hapjs.component.Component; import org.hapjs.component.Container; import org.hapjs.component.RecyclerDataItem; +import org.hapjs.system.utils.TalkBackUtils; +import org.hapjs.widgets.R; public class LoopPagerAdapter extends BaseLoopPagerAdapter { @@ -35,6 +38,7 @@ public class LoopPagerAdapter extends BaseLoopPagerAdapter { private Map mCreatedComponents = new ArrayMap<>(); private Container.RecyclerItem mContainerDataItem; private Handler mMainHandler = new Handler(Looper.getMainLooper()); + private boolean mIsEnableTalkBack; private Runnable mNotify = new Runnable() { @Override @@ -49,6 +53,7 @@ public void run() { public LoopPagerAdapter(LoopViewPager viewPager) { mViewPager = viewPager; + mIsEnableTalkBack = TalkBackUtils.isEnableTalkBack((null != viewPager ? viewPager.getContext() : null), false); } public void setData(Container container, Container.RecyclerItem data) { @@ -100,7 +105,15 @@ public Object instantiateActualItem(ViewGroup container, int position) { item.dispatchBindComponent(component); mContainer.addChild(component); // it will be destroy when mContainer destroy container.addView(component.getHostView()); - + if (mIsEnableTalkBack) { + View tmpHostView = component.getHostView(); + if (null != tmpHostView) { + CharSequence contentDesp = tmpHostView.getContentDescription(); + if (TextUtils.isEmpty(contentDesp)) { + tmpHostView.setContentDescription(tmpHostView.getResources().getString(R.string.talkback_notitle_defaultstr)); + } + } + } mCreatedComponents.put(component, item); return component; diff --git a/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/view/text/FlexCheckBox.java b/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/view/text/FlexCheckBox.java index e1e20f37..545d4b70 100644 --- a/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/view/text/FlexCheckBox.java +++ b/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/view/text/FlexCheckBox.java @@ -1,13 +1,18 @@ /* - * Copyright (c) 2021, the hapjs-platform Project Contributors + * Copyright (c) 2021-present, the hapjs-platform Project Contributors * SPDX-License-Identifier: Apache-2.0 */ package org.hapjs.widgets.view.text; import android.content.Context; +import android.os.Build; +import android.text.TextUtils; import android.view.KeyEvent; import android.view.MotionEvent; +import android.view.accessibility.AccessibilityNodeInfo; + +import androidx.annotation.RequiresApi; import androidx.appcompat.widget.AppCompatCheckBox; import org.hapjs.component.Component; import org.hapjs.component.view.ComponentHost; @@ -15,17 +20,64 @@ import org.hapjs.component.view.gesture.IGesture; import org.hapjs.component.view.helper.StateHelper; import org.hapjs.component.view.keyevent.KeyEventDelegate; +import org.hapjs.widgets.R; public class FlexCheckBox extends AppCompatCheckBox implements ComponentHost, GestureHost { private Component mComponent; private KeyEventDelegate mKeyEventDelegate; private IGesture mGesture; + private boolean mIsEnableTalkBack; + private String mValue; public FlexCheckBox(Context context) { super(context); } + public FlexCheckBox(Context context, boolean isEnableTalkBack) { + super(context); + mIsEnableTalkBack = isEnableTalkBack; + } + + public void setValue(String value) { + this.mValue = value; + } + + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + @Override + public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfo(info); + initTalkBack(info); + } + + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + private void initTalkBack(AccessibilityNodeInfo info) { + if (mIsEnableTalkBack && null != info) { + info.removeAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK); + info.setClassName(""); + info.setCheckable(false); + info.setClickable(false); + String realTextStr = ""; + if (!TextUtils.isEmpty(mValue)) { + realTextStr = mValue; + } + if (TextUtils.isEmpty(realTextStr)) { + info.setText((isChecked() ? getResources().getString(R.string.talkback_selected) : getResources().getString(R.string.talkback_unselected)) + + " " + + getResources().getString(R.string.talkback_checkbox) + + " " + + getResources().getString(R.string.talkback_no_use)); + } else { + info.setText((isChecked() ? getResources().getString(R.string.talkback_selected) : getResources().getString(R.string.talkback_unselected)) + + " " + + realTextStr + + " " + + getResources().getString(R.string.talkback_press_active_min) + + (isChecked() ? getResources().getString(R.string.talkback_cancel_select) : getResources().getString(R.string.talkback_select))); + } + } + } + @Override protected void drawableStateChanged() { super.drawableStateChanged(); diff --git a/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/view/text/FlexEditText.java b/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/view/text/FlexEditText.java index a76ab4a3..e67b6d5e 100644 --- a/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/view/text/FlexEditText.java +++ b/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/view/text/FlexEditText.java @@ -1,20 +1,24 @@ /* - * Copyright (c) 2021, the hapjs-platform Project Contributors + * Copyright (c) 2021-present, the hapjs-platform Project Contributors * SPDX-License-Identifier: Apache-2.0 */ package org.hapjs.widgets.view.text; import android.content.Context; +import android.os.Build; import android.text.Editable; import android.text.Spannable; import android.text.TextUtils; import android.view.KeyEvent; import android.view.MotionEvent; +import android.view.accessibility.AccessibilityNodeInfo; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputConnectionWrapper; import android.view.inputmethod.InputMethodManager; + +import androidx.annotation.RequiresApi; import androidx.appcompat.widget.AppCompatAutoCompleteTextView; import org.hapjs.common.compat.BuildPlatform; import org.hapjs.component.Component; @@ -23,6 +27,7 @@ import org.hapjs.component.view.gesture.IGesture; import org.hapjs.component.view.helper.StateHelper; import org.hapjs.component.view.keyevent.KeyEventDelegate; +import org.hapjs.widgets.R; import org.hapjs.widgets.input.Edit; public class FlexEditText extends AppCompatAutoCompleteTextView @@ -35,12 +40,58 @@ public class FlexEditText extends AppCompatAutoCompleteTextView private IGesture mGesture; private boolean mAutoCompleted = true; private boolean mLastWindowFocus = false; + private boolean mIsEnableTalkBack; public FlexEditText(Context context) { super(context); setThreshold(0); } + public FlexEditText(Context context, boolean isEnableTalkBack) { + super(context); + setThreshold(0); + mIsEnableTalkBack = isEnableTalkBack; + } + + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + private void initTalkBack(AccessibilityNodeInfo info) { + if (mIsEnableTalkBack && null != info) { + info.removeAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK); + info.removeAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_LONG_CLICK); + info.setClickable(false); + info.setLongClickable(false); + info.setClassName(""); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + info.setHintText(""); + } + Editable curTextStr = getText(); + String realTextStr = ""; + if (null != curTextStr && !TextUtils.isEmpty(curTextStr.toString())) { + realTextStr = curTextStr.toString(); + } + if (TextUtils.isEmpty(realTextStr)) { + info.setText(getResources().getString(R.string.talkback_edit_input) + + " " + + getResources().getString(R.string.talkback_press_active)); + } else { + info.setText((!TextUtils.isEmpty(getHint()) ? getHint() : "") + + " " + + getResources().getString(R.string.talkback_edit) + + " " + + (!TextUtils.isEmpty(realTextStr) ? realTextStr : "") + + " " + + getResources().getString(R.string.talkback_press_active_more)); + } + } + } + + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + @Override + public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfo(info); + initTalkBack(info); + } + @Override protected void drawableStateChanged() { super.drawableStateChanged(); diff --git a/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/view/text/FlexRadioButton.java b/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/view/text/FlexRadioButton.java index 34946d2b..372e4986 100644 --- a/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/view/text/FlexRadioButton.java +++ b/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/view/text/FlexRadioButton.java @@ -1,13 +1,16 @@ /* - * Copyright (c) 2021, the hapjs-platform Project Contributors + * Copyright (c) 2021-present, the hapjs-platform Project Contributors * SPDX-License-Identifier: Apache-2.0 */ package org.hapjs.widgets.view.text; import android.content.Context; +import android.text.TextUtils; import android.view.KeyEvent; import android.view.MotionEvent; +import android.view.accessibility.AccessibilityNodeInfo; + import androidx.appcompat.widget.AppCompatRadioButton; import org.hapjs.component.Component; import org.hapjs.component.view.ComponentHost; @@ -15,16 +18,61 @@ import org.hapjs.component.view.gesture.IGesture; import org.hapjs.component.view.helper.StateHelper; import org.hapjs.component.view.keyevent.KeyEventDelegate; +import org.hapjs.widgets.R; public class FlexRadioButton extends AppCompatRadioButton implements ComponentHost, GestureHost { private Component mComponent; private IGesture mGesture; private KeyEventDelegate mKeyEventDelegate; + private boolean mIsEnableTalkBack; + private String mValue; public FlexRadioButton(Context context) { super(context); } + public FlexRadioButton(Context context, boolean isEnableTalkBack) { + super(context); + mIsEnableTalkBack = isEnableTalkBack; + } + + public void setValue(String value) { + this.mValue = value; + } + + private void initTalkBack(AccessibilityNodeInfo info) { + if (mIsEnableTalkBack && null != info) { + info.removeAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK); + info.setClassName(""); + info.setCheckable(false); + info.setClickable(false); + String realTextStr = ""; + if (!TextUtils.isEmpty(mValue)) { + realTextStr = mValue; + } + if (TextUtils.isEmpty(realTextStr)) { + info.setText((isChecked() ? getResources().getString(R.string.talkback_selected) : getResources().getString(R.string.talkback_unselected)) + + " " + + getResources().getString(R.string.talkback_radio) + + " " + + getResources().getString(R.string.talkback_no_use)); + } else { + info.setText((isChecked() ? getResources().getString(R.string.talkback_selected) : getResources().getString(R.string.talkback_unselected)) + + " " + + realTextStr + + " " + + getResources().getString(R.string.talkback_press_active_min) + + (isChecked() ? getResources().getString(R.string.talkback_cancel_select) : getResources().getString(R.string.talkback_select))); + } + } + } + + @Override + public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfo(info); + initTalkBack(info); + } + @Override protected void drawableStateChanged() { super.drawableStateChanged(); diff --git a/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/view/video/MediaController.java b/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/view/video/MediaController.java index 4c95edf7..9042d5b0 100644 --- a/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/view/video/MediaController.java +++ b/core/runtime/android/widgets/src/main/java/org/hapjs/widgets/view/video/MediaController.java @@ -28,6 +28,7 @@ import org.hapjs.component.Component; import org.hapjs.model.videodata.VideoCacheData; import org.hapjs.model.videodata.VideoCacheManager; +import org.hapjs.system.utils.TalkBackUtils; import org.hapjs.widgets.R; import org.hapjs.widgets.video.ExoPlayer; import org.hapjs.widgets.video.IMediaPlayer; @@ -112,6 +113,7 @@ public void onClick(View v) { private TextView mTitle; private int mTitleHeight; private final String TAG = "MediaController"; + private boolean mIsEnableTalkBack; private FullscreenChangeListener mFullScreenChangeListener; private OnSeekBarChangeListener mOnSeekBarChangeListener; // There are two scenarios that can trigger the seekbar listener to trigger: @@ -205,6 +207,7 @@ public MediaController(Context context, AttributeSet attrs) { public MediaController(Context context, boolean useFastForward) { super(context); mContext = context; + mIsEnableTalkBack = TalkBackUtils.isEnableTalkBack(mContext, false); makeControllerView(); } @@ -245,6 +248,9 @@ private void initControllerView(View v) { if (mPauseButton != null) { mPauseButton.requestFocus(); mPauseButton.setOnClickListener(mPauseListener); + if (mIsEnableTalkBack) { + mPauseButton.setContentDescription(getContext().getResources().getString(R.string.talkback_video_controller_play)); + } } mProgress = (SeekBar) v.findViewById(R.id.mediacontroller_progress); if (mProgress != null) { @@ -279,6 +285,10 @@ public void onClick(View v) { mTitleBarContainer = v.findViewById(R.id.title_bar_container); v.findViewById(R.id.back_arrow).setOnClickListener(view -> mVideoView.exitFullscreen()); mTitle = v.findViewById(R.id.title); + if (mIsEnableTalkBack) { + v.findViewById(R.id.back_arrow).setContentDescription(getContext().getResources().getString(R.string.talkback_video_controller_exit_full_screen)); + mFullButton.setContentDescription(getContext().getResources().getString(R.string.talkback_video_controller_full_screen)); + } } private void applyButtonVisibility() { @@ -591,8 +601,14 @@ private void updatePausePlay() { if (mPlayer.isPlaying()) { mPauseButton.setImageResource(R.drawable.ic_media_pause); + if (mIsEnableTalkBack) { + mPauseButton.setContentDescription(getContext().getResources().getString(R.string.talkback_video_controller_pause)); + } } else { mPauseButton.setImageResource(R.drawable.ic_media_play); + if (mIsEnableTalkBack) { + mPauseButton.setContentDescription(getContext().getResources().getString(R.string.talkback_video_controller_play)); + } } } @@ -642,7 +658,9 @@ public void setOnSeekBarChangeListener(OnSeekBarChangeListener l) { public void enterFullscreen() { mFullButton.setImageResource(R.drawable.ic_media_exit_fullscreen); - + if (mIsEnableTalkBack) { + mFullButton.setContentDescription(getContext().getResources().getString(R.string.talkback_video_controller_exit_full_screen)); + } mTitleBarContainer.setVisibility(VISIBLE); View statusBarPlaceholder = mTitleBarContainer.findViewById(R.id.status_bar_bg); LinearLayout.LayoutParams params = @@ -658,7 +676,9 @@ public void enterFullscreen() { public void exitFullscreen() { mFullButton.setImageResource(R.drawable.ic_media_enter_fullscreen); - + if (mIsEnableTalkBack) { + mFullButton.setContentDescription(getContext().getResources().getString(R.string.talkback_video_controller_full_screen)); + } mTitleBarContainer.setVisibility(GONE); mVideoView.getComponent().getRootComponent().resetStatusBar(); } diff --git a/core/runtime/android/widgets/src/main/res/values-zh-rCN/strings.xml b/core/runtime/android/widgets/src/main/res/values-zh-rCN/strings.xml index 8c88a4b7..9d0dc196 100644 --- a/core/runtime/android/widgets/src/main/res/values-zh-rCN/strings.xml +++ b/core/runtime/android/widgets/src/main/res/values-zh-rCN/strings.xml @@ -38,4 +38,29 @@ 拒绝 允许 \u3000\u3000 + + 未加标签控件 + + 输入框 + 编辑框 + 点按两次即可激活 + 点按两次并长按即可弹出更多选项 + + 已选中 + 未选中 + 复选框 + 已停用 + 点按两次即可 + 取消选中 + 选中 + + 单选框 + + 百分之 + 进度条 + + 播放 + 暂停 + 全屏 + 退出全屏 diff --git a/core/runtime/android/widgets/src/main/res/values/strings.xml b/core/runtime/android/widgets/src/main/res/values/strings.xml index 7ff919ae..2e5b453c 100644 --- a/core/runtime/android/widgets/src/main/res/values/strings.xml +++ b/core/runtime/android/widgets/src/main/res/values/strings.xml @@ -39,4 +39,29 @@ Decline Allow \u3000\u3000 + + Unlabeled components + + Input box + Edit Box + Click twice to activate + Tap twice and hold for more options + + Selected + Unselected + check box + deactivated + Click twice + Uncheck + Check + + Radio + + Percent + Progress bar + + play + pause + full screen + exit full screen