1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #ifndef ASH_ACCELERATORS_ACCELERATOR_CONTROLLER_IMPL_H_
6 #define ASH_ACCELERATORS_ACCELERATOR_CONTROLLER_IMPL_H_
7 
8 #include <stddef.h>
9 
10 #include <map>
11 #include <memory>
12 #include <set>
13 #include <vector>
14 
15 #include "ash/accelerators/accelerator_confirmation_dialog.h"
16 #include "ash/accelerators/accelerator_table.h"
17 #include "ash/accelerators/exit_warning_handler.h"
18 #include "ash/ash_export.h"
19 #include "ash/public/cpp/accelerators.h"
20 #include "base/compiler_specific.h"
21 #include "base/gtest_prod_util.h"
22 #include "base/macros.h"
23 #include "ui/base/accelerators/accelerator.h"
24 #include "ui/base/accelerators/accelerator_history.h"
25 
26 namespace ui {
27 class AcceleratorManager;
28 }
29 
30 namespace ash {
31 
32 struct AcceleratorData;
33 class ExitWarningHandler;
34 
35 // See TabletModeVolumeAdjustType at tools/metrics/histograms/enums.xml.
36 enum class TabletModeVolumeAdjustType {
37   kAccidentalAdjustWithSwapEnabled = 0,
38   kNormalAdjustWithSwapEnabled = 1,
39   kAccidentalAdjustWithSwapDisabled = 2,
40   kNormalAdjustWithSwapDisabled = 3,
41   kMaxValue = kNormalAdjustWithSwapDisabled,
42 };
43 
44 // These values are persisted to logs. Entries should not be renumbered and
45 // numeric values should never be reused.
46 // Captures usage of Alt+[ and Alt+].
47 enum class WindowSnapAcceleratorAction {
48   kCycleLeftSnapInClamshellNoOverview = 0,
49   kCycleLeftSnapInClamshellOverview = 1,
50   kCycleLeftSnapInTablet = 2,
51   kCycleRightSnapInClamshellNoOverview = 3,
52   kCycleRightSnapInClamshellOverview = 4,
53   kCycleRightSnapInTablet = 5,
54   kMaxValue = kCycleRightSnapInTablet,
55 };
56 
57 // Histogram for volume adjustment in tablet mode.
58 ASH_EXPORT extern const char kTabletCountOfVolumeAdjustType[];
59 
60 // Identifiers for toggling accelerator notifications.
61 ASH_EXPORT extern const char kHighContrastToggleAccelNotificationId[];
62 ASH_EXPORT extern const char kDockedMagnifierToggleAccelNotificationId[];
63 ASH_EXPORT extern const char kFullscreenMagnifierToggleAccelNotificationId[];
64 
65 // UMA accessibility histogram names.
66 ASH_EXPORT extern const char kAccessibilityHighContrastShortcut[];
67 ASH_EXPORT extern const char kAccessibilitySpokenFeedbackShortcut[];
68 ASH_EXPORT extern const char kAccessibilityScreenMagnifierShortcut[];
69 ASH_EXPORT extern const char kAccessibilityDockedMagnifierShortcut[];
70 
71 // Name of histogram corresponding to |WindowSnapAcceleratorAction|.
72 ASH_EXPORT extern const char kAccelWindowSnap[];
73 
74 // AcceleratorControllerImpl provides functions for registering or unregistering
75 // global keyboard accelerators, which are handled earlier than any windows. It
76 // also implements several handlers as an accelerator target.
77 class ASH_EXPORT AcceleratorControllerImpl : public ui::AcceleratorTarget,
78                                              public AcceleratorController {
79  public:
80   // Some Chrome OS devices have volume up and volume down buttons on their
81   // side. We want the button that's closer to the top/right to increase the
82   // volume and the button that's closer to the bottom/left to decrease the
83   // volume, so we use the buttons' location and the device orientation to
84   // determine whether the buttons should be swapped.
85   struct SideVolumeButtonLocation {
86     // The button can be at the side of the keyboard or the display. Then value
87     // of the region could be kVolumeButtonRegionKeyboard or
88     // kVolumeButtonRegionScreen.
89     std::string region;
90     // Side info of region. The value could be kVolumeButtonSideLeft,
91     // kVolumeButtonSideRight, kVolumeButtonSideTop or kVolumeButtonSideBottom.
92     std::string side;
93   };
94 
95   // TestApi is used for tests to get internal implementation details.
96   class TestApi {
97    public:
98     explicit TestApi(AcceleratorControllerImpl* controller);
99     ~TestApi() = default;
100 
101     // If |controller_->tablet_mode_volume_adjust_timer_| is running, stops it,
102     // runs its task, and returns true. Otherwise returns false.
103     bool TriggerTabletModeVolumeAdjustTimer() WARN_UNUSED_RESULT;
104 
105     // Registers the specified accelerators.
106     void RegisterAccelerators(const AcceleratorData accelerators[],
107                               size_t accelerators_length);
108 
109     // Returns the corresponding accelerator data if |action| maps to a
110     // deprecated accelerator, otherwise return nullptr.
111     const DeprecatedAcceleratorData* GetDeprecatedAcceleratorData(
112         AcceleratorAction action);
113 
114     // Accessor to accelerator confirmation dialog.
115     AcceleratorConfirmationDialog* GetConfirmationDialog();
116 
117     AcceleratorControllerImpl::SideVolumeButtonLocation
side_volume_button_location()118     side_volume_button_location() {
119       return controller_->side_volume_button_location_;
120     }
121     void SetSideVolumeButtonFilePath(base::FilePath path);
122     void SetSideVolumeButtonLocation(const std::string& region,
123                                      const std::string& side);
124 
125    private:
126     AcceleratorControllerImpl* controller_;  // Not owned.
127 
128     DISALLOW_COPY_AND_ASSIGN(TestApi);
129   };
130 
131   // Fields of the side volume button location info.
132   static constexpr const char* kVolumeButtonRegion = "region";
133   static constexpr const char* kVolumeButtonSide = "side";
134 
135   // Values of kVolumeButtonRegion.
136   static constexpr const char* kVolumeButtonRegionKeyboard = "keyboard";
137   static constexpr const char* kVolumeButtonRegionScreen = "screen";
138   // Values of kVolumeButtonSide.
139   static constexpr const char* kVolumeButtonSideLeft = "left";
140   static constexpr const char* kVolumeButtonSideRight = "right";
141   static constexpr const char* kVolumeButtonSideTop = "top";
142   static constexpr const char* kVolumeButtonSideBottom = "bottom";
143 
144   AcceleratorControllerImpl();
145   ~AcceleratorControllerImpl() override;
146 
147   // A list of possible ways in which an accelerator should be restricted before
148   // processing. Any target registered with this controller should respect
149   // restrictions by calling |GetCurrentAcceleratorRestriction| during
150   // processing.
151   enum AcceleratorProcessingRestriction {
152     // Process the accelerator normally.
153     RESTRICTION_NONE,
154 
155     // Don't process the accelerator.
156     RESTRICTION_PREVENT_PROCESSING,
157 
158     // Don't process the accelerator and prevent propagation to other targets.
159     RESTRICTION_PREVENT_PROCESSING_AND_PROPAGATION
160   };
161 
162   // Registers global keyboard accelerators for the specified target. If
163   // multiple targets are registered for any given accelerator, a target
164   // registered later has higher priority.
165   void Register(const std::vector<ui::Accelerator>& accelerators,
166                 ui::AcceleratorTarget* target);
167 
168   // Unregisters the specified keyboard accelerator for the specified target.
169   void Unregister(const ui::Accelerator& accelerator,
170                   ui::AcceleratorTarget* target);
171 
172   // Unregisters all keyboard accelerators for the specified target.
173   void UnregisterAll(ui::AcceleratorTarget* target);
174 
175   // Returns true if there is an action for |accelerator| and it is enabled.
176   bool IsActionForAcceleratorEnabled(const ui::Accelerator& accelerator) const;
177 
178   // AcceleratorControllerImpl:
179   bool Process(const ui::Accelerator& accelerator) override;
180   bool IsDeprecated(const ui::Accelerator& accelerator) const override;
181   bool PerformActionIfEnabled(AcceleratorAction action,
182                               const ui::Accelerator& accelerator) override;
183   bool OnMenuAccelerator(const ui::Accelerator& accelerator) override;
184   bool IsRegistered(const ui::Accelerator& accelerator) const override;
185   ui::AcceleratorHistory* GetAcceleratorHistory() override;
186 
187   // Returns true if the |accelerator| is preferred. A preferred accelerator
188   // is handled before being passed to an window/web contents, unless
189   // the window is in fullscreen state.
190   bool IsPreferred(const ui::Accelerator& accelerator) const;
191 
192   // Returns true if the |accelerator| is reserved. A reserved accelerator
193   // is always handled and will never be passed to an window/web contents.
194   bool IsReserved(const ui::Accelerator& accelerator) const;
195 
196   // Returns the restriction for the current context.
197   AcceleratorProcessingRestriction GetCurrentAcceleratorRestriction();
198 
199   // Provides access to the ExitWarningHandler for testing.
GetExitWarningHandlerForTest()200   ExitWarningHandler* GetExitWarningHandlerForTest() {
201     return &exit_warning_handler_;
202   }
203 
accelerator_history()204   ui::AcceleratorHistory* accelerator_history() {
205     return accelerator_history_.get();
206   }
207 
208   // Overridden from ui::AcceleratorTarget:
209   bool AcceleratorPressed(const ui::Accelerator& accelerator) override;
210   bool CanHandleAccelerators() const override;
211 
212   // A confirmation dialog will be shown the first time an accessibility feature
213   // is enabled using the specified accelerator key sequence. Only one dialog
214   // will be shown at a time, and will not be shown again if the user has
215   // selected "accept" on a given dialog. The dialog was added to ensure that
216   // users would be aware of the shortcut they have just enabled, and to prevent
217   // users from accidentally triggering the feature. The dialog is currently
218   // shown when enabling the following features: high contrast, full screen
219   // magnifier, docked magnifier and screen rotation. The shown dialog is stored
220   // as a weak pointer in the variable |confirmation_dialog_| below.
221   void MaybeShowConfirmationDialog(int window_title_text_id,
222                                    int dialog_text_id,
223                                    base::OnceClosure on_accept_callback,
224                                    base::OnceClosure on_cancel_callback);
225 
226   // Read the side volume button location info from local file under
227   // kSideVolumeButtonLocationFilePath, parse and write it into
228   // |side_volume_button_location_|.
229   void ParseSideVolumeButtonLocationInfo();
230 
231  private:
232   // Initializes the accelerators this class handles as a target.
233   void Init();
234 
235   // Registers the specified accelerators.
236   void RegisterAccelerators(const AcceleratorData accelerators[],
237                             size_t accelerators_length);
238 
239   // Registers the deprecated accelerators and their replacing new ones.
240   void RegisterDeprecatedAccelerators();
241 
242   // Returns whether |action| can be performed. The |accelerator| may provide
243   // additional data the action needs.
244   bool CanPerformAction(AcceleratorAction action,
245                         const ui::Accelerator& accelerator) const;
246 
247   // Performs the specified action. The |accelerator| may provide additional
248   // data the action needs.
249   void PerformAction(AcceleratorAction action,
250                      const ui::Accelerator& accelerator);
251 
252   // Returns whether performing |action| should consume the key event.
253   bool ShouldActionConsumeKeyEvent(AcceleratorAction action);
254 
255   // Get the accelerator restriction for the given action. Supply an |action|
256   // of -1 to get restrictions that apply for the current context.
257   AcceleratorProcessingRestriction GetAcceleratorProcessingRestriction(
258       int action) const;
259 
260   // If |accelerator| is a deprecated accelerator, it performs the appropriate
261   // deprecated accelerator pre-handling.
262   // Returns PROCEED if the accelerator's action should be performed (i.e. if
263   // |accelerator| is not a deprecated accelerator, or it's an enabled
264   // deprecated accelerator), and STOP otherwise (if the accelerator is a
265   // disabled deprecated accelerator).
266   enum class AcceleratorProcessingStatus { PROCEED, STOP };
267   AcceleratorProcessingStatus MaybeDeprecatedAcceleratorPressed(
268       AcceleratorAction action,
269       const ui::Accelerator& accelerator) const;
270 
271   // Returns true if |source_device_id| corresponds to the internal keyboard or
272   // an internal uncategorized input device.
273   bool IsInternalKeyboardOrUncategorizedDevice(int source_device_id) const;
274 
275   // Returns true if |side_volume_button_location_| is in agreed format and
276   // values.
277   bool IsValidSideVolumeButtonLocation() const;
278 
279   // Returns true if the side volume buttons should be swapped. See
280   // SideVolumeButonLocation for the details.
281   bool ShouldSwapSideVolumeButtons(int source_device_id) const;
282 
283   // The metrics recorded include accidental volume adjustments (defined as a
284   // sequence of volume button events in close succession starting with a
285   // volume-up event but ending with an overall-decreased volume, or vice versa)
286   // or normal volume adjustments w/o SwapSideVolumeButtonsForOrientation
287   // feature enabled.
288   void UpdateTabletModeVolumeAdjustHistogram();
289 
290   // Starts |tablet_mode_volume_adjust_timer_| while see VOLUME_UP or
291   // VOLUME_DOWN acceleration action when in tablet mode.
292   void StartTabletModeVolumeAdjustTimer(AcceleratorAction action);
293 
294   std::unique_ptr<ui::AcceleratorManager> accelerator_manager_;
295 
296   // A tracker for the current and previous accelerators.
297   std::unique_ptr<ui::AcceleratorHistory> accelerator_history_;
298 
299   // Handles the exit accelerator which requires a double press to exit and
300   // shows a popup with an explanation.
301   ExitWarningHandler exit_warning_handler_;
302 
303   // A map from accelerators to the AcceleratorAction values, which are used in
304   // the implementation.
305   std::map<ui::Accelerator, AcceleratorAction> accelerators_;
306 
307   std::map<AcceleratorAction, const DeprecatedAcceleratorData*>
308       actions_with_deprecations_;
309   std::set<ui::Accelerator> deprecated_accelerators_;
310 
311   // Actions allowed when the user is not signed in.
312   std::set<int> actions_allowed_at_login_screen_;
313   // Actions allowed when the screen is locked.
314   std::set<int> actions_allowed_at_lock_screen_;
315   // Actions allowed when the power menu is opened.
316   std::set<int> actions_allowed_at_power_menu_;
317   // Actions allowed when a modal window is up.
318   std::set<int> actions_allowed_at_modal_window_;
319   // Preferred actions. See accelerator_table.h for details.
320   std::set<int> preferred_actions_;
321   // Reserved actions. See accelerator_table.h for details.
322   std::set<int> reserved_actions_;
323   // Actions which will be repeated while holding the accelerator key.
324   std::set<int> repeatable_actions_;
325   // Actions allowed in app mode.
326   std::set<int> actions_allowed_in_app_mode_;
327   // Actions allowed in pinned mode.
328   std::set<int> actions_allowed_in_pinned_mode_;
329   // Actions disallowed if there are no windows.
330   std::set<int> actions_needing_window_;
331   // Actions that can be performed without closing the menu (if one is present).
332   std::set<int> actions_keeping_menu_open_;
333 
334   // Holds a weak pointer to the accelerator confirmation dialog.
335   base::WeakPtr<AcceleratorConfirmationDialog> confirmation_dialog_;
336 
337   // Path of the file that contains the side volume button location info. It
338   // should always be kSideVolumeButtonLocationFilePath. But it is allowed to be
339   // set to different paths in test.
340   base::FilePath side_volume_button_location_file_path_;
341 
342   // Stores the location info of side volume buttons.
343   SideVolumeButtonLocation side_volume_button_location_;
344 
345   // Started when VOLUME_DOWN or VOLUME_UP accelerator action is seen while in
346   // tablet mode. Runs UpdateTabletModeVolumeAdjustHistogram() to record
347   // metrics.
348   base::OneShotTimer tablet_mode_volume_adjust_timer_;
349 
350   // True if volume adjust starts with VOLUME_UP action.
351   bool volume_adjust_starts_with_up_ = false;
352 
353   // The initial volume percentage when volume adjust starts.
354   int initial_volume_percent_ = 0;
355 
356   DISALLOW_COPY_AND_ASSIGN(AcceleratorControllerImpl);
357 };
358 
359 }  // namespace ash
360 
361 #endif  // ASH_ACCELERATORS_ACCELERATOR_CONTROLLER_IMPL_H_
362