Commit e601ec38 authored by Mark Cogan's avatar Mark Cogan Committed by Commit Bot

[iOS] Add accessibility notification of window count changes.

Adds a VoiceOver notification when the number of visible Chrome windows
changes, if multiple windows are possible.

This is complicated by some window manipulations causing transient
changes in the number of foregrounded windows -- for example, replacing
one window with another will briefly have three scenes with Foreground-
Inactive states.

To mitigate this, the app agent delays notifications, and coalesces
multiple notifications in the delay period into a single notification.

(No notification is posted if the window count doesn't change from the
last notification).

Bug: 1137818
Change-Id: I2a308072c92aceb3816397dae6fdf50d61d14398
Skip-Translation-Screenshots-Check: True
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2540670
Commit-Queue: Mark Cogan <marq@chromium.org>
Reviewed-by: default avatarRobbie Gibson <rkgibson@google.com>
Cr-Commit-Position: refs/heads/master@{#828420}
parent 27806a4a
......@@ -209,6 +209,7 @@ source_set("app_internal") {
"//ios/chrome/app/startup:startup_basic",
"//ios/chrome/app/strings",
"//ios/chrome/browser",
"//ios/chrome/browser/accessibility",
"//ios/chrome/browser/bookmarks",
"//ios/chrome/browser/browser_state",
"//ios/chrome/browser/browser_state:browser_state_impl",
......
......@@ -40,6 +40,7 @@
#include "ios/chrome/app/startup/setup_debugging.h"
#import "ios/chrome/app/startup_tasks.h"
#include "ios/chrome/app/tests_hook.h"
#import "ios/chrome/browser/accessibility/window_accessibility_change_notifier_app_agent.h"
#include "ios/chrome/browser/application_context.h"
#include "ios/chrome/browser/browser_state/chrome_browser_state.h"
#include "ios/chrome/browser/browser_state/chrome_browser_state_manager.h"
......@@ -605,6 +606,12 @@ void MainControllerAuthenticationServiceDelegate::ClearBrowsingData(
// Create app state agents.
[appState addAgent:[[ContentSuggestionsSchedulerAppAgent alloc] init]];
[appState addAgent:[[IncognitoUsageAppStateAgent alloc] init]];
// Create the window accessibility agent only when multuple windows are
// possible.
if (IsMultipleScenesSupported()) {
[appState addAgent:[[WindowAccessibityChangeNotifierAppAgent alloc] init]];
}
}
- (id<BrowserInterfaceProvider>)interfaceProvider {
......
......@@ -2702,6 +2702,11 @@ New Search
=1 {{domain} and 1 other}
other {{domain} and {count} others}}
</message>
<message name="IDS_IOS_WINDOW_COUNT_CHANGE" is_accessibility_with_no_ui="true" desc="VoiceOver text spoken when the count of visible windows changes -- note: this text is only spoken, never displayed on screen">
{count, plural,
=1 {Now showing 1 Chrome window}
other {Now showing {count} Chrome windows}}
</message>
</messages>
</release>
</grit>
# 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.
source_set("accessibility") {
sources = [
"window_accessibility_change_notifier_app_agent.h",
"window_accessibility_change_notifier_app_agent.mm",
]
deps = [
"//base",
"//base:i18n",
"//ios/chrome/app/application_delegate:app_state_header",
"//ios/chrome/app/strings:ios_strings_grit",
"//ios/chrome/browser/ui/main:scene_state_header",
"//ui/base",
]
configs += [ "//build/config/compiler:enable_arc" ]
}
// 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_ACCESSIBILITY_WINDOW_ACCESSIBILITY_CHANGE_NOTIFIER_APP_AGENT_H_
#define IOS_CHROME_BROWSER_ACCESSIBILITY_WINDOW_ACCESSIBILITY_CHANGE_NOTIFIER_APP_AGENT_H_
#import "ios/chrome/app/application_delegate/app_state_agent.h"
// An app agent that montitors the number of visible (that is: active) scenes
// and provides accessibility notifications when this count changes.
@interface WindowAccessibityChangeNotifierAppAgent : NSObject <AppStateAgent>
@end
#endif // IOS_CHROME_BROWSER_ACCESSIBILITY_WINDOW_ACCESSIBILITY_CHANGE_NOTIFIER_APP_AGENT_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/accessibility/window_accessibility_change_notifier_app_agent.h"
#import "base/check.h"
#import "base/i18n/message_formatter.h"
#import "base/logging.h"
#import "base/strings/sys_string_conversions.h"
#import "ios/chrome/app/application_delegate/app_state.h"
#import "ios/chrome/browser/ui/main/scene_state.h"
#import "ios/chrome/grit/ios_strings.h"
#import "ui/base/l10n/l10n_util.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
namespace {
// Delay between events and notification.
const NSTimeInterval kWindowNotifcationDelay = 0.5; // seconds
}
@interface WindowAccessibityChangeNotifierAppAgent () <AppStateObserver,
SceneStateObserver>
// Observed app state.
@property(nonatomic, weak) AppState* appState;
@property(nonatomic, assign) NSUInteger visibleWindowCount;
// If an update is pending, |lastUpdateTime| is the last time that an event
// occurred that might cause the window count to change. If no update is pending
// |lastUpdateTime| is nil.
@property(nonatomic) NSDate* lastUpdateTime;
@end
@implementation WindowAccessibityChangeNotifierAppAgent
#pragma mark - AppStateAgent
- (void)setAppState:(AppState*)appState {
// This should only be called once!
DCHECK(!_appState);
_appState = appState;
[appState addObserver:self];
[self updateWindowCount];
}
#pragma mark - AppStateObserver
- (void)appState:(AppState*)appState sceneConnected:(SceneState*)sceneState {
[sceneState addObserver:self];
}
#pragma mark - SceneStateObserver
// Changes in the activation level of scene states will indicate that the count
// of visible windows has changed. Some actions (such as opening a third window
// when two are already open) can cause a scene's activation level to change
// at a time when other scenes' activation levels have not yet updated, which
// would cause notifications of incorrect window counts. To handle this type
// of change, this class instead posts notifications with a delay. If another
// notification is requested before a queued one executes, the new request is
// skipped.
- (void)sceneState:(SceneState*)sceneState
transitionedToActivationLevel:(SceneActivationLevel)level {
if (self.lastUpdateTime == nil) {
[self scheduleWindowCountWithDelay:kWindowNotifcationDelay];
}
self.lastUpdateTime = [NSDate date];
}
#pragma mark - private
- (void)scheduleWindowCountWithDelay:(NSTimeInterval)delay {
// Weakify, since the window count can change in shutdown, so there are
// likely to be pending notifications that would otherwise keep this object
// alive.
__weak WindowAccessibityChangeNotifierAppAgent* weakSelf = self;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW,
static_cast<int64_t>(delay * NSEC_PER_SEC)),
dispatch_get_main_queue(), ^{
[weakSelf notifyWindowCount];
});
}
// Performs the notification, if enough time has passed since the last update.
// If the last update was more recent than the notification delay, then the
// notification is re-posted to happen after the delay has elapsed.
- (void)notifyWindowCount {
NSDate* now = [NSDate date];
NSTimeInterval delta = [now timeIntervalSinceDate:self.lastUpdateTime];
if (delta < kWindowNotifcationDelay) {
// Repost with a delay sufficient to be |kWindowNotifcationDelay| after
// the last update time.
NSTimeInterval newDelta = kWindowNotifcationDelay - delta;
[self scheduleWindowCountWithDelay:newDelta];
return;
}
self.lastUpdateTime = nil;
NSUInteger previousWindowCount = self.visibleWindowCount;
[self updateWindowCount];
// Only notify the user if (a) the window count has changed, and (b) it's
// non-zero. A zero window count would occur, for example, when the user
// enters the system app switcher. Other accessibility systems will notify
// them of that change; it isn't necessary to tell them that no Chrome windows
// are showing.
if (previousWindowCount != self.visibleWindowCount &&
self.visibleWindowCount > 0) {
base::string16 pattern =
l10n_util::GetStringUTF16(IDS_IOS_WINDOW_COUNT_CHANGE);
int numberOfWindows = static_cast<int>(self.visibleWindowCount);
base::string16 formattedMessage =
base::i18n::MessageFormatter::FormatWithNamedArgs(pattern, "count",
numberOfWindows);
NSString* windowCountNotification =
base::SysUTF16ToNSString(formattedMessage);
UIAccessibilityPostNotification(UIAccessibilityAnnouncementNotification,
windowCountNotification);
}
}
// Update |self.viisbleWindowCount| with the total number of foregrounded
// connected scenes.
- (void)updateWindowCount {
NSUInteger windowCount = 0;
for (SceneState* scene in [self.appState connectedScenes]) {
if (scene.activationLevel >= SceneActivationLevelForegroundInactive)
windowCount++;
}
self.visibleWindowCount = windowCount;
}
@end
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