Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
sxychenjing
engine
提交
f4d6ce13
E
engine
项目概览
sxychenjing
/
engine
与 Fork 源项目一致
从无法访问的项目Fork
通知
3
Star
0
Fork
0
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
0
列表
看板
标记
里程碑
合并请求
0
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
E
engine
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
0
Issue
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
Pages
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
提交
Issue看板
体验新版 GitCode,发现更多精彩内容 >>
未验证
提交
f4d6ce13
编写于
4月 16, 2020
作者:
M
Michael Goderbauer
提交者:
GitHub
4月 16, 2020
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
Clear focus if a platform view goes away (#17381)
上级
ef161fb5
变更
3
隐藏空白更改
内联
并排
Showing
3 changed file
with
173 addition
and
22 deletion
+173
-22
shell/platform/android/io/flutter/view/AccessibilityBridge.java
...platform/android/io/flutter/view/AccessibilityBridge.java
+39
-2
shell/platform/android/io/flutter/view/AccessibilityViewEmbedder.java
...rm/android/io/flutter/view/AccessibilityViewEmbedder.java
+13
-1
shell/platform/android/test/io/flutter/view/AccessibilityBridgeTest.java
...android/test/io/flutter/view/AccessibilityBridgeTest.java
+121
-19
未找到文件。
shell/platform/android/io/flutter/view/AccessibilityBridge.java
浏览文件 @
f4d6ce13
...
...
@@ -26,6 +26,7 @@ import android.view.accessibility.AccessibilityNodeProvider;
import
androidx.annotation.NonNull
;
import
androidx.annotation.Nullable
;
import
androidx.annotation.RequiresApi
;
import
androidx.annotation.VisibleForTesting
;
import
io.flutter.BuildConfig
;
import
io.flutter.embedding.engine.systemchannels.AccessibilityChannel
;
import
io.flutter.plugin.platform.PlatformViewsAccessibilityDelegate
;
...
...
@@ -333,10 +334,32 @@ public class AccessibilityBridge extends AccessibilityNodeProvider {
// TODO(mattcarrol): Add the annotation once the plumbing is done.
// https://github.com/flutter/flutter/issues/29618
PlatformViewsAccessibilityDelegate
platformViewsAccessibilityDelegate
)
{
this
(
rootAccessibilityView
,
accessibilityChannel
,
accessibilityManager
,
contentResolver
,
new
AccessibilityViewEmbedder
(
rootAccessibilityView
,
MIN_ENGINE_GENERATED_NODE_ID
),
platformViewsAccessibilityDelegate
);
}
@VisibleForTesting
public
AccessibilityBridge
(
@NonNull
View
rootAccessibilityView
,
@NonNull
AccessibilityChannel
accessibilityChannel
,
@NonNull
AccessibilityManager
accessibilityManager
,
@NonNull
ContentResolver
contentResolver
,
@NonNull
AccessibilityViewEmbedder
accessibilityViewEmbedder
,
// This should be @NonNull once the plumbing for
// io.flutter.embedding.engine.android.FlutterView is done.
// TODO(mattcarrol): Add the annotation once the plumbing is done.
// https://github.com/flutter/flutter/issues/29618
PlatformViewsAccessibilityDelegate
platformViewsAccessibilityDelegate
)
{
this
.
rootAccessibilityView
=
rootAccessibilityView
;
this
.
accessibilityChannel
=
accessibilityChannel
;
this
.
accessibilityManager
=
accessibilityManager
;
this
.
contentResolver
=
contentResolver
;
this
.
accessibilityViewEmbedder
=
accessibilityViewEmbedder
;
this
.
platformViewsAccessibilityDelegate
=
platformViewsAccessibilityDelegate
;
// Tell Flutter whether accessibility is initially active or not. Then register a listener
...
...
@@ -388,8 +411,6 @@ public class AccessibilityBridge extends AccessibilityNodeProvider {
if
(
platformViewsAccessibilityDelegate
!=
null
)
{
platformViewsAccessibilityDelegate
.
attachAccessibilityBridge
(
this
);
}
accessibilityViewEmbedder
=
new
AccessibilityViewEmbedder
(
rootAccessibilityView
,
MIN_ENGINE_GENERATED_NODE_ID
);
}
/**
...
...
@@ -1580,15 +1601,31 @@ public class AccessibilityBridge extends AccessibilityNodeProvider {
// for null'ing accessibilityFocusedSemanticsNode, inputFocusedSemanticsNode,
// and hoveredObject. Is this a hook method or a command?
semanticsNodeToBeRemoved
.
parent
=
null
;
if
(
semanticsNodeToBeRemoved
.
platformViewId
!=
-
1
&&
embeddedAccessibilityFocusedNodeId
!=
null
&&
accessibilityViewEmbedder
.
platformViewOfNode
(
embeddedAccessibilityFocusedNodeId
)
==
platformViewsAccessibilityDelegate
.
getPlatformViewById
(
semanticsNodeToBeRemoved
.
platformViewId
))
{
// If the currently focused a11y node is within a platform view that is
// getting removed: clear it's a11y focus.
sendAccessibilityEvent
(
embeddedAccessibilityFocusedNodeId
,
AccessibilityEvent
.
TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED
);
embeddedAccessibilityFocusedNodeId
=
null
;
}
if
(
accessibilityFocusedSemanticsNode
==
semanticsNodeToBeRemoved
)
{
sendAccessibilityEvent
(
accessibilityFocusedSemanticsNode
.
id
,
AccessibilityEvent
.
TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED
);
accessibilityFocusedSemanticsNode
=
null
;
}
if
(
inputFocusedSemanticsNode
==
semanticsNodeToBeRemoved
)
{
inputFocusedSemanticsNode
=
null
;
}
if
(
hoveredObject
==
semanticsNodeToBeRemoved
)
{
hoveredObject
=
null
;
}
...
...
shell/platform/android/io/flutter/view/AccessibilityViewEmbedder.java
浏览文件 @
f4d6ce13
...
...
@@ -44,7 +44,7 @@ import java.util.Map;
* corresponding platform view and `originId`.
*/
@Keep
final
class
AccessibilityViewEmbedder
{
class
AccessibilityViewEmbedder
{
private
static
final
String
TAG
=
"AccessibilityBridge"
;
private
final
ReflectionAccessors
reflectionAccessors
;
...
...
@@ -387,6 +387,18 @@ final class AccessibilityViewEmbedder {
return
origin
.
view
.
dispatchGenericMotionEvent
(
translatedEvent
);
}
/**
* Returns the View that contains the accessibility node identified by the provided flutterId or
* null if it doesn't belong to a view.
*/
public
View
platformViewOfNode
(
int
flutterId
)
{
ViewAndId
viewAndId
=
flutterIdToOrigin
.
get
(
flutterId
);
if
(
viewAndId
==
null
)
{
return
null
;
}
return
viewAndId
.
view
;
}
private
static
class
ViewAndId
{
final
View
view
;
final
int
id
;
...
...
shell/platform/android/test/io/flutter/view/AccessibilityBridgeTest.java
浏览文件 @
f4d6ce13
...
...
@@ -5,20 +5,27 @@
package
io.flutter.view
;
import
static
org
.
junit
.
Assert
.
assertEquals
;
import
static
org
.
mockito
.
Matchers
.
eq
;
import
static
org
.
mockito
.
Mockito
.
mock
;
import
static
org
.
mockito
.
Mockito
.
times
;
import
static
org
.
mockito
.
Mockito
.
verify
;
import
static
org
.
mockito
.
Mockito
.
when
;
import
android.content.ContentResolver
;
import
android.content.Context
;
import
android.view.View
;
import
android.view.ViewParent
;
import
android.view.accessibility.AccessibilityEvent
;
import
android.view.accessibility.AccessibilityManager
;
import
android.view.accessibility.AccessibilityNodeInfo
;
import
io.flutter.embedding.engine.systemchannels.AccessibilityChannel
;
import
io.flutter.plugin.platform.PlatformViewsAccessibilityDelegate
;
import
java.nio.ByteBuffer
;
import
java.util.ArrayList
;
import
java.util.List
;
import
org.junit.Test
;
import
org.junit.runner.RunWith
;
import
org.mockito.ArgumentCaptor
;
import
org.robolectric.RobolectricTestRunner
;
import
org.robolectric.annotation.Config
;
...
...
@@ -73,24 +80,103 @@ public class AccessibilityBridgeTest {
assertEquals
(
nodeInfo
.
getText
(),
null
);
}
AccessibilityBridge
setUpBridge
()
{
View
view
=
mock
(
View
.
class
);
@Test
public
void
itUnfocusesPlatformViewWhenPlatformViewGoesAway
()
{
AccessibilityViewEmbedder
mockViewEmbedder
=
mock
(
AccessibilityViewEmbedder
.
class
);
AccessibilityManager
mockManager
=
mock
(
AccessibilityManager
.
class
);
View
mockRootView
=
mock
(
View
.
class
);
Context
context
=
mock
(
Context
.
class
);
when
(
v
iew
.
getContext
()).
thenReturn
(
context
);
when
(
mockRootV
iew
.
getContext
()).
thenReturn
(
context
);
when
(
context
.
getPackageName
()).
thenReturn
(
"test"
);
AccessibilityChannel
accessibilityChannel
=
mock
(
AccessibilityChannel
.
class
);
AccessibilityManager
accessibilityManager
=
mock
(
AccessibilityManager
.
class
);
ContentResolver
contentResolver
=
mock
(
ContentResolver
.
class
);
PlatformViewsAccessibilityDelegate
platformViewsAccessibilityDelegate
=
mock
(
PlatformViewsAccessibilityDelegate
.
class
);
AccessibilityBridge
accessibilityBridge
=
new
AccessibilityBridge
(
view
,
accessibilityChannel
,
accessibilityManager
,
contentResolver
,
platformViewsAccessibilityDelegate
);
return
accessibilityBridge
;
setUpBridge
(
mockRootView
,
mockManager
,
mockViewEmbedder
);
// Sent a11y tree with platform view.
TestSemanticsNode
root
=
new
TestSemanticsNode
();
root
.
id
=
0
;
TestSemanticsNode
platformView
=
new
TestSemanticsNode
();
platformView
.
id
=
1
;
platformView
.
platformViewId
=
42
;
root
.
children
.
add
(
platformView
);
TestSemanticsUpdate
testSemanticsUpdate
=
root
.
toUpdate
();
accessibilityBridge
.
updateSemantics
(
testSemanticsUpdate
.
buffer
,
testSemanticsUpdate
.
strings
);
// Set a11y focus to platform view.
View
mockView
=
mock
(
View
.
class
);
AccessibilityEvent
focusEvent
=
mock
(
AccessibilityEvent
.
class
);
when
(
mockViewEmbedder
.
requestSendAccessibilityEvent
(
mockView
,
mockView
,
focusEvent
))
.
thenReturn
(
true
);
when
(
mockViewEmbedder
.
getRecordFlutterId
(
mockView
,
focusEvent
)).
thenReturn
(
42
);
when
(
focusEvent
.
getEventType
()).
thenReturn
(
AccessibilityEvent
.
TYPE_VIEW_ACCESSIBILITY_FOCUSED
);
accessibilityBridge
.
externalViewRequestSendAccessibilityEvent
(
mockView
,
mockView
,
focusEvent
);
// Replace the platform view.
TestSemanticsNode
node
=
new
TestSemanticsNode
();
node
.
id
=
2
;
root
.
children
.
clear
();
root
.
children
.
add
(
node
);
testSemanticsUpdate
=
root
.
toUpdate
();
when
(
mockManager
.
isEnabled
()).
thenReturn
(
true
);
ViewParent
mockParent
=
mock
(
ViewParent
.
class
);
when
(
mockRootView
.
getParent
()).
thenReturn
(
mockParent
);
accessibilityBridge
.
updateSemantics
(
testSemanticsUpdate
.
buffer
,
testSemanticsUpdate
.
strings
);
// Check that unfocus event was sent.
ArgumentCaptor
<
AccessibilityEvent
>
eventCaptor
=
ArgumentCaptor
.
forClass
(
AccessibilityEvent
.
class
);
verify
(
mockParent
,
times
(
2
))
.
requestSendAccessibilityEvent
(
eq
(
mockRootView
),
eventCaptor
.
capture
());
AccessibilityEvent
event
=
eventCaptor
.
getAllValues
().
get
(
0
);
assertEquals
(
event
.
getEventType
(),
AccessibilityEvent
.
TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED
);
}
AccessibilityBridge
setUpBridge
()
{
return
setUpBridge
(
null
,
null
,
null
,
null
,
null
,
null
);
}
AccessibilityBridge
setUpBridge
(
View
rootAccessibilityView
,
AccessibilityManager
accessibilityManager
,
AccessibilityViewEmbedder
accessibilityViewEmbedder
)
{
return
setUpBridge
(
rootAccessibilityView
,
null
,
accessibilityManager
,
null
,
accessibilityViewEmbedder
,
null
);
}
AccessibilityBridge
setUpBridge
(
View
rootAccessibilityView
,
AccessibilityChannel
accessibilityChannel
,
AccessibilityManager
accessibilityManager
,
ContentResolver
contentResolver
,
AccessibilityViewEmbedder
accessibilityViewEmbedder
,
PlatformViewsAccessibilityDelegate
platformViewsAccessibilityDelegate
)
{
if
(
rootAccessibilityView
==
null
)
{
rootAccessibilityView
=
mock
(
View
.
class
);
Context
context
=
mock
(
Context
.
class
);
when
(
rootAccessibilityView
.
getContext
()).
thenReturn
(
context
);
when
(
context
.
getPackageName
()).
thenReturn
(
"test"
);
}
if
(
accessibilityChannel
==
null
)
{
accessibilityChannel
=
mock
(
AccessibilityChannel
.
class
);
}
if
(
accessibilityManager
==
null
)
{
accessibilityManager
=
mock
(
AccessibilityManager
.
class
);
}
if
(
contentResolver
==
null
)
{
contentResolver
=
mock
(
ContentResolver
.
class
);
}
if
(
accessibilityViewEmbedder
==
null
)
{
accessibilityViewEmbedder
=
mock
(
AccessibilityViewEmbedder
.
class
);
}
if
(
platformViewsAccessibilityDelegate
==
null
)
{
platformViewsAccessibilityDelegate
=
mock
(
PlatformViewsAccessibilityDelegate
.
class
);
}
return
new
AccessibilityBridge
(
rootAccessibilityView
,
accessibilityChannel
,
accessibilityManager
,
contentResolver
,
accessibilityViewEmbedder
,
platformViewsAccessibilityDelegate
);
}
/// The encoding for semantics is described in platform_view_android.cc
...
...
@@ -136,11 +222,18 @@ public class AccessibilityBridgeTest {
float
top
=
0.0f
;
float
right
=
0.0f
;
float
bottom
=
0.0f
;
// children and custom actions not supported.
final
List
<
TestSemanticsNode
>
children
=
new
ArrayList
<
TestSemanticsNode
>();
// custom actions not supported.
TestSemanticsUpdate
toUpdate
()
{
ArrayList
<
String
>
strings
=
new
ArrayList
<
String
>();
ByteBuffer
bytes
=
ByteBuffer
.
allocate
(
1000
);
addToBuffer
(
bytes
,
strings
);
bytes
.
flip
();
return
new
TestSemanticsUpdate
(
bytes
,
strings
.
toArray
(
new
String
[
strings
.
size
()]));
}
protected
void
addToBuffer
(
ByteBuffer
bytes
,
ArrayList
<
String
>
strings
)
{
bytes
.
putInt
(
id
);
bytes
.
putInt
(
flags
);
bytes
.
putInt
(
actions
);
...
...
@@ -169,11 +262,20 @@ public class AccessibilityBridgeTest {
bytes
.
putFloat
(
0
);
}
// children in traversal order.
bytes
.
putInt
(
0
);
bytes
.
putInt
(
children
.
size
());
for
(
TestSemanticsNode
node
:
children
)
{
bytes
.
putInt
(
node
.
id
);
}
// children in hit test order.
for
(
TestSemanticsNode
node
:
children
)
{
bytes
.
putInt
(
node
.
id
);
}
// custom actions
bytes
.
putInt
(
0
);
bytes
.
flip
();
return
new
TestSemanticsUpdate
(
bytes
,
strings
.
toArray
(
new
String
[
strings
.
size
()]));
// child nodes
for
(
TestSemanticsNode
node
:
children
)
{
node
.
addToBuffer
(
bytes
,
strings
);
}
}
}
...
...
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录