From db8c40b3f3474b2e7acec9af1d20dcc22cf0cb68 Mon Sep 17 00:00:00 2001 From: chunhtai <47866232+chunhtai@users.noreply.github.com> Date: Tue, 28 Jul 2020 09:49:59 -0700 Subject: [PATCH] fix FlutterViewUpdateCustomAccessibilityActions uses correct string list (#19623) * fix FlutterViewUpdateCustomAccessibilityActions uses correct string list * add test to ci * format * add license file * fix test * fix lint --- BUILD.gn | 1 + ci/licenses_golden/licenses_flutter | 3 + shell/platform/android/BUILD.gn | 1 + .../platform/android/platform_view_android.cc | 145 +--------------- .../platform/android/platform_view_android.h | 3 + .../platform_view_android_delegate/BUILD.gn | 45 +++++ .../platform_view_android_delegate.cc | 157 ++++++++++++++++++ .../platform_view_android_delegate.h | 29 ++++ ...latform_view_android_delegate_unittests.cc | 96 +++++++++++ testing/run_tests.py | 1 + 10 files changed, 342 insertions(+), 139 deletions(-) create mode 100644 shell/platform/android/platform_view_android_delegate/BUILD.gn create mode 100644 shell/platform/android/platform_view_android_delegate/platform_view_android_delegate.cc create mode 100644 shell/platform/android/platform_view_android_delegate/platform_view_android_delegate.h create mode 100644 shell/platform/android/platform_view_android_delegate/platform_view_android_delegate_unittests.cc diff --git a/BUILD.gn b/BUILD.gn index 80da512d06aa8..5a5200f7e9522 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -116,6 +116,7 @@ group("flutter") { public_deps += [ "//flutter/shell/platform/android/external_view_embedder:android_external_view_embedder_unittests", "//flutter/shell/platform/android/jni:jni_unittests", + "//flutter/shell/platform/android/platform_view_android_delegate:platform_view_android_delegate_unittests", ] } diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index db332dbce81bc..8e53d1f41cb29 100755 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -815,6 +815,9 @@ FILE: ../../../flutter/shell/platform/android/platform_message_response_android. FILE: ../../../flutter/shell/platform/android/platform_message_response_android.h FILE: ../../../flutter/shell/platform/android/platform_view_android.cc FILE: ../../../flutter/shell/platform/android/platform_view_android.h +FILE: ../../../flutter/shell/platform/android/platform_view_android_delegate/platform_view_android_delegate.cc +FILE: ../../../flutter/shell/platform/android/platform_view_android_delegate/platform_view_android_delegate.h +FILE: ../../../flutter/shell/platform/android/platform_view_android_delegate/platform_view_android_delegate_unittests.cc FILE: ../../../flutter/shell/platform/android/platform_view_android_jni_impl.cc FILE: ../../../flutter/shell/platform/android/platform_view_android_jni_impl.h FILE: ../../../flutter/shell/platform/android/robolectric.properties diff --git a/shell/platform/android/BUILD.gn b/shell/platform/android/BUILD.gn index 93e6b140a7304..6d55b3cc74fd8 100644 --- a/shell/platform/android/BUILD.gn +++ b/shell/platform/android/BUILD.gn @@ -64,6 +64,7 @@ shared_library("flutter_shell_native") { "//flutter/shell/platform/android/context", "//flutter/shell/platform/android/external_view_embedder", "//flutter/shell/platform/android/jni", + "//flutter/shell/platform/android/platform_view_android_delegate", "//flutter/shell/platform/android/surface", "//flutter/shell/platform/android/surface:native_window", "//third_party/skia", diff --git a/shell/platform/android/platform_view_android.cc b/shell/platform/android/platform_view_android.cc index 9253e8e95f000..596994662f3a2 100644 --- a/shell/platform/android/platform_view_android.cc +++ b/shell/platform/android/platform_view_android.cc @@ -52,7 +52,9 @@ PlatformViewAndroid::PlatformViewAndroid( flutter::TaskRunners task_runners, std::shared_ptr jni_facade, bool use_software_rendering) - : PlatformView(delegate, std::move(task_runners)), jni_facade_(jni_facade) { + : PlatformView(delegate, std::move(task_runners)), + jni_facade_(jni_facade), + platform_view_android_delegate_(jni_facade) { std::shared_ptr android_context; if (use_software_rendering) { android_context = @@ -81,7 +83,8 @@ PlatformViewAndroid::PlatformViewAndroid( flutter::TaskRunners task_runners, std::shared_ptr jni_facade) : PlatformView(delegate, std::move(task_runners)), - jni_facade_(jni_facade) {} + jni_facade_(jni_facade), + platform_view_android_delegate_(jni_facade) {} PlatformViewAndroid::~PlatformViewAndroid() = default; @@ -260,143 +263,7 @@ void PlatformViewAndroid::DispatchSemanticsAction(JNIEnv* env, void PlatformViewAndroid::UpdateSemantics( flutter::SemanticsNodeUpdates update, flutter::CustomAccessibilityActionUpdates actions) { - constexpr size_t kBytesPerNode = 41 * sizeof(int32_t); - constexpr size_t kBytesPerChild = sizeof(int32_t); - constexpr size_t kBytesPerAction = 4 * sizeof(int32_t); - - { - size_t num_bytes = 0; - for (const auto& value : update) { - num_bytes += kBytesPerNode; - num_bytes += - value.second.childrenInTraversalOrder.size() * kBytesPerChild; - num_bytes += value.second.childrenInHitTestOrder.size() * kBytesPerChild; - num_bytes += - value.second.customAccessibilityActions.size() * kBytesPerChild; - } - - // The encoding defined here is used in: - // - // * AccessibilityBridge.java - // * AccessibilityBridgeTest.java - // * accessibility_bridge.mm - // - // If any of the encoding structure or length is changed, those locations - // must be updated (at a minimum). - std::vector buffer(num_bytes); - int32_t* buffer_int32 = reinterpret_cast(&buffer[0]); - float* buffer_float32 = reinterpret_cast(&buffer[0]); - - std::vector strings; - size_t position = 0; - for (const auto& value : update) { - // If you edit this code, make sure you update kBytesPerNode - // and/or kBytesPerChild above to match the number of values you are - // sending. - const flutter::SemanticsNode& node = value.second; - buffer_int32[position++] = node.id; - buffer_int32[position++] = node.flags; - buffer_int32[position++] = node.actions; - buffer_int32[position++] = node.maxValueLength; - buffer_int32[position++] = node.currentValueLength; - buffer_int32[position++] = node.textSelectionBase; - buffer_int32[position++] = node.textSelectionExtent; - buffer_int32[position++] = node.platformViewId; - buffer_int32[position++] = node.scrollChildren; - buffer_int32[position++] = node.scrollIndex; - buffer_float32[position++] = (float)node.scrollPosition; - buffer_float32[position++] = (float)node.scrollExtentMax; - buffer_float32[position++] = (float)node.scrollExtentMin; - if (node.label.empty()) { - buffer_int32[position++] = -1; - } else { - buffer_int32[position++] = strings.size(); - strings.push_back(node.label); - } - if (node.value.empty()) { - buffer_int32[position++] = -1; - } else { - buffer_int32[position++] = strings.size(); - strings.push_back(node.value); - } - if (node.increasedValue.empty()) { - buffer_int32[position++] = -1; - } else { - buffer_int32[position++] = strings.size(); - strings.push_back(node.increasedValue); - } - if (node.decreasedValue.empty()) { - buffer_int32[position++] = -1; - } else { - buffer_int32[position++] = strings.size(); - strings.push_back(node.decreasedValue); - } - if (node.hint.empty()) { - buffer_int32[position++] = -1; - } else { - buffer_int32[position++] = strings.size(); - strings.push_back(node.hint); - } - buffer_int32[position++] = node.textDirection; - buffer_float32[position++] = node.rect.left(); - buffer_float32[position++] = node.rect.top(); - buffer_float32[position++] = node.rect.right(); - buffer_float32[position++] = node.rect.bottom(); - node.transform.getColMajor(&buffer_float32[position]); - position += 16; - - buffer_int32[position++] = node.childrenInTraversalOrder.size(); - for (int32_t child : node.childrenInTraversalOrder) - buffer_int32[position++] = child; - - for (int32_t child : node.childrenInHitTestOrder) - buffer_int32[position++] = child; - - buffer_int32[position++] = node.customAccessibilityActions.size(); - for (int32_t child : node.customAccessibilityActions) - buffer_int32[position++] = child; - } - - // custom accessibility actions. - size_t num_action_bytes = actions.size() * kBytesPerAction; - std::vector actions_buffer(num_action_bytes); - int32_t* actions_buffer_int32 = - reinterpret_cast(&actions_buffer[0]); - - std::vector action_strings; - size_t actions_position = 0; - for (const auto& value : actions) { - // If you edit this code, make sure you update kBytesPerAction - // to match the number of values you are - // sending. - const flutter::CustomAccessibilityAction& action = value.second; - actions_buffer_int32[actions_position++] = action.id; - actions_buffer_int32[actions_position++] = action.overrideId; - if (action.label.empty()) { - actions_buffer_int32[actions_position++] = -1; - } else { - actions_buffer_int32[actions_position++] = action_strings.size(); - action_strings.push_back(action.label); - } - if (action.hint.empty()) { - actions_buffer_int32[actions_position++] = -1; - } else { - actions_buffer_int32[actions_position++] = action_strings.size(); - action_strings.push_back(action.hint); - } - } - - // Calling NewDirectByteBuffer in API level 22 and below with a size of zero - // will cause a JNI crash. - if (actions_buffer.size() > 0) { - jni_facade_->FlutterViewUpdateCustomAccessibilityActions(actions_buffer, - strings); - } - - if (buffer.size() > 0) { - jni_facade_->FlutterViewUpdateSemantics(buffer, strings); - } - } + platform_view_android_delegate_.UpdateSemantics(update, actions); } void PlatformViewAndroid::RegisterExternalTexture( diff --git a/shell/platform/android/platform_view_android.h b/shell/platform/android/platform_view_android.h index f0c7bfa5aa03d..34475ddbc5243 100644 --- a/shell/platform/android/platform_view_android.h +++ b/shell/platform/android/platform_view_android.h @@ -16,6 +16,7 @@ #include "flutter/lib/ui/window/platform_message.h" #include "flutter/shell/common/platform_view.h" #include "flutter/shell/platform/android/jni/platform_view_android_jni.h" +#include "flutter/shell/platform/android/platform_view_android_delegate/platform_view_android_delegate.h" #include "flutter/shell/platform/android/surface/android_native_window.h" #include "flutter/shell/platform/android/surface/android_surface.h" @@ -80,6 +81,8 @@ class PlatformViewAndroid final : public PlatformView { private: const std::shared_ptr jni_facade_; + PlatformViewAndroidDelegate platform_view_android_delegate_; + std::unique_ptr android_surface_; // We use id 0 to mean that no response is expected. int next_response_id_ = 1; diff --git a/shell/platform/android/platform_view_android_delegate/BUILD.gn b/shell/platform/android/platform_view_android_delegate/BUILD.gn new file mode 100644 index 0000000000000..61f2019f0902b --- /dev/null +++ b/shell/platform/android/platform_view_android_delegate/BUILD.gn @@ -0,0 +1,45 @@ +# Copyright 2013 The Flutter Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import("//flutter/common/config.gni") +import("//flutter/testing/testing.gni") + +source_set("platform_view_android_delegate") { + sources = [ + "platform_view_android_delegate.cc", + "platform_view_android_delegate.h", + ] + + public_configs = [ "//flutter:config" ] + + deps = [ + "//flutter/common", + "//flutter/fml", + "//flutter/lib/ui", + "//flutter/shell/common", + "//flutter/shell/platform/android/jni", + "//third_party/skia", + ] +} + +test_fixtures("platform_view_android_delegate_fixtures") { + fixtures = [] +} + +executable("platform_view_android_delegate_unittests") { + testonly = true + + sources = [ + "platform_view_android_delegate_unittests.cc", + ] + + deps = [ + ":platform_view_android_delegate", + ":platform_view_android_delegate_fixtures", + "//flutter/shell/platform/android/jni:jni_mock", + "//flutter/testing", + "//flutter/testing:dart", + "//flutter/third_party/txt", + ] +} diff --git a/shell/platform/android/platform_view_android_delegate/platform_view_android_delegate.cc b/shell/platform/android/platform_view_android_delegate/platform_view_android_delegate.cc new file mode 100644 index 0000000000000..9570d72c013c1 --- /dev/null +++ b/shell/platform/android/platform_view_android_delegate/platform_view_android_delegate.cc @@ -0,0 +1,157 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "flutter/shell/platform/android/platform_view_android_delegate/platform_view_android_delegate.h" + +namespace flutter { + +PlatformViewAndroidDelegate::PlatformViewAndroidDelegate( + std::shared_ptr jni_facade) + : jni_facade_(jni_facade){}; + +void PlatformViewAndroidDelegate::UpdateSemantics( + flutter::SemanticsNodeUpdates update, + flutter::CustomAccessibilityActionUpdates actions) { + constexpr size_t kBytesPerNode = 41 * sizeof(int32_t); + constexpr size_t kBytesPerChild = sizeof(int32_t); + constexpr size_t kBytesPerAction = 4 * sizeof(int32_t); + + { + size_t num_bytes = 0; + for (const auto& value : update) { + num_bytes += kBytesPerNode; + num_bytes += + value.second.childrenInTraversalOrder.size() * kBytesPerChild; + num_bytes += value.second.childrenInHitTestOrder.size() * kBytesPerChild; + num_bytes += + value.second.customAccessibilityActions.size() * kBytesPerChild; + } + // The encoding defined here is used in: + // + // * AccessibilityBridge.java + // * AccessibilityBridgeTest.java + // * accessibility_bridge.mm + // + // If any of the encoding structure or length is changed, those locations + // must be updated (at a minimum). + std::vector buffer(num_bytes); + int32_t* buffer_int32 = reinterpret_cast(&buffer[0]); + float* buffer_float32 = reinterpret_cast(&buffer[0]); + + std::vector strings; + size_t position = 0; + for (const auto& value : update) { + // If you edit this code, make sure you update kBytesPerNode + // and/or kBytesPerChild above to match the number of values you are + // sending. + const flutter::SemanticsNode& node = value.second; + buffer_int32[position++] = node.id; + buffer_int32[position++] = node.flags; + buffer_int32[position++] = node.actions; + buffer_int32[position++] = node.maxValueLength; + buffer_int32[position++] = node.currentValueLength; + buffer_int32[position++] = node.textSelectionBase; + buffer_int32[position++] = node.textSelectionExtent; + buffer_int32[position++] = node.platformViewId; + buffer_int32[position++] = node.scrollChildren; + buffer_int32[position++] = node.scrollIndex; + buffer_float32[position++] = static_cast(node.scrollPosition); + buffer_float32[position++] = static_cast(node.scrollExtentMax); + buffer_float32[position++] = static_cast(node.scrollExtentMin); + if (node.label.empty()) { + buffer_int32[position++] = -1; + } else { + buffer_int32[position++] = strings.size(); + strings.push_back(node.label); + } + if (node.value.empty()) { + buffer_int32[position++] = -1; + } else { + buffer_int32[position++] = strings.size(); + strings.push_back(node.value); + } + if (node.increasedValue.empty()) { + buffer_int32[position++] = -1; + } else { + buffer_int32[position++] = strings.size(); + strings.push_back(node.increasedValue); + } + if (node.decreasedValue.empty()) { + buffer_int32[position++] = -1; + } else { + buffer_int32[position++] = strings.size(); + strings.push_back(node.decreasedValue); + } + if (node.hint.empty()) { + buffer_int32[position++] = -1; + } else { + buffer_int32[position++] = strings.size(); + strings.push_back(node.hint); + } + buffer_int32[position++] = node.textDirection; + buffer_float32[position++] = node.rect.left(); + buffer_float32[position++] = node.rect.top(); + buffer_float32[position++] = node.rect.right(); + buffer_float32[position++] = node.rect.bottom(); + node.transform.getColMajor(&buffer_float32[position]); + position += 16; + + buffer_int32[position++] = node.childrenInTraversalOrder.size(); + for (int32_t child : node.childrenInTraversalOrder) { + buffer_int32[position++] = child; + } + + for (int32_t child : node.childrenInHitTestOrder) { + buffer_int32[position++] = child; + } + + buffer_int32[position++] = node.customAccessibilityActions.size(); + for (int32_t child : node.customAccessibilityActions) { + buffer_int32[position++] = child; + } + } + + // custom accessibility actions. + size_t num_action_bytes = actions.size() * kBytesPerAction; + std::vector actions_buffer(num_action_bytes); + int32_t* actions_buffer_int32 = + reinterpret_cast(&actions_buffer[0]); + + std::vector action_strings; + size_t actions_position = 0; + for (const auto& value : actions) { + // If you edit this code, make sure you update kBytesPerAction + // to match the number of values you are + // sending. + const flutter::CustomAccessibilityAction& action = value.second; + actions_buffer_int32[actions_position++] = action.id; + actions_buffer_int32[actions_position++] = action.overrideId; + if (action.label.empty()) { + actions_buffer_int32[actions_position++] = -1; + } else { + actions_buffer_int32[actions_position++] = action_strings.size(); + action_strings.push_back(action.label); + } + if (action.hint.empty()) { + actions_buffer_int32[actions_position++] = -1; + } else { + actions_buffer_int32[actions_position++] = action_strings.size(); + action_strings.push_back(action.hint); + } + } + + // Calling NewDirectByteBuffer in API level 22 and below with a size of zero + // will cause a JNI crash. + if (actions_buffer.size() > 0) { + jni_facade_->FlutterViewUpdateCustomAccessibilityActions(actions_buffer, + action_strings); + } + + if (buffer.size() > 0) { + jni_facade_->FlutterViewUpdateSemantics(buffer, strings); + } + } +} + +} // namespace flutter diff --git a/shell/platform/android/platform_view_android_delegate/platform_view_android_delegate.h b/shell/platform/android/platform_view_android_delegate/platform_view_android_delegate.h new file mode 100644 index 0000000000000..3f76f34c51d80 --- /dev/null +++ b/shell/platform/android/platform_view_android_delegate/platform_view_android_delegate.h @@ -0,0 +1,29 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SHELL_PLATFORM_ANDROID_PLATFORM_VIEW_ANDROID_DELEGATE_H_ +#define SHELL_PLATFORM_ANDROID_PLATFORM_VIEW_ANDROID_DELEGATE_H_ + +#include +#include +#include + +#include "flutter/shell/common/platform_view.h" +#include "flutter/shell/platform/android/jni/platform_view_android_jni.h" + +namespace flutter { + +class PlatformViewAndroidDelegate { + public: + PlatformViewAndroidDelegate( + std::shared_ptr jni_facade); + void UpdateSemantics(flutter::SemanticsNodeUpdates update, + flutter::CustomAccessibilityActionUpdates actions); + + private: + const std::shared_ptr jni_facade_; +}; +} // namespace flutter + +#endif // SHELL_PLATFORM_ANDROID_PLATFORM_VIEW_ANDROID_H_ diff --git a/shell/platform/android/platform_view_android_delegate/platform_view_android_delegate_unittests.cc b/shell/platform/android/platform_view_android_delegate/platform_view_android_delegate_unittests.cc new file mode 100644 index 0000000000000..56371ff230134 --- /dev/null +++ b/shell/platform/android/platform_view_android_delegate/platform_view_android_delegate_unittests.cc @@ -0,0 +1,96 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "flutter/shell/platform/android/jni/jni_mock.h" +#include "flutter/shell/platform/android/platform_view_android_delegate/platform_view_android_delegate.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace flutter { +namespace testing { + +TEST(PlatformViewShell, UpdateSemanticsDoesFlutterViewUpdateSemantics) { + auto jni_mock = std::make_shared(); + auto delegate = std::make_unique(jni_mock); + + flutter::SemanticsNodeUpdates update; + flutter::SemanticsNode node0; + node0.id = 0; + node0.label = "label"; + update.insert(std::make_pair(0, std::move(node0))); + + std::vector expected_buffer(164); + size_t position = 0; + int32_t* buffer_int32 = reinterpret_cast(&expected_buffer[0]); + float* buffer_float32 = reinterpret_cast(&expected_buffer[0]); + std::vector expected_strings; + buffer_int32[position++] = node0.id; + buffer_int32[position++] = node0.flags; + buffer_int32[position++] = node0.actions; + buffer_int32[position++] = node0.maxValueLength; + buffer_int32[position++] = node0.currentValueLength; + buffer_int32[position++] = node0.textSelectionBase; + buffer_int32[position++] = node0.textSelectionExtent; + buffer_int32[position++] = node0.platformViewId; + buffer_int32[position++] = node0.scrollChildren; + buffer_int32[position++] = node0.scrollIndex; + buffer_float32[position++] = static_cast(node0.scrollPosition); + buffer_float32[position++] = static_cast(node0.scrollExtentMax); + buffer_float32[position++] = static_cast(node0.scrollExtentMin); + buffer_int32[position++] = expected_strings.size(); // node0.label + expected_strings.push_back(node0.label); + buffer_int32[position++] = -1; // node0.value + buffer_int32[position++] = -1; // node0.increasedValue + buffer_int32[position++] = -1; // node0.decreasedValue + buffer_int32[position++] = -1; // node0.hint + buffer_int32[position++] = node0.textDirection; + buffer_float32[position++] = node0.rect.left(); + buffer_float32[position++] = node0.rect.top(); + buffer_float32[position++] = node0.rect.right(); + buffer_float32[position++] = node0.rect.bottom(); + node0.transform.getColMajor(&buffer_float32[position]); + position += 16; + buffer_int32[position++] = 0; // node0.childrenInTraversalOrder.size(); + buffer_int32[position++] = 0; // node0.customAccessibilityActions.size(); + + EXPECT_CALL(*jni_mock, + FlutterViewUpdateSemantics(expected_buffer, expected_strings)); + // Creates empty custom actions. + flutter::CustomAccessibilityActionUpdates actions; + delegate->UpdateSemantics(update, actions); +} + +TEST(PlatformViewShell, + UpdateSemanticsDoesFlutterViewUpdateCustomAccessibilityActions) { + auto jni_mock = std::make_shared(); + auto delegate = std::make_unique(jni_mock); + + flutter::CustomAccessibilityActionUpdates actions; + flutter::CustomAccessibilityAction action0; + action0.id = 0; + action0.overrideId = 1; + action0.label = "label"; + action0.hint = "hint"; + actions.insert(std::make_pair(0, std::move(action0))); + + std::vector expected_actions_buffer(16); + int32_t* actions_buffer_int32 = + reinterpret_cast(&expected_actions_buffer[0]); + std::vector expected_action_strings; + actions_buffer_int32[0] = action0.id; + actions_buffer_int32[1] = action0.overrideId; + actions_buffer_int32[2] = expected_action_strings.size(); + expected_action_strings.push_back(action0.label); + actions_buffer_int32[3] = expected_action_strings.size(); + expected_action_strings.push_back(action0.hint); + + EXPECT_CALL(*jni_mock, FlutterViewUpdateCustomAccessibilityActions( + expected_actions_buffer, expected_action_strings)); + // Creates empty update. + flutter::SemanticsNodeUpdates update; + delegate->UpdateSemantics(update, actions); +} + +} // namespace testing +} // namespace flutter diff --git a/testing/run_tests.py b/testing/run_tests.py index b6fd8d713dbbc..7df1757ee80b0 100755 --- a/testing/run_tests.py +++ b/testing/run_tests.py @@ -137,6 +137,7 @@ def RunCCTests(build_dir, filter): # https://github.com/google/googletest/issues/2490 RunEngineExecutable(build_dir, 'android_external_view_embedder_unittests', filter, shuffle_flags) RunEngineExecutable(build_dir, 'jni_unittests', filter, shuffle_flags) + RunEngineExecutable(build_dir, 'platform_view_android_delegate_unittests', filter, shuffle_flags) RunEngineExecutable(build_dir, 'ui_unittests', filter, shuffle_flags)