diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs index c2ce175f..7064920 100644 --- a/.git-blame-ignore-revs +++ b/.git-blame-ignore-revs
@@ -665,6 +665,23 @@ 59ac8227c5dd59754331b3f7f9f85e1a947f1242 ca067f2604f9bf0ff2fa070eafeed664698d819a 0b20ccad48bc63b0e6bf07b8511b32b8e3d963b1 +a98e4438c6229a95ecd8c5f0acebc6c95f9028b4 +74f09f8a69f0b27fda9581de477896071917256a +c686e8f4fd379312469fe018f5c390e9c8f20d0d +b263d258c9e4899afaa24036bde76d7e9e1d537c +8c3a4c4fd8ddc5c8c3384f29e48932f81a38a462 +69fa1c8ea7b8b207343b91ca8db5e3e7d372b1c7 +fe132eeb21687c455d695d6af346f15454828d01 +14af2a9cdbfb78e7244d0a861babaf4388944ff1 +c571efbef3ecfe66613d524ff39c58e25d18548c +618111ac68d8ed2e3a84eee9cae1cf61ce9ba236 +581b9e8403fdba9f33cc315c56180f841407c86b +e795a58002fc09c3417489cf419798c8d8ab0ba3 +d2b5c17eff35da6ebff8ba20c99688b87e1bc752 +9021f6eff4a13c7c43ef3c544b7ba5ae9953dab6 +4787fce6c51383f5631643ac3d14cc512d656de6 +3eb9fd518875bee685ad2e8c2b699822a8dfd5d9 +e5fff99cbb0380ea7f44c60ee15554b6b56320fb # Reformat .java code with google-java-format. Tagged with Bug: 1491626 (part 2) c89f6078b3d03a07e8859e4e2be5dc9baaf1d5f0
diff --git a/.gn b/.gn index afe0b3e..6abe8e5 100644 --- a/.gn +++ b/.gn
@@ -76,6 +76,12 @@ # their includes checked for proper dependencies when you run either # "gn check" or "gn gen --check". no_check_targets = [ + # TODO(crbug.com/326607005): Remove the exceptions for googletest once it is + # possible to support Abseil flags in tests without regressing things (e.g. + # no added static initializers) in production binaries. + "//third_party/googletest:gmock", + "//third_party/googletest:gtest", + # //v8, https://crbug.com/v8/7330 "//v8/src/inspector:inspector", # 20 errors "//v8/test/cctest:cctest_sources", # 15 errors
diff --git a/AUTHORS b/AUTHORS index 6119bcd7..e6438da3 100644 --- a/AUTHORS +++ b/AUTHORS
@@ -53,6 +53,7 @@ Ajay Sharma <ajay.sh@samsung.com> Ajith Kumar V <ajith.v@samsung.com> Akash Yadav <akash1.yadav@samsung.com> +Akihiko Odaki <akihiko.odaki@gmail.com> Akos Kiss <akiss@inf.u-szeged.hu> Aku Kotkavuo <a.kotkavuo@partner.samsung.com> Aldo Culquicondor <alculquicondor@gmail.com>
diff --git a/DEPS b/DEPS index 51578d1..8f4cbf7 100644 --- a/DEPS +++ b/DEPS
@@ -288,7 +288,7 @@ 'sysroots_json_path': 'build/linux/sysroot_scripts/sysroots.json', # siso CIPD package version. - 'siso_version': 'git_revision:b52a6552a2e17e34b747fd5d29875fc4aba99da8', + 'siso_version': 'git_revision:8632c71c8cadd9cb9ea4d43e6b30eeae4dffcd12', # download libaom test data 'download_libaom_testdata': False, @@ -308,27 +308,27 @@ # Three lines of non-changing comments so that # the commit queue can handle CLs rolling V8 # and whatever else without interference from each other. - 'src_internal_revision': 'ad2b19f04fe915ec0cdbac9830e7c5254b055cd7', + 'src_internal_revision': '5798a096a03a88db5e3a14645bd747d811dbd560', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling Skia # and whatever else without interference from each other. - 'skia_revision': 'f94b35f33a8277de713a2aafc64b26a241e2ab12', + 'skia_revision': '2e47b3ccb65fe6ca0c84f4f6b6ffc33f243247c3', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling V8 # and whatever else without interference from each other. - 'v8_revision': 'a2047bfab691438574d4485d96d2c3c006371877', + 'v8_revision': 'c5526930f87c21bd0994e7cdbb589c5c5510c112', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling ANGLE # and whatever else without interference from each other. - 'angle_revision': 'acba61cb3e27f15b56ca781813efa357b9ca0f1f', + 'angle_revision': '9100f2ec79ec11f024b9729fe26f45470f66ba8f', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling SwiftShader # and whatever else without interference from each other. - 'swiftshader_revision': '0f69b790c7a491e103802870b2f670c5936b9930', + 'swiftshader_revision': 'bbe6452b420c5ddc4b0fd421b0a3ce271262f4ca', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling PDFium # and whatever else without interference from each other. - 'pdfium_revision': '88eff81cca5902a687e4fc4cfbec03d293c200f3', + 'pdfium_revision': 'f1805084d8ec1a854ffa6e4f3d489aaa5c9818ab', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling BoringSSL # and whatever else without interference from each other. @@ -339,7 +339,7 @@ # Three lines of non-changing comments so that # the commit queue can handle CLs rolling Fuchsia sdk # and whatever else without interference from each other. - 'fuchsia_version': 'version:18.20240215.1.1', + 'fuchsia_version': '18.20240215.1.1', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling google-toolbox-for-mac # and whatever else without interference from each other. @@ -347,7 +347,7 @@ # Three lines of non-changing comments so that # the commit queue can handle CLs rolling googletest # and whatever else without interference from each other. - 'googletest_revision': 'af29db7ec28d6df1c7f0f745186884091e602e07', + 'googletest_revision': '76bb2afb8b522d24496ad1c757a49784fbfa2e42', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling lighttpd # and whatever else without interference from each other. @@ -383,11 +383,11 @@ # Three lines of non-changing comments so that # the commit queue can handle CLs rolling catapult # and whatever else without interference from each other. - 'catapult_revision': '4dc7721a144450da73f9adc3abcff225c804ff3f', + 'catapult_revision': '6f4a0d6c8731078f74f3de4fc92748c125da43d6', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling chromium_variations # and whatever else without interference from each other. - 'chromium_variations_revision': '04c3d68a4e1c34c789ad6b50e2b00960a4e9bc3c', + 'chromium_variations_revision': '1fae85ca70bdf203941f8ac90392e7d471247147', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling CrossBench # and whatever else without interference from each other. @@ -399,11 +399,11 @@ # Three lines of non-changing comments so that # the commit queue can handle CLs rolling fuzztest # and whatever else without interference from each other. - 'fuzztest_revision': '61d95200e7ece7d121cab26f0c39fbf392e6566e', + 'fuzztest_revision': 'bddcd9f77ba0a81a99ce50bcadf5149efe545df0', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling devtools-frontend # and whatever else without interference from each other. - 'devtools_frontend_revision': '0eb688bd504b2b3fdf5ea8adc8975afbda2e18bf', + 'devtools_frontend_revision': '5ccdd75fa57a758fa15db394dd6ad6092963cfed', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling libprotobuf-mutator # and whatever else without interference from each other. @@ -427,7 +427,7 @@ # Three lines of non-changing comments so that # the commit queue can handle CLs rolling feed # and whatever else without interference from each other. - 'dawn_revision': '8e33c32682850534d81a0b511261a94ff312978c', + 'dawn_revision': 'fd97e964d72ca376aa380fc0bbeca886674f45ed', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling feed # and whatever else without interference from each other. @@ -479,7 +479,7 @@ # Three lines of non-changing comments so that # the commit queue can handle CLs rolling feed # and whatever else without interference from each other. - 'libcxxabi_revision': 'a7b3d968a3a923886fea64b424bd770e69dc4ea4', + 'libcxxabi_revision': '5b35c9f06c3f8f17b43bd3da9527d6fecdf916c2', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling feed # and whatever else without interference from each other. @@ -499,14 +499,14 @@ # Three lines of non-changing comments so that # the commit queue can handle CLs rolling beto-core # and whatever else without interference from each other. - 'betocore_revision': '4d202dab960a0b6a6e4757ab4393945aca5a09db', + 'betocore_revision': 'bd1f1272cd2a6605ec793b914ada2c4ebcc820f6', # If you change this, also update the libc++ revision in # //buildtools/deps_revisions.gni. - 'libcxx_revision': '5a3d13ed42814e59d9b947a8fae826537116b3ee', + 'libcxx_revision': '08b8dfd3a9689085f0b0aa0f3e5912e88e5d4ac6', # GN CIPD package version. - 'gn_version': 'git_revision:03d10f1657b4ddace618c34ab61b6357d1ae9c9a', + 'gn_version': 'git_revision:5787e994aa4cb6cdb09c2c72ae6f1c6a7f1cf91a', # ninja CIPD package version. # https://chrome-infra-packages.appspot.com/p/infra/3pp/tools/ninja @@ -827,12 +827,12 @@ 'src/clank': { 'url': Var('chrome_git') + '/clank/internal/apps.git' + '@' + - '1b97a7f4cc5e80f931ec9a2b90253a17735ad7fe', + '6c7e651daf6bfddb87ebe2e63f0dc93b28aebf6a', 'condition': 'checkout_android and checkout_src_internal', }, 'src/docs/website': { - 'url': Var('chromium_git') + '/website.git' + '@' + '254fa7bcc05c8aa095289dc84db750efbcdae37e', + 'url': Var('chromium_git') + '/website.git' + '@' + 'ecc6daec0bc6bc14edaad139f726d7a0154d4572', }, 'src/ios/third_party/earl_grey2/src': { @@ -989,7 +989,7 @@ 'packages': [ { 'package': 'chromium/third_party/androidx', - 'version': 't9WCSa3pyfLqHhv8_577tLFVY-ANlLru3HBHLPHdgAAC', + 'version': 'unO3_k1jYtik0aRkumx_4IMBQoCIfx4yAcgeqnLPvugC', }, ], 'condition': 'checkout_android', @@ -1164,7 +1164,7 @@ # Tools used when building Chrome for Chrome OS. This affects both the Simple # Chrome workflow, as well as the chromeos-chrome ebuild. 'src/third_party/chromite': { - 'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + '070f3e0e65725de8f8d3f14a6e46ed7e0e911a45', + 'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + '21c56f5c330bab014098f49c6d5b9f2f9cba3473', 'condition': 'checkout_chromeos', }, @@ -1178,14 +1178,14 @@ Var('chromium_git') + '/external/github.com/google/cpu_features.git' + '@' + '936b9ab5515dead115606559502e3864958f7f6e', 'src/third_party/cpuinfo/src': - Var('chromium_git') + '/external/github.com/pytorch/cpuinfo.git' + '@' + '9484a6c590f831a30c1eec1311568b1a967a89dc', + Var('chromium_git') + '/external/github.com/pytorch/cpuinfo.git' + '@' + 'aa4b2163b99ac9534194520f70b93eeefb0b3b4e', 'src/third_party/crc32c/src': Var('chromium_git') + '/external/github.com/google/crc32c.git' + '@' + 'fa5ade41ee480003d9c5af6f43567ba22e4e17e6', # For Linux and Chromium OS. 'src/third_party/cros_system_api': { - 'url': Var('chromium_git') + '/chromiumos/platform2/system_api.git' + '@' + 'd2229e09bc30d6ceab275991e26a9f3b1d5a74c0', + 'url': Var('chromium_git') + '/chromiumos/platform2/system_api.git' + '@' + '422a282c95ed618b6401d1e05bba6808bcfc8173', 'condition': 'checkout_linux or checkout_chromeos', }, @@ -1199,13 +1199,13 @@ }, 'src/third_party/depot_tools': - Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + 'cb43b5d82d72ae1fddb56ce9a9092d0438ff0cd9', + Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '4df61147ba67316806617f74347f408d2e4ff2f1', 'src/third_party/devtools-frontend/src': Var('chromium_git') + '/devtools/devtools-frontend' + '@' + Var('devtools_frontend_revision'), 'src/third_party/devtools-frontend-internal': { - 'url': Var('chrome_git') + '/devtools/devtools-internal.git' + '@' + '88eb78cfb6b9ddc20128ffb9674b0ce81c593ca4', + 'url': Var('chrome_git') + '/devtools/devtools-internal.git' + '@' + '2173cbdf60dac9a327293104c19c93e63a3e46ee', 'condition': 'checkout_src_internal', }, @@ -1213,7 +1213,7 @@ Var('chromium_git') + '/chromium/dom-distiller/dist.git' + '@' + '199de96b345ada7c6e7e6ba3d2fa7a6911b8767d', 'src/third_party/eigen3/src': - Var('chromium_git') + '/external/gitlab.com/libeigen/eigen.git' + '@' + '7fd7a3f946e5ac152d28dad388cff8bfa1026925', + Var('chromium_git') + '/external/gitlab.com/libeigen/eigen.git' + '@' + '2a9055b50ed22101da7d77e999b90ed50956fe0b', 'src/third_party/emoji-metadata/src': { 'url': Var('chromium_git') + '/external/github.com/googlefonts/emoji-metadata' + '@' + '045f146fca682a836e01cd265171312bfb300e06', @@ -1533,7 +1533,7 @@ }, 'src/third_party/libvpx/source/libvpx': - Var('chromium_git') + '/webm/libvpx.git' + '@' + '3316c11240184851f8ce1c3061db8e22123cf9ed', + Var('chromium_git') + '/webm/libvpx.git' + '@' + 'd191c5f98454f544df501fa41999ba24b5cf171b', 'src/third_party/libwebm/source': Var('chromium_git') + '/webm/libwebm.git' + '@' + 'e4fbea0c9751ae8aa86629b197a28d8276a2b0da', @@ -1665,7 +1665,7 @@ Var('pdfium_git') + '/pdfium.git' + '@' + Var('pdfium_revision'), 'src/third_party/perfetto': - Var('android_git') + '/platform/external/perfetto.git' + '@' + '4183dabcac163845c7207da5d1d8a6e3927b95dc', + Var('android_git') + '/platform/external/perfetto.git' + '@' + '2778513f999795da191e124162f74a30dd6680c4', 'src/third_party/perl': { 'url': Var('chromium_git') + '/chromium/deps/perl.git' + '@' + '8ef97ff3b7332e38e61b347a2fbed425a4617151', @@ -1797,20 +1797,20 @@ Var('chromium_git') + '/external/github.com/GoogleChromeLabs/text-fragments-polyfill.git' + '@' + 'c036420683f672d685e27415de0a5f5e85bdc23f', 'src/third_party/tflite/src': - Var('chromium_git') + '/external/github.com/tensorflow/tensorflow.git' + '@' + 'e4ddf94e92d72ce34de2f45f036e29678a9527f7', + Var('chromium_git') + '/external/github.com/tensorflow/tensorflow.git' + '@' + '2d13600b56f64da95e9499a1bf95c735699858f0', 'src/third_party/turbine': { 'packages': [ { 'package': 'chromium/third_party/turbine', - 'version': 's-hdujub30RR2mH9Qf7pHv6h9uNGEiYVs6W1VXWeEe8C', + 'version': 'ZsrSMKpQt5d43K50XC1both1bFWzoloH6xOKYKZK_64C', }, ], 'condition': 'checkout_android', 'dep_type': 'cipd', }, - 'src/third_party/vulkan-deps': '{chromium_git}/vulkan-deps@fda3fc86e76347dc8d1df07d68aec006c98974d9', + 'src/third_party/vulkan-deps': '{chromium_git}/vulkan-deps@56b277ab9762b2f286010d7ba27c9c07131f712e', 'src/third_party/vulkan_memory_allocator': Var('chromium_git') + '/external/github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator.git' + '@' + '56300b29fbfcc693ee6609ddad3fdd5b7a449a21', @@ -1847,10 +1847,10 @@ Var('chromium_git') + '/external/khronosgroup/webgl.git' + '@' + 'f4bf599a8b575df685c31d9c4729a70a04e377ed', 'src/third_party/webgpu-cts/src': - Var('chromium_git') + '/external/github.com/gpuweb/cts.git' + '@' + '8dc82d09a9c69d4ea8e3227a75713e8c27365c15', + Var('chromium_git') + '/external/github.com/gpuweb/cts.git' + '@' + '98673cc24786be6c10dd8908e0b0b4ed27625c6a', 'src/third_party/webrtc': - Var('webrtc_git') + '/src.git' + '@' + 'ff0c960ee791fe1c816994c29f6b6d173690fd08', + Var('webrtc_git') + '/src.git' + '@' + '51532fd3554fc47c0d1087dc008e7b4fb99a7287', # Wuffs' canonical repository is at github.com/google/wuffs, but we use # Skia's mirror of Wuffs, the same as in upstream Skia's DEPS file. @@ -1875,7 +1875,7 @@ }, 'src/third_party/xnnpack/src': - Var('chromium_git') + '/external/github.com/google/XNNPACK.git' + '@' + '9325fcfe52092b2f8f816db218bca208db7b2750', + Var('chromium_git') + '/external/github.com/google/XNNPACK.git' + '@' + '3673b5777ef2c350558bac8c630fbffc74a3be25', 'src/tools/page_cycler/acid3': Var('chromium_git') + '/chromium/deps/acid3.git' + '@' + 'a926d0a32e02c4c03ae95bb798e6c780e0e184ba', @@ -1894,7 +1894,7 @@ }, 'src/third_party/zstd/src': - Var('chromium_git') + '/external/github.com/facebook/zstd.git' + '@' + '050fec5c378d676fede8b2171ec5e84f6afa1504', + Var('chromium_git') + '/external/github.com/facebook/zstd.git' + '@' + '621a263fb2e6c2175fbd489e5d77ee8038baa2b2', 'src/tools/skia_goldctl/linux': { 'packages': [ @@ -1962,7 +1962,7 @@ 'packages': [ { 'package': 'chromeos_internal/apps/eche_app/app', - 'version': 'hIJPWhPJNyRu_n8XR62Kqb7RQ5NfwnREHEvZ6I-TLR0C', + 'version': 'rUa1DCKTowD-CVyTwB4OITTMTuQ9zHyQ0iOjFRDHb-8C', }, ], 'condition': 'checkout_chromeos and checkout_src_internal', @@ -1973,7 +1973,7 @@ 'packages': [ { 'package': 'chromeos_internal/apps/help_app/app', - 'version': 'vlCZ6O7FpsQbXF3c7-wdjpidNElu0vp_6XuH6hrC9v8C', + 'version': '_g6MJUUrZNM_z2GjotgMlXISlMyDJcGAc-DMXOMBKkgC', }, ], 'condition': 'checkout_chromeos and checkout_src_internal', @@ -1984,7 +1984,7 @@ 'packages': [ { 'package': 'chromeos_internal/apps/media_app/app', - 'version': 'NnD_TAM5Gh2pqIw9zcN0qm-zishL0eJJpozX0hWOpncC', + 'version': 'iwZSBgY1X8nMG8at7xFZwtMPqU7_WP0TYJ9lYby1gCwC', }, ], 'condition': 'checkout_chromeos and checkout_src_internal', @@ -2017,7 +2017,7 @@ 'packages': [ { 'package': 'chromeos_internal/apps/projector_app/app', - 'version': 'z8XITk6bX7sZL2pROlKAxCt4CLgYdKLtFhDnfxz0bJEC', + 'version': 'iMf3-gy5BU1wc0TqU3UuS0qDM_HgBLtLgqUVbxM0jasC', }, ], 'condition': 'checkout_chromeos and checkout_src_internal', @@ -4036,7 +4036,7 @@ 'src/components/optimization_guide/internal': { 'url': Var('chrome_git') + '/chrome/components/optimization_guide.git' + '@' + - 'a7fe2c3fbd37e5e2a4cce41f38d7dbd395313d91', + '3e87b5cf956eeb1cfc3437ae5dd09e86cacbf9f9', 'condition': 'checkout_src_internal', }, @@ -4096,7 +4096,7 @@ 'src/ios_internal': { 'url': Var('chrome_git') + '/chrome/ios_internal.git' + '@' + - 'ec7715a343c234698035f588ce7c300a5dbf89ce', + '2e41c6955ac16c3568e2e0b1685e08fd689addb9', 'condition': 'checkout_ios and checkout_src_internal', },
diff --git a/PRESUBMIT.py b/PRESUBMIT.py index 127552b..cab2347 100644 --- a/PRESUBMIT.py +++ b/PRESUBMIT.py
@@ -1308,6 +1308,18 @@ [_THIRD_PARTY_EXCEPT_BLINK], # Don't warn in third_party folders. ), BanRule( + r'/\bstd::to_address\b', + ( + 'std::to_address is banned because it is not guaranteed to be', + 'SFINAE-compatible. Use base::to_address instead.', + ), + True, + [ + # Needed in base::to_address implementation. + r'base/types/to_address.h', + _THIRD_PARTY_EXCEPT_BLINK], # Not an error in third_party folders. + ), + BanRule( r'/#include <syncstream>', ( '<syncstream> is banned.',
diff --git a/android_webview/BUILD.gn b/android_webview/BUILD.gn index f287505..76f5594 100644 --- a/android_webview/BUILD.gn +++ b/android_webview/BUILD.gn
@@ -528,6 +528,7 @@ "java/src/org/chromium/android_webview/AwDevToolsServer.java", "java/src/org/chromium/android_webview/AwFeatureMap.java", "java/src/org/chromium/android_webview/AwHttpAuthHandler.java", + "java/src/org/chromium/android_webview/AwInterfaceRegistrar.java", "java/src/org/chromium/android_webview/AwPacProcessor.java", "java/src/org/chromium/android_webview/AwPdfExporter.java", "java/src/org/chromium/android_webview/AwProxyController.java", @@ -588,6 +589,7 @@ "java/src/org/chromium/android_webview/AwGeolocationPermissions.java", "java/src/org/chromium/android_webview/AwHistogramRecorder.java", "java/src/org/chromium/android_webview/AwHttpAuthHandler.java", + "java/src/org/chromium/android_webview/AwInterfaceRegistrar.java", "java/src/org/chromium/android_webview/AwKeyboardShortcuts.java", "java/src/org/chromium/android_webview/AwLayoutSizer.java", "java/src/org/chromium/android_webview/AwMediaIntegrityApiStatusConfig.java", @@ -705,6 +707,7 @@ "//components/embedder_support/android:util_java", "//components/embedder_support/android:web_contents_delegate_java", "//components/embedder_support/android/metrics:java", + "//components/externalauth/android:google_delegate_public_impl_java", "//components/minidump_uploader:minidump_uploader_java", "//components/navigation_interception/android:navigation_interception_java", "//components/policy/android:policy_java", @@ -714,22 +717,27 @@ "//components/variations:variations_java", "//components/variations/android:variations_java", "//components/viz/service:service_java", + "//components/webauthn/android:delegate_public_java", + "//components/webauthn/android:java", "//components/zoom/android:java", "//content/public/android:content_java", "//content/public/android:content_java_resources", "//content/public/common:common_java", "//device/gamepad:java", + "//mojo/public/java:bindings_java", "//mojo/public/java:system_java", "//mojo/public/java/system:system_impl_java", "//net/android:net_java", "//services/data_decoder/public/cpp/android:safe_json_java", "//services/network/public/mojom:mojom_java", "//services/network/public/mojom:url_loader_base_java", + "//services/service_manager/public/java:service_manager_java", "//third_party/android_deps:com_google_code_findbugs_jsr305_java", "//third_party/android_deps:protobuf_lite_runtime_java", "//third_party/androidx:androidx_annotation_annotation_java", "//third_party/androidx:androidx_core_core_java", "//third_party/blink/public:blink_headers_java", + "//third_party/blink/public/mojom:android_mojo_bindings_java", "//third_party/blink/public/mojom:mojom_platform_java", "//third_party/jni_zero:jni_zero_java", "//third_party/metrics_proto:metrics_proto_java",
diff --git a/android_webview/browser/BUILD.gn b/android_webview/browser/BUILD.gn index abf0b22..c80183a 100644 --- a/android_webview/browser/BUILD.gn +++ b/android_webview/browser/BUILD.gn
@@ -304,6 +304,8 @@ "//content/public/browser", "//media/mojo:buildflags", "//services/cert_verifier/public/mojom", + "//services/device/public/cpp:device_feature_list", + "//services/device/public/java:device_feature_list_jni", "//services/network/public/mojom", "//services/proxy_resolver:lib", "//third_party/blink/public/common",
diff --git a/android_webview/browser/aw_browser_context_store.cc b/android_webview/browser/aw_browser_context_store.cc index fe911b70..850b2e1 100644 --- a/android_webview/browser/aw_browser_context_store.cc +++ b/android_webview/browser/aw_browser_context_store.cc
@@ -13,6 +13,7 @@ #include "android_webview/browser/aw_browser_process.h" #include "android_webview/browser_jni_headers/AwBrowserContextStore_jni.h" #include "base/check_op.h" +#include "base/feature_list.h" #include "base/files/file_path.h" #include "base/files/file_util.h" #include "base/memory/raw_ptr.h" @@ -26,6 +27,7 @@ #include "components/prefs/pref_service.h" #include "components/prefs/scoped_user_pref_update.h" #include "content/public/browser/browser_thread.h" +#include "content/public/browser/render_process_host.h" namespace android_webview { @@ -36,6 +38,14 @@ bool g_initialized = false; +BASE_FEATURE(kCreateSpareRendererOnBrowserContextCreation, + "CreateSpareRendererOnBrowserContextCreation", + base::FEATURE_DISABLED_BY_DEFAULT); + +const base::FeatureParam<bool> kCreateSpareRendereForDefaultIfMultiProfile{ + &kCreateSpareRendererOnBrowserContextCreation, + "create_spare_renderer_for_default_if_multi_profile", true}; + } // namespace AwBrowserContextStore::AwBrowserContextStore(PrefService* pref_service) @@ -101,6 +111,16 @@ const bool is_default = name == kDefaultContextName; entry->instance = std::make_unique<AwBrowserContext>(name, entry->path, is_default); + // Ensure this code path is only taken if the IO thread is already running, + // as it's needed for launching processes. + if (base::FeatureList::IsEnabled( + kCreateSpareRendererOnBrowserContextCreation) && + content::BrowserThread::IsThreadInitialized( + content::BrowserThread::IO) && + (!is_default || kCreateSpareRendereForDefaultIfMultiProfile.Get())) { + content::RenderProcessHost::WarmupSpareRenderProcessHost( + entry->instance.get()); + } } return entry->instance.get(); }
diff --git a/android_webview/browser/aw_browser_main_parts.cc b/android_webview/browser/aw_browser_main_parts.cc index d46b3ca..8e72060c 100644 --- a/android_webview/browser/aw_browser_main_parts.cc +++ b/android_webview/browser/aw_browser_main_parts.cc
@@ -18,6 +18,7 @@ #include "android_webview/browser/metrics/system_state_util.h" #include "android_webview/browser/network_service/aw_network_change_notifier_factory.h" #include "android_webview/browser/tracing/background_tracing_field_trial.h" +#include "android_webview/browser_jni_headers/AwInterfaceRegistrar_jni.h" #include "android_webview/common/aw_descriptors.h" #include "android_webview/common/aw_paths.h" #include "android_webview/common/aw_resource.h" @@ -315,6 +316,9 @@ // to load the database from disk. AwBrowserContext::GetDefault()->GetOriginTrialsControllerDelegate(); + Java_AwInterfaceRegistrar_registerMojoInterfaces( + base::android::AttachCurrentThread()); + return content::RESULT_CODE_NORMAL_EXIT; }
diff --git a/android_webview/browser/aw_contents_io_thread_client.cc b/android_webview/browser/aw_contents_io_thread_client.cc index d299db1..da0ae21 100644 --- a/android_webview/browser/aw_contents_io_thread_client.cc +++ b/android_webview/browser/aw_contents_io_thread_client.cc
@@ -22,6 +22,7 @@ #include "base/functional/bind.h" #include "base/lazy_instance.h" #include "base/logging.h" +#include "base/memory/raw_ptr.h" #include "base/metrics/histogram_macros.h" #include "base/synchronization/lock.h" #include "base/threading/scoped_blocking_call.h" @@ -57,7 +58,8 @@ typedef map<content::GlobalRenderFrameHostToken, JavaObjectWeakGlobalRef> RenderFrameHostToWeakGlobalRefType; -typedef pair<base::flat_set<RenderFrameHost*>, JavaObjectWeakGlobalRef> +typedef pair<base::flat_set<raw_ptr<RenderFrameHost, CtnExperimental>>, + JavaObjectWeakGlobalRef> HostsAndWeakGlobalRefPair; // When browser side navigation is enabled, RenderFrameIDs do not have
diff --git a/android_webview/java/DEPS b/android_webview/java/DEPS index e00b811..f39c800 100644 --- a/android_webview/java/DEPS +++ b/android_webview/java/DEPS
@@ -8,6 +8,7 @@ "+components/safe_browsing/android/java", "+components/stylus_handwriting/android/java", "+components/component_updater/android/java", + "+components/webauthn/android", "+components/zoom/android/java", "-content/public/android/java",
diff --git a/android_webview/java/src/org/chromium/android_webview/AwInterfaceRegistrar.java b/android_webview/java/src/org/chromium/android_webview/AwInterfaceRegistrar.java new file mode 100644 index 0000000..6515e050 --- /dev/null +++ b/android_webview/java/src/org/chromium/android_webview/AwInterfaceRegistrar.java
@@ -0,0 +1,33 @@ +// Copyright 2024 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.android_webview; + +import org.jni_zero.CalledByNative; + +import org.chromium.blink.mojom.Authenticator; +import org.chromium.components.webauthn.AuthenticatorFactory; +import org.chromium.content_public.browser.InterfaceRegistrar; +import org.chromium.content_public.browser.RenderFrameHost; +import org.chromium.services.service_manager.InterfaceRegistry; + +/** Registers mojo interface implementations exposed to C++ code at the Android Webview layer. */ +class AwInterfaceRegistrar { + @CalledByNative + private static void registerMojoInterfaces() { + InterfaceRegistrar.Registry.addRenderFrameHostRegistrar( + new AndroidWebviewRenderFrameHostInterfaceRegistrar()); + } + + private static class AndroidWebviewRenderFrameHostInterfaceRegistrar + implements InterfaceRegistrar<RenderFrameHost> { + @Override + public void registerInterfaces( + InterfaceRegistry registry, final RenderFrameHost renderFrameHost) { + registry.addInterface( + Authenticator.MANAGER, + new AuthenticatorFactory(renderFrameHost, /* confirmationFactory= */ null)); + } + } +}
diff --git a/android_webview/java/src/org/chromium/android_webview/common/ProductionSupportedFlagList.java b/android_webview/java/src/org/chromium/android_webview/common/ProductionSupportedFlagList.java index 10311bb2..cd6dff6e 100644 --- a/android_webview/java/src/org/chromium/android_webview/common/ProductionSupportedFlagList.java +++ b/android_webview/java/src/org/chromium/android_webview/common/ProductionSupportedFlagList.java
@@ -427,10 +427,6 @@ "Enables establishing the GPU channel asnchronously when requesting a new " + "layer tree frame sink."), Flag.baseFeature( - BlinkFeatures.DECODE_SCRIPT_SOURCE_OFF_THREAD, - "If enabled, script source text will be decoded and hashed off the main" - + "thread."), - Flag.baseFeature( BaseFeatures.OPTIMIZE_DATA_URLS, "Optimizes parsing and loading of data: URLs."), Flag.baseFeature( BlinkFeatures.PREFETCH_FONT_LOOKUP_TABLES, @@ -907,6 +903,11 @@ "DoNotEvictOnAXLocationChange", "When enabled, do not evict the bfcache entry even when AXLocationChange happens."), Flag.baseFeature("PassHistogramSharedMemoryOnLaunch"), + Flag.baseFeature( + BlinkFeatures.NO_THROTTLING_VISIBLE_AGENT, + "Do not throttle Javascript timers to 1Hz on hidden cross-origin frames that are" + + " same-agent with a visible frame."), + Flag.baseFeature("CreateSpareRendererOnBrowserContextCreation"), // Add new commandline switches and features above. The final entry should have a // trailing comma for cleaner diffs. };
diff --git a/android_webview/javatests/src/org/chromium/android_webview/test/AwAutofillTest.java b/android_webview/javatests/src/org/chromium/android_webview/test/AwAutofillTest.java index aefb166..ba653dd 100644 --- a/android_webview/javatests/src/org/chromium/android_webview/test/AwAutofillTest.java +++ b/android_webview/javatests/src/org/chromium/android_webview/test/AwAutofillTest.java
@@ -96,16 +96,18 @@ public static final int AUTOFILL_VIEW_EXITED = 1; public static final int AUTOFILL_VALUE_CHANGED = 2; public static final int AUTOFILL_COMMIT = 3; - public static final int AUTOFILL_CANCEL = 4; - public static final int AUTOFILL_SESSION_STARTED = 5; - public static final int AUTOFILL_PREDICTIONS_AVAILABLE = 6; - public static final int AUTOFILL_EVENT_MAX = 7; + public static final int AUTOFILL_CANCEL_PRE_P = 4; + public static final int AUTOFILL_CANCEL = 5; + public static final int AUTOFILL_SESSION_STARTED = 6; + public static final int AUTOFILL_PREDICTIONS_AVAILABLE = 7; + public static final int AUTOFILL_EVENT_MAX = 8; public static final String[] EVENT = { "VIEW_ENTERED", "VIEW_EXITED", "VALUE_CHANGED", "COMMIT", + "CANCEL_PRE_P", "CANCEL", "SESSION_STARTED", "QUERY_DONE" @@ -183,7 +185,6 @@ mEventQueue.add(AUTOFILL_CANCEL); mCallbackHelper.notifyCalled(); } - @Override public void notifyNewSessionStarted(boolean hasServerPrediction) { if (DEBUG) Log.i(TAG, "notifyNewSessionStarted"); @@ -259,7 +260,7 @@ mTest.waitForCallbackAndVerifyTypes( mCnt, new Integer[] { - AUTOFILL_CANCEL, + AUTOFILL_CANCEL_PRE_P, AUTOFILL_VIEW_ENTERED, AUTOFILL_SESSION_STARTED, AUTOFILL_VALUE_CHANGED @@ -277,7 +278,7 @@ mTest.waitForCallbackAndVerifyTypes( mCnt, new Integer[] { - AUTOFILL_CANCEL, + AUTOFILL_CANCEL_PRE_P, AUTOFILL_VIEW_ENTERED, AUTOFILL_SESSION_STARTED, AUTOFILL_VALUE_CHANGED @@ -325,7 +326,10 @@ mTest.executeJavaScriptAndWaitForResult("document.getElementById('formid').submit();"); mCnt += mTest.waitForCallbackAndVerifyTypes( - mCnt, new Integer[] {AUTOFILL_VALUE_CHANGED, AUTOFILL_COMMIT}); + mCnt, + new Integer[] { + AUTOFILL_VALUE_CHANGED, AUTOFILL_COMMIT, AUTOFILL_CANCEL + }); } public void reload() throws Throwable { @@ -334,7 +338,10 @@ mTest.waitForCallbackAndVerifyTypes( mCnt, new Integer[] { - AUTOFILL_VIEW_EXITED, AUTOFILL_VALUE_CHANGED, AUTOFILL_COMMIT + AUTOFILL_VIEW_EXITED, + AUTOFILL_VALUE_CHANGED, + AUTOFILL_COMMIT, + AUTOFILL_CANCEL }); } @@ -347,7 +354,7 @@ mCnt, new Integer[] { AUTOFILL_VIEW_EXITED, - AUTOFILL_CANCEL, + AUTOFILL_CANCEL_PRE_P, AUTOFILL_VIEW_ENTERED, AUTOFILL_SESSION_STARTED, AUTOFILL_VALUE_CHANGED @@ -479,7 +486,7 @@ waitForCallbackAndVerifyTypes( cnt, new Integer[] { - AUTOFILL_CANCEL, AUTOFILL_VIEW_ENTERED, AUTOFILL_SESSION_STARTED + AUTOFILL_CANCEL_PRE_P, AUTOFILL_VIEW_ENTERED, AUTOFILL_SESSION_STARTED }); dispatchDownAndUpKeyEvents(KeyEvent.KEYCODE_A); waitForCallbackAndVerifyTypes(cnt, new Integer[] {AUTOFILL_VALUE_CHANGED}); @@ -519,7 +526,7 @@ waitForCallbackAndVerifyTypes( cnt, new Integer[] { - AUTOFILL_CANCEL, + AUTOFILL_CANCEL_PRE_P, AUTOFILL_VIEW_ENTERED, AUTOFILL_SESSION_STARTED, AUTOFILL_VALUE_CHANGED @@ -674,7 +681,7 @@ waitForCallbackAndVerifyTypes( cnt, new Integer[] { - AUTOFILL_CANCEL, + AUTOFILL_CANCEL_PRE_P, AUTOFILL_VIEW_ENTERED, AUTOFILL_SESSION_STARTED, AUTOFILL_VIEW_EXITED, @@ -740,7 +747,7 @@ waitForCallbackAndVerifyTypes( cnt, new Integer[] { - AUTOFILL_CANCEL, AUTOFILL_VIEW_ENTERED, AUTOFILL_SESSION_STARTED + AUTOFILL_CANCEL_PRE_P, AUTOFILL_VIEW_ENTERED, AUTOFILL_SESSION_STARTED }); // Reload the page and check that the user clicking on the same form field ends the current @@ -755,6 +762,7 @@ new Integer[] { AUTOFILL_VIEW_EXITED, AUTOFILL_CANCEL, + AUTOFILL_CANCEL_PRE_P, AUTOFILL_VIEW_ENTERED, AUTOFILL_SESSION_STARTED }); @@ -774,7 +782,7 @@ waitForCallbackAndVerifyTypes( cnt, new Integer[] { - AUTOFILL_CANCEL, + AUTOFILL_CANCEL_PRE_P, AUTOFILL_VIEW_ENTERED, AUTOFILL_SESSION_STARTED, AUTOFILL_VALUE_CHANGED @@ -809,7 +817,7 @@ waitForCallbackAndVerifyTypes( cnt, new Integer[] { - AUTOFILL_CANCEL, + AUTOFILL_CANCEL_PRE_P, AUTOFILL_VIEW_ENTERED, AUTOFILL_SESSION_STARTED, AUTOFILL_VALUE_CHANGED @@ -857,7 +865,7 @@ waitForCallbackAndVerifyTypes( cnt, new Integer[] { - AUTOFILL_CANCEL, + AUTOFILL_CANCEL_PRE_P, AUTOFILL_VIEW_ENTERED, AUTOFILL_SESSION_STARTED, AUTOFILL_VALUE_CHANGED @@ -909,7 +917,7 @@ waitForCallbackAndVerifyTypes( cnt, new Integer[] { - AUTOFILL_CANCEL, + AUTOFILL_CANCEL_PRE_P, AUTOFILL_VIEW_ENTERED, AUTOFILL_SESSION_STARTED, AUTOFILL_VALUE_CHANGED @@ -972,7 +980,7 @@ waitForCallbackAndVerifyTypes( cnt, new Integer[] { - AUTOFILL_CANCEL, + AUTOFILL_CANCEL_PRE_P, AUTOFILL_VIEW_ENTERED, AUTOFILL_SESSION_STARTED, AUTOFILL_VALUE_CHANGED @@ -1023,7 +1031,8 @@ AUTOFILL_VALUE_CHANGED, AUTOFILL_VALUE_CHANGED, AUTOFILL_VALUE_CHANGED, - AUTOFILL_COMMIT + AUTOFILL_COMMIT, + AUTOFILL_CANCEL }); ArrayList<Pair<Integer, AutofillValue>> values = getChangedValues(); assertEquals(4, values.size()); @@ -1046,7 +1055,7 @@ waitForCallbackAndVerifyTypes( cnt, new Integer[] { - AUTOFILL_CANCEL, + AUTOFILL_CANCEL_PRE_P, AUTOFILL_VIEW_ENTERED, AUTOFILL_SESSION_STARTED, AUTOFILL_VALUE_CHANGED @@ -1078,7 +1087,7 @@ waitForCallbackAndVerifyTypes( cnt, new Integer[] { - AUTOFILL_CANCEL, + AUTOFILL_CANCEL_PRE_P, AUTOFILL_VIEW_ENTERED, AUTOFILL_SESSION_STARTED, AUTOFILL_VALUE_CHANGED @@ -1090,7 +1099,7 @@ cnt, new Integer[] { AUTOFILL_VIEW_EXITED, - AUTOFILL_CANCEL, + AUTOFILL_CANCEL_PRE_P, AUTOFILL_VIEW_ENTERED, AUTOFILL_SESSION_STARTED, AUTOFILL_VALUE_CHANGED @@ -1163,7 +1172,7 @@ waitForCallbackAndVerifyTypes( count, new Integer[] { - AUTOFILL_CANCEL, + AUTOFILL_CANCEL_PRE_P, AUTOFILL_VIEW_ENTERED, AUTOFILL_SESSION_STARTED, AUTOFILL_VALUE_CHANGED @@ -1182,7 +1191,7 @@ count, new Integer[] { AUTOFILL_VIEW_EXITED, - AUTOFILL_CANCEL, + AUTOFILL_CANCEL_PRE_P, AUTOFILL_VIEW_ENTERED, AUTOFILL_SESSION_STARTED, AUTOFILL_VALUE_CHANGED @@ -1217,7 +1226,7 @@ waitForCallbackAndVerifyTypes( cnt, new Integer[] { - AUTOFILL_CANCEL, AUTOFILL_VIEW_ENTERED, AUTOFILL_SESSION_STARTED + AUTOFILL_CANCEL_PRE_P, AUTOFILL_VIEW_ENTERED, AUTOFILL_SESSION_STARTED }); } @@ -1247,7 +1256,7 @@ waitForCallbackAndVerifyTypes( cnt, new Integer[] { - AUTOFILL_CANCEL, AUTOFILL_VIEW_ENTERED, AUTOFILL_SESSION_STARTED + AUTOFILL_CANCEL_PRE_P, AUTOFILL_VIEW_ENTERED, AUTOFILL_SESSION_STARTED }); // Removing focus from this element should cause a notification that the autofill view was @@ -1293,7 +1302,7 @@ waitForCallbackAndVerifyTypes( cnt, new Integer[] { - AUTOFILL_CANCEL, + AUTOFILL_CANCEL_PRE_P, AUTOFILL_VIEW_ENTERED, AUTOFILL_SESSION_STARTED, AUTOFILL_VALUE_CHANGED @@ -1305,7 +1314,8 @@ AUTOFILL_VIEW_EXITED, AUTOFILL_VALUE_CHANGED, AUTOFILL_VALUE_CHANGED, - AUTOFILL_COMMIT + AUTOFILL_COMMIT, + AUTOFILL_CANCEL }); assertEquals(SubmissionSource.PROBABLY_FORM_SUBMITTED, mSubmissionSource); } @@ -1356,7 +1366,7 @@ waitForCallbackAndVerifyTypes( cnt, new Integer[] { - AUTOFILL_CANCEL, + AUTOFILL_CANCEL_PRE_P, AUTOFILL_VIEW_ENTERED, AUTOFILL_SESSION_STARTED, AUTOFILL_VALUE_CHANGED @@ -1403,7 +1413,7 @@ waitForCallbackAndVerifyTypes( cnt, new Integer[] { - AUTOFILL_CANCEL, + AUTOFILL_CANCEL_PRE_P, AUTOFILL_VIEW_ENTERED, AUTOFILL_SESSION_STARTED, AUTOFILL_VALUE_CHANGED @@ -1453,7 +1463,7 @@ waitForCallbackAndVerifyTypes( cnt, new Integer[] { - AUTOFILL_CANCEL, + AUTOFILL_CANCEL_PRE_P, AUTOFILL_VIEW_ENTERED, AUTOFILL_SESSION_STARTED, AUTOFILL_VALUE_CHANGED @@ -1496,7 +1506,7 @@ waitForCallbackAndVerifyTypes( cnt, new Integer[] { - AUTOFILL_CANCEL, + AUTOFILL_CANCEL_PRE_P, AUTOFILL_VIEW_ENTERED, AUTOFILL_SESSION_STARTED, AUTOFILL_VALUE_CHANGED @@ -1535,7 +1545,7 @@ waitForCallbackAndVerifyTypes( cnt, new Integer[] { - AUTOFILL_CANCEL, + AUTOFILL_CANCEL_PRE_P, AUTOFILL_VIEW_ENTERED, AUTOFILL_SESSION_STARTED, AUTOFILL_VALUE_CHANGED @@ -2176,7 +2186,7 @@ waitForCallbackAndVerifyTypes( cnt, new Integer[] { - AUTOFILL_CANCEL, + AUTOFILL_CANCEL_PRE_P, AUTOFILL_VIEW_ENTERED, AUTOFILL_SESSION_STARTED, AUTOFILL_VALUE_CHANGED @@ -2188,7 +2198,10 @@ waitForCallbackAndVerifyTypes( cnt, new Integer[] { - AUTOFILL_VIEW_EXITED, AUTOFILL_VALUE_CHANGED, AUTOFILL_COMMIT + AUTOFILL_VIEW_EXITED, + AUTOFILL_VALUE_CHANGED, + AUTOFILL_COMMIT, + AUTOFILL_CANCEL }); TestThreadUtils.runOnUiThreadBlocking( @@ -2229,7 +2242,7 @@ waitForCallbackAndVerifyTypes( cnt, new Integer[] { - AUTOFILL_CANCEL, + AUTOFILL_CANCEL_PRE_P, AUTOFILL_VIEW_ENTERED, AUTOFILL_SESSION_STARTED, AUTOFILL_VALUE_CHANGED @@ -2243,7 +2256,10 @@ waitForCallbackAndVerifyTypes( cnt, new Integer[] { - AUTOFILL_VIEW_EXITED, AUTOFILL_VALUE_CHANGED, AUTOFILL_COMMIT + AUTOFILL_VIEW_EXITED, + AUTOFILL_VALUE_CHANGED, + AUTOFILL_COMMIT, + AUTOFILL_CANCEL }); TestThreadUtils.runOnUiThreadBlocking( @@ -2280,7 +2296,7 @@ waitForCallbackAndVerifyTypes( cnt, new Integer[] { - AUTOFILL_CANCEL, + AUTOFILL_CANCEL_PRE_P, AUTOFILL_VIEW_ENTERED, AUTOFILL_SESSION_STARTED, AUTOFILL_VALUE_CHANGED @@ -2300,7 +2316,10 @@ waitForCallbackAndVerifyTypes( cnt, new Integer[] { - AUTOFILL_VIEW_EXITED, AUTOFILL_VALUE_CHANGED, AUTOFILL_COMMIT + AUTOFILL_VIEW_EXITED, + AUTOFILL_VALUE_CHANGED, + AUTOFILL_COMMIT, + AUTOFILL_CANCEL }); TestThreadUtils.runOnUiThreadBlocking( @@ -2386,7 +2405,7 @@ waitForCallbackAndVerifyTypes( cnt, new Integer[] { - AUTOFILL_CANCEL, + AUTOFILL_CANCEL_PRE_P, AUTOFILL_VIEW_ENTERED, AUTOFILL_SESSION_STARTED, AUTOFILL_VALUE_CHANGED @@ -2425,7 +2444,7 @@ waitForCallbackAndVerifyTypes( cnt, new Integer[] { - AUTOFILL_CANCEL, + AUTOFILL_CANCEL_PRE_P, AUTOFILL_VIEW_ENTERED, AUTOFILL_SESSION_STARTED, AUTOFILL_VALUE_CHANGED @@ -2458,7 +2477,7 @@ waitForCallbackAndVerifyTypes( cnt, new Integer[] { - AUTOFILL_CANCEL, + AUTOFILL_CANCEL_PRE_P, AUTOFILL_VIEW_ENTERED, AUTOFILL_SESSION_STARTED, AUTOFILL_VALUE_CHANGED @@ -2499,7 +2518,7 @@ // datalist. cnt += waitForCallbackAndVerifyTypes( - cnt, new Integer[] {AUTOFILL_CANCEL, AUTOFILL_SESSION_STARTED}); + cnt, new Integer[] {AUTOFILL_CANCEL_PRE_P, AUTOFILL_SESSION_STARTED}); // Verify input accepted. String value1 = executeJavaScriptAndWaitForResult("document.getElementById('text2').value;"); @@ -2563,7 +2582,7 @@ waitForCallbackAndVerifyTypes( cnt, new Integer[] { - AUTOFILL_CANCEL, + AUTOFILL_CANCEL_PRE_P, AUTOFILL_VIEW_ENTERED, AUTOFILL_SESSION_STARTED, AUTOFILL_VALUE_CHANGED @@ -2606,7 +2625,7 @@ waitForCallbackAndVerifyTypes( cnt, new Integer[] { - AUTOFILL_CANCEL, + AUTOFILL_CANCEL_PRE_P, AUTOFILL_VIEW_ENTERED, AUTOFILL_SESSION_STARTED, AUTOFILL_VALUE_CHANGED @@ -2690,7 +2709,7 @@ waitForCallbackAndVerifyTypes( cnt, new Integer[] { - AUTOFILL_CANCEL, + AUTOFILL_CANCEL_PRE_P, AUTOFILL_VIEW_ENTERED, AUTOFILL_SESSION_STARTED, AUTOFILL_VALUE_CHANGED @@ -2795,7 +2814,7 @@ waitForCallbackAndVerifyTypes( cnt, new Integer[] { - AUTOFILL_CANCEL, + AUTOFILL_CANCEL_PRE_P, AUTOFILL_VIEW_ENTERED, AUTOFILL_SESSION_STARTED, AUTOFILL_VALUE_CHANGED @@ -2858,7 +2877,7 @@ waitForCallbackAndVerifyTypes( cnt, new Integer[] { - AUTOFILL_CANCEL, + AUTOFILL_CANCEL_PRE_P, AUTOFILL_VIEW_ENTERED, AUTOFILL_SESSION_STARTED, AUTOFILL_VALUE_CHANGED @@ -2949,7 +2968,7 @@ waitForCallbackAndVerifyTypes( cnt, new Integer[] { - AUTOFILL_CANCEL, + AUTOFILL_CANCEL_PRE_P, AUTOFILL_VIEW_ENTERED, AUTOFILL_SESSION_STARTED, AUTOFILL_VALUE_CHANGED @@ -3035,7 +3054,7 @@ waitForCallbackAndVerifyTypes( cnt, new Integer[] { - AUTOFILL_CANCEL, + AUTOFILL_CANCEL_PRE_P, AUTOFILL_VIEW_ENTERED, AUTOFILL_SESSION_STARTED, AUTOFILL_VALUE_CHANGED @@ -3130,7 +3149,7 @@ waitForCallbackAndVerifyTypes( cnt, new Integer[] { - AUTOFILL_CANCEL, + AUTOFILL_CANCEL_PRE_P, AUTOFILL_VIEW_ENTERED, AUTOFILL_SESSION_STARTED, AUTOFILL_VALUE_CHANGED @@ -3192,7 +3211,7 @@ waitForCallbackAndVerifyTypes( cnt, new Integer[] { - AUTOFILL_CANCEL, + AUTOFILL_CANCEL_PRE_P, AUTOFILL_VIEW_ENTERED, AUTOFILL_SESSION_STARTED, AUTOFILL_VALUE_CHANGED @@ -3273,7 +3292,7 @@ waitForCallbackAndVerifyTypes( cnt, new Integer[] { - AUTOFILL_CANCEL, + AUTOFILL_CANCEL_PRE_P, AUTOFILL_VIEW_ENTERED, AUTOFILL_SESSION_STARTED, AUTOFILL_VALUE_CHANGED @@ -3283,7 +3302,8 @@ var iframe = document.getElementById('address_iframe'); var frame_doc = iframe.contentDocument; frame_doc.getElementById('submit_button').click();"""); - waitForCallbackAndVerifyTypes(cnt, new Integer[] {AUTOFILL_VALUE_CHANGED, AUTOFILL_COMMIT}); + waitForCallbackAndVerifyTypes( + cnt, new Integer[] {AUTOFILL_VALUE_CHANGED, AUTOFILL_COMMIT, AUTOFILL_CANCEL}); assertEquals(SubmissionSource.FORM_SUBMISSION, mSubmissionSource); } @@ -3332,7 +3352,7 @@ waitForCallbackAndVerifyTypes( cnt, new Integer[] { - AUTOFILL_CANCEL, + AUTOFILL_CANCEL_PRE_P, AUTOFILL_VIEW_ENTERED, AUTOFILL_SESSION_STARTED, AUTOFILL_VALUE_CHANGED @@ -3368,7 +3388,7 @@ waitForCallbackAndVerifyTypes( cnt, new Integer[] { - AUTOFILL_CANCEL, + AUTOFILL_CANCEL_PRE_P, AUTOFILL_VIEW_ENTERED, AUTOFILL_SESSION_STARTED, AUTOFILL_VALUE_CHANGED @@ -3382,7 +3402,7 @@ waitForCallbackAndVerifyTypes( cnt, new Integer[] { - AUTOFILL_CANCEL, + AUTOFILL_CANCEL_PRE_P, AUTOFILL_VIEW_EXITED, AUTOFILL_VIEW_ENTERED, AUTOFILL_SESSION_STARTED, @@ -3509,17 +3529,21 @@ private int waitForCallbackAndVerifyTypes(int currentCallCount, Integer[] expectedEventArray) throws TimeoutException { Integer[] adjustedEventArray; - // Didn't call cancel after Android P. - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { - ArrayList<Integer> adjusted = new ArrayList<Integer>(); + ArrayList<Integer> adjusted = new ArrayList<>(); for (Integer event : expectedEventArray) { - if (event != AUTOFILL_CANCEL) adjusted.add(event); + // Filter out AUTOFILL_CANCEL_PRE_P. + // TODO(b/326551145): clean that up once we stop supporting android O. + if (event == AUTOFILL_CANCEL_PRE_P) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) { + adjusted.add(AUTOFILL_CANCEL); + } + continue; } - adjustedEventArray = new Integer[adjusted.size()]; - adjusted.toArray(adjustedEventArray); - } else { - adjustedEventArray = expectedEventArray; + adjusted.add(event); } + + adjustedEventArray = new Integer[adjusted.size()]; + adjusted.toArray(adjustedEventArray); try { // Check against the call count to avoid missing out a callback in between waits, while // exposing it so that the test can control where the call count starts.
diff --git a/android_webview/javatests/src/org/chromium/android_webview/test/AwContentsClientOnRenderProcessGoneTest.java b/android_webview/javatests/src/org/chromium/android_webview/test/AwContentsClientOnRenderProcessGoneTest.java index a513b12..e9ee506 100644 --- a/android_webview/javatests/src/org/chromium/android_webview/test/AwContentsClientOnRenderProcessGoneTest.java +++ b/android_webview/javatests/src/org/chromium/android_webview/test/AwContentsClientOnRenderProcessGoneTest.java
@@ -164,6 +164,7 @@ @Test @Feature({"AndroidWebView"}) + @CommandLineFlags.Add({"disable-features=CreateSpareRendererOnBrowserContextCreation"}) @SmallTest @OnlyRunIn(MULTI_PROCESS) public void testRenderProcessCanNotTerminateBeforeStart() throws Throwable {
diff --git a/android_webview/javatests/src/org/chromium/android_webview/test/AwMetricsIntegrationTest.java b/android_webview/javatests/src/org/chromium/android_webview/test/AwMetricsIntegrationTest.java index b6f6edb..9bbaba1 100644 --- a/android_webview/javatests/src/org/chromium/android_webview/test/AwMetricsIntegrationTest.java +++ b/android_webview/javatests/src/org/chromium/android_webview/test/AwMetricsIntegrationTest.java
@@ -325,6 +325,7 @@ @Test @MediumTest @Feature({"AndroidWebView"}) + @CommandLineFlags.Add({"disable-features=CreateSpareRendererOnBrowserContextCreation"}) public void testMetadata_stability_rendererLaunchCount() throws Throwable { EmbeddedTestServer embeddedTestServer = EmbeddedTestServer.createAndStartServer(
diff --git a/android_webview/test/shell/src/draw_fn/context_manager.cc b/android_webview/test/shell/src/draw_fn/context_manager.cc index 41c6281..85de00a 100644 --- a/android_webview/test/shell/src/draw_fn/context_manager.cc +++ b/android_webview/test/shell/src/draw_fn/context_manager.cc
@@ -29,13 +29,13 @@ #include "third_party/skia/include/core/SkDrawable.h" #include "third_party/skia/include/core/SkSurface.h" #include "third_party/skia/include/core/SkSurfaceProps.h" -#include "third_party/skia/include/gpu/GrBackendDrawableInfo.h" #include "third_party/skia/include/gpu/GrBackendSemaphore.h" #include "third_party/skia/include/gpu/GrBackendSurface.h" #include "third_party/skia/include/gpu/GrDirectContext.h" #include "third_party/skia/include/gpu/GrTypes.h" #include "third_party/skia/include/gpu/MutableTextureState.h" #include "third_party/skia/include/gpu/ganesh/SkSurfaceGanesh.h" +#include "third_party/skia/include/gpu/ganesh/vk/GrBackendDrawableInfo.h" #include "third_party/skia/include/gpu/ganesh/vk/GrVkBackendSemaphore.h" #include "third_party/skia/include/gpu/ganesh/vk/GrVkBackendSurface.h" #include "third_party/skia/include/gpu/ganesh/vk/GrVkDirectContext.h"
diff --git a/android_webview/tools/cts_config/webview_cts_hostside_gcs_path.json b/android_webview/tools/cts_config/webview_cts_hostside_gcs_path.json index c325115..87ba9c6 100644 --- a/android_webview/tools/cts_config/webview_cts_hostside_gcs_path.json +++ b/android_webview/tools/cts_config/webview_cts_hostside_gcs_path.json
@@ -21,7 +21,11 @@ "is_hostside": true, "excludes": [ { - "match": "com.android.cts.webkit.WebViewHostSideMultipleProfileTest#*", + "match": "com.android.cts.webkit.WebViewHostSideMultipleProfileTest#testManagedProfile", + "_bug_id": "b/326395159" + }, + { + "match": "com.android.cts.webkit.WebViewHostSideMultipleProfileTest#testSecondProfile", "_bug_id": "b/326395159" } ]
diff --git a/ash/BUILD.gn b/ash/BUILD.gn index 0041cba7..73c3d5d 100644 --- a/ash/BUILD.gn +++ b/ash/BUILD.gn
@@ -963,8 +963,12 @@ "picker/picker_controller.h", "picker/picker_copy_media.cc", "picker/picker_copy_media.h", + "picker/picker_insert_media.cc", + "picker/picker_insert_media.h", "picker/picker_insert_media_request.cc", "picker/picker_insert_media_request.h", + "picker/picker_rich_media.cc", + "picker/picker_rich_media.h", "picker/picker_search_controller.cc", "picker/picker_search_controller.h", "picker/search/picker_category_search.cc", @@ -985,6 +989,8 @@ "picker/views/picker_emoji_item_view.h", "picker/views/picker_emoticon_item_view.cc", "picker/views/picker_emoticon_item_view.h", + "picker/views/picker_focus_indicator.cc", + "picker/views/picker_focus_indicator.h", "picker/views/picker_gif_view.cc", "picker/views/picker_gif_view.h", "picker/views/picker_icons.cc", @@ -997,17 +1003,21 @@ "picker/views/picker_item_view.h", "picker/views/picker_key_event_handler.cc", "picker/views/picker_key_event_handler.h", - "picker/views/picker_key_event_target.h", "picker/views/picker_list_item_container_view.cc", "picker/views/picker_list_item_container_view.h", "picker/views/picker_list_item_view.cc", "picker/views/picker_list_item_view.h", "picker/views/picker_page_view.cc", "picker/views/picker_page_view.h", + "picker/views/picker_positioning.cc", + "picker/views/picker_positioning.h", + "picker/views/picker_pseudo_focus_handler.h", "picker/views/picker_search_field_view.cc", "picker/views/picker_search_field_view.h", "picker/views/picker_search_results_view.cc", "picker/views/picker_search_results_view.h", + "picker/views/picker_section_list_view.cc", + "picker/views/picker_section_list_view.h", "picker/views/picker_section_view.cc", "picker/views/picker_section_view.h", "picker/views/picker_small_item_grid_view.cc", @@ -1020,6 +1030,8 @@ "picker/views/picker_view.cc", "picker/views/picker_view.h", "picker/views/picker_view_delegate.h", + "picker/views/picker_widget.cc", + "picker/views/picker_widget.h", "picker/views/picker_zero_state_view.cc", "picker/views/picker_zero_state_view.h", "policy/policy_recommendation_restorer.cc", @@ -3043,6 +3055,7 @@ "//chromeos/ash/components/scalable_iph:constants", "//chromeos/ash/components/settings", "//chromeos/ash/components/standalone_browser", + "//chromeos/ash/components/string_matching", "//chromeos/ash/components/system", "//chromeos/ash/resources:resources_grit", "//chromeos/ash/services/assistant/public/cpp", @@ -3533,6 +3546,7 @@ "picker/picker_controller_unittest.cc", "picker/picker_copy_media_unittest.cc", "picker/picker_insert_media_request_unittest.cc", + "picker/picker_insert_media_unittest.cc", "picker/picker_search_controller_unittest.cc", "picker/search/picker_category_search_unittest.cc", "picker/search/picker_date_search_unittest.cc", @@ -3540,14 +3554,18 @@ "picker/views/picker_contents_view_unittest.cc", "picker/views/picker_gif_view_unittest.cc", "picker/views/picker_image_item_grid_view_unittest.cc", + "picker/views/picker_item_view_unittest.cc", "picker/views/picker_key_event_handler_unittest.cc", "picker/views/picker_list_item_container_view_unittest.cc", "picker/views/picker_list_item_view_unittest.cc", + "picker/views/picker_positioning_unittest.cc", "picker/views/picker_search_field_view_unittest.cc", "picker/views/picker_search_results_view_unittest.cc", + "picker/views/picker_section_list_view_unittest.cc", "picker/views/picker_section_view_unittest.cc", "picker/views/picker_small_item_grid_view_unittest.cc", "picker/views/picker_view_unittest.cc", + "picker/views/picker_widget_unittest.cc", "picker/views/picker_zero_state_view_unittest.cc", "policy/policy_recommendation_restorer_unittest.cc", "power/hid_battery_util_unittest.cc", @@ -4721,6 +4739,8 @@ "wm/gestures/back_gesture/test_back_gesture_contextual_nudge_delegate.h", "wm/lock_state_controller_test_api.cc", "wm/lock_state_controller_test_api.h", + "wm/overview/overview_grid_test_api.cc", + "wm/overview/overview_grid_test_api.h", "wm/overview/overview_test_util.cc", "wm/overview/overview_test_util.h", "wm/tablet_mode/tablet_mode_controller_test_api.cc",
diff --git a/ash/DEPS b/ash/DEPS index 52c3f04..3481a25 100644 --- a/ash/DEPS +++ b/ash/DEPS
@@ -78,6 +78,7 @@ "+chromeos/ash/components/peripheral_notification", "+chromeos/ash/components/proximity_auth", "+chromeos/ash/components/standalone_browser", + "+chromeos/ash/components/string_matching", "+chromeos/ash/components/system", "+chromeos/components/mahi", "+chromeos/components/quick_answers",
diff --git a/ash/ambient/metrics/ambient_animation_metrics_recorder.h b/ash/ambient/metrics/ambient_animation_metrics_recorder.h index 56564972..7d97611 100644 --- a/ash/ambient/metrics/ambient_animation_metrics_recorder.h +++ b/ash/ambient/metrics/ambient_animation_metrics_recorder.h
@@ -57,7 +57,8 @@ const lottie::Animation& animation_r) const; const AmbientUiSettings ui_settings_; - base::flat_set<const lottie::Animation*> registered_animations_; + base::flat_set<raw_ptr<const lottie::Animation, CtnExperimental>> + registered_animations_; base::ScopedMultiSourceObservation<lottie::Animation, lottie::AnimationObserver> animation_observations_{this};
diff --git a/ash/ambient/ui/ambient_animation_progress_tracker.cc b/ash/ambient/ui/ambient_animation_progress_tracker.cc index fa5accb..e92f83a 100644 --- a/ash/ambient/ui/ambient_animation_progress_tracker.cc +++ b/ash/ambient/ui/ambient_animation_progress_tracker.cc
@@ -9,15 +9,17 @@ #include "base/check.h" #include "base/logging.h" +#include "base/memory/raw_ptr.h" #include "base/notreached.h" namespace ash { namespace { -void MoveAnimation(base::flat_set<const lottie::Animation*>& from, - base::flat_set<const lottie::Animation*>& to, - const lottie::Animation* animation) { +void MoveAnimation( + base::flat_set<raw_ptr<const lottie::Animation, CtnExperimental>>& from, + base::flat_set<raw_ptr<const lottie::Animation, CtnExperimental>>& to, + const lottie::Animation* animation) { if (to.contains(animation)) { CHECK(!from.contains(animation)); return;
diff --git a/ash/ambient/ui/ambient_animation_progress_tracker.h b/ash/ambient/ui/ambient_animation_progress_tracker.h index 3a3f8cde..b527f8d 100644 --- a/ash/ambient/ui/ambient_animation_progress_tracker.h +++ b/ash/ambient/ui/ambient_animation_progress_tracker.h
@@ -9,6 +9,7 @@ #include "ash/ash_export.h" #include "base/containers/flat_set.h" +#include "base/memory/raw_ptr.h" #include "base/scoped_multi_source_observation.h" #include "ui/lottie/animation.h" #include "ui/lottie/animation_observer.h" @@ -87,9 +88,11 @@ lottie::AnimationObserver> animation_observations_{this}; // Registered animations that have been Start()ed. - base::flat_set<const lottie::Animation*> started_animations_; + base::flat_set<raw_ptr<const lottie::Animation, CtnExperimental>> + started_animations_; // Registered animations that have not been Start()ed yet. - base::flat_set<const lottie::Animation*> inactive_animations_; + base::flat_set<raw_ptr<const lottie::Animation, CtnExperimental>> + inactive_animations_; }; } // namespace ash
diff --git a/ash/app_list/app_list_controller_impl.h b/ash/app_list/app_list_controller_impl.h index 77c4299..9cec34b 100644 --- a/ash/app_list/app_list_controller_impl.h +++ b/ash/app_list/app_list_controller_impl.h
@@ -41,6 +41,7 @@ #include "base/time/time.h" #include "chromeos/ash/services/assistant/public/cpp/assistant_enums.h" #include "ui/aura/window_observer.h" +#include "ui/compositor/throughput_tracker.h" #include "ui/display/display_observer.h" #include "ui/display/types/display_constants.h"
diff --git a/ash/app_list/app_list_feature_usage_metrics_unittest.cc b/ash/app_list/app_list_feature_usage_metrics_unittest.cc index 08cc556..fdf32cf 100644 --- a/ash/app_list/app_list_feature_usage_metrics_unittest.cc +++ b/ash/app_list/app_list_feature_usage_metrics_unittest.cc
@@ -9,6 +9,7 @@ #include "ash/constants/ash_switches.h" #include "ash/shell.h" #include "ash/test/ash_test_base.h" +#include "ash/wm/tablet_mode/tablet_mode_controller.h" #include "base/command_line.h" #include "base/test/metrics/histogram_tester.h" #include "base/test/scoped_feature_list.h"
diff --git a/ash/app_list/app_list_presenter_impl_unittest.cc b/ash/app_list/app_list_presenter_impl_unittest.cc index e5d0296..13aa697 100644 --- a/ash/app_list/app_list_presenter_impl_unittest.cc +++ b/ash/app_list/app_list_presenter_impl_unittest.cc
@@ -17,6 +17,7 @@ #include "ash/public/cpp/shell_window_ids.h" #include "ash/shell.h" #include "ash/test/ash_test_base.h" +#include "ash/wm/tablet_mode/tablet_mode_controller.h" #include "base/run_loop.h" #include "base/test/bind.h" #include "base/test/task_environment.h"
diff --git a/ash/app_list/app_list_util.cc b/ash/app_list/app_list_util.cc index 41d409c..dee3ae8 100644 --- a/ash/app_list/app_list_util.cc +++ b/ash/app_list/app_list_util.cc
@@ -161,7 +161,7 @@ void SetViewIgnoredForAccessibility(views::View* view, bool ignored) { auto& view_accessibility = view->GetViewAccessibility(); view_accessibility.OverrideIsLeaf(ignored); - view_accessibility.OverrideIsIgnored(ignored); + view_accessibility.SetIsIgnored(ignored); view->NotifyAccessibilityEvent(ax::mojom::Event::kTreeChanged, true); }
diff --git a/ash/app_list/views/app_list_bubble_view_unittest.cc b/ash/app_list/views/app_list_bubble_view_unittest.cc index 9e2a4e85..aa6d69b 100644 --- a/ash/app_list/views/app_list_bubble_view_unittest.cc +++ b/ash/app_list/views/app_list_bubble_view_unittest.cc
@@ -1374,33 +1374,33 @@ LeftClickOn(folder_item); auto* search_box = GetSearchBoxView(); - EXPECT_TRUE(search_box->GetViewAccessibility().IsIgnored()); + EXPECT_TRUE(search_box->GetViewAccessibility().GetIsIgnored()); EXPECT_TRUE(search_box->GetViewAccessibility().IsLeaf()); auto* continue_section = GetContinueSectionView(); - EXPECT_TRUE(continue_section->GetViewAccessibility().IsIgnored()); + EXPECT_TRUE(continue_section->GetViewAccessibility().GetIsIgnored()); EXPECT_TRUE(continue_section->GetViewAccessibility().IsLeaf()); auto* recent_apps = GetRecentAppsView(); - EXPECT_TRUE(recent_apps->GetViewAccessibility().IsIgnored()); + EXPECT_TRUE(recent_apps->GetViewAccessibility().GetIsIgnored()); EXPECT_TRUE(recent_apps->GetViewAccessibility().IsLeaf()); auto* toast_container = GetToastContainerView(); - EXPECT_TRUE(toast_container->GetViewAccessibility().IsIgnored()); + EXPECT_TRUE(toast_container->GetViewAccessibility().GetIsIgnored()); EXPECT_TRUE(toast_container->GetViewAccessibility().IsLeaf()); auto* apps_grid = GetAppsGridView(); - EXPECT_TRUE(apps_grid->GetViewAccessibility().IsIgnored()); + EXPECT_TRUE(apps_grid->GetViewAccessibility().GetIsIgnored()); EXPECT_TRUE(apps_grid->GetViewAccessibility().IsLeaf()); // Close the folder. PressAndReleaseKey(ui::VKEY_ESCAPE); - EXPECT_FALSE(search_box->GetViewAccessibility().IsIgnored()); + EXPECT_FALSE(search_box->GetViewAccessibility().GetIsIgnored()); EXPECT_FALSE(search_box->GetViewAccessibility().IsLeaf()); - EXPECT_FALSE(continue_section->GetViewAccessibility().IsIgnored()); + EXPECT_FALSE(continue_section->GetViewAccessibility().GetIsIgnored()); EXPECT_FALSE(continue_section->GetViewAccessibility().IsLeaf()); - EXPECT_FALSE(recent_apps->GetViewAccessibility().IsIgnored()); + EXPECT_FALSE(recent_apps->GetViewAccessibility().GetIsIgnored()); EXPECT_FALSE(recent_apps->GetViewAccessibility().IsLeaf()); - EXPECT_FALSE(toast_container->GetViewAccessibility().IsIgnored()); + EXPECT_FALSE(toast_container->GetViewAccessibility().GetIsIgnored()); EXPECT_FALSE(toast_container->GetViewAccessibility().IsLeaf()); - EXPECT_FALSE(apps_grid->GetViewAccessibility().IsIgnored()); + EXPECT_FALSE(apps_grid->GetViewAccessibility().GetIsIgnored()); EXPECT_FALSE(apps_grid->GetViewAccessibility().IsLeaf()); }
diff --git a/ash/app_list/views/apps_container_view_unittest.cc b/ash/app_list/views/apps_container_view_unittest.cc index a08e89d..39b53aba 100644 --- a/ash/app_list/views/apps_container_view_unittest.cc +++ b/ash/app_list/views/apps_container_view_unittest.cc
@@ -248,28 +248,28 @@ // Note: For fullscreen app list, the search box is part of the focus cycle // when a folder is open. auto* continue_section = helper->GetFullscreenContinueSectionView(); - EXPECT_TRUE(continue_section->GetViewAccessibility().IsIgnored()); + EXPECT_TRUE(continue_section->GetViewAccessibility().GetIsIgnored()); EXPECT_TRUE(continue_section->GetViewAccessibility().IsLeaf()); auto* recent_apps = helper->GetFullscreenRecentAppsView(); - EXPECT_TRUE(recent_apps->GetViewAccessibility().IsIgnored()); + EXPECT_TRUE(recent_apps->GetViewAccessibility().GetIsIgnored()); EXPECT_TRUE(recent_apps->GetViewAccessibility().IsLeaf()); auto* toast_container = GetToastContainerView(); - EXPECT_TRUE(toast_container->GetViewAccessibility().IsIgnored()); + EXPECT_TRUE(toast_container->GetViewAccessibility().GetIsIgnored()); EXPECT_TRUE(toast_container->GetViewAccessibility().IsLeaf()); auto* apps_grid_view = helper->GetRootPagedAppsGridView(); - EXPECT_TRUE(apps_grid_view->GetViewAccessibility().IsIgnored()); + EXPECT_TRUE(apps_grid_view->GetViewAccessibility().GetIsIgnored()); EXPECT_TRUE(apps_grid_view->GetViewAccessibility().IsLeaf()); // Close the folder. PressAndReleaseKey(ui::VKEY_ESCAPE); - EXPECT_FALSE(continue_section->GetViewAccessibility().IsIgnored()); + EXPECT_FALSE(continue_section->GetViewAccessibility().GetIsIgnored()); EXPECT_FALSE(continue_section->GetViewAccessibility().IsLeaf()); - EXPECT_FALSE(recent_apps->GetViewAccessibility().IsIgnored()); + EXPECT_FALSE(recent_apps->GetViewAccessibility().GetIsIgnored()); EXPECT_FALSE(recent_apps->GetViewAccessibility().IsLeaf()); - EXPECT_FALSE(toast_container->GetViewAccessibility().IsIgnored()); + EXPECT_FALSE(toast_container->GetViewAccessibility().GetIsIgnored()); EXPECT_FALSE(toast_container->GetViewAccessibility().IsLeaf()); - EXPECT_FALSE(apps_grid_view->GetViewAccessibility().IsIgnored()); + EXPECT_FALSE(apps_grid_view->GetViewAccessibility().GetIsIgnored()); EXPECT_FALSE(apps_grid_view->GetViewAccessibility().IsLeaf()); }
diff --git a/ash/app_list/views/recent_apps_view_unittest.cc b/ash/app_list/views/recent_apps_view_unittest.cc index c71e410c..45f49d3 100644 --- a/ash/app_list/views/recent_apps_view_unittest.cc +++ b/ash/app_list/views/recent_apps_view_unittest.cc
@@ -23,6 +23,7 @@ #include "ash/public/cpp/app_list/app_list_types.h" #include "ash/shell.h" #include "ash/test/ash_test_base.h" +#include "ash/wm/tablet_mode/tablet_mode_controller.h" #include "base/strings/stringprintf.h" #include "testing/gtest/include/gtest/gtest.h" #include "ui/gfx/geometry/point.h"
diff --git a/ash/app_list/views/remove_query_confirmation_dialog.cc b/ash/app_list/views/remove_query_confirmation_dialog.cc index bf1b183b..b5740a8 100644 --- a/ash/app_list/views/remove_query_confirmation_dialog.cc +++ b/ash/app_list/views/remove_query_confirmation_dialog.cc
@@ -100,7 +100,7 @@ title_->layer()->SetFillsBoundsOpaquely(false); // Ignore labels for accessibility - the accessible name is defined for the // whole dialog view. - title_->GetViewAccessibility().OverrideIsIgnored(true); + title_->GetViewAccessibility().SetIsIgnored(true); // Add dialog body. body_ = @@ -125,7 +125,7 @@ body_->layer()->SetFillsBoundsOpaquely(false); // Ignore labels for accessibility - the accessible name is defined for the // whole dialog view. - body_->GetViewAccessibility().OverrideIsIgnored(true); + body_->GetViewAccessibility().SetIsIgnored(true); auto run_callback = [](RemoveQueryConfirmationDialog* dialog, bool accept) { if (!dialog->confirm_callback_)
diff --git a/ash/app_list/views/search_result_image_view.cc b/ash/app_list/views/search_result_image_view.cc index ec9eed1..8322ff66 100644 --- a/ash/app_list/views/search_result_image_view.cc +++ b/ash/app_list/views/search_result_image_view.cc
@@ -88,7 +88,7 @@ SetLayoutManager(std::make_unique<views::FillLayout>()); result_image_ = AddChildView(std::make_unique<ImagePreviewView>()); result_image_->SetCanProcessEventsWithinSubtree(false); - result_image_->GetViewAccessibility().OverrideIsIgnored(true); + result_image_->GetViewAccessibility().SetIsIgnored(true); views::FocusRing::Install(this); views::FocusRing* const focus_ring = views::FocusRing::Get(this); @@ -138,7 +138,7 @@ pulsing_block_view_ = AddChildView(std::make_unique<PulsingBlockView>( size(), base::Milliseconds(index_ * 200), kRoundedCornerRadius)); pulsing_block_view_->SetCanProcessEventsWithinSubtree(false); - pulsing_block_view_->GetViewAccessibility().OverrideIsIgnored(true); + pulsing_block_view_->GetViewAccessibility().SetIsIgnored(true); } gfx::ImageSkia SearchResultImageView::CreateDragImage() {
diff --git a/ash/app_list/views/search_result_view.cc b/ash/app_list/views/search_result_view.cc index 97c12a5..2d32193 100644 --- a/ash/app_list/views/search_result_view.cc +++ b/ash/app_list/views/search_result_view.cc
@@ -237,7 +237,7 @@ views::ImageView* SetupChildImageView(views::FlexLayoutView* parent) { views::ImageView* image_view = parent->AddChildView(std::make_unique<views::ImageView>()); - image_view->GetViewAccessibility().OverrideIsIgnored(true); + image_view->GetViewAccessibility().SetIsIgnored(true); image_view->SetCanProcessEventsWithinSubtree(false); image_view->SetVerticalAlignment(views::ImageView::Alignment::kCenter); image_view->SetVisible(false); @@ -259,7 +259,7 @@ // Ignore labels for accessibility - the result accessible name is defined on // the whole result view. label->SetHorizontalAlignment(gfx::ALIGN_LEFT); - label->GetViewAccessibility().OverrideIsIgnored(true); + label->GetViewAccessibility().SetIsIgnored(true); label->SetBackgroundColor(SK_ColorTRANSPARENT); label->SetAutoColorReadabilityEnabled(false); label->SetEnabledColorId(color_id); @@ -331,7 +331,7 @@ std::optional<double> lower_warning_limit) { views::ProgressBar* progress_bar_view = parent->AddChildView(std::make_unique<views::ProgressBar>()); - progress_bar_view->GetViewAccessibility().OverrideIsIgnored(true); + progress_bar_view->GetViewAccessibility().SetIsIgnored(true); progress_bar_view->SetCanProcessEventsWithinSubtree(false); progress_bar_view->SetPreferredSize( gfx::Size(kProgressBarWidth, kProgressBarHeight)); @@ -363,7 +363,7 @@ parent->AddChildView(std::make_unique<SearchResultInlineIconView>( alterante_icon_and_text_styling)); inline_icon_view->SetCanProcessEventsWithinSubtree(false); - inline_icon_view->GetViewAccessibility().OverrideIsIgnored(true); + inline_icon_view->GetViewAccessibility().SetIsIgnored(true); inline_icon_view->SetVisible(false); inline_icon_view->SetProperty( views::kFlexBehaviorKey, @@ -464,11 +464,11 @@ icon_view_ = AddChildView(std::make_unique<MaskedImageView>()); icon_view_->SetCanProcessEventsWithinSubtree(false); - icon_view_->GetViewAccessibility().OverrideIsIgnored(true); + icon_view_->GetViewAccessibility().SetIsIgnored(true); badge_icon_view_ = AddChildView(std::make_unique<views::ImageView>()); badge_icon_view_->SetCanProcessEventsWithinSubtree(false); - badge_icon_view_->GetViewAccessibility().OverrideIsIgnored(true); + badge_icon_view_->GetViewAccessibility().SetIsIgnored(true); auto* actions_view = AddChildView(std::make_unique<SearchResultActionsView>(this)); @@ -596,7 +596,7 @@ SearchResultTextItem::OverflowBehavior::kNoElide); result_text_separator_label_->SetText( l10n_util::GetStringUTF16(IDS_ASH_SEARCH_RESULT_SEPARATOR)); - result_text_separator_label_->GetViewAccessibility().OverrideIsIgnored(true); + result_text_separator_label_->GetViewAccessibility().SetIsIgnored(true); details_container_ = title_and_details_container_->AddChildView( std::make_unique<views::FlexLayoutView>()); @@ -620,7 +620,7 @@ SearchResultTextItem::OverflowBehavior::kNoElide); rating_separator_label_->SetText( l10n_util::GetStringUTF16(IDS_ASH_SEARCH_RESULT_SEPARATOR)); - rating_separator_label_->GetViewAccessibility().OverrideIsIgnored(true); + rating_separator_label_->GetViewAccessibility().SetIsIgnored(true); rating_ = SetupChildLabelView( title_and_details_container_, view_type_, LabelType::kDetails,
diff --git a/ash/app_list/views/top_icon_animation_view.cc b/ash/app_list/views/top_icon_animation_view.cc index 177f5e5..03a75a30 100644 --- a/ash/app_list/views/top_icon_animation_view.cc +++ b/ash/app_list/views/top_icon_animation_view.cc
@@ -231,7 +231,7 @@ void TopIconAnimationView::OnThemeChanged() { views::View::OnThemeChanged(); - if (icon_background_) { + if (icon_background_ && icon_background_->layer()) { icon_background_->layer()->SetColor( GetColorProvider()->GetColor(cros_tokens::kCrosSysSystemOnBaseOpaque)); }
diff --git a/ash/birch/birch_item.cc b/ash/birch/birch_item.cc index 6363008..1fab39c 100644 --- a/ash/birch/birch_item.cc +++ b/ash/birch/birch_item.cc
@@ -95,7 +95,7 @@ //////////////////////////////////////////////////////////////////////////////// BirchFileItem::BirchFileItem(const base::FilePath& file_path, - const std::optional<base::Time>& timestamp) + base::Time timestamp) : BirchItem(base::UTF8ToUTF16(file_path.BaseName().value()), ui::ImageModel()), file_path(file_path), @@ -118,13 +118,9 @@ std::string BirchFileItem::ToString() const { std::stringstream ss; ss << "File item : {ranking: " << ranking - << ", title: " << base::UTF16ToUTF8(title) << ", file_path:" << file_path; - if (timestamp.has_value()) { - ss << ", timestamp: " - << base::UTF16ToUTF8( - base::TimeFormatShortDateAndTime(timestamp.value())); - } - ss << "}"; + << ", title: " << base::UTF16ToUTF8(title) << ", file_path:" << file_path + << ", timestamp: " + << base::UTF16ToUTF8(base::TimeFormatShortDateAndTime(timestamp)) << "}"; return ss.str(); }
diff --git a/ash/birch/birch_item.h b/ash/birch/birch_item.h index 5d283990..398d7aae 100644 --- a/ash/birch/birch_item.h +++ b/ash/birch/birch_item.h
@@ -5,7 +5,6 @@ #ifndef ASH_BIRCH_BIRCH_ITEM_H_ #define ASH_BIRCH_BIRCH_ITEM_H_ -#include <optional> #include <string> #include "ash/ash_export.h" @@ -82,8 +81,7 @@ // A birch item which contains file path and time information. struct ASH_EXPORT BirchFileItem : public BirchItem { - BirchFileItem(const base::FilePath& file_path, - const std::optional<base::Time>& timestamp); + BirchFileItem(const base::FilePath& file_path, base::Time timestamp); BirchFileItem(BirchFileItem&&); BirchFileItem(const BirchFileItem&); BirchFileItem& operator=(const BirchFileItem&); @@ -91,7 +89,7 @@ ~BirchFileItem() override; base::FilePath file_path; - std::optional<base::Time> timestamp; + base::Time timestamp; static constexpr char kItemType[] = "FileItem";
diff --git a/ash/birch/birch_model.cc b/ash/birch/birch_model.cc index 61f3a80..e5d431e7 100644 --- a/ash/birch/birch_model.cc +++ b/ash/birch/birch_model.cc
@@ -115,12 +115,16 @@ BirchRanker ranker(base::Time::Now()); ranker.RankCalendarItems(&calendar_items_); + ranker.RankAttachmentItems(&attachment_items_); ranker.RankWeatherItems(&weather_items_); // TODO(b/305094126): Rank all data types. for (auto& event : calendar_items_) { all_items.push_back(std::make_unique<BirchCalendarItem>(event)); } + for (auto& event : attachment_items_) { + all_items.push_back(std::make_unique<BirchAttachmentItem>(event)); + } for (auto& tab : recent_tab_items_) { all_items.push_back(std::make_unique<BirchTabItem>(tab)); }
diff --git a/ash/birch/birch_model_unittest.cc b/ash/birch/birch_model_unittest.cc index 42f0772..cff16725 100644 --- a/ash/birch/birch_model_unittest.cc +++ b/ash/birch/birch_model_unittest.cc
@@ -121,7 +121,7 @@ EXPECT_THAT(consumer.items_ready_responses(), testing::IsEmpty()); std::vector<BirchFileItem> file_item_list; - file_item_list.emplace_back(base::FilePath("test path 1"), std::nullopt); + file_item_list.emplace_back(base::FilePath("test path 1"), base::Time()); model->SetFileSuggestItems(std::move(file_item_list)); model->SetWeatherItems({}); model->SetCalendarItems({}); @@ -132,8 +132,8 @@ // Setting the file suggest items should not trigger items ready again, since // no data fetch was requested. - file_item_list.emplace_back(base::FilePath("test path 1"), std::nullopt); - file_item_list.emplace_back(base::FilePath("test path 2"), std::nullopt); + file_item_list.emplace_back(base::FilePath("test path 1"), base::Time()); + file_item_list.emplace_back(base::FilePath("test path 2"), base::Time()); model->SetFileSuggestItems(std::move(file_item_list)); EXPECT_THAT(consumer.items_ready_responses(), testing::ElementsAre("0")); @@ -170,7 +170,7 @@ task_environment()->FastForwardBy(base::Milliseconds(1000)); std::vector<BirchFileItem> file_item_list; - file_item_list.emplace_back(base::FilePath("test path 1"), std::nullopt); + file_item_list.emplace_back(base::FilePath("test path 1"), base::Time()); model->SetFileSuggestItems(std::move(file_item_list)); model->SetRecentTabItems(std::vector<BirchTabItem>()); std::vector<BirchWeatherItem> weather_items; @@ -217,7 +217,7 @@ EXPECT_TRUE(model); std::vector<BirchFileItem> file_item_list; - file_item_list.emplace_back(base::FilePath("test path 1"), std::nullopt); + file_item_list.emplace_back(base::FilePath("test path 1"), base::Time()); // Passing time and setting data before requesting a birch data fetch will // not notify consumer. @@ -277,7 +277,7 @@ EXPECT_THAT(consumer.items_ready_responses(), testing::IsEmpty()); std::vector<BirchFileItem> file_item_list; - file_item_list.emplace_back(base::FilePath("test path 1"), std::nullopt); + file_item_list.emplace_back(base::FilePath("test path 1"), base::Time()); model->SetFileSuggestItems(std::move(file_item_list)); model->SetWeatherItems({}); model->SetCalendarItems({}); @@ -288,8 +288,8 @@ // Setting the file suggest items should not trigger items ready again, since // no data fetch was requested. - file_item_list.emplace_back(base::FilePath("test path 1"), std::nullopt); - file_item_list.emplace_back(base::FilePath("test path 2"), std::nullopt); + file_item_list.emplace_back(base::FilePath("test path 1"), base::Time()); + file_item_list.emplace_back(base::FilePath("test path 2"), base::Time()); model->SetFileSuggestItems(std::move(file_item_list)); EXPECT_THAT(consumer.items_ready_responses(), testing::ElementsAre("0")); @@ -362,7 +362,7 @@ EXPECT_FALSE(model->IsDataFresh()); std::vector<BirchFileItem> file_item_list; - file_item_list.emplace_back(base::FilePath("test path 1"), std::nullopt); + file_item_list.emplace_back(base::FilePath("test path 1"), base::Time()); model->SetFileSuggestItems(std::move(file_item_list)); std::vector<BirchWeatherItem> weather_item_list; weather_item_list.emplace_back(u"cloudy", u"16 c", ui::ImageModel()); @@ -380,7 +380,7 @@ EXPECT_TRUE(model->IsDataFresh()); EXPECT_THAT(consumer.items_ready_responses(), testing::ElementsAre("0", "1")); - EXPECT_EQ(model->GetAllItems().size(), 4u); + EXPECT_EQ(model->GetAllItems().size(), 5u); model->RequestBirchDataFetch(base::BindOnce(&TestModelConsumer::OnItemsReady, base::Unretained(&consumer), @@ -402,4 +402,36 @@ EXPECT_TRUE(model->IsDataFresh()); } +TEST_F(BirchModelTest, GetAllItems) { + BirchModel* model = Shell::Get()->birch_model(); + + // Insert one item of each type. + std::vector<BirchCalendarItem> calendar_item_list; + calendar_item_list.emplace_back(u"Event 1"); + model->SetCalendarItems(std::move(calendar_item_list)); + std::vector<BirchAttachmentItem> attachment_item_list; + attachment_item_list.emplace_back(u"Attachment 1"); + model->SetAttachmentItems(std::move(attachment_item_list)); + std::vector<BirchTabItem> tab_item_list; + tab_item_list.emplace_back(u"tab", GURL("foo.bar"), base::Time(), + GURL("favicon"), "session"); + model->SetRecentTabItems(std::move(tab_item_list)); + std::vector<BirchFileItem> file_item_list; + file_item_list.emplace_back(base::FilePath("test path 1"), base::Time()); + model->SetFileSuggestItems(std::move(file_item_list)); + std::vector<BirchWeatherItem> weather_item_list; + weather_item_list.emplace_back(u"cloudy", u"16 c", ui::ImageModel()); + model->SetWeatherItems(std::move(weather_item_list)); + + // Verify that GetAllItems() returns the correct number of items and the + // code didn't skip a type. + std::vector<std::unique_ptr<BirchItem>> all_items = model->GetAllItems(); + ASSERT_EQ(all_items.size(), 5u); + EXPECT_STREQ(all_items[0]->GetItemType(), BirchCalendarItem::kItemType); + EXPECT_STREQ(all_items[1]->GetItemType(), BirchAttachmentItem::kItemType); + EXPECT_STREQ(all_items[2]->GetItemType(), BirchTabItem::kItemType); + EXPECT_STREQ(all_items[3]->GetItemType(), BirchFileItem::kItemType); + EXPECT_STREQ(all_items[4]->GetItemType(), BirchWeatherItem::kItemType); +} + } // namespace ash
diff --git a/ash/birch/birch_ranker.cc b/ash/birch/birch_ranker.cc index 7eafc4b..2851630 100644 --- a/ash/birch/birch_ranker.cc +++ b/ash/birch/birch_ranker.cc
@@ -65,7 +65,32 @@ } void BirchRanker::RankAttachmentItems(std::vector<BirchAttachmentItem>* items) { - // TODO(b/305094126): Rank all data types. + CHECK(items); + + // Sort the attachments by their event start time. + std::sort(items->begin(), items->end(), + [](const BirchAttachmentItem& a, const BirchAttachmentItem& b) { + return a.start_time < b.start_time; + }); + + const bool is_morning = IsMorning(); + + for (BirchAttachmentItem& item : *items) { + // Attachments for ongoing events have high priority in the morning and + // medium priority the rest of the day. + const bool is_ongoing = item.start_time <= now_ && now_ < item.end_time; + if (is_ongoing) { + item.ranking = is_morning ? 7.f : 10.f; + continue; + } + + // Attachments for events starting in the next 30 minutes have medium + // priority. + if (now_ <= item.start_time && item.start_time < now_ + base::Minutes(30)) { + item.ranking = 13.f; + continue; + } + } } void BirchRanker::RankFileSuggestItems(std::vector<BirchFileItem>* items) {
diff --git a/ash/birch/birch_ranker_unittest.cc b/ash/birch/birch_ranker_unittest.cc index 4bce9235..d153ac3 100644 --- a/ash/birch/birch_ranker_unittest.cc +++ b/ash/birch/birch_ranker_unittest.cc
@@ -148,6 +148,88 @@ EXPECT_FLOAT_EQ(items[0].ranking, 9.f); } +TEST(BirchRankerTest, RankAttachmentItems_Morning) { + base::test::ScopedRestoreDefaultTimezone timezone("Etc/GMT"); + + // Simulate 9 AM in the morning. + base::Time now = TimeFromString("22 Feb 2024 9:00 UTC"); + BirchRanker ranker(now); + ASSERT_TRUE(ranker.IsMorning()); + + // Create an attachment for an ongoing event (8 AM to 10 AM). + BirchAttachmentItem item0(u"Ongoing"); + item0.start_time = TimeFromString("22 Feb 2024 08:00 UTC"); + item0.end_time = TimeFromString("22 Feb 2024 10:00 UTC"); + + // Create an attachment for an upcoming event (9:15 to 9:45). + BirchAttachmentItem item1(u"Upcoming"); + item1.start_time = TimeFromString("22 Feb 2024 9:15 UTC"); + item1.end_time = TimeFromString("22 Feb 2024 9:45 UTC"); + + // Create an attachment for another event later in the day (1 PM). + BirchAttachmentItem item2(u"Later"); + item2.start_time = TimeFromString("22 Feb 2024 13:00 UTC"); + item2.end_time = TimeFromString("22 Feb 2024 13:30 UTC"); + + // Put the items in the vector in reverse order to validate that they are + // still handled in the correct order (by time) inside the ranker. + std::vector<BirchAttachmentItem> items = {item2, item1, item0}; + + ranker.RankAttachmentItems(&items); + + ASSERT_EQ(3u, items.size()); + + // The ongoing event's item has a high priority. + EXPECT_FLOAT_EQ(items[0].ranking, 7.f); + + // The upcoming event's item has a medium priority. + EXPECT_FLOAT_EQ(items[1].ranking, 13.f); + + // The later event's item wasn't ranked, so has the default value. + EXPECT_FLOAT_EQ(items[2].ranking, std::numeric_limits<float>::max()); +} + +TEST(BirchRankerTest, RankAttachmentItems_Evening) { + base::test::ScopedRestoreDefaultTimezone timezone("Etc/GMT"); + + // Simulate 6 PM in the evening. + base::Time now = TimeFromString("22 Feb 2024 18:00 UTC"); + BirchRanker ranker(now); + ASSERT_TRUE(ranker.IsEvening()); + + // Create an attachment for an ongoing event (5 PM to 7 PM). + BirchAttachmentItem item0(u"Ongoing"); + item0.start_time = TimeFromString("22 Feb 2024 17:00 UTC"); + item0.end_time = TimeFromString("22 Feb 2024 19:00 UTC"); + + // Create an attachment for an upcoming event (6:15 PM). + BirchAttachmentItem item1(u"Upcoming"); + item1.start_time = TimeFromString("22 Feb 2024 18:15 UTC"); + item1.end_time = TimeFromString("22 Feb 2024 18:45 UTC"); + + // Create an attachment for another event later in the evening (8 PM). + BirchAttachmentItem item2(u"Later"); + item2.start_time = TimeFromString("22 Feb 2024 20:00 UTC"); + item2.end_time = TimeFromString("22 Feb 2024 20:30 UTC"); + + // Put the items in the vector in reverse order to validate that they are + // still handled in the correct order (by time) inside the ranker. + std::vector<BirchAttachmentItem> items = {item2, item1, item0}; + + ranker.RankAttachmentItems(&items); + + ASSERT_EQ(3u, items.size()); + + // The ongoing event's item has a medium priority. + EXPECT_FLOAT_EQ(items[0].ranking, 10.f); + + // The upcoming event's item has a lower priority. + EXPECT_FLOAT_EQ(items[1].ranking, 13.f); + + // The later event's item wasn't ranked, so has the default value. + EXPECT_FLOAT_EQ(items[2].ranking, std::numeric_limits<float>::max()); +} + TEST(BirchRankerTest, RankWeatherItems_Morning) { base::test::ScopedRestoreDefaultTimezone timezone("Etc/GMT");
diff --git a/ash/clipboard/clipboard_history_menu_model_adapter.cc b/ash/clipboard/clipboard_history_menu_model_adapter.cc index e75f071..dbed6e85 100644 --- a/ash/clipboard/clipboard_history_menu_model_adapter.cc +++ b/ash/clipboard/clipboard_history_menu_model_adapter.cc
@@ -332,7 +332,7 @@ for (auto& item_view_command_id_pair : menu_model_adapter_->item_views_by_command_id_) { views::View* item_view = item_view_command_id_pair.second; - item_view->GetViewAccessibility().OverrideIsIgnored(ignore); + item_view->GetViewAccessibility().SetIsIgnored(ignore); } } @@ -530,7 +530,7 @@ l10n_util::GetStringUTF16(IDS_CLIPBOARD_HISTORY_ITEM_DELETION)); // Enable a11y announcement for the view to be deleted. - view_accessibility.OverrideIsIgnored(false); + view_accessibility.SetIsIgnored(false); // Disabling `item_view_to_delete` is more like implementation details. // So do not expose it to users. @@ -721,7 +721,7 @@ // Ignore `container` in accessibility events handling. Let `item_view` // handle. - container->GetViewAccessibility().OverrideIsIgnored(true); + container->GetViewAccessibility().SetIsIgnored(true); // Margins are managed by `ClipboardHistoryItemView`. container->set_vertical_margin(0);
diff --git a/ash/clipboard/views/clipboard_history_main_button.cc b/ash/clipboard/views/clipboard_history_main_button.cc index 0cb82099..a4ca7e9 100644 --- a/ash/clipboard/views/clipboard_history_main_button.cc +++ b/ash/clipboard/views/clipboard_history_main_button.cc
@@ -31,7 +31,7 @@ views::InkDrop::Get(this)->SetMode(views::InkDropHost::InkDropMode::ON); // Let the parent handle accessibility features. - GetViewAccessibility().OverrideIsIgnored(/*value=*/true); + GetViewAccessibility().SetIsIgnored(true); // TODO(crbug.com/1205227): Revisit if this comment makes sense still. It was // attached to CreateInkDrop() but sounds more about talking about a null
diff --git a/ash/components/arc/arc_features.cc b/ash/components/arc/arc_features.cc index df9a8e8..e69186f 100644 --- a/ash/components/arc/arc_features.cc +++ b/ash/components/arc/arc_features.cc
@@ -352,12 +352,12 @@ // RAM - 1024 MiB. BASE_FEATURE(kVmMemorySize, "ArcVmMemorySize", - base::FEATURE_DISABLED_BY_DEFAULT); + base::FEATURE_ENABLED_BY_DEFAULT); // Controls the amount to "shift" system RAM when sizing ARCVM. The default // value of 0 means that ARCVM's memory will be thr same as the system. const base::FeatureParam<int> kVmMemorySizeShiftMiB{&kVmMemorySize, "shift_mib", - 0}; + -500}; // Controls the maximum amount of memory to give ARCVM. The default value of // INT32_MAX means that ARCVM's memory is not capped.
diff --git a/ash/components/arc/arc_util.cc b/ash/components/arc/arc_util.cc index a5681f88..bd26cf3e 100644 --- a/ash/components/arc/arc_util.cc +++ b/ash/components/arc/arc_util.cc
@@ -218,18 +218,6 @@ ash::switches::kIgnoreArcVmDevConf); } -// TODO(b/315507371): Remove after deprecated switches are not in use -bool IsUreadaheadDisabled() { - return base::CommandLine::ForCurrentProcess()->HasSwitch( - ash::switches::kArcDisableUreadahead); -} - -// TODO(b/315507371): Remove after deprecated switches are not in use -bool IsHostUreadaheadGeneration() { - return base::CommandLine::ForCurrentProcess()->HasSwitch( - ash::switches::kArcHostUreadaheadGeneration); -} - bool IsArcUseDevCaches() { return base::CommandLine::ForCurrentProcess()->HasSwitch( ash::switches::kArcUseDevCaches);
diff --git a/ash/components/arc/arc_util.h b/ash/components/arc/arc_util.h index 2efbfe3..eae6c36 100644 --- a/ash/components/arc/arc_util.h +++ b/ash/components/arc/arc_util.h
@@ -50,13 +50,14 @@ JOB_STOP_AND_START, }; -// Enum for configuring ureadahead mode of operation during ARC boot process. +// Enum for configuring ureadahead mode of operation during ARC boot process for +// both host and guest. enum class ArcUreadaheadMode { - // ARCVM ureadahead is in readahead mode for normal user boot flow. + // ARC ureadahead is in readahead mode for normal user boot flow. READAHEAD = 0, - // ARCVM ureadahead is turned on for generate mode in data collector flow. + // ARC ureadahead is turned on for generate mode in data collector flow. GENERATE, - // ARCVM ureadahead is turned off for disabled mode. + // ARC ureadahead is turned off for disabled mode. DISABLED, }; @@ -150,14 +151,6 @@ // vm_tools/init/arcvm_dev.conf file are ignored during ARCVM start. bool IsArcVmDevConfIgnored(); -// Returns true if ureadahead is disabled completely, including host and guest -// parts. See also |GetArcVmUreadaheadMode|. -bool IsUreadaheadDisabled(); - -// Returns true in case host ureadahead generation is active in the current -// session. -bool IsHostUreadaheadGeneration(); - // Returns true if ARC is using dev caches for arccachesetup service. bool IsArcUseDevCaches();
diff --git a/ash/components/arc/arc_util_unittest.cc b/ash/components/arc/arc_util_unittest.cc index 3ec9c0a8..827d8e5a 100644 --- a/ash/components/arc/arc_util_unittest.cc +++ b/ash/components/arc/arc_util_unittest.cc
@@ -310,28 +310,6 @@ EXPECT_EQ(ArcUreadaheadMode::DISABLED, GetArcUreadaheadMode(mode)); } -TEST_F(ArcUtilTest, UreadaheadDefault) { - EXPECT_FALSE(IsUreadaheadDisabled()); -} - -TEST_F(ArcUtilTest, UreadaheadDisabled) { - auto* command_line = base::CommandLine::ForCurrentProcess(); - command_line->InitFromArgv({"", "--arc-disable-ureadahead"}); - EXPECT_TRUE(IsUreadaheadDisabled()); -} - -TEST_F(ArcUtilTest, HostUreadaheadGenerationDefault) { - EXPECT_FALSE(IsHostUreadaheadGeneration()); - EXPECT_FALSE(IsUreadaheadDisabled()); -} - -TEST_F(ArcUtilTest, HostUreadaheadGenerationSet) { - auto* command_line = base::CommandLine::ForCurrentProcess(); - command_line->InitFromArgv({"", "--arc-host-ureadahead-generation"}); - EXPECT_TRUE(IsHostUreadaheadGeneration()); - EXPECT_FALSE(IsUreadaheadDisabled()); -} - TEST_F(ArcUtilTest, UseDevCachesDefault) { EXPECT_FALSE(IsArcUseDevCaches()); }
diff --git a/ash/components/arc/compat_mode/arc_resize_lock_manager.h b/ash/components/arc/compat_mode/arc_resize_lock_manager.h index b3b11f8..5578d22 100644 --- a/ash/components/arc/compat_mode/arc_resize_lock_manager.h +++ b/ash/components/arc/compat_mode/arc_resize_lock_manager.h
@@ -87,7 +87,8 @@ std::unique_ptr<TouchModeMouseRewriter> touch_mode_mouse_rewriter_; - base::flat_set<aura::Window*> resize_lock_enabled_windows_; + base::flat_set<raw_ptr<aura::Window, CtnExperimental>> + resize_lock_enabled_windows_; base::ScopedObservation<aura::Env, aura::EnvObserver> env_observation{this};
diff --git a/ash/components/arc/compat_mode/arc_resize_lock_manager_unittest.cc b/ash/components/arc/compat_mode/arc_resize_lock_manager_unittest.cc index 8cb5760b..e8d65bd 100644 --- a/ash/components/arc/compat_mode/arc_resize_lock_manager_unittest.cc +++ b/ash/components/arc/compat_mode/arc_resize_lock_manager_unittest.cc
@@ -71,7 +71,8 @@ } private: - base::flat_set<const aura::Window*> update_compat_mode_button_called; + base::flat_set<raw_ptr<const aura::Window, CtnExperimental>> + update_compat_mode_button_called; }; class TestArcResizeLockManager : public ArcResizeLockManager {
diff --git a/ash/components/arc/compat_mode/overlay_dialog.cc b/ash/components/arc/compat_mode/overlay_dialog.cc index ece92e9..e5ca555 100644 --- a/ash/components/arc/compat_mode/overlay_dialog.cc +++ b/ash/components/arc/compat_mode/overlay_dialog.cc
@@ -62,7 +62,7 @@ return; auto& view_ax = GetWidget()->GetRootView()->GetViewAccessibility(); - view_ax.OverrideIsIgnored(true); + view_ax.SetIsIgnored(true); } void OverlayDialog::OnThemeChanged() {
diff --git a/ash/components/arc/compat_mode/overlay_dialog_unittest.cc b/ash/components/arc/compat_mode/overlay_dialog_unittest.cc index b781e28..6f9109bc 100644 --- a/ash/components/arc/compat_mode/overlay_dialog_unittest.cc +++ b/ash/components/arc/compat_mode/overlay_dialog_unittest.cc
@@ -39,7 +39,7 @@ std::move(dialog_view))); const auto& view_ax = widget->GetRootView()->GetViewAccessibility(); - EXPECT_EQ(!has_dialog_view, view_ax.IsIgnored()); + EXPECT_EQ(!has_dialog_view, view_ax.GetIsIgnored()); widget->Show(); EXPECT_FALSE(called);
diff --git a/ash/components/arc/mojom/intent_helper.mojom b/ash/components/arc/mojom/intent_helper.mojom index e7dd2e3..708f78a 100644 --- a/ash/components/arc/mojom/intent_helper.mojom +++ b/ash/components/arc/mojom/intent_helper.mojom
@@ -263,6 +263,8 @@ kGeoLocation, // Deprecated, replaced with specific scenarios. kGeoLocationAtBoot, kGeoLocationUserTriggered, + // We don't need atBoot for accuracy as the value is in sync with GeoLocation + kGeoLocationAccuracyUserTriggered, }; // The type of shadow used for Chrome captions.
diff --git a/ash/components/arc/power/arc_power_bridge.cc b/ash/components/arc/power/arc_power_bridge.cc index 3f7d56bf..e33a620 100644 --- a/ash/components/arc/power/arc_power_bridge.cc +++ b/ash/components/arc/power/arc_power_bridge.cc
@@ -302,6 +302,12 @@ void ArcPowerBridge::PowerChanged( const power_manager::PowerSupplyProperties& proto) { + // ARCVM doesn't use this message, since it gets the corresponding + // information from crosvm's goldfish battery device. + if (arc::IsArcVmEnabled()) { + return; + } + mojom::PowerInstance* power_instance = ARC_GET_INSTANCE_FOR_METHOD( arc_bridge_service_->power(), PowerSupplyInfoChanged); if (!power_instance)
diff --git a/ash/components/arc/session/arc_client_adapter.cc b/ash/components/arc/session/arc_client_adapter.cc index 0c4491d..4066839 100644 --- a/ash/components/arc/session/arc_client_adapter.cc +++ b/ash/components/arc/session/arc_client_adapter.cc
@@ -92,8 +92,6 @@ request.set_disable_media_store_maintenance( params.disable_media_store_maintenance); request.set_disable_download_provider(params.disable_download_provider); - request.set_disable_ureadahead(params.disable_ureadahead); - request.set_host_ureadahead_generation(params.host_ureadahead_generation); request.set_host_ureadahead_mode( ToArcMiniInstanceRequestHostUreadaheadMode(params.host_ureadahead_mode)); request.set_use_dev_caches(params.use_dev_caches);
diff --git a/ash/components/arc/session/arc_container_client_adapter_unittest.cc b/ash/components/arc/session/arc_container_client_adapter_unittest.cc index 0e67cbf..32a6cab 100644 --- a/ash/components/arc/session/arc_container_client_adapter_unittest.cc +++ b/ash/components/arc/session/arc_container_client_adapter_unittest.cc
@@ -155,49 +155,6 @@ EXPECT_TRUE(request.disable_download_provider()); } -TEST_F(ArcContainerClientAdapterTest, StartArc_UreadaheadByDefault) { - StartParams start_params; - client_adapter()->StartMiniArc(std::move(start_params), - base::BindOnce(&OnMiniInstanceStarted)); - const auto& request = ash::FakeSessionManagerClient::Get() - ->last_start_arc_mini_container_request(); - EXPECT_TRUE(request.has_disable_ureadahead()); - EXPECT_FALSE(request.disable_ureadahead()); -} - -TEST_F(ArcContainerClientAdapterTest, StartArc_DisableUreadahead) { - StartParams start_params; - start_params.disable_ureadahead = true; - client_adapter()->StartMiniArc(std::move(start_params), - base::BindOnce(&OnMiniInstanceStarted)); - const auto& request = ash::FakeSessionManagerClient::Get() - ->last_start_arc_mini_container_request(); - EXPECT_TRUE(request.has_disable_ureadahead()); - EXPECT_TRUE(request.disable_ureadahead()); -} - -TEST_F(ArcContainerClientAdapterTest, - StartArc_NoHostUreadaheadGenerationByDefault) { - StartParams start_params; - client_adapter()->StartMiniArc(std::move(start_params), - base::BindOnce(&OnMiniInstanceStarted)); - const auto& request = ash::FakeSessionManagerClient::Get() - ->last_start_arc_mini_container_request(); - EXPECT_TRUE(request.has_host_ureadahead_generation()); - EXPECT_FALSE(request.host_ureadahead_generation()); -} - -TEST_F(ArcContainerClientAdapterTest, StartArc_HostUreadaheadGenerationSet) { - StartParams start_params; - start_params.host_ureadahead_generation = true; - client_adapter()->StartMiniArc(std::move(start_params), - base::BindOnce(&OnMiniInstanceStarted)); - const auto& request = ash::FakeSessionManagerClient::Get() - ->last_start_arc_mini_container_request(); - EXPECT_TRUE(request.has_host_ureadahead_generation()); - EXPECT_TRUE(request.host_ureadahead_generation()); -} - TEST_F(ArcContainerClientAdapterTest, StartArc_DoNotUseDevCachesByDefault) { StartParams start_params; client_adapter()->StartMiniArc(std::move(start_params),
diff --git a/ash/components/arc/session/arc_session_impl.cc b/ash/components/arc/session/arc_session_impl.cc index 0caceafa..31ab486 100644 --- a/ash/components/arc/session/arc_session_impl.cc +++ b/ash/components/arc/session/arc_session_impl.cc
@@ -156,16 +156,6 @@ ash::switches::kArcDisableDownloadProvider); } -void ApplyDisableUreadahed(StartParams* params) { - // Host ureadahead generation implies disabling ureadahead. - params->disable_ureadahead = - IsUreadaheadDisabled() || IsHostUreadaheadGeneration(); -} - -void ApplyHostUreadahedGeneration(StartParams* params) { - params->host_ureadahead_generation = IsHostUreadaheadGeneration(); -} - void ApplyUseDevCaches(StartParams* params) { params->use_dev_caches = IsArcUseDevCaches(); } @@ -515,8 +505,6 @@ ApplyDalvikMemoryProfile(system_memory_info_callback_, ¶ms); ApplyDisableDownloadProvider(¶ms); - ApplyDisableUreadahed(¶ms); - ApplyHostUreadahedGeneration(¶ms); ApplyUseDevCaches(¶ms); ApplyHostUreadaheadMode(¶ms);
diff --git a/ash/components/arc/session/arc_session_impl_unittest.cc b/ash/components/arc/session/arc_session_impl_unittest.cc index a9885ce..1850cc8 100644 --- a/ash/components/arc/session/arc_session_impl_unittest.cc +++ b/ash/components/arc/session/arc_session_impl_unittest.cc
@@ -886,53 +886,6 @@ .is_managed_adb_sideloading_allowed); } -// Test that validates disabling ureadahead is not enforced by default. -TEST_F(ArcSessionImplTest, UreadaheadByDefault) { - auto arc_session = CreateArcSession(); - arc_session->StartMiniInstance(); - base::RunLoop().RunUntilIdle(); - EXPECT_FALSE( - GetClient(arc_session.get())->last_start_params().disable_ureadahead); -} - -// Test that validates disabling ureadahead is enforced by switch. -TEST_F(ArcSessionImplTest, DisableUreadahead) { - base::CommandLine* const command_line = - base::CommandLine::ForCurrentProcess(); - command_line->AppendSwitch(ash::switches::kArcDisableUreadahead); - auto arc_session = CreateArcSession(); - arc_session->StartMiniInstance(); - base::RunLoop().RunUntilIdle(); - EXPECT_TRUE( - GetClient(arc_session.get())->last_start_params().disable_ureadahead); -} - -// Test that validates host ureadahead generation flag is not set by default. -TEST_F(ArcSessionImplTest, NoHostUreadaheadGenerationDefault) { - auto arc_session = CreateArcSession(); - arc_session->StartMiniInstance(); - base::RunLoop().RunUntilIdle(); - EXPECT_FALSE(GetClient(arc_session.get()) - ->last_start_params() - .host_ureadahead_generation); -} - -// Test that validates host ureadahead generation flag is set. -TEST_F(ArcSessionImplTest, HostUreadaheadGenerationSet) { - base::CommandLine* const command_line = - base::CommandLine::ForCurrentProcess(); - command_line->AppendSwitch(ash::switches::kArcHostUreadaheadGeneration); - auto arc_session = CreateArcSession(); - arc_session->StartMiniInstance(); - base::RunLoop().RunUntilIdle(); - EXPECT_TRUE(GetClient(arc_session.get()) - ->last_start_params() - .host_ureadahead_generation); - // Host ureadahead generation implies disabling ureadahead. - EXPECT_TRUE( - GetClient(arc_session.get())->last_start_params().disable_ureadahead); -} - // Test that validates arc signed in flag is not set by default. TEST_F(ArcSessionImplTest, NotArcSignedInByDefault) { auto arc_session = CreateArcSession();
diff --git a/ash/components/arc/session/arc_start_params.h b/ash/components/arc/session/arc_start_params.h index 20e420e..4cf42ee 100644 --- a/ash/components/arc/session/arc_start_params.h +++ b/ash/components/arc/session/arc_start_params.h
@@ -75,15 +75,6 @@ // flakiness in tests. bool disable_download_provider = false; - // Flag to disable ureadahead completely, including host and guest parts. - // TODO(b/264585671): Refactor this and |host_ureadahead_generation| to - // mode enum. - bool disable_ureadahead = false; - - // Flag to indicate host ureadahead generation. - // TODO(b/264585671): Refactor this and |disable_ureadahead| to mode enum. - bool host_ureadahead_generation = false; - // Flag to indicate whether to use dev caches. bool use_dev_caches = false;
diff --git a/ash/components/arc/session/arc_vm_client_adapter.cc b/ash/components/arc/session/arc_vm_client_adapter.cc index 89fbbbba..64110ee 100644 --- a/ash/components/arc/session/arc_vm_client_adapter.cc +++ b/ash/components/arc/session/arc_vm_client_adapter.cc
@@ -837,9 +837,6 @@ } std::vector<std::string> environment; - if (start_params_.disable_ureadahead) { - environment.emplace_back("DISABLE_UREADAHEAD=1"); - } std::deque<JobDesc> jobs{ // Note: the first Upstart job is a task, and the callback for the start // request won't be called until the task finishes. When the callback is
diff --git a/ash/components/arc/session/arc_vm_client_adapter_unittest.cc b/ash/components/arc/session/arc_vm_client_adapter_unittest.cc index 9aa265d..2f257c3 100644 --- a/ash/components/arc/session/arc_vm_client_adapter_unittest.cc +++ b/ash/components/arc/session/arc_vm_client_adapter_unittest.cc
@@ -1023,25 +1023,6 @@ EXPECT_TRUE(ops[1].env.empty()); } -// Tests that StartMiniArc()'s JOB_STOP_AND_START for -// |kArcVmPreLoginServicesJobName| has DISABLE_UREADAHEAD variable. -TEST_F(ArcVmClientAdapterTest, StartMiniArc_DisableUreadahead) { - StartParams start_params(GetPopulatedStartParams()); - start_params.disable_ureadahead = true; - ash::FakeUpstartClient::Get()->StartRecordingUpstartOperations(); - StartMiniArcWithParams(true, std::move(start_params)); - - const auto& ops = - ash::FakeUpstartClient::Get()->GetRecordedUpstartOperationsForJob( - kArcVmPreLoginServicesJobName); - ASSERT_EQ(ops.size(), 2u); - EXPECT_EQ(ops[0].type, ash::FakeUpstartClient::UpstartOperationType::STOP); - EXPECT_EQ(ops[1].type, ash::FakeUpstartClient::UpstartOperationType::START); - const auto it_ureadahead = - base::ranges::find(ops[1].env, "DISABLE_UREADAHEAD=1"); - EXPECT_NE(ops[1].env.end(), it_ureadahead); -} - // Tests that StartMiniArc() handles arcvm-post-vm-start-services stop failures // properly. TEST_F(ArcVmClientAdapterTest, @@ -2189,7 +2170,8 @@ StartParams start_params(GetPopulatedStartParams()); StartMiniArcWithParams(true, std::move(start_params)); const auto& request = GetTestConciergeClient()->start_arc_vm_request(); - EXPECT_EQ(request.memory_mib(), total_mib / 4); + // shift_mib is -500 by default + EXPECT_EQ(request.memory_mib(), total_mib / 4 - 500); } // Test that ARCMVM size is set by both ram_percentage and shift_mib. @@ -2501,8 +2483,9 @@ StartMiniArcWithParams(true, std::move(start_params)); const auto& request = GetTestConciergeClient()->start_arc_vm_request(); - // 5GB system should result in 4GB VM size => 2GB ZRAM. - EXPECT_EQ(2048u, request.guest_zram_mib()); + // As shift_mib for memory size is -500 by default, + // 5GB system should result in 4.5GB VM size => 2.25GB ZRAM. + EXPECT_EQ(2310u, request.guest_zram_mib()); } TEST_F(ArcVmClientAdapterTest, ArcGuestZramSizeByPercentage_4GbSystem) { @@ -2525,8 +2508,9 @@ StartMiniArcWithParams(true, std::move(start_params)); const auto& request = GetTestConciergeClient()->start_arc_vm_request(); - // 4GB system should result in 3GB VM size => 1.5GB ZRAM. - EXPECT_EQ(1536u, request.guest_zram_mib()); + // As shift_mib for memory size is -500 by default, + // 4GB system should result in 3.5GB VM size => 1.75GB ZRAM. + EXPECT_EQ(1798u, request.guest_zram_mib()); } TEST_F(ArcVmClientAdapterTest, ArcGuestZramSizeByPercentage_CustomMem) {
diff --git a/ash/constants/ash_features.cc b/ash/constants/ash_features.cc index 9322bc9..7b4fd69 100644 --- a/ash/constants/ash_features.cc +++ b/ash/constants/ash_features.cc
@@ -1171,6 +1171,11 @@ "FilesLocalImageSearch", base::FEATURE_DISABLED_BY_DEFAULT); +// Enables materialized views in Files App. +BASE_FEATURE(kFilesMaterializedViews, + "FilesMaterializedViews", + base::FEATURE_DISABLED_BY_DEFAULT); + // Enables partitioning of removable disks in file manager. BASE_FEATURE(kFilesSinglePartitionFormat, "FilesSinglePartitionFormat", @@ -1202,9 +1207,7 @@ base::FEATURE_DISABLED_BY_DEFAULT); // Enables chrome.fileSystemProvider file systems in Files app Recents view. -BASE_FEATURE(kFSPsInRecents, - "FSPsInRecents", - base::FEATURE_DISABLED_BY_DEFAULT); +BASE_FEATURE(kFSPsInRecents, "FSPsInRecents", base::FEATURE_ENABLED_BY_DEFAULT); // Maximum delay to wait for restoring Floating Workspace after login. constexpr base::FeatureParam<base::TimeDelta> @@ -2780,6 +2783,13 @@ "UseAuthPanelInPasswordManager", base::FEATURE_DISABLED_BY_DEFAULT); +// This features toggles which implementation is used for authentication UIs on +// ChromeOS settings. When the feature is enabled, +// `AuthPanel` is used as an authentication UI. +BASE_FEATURE(kUseAuthPanelInSettings, + "UseAuthPanelInSettings", + base::FEATURE_DISABLED_BY_DEFAULT); + // Use the staging URL as part of the "Messages" feature under "Connected // Devices" settings. BASE_FEATURE(kUseMessagesStagingUrl, @@ -4517,4 +4527,8 @@ return base::FeatureList::IsEnabled(kUseAuthPanelInPasswordManager); } +bool IsUseAuthPanelInSettingsEnabled() { + return base::FeatureList::IsEnabled(kUseAuthPanelInSettings); +} + } // namespace ash::features
diff --git a/ash/constants/ash_features.h b/ash/constants/ash_features.h index fb94d1e..f629f00 100644 --- a/ash/constants/ash_features.h +++ b/ash/constants/ash_features.h
@@ -354,6 +354,7 @@ COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kFilesAppExperimental); COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kFilesConflictDialog); COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kFilesLocalImageSearch); +COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kFilesMaterializedViews); COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kFilesNewDirectoryTree); COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kFilesSinglePartitionFormat); @@ -844,6 +845,8 @@ BASE_DECLARE_FEATURE(kUseAndroidStagingSmds); COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kUseAuthPanelInPasswordManager); +COMPONENT_EXPORT(ASH_CONSTANTS) +BASE_DECLARE_FEATURE(kUseAuthPanelInSettings); COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kUseLoginShelfWidget); COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kUseMessagesStagingUrl); COMPONENT_EXPORT(ASH_CONSTANTS) @@ -1279,6 +1282,7 @@ COMPONENT_EXPORT(ASH_CONSTANTS) bool IsUnmanagedDeviceDeviceTrustConnectorFeatureEnabled(); COMPONENT_EXPORT(ASH_CONSTANTS) bool IsUseAuthPanelInPasswordManagerEnabled(); +COMPONENT_EXPORT(ASH_CONSTANTS) bool IsUseAuthPanelInSettingsEnabled(); COMPONENT_EXPORT(ASH_CONSTANTS) bool IsUserEducationEnabled(); COMPONENT_EXPORT(ASH_CONSTANTS) bool IsUpstreamTrustedReportsFirmwareEnabled(); COMPONENT_EXPORT(ASH_CONSTANTS) bool IsVideoConferenceEnabled();
diff --git a/ash/constants/ash_switches.cc b/ash/constants/ash_switches.cc index 68b0c549..add63503 100644 --- a/ash/constants/ash_switches.cc +++ b/ash/constants/ash_switches.cc
@@ -123,18 +123,6 @@ // Used in autotest to disable TTS cache which is on by default. const char kArcDisableTtsCache[] = "arc-disable-tts-cache"; -// Flag that disables ureadahead completely, including host and guest parts. -// To enable only guest ureadahead, please use --arcvm-ureadahead-mode=readahead -// in combination with this switch (see |kArcVmUreadaheadMode|). -// TODO(b/264585671): Refactore this and |kArcHostUreadaheadGeneration| to -// mode enum. -const char kArcDisableUreadahead[] = "arc-disable-ureadahead"; - -// Flag that indicates host ureadahead generation session. Note, it is still -// valid even in case of kArcDisableUreadahead is set. -// TODO(b/264585671): Refactor this and |kArcDisableUreadahead| to mode enum. -const char kArcHostUreadaheadGeneration[] = "arc-host-ureadahead-generation"; - // Flag that indicates ARC is using dev caches generated by data collector in // Uprev rather than caches from CrOS build stage for arccachesetup service. const char kArcUseDevCaches[] = "arc-use-dev-caches";
diff --git a/ash/constants/ash_switches.h b/ash/constants/ash_switches.h index 889697e..27e908d 100644 --- a/ash/constants/ash_switches.h +++ b/ash/constants/ash_switches.h
@@ -44,9 +44,6 @@ extern const char kArcDisableMediaStoreMaintenance[]; COMPONENT_EXPORT(ASH_CONSTANTS) extern const char kArcDisablePlayAutoInstall[]; COMPONENT_EXPORT(ASH_CONSTANTS) extern const char kArcDisableTtsCache[]; -COMPONENT_EXPORT(ASH_CONSTANTS) extern const char kArcDisableUreadahead[]; -COMPONENT_EXPORT(ASH_CONSTANTS) -extern const char kArcHostUreadaheadGeneration[]; COMPONENT_EXPORT(ASH_CONSTANTS) extern const char kArcUseDevCaches[]; COMPONENT_EXPORT(ASH_CONSTANTS) extern const char kArcErofs[]; COMPONENT_EXPORT(ASH_CONSTANTS) extern const char kArcForcePostBootDexOpt[];
diff --git a/ash/display/display_configuration_controller.cc b/ash/display/display_configuration_controller.cc index c285f56..6d4ade1 100644 --- a/ash/display/display_configuration_controller.cc +++ b/ash/display/display_configuration_controller.cc
@@ -164,8 +164,13 @@ display::Display::Rotation DisplayConfigurationController::GetTargetRotation( int64_t display_id) { - if (!display_manager_->IsDisplayIdValid(display_id)) + // The display for `display_id` may exist but there may be no root window for + // it, such as in the case of Unified Display. Query for the target rotation + // only if the root window exists. + if (!display_manager_->IsDisplayIdValid(display_id) || + !Shell::GetRootWindowForDisplayId(display_id)) { return display::Display::ROTATE_0; + } ScreenRotationAnimator* animator = GetScreenRotationAnimatorForDisplay(display_id);
diff --git a/ash/display/screen_orientation_controller.cc b/ash/display/screen_orientation_controller.cc index 2813826..8c71d95f 100644 --- a/ash/display/screen_orientation_controller.cc +++ b/ash/display/screen_orientation_controller.cc
@@ -10,6 +10,7 @@ #include "ash/constants/ash_switches.h" #include "ash/shell.h" #include "ash/wm/mru_window_tracker.h" +#include "ash/wm/tablet_mode/tablet_mode_controller.h" #include "ash/wm/window_state.h" #include "ash/wm/window_state_observer.h" #include "ash/wm/window_util.h" @@ -25,6 +26,7 @@ #include "ui/display/screen.h" #include "ui/display/util/display_util.h" #include "ui/gfx/geometry/size.h" +#include "ui/gfx/geometry/vector3d_f.h" #include "ui/wm/public/activation_client.h" namespace ash {
diff --git a/ash/events/event_rewriter_controller_impl.cc b/ash/events/event_rewriter_controller_impl.cc index c33b7cd2..441ae54 100644 --- a/ash/events/event_rewriter_controller_impl.cc +++ b/ash/events/event_rewriter_controller_impl.cc
@@ -127,8 +127,6 @@ AddEventRewriter(std::move(peripheral_customization_event_rewriter)); } AddEventRewriter(std::move(prerewritten_event_forwarder)); - AddEventRewriter(std::move(accessibility_event_rewriter)); - AddEventRewriter(std::move(keyboard_driven_event_rewriter)); AddEventRewriter(std::move(keyboard_device_id_event_rewriter)); if (features::IsKeyboardRewriterFixEnabled()) { auto keyboard_modifier_event_rewriter = @@ -139,6 +137,13 @@ ash::input_method::InputMethodManager::Get()->GetImeKeyboard()); AddEventRewriter(std::move(keyboard_modifier_event_rewriter)); } + // Accessibility rewriter is applied between modifier event rewriters and + // EventRewriterAsh. Specifically, Search modifier is captured by the + // accessibility rewriter, that should be the ones after modifier remapping. + // However, accessibility rewriter wants to capture it before it is rewritten + // into 6-pack keys, which is done in EventRewriterAsh. + AddEventRewriter(std::move(accessibility_event_rewriter)); + AddEventRewriter(std::move(keyboard_driven_event_rewriter)); AddEventRewriter(std::move(event_rewriter_ash)); }
diff --git a/ash/frame/caption_buttons/frame_size_button_unittest.cc b/ash/frame/caption_buttons/frame_size_button_unittest.cc index 36259d35..fb19260 100644 --- a/ash/frame/caption_buttons/frame_size_button_unittest.cc +++ b/ash/frame/caption_buttons/frame_size_button_unittest.cc
@@ -6,6 +6,7 @@ #include "ash/display/screen_orientation_controller.h" #include "ash/display/screen_orientation_controller_test_api.h" +#include "ash/public/cpp/tablet_mode.h" #include "ash/shell.h" #include "ash/test/ash_test_base.h" #include "ash/test/ash_test_util.h"
diff --git a/ash/glanceables/tasks/glanceables_task_view.cc b/ash/glanceables/tasks/glanceables_task_view.cc index f4f71ef..ef3f471 100644 --- a/ash/glanceables/tasks/glanceables_task_view.cc +++ b/ash/glanceables/tasks/glanceables_task_view.cc
@@ -59,7 +59,7 @@ label->SetHorizontalAlignment(gfx::ALIGN_LEFT); // Views should not be individually selected for accessibility. Accessible // name and behavior comes from the parent. - label->GetViewAccessibility().OverrideIsIgnored(true); + label->GetViewAccessibility().SetIsIgnored(true); label->SetBackgroundColor(SK_ColorTRANSPARENT); label->SetAutoColorReadabilityEnabled(false); return label;
diff --git a/ash/glanceables/tasks/glanceables_task_view_v2.cc b/ash/glanceables/tasks/glanceables_task_view_v2.cc index c6fd45b0..f41e0ef 100644 --- a/ash/glanceables/tasks/glanceables_task_view_v2.cc +++ b/ash/glanceables/tasks/glanceables_task_view_v2.cc
@@ -23,8 +23,10 @@ #include "ash/system/time/calendar_utils.h" #include "ash/system/time/date_helper.h" #include "base/functional/bind.h" +#include "base/location.h" #include "base/strings/string_util.h" #include "base/strings/utf_string_conversions.h" +#include "base/task/sequenced_task_runner.h" #include "base/types/cxx23_to_underlying.h" #include "chromeos/ash/components/network/network_handler.h" #include "chromeos/ash/components/network/network_state_handler.h" @@ -42,6 +44,7 @@ #include "ui/views/accessibility/view_accessibility.h" #include "ui/views/background.h" #include "ui/views/controls/button/button.h" +#include "ui/views/controls/button/button_controller.h" #include "ui/views/controls/button/image_button.h" #include "ui/views/controls/button/label_button.h" #include "ui/views/controls/focus_ring.h" @@ -51,7 +54,6 @@ #include "ui/views/controls/textfield/textfield_controller.h" #include "ui/views/layout/flex_layout_view.h" #include "ui/views/widget/widget_delegate.h" -#include "ui/wm/core/focus_controller.h" namespace ash { namespace { @@ -85,7 +87,7 @@ label->SetHorizontalAlignment(gfx::ALIGN_LEFT); // Views should not be individually selected for accessibility. Accessible // name and behavior comes from the parent. - label->GetViewAccessibility().OverrideIsIgnored(true); + label->GetViewAccessibility().SetIsIgnored(true); label->SetBackgroundColor(SK_ColorTRANSPARENT); label->SetAutoColorReadabilityEnabled(false); return label; @@ -138,7 +140,7 @@ public: using OnFinishedEditingCallback = - base::OnceCallback<void(const std::u16string& title)>; + base::RepeatingCallback<void(const std::u16string& title)>; TaskViewTextField(const std::u16string& title, OnFinishedEditingCallback on_finished_editing) @@ -192,8 +194,7 @@ // Entering inactive state from the active state implies the editing is // done. if (!IsActive()) { - // Running `on_finished_editing_` deletes `this`. - std::move(on_finished_editing_).Run(GetText()); + on_finished_editing_.Run(GetText()); } } @@ -216,6 +217,8 @@ SetEnabledTextColorIds(cros_tokens::kCrosSysPrimary); label()->SetFontList(TypographyProvider::Get()->ResolveTypographyToken( TypographyToken::kCrosButton2)); + button_controller()->set_notify_action( + views::ButtonController::NotifyAction::kOnPress); } }; @@ -409,6 +412,32 @@ GlanceablesTaskViewV2::~GlanceablesTaskViewV2() = default; +void GlanceablesTaskViewV2::OnViewBlurred(views::View* observed_view) { + if ((observed_view == edit_in_browser_button_ || + observed_view == task_title_textfield_) && + (!edit_in_browser_button_ || !edit_in_browser_button_->HasFocus()) && + (!task_title_textfield_ || !task_title_textfield_->HasFocus())) { + edit_exit_observer_.RemoveAllObservations(); + // Schedule a task to update task view state to kView. Done asynchronously + // to let the focus change to complete. State update will remove the blurred + // view while focus is still changing, which may cause a crash - + // b/324409607. + base::SequencedTaskRunner::GetCurrentDefault()->PostTask( + FROM_HERE, + base::BindOnce(&GlanceablesTaskViewV2::UpdateTaskTitleViewForState, + state_change_weak_ptr_factory_.GetWeakPtr(), + TaskTitleViewState::kView)); + } +} + +void GlanceablesTaskViewV2::OnViewIsDeleting(views::View* observed_view) { + edit_exit_observer_.RemoveObservation(observed_view); + + if (!edit_exit_observer_.IsObservingAnySource()) { + UpdateTaskTitleViewForState(TaskTitleViewState::kView); + } +} + const views::ImageButton* GlanceablesTaskViewV2::GetCheckButtonForTest() const { return check_button_; } @@ -419,6 +448,9 @@ void GlanceablesTaskViewV2::UpdateTaskTitleViewForState( TaskTitleViewState state) { + state_change_weak_ptr_factory_.InvalidateWeakPtrs(); + edit_exit_observer_.RemoveAllObservations(); + task_title_button_ = nullptr; task_title_textfield_ = nullptr; tasks_title_view_->RemoveAllChildViews(); @@ -436,6 +468,7 @@ task_title_, base::BindRepeating( &GlanceablesTaskViewV2::TaskTitleButtonPressed, base::Unretained(this)))); + task_title_button_->SetEnabled(!saving_task_changes_); task_title_button_->UpdateLabelForState( /*completed=*/check_button_->checked()); break; @@ -443,14 +476,15 @@ task_title_textfield_ = tasks_title_view_->AddChildView(std::make_unique<TaskViewTextField>( task_title_, - base::BindOnce(&GlanceablesTaskViewV2::OnFinishedEditing, - base::Unretained(this)))); + base::BindRepeating(&GlanceablesTaskViewV2::OnFinishedEditing, + base::Unretained(this)))); GetWidget()->widget_delegate()->SetCanActivate(true); task_title_textfield_->RequestFocus(); edit_in_browser_button_ = contents_view_->AddChildView( std::make_unique<EditInBrowserButton>(edit_in_browser_callback_)); - check_button_->SetEnabled(false); + edit_exit_observer_.AddObservation(task_title_textfield_); + edit_exit_observer_.AddObservation(edit_in_browser_button_); break; } @@ -491,6 +525,10 @@ return; } + if (saving_task_changes_) { + return; + } + bool target_state = !check_button_->checked(); check_button_->SetChecked(target_state); @@ -518,18 +556,18 @@ task_title_ = title; } - // Skip the title view resetting when the window lost active. Let the view - // hierarchy clean up be done by the native widget. - if (!(GetWidget() && - GetWidget()->GetNativeWindow() != - Shell::Get()->focus_controller()->GetActiveWindow())) { - UpdateTaskTitleViewForState(TaskTitleViewState::kView); + if (task_title_textfield_ && task_title_textfield_->HasFocus()) { + GetFocusManager()->ClearFocus(); } if (task_id_.empty() || task_title_ != old_title) { + saving_task_changes_ = true; if (task_title_button_) { task_title_button_->SetEnabled(false); } + if (task_title_textfield_) { + task_title_textfield_->SetEnabled(false); + } // Note: result for task addition flow will be recorded in the parent view, // which initialized add task flow. if (!task_id_.empty()) { @@ -545,7 +583,6 @@ } else { // Note: result for task addition flow will be recorded in the parent view, // which initialized add task flow. - check_button_->SetEnabled(true); if (!task_id_.empty()) { RecordTaskModificationResult(TaskModificationResult::kCancelled); } @@ -553,10 +590,13 @@ } void GlanceablesTaskViewV2::OnSaved(const api::Task* task) { - check_button_->SetEnabled(true); + saving_task_changes_ = false; if (task_title_button_) { task_title_button_->SetEnabled(true); } + if (task_title_textfield_) { + task_title_textfield_->SetEnabled(true); + } if (task) { task_id_ = task->id; }
diff --git a/ash/glanceables/tasks/glanceables_task_view_v2.h b/ash/glanceables/tasks/glanceables_task_view_v2.h index fbd6ad6..460da8b 100644 --- a/ash/glanceables/tasks/glanceables_task_view_v2.h +++ b/ash/glanceables/tasks/glanceables_task_view_v2.h
@@ -13,8 +13,10 @@ #include "base/functional/callback_forward.h" #include "base/memory/raw_ptr.h" #include "base/memory/weak_ptr.h" +#include "base/scoped_multi_source_observation.h" #include "ui/base/metadata/metadata_header_macros.h" #include "ui/views/layout/flex_layout_view.h" +#include "ui/views/view_observer.h" namespace views { class ImageButton; @@ -44,7 +46,8 @@ // | | | | +-----------------------------------+ | | // | +-----------------+ +---------------------------------------+ | // +---------------------------------------------------------------+ -class ASH_EXPORT GlanceablesTaskViewV2 : public views::FlexLayoutView { +class ASH_EXPORT GlanceablesTaskViewV2 : public views::FlexLayoutView, + public views::ViewObserver { METADATA_HEADER(GlanceablesTaskViewV2, views::FlexLayoutView) public: @@ -70,6 +73,10 @@ GlanceablesTaskViewV2& operator=(const GlanceablesTaskViewV2&) = delete; ~GlanceablesTaskViewV2() override; + // views::ViewObserver: + void OnViewBlurred(views::View* observed_view) override; + void OnViewIsDeleting(views::View* observed_view) override; + const views::ImageButton* GetCheckButtonForTest() const; bool GetCompletedForTest() const; @@ -115,6 +122,8 @@ // Title of the task. std::u16string task_title_; + bool saving_task_changes_ = false; + // Marks the task as completed. const MarkAsCompletedCallback mark_as_completed_callback_; @@ -127,6 +136,12 @@ // Shows an error message in the parent `GlanceablesTasksView`. const ShowErrorMessageCallback show_error_message_callback_; + base::ScopedMultiSourceObservation<views::View, GlanceablesTaskViewV2> + edit_exit_observer_{this}; + + base::WeakPtrFactory<GlanceablesTaskViewV2> state_change_weak_ptr_factory_{ + this}; + base::WeakPtrFactory<GlanceablesTaskViewV2> weak_ptr_factory_{this}; };
diff --git a/ash/glanceables/tasks/glanceables_task_view_v2_unittest.cc b/ash/glanceables/tasks/glanceables_task_view_v2_unittest.cc index ed23856d..326e011 100644 --- a/ash/glanceables/tasks/glanceables_task_view_v2_unittest.cc +++ b/ash/glanceables/tasks/glanceables_task_view_v2_unittest.cc
@@ -274,6 +274,7 @@ PressAndReleaseKey(ui::VKEY_D); PressAndReleaseKey(ui::VKEY_ESCAPE); + base::RunLoop().RunUntilIdle(); } { @@ -346,12 +347,97 @@ PressAndReleaseKey(ui::VKEY_P); PressAndReleaseKey(ui::VKEY_D); PressAndReleaseKey(ui::VKEY_ESCAPE); + base::RunLoop().RunUntilIdle(); const auto [task_view, task_id, title, callback] = future.Take(); EXPECT_EQ(task_id, "task-id"); EXPECT_EQ(title, "Task title upd"); } +TEST_F(GlanceablesTaskViewStableLaunchTest, CommitEditedTaskOnTab) { + const auto task = api::Task("task-id", "Task title", + /*due=*/std::nullopt, /*completed=*/false, + /*has_subtasks=*/false, /*has_email_link=*/false, + /*has_notes=*/false, /*updated=*/base::Time()); + + base::test::TestFuture<base::WeakPtr<GlanceablesTaskViewV2>, + const std::string&, const std::string&, + api::TasksClient::OnTaskSavedCallback> + future; + + const auto widget = CreateFramelessTestWidget(); + widget->SetFullscreen(true); + auto* const view = + widget->SetContentsView(std::make_unique<GlanceablesTaskViewV2>( + &task, /*mark_as_completed_callback=*/base::DoNothing(), + /*save_callback=*/future.GetRepeatingCallback(), + /*edit_in_browser_callback=*/base::DoNothing(), + /*show_error_message_callback=*/base::DoNothing())); + ASSERT_TRUE(view); + + view->UpdateTaskTitleViewForState( + GlanceablesTaskViewV2::TaskTitleViewState::kEdit); + PressAndReleaseKey(ui::VKEY_SPACE); + PressAndReleaseKey(ui::VKEY_U); + PressAndReleaseKey(ui::VKEY_P); + PressAndReleaseKey(ui::VKEY_D); + + PressAndReleaseKey(ui::VKEY_TAB); + base::RunLoop().RunUntilIdle(); + + { + auto [task_view, task_id, title, callback] = future.Take(); + EXPECT_EQ(task_id, "task-id"); + EXPECT_EQ(title, "Task title upd"); + const auto updated_task = + api::Task("task-id", "New upd", + /*due=*/std::nullopt, /*completed=*/false, + /*has_subtasks=*/false, + /*has_email_link=*/false, /*has_notes=*/false, + /*updated=*/base::Time::Now()); + std::move(callback).Run(&updated_task); + } + + EXPECT_FALSE(views::AsViewClass<views::Label>(view->GetViewByID( + base::to_underlying(GlanceablesViewId::kTaskItemTitleLabel)))); + EXPECT_TRUE(views::AsViewClass<views::Textfield>(view->GetViewByID( + base::to_underlying(GlanceablesViewId::kTaskItemTitleTextField)))); + const auto* edit_in_browser_button = view->GetViewByID( + base::to_underlying(GlanceablesViewId::kTaskItemEditInBrowserLabel)); + ASSERT_TRUE(edit_in_browser_button); + EXPECT_TRUE(edit_in_browser_button->HasFocus()); + + PressAndReleaseKey(ui::VKEY_TAB, ui::EF_SHIFT_DOWN); + base::RunLoop().RunUntilIdle(); + + PressAndReleaseKey(ui::VKEY_RIGHT); + PressAndReleaseKey(ui::VKEY_A); + + PressAndReleaseKey(ui::VKEY_TAB); + base::RunLoop().RunUntilIdle(); + + { + const auto [task_view, task_id, title, callback] = future.Take(); + EXPECT_EQ(task_id, "task-id"); + EXPECT_EQ(title, "Task title upda"); + } + + edit_in_browser_button = view->GetViewByID( + base::to_underlying(GlanceablesViewId::kTaskItemEditInBrowserLabel)); + ASSERT_TRUE(edit_in_browser_button); + EXPECT_TRUE(edit_in_browser_button->HasFocus()); + + view->GetFocusManager()->ClearFocus(); + base::RunLoop().RunUntilIdle(); + + EXPECT_TRUE(views::AsViewClass<views::Label>(view->GetViewByID( + base::to_underlying(GlanceablesViewId::kTaskItemTitleLabel)))); + EXPECT_FALSE(views::AsViewClass<views::Textfield>(view->GetViewByID( + base::to_underlying(GlanceablesViewId::kTaskItemTitleTextField)))); + EXPECT_FALSE(views::AsViewClass<views::Textfield>(view->GetViewByID( + base::to_underlying(GlanceablesViewId::kTaskItemEditInBrowserLabel)))); +} + TEST_F(GlanceablesTaskViewStableLaunchTest, SupportsEditingRightAfterAdding) { base::test::TestFuture<base::WeakPtr<GlanceablesTaskViewV2>, const std::string&, const std::string&, @@ -424,7 +510,6 @@ view->UpdateTaskTitleViewForState( GlanceablesTaskViewV2::TaskTitleViewState::kEdit); - EXPECT_FALSE(view->GetCheckButtonForTest()->GetEnabled()); EXPECT_FALSE(view->GetCompletedForTest()); PressAndReleaseKey(ui::VKEY_N, ui::EF_SHIFT_DOWN); @@ -437,7 +522,9 @@ auto [task_view, task_id, title, callback] = future.Take(); EXPECT_TRUE(task_id.empty()); EXPECT_EQ(title, "New"); - EXPECT_FALSE(view->GetCheckButtonForTest()->GetEnabled()); + EXPECT_FALSE(view->GetCompletedForTest()); + base::RunLoop().RunUntilIdle(); + EXPECT_FALSE(view->GetCompletedForTest()); const auto* const title_label =
diff --git a/ash/glanceables/tasks/glanceables_tasks_view.cc b/ash/glanceables/tasks/glanceables_tasks_view.cc index c3ac36a..722b44fd 100644 --- a/ash/glanceables/tasks/glanceables_tasks_view.cc +++ b/ash/glanceables/tasks/glanceables_tasks_view.cc
@@ -29,7 +29,9 @@ #include "base/functional/bind.h" #include "base/functional/callback_helpers.h" #include "base/i18n/time_formatting.h" +#include "base/location.h" #include "base/strings/utf_string_conversions.h" +#include "base/task/sequenced_task_runner.h" #include "base/time/time.h" #include "base/types/cxx23_to_underlying.h" #include "ui/base/l10n/l10n_util.h" @@ -52,7 +54,6 @@ #include "ui/views/layout/flex_layout_view.h" #include "ui/views/view.h" #include "ui/views/view_class_properties.h" -#include "ui/wm/core/focus_controller.h" #include "url/gurl.h" namespace ash { @@ -467,21 +468,19 @@ const std::string& title, api::TasksClient::OnTaskSavedCallback callback) { if (task_id.empty()) { - // Manually deleting `view` may cause the focus manager try storing the - // dangling `view`'s descendants. Let native window handle the view deletion - // when it lost active. - if (GetWidget() && - GetWidget()->GetNativeWindow() != - Shell::Get()->focus_controller()->GetActiveWindow()) { - return; - } - // Empty `task_id` means that the task has not yet been created. Verify that // this task has a non-empty title, otherwise just delete the `view` from // the scrollable container. if (title.empty() && view) { RecordTaskAdditionResult(TaskModificationResult::kCancelled); - task_items_container_view_->RemoveChildViewT(view.get()); + + // Removing the task immediately may cause a crash when the task is saved + // in response to the task title textfield losing focus, as it may result + // in deleting focused view while the focus manager is handling focus + // change to another view. b/324409607 + base::SequencedTaskRunner::GetCurrentDefault()->PostTask( + FROM_HERE, base::BindOnce(&GlanceablesTasksView::RemoveTaskView, + weak_ptr_factory_.GetWeakPtr(), view)); return; } @@ -560,6 +559,19 @@ } } +void GlanceablesTasksView::RemoveTaskView( + base::WeakPtr<GlanceablesTaskViewV2> task_view) { + if (!task_view) { + return; + } + + if (task_view->Contains(GetFocusManager()->GetFocusedView())) { + add_new_task_button_->RequestFocus(); + } + task_items_container_view_->RemoveChildViewT(task_view.get()); + PreferredSizeChanged(); +} + BEGIN_METADATA(GlanceablesTasksView) END_METADATA
diff --git a/ash/glanceables/tasks/glanceables_tasks_view.h b/ash/glanceables/tasks/glanceables_tasks_view.h index 1a0a41d..3a4ae6d 100644 --- a/ash/glanceables/tasks/glanceables_tasks_view.h +++ b/ash/glanceables/tasks/glanceables_tasks_view.h
@@ -130,6 +130,9 @@ // `error_type`. std::u16string GetErrorString(GlanceablesTasksErrorType error_type) const; + // Removes `task_view` from the tasks container. + void RemoveTaskView(base::WeakPtr<GlanceablesTaskViewV2> task_view); + // Model for the combobox used to change the active task list. std::unique_ptr<TasksComboboxModel> tasks_combobox_model_;
diff --git a/ash/glanceables/tasks/glanceables_tasks_view_unittest.cc b/ash/glanceables/tasks/glanceables_tasks_view_unittest.cc index cb2ae83c..39c8916 100644 --- a/ash/glanceables/tasks/glanceables_tasks_view_unittest.cc +++ b/ash/glanceables/tasks/glanceables_tasks_view_unittest.cc
@@ -258,6 +258,7 @@ PressAndReleaseKey(ui::VKEY_E); PressAndReleaseKey(ui::VKEY_W); PressAndReleaseKey(ui::VKEY_ESCAPE); + base::RunLoop().RunUntilIdle(); // Verify executed callbacks number. EXPECT_EQ(tasks_client()->RunPendingAddTaskCallbacks(), 1u); @@ -274,6 +275,7 @@ GetEventGenerator()->PressAndReleaseKey(ui::VKEY_SPACE); GetEventGenerator()->PressAndReleaseKey(ui::VKEY_1); PressAndReleaseKey(ui::VKEY_ESCAPE); + base::RunLoop().RunUntilIdle(); // Verify executed callbacks number. EXPECT_EQ(tasks_client()->RunPendingAddTaskCallbacks(), 0u); @@ -287,6 +289,96 @@ "Ash.Glanceables.TimeManagement.Tasks.UserAction", 4, 1); } +TEST_F(GlanceablesTasksViewTest, TabbingOutOfNewTaskTextfieldAddsTask) { + base::HistogramTester histogram_tester; + tasks_client()->set_paused(true); + + // Add a task. + GestureTapOn(GetAddNewTaskButton()); + + const auto* task_view = GetTaskItemsContainerView()->children()[0].get(); + EXPECT_TRUE(task_view->GetViewByID( + base::to_underlying(GlanceablesViewId::kTaskItemTitleTextField))); + + PressAndReleaseKey(ui::VKEY_N, ui::EF_SHIFT_DOWN); + PressAndReleaseKey(ui::VKEY_E); + PressAndReleaseKey(ui::VKEY_W); + PressAndReleaseKey(ui::VKEY_TAB); + base::RunLoop().RunUntilIdle(); + + // Verify that edit in browser button is visible and focused. + const auto* const edit_in_browser_button = GetEditInBrowserButton(); + ASSERT_TRUE(edit_in_browser_button); + EXPECT_TRUE(edit_in_browser_button->GetVisible()); + EXPECT_TRUE(edit_in_browser_button->HasFocus()); + + EXPECT_FALSE(task_view->GetViewByID( + base::to_underlying(GlanceablesViewId::kTaskItemTitleLabel))); + + // Verify executed callbacks number. + EXPECT_EQ(tasks_client()->RunPendingAddTaskCallbacks(), 1u); + EXPECT_EQ(tasks_client()->RunPendingUpdateTaskCallbacks(), 0u); + + // Tab back to the Add task textfield, and update the text. + PressAndReleaseKey(ui::VKEY_TAB, ui::EF_SHIFT_DOWN); + base::RunLoop().RunUntilIdle(); + + const auto* const title_text_field = + views::AsViewClass<views::Textfield>(task_view->GetViewByID( + base::to_underlying(GlanceablesViewId::kTaskItemTitleTextField))); + ASSERT_TRUE(title_text_field); + EXPECT_TRUE(title_text_field->HasFocus()); + EXPECT_EQ(u"New", title_text_field->GetText()); + + PressAndReleaseKey(ui::VKEY_RIGHT); + PressAndReleaseKey(ui::VKEY_1); + // Focus edit in browser button. + PressAndReleaseKey(ui::VKEY_TAB); + + EXPECT_EQ(tasks_client()->RunPendingAddTaskCallbacks(), 0u); + EXPECT_EQ(tasks_client()->RunPendingUpdateTaskCallbacks(), 1u); + + // Focus the next task, which exits the task editing state. + PressAndReleaseKey(ui::VKEY_TAB); + base::RunLoop().RunUntilIdle(); + + EXPECT_FALSE(GetEditInBrowserButton()); + + task_view = GetTaskItemsContainerView()->children()[0].get(); + EXPECT_FALSE(task_view->GetViewByID( + base::to_underlying(GlanceablesViewId::kTaskItemTitleTextField))); + const auto* title_label = + views::AsViewClass<views::Label>(task_view->GetViewByID( + base::to_underlying(GlanceablesViewId::kTaskItemTitleLabel))); + ASSERT_TRUE(title_label); + EXPECT_EQ(u"New1", title_label->GetText()); + + // Edit the same task. + view()->GetWidget()->LayoutRootViewIfNecessary(); + GestureTapOn(title_label); + GetEventGenerator()->PressAndReleaseKey(ui::VKEY_SPACE); + GetEventGenerator()->PressAndReleaseKey(ui::VKEY_1); + PressAndReleaseKey(ui::VKEY_ESCAPE); + base::RunLoop().RunUntilIdle(); + + // Verify executed callbacks number. + EXPECT_EQ(tasks_client()->RunPendingAddTaskCallbacks(), 0u); + EXPECT_EQ(tasks_client()->RunPendingUpdateTaskCallbacks(), 1u); + + title_label = views::AsViewClass<views::Label>( + GetTaskItemsContainerView()->children()[0]->GetViewByID( + base::to_underlying(GlanceablesViewId::kTaskItemTitleLabel))); + ASSERT_TRUE(title_label); + EXPECT_EQ(u"New1 1", title_label->GetText()); + + histogram_tester.ExpectTotalCount( + "Ash.Glanceables.TimeManagement.Tasks.UserAction", 2); + histogram_tester.ExpectBucketCount( + "Ash.Glanceables.TimeManagement.Tasks.UserAction", 3, 1); + histogram_tester.ExpectBucketCount( + "Ash.Glanceables.TimeManagement.Tasks.UserAction", 4, 1); +} + TEST_F(GlanceablesTasksViewTest, AllowsPressingAddNewTaskButtonWhileAdding) { const auto initial_tasks_count = GetTaskItemsContainerView()->children().size(); @@ -306,6 +398,8 @@ EXPECT_EQ(GetTaskItemsContainerView()->children().size(), initial_tasks_count + 2); + base::RunLoop().RunUntilIdle(); + // But the previous task becomes automatically committed due to losing focus. const auto* const previous_task_label = views::AsViewClass<views::Label>( GetTaskItemsContainerView()->children()[1]->GetViewByID( @@ -359,6 +453,8 @@ // Commit changes. PressAndReleaseKey(ui::VKEY_ESCAPE); + + base::RunLoop().RunUntilIdle(); } { @@ -385,6 +481,7 @@ EXPECT_EQ(GetTaskItemsContainerView()->children().size(), initial_tasks_count + 1); PressAndReleaseKey(ui::VKEY_ESCAPE); + base::RunLoop().RunUntilIdle(); // Verify executed callbacks number. EXPECT_EQ(GetTaskItemsContainerView()->children().size(), @@ -422,6 +519,7 @@ PressAndReleaseKey(ui::VKEY_E); PressAndReleaseKey(ui::VKEY_W); PressAndReleaseKey(ui::VKEY_ESCAPE); + base::RunLoop().RunUntilIdle(); EXPECT_EQ(task_items_container_view->children().size(), 3u); EXPECT_FALSE(GetErrorMessage()); @@ -450,6 +548,7 @@ GetEventGenerator()->PressAndReleaseKey(ui::VKEY_P); GetEventGenerator()->PressAndReleaseKey(ui::VKEY_D); PressAndReleaseKey(ui::VKEY_ESCAPE); + base::RunLoop().RunUntilIdle(); EXPECT_EQ(task_items_container_view->children().size(), 2u); EXPECT_FALSE(GetErrorMessage());
diff --git a/ash/picker/model/picker_search_results_section.h b/ash/picker/model/picker_search_results_section.h index 4ad116e..508d972 100644 --- a/ash/picker/model/picker_search_results_section.h +++ b/ash/picker/model/picker_search_results_section.h
@@ -19,6 +19,7 @@ kExpressions, kLinks, kFiles, + kDriveFiles, kGifs, kRecentlyUsed, };
diff --git a/ash/picker/picker_controller.cc b/ash/picker/picker_controller.cc index af3cf654..c19e1b1 100644 --- a/ash/picker/picker_controller.cc +++ b/ash/picker/picker_controller.cc
@@ -18,10 +18,13 @@ #include "ash/picker/picker_asset_fetcher_impl.h" #include "ash/picker/picker_copy_media.h" #include "ash/picker/picker_insert_media_request.h" +#include "ash/picker/picker_rich_media.h" #include "ash/picker/picker_search_controller.h" #include "ash/picker/views/picker_icons.h" +#include "ash/picker/views/picker_positioning.h" #include "ash/picker/views/picker_view.h" #include "ash/picker/views/picker_view_delegate.h" +#include "ash/picker/views/picker_widget.h" #include "ash/public/cpp/ash_web_view_factory.h" #include "ash/public/cpp/picker/picker_client.h" #include "ash/public/cpp/picker/picker_search_result.h" @@ -65,8 +68,7 @@ constexpr base::TimeDelta kInsertMediaTimeout = base::Seconds(2); // Time from when a start starts to when the first set of results are published. -// TODO: b/325195938 - Lower this to 200ms without affecting results. -constexpr base::TimeDelta kBurnInPeriod = base::Milliseconds(400); +constexpr base::TimeDelta kBurnInPeriod = base::Milliseconds(200); enum class PickerFeatureKeyType { kNone, kDev, kTest }; @@ -112,30 +114,29 @@ : gfx::Rect(); } -std::optional<PickerInsertMediaRequest::MediaData> ResultToInsertMediaData( +std::optional<PickerRichMedia> ResultToInsertMediaData( const PickerSearchResult& result) { - using ReturnType = std::optional<PickerInsertMediaRequest::MediaData>; + using ReturnType = std::optional<PickerRichMedia>; return std::visit( base::Overloaded{ [](const PickerSearchResult::TextData& data) -> ReturnType { - return PickerInsertMediaRequest::MediaData::Text(data.text); + return PickerTextMedia(data.text); }, [](const PickerSearchResult::EmojiData& data) -> ReturnType { - return PickerInsertMediaRequest::MediaData::Text(data.emoji); + return PickerTextMedia(data.emoji); }, [](const PickerSearchResult::SymbolData& data) -> ReturnType { - return PickerInsertMediaRequest::MediaData::Text(data.symbol); + return PickerTextMedia(data.symbol); }, [](const PickerSearchResult::EmoticonData& data) -> ReturnType { - return PickerInsertMediaRequest::MediaData::Text(data.emoticon); + return PickerTextMedia(data.emoticon); }, [](const PickerSearchResult::GifData& data) -> ReturnType { - return PickerInsertMediaRequest::MediaData::Image(data.url); + return PickerImageMedia(data.full_url, data.full_dimensions, + data.content_description); }, [](const PickerSearchResult::BrowsingHistoryData& data) - -> ReturnType { - return PickerInsertMediaRequest::MediaData::Link(data.url); - }, + -> ReturnType { return PickerLinkMedia(data.url); }, [](const PickerSearchResult::CategoryData& data) -> ReturnType { return std::nullopt; }, @@ -143,13 +144,6 @@ result.data()); } -void MaybeCopyMediaToClipboard(const PickerSearchResult& result) { - if (const auto* gif = - std::get_if<PickerSearchResult::GifData>(&result.data())) { - CopyGifMediaToClipboard(gif->url, gif->content_description); - } -} - } // namespace PickerController::PickerController() { @@ -204,9 +198,11 @@ if (widget_) { widget_->Close(); } else { - widget_ = PickerView::CreateWidget(GetCaretBounds(), GetCursorPoint(), - GetFocusedWindowBounds(), this, - trigger_event_timestamp); + widget_ = PickerWidget::Create( + this, + GetPickerAnchorBounds(GetCaretBounds(), GetCursorPoint(), + GetFocusedWindowBounds()), + trigger_event_timestamp); widget_->Show(); feature_usage_metrics_.StartUsage(); @@ -265,14 +261,14 @@ return; } - std::optional<PickerInsertMediaRequest::MediaData> media_to_insert = + std::optional<PickerRichMedia> media_to_insert = ResultToInsertMediaData(result); CHECK(media_to_insert.has_value()); // This cancels the previous request if there was one. insert_media_request_ = std::make_unique<PickerInsertMediaRequest>( input_method, *media_to_insert, kInsertMediaTimeout, - base::BindOnce(&MaybeCopyMediaToClipboard, result)); + base::BindOnce(&CopyMediaToClipboard, *media_to_insert)); } PickerAssetFetcher* PickerController::GetAssetFetcher() {
diff --git a/ash/picker/picker_controller_unittest.cc b/ash/picker/picker_controller_unittest.cc index 5d8ab05..980c942 100644 --- a/ash/picker/picker_controller_unittest.cc +++ b/ash/picker/picker_controller_unittest.cc
@@ -21,6 +21,7 @@ #include "ui/base/ime/fake_text_input_client.h" #include "ui/base/ime/input_method.h" #include "ui/base/models/image_model.h" +#include "ui/gfx/geometry/size.h" #include "ui/views/test/widget_test.h" namespace ash { @@ -67,6 +68,7 @@ FetchGifsCallback callback) override {} void StopGifSearch() override {} void StartCrosSearch(const std::u16string& query, + std::optional<PickerCategory> category, CrosSearchResultsCallback callback) override {} void StopCrosQuery() override {} @@ -178,8 +180,9 @@ Shell::GetPrimaryRootWindow()->GetHost()->GetInputMethod(); controller.InsertResultOnNextFocus(PickerSearchResult::Gif( - GURL("http://foo.com/fake.gif"), + GURL("http://foo.com/fake_preview.gif"), GURL("http://foo.com/fake_preview_image.png"), gfx::Size(), + GURL("http://foo.com/fake.gif"), gfx::Size(), /*content_description=*/u"")); controller.widget_for_testing()->CloseNow(); ui::FakeTextInputClient input_field( @@ -199,7 +202,9 @@ Shell::GetPrimaryRootWindow()->GetHost()->GetInputMethod(); controller.InsertResultOnNextFocus(PickerSearchResult::Gif( - /*url=*/GURL("http://foo.com"), /*preview_image_url=*/GURL(), gfx::Size(), + /*preview_url=*/GURL("http://foo.com/preview"), + /*preview_image_url=*/GURL(), gfx::Size(30, 20), + /*full_url=*/GURL("http://foo.com"), gfx::Size(60, 40), /*content_description=*/u"a gif")); controller.widget_for_testing()->CloseNow(); ui::FakeTextInputClient input_field( @@ -209,7 +214,7 @@ EXPECT_EQ( ReadHtmlFromClipboard(ui::Clipboard::GetForCurrentThread()), - uR"html(<img src="http://foo.com/" referrerpolicy="no-referrer" alt="a gif"/>)html"); + uR"html(<img src="http://foo.com/" referrerpolicy="no-referrer" alt="a gif" width="60" height="40"/>)html"); EXPECT_TRUE( ash::ToastManager::Get()->IsToastShown("picker_copy_to_clipboard")); }
diff --git a/ash/picker/picker_copy_media.cc b/ash/picker/picker_copy_media.cc index 8fe13fd..0a14e8e 100644 --- a/ash/picker/picker_copy_media.cc +++ b/ash/picker/picker_copy_media.cc
@@ -4,16 +4,24 @@ #include "ash/picker/picker_copy_media.h" +#include <iterator> #include <memory> #include <string> +#include <vector> #include "ash/constants/notifier_catalogs.h" +#include "ash/picker/picker_rich_media.h" #include "ash/public/cpp/system/toast_data.h" #include "ash/public/cpp/system/toast_manager.h" +#include "base/functional/overloaded.h" +#include "base/ranges/algorithm.h" #include "base/strings/escape.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_util.h" #include "base/strings/stringprintf.h" #include "base/strings/utf_string_conversions.h" #include "ui/base/clipboard/scoped_clipboard_writer.h" +#include "ui/gfx/geometry/size.h" #include "url/gurl.h" namespace ash { @@ -21,27 +29,70 @@ constexpr char kPickerCopyToClipboardToastId[] = "picker_copy_to_clipboard"; -std::string BuildGifHTML(const GURL& url, - std::u16string_view content_description) { +struct HtmlAttr { + // `name` has to be a compile-time constant. + const char* name; + std::string value; +}; + +std::string ImageHtmlAttrsToStr(std::vector<HtmlAttr> attrs) { + std::vector<std::string> attrs_as_strings; + attrs_as_strings.reserve(attrs.size()); + base::ranges::transform( + std::move(attrs), std::back_inserter(attrs_as_strings), + [](HtmlAttr attr) { + return base::StringPrintf(R"(%s="%s")", attr.name, attr.value.c_str()); + }); + + return base::StringPrintf( + R"(<img %s/>)", + base::JoinString(std::move(attrs_as_strings), " ").c_str()); +} + +std::string BuildImageHtml(const PickerImageMedia& image) { + std::vector<HtmlAttr> attrs; + // GURL::specs are always canonicalised and escaped, so this cannot result in + // an XSS. + attrs.emplace_back("src", image.url.spec()); // Referrer-Policy is used to prevent the website from getting information // about where the GIFs are being used. - return base::StringPrintf( - R"html(<img src="%s" referrerpolicy="no-referrer" alt="%s"/>)html", - url.spec().c_str(), - base::EscapeForHTML(base::UTF16ToUTF8(content_description)).c_str()); + attrs.emplace_back("referrerpolicy", "no-referrer"); + if (!image.content_description.empty()) { + attrs.emplace_back( + "alt", + base::EscapeForHTML(base::UTF16ToUTF8(image.content_description))); + } + if (image.dimensions.has_value()) { + attrs.emplace_back("width", + base::NumberToString(image.dimensions->width())); + attrs.emplace_back("height", + base::NumberToString(image.dimensions->height())); + } + return ImageHtmlAttrsToStr(std::move(attrs)); } } // namespace -void CopyGifMediaToClipboard(const GURL& url, - std::u16string_view content_description) { - // Overwrite the clipboard data with the GIF url. +void CopyMediaToClipboard(const PickerRichMedia& media) { auto clipboard = std::make_unique<ui::ScopedClipboardWriter>( ui::ClipboardBuffer::kCopyPaste); - clipboard->WriteHTML( - base::UTF8ToUTF16(BuildGifHTML(url, content_description)), - /*document_url=*/""); + // Overwrite the clipboard data. + std::visit(base::Overloaded{ + [&clipboard](const PickerTextMedia& media) { + clipboard->WriteText(std::move(media.text)); + }, + [&clipboard](const PickerImageMedia& media) { + clipboard->WriteHTML( + base::UTF8ToUTF16(BuildImageHtml(media)), + /*document_url=*/""); + }, + [&clipboard](const PickerLinkMedia& media) { + // TODO(b/322729192): Copy a real hyperlink. + clipboard->WriteText(base::UTF8ToUTF16(media.url.spec())); + }, + }, + media); // Show a toast to inform the user about the copy. // TODO: b/322928125 - Use dedicated toast catalog name.
diff --git a/ash/picker/picker_copy_media.h b/ash/picker/picker_copy_media.h index d085e60..5e65b478 100644 --- a/ash/picker/picker_copy_media.h +++ b/ash/picker/picker_copy_media.h
@@ -8,16 +8,12 @@ #include <string_view> #include "ash/ash_export.h" - -class GURL; +#include "ash/picker/picker_rich_media.h" namespace ash { -// Copies a GIF into the clipboard. -// TODO: b/322928125 - Take a PickerInsertMediaRequest::MediaData instead. -ASH_EXPORT void CopyGifMediaToClipboard( - const GURL& url, - std::u16string_view content_description); +// Copies rich media into the clipboard. +ASH_EXPORT void CopyMediaToClipboard(const PickerRichMedia& media); } // namespace ash
diff --git a/ash/picker/picker_copy_media_unittest.cc b/ash/picker/picker_copy_media_unittest.cc index 2c2c05f..56c9a143 100644 --- a/ash/picker/picker_copy_media_unittest.cc +++ b/ash/picker/picker_copy_media_unittest.cc
@@ -4,38 +4,87 @@ #include "ash/picker/picker_copy_media.h" +#include "ash/picker/picker_rich_media.h" #include "ash/picker/picker_test_util.h" #include "ash/public/cpp/system/toast_manager.h" #include "ash/test/ash_test_base.h" #include "testing/gtest/include/gtest/gtest.h" #include "ui/base/clipboard/clipboard.h" +#include "ui/gfx/geometry/size.h" namespace ash { namespace { class PickerCopyMediaTest : public AshTestBase {}; -TEST_F(PickerCopyMediaTest, CopiesGifAsHtml) { - CopyGifMediaToClipboard(GURL("https://foo.com"), - /*content_description=*/u"a gif"); +TEST_F(PickerCopyMediaTest, CopiesText) { + CopyMediaToClipboard(PickerTextMedia(u"hello")); + + EXPECT_EQ(ReadTextFromClipboard(ui::Clipboard::GetForCurrentThread()), + u"hello"); +} + +TEST_F(PickerCopyMediaTest, ShowsToastAfterCopyingText) { + CopyMediaToClipboard(PickerTextMedia(u"hello")); + + EXPECT_TRUE( + ash::ToastManager::Get()->IsToastShown("picker_copy_to_clipboard")); +} + +TEST_F(PickerCopyMediaTest, CopiesImageWithKnownDimensionsAsHtml) { + CopyMediaToClipboard( + PickerImageMedia(GURL("https://foo.com"), gfx::Size(30, 20))); EXPECT_EQ( ReadHtmlFromClipboard(ui::Clipboard::GetForCurrentThread()), - uR"html(<img src="https://foo.com/" referrerpolicy="no-referrer" alt="a gif"/>)html"); + uR"html(<img src="https://foo.com/" referrerpolicy="no-referrer" width="30" height="20"/>)html"); } -TEST_F(PickerCopyMediaTest, EscapesAltText) { - CopyGifMediaToClipboard(GURL("https://foo.com"), - /*content_description=*/u"a \"gif\""); +TEST_F(PickerCopyMediaTest, CopiesImageWithUnknownDimensionsAsHtml) { + CopyMediaToClipboard(PickerImageMedia(GURL("https://foo.com"))); EXPECT_EQ( ReadHtmlFromClipboard(ui::Clipboard::GetForCurrentThread()), - uR"html(<img src="https://foo.com/" referrerpolicy="no-referrer" alt="a "gif""/>)html"); + uR"html(<img src="https://foo.com/" referrerpolicy="no-referrer"/>)html"); } -TEST_F(PickerCopyMediaTest, ShowsToast) { - CopyGifMediaToClipboard(GURL("https://foo.com"), - /*content_description=*/u"a gif"); +TEST_F(PickerCopyMediaTest, CopiesImagesWithBothAltTextAndDimensionsAsHtml) { + CopyMediaToClipboard(PickerImageMedia(GURL("https://foo.com"), + gfx::Size(30, 20), + /*content_description=*/u"img")); + + EXPECT_EQ( + ReadHtmlFromClipboard(ui::Clipboard::GetForCurrentThread()), + uR"html(<img src="https://foo.com/" referrerpolicy="no-referrer" alt="img" width="30" height="20"/>)html"); +} + +TEST_F(PickerCopyMediaTest, EscapesAltTextForImages) { + CopyMediaToClipboard(PickerImageMedia(GURL("https://foo.com"), + /*dimensions=*/std::nullopt, + /*content_description=*/u"\"img\"")); + + EXPECT_EQ( + ReadHtmlFromClipboard(ui::Clipboard::GetForCurrentThread()), + uR"html(<img src="https://foo.com/" referrerpolicy="no-referrer" alt=""img""/>)html"); +} + +TEST_F(PickerCopyMediaTest, ShowsToastAfterCopyingImage) { + CopyMediaToClipboard( + PickerImageMedia(GURL("https://foo.com"), gfx::Size(30, 20))); + + EXPECT_TRUE( + ash::ToastManager::Get()->IsToastShown("picker_copy_to_clipboard")); +} + +TEST_F(PickerCopyMediaTest, CopiesLinks) { + CopyMediaToClipboard(PickerLinkMedia(GURL("https://foo.com"))); + + EXPECT_EQ(ReadTextFromClipboard(ui::Clipboard::GetForCurrentThread()), + u"https://foo.com/"); +} + +TEST_F(PickerCopyMediaTest, ShowsToastAfterCopyingLink) { + CopyMediaToClipboard(PickerLinkMedia(GURL("https://foo.com"))); EXPECT_TRUE( ash::ToastManager::Get()->IsToastShown("picker_copy_to_clipboard"));
diff --git a/ash/picker/picker_insert_media.cc b/ash/picker/picker_insert_media.cc new file mode 100644 index 0000000..53d61df --- /dev/null +++ b/ash/picker/picker_insert_media.cc
@@ -0,0 +1,47 @@ +// Copyright 2024 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ash/picker/picker_insert_media.h" + +#include "ash/picker/picker_rich_media.h" +#include "base/functional/overloaded.h" +#include "base/strings/utf_string_conversions.h" +#include "ui/base/ime/text_input_client.h" +#include "url/gurl.h" + +namespace ash { + +bool InsertMediaToInputField(PickerRichMedia media, + ui::TextInputClient* client) { + if (client == nullptr) { + return false; + } + + return std::visit( + base::Overloaded{ + [client](PickerTextMedia media) { + client->InsertText(media.text, + ui::TextInputClient::InsertTextCursorBehavior:: + kMoveCursorAfterText); + return true; + }, + [client](PickerImageMedia media) { + if (!client->CanInsertImage()) { + return false; + } + client->InsertImage(media.url); + return true; + }, + [client](PickerLinkMedia media) { + // TODO(b/322729192): Insert a real hyperlink. + client->InsertText(base::UTF8ToUTF16(media.url.spec()), + ui::TextInputClient::InsertTextCursorBehavior:: + kMoveCursorAfterText); + return true; + }, + }, + std::move(media)); +} + +} // namespace ash
diff --git a/ash/picker/picker_insert_media.h b/ash/picker/picker_insert_media.h new file mode 100644 index 0000000..c98ed45 --- /dev/null +++ b/ash/picker/picker_insert_media.h
@@ -0,0 +1,25 @@ +// Copyright 2024 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef ASH_PICKER_PICKER_INSERT_MEDIA_H_ +#define ASH_PICKER_PICKER_INSERT_MEDIA_H_ + +#include "ash/ash_export.h" +#include "ash/picker/picker_rich_media.h" + +namespace ui { +class TextInputClient; +} // namespace ui + +namespace ash { + +// Inserts `media` into `client`. +// Returns whether the insertion was successful. +[[nodiscard]] ASH_EXPORT bool InsertMediaToInputField( + PickerRichMedia media, + ui::TextInputClient* client); + +} // namespace ash + +#endif // ASH_PICKER_PICKER_INSERT_MEDIA_REQUEST_H_
diff --git a/ash/picker/picker_insert_media_request.cc b/ash/picker/picker_insert_media_request.cc index 098d04f..405b9917 100644 --- a/ash/picker/picker_insert_media_request.cc +++ b/ash/picker/picker_insert_media_request.cc
@@ -4,6 +4,8 @@ #include "ash/picker/picker_insert_media_request.h" +#include "ash/picker/picker_insert_media.h" +#include "ash/picker/picker_rich_media.h" #include "base/strings/utf_string_conversions.h" #include "base/time/time.h" #include "ui/base/ime/input_method.h" @@ -12,64 +14,12 @@ namespace ash { -PickerInsertMediaRequest::MediaData PickerInsertMediaRequest::MediaData::Text( - std::u16string_view text) { - return MediaData(MediaData::Type::kText, std::u16string(text)); -} - -PickerInsertMediaRequest::MediaData PickerInsertMediaRequest::MediaData::Text( - std::string_view text) { - return MediaData(MediaData::Type::kText, base::UTF8ToUTF16(text)); -} - -PickerInsertMediaRequest::MediaData PickerInsertMediaRequest::MediaData::Image( - const GURL& url) { - return MediaData(MediaData::Type::kImage, url); -} - -PickerInsertMediaRequest::MediaData PickerInsertMediaRequest::MediaData::Link( - const GURL& url) { - return MediaData(MediaData::Type::kLink, url); -} - -PickerInsertMediaRequest::MediaData::MediaData(const MediaData&) = default; - -PickerInsertMediaRequest::MediaData& -PickerInsertMediaRequest::MediaData::operator=(const MediaData&) = default; - -PickerInsertMediaRequest::MediaData::~MediaData() = default; - -bool PickerInsertMediaRequest::MediaData::Insert(ui::TextInputClient* client) { - switch (type_) { - case MediaData::Type::kText: - client->InsertText( - std::get<std::u16string>(data_), - ui::TextInputClient::InsertTextCursorBehavior::kMoveCursorAfterText); - return true; - case MediaData::Type::kImage: - if (!client->CanInsertImage()) { - return false; - } - client->InsertImage(std::get<GURL>(data_)); - return true; - case MediaData::Type::kLink: - // TODO(b/322729192): Insert a real hyperlink. - client->InsertText( - base::UTF8ToUTF16(std::get<GURL>(data_).spec()), - ui::TextInputClient::InsertTextCursorBehavior::kMoveCursorAfterText); - return true; - } -} - -PickerInsertMediaRequest::MediaData::MediaData(Type type, Data data) - : type_(type), data_(std::move(data)) {} - PickerInsertMediaRequest::PickerInsertMediaRequest( ui::InputMethod* input_method, - const MediaData& data_to_insert, + const PickerRichMedia& media_to_insert, const base::TimeDelta insert_timeout, InsertFailedCallback insert_failed_callback) - : data_to_insert(data_to_insert), + : media_to_insert_(media_to_insert), insert_failed_callback_(std::move(insert_failed_callback)) { observation_.Observe(input_method); insert_timeout_timer_.Start(FROM_HERE, insert_timeout, this, @@ -85,18 +35,18 @@ if (mutable_client == nullptr || mutable_client->GetTextInputType() == ui::TextInputType::TEXT_INPUT_TYPE_NONE || - !data_to_insert.has_value()) { + !media_to_insert_.has_value()) { return; } DCHECK_EQ(mutable_client, client); - if (!data_to_insert->Insert(mutable_client)) { + if (!InsertMediaToInputField(*media_to_insert_, mutable_client)) { if (!insert_failed_callback_.is_null()) { std::move(insert_failed_callback_).Run(); } } - data_to_insert = std::nullopt; + media_to_insert_ = std::nullopt; observation_.Reset(); } @@ -108,11 +58,13 @@ } void PickerInsertMediaRequest::CancelPendingInsert() { - data_to_insert = std::nullopt; observation_.Reset(); - if (!insert_failed_callback_.is_null()) { - std::move(insert_failed_callback_).Run(); + if (media_to_insert_.has_value()) { + media_to_insert_ = std::nullopt; + if (!insert_failed_callback_.is_null()) { + std::move(insert_failed_callback_).Run(); + } } }
diff --git a/ash/picker/picker_insert_media_request.h b/ash/picker/picker_insert_media_request.h index 2f01e62..05ab8198 100644 --- a/ash/picker/picker_insert_media_request.h +++ b/ash/picker/picker_insert_media_request.h
@@ -9,6 +9,7 @@ #include <variant> #include "ash/ash_export.h" +#include "ash/picker/picker_rich_media.h" #include "base/functional/callback_forward.h" #include "base/scoped_observation.h" #include "base/time/time.h" @@ -26,32 +27,6 @@ // Inserts rich media such as text and images into an input field. class ASH_EXPORT PickerInsertMediaRequest : public ui::InputMethodObserver { public: - class MediaData { - public: - static MediaData Text(std::u16string_view text); - static MediaData Text(std::string_view text); - static MediaData Image(const GURL& url); - static MediaData Link(const GURL& url); - - MediaData(const MediaData&); - MediaData& operator=(const MediaData&); - ~MediaData(); - - // Inserts this media data into `client`. - // Returns whether the insertion was successful. - [[nodiscard]] bool Insert(ui::TextInputClient* client); - - private: - enum class Type { kText, kImage, kLink }; - - using Data = std::variant<std::u16string, GURL>; - - explicit MediaData(Type type, Data data); - - Type type_; - Data data_; - }; - using InsertFailedCallback = base::OnceClosure; // Creates a request to insert `data` in the next focused input field. @@ -62,7 +37,7 @@ // not support inserting `data`, or no insertion happened before the timeout. explicit PickerInsertMediaRequest( ui::InputMethod* input_method, - const MediaData& data_to_insert, + const PickerRichMedia& media, base::TimeDelta insert_timeout, InsertFailedCallback insert_failed_callback = {}); ~PickerInsertMediaRequest() override; @@ -79,7 +54,7 @@ // Does nothing if the insertion has already happened. void CancelPendingInsert(); - std::optional<MediaData> data_to_insert; + std::optional<PickerRichMedia> media_to_insert_; base::ScopedObservation<ui::InputMethod, ui::InputMethodObserver> observation_{this}; base::OneShotTimer insert_timeout_timer_;
diff --git a/ash/picker/picker_insert_media_request_unittest.cc b/ash/picker/picker_insert_media_request_unittest.cc index d22afa7c..07ea01a 100644 --- a/ash/picker/picker_insert_media_request_unittest.cc +++ b/ash/picker/picker_insert_media_request_unittest.cc
@@ -21,8 +21,8 @@ constexpr base::TimeDelta kInsertionTimeout = base::Seconds(1); struct TestCase { - // The media data to insert. - PickerInsertMediaRequest::MediaData data_to_insert; + // The media to insert. + PickerRichMedia media_to_insert; // The expected text in the input field if the insertion was successful. std::u16string expected_text; @@ -47,18 +47,16 @@ PickerInsertMediaRequestTest, testing::Values( TestCase{ - .data_to_insert = - PickerInsertMediaRequest::MediaData::Text(u"hello"), + .media_to_insert = PickerTextMedia(u"hello"), .expected_text = u"hello", }, TestCase{ - .data_to_insert = PickerInsertMediaRequest::MediaData::Image( - GURL("http://foo.com/fake.jpg")), + .media_to_insert = PickerImageMedia(GURL("http://foo.com/fake.jpg"), + gfx::Size(10, 10)), .expected_image_url = GURL("http://foo.com/fake.jpg"), }, TestCase{ - .data_to_insert = PickerInsertMediaRequest::MediaData::Link( - GURL("http://foo.com")), + .media_to_insert = PickerLinkMedia(GURL("http://foo.com")), .expected_text = u"http://foo.com/", })); @@ -67,7 +65,7 @@ {.type = ui::TEXT_INPUT_TYPE_TEXT, .can_insert_image = true}); InputMethodAsh input_method(nullptr); - PickerInsertMediaRequest request(&input_method, GetParam().data_to_insert, + PickerInsertMediaRequest request(&input_method, GetParam().media_to_insert, /*insert_timeout=*/base::Seconds(1)); task_environment().FastForwardBy(base::Seconds(1)); @@ -79,7 +77,7 @@ {.type = ui::TEXT_INPUT_TYPE_TEXT, .can_insert_image = true}); InputMethodAsh input_method(nullptr); - PickerInsertMediaRequest request(&input_method, GetParam().data_to_insert, + PickerInsertMediaRequest request(&input_method, GetParam().media_to_insert, kInsertionTimeout); input_method.SetFocusedTextInputClient(&client); @@ -93,7 +91,7 @@ {.type = ui::TEXT_INPUT_TYPE_TEXT, .can_insert_image = true}); InputMethodAsh input_method(nullptr); - PickerInsertMediaRequest request(&input_method, GetParam().data_to_insert, + PickerInsertMediaRequest request(&input_method, GetParam().media_to_insert, /*insert_timeout=*/base::Seconds(1)); task_environment().FastForwardBy(base::Milliseconds(999)); input_method.SetFocusedTextInputClient(&client); @@ -107,7 +105,7 @@ {.type = ui::TEXT_INPUT_TYPE_TEXT, .can_insert_image = true}); InputMethodAsh input_method(nullptr); - PickerInsertMediaRequest request(&input_method, GetParam().data_to_insert, + PickerInsertMediaRequest request(&input_method, GetParam().media_to_insert, /*insert_timeout=*/base::Seconds(1)); task_environment().FastForwardBy(base::Seconds(1)); input_method.SetFocusedTextInputClient(&client); @@ -123,7 +121,7 @@ InputMethodAsh input_method(nullptr); input_method.SetFocusedTextInputClient(&prev_client); - PickerInsertMediaRequest request(&input_method, GetParam().data_to_insert, + PickerInsertMediaRequest request(&input_method, GetParam().media_to_insert, kInsertionTimeout); input_method.SetFocusedTextInputClient(&next_client); @@ -142,7 +140,7 @@ InputMethodAsh input_method(nullptr); input_method.SetFocusedTextInputClient(&prev_client); - PickerInsertMediaRequest request(&input_method, GetParam().data_to_insert, + PickerInsertMediaRequest request(&input_method, GetParam().media_to_insert, /*insert_timeout=*/base::Seconds(1)); task_environment().FastForwardBy(base::Milliseconds(999)); input_method.SetFocusedTextInputClient(&next_client); @@ -162,7 +160,7 @@ InputMethodAsh input_method(nullptr); input_method.SetFocusedTextInputClient(&prev_client); - PickerInsertMediaRequest request(&input_method, GetParam().data_to_insert, + PickerInsertMediaRequest request(&input_method, GetParam().media_to_insert, /*insert_timeout=*/base::Seconds(1)); task_environment().FastForwardBy(base::Seconds(1)); input_method.SetFocusedTextInputClient(&next_client); @@ -177,7 +175,7 @@ InputMethodAsh input_method(nullptr); { - PickerInsertMediaRequest request(&input_method, GetParam().data_to_insert, + PickerInsertMediaRequest request(&input_method, GetParam().media_to_insert, kInsertionTimeout); } input_method.SetFocusedTextInputClient(&client); @@ -191,7 +189,7 @@ {.type = ui::TEXT_INPUT_TYPE_TEXT, .can_insert_image = true}); InputMethodAsh input_method(nullptr); - PickerInsertMediaRequest request(&input_method, GetParam().data_to_insert, + PickerInsertMediaRequest request(&input_method, GetParam().media_to_insert, kInsertionTimeout); input_method.SetFocusedTextInputClient(&client_none); input_method.SetFocusedTextInputClient(&client_text); @@ -207,7 +205,7 @@ {.type = ui::TEXT_INPUT_TYPE_TEXT, .can_insert_image = true}); InputMethodAsh input_method(nullptr); - PickerInsertMediaRequest request(&input_method, GetParam().data_to_insert, + PickerInsertMediaRequest request(&input_method, GetParam().media_to_insert, kInsertionTimeout); input_method.SetFocusedTextInputClient(&client1); input_method.SetFocusedTextInputClient(&client2); @@ -221,7 +219,7 @@ {.type = ui::TEXT_INPUT_TYPE_TEXT, .can_insert_image = true}); InputMethodAsh input_method(nullptr); - PickerInsertMediaRequest request(&input_method, GetParam().data_to_insert, + PickerInsertMediaRequest request(&input_method, GetParam().media_to_insert, /*insert_timeout=*/base::Seconds(1)); input_method.SetFocusedTextInputClient(&client); task_environment().FastForwardBy(base::Seconds(1)); @@ -236,7 +234,7 @@ InputMethodAsh input_method(nullptr); { - PickerInsertMediaRequest request(&input_method, GetParam().data_to_insert, + PickerInsertMediaRequest request(&input_method, GetParam().media_to_insert, kInsertionTimeout); input_method.SetFocusedTextInputClient(&client); } @@ -251,7 +249,7 @@ auto old_input_method = std::make_unique<InputMethodAsh>(nullptr); PickerInsertMediaRequest request( - old_input_method.get(), GetParam().data_to_insert, kInsertionTimeout); + old_input_method.get(), GetParam().media_to_insert, kInsertionTimeout); old_input_method.reset(); InputMethodAsh new_input_method(nullptr); new_input_method.SetFocusedTextInputClient(&client); @@ -259,11 +257,27 @@ EXPECT_EQ(client.text(), u""); } +TEST_P(PickerInsertMediaRequestTest, DoesNotCallCallbackOnSuccess) { + InputMethodAsh input_method(nullptr); + ui::FakeTextInputClient client( + &input_method, + {.type = ui::TEXT_INPUT_TYPE_TEXT, .can_insert_image = true}); + + base::test::TestFuture<void> failure_future; + PickerInsertMediaRequest request(&input_method, GetParam().media_to_insert, + /*insert_timeout=*/base::Seconds(1), + failure_future.GetCallback()); + client.Focus(); + task_environment().FastForwardBy(base::Seconds(1)); + + EXPECT_FALSE(failure_future.IsReady()); +} + TEST_P(PickerInsertMediaRequestTest, CallsFailureCallbackOnTimeout) { InputMethodAsh input_method(nullptr); base::test::TestFuture<void> failure_future; - PickerInsertMediaRequest request(&input_method, GetParam().data_to_insert, + PickerInsertMediaRequest request(&input_method, GetParam().media_to_insert, /*insert_timeout=*/base::Seconds(1), failure_future.GetCallback()); task_environment().FastForwardBy(base::Seconds(1)); @@ -281,7 +295,7 @@ base::test::TestFuture<void> failure_future; PickerInsertMediaRequest request( &input_method, - PickerInsertMediaRequest::MediaData::Image(GURL("http://foo.com")), + PickerImageMedia(GURL("http://foo.com"), gfx::Size(10, 10)), /*insert_timeout=*/base::Seconds(1), failure_future.GetCallback()); input_method.SetFocusedTextInputClient(&client);
diff --git a/ash/picker/picker_insert_media_unittest.cc b/ash/picker/picker_insert_media_unittest.cc new file mode 100644 index 0000000..6ebf360 --- /dev/null +++ b/ash/picker/picker_insert_media_unittest.cc
@@ -0,0 +1,72 @@ +// Copyright 2024 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ash/picker/picker_insert_media.h" + +#include <optional> +#include <string> + +#include "ash/picker/picker_rich_media.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/base/ime/fake_text_input_client.h" + +namespace ash { +namespace { + +struct TestCase { + // The media to insert. + PickerRichMedia media_to_insert; + + // The expected text in the input field if the insertion was successful. + std::u16string expected_text; + + // The expected image in the input field if the insertion was successful. + std::optional<GURL> expected_image_url; +}; + +using PickerInsertMediaTest = testing::TestWithParam<TestCase>; + +TEST_P(PickerInsertMediaTest, ReturnsFalseForNullClient) { + EXPECT_FALSE(InsertMediaToInputField(GetParam().media_to_insert, + /*client=*/nullptr)); +} + +TEST_P(PickerInsertMediaTest, InsertsOnNextFocusWhileFocused) { + ui::FakeTextInputClient client( + {.type = ui::TEXT_INPUT_TYPE_TEXT, .can_insert_image = true}); + + EXPECT_TRUE(InsertMediaToInputField(GetParam().media_to_insert, &client)); + EXPECT_EQ(client.text(), GetParam().expected_text); + EXPECT_EQ(client.last_inserted_image_url(), GetParam().expected_image_url); +} + +INSTANTIATE_TEST_SUITE_P( + , + PickerInsertMediaTest, + testing::Values( + TestCase{ + .media_to_insert = PickerTextMedia(u"hello"), + .expected_text = u"hello", + }, + TestCase{ + .media_to_insert = PickerImageMedia(GURL("http://foo.com/fake.jpg"), + gfx::Size(10, 10)), + .expected_image_url = GURL("http://foo.com/fake.jpg"), + }, + TestCase{ + .media_to_insert = PickerLinkMedia(GURL("http://foo.com")), + .expected_text = u"http://foo.com/", + })); + +TEST(PickerInsertMediaUnsupportedTest, InsertingUnsupportedImageReturnsFalse) { + ui::FakeTextInputClient client( + {.type = ui::TEXT_INPUT_TYPE_TEXT, .can_insert_image = false}); + + EXPECT_FALSE(InsertMediaToInputField( + PickerImageMedia(GURL("http://foo.com"), gfx::Size(10, 10)), &client)); + EXPECT_EQ(client.last_inserted_image_url(), std::nullopt); +} + +} // namespace +} // namespace ash
diff --git a/ash/picker/picker_rich_media.cc b/ash/picker/picker_rich_media.cc new file mode 100644 index 0000000..3d091aab --- /dev/null +++ b/ash/picker/picker_rich_media.cc
@@ -0,0 +1,29 @@ +// Copyright 2024 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ash/picker/picker_rich_media.h" + +#include <memory> +#include <string> + +#include "base/strings/utf_string_conversions.h" +#include "url/gurl.h" + +namespace ash { + +PickerTextMedia::PickerTextMedia(std::u16string text) : text(std::move(text)) {} + +PickerTextMedia::PickerTextMedia(std::string_view text) + : PickerTextMedia(base::UTF8ToUTF16(text)) {} + +PickerImageMedia::PickerImageMedia(GURL url, + std::optional<gfx::Size> dimensions, + std::u16string content_description) + : url(std::move(url)), + dimensions(dimensions), + content_description(std::move(content_description)) {} + +PickerLinkMedia::PickerLinkMedia(GURL url) : url(std::move(url)) {} + +} // namespace ash
diff --git a/ash/picker/picker_rich_media.h b/ash/picker/picker_rich_media.h new file mode 100644 index 0000000..d9c99262 --- /dev/null +++ b/ash/picker/picker_rich_media.h
@@ -0,0 +1,48 @@ +// Copyright 2024 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef ASH_PICKER_PICKER_RICH_MEDIA_H_ +#define ASH_PICKER_PICKER_RICH_MEDIA_H_ + +#include <string> +#include <string_view> +#include <variant> + +#include "ash/ash_export.h" +#include "ui/gfx/geometry/size.h" +#include "url/gurl.h" + +namespace ash { + +struct ASH_EXPORT PickerTextMedia { + std::u16string text; + + explicit PickerTextMedia(std::u16string text); + explicit PickerTextMedia(std::string_view text); +}; + +struct ASH_EXPORT PickerImageMedia { + GURL url; + // `dimensions` is std::nullopt if it's unknown. + std::optional<gfx::Size> dimensions; + std::u16string content_description; + + explicit PickerImageMedia(GURL url, + std::optional<gfx::Size> dimensions = std::nullopt, + std::u16string content_description = u""); +}; + +struct ASH_EXPORT PickerLinkMedia { + GURL url; + + explicit PickerLinkMedia(GURL url); +}; + +// Rich media that can be inserted or copied, such as text and images. +using PickerRichMedia = + std::variant<PickerTextMedia, PickerImageMedia, PickerLinkMedia>; + +} // namespace ash + +#endif // ASH_PICKER_PICKER_INSERT_MEDIA_REQUEST_H_
diff --git a/ash/picker/picker_search_controller.cc b/ash/picker/picker_search_controller.cc index 84a1d86..8bac265 100644 --- a/ash/picker/picker_search_controller.cc +++ b/ash/picker/picker_search_controller.cc
@@ -47,8 +47,8 @@ constexpr int kMaxSymbolResults = 2; constexpr int kMaxEmoticonResults = 2; -base::span<const std::string> FirstNOrLessElements( - base::span<const std::string> container, +base::span<const emoji::EmojiSearchEntry> FirstNOrLessElements( + base::span<const emoji::EmojiSearchEntry> container, size_t n) { return container.subspan(0, std::min(container.size(), n)); } @@ -87,7 +87,7 @@ category == PickerCategory::kOpenTabs)) { cros_search_start_ = base::TimeTicks::Now(); client_->StartCrosSearch( - query, + query, category, base::BindRepeating(&PickerSearchController::HandleCrosSearchResults, weak_ptr_factory_.GetWeakPtr())); } @@ -179,6 +179,10 @@ sections.emplace_back(PickerSectionType::kFiles, std::move(local_file_results_)); } + if (!drive_file_results_.empty()) { + sections.emplace_back(PickerSectionType::kDriveFiles, + std::move(drive_file_results_)); + } if (!gif_results_.empty()) { sections.push_back(PickerSearchResultsSection(PickerSectionType::kGifs, std::move(gif_results_))); @@ -230,6 +234,24 @@ PickerSectionType::kLinks, std::move(omnibox_results_))); } break; + case AppListSearchResultType::kDriveSearch: { + if (cros_search_start_.has_value()) { + base::TimeDelta elapsed = base::TimeTicks::Now() - *cros_search_start_; + base::UmaHistogramTimes("Ash.Picker.Search.DriveProvider.QueryTime", + elapsed); + } + drive_file_results_ = std::move(results); + size_t files_to_remove = + std::max<size_t>(drive_file_results_.size(), 3) - 3; + drive_file_results_.erase(drive_file_results_.end() - files_to_remove, + drive_file_results_.end()); + + if (IsPostBurnIn()) { + AppendPostBurnInResults(PickerSearchResultsSection( + PickerSectionType::kDriveFiles, std::move(drive_file_results_))); + } + break; + } case AppListSearchResultType::kFileSearch: { if (cros_search_start_.has_value()) { base::TimeDelta elapsed = base::TimeTicks::Now() - *cros_search_start_; @@ -289,20 +311,20 @@ emoji_results_.reserve(kMaxEmojiResults + kMaxSymbolResults + kMaxEmoticonResults); - for (const std::string& result : + for (const emoji::EmojiSearchEntry& result : FirstNOrLessElements(results.emojis, kMaxEmojiResults)) { emoji_results_.push_back( - PickerSearchResult::Emoji(base::UTF8ToUTF16(result))); + PickerSearchResult::Emoji(base::UTF8ToUTF16(result.emoji_string))); } - for (const std::string& result : + for (const emoji::EmojiSearchEntry& result : FirstNOrLessElements(results.symbols, kMaxSymbolResults)) { emoji_results_.push_back( - PickerSearchResult::Symbol(base::UTF8ToUTF16(result))); + PickerSearchResult::Symbol(base::UTF8ToUTF16(result.emoji_string))); } - for (const std::string& result : + for (const emoji::EmojiSearchEntry& result : FirstNOrLessElements(results.emoticons, kMaxEmoticonResults)) { emoji_results_.push_back( - PickerSearchResult::Emoticon(base::UTF8ToUTF16(result))); + PickerSearchResult::Emoticon(base::UTF8ToUTF16(result.emoji_string))); } }
diff --git a/ash/picker/picker_search_controller.h b/ash/picker/picker_search_controller.h index d97dfbc..dd54f3e 100644 --- a/ash/picker/picker_search_controller.h +++ b/ash/picker/picker_search_controller.h
@@ -94,6 +94,7 @@ std::vector<PickerSearchResult> gif_results_; std::vector<PickerSearchResult> emoji_results_; std::vector<PickerSearchResult> local_file_results_; + std::vector<PickerSearchResult> drive_file_results_; PickerSearchDebouncer gif_search_debouncer_;
diff --git a/ash/picker/picker_search_controller_unittest.cc b/ash/picker/picker_search_controller_unittest.cc index 8c8377a..841449b 100644 --- a/ash/picker/picker_search_controller_unittest.cc +++ b/ash/picker/picker_search_controller_unittest.cc
@@ -79,7 +79,7 @@ // Set default behaviours. These can be overridden with `WillOnce` and // `WillRepeatedly`. ON_CALL(*this, StartCrosSearch) - .WillByDefault(SaveArg<1>(cros_search_callback())); + .WillByDefault(SaveArg<2>(cros_search_callback())); ON_CALL(*this, FetchGifSearch) .WillByDefault( Invoke(this, &MockPickerClient::FetchGifSearchToSetCallback)); @@ -105,7 +105,9 @@ MOCK_METHOD(void, StopGifSearch, (), (override)); MOCK_METHOD(void, StartCrosSearch, - (const std::u16string& query, CrosSearchResultsCallback callback), + (const std::u16string& query, + std::optional<PickerCategory> category, + CrosSearchResultsCallback callback), (override)); MOCK_METHOD(void, StopCrosQuery, (), (override)); @@ -167,7 +169,7 @@ NiceMock<MockPickerClient> client; PickerSearchController controller(&client, kAllCategories, kBurnInPeriod); NiceMock<MockSearchResultsCallback> search_results_callback; - EXPECT_CALL(client, StartCrosSearch(Eq(u"cat"), _)).Times(1); + EXPECT_CALL(client, StartCrosSearch(Eq(u"cat"), _, _)).Times(1); controller.StartSearch( u"cat", std::nullopt, @@ -252,6 +254,7 @@ ON_CALL(client, StartCrosSearch) .WillByDefault([&search_started, &client]( const std::u16string& query, + std::optional<PickerCategory> category, PickerClient::CrosSearchResultsCallback callback) { client.StopCrosQuery(); search_started = true; @@ -360,6 +363,7 @@ .Times(2) .WillRepeatedly([&search_started, &client]( const std::u16string& query, + std::optional<PickerCategory> category, PickerClient::CrosSearchResultsCallback callback) { client.StopCrosQuery(); search_started = true; @@ -379,7 +383,8 @@ histogram.ExpectTotalCount("Ash.Picker.Search.OmniboxProvider.QueryTime", 0); } -TEST_F(PickerSearchControllerTest, DoesNotRecordOmniboxMetricsIfFileResponse) { +TEST_F(PickerSearchControllerTest, + DoesNotRecordOmniboxMetricsIfOtherCrosSearchResponse) { base::HistogramTester histogram; NiceMock<MockPickerClient> client; PickerSearchController controller(&client, kAllCategories, kBurnInPeriod); @@ -398,6 +403,7 @@ .Times(2) .WillRepeatedly([&search_started, &client]( const std::u16string& query, + std::optional<PickerCategory> category, PickerClient::CrosSearchResultsCallback callback) { client.StopCrosQuery(); search_started = true; @@ -504,6 +510,7 @@ .Times(2) .WillRepeatedly([&search_started, &client]( const std::u16string& query, + std::optional<PickerCategory> category, PickerClient::CrosSearchResultsCallback callback) { client.StopCrosQuery(); search_started = true; @@ -523,7 +530,8 @@ histogram.ExpectTotalCount("Ash.Picker.Search.FileProvider.QueryTime", 0); } -TEST_F(PickerSearchControllerTest, DoesNotRecordFileMetricsIfOmniboxResponse) { +TEST_F(PickerSearchControllerTest, + DoesNotRecordFileMetricsIfOtherCrosSearchResponse) { base::HistogramTester histogram; NiceMock<MockPickerClient> client; PickerSearchController controller(&client, kAllCategories, kBurnInPeriod); @@ -542,6 +550,7 @@ .Times(2) .WillRepeatedly([&search_started, &client]( const std::u16string& query, + std::optional<PickerCategory> category, PickerClient::CrosSearchResultsCallback callback) { client.StopCrosQuery(); search_started = true; @@ -566,6 +575,155 @@ histogram.ExpectTotalCount("Ash.Picker.Search.FileProvider.QueryTime", 0); } +TEST_F(PickerSearchControllerTest, ShowsResultsFromDriveSearch) { + NiceMock<MockPickerClient> client; + PickerSearchController controller(&client, kAllCategories, kBurnInPeriod); + MockSearchResultsCallback search_results_callback; + EXPECT_CALL(search_results_callback, Call).Times(AnyNumber()); + EXPECT_CALL(search_results_callback, + Call(Contains(AllOf( + Property("type", &PickerSearchResultsSection::type, + PickerSectionType::kDriveFiles), + Property("results", &PickerSearchResultsSection::results, + ElementsAre(Property( + "data", &PickerSearchResult::data, + VariantWith<PickerSearchResult::TextData>(Field( + "text", &PickerSearchResult::TextData::text, + u"catrbug_135117.jpg"))))))))) + .Times(AtLeast(1)); + + controller.StartSearch( + u"cat", std::nullopt, + base::BindRepeating(&MockSearchResultsCallback::Call, + base::Unretained(&search_results_callback))); + client.cros_search_callback()->Run( + ash::AppListSearchResultType::kDriveSearch, + {ash::PickerSearchResult::Text(u"catrbug_135117.jpg")}); + task_environment().FastForwardBy(kBurnInPeriod); +} + +TEST_F(PickerSearchControllerTest, RecordsDriveMetricsBeforeBurnIn) { + base::HistogramTester histogram; + NiceMock<MockPickerClient> client; + PickerSearchController controller(&client, kAllCategories, kBurnInPeriod); + MockSearchResultsCallback search_results_callback; + + controller.StartSearch( + u"cat", std::nullopt, + base::BindRepeating(&MockSearchResultsCallback::Call, + base::Unretained(&search_results_callback))); + task_environment().FastForwardBy(kBeforeBurnIn); + client.cros_search_callback()->Run( + ash::AppListSearchResultType::kDriveSearch, + {ash::PickerSearchResult::Text(u"catrbug_135117.jpg")}); + + histogram.ExpectUniqueTimeSample("Ash.Picker.Search.DriveProvider.QueryTime", + kBeforeBurnIn, 1); +} + +TEST_F(PickerSearchControllerTest, RecordsDriveMetricsAfterBurnIn) { + base::HistogramTester histogram; + NiceMock<MockPickerClient> client; + PickerSearchController controller(&client, kAllCategories, kBurnInPeriod); + MockSearchResultsCallback search_results_callback; + + controller.StartSearch( + u"cat", std::nullopt, + base::BindRepeating(&MockSearchResultsCallback::Call, + base::Unretained(&search_results_callback))); + task_environment().FastForwardBy(kAfterBurnIn); + client.cros_search_callback()->Run( + ash::AppListSearchResultType::kDriveSearch, + {ash::PickerSearchResult::Text(u"catrbug_135117.jpg")}); + + histogram.ExpectUniqueTimeSample("Ash.Picker.Search.DriveProvider.QueryTime", + kAfterBurnIn, 1); +} + +TEST_F(PickerSearchControllerTest, DoesNotRecordDriveMetricsIfNoFileResponse) { + base::HistogramTester histogram; + NiceMock<MockPickerClient> client; + PickerSearchController controller(&client, kAllCategories, kBurnInPeriod); + MockSearchResultsCallback search_results_callback; + bool search_started = false; + EXPECT_CALL(client, StopCrosQuery) + .Times(AtLeast(2)) + .WillRepeatedly([&search_started, &client]() { + if (search_started) { + client.cros_search_callback()->Run(AppListSearchResultType::kOmnibox, + {}); + } + search_started = false; + }); + EXPECT_CALL(client, StartCrosSearch) + .Times(2) + .WillRepeatedly([&search_started, &client]( + const std::u16string& query, + std::optional<PickerCategory> category, + PickerClient::CrosSearchResultsCallback callback) { + client.StopCrosQuery(); + search_started = true; + *client.cros_search_callback() = std::move(callback); + }); + + controller.StartSearch( + u"cat", std::nullopt, + base::BindRepeating(&MockSearchResultsCallback::Call, + base::Unretained(&search_results_callback))); + task_environment().FastForwardBy(kBeforeBurnIn); + controller.StartSearch( + u"dog", std::nullopt, + base::BindRepeating(&MockSearchResultsCallback::Call, + base::Unretained(&search_results_callback))); + + histogram.ExpectTotalCount("Ash.Picker.Search.DriveProvider.QueryTime", 0); +} + +TEST_F(PickerSearchControllerTest, + DoesNotRecordDriveMetricsIfOtherCrosSearchResponse) { + base::HistogramTester histogram; + NiceMock<MockPickerClient> client; + PickerSearchController controller(&client, kAllCategories, kBurnInPeriod); + MockSearchResultsCallback search_results_callback; + bool search_started = false; + EXPECT_CALL(client, StopCrosQuery) + .Times(AtLeast(2)) + .WillRepeatedly([&search_started, &client]() { + if (search_started) { + client.cros_search_callback()->Run(AppListSearchResultType::kOmnibox, + {}); + } + search_started = false; + }); + EXPECT_CALL(client, StartCrosSearch) + .Times(2) + .WillRepeatedly([&search_started, &client]( + const std::u16string& query, + std::optional<PickerCategory> category, + PickerClient::CrosSearchResultsCallback callback) { + client.StopCrosQuery(); + search_started = true; + *client.cros_search_callback() = std::move(callback); + }); + + controller.StartSearch( + u"cat", std::nullopt, + base::BindRepeating(&MockSearchResultsCallback::Call, + base::Unretained(&search_results_callback))); + task_environment().FastForwardBy(kBeforeBurnIn); + client.cros_search_callback()->Run( + ash::AppListSearchResultType::kOmnibox, + {ash::PickerSearchResult::BrowsingHistory( + GURL("https://www.google.com/search?q=cat"), u"cat - Google Search", + ui::ImageModel())}); + controller.StartSearch( + u"dog", std::nullopt, + base::BindRepeating(&MockSearchResultsCallback::Call, + base::Unretained(&search_results_callback))); + + histogram.ExpectTotalCount("Ash.Picker.Search.DriveProvider.QueryTime", 0); +} + TEST_F(PickerSearchControllerTest, DoesNotSendQueryToGifSearchImmediately) { NiceMock<MockPickerClient> client; PickerSearchController controller(&client, kAllCategories, kBurnInPeriod); @@ -606,9 +764,9 @@ Contains(Property( "data", &PickerSearchResult::data, VariantWith<PickerSearchResult::GifData>(AllOf( - Field("url", &PickerSearchResult::GifData::url, + Field("full_url", &PickerSearchResult::GifData::full_url, Property("spec", &GURL::spec, - "https://media.tenor.com/GOabrbLMl4AAAAAd/" + "https://media.tenor.com/GOabrbLMl4AAAAAC/" "plink-cat-plink.gif")), Field("content_description", &PickerSearchResult::GifData::content_description, @@ -625,6 +783,8 @@ .Run({ash::PickerSearchResult::Gif( GURL("https://media.tenor.com/GOabrbLMl4AAAAAd/plink-cat-plink.gif"), GURL("https://media.tenor.com/GOabrbLMl4AAAAAe/plink-cat-plink.png"), + gfx::Size(360, 360), + GURL("https://media.tenor.com/GOabrbLMl4AAAAAC/plink-cat-plink.gif"), gfx::Size(480, 480), u"cat blink")}); task_environment().FastForwardBy(kBurnInPeriod - PickerSearchController::kGifDebouncingDelay); @@ -646,9 +806,9 @@ Contains(Property( "data", &PickerSearchResult::data, VariantWith<PickerSearchResult::GifData>(AllOf( - Field("url", &PickerSearchResult::GifData::url, + Field("full_url", &PickerSearchResult::GifData::full_url, Property("spec", &GURL::spec, - "https://media.tenor.com/GOabrbLMl4AAAAAd/" + "https://media.tenor.com/GOabrbLMl4AAAAAC/" "plink-cat-plink.gif")), Field("content_description", &PickerSearchResult::GifData::content_description, @@ -687,10 +847,9 @@ Contains(Property( "data", &PickerSearchResult::data, VariantWith<PickerSearchResult::GifData>(AllOf( - Field("url", &PickerSearchResult::GifData::url, + Field("full_url", &PickerSearchResult::GifData::full_url, Property("spec", &GURL::spec, - "https://media.tenor.com/" - "GOabrbLMl4AAAAAd/" + "https://media.tenor.com/GOabrbLMl4AAAAAC/" "plink-cat-plink.gif")), Field("content_description", &PickerSearchResult::GifData::content_description, @@ -712,6 +871,8 @@ .Run({ash::PickerSearchResult::Gif( GURL("https://media.tenor.com/GOabrbLMl4AAAAAd/plink-cat-plink.gif"), GURL("https://media.tenor.com/GOabrbLMl4AAAAAe/plink-cat-plink.png"), + gfx::Size(360, 360), + GURL("https://media.tenor.com/GOabrbLMl4AAAAAC/plink-cat-plink.gif"), gfx::Size(480, 480), u"cat blink")}); task_environment().FastForwardBy(kBurnInPeriod - PickerSearchController::kGifDebouncingDelay); @@ -732,6 +893,8 @@ .Run({ash::PickerSearchResult::Gif( GURL("https://media.tenor.com/GOabrbLMl4AAAAAd/plink-cat-plink.gif"), GURL("https://media.tenor.com/GOabrbLMl4AAAAAe/plink-cat-plink.png"), + gfx::Size(360, 360), + GURL("https://media.tenor.com/GOabrbLMl4AAAAAC/plink-cat-plink.gif"), gfx::Size(480, 480), u"cat blink")}); histogram.ExpectUniqueTimeSample( @@ -754,6 +917,8 @@ .Run({ash::PickerSearchResult::Gif( GURL("https://media.tenor.com/GOabrbLMl4AAAAAd/plink-cat-plink.gif"), GURL("https://media.tenor.com/GOabrbLMl4AAAAAe/plink-cat-plink.png"), + gfx::Size(360, 360), + GURL("https://media.tenor.com/GOabrbLMl4AAAAAC/plink-cat-plink.gif"), gfx::Size(480, 480), u"cat blink")}); histogram.ExpectUniqueTimeSample( @@ -796,10 +961,11 @@ Contains(Property( "data", &PickerSearchResult::data, VariantWith<PickerSearchResult::GifData>(AllOf( - Field("url", &PickerSearchResult::GifData::url, + Field("full_url", + &PickerSearchResult::GifData::full_url, Property("spec", &GURL::spec, "https://media.tenor.com/" - "GOabrbLMl4AAAAAd/" + "GOabrbLMl4AAAAAC/" "plink-cat-plink.gif")), Field( "content_description", @@ -838,6 +1004,8 @@ .Run({ash::PickerSearchResult::Gif( GURL("https://media.tenor.com/GOabrbLMl4AAAAAd/plink-cat-plink.gif"), GURL("https://media.tenor.com/GOabrbLMl4AAAAAe/plink-cat-plink.png"), + gfx::Size(360, 360), + GURL("https://media.tenor.com/GOabrbLMl4AAAAAC/plink-cat-plink.gif"), gfx::Size(480, 480), u"cat blink")}); task_environment().FastForwardBy(kBurnInPeriod - PickerSearchController::kGifDebouncingDelay); @@ -866,6 +1034,8 @@ .Run({ash::PickerSearchResult::Gif( GURL("https://media.tenor.com/GOabrbLMl4AAAAAd/plink-cat-plink.gif"), GURL("https://media.tenor.com/GOabrbLMl4AAAAAe/plink-cat-plink.png"), + gfx::Size(360, 360), + GURL("https://media.tenor.com/GOabrbLMl4AAAAAC/plink-cat-plink.gif"), gfx::Size(480, 480), u"cat blink")}); task_environment().FastForwardBy(kBurnInPeriod); } @@ -893,6 +1063,8 @@ .Run({ash::PickerSearchResult::Gif( GURL("https://media.tenor.com/GOabrbLMl4AAAAAd/plink-cat-plink.gif"), GURL("https://media.tenor.com/GOabrbLMl4AAAAAe/plink-cat-plink.png"), + gfx::Size(360, 360), + GURL("https://media.tenor.com/GOabrbLMl4AAAAAC/plink-cat-plink.gif"), gfx::Size(480, 480), u"cat blink")}); } @@ -911,10 +1083,9 @@ Contains(Property( "data", &PickerSearchResult::data, VariantWith<PickerSearchResult::GifData>(AllOf( - Field("url", &PickerSearchResult::GifData::url, + Field("full_url", &PickerSearchResult::GifData::full_url, Property("spec", &GURL::spec, - "https://media.tenor.com/" - "GOabrbLMl4AAAAAd/" + "https://media.tenor.com/GOabrbLMl4AAAAAC/" "plink-cat-plink.gif")), Field("content_description", &PickerSearchResult::GifData::content_description, @@ -930,21 +1101,29 @@ .Run({ash::PickerSearchResult::Gif( GURL("https://media.tenor.com/GOabrbLMl4AAAAAd/plink-cat-plink.gif"), GURL("https://media.tenor.com/GOabrbLMl4AAAAAe/plink-cat-plink.png"), + gfx::Size(360, 360), + GURL("https://media.tenor.com/GOabrbLMl4AAAAAC/plink-cat-plink.gif"), gfx::Size(480, 480), u"cat blink")}); } TEST_F(PickerSearchControllerTest, OnlyStartCrosSearchForCertainCategories) { NiceMock<MockPickerClient> client; PickerSearchController controller(&client, kAllCategories, kBurnInPeriod); - EXPECT_CALL(client, StartCrosSearch(Eq(u"ant"), _)).Times(1); - EXPECT_CALL(client, StartCrosSearch(Eq(u"bat"), _)).Times(1); - EXPECT_CALL(client, StartCrosSearch(Eq(u"cat"), _)).Times(1); + EXPECT_CALL(client, + StartCrosSearch(Eq(u"ant"), Eq(PickerCategory::kBookmarks), _)) + .Times(1); + EXPECT_CALL(client, StartCrosSearch(Eq(u"bat"), + Eq(PickerCategory::kBrowsingHistory), _)) + .Times(1); + EXPECT_CALL(client, + StartCrosSearch(Eq(u"cat"), Eq(PickerCategory::kOpenTabs), _)) + .Times(1); EXPECT_CALL(client, FetchGifSearch(_, _)).Times(0); - controller.StartSearch(u"cat", PickerCategory::kBookmarks, base::DoNothing()); - controller.StartSearch(u"ant", PickerCategory::kBrowsingHistory, + controller.StartSearch(u"ant", PickerCategory::kBookmarks, base::DoNothing()); + controller.StartSearch(u"bat", PickerCategory::kBrowsingHistory, base::DoNothing()); - controller.StartSearch(u"bat", PickerCategory::kOpenTabs, base::DoNothing()); + controller.StartSearch(u"cat", PickerCategory::kOpenTabs, base::DoNothing()); } } // namespace
diff --git a/ash/picker/picker_test_util.cc b/ash/picker/picker_test_util.cc index 6aa41fc2..c3c5077 100644 --- a/ash/picker/picker_test_util.cc +++ b/ash/picker/picker_test_util.cc
@@ -14,6 +14,13 @@ namespace ash { +std::u16string ReadTextFromClipboard(ui::Clipboard* clipboard) { + std::u16string data; + + clipboard->ReadText(ui::ClipboardBuffer::kCopyPaste, nullptr, &data); + return data; +} + std::u16string ReadHtmlFromClipboard(ui::Clipboard* clipboard) { std::u16string data; std::string url;
diff --git a/ash/picker/picker_test_util.h b/ash/picker/picker_test_util.h index 01267f5..977ac0f 100644 --- a/ash/picker/picker_test_util.h +++ b/ash/picker/picker_test_util.h
@@ -22,6 +22,9 @@ namespace ash { +// Returns the text contents of `clipboard`. +ASH_EXPORT std::u16string ReadTextFromClipboard(ui::Clipboard* clipboard); + // Returns the HTML contents of `clipboard`. ASH_EXPORT std::u16string ReadHtmlFromClipboard(ui::Clipboard* clipboard);
diff --git a/ash/picker/search/picker_category_search.cc b/ash/picker/search/picker_category_search.cc index 784399d..242b283 100644 --- a/ash/picker/search/picker_category_search.cc +++ b/ash/picker/search/picker_category_search.cc
@@ -4,6 +4,7 @@ #include "ash/picker/search/picker_category_search.h" +#include <string> #include <string_view> #include <vector> @@ -11,29 +12,26 @@ #include "ash/public/cpp/picker/picker_category.h" #include "ash/public/cpp/picker/picker_search_result.h" #include "base/check.h" -#include "base/ranges/algorithm.h" -#include "base/strings/string_util.h" +#include "chromeos/ash/components/string_matching/prefix_matcher.h" +#include "chromeos/ash/components/string_matching/tokenized_string.h" namespace ash { -namespace { - -bool ContainsCaseInsensitive(std::u16string_view haystack, - std::u16string_view needle) { - return base::ranges::search(haystack, needle, - base::CaseInsensitiveCompareASCII<char>()) != - haystack.end(); -} - -} // namespace std::vector<PickerSearchResult> PickerCategorySearch( base::span<const PickerCategory> categories, std::u16string_view query) { CHECK(!query.empty()); + string_matching::TokenizedString tokenized_query((std::u16string(query))); std::vector<PickerSearchResult> matches; for (const PickerCategory category : categories) { - if (ContainsCaseInsensitive(GetLabelForPickerCategory(category), query)) { + string_matching::TokenizedString tokenized_category( + GetLabelForPickerCategory(category)); + // Both arguments are stored as `raw_ref`s in the `PrefixMatcher` below, so + // they need to outlive the matcher. + string_matching::PrefixMatcher matcher(tokenized_query, tokenized_category); + // TODO: b/325973235 - Use `matcher.relevance()` to sort these results. + if (matcher.Match()) { matches.push_back(PickerSearchResult::Category(category)); } }
diff --git a/ash/picker/search/picker_category_search_unittest.cc b/ash/picker/search/picker_category_search_unittest.cc index 9fe3d49..3f4de066 100644 --- a/ash/picker/search/picker_category_search_unittest.cc +++ b/ash/picker/search/picker_category_search_unittest.cc
@@ -68,11 +68,17 @@ .query = u"e", .expected_categories = {PickerCategory::kEmojis}, }, + // Prefix match in second word + TestCase{ + .available_categories = {PickerCategory::kOpenTabs}, + .query = u"ta", + .expected_categories = {PickerCategory::kOpenTabs}, + }, // Substring match TestCase{ .available_categories = {PickerCategory::kEmojis}, .query = u"moj", - .expected_categories = {PickerCategory::kEmojis}, + .expected_categories = {}, }, // Category unavailable TestCase{
diff --git a/ash/picker/views/picker_category_view.cc b/ash/picker/views/picker_category_view.cc index c8bd01b..a0a7c96 100644 --- a/ash/picker/views/picker_category_view.cc +++ b/ash/picker/views/picker_category_view.cc
@@ -31,8 +31,24 @@ PickerCategoryView::~PickerCategoryView() = default; -bool PickerCategoryView::OnEnterKeyPressed() { - return search_results_view_->OnEnterKeyPressed(); +bool PickerCategoryView::DoPseudoFocusedAction() { + return search_results_view_->DoPseudoFocusedAction(); +} + +bool PickerCategoryView::MovePseudoFocusUp() { + return search_results_view_->MovePseudoFocusUp(); +} + +bool PickerCategoryView::MovePseudoFocusDown() { + return search_results_view_->MovePseudoFocusDown(); +} + +bool PickerCategoryView::MovePseudoFocusLeft() { + return search_results_view_->MovePseudoFocusLeft(); +} + +bool PickerCategoryView::MovePseudoFocusRight() { + return search_results_view_->MovePseudoFocusRight(); } void PickerCategoryView::SetResults(
diff --git a/ash/picker/views/picker_category_view.h b/ash/picker/views/picker_category_view.h index 26345a8..c1fcfeb 100644 --- a/ash/picker/views/picker_category_view.h +++ b/ash/picker/views/picker_category_view.h
@@ -30,7 +30,11 @@ ~PickerCategoryView() override; // PickerPageView: - bool OnEnterKeyPressed() override; + bool DoPseudoFocusedAction() override; + bool MovePseudoFocusUp() override; + bool MovePseudoFocusDown() override; + bool MovePseudoFocusLeft() override; + bool MovePseudoFocusRight() override; // Replaces the current results with `sections`. void SetResults(std::vector<PickerSearchResultsSection> sections);
diff --git a/ash/picker/views/picker_emoji_item_view.cc b/ash/picker/views/picker_emoji_item_view.cc index 24114cc..658340d 100644 --- a/ash/picker/views/picker_emoji_item_view.cc +++ b/ash/picker/views/picker_emoji_item_view.cc
@@ -9,11 +9,9 @@ #include "ash/ash_element_identifiers.h" #include "ash/picker/views/picker_item_view.h" -#include "ash/style/style_util.h" #include "ui/base/metadata/metadata_impl_macros.h" #include "ui/chromeos/styles/cros_tokens_color_mappings.h" #include "ui/gfx/font_list.h" -#include "ui/gfx/geometry/rounded_corners_f.h" #include "ui/views/controls/label.h" #include "ui/views/view_class_properties.h" @@ -26,7 +24,7 @@ kPickerEmojiFontSize, gfx::Font::Weight::NORMAL); -constexpr auto kPickerEmojiItemCornerRadius = gfx::RoundedCornersF(4); +constexpr int kPickerEmojiItemCornerRadius = 4; } // namespace @@ -35,6 +33,7 @@ const std::u16string& emoji) : PickerItemView(std::move(select_item_callback)) { SetUseDefaultFillLayout(true); + SetCornerRadius(kPickerEmojiItemCornerRadius); SetProperty(views::kElementIdentifierKey, kPickerSearchResultsEmojiItemElementId); @@ -43,9 +42,6 @@ .SetFontList(kPickerEmojiFont) .Build()); SetAccessibleName(emoji_label_); - - StyleUtil::InstallRoundedCornerHighlightPathGenerator( - this, kPickerEmojiItemCornerRadius); } std::u16string_view PickerEmojiItemView::GetTextForTesting() const {
diff --git a/ash/picker/views/picker_emoticon_item_view.cc b/ash/picker/views/picker_emoticon_item_view.cc index 32c03a3..06d1d8a7 100644 --- a/ash/picker/views/picker_emoticon_item_view.cc +++ b/ash/picker/views/picker_emoticon_item_view.cc
@@ -8,12 +8,10 @@ #include <utility> #include "ash/picker/views/picker_item_view.h" -#include "ash/style/style_util.h" #include "ui/base/metadata/metadata_impl_macros.h" #include "ui/chromeos/styles/cros_tokens_color_mappings.h" #include "ui/gfx/font_list.h" #include "ui/gfx/geometry/insets.h" -#include "ui/gfx/geometry/rounded_corners_f.h" #include "ui/views/border.h" #include "ui/views/controls/label.h" @@ -28,7 +26,7 @@ constexpr auto kPickerEmoticonItemMargins = gfx::Insets::VH(0, 6); -constexpr auto kPickerEmoticonItemCornerRadius = gfx::RoundedCornersF(4); +constexpr int kPickerEmoticonItemCornerRadius = 4; } // namespace @@ -37,6 +35,7 @@ const std::u16string& emoticon) : PickerItemView(std::move(select_item_callback)) { SetUseDefaultFillLayout(true); + SetCornerRadius(kPickerEmoticonItemCornerRadius); emoticon_label_ = AddChildView( views::Builder<views::Label>() @@ -46,9 +45,6 @@ .SetBorder(views::CreateEmptyBorder(kPickerEmoticonItemMargins)) .Build()); SetAccessibleName(emoticon_label_); - - StyleUtil::InstallRoundedCornerHighlightPathGenerator( - this, kPickerEmoticonItemCornerRadius); } PickerEmoticonItemView::~PickerEmoticonItemView() = default;
diff --git a/ash/picker/views/picker_focus_indicator.cc b/ash/picker/views/picker_focus_indicator.cc new file mode 100644 index 0000000..34cca1eb7 --- /dev/null +++ b/ash/picker/views/picker_focus_indicator.cc
@@ -0,0 +1,53 @@ +// Copyright 2024 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ash/picker/views/picker_focus_indicator.h" + +#include "cc/paint/paint_flags.h" +#include "third_party/skia/include/core/SkColor.h" +#include "third_party/skia/include/core/SkPath.h" +#include "third_party/skia/include/core/SkScalar.h" +#include "ui/gfx/canvas.h" +#include "ui/gfx/geometry/point.h" +#include "ui/gfx/geometry/rect.h" +#include "ui/gfx/geometry/size.h" +#include "ui/gfx/geometry/skia_conversions.h" + +namespace ash { +namespace { + +constexpr int kPickerFocusIndicatorWidth = 3; + +constexpr SkScalar kPickerFocusIndicatorRadius = + SkIntToScalar(kPickerFocusIndicatorWidth); +constexpr SkScalar kPickerFocusIndicatorRadii[8] = { + 0, + 0, // top-left + kPickerFocusIndicatorRadius, + kPickerFocusIndicatorRadius, // top-right + kPickerFocusIndicatorRadius, + kPickerFocusIndicatorRadius, // bottom-right + 0, + 0}; // bottom-left + +} // namespace + +void PaintPickerFocusIndicator(gfx::Canvas* canvas, + const gfx::Point& origin, + int height, + SkColor color) { + SkPath path; + const gfx::Rect focus_indicator_bounds( + origin, gfx::Size(kPickerFocusIndicatorWidth, height)); + path.addRoundRect(gfx::RectToSkRect(focus_indicator_bounds), + kPickerFocusIndicatorRadii); + + cc::PaintFlags flags; + flags.setStyle(cc::PaintFlags::kFill_Style); + flags.setAntiAlias(true); + flags.setColor(color); + canvas->DrawPath(path, flags); +} + +} // namespace ash
diff --git a/ash/picker/views/picker_focus_indicator.h b/ash/picker/views/picker_focus_indicator.h new file mode 100644 index 0000000..a6ebd47 --- /dev/null +++ b/ash/picker/views/picker_focus_indicator.h
@@ -0,0 +1,26 @@ +// Copyright 2024 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef ASH_PICKER_VIEWS_PICKER_FOCUS_INDICATOR_H_ +#define ASH_PICKER_VIEWS_PICKER_FOCUS_INDICATOR_H_ + +#include "third_party/skia/include/core/SkColor.h" + +namespace gfx { +class Canvas; +class Point; +} // namespace gfx + +namespace ash { + +// Paints a focus indicator onto `canvas`. The painted indicator looks like a +// vertical bar with half-rounded edges. +void PaintPickerFocusIndicator(gfx::Canvas* canvas, + const gfx::Point& origin, + int height, + SkColor color); + +} // namespace ash + +#endif // ASH_PICKER_VIEWS_PICKER_FOCUS_INDICATOR_H_
diff --git a/ash/picker/views/picker_image_item_grid_view.cc b/ash/picker/views/picker_image_item_grid_view.cc index 58bd947..fde0372d 100644 --- a/ash/picker/views/picker_image_item_grid_view.cc +++ b/ash/picker/views/picker_image_item_grid_view.cc
@@ -4,6 +4,7 @@ #include "ash/picker/views/picker_image_item_grid_view.h" +#include <iterator> #include <memory> #include <utility> @@ -17,6 +18,7 @@ #include "ui/views/layout/table_layout.h" #include "ui/views/view.h" #include "ui/views/view_class_properties.h" +#include "ui/views/view_utils.h" namespace ash { namespace { @@ -42,6 +44,17 @@ return column; } +PickerItemView* ItemInColumnWithIndexClosestTo(views::View* column, + const size_t index) { + if (column->children().empty()) { + return nullptr; + } else if (index < column->children().size()) { + return views::AsViewClass<PickerItemView>(column->children()[index].get()); + } else { + return views::AsViewClass<PickerItemView>(column->children().back().get()); + } +} + } // namespace PickerImageItemGridView::PickerImageItemGridView(int grid_width) @@ -71,6 +84,72 @@ PickerImageItemGridView::~PickerImageItemGridView() = default; +PickerItemView* PickerImageItemGridView::GetTopItem() { + views::View* column = children().front(); + return column->children().empty() ? nullptr + : views::AsViewClass<PickerItemView>( + column->children().front().get()); +} + +PickerItemView* PickerImageItemGridView::GetBottomItem() { + views::View* tallest_column = + base::ranges::max(children(), + /*comp=*/base::ranges::less(), + /*proj=*/[](const views::View* v) { + return v->GetPreferredSize().height(); + }); + return tallest_column->children().empty() + ? nullptr + : views::AsViewClass<PickerItemView>( + tallest_column->children().back().get()); +} + +PickerItemView* PickerImageItemGridView::GetItemAbove(PickerItemView* item) { + views::View* column = GetColumnContaining(item); + if (!column || item == column->children().front()) { + return nullptr; + } + return views::AsViewClass<PickerItemView>( + std::prev(base::ranges::find(column->children(), item))->get()); +} + +PickerItemView* PickerImageItemGridView::GetItemBelow(PickerItemView* item) { + views::View* column = GetColumnContaining(item); + if (!column || item == column->children().back()) { + return nullptr; + } + return views::AsViewClass<PickerItemView>( + std::next(base::ranges::find(column->children(), item))->get()); +} + +PickerItemView* PickerImageItemGridView::GetItemLeftOf(PickerItemView* item) { + views::View* column = GetColumnContaining(item); + if (!column || column == children().front()) { + return nullptr; + } + // Prefer to return the item with the same index in the column to the left, + // since this will probably be at a similar height to `item` (at least in + // usual scenarios where the grid items all have similar dimensions). + const size_t item_index = column->GetIndexOf(item).value(); + views::View* left_column = + std::prev(base::ranges::find(children(), column))->get(); + return ItemInColumnWithIndexClosestTo(left_column, item_index); +} + +PickerItemView* PickerImageItemGridView::GetItemRightOf(PickerItemView* item) { + views::View* column = GetColumnContaining(item); + if (!column || column == children().back()) { + return nullptr; + } + // Prefer to return the item with the same index in the column to the right, + // since this will probably be at a similar height to `item` (at least in + // usual scenarios where the grid items all have similar dimensions). + const size_t item_index = column->GetIndexOf(item).value(); + views::View* right_column = + std::next(base::ranges::find(children(), column))->get(); + return ItemInColumnWithIndexClosestTo(right_column, item_index); +} + PickerImageItemView* PickerImageItemGridView::AddImageItem( std::unique_ptr<PickerImageItemView> image_item) { image_item->SetImageSizeFromWidth(GetImageGridColumnWidth(grid_width_)); @@ -83,6 +162,12 @@ return shortest_column->AddChildView(std::move(image_item)); } +views::View* PickerImageItemGridView::GetColumnContaining( + PickerItemView* item) { + views::View* column = item->parent(); + return column && column->parent() == this ? column : nullptr; +} + BEGIN_METADATA(PickerImageItemGridView) END_METADATA
diff --git a/ash/picker/views/picker_image_item_grid_view.h b/ash/picker/views/picker_image_item_grid_view.h index d4321fe..bcaf5691 100644 --- a/ash/picker/views/picker_image_item_grid_view.h +++ b/ash/picker/views/picker_image_item_grid_view.h
@@ -8,6 +8,7 @@ #include <memory> #include "ash/ash_export.h" +#include "ash/picker/views/picker_traversable_item_container.h" #include "ui/base/metadata/metadata_header_macros.h" #include "ui/views/view.h" @@ -17,7 +18,9 @@ // Container view for the image items in a section. The image items are // displayed in a grid with two columns. -class ASH_EXPORT PickerImageItemGridView : public views::View { +class ASH_EXPORT PickerImageItemGridView + : public views::View, + public PickerTraversableItemContainer { METADATA_HEADER(PickerImageItemGridView, views::View) public: @@ -26,10 +29,22 @@ PickerImageItemGridView& operator=(const PickerImageItemGridView&) = delete; ~PickerImageItemGridView() override; + // PickerTraversableItemContainer: + PickerItemView* GetTopItem() override; + PickerItemView* GetBottomItem() override; + PickerItemView* GetItemAbove(PickerItemView* item) override; + PickerItemView* GetItemBelow(PickerItemView* item) override; + PickerItemView* GetItemLeftOf(PickerItemView* item) override; + PickerItemView* GetItemRightOf(PickerItemView* item) override; + PickerImageItemView* AddImageItem( std::unique_ptr<PickerImageItemView> image_item); private: + // Returns the column containing `item`, or nullptr if `item` is not part of + // this grid. + views::View* GetColumnContaining(PickerItemView* item); + int grid_width_ = 0; };
diff --git a/ash/picker/views/picker_image_item_grid_view_unittest.cc b/ash/picker/views/picker_image_item_grid_view_unittest.cc index f1eb10a..6d566c3 100644 --- a/ash/picker/views/picker_image_item_grid_view_unittest.cc +++ b/ash/picker/views/picker_image_item_grid_view_unittest.cc
@@ -19,10 +19,10 @@ namespace ash { namespace { -using ::testing::Eq; +using ::testing::ElementsAre; +using ::testing::IsEmpty; using ::testing::Pointee; using ::testing::Property; -using ::testing::SizeIs; constexpr int kDefaultGridWidth = 320; @@ -41,74 +41,268 @@ } TEST(PickerImageItemGridViewTest, OneGifItem) { - PickerImageItemGridView image_item_grid(kDefaultGridWidth); + PickerImageItemGridView item_grid(kDefaultGridWidth); - image_item_grid.AddImageItem(CreateGifItem(gfx::Size(100, 100))); + const PickerItemView* item = + item_grid.AddImageItem(CreateGifItem(gfx::Size(100, 100))); // Two columns, one item in the first column. EXPECT_THAT( - image_item_grid.children(), - ElementsAre(Pointee(Property(&views::View::children, SizeIs(1))), - Pointee(Property(&views::View::children, SizeIs(0))))); + item_grid.children(), + ElementsAre(Pointee(Property(&views::View::children, ElementsAre(item))), + Pointee(Property(&views::View::children, IsEmpty())))); } TEST(PickerImageItemGridViewTest, TwoGifItems) { - PickerImageItemGridView image_item_grid(kDefaultGridWidth); + PickerImageItemGridView item_grid(kDefaultGridWidth); - image_item_grid.AddImageItem(CreateGifItem(gfx::Size(100, 100))); - image_item_grid.AddImageItem(CreateGifItem(gfx::Size(100, 100))); + const PickerItemView* item1 = + item_grid.AddImageItem(CreateGifItem(gfx::Size(100, 100))); + const PickerItemView* item2 = + item_grid.AddImageItem(CreateGifItem(gfx::Size(100, 100))); // Two columns, one item in each column. EXPECT_THAT( - image_item_grid.children(), - ElementsAre(Pointee(Property(&views::View::children, SizeIs(1))), - Pointee(Property(&views::View::children, SizeIs(1))))); + item_grid.children(), + ElementsAre( + Pointee(Property(&views::View::children, ElementsAre(item1))), + Pointee(Property(&views::View::children, ElementsAre(item2))))); } TEST(PickerImageItemGridViewTest, GifItemsWithVaryingHeight) { - PickerImageItemGridView image_item_grid(kDefaultGridWidth); + PickerImageItemGridView item_grid(kDefaultGridWidth); - image_item_grid.AddImageItem(CreateGifItem(gfx::Size(100, 120))); - image_item_grid.AddImageItem(CreateGifItem(gfx::Size(100, 20))); - image_item_grid.AddImageItem(CreateGifItem(gfx::Size(100, 30))); - image_item_grid.AddImageItem(CreateGifItem(gfx::Size(100, 20))); + const PickerItemView* item1 = + item_grid.AddImageItem(CreateGifItem(gfx::Size(100, 120))); + const PickerItemView* item2 = + item_grid.AddImageItem(CreateGifItem(gfx::Size(100, 20))); + const PickerItemView* item3 = + item_grid.AddImageItem(CreateGifItem(gfx::Size(100, 30))); + const PickerItemView* item4 = + item_grid.AddImageItem(CreateGifItem(gfx::Size(100, 20))); // One item in first column, three items in second column. EXPECT_THAT( - image_item_grid.children(), - ElementsAre(Pointee(Property(&views::View::children, SizeIs(1))), - Pointee(Property(&views::View::children, SizeIs(3))))); + item_grid.children(), + ElementsAre(Pointee(Property(&views::View::children, ElementsAre(item1))), + Pointee(Property(&views::View::children, + ElementsAre(item2, item3, item4))))); } TEST(PickerImageItemGridViewTest, GifItemsAreResizedToSameWidth) { - PickerImageItemGridView image_item_grid(kDefaultGridWidth); + PickerImageItemGridView item_grid(kDefaultGridWidth); - image_item_grid.AddImageItem(CreateGifItem(gfx::Size(100, 100))); - image_item_grid.AddImageItem(CreateGifItem(gfx::Size(80, 160))); + const PickerItemView* item1 = + item_grid.AddImageItem(CreateGifItem(gfx::Size(100, 100))); + const PickerItemView* item2 = + item_grid.AddImageItem(CreateGifItem(gfx::Size(80, 160))); - const views::View::Views& columns = image_item_grid.children(); - ASSERT_THAT( - columns, - ElementsAre(Pointee(Property(&views::View::children, SizeIs(1))), - Pointee(Property(&views::View::children, SizeIs(1))))); - EXPECT_EQ(columns[0]->children()[0]->GetPreferredSize().width(), - columns[1]->children()[0]->GetPreferredSize().width()); + EXPECT_EQ(item1->GetPreferredSize().width(), + item2->GetPreferredSize().width()); } TEST(PickerImageItemGridViewTest, PreservesAspectRatioOfGifItems) { - PickerImageItemGridView image_item_grid(kDefaultGridWidth); + PickerImageItemGridView item_grid(kDefaultGridWidth); constexpr gfx::Size kGifDimensions(100, 200); - image_item_grid.AddImageItem(CreateGifItem(kGifDimensions)); + const PickerItemView* item = + item_grid.AddImageItem(CreateGifItem(kGifDimensions)); - const views::View::Views& columns = image_item_grid.children(); - ASSERT_THAT( - columns, - ElementsAre(Pointee(Property(&views::View::children, SizeIs(1))), - Pointee(Property(&views::View::children, SizeIs(0))))); - EXPECT_EQ(GetAspectRatio(columns[0]->children()[0]->GetPreferredSize()), + EXPECT_EQ(GetAspectRatio(item->GetPreferredSize()), GetAspectRatio(kGifDimensions)); } +TEST(PickerImageItemGridViewTest, GetsTopItem) { + PickerImageItemGridView item_grid(kDefaultGridWidth); + + PickerItemView* item1 = + item_grid.AddImageItem(CreateGifItem(gfx::Size(100, 100))); + PickerItemView* item2 = + item_grid.AddImageItem(CreateGifItem(gfx::Size(100, 110))); + PickerItemView* item3 = + item_grid.AddImageItem(CreateGifItem(gfx::Size(100, 120))); + + EXPECT_THAT( + item_grid.children(), + ElementsAre( + Pointee(Property(&views::View::children, ElementsAre(item1, item3))), + Pointee(Property(&views::View::children, ElementsAre(item2))))); + EXPECT_EQ(item_grid.GetTopItem(), item1); +} + +TEST(PickerImageItemGridViewTest, EmptyGridHasNoTopItem) { + PickerImageItemGridView item_grid(kDefaultGridWidth); + + EXPECT_EQ(item_grid.GetTopItem(), nullptr); +} + +TEST(PickerImageItemGridViewTest, GetsBottomItem) { + PickerImageItemGridView item_grid(kDefaultGridWidth); + + PickerItemView* item1 = + item_grid.AddImageItem(CreateGifItem(gfx::Size(100, 100))); + PickerItemView* item2 = + item_grid.AddImageItem(CreateGifItem(gfx::Size(100, 110))); + PickerItemView* item3 = + item_grid.AddImageItem(CreateGifItem(gfx::Size(100, 120))); + + EXPECT_THAT( + item_grid.children(), + ElementsAre( + Pointee(Property(&views::View::children, ElementsAre(item1, item3))), + Pointee(Property(&views::View::children, ElementsAre(item2))))); + EXPECT_EQ(item_grid.GetBottomItem(), item3); +} + +TEST(PickerImageItemGridViewTest, EmptyGridHasNoBottomItem) { + PickerImageItemGridView item_grid(kDefaultGridWidth); + + EXPECT_EQ(item_grid.GetBottomItem(), nullptr); +} + +TEST(PickerImageItemGridViewTest, GetsItemAbove) { + PickerImageItemGridView item_grid(kDefaultGridWidth); + + PickerItemView* item1 = + item_grid.AddImageItem(CreateGifItem(gfx::Size(100, 100))); + PickerItemView* item2 = + item_grid.AddImageItem(CreateGifItem(gfx::Size(100, 110))); + PickerItemView* item3 = + item_grid.AddImageItem(CreateGifItem(gfx::Size(100, 120))); + PickerItemView* item4 = + item_grid.AddImageItem(CreateGifItem(gfx::Size(100, 130))); + + EXPECT_THAT(item_grid.children(), + ElementsAre(Pointee(Property(&views::View::children, + ElementsAre(item1, item3))), + Pointee(Property(&views::View::children, + ElementsAre(item2, item4))))); + EXPECT_EQ(item_grid.GetItemAbove(item1), nullptr); + EXPECT_EQ(item_grid.GetItemAbove(item2), nullptr); + EXPECT_EQ(item_grid.GetItemAbove(item3), item1); + EXPECT_EQ(item_grid.GetItemAbove(item4), item2); +} + +TEST(PickerImageItemGridViewTest, ItemNotInGridHasNoItemAbove) { + PickerImageItemGridView item_grid(kDefaultGridWidth); + std::unique_ptr<PickerImageItemView> item_not_in_grid = + CreateGifItem(gfx::Size(100, 100)); + + EXPECT_EQ(item_grid.GetItemAbove(item_not_in_grid.get()), nullptr); +} + +TEST(PickerImageItemGridViewTest, GetsItemBelow) { + PickerImageItemGridView item_grid(kDefaultGridWidth); + + PickerItemView* item1 = + item_grid.AddImageItem(CreateGifItem(gfx::Size(100, 100))); + PickerItemView* item2 = + item_grid.AddImageItem(CreateGifItem(gfx::Size(100, 110))); + PickerItemView* item3 = + item_grid.AddImageItem(CreateGifItem(gfx::Size(100, 120))); + PickerItemView* item4 = + item_grid.AddImageItem(CreateGifItem(gfx::Size(100, 130))); + + EXPECT_THAT(item_grid.children(), + ElementsAre(Pointee(Property(&views::View::children, + ElementsAre(item1, item3))), + Pointee(Property(&views::View::children, + ElementsAre(item2, item4))))); + EXPECT_EQ(item_grid.GetItemBelow(item1), item3); + EXPECT_EQ(item_grid.GetItemBelow(item2), item4); + EXPECT_EQ(item_grid.GetItemBelow(item3), nullptr); + EXPECT_EQ(item_grid.GetItemBelow(item4), nullptr); +} + +TEST(PickerImageItemGridViewTest, ItemNotInGridHasNoItemBelow) { + PickerImageItemGridView item_grid(kDefaultGridWidth); + std::unique_ptr<PickerImageItemView> item_not_in_grid = + CreateGifItem(gfx::Size(100, 100)); + + EXPECT_EQ(item_grid.GetItemBelow(item_not_in_grid.get()), nullptr); +} + +TEST(PickerImageItemGridViewTest, GetsItemLeftOf) { + PickerImageItemGridView item_grid(kDefaultGridWidth); + + PickerItemView* item1 = + item_grid.AddImageItem(CreateGifItem(gfx::Size(100, 100))); + PickerItemView* item2 = + item_grid.AddImageItem(CreateGifItem(gfx::Size(100, 110))); + PickerItemView* item3 = + item_grid.AddImageItem(CreateGifItem(gfx::Size(100, 120))); + PickerItemView* item4 = + item_grid.AddImageItem(CreateGifItem(gfx::Size(100, 130))); + + EXPECT_THAT(item_grid.children(), + ElementsAre(Pointee(Property(&views::View::children, + ElementsAre(item1, item3))), + Pointee(Property(&views::View::children, + ElementsAre(item2, item4))))); + EXPECT_EQ(item_grid.GetItemLeftOf(item1), nullptr); + EXPECT_EQ(item_grid.GetItemLeftOf(item2), item1); + EXPECT_EQ(item_grid.GetItemLeftOf(item3), nullptr); + EXPECT_EQ(item_grid.GetItemLeftOf(item4), item3); +} + +TEST(PickerImageItemGridViewTest, GetsItemLeftOfWithUnbalancedColumns) { + PickerImageItemGridView item_grid(kDefaultGridWidth); + + PickerItemView* item1 = + item_grid.AddImageItem(CreateGifItem(gfx::Size(100, 300))); + PickerItemView* item2 = + item_grid.AddImageItem(CreateGifItem(gfx::Size(100, 110))); + PickerItemView* item3 = + item_grid.AddImageItem(CreateGifItem(gfx::Size(100, 120))); + + EXPECT_THAT( + item_grid.children(), + ElementsAre(Pointee(Property(&views::View::children, ElementsAre(item1))), + Pointee(Property(&views::View::children, + ElementsAre(item2, item3))))); + EXPECT_EQ(item_grid.GetItemLeftOf(item1), nullptr); + EXPECT_EQ(item_grid.GetItemLeftOf(item2), item1); + EXPECT_EQ(item_grid.GetItemLeftOf(item3), item1); +} + +TEST(PickerImageItemGridViewTest, ItemNotInGridHasNoItemLeftOf) { + PickerImageItemGridView item_grid(kDefaultGridWidth); + std::unique_ptr<PickerImageItemView> item_not_in_grid = + CreateGifItem(gfx::Size(100, 100)); + + EXPECT_EQ(item_grid.GetItemLeftOf(item_not_in_grid.get()), nullptr); +} + +TEST(PickerImageItemGridViewTest, GetsItemRightOf) { + PickerImageItemGridView item_grid(kDefaultGridWidth); + + PickerItemView* item1 = + item_grid.AddImageItem(CreateGifItem(gfx::Size(100, 100))); + PickerItemView* item2 = + item_grid.AddImageItem(CreateGifItem(gfx::Size(100, 110))); + PickerItemView* item3 = + item_grid.AddImageItem(CreateGifItem(gfx::Size(100, 120))); + PickerItemView* item4 = + item_grid.AddImageItem(CreateGifItem(gfx::Size(100, 130))); + + EXPECT_THAT(item_grid.children(), + ElementsAre(Pointee(Property(&views::View::children, + ElementsAre(item1, item3))), + Pointee(Property(&views::View::children, + ElementsAre(item2, item4))))); + EXPECT_EQ(item_grid.GetItemRightOf(item1), item2); + EXPECT_EQ(item_grid.GetItemRightOf(item2), nullptr); + EXPECT_EQ(item_grid.GetItemRightOf(item3), item4); + EXPECT_EQ(item_grid.GetItemRightOf(item4), nullptr); +} + +TEST(PickerImageItemGridViewTest, ItemNotInGridHasNoItemRightOf) { + PickerImageItemGridView item_grid(kDefaultGridWidth); + std::unique_ptr<PickerImageItemView> item_not_in_grid = + CreateGifItem(gfx::Size(100, 100)); + + EXPECT_EQ(item_grid.GetItemRightOf(item_not_in_grid.get()), nullptr); +} + } // namespace } // namespace ash
diff --git a/ash/picker/views/picker_image_item_view.cc b/ash/picker/views/picker_image_item_view.cc index 7f28645..07d0fa15 100644 --- a/ash/picker/views/picker_image_item_view.cc +++ b/ash/picker/views/picker_image_item_view.cc
@@ -8,17 +8,15 @@ #include <utility> #include "ash/picker/views/picker_item_view.h" -#include "ash/style/style_util.h" #include "base/memory/raw_ptr.h" #include "ui/base/metadata/metadata_impl_macros.h" -#include "ui/gfx/geometry/rounded_corners_f.h" #include "ui/gfx/geometry/size.h" #include "ui/views/controls/image_view.h" namespace ash { namespace { -constexpr auto kPickerImageItemCornerRadius = gfx::RoundedCornersF(8); +constexpr int kPickerImageItemCornerRadius = 8; } // namespace @@ -27,13 +25,11 @@ std::unique_ptr<views::ImageView> image) : PickerItemView(std::move(select_item_callback)) { SetUseDefaultFillLayout(true); + SetCornerRadius(kPickerImageItemCornerRadius); image_view_ = AddChildView(std::move(image)); image_view_->SetCanProcessEventsWithinSubtree(false); - StyleUtil::InstallRoundedCornerHighlightPathGenerator( - this, kPickerImageItemCornerRadius); - // TODO: b/316936418 - Get accessible name for image contents. SetAccessibleName(u"image contents"); }
diff --git a/ash/picker/views/picker_item_view.cc b/ash/picker/views/picker_item_view.cc index 9caeaa2e..10bc85f 100644 --- a/ash/picker/views/picker_item_view.cc +++ b/ash/picker/views/picker_item_view.cc
@@ -4,16 +4,39 @@ #include "ash/picker/views/picker_item_view.h" +#include <memory> #include <utility> +#include "ash/picker/views/picker_focus_indicator.h" #include "ash/style/style_util.h" #include "base/functional/callback.h" #include "ui/base/metadata/metadata_impl_macros.h" +#include "ui/chromeos/styles/cros_tokens_color_mappings.h" #include "ui/compositor/layer.h" #include "ui/gfx/geometry/insets.h" +#include "ui/gfx/geometry/point.h" +#include "ui/gfx/geometry/rounded_corners_f.h" +#include "ui/views/background.h" #include "ui/views/controls/button/button.h" namespace ash { +namespace { + +constexpr auto kPickerItemFocusIndicatorMargins = gfx::Insets::VH(6, 0); + +std::unique_ptr<views::Background> GetPickerItemBackground( + PickerItemView::ItemState item_state, + int corner_radius) { + switch (item_state) { + case PickerItemView::ItemState::kNormal: + return nullptr; + case PickerItemView::ItemState::kPseudoFocused: + return views::CreateThemedRoundedRectBackground( + cros_tokens::kCrosSysHoverOnSubtle, corner_radius); + } +} + +} // namespace PickerItemView::PickerItemView(SelectItemCallback select_item_callback) : views::Button(select_item_callback), @@ -28,10 +51,49 @@ PickerItemView::~PickerItemView() = default; +void PickerItemView::PaintButtonContents(gfx::Canvas* canvas) { + views::Button::PaintButtonContents(canvas); + + if (item_state_ == ItemState::kPseudoFocused) { + // TODO: b/326870199 - Check whether grid items should have different focus + // indicator styling to list items. + PaintPickerFocusIndicator( + canvas, gfx::Point(0, kPickerItemFocusIndicatorMargins.top()), + height() - kPickerItemFocusIndicatorMargins.height(), + GetColorProvider()->GetColor(cros_tokens::kCrosSysFocusRing)); + } +} + void PickerItemView::SelectItem() { select_item_callback_.Run(); } +void PickerItemView::SetCornerRadius(int corner_radius) { + if (corner_radius_ == corner_radius) { + return; + } + + corner_radius_ = corner_radius; + StyleUtil::InstallRoundedCornerHighlightPathGenerator( + this, gfx::RoundedCornersF(corner_radius_)); + SetBackground(GetPickerItemBackground(item_state_, corner_radius_)); +} + +PickerItemView::ItemState PickerItemView::GetItemState() const { + return item_state_; +} + +void PickerItemView::SetItemState(ItemState item_state) { + if (item_state_ == item_state) { + return; + } + + item_state_ = item_state; + SetBackground(GetPickerItemBackground(item_state_, corner_radius_)); + // Schedule paint to update pseudo focus indicator. + SchedulePaint(); +} + BEGIN_METADATA(PickerItemView) END_METADATA
diff --git a/ash/picker/views/picker_item_view.h b/ash/picker/views/picker_item_view.h index 5ecd3c3..1edce94 100644 --- a/ash/picker/views/picker_item_view.h +++ b/ash/picker/views/picker_item_view.h
@@ -17,6 +17,18 @@ METADATA_HEADER(PickerItemView, views::Button) public: + // Used to determine how the item looks and how the user can interact with it. + enum class ItemState { + // Normal state. + kNormal, + // Pseudo focused state. The item is painted as if it was focused to + // indicate that it responds to certain user actions, e.g. it can be + // selected if the user presses the enter key. Note that the item might not + // have actual view focus (which generally stays on the Picker search field + // to allow the user to easily type and modify their search query). + kPseudoFocused, + }; + using SelectItemCallback = base::RepeatingClosure; explicit PickerItemView(SelectItemCallback select_item_callback); @@ -24,10 +36,23 @@ PickerItemView& operator=(const PickerItemView&) = delete; ~PickerItemView() override; + // views::Button: + void PaintButtonContents(gfx::Canvas* canvas) override; + void SelectItem(); + void SetCornerRadius(int corner_radius); + + ItemState GetItemState() const; + void SetItemState(ItemState item_state); + private: SelectItemCallback select_item_callback_; + + ItemState item_state_ = ItemState::kNormal; + + // Corner radius of the item background and highlight. + int corner_radius_ = 0; }; } // namespace ash
diff --git a/ash/picker/views/picker_item_view_unittest.cc b/ash/picker/views/picker_item_view_unittest.cc new file mode 100644 index 0000000..1c3d5c6 --- /dev/null +++ b/ash/picker/views/picker_item_view_unittest.cc
@@ -0,0 +1,36 @@ +// Copyright 2024 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ash/picker/views/picker_item_view.h" + +#include "base/functional/callback_helpers.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace ash { +namespace { + +TEST(PickerItemViewTest, DefaultIsNormalState) { + PickerItemView item_view(base::DoNothing()); + + EXPECT_EQ(item_view.GetItemState(), PickerItemView::ItemState::kNormal); +} + +TEST(PickerItemViewTest, NoBackgroundInNormalState) { + PickerItemView item_view(base::DoNothing()); + + item_view.SetItemState(PickerItemView::ItemState::kNormal); + + EXPECT_FALSE(item_view.background()); +} + +TEST(PickerItemViewTest, HasBackgroundInPseudoFocusedState) { + PickerItemView item_view(base::DoNothing()); + + item_view.SetItemState(PickerItemView::ItemState::kPseudoFocused); + + EXPECT_TRUE(item_view.background()); +} + +} // namespace +} // namespace ash
diff --git a/ash/picker/views/picker_key_event_handler.cc b/ash/picker/views/picker_key_event_handler.cc index 5b4eb23..5a1299b 100644 --- a/ash/picker/views/picker_key_event_handler.cc +++ b/ash/picker/views/picker_key_event_handler.cc
@@ -4,7 +4,7 @@ #include "ash/picker/views/picker_key_event_handler.h" -#include "ash/picker/views/picker_key_event_target.h" +#include "ash/picker/views/picker_pseudo_focus_handler.h" #include "ui/events/event.h" #include "ui/events/keycodes/keyboard_codes.h" @@ -15,20 +15,37 @@ PickerKeyEventHandler::~PickerKeyEventHandler() = default; bool PickerKeyEventHandler::HandleKeyEvent(const ui::KeyEvent& event) { - if (active_key_event_target_ == nullptr) { + if (active_pseudo_focus_handler_ == nullptr || event.handled() || + event.type() != ui::ET_KEY_PRESSED) { return false; } - if (event.key_code() == ui::VKEY_RETURN) { - return active_key_event_target_->OnEnterKeyPressed(); + const bool has_modifier = + event.IsShiftDown() || event.IsControlDown() || event.IsAltDown(); + switch (event.key_code()) { + case ui::VKEY_RETURN: + return active_pseudo_focus_handler_->DoPseudoFocusedAction(); + case ui::VKEY_UP: + return has_modifier ? false + : active_pseudo_focus_handler_->MovePseudoFocusUp(); + case ui::VKEY_DOWN: + return has_modifier ? false + : active_pseudo_focus_handler_->MovePseudoFocusDown(); + case ui::VKEY_LEFT: + return has_modifier ? false + : active_pseudo_focus_handler_->MovePseudoFocusLeft(); + case ui::VKEY_RIGHT: + return has_modifier + ? false + : active_pseudo_focus_handler_->MovePseudoFocusRight(); + default: + return false; } - - return false; } -void PickerKeyEventHandler::SetActiveKeyEventTarget( - PickerKeyEventTarget* active_key_event_target) { - active_key_event_target_ = active_key_event_target; +void PickerKeyEventHandler::SetActivePseudoFocusHandler( + PickerPseudoFocusHandler* active_pseudo_focus_handler) { + active_pseudo_focus_handler_ = active_pseudo_focus_handler; } } // namespace ash
diff --git a/ash/picker/views/picker_key_event_handler.h b/ash/picker/views/picker_key_event_handler.h index 3f72008b..5f4f0e6 100644 --- a/ash/picker/views/picker_key_event_handler.h +++ b/ash/picker/views/picker_key_event_handler.h
@@ -14,7 +14,7 @@ namespace ash { -class PickerKeyEventTarget; +class PickerPseudoFocusHandler; // Helper for routing and handling key events, e.g. for keyboard navigation. class ASH_EXPORT PickerKeyEventHandler { @@ -28,10 +28,11 @@ // processed. bool HandleKeyEvent(const ui::KeyEvent& event); - void SetActiveKeyEventTarget(PickerKeyEventTarget* active_key_event_target); + void SetActivePseudoFocusHandler( + PickerPseudoFocusHandler* active_pseudo_focus_handler); private: - raw_ptr<PickerKeyEventTarget> active_key_event_target_ = nullptr; + raw_ptr<PickerPseudoFocusHandler> active_pseudo_focus_handler_ = nullptr; }; } // namespace ash
diff --git a/ash/picker/views/picker_key_event_handler_unittest.cc b/ash/picker/views/picker_key_event_handler_unittest.cc index 51f75df..dac2192 100644 --- a/ash/picker/views/picker_key_event_handler_unittest.cc +++ b/ash/picker/views/picker_key_event_handler_unittest.cc
@@ -4,7 +4,7 @@ #include "ash/picker/views/picker_key_event_handler.h" -#include "ash/picker/views/picker_key_event_target.h" +#include "ash/picker/views/picker_pseudo_focus_handler.h" #include "testing/gtest/include/gtest/gtest.h" #include "ui/events/base_event_utils.h" #include "ui/events/event.h" @@ -16,37 +16,60 @@ namespace ash { namespace { -ui::KeyEvent CreateKeyEvent(ui::KeyboardCode key_code) { - return ui::KeyEvent(ui::ET_KEY_PRESSED, key_code, ui::DomCode::NONE, - ui::EF_NONE, ui::EventTimeForNow()); +ui::KeyEvent CreateKeyEvent(ui::KeyboardCode key_code, + int flags = ui::EF_NONE) { + return ui::KeyEvent(ui::ET_KEY_PRESSED, key_code, ui::DomCode::NONE, flags, + ui::EventTimeForNow()); } -class MockPickerKeyEventTarget : public PickerKeyEventTarget { +class MockPseudoFocusHandler : public PickerPseudoFocusHandler { public: - MockPickerKeyEventTarget() = default; - MockPickerKeyEventTarget(const MockPickerKeyEventTarget&) = delete; - MockPickerKeyEventTarget& operator=(const MockPickerKeyEventTarget&) = delete; - ~MockPickerKeyEventTarget() override = default; + MockPseudoFocusHandler() = default; + MockPseudoFocusHandler(const MockPseudoFocusHandler&) = delete; + MockPseudoFocusHandler& operator=(const MockPseudoFocusHandler&) = delete; + ~MockPseudoFocusHandler() override = default; - // PickerKeyEventTarget: - bool OnEnterKeyPressed() override { return true; } + // PickerPseudoFocusHandler: + bool DoPseudoFocusedAction() override { return true; } + bool MovePseudoFocusUp() override { return true; } + bool MovePseudoFocusDown() override { return true; } + bool MovePseudoFocusLeft() override { return true; } + bool MovePseudoFocusRight() override { return true; } }; -TEST(PickerKeyEventHandlerTest, DoesNotHandleEnterKeyWithoutKeyEventTarget) { +TEST(PickerKeyEventHandlerTest, + DoesNotHandleKeyEventyWithoutPseudoFocusHandler) { PickerKeyEventHandler key_event_handler; EXPECT_FALSE( key_event_handler.HandleKeyEvent(CreateKeyEvent(ui::VKEY_RETURN))); } -TEST(PickerKeyEventHandlerTest, HandlesEnterKeyWithKeyEventTarget) { +TEST(PickerKeyEventHandlerTest, HandlesKeyEventWithPseudoFocusHandler) { PickerKeyEventHandler key_event_handler; - MockPickerKeyEventTarget key_event_target; - key_event_handler.SetActiveKeyEventTarget(&key_event_target); + MockPseudoFocusHandler pseudo_focus_handler; + key_event_handler.SetActivePseudoFocusHandler(&pseudo_focus_handler); EXPECT_TRUE( key_event_handler.HandleKeyEvent(CreateKeyEvent(ui::VKEY_RETURN))); } +TEST(PickerKeyEventHandlerTest, HandlesUnmodifedArrowKeyEvent) { + PickerKeyEventHandler key_event_handler; + MockPseudoFocusHandler pseudo_focus_handler; + key_event_handler.SetActivePseudoFocusHandler(&pseudo_focus_handler); + + EXPECT_TRUE(key_event_handler.HandleKeyEvent(CreateKeyEvent(ui::VKEY_UP))); +} + +TEST(PickerKeyEventHandlerTest, DoesNotHandleModifiedArrowKeyEvent) { + PickerKeyEventHandler key_event_handler; + MockPseudoFocusHandler pseudo_focus_handler; + key_event_handler.SetActivePseudoFocusHandler(&pseudo_focus_handler); + + EXPECT_FALSE(key_event_handler.HandleKeyEvent( + CreateKeyEvent(ui::VKEY_UP, ui::EF_SHIFT_DOWN))); +} + } // namespace } // namespace ash
diff --git a/ash/picker/views/picker_key_event_target.h b/ash/picker/views/picker_key_event_target.h deleted file mode 100644 index 6a77297..0000000 --- a/ash/picker/views/picker_key_event_target.h +++ /dev/null
@@ -1,23 +0,0 @@ -// Copyright 2024 The Chromium Authors -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef ASH_PICKER_VIEWS_PICKER_KEY_EVENT_TARGET_H_ -#define ASH_PICKER_VIEWS_PICKER_KEY_EVENT_TARGET_H_ - -#include "ash/ash_export.h" - -namespace ash { - -class ASH_EXPORT PickerKeyEventTarget { - public: - virtual ~PickerKeyEventTarget() = default; - - // Returns true if the enter key press was handled and should not be further - // processed. - virtual bool OnEnterKeyPressed() = 0; -}; - -} // namespace ash - -#endif // ASH_PICKER_VIEWS_PICKER_KEY_EVENT_TARGET_H_
diff --git a/ash/picker/views/picker_list_item_container_view.cc b/ash/picker/views/picker_list_item_container_view.cc index 53bd8fc2..2a74b2d 100644 --- a/ash/picker/views/picker_list_item_container_view.cc +++ b/ash/picker/views/picker_list_item_container_view.cc
@@ -10,6 +10,7 @@ #include "ash/picker/views/picker_item_view.h" #include "ash/picker/views/picker_list_item_view.h" +#include "base/ranges/algorithm.h" #include "ui/base/metadata/metadata_impl_macros.h" #include "ui/views/layout/flex_layout.h" #include "ui/views/layout/layout_types.h" @@ -39,32 +40,32 @@ } PickerItemView* PickerListItemContainerView::GetItemAbove( - const PickerItemView* item) { - const views::View::Views::const_iterator it = FindChild(item); - return it == children().cend() || it == children().cbegin() + PickerItemView* item) { + const auto it = base::ranges::find(children(), item); + return it == children().end() || it == children().begin() ? nullptr : views::AsViewClass<PickerItemView>(std::prev(it)->get()); } PickerItemView* PickerListItemContainerView::GetItemBelow( - const PickerItemView* item) { - const views::View::Views::const_iterator it = FindChild(item); - if (it == children().cend()) { + PickerItemView* item) { + const auto it = base::ranges::find(children(), item); + if (it == children().end()) { return nullptr; } - const views::View::Views::const_iterator next_it = std::next(it); - return next_it == children().cend() + const auto next_it = std::next(it); + return next_it == children().end() ? nullptr : views::AsViewClass<PickerItemView>(next_it->get()); } PickerItemView* PickerListItemContainerView::GetItemLeftOf( - const PickerItemView* item) { + PickerItemView* item) { return nullptr; } PickerItemView* PickerListItemContainerView::GetItemRightOf( - const PickerItemView* item) { + PickerItemView* item) { return nullptr; }
diff --git a/ash/picker/views/picker_list_item_container_view.h b/ash/picker/views/picker_list_item_container_view.h index e175450..6bf58fe 100644 --- a/ash/picker/views/picker_list_item_container_view.h +++ b/ash/picker/views/picker_list_item_container_view.h
@@ -34,10 +34,10 @@ // PickerTraversableItemContainer: PickerItemView* GetTopItem() override; PickerItemView* GetBottomItem() override; - PickerItemView* GetItemAbove(const PickerItemView* item) override; - PickerItemView* GetItemBelow(const PickerItemView* item) override; - PickerItemView* GetItemLeftOf(const PickerItemView* item) override; - PickerItemView* GetItemRightOf(const PickerItemView* item) override; + PickerItemView* GetItemAbove(PickerItemView* item) override; + PickerItemView* GetItemBelow(PickerItemView* item) override; + PickerItemView* GetItemLeftOf(PickerItemView* item) override; + PickerItemView* GetItemRightOf(PickerItemView* item) override; PickerListItemView* AddListItem( std::unique_ptr<PickerListItemView> list_item);
diff --git a/ash/picker/views/picker_page_view.h b/ash/picker/views/picker_page_view.h index a9aacdb..cb44aeaf 100644 --- a/ash/picker/views/picker_page_view.h +++ b/ash/picker/views/picker_page_view.h
@@ -6,7 +6,7 @@ #define ASH_PICKER_VIEWS_PICKER_PAGE_VIEW_H_ #include "ash/ash_export.h" -#include "ash/picker/views/picker_key_event_target.h" +#include "ash/picker/views/picker_pseudo_focus_handler.h" #include "ui/base/metadata/metadata_header_macros.h" #include "ui/views/view.h" @@ -14,7 +14,7 @@ // View for a page that can act as the main contents of the Picker. class ASH_EXPORT PickerPageView : public views::View, - public PickerKeyEventTarget { + public PickerPseudoFocusHandler { METADATA_HEADER(PickerPageView, views::View) };
diff --git a/ash/picker/views/picker_positioning.cc b/ash/picker/views/picker_positioning.cc new file mode 100644 index 0000000..a2bd4b8f --- /dev/null +++ b/ash/picker/views/picker_positioning.cc
@@ -0,0 +1,32 @@ +// Copyright 2024 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ash/picker/views/picker_positioning.h" + +#include "ui/gfx/geometry/outsets.h" +#include "ui/gfx/geometry/point.h" +#include "ui/gfx/geometry/rect.h" + +namespace ash { +namespace { + +// Padding to separate the Picker window from the caret. +constexpr gfx::Outsets kPaddingAroundCaret(4); + +} // namespace + +gfx::Rect GetPickerAnchorBounds(const gfx::Rect& caret_bounds, + const gfx::Point& cursor_point, + const gfx::Rect& focused_window_bounds) { + if (caret_bounds != gfx::Rect() && + focused_window_bounds.Contains(caret_bounds)) { + gfx::Rect anchor_rect = caret_bounds; + anchor_rect.Outset(kPaddingAroundCaret); + return anchor_rect; + } else { + return gfx::Rect(cursor_point, gfx::Size()); + } +} + +} // namespace ash
diff --git a/ash/picker/views/picker_positioning.h b/ash/picker/views/picker_positioning.h new file mode 100644 index 0000000..692f222 --- /dev/null +++ b/ash/picker/views/picker_positioning.h
@@ -0,0 +1,28 @@ +// Copyright 2024 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef ASH_PICKER_VIEWS_PICKER_POSITIONING_H_ +#define ASH_PICKER_VIEWS_PICKER_POSITIONING_H_ + +#include "ash/ash_export.h" +#include "ui/gfx/geometry/rect.h" + +namespace gfx { +class Point; +} // namespace gfx + +namespace ash { + +// Gets the anchor bounds to use for positioning the Picker. We prefer to anchor +// at `caret_bounds`, but may use `cursor_point` as a fallback. `caret_bounds`, +// `cursor_point`, `focused_window_bounds` and returned anchor bounds should be +// in screen coordinates. +gfx::Rect ASH_EXPORT +GetPickerAnchorBounds(const gfx::Rect& caret_bounds, + const gfx::Point& cursor_point, + const gfx::Rect& focused_window_bounds); + +} // namespace ash + +#endif // ASH_PICKER_VIEWS_PICKER_POSITIONING_H_
diff --git a/ash/picker/views/picker_positioning_unittest.cc b/ash/picker/views/picker_positioning_unittest.cc new file mode 100644 index 0000000..5f04de87 --- /dev/null +++ b/ash/picker/views/picker_positioning_unittest.cc
@@ -0,0 +1,52 @@ +// Copyright 2024 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ash/picker/views/picker_positioning.h" + +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/gfx/geometry/outsets.h" +#include "ui/gfx/geometry/point.h" +#include "ui/gfx/geometry/rect.h" + +namespace ash { +namespace { + +constexpr gfx::Outsets kPaddingAroundCaret(4); +constexpr gfx::Point kDefaultCursorPoint(42, 42); + +TEST(PickerPositioningTest, + UsesCaretBoundsWhenCaretBoundsIsWithinWindowBounds) { + gfx::Rect caret_bounds(100, 200, 5, 5); + const gfx::Rect anchor_bounds = GetPickerAnchorBounds( + /*caret_bounds=*/caret_bounds, kDefaultCursorPoint, + /*focused_window_bounds=*/gfx::Rect(0, 0, 500, 500)); + + caret_bounds.Outset(kPaddingAroundCaret); + EXPECT_EQ(anchor_bounds, caret_bounds); +} + +TEST(PickerPositioningTest, + UsesCursorPointWhenCaretBoundsIsOutsideWindowBounds) { + const gfx::Rect anchor_bounds = GetPickerAnchorBounds( + /*caret_bounds=*/gfx::Rect(600, 200, 5, 5), kDefaultCursorPoint, + /*focused_window_bounds=*/gfx::Rect(0, 0, 500, 500)); + + EXPECT_EQ(anchor_bounds.origin(), kDefaultCursorPoint); + EXPECT_EQ(anchor_bounds.width(), 0); + EXPECT_EQ(anchor_bounds.height(), 0); +} + +TEST(PickerPositioningTest, UsesCursorPointWhenCaretBoundsIsEmpty) { + const gfx::Rect anchor_bounds = GetPickerAnchorBounds( + /*caret_bounds=*/gfx::Rect(), kDefaultCursorPoint, + /*focused_window_bounds=*/gfx::Rect(0, 0, 500, 500)); + + EXPECT_EQ(anchor_bounds.origin(), kDefaultCursorPoint); + EXPECT_EQ(anchor_bounds.width(), 0); + EXPECT_EQ(anchor_bounds.height(), 0); +} + +} // namespace +} // namespace ash
diff --git a/ash/picker/views/picker_pseudo_focus_handler.h b/ash/picker/views/picker_pseudo_focus_handler.h new file mode 100644 index 0000000..87b7f064 --- /dev/null +++ b/ash/picker/views/picker_pseudo_focus_handler.h
@@ -0,0 +1,33 @@ +// Copyright 2024 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef ASH_PICKER_VIEWS_PICKER_PSEUDO_FOCUS_HANDLER_H_ +#define ASH_PICKER_VIEWS_PICKER_PSEUDO_FOCUS_HANDLER_H_ + +#include "ash/ash_export.h" + +namespace ash { + +// Interface for classes that have pseudo focusable elements, which can look and +// behave as if they were focused without having actual focus. We use "pseudo +// focus" since actual view focus generally stays on the Picker search field, +// which just forwards user actions to be handled by pseudo focused elements if +// needed (e.g. to select an item when the user presses the enter key). +class ASH_EXPORT PickerPseudoFocusHandler { + public: + virtual ~PickerPseudoFocusHandler() = default; + + // Returns true if an action was performed. + virtual bool DoPseudoFocusedAction() = 0; + + // Returns true if pseudo focus was moved to a different element. + virtual bool MovePseudoFocusUp() = 0; + virtual bool MovePseudoFocusDown() = 0; + virtual bool MovePseudoFocusLeft() = 0; + virtual bool MovePseudoFocusRight() = 0; +}; + +} // namespace ash + +#endif // ASH_PICKER_VIEWS_PICKER_PSEUDO_FOCUS_HANDLER_H_
diff --git a/ash/picker/views/picker_search_results_view.cc b/ash/picker/views/picker_search_results_view.cc index 88d5038..4fbb20b 100644 --- a/ash/picker/views/picker_search_results_view.cc +++ b/ash/picker/views/picker_search_results_view.cc
@@ -19,6 +19,7 @@ #include "ash/picker/views/picker_image_item_view.h" #include "ash/picker/views/picker_item_view.h" #include "ash/picker/views/picker_list_item_view.h" +#include "ash/picker/views/picker_section_list_view.h" #include "ash/picker/views/picker_section_view.h" #include "ash/picker/views/picker_strings.h" #include "ash/picker/views/picker_symbol_item_view.h" @@ -40,41 +41,102 @@ int picker_view_width, SelectSearchResultCallback select_search_result_callback, PickerAssetFetcher* asset_fetcher) - : picker_view_width_(picker_view_width), - select_search_result_callback_(std::move(select_search_result_callback)), + : select_search_result_callback_(std::move(select_search_result_callback)), asset_fetcher_(asset_fetcher) { SetLayoutManager(std::make_unique<views::FlexLayout>()) ->SetOrientation(views::LayoutOrientation::kVertical); SetProperty(views::kElementIdentifierKey, kPickerSearchResultsPageElementId); + + section_list_view_ = + AddChildView(std::make_unique<PickerSectionListView>(picker_view_width)); } PickerSearchResultsView::~PickerSearchResultsView() = default; -bool PickerSearchResultsView::OnEnterKeyPressed() { - // TODO: b/322900302 - Select the currently highlighted item instead of the - // first item. - if (section_views_.empty() || section_views_[0]->item_views().empty()) { +bool PickerSearchResultsView::DoPseudoFocusedAction() { + if (pseudo_focused_item_ == nullptr) { return false; } - section_views_[0]->item_views()[0]->SelectItem(); + + pseudo_focused_item_->SelectItem(); + return true; +} + +bool PickerSearchResultsView::MovePseudoFocusUp() { + if (pseudo_focused_item_ == nullptr) { + return false; + } + + PickerItemView* item = section_list_view_->GetItemAbove(pseudo_focused_item_); + if (item == nullptr) { + // If there's no item above, move pseudo focus to the bottom item. + item = section_list_view_->GetBottomItem(); + } + SetPseudoFocusedItem(item); + return true; +} + +bool PickerSearchResultsView::MovePseudoFocusDown() { + if (pseudo_focused_item_ == nullptr) { + return false; + } + + PickerItemView* item = section_list_view_->GetItemBelow(pseudo_focused_item_); + if (item == nullptr) { + // If there's no item below, move pseudo focus to the top item. + item = section_list_view_->GetTopItem(); + } + SetPseudoFocusedItem(item); + return true; +} + +bool PickerSearchResultsView::MovePseudoFocusLeft() { + if (pseudo_focused_item_ == nullptr) { + return false; + } + + PickerItemView* item = + section_list_view_->GetItemLeftOf(pseudo_focused_item_); + if (item == nullptr) { + return false; + } + SetPseudoFocusedItem(item); + return true; +} + +bool PickerSearchResultsView::MovePseudoFocusRight() { + if (pseudo_focused_item_ == nullptr) { + return false; + } + + PickerItemView* item = + section_list_view_->GetItemRightOf(pseudo_focused_item_); + if (item == nullptr) { + return false; + } + SetPseudoFocusedItem(item); return true; } void PickerSearchResultsView::ClearSearchResults() { + pseudo_focused_item_ = nullptr; section_views_.clear(); - RemoveAllChildViews(); + section_list_view_->ClearSectionList(); } void PickerSearchResultsView::AppendSearchResults( PickerSearchResultsSection section) { - auto* section_view = - AddChildView(std::make_unique<PickerSectionView>(picker_view_width_)); + auto* section_view = section_list_view_->AddSection(); section_view->AddTitleLabel( GetSectionTitleForPickerSectionType(section.type())); for (const auto& result : section.results()) { AddResultToSection(result, section_view); } section_views_.push_back(section_view); + + if (pseudo_focused_item_ == nullptr) { + SetPseudoFocusedItem(section_list_view_->GetTopItem()); + } } void PickerSearchResultsView::SelectSearchResult( @@ -120,11 +182,13 @@ // view and `asset_fetcher_` outlives `this`. auto gif_view = std::make_unique<PickerGifView>( base::BindRepeating(&PickerAssetFetcher::FetchGifFromUrl, - base::Unretained(asset_fetcher_), data.url), + base::Unretained(asset_fetcher_), + data.preview_url), base::BindRepeating( &PickerAssetFetcher::FetchGifPreviewImageFromUrl, base::Unretained(asset_fetcher_), data.preview_image_url), - data.dimensions, /*accessible_name=*/data.content_description); + data.preview_dimensions, + /*accessible_name=*/data.content_description); auto gif_item_view = std::make_unique<PickerImageItemView>( std::move(select_result_callback), std::move(gif_view)); section_view->AddImageItem(std::move(gif_item_view)); @@ -148,6 +212,44 @@ result.data()); } +void PickerSearchResultsView::SetPseudoFocusedItem(PickerItemView* item) { + if (pseudo_focused_item_ == item) { + return; + } + + if (pseudo_focused_item_ != nullptr) { + pseudo_focused_item_->SetItemState(PickerItemView::ItemState::kNormal); + } + + pseudo_focused_item_ = item; + + if (pseudo_focused_item_ != nullptr) { + pseudo_focused_item_->SetItemState( + PickerItemView::ItemState::kPseudoFocused); + ScrollPseudoFocusedItemToVisible(); + } +} + +void PickerSearchResultsView::ScrollPseudoFocusedItemToVisible() { + if (pseudo_focused_item_ == nullptr) { + return; + } + + if (section_list_view_->GetItemAbove(pseudo_focused_item_) == nullptr) { + // For items at the top, scroll all the way up to let users see that they + // have reached the top of the zero state view. + ScrollRectToVisible(gfx::Rect(GetLocalBounds().origin(), gfx::Size())); + } else if (section_list_view_->GetItemBelow(pseudo_focused_item_) == + nullptr) { + // For items at the bottom, scroll all the way down to let users see that + // they have reached the bottom of the zero state view. + ScrollRectToVisible(gfx::Rect(GetLocalBounds().bottom_left(), gfx::Size())); + } else { + // Otherwise, just ensure the item is visible. + pseudo_focused_item_->ScrollViewToVisible(); + } +} + BEGIN_METADATA(PickerSearchResultsView) END_METADATA
diff --git a/ash/picker/views/picker_search_results_view.h b/ash/picker/views/picker_search_results_view.h index 4b4ee969..642d72d 100644 --- a/ash/picker/views/picker_search_results_view.h +++ b/ash/picker/views/picker_search_results_view.h
@@ -16,7 +16,9 @@ namespace ash { class PickerAssetFetcher; +class PickerItemView; class PickerSearchResult; +class PickerSectionListView; class PickerSectionView; class ASH_EXPORT PickerSearchResultsView : public PickerPageView { @@ -37,7 +39,11 @@ ~PickerSearchResultsView() override; // PickerPageView: - bool OnEnterKeyPressed() override; + bool DoPseudoFocusedAction() override; + bool MovePseudoFocusUp() override; + bool MovePseudoFocusDown() override; + bool MovePseudoFocusLeft() override; + bool MovePseudoFocusRight() override; // Clears the search results. void ClearSearchResults(); @@ -46,6 +52,10 @@ // TODO: b/325840864 - Merge with existing sections if needed. void AppendSearchResults(PickerSearchResultsSection section); + const PickerSectionListView* section_list_view_for_testing() const { + return section_list_view_; + } + base::span<const raw_ptr<PickerSectionView>> section_views_for_testing() const { return section_views_; @@ -61,16 +71,24 @@ void AddResultToSection(const PickerSearchResult& result, PickerSectionView* section_view); - // Width of the containing PickerView. - int picker_view_width_ = 0; + void SetPseudoFocusedItem(PickerItemView* item); + + void ScrollPseudoFocusedItemToVisible(); SelectSearchResultCallback select_search_result_callback_; // `asset_fetcher` outlives `this`. raw_ptr<PickerAssetFetcher> asset_fetcher_ = nullptr; - // The views for each section of results. + // The section list view, contains the section views. + raw_ptr<PickerSectionListView> section_list_view_ = nullptr; + + // Used to track the views for each section of results. std::vector<raw_ptr<PickerSectionView>> section_views_; + + // The currently pseudo focused item, which responds to user actions that + // trigger `DoPseudoFocusedAction`. + raw_ptr<PickerItemView> pseudo_focused_item_ = nullptr; }; } // namespace ash
diff --git a/ash/picker/views/picker_search_results_view_unittest.cc b/ash/picker/views/picker_search_results_view_unittest.cc index 055f2c40..51062ee 100644 --- a/ash/picker/views/picker_search_results_view_unittest.cc +++ b/ash/picker/views/picker_search_results_view_unittest.cc
@@ -4,12 +4,16 @@ #include "ash/picker/views/picker_search_results_view.h" +#include <string> + #include "ash/picker/mock_picker_asset_fetcher.h" #include "ash/picker/model/picker_search_results_section.h" #include "ash/picker/picker_test_util.h" #include "ash/picker/views/picker_item_view.h" +#include "ash/picker/views/picker_section_list_view.h" #include "ash/picker/views/picker_section_view.h" #include "ash/picker/views/picker_strings.h" +#include "ash/public/cpp/picker/picker_search_result.h" #include "ash/style/ash_color_provider.h" #include "ash/test/view_drawn_waiter.h" #include "base/functional/callback_helpers.h" @@ -17,6 +21,7 @@ #include "testing/gmock/include/gmock/gmock-matchers.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" +#include "ui/gfx/geometry/size.h" #include "ui/views/controls/label.h" #include "ui/views/test/views_test_base.h" #include "ui/views/widget/widget.h" @@ -56,7 +61,7 @@ PickerSectionType::kLinks, {{PickerSearchResult::Text(u"Result B"), PickerSearchResult::Text(u"Result C")}})); - EXPECT_THAT(view.children(), SizeIs(2)); + EXPECT_THAT(view.section_list_view_for_testing()->children(), SizeIs(2)); EXPECT_THAT( view.section_views_for_testing(), ElementsAre( @@ -73,7 +78,7 @@ view.ClearSearchResults(); - EXPECT_THAT(view.children(), IsEmpty()); + EXPECT_THAT(view.section_list_view_for_testing()->children(), IsEmpty()); } TEST_F(PickerSearchResultsViewTest, CreatesResultsSectionWithGif) { @@ -83,10 +88,11 @@ view.AppendSearchResults(PickerSearchResultsSection( PickerSectionType::kGifs, {{PickerSearchResult::Gif( - /*url=*/GURL(), /*preview_image_url=*/GURL(), gfx::Size(), + /*preview_url=*/GURL(), /*preview_image_url=*/GURL(), gfx::Size(), + /*full_url=*/GURL(), gfx::Size(), /*content_description=*/u"")}})); - EXPECT_THAT(view.children(), SizeIs(1)); + EXPECT_THAT(view.section_list_view_for_testing()->children(), SizeIs(1)); EXPECT_THAT( view.section_views_for_testing(), ElementsAre(Pointee(MatchesResultSection(PickerSectionType::kGifs, 1)))); @@ -100,7 +106,7 @@ PickerSectionType::kCategories, {{PickerSearchResult::Category(PickerCategory::kEmojis)}})); - EXPECT_THAT(view.children(), SizeIs(1)); + EXPECT_THAT(view.section_list_view_for_testing()->children(), SizeIs(1)); EXPECT_THAT(view.section_views_for_testing(), ElementsAre(Pointee( MatchesResultSection(PickerSectionType::kCategories, 1)))); @@ -117,7 +123,7 @@ PickerSectionType::kLinks, {{PickerSearchResult::Text(u"Updated Result")}})); - EXPECT_THAT(view.children(), SizeIs(2)); + EXPECT_THAT(view.section_list_view_for_testing()->children(), SizeIs(2)); EXPECT_THAT( view.section_views_for_testing(), ElementsAre( @@ -126,11 +132,57 @@ } TEST_F(PickerSearchResultsViewTest, - PressingEnterDoesNothingForEmptySearchResults) { + NoPseudoFocusedActionForEmptySearchResults) { MockPickerAssetFetcher asset_fetcher; PickerSearchResultsView view(kPickerWidth, base::DoNothing(), &asset_fetcher); - EXPECT_FALSE(view.OnEnterKeyPressed()); + EXPECT_FALSE(view.DoPseudoFocusedAction()); +} + +TEST_F(PickerSearchResultsViewTest, PseudoFocusedActionDefaultsToFirstResult) { + base::test::TestFuture<const PickerSearchResult&> future; + MockPickerAssetFetcher asset_fetcher; + PickerSearchResultsView view(kPickerWidth, future.GetCallback(), + &asset_fetcher); + + view.AppendSearchResults(PickerSearchResultsSection( + PickerSectionType::kExpressions, + {{PickerSearchResult::Emoji(u"😊"), PickerSearchResult::Symbol(u"♬")}})); + + EXPECT_TRUE(view.DoPseudoFocusedAction()); + EXPECT_EQ(future.Get(), PickerSearchResult::Emoji(u"😊")); +} + +TEST_F(PickerSearchResultsViewTest, MovesPseudoFocusRight) { + base::test::TestFuture<const PickerSearchResult&> future; + MockPickerAssetFetcher asset_fetcher; + PickerSearchResultsView view(kPickerWidth, future.GetCallback(), + &asset_fetcher); + + view.AppendSearchResults(PickerSearchResultsSection( + PickerSectionType::kExpressions, + {{PickerSearchResult::Emoji(u"😊"), PickerSearchResult::Symbol(u"♬")}})); + + EXPECT_TRUE(view.MovePseudoFocusRight()); + EXPECT_TRUE(view.DoPseudoFocusedAction()); + EXPECT_EQ(future.Get(), PickerSearchResult::Symbol(u"♬")); +} + +TEST_F(PickerSearchResultsViewTest, MovesPseudoFocusDown) { + base::test::TestFuture<const PickerSearchResult&> future; + MockPickerAssetFetcher asset_fetcher; + PickerSearchResultsView view(kPickerWidth, future.GetCallback(), + &asset_fetcher); + + view.AppendSearchResults(PickerSearchResultsSection( + PickerSectionType::kCategories, + {{PickerSearchResult::Category(PickerCategory::kEmojis), + PickerSearchResult::Category(PickerCategory::kEmoticons)}})); + + EXPECT_TRUE(view.MovePseudoFocusDown()); + EXPECT_TRUE(view.DoPseudoFocusedAction()); + EXPECT_EQ(future.Get(), + PickerSearchResult::Category(PickerCategory::kEmoticons)); } struct PickerSearchResultTestCase { @@ -169,7 +221,8 @@ EXPECT_EQ(future.Get(), test_case.result); } -TEST_P(PickerSearchResultsViewResultSelectionTest, PressingEnterSelectsResult) { +TEST_P(PickerSearchResultsViewResultSelectionTest, + PseudoFocusedActionSelectsResult) { const PickerSearchResultTestCase& test_case = GetParam(); std::unique_ptr<views::Widget> widget = CreateTestWidget(); widget->SetFullscreen(true); @@ -181,7 +234,7 @@ view->AppendSearchResults(PickerSearchResultsSection( PickerSectionType::kExpressions, {{test_case.result}})); - EXPECT_TRUE(view->OnEnterKeyPressed()); + EXPECT_TRUE(view->DoPseudoFocusedAction()); EXPECT_EQ(future.Get(), test_case.result); } @@ -193,9 +246,11 @@ {"Emoji", PickerSearchResult::Emoji(u"😊")}, {"Symbol", PickerSearchResult::Symbol(u"♬")}, {"Emoticon", PickerSearchResult::Emoticon(u"¯\\_(ツ)_/¯")}, - {"Gif", PickerSearchResult::Gif(/*url=*/GURL(), + {"Gif", PickerSearchResult::Gif(/*preview_url=*/GURL(), /*preview_image_url=*/GURL(), gfx::Size(10, 10), + /*full_url=*/GURL(), + gfx::Size(20, 20), u"cat gif")}, {"Category", PickerSearchResult::Category(PickerCategory::kEmojis)}, }),
diff --git a/ash/picker/views/picker_section_list_view.cc b/ash/picker/views/picker_section_list_view.cc new file mode 100644 index 0000000..a18ddcc --- /dev/null +++ b/ash/picker/views/picker_section_list_view.cc
@@ -0,0 +1,127 @@ +// Copyright 2024 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ash/picker/views/picker_section_list_view.h" + +#include <iterator> +#include <memory> +#include <utility> + +#include "ash/picker/views/picker_item_view.h" +#include "ash/picker/views/picker_section_view.h" +#include "base/ranges/algorithm.h" +#include "ui/base/metadata/metadata_impl_macros.h" +#include "ui/views/layout/flex_layout.h" +#include "ui/views/layout/layout_types.h" +#include "ui/views/view.h" +#include "ui/views/view_utils.h" + +namespace ash { + +PickerSectionListView::PickerSectionListView(int section_width) + : section_width_(section_width) { + SetLayoutManager(std::make_unique<views::FlexLayout>()) + ->SetOrientation(views::LayoutOrientation::kVertical) + .SetCrossAxisAlignment(views::LayoutAlignment::kStretch); +} + +PickerSectionListView::~PickerSectionListView() = default; + +PickerItemView* PickerSectionListView::GetTopItem() { + return children().empty() + ? nullptr + : views::AsViewClass<PickerSectionView>(children().front().get()) + ->GetTopItem(); +} + +PickerItemView* PickerSectionListView::GetBottomItem() { + return children().empty() + ? nullptr + : views::AsViewClass<PickerSectionView>(children().back().get()) + ->GetBottomItem(); +} + +PickerItemView* PickerSectionListView::GetItemAbove(PickerItemView* item) { + PickerSectionView* section = GetSectionContaining(item); + if (section == nullptr) { + return nullptr; + } + + // First check if there is an item above in the same section. + if (PickerItemView* item_below = section->GetItemAbove(item)) { + return item_below; + } + + // Otherwise, return the bottom item in a previous non-empty section if there + // is one. + for (auto section_it = + std::make_reverse_iterator(base::ranges::find(children(), section)); + section_it != children().rend(); section_it = std::next(section_it)) { + if (PickerItemView* prev_section_bottom_item = + views::AsViewClass<PickerSectionView>(section_it->get()) + ->GetBottomItem()) { + return prev_section_bottom_item; + } + } + + return nullptr; +} + +PickerItemView* PickerSectionListView::GetItemBelow(PickerItemView* item) { + PickerSectionView* section = GetSectionContaining(item); + if (section == nullptr) { + return nullptr; + } + + // First check if there is an item below in the same section. + if (PickerItemView* item_below = section->GetItemBelow(item)) { + return item_below; + } + + // Otherwise, return the top item in the next non-empty section if there is + // one. + for (auto section_it = std::next(base::ranges::find(children(), section)); + section_it != children().end(); section_it = std::next(section_it)) { + if (PickerItemView* next_section_top_item = + views::AsViewClass<PickerSectionView>(section_it->get()) + ->GetTopItem()) { + return next_section_top_item; + } + } + return nullptr; +} + +PickerItemView* PickerSectionListView::GetItemLeftOf(PickerItemView* item) { + PickerSectionView* section = GetSectionContaining(item); + return section != nullptr ? section->GetItemLeftOf(item) : nullptr; +} + +PickerItemView* PickerSectionListView::GetItemRightOf(PickerItemView* item) { + PickerSectionView* section = GetSectionContaining(item); + return section != nullptr ? section->GetItemRightOf(item) : nullptr; +} + +PickerSectionView* PickerSectionListView::AddSection() { + return AddChildView(std::make_unique<PickerSectionView>(section_width_)); +} + +void PickerSectionListView::ClearSectionList() { + RemoveAllChildViews(); +} + +PickerSectionView* PickerSectionListView::GetSectionContaining( + PickerItemView* item) { + for (views::View* view = item->parent(); view != nullptr; + view = view->parent()) { + if (views::IsViewClass<PickerSectionView>(view) && view->parent() == this) { + return views::AsViewClass<PickerSectionView>(view); + } + } + return nullptr; +} + +BEGIN_METADATA(PickerSectionListView) +END_METADATA + +} // namespace ash
diff --git a/ash/picker/views/picker_section_list_view.h b/ash/picker/views/picker_section_list_view.h new file mode 100644 index 0000000..43db9f8 --- /dev/null +++ b/ash/picker/views/picker_section_list_view.h
@@ -0,0 +1,68 @@ +// Copyright 2024 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef ASH_PICKER_VIEWS_PICKER_SECTION_LIST_VIEW_H_ +#define ASH_PICKER_VIEWS_PICKER_SECTION_LIST_VIEW_H_ + +#include "ash/ash_export.h" +#include "ui/base/metadata/metadata_header_macros.h" +#include "ui/views/view.h" + +namespace ash { + +class PickerItemView; +class PickerSectionView; + +// View which displays Picker sections in a vertical list. +class ASH_EXPORT PickerSectionListView : public views::View { + METADATA_HEADER(PickerSectionListView, views::View) + + public: + explicit PickerSectionListView(int section_width); + PickerSectionListView(const PickerSectionListView&) = delete; + PickerSectionListView& operator=(const PickerSectionListView&) = delete; + ~PickerSectionListView() override; + + // Returns the item to highlight to when navigating to this section list from + // the top, or nullptr if the section list is empty. + PickerItemView* GetTopItem(); + + // Returns the item to highlight to when navigating to this section list from + // the bottom, or nullptr if the section list is empty. + PickerItemView* GetBottomItem(); + + // Returns the item directly above `item`, or nullptr if there is no such item + // in the section list. + PickerItemView* GetItemAbove(PickerItemView* item); + + // Returns the item directly below `item`, or nullptr if there is no such item + // in the section list. + PickerItemView* GetItemBelow(PickerItemView* item); + + // Returns the item directly to the left of `item`, or nullptr if there is no + // such item in the section list. + PickerItemView* GetItemLeftOf(PickerItemView* item); + + // Returns the item directly to the right of `item`, or nullptr if there is no + // such item in the section list. + PickerItemView* GetItemRightOf(PickerItemView* item); + + // Adds a section to the end of the section list. + PickerSectionView* AddSection(); + + // Clears the section list. This deletes all contained sections and items. + void ClearSectionList(); + + private: + // Returns the section containing `item`, or nullptr if `item` is not part of + // this section list. + PickerSectionView* GetSectionContaining(PickerItemView* item); + + // Width of the sections in this view. + int section_width_; +}; + +} // namespace ash + +#endif // ASH_PICKER_VIEWS_PICKER_SECTION_LIST_VIEW_H_
diff --git a/ash/picker/views/picker_section_list_view_unittest.cc b/ash/picker/views/picker_section_list_view_unittest.cc new file mode 100644 index 0000000..53c2db2 --- /dev/null +++ b/ash/picker/views/picker_section_list_view_unittest.cc
@@ -0,0 +1,188 @@ +// Copyright 2024 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ash/picker/views/picker_section_list_view.h" + +#include <memory> +#include <string> +#include <utility> + +#include "ash/picker/views/picker_emoji_item_view.h" +#include "ash/picker/views/picker_item_view.h" +#include "ash/picker/views/picker_list_item_view.h" +#include "ash/picker/views/picker_section_view.h" +#include "ash/picker/views/picker_symbol_item_view.h" +#include "base/functional/callback_helpers.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/views/test/views_test_base.h" +#include "ui/views/view.h" + +namespace ash { +namespace { + +using ::testing::ElementsAre; +using ::testing::IsEmpty; + +constexpr int kDefaultSectionWidth = 320; + +using PickerSectionListViewTest = views::ViewsTestBase; + +TEST_F(PickerSectionListViewTest, AddsSection) { + PickerSectionListView section_list(kDefaultSectionWidth); + + PickerSectionView* section = section_list.AddSection(); + + EXPECT_THAT(section_list.children(), ElementsAre(section)); +} + +TEST_F(PickerSectionListViewTest, ClearsSectionList) { + PickerSectionListView section_list(kDefaultSectionWidth); + + section_list.AddSection(); + section_list.ClearSectionList(); + + EXPECT_THAT(section_list.children(), IsEmpty()); +} + +TEST_F(PickerSectionListViewTest, GetsTopItem) { + PickerSectionListView section_list(kDefaultSectionWidth); + + PickerSectionView* section1 = section_list.AddSection(); + PickerItemView* top_item = section1->AddEmojiItem( + std::make_unique<PickerEmojiItemView>(base::DoNothing(), u"😊")); + section1->AddSymbolItem( + std::make_unique<PickerSymbolItemView>(base::DoNothing(), u"♬")); + PickerSectionView* section2 = section_list.AddSection(); + section2->AddListItem( + std::make_unique<PickerListItemView>(base::DoNothing())); + + EXPECT_EQ(section_list.GetTopItem(), top_item); +} + +TEST_F(PickerSectionListViewTest, EmptySectionListHasNoTopItem) { + PickerSectionListView section_list(kDefaultSectionWidth); + + EXPECT_EQ(section_list.GetTopItem(), nullptr); +} + +TEST_F(PickerSectionListViewTest, GetsBottomItem) { + PickerSectionListView section_list(kDefaultSectionWidth); + + PickerSectionView* section1 = section_list.AddSection(); + section1->AddEmojiItem( + std::make_unique<PickerEmojiItemView>(base::DoNothing(), u"😊")); + section1->AddSymbolItem( + std::make_unique<PickerSymbolItemView>(base::DoNothing(), u"♬")); + PickerSectionView* section2 = section_list.AddSection(); + PickerItemView* bottom_item = section2->AddListItem( + std::make_unique<PickerListItemView>(base::DoNothing())); + + EXPECT_EQ(section_list.GetBottomItem(), bottom_item); +} + +TEST_F(PickerSectionListViewTest, EmptySectionListHasNoBottomItem) { + PickerSectionListView section_list(kDefaultSectionWidth); + + EXPECT_EQ(section_list.GetBottomItem(), nullptr); +} + +TEST_F(PickerSectionListViewTest, GetsItemAbove) { + PickerSectionListView section_list(kDefaultSectionWidth); + + PickerSectionView* section1 = section_list.AddSection(); + PickerItemView* item1 = section1->AddEmojiItem( + std::make_unique<PickerEmojiItemView>(base::DoNothing(), u"😊")); + PickerItemView* item2 = section1->AddSymbolItem( + std::make_unique<PickerSymbolItemView>(base::DoNothing(), u"♬")); + PickerSectionView* section2 = section_list.AddSection(); + PickerItemView* item3 = section2->AddListItem( + std::make_unique<PickerListItemView>(base::DoNothing())); + + EXPECT_EQ(section_list.GetItemAbove(item1), nullptr); + EXPECT_EQ(section_list.GetItemAbove(item2), nullptr); + EXPECT_EQ(section_list.GetItemAbove(item3), item1); +} + +TEST_F(PickerSectionListViewTest, ItemNotInSectionListHasNoItemAbove) { + PickerSectionListView section_list(kDefaultSectionWidth); + PickerEmojiItemView item_not_in_section_list(base::DoNothing(), u"😊"); + + EXPECT_EQ(section_list.GetItemAbove(&item_not_in_section_list), nullptr); +} + +TEST_F(PickerSectionListViewTest, GetsItemBelow) { + PickerSectionListView section_list(kDefaultSectionWidth); + + PickerSectionView* section1 = section_list.AddSection(); + PickerItemView* item1 = section1->AddEmojiItem( + std::make_unique<PickerEmojiItemView>(base::DoNothing(), u"😊")); + PickerItemView* item2 = section1->AddSymbolItem( + std::make_unique<PickerSymbolItemView>(base::DoNothing(), u"♬")); + PickerSectionView* section2 = section_list.AddSection(); + PickerItemView* item3 = section2->AddListItem( + std::make_unique<PickerListItemView>(base::DoNothing())); + + EXPECT_EQ(section_list.GetItemBelow(item1), item3); + EXPECT_EQ(section_list.GetItemBelow(item2), item3); + EXPECT_EQ(section_list.GetItemBelow(item3), nullptr); +} + +TEST_F(PickerSectionListViewTest, ItemNotInSectionListHasNoItemBelow) { + PickerSectionListView section_list(kDefaultSectionWidth); + PickerEmojiItemView item_not_in_section_list(base::DoNothing(), u"😊"); + + EXPECT_EQ(section_list.GetItemBelow(&item_not_in_section_list), nullptr); +} + +TEST_F(PickerSectionListViewTest, GetsItemLeftOf) { + PickerSectionListView section_list(kDefaultSectionWidth); + + PickerSectionView* section1 = section_list.AddSection(); + PickerItemView* item1 = section1->AddEmojiItem( + std::make_unique<PickerEmojiItemView>(base::DoNothing(), u"😊")); + PickerItemView* item2 = section1->AddSymbolItem( + std::make_unique<PickerSymbolItemView>(base::DoNothing(), u"♬")); + PickerSectionView* section2 = section_list.AddSection(); + PickerItemView* item3 = section2->AddListItem( + std::make_unique<PickerListItemView>(base::DoNothing())); + + EXPECT_EQ(section_list.GetItemLeftOf(item1), nullptr); + EXPECT_EQ(section_list.GetItemLeftOf(item2), item1); + EXPECT_EQ(section_list.GetItemLeftOf(item3), nullptr); +} + +TEST_F(PickerSectionListViewTest, ItemNotInSectionListHasNoItemLeftOf) { + PickerSectionListView section_list(kDefaultSectionWidth); + PickerEmojiItemView item_not_in_section_list(base::DoNothing(), u"😊"); + + EXPECT_EQ(section_list.GetItemLeftOf(&item_not_in_section_list), nullptr); +} + +TEST_F(PickerSectionListViewTest, GetsItemRightOf) { + PickerSectionListView section_list(kDefaultSectionWidth); + + PickerSectionView* section1 = section_list.AddSection(); + PickerItemView* item1 = section1->AddEmojiItem( + std::make_unique<PickerEmojiItemView>(base::DoNothing(), u"😊")); + PickerItemView* item2 = section1->AddSymbolItem( + std::make_unique<PickerSymbolItemView>(base::DoNothing(), u"♬")); + PickerSectionView* section2 = section_list.AddSection(); + PickerItemView* item3 = section2->AddListItem( + std::make_unique<PickerListItemView>(base::DoNothing())); + + EXPECT_EQ(section_list.GetItemRightOf(item1), item2); + EXPECT_EQ(section_list.GetItemRightOf(item2), nullptr); + EXPECT_EQ(section_list.GetItemRightOf(item3), nullptr); +} + +TEST_F(PickerSectionListViewTest, ItemNotInSectionListHasNoItemRightOf) { + PickerSectionListView section_list(kDefaultSectionWidth); + PickerEmojiItemView item_not_in_section_list(base::DoNothing(), u"😊"); + + EXPECT_EQ(section_list.GetItemRightOf(&item_not_in_section_list), nullptr); +} + +} // namespace +} // namespace ash
diff --git a/ash/picker/views/picker_section_view.cc b/ash/picker/views/picker_section_view.cc index 181e95c1..b678cfe7 100644 --- a/ash/picker/views/picker_section_view.cc +++ b/ash/picker/views/picker_section_view.cc
@@ -18,6 +18,7 @@ #include "ash/picker/views/picker_list_item_view.h" #include "ash/picker/views/picker_small_item_grid_view.h" #include "ash/picker/views/picker_symbol_item_view.h" +#include "ash/picker/views/picker_traversable_item_container.h" #include "ash/style/typography.h" #include "ui/base/metadata/metadata_impl_macros.h" #include "ui/chromeos/styles/cros_tokens_color_mappings.h" @@ -85,43 +86,86 @@ .Build()); } -void PickerSectionView::AddListItem( +PickerListItemView* PickerSectionView::AddListItem( std::unique_ptr<PickerListItemView> list_item) { if (list_item_container_ == nullptr) { list_item_container_ = AddChildView(std::make_unique<PickerListItemContainerView>()); } - item_views_.push_back( - list_item_container_->AddListItem(std::move(list_item))); + PickerListItemView* list_item_ptr = + list_item_container_->AddListItem(std::move(list_item)); + item_views_.push_back(list_item_ptr); + return list_item_ptr; } -void PickerSectionView::AddEmojiItem( +PickerEmojiItemView* PickerSectionView::AddEmojiItem( std::unique_ptr<PickerEmojiItemView> emoji_item) { CreateSmallItemGridIfNeeded(); - item_views_.push_back(small_item_grid_->AddEmojiItem(std::move(emoji_item))); + PickerEmojiItemView* emoji_item_ptr = + small_item_grid_->AddEmojiItem(std::move(emoji_item)); + item_views_.push_back(emoji_item_ptr); + return emoji_item_ptr; } -void PickerSectionView::AddSymbolItem( +PickerSymbolItemView* PickerSectionView::AddSymbolItem( std::unique_ptr<PickerSymbolItemView> symbol_item) { CreateSmallItemGridIfNeeded(); - item_views_.push_back( - small_item_grid_->AddSymbolItem(std::move(symbol_item))); + PickerSymbolItemView* symbol_item_ptr = + small_item_grid_->AddSymbolItem(std::move(symbol_item)); + item_views_.push_back(symbol_item_ptr); + return symbol_item_ptr; } -void PickerSectionView::AddEmoticonItem( +PickerEmoticonItemView* PickerSectionView::AddEmoticonItem( std::unique_ptr<PickerEmoticonItemView> emoticon_item) { CreateSmallItemGridIfNeeded(); - item_views_.push_back( - small_item_grid_->AddEmoticonItem(std::move(emoticon_item))); + PickerEmoticonItemView* emoticon_item_ptr = + small_item_grid_->AddEmoticonItem(std::move(emoticon_item)); + item_views_.push_back(emoticon_item_ptr); + return emoticon_item_ptr; } -void PickerSectionView::AddImageItem( +PickerImageItemView* PickerSectionView::AddImageItem( std::unique_ptr<PickerImageItemView> image_item) { if (image_item_grid_ == nullptr) { image_item_grid_ = AddChildView(std::make_unique<PickerImageItemGridView>(section_width_)); } - item_views_.push_back(image_item_grid_->AddImageItem(std::move(image_item))); + PickerImageItemView* image_item_ptr = + image_item_grid_->AddImageItem(std::move(image_item)); + item_views_.push_back(image_item_ptr); + return image_item_ptr; +} + +PickerItemView* PickerSectionView::GetTopItem() { + return GetItemContainer() != nullptr ? GetItemContainer()->GetTopItem() + : nullptr; +} + +PickerItemView* PickerSectionView::GetBottomItem() { + return GetItemContainer() != nullptr ? GetItemContainer()->GetBottomItem() + : nullptr; +} + +PickerItemView* PickerSectionView::GetItemAbove(PickerItemView* item) { + return GetItemContainer() != nullptr ? GetItemContainer()->GetItemAbove(item) + : nullptr; +} + +PickerItemView* PickerSectionView::GetItemBelow(PickerItemView* item) { + return GetItemContainer() != nullptr ? GetItemContainer()->GetItemBelow(item) + : nullptr; +} + +PickerItemView* PickerSectionView::GetItemLeftOf(PickerItemView* item) { + return GetItemContainer() != nullptr ? GetItemContainer()->GetItemLeftOf(item) + : nullptr; +} + +PickerItemView* PickerSectionView::GetItemRightOf(PickerItemView* item) { + return GetItemContainer() != nullptr + ? GetItemContainer()->GetItemRightOf(item) + : nullptr; } void PickerSectionView::CreateSmallItemGridIfNeeded() { @@ -131,6 +175,16 @@ } } +PickerTraversableItemContainer* PickerSectionView::GetItemContainer() { + if (list_item_container_ != nullptr) { + return list_item_container_; + } else if (image_item_grid_ != nullptr) { + return image_item_grid_; + } else { + return small_item_grid_; + } +} + BEGIN_METADATA(PickerSectionView) END_METADATA
diff --git a/ash/picker/views/picker_section_view.h b/ash/picker/views/picker_section_view.h index a758489..d8d2a419 100644 --- a/ash/picker/views/picker_section_view.h +++ b/ash/picker/views/picker_section_view.h
@@ -31,6 +31,7 @@ class PickerListItemView; class PickerSmallItemGridView; class PickerSymbolItemView; +class PickerTraversableItemContainer; // View for a Picker section with a title and related items. class ASH_EXPORT PickerSectionView : public views::View { @@ -48,17 +49,46 @@ // Adds a list item. These are displayed in a vertical list, each item // spanning the width of the section. - void AddListItem(std::unique_ptr<PickerListItemView> list_item); + PickerListItemView* AddListItem( + std::unique_ptr<PickerListItemView> list_item); // Adds a emoji, symbol or emoticon. These are treated collectively as small // grid items and are displayed in rows. - void AddEmojiItem(std::unique_ptr<PickerEmojiItemView> emoji_item); - void AddSymbolItem(std::unique_ptr<PickerSymbolItemView> symbol_item); - void AddEmoticonItem(std::unique_ptr<PickerEmoticonItemView> emoticon_item); + PickerEmojiItemView* AddEmojiItem( + std::unique_ptr<PickerEmojiItemView> emoji_item); + PickerSymbolItemView* AddSymbolItem( + std::unique_ptr<PickerSymbolItemView> symbol_item); + PickerEmoticonItemView* AddEmoticonItem( + std::unique_ptr<PickerEmoticonItemView> emoticon_item); // Adds an image item to the section. These are displayed in a grid with two // columns. - void AddImageItem(std::unique_ptr<PickerImageItemView> image_item); + PickerImageItemView* AddImageItem( + std::unique_ptr<PickerImageItemView> image_item); + + // Returns the item to highlight to when navigating to this section from the + // top, or nullptr if the section is empty. + PickerItemView* GetTopItem(); + + // Returns the item to highlight to when navigating to this section from the + // bottom, or nullptr if the section is empty. + PickerItemView* GetBottomItem(); + + // Returns the item directly above `item`, or nullptr if there is no such item + // in the section. + PickerItemView* GetItemAbove(PickerItemView* item); + + // Returns the item directly below `item`, or nullptr if there is no such item + // in the section. + PickerItemView* GetItemBelow(PickerItemView* item); + + // Returns the item directly to the left of `item`, or nullptr if there is no + // such item in the section. + PickerItemView* GetItemLeftOf(PickerItemView* item); + + // Returns the item directly to the right of `item`, or nullptr if there is no + // such item in the section. + PickerItemView* GetItemRightOf(PickerItemView* item); const views::Label* title_label_for_testing() const { return title_label_; } @@ -75,6 +105,14 @@ private: void CreateSmallItemGridIfNeeded(); + // Returns a non-null item container if the section has one, otherwise returns + // nullptr. + // TODO: b/322900302 - Determine whether sections can have multiple item + // containers. If so, `GetItemContainer` will need to get the right item + // container. If not, just track a single PickerTraversableItemContainer and + // then we won't need this method anymore. + PickerTraversableItemContainer* GetItemContainer(); + // Width available for laying out section items. This is needed to determine // row and column widths for grid items in the section. int section_width_ = 0;
diff --git a/ash/picker/views/picker_small_item_grid_view.cc b/ash/picker/views/picker_small_item_grid_view.cc index 2e37182..bfa5463 100644 --- a/ash/picker/views/picker_small_item_grid_view.cc +++ b/ash/picker/views/picker_small_item_grid_view.cc
@@ -13,6 +13,7 @@ #include "ash/picker/views/picker_emoticon_item_view.h" #include "ash/picker/views/picker_item_view.h" #include "ash/picker/views/picker_symbol_item_view.h" +#include "base/ranges/algorithm.h" #include "ui/base/metadata/metadata_impl_macros.h" #include "ui/gfx/geometry/insets.h" #include "ui/gfx/geometry/size.h" @@ -64,7 +65,7 @@ return nullptr; } // Return the first item in the top row, if it exists. - const views::View* row = children().front(); + views::View* row = children().front(); return row->children().empty() ? nullptr : views::AsViewClass<PickerItemView>( row->children().front().get()); @@ -75,63 +76,56 @@ return nullptr; } // Return the first item in the bottom row, if it exists. - const views::View* row = children().back(); + views::View* row = children().back(); return row->children().empty() ? nullptr : views::AsViewClass<PickerItemView>( row->children().front().get()); } -PickerItemView* PickerSmallItemGridView::GetItemAbove( - const PickerItemView* item) { - const views::View* row = GetRowContaining(item); +PickerItemView* PickerSmallItemGridView::GetItemAbove(PickerItemView* item) { + views::View* row = GetRowContaining(item); if (!row || row == children().front()) { return nullptr; } // Return the first item in the row above, if it exists. - const views::View* row_above = std::prev(FindChild(row))->get(); + views::View* row_above = + std::prev(base::ranges::find(children(), row))->get(); return row_above->children().empty() ? nullptr : views::AsViewClass<PickerItemView>( row_above->children().front().get()); } -PickerItemView* PickerSmallItemGridView::GetItemBelow( - const PickerItemView* item) { - const views::View* row = GetRowContaining(item); +PickerItemView* PickerSmallItemGridView::GetItemBelow(PickerItemView* item) { + views::View* row = GetRowContaining(item); if (!row || row == children().back()) { return nullptr; } // Return the first item in the row below, if it exists. - const views::View* row_below = std::next(FindChild(row))->get(); + views::View* row_below = + std::next(base::ranges::find(children(), row))->get(); return row_below->children().empty() ? nullptr : views::AsViewClass<PickerItemView>( row_below->children().front().get()); } -PickerItemView* PickerSmallItemGridView::GetItemLeftOf( - const PickerItemView* item) { - const views::View* row = GetRowContaining(item); - if (!row) { +PickerItemView* PickerSmallItemGridView::GetItemLeftOf(PickerItemView* item) { + views::View* row = GetRowContaining(item); + if (!row || item == row->children().front()) { return nullptr; } - const views::View::Views::const_iterator it = row->FindChild(item); - return it == row->children().cbegin() - ? nullptr - : views::AsViewClass<PickerItemView>(std::prev(it)->get()); + return views::AsViewClass<PickerItemView>( + std::prev(base::ranges::find(row->children(), item))->get()); } -PickerItemView* PickerSmallItemGridView::GetItemRightOf( - const PickerItemView* item) { - const views::View* row = GetRowContaining(item); - if (!row) { +PickerItemView* PickerSmallItemGridView::GetItemRightOf(PickerItemView* item) { + views::View* row = GetRowContaining(item); + if (!row || item == row->children().back()) { return nullptr; } - const views::View::Views::const_iterator next_it = - std::next(row->FindChild(item)); - return next_it == row->children().cend() - ? nullptr - : views::AsViewClass<PickerItemView>(next_it->get()); + return views::AsViewClass<PickerItemView>( + std::next(base::ranges::find(row->children(), item))->get()); } PickerEmojiItemView* PickerSmallItemGridView::AddEmojiItem( @@ -172,9 +166,8 @@ return row->AddChildView(std::move(grid_item)); } -const views::View* PickerSmallItemGridView::GetRowContaining( - const PickerItemView* item) const { - const views::View* row = item->parent(); +views::View* PickerSmallItemGridView::GetRowContaining(PickerItemView* item) { + views::View* row = item->parent(); return row && row->parent() == this ? row : nullptr; }
diff --git a/ash/picker/views/picker_small_item_grid_view.h b/ash/picker/views/picker_small_item_grid_view.h index 93995f5..4b528bf 100644 --- a/ash/picker/views/picker_small_item_grid_view.h +++ b/ash/picker/views/picker_small_item_grid_view.h
@@ -35,10 +35,10 @@ // PickerTraversableItemContainer: PickerItemView* GetTopItem() override; PickerItemView* GetBottomItem() override; - PickerItemView* GetItemAbove(const PickerItemView* item) override; - PickerItemView* GetItemBelow(const PickerItemView* item) override; - PickerItemView* GetItemLeftOf(const PickerItemView* item) override; - PickerItemView* GetItemRightOf(const PickerItemView* item) override; + PickerItemView* GetItemAbove(PickerItemView* item) override; + PickerItemView* GetItemBelow(PickerItemView* item) override; + PickerItemView* GetItemLeftOf(PickerItemView* item) override; + PickerItemView* GetItemRightOf(PickerItemView* item) override; PickerEmojiItemView* AddEmojiItem( std::unique_ptr<PickerEmojiItemView> emoji_item); @@ -53,7 +53,7 @@ // Returns the row containing `item`, or nullptr if `item` is not part of this // grid. - const views::View* GetRowContaining(const PickerItemView* item) const; + views::View* GetRowContaining(PickerItemView* item); int grid_width_ = 0; };
diff --git a/ash/picker/views/picker_strings.cc b/ash/picker/views/picker_strings.cc index 85e0d96..e18dbd5b 100644 --- a/ash/picker/views/picker_strings.cc +++ b/ash/picker/views/picker_strings.cc
@@ -87,6 +87,8 @@ return u"Matching links"; case PickerSectionType::kFiles: return u"Matching files"; + case PickerSectionType::kDriveFiles: + return u"Matching Google Drive files"; case PickerSectionType::kGifs: return u"Other expressions"; case PickerSectionType::kRecentlyUsed:
diff --git a/ash/picker/views/picker_symbol_item_view.cc b/ash/picker/views/picker_symbol_item_view.cc index 9fb9833b..eade715 100644 --- a/ash/picker/views/picker_symbol_item_view.cc +++ b/ash/picker/views/picker_symbol_item_view.cc
@@ -8,12 +8,9 @@ #include <utility> #include "ash/picker/views/picker_item_view.h" -#include "ash/style/style_util.h" #include "ui/base/metadata/metadata_impl_macros.h" #include "ui/chromeos/styles/cros_tokens_color_mappings.h" #include "ui/gfx/font_list.h" -#include "ui/gfx/geometry/rounded_corners_f.h" -#include "ui/views/controls/button/button.h" #include "ui/views/controls/label.h" namespace ash { @@ -25,7 +22,7 @@ kPickerSymbolFontSize, gfx::Font::Weight::NORMAL); -constexpr auto kPickerSymbolItemCornerRadius = gfx::RoundedCornersF(4); +constexpr int kPickerSymbolItemCornerRadius = 4; } // namespace @@ -34,6 +31,7 @@ const std::u16string& symbol) : PickerItemView(std::move(select_item_callback)) { SetUseDefaultFillLayout(true); + SetCornerRadius(kPickerSymbolItemCornerRadius); symbol_label_ = AddChildView(views::Builder<views::Label>() @@ -42,9 +40,6 @@ .SetFontList(kPickerSymbolFont) .Build()); SetAccessibleName(symbol_label_); - - StyleUtil::InstallRoundedCornerHighlightPathGenerator( - this, kPickerSymbolItemCornerRadius); } PickerSymbolItemView::~PickerSymbolItemView() = default;
diff --git a/ash/picker/views/picker_traversable_item_container.h b/ash/picker/views/picker_traversable_item_container.h index 64581d1..f889ea9 100644 --- a/ash/picker/views/picker_traversable_item_container.h +++ b/ash/picker/views/picker_traversable_item_container.h
@@ -26,19 +26,19 @@ // Returns the item directly above `item`, or nullptr if there is no such item // in the container. - virtual PickerItemView* GetItemAbove(const PickerItemView* item) = 0; + virtual PickerItemView* GetItemAbove(PickerItemView* item) = 0; // Returns the item directly below `item`, or nullptr if there is no such item // in the container. - virtual PickerItemView* GetItemBelow(const PickerItemView* item) = 0; + virtual PickerItemView* GetItemBelow(PickerItemView* item) = 0; // Returns the item directly to the left of `item`, or nullptr if there is no // such item in the container. - virtual PickerItemView* GetItemLeftOf(const PickerItemView* item) = 0; + virtual PickerItemView* GetItemLeftOf(PickerItemView* item) = 0; // Returns the item directly to the right of `item`, or nullptr if there is no // such item in the container. - virtual PickerItemView* GetItemRightOf(const PickerItemView* item) = 0; + virtual PickerItemView* GetItemRightOf(PickerItemView* item) = 0; }; } // namespace ash
diff --git a/ash/picker/views/picker_view.cc b/ash/picker/views/picker_view.cc index a3a0ca9..11fc9dd 100644 --- a/ash/picker/views/picker_view.cc +++ b/ash/picker/views/picker_view.cc
@@ -7,7 +7,6 @@ #include <memory> #include "ash/ash_element_identifiers.h" -#include "ash/bubble/bubble_event_filter.h" #include "ash/picker/model/picker_search_results_section.h" #include "ash/picker/views/picker_category_view.h" #include "ash/picker/views/picker_contents_view.h" @@ -45,7 +44,6 @@ #include "ui/views/layout/flex_layout.h" #include "ui/views/view_class_properties.h" #include "ui/views/view_utils.h" -#include "ui/views/widget/unique_widget_ptr.h" #include "ui/views/widget/widget.h" #include "ui/views/window/non_client_view.h" @@ -58,8 +56,6 @@ constexpr ui::ColorId kBackgroundColor = cros_tokens::kCrosSysSystemBaseElevated; -// Padding to separate the Picker window from the caret. -constexpr gfx::Outsets kPaddingAroundCaret(4); // Padding to separate the Picker window from the screen edge. constexpr gfx::Insets kPaddingFromScreenEdge(16); @@ -77,34 +73,6 @@ .Build(); } -// Gets the anchor bounds to use for positioning the Picker. We prefer to anchor -// at `caret_bounds`, but may use `cursor_point` as a fallback. `caret_bounds`, -// `cursor_point`, `focused_window_bounds` and returned anchor bounds should be -// in screen coordinates. -gfx::Rect GetPickerAnchorBounds(const gfx::Rect& caret_bounds, - const gfx::Point& cursor_point, - const gfx::Rect& focused_window_bounds) { - if (caret_bounds != gfx::Rect() && - focused_window_bounds.Contains(caret_bounds)) { - gfx::Rect anchor_rect = caret_bounds; - anchor_rect.Outset(kPaddingAroundCaret); - return anchor_rect; - } else { - return gfx::Rect(cursor_point, gfx::Size()); - } -} - -// Gets the preferred layout to use given `anchor_bounds` in screen coordinates. -PickerView::PickerLayoutType GetLayoutType(const gfx::Rect& anchor_bounds) { - return anchor_bounds.bottom() + kPickerSize.height() <= - display::Screen::GetScreen() - ->GetDisplayMatching(anchor_bounds) - .work_area() - .bottom() - ? PickerView::PickerLayoutType::kResultsBelowSearchField - : PickerView::PickerLayoutType::kResultsAboveSearchField; -} - // Gets the preferred Picker view bounds in screen coordinates. We try to place // the Picker view close to `anchor_bounds`, while taking into account // `layout_type`, `picker_view_size` and available space on the screen. @@ -157,8 +125,8 @@ } // namespace PickerView::PickerView(PickerViewDelegate* delegate, - const base::TimeTicks trigger_event_timestamp, - PickerLayoutType layout_type) + PickerLayoutType layout_type, + const base::TimeTicks trigger_event_timestamp) : session_metrics_(trigger_event_timestamp), delegate_(delegate) { SetShowCloseButton(false); SetBackground(views::CreateThemedRoundedRectBackground(kBackgroundColor, @@ -195,39 +163,6 @@ PickerView::~PickerView() = default; -views::UniqueWidgetPtr PickerView::CreateWidget( - const gfx::Rect& caret_bounds, - const gfx::Point& cursor_point, - const gfx::Rect& focused_window_bounds, - PickerViewDelegate* delegate, - const base::TimeTicks trigger_event_timestamp) { - // Create the Picker view and set its size. This will trigger a layout, so - // that the position of the Picker view's search field can be used when - // setting the Picker widget bounds below. - const gfx::Rect anchor_bounds = - GetPickerAnchorBounds(caret_bounds, cursor_point, focused_window_bounds); - const PickerLayoutType layout_type = GetLayoutType(anchor_bounds); - auto picker_view = std::make_unique<PickerView>( - delegate, trigger_event_timestamp, layout_type); - picker_view->SetSize(kPickerSize); - - views::Widget::InitParams params; - params.activatable = views::Widget::InitParams::Activatable::kYes; - params.shadow_type = views::Widget::InitParams::ShadowType::kNone; - params.opacity = views::Widget::InitParams::WindowOpacity::kTranslucent; - params.type = views::Widget::InitParams::TYPE_BUBBLE; - params.z_order = ui::ZOrderLevel::kFloatingUIElement; - params.bounds = picker_view->GetTargetBounds(anchor_bounds, layout_type); - // TODO(b/309706053): Replace this with the finalized string. - params.name = "Picker"; - params.delegate = picker_view.release(); - - auto widget = std::make_unique<views::Widget>(std::move(params)); - widget->SetVisibilityAnimationTransition( - views::Widget::VisibilityTransition::ANIMATE_HIDE); - return widget; -} - bool PickerView::AcceleratorPressed(const ui::Accelerator& accelerator) { CHECK_EQ(accelerator.key_code(), ui::VKEY_ESCAPE); if (auto* widget = GetWidget()) { @@ -246,17 +181,10 @@ void PickerView::AddedToWidget() { session_metrics_.StartRecording(*GetWidget()); - // `base::Unretained` is safe here because this class owns - // `bubble_event_filter_`. - bubble_event_filter_ = std::make_unique<BubbleEventFilter>( - GetWidget(), /*button=*/nullptr, - base::BindRepeating(&PickerView::OnClickOutsideWidget, - base::Unretained(this))); } void PickerView::RemovedFromWidget() { session_metrics_.StopRecording(); - bubble_event_filter_.reset(); } gfx::Rect PickerView::GetTargetBounds(const gfx::Rect& anchor_bounds, @@ -346,12 +274,6 @@ category_view_->SetResults(results); } -void PickerView::OnClickOutsideWidget(const ui::LocatedEvent& event) { - if (auto* widget = GetWidget()) { - widget->Close(); - } -} - void PickerView::AddSearchFieldView() { // `base::Unretained` is safe here because this class owns // `search_field_view_`. @@ -390,7 +312,7 @@ void PickerView::SetActivePage(PickerPageView* page_view) { contents_view_->SetActivePage(page_view); - key_event_handler_.SetActiveKeyEventTarget(page_view); + key_event_handler_.SetActivePseudoFocusHandler(page_view); } BEGIN_METADATA(PickerView)
diff --git a/ash/picker/views/picker_view.h b/ash/picker/views/picker_view.h index a957a375..a5ec8367 100644 --- a/ash/picker/views/picker_view.h +++ b/ash/picker/views/picker_view.h
@@ -17,7 +17,6 @@ #include "base/time/time.h" #include "ui/base/metadata/metadata_header_macros.h" #include "ui/views/view.h" -#include "ui/views/widget/unique_widget_ptr.h" #include "ui/views/widget/widget_delegate.h" namespace views { @@ -27,7 +26,6 @@ namespace ash { -class BubbleEventFilter; class PickerContentsView; class PickerSearchFieldView; class PickerPageView; @@ -51,25 +49,12 @@ // `delegate` must remain valid for the lifetime of this class. explicit PickerView(PickerViewDelegate* delegate, - base::TimeTicks trigger_event_timestamp, - PickerLayoutType layout_type); + PickerLayoutType layout_type, + base::TimeTicks trigger_event_timestamp); PickerView(const PickerView&) = delete; PickerView& operator=(const PickerView&) = delete; ~PickerView() override; - // `trigger_event_timestamp` is the timestamp of the event that triggered the - // Widget to be created. For example, if the feature was triggered by a mouse - // click, then it should be the timestamp of the click. By default, the - // timestamp is the time this function is called. - // `delegate` must remain valid for the lifetime of the created Widget. - // `caret_bounds` and `cursor_point` should be in screen coordinates. - static views::UniqueWidgetPtr CreateWidget( - const gfx::Rect& caret_bounds, - const gfx::Point& cursor_point, - const gfx::Rect& focused_window_bounds, - PickerViewDelegate* delegate, - base::TimeTicks trigger_event_timestamp = base::TimeTicks::Now()); - // views::WidgetDelegateView: bool AcceleratorPressed(const ui::Accelerator& accelerator) override; std::unique_ptr<views::NonClientFrameView> CreateNonClientFrameView( @@ -113,8 +98,6 @@ // Displays `results` in the category view. void PublishCategoryResults(std::vector<PickerSearchResultsSection> results); - void OnClickOutsideWidget(const ui::LocatedEvent& event); - void AddSearchFieldView(); void AddContentsView(PickerLayoutType layout_type); @@ -123,9 +106,6 @@ std::optional<PickerCategory> selected_category_; - // Used to close the Picker widget when the user clicks outside of it. - std::unique_ptr<BubbleEventFilter> bubble_event_filter_; - std::unique_ptr<SystemShadow> shadow_; PickerKeyEventHandler key_event_handler_;
diff --git a/ash/picker/views/picker_view_unittest.cc b/ash/picker/views/picker_view_unittest.cc index 962fa8c..10ccec6 100644 --- a/ash/picker/views/picker_view_unittest.cc +++ b/ash/picker/views/picker_view_unittest.cc
@@ -5,6 +5,7 @@ #include "ash/picker/views/picker_view.h" #include <optional> +#include <string> #include "ash/picker/mock_picker_asset_fetcher.h" #include "ash/picker/model/picker_search_results_section.h" @@ -14,10 +15,13 @@ #include "ash/picker/views/picker_item_view.h" #include "ash/picker/views/picker_search_field_view.h" #include "ash/picker/views/picker_search_results_view.h" +#include "ash/picker/views/picker_section_list_view.h" #include "ash/picker/views/picker_section_view.h" #include "ash/picker/views/picker_view_delegate.h" +#include "ash/picker/views/picker_widget.h" #include "ash/picker/views/picker_zero_state_view.h" #include "ash/public/cpp/picker/picker_category.h" +#include "ash/public/cpp/picker/picker_search_result.h" #include "ash/strings/grit/ash_strings.h" #include "ash/test/ash_test_base.h" #include "ash/test/test_ash_web_view.h" @@ -31,6 +35,7 @@ #include "testing/gtest/include/gtest/gtest.h" #include "ui/base/emoji/emoji_panel_helper.h" #include "ui/base/l10n/l10n_util.h" +#include "ui/base/models/image_model.h" #include "ui/chromeos/styles/cros_tokens_color_mappings.h" #include "ui/display/screen.h" #include "ui/events/event_constants.h" @@ -42,6 +47,7 @@ #include "ui/views/controls/scroll_view.h" #include "ui/views/controls/textfield/textfield.h" #include "ui/views/view_utils.h" +#include "url/gurl.h" namespace ash { namespace { @@ -54,9 +60,7 @@ using ::testing::Property; using ::testing::Truly; -constexpr gfx::Rect kDefaultCaretBounds(200, 100, 0, 10); -constexpr gfx::Point kDefaultCursorPoint(300, 400); -constexpr gfx::Rect kDefaultFocusedWindowBounds(300, 400); +constexpr gfx::Rect kDefaultAnchorBounds(200, 100, 0, 10); class PickerViewTest : public AshTestBase { public: @@ -128,36 +132,9 @@ ->second->item_views_for_testing()[0]; // Should be open tabs } -TEST_F(PickerViewTest, CreateWidgetHasCorrectHierarchy) { - FakePickerViewDelegate delegate; - auto widget = - PickerView::CreateWidget(kDefaultCaretBounds, kDefaultCursorPoint, - kDefaultFocusedWindowBounds, &delegate); - - // Widget should contain a NonClientView, which has a NonClientFrameView for - // borders and shadows, and a ClientView with a sole child of the PickerView. - ASSERT_TRUE(widget); - ASSERT_TRUE(widget->non_client_view()); - ASSERT_TRUE(widget->non_client_view()->frame_view()); - ASSERT_TRUE(widget->non_client_view()->client_view()); - EXPECT_THAT(widget->non_client_view()->client_view()->children(), - ElementsAre(Truly(views::IsViewClass<PickerView>))); -} - -TEST_F(PickerViewTest, CreateWidgetHasCorrectBorder) { - FakePickerViewDelegate delegate; - auto widget = - PickerView::CreateWidget(kDefaultCaretBounds, kDefaultCursorPoint, - kDefaultFocusedWindowBounds, &delegate); - - EXPECT_TRUE(widget->non_client_view()->frame_view()->GetBorder()); -} - TEST_F(PickerViewTest, BackgroundIsCorrect) { FakePickerViewDelegate delegate; - auto widget = - PickerView::CreateWidget(kDefaultCaretBounds, kDefaultCursorPoint, - kDefaultFocusedWindowBounds, &delegate); + auto widget = PickerWidget::Create(&delegate, kDefaultAnchorBounds); PickerView* view = GetPickerViewFromWidget(*widget); ASSERT_TRUE(view); @@ -169,9 +146,7 @@ TEST_F(PickerViewTest, SizeIsCorrect) { FakePickerViewDelegate delegate; - auto widget = - PickerView::CreateWidget(kDefaultCaretBounds, kDefaultCursorPoint, - kDefaultFocusedWindowBounds, &delegate); + auto widget = PickerWidget::Create(&delegate, kDefaultAnchorBounds); widget->Show(); PickerView* view = GetPickerViewFromWidget(*widget); @@ -180,9 +155,7 @@ TEST_F(PickerViewTest, ShowsZeroStateView) { FakePickerViewDelegate delegate; - auto widget = - PickerView::CreateWidget(kDefaultCaretBounds, kDefaultCursorPoint, - kDefaultFocusedWindowBounds, &delegate); + auto widget = PickerWidget::Create(&delegate, kDefaultAnchorBounds); PickerView* view = GetPickerViewFromWidget(*widget); EXPECT_THAT(view->search_field_view_for_testing(), @@ -195,9 +168,7 @@ TEST_F(PickerViewTest, NonEmptySearchFieldContentsSwitchesToSearchResultsView) { FakePickerViewDelegate delegate; - auto widget = - PickerView::CreateWidget(kDefaultCaretBounds, kDefaultCursorPoint, - kDefaultFocusedWindowBounds, &delegate); + auto widget = PickerWidget::Create(&delegate, kDefaultAnchorBounds); widget->Show(); PickerView* view = GetPickerViewFromWidget(*widget); @@ -211,9 +182,7 @@ TEST_F(PickerViewTest, EmptySearchFieldContentsSwitchesToZeroStateView) { FakePickerViewDelegate delegate; - auto widget = - PickerView::CreateWidget(kDefaultCaretBounds, kDefaultCursorPoint, - kDefaultFocusedWindowBounds, &delegate); + auto widget = PickerWidget::Create(&delegate, kDefaultAnchorBounds); widget->Show(); PickerView* view = GetPickerViewFromWidget(*widget); PressAndReleaseKey(ui::KeyboardCode::VKEY_A, ui::EF_NONE); @@ -236,9 +205,7 @@ {{PickerSearchResult::Text(u"result")}}), }); })); - auto widget = - PickerView::CreateWidget(kDefaultCaretBounds, kDefaultCursorPoint, - kDefaultFocusedWindowBounds, &delegate); + auto widget = PickerWidget::Create(&delegate, kDefaultAnchorBounds); widget->Show(); PickerView* view = GetPickerViewFromWidget(*widget); PressAndReleaseKey(ui::KeyboardCode::VKEY_A, ui::EF_NONE); @@ -263,9 +230,7 @@ TEST_F(PickerViewTest, SwitchesToCategoryView) { FakePickerViewDelegate delegate; - auto widget = - PickerView::CreateWidget(kDefaultCaretBounds, kDefaultCursorPoint, - kDefaultFocusedWindowBounds, &delegate); + auto widget = PickerWidget::Create(&delegate, kDefaultAnchorBounds); widget->Show(); PickerView* picker_view = GetPickerViewFromWidget(*widget); @@ -291,9 +256,7 @@ PickerCategory::kBrowsingHistory)}}), }); })); - auto widget = - PickerView::CreateWidget(kDefaultCaretBounds, kDefaultCursorPoint, - kDefaultFocusedWindowBounds, &delegate); + auto widget = PickerWidget::Create(&delegate, kDefaultAnchorBounds); widget->Show(); PressAndReleaseKey(ui::KeyboardCode::VKEY_A, ui::EF_NONE); ASSERT_TRUE(search_called.Wait()); @@ -313,9 +276,7 @@ TEST_F(PickerViewTest, SelectingCategoryUpdatesSearchFieldPlaceholderText) { FakePickerViewDelegate delegate; - auto widget = - PickerView::CreateWidget(kDefaultCaretBounds, kDefaultCursorPoint, - kDefaultFocusedWindowBounds, &delegate); + auto widget = PickerWidget::Create(&delegate, kDefaultAnchorBounds); widget->Show(); PickerView* picker_view = GetPickerViewFromWidget(*widget); @@ -334,9 +295,7 @@ TEST_F(PickerViewTest, SearchingWithCategorySwitchesToSearchResultsView) { FakePickerViewDelegate delegate; - auto widget = - PickerView::CreateWidget(kDefaultCaretBounds, kDefaultCursorPoint, - kDefaultFocusedWindowBounds, &delegate); + auto widget = PickerWidget::Create(&delegate, kDefaultAnchorBounds); widget->Show(); // Switch to category view. @@ -356,9 +315,7 @@ TEST_F(PickerViewTest, EmptySearchFieldSwitchesBackToCategoryView) { FakePickerViewDelegate delegate; - auto widget = - PickerView::CreateWidget(kDefaultCaretBounds, kDefaultCursorPoint, - kDefaultFocusedWindowBounds, &delegate); + auto widget = PickerWidget::Create(&delegate, kDefaultAnchorBounds); widget->Show(); // Switch to category view. @@ -384,9 +341,7 @@ [&](FakePickerViewDelegate::SearchResultsCallback callback) { search_called.SetValue(); })); - auto widget = - PickerView::CreateWidget(kDefaultCaretBounds, kDefaultCursorPoint, - kDefaultFocusedWindowBounds, &delegate); + auto widget = PickerWidget::Create(&delegate, kDefaultAnchorBounds); widget->Show(); PickerView* picker_view = GetPickerViewFromWidget(*widget); @@ -395,7 +350,9 @@ // Results page should be empty until results arrive. EXPECT_TRUE(picker_view->search_results_view_for_testing().GetVisible()); - EXPECT_THAT(picker_view->search_results_view_for_testing().children(), + EXPECT_THAT(picker_view->search_results_view_for_testing() + .section_list_view_for_testing() + ->children(), IsEmpty()); } @@ -407,9 +364,7 @@ search_callback = std::move(callback); search_called.SetValue(); })); - auto widget = - PickerView::CreateWidget(kDefaultCaretBounds, kDefaultCursorPoint, - kDefaultFocusedWindowBounds, &delegate); + auto widget = PickerWidget::Create(&delegate, kDefaultAnchorBounds); widget->Show(); PickerView* picker_view = GetPickerViewFromWidget(*widget); @@ -443,9 +398,7 @@ search2_called.SetValue(); } })); - auto widget = - PickerView::CreateWidget(kDefaultCaretBounds, kDefaultCursorPoint, - kDefaultFocusedWindowBounds, &delegate); + auto widget = PickerWidget::Create(&delegate, kDefaultAnchorBounds); widget->Show(); PickerView* picker_view = GetPickerViewFromWidget(*widget); @@ -482,9 +435,7 @@ search2_called.SetValue(); } })); - auto widget = - PickerView::CreateWidget(kDefaultCaretBounds, kDefaultCursorPoint, - kDefaultFocusedWindowBounds, &delegate); + auto widget = PickerWidget::Create(&delegate, kDefaultAnchorBounds); widget->Show(); PickerView* picker_view = GetPickerViewFromWidget(*widget); @@ -518,9 +469,7 @@ {{PickerSearchResult::Text(u"result")}}), }); })); - auto widget = - PickerView::CreateWidget(kDefaultCaretBounds, kDefaultCursorPoint, - kDefaultFocusedWindowBounds, &delegate); + auto widget = PickerWidget::Create(&delegate, kDefaultAnchorBounds); widget->Show(); PickerView* picker_view = GetPickerViewFromWidget(*widget); @@ -531,15 +480,15 @@ PressAndReleaseKey(ui::KeyboardCode::VKEY_BACK, ui::EF_NONE); EXPECT_FALSE(picker_view->search_results_view_for_testing().GetVisible()); - EXPECT_THAT(picker_view->search_results_view_for_testing().children(), + EXPECT_THAT(picker_view->search_results_view_for_testing() + .section_list_view_for_testing() + ->children(), IsEmpty()); } TEST_F(PickerViewTest, PressingEscClosesPickerWidget) { FakePickerViewDelegate delegate; - auto widget = - PickerView::CreateWidget(kDefaultCaretBounds, kDefaultCursorPoint, - kDefaultFocusedWindowBounds, &delegate); + auto widget = PickerWidget::Create(&delegate, kDefaultAnchorBounds); widget->Show(); PressAndReleaseKey(ui::KeyboardCode::VKEY_ESCAPE, ui::EF_NONE); @@ -547,21 +496,6 @@ EXPECT_TRUE(widget->IsClosed()); } -TEST_F(PickerViewTest, ClickingOutsideClosesPickerWidget) { - FakePickerViewDelegate delegate; - auto widget = - PickerView::CreateWidget(kDefaultCaretBounds, kDefaultCursorPoint, - kDefaultFocusedWindowBounds, &delegate); - widget->Show(); - - gfx::Point point_outside_widget = widget->GetWindowBoundsInScreen().origin(); - point_outside_widget.Offset(-10, -10); - GetEventGenerator()->MoveMouseTo(point_outside_widget); - GetEventGenerator()->ClickLeftButton(); - - EXPECT_TRUE(widget->IsClosed()); -} - TEST_F(PickerViewTest, RecordsSearchLatencyAfterSearchFinished) { base::HistogramTester histogram; FakePickerViewDelegate delegate(base::BindLambdaForTesting( @@ -569,9 +503,7 @@ task_environment()->FastForwardBy(base::Seconds(1)); callback.Run({}); })); - auto widget = - PickerView::CreateWidget(kDefaultCaretBounds, kDefaultCursorPoint, - kDefaultFocusedWindowBounds, &delegate); + auto widget = PickerWidget::Create(&delegate, kDefaultAnchorBounds); widget->Show(); PressAndReleaseKey(ui::KeyboardCode::VKEY_A, ui::EF_NONE); @@ -580,162 +512,110 @@ base::Seconds(1), 1); } -TEST_F(PickerViewTest, BoundsDefaultAlignedWithCaret) { +TEST_F(PickerViewTest, BoundsDefaultAlignedWithAnchor) { FakePickerViewDelegate delegate; - auto widget = - PickerView::CreateWidget(kDefaultCaretBounds, kDefaultCursorPoint, - kDefaultFocusedWindowBounds, &delegate); + auto widget = PickerWidget::Create(&delegate, kDefaultAnchorBounds); widget->Show(); PickerView* view = GetPickerViewFromWidget(*widget); // Should be entirely on screen. EXPECT_TRUE(display::Screen::GetScreen() - ->GetDisplayMatching(kDefaultCaretBounds) + ->GetDisplayMatching(kDefaultAnchorBounds) .work_area() .Contains(view->GetBoundsInScreen())); - // Should be to the right of the caret. - EXPECT_GT(view->GetBoundsInScreen().x(), kDefaultCaretBounds.right()); - // Center of the search field should be vertically aligned with the caret. + // Should be to the right of the anchor. + EXPECT_EQ(view->GetBoundsInScreen().x(), kDefaultAnchorBounds.right()); + // Center of the search field should be vertically aligned with the anchor. EXPECT_EQ(view->search_field_view_for_testing() .GetBoundsInScreen() .CenterPoint() .y(), - kDefaultCaretBounds.CenterPoint().y()); + kDefaultAnchorBounds.CenterPoint().y()); } -TEST_F(PickerViewTest, BoundsAlignedWithCaretNearTopLeftOfScreen) { +TEST_F(PickerViewTest, BoundsAlignedWithAnchorNearTopLeftOfScreen) { FakePickerViewDelegate delegate; const gfx::Rect screen_work_area = display::Screen::GetScreen()->GetPrimaryDisplay().work_area(); - gfx::Rect caret_bounds(screen_work_area.origin(), {0, 10}); - caret_bounds.Offset(80, 80); + gfx::Rect anchor_bounds(screen_work_area.origin(), {0, 10}); + anchor_bounds.Offset(80, 80); - auto widget = PickerView::CreateWidget(caret_bounds, kDefaultCursorPoint, - screen_work_area, &delegate); + auto widget = PickerWidget::Create(&delegate, anchor_bounds); widget->Show(); PickerView* view = GetPickerViewFromWidget(*widget); // Should be entirely on screen. EXPECT_TRUE(screen_work_area.Contains(view->GetBoundsInScreen())); - // Should be to the right of the caret. - EXPECT_GT(view->GetBoundsInScreen().x(), caret_bounds.right()); - // Center of the search field should be vertically aligned with the caret. + // Should be to the right of the anchor. + EXPECT_EQ(view->GetBoundsInScreen().x(), anchor_bounds.right()); + // Center of the search field should be vertically aligned with the anchor. EXPECT_EQ(view->search_field_view_for_testing() .GetBoundsInScreen() .CenterPoint() .y(), - caret_bounds.CenterPoint().y()); + anchor_bounds.CenterPoint().y()); } -TEST_F(PickerViewTest, BoundsAlignedWithCaretNearBottomLeftOfScreen) { +TEST_F(PickerViewTest, BoundsAlignedWithAnchorNearBottomLeftOfScreen) { FakePickerViewDelegate delegate; const gfx::Rect screen_work_area = display::Screen::GetScreen()->GetPrimaryDisplay().work_area(); - gfx::Rect caret_bounds(screen_work_area.bottom_left(), {0, 10}); - caret_bounds.Offset(80, -80); + gfx::Rect anchor_bounds(screen_work_area.bottom_left(), {0, 10}); + anchor_bounds.Offset(80, -80); - auto widget = PickerView::CreateWidget(caret_bounds, kDefaultCursorPoint, - screen_work_area, &delegate); + auto widget = PickerWidget::Create(&delegate, anchor_bounds); widget->Show(); PickerView* view = GetPickerViewFromWidget(*widget); // Should be entirely on screen. EXPECT_TRUE(screen_work_area.Contains(view->GetBoundsInScreen())); - // Should be to the right of the caret. - EXPECT_GT(view->GetBoundsInScreen().x(), caret_bounds.right()); - // Center of the search field should be vertically aligned with the caret. + // Should be to the right of the anchor. + EXPECT_EQ(view->GetBoundsInScreen().x(), anchor_bounds.right()); + // Center of the search field should be vertically aligned with the anchor. EXPECT_EQ(view->search_field_view_for_testing() .GetBoundsInScreen() .CenterPoint() .y(), - caret_bounds.CenterPoint().y()); + anchor_bounds.CenterPoint().y()); } -TEST_F(PickerViewTest, BoundsBelowCaretForCaretNearTopRightOfScreen) { +TEST_F(PickerViewTest, BoundsBelowAnchorForAnchorNearTopRightOfScreen) { FakePickerViewDelegate delegate; const gfx::Rect screen_work_area = display::Screen::GetScreen()->GetPrimaryDisplay().work_area(); - gfx::Rect caret_bounds(screen_work_area.top_right(), {0, 10}); - caret_bounds.Offset(-20, 20); + gfx::Rect anchor_bounds(screen_work_area.top_right(), {0, 10}); + anchor_bounds.Offset(-20, 20); - auto widget = PickerView::CreateWidget(caret_bounds, kDefaultCursorPoint, - screen_work_area, &delegate); + auto widget = PickerWidget::Create(&delegate, anchor_bounds); widget->Show(); const PickerView* view = GetPickerViewFromWidget(*widget); // Should be entirely on screen. EXPECT_TRUE(screen_work_area.Contains(view->GetBoundsInScreen())); - // Should be below the caret. - EXPECT_GT(view->GetBoundsInScreen().y(), caret_bounds.bottom()); + // Should be below the anchor. + EXPECT_EQ(view->GetBoundsInScreen().y(), anchor_bounds.bottom()); } -TEST_F(PickerViewTest, BoundsAboveCaretForCaretNearBottomRightOfScreen) { +TEST_F(PickerViewTest, BoundsAboveAnchorForAnchorNearBottomRightOfScreen) { FakePickerViewDelegate delegate; const gfx::Rect screen_work_area = display::Screen::GetScreen()->GetPrimaryDisplay().work_area(); - gfx::Rect caret_bounds(screen_work_area.bottom_right(), {0, 10}); - caret_bounds.Offset(-20, -20); + gfx::Rect anchor_bounds(screen_work_area.bottom_right(), {0, 10}); + anchor_bounds.Offset(-20, -20); - auto widget = PickerView::CreateWidget(caret_bounds, kDefaultCursorPoint, - screen_work_area, &delegate); + auto widget = PickerWidget::Create(&delegate, anchor_bounds); widget->Show(); const PickerView* view = GetPickerViewFromWidget(*widget); // Should be entirely on screen. EXPECT_TRUE(screen_work_area.Contains(view->GetBoundsInScreen())); - // Should be above the caret. - EXPECT_LT(view->GetBoundsInScreen().bottom(), caret_bounds.y()); + // Should be above the anchor. + EXPECT_EQ(view->GetBoundsInScreen().bottom(), anchor_bounds.y()); } -TEST_F(PickerViewTest, BoundsAlignedWithCursorForEmptyCaretBounds) { +TEST_F(PickerViewTest, BoundsOnScreenForEmptyAnchorBounds) { FakePickerViewDelegate delegate; - auto widget = PickerView::CreateWidget( - gfx::Rect(), kDefaultCursorPoint, kDefaultFocusedWindowBounds, &delegate); - widget->Show(); - - PickerView* view = GetPickerViewFromWidget(*widget); - // Should be entirely on screen. - EXPECT_TRUE(display::Screen::GetScreen() - ->GetDisplayNearestPoint(kDefaultCursorPoint) - .work_area() - .Contains(view->GetBoundsInScreen())); - // Should be to the right of the cursor. - EXPECT_GE(view->GetBoundsInScreen().x(), kDefaultCursorPoint.x()); - // Center of the search field should be vertically aligned with the cursor. - EXPECT_EQ(view->search_field_view_for_testing() - .GetBoundsInScreen() - .CenterPoint() - .y(), - kDefaultCursorPoint.y()); -} - -TEST_F(PickerViewTest, BoundsAlignedWithCursorForCaretOutsideFocusedWindow) { - FakePickerViewDelegate delegate; - auto widget = PickerView::CreateWidget( - /*caret_bounds=*/gfx::Rect(10, 10, 0, 10), kDefaultCursorPoint, - /*focused_window_bounds=*/gfx::Rect(100, 100, 300, 300), &delegate); - widget->Show(); - - PickerView* view = GetPickerViewFromWidget(*widget); - // Should be entirely on screen. - EXPECT_TRUE(display::Screen::GetScreen() - ->GetDisplayNearestPoint(kDefaultCursorPoint) - .work_area() - .Contains(view->GetBoundsInScreen())); - // Should be to the right of the cursor. - EXPECT_GE(view->GetBoundsInScreen().x(), kDefaultCursorPoint.x()); - // Center of the search field should be vertically aligned with the cursor. - EXPECT_EQ(view->search_field_view_for_testing() - .GetBoundsInScreen() - .CenterPoint() - .y(), - kDefaultCursorPoint.y()); -} - -TEST_F(PickerViewTest, BoundsOnScreenForEmptyCaretAndEmptyCursor) { - FakePickerViewDelegate delegate; - auto widget = PickerView::CreateWidget( - gfx::Rect(), gfx::Point(), kDefaultFocusedWindowBounds, &delegate); + auto widget = PickerWidget::Create(&delegate, gfx::Rect()); widget->Show(); const PickerView* view = GetPickerViewFromWidget(*widget); @@ -748,11 +628,10 @@ FakePickerViewDelegate delegate; const gfx::Rect screen_work_area = display::Screen::GetScreen()->GetPrimaryDisplay().work_area(); - gfx::Rect caret_bounds(screen_work_area.top_center(), {0, 10}); - caret_bounds.Offset(0, 80); + gfx::Rect anchor_bounds(screen_work_area.top_center(), {0, 10}); + anchor_bounds.Offset(0, 80); - auto widget = PickerView::CreateWidget(caret_bounds, kDefaultCursorPoint, - screen_work_area, &delegate); + auto widget = PickerWidget::Create(&delegate, anchor_bounds); widget->Show(); PickerView* view = GetPickerViewFromWidget(*widget); @@ -764,11 +643,10 @@ FakePickerViewDelegate delegate; const gfx::Rect screen_work_area = display::Screen::GetScreen()->GetPrimaryDisplay().work_area(); - gfx::Rect caret_bounds(screen_work_area.bottom_center(), {0, 10}); - caret_bounds.Offset(0, -80); + gfx::Rect anchor_bounds(screen_work_area.bottom_center(), {0, 10}); + anchor_bounds.Offset(0, -80); - auto widget = PickerView::CreateWidget(caret_bounds, kDefaultCursorPoint, - screen_work_area, &delegate); + auto widget = PickerWidget::Create(&delegate, anchor_bounds); widget->Show(); PickerView* view = GetPickerViewFromWidget(*widget); @@ -778,9 +656,7 @@ TEST_F(PickerViewTest, ShowsEmojiPickerWhenClickingOnEmoji) { FakePickerViewDelegate delegate; - auto widget = - PickerView::CreateWidget(kDefaultCaretBounds, kDefaultCursorPoint, - kDefaultFocusedWindowBounds, &delegate); + auto widget = PickerWidget::Create(&delegate, kDefaultAnchorBounds); widget->Show(); bool called = false; ui::SetShowEmojiKeyboardCallback(base::BindLambdaForTesting( @@ -801,9 +677,7 @@ PickerSearchResultsSection(PickerSectionType::kExpressions, {}), }); })); - auto widget = - PickerView::CreateWidget(kDefaultCaretBounds, kDefaultCursorPoint, - kDefaultFocusedWindowBounds, &delegate); + auto widget = PickerWidget::Create(&delegate, kDefaultAnchorBounds); widget->Show(); PickerView* view = GetPickerViewFromWidget(*widget); PressAndReleaseKey(ui::KeyboardCode::VKEY_A, ui::EF_NONE); @@ -814,26 +688,73 @@ EXPECT_EQ(delegate.last_inserted_result(), std::nullopt); } -TEST_F(PickerViewTest, PressingEnterSelectsSearchResult) { +TEST_F(PickerViewTest, PressingEnterDefaultSelectsFirstSearchResult) { base::test::TestFuture<void> future; FakePickerViewDelegate delegate(base::BindLambdaForTesting( [&](FakePickerViewDelegate::SearchResultsCallback callback) { future.SetValue(); callback.Run({ PickerSearchResultsSection(PickerSectionType::kExpressions, - {{PickerSearchResult::Text(u"result")}}), + {{PickerSearchResult::Emoji(u"😊"), + PickerSearchResult::Symbol(u"♬")}}), }); })); - auto widget = - PickerView::CreateWidget(kDefaultCaretBounds, kDefaultCursorPoint, - kDefaultFocusedWindowBounds, &delegate); + auto widget = PickerWidget::Create(&delegate, kDefaultAnchorBounds); widget->Show(); PressAndReleaseKey(ui::KeyboardCode::VKEY_A, ui::EF_NONE); ASSERT_TRUE(future.Wait()); PressAndReleaseKey(ui::KeyboardCode::VKEY_RETURN, ui::EF_NONE); EXPECT_THAT(delegate.last_inserted_result(), - Optional(PickerSearchResult::Text(u"result"))); + Optional(PickerSearchResult::Emoji(u"😊"))); +} + +TEST_F(PickerViewTest, RightArrowKeyNavigatesSearchResults) { + base::test::TestFuture<void> future; + FakePickerViewDelegate delegate(base::BindLambdaForTesting( + [&](FakePickerViewDelegate::SearchResultsCallback callback) { + future.SetValue(); + callback.Run({ + PickerSearchResultsSection(PickerSectionType::kExpressions, + {{PickerSearchResult::Emoji(u"😊"), + PickerSearchResult::Symbol(u"♬")}}), + }); + })); + auto widget = PickerWidget::Create(&delegate, kDefaultAnchorBounds); + widget->Show(); + PressAndReleaseKey(ui::KeyboardCode::VKEY_A, ui::EF_NONE); + ASSERT_TRUE(future.Wait()); + PressAndReleaseKey(ui::KeyboardCode::VKEY_RIGHT, ui::EF_NONE); + PressAndReleaseKey(ui::KeyboardCode::VKEY_RETURN, ui::EF_NONE); + + EXPECT_THAT(delegate.last_inserted_result(), + Optional(PickerSearchResult::Symbol(u"♬"))); +} + +TEST_F(PickerViewTest, DownArrowKeyNavigatesSearchResults) { + base::test::TestFuture<void> future; + FakePickerViewDelegate delegate(base::BindLambdaForTesting( + [&](FakePickerViewDelegate::SearchResultsCallback callback) { + future.SetValue(); + callback.Run({ + PickerSearchResultsSection( + PickerSectionType::kCategories, + {{PickerSearchResult::BrowsingHistory(GURL("http://foo.com"), + u"Foo", ui::ImageModel()), + PickerSearchResult::BrowsingHistory( + GURL("http://bar.com"), u"Bar", ui::ImageModel())}}), + }); + })); + auto widget = PickerWidget::Create(&delegate, kDefaultAnchorBounds); + widget->Show(); + PressAndReleaseKey(ui::KeyboardCode::VKEY_A, ui::EF_NONE); + ASSERT_TRUE(future.Wait()); + PressAndReleaseKey(ui::KeyboardCode::VKEY_DOWN, ui::EF_NONE); + PressAndReleaseKey(ui::KeyboardCode::VKEY_RETURN, ui::EF_NONE); + + EXPECT_THAT(delegate.last_inserted_result(), + Optional(PickerSearchResult::BrowsingHistory( + GURL("http://bar.com"), u"Bar", ui::ImageModel()))); } } // namespace
diff --git a/ash/picker/views/picker_widget.cc b/ash/picker/views/picker_widget.cc new file mode 100644 index 0000000..5fb981e --- /dev/null +++ b/ash/picker/views/picker_widget.cc
@@ -0,0 +1,90 @@ +// Copyright 2024 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ash/picker/views/picker_widget.h" + +#include "ash/bubble/bubble_event_filter.h" +#include "ash/picker/views/picker_view.h" +#include "base/functional/bind.h" +#include "base/memory/ptr_util.h" +#include "ui/display/screen.h" +#include "ui/events/event.h" +#include "ui/gfx/geometry/insets.h" +#include "ui/gfx/geometry/rect.h" +#include "ui/gfx/geometry/size.h" +#include "ui/views/view_utils.h" +#include "ui/views/widget/unique_widget_ptr.h" +#include "ui/views/widget/widget.h" + +namespace ash { +namespace { + +constexpr gfx::Size kPickerSize(320, 340); + +// Gets the preferred layout to use given `anchor_bounds` in screen coordinates. +PickerView::PickerLayoutType GetLayoutType(const gfx::Rect& anchor_bounds) { + return anchor_bounds.bottom() + kPickerSize.height() <= + display::Screen::GetScreen() + ->GetDisplayMatching(anchor_bounds) + .work_area() + .bottom() + ? PickerView::PickerLayoutType::kResultsBelowSearchField + : PickerView::PickerLayoutType::kResultsAboveSearchField; +} + +views::Widget::InitParams CreateInitParams( + PickerViewDelegate* delegate, + const gfx::Rect& anchor_bounds, + const base::TimeTicks trigger_event_timestamp) { + const PickerView::PickerLayoutType layout_type = GetLayoutType(anchor_bounds); + auto picker_view = std::make_unique<PickerView>(delegate, layout_type, + trigger_event_timestamp); + picker_view->SetSize(kPickerSize); + + views::Widget::InitParams params; + params.activatable = views::Widget::InitParams::Activatable::kYes; + params.shadow_type = views::Widget::InitParams::ShadowType::kNone; + params.opacity = views::Widget::InitParams::WindowOpacity::kTranslucent; + params.type = views::Widget::InitParams::TYPE_BUBBLE; + params.z_order = ui::ZOrderLevel::kFloatingUIElement; + params.bounds = picker_view->GetTargetBounds(anchor_bounds, layout_type); + // TODO(b/309706053): Replace this with the finalized string. + params.name = "Picker"; + params.delegate = picker_view.release(); + return params; +} + +} // namespace + +views::UniqueWidgetPtr PickerWidget::Create( + PickerViewDelegate* delegate, + const gfx::Rect& anchor_bounds, + base::TimeTicks trigger_event_timestamp) { + return base::WrapUnique( + new PickerWidget(delegate, anchor_bounds, trigger_event_timestamp)); +} + +PickerWidget::PickerWidget(PickerViewDelegate* delegate, + const gfx::Rect& anchor_bounds, + base::TimeTicks trigger_event_timestamp) + : views::Widget( + CreateInitParams(delegate, anchor_bounds, trigger_event_timestamp)), + bubble_event_filter_( + /*widget=*/this, + /*button=*/nullptr, + // base::Unretained is safe here because this class owns + // `bubble_event_filter_. + base::BindRepeating(&PickerWidget::OnClickOutsideWidget, + base::Unretained(this))) { + SetVisibilityAnimationTransition( + views::Widget::VisibilityTransition::ANIMATE_HIDE); +} + +void PickerWidget::OnClickOutsideWidget(const ui::LocatedEvent& event) { + Close(); +} + +PickerWidget::~PickerWidget() = default; + +} // namespace ash
diff --git a/ash/picker/views/picker_widget.h b/ash/picker/views/picker_widget.h new file mode 100644 index 0000000..a26ed95 --- /dev/null +++ b/ash/picker/views/picker_widget.h
@@ -0,0 +1,55 @@ +// Copyright 2024 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef ASH_PICKER_VIEWS_PICKER_WIDGET_H_ +#define ASH_PICKER_VIEWS_PICKER_WIDGET_H_ + +#include "ash/ash_export.h" +#include "ash/bubble/bubble_event_filter.h" +#include "base/time/time.h" +#include "ui/views/widget/unique_widget_ptr.h" +#include "ui/views/widget/widget.h" + +namespace gfx { +class Rect; +} // namespace gfx + +namespace ui { +class LocatedEvent; +} + +namespace ash { +class PickerViewDelegate; + +class ASH_EXPORT PickerWidget : public views::Widget { + public: + PickerWidget(const PickerWidget&) = delete; + PickerWidget& operator=(const PickerWidget&) = delete; + ~PickerWidget() override; + + // `delegate` must remain valid for the lifetime of the created Widget. + // `anchor_bounds` is in screen coordinates. + // `trigger_event_timestamp` is the timestamp of the event that triggered the + // Widget to be created. For example, if the feature was triggered by a mouse + // click, then it should be the timestamp of the click. By default, the + // timestamp is the time this function is called. + static views::UniqueWidgetPtr Create( + PickerViewDelegate* delegate, + const gfx::Rect& anchor_bounds, + base::TimeTicks trigger_event_timestamp = base::TimeTicks::Now()); + + private: + explicit PickerWidget(PickerViewDelegate* delegate, + const gfx::Rect& anchor_bounds, + base::TimeTicks trigger_event_timestamp); + + void OnClickOutsideWidget(const ui::LocatedEvent& event); + + // Used to close the Picker widget when the user clicks outside of it. + BubbleEventFilter bubble_event_filter_; +}; + +} // namespace ash + +#endif // ASH_PICKER_VIEWS_PICKER_VIEW_H_
diff --git a/ash/picker/views/picker_widget_unittest.cc b/ash/picker/views/picker_widget_unittest.cc new file mode 100644 index 0000000..1cb7a73 --- /dev/null +++ b/ash/picker/views/picker_widget_unittest.cc
@@ -0,0 +1,81 @@ +// Copyright 2024 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ash/picker/views/picker_widget.h" + +#include <memory> + +#include "ash/picker/views/picker_view.h" +#include "ash/picker/views/picker_view_delegate.h" +#include "ash/test/ash_test_base.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/events/test/event_generator.h" +#include "ui/gfx/geometry/point.h" +#include "ui/gfx/geometry/rect.h" +#include "ui/views/view_utils.h" +#include "ui/views/widget/widget_utils.h" + +namespace ash { +namespace { + +using ::testing::ElementsAre; +using ::testing::Truly; + +constexpr gfx::Rect kDefaultAnchorBounds(200, 100, 0, 10); + +class FakePickerViewDelegate : public PickerViewDelegate { + public: + // PickerViewDelegate: + std::unique_ptr<AshWebView> CreateWebView( + const AshWebView::InitParams& params) override { + return nullptr; + } + void GetResultsForCategory(PickerCategory category, + SearchResultsCallback callback) override {} + void StartSearch(const std::u16string& query, + std::optional<PickerCategory> category, + SearchResultsCallback callback) override {} + void InsertResultOnNextFocus(const PickerSearchResult& result) override {} + PickerAssetFetcher* GetAssetFetcher() override { return nullptr; } +}; + +using PickerWidgetTest = AshTestBase; + +TEST_F(PickerWidgetTest, CreateWidgetHasCorrectHierarchy) { + FakePickerViewDelegate delegate; + auto widget = PickerWidget::Create(&delegate, kDefaultAnchorBounds); + + // Widget should contain a NonClientView, which has a NonClientFrameView for + // borders and shadows, and a ClientView with a sole child of the PickerView. + ASSERT_TRUE(widget); + ASSERT_TRUE(widget->non_client_view()); + ASSERT_TRUE(widget->non_client_view()->frame_view()); + ASSERT_TRUE(widget->non_client_view()->client_view()); + EXPECT_THAT(widget->non_client_view()->client_view()->children(), + ElementsAre(Truly(views::IsViewClass<PickerView>))); +} + +TEST_F(PickerWidgetTest, CreateWidgetHasCorrectBorder) { + FakePickerViewDelegate delegate; + auto widget = PickerWidget::Create(&delegate, kDefaultAnchorBounds); + + EXPECT_TRUE(widget->non_client_view()->frame_view()->GetBorder()); +} + +TEST_F(PickerWidgetTest, ClickingOutsideClosesPickerWidget) { + FakePickerViewDelegate delegate; + auto widget = PickerWidget::Create(&delegate, kDefaultAnchorBounds); + widget->Show(); + + gfx::Point point_outside_widget = widget->GetWindowBoundsInScreen().origin(); + point_outside_widget.Offset(-10, -10); + GetEventGenerator()->MoveMouseTo(point_outside_widget); + GetEventGenerator()->ClickLeftButton(); + + EXPECT_TRUE(widget->IsClosed()); +} + +} // namespace +} // namespace ash
diff --git a/ash/picker/views/picker_zero_state_view.cc b/ash/picker/views/picker_zero_state_view.cc index f303995..0c04263 100644 --- a/ash/picker/views/picker_zero_state_view.cc +++ b/ash/picker/views/picker_zero_state_view.cc
@@ -15,6 +15,7 @@ #include "ash/picker/views/picker_category_type.h" #include "ash/picker/views/picker_icons.h" #include "ash/picker/views/picker_list_item_view.h" +#include "ash/picker/views/picker_section_list_view.h" #include "ash/picker/views/picker_section_view.h" #include "ash/picker/views/picker_strings.h" #include "ash/public/cpp/picker/picker_category.h" @@ -30,13 +31,14 @@ PickerZeroStateView::PickerZeroStateView( int picker_view_width, - SelectCategoryCallback select_category_callback) - : picker_view_width_(picker_view_width) { + SelectCategoryCallback select_category_callback) { SetLayoutManager(std::make_unique<views::FlexLayout>()) ->SetOrientation(views::LayoutOrientation::kVertical); AddChildView(std::make_unique<PickerCapsNudgeView>()); + section_list_view_ = + AddChildView(std::make_unique<PickerSectionListView>(picker_view_width)); for (auto category : PickerModel().GetAvailableCategories()) { auto item_view = std::make_unique<PickerListItemView>( base::BindRepeating(select_category_callback, category)); @@ -44,14 +46,74 @@ item_view->SetLeadingIcon(GetIconForPickerCategory(category)); GetOrCreateSectionView(category)->AddListItem(std::move(item_view)); } + SetPseudoFocusedItem(section_list_view_->GetTopItem()); } PickerZeroStateView::~PickerZeroStateView() = default; -bool PickerZeroStateView::OnEnterKeyPressed() { - // TODO: b/322900302 - Select the highlighted item or a smart suggestion when - // enter key is pressed. - return false; +bool PickerZeroStateView::DoPseudoFocusedAction() { + if (pseudo_focused_item_ == nullptr) { + return false; + } + + pseudo_focused_item_->SelectItem(); + return true; +} + +bool PickerZeroStateView::MovePseudoFocusUp() { + if (pseudo_focused_item_ == nullptr) { + return false; + } + + PickerItemView* item = section_list_view_->GetItemAbove(pseudo_focused_item_); + if (item == nullptr) { + // If there's no item above, move pseudo focus to the bottom item. + item = section_list_view_->GetBottomItem(); + } + SetPseudoFocusedItem(item); + return true; +} + +bool PickerZeroStateView::MovePseudoFocusDown() { + if (pseudo_focused_item_ == nullptr) { + return false; + } + + PickerItemView* item = section_list_view_->GetItemBelow(pseudo_focused_item_); + if (item == nullptr) { + // If there's no item below, move pseudo focus to the top item. + item = section_list_view_->GetTopItem(); + } + SetPseudoFocusedItem(item); + return true; +} + +bool PickerZeroStateView::MovePseudoFocusLeft() { + if (pseudo_focused_item_ == nullptr) { + return false; + } + + PickerItemView* item = + section_list_view_->GetItemLeftOf(pseudo_focused_item_); + if (item == nullptr) { + return false; + } + SetPseudoFocusedItem(item); + return true; +} + +bool PickerZeroStateView::MovePseudoFocusRight() { + if (pseudo_focused_item_ == nullptr) { + return false; + } + + PickerItemView* item = + section_list_view_->GetItemRightOf(pseudo_focused_item_); + if (item == nullptr) { + return false; + } + SetPseudoFocusedItem(item); + return true; } PickerSectionView* PickerZeroStateView::GetOrCreateSectionView( @@ -62,14 +124,51 @@ return section_view_iterator->second; } - auto* section_view = - AddChildView(std::make_unique<PickerSectionView>(picker_view_width_)); + auto* section_view = section_list_view_->AddSection(); section_view->AddTitleLabel( GetSectionTitleForPickerCategoryType(category_type)); section_views_.insert({category_type, section_view}); return section_view; } +void PickerZeroStateView::SetPseudoFocusedItem(PickerItemView* item) { + if (pseudo_focused_item_ == item) { + return; + } + + if (pseudo_focused_item_ != nullptr) { + pseudo_focused_item_->SetItemState(PickerItemView::ItemState::kNormal); + } + + pseudo_focused_item_ = item; + + if (pseudo_focused_item_ != nullptr) { + pseudo_focused_item_->SetItemState( + PickerItemView::ItemState::kPseudoFocused); + ScrollPseudoFocusedItemToVisible(); + } +} + +void PickerZeroStateView::ScrollPseudoFocusedItemToVisible() { + if (pseudo_focused_item_ == nullptr) { + return; + } + + if (section_list_view_->GetItemAbove(pseudo_focused_item_) == nullptr) { + // For items at the top, scroll all the way up to let users see that they + // have reached the top of the zero state view. + ScrollRectToVisible(gfx::Rect(GetLocalBounds().origin(), gfx::Size())); + } else if (section_list_view_->GetItemBelow(pseudo_focused_item_) == + nullptr) { + // For items at the bottom, scroll all the way down to let users see that + // they have reached the bottom of the zero state view. + ScrollRectToVisible(gfx::Rect(GetLocalBounds().bottom_left(), gfx::Size())); + } else { + // Otherwise, just ensure the item is visible. + pseudo_focused_item_->ScrollViewToVisible(); + } +} + BEGIN_METADATA(PickerZeroStateView) END_METADATA
diff --git a/ash/picker/views/picker_zero_state_view.h b/ash/picker/views/picker_zero_state_view.h index 735ece9..82e51a1 100644 --- a/ash/picker/views/picker_zero_state_view.h +++ b/ash/picker/views/picker_zero_state_view.h
@@ -19,6 +19,8 @@ namespace ash { +class PickerItemView; +class PickerSectionListView; class PickerSectionView; class ASH_EXPORT PickerZeroStateView : public PickerPageView { @@ -36,7 +38,11 @@ ~PickerZeroStateView() override; // PickerPageView: - bool OnEnterKeyPressed() override; + bool DoPseudoFocusedAction() override; + bool MovePseudoFocusUp() override; + bool MovePseudoFocusDown() override; + bool MovePseudoFocusLeft() override; + bool MovePseudoFocusRight() override; std::map<PickerCategoryType, raw_ptr<PickerSectionView>> section_views_for_testing() const { @@ -47,11 +53,19 @@ // Gets or creates the section to contain `category`. PickerSectionView* GetOrCreateSectionView(PickerCategory category); - // Width of the containing PickerView. - int picker_view_width_ = 0; + void SetPseudoFocusedItem(PickerItemView* item); - // The views for each section of categories. + void ScrollPseudoFocusedItemToVisible(); + + // The section list view, contains the section views. + raw_ptr<PickerSectionListView> section_list_view_ = nullptr; + + // Used to track the section view for each category type. std::map<PickerCategoryType, raw_ptr<PickerSectionView>> section_views_; + + // The currently pseudo focused item, which responds to user actions that + // trigger `DoPseudoFocusedAction`. + raw_ptr<PickerItemView> pseudo_focused_item_ = nullptr; }; } // namespace ash
diff --git a/ash/public/cpp/new_window_delegate.h b/ash/public/cpp/new_window_delegate.h index 45d7ae3..5e98a17 100644 --- a/ash/public/cpp/new_window_delegate.h +++ b/ash/public/cpp/new_window_delegate.h
@@ -130,6 +130,9 @@ // Show the Personalization hub. virtual void OpenPersonalizationHub() = 0; + // Shows the a captive portal signin window. + virtual void OpenCaptivePortalSignin(const GURL& url) = 0; + protected: NewWindowDelegate(); NewWindowDelegate(const NewWindowDelegate&) = delete;
diff --git a/ash/public/cpp/picker/picker_client.h b/ash/public/cpp/picker/picker_client.h index a3a4498..122a6a8 100644 --- a/ash/public/cpp/picker/picker_client.h +++ b/ash/public/cpp/picker/picker_client.h
@@ -13,6 +13,7 @@ #include "ash/public/cpp/app_list/app_list_types.h" #include "ash/public/cpp/ash_public_export.h" #include "ash/public/cpp/ash_web_view.h" +#include "ash/public/cpp/picker/picker_category.h" #include "ash/public/cpp/picker/picker_search_result.h" #include "base/functional/callback_forward.h" #include "base/memory/scoped_refptr.h" @@ -52,6 +53,7 @@ // Starts a search using the CrOS Search API // (`app_list::SearchEngine::StartSearch`). virtual void StartCrosSearch(const std::u16string& query, + std::optional<PickerCategory> category, CrosSearchResultsCallback callback) = 0; // Stops a search using the CrOS Search API // (`app_list::SearchEngine::StopQuery`).
diff --git a/ash/public/cpp/picker/picker_search_result.cc b/ash/public/cpp/picker/picker_search_result.cc index f0a40b6..22769b7 100644 --- a/ash/public/cpp/picker/picker_search_result.cc +++ b/ash/public/cpp/picker/picker_search_result.cc
@@ -26,13 +26,17 @@ bool PickerSearchResult::EmoticonData::operator==( const PickerSearchResult::EmoticonData&) const = default; -PickerSearchResult::GifData::GifData(const GURL& url, +PickerSearchResult::GifData::GifData(const GURL& preview_url, const GURL& preview_image_url, - const gfx::Size& dimensions, + const gfx::Size& preview_dimensions, + const GURL& full_url, + const gfx::Size& full_dimensions, std::u16string content_description) - : url(url), + : preview_url(preview_url), preview_image_url(preview_image_url), - dimensions(dimensions), + preview_dimensions(preview_dimensions), + full_url(full_url), + full_dimensions(full_dimensions), content_description(std::move(content_description)) {} PickerSearchResult::GifData::GifData(const PickerSearchResult::GifData&) = @@ -75,12 +79,15 @@ return PickerSearchResult(EmoticonData{.emoticon = std::u16string(emoticon)}); } -PickerSearchResult PickerSearchResult::Gif(const GURL& url, +PickerSearchResult PickerSearchResult::Gif(const GURL& preview_url, const GURL& preview_image_url, - const gfx::Size& dimensions, + const gfx::Size& preview_dimensions, + const GURL& full_url, + const gfx::Size& full_dimensions, std::u16string content_description) { - return PickerSearchResult(GifData(url, preview_image_url, dimensions, - std::move(content_description))); + return PickerSearchResult( + GifData(preview_url, preview_image_url, preview_dimensions, full_url, + full_dimensions, std::move(content_description))); } PickerSearchResult PickerSearchResult::BrowsingHistory(const GURL& url,
diff --git a/ash/public/cpp/picker/picker_search_result.h b/ash/public/cpp/picker/picker_search_result.h index cf3873f..3ff9293 100644 --- a/ash/public/cpp/picker/picker_search_result.h +++ b/ash/public/cpp/picker/picker_search_result.h
@@ -46,22 +46,30 @@ }; struct GifData { - GifData(const GURL& url, + GifData(const GURL& preview_url, const GURL& preview_image_url, - const gfx::Size& dimensions, + const gfx::Size& preview_dimensions, + const GURL& full_url, + const gfx::Size& full_dimensions, std::u16string content_description); GifData(const GifData&); GifData& operator=(const GifData&); ~GifData(); - // A url to the gif media source. - GURL url; + // A url to an animated preview gif media source. + GURL preview_url; - // A url to a preview image of the gif media source. + // A url to an unanimated preview image of the gif media source. GURL preview_image_url; - // Width and height of the GIF at `url`. - gfx::Size dimensions; + // Width and height of the GIF at `preview_url`. + gfx::Size preview_dimensions; + + // A url to a full-sized gif media source. + GURL full_url; + + // Width and height of the GIF at `full_url`. + gfx::Size full_dimensions; // A textual description of the content, primarily used for accessibility // features. @@ -103,9 +111,11 @@ static PickerSearchResult Emoji(std::u16string_view emoji); static PickerSearchResult Symbol(std::u16string_view symbol); static PickerSearchResult Emoticon(std::u16string_view emoticon); - static PickerSearchResult Gif(const GURL& url, + static PickerSearchResult Gif(const GURL& preview_url, const GURL& preview_image_url, - const gfx::Size& dimensions, + const gfx::Size& preview_dimensions, + const GURL& full_url, + const gfx::Size& full_dimensions, std::u16string content_description); static PickerSearchResult Category(PickerCategory category);
diff --git a/ash/public/cpp/test/test_new_window_delegate.cc b/ash/public/cpp/test/test_new_window_delegate.cc index 736c63c..0ff2f7af 100644 --- a/ash/public/cpp/test/test_new_window_delegate.cc +++ b/ash/public/cpp/test/test_new_window_delegate.cc
@@ -39,6 +39,7 @@ FeedbackSource source, const std::string& description_template) {} void TestNewWindowDelegate::OpenPersonalizationHub() {} +void TestNewWindowDelegate::OpenCaptivePortalSignin(const GURL& url) {} TestNewWindowDelegateProvider::TestNewWindowDelegateProvider( std::unique_ptr<TestNewWindowDelegate> delegate)
diff --git a/ash/public/cpp/test/test_new_window_delegate.h b/ash/public/cpp/test/test_new_window_delegate.h index 7c33063..d5eb507 100644 --- a/ash/public/cpp/test/test_new_window_delegate.h +++ b/ash/public/cpp/test/test_new_window_delegate.h
@@ -43,6 +43,7 @@ void OpenFeedbackPage(FeedbackSource source, const std::string& description_template) override; void OpenPersonalizationHub() override; + void OpenCaptivePortalSignin(const GURL& url) override; }; // NewWindowDelegateProvider implementation to provide TestNewWindowDelegate.
diff --git a/ash/root_window_controller.h b/ash/root_window_controller.h index bddc854..491a9e6 100644 --- a/ash/root_window_controller.h +++ b/ash/root_window_controller.h
@@ -13,7 +13,6 @@ #include "ash/style/ash_color_provider_source.h" #include "ash/wm/overview/overview_metrics.h" #include "ash/wm/overview/overview_types.h" -#include "ash/wm/splitview/split_view_overview_session.h" #include "ash/wm/wm_metrics.h" #include "base/memory/raw_ptr.h" #include "ui/aura/window_tree_host.h" @@ -61,6 +60,7 @@ class WindowParentingController; class WorkAreaInsets; enum class LoginStatus; +enum class SplitViewOverviewSessionExitPoint; namespace curtain { class SecurityCurtainWidgetController;
diff --git a/ash/search_box/search_box_view_base.cc b/ash/search_box/search_box_view_base.cc index 58e8a2c..de088fe 100644 --- a/ash/search_box/search_box_view_base.cc +++ b/ash/search_box/search_box_view_base.cc
@@ -89,7 +89,7 @@ gfx::Insets border_insets, ui::ColorId color_id) { label->SetHorizontalAlignment(gfx::ALIGN_LEFT); - label->GetViewAccessibility().OverrideIsIgnored(true); + label->GetViewAccessibility().SetIsIgnored(true); label->SetBackgroundColor(SK_ColorTRANSPARENT); label->SetAutoColorReadabilityEnabled(false); label->SetEnabledColorId(color_id); @@ -243,8 +243,8 @@ // alert, so we ignored the search box in those cases. Now reset the flag // here. auto& accessibility = GetViewAccessibility(); - if (accessibility.IsIgnored()) { - accessibility.OverrideIsIgnored(false); + if (accessibility.GetIsIgnored()) { + accessibility.SetIsIgnored(false); NotifyAccessibilityEvent(ax::mojom::Event::kTreeChanged, true); } }
diff --git a/ash/shelf/drag_handle.h b/ash/shelf/drag_handle.h index fc051f56..90814c5 100644 --- a/ash/shelf/drag_handle.h +++ b/ash/shelf/drag_handle.h
@@ -19,6 +19,7 @@ #include "base/scoped_observation.h" #include "base/timer/timer.h" #include "ui/base/metadata/metadata_header_macros.h" +#include "ui/compositor/layer_animation_observer.h" #include "ui/compositor/layer_animator.h" #include "ui/views/controls/button/button.h" #include "ui/views/view_targeter_delegate.h"
diff --git a/ash/shelf/drag_window_from_shelf_controller.cc b/ash/shelf/drag_window_from_shelf_controller.cc index f0611fe..44654a5 100644 --- a/ash/shelf/drag_window_from_shelf_controller.cc +++ b/ash/shelf/drag_window_from_shelf_controller.cc
@@ -3,7 +3,6 @@ // found in the LICENSE file. #include "ash/shelf/drag_window_from_shelf_controller.h" -#include "base/memory/raw_ptr.h" #include <algorithm> @@ -39,12 +38,14 @@ #include "ash/wm/window_util.h" #include "base/functional/bind.h" #include "base/functional/callback_helpers.h" +#include "base/memory/raw_ptr.h" #include "base/metrics/histogram_macros.h" #include "base/ranges/algorithm.h" #include "ui/aura/window_tree_host.h" #include "ui/base/hit_test.h" #include "ui/compositor/layer.h" #include "ui/compositor/layer_animation_observer.h" +#include "ui/compositor/layer_tree_owner.h" #include "ui/compositor/presentation_time_recorder.h" #include "ui/compositor/scoped_layer_animation_settings.h" #include "ui/display/screen.h"
diff --git a/ash/shelf/drag_window_from_shelf_controller.h b/ash/shelf/drag_window_from_shelf_controller.h index 177d29b7..81d57a1 100644 --- a/ash/shelf/drag_window_from_shelf_controller.h +++ b/ash/shelf/drag_window_from_shelf_controller.h
@@ -19,6 +19,7 @@ #include "base/observer_list.h" #include "base/timer/timer.h" #include "ui/aura/window_observer.h" +#include "ui/compositor/layer_tree_owner.h" namespace aura { class Window;
diff --git a/ash/shelf/scrollable_shelf_view.cc b/ash/shelf/scrollable_shelf_view.cc index 6d16cd2..a91c9dd0 100644 --- a/ash/shelf/scrollable_shelf_view.cc +++ b/ash/shelf/scrollable_shelf_view.cc
@@ -112,7 +112,7 @@ // the hidden icon which receives the accessibility focus shows through // scroll animation. So the arrow button is not useful for the spoken // feedback users. The spoken feedback should ignore the arrow button. - GetViewAccessibility().OverrideIsIgnored(/*value=*/true); + GetViewAccessibility().SetIsIgnored(/*value=*/true); } ~ScrollableShelfArrowView() override = default;
diff --git a/ash/shelf/shelf_layout_manager.h b/ash/shelf/shelf_layout_manager.h index d3694f96..69ed59b 100644 --- a/ash/shelf/shelf_layout_manager.h +++ b/ash/shelf/shelf_layout_manager.h
@@ -30,6 +30,7 @@ #include "ash/wm/splitview/split_view_observer.h" #include "ash/wm/wm_default_layout_manager.h" #include "ash/wm/workspace/workspace_types.h" +#include "base/cancelable_callback.h" #include "base/memory/raw_ptr.h" #include "base/memory/weak_ptr.h" #include "base/observer_list.h"
diff --git a/ash/shelf/test/hotseat_state_watcher.cc b/ash/shelf/test/hotseat_state_watcher.cc index d3ec4107..44d7f35 100644 --- a/ash/shelf/test/hotseat_state_watcher.cc +++ b/ash/shelf/test/hotseat_state_watcher.cc
@@ -4,6 +4,8 @@ #include "ash/shelf/test/hotseat_state_watcher.h" +#include "ash/shelf/shelf_layout_manager_observer.h" + namespace ash { HotseatStateWatcher::HotseatStateWatcher(
diff --git a/ash/shelf/test/hotseat_state_watcher.h b/ash/shelf/test/hotseat_state_watcher.h index 15e9f52e..5ccd6c29 100644 --- a/ash/shelf/test/hotseat_state_watcher.h +++ b/ash/shelf/test/hotseat_state_watcher.h
@@ -7,6 +7,7 @@ #include "ash/shelf/shelf_layout_manager.h" #include "base/memory/raw_ptr.h" +#include "base/run_loop.h" #include "testing/gtest/include/gtest/gtest.h" namespace ash {
diff --git a/ash/strings/ash_strings_af.xtb b/ash/strings/ash_strings_af.xtb index 3c76338..ef62113 100644 --- a/ash/strings/ash_strings_af.xtb +++ b/ash/strings/ash_strings_af.xtb
@@ -1896,7 +1896,6 @@ <translation id="8553395910833293175">Reeds toegewys aan alle lessenaars.</translation> <translation id="8555757996376137129">Verwyder huidige lessenaar</translation> <translation id="856298576161209842"><ph name="MANAGER" /> beveel aan dat jy jou <ph name="DEVICE_TYPE" /> opdateer</translation> -<translation id="8563862697512465947">Kennisgewinginstellings</translation> <translation id="8569751806372591456">Hier is ’n paar voorstelle om te probeer</translation> <translation id="857201607579416096">Kieslys is na die skerm se hoek regs onder geskuif.</translation> <translation id="8581946341807941670">Druk <ph name="MODIFIER_1" /><ph name="MODIFIER_2" /> en klik ’n skakel</translation>
diff --git a/ash/strings/ash_strings_am.xtb b/ash/strings/ash_strings_am.xtb index 4e54924..0c2378dd 100644 --- a/ash/strings/ash_strings_am.xtb +++ b/ash/strings/ash_strings_am.xtb
@@ -1896,7 +1896,6 @@ <translation id="8553395910833293175">ለሁሉም ዴስኮች ቀድሞውኑ ተመድቧል።</translation> <translation id="8555757996376137129">የአሁኑን ዴስክ አስወግድ</translation> <translation id="856298576161209842"><ph name="MANAGER" /> የእርስዎን <ph name="DEVICE_TYPE" /> እንዲያዘምኑ ይመክራል</translation> -<translation id="8563862697512465947">የማስታወቂያ ቅንብሮች </translation> <translation id="8569751806372591456">የመሞከሪያ ጥቂት ጥቆማዎች እነሆ</translation> <translation id="857201607579416096">ምናሌ ወደ የማያ ገጹ የታችኛው ቀኝ ጥግ ተወስዷል።</translation> <translation id="8581946341807941670"><ph name="MODIFIER_1" /><ph name="MODIFIER_2" />ን ይጫኑ እና አገናኝ ላይ ጠቅ ያድርጉ</translation>
diff --git a/ash/strings/ash_strings_ar.xtb b/ash/strings/ash_strings_ar.xtb index 666da0b..bce7f3b0 100644 --- a/ash/strings/ash_strings_ar.xtb +++ b/ash/strings/ash_strings_ar.xtb
@@ -1898,7 +1898,6 @@ <translation id="8553395910833293175">سبقَ نقل المحتوى المرئي إلى جميع أسطح المكتب.</translation> <translation id="8555757996376137129">إزالة سطح المكتب الحالي</translation> <translation id="856298576161209842">يقترح <ph name="MANAGER" /> عليك تحديث جهاز <ph name="DEVICE_TYPE" />.</translation> -<translation id="8563862697512465947">إعدادات الاشعارات</translation> <translation id="8569751806372591456">إليك بعض الاقتراحات للتجربة:</translation> <translation id="857201607579416096">تم نقل القائمة إلى أسفل يسار الشاشة.</translation> <translation id="8581946341807941670">اضغط على <ph name="MODIFIER_1" /><ph name="MODIFIER_2" /> ثم انقر على الرابط.</translation>
diff --git a/ash/strings/ash_strings_as.xtb b/ash/strings/ash_strings_as.xtb index 34f058e..7c3300c 100644 --- a/ash/strings/ash_strings_as.xtb +++ b/ash/strings/ash_strings_as.xtb
@@ -1895,7 +1895,6 @@ <translation id="8553395910833293175">ইতিমধ্যে আটাইবোৰ ডেস্কক দায়িত্ব প্ৰদান কৰা হৈছে।</translation> <translation id="8555757996376137129">বৰ্তমানৰ ডেস্ক আঁতৰাওক</translation> <translation id="856298576161209842"><ph name="MANAGER" />এ আপোনাক নিজৰ <ph name="DEVICE_TYPE" />টো আপডে’ট কৰিবলৈ চুপাৰিছ কৰে</translation> -<translation id="8563862697512465947">জাননীৰ ছেটিংসমূহ</translation> <translation id="8569751806372591456">এয়া আপুনি কৰিব চাব পৰা কিছুমান পৰামৰ্শ</translation> <translation id="857201607579416096">স্ক্ৰীনখনৰ তলৰ সোঁফালৰ কোণটোলৈ মেনুখন নিয়া হৈছে।</translation> <translation id="8581946341807941670"><ph name="MODIFIER_1" /><ph name="MODIFIER_2" /> টিপক আৰু এটা লিংকত ক্লিক কৰক</translation>
diff --git a/ash/strings/ash_strings_az.xtb b/ash/strings/ash_strings_az.xtb index 3716669a..1bcf56c4 100644 --- a/ash/strings/ash_strings_az.xtb +++ b/ash/strings/ash_strings_az.xtb
@@ -1895,7 +1895,6 @@ <translation id="8553395910833293175">Artıq bütün masalara təyin olunub.</translation> <translation id="8555757996376137129">Cari masanı silin</translation> <translation id="856298576161209842"><ph name="MANAGER" /> <ph name="DEVICE_TYPE" /> cihazını güncəlləməyinizi tövsiyə edir</translation> -<translation id="8563862697512465947">Bildiriş Ayarları</translation> <translation id="8569751806372591456">Sınamaq üçün bəzi təkliflər</translation> <translation id="857201607579416096">Menyu ekranın aşağı sağ küncünə köçürülüb.</translation> <translation id="8581946341807941670"><ph name="MODIFIER_1" /><ph name="MODIFIER_2" /> üzərinə basıb, keçidə klikləyin</translation>
diff --git a/ash/strings/ash_strings_be.xtb b/ash/strings/ash_strings_be.xtb index fe1bbc75..56ed9c9 100644 --- a/ash/strings/ash_strings_be.xtb +++ b/ash/strings/ash_strings_be.xtb
@@ -358,6 +358,7 @@ <translation id="2484513351006226581">Каб пераключыць раскладку клавіятуры, націсніце <ph name="KEYBOARD_SHORTCUT" />.</translation> <translation id="2486214324139475545">Перадпрагляд працоўнага стала "<ph name="DESK_NAME" />". Актыўны працоўны стол.</translation> <translation id="2487915095798731898">Далучыцца</translation> +<translation id="2499445554382787206">Меню профілю працоўнага стала. <ph name="DESK_NAME" /></translation> <translation id="2501920221385095727">Заліпанне клавіш</translation> <translation id="2504454902900101003">Не наладжваць прагляд нядаўніх фота, медыяфайлаў і апавяшчэнняў з тэлефона</translation> <translation id="2509468283778169019">CAPS LOCK уключаны</translation> @@ -604,6 +605,7 @@ <translation id="3509391053705095206">Не ўдалося знайсці тэлефон. Пераканайцеся, што на тэлефоне ўключаны Bluetooth.</translation> <translation id="3510164367642747937">Вылучаць курсор мышы</translation> <translation id="3513798432020909783">Уліковы запіс пад кіраваннем <ph name="MANAGER_EMAIL" /></translation> +<translation id="3517037892157925473">Апошняе абнаўленне задач: <ph name="TIME" />, <ph name="DATE" />.</translation> <translation id="352245152354538528">{0,plural, =1{Абнавіце прыладу на працягу 1 хвіліны}one{Абнавіце прыладу на працягу # хвіліны}few{Абнавіце прыладу на працягу # хвілін}many{Абнавіце прыладу на працягу # хвілін}other{Абнавіце прыладу на працягу # хвілін}}</translation> <translation id="3522979239100719575">Ідзе пошук даступных профіляў. Гэта можа заняць некалькі хвілін.</translation> <translation id="3526440770046466733">Адкрыць спасылку ў новай укладцы, але заставацца на бягучай</translation> @@ -1142,6 +1144,7 @@ <translation id="5536723544185013515">Нядаўнія праграмы: для пераходу паміж імі выкарыстоўвайце клавішы са стрэлкамі ўлева і ўправа</translation> <translation id="553675580533261935">Выконваецца выхад з сеанса</translation> <translation id="5537725057119320332">Трансліраваць</translation> +<translation id="554017492391497564">Не ўдалося пазначыць задачу як выкананую.</translation> <translation id="5546397813406633847">Аднавіць доступ для карыстальніка</translation> <translation id="554893713779400387">Уключыць або выключыць галасавы ўвод</translation> <translation id="5550417424894892620">Перацягніце файлы на працоўны стол, каб дадаць іх у вобласць "<ph name="HOLDING_SPACE_TITLE" />". Вы не можаце дадаць файлы на працоўны стол.</translation> @@ -1336,6 +1339,7 @@ <translation id="6237231532760393653">1X</translation> <translation id="62380141479352646">Нізкі зарад акумулятара. Уключаны рэжым энергазберажэння.</translation> <translation id="6247728804802644171">Адкрыць апавяшчэнні</translation> +<translation id="6249795363855770621">Не ўдалося пазначыць задачу як выкананую. Паўтарыце спробу, калі падключэнне да інтэрнэту будзе адноўлена.</translation> <translation id="6254629735336163724">Зафіксаваны гарызантальны фармат</translation> <translation id="6259254695169772643">Для выбару выкарыстайце стыло</translation> <translation id="6267036997247669271"><ph name="NAME" />: ідзе актывацыя...</translation> @@ -1496,6 +1500,7 @@ <translation id="6896758677409633944">Капіраваць</translation> <translation id="6912841030378044227">Перайсці ў адрасны радок</translation> <translation id="6912901278692845878">Кароткі агляд</translation> +<translation id="6917259695595127329">Апошняе абнаўленне задач: <ph name="TIME" />.</translation> <translation id="6919251195245069855">Не ўдалося распазнаць вашу разумную картку. Паўтарыце спробу.</translation> <translation id="692135145298539227">выдаліць</translation> <translation id="6929081673585394903">Паказаць элементы кіравання</translation> @@ -1895,7 +1900,6 @@ <translation id="8553395910833293175">Акно ўжо даступнае на ўсіх працоўных сталах.</translation> <translation id="8555757996376137129">Выдаліць бягучы працоўны стол</translation> <translation id="856298576161209842"><ph name="MANAGER" /> рэкамендуе вам абнавіць прыладу <ph name="DEVICE_TYPE" /></translation> -<translation id="8563862697512465947">Налады апавяшчэнняў</translation> <translation id="8569751806372591456">Вось некалькі прапаноў</translation> <translation id="857201607579416096">Меню перамешчана ў правы ніжні вугал экрана.</translation> <translation id="8581946341807941670">Трымайце <ph name="MODIFIER_1" /><ph name="MODIFIER_2" /> і націсніце на спасылку</translation>
diff --git a/ash/strings/ash_strings_bg.xtb b/ash/strings/ash_strings_bg.xtb index 9faf26f4..2ac3d05 100644 --- a/ash/strings/ash_strings_bg.xtb +++ b/ash/strings/ash_strings_bg.xtb
@@ -1895,7 +1895,6 @@ <translation id="8553395910833293175">Вече е прехвърлено във всички работни кътове.</translation> <translation id="8555757996376137129">Премахване на текущия работен кът</translation> <translation id="856298576161209842"><ph name="MANAGER" /> препоръчва да актуализирате устройството си <ph name="DEVICE_TYPE" /></translation> -<translation id="8563862697512465947">Настройки за известия</translation> <translation id="8569751806372591456">Ето няколко предложения, които да изпробвате</translation> <translation id="857201607579416096">Менюто е преместено в долния десен ъгъл на екрана.</translation> <translation id="8581946341807941670">Натиснете <ph name="MODIFIER_1" /> + <ph name="MODIFIER_2" /> и кликнете върху връзка</translation>
diff --git a/ash/strings/ash_strings_bn.xtb b/ash/strings/ash_strings_bn.xtb index 38c53a9..c1134bc5 100644 --- a/ash/strings/ash_strings_bn.xtb +++ b/ash/strings/ash_strings_bn.xtb
@@ -1896,7 +1896,6 @@ <translation id="8553395910833293175">সব ডেস্কে আগেই অ্যাসাইন করা হয়েছে।</translation> <translation id="8555757996376137129">বর্তমান ডেস্ক সরিয়ে দিন</translation> <translation id="856298576161209842"><ph name="MANAGER" /> আপনার <ph name="DEVICE_TYPE" /> আপডেট করার সাজেশন দিচ্ছে</translation> -<translation id="8563862697512465947">বিজ্ঞপ্তি সেটিংস</translation> <translation id="8569751806372591456">চেষ্টা করে দেখতে পারেন এমন কয়েকটি সাজেশন দেওয়া হল</translation> <translation id="857201607579416096">স্ক্রিনের নিচে ডানদিকের কোণায় মেনু সরে গেছে।</translation> <translation id="8581946341807941670"><ph name="MODIFIER_1" /> <ph name="MODIFIER_2" /> প্রেস করুন এবং একটি লিঙ্কে ক্লিক করুন</translation>
diff --git a/ash/strings/ash_strings_bs.xtb b/ash/strings/ash_strings_bs.xtb index 09088d8..c10da56 100644 --- a/ash/strings/ash_strings_bs.xtb +++ b/ash/strings/ash_strings_bs.xtb
@@ -1895,7 +1895,6 @@ <translation id="8553395910833293175">Već je dodijeljeno svim radnim površinama.</translation> <translation id="8555757996376137129">Ukloni trenutnu radnu površinu</translation> <translation id="856298576161209842"><ph name="MANAGER" /> preporučuje da ažurirate uređaj <ph name="DEVICE_TYPE" /></translation> -<translation id="8563862697512465947">Postavke obavještenja</translation> <translation id="8569751806372591456">Evo nekoliko prijedloga koje možete isprobati</translation> <translation id="857201607579416096">Meni je pomjeren u donji desni ugao ekrana.</translation> <translation id="8581946341807941670">Pritisnite <ph name="MODIFIER_1" /><ph name="MODIFIER_2" /> i kliknite na link</translation>
diff --git a/ash/strings/ash_strings_ca.xtb b/ash/strings/ash_strings_ca.xtb index 2117be8..bb7e645 100644 --- a/ash/strings/ash_strings_ca.xtb +++ b/ash/strings/ash_strings_ca.xtb
@@ -1895,7 +1895,6 @@ <translation id="8553395910833293175">Ja està assignada a tots els escriptoris.</translation> <translation id="8555757996376137129">Suprimeix l'escriptori actual</translation> <translation id="856298576161209842"><ph name="MANAGER" /> et recomana que actualitzis el dispositiu <ph name="DEVICE_TYPE" /></translation> -<translation id="8563862697512465947">Configuració de notificació</translation> <translation id="8569751806372591456">Aquí tens alguns suggeriments que pots provar</translation> <translation id="857201607579416096">El menú s'ha mogut a l'extrem inferior dret de la pantalla.</translation> <translation id="8581946341807941670">Prem <ph name="MODIFIER_1" /><ph name="MODIFIER_2" /> i fes clic en un enllaç</translation>
diff --git a/ash/strings/ash_strings_cs.xtb b/ash/strings/ash_strings_cs.xtb index 9a9e6f5..0f76ac3 100644 --- a/ash/strings/ash_strings_cs.xtb +++ b/ash/strings/ash_strings_cs.xtb
@@ -358,6 +358,7 @@ <translation id="2484513351006226581">Rozložení klávesnice změníte stisknutím kláves <ph name="KEYBOARD_SHORTCUT" /></translation> <translation id="2486214324139475545">Náhled plochy <ph name="DESK_NAME" />. Aktivní plocha.</translation> <translation id="2487915095798731898">Připojit se</translation> +<translation id="2499445554382787206">Nabídka profilu plochy. <ph name="DESK_NAME" /></translation> <translation id="2501920221385095727">Jedním prstem</translation> <translation id="2504454902900101003">Zavřít nastavení zobrazování nedávných fotek, médií a oznámení z telefonu</translation> <translation id="2509468283778169019">CAPS LOCK je zapnutý</translation> @@ -604,6 +605,7 @@ <translation id="3509391053705095206">Telefon se nepodařilo najít. Zkontrolujte, jestli je na něm zapnutý Bluetooth.</translation> <translation id="3510164367642747937">Zvýraznit ukazatel myši</translation> <translation id="3513798432020909783">Správce účtu: <ph name="MANAGER_EMAIL" /></translation> +<translation id="3517037892157925473">Poslední aktualizace úkolů: <ph name="TIME" />, <ph name="DATE" />.</translation> <translation id="352245152354538528">{0,plural, =1{Do jedné minuty zařízení aktualizujte}few{Do # minut zařízení aktualizujte}many{Do # minuty zařízení aktualizujte}other{Do # minut zařízení aktualizujte}}</translation> <translation id="3522979239100719575">Probíhá vyhledávání dostupných profilů. Může to trvat několik minut.</translation> <translation id="3526440770046466733">Otevřít odkaz na nové kartě a zůstat na aktuální kartě</translation> @@ -1142,6 +1144,7 @@ <translation id="5536723544185013515">Nedávné aplikace, můžete je procházet pomocí kláves se šipkami vlevo a vpravo</translation> <translation id="553675580533261935">Ukončení návštěvy</translation> <translation id="5537725057119320332">Odeslat</translation> +<translation id="554017492391497564">Nelze označit jako dokončené.</translation> <translation id="5546397813406633847">Obnovit uživatele</translation> <translation id="554893713779400387">Přepnout diktování</translation> <translation id="5550417424894892620">Soubory přetažené na plochu budou přidány do prostoru <ph name="HOLDING_SPACE_TITLE" />. Na plochu soubory přidat nemůžete.</translation> @@ -1336,6 +1339,7 @@ <translation id="6237231532760393653">1X</translation> <translation id="62380141479352646">Baterie je téměř vybitá. Zapnul se spořič baterie.</translation> <translation id="6247728804802644171">Otevřít oznámení</translation> +<translation id="6249795363855770621">Nelze označit jako dokončené. Zkuste to, až budete online.</translation> <translation id="6254629735336163724">Uzamknuto na šířku</translation> <translation id="6259254695169772643">K výběru použijte dotykové pero</translation> <translation id="6267036997247669271"><ph name="NAME" />: Probíhá aktivace...</translation> @@ -1496,6 +1500,7 @@ <translation id="6896758677409633944">Kopírovat</translation> <translation id="6912841030378044227">Přejít do adresního řádku</translation> <translation id="6912901278692845878">Rychlá prohlídka</translation> +<translation id="6917259695595127329">Poslední aktualizace úkolů: <ph name="TIME" />.</translation> <translation id="6919251195245069855">Chytrou kartu se nepodařilo rozpoznat. Zkuste to znovu.</translation> <translation id="692135145298539227">smazat</translation> <translation id="6929081673585394903">Zobrazit ovládací prvky</translation> @@ -1894,7 +1899,6 @@ <translation id="8553395910833293175">Již přiřazeno ke všem plochám.</translation> <translation id="8555757996376137129">Odstranit aktuální plochu</translation> <translation id="856298576161209842">Organizace <ph name="MANAGER" /> vám doporučuje, abyste toto zařízení <ph name="DEVICE_TYPE" /> aktualizovali</translation> -<translation id="8563862697512465947">Nastavení oznámení</translation> <translation id="8569751806372591456">Zde je několik návrhů, které můžete vyzkoušet</translation> <translation id="857201607579416096">Nabídka byla přesunuta do pravého dolního rohu obrazovky.</translation> <translation id="8581946341807941670">Stiskněte <ph name="MODIFIER_1" /><ph name="MODIFIER_2" /> a klikněte na odkaz</translation>
diff --git a/ash/strings/ash_strings_cy.xtb b/ash/strings/ash_strings_cy.xtb index 16dc718..4b118a05 100644 --- a/ash/strings/ash_strings_cy.xtb +++ b/ash/strings/ash_strings_cy.xtb
@@ -1895,7 +1895,6 @@ <translation id="8553395910833293175">Wedi'i haseinio i bob desg yn barod.</translation> <translation id="8555757996376137129">Tynnu'r ddesg bresennol</translation> <translation id="856298576161209842">Mae <ph name="MANAGER" /> yn argymell eich bod yn diweddaru eich <ph name="DEVICE_TYPE" /></translation> -<translation id="8563862697512465947">Gosodiadau Hysbysiadau</translation> <translation id="8569751806372591456">Dyma ychydig o awgrymiadau i roi cynnig arnynt</translation> <translation id="857201607579416096">Cafodd y ddewislen ei symud i gornel dde waelod y sgrîn.</translation> <translation id="8581946341807941670">Pwyswch <ph name="MODIFIER_1" /><ph name="MODIFIER_2" /> a chliciwch ddolen</translation>
diff --git a/ash/strings/ash_strings_da.xtb b/ash/strings/ash_strings_da.xtb index e12f4aa6..7d2e4f3 100644 --- a/ash/strings/ash_strings_da.xtb +++ b/ash/strings/ash_strings_da.xtb
@@ -1888,7 +1888,6 @@ <translation id="8553395910833293175">Allerede tildelt til alle skriveborde.</translation> <translation id="8555757996376137129">Fjern det aktuelle skrivebord</translation> <translation id="856298576161209842"><ph name="MANAGER" /> anbefaler, at du opdaterer din <ph name="DEVICE_TYPE" /></translation> -<translation id="8563862697512465947">Indstillinger for notifikationer</translation> <translation id="8569751806372591456">Her er nogle forslag til, hvad du kan prøve</translation> <translation id="857201607579416096">Menuen blev flyttet til nederste højre hjørne af skærmen.</translation> <translation id="8581946341807941670">Tryk på <ph name="MODIFIER_1" /><ph name="MODIFIER_2" />, og klik på et link</translation>
diff --git a/ash/strings/ash_strings_de.xtb b/ash/strings/ash_strings_de.xtb index 2087ba0..7fa5b9c 100644 --- a/ash/strings/ash_strings_de.xtb +++ b/ash/strings/ash_strings_de.xtb
@@ -1895,7 +1895,6 @@ <translation id="8553395910833293175">Ist bereits allen Desktops zugewiesen.</translation> <translation id="8555757996376137129">Aktuellen Desktop entfernen</translation> <translation id="856298576161209842"><ph name="MANAGER" /> empfiehlt, dein Gerät (<ph name="DEVICE_TYPE" />) zu aktualisieren</translation> -<translation id="8563862697512465947">Benachrichtigungseinstellungen</translation> <translation id="8569751806372591456">Hier ein paar Vorschläge</translation> <translation id="857201607579416096">Menü wurde nach rechts unten auf dem Bildschirm verschoben.</translation> <translation id="8581946341807941670"><ph name="MODIFIER_1" /> + <ph name="MODIFIER_2" /> drücken und auf einen Link klicken</translation>
diff --git a/ash/strings/ash_strings_el.xtb b/ash/strings/ash_strings_el.xtb index 0a71048..2a1ce75c 100644 --- a/ash/strings/ash_strings_el.xtb +++ b/ash/strings/ash_strings_el.xtb
@@ -1895,7 +1895,6 @@ <translation id="8553395910833293175">Έχει εκχωρηθεί ήδη σε όλα τα γραφεία.</translation> <translation id="8555757996376137129">Κατάργηση τρέχοντος γραφείου</translation> <translation id="856298576161209842">Ο τομέας <ph name="MANAGER" /> προτείνει να ενημερώσετε τη συσκευή <ph name="DEVICE_TYPE" /></translation> -<translation id="8563862697512465947">Ρυθμίσεις ειδοποίησης</translation> <translation id="8569751806372591456">Δείτε μερικές προτάσεις που μπορείτε να δοκιμάσετε</translation> <translation id="857201607579416096">Το μενού μεταφέρθηκε στην κάτω δεξιά γωνία της οθόνης.</translation> <translation id="8581946341807941670">Πατήστε <ph name="MODIFIER_1" /><ph name="MODIFIER_2" /> και κάντε κλικ σε έναν σύνδεσμο</translation>
diff --git a/ash/strings/ash_strings_en-GB.xtb b/ash/strings/ash_strings_en-GB.xtb index ccb7db18..f320cdd 100644 --- a/ash/strings/ash_strings_en-GB.xtb +++ b/ash/strings/ash_strings_en-GB.xtb
@@ -1895,7 +1895,6 @@ <translation id="8553395910833293175">Already assigned to all desks.</translation> <translation id="8555757996376137129">Remove current desk</translation> <translation id="856298576161209842"><ph name="MANAGER" /> recommends that you update your <ph name="DEVICE_TYPE" /></translation> -<translation id="8563862697512465947">Notification Settings</translation> <translation id="8569751806372591456">Here are a few suggestions to try</translation> <translation id="857201607579416096">Menu moved to the bottom-right corner of the screen.</translation> <translation id="8581946341807941670">Press <ph name="MODIFIER_1" /><ph name="MODIFIER_2" /> and click a link</translation>
diff --git a/ash/strings/ash_strings_es-419.xtb b/ash/strings/ash_strings_es-419.xtb index bd5a2b1..f8d0bc6 100644 --- a/ash/strings/ash_strings_es-419.xtb +++ b/ash/strings/ash_strings_es-419.xtb
@@ -1896,7 +1896,6 @@ <translation id="8553395910833293175">Ya se asignó a todos los escritorios.</translation> <translation id="8555757996376137129">Quitar el escritorio actual</translation> <translation id="856298576161209842"><ph name="MANAGER" /> te recomienda que actualices tu <ph name="DEVICE_TYPE" /></translation> -<translation id="8563862697512465947">Configuración de notificación</translation> <translation id="8569751806372591456">Estas son algunas sugerencias que puedes probar</translation> <translation id="857201607579416096">El menú se movió a la esquina inferior derecha de la pantalla.</translation> <translation id="8581946341807941670">Presiona <ph name="MODIFIER_1" /><ph name="MODIFIER_2" /> y haz clic en un vínculo</translation>
diff --git a/ash/strings/ash_strings_es.xtb b/ash/strings/ash_strings_es.xtb index c9af840..3490f86 100644 --- a/ash/strings/ash_strings_es.xtb +++ b/ash/strings/ash_strings_es.xtb
@@ -1896,7 +1896,6 @@ <translation id="8553395910833293175">Ya está asignado a todos los escritorios.</translation> <translation id="8555757996376137129">Quitar escritorio actual</translation> <translation id="856298576161209842"><ph name="MANAGER" /> te recomienda que actualices tu <ph name="DEVICE_TYPE" /></translation> -<translation id="8563862697512465947">Ajustes de notificaciones</translation> <translation id="8569751806372591456">Aquí tienes algunas sugerencias que puedes probar</translation> <translation id="857201607579416096">Se ha movido el menú a la esquina inferior derecha de la pantalla.</translation> <translation id="8581946341807941670">Pulsa <ph name="MODIFIER_1" /><ph name="MODIFIER_2" /> y haz clic en un enlace</translation>
diff --git a/ash/strings/ash_strings_et.xtb b/ash/strings/ash_strings_et.xtb index 131499f..70c99e3 100644 --- a/ash/strings/ash_strings_et.xtb +++ b/ash/strings/ash_strings_et.xtb
@@ -1896,7 +1896,6 @@ <translation id="8553395910833293175">Juba määratud kõigile töölaudadele.</translation> <translation id="8555757996376137129">Eemaldage aktiivne töölaud</translation> <translation id="856298576161209842"><ph name="MANAGER" /> soovitab seadet <ph name="DEVICE_TYPE" /> värskendada</translation> -<translation id="8563862697512465947">Teatiste seaded</translation> <translation id="8569751806372591456">Siin on proovimiseks mõned soovitused</translation> <translation id="857201607579416096">Menüü teisaldati ekraanikuva paremasse alanurka.</translation> <translation id="8581946341807941670">Vajutage klahvi <ph name="MODIFIER_1" /><ph name="MODIFIER_2" /> ja klõpsake lingil</translation>
diff --git a/ash/strings/ash_strings_eu.xtb b/ash/strings/ash_strings_eu.xtb index e7b6543..4063dea 100644 --- a/ash/strings/ash_strings_eu.xtb +++ b/ash/strings/ash_strings_eu.xtb
@@ -1896,7 +1896,6 @@ <translation id="8553395910833293175">Lan-eremu guztiei esleitu zaie dagoeneko.</translation> <translation id="8555757996376137129">Kendu lan-eremu hau</translation> <translation id="856298576161209842"><ph name="DEVICE_TYPE" /> gailua eguneratzeko gomendatzen dizu <ph name="MANAGER" /> kudeatzaileak</translation> -<translation id="8563862697512465947">Jakinarazpen-ezarpenak</translation> <translation id="8569751806372591456">Hona hemen proba ditzakezun gauza batzuk:</translation> <translation id="857201607579416096">Pantailaren beheko eskuineko izkinara eraman da menua.</translation> <translation id="8581946341807941670">Sakatu <ph name="MODIFIER_1" /><ph name="MODIFIER_2" /> eta egin klik esteka batean</translation>
diff --git a/ash/strings/ash_strings_fa.xtb b/ash/strings/ash_strings_fa.xtb index f24ac7a..ed6f608 100644 --- a/ash/strings/ash_strings_fa.xtb +++ b/ash/strings/ash_strings_fa.xtb
@@ -1895,7 +1895,6 @@ <translation id="8553395910833293175">قبلاً به همه میزکارها اختصاص داده شده است.</translation> <translation id="8555757996376137129">حذف میزکار کنونی</translation> <translation id="856298576161209842"><ph name="MANAGER" /> توصیه میکند <ph name="DEVICE_TYPE" /> را بهروزرسانی کنید</translation> -<translation id="8563862697512465947">تنظیمات اعلام</translation> <translation id="8569751806372591456">چند پیشنهاد برای امتحان کردن:</translation> <translation id="857201607579416096">منو به گوشه چپ پایین صفحه منتقل شد.</translation> <translation id="8581946341807941670"><ph name="MODIFIER_1" /><ph name="MODIFIER_2" /> را فشار دهید و روی پیوندی کلیک کنید</translation>
diff --git a/ash/strings/ash_strings_fi.xtb b/ash/strings/ash_strings_fi.xtb index 7188a19..56ab058 100644 --- a/ash/strings/ash_strings_fi.xtb +++ b/ash/strings/ash_strings_fi.xtb
@@ -1895,7 +1895,6 @@ <translation id="8553395910833293175">Lisätty jo kaikille työpöydille.</translation> <translation id="8555757996376137129">Poista nykyinen työpöytä</translation> <translation id="856298576161209842"><ph name="MANAGER" /> suosittelee, että <ph name="DEVICE_TYPE" /> päivitetään</translation> -<translation id="8563862697512465947">Ilmoitusasetukset</translation> <translation id="8569751806372591456">Tässä on muutamia ehdotuksia</translation> <translation id="857201607579416096">Valikko siirretty näytön oikeaan alanurkkaan</translation> <translation id="8581946341807941670">Paina <ph name="MODIFIER_1" /><ph name="MODIFIER_2" /> ja klikkaa linkkiä</translation>
diff --git a/ash/strings/ash_strings_fil.xtb b/ash/strings/ash_strings_fil.xtb index 96e7b68..cef7d94 100644 --- a/ash/strings/ash_strings_fil.xtb +++ b/ash/strings/ash_strings_fil.xtb
@@ -1894,7 +1894,6 @@ <translation id="8553395910833293175">Naitalaga na sa lahat ng desk.</translation> <translation id="8555757996376137129">Alisin ang kasalukuyang desk</translation> <translation id="856298576161209842">Inirerekomenda ng <ph name="MANAGER" /> na i-update mo ang iyong <ph name="DEVICE_TYPE" /></translation> -<translation id="8563862697512465947">Mga Setting sa Abiso</translation> <translation id="8569751806372591456">Narito ang ilang suhestyong puwede mong subukan</translation> <translation id="857201607579416096">Inilipat ang menu sa kanang sulok sa ibaba ng screen.</translation> <translation id="8581946341807941670">Pindutin ang <ph name="MODIFIER_1" /><ph name="MODIFIER_2" /> at mag-click ng link</translation>
diff --git a/ash/strings/ash_strings_fr-CA.xtb b/ash/strings/ash_strings_fr-CA.xtb index 65c1617..297949df 100644 --- a/ash/strings/ash_strings_fr-CA.xtb +++ b/ash/strings/ash_strings_fr-CA.xtb
@@ -1894,7 +1894,6 @@ <translation id="8553395910833293175">Association déjà faite sur tous les bureaux.</translation> <translation id="8555757996376137129">Retirer le bureau actuel</translation> <translation id="856298576161209842"><ph name="MANAGER" /> vous recommande de mettre à jour votre <ph name="DEVICE_TYPE" />.</translation> -<translation id="8563862697512465947">Paramètres de notification</translation> <translation id="8569751806372591456">Voici quelques suggestions à essayer</translation> <translation id="857201607579416096">Le menu a été déplacé vers le coin inférieur droit de l'écran.</translation> <translation id="8581946341807941670">Appuyez sur <ph name="MODIFIER_1" /><ph name="MODIFIER_2" />, puis cliquez sur un lien</translation>
diff --git a/ash/strings/ash_strings_fr.xtb b/ash/strings/ash_strings_fr.xtb index b350e6a..6c4772d5 100644 --- a/ash/strings/ash_strings_fr.xtb +++ b/ash/strings/ash_strings_fr.xtb
@@ -1896,7 +1896,6 @@ <translation id="8553395910833293175">Association déjà faite sur tous les bureaux.</translation> <translation id="8555757996376137129">Supprimer le bureau actuel</translation> <translation id="856298576161209842"><ph name="MANAGER" /> vous recommande de mettre à jour votre <ph name="DEVICE_TYPE" /></translation> -<translation id="8563862697512465947">Paramètres de notification</translation> <translation id="8569751806372591456">Voici quelques suggestions à tester</translation> <translation id="857201607579416096">Le menu a été déplacé dans l'angle inférieur droit de l'écran.</translation> <translation id="8581946341807941670">Appuyez sur <ph name="MODIFIER_1" /><ph name="MODIFIER_2" />, puis cliquez sur un lien</translation>
diff --git a/ash/strings/ash_strings_gl.xtb b/ash/strings/ash_strings_gl.xtb index 59c93cba..9ef7dc4 100644 --- a/ash/strings/ash_strings_gl.xtb +++ b/ash/strings/ash_strings_gl.xtb
@@ -1896,7 +1896,6 @@ <translation id="8553395910833293175">Xa se asignou a todos os escritorios.</translation> <translation id="8555757996376137129">Quitar escritorio actual</translation> <translation id="856298576161209842"><ph name="MANAGER" /> recoméndache actualizar o teu dispositivo (<ph name="DEVICE_TYPE" />)</translation> -<translation id="8563862697512465947">Configuración de notificacións</translation> <translation id="8569751806372591456">Aquí tes algunhas suxestións que podes probar</translation> <translation id="857201607579416096">O menú moveuse á esquina inferior dereita da pantalla.</translation> <translation id="8581946341807941670">Preme <ph name="MODIFIER_1" /><ph name="MODIFIER_2" /> e fai clic nunha ligazón</translation>
diff --git a/ash/strings/ash_strings_gu.xtb b/ash/strings/ash_strings_gu.xtb index 92cdec0..31db47d3 100644 --- a/ash/strings/ash_strings_gu.xtb +++ b/ash/strings/ash_strings_gu.xtb
@@ -1895,7 +1895,6 @@ <translation id="8553395910833293175">બધા ડેસ્ક માટે પહેલેથી જ સોંપણી કરવામાં આવી છે.</translation> <translation id="8555757996376137129">વર્તમાન ડેસ્ક કાઢી નાખો</translation> <translation id="856298576161209842">તમે તમારું <ph name="DEVICE_TYPE" /> અપડેટ કરો તેવો <ph name="MANAGER" />નો સુઝાવ છે</translation> -<translation id="8563862697512465947">નોટિફિકેશન સેટિંગ</translation> <translation id="8569751806372591456">અહીં અજમાવવા જેવા અમુક સૂચનો આપવામાં આવ્યા છે</translation> <translation id="857201607579416096">મેનૂને સ્ક્રીનની નીચેના જમણા ખૂણામાં ખસેડ્યું.</translation> <translation id="8581946341807941670"><ph name="MODIFIER_1" />+<ph name="MODIFIER_2" /> દબાવો અને લિંકને ક્લિક કરો</translation>
diff --git a/ash/strings/ash_strings_hi.xtb b/ash/strings/ash_strings_hi.xtb index 1bf67f7..a1c8a0f 100644 --- a/ash/strings/ash_strings_hi.xtb +++ b/ash/strings/ash_strings_hi.xtb
@@ -1889,7 +1889,6 @@ <translation id="8553395910833293175">सभी डेस्क के लिए पहले से असाइन किया जा चुका है.</translation> <translation id="8555757996376137129">मौजूदा डेस्क हटाएं</translation> <translation id="856298576161209842"><ph name="MANAGER" /> से सुझाव मिला है कि आप अपने <ph name="DEVICE_TYPE" /> को अपडेट करें</translation> -<translation id="8563862697512465947">सूचना सेटिंग</translation> <translation id="8569751806372591456">आज़माने के लिए यहां कुछ सुझाव दिए गए हैं</translation> <translation id="857201607579416096">मेन्यू, स्क्रीन के सबसे नीचे दाएं कोने में चला गया है.</translation> <translation id="8581946341807941670"><ph name="MODIFIER_1" /><ph name="MODIFIER_2" /> दबाएं और किसी लिंक पर क्लिक करें</translation>
diff --git a/ash/strings/ash_strings_hr.xtb b/ash/strings/ash_strings_hr.xtb index b3930ac..d93e67d 100644 --- a/ash/strings/ash_strings_hr.xtb +++ b/ash/strings/ash_strings_hr.xtb
@@ -1895,7 +1895,6 @@ <translation id="8553395910833293175">Već je dodijeljeno svim radnim površinama.</translation> <translation id="8555757996376137129">Uklanjanje trenutačne radne površine</translation> <translation id="856298576161209842"><ph name="MANAGER" /> preporučuje da ažurirate svoj uređaj <ph name="DEVICE_TYPE" />.</translation> -<translation id="8563862697512465947">Postavke obavijesti</translation> <translation id="8569751806372591456">Evo nekoliko prijedloga koje možete isprobati</translation> <translation id="857201607579416096">Izbornik je premješten u donji desni kut zaslona.</translation> <translation id="8581946341807941670">Pritisnite <ph name="MODIFIER_1" /><ph name="MODIFIER_2" /> i kliknite vezu</translation>
diff --git a/ash/strings/ash_strings_hu.xtb b/ash/strings/ash_strings_hu.xtb index 41e2e44..be6734d3 100644 --- a/ash/strings/ash_strings_hu.xtb +++ b/ash/strings/ash_strings_hu.xtb
@@ -1896,7 +1896,6 @@ <translation id="8553395910833293175">Már minden asztalhoz hozzá van rendelve.</translation> <translation id="8555757996376137129">Jelenlegi asztal eltávolítása</translation> <translation id="856298576161209842">A(z) <ph name="MANAGER" /> azt javasolja, hogy frissítse a(z) <ph name="DEVICE_TYPE" /> eszközét.</translation> -<translation id="8563862697512465947">Értesítési beállítások</translation> <translation id="8569751806372591456">Íme néhány javaslat, amelyet érdemes kipróbálni</translation> <translation id="857201607579416096">A menü a képernyő jobb alsó sarkába került.</translation> <translation id="8581946341807941670">Nyomja le a(z) <ph name="MODIFIER_1" /><ph name="MODIFIER_2" /> billentyűkombinációt, majd kattintson a kívánt linkre</translation>
diff --git a/ash/strings/ash_strings_hy.xtb b/ash/strings/ash_strings_hy.xtb index 98a74a52..69ea6ebd9 100644 --- a/ash/strings/ash_strings_hy.xtb +++ b/ash/strings/ash_strings_hy.xtb
@@ -1895,7 +1895,6 @@ <translation id="8553395910833293175">Արդեն հասանելի է բոլոր աշխատասեղաններին։</translation> <translation id="8555757996376137129">Հեռացնել ընթացիկ աշխատասեղանը</translation> <translation id="856298576161209842">Կառավարիչը (<ph name="MANAGER" />) խորհուրդ է տալիս, որ թարմացնեք ձեր <ph name="DEVICE_TYPE" /> սարքը։</translation> -<translation id="8563862697512465947">Ծանուցումների կարգավորումներ</translation> <translation id="8569751806372591456">Ահա մի քանի առաջարկ, որոնք կարող եք փորձել</translation> <translation id="857201607579416096">Ընտրացանկը տեղափոխվեց էկրանի ներքևի աջ անկյուն։</translation> <translation id="8581946341807941670">Սեղմեք <ph name="MODIFIER_1" /><ph name="MODIFIER_2" />, ապա սեղմեք հղման վրա</translation>
diff --git a/ash/strings/ash_strings_id.xtb b/ash/strings/ash_strings_id.xtb index 94e5c5f..e114e52 100644 --- a/ash/strings/ash_strings_id.xtb +++ b/ash/strings/ash_strings_id.xtb
@@ -1895,7 +1895,6 @@ <translation id="8553395910833293175">Telah ditetapkan ke semua desktop.</translation> <translation id="8555757996376137129">Hapus desktop saat ini</translation> <translation id="856298576161209842"><ph name="MANAGER" /> menyarankan Anda mengupdate <ph name="DEVICE_TYPE" /></translation> -<translation id="8563862697512465947">Setelan Notifikasi</translation> <translation id="8569751806372591456">Berikut beberapa saran untuk dicoba</translation> <translation id="857201607579416096">Menu dipindahkan ke pojok kanan bawah layar.</translation> <translation id="8581946341807941670">Tekan <ph name="MODIFIER_1" /><ph name="MODIFIER_2" />, lalu klik link</translation>
diff --git a/ash/strings/ash_strings_is.xtb b/ash/strings/ash_strings_is.xtb index 7ac9071..108b375 100644 --- a/ash/strings/ash_strings_is.xtb +++ b/ash/strings/ash_strings_is.xtb
@@ -1895,7 +1895,6 @@ <translation id="8553395910833293175">Þegar úthlutað til allra skrifborða</translation> <translation id="8555757996376137129">Fjarlægja núverandi skrifborð</translation> <translation id="856298576161209842"><ph name="MANAGER" /> mælir með því að þú uppfærir <ph name="DEVICE_TYPE" /></translation> -<translation id="8563862697512465947">Tilkynningastillingar</translation> <translation id="8569751806372591456">Hér eru nokkrar tillögur sem þú getur prófað</translation> <translation id="857201607579416096">Valmynd færð neðst í hægra horn skjásins.</translation> <translation id="8581946341807941670">Ýttu á <ph name="MODIFIER_1" /><ph name="MODIFIER_2" /> og smelltu á tengil</translation>
diff --git a/ash/strings/ash_strings_it.xtb b/ash/strings/ash_strings_it.xtb index 4aa2920..c5bcc7c 100644 --- a/ash/strings/ash_strings_it.xtb +++ b/ash/strings/ash_strings_it.xtb
@@ -1892,7 +1892,6 @@ <translation id="8553395910833293175">Finestra già assegnata a tutte le scrivanie.</translation> <translation id="8555757996376137129">Rimuovi la scrivania corrente</translation> <translation id="856298576161209842"><ph name="MANAGER" /> ti consiglia di aggiornare <ph name="DEVICE_TYPE" /></translation> -<translation id="8563862697512465947">Impostazioni di notifica</translation> <translation id="8569751806372591456">Ecco alcuni suggerimenti da provare</translation> <translation id="857201607579416096">Menu spostato nell'angolo in basso a destra dello schermo.</translation> <translation id="8581946341807941670">Premi <ph name="MODIFIER_1" /><ph name="MODIFIER_2" /> e fai clic su un link</translation>
diff --git a/ash/strings/ash_strings_iw.xtb b/ash/strings/ash_strings_iw.xtb index 3aede68..c40c476 100644 --- a/ash/strings/ash_strings_iw.xtb +++ b/ash/strings/ash_strings_iw.xtb
@@ -18,6 +18,7 @@ <translation id="1056775291175587022">אין רשתות</translation> <translation id="1056898198331236512">אזהרה</translation> <translation id="1058009965971887428">שליחת דוח משוב</translation> +<translation id="1059120031266247284">קובץ ששותף איתך</translation> <translation id="1059194134494239015"><ph name="DISPLAY_NAME" />: <ph name="RESOLUTION" /></translation> <translation id="1062407476771304334">החלפה</translation> <translation id="1073899992769346247">צריך להחליף את הסוללה או לטעון אותה</translation> @@ -626,6 +627,7 @@ <translation id="3577473026931028326">משהו השתבש. יש לנסות שוב.</translation> <translation id="3580650856351781466">מתבצעת הורדה של קובצי דיבור</translation> <translation id="3585296979871889131">הצגת התמונות, המדיה, ההתראות והאפליקציות שבטלפון מהזמן האחרון</translation> +<translation id="358832729276157756">ExpressKey 1</translation> <translation id="3590441166907930941">הלחצן הצדדי</translation> <translation id="3593039967545720377">כדי להיכנס להיסטוריית הלוח, יש ללחוץ על <ph name="SHORTCUT_KEY_NAME" /> + V לצפייה בלוח. כדי להתחיל, צריך להעתיק פריט.</translation> <translation id="3593646411856133110">כדי להציג את האפליקציות הפתוחות, מחליקים כלפי מעלה ולוחצים לחיצה ארוכה</translation> @@ -642,6 +644,7 @@ <translation id="3621202678540785336">קלט</translation> <translation id="3621712662352432595">הגדרות אודיו</translation> <translation id="3626281679859535460">בהירות</translation> +<translation id="3628323833346754646">הלחצן הקדמי</translation> <translation id="3630697955794050612">כבויה</translation> <translation id="3631369015426612114">המקורות הבאים יכולים להציג התראות</translation> <translation id="3633097874324966332">צריך לפתוח את הגדרות Bluetooth כדי להתאים את המכשיר</translation> @@ -1283,6 +1286,7 @@ <translation id="6052614013050385269">לחיצה ימנית על קישור</translation> <translation id="6054305421211936131">כניסה באמצעות כרטיס חכם</translation> <translation id="6059276912018042191">כרטיסיות Chrome אחרונות</translation> +<translation id="606147842285839995">ExpressKey 3</translation> <translation id="6062360702481658777">תבוצע יציאה באופן אוטומטי בעוד <ph name="LOGOUT_TIME_LEFT" />.</translation> <translation id="6064463340679478396">סיימתי להשתמש בקובץ</translation> <translation id="6068258534237496331">פקדים לשיחות וידאו</translation> @@ -1344,6 +1348,7 @@ <translation id="6309219492973062892">לחיצה או הקשה על הסמלים 1-8 במדף</translation> <translation id="6315170314923504164">קול</translation> <translation id="6319058840130157106">מעבר אחורה בין הפינה השמאלית התחתונה, מרכז האפליקציות, סרגל הכתובות, סרגל הסימניות, האתר שפתוח וההורדות</translation> +<translation id="6319503618073410818">לצפייה בפרטים בדפדפן</translation> <translation id="6324916366299863871">עריכת קיצור דרך</translation> <translation id="6330012934079202188">מוצגים החלונות מכל שולחנות העבודה הווירטואליים. ניתן להקיש על מקש החץ למעלה כדי להציג את החלונות משולחן העבודה הווירטואלי הנוכחי</translation> <translation id="6338485349199627913"><ph name="DISPLAY_NAME" /> הוא סשן מנוהל שמנוהל על ידי <ph name="MANAGER" /></translation> @@ -1564,6 +1569,7 @@ <translation id="7229029500464092426">זיכרון פנוי: <ph name="AVAILABLE_MEMORY" /> | סה"כ: <ph name="TOTAL_MEMORY" /></translation> <translation id="7244725679040769470">רמת הטעינה שנותרה בסוללה: %<ph name="PERCENTAGE" />. צריך לחבר את המכשיר למקור חשמל.</translation> <translation id="7246071203293827765"><ph name="UPDATE_TEXT" />. יש להפעיל את ה-Chromebook מחדש כדי להחיל עדכון. התהליך עשוי להימשך עד דקה אחת.</translation> +<translation id="7256057185598509352">ExpressKey 2</translation> <translation id="7256634071279256947">מיקרופון אחורי</translation> <translation id="7258828758145722155">נערך אתמול</translation> <translation id="726276584504105859">לשימוש במסך מפוצל יש לגרור לכאן</translation> @@ -1693,6 +1699,7 @@ <translation id="7742327441377685481">אין התראות</translation> <translation id="7748275671948949022">הדגשת הלחצן של מרכז האפליקציות במדף</translation> <translation id="7749443890790263709">הגעת למספר המקסימלי של שולחנות עבודה.</translation> +<translation id="7749958366403230681">ExpressKey 4</translation> <translation id="776344839111254542">יש ללחוץ כדי להציג את פרטי העדכון</translation> <translation id="7768784765476638775">הקראה</translation> <translation id="7769299611924763557">קובץ ה-GIF יהיה מוכן בקרוב</translation> @@ -1890,7 +1897,6 @@ <translation id="8553395910833293175">כבר מוקצה לכל שולחנות העבודה הווירטואליים.</translation> <translation id="8555757996376137129">הסרת שולחן העבודה הווירטואלי הנוכחי</translation> <translation id="856298576161209842">לפי ההמלצה של <ph name="MANAGER" />, כדאי לעדכן את ה-<ph name="DEVICE_TYPE" /></translation> -<translation id="8563862697512465947">הגדרות התראה</translation> <translation id="8569751806372591456">הנה כמה הצעות שכדאי לנסות</translation> <translation id="857201607579416096">התפריט הועבר לפינה השמאלית התחתונה של המסך.</translation> <translation id="8581946341807941670">הקשה על <ph name="MODIFIER_1" /><ph name="MODIFIER_2" /> ולחיצה על קישור</translation> @@ -2020,6 +2026,7 @@ <translation id="9098969848082897657">השתקת הטלפון</translation> <translation id="9121941381564890244"><ph name="SNIP" /> או <ph name="CTRL" /><ph name="SEPARATOR1" /><ph name="SHIFT" /><ph name="SEPARATOR2" /><ph name="OVERVIEW" /></translation> <translation id="9126339866969410112">ביטול הפעולה האחרונה</translation> +<translation id="9129245940793250979">הלחצן האחורי</translation> <translation id="9133335900048457298">לא ניתן להקליט תוכן מוגן</translation> <translation id="9139720510312328767">מחיקת האות הבאה</translation> <translation id="9151906066336345901">מקש End</translation>
diff --git a/ash/strings/ash_strings_ja.xtb b/ash/strings/ash_strings_ja.xtb index dd079399..88c26f4 100644 --- a/ash/strings/ash_strings_ja.xtb +++ b/ash/strings/ash_strings_ja.xtb
@@ -1896,7 +1896,6 @@ <translation id="8553395910833293175">すべてのデスクに割り当て済みです。</translation> <translation id="8555757996376137129">現在のデスクを削除する</translation> <translation id="856298576161209842"><ph name="MANAGER" /> が <ph name="DEVICE_TYPE" /> を更新するよう推奨しています</translation> -<translation id="8563862697512465947">通知設定</translation> <translation id="8569751806372591456">新しい検索候補をお試しください</translation> <translation id="857201607579416096">メニューは画面右下に移動しました。</translation> <translation id="8581946341807941670"><ph name="MODIFIER_1" />+<ph name="MODIFIER_2" /> キーを押しながらリンクをクリックする</translation>
diff --git a/ash/strings/ash_strings_ka.xtb b/ash/strings/ash_strings_ka.xtb index 8f2705a..0ce118f 100644 --- a/ash/strings/ash_strings_ka.xtb +++ b/ash/strings/ash_strings_ka.xtb
@@ -358,6 +358,7 @@ <translation id="2484513351006226581">კლავიატურის განლაგების გადასართველად გამოიყენეთ კლავიშთა კომბინაცია: <ph name="KEYBOARD_SHORTCUT" />.</translation> <translation id="2486214324139475545"><ph name="DESK_NAME" />-ის გადასახედი ვერსია. აქტიური სამუშაო მაგიდა.</translation> <translation id="2487915095798731898">გაწევრიანება</translation> +<translation id="2499445554382787206">სამუშაო მაგიდის პროფილის მენიუ. <ph name="DESK_NAME" /></translation> <translation id="2501920221385095727">კლავიშების ფიქსაცია</translation> <translation id="2504454902900101003">თქვენი ტელეფონის ბოლოდროინდელი ფოტოების, მედიაფაილებისა და შეტყობინებების ნახვის დაყენების დახურვა</translation> <translation id="2509468283778169019">CAPS LOCK ჩართულია</translation> @@ -604,6 +605,7 @@ <translation id="3509391053705095206">თქვენი ტელეფონი ვერ მოიძებნა. დარწმუნდით, რომ თქვენს ტელეფონზე ჩართულია Bluetooth.</translation> <translation id="3510164367642747937">მაუსის კურსორის გამოყოფა</translation> <translation id="3513798432020909783">ანგარიშს მართავს <ph name="MANAGER_EMAIL" /></translation> +<translation id="3517037892157925473">ამოცანების ბოლო განახლება: <ph name="TIME" />, <ph name="DATE" />.</translation> <translation id="352245152354538528">{0,plural, =1{განაახლეთ მოწყობილობა 1 წუთის განმავლობაში}other{განაახლეთ მოწყობილობა # წუთის განმავლობაში}}</translation> <translation id="3522979239100719575">იძებნება ხელმისაწვდომი პროფილები. ამას შეიძლება რამდენიმე წუთი დასჭირდეს.</translation> <translation id="3526440770046466733">ბმულის გახსნა ახალ ჩანართზე და მიმდინარე ჩანართზე დარჩენა</translation> @@ -1142,6 +1144,7 @@ <translation id="5536723544185013515">ბოლოდროინდელი აპები, ყველა ბოლოდროინდელ აპზე წვდომისთვის გამოიყენეთ კლავიში მარცხნივ ან მარჯვნივ მიმართული ისრით</translation> <translation id="553675580533261935">მიმდინარეობს სესიიდან გამოსვლა</translation> <translation id="5537725057119320332">მაუწყებლობა</translation> +<translation id="554017492391497564">დასრულებულად ვერ მოინიშნა.</translation> <translation id="5546397813406633847">მომხმარებლის აღდგენა</translation> <translation id="554893713779400387">კარნახის გადართვა</translation> <translation id="5550417424894892620">ჩავლებით გადაიტანეთ ფაილები დესკტოპზე, ისინი <ph name="HOLDING_SPACE_TITLE" />-ს რომ დაამატოთ. ფაილებს დესკტოპს ვერ დაამატებთ.</translation> @@ -1336,6 +1339,7 @@ <translation id="6237231532760393653">1X</translation> <translation id="62380141479352646">ბატარეა იცლება. ბატარეის დამზოგველი ჩართულია.</translation> <translation id="6247728804802644171">შეტყობინებების გახსნა</translation> +<translation id="6249795363855770621">დასრულებულად ვერ მოინიშნა. ცადეთ ხელახლა, როცა ონლაინ იქნებით.</translation> <translation id="6254629735336163724">ჩაკეტილია ჰორიზონტალურზე</translation> <translation id="6259254695169772643">ასარჩევად ისარგებლეთ სტილუსით</translation> <translation id="6267036997247669271"><ph name="NAME" />: გააქტიურება…</translation> @@ -1496,6 +1500,7 @@ <translation id="6896758677409633944">კოპირება</translation> <translation id="6912841030378044227">მისამართთა ზოლის ფოკუსში მოქცევა</translation> <translation id="6912901278692845878">სწრაფი ტური</translation> +<translation id="6917259695595127329">ამოცანების ბოლო განახლება <ph name="TIME" />.</translation> <translation id="6919251195245069855">თქვენი სმარტ-ბარათის ამოცნობა ვერ მოხერხდა. ცადეთ ხელახლა.</translation> <translation id="692135145298539227">წაშლა</translation> <translation id="6929081673585394903">მართვის საშუალებების ჩვენება</translation> @@ -1895,7 +1900,6 @@ <translation id="8553395910833293175">უკვე მიმაგრებულია ყველა სამუშაო მაგიდაზე.</translation> <translation id="8555757996376137129">ამჟამინდელი სამუშაო მაგიდის წაშლა</translation> <translation id="856298576161209842"><ph name="MANAGER" /> გირჩევთ, განაახლოთ თქვენი <ph name="DEVICE_TYPE" /></translation> -<translation id="8563862697512465947">შეტყობინების პარამეტრები</translation> <translation id="8569751806372591456">აი, რამდენიმე საცდელი შემოთავაზება</translation> <translation id="857201607579416096">მენიუ გადატანილია ეკრანის ქვედა მარჯვენა კუთხეში.</translation> <translation id="8581946341807941670">დაჭერით აირჩიეთ <ph name="MODIFIER_1" /><ph name="MODIFIER_2" />, შემდეგ კი დააწკაპუნეთ ბმულზე</translation>
diff --git a/ash/strings/ash_strings_kk.xtb b/ash/strings/ash_strings_kk.xtb index 4034ee8..c2183c1 100644 --- a/ash/strings/ash_strings_kk.xtb +++ b/ash/strings/ash_strings_kk.xtb
@@ -1895,7 +1895,6 @@ <translation id="8553395910833293175">Барлық жұмыс үстеліне әлдеқашан тағайындалған.</translation> <translation id="8555757996376137129">Ағымдағы жұмыс үстелін өшіру</translation> <translation id="856298576161209842"><ph name="MANAGER" /> сізге <ph name="DEVICE_TYPE" /> құрылғысын жаңартуды ұсынады.</translation> -<translation id="8563862697512465947">Хабарландыру параметрлері</translation> <translation id="8569751806372591456">Мұнда ұсыныстар берілген</translation> <translation id="857201607579416096">Мәзір экранның төменгі оң жақ бұрышына жылжытылды.</translation> <translation id="8581946341807941670"><ph name="MODIFIER_1" /><ph name="MODIFIER_2" /> пернесін басып, сілтемені түртіңіз</translation>
diff --git a/ash/strings/ash_strings_km.xtb b/ash/strings/ash_strings_km.xtb index fe85d772..a6cc425 100644 --- a/ash/strings/ash_strings_km.xtb +++ b/ash/strings/ash_strings_km.xtb
@@ -358,6 +358,7 @@ <translation id="2484513351006226581">ចុច <ph name="KEYBOARD_SHORTCUT" /> ដើម្បីប្តូរប្លង់ក្តារចុច។</translation> <translation id="2486214324139475545">ការមើល <ph name="DESK_NAME" /> សាកល្បង។ តុសកម្ម។</translation> <translation id="2487915095798731898">ចូលរួម</translation> +<translation id="2499445554382787206">ម៉ឺនុយកម្រងព័ត៌មានតុ។ <ph name="DESK_NAME" /></translation> <translation id="2501920221385095727">គ្រាប់ចុចស្អិត</translation> <translation id="2504454902900101003">ច្រានចោលការរៀបចំការមើលរូបថត មេឌៀ និងការជូនដំណឹងថ្មីៗលើទូរសព្ទរបស់អ្នក</translation> <translation id="2509468283778169019">CAPS LOCK ត្រូវបានបើក</translation> @@ -604,6 +605,7 @@ <translation id="3509391053705095206">រកទូរសព្ទរបស់អ្នកមិនឃើញទេ។ សូមប្រាកដថាបានបើកប៊្លូធូសទូរសព្ទរបស់អ្នក។</translation> <translation id="3510164367642747937">រំលេចទស្សន៍ទ្រនិចកណ្ដុរ</translation> <translation id="3513798432020909783">គណនីស្ថិតក្រោមការគ្រប់គ្រងរបស់ <ph name="MANAGER_EMAIL" /></translation> +<translation id="3517037892157925473">បានធ្វើបច្ចុប្បន្នភាពកិច្ចការចុងក្រោយ៖ <ph name="TIME" /> <ph name="DATE" />។</translation> <translation id="352245152354538528">{0,plural, =1{ដំឡើងកំណែឧបករណ៍ក្នុងរយៈពេល 1 នាទី}other{ដំឡើងកំណែឧបករណ៍ក្នុងរយៈពេល # នាទី}}</translation> <translation id="3522979239100719575">កំពុងរកមើលកម្រងព័ត៌មានដែលអាចប្រើបាន។ សកម្មភាពនេះអាចចំណាយពេលពីរបីនាទី។</translation> <translation id="3526440770046466733">បើកតំណនៅក្នុងផ្ទាំងថ្មី រួចបន្តនៅក្នុងផ្ទាំងបច្ចុប្បន្ន</translation> @@ -1142,6 +1144,7 @@ <translation id="5536723544185013515">កម្មវិធីថ្មីៗ, រុករកដោយប្រើគ្រាប់ចុចព្រួញទៅឆ្វេង ឬទៅស្ដាំ ដើម្បីចូលប្រើប្រាស់កម្មវិធីថ្មីៗទាំងអស់</translation> <translation id="553675580533261935">ការចាកចេញពីសម័យ</translation> <translation id="5537725057119320332">ខាស</translation> +<translation id="554017492391497564">មិនអាចសម្គាល់ថាបានបញ្ចប់បានទេ។</translation> <translation id="5546397813406633847">ស្ដារអ្នកប្រើប្រាស់ឡើងវិញ</translation> <translation id="554893713779400387">បិទ/បើកការសរសេរតាមអាន</translation> <translation id="5550417424894892620">ទម្លាក់ឯកសារនៅលើអេក្រង់ដើម ដើម្បីបញ្ចូលឯកសារទាំងនោះទៅ <ph name="HOLDING_SPACE_TITLE" />។ អ្នកមិនអាចបញ្ចូលឯកសារទៅអេក្រង់ដើមបានទេ។</translation> @@ -1336,6 +1339,7 @@ <translation id="6237231532760393653">1X</translation> <translation id="62380141479352646">ថ្មសល់តិច។ បានបើកមុខងារសន្សំថ្ម។</translation> <translation id="6247728804802644171">បើកការជូនដំណឹង</translation> +<translation id="6249795363855770621">មិនអាចសម្គាល់ថាបានបញ្ចប់បានទេ។ សូមព្យាយាមម្ដងទៀត នៅពេលមានអ៊ីនធឺណិត។</translation> <translation id="6254629735336163724">បានចាក់សោឱ្យស្ថិតក្នុងទិសដៅផ្ដេក</translation> <translation id="6259254695169772643">ប្រើប៊ិចរបស់អ្នកដើម្បីជ្រើសរើស</translation> <translation id="6267036997247669271"><ph name="NAME" />: កំពុងធ្វើសកម្មភាព...</translation> @@ -1496,6 +1500,7 @@ <translation id="6896758677409633944">ចម្លង</translation> <translation id="6912841030378044227">ផ្ដោតលើរបារអាសយដ្ឋាន</translation> <translation id="6912901278692845878">ណែនាំបង្ហាញត្រួសៗ</translation> +<translation id="6917259695595127329">បានធ្វើបច្ចុប្បន្នភាពកិច្ចការចុងក្រោយ៖ <ph name="TIME" />។</translation> <translation id="6919251195245069855">មិនស្គាល់កាតឆ្លាតវៃរបស់អ្នកទេ។ សូមព្យាយាមម្ដងទៀត។</translation> <translation id="692135145298539227">លុប</translation> <translation id="6929081673585394903">បង្ហាញការគ្រប់គ្រង</translation> @@ -1894,7 +1899,6 @@ <translation id="8553395910833293175">បានកំណត់ទៅតុទាំងអស់រួចហើយ។</translation> <translation id="8555757996376137129">ដកតុបច្ចុប្បន្នចេញ</translation> <translation id="856298576161209842"><ph name="MANAGER" /> ណែនាំឱ្យអ្នកដំឡើងកំណែ <ph name="DEVICE_TYPE" /> របស់អ្នក</translation> -<translation id="8563862697512465947">កំណត់ការជូនដំណឹង</translation> <translation id="8569751806372591456">ទាំងនេះជាការណែនាំមួយចំនួនសម្រាប់សាកល្បង</translation> <translation id="857201607579416096">ម៉ឺនុយត្រូវបានផ្លាស់ទីទៅជ្រុងខាងក្រោមផ្នែកខាងស្ដាំនៃអេក្រង់។</translation> <translation id="8581946341807941670">ចុច <ph name="MODIFIER_1" /><ph name="MODIFIER_2" /> រួចចុចតំណ</translation>
diff --git a/ash/strings/ash_strings_kn.xtb b/ash/strings/ash_strings_kn.xtb index d8081f96..5969263 100644 --- a/ash/strings/ash_strings_kn.xtb +++ b/ash/strings/ash_strings_kn.xtb
@@ -1895,7 +1895,6 @@ <translation id="8553395910833293175">ಈಗಾಗಲೇ ಎಲ್ಲಾ ಡೆಸ್ಕ್ಗಳಿಗೆ ನಿಯೋಜಿಸಲಾಗಿದೆ.</translation> <translation id="8555757996376137129">ಪ್ರಸ್ತುತ ಡೆಸ್ಕ್ ಅನ್ನು ತೆಗೆದುಹಾಕಿ</translation> <translation id="856298576161209842">ಈ ನಿಮ್ಮ <ph name="DEVICE_TYPE" /> ಅನ್ನು ಅಪ್ಡೇಟ್ ಮಾಡಲು <ph name="MANAGER" /> ಶಿಫಾರಸು ಮಾಡುತ್ತದೆ</translation> -<translation id="8563862697512465947">ಸೂಚನೆ ಸೆಟ್ಟಿಂಗ್ಗಳು</translation> <translation id="8569751806372591456">ಪ್ರಯತ್ನಿಸಲು ಕೆಲವು ಸಲಹೆಗಳು ಇಲ್ಲಿವೆ</translation> <translation id="857201607579416096">ಮೆನುವನ್ನು ಪರದೆಯ ಕೆಳಭಾಗದಲ್ಲಿ ಬಲತುದಿಗೆ ಸರಿಸಲಾಗಿದೆ.</translation> <translation id="8581946341807941670"><ph name="MODIFIER_1" /><ph name="MODIFIER_2" /> ಒತ್ತಿರಿ ಮತ್ತು ಲಿಂಕ್ ಅನ್ನು ಕ್ಲಿಕ್ ಮಾಡಿ</translation>
diff --git a/ash/strings/ash_strings_ko.xtb b/ash/strings/ash_strings_ko.xtb index ef33442..837e490f 100644 --- a/ash/strings/ash_strings_ko.xtb +++ b/ash/strings/ash_strings_ko.xtb
@@ -1895,7 +1895,6 @@ <translation id="8553395910833293175">이미 모든 데스크에 할당되어 있습니다.</translation> <translation id="8555757996376137129">현재 데스크를 삭제합니다.</translation> <translation id="856298576161209842"><ph name="MANAGER" />에서 <ph name="DEVICE_TYPE" /> 기기의 업데이트를 권장합니다.</translation> -<translation id="8563862697512465947">알림 설정</translation> <translation id="8569751806372591456">다음은 몇 가지 제안사항입니다.</translation> <translation id="857201607579416096">메뉴가 화면 오른쪽 하단으로 이동했습니다.</translation> <translation id="8581946341807941670"><ph name="MODIFIER_1" /><ph name="MODIFIER_2" /> 키를 누른 상태에서 링크 클릭</translation>
diff --git a/ash/strings/ash_strings_ky.xtb b/ash/strings/ash_strings_ky.xtb index 3be2d832..a5eaf61 100644 --- a/ash/strings/ash_strings_ky.xtb +++ b/ash/strings/ash_strings_ky.xtb
@@ -1895,7 +1895,6 @@ <translation id="8553395910833293175">Бардык иш такталар дайындалган.</translation> <translation id="8555757996376137129">Учурдагы иш тактаны өчүрүү</translation> <translation id="856298576161209842"><ph name="MANAGER" /> <ph name="DEVICE_TYPE" /> түзмөгүңүздү жаңыртууну сунуштайт</translation> -<translation id="8563862697512465947">Билдирмелердин параметрлери</translation> <translation id="8569751806372591456">Төмөнкү сунуштарды байкап көрүңүз:</translation> <translation id="857201607579416096">Меню экрандын төмөнкү оң бурчуна жылдырылды.</translation> <translation id="8581946341807941670"><ph name="MODIFIER_1" /><ph name="MODIFIER_2" /> баскычтарын басып, шилтемени чыкылдатыңыз</translation>
diff --git a/ash/strings/ash_strings_lo.xtb b/ash/strings/ash_strings_lo.xtb index 97b2e5e..b6d41d6 100644 --- a/ash/strings/ash_strings_lo.xtb +++ b/ash/strings/ash_strings_lo.xtb
@@ -358,6 +358,7 @@ <translation id="2484513351006226581">ແຕະ <ph name="KEYBOARD_SHORTCUT" /> ເພື່ອປ່ຽນໂຄງຮ່າງແປ້ນພິມ.</translation> <translation id="2486214324139475545">ຕົວຢ່າງ <ph name="DESK_NAME" />. ໂຕະທີ່ໃຊ້ຢູ່.</translation> <translation id="2487915095798731898">ເຂົ້າຮ່ວມ</translation> +<translation id="2499445554382787206">ເມນູໂປຣໄຟລ໌ໂຕະ. <ph name="DESK_NAME" /></translation> <translation id="2501920221385095727">ປຸ່ມສະຕິກກີ້</translation> <translation id="2504454902900101003">ປິດການຕັ້ງຄ່າການເບິ່ງຮູບພາບຫຼ້າສຸດ, ມີເດຍ ແລະ ການແຈ້ງເຕືອນຢູ່ໂທລະສັບຂອງທ່ານໄວ້</translation> <translation id="2509468283778169019">CAPS LOCK ເປີດຢູ່</translation> @@ -604,6 +605,7 @@ <translation id="3509391053705095206">ບໍ່ສາມາດຊອກເຫັນໂທລະສັບຂອງທ່ານໄດ້. ກະລຸນາກວດສອບວ່າ Bluetooth ຂອງໂທລະສັບທ່ານເປີດຢູ່.</translation> <translation id="3510164367642747937">ໝາຍເຄີເຊີເມົ້າ</translation> <translation id="3513798432020909783">ບັນຊີທີ່ຈັດການໂດຍ <ph name="MANAGER_EMAIL" /></translation> +<translation id="3517037892157925473">ອັບເດດໜ້າວຽກຫຼ້າສຸດ: <ph name="TIME" />, <ph name="DATE" />.</translation> <translation id="352245152354538528">{0,plural, =1{ອັບເດດອຸປະກອນພາຍໃນ 1 ນາທີ}other{ອັບເດດອຸປະກອນພາຍໃນ # ນາທີ}}</translation> <translation id="3522979239100719575">ກຳລັງຊອກຫາໂປຣໄຟລ໌ທີ່ສາມາດໃຊ້ໄດ້. ຂັ້ນຕອນນີ້ອາດໃຊ້ເວລາສອງສາມນາທີ.</translation> <translation id="3526440770046466733">ເປີດລິ້ງໃນແຖບໃໝ່ ແລະ ຢູ່ໃນແຖບປັດຈຸບັນຕໍ່ໄປ</translation> @@ -1142,6 +1144,7 @@ <translation id="5536723544185013515">ແອັບຫຼ້າສຸດ, ນຳທາງດ້ວຍປຸ່ມລູກສອນຊ້າຍ ຫຼື ຂວາເພື່ອເຂົ້າເຖິງແອັບຫຼ້າສຸດທັງໝົດ</translation> <translation id="553675580533261935">ກໍາລັງອອກຈາກເຊດຊັນ</translation> <translation id="5537725057119320332">ຄາສທ໌</translation> +<translation id="554017492391497564">ບໍ່ສາມາດໝາຍເປັນສຳເລັດໄດ້.</translation> <translation id="5546397813406633847">ກູ້ຜູ້ໃຊ້ຄືນມາ</translation> <translation id="554893713779400387">ເປີດປິດການຂຽນຕາມຄຳບອກ</translation> <translation id="5550417424894892620">ວາງໄຟລ໌ຢູ່ເດັສທັອບເພື່ອເພີ່ມພວກມັນໄປໃສ່ <ph name="HOLDING_SPACE_TITLE" />. ທ່ານບໍ່ສາມາດເພີ່ມໄຟລ໌ນີ້ໄປໃສ່ເດັສທັອບໄດ້.</translation> @@ -1336,6 +1339,7 @@ <translation id="6237231532760393653">1X</translation> <translation id="62380141479352646">ແບັດເຕີຣີເຫຼືອໜ້ອຍ. ເປີດໃຊ້ຕົວປະຢັດແບັດເຕີຣີແລ້ວ.</translation> <translation id="6247728804802644171">ເປີດການແຈ້ງເຕືອນ</translation> +<translation id="6249795363855770621">ບໍ່ສາມາດໝາຍເປັນສຳເລັດໄດ້. ກະລຸນາລອງໃໝ່ເມື່ອອອນລາຍ.</translation> <translation id="6254629735336163724">ລັອກເປັນລວງນອນ</translation> <translation id="6259254695169772643">ໃຊ້ປາຍປາກກາຂອງທ່ານເພື່ອເລືອກ</translation> <translation id="6267036997247669271"><ph name="NAME" />: ກຳລັງເປີດນຳໃຊ້...</translation> @@ -1496,6 +1500,7 @@ <translation id="6896758677409633944">ກັອບປີ້</translation> <translation id="6912841030378044227">ເນັ້ນໃສ່ແຖບທີ່ຢູ່</translation> <translation id="6912901278692845878">ການທົວສັ້ນໆ</translation> +<translation id="6917259695595127329">ອັບເດດໜ້າວຽກຫຼ້າສຸດ: <ph name="TIME" />.</translation> <translation id="6919251195245069855">ບໍ່ສາມາດຮັບຮູ້ບັດອັດສະລິຍະຂອງທ່ານໄດ້. ລອງໃໝ່.</translation> <translation id="692135145298539227">ລຶບ</translation> <translation id="6929081673585394903">ສະແດງການຄວບຄຸມ</translation> @@ -1895,7 +1900,6 @@ <translation id="8553395910833293175">ມອບໝາຍໃຫ້ໂຕະທັງໝົດແລ້ວ.</translation> <translation id="8555757996376137129">ລຶບໂຕະປັດຈຸບັນອອກ</translation> <translation id="856298576161209842"><ph name="MANAGER" /> ແນະນຳໃຫ້ທ່ານອັບເດດ <ph name="DEVICE_TYPE" /> ຂອງທ່ານ</translation> -<translation id="8563862697512465947">ການຕັ້ງຄ່າການແຈ້ງເຕືອນ</translation> <translation id="8569751806372591456">ນີ້ແມ່ນການແນະນຳບາງຢ່າງໃຫ້ລອງ</translation> <translation id="857201607579416096">ຍ້າຍເມນູໄປແຈລຸ່ມສຸດເບື້ອງຂວາຂອງໜ້າຈໍ.</translation> <translation id="8581946341807941670">ກົດ <ph name="MODIFIER_1" /><ph name="MODIFIER_2" /> ແລ້ວຄລິກລິ້ງ</translation>
diff --git a/ash/strings/ash_strings_lt.xtb b/ash/strings/ash_strings_lt.xtb index cfc5a51..6664e1b 100644 --- a/ash/strings/ash_strings_lt.xtb +++ b/ash/strings/ash_strings_lt.xtb
@@ -1895,7 +1895,6 @@ <translation id="8553395910833293175">Jau priskirta visiems darbalaukiams.</translation> <translation id="8555757996376137129">Pašalinti dabartinį darbalaukį</translation> <translation id="856298576161209842"><ph name="MANAGER" /> rekomenduoja atnaujinti „<ph name="DEVICE_TYPE" />“</translation> -<translation id="8563862697512465947">Pranešimų nustatymai</translation> <translation id="8569751806372591456">Štai keli pasiūlymai, kuriuos galite išbandyti</translation> <translation id="857201607579416096">Meniu perkeltas į apatinį dešinįjį ekrano kampą.</translation> <translation id="8581946341807941670">Paspauskite <ph name="MODIFIER_1" /> <ph name="MODIFIER_2" /> ir spustelėkite nuorodą</translation>
diff --git a/ash/strings/ash_strings_lv.xtb b/ash/strings/ash_strings_lv.xtb index 86ab6b6d..e80edb3 100644 --- a/ash/strings/ash_strings_lv.xtb +++ b/ash/strings/ash_strings_lv.xtb
@@ -1895,7 +1895,6 @@ <translation id="8553395910833293175">Jau ir redzams visās darbvietās.</translation> <translation id="8555757996376137129">Noņemt pašreizējo darbvietu</translation> <translation id="856298576161209842"><ph name="MANAGER" /> iesaka atjaunināt ierīci <ph name="DEVICE_TYPE" /></translation> -<translation id="8563862697512465947">Paziņojumu iestatījumi</translation> <translation id="8569751806372591456">Tālāk ir sniegti daži ieteikumi, ko varat izmēģināt</translation> <translation id="857201607579416096">Izvēlne pārvietota uz ekrāna apakšējo kreiso malu.</translation> <translation id="8581946341807941670">Nospiediet <ph name="MODIFIER_1" /><ph name="MODIFIER_2" /> un noklikšķiniet uz saites</translation>
diff --git a/ash/strings/ash_strings_mk.xtb b/ash/strings/ash_strings_mk.xtb index 028402c..824f32f 100644 --- a/ash/strings/ash_strings_mk.xtb +++ b/ash/strings/ash_strings_mk.xtb
@@ -1896,7 +1896,6 @@ <translation id="8553395910833293175">Веќе е назначено на сите работни површини.</translation> <translation id="8555757996376137129">Отстранете ја тековната работна површина</translation> <translation id="856298576161209842"><ph name="MANAGER" /> препорачува да го ажурирате вашиот <ph name="DEVICE_TYPE" /></translation> -<translation id="8563862697512465947">Поставки за известувања</translation> <translation id="8569751806372591456">Еве неколку предлози за да испробате</translation> <translation id="857201607579416096">Менито е преместено во долниот десен агол на екранот.</translation> <translation id="8581946341807941670">Притиснете <ph name="MODIFIER_1" /><ph name="MODIFIER_2" /> и кликнете на линк</translation>
diff --git a/ash/strings/ash_strings_ml.xtb b/ash/strings/ash_strings_ml.xtb index 35c8758..ae9d6688 100644 --- a/ash/strings/ash_strings_ml.xtb +++ b/ash/strings/ash_strings_ml.xtb
@@ -1896,7 +1896,6 @@ <translation id="8553395910833293175">എല്ലാ ഡെസ്ക്കുകൾക്കും ഇതിനകം അസൈൻ ചെയ്തിട്ടുണ്ട്.</translation> <translation id="8555757996376137129">നിലവിലെ ഡെസ്ക് നീക്കം ചെയ്യുക</translation> <translation id="856298576161209842">നിങ്ങളുടെ <ph name="DEVICE_TYPE" /> അപ്ഡേറ്റ് ചെയ്യാൻ <ph name="MANAGER" /> നിർദ്ദേശിക്കുന്നു.</translation> -<translation id="8563862697512465947">വിജ്ഞാപന ക്രമീകരണങ്ങള്</translation> <translation id="8569751806372591456">പരീക്ഷിക്കാവുന്ന ചില നിർദ്ദേശങ്ങൾ ഇതാ</translation> <translation id="857201607579416096">സ്ക്രീനിന്റെ ചുവടെ വലത് കോണിലേക്ക് മെനു നീക്കിയിരിക്കുന്നു.</translation> <translation id="8581946341807941670"><ph name="MODIFIER_1" /><ph name="MODIFIER_2" /> അമർത്തി ഒരു ലിങ്ക് ക്ലിക്ക് ചെയ്യുക</translation>
diff --git a/ash/strings/ash_strings_mn.xtb b/ash/strings/ash_strings_mn.xtb index f744ab2b..dcafd6a 100644 --- a/ash/strings/ash_strings_mn.xtb +++ b/ash/strings/ash_strings_mn.xtb
@@ -1897,7 +1897,6 @@ <translation id="8553395910833293175">Бүх дэлгэц дээр аль хэдийн оноосон.</translation> <translation id="8555757996376137129">Одоогийн цонхыг хасах</translation> <translation id="856298576161209842"><ph name="MANAGER" /> танд өөрийн <ph name="DEVICE_TYPE" />-г шинэчлэхийг зөвлөж байна</translation> -<translation id="8563862697512465947">Мэдэгдлийн тохиргоо</translation> <translation id="8569751806372591456">Оролдож үзэх цөөн хэдэн зөвлөмж энд байна</translation> <translation id="857201607579416096">Цэсийг дэлгэцийн баруун доод булан руу зөөсөн.</translation> <translation id="8581946341807941670"><ph name="MODIFIER_1" /><ph name="MODIFIER_2" />-г дараад холбоосыг товшино уу</translation>
diff --git a/ash/strings/ash_strings_mr.xtb b/ash/strings/ash_strings_mr.xtb index 03e09b0..79234e2 100644 --- a/ash/strings/ash_strings_mr.xtb +++ b/ash/strings/ash_strings_mr.xtb
@@ -1895,7 +1895,6 @@ <translation id="8553395910833293175">सर्व डेस्कना आधीपासून असाइन केले आहे.</translation> <translation id="8555757996376137129">सध्याचा डेस्क काढून टाका</translation> <translation id="856298576161209842"><ph name="MANAGER" /> तुम्हाला तुमचे <ph name="DEVICE_TYPE" /> अपडेट करण्याची शिफारस करतो</translation> -<translation id="8563862697512465947">सूचना सेटिंग्ज</translation> <translation id="8569751806372591456">वापरून पाहण्यासाठी या काही सूचना आहेत</translation> <translation id="857201607579416096">मेनू स्क्रीनच्या तळाशी उजव्या कोपर्यात हलवला.</translation> <translation id="8581946341807941670"><ph name="MODIFIER_1" /><ph name="MODIFIER_2" /> प्रेस करा आणि लिंकवर क्लिक करा</translation>
diff --git a/ash/strings/ash_strings_ms.xtb b/ash/strings/ash_strings_ms.xtb index 7edc482..120de70 100644 --- a/ash/strings/ash_strings_ms.xtb +++ b/ash/strings/ash_strings_ms.xtb
@@ -1895,7 +1895,6 @@ <translation id="8553395910833293175">Telah pun diuntukkan kepada semua meja.</translation> <translation id="8555757996376137129">Alih keluar meja semasa</translation> <translation id="856298576161209842"><ph name="MANAGER" /> mengesyorkan supaya anda mengemas kini <ph name="DEVICE_TYPE" /> anda</translation> -<translation id="8563862697512465947">Tetapan Pemberitahuan</translation> <translation id="8569751806372591456">Yang berikut ialah beberapa cadangan untuk dicuba</translation> <translation id="857201607579416096">Menu dialihkan ke sudut bawah sebelah kanan skrin.</translation> <translation id="8581946341807941670">Tekan <ph name="MODIFIER_1" /><ph name="MODIFIER_2" /> dan klik pautan</translation>
diff --git a/ash/strings/ash_strings_my.xtb b/ash/strings/ash_strings_my.xtb index 7a77dac6..0dd9a09 100644 --- a/ash/strings/ash_strings_my.xtb +++ b/ash/strings/ash_strings_my.xtb
@@ -1895,7 +1895,6 @@ <translation id="8553395910833293175">မျက်နှာပြင်နေရာအားလုံးတွင် သတ်မှတ်ပြီးပါပြီ။</translation> <translation id="8555757996376137129">လက်ရှိမျက်နှာပြင် ဖယ်ရှားရန်</translation> <translation id="856298576161209842">သင့် <ph name="DEVICE_TYPE" /> ကိုအပ်ဒိတ်လုပ်ရန် <ph name="MANAGER" /> ကအကြံပြုသည်</translation> -<translation id="8563862697512465947">အကြောင်းကြားချက် ဆက်တင်များ</translation> <translation id="8569751806372591456">စမ်းလုပ်နိုင်သော အကြံပြုချက်အချို့ကို ပြထားသည်</translation> <translation id="857201607579416096">မီနူးကို ဖန်သားပြင်၏ ညာဘက်အောက်ခြေထောင့်သို့ ရွှေ့လိုက်သည်။</translation> <translation id="8581946341807941670"><ph name="MODIFIER_1" /><ph name="MODIFIER_2" /> ကိုဖိပြီး လင့်ခ်ကို နှိပ်ပါ</translation>
diff --git a/ash/strings/ash_strings_ne.xtb b/ash/strings/ash_strings_ne.xtb index 77c9220..4f5f882 100644 --- a/ash/strings/ash_strings_ne.xtb +++ b/ash/strings/ash_strings_ne.xtb
@@ -1895,7 +1895,6 @@ <translation id="8553395910833293175">सबै डेस्कलाई काम जिम्मा दिइसकिएको छ।</translation> <translation id="8555757996376137129">हालको डेस्क हटाउनुहोस्</translation> <translation id="856298576161209842"><ph name="MANAGER" /> तपाईंलाई <ph name="DEVICE_TYPE" /> अपडेट गर्न सिफारिस गर्छ।</translation> -<translation id="8563862697512465947">सूचनाका सेटिङहरू</translation> <translation id="8569751806372591456">तपाईंले अपनाई हेर्न सक्ने केही सुझाव यस प्रकार छन्</translation> <translation id="857201607579416096">मेनु सारेर स्क्रिनको फेदको दायाँ कुनामा लगियो।</translation> <translation id="8581946341807941670"><ph name="MODIFIER_1" /><ph name="MODIFIER_2" /> थिच्नुहोस् र कुनै लिंकमा क्लिक गर्नुहोस्</translation>
diff --git a/ash/strings/ash_strings_nl.xtb b/ash/strings/ash_strings_nl.xtb index a588e884..e4fa16b 100644 --- a/ash/strings/ash_strings_nl.xtb +++ b/ash/strings/ash_strings_nl.xtb
@@ -1895,7 +1895,6 @@ <translation id="8553395910833293175">Al toegewezen aan alle bureaus.</translation> <translation id="8555757996376137129">Huidig bureau verwijderen</translation> <translation id="856298576161209842"><ph name="MANAGER" /> raadt aan dat je je <ph name="DEVICE_TYPE" /> updatet</translation> -<translation id="8563862697512465947">Meldingsinstellingen</translation> <translation id="8569751806372591456">Je kunt de volgende suggesties proberen</translation> <translation id="857201607579416096">Menu verplaatst naar rechtsonder in het scherm.</translation> <translation id="8581946341807941670">Druk op <ph name="MODIFIER_1" /> + <ph name="MODIFIER_2" /> en klik op een link</translation>
diff --git a/ash/strings/ash_strings_no.xtb b/ash/strings/ash_strings_no.xtb index da38485..2239132b 100644 --- a/ash/strings/ash_strings_no.xtb +++ b/ash/strings/ash_strings_no.xtb
@@ -1895,7 +1895,6 @@ <translation id="8553395910833293175">Allerede tilordnet til alle skrivebord.</translation> <translation id="8555757996376137129">Fjern gjeldende skrivebord</translation> <translation id="856298576161209842"><ph name="MANAGER" /> anbefaler at du oppdaterer <ph name="DEVICE_TYPE" /></translation> -<translation id="8563862697512465947">Varslingsinnstillinger</translation> <translation id="8569751806372591456">Her er noen forslag du kan prøve</translation> <translation id="857201607579416096">Menyen ble flyttet til nedre høyre hjørne av skjermen.</translation> <translation id="8581946341807941670">Trykk på <ph name="MODIFIER_1" /><ph name="MODIFIER_2" /> og klikk på en link</translation>
diff --git a/ash/strings/ash_strings_or.xtb b/ash/strings/ash_strings_or.xtb index 372b181..b4b16eb6 100644 --- a/ash/strings/ash_strings_or.xtb +++ b/ash/strings/ash_strings_or.xtb
@@ -1894,7 +1894,6 @@ <translation id="8553395910833293175">ସମସ୍ତ ଡେସ୍କକୁ ପୂର୍ବରୁ ଆସାଇନ୍ କରାଯାଇଛି।</translation> <translation id="8555757996376137129">ବର୍ତ୍ତମାନର ଡେସ୍କ କାଢ଼ି ଦିଅନ୍ତୁ</translation> <translation id="856298576161209842"><ph name="MANAGER" /> ଆପଣଙ୍କୁ ଆପଣଙ୍କ <ph name="DEVICE_TYPE" />କୁ ଅପଡେଟ କରିବା ପାଇଁ ସୁପାରିଶ କରିଥାଏ</translation> -<translation id="8563862697512465947">ବିଜ୍ଞପ୍ତି ସେଟିଂସ୍</translation> <translation id="8569751806372591456">ବ୍ୟବହାର କରିବାକୁ ଏଠାରେ କିଛି ପରାମର୍ଶ ଅଛି</translation> <translation id="857201607579416096">ସ୍କ୍ରିନର ନିମ୍ନ-ଡାହାଣ କୋଣକୁ ମେନୁ ମୁଭ୍ କରାଯାଇଛି।</translation> <translation id="8581946341807941670"><ph name="MODIFIER_1" /><ph name="MODIFIER_2" />କୁ ଦବାଇ ଏକ ଲିଙ୍କରେ କ୍ଲିକ କରନ୍ତୁ</translation>
diff --git a/ash/strings/ash_strings_pa.xtb b/ash/strings/ash_strings_pa.xtb index 70b9a16..4aee8113 100644 --- a/ash/strings/ash_strings_pa.xtb +++ b/ash/strings/ash_strings_pa.xtb
@@ -358,6 +358,7 @@ <translation id="2484513351006226581">ਕੀ-ਬੋਰਡ ਖਾਕਾ ਬਦਲਣ ਲਈ <ph name="KEYBOARD_SHORTCUT" /> ਦਬਾਓ।</translation> <translation id="2486214324139475545"><ph name="DESK_NAME" /> ਦੀ ਪੂਰਵ-ਝਲਕ। ਕਿਰਿਆਸ਼ੀਲ ਡੈਸਕ।</translation> <translation id="2487915095798731898">ਸ਼ਾਮਲ ਹੋਵੋ</translation> +<translation id="2499445554382787206">ਡੈਸਕ ਪ੍ਰੋਫਾਈਲ ਮੀਨੂ। <ph name="DESK_NAME" /></translation> <translation id="2501920221385095727">ਸਟਿਕੀ ਕੁੰਜੀਆਂ</translation> <translation id="2504454902900101003">ਆਪਣੇ ਫ਼ੋਨ ਦੀਆਂ ਹਾਲੀਆ ਫ਼ੋਟੋਆਂ, ਮੀਡੀਆ ਅਤੇ ਸੂਚਨਾਵਾਂ ਨੂੰ ਦੇਖਣ ਦਾ ਸੈੱਟਅੱਪ ਕਰਨਾ ਖਾਰਜ ਕਰੋ</translation> <translation id="2509468283778169019">CAPS LOCK ਔਨ ਹੈ</translation> @@ -604,6 +605,7 @@ <translation id="3509391053705095206">ਤੁਹਾਡਾ ਫ਼ੋਨ ਨਹੀਂ ਲੱਭਿਆ ਜਾ ਸਕਦਾ। ਪੱਕਾ ਕਰੋ ਕਿ ਤੁਹਾਡੇ ਫ਼ੋਨ ਦਾ ਬਲੂਟੁੱਥ ਚਾਲੂ ਹੈ।</translation> <translation id="3510164367642747937">ਮਾਊਸ ਕਰਸਰ ਨੂੰ ਉਜਾਗਰ ਕਰੋ</translation> <translation id="3513798432020909783"><ph name="MANAGER_EMAIL" /> ਵੱਲੋਂ ਖਾਤੇ ਦਾ ਪ੍ਰਬੰਧਨ ਕੀਤਾ ਜਾਂਦਾ ਹੈ</translation> +<translation id="3517037892157925473">ਕਾਰਜਾਂ ਨੂੰ ਆਖਰੀ ਵਾਰ ਅੱਪਡੇਟ ਕਰਨ ਦਾ ਸਮਾਂ: <ph name="TIME" />, <ph name="DATE" />.</translation> <translation id="352245152354538528">{0,plural, =1{ਡੀਵਾਈਸ ਨੂੰ 1 ਮਿੰਟ ਦੇ ਅੰਦਰ ਅੱਪਡੇਟ ਕਰੋ}one{ਡੀਵਾਈਸ ਨੂੰ # ਮਿੰਟ ਦੇ ਅੰਦਰ ਅੱਪਡੇਟ ਕਰੋ}other{ਡੀਵਾਈਸ ਨੂੰ # ਮਿੰਟਾਂ ਦੇ ਅੰਦਰ ਅੱਪਡੇਟ ਕਰੋ}}</translation> <translation id="3522979239100719575">ਉਪਲਬਧ ਪ੍ਰੋਫਾਈਲਾਂ ਨੂੰ ਲੱਭਿਆ ਜਾ ਰਿਹਾ ਹੈ। ਇਸ ਵਿੱਚ ਕੁਝ ਮਿੰਟ ਲੱਗ ਸਕਦੇ ਹਨ।</translation> <translation id="3526440770046466733">ਨਵੀਂ ਟੈਬ ਵਿੱਚ ਲਿੰਕ ਖੋਲ੍ਹੋ ਅਤੇ ਮੌਜੂਦਾ ਟੈਬ ਵਿੱਚ ਬਣੇ ਰਹੋ</translation> @@ -1143,6 +1145,7 @@ <translation id="5536723544185013515">ਹਾਲੀਆ ਐਪਾਂ, ਸਾਰੀਆਂ ਹਾਲੀਆ ਐਪਾਂ ਤੱਕ ਪਹੁੰਚ ਕਰਨ ਲਈ ਖੱਬੀ ਜਾਂ ਸੱਜੀ ਤੀਰ ਕੁੰਜੀ ਨਾਲ ਨੈਵੀਗੇਟ ਕਰੋ</translation> <translation id="553675580533261935">ਸੈਸ਼ਨ ਤੋਂ ਬਾਹਰ ਜਾਇਆ ਜਾ ਰਿਹਾ ਹੈ</translation> <translation id="5537725057119320332">ਕਾਸਟ ਕਰੋ</translation> +<translation id="554017492391497564">ਇਸਦੀ ਮੁਕੰਮਲ ਵਜੋਂ ਨਿਸ਼ਾਨਦੇਹੀ ਨਹੀਂ ਕੀਤੀ ਜਾ ਸਕੀ।</translation> <translation id="5546397813406633847">ਵਰਤੋਂਕਾਰ ਦੇ ਪਾਸਵਰਡ ਨੂੰ ਮੁੜ-ਹਾਸਲ ਕਰੋ</translation> <translation id="554893713779400387">ਬੋਲ ਅਨੁਸਾਰ ਲਿਖਤ ਨੂੰ ਟੌਗਲ ਕਰੋ</translation> <translation id="5550417424894892620">ਫ਼ਾਈਲਾਂ ਨੂੰ <ph name="HOLDING_SPACE_TITLE" /> ਵਿੱਚ ਸ਼ਾਮਲ ਕਰਨ ਲਈ ਉਨ੍ਹਾਂ ਨੂੰ ਡੈਸਕਟਾਪ 'ਤੇ ਛੱਡੋ। ਤੁਸੀਂ ਡੈਸਕਟਾਪ 'ਤੇ ਫ਼ਾਈਲਾਂ ਸ਼ਾਮਲ ਨਹੀਂ ਕਰ ਸਕਦੇ।</translation> @@ -1337,6 +1340,7 @@ <translation id="6237231532760393653">1X</translation> <translation id="62380141479352646">ਬੈਟਰੀ ਘੱਟ ਹੈ। ਬੈਟਰੀ ਸੇਵਰ ਚਾਲੂ ਹੈ।</translation> <translation id="6247728804802644171">ਸੂਚਨਾਵਾਂ ਖੋਲ੍ਹੋ</translation> +<translation id="6249795363855770621">ਇਸਦੀ ਮੁਕੰਮਲ ਵਜੋਂ ਨਿਸ਼ਾਨਦੇਹੀ ਨਹੀਂ ਕੀਤੀ ਜਾ ਸਕੀ। ਆਨਲਾਈਨ ਹੋਣ 'ਤੇ ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰੋ।</translation> <translation id="6254629735336163724">ਲੇਟਵੀਂ ਸਥਿਤੀ ਵਿੱਚ ਲਾਕ ਕੀਤੀ ਗਈ</translation> <translation id="6259254695169772643">ਚੁਣਨ ਲਈ ਆਪਣੇ ਸਟਾਈਲਸ ਦੀ ਵਰਤੋਂ ਕਰੋ</translation> <translation id="6267036997247669271"><ph name="NAME" />: ਐਕਟੀਵੇਟ ਹੋ ਰਿਹਾ ਹੈ...</translation> @@ -1497,6 +1501,7 @@ <translation id="6896758677409633944">ਕਾਪੀ ਕਰੋ</translation> <translation id="6912841030378044227">ਪਤਾ ਬਾਰ 'ਤੇ ਫੋਕਸ ਕਰੋ</translation> <translation id="6912901278692845878">ਤਤਕਾਲ ਟੂਰ</translation> +<translation id="6917259695595127329">ਕਾਰਜਾਂ ਨੂੰ ਆਖਰੀ ਵਾਰ ਅੱਪਡੇਟ ਕਰਨ ਦਾ ਸਮਾਂ: <ph name="TIME" />.</translation> <translation id="6919251195245069855">ਤੁਹਾਡਾ ਸਮਾਰਟ ਕਾਰਡ ਪਛਾਣਿਆ ਨਹੀਂ ਜਾ ਸਕਿਆ। ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰੋ।</translation> <translation id="692135145298539227">ਮਿਟਾਓ</translation> <translation id="6929081673585394903">ਕੰਟਰੋਲ ਦਿਖਾਓ</translation> @@ -1896,7 +1901,6 @@ <translation id="8553395910833293175">ਪਹਿਲਾਂ ਹੀ ਸਾਰੇ ਡੈਸਕਾਂ ਦੇ ਜ਼ਿੰਮੇ ਲਗਾਇਆ ਗਿਆ।</translation> <translation id="8555757996376137129">ਮੌਜੂਦਾ ਡੈਸਕ ਨੂੰ ਹਟਾਓ</translation> <translation id="856298576161209842"><ph name="MANAGER" /> ਵੱਲੋਂ ਤੁਹਾਨੂੰ ਆਪਣੇ <ph name="DEVICE_TYPE" /> ਨੂੰ ਅੱਪਡੇਟ ਕਰਨ ਦੀ ਸਿਫ਼ਾਰਸ਼ ਕੀਤੀ ਜਾਂਦੀ ਹੈ</translation> -<translation id="8563862697512465947">ਸੂਚਨਾ ਸੈਟਿੰਗਾਂ</translation> <translation id="8569751806372591456">ਅਜ਼ਮਾਉਣ ਲਈ ਇਹ ਕੁਝ ਸੁਝਾਅ ਹਨ</translation> <translation id="857201607579416096">ਮੀਨੂ ਨੂੰ ਸਕ੍ਰੀਨ ਦੇ ਹੇਠਲੇ ਸੱਜੇ ਕੋਨੇ ਵਿੱਚ ਲਿਜਾਇਆ ਗਿਆ।</translation> <translation id="8581946341807941670"><ph name="MODIFIER_1" /><ph name="MODIFIER_2" /> ਦਬਾ ਕੇ ਲਿੰਕ 'ਤੇ ਕਲਿੱਕ ਕਰੋ</translation>
diff --git a/ash/strings/ash_strings_pl.xtb b/ash/strings/ash_strings_pl.xtb index 7bafd38..d529d50 100644 --- a/ash/strings/ash_strings_pl.xtb +++ b/ash/strings/ash_strings_pl.xtb
@@ -1894,7 +1894,6 @@ <translation id="8553395910833293175">Już przypisano do wszystkich biurek.</translation> <translation id="8555757996376137129">Usuń bieżące biurko</translation> <translation id="856298576161209842"><ph name="MANAGER" /> zaleca aktualizację tego urządzenia <ph name="DEVICE_TYPE" /></translation> -<translation id="8563862697512465947">Ustawienia powiadomień</translation> <translation id="8569751806372591456">Oto kilka sugestii, które możesz wypróbować</translation> <translation id="857201607579416096">Menu zostało przeniesione w prawy dolny róg ekranu.</translation> <translation id="8581946341807941670">Naciśnij <ph name="MODIFIER_1" /> + <ph name="MODIFIER_2" /> i kliknij link</translation>
diff --git a/ash/strings/ash_strings_pt-BR.xtb b/ash/strings/ash_strings_pt-BR.xtb index 33570f0..9f6db5a 100644 --- a/ash/strings/ash_strings_pt-BR.xtb +++ b/ash/strings/ash_strings_pt-BR.xtb
@@ -1895,7 +1895,6 @@ <translation id="8553395910833293175">Já atribuído a todos os espaços de trabalho.</translation> <translation id="8555757996376137129">Remover o espaço de trabalho atual</translation> <translation id="856298576161209842"><ph name="MANAGER" /> recomenda que você atualize seu <ph name="DEVICE_TYPE" /></translation> -<translation id="8563862697512465947">Configurações de notificação</translation> <translation id="8569751806372591456">Confira algumas sugestões</translation> <translation id="857201607579416096">O menu foi movido para o canto inferior direito da tela.</translation> <translation id="8581946341807941670">Pressione <ph name="MODIFIER_1" /> + <ph name="MODIFIER_2" /> e clique em um link</translation>
diff --git a/ash/strings/ash_strings_pt-PT.xtb b/ash/strings/ash_strings_pt-PT.xtb index 7460163..dda2d0bd 100644 --- a/ash/strings/ash_strings_pt-PT.xtb +++ b/ash/strings/ash_strings_pt-PT.xtb
@@ -1895,7 +1895,6 @@ <translation id="8553395910833293175">Já atribuído a todos os espaços de trabalho.</translation> <translation id="8555757996376137129">Remover espaço de trabalho atual</translation> <translation id="856298576161209842"><ph name="MANAGER" /> recomenda que atualize o dispositivo <ph name="DEVICE_TYPE" /></translation> -<translation id="8563862697512465947">Definições de notificação</translation> <translation id="8569751806372591456">Aqui estão algumas sugestões para experimentar</translation> <translation id="857201607579416096">O menu foi movido para o canto inferior direito do ecrã.</translation> <translation id="8581946341807941670">Prima <ph name="MODIFIER_1" /><ph name="MODIFIER_2" /> e clique num link</translation>
diff --git a/ash/strings/ash_strings_ro.xtb b/ash/strings/ash_strings_ro.xtb index c2bc364f7..7c85ff78 100644 --- a/ash/strings/ash_strings_ro.xtb +++ b/ash/strings/ash_strings_ro.xtb
@@ -1895,7 +1895,6 @@ <translation id="8553395910833293175">A fost deja atribuită tuturor desktopurilor.</translation> <translation id="8555757996376137129">Elimină desktopul actual</translation> <translation id="856298576161209842"><ph name="MANAGER" /> recomandă să actualizezi dispozitivul <ph name="DEVICE_TYPE" /></translation> -<translation id="8563862697512465947">Setări de notificare</translation> <translation id="8569751806372591456">Iată câteva sugestii de încercat</translation> <translation id="857201607579416096">Meniul a fost mutat în colțul din dreapta jos al ecranului.</translation> <translation id="8581946341807941670">Apasă pe <ph name="MODIFIER_1" /><ph name="MODIFIER_2" /> și dă clic pe un link</translation>
diff --git a/ash/strings/ash_strings_ru.xtb b/ash/strings/ash_strings_ru.xtb index 4a16c3c..f6c2ef3 100644 --- a/ash/strings/ash_strings_ru.xtb +++ b/ash/strings/ash_strings_ru.xtb
@@ -1895,7 +1895,6 @@ <translation id="8553395910833293175">Уже доступно на всех рабочих столах.</translation> <translation id="8555757996376137129">Удалить текущий рабочий стол</translation> <translation id="856298576161209842">Согласно рекомендациям <ph name="MANAGER" /> вам нужно обновить устройство <ph name="DEVICE_TYPE" />.</translation> -<translation id="8563862697512465947">Настройки оповещений</translation> <translation id="8569751806372591456">Вот несколько идей:</translation> <translation id="857201607579416096">Меню перемещено в правый нижний угол экрана.</translation> <translation id="8581946341807941670">Нажмите на ссылку, удерживая <ph name="MODIFIER_1" /><ph name="MODIFIER_2" /></translation>
diff --git a/ash/strings/ash_strings_si.xtb b/ash/strings/ash_strings_si.xtb index e241bd4..e0a5457 100644 --- a/ash/strings/ash_strings_si.xtb +++ b/ash/strings/ash_strings_si.xtb
@@ -1895,7 +1895,6 @@ <translation id="8553395910833293175">සියලු මේස වෙත දැනටමත් පවරා ඇත.</translation> <translation id="8555757996376137129">වත්මන් මේසය ඉවත් කරන්න</translation> <translation id="856298576161209842"><ph name="MANAGER" /> ඔබ ඔබගේ <ph name="DEVICE_TYPE" /> යාවත්කාලීන කළ යුතු බව නිර්දේශ කරයි</translation> -<translation id="8563862697512465947">දැනුම්දීම් සැකසීම්</translation> <translation id="8569751806372591456">උත්සාහ කිරීමට යෝජනා කිහිපයක් මෙන්න</translation> <translation id="857201607579416096">මෙනුව තිරයේ පහළ දකුණු කොණට ගෙන ගියා.</translation> <translation id="8581946341807941670"><ph name="MODIFIER_1" /><ph name="MODIFIER_2" /> ඔබා සබැඳියක් ක්ලික් කරන්න</translation>
diff --git a/ash/strings/ash_strings_sk.xtb b/ash/strings/ash_strings_sk.xtb index fd5d45a4..b743d29 100644 --- a/ash/strings/ash_strings_sk.xtb +++ b/ash/strings/ash_strings_sk.xtb
@@ -1895,7 +1895,6 @@ <translation id="8553395910833293175">Už bolo pridelené všetkým plochám.</translation> <translation id="8555757996376137129">Odstránenie aktuálnej plochy</translation> <translation id="856298576161209842"><ph name="MANAGER" /> odporúča, aby ste aktualizovali zariadenie <ph name="DEVICE_TYPE" /></translation> -<translation id="8563862697512465947">Nastavenia upozornení</translation> <translation id="8569751806372591456">Tu je niekoľko návrhov, ktoré môžete vyskúšať</translation> <translation id="857201607579416096">Ponuka bola presunutá do pravého dolného rohu obrazovky.</translation> <translation id="8581946341807941670">Stlačte <ph name="MODIFIER_1" /><ph name="MODIFIER_2" /> a kliknite na odkaz</translation>
diff --git a/ash/strings/ash_strings_sl.xtb b/ash/strings/ash_strings_sl.xtb index 730bb67c..3cfc7bf 100644 --- a/ash/strings/ash_strings_sl.xtb +++ b/ash/strings/ash_strings_sl.xtb
@@ -1895,7 +1895,6 @@ <translation id="8553395910833293175">Že dodeljeno vsem namizjem.</translation> <translation id="8555757996376137129">Odstranitev trenutnega namizja</translation> <translation id="856298576161209842"><ph name="MANAGER" /> priporoča, da posodobite napravo <ph name="DEVICE_TYPE" />.</translation> -<translation id="8563862697512465947">Nastavitve obvestil</translation> <translation id="8569751806372591456">Tukaj je nekaj predlogov, ki jih lahko poskusite</translation> <translation id="857201607579416096">Meni je bil premaknjen v spodnji desni kot zaslona.</translation> <translation id="8581946341807941670">Pritisnite <ph name="MODIFIER_1" /><ph name="MODIFIER_2" /> in kliknite povezavo</translation>
diff --git a/ash/strings/ash_strings_sq.xtb b/ash/strings/ash_strings_sq.xtb index 30ea6fc..93fd04e 100644 --- a/ash/strings/ash_strings_sq.xtb +++ b/ash/strings/ash_strings_sq.xtb
@@ -1895,7 +1895,6 @@ <translation id="8553395910833293175">Është caktuar tashmë te të gjitha tavolinat e punës.</translation> <translation id="8555757996376137129">Hiq tavolinën aktuale të punës</translation> <translation id="856298576161209842"><ph name="MANAGER" /> rekomandon që të përditësosh pajisjen tënde <ph name="DEVICE_TYPE" /></translation> -<translation id="8563862697512465947">Cilësimet e njoftimeve</translation> <translation id="8569751806372591456">Këtu janë disa sugjerime për t'i provuar</translation> <translation id="857201607579416096">Menyja u zhvendos në këndin poshtë djathtas të ekranit.</translation> <translation id="8581946341807941670">Shtyp <ph name="MODIFIER_1" /><ph name="MODIFIER_2" /> dhe kliko një lidhje</translation>
diff --git a/ash/strings/ash_strings_sr-Latn.xtb b/ash/strings/ash_strings_sr-Latn.xtb index d97a2fdf..9f16a1c8 100644 --- a/ash/strings/ash_strings_sr-Latn.xtb +++ b/ash/strings/ash_strings_sr-Latn.xtb
@@ -358,6 +358,7 @@ <translation id="2484513351006226581">Pritisnite <ph name="KEYBOARD_SHORTCUT" /> da biste promenili raspored tastature.</translation> <translation id="2486214324139475545">Pregled radne površine <ph name="DESK_NAME" />. Aktivna radna površina.</translation> <translation id="2487915095798731898">Pridruži me</translation> +<translation id="2499445554382787206">Meni za profil na radnoj površini. <ph name="DESK_NAME" /></translation> <translation id="2501920221385095727">Lepljivi tasteri</translation> <translation id="2504454902900101003">Odbacite podešavanje pregleda nedavnih slika, medija i obaveštenja na telefonu</translation> <translation id="2509468283778169019">CAPS LOCK je uključen</translation> @@ -604,6 +605,7 @@ <translation id="3509391053705095206">Pronalaženje telefona nije uspelo. Proverite da li je Bluetooth na telefonu uključen.</translation> <translation id="3510164367642747937">Istakni kursor miša</translation> <translation id="3513798432020909783">Nalogom upravlja <ph name="MANAGER_EMAIL" /></translation> +<translation id="3517037892157925473">Datum i vreme poslednjeg ažuriranja zadataka: <ph name="TIME" />, <ph name="DATE" />.</translation> <translation id="352245152354538528">{0,plural, =1{Ažurirajte uređaj u roku od 1 minuta}one{Ažurirajte uređaj u roku od # minuta}few{Ažurirajte uređaj u roku od # minuta}other{Ažurirajte uređaj u roku od # minuta}}</translation> <translation id="3522979239100719575">Traže se dostupni profili. To može da potraje nekoliko minuta.</translation> <translation id="3526440770046466733">Otvori link na novoj kartici i ostani na aktuelnoj kartici</translation> @@ -1143,6 +1145,7 @@ <translation id="5536723544185013515">Nedavne aplikacije, krećite se pomoću tastera sa strelicom nalevo ili nadesno da biste pristupali svim nedavnim aplikacijama</translation> <translation id="553675580533261935">Napuštanje sesije</translation> <translation id="5537725057119320332">Prebacuj</translation> +<translation id="554017492391497564">Označavanje stavke kao da je dovršena nije uspelo.</translation> <translation id="5546397813406633847">Vrati pristup korisničkom nalogu</translation> <translation id="554893713779400387">Uključi/isključi diktiranje</translation> <translation id="5550417424894892620">Otpustite fajlove na pozadini da biste ih dodali u <ph name="HOLDING_SPACE_TITLE" />. Ne možete da dodajete fajlove na pozadini.</translation> @@ -1337,6 +1340,7 @@ <translation id="6237231532760393653">1X</translation> <translation id="62380141479352646">Slaba baterija. Ušteda baterije je uključena.</translation> <translation id="6247728804802644171">Otvorite obaveštenja</translation> +<translation id="6249795363855770621">Označavanje stavke kao da je dovršena nije uspelo. Probajte ponovo kada budete onlajn.</translation> <translation id="6254629735336163724">Horizontalni prikaz je zaključan</translation> <translation id="6259254695169772643">Izaberite pomoću pisaljke</translation> <translation id="6267036997247669271"><ph name="NAME" />: Aktiviranje...</translation> @@ -1497,6 +1501,7 @@ <translation id="6896758677409633944">Kopiraj</translation> <translation id="6912841030378044227">Fokusiranje na traku za adresu</translation> <translation id="6912901278692845878">Kratak obilazak</translation> +<translation id="6917259695595127329">Vreme poslednjeg ažuriranja zadataka: <ph name="TIME" />.</translation> <translation id="6919251195245069855">Nismo uspeli da prepoznamo pametnu karticu. Probajte ponovo.</translation> <translation id="692135145298539227">izbriši</translation> <translation id="6929081673585394903">Pokaži kontrole</translation> @@ -1897,7 +1902,6 @@ <translation id="8553395910833293175">Već je dodeljeno svim radnim površinama.</translation> <translation id="8555757996376137129">Ukloni aktuelnu radnu površinu</translation> <translation id="856298576161209842"><ph name="MANAGER" /> preporučuje da ažurirate <ph name="DEVICE_TYPE" /></translation> -<translation id="8563862697512465947">Podešavanja obaveštenja</translation> <translation id="8569751806372591456">Evo par predloga koje možete da isprobate</translation> <translation id="857201607579416096">Meni je premešten u donji desni ugao ekrana.</translation> <translation id="8581946341807941670">Pritisnite <ph name="MODIFIER_1" /> <ph name="MODIFIER_2" /> i kliknite na link</translation>
diff --git a/ash/strings/ash_strings_sr.xtb b/ash/strings/ash_strings_sr.xtb index e162d3a..fc429eed 100644 --- a/ash/strings/ash_strings_sr.xtb +++ b/ash/strings/ash_strings_sr.xtb
@@ -358,6 +358,7 @@ <translation id="2484513351006226581">Притисните <ph name="KEYBOARD_SHORTCUT" /> да бисте променили распоред тастатуре.</translation> <translation id="2486214324139475545">Преглед радне површине <ph name="DESK_NAME" />. Активна радна површина.</translation> <translation id="2487915095798731898">Придружи ме</translation> +<translation id="2499445554382787206">Мени за профил на радној површини. <ph name="DESK_NAME" /></translation> <translation id="2501920221385095727">Лепљиви тастери</translation> <translation id="2504454902900101003">Одбаците подешавање прегледа недавних слика, медија и обавештења на телефону</translation> <translation id="2509468283778169019">CAPS LOCK је укључен</translation> @@ -604,6 +605,7 @@ <translation id="3509391053705095206">Проналажење телефона није успело. Проверите да ли је Bluetooth на телефону укључен.</translation> <translation id="3510164367642747937">Истакни курсор миша</translation> <translation id="3513798432020909783">Налогом управља <ph name="MANAGER_EMAIL" /></translation> +<translation id="3517037892157925473">Датум и време последњег ажурирања задатака: <ph name="TIME" />, <ph name="DATE" />.</translation> <translation id="352245152354538528">{0,plural, =1{Ажурирајте уређај у року од 1 минута}one{Ажурирајте уређај у року од # минута}few{Ажурирајте уређај у року од # минута}other{Ажурирајте уређај у року од # минута}}</translation> <translation id="3522979239100719575">Траже се доступни профили. То може да потраје неколико минута.</translation> <translation id="3526440770046466733">Отвори линк на новој картици и остани на актуелној картици</translation> @@ -1143,6 +1145,7 @@ <translation id="5536723544185013515">Недавне апликације, крећите се помоћу тастера са стрелицом налево или надесно да бисте приступали свим недавним апликацијама</translation> <translation id="553675580533261935">Напуштање сесије</translation> <translation id="5537725057119320332">Пребацуј</translation> +<translation id="554017492391497564">Означавање ставке као да је довршена није успело.</translation> <translation id="5546397813406633847">Врати приступ корисничком налогу</translation> <translation id="554893713779400387">Укључи/искључи диктирање</translation> <translation id="5550417424894892620">Отпустите фајлове на позадини да бисте их додали у <ph name="HOLDING_SPACE_TITLE" />. Не можете да додајете фајлове на позадини.</translation> @@ -1337,6 +1340,7 @@ <translation id="6237231532760393653">1X</translation> <translation id="62380141479352646">Слаба батерија. Уштеда батерије је укључена.</translation> <translation id="6247728804802644171">Отворите обавештења</translation> +<translation id="6249795363855770621">Означавање ставке као да је довршена није успело. Пробајте поново када будете онлајн.</translation> <translation id="6254629735336163724">Хоризонтални приказ је закључан</translation> <translation id="6259254695169772643">Изаберите помоћу писаљке</translation> <translation id="6267036997247669271"><ph name="NAME" />: Активирање...</translation> @@ -1497,6 +1501,7 @@ <translation id="6896758677409633944">Копирај</translation> <translation id="6912841030378044227">Фокусирање на траку за адресу</translation> <translation id="6912901278692845878">Кратак обилазак</translation> +<translation id="6917259695595127329">Време последњег ажурирања задатака: <ph name="TIME" />.</translation> <translation id="6919251195245069855">Нисмо успели да препознамо паметну картицу. Пробајте поново.</translation> <translation id="692135145298539227">избриши</translation> <translation id="6929081673585394903">Покажи контроле</translation> @@ -1897,7 +1902,6 @@ <translation id="8553395910833293175">Већ је додељено свим радним површинама.</translation> <translation id="8555757996376137129">Уклони актуелну радну површину</translation> <translation id="856298576161209842"><ph name="MANAGER" /> препоручује да ажурирате <ph name="DEVICE_TYPE" /></translation> -<translation id="8563862697512465947">Подешавања обавештења</translation> <translation id="8569751806372591456">Ево пар предлога које можете да испробате</translation> <translation id="857201607579416096">Мени је премештен у доњи десни угао екрана.</translation> <translation id="8581946341807941670">Притисните <ph name="MODIFIER_1" /> <ph name="MODIFIER_2" /> и кликните на линк</translation>
diff --git a/ash/strings/ash_strings_sv.xtb b/ash/strings/ash_strings_sv.xtb index 27bc2740..1d825f4 100644 --- a/ash/strings/ash_strings_sv.xtb +++ b/ash/strings/ash_strings_sv.xtb
@@ -1895,7 +1895,6 @@ <translation id="8553395910833293175">Detta har redan tilldelats alla skrivbord.</translation> <translation id="8555757996376137129">Ta bort aktuellt skrivbord</translation> <translation id="856298576161209842"><ph name="MANAGER" /> rekommenderar att du uppdaterar din <ph name="DEVICE_TYPE" /></translation> -<translation id="8563862697512465947">Aviseringsinställningar</translation> <translation id="8569751806372591456">Här är några förslag du kan prova</translation> <translation id="857201607579416096">Menyn har flyttats till skärmens nedre högra hörn.</translation> <translation id="8581946341807941670">Tryck på <ph name="MODIFIER_1" /><ph name="MODIFIER_2" /> och klicka på en länk</translation>
diff --git a/ash/strings/ash_strings_sw.xtb b/ash/strings/ash_strings_sw.xtb index 9751845..8f58714a 100644 --- a/ash/strings/ash_strings_sw.xtb +++ b/ash/strings/ash_strings_sw.xtb
@@ -1896,7 +1896,6 @@ <translation id="8553395910833293175">Tayari limewekwa kwenye maeneokazi yote.</translation> <translation id="8555757996376137129">Ondoa eneokazi la sasa</translation> <translation id="856298576161209842"><ph name="MANAGER" /> inapendekeza usasishe <ph name="DEVICE_TYPE" /> yako</translation> -<translation id="8563862697512465947">Mipangilio ya Arifa</translation> <translation id="8569751806372591456">Yafuatayo ni baadhi ya mapendekezo unayoweza kujaribu</translation> <translation id="857201607579416096">Menyu imehamishiwa kwenye kona ya chini kulia mwa skrini.</translation> <translation id="8581946341807941670">Bonyeza <ph name="MODIFIER_1" /><ph name="MODIFIER_2" /> na ubofye kiungo</translation>
diff --git a/ash/strings/ash_strings_ta.xtb b/ash/strings/ash_strings_ta.xtb index 56a04b75..3d55fc0 100644 --- a/ash/strings/ash_strings_ta.xtb +++ b/ash/strings/ash_strings_ta.xtb
@@ -1895,7 +1895,6 @@ <translation id="8553395910833293175">எல்லா டெஸ்க்குகளுக்கும் ஏற்கெனவே ஒதுக்கப்பட்டுள்ளது.</translation> <translation id="8555757996376137129">தற்போதைய டெஸ்க்கை அகற்று</translation> <translation id="856298576161209842">உங்கள் <ph name="DEVICE_TYPE" /> ஐப் புதுப்பிக்குமாறு <ph name="MANAGER" /> பரிந்துரைக்கிறது</translation> -<translation id="8563862697512465947">அறிவிப்பு அமைப்புகள்</translation> <translation id="8569751806372591456">இதோ சில பரிந்துரைகள்</translation> <translation id="857201607579416096">திரையின் கீழ் வலது மூலைக்கு மெனு நகர்த்தப்பட்டது.</translation> <translation id="8581946341807941670"><ph name="MODIFIER_1" /><ph name="MODIFIER_2" /> அழுத்தி, இணைப்பைக் கிளிக் செய்யவும்</translation>
diff --git a/ash/strings/ash_strings_te.xtb b/ash/strings/ash_strings_te.xtb index 8efa87fd..8142ff8 100644 --- a/ash/strings/ash_strings_te.xtb +++ b/ash/strings/ash_strings_te.xtb
@@ -1896,7 +1896,6 @@ <translation id="8553395910833293175">ఇప్పటికే అన్ని డెస్క్లకు కేటాయింపు జరిగిపోయింది.</translation> <translation id="8555757996376137129">ప్రస్తుత డెస్క్ను తీసివేయండి</translation> <translation id="856298576161209842">ఈ <ph name="DEVICE_TYPE" />ను అప్డేట్ చేయమని <ph name="MANAGER" /> మీకు సిఫార్సు చేస్తోంది</translation> -<translation id="8563862697512465947">నోటిఫికేషన్ సెట్టింగ్లు</translation> <translation id="8569751806372591456">ట్రై చేయడానికి ఇక్కడ కొన్ని సూచనలు ఉన్నాయి</translation> <translation id="857201607579416096">స్క్రీన్లో కింద కుడి వైపు మూలకు మెనూ తరలించబడింది.</translation> <translation id="8581946341807941670"><ph name="MODIFIER_1" /><ph name="MODIFIER_2" />ను నొక్కి, లింక్ను క్లిక్ చేయండి</translation>
diff --git a/ash/strings/ash_strings_th.xtb b/ash/strings/ash_strings_th.xtb index 43639ed..12a0fff 100644 --- a/ash/strings/ash_strings_th.xtb +++ b/ash/strings/ash_strings_th.xtb
@@ -1890,7 +1890,6 @@ <translation id="8553395910833293175">มอบหมายให้ทุกเดสก์แล้ว</translation> <translation id="8555757996376137129">นำเดสก์ปัจจุบันออก</translation> <translation id="856298576161209842"><ph name="MANAGER" /> แนะนำให้คุณอัปเดต <ph name="DEVICE_TYPE" /></translation> -<translation id="8563862697512465947">การตั้งค่าการแจ้งเตือน</translation> <translation id="8569751806372591456">ลองทำตามคำแนะนำต่อไปนี้</translation> <translation id="857201607579416096">เมนูได้ย้ายไปอยู่ที่มุมขวาล่างของหน้าจอ</translation> <translation id="8581946341807941670">กด <ph name="MODIFIER_1" /><ph name="MODIFIER_2" /> แล้วคลิกลิงก์</translation>
diff --git a/ash/strings/ash_strings_tr.xtb b/ash/strings/ash_strings_tr.xtb index 7b1df69..146f4f4 100644 --- a/ash/strings/ash_strings_tr.xtb +++ b/ash/strings/ash_strings_tr.xtb
@@ -1895,7 +1895,6 @@ <translation id="8553395910833293175">Zaten tüm masalara atandı.</translation> <translation id="8555757996376137129">Geçerli masayı kaldır</translation> <translation id="856298576161209842"><ph name="MANAGER" />, <ph name="DEVICE_TYPE" /> cihazınızı güncellemenizi öneriyor</translation> -<translation id="8563862697512465947">Bildirim Ayarları</translation> <translation id="8569751806372591456">Deneyebileceğiniz birkaç öneriyi burada bulabilirsiniz</translation> <translation id="857201607579416096">Menü, ekranın sağ alt köşesine taşındı.</translation> <translation id="8581946341807941670"><ph name="MODIFIER_1" /><ph name="MODIFIER_2" /> tuşunu basılı tutarken bağlantıyı tıklayın</translation>
diff --git a/ash/strings/ash_strings_uk.xtb b/ash/strings/ash_strings_uk.xtb index 23e7234..b023997 100644 --- a/ash/strings/ash_strings_uk.xtb +++ b/ash/strings/ash_strings_uk.xtb
@@ -1895,7 +1895,6 @@ <translation id="8553395910833293175">Уже призначено для всіх робочих столів.</translation> <translation id="8555757996376137129">Видалити поточний робочий стіл</translation> <translation id="856298576161209842"><ph name="MANAGER" /> радить оновити пристрій <ph name="DEVICE_TYPE" /></translation> -<translation id="8563862697512465947">Налаштування сповіщення</translation> <translation id="8569751806372591456">Спробуйте ввести наведені нижче запити</translation> <translation id="857201607579416096">Меню переміщено в нижній правий кут екрана.</translation> <translation id="8581946341807941670">Натисніть <ph name="MODIFIER_1" /><ph name="MODIFIER_2" />, а потім – посилання</translation>
diff --git a/ash/strings/ash_strings_ur.xtb b/ash/strings/ash_strings_ur.xtb index a7d554c..eaf2e65 100644 --- a/ash/strings/ash_strings_ur.xtb +++ b/ash/strings/ash_strings_ur.xtb
@@ -1894,7 +1894,6 @@ <translation id="8553395910833293175">پہلے ہی تمام ڈیسکس کو تفویض کر دیا گیا۔</translation> <translation id="8555757996376137129">حالیہ ڈیسک کو ہٹائیں</translation> <translation id="856298576161209842"><ph name="MANAGER" /> کی تجویز ہے کہ آپ اپنے <ph name="DEVICE_TYPE" /> کو اپ ڈیٹ کریں</translation> -<translation id="8563862697512465947">اطلاع کی ترتیبات</translation> <translation id="8569751806372591456">کوشش کرنے کے لیے یہاں کچھ تجاویز ہیں</translation> <translation id="857201607579416096">مینیو کو اسکرین کے نچلے دائیں کونے میں منتقل کر دیا گیا۔</translation> <translation id="8581946341807941670"><ph name="MODIFIER_1" /><ph name="MODIFIER_2" /> دبائیں اور کسی لنک پر کلک کریں</translation>
diff --git a/ash/strings/ash_strings_uz.xtb b/ash/strings/ash_strings_uz.xtb index 2613ac9b..ebaee02 100644 --- a/ash/strings/ash_strings_uz.xtb +++ b/ash/strings/ash_strings_uz.xtb
@@ -1895,7 +1895,6 @@ <translation id="8553395910833293175">Allaqachon barcha ish stollarida mavjud</translation> <translation id="8555757996376137129">Joriy ish stolini olib tashlash</translation> <translation id="856298576161209842"><ph name="MANAGER" /> <ph name="DEVICE_TYPE" /> tizimini eski versiyasiga qaytarishni tavsiya etadi</translation> -<translation id="8563862697512465947">Bildirishnoma sozlamalari</translation> <translation id="8569751806372591456">Quyidagi takliflarni sinang</translation> <translation id="857201607579416096">Menyu ekranning quyi oʻng burchagiga surildi.</translation> <translation id="8581946341807941670"><ph name="MODIFIER_1" /><ph name="MODIFIER_2" /> bilan havola ustiga bosing</translation>
diff --git a/ash/strings/ash_strings_vi.xtb b/ash/strings/ash_strings_vi.xtb index 4022bd9..235f518 100644 --- a/ash/strings/ash_strings_vi.xtb +++ b/ash/strings/ash_strings_vi.xtb
@@ -1895,7 +1895,6 @@ <translation id="8553395910833293175">Đã được gán cho tất cả không gian làm việc.</translation> <translation id="8555757996376137129">Xoá không gian làm việc hiện tại</translation> <translation id="856298576161209842"><ph name="MANAGER" /> khuyên bạn nên cập nhật thiết bị <ph name="DEVICE_TYPE" /> của mình</translation> -<translation id="8563862697512465947">Cài đặt Thông báo</translation> <translation id="8569751806372591456">Bạn có thể thử một số gợi ý sau đây</translation> <translation id="857201607579416096">Đã di chuyển trình đơn vào góc dưới cùng bên phải màn hình.</translation> <translation id="8581946341807941670">Nhấn phím <ph name="MODIFIER_1" /><ph name="MODIFIER_2" /> rồi nhấp vào một đường liên kết</translation>
diff --git a/ash/strings/ash_strings_zh-CN.xtb b/ash/strings/ash_strings_zh-CN.xtb index 5437155..702ab269 100644 --- a/ash/strings/ash_strings_zh-CN.xtb +++ b/ash/strings/ash_strings_zh-CN.xtb
@@ -1893,7 +1893,6 @@ <translation id="8553395910833293175">此前就已分配至所有桌面。</translation> <translation id="8555757996376137129">移除当前桌面</translation> <translation id="856298576161209842"><ph name="MANAGER" /> 建议您更新 <ph name="DEVICE_TYPE" /></translation> -<translation id="8563862697512465947">通知设置</translation> <translation id="8569751806372591456">不妨尝试以下搜索建议</translation> <translation id="857201607579416096">菜单已移至屏幕的右下角。</translation> <translation id="8581946341807941670">在按 <ph name="MODIFIER_1" /><ph name="MODIFIER_2" /> 的同时点击链接</translation>
diff --git a/ash/strings/ash_strings_zh-HK.xtb b/ash/strings/ash_strings_zh-HK.xtb index 498f81a..0e3a671 100644 --- a/ash/strings/ash_strings_zh-HK.xtb +++ b/ash/strings/ash_strings_zh-HK.xtb
@@ -18,7 +18,7 @@ <translation id="1056775291175587022">沒有網絡</translation> <translation id="1056898198331236512">警告</translation> <translation id="1058009965971887428">提供意見</translation> -<translation id="1059120031266247284">已與你分享</translation> +<translation id="1059120031266247284">與你分享</translation> <translation id="1059194134494239015"><ph name="DISPLAY_NAME" />:<ph name="RESOLUTION" /></translation> <translation id="1062407476771304334">取代</translation> <translation id="1073899992769346247">請更換電池或充電</translation> @@ -644,7 +644,7 @@ <translation id="3621202678540785336">輸入</translation> <translation id="3621712662352432595">音效檔案設定</translation> <translation id="3626281679859535460">亮度</translation> -<translation id="3628323833346754646">正面按鈕</translation> +<translation id="3628323833346754646">前按鈕</translation> <translation id="3630697955794050612">關閉</translation> <translation id="3631369015426612114">允許顯示來自以下來源的通知</translation> <translation id="3633097874324966332">開啟「藍牙設定」以配對裝置</translation> @@ -1347,7 +1347,7 @@ <translation id="6309219492973062892">點擊或輕按捷徑列圖示 1-8</translation> <translation id="6315170314923504164">語音</translation> <translation id="6319058840130157106">在右下角、啟動器、網址列、書籤列、開啟的網站和下載項目之間向後移動</translation> -<translation id="6319503618073410818">在瀏覽器中查看詳細資料</translation> +<translation id="6319503618073410818">在瀏覽器中查看詳情</translation> <translation id="6324916366299863871">編輯捷徑</translation> <translation id="6330012934079202188">顯示緊來自所有桌面嘅視窗,㩒向上箭咀掣就可以顯示來自目前桌面嘅視窗</translation> <translation id="6338485349199627913">「<ph name="DISPLAY_NAME" />」是由 <ph name="MANAGER" /> 管理的工作階段</translation> @@ -1893,7 +1893,6 @@ <translation id="8553395910833293175">已指派給所有桌面。</translation> <translation id="8555757996376137129">移除目前的桌面</translation> <translation id="856298576161209842"><ph name="MANAGER" /> 建議您更新 <ph name="DEVICE_TYPE" /></translation> -<translation id="8563862697512465947">通知設定</translation> <translation id="8569751806372591456">試試以下幾項建議</translation> <translation id="857201607579416096">已經將選單移去螢幕右下角。</translation> <translation id="8581946341807941670">按 <ph name="MODIFIER_1" /><ph name="MODIFIER_2" /> 鍵,然後按一下連結</translation> @@ -2023,7 +2022,7 @@ <translation id="9098969848082897657">將手機設為靜音</translation> <translation id="9121941381564890244"><ph name="SNIP" /> 或 <ph name="CTRL" /><ph name="SEPARATOR1" /><ph name="SHIFT" /><ph name="SEPARATOR2" /><ph name="OVERVIEW" /></translation> <translation id="9126339866969410112">復原上一個操作</translation> -<translation id="9129245940793250979">背面按鈕</translation> +<translation id="9129245940793250979">後按鈕</translation> <translation id="9133335900048457298">無法錄影受保護的內容</translation> <translation id="9139720510312328767">刪除下一個字母</translation> <translation id="9151906066336345901">End 鍵</translation>
diff --git a/ash/strings/ash_strings_zh-TW.xtb b/ash/strings/ash_strings_zh-TW.xtb index ef09211c..0b7c8cc 100644 --- a/ash/strings/ash_strings_zh-TW.xtb +++ b/ash/strings/ash_strings_zh-TW.xtb
@@ -1891,7 +1891,6 @@ <translation id="8553395910833293175">已指派給所有桌面。</translation> <translation id="8555757996376137129">移除目前的桌面</translation> <translation id="856298576161209842"><ph name="MANAGER" /> 建議你更新 <ph name="DEVICE_TYPE" /></translation> -<translation id="8563862697512465947">通知設定</translation> <translation id="8569751806372591456">試試以下幾項搜尋建議</translation> <translation id="857201607579416096">已將選單移至畫面右下角。</translation> <translation id="8581946341807941670">按下 <ph name="MODIFIER_1" /><ph name="MODIFIER_2" /> 鍵,然後點選連結</translation>
diff --git a/ash/strings/ash_strings_zu.xtb b/ash/strings/ash_strings_zu.xtb index eec73a1..cbe97513 100644 --- a/ash/strings/ash_strings_zu.xtb +++ b/ash/strings/ash_strings_zu.xtb
@@ -1895,7 +1895,6 @@ <translation id="8553395910833293175">Sewusayinde kuwo wonke amatafula.</translation> <translation id="8555757996376137129">Susa ideski lamanje</translation> <translation id="856298576161209842">I-<ph name="MANAGER" /> incoma ukuba ubuyekeze le <ph name="DEVICE_TYPE" />.</translation> -<translation id="8563862697512465947">Izilungiselelo Zesaziso</translation> <translation id="8569751806372591456">Nakhu ukusikisela okumbalwa ongakuzama</translation> <translation id="857201607579416096">Imenyu iyiswe phansi ekhoneni elingakwesokudla sesikrini.</translation> <translation id="8581946341807941670">Cindezela okuthi <ph name="MODIFIER_1" /><ph name="MODIFIER_2" /> uphinde uchofoze ilinki</translation>
diff --git a/ash/style/keyboard_shortcut_view.cc b/ash/style/keyboard_shortcut_view.cc index cbffdd4..9724b4f 100644 --- a/ash/style/keyboard_shortcut_view.cc +++ b/ash/style/keyboard_shortcut_view.cc
@@ -28,7 +28,7 @@ auto icon_view = std::make_unique<SearchResultInlineIconView>( /*use_modified_styling=*/true, /*is_first_key=*/i == 0); icon_view->SetCanProcessEventsWithinSubtree(false); - icon_view->GetViewAccessibility().OverrideIsIgnored(true); + icon_view->GetViewAccessibility().SetIsIgnored(true); icon_view->SetProperty( views::kFlexBehaviorKey, views::FlexSpecification(views::MinimumFlexSizeRule::kScaleToMinimum,
diff --git a/ash/system/accessibility/accessibility_detailed_view.cc b/ash/system/accessibility/accessibility_detailed_view.cc index a316c1d..1b50c1c0 100644 --- a/ash/system/accessibility/accessibility_detailed_view.cc +++ b/ash/system/accessibility/accessibility_detailed_view.cc
@@ -668,7 +668,7 @@ // Ignore the toggle for accessibility. auto& view_accessibility = toggle->GetViewAccessibility(); view_accessibility.OverrideIsLeaf(true); - view_accessibility.OverrideIsIgnored(true); + view_accessibility.SetIsIgnored(true); item->AddRightView(toggle.release()); } return item;
diff --git a/ash/system/accessibility/dictation_button_tray.cc b/ash/system/accessibility/dictation_button_tray.cc index a63593a..3208c71 100644 --- a/ash/system/accessibility/dictation_button_tray.cc +++ b/ash/system/accessibility/dictation_button_tray.cc
@@ -7,6 +7,7 @@ #include "ash/accessibility/accessibility_controller.h" #include "ash/constants/ash_pref_names.h" #include "ash/constants/tray_background_view_catalog.h" +#include "ash/display/window_tree_host_manager.h" #include "ash/metrics/user_metrics_recorder.h" #include "ash/public/cpp/accessibility_controller_enums.h" #include "ash/public/cpp/shelf_config.h"
diff --git a/ash/system/audio/audio_detailed_view.cc b/ash/system/audio/audio_detailed_view.cc index fc161c0..69445d7d 100644 --- a/ash/system/audio/audio_detailed_view.cc +++ b/ash/system/audio/audio_detailed_view.cc
@@ -514,7 +514,7 @@ // Ignore the toggle for accessibility. auto& view_accessibility = toggle->GetViewAccessibility(); view_accessibility.OverrideIsLeaf(true); - view_accessibility.OverrideIsIgnored(true); + view_accessibility.SetIsIgnored(true); noise_cancellation_button_ = toggle.get(); noise_cancellation_view->AddRightView(toggle.release());
diff --git a/ash/system/bluetooth/bluetooth_detailed_view_impl.cc b/ash/system/bluetooth/bluetooth_detailed_view_impl.cc index aff8256..7ba0bbf 100644 --- a/ash/system/bluetooth/bluetooth_detailed_view_impl.cc +++ b/ash/system/bluetooth/bluetooth_detailed_view_impl.cc
@@ -219,8 +219,8 @@ toggle_row_->tri_view()->SetInsets(kToggleRowTriViewInsets); // ChromeVox users will just use the `toggle_button_` to toggle. - toggle_icon_->GetViewAccessibility().OverrideIsIgnored(true); - toggle_row_->text_label()->GetViewAccessibility().OverrideIsIgnored(true); + toggle_icon_->GetViewAccessibility().SetIsIgnored(true); + toggle_row_->text_label()->GetViewAccessibility().SetIsIgnored(true); } void BluetoothDetailedViewImpl::CreateMainContainer() {
diff --git a/ash/system/brightness/display_detailed_view.cc b/ash/system/brightness/display_detailed_view.cc index 687ebe4..8a32dce3c 100644 --- a/ash/system/brightness/display_detailed_view.cc +++ b/ash/system/brightness/display_detailed_view.cc
@@ -17,6 +17,7 @@ #include "ash/system/night_light/night_light_feature_pod_controller.h" #include "ash/system/status_area_widget.h" #include "ash/system/tray/detailed_view_delegate.h" +#include "ash/system/tray/tray_constants.h" #include "ash/system/tray/tray_popup_utils.h" #include "ash/system/unified/feature_tile.h" #include "ash/system/unified/unified_system_tray.h" @@ -26,6 +27,8 @@ #include "ui/views/border.h" #include "ui/views/controls/scroll_view.h" #include "ui/views/layout/flex_layout.h" +#include "ui/views/layout/flex_layout_types.h" +#include "ui/views/layout/flex_layout_view.h" #include "ui/views/view.h" #include "ui/views/view_class_properties.h" @@ -34,9 +37,9 @@ namespace { constexpr auto kScrollViewMargin = gfx::Insets::TLBR(0, 12, 16, 12); -constexpr auto kTileMargin = gfx::Insets::TLBR(4, 4, 12, 4); -constexpr auto kSliderPadding = gfx::Insets::TLBR(0, 0, 4, 0); constexpr auto kSliderBorder = gfx::Insets(4); +constexpr auto kSliderPadding = gfx::Insets::TLBR(0, 0, 4, 0); +constexpr auto kTileContainerMargins = gfx::Insets::TLBR(4, 4, 12, 4); } // namespace @@ -56,21 +59,30 @@ auto dark_mode_controller = std::make_unique<DarkModeFeaturePodController>( unified_system_tray_controller_); - auto tile_container = std::make_unique<views::View>(); - // Sets the ID for testing. - tile_container->SetID(VIEW_ID_QS_DISPLAY_TILE_CONTAINER); + auto tile_container = + views::Builder<views::FlexLayoutView>() + .SetID(VIEW_ID_QS_DISPLAY_TILE_CONTAINER) + .SetPreferredSize( + gfx::Size(GetPreferredSize().width(), + kFeatureTileHeight + kTileContainerMargins.height())) + .CustomConfigure(base::BindOnce([](views::FlexLayoutView* layout) { + layout->SetDefault(views::kMarginsKey, kTileContainerMargins); + })) + .Build(); + tile_container->AddChildView(night_light_controller->CreateTile()); tile_container->AddChildView(dark_mode_controller->CreateTile()); - // Transfers the ownership so the controllers won't die while the page is - // open. + // Set `PreferredSize` to (1,1) so the `FlexLayout` allocates the same width + // for the child tiles based on their equal weights. + for (auto tile : tile_container->children()) { + tile->SetPreferredSize(gfx::Size(1, 1)); + } + + // Transfer ownership so the controllers won't die while the page is open. feature_tile_controllers_.push_back(std::move(night_light_controller)); feature_tile_controllers_.push_back(std::move(dark_mode_controller)); - auto* tile_layout = - tile_container->SetLayoutManager(std::make_unique<views::FlexLayout>()); - tile_layout->SetDefault(views::kMarginsKey, kTileMargin); - scroll_content()->AddChildView(std::move(tile_container)); brightness_slider_controller_ =
diff --git a/ash/system/holding_space/holding_space_item_chip_view.cc b/ash/system/holding_space/holding_space_item_chip_view.cc index 7433dc8..5d0adeb4 100644 --- a/ash/system/holding_space/holding_space_item_chip_view.cc +++ b/ash/system/holding_space/holding_space_item_chip_view.cc
@@ -141,7 +141,7 @@ } void SetViewAccessibilityIsIgnored(bool is_ignored) { - GetViewAccessibility().OverrideIsIgnored(is_ignored); + GetViewAccessibility().SetIsIgnored(is_ignored); } private:
diff --git a/ash/system/network/network_feature_tile_pixeltest.cc b/ash/system/network/network_feature_tile_pixeltest.cc index b2f09f0..5d4ff5e 100644 --- a/ash/system/network/network_feature_tile_pixeltest.cc +++ b/ash/system/network/network_feature_tile_pixeltest.cc
@@ -3,6 +3,7 @@ // found in the LICENSE file. #include "ash/system/network/network_feature_pod_controller.h" +#include "ash/system/tray/tray_constants.h" #include "ash/system/unified/unified_system_tray.h" #include "ash/system/unified/unified_system_tray_bubble.h" #include "ash/test/ash_test_base.h" @@ -12,7 +13,9 @@ #include "chromeos/ash/services/network_config/public/cpp/cros_network_config_test_helper.h" #include "chromeos/constants/chromeos_features.h" #include "ui/chromeos/styles/cros_tokens_color_mappings.h" +#include "ui/views/layout/flex_layout_types.h" #include "ui/views/view.h" +#include "ui/views/view_class_properties.h" #include "ui/views/widget/widget.h" namespace ash { @@ -36,6 +39,19 @@ constexpr char kServicePatternWiFi[] = R"({ "GUID": "%s", "Type": "wifi", "State": "online", "Strength": 100, "SecurityClass": "%s"})"; + +// Configures a `Feature Tile` base to follow Quick Settings sizing standards. +void ConfigureQSFeatureTile(FeatureTile* tile) { + // Quick Settings Feature Tiles set a fixed size for their feature tiles. + tile->SetPreferredSize( + gfx::Size(kPrimaryFeatureTileWidth, kFeatureTileHeight)); + tile->SetProperty( + views::kFlexBehaviorKey, + views::FlexSpecification(views::MinimumFlexSizeRule::kScaleToZero, + views::MaximumFlexSizeRule::kPreferred, + /*adjust_height_for_width=*/false)); +} + } // namespace // Pixel test for the quick settings network feature tile view. @@ -69,6 +85,7 @@ feature_tile_ = widget_->GetContentsView()->AddChildView(std::move(feature_tile)); + ConfigureQSFeatureTile(feature_tile_); // Add the non-default cellular and ethernet devices to Shill. network_state_helper()->manager_test()->AddTechnology(shill::kTypeCellular,
diff --git a/ash/system/network/network_list_network_header_view.cc b/ash/system/network/network_list_network_header_view.cc index 497cb3d..6155e0f 100644 --- a/ash/system/network/network_list_network_header_view.cc +++ b/ash/system/network/network_list_network_header_view.cc
@@ -56,8 +56,8 @@ entry_row()->SetExpandable(true); entry_row()->AddRightView(toggle.release()); // ChromeVox users will use the `toggle_` to toggle the feature. - entry_row()->left_view()->GetViewAccessibility().OverrideIsIgnored(true); - entry_row()->text_label()->GetViewAccessibility().OverrideIsIgnored(true); + entry_row()->left_view()->GetViewAccessibility().SetIsIgnored(true); + entry_row()->text_label()->GetViewAccessibility().SetIsIgnored(true); } NetworkListNetworkHeaderView::~NetworkListNetworkHeaderView() = default;
diff --git a/ash/system/tray/tray_background_view_unittest.cc b/ash/system/tray/tray_background_view_unittest.cc index 7c266bce..2a6a6da 100644 --- a/ash/system/tray/tray_background_view_unittest.cc +++ b/ash/system/tray/tray_background_view_unittest.cc
@@ -16,6 +16,7 @@ #include "ash/system/tray/tray_bubble_wrapper.h" #include "ash/system/tray/tray_utils.h" #include "ash/test/ash_test_base.h" +#include "ash/wm/tablet_mode/tablet_mode_controller.h" #include "base/memory/raw_ptr.h" #include "base/test/metrics/histogram_tester.h" #include "base/test/task_environment.h" @@ -23,6 +24,8 @@ #include "ui/compositor/layer_animator.h" #include "ui/compositor/scoped_animation_duration_scale_mode.h" #include "ui/compositor/test/layer_animation_stopped_waiter.h" +#include "ui/display/manager/display_manager.h" +#include "ui/display/manager/managed_display_info.h" namespace ash {
diff --git a/ash/system/tray/tray_constants.h b/ash/system/tray/tray_constants.h index 8266606..3a2c98d 100644 --- a/ash/system/tray/tray_constants.h +++ b/ash/system/tray/tray_constants.h
@@ -213,6 +213,8 @@ inline constexpr int kFeatureTileMaxRows = 4; inline constexpr int kFeatureTileMaxRowsWhenMediaViewIsShowing = 3; inline constexpr int kFeatureTileMinRows = 1; +inline constexpr int kPrimaryFeatureTileWidth = 180; +inline constexpr int kCompactFeatureTileWidth = 86; inline constexpr int kFeatureTileHeight = 64; // Constants used in system tray page transition animations.
diff --git a/ash/system/tray/tray_detailed_view.cc b/ash/system/tray/tray_detailed_view.cc index 38f000af..93e70639 100644 --- a/ash/system/tray/tray_detailed_view.cc +++ b/ash/system/tray/tray_detailed_view.cc
@@ -46,6 +46,7 @@ #include "ui/views/controls/scroll_view.h" #include "ui/views/layout/box_layout.h" #include "ui/views/layout/box_layout_view.h" +#include "ui/views/layout/flex_layout_view.h" #include "ui/views/view_class_properties.h" #include "ui/views/view_targeter.h" #include "ui/views/view_targeter_delegate.h" @@ -165,11 +166,12 @@ void TrayDetailedView::CreateScrollableList() { DCHECK(!scroller_); - auto scroll_content = std::make_unique<views::BoxLayoutView>(); - scroll_content->SetOrientation(views::BoxLayout::Orientation::kVertical); scroller_ = AddChildView(std::make_unique<views::ScrollView>()); scroller_->SetDrawOverflowIndicator(false); - scroll_content_ = scroller_->SetContents(std::move(scroll_content)); + scroll_content_ = scroller_->SetContents( + views::Builder<views::FlexLayoutView>() + .SetOrientation(views::LayoutOrientation::kVertical) + .Build()); auto vertical_scroll = std::make_unique<RoundedScrollBar>( views::ScrollBar::Orientation::kVertical);
diff --git a/ash/system/unified/feature_tile.cc b/ash/system/unified/feature_tile.cc index 95d95c2..f48635ef1 100644 --- a/ash/system/unified/feature_tile.cc +++ b/ash/system/unified/feature_tile.cc
@@ -36,7 +36,6 @@ #include "ui/views/controls/highlight_path_generator.h" #include "ui/views/controls/image_view.h" #include "ui/views/controls/label.h" -#include "ui/views/layout/box_layout.h" #include "ui/views/layout/flex_layout.h" #include "ui/views/layout/flex_layout_view.h" #include "ui/views/layout/layout_types.h" @@ -56,7 +55,6 @@ constexpr float kFocusRingPadding = 3.0f; // Primary tile constants -constexpr gfx::Size kDefaultSize(180, kFeatureTileHeight); constexpr gfx::Size kIconButtonSize(36, 52); constexpr int kIconButtonCornerRadius = 12; constexpr gfx::Insets kIconButtonMargins = gfx::Insets::VH(6, 6); @@ -68,7 +66,6 @@ // Compact tile constants constexpr int kCompactWidth = 86; constexpr int kCompactTitleLineHeight = 14; -constexpr gfx::Size kCompactSize(kCompactWidth, kFeatureTileHeight); constexpr gfx::Size kCompactIconButtonSize(kIconSize, kIconSize); constexpr gfx::Insets kCompactIconButtonMargins = gfx::Insets::TLBR(6, 22, 4, 22); @@ -205,10 +202,17 @@ void FeatureTile::CreateChildViews() { const bool is_compact = type_ == TileType::kCompact; - auto* layout_manager = SetLayoutManager(std::make_unique<views::BoxLayout>()); - layout_manager->SetOrientation( - is_compact ? views::BoxLayout::Orientation::kVertical - : views::BoxLayout::Orientation::kHorizontal); + SetLayoutManager(std::make_unique<views::FlexLayout>()) + ->SetOrientation(is_compact ? views::LayoutOrientation::kVertical + : views::LayoutOrientation::kHorizontal) + .SetMainAxisAlignment(views::LayoutAlignment::kCenter); + // Set `MaximumFlexSizeRule` to `kUnbounded` so the view takes up all of the + // available space in its parent container. + SetProperty(views::kFlexBehaviorKey, + views::FlexSpecification(views::FlexSpecification( + views::MinimumFlexSizeRule::kScaleToZero, + views::MaximumFlexSizeRule::kUnbounded, + /*adjust_height_for_width=*/true))); ink_drop_container_ = AddChildView(std::make_unique<views::InkDropContainerView>()); @@ -216,8 +220,6 @@ auto* focus_ring = views::FocusRing::Get(this); focus_ring->SetColorId(cros_tokens::kCrosSysFocusRing); - SetPreferredSize(is_compact ? kCompactSize : kDefaultSize); - icon_button_ = AddChildView(std::make_unique<views::ImageButton>()); icon_button_->SetImageHorizontalAlignment(views::ImageButton::ALIGN_CENTER); icon_button_->SetImageVerticalAlignment(views::ImageButton::ALIGN_MIDDLE); @@ -230,11 +232,23 @@ icon_button_->SetEnabled(false); icon_button_->SetCanProcessEventsWithinSubtree(false); - title_container_ = AddChildView(std::make_unique<FlexLayoutView>()); - title_container_->SetCanProcessEventsWithinSubtree(false); - title_container_->SetOrientation(views::LayoutOrientation::kVertical); - title_container_->SetMainAxisAlignment(views::LayoutAlignment::kCenter); - title_container_->SetCrossAxisAlignment(views::LayoutAlignment::kStretch); + title_container_ = + AddChildView(views::Builder<FlexLayoutView>() + .SetCanProcessEventsWithinSubtree(false) + .SetOrientation(views::LayoutOrientation::kVertical) + .SetMainAxisAlignment(views::LayoutAlignment::kCenter) + .SetCrossAxisAlignment(views::LayoutAlignment::kStretch) + .Build()); + // Set `MaximumFlexSizeRule` to `kUnbounded` so that `title_container_` takes + // up all of the available space in the middle of the primary tile. + if (!is_compact) { + title_container_->SetProperty( + views::kFlexBehaviorKey, + views::FlexSpecification( + views::FlexSpecification(views::MinimumFlexSizeRule::kScaleToZero, + views::MaximumFlexSizeRule::kUnbounded, + /*adjust_height_for_width=*/true))); + } label_ = title_container_->AddChildView(std::make_unique<views::Label>()); label_->SetAutoColorReadabilityEnabled(false); @@ -263,7 +277,6 @@ sub_label_->SetVisible(false); } else { // `title_container_` will take all the remaining space of the tile. - layout_manager->SetFlexForView(title_container_, 1); title_container_->SetProperty(views::kMarginsKey, kTitleContainerWithoutDiveInButtonMargins); label_->SetHorizontalAlignment(gfx::ALIGN_LEFT); @@ -300,7 +313,8 @@ } void FeatureTile::CreateDecorativeDrillInArrow() { - CHECK_EQ(type_, TileType::kPrimary); + CHECK_EQ(type_, TileType::kPrimary) + << "Drill-in arrows are just used in Primary tiles"; title_container_->SetProperty(views::kMarginsKey, kTitleContainerWithDiveInButtonMargins); @@ -465,6 +479,9 @@ } label_->SetText(label); + if (GetTooltipText().empty()) { + SetTooltipText(label); + } } int FeatureTile::GetSubLabelMaxWidth() const { @@ -557,6 +574,12 @@ ink_drop_container_->RemoveLayerFromRegions(layer); } +void FeatureTile::OnBoundsChanged(const gfx::Rect& previous_bounds) { + // Manual updating of the focus ring is necessary due to b/326983304, where it + // fails to update when the bounds of a view with a `FlexLayout` change. + views::FocusRing::Get(this)->InvalidateLayout(); +} + ui::ColorId FeatureTile::GetIconColorId() const { if (!GetEnabled()) { return cros_tokens::kCrosSysDisabled;
diff --git a/ash/system/unified/feature_tile.h b/ash/system/unified/feature_tile.h index 7becbc8..9515fb1 100644 --- a/ash/system/unified/feature_tile.h +++ b/ash/system/unified/feature_tile.h
@@ -175,6 +175,7 @@ void GetAccessibleNodeData(ui::AXNodeData* node_data) override; void AddLayerToRegion(ui::Layer* layer, views::LayerRegion region) override; void RemoveLayerFromRegions(ui::Layer* layer) override; + void OnBoundsChanged(const gfx::Rect& previous_bounds) override; base::WeakPtr<FeatureTile> GetWeakPtr() { return weak_ptr_factory_.GetWeakPtr();
diff --git a/ash/system/unified/feature_tile_pixeltest.cc b/ash/system/unified/feature_tile_pixeltest.cc index 9b49e1d..e49c397 100644 --- a/ash/system/unified/feature_tile_pixeltest.cc +++ b/ash/system/unified/feature_tile_pixeltest.cc
@@ -5,6 +5,7 @@ #include <memory> #include "ash/constants/ash_features.h" +#include "ash/system/tray/tray_constants.h" #include "ash/system/unified/feature_tile.h" #include "ash/system/video_conference/fake_video_conference_tray_controller.h" #include "ash/test/ash_test_base.h" @@ -28,8 +29,36 @@ #include "ui/views/widget/widget.h" namespace ash { + namespace { +// Quick Settings `FeatureTile` size constants. +constexpr gfx::Size kQSPrimaryTileSize = + gfx::Size(kPrimaryFeatureTileWidth, kFeatureTileHeight); +constexpr gfx::Size kQSCompactTileSize = + gfx::Size(kCompactFeatureTileWidth, kFeatureTileHeight); + +// Creates a `Feature Tile` base that follows Quick Settings sizing standards. +FeatureTile* CreateQSFeatureTileBase(views::Widget* widget, + bool is_compact = false) { + auto tile = std::make_unique<FeatureTile>( + views::Button::PressedCallback(), /*is_togglable=*/true, + is_compact ? FeatureTile::TileType::kCompact + : FeatureTile::TileType::kPrimary); + + // Quick Settings Feature Tiles set a fixed size for their feature tiles. + tile->SetPreferredSize(is_compact ? kQSCompactTileSize : kQSPrimaryTileSize); + tile->SetProperty( + views::kFlexBehaviorKey, + views::FlexSpecification(views::MinimumFlexSizeRule::kScaleToZero, + views::MaximumFlexSizeRule::kPreferred, + /*adjust_height_for_width=*/false)); + + return widget->GetContentsView()->AddChildView(std::move(tile)); +} + +} // namespace + // Pixel tests for the quick settings feature tile view. class FeatureTilePixelTest : public AshTestBase { public: @@ -76,10 +105,7 @@ }; TEST_F(FeatureTilePixelTest, PrimaryTile) { - auto* tile = - widget_->GetContentsView()->AddChildView(std::make_unique<FeatureTile>( - views::Button::PressedCallback(), /*is_togglable=*/true, - FeatureTile::TileType::kPrimary)); + auto* tile = CreateQSFeatureTileBase(widget_.get()); tile->SetVectorIcon(vector_icons::kDogfoodIcon); tile->SetLabel(u"Label"); tile->SetSubLabel(u"Sub-label"); @@ -110,10 +136,7 @@ } TEST_F(FeatureTilePixelTest, PrimaryTileWithoutDiveInButton) { - auto* tile = - widget_->GetContentsView()->AddChildView(std::make_unique<FeatureTile>( - views::Button::PressedCallback(), /*is_togglable=*/true, - FeatureTile::TileType::kPrimary)); + auto* tile = CreateQSFeatureTileBase(widget_.get()); tile->SetVectorIcon(vector_icons::kDogfoodIcon); tile->SetLabel(u"Label"); tile->SetSubLabel(u"Sub-label"); @@ -148,10 +171,7 @@ base::RunLoop().RunUntilIdle(); EXPECT_TRUE(base::i18n::IsRTL()); - auto* tile = - widget_->GetContentsView()->AddChildView(std::make_unique<FeatureTile>( - views::Button::PressedCallback(), /*is_togglable=*/true, - FeatureTile::TileType::kPrimary)); + auto* tile = CreateQSFeatureTileBase(widget_.get()); tile->SetVectorIcon(vector_icons::kDogfoodIcon); tile->SetLabel(u"Label"); tile->SetSubLabel(u"Sub-label"); @@ -166,10 +186,7 @@ } TEST_F(FeatureTilePixelTest, CompactTile) { - auto* tile = - widget_->GetContentsView()->AddChildView(std::make_unique<FeatureTile>( - views::Button::PressedCallback(), /*is_togglable=*/true, - FeatureTile::TileType::kCompact)); + auto* tile = CreateQSFeatureTileBase(widget_.get(), /*is_compact=*/true); tile->SetVectorIcon(vector_icons::kDogfoodIcon); tile->SetLabel(u"Multi-line label"); // Needed for accessibility paint checks. @@ -347,5 +364,4 @@ /*revision_number=*/0, widget_.get())); } -} // namespace } // namespace ash
diff --git a/ash/system/unified/feature_tiles_container_view.cc b/ash/system/unified/feature_tiles_container_view.cc index bc3659dc..fb44164 100644 --- a/ash/system/unified/feature_tiles_container_view.cc +++ b/ash/system/unified/feature_tiles_container_view.cc
@@ -38,8 +38,15 @@ return kPrimaryTileWeight; case FeatureTile::TileType::kCompact: return kCompactTileWeight; - default: - NOTREACHED(); + } +} + +int GetTileWidth(FeatureTile::TileType type) { + switch (type) { + case FeatureTile::TileType::kPrimary: + return kPrimaryFeatureTileWidth; + case FeatureTile::TileType::kCompact: + return kCompactFeatureTileWidth; } } @@ -141,6 +148,13 @@ // Invisible tiles don't take any weight. if (tile->GetVisible()) { row_weight += GetTileWeight(tile->tile_type()); + tile->SetPreferredSize( + gfx::Size(GetTileWidth(tile->tile_type()), kFeatureTileHeight)); + tile->SetProperty( + views::kFlexBehaviorKey, + views::FlexSpecification(views::MinimumFlexSizeRule::kScaleToZero, + views::MaximumFlexSizeRule::kPreferred, + /*adjust_height_for_width=*/true)); } DCHECK_LE(row_weight, kMaxRowWeight); rows_.back()->AddChildView(std::move(tile));
diff --git a/ash/system/unified/unified_system_tray.cc b/ash/system/unified/unified_system_tray.cc index 3203325..624faa8 100644 --- a/ash/system/unified/unified_system_tray.cc +++ b/ash/system/unified/unified_system_tray.cc
@@ -550,7 +550,7 @@ } void UnifiedSystemTray::UpdateTrayItemColor(bool is_active) { - for (auto* tray_item : tray_items_) { + for (TrayItemView* tray_item : tray_items_) { tray_item->UpdateLabelOrImageViewColor(is_active); } }
diff --git a/ash/system/unified/unified_system_tray.h b/ash/system/unified/unified_system_tray.h index 83485304..1f211c06 100644 --- a/ash/system/unified/unified_system_tray.h +++ b/ash/system/unified/unified_system_tray.h
@@ -256,7 +256,7 @@ raw_ptr<ChannelIndicatorView> channel_indicator_view_ = nullptr; // Contains all tray items views added to tray_container(). - std::list<TrayItemView*> tray_items_; + std::list<raw_ptr<TrayItemView, CtnExperimental>> tray_items_; bool first_interaction_recorded_ = false;
diff --git a/ash/utility/occlusion_tracker_pauser.h b/ash/utility/occlusion_tracker_pauser.h index 054015a..9aeb888 100644 --- a/ash/utility/occlusion_tracker_pauser.h +++ b/ash/utility/occlusion_tracker_pauser.h
@@ -8,6 +8,7 @@ #include <memory> #include "ash/ash_export.h" +#include "base/memory/raw_ptr.h" #include "base/scoped_multi_source_observation.h" #include "base/time/time.h" #include "base/timer/timer.h" @@ -49,7 +50,8 @@ // Keeps track of compositors that are animating. We can unpause when this is // empty. - base::flat_set<ui::Compositor*> animating_compositors_; + base::flat_set<raw_ptr<ui::Compositor, CtnExperimental>> + animating_compositors_; std::unique_ptr<aura::WindowOcclusionTracker::ScopedPause> scoped_pause_; };
diff --git a/ash/webui/camera_app_ui/resources.h b/ash/webui/camera_app_ui/resources.h index 3a72b92..61edb3c 100644 --- a/ash/webui/camera_app_ui/resources.h +++ b/ash/webui/camera_app_ui/resources.h
@@ -60,12 +60,6 @@ {"expert_enable_full_sized_video_snapshot", IDS_EXPERT_ENABLE_FULL_SIZED_VIDEO_SNAPSHOT}, {"expert_enable_ptz_for_builtin", IDS_EXPERT_ENABLE_PTZ_FOR_BUILTIN}, - {"expert_multistream_recording", IDS_EXPERT_MULTISTREAM_RECORDING}, - {"expert_multistream_recording_chrome", - IDS_EXPERT_MULTISTREAM_RECORDING_CHROME}, - {"expert_multistream_recording_disabled", - IDS_EXPERT_MULTISTREAM_RECORDING_DISABLED}, - {"expert_multistream_recording_hal", IDS_EXPERT_MULTISTREAM_RECORDING_HAL}, {"expert_mode_button", IDS_EXPERT_MODE_BUTTON}, {"expert_preview_metadata", IDS_EXPERT_PREVIEW_METADATA}, {"expert_print_performance_logs", IDS_EXPERT_PRINT_PERFORMANCE_LOGS},
diff --git a/ash/webui/camera_app_ui/resources/js/device/camera_operation.ts b/ash/webui/camera_app_ui/resources/js/device/camera_operation.ts index 309145e..b75ee697 100644 --- a/ash/webui/camera_app_ui/resources/js/device/camera_operation.ts +++ b/ash/webui/camera_app_ui/resources/js/device/camera_operation.ts
@@ -28,10 +28,10 @@ FakeCameraCaptureCandidate, } from './capture_candidate.js'; import {CaptureCandidatePreferrer} from './capture_candidate_preferrer.js'; +import {DeviceMonitor} from './device_monitor.js'; import {Modes, Video} from './mode/index.js'; import {Preview} from './preview.js'; import {StreamConstraints} from './stream_constraints.js'; -import {StreamManager} from './stream_manager.js'; import { CameraConfig, CameraConfigCandidate, @@ -409,6 +409,8 @@ private readonly togglePausedEventQueue = new AsyncJobQueue('drop'); + private readonly deviceMonitor = new DeviceMonitor(); + constructor( private readonly listener: EventListener, preview: Preview, @@ -423,7 +425,7 @@ defaultFacing, ); this.capturer = new Capturer(this.modes); - StreamManager.getInstance().addRealDeviceChangeListener((devices) => { + this.deviceMonitor.addDeviceChangeListener((devices) => { const info = new CameraInfo(devices); if (this.ongoingOperationType !== null) { this.pendingUpdateInfo = info; @@ -435,7 +437,7 @@ async initialize(cameraViewUI: CameraViewUI): Promise<void> { this.modes.initialize(cameraViewUI); - await StreamManager.getInstance().deviceUpdate(); + await this.deviceMonitor.deviceUpdate(); await this.firstInfoUpdate.wait(); }
diff --git a/ash/webui/camera_app_ui/resources/js/device/capture_candidate.ts b/ash/webui/camera_app_ui/resources/js/device/capture_candidate.ts index 8d0b9ae8..93082e0 100644 --- a/ash/webui/camera_app_ui/resources/js/device/capture_candidate.ts +++ b/ash/webui/camera_app_ui/resources/js/device/capture_candidate.ts
@@ -129,8 +129,7 @@ } getStreamConstraintsCandidates(): StreamConstraints[] { - // For non-multistream recording, preview stream is used directly - // to do video recording. + // Preview stream is used directly to do video recording. const {width, height} = this.resolution; const buildConstraint = (frameRate: MediaTrackConstraints['frameRate']) => ({ @@ -159,45 +158,6 @@ } } -export class MultiStreamVideoCaptureCandidate extends VideoCaptureCandidate { - constructor( - deviceId: string, resolution: Resolution, - previewResolutions: Resolution[], constFps: number|null, - hasAudio: boolean) { - super(deviceId, resolution, previewResolutions, constFps, hasAudio); - } - - override getStreamConstraintsCandidates(): StreamConstraints[] { - const frameRate = - this.constFps === null ? {min: 20, ideal: 30} : {exact: this.constFps}; - const buildConstraint = - (frameRate: MediaTrackConstraints['frameRate'], - {width, height}: Resolution) => ({ - deviceId: this.deviceId, - audio: this.hasAudio, - video: { - frameRate, - width, - height, - }, - }); - const streamConstraints = []; - for (const previewResolution of this.previewResolutions) { - streamConstraints.push(buildConstraint(frameRate, previewResolution)); - } - - // If another web app is opened and requests a low fps streaming, CCA will - // get an OverconstrainedError. In this case, the constraint is relaxed but - // the error message is kept in the log. - if (this.constFps === null) { - for (const previewResolution of this.previewResolutions) { - streamConstraints.push(buildConstraint({ideal: 30}, previewResolution)); - } - } - return streamConstraints; - } -} - export class FakeCameraCaptureCandidate implements CaptureCandidate { readonly resolution = null;
diff --git a/ash/webui/camera_app_ui/resources/js/device/capture_candidate_preferrer.ts b/ash/webui/camera_app_ui/resources/js/device/capture_candidate_preferrer.ts index d3e0cc7..78e1a45 100644 --- a/ash/webui/camera_app_ui/resources/js/device/capture_candidate_preferrer.ts +++ b/ash/webui/camera_app_ui/resources/js/device/capture_candidate_preferrer.ts
@@ -22,7 +22,6 @@ } from './camera3_device_info.js'; import { CaptureCandidate, - MultiStreamVideoCaptureCandidate, PhotoCaptureCandidate, VideoCaptureCandidate, } from './capture_candidate.js'; @@ -431,11 +430,6 @@ CaptureCandidate[] { const cameraInfo = this.cameraInfos.get(deviceId); assert(cameraInfo !== undefined); - const enableMultiStreamRecording = - expert.isEnabled(expert.ExpertOption.ENABLE_MULTISTREAM_RECORDING) || - expert.isEnabled( - expert.ExpertOption.ENABLE_MULTISTREAM_RECORDING_CHROME); - const candidates = []; const prefLevel = this.prefVideoResolutionLevelMap[deviceId]; const prefResolution = this.prefVideoResolutionMap[deviceId] ?? null; @@ -454,14 +448,8 @@ const previewResolutions = videoPreviewPair.previewResolutions; for (const {constFps, resolutions} of option.fpsOptions) { for (const resolution of resolutions) { - let candidate; - if (enableMultiStreamRecording) { - candidate = new MultiStreamVideoCaptureCandidate( - deviceId, resolution, previewResolutions, constFps, hasAudio); - } else { - candidate = new VideoCaptureCandidate( - deviceId, resolution, previewResolutions, constFps, hasAudio); - } + const candidate = new VideoCaptureCandidate( + deviceId, resolution, previewResolutions, constFps, hasAudio); if (prefFps === constFps) { targetFpsCandidates.push(candidate); } else {
diff --git a/ash/webui/camera_app_ui/resources/js/device/device_monitor.ts b/ash/webui/camera_app_ui/resources/js/device/device_monitor.ts new file mode 100644 index 0000000..5508ca0 --- /dev/null +++ b/ash/webui/camera_app_ui/resources/js/device/device_monitor.ts
@@ -0,0 +1,163 @@ +// Copyright 2024 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import {reportError} from '../error.js'; +import {I18nString} from '../i18n_string.js'; +import * as loadTimeData from '../models/load_time_data.js'; +import {DeviceOperator} from '../mojo/device_operator.js'; +import {speak} from '../spoken_msg.js'; +import {ErrorLevel, ErrorType, VideoConfig} from '../type.js'; +import {sleep} from '../util.js'; + +import {Camera3DeviceInfo} from './camera3_device_info.js'; + +/** + * DeviceInfo includes MediaDeviceInfo and Camera3DeviceInfo. + */ +export interface DeviceInfo { + v1Info: MediaDeviceInfo; + v3Info: Camera3DeviceInfo|null; +} + +/** + * Monitors device changes and provides different listener callbacks for + * device changes. + */ +export class DeviceMonitor { + /** + * Array of DeviceInfo of all available video devices. + */ + private devicesInfo: DeviceInfo[] = []; + + /** + * Array of listeners for device change event. + */ + private readonly listeners: Array<(devices: DeviceInfo[]) => void> = []; + + /** + * Filters out lagging 720p on grunt. See https://crbug.com/1122852. + */ + private readonly videoConfigFilter: (config: VideoConfig) => boolean; + + constructor() { + this.videoConfigFilter = (() => { + const board = loadTimeData.getBoard(); + return board === 'grunt' ? ({height}: VideoConfig) => height < 720 : + () => true; + })(); + + navigator.mediaDevices.addEventListener( + 'devicechange', () => this.deviceUpdate()); + } + + /** + * Registers listener to be called when state of available devices + * changes. + */ + addDeviceChangeListener(listener: (devices: DeviceInfo[]) => void): void { + this.listeners.push(listener); + } + + /** + * Handling function for device changing. + */ + async deviceUpdate(): Promise<void> { + const devices = await this.doDeviceInfoUpdate(); + if (devices === null) { + return; + } + this.doDeviceNotify(devices); + } + + /** + * Updates devices information via mojo IPC. + */ + private async doDeviceInfoUpdate(): Promise<DeviceInfo[]|null> { + try { + const devicesInfo = await this.enumerateDevices(); + return await this.queryMojoDevicesInfo(devicesInfo); + } catch (e) { + if (loadTimeData.isVideoCaptureDisallowed()) { + // The failure is expected due to the policy so don't throw any error. + // TODO(b/297317408): Show messages on the UI. + // eslint-disable-next-line no-console + console.log('Failed to load camera since it is blocked by policy'); + } else { + reportError(ErrorType.DEVICE_INFO_UPDATE_FAILURE, ErrorLevel.ERROR, e); + } + } + return null; + } + + /** + * Notifies device changes to listeners and creates a mapping for real and + * virtual device. + */ + private doDeviceNotify(devices: DeviceInfo[]) { + let isDeviceChanged = false; + for (const added of this.getDifference(devices, this.devicesInfo)) { + speak(I18nString.STATUS_MSG_CAMERA_PLUGGED, added.v1Info.label); + isDeviceChanged = true; + } + for (const removed of this.getDifference(devices, this.devicesInfo)) { + speak(I18nString.STATUS_MSG_CAMERA_UNPLUGGED, removed.v1Info.label); + isDeviceChanged = true; + } + if (isDeviceChanged) { + for (const listener of this.listeners) { + listener(devices); + } + } + this.devicesInfo = devices; + } + + /** + * Computes |devices| - |devices2|. + */ + private getDifference(devices: DeviceInfo[], devices2: DeviceInfo[]): + DeviceInfo[] { + const ids = new Set(devices2.map((d) => d.v1Info.deviceId)); + return devices.filter((d) => !ids.has(d.v1Info.deviceId)); + } + + /** + * Enumerates all available devices and gets their MediaDeviceInfo. Retries at + * one-second intervals if devices length is zero. + */ + private async enumerateDevices(): Promise<MediaDeviceInfo[]> { + const deviceType = loadTimeData.getDeviceType(); + const shouldHaveBuiltinCamera = + deviceType === 'chromebook' || deviceType === 'chromebase'; + let attempts = 5; + while (attempts-- > 0) { + const devices = (await navigator.mediaDevices.enumerateDevices()) + .filter((device) => device.kind === 'videoinput'); + if (!shouldHaveBuiltinCamera || devices.length > 0) { + return devices; + } + await sleep(1000); + } + throw new Error('Device list empty.'); + } + + /** + * Queries Camera3DeviceInfo of available devices through private mojo API. + * + * @return Camera3DeviceInfo of available devices. Maybe null on HALv1 + * devices without supporting private mojo api. + * @throws Thrown when camera unplugging happens between enumerating devices + * and querying mojo APIs with current device info results. + */ + private async queryMojoDevicesInfo(devices: MediaDeviceInfo[]): + Promise<DeviceInfo[]|null> { + const isV3Supported = DeviceOperator.isSupported(); + return Promise.all(devices.map( + async (d) => ({ + v1Info: d, + v3Info: isV3Supported ? + (await Camera3DeviceInfo.create(d, this.videoConfigFilter)) : + null, + }))); + } +}
diff --git a/ash/webui/camera_app_ui/resources/js/device/mode/index.ts b/ash/webui/camera_app_ui/resources/js/device/mode/index.ts index 70807b0..6b26b7c 100644 --- a/ash/webui/camera_app_ui/resources/js/device/mode/index.ts +++ b/ash/webui/camera_app_ui/resources/js/device/mode/index.ts
@@ -17,7 +17,6 @@ } from '../../type.js'; import {getFpsRangeFromConstraints} from '../../util.js'; import {StreamConstraints} from '../stream_constraints.js'; -import {StreamManagerChrome} from '../stream_manager_chrome.js'; import { ModeBase, @@ -156,24 +155,6 @@ const deviceId = constraints.deviceId; await deviceOperator.setCaptureIntent( deviceId, CaptureIntent.kVideoRecord); - await deviceOperator.setMultipleStreamsEnabled( - deviceId, - expert.isEnabled( - expert.ExpertOption.ENABLE_MULTISTREAM_RECORDING), - ); - if (expert.isEnabled( - expert.ExpertOption.ENABLE_MULTISTREAM_RECORDING_CHROME)) { - const captureResolution = - assertExists(this.getCaptureParams().captureResolution); - await StreamManagerChrome.getInstance().prepare({ - ...constraints, - video: { - ...constraints.video, - width: captureResolution.width, - height: captureResolution.height, - }, - }); - } if (await deviceOperator.isBlobVideoSnapshotEnabled(deviceId)) { await deviceOperator.setStillCaptureResolution(
diff --git a/ash/webui/camera_app_ui/resources/js/device/mode/video.ts b/ash/webui/camera_app_ui/resources/js/device/mode/video.ts index 3d1832e..1c7bc18b 100644 --- a/ash/webui/camera_app_ui/resources/js/device/mode/video.ts +++ b/ash/webui/camera_app_ui/resources/js/device/mode/video.ts
@@ -4,13 +4,11 @@ import { assert, - assertExists, assertInstanceof, } from '../../assert.js'; import {AsyncJobQueue} from '../../async_job_queue.js'; import * a