Commit baa04150 authored by Kurt Horimoto's avatar Kurt Horimoto Committed by Commit Bot

[iOS] Disable voice search keyboard accessory view for VoiceOver

crrev.com/c/2005903 was an incomplete solution because the util
functions generating the keyboard accessory views was only called once
per OmniboxCoordinator's lifetime.  This CL updates the coordinator
to observe the voice search availability and reconfigure the keyboard
accessory views when availability changes.

Bug: 998524
Change-Id: Ica88b4690fac0ada1be6cc77bb6ca911f5a245e6
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2075045Reviewed-by: default avatarGauthier Ambard <gambard@chromium.org>
Commit-Queue: Kurt Horimoto <kkhorimoto@chromium.org>
Auto-Submit: Kurt Horimoto <kkhorimoto@chromium.org>
Cr-Commit-Position: refs/heads/master@{#746855}
parent 9b5f0283
...@@ -17,6 +17,10 @@ source_set("keyboard_assist") { ...@@ -17,6 +17,10 @@ source_set("keyboard_assist") {
"toolbar_keyboard_accessory_view.mm", "toolbar_keyboard_accessory_view.mm",
"toolbar_ui_bar_button_item.h", "toolbar_ui_bar_button_item.h",
"toolbar_ui_bar_button_item.mm", "toolbar_ui_bar_button_item.mm",
"voice_search_keyboard_accessory_button.h",
"voice_search_keyboard_accessory_button.mm",
"voice_search_keyboard_bar_button_item.h",
"voice_search_keyboard_bar_button_item.mm",
] ]
deps = [ deps = [
"resources:keyboard_accessory_qr_scanner", "resources:keyboard_accessory_qr_scanner",
...@@ -42,3 +46,20 @@ source_set("keyboard_assist") { ...@@ -42,3 +46,20 @@ source_set("keyboard_assist") {
[ "//ios/chrome/browser/ui/omnibox:omnibox_internal" ] [ "//ios/chrome/browser/ui/omnibox:omnibox_internal" ]
libs = [ "UIKit.framework" ] libs = [ "UIKit.framework" ]
} }
source_set("unit_tests") {
testonly = true
sources = [
"voice_search_keyboard_accessory_button_unittest.mm",
"voice_search_keyboard_bar_button_item_unittest.mm",
]
configs += [ "//build/config/compiler:enable_arc" ]
deps = [
":keyboard_assist",
"//ios/chrome/browser/voice",
"//ios/chrome/browser/voice:test_support",
"//testing/gtest",
]
}
...@@ -6,7 +6,9 @@ ...@@ -6,7 +6,9 @@
#include "base/logging.h" #include "base/logging.h"
#import "ios/chrome/browser/ui/toolbar/keyboard_assist/toolbar_assistive_keyboard_delegate.h" #import "ios/chrome/browser/ui/toolbar/keyboard_assist/toolbar_assistive_keyboard_delegate.h"
#import "ios/chrome/browser/ui/toolbar/keyboard_assist/voice_search_keyboard_accessory_button.h"
#import "ios/chrome/browser/ui/util/uikit_ui_util.h" #import "ios/chrome/browser/ui/util/uikit_ui_util.h"
#import "ios/chrome/browser/voice/voice_search_availability.h"
#include "ios/chrome/grit/ios_strings.h" #include "ios/chrome/grit/ios_strings.h"
#include "ios/public/provider/chrome/browser/chrome_browser_provider.h" #include "ios/public/provider/chrome/browser/chrome_browser_provider.h"
#include "ui/base/l10n/l10n_util.h" #include "ui/base/l10n/l10n_util.h"
...@@ -21,12 +23,11 @@ NSString* const kVoiceSearchInputAccessoryViewID = ...@@ -21,12 +23,11 @@ NSString* const kVoiceSearchInputAccessoryViewID =
namespace { namespace {
UIButton* ButtonWithIcon(NSString* iconName) { void SetUpButtonWithIcon(UIButton* button, NSString* iconName) {
const CGFloat kButtonShadowOpacity = 0.35; const CGFloat kButtonShadowOpacity = 0.35;
const CGFloat kButtonShadowRadius = 1.0; const CGFloat kButtonShadowRadius = 1.0;
const CGFloat kButtonShadowVerticalOffset = 1.0; const CGFloat kButtonShadowVerticalOffset = 1.0;
UIButton* button = [UIButton buttonWithType:UIButtonTypeCustom];
[button setTranslatesAutoresizingMaskIntoConstraints:NO]; [button setTranslatesAutoresizingMaskIntoConstraints:NO];
UIImage* icon = [UIImage imageNamed:iconName]; UIImage* icon = [UIImage imageNamed:iconName];
[button setImage:icon forState:UIControlStateNormal]; [button setImage:icon forState:UIControlStateNormal];
...@@ -34,15 +35,18 @@ UIButton* ButtonWithIcon(NSString* iconName) { ...@@ -34,15 +35,18 @@ UIButton* ButtonWithIcon(NSString* iconName) {
button.layer.shadowOffset = CGSizeMake(0, kButtonShadowVerticalOffset); button.layer.shadowOffset = CGSizeMake(0, kButtonShadowVerticalOffset);
button.layer.shadowOpacity = kButtonShadowOpacity; button.layer.shadowOpacity = kButtonShadowOpacity;
button.layer.shadowRadius = kButtonShadowRadius; button.layer.shadowRadius = kButtonShadowRadius;
return button;
} }
} // namespace } // namespace
NSArray<UIButton*>* ToolbarAssistiveKeyboardLeadingButtons( NSArray<UIButton*>* ToolbarAssistiveKeyboardLeadingButtons(
id<ToolbarAssistiveKeyboardDelegate> delegate) { id<ToolbarAssistiveKeyboardDelegate> delegate) {
UIButton* voiceSearchButton = NSMutableArray<UIButton*>* buttons = [NSMutableArray<UIButton*> array];
ButtonWithIcon(@"keyboard_accessory_voice_search");
UIButton* voiceSearchButton = [[VoiceSearchKeyboardAccessoryButton alloc]
initWithVoiceSearchAvailability:std::make_unique<
VoiceSearchAvailability>()];
SetUpButtonWithIcon(voiceSearchButton, @"keyboard_accessory_voice_search");
NSString* accessibilityLabel = NSString* accessibilityLabel =
l10n_util::GetNSString(IDS_IOS_KEYBOARD_ACCESSORY_VIEW_VOICE_SEARCH); l10n_util::GetNSString(IDS_IOS_KEYBOARD_ACCESSORY_VIEW_VOICE_SEARCH);
voiceSearchButton.accessibilityLabel = accessibilityLabel; voiceSearchButton.accessibilityLabel = accessibilityLabel;
...@@ -51,15 +55,17 @@ NSArray<UIButton*>* ToolbarAssistiveKeyboardLeadingButtons( ...@@ -51,15 +55,17 @@ NSArray<UIButton*>* ToolbarAssistiveKeyboardLeadingButtons(
addTarget:delegate addTarget:delegate
action:@selector(keyboardAccessoryVoiceSearchTouchUpInside:) action:@selector(keyboardAccessoryVoiceSearchTouchUpInside:)
forControlEvents:UIControlEventTouchUpInside]; forControlEvents:UIControlEventTouchUpInside];
[buttons addObject:voiceSearchButton];
UIButton* cameraButton = ButtonWithIcon(@"keyboard_accessory_qr_scanner"); UIButton* cameraButton = [UIButton buttonWithType:UIButtonTypeCustom];
SetUpButtonWithIcon(cameraButton, @"keyboard_accessory_qr_scanner");
[cameraButton addTarget:delegate [cameraButton addTarget:delegate
action:@selector(keyboardAccessoryCameraSearchTouchUp) action:@selector(keyboardAccessoryCameraSearchTouchUp)
forControlEvents:UIControlEventTouchUpInside]; forControlEvents:UIControlEventTouchUpInside];
SetA11yLabelAndUiAutomationName( SetA11yLabelAndUiAutomationName(
cameraButton, IDS_IOS_KEYBOARD_ACCESSORY_VIEW_QR_CODE_SEARCH, cameraButton, IDS_IOS_KEYBOARD_ACCESSORY_VIEW_QR_CODE_SEARCH,
@"QR code Search"); @"QR code Search");
[buttons addObject:cameraButton];
NSArray<UIButton*>* buttons = @[ voiceSearchButton, cameraButton ];
return buttons; return buttons;
} }
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
#import "ios/chrome/browser/ui/toolbar/keyboard_assist/toolbar_assistive_keyboard_views.h" #import "ios/chrome/browser/ui/toolbar/keyboard_assist/toolbar_assistive_keyboard_views.h"
#import "ios/chrome/browser/ui/toolbar/keyboard_assist/toolbar_assistive_keyboard_views_utils.h" #import "ios/chrome/browser/ui/toolbar/keyboard_assist/toolbar_assistive_keyboard_views_utils.h"
#import "ios/chrome/browser/ui/toolbar/keyboard_assist/toolbar_ui_bar_button_item.h" #import "ios/chrome/browser/ui/toolbar/keyboard_assist/toolbar_ui_bar_button_item.h"
#import "ios/chrome/browser/ui/toolbar/keyboard_assist/voice_search_keyboard_bar_button_item.h"
#import "ios/chrome/browser/ui/util/uikit_ui_util.h" #import "ios/chrome/browser/ui/util/uikit_ui_util.h"
#import "ios/chrome/browser/voice/voice_search_availability.h" #import "ios/chrome/browser/voice/voice_search_availability.h"
#include "ios/chrome/grit/ios_strings.h" #include "ios/chrome/grit/ios_strings.h"
...@@ -18,25 +19,27 @@ ...@@ -18,25 +19,27 @@
#error "This file requires ARC support." #error "This file requires ARC support."
#endif #endif
#pragma mark - Util Functions
NSArray<UIBarButtonItemGroup*>* ToolbarAssistiveKeyboardLeadingBarButtonGroups( NSArray<UIBarButtonItemGroup*>* ToolbarAssistiveKeyboardLeadingBarButtonGroups(
id<ToolbarAssistiveKeyboardDelegate> delegate) { id<ToolbarAssistiveKeyboardDelegate> delegate) {
NSMutableArray<UIBarButtonItem*>* items = [NSMutableArray array]; NSMutableArray<UIBarButtonItem*>* items = [NSMutableArray array];
VoiceSearchAvailability voice_search_availability;
if (voice_search_availability.IsVoiceSearchAvailable()) { UIImage* voiceSearchIcon =
UIImage* voiceSearchIcon = [[UIImage imageNamed:@"keyboard_accessory_voice_search"]
[[UIImage imageNamed:@"keyboard_accessory_voice_search"] imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];
imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal]; UIBarButtonItem* voiceSearchItem = [[VoiceSearchKeyboardBarButtonItem alloc]
UIBarButtonItem* voiceSearchItem = [[UIBarButtonItem alloc] initWithImage:voiceSearchIcon
initWithImage:voiceSearchIcon style:UIBarButtonItemStylePlain
style:UIBarButtonItemStylePlain target:delegate
target:delegate action:@selector
action:@selector(keyboardAccessoryVoiceSearchTouchUpInside:)]; (keyboardAccessoryVoiceSearchTouchUpInside:)
NSString* accessibilityLabel = voiceSearchAvailability:std::make_unique<VoiceSearchAvailability>()];
l10n_util::GetNSString(IDS_IOS_KEYBOARD_ACCESSORY_VIEW_VOICE_SEARCH); NSString* accessibilityLabel =
voiceSearchItem.accessibilityLabel = accessibilityLabel; l10n_util::GetNSString(IDS_IOS_KEYBOARD_ACCESSORY_VIEW_VOICE_SEARCH);
voiceSearchItem.accessibilityIdentifier = kVoiceSearchInputAccessoryViewID; voiceSearchItem.accessibilityLabel = accessibilityLabel;
[items addObject:voiceSearchItem]; voiceSearchItem.accessibilityIdentifier = kVoiceSearchInputAccessoryViewID;
} [items addObject:voiceSearchItem];
UIImage* cameraIcon = [[UIImage imageNamed:@"keyboard_accessory_qr_scanner"] UIImage* cameraIcon = [[UIImage imageNamed:@"keyboard_accessory_qr_scanner"]
imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal]; imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];
...@@ -50,9 +53,10 @@ NSArray<UIBarButtonItemGroup*>* ToolbarAssistiveKeyboardLeadingBarButtonGroups( ...@@ -50,9 +53,10 @@ NSArray<UIBarButtonItemGroup*>* ToolbarAssistiveKeyboardLeadingBarButtonGroups(
@"QR code Search"); @"QR code Search");
[items addObject:cameraItem]; [items addObject:cameraItem];
UIBarButtonItemGroup* group = UIBarButtonItemGroup* group = [[UIBarButtonItemGroup alloc]
[[UIBarButtonItemGroup alloc] initWithBarButtonItems:items initWithBarButtonItems:@[ voiceSearchItem, cameraItem ]
representativeItem:nil]; representativeItem:nil];
return @[ group ]; return @[ group ];
} }
......
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef IOS_CHROME_BROWSER_UI_TOOLBAR_KEYBOARD_ASSIST_VOICE_SEARCH_KEYBOARD_ACCESSORY_BUTTON_H_
#define IOS_CHROME_BROWSER_UI_TOOLBAR_KEYBOARD_ASSIST_VOICE_SEARCH_KEYBOARD_ACCESSORY_BUTTON_H_
#import <UIKit/UIKit.h>
#include <memory>
class VoiceSearchAvailability;
// A custom button that disables itself when voice search becomes unavailable.
@interface VoiceSearchKeyboardAccessoryButton : UIButton
- (instancetype)initWithVoiceSearchAvailability:
(std::unique_ptr<VoiceSearchAvailability>)availability
NS_DESIGNATED_INITIALIZER;
- (instancetype)initWithFrame:(CGRect)frame NS_UNAVAILABLE;
- (instancetype)initWithCoder:(NSCoder*)coder NS_UNAVAILABLE;
@end
#endif // IOS_CHROME_BROWSER_UI_TOOLBAR_KEYBOARD_ASSIST_VOICE_SEARCH_KEYBOARD_ACCESSORY_BUTTON_H_
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "ios/chrome/browser/ui/toolbar/keyboard_assist/voice_search_keyboard_accessory_button.h"
#include "base/logging.h"
#import "ios/chrome/browser/voice/voice_search_availability.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
@interface VoiceSearchKeyboardAccessoryButton () <
VoiceSearchAvailabilityObserver> {
std::unique_ptr<VoiceSearchAvailability> _availability;
}
@end
@implementation VoiceSearchKeyboardAccessoryButton
- (instancetype)initWithVoiceSearchAvailability:
(std::unique_ptr<VoiceSearchAvailability>)availability {
if (self = [super initWithFrame:CGRectZero]) {
_availability = std::move(availability);
DCHECK(_availability);
_availability->AddObserver(self);
}
return self;
}
- (void)dealloc {
_availability->RemoveObserver(self);
}
#pragma mark - UIView
- (void)willMoveToSuperview:(UIView*)newSuperview {
[self updateEnabledState];
}
#pragma mark - VoiceSearchAvailabilityObserver
- (void)voiceSearchAvailability:(VoiceSearchAvailability*)availability
updatedAvailability:(BOOL)available {
[self updateEnabledState];
}
#pragma mark - Private
// Updates the button's enabled state according to its voice search
// availability.
- (void)updateEnabledState {
self.enabled = _availability->IsVoiceSearchAvailable();
}
@end
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "ios/chrome/browser/ui/toolbar/keyboard_assist/voice_search_keyboard_accessory_button.h"
#import "ios/chrome/browser/voice/fake_voice_search_availability.h"
#include "testing/platform_test.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
// Test fixture for VoiceSearchKeyboardAccessoryButton.
class VoiceSearchKeyboardAccessoryButtonTest : public PlatformTest {
public:
VoiceSearchKeyboardAccessoryButtonTest()
: superview_([[UIView alloc] initWithFrame:CGRectZero]) {
std::unique_ptr<FakeVoiceSearchAvailability> availability =
std::make_unique<FakeVoiceSearchAvailability>();
availability_ = availability.get();
availability_->SetVoiceOverEnabled(false);
availability_->SetVoiceProviderEnabled(true);
button_ = [[VoiceSearchKeyboardAccessoryButton alloc]
initWithVoiceSearchAvailability:std::move(availability)];
[superview_ addSubview:button_];
}
protected:
FakeVoiceSearchAvailability* availability_ = nullptr;
UIView* superview_ = nil;
VoiceSearchKeyboardAccessoryButton* button_ = nil;
};
// Tests that the button is disabled when VoiceOver is enabled.
TEST_F(VoiceSearchKeyboardAccessoryButtonTest, DisableForVoiceOver) {
ASSERT_TRUE(button_.enabled);
availability_->SetVoiceOverEnabled(true);
EXPECT_FALSE(button_.enabled);
availability_->SetVoiceOverEnabled(false);
EXPECT_TRUE(button_.enabled);
}
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef IOS_CHROME_BROWSER_UI_TOOLBAR_KEYBOARD_ASSIST_VOICE_SEARCH_KEYBOARD_BAR_BUTTON_ITEM_H_
#define IOS_CHROME_BROWSER_UI_TOOLBAR_KEYBOARD_ASSIST_VOICE_SEARCH_KEYBOARD_BAR_BUTTON_ITEM_H_
#import <UIKit/UIKit.h>
#include <memory>
class VoiceSearchAvailability;
// A custom bar button item that disables itself when voice search is
// unavailable.
@interface VoiceSearchKeyboardBarButtonItem : UIBarButtonItem
// Initializer for an item that disables itself when |availability| returns
// false for IsVoiceSearchAvailable().
- (instancetype)initWithImage:(UIImage*)image
style:(UIBarButtonItemStyle)style
target:(id)target
action:(SEL)action
voiceSearchAvailability:
(std::unique_ptr<VoiceSearchAvailability>)availability
NS_DESIGNATED_INITIALIZER;
- (instancetype)init NS_UNAVAILABLE;
- (instancetype)initWithCoder:(NSCoder*)coder NS_UNAVAILABLE;
@end
#endif // IOS_CHROME_BROWSER_UI_TOOLBAR_KEYBOARD_ASSIST_VOICE_SEARCH_KEYBOARD_BAR_BUTTON_ITEM_H_
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "ios/chrome/browser/ui/toolbar/keyboard_assist/voice_search_keyboard_bar_button_item.h"
#import "ios/chrome/browser/voice/voice_search_availability.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
@interface VoiceSearchKeyboardBarButtonItem () <
VoiceSearchAvailabilityObserver> {
std::unique_ptr<VoiceSearchAvailability> _availability;
}
@end
@implementation VoiceSearchKeyboardBarButtonItem
- (instancetype)initWithImage:(UIImage*)image
style:(UIBarButtonItemStyle)style
target:(id)target
action:(SEL)action
voiceSearchAvailability:
(std::unique_ptr<VoiceSearchAvailability>)availability {
if (self = [super init]) {
self.image = image;
self.style = style;
self.target = target;
self.action = action;
_availability = std::move(availability);
_availability->AddObserver(self);
[self updateEnabledState];
}
return self;
}
- (void)dealloc {
_availability->RemoveObserver(self);
}
#pragma mark - VoiceSearchAvailabilityObserver
- (void)voiceSearchAvailability:(VoiceSearchAvailability*)availability
updatedAvailability:(BOOL)available {
[self updateEnabledState];
}
#pragma mark - Private
// Updates the item's enabled state according to its voice search availability.
- (void)updateEnabledState {
self.enabled = _availability->IsVoiceSearchAvailable();
}
@end
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "ios/chrome/browser/ui/toolbar/keyboard_assist/voice_search_keyboard_bar_button_item.h"
#import "ios/chrome/browser/voice/fake_voice_search_availability.h"
#include "testing/platform_test.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
// Test fixture for VoiceSearchKeyboardBarButtonItem.
class VoiceSearchKeyboardBarButtonItemTest : public PlatformTest {
public:
VoiceSearchKeyboardBarButtonItemTest() {
std::unique_ptr<FakeVoiceSearchAvailability> availability =
std::make_unique<FakeVoiceSearchAvailability>();
availability_ = availability.get();
availability_->SetVoiceOverEnabled(false);
availability_->SetVoiceProviderEnabled(true);
item_ = [[VoiceSearchKeyboardBarButtonItem alloc]
initWithImage:nil
style:UIBarButtonItemStylePlain
target:nil
action:nil
voiceSearchAvailability:std::move(availability)];
}
protected:
FakeVoiceSearchAvailability* availability_ = nullptr;
VoiceSearchKeyboardBarButtonItem* item_ = nil;
};
// Tests that the item is disabled when VoiceOver is enabled.
TEST_F(VoiceSearchKeyboardBarButtonItemTest, DisableForVoiceOver) {
ASSERT_TRUE(item_.enabled);
availability_->SetVoiceOverEnabled(true);
EXPECT_FALSE(item_.enabled);
availability_->SetVoiceOverEnabled(false);
EXPECT_TRUE(item_.enabled);
}
...@@ -295,6 +295,7 @@ test("ios_chrome_unittests") { ...@@ -295,6 +295,7 @@ test("ios_chrome_unittests") {
"//ios/chrome/browser/ui/tabs:unit_tests", "//ios/chrome/browser/ui/tabs:unit_tests",
"//ios/chrome/browser/ui/toolbar:unit_tests", "//ios/chrome/browser/ui/toolbar:unit_tests",
"//ios/chrome/browser/ui/toolbar/fullscreen:unit_tests", "//ios/chrome/browser/ui/toolbar/fullscreen:unit_tests",
"//ios/chrome/browser/ui/toolbar/keyboard_assist:unit_tests",
"//ios/chrome/browser/ui/toolbar_container:unit_tests", "//ios/chrome/browser/ui/toolbar_container:unit_tests",
"//ios/chrome/browser/ui/translate:unit_tests", "//ios/chrome/browser/ui/translate:unit_tests",
"//ios/chrome/browser/ui/util:unit_tests", "//ios/chrome/browser/ui/util:unit_tests",
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment