summaryrefslogtreecommitdiff
path: root/chromium/ash
diff options
context:
space:
mode:
authorZeno Albisser <zeno.albisser@digia.com>2013-08-15 21:46:11 +0200
committerZeno Albisser <zeno.albisser@digia.com>2013-08-15 21:46:11 +0200
commit679147eead574d186ebf3069647b4c23e8ccace6 (patch)
treefc247a0ac8ff119f7c8550879ebb6d3dd8d1ff69 /chromium/ash
downloadqtwebengine-chromium-679147eead574d186ebf3069647b4c23e8ccace6.tar.gz
Initial import.
Diffstat (limited to 'chromium/ash')
-rw-r--r--chromium/ash/DEPS16
-rw-r--r--chromium/ash/OWNERS12
-rw-r--r--chromium/ash/PRESUBMIT.py12
-rw-r--r--chromium/ash/accelerators/accelerator_controller.cc1006
-rw-r--r--chromium/ash/accelerators/accelerator_controller.h178
-rw-r--r--chromium/ash/accelerators/accelerator_controller_unittest.cc1343
-rw-r--r--chromium/ash/accelerators/accelerator_dispatcher.cc134
-rw-r--r--chromium/ash/accelerators/accelerator_dispatcher.h47
-rw-r--r--chromium/ash/accelerators/accelerator_filter.cc94
-rw-r--r--chromium/ash/accelerators/accelerator_filter.h33
-rw-r--r--chromium/ash/accelerators/accelerator_filter_unittest.cc128
-rw-r--r--chromium/ash/accelerators/accelerator_table.cc413
-rw-r--r--chromium/ash/accelerators/accelerator_table.h187
-rw-r--r--chromium/ash/accelerators/accelerator_table_unittest.cc79
-rw-r--r--chromium/ash/accelerators/exit_warning_handler.cc194
-rw-r--r--chromium/ash/accelerators/exit_warning_handler.h87
-rw-r--r--chromium/ash/accelerators/focus_manager_factory.cc41
-rw-r--r--chromium/ash/accelerators/focus_manager_factory.h43
-rw-r--r--chromium/ash/accelerators/nested_dispatcher_controller.cc36
-rw-r--r--chromium/ash/accelerators/nested_dispatcher_controller.h35
-rw-r--r--chromium/ash/accelerators/nested_dispatcher_controller_unittest.cc164
-rw-r--r--chromium/ash/ash.gyp928
-rw-r--r--chromium/ash/ash_chromeos_strings.grdp270
-rw-r--r--chromium/ash/ash_constants.cc23
-rw-r--r--chromium/ash/ash_constants.h43
-rw-r--r--chromium/ash/ash_export.h32
-rw-r--r--chromium/ash/ash_resources.gypi29
-rw-r--r--chromium/ash/ash_strings.grd584
-rw-r--r--chromium/ash/ash_strings.gyp32
-rw-r--r--chromium/ash/ash_switches.cc215
-rw-r--r--chromium/ash/ash_switches.h95
-rw-r--r--chromium/ash/cancel_mode.cc22
-rw-r--r--chromium/ash/cancel_mode.h15
-rw-r--r--chromium/ash/caps_lock_delegate.h33
-rw-r--r--chromium/ash/caps_lock_delegate_stub.cc26
-rw-r--r--chromium/ash/caps_lock_delegate_stub.h34
-rw-r--r--chromium/ash/debug.cc61
-rw-r--r--chromium/ash/debug.h22
-rw-r--r--chromium/ash/desktop_background/OWNERS2
-rw-r--r--chromium/ash/desktop_background/desktop_background_controller.cc474
-rw-r--r--chromium/ash/desktop_background/desktop_background_controller.h214
-rw-r--r--chromium/ash/desktop_background/desktop_background_controller_observer.h23
-rw-r--r--chromium/ash/desktop_background/desktop_background_controller_unittest.cc528
-rw-r--r--chromium/ash/desktop_background/desktop_background_view.cc184
-rw-r--r--chromium/ash/desktop_background/desktop_background_view.h37
-rw-r--r--chromium/ash/desktop_background/desktop_background_widget_controller.cc160
-rw-r--r--chromium/ash/desktop_background/desktop_background_widget_controller.h99
-rw-r--r--chromium/ash/desktop_background/user_wallpaper_delegate.h49
-rw-r--r--chromium/ash/desktop_background/wallpaper_resizer.cc146
-rw-r--r--chromium/ash/desktop_background/wallpaper_resizer.h69
-rw-r--r--chromium/ash/desktop_background/wallpaper_resizer_observer.h23
-rw-r--r--chromium/ash/desktop_background/wallpaper_resizer_unittest.cc147
-rw-r--r--chromium/ash/dip_unittest.cc70
-rw-r--r--chromium/ash/display/OWNERS1
-rw-r--r--chromium/ash/display/display_change_observer_x11.cc199
-rw-r--r--chromium/ash/display/display_change_observer_x11.h57
-rw-r--r--chromium/ash/display/display_controller.cc834
-rw-r--r--chromium/ash/display/display_controller.h235
-rw-r--r--chromium/ash/display/display_controller_unittest.cc1036
-rw-r--r--chromium/ash/display/display_error_observer.cc89
-rw-r--r--chromium/ash/display/display_error_observer.h39
-rw-r--r--chromium/ash/display/display_error_observer_unittest.cc85
-rw-r--r--chromium/ash/display/display_info.cc252
-rw-r--r--chromium/ash/display/display_info.h173
-rw-r--r--chromium/ash/display/display_info_unittest.cc57
-rw-r--r--chromium/ash/display/display_layout.cc152
-rw-r--r--chromium/ash/display/display_layout.h71
-rw-r--r--chromium/ash/display/display_layout_store.cc94
-rw-r--r--chromium/ash/display/display_layout_store.h69
-rw-r--r--chromium/ash/display/display_manager.cc963
-rw-r--r--chromium/ash/display/display_manager.h305
-rw-r--r--chromium/ash/display/display_manager_unittest.cc1052
-rw-r--r--chromium/ash/display/display_pref_util.h48
-rw-r--r--chromium/ash/display/display_util_x11.cc97
-rw-r--r--chromium/ash/display/display_util_x11.h36
-rw-r--r--chromium/ash/display/display_util_x11_unittest.cc104
-rw-r--r--chromium/ash/display/event_transformation_handler.cc98
-rw-r--r--chromium/ash/display/event_transformation_handler.h49
-rw-r--r--chromium/ash/display/mirror_window_controller.cc343
-rw-r--r--chromium/ash/display/mirror_window_controller.h87
-rw-r--r--chromium/ash/display/mirror_window_controller_unittest.cc255
-rw-r--r--chromium/ash/display/mouse_cursor_event_filter.cc236
-rw-r--r--chromium/ash/display/mouse_cursor_event_filter.h101
-rw-r--r--chromium/ash/display/mouse_cursor_event_filter_unittest.cc360
-rw-r--r--chromium/ash/display/output_configurator_animation.cc228
-rw-r--r--chromium/ash/display/output_configurator_animation.h64
-rw-r--r--chromium/ash/display/resolution_notification_controller.cc299
-rw-r--r--chromium/ash/display/resolution_notification_controller.h96
-rw-r--r--chromium/ash/display/resolution_notification_controller_unittest.cc314
-rw-r--r--chromium/ash/display/root_window_transformers.cc293
-rw-r--r--chromium/ash/display/root_window_transformers.h40
-rw-r--r--chromium/ash/display/root_window_transformers_unittest.cc416
-rw-r--r--chromium/ash/display/screen_position_controller.cc216
-rw-r--r--chromium/ash/display/screen_position_controller.h37
-rw-r--r--chromium/ash/display/screen_position_controller_unittest.cc268
-rw-r--r--chromium/ash/display/shared_display_edge_indicator.cc117
-rw-r--r--chromium/ash/display/shared_display_edge_indicator.h62
-rw-r--r--chromium/ash/drag_drop/OWNERS1
-rw-r--r--chromium/ash/drag_drop/drag_drop_controller.cc567
-rw-r--r--chromium/ash/drag_drop/drag_drop_controller.h147
-rw-r--r--chromium/ash/drag_drop/drag_drop_controller_unittest.cc1108
-rw-r--r--chromium/ash/drag_drop/drag_drop_interactive_uitest.cc165
-rw-r--r--chromium/ash/drag_drop/drag_drop_tracker.cc79
-rw-r--r--chromium/ash/drag_drop/drag_drop_tracker.h60
-rw-r--r--chromium/ash/drag_drop/drag_drop_tracker_unittest.cc202
-rw-r--r--chromium/ash/drag_drop/drag_image_view.cc103
-rw-r--r--chromium/ash/drag_drop/drag_image_view.h47
-rw-r--r--chromium/ash/event_rewriter_delegate.h34
-rw-r--r--chromium/ash/extended_desktop_unittest.cc850
-rw-r--r--chromium/ash/focus_cycler.cc115
-rw-r--r--chromium/ash/focus_cycler.h61
-rw-r--r--chromium/ash/focus_cycler_unittest.cc402
-rw-r--r--chromium/ash/high_contrast/high_contrast_controller.cc36
-rw-r--r--chromium/ash/high_contrast/high_contrast_controller.h41
-rw-r--r--chromium/ash/host/root_window_host_factory.cc31
-rw-r--r--chromium/ash/host/root_window_host_factory.h33
-rw-r--r--chromium/ash/host/root_window_host_factory_win.cc40
-rw-r--r--chromium/ash/ime_control_delegate.h32
-rw-r--r--chromium/ash/keyboard_controller_proxy_stub.cc34
-rw-r--r--chromium/ash/keyboard_controller_proxy_stub.h33
-rw-r--r--chromium/ash/keyboard_overlay/keyboard_overlay_delegate.cc149
-rw-r--r--chromium/ash/keyboard_overlay/keyboard_overlay_delegate.h66
-rw-r--r--chromium/ash/keyboard_overlay/keyboard_overlay_delegate_unittest.cc45
-rw-r--r--chromium/ash/keyboard_overlay/keyboard_overlay_view.cc95
-rw-r--r--chromium/ash/keyboard_overlay/keyboard_overlay_view.h68
-rw-r--r--chromium/ash/keyboard_overlay/keyboard_overlay_view_unittest.cc77
-rw-r--r--chromium/ash/launcher/OWNERS1
-rw-r--r--chromium/ash/launcher/alternate_app_list_button.cc150
-rw-r--r--chromium/ash/launcher/alternate_app_list_button.h58
-rw-r--r--chromium/ash/launcher/app_list_button.cc134
-rw-r--r--chromium/ash/launcher/app_list_button.h45
-rw-r--r--chromium/ash/launcher/launcher.cc197
-rw-r--r--chromium/ash/launcher/launcher.h130
-rw-r--r--chromium/ash/launcher/launcher_alignment_menu.cc71
-rw-r--r--chromium/ash/launcher/launcher_alignment_menu.h48
-rw-r--r--chromium/ash/launcher/launcher_button.cc571
-rw-r--r--chromium/ash/launcher/launcher_button.h150
-rw-r--r--chromium/ash/launcher/launcher_button_host.h67
-rw-r--r--chromium/ash/launcher/launcher_delegate.h114
-rw-r--r--chromium/ash/launcher/launcher_icon_observer.h24
-rw-r--r--chromium/ash/launcher/launcher_model.cc184
-rw-r--r--chromium/ash/launcher/launcher_model.h91
-rw-r--r--chromium/ash/launcher/launcher_model_observer.h41
-rw-r--r--chromium/ash/launcher/launcher_model_unittest.cc278
-rw-r--r--chromium/ash/launcher/launcher_navigator.cc72
-rw-r--r--chromium/ash/launcher/launcher_navigator.h24
-rw-r--r--chromium/ash/launcher/launcher_navigator_unittest.cc123
-rw-r--r--chromium/ash/launcher/launcher_tooltip_manager.cc376
-rw-r--r--chromium/ash/launcher/launcher_tooltip_manager.h121
-rw-r--r--chromium/ash/launcher/launcher_tooltip_manager_unittest.cc249
-rw-r--r--chromium/ash/launcher/launcher_types.cc22
-rw-r--r--chromium/ash/launcher/launcher_types.h91
-rw-r--r--chromium/ash/launcher/launcher_unittest.cc119
-rw-r--r--chromium/ash/launcher/launcher_util.cc22
-rw-r--r--chromium/ash/launcher/launcher_util.h21
-rw-r--r--chromium/ash/launcher/launcher_view.cc1715
-rw-r--r--chromium/ash/launcher/launcher_view.h381
-rw-r--r--chromium/ash/launcher/launcher_view_unittest.cc1147
-rw-r--r--chromium/ash/launcher/overflow_bubble.cc285
-rw-r--r--chromium/ash/launcher/overflow_bubble.h52
-rw-r--r--chromium/ash/launcher/overflow_button.cc178
-rw-r--r--chromium/ash/launcher/overflow_button.h44
-rw-r--r--chromium/ash/launcher/scoped_observer_with_duplicated_sources.h70
-rw-r--r--chromium/ash/launcher/scoped_observer_with_duplicated_sources_unittest.cc82
-rw-r--r--chromium/ash/launcher/tabbed_launcher_button.cc153
-rw-r--r--chromium/ash/launcher/tabbed_launcher_button.h112
-rw-r--r--chromium/ash/magnifier/magnification_controller.cc631
-rw-r--r--chromium/ash/magnifier/magnification_controller.h61
-rw-r--r--chromium/ash/magnifier/magnification_controller_unittest.cc442
-rw-r--r--chromium/ash/magnifier/magnifier_constants.h27
-rw-r--r--chromium/ash/magnifier/partial_magnification_controller.cc211
-rw-r--r--chromium/ash/magnifier/partial_magnification_controller.h95
-rw-r--r--chromium/ash/popup_message.cc226
-rw-r--r--chromium/ash/popup_message.h83
-rw-r--r--chromium/ash/resources/OWNERS1
-rw-r--r--chromium/ash/resources/PRESUBMIT.py47
-rw-r--r--chromium/ash/resources/ash_resources.grd286
-rw-r--r--chromium/ash/resources/default_100_percent/common/alert_small.pngbin0 -> 494 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/common/alt_launcher/launcher_background.pngbin0 -> 118 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/common/alt_launcher/launcher_underline_active.pngbin0 -> 75 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/common/alt_launcher/launcher_underline_running.pngbin0 -> 78 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/common/alt_launcher/status_app_menu_icon.pngbin0 -> 224 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/common/alt_launcher/status_guest_icon.pngbin0 -> 209 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/common/alt_launcher/status_icon_background_normal.pngbin0 -> 128 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/common/alt_launcher/status_icon_background_onblack_normal.pngbin0 -> 179 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/common/alt_launcher/status_icon_background_pressed.pngbin0 -> 376 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/common/alt_launcher/status_notification_icon.pngbin0 -> 231 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/common/alt_launcher/status_tray_normal_center.pngbin0 -> 87 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/common/alt_launcher/status_tray_normal_left.pngbin0 -> 97 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/common/alt_launcher/status_tray_normal_onblack_center.pngbin0 -> 106 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/common/alt_launcher/status_tray_normal_onblack_left.pngbin0 -> 127 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/common/alt_launcher/status_tray_normal_onblack_right.pngbin0 -> 124 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/common/alt_launcher/status_tray_normal_right.pngbin0 -> 104 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/common/alt_launcher/status_tray_pressed_center.pngbin0 -> 146 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/common/alt_launcher/status_tray_pressed_left.pngbin0 -> 226 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/common/alt_launcher/status_tray_pressed_right.pngbin0 -> 235 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/common/alt_launcher/status_tray_vertical_normal_bottom.pngbin0 -> 103 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/common/alt_launcher/status_tray_vertical_normal_center.pngbin0 -> 86 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/common/alt_launcher/status_tray_vertical_normal_onblack_bottom.pngbin0 -> 103 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/common/alt_launcher/status_tray_vertical_normal_onblack_center.pngbin0 -> 93 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/common/alt_launcher/status_tray_vertical_normal_onblack_top.pngbin0 -> 102 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/common/alt_launcher/status_tray_vertical_normal_top.pngbin0 -> 94 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/common/alt_launcher/status_tray_vertical_pressed_bottom.pngbin0 -> 162 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/common/alt_launcher/status_tray_vertical_pressed_center.pngbin0 -> 105 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/common/alt_launcher/status_tray_vertical_pressed_top.pngbin0 -> 132 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/common/chromium-32.pngbin0 -> 1052 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/common/launcher/launcher_appmenu.pngbin0 -> 346 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/common/launcher/launcher_appmenu_hover.pngbin0 -> 327 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/common/launcher/launcher_appmenu_pressed.pngbin0 -> 314 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/common/launcher/launcher_background.pngbin0 -> 125 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/common/launcher/launcher_background_left.pngbin0 -> 118 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/common/launcher/launcher_background_right.pngbin0 -> 119 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/common/launcher/launcher_browser.pngbin0 -> 388 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/common/launcher/launcher_browser_panel.pngbin0 -> 394 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/common/launcher/launcher_dimming.pngbin0 -> 99 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/common/launcher/launcher_dimming_left.pngbin0 -> 99 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/common/launcher/launcher_dimming_right.pngbin0 -> 96 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/common/launcher/launcher_incognito_browser.pngbin0 -> 1204 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/common/launcher/launcher_incognito_browser_panel.pngbin0 -> 1176 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/common/launcher/launcher_overflow.pngbin0 -> 177 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/common/launcher/launcher_underline_bottom_active.pngbin0 -> 105 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/common/launcher/launcher_underline_bottom_hover.pngbin0 -> 105 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/common/launcher/launcher_underline_bottom_running.pngbin0 -> 117 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/common/launcher/launcher_underline_left_active.pngbin0 -> 241 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/common/launcher/launcher_underline_left_hover.pngbin0 -> 241 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/common/launcher/launcher_underline_left_running.pngbin0 -> 228 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/common/launcher/launcher_underline_right_active.pngbin0 -> 246 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/common/launcher/launcher_underline_right_hover.pngbin0 -> 246 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/common/launcher/launcher_underline_right_running.pngbin0 -> 229 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/common/launcher/task_manager.pngbin0 -> 530 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/common/launcher/window_switcher_icon_incognito.pngbin0 -> 133 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/common/launcher/window_switcher_icon_normal.pngbin0 -> 117 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/common/multi_window_resize_horizontal.pngbin0 -> 159 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/common/multi_window_resize_vertical.pngbin0 -> 157 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/common/resize_shadow_bottom.pngbin0 -> 68 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/common/resize_shadow_bottom_left.pngbin0 -> 76 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/common/resize_shadow_bottom_right.pngbin0 -> 76 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/common/resize_shadow_left.pngbin0 -> 68 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/common/resize_shadow_right.pngbin0 -> 68 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/common/resize_shadow_top.pngbin0 -> 68 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/common/resize_shadow_top_left.pngbin0 -> 80 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/common/resize_shadow_top_right.pngbin0 -> 81 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/common/tray_popup_label_button_border.pngbin0 -> 67 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/common/tray_popup_label_button_hover_background.pngbin0 -> 70 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/common/tray_popup_label_button_normal_background.pngbin0 -> 68 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/common/tray_popup_public_account_logout_button_border.pngbin0 -> 70 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/common/window_button_separator.pngbin0 -> 86 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/common/window_close_fullscreen_hover.pngbin0 -> 544 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/common/window_close_fullscreen_normal.pngbin0 -> 503 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/common/window_close_fullscreen_pressed.pngbin0 -> 631 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/common/window_close_short_black_hover.pngbin0 -> 143 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/common/window_close_short_black_normal.pngbin0 -> 178 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/common/window_close_short_black_pressed.pngbin0 -> 162 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/common/window_close_short_hover.pngbin0 -> 552 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/common/window_close_short_normal.pngbin0 -> 503 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/common/window_close_short_pressed.pngbin0 -> 630 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/common/window_close_tall_hover.pngbin0 -> 572 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/common/window_close_tall_normal.pngbin0 -> 530 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/common/window_close_tall_pressed.pngbin0 -> 634 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/common/window_fullscreen_shadow.pngbin0 -> 349 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/common/window_fullscreen_shadow_rtl.pngbin0 -> 922 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/common/window_header_base_active.pngbin0 -> 85 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/common/window_header_base_inactive.pngbin0 -> 85 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/common/window_header_base_incognito_active.pngbin0 -> 85 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/common/window_header_base_incognito_inactive.pngbin0 -> 85 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/common/window_header_base_minimal.pngbin0 -> 70 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/common/window_header_shade_left.pngbin0 -> 93 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/common/window_header_shade_middle.pngbin0 -> 92 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/common/window_header_shade_right.pngbin0 -> 93 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/common/window_header_shade_top.pngbin0 -> 74 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/common/window_header_shade_top_left.pngbin0 -> 78 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/common/window_header_shade_top_right.pngbin0 -> 78 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/common/window_immersive_enter_hover.pngbin0 -> 141 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/common/window_immersive_enter_normal.pngbin0 -> 158 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/common/window_immersive_enter_pressed.pngbin0 -> 150 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/common/window_immersive_exit_hover.pngbin0 -> 136 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/common/window_immersive_exit_normal.pngbin0 -> 153 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/common/window_immersive_exit_pressed.pngbin0 -> 140 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/common/window_minimize_short_hover.pngbin0 -> 422 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/common/window_minimize_short_normal.pngbin0 -> 365 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/common/window_minimize_short_pressed.pngbin0 -> 591 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/common/window_position_left_hover.pngbin0 -> 271 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/common/window_position_left_hover_restore.pngbin0 -> 344 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/common/window_position_left_normal.pngbin0 -> 112 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/common/window_position_left_normal_restore.pngbin0 -> 190 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/common/window_position_left_pressed.pngbin0 -> 363 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/common/window_position_left_pressed_restore.pngbin0 -> 476 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/common/window_position_middle_hover.pngbin0 -> 276 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/common/window_position_middle_normal.pngbin0 -> 113 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/common/window_position_middle_pressed.pngbin0 -> 365 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/common/window_position_right_hover.pngbin0 -> 273 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/common/window_position_right_hover_restore.pngbin0 -> 355 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/common/window_position_right_normal.pngbin0 -> 112 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/common/window_position_right_normal_restore.pngbin0 -> 189 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/common/window_position_right_pressed.pngbin0 -> 372 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/common/window_position_right_pressed_restore.pngbin0 -> 511 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/common/window_size_fullscreen_hover.pngbin0 -> 475 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/common/window_size_fullscreen_normal.pngbin0 -> 456 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/common/window_size_fullscreen_pressed.pngbin0 -> 630 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/common/window_size_short_black_hover.pngbin0 -> 116 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/common/window_size_short_black_normal.pngbin0 -> 154 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/common/window_size_short_black_pressed.pngbin0 -> 141 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/common/window_size_short_hover.pngbin0 -> 499 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/common/window_size_short_normal.pngbin0 -> 457 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/common/window_size_short_pressed.pngbin0 -> 649 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/common/window_size_tall_hover.pngbin0 -> 536 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/common/window_size_tall_normal.pngbin0 -> 486 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/common/window_size_tall_pressed.pngbin0 -> 690 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/cros/common/default_throbber.pngbin0 -> 6686 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/cros/network/notification_3g.pngbin0 -> 1170 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/cros/network/notification_lte.pngbin0 -> 909 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/cros/network/status_cellular_disabled.pngbin0 -> 267 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/cros/network/status_cellular_disabled_hover.pngbin0 -> 274 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/cros/network/status_cellular_enabled.pngbin0 -> 155 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/cros/network/status_cellular_enabled_hover.pngbin0 -> 157 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/cros/network/status_cellular_failed.pngbin0 -> 729 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/cros/network/status_data_low.pngbin0 -> 247 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/cros/network/status_data_none.pngbin0 -> 867 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/cros/network/status_network_failed.pngbin0 -> 978 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/cros/network/status_network_info.pngbin0 -> 491 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/cros/network/status_network_info_hover.pngbin0 -> 489 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/cros/network/status_wifi_disabled.pngbin0 -> 389 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/cros/network/status_wifi_disabled_hover.pngbin0 -> 398 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/cros/network/status_wifi_enabled.pngbin0 -> 367 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/cros/network/status_wifi_enabled_hover.pngbin0 -> 369 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/cros/network/statusbar_network_1x.pngbin0 -> 103 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/cros/network/statusbar_network_1x_error.pngbin0 -> 104 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/cros/network/statusbar_network_1x_unknown.pngbin0 -> 99 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/cros/network/statusbar_network_3g_dark.pngbin0 -> 180 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/cros/network/statusbar_network_3g_error.pngbin0 -> 104 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/cros/network/statusbar_network_3g_light.pngbin0 -> 241 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/cros/network/statusbar_network_3g_unknown.pngbin0 -> 97 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/cros/network/statusbar_network_4g_dark.pngbin0 -> 179 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/cros/network/statusbar_network_4g_light.pngbin0 -> 233 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/cros/network/statusbar_network_arcs_dark.pngbin0 -> 1170 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/cros/network/statusbar_network_arcs_light.pngbin0 -> 1198 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/cros/network/statusbar_network_bars_dark.pngbin0 -> 335 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/cros/network/statusbar_network_bars_light.pngbin0 -> 488 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/cros/network/statusbar_network_edge_dark.pngbin0 -> 103 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/cros/network/statusbar_network_edge_light.pngbin0 -> 146 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/cros/network/statusbar_network_evdo_dark.pngbin0 -> 158 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/cros/network/statusbar_network_evdo_light.pngbin0 -> 209 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/cros/network/statusbar_network_gprs_dark.pngbin0 -> 113 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/cros/network/statusbar_network_gprs_light.pngbin0 -> 93 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/cros/network/statusbar_network_hspa_dark.pngbin0 -> 108 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/cros/network/statusbar_network_hspa_light.pngbin0 -> 97 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/cros/network/statusbar_network_hspa_plus_dark.pngbin0 -> 125 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/cros/network/statusbar_network_hspa_plus_light.pngbin0 -> 102 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/cros/network/statusbar_network_lte_advanced_dark.pngbin0 -> 157 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/cros/network/statusbar_network_lte_advanced_light.pngbin0 -> 113 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/cros/network/statusbar_network_lte_dark.pngbin0 -> 133 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/cros/network/statusbar_network_lte_light.pngbin0 -> 107 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/cros/network/statusbar_network_roaming_dark.pngbin0 -> 217 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/cros/network/statusbar_network_roaming_light.pngbin0 -> 213 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/cros/network/statusbar_network_secure_dark.pngbin0 -> 184 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/cros/network/statusbar_network_secure_light.pngbin0 -> 183 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/cros/network/statusbar_network_vpn_badge.pngbin0 -> 135 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/cros/network/statusbar_vpn_dark.pngbin0 -> 199 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/cros/network/statusbar_wired.pngbin0 -> 219 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/cros/notification/notification_low_power_charger.pngbin0 -> 248 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/cros/status/multiprofiles_add.pngbin0 -> 91 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/cros/status/status_accessibility_dark.pngbin0 -> 399 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/cros/status/status_accessibility_mode.pngbin0 -> 451 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/cros/status/status_audio_device_bluetooth.pngbin0 -> 316 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/cros/status/status_audio_device_hdmi.pngbin0 -> 281 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/cros/status/status_audio_device_headphones.pngbin0 -> 530 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/cros/status/status_audio_device_usb.pngbin0 -> 296 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/cros/status/status_bluetooth.pngbin0 -> 624 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/cros/status/status_bluetooth_disabled.pngbin0 -> 576 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/cros/status/status_bluetooth_disabled_hover.pngbin0 -> 585 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/cros/status/status_bluetooth_enabled.pngbin0 -> 615 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/cros/status/status_bluetooth_enabled_hover.pngbin0 -> 627 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/cros/status/status_brightness.pngbin0 -> 356 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/cros/status/status_capslock.pngbin0 -> 281 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/cros/status/status_capslock_dark.pngbin0 -> 259 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/cros/status/status_display.pngbin0 -> 292 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/cros/status/status_display_dark.pngbin0 -> 247 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/cros/status/status_drive.pngbin0 -> 503 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/cros/status/status_drive_item_cancel.pngbin0 -> 146 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/cros/status/status_drive_item_cancel_hover.pngbin0 -> 237 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/cros/status/status_drive_item_done.pngbin0 -> 164 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/cros/status/status_drive_item_failed.pngbin0 -> 217 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/cros/status/status_drive_light.pngbin0 -> 517 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/cros/status/status_guest_icon.pngbin0 -> 143 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/cros/status/status_help.pngbin0 -> 213 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/cros/status/status_help_hover.pngbin0 -> 217 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/cros/status/status_ime.pngbin0 -> 195 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/cros/status/status_less.pngbin0 -> 140 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/cros/status/status_locale.pngbin0 -> 1254 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/cros/status/status_lockscreen.pngbin0 -> 242 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/cros/status/status_lockscreen_hover.pngbin0 -> 248 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/cros/status/status_logout_button_normal_bottom.pngbin0 -> 88 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/cros/status/status_logout_button_normal_bottom_left.pngbin0 -> 125 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/cros/status/status_logout_button_normal_bottom_right.pngbin0 -> 135 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/cros/status/status_logout_button_normal_center.pngbin0 -> 74 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/cros/status/status_logout_button_normal_left.pngbin0 -> 86 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/cros/status/status_logout_button_normal_right.pngbin0 -> 89 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/cros/status/status_logout_button_normal_top.pngbin0 -> 92 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/cros/status/status_logout_button_normal_top_left.pngbin0 -> 118 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/cros/status/status_logout_button_normal_top_right.pngbin0 -> 117 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/cros/status/status_logout_button_pushed_bottom.pngbin0 -> 88 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/cros/status/status_logout_button_pushed_bottom_left.pngbin0 -> 125 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/cros/status/status_logout_button_pushed_bottom_right.pngbin0 -> 135 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/cros/status/status_logout_button_pushed_center.pngbin0 -> 74 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/cros/status/status_logout_button_pushed_left.pngbin0 -> 86 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/cros/status/status_logout_button_pushed_right.pngbin0 -> 89 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/cros/status/status_logout_button_pushed_top.pngbin0 -> 92 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/cros/status/status_logout_button_pushed_top_left.pngbin0 -> 118 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/cros/status/status_logout_button_pushed_top_right.pngbin0 -> 125 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/cros/status/status_managed.pngbin0 -> 413 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/cros/status/status_managed_mode_user.pngbin0 -> 336 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/cros/status/status_managed_tray.pngbin0 -> 374 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/cros/status/status_more.pngbin0 -> 220 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/cros/status/status_power_small_all.pngbin0 -> 2819 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/cros/status/status_power_small_all_dark.pngbin0 -> 3158 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/cros/status/status_power_small_all_dark_discharging.pngbin0 -> 1172 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/cros/status/status_power_small_all_dark_fluctuating.pngbin0 -> 908 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/cros/status/status_power_small_all_discharging.pngbin0 -> 889 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/cros/status/status_power_small_all_fluctuating.pngbin0 -> 718 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/cros/status/status_screen_share_dark.pngbin0 -> 503 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/cros/status/status_screen_share_light.pngbin0 -> 517 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/cros/status/status_session_length_limit_timer.pngbin0 -> 612 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/cros/status/status_settings.pngbin0 -> 366 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/cros/status/status_shutdown.pngbin0 -> 336 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/cros/status/status_shutdown_hover.pngbin0 -> 343 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/cros/status/status_sms.pngbin0 -> 685 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/cros/status/status_tracing.pngbin0 -> 252 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/cros/status/status_update.pngbin0 -> 294 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/cros/status/status_update_dark.pngbin0 -> 287 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/cros/status/status_update_dark_green.pngbin0 -> 526 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/cros/status/status_update_dark_orange.pngbin0 -> 482 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/cros/status/status_update_dark_red.pngbin0 -> 502 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/cros/status/status_update_green.pngbin0 -> 485 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/cros/status/status_update_orange.pngbin0 -> 440 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/cros/status/status_update_red.pngbin0 -> 449 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/cros/status/status_volume_dark.pngbin0 -> 1000 bytes
-rw-r--r--chromium/ash/resources/default_100_percent/cros/status/status_volume_mute.pngbin0 -> 418 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/common/alert_small.pngbin0 -> 1066 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/common/alt_launcher/launcher_background.pngbin0 -> 148 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/common/alt_launcher/launcher_underline_active.pngbin0 -> 98 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/common/alt_launcher/launcher_underline_running.pngbin0 -> 97 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/common/alt_launcher/status_app_menu_icon.pngbin0 -> 307 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/common/alt_launcher/status_guest_icon.pngbin0 -> 356 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/common/alt_launcher/status_icon_background_normal.pngbin0 -> 189 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/common/alt_launcher/status_icon_background_onblack_normal.pngbin0 -> 279 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/common/alt_launcher/status_icon_background_pressed.pngbin0 -> 769 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/common/alt_launcher/status_notification_icon.pngbin0 -> 324 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/common/alt_launcher/status_tray_normal_center.pngbin0 -> 90 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/common/alt_launcher/status_tray_normal_left.pngbin0 -> 123 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/common/alt_launcher/status_tray_normal_onblack_center.pngbin0 -> 121 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/common/alt_launcher/status_tray_normal_onblack_left.pngbin0 -> 175 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/common/alt_launcher/status_tray_normal_onblack_right.pngbin0 -> 165 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/common/alt_launcher/status_tray_normal_right.pngbin0 -> 124 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/common/alt_launcher/status_tray_pressed_center.pngbin0 -> 177 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/common/alt_launcher/status_tray_pressed_left.pngbin0 -> 408 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/common/alt_launcher/status_tray_pressed_right.pngbin0 -> 431 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/common/alt_launcher/status_tray_vertical_normal_bottom.pngbin0 -> 127 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/common/alt_launcher/status_tray_vertical_normal_center.pngbin0 -> 91 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/common/alt_launcher/status_tray_vertical_normal_onblack_bottom.pngbin0 -> 125 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/common/alt_launcher/status_tray_vertical_normal_onblack_center.pngbin0 -> 96 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/common/alt_launcher/status_tray_vertical_normal_onblack_top.pngbin0 -> 147 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/common/alt_launcher/status_tray_vertical_normal_top.pngbin0 -> 123 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/common/alt_launcher/status_tray_vertical_pressed_bottom.pngbin0 -> 345 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/common/alt_launcher/status_tray_vertical_pressed_center.pngbin0 -> 129 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/common/alt_launcher/status_tray_vertical_pressed_top.pngbin0 -> 264 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/common/launcher/launcher_appmenu.pngbin0 -> 662 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/common/launcher/launcher_appmenu_hover.pngbin0 -> 574 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/common/launcher/launcher_appmenu_pressed.pngbin0 -> 545 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/common/launcher/launcher_background.pngbin0 -> 152 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/common/launcher/launcher_background_left.pngbin0 -> 146 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/common/launcher/launcher_background_right.pngbin0 -> 145 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/common/launcher/launcher_browser.pngbin0 -> 675 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/common/launcher/launcher_browser_panel.pngbin0 -> 647 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/common/launcher/launcher_dimming.pngbin0 -> 106 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/common/launcher/launcher_dimming_left.pngbin0 -> 109 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/common/launcher/launcher_dimming_right.pngbin0 -> 111 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/common/launcher/launcher_incognito_browser.pngbin0 -> 2378 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/common/launcher/launcher_incognito_browser_panel.pngbin0 -> 2359 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/common/launcher/launcher_overflow.pngbin0 -> 314 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/common/launcher/launcher_underline_bottom_active.pngbin0 -> 141 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/common/launcher/launcher_underline_bottom_hover.pngbin0 -> 141 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/common/launcher/launcher_underline_bottom_running.pngbin0 -> 168 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/common/launcher/launcher_underline_left_active.pngbin0 -> 413 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/common/launcher/launcher_underline_left_hover.pngbin0 -> 413 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/common/launcher/launcher_underline_left_running.pngbin0 -> 340 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/common/launcher/launcher_underline_right_active.pngbin0 -> 410 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/common/launcher/launcher_underline_right_hover.pngbin0 -> 410 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/common/launcher/launcher_underline_right_running.pngbin0 -> 345 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/common/launcher/task_manager.pngbin0 -> 1288 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/common/launcher/window_switcher_icon_incognito.pngbin0 -> 245 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/common/launcher/window_switcher_icon_normal.pngbin0 -> 157 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/common/multi_window_resize_vertical.pngbin0 -> 353 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/common/tray_popup_label_button_border.pngbin0 -> 71 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/common/tray_popup_label_button_hover_background.pngbin0 -> 73 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/common/tray_popup_label_button_normal_background.pngbin0 -> 68 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/common/tray_popup_public_account_logout_button_border.pngbin0 -> 73 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/common/window_close_fullscreen_hover.pngbin0 -> 1244 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/common/window_close_fullscreen_normal.pngbin0 -> 1187 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/common/window_close_fullscreen_pressed.pngbin0 -> 1286 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/common/window_close_short_black_hover.pngbin0 -> 293 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/common/window_close_short_black_normal.pngbin0 -> 502 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/common/window_close_short_black_pressed.pngbin0 -> 374 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/common/window_close_short_hover.pngbin0 -> 1247 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/common/window_close_short_normal.pngbin0 -> 1186 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/common/window_close_short_pressed.pngbin0 -> 1298 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/common/window_close_tall_hover.pngbin0 -> 1320 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/common/window_close_tall_normal.pngbin0 -> 1325 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/common/window_close_tall_pressed.pngbin0 -> 1399 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/common/window_fullscreen_shadow.pngbin0 -> 834 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/common/window_header_base_active.pngbin0 -> 90 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/common/window_header_base_inactive.pngbin0 -> 90 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/common/window_header_base_incognito_active.pngbin0 -> 90 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/common/window_header_base_incognito_inactive.pngbin0 -> 90 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/common/window_header_base_minimal.pngbin0 -> 75 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/common/window_header_shade_left.pngbin0 -> 123 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/common/window_header_shade_middle.pngbin0 -> 100 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/common/window_header_shade_right.pngbin0 -> 121 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/common/window_header_shade_top.pngbin0 -> 81 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/common/window_header_shade_top_left.pngbin0 -> 96 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/common/window_header_shade_top_right.pngbin0 -> 97 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/common/window_immersive_enter_hover.pngbin0 -> 268 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/common/window_immersive_enter_normal.pngbin0 -> 332 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/common/window_immersive_enter_pressed.pngbin0 -> 266 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/common/window_immersive_exit_hover.pngbin0 -> 260 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/common/window_immersive_exit_normal.pngbin0 -> 331 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/common/window_immersive_exit_pressed.pngbin0 -> 254 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/common/window_minimize_short_hover.pngbin0 -> 771 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/common/window_minimize_short_normal.pngbin0 -> 617 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/common/window_minimize_short_pressed.pngbin0 -> 1042 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/common/window_position_left_hover.pngbin0 -> 370 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/common/window_position_left_hover_restore.pngbin0 -> 724 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/common/window_position_left_normal.pngbin0 -> 140 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/common/window_position_left_normal_restore.pngbin0 -> 410 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/common/window_position_left_pressed.pngbin0 -> 686 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/common/window_position_left_pressed_restore.pngbin0 -> 1059 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/common/window_position_middle_hover.pngbin0 -> 321 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/common/window_position_middle_normal.pngbin0 -> 148 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/common/window_position_middle_pressed.pngbin0 -> 658 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/common/window_position_right_hover.pngbin0 -> 364 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/common/window_position_right_hover_restore.pngbin0 -> 743 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/common/window_position_right_normal.pngbin0 -> 146 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/common/window_position_right_normal_restore.pngbin0 -> 406 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/common/window_position_right_pressed.pngbin0 -> 690 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/common/window_position_right_pressed_restore.pngbin0 -> 1085 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/common/window_size_fullscreen_hover.pngbin0 -> 808 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/common/window_size_fullscreen_normal.pngbin0 -> 735 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/common/window_size_fullscreen_pressed.pngbin0 -> 1083 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/common/window_size_short_black_hover.pngbin0 -> 143 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/common/window_size_short_black_normal.pngbin0 -> 183 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/common/window_size_short_black_pressed.pngbin0 -> 172 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/common/window_size_short_hover.pngbin0 -> 869 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/common/window_size_short_normal.pngbin0 -> 736 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/common/window_size_short_pressed.pngbin0 -> 1191 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/common/window_size_tall_hover.pngbin0 -> 972 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/common/window_size_tall_normal.pngbin0 -> 894 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/common/window_size_tall_pressed.pngbin0 -> 1343 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/cros/common/default_throbber.pngbin0 -> 16231 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/cros/network/notification_3g.pngbin0 -> 2540 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/cros/network/notification_lte.pngbin0 -> 1584 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/cros/network/status_cellular_disabled.pngbin0 -> 373 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/cros/network/status_cellular_disabled_hover.pngbin0 -> 377 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/cros/network/status_cellular_enabled.pngbin0 -> 225 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/cros/network/status_cellular_enabled_hover.pngbin0 -> 226 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/cros/network/status_cellular_failed.pngbin0 -> 1465 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/cros/network/status_data_low.pngbin0 -> 333 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/cros/network/status_data_none.pngbin0 -> 1667 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/cros/network/status_network_failed.pngbin0 -> 1954 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/cros/network/status_network_info.pngbin0 -> 1190 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/cros/network/status_network_info_hover.pngbin0 -> 1203 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/cros/network/status_wifi_disabled.pngbin0 -> 728 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/cros/network/status_wifi_disabled_hover.pngbin0 -> 740 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/cros/network/status_wifi_enabled.pngbin0 -> 680 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/cros/network/status_wifi_enabled_hover.pngbin0 -> 683 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/cros/network/statusbar_network_3g_dark.pngbin0 -> 437 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/cros/network/statusbar_network_3g_light.pngbin0 -> 530 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/cros/network/statusbar_network_4g_dark.pngbin0 -> 400 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/cros/network/statusbar_network_4g_light.pngbin0 -> 508 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/cros/network/statusbar_network_arcs_dark.pngbin0 -> 3092 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/cros/network/statusbar_network_arcs_light.pngbin0 -> 2348 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/cros/network/statusbar_network_bars_dark.pngbin0 -> 628 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/cros/network/statusbar_network_bars_light.pngbin0 -> 749 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/cros/network/statusbar_network_edge_dark.pngbin0 -> 178 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/cros/network/statusbar_network_edge_light.pngbin0 -> 225 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/cros/network/statusbar_network_evdo_dark.pngbin0 -> 311 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/cros/network/statusbar_network_evdo_light.pngbin0 -> 365 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/cros/network/statusbar_network_gprs_dark.pngbin0 -> 145 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/cros/network/statusbar_network_gprs_light.pngbin0 -> 106 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/cros/network/statusbar_network_hspa_dark.pngbin0 -> 143 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/cros/network/statusbar_network_hspa_light.pngbin0 -> 106 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/cros/network/statusbar_network_hspa_plus_dark.pngbin0 -> 169 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/cros/network/statusbar_network_hspa_plus_light.pngbin0 -> 121 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/cros/network/statusbar_network_lte_advanced_dark.pngbin0 -> 198 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/cros/network/statusbar_network_lte_advanced_light.pngbin0 -> 139 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/cros/network/statusbar_network_lte_dark.pngbin0 -> 174 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/cros/network/statusbar_network_lte_light.pngbin0 -> 128 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/cros/network/statusbar_network_roaming_dark.pngbin0 -> 369 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/cros/network/statusbar_network_roaming_light.pngbin0 -> 332 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/cros/network/statusbar_network_secure_dark.pngbin0 -> 310 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/cros/network/statusbar_network_secure_light.pngbin0 -> 290 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/cros/network/statusbar_network_vpn_badge.pngbin0 -> 253 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/cros/network/statusbar_vpn_dark.pngbin0 -> 350 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/cros/network/statusbar_wired.pngbin0 -> 278 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/cros/notification/notification_low_power_charger.pngbin0 -> 445 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/cros/status/multiprofiles_add.pngbin0 -> 113 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/cros/status/status_accessibility_dark.pngbin0 -> 886 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/cros/status/status_audio_device_bluetooth.pngbin0 -> 546 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/cros/status/status_audio_device_hdmi.pngbin0 -> 463 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/cros/status/status_audio_device_headphones.pngbin0 -> 1150 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/cros/status/status_audio_device_usb.pngbin0 -> 314 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/cros/status/status_bluetooth.pngbin0 -> 1341 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/cros/status/status_bluetooth_disabled.pngbin0 -> 1106 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/cros/status/status_bluetooth_disabled_hover.pngbin0 -> 1130 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/cros/status/status_bluetooth_enabled.pngbin0 -> 1159 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/cros/status/status_bluetooth_enabled_hover.pngbin0 -> 1186 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/cros/status/status_brightness.pngbin0 -> 830 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/cros/status/status_capslock.pngbin0 -> 430 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/cros/status/status_capslock_dark.pngbin0 -> 432 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/cros/status/status_display.pngbin0 -> 396 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/cros/status/status_display_dark.pngbin0 -> 385 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/cros/status/status_drive.pngbin0 -> 1302 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/cros/status/status_drive_item_cancel.pngbin0 -> 303 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/cros/status/status_drive_item_cancel_hover.pngbin0 -> 466 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/cros/status/status_drive_item_done.pngbin0 -> 282 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/cros/status/status_drive_item_failed.pngbin0 -> 200 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/cros/status/status_drive_light.pngbin0 -> 1094 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/cros/status/status_guest_icon.pngbin0 -> 194 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/cros/status/status_help.pngbin0 -> 441 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/cros/status/status_help_hover.pngbin0 -> 457 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/cros/status/status_ime.pngbin0 -> 293 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/cros/status/status_less.pngbin0 -> 294 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/cros/status/status_locale.pngbin0 -> 3100 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/cros/status/status_lockscreen.pngbin0 -> 437 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/cros/status/status_lockscreen_hover.pngbin0 -> 448 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/cros/status/status_logout_button_normal_bottom.pngbin0 -> 104 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/cros/status/status_logout_button_normal_bottom_left.pngbin0 -> 250 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/cros/status/status_logout_button_normal_bottom_right.pngbin0 -> 247 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/cros/status/status_logout_button_normal_center.pngbin0 -> 91 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/cros/status/status_logout_button_normal_left.pngbin0 -> 112 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/cros/status/status_logout_button_normal_right.pngbin0 -> 121 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/cros/status/status_logout_button_normal_top.pngbin0 -> 102 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/cros/status/status_logout_button_normal_top_left.pngbin0 -> 212 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/cros/status/status_logout_button_normal_top_right.pngbin0 -> 236 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/cros/status/status_logout_button_pushed_bottom.pngbin0 -> 104 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/cros/status/status_logout_button_pushed_bottom_left.pngbin0 -> 240 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/cros/status/status_logout_button_pushed_bottom_right.pngbin0 -> 241 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/cros/status/status_logout_button_pushed_center.pngbin0 -> 91 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/cros/status/status_logout_button_pushed_left.pngbin0 -> 116 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/cros/status/status_logout_button_pushed_right.pngbin0 -> 125 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/cros/status/status_logout_button_pushed_top.pngbin0 -> 99 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/cros/status/status_logout_button_pushed_top_left.pngbin0 -> 201 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/cros/status/status_logout_button_pushed_top_right.pngbin0 -> 228 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/cros/status/status_managed.pngbin0 -> 775 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/cros/status/status_managed_mode_user.pngbin0 -> 668 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/cros/status/status_managed_tray.pngbin0 -> 665 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/cros/status/status_more.pngbin0 -> 262 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/cros/status/status_power_small_all.pngbin0 -> 6038 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/cros/status/status_power_small_all_dark.pngbin0 -> 6766 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/cros/status/status_power_small_all_dark_discharging.pngbin0 -> 3440 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/cros/status/status_power_small_all_dark_fluctuating.pngbin0 -> 2787 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/cros/status/status_power_small_all_discharging.pngbin0 -> 2818 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/cros/status/status_power_small_all_fluctuating.pngbin0 -> 2301 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/cros/status/status_screen_share_dark.pngbin0 -> 1302 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/cros/status/status_screen_share_light.pngbin0 -> 1094 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/cros/status/status_session_length_limit_timer.pngbin0 -> 1221 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/cros/status/status_settings.pngbin0 -> 714 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/cros/status/status_shutdown.pngbin0 -> 725 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/cros/status/status_shutdown_hover.pngbin0 -> 731 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/cros/status/status_sms.pngbin0 -> 1534 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/cros/status/status_tracing.pngbin0 -> 432 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/cros/status/status_update.pngbin0 -> 484 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/cros/status/status_update_dark.pngbin0 -> 542 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/cros/status/status_update_dark_green.pngbin0 -> 1105 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/cros/status/status_update_dark_orange.pngbin0 -> 974 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/cros/status/status_update_dark_red.pngbin0 -> 983 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/cros/status/status_update_green.pngbin0 -> 963 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/cros/status/status_update_orange.pngbin0 -> 822 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/cros/status/status_update_red.pngbin0 -> 821 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/cros/status/status_volume_dark.pngbin0 -> 2585 bytes
-rw-r--r--chromium/ash/resources/default_200_percent/cros/status/status_volume_mute.pngbin0 -> 775 bytes
-rw-r--r--chromium/ash/resources/wallpaper/default_large.jpgbin0 -> 18866 bytes
-rw-r--r--chromium/ash/resources/wallpaper/default_small.jpgbin0 -> 16154 bytes
-rw-r--r--chromium/ash/root_window_controller.cc788
-rw-r--r--chromium/ash/root_window_controller.h291
-rw-r--r--chromium/ash/root_window_controller_unittest.cc575
-rw-r--r--chromium/ash/rotator/screen_rotation.cc132
-rw-r--r--chromium/ash/rotator/screen_rotation.h64
-rw-r--r--chromium/ash/scoped_target_root_window.cc21
-rw-r--r--chromium/ash/scoped_target_root_window.h33
-rw-r--r--chromium/ash/screen_ash.cc154
-rw-r--r--chromium/ash/screen_ash.h88
-rw-r--r--chromium/ash/screen_ash_unittest.cc116
-rw-r--r--chromium/ash/screensaver/DEPS3
-rw-r--r--chromium/ash/screensaver/OWNERS1
-rw-r--r--chromium/ash/screensaver/screensaver_view.cc170
-rw-r--r--chromium/ash/screensaver/screensaver_view.h94
-rw-r--r--chromium/ash/screensaver/screensaver_view_unittest.cc81
-rw-r--r--chromium/ash/screenshot_delegate.h37
-rw-r--r--chromium/ash/session_state_delegate.h90
-rw-r--r--chromium/ash/session_state_delegate_stub.cc84
-rw-r--r--chromium/ash/session_state_delegate_stub.h54
-rw-r--r--chromium/ash/session_state_observer.h25
-rw-r--r--chromium/ash/shelf/background_animator.cc61
-rw-r--r--chromium/ash/shelf/background_animator.h74
-rw-r--r--chromium/ash/shelf/shelf_bezel_event_filter.cc73
-rw-r--r--chromium/ash/shelf/shelf_bezel_event_filter.h39
-rw-r--r--chromium/ash/shelf/shelf_layout_manager.cc1130
-rw-r--r--chromium/ash/shelf/shelf_layout_manager.h401
-rw-r--r--chromium/ash/shelf/shelf_layout_manager_observer.h37
-rw-r--r--chromium/ash/shelf/shelf_layout_manager_unittest.cc1833
-rw-r--r--chromium/ash/shelf/shelf_types.h57
-rw-r--r--chromium/ash/shelf/shelf_widget.cc656
-rw-r--r--chromium/ash/shelf/shelf_widget.h117
-rw-r--r--chromium/ash/shelf/shelf_widget_unittest.cc195
-rw-r--r--chromium/ash/shell.cc993
-rw-r--r--chromium/ash/shell.h639
-rw-r--r--chromium/ash/shell/DEPS4
-rw-r--r--chromium/ash/shell/app_list.cc337
-rw-r--r--chromium/ash/shell/bubble.cc47
-rw-r--r--chromium/ash/shell/cocoa/app-Info.plist34
-rw-r--r--chromium/ash/shell/cocoa/app.icnsbin0 -> 147653 bytes
-rw-r--r--chromium/ash/shell/cocoa/nibs/MainMenu.xib2163
-rw-r--r--chromium/ash/shell/cocoa/nibs/RootWindow.xib169
-rw-r--r--chromium/ash/shell/content_client/DEPS3
-rw-r--r--chromium/ash/shell/content_client/shell_browser_main_parts.cc174
-rw-r--r--chromium/ash/shell/content_client/shell_browser_main_parts.h60
-rw-r--r--chromium/ash/shell/content_client/shell_content_browser_client.cc40
-rw-r--r--chromium/ash/shell/content_client/shell_content_browser_client.h47
-rw-r--r--chromium/ash/shell/content_client/shell_main_delegate.cc45
-rw-r--r--chromium/ash/shell/content_client/shell_main_delegate.h45
-rw-r--r--chromium/ash/shell/context_menu.cc70
-rw-r--r--chromium/ash/shell/context_menu.h51
-rw-r--r--chromium/ash/shell/example_factory.h31
-rw-r--r--chromium/ash/shell/launcher_delegate_impl.cc87
-rw-r--r--chromium/ash/shell/launcher_delegate_impl.h58
-rw-r--r--chromium/ash/shell/lock_view.cc109
-rw-r--r--chromium/ash/shell/panel_window.cc89
-rw-r--r--chromium/ash/shell/panel_window.h55
-rw-r--r--chromium/ash/shell/shell_delegate_impl.cc225
-rw-r--r--chromium/ash/shell/shell_delegate_impl.h99
-rw-r--r--chromium/ash/shell/shell_main.cc25
-rw-r--r--chromium/ash/shell/shell_main_parts.cc21
-rw-r--r--chromium/ash/shell/shell_main_parts.h17
-rw-r--r--chromium/ash/shell/shell_main_parts_mac.mm30
-rw-r--r--chromium/ash/shell/toplevel_window.cc69
-rw-r--r--chromium/ash/shell/toplevel_window.h44
-rw-r--r--chromium/ash/shell/widgets.cc145
-rw-r--r--chromium/ash/shell/window_type_launcher.cc405
-rw-r--r--chromium/ash/shell/window_type_launcher.h93
-rw-r--r--chromium/ash/shell/window_watcher.cc156
-rw-r--r--chromium/ash/shell/window_watcher.h62
-rw-r--r--chromium/ash/shell/window_watcher_unittest.cc39
-rw-r--r--chromium/ash/shell_delegate.h257
-rw-r--r--chromium/ash/shell_factory.h37
-rw-r--r--chromium/ash/shell_observer.h44
-rw-r--r--chromium/ash/shell_unittest.cc463
-rw-r--r--chromium/ash/shell_window_ids.h107
-rw-r--r--chromium/ash/strings/ash_strings_am.xtb201
-rw-r--r--chromium/ash/strings/ash_strings_ar.xtb198
-rw-r--r--chromium/ash/strings/ash_strings_bg.xtb201
-rw-r--r--chromium/ash/strings/ash_strings_bn.xtb201
-rw-r--r--chromium/ash/strings/ash_strings_ca.xtb201
-rw-r--r--chromium/ash/strings/ash_strings_cs.xtb198
-rw-r--r--chromium/ash/strings/ash_strings_da.xtb201
-rw-r--r--chromium/ash/strings/ash_strings_de.xtb201
-rw-r--r--chromium/ash/strings/ash_strings_el.xtb200
-rw-r--r--chromium/ash/strings/ash_strings_en-GB.xtb201
-rw-r--r--chromium/ash/strings/ash_strings_es-419.xtb201
-rw-r--r--chromium/ash/strings/ash_strings_es.xtb201
-rw-r--r--chromium/ash/strings/ash_strings_et.xtb201
-rw-r--r--chromium/ash/strings/ash_strings_fa.xtb199
-rw-r--r--chromium/ash/strings/ash_strings_fi.xtb199
-rw-r--r--chromium/ash/strings/ash_strings_fil.xtb201
-rw-r--r--chromium/ash/strings/ash_strings_fr.xtb201
-rw-r--r--chromium/ash/strings/ash_strings_gu.xtb200
-rw-r--r--chromium/ash/strings/ash_strings_hi.xtb201
-rw-r--r--chromium/ash/strings/ash_strings_hr.xtb201
-rw-r--r--chromium/ash/strings/ash_strings_hu.xtb201
-rw-r--r--chromium/ash/strings/ash_strings_id.xtb201
-rw-r--r--chromium/ash/strings/ash_strings_it.xtb201
-rw-r--r--chromium/ash/strings/ash_strings_iw.xtb201
-rw-r--r--chromium/ash/strings/ash_strings_ja.xtb201
-rw-r--r--chromium/ash/strings/ash_strings_kn.xtb201
-rw-r--r--chromium/ash/strings/ash_strings_ko.xtb201
-rw-r--r--chromium/ash/strings/ash_strings_lt.xtb201
-rw-r--r--chromium/ash/strings/ash_strings_lv.xtb201
-rw-r--r--chromium/ash/strings/ash_strings_ml.xtb200
-rw-r--r--chromium/ash/strings/ash_strings_mr.xtb201
-rw-r--r--chromium/ash/strings/ash_strings_ms.xtb200
-rw-r--r--chromium/ash/strings/ash_strings_nl.xtb201
-rw-r--r--chromium/ash/strings/ash_strings_no.xtb201
-rw-r--r--chromium/ash/strings/ash_strings_pl.xtb201
-rw-r--r--chromium/ash/strings/ash_strings_pt-BR.xtb199
-rw-r--r--chromium/ash/strings/ash_strings_pt-PT.xtb201
-rw-r--r--chromium/ash/strings/ash_strings_ro.xtb201
-rw-r--r--chromium/ash/strings/ash_strings_ru.xtb200
-rw-r--r--chromium/ash/strings/ash_strings_sk.xtb201
-rw-r--r--chromium/ash/strings/ash_strings_sl.xtb201
-rw-r--r--chromium/ash/strings/ash_strings_sr.xtb201
-rw-r--r--chromium/ash/strings/ash_strings_sv.xtb199
-rw-r--r--chromium/ash/strings/ash_strings_sw.xtb201
-rw-r--r--chromium/ash/strings/ash_strings_ta.xtb201
-rw-r--r--chromium/ash/strings/ash_strings_te.xtb201
-rw-r--r--chromium/ash/strings/ash_strings_th.xtb201
-rw-r--r--chromium/ash/strings/ash_strings_tr.xtb201
-rw-r--r--chromium/ash/strings/ash_strings_uk.xtb201
-rw-r--r--chromium/ash/strings/ash_strings_vi.xtb201
-rw-r--r--chromium/ash/strings/ash_strings_zh-CN.xtb201
-rw-r--r--chromium/ash/strings/ash_strings_zh-TW.xtb201
-rw-r--r--chromium/ash/system/DEPS3
-rw-r--r--chromium/ash/system/OWNERS3
-rw-r--r--chromium/ash/system/bluetooth/bluetooth_observer.h20
-rw-r--r--chromium/ash/system/bluetooth/tray_bluetooth.cc463
-rw-r--r--chromium/ash/system/bluetooth/tray_bluetooth.h48
-rw-r--r--chromium/ash/system/brightness/brightness_control_delegate.h39
-rw-r--r--chromium/ash/system/brightness/brightness_observer.h22
-rw-r--r--chromium/ash/system/brightness/tray_brightness.cc219
-rw-r--r--chromium/ash/system/brightness/tray_brightness.h73
-rw-r--r--chromium/ash/system/chromeos/DEPS3
-rw-r--r--chromium/ash/system/chromeos/audio/tray_audio.cc606
-rw-r--r--chromium/ash/system/chromeos/audio/tray_audio.h61
-rw-r--r--chromium/ash/system/chromeos/enterprise/enterprise_domain_observer.h20
-rw-r--r--chromium/ash/system/chromeos/enterprise/tray_enterprise.cc60
-rw-r--r--chromium/ash/system/chromeos/enterprise/tray_enterprise.h52
-rw-r--r--chromium/ash/system/chromeos/keyboard_brightness_controller.cc40
-rw-r--r--chromium/ash/system/chromeos/keyboard_brightness_controller.h35
-rw-r--r--chromium/ash/system/chromeos/label_tray_view.cc68
-rw-r--r--chromium/ash/system/chromeos/label_tray_view.h37
-rw-r--r--chromium/ash/system/chromeos/managed/tray_locally_managed_user.cc95
-rw-r--r--chromium/ash/system/chromeos/managed/tray_locally_managed_user.h49
-rw-r--r--chromium/ash/system/chromeos/network/network_connect.cc397
-rw-r--r--chromium/ash/system/chromeos/network/network_connect.h48
-rw-r--r--chromium/ash/system/chromeos/network/network_detailed_view.h55
-rw-r--r--chromium/ash/system/chromeos/network/network_icon.cc849
-rw-r--r--chromium/ash/system/chromeos/network/network_icon.h76
-rw-r--r--chromium/ash/system/chromeos/network/network_icon_animation.cc60
-rw-r--r--chromium/ash/system/chromeos/network/network_icon_animation.h48
-rw-r--r--chromium/ash/system/chromeos/network/network_icon_animation_observer.h27
-rw-r--r--chromium/ash/system/chromeos/network/network_observer.cc35
-rw-r--r--chromium/ash/system/chromeos/network/network_observer.h69
-rw-r--r--chromium/ash/system/chromeos/network/network_state_list_detailed_view.cc882
-rw-r--r--chromium/ash/system/chromeos/network/network_state_list_detailed_view.h156
-rw-r--r--chromium/ash/system/chromeos/network/network_state_notifier.cc184
-rw-r--r--chromium/ash/system/chromeos/network/network_state_notifier.h73
-rw-r--r--chromium/ash/system/chromeos/network/network_state_notifier_unittest.cc97
-rw-r--r--chromium/ash/system/chromeos/network/network_tray_delegate.h24
-rw-r--r--chromium/ash/system/chromeos/network/tray_network.cc559
-rw-r--r--chromium/ash/system/chromeos/network/tray_network.h94
-rw-r--r--chromium/ash/system/chromeos/network/tray_network_state_observer.cc63
-rw-r--r--chromium/ash/system/chromeos/network/tray_network_state_observer.h54
-rw-r--r--chromium/ash/system/chromeos/network/tray_sms.cc416
-rw-r--r--chromium/ash/system/chromeos/network/tray_sms.h66
-rw-r--r--chromium/ash/system/chromeos/network/tray_vpn.cc182
-rw-r--r--chromium/ash/system/chromeos/network/tray_vpn.h59
-rw-r--r--chromium/ash/system/chromeos/power/power_status.cc294
-rw-r--r--chromium/ash/system/chromeos/power/power_status.h152
-rw-r--r--chromium/ash/system/chromeos/power/power_status_unittest.cc135
-rw-r--r--chromium/ash/system/chromeos/power/power_status_view.cc229
-rw-r--r--chromium/ash/system/chromeos/power/power_status_view.h69
-rw-r--r--chromium/ash/system/chromeos/power/tray_power.cc306
-rw-r--r--chromium/ash/system/chromeos/power/tray_power.h90
-rw-r--r--chromium/ash/system/chromeos/power/tray_power_unittest.cc173
-rw-r--r--chromium/ash/system/chromeos/screen_security/screen_capture_observer.h29
-rw-r--r--chromium/ash/system/chromeos/screen_security/screen_capture_tray_item.cc91
-rw-r--r--chromium/ash/system/chromeos/screen_security/screen_capture_tray_item.h47
-rw-r--r--chromium/ash/system/chromeos/screen_security/screen_share_observer.h29
-rw-r--r--chromium/ash/system/chromeos/screen_security/screen_share_tray_item.cc101
-rw-r--r--chromium/ash/system/chromeos/screen_security/screen_share_tray_item.h47
-rw-r--r--chromium/ash/system/chromeos/screen_security/screen_tray_item.cc200
-rw-r--r--chromium/ash/system/chromeos/screen_security/screen_tray_item.h145
-rw-r--r--chromium/ash/system/chromeos/screen_security/screen_tray_item_unittest.cc225
-rw-r--r--chromium/ash/system/chromeos/settings/tray_settings.cc169
-rw-r--r--chromium/ash/system/chromeos/settings/tray_settings.h40
-rw-r--r--chromium/ash/system/chromeos/tray_display.cc453
-rw-r--r--chromium/ash/system/chromeos/tray_display.h71
-rw-r--r--chromium/ash/system/chromeos/tray_display_unittest.cc460
-rw-r--r--chromium/ash/system/chromeos/tray_tracing.cc113
-rw-r--r--chromium/ash/system/chromeos/tray_tracing.h57
-rw-r--r--chromium/ash/system/date/clock_observer.h23
-rw-r--r--chromium/ash/system/date/date_view.cc291
-rw-r--r--chromium/ash/system/date/date_view.h127
-rw-r--r--chromium/ash/system/date/tray_date.cc209
-rw-r--r--chromium/ash/system/date/tray_date.h59
-rw-r--r--chromium/ash/system/drive/drive_observer.h22
-rw-r--r--chromium/ash/system/drive/tray_drive.cc512
-rw-r--r--chromium/ash/system/drive/tray_drive.h58
-rw-r--r--chromium/ash/system/ime/ime_observer.h22
-rw-r--r--chromium/ash/system/ime/tray_ime.cc311
-rw-r--r--chromium/ash/system/ime/tray_ime.h70
-rw-r--r--chromium/ash/system/keyboard_brightness/keyboard_brightness_control_delegate.h29
-rw-r--r--chromium/ash/system/locale/locale_notification_controller.cc124
-rw-r--r--chromium/ash/system/locale/locale_notification_controller.h41
-rw-r--r--chromium/ash/system/locale/locale_observer.h32
-rw-r--r--chromium/ash/system/logout_button/logout_button_observer.h22
-rw-r--r--chromium/ash/system/logout_button/logout_button_tray.cc158
-rw-r--r--chromium/ash/system/logout_button/logout_button_tray.h62
-rw-r--r--chromium/ash/system/monitor/tray_monitor.cc98
-rw-r--r--chromium/ash/system/monitor/tray_monitor.h43
-rw-r--r--chromium/ash/system/session_length_limit/session_length_limit_observer.h24
-rw-r--r--chromium/ash/system/session_length_limit/tray_session_length_limit.cc398
-rw-r--r--chromium/ash/system/session_length_limit/tray_session_length_limit.h72
-rw-r--r--chromium/ash/system/status_area_widget.cc156
-rw-r--r--chromium/ash/system/status_area_widget.h91
-rw-r--r--chromium/ash/system/status_area_widget_delegate.cc153
-rw-r--r--chromium/ash/system/status_area_widget_delegate.h70
-rw-r--r--chromium/ash/system/tray/actionable_view.cc78
-rw-r--r--chromium/ash/system/tray/actionable_view.h63
-rw-r--r--chromium/ash/system/tray/fixed_sized_image_view.cc27
-rw-r--r--chromium/ash/system/tray/fixed_sized_image_view.h35
-rw-r--r--chromium/ash/system/tray/fixed_sized_scroll_view.cc61
-rw-r--r--chromium/ash/system/tray/fixed_sized_scroll_view.h47
-rw-r--r--chromium/ash/system/tray/hover_highlight_view.cc173
-rw-r--r--chromium/ash/system/tray/hover_highlight_view.h90
-rw-r--r--chromium/ash/system/tray/special_popup_row.cc133
-rw-r--r--chromium/ash/system/tray/special_popup_row.h54
-rw-r--r--chromium/ash/system/tray/system_tray.cc661
-rw-r--r--chromium/ash/system/tray/system_tray.h238
-rw-r--r--chromium/ash/system/tray/system_tray_bubble.cc373
-rw-r--r--chromium/ash/system/tray/system_tray_bubble.h79
-rw-r--r--chromium/ash/system/tray/system_tray_delegate.cc62
-rw-r--r--chromium/ash/system/tray/system_tray_delegate.h325
-rw-r--r--chromium/ash/system/tray/system_tray_item.cc91
-rw-r--r--chromium/ash/system/tray/system_tray_item.h119
-rw-r--r--chromium/ash/system/tray/system_tray_notifier.cc352
-rw-r--r--chromium/ash/system/tray/system_tray_notifier.h169
-rw-r--r--chromium/ash/system/tray/system_tray_unittest.cc339
-rw-r--r--chromium/ash/system/tray/test_system_tray_delegate.cc294
-rw-r--r--chromium/ash/system/tray/test_system_tray_delegate.h115
-rw-r--r--chromium/ash/system/tray/throbber_view.cc110
-rw-r--r--chromium/ash/system/tray/throbber_view.h65
-rw-r--r--chromium/ash/system/tray/tray_background_view.cc608
-rw-r--r--chromium/ash/system/tray/tray_background_view.h180
-rw-r--r--chromium/ash/system/tray/tray_bar_button_with_title.cc113
-rw-r--r--chromium/ash/system/tray/tray_bar_button_with_title.h49
-rw-r--r--chromium/ash/system/tray/tray_bubble_wrapper.cc57
-rw-r--r--chromium/ash/system/tray/tray_bubble_wrapper.h49
-rw-r--r--chromium/ash/system/tray/tray_constants.cc83
-rw-r--r--chromium/ash/system/tray/tray_constants.h75
-rw-r--r--chromium/ash/system/tray/tray_details_view.cc149
-rw-r--r--chromium/ash/system/tray/tray_details_view.h67
-rw-r--r--chromium/ash/system/tray/tray_empty.cc67
-rw-r--r--chromium/ash/system/tray/tray_empty.h34
-rw-r--r--chromium/ash/system/tray/tray_event_filter.cc114
-rw-r--r--chromium/ash/system/tray/tray_event_filter.h49
-rw-r--r--chromium/ash/system/tray/tray_image_item.cc93
-rw-r--r--chromium/ash/system/tray/tray_image_item.h56
-rw-r--r--chromium/ash/system/tray/tray_item_more.cc107
-rw-r--r--chromium/ash/system/tray/tray_item_more.h64
-rw-r--r--chromium/ash/system/tray/tray_item_view.cc129
-rw-r--r--chromium/ash/system/tray/tray_item_view.h85
-rw-r--r--chromium/ash/system/tray/tray_notification_view.cc171
-rw-r--r--chromium/ash/system/tray/tray_notification_view.h98
-rw-r--r--chromium/ash/system/tray/tray_popup_header_button.cc72
-rw-r--r--chromium/ash/system/tray/tray_popup_header_button.h46
-rw-r--r--chromium/ash/system/tray/tray_popup_label_button.cc35
-rw-r--r--chromium/ash/system/tray/tray_popup_label_button.h33
-rw-r--r--chromium/ash/system/tray/tray_popup_label_button_border.cc109
-rw-r--r--chromium/ash/system/tray/tray_popup_label_button_border.h33
-rw-r--r--chromium/ash/system/tray/tray_utils.cc65
-rw-r--r--chromium/ash/system/tray/tray_utils.h35
-rw-r--r--chromium/ash/system/tray/view_click_listener.h26
-rw-r--r--chromium/ash/system/tray_accessibility.cc355
-rw-r--r--chromium/ash/system/tray_accessibility.h135
-rw-r--r--chromium/ash/system/tray_caps_lock.cc192
-rw-r--r--chromium/ash/system/tray_caps_lock.h60
-rw-r--r--chromium/ash/system/tray_update.cc205
-rw-r--r--chromium/ash/system/tray_update.h51
-rw-r--r--chromium/ash/system/user/login_status.cc51
-rw-r--r--chromium/ash/system/user/login_status.h31
-rw-r--r--chromium/ash/system/user/tray_user.cc1296
-rw-r--r--chromium/ash/system/user/tray_user.h92
-rw-r--r--chromium/ash/system/user/tray_user_unittest.cc233
-rw-r--r--chromium/ash/system/user/update_observer.h28
-rw-r--r--chromium/ash/system/user/user_observer.h21
-rw-r--r--chromium/ash/system/web_notification/web_notification_tray.cc599
-rw-r--r--chromium/ash/system/web_notification/web_notification_tray.h178
-rw-r--r--chromium/ash/system/web_notification/web_notification_tray_unittest.cc475
-rw-r--r--chromium/ash/tooltips/tooltip_controller_unittest.cc182
-rw-r--r--chromium/ash/touch/touch_hud_debug.cc491
-rw-r--r--chromium/ash/touch/touch_hud_debug.h87
-rw-r--r--chromium/ash/touch/touch_hud_projection.cc187
-rw-r--r--chromium/ash/touch/touch_hud_projection.h44
-rw-r--r--chromium/ash/touch/touch_observer_hud.cc150
-rw-r--r--chromium/ash/touch/touch_observer_hud.h91
-rw-r--r--chromium/ash/touch/touch_observer_hud_unittest.cc420
-rw-r--r--chromium/ash/touch/touch_uma.cc437
-rw-r--r--chromium/ash/touch/touch_uma.h83
-rw-r--r--chromium/ash/volume_control_delegate.h26
-rw-r--r--chromium/ash/wm/activation_controller.cc413
-rw-r--r--chromium/ash/wm/activation_controller.h126
-rw-r--r--chromium/ash/wm/activation_controller_delegate.h37
-rw-r--r--chromium/ash/wm/activation_controller_unittest.cc575
-rw-r--r--chromium/ash/wm/always_on_top_controller.cc73
-rw-r--r--chromium/ash/wm/always_on_top_controller.h51
-rw-r--r--chromium/ash/wm/app_list_controller.cc431
-rw-r--r--chromium/ash/wm/app_list_controller.h136
-rw-r--r--chromium/ash/wm/ash_activation_controller.cc96
-rw-r--r--chromium/ash/wm/ash_activation_controller.h37
-rw-r--r--chromium/ash/wm/ash_activation_controller_unittest.cc168
-rw-r--r--chromium/ash/wm/ash_focus_rules.cc164
-rw-r--r--chromium/ash/wm/ash_focus_rules.h43
-rw-r--r--chromium/ash/wm/ash_native_cursor_manager.cc124
-rw-r--r--chromium/ash/wm/ash_native_cursor_manager.h66
-rw-r--r--chromium/ash/wm/ash_native_cursor_manager_unittest.cc179
-rw-r--r--chromium/ash/wm/base_layout_manager.cc274
-rw-r--r--chromium/ash/wm/base_layout_manager.h121
-rw-r--r--chromium/ash/wm/base_layout_manager_unittest.cc316
-rw-r--r--chromium/ash/wm/boot_splash_screen_chromeos.cc76
-rw-r--r--chromium/ash/wm/boot_splash_screen_chromeos.h53
-rw-r--r--chromium/ash/wm/capture_controller.cc74
-rw-r--r--chromium/ash/wm/capture_controller.h36
-rw-r--r--chromium/ash/wm/coordinate_conversion.cc47
-rw-r--r--chromium/ash/wm/coordinate_conversion.h44
-rw-r--r--chromium/ash/wm/custom_frame_view_ash.cc217
-rw-r--r--chromium/ash/wm/custom_frame_view_ash.h98
-rw-r--r--chromium/ash/wm/custom_frame_view_ash_unittest.cc871
-rw-r--r--chromium/ash/wm/default_window_resizer.cc71
-rw-r--r--chromium/ash/wm/default_window_resizer.h55
-rw-r--r--chromium/ash/wm/dock/dock_types.h30
-rw-r--r--chromium/ash/wm/dock/docked_window_layout_manager.cc694
-rw-r--r--chromium/ash/wm/dock/docked_window_layout_manager.h235
-rw-r--r--chromium/ash/wm/dock/docked_window_layout_manager_observer.h31
-rw-r--r--chromium/ash/wm/dock/docked_window_layout_manager_unittest.cc436
-rw-r--r--chromium/ash/wm/dock/docked_window_resizer.cc360
-rw-r--r--chromium/ash/wm/dock/docked_window_resizer.h111
-rw-r--r--chromium/ash/wm/dock/docked_window_resizer_unittest.cc1056
-rw-r--r--chromium/ash/wm/drag_window_controller.cc125
-rw-r--r--chromium/ash/wm/drag_window_controller.h97
-rw-r--r--chromium/ash/wm/drag_window_resizer.cc210
-rw-r--r--chromium/ash/wm/drag_window_resizer.h78
-rw-r--r--chromium/ash/wm/drag_window_resizer_unittest.cc566
-rw-r--r--chromium/ash/wm/event_client_impl.cc60
-rw-r--r--chromium/ash/wm/event_client_impl.h35
-rw-r--r--chromium/ash/wm/event_rewriter_event_filter.cc76
-rw-r--r--chromium/ash/wm/event_rewriter_event_filter.h50
-rw-r--r--chromium/ash/wm/frame_painter.cc935
-rw-r--r--chromium/ash/wm/frame_painter.h256
-rw-r--r--chromium/ash/wm/frame_painter_unittest.cc693
-rw-r--r--chromium/ash/wm/gestures/OWNERS1
-rw-r--r--chromium/ash/wm/gestures/long_press_affordance_handler.cc377
-rw-r--r--chromium/ash/wm/gestures/long_press_affordance_handler.h80
-rw-r--r--chromium/ash/wm/gestures/shelf_gesture_handler.cc92
-rw-r--r--chromium/ash/wm/gestures/shelf_gesture_handler.h43
-rw-r--r--chromium/ash/wm/gestures/system_pinch_handler.cc145
-rw-r--r--chromium/ash/wm/gestures/system_pinch_handler.h85
-rw-r--r--chromium/ash/wm/gestures/tray_gesture_handler.cc109
-rw-r--r--chromium/ash/wm/gestures/tray_gesture_handler.h48
-rw-r--r--chromium/ash/wm/gestures/two_finger_drag_handler.cc198
-rw-r--r--chromium/ash/wm/gestures/two_finger_drag_handler.h55
-rw-r--r--chromium/ash/wm/image_cursors.cc140
-rw-r--r--chromium/ash/wm/image_cursors.h58
-rw-r--r--chromium/ash/wm/lock_state_controller.cc48
-rw-r--r--chromium/ash/wm/lock_state_controller.h146
-rw-r--r--chromium/ash/wm/lock_state_controller_impl2.cc646
-rw-r--r--chromium/ash/wm/lock_state_controller_impl2.h263
-rw-r--r--chromium/ash/wm/lock_state_controller_impl2_unittest.cc1070
-rw-r--r--chromium/ash/wm/lock_state_observer.h28
-rw-r--r--chromium/ash/wm/maximize_bubble_controller.cc864
-rw-r--r--chromium/ash/wm/maximize_bubble_controller.h101
-rw-r--r--chromium/ash/wm/mru_window_tracker.cc182
-rw-r--r--chromium/ash/wm/mru_window_tracker.h87
-rw-r--r--chromium/ash/wm/overlay_event_filter.cc78
-rw-r--r--chromium/ash/wm/overlay_event_filter.h71
-rw-r--r--chromium/ash/wm/panels/OWNERS2
-rw-r--r--chromium/ash/wm/panels/panel_frame_view.cc157
-rw-r--r--chromium/ash/wm/panels/panel_frame_view.h78
-rw-r--r--chromium/ash/wm/panels/panel_layout_manager.cc873
-rw-r--r--chromium/ash/wm/panels/panel_layout_manager.h186
-rw-r--r--chromium/ash/wm/panels/panel_layout_manager_unittest.cc785
-rw-r--r--chromium/ash/wm/panels/panel_window_event_handler.cc50
-rw-r--r--chromium/ash/wm/panels/panel_window_event_handler.h35
-rw-r--r--chromium/ash/wm/panels/panel_window_resizer.cc238
-rw-r--r--chromium/ash/wm/panels/panel_window_resizer.h95
-rw-r--r--chromium/ash/wm/panels/panel_window_resizer_unittest.cc457
-rw-r--r--chromium/ash/wm/partial_screenshot_view.cc243
-rw-r--r--chromium/ash/wm/partial_screenshot_view.h79
-rw-r--r--chromium/ash/wm/partial_screenshot_view_unittest.cc103
-rw-r--r--chromium/ash/wm/power_button_controller.cc110
-rw-r--r--chromium/ash/wm/power_button_controller.h69
-rw-r--r--chromium/ash/wm/power_button_controller_unittest.cc637
-rw-r--r--chromium/ash/wm/property_util.cc78
-rw-r--r--chromium/ash/wm/property_util.h79
-rw-r--r--chromium/ash/wm/resize_shadow.cc110
-rw-r--r--chromium/ash/wm/resize_shadow.h68
-rw-r--r--chromium/ash/wm/resize_shadow_controller.cc74
-rw-r--r--chromium/ash/wm/resize_shadow_controller.h65
-rw-r--r--chromium/ash/wm/root_window_layout_manager.cc79
-rw-r--r--chromium/ash/wm/root_window_layout_manager.h56
-rw-r--r--chromium/ash/wm/screen_dimmer.cc69
-rw-r--r--chromium/ash/wm/screen_dimmer.h75
-rw-r--r--chromium/ash/wm/screen_dimmer_unittest.cc79
-rw-r--r--chromium/ash/wm/session_state_animator.cc634
-rw-r--r--chromium/ash/wm/session_state_animator.h191
-rw-r--r--chromium/ash/wm/session_state_controller_impl.cc344
-rw-r--r--chromium/ash/wm/session_state_controller_impl.h195
-rw-r--r--chromium/ash/wm/stacking_controller.cc153
-rw-r--r--chromium/ash/wm/stacking_controller.h48
-rw-r--r--chromium/ash/wm/stacking_controller_unittest.cc64
-rw-r--r--chromium/ash/wm/status_area_layout_manager.cc81
-rw-r--r--chromium/ash/wm/status_area_layout_manager.h51
-rw-r--r--chromium/ash/wm/sticky_keys.cc223
-rw-r--r--chromium/ash/wm/sticky_keys.h180
-rw-r--r--chromium/ash/wm/sticky_keys_unittest.cc244
-rw-r--r--chromium/ash/wm/system_background_controller.cc54
-rw-r--r--chromium/ash/wm/system_background_controller.h58
-rw-r--r--chromium/ash/wm/system_gesture_event_filter.cc148
-rw-r--r--chromium/ash/wm/system_gesture_event_filter.h78
-rw-r--r--chromium/ash/wm/system_gesture_event_filter_unittest.cc565
-rw-r--r--chromium/ash/wm/system_modal_container_event_filter.cc36
-rw-r--r--chromium/ash/wm/system_modal_container_event_filter.h37
-rw-r--r--chromium/ash/wm/system_modal_container_event_filter_delegate.h26
-rw-r--r--chromium/ash/wm/system_modal_container_layout_manager.cc224
-rw-r--r--chromium/ash/wm/system_modal_container_layout_manager.h98
-rw-r--r--chromium/ash/wm/system_modal_container_layout_manager_unittest.cc503
-rw-r--r--chromium/ash/wm/toplevel_window_event_handler.cc534
-rw-r--r--chromium/ash/wm/toplevel_window_event_handler.h123
-rw-r--r--chromium/ash/wm/toplevel_window_event_handler_unittest.cc816
-rw-r--r--chromium/ash/wm/user_activity_detector.cc84
-rw-r--r--chromium/ash/wm/user_activity_detector.h81
-rw-r--r--chromium/ash/wm/user_activity_detector_unittest.cc225
-rw-r--r--chromium/ash/wm/user_activity_observer.h35
-rw-r--r--chromium/ash/wm/video_detector.cc134
-rw-r--r--chromium/ash/wm/video_detector.h109
-rw-r--r--chromium/ash/wm/video_detector_unittest.cc295
-rw-r--r--chromium/ash/wm/window_animations.cc560
-rw-r--r--chromium/ash/wm/window_animations.h83
-rw-r--r--chromium/ash/wm/window_animations_unittest.cc137
-rw-r--r--chromium/ash/wm/window_cycle_controller.cc139
-rw-r--r--chromium/ash/wm/window_cycle_controller.h82
-rw-r--r--chromium/ash/wm/window_cycle_controller_unittest.cc442
-rw-r--r--chromium/ash/wm/window_cycle_list.cc87
-rw-r--r--chromium/ash/wm/window_cycle_list.h59
-rw-r--r--chromium/ash/wm/window_manager_unittest.cc840
-rw-r--r--chromium/ash/wm/window_modality_controller_unittest.cc557
-rw-r--r--chromium/ash/wm/window_properties.cc36
-rw-r--r--chromium/ash/wm/window_properties.h98
-rw-r--r--chromium/ash/wm/window_resizer.cc425
-rw-r--r--chromium/ash/wm/window_resizer.h149
-rw-r--r--chromium/ash/wm/window_selector.cc550
-rw-r--r--chromium/ash/wm/window_selector.h120
-rw-r--r--chromium/ash/wm/window_selector_controller.cc122
-rw-r--r--chromium/ash/wm/window_selector_controller.h72
-rw-r--r--chromium/ash/wm/window_selector_delegate.h32
-rw-r--r--chromium/ash/wm/window_selector_unittest.cc250
-rw-r--r--chromium/ash/wm/window_util.cc208
-rw-r--r--chromium/ash/wm/window_util.h133
-rw-r--r--chromium/ash/wm/window_util_unittest.cc32
-rw-r--r--chromium/ash/wm/workspace/auto_window_management.cc201
-rw-r--r--chromium/ash/wm/workspace/auto_window_management.h20
-rw-r--r--chromium/ash/wm/workspace/colored_window_controller.cc92
-rw-r--r--chromium/ash/wm/workspace/colored_window_controller.h52
-rw-r--r--chromium/ash/wm/workspace/desktop_background_fade_controller.cc65
-rw-r--r--chromium/ash/wm/workspace/desktop_background_fade_controller.h68
-rw-r--r--chromium/ash/wm/workspace/frame_maximize_button.cc624
-rw-r--r--chromium/ash/wm/workspace/frame_maximize_button.h183
-rw-r--r--chromium/ash/wm/workspace/magnetism_matcher.cc194
-rw-r--r--chromium/ash/wm/workspace/magnetism_matcher.h191
-rw-r--r--chromium/ash/wm/workspace/magnetism_matcher_unittest.cc171
-rw-r--r--chromium/ash/wm/workspace/maximize_bubble_frame_state.h20
-rw-r--r--chromium/ash/wm/workspace/multi_window_resize_controller.cc545
-rw-r--r--chromium/ash/wm/workspace/multi_window_resize_controller.h186
-rw-r--r--chromium/ash/wm/workspace/multi_window_resize_controller_unittest.cc258
-rw-r--r--chromium/ash/wm/workspace/phantom_window_controller.cc214
-rw-r--r--chromium/ash/wm/workspace/phantom_window_controller.h104
-rw-r--r--chromium/ash/wm/workspace/snap_sizer.cc227
-rw-r--r--chromium/ash/wm/workspace/snap_sizer.h130
-rw-r--r--chromium/ash/wm/workspace/snap_types.h23
-rw-r--r--chromium/ash/wm/workspace/workspace_event_handler.cc196
-rw-r--r--chromium/ash/wm/workspace/workspace_event_handler.h50
-rw-r--r--chromium/ash/wm/workspace/workspace_event_handler_test_helper.cc19
-rw-r--r--chromium/ash/wm/workspace/workspace_event_handler_test_helper.h31
-rw-r--r--chromium/ash/wm/workspace/workspace_event_handler_unittest.cc341
-rw-r--r--chromium/ash/wm/workspace/workspace_layout_manager.cc354
-rw-r--r--chromium/ash/wm/workspace/workspace_layout_manager.h93
-rw-r--r--chromium/ash/wm/workspace/workspace_layout_manager_unittest.cc362
-rw-r--r--chromium/ash/wm/workspace/workspace_types.h27
-rw-r--r--chromium/ash/wm/workspace/workspace_window_resizer.cc936
-rw-r--r--chromium/ash/wm/workspace/workspace_window_resizer.h220
-rw-r--r--chromium/ash/wm/workspace/workspace_window_resizer_unittest.cc1978
-rw-r--r--chromium/ash/wm/workspace_controller.cc128
-rw-r--r--chromium/ash/wm/workspace_controller.h55
-rw-r--r--chromium/ash/wm/workspace_controller_test_helper.cc32
-rw-r--r--chromium/ash/wm/workspace_controller_test_helper.h33
-rw-r--r--chromium/ash/wm/workspace_controller_unittest.cc1261
1183 files changed, 129050 insertions, 0 deletions
diff --git a/chromium/ash/DEPS b/chromium/ash/DEPS
new file mode 100644
index 00000000000..f9a7e256df6
--- /dev/null
+++ b/chromium/ash/DEPS
@@ -0,0 +1,16 @@
+include_rules = [
+ "+cc/debug",
+ "+chromeos",
+ "+content/public",
+ "+gpu/config",
+ "+grit/ash_resources.h",
+ "+grit/ash_strings.h",
+ "+grit/ui_resources.h",
+ "+grit/ui_strings.h",
+ "+skia/ext",
+ "+third_party/cros_system_api",
+ "+third_party/skia",
+ "+net",
+ "+ui",
+ "+win8",
+]
diff --git a/chromium/ash/OWNERS b/chromium/ash/OWNERS
new file mode 100644
index 00000000000..55bce5efb06
--- /dev/null
+++ b/chromium/ash/OWNERS
@@ -0,0 +1,12 @@
+derat@chromium.org
+jamescook@chromium.org
+sky@chromium.org
+
+per-file ash_strings.grd=*
+per-file ash_chromeos_strings.grdp=*
+per-file ash_switches.*=*
+
+per-file ash_root_window_transformer.*=oshima@chromium.org
+per-file extended_desktop_unittest.*=oshima@chromium.org
+per-file root_window_controller*=oshima@chromium.org
+per-file screen_ash*=oshima@chromium.org
diff --git a/chromium/ash/PRESUBMIT.py b/chromium/ash/PRESUBMIT.py
new file mode 100644
index 00000000000..12b0c42e854
--- /dev/null
+++ b/chromium/ash/PRESUBMIT.py
@@ -0,0 +1,12 @@
+# Copyright (c) 2012 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.
+
+"""Chromium presubmit script for src/ash
+
+See http://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts
+for more details on the presubmit API built into gcl.
+"""
+
+def GetPreferredTrySlaves():
+ return ['linux_chromeos_clang']
diff --git a/chromium/ash/accelerators/accelerator_controller.cc b/chromium/ash/accelerators/accelerator_controller.cc
new file mode 100644
index 00000000000..3d669bbdcce
--- /dev/null
+++ b/chromium/ash/accelerators/accelerator_controller.cc
@@ -0,0 +1,1006 @@
+// Copyright (c) 2012 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.
+
+#include "ash/accelerators/accelerator_controller.h"
+
+#include <algorithm>
+#include <cmath>
+#include <iostream>
+#include <string>
+
+#include "ash/accelerators/accelerator_table.h"
+#include "ash/ash_switches.h"
+#include "ash/caps_lock_delegate.h"
+#include "ash/debug.h"
+#include "ash/desktop_background/desktop_background_controller.h"
+#include "ash/desktop_background/user_wallpaper_delegate.h"
+#include "ash/display/display_controller.h"
+#include "ash/display/display_manager.h"
+#include "ash/focus_cycler.h"
+#include "ash/ime_control_delegate.h"
+#include "ash/launcher/launcher.h"
+#include "ash/launcher/launcher_delegate.h"
+#include "ash/launcher/launcher_model.h"
+#include "ash/magnifier/magnification_controller.h"
+#include "ash/magnifier/partial_magnification_controller.h"
+#include "ash/root_window_controller.h"
+#include "ash/rotator/screen_rotation.h"
+#include "ash/screenshot_delegate.h"
+#include "ash/session_state_delegate.h"
+#include "ash/shelf/shelf_widget.h"
+#include "ash/shell.h"
+#include "ash/shell_delegate.h"
+#include "ash/shell_window_ids.h"
+#include "ash/system/brightness/brightness_control_delegate.h"
+#include "ash/system/keyboard_brightness/keyboard_brightness_control_delegate.h"
+#include "ash/system/status_area_widget.h"
+#include "ash/system/tray/system_tray.h"
+#include "ash/system/tray/system_tray_delegate.h"
+#include "ash/system/tray/system_tray_notifier.h"
+#include "ash/system/web_notification/web_notification_tray.h"
+#include "ash/touch/touch_hud_debug.h"
+#include "ash/volume_control_delegate.h"
+#include "ash/wm/partial_screenshot_view.h"
+#include "ash/wm/power_button_controller.h"
+#include "ash/wm/property_util.h"
+#include "ash/wm/window_cycle_controller.h"
+#include "ash/wm/window_selector_controller.h"
+#include "ash/wm/window_util.h"
+#include "ash/wm/workspace/snap_sizer.h"
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "content/public/browser/gpu_data_manager.h"
+#include "ui/aura/env.h"
+#include "ui/aura/root_window.h"
+#include "ui/base/accelerators/accelerator.h"
+#include "ui/base/accelerators/accelerator_manager.h"
+#include "ui/base/events/event.h"
+#include "ui/base/keycodes/keyboard_codes.h"
+#include "ui/compositor/debug_utils.h"
+#include "ui/compositor/layer.h"
+#include "ui/compositor/layer_animation_sequence.h"
+#include "ui/compositor/layer_animator.h"
+#include "ui/gfx/screen.h"
+#include "ui/oak/oak.h"
+#include "ui/views/controls/webview/webview.h"
+#include "ui/views/debug_utils.h"
+#include "ui/views/widget/widget.h"
+
+#if defined(OS_CHROMEOS)
+#include "ash/system/chromeos/keyboard_brightness_controller.h"
+#include "base/chromeos/chromeos_version.h"
+#endif // defined(OS_CHROMEOS)
+
+namespace ash {
+namespace {
+
+using internal::DisplayInfo;
+
+bool DebugShortcutsEnabled() {
+#if defined(NDEBUG)
+ return CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kAshDebugShortcuts);
+#else
+ return true;
+#endif
+}
+
+bool HandleCycleWindowMRU(WindowCycleController::Direction direction,
+ bool is_alt_down) {
+ Shell::GetInstance()->
+ window_cycle_controller()->HandleCycleWindow(direction, is_alt_down);
+ // Always report we handled the key, even if the window didn't change.
+ return true;
+}
+
+bool HandleCycleWindowOverviewMRU(WindowSelector::Direction direction) {
+ Shell::GetInstance()->
+ window_selector_controller()->HandleCycleWindow(direction);
+ return true;
+}
+
+void HandleCycleWindowLinear(CycleDirection direction) {
+ Shell::GetInstance()->
+ window_cycle_controller()->HandleLinearCycleWindow();
+}
+
+void ToggleOverviewMode() {
+ Shell::GetInstance()->window_selector_controller()->ToggleOverview();
+}
+
+bool HandleAccessibleFocusCycle(bool reverse) {
+ if (!Shell::GetInstance()->delegate()->IsSpokenFeedbackEnabled())
+ return false;
+ aura::Window* active_window = ash::wm::GetActiveWindow();
+ if (!active_window)
+ return false;
+ views::Widget* widget =
+ views::Widget::GetWidgetForNativeWindow(active_window);
+ if (!widget)
+ return false;
+ views::FocusManager* focus_manager = widget->GetFocusManager();
+ if (!focus_manager)
+ return false;
+ views::View* view = focus_manager->GetFocusedView();
+ if (!view)
+ return false;
+ if (!strcmp(view->GetClassName(), views::WebView::kViewClassName))
+ return false;
+
+ focus_manager->AdvanceFocus(reverse);
+ return true;
+}
+
+void HandleSilenceSpokenFeedback() {
+ if (!Shell::GetInstance()->delegate()->IsSpokenFeedbackEnabled())
+ return;
+
+ Shell::GetInstance()->delegate()->SilenceSpokenFeedback();
+}
+
+#if defined(OS_CHROMEOS)
+bool HandleLock() {
+ Shell::GetInstance()->session_state_delegate()->LockScreen();
+ return true;
+}
+
+bool HandleFileManager(bool as_dialog) {
+ Shell::GetInstance()->delegate()->OpenFileManager(as_dialog);
+ return true;
+}
+
+bool HandleCrosh() {
+ Shell::GetInstance()->delegate()->OpenCrosh();
+ return true;
+}
+
+bool HandleToggleSpokenFeedback() {
+ Shell::GetInstance()->delegate()->
+ ToggleSpokenFeedback(A11Y_NOTIFICATION_SHOW);
+ return true;
+}
+
+#endif // defined(OS_CHROMEOS)
+
+bool HandleRotatePaneFocus(Shell::Direction direction) {
+ Shell* shell = Shell::GetInstance();
+ switch (direction) {
+ case Shell::FORWARD:
+ shell->focus_cycler()->RotateFocus(internal::FocusCycler::FORWARD);
+ break;
+ case Shell::BACKWARD:
+ shell->focus_cycler()->RotateFocus(internal::FocusCycler::BACKWARD);
+ break;
+ }
+ return true;
+}
+
+// Rotate the active window.
+bool HandleRotateActiveWindow() {
+ aura::Window* active_window = wm::GetActiveWindow();
+ if (active_window) {
+ // The rotation animation bases its target transform on the current
+ // rotation and position. Since there could be an animation in progress
+ // right now, queue this animation so when it starts it picks up a neutral
+ // rotation and position. Use replace so we only enqueue one at a time.
+ active_window->layer()->GetAnimator()->
+ set_preemption_strategy(ui::LayerAnimator::REPLACE_QUEUED_ANIMATIONS);
+ active_window->layer()->GetAnimator()->StartAnimation(
+ new ui::LayerAnimationSequence(
+ new ash::ScreenRotation(360, active_window->layer())));
+ }
+ return true;
+}
+
+gfx::Display::Rotation GetNextRotation(gfx::Display::Rotation current) {
+ switch (current) {
+ case gfx::Display::ROTATE_0:
+ return gfx::Display::ROTATE_90;
+ case gfx::Display::ROTATE_90:
+ return gfx::Display::ROTATE_180;
+ case gfx::Display::ROTATE_180:
+ return gfx::Display::ROTATE_270;
+ case gfx::Display::ROTATE_270:
+ return gfx::Display::ROTATE_0;
+ }
+ NOTREACHED() << "Unknown rotation:" << current;
+ return gfx::Display::ROTATE_0;
+}
+
+bool HandleScaleUI(bool up) {
+ internal::DisplayManager* display_manager =
+ Shell::GetInstance()->display_manager();
+ int64 display_id = display_manager->GetDisplayIdForUIScaling();
+ if (display_id == gfx::Display::kInvalidDisplayID)
+ return false;
+ const DisplayInfo& display_info = display_manager->GetDisplayInfo(display_id);
+ float next_scale =
+ internal::DisplayManager::GetNextUIScale(display_info, up);
+ display_manager->SetDisplayUIScale(display_id, next_scale);
+ return true;
+}
+
+bool HandleScaleReset() {
+ internal::DisplayManager* display_manager =
+ Shell::GetInstance()->display_manager();
+ int64 display_id = display_manager->GetDisplayIdForUIScaling();
+ if (display_id == gfx::Display::kInvalidDisplayID)
+ return false;
+ display_manager->SetDisplayUIScale(display_id, 1.0f);
+ return true;
+}
+
+// Rotates the screen.
+bool HandleRotateScreen() {
+ gfx::Point point = Shell::GetScreen()->GetCursorScreenPoint();
+ gfx::Display display = Shell::GetScreen()->GetDisplayNearestPoint(point);
+ const DisplayInfo& display_info =
+ Shell::GetInstance()->display_manager()->GetDisplayInfo(display.id());
+ Shell::GetInstance()->display_manager()->SetDisplayRotation(
+ display.id(), GetNextRotation(display_info.rotation()));
+ return true;
+}
+
+bool HandleToggleDesktopBackgroundMode() {
+ DesktopBackgroundController* desktop_background_controller =
+ Shell::GetInstance()->desktop_background_controller();
+ if (desktop_background_controller->desktop_background_mode() ==
+ DesktopBackgroundController::BACKGROUND_IMAGE) {
+ desktop_background_controller->SetDesktopBackgroundSolidColorMode(
+ SK_ColorBLACK);
+ } else {
+ ash::Shell::GetInstance()->user_wallpaper_delegate()->
+ InitializeWallpaper();
+ }
+ return true;
+}
+
+bool HandleToggleRootWindowFullScreen() {
+ Shell::GetPrimaryRootWindow()->ToggleFullScreen();
+ return true;
+}
+
+// Magnify the screen
+bool HandleMagnifyScreen(int delta_index) {
+ if (ash::Shell::GetInstance()->magnification_controller()->IsEnabled()) {
+ // TODO(yoshiki): Move the following logic to MagnificationController.
+ float scale =
+ ash::Shell::GetInstance()->magnification_controller()->GetScale();
+ // Calculate rounded logarithm (base kMagnificationScaleFactor) of scale.
+ int scale_index =
+ std::floor(std::log(scale) / std::log(kMagnificationScaleFactor) + 0.5);
+
+ int new_scale_index = std::max(0, std::min(8, scale_index + delta_index));
+
+ ash::Shell::GetInstance()->magnification_controller()->
+ SetScale(std::pow(kMagnificationScaleFactor, new_scale_index), true);
+ } else if (ash::Shell::GetInstance()->
+ partial_magnification_controller()->is_enabled()) {
+ float scale = delta_index > 0 ? kDefaultPartialMagnifiedScale : 1;
+ ash::Shell::GetInstance()->partial_magnification_controller()->
+ SetScale(scale);
+ }
+
+ return true;
+}
+
+bool HandleMediaNextTrack() {
+ Shell::GetInstance()->delegate()->HandleMediaNextTrack();
+ return true;
+}
+
+bool HandleMediaPlayPause() {
+ Shell::GetInstance()->delegate()->HandleMediaPlayPause();
+ return true;
+}
+
+bool HandleMediaPrevTrack() {
+ Shell::GetInstance()->delegate()->HandleMediaPrevTrack();
+ return true;
+}
+
+bool HandlePrintLayerHierarchy() {
+ Shell::RootWindowList root_windows = Shell::GetAllRootWindows();
+ for (size_t i = 0; i < root_windows.size(); ++i) {
+ ui::PrintLayerHierarchy(root_windows[i]->layer(),
+ root_windows[i]->GetLastMouseLocationInRoot());
+ }
+ return true;
+}
+
+bool HandlePrintViewHierarchy() {
+ aura::Window* active_window = ash::wm::GetActiveWindow();
+ if (!active_window)
+ return true;
+ views::Widget* browser_widget =
+ views::Widget::GetWidgetForNativeWindow(active_window);
+ if (!browser_widget)
+ return true;
+ views::PrintViewHierarchy(browser_widget->GetRootView());
+ return true;
+}
+
+void PrintWindowHierarchy(aura::Window* window,
+ int indent,
+ std::ostringstream* out) {
+ std::string indent_str(indent, ' ');
+ std::string name(window->name());
+ if (name.empty())
+ name = "\"\"";
+ *out << indent_str << name << " (" << window << ")"
+ << " type=" << window->type()
+ << (wm::IsActiveWindow(window) ? " [active] " : " ")
+ << (window->IsVisible() ? " visible " : " ")
+ << window->bounds().ToString()
+ << '\n';
+
+ for (size_t i = 0; i < window->children().size(); ++i)
+ PrintWindowHierarchy(window->children()[i], indent + 3, out);
+}
+
+bool HandlePrintWindowHierarchy() {
+ Shell::RootWindowControllerList controllers =
+ Shell::GetAllRootWindowControllers();
+ for (size_t i = 0; i < controllers.size(); ++i) {
+ std::ostringstream out;
+ out << "RootWindow " << i << ":\n";
+ PrintWindowHierarchy(controllers[i]->root_window(), 0, &out);
+ // Error so logs can be collected from end-users.
+ LOG(ERROR) << out.str();
+ }
+ return true;
+}
+
+bool HandlePrintUIHierarchies() {
+ // This is a separate command so the user only has to hit one key to generate
+ // all the logs. Developers use the individual dumps repeatedly, so keep
+ // those as separate commands to avoid spamming their logs.
+ HandlePrintLayerHierarchy();
+ HandlePrintWindowHierarchy();
+ HandlePrintViewHierarchy();
+ return true;
+}
+
+} // namespace
+
+////////////////////////////////////////////////////////////////////////////////
+// AcceleratorControllerContext, public:
+
+AcceleratorControllerContext::AcceleratorControllerContext() {
+ current_accelerator_.set_type(ui::ET_UNKNOWN);
+ previous_accelerator_.set_type(ui::ET_UNKNOWN);
+}
+
+void AcceleratorControllerContext::UpdateContext(
+ const ui::Accelerator& accelerator) {
+ previous_accelerator_ = current_accelerator_;
+ current_accelerator_ = accelerator;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// AcceleratorController, public:
+
+AcceleratorController::AcceleratorController()
+ : accelerator_manager_(new ui::AcceleratorManager) {
+ Init();
+}
+
+AcceleratorController::~AcceleratorController() {
+}
+
+void AcceleratorController::Init() {
+ for (size_t i = 0; i < kActionsAllowedAtLoginOrLockScreenLength; ++i) {
+ actions_allowed_at_login_screen_.insert(
+ kActionsAllowedAtLoginOrLockScreen[i]);
+ actions_allowed_at_lock_screen_.insert(
+ kActionsAllowedAtLoginOrLockScreen[i]);
+ }
+ for (size_t i = 0; i < kActionsAllowedAtLockScreenLength; ++i)
+ actions_allowed_at_lock_screen_.insert(kActionsAllowedAtLockScreen[i]);
+ for (size_t i = 0; i < kActionsAllowedAtModalWindowLength; ++i)
+ actions_allowed_at_modal_window_.insert(kActionsAllowedAtModalWindow[i]);
+ for (size_t i = 0; i < kReservedActionsLength; ++i)
+ reserved_actions_.insert(kReservedActions[i]);
+ for (size_t i = 0; i < kNonrepeatableActionsLength; ++i)
+ nonrepeatable_actions_.insert(kNonrepeatableActions[i]);
+ for (size_t i = 0; i < kActionsAllowedInAppModeLength; ++i)
+ actions_allowed_in_app_mode_.insert(kActionsAllowedInAppMode[i]);
+
+ RegisterAccelerators(kAcceleratorData, kAcceleratorDataLength);
+
+#if !defined(NDEBUG)
+ RegisterAccelerators(kDesktopAcceleratorData, kDesktopAcceleratorDataLength);
+#endif
+
+ if (DebugShortcutsEnabled()) {
+ RegisterAccelerators(kDebugAcceleratorData, kDebugAcceleratorDataLength);
+ for (size_t i = 0; i < kReservedDebugActionsLength; ++i)
+ reserved_actions_.insert(kReservedDebugActions[i]);
+ }
+
+#if defined(OS_CHROMEOS)
+ keyboard_brightness_control_delegate_.reset(
+ new KeyboardBrightnessController());
+#endif
+}
+
+void AcceleratorController::Register(const ui::Accelerator& accelerator,
+ ui::AcceleratorTarget* target) {
+ accelerator_manager_->Register(accelerator,
+ ui::AcceleratorManager::kNormalPriority,
+ target);
+}
+
+void AcceleratorController::Unregister(const ui::Accelerator& accelerator,
+ ui::AcceleratorTarget* target) {
+ accelerator_manager_->Unregister(accelerator, target);
+}
+
+void AcceleratorController::UnregisterAll(ui::AcceleratorTarget* target) {
+ accelerator_manager_->UnregisterAll(target);
+}
+
+bool AcceleratorController::Process(const ui::Accelerator& accelerator) {
+ if (ime_control_delegate_) {
+ return accelerator_manager_->Process(
+ ime_control_delegate_->RemapAccelerator(accelerator));
+ }
+ return accelerator_manager_->Process(accelerator);
+}
+
+bool AcceleratorController::IsRegistered(
+ const ui::Accelerator& accelerator) const {
+ return accelerator_manager_->GetCurrentTarget(accelerator) != NULL;
+}
+
+bool AcceleratorController::IsReservedAccelerator(
+ const ui::Accelerator& accelerator) const {
+ const ui::Accelerator remapped_accelerator = ime_control_delegate_.get() ?
+ ime_control_delegate_->RemapAccelerator(accelerator) : accelerator;
+
+ std::map<ui::Accelerator, int>::const_iterator iter =
+ accelerators_.find(remapped_accelerator);
+ if (iter == accelerators_.end())
+ return false; // not an accelerator.
+
+ return reserved_actions_.find(iter->second) != reserved_actions_.end();
+}
+
+bool AcceleratorController::PerformAction(int action,
+ const ui::Accelerator& accelerator) {
+ ash::Shell* shell = ash::Shell::GetInstance();
+ if (!shell->session_state_delegate()->IsActiveUserSessionStarted() &&
+ actions_allowed_at_login_screen_.find(action) ==
+ actions_allowed_at_login_screen_.end()) {
+ return false;
+ }
+ if (shell->session_state_delegate()->IsScreenLocked() &&
+ actions_allowed_at_lock_screen_.find(action) ==
+ actions_allowed_at_lock_screen_.end()) {
+ return false;
+ }
+ if (shell->IsSystemModalWindowOpen() &&
+ actions_allowed_at_modal_window_.find(action) ==
+ actions_allowed_at_modal_window_.end()) {
+ // Note: we return true. This indicates the shortcut is handled
+ // and will not be passed to the modal window. This is important
+ // for things like Alt+Tab that would cause an undesired effect
+ // in the modal window by cycling through its window elements.
+ return true;
+ }
+ if (shell->delegate()->IsRunningInForcedAppMode() &&
+ actions_allowed_in_app_mode_.find(action) ==
+ actions_allowed_in_app_mode_.end()) {
+ return false;
+ }
+
+ const ui::KeyboardCode key_code = accelerator.key_code();
+ // PerformAction() is performed from gesture controllers and passes
+ // empty Accelerator() instance as the second argument. Such events
+ // should never be suspended.
+ const bool gesture_event = key_code == ui::VKEY_UNKNOWN;
+
+ // Ignore accelerators invoked as repeated (while holding a key for a long
+ // time, if their handling is nonrepeatable.
+ if (nonrepeatable_actions_.find(action) != nonrepeatable_actions_.end() &&
+ context_.repeated() && !gesture_event) {
+ return true;
+ }
+ // Type of the previous accelerator. Used by NEXT_IME and DISABLE_CAPS_LOCK.
+ const ui::EventType previous_event_type =
+ context_.previous_accelerator().type();
+ const ui::KeyboardCode previous_key_code =
+ context_.previous_accelerator().key_code();
+
+ // You *MUST* return true when some action is performed. Otherwise, this
+ // function might be called *twice*, via BrowserView::PreHandleKeyboardEvent
+ // and BrowserView::HandleKeyboardEvent, for a single accelerator press.
+ switch (action) {
+ case ACCESSIBLE_FOCUS_NEXT:
+ return HandleAccessibleFocusCycle(false);
+ case ACCESSIBLE_FOCUS_PREVIOUS:
+ return HandleAccessibleFocusCycle(true);
+ case CYCLE_BACKWARD_MRU:
+ if (key_code == ui::VKEY_TAB)
+ shell->delegate()->RecordUserMetricsAction(UMA_ACCEL_PREVWINDOW_TAB);
+ if (CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kAshEnableOverviewMode)) {
+ return HandleCycleWindowOverviewMRU(WindowSelector::BACKWARD);
+ }
+ return HandleCycleWindowMRU(WindowCycleController::BACKWARD,
+ accelerator.IsAltDown());
+ case CYCLE_FORWARD_MRU:
+ if (key_code == ui::VKEY_TAB)
+ shell->delegate()->RecordUserMetricsAction(UMA_ACCEL_NEXTWINDOW_TAB);
+ if (CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kAshEnableOverviewMode)) {
+ return HandleCycleWindowOverviewMRU(WindowSelector::FORWARD);
+ }
+ return HandleCycleWindowMRU(WindowCycleController::FORWARD,
+ accelerator.IsAltDown());
+ case CYCLE_BACKWARD_LINEAR:
+ if (CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kAshEnableOverviewMode)) {
+ shell->delegate()->RecordUserMetricsAction(UMA_ACCEL_OVERVIEW_F5);
+ ToggleOverviewMode();
+ return true;
+ }
+ if (key_code == ui::VKEY_MEDIA_LAUNCH_APP1)
+ shell->delegate()->RecordUserMetricsAction(UMA_ACCEL_PREVWINDOW_F5);
+ HandleCycleWindowLinear(CYCLE_BACKWARD);
+ return true;
+ case CYCLE_FORWARD_LINEAR:
+ if (CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kAshEnableOverviewMode)) {
+ shell->delegate()->RecordUserMetricsAction(UMA_ACCEL_OVERVIEW_F5);
+ ToggleOverviewMode();
+ return true;
+ }
+ if (key_code == ui::VKEY_MEDIA_LAUNCH_APP1)
+ shell->delegate()->RecordUserMetricsAction(UMA_ACCEL_NEXTWINDOW_F5);
+ HandleCycleWindowLinear(CYCLE_FORWARD);
+ return true;
+#if defined(OS_CHROMEOS)
+ case ADD_REMOVE_DISPLAY:
+ Shell::GetInstance()->display_manager()->AddRemoveDisplay();
+ return true;
+ case TOGGLE_MIRROR_MODE:
+ Shell::GetInstance()->display_controller()->ToggleMirrorMode();
+ return true;
+ case LOCK_SCREEN:
+ if (key_code == ui::VKEY_L)
+ shell->delegate()->RecordUserMetricsAction(UMA_ACCEL_LOCK_SCREEN_L);
+ return HandleLock();
+ case OPEN_FILE_DIALOG:
+ return HandleFileManager(true /* as_dialog */);
+ case OPEN_FILE_MANAGER:
+ return HandleFileManager(false /* as_dialog */);
+ case OPEN_CROSH:
+ return HandleCrosh();
+ case SILENCE_SPOKEN_FEEDBACK:
+ HandleSilenceSpokenFeedback();
+ break;
+ case SWAP_PRIMARY_DISPLAY:
+ Shell::GetInstance()->display_controller()->SwapPrimaryDisplay();
+ return true;
+ case TOGGLE_SPOKEN_FEEDBACK:
+ return HandleToggleSpokenFeedback();
+ case TOGGLE_WIFI:
+ Shell::GetInstance()->system_tray_notifier()->NotifyRequestToggleWifi();
+ return true;
+ case TOUCH_HUD_CLEAR: {
+ internal::RootWindowController* controller =
+ internal::RootWindowController::ForActiveRootWindow();
+ if (controller->touch_hud_debug()) {
+ controller->touch_hud_debug()->Clear();
+ return true;
+ }
+ return false;
+ }
+ case TOUCH_HUD_MODE_CHANGE: {
+ internal::RootWindowController* controller =
+ internal::RootWindowController::ForActiveRootWindow();
+ if (controller->touch_hud_debug()) {
+ controller->touch_hud_debug()->ChangeToNextMode();
+ return true;
+ }
+ return false;
+ }
+ case TOUCH_HUD_PROJECTION_TOGGLE: {
+ bool enabled = Shell::GetInstance()->is_touch_hud_projection_enabled();
+ Shell::GetInstance()->SetTouchHudProjectionEnabled(!enabled);
+ return true;
+ }
+ case DISABLE_GPU_WATCHDOG:
+ content::GpuDataManager::GetInstance()->DisableGpuWatchdog();
+ return true;
+#endif
+ case OPEN_FEEDBACK_PAGE:
+ ash::Shell::GetInstance()->delegate()->OpenFeedbackPage();
+ return true;
+ case EXIT:
+ // UMA metrics are recorded in the handler.
+ exit_warning_handler_.HandleAccelerator();
+ return true;
+ case NEW_INCOGNITO_WINDOW:
+ Shell::GetInstance()->delegate()->NewWindow(true /* is_incognito */);
+ return true;
+ case NEW_TAB:
+ if (key_code == ui::VKEY_T)
+ shell->delegate()->RecordUserMetricsAction(UMA_ACCEL_NEWTAB_T);
+ Shell::GetInstance()->delegate()->NewTab();
+ return true;
+ case NEW_WINDOW:
+ Shell::GetInstance()->delegate()->NewWindow(false /* is_incognito */);
+ return true;
+ case RESTORE_TAB:
+ Shell::GetInstance()->delegate()->RestoreTab();
+ return true;
+ case TAKE_SCREENSHOT:
+ if (screenshot_delegate_.get() &&
+ screenshot_delegate_->CanTakeScreenshot()) {
+ screenshot_delegate_->HandleTakeScreenshotForAllRootWindows();
+ }
+ // Return true to prevent propagation of the key event.
+ return true;
+ case TAKE_PARTIAL_SCREENSHOT:
+ if (screenshot_delegate_) {
+ ash::PartialScreenshotView::StartPartialScreenshot(
+ screenshot_delegate_.get());
+ }
+ // Return true to prevent propagation of the key event because
+ // this key combination is reserved for partial screenshot.
+ return true;
+ case TOGGLE_APP_LIST:
+ // If something else was pressed between the Search key (LWIN)
+ // being pressed and released, then ignore the release of the
+ // Search key.
+ if (key_code == ui::VKEY_LWIN &&
+ (previous_event_type == ui::ET_KEY_RELEASED ||
+ previous_key_code != ui::VKEY_LWIN))
+ return false;
+ if (key_code == ui::VKEY_LWIN)
+ shell->delegate()->RecordUserMetricsAction(UMA_ACCEL_SEARCH_LWIN);
+ // When spoken feedback is enabled, we should neither toggle the list nor
+ // consume the key since Search+Shift is one of the shortcuts the a11y
+ // feature uses. crbug.com/132296
+ DCHECK_EQ(ui::VKEY_LWIN, accelerator.key_code());
+ if (Shell::GetInstance()->delegate()->IsSpokenFeedbackEnabled())
+ return false;
+ ash::Shell::GetInstance()->ToggleAppList(NULL);
+ return true;
+ case DISABLE_CAPS_LOCK:
+ if (previous_event_type == ui::ET_KEY_RELEASED ||
+ (previous_key_code != ui::VKEY_LSHIFT &&
+ previous_key_code != ui::VKEY_SHIFT &&
+ previous_key_code != ui::VKEY_RSHIFT)) {
+ // If something else was pressed between the Shift key being pressed
+ // and released, then ignore the release of the Shift key.
+ return false;
+ }
+ if (shell->caps_lock_delegate()->IsCapsLockEnabled()) {
+ shell->caps_lock_delegate()->SetCapsLockEnabled(false);
+ return true;
+ }
+ return false;
+ case TOGGLE_CAPS_LOCK:
+ if (key_code == ui::VKEY_LWIN) {
+ // If something else was pressed between the Search key (LWIN)
+ // being pressed and released, then ignore the release of the
+ // Search key.
+ // TODO(danakj): Releasing Alt first breaks this: crbug.com/166495
+ if (previous_event_type == ui::ET_KEY_RELEASED ||
+ previous_key_code != ui::VKEY_LWIN)
+ return false;
+ }
+ shell->caps_lock_delegate()->ToggleCapsLock();
+ return true;
+ case BRIGHTNESS_DOWN:
+ if (brightness_control_delegate_)
+ return brightness_control_delegate_->HandleBrightnessDown(accelerator);
+ break;
+ case BRIGHTNESS_UP:
+ if (brightness_control_delegate_)
+ return brightness_control_delegate_->HandleBrightnessUp(accelerator);
+ break;
+ case KEYBOARD_BRIGHTNESS_DOWN:
+ if (keyboard_brightness_control_delegate_)
+ return keyboard_brightness_control_delegate_->
+ HandleKeyboardBrightnessDown(accelerator);
+ break;
+ case KEYBOARD_BRIGHTNESS_UP:
+ if (keyboard_brightness_control_delegate_)
+ return keyboard_brightness_control_delegate_->
+ HandleKeyboardBrightnessUp(accelerator);
+ break;
+ case VOLUME_MUTE:
+ return shell->system_tray_delegate()->GetVolumeControlDelegate()->
+ HandleVolumeMute(accelerator);
+ break;
+ case VOLUME_DOWN:
+ return shell->system_tray_delegate()->GetVolumeControlDelegate()->
+ HandleVolumeDown(accelerator);
+ break;
+ case VOLUME_UP:
+ return shell->system_tray_delegate()->GetVolumeControlDelegate()->
+ HandleVolumeUp(accelerator);
+ break;
+ case FOCUS_LAUNCHER:
+ return shell->focus_cycler()->FocusWidget(
+ Launcher::ForPrimaryDisplay()->shelf_widget());
+ break;
+ case FOCUS_NEXT_PANE:
+ return HandleRotatePaneFocus(Shell::FORWARD);
+ case FOCUS_PREVIOUS_PANE:
+ return HandleRotatePaneFocus(Shell::BACKWARD);
+ case SHOW_KEYBOARD_OVERLAY:
+ ash::Shell::GetInstance()->delegate()->ShowKeyboardOverlay();
+ return true;
+ case SHOW_OAK:
+ if (CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kAshEnableOak)) {
+ oak::ShowOakWindowWithContext(Shell::GetPrimaryRootWindow());
+ return true;
+ }
+ break;
+ case SHOW_SYSTEM_TRAY_BUBBLE: {
+ internal::RootWindowController* controller =
+ internal::RootWindowController::ForActiveRootWindow();
+ if (!controller->GetSystemTray()->HasSystemBubble())
+ controller->GetSystemTray()->ShowDefaultView(BUBBLE_CREATE_NEW);
+ break;
+ }
+ case SHOW_MESSAGE_CENTER_BUBBLE: {
+ internal::RootWindowController* controller =
+ internal::RootWindowController::ForActiveRootWindow();
+ internal::StatusAreaWidget* status_area_widget =
+ controller->shelf()->status_area_widget();
+ if (status_area_widget) {
+ WebNotificationTray* notification_tray =
+ status_area_widget->web_notification_tray();
+ if (notification_tray->visible())
+ notification_tray->ShowMessageCenterBubble();
+ }
+ break;
+ }
+ case SHOW_TASK_MANAGER:
+ Shell::GetInstance()->delegate()->ShowTaskManager();
+ return true;
+ case NEXT_IME:
+ // This check is necessary e.g. not to process the Shift+Alt+
+ // ET_KEY_RELEASED accelerator for Chrome OS (see ash/accelerators/
+ // accelerator_controller.cc) when Shift+Alt+Tab is pressed and then Tab
+ // is released.
+ if (previous_event_type == ui::ET_KEY_RELEASED &&
+ // Workaround for crbug.com/139556. CJK IME users tend to press
+ // Enter (or Space) and Shift+Alt almost at the same time to commit
+ // an IME string and then switch from the IME to the English layout.
+ // This workaround allows the user to trigger NEXT_IME even if the
+ // user presses Shift+Alt before releasing Enter.
+ // TODO(nona|mazda): Fix crbug.com/139556 in a cleaner way.
+ previous_key_code != ui::VKEY_RETURN &&
+ previous_key_code != ui::VKEY_SPACE) {
+ // We totally ignore this accelerator.
+ // TODO(mazda): Fix crbug.com/158217
+ return false;
+ }
+ if (ime_control_delegate_)
+ return ime_control_delegate_->HandleNextIme();
+ break;
+ case PREVIOUS_IME:
+ if (ime_control_delegate_)
+ return ime_control_delegate_->HandlePreviousIme(accelerator);
+ break;
+ case PRINT_UI_HIERARCHIES:
+ return HandlePrintUIHierarchies();
+ case SWITCH_IME:
+ if (ime_control_delegate_)
+ return ime_control_delegate_->HandleSwitchIme(accelerator);
+ break;
+ case LAUNCH_APP_0:
+ Launcher::ForPrimaryDisplay()->LaunchAppIndexAt(0);
+ return true;
+ case LAUNCH_APP_1:
+ Launcher::ForPrimaryDisplay()->LaunchAppIndexAt(1);
+ return true;
+ case LAUNCH_APP_2:
+ Launcher::ForPrimaryDisplay()->LaunchAppIndexAt(2);
+ return true;
+ case LAUNCH_APP_3:
+ Launcher::ForPrimaryDisplay()->LaunchAppIndexAt(3);
+ return true;
+ case LAUNCH_APP_4:
+ Launcher::ForPrimaryDisplay()->LaunchAppIndexAt(4);
+ return true;
+ case LAUNCH_APP_5:
+ Launcher::ForPrimaryDisplay()->LaunchAppIndexAt(5);
+ return true;
+ case LAUNCH_APP_6:
+ Launcher::ForPrimaryDisplay()->LaunchAppIndexAt(6);
+ return true;
+ case LAUNCH_APP_7:
+ Launcher::ForPrimaryDisplay()->LaunchAppIndexAt(7);
+ return true;
+ case LAUNCH_LAST_APP:
+ Launcher::ForPrimaryDisplay()->LaunchAppIndexAt(-1);
+ return true;
+ case WINDOW_SNAP_LEFT:
+ case WINDOW_SNAP_RIGHT: {
+ aura::Window* window = wm::GetActiveWindow();
+ // Disable window docking shortcut key for full screen window due to
+ // http://crbug.com/135487.
+ if (!window ||
+ window->type() != aura::client::WINDOW_TYPE_NORMAL ||
+ wm::IsWindowFullscreen(window)) {
+ break;
+ }
+
+ internal::SnapSizer::SnapWindow(window,
+ action == WINDOW_SNAP_LEFT ? internal::SnapSizer::LEFT_EDGE :
+ internal::SnapSizer::RIGHT_EDGE);
+ return true;
+ }
+ case WINDOW_MINIMIZE: {
+ aura::Window* window = wm::GetActiveWindow();
+ // Attempt to restore the window that would be cycled through next from
+ // the launcher when there is no active window.
+ if (!window)
+ return HandleCycleWindowMRU(WindowCycleController::FORWARD, false);
+ // Disable the shortcut for minimizing full screen window due to
+ // crbug.com/131709, which is a crashing issue related to minimizing
+ // full screen pepper window.
+ if (!wm::IsWindowFullscreen(window) && wm::CanMinimizeWindow(window)) {
+ ash::Shell::GetInstance()->delegate()->RecordUserMetricsAction(
+ ash::UMA_MINIMIZE_PER_KEY);
+ wm::MinimizeWindow(window);
+ return true;
+ }
+ break;
+ }
+ case TOGGLE_FULLSCREEN: {
+ if (key_code == ui::VKEY_MEDIA_LAUNCH_APP2) {
+ shell->delegate()->RecordUserMetricsAction(
+ UMA_ACCEL_FULLSCREEN_F4);
+ }
+ shell->delegate()->ToggleFullscreen();
+ return true;
+ }
+ case TOGGLE_MAXIMIZED: {
+ shell->delegate()->ToggleMaximized();
+ return true;
+ }
+ case WINDOW_POSITION_CENTER: {
+ aura::Window* window = wm::GetActiveWindow();
+ if (window) {
+ wm::CenterWindow(window);
+ return true;
+ }
+ break;
+ }
+ case SCALE_UI_UP:
+ return HandleScaleUI(true /* up */);
+ case SCALE_UI_DOWN:
+ return HandleScaleUI(false /* down */);
+ case SCALE_UI_RESET:
+ return HandleScaleReset();
+ case ROTATE_WINDOW:
+ return HandleRotateActiveWindow();
+ case ROTATE_SCREEN:
+ return HandleRotateScreen();
+ case TOGGLE_DESKTOP_BACKGROUND_MODE:
+ return HandleToggleDesktopBackgroundMode();
+ case TOGGLE_ROOT_WINDOW_FULL_SCREEN:
+ return HandleToggleRootWindowFullScreen();
+ case DEBUG_TOGGLE_DEVICE_SCALE_FACTOR:
+ Shell::GetInstance()->display_manager()->ToggleDisplayScaleFactor();
+ return true;
+ case DEBUG_TOGGLE_SHOW_DEBUG_BORDERS:
+ ash::debug::ToggleShowDebugBorders();
+ return true;
+ case DEBUG_TOGGLE_SHOW_FPS_COUNTER:
+ ash::debug::ToggleShowFpsCounter();
+ return true;
+ case DEBUG_TOGGLE_SHOW_PAINT_RECTS:
+ ash::debug::ToggleShowPaintRects();
+ return true;
+ case MAGNIFY_SCREEN_ZOOM_IN:
+ return HandleMagnifyScreen(1);
+ case MAGNIFY_SCREEN_ZOOM_OUT:
+ return HandleMagnifyScreen(-1);
+ case MEDIA_NEXT_TRACK:
+ return HandleMediaNextTrack();
+ case MEDIA_PLAY_PAUSE:
+ return HandleMediaPlayPause();
+ case MEDIA_PREV_TRACK:
+ return HandleMediaPrevTrack();
+ case POWER_PRESSED: // fallthrough
+ case POWER_RELEASED:
+#if defined(OS_CHROMEOS)
+ if (!base::chromeos::IsRunningOnChromeOS()) {
+ // There is no powerd in linux desktop, so call the
+ // PowerButtonController here.
+ Shell::GetInstance()->power_button_controller()->
+ OnPowerButtonEvent(action == POWER_PRESSED, base::TimeTicks());
+ }
+#endif
+ // We don't do anything with these at present on the device,
+ // (power button events are reported to us from powerm via
+ // D-BUS), but we consume them to prevent them from getting
+ // passed to apps -- see http://crbug.com/146609.
+ return true;
+ case LOCK_PRESSED:
+ case LOCK_RELEASED:
+ Shell::GetInstance()->power_button_controller()->
+ OnLockButtonEvent(action == LOCK_PRESSED, base::TimeTicks());
+ return true;
+ case PRINT_LAYER_HIERARCHY:
+ return HandlePrintLayerHierarchy();
+ case PRINT_VIEW_HIERARCHY:
+ return HandlePrintViewHierarchy();
+ case PRINT_WINDOW_HIERARCHY:
+ return HandlePrintWindowHierarchy();
+ default:
+ NOTREACHED() << "Unhandled action " << action;
+ }
+ return false;
+}
+
+void AcceleratorController::SetBrightnessControlDelegate(
+ scoped_ptr<BrightnessControlDelegate> brightness_control_delegate) {
+ // Install brightness control delegate only when internal
+ // display exists.
+ if (Shell::GetInstance()->display_manager()->HasInternalDisplay() ||
+ CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kAshEnableBrightnessControl)) {
+ brightness_control_delegate_ = brightness_control_delegate.Pass();
+ }
+}
+
+void AcceleratorController::SetImeControlDelegate(
+ scoped_ptr<ImeControlDelegate> ime_control_delegate) {
+ ime_control_delegate_ = ime_control_delegate.Pass();
+}
+
+void AcceleratorController::SetScreenshotDelegate(
+ scoped_ptr<ScreenshotDelegate> screenshot_delegate) {
+ screenshot_delegate_ = screenshot_delegate.Pass();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// AcceleratorController, ui::AcceleratorTarget implementation:
+
+bool AcceleratorController::AcceleratorPressed(
+ const ui::Accelerator& accelerator) {
+ std::map<ui::Accelerator, int>::const_iterator it =
+ accelerators_.find(accelerator);
+ DCHECK(it != accelerators_.end());
+ return PerformAction(static_cast<AcceleratorAction>(it->second), accelerator);
+}
+
+void AcceleratorController::RegisterAccelerators(
+ const AcceleratorData accelerators[],
+ size_t accelerators_length) {
+ for (size_t i = 0; i < accelerators_length; ++i) {
+ ui::Accelerator accelerator(accelerators[i].keycode,
+ accelerators[i].modifiers);
+ accelerator.set_type(accelerators[i].trigger_on_press ?
+ ui::ET_KEY_PRESSED : ui::ET_KEY_RELEASED);
+ Register(accelerator, this);
+ accelerators_.insert(
+ std::make_pair(accelerator, accelerators[i].action));
+ }
+}
+
+void AcceleratorController::SetKeyboardBrightnessControlDelegate(
+ scoped_ptr<KeyboardBrightnessControlDelegate>
+ keyboard_brightness_control_delegate) {
+ keyboard_brightness_control_delegate_ =
+ keyboard_brightness_control_delegate.Pass();
+}
+
+bool AcceleratorController::CanHandleAccelerators() const {
+ return true;
+}
+
+} // namespace ash
diff --git a/chromium/ash/accelerators/accelerator_controller.h b/chromium/ash/accelerators/accelerator_controller.h
new file mode 100644
index 00000000000..cacba0e0b21
--- /dev/null
+++ b/chromium/ash/accelerators/accelerator_controller.h
@@ -0,0 +1,178 @@
+// Copyright (c) 2012 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 ASH_ACCELERATORS_ACCELERATOR_CONTROLLER_H_
+#define ASH_ACCELERATORS_ACCELERATOR_CONTROLLER_H_
+
+#include <map>
+#include <set>
+
+#include "ash/accelerators/exit_warning_handler.h"
+#include "ash/ash_export.h"
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/gtest_prod_util.h"
+#include "base/memory/scoped_ptr.h"
+#include "ui/base/accelerators/accelerator.h"
+
+namespace ui {
+class AcceleratorManager;
+}
+
+namespace ash {
+
+struct AcceleratorData;
+class BrightnessControlDelegate;
+class ExitWarningHandler;
+class ImeControlDelegate;
+class KeyboardBrightnessControlDelegate;
+class ScreenshotDelegate;
+class VolumeControlDelegate;
+
+// Stores information about accelerator context, eg. previous accelerator
+// or if the current accelerator is repeated or not.
+class ASH_EXPORT AcceleratorControllerContext {
+ public:
+ AcceleratorControllerContext();
+ ~AcceleratorControllerContext() {}
+
+ // Updates context - determines if the accelerator is repeated, as well as
+ // event type of the previous accelerator.
+ void UpdateContext(const ui::Accelerator& accelerator);
+
+ const ui::Accelerator& previous_accelerator() const {
+ return previous_accelerator_;
+ }
+ bool repeated() const {
+ return current_accelerator_ == previous_accelerator_ &&
+ current_accelerator_.type() != ui::ET_UNKNOWN;
+ }
+
+ private:
+ ui::Accelerator current_accelerator_;
+ // Used for NEXT_IME and DISABLE_CAPS_LOCK accelerator actions.
+ ui::Accelerator previous_accelerator_;
+
+ DISALLOW_COPY_AND_ASSIGN(AcceleratorControllerContext);
+};
+
+// AcceleratorController provides functions for registering or unregistering
+// global keyboard accelerators, which are handled earlier than any windows. It
+// also implements several handlers as an accelerator target.
+class ASH_EXPORT AcceleratorController : public ui::AcceleratorTarget {
+ public:
+ AcceleratorController();
+ virtual ~AcceleratorController();
+
+ // Registers a global keyboard accelerator for the specified target. If
+ // multiple targets are registered for an accelerator, a target registered
+ // later has higher priority.
+ void Register(const ui::Accelerator& accelerator,
+ ui::AcceleratorTarget* target);
+
+ // Unregisters the specified keyboard accelerator for the specified target.
+ void Unregister(const ui::Accelerator& accelerator,
+ ui::AcceleratorTarget* target);
+
+ // Unregisters all keyboard accelerators for the specified target.
+ void UnregisterAll(ui::AcceleratorTarget* target);
+
+ // Activates the target associated with the specified accelerator.
+ // First, AcceleratorPressed handler of the most recently registered target
+ // is called, and if that handler processes the event (i.e. returns true),
+ // this method immediately returns. If not, we do the same thing on the next
+ // target, and so on.
+ // Returns true if an accelerator was activated.
+ bool Process(const ui::Accelerator& accelerator);
+
+ // Returns true if the |accelerator| is registered.
+ bool IsRegistered(const ui::Accelerator& accelerator) const;
+
+ // Returns true if the |accelerator| is one of the |reserved_actions_|.
+ bool IsReservedAccelerator(const ui::Accelerator& accelerator) const;
+
+ // Performs the specified action. The |accelerator| may provide additional
+ // data the action needs. Returns whether an action was performed
+ // successfully.
+ bool PerformAction(int action,
+ const ui::Accelerator& accelerator);
+
+ // Overridden from ui::AcceleratorTarget:
+ virtual bool AcceleratorPressed(const ui::Accelerator& accelerator) OVERRIDE;
+ virtual bool CanHandleAccelerators() const OVERRIDE;
+
+ void SetBrightnessControlDelegate(
+ scoped_ptr<BrightnessControlDelegate> brightness_control_delegate);
+ void SetImeControlDelegate(
+ scoped_ptr<ImeControlDelegate> ime_control_delegate);
+ void SetScreenshotDelegate(
+ scoped_ptr<ScreenshotDelegate> screenshot_delegate);
+ BrightnessControlDelegate* brightness_control_delegate() const {
+ return brightness_control_delegate_.get();
+ }
+
+ // Provides access to an object holding contextual information.
+ AcceleratorControllerContext* context() {
+ return &context_;
+ }
+
+ // Provides access to the ExitWarningHandler for testing.
+ ExitWarningHandler* GetExitWarningHandlerForTest() {
+ return &exit_warning_handler_;
+ }
+
+ private:
+ FRIEND_TEST_ALL_PREFIXES(AcceleratorControllerTest, GlobalAccelerators);
+
+ // Initializes the accelerators this class handles as a target.
+ void Init();
+
+ // Registers the specified accelerators.
+ void RegisterAccelerators(const AcceleratorData accelerators[],
+ size_t accelerators_length);
+
+ void SetKeyboardBrightnessControlDelegate(
+ scoped_ptr<KeyboardBrightnessControlDelegate>
+ keyboard_brightness_control_delegate);
+
+ scoped_ptr<ui::AcceleratorManager> accelerator_manager_;
+
+ // TODO(derat): BrightnessControlDelegate is also used by the system tray;
+ // move it outside of this class.
+ scoped_ptr<BrightnessControlDelegate> brightness_control_delegate_;
+ scoped_ptr<ImeControlDelegate> ime_control_delegate_;
+ scoped_ptr<KeyboardBrightnessControlDelegate>
+ keyboard_brightness_control_delegate_;
+ scoped_ptr<ScreenshotDelegate> screenshot_delegate_;
+
+ // Contextual information, eg. if the current accelerator is repeated.
+ AcceleratorControllerContext context_;
+
+ // Handles the exit accelerator which requires a double press to exit and
+ // shows a popup with an explanation.
+ ExitWarningHandler exit_warning_handler_;
+
+ // A map from accelerators to the AcceleratorAction values, which are used in
+ // the implementation.
+ std::map<ui::Accelerator, int> accelerators_;
+
+ // Actions allowed when the user is not signed in.
+ std::set<int> actions_allowed_at_login_screen_;
+ // Actions allowed when the screen is locked.
+ std::set<int> actions_allowed_at_lock_screen_;
+ // Actions allowed when a modal window is up.
+ std::set<int> actions_allowed_at_modal_window_;
+ // Reserved actions. See accelerator_table.h for details.
+ std::set<int> reserved_actions_;
+ // Actions which will not be repeated while holding the accelerator key.
+ std::set<int> nonrepeatable_actions_;
+ // Actions allowed in app mode.
+ std::set<int> actions_allowed_in_app_mode_;
+
+ DISALLOW_COPY_AND_ASSIGN(AcceleratorController);
+};
+
+} // namespace ash
+
+#endif // ASH_ACCELERATORS_ACCELERATOR_CONTROLLER_H_
diff --git a/chromium/ash/accelerators/accelerator_controller_unittest.cc b/chromium/ash/accelerators/accelerator_controller_unittest.cc
new file mode 100644
index 00000000000..4c00f727341
--- /dev/null
+++ b/chromium/ash/accelerators/accelerator_controller_unittest.cc
@@ -0,0 +1,1343 @@
+// Copyright (c) 2012 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.
+
+#include "ash/accelerators/accelerator_controller.h"
+#include "ash/accelerators/accelerator_table.h"
+#include "ash/caps_lock_delegate.h"
+#include "ash/display/display_manager.h"
+#include "ash/ime_control_delegate.h"
+#include "ash/screenshot_delegate.h"
+#include "ash/shell.h"
+#include "ash/shell_window_ids.h"
+#include "ash/system/brightness/brightness_control_delegate.h"
+#include "ash/system/keyboard_brightness/keyboard_brightness_control_delegate.h"
+#include "ash/system/tray/system_tray_delegate.h"
+#include "ash/test/ash_test_base.h"
+#include "ash/test/display_manager_test_api.h"
+#include "ash/test/test_shell_delegate.h"
+#include "ash/volume_control_delegate.h"
+#include "ash/wm/window_util.h"
+#include "ui/aura/root_window.h"
+#include "ui/aura/test/test_window_delegate.h"
+#include "ui/aura/test/test_windows.h"
+#include "ui/aura/window.h"
+#include "ui/base/events/event.h"
+
+#if defined(USE_X11)
+#include <X11/Xlib.h>
+#include "ui/base/x/x11_util.h"
+#endif
+
+namespace ash {
+
+namespace {
+
+class TestTarget : public ui::AcceleratorTarget {
+ public:
+ TestTarget() : accelerator_pressed_count_(0) {}
+ virtual ~TestTarget() {}
+
+ int accelerator_pressed_count() const {
+ return accelerator_pressed_count_;
+ }
+
+ void set_accelerator_pressed_count(int accelerator_pressed_count) {
+ accelerator_pressed_count_ = accelerator_pressed_count;
+ }
+
+ // Overridden from ui::AcceleratorTarget:
+ virtual bool AcceleratorPressed(const ui::Accelerator& accelerator) OVERRIDE;
+ virtual bool CanHandleAccelerators() const OVERRIDE;
+
+ private:
+ int accelerator_pressed_count_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestTarget);
+};
+
+class ReleaseAccelerator : public ui::Accelerator {
+ public:
+ ReleaseAccelerator(ui::KeyboardCode keycode, int modifiers)
+ : ui::Accelerator(keycode, modifiers) {
+ set_type(ui::ET_KEY_RELEASED);
+ }
+};
+
+class DummyScreenshotDelegate : public ScreenshotDelegate {
+ public:
+ DummyScreenshotDelegate()
+ : handle_take_screenshot_count_(0) {
+ }
+ virtual ~DummyScreenshotDelegate() {}
+
+ // Overridden from ScreenshotDelegate:
+ virtual void HandleTakeScreenshotForAllRootWindows() OVERRIDE {
+ ++handle_take_screenshot_count_;
+ }
+
+ virtual void HandleTakePartialScreenshot(
+ aura::Window* window, const gfx::Rect& rect) OVERRIDE {
+ }
+
+ virtual bool CanTakeScreenshot() OVERRIDE {
+ return true;
+ }
+
+ int handle_take_screenshot_count() const {
+ return handle_take_screenshot_count_;
+ }
+
+ private:
+ int handle_take_screenshot_count_;
+
+ DISALLOW_COPY_AND_ASSIGN(DummyScreenshotDelegate);
+};
+
+class DummyVolumeControlDelegate : public VolumeControlDelegate {
+ public:
+ explicit DummyVolumeControlDelegate(bool consume)
+ : consume_(consume),
+ handle_volume_mute_count_(0),
+ handle_volume_down_count_(0),
+ handle_volume_up_count_(0) {
+ }
+ virtual ~DummyVolumeControlDelegate() {}
+
+ virtual bool HandleVolumeMute(const ui::Accelerator& accelerator) OVERRIDE {
+ ++handle_volume_mute_count_;
+ last_accelerator_ = accelerator;
+ return consume_;
+ }
+ virtual bool HandleVolumeDown(const ui::Accelerator& accelerator) OVERRIDE {
+ ++handle_volume_down_count_;
+ last_accelerator_ = accelerator;
+ return consume_;
+ }
+ virtual bool HandleVolumeUp(const ui::Accelerator& accelerator) OVERRIDE {
+ ++handle_volume_up_count_;
+ last_accelerator_ = accelerator;
+ return consume_;
+ }
+
+ int handle_volume_mute_count() const {
+ return handle_volume_mute_count_;
+ }
+ int handle_volume_down_count() const {
+ return handle_volume_down_count_;
+ }
+ int handle_volume_up_count() const {
+ return handle_volume_up_count_;
+ }
+ const ui::Accelerator& last_accelerator() const {
+ return last_accelerator_;
+ }
+
+ private:
+ const bool consume_;
+ int handle_volume_mute_count_;
+ int handle_volume_down_count_;
+ int handle_volume_up_count_;
+ ui::Accelerator last_accelerator_;
+
+ DISALLOW_COPY_AND_ASSIGN(DummyVolumeControlDelegate);
+};
+
+class DummyBrightnessControlDelegate : public BrightnessControlDelegate {
+ public:
+ explicit DummyBrightnessControlDelegate(bool consume)
+ : consume_(consume),
+ handle_brightness_down_count_(0),
+ handle_brightness_up_count_(0) {
+ }
+ virtual ~DummyBrightnessControlDelegate() {}
+
+ virtual bool HandleBrightnessDown(
+ const ui::Accelerator& accelerator) OVERRIDE {
+ ++handle_brightness_down_count_;
+ last_accelerator_ = accelerator;
+ return consume_;
+ }
+ virtual bool HandleBrightnessUp(const ui::Accelerator& accelerator) OVERRIDE {
+ ++handle_brightness_up_count_;
+ last_accelerator_ = accelerator;
+ return consume_;
+ }
+ virtual void SetBrightnessPercent(double percent, bool gradual) OVERRIDE {}
+ virtual void GetBrightnessPercent(
+ const base::Callback<void(double)>& callback) OVERRIDE {
+ callback.Run(100.0);
+ }
+
+ int handle_brightness_down_count() const {
+ return handle_brightness_down_count_;
+ }
+ int handle_brightness_up_count() const {
+ return handle_brightness_up_count_;
+ }
+ const ui::Accelerator& last_accelerator() const {
+ return last_accelerator_;
+ }
+
+ private:
+ const bool consume_;
+ int handle_brightness_down_count_;
+ int handle_brightness_up_count_;
+ ui::Accelerator last_accelerator_;
+
+ DISALLOW_COPY_AND_ASSIGN(DummyBrightnessControlDelegate);
+};
+
+class DummyImeControlDelegate : public ImeControlDelegate {
+ public:
+ explicit DummyImeControlDelegate(bool consume)
+ : consume_(consume),
+ handle_next_ime_count_(0),
+ handle_previous_ime_count_(0),
+ handle_switch_ime_count_(0) {
+ }
+ virtual ~DummyImeControlDelegate() {}
+
+ virtual bool HandleNextIme() OVERRIDE {
+ ++handle_next_ime_count_;
+ return consume_;
+ }
+ virtual bool HandlePreviousIme(const ui::Accelerator& accelerator) OVERRIDE {
+ ++handle_previous_ime_count_;
+ last_accelerator_ = accelerator;
+ return consume_;
+ }
+ virtual bool HandleSwitchIme(const ui::Accelerator& accelerator) OVERRIDE {
+ ++handle_switch_ime_count_;
+ last_accelerator_ = accelerator;
+ return consume_;
+ }
+
+ int handle_next_ime_count() const {
+ return handle_next_ime_count_;
+ }
+ int handle_previous_ime_count() const {
+ return handle_previous_ime_count_;
+ }
+ int handle_switch_ime_count() const {
+ return handle_switch_ime_count_;
+ }
+ const ui::Accelerator& last_accelerator() const {
+ return last_accelerator_;
+ }
+ virtual ui::Accelerator RemapAccelerator(
+ const ui::Accelerator& accelerator) OVERRIDE {
+ return ui::Accelerator(accelerator);
+ }
+
+ private:
+ const bool consume_;
+ int handle_next_ime_count_;
+ int handle_previous_ime_count_;
+ int handle_switch_ime_count_;
+ ui::Accelerator last_accelerator_;
+
+ DISALLOW_COPY_AND_ASSIGN(DummyImeControlDelegate);
+};
+
+class DummyKeyboardBrightnessControlDelegate
+ : public KeyboardBrightnessControlDelegate {
+ public:
+ explicit DummyKeyboardBrightnessControlDelegate(bool consume)
+ : consume_(consume),
+ handle_keyboard_brightness_down_count_(0),
+ handle_keyboard_brightness_up_count_(0) {
+ }
+ virtual ~DummyKeyboardBrightnessControlDelegate() {}
+
+ virtual bool HandleKeyboardBrightnessDown(
+ const ui::Accelerator& accelerator) OVERRIDE {
+ ++handle_keyboard_brightness_down_count_;
+ last_accelerator_ = accelerator;
+ return consume_;
+ }
+
+ virtual bool HandleKeyboardBrightnessUp(
+ const ui::Accelerator& accelerator) OVERRIDE {
+ ++handle_keyboard_brightness_up_count_;
+ last_accelerator_ = accelerator;
+ return consume_;
+ }
+
+ int handle_keyboard_brightness_down_count() const {
+ return handle_keyboard_brightness_down_count_;
+ }
+
+ int handle_keyboard_brightness_up_count() const {
+ return handle_keyboard_brightness_up_count_;
+ }
+
+ const ui::Accelerator& last_accelerator() const {
+ return last_accelerator_;
+ }
+
+ private:
+ const bool consume_;
+ int handle_keyboard_brightness_down_count_;
+ int handle_keyboard_brightness_up_count_;
+ ui::Accelerator last_accelerator_;
+
+ DISALLOW_COPY_AND_ASSIGN(DummyKeyboardBrightnessControlDelegate);
+};
+
+bool TestTarget::AcceleratorPressed(const ui::Accelerator& accelerator) {
+ ++accelerator_pressed_count_;
+ return true;
+}
+
+bool TestTarget::CanHandleAccelerators() const {
+ return true;
+}
+
+} // namespace
+
+class AcceleratorControllerTest : public test::AshTestBase {
+ public:
+ AcceleratorControllerTest() {}
+ virtual ~AcceleratorControllerTest() {}
+
+ protected:
+ void EnableInternalDisplay() {
+ test::DisplayManagerTestApi(Shell::GetInstance()->display_manager()).
+ SetFirstDisplayAsInternalDisplay();
+ }
+
+ static AcceleratorController* GetController();
+ static bool ProcessWithContext(const ui::Accelerator& accelerator);
+
+ // Several functions to access ExitWarningHandler (as friend).
+ static void StubForTest(ExitWarningHandler* ewh) {
+ ewh->stub_timer_for_test_ = true;
+ }
+ static void Reset(ExitWarningHandler* ewh) {
+ ewh->state_ = ExitWarningHandler::IDLE;
+ }
+ static void SimulateTimerExpired(ExitWarningHandler* ewh) {
+ ewh->TimerAction();
+ }
+ static bool is_ui_shown(ExitWarningHandler* ewh) {
+ return !!ewh->widget_;
+ }
+ static bool is_idle(ExitWarningHandler* ewh) {
+ return ewh->state_ == ExitWarningHandler::IDLE;
+ }
+ static bool is_exiting(ExitWarningHandler* ewh) {
+ return ewh->state_ == ExitWarningHandler::EXITING;
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(AcceleratorControllerTest);
+};
+
+AcceleratorController* AcceleratorControllerTest::GetController() {
+ return Shell::GetInstance()->accelerator_controller();
+}
+
+bool AcceleratorControllerTest::ProcessWithContext(
+ const ui::Accelerator& accelerator) {
+ AcceleratorController* controller = GetController();
+ controller->context()->UpdateContext(accelerator);
+ return controller->Process(accelerator);
+}
+
+#if !defined(OS_WIN)
+// Double press of exit shortcut => exiting
+TEST_F(AcceleratorControllerTest, ExitWarningHandlerTestDoublePress) {
+ ui::Accelerator press(ui::VKEY_Q, ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN);
+ ui::Accelerator release(press);
+ release.set_type(ui::ET_KEY_RELEASED);
+ ExitWarningHandler* ewh = GetController()->GetExitWarningHandlerForTest();
+ ASSERT_TRUE(!!ewh);
+ StubForTest(ewh);
+ EXPECT_TRUE(is_idle(ewh));
+ EXPECT_FALSE(is_ui_shown(ewh));
+ EXPECT_TRUE(ProcessWithContext(press));
+ EXPECT_FALSE(ProcessWithContext(release));
+ EXPECT_FALSE(is_idle(ewh));
+ EXPECT_TRUE(is_ui_shown(ewh));
+ EXPECT_TRUE(ProcessWithContext(press)); // second press before timer.
+ EXPECT_FALSE(ProcessWithContext(release));
+ SimulateTimerExpired(ewh);
+ EXPECT_TRUE(is_exiting(ewh));
+ EXPECT_FALSE(is_ui_shown(ewh));
+ Reset(ewh);
+}
+
+// Single press of exit shortcut before timer => idle
+TEST_F(AcceleratorControllerTest, ExitWarningHandlerTestSinglePress) {
+ ui::Accelerator press(ui::VKEY_Q, ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN);
+ ui::Accelerator release(press);
+ release.set_type(ui::ET_KEY_RELEASED);
+ ExitWarningHandler* ewh = GetController()->GetExitWarningHandlerForTest();
+ ASSERT_TRUE(!!ewh);
+ StubForTest(ewh);
+ EXPECT_TRUE(is_idle(ewh));
+ EXPECT_FALSE(is_ui_shown(ewh));
+ EXPECT_TRUE(ProcessWithContext(press));
+ EXPECT_FALSE(ProcessWithContext(release));
+ EXPECT_FALSE(is_idle(ewh));
+ EXPECT_TRUE(is_ui_shown(ewh));
+ SimulateTimerExpired(ewh);
+ EXPECT_TRUE(is_idle(ewh));
+ EXPECT_FALSE(is_ui_shown(ewh));
+ Reset(ewh);
+}
+
+// Shutdown ash with exit warning bubble open should not crash.
+TEST_F(AcceleratorControllerTest, LingeringExitWarningBubble) {
+ ExitWarningHandler* ewh = GetController()->GetExitWarningHandlerForTest();
+ ASSERT_TRUE(!!ewh);
+ StubForTest(ewh);
+
+ // Trigger once to show the bubble.
+ ewh->HandleAccelerator();
+ EXPECT_FALSE(is_idle(ewh));
+ EXPECT_TRUE(is_ui_shown(ewh));
+
+ // Exit ash and there should be no crash
+}
+#endif // !defined(OS_WIN)
+
+TEST_F(AcceleratorControllerTest, Register) {
+ const ui::Accelerator accelerator_a(ui::VKEY_A, ui::EF_NONE);
+ TestTarget target;
+ GetController()->Register(accelerator_a, &target);
+
+ // The registered accelerator is processed.
+ EXPECT_TRUE(ProcessWithContext(accelerator_a));
+ EXPECT_EQ(1, target.accelerator_pressed_count());
+}
+
+TEST_F(AcceleratorControllerTest, RegisterMultipleTarget) {
+ const ui::Accelerator accelerator_a(ui::VKEY_A, ui::EF_NONE);
+ TestTarget target1;
+ GetController()->Register(accelerator_a, &target1);
+ TestTarget target2;
+ GetController()->Register(accelerator_a, &target2);
+
+ // If multiple targets are registered with the same accelerator, the target
+ // registered later processes the accelerator.
+ EXPECT_TRUE(ProcessWithContext(accelerator_a));
+ EXPECT_EQ(0, target1.accelerator_pressed_count());
+ EXPECT_EQ(1, target2.accelerator_pressed_count());
+}
+
+TEST_F(AcceleratorControllerTest, Unregister) {
+ const ui::Accelerator accelerator_a(ui::VKEY_A, ui::EF_NONE);
+ TestTarget target;
+ GetController()->Register(accelerator_a, &target);
+ const ui::Accelerator accelerator_b(ui::VKEY_B, ui::EF_NONE);
+ GetController()->Register(accelerator_b, &target);
+
+ // Unregistering a different accelerator does not affect the other
+ // accelerator.
+ GetController()->Unregister(accelerator_b, &target);
+ EXPECT_TRUE(ProcessWithContext(accelerator_a));
+ EXPECT_EQ(1, target.accelerator_pressed_count());
+
+ // The unregistered accelerator is no longer processed.
+ target.set_accelerator_pressed_count(0);
+ GetController()->Unregister(accelerator_a, &target);
+ EXPECT_FALSE(ProcessWithContext(accelerator_a));
+ EXPECT_EQ(0, target.accelerator_pressed_count());
+}
+
+TEST_F(AcceleratorControllerTest, UnregisterAll) {
+ const ui::Accelerator accelerator_a(ui::VKEY_A, ui::EF_NONE);
+ TestTarget target1;
+ GetController()->Register(accelerator_a, &target1);
+ const ui::Accelerator accelerator_b(ui::VKEY_B, ui::EF_NONE);
+ GetController()->Register(accelerator_b, &target1);
+ const ui::Accelerator accelerator_c(ui::VKEY_C, ui::EF_NONE);
+ TestTarget target2;
+ GetController()->Register(accelerator_c, &target2);
+ GetController()->UnregisterAll(&target1);
+
+ // All the accelerators registered for |target1| are no longer processed.
+ EXPECT_FALSE(ProcessWithContext(accelerator_a));
+ EXPECT_FALSE(ProcessWithContext(accelerator_b));
+ EXPECT_EQ(0, target1.accelerator_pressed_count());
+
+ // UnregisterAll with a different target does not affect the other target.
+ EXPECT_TRUE(ProcessWithContext(accelerator_c));
+ EXPECT_EQ(1, target2.accelerator_pressed_count());
+}
+
+TEST_F(AcceleratorControllerTest, Process) {
+ const ui::Accelerator accelerator_a(ui::VKEY_A, ui::EF_NONE);
+ TestTarget target1;
+ GetController()->Register(accelerator_a, &target1);
+
+ // The registered accelerator is processed.
+ EXPECT_TRUE(ProcessWithContext(accelerator_a));
+ EXPECT_EQ(1, target1.accelerator_pressed_count());
+
+ // The non-registered accelerator is not processed.
+ const ui::Accelerator accelerator_b(ui::VKEY_B, ui::EF_NONE);
+ EXPECT_FALSE(ProcessWithContext(accelerator_b));
+}
+
+TEST_F(AcceleratorControllerTest, IsRegistered) {
+ const ui::Accelerator accelerator_a(ui::VKEY_A, ui::EF_NONE);
+ const ui::Accelerator accelerator_shift_a(ui::VKEY_A, ui::EF_SHIFT_DOWN);
+ TestTarget target;
+ GetController()->Register(accelerator_a, &target);
+ EXPECT_TRUE(GetController()->IsRegistered(accelerator_a));
+ EXPECT_FALSE(GetController()->IsRegistered(accelerator_shift_a));
+ GetController()->UnregisterAll(&target);
+ EXPECT_FALSE(GetController()->IsRegistered(accelerator_a));
+}
+
+TEST_F(AcceleratorControllerTest, WindowSnap) {
+ scoped_ptr<aura::Window> window(
+ CreateTestWindowInShellWithBounds(gfx::Rect(5, 5, 20, 20)));
+ const ui::Accelerator dummy;
+
+ wm::ActivateWindow(window.get());
+
+ {
+ GetController()->PerformAction(WINDOW_SNAP_LEFT, dummy);
+ gfx::Rect snap_left = window->bounds();
+ GetController()->PerformAction(WINDOW_SNAP_LEFT, dummy);
+ EXPECT_NE(window->bounds().ToString(), snap_left.ToString());
+ GetController()->PerformAction(WINDOW_SNAP_LEFT, dummy);
+ EXPECT_NE(window->bounds().ToString(), snap_left.ToString());
+
+ // It should cycle back to the first snapped position.
+ GetController()->PerformAction(WINDOW_SNAP_LEFT, dummy);
+ EXPECT_EQ(window->bounds().ToString(), snap_left.ToString());
+ }
+ {
+ GetController()->PerformAction(WINDOW_SNAP_RIGHT, dummy);
+ gfx::Rect snap_right = window->bounds();
+ GetController()->PerformAction(WINDOW_SNAP_RIGHT, dummy);
+ EXPECT_NE(window->bounds().ToString(), snap_right.ToString());
+ GetController()->PerformAction(WINDOW_SNAP_RIGHT, dummy);
+ EXPECT_NE(window->bounds().ToString(), snap_right.ToString());
+
+ // It should cycle back to the first snapped position.
+ GetController()->PerformAction(WINDOW_SNAP_RIGHT, dummy);
+ EXPECT_EQ(window->bounds().ToString(), snap_right.ToString());
+ }
+ {
+ gfx::Rect normal_bounds = window->bounds();
+
+ GetController()->PerformAction(TOGGLE_MAXIMIZED, dummy);
+ EXPECT_TRUE(wm::IsWindowMaximized(window.get()));
+ EXPECT_NE(normal_bounds.ToString(), window->bounds().ToString());
+
+ GetController()->PerformAction(TOGGLE_MAXIMIZED, dummy);
+ EXPECT_FALSE(wm::IsWindowMaximized(window.get()));
+ EXPECT_EQ(normal_bounds.ToString(), window->bounds().ToString());
+
+ GetController()->PerformAction(TOGGLE_MAXIMIZED, dummy);
+ GetController()->PerformAction(WINDOW_SNAP_LEFT, dummy);
+ EXPECT_FALSE(wm::IsWindowMaximized(window.get()));
+
+ GetController()->PerformAction(TOGGLE_MAXIMIZED, dummy);
+ GetController()->PerformAction(WINDOW_SNAP_RIGHT, dummy);
+ EXPECT_FALSE(wm::IsWindowMaximized(window.get()));
+
+ GetController()->PerformAction(TOGGLE_MAXIMIZED, dummy);
+ EXPECT_TRUE(wm::IsWindowMaximized(window.get()));
+ GetController()->PerformAction(WINDOW_MINIMIZE, dummy);
+ EXPECT_FALSE(wm::IsWindowMaximized(window.get()));
+ EXPECT_TRUE(wm::IsWindowMinimized(window.get()));
+ wm::RestoreWindow(window.get());
+ wm::ActivateWindow(window.get());
+ }
+ {
+ GetController()->PerformAction(WINDOW_MINIMIZE, dummy);
+ EXPECT_TRUE(wm::IsWindowMinimized(window.get()));
+ }
+}
+
+TEST_F(AcceleratorControllerTest, ControllerContext) {
+ ui::Accelerator accelerator_a(ui::VKEY_A, ui::EF_NONE);
+ ui::Accelerator accelerator_a2(ui::VKEY_A, ui::EF_NONE);
+ ui::Accelerator accelerator_b(ui::VKEY_B, ui::EF_NONE);
+
+ accelerator_a.set_type(ui::ET_KEY_PRESSED);
+ accelerator_a2.set_type(ui::ET_KEY_RELEASED);
+ accelerator_b.set_type(ui::ET_KEY_PRESSED);
+
+ EXPECT_FALSE(GetController()->context()->repeated());
+ EXPECT_EQ(ui::ET_UNKNOWN,
+ GetController()->context()->previous_accelerator().type());
+
+ GetController()->context()->UpdateContext(accelerator_a);
+ EXPECT_FALSE(GetController()->context()->repeated());
+ EXPECT_EQ(ui::ET_UNKNOWN,
+ GetController()->context()->previous_accelerator().type());
+
+ GetController()->context()->UpdateContext(accelerator_a2);
+ EXPECT_FALSE(GetController()->context()->repeated());
+ EXPECT_EQ(ui::ET_KEY_PRESSED,
+ GetController()->context()->previous_accelerator().type());
+
+ GetController()->context()->UpdateContext(accelerator_a2);
+ EXPECT_TRUE(GetController()->context()->repeated());
+ EXPECT_EQ(ui::ET_KEY_RELEASED,
+ GetController()->context()->previous_accelerator().type());
+
+ GetController()->context()->UpdateContext(accelerator_b);
+ EXPECT_FALSE(GetController()->context()->repeated());
+ EXPECT_EQ(ui::ET_KEY_RELEASED,
+ GetController()->context()->previous_accelerator().type());
+}
+
+TEST_F(AcceleratorControllerTest, SuppressToggleMaximized) {
+ scoped_ptr<aura::Window> window(
+ CreateTestWindowInShellWithBounds(gfx::Rect(5, 5, 20, 20)));
+ wm::ActivateWindow(window.get());
+ const ui::Accelerator accelerator(ui::VKEY_A, ui::EF_NONE);
+ const ui::Accelerator empty_accelerator;
+
+ // Toggling not suppressed.
+ GetController()->context()->UpdateContext(accelerator);
+ GetController()->PerformAction(TOGGLE_MAXIMIZED, accelerator);
+ EXPECT_TRUE(wm::IsWindowMaximized(window.get()));
+
+ // The same accelerator - toggling suppressed.
+ GetController()->context()->UpdateContext(accelerator);
+ GetController()->PerformAction(TOGGLE_MAXIMIZED, accelerator);
+ EXPECT_TRUE(wm::IsWindowMaximized(window.get()));
+
+ // Suppressed but not for gesture events.
+ GetController()->PerformAction(TOGGLE_MAXIMIZED, empty_accelerator);
+ EXPECT_FALSE(wm::IsWindowMaximized(window.get()));
+}
+
+#if defined(OS_WIN) || defined(USE_X11)
+TEST_F(AcceleratorControllerTest, ProcessOnce) {
+ ui::Accelerator accelerator_a(ui::VKEY_A, ui::EF_NONE);
+ TestTarget target;
+ GetController()->Register(accelerator_a, &target);
+
+ // The accelerator is processed only once.
+#if defined(OS_WIN)
+ MSG msg1 = { NULL, WM_KEYDOWN, ui::VKEY_A, 0 };
+ ui::TranslatedKeyEvent key_event1(msg1, false);
+ EXPECT_TRUE(Shell::GetPrimaryRootWindow()->AsRootWindowHostDelegate()->
+ OnHostKeyEvent(&key_event1));
+
+ MSG msg2 = { NULL, WM_CHAR, L'A', 0 };
+ ui::TranslatedKeyEvent key_event2(msg2, true);
+ EXPECT_FALSE(Shell::GetPrimaryRootWindow()->AsRootWindowHostDelegate()->
+ OnHostKeyEvent(&key_event2));
+
+ MSG msg3 = { NULL, WM_KEYUP, ui::VKEY_A, 0 };
+ ui::TranslatedKeyEvent key_event3(msg3, false);
+ EXPECT_FALSE(Shell::GetPrimaryRootWindow()->AsRootWindowHostDelegate()->
+ OnHostKeyEvent(&key_event3));
+#elif defined(USE_X11)
+ XEvent key_event;
+ ui::InitXKeyEventForTesting(ui::ET_KEY_PRESSED,
+ ui::VKEY_A,
+ 0,
+ &key_event);
+ ui::TranslatedKeyEvent key_event1(&key_event, false);
+ EXPECT_TRUE(Shell::GetPrimaryRootWindow()->AsRootWindowHostDelegate()->
+ OnHostKeyEvent(&key_event1));
+
+ ui::TranslatedKeyEvent key_event2(&key_event, true);
+ EXPECT_FALSE(Shell::GetPrimaryRootWindow()->AsRootWindowHostDelegate()->
+ OnHostKeyEvent(&key_event2));
+
+ ui::InitXKeyEventForTesting(ui::ET_KEY_RELEASED,
+ ui::VKEY_A,
+ 0,
+ &key_event);
+ ui::TranslatedKeyEvent key_event3(&key_event, false);
+ EXPECT_FALSE(Shell::GetPrimaryRootWindow()->AsRootWindowHostDelegate()->
+ OnHostKeyEvent(&key_event3));
+#endif
+ EXPECT_EQ(1, target.accelerator_pressed_count());
+}
+#endif
+
+TEST_F(AcceleratorControllerTest, GlobalAccelerators) {
+ // CycleBackward
+ EXPECT_TRUE(ProcessWithContext(
+ ui::Accelerator(ui::VKEY_TAB, ui::EF_SHIFT_DOWN | ui::EF_ALT_DOWN)));
+ // CycleForward
+ EXPECT_TRUE(ProcessWithContext(
+ ui::Accelerator(ui::VKEY_TAB, ui::EF_ALT_DOWN)));
+#if defined(OS_CHROMEOS)
+ // CycleBackward
+ EXPECT_TRUE(ProcessWithContext(
+ ui::Accelerator(ui::VKEY_MEDIA_LAUNCH_APP1, ui::EF_SHIFT_DOWN)));
+ // CycleForward
+ EXPECT_TRUE(ProcessWithContext(
+ ui::Accelerator(ui::VKEY_MEDIA_LAUNCH_APP1, ui::EF_NONE)));
+
+ // Take screenshot / partial screenshot
+ // True should always be returned regardless of the existence of the delegate.
+ {
+ EXPECT_TRUE(ProcessWithContext(
+ ui::Accelerator(ui::VKEY_MEDIA_LAUNCH_APP1, ui::EF_CONTROL_DOWN)));
+ EXPECT_TRUE(ProcessWithContext(
+ ui::Accelerator(ui::VKEY_PRINT, ui::EF_NONE)));
+ EXPECT_TRUE(ProcessWithContext(
+ ui::Accelerator(ui::VKEY_MEDIA_LAUNCH_APP1,
+ ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN)));
+ DummyScreenshotDelegate* delegate = new DummyScreenshotDelegate;
+ GetController()->SetScreenshotDelegate(
+ scoped_ptr<ScreenshotDelegate>(delegate).Pass());
+ EXPECT_EQ(0, delegate->handle_take_screenshot_count());
+ EXPECT_TRUE(ProcessWithContext(
+ ui::Accelerator(ui::VKEY_MEDIA_LAUNCH_APP1, ui::EF_CONTROL_DOWN)));
+ EXPECT_EQ(1, delegate->handle_take_screenshot_count());
+ EXPECT_TRUE(ProcessWithContext(
+ ui::Accelerator(ui::VKEY_PRINT, ui::EF_NONE)));
+ EXPECT_EQ(2, delegate->handle_take_screenshot_count());
+ EXPECT_TRUE(ProcessWithContext(
+ ui::Accelerator(ui::VKEY_MEDIA_LAUNCH_APP1,
+ ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN)));
+ EXPECT_EQ(2, delegate->handle_take_screenshot_count());
+ }
+#endif
+ // DisableCapsLock
+ {
+ CapsLockDelegate* delegate = Shell::GetInstance()->caps_lock_delegate();
+ delegate->SetCapsLockEnabled(true);
+ EXPECT_TRUE(delegate->IsCapsLockEnabled());
+ // Handled only on key release.
+ EXPECT_FALSE(ProcessWithContext(
+ ui::Accelerator(ui::VKEY_LSHIFT, ui::EF_NONE)));
+ EXPECT_TRUE(delegate->IsCapsLockEnabled());
+ EXPECT_TRUE(ProcessWithContext(
+ ReleaseAccelerator(ui::VKEY_SHIFT, ui::EF_NONE)));
+ EXPECT_FALSE(delegate->IsCapsLockEnabled());
+ delegate->SetCapsLockEnabled(true);
+ EXPECT_FALSE(ProcessWithContext(
+ ui::Accelerator(ui::VKEY_RSHIFT, ui::EF_NONE)));
+ EXPECT_TRUE(delegate->IsCapsLockEnabled());
+ EXPECT_TRUE(ProcessWithContext(
+ ReleaseAccelerator(ui::VKEY_LSHIFT, ui::EF_NONE)));
+ EXPECT_FALSE(delegate->IsCapsLockEnabled());
+ delegate->SetCapsLockEnabled(true);
+ EXPECT_FALSE(ProcessWithContext(
+ ui::Accelerator(ui::VKEY_SHIFT, ui::EF_NONE)));
+ EXPECT_TRUE(delegate->IsCapsLockEnabled());
+ EXPECT_TRUE(ProcessWithContext(
+ ReleaseAccelerator(ui::VKEY_RSHIFT, ui::EF_NONE)));
+ EXPECT_FALSE(delegate->IsCapsLockEnabled());
+
+ // Do not handle when a shift pressed with other keys.
+ delegate->SetCapsLockEnabled(true);
+ EXPECT_FALSE(ProcessWithContext(
+ ui::Accelerator(ui::VKEY_A, ui::EF_SHIFT_DOWN)));
+ EXPECT_TRUE(delegate->IsCapsLockEnabled());
+ EXPECT_FALSE(ProcessWithContext(
+ ReleaseAccelerator(ui::VKEY_A, ui::EF_SHIFT_DOWN)));
+ EXPECT_TRUE(delegate->IsCapsLockEnabled());
+
+ // Do not handle when a shift pressed with other keys, and shift is
+ // released first.
+ delegate->SetCapsLockEnabled(true);
+ EXPECT_FALSE(ProcessWithContext(
+ ui::Accelerator(ui::VKEY_A, ui::EF_SHIFT_DOWN)));
+ EXPECT_TRUE(delegate->IsCapsLockEnabled());
+ EXPECT_FALSE(ProcessWithContext(
+ ReleaseAccelerator(ui::VKEY_LSHIFT, ui::EF_NONE)));
+ EXPECT_TRUE(delegate->IsCapsLockEnabled());
+
+ EXPECT_FALSE(ProcessWithContext(
+ ui::Accelerator(ui::VKEY_A, ui::EF_SHIFT_DOWN)));
+ EXPECT_TRUE(delegate->IsCapsLockEnabled());
+ EXPECT_FALSE(ProcessWithContext(
+ ReleaseAccelerator(ui::VKEY_SHIFT, ui::EF_NONE)));
+ EXPECT_TRUE(delegate->IsCapsLockEnabled());
+
+ EXPECT_FALSE(ProcessWithContext(
+ ui::Accelerator(ui::VKEY_A, ui::EF_SHIFT_DOWN)));
+ EXPECT_TRUE(delegate->IsCapsLockEnabled());
+ EXPECT_FALSE(ProcessWithContext(
+ ReleaseAccelerator(ui::VKEY_RSHIFT, ui::EF_NONE)));
+ EXPECT_TRUE(delegate->IsCapsLockEnabled());
+
+ // Do not consume shift keyup when caps lock is off.
+ delegate->SetCapsLockEnabled(false);
+ EXPECT_FALSE(ProcessWithContext(
+ ui::Accelerator(ui::VKEY_LSHIFT, ui::EF_NONE)));
+ EXPECT_FALSE(ProcessWithContext(
+ ReleaseAccelerator(ui::VKEY_LSHIFT, ui::EF_NONE)));
+ EXPECT_FALSE(ProcessWithContext(
+ ui::Accelerator(ui::VKEY_RSHIFT, ui::EF_NONE)));
+ EXPECT_FALSE(ProcessWithContext(
+ ReleaseAccelerator(ui::VKEY_RSHIFT, ui::EF_NONE)));
+ EXPECT_FALSE(ProcessWithContext(
+ ui::Accelerator(ui::VKEY_SHIFT, ui::EF_NONE)));
+ EXPECT_FALSE(ProcessWithContext(
+ ReleaseAccelerator(ui::VKEY_SHIFT, ui::EF_NONE)));
+ }
+ // ToggleCapsLock
+ {
+ CapsLockDelegate* delegate = Shell::GetInstance()->caps_lock_delegate();
+ delegate->SetCapsLockEnabled(true);
+ EXPECT_TRUE(delegate->IsCapsLockEnabled());
+ EXPECT_FALSE(ProcessWithContext(
+ ui::Accelerator(ui::VKEY_LWIN, ui::EF_ALT_DOWN)));
+ EXPECT_TRUE(ProcessWithContext(
+ ReleaseAccelerator(ui::VKEY_LWIN, ui::EF_ALT_DOWN)));
+ EXPECT_FALSE(delegate->IsCapsLockEnabled());
+ EXPECT_FALSE(ProcessWithContext(
+ ui::Accelerator(ui::VKEY_LWIN, ui::EF_ALT_DOWN)));
+ EXPECT_TRUE(ProcessWithContext(
+ ReleaseAccelerator(ui::VKEY_LWIN, ui::EF_ALT_DOWN)));
+ EXPECT_TRUE(delegate->IsCapsLockEnabled());
+ }
+ const ui::Accelerator volume_mute(ui::VKEY_VOLUME_MUTE, ui::EF_NONE);
+ const ui::Accelerator volume_down(ui::VKEY_VOLUME_DOWN, ui::EF_NONE);
+ const ui::Accelerator volume_up(ui::VKEY_VOLUME_UP, ui::EF_NONE);
+ {
+ DummyVolumeControlDelegate* delegate =
+ new DummyVolumeControlDelegate(false);
+ ash::Shell::GetInstance()->system_tray_delegate()->SetVolumeControlDelegate(
+ scoped_ptr<VolumeControlDelegate>(delegate).Pass());
+ EXPECT_EQ(0, delegate->handle_volume_mute_count());
+ EXPECT_FALSE(ProcessWithContext(volume_mute));
+ EXPECT_EQ(1, delegate->handle_volume_mute_count());
+ EXPECT_EQ(volume_mute, delegate->last_accelerator());
+ EXPECT_EQ(0, delegate->handle_volume_down_count());
+ EXPECT_FALSE(ProcessWithContext(volume_down));
+ EXPECT_EQ(1, delegate->handle_volume_down_count());
+ EXPECT_EQ(volume_down, delegate->last_accelerator());
+ EXPECT_EQ(0, delegate->handle_volume_up_count());
+ EXPECT_FALSE(ProcessWithContext(volume_up));
+ EXPECT_EQ(1, delegate->handle_volume_up_count());
+ EXPECT_EQ(volume_up, delegate->last_accelerator());
+ }
+ {
+ DummyVolumeControlDelegate* delegate = new DummyVolumeControlDelegate(true);
+ ash::Shell::GetInstance()->system_tray_delegate()->SetVolumeControlDelegate(
+ scoped_ptr<VolumeControlDelegate>(delegate).Pass());
+ EXPECT_EQ(0, delegate->handle_volume_mute_count());
+ EXPECT_TRUE(ProcessWithContext(volume_mute));
+ EXPECT_EQ(1, delegate->handle_volume_mute_count());
+ EXPECT_EQ(volume_mute, delegate->last_accelerator());
+ EXPECT_EQ(0, delegate->handle_volume_down_count());
+ EXPECT_TRUE(ProcessWithContext(volume_down));
+ EXPECT_EQ(1, delegate->handle_volume_down_count());
+ EXPECT_EQ(volume_down, delegate->last_accelerator());
+ EXPECT_EQ(0, delegate->handle_volume_up_count());
+ EXPECT_TRUE(ProcessWithContext(volume_up));
+ EXPECT_EQ(1, delegate->handle_volume_up_count());
+ EXPECT_EQ(volume_up, delegate->last_accelerator());
+ }
+#if defined(OS_CHROMEOS)
+ // Brightness
+ // ui::VKEY_BRIGHTNESS_DOWN/UP are not defined on Windows.
+ const ui::Accelerator brightness_down(ui::VKEY_BRIGHTNESS_DOWN, ui::EF_NONE);
+ const ui::Accelerator brightness_up(ui::VKEY_BRIGHTNESS_UP, ui::EF_NONE);
+ {
+ EXPECT_FALSE(ProcessWithContext(brightness_down));
+ EXPECT_FALSE(ProcessWithContext(brightness_up));
+ DummyBrightnessControlDelegate* delegate =
+ new DummyBrightnessControlDelegate(true);
+ GetController()->SetBrightnessControlDelegate(
+ scoped_ptr<BrightnessControlDelegate>(delegate).Pass());
+ EXPECT_FALSE(ProcessWithContext(brightness_down));
+ EXPECT_FALSE(ProcessWithContext(brightness_up));
+ }
+ // Enable internal display.
+ EnableInternalDisplay();
+ {
+ DummyBrightnessControlDelegate* delegate =
+ new DummyBrightnessControlDelegate(false);
+ GetController()->SetBrightnessControlDelegate(
+ scoped_ptr<BrightnessControlDelegate>(delegate).Pass());
+ EXPECT_EQ(0, delegate->handle_brightness_down_count());
+ EXPECT_FALSE(ProcessWithContext(brightness_down));
+ EXPECT_EQ(1, delegate->handle_brightness_down_count());
+ EXPECT_EQ(brightness_down, delegate->last_accelerator());
+ EXPECT_EQ(0, delegate->handle_brightness_up_count());
+ EXPECT_FALSE(ProcessWithContext(brightness_up));
+ EXPECT_EQ(1, delegate->handle_brightness_up_count());
+ EXPECT_EQ(brightness_up, delegate->last_accelerator());
+ }
+ {
+ DummyBrightnessControlDelegate* delegate =
+ new DummyBrightnessControlDelegate(true);
+ GetController()->SetBrightnessControlDelegate(
+ scoped_ptr<BrightnessControlDelegate>(delegate).Pass());
+ EXPECT_EQ(0, delegate->handle_brightness_down_count());
+ EXPECT_TRUE(ProcessWithContext(brightness_down));
+ EXPECT_EQ(1, delegate->handle_brightness_down_count());
+ EXPECT_EQ(brightness_down, delegate->last_accelerator());
+ EXPECT_EQ(0, delegate->handle_brightness_up_count());
+ EXPECT_TRUE(ProcessWithContext(brightness_up));
+ EXPECT_EQ(1, delegate->handle_brightness_up_count());
+ EXPECT_EQ(brightness_up, delegate->last_accelerator());
+ }
+
+ // Keyboard brightness
+ const ui::Accelerator alt_brightness_down(ui::VKEY_BRIGHTNESS_DOWN,
+ ui::EF_ALT_DOWN);
+ const ui::Accelerator alt_brightness_up(ui::VKEY_BRIGHTNESS_UP,
+ ui::EF_ALT_DOWN);
+ {
+ EXPECT_TRUE(ProcessWithContext(alt_brightness_down));
+ EXPECT_TRUE(ProcessWithContext(alt_brightness_up));
+ DummyKeyboardBrightnessControlDelegate* delegate =
+ new DummyKeyboardBrightnessControlDelegate(false);
+ GetController()->SetKeyboardBrightnessControlDelegate(
+ scoped_ptr<KeyboardBrightnessControlDelegate>(delegate).Pass());
+ EXPECT_EQ(0, delegate->handle_keyboard_brightness_down_count());
+ EXPECT_FALSE(ProcessWithContext(alt_brightness_down));
+ EXPECT_EQ(1, delegate->handle_keyboard_brightness_down_count());
+ EXPECT_EQ(alt_brightness_down, delegate->last_accelerator());
+ EXPECT_EQ(0, delegate->handle_keyboard_brightness_up_count());
+ EXPECT_FALSE(ProcessWithContext(alt_brightness_up));
+ EXPECT_EQ(1, delegate->handle_keyboard_brightness_up_count());
+ EXPECT_EQ(alt_brightness_up, delegate->last_accelerator());
+ }
+ {
+ DummyKeyboardBrightnessControlDelegate* delegate =
+ new DummyKeyboardBrightnessControlDelegate(true);
+ GetController()->SetKeyboardBrightnessControlDelegate(
+ scoped_ptr<KeyboardBrightnessControlDelegate>(delegate).Pass());
+ EXPECT_EQ(0, delegate->handle_keyboard_brightness_down_count());
+ EXPECT_TRUE(ProcessWithContext(alt_brightness_down));
+ EXPECT_EQ(1, delegate->handle_keyboard_brightness_down_count());
+ EXPECT_EQ(alt_brightness_down, delegate->last_accelerator());
+ EXPECT_EQ(0, delegate->handle_keyboard_brightness_up_count());
+ EXPECT_TRUE(ProcessWithContext(alt_brightness_up));
+ EXPECT_EQ(1, delegate->handle_keyboard_brightness_up_count());
+ EXPECT_EQ(alt_brightness_up, delegate->last_accelerator());
+ }
+#endif
+
+#if !defined(NDEBUG)
+ // ToggleDesktopBackgroundMode
+ EXPECT_TRUE(ProcessWithContext(
+ ui::Accelerator(ui::VKEY_B, ui::EF_CONTROL_DOWN | ui::EF_ALT_DOWN)));
+#if !defined(OS_LINUX)
+ // ToggleDesktopFullScreen (not implemented yet on Linux)
+ EXPECT_TRUE(ProcessWithContext(
+ ui::Accelerator(ui::VKEY_F11, ui::EF_CONTROL_DOWN)));
+#endif // OS_LINUX
+#endif // !NDEBUG
+
+#if !defined(OS_WIN)
+ // Exit
+ ExitWarningHandler* ewh = GetController()->GetExitWarningHandlerForTest();
+ ASSERT_TRUE(!!ewh);
+ StubForTest(ewh);
+ EXPECT_TRUE(is_idle(ewh));
+ EXPECT_FALSE(is_ui_shown(ewh));
+ EXPECT_TRUE(ProcessWithContext(
+ ui::Accelerator(ui::VKEY_Q, ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN)));
+ EXPECT_FALSE(is_idle(ewh));
+ EXPECT_TRUE(is_ui_shown(ewh));
+ SimulateTimerExpired(ewh);
+ EXPECT_TRUE(is_idle(ewh));
+ EXPECT_FALSE(is_ui_shown(ewh));
+ Reset(ewh);
+#endif
+
+ // New tab
+ EXPECT_TRUE(ProcessWithContext(
+ ui::Accelerator(ui::VKEY_T, ui::EF_CONTROL_DOWN)));
+
+ // New incognito window
+ EXPECT_TRUE(ProcessWithContext(
+ ui::Accelerator(ui::VKEY_N, ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN)));
+
+ // New window
+ EXPECT_TRUE(ProcessWithContext(
+ ui::Accelerator(ui::VKEY_N, ui::EF_CONTROL_DOWN)));
+
+ // Restore tab
+ EXPECT_TRUE(ProcessWithContext(
+ ui::Accelerator(ui::VKEY_T, ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN)));
+
+ // Show task manager
+ EXPECT_TRUE(ProcessWithContext(
+ ui::Accelerator(ui::VKEY_ESCAPE, ui::EF_SHIFT_DOWN)));
+
+#if defined(OS_CHROMEOS)
+ // Open 'open file' dialog
+ EXPECT_TRUE(ProcessWithContext(
+ ui::Accelerator(ui::VKEY_O, ui::EF_CONTROL_DOWN)));
+
+ // Open file manager
+ EXPECT_TRUE(ProcessWithContext(
+ ui::Accelerator(ui::VKEY_M, ui::EF_SHIFT_DOWN | ui::EF_ALT_DOWN)));
+
+ // Lock screen
+ // NOTE: Accelerators that do not work on the lock screen need to be
+ // tested before the sequence below is invoked because it causes a side
+ // effect of locking the screen.
+ EXPECT_TRUE(ProcessWithContext(
+ ui::Accelerator(ui::VKEY_L, ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN)));
+#endif
+}
+
+TEST_F(AcceleratorControllerTest, GlobalAcceleratorsToggleAppList) {
+ test::TestShellDelegate* delegate =
+ reinterpret_cast<test::TestShellDelegate*>(
+ ash::Shell::GetInstance()->delegate());
+ EXPECT_FALSE(ash::Shell::GetInstance()->GetAppListTargetVisibility());
+
+ // The press event should not open the AppList, the release should instead.
+ EXPECT_FALSE(ProcessWithContext(
+ ui::Accelerator(ui::VKEY_LWIN, ui::EF_NONE)));
+ EXPECT_TRUE(ProcessWithContext(
+ ReleaseAccelerator(ui::VKEY_LWIN, ui::EF_NONE)));
+ EXPECT_TRUE(ash::Shell::GetInstance()->GetAppListTargetVisibility());
+
+ // When spoken feedback is on, the AppList should not toggle.
+ delegate->ToggleSpokenFeedback(A11Y_NOTIFICATION_NONE);
+ EXPECT_FALSE(ProcessWithContext(
+ ui::Accelerator(ui::VKEY_LWIN, ui::EF_NONE)));
+ EXPECT_FALSE(ProcessWithContext(
+ ReleaseAccelerator(ui::VKEY_LWIN, ui::EF_NONE)));
+ delegate->ToggleSpokenFeedback(A11Y_NOTIFICATION_NONE);
+ EXPECT_TRUE(ash::Shell::GetInstance()->GetAppListTargetVisibility());
+
+ EXPECT_FALSE(ProcessWithContext(
+ ui::Accelerator(ui::VKEY_LWIN, ui::EF_NONE)));
+ EXPECT_TRUE(ProcessWithContext(
+ ReleaseAccelerator(ui::VKEY_LWIN, ui::EF_NONE)));
+ EXPECT_FALSE(ash::Shell::GetInstance()->GetAppListTargetVisibility());
+
+ // When spoken feedback is on, the AppList should not toggle.
+ delegate->ToggleSpokenFeedback(A11Y_NOTIFICATION_NONE);
+ EXPECT_FALSE(ProcessWithContext(
+ ui::Accelerator(ui::VKEY_LWIN, ui::EF_NONE)));
+ EXPECT_FALSE(ProcessWithContext(
+ ReleaseAccelerator(ui::VKEY_LWIN, ui::EF_NONE)));
+ delegate->ToggleSpokenFeedback(A11Y_NOTIFICATION_NONE);
+ EXPECT_FALSE(ash::Shell::GetInstance()->GetAppListTargetVisibility());
+}
+
+TEST_F(AcceleratorControllerTest, ImeGlobalAccelerators) {
+ // Test IME shortcuts.
+ {
+ const ui::Accelerator control_space(ui::VKEY_SPACE, ui::EF_CONTROL_DOWN);
+ const ui::Accelerator convert(ui::VKEY_CONVERT, ui::EF_NONE);
+ const ui::Accelerator non_convert(ui::VKEY_NONCONVERT, ui::EF_NONE);
+ const ui::Accelerator wide_half_1(ui::VKEY_DBE_SBCSCHAR, ui::EF_NONE);
+ const ui::Accelerator wide_half_2(ui::VKEY_DBE_DBCSCHAR, ui::EF_NONE);
+ const ui::Accelerator hangul(ui::VKEY_HANGUL, ui::EF_NONE);
+ EXPECT_FALSE(ProcessWithContext(control_space));
+ EXPECT_FALSE(ProcessWithContext(convert));
+ EXPECT_FALSE(ProcessWithContext(non_convert));
+ EXPECT_FALSE(ProcessWithContext(wide_half_1));
+ EXPECT_FALSE(ProcessWithContext(wide_half_2));
+ EXPECT_FALSE(ProcessWithContext(hangul));
+ DummyImeControlDelegate* delegate = new DummyImeControlDelegate(true);
+ GetController()->SetImeControlDelegate(
+ scoped_ptr<ImeControlDelegate>(delegate).Pass());
+ EXPECT_EQ(0, delegate->handle_previous_ime_count());
+ EXPECT_TRUE(ProcessWithContext(control_space));
+ EXPECT_EQ(1, delegate->handle_previous_ime_count());
+ EXPECT_EQ(0, delegate->handle_switch_ime_count());
+ EXPECT_TRUE(ProcessWithContext(convert));
+ EXPECT_EQ(1, delegate->handle_switch_ime_count());
+ EXPECT_TRUE(ProcessWithContext(non_convert));
+ EXPECT_EQ(2, delegate->handle_switch_ime_count());
+ EXPECT_TRUE(ProcessWithContext(wide_half_1));
+ EXPECT_EQ(3, delegate->handle_switch_ime_count());
+ EXPECT_TRUE(ProcessWithContext(wide_half_2));
+ EXPECT_EQ(4, delegate->handle_switch_ime_count());
+ EXPECT_TRUE(ProcessWithContext(hangul));
+ EXPECT_EQ(5, delegate->handle_switch_ime_count());
+ }
+
+ // Test IME shortcuts that are triggered on key release.
+ {
+ const ui::Accelerator shift_alt_press(ui::VKEY_MENU,
+ ui::EF_SHIFT_DOWN | ui::EF_ALT_DOWN);
+ const ReleaseAccelerator shift_alt(ui::VKEY_MENU, ui::EF_SHIFT_DOWN);
+ const ui::Accelerator alt_shift_press(ui::VKEY_SHIFT,
+ ui::EF_SHIFT_DOWN | ui::EF_ALT_DOWN);
+ const ReleaseAccelerator alt_shift(ui::VKEY_SHIFT, ui::EF_ALT_DOWN);
+
+ DummyImeControlDelegate* delegate = new DummyImeControlDelegate(true);
+ GetController()->SetImeControlDelegate(
+ scoped_ptr<ImeControlDelegate>(delegate).Pass());
+ EXPECT_EQ(0, delegate->handle_next_ime_count());
+ EXPECT_FALSE(ProcessWithContext(shift_alt_press));
+ EXPECT_TRUE(ProcessWithContext(shift_alt));
+ EXPECT_EQ(1, delegate->handle_next_ime_count());
+ EXPECT_FALSE(ProcessWithContext(alt_shift_press));
+ EXPECT_TRUE(ProcessWithContext(alt_shift));
+ EXPECT_EQ(2, delegate->handle_next_ime_count());
+
+ // We should NOT switch IME when e.g. Shift+Alt+X is pressed and X is
+ // released.
+ const ui::Accelerator shift_alt_x_press(
+ ui::VKEY_X,
+ ui::EF_SHIFT_DOWN | ui::EF_ALT_DOWN);
+ const ReleaseAccelerator shift_alt_x(ui::VKEY_X,
+ ui::EF_SHIFT_DOWN | ui::EF_ALT_DOWN);
+
+ EXPECT_FALSE(ProcessWithContext(shift_alt_press));
+ EXPECT_FALSE(ProcessWithContext(shift_alt_x_press));
+ EXPECT_FALSE(ProcessWithContext(shift_alt_x));
+ EXPECT_FALSE(ProcessWithContext(shift_alt));
+ EXPECT_EQ(2, delegate->handle_next_ime_count());
+
+ // But we _should_ if X is either VKEY_RETURN or VKEY_SPACE.
+ // TODO(nona|mazda): Remove this when crbug.com/139556 in a better way.
+ const ui::Accelerator shift_alt_return_press(
+ ui::VKEY_RETURN,
+ ui::EF_SHIFT_DOWN | ui::EF_ALT_DOWN);
+ const ReleaseAccelerator shift_alt_return(
+ ui::VKEY_RETURN,
+ ui::EF_SHIFT_DOWN | ui::EF_ALT_DOWN);
+
+ EXPECT_FALSE(ProcessWithContext(shift_alt_press));
+ EXPECT_FALSE(ProcessWithContext(shift_alt_return_press));
+ EXPECT_FALSE(ProcessWithContext(shift_alt_return));
+ EXPECT_TRUE(ProcessWithContext(shift_alt));
+ EXPECT_EQ(3, delegate->handle_next_ime_count());
+
+ const ui::Accelerator shift_alt_space_press(
+ ui::VKEY_SPACE,
+ ui::EF_SHIFT_DOWN | ui::EF_ALT_DOWN);
+ const ReleaseAccelerator shift_alt_space(
+ ui::VKEY_SPACE,
+ ui::EF_SHIFT_DOWN | ui::EF_ALT_DOWN);
+
+ EXPECT_FALSE(ProcessWithContext(shift_alt_press));
+ EXPECT_FALSE(ProcessWithContext(shift_alt_space_press));
+ EXPECT_FALSE(ProcessWithContext(shift_alt_space));
+ EXPECT_TRUE(ProcessWithContext(shift_alt));
+ EXPECT_EQ(4, delegate->handle_next_ime_count());
+ }
+
+#if defined(OS_CHROMEOS)
+ // Test IME shortcuts again with unnormalized accelerators (Chrome OS only).
+ {
+ const ui::Accelerator shift_alt_press(ui::VKEY_MENU, ui::EF_SHIFT_DOWN);
+ const ReleaseAccelerator shift_alt(ui::VKEY_MENU, ui::EF_SHIFT_DOWN);
+ const ui::Accelerator alt_shift_press(ui::VKEY_SHIFT, ui::EF_ALT_DOWN);
+ const ReleaseAccelerator alt_shift(ui::VKEY_SHIFT, ui::EF_ALT_DOWN);
+
+ DummyImeControlDelegate* delegate = new DummyImeControlDelegate(true);
+ GetController()->SetImeControlDelegate(
+ scoped_ptr<ImeControlDelegate>(delegate).Pass());
+ EXPECT_EQ(0, delegate->handle_next_ime_count());
+ EXPECT_FALSE(ProcessWithContext(shift_alt_press));
+ EXPECT_TRUE(ProcessWithContext(shift_alt));
+ EXPECT_EQ(1, delegate->handle_next_ime_count());
+ EXPECT_FALSE(ProcessWithContext(alt_shift_press));
+ EXPECT_TRUE(ProcessWithContext(alt_shift));
+ EXPECT_EQ(2, delegate->handle_next_ime_count());
+
+ // We should NOT switch IME when e.g. Shift+Alt+X is pressed and X is
+ // released.
+ const ui::Accelerator shift_alt_x_press(
+ ui::VKEY_X,
+ ui::EF_SHIFT_DOWN | ui::EF_ALT_DOWN);
+ const ReleaseAccelerator shift_alt_x(ui::VKEY_X,
+ ui::EF_SHIFT_DOWN | ui::EF_ALT_DOWN);
+
+ EXPECT_FALSE(ProcessWithContext(shift_alt_press));
+ EXPECT_FALSE(ProcessWithContext(shift_alt_x_press));
+ EXPECT_FALSE(ProcessWithContext(shift_alt_x));
+ EXPECT_FALSE(ProcessWithContext(shift_alt));
+ EXPECT_EQ(2, delegate->handle_next_ime_count());
+ }
+#endif
+}
+
+// TODO(nona|mazda): Remove this when crbug.com/139556 in a better way.
+TEST_F(AcceleratorControllerTest, ImeGlobalAcceleratorsWorkaround139556) {
+ // The workaround for crbug.com/139556 depends on the fact that we don't
+ // use Shift+Alt+Enter/Space with ET_KEY_PRESSED as an accelerator. Test it.
+ const ui::Accelerator shift_alt_return_press(
+ ui::VKEY_RETURN,
+ ui::EF_SHIFT_DOWN | ui::EF_ALT_DOWN);
+ EXPECT_FALSE(ProcessWithContext(shift_alt_return_press));
+ const ui::Accelerator shift_alt_space_press(
+ ui::VKEY_SPACE,
+ ui::EF_SHIFT_DOWN | ui::EF_ALT_DOWN);
+ EXPECT_FALSE(ProcessWithContext(shift_alt_space_press));
+}
+
+TEST_F(AcceleratorControllerTest, ReservedAccelerators) {
+ // (Shift+)Alt+Tab and Chrome OS top-row keys are reserved.
+ EXPECT_TRUE(GetController()->IsReservedAccelerator(
+ ui::Accelerator(ui::VKEY_TAB, ui::EF_ALT_DOWN)));
+ EXPECT_TRUE(GetController()->IsReservedAccelerator(
+ ui::Accelerator(ui::VKEY_TAB, ui::EF_SHIFT_DOWN | ui::EF_ALT_DOWN)));
+#if defined(OS_CHROMEOS)
+ EXPECT_TRUE(GetController()->IsReservedAccelerator(
+ ui::Accelerator(ui::VKEY_POWER, ui::EF_NONE)));
+#endif
+ // Others are not reserved.
+ EXPECT_FALSE(GetController()->IsReservedAccelerator(
+ ui::Accelerator(ui::VKEY_PRINT, ui::EF_NONE)));
+ EXPECT_FALSE(GetController()->IsReservedAccelerator(
+ ui::Accelerator(ui::VKEY_TAB, ui::EF_NONE)));
+ EXPECT_FALSE(GetController()->IsReservedAccelerator(
+ ui::Accelerator(ui::VKEY_A, ui::EF_NONE)));
+}
+
+#if defined(OS_CHROMEOS)
+TEST_F(AcceleratorControllerTest, DisallowedAtModalWindow) {
+ std::set<AcceleratorAction> all_actions;
+ for (size_t i = 0 ; i < kAcceleratorDataLength; ++i)
+ all_actions.insert(kAcceleratorData[i].action);
+#if !defined(NDEBUG)
+ std::set<AcceleratorAction> all_desktop_actions;
+ for (size_t i = 0 ; i < kDesktopAcceleratorDataLength; ++i)
+ all_desktop_actions.insert(kDesktopAcceleratorData[i].action);
+#endif
+
+ std::set<AcceleratorAction> actionsAllowedAtModalWindow;
+ for (size_t k = 0 ; k < kActionsAllowedAtModalWindowLength; ++k)
+ actionsAllowedAtModalWindow.insert(kActionsAllowedAtModalWindow[k]);
+ for (std::set<AcceleratorAction>::const_iterator it =
+ actionsAllowedAtModalWindow.begin();
+ it != actionsAllowedAtModalWindow.end(); ++it) {
+ EXPECT_TRUE(all_actions.find(*it) != all_actions.end()
+
+#if !defined(NDEBUG)
+ || all_desktop_actions.find(*it) != all_desktop_actions.end()
+#endif
+ )
+ << " action from kActionsAllowedAtModalWindow"
+ << " not found in kAcceleratorData or kDesktopAcceleratorData. "
+ << "action: " << *it;
+ }
+ scoped_ptr<aura::Window> window(
+ CreateTestWindowInShellWithBounds(gfx::Rect(5, 5, 20, 20)));
+ const ui::Accelerator dummy;
+ wm::ActivateWindow(window.get());
+ Shell::GetInstance()->SimulateModalWindowOpenForTesting(true);
+ for (std::set<AcceleratorAction>::const_iterator it = all_actions.begin();
+ it != all_actions.end(); ++it) {
+ if (actionsAllowedAtModalWindow.find(*it) ==
+ actionsAllowedAtModalWindow.end()) {
+ EXPECT_TRUE(GetController()->PerformAction(*it, dummy))
+ << " for action (disallowed at modal window): " << *it;
+ }
+ }
+ // Testing of top row (F5-F10) accelerators that should still work
+ // when a modal window is open
+ //
+ // Screenshot
+ {
+ EXPECT_TRUE(ProcessWithContext(
+ ui::Accelerator(ui::VKEY_MEDIA_LAUNCH_APP1, ui::EF_CONTROL_DOWN)));
+ EXPECT_TRUE(ProcessWithContext(
+ ui::Accelerator(ui::VKEY_PRINT, ui::EF_NONE)));
+ EXPECT_TRUE(ProcessWithContext(
+ ui::Accelerator(ui::VKEY_MEDIA_LAUNCH_APP1,
+ ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN)));
+ DummyScreenshotDelegate* delegate = new DummyScreenshotDelegate;
+ GetController()->SetScreenshotDelegate(
+ scoped_ptr<ScreenshotDelegate>(delegate).Pass());
+ EXPECT_EQ(0, delegate->handle_take_screenshot_count());
+ EXPECT_TRUE(ProcessWithContext(
+ ui::Accelerator(ui::VKEY_MEDIA_LAUNCH_APP1, ui::EF_CONTROL_DOWN)));
+ EXPECT_EQ(1, delegate->handle_take_screenshot_count());
+ EXPECT_TRUE(ProcessWithContext(
+ ui::Accelerator(ui::VKEY_PRINT, ui::EF_NONE)));
+ EXPECT_EQ(2, delegate->handle_take_screenshot_count());
+ EXPECT_TRUE(ProcessWithContext(
+ ui::Accelerator(ui::VKEY_MEDIA_LAUNCH_APP1,
+ ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN)));
+ EXPECT_EQ(2, delegate->handle_take_screenshot_count());
+ }
+ // Brightness
+ const ui::Accelerator brightness_down(ui::VKEY_BRIGHTNESS_DOWN, ui::EF_NONE);
+ const ui::Accelerator brightness_up(ui::VKEY_BRIGHTNESS_UP, ui::EF_NONE);
+ {
+ EXPECT_FALSE(ProcessWithContext(brightness_down));
+ EXPECT_FALSE(ProcessWithContext(brightness_up));
+ DummyBrightnessControlDelegate* delegate =
+ new DummyBrightnessControlDelegate(true);
+ GetController()->SetBrightnessControlDelegate(
+ scoped_ptr<BrightnessControlDelegate>(delegate).Pass());
+ EXPECT_FALSE(ProcessWithContext(brightness_down));
+ EXPECT_FALSE(ProcessWithContext(brightness_up));
+ }
+ EnableInternalDisplay();
+ {
+ EXPECT_FALSE(ProcessWithContext(brightness_down));
+ EXPECT_FALSE(ProcessWithContext(brightness_up));
+ DummyBrightnessControlDelegate* delegate =
+ new DummyBrightnessControlDelegate(false);
+ GetController()->SetBrightnessControlDelegate(
+ scoped_ptr<BrightnessControlDelegate>(delegate).Pass());
+ EXPECT_EQ(0, delegate->handle_brightness_down_count());
+ EXPECT_FALSE(ProcessWithContext(brightness_down));
+ EXPECT_EQ(1, delegate->handle_brightness_down_count());
+ EXPECT_EQ(brightness_down, delegate->last_accelerator());
+ EXPECT_EQ(0, delegate->handle_brightness_up_count());
+ EXPECT_FALSE(ProcessWithContext(brightness_up));
+ EXPECT_EQ(1, delegate->handle_brightness_up_count());
+ EXPECT_EQ(brightness_up, delegate->last_accelerator());
+ }
+ {
+ DummyBrightnessControlDelegate* delegate =
+ new DummyBrightnessControlDelegate(true);
+ GetController()->SetBrightnessControlDelegate(
+ scoped_ptr<BrightnessControlDelegate>(delegate).Pass());
+ EXPECT_EQ(0, delegate->handle_brightness_down_count());
+ EXPECT_TRUE(ProcessWithContext(brightness_down));
+ EXPECT_EQ(1, delegate->handle_brightness_down_count());
+ EXPECT_EQ(brightness_down, delegate->last_accelerator());
+ EXPECT_EQ(0, delegate->handle_brightness_up_count());
+ EXPECT_TRUE(ProcessWithContext(brightness_up));
+ EXPECT_EQ(1, delegate->handle_brightness_up_count());
+ EXPECT_EQ(brightness_up, delegate->last_accelerator());
+ }
+ // Volume
+ const ui::Accelerator volume_mute(ui::VKEY_VOLUME_MUTE, ui::EF_NONE);
+ const ui::Accelerator volume_down(ui::VKEY_VOLUME_DOWN, ui::EF_NONE);
+ const ui::Accelerator volume_up(ui::VKEY_VOLUME_UP, ui::EF_NONE);
+ {
+ EXPECT_TRUE(ProcessWithContext(volume_mute));
+ EXPECT_TRUE(ProcessWithContext(volume_down));
+ EXPECT_TRUE(ProcessWithContext(volume_up));
+ DummyVolumeControlDelegate* delegate =
+ new DummyVolumeControlDelegate(false);
+ ash::Shell::GetInstance()->system_tray_delegate()->SetVolumeControlDelegate(
+ scoped_ptr<VolumeControlDelegate>(delegate).Pass());
+ EXPECT_EQ(0, delegate->handle_volume_mute_count());
+ EXPECT_FALSE(ProcessWithContext(volume_mute));
+ EXPECT_EQ(1, delegate->handle_volume_mute_count());
+ EXPECT_EQ(volume_mute, delegate->last_accelerator());
+ EXPECT_EQ(0, delegate->handle_volume_down_count());
+ EXPECT_FALSE(ProcessWithContext(volume_down));
+ EXPECT_EQ(1, delegate->handle_volume_down_count());
+ EXPECT_EQ(volume_down, delegate->last_accelerator());
+ EXPECT_EQ(0, delegate->handle_volume_up_count());
+ EXPECT_FALSE(ProcessWithContext(volume_up));
+ EXPECT_EQ(1, delegate->handle_volume_up_count());
+ EXPECT_EQ(volume_up, delegate->last_accelerator());
+ }
+ {
+ DummyVolumeControlDelegate* delegate = new DummyVolumeControlDelegate(true);
+ ash::Shell::GetInstance()->system_tray_delegate()->SetVolumeControlDelegate(
+ scoped_ptr<VolumeControlDelegate>(delegate).Pass());
+ EXPECT_EQ(0, delegate->handle_volume_mute_count());
+ EXPECT_TRUE(ProcessWithContext(volume_mute));
+ EXPECT_EQ(1, delegate->handle_volume_mute_count());
+ EXPECT_EQ(volume_mute, delegate->last_accelerator());
+ EXPECT_EQ(0, delegate->handle_volume_down_count());
+ EXPECT_TRUE(ProcessWithContext(volume_down));
+ EXPECT_EQ(1, delegate->handle_volume_down_count());
+ EXPECT_EQ(volume_down, delegate->last_accelerator());
+ EXPECT_EQ(0, delegate->handle_volume_up_count());
+ EXPECT_TRUE(ProcessWithContext(volume_up));
+ EXPECT_EQ(1, delegate->handle_volume_up_count());
+ EXPECT_EQ(volume_up, delegate->last_accelerator());
+ }
+}
+#endif
+
+} // namespace ash
diff --git a/chromium/ash/accelerators/accelerator_dispatcher.cc b/chromium/ash/accelerators/accelerator_dispatcher.cc
new file mode 100644
index 00000000000..91f01540f63
--- /dev/null
+++ b/chromium/ash/accelerators/accelerator_dispatcher.cc
@@ -0,0 +1,134 @@
+// Copyright (c) 2012 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.
+
+#include "ash/accelerators/accelerator_dispatcher.h"
+
+#if defined(USE_X11)
+#include <X11/Xlib.h>
+
+// Xlib defines RootWindow
+#ifdef RootWindow
+#undef RootWindow
+#endif
+#endif // defined(USE_X11)
+
+#include "ash/accelerators/accelerator_controller.h"
+#include "ash/shell.h"
+#include "ash/wm/event_rewriter_event_filter.h"
+#include "ui/aura/env.h"
+#include "ui/aura/root_window.h"
+#include "ui/base/accelerators/accelerator.h"
+#include "ui/base/events/event.h"
+#include "ui/base/events/event_constants.h"
+#include "ui/base/events/event_utils.h"
+#include "ui/views/controls/menu/menu_controller.h"
+
+namespace ash {
+namespace {
+
+const int kModifierMask = (ui::EF_SHIFT_DOWN |
+ ui::EF_CONTROL_DOWN |
+ ui::EF_ALT_DOWN);
+#if defined(OS_WIN)
+bool IsKeyEvent(const MSG& msg) {
+ return
+ msg.message == WM_KEYDOWN || msg.message == WM_SYSKEYDOWN ||
+ msg.message == WM_KEYUP || msg.message == WM_SYSKEYUP;
+}
+#elif defined(USE_X11)
+bool IsKeyEvent(const XEvent* xev) {
+ return xev->type == KeyPress || xev->type == KeyRelease;
+}
+#endif
+
+bool IsPossibleAcceleratorNotForMenu(const ui::KeyEvent& key_event) {
+ // For shortcuts generated by Ctrl or Alt plus a letter, number or
+ // the tab key, we want to exit the context menu first and then
+ // repost the event. That allows for the shortcut execution after
+ // the context menu has exited.
+ if (key_event.type() == ui::ET_KEY_PRESSED &&
+ (key_event.flags() & (ui::EF_CONTROL_DOWN | ui::EF_ALT_DOWN))) {
+ const ui::KeyboardCode key_code = key_event.key_code();
+ if ((key_code >= ui::VKEY_A && key_code <= ui::VKEY_Z) ||
+ (key_code >= ui::VKEY_0 && key_code <= ui::VKEY_9) ||
+ (key_code == ui::VKEY_TAB)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+} // namespace
+
+AcceleratorDispatcher::AcceleratorDispatcher(
+ base::MessageLoop::Dispatcher* nested_dispatcher,
+ aura::Window* associated_window)
+ : nested_dispatcher_(nested_dispatcher),
+ associated_window_(associated_window) {
+ DCHECK(nested_dispatcher_);
+ associated_window_->AddObserver(this);
+}
+
+AcceleratorDispatcher::~AcceleratorDispatcher() {
+ if (associated_window_)
+ associated_window_->RemoveObserver(this);
+}
+
+void AcceleratorDispatcher::OnWindowDestroying(aura::Window* window) {
+ if (associated_window_ == window)
+ associated_window_ = NULL;
+}
+
+bool AcceleratorDispatcher::Dispatch(const base::NativeEvent& event) {
+ if (!associated_window_)
+ return false;
+ if (!ui::IsNoopEvent(event) && !associated_window_->CanReceiveEvents())
+ return aura::Env::GetInstance()->GetDispatcher()->Dispatch(event);
+
+ if (IsKeyEvent(event)) {
+ // Modifiers can be changed by the user preference, so we need to rewrite
+ // the event explicitly.
+ ui::KeyEvent key_event(event, false);
+ ui::EventHandler* event_rewriter =
+ ash::Shell::GetInstance()->event_rewriter_filter();
+ DCHECK(event_rewriter);
+ event_rewriter->OnKeyEvent(&key_event);
+ if (key_event.stopped_propagation())
+ return true;
+
+ if (IsPossibleAcceleratorNotForMenu(key_event)) {
+ if (views::MenuController* menu_controller =
+ views::MenuController::GetActiveInstance()) {
+ menu_controller->CancelAll();
+#if defined(USE_X11)
+ XPutBackEvent(event->xany.display, event);
+#else
+ NOTIMPLEMENTED() << " Repost NativeEvent here.";
+#endif
+ return false;
+ }
+ }
+
+ ash::AcceleratorController* accelerator_controller =
+ ash::Shell::GetInstance()->accelerator_controller();
+ if (accelerator_controller) {
+ ui::Accelerator accelerator(key_event.key_code(),
+ key_event.flags() & kModifierMask);
+ if (key_event.type() == ui::ET_KEY_RELEASED)
+ accelerator.set_type(ui::ET_KEY_RELEASED);
+ // Fill out context object so AcceleratorController will know what
+ // was the previous accelerator or if the current accelerator is repeated.
+ Shell::GetInstance()->accelerator_controller()->context()->
+ UpdateContext(accelerator);
+ if (accelerator_controller->Process(accelerator))
+ return true;
+ }
+
+ return nested_dispatcher_->Dispatch(key_event.native_event());
+ }
+
+ return nested_dispatcher_->Dispatch(event);
+}
+
+} // namespace ash
diff --git a/chromium/ash/accelerators/accelerator_dispatcher.h b/chromium/ash/accelerators/accelerator_dispatcher.h
new file mode 100644
index 00000000000..c0dfb03a0d4
--- /dev/null
+++ b/chromium/ash/accelerators/accelerator_dispatcher.h
@@ -0,0 +1,47 @@
+// Copyright (c) 2012 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 ASH_ACCELERATORS_ACCELERATOR_DISPATCHER_H_
+#define ASH_ACCELERATORS_ACCELERATOR_DISPATCHER_H_
+
+#include "ash/ash_export.h"
+#include "base/message_loop/message_loop.h"
+#include "ui/aura/window.h"
+#include "ui/aura/window_observer.h"
+
+namespace ash {
+
+// Dispatcher for handling accelerators from menu.
+//
+// Wraps a nested dispatcher to which control is passed if no accelerator key
+// has been pressed.
+// TODO(pkotwicz): Port AcceleratorDispatcher to mac.
+// TODO(pkotwicz): Add support for a |nested_dispatcher| which sends
+// events to a system IME.
+class ASH_EXPORT AcceleratorDispatcher : public base::MessageLoop::Dispatcher,
+ public aura::WindowObserver {
+ public:
+ AcceleratorDispatcher(base::MessageLoop::Dispatcher* nested_dispatcher,
+ aura::Window* associated_window);
+ virtual ~AcceleratorDispatcher();
+
+ // MessageLoop::Dispatcher overrides:
+ virtual bool Dispatch(const base::NativeEvent& event) OVERRIDE;
+
+ // aura::WindowObserver overrides:
+ virtual void OnWindowDestroying(aura::Window* window) OVERRIDE;
+
+ private:
+ base::MessageLoop::Dispatcher* nested_dispatcher_;
+
+ // Window associated with |nested_dispatcher_| which is used to determine
+ // whether the |nested_dispatcher_| is allowed to receive events.
+ aura::Window* associated_window_;
+
+ DISALLOW_COPY_AND_ASSIGN(AcceleratorDispatcher);
+};
+
+} // namespace ash
+
+#endif // ASH_ACCELERATORS_ACCELERATOR_DISPATCHER_H_
diff --git a/chromium/ash/accelerators/accelerator_filter.cc b/chromium/ash/accelerators/accelerator_filter.cc
new file mode 100644
index 00000000000..892beabff26
--- /dev/null
+++ b/chromium/ash/accelerators/accelerator_filter.cc
@@ -0,0 +1,94 @@
+// Copyright (c) 2012 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.
+
+#include "ash/accelerators/accelerator_filter.h"
+
+#include "ash/accelerators/accelerator_controller.h"
+#include "ash/shell.h"
+#include "ash/wm/window_util.h"
+#include "ui/aura/root_window.h"
+#include "ui/base/accelerators/accelerator.h"
+#include "ui/base/accelerators/accelerator_manager.h"
+#include "ui/base/events/event.h"
+
+namespace ash {
+namespace {
+
+const int kModifierFlagMask = (ui::EF_SHIFT_DOWN |
+ ui::EF_CONTROL_DOWN |
+ ui::EF_ALT_DOWN);
+
+// Returns true if the |accelerator| should be processed now, inside Ash's env
+// event filter.
+bool ShouldProcessAcceleratorsNow(const ui::Accelerator& accelerator,
+ aura::Window* target) {
+ if (!target)
+ return true;
+
+ Shell::RootWindowList root_windows = Shell::GetAllRootWindows();
+ if (std::find(root_windows.begin(), root_windows.end(), target) !=
+ root_windows.end())
+ return true;
+
+ // A full screen window should be able to handle all key events including the
+ // reserved ones.
+ if (wm::IsWindowFullscreen(target)) {
+ // TODO(yusukes): On Chrome OS, only browser and flash windows can be full
+ // screen. Launching an app in "open full-screen" mode is not supported yet.
+ // That makes the IsWindowFullscreen() check above almost meaningless
+ // because a browser and flash window do handle Ash accelerators anyway
+ // before they're passed to a page or flash content.
+ return false;
+ }
+
+ if (Shell::GetInstance()->GetAppListTargetVisibility())
+ return true;
+
+ // Unless |target| is in the full screen state, handle reserved accelerators
+ // such as Alt+Tab now.
+ return Shell::GetInstance()->accelerator_controller()->IsReservedAccelerator(
+ accelerator);
+}
+
+} // namespace
+
+namespace internal {
+
+////////////////////////////////////////////////////////////////////////////////
+// AcceleratorFilter, public:
+
+AcceleratorFilter::AcceleratorFilter() {
+}
+
+AcceleratorFilter::~AcceleratorFilter() {
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// AcceleratorFilter, EventFilter implementation:
+
+void AcceleratorFilter::OnKeyEvent(ui::KeyEvent* event) {
+ const ui::EventType type = event->type();
+ if (type != ui::ET_KEY_PRESSED && type != ui::ET_KEY_RELEASED)
+ return;
+ if (event->is_char())
+ return;
+
+ ui::Accelerator accelerator(event->key_code(),
+ event->flags() & kModifierFlagMask);
+ accelerator.set_type(type);
+
+ // Fill out context object so AcceleratorController will know what
+ // was the previous accelerator or if the current accelerator is repeated.
+ Shell::GetInstance()->accelerator_controller()->context()->
+ UpdateContext(accelerator);
+
+ aura::Window* target = static_cast<aura::Window*>(event->target());
+ if (!ShouldProcessAcceleratorsNow(accelerator, target))
+ return;
+ if (Shell::GetInstance()->accelerator_controller()->Process(accelerator))
+ event->StopPropagation();
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/accelerators/accelerator_filter.h b/chromium/ash/accelerators/accelerator_filter.h
new file mode 100644
index 00000000000..a43241a7f54
--- /dev/null
+++ b/chromium/ash/accelerators/accelerator_filter.h
@@ -0,0 +1,33 @@
+// Copyright (c) 2012 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 ASH_ACCELERATORS_ACCELERATOR_FILTER_H_
+#define ASH_ACCELERATORS_ACCELERATOR_FILTER_H_
+
+#include "ash/ash_export.h"
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "ui/base/events/event_handler.h"
+
+namespace ash {
+namespace internal {
+
+// AcceleratorFilter filters key events for AcceleratorControler handling global
+// keyboard accelerators.
+class ASH_EXPORT AcceleratorFilter : public ui::EventHandler {
+ public:
+ AcceleratorFilter();
+ virtual ~AcceleratorFilter();
+
+ // Overridden from ui::EventHandler:
+ virtual void OnKeyEvent(ui::KeyEvent* event) OVERRIDE;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(AcceleratorFilter);
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_ACCELERATORS_ACCELERATOR_FILTER_H_
diff --git a/chromium/ash/accelerators/accelerator_filter_unittest.cc b/chromium/ash/accelerators/accelerator_filter_unittest.cc
new file mode 100644
index 00000000000..efeb90bf8d5
--- /dev/null
+++ b/chromium/ash/accelerators/accelerator_filter_unittest.cc
@@ -0,0 +1,128 @@
+// Copyright (c) 2012 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.
+
+#include "ash/accelerators/accelerator_filter.h"
+
+#include "ash/accelerators/accelerator_controller.h"
+#include "ash/screenshot_delegate.h"
+#include "ash/shell.h"
+#include "ash/shell_window_ids.h"
+#include "ash/test/ash_test_base.h"
+#include "ash/wm/window_util.h"
+#include "base/memory/scoped_ptr.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/aura/test/aura_test_base.h"
+#include "ui/aura/test/event_generator.h"
+#include "ui/aura/test/test_windows.h"
+#include "ui/aura/window.h"
+#include "ui/gfx/rect.h"
+
+namespace ash {
+namespace test {
+
+namespace {
+
+class DummyScreenshotDelegate : public ScreenshotDelegate {
+ public:
+ DummyScreenshotDelegate() : handle_take_screenshot_count_(0) {
+ }
+ virtual ~DummyScreenshotDelegate() {}
+
+ // Overridden from ScreenshotDelegate:
+ virtual void HandleTakeScreenshotForAllRootWindows() OVERRIDE {
+ ++handle_take_screenshot_count_;
+ }
+
+ virtual void HandleTakePartialScreenshot(
+ aura::Window* window, const gfx::Rect& rect) OVERRIDE {
+ // Do nothing because it's not tested yet.
+ }
+
+ virtual bool CanTakeScreenshot() OVERRIDE {
+ return true;
+ }
+
+ int handle_take_screenshot_count() const {
+ return handle_take_screenshot_count_;
+ }
+
+ private:
+ int handle_take_screenshot_count_;
+
+ DISALLOW_COPY_AND_ASSIGN(DummyScreenshotDelegate);
+};
+
+AcceleratorController* GetController() {
+ return Shell::GetInstance()->accelerator_controller();
+}
+
+} // namespace
+
+typedef AshTestBase AcceleratorFilterTest;
+
+// Tests if AcceleratorFilter works without a focused window.
+TEST_F(AcceleratorFilterTest, TestFilterWithoutFocus) {
+ DummyScreenshotDelegate* delegate = new DummyScreenshotDelegate;
+ GetController()->SetScreenshotDelegate(
+ scoped_ptr<ScreenshotDelegate>(delegate).Pass());
+ EXPECT_EQ(0, delegate->handle_take_screenshot_count());
+
+ aura::test::EventGenerator generator(Shell::GetPrimaryRootWindow());
+ // AcceleratorController calls ScreenshotDelegate::HandleTakeScreenshot() when
+ // VKEY_PRINT is pressed. See kAcceleratorData[] in accelerator_controller.cc.
+ generator.PressKey(ui::VKEY_PRINT, 0);
+ EXPECT_EQ(1, delegate->handle_take_screenshot_count());
+ generator.ReleaseKey(ui::VKEY_PRINT, 0);
+ EXPECT_EQ(1, delegate->handle_take_screenshot_count());
+}
+
+// Tests if AcceleratorFilter works as expected with a focused window.
+TEST_F(AcceleratorFilterTest, TestFilterWithFocus) {
+ aura::test::TestWindowDelegate test_delegate;
+ scoped_ptr<aura::Window> window(CreateTestWindowInShellWithDelegate(
+ &test_delegate,
+ -1,
+ gfx::Rect()));
+ wm::ActivateWindow(window.get());
+
+ DummyScreenshotDelegate* delegate = new DummyScreenshotDelegate;
+ GetController()->SetScreenshotDelegate(
+ scoped_ptr<ScreenshotDelegate>(delegate).Pass());
+ EXPECT_EQ(0, delegate->handle_take_screenshot_count());
+
+ // AcceleratorFilter should ignore the key events since the root window is
+ // not focused.
+ aura::test::EventGenerator generator(Shell::GetPrimaryRootWindow());
+ generator.PressKey(ui::VKEY_PRINT, 0);
+ EXPECT_EQ(0, delegate->handle_take_screenshot_count());
+ generator.ReleaseKey(ui::VKEY_PRINT, 0);
+ EXPECT_EQ(0, delegate->handle_take_screenshot_count());
+
+ // Reset window before |test_delegate| gets deleted.
+ window.reset();
+}
+
+// Tests if AcceleratorFilter ignores the flag for Caps Lock.
+TEST_F(AcceleratorFilterTest, TestCapsLockMask) {
+ DummyScreenshotDelegate* delegate = new DummyScreenshotDelegate;
+ GetController()->SetScreenshotDelegate(
+ scoped_ptr<ScreenshotDelegate>(delegate).Pass());
+ EXPECT_EQ(0, delegate->handle_take_screenshot_count());
+
+ aura::test::EventGenerator generator(Shell::GetPrimaryRootWindow());
+ generator.PressKey(ui::VKEY_PRINT, 0);
+ EXPECT_EQ(1, delegate->handle_take_screenshot_count());
+ generator.ReleaseKey(ui::VKEY_PRINT, 0);
+ EXPECT_EQ(1, delegate->handle_take_screenshot_count());
+
+ // Check if AcceleratorFilter ignores the mask for Caps Lock. Note that there
+ // is no ui::EF_ mask for Num Lock.
+ generator.PressKey(ui::VKEY_PRINT, ui::EF_CAPS_LOCK_DOWN);
+ EXPECT_EQ(2, delegate->handle_take_screenshot_count());
+ generator.ReleaseKey(ui::VKEY_PRINT, ui::EF_CAPS_LOCK_DOWN);
+ EXPECT_EQ(2, delegate->handle_take_screenshot_count());
+}
+
+} // namespace test
+} // namespace ash
diff --git a/chromium/ash/accelerators/accelerator_table.cc b/chromium/ash/accelerators/accelerator_table.cc
new file mode 100644
index 00000000000..982176acc33
--- /dev/null
+++ b/chromium/ash/accelerators/accelerator_table.cc
@@ -0,0 +1,413 @@
+// Copyright (c) 2012 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.
+
+#include "ash/accelerators/accelerator_table.h"
+
+#include "base/basictypes.h"
+
+namespace ash {
+
+const AcceleratorData kAcceleratorData[] = {
+ // We have to define 3 entries for Shift+Alt. VKEY_[LR]MENU might be sent to
+ // the accelerator controller when RenderWidgetHostViewAura is focused, and
+ // VKEY_MENU might be when it's not (e.g. when NativeWidgetAura is focused).
+ { false, ui::VKEY_LMENU, ui::EF_SHIFT_DOWN, NEXT_IME },
+ { false, ui::VKEY_MENU, ui::EF_SHIFT_DOWN, NEXT_IME },
+ { false, ui::VKEY_RMENU, ui::EF_SHIFT_DOWN, NEXT_IME },
+ // The same is true for Alt+Shift.
+ { false, ui::VKEY_LSHIFT, ui::EF_ALT_DOWN, NEXT_IME },
+ { false, ui::VKEY_SHIFT, ui::EF_ALT_DOWN, NEXT_IME },
+ { false, ui::VKEY_RSHIFT, ui::EF_ALT_DOWN, NEXT_IME },
+ // Single shift release turns off caps lock.
+ { false, ui::VKEY_LSHIFT, ui::EF_NONE, DISABLE_CAPS_LOCK },
+ { false, ui::VKEY_SHIFT, ui::EF_NONE, DISABLE_CAPS_LOCK },
+ { false, ui::VKEY_RSHIFT, ui::EF_NONE, DISABLE_CAPS_LOCK },
+
+ { true, ui::VKEY_SPACE, ui::EF_CONTROL_DOWN, PREVIOUS_IME },
+ { false, ui::VKEY_SPACE, ui::EF_CONTROL_DOWN, PREVIOUS_IME },
+ // Shortcuts for Japanese IME.
+ { true, ui::VKEY_CONVERT, ui::EF_NONE, SWITCH_IME },
+ { true, ui::VKEY_NONCONVERT, ui::EF_NONE, SWITCH_IME },
+ { true, ui::VKEY_DBE_SBCSCHAR, ui::EF_NONE, SWITCH_IME },
+ { true, ui::VKEY_DBE_DBCSCHAR, ui::EF_NONE, SWITCH_IME },
+ // Shortcut for Koren IME.
+ { true, ui::VKEY_HANGUL, ui::EF_NONE, SWITCH_IME },
+
+ { true, ui::VKEY_TAB, ui::EF_ALT_DOWN, CYCLE_FORWARD_MRU },
+ { true, ui::VKEY_TAB, ui::EF_SHIFT_DOWN | ui::EF_ALT_DOWN,
+ CYCLE_BACKWARD_MRU },
+ { true, ui::VKEY_MEDIA_LAUNCH_APP1, ui::EF_NONE,
+ CYCLE_FORWARD_LINEAR },
+#if defined(OS_CHROMEOS)
+ { true, ui::VKEY_BROWSER_SEARCH, ui::EF_NONE, TOGGLE_APP_LIST },
+ { true, ui::VKEY_WLAN, ui::EF_NONE, TOGGLE_WIFI },
+ { true, ui::VKEY_KBD_BRIGHTNESS_DOWN, ui::EF_NONE, KEYBOARD_BRIGHTNESS_DOWN },
+ { true, ui::VKEY_KBD_BRIGHTNESS_UP, ui::EF_NONE, KEYBOARD_BRIGHTNESS_UP },
+ // Maximize button.
+ { true, ui::VKEY_MEDIA_LAUNCH_APP2, ui::EF_CONTROL_DOWN, TOGGLE_MIRROR_MODE },
+ { true, ui::VKEY_MEDIA_LAUNCH_APP2, ui::EF_ALT_DOWN, SWAP_PRIMARY_DISPLAY },
+ // Cycle windows button.
+ { true, ui::VKEY_MEDIA_LAUNCH_APP1, ui::EF_CONTROL_DOWN, TAKE_SCREENSHOT },
+ { true, ui::VKEY_MEDIA_LAUNCH_APP1, ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN,
+ TAKE_PARTIAL_SCREENSHOT },
+ { true, ui::VKEY_BRIGHTNESS_DOWN, ui::EF_NONE, BRIGHTNESS_DOWN },
+ { true, ui::VKEY_BRIGHTNESS_DOWN, ui::EF_ALT_DOWN, KEYBOARD_BRIGHTNESS_DOWN },
+ { true, ui::VKEY_BRIGHTNESS_UP, ui::EF_NONE, BRIGHTNESS_UP },
+ { true, ui::VKEY_BRIGHTNESS_UP, ui::EF_ALT_DOWN, KEYBOARD_BRIGHTNESS_UP },
+ { true, ui::VKEY_BRIGHTNESS_DOWN, ui::EF_CONTROL_DOWN | ui::EF_ALT_DOWN,
+ MAGNIFY_SCREEN_ZOOM_OUT},
+ { true, ui::VKEY_BRIGHTNESS_UP, ui::EF_CONTROL_DOWN | ui::EF_ALT_DOWN,
+ MAGNIFY_SCREEN_ZOOM_IN},
+ { true, ui::VKEY_L, ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN, LOCK_SCREEN },
+ // The lock key on Chrome OS keyboards produces F13 scancodes.
+ { true, ui::VKEY_F13, ui::EF_NONE, LOCK_PRESSED },
+ { false, ui::VKEY_F13, ui::EF_NONE, LOCK_RELEASED },
+ { true, ui::VKEY_POWER, ui::EF_NONE, POWER_PRESSED },
+ { false, ui::VKEY_POWER, ui::EF_NONE, POWER_RELEASED },
+ { true, ui::VKEY_O, ui::EF_CONTROL_DOWN, OPEN_FILE_DIALOG },
+ { true, ui::VKEY_M, ui::EF_SHIFT_DOWN | ui::EF_ALT_DOWN,
+ OPEN_FILE_MANAGER },
+ { true, ui::VKEY_T, ui::EF_CONTROL_DOWN | ui::EF_ALT_DOWN, OPEN_CROSH },
+ { true, ui::VKEY_G, ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN | ui::EF_ALT_DOWN,
+ DISABLE_GPU_WATCHDOG },
+ { true, ui::VKEY_I, ui::EF_CONTROL_DOWN | ui::EF_ALT_DOWN,
+ TOUCH_HUD_MODE_CHANGE },
+ { true, ui::VKEY_I, ui::EF_CONTROL_DOWN | ui::EF_ALT_DOWN | ui::EF_SHIFT_DOWN,
+ TOUCH_HUD_CLEAR },
+ { true, ui::VKEY_9, ui::EF_CONTROL_DOWN | ui::EF_SHIFT_DOWN,
+ TOUCH_HUD_PROJECTION_TOGGLE },
+ // Accessibility: Spoken feedback shortcuts. The first one is to toggle
+ // spoken feedback on or off. The others are only valid when
+ // spoken feedback is enabled.
+ { true, ui::VKEY_Z, ui::EF_CONTROL_DOWN | ui::EF_ALT_DOWN,
+ TOGGLE_SPOKEN_FEEDBACK },
+ { true, ui::VKEY_CONTROL, ui::EF_CONTROL_DOWN, SILENCE_SPOKEN_FEEDBACK},
+#endif // defined(OS_CHROMEOS)
+ { true, ui::VKEY_I, ui::EF_SHIFT_DOWN | ui::EF_ALT_DOWN, OPEN_FEEDBACK_PAGE },
+#if !defined(OS_WIN)
+ { true, ui::VKEY_Q, ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN, EXIT },
+#endif
+ { true, ui::VKEY_N, ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN,
+ NEW_INCOGNITO_WINDOW },
+ { true, ui::VKEY_N, ui::EF_CONTROL_DOWN, NEW_WINDOW },
+ { true, ui::VKEY_T, ui::EF_CONTROL_DOWN, NEW_TAB },
+ { true, ui::VKEY_OEM_MINUS,
+ ui::EF_CONTROL_DOWN | ui::EF_SHIFT_DOWN, SCALE_UI_UP },
+ { true, ui::VKEY_OEM_PLUS,
+ ui::EF_CONTROL_DOWN | ui::EF_SHIFT_DOWN, SCALE_UI_DOWN },
+ { true, ui::VKEY_0,
+ ui::EF_CONTROL_DOWN | ui::EF_SHIFT_DOWN, SCALE_UI_RESET },
+ { true, ui::VKEY_BROWSER_REFRESH,
+ ui::EF_CONTROL_DOWN | ui::EF_SHIFT_DOWN, ROTATE_SCREEN },
+ { true, ui::VKEY_BROWSER_REFRESH,
+ ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN | ui::EF_ALT_DOWN,
+ ROTATE_WINDOW },
+ { true, ui::VKEY_MEDIA_LAUNCH_APP1, ui::EF_SHIFT_DOWN,
+ CYCLE_BACKWARD_LINEAR },
+ { true, ui::VKEY_T, ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN, RESTORE_TAB },
+ { true, ui::VKEY_PRINT, ui::EF_NONE, TAKE_SCREENSHOT },
+ // On Chrome OS, Search key is mapped to LWIN. The Search key binding should
+ // act on release instead of press when using Search as a modifier key for
+ // extended keyboard shortcuts.
+ { false, ui::VKEY_LWIN, ui::EF_NONE, TOGGLE_APP_LIST },
+ { false, ui::VKEY_LWIN, ui::EF_ALT_DOWN, TOGGLE_CAPS_LOCK },
+ { true, ui::VKEY_MEDIA_LAUNCH_APP2, ui::EF_NONE, TOGGLE_FULLSCREEN },
+ { true, ui::VKEY_MEDIA_LAUNCH_APP2, ui::EF_SHIFT_DOWN, TOGGLE_FULLSCREEN },
+ { true, ui::VKEY_VOLUME_MUTE, ui::EF_NONE, VOLUME_MUTE },
+ { true, ui::VKEY_VOLUME_DOWN, ui::EF_NONE, VOLUME_DOWN },
+ { true, ui::VKEY_VOLUME_UP, ui::EF_NONE, VOLUME_UP },
+ { true, ui::VKEY_L, ui::EF_SHIFT_DOWN | ui::EF_ALT_DOWN, FOCUS_LAUNCHER },
+ { true, ui::VKEY_HELP, ui::EF_NONE, SHOW_KEYBOARD_OVERLAY },
+ { true, ui::VKEY_OEM_2, ui::EF_CONTROL_DOWN | ui::EF_ALT_DOWN,
+ SHOW_KEYBOARD_OVERLAY },
+ { true, ui::VKEY_OEM_2,
+ ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN | ui::EF_ALT_DOWN,
+ SHOW_KEYBOARD_OVERLAY },
+ { true, ui::VKEY_F14, ui::EF_NONE, SHOW_KEYBOARD_OVERLAY },
+ { true, ui::VKEY_N, ui::EF_SHIFT_DOWN | ui::EF_ALT_DOWN,
+ SHOW_MESSAGE_CENTER_BUBBLE },
+ { true, ui::VKEY_BROWSER_BACK,
+ ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN | ui::EF_ALT_DOWN,
+ SHOW_OAK },
+ { true, ui::VKEY_S, ui::EF_SHIFT_DOWN | ui::EF_ALT_DOWN,
+ SHOW_SYSTEM_TRAY_BUBBLE },
+ { true, ui::VKEY_ESCAPE, ui::EF_SHIFT_DOWN, SHOW_TASK_MANAGER },
+ { true, ui::VKEY_1, ui::EF_ALT_DOWN, LAUNCH_APP_0 },
+ { true, ui::VKEY_2, ui::EF_ALT_DOWN, LAUNCH_APP_1 },
+ { true, ui::VKEY_3, ui::EF_ALT_DOWN, LAUNCH_APP_2 },
+ { true, ui::VKEY_4, ui::EF_ALT_DOWN, LAUNCH_APP_3 },
+ { true, ui::VKEY_5, ui::EF_ALT_DOWN, LAUNCH_APP_4 },
+ { true, ui::VKEY_6, ui::EF_ALT_DOWN, LAUNCH_APP_5 },
+ { true, ui::VKEY_7, ui::EF_ALT_DOWN, LAUNCH_APP_6 },
+ { true, ui::VKEY_8, ui::EF_ALT_DOWN, LAUNCH_APP_7 },
+ { true, ui::VKEY_9, ui::EF_ALT_DOWN, LAUNCH_LAST_APP },
+
+ // Window management shortcuts.
+ { true, ui::VKEY_OEM_4, ui::EF_ALT_DOWN, WINDOW_SNAP_LEFT },
+ { true, ui::VKEY_OEM_6, ui::EF_ALT_DOWN, WINDOW_SNAP_RIGHT },
+ { true, ui::VKEY_OEM_MINUS, ui::EF_ALT_DOWN, WINDOW_MINIMIZE },
+ // Convenience for users switching from Mac OS.
+ { true, ui::VKEY_M, ui::EF_CONTROL_DOWN, WINDOW_MINIMIZE },
+ { true, ui::VKEY_OEM_PLUS, ui::EF_ALT_DOWN, TOGGLE_MAXIMIZED },
+ { true, ui::VKEY_OEM_PLUS, ui::EF_SHIFT_DOWN | ui::EF_ALT_DOWN,
+ WINDOW_POSITION_CENTER },
+ { true, ui::VKEY_BROWSER_FORWARD, ui::EF_CONTROL_DOWN, FOCUS_NEXT_PANE },
+ { true, ui::VKEY_BROWSER_BACK, ui::EF_CONTROL_DOWN, FOCUS_PREVIOUS_PANE },
+
+ // Media Player shortcuts.
+ { true, ui::VKEY_MEDIA_NEXT_TRACK, ui::EF_NONE, MEDIA_NEXT_TRACK},
+ { true, ui::VKEY_MEDIA_PLAY_PAUSE, ui::EF_NONE, MEDIA_PLAY_PAUSE},
+ { true, ui::VKEY_MEDIA_PREV_TRACK, ui::EF_NONE, MEDIA_PREV_TRACK},
+
+ // Debugging shortcuts that need to be available to end-users in
+ // release builds.
+ { true, ui::VKEY_U, ui::EF_CONTROL_DOWN | ui::EF_ALT_DOWN | ui::EF_SHIFT_DOWN,
+ PRINT_UI_HIERARCHIES },
+
+ { false, ui::VKEY_HOME, ui::EF_SHIFT_DOWN, ACCESSIBLE_FOCUS_PREVIOUS},
+ { false, ui::VKEY_PRIOR, ui::EF_SHIFT_DOWN, ACCESSIBLE_FOCUS_PREVIOUS},
+ { false, ui::VKEY_END, ui::EF_SHIFT_DOWN, ACCESSIBLE_FOCUS_NEXT},
+ { false, ui::VKEY_NEXT, ui::EF_SHIFT_DOWN, ACCESSIBLE_FOCUS_NEXT},
+
+ // TODO(yusukes): Handle VKEY_MEDIA_STOP, and
+ // VKEY_MEDIA_LAUNCH_MAIL.
+};
+
+const size_t kAcceleratorDataLength = arraysize(kAcceleratorData);
+
+#if !defined(NDEBUG)
+const AcceleratorData kDesktopAcceleratorData[] = {
+#if defined(OS_CHROMEOS)
+ // Extra shortcut for debug build to control magnifier on linux desktop.
+ { true, ui::VKEY_BRIGHTNESS_DOWN, ui::EF_CONTROL_DOWN,
+ MAGNIFY_SCREEN_ZOOM_OUT},
+ { true, ui::VKEY_BRIGHTNESS_UP, ui::EF_CONTROL_DOWN, MAGNIFY_SCREEN_ZOOM_IN},
+ // Extra shortcuts to lock the screen on linux desktop.
+ { true, ui::VKEY_L, ui::EF_ALT_DOWN, LOCK_SCREEN },
+ { true, ui::VKEY_POWER, ui::EF_SHIFT_DOWN, LOCK_PRESSED },
+ { false, ui::VKEY_POWER, ui::EF_SHIFT_DOWN, LOCK_RELEASED },
+ { true, ui::VKEY_D, ui::EF_CONTROL_DOWN | ui::EF_SHIFT_DOWN,
+ ADD_REMOVE_DISPLAY },
+ { true, ui::VKEY_M, ui::EF_CONTROL_DOWN | ui::EF_SHIFT_DOWN,
+ TOGGLE_MIRROR_MODE },
+ { true, ui::VKEY_W, ui::EF_CONTROL_DOWN | ui::EF_ALT_DOWN, TOGGLE_WIFI },
+ // Extra shortcut for display swaping as alt-f4 is taken on linux desktop.
+ { true, ui::VKEY_S, ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN,
+ SWAP_PRIMARY_DISPLAY },
+#endif
+ // Extra shortcut to rotate/scale up/down the screen on linux desktop.
+ { true, ui::VKEY_R,
+ ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN | ui::EF_ALT_DOWN, ROTATE_SCREEN },
+ // For testing on systems where Alt-Tab is already mapped.
+ { true, ui::VKEY_W, ui::EF_ALT_DOWN, CYCLE_FORWARD_MRU },
+
+ { true, ui::VKEY_F11, ui::EF_CONTROL_DOWN, TOGGLE_ROOT_WINDOW_FULL_SCREEN },
+ { true, ui::VKEY_W, ui::EF_SHIFT_DOWN | ui::EF_ALT_DOWN,
+ CYCLE_BACKWARD_MRU },
+ { true, ui::VKEY_B, ui::EF_CONTROL_DOWN | ui::EF_ALT_DOWN,
+ TOGGLE_DESKTOP_BACKGROUND_MODE },
+};
+
+const size_t kDesktopAcceleratorDataLength = arraysize(kDesktopAcceleratorData);
+#endif
+
+const AcceleratorData kDebugAcceleratorData[] = {
+ { true, ui::VKEY_L, ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN | ui::EF_ALT_DOWN,
+ PRINT_LAYER_HIERARCHY },
+ { true, ui::VKEY_V, ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN | ui::EF_ALT_DOWN,
+ PRINT_VIEW_HIERARCHY },
+ { true, ui::VKEY_W, ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN | ui::EF_ALT_DOWN,
+ PRINT_WINDOW_HIERARCHY },
+ { true, ui::VKEY_S, ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN | ui::EF_ALT_DOWN,
+ DEBUG_TOGGLE_DEVICE_SCALE_FACTOR },
+ { true, ui::VKEY_B, ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN | ui::EF_ALT_DOWN,
+ DEBUG_TOGGLE_SHOW_DEBUG_BORDERS },
+ { true, ui::VKEY_F, ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN | ui::EF_ALT_DOWN,
+ DEBUG_TOGGLE_SHOW_FPS_COUNTER },
+ { true, ui::VKEY_P, ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN | ui::EF_ALT_DOWN,
+ DEBUG_TOGGLE_SHOW_PAINT_RECTS },
+};
+
+const size_t kDebugAcceleratorDataLength = arraysize(kDebugAcceleratorData);
+
+const AcceleratorAction kReservedActions[] = {
+ // Window cycling accelerators.
+ CYCLE_BACKWARD_MRU, // Shift+Alt+Tab
+ CYCLE_FORWARD_MRU, // Alt+Tab
+#if defined(OS_CHROMEOS)
+ POWER_PRESSED,
+ POWER_RELEASED,
+#endif
+};
+
+const size_t kReservedActionsLength = arraysize(kReservedActions);
+
+const AcceleratorAction kReservedDebugActions[] = {
+ PRINT_LAYER_HIERARCHY,
+ PRINT_VIEW_HIERARCHY,
+ PRINT_WINDOW_HIERARCHY,
+ DEBUG_TOGGLE_DEVICE_SCALE_FACTOR,
+ DEBUG_TOGGLE_SHOW_DEBUG_BORDERS,
+ DEBUG_TOGGLE_SHOW_FPS_COUNTER,
+ DEBUG_TOGGLE_SHOW_PAINT_RECTS,
+};
+
+const size_t kReservedDebugActionsLength = arraysize(kReservedDebugActions);
+
+const AcceleratorAction kActionsAllowedAtLoginOrLockScreen[] = {
+ BRIGHTNESS_DOWN,
+ BRIGHTNESS_UP,
+ DISABLE_CAPS_LOCK,
+ KEYBOARD_BRIGHTNESS_DOWN,
+ KEYBOARD_BRIGHTNESS_UP,
+ MAGNIFY_SCREEN_ZOOM_IN, // Control+F7
+ MAGNIFY_SCREEN_ZOOM_OUT, // Control+F6
+ NEXT_IME,
+ PREVIOUS_IME,
+ PRINT_LAYER_HIERARCHY,
+ PRINT_UI_HIERARCHIES,
+ PRINT_VIEW_HIERARCHY,
+ PRINT_WINDOW_HIERARCHY,
+ ROTATE_WINDOW,
+ SWITCH_IME, // Switch to another IME depending on the accelerator.
+ TAKE_PARTIAL_SCREENSHOT,
+ TAKE_SCREENSHOT,
+ TOGGLE_CAPS_LOCK,
+ TOGGLE_WIFI,
+ TOUCH_HUD_CLEAR,
+ VOLUME_DOWN,
+ VOLUME_MUTE,
+ VOLUME_UP,
+#if defined(OS_CHROMEOS)
+ TOGGLE_SPOKEN_FEEDBACK,
+ ADD_REMOVE_DISPLAY,
+ DISABLE_GPU_WATCHDOG,
+ TOGGLE_MIRROR_MODE,
+#endif
+#if defined(OS_CHROMEOS) && !defined(NDEBUG)
+ POWER_PRESSED,
+ POWER_RELEASED,
+#endif // defined(OS_CHROMEOS)
+};
+
+const size_t kActionsAllowedAtLoginOrLockScreenLength =
+ arraysize(kActionsAllowedAtLoginOrLockScreen);
+
+const AcceleratorAction kActionsAllowedAtLockScreen[] = {
+ EXIT,
+};
+
+const size_t kActionsAllowedAtLockScreenLength =
+ arraysize(kActionsAllowedAtLockScreen);
+
+const AcceleratorAction kActionsAllowedAtModalWindow[] = {
+ BRIGHTNESS_DOWN,
+ BRIGHTNESS_UP,
+ DISABLE_CAPS_LOCK,
+ EXIT,
+ KEYBOARD_BRIGHTNESS_DOWN,
+ KEYBOARD_BRIGHTNESS_UP,
+ MAGNIFY_SCREEN_ZOOM_IN,
+ MAGNIFY_SCREEN_ZOOM_OUT,
+ MEDIA_NEXT_TRACK,
+ MEDIA_PLAY_PAUSE,
+ MEDIA_PREV_TRACK,
+ NEXT_IME,
+ OPEN_FEEDBACK_PAGE,
+ POWER_PRESSED,
+ POWER_RELEASED,
+ PREVIOUS_IME,
+ PRINT_UI_HIERARCHIES,
+ SHOW_KEYBOARD_OVERLAY,
+ SWITCH_IME,
+ TAKE_PARTIAL_SCREENSHOT,
+ TAKE_SCREENSHOT,
+ TOGGLE_CAPS_LOCK,
+ TOGGLE_WIFI,
+ VOLUME_DOWN,
+ VOLUME_MUTE,
+ VOLUME_UP,
+#if defined(OS_CHROMEOS)
+ SWAP_PRIMARY_DISPLAY,
+ TOGGLE_SPOKEN_FEEDBACK,
+#if !defined(NDEBUG)
+ ADD_REMOVE_DISPLAY,
+#endif
+ LOCK_SCREEN,
+ TOGGLE_MIRROR_MODE,
+#endif
+};
+
+const size_t kActionsAllowedAtModalWindowLength =
+ arraysize(kActionsAllowedAtModalWindow);
+
+const AcceleratorAction kNonrepeatableActions[] = {
+ // TODO(mazda): Add other actions which should not be repeated.
+ CYCLE_BACKWARD_LINEAR,
+ CYCLE_BACKWARD_MRU,
+ CYCLE_FORWARD_LINEAR,
+ CYCLE_FORWARD_MRU,
+ EXIT,
+ PRINT_UI_HIERARCHIES, // Don't fill the logs if the key is held down.
+ ROTATE_SCREEN,
+ ROTATE_WINDOW,
+ SCALE_UI_UP,
+ SCALE_UI_DOWN,
+ SCALE_UI_RESET,
+ TOGGLE_FULLSCREEN,
+ TOGGLE_MAXIMIZED,
+ WINDOW_MINIMIZE,
+};
+
+const size_t kNonrepeatableActionsLength =
+ arraysize(kNonrepeatableActions);
+
+const AcceleratorAction kActionsAllowedInAppMode[] = {
+ BRIGHTNESS_DOWN,
+ BRIGHTNESS_UP,
+ CYCLE_BACKWARD_LINEAR,
+ CYCLE_BACKWARD_MRU,
+ CYCLE_FORWARD_LINEAR,
+ CYCLE_FORWARD_MRU,
+ DISABLE_CAPS_LOCK,
+ EXIT,
+ KEYBOARD_BRIGHTNESS_DOWN,
+ KEYBOARD_BRIGHTNESS_UP,
+ MAGNIFY_SCREEN_ZOOM_IN, // Control+F7
+ MAGNIFY_SCREEN_ZOOM_OUT, // Control+F6
+ MEDIA_NEXT_TRACK,
+ MEDIA_PLAY_PAUSE,
+ MEDIA_PREV_TRACK,
+ NEXT_IME,
+ POWER_PRESSED,
+ POWER_RELEASED,
+ PREVIOUS_IME,
+ PRINT_LAYER_HIERARCHY,
+ PRINT_UI_HIERARCHIES,
+ PRINT_VIEW_HIERARCHY,
+ PRINT_WINDOW_HIERARCHY,
+ ROTATE_SCREEN,
+ SCALE_UI_DOWN,
+ SCALE_UI_RESET,
+ SCALE_UI_UP,
+ SWITCH_IME, // Switch to another IME depending on the accelerator.
+ TOGGLE_CAPS_LOCK,
+ TOGGLE_WIFI,
+ TOUCH_HUD_CLEAR,
+ VOLUME_DOWN,
+ VOLUME_MUTE,
+ VOLUME_UP,
+#if defined(OS_CHROMEOS)
+ SWAP_PRIMARY_DISPLAY,
+ TOGGLE_SPOKEN_FEEDBACK,
+ ADD_REMOVE_DISPLAY,
+ DISABLE_GPU_WATCHDOG,
+ TOGGLE_MIRROR_MODE,
+#endif // defined(OS_CHROMEOS)
+};
+
+const size_t kActionsAllowedInAppModeLength =
+ arraysize(kActionsAllowedInAppMode);
+
+} // namespace ash
diff --git a/chromium/ash/accelerators/accelerator_table.h b/chromium/ash/accelerators/accelerator_table.h
new file mode 100644
index 00000000000..8c400cefc0d
--- /dev/null
+++ b/chromium/ash/accelerators/accelerator_table.h
@@ -0,0 +1,187 @@
+// Copyright (c) 2012 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 ASH_ACCELERATORS_ACCELERATOR_TABLE_H_
+#define ASH_ACCELERATORS_ACCELERATOR_TABLE_H_
+
+#include "ash/ash_export.h"
+#include "base/basictypes.h"
+#include "ui/base/events/event_constants.h"
+#include "ui/base/keycodes/keyboard_codes.h"
+
+namespace ash {
+
+// Please put if/def sections at the end of the bare section and keep the list
+// within each section in alphabetical order.
+enum AcceleratorAction {
+ ACCESSIBLE_FOCUS_NEXT,
+ ACCESSIBLE_FOCUS_PREVIOUS,
+ BRIGHTNESS_DOWN,
+ BRIGHTNESS_UP,
+ CYCLE_BACKWARD_LINEAR,
+ CYCLE_BACKWARD_MRU,
+ CYCLE_FORWARD_LINEAR,
+ CYCLE_FORWARD_MRU,
+ DEBUG_TOGGLE_DEVICE_SCALE_FACTOR,
+ DEBUG_TOGGLE_SHOW_DEBUG_BORDERS,
+ DEBUG_TOGGLE_SHOW_FPS_COUNTER,
+ DEBUG_TOGGLE_SHOW_PAINT_RECTS,
+ DISABLE_CAPS_LOCK,
+ EXIT,
+ FOCUS_LAUNCHER,
+ FOCUS_NEXT_PANE,
+ FOCUS_PREVIOUS_PANE,
+ KEYBOARD_BRIGHTNESS_DOWN,
+ KEYBOARD_BRIGHTNESS_UP,
+ LAUNCH_APP_0,
+ LAUNCH_APP_1,
+ LAUNCH_APP_2,
+ LAUNCH_APP_3,
+ LAUNCH_APP_4,
+ LAUNCH_APP_5,
+ LAUNCH_APP_6,
+ LAUNCH_APP_7,
+ LAUNCH_LAST_APP,
+ LOCK_PRESSED,
+ LOCK_RELEASED,
+ MAGNIFY_SCREEN_ZOOM_IN,
+ MAGNIFY_SCREEN_ZOOM_OUT,
+ MEDIA_NEXT_TRACK,
+ MEDIA_PLAY_PAUSE,
+ MEDIA_PREV_TRACK,
+ NEW_INCOGNITO_WINDOW,
+ NEW_TAB,
+ NEW_WINDOW,
+ NEXT_IME,
+ OPEN_FEEDBACK_PAGE,
+ POWER_PRESSED,
+ POWER_RELEASED,
+ PREVIOUS_IME,
+ PRINT_LAYER_HIERARCHY,
+ PRINT_UI_HIERARCHIES,
+ PRINT_VIEW_HIERARCHY,
+ PRINT_WINDOW_HIERARCHY,
+ RESTORE_TAB,
+ ROTATE_SCREEN,
+ ROTATE_WINDOW,
+ SCALE_UI_DOWN,
+ SCALE_UI_RESET,
+ SCALE_UI_UP,
+ SHOW_KEYBOARD_OVERLAY,
+ SHOW_MESSAGE_CENTER_BUBBLE,
+ SHOW_OAK,
+ SHOW_SYSTEM_TRAY_BUBBLE,
+ SHOW_TASK_MANAGER,
+ SILENCE_SPOKEN_FEEDBACK,
+ SWAP_PRIMARY_DISPLAY,
+ SWITCH_IME, // Switch to another IME depending on the accelerator.
+ TAKE_PARTIAL_SCREENSHOT,
+ TAKE_SCREENSHOT,
+ TOGGLE_APP_LIST,
+ TOGGLE_CAPS_LOCK,
+ TOGGLE_CAPS_LOCK_BY_ALT_LWIN,
+ TOGGLE_DESKTOP_BACKGROUND_MODE,
+ TOGGLE_FULLSCREEN,
+ TOGGLE_MAXIMIZED,
+ TOGGLE_ROOT_WINDOW_FULL_SCREEN,
+ TOGGLE_SPOKEN_FEEDBACK,
+ TOGGLE_WIFI,
+ TOUCH_HUD_CLEAR,
+ TOUCH_HUD_MODE_CHANGE,
+ TOUCH_HUD_PROJECTION_TOGGLE,
+ VOLUME_DOWN,
+ VOLUME_MUTE,
+ VOLUME_UP,
+ WINDOW_MINIMIZE,
+ WINDOW_POSITION_CENTER,
+ WINDOW_SNAP_LEFT,
+ WINDOW_SNAP_RIGHT,
+#if defined(OS_CHROMEOS)
+ ADD_REMOVE_DISPLAY,
+ TOGGLE_MIRROR_MODE,
+ DISABLE_GPU_WATCHDOG,
+ LOCK_SCREEN,
+ OPEN_CROSH,
+ OPEN_FILE_DIALOG, // Open 'Open file' dialog.
+ OPEN_FILE_MANAGER,
+#endif
+};
+
+struct AcceleratorData {
+ bool trigger_on_press;
+ ui::KeyboardCode keycode;
+ int modifiers;
+ AcceleratorAction action;
+};
+
+// Accelerators handled by AcceleratorController.
+ASH_EXPORT extern const AcceleratorData kAcceleratorData[];
+
+// The number of elements in kAcceleratorData.
+ASH_EXPORT extern const size_t kAcceleratorDataLength;
+
+#if !defined(NDEBUG)
+// Accelerators useful when running on desktop. Debug build only.
+ASH_EXPORT extern const AcceleratorData kDesktopAcceleratorData[];
+
+// The number of elements in kDesktopAcceleratorData.
+ASH_EXPORT extern const size_t kDesktopAcceleratorDataLength;
+#endif
+
+// Debug accelerators enabled only when "Debugging keyboard shortcuts" flag
+// (--ash-debug-shortcuts) is enabled.
+ASH_EXPORT extern const AcceleratorData kDebugAcceleratorData[];
+
+// The number of elements in kDebugAcceleratorData.
+ASH_EXPORT extern const size_t kDebugAcceleratorDataLength;
+
+// Actions that should be handled very early in Ash unless the current target
+// window is full-screen.
+ASH_EXPORT extern const AcceleratorAction kReservedActions[];
+
+// The number of elements in kReservedActions.
+ASH_EXPORT extern const size_t kReservedActionsLength;
+
+// Actions that should be handled very early in Ash unless the current target
+// window is full-screen, these actions are only handled if
+// DebugShortcutsEnabled is true (command line switch 'ash-debug-shortcuts').
+ASH_EXPORT extern const AcceleratorAction kReservedDebugActions[];
+
+// The number of elements in kReservedDebugActions.
+ASH_EXPORT extern const size_t kReservedDebugActionsLength;
+
+// Actions allowed while user is not signed in or screen is locked.
+ASH_EXPORT extern const AcceleratorAction kActionsAllowedAtLoginOrLockScreen[];
+
+// The number of elements in kActionsAllowedAtLoginOrLockScreen.
+ASH_EXPORT extern const size_t kActionsAllowedAtLoginOrLockScreenLength;
+
+// Actions allowed while screen is locked (in addition to
+// kActionsAllowedAtLoginOrLockScreen).
+ASH_EXPORT extern const AcceleratorAction kActionsAllowedAtLockScreen[];
+
+// The number of elements in kActionsAllowedAtLockScreen.
+ASH_EXPORT extern const size_t kActionsAllowedAtLockScreenLength;
+
+// Actions allowed while a modal window is up.
+ASH_EXPORT extern const AcceleratorAction kActionsAllowedAtModalWindow[];
+
+// The number of elements in kActionsAllowedAtModalWindow.
+ASH_EXPORT extern const size_t kActionsAllowedAtModalWindowLength;
+
+// Actions which will not be repeated while holding an accelerator key.
+ASH_EXPORT extern const AcceleratorAction kNonrepeatableActions[];
+
+// The number of elements in kNonrepeatableActions.
+ASH_EXPORT extern const size_t kNonrepeatableActionsLength;
+
+// Actions allowed in app mode.
+ASH_EXPORT extern const AcceleratorAction kActionsAllowedInAppMode[];
+
+// The number of elements in kActionsAllowedInAppMode.
+ASH_EXPORT extern const size_t kActionsAllowedInAppModeLength;
+
+} // namespace ash
+
+#endif // ASH_ACCELERATORS_ACCELERATOR_TABLE_H_
diff --git a/chromium/ash/accelerators/accelerator_table_unittest.cc b/chromium/ash/accelerators/accelerator_table_unittest.cc
new file mode 100644
index 00000000000..ec71f412eb1
--- /dev/null
+++ b/chromium/ash/accelerators/accelerator_table_unittest.cc
@@ -0,0 +1,79 @@
+// Copyright (c) 2012 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.
+
+#include <set>
+
+#include "base/basictypes.h"
+#include "ash/accelerators/accelerator_table.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace ash {
+
+namespace {
+
+struct Cmp {
+ bool operator()(const AcceleratorData& lhs,
+ const AcceleratorData& rhs) {
+ if (lhs.trigger_on_press != rhs.trigger_on_press)
+ return lhs.trigger_on_press < rhs.trigger_on_press;
+ if (lhs.keycode != rhs.keycode)
+ return lhs.keycode < rhs.keycode;
+ return lhs.modifiers < rhs.modifiers;
+ // Do not check |action|.
+ }
+};
+
+} // namespace
+
+TEST(AcceleratorTableTest, CheckDuplicatedAccelerators) {
+ std::set<AcceleratorData, Cmp> acclerators;
+ for (size_t i = 0; i < kAcceleratorDataLength; ++i) {
+ const AcceleratorData& entry = kAcceleratorData[i];
+ EXPECT_TRUE(acclerators.insert(entry).second)
+ << "Duplicated accelerator: " << entry.trigger_on_press << ", "
+ << entry.keycode << ", " << (entry.modifiers & ui::EF_SHIFT_DOWN)
+ << ", " << (entry.modifiers & ui::EF_CONTROL_DOWN) << ", "
+ << (entry.modifiers & ui::EF_ALT_DOWN);
+ }
+}
+
+TEST(AcceleratorTableTest, CheckDuplicatedReservedActions) {
+ std::set<AcceleratorAction> actions;
+ for (size_t i = 0; i < kReservedActionsLength; ++i) {
+ EXPECT_TRUE(actions.insert(kReservedActions[i]).second)
+ << "Duplicated action: " << kReservedActions[i];
+ }
+}
+
+TEST(AcceleratorTableTest, CheckDuplicatedActionsAllowedAtLoginOrLockScreen) {
+ std::set<AcceleratorAction> actions;
+ for (size_t i = 0; i < kActionsAllowedAtLoginOrLockScreenLength; ++i) {
+ EXPECT_TRUE(actions.insert(kActionsAllowedAtLoginOrLockScreen[i]).second)
+ << "Duplicated action: " << kActionsAllowedAtLoginOrLockScreen[i];
+ }
+ for (size_t i = 0; i < kActionsAllowedAtLockScreenLength; ++i) {
+ EXPECT_TRUE(actions.insert(kActionsAllowedAtLockScreen[i]).second)
+ << "Duplicated action: " << kActionsAllowedAtLockScreen[i];
+ }
+}
+
+TEST(AcceleratorTableTest, CheckDuplicatedActionsAllowedAtModalWindow) {
+ std::set<AcceleratorAction> actions;
+ for (size_t i = 0; i < kActionsAllowedAtModalWindowLength; ++i) {
+ EXPECT_TRUE(actions.insert(kActionsAllowedAtModalWindow[i]).second)
+ << "Duplicated action: " << kActionsAllowedAtModalWindow[i]
+ << " at index: " << i;
+ }
+}
+
+TEST(AcceleratorTableTest, CheckDuplicatedNonrepeatableActions) {
+ std::set<AcceleratorAction> actions;
+ for (size_t i = 0; i < kNonrepeatableActionsLength; ++i) {
+ EXPECT_TRUE(actions.insert(kNonrepeatableActions[i]).second)
+ << "Duplicated action: " << kNonrepeatableActions[i]
+ << " at index: " << i;
+ }
+}
+
+} // namespace ash
diff --git a/chromium/ash/accelerators/exit_warning_handler.cc b/chromium/ash/accelerators/exit_warning_handler.cc
new file mode 100644
index 00000000000..6612fc88d18
--- /dev/null
+++ b/chromium/ash/accelerators/exit_warning_handler.cc
@@ -0,0 +1,194 @@
+// Copyright 2013 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.
+
+#include "ash/accelerators/exit_warning_handler.h"
+
+#include "ash/shell.h"
+#include "ash/shell_delegate.h"
+#include "ash/shell_window_ids.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/time/time.h"
+#include "base/timer/timer.h"
+#include "grit/ash_strings.h"
+#include "ui/aura/root_window.h"
+#include "ui/base/accessibility/accessible_view_state.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "ui/gfx/canvas.h"
+#include "ui/gfx/font.h"
+#include "ui/views/controls/label.h"
+#include "ui/views/layout/fill_layout.h"
+#include "ui/views/view.h"
+#include "ui/views/widget/widget.h"
+#include "ui/views/widget/widget_delegate.h"
+
+namespace ash {
+namespace {
+
+const int64 kTimeOutMilliseconds = 2000;
+const SkColor kForegroundColor = 0xFFFFFFFF;
+const SkColor kBackgroundColor = 0xE0808080;
+const int kHorizontalMarginAroundText = 100;
+const int kVerticalMarginAroundText = 100;
+
+class ExitWarningLabel : public views::Label {
+ public:
+ ExitWarningLabel() {}
+
+ virtual ~ExitWarningLabel() {}
+
+ private:
+ virtual void PaintText(gfx::Canvas* canvas,
+ const string16& text,
+ const gfx::Rect& text_bounds,
+ int flags) OVERRIDE {
+ // Turn off subpixel rendering.
+ views::Label::PaintText(canvas,
+ text,
+ text_bounds,
+ flags | gfx::Canvas::NO_SUBPIXEL_RENDERING);
+ }
+
+ DISALLOW_COPY_AND_ASSIGN(ExitWarningLabel);
+};
+
+class ExitWarningWidgetDelegateView : public views::WidgetDelegateView {
+ public:
+ ExitWarningWidgetDelegateView() : text_width_(0), width_(0), height_(0) {
+ text_ = l10n_util::GetStringUTF16(IDS_ASH_EXIT_WARNING_POPUP_TEXT);
+ accessible_name_ =
+ l10n_util::GetStringUTF16(IDS_ASH_EXIT_WARNING_POPUP_TEXT_ACCESSIBLE);
+ ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
+ font_ = rb.GetFont(ui::ResourceBundle::LargeFont);
+ text_width_ = font_.GetStringWidth(text_);
+ width_ = text_width_ + kHorizontalMarginAroundText;
+ height_ = font_.GetHeight() + kVerticalMarginAroundText;
+ views::Label* label = new ExitWarningLabel;
+ label->SetText(text_);
+ label->SetHorizontalAlignment(gfx::ALIGN_CENTER);
+ label->SetFont(font_);
+ label->SetEnabledColor(kForegroundColor);
+ label->SetDisabledColor(kForegroundColor);
+ label->SetAutoColorReadabilityEnabled(false);
+ AddChildView(label);
+ SetLayoutManager(new views::FillLayout);
+ }
+
+ virtual gfx::Size GetPreferredSize() OVERRIDE {
+ return gfx::Size(width_, height_);
+ }
+
+ virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE {
+ canvas->FillRect(GetLocalBounds(), kBackgroundColor);
+ views::WidgetDelegateView::OnPaint(canvas);
+ }
+
+ virtual void GetAccessibleState(ui::AccessibleViewState* state) OVERRIDE {
+ state->name = accessible_name_;
+ state->role = ui::AccessibilityTypes::ROLE_ALERT;
+ }
+
+ private:
+ base::string16 text_;
+ base::string16 accessible_name_;
+ gfx::Font font_;
+ int text_width_;
+ int width_;
+ int height_;
+
+ DISALLOW_COPY_AND_ASSIGN(ExitWarningWidgetDelegateView);
+};
+
+} // namespace
+
+ExitWarningHandler::ExitWarningHandler()
+ : state_(IDLE),
+ stub_timer_for_test_(false) {
+}
+
+ExitWarningHandler::~ExitWarningHandler() {
+ // Note: If a timer is outstanding, it is stopped in its destructor.
+ Hide();
+}
+
+void ExitWarningHandler::HandleAccelerator() {
+ ShellDelegate* shell_delegate = Shell::GetInstance()->delegate();
+ switch (state_) {
+ case IDLE:
+ state_ = WAIT_FOR_DOUBLE_PRESS;
+ Show();
+ StartTimer();
+ shell_delegate->RecordUserMetricsAction(UMA_ACCEL_EXIT_FIRST_Q);
+ break;
+ case WAIT_FOR_DOUBLE_PRESS:
+ state_ = EXITING;
+ CancelTimer();
+ Hide();
+ shell_delegate->RecordUserMetricsAction(UMA_ACCEL_EXIT_SECOND_Q);
+ shell_delegate->Exit();
+ break;
+ case EXITING:
+ break;
+ default:
+ NOTREACHED();
+ break;
+ }
+}
+
+void ExitWarningHandler::TimerAction() {
+ Hide();
+ if (state_ == WAIT_FOR_DOUBLE_PRESS)
+ state_ = IDLE;
+}
+
+void ExitWarningHandler::StartTimer() {
+ if (stub_timer_for_test_)
+ return;
+ timer_.Start(FROM_HERE,
+ base::TimeDelta::FromMilliseconds(kTimeOutMilliseconds),
+ this,
+ &ExitWarningHandler::TimerAction);
+}
+
+void ExitWarningHandler::CancelTimer() {
+ timer_.Stop();
+}
+
+void ExitWarningHandler::Show() {
+ if (widget_)
+ return;
+ aura::RootWindow* root_window = Shell::GetActiveRootWindow();
+ ExitWarningWidgetDelegateView* delegate = new ExitWarningWidgetDelegateView;
+ gfx::Size rs = root_window->bounds().size();
+ gfx::Size ps = delegate->GetPreferredSize();
+ gfx::Rect bounds((rs.width() - ps.width()) / 2,
+ (rs.height() - ps.height()) / 3,
+ ps.width(), ps.height());
+ views::Widget::InitParams params;
+ params.type = views::Widget::InitParams::TYPE_POPUP;
+ params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW;
+ params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
+ params.accept_events = false;
+ params.can_activate = false;
+ params.keep_on_top = true;
+ params.remove_standard_frame = true;
+ params.delegate = delegate;
+ params.bounds = bounds;
+ params.parent = Shell::GetContainer(
+ root_window,
+ internal::kShellWindowId_SettingBubbleContainer);
+ widget_.reset(new views::Widget);
+ widget_->Init(params);
+ widget_->SetContentsView(delegate);
+ widget_->GetNativeView()->SetName("ExitWarningWindow");
+ widget_->Show();
+
+ delegate->NotifyAccessibilityEvent(ui::AccessibilityTypes::EVENT_ALERT, true);
+}
+
+void ExitWarningHandler::Hide() {
+ widget_.reset();
+}
+
+} // namespace ash
diff --git a/chromium/ash/accelerators/exit_warning_handler.h b/chromium/ash/accelerators/exit_warning_handler.h
new file mode 100644
index 00000000000..b99681cb435
--- /dev/null
+++ b/chromium/ash/accelerators/exit_warning_handler.h
@@ -0,0 +1,87 @@
+// Copyright 2013 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 ASH_ACCELERATORS_EXIT_WARNING_HANDLER_H_
+#define ASH_ACCELERATORS_EXIT_WARNING_HANDLER_H_
+
+#include "ash/ash_export.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/timer/timer.h"
+#include "ui/base/accelerators/accelerator.h"
+
+namespace views {
+class Widget;
+}
+
+namespace ash {
+
+// In order to avoid accidental exits when the user presses the exit
+// shortcut by mistake, we require the user press it twice within a
+// period of time. During that time we show a popup informing the
+// user of this.
+//
+// Notes:
+//
+// The corresponding accelerator must be non-repeatable (see
+// kNonrepeatableActions in accelerator_table.cc). Otherwise the "Double Press
+// Exit" will be activated just by holding it down, i.e. probably every time.
+//
+// State Transition Diagrams:
+//
+// IDLE
+// | Press
+// WAIT_FOR_DOUBLE_PRESS action: show ui & start timers
+// | Press (before time limit )
+// EXITING action: hide ui, stop timer, exit
+//
+// IDLE
+// | Press
+// WAIT_FOR_DOUBLE_PRESS action: show ui & start timers
+// | T timer expires
+// IDLE action: hide ui
+//
+
+class AcceleratorControllerTest;
+
+class ASH_EXPORT ExitWarningHandler {
+ public:
+ ExitWarningHandler();
+
+ ~ExitWarningHandler();
+
+ // Handles accelerator for exit (Ctrl-Shift-Q).
+ void HandleAccelerator();
+
+ private:
+ friend class AcceleratorControllerTest;
+
+ enum State {
+ IDLE,
+ WAIT_FOR_DOUBLE_PRESS,
+ EXITING
+ };
+
+ // Performs actions when the time limit is exceeded.
+ void TimerAction();
+
+ void StartTimer();
+ void CancelTimer();
+
+ void Show();
+ void Hide();
+
+ State state_;
+ scoped_ptr<views::Widget> widget_;
+ base::OneShotTimer<ExitWarningHandler> timer_;
+
+ // Flag to suppress starting the timer for testing. For test we call
+ // TimerAction() directly to simulate the expiration of the timer.
+ bool stub_timer_for_test_;
+
+ DISALLOW_COPY_AND_ASSIGN(ExitWarningHandler);
+};
+
+} // namespace ash
+
+#endif // ASH_ACCELERATORS_EXIT_WARNING_HANDLER_H_
diff --git a/chromium/ash/accelerators/focus_manager_factory.cc b/chromium/ash/accelerators/focus_manager_factory.cc
new file mode 100644
index 00000000000..0695fd3e7a0
--- /dev/null
+++ b/chromium/ash/accelerators/focus_manager_factory.cc
@@ -0,0 +1,41 @@
+// Copyright (c) 2012 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.
+
+#include "ash/accelerators/focus_manager_factory.h"
+
+#include "ash/accelerators/accelerator_controller.h"
+#include "ash/shell.h"
+#include "ui/views/focus/focus_manager.h"
+
+namespace ash {
+
+AshFocusManagerFactory::AshFocusManagerFactory() {}
+AshFocusManagerFactory::~AshFocusManagerFactory() {}
+
+views::FocusManager* AshFocusManagerFactory::CreateFocusManager(
+ views::Widget* widget,
+ bool desktop_widget) {
+ return new views::FocusManager(widget, desktop_widget ? NULL : new Delegate);
+}
+
+bool AshFocusManagerFactory::Delegate::ProcessAccelerator(
+ const ui::Accelerator& accelerator) {
+ AcceleratorController* controller =
+ Shell::GetInstance()->accelerator_controller();
+ if (controller)
+ return controller->Process(accelerator);
+ return false;
+}
+
+ui::AcceleratorTarget*
+AshFocusManagerFactory::Delegate::GetCurrentTargetForAccelerator(
+ const ui::Accelerator& accelerator) const {
+ AcceleratorController* controller =
+ Shell::GetInstance()->accelerator_controller();
+ if (controller && controller->IsRegistered(accelerator))
+ return controller;
+ return NULL;
+}
+
+} // namespace ash
diff --git a/chromium/ash/accelerators/focus_manager_factory.h b/chromium/ash/accelerators/focus_manager_factory.h
new file mode 100644
index 00000000000..cbe27cba9a3
--- /dev/null
+++ b/chromium/ash/accelerators/focus_manager_factory.h
@@ -0,0 +1,43 @@
+// Copyright (c) 2012 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 ASH_ACCELERATORS_FOCUS_MANAGER_FACTORY_H_
+#define ASH_ACCELERATORS_FOCUS_MANAGER_FACTORY_H_
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "ui/views/focus/focus_manager_delegate.h"
+#include "ui/views/focus/focus_manager_factory.h"
+
+namespace ash {
+
+// A factory class for creating a custom views::FocusManager object which
+// supports Ash shortcuts.
+class AshFocusManagerFactory : public views::FocusManagerFactory {
+ public:
+ AshFocusManagerFactory();
+ virtual ~AshFocusManagerFactory();
+
+ protected:
+ // views::FocusManagerFactory overrides:
+ virtual views::FocusManager* CreateFocusManager(
+ views::Widget* widget,
+ bool desktop_widget) OVERRIDE;
+
+ private:
+ class Delegate : public views::FocusManagerDelegate {
+ public:
+ // views::FocusManagerDelegate overrides:
+ virtual bool ProcessAccelerator(
+ const ui::Accelerator& accelerator) OVERRIDE;
+ virtual ui::AcceleratorTarget* GetCurrentTargetForAccelerator(
+ const ui::Accelerator& accelerator) const OVERRIDE;
+ };
+
+ DISALLOW_COPY_AND_ASSIGN(AshFocusManagerFactory);
+};
+
+} // namespace ash
+
+#endif // ASH_ACCELERATORS_FOCUS_MANAGER_FACTORY_H_
diff --git a/chromium/ash/accelerators/nested_dispatcher_controller.cc b/chromium/ash/accelerators/nested_dispatcher_controller.cc
new file mode 100644
index 00000000000..82e477cd782
--- /dev/null
+++ b/chromium/ash/accelerators/nested_dispatcher_controller.cc
@@ -0,0 +1,36 @@
+// Copyright (c) 2012 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.
+
+#include "ash/accelerators/nested_dispatcher_controller.h"
+
+#include "ash/accelerators/accelerator_dispatcher.h"
+#include "ash/shell.h"
+#include "base/run_loop.h"
+
+namespace ash {
+
+NestedDispatcherController::NestedDispatcherController() {
+}
+
+NestedDispatcherController::~NestedDispatcherController() {
+}
+
+void NestedDispatcherController::RunWithDispatcher(
+ base::MessageLoop::Dispatcher* nested_dispatcher,
+ aura::Window* associated_window,
+ bool nestable_tasks_allowed) {
+ base::MessageLoopForUI* loop = base::MessageLoopForUI::current();
+ bool did_allow_task_nesting = loop->NestableTasksAllowed();
+ loop->SetNestableTasksAllowed(nestable_tasks_allowed);
+
+ AcceleratorDispatcher dispatcher(nested_dispatcher, associated_window);
+
+ // TODO(jbates) crbug.com/134753 Find quitters of this RunLoop and have them
+ // use run_loop.QuitClosure().
+ base::RunLoop run_loop(&dispatcher);
+ run_loop.Run();
+ loop->SetNestableTasksAllowed(did_allow_task_nesting);
+}
+
+} // namespace ash
diff --git a/chromium/ash/accelerators/nested_dispatcher_controller.h b/chromium/ash/accelerators/nested_dispatcher_controller.h
new file mode 100644
index 00000000000..c6f9320d447
--- /dev/null
+++ b/chromium/ash/accelerators/nested_dispatcher_controller.h
@@ -0,0 +1,35 @@
+// Copyright (c) 2012 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 ASH_ACCELERATORS_NESTED_DISPATCHER_CONTROLLER_H_
+#define ASH_ACCELERATORS_NESTED_DISPATCHER_CONTROLLER_H_
+
+#include "ash/ash_export.h"
+#include "base/message_loop/message_loop.h"
+#include "ui/aura/client/dispatcher_client.h"
+#include "ui/aura/window.h"
+
+namespace ash {
+
+// Creates a dispatcher which wraps another dispatcher.
+// The outer dispatcher runs first and performs ash specific handling.
+// If it does not consume the event it forwards the event to the nested
+// dispatcher.
+class ASH_EXPORT NestedDispatcherController
+ : public aura::client::DispatcherClient {
+ public:
+ NestedDispatcherController();
+ virtual ~NestedDispatcherController();
+
+ virtual void RunWithDispatcher(base::MessageLoop::Dispatcher* dispatcher,
+ aura::Window* associated_window,
+ bool nestable_tasks_allowed) OVERRIDE;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(NestedDispatcherController);
+};
+
+} // namespace ash
+
+#endif // ASH_ACCELERATORS_NESTED_DISPATCHER_CONTROLLER_H_
diff --git a/chromium/ash/accelerators/nested_dispatcher_controller_unittest.cc b/chromium/ash/accelerators/nested_dispatcher_controller_unittest.cc
new file mode 100644
index 00000000000..8b0f6a5e866
--- /dev/null
+++ b/chromium/ash/accelerators/nested_dispatcher_controller_unittest.cc
@@ -0,0 +1,164 @@
+// Copyright (c) 2012 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.
+
+#include "ash/accelerators/accelerator_controller.h"
+#include "ash/session_state_delegate.h"
+#include "ash/shell.h"
+#include "ash/shell_window_ids.h"
+#include "ash/test/ash_test_base.h"
+#include "base/bind.h"
+#include "base/event_types.h"
+#include "base/message_loop/message_loop.h"
+#include "ui/aura/client/dispatcher_client.h"
+#include "ui/aura/root_window.h"
+#include "ui/aura/test/test_windows.h"
+#include "ui/aura/window.h"
+#include "ui/base/accelerators/accelerator.h"
+#include "ui/base/events/event_constants.h"
+#include "ui/base/events/event_utils.h"
+
+#if defined(USE_X11)
+#include <X11/Xlib.h>
+#include "ui/base/x/x11_util.h"
+#endif // USE_X11
+
+namespace ash {
+namespace test {
+
+namespace {
+
+class MockDispatcher : public base::MessageLoop::Dispatcher {
+ public:
+ MockDispatcher() : num_key_events_dispatched_(0) {
+ }
+
+ int num_key_events_dispatched() { return num_key_events_dispatched_; }
+
+#if defined(OS_WIN) || defined(USE_X11) || defined(USE_OZONE)
+ virtual bool Dispatch(const base::NativeEvent& event) OVERRIDE {
+ if (ui::EventTypeFromNative(event) == ui::ET_KEY_RELEASED)
+ num_key_events_dispatched_++;
+ return !ui::IsNoopEvent(event);
+ }
+#endif
+
+ private:
+ int num_key_events_dispatched_;
+};
+
+class TestTarget : public ui::AcceleratorTarget {
+ public:
+ TestTarget() : accelerator_pressed_count_(0) {
+ }
+ virtual ~TestTarget() {
+ }
+
+ int accelerator_pressed_count() const {
+ return accelerator_pressed_count_;
+ }
+
+ // Overridden from ui::AcceleratorTarget:
+ virtual bool AcceleratorPressed(const ui::Accelerator& accelerator) OVERRIDE {
+ accelerator_pressed_count_++;
+ return true;
+ }
+ virtual bool CanHandleAccelerators() const OVERRIDE {
+ return true;
+ }
+
+ private:
+ int accelerator_pressed_count_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestTarget);
+};
+
+void DispatchKeyReleaseA() {
+ // Sending both keydown and keyup is necessary here because the accelerator
+ // manager only checks a keyup event following a keydown event. See
+ // ShouldHandle() in ui/base/accelerators/accelerator_manager.cc for details.
+#if defined(OS_WIN)
+ MSG native_event_down = { NULL, WM_KEYDOWN, ui::VKEY_A, 0 };
+ ash::Shell::GetPrimaryRootWindow()->PostNativeEvent(native_event_down);
+ MSG native_event_up = { NULL, WM_KEYUP, ui::VKEY_A, 0 };
+ ash::Shell::GetPrimaryRootWindow()->PostNativeEvent(native_event_up);
+#elif defined(USE_X11)
+ XEvent native_event;
+ ui::InitXKeyEventForTesting(ui::ET_KEY_PRESSED,
+ ui::VKEY_A,
+ 0,
+ &native_event);
+ ash::Shell::GetPrimaryRootWindow()->PostNativeEvent(&native_event);
+ ui::InitXKeyEventForTesting(ui::ET_KEY_RELEASED,
+ ui::VKEY_A,
+ 0,
+ &native_event);
+ ash::Shell::GetPrimaryRootWindow()->PostNativeEvent(&native_event);
+#endif
+
+ // Send noop event to signal dispatcher to exit.
+ ash::Shell::GetPrimaryRootWindow()->PostNativeEvent(ui::CreateNoopEvent());
+}
+
+} // namespace
+
+typedef AshTestBase NestedDispatcherTest;
+
+// Aura window below lock screen in z order.
+TEST_F(NestedDispatcherTest, AssociatedWindowBelowLockScreen) {
+ MockDispatcher inner_dispatcher;
+ scoped_ptr<aura::Window> associated_window(CreateTestWindowInShellWithId(0));
+
+ Shell::GetInstance()->session_state_delegate()->LockScreen();
+ DispatchKeyReleaseA();
+ aura::RootWindow* root_window = ash::Shell::GetPrimaryRootWindow();
+ aura::client::GetDispatcherClient(root_window)->RunWithDispatcher(
+ &inner_dispatcher,
+ associated_window.get(),
+ true /* nestable_tasks_allowed */);
+ EXPECT_EQ(0, inner_dispatcher.num_key_events_dispatched());
+ Shell::GetInstance()->session_state_delegate()->UnlockScreen();
+}
+
+// Aura window above lock screen in z order.
+TEST_F(NestedDispatcherTest, AssociatedWindowAboveLockScreen) {
+ MockDispatcher inner_dispatcher;
+
+ scoped_ptr<aura::Window>mock_lock_container(
+ CreateTestWindowInShellWithId(0));
+ aura::test::CreateTestWindowWithId(0, mock_lock_container.get());
+ scoped_ptr<aura::Window> associated_window(CreateTestWindowInShellWithId(0));
+ EXPECT_TRUE(aura::test::WindowIsAbove(associated_window.get(),
+ mock_lock_container.get()));
+
+ DispatchKeyReleaseA();
+ aura::RootWindow* root_window = ash::Shell::GetPrimaryRootWindow();
+ aura::client::GetDispatcherClient(root_window)->RunWithDispatcher(
+ &inner_dispatcher,
+ associated_window.get(),
+ true /* nestable_tasks_allowed */);
+ EXPECT_EQ(1, inner_dispatcher.num_key_events_dispatched());
+}
+
+// Test that the nested dispatcher handles accelerators.
+TEST_F(NestedDispatcherTest, AcceleratorsHandled) {
+ MockDispatcher inner_dispatcher;
+ aura::RootWindow* root_window = ash::Shell::GetPrimaryRootWindow();
+
+ ui::Accelerator accelerator(ui::VKEY_A, ui::EF_NONE);
+ accelerator.set_type(ui::ET_KEY_RELEASED);
+ TestTarget target;
+ Shell::GetInstance()->accelerator_controller()->Register(accelerator,
+ &target);
+
+ DispatchKeyReleaseA();
+ aura::client::GetDispatcherClient(root_window)->RunWithDispatcher(
+ &inner_dispatcher,
+ root_window,
+ true /* nestable_tasks_allowed */);
+ EXPECT_EQ(0, inner_dispatcher.num_key_events_dispatched());
+ EXPECT_EQ(1, target.accelerator_pressed_count());
+}
+
+} // namespace test
+} // namespace ash
diff --git a/chromium/ash/ash.gyp b/chromium/ash/ash.gyp
new file mode 100644
index 00000000000..2fba6f3122a
--- /dev/null
+++ b/chromium/ash/ash.gyp
@@ -0,0 +1,928 @@
+# Copyright (c) 2012 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.
+
+{
+ 'variables': {
+ 'chromium_code': 1,
+ 'grit_out_dir': '<(SHARED_INTERMEDIATE_DIR)/chrome',
+ },
+ 'includes': [
+ 'ash_resources.gypi',
+ ],
+ 'targets': [
+ {
+ 'target_name': 'ash',
+ 'type': '<(component)',
+ 'dependencies': [
+ '../base/base.gyp:base',
+ '../base/base.gyp:base_i18n',
+ '../base/third_party/dynamic_annotations/dynamic_annotations.gyp:dynamic_annotations',
+ '../cc/cc.gyp:cc',
+ '../content/content.gyp:content',
+ '../content/content.gyp:content_browser',
+ '../ipc/ipc.gyp:ipc',
+ '../net/net.gyp:net',
+ '../skia/skia.gyp:skia',
+ '../third_party/icu/icu.gyp:icui18n',
+ '../third_party/icu/icu.gyp:icuuc',
+ '../ui/app_list/app_list.gyp:app_list',
+ '../ui/aura/aura.gyp:aura',
+ '../ui/base/strings/ui_strings.gyp:ui_strings',
+ '../ui/compositor/compositor.gyp:compositor',
+ '../ui/keyboard/keyboard.gyp:keyboard',
+ '../ui/message_center/message_center.gyp:message_center',
+ '../ui/oak/oak.gyp:oak',
+ '../ui/ui.gyp:ui',
+ '../ui/ui.gyp:ui_resources',
+ '../ui/views/controls/webview/webview.gyp:webview',
+ '../ui/views/views.gyp:views',
+ '../ui/web_dialogs/web_dialogs.gyp:web_dialogs',
+ '../url/url.gyp:url_lib',
+ 'ash_strings.gyp:ash_strings',
+ 'ash_resources',
+ ],
+ 'defines': [
+ 'ASH_IMPLEMENTATION',
+ ],
+ 'sources': [
+ # All .cc, .h under ash, except unittests
+ 'accelerators/accelerator_controller.cc',
+ 'accelerators/accelerator_controller.h',
+ 'accelerators/accelerator_dispatcher.cc',
+ 'accelerators/accelerator_dispatcher.h',
+ 'accelerators/accelerator_filter.cc',
+ 'accelerators/accelerator_filter.h',
+ 'accelerators/accelerator_table.cc',
+ 'accelerators/accelerator_table.h',
+ 'accelerators/exit_warning_handler.cc',
+ 'accelerators/exit_warning_handler.h',
+ 'accelerators/focus_manager_factory.cc',
+ 'accelerators/focus_manager_factory.h',
+ 'accelerators/nested_dispatcher_controller.cc',
+ 'accelerators/nested_dispatcher_controller.h',
+ 'ash_constants.cc',
+ 'ash_constants.h',
+ 'ash_switches.cc',
+ 'ash_switches.h',
+ 'cancel_mode.cc',
+ 'cancel_mode.h',
+ 'caps_lock_delegate.h',
+ 'caps_lock_delegate_stub.cc',
+ 'caps_lock_delegate_stub.h',
+ 'debug.cc',
+ 'debug.h',
+ 'desktop_background/desktop_background_controller.cc',
+ 'desktop_background/desktop_background_controller.h',
+ 'desktop_background/desktop_background_controller_observer.h',
+ 'desktop_background/desktop_background_view.cc',
+ 'desktop_background/desktop_background_view.h',
+ 'desktop_background/desktop_background_widget_controller.cc',
+ 'desktop_background/desktop_background_widget_controller.h',
+ 'desktop_background/user_wallpaper_delegate.h',
+ 'desktop_background/wallpaper_resizer.cc',
+ 'desktop_background/wallpaper_resizer.h',
+ 'desktop_background/wallpaper_resizer_observer.h',
+ 'display/display_change_observer_x11.cc',
+ 'display/display_change_observer_x11.h',
+ 'display/display_controller.cc',
+ 'display/display_controller.h',
+ 'display/display_error_observer.cc',
+ 'display/display_error_observer.h',
+ 'display/display_info.h',
+ 'display/display_info.cc',
+ 'display/display_layout.h',
+ 'display/display_layout.cc',
+ 'display/display_layout_store.h',
+ 'display/display_layout_store.cc',
+ 'display/display_manager.cc',
+ 'display/display_manager.h',
+ 'display/display_pref_util.h',
+ 'display/display_util_x11.cc',
+ 'display/display_util_x11.h',
+ 'display/event_transformation_handler.cc',
+ 'display/event_transformation_handler.h',
+ 'display/mirror_window_controller.cc',
+ 'display/mirror_window_controller.h',
+ 'display/mouse_cursor_event_filter.cc',
+ 'display/mouse_cursor_event_filter.h',
+ 'display/output_configurator_animation.cc',
+ 'display/output_configurator_animation.h',
+ 'display/resolution_notification_controller.cc',
+ 'display/resolution_notification_controller.h',
+ 'display/root_window_transformers.cc',
+ 'display/root_window_transformers.h',
+ 'display/screen_position_controller.cc',
+ 'display/screen_position_controller.h',
+ 'display/shared_display_edge_indicator.cc',
+ 'display/shared_display_edge_indicator.h',
+ 'drag_drop/drag_drop_controller.cc',
+ 'drag_drop/drag_drop_controller.h',
+ 'drag_drop/drag_drop_tracker.cc',
+ 'drag_drop/drag_drop_tracker.h',
+ 'drag_drop/drag_image_view.cc',
+ 'drag_drop/drag_image_view.h',
+ 'event_rewriter_delegate.h',
+ 'focus_cycler.cc',
+ 'focus_cycler.h',
+ 'high_contrast/high_contrast_controller.cc',
+ 'high_contrast/high_contrast_controller.h',
+ 'host/root_window_host_factory.cc',
+ 'host/root_window_host_factory.h',
+ 'host/root_window_host_factory_win.cc',
+ 'keyboard_controller_proxy_stub.cc',
+ 'keyboard_controller_proxy_stub.h',
+ 'keyboard_overlay/keyboard_overlay_delegate.cc',
+ 'keyboard_overlay/keyboard_overlay_delegate.h',
+ 'keyboard_overlay/keyboard_overlay_view.cc',
+ 'keyboard_overlay/keyboard_overlay_view.h',
+ 'launcher/alternate_app_list_button.cc',
+ 'launcher/alternate_app_list_button.h',
+ 'launcher/app_list_button.cc',
+ 'launcher/app_list_button.h',
+ 'launcher/launcher.cc',
+ 'launcher/launcher.h',
+ 'launcher/launcher_alignment_menu.cc',
+ 'launcher/launcher_alignment_menu.h',
+ 'launcher/launcher_button.cc',
+ 'launcher/launcher_button.h',
+ 'launcher/launcher_delegate.h',
+ 'launcher/launcher_icon_observer.h',
+ 'launcher/launcher_model.cc',
+ 'launcher/launcher_model.h',
+ 'launcher/launcher_model_observer.h',
+ 'launcher/launcher_navigator.cc',
+ 'launcher/launcher_navigator.h',
+ 'launcher/launcher_tooltip_manager.cc',
+ 'launcher/launcher_tooltip_manager.h',
+ 'launcher/launcher_types.cc',
+ 'launcher/launcher_types.h',
+ 'launcher/launcher_util.cc',
+ 'launcher/launcher_util.h',
+ 'launcher/launcher_view.cc',
+ 'launcher/launcher_view.h',
+ 'launcher/overflow_bubble.cc',
+ 'launcher/overflow_bubble.h',
+ 'launcher/overflow_button.cc',
+ 'launcher/overflow_button.h',
+ 'launcher/scoped_observer_with_duplicated_sources.h',
+ 'launcher/tabbed_launcher_button.cc',
+ 'launcher/tabbed_launcher_button.h',
+ 'magnifier/magnification_controller.cc',
+ 'magnifier/magnification_controller.h',
+ 'magnifier/magnifier_constants.h',
+ 'magnifier/partial_magnification_controller.cc',
+ 'magnifier/partial_magnification_controller.h',
+ 'popup_message.cc',
+ 'popup_message.h',
+ 'root_window_controller.cc',
+ 'root_window_controller.h',
+ 'rotator/screen_rotation.cc',
+ 'rotator/screen_rotation.h',
+ 'scoped_target_root_window.cc',
+ 'scoped_target_root_window.h',
+ 'screen_ash.cc',
+ 'screen_ash.h',
+ 'screensaver/screensaver_view.cc',
+ 'screensaver/screensaver_view.h',
+ 'screenshot_delegate.h',
+ 'session_state_delegate.h',
+ 'session_state_observer.h',
+ 'shelf/background_animator.cc',
+ 'shelf/background_animator.h',
+ 'shelf/shelf_bezel_event_filter.cc',
+ 'shelf/shelf_bezel_event_filter.h',
+ 'shelf/shelf_layout_manager.cc',
+ 'shelf/shelf_layout_manager.h',
+ 'shelf/shelf_layout_manager_observer.h',
+ 'shelf/shelf_types.h',
+ 'shelf/shelf_widget.cc',
+ 'shelf/shelf_widget.h',
+ 'shell.cc',
+ 'shell.h',
+ 'shell_delegate.h',
+ 'shell_factory.h',
+ 'shell_window_ids.h',
+ 'system/bluetooth/bluetooth_observer.h',
+ 'system/bluetooth/tray_bluetooth.cc',
+ 'system/bluetooth/tray_bluetooth.h',
+ 'system/brightness/brightness_observer.h',
+ 'system/brightness/brightness_control_delegate.h',
+ 'system/brightness/tray_brightness.cc',
+ 'system/brightness/tray_brightness.h',
+ 'system/chromeos/audio/tray_audio.cc',
+ 'system/chromeos/audio/tray_audio.h',
+ 'system/chromeos/enterprise/enterprise_domain_observer.h',
+ 'system/chromeos/enterprise/tray_enterprise.h',
+ 'system/chromeos/enterprise/tray_enterprise.cc',
+ 'system/chromeos/keyboard_brightness_controller.cc',
+ 'system/chromeos/keyboard_brightness_controller.h',
+ 'system/chromeos/label_tray_view.h',
+ 'system/chromeos/label_tray_view.cc',
+ 'system/chromeos/managed/tray_locally_managed_user.h',
+ 'system/chromeos/managed/tray_locally_managed_user.cc',
+ 'system/chromeos/network/network_connect.cc',
+ 'system/chromeos/network/network_connect.h',
+ 'system/chromeos/network/network_detailed_view.h',
+ 'system/chromeos/network/network_icon.cc',
+ 'system/chromeos/network/network_icon.h',
+ 'system/chromeos/network/network_icon_animation.cc',
+ 'system/chromeos/network/network_icon_animation.h',
+ 'system/chromeos/network/network_icon_animation_observer.h',
+ 'system/chromeos/network/network_observer.cc',
+ 'system/chromeos/network/network_observer.h',
+ 'system/chromeos/network/network_state_list_detailed_view.cc',
+ 'system/chromeos/network/network_state_list_detailed_view.h',
+ 'system/chromeos/network/network_state_notifier.cc',
+ 'system/chromeos/network/network_state_notifier.h',
+ 'system/chromeos/network/network_tray_delegate.h',
+ 'system/chromeos/network/tray_network.cc',
+ 'system/chromeos/network/tray_network.h',
+ 'system/chromeos/network/tray_network_state_observer.cc',
+ 'system/chromeos/network/tray_network_state_observer.h',
+ 'system/chromeos/network/tray_sms.cc',
+ 'system/chromeos/network/tray_sms.h',
+ 'system/chromeos/network/tray_vpn.cc',
+ 'system/chromeos/network/tray_vpn.h',
+ 'system/chromeos/power/power_status.cc',
+ 'system/chromeos/power/power_status.h',
+ 'system/chromeos/power/power_status_view.cc',
+ 'system/chromeos/power/power_status_view.h',
+ 'system/chromeos/power/tray_power.cc',
+ 'system/chromeos/power/tray_power.h',
+ 'system/chromeos/screen_security/screen_capture_observer.h',
+ 'system/chromeos/screen_security/screen_capture_tray_item.cc',
+ 'system/chromeos/screen_security/screen_capture_tray_item.h',
+ 'system/chromeos/screen_security/screen_share_observer.h',
+ 'system/chromeos/screen_security/screen_share_tray_item.cc',
+ 'system/chromeos/screen_security/screen_share_tray_item.h',
+ 'system/chromeos/screen_security/screen_tray_item.cc',
+ 'system/chromeos/screen_security/screen_tray_item.h',
+ 'system/chromeos/settings/tray_settings.cc',
+ 'system/chromeos/settings/tray_settings.h',
+ 'system/chromeos/tray_display.cc',
+ 'system/chromeos/tray_display.h',
+ 'system/chromeos/tray_tracing.cc',
+ 'system/chromeos/tray_tracing.h',
+ 'system/date/clock_observer.h',
+ 'system/date/date_view.cc',
+ 'system/date/date_view.h',
+ 'system/date/tray_date.cc',
+ 'system/date/tray_date.h',
+ 'system/drive/drive_observer.h',
+ 'system/drive/tray_drive.cc',
+ 'system/drive/tray_drive.h',
+ 'system/ime/ime_observer.h',
+ 'system/ime/tray_ime.cc',
+ 'system/ime/tray_ime.h',
+ 'system/keyboard_brightness/keyboard_brightness_control_delegate.h',
+ 'system/locale/locale_notification_controller.cc',
+ 'system/locale/locale_notification_controller.h',
+ 'system/logout_button/logout_button_observer.h',
+ 'system/logout_button/logout_button_tray.cc',
+ 'system/logout_button/logout_button_tray.h',
+ 'system/monitor/tray_monitor.cc',
+ 'system/monitor/tray_monitor.h',
+ 'system/session_length_limit/session_length_limit_observer.h',
+ 'system/session_length_limit/tray_session_length_limit.cc',
+ 'system/session_length_limit/tray_session_length_limit.h',
+ 'system/status_area_widget.cc',
+ 'system/status_area_widget.h',
+ 'system/status_area_widget_delegate.cc',
+ 'system/status_area_widget_delegate.h',
+ 'system/tray/actionable_view.cc',
+ 'system/tray/actionable_view.h',
+ 'system/tray/fixed_sized_image_view.cc',
+ 'system/tray/fixed_sized_image_view.h',
+ 'system/tray/fixed_sized_scroll_view.cc',
+ 'system/tray/fixed_sized_scroll_view.h',
+ 'system/tray/hover_highlight_view.cc',
+ 'system/tray/hover_highlight_view.h',
+ 'system/tray/special_popup_row.cc',
+ 'system/tray/special_popup_row.h',
+ 'system/tray/system_tray.cc',
+ 'system/tray/system_tray.h',
+ 'system/tray/system_tray_bubble.cc',
+ 'system/tray/system_tray_bubble.h',
+ 'system/tray/system_tray_delegate.cc',
+ 'system/tray/system_tray_delegate.h',
+ 'system/tray/system_tray_item.cc',
+ 'system/tray/system_tray_item.h',
+ 'system/tray/system_tray_notifier.cc',
+ 'system/tray/system_tray_notifier.h',
+ 'system/tray/test_system_tray_delegate.cc',
+ 'system/tray/test_system_tray_delegate.h',
+ 'system/tray/throbber_view.cc',
+ 'system/tray/throbber_view.h',
+ 'system/tray/tray_background_view.cc',
+ 'system/tray/tray_background_view.h',
+ 'system/tray/tray_bar_button_with_title.cc',
+ 'system/tray/tray_bar_button_with_title.h',
+ 'system/tray/tray_bubble_wrapper.cc',
+ 'system/tray/tray_bubble_wrapper.h',
+ 'system/tray/tray_constants.cc',
+ 'system/tray/tray_constants.h',
+ 'system/tray/tray_details_view.cc',
+ 'system/tray/tray_details_view.h',
+ 'system/tray/tray_empty.cc',
+ 'system/tray/tray_empty.h',
+ 'system/tray/tray_event_filter.cc',
+ 'system/tray/tray_event_filter.h',
+ 'system/tray/tray_image_item.cc',
+ 'system/tray/tray_image_item.h',
+ 'system/tray/tray_item_more.cc',
+ 'system/tray/tray_item_more.h',
+ 'system/tray/tray_item_view.cc',
+ 'system/tray/tray_item_view.h',
+ 'system/tray/tray_notification_view.cc',
+ 'system/tray/tray_notification_view.h',
+ 'system/tray/tray_popup_header_button.cc',
+ 'system/tray/tray_popup_header_button.h',
+ 'system/tray/tray_popup_label_button.cc',
+ 'system/tray/tray_popup_label_button.cc',
+ 'system/tray/tray_popup_label_button.h',
+ 'system/tray/tray_popup_label_button_border.cc',
+ 'system/tray/tray_popup_label_button_border.h',
+ 'system/tray/tray_utils.cc',
+ 'system/tray/tray_utils.h',
+ 'system/tray/view_click_listener.h',
+ 'system/tray_accessibility.cc',
+ 'system/tray_accessibility.h',
+ 'system/tray_caps_lock.cc',
+ 'system/tray_caps_lock.h',
+ 'system/tray_update.cc',
+ 'system/tray_update.h',
+ 'system/user/login_status.cc',
+ 'system/user/login_status.h',
+ 'system/user/tray_user.cc',
+ 'system/user/tray_user.h',
+ 'system/user/update_observer.h',
+ 'system/user/user_observer.h',
+ 'system/web_notification/web_notification_tray.cc',
+ 'system/web_notification/web_notification_tray.h',
+ 'touch/touch_hud_debug.cc',
+ 'touch/touch_hud_debug.h',
+ 'touch/touch_hud_projection.cc',
+ 'touch/touch_hud_projection.h',
+ 'touch/touch_observer_hud.cc',
+ 'touch/touch_observer_hud.h',
+ 'touch/touch_uma.cc',
+ 'touch/touch_uma.h',
+ 'volume_control_delegate.h',
+ 'wm/app_list_controller.cc',
+ 'wm/app_list_controller.h',
+ 'wm/activation_controller.cc',
+ 'wm/activation_controller.h',
+ 'wm/activation_controller_delegate.h',
+ 'wm/always_on_top_controller.cc',
+ 'wm/always_on_top_controller.h',
+ 'wm/ash_activation_controller.cc',
+ 'wm/ash_activation_controller.h',
+ 'wm/ash_native_cursor_manager.cc',
+ 'wm/ash_native_cursor_manager.h',
+ 'wm/ash_focus_rules.cc',
+ 'wm/ash_focus_rules.h',
+ 'wm/base_layout_manager.cc',
+ 'wm/base_layout_manager.h',
+ 'wm/boot_splash_screen_chromeos.cc',
+ 'wm/boot_splash_screen_chromeos.h',
+ 'wm/capture_controller.cc',
+ 'wm/capture_controller.h',
+ 'wm/coordinate_conversion.cc',
+ 'wm/coordinate_conversion.h',
+ 'wm/custom_frame_view_ash.cc',
+ 'wm/custom_frame_view_ash.h',
+ 'wm/default_window_resizer.cc',
+ 'wm/default_window_resizer.h',
+ 'wm/dock/docked_window_layout_manager.cc',
+ 'wm/dock/docked_window_layout_manager.h',
+ 'wm/dock/docked_window_layout_manager_observer.h',
+ 'wm/dock/docked_window_resizer.cc',
+ 'wm/dock/docked_window_resizer.h',
+ 'wm/drag_window_controller.cc',
+ 'wm/drag_window_controller.h',
+ 'wm/drag_window_resizer.cc',
+ 'wm/drag_window_resizer.h',
+ 'wm/event_client_impl.cc',
+ 'wm/event_client_impl.h',
+ 'wm/event_rewriter_event_filter.cc',
+ 'wm/event_rewriter_event_filter.h',
+ 'wm/frame_painter.cc',
+ 'wm/frame_painter.h',
+ 'wm/gestures/long_press_affordance_handler.cc',
+ 'wm/gestures/long_press_affordance_handler.h',
+ 'wm/gestures/shelf_gesture_handler.cc',
+ 'wm/gestures/shelf_gesture_handler.h',
+ 'wm/gestures/system_pinch_handler.cc',
+ 'wm/gestures/system_pinch_handler.h',
+ 'wm/gestures/tray_gesture_handler.cc',
+ 'wm/gestures/tray_gesture_handler.h',
+ 'wm/gestures/two_finger_drag_handler.cc',
+ 'wm/gestures/two_finger_drag_handler.h',
+ 'wm/image_cursors.cc',
+ 'wm/image_cursors.h',
+ 'wm/lock_state_controller.cc',
+ 'wm/lock_state_controller.h',
+ 'wm/lock_state_controller_impl2.cc',
+ 'wm/lock_state_controller_impl2.h',
+ 'wm/lock_state_observer.h',
+ 'wm/maximize_bubble_controller.cc',
+ 'wm/maximize_bubble_controller.h',
+ 'wm/mru_window_tracker.cc',
+ 'wm/mru_window_tracker.h',
+ 'wm/overlay_event_filter.cc',
+ 'wm/overlay_event_filter.h',
+ 'wm/panels/panel_frame_view.cc',
+ 'wm/panels/panel_frame_view.h',
+ 'wm/panels/panel_layout_manager.cc',
+ 'wm/panels/panel_layout_manager.h',
+ 'wm/panels/panel_window_event_handler.cc',
+ 'wm/panels/panel_window_event_handler.h',
+ 'wm/panels/panel_window_resizer.cc',
+ 'wm/panels/panel_window_resizer.h',
+ 'wm/partial_screenshot_view.cc',
+ 'wm/partial_screenshot_view.h',
+ 'wm/power_button_controller.cc',
+ 'wm/power_button_controller.h',
+ 'wm/property_util.cc',
+ 'wm/property_util.h',
+ 'wm/resize_shadow.cc',
+ 'wm/resize_shadow.h',
+ 'wm/resize_shadow_controller.cc',
+ 'wm/resize_shadow_controller.h',
+ 'wm/root_window_layout_manager.cc',
+ 'wm/root_window_layout_manager.h',
+ 'wm/screen_dimmer.cc',
+ 'wm/screen_dimmer.h',
+ 'wm/session_state_animator.cc',
+ 'wm/session_state_animator.h',
+ 'wm/session_state_controller_impl.cc',
+ 'wm/session_state_controller_impl.h',
+ 'wm/stacking_controller.cc',
+ 'wm/stacking_controller.h',
+ 'wm/status_area_layout_manager.cc',
+ 'wm/status_area_layout_manager.h',
+ 'wm/sticky_keys.cc',
+ 'wm/sticky_keys.h',
+ 'wm/system_background_controller.cc',
+ 'wm/system_background_controller.h',
+ 'wm/system_gesture_event_filter.cc',
+ 'wm/system_gesture_event_filter.h',
+ 'wm/system_modal_container_event_filter.cc',
+ 'wm/system_modal_container_event_filter.h',
+ 'wm/system_modal_container_event_filter_delegate.h',
+ 'wm/system_modal_container_layout_manager.cc',
+ 'wm/system_modal_container_layout_manager.h',
+ 'wm/toplevel_window_event_handler.cc',
+ 'wm/toplevel_window_event_handler.h',
+ 'wm/user_activity_detector.cc',
+ 'wm/user_activity_detector.h',
+ 'wm/user_activity_observer.h',
+ 'wm/video_detector.cc',
+ 'wm/video_detector.h',
+ 'wm/window_animations.cc',
+ 'wm/window_animations.h',
+ 'wm/window_cycle_controller.cc',
+ 'wm/window_cycle_controller.h',
+ 'wm/window_cycle_list.cc',
+ 'wm/window_cycle_list.h',
+ 'wm/window_properties.cc',
+ 'wm/window_properties.h',
+ 'wm/window_resizer.cc',
+ 'wm/window_resizer.h',
+ 'wm/window_selector.cc',
+ 'wm/window_selector.h',
+ 'wm/window_selector_controller.cc',
+ 'wm/window_selector_controller.h',
+ 'wm/window_selector_delegate.h',
+ 'wm/window_util.cc',
+ 'wm/window_util.h',
+ 'wm/workspace_controller.cc',
+ 'wm/workspace_controller.h',
+ 'wm/workspace/auto_window_management.cc',
+ 'wm/workspace/auto_window_management.h',
+ 'wm/workspace/colored_window_controller.cc',
+ 'wm/workspace/colored_window_controller.h',
+ 'wm/workspace/desktop_background_fade_controller.cc',
+ 'wm/workspace/desktop_background_fade_controller.h',
+ 'wm/workspace/frame_maximize_button.cc',
+ 'wm/workspace/frame_maximize_button.h',
+ 'wm/workspace/magnetism_matcher.cc',
+ 'wm/workspace/magnetism_matcher.h',
+ 'wm/workspace/maximize_bubble_frame_state.h',
+ 'wm/workspace/multi_window_resize_controller.cc',
+ 'wm/workspace/multi_window_resize_controller.h',
+ 'wm/workspace/phantom_window_controller.cc',
+ 'wm/workspace/phantom_window_controller.h',
+ 'wm/workspace/snap_sizer.cc',
+ 'wm/workspace/snap_sizer.h',
+ 'wm/workspace/snap_types.h',
+ 'wm/workspace/workspace_event_handler.cc',
+ 'wm/workspace/workspace_event_handler.h',
+ 'wm/workspace/workspace_layout_manager.cc',
+ 'wm/workspace/workspace_layout_manager.h',
+ 'wm/workspace/workspace_types.h',
+ 'wm/workspace/workspace_window_resizer.cc',
+ 'wm/workspace/workspace_window_resizer.h',
+ ],
+ 'conditions': [
+ ['OS=="mac"', {
+ 'sources/': [
+ ['exclude', 'accelerators/accelerator_controller.cc'],
+ ['exclude', 'accelerators/accelerator_controller.h'],
+ ['exclude', 'accelerators/accelerator_dispatcher.cc'],
+ ['exclude', 'accelerators/accelerator_dispatcher.h'],
+ ['exclude', 'accelerators/accelerator_filter.cc'],
+ ['exclude', 'accelerators/accelerator_filter.h'],
+ ['exclude', 'accelerators/exit_warning_handler.cc'],
+ ['exclude', 'accelerators/exit_warning_handler.h'],
+ ['exclude', 'accelerators/nested_dispatcher_controller.cc'],
+ ['exclude', 'accelerators/nested_dispatcher_controller.h'],
+ ],
+ }],
+ ['OS=="win"', {
+ 'sources/': [
+ ['exclude', 'host/root_window_host_factory.cc'],
+ ['exclude', 'wm/sticky_keys.cc'],
+ ['exclude', 'wm/sticky_keys.h'],
+ ],
+ # TODO(jschuh): crbug.com/167187 fix size_t to int truncations.
+ 'msvs_disabled_warnings': [ 4267, ],
+ }],
+ ['OS!="linux"', {
+ 'sources/': [
+ ['exclude', 'system/monitor/tray_monitor.cc'],
+ ['exclude', 'system/monitor/tray_monitor.h'],
+ ],
+ }],
+ ['chromeos==1', {
+ 'dependencies': [
+ '../chromeos/chromeos.gyp:chromeos',
+ # Ash #includes power_supply_properties.pb.h directly.
+ '../chromeos/chromeos.gyp:power_manager_proto',
+ ],
+ }, { # else: chromeos!=1
+ 'sources/': [
+ ['exclude', '/chromeos/'],
+ ['exclude', 'display/display_error_observer.cc'],
+ ['exclude', 'display/display_error_observer.h'],
+ ['exclude', 'display/output_configurator_animation.cc'],
+ ['exclude', 'display/output_configurator_animation.h'],
+ ],
+ }],
+ ],
+ },
+ {
+ 'target_name': 'ash_test_support',
+ 'type': 'static_library',
+ 'dependencies': [
+ '../skia/skia.gyp:skia',
+ '../testing/gtest.gyp:gtest',
+ 'ash',
+ 'ash_resources',
+ ],
+ 'sources': [
+ 'test/ash_test_base.cc',
+ 'test/ash_test_base.h',
+ 'test/ash_test_helper.cc',
+ 'test/ash_test_helper.h',
+ 'test/cursor_manager_test_api.cc',
+ 'test/cursor_manager_test_api.h',
+ 'test/launcher_view_test_api.cc',
+ 'test/launcher_view_test_api.h',
+ 'test/display_manager_test_api.cc',
+ 'test/display_manager_test_api.h',
+ 'test/mirror_window_test_api.cc',
+ 'test/mirror_window_test_api.h',
+ 'test/shell_test_api.cc',
+ 'test/shell_test_api.h',
+ 'test/test_activation_delegate.cc',
+ 'test/test_activation_delegate.h',
+ 'test/test_launcher_delegate.cc',
+ 'test/test_launcher_delegate.h',
+ 'test/test_session_state_delegate.cc',
+ 'test/test_session_state_delegate.cc',
+ 'test/test_shell_delegate.cc',
+ 'test/test_shell_delegate.h',
+ 'test/test_suite.cc',
+ 'test/test_suite.h',
+ 'test/test_suite_init.h',
+ 'test/test_suite_init.mm',
+ 'test/ui_controls_factory_ash.cc',
+ 'test/ui_controls_factory_ash.h',
+ ],
+ 'conditions': [
+ ['OS=="win"', {
+ 'dependencies': [
+ '../ipc/ipc.gyp:ipc',
+ '../ui/metro_viewer/metro_viewer.gyp:metro_viewer_messages',
+ '../win8/win8.gyp:metro_viewer',
+ '../win8/win8.gyp:test_support_win8',
+ '../win8/win8_tests.gyp:test_registrar',
+ ],
+ 'sources': [
+ 'test/test_metro_viewer_process_host.cc',
+ 'test/test_metro_viewer_process_host.h',
+ ],
+ }],
+ ],
+ },
+ {
+ 'target_name': 'ash_unittests',
+ 'type': 'executable',
+ 'dependencies': [
+ '../base/base.gyp:base',
+ '../base/base.gyp:test_support_base',
+ '../chrome/chrome_resources.gyp:packed_resources',
+ '../content/content.gyp:content_browser',
+ '../content/content.gyp:test_support_content',
+ '../skia/skia.gyp:skia',
+ '../testing/gtest.gyp:gtest',
+ '../third_party/icu/icu.gyp:icui18n',
+ '../third_party/icu/icu.gyp:icuuc',
+ '../ui/app_list/app_list.gyp:app_list',
+ '../ui/aura/aura.gyp:aura',
+ '../ui/aura/aura.gyp:aura_test_support',
+ '../ui/compositor/compositor.gyp:compositor',
+ '../ui/keyboard/keyboard.gyp:keyboard',
+ '../ui/message_center/message_center.gyp:message_center',
+ '../ui/message_center/message_center.gyp:message_center_test_support',
+ '../ui/ui.gyp:ui',
+ '../ui/ui.gyp:ui_resources',
+ '../ui/ui.gyp:ui_test_support',
+ '../ui/views/views.gyp:views',
+ '../ui/views/views.gyp:views_examples_with_content_lib',
+ '../ui/views/views.gyp:views_test_support',
+ '../ui/views/views.gyp:views_with_content_test_support',
+ '../ui/web_dialogs/web_dialogs.gyp:web_dialogs_test_support',
+ '../url/url.gyp:url_lib',
+ 'ash_strings.gyp:ash_strings',
+ 'ash',
+ 'ash_resources',
+ 'ash_test_support',
+ ],
+ 'sources': [
+ '../ui/compositor/test/layer_animator_test_controller.cc',
+ '../ui/compositor/test/layer_animator_test_controller.h',
+ '../ui/views/test/test_views_delegate.cc',
+ '../ui/views/test/test_views_delegate.h',
+ 'accelerators/accelerator_controller_unittest.cc',
+ 'accelerators/accelerator_filter_unittest.cc',
+ 'accelerators/accelerator_table_unittest.cc',
+ 'accelerators/nested_dispatcher_controller_unittest.cc',
+ 'desktop_background/desktop_background_controller_unittest.cc',
+ 'desktop_background/wallpaper_resizer_unittest.cc',
+ 'dip_unittest.cc',
+ 'display/display_controller_unittest.cc',
+ 'display/display_error_observer_unittest.cc',
+ 'display/display_info_unittest.cc',
+ 'display/display_manager_unittest.cc',
+ 'display/mirror_window_controller_unittest.cc',
+ 'display/mouse_cursor_event_filter_unittest.cc',
+ 'display/resolution_notification_controller_unittest.cc',
+ 'display/root_window_transformers_unittest.cc',
+ 'display/screen_position_controller_unittest.cc',
+ 'drag_drop/drag_drop_controller_unittest.cc',
+ 'drag_drop/drag_drop_tracker_unittest.cc',
+ 'extended_desktop_unittest.cc',
+ 'focus_cycler_unittest.cc',
+ 'keyboard_overlay/keyboard_overlay_delegate_unittest.cc',
+ 'keyboard_overlay/keyboard_overlay_view_unittest.cc',
+ 'launcher/launcher_model_unittest.cc',
+ 'launcher/launcher_navigator_unittest.cc',
+ 'launcher/launcher_tooltip_manager_unittest.cc',
+ 'launcher/launcher_unittest.cc',
+ 'launcher/launcher_view_unittest.cc',
+ 'launcher/scoped_observer_with_duplicated_sources_unittest.cc',
+ 'magnifier/magnification_controller_unittest.cc',
+ 'root_window_controller_unittest.cc',
+ 'screen_ash_unittest.cc',
+ 'screensaver/screensaver_view_unittest.cc',
+ 'session_state_delegate_stub.cc',
+ 'session_state_delegate_stub.h',
+ 'shelf/shelf_layout_manager_unittest.cc',
+ 'shelf/shelf_widget_unittest.cc',
+ 'shell_unittest.cc',
+ 'shell/app_list.cc',
+ 'shell/bubble.cc',
+ 'shell/context_menu.cc',
+ 'shell/context_menu.h',
+ 'shell/launcher_delegate_impl.cc',
+ 'shell/lock_view.cc',
+ 'shell/panel_window.cc',
+ 'shell/shell_delegate_impl.cc',
+ 'shell/shell_delegate_impl.h',
+ 'shell/toplevel_window.cc',
+ 'shell/widgets.cc',
+ 'shell/window_type_launcher.cc',
+ 'shell/window_watcher.cc',
+ 'shell/window_watcher_unittest.cc',
+ 'system/chromeos/network/network_state_notifier_unittest.cc',
+ 'system/chromeos/power/power_status_unittest.cc',
+ 'system/chromeos/power/tray_power_unittest.cc',
+ 'system/chromeos/screen_security/screen_tray_item_unittest.cc',
+ 'system/chromeos/tray_display_unittest.cc',
+ 'system/tray/system_tray_unittest.cc',
+ 'system/user/tray_user_unittest.cc',
+ 'system/web_notification/web_notification_tray_unittest.cc',
+ 'test/ash_test_helper_unittest.cc',
+ 'test/ash_unittests.cc',
+ 'tooltips/tooltip_controller_unittest.cc',
+ 'touch/touch_observer_hud_unittest.cc',
+ 'wm/activation_controller_unittest.cc',
+ 'wm/ash_activation_controller_unittest.cc',
+ 'wm/ash_native_cursor_manager_unittest.cc',
+ 'wm/base_layout_manager_unittest.cc',
+ 'wm/custom_frame_view_ash_unittest.cc',
+ 'wm/dock/docked_window_layout_manager_unittest.cc',
+ 'wm/dock/docked_window_resizer_unittest.cc',
+ 'wm/drag_window_resizer_unittest.cc',
+ 'wm/frame_painter_unittest.cc',
+ 'wm/lock_state_controller_impl2_unittest.cc',
+ 'wm/panels/panel_layout_manager_unittest.cc',
+ 'wm/panels/panel_window_resizer_unittest.cc',
+ 'wm/partial_screenshot_view_unittest.cc',
+ 'wm/power_button_controller_unittest.cc',
+ 'wm/screen_dimmer_unittest.cc',
+ 'wm/stacking_controller_unittest.cc',
+ 'wm/sticky_keys_unittest.cc',
+ 'wm/system_gesture_event_filter_unittest.cc',
+ 'wm/system_modal_container_layout_manager_unittest.cc',
+ 'wm/toplevel_window_event_handler_unittest.cc',
+ 'wm/user_activity_detector_unittest.cc',
+ 'wm/video_detector_unittest.cc',
+ 'wm/window_animations_unittest.cc',
+ 'wm/window_cycle_controller_unittest.cc',
+ 'wm/window_manager_unittest.cc',
+ 'wm/window_modality_controller_unittest.cc',
+ 'wm/window_selector_unittest.cc',
+ 'wm/window_util_unittest.cc',
+ 'wm/workspace_controller_test_helper.cc',
+ 'wm/workspace_controller_test_helper.h',
+ 'wm/workspace_controller_unittest.cc',
+ 'wm/workspace/magnetism_matcher_unittest.cc',
+ 'wm/workspace/multi_window_resize_controller_unittest.cc',
+ 'wm/workspace/workspace_event_handler_test_helper.cc',
+ 'wm/workspace/workspace_event_handler_test_helper.h',
+ 'wm/workspace/workspace_event_handler_unittest.cc',
+ 'wm/workspace/workspace_layout_manager_unittest.cc',
+ 'wm/workspace/workspace_window_resizer_unittest.cc',
+ ],
+ 'conditions': [
+ ['OS=="win"', {
+ 'sources/': [
+ # TODO(zork): fix this test to build on Windows. See: crosbug.com/26906
+ ['exclude', 'focus_cycler_unittest.cc'],
+ # All tests for multiple displays: not supported on Windows Ash.
+ ['exclude', 'accelerators/nested_dispatcher_controller_unittest.cc'],
+ ['exclude', 'wm/drag_window_resizer_unittest.cc'],
+ # Can't resize on Windows Ash. http://crbug.com/165962
+ ['exclude', 'ash_root_window_transformer_unittest.cc'],
+ ['exclude', 'magnifier/magnification_controller_unittest.cc'],
+ ['exclude', 'wm/workspace/workspace_window_resizer_unittest.cc'],
+ ['exclude', 'wm/sticky_keys_unittest.cc'],
+ ],
+ 'sources': [
+ '<(SHARED_INTERMEDIATE_DIR)/ui/ui_resources/ui_unscaled_resources.rc',
+ ],
+ # TODO(jschuh): crbug.com/167187 fix size_t to int truncations.
+ 'msvs_disabled_warnings': [ 4267, ],
+ }],
+ ['OS=="mac"', {
+ 'sources/': [
+ ['exclude', 'accelerators/accelerator_controller_unittest.cc'],
+ ['exclude', 'accelerators/accelerator_filter_unittest.cc'],
+ ['exclude', 'accelerators/nested_dispatcher_controller_unittest.cc'],
+ ['exclude', 'drag_drop/drag_drop_controller_unittest.cc'],
+ ['exclude', 'tooltips/tooltip_controller_unittest.cc'],
+ ],
+ 'dependencies': [
+ # Mac tests access resources via the 'AuraShell.app' directory.
+ 'ash_shell',
+ ],
+ # Special linker instructions that avoids stripping Obj-C classes that
+ # are not referenced in code, but are referenced in nibs.
+ 'xcode_settings': {'OTHER_LDFLAGS': ['-Wl,-ObjC']},
+ }],
+ ['use_x11==1', {
+ 'sources': [
+ 'display/display_util_x11_unittest.cc'
+ ],
+ }],
+ ['chromeos!=1', {
+ 'sources/': [
+ ['exclude', 'display/display_error_observer_unittest.cc'],
+ ],
+ }, { # chromeos==1
+ 'dependencies': [
+ '../chromeos/chromeos.gyp:power_manager_proto',
+ ],
+ }],
+ ['OS=="linux" and component=="shared_library" and linux_use_tcmalloc==1', {
+ 'dependencies': [
+ '<(DEPTH)/base/allocator/allocator.gyp:allocator',
+ ],
+ 'link_settings': {
+ 'ldflags': ['-rdynamic'],
+ },
+ }],
+ ],
+ },
+ {
+ 'target_name': 'ash_shell',
+ 'type': 'executable',
+ 'dependencies': [
+ 'ash_strings.gyp:ash_strings',
+ '../base/base.gyp:base',
+ '../base/base.gyp:base_i18n',
+ '../chrome/chrome_resources.gyp:packed_resources',
+ '../content/content.gyp:content_shell_lib',
+ '../content/content.gyp:content',
+ '../skia/skia.gyp:skia',
+ '../third_party/icu/icu.gyp:icui18n',
+ '../third_party/icu/icu.gyp:icuuc',
+ '../ui/app_list/app_list.gyp:app_list',
+ '../ui/aura/aura.gyp:aura',
+ '../ui/compositor/compositor.gyp:compositor',
+ '../ui/keyboard/keyboard.gyp:keyboard',
+ '../ui/message_center/message_center.gyp:message_center',
+ '../ui/ui.gyp:ui',
+ '../ui/ui.gyp:ui_resources',
+ '../ui/views/views.gyp:views',
+ '../ui/views/views.gyp:views_examples_lib',
+ '../ui/views/views.gyp:views_examples_with_content_lib',
+ '../ui/views/views.gyp:views_test_support',
+ 'ash',
+ 'ash_resources',
+ ],
+ 'sources': [
+ 'session_state_delegate_stub.cc',
+ 'session_state_delegate_stub.h',
+ 'shell/app_list.cc',
+ 'shell/bubble.cc',
+ 'shell/content_client/shell_browser_main_parts.cc',
+ 'shell/content_client/shell_browser_main_parts.h',
+ 'shell/content_client/shell_content_browser_client.cc',
+ 'shell/content_client/shell_content_browser_client.h',
+ 'shell/content_client/shell_main_delegate.cc',
+ 'shell/content_client/shell_main_delegate.h',
+ 'shell/context_menu.cc',
+ 'shell/context_menu.h',
+ 'shell/example_factory.h',
+ 'shell/launcher_delegate_impl.cc',
+ 'shell/launcher_delegate_impl.h',
+ 'shell/lock_view.cc',
+ 'shell/panel_window.cc',
+ 'shell/panel_window.h',
+ 'shell/shell_delegate_impl.cc',
+ 'shell/shell_delegate_impl.h',
+ 'shell/shell_main.cc',
+ 'shell/shell_main_parts.cc',
+ 'shell/shell_main_parts.h',
+ 'shell/shell_main_parts_mac.mm',
+ 'shell/toplevel_window.cc',
+ 'shell/toplevel_window.h',
+ 'shell/widgets.cc',
+ 'shell/window_type_launcher.cc',
+ 'shell/window_type_launcher.h',
+ 'shell/window_watcher.cc',
+ 'shell/window_watcher.h',
+ '../content/app/startup_helper_win.cc',
+ '../ui/views/test/test_views_delegate.cc',
+ ],
+ 'conditions': [
+ ['OS=="win"', {
+ 'msvs_settings': {
+ 'VCLinkerTool': {
+ 'SubSystem': '2', # Set /SUBSYSTEM:WINDOWS
+ },
+ },
+ 'dependencies': [
+ '../sandbox/sandbox.gyp:sandbox',
+ ],
+ }],
+ ['OS=="mac"', {
+ 'product_name': 'AuraShell',
+ 'mac_bundle': 1,
+ 'sources/': [
+ ['exclude', 'shell/shell_main_parts.cc'],
+ ],
+ 'mac_bundle_resources': [
+ 'shell/cocoa/app.icns',
+ 'shell/cocoa/app-Info.plist',
+ 'shell/cocoa/nibs/MainMenu.xib',
+ 'shell/cocoa/nibs/RootWindow.xib',
+ '<(SHARED_INTERMEDIATE_DIR)/repack/chrome.pak',
+ '<!@pymod_do_main(repack_locales -o -p <(OS) -g <(grit_out_dir) -s <(SHARED_INTERMEDIATE_DIR) -x <(SHARED_INTERMEDIATE_DIR) <(locales))',
+ ],
+ 'mac_bundle_resources!': [
+ 'shell/cocoa/app-Info.plist',
+ ],
+ 'xcode_settings': {
+ 'INFOPLIST_FILE': 'shell/cocoa/app-Info.plist',
+ },
+ }],
+ ],
+ },
+ ],
+}
diff --git a/chromium/ash/ash_chromeos_strings.grdp b/chromium/ash/ash_chromeos_strings.grdp
new file mode 100644
index 00000000000..1190db9c04e
--- /dev/null
+++ b/chromium/ash/ash_chromeos_strings.grdp
@@ -0,0 +1,270 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- ChromeOS-specific strings (included from ash_strings.grd).
+ Everything in this file is wrapped in <if expr="pp_ifdef('chromeos')">. -->
+<grit-part>
+
+ <!-- Status tray charging strings. -->
+ <message name="IDS_ASH_STATUS_TRAY_LOW_POWER_CHARGER_TITLE" desc="The title of a notification indicating that a low-current USB charger has been connected.">
+ Low-power charger connected
+ </message>
+ <message name="IDS_ASH_STATUS_TRAY_LOW_POWER_CHARGER_MESSAGE" desc="The message body of a notification indicating that a low-current USB charger has been connected.">
+ Your Chromebook may not charge while it is turned on. Consider using the official charger.
+ </message>
+
+ <!-- Status Tray Network strings -->
+ <message name="IDS_ASH_STATUS_TRAY_NETWORK" desc="The label used in the network dialog header.">
+ Network
+ </message>
+ <message name="IDS_ASH_STATUS_TRAY_VPN" desc="The label used in the VPN detailed view header.">
+ Private Network
+ </message>
+ <message name="IDS_ASH_STATUS_TRAY_ETHERNET" desc="The ethernet network device.">
+ Ethernet
+ </message>
+ <message name="IDS_ASH_STATUS_TRAY_WIFI" desc="The wifi network device.">
+ Wi-Fi
+ </message>
+ <message name="IDS_ASH_STATUS_TRAY_CELLULAR" desc="The accessible text for the cellular button.">
+ Cellular
+ </message>
+ <message name="IDS_ASH_STATUS_TRAY_NETWORK_INFO" desc="The accessible text for the network info button.">
+ Network Info
+ </message>
+ <message name="IDS_ASH_STATUS_TRAY_IP" desc="The label for the IP address of the network:">
+ IP Address
+ </message>
+ <message name="IDS_ASH_STATUS_TRAY_NO_NETWORKS" desc="The message to display in the network info bubble when it is otherwise empty.">
+ No network information available
+ </message>
+ <message name="IDS_ASH_STATUS_TRAY_NO_CELLULAR_NETWORKS" desc="The message to display in the network list when no cellular networks are available.">
+ No cellular network available
+ </message>
+ <message name="IDS_ASH_STATUS_TRAY_NETWORK_NOT_CONNECTED" desc="Description in status area or network dropdown when no network is connected.">
+ No network
+ </message>
+ <message name="IDS_ASH_STATUS_TRAY_VPN_DISCONNECTED" desc="The label used in system tray bubble to display vpn is disconnected.">
+ VPN disconnected
+ </message>
+ <message name="IDS_ASH_STATUS_TRAY_NETWORK_CONNECTED" desc="Message for the network tray tooltip and default menu text when connected to a network.">
+ Connected to <ph name="NAME">$1<ex>GoogleGuest</ex></ph>
+ </message>
+ <message name="IDS_ASH_STATUS_TRAY_NETWORK_CONNECTING" desc="Message for the network tray tooltip and default menu text when connecting to a network.">
+ Connecting to <ph name="NAME">$1<ex>GoogleGuest</ex></ph>
+ </message>
+ <message name="IDS_ASH_STATUS_TRAY_NETWORK_ACTIVATING" desc="Message for the network tray tooltip and default menu text when activating a network.">
+ Activating <ph name="NAME">$1<ex>YBH Cellular</ex></ph>
+ </message>
+ <message name="IDS_ASH_STATUS_TRAY_NETWORK_LIST_CONNECTING" desc="Message for the network list when connecting to a network.">
+ <ph name="NAME">$1<ex>GoogleGuest</ex></ph>: Connecting...
+ </message>
+ <message name="IDS_ASH_STATUS_TRAY_NETWORK_LIST_ACTIVATING" desc="Message for the network list when activating a network.">
+ <ph name="NAME">$1<ex>YBH Cellular</ex></ph>: Activating...
+ </message>
+ <message name="IDS_ASH_STATUS_TRAY_NETWORK_LIST_ACTIVATE" desc="Message for the network list to activate the network.">
+ Activate <ph name="NETWORKSERVICE">$1<ex>YBH Cellular</ex></ph>
+ </message>
+ <message name="IDS_ASH_STATUS_TRAY_NETWORK_SETTINGS" desc="The label used in the settings entry in the network dialog.">
+ Settings...
+ </message>
+ <message name="IDS_ASH_STATUS_TRAY_NETWORK_PROXY_SETTINGS" desc="The label used in the proxy settings entry in the network dialog in the login screen.">
+ Proxy...
+ </message>
+ <message name="IDS_ASH_STATUS_TRAY_NETWORK_WIFI_ENABLED" desc="The label used in the tray popup to notify that Wi-Fi is turned on.">
+ Wi-Fi is turned on.
+ </message>
+ <message name="IDS_ASH_STATUS_TRAY_NETWORK_WIFI_DISABLED" desc="The label used in the tray popup to notify that Wi-Fi is turned off.">
+ Wi-Fi is turned off.
+ </message>
+ <message name="IDS_ASH_STATUS_TRAY_NETWORK_NO_VPN" desc="The label used in the tray popup to notify no vpn is configured.">
+ VPN is not configured.
+ </message>
+ <message name="IDS_ASH_STATUS_TRAY_ENABLE_WIFI" desc="The label used for the item to enable wifi.">
+ Enable Wi-Fi
+ </message>
+ <message name="IDS_ASH_STATUS_TRAY_DISABLE_WIFI" desc="The label used for the item to disable wifi.">
+ Disable Wi-Fi
+ </message>
+ <message name="IDS_ASH_STATUS_TRAY_OTHER_WIFI" desc="The label used for the item to display other Wi-Fi networks.">
+ Join other...
+ </message>
+ <message name="IDS_ASH_STATUS_TRAY_OTHER_VPN" desc="The label used for the item to configure other private networks.">
+ Join other...
+ </message>
+ <message name="IDS_ASH_STATUS_TRAY_TURN_ON_WIFI" desc="The label used for the item to turn on Wi-Fi networks.">
+ Turn Wi-Fi on...
+ </message>
+ <message name="IDS_ASH_STATUS_TRAY_ENABLE_MOBILE" desc="The label used for the item to enable cellular networks.">
+ Enable mobile data
+ </message>
+ <message name="IDS_ASH_STATUS_TRAY_DISABLE_MOBILE" desc="The label used for the item to disable cellular networks.">
+ Disable mobile data
+ </message>
+ <message name="IDS_ASH_STATUS_TRAY_SETUP_MOBILE" desc="The label used for the item to setup cellular network when no SIM card in installed.">
+ Setup mobile data
+ </message>
+ <message name="IDS_ASH_STATUS_TRAY_OTHER_MOBILE" desc="The label used for the item to display other cellular networks.">
+ Mobile ...
+ </message>
+ <message name="IDS_ASH_STATUS_TRAY_MOBILE_VIEW_ACCOUNT" desc="In the network popup bubble, the text of the top-up URL link.">
+ View mobile account
+ </message>
+ <message name="IDS_ASH_STATUS_TRAY_WIFI_SCANNING_MESSAGE" desc="Scanning for wifi networks">
+ Searching for Wi-Fi networks...
+ </message>
+ <message name="IDS_ASH_STATUS_TRAY_INITIALIZING_CELLULAR" desc="Message for the status area when initializing the cellular device.">
+ Initializing cellular modem...
+ </message>
+ <message name="IDS_ASH_STATUS_TRAY_CELLULAR_SCANNING" desc="Message when scanning for cellular networks">
+ Searching for cellular networks...
+ </message>
+
+ <!-- Other Network UI strings -->
+ <message name="IDS_NETWORK_CONNECTION_ERROR_TITLE" desc="Title for network connection error notification">
+ Network Connection Error
+ </message>
+ <message name="IDS_NETWORK_CONNECTION_ERROR_MESSAGE_WITH_DETAILS" desc="Message for network connection error notification with details">
+ Failed to connect to network '<ph name="name">$1<ex>GoogleGuest</ex></ph>': <ph name="details">$2<ex>Unrecognized error</ex></ph>
+ </message>
+ <message name="IDS_NETWORK_CONNECTION_ERROR_MESSAGE_WITH_SERVER_MESSAGE" desc="Message for network connection error notification with details and a server message">
+Failed to connect to '<ph name="name">$1<ex>GoogleGuest</ex></ph>': <ph name="details">$2<ex>Unrecognized error</ex></ph>
+Server message: <ph name="server_msg">$3<ex>Incorrect password</ex></ph>
+ </message>
+ <message name="IDS_NETWORK_OUT_OF_CREDITS_TITLE" desc="Title for network out of data error notification">
+ Network Connection Error
+ </message>
+ <message name="IDS_NETWORK_OUT_OF_CREDITS_BODY" desc="Message body for network out of data error notification">
+ You may have used up your mobile data allowance.
+ </message>
+ <message name="IDS_NETWORK_OUT_OF_CREDITS_LINK" desc="Link text for network out of data error notification">
+ Visit the <ph name="name">$1<ex>GoogleGuest</ex></ph> activation portal to buy more data.
+ </message>
+ <message name="IDS_NETWORK_UNRECOGNIZED_ERROR" desc="Unrecognized Network error text">
+ Unrecognized error: <ph name="desc">$1<ex>ShillErrorString</ex></ph>
+ </message>
+
+ <!-- Network state strings -->
+ <message name="IDS_CHROMEOS_NETWORK_STATE_UNKNOWN" desc="Network state in about:network: UNKNOWN">
+ Unknown
+ </message>
+ <message name="IDS_CHROMEOS_NETWORK_STATE_IDLE" desc="Network state in about:network: IDLE">
+ Idle
+ </message>
+ <message name="IDS_CHROMEOS_NETWORK_STATE_CARRIER" desc="Network state in about:network: CARRIER">
+ Carrier
+ </message>
+ <message name="IDS_CHROMEOS_NETWORK_STATE_ASSOCIATION" desc="Network state in about:network: ASSOCIATION">
+ Association
+ </message>
+ <message name="IDS_CHROMEOS_NETWORK_STATE_CONFIGURATION" desc="Network state in about:network: CONFIGURATION">
+ Configuration
+ </message>
+ <message name="IDS_CHROMEOS_NETWORK_STATE_READY" desc="Network state in about:network: READY">
+ Connected
+ </message>
+ <message name="IDS_CHROMEOS_NETWORK_STATE_DISCONNECT" desc="Network state in about:network: DISCONNECT">
+ Disconnect
+ </message>
+ <message name="IDS_CHROMEOS_NETWORK_STATE_FAILURE" desc="Network state in about:network: FAILURE">
+ Failure
+ </message>
+ <message name="IDS_CHROMEOS_NETWORK_STATE_ACTIVATION_FAILURE" desc="Network state in about:network: ACTIVATION_FAILURE">
+ Activation failure
+ </message>
+ <message name="IDS_CHROMEOS_NETWORK_STATE_UNRECOGNIZED" desc="Network state in about:network: Unrecognized">
+ Unrecognized state
+ </message>
+ <message name="IDS_CHROMEOS_NETWORK_STATE_PORTAL" desc="Network state in about:network: Portal">
+ Portal state
+ </message>
+ <message name="IDS_CHROMEOS_NETWORK_STATE_ONLINE" desc="Network state in about:network: Online">
+ Online state
+ </message>
+ <message name="IDS_CHROMEOS_NETWORK_STATE_CONNECT_REQUESTED" desc="Network state in about:network: Connect Requested">
+ Connect Requested
+ </message>
+
+ <!-- Network error strings -->
+ <message name="IDS_CHROMEOS_NETWORK_ERROR_UNKNOWN" desc="Network error details in notifications: UNKNOWN">
+ Unknown network error
+ </message>
+ <message name="IDS_CHROMEOS_NETWORK_ERROR_OUT_OF_RANGE" desc="Network error details in notifications: OUT_OF_RANGE">
+ Out of range
+ </message>
+ <message name="IDS_CHROMEOS_NETWORK_ERROR_PIN_MISSING" desc="Network error details in notifications: PIN_MISSING">
+ PIN missing
+ </message>
+ <message name="IDS_CHROMEOS_NETWORK_ERROR_DHCP_FAILED" desc="Network error details in notifications: DHCP_FAILED">
+ DHCP lookup failed
+ </message>
+ <message name="IDS_CHROMEOS_NETWORK_ERROR_CONNECT_FAILED" desc="Network error details in notifications: CONNECT_FAILED">
+ Connect failed
+ </message>
+ <message name="IDS_CHROMEOS_NETWORK_ERROR_BAD_PASSPHRASE" desc="Network error details in notifications: BAD_PASSPHRASE">
+ Bad passphrase
+ </message>
+ <message name="IDS_CHROMEOS_NETWORK_ERROR_BAD_WEPKEY" desc="Network error details in notifications: BAD_WEPKEY">
+ Bad WEP key
+ </message>
+ <message name="IDS_CHROMEOS_NETWORK_ERROR_ACTIVATION_FAILED" desc="Network error details in notifications: ACTIVATION_FAILED">
+ Activation failed
+ </message>
+ <message name="IDS_CHROMEOS_NETWORK_ERROR_NEED_EVDO" desc="Network error details in notifications: NEED_EVDO">
+ Need EVDO
+ </message>
+ <message name="IDS_CHROMEOS_NETWORK_ERROR_NEED_HOME_NETWORK" desc="Network error details in notifications: NEED_HOME_NETWORK">
+ Need home network
+ </message>
+ <message name="IDS_CHROMEOS_NETWORK_ERROR_OTASP_FAILED" desc="Network error details in notifications: OTASP_FAILED">
+ OTASP failed
+ </message>
+ <message name="IDS_CHROMEOS_NETWORK_ERROR_AAA_FAILED" desc="Network error details in notifications: AAA_FAILED">
+ AAA check failed
+ </message>
+ <message name="IDS_CHROMEOS_NETWORK_ERROR_INTERNAL" desc="Network error details in notifications: INTERNAL">
+ Internal error
+ </message>
+ <message name="IDS_CHROMEOS_NETWORK_ERROR_DNS_LOOKUP_FAILED" desc="Network error details in notifications: DNS_LOOKUP_FAILED">
+ DNS lookup failed
+ </message>
+ <message name="IDS_CHROMEOS_NETWORK_ERROR_HTTP_GET_FAILED" desc="Network error details in notifications: HTTP_GET_FAILED">
+ HTTP get failed
+ </message>
+ <message name="IDS_CHROMEOS_NETWORK_ERROR_IPSEC_PSK_AUTH_FAILED" desc="Network error details in notifications: IPSEC_PSK_AUTH_FAILED">
+ Incorrect password
+ </message>
+ <message name="IDS_CHROMEOS_NETWORK_ERROR_CERT_AUTH_FAILED" desc="Network error details in notifications: IPSEC_CERT_AUTH_FAILED">
+ Authentication certificate rejected by network
+ </message>
+ <message name="IDS_CHROMEOS_NETWORK_ERROR_EAP_LOCAL_TLS_FAILED" desc="Network error details in notifications: EAP_LOCAL_TLS_FAILED">
+ Authentication certificate rejected locally
+ </message>
+ <message name="IDS_CHROMEOS_NETWORK_ERROR_EAP_REMOTE_TLS_FAILED" desc="Network error details in notifications: EAP_REMOTE_TLS_FAILED">
+ Authentication certificate rejected remotely
+ </message>
+ <message name="IDS_CHROMEOS_NETWORK_ERROR_PPP_AUTH_FAILED" desc="Network error details in notifications: PPP_AUTH_FAILED">
+ PPP authentication failed due to an incorrect username or password
+ </message>
+ <message name="IDS_CHROMEOS_NETWORK_ERROR_CONFIGURE_FAILED" desc="Network error details in notifications: Configuration error">
+ Failed to configure network
+ </message>
+ <message name="IDS_CHROMEOS_NETWORK_ERROR_UNRECOGNIZED" desc="Network error details in notifications: Unrecognized">
+ Unrecognized error
+ </message>
+
+ <!-- Status tray screen capture strings. -->
+ <message name="IDS_ASH_STATUS_TRAY_SCREEN_CAPTURE_STOP" desc="label used for screen capture stop button">
+ Stop
+ </message>
+
+ <!-- Status tray screen share strings. -->
+ <message name="IDS_ASH_STATUS_TRAY_SCREEN_SHARE_STOP" desc="label used for screen sharing stop button">
+ Stop
+ </message>
+ <message name="IDS_ASH_STATUS_TRAY_SCREEN_SHARE_BEING_HELPED" desc="label for screen sharing notification">
+ Sharing control of your screen via Hangouts.
+ </message>
+ <message name="IDS_ASH_STATUS_TRAY_SCREEN_SHARE_BEING_HELPED_NAME" desc="label for screen sharing notification with name">
+ Sharing control of your screen with <ph name="HELPER_NAME">$1<ex>Walder Frey</ex></ph> via Hangouts.
+ </message>
+
+</grit-part>
diff --git a/chromium/ash/ash_constants.cc b/chromium/ash/ash_constants.cc
new file mode 100644
index 00000000000..a1404d06081
--- /dev/null
+++ b/chromium/ash/ash_constants.cc
@@ -0,0 +1,23 @@
+// Copyright (c) 2012 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.
+
+#include "ash/ash_constants.h"
+
+#include "third_party/skia/include/core/SkColor.h"
+#include "ui/aura/window_property.h"
+
+namespace ash {
+
+DEFINE_WINDOW_PROPERTY_KEY(bool, kConstrainedWindowKey, false);
+
+const int kResizeAreaCornerSize = 16;
+const int kResizeOutsideBoundsSize = 6;
+const int kResizeOutsideBoundsScaleForTouch = 5;
+const int kResizeInsideBoundsSize = 1;
+
+const SkColor kChromeOsBootColor = SkColorSetRGB(0xfe, 0xfe, 0xfe);
+
+const SkColor kFocusBorderColor = SkColorSetRGB(64, 128, 250);
+
+} // namespace ash
diff --git a/chromium/ash/ash_constants.h b/chromium/ash/ash_constants.h
new file mode 100644
index 00000000000..8e889851ea6
--- /dev/null
+++ b/chromium/ash/ash_constants.h
@@ -0,0 +1,43 @@
+// Copyright (c) 2012 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 ASH_ASH_CONSTANTS_H_
+#define ASH_ASH_CONSTANTS_H_
+
+#include "ash/ash_export.h"
+#include "ui/aura/window.h"
+#include "ui/base/ui_base_types.h"
+
+typedef unsigned int SkColor;
+
+namespace ash {
+
+// The window is a constrained window and lives therefore entirely within
+// another aura window.
+ASH_EXPORT extern const aura::WindowProperty<bool>* const
+ kConstrainedWindowKey;
+
+// In the window corners, the resize areas don't actually expand bigger, but the
+// 16 px at the end of each edge triggers diagonal resizing.
+ASH_EXPORT extern const int kResizeAreaCornerSize;
+
+// Ash windows do not have a traditional visible window frame. Window content
+// extends to the edge of the window. We consider a small region outside the
+// window bounds and an even smaller region overlapping the window to be the
+// "non-client" area and use it for resizing.
+ASH_EXPORT extern const int kResizeOutsideBoundsSize;
+ASH_EXPORT extern const int kResizeOutsideBoundsScaleForTouch;
+ASH_EXPORT extern const int kResizeInsideBoundsSize;
+
+#if defined(OS_CHROMEOS)
+// Background color used for the Chrome OS boot splash screen.
+extern const SkColor kChromeOsBootColor;
+#endif
+
+// The border color of keyboard focus for launcher items and system tray.
+extern const SkColor kFocusBorderColor;
+
+} // namespace ash
+
+#endif // ASH_ASH_CONSTANTS_H_
diff --git a/chromium/ash/ash_export.h b/chromium/ash/ash_export.h
new file mode 100644
index 00000000000..c220690c991
--- /dev/null
+++ b/chromium/ash/ash_export.h
@@ -0,0 +1,32 @@
+// Copyright (c) 2012 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 ASH_ASH_EXPORT_H_
+#define ASH_ASH_EXPORT_H_
+
+// Defines ASH_EXPORT so that functionality implemented by the aura_shell
+// module can be exported to consumers.
+
+#if defined(COMPONENT_BUILD)
+#if defined(WIN32)
+
+#if defined(ASH_IMPLEMENTATION)
+#define ASH_EXPORT __declspec(dllexport)
+#else
+#define ASH_EXPORT __declspec(dllimport)
+#endif // defined(ASH_IMPLEMENTATION)
+
+#else // defined(WIN32)
+#if defined(ASH_IMPLEMENTATION)
+#define ASH_EXPORT __attribute__((visibility("default")))
+#else
+#define ASH_EXPORT
+#endif
+#endif
+
+#else // defined(COMPONENT_BUILD)
+#define ASH_EXPORT
+#endif
+
+#endif // ASH_ASH_EXPORT_H_
diff --git a/chromium/ash/ash_resources.gypi b/chromium/ash/ash_resources.gypi
new file mode 100644
index 00000000000..5c2d62ad43b
--- /dev/null
+++ b/chromium/ash/ash_resources.gypi
@@ -0,0 +1,29 @@
+# Copyright (c) 2012 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.
+
+{
+ 'targets': [
+ {
+ 'target_name': 'ash_resources',
+ 'type': 'none',
+ 'variables': {
+ 'grit_out_dir': '<(SHARED_INTERMEDIATE_DIR)/ash/ash_resources',
+ },
+ 'actions': [
+ {
+ 'action_name': 'ash_resources',
+ 'variables': {
+ 'grit_grd_file': 'resources/ash_resources.grd',
+ },
+ 'includes': [ '../build/grit_action.gypi' ],
+ },
+ ],
+ 'direct_dependent_settings': {
+ 'include_dirs': [
+ '<(SHARED_INTERMEDIATE_DIR)/ash/ash_resources',
+ ],
+ },
+ },
+ ],
+}
diff --git a/chromium/ash/ash_strings.grd b/chromium/ash/ash_strings.grd
new file mode 100644
index 00000000000..2d75802d0ca
--- /dev/null
+++ b/chromium/ash/ash_strings.grd
@@ -0,0 +1,584 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+This file contains the strings for ash.
+-->
+
+<grit base_dir="." latest_public_release="0" current_release="1"
+ source_lang_id="en" enc_check="möl">
+ <outputs>
+ <output filename="grit/ash_strings.h" type="rc_header">
+ <emit emit_type='prepend'></emit>
+ </output>
+ <output filename="ash_strings_am.pak" type="data_package" lang="am" />
+ <output filename="ash_strings_ar.pak" type="data_package" lang="ar" />
+ <if expr="pp_ifdef('use_third_party_translations')">
+ <output filename="ash_strings_ast.pak" type="data_package" lang="ast" />
+ </if>
+ <output filename="ash_strings_bg.pak" type="data_package" lang="bg" />
+ <output filename="ash_strings_bn.pak" type="data_package" lang="bn" />
+ <if expr="pp_ifdef('use_third_party_translations')">
+ <output filename="ash_strings_bs.pak" type="data_package" lang="bs" />
+ </if>
+ <output filename="ash_strings_ca.pak" type="data_package" lang="ca" />
+ <if expr="pp_ifdef('use_third_party_translations')">
+ <output filename="ash_strings_ca@valencia.pak" type="data_package" lang="ca@valencia" />
+ </if>
+ <output filename="ash_strings_cs.pak" type="data_package" lang="cs" />
+ <output filename="ash_strings_da.pak" type="data_package" lang="da" />
+ <output filename="ash_strings_de.pak" type="data_package" lang="de" />
+ <output filename="ash_strings_el.pak" type="data_package" lang="el" />
+ <if expr="pp_ifdef('use_third_party_translations')">
+ <output filename="ash_strings_en-AU.pak" type="data_package" lang="en-AU" />
+ </if>
+ <output filename="ash_strings_en-GB.pak" type="data_package" lang="en-GB" />
+ <output filename="ash_strings_en-US.pak" type="data_package" lang="en" />
+ <if expr="pp_ifdef('use_third_party_translations')">
+ <output filename="ash_strings_eo.pak" type="data_package" lang="eo" />
+ </if>
+ <output filename="ash_strings_es.pak" type="data_package" lang="es" />
+ <output filename="ash_strings_es-419.pak" type="data_package" lang="es-419" />
+ <output filename="ash_strings_et.pak" type="data_package" lang="et" />
+ <if expr="pp_ifdef('use_third_party_translations')">
+ <output filename="ash_strings_eu.pak" type="data_package" lang="eu" />
+ </if>
+ <output filename="ash_strings_fa.pak" type="data_package" lang="fa" />
+ <output filename="ash_strings_fake-bidi.pak" type="data_package" lang="fake-bidi" />
+ <output filename="ash_strings_fi.pak" type="data_package" lang="fi" />
+ <output filename="ash_strings_fil.pak" type="data_package" lang="fil" />
+ <output filename="ash_strings_fr.pak" type="data_package" lang="fr" />
+ <if expr="pp_ifdef('use_third_party_translations')">
+ <output filename="ash_strings_gl.pak" type="data_package" lang="gl" />
+ </if>
+ <output filename="ash_strings_gu.pak" type="data_package" lang="gu" />
+ <output filename="ash_strings_he.pak" type="data_package" lang="he" />
+ <output filename="ash_strings_hi.pak" type="data_package" lang="hi" />
+ <output filename="ash_strings_hr.pak" type="data_package" lang="hr" />
+ <output filename="ash_strings_hu.pak" type="data_package" lang="hu" />
+ <if expr="pp_ifdef('use_third_party_translations')">
+ <output filename="ash_strings_hy.pak" type="data_package" lang="hy" />
+ <output filename="ash_strings_ia.pak" type="data_package" lang="ia" />
+ </if>
+ <output filename="ash_strings_id.pak" type="data_package" lang="id" />
+ <output filename="ash_strings_it.pak" type="data_package" lang="it" />
+ <output filename="ash_strings_ja.pak" type="data_package" lang="ja" />
+ <if expr="pp_ifdef('use_third_party_translations')">
+ <output filename="ash_strings_ka.pak" type="data_package" lang="ka" />
+ </if>
+ <output filename="ash_strings_kn.pak" type="data_package" lang="kn" />
+ <output filename="ash_strings_ko.pak" type="data_package" lang="ko" />
+ <if expr="pp_ifdef('use_third_party_translations')">
+ <output filename="ash_strings_ku.pak" type="data_package" lang="ku" />
+ <output filename="ash_strings_kw.pak" type="data_package" lang="kw" />
+ </if>
+ <output filename="ash_strings_lt.pak" type="data_package" lang="lt" />
+ <output filename="ash_strings_lv.pak" type="data_package" lang="lv" />
+ <output filename="ash_strings_ml.pak" type="data_package" lang="ml" />
+ <output filename="ash_strings_mr.pak" type="data_package" lang="mr" />
+ <output filename="ash_strings_ms.pak" type="data_package" lang="ms" />
+ <output filename="ash_strings_nl.pak" type="data_package" lang="nl" />
+ <!-- The translation console uses 'no' for Norwegian Bokmål. It should
+ be 'nb'. -->
+ <output filename="ash_strings_nb.pak" type="data_package" lang="no" />
+ <output filename="ash_strings_pl.pak" type="data_package" lang="pl" />
+ <output filename="ash_strings_pt-BR.pak" type="data_package" lang="pt-BR" />
+ <output filename="ash_strings_pt-PT.pak" type="data_package" lang="pt-PT" />
+ <output filename="ash_strings_ro.pak" type="data_package" lang="ro" />
+ <output filename="ash_strings_ru.pak" type="data_package" lang="ru" />
+ <output filename="ash_strings_sk.pak" type="data_package" lang="sk" />
+ <output filename="ash_strings_sl.pak" type="data_package" lang="sl" />
+ <output filename="ash_strings_sr.pak" type="data_package" lang="sr" />
+ <output filename="ash_strings_sv.pak" type="data_package" lang="sv" />
+ <output filename="ash_strings_sw.pak" type="data_package" lang="sw" />
+ <output filename="ash_strings_ta.pak" type="data_package" lang="ta" />
+ <output filename="ash_strings_te.pak" type="data_package" lang="te" />
+ <output filename="ash_strings_th.pak" type="data_package" lang="th" />
+ <output filename="ash_strings_tr.pak" type="data_package" lang="tr" />
+ <if expr="pp_ifdef('use_third_party_translations')">
+ <output filename="ash_strings_ug.pak" type="data_package" lang="ug" />
+ </if>
+ <output filename="ash_strings_uk.pak" type="data_package" lang="uk" />
+ <output filename="ash_strings_vi.pak" type="data_package" lang="vi" />
+ <output filename="ash_strings_zh-CN.pak" type="data_package" lang="zh-CN" />
+ <output filename="ash_strings_zh-TW.pak" type="data_package" lang="zh-TW" />
+ </outputs>
+ <translations>
+ <file path="strings/ash_strings_am.xtb" lang="am" />
+ <file path="strings/ash_strings_ar.xtb" lang="ar" />
+ <file path="strings/ash_strings_bg.xtb" lang="bg" />
+ <file path="strings/ash_strings_bn.xtb" lang="bn" />
+ <file path="strings/ash_strings_ca.xtb" lang="ca" />
+ <file path="strings/ash_strings_cs.xtb" lang="cs" />
+ <file path="strings/ash_strings_da.xtb" lang="da" />
+ <file path="strings/ash_strings_de.xtb" lang="de" />
+ <file path="strings/ash_strings_el.xtb" lang="el" />
+ <file path="strings/ash_strings_en-GB.xtb" lang="en-GB" />
+ <file path="strings/ash_strings_es.xtb" lang="es" />
+ <file path="strings/ash_strings_es-419.xtb" lang="es-419" />
+ <file path="strings/ash_strings_et.xtb" lang="et" />
+ <file path="strings/ash_strings_fa.xtb" lang="fa" />
+ <file path="strings/ash_strings_fi.xtb" lang="fi" />
+ <file path="strings/ash_strings_fil.xtb" lang="fil" />
+ <file path="strings/ash_strings_fr.xtb" lang="fr" />
+ <file path="strings/ash_strings_gu.xtb" lang="gu" />
+ <file path="strings/ash_strings_hi.xtb" lang="hi" />
+ <file path="strings/ash_strings_hr.xtb" lang="hr" />
+ <file path="strings/ash_strings_hu.xtb" lang="hu" />
+ <file path="strings/ash_strings_id.xtb" lang="id" />
+ <file path="strings/ash_strings_it.xtb" lang="it" />
+ <!-- The translation console uses 'iw' for Hebrew, but we use 'he'. -->
+ <file path="strings/ash_strings_iw.xtb" lang="he" />
+ <file path="strings/ash_strings_ja.xtb" lang="ja" />
+ <file path="strings/ash_strings_kn.xtb" lang="kn" />
+ <file path="strings/ash_strings_ko.xtb" lang="ko" />
+ <file path="strings/ash_strings_lt.xtb" lang="lt" />
+ <file path="strings/ash_strings_lv.xtb" lang="lv" />
+ <file path="strings/ash_strings_ml.xtb" lang="ml" />
+ <file path="strings/ash_strings_mr.xtb" lang="mr" />
+ <file path="strings/ash_strings_ms.xtb" lang="ms" />
+ <file path="strings/ash_strings_nl.xtb" lang="nl" />
+ <file path="strings/ash_strings_no.xtb" lang="no" />
+ <file path="strings/ash_strings_pl.xtb" lang="pl" />
+ <file path="strings/ash_strings_pt-BR.xtb" lang="pt-BR" />
+ <file path="strings/ash_strings_pt-PT.xtb" lang="pt-PT" />
+ <file path="strings/ash_strings_ro.xtb" lang="ro" />
+ <file path="strings/ash_strings_ru.xtb" lang="ru" />
+ <file path="strings/ash_strings_sk.xtb" lang="sk" />
+ <file path="strings/ash_strings_sl.xtb" lang="sl" />
+ <file path="strings/ash_strings_sr.xtb" lang="sr" />
+ <file path="strings/ash_strings_sv.xtb" lang="sv" />
+ <file path="strings/ash_strings_sw.xtb" lang="sw" />
+ <file path="strings/ash_strings_ta.xtb" lang="ta" />
+ <file path="strings/ash_strings_te.xtb" lang="te" />
+ <file path="strings/ash_strings_th.xtb" lang="th" />
+ <file path="strings/ash_strings_tr.xtb" lang="tr" />
+ <file path="strings/ash_strings_uk.xtb" lang="uk" />
+ <file path="strings/ash_strings_vi.xtb" lang="vi" />
+ <file path="strings/ash_strings_zh-CN.xtb" lang="zh-CN" />
+ <file path="strings/ash_strings_zh-TW.xtb" lang="zh-TW" />
+ </translations>
+ <release seq="1" allow_pseudo="false">
+ <messages fallback_to_english="true">
+ <!-- TODO add all of your "string table" messages here. Remember to
+ change nontranslateable parts of the messages into placeholders (using the
+ <ph> element). You can also use the 'grit add' tool to help you identify
+ nontranslateable parts and create placeholders for them. -->
+ <!-- TODO(zork): Only include these in Aura builds -->
+ <message name="IDS_AURA_APP_LIST_TITLE" desc="The title used for the Aura app list in the launcher">
+ Apps
+ </message>
+ <message name="IDS_AURA_APP_LIST_SYNCING_TITLE" desc="The title used for the Aura app list in the launcher to indicate loading/syncing apps.">
+ Syncing apps...
+ </message>
+ <message name="IDS_ASH_SHELF_OVERFLOW_NAME" desc="The title used for the Ash overflow button in the shelf">
+ Overflow Button
+ </message>
+ <message name="IDS_ASH_SHELF_CONTEXT_MENU_AUTO_HIDE" desc="Title of the menu item in the context menu for auto-hiding the shelf when the current window is not maximized">
+ Autohide shelf
+ </message>
+ <message name="IDS_ASH_SHELF_CONTEXT_MENU_POSITION" desc="Title of the menu item in the context menu for aligning the shelf">
+ Shelf position
+ </message>
+ <message name="IDS_ASH_SHELF_CONTEXT_MENU_ALIGN_BOTTOM" desc="Title of the menu item in the context menu for aligning the shelf to the bottom of the screen">
+ Bottom
+ </message>
+ <message name="IDS_ASH_SHELF_CONTEXT_MENU_ALIGN_LEFT" desc="Title of the menu item in the context menu for aligning the shelf to the left of the screen">
+ Left
+ </message>
+ <message name="IDS_ASH_SHELF_CONTEXT_MENU_ALIGN_RIGHT" desc="Title of the menu item in the context menu for aligning the shelf to the right of the screen">
+ Right
+ </message>
+ <message name="IDS_ASH_SHELF_ACCESSIBLE_NAME" desc="The accessible name of the shelf.">
+ Shelf
+ </message>
+
+ <message name="IDS_AURA_SET_DESKTOP_WALLPAPER" desc="The label used for change wallpaper in context menu">
+ Set wallpaper...
+ </message>
+
+ <message name="IDS_ASH_KEYBOARD_OVERLAY_TITLE" desc="The title of the keyboard overlay.">
+ Keyboard Overlay
+ </message>
+
+ <message name="IDS_ASH_LEARN_MORE" desc="Text of Learn more links.">
+ Learn more
+ </message>
+
+ <!-- Status tray items -->
+ <message name="IDS_ASH_STATUS_TRAY_ACCESSIBLE_NAME" desc="The accessible name of the status tray.">
+ Status tray
+ </message>
+ <message name="IDS_ASH_STATUS_TRAY_SPOKEN_FEEDBACK_ENABLED_BUBBLE" desc="The message shown on a bubble when spoken feedback is enabled">
+Spoken feedback is enabled.
+Press Ctrl+Alt+Z to disable.
+ </message>
+ <message name="IDS_ASH_STATUS_TRAY_SETTINGS" desc="The label used for the settings item in the status tray.">
+ Settings
+ </message>
+ <message name="IDS_ASH_STATUS_TRAY_SIGN_OUT" desc="The label used for the button in the status tray to sign out of the system.">
+ Sign out
+ </message>
+ <message name="IDS_ASH_STATUS_TRAY_SIGN_OUT_ALL" desc="The label used for the button in the status tray to sign out all users of the system.">
+ Sign out all
+ </message>
+ <message name="IDS_ASH_STATUS_TRAY_GUEST_LABEL" desc="The label used in the system tray's user card to indicate that the current session is a guest session.">
+ Guest
+ </message>
+ <message name="IDS_ASH_STATUS_TRAY_EXIT_GUEST" desc="The label used for the button in the status tray to terminate a guest session.">
+ Exit guest
+ </message>
+ <message name="IDS_ASH_STATUS_TRAY_KIOSK_LABEL" desc="The label used in the system tray's user card to indicate that the current session is running in demo mode.">
+ Demo mode
+ </message>
+ <message name="IDS_ASH_STATUS_TRAY_EXIT_KIOSK" desc="The label used for the button in the status tray to terminate a session kiosk mode.">
+ Exit session
+ </message>
+ <message name="IDS_ASH_STATUS_TRAY_EXIT_PUBLIC" desc="The label used for the button in the status tray to terminate a public account session. If the label is long, indicate where it may be broken into two lines by inserting \n instead of a whitespace.">
+ Exit session
+ </message>
+ <message name="IDS_ASH_STATUS_TRAY_PUBLIC_LABEL" desc="Text of the the ash system bubble's user card when the current session is a public account session.">
+ <ph name="DISPLAY_NAME">$1<ex>Internet kiosk</ex></ph> is a public session managed by <ph name="DOMAIN">$2<ex>yourdomain.com</ex></ph>
+ </message>
+ <message name="IDS_ASH_STATUS_TRAY_LOCK" desc="The label used for the button in the status tray to lock the screen.">
+ Lock
+ </message>
+ <message name="IDS_ASH_STATUS_TRAY_SMS" desc="The label used in the status tray for SMS.">
+ SMS
+ </message>
+ <message name="IDS_ASH_STATUS_TRAY_SMS_MESSAGES" desc="The label used in the default status tray view for SMS with the number of messages.">
+ SMS messages: <ph name="MESSAGE_COUNT">$1<ex>3</ex></ph>
+ </message>
+ <message name="IDS_ASH_STATUS_TRAY_SMS_NUMBER" desc="Sender for SMS messagees in the system tray.">
+ SMS from <ph name="PHONE_NUMBER">$1<ex>08700 776655</ex></ph>
+ </message>
+ <message name="IDS_ASH_STATUS_TRAY_SIGN_IN_ANOTHER_ACCOUNT" desc="The string for the button which lets the user add another account to the current session.">
+ Sign in another account...
+ </message>
+ <message name="IDS_ASH_STATUS_TRAY_CAPTION_CANNOT_ADD_USER" desc="The caption for the error message when the user has reached the limit of multi profile users.">
+ Can't sign into another account.
+ </message>
+ <message name="IDS_ASH_STATUS_TRAY_MESSAGE_CANNOT_ADD_USER" desc="The error message when the user has reached the limit of multi profile users.">
+ You can only have up to three accounts in multiple sign-in.
+ </message>
+
+ <message name="IDS_ASH_STATUS_TRAY_BLUETOOTH" desc="The label used as the header in the bluetooth popup.">
+ Bluetooth
+ </message>
+ <message name="IDS_ASH_STATUS_TRAY_BLUETOOTH_ENABLED" desc="The label used in the tray popup to notify that bluetooth is enabled.">
+ Bluetooth enabled
+ </message>
+ <message name="IDS_ASH_STATUS_TRAY_BLUETOOTH_DISABLED" desc="The label used in the tray popup to notify that bluetooth is disabled.">
+ Bluetooth disabled
+ </message>
+ <message name="IDS_ASH_STATUS_TRAY_BLUETOOTH_CONNECTING" desc="Shows a connecting bluetooth device in the bluetooth list.">
+ <ph name="BLUETOOTH">$1<ex>Apple Magic Mouse</ex></ph>: Connecting...
+ </message>
+ <message name="IDS_ASH_STATUS_TRAY_DISABLE_BLUETOOTH" desc="The label used in the tray popup to disable bluetooth.">
+ Disable Bluetooth
+ </message>
+ <message name="IDS_ASH_STATUS_TRAY_ENABLE_BLUETOOTH" desc="The label used in the tray popup to enable bluetooth.">
+ Enable Bluetooth
+ </message>
+ <message name="IDS_ASH_STATUS_TRAY_BLUETOOTH_MANAGE_DEVICES" desc="The label used in the tray popup to manage bluetooth devices.">
+ Manage devices...
+ </message>
+ <message name="IDS_ASH_STATUS_TRAY_BLUETOOTH_DISCOVERING" desc="The label used in the tray popup to show bluetooth is discovering devices.">
+ Scanning for devices...
+ </message>
+ <message name="IDS_ASH_STATUS_TRAY_TRACING" desc="The status tray item indicating that performance tracing is running.">
+ Performance tracing enabled
+ </message>
+
+ <message name="IDS_ASH_STATUS_TRAY_UPDATE" desc="The label used in the tray popup to notify that the user should restart to get system updates.">
+ Restart to update
+ </message>
+ <message name="IDS_ASH_STATUS_TRAY_RESTART_AND_POWERWASH_UPDATE" desc="The label used in the tray popup to notify that the user should restart and powerwash to get system updates.">
+ Restart and Powerwash to update
+ </message>
+
+ <message name="IDS_ASH_STATUS_TRAY_BRIGHTNESS" desc="The accessible text for the brightness slider.">
+ Brightness
+ </message>
+ <message name="IDS_ASH_STATUS_TRAY_VOLUME" desc="The accessible text for the volume slider.">
+ Volume
+ </message>
+ <message name="IDS_ASH_STATUS_TRAY_AUDIO" desc="The label used in audio detailed page bottom header of ash tray pop up.">
+ Audio Settings
+ </message>
+ <message name="IDS_ASH_STATUS_TRAY_AUDIO_OUTPUT" desc="The label used in audio detailed page for audio output section of ash tray pop up.">
+ OUTPUT
+ </message>
+ <message name="IDS_ASH_STATUS_TRAY_AUDIO_INPUT" desc="The label used in audio detailed page for audio input section of ash tray pop up.">
+ INPUT
+ </message>
+
+ <message name="IDS_ASH_STATUS_TRAY_DISPLAY_MIRRORING" desc="The label used in the tray to show that the current status is mirroring.">
+ Mirroring to <ph name="DISPLAY_NAME">$1</ph>
+ </message>
+ <message name="IDS_ASH_STATUS_TRAY_DISPLAY_EXTENDED" desc="The label used in the tray to show that the current status is extended.">
+ Extending screen to <ph name="DISPLAY_NAME">$1</ph>
+ </message>
+ <message name="IDS_ASH_STATUS_TRAY_DISPLAY_MIRRORING_NO_INTERNAL" desc="The label used in the tray to show that the current status is mirroring and the device doesn't have the internal display.">
+ Mirroring
+ </message>
+ <message name="IDS_ASH_STATUS_TRAY_DISPLAY_EXTENDED_NO_INTERNAL" desc="The label used in the tray to show that the current status is extended and the device doesn't have the internal display.">
+ Extending screen
+ </message>
+ <message name="IDS_ASH_STATUS_TRAY_DISPLAY_DOCKED" desc="The label used in the tray to show that the current status is docked mode.">
+ Dock mode
+ </message>
+ <message name="IDS_ASH_STATUS_TRAY_DISPLAY_RESOLUTION_CHANGED" desc="The label used in the tray to notify that the display resolution settings has changed.">
+ <ph name="DISPLAY_NAME">$1</ph> resolution was changed to <ph name="RESOLUTION">$2</ph>
+ </message>
+ <message name="IDS_ASH_STATUS_TRAY_DISPLAY_ROTATED" desc="The label used in the tray to notify that the display rotation settings has changed.">
+ <ph name="DISPLAY_NAME">$1</ph> was rotated to <ph name="ROTATION">$2</ph>
+ </message>
+ <message name="IDS_ASH_STATUS_TRAY_DISPLAY_SINGLE_DISPLAY" desc="The label used in the tray to show a single display's configuration">
+ <ph name="DISPLAY_NAME">$1</ph>: <ph name="ANNOTATION">$2</ph>
+ </message>
+ <message name="IDS_ASH_STATUS_TRAY_UNKNOWN_DISPLAY_NAME" desc="Label shown in tray for a display whose name is unknown.">
+ Unknown Display
+ </message>
+ <message name="IDS_ASH_STATUS_TRAY_DISPLAY_ANNOTATED_NAME" desc="Label used to show a display name with annotation (like screen resolution or overscan info).">
+ <ph name="DISPLAY_NAME">$1</ph> (<ph name="ANNOTATION">$2</ph>)
+ </message>
+ <message name="IDS_ASH_STATUS_TRAY_DISPLAY_ANNOTATION" desc="Label used to annotate the display's status like resolution or overscan flag.">
+ <ph name="RESOLUTION">$1</ph>, <ph name="OVERSCAN">$2</ph>
+ </message>
+ <message name="IDS_ASH_STATUS_TRAY_DISPLAY_ANNOTATION_OVERSCAN" desc="Label used to describe that the system notice that this display device may have overscan area.">
+ overscan
+ </message>
+ <message name="IDS_ASH_STATUS_TRAY_DISPLAY_STANDARD_ORIENTATION" desc="The default value of display orientation option item.">
+ 0&#x00B0;
+ </message>
+ <message name="IDS_ASH_STATUS_TRAY_DISPLAY_ORIENTATION_90" desc="The value of display orientation option item: 90-degree rotated">
+ 90&#x00B0;
+ </message>
+ <message name="IDS_ASH_STATUS_TRAY_DISPLAY_ORIENTATION_180" desc="The value of display orientation option item: 180-degree rotated">
+ 180&#x00B0;
+ </message>
+ <message name="IDS_ASH_STATUS_TRAY_DISPLAY_ORIENTATION_270" desc="The value of display orientation option item: 270-degree rotated">
+ 270&#x00B0;
+ </message>
+ <message name="IDS_ASH_STATUS_TRAY_DRIVE_SYNCING" desc="The label in the tray to indicate onoing file sync operations.">
+ Syncing <ph name="count">$1<ex>3</ex></ph> file(s)
+ </message>
+ <message name="IDS_ASH_STATUS_TRAY_DRIVE" desc="The label used for Google Drive tray details header.">
+ Google Drive
+ </message>
+ <message name="IDS_ASH_STATUS_TRAY_DRIVE_SETTINGS" desc="The label used for Google Drive settings entry.">
+ Google Drive settings...
+ </message>
+ <message name="IDS_ASH_STATUS_TRAY_IME" desc="The label used as the header in the IME popup.">
+ Input methods
+ </message>
+ <message name="IDS_ASH_STATUS_TRAY_IME_SETTINGS" desc="The label used for IME settings entry.">
+ Customize languages and input...
+ </message>
+ <message name="IDS_ASH_STATUS_TRAY_CAPS_LOCK_ENABLED" desc="The label used for the tray item to indicate caps lock is on and to toggle caps lock by the click.">
+ CAPS LOCK is on
+ </message>
+ <message name="IDS_ASH_STATUS_TRAY_CAPS_LOCK_DISABLED" desc="The label used for the tray item to indicate caps lock is on and to toggle caps lock by the click.">
+ CAPS LOCK is off
+ </message>
+ <message name="IDS_ASH_STATUS_TRAY_CAPS_LOCK_SHORTCUT_SEARCH" desc="The shortcut text used for the caps lock tray item.">
+ Search
+ </message>
+ <message name="IDS_ASH_STATUS_TRAY_CAPS_LOCK_SHORTCUT_SEARCH_OR_SHIFT" desc="The shortcut text used for the caps lock tray item.">
+ Search or Shift
+ </message>
+ <message name="IDS_ASH_STATUS_TRAY_CAPS_LOCK_SHORTCUT_ALT_SEARCH" desc="The shortcut text used for the caps lock tray item.">
+ Alt+Search
+ </message>
+ <message name="IDS_ASH_STATUS_TRAY_CAPS_LOCK_SHORTCUT_ALT_SEARCH_OR_SHIFT" desc="The shortcut text used for the caps lock tray item.">
+ Alt+Search or Shift
+ </message>
+ <message name="IDS_ASH_STATUS_TRAY_CAPS_LOCK_CANCEL_BY_ALT_SEARCH" desc="The message shown on a bubble when caps lock is turned on.">
+CAPS LOCK is on.
+Press Alt+Search or Shift to cancel.
+ </message>
+ <message name="IDS_ASH_STATUS_TRAY_CAPS_LOCK_CANCEL_BY_SEARCH" desc="The message shown on a bubble when caps lock is turned on.">
+CAPS LOCK is on.
+Press Search or Shift to cancel.
+ </message>
+ <message name="IDS_ASH_STATUS_TRAY_IME_TURNED_ON_BUBBLE" desc="The message shown on a bubble when an IME is enabled">
+ Your input method has changed to <ph name="INPUT_METHOD_ID">$1<ex>EN</ex></ph>.
+Press Shift + Alt to switch.
+ </message>
+ <message name="IDS_ASH_STATUS_TRAY_THIRD_PARTY_IME_TURNED_ON_BUBBLE" desc="The message shown on a bubble when a third party IME is enabled">
+ Your input method has changed to <ph name="INPUT_METHOD_ID">$1<ex>EN</ex></ph>*(<ph name="BEGIN_LINK">&lt;a target="_blank" href="$2"&gt;</ph>3rd party<ph name="END_LINK">&lt;/a&gt;<ex>&lt;/a&gt;</ex></ph>).
+Press Shift + Alt to switch.
+ </message>
+
+ <message name="IDS_ASH_STATUS_TRAY_LOCALE_CHANGE_MESSAGE" desc="The message used for locale change notifications in the system tray.">
+ The language has changed from "<ph name="FROM_LOCALE">$1<ex>Italian</ex></ph>" to "<ph name="TO_LOCALE">$2<ex>English (United States)</ex></ph>" after syncing your settings.
+ </message>
+ <message name="IDS_ASH_STATUS_TRAY_LOCALE_REVERT_MESSAGE" desc="Link to revert a change.">
+ Change back to "<ph name="FROM_LOCALE">$1<ex>Italian</ex></ph>" (requires restart)
+ </message>
+
+ <message name="IDS_ASH_STATUS_TRAY_ACCESSIBILITY" desc="The label used in the tray menu to show the accessibility option menu.">
+ Accessibility
+ </message>
+ <message name="IDS_ASH_STATUS_TRAY_ACCESSIBILITY_TITLE" desc="The label used in the title of the accessibility option menu.">
+ Accessibility
+ </message>
+ <message name="IDS_ASH_STATUS_TRAY_ACCESSIBILITY_SPOKEN_FEEDBACK" desc="The label used in the accessibility menu of the
+ system tray to toggle on/off spoken feedback feature.">
+ Spoken feedback
+ </message>
+ <message name="IDS_ASH_STATUS_TRAY_ACCESSIBILITY_HIGH_CONTRAST_MODE" desc="The label used in the accessibility menu of the system tray to toggle on/off high contrast feature.">
+ High contrast mode
+ </message>
+ <message name="IDS_ASH_STATUS_TRAY_ACCESSIBILITY_SCREEN_MAGNIFIER" desc="The label used in the accessibility menu of the system tray to toggle on/off magnifier feature.">
+ Screen magnifier
+ </message>
+ <message name="IDS_ASH_STATUS_TRAY_ACCESSIBILITY_LARGE_CURSOR" desc="The label used in the accessibility menu of the system tray to toggle on/off large mouse cursor feature.">
+ Large mouse cursor
+ </message>
+ <message name="IDS_ASH_STATUS_TRAY_ACCESSIBILITY_LEARN_MORE" desc="The label used in the accessibility menu of the system tray
+ to open a webpage (article on help center) containing explanation about accessibility feature.">
+ Learn more...
+ </message>
+ <message name="IDS_ASH_STATUS_TRAY_ACCESSIBILITY_SETTINGS" desc="The label of the item 'Settings...' used in the accessibility menu of the system tray to open an accessibility setting page.">
+ Settings...
+ </message>
+ <message name="IDS_ASH_STATUS_TRAY_BATTERY_FULL" desc="The label in the tray dialog to indicate that the battery is full.">
+ Battery full
+ </message>
+ <message name="IDS_ASH_STATUS_TRAY_BATTERY_PERCENT" desc="The label in the tray dialog to show the remaining battery power as a percent.">
+ <ph name="percentage">$1<ex>56</ex></ph>% remaining
+ </message>
+ <message name="IDS_ASH_STATUS_TRAY_BATTERY_PERCENT_ONLY" desc="The label in the tray bubble setting row to show the remaining battery power as a percent.">
+ <ph name="percentage">$1<ex>56</ex></ph>%
+ </message>
+ <message name="IDS_ASH_STATUS_TRAY_BATTERY_TIME_UNTIL_FULL" desc="The label in the tray dialog to show a time estimate until the battery is fully charged.">
+ <ph name="hour">$1<ex>2</ex></ph>h <ph name="minute">$2<ex>53</ex></ph>m until full
+ </message>
+ <message name="IDS_ASH_STATUS_TRAY_BATTERY_CALCULATING" desc="The label in the tray dialog indicating that the time to charge or discharge the battery is being calculated.">
+ Calculating...
+ </message>
+ <message name="IDS_ASH_STATUS_TRAY_BATTERY_TIME_LEFT_SHORT" desc="The label in the tray bubble settings row to show a time estimate until the battery is empty.">
+ <ph name="hour">$1<ex>2</ex></ph>:<ph name="minute">$2<ex>53</ex></ph> left
+ </message>
+ <message name="IDS_ASH_STATUS_TRAY_BATTERY_TIME_UNTIL_FULL_SHORT" desc="The label in the tray bubble settings row to show a time estimate until the battery is fully charged.">
+ <ph name="hour">$1<ex>2</ex></ph>:<ph name="minute">$2<ex>53</ex></ph> until full
+ </message>
+ <message name="IDS_ASH_STATUS_TRAY_BATTERY_CHARGING_UNRELIABLE" desc="The label in the tray dialog to indicate that battery charging is unreliable.">
+ Low-power charger
+ </message>
+ <message name="IDS_ASH_STATUS_TRAY_BATTERY_FULL_CHARGE_ACCESSIBLE" desc="The message used by accessibility to show battery is fully charged.">
+ Battery is full.
+ </message>
+ <message name="IDS_ASH_STATUS_TRAY_BATTERY_PERCENT_ACCESSIBLE" desc="The message used by accessibility to show battery is discharging.">
+ Battery is <ph name="percentage">$1<ex>56</ex></ph>% full.
+ </message>
+ <message name="IDS_ASH_STATUS_TRAY_BATTERY_PERCENT_CHARGING_ACCESSIBLE" desc="The message used by accessibility to show battery is being charged.">
+ Battery is <ph name="percentage">$1<ex>56</ex></ph>% full and charging.
+ </message>
+ <message name="IDS_ASH_STATUS_TRAY_BATTERY_CALCULATING_ACCESSIBLE" desc="The message used by accessibility to show battery is calculating its time in short message.">
+ Calculating battery time.
+ </message>
+ <message name= "IDS_ASH_STATUS_TRAY_BATTERY_TIME_LEFT_ACCESSIBLE" desc="The message used by accessibility to read remaining battery time until empty.">
+ Time left until battery is empty, <ph name="time_left">$1<ex>1 hour and 15 minutes</ex></ph>
+ </message>
+ <message name= "IDS_ASH_STATUS_TRAY_BATTERY_TIME_UNTIL_FULL_ACCESSIBLE" desc="The message used by accessibility to read the estimated time until full.">
+ Time remaining until battery is fully charged, <ph name="time_remaining">$1<ex>1 hour and 15 minutes</ex></ph>
+ </message>
+ <message name = "IDS_ASH_STATUS_TRAY_BATTERY_TIME_ACCESSIBLE" desc="The message used by accessibility to read battery time, which includes both non-zero hours and minutes.">
+ <ph name="hour">$1<ex> 1 hour</ex></ph> and <ph name="minute">$2<ex>15 minutes</ex></ph>
+ </message>
+ <message name="IDS_ASH_STATUS_TRAY_BATTERY_CHARGING_UNRELIABLE_ACCESSIBLE" desc="The message used by accessibility to indicate that battery charging is unreliable.">
+ Plugged in to a low-power charger. Battery charging may not be reliable.
+ </message>
+ <message name="IDS_ASH_STATUS_TRAY_HELP" desc="The accessible text for the help button.">
+ Help
+ </message>
+ <message name="IDS_ASH_STATUS_TRAY_DATE" desc="The date displayed on ash system bubble, Depending on launguage, please choose the best separator(eg ',') between abbreviated weekday and date">
+ <ph name="short_weekday">$1<ex>Fri</ex></ph>, <ph name="date">$2<ex>Aug 31, 2012</ex></ph>
+ </message>
+ <message name="IDS_ASH_STATUS_TRAY_SHUTDOWN" desc="The accessible text for the shutdown button.">
+ Shutdown
+ </message>
+ <message name="IDS_ASH_STATUS_TRAY_REMAINING_SESSION_TIME" desc="The time remaining until the end of the session. Shown if the session length is limited.">
+ <ph name="hours">$1<ex>01</ex></ph>:<ph name="minutes">$2<ex>35</ex></ph>:<ph name="seconds">$3<ex>12</ex></ph>
+ </message>
+ <message name="IDS_ASH_STATUS_TRAY_REMAINING_SESSION_TIME_NOTIFICATION" desc="Notification shown to inform the user that the session length is limited.">
+ This session will end in <ph name="session_time_remaining">$1<ex>15 minutes</ex></ph>. You will be automatically signed out.
+ </message>
+
+ <message name="IDS_ASH_STATUS_TRAY_LOCALLY_MANAGED_LABEL" desc="Label shown instead of email for supervised users">
+ Supervised user
+ </message>
+
+ <message name="IDS_ASH_STATUS_TRAY_PREVIOUS_MENU" desc="The accessible text for header entries for detailed versions of status tray items.">
+ Previous menu
+ </message>
+
+ <message name="IDS_ASH_NOTIFICATION_UNREAD_COUNT_NINE_PLUS" desc="The text shown in the notification icon for the unread count when the count is more than 9.">
+ 9+
+ </message>
+ <message name="IDS_ASH_MAXIMIZE_WINDOW" desc="A help text to show that when the button gets clicked the window get maximized.">
+ Maximize
+ </message>
+ <message name="IDS_ASH_SNAP_WINDOW_RIGHT" desc="A help text to show that when the button gets clicked the window get snapped to the right side.">
+ Right
+ </message>
+ <message name="IDS_ASH_SNAP_WINDOW_LEFT" desc="A help text to show that when the button gets clicked the window get snapped to the left side.">
+ Left
+ </message>
+ <message name="IDS_ASH_RESTORE_WINDOW" desc="A help text to show that when the button gets clicked the window gets restored to its normal - non maximized - state.">
+ Restore
+ </message>
+ <message name="IDS_ASH_MINIMIZE_WINDOW" desc="A help text to show that when the button gets clicked the window gets minimized.">
+ Minimize
+ </message>
+ <message name="IDS_ASH_DISPLAY_FAILURE_ON_MIRRORING" desc="An error message to show that the system failed to enter the mirroring mode.">
+ Could not mirror displays since no supported resolutions found. Entered extended desktop instead.
+ </message>
+ <message name="IDS_ASH_DISPLAY_FAILURE_ON_NON_MIRRORING" desc="An error message to show that the system failed to enter the extended desktop mode or unknown status. Please translate the parentized text.">
+ Dear Monitor, it's not working out between us. (That monitor is not supported)
+ </message>
+ <message name="IDS_ASH_DISPLAY_RESOLUTION_CHANGE_ACCEPT" desc="A button label shown in the notification for the resolution change to accept the change">
+ Accept
+ </message>
+ <message name="IDS_ASH_DISPLAY_RESOLUTION_CHANGE_REVERT" desc="A button label shown in the notification for the resolution change to revert the change">
+ Revert
+ </message>
+ <message name="IDS_ASH_DISPLAY_RESOLUTION_TIMEOUT" desc="A message for to notify the user about the timeout of display resolution change.">
+ Reverting to old resolution in <ph name="TIMEOUT_SECONDS">$1</ph>
+ </message>
+ <message name="IDS_ASH_INTERNAL_DISPLAY_NAME" desc="The name of the internal display which is shown in the display settings.">
+ Internal Display
+ </message>
+ <message name="IDS_ASH_LOW_PERIPHERAL_BATTERY_NOTIFICATION_TEXT" desc="The text of the notification when a peripheral device is in low battery condition.">
+ Battery low (<ph name="percentage">$1<ex>56</ex></ph>%)
+ </message>
+ <message name="IDS_ASH_SCREENSHOT_NOTIFICATION_TITLE_SUCCESS" desc="The title of the notification when a screenshot was taken.">
+ Screenshot taken
+ </message>
+ <message name="IDS_ASH_SCREENSHOT_NOTIFICATION_TITLE_FAIL" desc="The title of the notification when taking a screenshot failed.">
+ An error occurred
+ </message>
+ <message name="IDS_ASH_SCREENSHOT_NOTIFICATION_TEXT_SUCCESS" desc="The text of the notification when a screenshot was taken.">
+ Click to view
+ </message>
+ <message name="IDS_ASH_SCREENSHOT_NOTIFICATION_TEXT_FAIL" desc="The text of the notification when taking a screenshot failed.">
+ Failed to save screenshot
+ </message>
+ <message name="IDS_ASH_EXIT_WARNING_POPUP_TEXT" desc="The text of the popup when the user preses the exit shortcut.">
+ Press Ctrl+Shift+Q twice to quit.
+ </message>
+ <message name="IDS_ASH_EXIT_WARNING_POPUP_TEXT_ACCESSIBLE" desc="The message used by accessibility to indicate the content of the popup when the user preses the exit shortcut.">
+ Press Control Shift Q twice to quit.
+ </message>
+
+ <!-- ChromeOS-specific strings -->
+ <if expr="pp_ifdef('chromeos')">
+ <part file="ash_chromeos_strings.grdp" />
+ </if>
+
+ </messages>
+ </release>
+</grit>
+
diff --git a/chromium/ash/ash_strings.gyp b/chromium/ash/ash_strings.gyp
new file mode 100644
index 00000000000..4b09ad25101
--- /dev/null
+++ b/chromium/ash/ash_strings.gyp
@@ -0,0 +1,32 @@
+# Copyright (c) 2012 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.
+
+{
+ 'variables': {
+ 'grit_out_dir': '<(SHARED_INTERMEDIATE_DIR)/chrome',
+ },
+
+ 'targets': [
+ {
+ 'target_name': 'ash_strings',
+ 'type': 'none',
+ 'actions': [
+ # Localizable resources.
+ {
+ 'action_name': 'ash_strings',
+ 'variables': {
+ 'grit_grd_file': 'ash_strings.grd',
+ 'grit_out_dir': '<(SHARED_INTERMEDIATE_DIR)/ash_strings',
+ },
+ 'includes': [ '../build/grit_action.gypi' ],
+ },
+ ],
+ 'direct_dependent_settings': {
+ 'include_dirs': [
+ '<(SHARED_INTERMEDIATE_DIR)/ash_strings',
+ ],
+ },
+ },
+ ],
+}
diff --git a/chromium/ash/ash_switches.cc b/chromium/ash/ash_switches.cc
new file mode 100644
index 00000000000..c328b59ef35
--- /dev/null
+++ b/chromium/ash/ash_switches.cc
@@ -0,0 +1,215 @@
+// Copyright (c) 2012 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.
+
+#include "ash/ash_switches.h"
+
+#include "base/command_line.h"
+
+namespace ash {
+namespace switches {
+
+// Enables an animated transition from the boot splash screen (Chrome logo on a
+// white background) to the login screen. Implies
+// |kAshCopyHostBackgroundAtBoot| and doesn't make much sense if used in
+// conjunction with |kDisableBootAnimation| (since the transition begins at the
+// same time as the white/grayscale login screen animation).
+const char kAshAnimateFromBootSplashScreen[] =
+ "ash-animate-from-boot-splash-screen";
+
+// Constrains the pointer movement within a root window on desktop.
+const char kAshConstrainPointerToRoot[] = "ash-constrain-pointer-to-root";
+
+// Copies the host window's content to the system background layer at startup.
+// Can make boot slightly slower, but also hides an even-longer awkward period
+// where we display a white background if the login wallpaper takes a long time
+// to load.
+const char kAshCopyHostBackgroundAtBoot[] = "ash-copy-host-background-at-boot";
+
+// Enable keyboard shortcuts useful for debugging.
+const char kAshDebugShortcuts[] = "ash-debug-shortcuts";
+
+// UI to show preferred networks in the status area (for testing).
+const char kAshDebugShowPreferredNetworks[] =
+ "ash-debug-show-preferred-networks";
+
+// Default wallpaper to use in guest mode (as paths to trusted,
+// non-user-writable JPEG files).
+const char kAshDefaultGuestWallpaperLarge[] =
+ "ash-default-guest-wallpaper-large";
+const char kAshDefaultGuestWallpaperSmall[] =
+ "ash-default-guest-wallpaper-small";
+
+// Default wallpaper to use (as paths to trusted, non-user-writable JPEG files).
+const char kAshDefaultWallpaperLarge[] = "ash-default-wallpaper-large";
+const char kAshDefaultWallpaperSmall[] = "ash-default-wallpaper-small";
+
+#if defined(OS_CHROMEOS)
+// Disable the status tray volume menu for allowing the user to choose an audio
+// input and output device.
+const char kAshDisableAudioDeviceMenu[] =
+ "ash-disable-audio-device-menu";
+#endif
+
+// Disable auto window maximization logic.
+const char kAshDisableAutoMaximizing[] = "ash-disable-auto-maximizing";
+
+// Disable support for auto window placement.
+const char kAshDisableAutoWindowPlacement[] =
+ "ash-enable-auto-window-placement";
+
+// Disables the limitter to throttle how quickly a user
+// can change display settings.
+const char kAshDisableDisplayChangeLimiter[] =
+ "ash-disable-display-change-limiter";
+
+// If present new lock animations are enabled.
+const char kAshDisableNewLockAnimations[] = "ash-disable-new-lock-animations";
+
+// Disable the per application grouping version of the launcher.
+const char kAshDisablePerAppLauncher[] = "ash-disable-per-app-launcher";
+
+// Disables display rotation.
+const char kAshDisableDisplayRotation[] = "ash-disable-display-rotation";
+
+// Disable immersive fullscreen mode, regardless of default setting.
+const char kAshDisableImmersiveFullscreen[] =
+ "ash-disable-immersive-fullscreen";
+
+// Disables ui scaling.
+const char kAshDisableUIScaling[] = "ash-disable-ui-scaling";
+
+#if defined(OS_CHROMEOS)
+// Disable compositor based mirroring.
+const char kAshDisableSoftwareMirroring[] = "ash-disable-software-mirroring";
+
+// Disable the notification when a low-power USB charger is connected.
+const char kAshDisableUsbChargerNotification[] =
+ "ash-disable-usb-charger-notification";
+
+// TODO(jamescook): Remove this unused flag. It exists only to allow the
+// "Enable audio device menu" about:flags item to have the tri-state
+// default/enabled/disabled UI.
+const char kAshEnableAudioDeviceMenu[] = "ash-enable-audio-device-menu";
+#endif // defined(OS_CHROMEOS)
+
+// Enable advanced gestures (e.g. for window management).
+const char kAshEnableAdvancedGestures[] = "ash-enable-advanced-gestures";
+
+// Always enable brightness control. Used by machines that don't report their
+// main monitor as internal.
+const char kAshEnableBrightnessControl[] = "ash-enable-brightness-control";
+
+// Enable the dock area on a desktop.
+const char kAshEnableDockedWindows[] = "ash-enable-docked-windows";
+
+// Enable immersive fullscreen mode, regardless of default setting.
+const char kAshEnableImmersiveFullscreen[] = "ash-enable-immersive-fullscreen";
+
+#if defined(OS_LINUX)
+// Enable memory monitoring.
+const char kAshEnableMemoryMonitor[] = "ash-enable-memory-monitor";
+#endif
+
+// Enables the Oak tree viewer.
+const char kAshEnableOak[] = "ash-enable-oak";
+
+// Enables overview mode for window switching.
+const char kAshEnableOverviewMode[] = "ash-enable-overview-mode";
+
+// Enables "sticky" edges instead of "snap-to-edge"
+const char kAshEnableStickyEdges[] = "ash-enable-sticky-edges";
+
+// Enables showing the tray bubble by dragging on the shelf.
+const char kAshEnableTrayDragging[] = "ash-enable-tray-dragging";
+
+// Forces chrome to use mirror mode when an external display is connected.
+const char kAshForceMirrorMode[] = "ash-force-mirror-mode";
+
+// Hides notifications that are irrelevant to Chrome OS device factory testing,
+// such as battery level updates.
+const char kAshHideNotificationsForFactory[] =
+ "ash-hide-notifications-for-factory";
+
+// Sets a window size, optional position, and optional scale factor.
+// "1024x768" creates a window of size 1024x768.
+// "100+200-1024x768" positions the window at 100,200.
+// "1024x768*2" sets the scale factor to 2 for a high DPI display.
+const char kAshHostWindowBounds[] = "ash-host-window-bounds";
+
+// Hides the small tab indicators at the top of the screen during immersive
+// fullscreen mode.
+const char kAshImmersiveHideTabIndicators[] =
+ "ash-immersive-hide-tab-indicators";
+
+// Specifies the layout mode and offsets for the secondary display for
+// testing. The format is "<t|r|b|l>,<offset>" where t=TOP, r=RIGHT,
+// b=BOTTOM and L=LEFT. For example, 'r,-100' means the secondary display
+// is positioned on the right with -100 offset. (above than primary)
+const char kAshSecondaryDisplayLayout[] = "ash-secondary-display-layout";
+
+// Enables the heads-up display for tracking touch points.
+const char kAshTouchHud[] = "ash-touch-hud";
+
+// Use alternate layout of the shelf for testing a new look and feel:
+// Slightly smaller profile, only 2 states for the "bar highlight" on
+// launcher buttons, app list icon with more visible state indication,
+// app list icon repositionable and defaulting as 1st item in shelf,
+// more visible state indication for background on status area.
+// crbug's [244983, 244990, 244994, 245005, 245012]
+const char kAshUseAlternateShelfLayout[] = "ash-use-alternate-shelf";
+
+// Flags explicitly show or hide the shelf alignment menu.
+const char kShowShelfAlignmentMenu[] = "show-launcher-alignment-menu";
+const char kHideShelfAlignmentMenu[] = "hide-launcher-alignment-menu";
+
+// Uses the 1st display in --ash-host-window-bounds as internal display.
+// This is for debugging on linux desktop.
+const char kAshUseFirstDisplayAsInternal[] =
+ "ash-use-first-display-as-internal";
+
+// (Most) Chrome OS hardware reports ACPI power button releases correctly.
+// Standard hardware reports releases immediately after presses. If set, we
+// lock the screen or shutdown the system immediately in response to a press
+// instead of displaying an interactive animation.
+const char kAuraLegacyPowerButton[] = "aura-legacy-power-button";
+
+#if defined(OS_WIN)
+// Force Ash to open its root window on the desktop, even on Windows 8 where
+// it would normally end up in metro.
+const char kForceAshToDesktop[] = "ash-force-desktop";
+
+#endif
+
+// Disallow items to be dragged from the app launcher list into the launcher.
+const char kAshDisableDragAndDropAppListToLauncher[] =
+ "ash-disable-drag-and-drop-applist-to-launcher";
+
+// Enables a mode which enforces all browser & application windows to be created
+// in maximized mode.
+const char kForcedMaximizeMode[] = "forced-maximize-mode";
+
+bool UseAlternateShelfLayout() {
+ return CommandLine::ForCurrentProcess()->
+ HasSwitch(ash::switches::kAshUseAlternateShelfLayout);
+}
+
+bool ShowShelfAlignmentMenu() {
+ return CommandLine::ForCurrentProcess()->
+ HasSwitch(switches::kShowShelfAlignmentMenu);
+}
+
+#if defined(OS_CHROMEOS)
+bool ShowAudioDeviceMenu() {
+ return !CommandLine::ForCurrentProcess()->
+ HasSwitch(ash::switches::kAshDisableAudioDeviceMenu);
+}
+
+bool UseUsbChargerNotification() {
+ return !CommandLine::ForCurrentProcess()->
+ HasSwitch(ash::switches::kAshDisableUsbChargerNotification);
+}
+#endif
+
+} // namespace switches
+} // namespace ash
diff --git a/chromium/ash/ash_switches.h b/chromium/ash/ash_switches.h
new file mode 100644
index 00000000000..df615b87f76
--- /dev/null
+++ b/chromium/ash/ash_switches.h
@@ -0,0 +1,95 @@
+// Copyright (c) 2012 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 ASH_ASH_SWITCHES_H_
+#define ASH_ASH_SWITCHES_H_
+
+#include "ash/ash_export.h"
+
+#include "build/build_config.h"
+
+namespace ash {
+namespace switches {
+
+// Note: If you add a switch, consider if it needs to be copied to a subsequent
+// command line if the process executes a new copy of itself. (For example,
+// see chromeos::LoginUtil::GetOffTheRecordCommandLine().)
+
+// Please keep alphabetized.
+ASH_EXPORT extern const char kAshAnimateFromBootSplashScreen[];
+ASH_EXPORT extern const char kAshConstrainPointerToRoot[];
+ASH_EXPORT extern const char kAshCopyHostBackgroundAtBoot[];
+ASH_EXPORT extern const char kAshDebugShortcuts[];
+ASH_EXPORT extern const char kAshDebugShowPreferredNetworks[];
+ASH_EXPORT extern const char kAshDefaultGuestWallpaperLarge[];
+ASH_EXPORT extern const char kAshDefaultGuestWallpaperSmall[];
+ASH_EXPORT extern const char kAshDefaultWallpaperLarge[];
+ASH_EXPORT extern const char kAshDefaultWallpaperSmall[];
+#if defined(OS_CHROMEOS)
+ASH_EXPORT extern const char kAshDisableAudioDeviceMenu[];
+#endif
+ASH_EXPORT extern const char kAshDisableAutoMaximizing[];
+ASH_EXPORT extern const char kAshDisableAutoWindowPlacement[];
+ASH_EXPORT extern const char kAshDisableDisplayChangeLimiter[];
+ASH_EXPORT extern const char kAshDisableImmersiveFullscreen[];
+ASH_EXPORT extern const char kAshDisableNewLockAnimations[];
+ASH_EXPORT extern const char kAshDisablePerAppLauncher[];
+ASH_EXPORT extern const char kAshDisableUIScaling[];
+ASH_EXPORT extern const char kAshDisableDisplayRotation[];
+ASH_EXPORT extern const char kAshDisableDragAndDropAppListToLauncher[];
+#if defined(OS_CHROMEOS)
+ASH_EXPORT extern const char kAshDisableSoftwareMirroring[];
+ASH_EXPORT extern const char kAshDisableUsbChargerNotification[];
+ASH_EXPORT extern const char kAshEnableAudioDeviceMenu[];
+#endif
+ASH_EXPORT extern const char kAshEnableAdvancedGestures[];
+ASH_EXPORT extern const char kAshEnableBrightnessControl[];
+ASH_EXPORT extern const char kAshEnableDockedWindows[];
+#if defined(OS_LINUX)
+ASH_EXPORT extern const char kAshEnableMemoryMonitor[];
+#endif
+ASH_EXPORT extern const char kAshEnableImmersiveFullscreen[];
+ASH_EXPORT extern const char kAshEnableOak[];
+ASH_EXPORT extern const char kAshEnableOverviewMode[];
+ASH_EXPORT extern const char kAshEnableStickyEdges[];
+ASH_EXPORT extern const char kAshEnableTrayDragging[];
+ASH_EXPORT extern const char kAshForceMirrorMode[];
+ASH_EXPORT extern const char kAshHideNotificationsForFactory[];
+ASH_EXPORT extern const char kAshHostWindowBounds[];
+ASH_EXPORT extern const char kAshImmersiveHideTabIndicators[];
+ASH_EXPORT extern const char kAshSecondaryDisplayLayout[];
+ASH_EXPORT extern const char kAshTouchHud[];
+ASH_EXPORT extern const char kAshUseAlternateShelfLayout[];
+ASH_EXPORT extern const char kAshUseFirstDisplayAsInternal[];
+ASH_EXPORT extern const char kAuraLegacyPowerButton[];
+#if defined(OS_WIN)
+ASH_EXPORT extern const char kForceAshToDesktop[];
+#endif
+ASH_EXPORT extern const char kForcedMaximizeMode[];
+
+ASH_EXPORT extern const char kShowShelfAlignmentMenu[];
+ASH_EXPORT extern const char kHideShelfAlignmentMenu[];
+
+// Returns true if the alternate shelf layout should be used.
+ASH_EXPORT bool UseAlternateShelfLayout();
+
+// Returns true if side shelf alignment is enabled.
+ASH_EXPORT bool ShowShelfAlignmentMenu();
+
+#if defined(OS_CHROMEOS)
+// Returns true if new audio handler should be used.
+ASH_EXPORT bool UseNewAudioHandler();
+
+// Returns true if we should show the audio device switching UI.
+ASH_EXPORT bool ShowAudioDeviceMenu();
+
+// Returns true if a notification should appear when a low-power USB charger
+// is connected.
+ASH_EXPORT bool UseUsbChargerNotification();
+#endif
+
+} // namespace switches
+} // namespace ash
+
+#endif // ASH_ASH_SWITCHES_H_
diff --git a/chromium/ash/cancel_mode.cc b/chromium/ash/cancel_mode.cc
new file mode 100644
index 00000000000..92f054309b1
--- /dev/null
+++ b/chromium/ash/cancel_mode.cc
@@ -0,0 +1,22 @@
+// Copyright (c) 2013 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.
+
+#include "ash/cancel_mode.h"
+
+#include "ash/root_window_controller.h"
+#include "ash/shell.h"
+#include "ui/aura/root_window.h"
+
+namespace ash {
+
+void DispatchCancelMode() {
+ Shell::RootWindowControllerList controllers(
+ Shell::GetAllRootWindowControllers());
+ for (Shell::RootWindowControllerList::const_iterator i = controllers.begin();
+ i != controllers.end(); ++i) {
+ (*i)->root_window()->AsRootWindowHostDelegate()->OnHostCancelMode();
+ }
+}
+
+} // namespace ash
diff --git a/chromium/ash/cancel_mode.h b/chromium/ash/cancel_mode.h
new file mode 100644
index 00000000000..915fe43c490
--- /dev/null
+++ b/chromium/ash/cancel_mode.h
@@ -0,0 +1,15 @@
+// Copyright (c) 2013 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 ASH_CANCEL_MODE_H_
+#define ASH_CANCEL_MODE_H_
+
+namespace ash {
+
+// Sends OnHostCancelMode() to all RootWindows.
+void DispatchCancelMode();
+
+} // namespace ash
+
+#endif // ASH_CANCEL_MODE_H_
diff --git a/chromium/ash/caps_lock_delegate.h b/chromium/ash/caps_lock_delegate.h
new file mode 100644
index 00000000000..6bd2b69eb8c
--- /dev/null
+++ b/chromium/ash/caps_lock_delegate.h
@@ -0,0 +1,33 @@
+// Copyright (c) 2012 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 ASH_CAPS_LOCK_DELEGATE_H_
+#define ASH_CAPS_LOCK_DELEGATE_H_
+
+#include "ash/ash_export.h"
+
+namespace ash {
+
+// Delegate for controlling Caps Lock.
+class ASH_EXPORT CapsLockDelegate {
+ public:
+ virtual ~CapsLockDelegate() {}
+
+ // Returns true if caps lock is enabled.
+ virtual bool IsCapsLockEnabled() const = 0;
+
+ // Sets the caps lock state to |enabled|.
+ // The state change can occur asynchronously and calling IsCapsLockEnabled
+ // just after this may return the old state.
+ virtual void SetCapsLockEnabled(bool enabled) = 0;
+
+ // Toggles the caps lock state.
+ // The state change can occur asynchronously and calling IsCapsLockEnabled
+ // just after this may return the old state.
+ virtual void ToggleCapsLock() = 0;
+};
+
+} // namespace ash
+
+#endif // ASH_CAPS_LOCK_DELEGATE_H_
diff --git a/chromium/ash/caps_lock_delegate_stub.cc b/chromium/ash/caps_lock_delegate_stub.cc
new file mode 100644
index 00000000000..8dc61cffef0
--- /dev/null
+++ b/chromium/ash/caps_lock_delegate_stub.cc
@@ -0,0 +1,26 @@
+// Copyright (c) 2012 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.
+
+#include "ash/caps_lock_delegate_stub.h"
+
+namespace ash {
+
+CapsLockDelegateStub::CapsLockDelegateStub()
+ : enabled_(false) {}
+
+CapsLockDelegateStub::~CapsLockDelegateStub() {}
+
+bool CapsLockDelegateStub::IsCapsLockEnabled() const {
+ return enabled_;
+}
+
+void CapsLockDelegateStub::SetCapsLockEnabled(bool enabled) {
+ enabled_ = enabled;
+}
+
+void CapsLockDelegateStub::ToggleCapsLock() {
+ enabled_ = !enabled_;
+}
+
+} // namespace ash
diff --git a/chromium/ash/caps_lock_delegate_stub.h b/chromium/ash/caps_lock_delegate_stub.h
new file mode 100644
index 00000000000..d701e47bc48
--- /dev/null
+++ b/chromium/ash/caps_lock_delegate_stub.h
@@ -0,0 +1,34 @@
+// Copyright (c) 2012 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 ASH_CAPS_LOCK_DELEGATE_STUB_H_
+#define ASH_CAPS_LOCK_DELEGATE_STUB_H_
+
+#include "ash/ash_export.h"
+#include "ash/caps_lock_delegate.h"
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+
+namespace ash {
+
+// Stub implementation of CapsLockDelegate mainly for testing.
+class ASH_EXPORT CapsLockDelegateStub : public CapsLockDelegate {
+ public:
+ CapsLockDelegateStub();
+ virtual ~CapsLockDelegateStub();
+
+ // Overridden from CapsLockDelegate:
+ virtual bool IsCapsLockEnabled() const OVERRIDE;
+ virtual void SetCapsLockEnabled(bool enabled) OVERRIDE;
+ virtual void ToggleCapsLock() OVERRIDE;
+
+ private:
+ bool enabled_;
+
+ DISALLOW_COPY_AND_ASSIGN(CapsLockDelegateStub);
+};
+
+} // namespace ash
+
+#endif // ASH_CAPS_LOCK_DELEGATE_STUB
diff --git a/chromium/ash/debug.cc b/chromium/ash/debug.cc
new file mode 100644
index 00000000000..060627cadc0
--- /dev/null
+++ b/chromium/ash/debug.cc
@@ -0,0 +1,61 @@
+// Copyright (c) 2013 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.
+
+#include "ash/debug.h"
+
+#include "ash/shell.h"
+#include "cc/debug/layer_tree_debug_state.h"
+#include "ui/aura/root_window.h"
+#include "ui/compositor/compositor.h"
+
+namespace ash {
+namespace debug {
+
+void ToggleShowDebugBorders() {
+ Shell::RootWindowList root_windows =
+ Shell::GetInstance()->GetAllRootWindows();
+ scoped_ptr<bool> value;
+ for (Shell::RootWindowList::iterator it = root_windows.begin();
+ it != root_windows.end(); ++it) {
+ ui::Compositor* compositor = (*it)->compositor();
+ cc::LayerTreeDebugState state = compositor->GetLayerTreeDebugState();
+ if (!value.get())
+ value.reset(new bool(!state.show_debug_borders));
+ state.show_debug_borders = *value.get();
+ compositor->SetLayerTreeDebugState(state);
+ }
+}
+
+void ToggleShowFpsCounter() {
+ Shell::RootWindowList root_windows =
+ Shell::GetInstance()->GetAllRootWindows();
+ scoped_ptr<bool> value;
+ for (Shell::RootWindowList::iterator it = root_windows.begin();
+ it != root_windows.end(); ++it) {
+ ui::Compositor* compositor = (*it)->compositor();
+ cc::LayerTreeDebugState state = compositor->GetLayerTreeDebugState();
+ if (!value.get())
+ value.reset(new bool(!state.show_fps_counter));
+ state.show_fps_counter = *value.get();
+ compositor->SetLayerTreeDebugState(state);
+ }
+}
+
+void ToggleShowPaintRects() {
+ Shell::RootWindowList root_windows =
+ Shell::GetInstance()->GetAllRootWindows();
+ scoped_ptr<bool> value;
+ for (Shell::RootWindowList::iterator it = root_windows.begin();
+ it != root_windows.end(); ++it) {
+ ui::Compositor* compositor = (*it)->compositor();
+ cc::LayerTreeDebugState state = compositor->GetLayerTreeDebugState();
+ if (!value.get())
+ value.reset(new bool(!state.show_paint_rects));
+ state.show_paint_rects = *value.get();
+ compositor->SetLayerTreeDebugState(state);
+ }
+}
+
+} // debug
+} // ash
diff --git a/chromium/ash/debug.h b/chromium/ash/debug.h
new file mode 100644
index 00000000000..92838d61147
--- /dev/null
+++ b/chromium/ash/debug.h
@@ -0,0 +1,22 @@
+// Copyright (c) 2013 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 ASH_DEBUG_H_
+#define ASH_DEBUG_H_
+
+#include "ash/ash_export.h"
+
+namespace ash {
+namespace debug {
+
+// Toggles debugging features controlled by
+// cc::LayerTreeDebugState.
+ASH_EXPORT void ToggleShowDebugBorders();
+ASH_EXPORT void ToggleShowFpsCounter();
+ASH_EXPORT void ToggleShowPaintRects();
+
+} // debug
+} // ash
+
+#endif // ASH_DEBUG_H_
diff --git a/chromium/ash/desktop_background/OWNERS b/chromium/ash/desktop_background/OWNERS
new file mode 100644
index 00000000000..679223ebc47
--- /dev/null
+++ b/chromium/ash/desktop_background/OWNERS
@@ -0,0 +1,2 @@
+bshe@chromium.org
+nkostylev@chromium.org
diff --git a/chromium/ash/desktop_background/desktop_background_controller.cc b/chromium/ash/desktop_background/desktop_background_controller.cc
new file mode 100644
index 00000000000..267d962fa50
--- /dev/null
+++ b/chromium/ash/desktop_background/desktop_background_controller.cc
@@ -0,0 +1,474 @@
+// Copyright (c) 2012 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.
+
+#include "ash/desktop_background/desktop_background_controller.h"
+
+#include "ash/ash_switches.h"
+#include "ash/desktop_background/desktop_background_controller_observer.h"
+#include "ash/desktop_background/desktop_background_view.h"
+#include "ash/desktop_background/desktop_background_widget_controller.h"
+#include "ash/desktop_background/user_wallpaper_delegate.h"
+#include "ash/desktop_background/wallpaper_resizer.h"
+#include "ash/root_window_controller.h"
+#include "ash/shell.h"
+#include "ash/shell_factory.h"
+#include "ash/shell_window_ids.h"
+#include "ash/wm/property_util.h"
+#include "ash/wm/root_window_layout_manager.h"
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "base/file_util.h"
+#include "base/logging.h"
+#include "base/synchronization/cancellation_flag.h"
+#include "base/threading/worker_pool.h"
+#include "content/public/browser/browser_thread.h"
+#include "grit/ash_resources.h"
+#include "ui/aura/root_window.h"
+#include "ui/aura/window.h"
+#include "ui/compositor/layer.h"
+#include "ui/gfx/codec/jpeg_codec.h"
+#include "ui/gfx/image/image_skia.h"
+#include "ui/gfx/rect.h"
+#include "ui/views/widget/widget.h"
+
+using ash::internal::DesktopBackgroundWidgetController;
+using content::BrowserThread;
+
+namespace ash {
+namespace {
+
+const SkColor kTransparentColor = SkColorSetARGB(0x00, 0x00, 0x00, 0x00);
+
+internal::RootWindowLayoutManager* GetRootWindowLayoutManager(
+ aura::RootWindow* root_window) {
+ return static_cast<internal::RootWindowLayoutManager*>(
+ root_window->layout_manager());
+}
+
+// Returns the maximum width and height of all root windows.
+gfx::Size GetRootWindowsSize() {
+ int width = 0;
+ int height = 0;
+ Shell::RootWindowList root_windows = Shell::GetAllRootWindows();
+ for (Shell::RootWindowList::iterator iter = root_windows.begin();
+ iter != root_windows.end(); ++iter) {
+ gfx::Size root_window_size = (*iter)->GetHostSize();
+ if (root_window_size.width() > width)
+ width = root_window_size.width();
+ if (root_window_size.height() > height)
+ height = root_window_size.height();
+ }
+ return gfx::Size(width, height);
+}
+
+} // namespace
+
+const int kSmallWallpaperMaxWidth = 1366;
+const int kSmallWallpaperMaxHeight = 800;
+const int kLargeWallpaperMaxWidth = 2560;
+const int kLargeWallpaperMaxHeight = 1700;
+const int kWallpaperThumbnailWidth = 108;
+const int kWallpaperThumbnailHeight = 68;
+
+// DesktopBackgroundController::WallpaperLoader wraps background wallpaper
+// loading.
+class DesktopBackgroundController::WallpaperLoader
+ : public base::RefCountedThreadSafe<
+ DesktopBackgroundController::WallpaperLoader> {
+ public:
+ // If set, |file_path| must be a trusted (i.e. read-only,
+ // non-user-controlled) file containing a JPEG image.
+ WallpaperLoader(const base::FilePath& file_path,
+ WallpaperLayout file_layout,
+ int resource_id,
+ WallpaperLayout resource_layout)
+ : file_path_(file_path),
+ file_layout_(file_layout),
+ resource_id_(resource_id),
+ resource_layout_(resource_layout) {
+ }
+
+ static void LoadOnWorkerPoolThread(scoped_refptr<WallpaperLoader> loader) {
+ DCHECK(!BrowserThread::CurrentlyOn(BrowserThread::UI));
+ loader->LoadWallpaper();
+ }
+
+ const base::FilePath& file_path() const { return file_path_; }
+ int resource_id() const { return resource_id_; }
+
+ void Cancel() {
+ cancel_flag_.Set();
+ }
+
+ WallpaperResizer* ReleaseWallpaperResizer() {
+ return wallpaper_resizer_.release();
+ }
+
+ private:
+ friend class base::RefCountedThreadSafe<
+ DesktopBackgroundController::WallpaperLoader>;
+
+ // Loads a JPEG image from |path|, a trusted file -- note that the image
+ // is not loaded in a sandboxed process. Returns an empty pointer on
+ // error.
+ static scoped_ptr<SkBitmap> LoadSkBitmapFromJPEGFile(
+ const base::FilePath& path) {
+ std::string data;
+ if (!file_util::ReadFileToString(path, &data)) {
+ LOG(ERROR) << "Unable to read data from " << path.value();
+ return scoped_ptr<SkBitmap>();
+ }
+
+ scoped_ptr<SkBitmap> bitmap(gfx::JPEGCodec::Decode(
+ reinterpret_cast<const unsigned char*>(data.data()), data.size()));
+ if (!bitmap)
+ LOG(ERROR) << "Unable to decode JPEG data from " << path.value();
+ return bitmap.Pass();
+ }
+
+ void LoadWallpaper() {
+ if (cancel_flag_.IsSet())
+ return;
+
+ if (!file_path_.empty())
+ file_bitmap_ = LoadSkBitmapFromJPEGFile(file_path_);
+
+ if (cancel_flag_.IsSet())
+ return;
+
+ if (file_bitmap_) {
+ gfx::ImageSkia image = gfx::ImageSkia::CreateFrom1xBitmap(*file_bitmap_);
+ wallpaper_resizer_.reset(new WallpaperResizer(
+ image, GetRootWindowsSize(), file_layout_));
+ } else {
+ wallpaper_resizer_.reset(new WallpaperResizer(
+ resource_id_, GetRootWindowsSize(), resource_layout_));
+ }
+ }
+
+ ~WallpaperLoader() {}
+
+ base::CancellationFlag cancel_flag_;
+
+ // Bitmap loaded from |file_path_|.
+ scoped_ptr<SkBitmap> file_bitmap_;
+
+ scoped_ptr<WallpaperResizer> wallpaper_resizer_;
+
+ // Path to a trusted JPEG file.
+ base::FilePath file_path_;
+
+ // Layout to be used when displaying the image from |file_path_|.
+ WallpaperLayout file_layout_;
+
+ // ID of an image resource to use if |file_path_| is empty or unloadable.
+ int resource_id_;
+
+ // Layout to be used when displaying |resource_id_|.
+ WallpaperLayout resource_layout_;
+
+ DISALLOW_COPY_AND_ASSIGN(WallpaperLoader);
+};
+
+DesktopBackgroundController::DesktopBackgroundController()
+ : command_line_for_testing_(NULL),
+ locked_(false),
+ desktop_background_mode_(BACKGROUND_NONE),
+ background_color_(kTransparentColor),
+ current_default_wallpaper_resource_id_(-1),
+ weak_ptr_factory_(this) {
+}
+
+DesktopBackgroundController::~DesktopBackgroundController() {
+ CancelPendingWallpaperOperation();
+}
+
+gfx::ImageSkia DesktopBackgroundController::GetWallpaper() const {
+ if (current_wallpaper_)
+ return current_wallpaper_->wallpaper_image();
+ return gfx::ImageSkia();
+}
+
+void DesktopBackgroundController::AddObserver(
+ DesktopBackgroundControllerObserver* observer) {
+ observers_.AddObserver(observer);
+}
+
+void DesktopBackgroundController::RemoveObserver(
+ DesktopBackgroundControllerObserver* observer) {
+ observers_.RemoveObserver(observer);
+}
+
+WallpaperLayout DesktopBackgroundController::GetWallpaperLayout() const {
+ if (current_wallpaper_)
+ return current_wallpaper_->layout();
+ return WALLPAPER_LAYOUT_CENTER_CROPPED;
+}
+
+void DesktopBackgroundController::OnRootWindowAdded(
+ aura::RootWindow* root_window) {
+ // The background hasn't been set yet.
+ if (desktop_background_mode_ == BACKGROUND_NONE)
+ return;
+
+ // Handle resolution change for "built-in" images.
+ if (BACKGROUND_IMAGE == desktop_background_mode_ &&
+ current_wallpaper_.get()) {
+ gfx::Size root_window_size = root_window->GetHostSize();
+ int width = current_wallpaper_->wallpaper_image().width();
+ int height = current_wallpaper_->wallpaper_image().height();
+ // Reloads wallpaper if current wallpaper is smaller than the new added root
+ // window.
+ if (width < root_window_size.width() ||
+ height < root_window_size.height()) {
+ current_wallpaper_.reset(NULL);
+ current_default_wallpaper_path_ = base::FilePath();
+ current_default_wallpaper_resource_id_ = -1;
+ ash::Shell::GetInstance()->user_wallpaper_delegate()->
+ UpdateWallpaper();
+ }
+ }
+
+ InstallDesktopController(root_window);
+}
+
+bool DesktopBackgroundController::SetDefaultWallpaper(bool is_guest) {
+ const bool use_large =
+ GetAppropriateResolution() == WALLPAPER_RESOLUTION_LARGE;
+
+ base::FilePath file_path;
+ WallpaperLayout file_layout = use_large ? WALLPAPER_LAYOUT_CENTER_CROPPED :
+ WALLPAPER_LAYOUT_CENTER;
+ int resource_id = use_large ? IDR_AURA_WALLPAPER_DEFAULT_LARGE :
+ IDR_AURA_WALLPAPER_DEFAULT_SMALL;
+ WallpaperLayout resource_layout = WALLPAPER_LAYOUT_TILE;
+
+ const char* switch_name = is_guest ?
+ (use_large ? switches::kAshDefaultGuestWallpaperLarge :
+ switches::kAshDefaultGuestWallpaperSmall) :
+ (use_large ? switches::kAshDefaultWallpaperLarge :
+ switches::kAshDefaultWallpaperSmall);
+ CommandLine* command_line = command_line_for_testing_ ?
+ command_line_for_testing_ : CommandLine::ForCurrentProcess();
+ file_path = command_line->GetSwitchValuePath(switch_name);
+
+ if (DefaultWallpaperIsAlreadyLoadingOrLoaded(file_path, resource_id))
+ return false;
+
+ CancelPendingWallpaperOperation();
+ wallpaper_loader_ = new WallpaperLoader(
+ file_path, file_layout, resource_id, resource_layout);
+ base::WorkerPool::PostTaskAndReply(
+ FROM_HERE,
+ base::Bind(&WallpaperLoader::LoadOnWorkerPoolThread, wallpaper_loader_),
+ base::Bind(&DesktopBackgroundController::OnDefaultWallpaperLoadCompleted,
+ weak_ptr_factory_.GetWeakPtr(),
+ wallpaper_loader_),
+ true /* task_is_slow */);
+ return true;
+}
+
+void DesktopBackgroundController::SetCustomWallpaper(
+ const gfx::ImageSkia& image,
+ WallpaperLayout layout) {
+ CancelPendingWallpaperOperation();
+ if (CustomWallpaperIsAlreadyLoaded(image))
+ return;
+
+ current_wallpaper_.reset(new WallpaperResizer(
+ image, GetRootWindowsSize(), layout));
+ current_wallpaper_->StartResize();
+
+ current_default_wallpaper_path_ = base::FilePath();
+ current_default_wallpaper_resource_id_ = -1;
+
+ FOR_EACH_OBSERVER(DesktopBackgroundControllerObserver, observers_,
+ OnWallpaperDataChanged());
+ SetDesktopBackgroundImageMode();
+}
+
+void DesktopBackgroundController::CancelPendingWallpaperOperation() {
+ // Set canceled flag of previous request to skip unneeded loading.
+ if (wallpaper_loader_.get())
+ wallpaper_loader_->Cancel();
+
+ // Cancel reply callback for previous request.
+ weak_ptr_factory_.InvalidateWeakPtrs();
+}
+
+void DesktopBackgroundController::SetDesktopBackgroundSolidColorMode(
+ SkColor color) {
+ background_color_ = color;
+ desktop_background_mode_ = BACKGROUND_SOLID_COLOR;
+
+ InstallDesktopControllerForAllWindows();
+}
+
+void DesktopBackgroundController::CreateEmptyWallpaper() {
+ current_wallpaper_.reset(NULL);
+ SetDesktopBackgroundImageMode();
+}
+
+WallpaperResolution DesktopBackgroundController::GetAppropriateResolution() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ Shell::RootWindowList root_windows = Shell::GetAllRootWindows();
+ for (Shell::RootWindowList::iterator iter = root_windows.begin();
+ iter != root_windows.end(); ++iter) {
+ // Compare to host size as constants are defined in terms of
+ // physical pixel size.
+ // TODO(oshima): This may not be ideal for fractional scaling
+ // scenario. Revisit and fix if necessary.
+ gfx::Size host_window_size = (*iter)->GetHostSize();
+ if (host_window_size.width() > kSmallWallpaperMaxWidth ||
+ host_window_size.height() > kSmallWallpaperMaxHeight)
+ return WALLPAPER_RESOLUTION_LARGE;
+ }
+ return WALLPAPER_RESOLUTION_SMALL;
+}
+
+bool DesktopBackgroundController::MoveDesktopToLockedContainer() {
+ if (locked_)
+ return false;
+ locked_ = true;
+ return ReparentBackgroundWidgets(GetBackgroundContainerId(false),
+ GetBackgroundContainerId(true));
+}
+
+bool DesktopBackgroundController::MoveDesktopToUnlockedContainer() {
+ if (!locked_)
+ return false;
+ locked_ = false;
+ return ReparentBackgroundWidgets(GetBackgroundContainerId(true),
+ GetBackgroundContainerId(false));
+}
+
+bool DesktopBackgroundController::DefaultWallpaperIsAlreadyLoadingOrLoaded(
+ const base::FilePath& image_file, int image_resource_id) const {
+ return (wallpaper_loader_.get() &&
+ wallpaper_loader_->file_path() == image_file &&
+ wallpaper_loader_->resource_id() == image_resource_id) ||
+ (current_wallpaper_.get() &&
+ current_default_wallpaper_path_ == image_file &&
+ current_default_wallpaper_resource_id_ == image_resource_id);
+}
+
+bool DesktopBackgroundController::CustomWallpaperIsAlreadyLoaded(
+ const gfx::ImageSkia& image) const {
+ return current_wallpaper_.get() &&
+ current_wallpaper_->wallpaper_image().BackedBySameObjectAs(image);
+}
+
+void DesktopBackgroundController::SetDesktopBackgroundImageMode() {
+ desktop_background_mode_ = BACKGROUND_IMAGE;
+ InstallDesktopControllerForAllWindows();
+}
+
+void DesktopBackgroundController::OnDefaultWallpaperLoadCompleted(
+ scoped_refptr<WallpaperLoader> loader) {
+ current_wallpaper_.reset(loader->ReleaseWallpaperResizer());
+ current_wallpaper_->StartResize();
+ current_default_wallpaper_path_ = loader->file_path();
+ current_default_wallpaper_resource_id_ = loader->resource_id();
+ FOR_EACH_OBSERVER(DesktopBackgroundControllerObserver, observers_,
+ OnWallpaperDataChanged());
+
+ SetDesktopBackgroundImageMode();
+
+ DCHECK(loader.get() == wallpaper_loader_.get());
+ wallpaper_loader_ = NULL;
+}
+
+ui::Layer* DesktopBackgroundController::SetColorLayerForContainer(
+ SkColor color,
+ aura::RootWindow* root_window,
+ int container_id) {
+ ui::Layer* background_layer = new ui::Layer(ui::LAYER_SOLID_COLOR);
+ background_layer->SetColor(color);
+
+ Shell::GetContainer(root_window,container_id)->
+ layer()->Add(background_layer);
+ return background_layer;
+}
+
+void DesktopBackgroundController::InstallDesktopController(
+ aura::RootWindow* root_window) {
+ internal::DesktopBackgroundWidgetController* component = NULL;
+ int container_id = GetBackgroundContainerId(locked_);
+
+ switch (desktop_background_mode_) {
+ case BACKGROUND_IMAGE: {
+ views::Widget* widget = internal::CreateDesktopBackground(root_window,
+ container_id);
+ component = new internal::DesktopBackgroundWidgetController(widget);
+ break;
+ }
+ case BACKGROUND_SOLID_COLOR: {
+ ui::Layer* layer = SetColorLayerForContainer(background_color_,
+ root_window,
+ container_id);
+ component = new internal::DesktopBackgroundWidgetController(layer);
+ break;
+ }
+ case BACKGROUND_NONE:
+ NOTREACHED();
+ return;
+ }
+ GetRootWindowController(root_window)->SetAnimatingWallpaperController(
+ new internal::AnimatingDesktopController(component));
+
+ component->StartAnimating(GetRootWindowController(root_window));
+}
+
+void DesktopBackgroundController::InstallDesktopControllerForAllWindows() {
+ Shell::RootWindowList root_windows = Shell::GetAllRootWindows();
+ for (Shell::RootWindowList::iterator iter = root_windows.begin();
+ iter != root_windows.end(); ++iter) {
+ InstallDesktopController(*iter);
+ }
+}
+
+bool DesktopBackgroundController::ReparentBackgroundWidgets(int src_container,
+ int dst_container) {
+ bool moved = false;
+ Shell::RootWindowControllerList controllers =
+ Shell::GetAllRootWindowControllers();
+ for (Shell::RootWindowControllerList::iterator iter = controllers.begin();
+ iter != controllers.end(); ++iter) {
+ internal::RootWindowController* root_window_controller = *iter;
+ // In the steady state (no animation playing) the background widget
+ // controller exists in the RootWindowController.
+ DesktopBackgroundWidgetController* desktop_controller =
+ root_window_controller->wallpaper_controller();
+ if (desktop_controller) {
+ moved |= desktop_controller->Reparent(
+ root_window_controller->root_window(),
+ src_container,
+ dst_container);
+ }
+ // During desktop show animations the controller lives in
+ // AnimatingDesktopController owned by RootWindowController.
+ // NOTE: If a wallpaper load happens during a desktop show animation there
+ // can temporarily be two desktop background widgets. We must reparent
+ // both of them - one above and one here.
+ DesktopBackgroundWidgetController* animating_controller =
+ root_window_controller->animating_wallpaper_controller() ?
+ root_window_controller->animating_wallpaper_controller()->
+ GetController(false) :
+ NULL;
+ if (animating_controller) {
+ moved |= animating_controller->Reparent(
+ root_window_controller->root_window(),
+ src_container,
+ dst_container);
+ }
+ }
+ return moved;
+}
+
+int DesktopBackgroundController::GetBackgroundContainerId(bool locked) {
+ return locked ? internal::kShellWindowId_LockScreenBackgroundContainer :
+ internal::kShellWindowId_DesktopBackgroundContainer;
+}
+
+} // namespace ash
diff --git a/chromium/ash/desktop_background/desktop_background_controller.h b/chromium/ash/desktop_background/desktop_background_controller.h
new file mode 100644
index 00000000000..afdc2dc52fc
--- /dev/null
+++ b/chromium/ash/desktop_background/desktop_background_controller.h
@@ -0,0 +1,214 @@
+// Copyright (c) 2012 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 ASH_DESKTOP_BACKGROUND_DESKTOP_BACKGROUND_CONTROLLER_H_
+#define ASH_DESKTOP_BACKGROUND_DESKTOP_BACKGROUND_CONTROLLER_H_
+
+#include "ash/ash_export.h"
+#include "base/basictypes.h"
+#include "base/files/file_path.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/observer_list.h"
+#include "ui/aura/window.h"
+#include "ui/compositor/layer.h"
+#include "ui/gfx/image/image_skia.h"
+
+typedef unsigned int SkColor;
+
+class CommandLine;
+
+namespace aura {
+class RootWindow;
+}
+
+namespace ash {
+namespace internal {
+class DesktopBackgroundControllerTest;
+} // namespace internal
+
+enum WallpaperLayout {
+ // Center the wallpaper on the desktop without scaling it. The wallpaper
+ // may be cropped.
+ WALLPAPER_LAYOUT_CENTER,
+ // Scale the wallpaper (while preserving its aspect ratio) to cover the
+ // desktop; the wallpaper may be cropped.
+ WALLPAPER_LAYOUT_CENTER_CROPPED,
+ // Scale the wallpaper (without preserving its aspect ratio) to match the
+ // desktop's size.
+ WALLPAPER_LAYOUT_STRETCH,
+ // Tile the wallpaper over the background without scaling it.
+ WALLPAPER_LAYOUT_TILE,
+};
+
+enum WallpaperResolution {
+ WALLPAPER_RESOLUTION_LARGE,
+ WALLPAPER_RESOLUTION_SMALL
+};
+
+const SkColor kLoginWallpaperColor = 0xFEFEFE;
+
+// The width and height of small/large resolution wallpaper. When screen size is
+// smaller than |kSmallWallpaperMaxWidth| and |kSmallWallpaperMaxHeight|, the
+// small resolution wallpaper should be used. Otherwise, uses the large
+// resolution wallpaper.
+ASH_EXPORT extern const int kSmallWallpaperMaxWidth;
+ASH_EXPORT extern const int kSmallWallpaperMaxHeight;
+ASH_EXPORT extern const int kLargeWallpaperMaxWidth;
+ASH_EXPORT extern const int kLargeWallpaperMaxHeight;
+
+// The width and heigh of wallpaper thumbnails.
+ASH_EXPORT extern const int kWallpaperThumbnailWidth;
+ASH_EXPORT extern const int kWallpaperThumbnailHeight;
+
+class DesktopBackgroundControllerObserver;
+class WallpaperResizer;
+
+// Loads selected desktop wallpaper from file system asynchronously and updates
+// background layer if loaded successfully.
+class ASH_EXPORT DesktopBackgroundController {
+ public:
+ enum BackgroundMode {
+ BACKGROUND_NONE,
+ BACKGROUND_IMAGE,
+ BACKGROUND_SOLID_COLOR
+ };
+
+ DesktopBackgroundController();
+ virtual ~DesktopBackgroundController();
+
+ BackgroundMode desktop_background_mode() const {
+ return desktop_background_mode_;
+ }
+
+ void set_command_line_for_testing(CommandLine* command_line) {
+ command_line_for_testing_ = command_line;
+ }
+
+ // Add/Remove observers.
+ void AddObserver(DesktopBackgroundControllerObserver* observer);
+ void RemoveObserver(DesktopBackgroundControllerObserver* observer);
+
+ // Provides current image on the background, or empty gfx::ImageSkia if there
+ // is no image, e.g. background is solid color.
+ gfx::ImageSkia GetWallpaper() const;
+
+ WallpaperLayout GetWallpaperLayout() const;
+
+ // Initialize root window's background.
+ void OnRootWindowAdded(aura::RootWindow* root_window);
+
+ // Loads builtin wallpaper asynchronously and sets to current wallpaper
+ // after loaded. Returns true if the controller started loading the
+ // wallpaper and false otherwise (i.e. the appropriate wallpaper was
+ // already loading or loaded).
+ bool SetDefaultWallpaper(bool is_guest);
+
+ // Sets the user selected custom wallpaper. Called when user selected a file
+ // from file system or changed the layout of wallpaper.
+ void SetCustomWallpaper(const gfx::ImageSkia& image, WallpaperLayout layout);
+
+ // Cancels the current wallpaper loading operation.
+ void CancelPendingWallpaperOperation();
+
+ // Sets the desktop background to solid color mode and creates a solid
+ // |color| layout.
+ void SetDesktopBackgroundSolidColorMode(SkColor color);
+
+ // Creates an empty wallpaper. Some tests require a wallpaper widget is ready
+ // when running. However, the wallpaper widgets are now created asynchronously
+ // . If loading a real wallpaper, there are cases that these tests crash
+ // because the required widget is not ready. This function synchronously
+ // creates an empty widget for those tests to prevent crashes. An example test
+ // is SystemGestureEventFilterTest.ThreeFingerSwipe.
+ void CreateEmptyWallpaper();
+
+ // Returns the appropriate wallpaper resolution for all root windows.
+ WallpaperResolution GetAppropriateResolution();
+
+ // Move all desktop widgets to locked container.
+ // Returns true if the desktop moved.
+ bool MoveDesktopToLockedContainer();
+
+ // Move all desktop widgets to unlocked container.
+ // Returns true if the desktop moved.
+ bool MoveDesktopToUnlockedContainer();
+
+ private:
+ friend class internal::DesktopBackgroundControllerTest;
+
+ // An operation to asynchronously loads wallpaper.
+ class WallpaperLoader;
+
+ // Returns true if the specified default wallpaper is already being
+ // loaded by |wallpaper_loader_| or stored in |current_wallpaper_|.
+ bool DefaultWallpaperIsAlreadyLoadingOrLoaded(
+ const base::FilePath& image_file, int image_resource_id) const;
+
+ // Returns true if the specified custom wallpaper is already stored
+ // in |current_wallpaper_|.
+ bool CustomWallpaperIsAlreadyLoaded(const gfx::ImageSkia& image) const;
+
+ // Creates view for all root windows, or notifies them to repaint if they
+ // already exist.
+ void SetDesktopBackgroundImageMode();
+
+ // Creates a new background widget and sets the background mode to image mode.
+ // Called after a default wallpaper has been loaded successfully.
+ void OnDefaultWallpaperLoadCompleted(scoped_refptr<WallpaperLoader> loader);
+
+ // Adds layer with solid |color| to container |container_id| in |root_window|.
+ ui::Layer* SetColorLayerForContainer(SkColor color,
+ aura::RootWindow* root_window,
+ int container_id);
+
+ // Creates and adds component for current mode (either Widget or Layer) to
+ // |root_window|.
+ void InstallDesktopController(aura::RootWindow* root_window);
+
+ // Creates and adds component for current mode (either Widget or Layer) to
+ // all root windows.
+ void InstallDesktopControllerForAllWindows();
+
+ // Moves all desktop components from one container to other across all root
+ // windows. Returns true if a desktop moved.
+ bool ReparentBackgroundWidgets(int src_container, int dst_container);
+
+ // Returns id for background container for unlocked and locked states.
+ int GetBackgroundContainerId(bool locked);
+
+ // Send notification that background animation finished.
+ void NotifyAnimationFinished();
+
+ // If non-NULL, used in place of the real command line.
+ CommandLine* command_line_for_testing_;
+
+ // Can change at runtime.
+ bool locked_;
+
+ BackgroundMode desktop_background_mode_;
+
+ SkColor background_color_;
+
+ ObserverList<DesktopBackgroundControllerObserver> observers_;
+
+ // The current wallpaper.
+ scoped_ptr<WallpaperResizer> current_wallpaper_;
+
+ // If a default wallpaper is stored in |current_wallpaper_|, the path and
+ // resource ID that were passed to WallpaperLoader when loading it.
+ // Otherwise, empty and -1, respectively.
+ base::FilePath current_default_wallpaper_path_;
+ int current_default_wallpaper_resource_id_;
+
+ scoped_refptr<WallpaperLoader> wallpaper_loader_;
+
+ base::WeakPtrFactory<DesktopBackgroundController> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(DesktopBackgroundController);
+};
+
+} // namespace ash
+
+#endif // ASH_DESKTOP_BACKGROUND_DESKTOP_BACKGROUND_CONTROLLER_H_
diff --git a/chromium/ash/desktop_background/desktop_background_controller_observer.h b/chromium/ash/desktop_background/desktop_background_controller_observer.h
new file mode 100644
index 00000000000..9f74306839b
--- /dev/null
+++ b/chromium/ash/desktop_background/desktop_background_controller_observer.h
@@ -0,0 +1,23 @@
+// Copyright (c) 2012 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 ASH_DESKTOP_BACKGROUND_DESKTOP_BACKGROUND_CONTROLLER_OBSERVER_H_
+#define ASH_DESKTOP_BACKGROUND_DESKTOP_BACKGROUND_CONTROLLER_OBSERVER_H_
+
+#include "ash/ash_export.h"
+
+namespace ash {
+
+class ASH_EXPORT DesktopBackgroundControllerObserver {
+ public:
+ // Invoked when the wallpaper data is changed.
+ virtual void OnWallpaperDataChanged() = 0;
+
+ protected:
+ virtual ~DesktopBackgroundControllerObserver() {}
+};
+
+} // namespace ash
+
+#endif // ASH_DESKTOP_BACKGROUND_DESKTOP_BACKGROUND_CONTROLLER_OBSERVER_H_
diff --git a/chromium/ash/desktop_background/desktop_background_controller_unittest.cc b/chromium/ash/desktop_background/desktop_background_controller_unittest.cc
new file mode 100644
index 00000000000..b46b6e336ef
--- /dev/null
+++ b/chromium/ash/desktop_background/desktop_background_controller_unittest.cc
@@ -0,0 +1,528 @@
+// Copyright (c) 2012 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.
+
+#include "ash/desktop_background/desktop_background_controller.h"
+
+#include <cmath>
+#include <cstdlib>
+
+#include "ash/ash_switches.h"
+#include "ash/desktop_background/desktop_background_controller_observer.h"
+#include "ash/desktop_background/desktop_background_widget_controller.h"
+#include "ash/root_window_controller.h"
+#include "ash/shell.h"
+#include "ash/shell_window_ids.h"
+#include "ash/test/ash_test_base.h"
+#include "ash/test/display_manager_test_api.h"
+#include "base/command_line.h"
+#include "base/file_util.h"
+#include "base/files/file_path.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/message_loop/message_loop.h"
+#include "content/public/test/test_browser_thread.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+#include "third_party/skia/include/core/SkColor.h"
+#include "ui/aura/root_window.h"
+#include "ui/compositor/scoped_animation_duration_scale_mode.h"
+#include "ui/compositor/test/layer_animator_test_controller.h"
+#include "ui/gfx/codec/jpeg_codec.h"
+#include "ui/gfx/point.h"
+#include "ui/gfx/rect.h"
+
+using aura::RootWindow;
+using aura::Window;
+
+namespace ash {
+namespace internal {
+
+namespace {
+
+// Containers IDs used for tests.
+const int kDesktopBackgroundId =
+ ash::internal::kShellWindowId_DesktopBackgroundContainer;
+const int kLockScreenBackgroundId =
+ ash::internal::kShellWindowId_LockScreenBackgroundContainer;
+
+// Returns number of child windows in a shell window container.
+int ChildCountForContainer(int container_id) {
+ RootWindow* root = ash::Shell::GetPrimaryRootWindow();
+ Window* container = root->GetChildById(container_id);
+ return static_cast<int>(container->children().size());
+}
+
+class TestObserver : public DesktopBackgroundControllerObserver {
+ public:
+ explicit TestObserver(DesktopBackgroundController* controller)
+ : controller_(controller) {
+ DCHECK(controller_);
+ controller_->AddObserver(this);
+ }
+
+ virtual ~TestObserver() {
+ controller_->RemoveObserver(this);
+ }
+
+ void WaitForWallpaperDataChanged() {
+ base::MessageLoop::current()->Run();
+ }
+
+ // DesktopBackgroundControllerObserver overrides:
+ virtual void OnWallpaperDataChanged() OVERRIDE {
+ base::MessageLoop::current()->Quit();
+ }
+
+ private:
+ DesktopBackgroundController* controller_;
+};
+
+// Steps a widget's layer animation until it is completed. Animations must be
+// enabled.
+void RunAnimationForWidget(views::Widget* widget) {
+ // Animations must be enabled for stepping to work.
+ ASSERT_NE(ui::ScopedAnimationDurationScaleMode::duration_scale_mode(),
+ ui::ScopedAnimationDurationScaleMode::ZERO_DURATION);
+
+ ui::Layer* layer = widget->GetNativeView()->layer();
+ ui::LayerAnimatorTestController controller(layer->GetAnimator());
+ ui::AnimationContainerElement* element = layer->GetAnimator();
+ // Multiple steps are required to complete complex animations.
+ // TODO(vollick): This should not be necessary. crbug.com/154017
+ while (controller.animator()->is_animating()) {
+ controller.StartThreadedAnimationsIfNeeded();
+ base::TimeTicks step_time = controller.animator()->last_step_time();
+ element->Step(step_time + base::TimeDelta::FromMilliseconds(1000));
+ }
+}
+
+} // namespace
+
+class DesktopBackgroundControllerTest : public test::AshTestBase {
+ public:
+ DesktopBackgroundControllerTest()
+ : command_line_(CommandLine::NO_PROGRAM),
+ controller_(NULL) {
+ }
+ virtual ~DesktopBackgroundControllerTest() {}
+
+ virtual void SetUp() OVERRIDE {
+ test::AshTestBase::SetUp();
+ // Ash shell initialization creates wallpaper. Reset it so we can manually
+ // control wallpaper creation and animation in our tests.
+ RootWindowController* root_window_controller =
+ Shell::GetPrimaryRootWindowController();
+ root_window_controller->SetWallpaperController(NULL);
+ root_window_controller->SetAnimatingWallpaperController(NULL);
+ controller_ = Shell::GetInstance()->desktop_background_controller();
+ }
+
+ protected:
+ // Colors used for different default wallpapers by
+ // WriteWallpapersAndSetFlags().
+ static const SkColor kLargeWallpaperColor = SK_ColorRED;
+ static const SkColor kSmallWallpaperColor = SK_ColorGREEN;
+ static const SkColor kLargeGuestWallpaperColor = SK_ColorBLUE;
+ static const SkColor kSmallGuestWallpaperColor = SK_ColorYELLOW;
+
+ // Dimension used for width and height of default wallpaper images. A
+ // small value is used to minimize the amount of time spent compressing
+ // and writing images.
+ static const int kWallpaperSize = 2;
+
+ // Runs kAnimatingDesktopController's animation to completion.
+ // TODO(bshe): Don't require tests to run animations; it's slow.
+ void RunDesktopControllerAnimation() {
+ DesktopBackgroundWidgetController* controller =
+ Shell::GetPrimaryRootWindowController()->
+ animating_wallpaper_controller()->GetController(false);
+ ASSERT_NO_FATAL_FAILURE(RunAnimationForWidget(controller->widget()));
+ }
+
+ // Returns true if the color at the center of |image| is close to
+ // |expected_color|. (The center is used so small wallpaper images can be
+ // used.)
+ bool ImageIsNearColor(gfx::ImageSkia image, SkColor expected_color) {
+ if (image.size().IsEmpty()) {
+ LOG(ERROR) << "Image is empty";
+ return false;
+ }
+
+ const SkBitmap* bitmap = image.bitmap();
+ if (!bitmap) {
+ LOG(ERROR) << "Unable to get bitmap from image";
+ return false;
+ }
+
+ bitmap->lockPixels();
+ gfx::Point center = gfx::Rect(image.size()).CenterPoint();
+ SkColor image_color = bitmap->getColor(center.x(), center.y());
+ bitmap->unlockPixels();
+
+ const int kDiff = 3;
+ if (std::abs(static_cast<int>(SkColorGetA(image_color)) -
+ static_cast<int>(SkColorGetA(expected_color))) > kDiff ||
+ std::abs(static_cast<int>(SkColorGetR(image_color)) -
+ static_cast<int>(SkColorGetR(expected_color))) > kDiff ||
+ std::abs(static_cast<int>(SkColorGetG(image_color)) -
+ static_cast<int>(SkColorGetG(expected_color))) > kDiff ||
+ std::abs(static_cast<int>(SkColorGetB(image_color)) -
+ static_cast<int>(SkColorGetB(expected_color))) > kDiff) {
+ LOG(ERROR) << "Expected color near 0x" << std::hex << expected_color
+ << " but got 0x" << image_color;
+ return false;
+ }
+
+ return true;
+ }
+
+ // Writes a JPEG image of the specified size and color to |path|. Returns
+ // true on success.
+ bool WriteJPEGFile(const base::FilePath& path,
+ int width,
+ int height,
+ SkColor color) {
+ SkBitmap bitmap;
+ bitmap.setConfig(SkBitmap::kARGB_8888_Config, width, height, 0);
+ bitmap.allocPixels();
+ bitmap.eraseColor(color);
+
+ const int kQuality = 80;
+ std::vector<unsigned char> output;
+ if (!gfx::JPEGCodec::Encode(
+ static_cast<const unsigned char*>(bitmap.getPixels()),
+ gfx::JPEGCodec::FORMAT_SkBitmap, width, height, bitmap.rowBytes(),
+ kQuality, &output)) {
+ LOG(ERROR) << "Unable to encode " << width << "x" << height << " bitmap";
+ return false;
+ }
+
+ size_t bytes_written = file_util::WriteFile(
+ path, reinterpret_cast<const char*>(&output[0]), output.size());
+ if (bytes_written != output.size()) {
+ LOG(ERROR) << "Wrote " << bytes_written << " byte(s) instead of "
+ << output.size() << " to " << path.value();
+ return false;
+ }
+
+ return true;
+ }
+
+ // Initializes |wallpaper_dir_|, writes JPEG wallpaper images to it, and
+ // passes |controller_| a command line instructing it to use the images.
+ // Only needs to be called (once) by tests that want to test loading of
+ // default wallpapers.
+ void WriteWallpapersAndSetFlags() {
+ wallpaper_dir_.reset(new base::ScopedTempDir);
+ ASSERT_TRUE(wallpaper_dir_->CreateUniqueTempDir());
+
+ const base::FilePath kLargePath =
+ wallpaper_dir_->path().Append(FILE_PATH_LITERAL("large.jpg"));
+ ASSERT_TRUE(WriteJPEGFile(kLargePath, kWallpaperSize, kWallpaperSize,
+ kLargeWallpaperColor));
+ command_line_.AppendSwitchPath(
+ switches::kAshDefaultWallpaperLarge, kLargePath);
+
+ const base::FilePath kSmallPath =
+ wallpaper_dir_->path().Append(FILE_PATH_LITERAL("small.jpg"));
+ ASSERT_TRUE(WriteJPEGFile(kSmallPath, kWallpaperSize, kWallpaperSize,
+ kSmallWallpaperColor));
+ command_line_.AppendSwitchPath(
+ switches::kAshDefaultWallpaperSmall, kSmallPath);
+
+ const base::FilePath kLargeGuestPath =
+ wallpaper_dir_->path().Append(FILE_PATH_LITERAL("guest_large.jpg"));
+ ASSERT_TRUE(WriteJPEGFile(kLargeGuestPath, kWallpaperSize, kWallpaperSize,
+ kLargeGuestWallpaperColor));
+ command_line_.AppendSwitchPath(
+ switches::kAshDefaultGuestWallpaperLarge, kLargeGuestPath);
+
+ const base::FilePath kSmallGuestPath =
+ wallpaper_dir_->path().Append(FILE_PATH_LITERAL("guest_small.jpg"));
+ ASSERT_TRUE(WriteJPEGFile(kSmallGuestPath, kWallpaperSize, kWallpaperSize,
+ kSmallGuestWallpaperColor));
+ command_line_.AppendSwitchPath(
+ switches::kAshDefaultGuestWallpaperSmall, kSmallGuestPath);
+
+ controller_->set_command_line_for_testing(&command_line_);
+ }
+
+ // Custom command line passed to DesktopBackgroundController by
+ // WriteWallpapersAndSetFlags().
+ CommandLine command_line_;
+
+ // Directory created by WriteWallpapersAndSetFlags() to store default
+ // wallpaper images.
+ scoped_ptr<base::ScopedTempDir> wallpaper_dir_;
+
+ DesktopBackgroundController* controller_; // Not owned.
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(DesktopBackgroundControllerTest);
+};
+
+TEST_F(DesktopBackgroundControllerTest, BasicReparenting) {
+ DesktopBackgroundController* controller =
+ Shell::GetInstance()->desktop_background_controller();
+ controller->CreateEmptyWallpaper();
+
+ // Wallpaper view/window exists in the desktop background container and
+ // nothing is in the lock screen background container.
+ EXPECT_EQ(1, ChildCountForContainer(kDesktopBackgroundId));
+ EXPECT_EQ(0, ChildCountForContainer(kLockScreenBackgroundId));
+
+ // Moving background to lock container should succeed the first time but
+ // subsequent calls should do nothing.
+ EXPECT_TRUE(controller->MoveDesktopToLockedContainer());
+ EXPECT_FALSE(controller->MoveDesktopToLockedContainer());
+
+ // One window is moved from desktop to lock container.
+ EXPECT_EQ(0, ChildCountForContainer(kDesktopBackgroundId));
+ EXPECT_EQ(1, ChildCountForContainer(kLockScreenBackgroundId));
+
+ // Moving background to desktop container should succeed the first time.
+ EXPECT_TRUE(controller->MoveDesktopToUnlockedContainer());
+ EXPECT_FALSE(controller->MoveDesktopToUnlockedContainer());
+
+ // One window is moved from lock to desktop container.
+ EXPECT_EQ(1, ChildCountForContainer(kDesktopBackgroundId));
+ EXPECT_EQ(0, ChildCountForContainer(kLockScreenBackgroundId));
+}
+
+TEST_F(DesktopBackgroundControllerTest, ControllerOwnership) {
+ // We cannot short-circuit animations for this test.
+ ui::ScopedAnimationDurationScaleMode normal_duration_mode(
+ ui::ScopedAnimationDurationScaleMode::NORMAL_DURATION);
+
+ // Create wallpaper and background view.
+ DesktopBackgroundController* controller =
+ Shell::GetInstance()->desktop_background_controller();
+ controller->CreateEmptyWallpaper();
+
+ // The new wallpaper is ready to start animating. kAnimatingDesktopController
+ // holds the widget controller instance. kDesktopController will get it later.
+ RootWindowController* root_window_controller =
+ Shell::GetPrimaryRootWindowController();
+ EXPECT_TRUE(root_window_controller->animating_wallpaper_controller()->
+ GetController(false));
+
+ // kDesktopController will receive the widget controller when the animation
+ // is done.
+ EXPECT_FALSE(root_window_controller->wallpaper_controller());
+
+ // Force the widget's layer animation to play to completion.
+ RunDesktopControllerAnimation();
+
+ // Ownership has moved from kAnimatingDesktopController to kDesktopController.
+ EXPECT_FALSE(root_window_controller->animating_wallpaper_controller()->
+ GetController(false));
+ EXPECT_TRUE(root_window_controller->wallpaper_controller());
+}
+
+// Test for crbug.com/149043 "Unlock screen, no launcher appears". Ensure we
+// move all desktop views if there are more than one.
+TEST_F(DesktopBackgroundControllerTest, BackgroundMovementDuringUnlock) {
+ // We cannot short-circuit animations for this test.
+ ui::ScopedAnimationDurationScaleMode normal_duration_mode(
+ ui::ScopedAnimationDurationScaleMode::NORMAL_DURATION);
+
+ // Reset wallpaper state, see ControllerOwnership above.
+ DesktopBackgroundController* controller =
+ Shell::GetInstance()->desktop_background_controller();
+ controller->CreateEmptyWallpaper();
+
+ // Run wallpaper show animation to completion.
+ RunDesktopControllerAnimation();
+
+ // User locks the screen, which moves the background forward.
+ controller->MoveDesktopToLockedContainer();
+
+ // Suspend/resume cycle causes wallpaper to refresh, loading a new desktop
+ // background that will animate in on top of the old one.
+ controller->CreateEmptyWallpaper();
+
+ // In this state we have two desktop background views stored in different
+ // properties. Both are in the lock screen background container.
+ RootWindowController* root_window_controller =
+ Shell::GetPrimaryRootWindowController();
+ EXPECT_TRUE(root_window_controller->animating_wallpaper_controller()->
+ GetController(false));
+ EXPECT_TRUE(root_window_controller->wallpaper_controller());
+ EXPECT_EQ(0, ChildCountForContainer(kDesktopBackgroundId));
+ EXPECT_EQ(2, ChildCountForContainer(kLockScreenBackgroundId));
+
+ // Before the wallpaper's animation completes, user unlocks the screen, which
+ // moves the desktop to the back.
+ controller->MoveDesktopToUnlockedContainer();
+
+ // Ensure both desktop backgrounds have moved.
+ EXPECT_EQ(2, ChildCountForContainer(kDesktopBackgroundId));
+ EXPECT_EQ(0, ChildCountForContainer(kLockScreenBackgroundId));
+
+ // Finish the new desktop background animation.
+ RunDesktopControllerAnimation();
+
+ // Now there is one desktop background, in the back.
+ EXPECT_EQ(1, ChildCountForContainer(kDesktopBackgroundId));
+ EXPECT_EQ(0, ChildCountForContainer(kLockScreenBackgroundId));
+}
+
+// Test for crbug.com/156542. Animating wallpaper should immediately finish
+// animation and replace current wallpaper before next animation starts.
+TEST_F(DesktopBackgroundControllerTest, ChangeWallpaperQuick) {
+ // We cannot short-circuit animations for this test.
+ ui::ScopedAnimationDurationScaleMode normal_duration_mode(
+ ui::ScopedAnimationDurationScaleMode::NORMAL_DURATION);
+
+ // Reset wallpaper state, see ControllerOwnership above.
+ DesktopBackgroundController* controller =
+ Shell::GetInstance()->desktop_background_controller();
+ controller->CreateEmptyWallpaper();
+
+ // Run wallpaper show animation to completion.
+ RunDesktopControllerAnimation();
+
+ // Change to a new wallpaper.
+ controller->CreateEmptyWallpaper();
+
+ RootWindowController* root_window_controller =
+ Shell::GetPrimaryRootWindowController();
+ DesktopBackgroundWidgetController* animating_controller =
+ root_window_controller->animating_wallpaper_controller()->
+ GetController(false);
+ EXPECT_TRUE(animating_controller);
+ EXPECT_TRUE(root_window_controller->wallpaper_controller());
+
+ // Change to another wallpaper before animation finished.
+ controller->CreateEmptyWallpaper();
+
+ // The animating controller should immediately move to desktop controller.
+ EXPECT_EQ(animating_controller,
+ root_window_controller->wallpaper_controller());
+
+ // Cache the new animating controller.
+ animating_controller = root_window_controller->
+ animating_wallpaper_controller()->GetController(false);
+
+ // Run wallpaper show animation to completion.
+ ASSERT_NO_FATAL_FAILURE(
+ RunAnimationForWidget(
+ root_window_controller->animating_wallpaper_controller()->
+ GetController(false)->widget()));
+
+ EXPECT_TRUE(root_window_controller->wallpaper_controller());
+ EXPECT_FALSE(root_window_controller->animating_wallpaper_controller()->
+ GetController(false));
+ // The desktop controller should be the last created animating controller.
+ EXPECT_EQ(animating_controller,
+ root_window_controller->wallpaper_controller());
+}
+
+TEST_F(DesktopBackgroundControllerTest, GetAppropriateResolution) {
+ // TODO(derat|oshima|bshe): Configuring desktops seems busted on Win8,
+ // even when just a single display is being used -- the small wallpaper
+ // is used instead of the large one. Track down the cause of the problem
+ // and only use a SupportsMultipleDisplays() clause for the dual-display
+ // code below.
+ if (!SupportsMultipleDisplays())
+ return;
+
+ test::DisplayManagerTestApi display_manager_test_api(
+ Shell::GetInstance()->display_manager());
+
+ // Small wallpaper images should be used for configurations less than or
+ // equal to kSmallWallpaperMaxWidth by kSmallWallpaperMaxHeight, even if
+ // multiple displays are connected.
+ display_manager_test_api.UpdateDisplay("800x600");
+ EXPECT_EQ(WALLPAPER_RESOLUTION_SMALL,
+ controller_->GetAppropriateResolution());
+ display_manager_test_api.UpdateDisplay("800x600,800x600");
+ EXPECT_EQ(WALLPAPER_RESOLUTION_SMALL,
+ controller_->GetAppropriateResolution());
+ display_manager_test_api.UpdateDisplay("1366x800");
+ EXPECT_EQ(WALLPAPER_RESOLUTION_SMALL,
+ controller_->GetAppropriateResolution());
+
+ // At larger sizes, large wallpapers should be used.
+ display_manager_test_api.UpdateDisplay("1367x800");
+ EXPECT_EQ(WALLPAPER_RESOLUTION_LARGE,
+ controller_->GetAppropriateResolution());
+ display_manager_test_api.UpdateDisplay("1367x801");
+ EXPECT_EQ(WALLPAPER_RESOLUTION_LARGE,
+ controller_->GetAppropriateResolution());
+ display_manager_test_api.UpdateDisplay("2560x1700");
+ EXPECT_EQ(WALLPAPER_RESOLUTION_LARGE,
+ controller_->GetAppropriateResolution());
+}
+
+// Test that DesktopBackgroundController loads the appropriate wallpaper
+// images as specified via command-line flags in various situations.
+// Splitting these into separate tests avoids needing to run animations.
+// TODO(derat): Combine these into a single test -- see
+// RunDesktopControllerAnimation()'s TODO.
+TEST_F(DesktopBackgroundControllerTest, SmallDefaultWallpaper) {
+ if (!SupportsMultipleDisplays())
+ return;
+
+ WriteWallpapersAndSetFlags();
+ TestObserver observer(controller_);
+
+ // At 800x600, the small wallpaper should be loaded.
+ test::DisplayManagerTestApi display_manager_test_api(
+ Shell::GetInstance()->display_manager());
+ display_manager_test_api.UpdateDisplay("800x600");
+ ASSERT_TRUE(controller_->SetDefaultWallpaper(false));
+ observer.WaitForWallpaperDataChanged();
+ EXPECT_TRUE(ImageIsNearColor(controller_->GetWallpaper(),
+ kSmallWallpaperColor));
+
+ // Requesting the same wallpaper again should be a no-op.
+ ASSERT_FALSE(controller_->SetDefaultWallpaper(false));
+}
+
+TEST_F(DesktopBackgroundControllerTest, LargeDefaultWallpaper) {
+ if (!SupportsMultipleDisplays())
+ return;
+
+ WriteWallpapersAndSetFlags();
+ TestObserver observer(controller_);
+ test::DisplayManagerTestApi display_manager_test_api(
+ Shell::GetInstance()->display_manager());
+ display_manager_test_api.UpdateDisplay("1600x1200");
+ ASSERT_TRUE(controller_->SetDefaultWallpaper(false));
+ observer.WaitForWallpaperDataChanged();
+ EXPECT_TRUE(ImageIsNearColor(controller_->GetWallpaper(),
+ kLargeWallpaperColor));
+}
+
+TEST_F(DesktopBackgroundControllerTest, SmallGuestWallpaper) {
+ if (!SupportsMultipleDisplays())
+ return;
+
+ WriteWallpapersAndSetFlags();
+ TestObserver observer(controller_);
+ test::DisplayManagerTestApi display_manager_test_api(
+ Shell::GetInstance()->display_manager());
+ display_manager_test_api.UpdateDisplay("800x600");
+ ASSERT_TRUE(controller_->SetDefaultWallpaper(true));
+ observer.WaitForWallpaperDataChanged();
+ EXPECT_TRUE(ImageIsNearColor(controller_->GetWallpaper(),
+ kSmallGuestWallpaperColor));
+}
+
+TEST_F(DesktopBackgroundControllerTest, LargeGuestWallpaper) {
+ if (!SupportsMultipleDisplays())
+ return;
+
+ WriteWallpapersAndSetFlags();
+ TestObserver observer(controller_);
+ test::DisplayManagerTestApi display_manager_test_api(
+ Shell::GetInstance()->display_manager());
+ display_manager_test_api.UpdateDisplay("1600x1200");
+ ASSERT_TRUE(controller_->SetDefaultWallpaper(true));
+ observer.WaitForWallpaperDataChanged();
+ EXPECT_TRUE(ImageIsNearColor(controller_->GetWallpaper(),
+ kLargeGuestWallpaperColor));
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/desktop_background/desktop_background_view.cc b/chromium/ash/desktop_background/desktop_background_view.cc
new file mode 100644
index 00000000000..6f1685039ab
--- /dev/null
+++ b/chromium/ash/desktop_background/desktop_background_view.cc
@@ -0,0 +1,184 @@
+// Copyright (c) 2012 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.
+
+#include "ash/desktop_background/desktop_background_view.h"
+
+#include <limits>
+
+#include "ash/ash_export.h"
+#include "ash/desktop_background/desktop_background_controller.h"
+#include "ash/desktop_background/desktop_background_widget_controller.h"
+#include "ash/desktop_background/user_wallpaper_delegate.h"
+#include "ash/display/display_manager.h"
+#include "ash/root_window_controller.h"
+#include "ash/session_state_delegate.h"
+#include "ash/shell.h"
+#include "ash/shell_window_ids.h"
+#include "ash/wm/property_util.h"
+#include "ash/wm/window_animations.h"
+#include "base/message_loop/message_loop.h"
+#include "base/strings/utf_string_conversions.h"
+#include "ui/aura/root_window.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "ui/compositor/layer.h"
+#include "ui/gfx/canvas.h"
+#include "ui/gfx/image/image.h"
+#include "ui/views/widget/widget.h"
+
+namespace ash {
+namespace internal {
+namespace {
+
+// For our scaling ratios we need to round positive numbers.
+int RoundPositive(double x) {
+ return static_cast<int>(floor(x + 0.5));
+}
+
+} // namespace
+
+////////////////////////////////////////////////////////////////////////////////
+// DesktopBackgroundView, public:
+
+DesktopBackgroundView::DesktopBackgroundView() {
+ set_context_menu_controller(this);
+}
+
+DesktopBackgroundView::~DesktopBackgroundView() {
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// DesktopBackgroundView, views::View overrides:
+
+void DesktopBackgroundView::OnPaint(gfx::Canvas* canvas) {
+ // Scale the image while maintaining the aspect ratio, cropping as
+ // necessary to fill the background. Ideally the image should be larger
+ // than the largest display supported, if not we will center it rather than
+ // streching to avoid upsampling artifacts (Note that we could tile too, but
+ // decided not to do this at the moment).
+ DesktopBackgroundController* controller =
+ Shell::GetInstance()->desktop_background_controller();
+ gfx::ImageSkia wallpaper = controller->GetWallpaper();
+ WallpaperLayout wallpaper_layout = controller->GetWallpaperLayout();
+
+ gfx::NativeView native_view = GetWidget()->GetNativeView();
+ gfx::Display display = gfx::Screen::GetScreenFor(native_view)->
+ GetDisplayNearestWindow(native_view);
+
+ DisplayManager* display_manager = Shell::GetInstance()->display_manager();
+ DisplayInfo display_info = display_manager->GetDisplayInfo(display.id());
+ float scaling = display_info.ui_scale();
+ if (scaling <= 1.0f)
+ scaling = 1.0f;
+ // Allow scaling up to the UI scaling.
+ // TODO(oshima): Create separate layer that fits to the image and then
+ // scale to avoid artifacts and be more efficient when clipped.
+ gfx::Rect wallpaper_rect(
+ 0, 0, wallpaper.width() * scaling, wallpaper.height() * scaling);
+
+ if (wallpaper_layout == WALLPAPER_LAYOUT_CENTER_CROPPED &&
+ wallpaper_rect.width() >= width() &&
+ wallpaper_rect.height() >= height()) {
+ // The dimension with the smallest ratio must be cropped, the other one
+ // is preserved. Both are set in gfx::Size cropped_size.
+ double horizontal_ratio = static_cast<double>(width()) /
+ static_cast<double>(wallpaper.width());
+ double vertical_ratio = static_cast<double>(height()) /
+ static_cast<double>(wallpaper.height());
+
+ gfx::Size cropped_size;
+ if (vertical_ratio > horizontal_ratio) {
+ cropped_size = gfx::Size(
+ RoundPositive(static_cast<double>(width()) / vertical_ratio),
+ wallpaper.height());
+ } else {
+ cropped_size = gfx::Size(wallpaper.width(),
+ RoundPositive(static_cast<double>(height()) / horizontal_ratio));
+ }
+
+ gfx::Rect wallpaper_cropped_rect(
+ 0, 0, wallpaper.width(), wallpaper.height());
+ wallpaper_cropped_rect.ClampToCenteredSize(cropped_size);
+ canvas->DrawImageInt(wallpaper,
+ wallpaper_cropped_rect.x(), wallpaper_cropped_rect.y(),
+ wallpaper_cropped_rect.width(), wallpaper_cropped_rect.height(),
+ 0, 0, width(), height(),
+ true);
+ } else if (wallpaper_layout == WALLPAPER_LAYOUT_TILE) {
+ canvas->TileImageInt(wallpaper, 0, 0, width(), height());
+ } else if (wallpaper_layout == WALLPAPER_LAYOUT_STRETCH) {
+ // This is generally not recommended as it may show artifacts.
+ canvas->DrawImageInt(wallpaper, 0, 0, wallpaper.width(),
+ wallpaper.height(), 0, 0, width(), height(), true);
+ } else {
+ // Fill with black to make sure that the entire area is opaque.
+ canvas->FillRect(GetLocalBounds(), SK_ColorBLACK);
+ // All other are simply centered, and not scaled (but may be clipped).
+ if (wallpaper.width() && wallpaper.height()) {
+ canvas->DrawImageInt(
+ wallpaper,
+ 0, 0, wallpaper.width(), wallpaper.height(),
+ (width() - wallpaper_rect.width()) / 2,
+ (height() - wallpaper_rect.height()) / 2,
+ wallpaper_rect.width(),
+ wallpaper_rect.height(),
+ true);
+ }
+ }
+}
+
+bool DesktopBackgroundView::OnMousePressed(const ui::MouseEvent& event) {
+ return true;
+}
+
+void DesktopBackgroundView::ShowContextMenuForView(
+ views::View* source,
+ const gfx::Point& point,
+ ui::MenuSourceType source_type) {
+ Shell::GetInstance()->ShowContextMenu(point, source_type);
+}
+
+views::Widget* CreateDesktopBackground(aura::RootWindow* root_window,
+ int container_id) {
+ DesktopBackgroundController* controller =
+ Shell::GetInstance()->desktop_background_controller();
+ UserWallpaperDelegate* wallpaper_delegate =
+ Shell::GetInstance()->user_wallpaper_delegate();
+
+ views::Widget* desktop_widget = new views::Widget;
+ views::Widget::InitParams params(
+ views::Widget::InitParams::TYPE_WINDOW_FRAMELESS);
+ if (controller->GetWallpaper().isNull())
+ params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW;
+ params.parent = root_window->GetChildById(container_id);
+ desktop_widget->Init(params);
+ desktop_widget->SetContentsView(new DesktopBackgroundView());
+ int animation_type = wallpaper_delegate->GetAnimationType();
+ views::corewm::SetWindowVisibilityAnimationType(
+ desktop_widget->GetNativeView(), animation_type);
+
+ RootWindowController* root_window_controller =
+ GetRootWindowController(root_window);
+
+ // Enable wallpaper transition for the following cases:
+ // 1. Initial(OOBE) wallpaper animation.
+ // 2. Wallpaper fades in from a non empty background.
+ // 3. From an empty background, chrome transit to a logged in user session.
+ // 4. From an empty background, guest user logged in.
+ if (wallpaper_delegate->ShouldShowInitialAnimation() ||
+ root_window_controller->animating_wallpaper_controller() ||
+ Shell::GetInstance()->session_state_delegate()->NumberOfLoggedInUsers()) {
+ views::corewm::SetWindowVisibilityAnimationTransition(
+ desktop_widget->GetNativeView(), views::corewm::ANIMATE_SHOW);
+ } else {
+ // Disable animation if transition to login screen from an empty background.
+ views::corewm::SetWindowVisibilityAnimationTransition(
+ desktop_widget->GetNativeView(), views::corewm::ANIMATE_NONE);
+ }
+
+ desktop_widget->SetBounds(params.parent->bounds());
+ return desktop_widget;
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/desktop_background/desktop_background_view.h b/chromium/ash/desktop_background/desktop_background_view.h
new file mode 100644
index 00000000000..dbd33206fb2
--- /dev/null
+++ b/chromium/ash/desktop_background/desktop_background_view.h
@@ -0,0 +1,37 @@
+// Copyright (c) 2012 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 ASH_DESKTOP_BACKGROUND_DESKTOP_BACKGROUND_VIEW_H_
+#define ASH_DESKTOP_BACKGROUND_DESKTOP_BACKGROUND_VIEW_H_
+
+#include "ui/gfx/image/image_skia.h"
+#include "ui/views/context_menu_controller.h"
+#include "ui/views/view.h"
+
+namespace ash {
+namespace internal {
+
+class DesktopBackgroundView : public views::View,
+ public views::ContextMenuController {
+ public:
+ DesktopBackgroundView();
+ virtual ~DesktopBackgroundView();
+
+ private:
+ // Overridden from views::View:
+ virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE;
+ virtual bool OnMousePressed(const ui::MouseEvent& event) OVERRIDE;
+
+ // Overridden from views::ContextMenuController:
+ virtual void ShowContextMenuForView(views::View* source,
+ const gfx::Point& point,
+ ui::MenuSourceType source_type) OVERRIDE;
+
+ DISALLOW_COPY_AND_ASSIGN(DesktopBackgroundView);
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_DESKTOP_BACKGROUND_DESKTOP_BACKGROUND_VIEW_H_
diff --git a/chromium/ash/desktop_background/desktop_background_widget_controller.cc b/chromium/ash/desktop_background/desktop_background_widget_controller.cc
new file mode 100644
index 00000000000..cbd7727af2e
--- /dev/null
+++ b/chromium/ash/desktop_background/desktop_background_widget_controller.cc
@@ -0,0 +1,160 @@
+// Copyright (c) 2012 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.
+
+#include "ash/desktop_background/desktop_background_widget_controller.h"
+
+#include "ash/ash_export.h"
+#include "ash/desktop_background/user_wallpaper_delegate.h"
+#include "ash/root_window_controller.h"
+#include "ash/shell.h"
+#include "ui/aura/root_window.h"
+#include "ui/compositor/layer_animation_observer.h"
+#include "ui/compositor/scoped_layer_animation_settings.h"
+#include "ui/views/widget/widget.h"
+#include "ui/views/widget/widget_observer.h"
+
+namespace ash {
+namespace internal {
+namespace {
+
+class ShowWallpaperAnimationObserver : public ui::ImplicitAnimationObserver,
+ public views::WidgetObserver {
+ public:
+ ShowWallpaperAnimationObserver(RootWindowController* root_window_controller,
+ views::Widget* desktop_widget,
+ bool is_initial_animation)
+ : root_window_controller_(root_window_controller),
+ desktop_widget_(desktop_widget),
+ is_initial_animation_(is_initial_animation) {
+ DCHECK(desktop_widget_);
+ desktop_widget_->AddObserver(this);
+ }
+
+ virtual ~ShowWallpaperAnimationObserver() {
+ StopObservingImplicitAnimations();
+ if (desktop_widget_)
+ desktop_widget_->RemoveObserver(this);
+ }
+
+ private:
+ // Overridden from ui::ImplicitAnimationObserver:
+ virtual void OnImplicitAnimationsScheduled() OVERRIDE {
+ if (is_initial_animation_) {
+ root_window_controller_->
+ HandleInitialDesktopBackgroundAnimationStarted();
+ }
+ }
+
+ virtual void OnImplicitAnimationsCompleted() OVERRIDE {
+ root_window_controller_->OnWallpaperAnimationFinished(desktop_widget_);
+ delete this;
+ }
+
+ // Overridden from views::WidgetObserver.
+ virtual void OnWidgetDestroying(views::Widget* widget) OVERRIDE {
+ delete this;
+ }
+
+ RootWindowController* root_window_controller_;
+ views::Widget* desktop_widget_;
+
+ // Is this object observing the initial brightness/grayscale animation?
+ const bool is_initial_animation_;
+
+ DISALLOW_COPY_AND_ASSIGN(ShowWallpaperAnimationObserver);
+};
+
+} // namespace
+
+DesktopBackgroundWidgetController::DesktopBackgroundWidgetController(
+ views::Widget* widget) : widget_(widget) {
+ DCHECK(widget_);
+ widget_->AddObserver(this);
+}
+
+DesktopBackgroundWidgetController::DesktopBackgroundWidgetController(
+ ui::Layer* layer) : widget_(NULL) {
+ layer_.reset(layer);
+}
+
+DesktopBackgroundWidgetController::~DesktopBackgroundWidgetController() {
+ if (widget_) {
+ widget_->RemoveObserver(this);
+ widget_->CloseNow();
+ widget_ = NULL;
+ } else if (layer_)
+ layer_.reset(NULL);
+}
+
+void DesktopBackgroundWidgetController::OnWidgetDestroying(
+ views::Widget* widget) {
+ widget_->RemoveObserver(this);
+ widget_ = NULL;
+}
+
+void DesktopBackgroundWidgetController::SetBounds(gfx::Rect bounds) {
+ if (widget_)
+ widget_->SetBounds(bounds);
+ else if (layer_)
+ layer_->SetBounds(bounds);
+}
+
+bool DesktopBackgroundWidgetController::Reparent(aura::RootWindow* root_window,
+ int src_container,
+ int dest_container) {
+ if (widget_) {
+ views::Widget::ReparentNativeView(widget_->GetNativeView(),
+ root_window->GetChildById(dest_container));
+ return true;
+ } else if (layer_) {
+ ui::Layer* layer = layer_.get();
+ root_window->GetChildById(src_container)->layer()->Remove(layer);
+ root_window->GetChildById(dest_container)->layer()->Add(layer);
+ return true;
+ }
+ // Nothing to reparent.
+ return false;
+}
+
+void DesktopBackgroundWidgetController::StartAnimating(
+ RootWindowController* root_window_controller) {
+ if (widget_) {
+ ui::ScopedLayerAnimationSettings settings(
+ widget_->GetNativeView()->layer()->GetAnimator());
+ settings.SetPreemptionStrategy(ui::LayerAnimator::ENQUEUE_NEW_ANIMATION);
+ settings.AddObserver(new ShowWallpaperAnimationObserver(
+ root_window_controller, widget_,
+ Shell::GetInstance()->user_wallpaper_delegate()->
+ ShouldShowInitialAnimation()));
+ widget_->Show();
+ widget_->GetNativeView()->SetName("DesktopBackgroundView");
+ } else if (layer_)
+ root_window_controller->OnWallpaperAnimationFinished(NULL);
+}
+
+AnimatingDesktopController::AnimatingDesktopController(
+ DesktopBackgroundWidgetController* component) {
+ controller_.reset(component);
+}
+
+AnimatingDesktopController::~AnimatingDesktopController() {
+}
+
+void AnimatingDesktopController::StopAnimating() {
+ if (controller_) {
+ ui::Layer* layer = controller_->layer() ? controller_->layer() :
+ controller_->widget()->GetNativeView()->layer();
+ layer->GetAnimator()->StopAnimating();
+ }
+}
+
+DesktopBackgroundWidgetController* AnimatingDesktopController::GetController(
+ bool pass_ownership) {
+ if (pass_ownership)
+ return controller_.release();
+ return controller_.get();
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/desktop_background/desktop_background_widget_controller.h b/chromium/ash/desktop_background/desktop_background_widget_controller.h
new file mode 100644
index 00000000000..8aa34490cf9
--- /dev/null
+++ b/chromium/ash/desktop_background/desktop_background_widget_controller.h
@@ -0,0 +1,99 @@
+// Copyright (c) 2012 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 ASH_DESKTOP_BACKGROUND_DESKTOP_BACKGROUND_WIDGET_CONTROLLER_H_
+#define ASH_DESKTOP_BACKGROUND_DESKTOP_BACKGROUND_WIDGET_CONTROLLER_H_
+
+#include "ash/ash_export.h"
+#include "base/memory/scoped_ptr.h"
+#include "ui/aura/window.h"
+#include "ui/compositor/layer.h"
+#include "ui/views/widget/widget.h"
+#include "ui/views/widget/widget_observer.h"
+
+namespace ash {
+namespace internal {
+class RootWindowController;
+
+// This class hides difference between two possible background implementations:
+// effective Layer-based for solid color, and Widget-based for images.
+// DesktopBackgroundWidgetController is owned by RootWindowController.
+// When the animation completes the old DesktopBackgroundWidgetController is
+// destroyed. Exported for tests.
+class ASH_EXPORT DesktopBackgroundWidgetController
+ : public views::WidgetObserver {
+ public:
+ // Create
+ explicit DesktopBackgroundWidgetController(views::Widget* widget);
+ explicit DesktopBackgroundWidgetController(ui::Layer* layer);
+
+ virtual ~DesktopBackgroundWidgetController();
+
+ // Overridden from views::WidgetObserver.
+ virtual void OnWidgetDestroying(views::Widget* widget) OVERRIDE;
+
+ // Set bounds of component that draws background.
+ void SetBounds(gfx::Rect bounds);
+
+ // Move component from |src_container| in |root_window| to |dest_container|.
+ // It is required for lock screen, when we need to move background so that
+ // it hides user's windows. Returns true if there was something to reparent.
+ bool Reparent(aura::RootWindow* root_window,
+ int src_container,
+ int dest_container);
+
+ // Starts wallpaper fade in animation. |root_window_controller| is
+ // the root window where the animation will happen. (This is
+ // necessary this as |layer_| doesn't have access to the root window).
+ void StartAnimating(RootWindowController* root_window_controller);
+
+ views::Widget* widget() { return widget_; }
+ ui::Layer* layer() { return layer_.get(); }
+
+ private:
+ views::Widget* widget_;
+ scoped_ptr<ui::Layer> layer_;
+
+ DISALLOW_COPY_AND_ASSIGN(DesktopBackgroundWidgetController);
+};
+
+// This class wraps a DesktopBackgroundWidgetController pointer. It is owned
+// by RootWindowController. The instance of DesktopBackgroundWidgetController is
+// moved to this RootWindowController when the animation completes.
+// Exported for tests.
+class ASH_EXPORT AnimatingDesktopController {
+ public:
+ explicit AnimatingDesktopController(
+ DesktopBackgroundWidgetController* component);
+ ~AnimatingDesktopController();
+
+ // Stops animation and makes sure OnImplicitAnimationsCompleted() is called if
+ // current animation is not finished yet.
+ // Note we have to make sure this function is called before we set
+ // kAnimatingDesktopController to a new controller. If it is not called, the
+ // animating widget/layer is closed immediately and the new one is animating
+ // from the widget/layer before animation. For instance, if a user quickly
+ // switches between red, green and blue wallpapers. The green wallpaper will
+ // first fade in from red wallpaper. And in the middle of the animation, blue
+ // wallpaper also wants to fade in. If the green wallpaper animation does not
+ // finish immediately, the green wallpaper widget will be removed and the red
+ // widget will show again. As a result, the blue wallpaper fades in from red
+ // wallpaper. This is a bad user experience. See bug http://crbug.com/156542
+ // for more details.
+ void StopAnimating();
+
+ // Gets the wrapped DesktopBackgroundWidgetController pointer. Caller should
+ // take ownership of the pointer if |pass_ownership| is true.
+ DesktopBackgroundWidgetController* GetController(bool pass_ownership);
+
+ private:
+ scoped_ptr<DesktopBackgroundWidgetController> controller_;
+
+ DISALLOW_COPY_AND_ASSIGN(AnimatingDesktopController);
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_DESKTOP_BACKGROUND_DESKTOP_BACKGROUND_WIDGET_CONTROLLER_H_
diff --git a/chromium/ash/desktop_background/user_wallpaper_delegate.h b/chromium/ash/desktop_background/user_wallpaper_delegate.h
new file mode 100644
index 00000000000..440d284eba1
--- /dev/null
+++ b/chromium/ash/desktop_background/user_wallpaper_delegate.h
@@ -0,0 +1,49 @@
+// Copyright 2012 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 ASH_DESKTOP_BACKGROUND_USER_WALLPAPER_DELEGATE_H_
+#define ASH_DESKTOP_BACKGROUND_USER_WALLPAPER_DELEGATE_H_
+
+#include "ash/ash_export.h"
+#include "ui/views/corewm/window_animations.h"
+
+namespace ash {
+
+class ASH_EXPORT UserWallpaperDelegate {
+ public:
+ virtual ~UserWallpaperDelegate() {}
+
+ // Returns the type of window animation that should be used when showing the
+ // wallpaper.
+ virtual int GetAnimationType() = 0;
+
+ // Should the slower initial animation be shown (as opposed to the faster
+ // animation that's used e.g. when switching from one user's wallpaper to
+ // another's on the login screen)?
+ virtual bool ShouldShowInitialAnimation() = 0;
+
+ // Updates current wallpaper. It may switch the size of wallpaper based on the
+ // current display's resolution.
+ virtual void UpdateWallpaper() = 0;
+
+ // Initialize wallpaper.
+ virtual void InitializeWallpaper() = 0;
+
+ // Opens the set wallpaper page in the browser.
+ virtual void OpenSetWallpaperPage() = 0;
+
+ // Returns true if user can open set wallpaper page. Only guest user returns
+ // false currently.
+ virtual bool CanOpenSetWallpaperPage() = 0;
+
+ // Notifies delegate that wallpaper animation has finished.
+ virtual void OnWallpaperAnimationFinished() = 0;
+
+ // Notifies delegate that wallpaper boot animation has finished.
+ virtual void OnWallpaperBootAnimationFinished() = 0;
+};
+
+} // namespace ash
+
+#endif // ASH_DESKTOP_BACKGROUND_USER_WALLPAPER_DELEGATE_H_
diff --git a/chromium/ash/desktop_background/wallpaper_resizer.cc b/chromium/ash/desktop_background/wallpaper_resizer.cc
new file mode 100644
index 00000000000..b75e262e25f
--- /dev/null
+++ b/chromium/ash/desktop_background/wallpaper_resizer.cc
@@ -0,0 +1,146 @@
+// Copyright (c) 2013 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.
+
+#include "ash/desktop_background/wallpaper_resizer.h"
+
+#include "ash/desktop_background/wallpaper_resizer_observer.h"
+#include "base/bind.h"
+#include "base/logging.h"
+#include "base/threading/sequenced_worker_pool.h"
+#include "base/threading/worker_pool.h"
+#include "content/public/browser/browser_thread.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "ui/gfx/skia_util.h"
+
+using content::BrowserThread;
+
+namespace ash {
+namespace {
+
+// For our scaling ratios we need to round positive numbers.
+int RoundPositive(double x) {
+ return static_cast<int>(floor(x + 0.5));
+}
+
+// Resizes |orig_bitmap| to |target_size| using |layout| and stores the
+// resulting bitmap at |resized_bitmap_out|.
+void Resize(SkBitmap orig_bitmap,
+ const gfx::Size& target_size,
+ WallpaperLayout layout,
+ SkBitmap* resized_bitmap_out) {
+ DCHECK(BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread());
+ SkBitmap new_bitmap = orig_bitmap;
+
+ const int orig_width = orig_bitmap.width();
+ const int orig_height = orig_bitmap.height();
+ const int new_width = target_size.width();
+ const int new_height = target_size.height();
+
+ if (orig_width > new_width || orig_height > new_height) {
+ gfx::Rect wallpaper_rect(0, 0, orig_width, orig_height);
+ gfx::Size cropped_size = gfx::Size(std::min(new_width, orig_width),
+ std::min(new_height, orig_height));
+ switch (layout) {
+ case WALLPAPER_LAYOUT_CENTER:
+ wallpaper_rect.ClampToCenteredSize(cropped_size);
+ orig_bitmap.extractSubset(&new_bitmap,
+ gfx::RectToSkIRect(wallpaper_rect));
+ break;
+ case WALLPAPER_LAYOUT_TILE:
+ wallpaper_rect.set_size(cropped_size);
+ orig_bitmap.extractSubset(&new_bitmap,
+ gfx::RectToSkIRect(wallpaper_rect));
+ break;
+ case WALLPAPER_LAYOUT_STRETCH:
+ new_bitmap = skia::ImageOperations::Resize(
+ orig_bitmap, skia::ImageOperations::RESIZE_LANCZOS3,
+ new_width, new_height);
+ break;
+ case WALLPAPER_LAYOUT_CENTER_CROPPED:
+ if (orig_width > new_width && orig_height > new_height) {
+ // The dimension with the smallest ratio must be cropped, the other
+ // one is preserved. Both are set in gfx::Size cropped_size.
+ double horizontal_ratio = static_cast<double>(new_width) /
+ static_cast<double>(orig_width);
+ double vertical_ratio = static_cast<double>(new_height) /
+ static_cast<double>(orig_height);
+
+ if (vertical_ratio > horizontal_ratio) {
+ cropped_size = gfx::Size(
+ RoundPositive(static_cast<double>(new_width) / vertical_ratio),
+ orig_height);
+ } else {
+ cropped_size = gfx::Size(orig_width, RoundPositive(
+ static_cast<double>(new_height) / horizontal_ratio));
+ }
+ wallpaper_rect.ClampToCenteredSize(cropped_size);
+ SkBitmap sub_image;
+ orig_bitmap.extractSubset(&sub_image,
+ gfx::RectToSkIRect(wallpaper_rect));
+ new_bitmap = skia::ImageOperations::Resize(
+ sub_image, skia::ImageOperations::RESIZE_LANCZOS3,
+ new_width, new_height);
+ }
+ }
+ }
+
+ *resized_bitmap_out = new_bitmap;
+ resized_bitmap_out->setImmutable();
+}
+
+} // namespace
+
+WallpaperResizer::WallpaperResizer(int image_resource_id,
+ const gfx::Size& target_size,
+ WallpaperLayout layout)
+ : wallpaper_image_(*(ui::ResourceBundle::GetSharedInstance().
+ GetImageNamed(image_resource_id).ToImageSkia())),
+ target_size_(target_size),
+ layout_(layout),
+ weak_ptr_factory_(this) {
+}
+
+WallpaperResizer::WallpaperResizer(const gfx::ImageSkia& image,
+ const gfx::Size& target_size,
+ WallpaperLayout layout)
+ : wallpaper_image_(image),
+ target_size_(target_size),
+ layout_(layout),
+ weak_ptr_factory_(this) {
+}
+
+WallpaperResizer::~WallpaperResizer() {
+}
+
+void WallpaperResizer::StartResize() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ SkBitmap* resized_bitmap = new SkBitmap;
+ if (!content::BrowserThread::PostBlockingPoolTaskAndReply(
+ FROM_HERE,
+ base::Bind(&Resize, *wallpaper_image_.bitmap(), target_size_,
+ layout_, resized_bitmap),
+ base::Bind(&WallpaperResizer::OnResizeFinished,
+ weak_ptr_factory_.GetWeakPtr(),
+ base::Owned(resized_bitmap)))) {
+ LOG(WARNING) << "PostSequencedWorkerTask failed. "
+ << "Wallpaper may not be resized.";
+ }
+}
+
+void WallpaperResizer::AddObserver(WallpaperResizerObserver* observer) {
+ observers_.AddObserver(observer);
+}
+
+void WallpaperResizer::RemoveObserver(WallpaperResizerObserver* observer) {
+ observers_.RemoveObserver(observer);
+}
+
+void WallpaperResizer::OnResizeFinished(SkBitmap* resized_bitmap) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ wallpaper_image_ = gfx::ImageSkia::CreateFrom1xBitmap(*resized_bitmap);
+ FOR_EACH_OBSERVER(WallpaperResizerObserver, observers_,
+ OnWallpaperResized());
+}
+
+} // namespace ash
diff --git a/chromium/ash/desktop_background/wallpaper_resizer.h b/chromium/ash/desktop_background/wallpaper_resizer.h
new file mode 100644
index 00000000000..e39dcd56aa0
--- /dev/null
+++ b/chromium/ash/desktop_background/wallpaper_resizer.h
@@ -0,0 +1,69 @@
+// Copyright (c) 2013 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 ASH_DESKTOP_BACKGROUND_DESKTOP_WALLPAPER_RESIZER_H_
+#define ASH_DESKTOP_BACKGROUND_DESKTOP_WALLPAPER_RESIZER_H_
+
+#include "ash/ash_export.h"
+#include "ash/desktop_background/desktop_background_controller.h"
+#include "base/memory/weak_ptr.h"
+#include "base/observer_list.h"
+#include "skia/ext/image_operations.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+#include "ui/gfx/image/image_skia.h"
+#include "ui/gfx/size.h"
+
+namespace ash {
+
+class WallpaperResizerObserver;
+
+// Stores the current wallpaper data and resize it to |target_size| if needed.
+class ASH_EXPORT WallpaperResizer {
+ public:
+ WallpaperResizer(int image_resource_id,
+ const gfx::Size& target_size,
+ WallpaperLayout layout);
+
+ WallpaperResizer(const gfx::ImageSkia& image,
+ const gfx::Size& target_size,
+ WallpaperLayout layout);
+
+ ~WallpaperResizer();
+
+ const gfx::ImageSkia& wallpaper_image() const { return wallpaper_image_; }
+ const WallpaperLayout layout() const { return layout_; }
+
+ // Called on the UI thread to run Resize() on the worker pool and post an
+ // OnResizeFinished() task back to the UI thread on completion.
+ void StartResize();
+
+ // Add/Remove observers.
+ void AddObserver(WallpaperResizerObserver* observer);
+ void RemoveObserver(WallpaperResizerObserver* observer);
+
+ private:
+ // Copies |resized_bitmap| to |wallpaper_image_| and notifies observers
+ // after Resize() has finished running.
+ void OnResizeFinished(SkBitmap* resized_bitmap);
+
+ ObserverList<WallpaperResizerObserver> observers_;
+
+ // Image that should currently be used for wallpaper. It initially
+ // contains the original image and is updated to contain the resized
+ // image by OnResizeFinished().
+ gfx::ImageSkia wallpaper_image_;
+
+ gfx::Size target_size_;
+
+ WallpaperLayout layout_;
+
+ base::WeakPtrFactory<WallpaperResizer> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(WallpaperResizer);
+};
+
+} // namespace ash
+
+
+#endif // ASH_DESKTOP_BACKGROUND_DESKTOP_WALLPAPER_RESIZER_H_
diff --git a/chromium/ash/desktop_background/wallpaper_resizer_observer.h b/chromium/ash/desktop_background/wallpaper_resizer_observer.h
new file mode 100644
index 00000000000..5a10651d4c1
--- /dev/null
+++ b/chromium/ash/desktop_background/wallpaper_resizer_observer.h
@@ -0,0 +1,23 @@
+// Copyright (c) 2013 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 ASH_DESKTOP_BACKGROUND_WALLPAPER_RESIZER_OBSERVER_H_
+#define ASH_DESKTOP_BACKGROUND_WALLPAPER_RESIZER_OBSERVER_H_
+
+#include "ash/ash_export.h"
+
+namespace ash {
+
+class ASH_EXPORT WallpaperResizerObserver {
+ public:
+ // Invoked when the wallpaper is resized.
+ virtual void OnWallpaperResized() = 0;
+
+ protected:
+ virtual ~WallpaperResizerObserver() {}
+};
+
+} // namespace ash
+
+#endif // ASH_DESKTOP_BACKGROUND_WALLPAPER_RESIZER_OBSERVER_H_
diff --git a/chromium/ash/desktop_background/wallpaper_resizer_unittest.cc b/chromium/ash/desktop_background/wallpaper_resizer_unittest.cc
new file mode 100644
index 00000000000..5761b675f17
--- /dev/null
+++ b/chromium/ash/desktop_background/wallpaper_resizer_unittest.cc
@@ -0,0 +1,147 @@
+// Copyright 2013 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.
+
+#include "ash/desktop_background/wallpaper_resizer.h"
+
+#include "ash/desktop_background/wallpaper_resizer_observer.h"
+#include "base/message_loop/message_loop.h"
+#include "content/public/test/test_browser_thread.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/gfx/image/image_skia_rep.h"
+
+using aura::RootWindow;
+using aura::Window;
+
+namespace {
+
+const int kTestImageWidth = 5;
+const int kTestImageHeight = 2;
+const int kTargetWidth = 1;
+const int kTargetHeight = 1;
+const uint32_t kExpectedCenter = 0x02020202u;
+const uint32_t kExpectedCenterCropped = 0x03030303u;
+const uint32_t kExpectedStretch = 0x04040404u;
+const uint32_t kExpectedTile = 0;
+
+gfx::ImageSkia CreateTestImage(const gfx::Size& size) {
+ SkBitmap src;
+ int w = size.width();
+ int h = size.height();
+ src.setConfig(SkBitmap::kARGB_8888_Config, w, h);
+ src.allocPixels();
+
+ // Fill bitmap with data.
+ for (int y = 0; y < h; ++y) {
+ for (int x = 0; x < w; ++x) {
+ const uint8_t component = static_cast<uint8_t>(y * w + x);
+ const SkColor pixel = SkColorSetARGB(component, component,
+ component, component);
+ *(src.getAddr32(x, y)) = pixel;
+ }
+ }
+
+ return gfx::ImageSkia::CreateFrom1xBitmap(src);
+}
+
+bool IsColor(const gfx::ImageSkia& image, const uint32_t expect) {
+ EXPECT_EQ(image.width(), kTargetWidth);
+ EXPECT_EQ(image.height(), kTargetHeight);
+ const SkBitmap* image_bitmap = image.bitmap();
+ SkAutoLockPixels image_lock(*image_bitmap);
+ return *image_bitmap->getAddr32(0, 0) == expect;
+}
+
+} // namespace
+
+namespace ash {
+namespace internal {
+
+class WallpaperResizerTest : public testing::Test,
+ public WallpaperResizerObserver {
+ public:
+ WallpaperResizerTest()
+ : ui_thread_(content::BrowserThread::UI, &message_loop_) {
+ }
+ virtual ~WallpaperResizerTest() {}
+
+ gfx::ImageSkia Resize(const gfx::ImageSkia& image,
+ const gfx::Size& target_size,
+ WallpaperLayout layout) {
+ scoped_ptr<WallpaperResizer> resizer;
+ resizer.reset(new WallpaperResizer(image, target_size, layout));
+ resizer->AddObserver(this);
+ resizer->StartResize();
+ WaitForResize();
+ resizer->RemoveObserver(this);
+ return resizer->wallpaper_image();
+ }
+
+ void WaitForResize() {
+ message_loop_.Run();
+ }
+
+ virtual void OnWallpaperResized() OVERRIDE {
+ message_loop_.Quit();
+ }
+
+ private:
+ base::MessageLoop message_loop_;
+ content::TestBrowserThread ui_thread_;
+
+ DISALLOW_COPY_AND_ASSIGN(WallpaperResizerTest);
+};
+
+TEST_F(WallpaperResizerTest, BasicResize) {
+ // Keeps in sync with WallpaperLayout enum.
+ WallpaperLayout layouts[4] = {
+ WALLPAPER_LAYOUT_CENTER,
+ WALLPAPER_LAYOUT_CENTER_CROPPED,
+ WALLPAPER_LAYOUT_STRETCH,
+ WALLPAPER_LAYOUT_TILE,
+ };
+ const int length = arraysize(layouts);
+
+ for (int i = 0; i < length; i++) {
+ WallpaperLayout layout = layouts[i];
+ gfx::ImageSkia small_image(gfx::ImageSkiaRep(gfx::Size(10, 20),
+ ui::SCALE_FACTOR_100P));
+
+ gfx::ImageSkia resized_small = Resize(small_image, gfx::Size(800, 600),
+ layout);
+ EXPECT_EQ(10, resized_small.width());
+ EXPECT_EQ(20, resized_small.height());
+
+ gfx::ImageSkia large_image(gfx::ImageSkiaRep(gfx::Size(1000, 1000),
+ ui::SCALE_FACTOR_100P));
+ gfx::ImageSkia resized_large = Resize(large_image, gfx::Size(800, 600),
+ layout);
+ EXPECT_EQ(800, resized_large.width());
+ EXPECT_EQ(600, resized_large.height());
+ }
+}
+
+// Test for crbug.com/244629. "CENTER_CROPPED generates the same image as
+// STRETCH layout"
+TEST_F(WallpaperResizerTest, AllLayoutDifferent) {
+ gfx::ImageSkia image = CreateTestImage(
+ gfx::Size(kTestImageWidth, kTestImageHeight));
+
+ gfx::Size target_size = gfx::Size(kTargetWidth, kTargetHeight);
+ gfx::ImageSkia center = Resize(image, target_size, WALLPAPER_LAYOUT_CENTER);
+
+ gfx::ImageSkia center_cropped = Resize(image, target_size,
+ WALLPAPER_LAYOUT_CENTER_CROPPED);
+
+ gfx::ImageSkia stretch = Resize(image, target_size, WALLPAPER_LAYOUT_STRETCH);
+
+ gfx::ImageSkia tile = Resize(image, target_size, WALLPAPER_LAYOUT_TILE);
+
+ EXPECT_TRUE(IsColor(center, kExpectedCenter));
+ EXPECT_TRUE(IsColor(center_cropped, kExpectedCenterCropped));
+ EXPECT_TRUE(IsColor(stretch, kExpectedStretch));
+ EXPECT_TRUE(IsColor(tile, kExpectedTile));
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/dip_unittest.cc b/chromium/ash/dip_unittest.cc
new file mode 100644
index 00000000000..9869a010520
--- /dev/null
+++ b/chromium/ash/dip_unittest.cc
@@ -0,0 +1,70 @@
+// Copyright (c) 2012 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.
+
+#include <algorithm>
+#include <vector>
+
+#include "ash/display/display_manager.h"
+#include "ash/launcher/launcher.h"
+#include "ash/shelf/shelf_widget.h"
+#include "ash/shell.h"
+#include "ash/test/ash_test_base.h"
+#include "ash/wm/window_properties.h"
+#include "ash/wm/window_util.h"
+#include "base/memory/scoped_ptr.h"
+#include "ui/aura/client/activation_client.h"
+#include "ui/aura/root_window.h"
+#include "ui/aura/window.h"
+#include "ui/compositor/layer.h"
+#include "ui/gfx/display.h"
+#include "ui/gfx/insets.h"
+#include "ui/gfx/screen.h"
+#include "ui/views/corewm/shadow.h"
+#include "ui/views/corewm/shadow_controller.h"
+#include "ui/views/corewm/shadow_types.h"
+#include "ui/views/widget/widget.h"
+
+namespace ash {
+
+typedef ash::test::AshTestBase DIPTest;
+
+// Test if the WM sets correct work area under different density.
+TEST_F(DIPTest, WorkArea) {
+ UpdateDisplay("1000x900*1.0f");
+
+ aura::RootWindow* root = Shell::GetPrimaryRootWindow();
+ const gfx::Display display =
+ Shell::GetScreen()->GetDisplayNearestWindow(root);
+
+ EXPECT_EQ("0,0 1000x900", display.bounds().ToString());
+ gfx::Rect work_area = display.work_area();
+ EXPECT_EQ("0,0 1000x852", work_area.ToString());
+ EXPECT_EQ("0,0,48,0", display.bounds().InsetsFrom(work_area).ToString());
+
+ UpdateDisplay("2000x1800*2.0f");
+ gfx::Screen* screen = Shell::GetScreen();
+
+ const gfx::Display display_2x = screen->GetDisplayNearestWindow(root);
+ const internal::DisplayInfo display_info_2x =
+ Shell::GetInstance()->display_manager()->GetDisplayInfo(display_2x.id());
+
+ // The |bounds_in_pixel()| should report bounds in pixel coordinate.
+ EXPECT_EQ("1,1 2000x1800",
+ display_info_2x.bounds_in_pixel().ToString());
+
+ // Aura and views coordinates are in DIP, so they their bounds do not change.
+ EXPECT_EQ("0,0 1000x900", display_2x.bounds().ToString());
+ work_area = display_2x.work_area();
+ EXPECT_EQ("0,0 1000x852", work_area.ToString());
+ EXPECT_EQ("0,0,48,0", display_2x.bounds().InsetsFrom(work_area).ToString());
+
+ // Sanity check if the workarea's inset hight is same as
+ // the launcher's height.
+ Launcher* launcher = Launcher::ForPrimaryDisplay();
+ EXPECT_EQ(
+ display_2x.bounds().InsetsFrom(work_area).height(),
+ launcher->shelf_widget()->GetNativeView()->layer()->bounds().height());
+}
+
+} // namespace ash
diff --git a/chromium/ash/display/OWNERS b/chromium/ash/display/OWNERS
new file mode 100644
index 00000000000..92f3fbb9210
--- /dev/null
+++ b/chromium/ash/display/OWNERS
@@ -0,0 +1 @@
+oshima@chromium.org
diff --git a/chromium/ash/display/display_change_observer_x11.cc b/chromium/ash/display/display_change_observer_x11.cc
new file mode 100644
index 00000000000..860a5ea4996
--- /dev/null
+++ b/chromium/ash/display/display_change_observer_x11.cc
@@ -0,0 +1,199 @@
+// Copyright (c) 2012 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.
+
+#include "ash/display/display_change_observer_x11.h"
+
+#include <algorithm>
+#include <map>
+#include <set>
+#include <vector>
+
+#include <X11/extensions/Xrandr.h>
+
+#include "ash/ash_switches.h"
+#include "ash/display/display_info.h"
+#include "ash/display/display_layout_store.h"
+#include "ash/display/display_manager.h"
+#include "ash/display/display_util_x11.h"
+#include "ash/shell.h"
+#include "base/command_line.h"
+#include "base/message_loop/message_pump_aurax11.h"
+#include "chromeos/display/output_util.h"
+#include "grit/ash_strings.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/compositor/dip_util.h"
+#include "ui/gfx/display.h"
+
+namespace ash {
+namespace internal {
+namespace {
+
+// The DPI threshold to detect high density screen.
+// Higher DPI than this will use device_scale_factor=2.
+const unsigned int kHighDensityDPIThreshold = 160;
+
+// 1 inch in mm.
+const float kInchInMm = 25.4f;
+
+int64 GetDisplayId(XID output, size_t output_index) {
+ int64 display_id;
+ if (chromeos::GetDisplayId(output, output_index, &display_id))
+ return display_id;
+ return gfx::Display::kInvalidDisplayID;
+}
+
+} // namespace
+
+DisplayChangeObserverX11::DisplayChangeObserverX11()
+ : xdisplay_(base::MessagePumpAuraX11::GetDefaultXDisplay()),
+ x_root_window_(DefaultRootWindow(xdisplay_)),
+ xrandr_event_base_(0) {
+ int error_base_ignored;
+ XRRQueryExtension(xdisplay_, &xrandr_event_base_, &error_base_ignored);
+
+ Shell::GetInstance()->AddShellObserver(this);
+}
+
+DisplayChangeObserverX11::~DisplayChangeObserverX11() {
+ Shell::GetInstance()->RemoveShellObserver(this);
+}
+
+chromeos::OutputState DisplayChangeObserverX11::GetStateForDisplayIds(
+ const std::vector<int64>& display_ids) const {
+ if (CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kAshForceMirrorMode)) {
+ return chromeos::STATE_DUAL_MIRROR;
+ }
+
+ CHECK_EQ(2U, display_ids.size());
+ DisplayIdPair pair = std::make_pair(display_ids[0], display_ids[1]);
+ DisplayLayout layout = Shell::GetInstance()->display_manager()->
+ layout_store()->GetRegisteredDisplayLayout(pair);
+ return layout.mirrored ?
+ chromeos::STATE_DUAL_MIRROR : chromeos::STATE_DUAL_EXTENDED;
+}
+
+bool DisplayChangeObserverX11::GetResolutionForDisplayId(int64 display_id,
+ int* width,
+ int* height) const {
+
+ gfx::Size resolution;
+ if (!Shell::GetInstance()->display_manager()->
+ GetSelectedResolutionForDisplayId(display_id, &resolution)) {
+ return false;
+ }
+
+ *width = resolution.width();
+ *height = resolution.height();
+ return true;
+}
+
+void DisplayChangeObserverX11::OnDisplayModeChanged() {
+ XRRScreenResources* screen_resources =
+ XRRGetScreenResources(xdisplay_, x_root_window_);
+ std::map<XID, XRRCrtcInfo*> crtc_info_map;
+
+ for (int c = 0; c < screen_resources->ncrtc; c++) {
+ XID crtc_id = screen_resources->crtcs[c];
+ XRRCrtcInfo *crtc_info =
+ XRRGetCrtcInfo(xdisplay_, screen_resources, crtc_id);
+ crtc_info_map[crtc_id] = crtc_info;
+ }
+
+ std::vector<DisplayInfo> displays;
+ std::set<int64> ids;
+ for (int output_index = 0; output_index < screen_resources->noutput;
+ output_index++) {
+ XID output = screen_resources->outputs[output_index];
+ XRROutputInfo *output_info =
+ XRRGetOutputInfo(xdisplay_, screen_resources, output);
+
+ const bool is_internal = chromeos::IsInternalOutputName(
+ std::string(output_info->name));
+
+ if (is_internal &&
+ gfx::Display::InternalDisplayId() == gfx::Display::kInvalidDisplayID) {
+ int64 id = GetDisplayId(output, output_index);
+ // Fallback to output index. crbug.com/180100
+ gfx::Display::SetInternalDisplayId(
+ id == gfx::Display::kInvalidDisplayID ? output_index : id);
+ }
+
+ if (output_info->connection != RR_Connected) {
+ XRRFreeOutputInfo(output_info);
+ continue;
+ }
+ const XRRCrtcInfo* crtc_info = crtc_info_map[output_info->crtc];
+ if (!crtc_info) {
+ LOG(WARNING) << "Crtc not found for output: output_index="
+ << output_index;
+ continue;
+ }
+ const XRRModeInfo* mode =
+ chromeos::FindModeInfo(screen_resources, crtc_info->mode);
+ if (!mode) {
+ LOG(WARNING) << "Could not find a mode for the output: output_index="
+ << output_index;
+ continue;
+ }
+
+ float device_scale_factor = 1.0f;
+ if (!ShouldIgnoreSize(output_info->mm_width, output_info->mm_height) &&
+ (kInchInMm * mode->width / output_info->mm_width) >
+ kHighDensityDPIThreshold) {
+ device_scale_factor = 2.0f;
+ }
+ gfx::Rect display_bounds(
+ crtc_info->x, crtc_info->y, mode->width, mode->height);
+
+ std::vector<Resolution> resolutions;
+ if (!is_internal)
+ resolutions = GetResolutionList(screen_resources, output_info);
+
+ XRRFreeOutputInfo(output_info);
+
+ std::string name = is_internal ?
+ l10n_util::GetStringUTF8(IDS_ASH_INTERNAL_DISPLAY_NAME) :
+ chromeos::GetDisplayName(output);
+ if (name.empty())
+ name = l10n_util::GetStringUTF8(IDS_ASH_STATUS_TRAY_UNKNOWN_DISPLAY_NAME);
+
+ bool has_overscan = false;
+ chromeos::GetOutputOverscanFlag(output, &has_overscan);
+
+ int64 id = GetDisplayId(output, output_index);
+
+ // If ID is invalid or there is an duplicate, just use output index.
+ if (id == gfx::Display::kInvalidDisplayID || ids.find(id) != ids.end())
+ id = output_index;
+ ids.insert(id);
+
+ displays.push_back(DisplayInfo(id, name, has_overscan));
+ displays.back().set_device_scale_factor(device_scale_factor);
+ displays.back().SetBounds(display_bounds);
+ displays.back().set_native(true);
+ displays.back().set_resolutions(resolutions);
+ }
+
+ // Free all allocated resources.
+ for (std::map<XID, XRRCrtcInfo*>::const_iterator iter = crtc_info_map.begin();
+ iter != crtc_info_map.end(); ++iter) {
+ XRRFreeCrtcInfo(iter->second);
+ }
+ XRRFreeScreenResources(screen_resources);
+
+ // DisplayManager can be null during the boot.
+ Shell::GetInstance()->display_manager()->OnNativeDisplaysChanged(displays);
+}
+
+void DisplayChangeObserverX11::OnAppTerminating() {
+#if defined(USE_ASH)
+ // Stop handling display configuration events once the shutdown
+ // process starts. crbug.com/177014.
+ Shell::GetInstance()->output_configurator()->Stop();
+#endif
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/display/display_change_observer_x11.h b/chromium/ash/display/display_change_observer_x11.h
new file mode 100644
index 00000000000..67811515b19
--- /dev/null
+++ b/chromium/ash/display/display_change_observer_x11.h
@@ -0,0 +1,57 @@
+// Copyright (c) 2012 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 ASH_DISPLAY_DISPLAY_CHANGE_OBSERVER_X11_H
+#define ASH_DISPLAY_DISPLAY_CHANGE_OBSERVER_X11_H
+
+#include <X11/Xlib.h>
+
+// Xlib.h defines RootWindow.
+#undef RootWindow
+
+#include "ash/ash_export.h"
+#include "ash/shell_observer.h"
+#include "base/basictypes.h"
+#include "chromeos/display/output_configurator.h"
+
+namespace ash {
+namespace internal {
+
+// An object that observes changes in display configuration and
+// update DisplayManagers.
+class DisplayChangeObserverX11
+ : public chromeos::OutputConfigurator::StateController,
+ public chromeos::OutputConfigurator::Observer,
+ public ShellObserver {
+ public:
+ DisplayChangeObserverX11();
+ virtual ~DisplayChangeObserverX11();
+
+ // chromeos::OutputConfigurator::StateController overrides:
+ virtual chromeos::OutputState GetStateForDisplayIds(
+ const std::vector<int64>& outputs) const OVERRIDE;
+ virtual bool GetResolutionForDisplayId(int64 display_id,
+ int* width,
+ int* height) const OVERRIDE;
+
+ // Overriden from chromeos::OutputConfigurator::Observer:
+ virtual void OnDisplayModeChanged() OVERRIDE;
+
+ // Overriden from ShellObserver:
+ virtual void OnAppTerminating() OVERRIDE;
+
+ private:
+ Display* xdisplay_;
+
+ ::Window x_root_window_;
+
+ int xrandr_event_base_;
+
+ DISALLOW_COPY_AND_ASSIGN(DisplayChangeObserverX11);
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_DISPLAY_AURA_DISPLAY_CHANGE_OBSERVER_X11_H
diff --git a/chromium/ash/display/display_controller.cc b/chromium/ash/display/display_controller.cc
new file mode 100644
index 00000000000..f0ac711e991
--- /dev/null
+++ b/chromium/ash/display/display_controller.cc
@@ -0,0 +1,834 @@
+// Copyright (c) 2012 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.
+
+#include "ash/display/display_controller.h"
+
+#include <algorithm>
+#include <cmath>
+#include <map>
+
+#include "ash/ash_switches.h"
+#include "ash/display/display_layout_store.h"
+#include "ash/display/display_manager.h"
+#include "ash/display/mirror_window_controller.h"
+#include "ash/display/root_window_transformers.h"
+#include "ash/host/root_window_host_factory.h"
+#include "ash/root_window_controller.h"
+#include "ash/screen_ash.h"
+#include "ash/shell.h"
+#include "ash/wm/coordinate_conversion.h"
+#include "ash/wm/property_util.h"
+#include "ash/wm/window_properties.h"
+#include "ash/wm/window_util.h"
+#include "base/command_line.h"
+#include "base/strings/stringprintf.h"
+#include "third_party/skia/include/utils/SkMatrix44.h"
+#include "ui/aura/client/activation_client.h"
+#include "ui/aura/client/capture_client.h"
+#include "ui/aura/client/cursor_client.h"
+#include "ui/aura/client/focus_client.h"
+#include "ui/aura/client/screen_position_client.h"
+#include "ui/aura/root_window.h"
+#include "ui/aura/root_window_transformer.h"
+#include "ui/aura/window.h"
+#include "ui/aura/window_property.h"
+#include "ui/aura/window_tracker.h"
+#include "ui/compositor/compositor.h"
+#include "ui/compositor/dip_util.h"
+#include "ui/gfx/display.h"
+#include "ui/gfx/screen.h"
+
+#if defined(OS_CHROMEOS)
+#include "base/chromeos/chromeos_version.h"
+#include "base/time/time.h"
+#if defined(USE_X11)
+#include "ash/display/output_configurator_animation.h"
+#include "chromeos/display/output_configurator.h"
+#include "ui/base/x/x11_util.h"
+
+// Including this at the bottom to avoid other
+// potential conflict with chrome headers.
+#include <X11/extensions/Xrandr.h>
+#undef RootWindow
+#endif // defined(USE_X11)
+#endif // defined(OS_CHROMEOS)
+
+DECLARE_WINDOW_PROPERTY_TYPE(int64);
+
+namespace ash {
+namespace {
+
+// Primary display stored in global object as it can be
+// accessed after Shell is deleted. A separate display instance is created
+// during the shutdown instead of always keeping two display instances
+// (one here and another one in display_manager) in sync, which is error prone.
+int64 primary_display_id = gfx::Display::kInvalidDisplayID;
+gfx::Display* primary_display_for_shutdown = NULL;
+// Keeps the number of displays during the shutdown after
+// ash::Shell:: is deleted.
+int num_displays_for_shutdown = -1;
+
+// Specifies how long the display change should have been disabled
+// after each display change operations.
+// |kCycleDisplayThrottleTimeoutMs| is set to be longer to avoid
+// changing the settings while the system is still configurating
+// displays. It will be overriden by |kAfterDisplayChangeThrottleTimeoutMs|
+// when the display change happens, so the actual timeout is much shorter.
+const int64 kAfterDisplayChangeThrottleTimeoutMs = 500;
+const int64 kCycleDisplayThrottleTimeoutMs = 4000;
+const int64 kSwapDisplayThrottleTimeoutMs = 500;
+
+internal::DisplayManager* GetDisplayManager() {
+ return Shell::GetInstance()->display_manager();
+}
+
+void SetDisplayPropertiesOnHostWindow(aura::RootWindow* root,
+ const gfx::Display& display) {
+ internal::DisplayInfo info =
+ GetDisplayManager()->GetDisplayInfo(display.id());
+#if defined(OS_CHROMEOS) && defined(USE_X11)
+ // Native window property (Atom in X11) that specifies the display's
+ // rotation, scale factor and if it's internal display. They are
+ // read and used by touchpad/mouse driver directly on X (contact
+ // adlr@ for more details on touchpad/mouse driver side). The value
+ // of the rotation is one of 0 (normal), 1 (90 degrees clockwise), 2
+ // (180 degree) or 3 (270 degrees clockwise). The value of the
+ // scale factor is in percent (100, 140, 200 etc).
+ const char kRotationProp[] = "_CHROME_DISPLAY_ROTATION";
+ const char kScaleFactorProp[] = "_CHROME_DISPLAY_SCALE_FACTOR";
+ const char kInternalProp[] = "_CHROME_DISPLAY_INTERNAL";
+ const char kCARDINAL[] = "CARDINAL";
+ int xrandr_rotation = RR_Rotate_0;
+ switch (info.rotation()) {
+ case gfx::Display::ROTATE_0:
+ xrandr_rotation = RR_Rotate_0;
+ break;
+ case gfx::Display::ROTATE_90:
+ xrandr_rotation = RR_Rotate_90;
+ break;
+ case gfx::Display::ROTATE_180:
+ xrandr_rotation = RR_Rotate_180;
+ break;
+ case gfx::Display::ROTATE_270:
+ xrandr_rotation = RR_Rotate_270;
+ break;
+ }
+
+ int internal = display.IsInternal() ? 1 : 0;
+ gfx::AcceleratedWidget xwindow = root->GetAcceleratedWidget();
+ ui::SetIntProperty(xwindow, kInternalProp, kCARDINAL, internal);
+ ui::SetIntProperty(xwindow, kRotationProp, kCARDINAL, xrandr_rotation);
+ ui::SetIntProperty(xwindow,
+ kScaleFactorProp,
+ kCARDINAL,
+ 100 * display.device_scale_factor());
+#endif
+ scoped_ptr<aura::RootWindowTransformer> transformer(
+ internal::CreateRootWindowTransformerForDisplay(root, display));
+ root->SetRootWindowTransformer(transformer.Pass());
+}
+
+} // namespace
+
+namespace internal {
+
+DEFINE_WINDOW_PROPERTY_KEY(int64, kDisplayIdKey,
+ gfx::Display::kInvalidDisplayID);
+
+// A utility class to store/restore focused/active window
+// when the display configuration has changed.
+class FocusActivationStore {
+ public:
+ FocusActivationStore()
+ : activation_client_(NULL),
+ capture_client_(NULL),
+ focus_client_(NULL),
+ focused_(NULL),
+ active_(NULL) {
+ }
+
+ void Store() {
+ if (!activation_client_) {
+ aura::RootWindow* root = Shell::GetPrimaryRootWindow();
+ activation_client_ = aura::client::GetActivationClient(root);
+ capture_client_ = aura::client::GetCaptureClient(root);
+ focus_client_ = aura::client::GetFocusClient(root);
+ }
+ focused_ = focus_client_->GetFocusedWindow();
+ if (focused_)
+ tracker_.Add(focused_);
+ active_ = activation_client_->GetActiveWindow();
+ if (active_ && focused_ != active_)
+ tracker_.Add(active_);
+
+ // Deactivate the window to close menu / bubble windows.
+ activation_client_->DeactivateWindow(active_);
+ // Release capture if any.
+ capture_client_->SetCapture(NULL);
+ // Clear the focused window if any. This is necessary because a
+ // window may be deleted when losing focus (fullscreen flash for
+ // example). If the focused window is still alive after move, it'll
+ // be re-focused below.
+ focus_client_->FocusWindow(NULL);
+ }
+
+ void Restore() {
+ // Restore focused or active window if it's still alive.
+ if (focused_ && tracker_.Contains(focused_)) {
+ focus_client_->FocusWindow(focused_);
+ } else if (active_ && tracker_.Contains(active_)) {
+ activation_client_->ActivateWindow(active_);
+ }
+ if (focused_)
+ tracker_.Remove(focused_);
+ if (active_)
+ tracker_.Remove(active_);
+ focused_ = NULL;
+ active_ = NULL;
+ }
+
+ private:
+ aura::client::ActivationClient* activation_client_;
+ aura::client::CaptureClient* capture_client_;
+ aura::client::FocusClient* focus_client_;
+ aura::WindowTracker tracker_;
+ aura::Window* focused_;
+ aura::Window* active_;
+
+ DISALLOW_COPY_AND_ASSIGN(FocusActivationStore);
+};
+
+} // namespace internal
+
+////////////////////////////////////////////////////////////////////////////////
+// DisplayChangeLimiter
+
+DisplayController::DisplayChangeLimiter::DisplayChangeLimiter()
+ : throttle_timeout_(base::Time::Now()) {
+}
+
+void DisplayController::DisplayChangeLimiter::SetThrottleTimeout(
+ int64 throttle_ms) {
+ throttle_timeout_ =
+ base::Time::Now() + base::TimeDelta::FromMilliseconds(throttle_ms);
+}
+
+bool DisplayController::DisplayChangeLimiter::IsThrottled() const {
+ return base::Time::Now() < throttle_timeout_;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// DisplayController
+
+DisplayController::DisplayController()
+ : primary_root_window_for_replace_(NULL),
+ focus_activation_store_(new internal::FocusActivationStore()),
+ mirror_window_controller_(new internal::MirrorWindowController) {
+ CommandLine* command_line = CommandLine::ForCurrentProcess();
+#if defined(OS_CHROMEOS)
+ if (!command_line->HasSwitch(switches::kAshDisableDisplayChangeLimiter) &&
+ base::chromeos::IsRunningOnChromeOS())
+ limiter_.reset(new DisplayChangeLimiter);
+#endif
+ // Reset primary display to make sure that tests don't use
+ // stale display info from previous tests.
+ primary_display_id = gfx::Display::kInvalidDisplayID;
+ delete primary_display_for_shutdown;
+ primary_display_for_shutdown = NULL;
+ num_displays_for_shutdown = -1;
+}
+
+DisplayController::~DisplayController() {
+ DCHECK(primary_display_for_shutdown);
+}
+
+void DisplayController::Start() {
+ Shell::GetScreen()->AddObserver(this);
+ Shell::GetInstance()->display_manager()->set_delegate(this);
+}
+
+void DisplayController::Shutdown() {
+ // Unset the display manager's delegate here because
+ // DisplayManager outlives DisplayController.
+ Shell::GetInstance()->display_manager()->set_delegate(NULL);
+
+ mirror_window_controller_.reset();
+
+ DCHECK(!primary_display_for_shutdown);
+ primary_display_for_shutdown = new gfx::Display(
+ GetDisplayManager()->GetDisplayForId(primary_display_id));
+ num_displays_for_shutdown = GetDisplayManager()->GetNumDisplays();
+
+ Shell::GetScreen()->RemoveObserver(this);
+ // Delete all root window controllers, which deletes root window
+ // from the last so that the primary root window gets deleted last.
+ for (std::map<int64, aura::RootWindow*>::const_reverse_iterator it =
+ root_windows_.rbegin(); it != root_windows_.rend(); ++it) {
+ internal::RootWindowController* controller =
+ GetRootWindowController(it->second);
+ DCHECK(controller);
+ delete controller;
+ }
+}
+
+// static
+const gfx::Display& DisplayController::GetPrimaryDisplay() {
+ DCHECK_NE(primary_display_id, gfx::Display::kInvalidDisplayID);
+ if (primary_display_for_shutdown)
+ return *primary_display_for_shutdown;
+ return GetDisplayManager()->GetDisplayForId(primary_display_id);
+}
+
+// static
+int DisplayController::GetNumDisplays() {
+ if (num_displays_for_shutdown >= 0)
+ return num_displays_for_shutdown;
+ return GetDisplayManager()->GetNumDisplays();
+}
+
+void DisplayController::InitPrimaryDisplay() {
+ const gfx::Display& primary_candidate =
+ GetDisplayManager()->GetPrimaryDisplayCandidate();
+ primary_display_id = primary_candidate.id();
+ AddRootWindowForDisplay(primary_candidate);
+}
+
+void DisplayController::InitSecondaryDisplays() {
+ internal::DisplayManager* display_manager = GetDisplayManager();
+ for (size_t i = 0; i < display_manager->GetNumDisplays(); ++i) {
+ const gfx::Display& display = display_manager->GetDisplayAt(i);
+ if (primary_display_id != display.id()) {
+ aura::RootWindow* root = AddRootWindowForDisplay(display);
+ Shell::GetInstance()->InitRootWindowForSecondaryDisplay(root);
+ }
+ }
+ UpdateHostWindowNames();
+}
+
+void DisplayController::AddObserver(Observer* observer) {
+ observers_.AddObserver(observer);
+}
+
+void DisplayController::RemoveObserver(Observer* observer) {
+ observers_.RemoveObserver(observer);
+}
+
+aura::RootWindow* DisplayController::GetPrimaryRootWindow() {
+ DCHECK(!root_windows_.empty());
+ return root_windows_[primary_display_id];
+}
+
+aura::RootWindow* DisplayController::GetRootWindowForDisplayId(int64 id) {
+ return root_windows_[id];
+}
+
+void DisplayController::CloseChildWindows() {
+ for (std::map<int64, aura::RootWindow*>::const_iterator it =
+ root_windows_.begin(); it != root_windows_.end(); ++it) {
+ aura::RootWindow* root_window = it->second;
+ internal::RootWindowController* controller =
+ GetRootWindowController(root_window);
+ if (controller) {
+ controller->CloseChildWindows();
+ } else {
+ while (!root_window->children().empty()) {
+ aura::Window* child = root_window->children()[0];
+ delete child;
+ }
+ }
+ }
+}
+
+std::vector<aura::RootWindow*> DisplayController::GetAllRootWindows() {
+ std::vector<aura::RootWindow*> windows;
+ for (std::map<int64, aura::RootWindow*>::const_iterator it =
+ root_windows_.begin(); it != root_windows_.end(); ++it) {
+ DCHECK(it->second);
+ if (GetRootWindowController(it->second))
+ windows.push_back(it->second);
+ }
+ return windows;
+}
+
+gfx::Insets DisplayController::GetOverscanInsets(int64 display_id) const {
+ return GetDisplayManager()->GetOverscanInsets(display_id);
+}
+
+void DisplayController::SetOverscanInsets(int64 display_id,
+ const gfx::Insets& insets_in_dip) {
+ GetDisplayManager()->SetOverscanInsets(display_id, insets_in_dip);
+}
+
+std::vector<internal::RootWindowController*>
+DisplayController::GetAllRootWindowControllers() {
+ std::vector<internal::RootWindowController*> controllers;
+ for (std::map<int64, aura::RootWindow*>::const_iterator it =
+ root_windows_.begin(); it != root_windows_.end(); ++it) {
+ internal::RootWindowController* controller =
+ GetRootWindowController(it->second);
+ if (controller)
+ controllers.push_back(controller);
+ }
+ return controllers;
+}
+
+void DisplayController::SetLayoutForCurrentDisplays(
+ const DisplayLayout& layout_relative_to_primary) {
+ DCHECK_EQ(2U, GetDisplayManager()->GetNumDisplays());
+ if (GetDisplayManager()->GetNumDisplays() < 2)
+ return;
+ const gfx::Display& primary = GetPrimaryDisplay();
+ const DisplayIdPair pair = GetDisplayManager()->GetCurrentDisplayIdPair();
+ // Invert if the primary was swapped.
+ DisplayLayout to_set = pair.first == primary.id() ?
+ layout_relative_to_primary : layout_relative_to_primary.Invert();
+
+ internal::DisplayLayoutStore* layout_store =
+ GetDisplayManager()->layout_store();
+ DisplayLayout current_layout =
+ layout_store->GetRegisteredDisplayLayout(pair);
+ if (to_set.position != current_layout.position ||
+ to_set.offset != current_layout.offset) {
+ to_set.primary_id = primary.id();
+ layout_store->RegisterLayoutForDisplayIdPair(
+ pair.first, pair.second, to_set);
+ PreDisplayConfigurationChange();
+ // TODO(oshima): Call UpdateDisplays instead.
+ UpdateDisplayBoundsForLayout();
+ // Primary's bounds stay the same. Just notify bounds change
+ // on the secondary.
+ Shell::GetInstance()->screen()->NotifyBoundsChanged(
+ ScreenAsh::GetSecondaryDisplay());
+ PostDisplayConfigurationChange();
+ }
+}
+
+void DisplayController::ToggleMirrorMode() {
+ internal::DisplayManager* display_manager = GetDisplayManager();
+ if (display_manager->num_connected_displays() <= 1)
+ return;
+
+ if (limiter_) {
+ if (limiter_->IsThrottled())
+ return;
+ limiter_->SetThrottleTimeout(kCycleDisplayThrottleTimeoutMs);
+ }
+#if defined(OS_CHROMEOS) && defined(USE_X11)
+ Shell* shell = Shell::GetInstance();
+ internal::OutputConfiguratorAnimation* animation =
+ shell->output_configurator_animation();
+ animation->StartFadeOutAnimation(base::Bind(
+ base::IgnoreResult(&internal::DisplayManager::SetMirrorMode),
+ base::Unretained(display_manager),
+ !display_manager->IsMirrored()));
+#endif
+}
+
+void DisplayController::SwapPrimaryDisplay() {
+ if (limiter_) {
+ if (limiter_->IsThrottled())
+ return;
+ limiter_->SetThrottleTimeout(kSwapDisplayThrottleTimeoutMs);
+ }
+
+ if (Shell::GetScreen()->GetNumDisplays() > 1) {
+#if defined(OS_CHROMEOS) && defined(USE_X11)
+ internal::OutputConfiguratorAnimation* animation =
+ Shell::GetInstance()->output_configurator_animation();
+ if (animation) {
+ animation->StartFadeOutAnimation(base::Bind(
+ &DisplayController::OnFadeOutForSwapDisplayFinished,
+ base::Unretained(this)));
+ } else {
+ SetPrimaryDisplay(ScreenAsh::GetSecondaryDisplay());
+ }
+#else
+ SetPrimaryDisplay(ScreenAsh::GetSecondaryDisplay());
+#endif
+ }
+}
+
+void DisplayController::SetPrimaryDisplayId(int64 id) {
+ DCHECK_NE(gfx::Display::kInvalidDisplayID, id);
+ if (id == gfx::Display::kInvalidDisplayID || primary_display_id == id)
+ return;
+
+ const gfx::Display& display = GetDisplayManager()->GetDisplayForId(id);
+ if (display.is_valid())
+ SetPrimaryDisplay(display);
+}
+
+void DisplayController::SetPrimaryDisplay(
+ const gfx::Display& new_primary_display) {
+ internal::DisplayManager* display_manager = GetDisplayManager();
+ DCHECK(new_primary_display.is_valid());
+ DCHECK(display_manager->IsActiveDisplay(new_primary_display));
+
+ if (!new_primary_display.is_valid() ||
+ !display_manager->IsActiveDisplay(new_primary_display)) {
+ LOG(ERROR) << "Invalid or non-existent display is requested:"
+ << new_primary_display.ToString();
+ return;
+ }
+
+ if (primary_display_id == new_primary_display.id() ||
+ root_windows_.size() < 2) {
+ return;
+ }
+
+ aura::RootWindow* non_primary_root = root_windows_[new_primary_display.id()];
+ LOG_IF(ERROR, !non_primary_root)
+ << "Unknown display is requested in SetPrimaryDisplay: id="
+ << new_primary_display.id();
+ if (!non_primary_root)
+ return;
+
+ gfx::Display old_primary_display = GetPrimaryDisplay();
+
+ // Swap root windows between current and new primary display.
+ aura::RootWindow* primary_root = root_windows_[primary_display_id];
+ DCHECK(primary_root);
+ DCHECK_NE(primary_root, non_primary_root);
+
+ root_windows_[new_primary_display.id()] = primary_root;
+ primary_root->SetProperty(internal::kDisplayIdKey, new_primary_display.id());
+
+ root_windows_[old_primary_display.id()] = non_primary_root;
+ non_primary_root->SetProperty(internal::kDisplayIdKey,
+ old_primary_display.id());
+
+ primary_display_id = new_primary_display.id();
+ GetDisplayManager()->layout_store()->UpdatePrimaryDisplayId(
+ display_manager->GetCurrentDisplayIdPair(), primary_display_id);
+
+ UpdateWorkAreaOfDisplayNearestWindow(
+ primary_root, old_primary_display.GetWorkAreaInsets());
+ UpdateWorkAreaOfDisplayNearestWindow(
+ non_primary_root, new_primary_display.GetWorkAreaInsets());
+
+ // Update the dispay manager with new display info.
+ std::vector<internal::DisplayInfo> display_info_list;
+ display_info_list.push_back(display_manager->GetDisplayInfo(
+ primary_display_id));
+ display_info_list.push_back(display_manager->GetDisplayInfo(
+ ScreenAsh::GetSecondaryDisplay().id()));
+ GetDisplayManager()->set_force_bounds_changed(true);
+ GetDisplayManager()->UpdateDisplays(display_info_list);
+ GetDisplayManager()->set_force_bounds_changed(false);
+}
+
+void DisplayController::EnsurePointerInDisplays() {
+ // If the mouse is currently on a display in native location,
+ // use the same native location. Otherwise find the display closest
+ // to the current cursor location in screen coordinates.
+
+ gfx::Point point_in_screen = Shell::GetScreen()->GetCursorScreenPoint();
+ gfx::Point target_location_in_native;
+ int64 closest_distance_squared = -1;
+ internal::DisplayManager* display_manager = GetDisplayManager();
+
+ aura::RootWindow* dst_root_window = NULL;
+ for (size_t i = 0; i < display_manager->GetNumDisplays(); ++i) {
+ const gfx::Display& display = display_manager->GetDisplayAt(i);
+ const internal::DisplayInfo display_info =
+ display_manager->GetDisplayInfo(display.id());
+ aura::RootWindow* root_window = GetRootWindowForDisplayId(display.id());
+ if (display_info.bounds_in_pixel().Contains(
+ cursor_location_in_native_coords_for_restore_)) {
+ dst_root_window = root_window;
+ target_location_in_native = cursor_location_in_native_coords_for_restore_;
+ break;
+ }
+ gfx::Point center = display.bounds().CenterPoint();
+ // Use the distance squared from the center of the dislay. This is not
+ // exactly "closest" display, but good enough to pick one
+ // appropriate (and there are at most two displays).
+ // We don't care about actual distance, only relative to other displays, so
+ // using the LengthSquared() is cheaper than Length().
+
+ int64 distance_squared = (center - point_in_screen).LengthSquared();
+ if (closest_distance_squared < 0 ||
+ closest_distance_squared > distance_squared) {
+ aura::RootWindow* root_window = GetRootWindowForDisplayId(display.id());
+ aura::client::ScreenPositionClient* client =
+ aura::client::GetScreenPositionClient(root_window);
+ client->ConvertPointFromScreen(root_window, &center);
+ root_window->ConvertPointToNativeScreen(&center);
+ dst_root_window = root_window;
+ target_location_in_native = center;
+ closest_distance_squared = distance_squared;
+ }
+ }
+ dst_root_window->ConvertPointFromNativeScreen(&target_location_in_native);
+ dst_root_window->MoveCursorTo(target_location_in_native);
+}
+
+bool DisplayController::UpdateWorkAreaOfDisplayNearestWindow(
+ const aura::Window* window,
+ const gfx::Insets& insets) {
+ const aura::RootWindow* root_window = window->GetRootWindow();
+ int64 id = root_window->GetProperty(internal::kDisplayIdKey);
+ // if id is |kInvaildDisplayID|, it's being deleted.
+ DCHECK(id != gfx::Display::kInvalidDisplayID);
+ return GetDisplayManager()->UpdateWorkAreaOfDisplay(id, insets);
+}
+
+const gfx::Display& DisplayController::GetDisplayNearestWindow(
+ const aura::Window* window) const {
+ if (!window)
+ return GetPrimaryDisplay();
+ const aura::RootWindow* root_window = window->GetRootWindow();
+ if (!root_window)
+ return GetPrimaryDisplay();
+ int64 id = root_window->GetProperty(internal::kDisplayIdKey);
+ // if id is |kInvaildDisplayID|, it's being deleted.
+ DCHECK(id != gfx::Display::kInvalidDisplayID);
+
+ internal::DisplayManager* display_manager = GetDisplayManager();
+ // RootWindow needs Display to determine its device scale factor.
+ // TODO(oshima): We don't need full display info for mirror
+ // window. Refactor so that RootWindow doesn't use it.
+ if (display_manager->mirrored_display().id() == id)
+ return display_manager->mirrored_display();
+
+ return display_manager->GetDisplayForId(id);
+}
+
+const gfx::Display& DisplayController::GetDisplayNearestPoint(
+ const gfx::Point& point) const {
+ // Fallback to the primary display if there is no root display containing
+ // the |point|.
+ const gfx::Display& display =
+ GetDisplayManager()->FindDisplayContainingPoint(point);
+ return display.is_valid() ? display : GetPrimaryDisplay();
+}
+
+const gfx::Display& DisplayController::GetDisplayMatching(
+ const gfx::Rect& rect) const {
+ if (rect.IsEmpty())
+ return GetDisplayNearestPoint(rect.origin());
+
+ int max_area = 0;
+ const gfx::Display* matching = NULL;
+ for (size_t i = 0; i < GetDisplayManager()->GetNumDisplays(); ++i) {
+ const gfx::Display& display = GetDisplayManager()->GetDisplayAt(i);
+ gfx::Rect intersect = gfx::IntersectRects(display.bounds(), rect);
+ int area = intersect.width() * intersect.height();
+ if (area > max_area) {
+ max_area = area;
+ matching = &display;
+ }
+ }
+ // Fallback to the primary display if there is no matching display.
+ return matching ? *matching : GetPrimaryDisplay();
+}
+
+void DisplayController::OnDisplayBoundsChanged(const gfx::Display& display) {
+ const internal::DisplayInfo& display_info =
+ GetDisplayManager()->GetDisplayInfo(display.id());
+ DCHECK(!display_info.bounds_in_pixel().IsEmpty());
+ aura::RootWindow* root = root_windows_[display.id()];
+ root->SetHostBounds(display_info.bounds_in_pixel());
+ SetDisplayPropertiesOnHostWindow(root, display);
+}
+
+void DisplayController::OnDisplayAdded(const gfx::Display& display) {
+ if (primary_root_window_for_replace_) {
+ DCHECK(root_windows_.empty());
+ primary_display_id = display.id();
+ root_windows_[display.id()] = primary_root_window_for_replace_;
+ primary_root_window_for_replace_->SetProperty(
+ internal::kDisplayIdKey, display.id());
+ primary_root_window_for_replace_ = NULL;
+ const internal::DisplayInfo& display_info =
+ GetDisplayManager()->GetDisplayInfo(display.id());
+ root_windows_[display.id()]->SetHostBounds(
+ display_info.bounds_in_pixel());
+ } else {
+ if (primary_display_id == gfx::Display::kInvalidDisplayID)
+ primary_display_id = display.id();
+ DCHECK(!root_windows_.empty());
+ aura::RootWindow* root = AddRootWindowForDisplay(display);
+ Shell::GetInstance()->InitRootWindowForSecondaryDisplay(root);
+ }
+}
+
+void DisplayController::OnDisplayRemoved(const gfx::Display& display) {
+ aura::RootWindow* root_to_delete = root_windows_[display.id()];
+ DCHECK(root_to_delete) << display.ToString();
+
+ // Display for root window will be deleted when the Primary RootWindow
+ // is deleted by the Shell.
+ root_windows_.erase(display.id());
+
+ // When the primary root window's display is removed, move the primary
+ // root to the other display.
+ if (primary_display_id == display.id()) {
+ // Temporarily store the primary root window in
+ // |primary_root_window_for_replace_| when replacing the display.
+ if (root_windows_.size() == 0) {
+ primary_display_id = gfx::Display::kInvalidDisplayID;
+ primary_root_window_for_replace_ = root_to_delete;
+ return;
+ }
+ DCHECK_EQ(1U, root_windows_.size());
+ primary_display_id = ScreenAsh::GetSecondaryDisplay().id();
+ aura::RootWindow* primary_root = root_to_delete;
+
+ // Delete the other root instead.
+ root_to_delete = root_windows_[primary_display_id];
+ root_to_delete->SetProperty(internal::kDisplayIdKey, display.id());
+
+ // Setup primary root.
+ root_windows_[primary_display_id] = primary_root;
+ primary_root->SetProperty(internal::kDisplayIdKey, primary_display_id);
+
+ OnDisplayBoundsChanged(
+ GetDisplayManager()->GetDisplayForId(primary_display_id));
+ }
+ internal::RootWindowController* controller =
+ GetRootWindowController(root_to_delete);
+ DCHECK(controller);
+ controller->MoveWindowsTo(GetPrimaryRootWindow());
+ // Delete most of root window related objects, but don't delete
+ // root window itself yet because the stack may be using it.
+ controller->Shutdown();
+ base::MessageLoop::current()->DeleteSoon(FROM_HERE, controller);
+}
+
+void DisplayController::OnRootWindowHostResized(const aura::RootWindow* root) {
+ internal::DisplayManager* display_manager = GetDisplayManager();
+ gfx::Display display = GetDisplayNearestWindow(root);
+ if (display_manager->UpdateDisplayBounds(
+ display.id(),
+ gfx::Rect(root->GetHostOrigin(), root->GetHostSize()))) {
+ mirror_window_controller_->UpdateWindow();
+ }
+}
+
+void DisplayController::CreateOrUpdateMirrorWindow(
+ const internal::DisplayInfo& info) {
+ mirror_window_controller_->UpdateWindow(info);
+}
+
+void DisplayController::CloseMirrorWindow() {
+ mirror_window_controller_->Close();
+}
+
+void DisplayController::PreDisplayConfigurationChange() {
+ FOR_EACH_OBSERVER(Observer, observers_, OnDisplayConfigurationChanging());
+ focus_activation_store_->Store();
+
+ gfx::Point point_in_screen = Shell::GetScreen()->GetCursorScreenPoint();
+ gfx::Display display =
+ Shell::GetScreen()->GetDisplayNearestPoint(point_in_screen);
+ aura::RootWindow* root_window = GetRootWindowForDisplayId(display.id());
+
+ aura::client::ScreenPositionClient* client =
+ aura::client::GetScreenPositionClient(root_window);
+ client->ConvertPointFromScreen(root_window, &point_in_screen);
+ root_window->ConvertPointToNativeScreen(&point_in_screen);
+ cursor_location_in_native_coords_for_restore_ = point_in_screen;
+}
+
+void DisplayController::PostDisplayConfigurationChange() {
+ if (limiter_)
+ limiter_->SetThrottleTimeout(kAfterDisplayChangeThrottleTimeoutMs);
+
+ focus_activation_store_->Restore();
+
+ internal::DisplayManager* display_manager = GetDisplayManager();
+ internal::DisplayLayoutStore* layout_store = display_manager->layout_store();
+ if (display_manager->num_connected_displays() > 1) {
+ DisplayIdPair pair = display_manager->GetCurrentDisplayIdPair();
+ layout_store->UpdateMirrorStatus(pair, display_manager->IsMirrored());
+ DisplayLayout layout = layout_store->GetRegisteredDisplayLayout(pair);
+
+ if (Shell::GetScreen()->GetNumDisplays() > 1 ) {
+ int64 primary_id = layout.primary_id;
+ SetPrimaryDisplayId(
+ primary_id == gfx::Display::kInvalidDisplayID ?
+ pair.first : primary_id);
+ // Update the primary_id in case the above call is
+ // ignored. Happens when a) default layout's primary id
+ // doesn't exist, or b) the primary_id has already been
+ // set to the same and didn't update it.
+ layout_store->UpdatePrimaryDisplayId(pair, GetPrimaryDisplay().id());
+ }
+ }
+ FOR_EACH_OBSERVER(Observer, observers_, OnDisplayConfigurationChanged());
+ UpdateHostWindowNames();
+ EnsurePointerInDisplays();
+}
+
+aura::RootWindow* DisplayController::AddRootWindowForDisplay(
+ const gfx::Display& display) {
+ static int root_window_count = 0;
+ const internal::DisplayInfo& display_info =
+ GetDisplayManager()->GetDisplayInfo(display.id());
+ const gfx::Rect& bounds_in_pixel = display_info.bounds_in_pixel();
+ aura::RootWindow::CreateParams params(bounds_in_pixel);
+ params.host = Shell::GetInstance()->root_window_host_factory()->
+ CreateRootWindowHost(bounds_in_pixel);
+ aura::RootWindow* root_window = new aura::RootWindow(params);
+ root_window->SetName(
+ base::StringPrintf("RootWindow-%d", root_window_count++));
+ root_window->compositor()->SetBackgroundColor(SK_ColorBLACK);
+ // No need to remove RootWindowObserver because
+ // the DisplayController object outlives RootWindow objects.
+ root_window->AddRootWindowObserver(this);
+ root_window->SetProperty(internal::kDisplayIdKey, display.id());
+ root_window->Init();
+
+ root_windows_[display.id()] = root_window;
+ SetDisplayPropertiesOnHostWindow(root_window, display);
+
+#if defined(OS_CHROMEOS)
+ static bool force_constrain_pointer_to_root =
+ CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kAshConstrainPointerToRoot);
+ if (base::chromeos::IsRunningOnChromeOS() || force_constrain_pointer_to_root)
+ root_window->ConfineCursorToWindow();
+#endif
+ return root_window;
+}
+
+void DisplayController::UpdateDisplayBoundsForLayout() {
+ internal::DisplayManager* display_manager = GetDisplayManager();
+ if (Shell::GetScreen()->GetNumDisplays() < 2 ||
+ display_manager->num_connected_displays() < 2) {
+ return;
+ }
+ DCHECK_EQ(2, Shell::GetScreen()->GetNumDisplays());
+
+ const DisplayLayout layout = display_manager->GetCurrentDisplayLayout();
+ display_manager->UpdateDisplayBoundsForLayoutById(
+ layout, GetPrimaryDisplay(),
+ ScreenAsh::GetSecondaryDisplay().id());
+}
+
+void DisplayController::OnFadeOutForSwapDisplayFinished() {
+#if defined(OS_CHROMEOS) && defined(USE_X11)
+ SetPrimaryDisplay(ScreenAsh::GetSecondaryDisplay());
+ Shell::GetInstance()->output_configurator_animation()->StartFadeInAnimation();
+#endif
+}
+
+void DisplayController::UpdateHostWindowNames() {
+#if defined(USE_X11)
+ // crbug.com/120229 - set the window title for the primary dislpay
+ // to "aura_root_0" so gtalk can find the primary root window to broadcast.
+ // TODO(jhorwich) Remove this once Chrome supports window-based broadcasting.
+ aura::RootWindow* primary = Shell::GetPrimaryRootWindow();
+ Shell::RootWindowList root_windows = Shell::GetAllRootWindows();
+ for (size_t i = 0; i < root_windows.size(); ++i) {
+ std::string name =
+ root_windows[i] == primary ? "aura_root_0" : "aura_root_x";
+ gfx::AcceleratedWidget xwindow = root_windows[i]->GetAcceleratedWidget();
+ XStoreName(ui::GetXDisplay(), xwindow, name.c_str());
+ }
+#endif
+}
+
+} // namespace ash
diff --git a/chromium/ash/display/display_controller.h b/chromium/ash/display/display_controller.h
new file mode 100644
index 00000000000..97620fc93b0
--- /dev/null
+++ b/chromium/ash/display/display_controller.h
@@ -0,0 +1,235 @@
+// Copyright (c) 2012 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 ASH_DISPLAY_DISPLAY_CONTROLLER_H_
+#define ASH_DISPLAY_DISPLAY_CONTROLLER_H_
+
+#include <map>
+#include <vector>
+
+#include "ash/ash_export.h"
+#include "ash/display/display_layout.h"
+#include "ash/display/display_manager.h"
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/gtest_prod_util.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/observer_list.h"
+#include "base/time/time.h"
+#include "ui/aura/root_window_observer.h"
+#include "ui/gfx/display_observer.h"
+#include "ui/gfx/point.h"
+
+namespace aura {
+class Display;
+class RootWindow;
+}
+
+namespace base {
+class Value;
+template <typename T> class JSONValueConverter;
+}
+
+namespace gfx {
+class Display;
+class Insets;
+}
+
+namespace ash {
+namespace internal {
+class DisplayInfo;
+class DisplayManager;
+class FocusActivationStore;
+class MirrorWindowController;
+class RootWindowController;
+}
+
+// DisplayController owns and maintains RootWindows for each attached
+// display, keeping them in sync with display configuration changes.
+class ASH_EXPORT DisplayController : public gfx::DisplayObserver,
+ public aura::RootWindowObserver,
+ public internal::DisplayManager::Delegate {
+ public:
+ class ASH_EXPORT Observer {
+ public:
+ // Invoked when the display configuration change is requested,
+ // but before the change is applied to aura/ash.
+ virtual void OnDisplayConfigurationChanging() {}
+
+ // Invoked when the all display configuration changes
+ // have been applied.
+ virtual void OnDisplayConfigurationChanged() {};
+
+ protected:
+ virtual ~Observer() {}
+ };
+
+ DisplayController();
+ virtual ~DisplayController();
+
+ void Start();
+ void Shutdown();
+
+ // Returns primary display. This is safe to use after ash::Shell is
+ // deleted.
+ static const gfx::Display& GetPrimaryDisplay();
+
+ // Returns the number of display. This is safe to use after
+ // ash::Shell is deleted.
+ static int GetNumDisplays();
+
+ internal::MirrorWindowController* mirror_window_controller() {
+ return mirror_window_controller_.get();
+ }
+
+ // Initializes primary display.
+ void InitPrimaryDisplay();
+
+ // Initialize secondary displays.
+ void InitSecondaryDisplays();
+
+ // Add/Remove observers.
+ void AddObserver(Observer* observer);
+ void RemoveObserver(Observer* observer);
+
+ // Returns the root window for primary display.
+ aura::RootWindow* GetPrimaryRootWindow();
+
+ // Returns the root window for |display_id|.
+ aura::RootWindow* GetRootWindowForDisplayId(int64 id);
+
+ // Toggle mirror mode.
+ void ToggleMirrorMode();
+
+ // Swap primary and secondary display.
+ void SwapPrimaryDisplay();
+
+ // Sets the ID of the primary display. If the display is not connected, it
+ // will switch the primary display when connected.
+ void SetPrimaryDisplayId(int64 id);
+
+ // Sets primary display. This re-assigns the current root
+ // window to given |display|.
+ void SetPrimaryDisplay(const gfx::Display& display);
+
+ // Closes all child windows in the all root windows.
+ void CloseChildWindows();
+
+ // Returns all root windows. In non extended desktop mode, this
+ // returns the primary root window only.
+ std::vector<aura::RootWindow*> GetAllRootWindows();
+
+ // Returns all oot window controllers. In non extended desktop
+ // mode, this return a RootWindowController for the primary root window only.
+ std::vector<internal::RootWindowController*> GetAllRootWindowControllers();
+
+ // Gets/Sets/Clears the overscan insets for the specified |display_id|. See
+ // display_manager.h for the details.
+ gfx::Insets GetOverscanInsets(int64 display_id) const;
+ void SetOverscanInsets(int64 display_id, const gfx::Insets& insets_in_dip);
+
+ // Sets the layout for the current display pair. The |layout| specifies
+ // the locaion of the secondary display relative to the primary.
+ void SetLayoutForCurrentDisplays(const DisplayLayout& layout);
+
+ // Checks if the mouse pointer is on one of displays, and moves to
+ // the center of the nearest display if it's outside of all displays.
+ void EnsurePointerInDisplays();
+
+ // Sets the work area's |insets| to the display assigned to |window|.
+ bool UpdateWorkAreaOfDisplayNearestWindow(const aura::Window* window,
+ const gfx::Insets& insets);
+
+ // Returns the display object nearest given |point|.
+ const gfx::Display& GetDisplayNearestPoint(
+ const gfx::Point& point) const;
+
+ // Returns the display object nearest given |window|.
+ const gfx::Display& GetDisplayNearestWindow(
+ const aura::Window* window) const;
+
+ // Returns the display that most closely intersects |match_rect|.
+ const gfx::Display& GetDisplayMatching(
+ const gfx::Rect& match_rect)const;
+
+ // aura::DisplayObserver overrides:
+ virtual void OnDisplayBoundsChanged(
+ const gfx::Display& display) OVERRIDE;
+ virtual void OnDisplayAdded(const gfx::Display& display) OVERRIDE;
+ virtual void OnDisplayRemoved(const gfx::Display& display) OVERRIDE;
+
+ // RootWindowObserver overrides:
+ virtual void OnRootWindowHostResized(const aura::RootWindow* root) OVERRIDE;
+
+ // aura::DisplayManager::Delegate overrides:
+ virtual void CreateOrUpdateMirrorWindow(
+ const internal::DisplayInfo& info) OVERRIDE;
+ virtual void CloseMirrorWindow() OVERRIDE;
+ virtual void PreDisplayConfigurationChange() OVERRIDE;
+ virtual void PostDisplayConfigurationChange() OVERRIDE;
+
+ private:
+ FRIEND_TEST_ALL_PREFIXES(DisplayControllerTest, BoundsUpdated);
+ FRIEND_TEST_ALL_PREFIXES(DisplayControllerTest, SecondaryDisplayLayout);
+ friend class internal::DisplayManager;
+ friend class internal::MirrorWindowController;
+
+ // Creates a root window for |display| and stores it in the |root_windows_|
+ // map.
+ aura::RootWindow* AddRootWindowForDisplay(const gfx::Display& display);
+
+ void UpdateDisplayBoundsForLayout();
+
+ void SetLayoutForDisplayIdPair(const DisplayIdPair& display_pair,
+ const DisplayLayout& layout);
+
+ void OnFadeOutForSwapDisplayFinished();
+
+ void UpdateHostWindowNames();
+
+ class DisplayChangeLimiter {
+ public:
+ DisplayChangeLimiter();
+
+ // Sets how long the throttling should last.
+ void SetThrottleTimeout(int64 throttle_ms);
+
+ bool IsThrottled() const;
+
+ private:
+ // The time when the throttling ends.
+ base::Time throttle_timeout_;
+
+ DISALLOW_COPY_AND_ASSIGN(DisplayChangeLimiter);
+ };
+
+ // The limiter to throttle how fast a user can
+ // change the display configuration.
+ scoped_ptr<DisplayChangeLimiter> limiter_;
+
+ // The mapping from display ID to its root window.
+ std::map<int64, aura::RootWindow*> root_windows_;
+
+ ObserverList<Observer> observers_;
+
+ // Store the primary root window temporarily while replacing
+ // display.
+ aura::RootWindow* primary_root_window_for_replace_;
+
+ scoped_ptr<internal::FocusActivationStore> focus_activation_store_;
+
+
+ scoped_ptr<internal::MirrorWindowController> mirror_window_controller_;
+
+ // Stores the curent cursor location (in native coordinates) used to
+ // restore the cursor location when display configuration
+ // changed.
+ gfx::Point cursor_location_in_native_coords_for_restore_;
+
+ DISALLOW_COPY_AND_ASSIGN(DisplayController);
+};
+
+} // namespace ash
+
+#endif // ASH_DISPLAY_DISPLAY_CONTROLLER_H_
diff --git a/chromium/ash/display/display_controller_unittest.cc b/chromium/ash/display/display_controller_unittest.cc
new file mode 100644
index 00000000000..7e6a0480ec0
--- /dev/null
+++ b/chromium/ash/display/display_controller_unittest.cc
@@ -0,0 +1,1036 @@
+// Copyright (c) 2012 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.
+
+#include "ash/display/display_controller.h"
+
+#include "ash/display/display_info.h"
+#include "ash/display/display_layout_store.h"
+#include "ash/display/display_manager.h"
+#include "ash/launcher/launcher.h"
+#include "ash/screen_ash.h"
+#include "ash/shelf/shelf_widget.h"
+#include "ash/shell.h"
+#include "ash/test/ash_test_base.h"
+#include "ash/test/cursor_manager_test_api.h"
+#include "ui/aura/env.h"
+#include "ui/aura/root_window.h"
+#include "ui/aura/test/event_generator.h"
+#include "ui/aura/window_tracker.h"
+#include "ui/base/events/event_handler.h"
+#include "ui/gfx/display.h"
+#include "ui/gfx/screen.h"
+#include "ui/views/widget/widget.h"
+
+#if defined(USE_X11)
+#include "ui/base/x/x11_util.h"
+#include <X11/Xlib.h>
+#undef RootWindow
+#endif
+
+namespace ash {
+namespace {
+
+const char kDesktopBackgroundView[] = "DesktopBackgroundView";
+
+class TestObserver : public DisplayController::Observer,
+ public gfx::DisplayObserver {
+ public:
+ TestObserver()
+ : changing_count_(0),
+ changed_count_(0),
+ bounds_changed_count_(0),
+ changed_display_id_(0) {
+ Shell::GetInstance()->display_controller()->AddObserver(this);
+ Shell::GetScreen()->AddObserver(this);
+ }
+
+ virtual ~TestObserver() {
+ Shell::GetInstance()->display_controller()->RemoveObserver(this);
+ Shell::GetScreen()->RemoveObserver(this);
+ }
+
+ // Overridden from DisplayController::Observer
+ virtual void OnDisplayConfigurationChanging() OVERRIDE {
+ ++changing_count_;
+ }
+ virtual void OnDisplayConfigurationChanged() OVERRIDE {
+ ++changed_count_;
+ }
+
+ // Overrideen from gfx::DisplayObserver
+ virtual void OnDisplayBoundsChanged(const gfx::Display& display) OVERRIDE {
+ changed_display_id_ = display.id();
+ bounds_changed_count_ ++;
+ }
+ virtual void OnDisplayAdded(const gfx::Display& new_display) OVERRIDE {
+ }
+ virtual void OnDisplayRemoved(const gfx::Display& old_display) OVERRIDE {
+ }
+
+ int CountAndReset() {
+ EXPECT_EQ(changing_count_, changed_count_);
+ int count = changing_count_;
+ changing_count_ = changed_count_ = 0;
+ return count;
+ }
+
+ int64 GetBoundsChangedCountAndReset() {
+ int count = bounds_changed_count_;
+ bounds_changed_count_ = 0;
+ return count;
+ }
+
+ int64 GetChangedDisplayIdAndReset() {
+ int64 id = changed_display_id_;
+ changed_display_id_ = 0;
+ return id;
+ }
+
+ private:
+ int changing_count_;
+ int changed_count_;
+
+ int bounds_changed_count_;
+ int64 changed_display_id_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestObserver);
+};
+
+gfx::Display GetPrimaryDisplay() {
+ return Shell::GetScreen()->GetDisplayNearestWindow(
+ Shell::GetAllRootWindows()[0]);
+}
+
+gfx::Display GetSecondaryDisplay() {
+ return Shell::GetScreen()->GetDisplayNearestWindow(
+ Shell::GetAllRootWindows()[1]);
+}
+
+void SetSecondaryDisplayLayoutAndOffset(DisplayLayout::Position position,
+ int offset) {
+ DisplayLayout layout(position, offset);
+ ASSERT_GT(Shell::GetScreen()->GetNumDisplays(), 1);
+ Shell::GetInstance()->display_controller()->
+ SetLayoutForCurrentDisplays(layout);
+}
+
+void SetSecondaryDisplayLayout(DisplayLayout::Position position) {
+ SetSecondaryDisplayLayoutAndOffset(position, 0);
+}
+
+void SetDefaultDisplayLayout(DisplayLayout::Position position) {
+ Shell::GetInstance()->display_manager()->layout_store()->
+ SetDefaultDisplayLayout(DisplayLayout(position, 0));
+}
+
+class DisplayControllerShutdownTest : public test::AshTestBase {
+ public:
+ virtual void TearDown() OVERRIDE {
+ test::AshTestBase::TearDown();
+ if (!SupportsMultipleDisplays())
+ return;
+
+ // Make sure that primary display is accessible after shutdown.
+ gfx::Display primary = Shell::GetScreen()->GetPrimaryDisplay();
+ EXPECT_EQ("0,0 444x333", primary.bounds().ToString());
+ EXPECT_EQ(2, Shell::GetScreen()->GetNumDisplays());
+ }
+};
+
+class TestEventHandler : public ui::EventHandler {
+ public:
+ TestEventHandler() : target_root_(NULL),
+ touch_radius_x_(0.0),
+ touch_radius_y_(0.0),
+ scroll_x_offset_(0.0),
+ scroll_y_offset_(0.0),
+ scroll_x_offset_ordinal_(0.0),
+ scroll_y_offset_ordinal_(0.0) {}
+ virtual ~TestEventHandler() {}
+
+ virtual void OnMouseEvent(ui::MouseEvent* event) OVERRIDE {
+ if (event->flags() & ui::EF_IS_SYNTHESIZED &&
+ event->type() != ui::ET_MOUSE_EXITED &&
+ event->type() != ui::ET_MOUSE_ENTERED) {
+ return;
+ }
+ aura::Window* target = static_cast<aura::Window*>(event->target());
+ mouse_location_ = event->root_location();
+ target_root_ = target->GetRootWindow();
+ event->StopPropagation();
+ }
+
+ virtual void OnTouchEvent(ui::TouchEvent* event) OVERRIDE {
+ aura::Window* target = static_cast<aura::Window*>(event->target());
+ // Only record when the target is the background which covers
+ // entire root window.
+ if (target->name() != kDesktopBackgroundView)
+ return;
+ touch_radius_x_ = event->radius_x();
+ touch_radius_y_ = event->radius_y();
+ event->StopPropagation();
+ }
+
+ virtual void OnScrollEvent(ui::ScrollEvent* event) OVERRIDE {
+ aura::Window* target = static_cast<aura::Window*>(event->target());
+ // Only record when the target is the background which covers
+ // entire root window.
+ if (target->name() != kDesktopBackgroundView)
+ return;
+
+ if (event->type() == ui::ET_SCROLL) {
+ scroll_x_offset_ = event->x_offset();
+ scroll_y_offset_ = event->y_offset();
+ scroll_x_offset_ordinal_ = event->x_offset_ordinal();
+ scroll_y_offset_ordinal_ = event->y_offset_ordinal();
+ }
+ event->StopPropagation();
+ }
+
+ std::string GetLocationAndReset() {
+ std::string result = mouse_location_.ToString();
+ mouse_location_.SetPoint(0, 0);
+ target_root_ = NULL;
+ return result;
+ }
+
+ float touch_radius_x() { return touch_radius_x_; }
+ float touch_radius_y() { return touch_radius_y_; }
+ float scroll_x_offset() { return scroll_x_offset_; }
+ float scroll_y_offset() { return scroll_y_offset_; }
+ float scroll_x_offset_ordinal() { return scroll_x_offset_ordinal_; }
+ float scroll_y_offset_ordinal() { return scroll_y_offset_ordinal_; }
+
+ private:
+ gfx::Point mouse_location_;
+ aura::RootWindow* target_root_;
+
+ float touch_radius_x_;
+ float touch_radius_y_;
+ float scroll_x_offset_;
+ float scroll_y_offset_;
+ float scroll_x_offset_ordinal_;
+ float scroll_y_offset_ordinal_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestEventHandler);
+};
+
+gfx::Display::Rotation GetStoredRotation(int64 id) {
+ return Shell::GetInstance()->display_manager()->GetDisplayInfo(id).rotation();
+}
+
+float GetStoredUIScale(int64 id) {
+ return Shell::GetInstance()->display_manager()->GetDisplayInfo(id).ui_scale();
+}
+
+#if defined(USE_X11)
+void GetPrimaryAndSeconary(aura::RootWindow** primary,
+ aura::RootWindow** secondary) {
+ *primary = Shell::GetPrimaryRootWindow();
+ Shell::RootWindowList root_windows = Shell::GetAllRootWindows();
+ *secondary = root_windows[0] == *primary ? root_windows[1] : root_windows[0];
+}
+
+std::string GetXWindowName(aura::RootWindow* window) {
+ char* name = NULL;
+ XFetchName(ui::GetXDisplay(), window->GetAcceleratedWidget(), &name);
+ std::string ret(name);
+ XFree(name);
+ return ret;
+}
+#endif
+
+} // namespace
+
+typedef test::AshTestBase DisplayControllerTest;
+
+TEST_F(DisplayControllerShutdownTest, Shutdown) {
+ if (!SupportsMultipleDisplays())
+ return;
+
+ UpdateDisplay("444x333, 200x200");
+}
+
+TEST_F(DisplayControllerTest, SecondaryDisplayLayout) {
+ if (!SupportsMultipleDisplays())
+ return;
+
+ TestObserver observer;
+ UpdateDisplay("500x500,400x400");
+ EXPECT_EQ(1, observer.CountAndReset()); // resize and add
+ EXPECT_EQ(1, observer.GetBoundsChangedCountAndReset());
+ gfx::Insets insets(5, 5, 5, 5);
+ int64 secondary_display_id = ScreenAsh::GetSecondaryDisplay().id();
+ Shell::GetInstance()->display_manager()->UpdateWorkAreaOfDisplay(
+ secondary_display_id, insets);
+
+ // Default layout is RIGHT.
+ EXPECT_EQ("0,0 500x500", GetPrimaryDisplay().bounds().ToString());
+ EXPECT_EQ("500,0 400x400", GetSecondaryDisplay().bounds().ToString());
+ EXPECT_EQ("505,5 390x390", GetSecondaryDisplay().work_area().ToString());
+
+ // Layout the secondary display to the bottom of the primary.
+ SetSecondaryDisplayLayout(DisplayLayout::BOTTOM);
+ EXPECT_EQ(1, observer.CountAndReset());
+ EXPECT_EQ(1, observer.GetBoundsChangedCountAndReset());
+ EXPECT_EQ(secondary_display_id, observer.GetChangedDisplayIdAndReset());
+ EXPECT_EQ("0,0 500x500", GetPrimaryDisplay().bounds().ToString());
+ EXPECT_EQ("0,500 400x400", GetSecondaryDisplay().bounds().ToString());
+ EXPECT_EQ("5,505 390x390", GetSecondaryDisplay().work_area().ToString());
+
+ // Layout the secondary display to the left of the primary.
+ SetSecondaryDisplayLayout(DisplayLayout::LEFT);
+ EXPECT_EQ(1, observer.CountAndReset());
+ EXPECT_EQ(1, observer.GetBoundsChangedCountAndReset());
+ EXPECT_EQ(secondary_display_id, observer.GetChangedDisplayIdAndReset());
+ EXPECT_EQ("0,0 500x500", GetPrimaryDisplay().bounds().ToString());
+ EXPECT_EQ("-400,0 400x400", GetSecondaryDisplay().bounds().ToString());
+ EXPECT_EQ("-395,5 390x390", GetSecondaryDisplay().work_area().ToString());
+
+ // Layout the secondary display to the top of the primary.
+ SetSecondaryDisplayLayout(DisplayLayout::TOP);
+ EXPECT_EQ(1, observer.CountAndReset());
+ EXPECT_EQ(1, observer.GetBoundsChangedCountAndReset());
+ EXPECT_EQ(secondary_display_id, observer.GetChangedDisplayIdAndReset());
+ EXPECT_EQ("0,0 500x500", GetPrimaryDisplay().bounds().ToString());
+ EXPECT_EQ("0,-400 400x400", GetSecondaryDisplay().bounds().ToString());
+ EXPECT_EQ("5,-395 390x390", GetSecondaryDisplay().work_area().ToString());
+
+ // Layout to the right with an offset.
+ SetSecondaryDisplayLayoutAndOffset(DisplayLayout::RIGHT, 300);
+ EXPECT_EQ(1, observer.CountAndReset()); // resize and add
+ EXPECT_EQ(1, observer.GetBoundsChangedCountAndReset());
+ EXPECT_EQ(secondary_display_id, observer.GetChangedDisplayIdAndReset());
+ EXPECT_EQ("0,0 500x500", GetPrimaryDisplay().bounds().ToString());
+ EXPECT_EQ("500,300 400x400", GetSecondaryDisplay().bounds().ToString());
+
+ // Keep the minimum 100.
+ SetSecondaryDisplayLayoutAndOffset(DisplayLayout::RIGHT, 490);
+ EXPECT_EQ(1, observer.CountAndReset()); // resize and add
+ EXPECT_EQ(1, observer.GetBoundsChangedCountAndReset());
+ EXPECT_EQ(secondary_display_id, observer.GetChangedDisplayIdAndReset());
+ EXPECT_EQ("0,0 500x500", GetPrimaryDisplay().bounds().ToString());
+ EXPECT_EQ("500,400 400x400", GetSecondaryDisplay().bounds().ToString());
+
+ SetSecondaryDisplayLayoutAndOffset(DisplayLayout::RIGHT, -400);
+ EXPECT_EQ(secondary_display_id, observer.GetChangedDisplayIdAndReset());
+ EXPECT_EQ(1, observer.GetBoundsChangedCountAndReset());
+ EXPECT_EQ(1, observer.CountAndReset()); // resize and add
+ EXPECT_EQ("0,0 500x500", GetPrimaryDisplay().bounds().ToString());
+ EXPECT_EQ("500,-300 400x400", GetSecondaryDisplay().bounds().ToString());
+
+ // Layout to the bottom with an offset.
+ SetSecondaryDisplayLayoutAndOffset(DisplayLayout::BOTTOM, -200);
+ EXPECT_EQ(secondary_display_id, observer.GetChangedDisplayIdAndReset());
+ EXPECT_EQ(1, observer.GetBoundsChangedCountAndReset());
+ EXPECT_EQ(1, observer.CountAndReset()); // resize and add
+ EXPECT_EQ("0,0 500x500", GetPrimaryDisplay().bounds().ToString());
+ EXPECT_EQ("-200,500 400x400", GetSecondaryDisplay().bounds().ToString());
+
+ // Keep the minimum 100.
+ SetSecondaryDisplayLayoutAndOffset(DisplayLayout::BOTTOM, 490);
+ EXPECT_EQ(secondary_display_id, observer.GetChangedDisplayIdAndReset());
+ EXPECT_EQ(1, observer.GetBoundsChangedCountAndReset());
+ EXPECT_EQ(1, observer.CountAndReset()); // resize and add
+ EXPECT_EQ("0,0 500x500", GetPrimaryDisplay().bounds().ToString());
+ EXPECT_EQ("400,500 400x400", GetSecondaryDisplay().bounds().ToString());
+
+ SetSecondaryDisplayLayoutAndOffset(DisplayLayout::BOTTOM, -400);
+ EXPECT_EQ(secondary_display_id, observer.GetChangedDisplayIdAndReset());
+ EXPECT_EQ(1, observer.GetBoundsChangedCountAndReset());
+ EXPECT_EQ(1, observer.CountAndReset()); // resize and add
+ EXPECT_EQ("0,0 500x500", GetPrimaryDisplay().bounds().ToString());
+ EXPECT_EQ("-300,500 400x400", GetSecondaryDisplay().bounds().ToString());
+
+ // Setting the same layout shouldn't invoke observers.
+ SetSecondaryDisplayLayoutAndOffset(DisplayLayout::BOTTOM, -400);
+ EXPECT_EQ(0, observer.GetChangedDisplayIdAndReset());
+ EXPECT_EQ(0, observer.GetBoundsChangedCountAndReset());
+ EXPECT_EQ(0, observer.CountAndReset()); // resize and add
+ EXPECT_EQ("0,0 500x500", GetPrimaryDisplay().bounds().ToString());
+ EXPECT_EQ("-300,500 400x400", GetSecondaryDisplay().bounds().ToString());
+}
+
+TEST_F(DisplayControllerTest, BoundsUpdated) {
+ if (!SupportsMultipleDisplays())
+ return;
+
+ TestObserver observer;
+ SetDefaultDisplayLayout(DisplayLayout::BOTTOM);
+ UpdateDisplay("200x200,300x300"); // layout, resize and add.
+ EXPECT_EQ(1, observer.CountAndReset());
+
+ internal::DisplayManager* display_manager =
+ Shell::GetInstance()->display_manager();
+ gfx::Insets insets(5, 5, 5, 5);
+ display_manager->UpdateWorkAreaOfDisplay(
+ ScreenAsh::GetSecondaryDisplay().id(), insets);
+
+ EXPECT_EQ("0,0 200x200", GetPrimaryDisplay().bounds().ToString());
+ EXPECT_EQ("0,200 300x300", GetSecondaryDisplay().bounds().ToString());
+ EXPECT_EQ("5,205 290x290", GetSecondaryDisplay().work_area().ToString());
+
+ UpdateDisplay("400x400,200x200");
+ EXPECT_EQ(1, observer.CountAndReset()); // two resizes
+ EXPECT_EQ("0,0 400x400", GetPrimaryDisplay().bounds().ToString());
+ EXPECT_EQ("0,400 200x200", GetSecondaryDisplay().bounds().ToString());
+
+ UpdateDisplay("400x400,300x300");
+ EXPECT_EQ(1, observer.CountAndReset());
+ EXPECT_EQ("0,0 400x400", GetPrimaryDisplay().bounds().ToString());
+ EXPECT_EQ("0,400 300x300", GetSecondaryDisplay().bounds().ToString());
+
+ UpdateDisplay("400x400");
+ EXPECT_EQ(1, observer.CountAndReset());
+ EXPECT_EQ("0,0 400x400", GetPrimaryDisplay().bounds().ToString());
+ EXPECT_EQ(1, Shell::GetScreen()->GetNumDisplays());
+
+ UpdateDisplay("400x500*2,300x300");
+ EXPECT_EQ(1, observer.CountAndReset());
+ ASSERT_EQ(2, Shell::GetScreen()->GetNumDisplays());
+ EXPECT_EQ("0,0 200x250", GetPrimaryDisplay().bounds().ToString());
+ EXPECT_EQ("0,250 300x300", GetSecondaryDisplay().bounds().ToString());
+
+ // No change
+ UpdateDisplay("400x500*2,300x300");
+ EXPECT_EQ(0, observer.CountAndReset());
+
+ // Rotation
+ int64 primary_id = GetPrimaryDisplay().id();
+ display_manager->SetDisplayRotation(primary_id, gfx::Display::ROTATE_90);
+ EXPECT_EQ(1, observer.CountAndReset());
+ display_manager->SetDisplayRotation(primary_id, gfx::Display::ROTATE_90);
+ EXPECT_EQ(0, observer.CountAndReset());
+
+ // UI scale is eanbled only on internal display.
+ int64 secondary_id = GetSecondaryDisplay().id();
+ gfx::Display::SetInternalDisplayId(secondary_id);
+ display_manager->SetDisplayUIScale(secondary_id, 1.125f);
+ EXPECT_EQ(1, observer.CountAndReset());
+ display_manager->SetDisplayUIScale(secondary_id, 1.125f);
+ EXPECT_EQ(0, observer.CountAndReset());
+ display_manager->SetDisplayUIScale(primary_id, 1.125f);
+ EXPECT_EQ(0, observer.CountAndReset());
+ display_manager->SetDisplayUIScale(primary_id, 1.125f);
+ EXPECT_EQ(0, observer.CountAndReset());
+}
+
+TEST_F(DisplayControllerTest, InvertLayout) {
+ EXPECT_EQ("left, 0",
+ DisplayLayout(DisplayLayout::RIGHT, 0).Invert().ToString());
+ EXPECT_EQ("left, -100",
+ DisplayLayout(DisplayLayout::RIGHT, 100).Invert().ToString());
+ EXPECT_EQ("left, 50",
+ DisplayLayout(DisplayLayout::RIGHT, -50).Invert().ToString());
+
+ EXPECT_EQ("right, 0",
+ DisplayLayout(DisplayLayout::LEFT, 0).Invert().ToString());
+ EXPECT_EQ("right, -90",
+ DisplayLayout(DisplayLayout::LEFT, 90).Invert().ToString());
+ EXPECT_EQ("right, 60",
+ DisplayLayout(DisplayLayout::LEFT, -60).Invert().ToString());
+
+ EXPECT_EQ("bottom, 0",
+ DisplayLayout(DisplayLayout::TOP, 0).Invert().ToString());
+ EXPECT_EQ("bottom, -80",
+ DisplayLayout(DisplayLayout::TOP, 80).Invert().ToString());
+ EXPECT_EQ("bottom, 70",
+ DisplayLayout(DisplayLayout::TOP, -70).Invert().ToString());
+
+ EXPECT_EQ("top, 0",
+ DisplayLayout(DisplayLayout::BOTTOM, 0).Invert().ToString());
+ EXPECT_EQ("top, -70",
+ DisplayLayout(DisplayLayout::BOTTOM, 70).Invert().ToString());
+ EXPECT_EQ("top, 80",
+ DisplayLayout(DisplayLayout::BOTTOM, -80).Invert().ToString());
+}
+
+TEST_F(DisplayControllerTest, SwapPrimary) {
+ if (!SupportsMultipleDisplays())
+ return;
+
+ DisplayController* display_controller =
+ Shell::GetInstance()->display_controller();
+ internal::DisplayManager* display_manager =
+ Shell::GetInstance()->display_manager();
+
+ UpdateDisplay("200x200,300x300");
+ gfx::Display primary_display = Shell::GetScreen()->GetPrimaryDisplay();
+ gfx::Display secondary_display = ScreenAsh::GetSecondaryDisplay();
+
+ DisplayLayout display_layout(DisplayLayout::RIGHT, 50);
+ display_controller->SetLayoutForCurrentDisplays(display_layout);
+
+ EXPECT_NE(primary_display.id(), secondary_display.id());
+ aura::RootWindow* primary_root =
+ display_controller->GetRootWindowForDisplayId(primary_display.id());
+ aura::RootWindow* secondary_root =
+ display_controller->GetRootWindowForDisplayId(secondary_display.id());
+ EXPECT_NE(primary_root, secondary_root);
+ aura::Window* launcher_window =
+ Launcher::ForPrimaryDisplay()->shelf_widget()->GetNativeView();
+ EXPECT_TRUE(primary_root->Contains(launcher_window));
+ EXPECT_FALSE(secondary_root->Contains(launcher_window));
+ EXPECT_EQ(primary_display.id(),
+ Shell::GetScreen()->GetDisplayNearestPoint(
+ gfx::Point(-100, -100)).id());
+ EXPECT_EQ(primary_display.id(),
+ Shell::GetScreen()->GetDisplayNearestWindow(NULL).id());
+
+ EXPECT_EQ("0,0 200x200", primary_display.bounds().ToString());
+ EXPECT_EQ("0,0 200x152", primary_display.work_area().ToString());
+ EXPECT_EQ("200,0 300x300", secondary_display.bounds().ToString());
+ EXPECT_EQ("200,0 300x252", secondary_display.work_area().ToString());
+ EXPECT_EQ("right, 50",
+ display_manager->GetCurrentDisplayLayout().ToString());
+
+ // Switch primary and secondary
+ display_controller->SetPrimaryDisplay(secondary_display);
+ const DisplayLayout& inverted_layout =
+ display_manager->GetCurrentDisplayLayout();
+ EXPECT_EQ("left, -50", inverted_layout.ToString());
+
+ EXPECT_EQ(secondary_display.id(),
+ Shell::GetScreen()->GetPrimaryDisplay().id());
+ EXPECT_EQ(primary_display.id(), ScreenAsh::GetSecondaryDisplay().id());
+ EXPECT_EQ(secondary_display.id(),
+ Shell::GetScreen()->GetDisplayNearestPoint(
+ gfx::Point(-100, -100)).id());
+ EXPECT_EQ(secondary_display.id(),
+ Shell::GetScreen()->GetDisplayNearestWindow(NULL).id());
+
+ EXPECT_EQ(
+ primary_root,
+ display_controller->GetRootWindowForDisplayId(secondary_display.id()));
+ EXPECT_EQ(
+ secondary_root,
+ display_controller->GetRootWindowForDisplayId(primary_display.id()));
+ EXPECT_TRUE(primary_root->Contains(launcher_window));
+ EXPECT_FALSE(secondary_root->Contains(launcher_window));
+
+ // Test if the bounds are correctly swapped.
+ gfx::Display swapped_primary = Shell::GetScreen()->GetPrimaryDisplay();
+ gfx::Display swapped_secondary = ScreenAsh::GetSecondaryDisplay();
+ EXPECT_EQ("0,0 300x300", swapped_primary.bounds().ToString());
+ EXPECT_EQ("0,0 300x252", swapped_primary.work_area().ToString());
+ EXPECT_EQ("-200,-50 200x200", swapped_secondary.bounds().ToString());
+
+ EXPECT_EQ("-200,-50 200x152", swapped_secondary.work_area().ToString());
+
+ aura::WindowTracker tracker;
+ tracker.Add(primary_root);
+ tracker.Add(secondary_root);
+
+ // Deleting 2nd display should move the primary to original primary display.
+ UpdateDisplay("200x200");
+ RunAllPendingInMessageLoop(); // RootWindow is deleted in a posted task.
+ EXPECT_EQ(1, Shell::GetScreen()->GetNumDisplays());
+ EXPECT_EQ(primary_display.id(), Shell::GetScreen()->GetPrimaryDisplay().id());
+ EXPECT_EQ(primary_display.id(),
+ Shell::GetScreen()->GetDisplayNearestPoint(
+ gfx::Point(-100, -100)).id());
+ EXPECT_EQ(primary_display.id(),
+ Shell::GetScreen()->GetDisplayNearestWindow(NULL).id());
+ EXPECT_TRUE(tracker.Contains(primary_root));
+ EXPECT_FALSE(tracker.Contains(secondary_root));
+ EXPECT_TRUE(primary_root->Contains(launcher_window));
+}
+
+TEST_F(DisplayControllerTest, SwapPrimaryById) {
+ if (!SupportsMultipleDisplays())
+ return;
+
+ DisplayController* display_controller =
+ Shell::GetInstance()->display_controller();
+ internal::DisplayManager* display_manager =
+ Shell::GetInstance()->display_manager();
+
+ UpdateDisplay("200x200,300x300");
+ gfx::Display primary_display = Shell::GetScreen()->GetPrimaryDisplay();
+ gfx::Display secondary_display = ScreenAsh::GetSecondaryDisplay();
+
+ DisplayLayout display_layout(DisplayLayout::RIGHT, 50);
+ display_controller->SetLayoutForCurrentDisplays(display_layout);
+
+ EXPECT_NE(primary_display.id(), secondary_display.id());
+ aura::RootWindow* primary_root =
+ display_controller->GetRootWindowForDisplayId(primary_display.id());
+ aura::RootWindow* secondary_root =
+ display_controller->GetRootWindowForDisplayId(secondary_display.id());
+ aura::Window* launcher_window =
+ Launcher::ForPrimaryDisplay()->shelf_widget()->GetNativeView();
+ EXPECT_TRUE(primary_root->Contains(launcher_window));
+ EXPECT_FALSE(secondary_root->Contains(launcher_window));
+ EXPECT_NE(primary_root, secondary_root);
+ EXPECT_EQ(primary_display.id(),
+ Shell::GetScreen()->GetDisplayNearestPoint(
+ gfx::Point(-100, -100)).id());
+ EXPECT_EQ(primary_display.id(),
+ Shell::GetScreen()->GetDisplayNearestWindow(NULL).id());
+
+ // Switch primary and secondary by display ID.
+ TestObserver observer;
+ display_controller->SetPrimaryDisplayId(secondary_display.id());
+ EXPECT_EQ(secondary_display.id(),
+ Shell::GetScreen()->GetPrimaryDisplay().id());
+ EXPECT_EQ(primary_display.id(), ScreenAsh::GetSecondaryDisplay().id());
+ EXPECT_LT(0, observer.CountAndReset());
+
+ EXPECT_EQ(
+ primary_root,
+ display_controller->GetRootWindowForDisplayId(secondary_display.id()));
+ EXPECT_EQ(
+ secondary_root,
+ display_controller->GetRootWindowForDisplayId(primary_display.id()));
+ EXPECT_TRUE(primary_root->Contains(launcher_window));
+ EXPECT_FALSE(secondary_root->Contains(launcher_window));
+
+ const DisplayLayout& inverted_layout =
+ display_manager->GetCurrentDisplayLayout();
+
+ EXPECT_EQ("left, -50", inverted_layout.ToString());
+
+ // Calling the same ID don't do anything.
+ display_controller->SetPrimaryDisplayId(secondary_display.id());
+ EXPECT_EQ(0, observer.CountAndReset());
+
+ aura::WindowTracker tracker;
+ tracker.Add(primary_root);
+ tracker.Add(secondary_root);
+
+ // Deleting 2nd display should move the primary to original primary display.
+ UpdateDisplay("200x200");
+ RunAllPendingInMessageLoop(); // RootWindow is deleted in a posted task.
+ EXPECT_EQ(1, Shell::GetScreen()->GetNumDisplays());
+ EXPECT_EQ(primary_display.id(), Shell::GetScreen()->GetPrimaryDisplay().id());
+ EXPECT_EQ(primary_display.id(),
+ Shell::GetScreen()->GetDisplayNearestPoint(
+ gfx::Point(-100, -100)).id());
+ EXPECT_EQ(primary_display.id(),
+ Shell::GetScreen()->GetDisplayNearestWindow(NULL).id());
+ EXPECT_TRUE(tracker.Contains(primary_root));
+ EXPECT_FALSE(tracker.Contains(secondary_root));
+ EXPECT_TRUE(primary_root->Contains(launcher_window));
+
+ // Adding 2nd display with the same ID. The 2nd display should become primary
+ // since secondary id is still stored as desirable_primary_id.
+ std::vector<internal::DisplayInfo> display_info_list;
+ display_info_list.push_back(
+ display_manager->GetDisplayInfo(primary_display.id()));
+ display_info_list.push_back(
+ display_manager->GetDisplayInfo(secondary_display.id()));
+ display_manager->OnNativeDisplaysChanged(display_info_list);
+
+ EXPECT_EQ(2, Shell::GetScreen()->GetNumDisplays());
+ EXPECT_EQ(secondary_display.id(),
+ Shell::GetScreen()->GetPrimaryDisplay().id());
+ EXPECT_EQ(primary_display.id(), ScreenAsh::GetSecondaryDisplay().id());
+ EXPECT_EQ(
+ primary_root,
+ display_controller->GetRootWindowForDisplayId(secondary_display.id()));
+ EXPECT_NE(
+ primary_root,
+ display_controller->GetRootWindowForDisplayId(primary_display.id()));
+ EXPECT_TRUE(primary_root->Contains(launcher_window));
+
+ // Deleting 2nd display and adding 2nd display with a different ID. The 2nd
+ // display shouldn't become primary.
+ UpdateDisplay("200x200");
+ internal::DisplayInfo third_display_info(
+ secondary_display.id() + 1, std::string(), false);
+ third_display_info.SetBounds(secondary_display.bounds());
+ ASSERT_NE(primary_display.id(), third_display_info.id());
+
+ const internal::DisplayInfo& primary_display_info =
+ display_manager->GetDisplayInfo(primary_display.id());
+ std::vector<internal::DisplayInfo> display_info_list2;
+ display_info_list2.push_back(primary_display_info);
+ display_info_list2.push_back(third_display_info);
+ display_manager->OnNativeDisplaysChanged(display_info_list2);
+ EXPECT_EQ(2, Shell::GetScreen()->GetNumDisplays());
+ EXPECT_EQ(primary_display.id(),
+ Shell::GetScreen()->GetPrimaryDisplay().id());
+ EXPECT_EQ(third_display_info.id(), ScreenAsh::GetSecondaryDisplay().id());
+ EXPECT_EQ(
+ primary_root,
+ display_controller->GetRootWindowForDisplayId(primary_display.id()));
+ EXPECT_NE(
+ primary_root,
+ display_controller->GetRootWindowForDisplayId(third_display_info.id()));
+ EXPECT_TRUE(primary_root->Contains(launcher_window));
+}
+
+TEST_F(DisplayControllerTest, CursorDeviceScaleFactorSwapPrimary) {
+ if (!SupportsMultipleDisplays())
+ return;
+
+ DisplayController* display_controller =
+ Shell::GetInstance()->display_controller();
+
+ UpdateDisplay("200x200,200x200*2");
+ gfx::Display primary_display = Shell::GetScreen()->GetPrimaryDisplay();
+ gfx::Display secondary_display = ScreenAsh::GetSecondaryDisplay();
+
+ aura::RootWindow* primary_root =
+ display_controller->GetRootWindowForDisplayId(primary_display.id());
+ aura::RootWindow* secondary_root =
+ display_controller->GetRootWindowForDisplayId(secondary_display.id());
+ EXPECT_NE(primary_root, secondary_root);
+
+ test::CursorManagerTestApi test_api(Shell::GetInstance()->cursor_manager());
+
+ EXPECT_EQ(1.0f,
+ primary_root->AsRootWindowHostDelegate()->GetDeviceScaleFactor());
+ primary_root->MoveCursorTo(gfx::Point(50, 50));
+ EXPECT_EQ(1.0f, test_api.GetDisplay().device_scale_factor());
+ EXPECT_EQ(2.0f,
+ secondary_root->AsRootWindowHostDelegate()->GetDeviceScaleFactor());
+ secondary_root->MoveCursorTo(gfx::Point(50, 50));
+ EXPECT_EQ(2.0f, test_api.GetDisplay().device_scale_factor());
+
+ // Switch primary and secondary
+ display_controller->SetPrimaryDisplay(secondary_display);
+
+ // Cursor's device scale factor should be updated accroding to the swap of
+ // primary and secondary.
+ EXPECT_EQ(1.0f,
+ secondary_root->AsRootWindowHostDelegate()->GetDeviceScaleFactor());
+ secondary_root->MoveCursorTo(gfx::Point(50, 50));
+ EXPECT_EQ(1.0f, test_api.GetDisplay().device_scale_factor());
+ primary_root->MoveCursorTo(gfx::Point(50, 50));
+ EXPECT_EQ(2.0f,
+ primary_root->AsRootWindowHostDelegate()->GetDeviceScaleFactor());
+ EXPECT_EQ(2.0f, test_api.GetDisplay().device_scale_factor());
+
+ // Deleting 2nd display.
+ UpdateDisplay("200x200");
+ RunAllPendingInMessageLoop(); // RootWindow is deleted in a posted task.
+
+ // Cursor's device scale factor should be updated even without moving cursor.
+ EXPECT_EQ(1.0f, test_api.GetDisplay().device_scale_factor());
+
+ primary_root->MoveCursorTo(gfx::Point(50, 50));
+ EXPECT_EQ(1.0f,
+ primary_root->AsRootWindowHostDelegate()->GetDeviceScaleFactor());
+ EXPECT_EQ(1.0f, test_api.GetDisplay().device_scale_factor());
+}
+
+#if defined(OS_WIN)
+// TODO(scottmg): RootWindow doesn't get resized on Windows
+// Ash. http://crbug.com/247916.
+#define MAYBE_UpdateDisplayWithHostOrigin DISABLED_UpdateDisplayWithHostOrigin
+#else
+#define MAYBE_UpdateDisplayWithHostOrigin UpdateDisplayWithHostOrigin
+#endif
+
+TEST_F(DisplayControllerTest, MAYBE_UpdateDisplayWithHostOrigin) {
+ UpdateDisplay("100x200,300x400");
+ ASSERT_EQ(2, Shell::GetScreen()->GetNumDisplays());
+ Shell::RootWindowList root_windows =
+ Shell::GetInstance()->GetAllRootWindows();
+ ASSERT_EQ(2U, root_windows.size());
+ EXPECT_EQ("1,1", root_windows[0]->GetHostOrigin().ToString());
+ EXPECT_EQ("100x200", root_windows[0]->GetHostSize().ToString());
+ // UpdateDisplay set the origin if it's not set.
+ EXPECT_NE("1,1", root_windows[1]->GetHostOrigin().ToString());
+ EXPECT_EQ("300x400", root_windows[1]->GetHostSize().ToString());
+
+ UpdateDisplay("100x200,200+300-300x400");
+ ASSERT_EQ(2, Shell::GetScreen()->GetNumDisplays());
+ EXPECT_EQ("0,0", root_windows[0]->GetHostOrigin().ToString());
+ EXPECT_EQ("100x200", root_windows[0]->GetHostSize().ToString());
+ EXPECT_EQ("200,300", root_windows[1]->GetHostOrigin().ToString());
+ EXPECT_EQ("300x400", root_windows[1]->GetHostSize().ToString());
+
+ UpdateDisplay("400+500-200x300,300x400");
+ ASSERT_EQ(2, Shell::GetScreen()->GetNumDisplays());
+ EXPECT_EQ("400,500", root_windows[0]->GetHostOrigin().ToString());
+ EXPECT_EQ("200x300", root_windows[0]->GetHostSize().ToString());
+ EXPECT_EQ("0,0", root_windows[1]->GetHostOrigin().ToString());
+ EXPECT_EQ("300x400", root_windows[1]->GetHostSize().ToString());
+
+ UpdateDisplay("100+200-100x200,300+500-200x300");
+ ASSERT_EQ(2, Shell::GetScreen()->GetNumDisplays());
+ EXPECT_EQ("100,200", root_windows[0]->GetHostOrigin().ToString());
+ EXPECT_EQ("100x200", root_windows[0]->GetHostSize().ToString());
+ EXPECT_EQ("300,500", root_windows[1]->GetHostOrigin().ToString());
+ EXPECT_EQ("200x300", root_windows[1]->GetHostSize().ToString());
+}
+
+TEST_F(DisplayControllerTest, OverscanInsets) {
+ if (!SupportsMultipleDisplays())
+ return;
+
+ DisplayController* display_controller =
+ Shell::GetInstance()->display_controller();
+ TestEventHandler event_handler;
+ Shell::GetInstance()->AddPreTargetHandler(&event_handler);
+
+ UpdateDisplay("120x200,300x400*2");
+ gfx::Display display1 = Shell::GetScreen()->GetPrimaryDisplay();
+ Shell::RootWindowList root_windows = Shell::GetAllRootWindows();
+
+ display_controller->SetOverscanInsets(display1.id(),
+ gfx::Insets(10, 15, 20, 25));
+ EXPECT_EQ("0,0 80x170", root_windows[0]->bounds().ToString());
+ EXPECT_EQ("150x200", root_windows[1]->bounds().size().ToString());
+ EXPECT_EQ("80,0 150x200",
+ ScreenAsh::GetSecondaryDisplay().bounds().ToString());
+
+ aura::test::EventGenerator generator(root_windows[0]);
+ generator.MoveMouseToInHost(20, 25);
+ EXPECT_EQ("5,15", event_handler.GetLocationAndReset());
+
+ display_controller->SetOverscanInsets(display1.id(), gfx::Insets());
+ EXPECT_EQ("0,0 120x200", root_windows[0]->bounds().ToString());
+ EXPECT_EQ("120,0 150x200",
+ ScreenAsh::GetSecondaryDisplay().bounds().ToString());
+
+ generator.MoveMouseToInHost(30, 20);
+ EXPECT_EQ("30,20", event_handler.GetLocationAndReset());
+
+ // Make sure the root window transformer uses correct scale
+ // factor when swapping display. Test crbug.com/253690.
+ UpdateDisplay("400x300*2,600x400/o");
+ root_windows = Shell::GetAllRootWindows();
+ gfx::Point point;
+ Shell::GetAllRootWindows()[1]->GetRootTransform().TransformPoint(point);
+ EXPECT_EQ("15,10", point.ToString());
+
+ display_controller->SwapPrimaryDisplay();
+ point.SetPoint(0, 0);
+ Shell::GetAllRootWindows()[1]->GetRootTransform().TransformPoint(point);
+ EXPECT_EQ("15,10", point.ToString());
+
+ Shell::GetInstance()->RemovePreTargetHandler(&event_handler);
+}
+
+TEST_F(DisplayControllerTest, Rotate) {
+ if (!SupportsMultipleDisplays())
+ return;
+
+ DisplayController* display_controller =
+ Shell::GetInstance()->display_controller();
+ internal::DisplayManager* display_manager =
+ Shell::GetInstance()->display_manager();
+ TestEventHandler event_handler;
+ Shell::GetInstance()->AddPreTargetHandler(&event_handler);
+
+ UpdateDisplay("120x200,300x400*2");
+ gfx::Display display1 = Shell::GetScreen()->GetPrimaryDisplay();
+ int64 display2_id = ScreenAsh::GetSecondaryDisplay().id();
+ Shell::RootWindowList root_windows = Shell::GetAllRootWindows();
+ aura::test::EventGenerator generator1(root_windows[0]);
+
+ EXPECT_EQ("120x200", root_windows[0]->bounds().size().ToString());
+ EXPECT_EQ("150x200", root_windows[1]->bounds().size().ToString());
+ EXPECT_EQ("120,0 150x200",
+ ScreenAsh::GetSecondaryDisplay().bounds().ToString());
+ generator1.MoveMouseToInHost(50, 40);
+ EXPECT_EQ("50,40", event_handler.GetLocationAndReset());
+ EXPECT_EQ(gfx::Display::ROTATE_0, GetStoredRotation(display1.id()));
+ EXPECT_EQ(gfx::Display::ROTATE_0, GetStoredRotation(display2_id));
+
+ display_manager->SetDisplayRotation(display1.id(),
+ gfx::Display::ROTATE_90);
+ EXPECT_EQ("200x120", root_windows[0]->bounds().size().ToString());
+ EXPECT_EQ("150x200", root_windows[1]->bounds().size().ToString());
+ EXPECT_EQ("200,0 150x200",
+ ScreenAsh::GetSecondaryDisplay().bounds().ToString());
+ generator1.MoveMouseToInHost(50, 40);
+ EXPECT_EQ("40,69", event_handler.GetLocationAndReset());
+ EXPECT_EQ(gfx::Display::ROTATE_90, GetStoredRotation(display1.id()));
+ EXPECT_EQ(gfx::Display::ROTATE_0, GetStoredRotation(display2_id));
+
+ DisplayLayout display_layout(DisplayLayout::BOTTOM, 50);
+ display_controller->SetLayoutForCurrentDisplays(display_layout);
+ EXPECT_EQ("50,120 150x200",
+ ScreenAsh::GetSecondaryDisplay().bounds().ToString());
+
+ display_manager->SetDisplayRotation(display2_id,
+ gfx::Display::ROTATE_270);
+ EXPECT_EQ("200x120", root_windows[0]->bounds().size().ToString());
+ EXPECT_EQ("200x150", root_windows[1]->bounds().size().ToString());
+ EXPECT_EQ("50,120 200x150",
+ ScreenAsh::GetSecondaryDisplay().bounds().ToString());
+ EXPECT_EQ(gfx::Display::ROTATE_90, GetStoredRotation(display1.id()));
+ EXPECT_EQ(gfx::Display::ROTATE_270, GetStoredRotation(display2_id));
+
+#if !defined(OS_WIN)
+ aura::test::EventGenerator generator2(root_windows[1]);
+ generator2.MoveMouseToInHost(50, 40);
+ EXPECT_EQ("179,25", event_handler.GetLocationAndReset());
+ display_manager->SetDisplayRotation(display1.id(),
+ gfx::Display::ROTATE_180);
+
+ EXPECT_EQ("120x200", root_windows[0]->bounds().size().ToString());
+ EXPECT_EQ("200x150", root_windows[1]->bounds().size().ToString());
+ // Dislay must share at least 100, so the x's offset becomes 20.
+ EXPECT_EQ("20,200 200x150",
+ ScreenAsh::GetSecondaryDisplay().bounds().ToString());
+ EXPECT_EQ(gfx::Display::ROTATE_180, GetStoredRotation(display1.id()));
+ EXPECT_EQ(gfx::Display::ROTATE_270, GetStoredRotation(display2_id));
+
+ generator1.MoveMouseToInHost(50, 40);
+ EXPECT_EQ("69,159", event_handler.GetLocationAndReset());
+#endif
+
+ Shell::GetInstance()->RemovePreTargetHandler(&event_handler);
+}
+
+TEST_F(DisplayControllerTest, ScaleRootWindow) {
+ if (!SupportsMultipleDisplays())
+ return;
+
+ TestEventHandler event_handler;
+ Shell::GetInstance()->AddPreTargetHandler(&event_handler);
+
+ UpdateDisplay("600x400*2@1.5,500x300");
+
+ gfx::Display display1 = Shell::GetScreen()->GetPrimaryDisplay();
+ gfx::Display::SetInternalDisplayId(display1.id());
+
+ gfx::Display display2 = ScreenAsh::GetSecondaryDisplay();
+ Shell::RootWindowList root_windows = Shell::GetAllRootWindows();
+ EXPECT_EQ("0,0 450x300", display1.bounds().ToString());
+ EXPECT_EQ("0,0 450x300", root_windows[0]->bounds().ToString());
+ EXPECT_EQ("450,0 500x300", display2.bounds().ToString());
+ EXPECT_EQ(1.5f, GetStoredUIScale(display1.id()));
+ EXPECT_EQ(1.0f, GetStoredUIScale(display2.id()));
+
+ aura::test::EventGenerator generator(root_windows[0]);
+ generator.MoveMouseToInHost(599, 200);
+ EXPECT_EQ("449,150", event_handler.GetLocationAndReset());
+
+ internal::DisplayManager* display_manager =
+ Shell::GetInstance()->display_manager();
+ display_manager->SetDisplayUIScale(display1.id(), 1.25f);
+ display1 = Shell::GetScreen()->GetPrimaryDisplay();
+ display2 = ScreenAsh::GetSecondaryDisplay();
+ EXPECT_EQ("0,0 375x250", display1.bounds().ToString());
+ EXPECT_EQ("0,0 375x250", root_windows[0]->bounds().ToString());
+ EXPECT_EQ("375,0 500x300", display2.bounds().ToString());
+ EXPECT_EQ(1.25f, GetStoredUIScale(display1.id()));
+ EXPECT_EQ(1.0f, GetStoredUIScale(display2.id()));
+
+ Shell::GetInstance()->RemovePreTargetHandler(&event_handler);
+}
+
+TEST_F(DisplayControllerTest, TouchScale) {
+ if (!SupportsMultipleDisplays())
+ return;
+
+ TestEventHandler event_handler;
+ Shell::GetInstance()->AddPreTargetHandler(&event_handler);
+
+ UpdateDisplay("200x200*2");
+ gfx::Display display = Shell::GetScreen()->GetPrimaryDisplay();
+ Shell::RootWindowList root_windows = Shell::GetAllRootWindows();
+ aura::RootWindow* root_window = root_windows[0];
+ aura::test::EventGenerator generator(root_window);
+
+ generator.PressMoveAndReleaseTouchTo(50, 50);
+ // Default test touches have radius_x/y = 1.0, with device scale
+ // factor = 2, the scaled radius_x/y should be 0.5.
+ EXPECT_EQ(0.5, event_handler.touch_radius_x());
+ EXPECT_EQ(0.5, event_handler.touch_radius_y());
+
+ generator.ScrollSequence(gfx::Point(0,0),
+ base::TimeDelta::FromMilliseconds(100),
+ 10.0, 1.0, 5, 1);
+
+ // With device scale factor = 2, ordinal_offset * 2 = offset.
+ EXPECT_EQ(event_handler.scroll_x_offset(),
+ event_handler.scroll_x_offset_ordinal() * 2);
+ EXPECT_EQ(event_handler.scroll_y_offset(),
+ event_handler.scroll_y_offset_ordinal() * 2);
+
+ Shell::GetInstance()->RemovePreTargetHandler(&event_handler);
+}
+
+TEST_F(DisplayControllerTest, ConvertHostToRootCoords) {
+ if (!SupportsMultipleDisplays())
+ return;
+
+ TestEventHandler event_handler;
+ Shell::GetInstance()->AddPreTargetHandler(&event_handler);
+
+ UpdateDisplay("600x400*2/r@1.5");
+
+ gfx::Display display1 = Shell::GetScreen()->GetPrimaryDisplay();
+ Shell::RootWindowList root_windows = Shell::GetAllRootWindows();
+ EXPECT_EQ("0,0 300x450", display1.bounds().ToString());
+ EXPECT_EQ("0,0 300x450", root_windows[0]->bounds().ToString());
+ EXPECT_EQ(1.5f, GetStoredUIScale(display1.id()));
+
+ aura::test::EventGenerator generator(root_windows[0]);
+ generator.MoveMouseToInHost(0, 0);
+ EXPECT_EQ("0,449", event_handler.GetLocationAndReset());
+ generator.MoveMouseToInHost(599, 0);
+ EXPECT_EQ("0,0", event_handler.GetLocationAndReset());
+ generator.MoveMouseToInHost(599, 399);
+ EXPECT_EQ("299,0", event_handler.GetLocationAndReset());
+ generator.MoveMouseToInHost(0, 399);
+ EXPECT_EQ("299,449", event_handler.GetLocationAndReset());
+
+ UpdateDisplay("600x400*2/u@1.5");
+ display1 = Shell::GetScreen()->GetPrimaryDisplay();
+ root_windows = Shell::GetAllRootWindows();
+ EXPECT_EQ("0,0 450x300", display1.bounds().ToString());
+ EXPECT_EQ("0,0 450x300", root_windows[0]->bounds().ToString());
+ EXPECT_EQ(1.5f, GetStoredUIScale(display1.id()));
+
+ generator.MoveMouseToInHost(0, 0);
+ EXPECT_EQ("449,299", event_handler.GetLocationAndReset());
+ generator.MoveMouseToInHost(599, 0);
+ EXPECT_EQ("0,299", event_handler.GetLocationAndReset());
+ generator.MoveMouseToInHost(599, 399);
+ EXPECT_EQ("0,0", event_handler.GetLocationAndReset());
+ generator.MoveMouseToInHost(0, 399);
+ EXPECT_EQ("449,0", event_handler.GetLocationAndReset());
+
+ UpdateDisplay("600x400*2/l@1.5");
+ display1 = Shell::GetScreen()->GetPrimaryDisplay();
+ root_windows = Shell::GetAllRootWindows();
+ EXPECT_EQ("0,0 300x450", display1.bounds().ToString());
+ EXPECT_EQ("0,0 300x450", root_windows[0]->bounds().ToString());
+ EXPECT_EQ(1.5f, GetStoredUIScale(display1.id()));
+
+ generator.MoveMouseToInHost(0, 0);
+ EXPECT_EQ("299,0", event_handler.GetLocationAndReset());
+ generator.MoveMouseToInHost(599, 0);
+ EXPECT_EQ("299,449", event_handler.GetLocationAndReset());
+ generator.MoveMouseToInHost(599, 399);
+ EXPECT_EQ("0,449", event_handler.GetLocationAndReset());
+ generator.MoveMouseToInHost(0, 399);
+ EXPECT_EQ("0,0", event_handler.GetLocationAndReset());
+
+ Shell::GetInstance()->RemovePreTargetHandler(&event_handler);
+}
+
+#if defined(USE_X11)
+TEST_F(DisplayControllerTest, XWidowNameForRootWindow) {
+ EXPECT_EQ("aura_root_0", GetXWindowName(Shell::GetPrimaryRootWindow()));
+
+ // Multiple display.
+ UpdateDisplay("200x200,300x300");
+ aura::RootWindow* primary, *secondary;
+ GetPrimaryAndSeconary(&primary, &secondary);
+ EXPECT_EQ("aura_root_0", GetXWindowName(primary));
+ EXPECT_EQ("aura_root_x", GetXWindowName(secondary));
+
+ // Swap primary.
+ primary = secondary = NULL;
+ Shell::GetInstance()->display_controller()->SwapPrimaryDisplay();
+ GetPrimaryAndSeconary(&primary, &secondary);
+ EXPECT_EQ("aura_root_0", GetXWindowName(primary));
+ EXPECT_EQ("aura_root_x", GetXWindowName(secondary));
+
+ // Switching back to single display.
+ UpdateDisplay("300x400");
+ EXPECT_EQ("aura_root_0", GetXWindowName(Shell::GetPrimaryRootWindow()));
+}
+#endif
+
+} // namespace ash
diff --git a/chromium/ash/display/display_error_observer.cc b/chromium/ash/display/display_error_observer.cc
new file mode 100644
index 00000000000..d4080a27b54
--- /dev/null
+++ b/chromium/ash/display/display_error_observer.cc
@@ -0,0 +1,89 @@
+// Copyright 2013 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.
+
+#include "ash/display/display_error_observer.h"
+
+#include "grit/ash_resources.h"
+#include "grit/ash_strings.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "ui/message_center/message_center.h"
+#include "ui/message_center/notification.h"
+#include "ui/message_center/notification_delegate.h"
+#include "ui/message_center/notification_list.h"
+
+using message_center::Notification;
+
+namespace ash {
+namespace internal {
+namespace {
+
+const char kDisplayErrorNotificationId[] = "chrome://settings/display/error";
+
+class DisplayErrorNotificationDelegate
+ : public message_center::NotificationDelegate {
+ public:
+ DisplayErrorNotificationDelegate() {}
+
+ // message_center::NotificationDelegate overrides:
+ virtual void Display() OVERRIDE {}
+ virtual void Error() OVERRIDE {}
+ virtual void Close(bool by_user) OVERRIDE {}
+ virtual bool HasClickedListener() OVERRIDE { return false; }
+ virtual void Click() OVERRIDE { }
+
+ protected:
+ virtual ~DisplayErrorNotificationDelegate() {}
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(DisplayErrorNotificationDelegate);
+};
+
+} // namespace
+
+DisplayErrorObserver::DisplayErrorObserver() {
+}
+
+DisplayErrorObserver::~DisplayErrorObserver() {
+}
+
+void DisplayErrorObserver::OnDisplayModeChangeFailed(
+ chromeos::OutputState new_state) {
+ // Always remove the notification to make sure the notification appears
+ // as a popup in any situation.
+ message_center::MessageCenter::Get()->RemoveNotification(
+ kDisplayErrorNotificationId, false /* by_user */);
+
+ int message_id = (new_state == chromeos::STATE_DUAL_MIRROR) ?
+ IDS_ASH_DISPLAY_FAILURE_ON_MIRRORING :
+ IDS_ASH_DISPLAY_FAILURE_ON_NON_MIRRORING;
+
+ ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance();
+ scoped_ptr<Notification> notification(new Notification(
+ message_center::NOTIFICATION_TYPE_SIMPLE,
+ kDisplayErrorNotificationId,
+ l10n_util::GetStringUTF16(message_id),
+ base::string16(), // message
+ bundle.GetImageNamed(IDR_AURA_UBER_TRAY_DISPLAY),
+ base::string16(), // display_source
+ std::string(), // extension_id
+ message_center::RichNotificationData(),
+ new DisplayErrorNotificationDelegate()));
+ message_center::MessageCenter::Get()->AddNotification(notification.Pass());
+}
+
+string16 DisplayErrorObserver::GetTitleOfDisplayErrorNotificationForTest() {
+ message_center::NotificationList::Notifications notifications =
+ message_center::MessageCenter::Get()->GetNotifications();
+ for (message_center::NotificationList::Notifications::const_iterator iter =
+ notifications.begin(); iter != notifications.end(); ++iter) {
+ if ((*iter)->id() == kDisplayErrorNotificationId)
+ return (*iter)->title();
+ }
+
+ return base::string16();
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/display/display_error_observer.h b/chromium/ash/display/display_error_observer.h
new file mode 100644
index 00000000000..5cba904fab0
--- /dev/null
+++ b/chromium/ash/display/display_error_observer.h
@@ -0,0 +1,39 @@
+// Copyright 2013 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 ASH_DISPLAY_DISPLAY_ERROR_OBSERVER_H_
+#define ASH_DISPLAY_DISPLAY_ERROR_OBSERVER_H_
+
+#include "ash/ash_export.h"
+#include "base/compiler_specific.h"
+#include "base/strings/string16.h"
+#include "chromeos/display/output_configurator.h"
+
+namespace ash {
+namespace internal {
+
+// The class to observe the output failures and shows the error dialog when
+// necessary.
+class ASH_EXPORT DisplayErrorObserver
+ : public chromeos::OutputConfigurator::Observer {
+ public:
+ DisplayErrorObserver();
+ virtual ~DisplayErrorObserver();
+
+ // chromeos::OutputConfigurator::Observer overrides:
+ virtual void OnDisplayModeChangeFailed(
+ chromeos::OutputState failed_new_state) OVERRIDE;
+
+ private:
+ friend class DisplayErrorObserverTest;
+
+ base::string16 GetTitleOfDisplayErrorNotificationForTest();
+
+ DISALLOW_COPY_AND_ASSIGN(DisplayErrorObserver);
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_DISPLAY_DISPLAY_ERROR_OBSERVER_H_
diff --git a/chromium/ash/display/display_error_observer_unittest.cc b/chromium/ash/display/display_error_observer_unittest.cc
new file mode 100644
index 00000000000..abe49076140
--- /dev/null
+++ b/chromium/ash/display/display_error_observer_unittest.cc
@@ -0,0 +1,85 @@
+// Copyright 2013 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.
+
+#include "ash/display/display_error_observer.h"
+
+#include "ash/shell.h"
+#include "ash/test/ash_test_base.h"
+#include "grit/ash_strings.h"
+#include "ui/aura/window.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/views/controls/label.h"
+#include "ui/views/view.h"
+#include "ui/views/widget/widget.h"
+
+namespace ash {
+namespace internal {
+
+class DisplayErrorObserverTest : public test::AshTestBase {
+ protected:
+ DisplayErrorObserverTest() {
+ }
+
+ virtual ~DisplayErrorObserverTest() {
+ }
+
+ virtual void SetUp() OVERRIDE {
+ test::AshTestBase::SetUp();
+ observer_.reset(new DisplayErrorObserver());
+ }
+
+ protected:
+ DisplayErrorObserver* observer() { return observer_.get(); }
+
+ base::string16 GetMessageContents() {
+ return observer_->GetTitleOfDisplayErrorNotificationForTest();
+ }
+
+ private:
+ scoped_ptr<DisplayErrorObserver> observer_;
+
+ DISALLOW_COPY_AND_ASSIGN(DisplayErrorObserverTest);
+};
+
+TEST_F(DisplayErrorObserverTest, Normal) {
+ if (!SupportsMultipleDisplays())
+ return;
+
+ UpdateDisplay("200x200,300x300");
+ observer()->OnDisplayModeChangeFailed(chromeos::STATE_DUAL_MIRROR);
+ EXPECT_EQ(l10n_util::GetStringUTF16(IDS_ASH_DISPLAY_FAILURE_ON_MIRRORING),
+ GetMessageContents());
+}
+
+TEST_F(DisplayErrorObserverTest, CallTwice) {
+ if (!SupportsMultipleDisplays())
+ return;
+
+ UpdateDisplay("200x200,300x300");
+ observer()->OnDisplayModeChangeFailed(chromeos::STATE_DUAL_MIRROR);
+ base::string16 message = GetMessageContents();
+ EXPECT_FALSE(message.empty());
+
+ observer()->OnDisplayModeChangeFailed(chromeos::STATE_DUAL_MIRROR);
+ base::string16 message2 = GetMessageContents();
+ EXPECT_FALSE(message2.empty());
+ EXPECT_EQ(message, message2);
+}
+
+TEST_F(DisplayErrorObserverTest, CallWithDifferentState) {
+ if (!SupportsMultipleDisplays())
+ return;
+
+ UpdateDisplay("200x200,300x300");
+ observer()->OnDisplayModeChangeFailed(chromeos::STATE_DUAL_MIRROR);
+ EXPECT_EQ(l10n_util::GetStringUTF16(IDS_ASH_DISPLAY_FAILURE_ON_MIRRORING),
+ GetMessageContents());
+
+ observer()->OnDisplayModeChangeFailed(chromeos::STATE_DUAL_EXTENDED);
+ EXPECT_EQ(l10n_util::GetStringUTF16(IDS_ASH_DISPLAY_FAILURE_ON_NON_MIRRORING),
+ GetMessageContents());
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/display/display_info.cc b/chromium/ash/display/display_info.cc
new file mode 100644
index 00000000000..5e89a0d97f6
--- /dev/null
+++ b/chromium/ash/display/display_info.cc
@@ -0,0 +1,252 @@
+// Copyright (c) 2013 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.
+
+#include <stdio.h>
+#include <string>
+#include <vector>
+
+#include "ash/display/display_info.h"
+#include "base/logging.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "ui/gfx/display.h"
+#include "ui/gfx/size_conversions.h"
+#include "ui/gfx/size_f.h"
+
+#if defined(OS_WIN)
+#include "ui/aura/root_window_host.h"
+#endif
+
+namespace ash {
+namespace internal {
+
+Resolution::Resolution(const gfx::Size& size, bool interlaced)
+ : size(size),
+ interlaced(interlaced) {
+}
+
+// satic
+DisplayInfo DisplayInfo::CreateFromSpec(const std::string& spec) {
+ return CreateFromSpecWithID(spec, gfx::Display::kInvalidDisplayID);
+}
+
+// static
+DisplayInfo DisplayInfo::CreateFromSpecWithID(const std::string& spec,
+ int64 id) {
+ // Default bounds for a display.
+ const int kDefaultHostWindowX = 200;
+ const int kDefaultHostWindowY = 200;
+ const int kDefaultHostWindowWidth = 1366;
+ const int kDefaultHostWindowHeight = 768;
+
+ // Use larger than max int to catch overflow early.
+ static int64 synthesized_display_id = 2200000000LL;
+
+#if defined(OS_WIN)
+ gfx::Rect bounds_in_pixel(aura::RootWindowHost::GetNativeScreenSize());
+#else
+ gfx::Rect bounds_in_pixel(kDefaultHostWindowX, kDefaultHostWindowY,
+ kDefaultHostWindowWidth, kDefaultHostWindowHeight);
+#endif
+ std::string main_spec = spec;
+
+ float ui_scale = 1.0f;
+ std::vector<std::string> parts;
+ if (Tokenize(main_spec, "@", &parts) == 2) {
+ double scale_in_double = 0;
+ if (base::StringToDouble(parts[1], &scale_in_double))
+ ui_scale = scale_in_double;
+ main_spec = parts[0];
+ }
+
+ size_t count = Tokenize(main_spec, "/", &parts);
+ gfx::Display::Rotation rotation(gfx::Display::ROTATE_0);
+ bool has_overscan = false;
+ if (count) {
+ main_spec = parts[0];
+ if (count >= 2) {
+ std::string options = parts[1];
+ for (size_t i = 0; i < options.size(); ++i) {
+ char c = options[i];
+ switch (c) {
+ case 'o':
+ has_overscan = true;
+ break;
+ case 'r': // rotate 90 degrees to 'right'.
+ rotation = gfx::Display::ROTATE_90;
+ break;
+ case 'u': // 180 degrees, 'u'pside-down.
+ rotation = gfx::Display::ROTATE_180;
+ break;
+ case 'l': // rotate 90 degrees to 'left'.
+ rotation = gfx::Display::ROTATE_270;
+ break;
+ }
+ }
+ }
+ }
+
+ int x = 0, y = 0, width, height;
+ float device_scale_factor = 1.0f;
+ if (sscanf(main_spec.c_str(), "%dx%d*%f",
+ &width, &height, &device_scale_factor) >= 2 ||
+ sscanf(main_spec.c_str(), "%d+%d-%dx%d*%f", &x, &y, &width, &height,
+ &device_scale_factor) >= 4) {
+ bounds_in_pixel.SetRect(x, y, width, height);
+ }
+
+ std::vector<Resolution> resolutions;
+ if (Tokenize(main_spec, "#", &parts) == 2) {
+ main_spec = parts[0];
+ std::string resolution_list = parts[1];
+ count = Tokenize(resolution_list, "|", &parts);
+ for (size_t i = 0; i < count; ++i) {
+ std::string resolution = parts[i];
+ int width, height;
+ if (sscanf(resolution.c_str(), "%dx%d", &width, &height) == 2)
+ resolutions.push_back(Resolution(gfx::Size(width, height), false));
+ }
+ }
+
+ if (id == gfx::Display::kInvalidDisplayID)
+ id = synthesized_display_id++;
+ DisplayInfo display_info(
+ id, base::StringPrintf("Display-%d", static_cast<int>(id)), has_overscan);
+ display_info.set_device_scale_factor(device_scale_factor);
+ display_info.set_rotation(rotation);
+ display_info.set_ui_scale(ui_scale);
+ display_info.SetBounds(bounds_in_pixel);
+ display_info.set_resolutions(resolutions);
+
+ // To test the overscan, it creates the default 5% overscan.
+ if (has_overscan) {
+ int width = bounds_in_pixel.width() / device_scale_factor / 40;
+ int height = bounds_in_pixel.height() / device_scale_factor / 40;
+ display_info.SetOverscanInsets(gfx::Insets(height, width, height, width));
+ display_info.UpdateDisplaySize();
+ }
+
+ DVLOG(1) << "DisplayInfoFromSpec info=" << display_info.ToString()
+ << ", spec=" << spec;
+ return display_info;
+}
+
+DisplayInfo::DisplayInfo()
+ : id_(gfx::Display::kInvalidDisplayID),
+ has_overscan_(false),
+ rotation_(gfx::Display::ROTATE_0),
+ device_scale_factor_(1.0f),
+ overscan_insets_in_dip_(0, 0, 0, 0),
+ ui_scale_(1.0f),
+ native_(false) {
+}
+
+DisplayInfo::DisplayInfo(int64 id,
+ const std::string& name,
+ bool has_overscan)
+ : id_(id),
+ name_(name),
+ has_overscan_(has_overscan),
+ rotation_(gfx::Display::ROTATE_0),
+ device_scale_factor_(1.0f),
+ overscan_insets_in_dip_(0, 0, 0, 0),
+ ui_scale_(1.0f),
+ native_(false) {
+}
+
+DisplayInfo::~DisplayInfo() {
+}
+
+void DisplayInfo::Copy(const DisplayInfo& native_info) {
+ DCHECK(id_ == native_info.id_);
+ name_ = native_info.name_;
+ has_overscan_ = native_info.has_overscan_;
+
+ DCHECK(!native_info.bounds_in_pixel_.IsEmpty());
+ bounds_in_pixel_ = native_info.bounds_in_pixel_;
+ size_in_pixel_ = native_info.size_in_pixel_;
+ device_scale_factor_ = native_info.device_scale_factor_;
+ resolutions_ = native_info.resolutions_;
+
+ // Copy overscan_insets_in_dip_ if it's not empty. This is for test
+ // cases which use "/o" annotation which sets the overscan inset
+ // to native, and that overscan has to be propagated. This does not
+ // happen on the real environment.
+ if (!native_info.overscan_insets_in_dip_.empty())
+ overscan_insets_in_dip_ = native_info.overscan_insets_in_dip_;
+
+ // Rotation_ and ui_scale_ are given by preference, or unit
+ // tests. Don't copy if this native_info came from
+ // DisplayChangeObserverX11.
+ if (!native_info.native()) {
+ rotation_ = native_info.rotation_;
+ ui_scale_ = native_info.ui_scale_;
+ }
+ // Don't copy insets as it may be given by preference. |rotation_|
+ // is treated as a native so that it can be specified in
+ // |CreateFromSpec|.
+}
+
+void DisplayInfo::SetBounds(const gfx::Rect& new_bounds_in_pixel) {
+ bounds_in_pixel_ = new_bounds_in_pixel;
+ size_in_pixel_ = new_bounds_in_pixel.size();
+ UpdateDisplaySize();
+}
+
+void DisplayInfo::UpdateDisplaySize() {
+ size_in_pixel_ = bounds_in_pixel_.size();
+ if (!overscan_insets_in_dip_.empty()) {
+ gfx::Insets insets_in_pixel =
+ overscan_insets_in_dip_.Scale(device_scale_factor_);
+ size_in_pixel_.Enlarge(-insets_in_pixel.width(), -insets_in_pixel.height());
+ } else {
+ overscan_insets_in_dip_.Set(0, 0, 0, 0);
+ }
+
+ if (rotation_ == gfx::Display::ROTATE_90 ||
+ rotation_ == gfx::Display::ROTATE_270)
+ size_in_pixel_.SetSize(size_in_pixel_.height(), size_in_pixel_.width());
+ gfx::SizeF size_f(size_in_pixel_);
+ size_f.Scale(ui_scale_);
+ size_in_pixel_ = gfx::ToFlooredSize(size_f);
+}
+
+void DisplayInfo::SetOverscanInsets(const gfx::Insets& insets_in_dip) {
+ overscan_insets_in_dip_ = insets_in_dip;
+}
+
+gfx::Insets DisplayInfo::GetOverscanInsetsInPixel() const {
+ return overscan_insets_in_dip_.Scale(device_scale_factor_);
+}
+
+std::string DisplayInfo::ToString() const {
+ int rotation_degree = static_cast<int>(rotation_) * 90;
+ return base::StringPrintf(
+ "DisplayInfo[%lld] native bounds=%s, size=%s, scale=%f, "
+ "overscan=%s, rotation=%d, ui-scale=%f",
+ static_cast<long long int>(id_),
+ bounds_in_pixel_.ToString().c_str(),
+ size_in_pixel_.ToString().c_str(),
+ device_scale_factor_,
+ overscan_insets_in_dip_.ToString().c_str(),
+ rotation_degree,
+ ui_scale_);
+}
+
+std::string DisplayInfo::ToFullString() const {
+ std::string resolutions_str;
+ std::vector<Resolution>::const_iterator iter = resolutions_.begin();
+ for (; iter != resolutions_.end(); ++iter) {
+ if (!resolutions_str.empty())
+ resolutions_str += ",";
+ resolutions_str += iter->size.ToString();
+ if (iter->interlaced)
+ resolutions_str += "(i)";
+ }
+ return ToString() + ", resolutions=" + resolutions_str;
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/display/display_info.h b/chromium/ash/display/display_info.h
new file mode 100644
index 00000000000..17ba5f6cfaa
--- /dev/null
+++ b/chromium/ash/display/display_info.h
@@ -0,0 +1,173 @@
+// Copyright (c) 2013 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 ASH_DISPLAY_DISPLAY_INFO_H_
+#define ASH_DISPLAY_DISPLAY_INFO_H_
+
+#include <string>
+#include <vector>
+
+#include "ash/ash_export.h"
+#include "base/gtest_prod_util.h"
+#include "ui/gfx/display.h"
+#include "ui/gfx/insets.h"
+#include "ui/gfx/rect.h"
+
+namespace ash {
+namespace internal {
+
+// A struct that represents the display's resolution and
+// interlaced info.
+struct ASH_EXPORT Resolution {
+ Resolution(const gfx::Size& size, bool interlaced);
+
+ gfx::Size size;
+ bool interlaced;
+};
+
+// DisplayInfo contains metadata for each display. This is used to
+// create |gfx::Display| as well as to maintain extra infomation
+// to manage displays in ash environment.
+// This class is intentionally made copiable.
+class ASH_EXPORT DisplayInfo {
+ public:
+ // Creates a DisplayInfo from string spec. 100+200-1440x800 creates display
+ // whose size is 1440x800 at the location (100, 200) in host coordinates.
+ // The format is
+ //
+ // [origin-]widthxheight[*device_scale_factor][#resolutions list]
+ // [/<properties>][@ui-scale]
+ //
+ // where [] are optional:
+ // - |origin| is given in x+y- format.
+ // - |device_scale_factor| is either 2 or 1 (or empty).
+ // - properties can combination of 'o', which adds default overscan insets
+ // (5%), and one rotation property where 'r' is 90 degree clock-wise
+ // (to the 'r'ight) 'u' is 180 degrees ('u'pside-down) and 'l' is
+ // 270 degrees (to the 'l'eft).
+ // - ui-scale is floating value, e.g. @1.5 or @1.25.
+ // - |resolution list| is the list of size that is given in
+ // |width x height| separated by '|'.
+ //
+ // A couple of examples:
+ // "100x100"
+ // 100x100 window at 0,0 origin. 1x device scale factor. no overscan.
+ // no rotation. 1.0 ui scale.
+ // "5+5-300x200*2"
+ // 300x200 window at 5,5 origin. 2x device scale factor.
+ // no overscan, no rotation. 1.0 ui scale.
+ // "300x200/ol"
+ // 300x200 window at 0,0 origin. 1x device scale factor.
+ // with 5% overscan. rotated to left (90 degree counter clockwise).
+ // 1.0 ui scale.
+ // "10+20-300x200/u@1.5"
+ // 300x200 window at 10,20 origin. 1x device scale factor.
+ // no overscan. flipped upside-down (180 degree) and 1.5 ui scale.
+ // "200x100#300x200|200x100|100x100"
+ // 200x100 window at 0,0 origin, with 3 possible resolutions,
+ // 300x200, 200x100 and 100x100.
+ static DisplayInfo CreateFromSpec(const std::string& spec);
+
+ // Creates a DisplayInfo from string spec using given |id|.
+ static DisplayInfo CreateFromSpecWithID(const std::string& spec,
+ int64 id);
+
+ DisplayInfo();
+ DisplayInfo(int64 id, const std::string& name, bool has_overscan);
+ ~DisplayInfo();
+
+ int64 id() const { return id_; }
+
+ // The name of the display.
+ const std::string& name() const { return name_; }
+
+ // True if the display EDID has the overscan flag. This does not create the
+ // actual overscan automatically, but used in the message.
+ bool has_overscan() const { return has_overscan_; }
+
+ void set_rotation(gfx::Display::Rotation rotation) { rotation_ = rotation; }
+ gfx::Display::Rotation rotation() const { return rotation_; }
+
+ // Gets/Sets the device scale factor of the display.
+ float device_scale_factor() const { return device_scale_factor_; }
+ void set_device_scale_factor(float scale) { device_scale_factor_ = scale; }
+
+ // The bounds_in_pixel for the display. The size of this can be different from
+ // the |size_in_pixel| in case of overscan insets.
+ const gfx::Rect bounds_in_pixel() const {
+ return bounds_in_pixel_;
+ }
+
+ // The size for the display in pixels.
+ const gfx::Size& size_in_pixel() const { return size_in_pixel_; }
+
+ // The overscan insets for the display in DIP.
+ const gfx::Insets& overscan_insets_in_dip() const {
+ return overscan_insets_in_dip_;
+ }
+
+ float ui_scale() const { return ui_scale_; }
+ void set_ui_scale(float scale) { ui_scale_ = scale; }
+
+ // Copy the display info except for fields that can be modified by a user
+ // (|rotation_| and |ui_scale_|). |rotation_| and |ui_scale_| are copied
+ // when the |another_info| isn't native one.
+ void Copy(const DisplayInfo& another_info);
+
+ // Update the |bounds_in_pixel_| and |size_in_pixel_| using
+ // given |bounds_in_pixel|.
+ void SetBounds(const gfx::Rect& bounds_in_pixel);
+
+ // Update the |bounds_in_pixel| according to the current overscan
+ // and rotation settings.
+ void UpdateDisplaySize();
+
+ // Sets/Clears the overscan insets.
+ void SetOverscanInsets(const gfx::Insets& insets_in_dip);
+ gfx::Insets GetOverscanInsetsInPixel() const;
+
+ void set_native(bool native) { native_ = native; }
+ bool native() const { return native_; }
+
+ const std::vector<Resolution>& resolutions() const {
+ return resolutions_;
+ }
+ void set_resolutions(std::vector<Resolution>& resolution) {
+ resolutions_.swap(resolution);
+ }
+
+ // Returns a string representation of the DisplayInfo
+ // excluding resolutions.
+ std::string ToString() const;
+
+ // Returns a string representation of the DisplayInfo
+ // including resolutions.
+ std::string ToFullString() const;
+
+ private:
+ int64 id_;
+ std::string name_;
+ bool has_overscan_;
+ gfx::Display::Rotation rotation_;
+ float device_scale_factor_;
+ gfx::Rect bounds_in_pixel_;
+ // The size of the display in use. The size can be different from the size
+ // of |bounds_in_pixel_| if the display has overscan insets and/or rotation.
+ gfx::Size size_in_pixel_;
+ gfx::Insets overscan_insets_in_dip_;
+
+ // UI scale of the display.
+ float ui_scale_;
+
+ // True if this comes from native platform (DisplayChangeObserverX11).
+ bool native_;
+
+ // The list of resolutions supported by this display.
+ std::vector<Resolution> resolutions_;
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_DISPLAY_DISPLAY_INFO_H_
diff --git a/chromium/ash/display/display_info_unittest.cc b/chromium/ash/display/display_info_unittest.cc
new file mode 100644
index 00000000000..4e9768de049
--- /dev/null
+++ b/chromium/ash/display/display_info_unittest.cc
@@ -0,0 +1,57 @@
+// Copyright (c) 2013 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.
+
+#include "ash/display/display_info.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace ash {
+namespace internal {
+
+typedef testing::Test DisplayInfoTest;
+
+TEST_F(DisplayInfoTest, CreateFromSpec) {
+ DisplayInfo info = DisplayInfo::CreateFromSpecWithID("200x100", 10);
+ EXPECT_EQ(10, info.id());
+ EXPECT_EQ("0,0 200x100", info.bounds_in_pixel().ToString());
+ EXPECT_EQ("200x100", info.size_in_pixel().ToString());
+ EXPECT_EQ(gfx::Display::ROTATE_0, info.rotation());
+ EXPECT_EQ("0,0,0,0", info.overscan_insets_in_dip().ToString());
+ EXPECT_EQ(1.0f, info.ui_scale());
+
+ info = DisplayInfo::CreateFromSpecWithID("10+20-300x400*2/o", 10);
+ EXPECT_EQ("10,20 300x400", info.bounds_in_pixel().ToString());
+ EXPECT_EQ("288x380", info.size_in_pixel().ToString());
+ EXPECT_EQ(gfx::Display::ROTATE_0, info.rotation());
+ EXPECT_EQ("5,3,5,3", info.overscan_insets_in_dip().ToString());
+
+ info = DisplayInfo::CreateFromSpecWithID("10+20-300x400*2/ob", 10);
+ EXPECT_EQ("10,20 300x400", info.bounds_in_pixel().ToString());
+ EXPECT_EQ("288x380", info.size_in_pixel().ToString());
+ EXPECT_EQ(gfx::Display::ROTATE_0, info.rotation());
+ EXPECT_EQ("5,3,5,3", info.overscan_insets_in_dip().ToString());
+
+ info = DisplayInfo::CreateFromSpecWithID("10+20-300x400*2/or", 10);
+ EXPECT_EQ("10,20 300x400", info.bounds_in_pixel().ToString());
+ EXPECT_EQ("380x288", info.size_in_pixel().ToString());
+ EXPECT_EQ(gfx::Display::ROTATE_90, info.rotation());
+ // TODO(oshima): This should be rotated too. Fix this.
+ EXPECT_EQ("5,3,5,3", info.overscan_insets_in_dip().ToString());
+
+ info = DisplayInfo::CreateFromSpecWithID("10+20-300x400*2/l@1.5", 10);
+ EXPECT_EQ("10,20 300x400", info.bounds_in_pixel().ToString());
+ EXPECT_EQ(gfx::Display::ROTATE_270, info.rotation());
+ EXPECT_EQ(1.5f, info.ui_scale());
+
+ info = DisplayInfo::CreateFromSpecWithID(
+ "200x200#300x200|200x200|100x100", 10);
+ EXPECT_EQ("0,0 200x200", info.bounds_in_pixel().ToString());
+ EXPECT_EQ(3u, info.resolutions().size());
+ EXPECT_EQ("300x200", info.resolutions()[0].size.ToString());
+ EXPECT_EQ("200x200", info.resolutions()[1].size.ToString());
+ EXPECT_EQ("100x100", info.resolutions()[2].size.ToString());
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/display/display_layout.cc b/chromium/ash/display/display_layout.cc
new file mode 100644
index 00000000000..a733e762a82
--- /dev/null
+++ b/chromium/ash/display/display_layout.cc
@@ -0,0 +1,152 @@
+// Copyright (c) 2013 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.
+
+#include "ash/display/display_layout.h"
+
+#include "ash/display/display_pref_util.h"
+#include "base/json/json_value_converter.h"
+#include "base/logging.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_piece.h"
+#include "base/strings/stringprintf.h"
+#include "base/values.h"
+#include "ui/gfx/display.h"
+
+namespace ash {
+namespace {
+
+// The maximum value for 'offset' in DisplayLayout in case of outliers. Need
+// to change this value in case to support even larger displays.
+const int kMaxValidOffset = 10000;
+
+// Persistent key names
+const char kPositionKey[] = "position";
+const char kOffsetKey[] = "offset";
+const char kMirroredKey[] = "mirrored";
+const char kPrimaryIdKey[] = "primary-id";
+
+typedef std::map<DisplayLayout::Position, std::string> PositionToStringMap;
+
+const PositionToStringMap* GetPositionToStringMap() {
+ static const PositionToStringMap* map = CreateToStringMap(
+ DisplayLayout::TOP, "top",
+ DisplayLayout::BOTTOM, "bottom",
+ DisplayLayout::RIGHT, "right",
+ DisplayLayout::LEFT, "left");
+ return map;
+}
+
+bool GetPositionFromString(const base::StringPiece& position,
+ DisplayLayout::Position* field) {
+ if (ReverseFind(GetPositionToStringMap(), position, field))
+ return true;
+ LOG(ERROR) << "Invalid position value:" << position;
+ return false;
+}
+
+std::string GetStringFromPosition(DisplayLayout::Position position) {
+ const PositionToStringMap* map = GetPositionToStringMap();
+ PositionToStringMap::const_iterator iter = map->find(position);
+ return iter != map->end() ? iter->second : std::string("unknown");
+}
+
+bool GetDisplayIdFromString(const base::StringPiece& position, int64* field) {
+ return base::StringToInt64(position, field);
+}
+
+} // namespace
+
+////////////////////////////////////////////////////////////////////////////////
+// DisplayLayout
+
+// static
+DisplayLayout DisplayLayout::FromInts(int position, int offsets) {
+ return DisplayLayout(static_cast<Position>(position), offsets);
+}
+
+DisplayLayout::DisplayLayout()
+ : position(RIGHT),
+ offset(0),
+ mirrored(false),
+ primary_id(gfx::Display::kInvalidDisplayID) {
+}
+
+DisplayLayout::DisplayLayout(DisplayLayout::Position position, int offset)
+ : position(position),
+ offset(offset),
+ mirrored(false),
+ primary_id(gfx::Display::kInvalidDisplayID) {
+ DCHECK_LE(TOP, position);
+ DCHECK_GE(LEFT, position);
+
+ // Set the default value to |position| in case position is invalid. DCHECKs
+ // above doesn't stop in Release builds.
+ if (TOP > position || LEFT < position)
+ this->position = RIGHT;
+
+ DCHECK_GE(kMaxValidOffset, abs(offset));
+}
+
+DisplayLayout DisplayLayout::Invert() const {
+ Position inverted_position = RIGHT;
+ switch (position) {
+ case TOP:
+ inverted_position = BOTTOM;
+ break;
+ case BOTTOM:
+ inverted_position = TOP;
+ break;
+ case RIGHT:
+ inverted_position = LEFT;
+ break;
+ case LEFT:
+ inverted_position = RIGHT;
+ break;
+ }
+ DisplayLayout ret = DisplayLayout(inverted_position, -offset);
+ ret.primary_id = primary_id;
+ return ret;
+}
+
+// static
+bool DisplayLayout::ConvertFromValue(const base::Value& value,
+ DisplayLayout* layout) {
+ base::JSONValueConverter<DisplayLayout> converter;
+ return converter.Convert(value, layout);
+}
+
+// static
+bool DisplayLayout::ConvertToValue(const DisplayLayout& layout,
+ base::Value* value) {
+ base::DictionaryValue* dict_value = NULL;
+ if (!value->GetAsDictionary(&dict_value) || dict_value == NULL)
+ return false;
+
+ const std::string position_str = GetStringFromPosition(layout.position);
+ dict_value->SetString(kPositionKey, position_str);
+ dict_value->SetInteger(kOffsetKey, layout.offset);
+ dict_value->SetBoolean(kMirroredKey, layout.mirrored);
+ dict_value->SetString(kPrimaryIdKey, base::Int64ToString(layout.primary_id));
+ return true;
+}
+
+std::string DisplayLayout::ToString() const {
+ const std::string position_str = GetStringFromPosition(position);
+ return base::StringPrintf(
+ "%s, %d%s",
+ position_str.c_str(), offset, mirrored ? ", mirrored" : "");
+}
+
+// static
+void DisplayLayout::RegisterJSONConverter(
+ base::JSONValueConverter<DisplayLayout>* converter) {
+ converter->RegisterCustomField<Position>(
+ kPositionKey, &DisplayLayout::position, &GetPositionFromString);
+ converter->RegisterIntField(kOffsetKey, &DisplayLayout::offset);
+ converter->RegisterBoolField(kMirroredKey, &DisplayLayout::mirrored);
+ converter->RegisterCustomField<int64>(
+ kPrimaryIdKey, &DisplayLayout::primary_id, &GetDisplayIdFromString);
+}
+
+} // namespace ash
diff --git a/chromium/ash/display/display_layout.h b/chromium/ash/display/display_layout.h
new file mode 100644
index 00000000000..dc322087c2f
--- /dev/null
+++ b/chromium/ash/display/display_layout.h
@@ -0,0 +1,71 @@
+// Copyright (c) 2013 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 ASH_DISPLAY_DISPLAY_LAYOUT_H_
+#define ASH_DISPLAY_DISPLAY_LAYOUT_H_
+
+#include <map>
+#include <string>
+
+#include "ash/ash_export.h"
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+
+namespace base {
+class Value;
+template <typename T> class JSONValueConverter;
+}
+
+namespace ash {
+
+typedef std::pair<int64, int64> DisplayIdPair;
+
+struct ASH_EXPORT DisplayLayout {
+ // Layout options where the secondary display should be positioned.
+ enum Position {
+ TOP,
+ RIGHT,
+ BOTTOM,
+ LEFT
+ };
+
+ // Factory method to create DisplayLayout from ints. The |mirrored| is
+ // set to false and |primary_id| is set to gfx::Display::kInvalidDisplayId.
+ // Used for persistence and webui.
+ static DisplayLayout FromInts(int position, int offsets);
+
+ DisplayLayout();
+ DisplayLayout(Position position, int offset);
+
+ // Returns an inverted display layout.
+ DisplayLayout Invert() const WARN_UNUSED_RESULT;
+
+ // Converter functions to/from base::Value.
+ static bool ConvertFromValue(const base::Value& value, DisplayLayout* layout);
+ static bool ConvertToValue(const DisplayLayout& layout, base::Value* value);
+
+ // This method is used by base::JSONValueConverter, you don't need to call
+ // this directly. Instead consider using converter functions above.
+ static void RegisterJSONConverter(
+ base::JSONValueConverter<DisplayLayout>* converter);
+
+ Position position;
+
+ // The offset of the position of the secondary display. The offset is
+ // based on the top/left edge of the primary display.
+ int offset;
+
+ // True if displays are mirrored.
+ bool mirrored;
+
+ // The id of the display used as a primary display.
+ int64 primary_id;
+
+ // Returns string representation of the layout for debugging/testing.
+ std::string ToString() const;
+};
+
+} // namespace ash
+
+#endif
diff --git a/chromium/ash/display/display_layout_store.cc b/chromium/ash/display/display_layout_store.cc
new file mode 100644
index 00000000000..63b1d60b2c8
--- /dev/null
+++ b/chromium/ash/display/display_layout_store.cc
@@ -0,0 +1,94 @@
+// Copyright 2013 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.
+
+#include <stdio.h>
+
+#include "ash/ash_switches.h"
+#include "ash/display/display_layout_store.h"
+#include "base/command_line.h"
+#include "base/logging.h"
+#include "ui/gfx/display.h"
+
+namespace ash {
+namespace internal {
+
+DisplayLayoutStore::DisplayLayoutStore() {
+ CommandLine* command_line = CommandLine::ForCurrentProcess();
+ if (command_line->HasSwitch(switches::kAshSecondaryDisplayLayout)) {
+ std::string value = command_line->GetSwitchValueASCII(
+ switches::kAshSecondaryDisplayLayout);
+ char layout;
+ int offset = 0;
+ if (sscanf(value.c_str(), "%c,%d", &layout, &offset) == 2) {
+ if (layout == 't')
+ default_display_layout_.position = DisplayLayout::TOP;
+ else if (layout == 'b')
+ default_display_layout_.position = DisplayLayout::BOTTOM;
+ else if (layout == 'r')
+ default_display_layout_.position = DisplayLayout::RIGHT;
+ else if (layout == 'l')
+ default_display_layout_.position = DisplayLayout::LEFT;
+ default_display_layout_.offset = offset;
+ }
+ }
+}
+
+DisplayLayoutStore::~DisplayLayoutStore() {
+}
+
+void DisplayLayoutStore::SetDefaultDisplayLayout(const DisplayLayout& layout) {
+ CommandLine* command_line = CommandLine::ForCurrentProcess();
+ if (!command_line->HasSwitch(switches::kAshSecondaryDisplayLayout))
+ default_display_layout_ = layout;
+}
+
+void DisplayLayoutStore::RegisterLayoutForDisplayIdPair(
+ int64 id1,
+ int64 id2,
+ const DisplayLayout& layout) {
+ paired_layouts_[std::make_pair(id1, id2)] = layout;
+}
+
+DisplayLayout DisplayLayoutStore::GetRegisteredDisplayLayout(
+ const DisplayIdPair& pair) {
+ std::map<DisplayIdPair, DisplayLayout>::const_iterator iter =
+ paired_layouts_.find(pair);
+ return
+ iter != paired_layouts_.end() ? iter->second : CreateDisplayLayout(pair);
+}
+
+DisplayLayout DisplayLayoutStore::ComputeDisplayLayoutForDisplayIdPair(
+ const DisplayIdPair& pair) {
+ DisplayLayout layout = GetRegisteredDisplayLayout(pair);
+ DCHECK_NE(layout.primary_id, gfx::Display::kInvalidDisplayID);
+ // Invert if the primary was swapped. If mirrored, first is always
+ // primary.
+ return (layout.primary_id == gfx::Display::kInvalidDisplayID ||
+ pair.first == layout.primary_id) ? layout : layout.Invert();
+}
+
+void DisplayLayoutStore::UpdateMirrorStatus(const DisplayIdPair& pair,
+ bool mirrored) {
+ if (paired_layouts_.find(pair) == paired_layouts_.end())
+ CreateDisplayLayout(pair);
+ paired_layouts_[pair].mirrored = mirrored;
+}
+
+void DisplayLayoutStore::UpdatePrimaryDisplayId(const DisplayIdPair& pair,
+ int64 display_id) {
+ if (paired_layouts_.find(pair) == paired_layouts_.end())
+ CreateDisplayLayout(pair);
+ paired_layouts_[pair].primary_id = display_id;
+}
+
+DisplayLayout DisplayLayoutStore::CreateDisplayLayout(
+ const DisplayIdPair& pair) {
+ DisplayLayout layout = default_display_layout_;
+ layout.primary_id = pair.first;
+ paired_layouts_[pair] = layout;
+ return layout;
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/display/display_layout_store.h b/chromium/ash/display/display_layout_store.h
new file mode 100644
index 00000000000..67c3c2e523c
--- /dev/null
+++ b/chromium/ash/display/display_layout_store.h
@@ -0,0 +1,69 @@
+// Copyright 2013 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 ASH_DISPLAY_DISPLAY_LAYOUT_STORE_H_
+#define ASH_DISPLAY_DISPLAY_LAYOUT_STORE_H_
+
+#include <map>
+
+#include "ash/ash_export.h"
+#include "ash/display/display_layout.h"
+
+namespace ash {
+namespace internal {
+
+class ASH_EXPORT DisplayLayoutStore {
+ public:
+ DisplayLayoutStore();
+ ~DisplayLayoutStore();
+
+ const DisplayLayout& default_display_layout() const {
+ return default_display_layout_;
+ }
+ void SetDefaultDisplayLayout(const DisplayLayout& layout);
+
+ // Registeres the display layout info for the specified display(s).
+ void RegisterLayoutForDisplayIdPair(int64 id1,
+ int64 id2,
+ const DisplayLayout& layout);
+
+ // If no layout is registered, it creatas new layout using
+ // |default_display_layout_|.
+ DisplayLayout GetRegisteredDisplayLayout(const DisplayIdPair& pair);
+
+ // Returns the display layout for the display id pair
+ // with display swapping applied. That is, this returns
+ // flipped layout if the displays are swapped.
+ DisplayLayout ComputeDisplayLayoutForDisplayIdPair(
+ const DisplayIdPair& display_pair);
+
+ // Update the mirrored flag in the display layout for
+ // |display_pair|. This creates new display layout if no layout is
+ // registered for |display_pair|.
+ void UpdateMirrorStatus(const DisplayIdPair& display_pair,
+ bool mirrored);
+
+ // Update the |primary_id| in the display layout for
+ // |display_pair|. This creates new display layout if no layout is
+ // registered for |display_pair|.
+ void UpdatePrimaryDisplayId(const DisplayIdPair& display_pair,
+ int64 display_id);
+
+ private:
+ // Creates new layout for display pair from |default_display_layout_|.
+ DisplayLayout CreateDisplayLayout(const DisplayIdPair& display_pair);
+
+ // The default display layout.
+ DisplayLayout default_display_layout_;
+
+ // Display layout per pair of devices.
+ std::map<DisplayIdPair, DisplayLayout> paired_layouts_;
+
+ DISALLOW_COPY_AND_ASSIGN(DisplayLayoutStore);
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_DISPLAY_DISPLAY_LAYOUT_STORE_H_
diff --git a/chromium/ash/display/display_manager.cc b/chromium/ash/display/display_manager.cc
new file mode 100644
index 00000000000..8953fc8ab93
--- /dev/null
+++ b/chromium/ash/display/display_manager.cc
@@ -0,0 +1,963 @@
+// Copyright (c) 2012 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.
+
+#include "ash/display/display_manager.h"
+
+#include <cmath>
+#include <set>
+#include <string>
+#include <vector>
+
+#include "ash/ash_switches.h"
+#include "ash/display/display_layout_store.h"
+#include "ash/screen_ash.h"
+#include "ash/shell.h"
+#include "base/auto_reset.h"
+#include "base/command_line.h"
+#include "base/logging.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_split.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "grit/ash_strings.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/gfx/display.h"
+#include "ui/gfx/rect.h"
+#include "ui/gfx/screen.h"
+#include "ui/gfx/size_conversions.h"
+
+#if defined(USE_X11)
+#include "ui/base/x/x11_util.h"
+#endif
+
+#if defined(OS_CHROMEOS)
+#include "ash/display/output_configurator_animation.h"
+#include "base/chromeos/chromeos_version.h"
+#include "chromeos/display/output_configurator.h"
+#endif
+
+#if defined(OS_WIN)
+#include "base/win/windows_version.h"
+#endif
+
+namespace ash {
+namespace internal {
+typedef std::vector<gfx::Display> DisplayList;
+typedef std::vector<DisplayInfo> DisplayInfoList;
+
+namespace {
+
+// The number of pixels to overlap between the primary and secondary displays,
+// in case that the offset value is too large.
+const int kMinimumOverlapForInvalidOffset = 100;
+
+// List of value UI Scale values. Scales for 2x are equivalent to 640,
+// 800, 1024, 1280, 1440, 1600 and 1920 pixel width respectively on
+// 2560 pixel width 2x density display. Please see crbug.com/233375
+// for the full list of resolutions.
+const float kUIScalesFor2x[] = {0.5f, 0.625f, 0.8f, 1.0f, 1.125f, 1.25f, 1.5f};
+const float kUIScalesFor1280[] = {0.5f, 0.625f, 0.8f, 1.0f, 1.125f };
+const float kUIScalesFor1366[] = {0.5f, 0.6f, 0.75f, 1.0f, 1.125f };
+
+struct DisplaySortFunctor {
+ bool operator()(const gfx::Display& a, const gfx::Display& b) {
+ return a.id() < b.id();
+ }
+};
+
+struct DisplayInfoSortFunctor {
+ bool operator()(const DisplayInfo& a, const DisplayInfo& b) {
+ return a.id() < b.id();
+ }
+};
+
+struct ResolutionMatcher {
+ ResolutionMatcher(const gfx::Size& size) : size(size) {}
+ bool operator()(const Resolution& resolution) {
+ return resolution.size == size;
+ }
+ gfx::Size size;
+};
+
+struct ScaleComparator {
+ ScaleComparator(float s) : scale(s) {}
+
+ bool operator()(float s) const {
+ const float kEpsilon = 0.0001f;
+ return std::abs(scale - s) < kEpsilon;
+ }
+ float scale;
+};
+
+gfx::Display& GetInvalidDisplay() {
+ static gfx::Display* invalid_display = new gfx::Display();
+ return *invalid_display;
+}
+
+void MaybeInitInternalDisplay(int64 id) {
+ CommandLine* command_line = CommandLine::ForCurrentProcess();
+ if (command_line->HasSwitch(switches::kAshUseFirstDisplayAsInternal))
+ gfx::Display::SetInternalDisplayId(id);
+}
+
+// Scoped objects used to either create or close the mirror window
+// at specific timing.
+class MirrorWindowCreator {
+ public:
+ MirrorWindowCreator(DisplayManager::Delegate* delegate,
+ const DisplayInfo& display_info)
+ : delegate_(delegate),
+ display_info_(display_info) {
+ }
+
+ virtual ~MirrorWindowCreator() {
+ if (delegate_)
+ delegate_->CreateOrUpdateMirrorWindow(display_info_);
+ }
+
+ private:
+ DisplayManager::Delegate* delegate_;
+ const DisplayInfo display_info_;
+ DISALLOW_COPY_AND_ASSIGN(MirrorWindowCreator);
+};
+
+class MirrorWindowCloser {
+ public:
+ explicit MirrorWindowCloser(DisplayManager::Delegate* delegate)
+ : delegate_(delegate) {}
+
+ virtual ~MirrorWindowCloser() {
+ if (delegate_)
+ delegate_->CloseMirrorWindow();
+ }
+
+ private:
+ DisplayManager::Delegate* delegate_;
+
+ DISALLOW_COPY_AND_ASSIGN(MirrorWindowCloser);
+};
+
+} // namespace
+
+using std::string;
+using std::vector;
+
+DisplayManager::DisplayManager()
+ : delegate_(NULL),
+ layout_store_(new DisplayLayoutStore),
+ first_display_id_(gfx::Display::kInvalidDisplayID),
+ num_connected_displays_(0),
+ force_bounds_changed_(false),
+ change_display_upon_host_resize_(false),
+ software_mirroring_enabled_(false) {
+#if defined(OS_CHROMEOS)
+ change_display_upon_host_resize_ = !base::chromeos::IsRunningOnChromeOS();
+#endif
+}
+
+DisplayManager::~DisplayManager() {
+}
+
+// static
+std::vector<float> DisplayManager::GetScalesForDisplay(
+ const DisplayInfo& info) {
+ std::vector<float> ret;
+ if (info.device_scale_factor() == 2.0f) {
+ ret.assign(kUIScalesFor2x, kUIScalesFor2x + arraysize(kUIScalesFor2x));
+ return ret;
+ }
+ switch (info.bounds_in_pixel().width()) {
+ case 1280:
+ ret.assign(kUIScalesFor1280,
+ kUIScalesFor1280 + arraysize(kUIScalesFor1280));
+ break;
+ case 1366:
+ ret.assign(kUIScalesFor1366,
+ kUIScalesFor1366 + arraysize(kUIScalesFor1366));
+ break;
+ default:
+ ret.assign(kUIScalesFor1280,
+ kUIScalesFor1280 + arraysize(kUIScalesFor1280));
+#if defined(OS_CHROMEOS)
+ if (base::chromeos::IsRunningOnChromeOS())
+ NOTREACHED() << "Unknown resolution:" << info.ToString();
+#endif
+ }
+ return ret;
+}
+
+// static
+float DisplayManager::GetNextUIScale(const DisplayInfo& info, bool up) {
+ float scale = info.ui_scale();
+ std::vector<float> scales = GetScalesForDisplay(info);
+ for (size_t i = 0; i < scales.size(); ++i) {
+ if (ScaleComparator(scales[i])(scale)) {
+ if (up && i != scales.size() - 1)
+ return scales[i + 1];
+ if (!up && i != 0)
+ return scales[i - 1];
+ return scales[i];
+ }
+ }
+ // Fallback to 1.0f if the |scale| wasn't in the list.
+ return 1.0f;
+}
+
+void DisplayManager::InitFromCommandLine() {
+ DisplayInfoList info_list;
+
+ const string size_str = CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
+ switches::kAshHostWindowBounds);
+ vector<string> parts;
+ base::SplitString(size_str, ',', &parts);
+ for (vector<string>::const_iterator iter = parts.begin();
+ iter != parts.end(); ++iter) {
+ info_list.push_back(DisplayInfo::CreateFromSpec(*iter));
+ }
+ if (info_list.size())
+ MaybeInitInternalDisplay(info_list[0].id());
+ OnNativeDisplaysChanged(info_list);
+}
+
+// static
+void DisplayManager::UpdateDisplayBoundsForLayoutById(
+ const DisplayLayout& layout,
+ const gfx::Display& primary_display,
+ int64 secondary_display_id) {
+ DCHECK_NE(gfx::Display::kInvalidDisplayID, secondary_display_id);
+ UpdateDisplayBoundsForLayout(
+ layout, primary_display,
+ Shell::GetInstance()->display_manager()->
+ FindDisplayForId(secondary_display_id));
+}
+
+bool DisplayManager::IsActiveDisplay(const gfx::Display& display) const {
+ for (DisplayList::const_iterator iter = displays_.begin();
+ iter != displays_.end(); ++iter) {
+ if ((*iter).id() == display.id())
+ return true;
+ }
+ return false;
+}
+
+bool DisplayManager::HasInternalDisplay() const {
+ return gfx::Display::InternalDisplayId() != gfx::Display::kInvalidDisplayID;
+}
+
+bool DisplayManager::IsInternalDisplayId(int64 id) const {
+ return gfx::Display::InternalDisplayId() == id;
+}
+
+DisplayLayout DisplayManager::GetCurrentDisplayLayout() {
+ DCHECK_EQ(2U, num_connected_displays());
+ // Invert if the primary was swapped.
+ if (num_connected_displays() > 1) {
+ DisplayIdPair pair = GetCurrentDisplayIdPair();
+ return layout_store_->ComputeDisplayLayoutForDisplayIdPair(pair);
+ }
+ NOTREACHED() << "DisplayLayout is requested for single display";
+ // On release build, just fallback to default instead of blowing up.
+ DisplayLayout layout =
+ layout_store_->default_display_layout();
+ layout.primary_id = displays_[0].id();
+ return layout;
+}
+
+DisplayIdPair DisplayManager::GetCurrentDisplayIdPair() const {
+ if (IsMirrored()) {
+ int64 mirrored_id = mirrored_display().id();
+ return displays_[0].id() == mirrored_id ?
+ std::make_pair(displays_[1].id(), mirrored_id) :
+ std::make_pair(displays_[0].id(), mirrored_id);
+ } else {
+ int64 id_at_zero = displays_[0].id();
+ if (id_at_zero == gfx::Display::InternalDisplayId() ||
+ id_at_zero == first_display_id()) {
+ return std::make_pair(id_at_zero, displays_[1].id());
+ } else {
+ return std::make_pair(displays_[1].id(), id_at_zero);
+ }
+ }
+}
+
+const gfx::Display& DisplayManager::GetDisplayForId(int64 id) const {
+ gfx::Display* display =
+ const_cast<DisplayManager*>(this)->FindDisplayForId(id);
+ return display ? *display : GetInvalidDisplay();
+}
+
+const gfx::Display& DisplayManager::FindDisplayContainingPoint(
+ const gfx::Point& point_in_screen) const {
+ for (DisplayList::const_iterator iter = displays_.begin();
+ iter != displays_.end(); ++iter) {
+ const gfx::Display& display = *iter;
+ if (display.bounds().Contains(point_in_screen))
+ return display;
+ }
+ return GetInvalidDisplay();
+}
+
+bool DisplayManager::UpdateWorkAreaOfDisplay(int64 display_id,
+ const gfx::Insets& insets) {
+ gfx::Display* display = FindDisplayForId(display_id);
+ DCHECK(display);
+ gfx::Rect old_work_area = display->work_area();
+ display->UpdateWorkAreaFromInsets(insets);
+ return old_work_area != display->work_area();
+}
+
+void DisplayManager::SetOverscanInsets(int64 display_id,
+ const gfx::Insets& insets_in_dip) {
+ display_info_[display_id].SetOverscanInsets(insets_in_dip);
+ DisplayInfoList display_info_list;
+ for (DisplayList::const_iterator iter = displays_.begin();
+ iter != displays_.end(); ++iter) {
+ display_info_list.push_back(GetDisplayInfo(iter->id()));
+ }
+ AddMirrorDisplayInfoIfAny(&display_info_list);
+ UpdateDisplays(display_info_list);
+}
+
+void DisplayManager::SetDisplayRotation(int64 display_id,
+ gfx::Display::Rotation rotation) {
+ if (!IsDisplayRotationEnabled())
+ return;
+ DisplayInfoList display_info_list;
+ for (DisplayList::const_iterator iter = displays_.begin();
+ iter != displays_.end(); ++iter) {
+ DisplayInfo info = GetDisplayInfo(iter->id());
+ if (info.id() == display_id) {
+ if (info.rotation() == rotation)
+ return;
+ info.set_rotation(rotation);
+ }
+ display_info_list.push_back(info);
+ }
+ AddMirrorDisplayInfoIfAny(&display_info_list);
+ UpdateDisplays(display_info_list);
+}
+
+void DisplayManager::SetDisplayUIScale(int64 display_id,
+ float ui_scale) {
+ if (!IsDisplayUIScalingEnabled() ||
+ gfx::Display::InternalDisplayId() != display_id) {
+ return;
+ }
+
+ DisplayInfoList display_info_list;
+ for (DisplayList::const_iterator iter = displays_.begin();
+ iter != displays_.end(); ++iter) {
+ DisplayInfo info = GetDisplayInfo(iter->id());
+ if (info.id() == display_id) {
+ if (info.ui_scale() == ui_scale)
+ return;
+ std::vector<float> scales = GetScalesForDisplay(info);
+ ScaleComparator comparator(ui_scale);
+ if (std::find_if(scales.begin(), scales.end(), comparator) ==
+ scales.end()) {
+ return;
+ }
+ info.set_ui_scale(ui_scale);
+ }
+ display_info_list.push_back(info);
+ }
+ AddMirrorDisplayInfoIfAny(&display_info_list);
+ UpdateDisplays(display_info_list);
+}
+
+void DisplayManager::SetDisplayResolution(int64 display_id,
+ const gfx::Size& resolution) {
+ DCHECK_NE(gfx::Display::InternalDisplayId(), display_id);
+ if (gfx::Display::InternalDisplayId() == display_id)
+ return;
+ const DisplayInfo& display_info = GetDisplayInfo(display_id);
+ const std::vector<Resolution>& resolutions = display_info.resolutions();
+ DCHECK_NE(0u, resolutions.size());
+ std::vector<Resolution>::const_iterator iter =
+ std::find_if(resolutions.begin(),
+ resolutions.end(),
+ ResolutionMatcher(resolution));
+ if (iter == resolutions.end()) {
+ LOG(WARNING) << "Unsupported resolution was requested:"
+ << resolution.ToString();
+ return;
+ } else if (iter == resolutions.begin()) {
+ // The best resolution was set, so forget it.
+ resolutions_.erase(display_id);
+ } else {
+ resolutions_[display_id] = resolution;
+ }
+#if defined(OS_CHROMEOS) && defined(USE_X11)
+ if (base::chromeos::IsRunningOnChromeOS())
+ Shell::GetInstance()->output_configurator()->ScheduleConfigureOutputs();
+#endif
+}
+
+void DisplayManager::RegisterDisplayProperty(
+ int64 display_id,
+ gfx::Display::Rotation rotation,
+ float ui_scale,
+ const gfx::Insets* overscan_insets,
+ const gfx::Size& resolution_in_pixels) {
+ if (display_info_.find(display_id) == display_info_.end()) {
+ display_info_[display_id] =
+ DisplayInfo(display_id, std::string(""), false);
+ }
+
+ display_info_[display_id].set_rotation(rotation);
+ // Just in case the preference file was corrupted.
+ if (0.5f <= ui_scale && ui_scale <= 2.0f)
+ display_info_[display_id].set_ui_scale(ui_scale);
+ if (overscan_insets)
+ display_info_[display_id].SetOverscanInsets(*overscan_insets);
+ if (!resolution_in_pixels.IsEmpty())
+ resolutions_[display_id] = resolution_in_pixels;
+}
+
+bool DisplayManager::GetSelectedResolutionForDisplayId(
+ int64 id,
+ gfx::Size* resolution_out) const {
+ std::map<int64, gfx::Size>::const_iterator iter =
+ resolutions_.find(id);
+ if (iter == resolutions_.end())
+ return false;
+ *resolution_out = iter->second;
+ return true;
+}
+
+bool DisplayManager::IsDisplayRotationEnabled() const {
+ static bool enabled = !CommandLine::ForCurrentProcess()->
+ HasSwitch(switches::kAshDisableDisplayRotation);
+ return enabled;
+}
+
+bool DisplayManager::IsDisplayUIScalingEnabled() const {
+ static bool enabled = !CommandLine::ForCurrentProcess()->
+ HasSwitch(switches::kAshDisableUIScaling);
+ if (!enabled)
+ return false;
+ return GetDisplayIdForUIScaling() != gfx::Display::kInvalidDisplayID;
+}
+
+gfx::Insets DisplayManager::GetOverscanInsets(int64 display_id) const {
+ std::map<int64, DisplayInfo>::const_iterator it =
+ display_info_.find(display_id);
+ return (it != display_info_.end()) ?
+ it->second.overscan_insets_in_dip() : gfx::Insets();
+}
+
+void DisplayManager::OnNativeDisplaysChanged(
+ const std::vector<DisplayInfo>& updated_displays) {
+ if (updated_displays.empty()) {
+ // If the device is booted without display, or chrome is started
+ // without --ash-host-window-bounds on linux desktop, use the
+ // default display.
+ if (displays_.empty()) {
+ std::vector<DisplayInfo> init_displays;
+ init_displays.push_back(DisplayInfo::CreateFromSpec(std::string()));
+ MaybeInitInternalDisplay(init_displays[0].id());
+ OnNativeDisplaysChanged(init_displays);
+ } else {
+ // Otherwise don't update the displays when all displays are disconnected.
+ // This happens when:
+ // - the device is idle and powerd requested to turn off all displays.
+ // - the device is suspended. (kernel turns off all displays)
+ // - the internal display's brightness is set to 0 and no external
+ // display is connected.
+ // - the internal display's brightness is 0 and external display is
+ // disconnected.
+ // The display will be updated when one of displays is turned on, and the
+ // display list will be updated correctly.
+ }
+ return;
+ }
+ first_display_id_ = updated_displays[0].id();
+ std::set<gfx::Point> origins;
+
+ if (updated_displays.size() == 1) {
+ VLOG(1) << "OnNativeDisplaysChanged(1):" << updated_displays[0].ToString();
+ } else {
+ VLOG(1) << "OnNativeDisplaysChanged(" << updated_displays.size()
+ << ") [0]=" << updated_displays[0].ToString()
+ << ", [1]=" << updated_displays[1].ToString();
+ }
+
+ bool internal_display_connected = false;
+ num_connected_displays_ = updated_displays.size();
+ mirrored_display_ = gfx::Display();
+ DisplayInfoList new_display_info_list;
+ for (DisplayInfoList::const_iterator iter = updated_displays.begin();
+ iter != updated_displays.end();
+ ++iter) {
+ if (!internal_display_connected)
+ internal_display_connected = IsInternalDisplayId(iter->id());
+ // Mirrored monitors have the same origins.
+ gfx::Point origin = iter->bounds_in_pixel().origin();
+ if (origins.find(origin) != origins.end()) {
+ InsertAndUpdateDisplayInfo(*iter);
+ mirrored_display_ = CreateDisplayFromDisplayInfoById(iter->id());
+ } else {
+ origins.insert(origin);
+ new_display_info_list.push_back(*iter);
+ }
+ }
+ if (HasInternalDisplay() &&
+ !internal_display_connected &&
+ display_info_.find(gfx::Display::InternalDisplayId()) ==
+ display_info_.end()) {
+ DisplayInfo internal_display_info(
+ gfx::Display::InternalDisplayId(),
+ l10n_util::GetStringUTF8(IDS_ASH_INTERNAL_DISPLAY_NAME),
+ false /*Internal display must not have overscan */);
+ internal_display_info.SetBounds(gfx::Rect(0, 0, 800, 600));
+ display_info_[gfx::Display::InternalDisplayId()] = internal_display_info;
+ }
+ UpdateDisplays(new_display_info_list);
+}
+
+void DisplayManager::UpdateDisplays() {
+ DisplayInfoList display_info_list;
+ for (DisplayList::const_iterator iter = displays_.begin();
+ iter != displays_.end(); ++iter) {
+ display_info_list.push_back(GetDisplayInfo(iter->id()));
+ }
+ AddMirrorDisplayInfoIfAny(&display_info_list);
+ UpdateDisplays(display_info_list);
+}
+
+void DisplayManager::UpdateDisplays(
+ const std::vector<DisplayInfo>& updated_display_info_list) {
+#if defined(OS_WIN)
+ if (base::win::GetVersion() >= base::win::VERSION_WIN8) {
+ DCHECK_EQ(1u, updated_display_info_list.size()) <<
+ "Multiple display test does not work on Win8 bots. Please "
+ "skip (don't disable) the test using SupportsMultipleDisplays()";
+ }
+#endif
+
+ DisplayInfoList new_display_info_list = updated_display_info_list;
+ std::sort(displays_.begin(), displays_.end(), DisplaySortFunctor());
+ std::sort(new_display_info_list.begin(),
+ new_display_info_list.end(),
+ DisplayInfoSortFunctor());
+ DisplayList removed_displays;
+ std::vector<size_t> changed_display_indices;
+ std::vector<size_t> added_display_indices;
+
+ DisplayList::iterator curr_iter = displays_.begin();
+ DisplayInfoList::const_iterator new_info_iter = new_display_info_list.begin();
+
+ DisplayList new_displays;
+
+ scoped_ptr<MirrorWindowCreator> mirror_window_creater;
+
+ // Use the internal display or 1st as the mirror source, then scale
+ // the root window so that it matches the external display's
+ // resolution. This is necessary in order for scaling to work while
+ // mirrored.
+ int64 mirrored_display_id = gfx::Display::kInvalidDisplayID;
+ if (software_mirroring_enabled_ && new_display_info_list.size() == 2)
+ mirrored_display_id = new_display_info_list[1].id();
+
+ while (curr_iter != displays_.end() ||
+ new_info_iter != new_display_info_list.end()) {
+ if (new_info_iter != new_display_info_list.end() &&
+ mirrored_display_id == new_info_iter->id()) {
+ DisplayInfo info = *new_info_iter;
+ info.SetOverscanInsets(gfx::Insets());
+ InsertAndUpdateDisplayInfo(info);
+
+ mirrored_display_ = CreateDisplayFromDisplayInfoById(new_info_iter->id());
+ mirror_window_creater.reset(new MirrorWindowCreator(
+ delegate_, display_info_[new_info_iter->id()]));
+ ++new_info_iter;
+ // Remove existing external dispaly if it is going to be mirrored.
+ if (curr_iter != displays_.end() &&
+ curr_iter->id() == mirrored_display_id) {
+ removed_displays.push_back(*curr_iter);
+ ++curr_iter;
+ }
+ continue;
+ }
+
+ if (curr_iter == displays_.end()) {
+ // more displays in new list.
+ added_display_indices.push_back(new_displays.size());
+ InsertAndUpdateDisplayInfo(*new_info_iter);
+ new_displays.push_back(
+ CreateDisplayFromDisplayInfoById(new_info_iter->id()));
+ ++new_info_iter;
+ } else if (new_info_iter == new_display_info_list.end()) {
+ // more displays in current list.
+ removed_displays.push_back(*curr_iter);
+ ++curr_iter;
+ } else if (curr_iter->id() == new_info_iter->id()) {
+ const gfx::Display& current_display = *curr_iter;
+ // Copy the info because |CreateDisplayFromInfo| updates the instance.
+ const DisplayInfo current_display_info =
+ GetDisplayInfo(current_display.id());
+ InsertAndUpdateDisplayInfo(*new_info_iter);
+ gfx::Display new_display =
+ CreateDisplayFromDisplayInfoById(new_info_iter->id());
+ const DisplayInfo& new_display_info = GetDisplayInfo(new_display.id());
+
+ bool host_window_bounds_changed =
+ current_display_info.bounds_in_pixel() !=
+ new_display_info.bounds_in_pixel();
+
+ if (force_bounds_changed_ ||
+ host_window_bounds_changed ||
+ (current_display.device_scale_factor() !=
+ new_display.device_scale_factor()) ||
+ (current_display_info.size_in_pixel() !=
+ new_display.GetSizeInPixel()) ||
+ (current_display.rotation() != new_display.rotation())) {
+
+ changed_display_indices.push_back(new_displays.size());
+ }
+
+ new_display.UpdateWorkAreaFromInsets(current_display.GetWorkAreaInsets());
+ new_displays.push_back(new_display);
+ ++curr_iter;
+ ++new_info_iter;
+ } else if (curr_iter->id() < new_info_iter->id()) {
+ // more displays in current list between ids, which means it is deleted.
+ removed_displays.push_back(*curr_iter);
+ ++curr_iter;
+ } else {
+ // more displays in new list between ids, which means it is added.
+ added_display_indices.push_back(new_displays.size());
+ InsertAndUpdateDisplayInfo(*new_info_iter);
+ new_displays.push_back(
+ CreateDisplayFromDisplayInfoById(new_info_iter->id()));
+ ++new_info_iter;
+ }
+ }
+
+ scoped_ptr<MirrorWindowCloser> mirror_window_closer;
+ // Try to close mirror window unless mirror window is necessary.
+ if (!mirror_window_creater.get())
+ mirror_window_closer.reset(new MirrorWindowCloser(delegate_));
+
+ // Do not update |displays_| if there's nothing to be updated. Without this,
+ // it will not update the display layout, which causes the bug
+ // http://crbug.com/155948.
+ if (changed_display_indices.empty() && added_display_indices.empty() &&
+ removed_displays.empty()) {
+ return;
+ }
+ if (delegate_)
+ delegate_->PreDisplayConfigurationChange();
+
+ size_t updated_index;
+ if (UpdateSecondaryDisplayBoundsForLayout(&new_displays, &updated_index) &&
+ std::find(added_display_indices.begin(),
+ added_display_indices.end(),
+ updated_index) == added_display_indices.end() &&
+ std::find(changed_display_indices.begin(),
+ changed_display_indices.end(),
+ updated_index) == changed_display_indices.end()) {
+ changed_display_indices.push_back(updated_index);
+ }
+
+ displays_ = new_displays;
+
+ base::AutoReset<bool> resetter(&change_display_upon_host_resize_, false);
+
+ // Temporarily add displays to be removed because display object
+ // being removed are accessed during shutting down the root.
+ displays_.insert(displays_.end(), removed_displays.begin(),
+ removed_displays.end());
+
+ for (DisplayList::const_reverse_iterator iter = removed_displays.rbegin();
+ iter != removed_displays.rend(); ++iter) {
+ Shell::GetInstance()->screen()->NotifyDisplayRemoved(displays_.back());
+ displays_.pop_back();
+ }
+ // Close the mirror window here to avoid creating two compositor on
+ // one display.
+ mirror_window_closer.reset();
+ for (std::vector<size_t>::iterator iter = added_display_indices.begin();
+ iter != added_display_indices.end(); ++iter) {
+ Shell::GetInstance()->screen()->NotifyDisplayAdded(displays_[*iter]);
+ }
+ // Create the mirror window after all displays are added so that
+ // it can mirror the display newly added. This can happen when switching
+ // from dock mode to software mirror mode.
+ mirror_window_creater.reset();
+ for (std::vector<size_t>::iterator iter = changed_display_indices.begin();
+ iter != changed_display_indices.end(); ++iter) {
+ Shell::GetInstance()->screen()->NotifyBoundsChanged(displays_[*iter]);
+ }
+ if (delegate_)
+ delegate_->PostDisplayConfigurationChange();
+
+#if defined(USE_X11) && defined(OS_CHROMEOS)
+ if (!changed_display_indices.empty() && base::chromeos::IsRunningOnChromeOS())
+ ui::ClearX11DefaultRootWindow();
+#endif
+}
+
+const gfx::Display& DisplayManager::GetDisplayAt(size_t index) const {
+ DCHECK_LT(index, displays_.size());
+ return displays_[index];
+}
+
+const gfx::Display& DisplayManager::GetPrimaryDisplayCandidate() const {
+ if (GetNumDisplays() == 1) {
+ return displays_[0];
+ }
+ DisplayLayout layout = layout_store_->GetRegisteredDisplayLayout(
+ GetCurrentDisplayIdPair());
+ return GetDisplayForId(layout.primary_id);
+}
+
+size_t DisplayManager::GetNumDisplays() const {
+ return displays_.size();
+}
+
+bool DisplayManager::IsMirrored() const {
+ return mirrored_display_.id() != gfx::Display::kInvalidDisplayID;
+}
+
+const DisplayInfo& DisplayManager::GetDisplayInfo(int64 display_id) const {
+ std::map<int64, DisplayInfo>::const_iterator iter =
+ display_info_.find(display_id);
+ CHECK(iter != display_info_.end()) << display_id;
+ return iter->second;
+}
+
+std::string DisplayManager::GetDisplayNameForId(int64 id) {
+ if (id == gfx::Display::kInvalidDisplayID)
+ return l10n_util::GetStringUTF8(IDS_ASH_STATUS_TRAY_UNKNOWN_DISPLAY_NAME);
+
+ std::map<int64, DisplayInfo>::const_iterator iter = display_info_.find(id);
+ if (iter != display_info_.end() && !iter->second.name().empty())
+ return iter->second.name();
+
+ return base::StringPrintf("Display %d", static_cast<int>(id));
+}
+
+int64 DisplayManager::GetDisplayIdForUIScaling() const {
+ // UI Scaling is effective only on internal display.
+ int64 display_id = gfx::Display::InternalDisplayId();
+#if defined(OS_WIN)
+ display_id = first_display_id();
+#endif
+ return display_id;
+}
+
+void DisplayManager::SetMirrorMode(bool mirrored) {
+ if (num_connected_displays() <= 1)
+ return;
+
+#if defined(OS_CHROMEOS)
+ if (base::chromeos::IsRunningOnChromeOS()) {
+ chromeos::OutputState new_state = mirrored ?
+ chromeos::STATE_DUAL_MIRROR : chromeos::STATE_DUAL_EXTENDED;
+ Shell::GetInstance()->output_configurator()->SetDisplayMode(new_state);
+ return;
+ }
+#endif
+ SetSoftwareMirroring(mirrored);
+ DisplayInfoList display_info_list;
+ int count = 0;
+ for (std::map<int64, DisplayInfo>::const_iterator iter =
+ display_info_.begin();
+ count < 2; ++iter, ++count) {
+ display_info_list.push_back(GetDisplayInfo(iter->second.id()));
+ }
+ UpdateDisplays(display_info_list);
+#if defined(OS_CHROMEOS)
+ if (Shell::GetInstance()->output_configurator_animation()) {
+ Shell::GetInstance()->output_configurator_animation()->
+ StartFadeInAnimation();
+ }
+#endif
+}
+
+void DisplayManager::AddRemoveDisplay() {
+ DCHECK(!displays_.empty());
+ std::vector<DisplayInfo> new_display_info_list;
+ const DisplayInfo& first_display = GetDisplayInfo(displays_[0].id());
+ new_display_info_list.push_back(first_display);
+ // Add if there is only one display connected.
+ if (num_connected_displays() == 1) {
+ // Layout the 2nd display below the primary as with the real device.
+ gfx::Rect host_bounds = first_display.bounds_in_pixel();
+ new_display_info_list.push_back(DisplayInfo::CreateFromSpec(
+ base::StringPrintf(
+ "%d+%d-500x400", host_bounds.x(), host_bounds.bottom())));
+ }
+ num_connected_displays_ = new_display_info_list.size();
+ mirrored_display_ = gfx::Display();
+ UpdateDisplays(new_display_info_list);
+}
+
+void DisplayManager::ToggleDisplayScaleFactor() {
+ DCHECK(!displays_.empty());
+ std::vector<DisplayInfo> new_display_info_list;
+ for (DisplayList::const_iterator iter = displays_.begin();
+ iter != displays_.end(); ++iter) {
+ DisplayInfo display_info = GetDisplayInfo(iter->id());
+ display_info.set_device_scale_factor(
+ display_info.device_scale_factor() == 1.0f ? 2.0f : 1.0f);
+ new_display_info_list.push_back(display_info);
+ }
+ AddMirrorDisplayInfoIfAny(&new_display_info_list);
+ UpdateDisplays(new_display_info_list);
+}
+
+void DisplayManager::SetSoftwareMirroring(bool enabled) {
+ software_mirroring_enabled_ = enabled;
+ mirrored_display_ = gfx::Display();
+}
+
+bool DisplayManager::UpdateDisplayBounds(int64 display_id,
+ const gfx::Rect& new_bounds) {
+ if (change_display_upon_host_resize_) {
+ display_info_[display_id].SetBounds(new_bounds);
+ // Don't notify observers if the mirrored window has changed.
+ if (software_mirroring_enabled_ && mirrored_display_.id() == display_id)
+ return false;
+ gfx::Display* display = FindDisplayForId(display_id);
+ display->SetSize(display_info_[display_id].size_in_pixel());
+ Shell::GetInstance()->screen()->NotifyBoundsChanged(*display);
+ return true;
+ }
+ return false;
+}
+
+gfx::Display* DisplayManager::FindDisplayForId(int64 id) {
+ for (DisplayList::iterator iter = displays_.begin();
+ iter != displays_.end(); ++iter) {
+ if ((*iter).id() == id)
+ return &(*iter);
+ }
+ DLOG(WARNING) << "Could not find display:" << id;
+ return NULL;
+}
+
+void DisplayManager::AddMirrorDisplayInfoIfAny(
+ std::vector<DisplayInfo>* display_info_list) {
+ if (software_mirroring_enabled_ && mirrored_display_.is_valid())
+ display_info_list->push_back(GetDisplayInfo(mirrored_display_.id()));
+}
+
+void DisplayManager::InsertAndUpdateDisplayInfo(const DisplayInfo& new_info) {
+ std::map<int64, DisplayInfo>::iterator info =
+ display_info_.find(new_info.id());
+ if (info != display_info_.end())
+ info->second.Copy(new_info);
+ else {
+ display_info_[new_info.id()] = new_info;
+ display_info_[new_info.id()].set_native(false);
+ }
+ display_info_[new_info.id()].UpdateDisplaySize();
+}
+
+gfx::Display DisplayManager::CreateDisplayFromDisplayInfoById(int64 id) {
+ DCHECK(display_info_.find(id) != display_info_.end());
+ const DisplayInfo& display_info = display_info_[id];
+
+ gfx::Display new_display(display_info.id());
+ gfx::Rect bounds_in_pixel(display_info.size_in_pixel());
+
+ // Simply set the origin to (0,0). The primary display's origin is
+ // always (0,0) and the secondary display's bounds will be updated
+ // in |UpdateSecondaryDisplayBoundsForLayout| called in |UpdateDisplay|.
+ new_display.SetScaleAndBounds(
+ display_info.device_scale_factor(), gfx::Rect(bounds_in_pixel.size()));
+ new_display.set_rotation(display_info.rotation());
+ return new_display;
+}
+
+bool DisplayManager::UpdateSecondaryDisplayBoundsForLayout(
+ DisplayList* displays,
+ size_t* updated_index) const {
+ if (displays->size() != 2U)
+ return false;
+
+ int64 id_at_zero = displays->at(0).id();
+ DisplayIdPair pair =
+ (id_at_zero == first_display_id_ ||
+ id_at_zero == gfx::Display::InternalDisplayId()) ?
+ std::make_pair(id_at_zero, displays->at(1).id()) :
+ std::make_pair(displays->at(1).id(), id_at_zero) ;
+ DisplayLayout layout =
+ layout_store_->ComputeDisplayLayoutForDisplayIdPair(pair);
+
+ // Ignore if a user has a old format (should be extremely rare)
+ // and this will be replaced with DCHECK.
+ if (layout.primary_id != gfx::Display::kInvalidDisplayID) {
+ size_t primary_index, secondary_index;
+ if (displays->at(0).id() == layout.primary_id) {
+ primary_index = 0;
+ secondary_index = 1;
+ } else {
+ primary_index = 1;
+ secondary_index = 0;
+ }
+ // This function may be called before the secondary display is
+ // registered. The bounds is empty in that case and will
+ // return true.
+ gfx::Rect bounds =
+ GetDisplayForId(displays->at(secondary_index).id()).bounds();
+ UpdateDisplayBoundsForLayout(
+ layout, displays->at(primary_index), &displays->at(secondary_index));
+ *updated_index = secondary_index;
+ return bounds != displays->at(secondary_index).bounds();
+ }
+ return false;
+}
+
+// static
+void DisplayManager::UpdateDisplayBoundsForLayout(
+ const DisplayLayout& layout,
+ const gfx::Display& primary_display,
+ gfx::Display* secondary_display) {
+ DCHECK_EQ("0,0", primary_display.bounds().origin().ToString());
+
+ const gfx::Rect& primary_bounds = primary_display.bounds();
+ const gfx::Rect& secondary_bounds = secondary_display->bounds();
+ gfx::Point new_secondary_origin = primary_bounds.origin();
+
+ DisplayLayout::Position position = layout.position;
+
+ // Ignore the offset in case the secondary display doesn't share edges with
+ // the primary display.
+ int offset = layout.offset;
+ if (position == DisplayLayout::TOP || position == DisplayLayout::BOTTOM) {
+ offset = std::min(
+ offset, primary_bounds.width() - kMinimumOverlapForInvalidOffset);
+ offset = std::max(
+ offset, -secondary_bounds.width() + kMinimumOverlapForInvalidOffset);
+ } else {
+ offset = std::min(
+ offset, primary_bounds.height() - kMinimumOverlapForInvalidOffset);
+ offset = std::max(
+ offset, -secondary_bounds.height() + kMinimumOverlapForInvalidOffset);
+ }
+ switch (position) {
+ case DisplayLayout::TOP:
+ new_secondary_origin.Offset(offset, -secondary_bounds.height());
+ break;
+ case DisplayLayout::RIGHT:
+ new_secondary_origin.Offset(primary_bounds.width(), offset);
+ break;
+ case DisplayLayout::BOTTOM:
+ new_secondary_origin.Offset(offset, primary_bounds.height());
+ break;
+ case DisplayLayout::LEFT:
+ new_secondary_origin.Offset(-secondary_bounds.width(), offset);
+ break;
+ }
+ gfx::Insets insets = secondary_display->GetWorkAreaInsets();
+ secondary_display->set_bounds(
+ gfx::Rect(new_secondary_origin, secondary_bounds.size()));
+ secondary_display->UpdateWorkAreaFromInsets(insets);
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/display/display_manager.h b/chromium/ash/display/display_manager.h
new file mode 100644
index 00000000000..f7554a79705
--- /dev/null
+++ b/chromium/ash/display/display_manager.h
@@ -0,0 +1,305 @@
+// Copyright (c) 2012 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 ASH_DISPLAY_DISPLAY_MANAGER_H_
+#define ASH_DISPLAY_DISPLAY_MANAGER_H_
+
+#include <string>
+#include <vector>
+
+#include "ash/ash_export.h"
+#include "ash/display/display_info.h"
+#include "ash/display/display_layout.h"
+#include "base/compiler_specific.h"
+#include "base/gtest_prod_util.h"
+#include "base/memory/scoped_ptr.h"
+#include "ui/gfx/display.h"
+
+#if defined(OS_CHROMEOS)
+#include "chromeos/display/output_configurator.h"
+#endif
+
+namespace gfx {
+class Display;
+class Insets;
+class Rect;
+}
+
+namespace ash {
+class AcceleratorControllerTest;
+class DisplayController;
+
+namespace test {
+class DisplayManagerTestApi;
+class SystemGestureEventFilterTest;
+}
+namespace internal {
+class DisplayLayoutStore;
+
+// DisplayManager maintains the current display configurations,
+// and notifies observers when configuration changes.
+//
+// TODO(oshima): Make this non internal.
+class ASH_EXPORT DisplayManager
+#if defined(OS_CHROMEOS)
+ : public chromeos::OutputConfigurator::SoftwareMirroringController
+#endif
+ {
+ public:
+ class ASH_EXPORT Delegate {
+ public:
+ virtual ~Delegate() {}
+
+ // Create or updates the mirror window with |display_info|.
+ virtual void CreateOrUpdateMirrorWindow(
+ const DisplayInfo& display_info) = 0;
+
+ // Closes the mirror window if exists.
+ virtual void CloseMirrorWindow() = 0;
+
+ // Called before and after the display configuration changes.
+ virtual void PreDisplayConfigurationChange() = 0;
+ virtual void PostDisplayConfigurationChange() = 0;
+ };
+
+ // Returns the list of possible UI scales for the display.
+ static std::vector<float> GetScalesForDisplay(const DisplayInfo& info);
+
+ // Returns next valid UI scale.
+ static float GetNextUIScale(const DisplayInfo& info, bool up);
+
+ // Updates the bounds of the display given by |secondary_display_id|
+ // according to |layout|.
+ static void UpdateDisplayBoundsForLayoutById(
+ const DisplayLayout& layout,
+ const gfx::Display& primary_display,
+ int64 secondary_display_id);
+
+ DisplayManager();
+ virtual ~DisplayManager();
+
+ DisplayLayoutStore* layout_store() {
+ return layout_store_.get();
+ }
+
+ void set_delegate(Delegate* delegate) { delegate_ = delegate; }
+
+ // When set to true, the MonitorManager calls OnDisplayBoundsChanged
+ // even if the display's bounds didn't change. Used to swap primary
+ // display.
+ void set_force_bounds_changed(bool force_bounds_changed) {
+ force_bounds_changed_ = force_bounds_changed;
+ }
+
+ // Returns the display id of the first display in the outupt list.
+ int64 first_display_id() const { return first_display_id_; }
+
+ // Initializes displays using command line flag, or uses
+ // defualt if no options are specified.
+ void InitFromCommandLine();
+
+ // True if the given |display| is currently connected.
+ bool IsActiveDisplay(const gfx::Display& display) const;
+
+ // True if there is an internal display.
+ bool HasInternalDisplay() const;
+
+ bool IsInternalDisplayId(int64 id) const;
+
+ // Returns the display layout used for current displays.
+ DisplayLayout GetCurrentDisplayLayout();
+
+ // Returns the current display pair.
+ DisplayIdPair GetCurrentDisplayIdPair() const;
+
+ // Returns display for given |id|;
+ const gfx::Display& GetDisplayForId(int64 id) const;
+
+ // Finds the display that contains |point| in screeen coordinates.
+ // Returns invalid display if there is no display that can satisfy
+ // the condition.
+ const gfx::Display& FindDisplayContainingPoint(
+ const gfx::Point& point_in_screen) const;
+
+ // Sets the work area's |insets| to the display given by |display_id|.
+ bool UpdateWorkAreaOfDisplay(int64 display_id, const gfx::Insets& insets);
+
+ // Registers the overscan insets for the display of the specified ID. Note
+ // that the insets size should be specified in DIP size. It also triggers the
+ // display's bounds change.
+ void SetOverscanInsets(int64 display_id, const gfx::Insets& insets_in_dip);
+
+ // Sets the display's rotation.
+ void SetDisplayRotation(int64 display_id, gfx::Display::Rotation rotation);
+
+ // Sets the display's ui scale.
+ void SetDisplayUIScale(int64 display_id, float ui_scale);
+
+ // Sets the display's resolution.
+ void SetDisplayResolution(int64 display_id, const gfx::Size& resolution);
+
+ // Register per display properties. |overscan_insets| is NULL if
+ // the display has no custom overscan insets.
+ void RegisterDisplayProperty(int64 display_id,
+ gfx::Display::Rotation rotation,
+ float ui_scale,
+ const gfx::Insets* overscan_insets,
+ const gfx::Size& resolution_in_pixels);
+
+ // Returns the display's selected resolution.
+ bool GetSelectedResolutionForDisplayId(int64 display_id,
+ gfx::Size* resolution_out) const;
+
+ // Tells if display rotation/ui scaling features are enabled.
+ bool IsDisplayRotationEnabled() const;
+ bool IsDisplayUIScalingEnabled() const;
+
+ // Returns the current overscan insets for the specified |display_id|.
+ // Returns an empty insets (0, 0, 0, 0) if no insets are specified for
+ // the display.
+ gfx::Insets GetOverscanInsets(int64 display_id) const;
+
+ // Called when display configuration has changed. The new display
+ // configurations is passed as a vector of Display object, which
+ // contains each display's new infomration.
+ void OnNativeDisplaysChanged(
+ const std::vector<DisplayInfo>& display_info_list);
+
+ // Updates the internal display data and notifies observers about the changes.
+ void UpdateDisplays(const std::vector<DisplayInfo>& display_info_list);
+
+ // Updates current displays using current |display_info_|.
+ void UpdateDisplays();
+
+ // Returns the display at |index|. The display at 0 is
+ // no longer considered "primary".
+ const gfx::Display& GetDisplayAt(size_t index) const;
+
+ const gfx::Display& GetPrimaryDisplayCandidate() const;
+
+ // Returns the logical number of displays. This returns 1
+ // when displays are mirrored.
+ size_t GetNumDisplays() const;
+
+ // Returns the number of connected displays. This returns 2
+ // when displays are mirrored.
+ size_t num_connected_displays() const { return num_connected_displays_; }
+
+ // Returns the mirroring status.
+ bool IsMirrored() const;
+ const gfx::Display& mirrored_display() const { return mirrored_display_; }
+
+ // Retuns the display info associated with |display_id|.
+ const DisplayInfo& GetDisplayInfo(int64 display_id) const;
+
+ // Returns the human-readable name for the display |id|.
+ std::string GetDisplayNameForId(int64 id);
+
+ // Returns the display id that is capable of UI scaling. On device,
+ // this returns internal display's ID if its device scale factor is 2,
+ // or invalid ID if such internal display doesn't exist. On linux
+ // desktop, this returns the first display ID.
+ int64 GetDisplayIdForUIScaling() const;
+
+ // Change the mirror mode.
+ void SetMirrorMode(bool mirrored);
+
+ // Used to emulate display change when run in a desktop environment instead
+ // of on a device.
+ void AddRemoveDisplay();
+ void ToggleDisplayScaleFactor();
+
+ // SoftwareMirroringController override:
+#if defined(OS_CHROMEOS)
+ virtual void SetSoftwareMirroring(bool enabled) OVERRIDE;
+#else
+ void SetSoftwareMirroring(bool enabled);
+#endif
+
+ // Update the bounds of the display given by |display_id|.
+ bool UpdateDisplayBounds(int64 display_id,
+ const gfx::Rect& new_bounds);
+private:
+ FRIEND_TEST_ALL_PREFIXES(ExtendedDesktopTest, ConvertPoint);
+ FRIEND_TEST_ALL_PREFIXES(DisplayManagerTest, TestNativeDisplaysChanged);
+ FRIEND_TEST_ALL_PREFIXES(DisplayManagerTest,
+ NativeDisplaysChangedAfterPrimaryChange);
+ FRIEND_TEST_ALL_PREFIXES(DisplayManagerTest, AutomaticOverscanInsets);
+ friend class ash::AcceleratorControllerTest;
+ friend class test::DisplayManagerTestApi;
+ friend class test::SystemGestureEventFilterTest;
+ friend class DisplayManagerTest;
+
+ typedef std::vector<gfx::Display> DisplayList;
+
+ void set_change_display_upon_host_resize(bool value) {
+ change_display_upon_host_resize_ = value;
+ }
+
+ gfx::Display* FindDisplayForId(int64 id);
+
+ // Add the mirror display's display info if the software based
+ // mirroring is in use.
+ void AddMirrorDisplayInfoIfAny(std::vector<DisplayInfo>* display_info_list);
+
+ // Inserts and update the DisplayInfo according to the overscan
+ // state. Note that The DisplayInfo stored in the |internal_display_info_|
+ // can be different from |new_info| (due to overscan state), so
+ // you must use |GetDisplayInfo| to get the correct DisplayInfo for
+ // a display.
+ void InsertAndUpdateDisplayInfo(const DisplayInfo& new_info);
+
+ // Creates a display object from the DisplayInfo for |display_id|.
+ gfx::Display CreateDisplayFromDisplayInfoById(int64 display_id);
+
+ // Updates the bounds of the secondary display in |display_list|
+ // using the layout registered for the display pair and set the
+ // index of display updated to |updated_index|. Returns true
+ // if the secondary display's bounds has been changed from current
+ // value, or false otherwise.
+ bool UpdateSecondaryDisplayBoundsForLayout(DisplayList* display_list,
+ size_t* updated_index) const;
+
+ static void UpdateDisplayBoundsForLayout(
+ const DisplayLayout& layout,
+ const gfx::Display& primary_display,
+ gfx::Display* secondary_display);
+
+ Delegate* delegate_; // not owned.
+
+ scoped_ptr<DisplayLayoutStore> layout_store_;
+
+ int64 first_display_id_;
+
+ gfx::Display mirrored_display_;
+
+ // List of current active dispays.
+ DisplayList displays_;
+
+ int num_connected_displays_;
+
+ bool force_bounds_changed_;
+
+ // The mapping from the display ID to its internal data.
+ std::map<int64, DisplayInfo> display_info_;
+
+ // Selected resolutions for displays. Key is the displays' ID.
+ std::map<int64, gfx::Size> resolutions_;
+
+ // When set to true, the host window's resize event updates
+ // the display's size. This is set to true when running on
+ // desktop environment (for debugging) so that resizing the host
+ // window wil update the display properly. This is set to false
+ // on device as well as during the unit tests.
+ bool change_display_upon_host_resize_;
+
+ bool software_mirroring_enabled_;
+
+ DISALLOW_COPY_AND_ASSIGN(DisplayManager);
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_DISPLAY_DISPLAY_MANAGER_H_
diff --git a/chromium/ash/display/display_manager_unittest.cc b/chromium/ash/display/display_manager_unittest.cc
new file mode 100644
index 00000000000..f7b6d1ded4f
--- /dev/null
+++ b/chromium/ash/display/display_manager_unittest.cc
@@ -0,0 +1,1052 @@
+// Copyright (c) 2012 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.
+
+#include "ash/display/display_manager.h"
+
+#include "ash/display/display_controller.h"
+#include "ash/display/display_layout_store.h"
+#include "ash/screen_ash.h"
+#include "ash/shell.h"
+#include "ash/test/ash_test_base.h"
+#include "ash/test/display_manager_test_api.h"
+#include "ash/test/mirror_window_test_api.h"
+#include "base/format_macros.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/stringprintf.h"
+#include "ui/aura/env.h"
+#include "ui/aura/root_window.h"
+#include "ui/aura/test/event_generator.h"
+#include "ui/aura/window_observer.h"
+#include "ui/gfx/display_observer.h"
+#include "ui/gfx/display.h"
+
+namespace ash {
+namespace internal {
+
+using std::vector;
+using std::string;
+
+using base::StringPrintf;
+
+namespace {
+
+std::string ToDisplayName(int64 id) {
+ return "x-" + base::Int64ToString(id);
+}
+
+} // namespace
+
+class DisplayManagerTest : public test::AshTestBase,
+ public gfx::DisplayObserver,
+ public aura::WindowObserver {
+ public:
+ DisplayManagerTest()
+ : removed_count_(0U),
+ root_window_destroyed_(false) {
+ }
+ virtual ~DisplayManagerTest() {}
+
+ virtual void SetUp() OVERRIDE {
+ AshTestBase::SetUp();
+ Shell::GetScreen()->AddObserver(this);
+ Shell::GetPrimaryRootWindow()->AddObserver(this);
+ }
+ virtual void TearDown() OVERRIDE {
+ Shell::GetPrimaryRootWindow()->RemoveObserver(this);
+ Shell::GetScreen()->RemoveObserver(this);
+ AshTestBase::TearDown();
+ }
+
+ DisplayManager* display_manager() {
+ return Shell::GetInstance()->display_manager();
+ }
+ const vector<gfx::Display>& changed() const { return changed_; }
+ const vector<gfx::Display>& added() const { return added_; }
+
+ string GetCountSummary() const {
+ return StringPrintf("%" PRIuS " %" PRIuS " %" PRIuS,
+ changed_.size(), added_.size(), removed_count_);
+ }
+
+ void reset() {
+ changed_.clear();
+ added_.clear();
+ removed_count_ = 0U;
+ root_window_destroyed_ = false;
+ }
+
+ bool root_window_destroyed() const {
+ return root_window_destroyed_;
+ }
+
+ const DisplayInfo& GetDisplayInfo(const gfx::Display& display) {
+ return display_manager()->GetDisplayInfo(display.id());
+ }
+
+ const DisplayInfo& GetDisplayInfoAt(int index) {
+ return GetDisplayInfo(display_manager()->GetDisplayAt(index));
+ }
+
+ const gfx::Display& GetDisplayForId(int64 id) {
+ return display_manager()->GetDisplayForId(id);
+ }
+
+ const DisplayInfo& GetDisplayInfoForId(int64 id) {
+ return GetDisplayInfo(display_manager()->GetDisplayForId(id));
+ }
+
+ const gfx::Display GetMirroredDisplay() {
+ return Shell::GetInstance()->display_manager()->mirrored_display();
+ }
+
+ // aura::DisplayObserver overrides:
+ virtual void OnDisplayBoundsChanged(const gfx::Display& display) OVERRIDE {
+ changed_.push_back(display);
+ }
+ virtual void OnDisplayAdded(const gfx::Display& new_display) OVERRIDE {
+ added_.push_back(new_display);
+ }
+ virtual void OnDisplayRemoved(const gfx::Display& old_display) OVERRIDE {
+ ++removed_count_;
+ }
+
+ // aura::WindowObserver overrides:
+ virtual void OnWindowDestroying(aura::Window* window) OVERRIDE {
+ ASSERT_EQ(Shell::GetPrimaryRootWindow(), window);
+ root_window_destroyed_ = true;
+ }
+
+ private:
+ vector<gfx::Display> changed_;
+ vector<gfx::Display> added_;
+ size_t removed_count_;
+ bool root_window_destroyed_;
+
+ DISALLOW_COPY_AND_ASSIGN(DisplayManagerTest);
+};
+
+TEST_F(DisplayManagerTest, UpdateDisplayTest) {
+ if (!SupportsMultipleDisplays())
+ return;
+
+ EXPECT_EQ(1U, display_manager()->GetNumDisplays());
+
+ // Update primary and add seconary.
+ UpdateDisplay("100+0-500x500,0+501-400x400");
+ EXPECT_EQ(2U, display_manager()->GetNumDisplays());
+ EXPECT_EQ("0,0 500x500",
+ display_manager()->GetDisplayAt(0).bounds().ToString());
+
+ EXPECT_EQ("1 1 0", GetCountSummary());
+ EXPECT_EQ(display_manager()->GetDisplayAt(0).id(), changed()[0].id());
+ EXPECT_EQ(display_manager()->GetDisplayAt(1).id(), added()[0].id());
+ EXPECT_EQ("0,0 500x500", changed()[0].bounds().ToString());
+ // Secondary display is on right.
+ EXPECT_EQ("500,0 400x400", added()[0].bounds().ToString());
+ EXPECT_EQ("0,501 400x400",
+ GetDisplayInfo(added()[0]).bounds_in_pixel().ToString());
+ reset();
+
+ // Delete secondary.
+ UpdateDisplay("100+0-500x500");
+ EXPECT_EQ("0 0 1", GetCountSummary());
+ reset();
+
+ // Change primary.
+ UpdateDisplay("1+1-1000x600");
+ EXPECT_EQ("1 0 0", GetCountSummary());
+ EXPECT_EQ(display_manager()->GetDisplayAt(0).id(), changed()[0].id());
+ EXPECT_EQ("0,0 1000x600", changed()[0].bounds().ToString());
+ reset();
+
+ // Add secondary.
+ UpdateDisplay("1+1-1000x600,1002+0-600x400");
+ EXPECT_EQ(2U, display_manager()->GetNumDisplays());
+ EXPECT_EQ("0 1 0", GetCountSummary());
+ EXPECT_EQ(display_manager()->GetDisplayAt(1).id(), added()[0].id());
+ // Secondary display is on right.
+ EXPECT_EQ("1000,0 600x400", added()[0].bounds().ToString());
+ EXPECT_EQ("1002,0 600x400",
+ GetDisplayInfo(added()[0]).bounds_in_pixel().ToString());
+ reset();
+
+ // Secondary removed, primary changed.
+ UpdateDisplay("1+1-800x300");
+ EXPECT_EQ(1U, display_manager()->GetNumDisplays());
+ EXPECT_EQ("1 0 1", GetCountSummary());
+ EXPECT_EQ(display_manager()->GetDisplayAt(0).id(), changed()[0].id());
+ EXPECT_EQ("0,0 800x300", changed()[0].bounds().ToString());
+ reset();
+
+ // # of display can go to zero when screen is off.
+ const vector<DisplayInfo> empty;
+ display_manager()->OnNativeDisplaysChanged(empty);
+ EXPECT_EQ(1U, display_manager()->GetNumDisplays());
+ EXPECT_EQ("0 0 0", GetCountSummary());
+ EXPECT_FALSE(root_window_destroyed());
+ // Display configuration stays the same
+ EXPECT_EQ("0,0 800x300",
+ display_manager()->GetDisplayAt(0).bounds().ToString());
+ reset();
+
+ // Connect to display again
+ UpdateDisplay("100+100-500x400");
+ EXPECT_EQ(1U, display_manager()->GetNumDisplays());
+ EXPECT_EQ("1 0 0", GetCountSummary());
+ EXPECT_FALSE(root_window_destroyed());
+ EXPECT_EQ("0,0 500x400", changed()[0].bounds().ToString());
+ EXPECT_EQ("100,100 500x400",
+ GetDisplayInfo(changed()[0]).bounds_in_pixel().ToString());
+ reset();
+
+ // Go back to zero and wake up with multiple displays.
+ display_manager()->OnNativeDisplaysChanged(empty);
+ EXPECT_EQ(1U, display_manager()->GetNumDisplays());
+ EXPECT_FALSE(root_window_destroyed());
+ reset();
+
+ // Add secondary.
+ UpdateDisplay("0+0-1000x600,1000+1000-600x400");
+ EXPECT_EQ(2U, display_manager()->GetNumDisplays());
+ EXPECT_EQ("0,0 1000x600",
+ display_manager()->GetDisplayAt(0).bounds().ToString());
+ // Secondary display is on right.
+ EXPECT_EQ("1000,0 600x400",
+ display_manager()->GetDisplayAt(1).bounds().ToString());
+ EXPECT_EQ("1000,1000 600x400",
+ GetDisplayInfoAt(1).bounds_in_pixel().ToString());
+ reset();
+
+ // Changing primary will update secondary as well.
+ UpdateDisplay("0+0-800x600,1000+1000-600x400");
+ EXPECT_EQ("2 0 0", GetCountSummary());
+ reset();
+ EXPECT_EQ("0,0 800x600",
+ display_manager()->GetDisplayAt(0).bounds().ToString());
+ EXPECT_EQ("800,0 600x400",
+ display_manager()->GetDisplayAt(1).bounds().ToString());
+}
+
+// Test in emulation mode (use_fullscreen_host_window=false)
+TEST_F(DisplayManagerTest, EmulatorTest) {
+ if (!SupportsMultipleDisplays())
+ return;
+
+ EXPECT_EQ(1U, display_manager()->GetNumDisplays());
+
+ display_manager()->AddRemoveDisplay();
+ // Update primary and add seconary.
+ EXPECT_EQ(2U, display_manager()->GetNumDisplays());
+ EXPECT_EQ("0 1 0", GetCountSummary());
+ reset();
+
+ display_manager()->AddRemoveDisplay();
+ EXPECT_EQ(1U, display_manager()->GetNumDisplays());
+ EXPECT_EQ("0 0 1", GetCountSummary());
+ reset();
+
+ display_manager()->AddRemoveDisplay();
+ EXPECT_EQ(2U, display_manager()->GetNumDisplays());
+ EXPECT_EQ("0 1 0", GetCountSummary());
+ reset();
+}
+
+TEST_F(DisplayManagerTest, OverscanInsetsTest) {
+ if (!SupportsMultipleDisplays())
+ return;
+
+ UpdateDisplay("0+0-500x500,0+501-400x400");
+ reset();
+ ASSERT_EQ(2u, display_manager()->GetNumDisplays());
+ const DisplayInfo& display_info1 = GetDisplayInfoAt(0);
+ const DisplayInfo& display_info2 = GetDisplayInfoAt(1);
+ display_manager()->SetOverscanInsets(
+ display_info2.id(), gfx::Insets(13, 12, 11, 10));
+
+ std::vector<gfx::Display> changed_displays = changed();
+ EXPECT_EQ(1u, changed_displays.size());
+ EXPECT_EQ(display_info2.id(), changed_displays[0].id());
+ EXPECT_EQ("0,0 500x500",
+ GetDisplayInfoAt(0).bounds_in_pixel().ToString());
+ DisplayInfo updated_display_info2 = GetDisplayInfoAt(1);
+ EXPECT_EQ("0,501 400x400",
+ updated_display_info2.bounds_in_pixel().ToString());
+ EXPECT_EQ("378x376",
+ updated_display_info2.size_in_pixel().ToString());
+ EXPECT_EQ("13,12,11,10",
+ updated_display_info2.overscan_insets_in_dip().ToString());
+ EXPECT_EQ("500,0 378x376",
+ ScreenAsh::GetSecondaryDisplay().bounds().ToString());
+
+ // Make sure that SetOverscanInsets() is idempotent.
+ display_manager()->SetOverscanInsets(display_info1.id(), gfx::Insets());
+ display_manager()->SetOverscanInsets(
+ display_info2.id(), gfx::Insets(13, 12, 11, 10));
+ EXPECT_EQ("0,0 500x500",
+ GetDisplayInfoAt(0).bounds_in_pixel().ToString());
+ updated_display_info2 = GetDisplayInfoAt(1);
+ EXPECT_EQ("0,501 400x400",
+ updated_display_info2.bounds_in_pixel().ToString());
+ EXPECT_EQ("378x376",
+ updated_display_info2.size_in_pixel().ToString());
+ EXPECT_EQ("13,12,11,10",
+ updated_display_info2.overscan_insets_in_dip().ToString());
+
+ display_manager()->SetOverscanInsets(
+ display_info2.id(), gfx::Insets(10, 11, 12, 13));
+ EXPECT_EQ("0,0 500x500",
+ GetDisplayInfoAt(0).bounds_in_pixel().ToString());
+ EXPECT_EQ("376x378",
+ GetDisplayInfoAt(1).size_in_pixel().ToString());
+ EXPECT_EQ("10,11,12,13",
+ GetDisplayInfoAt(1).overscan_insets_in_dip().ToString());
+
+ // Recreate a new 2nd display. It won't apply the overscan inset because the
+ // new display has a different ID.
+ UpdateDisplay("0+0-500x500");
+ UpdateDisplay("0+0-500x500,0+501-400x400");
+ EXPECT_EQ("0,0 500x500",
+ GetDisplayInfoAt(0).bounds_in_pixel().ToString());
+ EXPECT_EQ("0,501 400x400",
+ GetDisplayInfoAt(1).bounds_in_pixel().ToString());
+
+ // Recreate the displays with the same ID. It should apply the overscan
+ // inset.
+ UpdateDisplay("0+0-500x500");
+ std::vector<DisplayInfo> display_info_list;
+ display_info_list.push_back(display_info1);
+ display_info_list.push_back(display_info2);
+ display_manager()->OnNativeDisplaysChanged(display_info_list);
+ EXPECT_EQ("1,1 500x500",
+ GetDisplayInfoAt(0).bounds_in_pixel().ToString());
+ updated_display_info2 = GetDisplayInfoAt(1);
+ EXPECT_EQ("376x378",
+ updated_display_info2.size_in_pixel().ToString());
+ EXPECT_EQ("10,11,12,13",
+ updated_display_info2.overscan_insets_in_dip().ToString());
+
+ // HiDPI but overscan display. The specified insets size should be doubled.
+ UpdateDisplay("0+0-500x500,0+501-400x400*2");
+ display_manager()->SetOverscanInsets(
+ display_manager()->GetDisplayAt(1).id(), gfx::Insets(4, 5, 6, 7));
+ EXPECT_EQ("0,0 500x500",
+ GetDisplayInfoAt(0).bounds_in_pixel().ToString());
+ updated_display_info2 = GetDisplayInfoAt(1);
+ EXPECT_EQ("0,501 400x400",
+ updated_display_info2.bounds_in_pixel().ToString());
+ EXPECT_EQ("376x380",
+ updated_display_info2.size_in_pixel().ToString());
+ EXPECT_EQ("4,5,6,7",
+ updated_display_info2.overscan_insets_in_dip().ToString());
+ EXPECT_EQ("8,10,12,14",
+ updated_display_info2.GetOverscanInsetsInPixel().ToString());
+
+ // Make sure switching primary display applies the overscan offset only once.
+ ash::Shell::GetInstance()->display_controller()->SetPrimaryDisplay(
+ ScreenAsh::GetSecondaryDisplay());
+ EXPECT_EQ("-500,0 500x500",
+ ScreenAsh::GetSecondaryDisplay().bounds().ToString());
+ EXPECT_EQ("0,0 500x500",
+ GetDisplayInfo(ScreenAsh::GetSecondaryDisplay()).
+ bounds_in_pixel().ToString());
+ EXPECT_EQ("0,501 400x400",
+ GetDisplayInfo(Shell::GetScreen()->GetPrimaryDisplay()).
+ bounds_in_pixel().ToString());
+ EXPECT_EQ("0,0 188x190",
+ Shell::GetScreen()->GetPrimaryDisplay().bounds().ToString());
+}
+
+TEST_F(DisplayManagerTest, ZeroOverscanInsets) {
+ if (!SupportsMultipleDisplays())
+ return;
+
+ // Make sure the display change events is emitted for overscan inset changes.
+ UpdateDisplay("0+0-500x500,0+501-400x400");
+ ASSERT_EQ(2u, display_manager()->GetNumDisplays());
+ int64 display2_id = display_manager()->GetDisplayAt(1).id();
+
+ reset();
+ display_manager()->SetOverscanInsets(display2_id, gfx::Insets(0, 0, 0, 0));
+ EXPECT_EQ(0u, changed().size());
+
+ reset();
+ display_manager()->SetOverscanInsets(display2_id, gfx::Insets(1, 0, 0, 0));
+ EXPECT_EQ(1u, changed().size());
+ EXPECT_EQ(display2_id, changed()[0].id());
+
+ reset();
+ display_manager()->SetOverscanInsets(display2_id, gfx::Insets(0, 0, 0, 0));
+ EXPECT_EQ(1u, changed().size());
+ EXPECT_EQ(display2_id, changed()[0].id());
+}
+
+TEST_F(DisplayManagerTest, TestDeviceScaleOnlyChange) {
+ if (!SupportsHostWindowResize())
+ return;
+
+ UpdateDisplay("1000x600");
+ EXPECT_EQ(1,
+ Shell::GetPrimaryRootWindow()->compositor()->device_scale_factor());
+ EXPECT_EQ("1000x600",
+ Shell::GetPrimaryRootWindow()->bounds().size().ToString());
+ UpdateDisplay("1000x600*2");
+ EXPECT_EQ(2,
+ Shell::GetPrimaryRootWindow()->compositor()->device_scale_factor());
+ EXPECT_EQ("500x300",
+ Shell::GetPrimaryRootWindow()->bounds().size().ToString());
+}
+
+DisplayInfo CreateDisplayInfo(int64 id, const gfx::Rect& bounds) {
+ DisplayInfo info(id, ToDisplayName(id), false);
+ info.SetBounds(bounds);
+ return info;
+}
+
+TEST_F(DisplayManagerTest, TestNativeDisplaysChanged) {
+ const int64 internal_display_id =
+ test::DisplayManagerTestApi(display_manager()).
+ SetFirstDisplayAsInternalDisplay();
+ const int external_id = 10;
+ const int mirror_id = 11;
+ const int64 invalid_id = gfx::Display::kInvalidDisplayID;
+ const DisplayInfo internal_display_info =
+ CreateDisplayInfo(internal_display_id, gfx::Rect(0, 0, 500, 500));
+ const DisplayInfo external_display_info =
+ CreateDisplayInfo(external_id, gfx::Rect(1, 1, 100, 100));
+ const DisplayInfo mirrored_display_info =
+ CreateDisplayInfo(mirror_id, gfx::Rect(0, 0, 500, 500));
+
+ EXPECT_EQ(1U, display_manager()->GetNumDisplays());
+ EXPECT_EQ(1U, display_manager()->num_connected_displays());
+ std::string default_bounds =
+ display_manager()->GetDisplayAt(0).bounds().ToString();
+
+ std::vector<DisplayInfo> display_info_list;
+ // Primary disconnected.
+ display_manager()->OnNativeDisplaysChanged(display_info_list);
+ EXPECT_EQ(1U, display_manager()->GetNumDisplays());
+ EXPECT_EQ(default_bounds,
+ display_manager()->GetDisplayAt(0).bounds().ToString());
+ EXPECT_EQ(1U, display_manager()->num_connected_displays());
+ EXPECT_FALSE(display_manager()->mirrored_display().is_valid());
+
+ if (!SupportsMultipleDisplays())
+ return;
+
+ // External connected while primary was disconnected.
+ display_info_list.push_back(external_display_info);
+ display_manager()->OnNativeDisplaysChanged(display_info_list);
+ EXPECT_EQ(1U, display_manager()->GetNumDisplays());
+
+ EXPECT_EQ(invalid_id, GetDisplayForId(internal_display_id).id());
+ EXPECT_EQ("1,1 100x100",
+ GetDisplayInfoForId(external_id).bounds_in_pixel().ToString());
+ EXPECT_EQ(1U, display_manager()->num_connected_displays());
+ EXPECT_FALSE(display_manager()->mirrored_display().is_valid());
+ EXPECT_EQ(external_id, Shell::GetScreen()->GetPrimaryDisplay().id());
+
+ EXPECT_EQ(internal_display_id, gfx::Display::InternalDisplayId());
+
+ // Primary connected, with different bounds.
+ display_info_list.clear();
+ display_info_list.push_back(internal_display_info);
+ display_info_list.push_back(external_display_info);
+ display_manager()->OnNativeDisplaysChanged(display_info_list);
+ EXPECT_EQ(2U, display_manager()->GetNumDisplays());
+ EXPECT_EQ(internal_display_id, Shell::GetScreen()->GetPrimaryDisplay().id());
+
+ // This combinatino is new, so internal display becomes primary.
+ EXPECT_EQ("0,0 500x500",
+ GetDisplayForId(internal_display_id).bounds().ToString());
+ EXPECT_EQ("1,1 100x100",
+ GetDisplayInfoForId(10).bounds_in_pixel().ToString());
+ EXPECT_EQ(2U, display_manager()->num_connected_displays());
+ EXPECT_FALSE(display_manager()->mirrored_display().is_valid());
+ EXPECT_EQ(ToDisplayName(internal_display_id),
+ display_manager()->GetDisplayNameForId(internal_display_id));
+
+ // Emulate suspend.
+ display_info_list.clear();
+ display_manager()->OnNativeDisplaysChanged(display_info_list);
+ EXPECT_EQ(2U, display_manager()->GetNumDisplays());
+ EXPECT_EQ("0,0 500x500",
+ GetDisplayForId(internal_display_id).bounds().ToString());
+ EXPECT_EQ("1,1 100x100",
+ GetDisplayInfoForId(10).bounds_in_pixel().ToString());
+ EXPECT_EQ(2U, display_manager()->num_connected_displays());
+ EXPECT_FALSE(display_manager()->mirrored_display().is_valid());
+ EXPECT_EQ(ToDisplayName(internal_display_id),
+ display_manager()->GetDisplayNameForId(internal_display_id));
+
+ // External display has disconnected then resumed.
+ display_info_list.push_back(internal_display_info);
+ display_manager()->OnNativeDisplaysChanged(display_info_list);
+ EXPECT_EQ(1U, display_manager()->GetNumDisplays());
+ EXPECT_EQ("0,0 500x500",
+ GetDisplayForId(internal_display_id).bounds().ToString());
+ EXPECT_EQ(1U, display_manager()->num_connected_displays());
+ EXPECT_FALSE(display_manager()->mirrored_display().is_valid());
+
+ // External display was changed during suspend.
+ display_info_list.push_back(external_display_info);
+ display_manager()->OnNativeDisplaysChanged(display_info_list);
+ EXPECT_EQ(2U, display_manager()->GetNumDisplays());
+ EXPECT_EQ(2U, display_manager()->num_connected_displays());
+ EXPECT_FALSE(display_manager()->mirrored_display().is_valid());
+
+ // suspend...
+ display_info_list.clear();
+ display_manager()->OnNativeDisplaysChanged(display_info_list);
+ EXPECT_EQ(2U, display_manager()->GetNumDisplays());
+ EXPECT_EQ(2U, display_manager()->num_connected_displays());
+ EXPECT_FALSE(display_manager()->mirrored_display().is_valid());
+
+ // and resume with different external display.
+ display_info_list.push_back(internal_display_info);
+ display_info_list.push_back(CreateDisplayInfo(12, gfx::Rect(1, 1, 100, 100)));
+ display_manager()->OnNativeDisplaysChanged(display_info_list);
+ EXPECT_EQ(2U, display_manager()->GetNumDisplays());
+ EXPECT_EQ(2U, display_manager()->num_connected_displays());
+ EXPECT_FALSE(display_manager()->mirrored_display().is_valid());
+ EXPECT_FALSE(display_manager()->IsMirrored());
+
+ // mirrored...
+ display_info_list.clear();
+ display_info_list.push_back(internal_display_info);
+ display_info_list.push_back(mirrored_display_info);
+ display_manager()->OnNativeDisplaysChanged(display_info_list);
+ EXPECT_EQ(1U, display_manager()->GetNumDisplays());
+ EXPECT_EQ("0,0 500x500",
+ GetDisplayForId(internal_display_id).bounds().ToString());
+ EXPECT_EQ(2U, display_manager()->num_connected_displays());
+ EXPECT_EQ(11U, display_manager()->mirrored_display().id());
+ EXPECT_TRUE(display_manager()->IsMirrored());
+
+ // Test display name.
+ EXPECT_EQ(ToDisplayName(internal_display_id),
+ display_manager()->GetDisplayNameForId(internal_display_id));
+ EXPECT_EQ("x-10", display_manager()->GetDisplayNameForId(10));
+ EXPECT_EQ("x-11", display_manager()->GetDisplayNameForId(11));
+ EXPECT_EQ("x-12", display_manager()->GetDisplayNameForId(12));
+ // Default name for the id that doesn't exist.
+ EXPECT_EQ("Display 100", display_manager()->GetDisplayNameForId(100));
+
+ // and exit mirroring.
+ display_info_list.clear();
+ display_info_list.push_back(internal_display_info);
+ display_info_list.push_back(external_display_info);
+ display_manager()->OnNativeDisplaysChanged(display_info_list);
+ EXPECT_EQ(2U, display_manager()->GetNumDisplays());
+ EXPECT_EQ(2U, display_manager()->num_connected_displays());
+ EXPECT_FALSE(display_manager()->IsMirrored());
+ EXPECT_EQ("0,0 500x500",
+ GetDisplayForId(internal_display_id).bounds().ToString());
+ EXPECT_EQ("500,0 100x100",
+ GetDisplayForId(10).bounds().ToString());
+
+ // Turn off internal
+ display_info_list.clear();
+ display_info_list.push_back(external_display_info);
+ display_manager()->OnNativeDisplaysChanged(display_info_list);
+ EXPECT_EQ(1U, display_manager()->GetNumDisplays());
+ EXPECT_EQ(invalid_id, GetDisplayForId(internal_display_id).id());
+ EXPECT_EQ("1,1 100x100",
+ GetDisplayInfoForId(external_id).bounds_in_pixel().ToString());
+ EXPECT_EQ(1U, display_manager()->num_connected_displays());
+ EXPECT_FALSE(display_manager()->mirrored_display().is_valid());
+
+ // Switched to another display
+ display_info_list.clear();
+ display_info_list.push_back(internal_display_info);
+ display_manager()->OnNativeDisplaysChanged(display_info_list);
+ EXPECT_EQ(1U, display_manager()->GetNumDisplays());
+ EXPECT_EQ(
+ "0,0 500x500",
+ GetDisplayInfoForId(internal_display_id).bounds_in_pixel().ToString());
+ EXPECT_EQ(1U, display_manager()->num_connected_displays());
+ EXPECT_FALSE(display_manager()->mirrored_display().is_valid());
+}
+
+#if defined(OS_WIN)
+// TODO(scottmg): RootWindow doesn't get resized on Windows
+// Ash. http://crbug.com/247916.
+#define MAYBE_TestNativeDisplaysChangedNoInternal \
+ DISABLED_TestNativeDisplaysChangedNoInternal
+#else
+#define MAYBE_TestNativeDisplaysChangedNoInternal \
+ TestNativeDisplaysChangedNoInternal
+#endif
+
+TEST_F(DisplayManagerTest, MAYBE_TestNativeDisplaysChangedNoInternal) {
+ EXPECT_EQ(1U, display_manager()->GetNumDisplays());
+
+ // Don't change the display info if all displays are disconnected.
+ std::vector<DisplayInfo> display_info_list;
+ display_manager()->OnNativeDisplaysChanged(display_info_list);
+ EXPECT_EQ(1U, display_manager()->GetNumDisplays());
+
+ // Connect another display which will become primary.
+ const DisplayInfo external_display_info =
+ CreateDisplayInfo(10, gfx::Rect(1, 1, 100, 100));
+ display_info_list.push_back(external_display_info);
+ display_manager()->OnNativeDisplaysChanged(display_info_list);
+ EXPECT_EQ(1U, display_manager()->GetNumDisplays());
+ EXPECT_EQ("1,1 100x100",
+ GetDisplayInfoForId(10).bounds_in_pixel().ToString());
+ EXPECT_EQ("100x100",
+ ash::Shell::GetPrimaryRootWindow()->GetHostSize().ToString());
+}
+
+#if defined(OS_WIN)
+// Tests that rely on the actual host size/location does not work on windows.
+#define MAYBE_EnsurePointerInDisplays DISABLED_EnsurePointerInDisplays
+#define MAYBE_EnsurePointerInDisplays_2ndOnLeft DISABLED_EnsurePointerInDisplays_2ndOnLeft
+#else
+#define MAYBE_EnsurePointerInDisplays EnsurePointerInDisplays
+#define MAYBE_EnsurePointerInDisplays_2ndOnLeft EnsurePointerInDisplays_2ndOnLeft
+#endif
+
+TEST_F(DisplayManagerTest, MAYBE_EnsurePointerInDisplays) {
+ UpdateDisplay("200x200,300x300");
+ Shell::RootWindowList root_windows = Shell::GetAllRootWindows();
+
+ aura::Env* env = aura::Env::GetInstance();
+
+ aura::test::EventGenerator generator(root_windows[0]);
+
+ // Set the initial position.
+ generator.MoveMouseToInHost(350, 150);
+ EXPECT_EQ("350,150", env->last_mouse_location().ToString());
+
+ // A mouse pointer will stay in the 2nd display.
+ UpdateDisplay("300x300,200x200");
+ EXPECT_EQ("450,50", env->last_mouse_location().ToString());
+
+ // A mouse pointer will be outside of displays and move to the
+ // center of 2nd display.
+ UpdateDisplay("300x300,100x100");
+ EXPECT_EQ("350,50", env->last_mouse_location().ToString());
+
+ // 2nd display was disconnected, and the cursor is
+ // now in the 1st display.
+ UpdateDisplay("400x400");
+ EXPECT_EQ("50,350", env->last_mouse_location().ToString());
+
+ // 1st display's resolution has changed, and the mouse pointer is
+ // now outside. Move the mouse pointer to the center of 1st display.
+ UpdateDisplay("300x300");
+ EXPECT_EQ("150,150", env->last_mouse_location().ToString());
+
+ // Move the mouse pointer to the bottom of 1st display.
+ generator.MoveMouseToInHost(150, 290);
+ EXPECT_EQ("150,290", env->last_mouse_location().ToString());
+
+ // The mouse pointer is now on 2nd display.
+ UpdateDisplay("300x280,200x200");
+ EXPECT_EQ("450,10", env->last_mouse_location().ToString());
+}
+
+TEST_F(DisplayManagerTest, MAYBE_EnsurePointerInDisplays_2ndOnLeft) {
+ // Set the 2nd display on the left.
+ DisplayLayoutStore* layout_store =
+ Shell::GetInstance()->display_manager()->layout_store();
+ DisplayLayout layout = layout_store->default_display_layout();
+ layout.position = DisplayLayout::LEFT;
+ layout_store->SetDefaultDisplayLayout(layout);
+
+ UpdateDisplay("200x200,300x300");
+ Shell::RootWindowList root_windows = Shell::GetAllRootWindows();
+
+ EXPECT_EQ("-300,0 300x300",
+ ScreenAsh::GetSecondaryDisplay().bounds().ToString());
+
+ aura::Env* env = aura::Env::GetInstance();
+
+ // Set the initial position.
+ root_windows[0]->MoveCursorTo(gfx::Point(-150, 250));
+ EXPECT_EQ("-150,250", env->last_mouse_location().ToString());
+
+ // A mouse pointer will stay in 2nd display.
+ UpdateDisplay("300x300,200x300");
+ EXPECT_EQ("-50,150", env->last_mouse_location().ToString());
+
+ // A mouse pointer will be outside of displays and move to the
+ // center of 2nd display.
+ UpdateDisplay("300x300,200x100");
+ EXPECT_EQ("-100,50", env->last_mouse_location().ToString());
+
+ // 2nd display was disconnected. Mouse pointer should move to
+ // 1st display.
+ UpdateDisplay("300x300");
+ EXPECT_EQ("150,150", env->last_mouse_location().ToString());
+}
+
+TEST_F(DisplayManagerTest, NativeDisplaysChangedAfterPrimaryChange) {
+ if (!SupportsMultipleDisplays())
+ return;
+
+ const int64 internal_display_id =
+ test::DisplayManagerTestApi(display_manager()).
+ SetFirstDisplayAsInternalDisplay();
+ const DisplayInfo native_display_info =
+ CreateDisplayInfo(internal_display_id, gfx::Rect(0, 0, 500, 500));
+ const DisplayInfo secondary_display_info =
+ CreateDisplayInfo(10, gfx::Rect(1, 1, 100, 100));
+
+ std::vector<DisplayInfo> display_info_list;
+ display_info_list.push_back(native_display_info);
+ display_info_list.push_back(secondary_display_info);
+ display_manager()->OnNativeDisplaysChanged(display_info_list);
+ EXPECT_EQ(2U, display_manager()->GetNumDisplays());
+ EXPECT_EQ("0,0 500x500",
+ GetDisplayForId(internal_display_id).bounds().ToString());
+ EXPECT_EQ("500,0 100x100", GetDisplayForId(10).bounds().ToString());
+
+ ash::Shell::GetInstance()->display_controller()->SetPrimaryDisplay(
+ GetDisplayForId(secondary_display_info.id()));
+ EXPECT_EQ("-500,0 500x500",
+ GetDisplayForId(internal_display_id).bounds().ToString());
+ EXPECT_EQ("0,0 100x100", GetDisplayForId(10).bounds().ToString());
+
+ // OnNativeDisplaysChanged may change the display bounds. Here makes sure
+ // nothing changed if the exactly same displays are specified.
+ display_manager()->OnNativeDisplaysChanged(display_info_list);
+ EXPECT_EQ("-500,0 500x500",
+ GetDisplayForId(internal_display_id).bounds().ToString());
+ EXPECT_EQ("0,0 100x100", GetDisplayForId(10).bounds().ToString());
+}
+
+TEST_F(DisplayManagerTest, DontRememberBestResolution) {
+ int display_id = 1000;
+ DisplayInfo native_display_info =
+ CreateDisplayInfo(display_id, gfx::Rect(0, 0, 1000, 500));
+ std::vector<Resolution> resolutions;
+ resolutions.push_back(Resolution(gfx::Size(1000, 500), false));
+ resolutions.push_back(Resolution(gfx::Size(800, 300), false));
+ resolutions.push_back(Resolution(gfx::Size(400, 500), false));
+
+ native_display_info.set_resolutions(resolutions);
+
+ std::vector<DisplayInfo> display_info_list;
+ display_info_list.push_back(native_display_info);
+ display_manager()->OnNativeDisplaysChanged(display_info_list);
+
+ gfx::Size selected;
+ EXPECT_FALSE(display_manager()->GetSelectedResolutionForDisplayId(
+ display_id, &selected));
+
+ // Unsupported resolution.
+ display_manager()->SetDisplayResolution(display_id, gfx::Size(800, 4000));
+ EXPECT_FALSE(display_manager()->GetSelectedResolutionForDisplayId(
+ display_id, &selected));
+
+ // Supported resolution.
+ display_manager()->SetDisplayResolution(display_id, gfx::Size(800, 300));
+ EXPECT_TRUE(display_manager()->GetSelectedResolutionForDisplayId(
+ display_id, &selected));
+ EXPECT_EQ("800x300", selected.ToString());
+
+ // Best resolution.
+ display_manager()->SetDisplayResolution(display_id, gfx::Size(1000, 500));
+ EXPECT_FALSE(display_manager()->GetSelectedResolutionForDisplayId(
+ display_id, &selected));
+}
+
+TEST_F(DisplayManagerTest, Rotate) {
+ if (!SupportsMultipleDisplays())
+ return;
+
+ UpdateDisplay("100x200/r,300x400/l");
+ EXPECT_EQ("1,1 100x200",
+ GetDisplayInfoAt(0).bounds_in_pixel().ToString());
+ EXPECT_EQ("200x100",
+ GetDisplayInfoAt(0).size_in_pixel().ToString());
+
+ EXPECT_EQ("1,201 300x400",
+ GetDisplayInfoAt(1).bounds_in_pixel().ToString());
+ EXPECT_EQ("400x300",
+ GetDisplayInfoAt(1).size_in_pixel().ToString());
+ reset();
+ UpdateDisplay("100x200/b,300x400");
+ EXPECT_EQ("2 0 0", GetCountSummary());
+ reset();
+
+ EXPECT_EQ("1,1 100x200",
+ GetDisplayInfoAt(0).bounds_in_pixel().ToString());
+ EXPECT_EQ("100x200",
+ GetDisplayInfoAt(0).size_in_pixel().ToString());
+
+ EXPECT_EQ("1,201 300x400",
+ GetDisplayInfoAt(1).bounds_in_pixel().ToString());
+ EXPECT_EQ("300x400",
+ GetDisplayInfoAt(1).size_in_pixel().ToString());
+
+ // Just Rotating display will change the bounds on both display.
+ UpdateDisplay("100x200/l,300x400");
+ EXPECT_EQ("2 0 0", GetCountSummary());
+ reset();
+
+ // Updating tothe same configuration should report no changes.
+ UpdateDisplay("100x200/l,300x400");
+ EXPECT_EQ("0 0 0", GetCountSummary());
+ reset();
+
+ UpdateDisplay("100x200/l,300x400");
+ EXPECT_EQ("0 0 0", GetCountSummary());
+ reset();
+
+ UpdateDisplay("200x200");
+ EXPECT_EQ("1 0 1", GetCountSummary());
+ reset();
+
+ UpdateDisplay("200x200/l");
+ EXPECT_EQ("1 0 0", GetCountSummary());
+}
+
+TEST_F(DisplayManagerTest, UIScale) {
+ UpdateDisplay("1280x800");
+ int64 display_id = Shell::GetScreen()->GetPrimaryDisplay().id();
+ display_manager()->SetDisplayUIScale(display_id, 1.125f);
+ EXPECT_EQ(1.0, GetDisplayInfoAt(0).ui_scale());
+ display_manager()->SetDisplayUIScale(display_id, 0.8f);
+ EXPECT_EQ(1.0f, GetDisplayInfoAt(0).ui_scale());
+ display_manager()->SetDisplayUIScale(display_id, 0.75f);
+ EXPECT_EQ(1.0f, GetDisplayInfoAt(0).ui_scale());
+ display_manager()->SetDisplayUIScale(display_id, 0.625f);
+ EXPECT_EQ(1.0f, GetDisplayInfoAt(0).ui_scale());
+
+ gfx::Display::SetInternalDisplayId(display_id);
+
+ display_manager()->SetDisplayUIScale(display_id, 1.5f);
+ EXPECT_EQ(1.0f, GetDisplayInfoAt(0).ui_scale());
+ display_manager()->SetDisplayUIScale(display_id, 1.25f);
+ EXPECT_EQ(1.0f, GetDisplayInfoAt(0).ui_scale());
+ display_manager()->SetDisplayUIScale(display_id, 1.125f);
+ EXPECT_EQ(1.125f, GetDisplayInfoAt(0).ui_scale());
+ display_manager()->SetDisplayUIScale(display_id, 0.8f);
+ EXPECT_EQ(0.8f, GetDisplayInfoAt(0).ui_scale());
+ display_manager()->SetDisplayUIScale(display_id, 0.75f);
+ EXPECT_EQ(0.8f, GetDisplayInfoAt(0).ui_scale());
+ display_manager()->SetDisplayUIScale(display_id, 0.625f);
+ EXPECT_EQ(0.625f, GetDisplayInfoAt(0).ui_scale());
+ display_manager()->SetDisplayUIScale(display_id, 0.6f);
+ EXPECT_EQ(0.625f, GetDisplayInfoAt(0).ui_scale());
+ display_manager()->SetDisplayUIScale(display_id, 0.5f);
+ EXPECT_EQ(0.5f, GetDisplayInfoAt(0).ui_scale());
+
+ UpdateDisplay("1366x768");
+ display_manager()->SetDisplayUIScale(display_id, 1.5f);
+ EXPECT_EQ(1.0f, GetDisplayInfoAt(0).ui_scale());
+ display_manager()->SetDisplayUIScale(display_id, 1.25f);
+ EXPECT_EQ(1.0f, GetDisplayInfoAt(0).ui_scale());
+ display_manager()->SetDisplayUIScale(display_id, 1.125f);
+ EXPECT_EQ(1.125f, GetDisplayInfoAt(0).ui_scale());
+ display_manager()->SetDisplayUIScale(display_id, 0.8f);
+ EXPECT_EQ(1.125f, GetDisplayInfoAt(0).ui_scale());
+ display_manager()->SetDisplayUIScale(display_id, 0.75f);
+ EXPECT_EQ(0.75f, GetDisplayInfoAt(0).ui_scale());
+ display_manager()->SetDisplayUIScale(display_id, 0.6f);
+ EXPECT_EQ(0.6f, GetDisplayInfoAt(0).ui_scale());
+ display_manager()->SetDisplayUIScale(display_id, 0.625f);
+ EXPECT_EQ(0.6f, GetDisplayInfoAt(0).ui_scale());
+ display_manager()->SetDisplayUIScale(display_id, 0.5f);
+ EXPECT_EQ(0.5f, GetDisplayInfoAt(0).ui_scale());
+
+ UpdateDisplay("1280x850*2");
+ EXPECT_EQ(1.0f, GetDisplayInfoAt(0).ui_scale());
+ display_manager()->SetDisplayUIScale(display_id, 1.5f);
+ EXPECT_EQ(1.5f, GetDisplayInfoAt(0).ui_scale());
+ display_manager()->SetDisplayUIScale(display_id, 1.25f);
+ EXPECT_EQ(1.25f, GetDisplayInfoAt(0).ui_scale());
+ display_manager()->SetDisplayUIScale(display_id, 1.125f);
+ EXPECT_EQ(1.125f, GetDisplayInfoAt(0).ui_scale());
+ display_manager()->SetDisplayUIScale(display_id, 0.8f);
+ EXPECT_EQ(0.8f, GetDisplayInfoAt(0).ui_scale());
+ display_manager()->SetDisplayUIScale(display_id, 0.75f);
+ EXPECT_EQ(0.8f, GetDisplayInfoAt(0).ui_scale());
+ display_manager()->SetDisplayUIScale(display_id, 0.625f);
+ EXPECT_EQ(0.625f, GetDisplayInfoAt(0).ui_scale());
+ display_manager()->SetDisplayUIScale(display_id, 0.6f);
+ EXPECT_EQ(0.625f, GetDisplayInfoAt(0).ui_scale());
+ display_manager()->SetDisplayUIScale(display_id, 0.5f);
+ EXPECT_EQ(0.5f, GetDisplayInfoAt(0).ui_scale());
+}
+
+
+#if defined(OS_WIN)
+// TODO(scottmg): RootWindow doesn't get resized on Windows
+// Ash. http://crbug.com/247916.
+#define MAYBE_UpdateMouseCursorAfterRotateZoom DISABLED_UpdateMouseCursorAfterRotateZoom
+#else
+#define MAYBE_UpdateMouseCursorAfterRotateZoom UpdateMouseCursorAfterRotateZoom
+#endif
+
+TEST_F(DisplayManagerTest, MAYBE_UpdateMouseCursorAfterRotateZoom) {
+ // Make sure just rotating will not change native location.
+ UpdateDisplay("300x200,200x150");
+ Shell::RootWindowList root_windows = Shell::GetAllRootWindows();
+ aura::Env* env = aura::Env::GetInstance();
+
+ aura::test::EventGenerator generator1(root_windows[0]);
+ aura::test::EventGenerator generator2(root_windows[1]);
+
+ // Test on 1st display.
+ generator1.MoveMouseToInHost(150, 50);
+ EXPECT_EQ("150,50", env->last_mouse_location().ToString());
+ UpdateDisplay("300x200/r,200x150");
+ EXPECT_EQ("50,149", env->last_mouse_location().ToString());
+
+ // Test on 2nd display.
+ generator2.MoveMouseToInHost(50, 100);
+ EXPECT_EQ("250,100", env->last_mouse_location().ToString());
+ UpdateDisplay("300x200/r,200x150/l");
+ EXPECT_EQ("249,50", env->last_mouse_location().ToString());
+
+ // The native location is now outside, so move to the center
+ // of closest display.
+ UpdateDisplay("300x200/r,100x50/l");
+ EXPECT_EQ("225,50", env->last_mouse_location().ToString());
+
+ // Make sure just zooming will not change native location.
+ UpdateDisplay("600x400*2,400x300");
+
+ // Test on 1st display.
+ generator1.MoveMouseToInHost(200, 300);
+ EXPECT_EQ("100,150", env->last_mouse_location().ToString());
+ UpdateDisplay("600x400*2@1.5,400x300");
+ EXPECT_EQ("150,225", env->last_mouse_location().ToString());
+
+ // Test on 2nd display.
+ UpdateDisplay("600x400,400x300*2");
+ generator2.MoveMouseToInHost(200, 250);
+ EXPECT_EQ("700,125", env->last_mouse_location().ToString());
+ UpdateDisplay("600x400,400x300*2@1.5");
+ EXPECT_EQ("750,187", env->last_mouse_location().ToString());
+
+ // The native location is now outside, so move to the
+ // center of closest display.
+ UpdateDisplay("600x400,400x200*2@1.5");
+ EXPECT_EQ("750,75", env->last_mouse_location().ToString());
+}
+
+class TestDisplayObserver : public gfx::DisplayObserver {
+ public:
+ TestDisplayObserver() : changed_(false) {}
+ virtual ~TestDisplayObserver() {}
+
+ // gfx::DisplayObserver overrides:
+ virtual void OnDisplayBoundsChanged(const gfx::Display& display) OVERRIDE {
+ }
+ virtual void OnDisplayAdded(const gfx::Display& new_display) OVERRIDE {
+ // Mirror window should already be delete before restoring
+ // the external dispay.
+ EXPECT_FALSE(test_api.GetRootWindow());
+ changed_ = true;
+ }
+ virtual void OnDisplayRemoved(const gfx::Display& old_display) OVERRIDE {
+ // Mirror window should not be created until the external display
+ // is removed.
+ EXPECT_FALSE(test_api.GetRootWindow());
+ changed_ = true;
+ }
+
+ bool changed_and_reset() {
+ bool changed = changed_;
+ changed_ = false;
+ return changed;
+ }
+
+ private:
+ test::MirrorWindowTestApi test_api;
+ bool changed_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestDisplayObserver);
+};
+
+TEST_F(DisplayManagerTest, SoftwareMirroring) {
+ if (!SupportsMultipleDisplays())
+ return;
+
+ UpdateDisplay("300x400,400x500");
+
+ test::MirrorWindowTestApi test_api;
+ EXPECT_EQ(NULL, test_api.GetRootWindow());
+
+ TestDisplayObserver display_observer;
+ Shell::GetScreen()->AddObserver(&display_observer);
+
+ DisplayManager* display_manager = Shell::GetInstance()->display_manager();
+ display_manager->SetSoftwareMirroring(true);
+ display_manager->UpdateDisplays();
+ EXPECT_TRUE(display_observer.changed_and_reset());
+ EXPECT_EQ(1U, display_manager->GetNumDisplays());
+ EXPECT_EQ("0,0 300x400",
+ Shell::GetScreen()->GetPrimaryDisplay().bounds().ToString());
+ EXPECT_EQ("400x500", test_api.GetRootWindow()->GetHostSize().ToString());
+ EXPECT_EQ("300x400", test_api.GetRootWindow()->bounds().size().ToString());
+ EXPECT_TRUE(display_manager->IsMirrored());
+
+ display_manager->SetMirrorMode(false);
+ EXPECT_TRUE(display_observer.changed_and_reset());
+ EXPECT_EQ(NULL, test_api.GetRootWindow());
+ EXPECT_EQ(2U, display_manager->GetNumDisplays());
+ EXPECT_FALSE(display_manager->IsMirrored());
+
+ // Make sure the mirror window has the pixel size of the
+ // source display.
+ display_manager->SetMirrorMode(true);
+ EXPECT_TRUE(display_observer.changed_and_reset());
+
+ UpdateDisplay("300x400@0.5,400x500");
+ EXPECT_FALSE(display_observer.changed_and_reset());
+ EXPECT_EQ("300x400", test_api.GetRootWindow()->bounds().size().ToString());
+ EXPECT_EQ("400x500", GetMirroredDisplay().size().ToString());
+
+ UpdateDisplay("310x410*2,400x500");
+ EXPECT_FALSE(display_observer.changed_and_reset());
+ EXPECT_EQ("310x410", test_api.GetRootWindow()->bounds().size().ToString());
+ EXPECT_EQ("400x500", GetMirroredDisplay().size().ToString());
+
+ UpdateDisplay("320x420/r,400x500");
+ EXPECT_FALSE(display_observer.changed_and_reset());
+ EXPECT_EQ("320x420", test_api.GetRootWindow()->bounds().size().ToString());
+ EXPECT_EQ("400x500", GetMirroredDisplay().size().ToString());
+
+ UpdateDisplay("330x440/r,400x500");
+ EXPECT_FALSE(display_observer.changed_and_reset());
+ EXPECT_EQ("330x440", test_api.GetRootWindow()->bounds().size().ToString());
+ EXPECT_EQ("400x500", GetMirroredDisplay().size().ToString());
+
+ // Overscan insets are ignored.
+ UpdateDisplay("400x600/o,600x800/o");
+ EXPECT_FALSE(display_observer.changed_and_reset());
+ EXPECT_EQ("400x600", test_api.GetRootWindow()->bounds().size().ToString());
+ EXPECT_EQ("600x800", GetMirroredDisplay().size().ToString());
+
+ Shell::GetScreen()->RemoveObserver(&display_observer);
+}
+
+TEST_F(DisplayManagerTest, MirroredLayout) {
+ if (!SupportsMultipleDisplays())
+ return;
+
+ DisplayManager* display_manager = Shell::GetInstance()->display_manager();
+ UpdateDisplay("500x500,400x400");
+ EXPECT_FALSE(display_manager->GetCurrentDisplayLayout().mirrored);
+ EXPECT_EQ(2, Shell::GetScreen()->GetNumDisplays());
+ EXPECT_EQ(2U, display_manager->num_connected_displays());
+
+ UpdateDisplay("1+0-500x500,1+0-500x500");
+ EXPECT_TRUE(display_manager->GetCurrentDisplayLayout().mirrored);
+ EXPECT_EQ(1, Shell::GetScreen()->GetNumDisplays());
+ EXPECT_EQ(2U, display_manager->num_connected_displays());
+
+ UpdateDisplay("500x500,500x500");
+ EXPECT_FALSE(display_manager->GetCurrentDisplayLayout().mirrored);
+ EXPECT_EQ(2, Shell::GetScreen()->GetNumDisplays());
+ EXPECT_EQ(2U, display_manager->num_connected_displays());
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/display/display_pref_util.h b/chromium/ash/display/display_pref_util.h
new file mode 100644
index 00000000000..1b449625479
--- /dev/null
+++ b/chromium/ash/display/display_pref_util.h
@@ -0,0 +1,48 @@
+// Copyright (c) 2013 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 ASH_DISPLAY_DISPLAY_PREF_UTIL_H
+#define ASH_DISPLAY_DISPLAY_PREF_UTIL_H
+
+#include <map>
+#include <string>
+
+#include "base/strings/string_piece.h"
+
+namespace ash {
+
+// Utility templates to create enum to string map and
+// a function to find an enum value from a string.
+template<typename T>
+std::map<T, std::string>* CreateToStringMap(T k1, const std::string& v1,
+ T k2, const std::string& v2,
+ T k3, const std::string& v3,
+ T k4, const std::string& v4) {
+ std::map<T, std::string>* map = new std::map<T, std::string>();
+ (*map)[k1] = v1;
+ (*map)[k2] = v2;
+ (*map)[k3] = v3;
+ (*map)[k4] = v4;
+ return map;
+}
+
+template<typename T>
+bool ReverseFind(const std::map<T, std::string>* map,
+ const base::StringPiece& value,
+ T* key) {
+ typename std::map<T, std::string>::const_iterator iter = map->begin();
+ for (;
+ iter != map->end();
+ ++iter) {
+ if (iter->second == value) {
+ *key = iter->first;
+ return true;
+ }
+ }
+ return false;
+}
+
+} // namespace ash
+
+#endif // ASH_DISPLAY_DISPLAY_PREF_UTIL_H
diff --git a/chromium/ash/display/display_util_x11.cc b/chromium/ash/display/display_util_x11.cc
new file mode 100644
index 00000000000..e662ce23742
--- /dev/null
+++ b/chromium/ash/display/display_util_x11.cc
@@ -0,0 +1,97 @@
+// Copyright 2013 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.
+
+#include "ash/display/display_util_x11.h"
+
+#include <algorithm>
+#include <map>
+#include <X11/extensions/Xrandr.h>
+
+#include "ash/display/display_info.h"
+#include "base/logging.h"
+#include "chromeos/display/output_util.h"
+
+namespace ash {
+namespace internal {
+namespace {
+
+// A list of bogus sizes in mm that X detects that should be ignored.
+// See crbug.com/136533. The first element maintains the minimum
+// size required to be valid size.
+const unsigned long kInvalidDisplaySizeList[][2] = {
+ {40, 30},
+ {50, 40},
+ {160, 90},
+ {160, 100},
+};
+
+// Resolution list are sorted by the area in pixels and the larger
+// one comes first.
+struct ResolutionSorter {
+ bool operator()(const Resolution& a, const Resolution& b) {
+ return a.size.width() * a.size.height() > b.size.width() * b.size.height();
+ }
+};
+
+} // namespace
+
+bool ShouldIgnoreSize(unsigned long mm_width, unsigned long mm_height) {
+ // Ignore if the reported display is smaller than minimum size.
+ if (mm_width <= kInvalidDisplaySizeList[0][0] ||
+ mm_height <= kInvalidDisplaySizeList[0][1]) {
+ LOG(WARNING) << "Smaller than minimum display size";
+ return true;
+ }
+ for (unsigned long i = 1 ; i < arraysize(kInvalidDisplaySizeList); ++i) {
+ const unsigned long* size = kInvalidDisplaySizeList[i];
+ if (mm_width == size[0] && mm_height == size[1]) {
+ LOG(WARNING) << "Black listed display size detected:"
+ << size[0] << "x" << size[1];
+ return true;
+ }
+ }
+ return false;
+}
+
+std::vector<Resolution> GetResolutionList(
+ XRRScreenResources* screen_resources,
+ XRROutputInfo* output_info) {
+ typedef std::map<std::pair<int,int>, Resolution> ResolutionMap;
+
+ ResolutionMap resolution_map;
+
+ for (int i = 0; i < output_info->nmode; i++) {
+ RRMode mode = output_info->modes[i];
+ const XRRModeInfo* info = chromeos::FindModeInfo(screen_resources, mode);
+ DCHECK(info);
+ // Just ignore bad entry on Release build.
+ if (!info)
+ continue;
+ ResolutionMap::key_type size = std::make_pair(info->width, info->height);
+ bool interlaced = (info->modeFlags & RR_Interlace) != 0;
+
+ ResolutionMap::iterator iter = resolution_map.find(size);
+
+ // Add new resolution if it's new size or override interlaced mode.
+ if (iter == resolution_map.end()) {
+ resolution_map.insert(ResolutionMap::value_type(
+ size,
+ Resolution(gfx::Size(info->width, info->height), interlaced)));
+ } else if (iter->second.interlaced && !interlaced) {
+ iter->second.interlaced = false;
+ }
+ }
+
+ std::vector<Resolution> resolution_list;
+ for (ResolutionMap::const_iterator iter = resolution_map.begin();
+ iter != resolution_map.end();
+ ++iter) {
+ resolution_list.push_back(iter->second);
+ }
+ std::sort(resolution_list.begin(), resolution_list.end(), ResolutionSorter());
+ return resolution_list;
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/display/display_util_x11.h b/chromium/ash/display/display_util_x11.h
new file mode 100644
index 00000000000..6647688ce1c
--- /dev/null
+++ b/chromium/ash/display/display_util_x11.h
@@ -0,0 +1,36 @@
+// Copyright 2013 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 ASH_DISPLAY_DISPLAY_UTIL_X11_H_
+#define ASH_DISPLAY_DISPLAY_UTIL_X11_H_
+
+#include <vector>
+
+#include "ash/ash_export.h"
+#include "ash/display/display_info.h"
+
+struct _XRRScreenResources;
+typedef _XRRScreenResources XRRScreenResources;
+struct _XRROutputInfo;
+typedef _XRROutputInfo XRROutputInfo;
+
+namespace ash {
+namespace internal {
+struct Resolution;
+
+// Returns true if the size info in the output_info isn't valid
+// and should be ignored. This is exposed for testing.
+// |mm_width| and |mm_height| are given in millimeters.
+ASH_EXPORT bool ShouldIgnoreSize(unsigned long mm_width,
+ unsigned long mm_height);
+
+// Returns the resolution list.
+ASH_EXPORT std::vector<Resolution> GetResolutionList(
+ XRRScreenResources* screen_resources,
+ XRROutputInfo* output_info);
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_DISPLAY_DISPLAY_UTIL_X11_H_
diff --git a/chromium/ash/display/display_util_x11_unittest.cc b/chromium/ash/display/display_util_x11_unittest.cc
new file mode 100644
index 00000000000..f534a28dcbb
--- /dev/null
+++ b/chromium/ash/display/display_util_x11_unittest.cc
@@ -0,0 +1,104 @@
+// Copyright 2013 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.
+
+#include "ash/display/display_util_x11.h"
+
+#include <X11/extensions/Xrandr.h>
+
+// Undefine X's macros used in gtest.
+#undef Bool
+#undef None
+
+#include "chromeos/display/output_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+typedef testing::Test DisplayUtilX11Test;
+
+namespace ash {
+namespace internal {
+
+TEST_F(DisplayUtilX11Test, TestBlackListedDisplay) {
+ EXPECT_TRUE(ShouldIgnoreSize(10, 10));
+ EXPECT_TRUE(ShouldIgnoreSize(40, 30));
+ EXPECT_TRUE(ShouldIgnoreSize(50, 40));
+ EXPECT_TRUE(ShouldIgnoreSize(160, 90));
+ EXPECT_TRUE(ShouldIgnoreSize(160, 100));
+
+ EXPECT_FALSE(ShouldIgnoreSize(50, 60));
+ EXPECT_FALSE(ShouldIgnoreSize(100, 70));
+ EXPECT_FALSE(ShouldIgnoreSize(272, 181));
+}
+
+TEST_F(DisplayUtilX11Test, GetResolutionList) {
+ XRRScreenResources resources = {0};
+ RROutput outputs[] = {1};
+ resources.noutput = arraysize(outputs);
+ resources.outputs = outputs;
+ XRRModeInfo modes[] = {
+ // id, width, height, interlaced, refresh rate
+ chromeos::test::CreateModeInfo(11, 1920, 1200, false, 60.0f),
+
+ // different rates
+ chromeos::test::CreateModeInfo(12, 1920, 1080, false, 30.0f),
+ chromeos::test::CreateModeInfo(13, 1920, 1080, false, 50.0f),
+ chromeos::test::CreateModeInfo(14, 1920, 1080, false, 40.0f),
+
+ // interlace vs non interlace
+ chromeos::test::CreateModeInfo(15, 1280, 720, true, 60.0f),
+ chromeos::test::CreateModeInfo(16, 1280, 720, false, 40.0f),
+
+ // interlace only
+ chromeos::test::CreateModeInfo(17, 1024, 768, true, 40.0f),
+ chromeos::test::CreateModeInfo(18, 1024, 768, true, 60.0f),
+
+ // mixed
+ chromeos::test::CreateModeInfo(19, 1024, 600, true, 60.0f),
+ chromeos::test::CreateModeInfo(20, 1024, 600, false, 40.0f),
+ chromeos::test::CreateModeInfo(21, 1024, 600, false, 50.0f),
+
+ // just one interlaced mode
+ chromeos::test::CreateModeInfo(22, 640, 480, true, 60.0f),
+ };
+ resources.nmode = arraysize(modes);
+ resources.modes = modes;
+
+ XRROutputInfo output_info = {0};
+ RRMode output_modes[] = {
+ 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22
+ };
+ output_info.nmode = arraysize(output_modes);
+ output_info.modes = output_modes;
+
+ std::vector<Resolution> resolutions =
+ GetResolutionList(&resources, &output_info);
+ EXPECT_EQ(6u, resolutions.size());
+ EXPECT_EQ("1920x1200", resolutions[0].size.ToString());
+ EXPECT_FALSE(resolutions[0].interlaced);
+
+ EXPECT_EQ("1920x1080", resolutions[1].size.ToString());
+ EXPECT_FALSE(resolutions[1].interlaced);
+
+ EXPECT_EQ("1280x720", resolutions[2].size.ToString());
+ EXPECT_FALSE(resolutions[2].interlaced);
+
+ EXPECT_EQ("1024x768", resolutions[3].size.ToString());
+ EXPECT_TRUE(resolutions[3].interlaced);
+
+ EXPECT_EQ("1024x600", resolutions[4].size.ToString());
+ EXPECT_FALSE(resolutions[4].interlaced);
+
+ EXPECT_EQ("640x480", resolutions[5].size.ToString());
+ EXPECT_TRUE(resolutions[5].interlaced);
+
+ // Empty output shouldn't crash.
+ RRMode empty_output_modes[] = {};
+ output_info.nmode = 0;
+ output_info.modes = empty_output_modes;
+
+ resolutions = GetResolutionList(&resources, &output_info);
+ EXPECT_EQ(0u, resolutions.size());
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/display/event_transformation_handler.cc b/chromium/ash/display/event_transformation_handler.cc
new file mode 100644
index 00000000000..cb33891edf6
--- /dev/null
+++ b/chromium/ash/display/event_transformation_handler.cc
@@ -0,0 +1,98 @@
+// Copyright (c) 2013 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.
+
+#include "ash/display/event_transformation_handler.h"
+
+#include <cmath>
+
+#include "ash/screen_ash.h"
+#include "ash/shell.h"
+#include "ash/wm/coordinate_conversion.h"
+#include "ash/wm/window_util.h"
+#include "ui/aura/root_window.h"
+#include "ui/aura/window.h"
+#include "ui/base/events/event.h"
+#include "ui/compositor/dip_util.h"
+#include "ui/gfx/display.h"
+#include "ui/gfx/screen.h"
+
+#if defined(OS_CHROMEOS)
+#include "chromeos/display/output_configurator.h"
+#endif // defined(OS_CHROMEOS)
+
+namespace ash {
+namespace internal {
+namespace {
+
+// Boost factor for non-integrated displays.
+const float kBoostForNonIntegrated = 1.20f;
+}
+
+EventTransformationHandler::EventTransformationHandler()
+ : transformation_mode_(TRANSFORM_AUTO) {
+}
+
+EventTransformationHandler::~EventTransformationHandler() {
+}
+
+void EventTransformationHandler::OnScrollEvent(ui::ScrollEvent* event) {
+ if (transformation_mode_ == TRANSFORM_NONE)
+ return;
+
+ // Get the device scale factor and stack it on the final scale factor.
+ gfx::Point point_in_screen(event->location());
+ aura::Window* target = static_cast<aura::Window*>(event->target());
+ const float scale_at_target = ui::GetDeviceScaleFactor(target->layer());
+ float scale = scale_at_target;
+
+ // Apply some additional scaling if the display is non-integrated.
+ wm::ConvertPointToScreen(target, &point_in_screen);
+ const gfx::Display& display =
+ Shell::GetScreen()->GetDisplayNearestPoint(point_in_screen);
+ if (!display.IsInternal())
+ scale *= kBoostForNonIntegrated;
+
+ event->Scale(scale);
+}
+
+#if defined(OS_CHROMEOS)
+// This is to scale the TouchEvent's radius when the touch display is in
+// mirror mode. TouchEvent's radius is often reported in the touchscreen's
+// native resolution. In mirror mode, the touch display could be configured
+// at a lower resolution. We scale down the radius using the ratio defined as
+// the sqrt of
+// (mirror_width * mirror_height) / (native_width * native_height)
+void EventTransformationHandler::OnTouchEvent(ui::TouchEvent* event) {
+ using chromeos::OutputConfigurator;
+ OutputConfigurator* output_configurator =
+ ash::Shell::GetInstance()->output_configurator();
+
+ // Check output_configurator's output_state instead of checking
+ // DisplayManager::IsMirrored() because the compositor based mirroring
+ // won't cause the scaling issue.
+ if (output_configurator->output_state() != chromeos::STATE_DUAL_MIRROR)
+ return;
+
+ const std::map<int, float>& area_ratio_map =
+ output_configurator->GetMirroredDisplayAreaRatioMap();
+
+ // TODO(miletus): When there are more than 1 touchscreen (e.g. Link connected
+ // to an external touchscreen), the correct way to do is to have a way
+ // to find out which touchscreen is the event originating from and use the
+ // area ratio of that touchscreen to scale the event's radius.
+ // Tracked here crbug.com/233245
+ if (area_ratio_map.size() != 1) {
+ LOG(ERROR) << "Mirroring mode with " << area_ratio_map.size()
+ << " touch display found";
+ return;
+ }
+
+ float area_ratio_sqrt = std::sqrt(area_ratio_map.begin()->second);
+ event->set_radius_x(event->radius_x() * area_ratio_sqrt);
+ event->set_radius_y(event->radius_y() * area_ratio_sqrt);
+}
+#endif // defined(OS_CHROMEOS)
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/display/event_transformation_handler.h b/chromium/ash/display/event_transformation_handler.h
new file mode 100644
index 00000000000..b2a71677769
--- /dev/null
+++ b/chromium/ash/display/event_transformation_handler.h
@@ -0,0 +1,49 @@
+// Copyright (c) 2013 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 ASH_DISPLAY_EVENT_TRANSFORMATION_HANDLER_H_
+#define ASH_DISPLAY_EVENT_TRANSFORMATION_HANDLER_H_
+
+#include "ash/ash_export.h"
+#include "base/compiler_specific.h"
+#include "ui/base/events/event_handler.h"
+
+namespace ash {
+
+namespace internal {
+
+// An event filter that transforms input event properties in extended desktop
+// environment.
+class ASH_EXPORT EventTransformationHandler : public ui::EventHandler {
+ public:
+ enum TransformationMode {
+ TRANSFORM_AUTO, // Transform events by the default amount.
+ // 1. Linear scaling w.r.t. the device scale factor.
+ // 2. Add 20% more for non-integrated displays.
+ TRANSFORM_NONE, // No transformation.
+ };
+
+ EventTransformationHandler();
+ virtual ~EventTransformationHandler();
+
+ void set_transformation_mode(TransformationMode transformation_mode) {
+ transformation_mode_ = transformation_mode;
+ }
+
+ // Overridden from ui::EventHandler.
+ virtual void OnScrollEvent(ui::ScrollEvent* event) OVERRIDE;
+#if defined(OS_CHROMEOS)
+ virtual void OnTouchEvent(ui::TouchEvent* event) OVERRIDE;
+#endif // defined(OS_CHROMEOS)
+
+ private:
+ TransformationMode transformation_mode_;
+
+ DISALLOW_COPY_AND_ASSIGN(EventTransformationHandler);
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_DISPLAY_EVENT_TRANSFORMATION_HANDLER_H_
diff --git a/chromium/ash/display/mirror_window_controller.cc b/chromium/ash/display/mirror_window_controller.cc
new file mode 100644
index 00000000000..6ed6ff48a04
--- /dev/null
+++ b/chromium/ash/display/mirror_window_controller.cc
@@ -0,0 +1,343 @@
+// Copyright (c) 2013 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.
+
+#include "ash/display/mirror_window_controller.h"
+
+#if defined(USE_X11)
+#include <X11/Xlib.h>
+
+// Xlib.h defines RootWindow.
+#undef RootWindow
+#endif
+
+#include "ash/display/display_controller.h"
+#include "ash/display/display_info.h"
+#include "ash/display/display_manager.h"
+#include "ash/display/root_window_transformers.h"
+#include "ash/host/root_window_host_factory.h"
+#include "ash/shell.h"
+#include "ash/wm/window_properties.h"
+#include "base/strings/stringprintf.h"
+#include "ui/aura/client/capture_client.h"
+#include "ui/aura/env.h"
+#include "ui/aura/root_window.h"
+#include "ui/aura/root_window_transformer.h"
+#include "ui/aura/window_delegate.h"
+#include "ui/base/cursor/cursors_aura.h"
+#include "ui/base/hit_test.h"
+#include "ui/base/layout.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "ui/compositor/reflector.h"
+#include "ui/gfx/canvas.h"
+#include "ui/gfx/image/image_skia.h"
+#include "ui/gfx/image/image_skia_operations.h"
+#include "ui/gfx/native_widget_types.h"
+
+namespace ash {
+namespace internal {
+namespace {
+
+#if defined(USE_X11)
+// Mirror window shouldn't handle input events.
+void DisableInput(XID window) {
+ long event_mask = ExposureMask | VisibilityChangeMask |
+ StructureNotifyMask | PropertyChangeMask;
+ XSelectInput(base::MessagePumpAuraX11::GetDefaultXDisplay(),
+ window, event_mask);
+}
+#endif
+
+class NoneCaptureClient : public aura::client::CaptureClient {
+ public:
+ NoneCaptureClient() {}
+ virtual ~NoneCaptureClient() {}
+
+ private:
+ // Does a capture on the |window|.
+ virtual void SetCapture(aura::Window* window) OVERRIDE {}
+
+ // Releases a capture from the |window|.
+ virtual void ReleaseCapture(aura::Window* window) OVERRIDE {}
+
+ // Returns the current capture window.
+ virtual aura::Window* GetCaptureWindow() OVERRIDE {
+ return NULL;
+ }
+
+ DISALLOW_COPY_AND_ASSIGN(NoneCaptureClient);
+};
+
+} // namespace
+
+class CursorWindowDelegate : public aura::WindowDelegate {
+ public:
+ CursorWindowDelegate() {}
+ virtual ~CursorWindowDelegate() {}
+
+ // aura::WindowDelegate overrides:
+ virtual gfx::Size GetMinimumSize() const OVERRIDE {
+ return size_;
+ }
+ virtual gfx::Size GetMaximumSize() const OVERRIDE {
+ return size_;
+ }
+ virtual void OnBoundsChanged(const gfx::Rect& old_bounds,
+ const gfx::Rect& new_bounds) OVERRIDE {
+ }
+ virtual gfx::NativeCursor GetCursor(const gfx::Point& point) OVERRIDE {
+ return gfx::kNullCursor;
+ }
+ virtual int GetNonClientComponent(
+ const gfx::Point& point) const OVERRIDE {
+ return HTNOWHERE;
+ }
+ virtual bool ShouldDescendIntoChildForEventHandling(
+ aura::Window* child,
+ const gfx::Point& location) OVERRIDE {
+ return false;
+ }
+ virtual bool CanFocus() OVERRIDE {
+ return false;
+ }
+ virtual void OnCaptureLost() OVERRIDE {
+ }
+ virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE {
+ canvas->DrawImageInt(cursor_image_, 0, 0);
+ }
+ virtual void OnDeviceScaleFactorChanged(
+ float device_scale_factor) OVERRIDE {
+ }
+ virtual void OnWindowDestroying() OVERRIDE {}
+ virtual void OnWindowDestroyed() OVERRIDE {}
+ virtual void OnWindowTargetVisibilityChanged(bool visible) OVERRIDE {
+ }
+ virtual bool HasHitTestMask() const OVERRIDE {
+ return false;
+ }
+ virtual void GetHitTestMask(gfx::Path* mask) const OVERRIDE {}
+ virtual scoped_refptr<ui::Texture> CopyTexture() OVERRIDE {
+ NOTREACHED();
+ return scoped_refptr<ui::Texture>();
+ }
+
+ // Set the cursor image for the |display|'s scale factor. Note that
+ // mirror window's scale factor is always 1.0f, therefore we need to
+ // take 2x's image and paint as if it's 1x image.
+ void SetCursorImage(const gfx::ImageSkia& image,
+ const gfx::Display& display) {
+ device_scale_factor_ =
+ ui::GetScaleFactorFromScale(display.device_scale_factor());
+ const gfx::ImageSkiaRep& image_rep =
+ image.GetRepresentation(device_scale_factor_);
+ size_ = image_rep.pixel_size();
+ cursor_image_ = gfx::ImageSkia::CreateFrom1xBitmap(image_rep.sk_bitmap());
+ }
+
+ const gfx::Size size() const { return size_; }
+
+ private:
+ gfx::ImageSkia cursor_image_;
+ ui::ScaleFactor device_scale_factor_;
+ gfx::Size size_;
+
+ DISALLOW_COPY_AND_ASSIGN(CursorWindowDelegate);
+};
+
+MirrorWindowController::MirrorWindowController()
+ : current_cursor_type_(ui::kCursorNone),
+ current_cursor_rotation_(gfx::Display::ROTATE_0),
+ cursor_window_(NULL),
+ cursor_window_delegate_(new CursorWindowDelegate) {
+}
+
+MirrorWindowController::~MirrorWindowController() {
+ // Make sure the root window gets deleted before cursor_window_delegate.
+ Close();
+}
+
+void MirrorWindowController::UpdateWindow(const DisplayInfo& display_info) {
+ static int mirror_root_window_count = 0;
+
+ if (!root_window_.get()) {
+ const gfx::Rect& bounds_in_pixel = display_info.bounds_in_pixel();
+ aura::RootWindow::CreateParams params(bounds_in_pixel);
+ params.host = Shell::GetInstance()->root_window_host_factory()->
+ CreateRootWindowHost(bounds_in_pixel);
+ root_window_.reset(new aura::RootWindow(params));
+ root_window_->SetName(
+ base::StringPrintf("MirrorRootWindow-%d", mirror_root_window_count++));
+ root_window_->compositor()->SetBackgroundColor(SK_ColorBLACK);
+ // No need to remove RootWindowObserver because
+ // the DisplayController object outlives RootWindow objects.
+ root_window_->AddRootWindowObserver(
+ Shell::GetInstance()->display_controller());
+ root_window_->AddRootWindowObserver(this);
+ // TODO(oshima): TouchHUD is using idkey.
+ root_window_->SetProperty(internal::kDisplayIdKey, display_info.id());
+ root_window_->Init();
+#if defined(USE_X11)
+ DisableInput(root_window_->GetAcceleratedWidget());
+#endif
+
+ aura::client::SetCaptureClient(root_window_.get(), new NoneCaptureClient());
+ root_window_->ShowRootWindow();
+
+ // TODO(oshima): Start mirroring.
+ aura::Window* mirror_window = new aura::Window(NULL);
+ mirror_window->Init(ui::LAYER_TEXTURED);
+ root_window_->AddChild(mirror_window);
+ mirror_window->SetBounds(root_window_->bounds());
+ mirror_window->Show();
+ reflector_ = ui::ContextFactory::GetInstance()->
+ CreateReflector(Shell::GetPrimaryRootWindow()->compositor(),
+ mirror_window->layer());
+
+ cursor_window_ = new aura::Window(cursor_window_delegate_.get());
+ cursor_window_->SetTransparent(true);
+ cursor_window_->Init(ui::LAYER_TEXTURED);
+ root_window_->AddChild(cursor_window_);
+ cursor_window_->Show();
+ } else {
+ root_window_->SetProperty(internal::kDisplayIdKey, display_info.id());
+ root_window_->SetHostBounds(display_info.bounds_in_pixel());
+ }
+
+ DisplayManager* display_manager = Shell::GetInstance()->display_manager();
+ const DisplayInfo& source_display_info = display_manager->GetDisplayInfo(
+ Shell::GetScreen()->GetPrimaryDisplay().id());
+ DCHECK(display_manager->mirrored_display().is_valid());
+ scoped_ptr<aura::RootWindowTransformer> transformer(
+ internal::CreateRootWindowTransformerForMirroredDisplay(
+ source_display_info,
+ display_info));
+ root_window_->SetRootWindowTransformer(transformer.Pass());
+
+ UpdateCursorLocation();
+}
+
+void MirrorWindowController::UpdateWindow() {
+ if (root_window_.get()) {
+ DisplayManager* display_manager = Shell::GetInstance()->display_manager();
+ const DisplayInfo& mirror_display_info = display_manager->GetDisplayInfo(
+ display_manager->mirrored_display().id());
+ UpdateWindow(mirror_display_info);
+ }
+}
+
+void MirrorWindowController::Close() {
+ if (root_window_.get()) {
+ ui::ContextFactory::GetInstance()->RemoveReflector(reflector_);
+ reflector_ = NULL;
+ NoneCaptureClient* capture_client = static_cast<NoneCaptureClient*>(
+ aura::client::GetCaptureClient(root_window_.get()));
+ delete capture_client;
+
+ root_window_->RemoveRootWindowObserver(
+ Shell::GetInstance()->display_controller());
+ root_window_->RemoveRootWindowObserver(this);
+ root_window_.reset();
+ cursor_window_ = NULL;
+ }
+}
+
+void MirrorWindowController::UpdateCursorLocation() {
+ if (cursor_window_) {
+ // TODO(oshima): Rotate cursor image (including hotpoint).
+ gfx::Point point = aura::Env::GetInstance()->last_mouse_location();
+ Shell::GetPrimaryRootWindow()->ConvertPointToHost(&point);
+ point.Offset(-hot_point_.x(), -hot_point_.y());
+ gfx::Rect bounds = cursor_window_->bounds();
+ bounds.set_origin(point);
+ cursor_window_->SetBounds(bounds);
+ }
+}
+
+void MirrorWindowController::SetMirroredCursor(gfx::NativeCursor cursor) {
+ const gfx::Display& display = Shell::GetScreen()->GetPrimaryDisplay();
+ if (current_cursor_type_ == cursor.native_type() &&
+ current_cursor_rotation_ == display.rotation())
+ return;
+ current_cursor_type_ = cursor.native_type();
+ current_cursor_rotation_ = display.rotation();
+ int resource_id;
+ bool success = ui::GetCursorDataFor(
+ current_cursor_type_,
+ display.device_scale_factor(),
+ &resource_id,
+ &hot_point_);
+ if (!success)
+ return;
+ const gfx::ImageSkia* image =
+ ResourceBundle::GetSharedInstance().GetImageSkiaNamed(resource_id);
+ gfx::ImageSkia rotated = *image;
+ switch (current_cursor_rotation_) {
+ case gfx::Display::ROTATE_0:
+ break;
+ case gfx::Display::ROTATE_90:
+ rotated = gfx::ImageSkiaOperations::CreateRotatedImage(
+ *image, SkBitmapOperations::ROTATION_90_CW);
+ hot_point_.SetPoint(
+ rotated.width() - hot_point_.y(),
+ hot_point_.x());
+ break;
+ case gfx::Display::ROTATE_180:
+ rotated = gfx::ImageSkiaOperations::CreateRotatedImage(
+ *image, SkBitmapOperations::ROTATION_180_CW);
+ hot_point_.SetPoint(
+ rotated.height() - hot_point_.x(),
+ rotated.width() - hot_point_.y());
+ break;
+ case gfx::Display::ROTATE_270:
+ rotated = gfx::ImageSkiaOperations::CreateRotatedImage(
+ *image, SkBitmapOperations::ROTATION_270_CW);
+ hot_point_.SetPoint(
+ hot_point_.y(),
+ rotated.height() - hot_point_.x());
+ break;
+ }
+ cursor_window_delegate_->SetCursorImage(rotated, display);
+
+ if (cursor_window_) {
+ cursor_window_->SetBounds(gfx::Rect(cursor_window_delegate_->size()));
+ cursor_window_->SchedulePaintInRect(
+ gfx::Rect(cursor_window_->bounds().size()));
+ UpdateCursorLocation();
+ }
+}
+
+void MirrorWindowController::SetMirroredCursorVisibility(bool visible) {
+ if (cursor_window_)
+ visible ? cursor_window_->Show() : cursor_window_->Hide();
+}
+
+void MirrorWindowController::OnRootWindowHostResized(
+ const aura::RootWindow* root) {
+ // Do not use |old_size| as it contains RootWindow's (but not host's) size,
+ // and this parameter wil be removed soon.
+ if (mirror_window_host_size_ == root->GetHostSize())
+ return;
+ mirror_window_host_size_ = root->GetHostSize();
+ reflector_->OnMirroringCompositorResized();
+ root_window_->SetRootWindowTransformer(
+ CreateRootWindowTransformer().Pass());
+ UpdateCursorLocation();
+}
+
+
+scoped_ptr<aura::RootWindowTransformer>
+MirrorWindowController::CreateRootWindowTransformer() const {
+ DisplayManager* display_manager = Shell::GetInstance()->display_manager();
+ const DisplayInfo& mirror_display_info = display_manager->GetDisplayInfo(
+ display_manager->mirrored_display().id());
+ const DisplayInfo& source_display_info = display_manager->GetDisplayInfo(
+ Shell::GetScreen()->GetPrimaryDisplay().id());
+ DCHECK(display_manager->mirrored_display().is_valid());
+ return scoped_ptr<aura::RootWindowTransformer>(
+ internal::CreateRootWindowTransformerForMirroredDisplay(
+ source_display_info,
+ mirror_display_info));
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/display/mirror_window_controller.h b/chromium/ash/display/mirror_window_controller.h
new file mode 100644
index 00000000000..13460d7239b
--- /dev/null
+++ b/chromium/ash/display/mirror_window_controller.h
@@ -0,0 +1,87 @@
+// Copyright (c) 2013 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 ASH_DISPLAY_MIRROR_WINDOW_CONTROLLER_H_
+#define ASH_DISPLAY_MIRROR_WINDOW_CONTROLLER_H_
+
+#include "ash/ash_export.h"
+#include "base/compiler_specific.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "ui/aura/root_window_observer.h"
+#include "ui/gfx/display.h"
+#include "ui/gfx/native_widget_types.h"
+#include "ui/gfx/point.h"
+#include "ui/gfx/size.h"
+
+namespace aura {
+class RootWindow;
+class RootWindowTransformer;
+class Window;
+}
+
+namespace ui {
+class Reflector;
+}
+
+namespace ash {
+namespace test{
+class MirrorWindowTestApi;
+}
+
+namespace internal {
+class DisplayInfo;
+class CursorWindowDelegate;
+
+// An object that copies the content of the primary root window to a
+// mirror window. This also draws a mouse cursor as the mouse cursor
+// is typically drawn by the window system.
+class ASH_EXPORT MirrorWindowController : public aura::RootWindowObserver {
+ public:
+ MirrorWindowController();
+ virtual ~MirrorWindowController();
+
+ // Updates the root window's bounds using |display_info|.
+ // Creates the new root window if one doesn't exist.
+ void UpdateWindow(const DisplayInfo& display_info);
+
+ // Same as above, but using existing display info
+ // for the mirrored display.
+ void UpdateWindow();
+
+ // Close the mirror window.
+ void Close();
+
+ // Updates the mirrored cursor location,shape and
+ // visibility.
+ void UpdateCursorLocation();
+ void SetMirroredCursor(gfx::NativeCursor cursor);
+ void SetMirroredCursorVisibility(bool visible);
+
+ // aura::RootWindowObserver overrides:
+ virtual void OnRootWindowHostResized(const aura::RootWindow* root) OVERRIDE;
+
+ private:
+ friend class test::MirrorWindowTestApi;
+
+ // Creates a RootWindowTransformer for current display
+ // configuration.
+ scoped_ptr<aura::RootWindowTransformer> CreateRootWindowTransformer() const;
+
+ int current_cursor_type_;
+ gfx::Display::Rotation current_cursor_rotation_;
+ aura::Window* cursor_window_; // owned by root window.
+ scoped_ptr<aura::RootWindow> root_window_;
+ scoped_ptr<CursorWindowDelegate> cursor_window_delegate_;
+ gfx::Point hot_point_;
+ gfx::Size mirror_window_host_size_;
+ scoped_refptr<ui::Reflector> reflector_;
+
+ DISALLOW_COPY_AND_ASSIGN(MirrorWindowController);
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_DISPLAY_MIRROR_WINDOW_CONTROLLER_H_
diff --git a/chromium/ash/display/mirror_window_controller_unittest.cc b/chromium/ash/display/mirror_window_controller_unittest.cc
new file mode 100644
index 00000000000..75317d1810a
--- /dev/null
+++ b/chromium/ash/display/mirror_window_controller_unittest.cc
@@ -0,0 +1,255 @@
+// Copyright (c) 2013 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.
+
+#include "ash/display/mirror_window_controller.h"
+
+#include "ash/display/display_manager.h"
+#include "ash/shell.h"
+#include "ash/test/ash_test_base.h"
+#include "ash/test/mirror_window_test_api.h"
+#include "base/strings/stringprintf.h"
+#include "ui/aura/root_window.h"
+#include "ui/aura/test/event_generator.h"
+#include "ui/aura/test/test_window_delegate.h"
+#include "ui/aura/test/test_windows.h"
+#include "ui/aura/window.h"
+#include "ui/base/hit_test.h"
+
+namespace ash {
+namespace internal {
+
+namespace {
+DisplayInfo CreateDisplayInfo(int64 id, const gfx::Rect& bounds) {
+ DisplayInfo info(id, base::StringPrintf("x-%d", static_cast<int>(id)), false);
+ info.SetBounds(bounds);
+ return info;
+}
+
+}
+
+typedef test::AshTestBase MirrorWindowControllerTest;
+
+#if defined(OS_WIN)
+// Software mirroring does not work on win.
+#define MAYBE_MirrorCursorBasic DISABLED_MirrorCursorBasic
+#define MAYBE_MirrorCursorLocations DISABLED_MirrorCursorLocations
+#define MAYBE_MirrorCursorRotate DISABLED_MirrorCursorRotate
+#define MAYBE_DockMode DISABLED_DockMode
+#else
+#define MAYBE_MirrorCursorBasic MirrorCursorBasic
+#define MAYBE_MirrorCursorLocations MirrorCursorLocations
+#define MAYBE_MirrorCursorRotate MirrorCursorRotate
+#define MAYBE_DockMode DockMode
+#endif
+
+TEST_F(MirrorWindowControllerTest, MAYBE_MirrorCursorBasic) {
+ test::MirrorWindowTestApi test_api;
+ aura::test::TestWindowDelegate test_window_delegate;
+ test_window_delegate.set_window_component(HTTOP);
+
+ DisplayManager* display_manager = Shell::GetInstance()->display_manager();
+ display_manager->SetSoftwareMirroring(true);
+ UpdateDisplay("400x400,400x400");
+ aura::RootWindow* root = Shell::GetInstance()->GetPrimaryRootWindow();
+ scoped_ptr<aura::Window> window(aura::test::CreateTestWindowWithDelegate(
+ &test_window_delegate,
+ 0,
+ gfx::Rect(50, 50, 100, 100),
+ root));
+ window->Show();
+ window->SetName("foo");
+
+ EXPECT_TRUE(test_api.GetCursorWindow());
+ EXPECT_EQ("50,50 100x100", window->bounds().ToString());
+
+ aura::test::EventGenerator generator(root);
+ generator.MoveMouseTo(10, 10);
+
+ // Test if cursor movement is propertly reflected in mirror window.
+ gfx::Point hot_point = test_api.GetCursorHotPoint();
+ gfx::Point cursor_window_origin =
+ test_api.GetCursorWindow()->bounds().origin();
+ EXPECT_EQ("4,4", hot_point.ToString());
+ EXPECT_EQ(10 - hot_point.x(), cursor_window_origin.x());
+ EXPECT_EQ(10 - hot_point.y(), cursor_window_origin.y());
+ EXPECT_EQ(ui::kCursorNull, test_api.GetCurrentCursorType());
+ EXPECT_TRUE(test_api.GetCursorWindow()->IsVisible());
+
+ // Test if cursor type change is propertly reflected in mirror window.
+ generator.MoveMouseTo(100, 100);
+ hot_point = test_api.GetCursorHotPoint();
+ cursor_window_origin = test_api.GetCursorWindow()->bounds().origin();
+ EXPECT_EQ(100 - hot_point.x(), cursor_window_origin.x());
+ EXPECT_EQ(100 - hot_point.y(), cursor_window_origin.y());
+ EXPECT_EQ(ui::kCursorNorthResize, test_api.GetCurrentCursorType());
+
+ // Test if visibility change is propertly reflected in mirror window.
+ // A key event hides cursor.
+ generator.PressKey(ui::VKEY_A, 0);
+ generator.ReleaseKey(ui::VKEY_A, 0);
+ EXPECT_FALSE(test_api.GetCursorWindow()->IsVisible());
+
+ // Mouse event makes it visible again.
+ generator.MoveMouseTo(300, 300);
+ hot_point = test_api.GetCursorHotPoint();
+ cursor_window_origin = test_api.GetCursorWindow()->bounds().origin();
+ EXPECT_EQ(300 - hot_point.x(), cursor_window_origin.x());
+ EXPECT_EQ(300 - hot_point.y(), cursor_window_origin.y());
+ EXPECT_EQ(ui::kCursorNull, test_api.GetCurrentCursorType());
+ EXPECT_TRUE(test_api.GetCursorWindow()->IsVisible());
+}
+
+TEST_F(MirrorWindowControllerTest, MAYBE_MirrorCursorRotate) {
+ test::MirrorWindowTestApi test_api;
+ aura::test::TestWindowDelegate test_window_delegate;
+ test_window_delegate.set_window_component(HTTOP);
+
+ DisplayManager* display_manager = Shell::GetInstance()->display_manager();
+ display_manager->SetSoftwareMirroring(true);
+ UpdateDisplay("400x400,400x400");
+ aura::RootWindow* root = Shell::GetInstance()->GetPrimaryRootWindow();
+ scoped_ptr<aura::Window> window(aura::test::CreateTestWindowWithDelegate(
+ &test_window_delegate,
+ 0,
+ gfx::Rect(50, 50, 100, 100),
+ root));
+ window->Show();
+ window->SetName("foo");
+
+ EXPECT_TRUE(test_api.GetCursorWindow());
+ EXPECT_EQ("50,50 100x100", window->bounds().ToString());
+
+ aura::test::EventGenerator generator(root);
+ generator.MoveMouseToInHost(100, 100);
+
+ // Test if cursor movement is propertly reflected in mirror window.
+ gfx::Point hot_point = test_api.GetCursorHotPoint();
+ gfx::Point cursor_window_origin =
+ test_api.GetCursorWindow()->bounds().origin();
+ EXPECT_EQ("11,12", hot_point.ToString());
+ EXPECT_EQ(100 - hot_point.x(), cursor_window_origin.x());
+ EXPECT_EQ(100 - hot_point.y(), cursor_window_origin.y());
+ EXPECT_EQ(ui::kCursorNorthResize, test_api.GetCurrentCursorType());
+
+ UpdateDisplay("400x400/r,400x400"); // 90 degrees.
+ generator.MoveMouseToInHost(300, 100);
+ hot_point = test_api.GetCursorHotPoint();
+ cursor_window_origin = test_api.GetCursorWindow()->bounds().origin();
+ EXPECT_EQ(ui::kCursorNorthResize, test_api.GetCurrentCursorType());
+ // The size of cursor image is 25x25, so the rotated hot point must
+ // be (25-12, 11).
+ EXPECT_EQ("13,11", hot_point.ToString());
+ EXPECT_EQ(300 - hot_point.x(), cursor_window_origin.x());
+ EXPECT_EQ(100 - hot_point.y(), cursor_window_origin.y());
+
+ UpdateDisplay("400x400/u,400x400"); // 180 degrees.
+ generator.MoveMouseToInHost(300, 300);
+ hot_point = test_api.GetCursorHotPoint();
+ cursor_window_origin = test_api.GetCursorWindow()->bounds().origin();
+ EXPECT_EQ(ui::kCursorNorthResize, test_api.GetCurrentCursorType());
+ // Rotated hot point must be (25-11, 25-12).
+ EXPECT_EQ("14,13", hot_point.ToString());
+ EXPECT_EQ(300 - hot_point.x(), cursor_window_origin.x());
+ EXPECT_EQ(300 - hot_point.y(), cursor_window_origin.y());
+
+ UpdateDisplay("400x400/l,400x400"); // 270 degrees.
+ generator.MoveMouseToInHost(100, 300);
+ hot_point = test_api.GetCursorHotPoint();
+ cursor_window_origin = test_api.GetCursorWindow()->bounds().origin();
+ EXPECT_EQ(ui::kCursorNorthResize, test_api.GetCurrentCursorType());
+ // Rotated hot point must be (12, 25-11).
+ EXPECT_EQ("12,14", hot_point.ToString());
+ EXPECT_EQ(100 - hot_point.x(), cursor_window_origin.x());
+ EXPECT_EQ(300 - hot_point.y(), cursor_window_origin.y());
+}
+
+// Make sure that the mirror cursor's location is same as
+// the source display's host location in the mirror root window's
+// coordinates.
+TEST_F(MirrorWindowControllerTest, MAYBE_MirrorCursorLocations) {
+ test::MirrorWindowTestApi test_api;
+ DisplayManager* display_manager = Shell::GetInstance()->display_manager();
+ display_manager->SetSoftwareMirroring(true);
+
+ // Test with device scale factor.
+ UpdateDisplay("400x600*2,400x600");
+
+ aura::RootWindow* root = Shell::GetInstance()->GetPrimaryRootWindow();
+ aura::test::EventGenerator generator(root);
+ generator.MoveMouseToInHost(10, 20);
+
+ gfx::Point hot_point = test_api.GetCursorHotPoint();
+ EXPECT_EQ("8,9", hot_point.ToString());
+ gfx::Point cursor_window_origin =
+ test_api.GetCursorWindow()->bounds().origin();
+ EXPECT_EQ(10 - hot_point.x(), cursor_window_origin.x());
+ EXPECT_EQ(20 - hot_point.y(), cursor_window_origin.y());
+
+ // Test with ui scale
+ UpdateDisplay("400x600*0.5,400x600");
+ generator.MoveMouseToInHost(20, 30);
+
+ hot_point = test_api.GetCursorHotPoint();
+ EXPECT_EQ("4,4", hot_point.ToString());
+ cursor_window_origin = test_api.GetCursorWindow()->bounds().origin();
+ EXPECT_EQ(20 - hot_point.x(), cursor_window_origin.x());
+ EXPECT_EQ(30 - hot_point.y(), cursor_window_origin.y());
+
+ // Test with rotation
+ UpdateDisplay("400x600/r,400x600");
+ generator.MoveMouseToInHost(30, 40);
+
+ hot_point = test_api.GetCursorHotPoint();
+ EXPECT_EQ("21,4", hot_point.ToString());
+ cursor_window_origin = test_api.GetCursorWindow()->bounds().origin();
+ EXPECT_EQ(30 - hot_point.x(), cursor_window_origin.x());
+ EXPECT_EQ(40 - hot_point.y(), cursor_window_origin.y());
+}
+
+// Make sure that the compositor based mirroring can switch
+// from/to dock mode.
+TEST_F(MirrorWindowControllerTest, MAYBE_DockMode) {
+ DisplayManager* display_manager = Shell::GetInstance()->display_manager();
+
+ const int64 internal_id = 1;
+ const int64 external_id = 2;
+ //const int64 invalid_id = gfx::Display::kInvalidDisplayID;
+
+ const DisplayInfo internal_display_info =
+ CreateDisplayInfo(internal_id, gfx::Rect(0, 0, 500, 500));
+ const DisplayInfo external_display_info =
+ CreateDisplayInfo(external_id, gfx::Rect(1, 1, 100, 100));
+ std::vector<DisplayInfo> display_info_list;
+
+ display_manager->SetSoftwareMirroring(true);
+
+ // software mirroring.
+ display_info_list.push_back(internal_display_info);
+ display_info_list.push_back(external_display_info);
+ display_manager->UpdateDisplays(display_info_list);
+ EXPECT_EQ(1U, display_manager->GetNumDisplays());
+ EXPECT_TRUE(display_manager->IsMirrored());
+ EXPECT_EQ(external_id, display_manager->mirrored_display().id());
+
+ // dock mode.
+ display_info_list.clear();
+ display_info_list.push_back(external_display_info);
+ display_manager->SetSoftwareMirroring(true);
+ display_manager->UpdateDisplays(display_info_list);
+ EXPECT_EQ(1U, display_manager->GetNumDisplays());
+ EXPECT_FALSE(display_manager->IsMirrored());
+
+ // back to software mirroring.
+ display_info_list.clear();
+ display_info_list.push_back(internal_display_info);
+ display_info_list.push_back(external_display_info);
+ display_manager->SetSoftwareMirroring(true);
+ display_manager->UpdateDisplays(display_info_list);
+ EXPECT_EQ(1U, display_manager->GetNumDisplays());
+ EXPECT_TRUE(display_manager->IsMirrored());
+ EXPECT_EQ(external_id, display_manager->mirrored_display().id());
+}
+
+} // namsspace internal
+} // namespace ash
diff --git a/chromium/ash/display/mouse_cursor_event_filter.cc b/chromium/ash/display/mouse_cursor_event_filter.cc
new file mode 100644
index 00000000000..f3c3a746d47
--- /dev/null
+++ b/chromium/ash/display/mouse_cursor_event_filter.cc
@@ -0,0 +1,236 @@
+// Copyright (c) 2012 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.
+
+#include "ash/display/mouse_cursor_event_filter.h"
+
+#include "ash/display/display_controller.h"
+#include "ash/display/display_manager.h"
+#include "ash/display/mirror_window_controller.h"
+#include "ash/display/shared_display_edge_indicator.h"
+#include "ash/screen_ash.h"
+#include "ash/shell.h"
+#include "ash/wm/coordinate_conversion.h"
+#include "ash/wm/window_util.h"
+#include "ui/aura/env.h"
+#include "ui/aura/root_window.h"
+#include "ui/aura/window.h"
+#include "ui/base/events/event.h"
+#include "ui/base/layout.h"
+#include "ui/compositor/dip_util.h"
+#include "ui/gfx/screen.h"
+
+namespace ash {
+namespace internal {
+namespace {
+
+// Maximum size on the display edge that initiate snapping phantom window,
+// from the corner of the display.
+const int kMaximumSnapHeight = 16;
+
+// Minimum height of an indicator on the display edge that allows
+// dragging a window. If two displays shares the edge smaller than
+// this, entire edge will be used as a draggable space.
+const int kMinimumIndicatorHeight = 200;
+
+const int kIndicatorThickness = 1;
+}
+
+MouseCursorEventFilter::MouseCursorEventFilter()
+ : mouse_warp_mode_(WARP_ALWAYS),
+ drag_source_root_(NULL),
+ shared_display_edge_indicator_(new SharedDisplayEdgeIndicator) {
+}
+
+MouseCursorEventFilter::~MouseCursorEventFilter() {
+ HideSharedEdgeIndicator();
+}
+
+void MouseCursorEventFilter::ShowSharedEdgeIndicator(
+ const aura::RootWindow* from) {
+ HideSharedEdgeIndicator();
+ if (Shell::GetScreen()->GetNumDisplays() <= 1 || from == NULL) {
+ src_indicator_bounds_.SetRect(0, 0, 0, 0);
+ dst_indicator_bounds_.SetRect(0, 0, 0, 0);
+ drag_source_root_ = NULL;
+ return;
+ }
+ drag_source_root_ = from;
+
+ DisplayLayout::Position position = Shell::GetInstance()->
+ display_manager()->GetCurrentDisplayLayout().position;
+ if (position == DisplayLayout::TOP || position == DisplayLayout::BOTTOM)
+ UpdateHorizontalIndicatorWindowBounds();
+ else
+ UpdateVerticalIndicatorWindowBounds();
+
+ shared_display_edge_indicator_->Show(src_indicator_bounds_,
+ dst_indicator_bounds_);
+}
+
+void MouseCursorEventFilter::HideSharedEdgeIndicator() {
+ shared_display_edge_indicator_->Hide();
+}
+
+void MouseCursorEventFilter::OnMouseEvent(ui::MouseEvent* event) {
+ // Handle both MOVED and DRAGGED events here because when the mouse pointer
+ // enters the other root window while dragging, the underlying window system
+ // (at least X11) stops generating a ui::ET_MOUSE_MOVED event.
+ if (event->type() != ui::ET_MOUSE_MOVED &&
+ event->type() != ui::ET_MOUSE_DRAGGED) {
+ return;
+ }
+ Shell::GetInstance()->display_controller()->
+ mirror_window_controller()->UpdateCursorLocation();
+
+ gfx::Point point_in_screen(event->location());
+ aura::Window* target = static_cast<aura::Window*>(event->target());
+ wm::ConvertPointToScreen(target, &point_in_screen);
+ if (WarpMouseCursorIfNecessary(target->GetRootWindow(), point_in_screen))
+ event->StopPropagation();
+}
+
+bool MouseCursorEventFilter::WarpMouseCursorIfNecessary(
+ aura::RootWindow* target_root,
+ const gfx::Point& point_in_screen) {
+ if (Shell::GetScreen()->GetNumDisplays() <= 1 ||
+ mouse_warp_mode_ == WARP_NONE)
+ return false;
+ const float scale_at_target = ui::GetDeviceScaleFactor(target_root->layer());
+
+ aura::RootWindow* root_at_point = wm::GetRootWindowAt(point_in_screen);
+ gfx::Point point_in_root = point_in_screen;
+ wm::ConvertPointFromScreen(root_at_point, &point_in_root);
+ gfx::Rect root_bounds = root_at_point->bounds();
+ int offset_x = 0;
+ int offset_y = 0;
+
+ const float scale_at_point = ui::GetDeviceScaleFactor(root_at_point->layer());
+ // If the window is dragged from 2x display to 1x display, the
+ // pointer location is rounded by the source scale factor (2x) so
+ // it will never reach the edge (which is odd). Shrink by scale
+ // factor instead. Only integral scale factor is supported.
+ int shrink =
+ target_root != root_at_point && scale_at_target != scale_at_point ?
+ static_cast<int>(scale_at_target) : 1;
+ // Make the bounds inclusive to detect the edge.
+ root_bounds.Inset(0, 0, shrink, shrink);
+
+ if (point_in_root.x() <= root_bounds.x()) {
+ // Use -2, not -1, to avoid infinite loop of pointer warp.
+ offset_x = -2 * scale_at_target;
+ } else if (point_in_root.x() >= root_bounds.right()) {
+ offset_x = 2 * scale_at_target;
+ } else if (point_in_root.y() <= root_bounds.y()) {
+ offset_y = -2 * scale_at_target;
+ } else if (point_in_root.y() >= root_bounds.bottom()) {
+ offset_y = 2 * scale_at_target;
+ } else {
+ return false;
+ }
+
+ gfx::Point point_in_dst_screen(point_in_screen);
+ point_in_dst_screen.Offset(offset_x, offset_y);
+ aura::RootWindow* dst_root = wm::GetRootWindowAt(point_in_dst_screen);
+
+ // Warp the mouse cursor only if the location is in the indicator bounds
+ // or the mouse pointer is in the destination root.
+ if (mouse_warp_mode_ == WARP_DRAG &&
+ dst_root != drag_source_root_ &&
+ !src_indicator_bounds_.Contains(point_in_screen)) {
+ return false;
+ }
+
+ wm::ConvertPointFromScreen(dst_root, &point_in_dst_screen);
+
+ if (dst_root->bounds().Contains(point_in_dst_screen)) {
+ DCHECK_NE(dst_root, root_at_point);
+ dst_root->MoveCursorTo(point_in_dst_screen);
+ return true;
+ }
+ return false;
+}
+
+void MouseCursorEventFilter::UpdateHorizontalIndicatorWindowBounds() {
+ bool from_primary = Shell::GetPrimaryRootWindow() == drag_source_root_;
+ // GetPrimaryDisplay returns an object on stack, so copy the bounds
+ // instead of using reference.
+ const gfx::Rect primary_bounds =
+ Shell::GetScreen()->GetPrimaryDisplay().bounds();
+ const gfx::Rect secondary_bounds = ScreenAsh::GetSecondaryDisplay().bounds();
+ DisplayLayout::Position position = Shell::GetInstance()->
+ display_manager()->GetCurrentDisplayLayout().position;
+
+ src_indicator_bounds_.set_x(
+ std::max(primary_bounds.x(), secondary_bounds.x()));
+ src_indicator_bounds_.set_width(
+ std::min(primary_bounds.right(), secondary_bounds.right()) -
+ src_indicator_bounds_.x());
+ src_indicator_bounds_.set_height(kIndicatorThickness);
+ src_indicator_bounds_.set_y(
+ position == DisplayLayout::TOP ?
+ primary_bounds.y() - (from_primary ? 0 : kIndicatorThickness) :
+ primary_bounds.bottom() - (from_primary ? kIndicatorThickness : 0));
+
+ dst_indicator_bounds_ = src_indicator_bounds_;
+ dst_indicator_bounds_.set_height(kIndicatorThickness);
+ dst_indicator_bounds_.set_y(
+ position == DisplayLayout::TOP ?
+ primary_bounds.y() - (from_primary ? kIndicatorThickness : 0) :
+ primary_bounds.bottom() - (from_primary ? 0 : kIndicatorThickness));
+}
+
+void MouseCursorEventFilter::UpdateVerticalIndicatorWindowBounds() {
+ bool in_primary = Shell::GetPrimaryRootWindow() == drag_source_root_;
+ // GetPrimaryDisplay returns an object on stack, so copy the bounds
+ // instead of using reference.
+ const gfx::Rect primary_bounds =
+ Shell::GetScreen()->GetPrimaryDisplay().bounds();
+ const gfx::Rect secondary_bounds = ScreenAsh::GetSecondaryDisplay().bounds();
+ DisplayLayout::Position position = Shell::GetInstance()->
+ display_manager()->GetCurrentDisplayLayout().position;
+
+ int upper_shared_y = std::max(primary_bounds.y(), secondary_bounds.y());
+ int lower_shared_y = std::min(primary_bounds.bottom(),
+ secondary_bounds.bottom());
+ int shared_height = lower_shared_y - upper_shared_y;
+
+ int dst_x = position == DisplayLayout::LEFT ?
+ primary_bounds.x() - (in_primary ? kIndicatorThickness : 0) :
+ primary_bounds.right() - (in_primary ? 0 : kIndicatorThickness);
+ dst_indicator_bounds_.SetRect(
+ dst_x, upper_shared_y, kIndicatorThickness, shared_height);
+
+ // The indicator on the source display.
+ src_indicator_bounds_.set_width(kIndicatorThickness);
+ src_indicator_bounds_.set_x(
+ position == DisplayLayout::LEFT ?
+ primary_bounds.x() - (in_primary ? 0 : kIndicatorThickness) :
+ primary_bounds.right() - (in_primary ? kIndicatorThickness : 0));
+
+ const gfx::Rect& source_bounds =
+ in_primary ? primary_bounds : secondary_bounds;
+ int upper_indicator_y = source_bounds.y() + kMaximumSnapHeight;
+ int lower_indicator_y = std::min(source_bounds.bottom(), lower_shared_y);
+
+ // This gives a hight that can be used without sacrifying the snap space.
+ int available_space = lower_indicator_y -
+ std::max(upper_shared_y, upper_indicator_y);
+
+ if (shared_height < kMinimumIndicatorHeight) {
+ // If the shared height is smaller than minimum height, use the
+ // entire height.
+ upper_indicator_y = upper_shared_y;
+ } else if (available_space < kMinimumIndicatorHeight) {
+ // Snap to the bottom.
+ upper_indicator_y =
+ std::max(upper_shared_y, lower_indicator_y + kMinimumIndicatorHeight);
+ } else {
+ upper_indicator_y = std::max(upper_indicator_y, upper_shared_y);
+ }
+ src_indicator_bounds_.set_y(upper_indicator_y);
+ src_indicator_bounds_.set_height(lower_indicator_y - upper_indicator_y);
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/display/mouse_cursor_event_filter.h b/chromium/ash/display/mouse_cursor_event_filter.h
new file mode 100644
index 00000000000..593448ff024
--- /dev/null
+++ b/chromium/ash/display/mouse_cursor_event_filter.h
@@ -0,0 +1,101 @@
+// Copyright (c) 2012 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 ASH_DISPLAY_MOUSE_CURSOR_EVENT_FILTER_H
+#define ASH_DISPLAY_MOUSE_CURSOR_EVENT_FILTER_H
+
+#include "ash/ash_export.h"
+#include "base/compiler_specific.h"
+#include "base/gtest_prod_util.h"
+#include "base/memory/scoped_ptr.h"
+#include "ui/base/events/event_handler.h"
+#include "ui/gfx/rect.h"
+
+namespace aura {
+class RootWindow;
+}
+
+namespace ash {
+class DisplayController;
+
+namespace internal {
+class SharedDisplayEdgeIndicator;
+
+// An event filter that controls mouse location in extended desktop
+// environment.
+class ASH_EXPORT MouseCursorEventFilter : public ui::EventHandler {
+ public:
+ enum MouseWarpMode {
+ WARP_ALWAYS, // Always warp the mouse when possible.
+ WARP_DRAG, // Used when dragging a window. Top and bottom
+ // corner of the shared edge is reserved for window
+ // snapping.
+ WARP_NONE, // No mouse warping. Used when resizing the window.
+ };
+
+ MouseCursorEventFilter();
+ virtual ~MouseCursorEventFilter();
+
+ void set_mouse_warp_mode(MouseWarpMode mouse_warp_mode) {
+ mouse_warp_mode_ = mouse_warp_mode;
+ }
+
+ // Shows/Hide the indicator for window dragging. The |from|
+ // is the window where the dragging started.
+ void ShowSharedEdgeIndicator(const aura::RootWindow* from);
+ void HideSharedEdgeIndicator();
+
+ // Overridden from ui::EventHandler:
+ virtual void OnMouseEvent(ui::MouseEvent* event) OVERRIDE;
+
+ private:
+ FRIEND_TEST_ALL_PREFIXES(MouseCursorEventFilterTest, SetMouseWarpModeFlag);
+ FRIEND_TEST_ALL_PREFIXES(MouseCursorEventFilterTest, WarpMouse);
+ FRIEND_TEST_ALL_PREFIXES(MouseCursorEventFilterTest,
+ WarpMouseDifferentSizeDisplays);
+ FRIEND_TEST_ALL_PREFIXES(MouseCursorEventFilterTest,
+ WarpMouseDifferentScaleDisplays);
+ FRIEND_TEST_ALL_PREFIXES(MouseCursorEventFilterTest,
+ IndicatorBoundsTestOnRight);
+ FRIEND_TEST_ALL_PREFIXES(MouseCursorEventFilterTest,
+ IndicatorBoundsTestOnLeft);
+ FRIEND_TEST_ALL_PREFIXES(MouseCursorEventFilterTest,
+ IndicatorBoundsTestOnTopBottom);
+ FRIEND_TEST_ALL_PREFIXES(MouseCursorEventFilterTest, CursorDeviceScaleFactor);
+ FRIEND_TEST_ALL_PREFIXES(DragWindowResizerTest, WarpMousePointer);
+ FRIEND_TEST_ALL_PREFIXES(DragWindowResizerTest, CursorDeviceScaleFactor);
+ FRIEND_TEST_ALL_PREFIXES(DragWindowResizerTest, MoveWindowAcrossDisplays);
+
+ // Warps the mouse cursor to an alternate root window when the
+ // |point_in_screen|, which is the location of the mouse cursor,
+ // hits or exceeds the edge of the |target_root| and the mouse cursor
+ // is considered to be in an alternate display. Returns true if
+ // the cursor was moved.
+ bool WarpMouseCursorIfNecessary(aura::RootWindow* target_root,
+ const gfx::Point& point_in_screen);
+
+ void UpdateHorizontalIndicatorWindowBounds();
+ void UpdateVerticalIndicatorWindowBounds();
+
+ MouseWarpMode mouse_warp_mode_;
+
+ // The bounds for warp hole windows. |dst_indicator_bounds_| is kept
+ // in the instance for testing.
+ gfx::Rect src_indicator_bounds_;
+ gfx::Rect dst_indicator_bounds_;
+
+ // The root window in which the dragging started.
+ const aura::RootWindow* drag_source_root_;
+
+ // Shows the area where a window can be dragged in to/out from
+ // another display.
+ scoped_ptr<SharedDisplayEdgeIndicator> shared_display_edge_indicator_;
+
+ DISALLOW_COPY_AND_ASSIGN(MouseCursorEventFilter);
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_DISPLAY_MOUSE_CURSOR_EVENT_FILTER_H
diff --git a/chromium/ash/display/mouse_cursor_event_filter_unittest.cc b/chromium/ash/display/mouse_cursor_event_filter_unittest.cc
new file mode 100644
index 00000000000..e4c1f554ed8
--- /dev/null
+++ b/chromium/ash/display/mouse_cursor_event_filter_unittest.cc
@@ -0,0 +1,360 @@
+// Copyright (c) 2012 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.
+
+#include "ash/display/mouse_cursor_event_filter.h"
+
+#include "ash/shell.h"
+#include "ash/test/ash_test_base.h"
+#include "ash/test/cursor_manager_test_api.h"
+#include "ash/display/display_controller.h"
+#include "ash/display/display_layout_store.h"
+#include "ash/display/display_manager.h"
+#include "ui/aura/env.h"
+#include "ui/aura/root_window.h"
+#include "ui/gfx/display.h"
+#include "ui/gfx/screen.h"
+
+namespace ash {
+namespace internal {
+namespace {
+
+gfx::Display GetPrimaryDisplay() {
+ return Shell::GetScreen()->GetDisplayNearestWindow(
+ Shell::GetAllRootWindows()[0]);
+}
+
+gfx::Display GetSecondaryDisplay() {
+ return Shell::GetScreen()->GetDisplayNearestWindow(
+ Shell::GetAllRootWindows()[1]);
+}
+
+} // namespace
+
+typedef test::AshTestBase MouseCursorEventFilterTest;
+
+// Verifies if the mouse pointer correctly moves to another display when there
+// are two displays.
+TEST_F(MouseCursorEventFilterTest, WarpMouse) {
+ if (!SupportsMultipleDisplays())
+ return;
+
+ UpdateDisplay("500x500,500x500");
+
+ MouseCursorEventFilter* event_filter =
+ Shell::GetInstance()->mouse_cursor_filter();
+ ASSERT_EQ(
+ DisplayLayout::RIGHT,
+ Shell::GetInstance()->display_manager()->layout_store()->
+ default_display_layout().position);
+
+ Shell::RootWindowList root_windows = Shell::GetAllRootWindows();
+ bool is_warped = event_filter->WarpMouseCursorIfNecessary(root_windows[0],
+ gfx::Point(11, 11));
+ EXPECT_FALSE(is_warped);
+ is_warped = event_filter->WarpMouseCursorIfNecessary(root_windows[1],
+ gfx::Point(11, 11));
+ EXPECT_FALSE(is_warped);
+
+ // Touch the right edge of the primary root window. Pointer should warp.
+ is_warped = event_filter->WarpMouseCursorIfNecessary(root_windows[0],
+ gfx::Point(499, 11));
+ EXPECT_TRUE(is_warped);
+ EXPECT_EQ("501,11", // by 2px.
+ aura::Env::GetInstance()->last_mouse_location().ToString());
+
+ // Touch the left edge of the secondary root window. Pointer should warp.
+ is_warped = event_filter->WarpMouseCursorIfNecessary(root_windows[1],
+ gfx::Point(500, 11));
+ EXPECT_TRUE(is_warped);
+ EXPECT_EQ("498,11", // by 2px.
+ aura::Env::GetInstance()->last_mouse_location().ToString());
+
+ // Touch the left edge of the primary root window.
+ is_warped = event_filter->WarpMouseCursorIfNecessary(root_windows[0],
+ gfx::Point(0, 11));
+ EXPECT_FALSE(is_warped);
+ // Touch the top edge of the primary root window.
+ is_warped = event_filter->WarpMouseCursorIfNecessary(root_windows[0],
+ gfx::Point(11, 0));
+ EXPECT_FALSE(is_warped);
+ // Touch the bottom edge of the primary root window.
+ is_warped = event_filter->WarpMouseCursorIfNecessary(root_windows[0],
+ gfx::Point(11, 499));
+ EXPECT_FALSE(is_warped);
+ // Touch the right edge of the secondary root window.
+ is_warped = event_filter->WarpMouseCursorIfNecessary(root_windows[1],
+ gfx::Point(999, 11));
+ EXPECT_FALSE(is_warped);
+ // Touch the top edge of the secondary root window.
+ is_warped = event_filter->WarpMouseCursorIfNecessary(root_windows[1],
+ gfx::Point(11, 0));
+ EXPECT_FALSE(is_warped);
+ // Touch the bottom edge of the secondary root window.
+ is_warped = event_filter->WarpMouseCursorIfNecessary(root_windows[1],
+ gfx::Point(11, 499));
+ EXPECT_FALSE(is_warped);
+}
+
+// Verifies if the mouse pointer correctly moves to another display even when
+// two displays are not the same size.
+TEST_F(MouseCursorEventFilterTest, WarpMouseDifferentSizeDisplays) {
+ if (!SupportsMultipleDisplays())
+ return;
+
+ UpdateDisplay("500x500,600x600"); // the second one is larger.
+
+ MouseCursorEventFilter* event_filter =
+ Shell::GetInstance()->mouse_cursor_filter();
+ ASSERT_EQ(
+ DisplayLayout::RIGHT,
+ Shell::GetInstance()->display_manager()->
+ GetCurrentDisplayLayout().position);
+
+ Shell::RootWindowList root_windows = Shell::GetAllRootWindows();
+ aura::Env::GetInstance()->set_last_mouse_location(gfx::Point(623, 123));
+
+ // Touch the left edge of the secondary root window. Pointer should NOT warp
+ // because 1px left of (0, 500) is outside the primary root window.
+ bool is_warped = event_filter->WarpMouseCursorIfNecessary(root_windows[1],
+ gfx::Point(0, 500));
+ EXPECT_FALSE(is_warped);
+ EXPECT_EQ("623,123", // by 2px.
+ aura::Env::GetInstance()->last_mouse_location().ToString());
+
+ // Touch the left edge of the secondary root window. Pointer should warp
+ // because 1px left of (0, 499) is inside the primary root window.
+ is_warped = event_filter->WarpMouseCursorIfNecessary(root_windows[1],
+ gfx::Point(500, 499));
+ EXPECT_TRUE(is_warped);
+ EXPECT_EQ("498,499", // by 2px.
+ aura::Env::GetInstance()->last_mouse_location().ToString());
+}
+
+// Verifies if the mouse pointer correctly moves between displays with
+// different scale factors.
+TEST_F(MouseCursorEventFilterTest, WarpMouseDifferentScaleDisplays) {
+ if (!SupportsMultipleDisplays())
+ return;
+
+ UpdateDisplay("500x500,600x600*2");
+
+ MouseCursorEventFilter* event_filter =
+ Shell::GetInstance()->mouse_cursor_filter();
+ ASSERT_EQ(
+ DisplayLayout::RIGHT,
+ Shell::GetInstance()->display_manager()->
+ GetCurrentDisplayLayout().position);
+
+ Shell::RootWindowList root_windows = Shell::GetAllRootWindows();
+ aura::Env::GetInstance()->set_last_mouse_location(gfx::Point(900, 123));
+
+ // This emulates the dragging back to the 2nd display, which has
+ // higher scale factor, by having 2nd display's root as target
+ // but have the edge of 1st display.
+ bool is_warped =
+ event_filter->WarpMouseCursorIfNecessary(root_windows[1],
+ gfx::Point(498, 123));
+ EXPECT_TRUE(is_warped);
+ EXPECT_EQ("502,123",
+ aura::Env::GetInstance()->last_mouse_location().ToString());
+
+ // Touch the edge of 2nd display again and make sure it warps to
+ // 1st dislay.
+ is_warped = event_filter->WarpMouseCursorIfNecessary(root_windows[1],
+ gfx::Point(500, 123));
+ EXPECT_TRUE(is_warped);
+ EXPECT_EQ("496,123",
+ aura::Env::GetInstance()->last_mouse_location().ToString());
+}
+
+// Verifies if MouseCursorEventFilter::set_mouse_warp_mode() works as expected.
+TEST_F(MouseCursorEventFilterTest, SetMouseWarpModeFlag) {
+ if (!SupportsMultipleDisplays())
+ return;
+
+ UpdateDisplay("500x500,500x500");
+
+ MouseCursorEventFilter* event_filter =
+ Shell::GetInstance()->mouse_cursor_filter();
+
+ Shell::RootWindowList root_windows = Shell::GetAllRootWindows();
+ aura::Env::GetInstance()->set_last_mouse_location(gfx::Point(1, 1));
+
+ event_filter->set_mouse_warp_mode(MouseCursorEventFilter::WARP_NONE);
+ bool is_warped = event_filter->WarpMouseCursorIfNecessary(root_windows[0],
+ gfx::Point(499, 11));
+ EXPECT_FALSE(is_warped);
+ EXPECT_EQ("1,1",
+ aura::Env::GetInstance()->last_mouse_location().ToString());
+
+ event_filter->set_mouse_warp_mode(MouseCursorEventFilter::WARP_ALWAYS);
+ is_warped = event_filter->WarpMouseCursorIfNecessary(root_windows[0],
+ gfx::Point(499, 11));
+ EXPECT_TRUE(is_warped);
+ EXPECT_EQ("501,11",
+ aura::Env::GetInstance()->last_mouse_location().ToString());
+}
+
+// Verifies if MouseCursorEventFilter's bounds calculation works correctly.
+TEST_F(MouseCursorEventFilterTest, IndicatorBoundsTestOnRight) {
+ if (!SupportsMultipleDisplays())
+ return;
+
+ UpdateDisplay("360x360,700x700");
+ Shell::RootWindowList root_windows = Shell::GetAllRootWindows();
+
+ DisplayController* controller =
+ Shell::GetInstance()->display_controller();
+ DisplayLayout layout(DisplayLayout::RIGHT, 0);
+ controller->SetLayoutForCurrentDisplays(layout);
+ ash::internal::MouseCursorEventFilter* event_filter =
+ Shell::GetInstance()->mouse_cursor_filter();
+ event_filter->ShowSharedEdgeIndicator(root_windows[0] /* primary */);
+ EXPECT_EQ("359,16 1x344", event_filter->src_indicator_bounds_.ToString());
+ EXPECT_EQ("360,0 1x360", event_filter->dst_indicator_bounds_.ToString());
+ event_filter->ShowSharedEdgeIndicator(root_windows[1] /* secondary */);
+ EXPECT_EQ("360,16 1x344", event_filter->src_indicator_bounds_.ToString());
+ EXPECT_EQ("359,0 1x360", event_filter->dst_indicator_bounds_.ToString());
+
+ // Move 2nd display downwards a bit.
+ layout.offset = 5;
+ controller->SetLayoutForCurrentDisplays(layout);
+ event_filter->ShowSharedEdgeIndicator(root_windows[0] /* primary */);
+ // This is same as before because the 2nd display's y is above
+ // the indicator's x.
+ EXPECT_EQ("359,16 1x344", event_filter->src_indicator_bounds_.ToString());
+ EXPECT_EQ("360,5 1x355", event_filter->dst_indicator_bounds_.ToString());
+ event_filter->ShowSharedEdgeIndicator(root_windows[1] /* secondary */);
+ EXPECT_EQ("360,21 1x339", event_filter->src_indicator_bounds_.ToString());
+ EXPECT_EQ("359,5 1x355", event_filter->dst_indicator_bounds_.ToString());
+
+ // Move it down further so that the shared edge is shorter than
+ // minimum hole size (160).
+ layout.offset = 200;
+ controller->SetLayoutForCurrentDisplays(layout);
+ event_filter->ShowSharedEdgeIndicator(root_windows[0] /* primary */);
+ EXPECT_EQ("359,200 1x160", event_filter->src_indicator_bounds_.ToString());
+ EXPECT_EQ("360,200 1x160", event_filter->dst_indicator_bounds_.ToString());
+ event_filter->ShowSharedEdgeIndicator(root_windows[1] /* secondary */);
+ EXPECT_EQ("360,200 1x160", event_filter->src_indicator_bounds_.ToString());
+ EXPECT_EQ("359,200 1x160", event_filter->dst_indicator_bounds_.ToString());
+
+ // Now move 2nd display upwards
+ layout.offset = -5;
+ controller->SetLayoutForCurrentDisplays(layout);
+ event_filter->ShowSharedEdgeIndicator(root_windows[0] /* primary */);
+ EXPECT_EQ("359,16 1x344", event_filter->src_indicator_bounds_.ToString());
+ EXPECT_EQ("360,0 1x360", event_filter->dst_indicator_bounds_.ToString());
+ event_filter->ShowSharedEdgeIndicator(root_windows[1] /* secondary */);
+ // 16 px are reserved on 2nd display from top, so y must be
+ // (16 - 5) = 11
+ EXPECT_EQ("360,11 1x349", event_filter->src_indicator_bounds_.ToString());
+ EXPECT_EQ("359,0 1x360", event_filter->dst_indicator_bounds_.ToString());
+
+ event_filter->HideSharedEdgeIndicator();
+}
+
+TEST_F(MouseCursorEventFilterTest, IndicatorBoundsTestOnLeft) {
+ if (!SupportsMultipleDisplays())
+ return;
+
+ UpdateDisplay("360x360,700x700");
+ Shell::RootWindowList root_windows = Shell::GetAllRootWindows();
+
+ DisplayController* controller =
+ Shell::GetInstance()->display_controller();
+ DisplayLayout layout(DisplayLayout::LEFT, 0);
+ controller->SetLayoutForCurrentDisplays(layout);
+ ash::internal::MouseCursorEventFilter* event_filter =
+ Shell::GetInstance()->mouse_cursor_filter();
+ event_filter->ShowSharedEdgeIndicator(root_windows[0] /* primary */);
+ EXPECT_EQ("0,16 1x344", event_filter->src_indicator_bounds_.ToString());
+ EXPECT_EQ("-1,0 1x360", event_filter->dst_indicator_bounds_.ToString());
+ event_filter->ShowSharedEdgeIndicator(root_windows[1] /* secondary */);
+ EXPECT_EQ("-1,16 1x344", event_filter->src_indicator_bounds_.ToString());
+ EXPECT_EQ("0,0 1x360", event_filter->dst_indicator_bounds_.ToString());
+
+ layout.offset = 250;
+ controller->SetLayoutForCurrentDisplays(layout);
+ event_filter->ShowSharedEdgeIndicator(root_windows[0] /* primary */);
+ EXPECT_EQ("0,250 1x110", event_filter->src_indicator_bounds_.ToString());
+ EXPECT_EQ("-1,250 1x110", event_filter->dst_indicator_bounds_.ToString());
+ event_filter->ShowSharedEdgeIndicator(root_windows[1] /* secondary */);
+ EXPECT_EQ("-1,250 1x110", event_filter->src_indicator_bounds_.ToString());
+ EXPECT_EQ("0,250 1x110", event_filter->dst_indicator_bounds_.ToString());
+ event_filter->HideSharedEdgeIndicator();
+}
+
+TEST_F(MouseCursorEventFilterTest, IndicatorBoundsTestOnTopBottom) {
+ if (!SupportsMultipleDisplays())
+ return;
+
+ UpdateDisplay("360x360,700x700");
+ Shell::RootWindowList root_windows = Shell::GetAllRootWindows();
+
+ DisplayController* controller =
+ Shell::GetInstance()->display_controller();
+ DisplayLayout layout(DisplayLayout::TOP, 0);
+ controller->SetLayoutForCurrentDisplays(layout);
+ ash::internal::MouseCursorEventFilter* event_filter =
+ Shell::GetInstance()->mouse_cursor_filter();
+ event_filter->ShowSharedEdgeIndicator(root_windows[0] /* primary */);
+ EXPECT_EQ("0,0 360x1", event_filter->src_indicator_bounds_.ToString());
+ EXPECT_EQ("0,-1 360x1", event_filter->dst_indicator_bounds_.ToString());
+ event_filter->ShowSharedEdgeIndicator(root_windows[1] /* secondary */);
+ EXPECT_EQ("0,-1 360x1", event_filter->src_indicator_bounds_.ToString());
+ EXPECT_EQ("0,0 360x1", event_filter->dst_indicator_bounds_.ToString());
+
+ layout.offset = 250;
+ controller->SetLayoutForCurrentDisplays(layout);
+ event_filter->ShowSharedEdgeIndicator(root_windows[0] /* primary */);
+ EXPECT_EQ("250,0 110x1", event_filter->src_indicator_bounds_.ToString());
+ EXPECT_EQ("250,-1 110x1", event_filter->dst_indicator_bounds_.ToString());
+ event_filter->ShowSharedEdgeIndicator(root_windows[1] /* secondary */);
+ EXPECT_EQ("250,-1 110x1", event_filter->src_indicator_bounds_.ToString());
+ EXPECT_EQ("250,0 110x1", event_filter->dst_indicator_bounds_.ToString());
+
+ layout.position = DisplayLayout::BOTTOM;
+ layout.offset = 0;
+ controller->SetLayoutForCurrentDisplays(layout);
+ event_filter->ShowSharedEdgeIndicator(root_windows[0] /* primary */);
+ EXPECT_EQ("0,359 360x1", event_filter->src_indicator_bounds_.ToString());
+ EXPECT_EQ("0,360 360x1", event_filter->dst_indicator_bounds_.ToString());
+ event_filter->ShowSharedEdgeIndicator(root_windows[1] /* secondary */);
+ EXPECT_EQ("0,360 360x1", event_filter->src_indicator_bounds_.ToString());
+ EXPECT_EQ("0,359 360x1", event_filter->dst_indicator_bounds_.ToString());
+
+ event_filter->HideSharedEdgeIndicator();
+}
+
+// Verifies cursor's device scale factor is updated when a cursor has moved
+// across root windows with different device scale factors
+// (http://crbug.com/154183).
+TEST_F(MouseCursorEventFilterTest, CursorDeviceScaleFactor) {
+ if (!SupportsMultipleDisplays())
+ return;
+
+ UpdateDisplay("400x400,800x800*2");
+ DisplayController* controller =
+ Shell::GetInstance()->display_controller();
+ controller->SetLayoutForCurrentDisplays(
+ DisplayLayout(DisplayLayout::RIGHT, 0));
+ Shell::RootWindowList root_windows = Shell::GetAllRootWindows();
+ ASSERT_EQ(2U, root_windows.size());
+ test::CursorManagerTestApi cursor_test_api(
+ Shell::GetInstance()->cursor_manager());
+ MouseCursorEventFilter* event_filter =
+ Shell::GetInstance()->mouse_cursor_filter();
+
+ EXPECT_EQ(1.0f, cursor_test_api.GetDisplay().device_scale_factor());
+ event_filter->WarpMouseCursorIfNecessary(root_windows[0],
+ gfx::Point(399, 200));
+ EXPECT_EQ(2.0f, cursor_test_api.GetDisplay().device_scale_factor());
+ event_filter->WarpMouseCursorIfNecessary(root_windows[1],
+ gfx::Point(400, 200));
+ EXPECT_EQ(1.0f, cursor_test_api.GetDisplay().device_scale_factor());
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/display/output_configurator_animation.cc b/chromium/ash/display/output_configurator_animation.cc
new file mode 100644
index 00000000000..6161befb34b
--- /dev/null
+++ b/chromium/ash/display/output_configurator_animation.cc
@@ -0,0 +1,228 @@
+// Copyright (c) 2012 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.
+
+#include "ash/display/output_configurator_animation.h"
+
+#include "ash/shell.h"
+#include "ash/shell_window_ids.h"
+#include "base/bind.h"
+#include "base/stl_util.h"
+#include "base/time/time.h"
+#include "ui/aura/root_window.h"
+#include "ui/aura/window.h"
+#include "ui/compositor/layer.h"
+#include "ui/compositor/layer_animation_observer.h"
+#include "ui/compositor/layer_animation_sequence.h"
+#include "ui/compositor/layer_animator.h"
+#include "ui/compositor/scoped_layer_animation_settings.h"
+
+namespace ash {
+namespace internal {
+namespace {
+
+const int kFadingAnimationDurationInMS = 200;
+const int kFadingTimeoutDurationInSeconds = 10;
+
+// CallbackRunningObserver accepts multiple layer animations and
+// runs the specified |callback| when all of the animations have finished.
+class CallbackRunningObserver {
+ public:
+ CallbackRunningObserver(base::Closure callback)
+ : completed_counter_(0),
+ animation_aborted_(false),
+ callback_(callback) {}
+
+ void AddNewAnimator(ui::LayerAnimator* animator) {
+ Observer* observer = new Observer(animator, this);
+ animator->AddObserver(observer);
+ observer_list_.push_back(observer);
+ }
+
+ private:
+ void OnSingleTaskCompleted() {
+ completed_counter_++;
+ if (completed_counter_ >= observer_list_.size()) {
+ base::MessageLoopForUI::current()->DeleteSoon(FROM_HERE, this);
+ if (!animation_aborted_)
+ base::MessageLoopForUI::current()->PostTask(FROM_HERE, callback_);
+ }
+ }
+
+ void OnSingleTaskAborted() {
+ animation_aborted_ = true;
+ OnSingleTaskCompleted();
+ }
+
+ // The actual observer to listen each animation completion.
+ class Observer : public ui::LayerAnimationObserver {
+ public:
+ Observer(ui::LayerAnimator* animator,
+ CallbackRunningObserver* observer)
+ : animator_(animator),
+ observer_(observer) {}
+
+ protected:
+ // ui::LayerAnimationObserver overrides:
+ virtual void OnLayerAnimationEnded(
+ ui::LayerAnimationSequence* sequence) OVERRIDE {
+ animator_->RemoveObserver(this);
+ observer_->OnSingleTaskCompleted();
+ }
+ virtual void OnLayerAnimationAborted(
+ ui::LayerAnimationSequence* sequence) OVERRIDE {
+ animator_->RemoveObserver(this);
+ observer_->OnSingleTaskAborted();
+ }
+ virtual void OnLayerAnimationScheduled(
+ ui::LayerAnimationSequence* sequence) OVERRIDE {
+ }
+ virtual bool RequiresNotificationWhenAnimatorDestroyed() const OVERRIDE {
+ return true;
+ }
+
+ private:
+ ui::LayerAnimator* animator_;
+ CallbackRunningObserver* observer_;
+
+ DISALLOW_COPY_AND_ASSIGN(Observer);
+ };
+
+ size_t completed_counter_;
+ bool animation_aborted_;
+ ScopedVector<Observer> observer_list_;
+ base::Closure callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(CallbackRunningObserver);
+};
+
+} // namespace
+
+OutputConfiguratorAnimation::OutputConfiguratorAnimation() {
+}
+
+OutputConfiguratorAnimation::~OutputConfiguratorAnimation() {
+ ClearHidingLayers();
+}
+
+void OutputConfiguratorAnimation::StartFadeOutAnimation(
+ base::Closure callback) {
+ CallbackRunningObserver* observer = new CallbackRunningObserver(callback);
+ ClearHidingLayers();
+
+ // Make the fade-out animation for all root windows. Instead of actually
+ // hiding the root windows, we put a black layer over a root window for
+ // safety. These layers remain to hide root windows and will be deleted
+ // after the animation of OnDisplayModeChanged().
+ Shell::RootWindowList root_windows =
+ Shell::GetInstance()->GetAllRootWindows();
+ for (Shell::RootWindowList::const_iterator it = root_windows.begin();
+ it != root_windows.end(); ++it) {
+ aura::RootWindow* root_window = *it;
+ ui::Layer* hiding_layer = new ui::Layer(ui::LAYER_SOLID_COLOR);
+ hiding_layer->SetColor(SK_ColorBLACK);
+ hiding_layer->SetBounds(root_window->bounds());
+ ui::Layer* parent = ash::Shell::GetContainer(
+ root_window,
+ ash::internal::kShellWindowId_OverlayContainer)->layer();
+ parent->Add(hiding_layer);
+
+ hiding_layer->SetOpacity(0.0);
+
+ ui::ScopedLayerAnimationSettings settings(hiding_layer->GetAnimator());
+ settings.SetTransitionDuration(base::TimeDelta::FromMilliseconds(
+ kFadingAnimationDurationInMS));
+ observer->AddNewAnimator(hiding_layer->GetAnimator());
+ hiding_layer->SetOpacity(1.0f);
+ hiding_layer->SetVisible(true);
+ hiding_layers_[root_window] = hiding_layer;
+ }
+
+ // In case that OnDisplayModeChanged() isn't called or its animator is
+ // canceled due to some unknown errors, we set a timer to clear these
+ // hiding layers.
+ timer_.reset(new base::OneShotTimer<OutputConfiguratorAnimation>());
+ timer_->Start(FROM_HERE,
+ base::TimeDelta::FromSeconds(kFadingTimeoutDurationInSeconds),
+ this,
+ &OutputConfiguratorAnimation::ClearHidingLayers);
+}
+
+void OutputConfiguratorAnimation::StartFadeInAnimation() {
+ // We want to make sure clearing all of hiding layers after the animation
+ // finished. Note that this callback can be canceled, but the cancel only
+ // happens when the next animation is scheduled. Thus the hiding layers
+ // should be deleted eventually.
+ CallbackRunningObserver* observer = new CallbackRunningObserver(
+ base::Bind(&OutputConfiguratorAnimation::ClearHidingLayers,
+ base::Unretained(this)));
+
+ // Ensure that layers are not animating.
+ for (std::map<aura::RootWindow*, ui::Layer*>::iterator it =
+ hiding_layers_.begin(); it != hiding_layers_.end(); ++it) {
+ ui::LayerAnimator* animator = it->second->GetAnimator();
+ if (animator->is_animating())
+ animator->StopAnimating();
+ }
+
+ // Schedules the fade-in effect for all root windows. Because we put the
+ // black layers for fade-out, here we actually turn those black layers
+ // invisible.
+ Shell::RootWindowList root_windows =
+ Shell::GetInstance()->GetAllRootWindows();
+ for (Shell::RootWindowList::const_iterator it = root_windows.begin();
+ it != root_windows.end(); ++it) {
+ aura::RootWindow* root_window = *it;
+ ui::Layer* hiding_layer = NULL;
+ if (hiding_layers_.find(root_window) == hiding_layers_.end()) {
+ // In case of the transition from mirroring->non-mirroring, new root
+ // windows appear and we do not have the black layers for them. Thus
+ // we need to create the layer and make it visible.
+ hiding_layer = new ui::Layer(ui::LAYER_SOLID_COLOR);
+ hiding_layer->SetColor(SK_ColorBLACK);
+ hiding_layer->SetBounds(root_window->bounds());
+ ui::Layer* parent = ash::Shell::GetContainer(
+ root_window,
+ ash::internal::kShellWindowId_OverlayContainer)->layer();
+ parent->Add(hiding_layer);
+ hiding_layer->SetOpacity(1.0f);
+ hiding_layer->SetVisible(true);
+ hiding_layers_[root_window] = hiding_layer;
+ } else {
+ hiding_layer = hiding_layers_[root_window];
+ if (hiding_layer->bounds() != root_window->bounds())
+ hiding_layer->SetBounds(root_window->bounds());
+ }
+
+ ui::ScopedLayerAnimationSettings settings(hiding_layer->GetAnimator());
+ settings.SetTransitionDuration(base::TimeDelta::FromMilliseconds(
+ kFadingAnimationDurationInMS));
+ observer->AddNewAnimator(hiding_layer->GetAnimator());
+ hiding_layer->SetOpacity(0.0f);
+ hiding_layer->SetVisible(false);
+ }
+}
+
+void OutputConfiguratorAnimation::OnDisplayModeChanged() {
+ if (!hiding_layers_.empty())
+ StartFadeInAnimation();
+}
+
+void OutputConfiguratorAnimation::OnDisplayModeChangeFailed(
+ chromeos::OutputState failed_new_state) {
+ if (!hiding_layers_.empty())
+ StartFadeInAnimation();
+}
+
+void OutputConfiguratorAnimation::ClearHidingLayers() {
+ if (timer_) {
+ timer_->Stop();
+ timer_.reset();
+ }
+ STLDeleteContainerPairSecondPointers(
+ hiding_layers_.begin(), hiding_layers_.end());
+ hiding_layers_.clear();
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/display/output_configurator_animation.h b/chromium/ash/display/output_configurator_animation.h
new file mode 100644
index 00000000000..3fefe923434
--- /dev/null
+++ b/chromium/ash/display/output_configurator_animation.h
@@ -0,0 +1,64 @@
+// Copyright (c) 2012 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 ASH_DISPLAY_OUTPUT_CONFIGURATOR_ANIMATION_H_
+#define ASH_DISPLAY_OUTPUT_CONFIGURATOR_ANIMATION_H_
+
+#include <map>
+
+#include "ash/ash_export.h"
+#include "base/callback.h"
+#include "base/timer/timer.h"
+#include "chromeos/display/output_configurator.h"
+
+namespace aura {
+class RootWindow;
+} // namespace aura
+
+namespace ui {
+class Layer;
+} // namespace ui
+
+namespace ash {
+namespace internal {
+
+// OutputConfiguratorAnimation provides the visual effects for
+// chromeos::OutputConfigurator, such like fade-out/in during changing
+// the display mode.
+class ASH_EXPORT OutputConfiguratorAnimation
+ : public chromeos::OutputConfigurator::Observer {
+ public:
+ OutputConfiguratorAnimation();
+ virtual ~OutputConfiguratorAnimation();
+
+ // Starts the fade-out animation for the all root windows. It will
+ // call |callback| once all of the animations have finished.
+ void StartFadeOutAnimation(base::Closure callback);
+
+ // Starts the animation to clear the fade-out animation effect
+ // for the all root windows.
+ void StartFadeInAnimation();
+
+ protected:
+ // chromeos::OutputConfigurator::Observer overrides:
+ virtual void OnDisplayModeChanged() OVERRIDE;
+ virtual void OnDisplayModeChangeFailed(
+ chromeos::OutputState failed_new_state) OVERRIDE;
+
+ private:
+ // Clears all hiding layers. Note that in case that this method is called
+ // during an animation, the method call will cancel all of the animations
+ // and *not* call the registered callback.
+ void ClearHidingLayers();
+
+ std::map<aura::RootWindow*, ui::Layer*> hiding_layers_;
+ scoped_ptr<base::OneShotTimer<OutputConfiguratorAnimation> > timer_;
+
+ DISALLOW_COPY_AND_ASSIGN(OutputConfiguratorAnimation);
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_DISPLAY_OUTPUT_CONFIGURATION_CONTROLLER_H_
diff --git a/chromium/ash/display/resolution_notification_controller.cc b/chromium/ash/display/resolution_notification_controller.cc
new file mode 100644
index 00000000000..e2755a037a8
--- /dev/null
+++ b/chromium/ash/display/resolution_notification_controller.cc
@@ -0,0 +1,299 @@
+// Copyright 2013 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.
+
+#include "ash/display/resolution_notification_controller.h"
+
+#include "ash/display/display_controller.h"
+#include "ash/display/display_manager.h"
+#include "ash/shell.h"
+#include "base/strings/utf_string_conversions.h"
+#include "grit/ash_resources.h"
+#include "grit/ash_strings.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/base/l10n/time_format.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "ui/gfx/display.h"
+#include "ui/gfx/screen.h"
+#include "ui/message_center/message_center.h"
+#include "ui/message_center/notification.h"
+#include "ui/message_center/notification_delegate.h"
+
+using message_center::Notification;
+
+namespace ash {
+namespace internal {
+namespace {
+
+bool g_use_timer = true;
+
+class ResolutionChangeNotificationDelegate
+ : public message_center::NotificationDelegate {
+ public:
+ ResolutionChangeNotificationDelegate(
+ ResolutionNotificationController* controller,
+ bool has_timeout);
+
+ protected:
+ virtual ~ResolutionChangeNotificationDelegate();
+
+ private:
+ // message_center::NotificationDelegate overrides:
+ virtual void Display() OVERRIDE;
+ virtual void Error() OVERRIDE;
+ virtual void Close(bool by_user) OVERRIDE;
+ virtual void Click() OVERRIDE;
+ virtual bool HasClickedListener() OVERRIDE;
+ virtual void ButtonClick(int button_index) OVERRIDE;
+
+ ResolutionNotificationController* controller_;
+ bool has_timeout_;
+
+ DISALLOW_COPY_AND_ASSIGN(ResolutionChangeNotificationDelegate);
+};
+
+ResolutionChangeNotificationDelegate::ResolutionChangeNotificationDelegate(
+ ResolutionNotificationController* controller,
+ bool has_timeout)
+ : controller_(controller),
+ has_timeout_(has_timeout) {
+ DCHECK(controller_);
+}
+
+ResolutionChangeNotificationDelegate::~ResolutionChangeNotificationDelegate() {
+}
+
+void ResolutionChangeNotificationDelegate::Display() {
+}
+
+void ResolutionChangeNotificationDelegate::Error() {
+}
+
+void ResolutionChangeNotificationDelegate::Close(bool by_user) {
+ if (by_user)
+ controller_->AcceptResolutionChange(false);
+}
+
+void ResolutionChangeNotificationDelegate::Click() {
+ controller_->AcceptResolutionChange(true);
+}
+
+bool ResolutionChangeNotificationDelegate::HasClickedListener() {
+ return true;
+}
+
+void ResolutionChangeNotificationDelegate::ButtonClick(int button_index) {
+ // If there's the timeout, the first button is "Accept". Otherwise the
+ // button click should be "Revert".
+ if (has_timeout_ && button_index == 0)
+ controller_->AcceptResolutionChange(true);
+ else
+ controller_->RevertResolutionChange();
+}
+
+} // namespace
+
+// static
+const int ResolutionNotificationController::kTimeoutInSec = 15;
+
+// static
+const char ResolutionNotificationController::kNotificationId[] =
+ "chrome://settings/display/resolution";
+
+struct ResolutionNotificationController::ResolutionChangeInfo {
+ ResolutionChangeInfo(int64 display_id,
+ const gfx::Size& old_resolution,
+ const gfx::Size& new_resolution,
+ const base::Closure& accept_callback);
+ ~ResolutionChangeInfo();
+
+ // The id of the display where the resolution change happens.
+ int64 display_id;
+
+ // The resolution before the change.
+ gfx::Size old_resolution;
+
+ // The new resolution after the change.
+ gfx::Size new_resolution;
+
+ // The callback when accept is chosen.
+ base::Closure accept_callback;
+
+ // The remaining timeout in seconds. 0 if the change does not time out.
+ uint8 timeout_count;
+
+ // The timer to invoke OnTimerTick() every second. This cannot be
+ // OneShotTimer since the message contains text "automatically closed in xx
+ // seconds..." which has to be updated every second.
+ base::RepeatingTimer<ResolutionNotificationController> timer;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ResolutionChangeInfo);
+};
+
+ResolutionNotificationController::ResolutionChangeInfo::ResolutionChangeInfo(
+ int64 display_id,
+ const gfx::Size& old_resolution,
+ const gfx::Size& new_resolution,
+ const base::Closure& accept_callback)
+ : display_id(display_id),
+ old_resolution(old_resolution),
+ new_resolution(new_resolution),
+ accept_callback(accept_callback),
+ timeout_count(0) {
+ DisplayManager* display_manager = Shell::GetInstance()->display_manager();
+ if (!display_manager->HasInternalDisplay() &&
+ display_manager->num_connected_displays() == 1u) {
+ timeout_count = kTimeoutInSec;
+ }
+}
+
+ResolutionNotificationController::ResolutionChangeInfo::
+ ~ResolutionChangeInfo() {
+}
+
+ResolutionNotificationController::ResolutionNotificationController() {
+ Shell::GetInstance()->display_controller()->AddObserver(this);
+ Shell::GetScreen()->AddObserver(this);
+}
+
+ResolutionNotificationController::~ResolutionNotificationController() {
+ Shell::GetInstance()->display_controller()->RemoveObserver(this);
+ Shell::GetScreen()->RemoveObserver(this);
+}
+
+void ResolutionNotificationController::SetDisplayResolutionAndNotify(
+ int64 display_id,
+ const gfx::Size& old_resolution,
+ const gfx::Size& new_resolution,
+ const base::Closure& accept_callback) {
+ // If multiple resolution changes are invoked for the same display,
+ // the original resolution for the first resolution change has to be used
+ // instead of the specified |old_resolution|.
+ gfx::Size original_resolution;
+ if (change_info_ && change_info_->display_id == display_id) {
+ DCHECK(change_info_->new_resolution == old_resolution);
+ original_resolution = change_info_->old_resolution;
+ }
+
+ change_info_.reset(new ResolutionChangeInfo(
+ display_id, old_resolution, new_resolution, accept_callback));
+ if (!original_resolution.IsEmpty())
+ change_info_->old_resolution = original_resolution;
+
+ // SetDisplayResolution() causes OnConfigurationChanged() and the notification
+ // will be shown at that point.
+ Shell::GetInstance()->display_manager()->SetDisplayResolution(
+ display_id, new_resolution);
+}
+
+bool ResolutionNotificationController::DoesNotificationTimeout() {
+ return change_info_ && change_info_->timeout_count > 0;
+}
+
+void ResolutionNotificationController::CreateOrUpdateNotification() {
+ message_center::MessageCenter* message_center =
+ message_center::MessageCenter::Get();
+ if (!change_info_) {
+ message_center->RemoveNotification(kNotificationId, false /* by_user */);
+ return;
+ }
+
+ base::string16 timeout_message;
+ message_center::RichNotificationData data;
+ if (change_info_->timeout_count > 0) {
+ data.buttons.push_back(message_center::ButtonInfo(
+ l10n_util::GetStringUTF16(IDS_ASH_DISPLAY_RESOLUTION_CHANGE_ACCEPT)));
+ timeout_message = l10n_util::GetStringFUTF16(
+ IDS_ASH_DISPLAY_RESOLUTION_TIMEOUT,
+ ui::TimeFormat::TimeDurationLong(
+ base::TimeDelta::FromSeconds(change_info_->timeout_count)));
+ }
+ data.buttons.push_back(message_center::ButtonInfo(
+ l10n_util::GetStringUTF16(IDS_ASH_DISPLAY_RESOLUTION_CHANGE_REVERT)));
+
+ ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance();
+ scoped_ptr<Notification> notification(new Notification(
+ message_center::NOTIFICATION_TYPE_SIMPLE,
+ kNotificationId,
+ l10n_util::GetStringFUTF16(
+ IDS_ASH_STATUS_TRAY_DISPLAY_RESOLUTION_CHANGED,
+ UTF8ToUTF16(Shell::GetInstance()->display_manager()->
+ GetDisplayNameForId(change_info_->display_id)),
+ UTF8ToUTF16(change_info_->new_resolution.ToString())),
+ timeout_message,
+ bundle.GetImageNamed(IDR_AURA_UBER_TRAY_DISPLAY),
+ base::string16() /* display_source */,
+ std::string() /* extension_id */,
+ data,
+ new ResolutionChangeNotificationDelegate(
+ this, change_info_->timeout_count > 0)));
+ notification->SetSystemPriority();
+ message_center->AddNotification(notification.Pass());
+}
+
+void ResolutionNotificationController::OnTimerTick() {
+ if (!change_info_)
+ return;
+
+ --change_info_->timeout_count;
+ if (change_info_->timeout_count == 0)
+ RevertResolutionChange();
+ else
+ CreateOrUpdateNotification();
+}
+
+void ResolutionNotificationController::AcceptResolutionChange(
+ bool close_notification) {
+ if (close_notification) {
+ message_center::MessageCenter::Get()->RemoveNotification(
+ kNotificationId, false /* by_user */);
+ }
+ base::Closure callback = change_info_->accept_callback;
+ change_info_.reset();
+ callback.Run();
+}
+
+void ResolutionNotificationController::RevertResolutionChange() {
+ message_center::MessageCenter::Get()->RemoveNotification(
+ kNotificationId, false /* by_user */);
+ int64 display_id = change_info_->display_id;
+ gfx::Size old_resolution = change_info_->old_resolution;
+ change_info_.reset();
+ Shell::GetInstance()->display_manager()->SetDisplayResolution(
+ display_id, old_resolution);
+}
+
+void ResolutionNotificationController::OnDisplayBoundsChanged(
+ const gfx::Display& display) {
+}
+
+void ResolutionNotificationController::OnDisplayAdded(
+ const gfx::Display& new_display) {
+}
+
+void ResolutionNotificationController::OnDisplayRemoved(
+ const gfx::Display& old_display) {
+ if (change_info_ && change_info_->display_id == old_display.id())
+ RevertResolutionChange();
+}
+
+void ResolutionNotificationController::OnDisplayConfigurationChanged() {
+ if (!change_info_)
+ return;
+
+ CreateOrUpdateNotification();
+ if (g_use_timer && change_info_->timeout_count > 0) {
+ change_info_->timer.Start(FROM_HERE,
+ base::TimeDelta::FromSeconds(1),
+ this,
+ &ResolutionNotificationController::OnTimerTick);
+ }
+}
+
+void ResolutionNotificationController::SuppressTimerForTest() {
+ g_use_timer = false;
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/display/resolution_notification_controller.h b/chromium/ash/display/resolution_notification_controller.h
new file mode 100644
index 00000000000..14e6b593888
--- /dev/null
+++ b/chromium/ash/display/resolution_notification_controller.h
@@ -0,0 +1,96 @@
+// Copyright 2013 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 ASH_DISPLAY_RESOLUTION_NOTIFICATION_CONTROLLER_H_
+#define ASH_DISPLAY_RESOLUTION_NOTIFICATION_CONTROLLER_H_
+
+#include "ash/ash_export.h"
+#include "ash/display/display_controller.h"
+#include "base/callback.h"
+#include "base/gtest_prod_util.h"
+#include "base/timer/timer.h"
+#include "ui/gfx/display_observer.h"
+#include "ui/gfx/size.h"
+
+namespace chromeos {
+FORWARD_DECLARE_TEST(DisplayPreferencesTest, PreventStore);
+} // namespace chromeos
+
+namespace views {
+class Label;
+class Widget;
+} // namespace views
+
+namespace ash {
+namespace internal {
+// A class which manages the notification of display resolution change and
+// also manages the timeout in case the new resolution is unusable.
+class ASH_EXPORT ResolutionNotificationController
+ : public gfx::DisplayObserver,
+ public DisplayController::Observer {
+ public:
+ ResolutionNotificationController();
+ virtual ~ResolutionNotificationController();
+
+ // Updates the display resolution for |display_id| to |new_resolution| and
+ // creates a notification for this change which offers a button to revert the
+ // change in case something goes wrong. The notification times out if there's
+ // only one display connected and the user is trying to modify its resolution.
+ // In that case, the timeout has to be set since the user cannot make any
+ // changes if something goes wrong.
+ void SetDisplayResolutionAndNotify(
+ int64 display_id,
+ const gfx::Size& old_resolution,
+ const gfx::Size& new_resolution,
+ const base::Closure& accept_callback);
+
+ // Returns true if the notification is visible or scheduled to be visible and
+ // the notification times out.
+ bool DoesNotificationTimeout();
+
+ // Called by the notification delegate when the user accepts the display
+ // resolution change. Set |close_notification| to true when the notification
+ // should be removed.
+ void AcceptResolutionChange(bool close_notification);
+
+ // Called by the notification delegate when the user wants to revert the
+ // display resolution change.
+ void RevertResolutionChange();
+
+ private:
+ friend class ResolutionNotificationControllerTest;
+ FRIEND_TEST_ALL_PREFIXES(ResolutionNotificationControllerTest, Timeout);
+ FRIEND_TEST_ALL_PREFIXES(chromeos::DisplayPreferencesTest, PreventStore);
+
+ // A struct to bundle the data for a single resolution change.
+ struct ResolutionChangeInfo;
+
+ static const int kTimeoutInSec;
+ static const char kNotificationId[];
+
+ // Create a new notification, or update its content if it already exists.
+ void CreateOrUpdateNotification();
+
+ // Called every second for timeout.
+ void OnTimerTick();
+
+ // gfx::DisplayObserver overrides:
+ virtual void OnDisplayBoundsChanged(const gfx::Display& display) OVERRIDE;
+ virtual void OnDisplayAdded(const gfx::Display& new_display) OVERRIDE;
+ virtual void OnDisplayRemoved(const gfx::Display& old_display) OVERRIDE;
+
+ // DisplayController::Observer overrides:
+ virtual void OnDisplayConfigurationChanged() OVERRIDE;
+
+ static void SuppressTimerForTest();
+
+ scoped_ptr<ResolutionChangeInfo> change_info_;
+
+ DISALLOW_COPY_AND_ASSIGN(ResolutionNotificationController);
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_DISPLAY_RESOLUTION_NOTIFICATION_CONTROLLER_H_
diff --git a/chromium/ash/display/resolution_notification_controller_unittest.cc b/chromium/ash/display/resolution_notification_controller_unittest.cc
new file mode 100644
index 00000000000..036cce0d85c
--- /dev/null
+++ b/chromium/ash/display/resolution_notification_controller_unittest.cc
@@ -0,0 +1,314 @@
+// Copyright 2013 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.
+
+#include "ash/display/resolution_notification_controller.h"
+
+#include "ash/display/display_manager.h"
+#include "ash/screen_ash.h"
+#include "ash/shell.h"
+#include "ash/test/ash_test_base.h"
+#include "base/bind.h"
+#include "ui/gfx/size.h"
+#include "ui/message_center/message_center.h"
+
+namespace ash {
+namespace internal {
+
+class ResolutionNotificationControllerTest : public ash::test::AshTestBase {
+ public:
+ ResolutionNotificationControllerTest()
+ : accept_count_(0) {
+ }
+
+ virtual ~ResolutionNotificationControllerTest() {}
+
+ protected:
+ virtual void SetUp() OVERRIDE {
+ ash::test::AshTestBase::SetUp();
+ ResolutionNotificationController::SuppressTimerForTest();
+ }
+
+ void SetDisplayResolutionAndNotify(const gfx::Display& display,
+ const gfx::Size& new_resolution) {
+ DisplayManager* display_manager = Shell::GetInstance()->display_manager();
+ const DisplayInfo& info = display_manager->GetDisplayInfo(display.id());
+ Shell::GetInstance()->resolution_notification_controller()->
+ SetDisplayResolutionAndNotify(
+ display.id(),
+ info.size_in_pixel(),
+ new_resolution,
+ base::Bind(&ResolutionNotificationControllerTest::OnAccepted,
+ base::Unretained(this)));
+
+ // OnConfigurationChanged event won't be emitted in the test environment,
+ // so invoke UpdateDisplay() to emit that event explicitly.
+ std::vector<DisplayInfo> info_list;
+ for (size_t i = 0; i < display_manager->GetNumDisplays(); ++i) {
+ int64 id = display_manager->GetDisplayAt(i).id();
+ DisplayInfo info = display_manager->GetDisplayInfo(id);
+ if (display.id() == id) {
+ gfx::Rect bounds = info.bounds_in_pixel();
+ bounds.set_size(new_resolution);
+ info.SetBounds(bounds);
+ }
+ info_list.push_back(info);
+ }
+ display_manager->OnNativeDisplaysChanged(info_list);
+ RunAllPendingInMessageLoop();
+ }
+
+ void ClickOnNotification() {
+ message_center::MessageCenter::Get()->ClickOnNotification(
+ ResolutionNotificationController::kNotificationId);
+ }
+
+ void ClickOnNotificationButton(int index) {
+ message_center::MessageCenter::Get()->ClickOnNotificationButton(
+ ResolutionNotificationController::kNotificationId, index);
+ }
+
+ void CloseNotification() {
+ message_center::MessageCenter::Get()->RemoveNotification(
+ ResolutionNotificationController::kNotificationId, true /* by_user */);
+ }
+
+ bool IsNotificationVisible() {
+ return message_center::MessageCenter::Get()->HasNotification(
+ ResolutionNotificationController::kNotificationId);
+ }
+
+ void TickTimer() {
+ controller()->OnTimerTick();
+ }
+
+ ResolutionNotificationController* controller() {
+ return Shell::GetInstance()->resolution_notification_controller();
+ }
+
+ int accept_count() const {
+ return accept_count_;
+ }
+
+ private:
+ void OnAccepted() {
+ EXPECT_FALSE(controller()->DoesNotificationTimeout());
+ accept_count_++;
+ }
+
+ int accept_count_;
+
+ DISALLOW_COPY_AND_ASSIGN(ResolutionNotificationControllerTest);
+};
+
+// Basic behaviors and verifies it doesn't cause crashes.
+TEST_F(ResolutionNotificationControllerTest, Basic) {
+ if (!SupportsMultipleDisplays())
+ return;
+
+ UpdateDisplay("300x300#300x300|200x200,250x250#250x250|200x200");
+ int64 id2 = ash::ScreenAsh::GetSecondaryDisplay().id();
+ ash::internal::DisplayManager* display_manager =
+ ash::Shell::GetInstance()->display_manager();
+ ASSERT_EQ(0, accept_count());
+ EXPECT_FALSE(IsNotificationVisible());
+
+ // Changes the resolution and apply the result.
+ SetDisplayResolutionAndNotify(
+ ScreenAsh::GetSecondaryDisplay(), gfx::Size(200, 200));
+ EXPECT_TRUE(IsNotificationVisible());
+ EXPECT_FALSE(controller()->DoesNotificationTimeout());
+ gfx::Size resolution;
+ EXPECT_TRUE(
+ display_manager->GetSelectedResolutionForDisplayId(id2, &resolution));
+ EXPECT_EQ("200x200", resolution.ToString());
+
+ // Click the revert button, which reverts to the best resolution.
+ ClickOnNotificationButton(0);
+ RunAllPendingInMessageLoop();
+ EXPECT_FALSE(IsNotificationVisible());
+ EXPECT_EQ(0, accept_count());
+ EXPECT_FALSE(
+ display_manager->GetSelectedResolutionForDisplayId(id2, &resolution));
+}
+
+TEST_F(ResolutionNotificationControllerTest, ClickMeansAccept) {
+ if (!SupportsMultipleDisplays())
+ return;
+
+ UpdateDisplay("300x300#300x300|200x200,250x250#250x250|200x200");
+ int64 id2 = ash::ScreenAsh::GetSecondaryDisplay().id();
+ ash::internal::DisplayManager* display_manager =
+ ash::Shell::GetInstance()->display_manager();
+ ASSERT_EQ(0, accept_count());
+ EXPECT_FALSE(IsNotificationVisible());
+
+ // Changes the resolution and apply the result.
+ SetDisplayResolutionAndNotify(
+ ScreenAsh::GetSecondaryDisplay(), gfx::Size(200, 200));
+ EXPECT_TRUE(IsNotificationVisible());
+ EXPECT_FALSE(controller()->DoesNotificationTimeout());
+ gfx::Size resolution;
+ EXPECT_TRUE(
+ display_manager->GetSelectedResolutionForDisplayId(id2, &resolution));
+ EXPECT_EQ("200x200", resolution.ToString());
+
+ // Click the revert button, which reverts the resolution.
+ ClickOnNotification();
+ RunAllPendingInMessageLoop();
+ EXPECT_FALSE(IsNotificationVisible());
+ EXPECT_EQ(1, accept_count());
+ EXPECT_TRUE(
+ display_manager->GetSelectedResolutionForDisplayId(id2, &resolution));
+ EXPECT_EQ("200x200", resolution.ToString());
+}
+
+TEST_F(ResolutionNotificationControllerTest, AcceptButton) {
+ if (!SupportsMultipleDisplays())
+ return;
+
+ ash::internal::DisplayManager* display_manager =
+ ash::Shell::GetInstance()->display_manager();
+
+ UpdateDisplay("300x300#300x300|200x200");
+ const gfx::Display& display = ash::Shell::GetScreen()->GetPrimaryDisplay();
+ SetDisplayResolutionAndNotify(display, gfx::Size(200, 200));
+ EXPECT_TRUE(IsNotificationVisible());
+
+ // If there's a single display only, it will have timeout and the first button
+ // becomes accept.
+ EXPECT_TRUE(controller()->DoesNotificationTimeout());
+ ClickOnNotificationButton(0);
+ EXPECT_FALSE(IsNotificationVisible());
+ EXPECT_EQ(1, accept_count());
+ gfx::Size resolution;
+ EXPECT_TRUE(display_manager->GetSelectedResolutionForDisplayId(
+ display.id(), &resolution));
+ EXPECT_EQ("200x200", resolution.ToString());
+
+ // In that case the second button is revert.
+ UpdateDisplay("300x300#300x300|200x200");
+ SetDisplayResolutionAndNotify(display, gfx::Size(200, 200));
+ EXPECT_TRUE(IsNotificationVisible());
+
+ EXPECT_TRUE(controller()->DoesNotificationTimeout());
+ ClickOnNotificationButton(1);
+ EXPECT_FALSE(IsNotificationVisible());
+ EXPECT_EQ(1, accept_count());
+ EXPECT_FALSE(display_manager->GetSelectedResolutionForDisplayId(
+ display.id(), &resolution));
+}
+
+TEST_F(ResolutionNotificationControllerTest, Close) {
+ if (!SupportsMultipleDisplays())
+ return;
+
+ UpdateDisplay("100x100,150x150#150x150|200x200");
+ int64 id2 = ash::ScreenAsh::GetSecondaryDisplay().id();
+ ash::internal::DisplayManager* display_manager =
+ ash::Shell::GetInstance()->display_manager();
+ ASSERT_EQ(0, accept_count());
+ EXPECT_FALSE(IsNotificationVisible());
+
+ // Changes the resolution and apply the result.
+ SetDisplayResolutionAndNotify(
+ ScreenAsh::GetSecondaryDisplay(), gfx::Size(200, 200));
+ EXPECT_TRUE(IsNotificationVisible());
+ EXPECT_FALSE(controller()->DoesNotificationTimeout());
+ gfx::Size resolution;
+ EXPECT_TRUE(
+ display_manager->GetSelectedResolutionForDisplayId(id2, &resolution));
+ EXPECT_EQ("200x200", resolution.ToString());
+
+ // Close the notification (imitates clicking [x] button). Also verifies if
+ // this does not cause a crash. See crbug.com/271784
+ CloseNotification();
+ RunAllPendingInMessageLoop();
+ EXPECT_FALSE(IsNotificationVisible());
+ EXPECT_EQ(1, accept_count());
+}
+
+TEST_F(ResolutionNotificationControllerTest, Timeout) {
+ if (!SupportsMultipleDisplays())
+ return;
+
+ UpdateDisplay("300x300#300x300|200x200");
+ const gfx::Display& display = ash::Shell::GetScreen()->GetPrimaryDisplay();
+ SetDisplayResolutionAndNotify(display, gfx::Size(200, 200));
+
+ for (int i = 0; i < ResolutionNotificationController::kTimeoutInSec; ++i) {
+ EXPECT_TRUE(IsNotificationVisible()) << "notification is closed after "
+ << i << "-th timer tick";
+ TickTimer();
+ RunAllPendingInMessageLoop();
+ }
+ EXPECT_FALSE(IsNotificationVisible());
+ EXPECT_EQ(0, accept_count());
+ gfx::Size resolution;
+ ash::internal::DisplayManager* display_manager =
+ ash::Shell::GetInstance()->display_manager();
+ EXPECT_FALSE(display_manager->GetSelectedResolutionForDisplayId(
+ display.id(), &resolution));
+}
+
+TEST_F(ResolutionNotificationControllerTest, DisplayDisconnected) {
+ if (!SupportsMultipleDisplays())
+ return;
+
+ UpdateDisplay("300x300#300x300|200x200,200x200#250x250|200x200|100x100");
+ int64 id2 = ash::ScreenAsh::GetSecondaryDisplay().id();
+ ash::internal::DisplayManager* display_manager =
+ ash::Shell::GetInstance()->display_manager();
+ SetDisplayResolutionAndNotify(
+ ScreenAsh::GetSecondaryDisplay(), gfx::Size(100, 100));
+ ASSERT_TRUE(IsNotificationVisible());
+
+ // Disconnects the secondary display and verifies it doesn't cause crashes.
+ UpdateDisplay("300x300#300x300|200x200");
+ RunAllPendingInMessageLoop();
+ EXPECT_FALSE(IsNotificationVisible());
+ EXPECT_EQ(0, accept_count());
+ gfx::Size resolution;
+ EXPECT_TRUE(
+ display_manager->GetSelectedResolutionForDisplayId(id2, &resolution));
+ EXPECT_EQ("200x200", resolution.ToString());
+}
+
+TEST_F(ResolutionNotificationControllerTest, MultipleResolutionChange) {
+ if (!SupportsMultipleDisplays())
+ return;
+
+ UpdateDisplay("300x300#300x300|200x200,250x250#250x250|200x200");
+ int64 id2 = ash::ScreenAsh::GetSecondaryDisplay().id();
+ ash::internal::DisplayManager* display_manager =
+ ash::Shell::GetInstance()->display_manager();
+
+ SetDisplayResolutionAndNotify(
+ ScreenAsh::GetSecondaryDisplay(), gfx::Size(200, 200));
+ EXPECT_TRUE(IsNotificationVisible());
+ EXPECT_FALSE(controller()->DoesNotificationTimeout());
+ gfx::Size resolution;
+ EXPECT_TRUE(
+ display_manager->GetSelectedResolutionForDisplayId(id2, &resolution));
+ EXPECT_EQ("200x200", resolution.ToString());
+
+ // Invokes SetDisplayResolutionAndNotify during the previous notification is
+ // visible.
+ SetDisplayResolutionAndNotify(
+ ScreenAsh::GetSecondaryDisplay(), gfx::Size(250, 250));
+ EXPECT_FALSE(
+ display_manager->GetSelectedResolutionForDisplayId(id2, &resolution));
+
+ // Then, click the revert button. Although |old_resolution| for the second
+ // SetDisplayResolutionAndNotify is 200x200, it should revert to the original
+ // size 150x150.
+ ClickOnNotificationButton(0);
+ RunAllPendingInMessageLoop();
+ EXPECT_FALSE(IsNotificationVisible());
+ EXPECT_EQ(0, accept_count());
+ EXPECT_FALSE(
+ display_manager->GetSelectedResolutionForDisplayId(id2, &resolution));
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/display/root_window_transformers.cc b/chromium/ash/display/root_window_transformers.cc
new file mode 100644
index 00000000000..a698fa4797a
--- /dev/null
+++ b/chromium/ash/display/root_window_transformers.cc
@@ -0,0 +1,293 @@
+// Copyright (c) 2013 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.
+
+#include "ash/display/root_window_transformers.h"
+
+#include <cmath>
+
+#include "ash/display/display_info.h"
+#include "ash/display/display_manager.h"
+#include "ash/magnifier/magnification_controller.h"
+#include "ash/shell.h"
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "third_party/skia/include/utils/SkMatrix44.h"
+#include "ui/aura/root_window.h"
+#include "ui/aura/root_window_transformer.h"
+#include "ui/aura/window_property.h"
+#include "ui/compositor/dip_util.h"
+#include "ui/gfx/display.h"
+#include "ui/gfx/insets.h"
+#include "ui/gfx/size_conversions.h"
+#include "ui/gfx/transform.h"
+#include "ui/gfx/transform.h"
+
+DECLARE_WINDOW_PROPERTY_TYPE(gfx::Display::Rotation);
+
+namespace ash {
+namespace internal {
+namespace {
+
+DEFINE_WINDOW_PROPERTY_KEY(gfx::Display::Rotation, kRotationPropertyKey,
+ gfx::Display::ROTATE_0);
+
+// Round near zero value to zero.
+void RoundNearZero(gfx::Transform* transform) {
+ const float kEpsilon = 0.001f;
+ SkMatrix44& matrix = transform->matrix();
+ for (int x = 0; x < 4; ++x) {
+ for (int y = 0; y < 4; ++y) {
+ if (std::abs(SkMScalarToFloat(matrix.get(x, y))) < kEpsilon)
+ matrix.set(x, y, SkFloatToMScalar(0.0f));
+ }
+ }
+}
+
+// TODO(oshima): Transformers should be able to adjust itself
+// when the device scale factor is changed, instead of
+// precalculating the transform using fixed value.
+
+gfx::Transform CreateRotationTransform(aura::RootWindow* root_window,
+ const gfx::Display& display) {
+ DisplayInfo info =
+ Shell::GetInstance()->display_manager()->GetDisplayInfo(display.id());
+
+ // TODO(oshima): Add animation. (crossfade+rotation, or just cross-fade)
+#if defined(OS_WIN)
+ // Windows 8 bots refused to resize the host window, and
+ // updating the transform results in incorrectly resizing
+ // the root window. Don't apply the transform unless
+ // necessary so that unit tests pass on win8 bots.
+ if (info.rotation() == root_window->GetProperty(kRotationPropertyKey))
+ return gfx::Transform();
+ root_window->SetProperty(kRotationPropertyKey, info.rotation());
+#endif
+
+ gfx::Transform rotate;
+ // The origin is (0, 0), so the translate width/height must be reduced by
+ // 1 pixel.
+ float one_pixel = 1.0f / display.device_scale_factor();
+ switch (info.rotation()) {
+ case gfx::Display::ROTATE_0:
+ break;
+ case gfx::Display::ROTATE_90:
+ rotate.Translate(display.bounds().height() - one_pixel, 0);
+ rotate.Rotate(90);
+ break;
+ case gfx::Display::ROTATE_270:
+ rotate.Translate(0, display.bounds().width() - one_pixel);
+ rotate.Rotate(270);
+ break;
+ case gfx::Display::ROTATE_180:
+ rotate.Translate(display.bounds().width() - one_pixel,
+ display.bounds().height() - one_pixel);
+ rotate.Rotate(180);
+ break;
+ }
+
+ RoundNearZero(&rotate);
+ return rotate;
+}
+
+gfx::Transform CreateMagnifierTransform(aura::RootWindow* root_window) {
+ MagnificationController* magnifier =
+ Shell::GetInstance()->magnification_controller();
+ float magnifier_scale = 1.f;
+ gfx::Point magnifier_offset;
+ if (magnifier && magnifier->IsEnabled()) {
+ magnifier_scale = magnifier->GetScale();
+ magnifier_offset = magnifier->GetWindowPosition();
+ }
+ gfx::Transform transform;
+ if (magnifier_scale != 1.f) {
+ transform.Scale(magnifier_scale, magnifier_scale);
+ transform.Translate(-magnifier_offset.x(), -magnifier_offset.y());
+ }
+ return transform;
+}
+
+gfx::Transform CreateInsetsAndScaleTransform(const gfx::Insets& insets,
+ float device_scale_factor,
+ float ui_scale) {
+ gfx::Transform transform;
+ if (insets.top() != 0 || insets.left() != 0) {
+ float x_offset = insets.left() / device_scale_factor;
+ float y_offset = insets.top() / device_scale_factor;
+ transform.Translate(x_offset, y_offset);
+ }
+ float inverted_scale = 1.0f / ui_scale;
+ transform.Scale(inverted_scale, inverted_scale);
+ return transform;
+}
+
+gfx::Transform CreateOverscanAndUIScaleTransform(aura::RootWindow* root_window,
+ const gfx::Display& display) {
+ DisplayInfo info =
+ Shell::GetInstance()->display_manager()->GetDisplayInfo(display.id());
+ return CreateInsetsAndScaleTransform(
+ info.GetOverscanInsetsInPixel(),
+ ui::GetDeviceScaleFactor(root_window->layer()),
+ info.ui_scale());
+}
+
+// RootWindowTransformer for ash environment.
+class AshRootWindowTransformer : public aura::RootWindowTransformer {
+ public:
+ AshRootWindowTransformer(aura::RootWindow* root,
+ const gfx::Display& display)
+ : root_window_(root) {
+ root_window_bounds_transform_ =
+ CreateOverscanAndUIScaleTransform(root, display) *
+ CreateRotationTransform(root, display);
+ transform_ = root_window_bounds_transform_ * CreateMagnifierTransform(root);
+ CHECK(transform_.GetInverse(&invert_transform_));
+
+ DisplayInfo info = Shell::GetInstance()->display_manager()->
+ GetDisplayInfo(display.id());
+ root_window_ui_scale_ = info.ui_scale();
+ host_insets_ = info.GetOverscanInsetsInPixel();
+ }
+
+ // aura::RootWindowTransformer overrides:
+ virtual gfx::Transform GetTransform() const OVERRIDE {
+ return transform_;
+ }
+ virtual gfx::Transform GetInverseTransform() const OVERRIDE {
+ return invert_transform_;
+ }
+ virtual gfx::Rect GetRootWindowBounds(
+ const gfx::Size& host_size) const OVERRIDE {
+ gfx::Rect bounds(host_size);
+ bounds.Inset(host_insets_);
+ bounds = ui::ConvertRectToDIP(root_window_->layer(), bounds);
+ gfx::RectF new_bounds(bounds);
+ root_window_bounds_transform_.TransformRect(&new_bounds);
+ // Apply |root_window_scale_| twice as the downscaling
+ // is already applied once in |SetTransformInternal()|.
+ // TODO(oshima): This is a bit ugly. Consider specifying
+ // the pseudo host resolution instead.
+ new_bounds.Scale(root_window_ui_scale_ * root_window_ui_scale_);
+ // Ignore the origin because RootWindow's insets are handled by
+ // the transform.
+ // Floor the size because the bounds is no longer aligned to
+ // backing pixel when |root_window_scale_| is specified
+ // (850 height at 1.25 scale becomes 1062.5 for example.)
+ return gfx::Rect(gfx::ToFlooredSize(new_bounds.size()));
+ }
+
+ virtual gfx::Insets GetHostInsets() const OVERRIDE {
+ return host_insets_;
+ }
+
+ private:
+ virtual ~AshRootWindowTransformer() {}
+
+ aura::RootWindow* root_window_;
+ gfx::Transform transform_;
+
+ // The accurate representation of the inverse of the |transform_|.
+ // This is used to avoid computation error caused by
+ // |gfx::Transform::GetInverse|.
+ gfx::Transform invert_transform_;
+
+ // The transform of the root window bounds. This is used to calculate
+ // the size of root window.
+ gfx::Transform root_window_bounds_transform_;
+
+ // The scale of the root window. This is used to expand the
+ // area of the root window (useful in HighDPI display).
+ // Note that this should not be confused with the device scale
+ // factor, which specfies the pixel density of the display.
+ float root_window_ui_scale_;
+
+ gfx::Insets host_insets_;
+
+ DISALLOW_COPY_AND_ASSIGN(AshRootWindowTransformer);
+};
+
+// RootWindowTransformer for mirror root window. We simply copy the
+// texture (bitmap) of the source display into the mirror window, so
+// the root window bounds is the same as the source display's
+// pixel size (excluding overscan insets).
+class MirrorRootWindowTransformer : public aura::RootWindowTransformer {
+ public:
+ MirrorRootWindowTransformer(const DisplayInfo& source_display_info,
+ const DisplayInfo& mirror_display_info) {
+ root_bounds_ = gfx::Rect(source_display_info.bounds_in_pixel().size());
+ gfx::Rect mirror_display_rect =
+ gfx::Rect(mirror_display_info.bounds_in_pixel().size());
+
+ bool letterbox = root_bounds_.width() * mirror_display_rect.height() >
+ root_bounds_.height() * mirror_display_rect.width();
+ if (letterbox) {
+ float mirror_scale_ratio =
+ (static_cast<float>(root_bounds_.width()) /
+ static_cast<float>(mirror_display_rect.width()));
+ float inverted_scale = 1.0f / mirror_scale_ratio;
+ int margin = static_cast<int>(
+ (mirror_display_rect.height() -
+ root_bounds_.height() * inverted_scale) / 2);
+ insets_.Set(0, margin, 0, margin);
+
+ transform_.Translate(0, margin);
+ transform_.Scale(inverted_scale, inverted_scale);
+ } else {
+ float mirror_scale_ratio =
+ (static_cast<float>(root_bounds_.height()) /
+ static_cast<float>(mirror_display_rect.height()));
+ float inverted_scale = 1.0f / mirror_scale_ratio;
+ int margin = static_cast<int>(
+ (mirror_display_rect.width() -
+ root_bounds_.width() * inverted_scale) / 2);
+ insets_.Set(margin, 0, margin, 0);
+
+ transform_.Translate(margin, 0);
+ transform_.Scale(inverted_scale, inverted_scale);
+ }
+ }
+
+ // aura::RootWindowTransformer overrides:
+ virtual gfx::Transform GetTransform() const OVERRIDE {
+ return transform_;
+ }
+ virtual gfx::Transform GetInverseTransform() const OVERRIDE {
+ gfx::Transform invert;
+ CHECK(transform_.GetInverse(&invert));
+ return invert;
+ }
+ virtual gfx::Rect GetRootWindowBounds(
+ const gfx::Size& host_size) const OVERRIDE {
+ return root_bounds_;
+ }
+ virtual gfx::Insets GetHostInsets() const OVERRIDE {
+ return insets_;
+ }
+
+ private:
+ virtual ~MirrorRootWindowTransformer() {}
+
+ gfx::Transform transform_;
+ gfx::Rect root_bounds_;
+ gfx::Insets insets_;
+
+ DISALLOW_COPY_AND_ASSIGN(MirrorRootWindowTransformer);
+};
+
+} // namespace
+
+aura::RootWindowTransformer* CreateRootWindowTransformerForDisplay(
+ aura::RootWindow* root,
+ const gfx::Display& display) {
+ return new AshRootWindowTransformer(root, display);
+}
+
+aura::RootWindowTransformer* CreateRootWindowTransformerForMirroredDisplay(
+ const DisplayInfo& source_display_info,
+ const DisplayInfo& mirror_display_info) {
+ return new MirrorRootWindowTransformer(source_display_info,
+ mirror_display_info);
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/display/root_window_transformers.h b/chromium/ash/display/root_window_transformers.h
new file mode 100644
index 00000000000..8af9ee8526e
--- /dev/null
+++ b/chromium/ash/display/root_window_transformers.h
@@ -0,0 +1,40 @@
+// Copyright (c) 2013 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 ASH_DISPLAY_ROOT_WINDOW_TRANSFORMERS_H_
+#define ASH_DISPLAY_ROOT_WINDOW_TRANSFORMERS_H_
+
+#include "ash/ash_export.h"
+
+namespace aura {
+class RootWindow;
+class RootWindowTransformer;
+}
+
+namespace gfx {
+class Display;
+class Transform;
+}
+
+namespace ash {
+namespace internal {
+class DisplayInfo;
+
+ASH_EXPORT aura::RootWindowTransformer* CreateRootWindowTransformerForDisplay(
+ aura::RootWindow* root,
+ const gfx::Display& display);
+
+// Creates a RootWindowTransformers for mirror root window.
+// |source_display_info| specifies the display being mirrored,
+// and |mirror_display_info| specifies the display used to
+// mirror the content.
+ASH_EXPORT aura::RootWindowTransformer*
+CreateRootWindowTransformerForMirroredDisplay(
+ const DisplayInfo& source_display_info,
+ const DisplayInfo& mirror_display_info);
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_DISPLAY_ROOT_WINDOW_TRANSFORMERS_H_
diff --git a/chromium/ash/display/root_window_transformers_unittest.cc b/chromium/ash/display/root_window_transformers_unittest.cc
new file mode 100644
index 00000000000..39d500ceef3
--- /dev/null
+++ b/chromium/ash/display/root_window_transformers_unittest.cc
@@ -0,0 +1,416 @@
+// Copyright 2013 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.
+
+#include "ash/display/root_window_transformers.h"
+
+#include "ash/display/display_controller.h"
+#include "ash/display/display_info.h"
+#include "ash/display/display_manager.h"
+#include "ash/launcher/launcher.h"
+#include "ash/magnifier/magnification_controller.h"
+#include "ash/screen_ash.h"
+#include "ash/shelf/shelf_widget.h"
+#include "ash/shell.h"
+#include "ash/test/ash_test_base.h"
+#include "ash/test/cursor_manager_test_api.h"
+#include "ash/test/mirror_window_test_api.h"
+#include "base/synchronization/waitable_event.h"
+#include "ui/aura/env.h"
+#include "ui/aura/root_window.h"
+#include "ui/aura/root_window_transformer.h"
+#include "ui/aura/test/event_generator.h"
+#include "ui/aura/window_tracker.h"
+#include "ui/base/events/event_handler.h"
+#include "ui/gfx/display.h"
+#include "ui/gfx/rect_conversions.h"
+#include "ui/gfx/screen.h"
+#include "ui/views/widget/widget.h"
+
+namespace ash {
+namespace internal {
+
+namespace {
+
+const char kDesktopBackgroundView[] = "DesktopBackgroundView";
+
+class TestEventHandler : public ui::EventHandler {
+ public:
+ TestEventHandler() : target_root_(NULL),
+ touch_radius_x_(0.0),
+ touch_radius_y_(0.0),
+ scroll_x_offset_(0.0),
+ scroll_y_offset_(0.0),
+ scroll_x_offset_ordinal_(0.0),
+ scroll_y_offset_ordinal_(0.0) {}
+ virtual ~TestEventHandler() {}
+
+ virtual void OnMouseEvent(ui::MouseEvent* event) OVERRIDE {
+ if (event->flags() & ui::EF_IS_SYNTHESIZED)
+ return;
+ aura::Window* target = static_cast<aura::Window*>(event->target());
+ mouse_location_ = event->root_location();
+ target_root_ = target->GetRootWindow();
+ event->StopPropagation();
+ }
+
+ virtual void OnTouchEvent(ui::TouchEvent* event) OVERRIDE {
+ aura::Window* target = static_cast<aura::Window*>(event->target());
+ // Only record when the target is the background which covers
+ // entire root window.
+ if (target->name() != kDesktopBackgroundView)
+ return;
+ touch_radius_x_ = event->radius_x();
+ touch_radius_y_ = event->radius_y();
+ event->StopPropagation();
+ }
+
+ virtual void OnScrollEvent(ui::ScrollEvent* event) OVERRIDE {
+ aura::Window* target = static_cast<aura::Window*>(event->target());
+ // Only record when the target is the background which covers
+ // entire root window.
+ if (target->name() != kDesktopBackgroundView)
+ return;
+
+ if (event->type() == ui::ET_SCROLL) {
+ scroll_x_offset_ = event->x_offset();
+ scroll_y_offset_ = event->y_offset();
+ scroll_x_offset_ordinal_ = event->x_offset_ordinal();
+ scroll_y_offset_ordinal_ = event->y_offset_ordinal();
+ }
+ event->StopPropagation();
+ }
+
+ std::string GetLocationAndReset() {
+ std::string result = mouse_location_.ToString();
+ mouse_location_.SetPoint(0, 0);
+ target_root_ = NULL;
+ return result;
+ }
+
+ float touch_radius_x() const { return touch_radius_x_; }
+ float touch_radius_y() const { return touch_radius_y_; }
+ float scroll_x_offset() const { return scroll_x_offset_; }
+ float scroll_y_offset() const { return scroll_y_offset_; }
+ float scroll_x_offset_ordinal() const { return scroll_x_offset_ordinal_; }
+ float scroll_y_offset_ordinal() const { return scroll_y_offset_ordinal_; }
+
+ private:
+ gfx::Point mouse_location_;
+ aura::RootWindow* target_root_;
+
+ float touch_radius_x_;
+ float touch_radius_y_;
+ float scroll_x_offset_;
+ float scroll_y_offset_;
+ float scroll_x_offset_ordinal_;
+ float scroll_y_offset_ordinal_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestEventHandler);
+};
+
+gfx::Display::Rotation GetStoredRotation(int64 id) {
+ return Shell::GetInstance()->display_manager()->GetDisplayInfo(id).rotation();
+}
+
+float GetStoredUIScale(int64 id) {
+ return Shell::GetInstance()->display_manager()->GetDisplayInfo(id).ui_scale();
+}
+
+} // namespace
+
+typedef test::AshTestBase RootWindowTransformersTest;
+
+#if defined(OS_WIN)
+// TODO(scottmg): RootWindow doesn't get resized on Windows
+// Ash. http://crbug.com/247916.
+#define MAYBE_RotateAndMagnify DISABLED_RotateAndMagniy
+#define MAYBE_TouchScaleAndMagnify DISABLED_TouchScaleAndMagnify
+#define MAYBE_ConvertHostToRootCoords DISABLED_ConvertHostToRootCoords
+#else
+#define MAYBE_RotateAndMagnify RotateAndMagniy
+#define MAYBE_TouchScaleAndMagnify TouchScaleAndMagnify
+#define MAYBE_ConvertHostToRootCoords ConvertHostToRootCoords
+#endif
+
+TEST_F(RootWindowTransformersTest, MAYBE_RotateAndMagnify) {
+ DisplayController* display_controller =
+ Shell::GetInstance()->display_controller();
+ MagnificationController* magnifier =
+ Shell::GetInstance()->magnification_controller();
+ DisplayManager* display_manager = Shell::GetInstance()->display_manager();
+
+ TestEventHandler event_handler;
+ Shell::GetInstance()->AddPreTargetHandler(&event_handler);
+
+ UpdateDisplay("120x200,300x400*2");
+ gfx::Display display1 = Shell::GetScreen()->GetPrimaryDisplay();
+ int64 display2_id = ScreenAsh::GetSecondaryDisplay().id();
+
+ Shell::RootWindowList root_windows = Shell::GetAllRootWindows();
+ aura::test::EventGenerator generator1(root_windows[0]);
+ aura::test::EventGenerator generator2(root_windows[1]);
+
+ magnifier->SetEnabled(true);
+ EXPECT_EQ(2.0f, magnifier->GetScale());
+ EXPECT_EQ("120x200", root_windows[0]->bounds().size().ToString());
+ EXPECT_EQ("150x200", root_windows[1]->bounds().size().ToString());
+ EXPECT_EQ("120,0 150x200",
+ ScreenAsh::GetSecondaryDisplay().bounds().ToString());
+ generator1.MoveMouseToInHost(40, 80);
+ EXPECT_EQ("50,90", event_handler.GetLocationAndReset());
+ EXPECT_EQ("50,90",
+ aura::Env::GetInstance()->last_mouse_location().ToString());
+ EXPECT_EQ(gfx::Display::ROTATE_0, GetStoredRotation(display1.id()));
+ EXPECT_EQ(gfx::Display::ROTATE_0, GetStoredRotation(display2_id));
+ magnifier->SetEnabled(false);
+
+ display_manager->SetDisplayRotation(display1.id(),
+ gfx::Display::ROTATE_90);
+ // Move the cursor to the center of the first root window.
+ generator1.MoveMouseToInHost(59, 100);
+
+ magnifier->SetEnabled(true);
+ EXPECT_EQ(2.0f, magnifier->GetScale());
+ EXPECT_EQ("200x120", root_windows[0]->bounds().size().ToString());
+ EXPECT_EQ("150x200", root_windows[1]->bounds().size().ToString());
+ EXPECT_EQ("200,0 150x200",
+ ScreenAsh::GetSecondaryDisplay().bounds().ToString());
+ generator1.MoveMouseToInHost(39, 120);
+ EXPECT_EQ("110,70", event_handler.GetLocationAndReset());
+ EXPECT_EQ("110,70",
+ aura::Env::GetInstance()->last_mouse_location().ToString());
+ EXPECT_EQ(gfx::Display::ROTATE_90, GetStoredRotation(display1.id()));
+ EXPECT_EQ(gfx::Display::ROTATE_0, GetStoredRotation(display2_id));
+ magnifier->SetEnabled(false);
+
+ DisplayLayout display_layout(DisplayLayout::BOTTOM, 50);
+ display_controller->SetLayoutForCurrentDisplays(display_layout);
+ EXPECT_EQ("50,120 150x200",
+ ScreenAsh::GetSecondaryDisplay().bounds().ToString());
+
+ display_manager->SetDisplayRotation(display2_id,
+ gfx::Display::ROTATE_270);
+ // Move the cursor to the center of the second root window.
+ generator2.MoveMouseToInHost(151, 199);
+
+ magnifier->SetEnabled(true);
+ EXPECT_EQ("200x120", root_windows[0]->bounds().size().ToString());
+ EXPECT_EQ("200x150", root_windows[1]->bounds().size().ToString());
+ EXPECT_EQ("50,120 200x150",
+ ScreenAsh::GetSecondaryDisplay().bounds().ToString());
+ generator2.MoveMouseToInHost(172, 219);
+ EXPECT_EQ("95,80", event_handler.GetLocationAndReset());
+ EXPECT_EQ("145,200",
+ aura::Env::GetInstance()->last_mouse_location().ToString());
+ EXPECT_EQ(gfx::Display::ROTATE_90, GetStoredRotation(display1.id()));
+ EXPECT_EQ(gfx::Display::ROTATE_270, GetStoredRotation(display2_id));
+ magnifier->SetEnabled(false);
+
+ display_manager->SetDisplayRotation(display1.id(),
+ gfx::Display::ROTATE_180);
+ // Move the cursor to the center of the first root window.
+ generator1.MoveMouseToInHost(59, 99);
+
+ magnifier->SetEnabled(true);
+ EXPECT_EQ("120x200", root_windows[0]->bounds().size().ToString());
+ EXPECT_EQ("200x150", root_windows[1]->bounds().size().ToString());
+ // Dislay must share at least 100, so the x's offset becomes 20.
+ EXPECT_EQ("20,200 200x150",
+ ScreenAsh::GetSecondaryDisplay().bounds().ToString());
+ generator1.MoveMouseToInHost(39, 59);
+ EXPECT_EQ("70,120", event_handler.GetLocationAndReset());
+ EXPECT_EQ(gfx::Display::ROTATE_180, GetStoredRotation(display1.id()));
+ EXPECT_EQ(gfx::Display::ROTATE_270, GetStoredRotation(display2_id));
+ magnifier->SetEnabled(false);
+
+ Shell::GetInstance()->RemovePreTargetHandler(&event_handler);
+}
+
+TEST_F(RootWindowTransformersTest, ScaleAndMagnify) {
+ if (!SupportsMultipleDisplays())
+ return;
+
+ TestEventHandler event_handler;
+ Shell::GetInstance()->AddPreTargetHandler(&event_handler);
+
+ UpdateDisplay("600x400*2@1.5,500x300");
+
+ gfx::Display display1 = Shell::GetScreen()->GetPrimaryDisplay();
+ gfx::Display::SetInternalDisplayId(display1.id());
+ gfx::Display display2 = ScreenAsh::GetSecondaryDisplay();
+ Shell::RootWindowList root_windows = Shell::GetAllRootWindows();
+ MagnificationController* magnifier =
+ Shell::GetInstance()->magnification_controller();
+
+ magnifier->SetEnabled(true);
+ EXPECT_EQ(2.0f, magnifier->GetScale());
+ EXPECT_EQ("0,0 450x300", display1.bounds().ToString());
+ EXPECT_EQ("0,0 450x300", root_windows[0]->bounds().ToString());
+ EXPECT_EQ("450,0 500x300", display2.bounds().ToString());
+ EXPECT_EQ(1.5f, GetStoredUIScale(display1.id()));
+ EXPECT_EQ(1.0f, GetStoredUIScale(display2.id()));
+
+ aura::test::EventGenerator generator(root_windows[0]);
+ generator.MoveMouseToInHost(500, 200);
+ EXPECT_EQ("299,150", event_handler.GetLocationAndReset());
+ magnifier->SetEnabled(false);
+
+ DisplayManager* display_manager = Shell::GetInstance()->display_manager();
+ display_manager->SetDisplayUIScale(display1.id(), 1.25);
+ display1 = Shell::GetScreen()->GetPrimaryDisplay();
+ display2 = ScreenAsh::GetSecondaryDisplay();
+ magnifier->SetEnabled(true);
+ EXPECT_EQ(2.0f, magnifier->GetScale());
+ EXPECT_EQ("0,0 375x250", display1.bounds().ToString());
+ EXPECT_EQ("0,0 375x250", root_windows[0]->bounds().ToString());
+ EXPECT_EQ("375,0 500x300", display2.bounds().ToString());
+ EXPECT_EQ(1.25f, GetStoredUIScale(display1.id()));
+ EXPECT_EQ(1.0f, GetStoredUIScale(display2.id()));
+ magnifier->SetEnabled(false);
+
+ Shell::GetInstance()->RemovePreTargetHandler(&event_handler);
+}
+
+TEST_F(RootWindowTransformersTest, MAYBE_TouchScaleAndMagnify) {
+ TestEventHandler event_handler;
+ Shell::GetInstance()->AddPreTargetHandler(&event_handler);
+
+ UpdateDisplay("200x200*2");
+ gfx::Display display = Shell::GetScreen()->GetPrimaryDisplay();
+ Shell::RootWindowList root_windows = Shell::GetAllRootWindows();
+ aura::RootWindow* root_window = root_windows[0];
+ aura::test::EventGenerator generator(root_window);
+ MagnificationController* magnifier =
+ Shell::GetInstance()->magnification_controller();
+
+ magnifier->SetEnabled(true);
+ EXPECT_FLOAT_EQ(2.0f, magnifier->GetScale());
+ magnifier->SetScale(2.5f, false);
+ EXPECT_FLOAT_EQ(2.5f, magnifier->GetScale());
+ generator.PressMoveAndReleaseTouchTo(50, 50);
+ // Default test touches have radius_x/y = 1.0, with device scale
+ // factor = 2, the scaled radius_x/y should be 0.5.
+ EXPECT_FLOAT_EQ(0.2f, event_handler.touch_radius_x());
+ EXPECT_FLOAT_EQ(0.2f, event_handler.touch_radius_y());
+
+ generator.ScrollSequence(gfx::Point(0,0),
+ base::TimeDelta::FromMilliseconds(100),
+ 10.0, 1.0, 5, 1);
+
+ // With device scale factor = 2, ordinal_offset * 2 = offset.
+ EXPECT_FLOAT_EQ(event_handler.scroll_x_offset(),
+ event_handler.scroll_x_offset_ordinal() * 2 * 2.5f);
+ EXPECT_FLOAT_EQ(event_handler.scroll_y_offset(),
+ event_handler.scroll_y_offset_ordinal() * 2 * 2.5f);
+ magnifier->SetEnabled(false);
+
+ Shell::GetInstance()->RemovePreTargetHandler(&event_handler);
+}
+
+TEST_F(RootWindowTransformersTest, MAYBE_ConvertHostToRootCoords) {
+ TestEventHandler event_handler;
+ Shell::GetInstance()->AddPreTargetHandler(&event_handler);
+ MagnificationController* magnifier =
+ Shell::GetInstance()->magnification_controller();
+
+ // Test 1
+ UpdateDisplay("600x400*2/r@1.5");
+
+ gfx::Display display1 = Shell::GetScreen()->GetPrimaryDisplay();
+ Shell::RootWindowList root_windows = Shell::GetAllRootWindows();
+ EXPECT_EQ("0,0 300x450", display1.bounds().ToString());
+ EXPECT_EQ("0,0 300x450", root_windows[0]->bounds().ToString());
+ EXPECT_EQ(1.5f, GetStoredUIScale(display1.id()));
+
+ aura::test::EventGenerator generator(root_windows[0]);
+ generator.MoveMouseToInHost(300, 200);
+ magnifier->SetEnabled(true);
+ EXPECT_EQ("150,224", event_handler.GetLocationAndReset());
+ EXPECT_FLOAT_EQ(2.0f, magnifier->GetScale());
+
+ generator.MoveMouseToInHost(300, 200);
+ EXPECT_EQ("150,224", event_handler.GetLocationAndReset());
+ generator.MoveMouseToInHost(200, 300);
+ EXPECT_EQ("187,261", event_handler.GetLocationAndReset());
+ generator.MoveMouseToInHost(100, 400);
+ EXPECT_EQ("237,299", event_handler.GetLocationAndReset());
+ generator.MoveMouseToInHost(0, 0);
+ EXPECT_EQ("137,348", event_handler.GetLocationAndReset());
+
+ magnifier->SetEnabled(false);
+ EXPECT_FLOAT_EQ(1.0f, magnifier->GetScale());
+
+ // Test 2
+ UpdateDisplay("600x400*2/u@1.5");
+ display1 = Shell::GetScreen()->GetPrimaryDisplay();
+ root_windows = Shell::GetAllRootWindows();
+ EXPECT_EQ("0,0 450x300", display1.bounds().ToString());
+ EXPECT_EQ("0,0 450x300", root_windows[0]->bounds().ToString());
+ EXPECT_EQ(1.5f, GetStoredUIScale(display1.id()));
+
+ generator.MoveMouseToInHost(300, 200);
+ magnifier->SetEnabled(true);
+ EXPECT_EQ("224,149", event_handler.GetLocationAndReset());
+ EXPECT_FLOAT_EQ(2.0f, magnifier->GetScale());
+
+ generator.MoveMouseToInHost(300, 200);
+ EXPECT_EQ("224,148", event_handler.GetLocationAndReset());
+ generator.MoveMouseToInHost(200, 300);
+ EXPECT_EQ("261,111", event_handler.GetLocationAndReset());
+ generator.MoveMouseToInHost(100, 400);
+ EXPECT_EQ("299,60", event_handler.GetLocationAndReset());
+ generator.MoveMouseToInHost(0, 0);
+ EXPECT_EQ("348,159", event_handler.GetLocationAndReset());
+
+ magnifier->SetEnabled(false);
+ EXPECT_FLOAT_EQ(1.0f, magnifier->GetScale());
+
+ // Test 3
+ UpdateDisplay("600x400*2/l@1.5");
+ display1 = Shell::GetScreen()->GetPrimaryDisplay();
+ root_windows = Shell::GetAllRootWindows();
+ EXPECT_EQ("0,0 300x450", display1.bounds().ToString());
+ EXPECT_EQ("0,0 300x450", root_windows[0]->bounds().ToString());
+ EXPECT_EQ(1.5f, GetStoredUIScale(display1.id()));
+
+ generator.MoveMouseToInHost(300, 200);
+ magnifier->SetEnabled(true);
+ EXPECT_EQ("149,225", event_handler.GetLocationAndReset());
+ EXPECT_FLOAT_EQ(2.0f, magnifier->GetScale());
+
+ generator.MoveMouseToInHost(300, 200);
+ EXPECT_EQ("148,224", event_handler.GetLocationAndReset());
+ generator.MoveMouseToInHost(200, 300);
+ EXPECT_EQ("111,187", event_handler.GetLocationAndReset());
+ generator.MoveMouseToInHost(100, 400);
+ EXPECT_EQ("60,149", event_handler.GetLocationAndReset());
+ generator.MoveMouseToInHost(0, 0);
+ EXPECT_EQ("159,99", event_handler.GetLocationAndReset());
+
+ magnifier->SetEnabled(false);
+ EXPECT_FLOAT_EQ(1.0f, magnifier->GetScale());
+
+ Shell::GetInstance()->RemovePreTargetHandler(&event_handler);
+}
+
+TEST_F(RootWindowTransformersTest, LetterBoxPillarBox) {
+ if (!SupportsMultipleDisplays())
+ return;
+ test::MirrorWindowTestApi test_api;
+ DisplayManager* display_manager = Shell::GetInstance()->display_manager();
+ display_manager->SetSoftwareMirroring(true);
+ UpdateDisplay("400x200,500x500");
+ scoped_ptr<aura::RootWindowTransformer> transformer(
+ test_api.CreateCurrentRootWindowTransformer());
+ // Y margin must be margin is (500 - 500/400 * 200) / 2 = 125.
+ EXPECT_EQ("0,125,0,125", transformer->GetHostInsets().ToString());
+
+ UpdateDisplay("200x400,500x500");
+ // The aspect ratio is flipped, so X margin is now 125.
+ transformer = test_api.CreateCurrentRootWindowTransformer();
+ EXPECT_EQ("125,0,125,0", transformer->GetHostInsets().ToString());
+}
+
+} // namespace test
+} // namespace ash
diff --git a/chromium/ash/display/screen_position_controller.cc b/chromium/ash/display/screen_position_controller.cc
new file mode 100644
index 00000000000..8c37efeebe4
--- /dev/null
+++ b/chromium/ash/display/screen_position_controller.cc
@@ -0,0 +1,216 @@
+// Copyright (c) 2012 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.
+
+#include "ash/display/screen_position_controller.h"
+
+#include "ash/display/display_controller.h"
+#include "ash/root_window_controller.h"
+#include "ash/shell.h"
+#include "ash/shell_window_ids.h"
+#include "ash/wm/coordinate_conversion.h"
+#include "ash/wm/system_modal_container_layout_manager.h"
+#include "ash/wm/window_properties.h"
+#include "ui/aura/client/activation_client.h"
+#include "ui/aura/client/capture_client.h"
+#include "ui/aura/client/focus_client.h"
+#include "ui/aura/client/stacking_client.h"
+#include "ui/aura/root_window.h"
+#include "ui/aura/window_tracker.h"
+#include "ui/compositor/dip_util.h"
+#include "ui/gfx/display.h"
+#include "ui/gfx/screen.h"
+
+namespace ash {
+namespace {
+
+// Return true if the window or its ancestor has |kStayInSameRootWindowkey|
+// property.
+bool ShouldStayInSameRootWindow(const aura::Window* window) {
+ return window &&
+ (window->GetProperty(internal::kStayInSameRootWindowKey) ||
+ ShouldStayInSameRootWindow(window->parent()));
+}
+
+// Move all transient children to |dst_root|, including the ones in
+// the child windows and transient children of the transient children.
+void MoveAllTransientChildrenToNewRoot(const gfx::Display& display,
+ aura::Window* window) {
+ aura::RootWindow* dst_root = Shell::GetInstance()->display_controller()->
+ GetRootWindowForDisplayId(display.id());
+ aura::Window::Windows transient_children = window->transient_children();
+ for (aura::Window::Windows::iterator iter = transient_children.begin();
+ iter != transient_children.end(); ++iter) {
+ aura::Window* transient_child = *iter;
+ int container_id = transient_child->parent()->id();
+ DCHECK_GE(container_id, 0);
+ aura::Window* container = Shell::GetContainer(dst_root, container_id);
+ gfx::Rect parent_bounds_in_screen = transient_child->GetBoundsInScreen();
+ container->AddChild(transient_child);
+ transient_child->SetBoundsInScreen(parent_bounds_in_screen, display);
+
+ // Transient children may have transient children.
+ MoveAllTransientChildrenToNewRoot(display, transient_child);
+ }
+ // Move transient children of the child windows if any.
+ aura::Window::Windows children = window->children();
+ for (aura::Window::Windows::iterator iter = children.begin();
+ iter != children.end(); ++iter)
+ MoveAllTransientChildrenToNewRoot(display, *iter);
+}
+
+// Finds the root window at |location| in |window|'s coordinates and returns a
+// pair of root window and location in that root window's coordinates. The
+// function usually returns |window->GetRootWindow()|, but if the mouse pointer
+// is moved outside the |window|'s root while the mouse is captured, it returns
+// the other root window.
+std::pair<aura::RootWindow*, gfx::Point> GetRootWindowRelativeToWindow(
+ aura::Window* window,
+ const gfx::Point& location) {
+ aura::RootWindow* root_window = window->GetRootWindow();
+ gfx::Point location_in_root(location);
+ aura::Window::ConvertPointToTarget(window, root_window, &location_in_root);
+
+#if defined(USE_X11)
+ if (!root_window->ContainsPointInRoot(location_in_root)) {
+ // This conversion is necessary to deal with X's passive input
+ // grab while dragging window. For example, if we have two
+ // displays, say 1000x1000 (primary) and 500x500 (extended one
+ // on the right), and start dragging a window at (999, 123), and
+ // then move the pointer to the right, the pointer suddenly
+ // warps to the extended display. The destination is (0, 123) in
+ // the secondary root window's coordinates, or (1000, 123) in
+ // the screen coordinates. However, since the mouse is captured
+ // by X during drag, a weird LocatedEvent, something like (0, 1123)
+ // in the *primary* root window's coordinates, is sent to Chrome
+ // (Remember that in the native X11 world, the two root windows
+ // are always stacked vertically regardless of the display
+ // layout in Ash). We need to figure out that (0, 1123) in the
+ // primary root window's coordinates is actually (0, 123) in the
+ // extended root window's coordinates.
+
+ gfx::Point location_in_native(location_in_root);
+ root_window->ConvertPointToNativeScreen(&location_in_native);
+
+ Shell::RootWindowList root_windows = Shell::GetAllRootWindows();
+ for (size_t i = 0; i < root_windows.size(); ++i) {
+ const gfx::Rect native_bounds(
+ root_windows[i]->GetHostOrigin(),
+ root_windows[i]->GetHostSize()); // in px.
+ if (native_bounds.Contains(location_in_native)) {
+ root_window = root_windows[i];
+ location_in_root = location_in_native;
+ root_window->ConvertPointFromNativeScreen(&location_in_root);
+ break;
+ }
+ }
+ }
+#else
+ // TODO(yusukes): Support non-X11 platforms if necessary.
+#endif
+
+ return std::make_pair(root_window, location_in_root);
+}
+
+} // namespace
+
+namespace internal {
+
+void ScreenPositionController::ConvertPointToScreen(
+ const aura::Window* window,
+ gfx::Point* point) {
+ const aura::RootWindow* root = window->GetRootWindow();
+ aura::Window::ConvertPointToTarget(window, root, point);
+ const gfx::Point display_origin = Shell::GetScreen()->GetDisplayNearestWindow(
+ const_cast<aura::RootWindow*>(root)).bounds().origin();
+ point->Offset(display_origin.x(), display_origin.y());
+}
+
+void ScreenPositionController::ConvertPointFromScreen(
+ const aura::Window* window,
+ gfx::Point* point) {
+ const aura::RootWindow* root = window->GetRootWindow();
+ const gfx::Point display_origin = Shell::GetScreen()->GetDisplayNearestWindow(
+ const_cast<aura::RootWindow*>(root)).bounds().origin();
+ point->Offset(-display_origin.x(), -display_origin.y());
+ aura::Window::ConvertPointToTarget(root, window, point);
+}
+
+void ScreenPositionController::ConvertHostPointToScreen(
+ aura::RootWindow* root_window,
+ gfx::Point* point) {
+ root_window->ConvertPointFromHost(point);
+ std::pair<aura::RootWindow*, gfx::Point> pair =
+ GetRootWindowRelativeToWindow(root_window, *point);
+ *point = pair.second;
+ ConvertPointToScreen(pair.first, point);
+}
+
+void ScreenPositionController::SetBounds(aura::Window* window,
+ const gfx::Rect& bounds,
+ const gfx::Display& display) {
+ DCHECK_NE(-1, display.id());
+ if (!window->parent()->GetProperty(internal::kUsesScreenCoordinatesKey)) {
+ window->SetBounds(bounds);
+ return;
+ }
+
+ // Don't move a window to other root window if:
+ // a) the window is a transient window. It moves when its
+ // transient_parent moves.
+ // b) if the window or its ancestor has kStayInSameRootWindowkey. It's
+ // intentionally kept in the same root window even if the bounds is
+ // outside of the display.
+ if (!window->transient_parent() &&
+ !ShouldStayInSameRootWindow(window)) {
+ aura::RootWindow* dst_root =
+ Shell::GetInstance()->display_controller()->GetRootWindowForDisplayId(
+ display.id());
+ DCHECK(dst_root);
+ aura::Window* dst_container = NULL;
+ if (dst_root != window->GetRootWindow()) {
+ int container_id = window->parent()->id();
+ // All containers that uses screen coordinates must have valid window ids.
+ DCHECK_GE(container_id, 0);
+ // Don't move modal background.
+ if (!SystemModalContainerLayoutManager::IsModalBackground(window))
+ dst_container = Shell::GetContainer(dst_root, container_id);
+ }
+
+ if (dst_container && window->parent() != dst_container) {
+ aura::Window* focused = aura::client::GetFocusClient(window)->
+ GetFocusedWindow();
+ aura::client::ActivationClient* activation_client =
+ aura::client::GetActivationClient(window->GetRootWindow());
+ aura::Window* active = activation_client->GetActiveWindow();
+
+ aura::WindowTracker tracker;
+ if (focused)
+ tracker.Add(focused);
+ if (active && focused != active)
+ tracker.Add(active);
+
+ dst_container->AddChild(window);
+
+ MoveAllTransientChildrenToNewRoot(display, window);
+
+ // Restore focused/active window.
+ if (tracker.Contains(focused)) {
+ aura::client::GetFocusClient(window)->FocusWindow(focused);
+ ash::Shell::GetInstance()->set_active_root_window(
+ focused->GetRootWindow());
+ } else if (tracker.Contains(active)) {
+ activation_client->ActivateWindow(active);
+ }
+ }
+ }
+
+ gfx::Point origin(bounds.origin());
+ const gfx::Point display_origin = Shell::GetScreen()->GetDisplayNearestWindow(
+ window).bounds().origin();
+ origin.Offset(-display_origin.x(), -display_origin.y());
+ window->SetBounds(gfx::Rect(origin, bounds.size()));
+}
+
+} // internal
+} // ash
diff --git a/chromium/ash/display/screen_position_controller.h b/chromium/ash/display/screen_position_controller.h
new file mode 100644
index 00000000000..51bf5010405
--- /dev/null
+++ b/chromium/ash/display/screen_position_controller.h
@@ -0,0 +1,37 @@
+// Copyright (c) 2012 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 ASH_DISPLAY_SCREEN_POSITION_CONTROLLER_H_
+#define ASH_DISPLAY_SCREEN_POSITION_CONTROLLER_H_
+
+#include "base/basictypes.h"
+#include "ui/aura/client/screen_position_client.h"
+
+namespace ash {
+namespace internal {
+
+class ScreenPositionController : public aura::client::ScreenPositionClient {
+ public:
+ ScreenPositionController() {}
+ virtual ~ScreenPositionController() {}
+
+ // aura::client::ScreenPositionClient overrides:
+ virtual void ConvertPointToScreen(const aura::Window* window,
+ gfx::Point* point) OVERRIDE;
+ virtual void ConvertPointFromScreen(const aura::Window* window,
+ gfx::Point* point) OVERRIDE;
+ virtual void ConvertHostPointToScreen(aura::RootWindow* window,
+ gfx::Point* point) OVERRIDE;
+ virtual void SetBounds(aura::Window* window,
+ const gfx::Rect& bounds,
+ const gfx::Display& display) OVERRIDE;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ScreenPositionController);
+};
+
+} // internal
+} // ash
+
+#endif // ASH_DISPLAY_SCREEN_POSITION_CONTROLLER_H_
diff --git a/chromium/ash/display/screen_position_controller_unittest.cc b/chromium/ash/display/screen_position_controller_unittest.cc
new file mode 100644
index 00000000000..260590ac5d3
--- /dev/null
+++ b/chromium/ash/display/screen_position_controller_unittest.cc
@@ -0,0 +1,268 @@
+// Copyright (c) 2012 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.
+
+#include "ash/display/screen_position_controller.h"
+
+#include "ash/display/display_controller.h"
+#include "ash/screen_ash.h"
+#include "ash/shell.h"
+#include "ash/test/ash_test_base.h"
+#include "ash/test/shell_test_api.h"
+#include "ui/aura/env.h"
+#include "ui/aura/root_window.h"
+#include "ui/aura/test/test_window_delegate.h"
+#include "ui/base/layout.h"
+#include "ui/gfx/screen.h"
+
+#if defined(OS_WIN)
+// TODO(scottmg): RootWindow doesn't get resized immediately on Windows
+// Ash. http://crbug.com/247916.
+#define MAYBE_ConvertHostPointToScreen DISABLED_ConvertHostPointToScreen
+#define MAYBE_ConvertHostPointToScreenHiDPI DISABLED_ConvertHostPointToScreenHiDPI
+#define MAYBE_ConvertHostPointToScreenRotate DISABLED_ConvertHostPointToScreenRotate
+#define MAYBE_ConvertHostPointToScreenUIScale DISABLED_ConvertHostPointToScreenUIScale
+#else
+#define MAYBE_ConvertHostPointToScreen ConvertHostPointToScreen
+#define MAYBE_ConvertHostPointToScreenHiDPI ConvertHostPointToScreenHiDPI
+#define MAYBE_ConvertHostPointToScreenRotate ConvertHostPointToScreenRotate
+#define MAYBE_ConvertHostPointToScreenUIScale ConvertHostPointToScreenUIScale
+#endif
+
+namespace ash {
+namespace test {
+
+namespace {
+void SetSecondaryDisplayLayout(DisplayLayout::Position position) {
+ DisplayLayout layout =
+ Shell::GetInstance()->display_manager()->GetCurrentDisplayLayout();
+ layout.position = position;
+ Shell::GetInstance()->display_controller()->
+ SetLayoutForCurrentDisplays(layout);
+}
+
+internal::ScreenPositionController* GetScreenPositionController() {
+ ShellTestApi test_api(Shell::GetInstance());
+ return test_api.screen_position_controller();
+}
+
+class ScreenPositionControllerTest : public test::AshTestBase {
+ public:
+ ScreenPositionControllerTest() {}
+ virtual ~ScreenPositionControllerTest() {}
+
+ virtual void SetUp() OVERRIDE {
+ AshTestBase::SetUp();
+ window_.reset(new aura::Window(&window_delegate_));
+ window_->SetType(aura::client::WINDOW_TYPE_NORMAL);
+ window_->Init(ui::LAYER_NOT_DRAWN);
+ SetDefaultParentByPrimaryRootWindow(window_.get());
+ window_->set_id(1);
+ }
+
+ virtual void TearDown() OVERRIDE {
+ window_.reset();
+ AshTestBase::TearDown();
+ }
+
+ // Converts a point (x, y) in host window's coordinate to screen and
+ // returns its string representation.
+ std::string ConvertHostPointToScreen(int x, int y) const {
+ gfx::Point point(x, y);
+ GetScreenPositionController()->ConvertHostPointToScreen(
+ window_->GetRootWindow(), &point);
+ return point.ToString();
+ }
+
+ protected:
+ scoped_ptr<aura::Window> window_;
+ aura::test::TestWindowDelegate window_delegate_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ScreenPositionControllerTest);
+};
+
+} // namespace
+
+TEST_F(ScreenPositionControllerTest, MAYBE_ConvertHostPointToScreen) {
+ UpdateDisplay("100+100-200x200,100+500-200x200");
+
+ Shell::RootWindowList root_windows =
+ Shell::GetInstance()->GetAllRootWindows();
+ EXPECT_EQ("100,100", root_windows[0]->GetHostOrigin().ToString());
+ EXPECT_EQ("200x200", root_windows[0]->GetHostSize().ToString());
+ EXPECT_EQ("100,500", root_windows[1]->GetHostOrigin().ToString());
+ EXPECT_EQ("200x200", root_windows[1]->GetHostSize().ToString());
+
+ const gfx::Point window_pos(100, 100);
+ window_->SetBoundsInScreen(
+ gfx::Rect(window_pos, gfx::Size(100, 100)),
+ Shell::GetScreen()->GetDisplayNearestPoint(window_pos));
+ SetSecondaryDisplayLayout(DisplayLayout::RIGHT);
+ // The point is on the primary root window.
+ EXPECT_EQ("50,50", ConvertHostPointToScreen(50, 50));
+ // The point is out of the all root windows.
+ EXPECT_EQ("250,250", ConvertHostPointToScreen(250, 250));
+ // The point is on the secondary display.
+ EXPECT_EQ("250,0", ConvertHostPointToScreen(50, 400));
+
+ SetSecondaryDisplayLayout(DisplayLayout::BOTTOM);
+ // The point is on the primary root window.
+ EXPECT_EQ("50,50", ConvertHostPointToScreen(50, 50));
+ // The point is out of the all root windows.
+ EXPECT_EQ("250,250", ConvertHostPointToScreen(250, 250));
+ // The point is on the secondary display.
+ EXPECT_EQ("50,200", ConvertHostPointToScreen(50, 400));
+
+ SetSecondaryDisplayLayout(DisplayLayout::LEFT);
+ // The point is on the primary root window.
+ EXPECT_EQ("50,50", ConvertHostPointToScreen(50, 50));
+ // The point is out of the all root windows.
+ EXPECT_EQ("250,250", ConvertHostPointToScreen(250, 250));
+ // The point is on the secondary display.
+ EXPECT_EQ("-150,0", ConvertHostPointToScreen(50, 400));
+
+ SetSecondaryDisplayLayout(DisplayLayout::TOP);
+ // The point is on the primary root window.
+ EXPECT_EQ("50,50", ConvertHostPointToScreen(50, 50));
+ // The point is out of the all root windows.
+ EXPECT_EQ("250,250", ConvertHostPointToScreen(250, 250));
+ // The point is on the secondary display.
+ EXPECT_EQ("50,-200", ConvertHostPointToScreen(50, 400));
+
+
+ SetSecondaryDisplayLayout(DisplayLayout::RIGHT);
+ const gfx::Point window_pos2(300, 100);
+ window_->SetBoundsInScreen(
+ gfx::Rect(window_pos2, gfx::Size(100, 100)),
+ Shell::GetScreen()->GetDisplayNearestPoint(window_pos2));
+ // The point is on the secondary display.
+ EXPECT_EQ("250,50", ConvertHostPointToScreen(50, 50));
+ // The point is out of the all root windows.
+ EXPECT_EQ("450,250", ConvertHostPointToScreen(250, 250));
+ // The point is on the primary root window.
+ EXPECT_EQ("50,0", ConvertHostPointToScreen(50, -400));
+
+ SetSecondaryDisplayLayout(DisplayLayout::BOTTOM);
+ // The point is on the secondary display.
+ EXPECT_EQ("50,250", ConvertHostPointToScreen(50, 50));
+ // The point is out of the all root windows.
+ EXPECT_EQ("250,450", ConvertHostPointToScreen(250, 250));
+ // The point is on the primary root window.
+ EXPECT_EQ("50,0", ConvertHostPointToScreen(50, -400));
+
+ SetSecondaryDisplayLayout(DisplayLayout::LEFT);
+ // The point is on the secondary display.
+ EXPECT_EQ("-150,50", ConvertHostPointToScreen(50, 50));
+ // The point is out of the all root windows.
+ EXPECT_EQ("50,250", ConvertHostPointToScreen(250, 250));
+ // The point is on the primary root window.
+ EXPECT_EQ("50,0", ConvertHostPointToScreen(50, -400));
+
+ SetSecondaryDisplayLayout(DisplayLayout::TOP);
+ // The point is on the secondary display.
+ EXPECT_EQ("50,-150", ConvertHostPointToScreen(50, 50));
+ // The point is out of the all root windows.
+ EXPECT_EQ("250,50", ConvertHostPointToScreen(250, 250));
+ // The point is on the primary root window.
+ EXPECT_EQ("50,0", ConvertHostPointToScreen(50, -400));
+}
+
+TEST_F(ScreenPositionControllerTest, MAYBE_ConvertHostPointToScreenHiDPI) {
+ UpdateDisplay("100+100-200x200*2,100+500-200x200");
+
+ Shell::RootWindowList root_windows =
+ Shell::GetInstance()->GetAllRootWindows();
+ EXPECT_EQ("100,100", root_windows[0]->GetHostOrigin().ToString());
+ EXPECT_EQ("200x200", root_windows[0]->GetHostSize().ToString());
+ EXPECT_EQ("100,500", root_windows[1]->GetHostOrigin().ToString());
+ EXPECT_EQ("200x200", root_windows[1]->GetHostSize().ToString());
+
+ // Put |window_| to the primary 2x display.
+ window_->SetBoundsInScreen(gfx::Rect(20, 20, 50, 50),
+ Shell::GetScreen()->GetPrimaryDisplay());
+ // (30, 30) means the host coordinate, so the point is still on the primary
+ // root window. Since it's 2x, the specified native point was halved.
+ EXPECT_EQ("15,15", ConvertHostPointToScreen(30, 30));
+ // Similar to above but the point is out of the all root windows.
+ EXPECT_EQ("200,200", ConvertHostPointToScreen(400, 400));
+ // Similar to above but the point is on the secondary display.
+ EXPECT_EQ("100,15", ConvertHostPointToScreen(200, 30));
+
+ // On secondary display. The position on the 2nd host window is (150,50)
+ // so the screen position is (100,0) + (150,50).
+ EXPECT_EQ("250,50", ConvertHostPointToScreen(150, 450));
+
+ // At the edge but still in the primary display. Remaining of the primary
+ // display is (50, 50) but adding ~100 since it's 2x-display.
+ EXPECT_EQ("79,79", ConvertHostPointToScreen(158, 158));
+ // At the edge of the secondary display.
+ EXPECT_EQ("80,80", ConvertHostPointToScreen(160, 160));
+}
+
+TEST_F(ScreenPositionControllerTest, MAYBE_ConvertHostPointToScreenRotate) {
+ // 1st display is rotated 90 clockise, and 2nd display is rotated
+ // 270 clockwise.
+ UpdateDisplay("100+100-200x200/r,100+500-200x200/l");
+ // Put |window_| to the 1st.
+ window_->SetBoundsInScreen(gfx::Rect(20, 20, 50, 50),
+ Shell::GetScreen()->GetPrimaryDisplay());
+
+ // The point is on the 1st host.
+ EXPECT_EQ("70,149", ConvertHostPointToScreen(50, 70));
+ // The point is out of the host windows.
+ EXPECT_EQ("250,-51", ConvertHostPointToScreen(250, 250));
+ // The point is on the 2nd host. Point on 2nd host (30,150) -
+ // rotate 270 clockwise -> (149, 30) - layout [+(200,0)] -> (349,30).
+ EXPECT_EQ("349,30", ConvertHostPointToScreen(30, 450));
+
+ // Move |window_| to the 2nd.
+ window_->SetBoundsInScreen(gfx::Rect(300, 20, 50, 50),
+ ScreenAsh::GetSecondaryDisplay());
+ Shell::RootWindowList root_windows =
+ Shell::GetInstance()->GetAllRootWindows();
+ EXPECT_EQ(root_windows[1], window_->GetRootWindow());
+
+ // The point is on the 2nd host. (50,70) on 2n host -
+ // roatate 270 clockwise -> (129,50) -layout [+(200,0)] -> (329,50)
+ EXPECT_EQ("329,50", ConvertHostPointToScreen(50, 70));
+ // The point is out of the host windows.
+ EXPECT_EQ("449,50", ConvertHostPointToScreen(50, -50));
+ // The point is on the 2nd host. Point on 2nd host (50,50) -
+ // rotate 90 clockwise -> (50, 149)
+ EXPECT_EQ("50,149", ConvertHostPointToScreen(50, -350));
+}
+
+TEST_F(ScreenPositionControllerTest, MAYBE_ConvertHostPointToScreenUIScale) {
+ // 1st display is 2x density with 1.5 UI scale.
+ UpdateDisplay("100+100-200x200*2@1.5,100+500-200x200");
+ // Put |window_| to the 1st.
+ window_->SetBoundsInScreen(gfx::Rect(20, 20, 50, 50),
+ Shell::GetScreen()->GetPrimaryDisplay());
+
+ // The point is on the 1st host.
+ EXPECT_EQ("45,45", ConvertHostPointToScreen(60, 60));
+ // The point is out of the host windows.
+ EXPECT_EQ("45,225", ConvertHostPointToScreen(60, 300));
+ // The point is on the 2nd host. Point on 2nd host (60,150) -
+ // - screen [+(150,0)]
+ EXPECT_EQ("210,49", ConvertHostPointToScreen(60, 450));
+
+ // Move |window_| to the 2nd.
+ window_->SetBoundsInScreen(gfx::Rect(300, 20, 50, 50),
+ ScreenAsh::GetSecondaryDisplay());
+ Shell::RootWindowList root_windows =
+ Shell::GetInstance()->GetAllRootWindows();
+ EXPECT_EQ(root_windows[1], window_->GetRootWindow());
+
+ // The point is on the 2nd host. (50,70) - ro
+ EXPECT_EQ("210,70", ConvertHostPointToScreen(60, 70));
+ // The point is out of the host windows.
+ EXPECT_EQ("210,-50", ConvertHostPointToScreen(60, -50));
+ // The point is on the 2nd host. Point on 1nd host (60, 60)
+ // 1/2 * 1.5 = (45,45)
+ EXPECT_EQ("45,45", ConvertHostPointToScreen(60, -340));
+}
+
+} // namespace test
+} // namespace ash
diff --git a/chromium/ash/display/shared_display_edge_indicator.cc b/chromium/ash/display/shared_display_edge_indicator.cc
new file mode 100644
index 00000000000..cfcf4bafdfd
--- /dev/null
+++ b/chromium/ash/display/shared_display_edge_indicator.cc
@@ -0,0 +1,117 @@
+// Copyright (c) 2012 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.
+
+#include "ash/display/shared_display_edge_indicator.h"
+
+#include "ash/shell.h"
+#include "ash/shell_window_ids.h"
+#include "ash/wm/coordinate_conversion.h"
+#include "third_party/skia/include/core/SkColor.h"
+#include "ui/aura/client/screen_position_client.h"
+#include "ui/aura/root_window.h"
+#include "ui/base/animation/throb_animation.h"
+#include "ui/gfx/canvas.h"
+#include "ui/gfx/display.h"
+#include "ui/gfx/screen.h"
+#include "ui/views/view.h"
+#include "ui/views/widget/widget.h"
+
+namespace ash {
+namespace internal {
+namespace {
+
+const int kIndicatorAnimationDurationMs = 1000;
+
+class IndicatorView : public views::View {
+ public:
+ IndicatorView() {
+ }
+ virtual ~IndicatorView() {
+ }
+
+ void SetColor(SkColor color) {
+ color_ = color;
+ SchedulePaint();
+ }
+
+ // views::Views overrides:
+ virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE {
+ canvas->FillRect(gfx::Rect(bounds().size()), color_);
+ }
+
+ private:
+ SkColor color_;
+ DISALLOW_COPY_AND_ASSIGN(IndicatorView);
+};
+
+views::Widget* CreateWidget(const gfx::Rect& bounds,
+ views::View* contents_view) {
+ views::Widget* widget = new views::Widget;
+ views::Widget::InitParams params(views::Widget::InitParams::TYPE_POPUP);
+ params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW;
+ params.can_activate = false;
+ params.keep_on_top = true;
+ // We set the context to the primary root window; this is OK because the ash
+ // stacking controller will still place us in the correct RootWindow.
+ params.context = Shell::GetPrimaryRootWindow();
+ widget->set_focus_on_creation(false);
+ widget->Init(params);
+ widget->SetVisibilityChangedAnimationsEnabled(false);
+ widget->GetNativeWindow()->SetName("SharedEdgeIndicator");
+ widget->SetContentsView(contents_view);
+ gfx::Display display = Shell::GetScreen()->GetDisplayMatching(bounds);
+ aura::Window* window = widget->GetNativeWindow();
+ aura::client::ScreenPositionClient* screen_position_client =
+ aura::client::GetScreenPositionClient(window->GetRootWindow());
+ screen_position_client->SetBounds(window, bounds, display);
+ widget->Show();
+ return widget;
+}
+
+} // namespace
+
+SharedDisplayEdgeIndicator::SharedDisplayEdgeIndicator()
+ : src_indicator_(NULL),
+ dst_indicator_(NULL) {
+}
+
+SharedDisplayEdgeIndicator::~SharedDisplayEdgeIndicator() {
+ Hide();
+}
+
+void SharedDisplayEdgeIndicator::Show(const gfx::Rect& src_bounds,
+ const gfx::Rect& dst_bounds) {
+ DCHECK(!src_indicator_);
+ DCHECK(!dst_indicator_);
+ src_indicator_ = new IndicatorView;
+ dst_indicator_ = new IndicatorView;
+ CreateWidget(src_bounds, src_indicator_);
+ CreateWidget(dst_bounds, dst_indicator_);
+ animation_.reset(new ui::ThrobAnimation(this));
+ animation_->SetThrobDuration(kIndicatorAnimationDurationMs);
+ animation_->StartThrobbing(-1 /* infinite */);
+}
+
+void SharedDisplayEdgeIndicator::Hide() {
+ if (src_indicator_)
+ src_indicator_->GetWidget()->Close();
+ src_indicator_ = NULL;
+ if (dst_indicator_)
+ dst_indicator_->GetWidget()->Close();
+ dst_indicator_ = NULL;
+}
+
+void SharedDisplayEdgeIndicator::AnimationProgressed(
+ const ui::Animation* animation) {
+ int value = animation->CurrentValueBetween(0, 255);
+ SkColor color = SkColorSetARGB(0xFF, value, value, value);
+ if (src_indicator_)
+ static_cast<IndicatorView*>(src_indicator_)->SetColor(color);
+ if (dst_indicator_)
+ static_cast<IndicatorView*>(dst_indicator_)->SetColor(color);
+
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/display/shared_display_edge_indicator.h b/chromium/ash/display/shared_display_edge_indicator.h
new file mode 100644
index 00000000000..762d067b458
--- /dev/null
+++ b/chromium/ash/display/shared_display_edge_indicator.h
@@ -0,0 +1,62 @@
+// Copyright (c) 2012 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 ASH_DISPLAY_SHARED_DISPLAY_EDGE_INDICATOR_H_
+#define ASH_DISPLAY_SHARED_DISPLAY_EDGE_INDICATOR_H_
+
+#include "ash/ash_export.h"
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "ui/base/animation/animation_delegate.h"
+#include "ui/gfx/display.h"
+
+namespace gfx {
+class Rect;
+}
+
+namespace ui {
+class ThrobAnimation;
+}
+
+namespace views {
+class View;
+class Widget;
+}
+
+namespace ash {
+namespace internal {
+
+// SharedDisplayEdgeIndicator is responsible for showing a window that indicates
+// the edge that a window can be dragged into another display.
+class ASH_EXPORT SharedDisplayEdgeIndicator : public ui::AnimationDelegate {
+ public:
+ SharedDisplayEdgeIndicator();
+ virtual ~SharedDisplayEdgeIndicator();
+
+ // Shows/Hides the indicator window. The |src_bounds| is for the window on
+ // the source display, and the |dst_bounds| is for the window on the other
+ // display.
+ void Show(const gfx::Rect& src_bounds, const gfx::Rect& dst_bounds);
+ void Hide();
+
+ // ui::AnimationDelegate overrides:
+ virtual void AnimationProgressed(const ui::Animation* animation) OVERRIDE;
+
+ private:
+ // Used to show the displays' shared edge where a window can be moved across.
+ // |src_widget_| is for the display where drag starts and |dst_widget_| is
+ // for the other display.
+ views::View* src_indicator_;
+ views::View* dst_indicator_;
+
+ // Used to transition the opacity.
+ scoped_ptr<ui::ThrobAnimation> animation_;
+
+ DISALLOW_COPY_AND_ASSIGN(SharedDisplayEdgeIndicator);
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_DISPLAY_SHARED_DISPLAY_EDGE_INDICATOR_H_
diff --git a/chromium/ash/drag_drop/OWNERS b/chromium/ash/drag_drop/OWNERS
new file mode 100644
index 00000000000..5762b1cfb19
--- /dev/null
+++ b/chromium/ash/drag_drop/OWNERS
@@ -0,0 +1 @@
+varunjain@chromium.org
diff --git a/chromium/ash/drag_drop/drag_drop_controller.cc b/chromium/ash/drag_drop/drag_drop_controller.cc
new file mode 100644
index 00000000000..5973343584e
--- /dev/null
+++ b/chromium/ash/drag_drop/drag_drop_controller.cc
@@ -0,0 +1,567 @@
+// Copyright (c) 2012 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.
+
+#include "ash/drag_drop/drag_drop_controller.h"
+
+#include "ash/drag_drop/drag_drop_tracker.h"
+#include "ash/drag_drop/drag_image_view.h"
+#include "ash/shell.h"
+#include "ash/wm/coordinate_conversion.h"
+#include "base/bind.h"
+#include "base/message_loop/message_loop.h"
+#include "base/run_loop.h"
+#include "ui/aura/client/capture_client.h"
+#include "ui/aura/client/drag_drop_delegate.h"
+#include "ui/aura/env.h"
+#include "ui/aura/root_window.h"
+#include "ui/aura/window.h"
+#include "ui/aura/window_delegate.h"
+#include "ui/base/animation/linear_animation.h"
+#include "ui/base/dragdrop/drag_drop_types.h"
+#include "ui/base/dragdrop/os_exchange_data.h"
+#include "ui/base/events/event.h"
+#include "ui/base/events/event_utils.h"
+#include "ui/base/hit_test.h"
+#include "ui/gfx/path.h"
+#include "ui/gfx/point.h"
+#include "ui/gfx/rect.h"
+#include "ui/gfx/rect_conversions.h"
+#include "ui/views/views_delegate.h"
+#include "ui/views/widget/native_widget_aura.h"
+
+namespace ash {
+namespace internal {
+
+using aura::RootWindow;
+
+namespace {
+// The duration of the drag cancel animation in millisecond.
+const int kCancelAnimationDuration = 250;
+const int kTouchCancelAnimationDuration = 20;
+// The frame rate of the drag cancel animation in hertz.
+const int kCancelAnimationFrameRate = 60;
+
+// For touch initiated dragging, we scale and shift drag image by the following:
+static const float kTouchDragImageScale = 1.2f;
+static const int kTouchDragImageVerticalOffset = -25;
+
+// Adjusts the drag image bounds such that the new bounds are scaled by |scale|
+// and translated by the |drag_image_offset| and and additional
+// |vertical_offset|.
+gfx::Rect AdjustDragImageBoundsForScaleAndOffset(
+ const gfx::Rect& drag_image_bounds,
+ int vertical_offset,
+ float scale,
+ gfx::Vector2d* drag_image_offset) {
+ gfx::PointF final_origin = drag_image_bounds.origin();
+ gfx::SizeF final_size = drag_image_bounds.size();
+ final_size.Scale(scale);
+ drag_image_offset->set_x(drag_image_offset->x() * scale);
+ drag_image_offset->set_y(drag_image_offset->y() * scale);
+ float total_x_offset = drag_image_offset->x();
+ float total_y_offset = drag_image_offset->y() - vertical_offset;
+ final_origin.Offset(-total_x_offset, -total_y_offset);
+ return gfx::ToEnclosingRect(gfx::RectF(final_origin, final_size));
+}
+
+void DispatchGestureEndToWindow(aura::Window* window) {
+ if (window && window->delegate()) {
+ ui::GestureEvent gesture_end(
+ ui::ET_GESTURE_END,
+ 0,
+ 0,
+ 0,
+ ui::EventTimeForNow(),
+ ui::GestureEventDetails(ui::ET_GESTURE_END, 0, 0),
+ 0);
+ window->delegate()->OnGestureEvent(&gesture_end);
+ }
+}
+} // namespace
+
+class DragDropTrackerDelegate : public aura::WindowDelegate {
+ public:
+ explicit DragDropTrackerDelegate(DragDropController* controller)
+ : drag_drop_controller_(controller) {}
+ virtual ~DragDropTrackerDelegate() {}
+
+ // Overridden from WindowDelegate:
+ virtual gfx::Size GetMinimumSize() const OVERRIDE {
+ return gfx::Size();
+ }
+
+ virtual gfx::Size GetMaximumSize() const OVERRIDE {
+ return gfx::Size();
+ }
+
+ virtual void OnBoundsChanged(const gfx::Rect& old_bounds,
+ const gfx::Rect& new_bounds) OVERRIDE {}
+ virtual gfx::NativeCursor GetCursor(const gfx::Point& point) OVERRIDE {
+ return gfx::kNullCursor;
+ }
+ virtual int GetNonClientComponent(const gfx::Point& point) const OVERRIDE {
+ return HTCAPTION;
+ }
+ virtual bool ShouldDescendIntoChildForEventHandling(
+ aura::Window* child,
+ const gfx::Point& location) OVERRIDE {
+ return true;
+ }
+ virtual bool CanFocus() OVERRIDE { return true; }
+ virtual void OnCaptureLost() OVERRIDE {
+ if (drag_drop_controller_->IsDragDropInProgress())
+ drag_drop_controller_->DragCancel();
+ }
+ virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE {
+ }
+ virtual void OnDeviceScaleFactorChanged(float device_scale_factor) OVERRIDE {}
+ virtual void OnWindowDestroying() OVERRIDE {}
+ virtual void OnWindowDestroyed() OVERRIDE {}
+ virtual void OnWindowTargetVisibilityChanged(bool visible) OVERRIDE {}
+ virtual bool HasHitTestMask() const OVERRIDE {
+ return true;
+ }
+ virtual void GetHitTestMask(gfx::Path* mask) const OVERRIDE {
+ DCHECK(mask->isEmpty());
+ }
+ virtual scoped_refptr<ui::Texture> CopyTexture() OVERRIDE {
+ return scoped_refptr<ui::Texture>();
+ }
+
+ private:
+ DragDropController* drag_drop_controller_;
+
+ DISALLOW_COPY_AND_ASSIGN(DragDropTrackerDelegate);
+};
+
+////////////////////////////////////////////////////////////////////////////////
+// DragDropController, public:
+
+DragDropController::DragDropController()
+ : drag_data_(NULL),
+ drag_operation_(0),
+ drag_window_(NULL),
+ drag_source_window_(NULL),
+ should_block_during_drag_drop_(true),
+ drag_drop_window_delegate_(new DragDropTrackerDelegate(this)),
+ current_drag_event_source_(ui::DragDropTypes::DRAG_EVENT_SOURCE_MOUSE),
+ weak_factory_(this) {
+ Shell::GetInstance()->PrependPreTargetHandler(this);
+}
+
+DragDropController::~DragDropController() {
+ Shell::GetInstance()->RemovePreTargetHandler(this);
+ Cleanup();
+ if (cancel_animation_)
+ cancel_animation_->End();
+ if (drag_image_)
+ drag_image_.reset();
+}
+
+int DragDropController::StartDragAndDrop(
+ const ui::OSExchangeData& data,
+ aura::RootWindow* root_window,
+ aura::Window* source_window,
+ const gfx::Point& root_location,
+ int operation,
+ ui::DragDropTypes::DragEventSource source) {
+ if (IsDragDropInProgress())
+ return 0;
+
+ const ui::OSExchangeData::Provider* provider = &data.provider();
+ // We do not support touch drag/drop without a drag image.
+ if (source == ui::DragDropTypes::DRAG_EVENT_SOURCE_TOUCH &&
+ provider->GetDragImage().size().IsEmpty())
+ return 0;
+
+ current_drag_event_source_ = source;
+ DragDropTracker* tracker =
+ new DragDropTracker(root_window, drag_drop_window_delegate_.get());
+ if (source == ui::DragDropTypes::DRAG_EVENT_SOURCE_TOUCH) {
+ // We need to transfer the current gesture sequence and the GR's touch event
+ // queue to the |drag_drop_tracker_|'s capture window so that when it takes
+ // capture, it still gets a valid gesture state.
+ root_window->gesture_recognizer()->TransferEventsTo(source_window,
+ tracker->capture_window());
+ // We also send a gesture end to the source window so it can clear state.
+ // TODO(varunjain): Remove this whole block when gesture sequence
+ // transferring is properly done in the GR (http://crbug.com/160558)
+ DispatchGestureEndToWindow(source_window);
+ }
+ tracker->TakeCapture();
+ drag_drop_tracker_.reset(tracker);
+ drag_source_window_ = source_window;
+ if (drag_source_window_)
+ drag_source_window_->AddObserver(this);
+ pending_long_tap_.reset();
+
+ drag_data_ = &data;
+ drag_operation_ = operation;
+
+ float drag_image_scale = 1;
+ int drag_image_vertical_offset = 0;
+ if (source == ui::DragDropTypes::DRAG_EVENT_SOURCE_TOUCH) {
+ drag_image_scale = kTouchDragImageScale;
+ drag_image_vertical_offset = kTouchDragImageVerticalOffset;
+ }
+ gfx::Point start_location = root_location;
+ ash::wm::ConvertPointToScreen(root_window, &start_location);
+ drag_image_final_bounds_for_cancel_animation_ = gfx::Rect(
+ start_location - provider->GetDragImageOffset(),
+ provider->GetDragImage().size());
+ drag_image_.reset(new DragImageView(source_window->GetRootWindow()));
+ drag_image_->SetImage(provider->GetDragImage());
+ drag_image_offset_ = provider->GetDragImageOffset();
+ gfx::Rect drag_image_bounds(start_location, drag_image_->GetPreferredSize());
+ drag_image_bounds = AdjustDragImageBoundsForScaleAndOffset(drag_image_bounds,
+ drag_image_vertical_offset, drag_image_scale, &drag_image_offset_);
+ drag_image_->SetBoundsInScreen(drag_image_bounds);
+ drag_image_->SetWidgetVisible(true);
+
+ drag_window_ = NULL;
+
+ // Ends cancel animation if it's in progress.
+ if (cancel_animation_)
+ cancel_animation_->End();
+
+#if !defined(OS_MACOSX)
+ if (should_block_during_drag_drop_) {
+ base::RunLoop run_loop(aura::Env::GetInstance()->GetDispatcher());
+ quit_closure_ = run_loop.QuitClosure();
+ base::MessageLoopForUI* loop = base::MessageLoopForUI::current();
+ base::MessageLoop::ScopedNestableTaskAllower allow_nested(loop);
+ run_loop.Run();
+ }
+#endif // !defined(OS_MACOSX)
+
+ if (!cancel_animation_.get() || !cancel_animation_->is_animating() ||
+ !pending_long_tap_.get()) {
+ // If drag cancel animation is running, this cleanup is done when the
+ // animation completes.
+ if (drag_source_window_)
+ drag_source_window_->RemoveObserver(this);
+ drag_source_window_ = NULL;
+ }
+
+ return drag_operation_;
+}
+
+void DragDropController::DragUpdate(aura::Window* target,
+ const ui::LocatedEvent& event) {
+ aura::client::DragDropDelegate* delegate = NULL;
+ if (target != drag_window_) {
+ if (drag_window_) {
+ if ((delegate = aura::client::GetDragDropDelegate(drag_window_)))
+ delegate->OnDragExited();
+ if (drag_window_ != drag_source_window_)
+ drag_window_->RemoveObserver(this);
+ }
+ drag_window_ = target;
+ // We are already an observer of |drag_source_window_| so no need to add.
+ if (drag_window_ != drag_source_window_)
+ drag_window_->AddObserver(this);
+ if ((delegate = aura::client::GetDragDropDelegate(drag_window_))) {
+ ui::DropTargetEvent e(*drag_data_,
+ event.location(),
+ event.root_location(),
+ drag_operation_);
+ e.set_flags(event.flags());
+ delegate->OnDragEntered(e);
+ }
+ } else {
+ if ((delegate = aura::client::GetDragDropDelegate(drag_window_))) {
+ ui::DropTargetEvent e(*drag_data_,
+ event.location(),
+ event.root_location(),
+ drag_operation_);
+ e.set_flags(event.flags());
+ int op = delegate->OnDragUpdated(e);
+ gfx::NativeCursor cursor = ui::kCursorNoDrop;
+ if (op & ui::DragDropTypes::DRAG_COPY)
+ cursor = ui::kCursorCopy;
+ else if (op & ui::DragDropTypes::DRAG_LINK)
+ cursor = ui::kCursorAlias;
+ else if (op & ui::DragDropTypes::DRAG_MOVE)
+ cursor = ui::kCursorGrabbing;
+ ash::Shell::GetInstance()->cursor_manager()->SetCursor(cursor);
+ }
+ }
+
+ DCHECK(drag_image_.get());
+ if (drag_image_->visible()) {
+ gfx::Point root_location_in_screen = event.root_location();
+ ash::wm::ConvertPointToScreen(target->GetRootWindow(),
+ &root_location_in_screen);
+ drag_image_->SetScreenPosition(
+ root_location_in_screen - drag_image_offset_);
+ }
+}
+
+void DragDropController::Drop(aura::Window* target,
+ const ui::LocatedEvent& event) {
+ ash::Shell::GetInstance()->cursor_manager()->SetCursor(ui::kCursorPointer);
+ aura::client::DragDropDelegate* delegate = NULL;
+
+ // We must guarantee that a target gets a OnDragEntered before Drop. WebKit
+ // depends on not getting a Drop without DragEnter. This behavior is
+ // consistent with drag/drop on other platforms.
+ if (target != drag_window_)
+ DragUpdate(target, event);
+ DCHECK(target == drag_window_);
+
+ if ((delegate = aura::client::GetDragDropDelegate(target))) {
+ ui::DropTargetEvent e(
+ *drag_data_, event.location(), event.root_location(), drag_operation_);
+ e.set_flags(event.flags());
+ drag_operation_ = delegate->OnPerformDrop(e);
+ if (drag_operation_ == 0)
+ StartCanceledAnimation(kCancelAnimationDuration);
+ else
+ drag_image_.reset();
+ } else {
+ drag_image_.reset();
+ }
+
+ Cleanup();
+ if (should_block_during_drag_drop_)
+ quit_closure_.Run();
+}
+
+void DragDropController::DragCancel() {
+ DoDragCancel(kCancelAnimationDuration);
+}
+
+bool DragDropController::IsDragDropInProgress() {
+ return !!drag_drop_tracker_.get();
+}
+
+void DragDropController::OnKeyEvent(ui::KeyEvent* event) {
+ if (IsDragDropInProgress() && event->key_code() == ui::VKEY_ESCAPE) {
+ DragCancel();
+ event->StopPropagation();
+ }
+}
+
+void DragDropController::OnMouseEvent(ui::MouseEvent* event) {
+ if (!IsDragDropInProgress())
+ return;
+
+ // If current drag session was not started by mouse, dont process this mouse
+ // event, but consume it so it does not interfere with current drag session.
+ if (current_drag_event_source_ !=
+ ui::DragDropTypes::DRAG_EVENT_SOURCE_MOUSE) {
+ event->StopPropagation();
+ return;
+ }
+
+ aura::Window* translated_target = drag_drop_tracker_->GetTarget(*event);
+ if (!translated_target) {
+ DragCancel();
+ event->StopPropagation();
+ return;
+ }
+ scoped_ptr<ui::LocatedEvent> translated_event(
+ drag_drop_tracker_->ConvertEvent(translated_target, *event));
+ switch (translated_event->type()) {
+ case ui::ET_MOUSE_DRAGGED:
+ DragUpdate(translated_target, *translated_event.get());
+ break;
+ case ui::ET_MOUSE_RELEASED:
+ Drop(translated_target, *translated_event.get());
+ break;
+ default:
+ // We could also reach here because RootWindow may sometimes generate a
+ // bunch of fake mouse events
+ // (aura::RootWindow::PostMouseMoveEventAfterWindowChange).
+ break;
+ }
+ event->StopPropagation();
+}
+
+void DragDropController::OnTouchEvent(ui::TouchEvent* event) {
+ if (!IsDragDropInProgress())
+ return;
+
+ // If current drag session was not started by touch, dont process this touch
+ // event, but consume it so it does not interfere with current drag session.
+ if (current_drag_event_source_ != ui::DragDropTypes::DRAG_EVENT_SOURCE_TOUCH)
+ event->StopPropagation();
+
+ if (event->handled())
+ return;
+
+ if (event->type() == ui::ET_TOUCH_CANCELLED)
+ DragCancel();
+}
+
+void DragDropController::OnGestureEvent(ui::GestureEvent* event) {
+ if (!IsDragDropInProgress())
+ return;
+
+ // No one else should handle gesture events when in drag drop. Note that it is
+ // not enough to just set ER_HANDLED because the dispatcher only stops
+ // dispatching when the event has ER_CONSUMED. If we just set ER_HANDLED, the
+ // event will still be dispatched to other handlers and we depend on
+ // individual handlers' kindness to not touch events marked ER_HANDLED (not
+ // all handlers are so kind and may cause bugs like crbug.com/236493).
+ event->StopPropagation();
+
+ // If current drag session was not started by touch, dont process this event.
+ if (current_drag_event_source_ != ui::DragDropTypes::DRAG_EVENT_SOURCE_TOUCH)
+ return;
+
+ // Apply kTouchDragImageVerticalOffset to the location.
+ ui::GestureEvent touch_offset_event(*event,
+ static_cast<aura::Window*>(NULL),
+ static_cast<aura::Window*>(NULL));
+ gfx::Point touch_offset_location = touch_offset_event.location();
+ gfx::Point touch_offset_root_location = touch_offset_event.root_location();
+ touch_offset_location.Offset(0, kTouchDragImageVerticalOffset);
+ touch_offset_root_location.Offset(0, kTouchDragImageVerticalOffset);
+ touch_offset_event.set_location(touch_offset_location);
+ touch_offset_event.set_root_location(touch_offset_root_location);
+
+ aura::Window* translated_target =
+ drag_drop_tracker_->GetTarget(touch_offset_event);
+ if (!translated_target) {
+ DragCancel();
+ event->SetHandled();
+ return;
+ }
+ scoped_ptr<ui::LocatedEvent> translated_event(
+ drag_drop_tracker_->ConvertEvent(translated_target, touch_offset_event));
+
+ switch (event->type()) {
+ case ui::ET_GESTURE_SCROLL_UPDATE:
+ DragUpdate(translated_target, *translated_event.get());
+ break;
+ case ui::ET_GESTURE_SCROLL_END:
+ Drop(translated_target, *translated_event.get());
+ break;
+ case ui::ET_SCROLL_FLING_START:
+ case ui::ET_GESTURE_LONG_TAP:
+ // Ideally we would want to just forward this long tap event to the
+ // |drag_source_window_|. However, webkit does not accept events while a
+ // drag drop is still in progress. The drag drop ends only when the nested
+ // message loop ends. Due to this stupidity, we have to defer forwarding
+ // the long tap.
+ pending_long_tap_.reset(
+ new ui::GestureEvent(*event,
+ static_cast<aura::Window*>(drag_drop_tracker_->capture_window()),
+ static_cast<aura::Window*>(drag_source_window_)));
+ DoDragCancel(kTouchCancelAnimationDuration);
+ break;
+ default:
+ break;
+ }
+ event->SetHandled();
+}
+
+void DragDropController::OnWindowDestroyed(aura::Window* window) {
+ if (drag_window_ == window) {
+ drag_window_->RemoveObserver(this);
+ drag_window_ = NULL;
+ }
+ if (drag_source_window_ == window) {
+ drag_source_window_->RemoveObserver(this);
+ drag_source_window_ = NULL;
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// DragDropController, protected:
+
+ui::LinearAnimation* DragDropController::CreateCancelAnimation(
+ int duration,
+ int frame_rate,
+ ui::AnimationDelegate* delegate) {
+ return new ui::LinearAnimation(duration, frame_rate, delegate);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// DragDropController, private:
+
+void DragDropController::AnimationEnded(const ui::Animation* animation) {
+ cancel_animation_.reset();
+
+ // By the time we finish animation, another drag/drop session may have
+ // started. We do not want to destroy the drag image in that case.
+ if (!IsDragDropInProgress())
+ drag_image_.reset();
+ if (pending_long_tap_) {
+ // If not in a nested message loop, we can forward the long tap right now.
+ if (!should_block_during_drag_drop_)
+ ForwardPendingLongTap();
+ else {
+ // See comment about this in OnGestureEvent().
+ base::MessageLoopForUI::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&DragDropController::ForwardPendingLongTap,
+ weak_factory_.GetWeakPtr()));
+ }
+ }
+}
+
+void DragDropController::DoDragCancel(int drag_cancel_animation_duration_ms) {
+ ash::Shell::GetInstance()->cursor_manager()->SetCursor(ui::kCursorPointer);
+
+ // |drag_window_| can be NULL if we have just started the drag and have not
+ // received any DragUpdates, or, if the |drag_window_| gets destroyed during
+ // a drag/drop.
+ aura::client::DragDropDelegate* delegate = drag_window_?
+ aura::client::GetDragDropDelegate(drag_window_) : NULL;
+ if (delegate)
+ delegate->OnDragExited();
+
+ Cleanup();
+ drag_operation_ = 0;
+ StartCanceledAnimation(drag_cancel_animation_duration_ms);
+ if (should_block_during_drag_drop_)
+ quit_closure_.Run();
+}
+
+void DragDropController::AnimationProgressed(const ui::Animation* animation) {
+ gfx::Rect current_bounds = animation->CurrentValueBetween(
+ drag_image_initial_bounds_for_cancel_animation_,
+ drag_image_final_bounds_for_cancel_animation_);
+ drag_image_->SetBoundsInScreen(current_bounds);
+}
+
+void DragDropController::AnimationCanceled(const ui::Animation* animation) {
+ AnimationEnded(animation);
+}
+
+void DragDropController::StartCanceledAnimation(int animation_duration_ms) {
+ DCHECK(drag_image_.get());
+ drag_image_initial_bounds_for_cancel_animation_ =
+ drag_image_->GetBoundsInScreen();
+ cancel_animation_.reset(CreateCancelAnimation(animation_duration_ms,
+ kCancelAnimationFrameRate,
+ this));
+ cancel_animation_->Start();
+}
+
+void DragDropController::ForwardPendingLongTap() {
+ if (drag_source_window_ && drag_source_window_->delegate()) {
+ drag_source_window_->delegate()->OnGestureEvent(pending_long_tap_.get());
+ DispatchGestureEndToWindow(drag_source_window_);
+ }
+ pending_long_tap_.reset();
+ if (drag_source_window_)
+ drag_source_window_->RemoveObserver(this);
+ drag_source_window_ = NULL;
+}
+
+void DragDropController::Cleanup() {
+ if (drag_window_)
+ drag_window_->RemoveObserver(this);
+ drag_window_ = NULL;
+ drag_data_ = NULL;
+ // Cleanup can be called again while deleting DragDropTracker, so use Pass
+ // instead of reset to avoid double free.
+ drag_drop_tracker_.Pass();
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/drag_drop/drag_drop_controller.h b/chromium/ash/drag_drop/drag_drop_controller.h
new file mode 100644
index 00000000000..a23ed913f2a
--- /dev/null
+++ b/chromium/ash/drag_drop/drag_drop_controller.h
@@ -0,0 +1,147 @@
+// Copyright (c) 2012 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 ASH_DRAG_DROP_DRAG_DROP_CONTROLLER_H_
+#define ASH_DRAG_DROP_DRAG_DROP_CONTROLLER_H_
+
+#include "ash/ash_export.h"
+#include "base/callback.h"
+#include "base/memory/weak_ptr.h"
+#include "ui/aura/client/drag_drop_client.h"
+#include "ui/aura/window_observer.h"
+#include "ui/base/animation/animation_delegate.h"
+#include "ui/base/dragdrop/os_exchange_data.h"
+#include "ui/base/events/event_constants.h"
+#include "ui/base/events/event_handler.h"
+#include "ui/gfx/rect.h"
+
+namespace aura {
+class RootWindow;
+class Window;
+}
+
+namespace ui {
+class LinearAnimation;
+}
+
+namespace ash {
+
+namespace test {
+class DragDropControllerTest;
+}
+
+namespace internal {
+
+class DragDropTracker;
+class DragDropTrackerDelegate;
+class DragImageView;
+
+class ASH_EXPORT DragDropController
+ : public aura::client::DragDropClient,
+ public ui::EventHandler,
+ public ui::AnimationDelegate,
+ public aura::WindowObserver {
+ public:
+ DragDropController();
+ virtual ~DragDropController();
+
+ void set_should_block_during_drag_drop(bool should_block_during_drag_drop) {
+ should_block_during_drag_drop_ = should_block_during_drag_drop;
+ }
+
+ // Overridden from aura::client::DragDropClient:
+ virtual int StartDragAndDrop(
+ const ui::OSExchangeData& data,
+ aura::RootWindow* root_window,
+ aura::Window* source_window,
+ const gfx::Point& root_location,
+ int operation,
+ ui::DragDropTypes::DragEventSource source) OVERRIDE;
+ virtual void DragUpdate(aura::Window* target,
+ const ui::LocatedEvent& event) OVERRIDE;
+ virtual void Drop(aura::Window* target,
+ const ui::LocatedEvent& event) OVERRIDE;
+ virtual void DragCancel() OVERRIDE;
+ virtual bool IsDragDropInProgress() OVERRIDE;
+
+ // Overridden from ui::EventHandler:
+ virtual void OnKeyEvent(ui::KeyEvent* event) OVERRIDE;
+ virtual void OnMouseEvent(ui::MouseEvent* event) OVERRIDE;
+ virtual void OnTouchEvent(ui::TouchEvent* event) OVERRIDE;
+ virtual void OnGestureEvent(ui::GestureEvent* event) OVERRIDE;
+
+ // Overridden from aura::WindowObserver.
+ virtual void OnWindowDestroyed(aura::Window* window) OVERRIDE;
+
+ protected:
+ // Helper method to create a LinearAnimation object that will run the drag
+ // cancel animation. Caller take ownership of the returned object. Protected
+ // for testing.
+ virtual ui::LinearAnimation* CreateCancelAnimation(
+ int duration,
+ int frame_rate,
+ ui::AnimationDelegate* delegate);
+
+ // Actual implementation of |DragCancel()|. protected for testing.
+ virtual void DoDragCancel(int drag_cancel_animation_duration_ms);
+
+ private:
+ friend class ash::test::DragDropControllerTest;
+
+ // Overridden from ui::AnimationDelegate:
+ virtual void AnimationEnded(const ui::Animation* animation) OVERRIDE;
+ virtual void AnimationProgressed(const ui::Animation* animation) OVERRIDE;
+ virtual void AnimationCanceled(const ui::Animation* animation) OVERRIDE;
+
+ // Helper method to start drag widget flying back animation.
+ void StartCanceledAnimation(int animation_duration_ms);
+
+ // Helper method to forward |pending_log_tap_| event to |drag_source_window_|.
+ void ForwardPendingLongTap();
+
+ // Helper method to reset everything.
+ void Cleanup();
+
+ scoped_ptr<DragImageView> drag_image_;
+ gfx::Vector2d drag_image_offset_;
+ const ui::OSExchangeData* drag_data_;
+ int drag_operation_;
+
+ // Window that is currently under the drag cursor.
+ aura::Window* drag_window_;
+
+ // Starting and final bounds for the drag image for the drag cancel animation.
+ gfx::Rect drag_image_initial_bounds_for_cancel_animation_;
+ gfx::Rect drag_image_final_bounds_for_cancel_animation_;
+
+ scoped_ptr<ui::LinearAnimation> cancel_animation_;
+
+ // Window that started the drag.
+ aura::Window* drag_source_window_;
+
+ // Indicates whether the caller should be blocked on a drag/drop session.
+ // Only be used for tests.
+ bool should_block_during_drag_drop_;
+
+ // Closure for quitting nested message loop.
+ base::Closure quit_closure_;
+
+ scoped_ptr<ash::internal::DragDropTracker> drag_drop_tracker_;
+ scoped_ptr<DragDropTrackerDelegate> drag_drop_window_delegate_;
+
+ ui::DragDropTypes::DragEventSource current_drag_event_source_;
+
+ // Holds a synthetic long tap event to be sent to the |drag_source_window_|.
+ // See comment in OnGestureEvent() on why we need this.
+ scoped_ptr<ui::GestureEvent> pending_long_tap_;
+
+ base::WeakPtrFactory<DragDropController> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(DragDropController);
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_DRAG_DROP_DRAG_DROP_CONTROLLER_H_
diff --git a/chromium/ash/drag_drop/drag_drop_controller_unittest.cc b/chromium/ash/drag_drop/drag_drop_controller_unittest.cc
new file mode 100644
index 00000000000..7ca79ea0a05
--- /dev/null
+++ b/chromium/ash/drag_drop/drag_drop_controller_unittest.cc
@@ -0,0 +1,1108 @@
+// Copyright (c) 2012 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.
+
+#include "ash/drag_drop/drag_drop_controller.h"
+
+#include "ash/drag_drop/drag_drop_tracker.h"
+#include "ash/drag_drop/drag_image_view.h"
+#include "ash/shell.h"
+#include "ash/test/ash_test_base.h"
+#include "base/command_line.h"
+#include "base/location.h"
+#include "base/strings/utf_string_conversions.h"
+#include "ui/aura/client/capture_client.h"
+#include "ui/aura/root_window.h"
+#include "ui/aura/test/event_generator.h"
+#include "ui/base/animation/linear_animation.h"
+#include "ui/base/clipboard/clipboard.h"
+#include "ui/base/clipboard/scoped_clipboard_writer.h"
+#include "ui/base/dragdrop/drag_drop_types.h"
+#include "ui/base/dragdrop/drag_utils.h"
+#include "ui/base/dragdrop/os_exchange_data.h"
+#include "ui/base/events/event.h"
+#include "ui/base/events/event_utils.h"
+#include "ui/base/gestures/gesture_types.h"
+#include "ui/base/ui_base_switches.h"
+#include "ui/gfx/image/image_skia_rep.h"
+#include "ui/views/test/test_views_delegate.h"
+#include "ui/views/view.h"
+#include "ui/views/views_delegate.h"
+#include "ui/views/widget/native_widget_aura.h"
+#include "ui/views/widget/native_widget_delegate.h"
+#include "ui/views/widget/widget.h"
+
+namespace ash {
+namespace test {
+
+namespace {
+
+// A simple view that makes sure RunShellDrag is invoked on mouse drag.
+class DragTestView : public views::View {
+ public:
+ DragTestView() : views::View() {
+ Reset();
+ }
+
+ void Reset() {
+ num_drag_enters_ = 0;
+ num_drag_exits_ = 0;
+ num_drag_updates_ = 0;
+ num_drops_ = 0;
+ drag_done_received_ = false;
+ long_tap_received_ = false;
+ }
+
+ int VerticalDragThreshold() {
+ return views::View::GetVerticalDragThreshold();
+ }
+
+ int HorizontalDragThreshold() {
+ return views::View::GetHorizontalDragThreshold();
+ }
+
+ int num_drag_enters_;
+ int num_drag_exits_;
+ int num_drag_updates_;
+ int num_drops_;
+ bool drag_done_received_;
+ bool long_tap_received_;
+
+ private:
+ // View overrides:
+ virtual int GetDragOperations(const gfx::Point& press_pt) OVERRIDE {
+ return ui::DragDropTypes::DRAG_COPY;
+ }
+
+ virtual void WriteDragData(const gfx::Point& p,
+ OSExchangeData* data) OVERRIDE {
+ data->SetString(UTF8ToUTF16("I am being dragged"));
+ gfx::ImageSkiaRep image_rep(gfx::Size(10, 20), ui::SCALE_FACTOR_100P);
+ gfx::ImageSkia image_skia(image_rep);
+
+ drag_utils::SetDragImageOnDataObject(
+ image_skia, image_skia.size(), gfx::Vector2d(), data);
+ }
+
+ virtual bool OnMousePressed(const ui::MouseEvent& event) OVERRIDE {
+ return true;
+ }
+
+ virtual void OnGestureEvent(ui::GestureEvent* event) OVERRIDE {
+ if (event->type() == ui::ET_GESTURE_LONG_TAP)
+ long_tap_received_ = true;
+ return;
+ }
+
+ virtual bool GetDropFormats(
+ int* formats,
+ std::set<OSExchangeData::CustomFormat>* custom_formats) OVERRIDE {
+ *formats = ui::OSExchangeData::STRING;
+ return true;
+ }
+
+ virtual bool CanDrop(const OSExchangeData& data) OVERRIDE {
+ return true;
+ }
+
+ virtual void OnDragEntered(const ui::DropTargetEvent& event) OVERRIDE {
+ num_drag_enters_++;
+ }
+
+ virtual int OnDragUpdated(const ui::DropTargetEvent& event) OVERRIDE {
+ num_drag_updates_++;
+ return ui::DragDropTypes::DRAG_COPY;
+ }
+
+ virtual void OnDragExited() OVERRIDE {
+ num_drag_exits_++;
+ }
+
+ virtual int OnPerformDrop(const ui::DropTargetEvent& event) OVERRIDE {
+ num_drops_++;
+ return ui::DragDropTypes::DRAG_COPY;
+ }
+
+ virtual void OnDragDone() OVERRIDE {
+ drag_done_received_ = true;
+ }
+
+ DISALLOW_COPY_AND_ASSIGN(DragTestView);
+};
+
+class CompletableLinearAnimation : public ui::LinearAnimation {
+ public:
+ CompletableLinearAnimation(int duration,
+ int frame_rate,
+ ui::AnimationDelegate* delegate)
+ : ui::LinearAnimation(duration, frame_rate, delegate),
+ duration_(duration) {
+ }
+
+ void Complete() {
+ Step(start_time() + base::TimeDelta::FromMilliseconds(duration_));
+ }
+
+ private:
+ int duration_;
+};
+
+class TestDragDropController : public internal::DragDropController {
+ public:
+ TestDragDropController() : internal::DragDropController() {
+ Reset();
+ }
+
+ void Reset() {
+ drag_start_received_ = false;
+ num_drag_updates_ = 0;
+ drop_received_ = false;
+ drag_canceled_ = false;
+ drag_string_.clear();
+ }
+
+ virtual int StartDragAndDrop(
+ const ui::OSExchangeData& data,
+ aura::RootWindow* root_window,
+ aura::Window* source_window,
+ const gfx::Point& location,
+ int operation,
+ ui::DragDropTypes::DragEventSource source) OVERRIDE {
+ drag_start_received_ = true;
+ data.GetString(&drag_string_);
+ return DragDropController::StartDragAndDrop(
+ data, root_window, source_window, location, operation, source);
+ }
+
+ virtual void DragUpdate(aura::Window* target,
+ const ui::LocatedEvent& event) OVERRIDE {
+ DragDropController::DragUpdate(target, event);
+ num_drag_updates_++;
+ }
+
+ virtual void Drop(aura::Window* target,
+ const ui::LocatedEvent& event) OVERRIDE {
+ DragDropController::Drop(target, event);
+ drop_received_ = true;
+ }
+
+ virtual void DragCancel() OVERRIDE {
+ DragDropController::DragCancel();
+ drag_canceled_ = true;
+ }
+
+ virtual ui::LinearAnimation* CreateCancelAnimation(
+ int duration,
+ int frame_rate,
+ ui::AnimationDelegate* delegate) OVERRIDE {
+ return new CompletableLinearAnimation(duration, frame_rate, delegate);
+ }
+
+ virtual void DoDragCancel(int animation_duration_ms) OVERRIDE {
+ DragDropController::DoDragCancel(animation_duration_ms);
+ drag_canceled_ = true;
+ }
+
+ bool drag_start_received_;
+ int num_drag_updates_;
+ bool drop_received_;
+ bool drag_canceled_;
+ base::string16 drag_string_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(TestDragDropController);
+};
+
+class TestNativeWidgetAura : public views::NativeWidgetAura {
+ public:
+ explicit TestNativeWidgetAura(views::internal::NativeWidgetDelegate* delegate)
+ : NativeWidgetAura(delegate),
+ check_if_capture_lost_(false) {
+ }
+
+ void set_check_if_capture_lost(bool value) {
+ check_if_capture_lost_ = value;
+ }
+
+ virtual void OnCaptureLost() OVERRIDE {
+ DCHECK(!check_if_capture_lost_);
+ views::NativeWidgetAura::OnCaptureLost();
+ }
+
+ private:
+ bool check_if_capture_lost_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestNativeWidgetAura);
+};
+
+// TODO(sky): this is for debugging, remove when track down failure.
+void SetCheckIfCaptureLost(views::Widget* widget, bool value) {
+ // On Windows, the DCHECK triggers when running on bot or locally through RDP,
+ // but not when logged in locally.
+#if !defined(OS_WIN)
+ static_cast<TestNativeWidgetAura*>(widget->native_widget())->
+ set_check_if_capture_lost(value);
+#endif
+}
+
+views::Widget* CreateNewWidget() {
+ views::Widget* widget = new views::Widget;
+ views::Widget::InitParams params;
+ params.type = views::Widget::InitParams::TYPE_WINDOW_FRAMELESS;
+ params.accept_events = true;
+ params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
+ params.parent = Shell::GetPrimaryRootWindow();
+ params.child = true;
+ params.native_widget = new TestNativeWidgetAura(widget);
+ widget->Init(params);
+ widget->Show();
+ return widget;
+}
+
+void AddViewToWidgetAndResize(views::Widget* widget, views::View* view) {
+ if (!widget->GetContentsView()) {
+ views::View* contents_view = new views::View;
+ widget->SetContentsView(contents_view);
+ }
+
+ views::View* contents_view = widget->GetContentsView();
+ contents_view->AddChildView(view);
+ view->SetBounds(contents_view->width(), 0, 100, 100);
+ gfx::Rect contents_view_bounds = contents_view->bounds();
+ contents_view_bounds.Union(view->bounds());
+ contents_view->SetBoundsRect(contents_view_bounds);
+ widget->SetBounds(contents_view_bounds);
+}
+
+void DispatchGesture(ui::EventType gesture_type, gfx::Point location) {
+ ui::GestureEvent gesture_event(
+ gesture_type,
+ location.x(),
+ location.y(),
+ 0,
+ ui::EventTimeForNow(),
+ ui::GestureEventDetails(gesture_type, 0, 0),
+ 1);
+ Shell::GetPrimaryRootWindow()->DispatchGestureEvent(&gesture_event);
+}
+
+bool IsGestureEventType(ui::EventType type) {
+ switch (type) {
+ case ui::ET_GESTURE_SCROLL_BEGIN:
+ case ui::ET_GESTURE_SCROLL_END:
+ case ui::ET_GESTURE_SCROLL_UPDATE:
+ case ui::ET_GESTURE_TAP:
+ case ui::ET_GESTURE_TAP_CANCEL:
+ case ui::ET_GESTURE_TAP_DOWN:
+ case ui::ET_GESTURE_BEGIN:
+ case ui::ET_GESTURE_END:
+ case ui::ET_GESTURE_TWO_FINGER_TAP:
+ case ui::ET_GESTURE_PINCH_BEGIN:
+ case ui::ET_GESTURE_PINCH_END:
+ case ui::ET_GESTURE_PINCH_UPDATE:
+ case ui::ET_GESTURE_LONG_PRESS:
+ case ui::ET_GESTURE_LONG_TAP:
+ case ui::ET_GESTURE_MULTIFINGER_SWIPE:
+ case ui::ET_SCROLL_FLING_CANCEL:
+ case ui::ET_SCROLL_FLING_START:
+ return true;
+ default:
+ break;
+ }
+ return false;
+}
+
+} // namespace
+
+class DragDropControllerTest : public AshTestBase {
+ public:
+ DragDropControllerTest() : AshTestBase() {}
+ virtual ~DragDropControllerTest() {}
+
+ virtual void SetUp() OVERRIDE {
+ AshTestBase::SetUp();
+ drag_drop_controller_.reset(new TestDragDropController);
+ drag_drop_controller_->set_should_block_during_drag_drop(false);
+ aura::client::SetDragDropClient(Shell::GetPrimaryRootWindow(),
+ drag_drop_controller_.get());
+ views_delegate_.reset(new views::TestViewsDelegate);
+ }
+
+ virtual void TearDown() OVERRIDE {
+ aura::client::SetDragDropClient(Shell::GetPrimaryRootWindow(), NULL);
+ drag_drop_controller_.reset();
+ AshTestBase::TearDown();
+ }
+
+ void UpdateDragData(ui::OSExchangeData* data) {
+ drag_drop_controller_->drag_data_ = data;
+ }
+
+ aura::Window* GetDragWindow() {
+ return drag_drop_controller_->drag_window_;
+ }
+
+ aura::Window* GetDragSourceWindow() {
+ return drag_drop_controller_->drag_source_window_;
+ }
+
+ void SetDragSourceWindow(aura::Window* drag_source_window) {
+ drag_drop_controller_->drag_source_window_ = drag_source_window;
+ drag_source_window->AddObserver(drag_drop_controller_.get());
+ }
+
+ aura::Window* GetDragImageWindow() {
+ return drag_drop_controller_->drag_image_.get() ?
+ drag_drop_controller_->drag_image_->GetWidget()->GetNativeWindow() :
+ NULL;
+ }
+
+ internal::DragDropTracker* drag_drop_tracker() {
+ return drag_drop_controller_->drag_drop_tracker_.get();
+ }
+
+ void CompleteCancelAnimation() {
+ CompletableLinearAnimation* animation =
+ static_cast<CompletableLinearAnimation*>(
+ drag_drop_controller_->cancel_animation_.get());
+ animation->Complete();
+ }
+
+ protected:
+ scoped_ptr<TestDragDropController> drag_drop_controller_;
+ scoped_ptr<views::TestViewsDelegate> views_delegate_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(DragDropControllerTest);
+};
+
+// TODO(win_aura) http://crbug.com/154081
+#if defined(OS_WIN)
+#define MAYBE_DragDropInSingleViewTest DISABLED_DragDropInSingleViewTest
+#else
+#define MAYBE_DragDropInSingleViewTest DragDropInSingleViewTest
+#endif
+TEST_F(DragDropControllerTest, MAYBE_DragDropInSingleViewTest) {
+ scoped_ptr<views::Widget> widget(CreateNewWidget());
+ DragTestView* drag_view = new DragTestView;
+ AddViewToWidgetAndResize(widget.get(), drag_view);
+ ui::OSExchangeData data;
+ data.SetString(UTF8ToUTF16("I am being dragged"));
+ aura::test::EventGenerator generator(Shell::GetPrimaryRootWindow(),
+ widget->GetNativeView());
+ generator.PressLeftButton();
+
+ int num_drags = 17;
+ SetCheckIfCaptureLost(widget.get(), true);
+ for (int i = 0; i < num_drags; ++i) {
+ // Because we are not doing a blocking drag and drop, the original
+ // OSDragExchangeData object is lost as soon as we return from the drag
+ // initiation in DragDropController::StartDragAndDrop(). Hence we set the
+ // drag_data_ to a fake drag data object that we created.
+ if (i > 0)
+ UpdateDragData(&data);
+ // 7 comes from views::View::GetVerticalDragThreshold()).
+ if (i >= 7)
+ SetCheckIfCaptureLost(widget.get(), false);
+
+ generator.MoveMouseBy(0, 1);
+
+ // Execute any scheduled draws to process deferred mouse events.
+ RunAllPendingInMessageLoop();
+ }
+
+ generator.ReleaseLeftButton();
+
+ EXPECT_TRUE(drag_drop_controller_->drag_start_received_);
+ EXPECT_EQ(num_drags - 1 - drag_view->VerticalDragThreshold(),
+ drag_drop_controller_->num_drag_updates_);
+ EXPECT_TRUE(drag_drop_controller_->drop_received_);
+ EXPECT_EQ(UTF8ToUTF16("I am being dragged"),
+ drag_drop_controller_->drag_string_);
+
+ EXPECT_EQ(1, drag_view->num_drag_enters_);
+ EXPECT_EQ(num_drags - 1 - drag_view->VerticalDragThreshold(),
+ drag_view->num_drag_updates_);
+ EXPECT_EQ(1, drag_view->num_drops_);
+ EXPECT_EQ(0, drag_view->num_drag_exits_);
+ EXPECT_TRUE(drag_view->drag_done_received_);
+}
+
+TEST_F(DragDropControllerTest, DragDropWithZeroDragUpdates) {
+ scoped_ptr<views::Widget> widget(CreateNewWidget());
+ DragTestView* drag_view = new DragTestView;
+ AddViewToWidgetAndResize(widget.get(), drag_view);
+ ui::OSExchangeData data;
+ data.SetString(UTF8ToUTF16("I am being dragged"));
+ aura::test::EventGenerator generator(Shell::GetPrimaryRootWindow(),
+ widget->GetNativeView());
+ generator.PressLeftButton();
+
+ int num_drags = drag_view->VerticalDragThreshold() + 1;
+ for (int i = 0; i < num_drags; ++i) {
+ // Because we are not doing a blocking drag and drop, the original
+ // OSDragExchangeData object is lost as soon as we return from the drag
+ // initiation in DragDropController::StartDragAndDrop(). Hence we set the
+ // drag_data_ to a fake drag data object that we created.
+ if (i > 0)
+ UpdateDragData(&data);
+ generator.MoveMouseBy(0, 1);
+ }
+
+ UpdateDragData(&data);
+
+ generator.ReleaseLeftButton();
+
+ EXPECT_TRUE(drag_drop_controller_->drag_start_received_);
+ EXPECT_EQ(num_drags - 1 - drag_view->VerticalDragThreshold() + 1,
+ drag_drop_controller_->num_drag_updates_);
+ EXPECT_TRUE(drag_drop_controller_->drop_received_);
+
+ EXPECT_EQ(1, drag_view->num_drag_enters_);
+ EXPECT_EQ(num_drags - 1 - drag_view->VerticalDragThreshold() + 1,
+ drag_view->num_drag_updates_);
+ EXPECT_EQ(1, drag_view->num_drops_);
+ EXPECT_EQ(0, drag_view->num_drag_exits_);
+ EXPECT_TRUE(drag_view->drag_done_received_);
+}
+
+// TODO(win_aura) http://crbug.com/154081
+#if defined(OS_WIN)
+#define MAYBE_DragDropInMultipleViewsSingleWidgetTest DISABLED_DragDropInMultipleViewsSingleWidgetTest
+#else
+#define MAYBE_DragDropInMultipleViewsSingleWidgetTest DragDropInMultipleViewsSingleWidgetTest
+#endif
+TEST_F(DragDropControllerTest, MAYBE_DragDropInMultipleViewsSingleWidgetTest) {
+ scoped_ptr<views::Widget> widget(CreateNewWidget());
+ DragTestView* drag_view1 = new DragTestView;
+ AddViewToWidgetAndResize(widget.get(), drag_view1);
+ DragTestView* drag_view2 = new DragTestView;
+ AddViewToWidgetAndResize(widget.get(), drag_view2);
+
+ ui::OSExchangeData data;
+ data.SetString(UTF8ToUTF16("I am being dragged"));
+
+ aura::test::EventGenerator generator(Shell::GetPrimaryRootWindow());
+ generator.MoveMouseRelativeTo(widget->GetNativeView(),
+ drag_view1->bounds().CenterPoint());
+ generator.PressLeftButton();
+
+ int num_drags = drag_view1->width();
+ for (int i = 0; i < num_drags; ++i) {
+ // Because we are not doing a blocking drag and drop, the original
+ // OSDragExchangeData object is lost as soon as we return from the drag
+ // initiation in DragDropController::StartDragAndDrop(). Hence we set the
+ // drag_data_ to a fake drag data object that we created.
+ if (i > 0)
+ UpdateDragData(&data);
+ generator.MoveMouseBy(1, 0);
+
+ // Execute any scheduled draws to process deferred mouse events.
+ RunAllPendingInMessageLoop();
+ }
+
+ generator.ReleaseLeftButton();
+
+ EXPECT_TRUE(drag_drop_controller_->drag_start_received_);
+ EXPECT_EQ(num_drags - 1 - drag_view1->HorizontalDragThreshold(),
+ drag_drop_controller_->num_drag_updates_);
+ EXPECT_TRUE(drag_drop_controller_->drop_received_);
+ EXPECT_EQ(UTF8ToUTF16("I am being dragged"),
+ drag_drop_controller_->drag_string_);
+
+ EXPECT_EQ(1, drag_view1->num_drag_enters_);
+ int num_expected_updates = drag_view1->bounds().width() -
+ drag_view1->bounds().CenterPoint().x() - 2;
+ EXPECT_EQ(num_expected_updates - drag_view1->HorizontalDragThreshold(),
+ drag_view1->num_drag_updates_);
+ EXPECT_EQ(0, drag_view1->num_drops_);
+ EXPECT_EQ(1, drag_view1->num_drag_exits_);
+ EXPECT_TRUE(drag_view1->drag_done_received_);
+
+ EXPECT_EQ(1, drag_view2->num_drag_enters_);
+ num_expected_updates = num_drags - num_expected_updates - 1;
+ EXPECT_EQ(num_expected_updates, drag_view2->num_drag_updates_);
+ EXPECT_EQ(1, drag_view2->num_drops_);
+ EXPECT_EQ(0, drag_view2->num_drag_exits_);
+ EXPECT_FALSE(drag_view2->drag_done_received_);
+}
+
+// TODO(win_aura) http://crbug.com/154081
+#if defined(OS_WIN)
+#define MAYBE_DragDropInMultipleViewsMultipleWidgetsTest DISABLED_DragDropInMultipleViewsMultipleWidgetsTest
+#else
+#define MAYBE_DragDropInMultipleViewsMultipleWidgetsTest DragDropInMultipleViewsMultipleWidgetsTest
+#endif
+TEST_F(DragDropControllerTest, MAYBE_DragDropInMultipleViewsMultipleWidgetsTest) {
+ scoped_ptr<views::Widget> widget1(CreateNewWidget());
+ DragTestView* drag_view1 = new DragTestView;
+ AddViewToWidgetAndResize(widget1.get(), drag_view1);
+ scoped_ptr<views::Widget> widget2(CreateNewWidget());
+ DragTestView* drag_view2 = new DragTestView;
+ AddViewToWidgetAndResize(widget2.get(), drag_view2);
+ gfx::Rect widget1_bounds = widget1->GetClientAreaBoundsInScreen();
+ gfx::Rect widget2_bounds = widget2->GetClientAreaBoundsInScreen();
+ widget2->SetBounds(gfx::Rect(widget1_bounds.width(), 0,
+ widget2_bounds.width(), widget2_bounds.height()));
+
+ ui::OSExchangeData data;
+ data.SetString(UTF8ToUTF16("I am being dragged"));
+
+ aura::test::EventGenerator generator(Shell::GetPrimaryRootWindow(),
+ widget1->GetNativeView());
+ generator.PressLeftButton();
+
+ int num_drags = drag_view1->width();
+ for (int i = 0; i < num_drags; ++i) {
+ // Because we are not doing a blocking drag and drop, the original
+ // OSDragExchangeData object is lost as soon as we return from the drag
+ // initiation in DragDropController::StartDragAndDrop(). Hence we set the
+ // drag_data_ to a fake drag data object that we created.
+ if (i > 0)
+ UpdateDragData(&data);
+ generator.MoveMouseBy(1, 0);
+
+ // Execute any scheduled draws to process deferred mouse events.
+ RunAllPendingInMessageLoop();
+ }
+
+ generator.ReleaseLeftButton();
+
+ EXPECT_TRUE(drag_drop_controller_->drag_start_received_);
+ EXPECT_EQ(num_drags - 1 - drag_view1->HorizontalDragThreshold(),
+ drag_drop_controller_->num_drag_updates_);
+ EXPECT_TRUE(drag_drop_controller_->drop_received_);
+ EXPECT_EQ(UTF8ToUTF16("I am being dragged"),
+ drag_drop_controller_->drag_string_);
+
+ EXPECT_EQ(1, drag_view1->num_drag_enters_);
+ int num_expected_updates = drag_view1->bounds().width() -
+ drag_view1->bounds().CenterPoint().x() - 2;
+ EXPECT_EQ(num_expected_updates - drag_view1->HorizontalDragThreshold(),
+ drag_view1->num_drag_updates_);
+ EXPECT_EQ(0, drag_view1->num_drops_);
+ EXPECT_EQ(1, drag_view1->num_drag_exits_);
+ EXPECT_TRUE(drag_view1->drag_done_received_);
+
+ EXPECT_EQ(1, drag_view2->num_drag_enters_);
+ num_expected_updates = num_drags - num_expected_updates - 1;
+ EXPECT_EQ(num_expected_updates, drag_view2->num_drag_updates_);
+ EXPECT_EQ(1, drag_view2->num_drops_);
+ EXPECT_EQ(0, drag_view2->num_drag_exits_);
+ EXPECT_FALSE(drag_view2->drag_done_received_);
+}
+
+// TODO(win_aura) http://crbug.com/154081
+#if defined(OS_WIN)
+#define MAYBE_ViewRemovedWhileInDragDropTest DISABLED_ViewRemovedWhileInDragDropTest
+#else
+#define MAYBE_ViewRemovedWhileInDragDropTest ViewRemovedWhileInDragDropTest
+#endif
+TEST_F(DragDropControllerTest, MAYBE_ViewRemovedWhileInDragDropTest) {
+ scoped_ptr<views::Widget> widget(CreateNewWidget());
+ scoped_ptr<DragTestView> drag_view(new DragTestView);
+ AddViewToWidgetAndResize(widget.get(), drag_view.get());
+ gfx::Point point = gfx::Rect(drag_view->bounds()).CenterPoint();
+ ui::OSExchangeData data;
+ data.SetString(UTF8ToUTF16("I am being dragged"));
+
+ aura::test::EventGenerator generator(Shell::GetPrimaryRootWindow());
+ generator.MoveMouseToCenterOf(widget->GetNativeView());
+ generator.PressLeftButton();
+
+ int num_drags_1 = 17;
+ for (int i = 0; i < num_drags_1; ++i) {
+ // Because we are not doing a blocking drag and drop, the original
+ // OSDragExchangeData object is lost as soon as we return from the drag
+ // initiation in DragDropController::StartDragAndDrop(). Hence we set the
+ // drag_data_ to a fake drag data object that we created.
+ if (i > 0)
+ UpdateDragData(&data);
+ generator.MoveMouseBy(0, 1);
+
+ // Execute any scheduled draws to process deferred mouse events.
+ RunAllPendingInMessageLoop();
+ }
+
+ drag_view->parent()->RemoveChildView(drag_view.get());
+ // View has been removed. We will not get any of the following drag updates.
+ int num_drags_2 = 23;
+ for (int i = 0; i < num_drags_2; ++i) {
+ UpdateDragData(&data);
+ generator.MoveMouseBy(0, 1);
+
+ // Execute any scheduled draws to process deferred mouse events.
+ RunAllPendingInMessageLoop();
+ }
+
+ generator.ReleaseLeftButton();
+
+ EXPECT_TRUE(drag_drop_controller_->drag_start_received_);
+ EXPECT_EQ(num_drags_1 + num_drags_2 - 1 - drag_view->VerticalDragThreshold(),
+ drag_drop_controller_->num_drag_updates_);
+ EXPECT_TRUE(drag_drop_controller_->drop_received_);
+ EXPECT_EQ(UTF8ToUTF16("I am being dragged"),
+ drag_drop_controller_->drag_string_);
+
+ EXPECT_EQ(1, drag_view->num_drag_enters_);
+ EXPECT_EQ(num_drags_1 - 1 - drag_view->VerticalDragThreshold(),
+ drag_view->num_drag_updates_);
+ EXPECT_EQ(0, drag_view->num_drops_);
+ EXPECT_EQ(0, drag_view->num_drag_exits_);
+ EXPECT_TRUE(drag_view->drag_done_received_);
+}
+
+TEST_F(DragDropControllerTest, DragLeavesClipboardAloneTest) {
+ ui::Clipboard* cb = ui::Clipboard::GetForCurrentThread();
+ std::string clip_str("I am on the clipboard");
+ {
+ // We first copy some text to the clipboard.
+ ui::ScopedClipboardWriter scw(cb, ui::Clipboard::BUFFER_STANDARD);
+ scw.WriteText(ASCIIToUTF16(clip_str));
+ }
+ EXPECT_TRUE(cb->IsFormatAvailable(ui::Clipboard::GetPlainTextFormatType(),
+ ui::Clipboard::BUFFER_STANDARD));
+
+ scoped_ptr<views::Widget> widget(CreateNewWidget());
+ DragTestView* drag_view = new DragTestView;
+ AddViewToWidgetAndResize(widget.get(), drag_view);
+
+ aura::test::EventGenerator generator(Shell::GetPrimaryRootWindow(),
+ widget->GetNativeView());
+ ui::OSExchangeData data;
+ std::string data_str("I am being dragged");
+ data.SetString(ASCIIToUTF16(data_str));
+
+ generator.PressLeftButton();
+ generator.MoveMouseBy(0, drag_view->VerticalDragThreshold() + 1);
+
+ // Execute any scheduled draws to process deferred mouse events.
+ RunAllPendingInMessageLoop();
+
+ // Verify the clipboard contents haven't changed
+ std::string result;
+ EXPECT_TRUE(cb->IsFormatAvailable(ui::Clipboard::GetPlainTextFormatType(),
+ ui::Clipboard::BUFFER_STANDARD));
+ cb->ReadAsciiText(ui::Clipboard::BUFFER_STANDARD, &result);
+ EXPECT_EQ(clip_str, result);
+ // Destory the clipboard here because ash doesn't delete it.
+ // crbug.com/158150.
+ ui::Clipboard::DestroyClipboardForCurrentThread();
+}
+
+TEST_F(DragDropControllerTest, WindowDestroyedDuringDragDrop) {
+ scoped_ptr<views::Widget> widget(CreateNewWidget());
+ DragTestView* drag_view = new DragTestView;
+ AddViewToWidgetAndResize(widget.get(), drag_view);
+ aura::Window* window = widget->GetNativeView();
+
+ ui::OSExchangeData data;
+ data.SetString(UTF8ToUTF16("I am being dragged"));
+ aura::test::EventGenerator generator(Shell::GetPrimaryRootWindow(),
+ widget->GetNativeView());
+ generator.PressLeftButton();
+
+ int num_drags = 17;
+ for (int i = 0; i < num_drags; ++i) {
+ // Because we are not doing a blocking drag and drop, the original
+ // OSDragExchangeData object is lost as soon as we return from the drag
+ // initiation in DragDropController::StartDragAndDrop(). Hence we set the
+ // drag_data_ to a fake drag data object that we created.
+ if (i > 0)
+ UpdateDragData(&data);
+ generator.MoveMouseBy(0, 1);
+
+ // Execute any scheduled draws to process deferred mouse events.
+ RunAllPendingInMessageLoop();
+
+ if (i > drag_view->VerticalDragThreshold())
+ EXPECT_EQ(window, GetDragWindow());
+ }
+
+ widget->CloseNow();
+ EXPECT_FALSE(GetDragWindow());
+
+ num_drags = 23;
+ for (int i = 0; i < num_drags; ++i) {
+ if (i > 0)
+ UpdateDragData(&data);
+ generator.MoveMouseBy(0, 1);
+ // We should not crash here.
+ }
+
+ generator.ReleaseLeftButton();
+
+ EXPECT_TRUE(drag_drop_controller_->drag_start_received_);
+ EXPECT_TRUE(drag_drop_controller_->drop_received_);
+}
+
+TEST_F(DragDropControllerTest, SyntheticEventsDuringDragDrop) {
+ scoped_ptr<views::Widget> widget(CreateNewWidget());
+ DragTestView* drag_view = new DragTestView;
+ AddViewToWidgetAndResize(widget.get(), drag_view);
+ ui::OSExchangeData data;
+ data.SetString(UTF8ToUTF16("I am being dragged"));
+ aura::test::EventGenerator generator(Shell::GetPrimaryRootWindow(),
+ widget->GetNativeView());
+ generator.PressLeftButton();
+
+ int num_drags = 17;
+ for (int i = 0; i < num_drags; ++i) {
+ // Because we are not doing a blocking drag and drop, the original
+ // OSDragExchangeData object is lost as soon as we return from the drag
+ // initiation in DragDropController::StartDragAndDrop(). Hence we set the
+ // drag_data_ to a fake drag data object that we created.
+ if (i > 0)
+ UpdateDragData(&data);
+ generator.MoveMouseBy(0, 1);
+
+ // We send a unexpected mouse move event. Note that we cannot use
+ // EventGenerator since it implicitly turns these into mouse drag events.
+ // The DragDropController should simply ignore these events.
+ gfx::Point mouse_move_location = drag_view->bounds().CenterPoint();
+ ui::MouseEvent mouse_move(ui::ET_MOUSE_MOVED, mouse_move_location,
+ mouse_move_location, 0);
+ Shell::GetPrimaryRootWindow()->AsRootWindowHostDelegate()->OnHostMouseEvent(
+ &mouse_move);
+ }
+
+ generator.ReleaseLeftButton();
+
+ EXPECT_TRUE(drag_drop_controller_->drag_start_received_);
+ EXPECT_EQ(num_drags - 1 - drag_view->VerticalDragThreshold(),
+ drag_drop_controller_->num_drag_updates_);
+ EXPECT_TRUE(drag_drop_controller_->drop_received_);
+ EXPECT_EQ(UTF8ToUTF16("I am being dragged"),
+ drag_drop_controller_->drag_string_);
+
+ EXPECT_EQ(1, drag_view->num_drag_enters_);
+ EXPECT_EQ(num_drags - 1 - drag_view->VerticalDragThreshold(),
+ drag_view->num_drag_updates_);
+ EXPECT_EQ(1, drag_view->num_drops_);
+ EXPECT_EQ(0, drag_view->num_drag_exits_);
+ EXPECT_TRUE(drag_view->drag_done_received_);
+}
+
+// TODO(win_aura) http://crbug.com/154081
+#if defined(OS_WIN)
+#define MAYBE_PressingEscapeCancelsDragDrop DISABLED_PressingEscapeCancelsDragDrop
+#define MAYBE_CaptureLostCancelsDragDrop DISABLED_CaptureLostCancelsDragDrop
+#else
+#define MAYBE_PressingEscapeCancelsDragDrop PressingEscapeCancelsDragDrop
+#define MAYBE_CaptureLostCancelsDragDrop CaptureLostCancelsDragDrop
+#endif
+TEST_F(DragDropControllerTest, MAYBE_PressingEscapeCancelsDragDrop) {
+ scoped_ptr<views::Widget> widget(CreateNewWidget());
+ DragTestView* drag_view = new DragTestView;
+ AddViewToWidgetAndResize(widget.get(), drag_view);
+ ui::OSExchangeData data;
+ data.SetString(UTF8ToUTF16("I am being dragged"));
+ aura::test::EventGenerator generator(Shell::GetPrimaryRootWindow(),
+ widget->GetNativeView());
+ generator.PressLeftButton();
+
+ int num_drags = 17;
+ for (int i = 0; i < num_drags; ++i) {
+ // Because we are not doing a blocking drag and drop, the original
+ // OSDragExchangeData object is lost as soon as we return from the drag
+ // initiation in DragDropController::StartDragAndDrop(). Hence we set the
+ // drag_data_ to a fake drag data object that we created.
+ if (i > 0)
+ UpdateDragData(&data);
+ generator.MoveMouseBy(0, 1);
+
+ // Execute any scheduled draws to process deferred mouse events.
+ RunAllPendingInMessageLoop();
+ }
+
+ generator.PressKey(ui::VKEY_ESCAPE, 0);
+
+ EXPECT_TRUE(drag_drop_controller_->drag_start_received_);
+ EXPECT_EQ(num_drags - 1 - drag_view->VerticalDragThreshold(),
+ drag_drop_controller_->num_drag_updates_);
+ EXPECT_FALSE(drag_drop_controller_->drop_received_);
+ EXPECT_TRUE(drag_drop_controller_->drag_canceled_);
+ EXPECT_EQ(UTF8ToUTF16("I am being dragged"),
+ drag_drop_controller_->drag_string_);
+
+ EXPECT_EQ(1, drag_view->num_drag_enters_);
+ EXPECT_EQ(num_drags - 1 - drag_view->VerticalDragThreshold(),
+ drag_view->num_drag_updates_);
+ EXPECT_EQ(0, drag_view->num_drops_);
+ EXPECT_EQ(1, drag_view->num_drag_exits_);
+ EXPECT_TRUE(drag_view->drag_done_received_);
+}
+
+TEST_F(DragDropControllerTest, MAYBE_CaptureLostCancelsDragDrop) {
+ scoped_ptr<views::Widget> widget(CreateNewWidget());
+ DragTestView* drag_view = new DragTestView;
+ AddViewToWidgetAndResize(widget.get(), drag_view);
+ ui::OSExchangeData data;
+ data.SetString(UTF8ToUTF16("I am being dragged"));
+ aura::test::EventGenerator generator(Shell::GetPrimaryRootWindow(),
+ widget->GetNativeView());
+ generator.PressLeftButton();
+
+ int num_drags = 17;
+ for (int i = 0; i < num_drags; ++i) {
+ // Because we are not doing a blocking drag and drop, the original
+ // OSDragExchangeData object is lost as soon as we return from the drag
+ // initiation in DragDropController::StartDragAndDrop(). Hence we set the
+ // drag_data_ to a fake drag data object that we created.
+ if (i > 0)
+ UpdateDragData(&data);
+ generator.MoveMouseBy(0, 1);
+
+ // Execute any scheduled draws to process deferred mouse events.
+ RunAllPendingInMessageLoop();
+ }
+ // Make sure the capture window won't handle mouse events.
+ aura::Window* capture_window = drag_drop_tracker()->capture_window();
+ ASSERT_TRUE(!!capture_window);
+ EXPECT_EQ("0x0", capture_window->bounds().size().ToString());
+ EXPECT_EQ(NULL,
+ capture_window->GetEventHandlerForPoint(gfx::Point()));
+ EXPECT_EQ(NULL,
+ capture_window->GetTopWindowContainingPoint(gfx::Point()));
+
+ aura::client::GetCaptureClient(widget->GetNativeView()->GetRootWindow())->
+ SetCapture(NULL);
+
+ EXPECT_TRUE(drag_drop_controller_->drag_start_received_);
+ EXPECT_EQ(num_drags - 1 - drag_view->VerticalDragThreshold(),
+ drag_drop_controller_->num_drag_updates_);
+ EXPECT_FALSE(drag_drop_controller_->drop_received_);
+ EXPECT_TRUE(drag_drop_controller_->drag_canceled_);
+ EXPECT_EQ(UTF8ToUTF16("I am being dragged"),
+ drag_drop_controller_->drag_string_);
+
+ EXPECT_EQ(1, drag_view->num_drag_enters_);
+ EXPECT_EQ(num_drags - 1 - drag_view->VerticalDragThreshold(),
+ drag_view->num_drag_updates_);
+ EXPECT_EQ(0, drag_view->num_drops_);
+ EXPECT_EQ(1, drag_view->num_drag_exits_);
+ EXPECT_TRUE(drag_view->drag_done_received_);
+}
+
+TEST_F(DragDropControllerTest, TouchDragDropInMultipleWindows) {
+ CommandLine::ForCurrentProcess()->AppendSwitch(
+ switches::kEnableTouchDragDrop);
+ scoped_ptr<views::Widget> widget1(CreateNewWidget());
+ DragTestView* drag_view1 = new DragTestView;
+ AddViewToWidgetAndResize(widget1.get(), drag_view1);
+ scoped_ptr<views::Widget> widget2(CreateNewWidget());
+ DragTestView* drag_view2 = new DragTestView;
+ AddViewToWidgetAndResize(widget2.get(), drag_view2);
+ gfx::Rect widget1_bounds = widget1->GetClientAreaBoundsInScreen();
+ gfx::Rect widget2_bounds = widget2->GetClientAreaBoundsInScreen();
+ widget2->SetBounds(gfx::Rect(widget1_bounds.width(), 0,
+ widget2_bounds.width(), widget2_bounds.height()));
+
+ ui::OSExchangeData data;
+ data.SetString(UTF8ToUTF16("I am being dragged"));
+
+ aura::test::EventGenerator generator(Shell::GetPrimaryRootWindow(),
+ widget1->GetNativeView());
+ generator.PressTouch();
+ gfx::Point point = gfx::Rect(drag_view1->bounds()).CenterPoint();
+ DispatchGesture(ui::ET_GESTURE_LONG_PRESS, point);
+ // Because we are not doing a blocking drag and drop, the original
+ // OSDragExchangeData object is lost as soon as we return from the drag
+ // initiation in DragDropController::StartDragAndDrop(). Hence we set the
+ // drag_data_ to a fake drag data object that we created.
+ UpdateDragData(&data);
+ gfx::Point gesture_location = point;
+ int num_drags = drag_view1->width();
+ for (int i = 0; i < num_drags; ++i) {
+ gesture_location.Offset(1, 0);
+ DispatchGesture(ui::ET_GESTURE_SCROLL_UPDATE, gesture_location);
+
+ // Execute any scheduled draws to process deferred mouse events.
+ RunAllPendingInMessageLoop();
+ }
+
+ DispatchGesture(ui::ET_GESTURE_SCROLL_END, gesture_location);
+
+ EXPECT_TRUE(drag_drop_controller_->drag_start_received_);
+ EXPECT_EQ(num_drags, drag_drop_controller_->num_drag_updates_);
+ EXPECT_TRUE(drag_drop_controller_->drop_received_);
+ EXPECT_EQ(UTF8ToUTF16("I am being dragged"),
+ drag_drop_controller_->drag_string_);
+
+ EXPECT_EQ(1, drag_view1->num_drag_enters_);
+ int num_expected_updates = drag_view1->bounds().width() -
+ drag_view1->bounds().CenterPoint().x() - 1;
+ EXPECT_EQ(num_expected_updates, drag_view1->num_drag_updates_);
+ EXPECT_EQ(0, drag_view1->num_drops_);
+ EXPECT_EQ(1, drag_view1->num_drag_exits_);
+ EXPECT_TRUE(drag_view1->drag_done_received_);
+
+ EXPECT_EQ(1, drag_view2->num_drag_enters_);
+ num_expected_updates = num_drags - num_expected_updates;
+ EXPECT_EQ(num_expected_updates, drag_view2->num_drag_updates_);
+ EXPECT_EQ(1, drag_view2->num_drops_);
+ EXPECT_EQ(0, drag_view2->num_drag_exits_);
+ EXPECT_FALSE(drag_view2->drag_done_received_);
+}
+
+TEST_F(DragDropControllerTest, TouchDragDropCancelsOnLongTap) {
+ CommandLine::ForCurrentProcess()->AppendSwitch(
+ switches::kEnableTouchDragDrop);
+ scoped_ptr<views::Widget> widget(CreateNewWidget());
+ DragTestView* drag_view = new DragTestView;
+ AddViewToWidgetAndResize(widget.get(), drag_view);
+ aura::test::EventGenerator generator(Shell::GetPrimaryRootWindow(),
+ widget->GetNativeView());
+
+ generator.PressTouch();
+ gfx::Point point = gfx::Rect(drag_view->bounds()).CenterPoint();
+ DispatchGesture(ui::ET_GESTURE_LONG_PRESS, point);
+ DispatchGesture(ui::ET_GESTURE_LONG_TAP, point);
+
+ EXPECT_TRUE(drag_drop_controller_->drag_start_received_);
+ EXPECT_TRUE(drag_drop_controller_->drag_canceled_);
+ EXPECT_EQ(0, drag_drop_controller_->num_drag_updates_);
+ EXPECT_FALSE(drag_drop_controller_->drop_received_);
+ EXPECT_EQ(UTF8ToUTF16("I am being dragged"),
+ drag_drop_controller_->drag_string_);
+ EXPECT_EQ(0, drag_view->num_drag_enters_);
+ EXPECT_EQ(0, drag_view->num_drops_);
+ EXPECT_EQ(0, drag_view->num_drag_exits_);
+ EXPECT_TRUE(drag_view->drag_done_received_);
+}
+
+TEST_F(DragDropControllerTest, TouchDragDropLongTapGestureIsForwarded) {
+ CommandLine::ForCurrentProcess()->AppendSwitch(
+ switches::kEnableTouchDragDrop);
+ scoped_ptr<views::Widget> widget(CreateNewWidget());
+ DragTestView* drag_view = new DragTestView;
+ AddViewToWidgetAndResize(widget.get(), drag_view);
+ aura::test::EventGenerator generator(Shell::GetPrimaryRootWindow(),
+ widget->GetNativeView());
+
+ generator.PressTouch();
+ gfx::Point point = gfx::Rect(drag_view->bounds()).CenterPoint();
+ DispatchGesture(ui::ET_GESTURE_LONG_PRESS, point);
+
+ // Since we are not running inside a nested loop, the |drag_source_window_|
+ // will get destroyed immediately. Hence we reassign it.
+ EXPECT_EQ(NULL, GetDragSourceWindow());
+ SetDragSourceWindow(widget->GetNativeView());
+ EXPECT_FALSE(drag_view->long_tap_received_);
+ DispatchGesture(ui::ET_GESTURE_LONG_TAP, point);
+ CompleteCancelAnimation();
+ EXPECT_TRUE(drag_view->long_tap_received_);
+}
+
+namespace {
+
+class DragImageWindowObserver : public aura::WindowObserver {
+ public:
+ virtual void OnWindowDestroying(aura::Window* window) OVERRIDE {
+ window_location_on_destroying_ = window->GetBoundsInScreen().origin();
+ }
+
+ gfx::Point window_location_on_destroying() const {
+ return window_location_on_destroying_;
+ }
+
+ public:
+ gfx::Point window_location_on_destroying_;
+};
+
+}
+
+// Verifies the drag image moves back to the position where drag is started
+// across displays when drag is cancelled.
+TEST_F(DragDropControllerTest, DragCancelAcrossDisplays) {
+ if (!SupportsMultipleDisplays())
+ return;
+
+ UpdateDisplay("400x400,400x400");
+ Shell::RootWindowList root_windows =
+ Shell::GetInstance()->GetAllRootWindows();
+ for (Shell::RootWindowList::iterator iter = root_windows.begin();
+ iter != root_windows.end(); ++iter) {
+ aura::client::SetDragDropClient(*iter, drag_drop_controller_.get());
+ }
+
+ ui::OSExchangeData data;
+ data.SetString(UTF8ToUTF16("I am being dragged"));
+ {
+ scoped_ptr<views::Widget> widget(CreateNewWidget());
+ aura::Window* window = widget->GetNativeWindow();
+ drag_drop_controller_->StartDragAndDrop(
+ data,
+ window->GetRootWindow(),
+ window,
+ gfx::Point(5, 5),
+ ui::DragDropTypes::DRAG_MOVE,
+ ui::DragDropTypes::DRAG_EVENT_SOURCE_MOUSE);
+
+ DragImageWindowObserver observer;
+ ASSERT_TRUE(GetDragImageWindow());
+ GetDragImageWindow()->AddObserver(&observer);
+
+ {
+ ui::MouseEvent e(ui::ET_MOUSE_DRAGGED,
+ gfx::Point(200, 0),
+ gfx::Point(200, 0),
+ ui::EF_NONE);
+ drag_drop_controller_->DragUpdate(window, e);
+ }
+ {
+ ui::MouseEvent e(ui::ET_MOUSE_DRAGGED,
+ gfx::Point(600, 0),
+ gfx::Point(600, 0),
+ ui::EF_NONE);
+ drag_drop_controller_->DragUpdate(window, e);
+ }
+
+ drag_drop_controller_->DragCancel();
+ CompleteCancelAnimation();
+
+ EXPECT_EQ("5,5", observer.window_location_on_destroying().ToString());
+ }
+
+ {
+ scoped_ptr<views::Widget> widget(CreateNewWidget());
+ aura::Window* window = widget->GetNativeWindow();
+ drag_drop_controller_->StartDragAndDrop(
+ data,
+ window->GetRootWindow(),
+ window,
+ gfx::Point(405, 405),
+ ui::DragDropTypes::DRAG_MOVE,
+ ui::DragDropTypes::DRAG_EVENT_SOURCE_MOUSE);
+ DragImageWindowObserver observer;
+ ASSERT_TRUE(GetDragImageWindow());
+ GetDragImageWindow()->AddObserver(&observer);
+
+ {
+ ui::MouseEvent e(ui::ET_MOUSE_DRAGGED,
+ gfx::Point(600, 0),
+ gfx::Point(600, 0),
+ ui::EF_NONE);
+ drag_drop_controller_->DragUpdate(window, e);
+ }
+ {
+ ui::MouseEvent e(ui::ET_MOUSE_DRAGGED,
+ gfx::Point(200, 0),
+ gfx::Point(200, 0),
+ ui::EF_NONE);
+ drag_drop_controller_->DragUpdate(window, e);
+ }
+
+ drag_drop_controller_->DragCancel();
+ CompleteCancelAnimation();
+
+ EXPECT_EQ("405,405", observer.window_location_on_destroying().ToString());
+ }
+ for (Shell::RootWindowList::iterator iter = root_windows.begin();
+ iter != root_windows.end(); ++iter) {
+ aura::client::SetDragDropClient(*iter, NULL);
+ }
+}
+
+} // namespace test
+} // namespace aura
diff --git a/chromium/ash/drag_drop/drag_drop_interactive_uitest.cc b/chromium/ash/drag_drop/drag_drop_interactive_uitest.cc
new file mode 100644
index 00000000000..5f13c5b8f90
--- /dev/null
+++ b/chromium/ash/drag_drop/drag_drop_interactive_uitest.cc
@@ -0,0 +1,165 @@
+// Copyright (c) 2013 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.
+
+#include "ash/drag_drop/drag_drop_controller.h"
+
+#include "ash/shell.h"
+#include "ash/test/ash_test_base.h"
+#include "base/bind.h"
+#include "base/message_loop/message_loop.h"
+#include "base/strings/utf_string_conversions.h"
+#include "ui/aura/root_window.h"
+#include "ui/base/dragdrop/drag_drop_types.h"
+#include "ui/base/test/ui_controls.h"
+#include "ui/views/view.h"
+#include "ui/views/widget/widget.h"
+
+namespace ash {
+namespace internal {
+namespace {
+
+class DraggableView : public views::View {
+ public:
+ DraggableView() {}
+ virtual ~DraggableView() {}
+
+ // views::View overrides:
+ virtual int GetDragOperations(const gfx::Point& press_pt) OVERRIDE {
+ return ui::DragDropTypes::DRAG_MOVE;
+ }
+ virtual void WriteDragData(const gfx::Point& press_pt,
+ OSExchangeData* data)OVERRIDE {
+ data->SetString(UTF8ToUTF16("test"));
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(DraggableView);
+};
+
+class TargetView : public views::View {
+ public:
+ TargetView() : dropped_(false) {}
+ virtual ~TargetView() {}
+
+ // views::View overrides:
+ virtual bool GetDropFormats(
+ int* formats,
+ std::set<OSExchangeData::CustomFormat>* custom_formats) OVERRIDE {
+ *formats = ui::OSExchangeData::STRING;
+ return true;
+ }
+ virtual bool AreDropTypesRequired() OVERRIDE {
+ return false;
+ }
+ virtual bool CanDrop(const OSExchangeData& data) OVERRIDE {
+ return true;
+ }
+ virtual int OnDragUpdated(const ui::DropTargetEvent& event) OVERRIDE {
+ return ui::DragDropTypes::DRAG_MOVE;
+ }
+ virtual int OnPerformDrop(const ui::DropTargetEvent& event) OVERRIDE {
+ dropped_ = true;
+ return ui::DragDropTypes::DRAG_MOVE;
+ }
+
+ bool dropped() const { return dropped_; }
+
+ private:
+ bool dropped_;
+
+ DISALLOW_COPY_AND_ASSIGN(TargetView);
+};
+
+views::Widget* CreateWidget(views::View* contents_view,
+ const gfx::Rect& bounds) {
+ views::Widget* widget = new views::Widget;
+ views::Widget::InitParams params;
+ params.type = views::Widget::InitParams::TYPE_WINDOW_FRAMELESS;
+ params.accept_events = true;
+ params.context = Shell::GetPrimaryRootWindow();
+ params.bounds = bounds;
+ widget->Init(params);
+
+ widget->SetContentsView(contents_view);
+ widget->Show();
+ return widget;
+}
+
+void QuitLoop() {
+ base::MessageLoop::current()->Quit();
+}
+
+void DragDropAcrossMultiDisplay_Step4() {
+ ui_controls::SendMouseEventsNotifyWhenDone(
+ ui_controls::LEFT, ui_controls::UP,
+ base::Bind(&QuitLoop));
+}
+
+void DragDropAcrossMultiDisplay_Step3() {
+ // Move to the edge of the 1st display so that the mouse
+ // is moved to 2nd display by ash.
+ ui_controls::SendMouseMoveNotifyWhenDone(
+ 399, 10,
+ base::Bind(&DragDropAcrossMultiDisplay_Step4));
+}
+
+void DragDropAcrossMultiDisplay_Step2() {
+ ui_controls::SendMouseMoveNotifyWhenDone(
+ 20, 10,
+ base::Bind(&DragDropAcrossMultiDisplay_Step3));
+}
+
+void DragDropAcrossMultiDisplay_Step1() {
+ ui_controls::SendMouseEventsNotifyWhenDone(
+ ui_controls::LEFT, ui_controls::DOWN,
+ base::Bind(&DragDropAcrossMultiDisplay_Step2));
+}
+
+} // namespace
+
+typedef test::AshTestBase DragDropTest;
+
+#if defined(OS_WIN)
+#define MAYBE_DragDropAcrossMultiDisplay DISABLED_DragDropAcrossMultiDisplay
+#else
+#define MAYBE_DragDropAcrossMultiDisplay DragDropAcrossMultiDisplay
+#endif
+
+// Test if the mouse gets moved properly to another display
+// during drag & drop operation.
+TEST_F(DragDropTest, MAYBE_DragDropAcrossMultiDisplay) {
+ if (!SupportsMultipleDisplays())
+ return;
+
+ UpdateDisplay("400x400,400x400");
+ Shell::RootWindowList root_windows =
+ Shell::GetInstance()->GetAllRootWindows();
+ views::View* draggable_view = new DraggableView();
+ draggable_view->set_drag_controller(NULL);
+ draggable_view->SetBounds(0, 0, 100, 100);
+ views::Widget* source =
+ CreateWidget(draggable_view, gfx::Rect(0, 0, 100, 100));
+
+ TargetView* target_view = new TargetView();
+ target_view->SetBounds(0, 0, 100, 100);
+ views::Widget* target =
+ CreateWidget(target_view, gfx::Rect(400, 0, 100, 100));
+
+ // Make sure they're on the different root windows.
+ EXPECT_EQ(root_windows[0], source->GetNativeView()->GetRootWindow());
+ EXPECT_EQ(root_windows[1], target->GetNativeView()->GetRootWindow());
+
+ ui_controls::SendMouseMoveNotifyWhenDone(
+ 10, 10, base::Bind(&DragDropAcrossMultiDisplay_Step1));
+
+ base::MessageLoop::current()->Run();
+
+ EXPECT_TRUE(target_view->dropped());
+
+ source->Close();
+ target->Close();
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/drag_drop/drag_drop_tracker.cc b/chromium/ash/drag_drop/drag_drop_tracker.cc
new file mode 100644
index 00000000000..9de464540a7
--- /dev/null
+++ b/chromium/ash/drag_drop/drag_drop_tracker.cc
@@ -0,0 +1,79 @@
+// Copyright (c) 2012 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.
+
+#include "ash/drag_drop/drag_drop_tracker.h"
+
+#include "ash/shell.h"
+#include "ash/shell_window_ids.h"
+#include "ash/wm/coordinate_conversion.h"
+#include "ui/aura/root_window.h"
+#include "ui/base/events/event.h"
+#include "ui/gfx/screen.h"
+
+namespace ash {
+namespace internal {
+
+namespace {
+
+// Creates a window for capturing drag events.
+aura::Window* CreateCaptureWindow(aura::RootWindow* context_root,
+ aura::WindowDelegate* delegate) {
+ aura::Window* window = new aura::Window(delegate);
+ window->SetType(aura::client::WINDOW_TYPE_NORMAL);
+ window->Init(ui::LAYER_NOT_DRAWN);
+ window->SetDefaultParentByRootWindow(context_root, gfx::Rect());
+ window->Show();
+ DCHECK(window->bounds().size().IsEmpty());
+ return window;
+}
+
+} // namespace
+
+DragDropTracker::DragDropTracker(aura::RootWindow* context_root,
+ aura::WindowDelegate* delegate)
+ : capture_window_(CreateCaptureWindow(context_root, delegate)) {
+}
+
+DragDropTracker::~DragDropTracker() {
+ capture_window_->ReleaseCapture();
+}
+
+void DragDropTracker::TakeCapture() {
+ capture_window_->SetCapture();
+}
+
+aura::Window* DragDropTracker::GetTarget(const ui::LocatedEvent& event) {
+ DCHECK(capture_window_.get());
+ gfx::Point location_in_screen = event.location();
+ wm::ConvertPointToScreen(capture_window_.get(),
+ &location_in_screen);
+ aura::RootWindow* root_window_at_point =
+ wm::GetRootWindowAt(location_in_screen);
+ gfx::Point location_in_root = location_in_screen;
+ wm::ConvertPointFromScreen(root_window_at_point, &location_in_root);
+ return root_window_at_point->GetEventHandlerForPoint(location_in_root);
+}
+
+ui::LocatedEvent* DragDropTracker::ConvertEvent(
+ aura::Window* target,
+ const ui::LocatedEvent& event) {
+ DCHECK(capture_window_.get());
+ gfx::Point target_location = event.location();
+ aura::Window::ConvertPointToTarget(capture_window_.get(), target,
+ &target_location);
+ gfx::Point location_in_screen = event.location();
+ ash::wm::ConvertPointToScreen(capture_window_.get(), &location_in_screen);
+ gfx::Point target_root_location = event.root_location();
+ aura::Window::ConvertPointToTarget(
+ capture_window_->GetRootWindow(),
+ ash::wm::GetRootWindowAt(location_in_screen),
+ &target_root_location);
+ return new ui::MouseEvent(event.type(),
+ target_location,
+ target_root_location,
+ event.flags());
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/drag_drop/drag_drop_tracker.h b/chromium/ash/drag_drop/drag_drop_tracker.h
new file mode 100644
index 00000000000..d7212ef796f
--- /dev/null
+++ b/chromium/ash/drag_drop/drag_drop_tracker.h
@@ -0,0 +1,60 @@
+// Copyright (c) 2012 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 ASH_DRAG_DROP_DRAG_DROP_TRACKER_H_
+#define ASH_DRAG_DROP_DRAG_DROP_TRACKER_H_
+
+#include "ash/ash_export.h"
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "ui/base/events/event.h"
+
+namespace aura {
+class RootWindow;
+class Window;
+class WindowDelegate;
+}
+
+namespace ash {
+namespace internal {
+
+// Provides functions for handling drag events inside and outside the root
+// window where drag is started. This internally sets up a capture window for
+// tracking drag events outside the root window where drag is initiated.
+// ash/wm/coordinate_conversion.h is used internally and only X11 environment
+// is supported for now.
+class ASH_EXPORT DragDropTracker {
+ public:
+ DragDropTracker(aura::RootWindow* context_root,
+ aura::WindowDelegate* delegate);
+ ~DragDropTracker();
+
+ aura::Window* capture_window() { return capture_window_.get(); }
+
+ // Tells our |capture_window_| to take capture. This is not done right at
+ // creation to give the caller a chance to perform any operations needed
+ // before the capture is transfered.
+ void TakeCapture();
+
+ // Gets the target located at |event| in the coordinates of the active root
+ // window.
+ aura::Window* GetTarget(const ui::LocatedEvent& event);
+
+ // Converts the locations of |event| in the coordinates of the active root
+ // window to the ones in |target|'s coordinates.
+ // Caller takes ownership of the returned object.
+ ui::LocatedEvent* ConvertEvent(aura::Window* target,
+ const ui::LocatedEvent& event);
+
+ private:
+ // A window for capturing drag events while dragging.
+ scoped_ptr<aura::Window> capture_window_;
+
+ DISALLOW_COPY_AND_ASSIGN(DragDropTracker);
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_DRAG_DROP_DRAG_DROP_TRACKER_H_
diff --git a/chromium/ash/drag_drop/drag_drop_tracker_unittest.cc b/chromium/ash/drag_drop/drag_drop_tracker_unittest.cc
new file mode 100644
index 00000000000..c0a9e91fa5d
--- /dev/null
+++ b/chromium/ash/drag_drop/drag_drop_tracker_unittest.cc
@@ -0,0 +1,202 @@
+// Copyright (c) 2012 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.
+
+#include "ash/drag_drop/drag_drop_tracker.h"
+
+#include "ash/shell.h"
+#include "ash/shell_window_ids.h"
+#include "ash/test/ash_test_base.h"
+#include "base/memory/scoped_ptr.h"
+#include "ui/aura/root_window.h"
+#include "ui/aura/test/test_windows.h"
+#include "ui/aura/window.h"
+
+namespace ash {
+namespace test {
+
+class DragDropTrackerTest : public test::AshTestBase {
+ public:
+ virtual void SetUp() OVERRIDE {
+ AshTestBase::SetUp();
+ UpdateDisplay("200x200,300x300");
+ }
+
+ aura::Window* CreateTestWindow(const gfx::Rect& bounds) {
+ static int window_id = 0;
+ return CreateTestWindowInShellWithDelegate(
+ aura::test::TestWindowDelegate::CreateSelfDestroyingDelegate(),
+ window_id++,
+ bounds);
+ }
+
+ static aura::Window* GetTarget(const gfx::Point& location) {
+ scoped_ptr<internal::DragDropTracker> tracker(
+ new internal::DragDropTracker(Shell::GetPrimaryRootWindow(),
+ NULL));
+ ui::MouseEvent e(ui::ET_MOUSE_DRAGGED,
+ location,
+ location,
+ ui::EF_NONE);
+ aura::Window* target = tracker->GetTarget(e);
+ return target;
+ }
+
+ static ui::LocatedEvent* ConvertEvent(aura::Window* target,
+ const ui::MouseEvent& event) {
+ scoped_ptr<internal::DragDropTracker> tracker(
+ new internal::DragDropTracker(Shell::GetPrimaryRootWindow(),
+ NULL));
+ ui::LocatedEvent* converted = tracker->ConvertEvent(target, event);
+ return converted;
+ }
+};
+
+// TODO(mazda): Remove this once ash/wm/coordinate_conversion.h supports
+// non-X11 platforms.
+#if defined(USE_X11)
+#define MAYBE_GetTarget GetTarget
+#else
+#define MAYBE_GetTarget DISABLED_GetTarget
+#endif
+
+TEST_F(DragDropTrackerTest, MAYBE_GetTarget) {
+ Shell::RootWindowList root_windows = Shell::GetAllRootWindows();
+ EXPECT_EQ(2U, root_windows.size());
+
+ scoped_ptr<aura::Window> window0(
+ CreateTestWindow(gfx::Rect(0, 0, 100, 100)));
+ window0->Show();
+
+ scoped_ptr<aura::Window> window1(
+ CreateTestWindow(gfx::Rect(300, 100, 100, 100)));
+ window1->Show();
+ EXPECT_EQ(root_windows[0], window0->GetRootWindow());
+ EXPECT_EQ(root_windows[1], window1->GetRootWindow());
+ EXPECT_EQ("0,0 100x100", window0->GetBoundsInScreen().ToString());
+ EXPECT_EQ("300,100 100x100", window1->GetBoundsInScreen().ToString());
+
+ // Make RootWindow0 active so that capture window is parented to it.
+ Shell::GetInstance()->set_active_root_window(root_windows[0]);
+
+ // Start tracking from the RootWindow1 and check the point on RootWindow0 that
+ // |window0| covers.
+ EXPECT_EQ(window0.get(), GetTarget(gfx::Point(50, 50)));
+
+ // Start tracking from the RootWindow0 and check the point on RootWindow0 that
+ // neither |window0| nor |window1| covers.
+ EXPECT_NE(window0.get(), GetTarget(gfx::Point(150, 150)));
+ EXPECT_NE(window1.get(), GetTarget(gfx::Point(150, 150)));
+
+ // Start tracking from the RootWindow0 and check the point on RootWindow1 that
+ // |window1| covers.
+ EXPECT_EQ(window1.get(), GetTarget(gfx::Point(350, 150)));
+
+ // Start tracking from the RootWindow0 and check the point on RootWindow1 that
+ // neither |window0| nor |window1| covers.
+ EXPECT_NE(window0.get(), GetTarget(gfx::Point(50, 250)));
+ EXPECT_NE(window1.get(), GetTarget(gfx::Point(50, 250)));
+
+ // Make RootWindow1 active so that capture window is parented to it.
+ Shell::GetInstance()->set_active_root_window(root_windows[1]);
+
+ // Start tracking from the RootWindow1 and check the point on RootWindow0 that
+ // |window0| covers.
+ EXPECT_EQ(window0.get(), GetTarget(gfx::Point(-150, 50)));
+
+ // Start tracking from the RootWindow1 and check the point on RootWindow0 that
+ // neither |window0| nor |window1| covers.
+ EXPECT_NE(window0.get(), GetTarget(gfx::Point(150, -50)));
+ EXPECT_NE(window1.get(), GetTarget(gfx::Point(150, -50)));
+
+ // Start tracking from the RootWindow1 and check the point on RootWindow1 that
+ // |window1| covers.
+ EXPECT_EQ(window1.get(), GetTarget(gfx::Point(150, 150)));
+
+ // Start tracking from the RootWindow1 and check the point on RootWindow1 that
+ // neither |window0| nor |window1| covers.
+ EXPECT_NE(window0.get(), GetTarget(gfx::Point(50, 50)));
+ EXPECT_NE(window1.get(), GetTarget(gfx::Point(50, 50)));
+}
+
+// TODO(mazda): Remove this once ash/wm/coordinate_conversion.h supports
+// non-X11 platforms.
+#if defined(USE_X11)
+#define MAYBE_ConvertEvent ConvertEvent
+#else
+#define MAYBE_ConvertEvent DISABLED_ConvertEvent
+#endif
+
+TEST_F(DragDropTrackerTest, MAYBE_ConvertEvent) {
+ Shell::RootWindowList root_windows = Shell::GetAllRootWindows();
+ EXPECT_EQ(2U, root_windows.size());
+
+ scoped_ptr<aura::Window> window0(
+ CreateTestWindow(gfx::Rect(0, 0, 100, 100)));
+ window0->Show();
+
+ scoped_ptr<aura::Window> window1(
+ CreateTestWindow(gfx::Rect(300, 100, 100, 100)));
+ window1->Show();
+
+ // Make RootWindow0 active so that capture window is parented to it.
+ Shell::GetInstance()->set_active_root_window(root_windows[0]);
+
+ // Start tracking from the RootWindow0 and converts the mouse event into
+ // |window0|'s coodinates.
+ ui::MouseEvent original00(ui::ET_MOUSE_DRAGGED,
+ gfx::Point(50, 50),
+ gfx::Point(50, 50),
+ ui::EF_NONE);
+ scoped_ptr<ui::LocatedEvent> converted00(ConvertEvent(window0.get(),
+ original00));
+ EXPECT_EQ(original00.type(), converted00->type());
+ EXPECT_EQ("50,50", converted00->location().ToString());
+ EXPECT_EQ("50,50", converted00->root_location().ToString());
+ EXPECT_EQ(original00.flags(), converted00->flags());
+
+ // Start tracking from the RootWindow0 and converts the mouse event into
+ // |window1|'s coodinates.
+ ui::MouseEvent original01(ui::ET_MOUSE_DRAGGED,
+ gfx::Point(350, 150),
+ gfx::Point(350, 150),
+ ui::EF_NONE);
+ scoped_ptr<ui::LocatedEvent> converted01(ConvertEvent(window1.get(),
+ original01));
+ EXPECT_EQ(original01.type(), converted01->type());
+ EXPECT_EQ("50,50", converted01->location().ToString());
+ EXPECT_EQ("150,150", converted01->root_location().ToString());
+ EXPECT_EQ(original01.flags(), converted01->flags());
+
+ // Make RootWindow1 active so that capture window is parented to it.
+ Shell::GetInstance()->set_active_root_window(root_windows[1]);
+
+ // Start tracking from the RootWindow1 and converts the mouse event into
+ // |window0|'s coodinates.
+ ui::MouseEvent original10(ui::ET_MOUSE_DRAGGED,
+ gfx::Point(-150, 50),
+ gfx::Point(-150, 50),
+ ui::EF_NONE);
+ scoped_ptr<ui::LocatedEvent> converted10(ConvertEvent(window0.get(),
+ original10));
+ EXPECT_EQ(original10.type(), converted10->type());
+ EXPECT_EQ("50,50", converted10->location().ToString());
+ EXPECT_EQ("50,50", converted10->root_location().ToString());
+ EXPECT_EQ(original10.flags(), converted10->flags());
+
+ // Start tracking from the RootWindow1 and converts the mouse event into
+ // |window1|'s coodinates.
+ ui::MouseEvent original11(ui::ET_MOUSE_DRAGGED,
+ gfx::Point(150, 150),
+ gfx::Point(150, 150),
+ ui::EF_NONE);
+ scoped_ptr<ui::LocatedEvent> converted11(ConvertEvent(window1.get(),
+ original11));
+ EXPECT_EQ(original11.type(), converted11->type());
+ EXPECT_EQ("50,50", converted11->location().ToString());
+ EXPECT_EQ("150,150", converted11->root_location().ToString());
+ EXPECT_EQ(original11.flags(), converted11->flags());
+}
+
+} // namespace test
+} // namespace aura
diff --git a/chromium/ash/drag_drop/drag_image_view.cc b/chromium/ash/drag_drop/drag_image_view.cc
new file mode 100644
index 00000000000..4dd729b3b4c
--- /dev/null
+++ b/chromium/ash/drag_drop/drag_image_view.cc
@@ -0,0 +1,103 @@
+// Copyright (c) 2012 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.
+
+#include "ash/drag_drop/drag_image_view.h"
+
+#include "skia/ext/image_operations.h"
+#include "ui/aura/window.h"
+#include "ui/compositor/dip_util.h"
+#include "ui/gfx/canvas.h"
+#include "ui/gfx/size_conversions.h"
+#include "ui/views/corewm/shadow_types.h"
+#include "ui/views/widget/widget.h"
+
+namespace ash {
+namespace internal {
+
+namespace {
+using views::Widget;
+
+Widget* CreateDragWidget(gfx::NativeView context) {
+ Widget* drag_widget = new Widget;
+ Widget::InitParams params;
+ params.type = Widget::InitParams::TYPE_TOOLTIP;
+ params.keep_on_top = true;
+ params.context = context;
+ params.accept_events = false;
+ params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
+ params.opacity = Widget::InitParams::TRANSLUCENT_WINDOW;
+ drag_widget->Init(params);
+ drag_widget->SetOpacity(0xFF);
+ drag_widget->GetNativeWindow()->set_owned_by_parent(false);
+ drag_widget->GetNativeWindow()->SetName("DragWidget");
+ SetShadowType(drag_widget->GetNativeView(), views::corewm::SHADOW_TYPE_NONE);
+ return drag_widget;
+}
+}
+
+DragImageView::DragImageView(gfx::NativeView context) : views::ImageView() {
+ widget_.reset(CreateDragWidget(context));
+ widget_->SetContentsView(this);
+ widget_->SetAlwaysOnTop(true);
+
+ // We are owned by the DragDropController.
+ set_owned_by_client();
+}
+
+DragImageView::~DragImageView() {
+ widget_->Hide();
+}
+
+void DragImageView::SetBoundsInScreen(const gfx::Rect& bounds) {
+ widget_->SetBounds(bounds);
+ widget_size_ = bounds.size();
+}
+
+void DragImageView::SetScreenPosition(const gfx::Point& position) {
+ widget_->SetBounds(gfx::Rect(position, widget_size_));
+}
+
+void DragImageView::SetWidgetVisible(bool visible) {
+ if (visible != widget_->IsVisible()) {
+ if (visible)
+ widget_->Show();
+ else
+ widget_->Hide();
+ }
+}
+
+void DragImageView::OnPaint(gfx::Canvas* canvas) {
+ if (GetImage().isNull())
+ return;
+
+ // |widget_size_| is in DIP. ImageSkia::size() also returns the size in DIP.
+ if (GetImage().size() == widget_size_) {
+ canvas->DrawImageInt(GetImage(), 0, 0);
+ } else {
+ float device_scale = 1;
+ if (widget_->GetNativeView() && widget_->GetNativeView()->layer()) {
+ device_scale = ui::GetDeviceScaleFactor(
+ widget_->GetNativeView()->layer());
+ }
+ ui::ScaleFactor device_scale_factor =
+ ui::GetScaleFactorFromScale(device_scale);
+
+ // The drag image already has device scale factor applied. But
+ // |widget_size_| is in DIP units.
+ gfx::Size scaled_widget_size = gfx::ToRoundedSize(
+ gfx::ScaleSize(widget_size_, device_scale));
+ gfx::ImageSkiaRep image_rep = GetImage().GetRepresentation(
+ device_scale_factor);
+ if (image_rep.is_null())
+ return;
+ SkBitmap scaled = skia::ImageOperations::Resize(
+ image_rep.sk_bitmap(), skia::ImageOperations::RESIZE_LANCZOS3,
+ scaled_widget_size.width(), scaled_widget_size.height());
+ gfx::ImageSkia image_skia(gfx::ImageSkiaRep(scaled, device_scale_factor));
+ canvas->DrawImageInt(image_skia, 0, 0);
+ }
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/drag_drop/drag_image_view.h b/chromium/ash/drag_drop/drag_image_view.h
new file mode 100644
index 00000000000..c2791c497e9
--- /dev/null
+++ b/chromium/ash/drag_drop/drag_image_view.h
@@ -0,0 +1,47 @@
+// Copyright (c) 2012 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 ASH_DRAG_DROP_DRAG_IMAGE_VIEW_H_
+#define ASH_DRAG_DROP_DRAG_IMAGE_VIEW_H_
+
+#include "ui/views/controls/image_view.h"
+
+namespace views {
+class Widget;
+}
+
+namespace ash {
+namespace internal {
+
+class DragImageView : public views::ImageView {
+ public:
+ explicit DragImageView(gfx::NativeView context);
+ virtual ~DragImageView();
+
+ // Sets the bounds of the native widget in screen
+ // coordinates.
+ // TODO(oshima): Looks like this is root window's
+ // coordinate. Change this to screen's coordinate.
+ void SetBoundsInScreen(const gfx::Rect& bounds);
+
+ // Sets the position of the native widget.
+ void SetScreenPosition(const gfx::Point& position);
+
+ // Sets the visibility of the native widget.
+ void SetWidgetVisible(bool visible);
+
+ private:
+ // Overridden from views::ImageView.
+ virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE;
+
+ scoped_ptr<views::Widget> widget_;
+ gfx::Size widget_size_;
+
+ DISALLOW_COPY_AND_ASSIGN(DragImageView);
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_DRAG_DROP_DRAG_IMAGE_VIEW_H_
diff --git a/chromium/ash/event_rewriter_delegate.h b/chromium/ash/event_rewriter_delegate.h
new file mode 100644
index 00000000000..c54c9a37078
--- /dev/null
+++ b/chromium/ash/event_rewriter_delegate.h
@@ -0,0 +1,34 @@
+// Copyright (c) 2012 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 ASH_EVENT_REWRITER_DELEGATE_H_
+#define ASH_EVENT_REWRITER_DELEGATE_H_
+
+namespace ui {
+class KeyEvent;
+class LocatedEvent;
+} // namespace aura
+
+namespace ash {
+
+// Delegate for rewriting or filtering an event.
+class EventRewriterDelegate {
+ public:
+ enum Action {
+ ACTION_REWRITE_EVENT,
+ ACTION_DROP_EVENT,
+ };
+
+ virtual ~EventRewriterDelegate() {}
+
+ // A derived class can do either of the following:
+ // 1) Just return ACTION_DROP_EVENT to drop the |event|.
+ // 2) Rewrite the |event| and return ACTION_REWRITE_EVENT.
+ virtual Action RewriteOrFilterKeyEvent(ui::KeyEvent* event) = 0;
+ virtual Action RewriteOrFilterLocatedEvent(ui::LocatedEvent* event) = 0;
+};
+
+} // namespace ash
+
+#endif // ASH_EVENT_REWRITER_DELEGATE_H_
diff --git a/chromium/ash/extended_desktop_unittest.cc b/chromium/ash/extended_desktop_unittest.cc
new file mode 100644
index 00000000000..d36e8240893
--- /dev/null
+++ b/chromium/ash/extended_desktop_unittest.cc
@@ -0,0 +1,850 @@
+// Copyright (c) 2012 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.
+
+#include "ash/display/display_controller.h"
+#include "ash/display/display_manager.h"
+#include "ash/root_window_controller.h"
+#include "ash/screen_ash.h"
+#include "ash/shell.h"
+#include "ash/shell_window_ids.h"
+#include "ash/system/tray/system_tray.h"
+#include "ash/test/ash_test_base.h"
+#include "ash/wm/coordinate_conversion.h"
+#include "ash/wm/property_util.h"
+#include "ash/wm/window_cycle_controller.h"
+#include "ash/wm/window_properties.h"
+#include "ash/wm/window_util.h"
+#include "base/strings/string_util.h"
+#include "ui/aura/client/activation_client.h"
+#include "ui/aura/client/capture_client.h"
+#include "ui/aura/client/focus_client.h"
+#include "ui/aura/root_window.h"
+#include "ui/aura/test/event_generator.h"
+#include "ui/aura/test/test_windows.h"
+#include "ui/aura/test/window_test_api.h"
+#include "ui/aura/window.h"
+#include "ui/base/cursor/cursor.h"
+#include "ui/base/events/event_handler.h"
+#include "ui/gfx/display.h"
+#include "ui/gfx/screen.h"
+#include "ui/views/controls/textfield/textfield.h"
+#include "ui/views/widget/widget.h"
+#include "ui/views/widget/widget_delegate.h"
+
+namespace ash {
+namespace {
+
+void SetSecondaryDisplayLayout(DisplayLayout::Position position) {
+ DisplayLayout layout =
+ Shell::GetInstance()->display_manager()->GetCurrentDisplayLayout();
+ layout.position = position;
+ Shell::GetInstance()->display_controller()->
+ SetLayoutForCurrentDisplays(layout);
+}
+
+internal::DisplayManager* GetDisplayManager() {
+ return Shell::GetInstance()->display_manager();
+}
+
+class ModalWidgetDelegate : public views::WidgetDelegateView {
+ public:
+ ModalWidgetDelegate() {}
+ virtual ~ModalWidgetDelegate() {}
+
+ // Overridden from views::WidgetDelegate:
+ virtual views::View* GetContentsView() OVERRIDE {
+ return this;
+ }
+ virtual ui::ModalType GetModalType() const OVERRIDE {
+ return ui::MODAL_TYPE_SYSTEM;
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ModalWidgetDelegate);
+};
+
+// An event handler which moves the target window to the secondary root window
+// at pre-handle phase of a mouse release event.
+class MoveWindowByClickEventHandler : public ui::EventHandler {
+ public:
+ explicit MoveWindowByClickEventHandler(aura::Window* target)
+ : target_(target) {}
+ virtual ~MoveWindowByClickEventHandler() {}
+
+ private:
+ // ui::EventHandler overrides:
+ virtual void OnMouseEvent(ui::MouseEvent* event) OVERRIDE {
+ if (event->type() == ui::ET_MOUSE_RELEASED) {
+ Shell::RootWindowList root_windows = Shell::GetAllRootWindows();
+ DCHECK_LT(1u, root_windows.size());
+ root_windows[1]->AddChild(target_);
+ }
+ }
+
+ aura::Window* target_;
+ DISALLOW_COPY_AND_ASSIGN(MoveWindowByClickEventHandler);
+};
+
+// An event handler which records the event's locations.
+class EventLocationRecordingEventHandler : public ui::EventHandler {
+ public:
+ explicit EventLocationRecordingEventHandler() {
+ reset();
+ }
+ virtual ~EventLocationRecordingEventHandler() {}
+
+ std::string GetLocationsAndReset() {
+ std::string result =
+ location_.ToString() + " " + root_location_.ToString();
+ reset();
+ return result;
+ }
+
+ private:
+ // ui::EventHandler overrides:
+ virtual void OnMouseEvent(ui::MouseEvent* event) OVERRIDE {
+ if (event->type() == ui::ET_MOUSE_MOVED ||
+ event->type() == ui::ET_MOUSE_DRAGGED) {
+ location_ = event->location();
+ root_location_ = event->root_location();
+ }
+ }
+
+ void reset() {
+ location_.SetPoint(-999, -999);
+ root_location_.SetPoint(-999, -999);
+ }
+
+ gfx::Point root_location_;
+ gfx::Point location_;
+
+ DISALLOW_COPY_AND_ASSIGN(EventLocationRecordingEventHandler);
+};
+
+} // namespace
+
+class ExtendedDesktopTest : public test::AshTestBase {
+ public:
+ views::Widget* CreateTestWidget(const gfx::Rect& bounds) {
+ return CreateTestWidgetWithParentAndContext(
+ NULL, CurrentContext(), bounds, false);
+ }
+
+ views::Widget* CreateTestWidgetWithParent(views::Widget* parent,
+ const gfx::Rect& bounds,
+ bool child) {
+ CHECK(parent);
+ return CreateTestWidgetWithParentAndContext(parent, NULL, bounds, child);
+ }
+
+ views::Widget* CreateTestWidgetWithParentAndContext(views::Widget* parent,
+ gfx::NativeView context,
+ const gfx::Rect& bounds,
+ bool child) {
+ views::Widget::InitParams params(views::Widget::InitParams::TYPE_WINDOW);
+ if (parent)
+ params.parent = parent->GetNativeView();
+ params.context = context;
+ params.bounds = bounds;
+ params.child = child;
+ views::Widget* widget = new views::Widget;
+ widget->Init(params);
+ widget->Show();
+ return widget;
+ }
+};
+
+// Test conditions that root windows in extended desktop mode
+// must satisfy.
+TEST_F(ExtendedDesktopTest, Basic) {
+ if (!SupportsMultipleDisplays())
+ return;
+
+ UpdateDisplay("1000x600,600x400");
+ Shell::RootWindowList root_windows = Shell::GetAllRootWindows();
+
+ // All root windows must have the root window controller.
+ ASSERT_EQ(2U, root_windows.size());
+ for (Shell::RootWindowList::const_iterator iter = root_windows.begin();
+ iter != root_windows.end(); ++iter) {
+ EXPECT_TRUE(GetRootWindowController(*iter) != NULL);
+ }
+ // Make sure root windows share the same controllers.
+ EXPECT_EQ(aura::client::GetFocusClient(root_windows[0]),
+ aura::client::GetFocusClient(root_windows[1]));
+ EXPECT_EQ(aura::client::GetActivationClient(root_windows[0]),
+ aura::client::GetActivationClient(root_windows[1]));
+ EXPECT_EQ(aura::client::GetCaptureClient(root_windows[0]),
+ aura::client::GetCaptureClient(root_windows[1]));
+}
+
+TEST_F(ExtendedDesktopTest, Activation) {
+ if (!SupportsMultipleDisplays())
+ return;
+
+ UpdateDisplay("1000x600,600x400");
+ Shell::RootWindowList root_windows = Shell::GetAllRootWindows();
+
+ views::Widget* widget_on_1st = CreateTestWidget(gfx::Rect(10, 10, 100, 100));
+ views::Widget* widget_on_2nd =
+ CreateTestWidget(gfx::Rect(1200, 10, 100, 100));
+ EXPECT_EQ(root_windows[0], widget_on_1st->GetNativeView()->GetRootWindow());
+ EXPECT_EQ(root_windows[1], widget_on_2nd->GetNativeView()->GetRootWindow());
+
+ EXPECT_EQ(widget_on_2nd->GetNativeView(),
+ aura::client::GetFocusClient(root_windows[0])->GetFocusedWindow());
+ EXPECT_TRUE(wm::IsActiveWindow(widget_on_2nd->GetNativeView()));
+
+ aura::test::EventGenerator& event_generator(GetEventGenerator());
+ // Clicking a window changes the active window and active root window.
+ event_generator.MoveMouseToCenterOf(widget_on_1st->GetNativeView());
+ event_generator.ClickLeftButton();
+
+ EXPECT_EQ(widget_on_1st->GetNativeView(),
+ aura::client::GetFocusClient(root_windows[0])->GetFocusedWindow());
+ EXPECT_TRUE(wm::IsActiveWindow(widget_on_1st->GetNativeView()));
+
+ event_generator.MoveMouseToCenterOf(widget_on_2nd->GetNativeView());
+ event_generator.ClickLeftButton();
+
+ EXPECT_EQ(widget_on_2nd->GetNativeView(),
+ aura::client::GetFocusClient(root_windows[0])->GetFocusedWindow());
+ EXPECT_TRUE(wm::IsActiveWindow(widget_on_2nd->GetNativeView()));
+}
+
+TEST_F(ExtendedDesktopTest, SystemModal) {
+ if (!SupportsMultipleDisplays())
+ return;
+
+ UpdateDisplay("1000x600,600x400");
+ Shell::RootWindowList root_windows = Shell::GetAllRootWindows();
+
+ views::Widget* widget_on_1st = CreateTestWidget(gfx::Rect(10, 10, 100, 100));
+ EXPECT_TRUE(wm::IsActiveWindow(widget_on_1st->GetNativeView()));
+ EXPECT_EQ(root_windows[0], widget_on_1st->GetNativeView()->GetRootWindow());
+ EXPECT_EQ(root_windows[0], Shell::GetActiveRootWindow());
+
+ // Open system modal. Make sure it's on 2nd root window and active.
+ views::Widget* modal_widget = views::Widget::CreateWindowWithContextAndBounds(
+ new ModalWidgetDelegate(),
+ CurrentContext(),
+ gfx::Rect(1200, 100, 100, 100));
+ modal_widget->Show();
+ EXPECT_TRUE(wm::IsActiveWindow(modal_widget->GetNativeView()));
+ EXPECT_EQ(root_windows[1], modal_widget->GetNativeView()->GetRootWindow());
+ EXPECT_EQ(root_windows[1], Shell::GetActiveRootWindow());
+
+ aura::test::EventGenerator& event_generator(GetEventGenerator());
+
+ // Clicking a widget on widget_on_1st display should not change activation.
+ event_generator.MoveMouseToCenterOf(widget_on_1st->GetNativeView());
+ event_generator.ClickLeftButton();
+ EXPECT_TRUE(wm::IsActiveWindow(modal_widget->GetNativeView()));
+ EXPECT_EQ(root_windows[1], Shell::GetActiveRootWindow());
+
+ // Close system modal and so clicking a widget should work now.
+ modal_widget->Close();
+ event_generator.MoveMouseToCenterOf(widget_on_1st->GetNativeView());
+ event_generator.ClickLeftButton();
+ EXPECT_TRUE(wm::IsActiveWindow(widget_on_1st->GetNativeView()));
+ EXPECT_EQ(root_windows[0], Shell::GetActiveRootWindow());
+}
+
+TEST_F(ExtendedDesktopTest, TestCursor) {
+ if (!SupportsMultipleDisplays())
+ return;
+
+ UpdateDisplay("1000x600,600x400");
+ Shell::RootWindowList root_windows = Shell::GetAllRootWindows();
+ EXPECT_EQ(ui::kCursorPointer, root_windows[0]->last_cursor().native_type());
+ EXPECT_EQ(ui::kCursorPointer, root_windows[1]->last_cursor().native_type());
+ Shell::GetInstance()->cursor_manager()->SetCursor(ui::kCursorCopy);
+ EXPECT_EQ(ui::kCursorCopy, root_windows[0]->last_cursor().native_type());
+ EXPECT_EQ(ui::kCursorCopy, root_windows[1]->last_cursor().native_type());
+}
+
+TEST_F(ExtendedDesktopTest, TestCursorLocation) {
+ if (!SupportsMultipleDisplays())
+ return;
+
+ UpdateDisplay("1000x600,600x400");
+ Shell::RootWindowList root_windows = Shell::GetAllRootWindows();
+ aura::test::WindowTestApi root_window0_test_api(root_windows[0]);
+ aura::test::WindowTestApi root_window1_test_api(root_windows[1]);
+
+ root_windows[0]->MoveCursorTo(gfx::Point(10, 10));
+ EXPECT_EQ("10,10", Shell::GetScreen()->GetCursorScreenPoint().ToString());
+ EXPECT_TRUE(root_window0_test_api.ContainsMouse());
+ EXPECT_FALSE(root_window1_test_api.ContainsMouse());
+ root_windows[1]->MoveCursorTo(gfx::Point(10, 20));
+ EXPECT_EQ("1010,20", Shell::GetScreen()->GetCursorScreenPoint().ToString());
+ EXPECT_FALSE(root_window0_test_api.ContainsMouse());
+ EXPECT_TRUE(root_window1_test_api.ContainsMouse());
+ root_windows[0]->MoveCursorTo(gfx::Point(20, 10));
+ EXPECT_EQ("20,10", Shell::GetScreen()->GetCursorScreenPoint().ToString());
+ EXPECT_TRUE(root_window0_test_api.ContainsMouse());
+ EXPECT_FALSE(root_window1_test_api.ContainsMouse());
+}
+
+TEST_F(ExtendedDesktopTest, CycleWindows) {
+ if (!SupportsMultipleDisplays())
+ return;
+
+ UpdateDisplay("700x500,500x500");
+ Shell::RootWindowList root_windows = Shell::GetAllRootWindows();
+
+ WindowCycleController* controller =
+ Shell::GetInstance()->window_cycle_controller();
+
+ views::Widget* d1_w1 = CreateTestWidget(gfx::Rect(10, 10, 100, 100));
+ EXPECT_EQ(root_windows[0], d1_w1->GetNativeView()->GetRootWindow());
+ views::Widget* d2_w1 = CreateTestWidget(gfx::Rect(800, 10, 100, 100));
+ EXPECT_EQ(root_windows[1], d2_w1->GetNativeView()->GetRootWindow());
+ EXPECT_TRUE(wm::IsActiveWindow(d2_w1->GetNativeView()));
+
+ controller->HandleCycleWindow(WindowCycleController::FORWARD, false);
+ EXPECT_TRUE(wm::IsActiveWindow(d1_w1->GetNativeView()));
+ controller->HandleCycleWindow(WindowCycleController::FORWARD, false);
+ EXPECT_TRUE(wm::IsActiveWindow(d2_w1->GetNativeView()));
+ controller->HandleCycleWindow(WindowCycleController::BACKWARD, false);
+ EXPECT_TRUE(wm::IsActiveWindow(d1_w1->GetNativeView()));
+ controller->HandleCycleWindow(WindowCycleController::BACKWARD, false);
+ EXPECT_TRUE(wm::IsActiveWindow(d2_w1->GetNativeView()));
+
+ // Cycle through all windows across root windows.
+ views::Widget* d1_w2 = CreateTestWidget(gfx::Rect(10, 200, 100, 100));
+ EXPECT_EQ(root_windows[0], d1_w2->GetNativeView()->GetRootWindow());
+ views::Widget* d2_w2 = CreateTestWidget(gfx::Rect(800, 200, 100, 100));
+ EXPECT_EQ(root_windows[1], d2_w2->GetNativeView()->GetRootWindow());
+
+ controller->HandleCycleWindow(WindowCycleController::FORWARD, true);
+ EXPECT_TRUE(wm::IsActiveWindow(d1_w2->GetNativeView()));
+ controller->HandleCycleWindow(WindowCycleController::FORWARD, true);
+ EXPECT_TRUE(wm::IsActiveWindow(d2_w1->GetNativeView()));
+ controller->HandleCycleWindow(WindowCycleController::FORWARD, true);
+ EXPECT_TRUE(wm::IsActiveWindow(d1_w1->GetNativeView()));
+ controller->HandleCycleWindow(WindowCycleController::FORWARD, true);
+ EXPECT_TRUE(wm::IsActiveWindow(d2_w2->GetNativeView()));
+
+ // Backwards
+ controller->HandleCycleWindow(WindowCycleController::BACKWARD, true);
+ EXPECT_TRUE(wm::IsActiveWindow(d1_w1->GetNativeView()));
+ controller->HandleCycleWindow(WindowCycleController::BACKWARD, true);
+ EXPECT_TRUE(wm::IsActiveWindow(d2_w1->GetNativeView()));
+ controller->HandleCycleWindow(WindowCycleController::BACKWARD, true);
+ EXPECT_TRUE(wm::IsActiveWindow(d1_w2->GetNativeView()));
+ controller->HandleCycleWindow(WindowCycleController::BACKWARD, true);
+ EXPECT_TRUE(wm::IsActiveWindow(d2_w2->GetNativeView()));
+}
+
+TEST_F(ExtendedDesktopTest, GetRootWindowAt) {
+ if (!SupportsMultipleDisplays())
+ return;
+
+ UpdateDisplay("700x500,500x500");
+ SetSecondaryDisplayLayout(DisplayLayout::LEFT);
+ Shell::RootWindowList root_windows = Shell::GetAllRootWindows();
+
+ EXPECT_EQ(root_windows[1], wm::GetRootWindowAt(gfx::Point(-400, 100)));
+ EXPECT_EQ(root_windows[1], wm::GetRootWindowAt(gfx::Point(-1, 100)));
+ EXPECT_EQ(root_windows[0], wm::GetRootWindowAt(gfx::Point(0, 300)));
+ EXPECT_EQ(root_windows[0], wm::GetRootWindowAt(gfx::Point(700,300)));
+
+ // Zero origin.
+ EXPECT_EQ(root_windows[0], wm::GetRootWindowAt(gfx::Point(0, 0)));
+
+ // Out of range point should return the primary root window
+ EXPECT_EQ(root_windows[0], wm::GetRootWindowAt(gfx::Point(-600, 0)));
+ EXPECT_EQ(root_windows[0], wm::GetRootWindowAt(gfx::Point(701, 100)));
+}
+
+TEST_F(ExtendedDesktopTest, GetRootWindowMatching) {
+ if (!SupportsMultipleDisplays())
+ return;
+
+ UpdateDisplay("700x500,500x500");
+ SetSecondaryDisplayLayout(DisplayLayout::LEFT);
+
+ Shell::RootWindowList root_windows = Shell::GetAllRootWindows();
+
+ // Containing rect.
+ EXPECT_EQ(root_windows[1],
+ wm::GetRootWindowMatching(gfx::Rect(-300, 10, 50, 50)));
+ EXPECT_EQ(root_windows[0],
+ wm::GetRootWindowMatching(gfx::Rect(100, 10, 50, 50)));
+
+ // Intersecting rect.
+ EXPECT_EQ(root_windows[1],
+ wm::GetRootWindowMatching(gfx::Rect(-200, 0, 300, 300)));
+ EXPECT_EQ(root_windows[0],
+ wm::GetRootWindowMatching(gfx::Rect(-100, 0, 300, 300)));
+
+ // Zero origin.
+ EXPECT_EQ(root_windows[0],
+ wm::GetRootWindowMatching(gfx::Rect(0, 0, 0, 0)));
+ EXPECT_EQ(root_windows[0],
+ wm::GetRootWindowMatching(gfx::Rect(0, 0, 1, 1)));
+
+ // Empty rect.
+ EXPECT_EQ(root_windows[1],
+ wm::GetRootWindowMatching(gfx::Rect(-400, 100, 0, 0)));
+ EXPECT_EQ(root_windows[0],
+ wm::GetRootWindowMatching(gfx::Rect(100, 100, 0, 0)));
+
+ // Out of range rect should return the primary root window.
+ EXPECT_EQ(root_windows[0],
+ wm::GetRootWindowMatching(gfx::Rect(-600, -300, 50, 50)));
+ EXPECT_EQ(root_windows[0],
+ wm::GetRootWindowMatching(gfx::Rect(0, 1000, 50, 50)));
+}
+
+TEST_F(ExtendedDesktopTest, Capture) {
+ if (!SupportsMultipleDisplays())
+ return;
+
+ UpdateDisplay("1000x600,600x400");
+ Shell::RootWindowList root_windows = Shell::GetAllRootWindows();
+
+ aura::test::EventCountDelegate r1_d1;
+ aura::test::EventCountDelegate r1_d2;
+ aura::test::EventCountDelegate r2_d1;
+
+ scoped_ptr<aura::Window> r1_w1(aura::test::CreateTestWindowWithDelegate(
+ &r1_d1, 0, gfx::Rect(10, 10, 100, 100), root_windows[0]));
+ scoped_ptr<aura::Window> r1_w2(aura::test::CreateTestWindowWithDelegate(
+ &r1_d2, 0, gfx::Rect(10, 100, 100, 100), root_windows[0]));
+ scoped_ptr<aura::Window> r2_w1(aura::test::CreateTestWindowWithDelegate(
+ &r2_d1, 0, gfx::Rect(10, 10, 100, 100), root_windows[1]));
+
+ r1_w1->SetCapture();
+
+ EXPECT_EQ(r1_w1.get(),
+ aura::client::GetCaptureWindow(r2_w1->GetRootWindow()));
+
+ aura::test::EventGenerator generator2(root_windows[1]);
+ generator2.MoveMouseToCenterOf(r2_w1.get());
+ generator2.ClickLeftButton();
+ EXPECT_EQ("0 0 0", r2_d1.GetMouseMotionCountsAndReset());
+ EXPECT_EQ("0 0", r2_d1.GetMouseButtonCountsAndReset());
+ // The mouse is outside. On chromeos, the mouse is warped to the
+ // dest root window, but it's not implemented on Win yet, so
+ // no mouse move event on Win.
+ EXPECT_EQ("1 1 0", r1_d1.GetMouseMotionCountsAndReset());
+ EXPECT_EQ("1 1", r1_d1.GetMouseButtonCountsAndReset());
+ // Emulate passive grab. (15,15) on 1st display is (-985,15) on 2nd
+ // display.
+ generator2.MoveMouseTo(-985, 15);
+ EXPECT_EQ("0 1 0", r1_d1.GetMouseMotionCountsAndReset());
+
+ r1_w2->SetCapture();
+ EXPECT_EQ(r1_w2.get(),
+ aura::client::GetCaptureWindow(r2_w1->GetRootWindow()));
+ generator2.MoveMouseBy(10, 10);
+ generator2.ClickLeftButton();
+ EXPECT_EQ("0 0 0", r2_d1.GetMouseMotionCountsAndReset());
+ EXPECT_EQ("0 0", r2_d1.GetMouseButtonCountsAndReset());
+ // mouse is already entered.
+ EXPECT_EQ("0 1 0", r1_d2.GetMouseMotionCountsAndReset());
+ EXPECT_EQ("1 1", r1_d2.GetMouseButtonCountsAndReset());
+ r1_w2->ReleaseCapture();
+ EXPECT_EQ(NULL, aura::client::GetCaptureWindow(r2_w1->GetRootWindow()));
+ generator2.MoveMouseTo(15, 15);
+ generator2.ClickLeftButton();
+ EXPECT_EQ("1 1 0", r2_d1.GetMouseMotionCountsAndReset());
+ EXPECT_EQ("1 1", r2_d1.GetMouseButtonCountsAndReset());
+ // Make sure the mouse_moved_handler_ is properly reset.
+ EXPECT_EQ("0 0 0", r1_d2.GetMouseMotionCountsAndReset());
+ EXPECT_EQ("0 0", r1_d2.GetMouseButtonCountsAndReset());
+}
+
+TEST_F(ExtendedDesktopTest, MoveWindow) {
+ if (!SupportsMultipleDisplays())
+ return;
+
+ UpdateDisplay("1000x600,600x400");
+ Shell::RootWindowList root_windows = Shell::GetAllRootWindows();
+ views::Widget* d1 = CreateTestWidget(gfx::Rect(10, 10, 100, 100));
+
+ EXPECT_EQ(root_windows[0], d1->GetNativeView()->GetRootWindow());
+
+ d1->SetBounds(gfx::Rect(1010, 10, 100, 100));
+ EXPECT_EQ("1010,10 100x100",
+ d1->GetWindowBoundsInScreen().ToString());
+
+ EXPECT_EQ(root_windows[1], d1->GetNativeView()->GetRootWindow());
+
+ d1->SetBounds(gfx::Rect(10, 10, 100, 100));
+ EXPECT_EQ("10,10 100x100",
+ d1->GetWindowBoundsInScreen().ToString());
+
+ EXPECT_EQ(root_windows[0], d1->GetNativeView()->GetRootWindow());
+
+ // Make sure the bounds which doesn't fit to the root window
+ // works correctly.
+ d1->SetBounds(gfx::Rect(1560, 30, 100, 100));
+ EXPECT_EQ(root_windows[1], d1->GetNativeView()->GetRootWindow());
+ EXPECT_EQ("1560,30 100x100",
+ d1->GetWindowBoundsInScreen().ToString());
+
+ // Setting outside of root windows will be moved to primary root window.
+ // TODO(oshima): This one probably should pick the closest root window.
+ d1->SetBounds(gfx::Rect(200, 10, 100, 100));
+ EXPECT_EQ(root_windows[0], d1->GetNativeView()->GetRootWindow());
+}
+
+// Verifies if the mouse event arrives to the window even when the window
+// moves to another root in a pre-target handler. See: crbug.com/157583
+TEST_F(ExtendedDesktopTest, MoveWindowByMouseClick) {
+ if (!SupportsMultipleDisplays())
+ return;
+
+ UpdateDisplay("1000x600,600x400");
+
+ Shell::RootWindowList root_windows = Shell::GetAllRootWindows();
+ aura::test::EventCountDelegate delegate;
+ scoped_ptr<aura::Window> window(aura::test::CreateTestWindowWithDelegate(
+ &delegate, 0, gfx::Rect(10, 10, 100, 100), root_windows[0]));
+ MoveWindowByClickEventHandler event_handler(window.get());
+ window->AddPreTargetHandler(&event_handler);
+
+ aura::test::EventGenerator& event_generator(GetEventGenerator());
+
+ event_generator.MoveMouseToCenterOf(window.get());
+ event_generator.ClickLeftButton();
+ // Both mouse pressed and released arrive at the window and its delegate.
+ EXPECT_EQ("1 1", delegate.GetMouseButtonCountsAndReset());
+ // Also event_handler moves the window to another root at mouse release.
+ EXPECT_EQ(root_windows[1], window->GetRootWindow());
+}
+
+TEST_F(ExtendedDesktopTest, MoveWindowToDisplay) {
+ if (!SupportsMultipleDisplays())
+ return;
+
+ UpdateDisplay("1000x1000,1000x1000");
+ Shell::RootWindowList root_windows = Shell::GetAllRootWindows();
+
+ gfx::Display display0 = Shell::GetScreen()->GetDisplayMatching(
+ root_windows[0]->GetBoundsInScreen());
+ gfx::Display display1 = Shell::GetScreen()->GetDisplayMatching(
+ root_windows[1]->GetBoundsInScreen());
+ EXPECT_NE(display0.id(), display1.id());
+
+ views::Widget* d1 = CreateTestWidget(gfx::Rect(10, 10, 1000, 100));
+ EXPECT_EQ(root_windows[0], d1->GetNativeView()->GetRootWindow());
+
+ // Move the window where the window spans both root windows. Since the second
+ // parameter is |display1|, the window should be shown on the secondary root.
+ d1->GetNativeWindow()->SetBoundsInScreen(gfx::Rect(500, 10, 1000, 100),
+ display1);
+ EXPECT_EQ("500,10 1000x100",
+ d1->GetWindowBoundsInScreen().ToString());
+ EXPECT_EQ(root_windows[1], d1->GetNativeView()->GetRootWindow());
+
+ // Move to the primary root.
+ d1->GetNativeWindow()->SetBoundsInScreen(gfx::Rect(500, 10, 1000, 100),
+ display0);
+ EXPECT_EQ("500,10 1000x100",
+ d1->GetWindowBoundsInScreen().ToString());
+ EXPECT_EQ(root_windows[0], d1->GetNativeView()->GetRootWindow());
+}
+
+TEST_F(ExtendedDesktopTest, MoveWindowWithTransient) {
+ if (!SupportsMultipleDisplays())
+ return;
+
+ UpdateDisplay("1000x600,600x400");
+ Shell::RootWindowList root_windows = Shell::GetAllRootWindows();
+ views::Widget* w1 = CreateTestWidget(gfx::Rect(10, 10, 100, 100));
+ views::Widget* w1_t1 = CreateTestWidgetWithParent(
+ w1, gfx::Rect(50, 50, 50, 50), false /* transient */);
+ // Transient child of the transient child.
+ views::Widget* w1_t11 = CreateTestWidgetWithParent(
+ w1_t1, gfx::Rect(1200, 70, 30, 30), false /* transient */);
+
+ views::Widget* w11 = CreateTestWidgetWithParent(
+ w1, gfx::Rect(10, 10, 40, 40), true /* child */);
+ views::Widget* w11_t1 = CreateTestWidgetWithParent(
+ w1, gfx::Rect(1300, 100, 80, 80), false /* transient */);
+
+ EXPECT_EQ(root_windows[0], w1->GetNativeView()->GetRootWindow());
+ EXPECT_EQ(root_windows[0], w11->GetNativeView()->GetRootWindow());
+ EXPECT_EQ(root_windows[0], w1_t1->GetNativeView()->GetRootWindow());
+ EXPECT_EQ(root_windows[0], w1_t11->GetNativeView()->GetRootWindow());
+ EXPECT_EQ(root_windows[0], w11_t1->GetNativeView()->GetRootWindow());
+ EXPECT_EQ("50,50 50x50",
+ w1_t1->GetWindowBoundsInScreen().ToString());
+ EXPECT_EQ("1200,70 30x30",
+ w1_t11->GetWindowBoundsInScreen().ToString());
+ EXPECT_EQ("20,20 40x40",
+ w11->GetWindowBoundsInScreen().ToString());
+ EXPECT_EQ("1300,100 80x80",
+ w11_t1->GetWindowBoundsInScreen().ToString());
+
+ w1->SetBounds(gfx::Rect(1100,10,100,100));
+
+ EXPECT_EQ(root_windows[1], w1_t1->GetNativeView()->GetRootWindow());
+ EXPECT_EQ(root_windows[1], w1_t1->GetNativeView()->GetRootWindow());
+ EXPECT_EQ(root_windows[1], w1_t11->GetNativeView()->GetRootWindow());
+ EXPECT_EQ(root_windows[1], w11->GetNativeView()->GetRootWindow());
+ EXPECT_EQ(root_windows[1], w11_t1->GetNativeView()->GetRootWindow());
+
+ EXPECT_EQ("1110,20 40x40",
+ w11->GetWindowBoundsInScreen().ToString());
+ // Transient window's screen bounds stays the same.
+ EXPECT_EQ("50,50 50x50",
+ w1_t1->GetWindowBoundsInScreen().ToString());
+ EXPECT_EQ("1200,70 30x30",
+ w1_t11->GetWindowBoundsInScreen().ToString());
+ EXPECT_EQ("1300,100 80x80",
+ w11_t1->GetWindowBoundsInScreen().ToString());
+
+ // Transient window doesn't move between root window unless
+ // its transient parent moves.
+ w1_t1->SetBounds(gfx::Rect(10, 50, 50, 50));
+ EXPECT_EQ(root_windows[1], w1_t1->GetNativeView()->GetRootWindow());
+ EXPECT_EQ("10,50 50x50",
+ w1_t1->GetWindowBoundsInScreen().ToString());
+}
+
+// Test if the Window::ConvertPointToTarget works across root windows.
+// TODO(oshima): Move multiple display suport and this test to aura.
+TEST_F(ExtendedDesktopTest, ConvertPoint) {
+ if (!SupportsMultipleDisplays())
+ return;
+ gfx::Screen* screen = Shell::GetInstance()->screen();
+ UpdateDisplay("1000x600,600x400");
+ Shell::RootWindowList root_windows = Shell::GetAllRootWindows();
+ gfx::Display display_1 = screen->GetDisplayNearestWindow(root_windows[0]);
+ EXPECT_EQ("0,0", display_1.bounds().origin().ToString());
+ gfx::Display display_2 = screen->GetDisplayNearestWindow(root_windows[1]);
+ EXPECT_EQ("1000,0", display_2.bounds().origin().ToString());
+
+ aura::Window* d1 =
+ CreateTestWidget(gfx::Rect(10, 10, 100, 100))->GetNativeView();
+ aura::Window* d2 =
+ CreateTestWidget(gfx::Rect(1020, 20, 100, 100))->GetNativeView();
+ EXPECT_EQ(root_windows[0], d1->GetRootWindow());
+ EXPECT_EQ(root_windows[1], d2->GetRootWindow());
+
+ // Convert point in the Root2's window to the Root1's window Coord.
+ gfx::Point p(0, 0);
+ aura::Window::ConvertPointToTarget(root_windows[1], root_windows[0], &p);
+ EXPECT_EQ("1000,0", p.ToString());
+ p.SetPoint(0, 0);
+ aura::Window::ConvertPointToTarget(d2, d1, &p);
+ EXPECT_EQ("1010,10", p.ToString());
+
+ // Convert point in the Root1's window to the Root2's window Coord.
+ p.SetPoint(0, 0);
+ aura::Window::ConvertPointToTarget(root_windows[0], root_windows[1], &p);
+ EXPECT_EQ("-1000,0", p.ToString());
+ p.SetPoint(0, 0);
+ aura::Window::ConvertPointToTarget(d1, d2, &p);
+ EXPECT_EQ("-1010,-10", p.ToString());
+
+ // Move the 2nd display to the bottom and test again.
+ SetSecondaryDisplayLayout(DisplayLayout::BOTTOM);
+
+ display_2 = screen->GetDisplayNearestWindow(root_windows[1]);
+ EXPECT_EQ("0,600", display_2.bounds().origin().ToString());
+
+ // Convert point in Root2's window to Root1's window Coord.
+ p.SetPoint(0, 0);
+ aura::Window::ConvertPointToTarget(root_windows[1], root_windows[0], &p);
+ EXPECT_EQ("0,600", p.ToString());
+ p.SetPoint(0, 0);
+ aura::Window::ConvertPointToTarget(d2, d1, &p);
+ EXPECT_EQ("10,610", p.ToString());
+
+ // Convert point in Root1's window to Root2's window Coord.
+ p.SetPoint(0, 0);
+ aura::Window::ConvertPointToTarget(root_windows[0], root_windows[1], &p);
+ EXPECT_EQ("0,-600", p.ToString());
+ p.SetPoint(0, 0);
+ aura::Window::ConvertPointToTarget(d1, d2, &p);
+ EXPECT_EQ("-10,-610", p.ToString());
+}
+
+TEST_F(ExtendedDesktopTest, OpenSystemTray) {
+ if (!SupportsMultipleDisplays())
+ return;
+
+ UpdateDisplay("500x600,600x400");
+ SystemTray* tray = ash::Shell::GetInstance()->GetPrimarySystemTray();
+ ASSERT_FALSE(tray->HasSystemBubble());
+
+ aura::test::EventGenerator& event_generator(GetEventGenerator());
+
+ // Opens the tray by a dummy click event and makes sure that adding/removing
+ // displays doesn't break anything.
+ event_generator.MoveMouseToCenterOf(tray->GetWidget()->GetNativeWindow());
+ event_generator.ClickLeftButton();
+ EXPECT_TRUE(tray->HasSystemBubble());
+
+ UpdateDisplay("500x600");
+ EXPECT_TRUE(tray->HasSystemBubble());
+ UpdateDisplay("500x600,600x400");
+ EXPECT_TRUE(tray->HasSystemBubble());
+
+ // Closes the tray and again makes sure that adding/removing displays doesn't
+ // break anything.
+ event_generator.ClickLeftButton();
+ RunAllPendingInMessageLoop();
+
+ EXPECT_FALSE(tray->HasSystemBubble());
+
+ UpdateDisplay("500x600");
+ EXPECT_FALSE(tray->HasSystemBubble());
+ UpdateDisplay("500x600,600x400");
+ EXPECT_FALSE(tray->HasSystemBubble());
+}
+
+TEST_F(ExtendedDesktopTest, StayInSameRootWindow) {
+ if (!SupportsMultipleDisplays())
+ return;
+
+ UpdateDisplay("100x100,200x200");
+ Shell::RootWindowList root_windows = Shell::GetAllRootWindows();
+ views::Widget* w1 = CreateTestWidget(gfx::Rect(10, 10, 50, 50));
+ EXPECT_EQ(root_windows[0], w1->GetNativeView()->GetRootWindow());
+ w1->SetBounds(gfx::Rect(150, 10, 50, 50));
+ EXPECT_EQ(root_windows[1], w1->GetNativeView()->GetRootWindow());
+
+ // The widget stays in the same root if kStayInSameRootWindowKey is set to
+ // true.
+ w1->GetNativeView()->SetProperty(internal::kStayInSameRootWindowKey, true);
+ w1->SetBounds(gfx::Rect(10, 10, 50, 50));
+ EXPECT_EQ(root_windows[1], w1->GetNativeView()->GetRootWindow());
+
+ // The widget should now move to the 1st root window without the property.
+ w1->GetNativeView()->ClearProperty(internal::kStayInSameRootWindowKey);
+ w1->SetBounds(gfx::Rect(10, 10, 50, 50));
+ EXPECT_EQ(root_windows[0], w1->GetNativeView()->GetRootWindow());
+
+ // a window in SettingsBubbleContainer and StatusContainer should
+ // not move to another root window regardles of the bounds specified.
+ aura::Window* settings_bubble_container =
+ Shell::GetPrimaryRootWindowController()->GetContainer(
+ internal::kShellWindowId_SettingBubbleContainer);
+ aura::Window* window = aura::test::CreateTestWindowWithId(
+ 100, settings_bubble_container);
+ window->SetBoundsInScreen(gfx::Rect(150, 10, 50, 50),
+ ScreenAsh::GetSecondaryDisplay());
+ EXPECT_EQ(root_windows[0], window->GetRootWindow());
+
+ aura::Window* status_container =
+ Shell::GetPrimaryRootWindowController()->GetContainer(
+ internal::kShellWindowId_StatusContainer);
+ window = aura::test::CreateTestWindowWithId(100, status_container);
+ window->SetBoundsInScreen(gfx::Rect(150, 10, 50, 50),
+ ScreenAsh::GetSecondaryDisplay());
+ EXPECT_EQ(root_windows[0], window->GetRootWindow());
+}
+
+TEST_F(ExtendedDesktopTest, KeyEventsOnLockScreen) {
+ if (!SupportsMultipleDisplays())
+ return;
+
+ UpdateDisplay("100x100,200x200");
+ Shell::RootWindowList root_windows = Shell::GetAllRootWindows();
+
+ // Create normal windows on both displays.
+ views::Widget* widget1 = CreateTestWidget(
+ Shell::GetScreen()->GetPrimaryDisplay().bounds());
+ widget1->Show();
+ EXPECT_EQ(root_windows[0], widget1->GetNativeView()->GetRootWindow());
+ views::Widget* widget2 = CreateTestWidget(
+ ScreenAsh::GetSecondaryDisplay().bounds());
+ widget2->Show();
+ EXPECT_EQ(root_windows[1], widget2->GetNativeView()->GetRootWindow());
+
+ // Create a LockScreen window.
+ views::Widget* lock_widget = CreateTestWidget(
+ Shell::GetScreen()->GetPrimaryDisplay().bounds());
+ views::Textfield* textfield = new views::Textfield;
+ lock_widget->client_view()->AddChildView(textfield);
+
+ ash::Shell::GetContainer(
+ Shell::GetPrimaryRootWindow(),
+ ash::internal::kShellWindowId_LockScreenContainer)->
+ AddChild(lock_widget->GetNativeView());
+ lock_widget->Show();
+ textfield->RequestFocus();
+
+ aura::client::FocusClient* focus_client =
+ aura::client::GetFocusClient(root_windows[0]);
+ EXPECT_EQ(lock_widget->GetNativeView(), focus_client->GetFocusedWindow());
+
+ // The lock window should get events on both root windows.
+ aura::test::EventGenerator& event_generator(GetEventGenerator());
+
+ event_generator.set_current_root_window(root_windows[0]);
+ event_generator.PressKey(ui::VKEY_A, 0);
+ event_generator.ReleaseKey(ui::VKEY_A, 0);
+ EXPECT_EQ(lock_widget->GetNativeView(), focus_client->GetFocusedWindow());
+ EXPECT_EQ("a", UTF16ToASCII(textfield->text()));
+
+ event_generator.set_current_root_window(root_windows[1]);
+ event_generator.PressKey(ui::VKEY_B, 0);
+ event_generator.ReleaseKey(ui::VKEY_B, 0);
+ EXPECT_EQ(lock_widget->GetNativeView(), focus_client->GetFocusedWindow());
+ EXPECT_EQ("ab", UTF16ToASCII(textfield->text()));
+
+ // Deleting 2nd display. The lock window still should get the events.
+ UpdateDisplay("100x100");
+ event_generator.PressKey(ui::VKEY_C, 0);
+ event_generator.ReleaseKey(ui::VKEY_C, 0);
+ EXPECT_EQ(lock_widget->GetNativeView(), focus_client->GetFocusedWindow());
+ EXPECT_EQ("abc", UTF16ToASCII(textfield->text()));
+
+ // Creating 2nd display again, and lock window still should get events
+ // on both root windows.
+ UpdateDisplay("100x100,200x200");
+ root_windows = Shell::GetAllRootWindows();
+ event_generator.set_current_root_window(root_windows[0]);
+ event_generator.PressKey(ui::VKEY_D, 0);
+ event_generator.ReleaseKey(ui::VKEY_D, 0);
+ EXPECT_EQ(lock_widget->GetNativeView(), focus_client->GetFocusedWindow());
+ EXPECT_EQ("abcd", UTF16ToASCII(textfield->text()));
+
+ event_generator.set_current_root_window(root_windows[1]);
+ event_generator.PressKey(ui::VKEY_E, 0);
+ event_generator.ReleaseKey(ui::VKEY_E, 0);
+ EXPECT_EQ(lock_widget->GetNativeView(), focus_client->GetFocusedWindow());
+ EXPECT_EQ("abcde", UTF16ToASCII(textfield->text()));
+}
+
+TEST_F(ExtendedDesktopTest, PassiveGrab) {
+ if (!SupportsMultipleDisplays())
+ return;
+
+ EventLocationRecordingEventHandler event_handler;
+ ash::Shell::GetInstance()->AddPreTargetHandler(&event_handler);
+
+ UpdateDisplay("300x300,200x200");
+
+ views::Widget* widget = CreateTestWidget(gfx::Rect(50, 50, 200, 200));
+ widget->Show();
+ ASSERT_EQ("50,50 200x200", widget->GetWindowBoundsInScreen().ToString());
+
+ aura::test::EventGenerator& generator(GetEventGenerator());
+ generator.MoveMouseTo(150, 150);
+ EXPECT_EQ("100,100 150,150", event_handler.GetLocationsAndReset());
+
+ generator.PressLeftButton();
+ generator.MoveMouseTo(400, 150);
+
+ EXPECT_EQ("350,100 400,150", event_handler.GetLocationsAndReset());
+
+ generator.ReleaseLeftButton();
+ EXPECT_EQ("-999,-999 -999,-999", event_handler.GetLocationsAndReset());
+
+ generator.MoveMouseTo(400, 150);
+ EXPECT_EQ("100,150 100,150", event_handler.GetLocationsAndReset());
+
+ ash::Shell::GetInstance()->RemovePreTargetHandler(&event_handler);
+}
+
+} // namespace ash
diff --git a/chromium/ash/focus_cycler.cc b/chromium/ash/focus_cycler.cc
new file mode 100644
index 00000000000..c83db87b134
--- /dev/null
+++ b/chromium/ash/focus_cycler.cc
@@ -0,0 +1,115 @@
+// Copyright (c) 2012 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.
+
+#include "ash/focus_cycler.h"
+
+#include "ash/shell.h"
+#include "ash/wm/mru_window_tracker.h"
+#include "ash/wm/window_cycle_controller.h"
+#include "ash/wm/window_util.h"
+#include "ui/aura/client/activation_client.h"
+#include "ui/aura/window.h"
+#include "ui/views/accessible_pane_view.h"
+#include "ui/views/focus/focus_search.h"
+#include "ui/views/widget/widget.h"
+
+namespace ash {
+
+namespace {
+
+bool HasFocusableWindow() {
+ return !MruWindowTracker::BuildWindowList(false).empty();
+}
+
+} // namespace
+
+namespace internal {
+
+FocusCycler::FocusCycler() : widget_activating_(NULL) {
+}
+
+FocusCycler::~FocusCycler() {
+}
+
+void FocusCycler::AddWidget(views::Widget* widget) {
+ widgets_.push_back(widget);
+}
+
+void FocusCycler::RotateFocus(Direction direction) {
+ aura::Window* window = ash::wm::GetActiveWindow();
+ if (window) {
+ views::Widget* widget = views::Widget::GetWidgetForNativeWindow(window);
+ // First try to rotate focus within the active widget. If that succeeds,
+ // we're done.
+ if (widget && widget->GetFocusManager()->RotatePaneFocus(
+ direction == BACKWARD ?
+ views::FocusManager::kBackward : views::FocusManager::kForward,
+ views::FocusManager::kNoWrap)) {
+ return;
+ }
+ }
+
+ const bool has_window = HasFocusableWindow();
+ int index = 0;
+ int count = static_cast<int>(widgets_.size());
+ int browser_index = has_window ? count : -1;
+
+ for (; index < count; ++index) {
+ if (widgets_[index]->IsActive())
+ break;
+ }
+
+ int start_index = index;
+
+ if (has_window)
+ ++count;
+
+ for (;;) {
+ if (direction == FORWARD)
+ index = (index + 1) % count;
+ else
+ index = ((index - 1) + count) % count;
+
+ // Ensure that we don't loop more than once.
+ if (index == start_index)
+ break;
+
+ if (index == browser_index) {
+ // Activate the most recently active browser window.
+ ash::Shell::GetInstance()->window_cycle_controller()->HandleCycleWindow(
+ WindowCycleController::FORWARD, false);
+
+ // Rotate pane focus within that window.
+ aura::Window* window = ash::wm::GetActiveWindow();
+ if (!window)
+ break;
+ views::Widget* widget = views::Widget::GetWidgetForNativeWindow(window);
+ if (!widget)
+ break;
+ views::FocusManager* focus_manager = widget->GetFocusManager();
+ focus_manager->ClearFocus();
+ focus_manager->RotatePaneFocus(
+ direction == BACKWARD ?
+ views::FocusManager::kBackward : views::FocusManager::kForward,
+ views::FocusManager::kWrap);
+ break;
+ } else {
+ if (FocusWidget(widgets_[index]))
+ break;
+ }
+ }
+}
+
+bool FocusCycler::FocusWidget(views::Widget* widget) {
+ // Note: It is not necessary to set the focus directly to the pane since that
+ // will be taken care of by the widget activation.
+ widget_activating_ = widget;
+ widget->Activate();
+ widget_activating_ = NULL;
+ return widget->IsActive();
+}
+
+} // namespace internal
+
+} // namespace ash
diff --git a/chromium/ash/focus_cycler.h b/chromium/ash/focus_cycler.h
new file mode 100644
index 00000000000..d0071d10788
--- /dev/null
+++ b/chromium/ash/focus_cycler.h
@@ -0,0 +1,61 @@
+// Copyright (c) 2012 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 FOCUS_CYCLER_H_
+#define FOCUS_CYCLER_H_
+
+#include <vector>
+
+#include "ash/ash_export.h"
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+
+namespace views {
+class Widget;
+} // namespace views
+
+namespace ash {
+
+namespace internal {
+
+// This class handles moving focus between a set of widgets and the main browser
+// window.
+class ASH_EXPORT FocusCycler {
+ public:
+ enum Direction {
+ FORWARD,
+ BACKWARD
+ };
+
+ FocusCycler();
+ ~FocusCycler();
+
+ // Returns the widget the FocusCycler is attempting to activate or NULL if
+ // FocusCycler is not activating any widgets.
+ const views::Widget* widget_activating() const { return widget_activating_; }
+
+ // Add a widget to the focus cycle. The widget needs to have an
+ // AccessiblePaneView as the content view.
+ void AddWidget(views::Widget* widget);
+
+ // Move focus to the next widget.
+ void RotateFocus(Direction direction);
+
+ // Moves focus the specified widget. Returns true if the widget was activated.
+ bool FocusWidget(views::Widget* widget);
+
+ private:
+ std::vector<views::Widget*> widgets_;
+
+ // See description above getter.
+ views::Widget* widget_activating_;
+
+ DISALLOW_COPY_AND_ASSIGN(FocusCycler);
+};
+
+} // namespace internal
+
+} // namespace ash
+
+#endif // FOCUS_CYCLER_H_
diff --git a/chromium/ash/focus_cycler_unittest.cc b/chromium/ash/focus_cycler_unittest.cc
new file mode 100644
index 00000000000..aebede49b07
--- /dev/null
+++ b/chromium/ash/focus_cycler_unittest.cc
@@ -0,0 +1,402 @@
+// Copyright (c) 2012 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.
+
+#include "ash/focus_cycler.h"
+
+#include "ash/launcher/launcher.h"
+#include "ash/root_window_controller.h"
+#include "ash/shelf/shelf_widget.h"
+#include "ash/shell.h"
+#include "ash/shell_window_ids.h"
+#include "ash/system/status_area_widget.h"
+#include "ash/system/status_area_widget_delegate.h"
+#include "ash/system/tray/system_tray.h"
+#include "ash/wm/window_util.h"
+#include "ash/test/ash_test_base.h"
+#include "ash/shell_factory.h"
+#include "ui/aura/root_window.h"
+#include "ui/aura/test/event_generator.h"
+#include "ui/aura/test/test_windows.h"
+#include "ui/aura/window.h"
+#include "ui/views/accessible_pane_view.h"
+#include "ui/views/controls/button/menu_button.h"
+#include "ui/views/widget/widget.h"
+
+namespace ash {
+namespace test {
+
+using aura::Window;
+using internal::FocusCycler;
+
+namespace {
+
+internal::StatusAreaWidgetDelegate* GetStatusAreaWidgetDelegate(
+ views::Widget* widget) {
+ return static_cast<internal::StatusAreaWidgetDelegate*>(
+ widget->GetContentsView());
+}
+
+class PanedWidgetDelegate : public views::WidgetDelegate {
+ public:
+ PanedWidgetDelegate(views::Widget* widget) : widget_(widget) {}
+
+ void SetAccessiblePanes(const std::vector<views::View*>& panes) {
+ accessible_panes_ = panes;
+ }
+
+ // views::WidgetDelegate.
+ virtual void GetAccessiblePanes(std::vector<views::View*>* panes) OVERRIDE {
+ std::copy(accessible_panes_.begin(),
+ accessible_panes_.end(),
+ std::back_inserter(*panes));
+ }
+ virtual views::Widget* GetWidget() OVERRIDE {
+ return widget_;
+ };
+ virtual const views::Widget* GetWidget() const OVERRIDE {
+ return widget_;
+ }
+
+ private:
+ views::Widget* widget_;
+ std::vector<views::View*> accessible_panes_;
+};
+
+} // namespace
+
+class FocusCyclerTest : public AshTestBase {
+ public:
+ FocusCyclerTest() {}
+
+ virtual void SetUp() OVERRIDE {
+ AshTestBase::SetUp();
+
+ focus_cycler_.reset(new FocusCycler());
+
+ ASSERT_TRUE(Launcher::ForPrimaryDisplay());
+ }
+
+ virtual void TearDown() OVERRIDE {
+ if (tray_) {
+ GetStatusAreaWidgetDelegate(tray_->GetWidget())->
+ SetFocusCyclerForTesting(NULL);
+ tray_.reset();
+ }
+
+ shelf_widget()->SetFocusCycler(NULL);
+
+ focus_cycler_.reset();
+
+ AshTestBase::TearDown();
+ }
+
+ protected:
+ // Creates the system tray, returning true on success.
+ bool CreateTray() {
+ if (tray_)
+ return false;
+ aura::Window* parent = Shell::GetPrimaryRootWindowController()->
+ GetContainer(ash::internal::kShellWindowId_StatusContainer);
+
+ internal::StatusAreaWidget* widget = new internal::StatusAreaWidget(parent);
+ widget->CreateTrayViews();
+ widget->Show();
+ tray_.reset(widget->system_tray());
+ if (!tray_->GetWidget())
+ return false;
+ focus_cycler_->AddWidget(tray()->GetWidget());
+ GetStatusAreaWidgetDelegate(tray_->GetWidget())->SetFocusCyclerForTesting(
+ focus_cycler());
+ return true;
+ }
+
+ FocusCycler* focus_cycler() { return focus_cycler_.get(); }
+
+ SystemTray* tray() { return tray_.get(); }
+
+ ShelfWidget* shelf_widget() {
+ return Launcher::ForPrimaryDisplay()->shelf_widget();
+ }
+
+ void InstallFocusCycleOnShelf() {
+ // Add the shelf.
+ shelf_widget()->SetFocusCycler(focus_cycler());
+ }
+
+ private:
+ scoped_ptr<FocusCycler> focus_cycler_;
+ scoped_ptr<SystemTray> tray_;
+
+ DISALLOW_COPY_AND_ASSIGN(FocusCyclerTest);
+};
+
+TEST_F(FocusCyclerTest, CycleFocusBrowserOnly) {
+ // Create a single test window.
+ scoped_ptr<Window> window0(CreateTestWindowInShellWithId(0));
+ wm::ActivateWindow(window0.get());
+ EXPECT_TRUE(wm::IsActiveWindow(window0.get()));
+
+ // Cycle the window
+ focus_cycler()->RotateFocus(FocusCycler::FORWARD);
+ EXPECT_TRUE(wm::IsActiveWindow(window0.get()));
+}
+
+TEST_F(FocusCyclerTest, CycleFocusForward) {
+ ASSERT_TRUE(CreateTray());
+
+ InstallFocusCycleOnShelf();
+
+ // Create a single test window.
+ scoped_ptr<Window> window0(CreateTestWindowInShellWithId(0));
+ wm::ActivateWindow(window0.get());
+ EXPECT_TRUE(wm::IsActiveWindow(window0.get()));
+
+ // Cycle focus to the status area.
+ focus_cycler()->RotateFocus(FocusCycler::FORWARD);
+ EXPECT_TRUE(tray()->GetWidget()->IsActive());
+
+ // Cycle focus to the shelf.
+ focus_cycler()->RotateFocus(FocusCycler::FORWARD);
+ EXPECT_TRUE(shelf_widget()->IsActive());
+
+ // Cycle focus to the browser.
+ focus_cycler()->RotateFocus(FocusCycler::FORWARD);
+ EXPECT_TRUE(wm::IsActiveWindow(window0.get()));
+}
+
+TEST_F(FocusCyclerTest, CycleFocusBackward) {
+ ASSERT_TRUE(CreateTray());
+
+ InstallFocusCycleOnShelf();
+
+ // Create a single test window.
+ scoped_ptr<Window> window0(CreateTestWindowInShellWithId(0));
+ wm::ActivateWindow(window0.get());
+ EXPECT_TRUE(wm::IsActiveWindow(window0.get()));
+
+ // Cycle focus to the shelf.
+ focus_cycler()->RotateFocus(FocusCycler::BACKWARD);
+ EXPECT_TRUE(shelf_widget()->IsActive());
+
+ // Cycle focus to the status area.
+ focus_cycler()->RotateFocus(FocusCycler::BACKWARD);
+ EXPECT_TRUE(tray()->GetWidget()->IsActive());
+
+ // Cycle focus to the browser.
+ focus_cycler()->RotateFocus(FocusCycler::BACKWARD);
+ EXPECT_TRUE(wm::IsActiveWindow(window0.get()));
+}
+
+TEST_F(FocusCyclerTest, CycleFocusForwardBackward) {
+ ASSERT_TRUE(CreateTray());
+
+ InstallFocusCycleOnShelf();
+
+ // Create a single test window.
+ scoped_ptr<Window> window0(CreateTestWindowInShellWithId(0));
+ wm::ActivateWindow(window0.get());
+ EXPECT_TRUE(wm::IsActiveWindow(window0.get()));
+
+ // Cycle focus to the shelf.
+ focus_cycler()->RotateFocus(FocusCycler::BACKWARD);
+ EXPECT_TRUE(shelf_widget()->IsActive());
+
+ // Cycle focus to the status area.
+ focus_cycler()->RotateFocus(FocusCycler::BACKWARD);
+ EXPECT_TRUE(tray()->GetWidget()->IsActive());
+
+ // Cycle focus to the browser.
+ focus_cycler()->RotateFocus(FocusCycler::BACKWARD);
+ EXPECT_TRUE(wm::IsActiveWindow(window0.get()));
+
+ // Cycle focus to the status area.
+ focus_cycler()->RotateFocus(FocusCycler::FORWARD);
+ EXPECT_TRUE(tray()->GetWidget()->IsActive());
+
+ // Cycle focus to the shelf.
+ focus_cycler()->RotateFocus(FocusCycler::FORWARD);
+ EXPECT_TRUE(shelf_widget()->IsActive());
+
+ // Cycle focus to the browser.
+ focus_cycler()->RotateFocus(FocusCycler::FORWARD);
+ EXPECT_TRUE(wm::IsActiveWindow(window0.get()));
+}
+
+TEST_F(FocusCyclerTest, CycleFocusNoBrowser) {
+ ASSERT_TRUE(CreateTray());
+
+ InstallFocusCycleOnShelf();
+
+ // Add the shelf and focus it.
+ focus_cycler()->FocusWidget(shelf_widget());
+
+ // Cycle focus to the status area.
+ focus_cycler()->RotateFocus(FocusCycler::FORWARD);
+ EXPECT_TRUE(tray()->GetWidget()->IsActive());
+
+ // Cycle focus to the shelf.
+ focus_cycler()->RotateFocus(FocusCycler::FORWARD);
+ EXPECT_TRUE(shelf_widget()->IsActive());
+
+ // Cycle focus to the status area.
+ focus_cycler()->RotateFocus(FocusCycler::FORWARD);
+ EXPECT_TRUE(tray()->GetWidget()->IsActive());
+
+ // Cycle focus to the shelf.
+ focus_cycler()->RotateFocus(FocusCycler::BACKWARD);
+ EXPECT_TRUE(shelf_widget()->IsActive());
+
+ // Cycle focus to the status area.
+ focus_cycler()->RotateFocus(FocusCycler::BACKWARD);
+ EXPECT_TRUE(tray()->GetWidget()->IsActive());
+}
+
+TEST_F(FocusCyclerTest, Shelf_CycleFocusForward) {
+ ASSERT_TRUE(CreateTray());
+ InstallFocusCycleOnShelf();
+ shelf_widget()->Hide();
+
+ // Create a single test window.
+ scoped_ptr<Window> window0(CreateTestWindowInShellWithId(0));
+ wm::ActivateWindow(window0.get());
+ EXPECT_TRUE(wm::IsActiveWindow(window0.get()));
+
+ // Cycle focus to the status area.
+ focus_cycler()->RotateFocus(FocusCycler::FORWARD);
+ EXPECT_TRUE(tray()->GetWidget()->IsActive());
+
+ // Cycle focus to the browser.
+ focus_cycler()->RotateFocus(FocusCycler::FORWARD);
+ EXPECT_TRUE(wm::IsActiveWindow(window0.get()));
+}
+
+TEST_F(FocusCyclerTest, Shelf_CycleFocusBackwardInvisible) {
+ ASSERT_TRUE(CreateTray());
+ InstallFocusCycleOnShelf();
+ shelf_widget()->Hide();
+
+ // Create a single test window.
+ scoped_ptr<Window> window0(CreateTestWindowInShellWithId(0));
+ wm::ActivateWindow(window0.get());
+ EXPECT_TRUE(wm::IsActiveWindow(window0.get()));
+
+ // Cycle focus to the status area.
+ focus_cycler()->RotateFocus(FocusCycler::BACKWARD);
+ EXPECT_TRUE(tray()->GetWidget()->IsActive());
+
+ // Cycle focus to the browser.
+ focus_cycler()->RotateFocus(FocusCycler::BACKWARD);
+ EXPECT_TRUE(wm::IsActiveWindow(window0.get()));
+}
+
+TEST_F(FocusCyclerTest, CycleFocusThroughWindowWithPanes) {
+ ASSERT_TRUE(CreateTray());
+
+ InstallFocusCycleOnShelf();
+
+ scoped_ptr<views::Widget> browser_widget(new views::Widget);
+ PanedWidgetDelegate* test_widget_delegate =
+ new PanedWidgetDelegate(browser_widget.get());
+ views::Widget::InitParams widget_params(
+ views::Widget::InitParams::TYPE_WINDOW);
+ widget_params.context = CurrentContext();
+ widget_params.delegate = test_widget_delegate;
+ widget_params.ownership =
+ views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
+ browser_widget->Init(widget_params);
+ browser_widget->Show();
+
+ aura::Window* browser_window = browser_widget->GetNativeView();
+
+ views::View* root_view = browser_widget->GetRootView();
+
+ views::AccessiblePaneView* pane1 = new views::AccessiblePaneView();
+ root_view->AddChildView(pane1);
+
+ views::View* view1 = new views::View;
+ view1->set_focusable(true);
+ pane1->AddChildView(view1);
+
+ views::View* view2 = new views::View;
+ view2->set_focusable(true);
+ pane1->AddChildView(view2);
+
+ views::AccessiblePaneView* pane2 = new views::AccessiblePaneView();
+ root_view->AddChildView(pane2);
+
+ views::View* view3 = new views::View;
+ view3->set_focusable(true);
+ pane2->AddChildView(view3);
+
+ views::View* view4 = new views::View;
+ view4->set_focusable(true);
+ pane2->AddChildView(view4);
+
+ std::vector<views::View*> panes;
+ panes.push_back(pane1);
+ panes.push_back(pane2);
+
+ test_widget_delegate->SetAccessiblePanes(panes);
+
+ views::FocusManager* focus_manager = browser_widget->GetFocusManager();
+
+ // Cycle focus to the status area.
+ focus_cycler()->RotateFocus(FocusCycler::FORWARD);
+ EXPECT_TRUE(tray()->GetWidget()->IsActive());
+
+ // Cycle focus to the shelf.
+ focus_cycler()->RotateFocus(FocusCycler::FORWARD);
+ EXPECT_TRUE(shelf_widget()->IsActive());
+
+ // Cycle focus to the first pane in the browser.
+ focus_cycler()->RotateFocus(FocusCycler::FORWARD);
+ EXPECT_TRUE(wm::IsActiveWindow(browser_window));
+ EXPECT_EQ(focus_manager->GetFocusedView(), view1);
+
+ // Cycle focus to the second pane in the browser.
+ focus_cycler()->RotateFocus(FocusCycler::FORWARD);
+ EXPECT_TRUE(wm::IsActiveWindow(browser_window));
+ EXPECT_EQ(focus_manager->GetFocusedView(), view3);
+
+ // Cycle focus back to the status area.
+ focus_cycler()->RotateFocus(FocusCycler::FORWARD);
+ EXPECT_TRUE(tray()->GetWidget()->IsActive());
+
+ // Reverse direction - back to the second pane in the browser.
+ focus_cycler()->RotateFocus(FocusCycler::BACKWARD);
+ EXPECT_TRUE(wm::IsActiveWindow(browser_window));
+ EXPECT_EQ(focus_manager->GetFocusedView(), view3);
+
+ // Back to the first pane in the browser.
+ focus_cycler()->RotateFocus(FocusCycler::BACKWARD);
+ EXPECT_TRUE(wm::IsActiveWindow(browser_window));
+ EXPECT_EQ(focus_manager->GetFocusedView(), view1);
+
+ // Back to the shelf.
+ focus_cycler()->RotateFocus(FocusCycler::BACKWARD);
+ EXPECT_TRUE(shelf_widget()->IsActive());
+
+ // Back to the status area.
+ focus_cycler()->RotateFocus(FocusCycler::BACKWARD);
+ EXPECT_TRUE(tray()->GetWidget()->IsActive());
+
+ // Pressing "Escape" while on the status area should
+ // deactivate it, and activate the browser window.
+ aura::RootWindow* root = Shell::GetPrimaryRootWindow();
+ aura::test::EventGenerator event_generator(root, root);
+ event_generator.PressKey(ui::VKEY_ESCAPE, 0);
+ EXPECT_TRUE(wm::IsActiveWindow(browser_window));
+ EXPECT_EQ(focus_manager->GetFocusedView(), view1);
+
+ // Similarly, pressing "Escape" while on the shelf.
+ // should do the same thing.
+ focus_cycler()->RotateFocus(FocusCycler::BACKWARD);
+ EXPECT_TRUE(shelf_widget()->IsActive());
+ event_generator.PressKey(ui::VKEY_ESCAPE, 0);
+ EXPECT_TRUE(wm::IsActiveWindow(browser_window));
+ EXPECT_EQ(focus_manager->GetFocusedView(), view1);
+}
+
+} // namespace test
+} // namespace ash
diff --git a/chromium/ash/high_contrast/high_contrast_controller.cc b/chromium/ash/high_contrast/high_contrast_controller.cc
new file mode 100644
index 00000000000..31a0dbbcc6c
--- /dev/null
+++ b/chromium/ash/high_contrast/high_contrast_controller.cc
@@ -0,0 +1,36 @@
+// Copyright (c) 2012 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.
+
+#include "ash/high_contrast/high_contrast_controller.h"
+
+#include "ash/shell.h"
+#include "ui/aura/root_window.h"
+#include "ui/compositor/layer.h"
+
+namespace ash {
+
+HighContrastController::HighContrastController()
+ : enabled_(false) {
+}
+
+void HighContrastController::SetEnabled(bool enabled) {
+ enabled_ = enabled;
+
+ // Update all active displays.
+ Shell::RootWindowList root_window_list = Shell::GetAllRootWindows();
+ for (Shell::RootWindowList::iterator it = root_window_list.begin();
+ it != root_window_list.end(); it++) {
+ UpdateDisplay(*it);
+ }
+}
+
+void HighContrastController::OnRootWindowAdded(aura::RootWindow* root_window) {
+ UpdateDisplay(root_window);
+}
+
+void HighContrastController::UpdateDisplay(aura::RootWindow* root_window) {
+ root_window->layer()->SetLayerInverted(enabled_);
+}
+
+} // namespace ash
diff --git a/chromium/ash/high_contrast/high_contrast_controller.h b/chromium/ash/high_contrast/high_contrast_controller.h
new file mode 100644
index 00000000000..2afd60b3aa4
--- /dev/null
+++ b/chromium/ash/high_contrast/high_contrast_controller.h
@@ -0,0 +1,41 @@
+// Copyright (c) 2012 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 ASH_HIGH_CONTRAST_HIGH_CONTRAST_CONTROLLER_H_
+#define ASH_HIGH_CONTRAST_HIGH_CONTRAST_CONTROLLER_H_
+
+#include "ash/ash_export.h"
+#include "base/basictypes.h"
+
+namespace aura {
+class RootWindow;
+}
+
+namespace ash {
+
+class ASH_EXPORT HighContrastController {
+ public:
+ HighContrastController();
+
+ ~HighContrastController() {}
+
+ // Set high contrast mode and update all available displays.
+ void SetEnabled(bool enabled);
+
+ // Update high contrast mode on the just added display.
+ void OnRootWindowAdded(aura::RootWindow* root_window);
+
+ private:
+ // Update high contrast mode on the passed display.
+ void UpdateDisplay(aura::RootWindow* root_window);
+
+ // Indicates if the high contrast mode is enabled or disabled.
+ bool enabled_;
+
+ DISALLOW_COPY_AND_ASSIGN(HighContrastController);
+};
+
+} // namespace ash
+
+#endif // ASH_HIGH_CONTRAST_HIGH_CONTRAST_CONTROLLER_H_
diff --git a/chromium/ash/host/root_window_host_factory.cc b/chromium/ash/host/root_window_host_factory.cc
new file mode 100644
index 00000000000..a0dbc0e5906
--- /dev/null
+++ b/chromium/ash/host/root_window_host_factory.cc
@@ -0,0 +1,31 @@
+// Copyright (c) 2012 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.
+
+#include "ash/host/root_window_host_factory.h"
+
+#include "ui/aura/root_window_host.h"
+
+namespace {
+
+class RootWindowHostFactoryImpl : public ash::RootWindowHostFactory {
+ public:
+ RootWindowHostFactoryImpl() {}
+
+ // Overridden from RootWindowHostFactory:
+ virtual aura::RootWindowHost* CreateRootWindowHost(
+ const gfx::Rect& initial_bounds) OVERRIDE {
+ return aura::RootWindowHost::Create(initial_bounds);
+ }
+};
+
+}
+
+namespace ash {
+
+// static
+RootWindowHostFactory* RootWindowHostFactory::Create() {
+ return new RootWindowHostFactoryImpl;
+}
+
+} // namespace ash
diff --git a/chromium/ash/host/root_window_host_factory.h b/chromium/ash/host/root_window_host_factory.h
new file mode 100644
index 00000000000..d357d9b2cb3
--- /dev/null
+++ b/chromium/ash/host/root_window_host_factory.h
@@ -0,0 +1,33 @@
+// Copyright (c) 2012 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 ASH_HOST_ROOT_WINDOW_HOST_FACTORY_H_
+#define ASH_HOST_ROOT_WINDOW_HOST_FACTORY_H_
+
+#include "ash/ash_export.h"
+#include "ui/gfx/rect.h"
+
+namespace aura {
+class RootWindowHost;
+}
+
+namespace ash {
+
+class ASH_EXPORT RootWindowHostFactory {
+ public:
+ virtual ~RootWindowHostFactory() {}
+
+ static RootWindowHostFactory* Create();
+
+ // Creates a new aura::RootWindowHost. The caller owns the returned value.
+ virtual aura::RootWindowHost* CreateRootWindowHost(
+ const gfx::Rect& initial_bounds) = 0;
+
+ protected:
+ RootWindowHostFactory() {}
+};
+
+} // namespace ash
+
+#endif // ASH_HOST_ROOT_WINDOW_HOST_FACTORY_H_
diff --git a/chromium/ash/host/root_window_host_factory_win.cc b/chromium/ash/host/root_window_host_factory_win.cc
new file mode 100644
index 00000000000..5dceef90be0
--- /dev/null
+++ b/chromium/ash/host/root_window_host_factory_win.cc
@@ -0,0 +1,40 @@
+// Copyright (c) 2012 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.
+
+#include "ash/host/root_window_host_factory.h"
+
+#include "ash/ash_switches.h"
+#include "base/command_line.h"
+#include "base/win/windows_version.h"
+#include "ui/aura/remote_root_window_host_win.h"
+#include "ui/aura/root_window_host.h"
+
+namespace {
+
+class RootWindowHostFactoryImpl : public ash::RootWindowHostFactory {
+ public:
+ RootWindowHostFactoryImpl() {}
+
+ // Overridden from RootWindowHostFactory:
+ virtual aura::RootWindowHost* CreateRootWindowHost(
+ const gfx::Rect& initial_bounds) OVERRIDE {
+ if (base::win::GetVersion() >= base::win::VERSION_WIN8 &&
+ !CommandLine::ForCurrentProcess()->HasSwitch(
+ ash::switches::kForceAshToDesktop))
+ return aura::RemoteRootWindowHostWin::Create(initial_bounds);
+
+ return aura::RootWindowHost::Create(initial_bounds);
+ }
+};
+
+}
+
+namespace ash {
+
+// static
+RootWindowHostFactory* RootWindowHostFactory::Create() {
+ return new RootWindowHostFactoryImpl;
+}
+
+} // namespace ash
diff --git a/chromium/ash/ime_control_delegate.h b/chromium/ash/ime_control_delegate.h
new file mode 100644
index 00000000000..6aa560902d8
--- /dev/null
+++ b/chromium/ash/ime_control_delegate.h
@@ -0,0 +1,32 @@
+// Copyright (c) 2012 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 ASH_IME_CONTROL_DELEGATE_H_
+#define ASH_IME_CONTROL_DELEGATE_H_
+
+namespace ui {
+class Accelerator;
+} // namespace ui
+
+namespace ash {
+
+// Delegate for controlling IME (input method editor).
+class ImeControlDelegate {
+ public:
+ virtual ~ImeControlDelegate() {}
+
+ virtual bool HandleNextIme() = 0;
+ virtual bool HandlePreviousIme(const ui::Accelerator& accelerator) = 0;
+ // Switches to another IME depending on the |accelerator|.
+ virtual bool HandleSwitchIme(const ui::Accelerator& accelerator) = 0;
+
+ // Checks for special language anomalies and re-map the |accelerator|
+ // accordingly.
+ virtual ui::Accelerator RemapAccelerator(
+ const ui::Accelerator& accelerator) = 0;
+};
+
+} // namespace ash
+
+#endif // ASH_IME_CONTROL_DELEGATE_H_
diff --git a/chromium/ash/keyboard_controller_proxy_stub.cc b/chromium/ash/keyboard_controller_proxy_stub.cc
new file mode 100644
index 00000000000..caaa3dd8a2e
--- /dev/null
+++ b/chromium/ash/keyboard_controller_proxy_stub.cc
@@ -0,0 +1,34 @@
+// Copyright 2013 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.
+
+#include "ash/keyboard_controller_proxy_stub.h"
+
+#include "ash/shell.h"
+#include "ui/views/corewm/input_method_event_filter.h"
+
+using namespace content;
+
+namespace ash {
+
+KeyboardControllerProxyStub::KeyboardControllerProxyStub() {
+}
+
+KeyboardControllerProxyStub::~KeyboardControllerProxyStub() {
+}
+
+BrowserContext* KeyboardControllerProxyStub::GetBrowserContext() {
+ return Shell::GetInstance()->browser_context();
+}
+
+ui::InputMethod* KeyboardControllerProxyStub::GetInputMethod() {
+ return Shell::GetInstance()->input_method_filter()->input_method();
+}
+
+void KeyboardControllerProxyStub::RequestAudioInput(
+ WebContents* web_contents,
+ const MediaStreamRequest& request,
+ const MediaResponseCallback& callback) {
+}
+
+} // namespace ash
diff --git a/chromium/ash/keyboard_controller_proxy_stub.h b/chromium/ash/keyboard_controller_proxy_stub.h
new file mode 100644
index 00000000000..25310d71601
--- /dev/null
+++ b/chromium/ash/keyboard_controller_proxy_stub.h
@@ -0,0 +1,33 @@
+// Copyright 2013 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 ASH_KEYBOARD_CONTROLLER_PROXY_STUB_H_
+#define ASH_KEYBOARD_CONTROLLER_PROXY_STUB_H_
+
+#include "ash/ash_export.h"
+#include "ui/keyboard/keyboard_controller_proxy.h"
+
+namespace ash {
+
+// Stub implementation of KeyboardControllerProxy
+class ASH_EXPORT KeyboardControllerProxyStub
+ : public keyboard::KeyboardControllerProxy {
+ public:
+ KeyboardControllerProxyStub();
+ virtual ~KeyboardControllerProxyStub();
+
+ private:
+ // Overridden from keyboard::KeyboardControllerProxy:
+ virtual content::BrowserContext* GetBrowserContext() OVERRIDE;
+ virtual ui::InputMethod* GetInputMethod() OVERRIDE;
+ virtual void RequestAudioInput(content::WebContents* web_contents,
+ const content::MediaStreamRequest& request,
+ const content::MediaResponseCallback& callback) OVERRIDE;
+
+ DISALLOW_COPY_AND_ASSIGN(KeyboardControllerProxyStub);
+};
+
+} // namespace ash
+
+#endif // ASH_KEYBOARD_CONTROLLER_PROXY_STUB_H_
diff --git a/chromium/ash/keyboard_overlay/keyboard_overlay_delegate.cc b/chromium/ash/keyboard_overlay/keyboard_overlay_delegate.cc
new file mode 100644
index 00000000000..681efaf07ef
--- /dev/null
+++ b/chromium/ash/keyboard_overlay/keyboard_overlay_delegate.cc
@@ -0,0 +1,149 @@
+// Copyright (c) 2012 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.
+
+#include "ash/keyboard_overlay/keyboard_overlay_delegate.h"
+
+#include <algorithm>
+
+#include "ash/shell.h"
+#include "base/bind.h"
+#include "base/memory/weak_ptr.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/values.h"
+#include "content/public/browser/web_ui.h"
+#include "content/public/browser/web_ui_message_handler.h"
+#include "ui/aura/root_window.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/gfx/screen.h"
+#include "ui/views/controls/webview/web_dialog_view.h"
+#include "ui/views/widget/widget.h"
+
+using content::WebContents;
+using content::WebUIMessageHandler;
+
+namespace {
+
+const int kBaseWidth = 1252;
+const int kBaseHeight = 516;
+const int kHorizontalMargin = 28;
+
+// A message handler for detecting the timing when the web contents is painted.
+class PaintMessageHandler
+ : public WebUIMessageHandler,
+ public base::SupportsWeakPtr<PaintMessageHandler> {
+ public:
+ explicit PaintMessageHandler(views::Widget* widget) : widget_(widget) {}
+ virtual ~PaintMessageHandler() {}
+
+ // WebUIMessageHandler implementation.
+ virtual void RegisterMessages() OVERRIDE;
+
+ private:
+ void DidPaint(const ListValue* args);
+
+ views::Widget* widget_;
+
+ DISALLOW_COPY_AND_ASSIGN(PaintMessageHandler);
+};
+
+void PaintMessageHandler::RegisterMessages() {
+ web_ui()->RegisterMessageCallback(
+ "didPaint",
+ base::Bind(&PaintMessageHandler::DidPaint, base::Unretained(this)));
+}
+
+void PaintMessageHandler::DidPaint(const ListValue* args) {
+ // Show the widget after the web content has been painted.
+ widget_->Show();
+}
+
+} // namespace
+
+namespace ash {
+
+KeyboardOverlayDelegate::KeyboardOverlayDelegate(const base::string16& title,
+ const GURL& url)
+ : title_(title),
+ url_(url),
+ widget_(NULL) {
+}
+
+KeyboardOverlayDelegate::~KeyboardOverlayDelegate() {
+}
+
+views::Widget* KeyboardOverlayDelegate::Show(views::WebDialogView* view) {
+ widget_ = new views::Widget;
+ views::Widget::InitParams params(
+ views::Widget::InitParams::TYPE_WINDOW_FRAMELESS);
+ params.context = Shell::GetPrimaryRootWindow();
+ params.delegate = view;
+ widget_->Init(params);
+
+ // Show the widget at the bottom of the work area.
+ gfx::Size size;
+ GetDialogSize(&size);
+ const gfx::Rect& rect = Shell::GetScreen()->GetDisplayNearestWindow(
+ widget_->GetNativeView()).work_area();
+ gfx::Rect bounds((rect.width() - size.width()) / 2,
+ rect.height() - size.height(),
+ size.width(),
+ size.height());
+ widget_->SetBounds(bounds);
+
+ // The widget will be shown when the web contents gets ready to display.
+ return widget_;
+}
+
+ui::ModalType KeyboardOverlayDelegate::GetDialogModalType() const {
+ return ui::MODAL_TYPE_SYSTEM;
+}
+
+base::string16 KeyboardOverlayDelegate::GetDialogTitle() const {
+ return title_;
+}
+
+GURL KeyboardOverlayDelegate::GetDialogContentURL() const {
+ return url_;
+}
+
+void KeyboardOverlayDelegate::GetWebUIMessageHandlers(
+ std::vector<WebUIMessageHandler*>* handlers) const {
+ handlers->push_back(new PaintMessageHandler(widget_));
+}
+
+void KeyboardOverlayDelegate::GetDialogSize(
+ gfx::Size* size) const {
+ using std::min;
+ DCHECK(widget_);
+ gfx::Rect rect = ash::Shell::GetScreen()->GetDisplayNearestWindow(
+ widget_->GetNativeView()).bounds();
+ const int width = min(kBaseWidth, rect.width() - kHorizontalMargin);
+ const int height = width * kBaseHeight / kBaseWidth;
+ size->SetSize(width, height);
+}
+
+std::string KeyboardOverlayDelegate::GetDialogArgs() const {
+ return "[]";
+}
+
+void KeyboardOverlayDelegate::OnDialogClosed(
+ const std::string& json_retval) {
+ delete this;
+ return;
+}
+
+void KeyboardOverlayDelegate::OnCloseContents(WebContents* source,
+ bool* out_close_dialog) {
+}
+
+bool KeyboardOverlayDelegate::ShouldShowDialogTitle() const {
+ return false;
+}
+
+bool KeyboardOverlayDelegate::HandleContextMenu(
+ const content::ContextMenuParams& params) {
+ return true;
+}
+
+} // namespace ash
diff --git a/chromium/ash/keyboard_overlay/keyboard_overlay_delegate.h b/chromium/ash/keyboard_overlay/keyboard_overlay_delegate.h
new file mode 100644
index 00000000000..caf8f2a9cc4
--- /dev/null
+++ b/chromium/ash/keyboard_overlay/keyboard_overlay_delegate.h
@@ -0,0 +1,66 @@
+// Copyright (c) 2012 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 ASH_KEYBOARD_OVERLAY_KEYBOARD_OVERLAY_DELEGATE_H_
+#define ASH_KEYBOARD_OVERLAY_KEYBOARD_OVERLAY_DELEGATE_H_
+
+#include "ash/ash_export.h"
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/gtest_prod_util.h"
+#include "ui/web_dialogs/web_dialog_delegate.h"
+#include "url/gurl.h"
+
+namespace views {
+class WebDialogView;
+class Widget;
+}
+
+namespace ash {
+
+// Delegate to handle showing the keyboard overlay drawing. Exported for test.
+class ASH_EXPORT KeyboardOverlayDelegate : public ui::WebDialogDelegate {
+ public:
+ KeyboardOverlayDelegate(const base::string16& title, const GURL& url);
+
+ // Shows the keyboard overlay widget. Returns the widget for testing.
+ views::Widget* Show(views::WebDialogView* view);
+
+ // Overridden from ui::WebDialogDelegate:
+ virtual void GetDialogSize(gfx::Size* size) const OVERRIDE;
+
+ private:
+ FRIEND_TEST_ALL_PREFIXES(KeyboardOverlayDelegateTest, ShowAndClose);
+
+ virtual ~KeyboardOverlayDelegate();
+
+ // Overridden from ui::WebDialogDelegate:
+ virtual ui::ModalType GetDialogModalType() const OVERRIDE;
+ virtual base::string16 GetDialogTitle() const OVERRIDE;
+ virtual GURL GetDialogContentURL() const OVERRIDE;
+ virtual void GetWebUIMessageHandlers(
+ std::vector<content::WebUIMessageHandler*>* handlers) const OVERRIDE;
+ virtual std::string GetDialogArgs() const OVERRIDE;
+ virtual void OnDialogClosed(const std::string& json_retval) OVERRIDE;
+ virtual void OnCloseContents(content::WebContents* source,
+ bool* out_close_dialog) OVERRIDE;
+ virtual bool ShouldShowDialogTitle() const OVERRIDE;
+ virtual bool HandleContextMenu(
+ const content::ContextMenuParams& params) OVERRIDE;
+
+ // The dialog title.
+ base::string16 title_;
+
+ // The URL of the keyboard overlay.
+ GURL url_;
+
+ // The widget associated with this delegate. Not owned.
+ views::Widget* widget_;
+
+ DISALLOW_COPY_AND_ASSIGN(KeyboardOverlayDelegate);
+};
+
+} // namespace ash
+
+#endif // ASH_KEYBOARD_OVERLAY_KEYBOARD_OVERLAY_DELEGATE_H_
diff --git a/chromium/ash/keyboard_overlay/keyboard_overlay_delegate_unittest.cc b/chromium/ash/keyboard_overlay/keyboard_overlay_delegate_unittest.cc
new file mode 100644
index 00000000000..0f7158a009a
--- /dev/null
+++ b/chromium/ash/keyboard_overlay/keyboard_overlay_delegate_unittest.cc
@@ -0,0 +1,45 @@
+// Copyright (c) 2013 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.
+
+#include "ash/keyboard_overlay/keyboard_overlay_delegate.h"
+
+#include "ash/shell.h"
+#include "ash/test/ash_test_base.h"
+#include "base/strings/utf_string_conversions.h"
+#include "ui/aura/window.h"
+#include "ui/gfx/display.h"
+#include "ui/gfx/screen.h"
+#include "ui/views/widget/widget.h"
+
+namespace ash {
+
+typedef test::AshTestBase KeyboardOverlayDelegateTest;
+
+// Verifies we can show and close the widget for the overlay dialog.
+TEST_F(KeyboardOverlayDelegateTest, ShowAndClose) {
+ if (!SupportsMultipleDisplays())
+ return;
+
+ UpdateDisplay("500x400,300x200");
+ KeyboardOverlayDelegate delegate(ASCIIToUTF16("Title"),
+ GURL("chrome://keyboardoverlay/"));
+ // Showing the dialog creates a widget.
+ views::Widget* widget = delegate.Show(NULL);
+ EXPECT_TRUE(widget);
+
+ // The widget is on the primary root window.
+ EXPECT_EQ(Shell::GetPrimaryRootWindow(),
+ widget->GetNativeWindow()->GetRootWindow());
+
+ // The widget is horizontally centered at the bottom of the work area.
+ gfx::Rect work_area = Shell::GetScreen()->GetPrimaryDisplay().work_area();
+ gfx::Rect bounds = widget->GetRestoredBounds();
+ EXPECT_EQ(work_area.CenterPoint().x(), bounds.CenterPoint().x());
+ EXPECT_EQ(work_area.bottom(), bounds.bottom());
+
+ // Clean up.
+ widget->CloseNow();
+}
+
+} // namespace ash
diff --git a/chromium/ash/keyboard_overlay/keyboard_overlay_view.cc b/chromium/ash/keyboard_overlay/keyboard_overlay_view.cc
new file mode 100644
index 00000000000..2144caa4e80
--- /dev/null
+++ b/chromium/ash/keyboard_overlay/keyboard_overlay_view.cc
@@ -0,0 +1,95 @@
+// Copyright (c) 2012 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.
+
+#include "ash/keyboard_overlay/keyboard_overlay_view.h"
+
+#include "ash/keyboard_overlay/keyboard_overlay_delegate.h"
+#include "ash/shell.h"
+#include "base/strings/utf_string_conversions.h"
+#include "content/public/browser/browser_context.h"
+#include "grit/ash_strings.h"
+#include "ui/base/events/event.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/gfx/screen.h"
+#include "ui/views/widget/widget.h"
+#include "ui/web_dialogs/web_dialog_delegate.h"
+
+using ui::WebDialogDelegate;
+
+namespace {
+
+// Keys to invoke Cancel (Escape, Ctrl+Alt+/, or Shift+Ctrl+Alt+/, Help, F14).
+const ash::KeyboardOverlayView::KeyEventData kCancelKeys[] = {
+ { ui::VKEY_ESCAPE, ui::EF_NONE},
+ { ui::VKEY_OEM_2, ui::EF_CONTROL_DOWN | ui::EF_ALT_DOWN },
+ { ui::VKEY_OEM_2, ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN | ui::EF_ALT_DOWN },
+ { ui::VKEY_HELP, ui::EF_NONE },
+ { ui::VKEY_F14, ui::EF_NONE },
+};
+
+}
+
+namespace ash {
+
+KeyboardOverlayView::KeyboardOverlayView(
+ content::BrowserContext* context,
+ WebDialogDelegate* delegate,
+ WebContentsHandler* handler)
+ : views::WebDialogView(context, delegate, handler) {
+}
+
+KeyboardOverlayView::~KeyboardOverlayView() {
+}
+
+void KeyboardOverlayView::Cancel() {
+ Shell::GetInstance()->overlay_filter()->Deactivate();
+ views::Widget* widget = GetWidget();
+ if (widget)
+ widget->Close();
+}
+
+bool KeyboardOverlayView::IsCancelingKeyEvent(ui::KeyEvent* event) {
+ if (event->type() != ui::ET_KEY_PRESSED)
+ return false;
+ // Ignore the caps lock state.
+ const int flags = (event->flags() & ~ui::EF_CAPS_LOCK_DOWN);
+ for (size_t i = 0; i < arraysize(kCancelKeys); ++i) {
+ if ((kCancelKeys[i].key_code == event->key_code()) &&
+ (kCancelKeys[i].flags == flags))
+ return true;
+ }
+ return false;
+}
+
+aura::Window* KeyboardOverlayView::GetWindow() {
+ return GetWidget()->GetNativeWindow();
+}
+
+void KeyboardOverlayView::ShowDialog(
+ content::BrowserContext* context,
+ WebContentsHandler* handler,
+ const GURL& url) {
+ KeyboardOverlayDelegate* delegate = new KeyboardOverlayDelegate(
+ l10n_util::GetStringUTF16(IDS_ASH_KEYBOARD_OVERLAY_TITLE), url);
+ KeyboardOverlayView* view =
+ new KeyboardOverlayView(context, delegate, handler);
+ delegate->Show(view);
+
+ Shell::GetInstance()->overlay_filter()->Activate(view);
+}
+
+void KeyboardOverlayView::WindowClosing() {
+ Cancel();
+}
+
+// static
+void KeyboardOverlayView::GetCancelingKeysForTesting(
+ std::vector<KeyboardOverlayView::KeyEventData>* canceling_keys) {
+ CHECK(canceling_keys);
+ canceling_keys->clear();
+ for (size_t i = 0; i < arraysize(kCancelKeys); ++i)
+ canceling_keys->push_back(kCancelKeys[i]);
+}
+
+} // namespace ash
diff --git a/chromium/ash/keyboard_overlay/keyboard_overlay_view.h b/chromium/ash/keyboard_overlay/keyboard_overlay_view.h
new file mode 100644
index 00000000000..3e352ae3169
--- /dev/null
+++ b/chromium/ash/keyboard_overlay/keyboard_overlay_view.h
@@ -0,0 +1,68 @@
+// Copyright (c) 2012 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 ASH_KEYBOARD_OVERLAY_KEYBOARD_OVERLAY_VIEW_H_
+#define ASH_KEYBOARD_OVERLAY_KEYBOARD_OVERLAY_VIEW_H_
+
+#include <vector>
+
+#include "ash/ash_export.h"
+#include "ash/wm/overlay_event_filter.h"
+#include "base/compiler_specific.h"
+#include "base/gtest_prod_util.h"
+#include "ui/views/controls/webview/web_dialog_view.h"
+
+class GURL;
+
+namespace content {
+class BrowserContext;
+}
+
+namespace ui {
+class WebDialogDelegate;
+}
+
+namespace ash {
+
+// A customized dialog view for the keyboard overlay.
+class ASH_EXPORT KeyboardOverlayView
+ : public views::WebDialogView,
+ public ash::internal::OverlayEventFilter::Delegate {
+ public:
+ struct KeyEventData {
+ ui::KeyboardCode key_code;
+ int flags;
+ };
+
+ KeyboardOverlayView(content::BrowserContext* context,
+ ui::WebDialogDelegate* delegate,
+ WebContentsHandler* handler);
+ virtual ~KeyboardOverlayView();
+
+ // Overridden from ash::internal::OverlayEventFilter::Delegate:
+ virtual void Cancel() OVERRIDE;
+ virtual bool IsCancelingKeyEvent(ui::KeyEvent* event) OVERRIDE;
+ virtual aura::Window* GetWindow() OVERRIDE;
+
+ // Shows the keyboard overlay.
+ static void ShowDialog(content::BrowserContext* context,
+ WebContentsHandler* handler,
+ const GURL& url);
+
+ private:
+ FRIEND_TEST_ALL_PREFIXES(KeyboardOverlayViewTest, OpenAcceleratorsClose);
+ FRIEND_TEST_ALL_PREFIXES(KeyboardOverlayViewTest, NoRedundantCancelingKeys);
+
+ // Overridden from views::WidgetDelegate:
+ virtual void WindowClosing() OVERRIDE;
+
+ static void GetCancelingKeysForTesting(
+ std::vector<KeyEventData>* canceling_keys);
+
+ DISALLOW_COPY_AND_ASSIGN(KeyboardOverlayView);
+};
+
+} // namespace ash
+
+#endif // ASH_KEYBOARD_OVERLAY_KEYBOARD_OVERLAY_VIEW_H_
diff --git a/chromium/ash/keyboard_overlay/keyboard_overlay_view_unittest.cc b/chromium/ash/keyboard_overlay/keyboard_overlay_view_unittest.cc
new file mode 100644
index 00000000000..87afe0572a3
--- /dev/null
+++ b/chromium/ash/keyboard_overlay/keyboard_overlay_view_unittest.cc
@@ -0,0 +1,77 @@
+// Copyright (c) 2012 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.
+
+#include "ash/keyboard_overlay/keyboard_overlay_view.h"
+
+#include <algorithm>
+
+#include "ash/accelerators/accelerator_table.h"
+#include "ash/keyboard_overlay/keyboard_overlay_delegate.h"
+#include "ash/shell.h"
+#include "ash/test/ash_test_base.h"
+#include "ui/web_dialogs/test/test_web_contents_handler.h"
+#include "ui/web_dialogs/test/test_web_dialog_delegate.h"
+
+namespace ash {
+
+typedef test::AshTestBase KeyboardOverlayViewTest;
+
+bool operator==(const KeyboardOverlayView::KeyEventData& lhs,
+ const KeyboardOverlayView::KeyEventData& rhs) {
+ return (lhs.key_code == rhs.key_code) && (lhs.flags == rhs.flags);
+}
+
+// Verifies that the accelerators that open the keyboard overlay close it.
+TEST_F(KeyboardOverlayViewTest, OpenAcceleratorsClose) {
+ ui::test::TestWebDialogDelegate delegate(GURL("chrome://keyboardoverlay"));
+ KeyboardOverlayView view(
+ Shell::GetInstance()->browser_context(),
+ &delegate,
+ new ui::test::TestWebContentsHandler);
+ for (size_t i = 0; i < kAcceleratorDataLength; ++i) {
+ if (kAcceleratorData[i].action != SHOW_KEYBOARD_OVERLAY)
+ continue;
+ const AcceleratorData& open_key_data = kAcceleratorData[i];
+ ui::KeyEvent open_key(open_key_data.trigger_on_press ?
+ ui::ET_KEY_PRESSED : ui::ET_KEY_RELEASED,
+ open_key_data.keycode,
+ open_key_data.modifiers,
+ false);
+ EXPECT_TRUE(view.IsCancelingKeyEvent(&open_key));
+ }
+}
+
+// Verifies that there are no redunduant keys in the canceling keys.
+TEST_F(KeyboardOverlayViewTest, NoRedundantCancelingKeys) {
+ std::vector<KeyboardOverlayView::KeyEventData> open_keys;
+ for (size_t i = 0; i < kAcceleratorDataLength; ++i) {
+ if (kAcceleratorData[i].action != SHOW_KEYBOARD_OVERLAY)
+ continue;
+ // Escape is used just for canceling.
+ KeyboardOverlayView::KeyEventData open_key = {
+ kAcceleratorData[i].keycode,
+ kAcceleratorData[i].modifiers,
+ };
+ open_keys.push_back(open_key);
+ }
+
+ std::vector<KeyboardOverlayView::KeyEventData> canceling_keys;
+ KeyboardOverlayView::GetCancelingKeysForTesting(&canceling_keys);
+
+ // Escape is used just for canceling, so exclude it from the comparison with
+ // open keys.
+ KeyboardOverlayView::KeyEventData escape = { ui::VKEY_ESCAPE, ui::EF_NONE };
+ std::vector<KeyboardOverlayView::KeyEventData>::iterator escape_itr =
+ std::find(canceling_keys.begin(), canceling_keys.end(), escape);
+ canceling_keys.erase(escape_itr);
+
+ // Other canceling keys should be same as opening keys.
+ EXPECT_EQ(open_keys.size(), canceling_keys.size());
+ for (size_t i = 0; i < canceling_keys.size(); ++i) {
+ EXPECT_NE(std::find(open_keys.begin(), open_keys.end(), canceling_keys[i]),
+ open_keys.end());
+ }
+}
+
+} // namespace ash
diff --git a/chromium/ash/launcher/OWNERS b/chromium/ash/launcher/OWNERS
new file mode 100644
index 00000000000..3d25bb8c15b
--- /dev/null
+++ b/chromium/ash/launcher/OWNERS
@@ -0,0 +1 @@
+davemoore@chromium.org
diff --git a/chromium/ash/launcher/alternate_app_list_button.cc b/chromium/ash/launcher/alternate_app_list_button.cc
new file mode 100644
index 00000000000..0466c71ae54
--- /dev/null
+++ b/chromium/ash/launcher/alternate_app_list_button.cc
@@ -0,0 +1,150 @@
+// Copyright 2013 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.
+#include "ash/launcher/alternate_app_list_button.h"
+
+#include "ash/ash_switches.h"
+#include "ash/launcher/launcher_button_host.h"
+#include "ash/launcher/launcher_types.h"
+#include "ash/shelf/shelf_layout_manager.h"
+#include "ash/shelf/shelf_widget.h"
+#include "ash/shell.h"
+#include "grit/ash_resources.h"
+#include "grit/ash_strings.h"
+#include "ui/base/accessibility/accessible_view_state.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "ui/compositor/layer.h"
+#include "ui/compositor/layer_animation_element.h"
+#include "ui/compositor/layer_animation_sequence.h"
+#include "ui/compositor/scoped_layer_animation_settings.h"
+#include "ui/gfx/canvas.h"
+#include "ui/gfx/image/image_skia_operations.h"
+#include "ui/views/controls/button/image_button.h"
+
+namespace ash {
+namespace internal {
+namespace {
+const int kImagePaddingFromShelf = 5;
+} // namespace
+
+// static
+const int AlternateAppListButton::kImageBoundsSize = 7;
+
+
+AlternateAppListButton::AlternateAppListButton(views::ButtonListener* listener,
+ LauncherButtonHost* host,
+ ShelfWidget* shelf_widget)
+ : views::ImageButton(listener),
+ host_(host),
+ shelf_widget_(shelf_widget) {
+ SetAccessibleName(l10n_util::GetStringUTF16(IDS_AURA_APP_LIST_TITLE));
+ SetSize(gfx::Size(ShelfLayoutManager::kShelfSize,
+ ShelfLayoutManager::kShelfSize));
+}
+
+AlternateAppListButton::~AlternateAppListButton() {
+}
+
+bool AlternateAppListButton::OnMousePressed(const ui::MouseEvent& event) {
+ ImageButton::OnMousePressed(event);
+ host_->PointerPressedOnButton(this, LauncherButtonHost::MOUSE, event);
+ return true;
+}
+
+void AlternateAppListButton::OnMouseReleased(const ui::MouseEvent& event) {
+ ImageButton::OnMouseReleased(event);
+ host_->PointerReleasedOnButton(this, LauncherButtonHost::MOUSE, false);
+}
+
+void AlternateAppListButton::OnMouseCaptureLost() {
+ host_->PointerReleasedOnButton(this, LauncherButtonHost::MOUSE, true);
+ ImageButton::OnMouseCaptureLost();
+}
+
+bool AlternateAppListButton::OnMouseDragged(const ui::MouseEvent& event) {
+ ImageButton::OnMouseDragged(event);
+ host_->PointerDraggedOnButton(this, LauncherButtonHost::MOUSE, event);
+ return true;
+}
+
+void AlternateAppListButton::OnMouseMoved(const ui::MouseEvent& event) {
+ ImageButton::OnMouseMoved(event);
+ host_->MouseMovedOverButton(this);
+}
+
+void AlternateAppListButton::OnMouseEntered(const ui::MouseEvent& event) {
+ ImageButton::OnMouseEntered(event);
+ host_->MouseEnteredButton(this);
+}
+
+void AlternateAppListButton::OnMouseExited(const ui::MouseEvent& event) {
+ ImageButton::OnMouseExited(event);
+ host_->MouseExitedButton(this);
+}
+
+void AlternateAppListButton::OnPaint(gfx::Canvas* canvas) {
+ // Call the base class first to paint any background/borders.
+ View::OnPaint(canvas);
+
+ int background_image_id = 0;
+ if (Shell::GetInstance()->GetAppListTargetVisibility()) {
+ background_image_id = IDR_AURA_NOTIFICATION_BACKGROUND_PRESSED;
+ } else {
+ if (shelf_widget_->GetDimsShelf())
+ background_image_id = IDR_AURA_NOTIFICATION_BACKGROUND_ON_BLACK;
+ else
+ background_image_id = IDR_AURA_NOTIFICATION_BACKGROUND_NORMAL;
+ }
+ ResourceBundle& rb = ResourceBundle::GetSharedInstance();
+ const gfx::ImageSkia* background_image =
+ rb.GetImageNamed(background_image_id).ToImageSkia();
+ const gfx::ImageSkia* forground_image =
+ rb.GetImageNamed(IDR_AURA_LAUNCHER_ICON_APPLIST_ALTERNATE).ToImageSkia();
+
+ gfx::Rect contents_bounds = GetContentsBounds();
+ gfx::Rect background_bounds, forground_bounds;
+
+ ShelfAlignment alignment = shelf_widget_->GetAlignment();
+ background_bounds.set_size(background_image->size());
+ if (alignment == SHELF_ALIGNMENT_LEFT) {
+ background_bounds.set_x(contents_bounds.width() -
+ kImagePaddingFromShelf - background_image->width());
+ background_bounds.set_y(contents_bounds.y() +
+ (contents_bounds.height() - background_image->height()) / 2);
+ } else if(alignment == SHELF_ALIGNMENT_RIGHT) {
+ background_bounds.set_x(kImagePaddingFromShelf);
+ background_bounds.set_y(contents_bounds.y() +
+ (contents_bounds.height() - background_image->height()) / 2);
+ } else {
+ background_bounds.set_y(kImagePaddingFromShelf);
+ background_bounds.set_x(contents_bounds.x() +
+ (contents_bounds.width() - background_image->width()) / 2);
+ }
+
+ forground_bounds.set_size(forground_image->size());
+ forground_bounds.set_x(background_bounds.x() +
+ std::max(0,
+ (background_bounds.width() - forground_bounds.width()) / 2));
+ forground_bounds.set_y(background_bounds.y() +
+ std::max(0,
+ (background_bounds.height() - forground_bounds.height()) / 2));
+
+ canvas->DrawImageInt(*background_image,
+ background_bounds.x(),
+ background_bounds.y());
+ canvas->DrawImageInt(*forground_image,
+ forground_bounds.x(),
+ forground_bounds.y());
+
+ OnPaintFocusBorder(canvas);
+}
+
+void AlternateAppListButton::GetAccessibleState(
+ ui::AccessibleViewState* state) {
+ state->role = ui::AccessibilityTypes::ROLE_PUSHBUTTON;
+ state->name = host_->GetAccessibleName(this);
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/launcher/alternate_app_list_button.h b/chromium/ash/launcher/alternate_app_list_button.h
new file mode 100644
index 00000000000..f68207c2acb
--- /dev/null
+++ b/chromium/ash/launcher/alternate_app_list_button.h
@@ -0,0 +1,58 @@
+// Copyright 2013 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 ASH_LAUNCHER_ALTERNATE_APP_LIST_BUTTON_H_
+#define ASH_LAUNCHER_ALTERNATE_APP_LIST_BUTTON_H_
+
+#include "ui/views/controls/button/image_button.h"
+
+namespace ash {
+
+class ShelfWidget;
+
+namespace internal {
+
+class LauncherButtonHost;
+
+
+
+// Button used for the AppList icon on the launcher.
+// This class is an alternate implementation to
+// ash::internal::AppListButton for the purposes of testing an
+// alternate shelf layout (see ash_switches: UseAlternateShelfLayout).
+class AlternateAppListButton : public views::ImageButton {
+ public:
+ // Bounds size (inset) required for the app icon image (in pixels).
+ static const int kImageBoundsSize;
+
+ AlternateAppListButton(views::ButtonListener* listener,
+ LauncherButtonHost* host,
+ ShelfWidget* shelf_widget);
+ virtual ~AlternateAppListButton();
+
+ protected:
+ // View overrides:
+ virtual bool OnMousePressed(const ui::MouseEvent& event) OVERRIDE;
+ virtual void OnMouseReleased(const ui::MouseEvent& event) OVERRIDE;
+ virtual void OnMouseCaptureLost() OVERRIDE;
+ virtual bool OnMouseDragged(const ui::MouseEvent& event) OVERRIDE;
+ virtual void OnMouseMoved(const ui::MouseEvent& event) OVERRIDE;
+ virtual void OnMouseEntered(const ui::MouseEvent& event) OVERRIDE;
+ virtual void OnMouseExited(const ui::MouseEvent& event) OVERRIDE;
+ virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE;
+ virtual void GetAccessibleState(ui::AccessibleViewState* state) OVERRIDE;
+
+ private:
+ LauncherButtonHost* host_;
+ // Reference to the shelf widget containing this button, owned by the
+ // root window controller.
+ ShelfWidget* shelf_widget_;
+
+ DISALLOW_COPY_AND_ASSIGN(AlternateAppListButton);
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_LAUNCHER_ALTERNATE_APP_LIST_BUTTON_H_
diff --git a/chromium/ash/launcher/app_list_button.cc b/chromium/ash/launcher/app_list_button.cc
new file mode 100644
index 00000000000..8879a387cc1
--- /dev/null
+++ b/chromium/ash/launcher/app_list_button.cc
@@ -0,0 +1,134 @@
+// Copyright (c) 2012 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.
+
+#include "ash/launcher/app_list_button.h"
+
+#include <vector>
+
+#include "ash/launcher/launcher_button_host.h"
+#include "ash/launcher/launcher_types.h"
+#include "grit/ash_resources.h"
+#include "grit/ash_strings.h"
+#include "ui/base/accessibility/accessible_view_state.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "ui/compositor/layer.h"
+#include "ui/compositor/layer_animation_element.h"
+#include "ui/compositor/layer_animation_sequence.h"
+#include "ui/compositor/scoped_layer_animation_settings.h"
+
+namespace ash {
+namespace internal {
+
+namespace {
+
+const int kAnimationDurationInMs = 600;
+const float kAnimationOpacity[] = { 1.0f, 0.4f, 1.0f };
+const int kBorderSize = 9;
+} // namespace
+
+AppListButton::AppListButton(views::ButtonListener* listener,
+ LauncherButtonHost* host)
+ : views::ImageButton(listener),
+ host_(host) {
+ ResourceBundle& rb = ResourceBundle::GetSharedInstance();
+ SetImage(
+ views::CustomButton::STATE_NORMAL,
+ rb.GetImageNamed(IDR_AURA_LAUNCHER_ICON_APPLIST).ToImageSkia());
+ SetImage(
+ views::CustomButton::STATE_HOVERED,
+ rb.GetImageNamed(IDR_AURA_LAUNCHER_ICON_APPLIST_HOT).
+ ToImageSkia());
+ SetImage(
+ views::CustomButton::STATE_PRESSED,
+ rb.GetImageNamed(IDR_AURA_LAUNCHER_ICON_APPLIST_PUSHED).
+ ToImageSkia());
+ SetAccessibleName(l10n_util::GetStringUTF16(IDS_AURA_APP_LIST_TITLE));
+ SetSize(gfx::Size(kLauncherPreferredSize, kLauncherPreferredSize));
+ SetImageAlignment(ImageButton::ALIGN_CENTER, ImageButton::ALIGN_TOP);
+}
+
+AppListButton::~AppListButton() {
+}
+
+void AppListButton::StartLoadingAnimation() {
+ layer()->GetAnimator()->StopAnimating();
+
+ scoped_ptr<ui::LayerAnimationSequence> opacity_sequence(
+ new ui::LayerAnimationSequence());
+
+ opacity_sequence->set_is_cyclic(true);
+
+ for (size_t i = 0; i < arraysize(kAnimationOpacity); ++i) {
+ opacity_sequence->AddElement(
+ ui::LayerAnimationElement::CreateOpacityElement(
+ kAnimationOpacity[i],
+ base::TimeDelta::FromMilliseconds(kAnimationDurationInMs)));
+ }
+
+ ui::LayerAnimationElement::AnimatableProperties opacity_properties;
+ opacity_properties.insert(ui::LayerAnimationElement::OPACITY);
+ opacity_sequence->AddElement(
+ ui::LayerAnimationElement::CreatePauseElement(
+ opacity_properties,
+ base::TimeDelta::FromMilliseconds(kAnimationDurationInMs)));
+
+ // LayerAnimator takes ownership of the sequences.
+ layer()->GetAnimator()->ScheduleAnimation(opacity_sequence.release());
+}
+
+void AppListButton::StopLoadingAnimation() {
+ layer()->GetAnimator()->StopAnimating();
+
+ ui::ScopedLayerAnimationSettings settings(layer()->GetAnimator());
+ settings.SetTransitionDuration(
+ base::TimeDelta::FromMilliseconds(kAnimationDurationInMs));
+ layer()->SetOpacity(1.0f);
+ layer()->SetTransform(gfx::Transform());
+}
+
+bool AppListButton::OnMousePressed(const ui::MouseEvent& event) {
+ ImageButton::OnMousePressed(event);
+ host_->PointerPressedOnButton(this, LauncherButtonHost::MOUSE, event);
+ return true;
+}
+
+void AppListButton::OnMouseReleased(const ui::MouseEvent& event) {
+ ImageButton::OnMouseReleased(event);
+ host_->PointerReleasedOnButton(this, LauncherButtonHost::MOUSE, false);
+}
+
+void AppListButton::OnMouseCaptureLost() {
+ host_->PointerReleasedOnButton(this, LauncherButtonHost::MOUSE, true);
+ ImageButton::OnMouseCaptureLost();
+}
+
+bool AppListButton::OnMouseDragged(const ui::MouseEvent& event) {
+ ImageButton::OnMouseDragged(event);
+ host_->PointerDraggedOnButton(this, LauncherButtonHost::MOUSE, event);
+ return true;
+}
+
+void AppListButton::OnMouseMoved(const ui::MouseEvent& event) {
+ ImageButton::OnMouseMoved(event);
+ host_->MouseMovedOverButton(this);
+}
+
+void AppListButton::OnMouseEntered(const ui::MouseEvent& event) {
+ ImageButton::OnMouseEntered(event);
+ host_->MouseEnteredButton(this);
+}
+
+void AppListButton::OnMouseExited(const ui::MouseEvent& event) {
+ ImageButton::OnMouseExited(event);
+ host_->MouseExitedButton(this);
+}
+
+void AppListButton::GetAccessibleState(ui::AccessibleViewState* state) {
+ state->role = ui::AccessibilityTypes::ROLE_PUSHBUTTON;
+ state->name = host_->GetAccessibleName(this);
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/launcher/app_list_button.h b/chromium/ash/launcher/app_list_button.h
new file mode 100644
index 00000000000..4d252542653
--- /dev/null
+++ b/chromium/ash/launcher/app_list_button.h
@@ -0,0 +1,45 @@
+// Copyright (c) 2012 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 ASH_LAUNCHER_APP_LIST_BUTTON_H_
+#define ASH_LAUNCHER_APP_LIST_BUTTON_H_
+
+#include "ui/views/controls/button/image_button.h"
+
+namespace ash {
+namespace internal {
+
+class LauncherButtonHost;
+
+// Button used for the AppList icon on the launcher.
+class AppListButton : public views::ImageButton {
+ public:
+ AppListButton(views::ButtonListener* listener,
+ LauncherButtonHost* host);
+ virtual ~AppListButton();
+
+ void StartLoadingAnimation();
+ void StopLoadingAnimation();
+
+ protected:
+ // View overrides:
+ virtual bool OnMousePressed(const ui::MouseEvent& event) OVERRIDE;
+ virtual void OnMouseReleased(const ui::MouseEvent& event) OVERRIDE;
+ virtual void OnMouseCaptureLost() OVERRIDE;
+ virtual bool OnMouseDragged(const ui::MouseEvent& event) OVERRIDE;
+ virtual void OnMouseMoved(const ui::MouseEvent& event) OVERRIDE;
+ virtual void OnMouseEntered(const ui::MouseEvent& event) OVERRIDE;
+ virtual void OnMouseExited(const ui::MouseEvent& event) OVERRIDE;
+ virtual void GetAccessibleState(ui::AccessibleViewState* state) OVERRIDE;
+
+ private:
+ LauncherButtonHost* host_;
+
+ DISALLOW_COPY_AND_ASSIGN(AppListButton);
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_LAUNCHER_APP_LIST_BUTTON_H_
diff --git a/chromium/ash/launcher/launcher.cc b/chromium/ash/launcher/launcher.cc
new file mode 100644
index 00000000000..5bd12622319
--- /dev/null
+++ b/chromium/ash/launcher/launcher.cc
@@ -0,0 +1,197 @@
+// Copyright (c) 2012 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.
+
+#include "ash/launcher/launcher.h"
+
+#include <algorithm>
+#include <cmath>
+
+#include "ash/focus_cycler.h"
+#include "ash/launcher/launcher_delegate.h"
+#include "ash/launcher/launcher_model.h"
+#include "ash/launcher/launcher_navigator.h"
+#include "ash/launcher/launcher_view.h"
+#include "ash/root_window_controller.h"
+#include "ash/screen_ash.h"
+#include "ash/shelf/shelf_layout_manager.h"
+#include "ash/shelf/shelf_widget.h"
+#include "ash/shell.h"
+#include "ash/shell_delegate.h"
+#include "ash/shell_window_ids.h"
+#include "ash/wm/property_util.h"
+#include "ash/wm/window_properties.h"
+#include "grit/ash_resources.h"
+#include "ui/aura/client/activation_client.h"
+#include "ui/aura/root_window.h"
+#include "ui/aura/window.h"
+#include "ui/aura/window_observer.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "ui/compositor/layer.h"
+#include "ui/gfx/canvas.h"
+#include "ui/gfx/image/image.h"
+#include "ui/gfx/image/image_skia_operations.h"
+#include "ui/gfx/skbitmap_operations.h"
+#include "ui/views/accessible_pane_view.h"
+#include "ui/views/widget/widget.h"
+#include "ui/views/widget/widget_delegate.h"
+
+namespace ash {
+
+const char Launcher::kNativeViewName[] = "LauncherView";
+
+Launcher::Launcher(LauncherModel* launcher_model,
+ LauncherDelegate* launcher_delegate,
+ ShelfWidget* shelf_widget)
+ : launcher_view_(NULL),
+ alignment_(shelf_widget->GetAlignment()),
+ delegate_(launcher_delegate),
+ shelf_widget_(shelf_widget) {
+ launcher_view_ = new internal::LauncherView(
+ launcher_model, delegate_, shelf_widget_->shelf_layout_manager());
+ launcher_view_->Init();
+ shelf_widget_->GetContentsView()->AddChildView(launcher_view_);
+ shelf_widget_->GetNativeView()->SetName(kNativeViewName);
+ delegate_->OnLauncherCreated(this);
+}
+
+Launcher::~Launcher() {
+ delegate_->OnLauncherDestroyed(this);
+}
+
+// static
+Launcher* Launcher::ForPrimaryDisplay() {
+ ShelfWidget* shelf_widget = internal::RootWindowController::ForLauncher(
+ Shell::GetPrimaryRootWindow())->shelf();
+ return shelf_widget ? shelf_widget->launcher() : NULL;
+}
+
+// static
+Launcher* Launcher::ForWindow(aura::Window* window) {
+ ShelfWidget* shelf_widget =
+ internal::RootWindowController::ForLauncher(window)->shelf();
+ return shelf_widget ? shelf_widget->launcher() : NULL;
+}
+
+void Launcher::SetAlignment(ShelfAlignment alignment) {
+ alignment_ = alignment;
+ launcher_view_->OnShelfAlignmentChanged();
+ // ShelfLayoutManager will resize the launcher.
+}
+
+gfx::Rect Launcher::GetScreenBoundsOfItemIconForWindow(aura::Window* window) {
+ LauncherID id = delegate_->GetIDByWindow(window);
+ gfx::Rect bounds(launcher_view_->GetIdealBoundsOfItemIcon(id));
+ gfx::Point screen_origin;
+ views::View::ConvertPointToScreen(launcher_view_, &screen_origin);
+ return gfx::Rect(screen_origin.x() + bounds.x(),
+ screen_origin.y() + bounds.y(),
+ bounds.width(),
+ bounds.height());
+}
+
+void Launcher::UpdateIconPositionForWindow(aura::Window* window) {
+ launcher_view_->UpdatePanelIconPosition(
+ delegate_->GetIDByWindow(window),
+ ash::ScreenAsh::ConvertRectFromScreen(
+ shelf_widget()->GetNativeView(),
+ window->GetBoundsInScreen()).CenterPoint());
+}
+
+void Launcher::ActivateLauncherItem(int index) {
+ // We pass in a keyboard event which will then trigger a switch to the
+ // next item if the current one is already active.
+ ui::KeyEvent event(ui::ET_KEY_RELEASED,
+ ui::VKEY_UNKNOWN, // The actual key gets ignored.
+ ui::EF_NONE,
+ false);
+
+ const ash::LauncherItems& items =
+ launcher_view_->model()->items();
+ delegate_->ItemSelected(items[index], event);
+}
+
+void Launcher::CycleWindowLinear(CycleDirection direction) {
+ int item_index = GetNextActivatedItemIndex(
+ *(launcher_view_->model()), direction);
+ if (item_index >= 0)
+ ActivateLauncherItem(item_index);
+}
+
+void Launcher::AddIconObserver(LauncherIconObserver* observer) {
+ launcher_view_->AddIconObserver(observer);
+}
+
+void Launcher::RemoveIconObserver(LauncherIconObserver* observer) {
+ launcher_view_->RemoveIconObserver(observer);
+}
+
+bool Launcher::IsShowingMenu() const {
+ return launcher_view_->IsShowingMenu();
+}
+
+bool Launcher::IsShowingOverflowBubble() const {
+ return launcher_view_->IsShowingOverflowBubble();
+}
+
+void Launcher::SetVisible(bool visible) const {
+ launcher_view_->SetVisible(visible);
+}
+
+bool Launcher::IsVisible() const {
+ return launcher_view_->visible();
+}
+
+void Launcher::SchedulePaint() {
+ launcher_view_->SchedulePaintForAllButtons();
+}
+
+views::View* Launcher::GetAppListButtonView() const {
+ return launcher_view_->GetAppListButtonView();
+}
+
+void Launcher::LaunchAppIndexAt(int item_index) {
+ LauncherModel* launcher_model = launcher_view_->model();
+ const LauncherItems& items = launcher_model->items();
+ int item_count = launcher_model->item_count();
+ int indexes_left = item_index >= 0 ? item_index : item_count;
+ int found_index = -1;
+
+ // Iterating until we have hit the index we are interested in which
+ // is true once indexes_left becomes negative.
+ for (int i = 0; i < item_count && indexes_left >= 0; i++) {
+ if (items[i].type != TYPE_APP_LIST) {
+ found_index = i;
+ indexes_left--;
+ }
+ }
+
+ // There are two ways how found_index can be valid: a.) the nth item was
+ // found (which is true when indexes_left is -1) or b.) the last item was
+ // requested (which is true when index was passed in as a negative number).
+ if (found_index >= 0 && (indexes_left == -1 || item_index < 0) &&
+ (delegate_->IsPerAppLauncher() ||
+ (items[found_index].status == ash::STATUS_RUNNING ||
+ items[found_index].status == ash::STATUS_CLOSED))) {
+ // Then set this one as active (or advance to the next item of its kind).
+ ActivateLauncherItem(found_index);
+ }
+}
+
+internal::LauncherView* Launcher::GetLauncherViewForTest() {
+ return launcher_view_;
+}
+
+void Launcher::SetLauncherViewBounds(gfx::Rect bounds) {
+ launcher_view_->SetBoundsRect(bounds);
+}
+
+gfx::Rect Launcher::GetLauncherViewBounds() const {
+ return launcher_view_->bounds();
+}
+
+app_list::ApplicationDragAndDropHost* Launcher::GetDragAndDropHostForAppList() {
+ return launcher_view_;
+}
+
+} // namespace ash
diff --git a/chromium/ash/launcher/launcher.h b/chromium/ash/launcher/launcher.h
new file mode 100644
index 00000000000..67810586715
--- /dev/null
+++ b/chromium/ash/launcher/launcher.h
@@ -0,0 +1,130 @@
+// Copyright (c) 2012 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 ASH_LAUNCHER_LAUNCHER_H_
+#define ASH_LAUNCHER_LAUNCHER_H_
+
+#include "ash/ash_export.h"
+#include "ash/launcher/launcher_types.h"
+#include "ash/shelf/shelf_types.h"
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "ui/gfx/size.h"
+#include "ui/views/widget/widget_observer.h"
+
+namespace app_list {
+class ApplicationDragAndDropHost;
+}
+
+namespace aura {
+class Window;
+}
+
+namespace gfx {
+class Rect;
+}
+
+namespace views {
+class View;
+}
+
+namespace ash {
+
+namespace internal {
+class FocusCycler;
+class LauncherView;
+class ShelfLayoutManager;
+}
+
+class LauncherIconObserver;
+class LauncherDelegate;
+class LauncherModel;
+class ShelfWidget;
+
+class ASH_EXPORT Launcher {
+ public:
+ static const char kNativeViewName[];
+
+ Launcher(LauncherModel* launcher_model,
+ LauncherDelegate* launcher_delegate,
+ ShelfWidget* shelf_widget);
+ virtual ~Launcher();
+
+ // Return the launcher for the primary display. NULL if no user is
+ // logged in yet.
+ static Launcher* ForPrimaryDisplay();
+
+ // Return the launcher for the display that |window| is currently on,
+ // or a launcher on primary display if the launcher per display feature
+ // is disabled. NULL if no user is logged in yet.
+ static Launcher* ForWindow(aura::Window* window);
+
+ void SetAlignment(ShelfAlignment alignment);
+ ShelfAlignment alignment() const { return alignment_; }
+
+ // Returns the screen bounds of the item for the specified window. If there is
+ // no item for the specified window an empty rect is returned.
+ gfx::Rect GetScreenBoundsOfItemIconForWindow(aura::Window* window);
+
+ // Updates the icon position given the current window bounds. This is used
+ // when dragging panels to reposition them with respect to the other panels.
+ void UpdateIconPositionForWindow(aura::Window* window);
+
+ // Activates the the launcher item specified by the index in the list
+ // of launcher items.
+ void ActivateLauncherItem(int index);
+
+ // Cycles the window focus linearly over the current launcher items.
+ void CycleWindowLinear(CycleDirection direction);
+
+ void AddIconObserver(LauncherIconObserver* observer);
+ void RemoveIconObserver(LauncherIconObserver* observer);
+
+ // Returns true if the Launcher is showing a context menu.
+ bool IsShowingMenu() const;
+
+ bool IsShowingOverflowBubble() const;
+
+ void SetVisible(bool visible) const;
+ bool IsVisible() const;
+
+ void SchedulePaint();
+
+ views::View* GetAppListButtonView() const;
+
+ // Launch a 0-indexed launcher item in the Launcher.
+ // A negative index launches the last launcher item in the launcher.
+ void LaunchAppIndexAt(int item_index);
+
+ // Only to be called for testing. Retrieves the LauncherView.
+ // TODO(sky): remove this!
+ internal::LauncherView* GetLauncherViewForTest();
+
+ LauncherDelegate* delegate() { return delegate_; }
+
+ ShelfWidget* shelf_widget() { return shelf_widget_; }
+
+ // Set the bounds of the launcher view.
+ void SetLauncherViewBounds(gfx::Rect bounds);
+ gfx::Rect GetLauncherViewBounds() const;
+
+ // Returns ApplicationDragAndDropHost for this Launcher.
+ app_list::ApplicationDragAndDropHost* GetDragAndDropHostForAppList();
+
+ private:
+ // LauncherView used to display icons.
+ internal::LauncherView* launcher_view_;
+
+ ShelfAlignment alignment_;
+
+ LauncherDelegate* delegate_;
+
+ ShelfWidget* shelf_widget_;
+
+ DISALLOW_COPY_AND_ASSIGN(Launcher);
+};
+
+} // namespace ash
+
+#endif // ASH_LAUNCHER_LAUNCHER_H_
diff --git a/chromium/ash/launcher/launcher_alignment_menu.cc b/chromium/ash/launcher/launcher_alignment_menu.cc
new file mode 100644
index 00000000000..8d6b6cfdcd3
--- /dev/null
+++ b/chromium/ash/launcher/launcher_alignment_menu.cc
@@ -0,0 +1,71 @@
+// Copyright (c) 2012 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.
+
+#include "ash/launcher/launcher_alignment_menu.h"
+
+#include "ash/shelf/shelf_layout_manager.h"
+#include "ash/shelf/shelf_types.h"
+#include "ash/shell.h"
+#include "grit/ash_strings.h"
+#include "ui/aura/root_window.h"
+#include "ui/base/l10n/l10n_util.h"
+
+namespace ash {
+
+LauncherAlignmentMenu::LauncherAlignmentMenu(aura::RootWindow* root)
+ : ui::SimpleMenuModel(NULL),
+ root_window_(root) {
+ DCHECK(root_window_);
+ int align_group_id = 1;
+ set_delegate(this);
+ AddRadioItemWithStringId(MENU_ALIGN_LEFT,
+ IDS_ASH_SHELF_CONTEXT_MENU_ALIGN_LEFT,
+ align_group_id);
+ AddRadioItemWithStringId(MENU_ALIGN_BOTTOM,
+ IDS_ASH_SHELF_CONTEXT_MENU_ALIGN_BOTTOM,
+ align_group_id);
+ AddRadioItemWithStringId(MENU_ALIGN_RIGHT,
+ IDS_ASH_SHELF_CONTEXT_MENU_ALIGN_RIGHT,
+ align_group_id);
+}
+
+LauncherAlignmentMenu::~LauncherAlignmentMenu() {}
+
+bool LauncherAlignmentMenu::IsCommandIdChecked(int command_id) const {
+ return internal::ShelfLayoutManager::ForLauncher(root_window_)->
+ SelectValueForShelfAlignment(
+ MENU_ALIGN_BOTTOM == command_id,
+ MENU_ALIGN_LEFT == command_id,
+ MENU_ALIGN_RIGHT == command_id,
+ false);
+}
+
+bool LauncherAlignmentMenu::IsCommandIdEnabled(int command_id) const {
+ return true;
+}
+
+bool LauncherAlignmentMenu::GetAcceleratorForCommandId(
+ int command_id,
+ ui::Accelerator* accelerator) {
+ return false;
+}
+
+void LauncherAlignmentMenu::ExecuteCommand(int command_id, int event_flags) {
+ switch (static_cast<MenuItem>(command_id)) {
+ case MENU_ALIGN_LEFT:
+ Shell::GetInstance()->SetShelfAlignment(SHELF_ALIGNMENT_LEFT,
+ root_window_);
+ break;
+ case MENU_ALIGN_BOTTOM:
+ Shell::GetInstance()->SetShelfAlignment(SHELF_ALIGNMENT_BOTTOM,
+ root_window_);
+ break;
+ case MENU_ALIGN_RIGHT:
+ Shell::GetInstance()->SetShelfAlignment(SHELF_ALIGNMENT_RIGHT,
+ root_window_);
+ break;
+ }
+}
+
+} // namespace ash
diff --git a/chromium/ash/launcher/launcher_alignment_menu.h b/chromium/ash/launcher/launcher_alignment_menu.h
new file mode 100644
index 00000000000..f5e551f34cb
--- /dev/null
+++ b/chromium/ash/launcher/launcher_alignment_menu.h
@@ -0,0 +1,48 @@
+// Copyright (c) 2012 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 ASH_WM_LAUNCHER_LAUNCHER_ALIGNMENT_MENU_H_
+#define ASH_WM_LAUNCHER_LAUNCHER_ALIGNMENT_MENU_H_
+
+#include "ash/ash_export.h"
+#include "base/basictypes.h"
+#include "ui/base/models/simple_menu_model.h"
+
+namespace aura {
+class RootWindow;
+}
+
+namespace ash {
+
+// Submenu for choosing the alignment of the launcher.
+class ASH_EXPORT LauncherAlignmentMenu : public ui::SimpleMenuModel,
+ public ui::SimpleMenuModel::Delegate {
+ public:
+ explicit LauncherAlignmentMenu(aura::RootWindow* root);
+ virtual ~LauncherAlignmentMenu();
+
+ // ui::SimpleMenuModel::Delegate overrides:
+ virtual bool IsCommandIdChecked(int command_id) const OVERRIDE;
+ virtual bool IsCommandIdEnabled(int command_id) const OVERRIDE;
+ virtual bool GetAcceleratorForCommandId(
+ int command_id,
+ ui::Accelerator* accelerator) OVERRIDE;
+ virtual void ExecuteCommand(int command_id, int event_flags) OVERRIDE;
+
+ private:
+ enum MenuItem {
+ // Offset so as not to interfere with other menus.
+ MENU_ALIGN_LEFT = 500,
+ MENU_ALIGN_RIGHT,
+ MENU_ALIGN_BOTTOM,
+ };
+
+ aura::RootWindow* root_window_;
+
+ DISALLOW_COPY_AND_ASSIGN(LauncherAlignmentMenu);
+};
+
+} // namespace ash
+
+#endif // ASH_WM_LAUNCHER_LAUNCHER_ALIGNMENT_MENU_H_
diff --git a/chromium/ash/launcher/launcher_button.cc b/chromium/ash/launcher/launcher_button.cc
new file mode 100644
index 00000000000..26677aef06a
--- /dev/null
+++ b/chromium/ash/launcher/launcher_button.cc
@@ -0,0 +1,571 @@
+// Copyright (c) 2012 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.
+
+#include "ash/launcher/launcher_button.h"
+
+#include <algorithm>
+
+#include "ash/ash_switches.h"
+#include "ash/launcher/launcher_button_host.h"
+#include "ash/shelf/shelf_layout_manager.h"
+#include "grit/ash_resources.h"
+#include "skia/ext/image_operations.h"
+#include "ui/base/accessibility/accessible_view_state.h"
+#include "ui/base/animation/animation_delegate.h"
+#include "ui/base/animation/throb_animation.h"
+#include "ui/base/events/event_constants.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "ui/compositor/layer.h"
+#include "ui/compositor/scoped_layer_animation_settings.h"
+#include "ui/gfx/canvas.h"
+#include "ui/gfx/image/image.h"
+#include "ui/gfx/image/image_skia_operations.h"
+#include "ui/gfx/skbitmap_operations.h"
+#include "ui/views/controls/image_view.h"
+
+namespace {
+
+// Size of the bar. This is along the opposite axis of the shelf. For example,
+// if the shelf is aligned horizontally then this is the height of the bar.
+const int kBarSize = 3;
+const int kBarSpacing = 5;
+const int kIconSize = 32;
+const int kHopSpacing = 2;
+const int kIconPad = 8;
+const int kAlternateIconPad = 5;
+const int kHopUpMS = 0;
+const int kHopDownMS = 200;
+const int kAttentionThrobDurationMS = 800;
+
+bool ShouldHop(int state) {
+ return state & ash::internal::LauncherButton::STATE_HOVERED ||
+ state & ash::internal::LauncherButton::STATE_ACTIVE ||
+ state & ash::internal::LauncherButton::STATE_FOCUSED;
+}
+
+// Simple AnimationDelegate that owns a single ThrobAnimation instance to
+// keep all Draw Attention animations in sync.
+class LauncherButtonAnimation : public ui::AnimationDelegate {
+ public:
+ class Observer {
+ public:
+ virtual void AnimationProgressed() = 0;
+
+ protected:
+ virtual ~Observer() {}
+ };
+
+ static LauncherButtonAnimation* GetInstance() {
+ static LauncherButtonAnimation* s_instance = new LauncherButtonAnimation();
+ return s_instance;
+ }
+
+ void AddObserver(Observer* observer) {
+ observers_.AddObserver(observer);
+ }
+
+ void RemoveObserver(Observer* observer) {
+ observers_.RemoveObserver(observer);
+ if (observers_.size() == 0)
+ animation_.Stop();
+ }
+
+ int GetAlpha() {
+ return GetThrobAnimation().CurrentValueBetween(0, 255);
+ }
+
+ double GetAnimation() {
+ return GetThrobAnimation().GetCurrentValue();
+ }
+
+ private:
+ LauncherButtonAnimation()
+ : animation_(this) {
+ animation_.SetThrobDuration(kAttentionThrobDurationMS);
+ animation_.SetTweenType(ui::Tween::SMOOTH_IN_OUT);
+ }
+
+ virtual ~LauncherButtonAnimation() {
+ }
+
+ ui::ThrobAnimation& GetThrobAnimation() {
+ if (!animation_.is_animating()) {
+ animation_.Reset();
+ animation_.StartThrobbing(-1 /*throb indefinitely*/);
+ }
+ return animation_;
+ }
+
+ // ui::AnimationDelegate
+ virtual void AnimationProgressed(const ui::Animation* animation) OVERRIDE {
+ if (animation != &animation_)
+ return;
+ if (!animation_.is_animating())
+ return;
+ FOR_EACH_OBSERVER(Observer, observers_, AnimationProgressed());
+ }
+
+ ui::ThrobAnimation animation_;
+ ObserverList<Observer> observers_;
+
+ DISALLOW_COPY_AND_ASSIGN(LauncherButtonAnimation);
+};
+
+} // namespace
+
+namespace ash {
+namespace internal {
+
+////////////////////////////////////////////////////////////////////////////////
+// LauncherButton::BarView
+
+class LauncherButton::BarView : public views::ImageView,
+ public LauncherButtonAnimation::Observer {
+ public:
+ BarView(LauncherButton* host)
+ : host_(host),
+ show_attention_(false) {
+ }
+
+ virtual ~BarView() {
+ if (show_attention_)
+ LauncherButtonAnimation::GetInstance()->RemoveObserver(this);
+ }
+
+ // View
+ virtual bool HitTestRect(const gfx::Rect& rect) const OVERRIDE {
+ // Allow Mouse...() messages to go to the parent view.
+ return false;
+ }
+
+ virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE {
+ if (show_attention_) {
+ int alpha = LauncherButtonAnimation::GetInstance()->GetAlpha();
+ canvas->SaveLayerAlpha(alpha);
+ views::ImageView::OnPaint(canvas);
+ canvas->Restore();
+ } else {
+ views::ImageView::OnPaint(canvas);
+ }
+ }
+
+ // LauncherButtonAnimation::Observer
+ virtual void AnimationProgressed() OVERRIDE {
+ UpdateBounds();
+ SchedulePaint();
+ }
+
+ void SetBarBoundsRect(const gfx::Rect& bounds) {
+ base_bounds_ = bounds;
+ UpdateBounds();
+ }
+
+ void ShowAttention(bool show) {
+ if (show_attention_ != show) {
+ show_attention_ = show;
+ if (show_attention_)
+ LauncherButtonAnimation::GetInstance()->AddObserver(this);
+ else
+ LauncherButtonAnimation::GetInstance()->RemoveObserver(this);
+ }
+ UpdateBounds();
+ }
+
+ private:
+ void UpdateBounds() {
+ gfx::Rect bounds = base_bounds_;
+ if (show_attention_) {
+ // Scale from .35 to 1.0 of the total width (which is wider than the
+ // visible width of the image, so the animation "rests" briefly at full
+ // visible width.
+ double animation = LauncherButtonAnimation::GetInstance()->GetAnimation();
+ double scale = (.35 + .65 * animation);
+ if (host_->shelf_layout_manager()->GetAlignment() ==
+ SHELF_ALIGNMENT_BOTTOM) {
+ bounds.set_width(base_bounds_.width() * scale);
+ int x_offset = (base_bounds_.width() - bounds.width()) / 2;
+ bounds.set_x(base_bounds_.x() + x_offset);
+ } else {
+ bounds.set_height(base_bounds_.height() * scale);
+ int y_offset = (base_bounds_.height() - bounds.height()) / 2;
+ bounds.set_y(base_bounds_.y() + y_offset);
+ }
+ }
+ SetBoundsRect(bounds);
+ }
+
+ LauncherButton* host_;
+ bool show_attention_;
+ gfx::Rect base_bounds_;
+
+ DISALLOW_COPY_AND_ASSIGN(BarView);
+};
+
+////////////////////////////////////////////////////////////////////////////////
+// LauncherButton::IconView
+
+LauncherButton::IconView::IconView() : icon_size_(kIconSize) {
+}
+
+LauncherButton::IconView::~IconView() {
+}
+
+bool LauncherButton::IconView::HitTestRect(const gfx::Rect& rect) const {
+ // Return false so that LauncherButton gets all the mouse events.
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// LauncherButton
+
+LauncherButton* LauncherButton::Create(
+ views::ButtonListener* listener,
+ LauncherButtonHost* host,
+ ShelfLayoutManager* shelf_layout_manager) {
+ LauncherButton* button =
+ new LauncherButton(listener, host, shelf_layout_manager);
+ button->Init();
+ return button;
+}
+
+LauncherButton::LauncherButton(views::ButtonListener* listener,
+ LauncherButtonHost* host,
+ ShelfLayoutManager* shelf_layout_manager)
+ : CustomButton(listener),
+ host_(host),
+ icon_view_(NULL),
+ bar_(new BarView(this)),
+ state_(STATE_NORMAL),
+ shelf_layout_manager_(shelf_layout_manager),
+ destroyed_flag_(NULL) {
+ set_accessibility_focusable(true);
+
+ const gfx::ShadowValue kShadows[] = {
+ gfx::ShadowValue(gfx::Point(0, 2), 0, SkColorSetARGB(0x1A, 0, 0, 0)),
+ gfx::ShadowValue(gfx::Point(0, 3), 1, SkColorSetARGB(0x1A, 0, 0, 0)),
+ gfx::ShadowValue(gfx::Point(0, 0), 1, SkColorSetARGB(0x54, 0, 0, 0)),
+ };
+ icon_shadows_.assign(kShadows, kShadows + arraysize(kShadows));
+
+ AddChildView(bar_);
+}
+
+LauncherButton::~LauncherButton() {
+ if (destroyed_flag_)
+ *destroyed_flag_ = true;
+}
+
+void LauncherButton::SetShadowedImage(const gfx::ImageSkia& image) {
+ icon_view_->SetImage(gfx::ImageSkiaOperations::CreateImageWithDropShadow(
+ image, icon_shadows_));
+}
+
+void LauncherButton::SetImage(const gfx::ImageSkia& image) {
+ if (image.isNull()) {
+ // TODO: need an empty image.
+ icon_view_->SetImage(image);
+ return;
+ }
+
+ if (icon_view_->icon_size() == 0) {
+ SetShadowedImage(image);
+ return;
+ }
+
+ // Resize the image maintaining our aspect ratio.
+ int pref = icon_view_->icon_size();
+ float aspect_ratio =
+ static_cast<float>(image.width()) / static_cast<float>(image.height());
+ int height = pref;
+ int width = static_cast<int>(aspect_ratio * height);
+ if (width > pref) {
+ width = pref;
+ height = static_cast<int>(width / aspect_ratio);
+ }
+
+ if (width == image.width() && height == image.height()) {
+ SetShadowedImage(image);
+ return;
+ }
+
+ SetShadowedImage(gfx::ImageSkiaOperations::CreateResizedImage(image,
+ skia::ImageOperations::RESIZE_BEST, gfx::Size(width, height)));
+}
+
+void LauncherButton::AddState(State state) {
+ if (!(state_ & state)) {
+ if (!ash::switches::UseAlternateShelfLayout() &&
+ (ShouldHop(state) || !ShouldHop(state_))) {
+ ui::ScopedLayerAnimationSettings scoped_setter(
+ icon_view_->layer()->GetAnimator());
+ scoped_setter.SetTransitionDuration(
+ base::TimeDelta::FromMilliseconds(kHopUpMS));
+ }
+ state_ |= state;
+ Layout();
+ if (state & STATE_ATTENTION)
+ bar_->ShowAttention(true);
+ }
+}
+
+void LauncherButton::ClearState(State state) {
+ if (state_ & state) {
+ if (!ash::switches::UseAlternateShelfLayout() &&
+ (!ShouldHop(state) || ShouldHop(state_))) {
+ ui::ScopedLayerAnimationSettings scoped_setter(
+ icon_view_->layer()->GetAnimator());
+ scoped_setter.SetTweenType(ui::Tween::LINEAR);
+ scoped_setter.SetTransitionDuration(
+ base::TimeDelta::FromMilliseconds(kHopDownMS));
+ }
+ state_ &= ~state;
+ Layout();
+ if (state & STATE_ATTENTION)
+ bar_->ShowAttention(false);
+ }
+}
+
+gfx::Rect LauncherButton::GetIconBounds() const {
+ return icon_view_->bounds();
+}
+
+void LauncherButton::ShowContextMenu(const gfx::Point& p,
+ ui::MenuSourceType source_type) {
+ if (!context_menu_controller())
+ return;
+
+ bool destroyed = false;
+ destroyed_flag_ = &destroyed;
+
+ CustomButton::ShowContextMenu(p, source_type);
+
+ if (!destroyed) {
+ destroyed_flag_ = NULL;
+ // The menu will not propagate mouse events while its shown. To address,
+ // the hover state gets cleared once the menu was shown (and this was not
+ // destroyed).
+ ClearState(STATE_HOVERED);
+ }
+}
+
+bool LauncherButton::OnMousePressed(const ui::MouseEvent& event) {
+ CustomButton::OnMousePressed(event);
+ host_->PointerPressedOnButton(this, LauncherButtonHost::MOUSE, event);
+ return true;
+}
+
+void LauncherButton::OnMouseReleased(const ui::MouseEvent& event) {
+ CustomButton::OnMouseReleased(event);
+ host_->PointerReleasedOnButton(this, LauncherButtonHost::MOUSE, false);
+}
+
+void LauncherButton::OnMouseCaptureLost() {
+ ClearState(STATE_HOVERED);
+ host_->PointerReleasedOnButton(this, LauncherButtonHost::MOUSE, true);
+ CustomButton::OnMouseCaptureLost();
+}
+
+bool LauncherButton::OnMouseDragged(const ui::MouseEvent& event) {
+ CustomButton::OnMouseDragged(event);
+ host_->PointerDraggedOnButton(this, LauncherButtonHost::MOUSE, event);
+ return true;
+}
+
+void LauncherButton::OnMouseMoved(const ui::MouseEvent& event) {
+ CustomButton::OnMouseMoved(event);
+ host_->MouseMovedOverButton(this);
+}
+
+void LauncherButton::OnMouseEntered(const ui::MouseEvent& event) {
+ AddState(STATE_HOVERED);
+ CustomButton::OnMouseEntered(event);
+ host_->MouseEnteredButton(this);
+}
+
+void LauncherButton::OnMouseExited(const ui::MouseEvent& event) {
+ ClearState(STATE_HOVERED);
+ CustomButton::OnMouseExited(event);
+ host_->MouseExitedButton(this);
+}
+
+void LauncherButton::GetAccessibleState(ui::AccessibleViewState* state) {
+ state->role = ui::AccessibilityTypes::ROLE_PUSHBUTTON;
+ state->name = host_->GetAccessibleName(this);
+}
+
+void LauncherButton::Layout() {
+ const gfx::Rect button_bounds(GetContentsBounds());
+ int icon_pad = ash::switches::UseAlternateShelfLayout() ?
+ kAlternateIconPad : kIconPad;
+ int x_offset = shelf_layout_manager_->PrimaryAxisValue(0, icon_pad);
+ int y_offset = shelf_layout_manager_->PrimaryAxisValue(icon_pad, 0);
+
+ int icon_width = std::min(kIconSize,
+ button_bounds.width() - x_offset);
+ int icon_height = std::min(kIconSize,
+ button_bounds.height() - y_offset);
+
+ // If on the left or top 'invert' the inset so the constant gap is on
+ // the interior (towards the center of display) edge of the shelf.
+ if (SHELF_ALIGNMENT_LEFT == shelf_layout_manager_->GetAlignment())
+ x_offset = button_bounds.width() - (kIconSize + icon_pad);
+
+ if (SHELF_ALIGNMENT_TOP == shelf_layout_manager_->GetAlignment())
+ y_offset = button_bounds.height() - (kIconSize + icon_pad);
+
+ if (ShouldHop(state_) && !ash::switches::UseAlternateShelfLayout()) {
+ x_offset += shelf_layout_manager_->SelectValueForShelfAlignment(
+ 0, kHopSpacing, -kHopSpacing, 0);
+ y_offset += shelf_layout_manager_->SelectValueForShelfAlignment(
+ -kHopSpacing, 0, 0, kHopSpacing);
+ }
+
+ // Center icon with respect to the secondary axis, and ensure
+ // that the icon doesn't occlude the bar highlight.
+ if (shelf_layout_manager_->IsHorizontalAlignment()) {
+ x_offset = std::max(0, button_bounds.width() - icon_width) / 2;
+ if (y_offset + icon_height + kBarSize > button_bounds.height())
+ icon_height = button_bounds.height() - (y_offset + kBarSize);
+ } else {
+ y_offset = std::max(0, button_bounds.height() - icon_height) / 2;
+ if (x_offset + icon_width + kBarSize > button_bounds.width())
+ icon_width = button_bounds.width() - (x_offset + kBarSize);
+ }
+
+ icon_view_->SetBoundsRect(gfx::Rect(
+ button_bounds.x() + x_offset,
+ button_bounds.y() + y_offset,
+ icon_width,
+ icon_height));
+
+ // Icon size has been incorrect when running
+ // PanelLayoutManagerTest.PanelAlignmentSecondDisplay on valgrind bot, see
+ // http://crbug.com/234854.
+ DCHECK_LE(icon_width, kIconSize);
+ DCHECK_LE(icon_height, kIconSize);
+
+ bar_->SetBarBoundsRect(button_bounds);
+
+ UpdateState();
+}
+
+void LauncherButton::ChildPreferredSizeChanged(views::View* child) {
+ Layout();
+}
+
+void LauncherButton::OnFocus() {
+ AddState(STATE_FOCUSED);
+ CustomButton::OnFocus();
+}
+
+void LauncherButton::OnBlur() {
+ ClearState(STATE_FOCUSED);
+ CustomButton::OnBlur();
+}
+
+void LauncherButton::OnGestureEvent(ui::GestureEvent* event) {
+ switch (event->type()) {
+ case ui::ET_GESTURE_TAP_DOWN:
+ AddState(STATE_HOVERED);
+ return CustomButton::OnGestureEvent(event);
+ case ui::ET_GESTURE_END:
+ ClearState(STATE_HOVERED);
+ return CustomButton::OnGestureEvent(event);
+ case ui::ET_GESTURE_SCROLL_BEGIN:
+ host_->PointerPressedOnButton(this, LauncherButtonHost::TOUCH, *event);
+ event->SetHandled();
+ return;
+ case ui::ET_GESTURE_SCROLL_UPDATE:
+ host_->PointerDraggedOnButton(this, LauncherButtonHost::TOUCH, *event);
+ event->SetHandled();
+ return;
+ case ui::ET_GESTURE_SCROLL_END:
+ case ui::ET_SCROLL_FLING_START:
+ host_->PointerReleasedOnButton(this, LauncherButtonHost::TOUCH, false);
+ event->SetHandled();
+ return;
+ default:
+ return CustomButton::OnGestureEvent(event);
+ }
+}
+
+void LauncherButton::Init() {
+ icon_view_ = CreateIconView();
+
+ // TODO: refactor the layers so each button doesn't require 2.
+ icon_view_->SetPaintToLayer(true);
+ icon_view_->SetFillsBoundsOpaquely(false);
+ icon_view_->SetHorizontalAlignment(views::ImageView::CENTER);
+ icon_view_->SetVerticalAlignment(views::ImageView::LEADING);
+
+ AddChildView(icon_view_);
+}
+
+LauncherButton::IconView* LauncherButton::CreateIconView() {
+ return new IconView;
+}
+
+bool LauncherButton::IsShelfHorizontal() const {
+ return shelf_layout_manager_->IsHorizontalAlignment();
+}
+
+void LauncherButton::UpdateState() {
+ // Even if not shown, the activation state image has an influence on the
+ // layout. To avoid any odd movement we assign a bitmap here.
+ int bar_id = 0;
+ if (ash::switches::UseAlternateShelfLayout()) {
+ if (state_ & STATE_ACTIVE)
+ bar_id = IDR_AURA_LAUNCHER_UNDERLINE_ACTIVE_ALTERNATE;
+ else if (state_ & STATE_RUNNING)
+ bar_id = IDR_AURA_LAUNCHER_UNDERLINE_RUNNING_ALTERNATE;
+ } else {
+ if (state_ & (STATE_ACTIVE | STATE_ATTENTION))
+ bar_id = IDR_AURA_LAUNCHER_UNDERLINE_ACTIVE;
+ else if (state_ & (STATE_HOVERED | STATE_FOCUSED))
+ bar_id = IDR_AURA_LAUNCHER_UNDERLINE_HOVER;
+ else
+ bar_id = IDR_AURA_LAUNCHER_UNDERLINE_RUNNING;
+ }
+
+ if (bar_id != 0) {
+ ResourceBundle& rb = ResourceBundle::GetSharedInstance();
+ const gfx::ImageSkia* image = rb.GetImageNamed(bar_id).ToImageSkia();
+ if (shelf_layout_manager_->GetAlignment() == SHELF_ALIGNMENT_BOTTOM) {
+ bar_->SetImage(*image);
+ } else {
+ bar_->SetImage(gfx::ImageSkiaOperations::CreateRotatedImage(*image,
+ shelf_layout_manager_->SelectValueForShelfAlignment(
+ SkBitmapOperations::ROTATION_90_CW,
+ SkBitmapOperations::ROTATION_90_CW,
+ SkBitmapOperations::ROTATION_270_CW,
+ SkBitmapOperations::ROTATION_180_CW)));
+ }
+ bar_->SetHorizontalAlignment(
+ shelf_layout_manager_->SelectValueForShelfAlignment(
+ views::ImageView::CENTER,
+ views::ImageView::LEADING,
+ views::ImageView::TRAILING,
+ views::ImageView::CENTER));
+ bar_->SetVerticalAlignment(
+ shelf_layout_manager_->SelectValueForShelfAlignment(
+ views::ImageView::TRAILING,
+ views::ImageView::CENTER,
+ views::ImageView::CENTER,
+ views::ImageView::LEADING));
+ bar_->SchedulePaint();
+ }
+
+ bar_->SetVisible(bar_id != 0 && state_ != STATE_NORMAL);
+
+ icon_view_->SetHorizontalAlignment(
+ shelf_layout_manager_->PrimaryAxisValue(views::ImageView::CENTER,
+ views::ImageView::LEADING));
+ icon_view_->SetVerticalAlignment(
+ shelf_layout_manager_->PrimaryAxisValue(views::ImageView::LEADING,
+ views::ImageView::CENTER));
+ SchedulePaint();
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/launcher/launcher_button.h b/chromium/ash/launcher/launcher_button.h
new file mode 100644
index 00000000000..707aac06523
--- /dev/null
+++ b/chromium/ash/launcher/launcher_button.h
@@ -0,0 +1,150 @@
+// Copyright (c) 2012 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 ASH_LAUNCHER_LAUNCHER_BUTTON_H_
+#define ASH_LAUNCHER_LAUNCHER_BUTTON_H_
+
+#include "ash/ash_export.h"
+#include "ui/gfx/shadow_value.h"
+#include "ui/views/controls/button/custom_button.h"
+#include "ui/views/controls/image_view.h"
+
+namespace ash {
+namespace internal {
+
+class LauncherButtonHost;
+class ShelfLayoutManager;
+
+// Button used for items on the launcher, except for the AppList.
+class ASH_EXPORT LauncherButton : public views::CustomButton {
+ public:
+ // Used to indicate the current state of the button.
+ enum State {
+ // Nothing special. Usually represents an app shortcut item with no running
+ // instance.
+ STATE_NORMAL = 0,
+ // Button has mouse hovering on it.
+ STATE_HOVERED = 1 << 0,
+ // Underlying LauncherItem has a running instance.
+ // e.g. A TYPE_TABBED item that has a window.
+ STATE_RUNNING = 1 << 1,
+ // Underlying LauncherItem is active (i.e. has focus).
+ STATE_ACTIVE = 1 << 2,
+ // Underlying LauncherItem needs user's attention.
+ STATE_ATTENTION = 1 << 3,
+ STATE_FOCUSED = 1 << 4,
+ };
+
+ virtual ~LauncherButton();
+
+ // Called to create an instance of a LauncherButton.
+ static LauncherButton* Create(views::ButtonListener* listener,
+ LauncherButtonHost* host,
+ ShelfLayoutManager* shelf_layout_manager);
+
+ // Sets the image to display for this entry.
+ void SetImage(const gfx::ImageSkia& image);
+
+ // |state| is or'd into the current state.
+ void AddState(State state);
+ void ClearState(State state);
+ int state() const { return state_; }
+ const ShelfLayoutManager* shelf_layout_manager() const {
+ return shelf_layout_manager_;
+ }
+
+ // Returns the bounds of the icon.
+ gfx::Rect GetIconBounds() const;
+
+ // Overrides to views::CustomButton:
+ virtual void ShowContextMenu(const gfx::Point& p,
+ ui::MenuSourceType source_type) OVERRIDE;
+
+ protected:
+ LauncherButton(views::ButtonListener* listener,
+ LauncherButtonHost* host,
+ ShelfLayoutManager* shelf_layout_manager);
+
+ // Class that draws the icon part of a button, so it can be animated
+ // independently of the rest. This can be subclassed to provide a custom
+ // implementation, by overriding CreateIconView().
+ class IconView : public views::ImageView {
+ public:
+ IconView();
+ virtual ~IconView();
+
+ void set_icon_size(int icon_size) { icon_size_ = icon_size; }
+ int icon_size() const { return icon_size_; }
+
+ // views::View overrides.
+ virtual bool HitTestRect(const gfx::Rect& rect) const OVERRIDE;
+
+ private:
+ // Set to non-zero to force icons to be resized to fit within a square,
+ // while maintaining original proportions.
+ int icon_size_;
+
+ DISALLOW_COPY_AND_ASSIGN(IconView);
+ };
+
+ // View overrides:
+ virtual bool OnMousePressed(const ui::MouseEvent& event) OVERRIDE;
+ virtual void OnMouseReleased(const ui::MouseEvent& event) OVERRIDE;
+ virtual void OnMouseCaptureLost() OVERRIDE;
+ virtual bool OnMouseDragged(const ui::MouseEvent& event) OVERRIDE;
+ virtual void OnMouseMoved(const ui::MouseEvent& event) OVERRIDE;
+ virtual void OnMouseEntered(const ui::MouseEvent& event) OVERRIDE;
+ virtual void OnMouseExited(const ui::MouseEvent& event) OVERRIDE;
+ virtual void GetAccessibleState(ui::AccessibleViewState* state) OVERRIDE;
+ virtual void Layout() OVERRIDE;
+ virtual void ChildPreferredSizeChanged(views::View* child) OVERRIDE;
+ virtual void OnFocus() OVERRIDE;
+ virtual void OnBlur() OVERRIDE;
+
+ // ui::EventHandler overrides:
+ virtual void OnGestureEvent(ui::GestureEvent* event) OVERRIDE;
+
+ // Sets the icon image with a shadow.
+ void SetShadowedImage(const gfx::ImageSkia& bitmap);
+ // Override for custom initialization.
+ virtual void Init();
+ // Override to subclass IconView.
+ virtual IconView* CreateIconView();
+ IconView* icon_view() const { return icon_view_; }
+ LauncherButtonHost* host() const { return host_; }
+
+ private:
+ class BarView;
+
+ // Returns true if the shelf is horizontal. If this returns false the shelf is
+ // vertical.
+ bool IsShelfHorizontal() const;
+
+ // Updates the parts of the button to reflect the current |state_| and
+ // alignment. This may add or remove views, layout and paint.
+ void UpdateState();
+
+ LauncherButtonHost* host_;
+ IconView* icon_view_;
+ // Draws a bar underneath the image to represent the state of the application.
+ BarView* bar_;
+ // The current state of the application, multiple values of AppState are or'd
+ // together.
+ int state_;
+
+ ShelfLayoutManager* shelf_layout_manager_;
+
+ gfx::ShadowValues icon_shadows_;
+
+ // If non-null the destuctor sets this to true. This is set while the menu is
+ // showing and used to detect if the menu was deleted while running.
+ bool* destroyed_flag_;
+
+ DISALLOW_COPY_AND_ASSIGN(LauncherButton);
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_LAUNCHER_LAUNCHER_BUTTON_H_
diff --git a/chromium/ash/launcher/launcher_button_host.h b/chromium/ash/launcher/launcher_button_host.h
new file mode 100644
index 00000000000..46f2e593a2d
--- /dev/null
+++ b/chromium/ash/launcher/launcher_button_host.h
@@ -0,0 +1,67 @@
+// Copyright (c) 2012 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 ASH_LAUNCHER_LAUNCHER_BUTTON_HOST_H_
+#define ASH_LAUNCHER_LAUNCHER_BUTTON_HOST_H_
+
+#include "ash/ash_export.h"
+#include "base/strings/string16.h"
+
+namespace ui {
+class LocatedEvent;
+}
+
+namespace views {
+class View;
+}
+
+namespace ash {
+namespace internal {
+
+// The launcher buttons communicate back to the host by way of this interface.
+// This interface is used to enable reordering the items on the launcher.
+class ASH_EXPORT LauncherButtonHost {
+ public:
+ enum Pointer {
+ NONE,
+ DRAG_AND_DROP,
+ MOUSE,
+ TOUCH,
+ };
+
+ // Invoked when a pointer device is pressed on a view.
+ virtual void PointerPressedOnButton(views::View* view,
+ Pointer pointer,
+ const ui::LocatedEvent& event) = 0;
+
+ // Invoked when a pointer device is dragged over a view.
+ virtual void PointerDraggedOnButton(views::View* view,
+ Pointer pointer,
+ const ui::LocatedEvent& event) = 0;
+
+ // Invoked either if a pointer device is released or mouse capture canceled.
+ virtual void PointerReleasedOnButton(views::View* view,
+ Pointer pointer,
+ bool canceled) = 0;
+
+ // Invoked when the mouse moves on the item.
+ virtual void MouseMovedOverButton(views::View* view) = 0;
+
+ // Invoked when the mouse enters the item.
+ virtual void MouseEnteredButton(views::View* view) = 0;
+
+ // Invoked when the mouse exits the item.
+ virtual void MouseExitedButton(views::View* view) = 0;
+
+ // Invoked to get the accessible name of the item.
+ virtual base::string16 GetAccessibleName(const views::View* view) = 0;
+
+ protected:
+ virtual ~LauncherButtonHost() {}
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_LAUNCHER_LAUNCHER_BUTTON_HOST_H_
diff --git a/chromium/ash/launcher/launcher_delegate.h b/chromium/ash/launcher/launcher_delegate.h
new file mode 100644
index 00000000000..20064ea6777
--- /dev/null
+++ b/chromium/ash/launcher/launcher_delegate.h
@@ -0,0 +1,114 @@
+// Copyright (c) 2012 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 ASH_LAUNCHER_LAUNCHER_DELEGATE_H_
+#define ASH_LAUNCHER_LAUNCHER_DELEGATE_H_
+
+#include "ash/ash_export.h"
+#include "ash/launcher/launcher_types.h"
+#include "base/strings/string16.h"
+#include "ui/base/models/simple_menu_model.h"
+
+namespace aura {
+class RootWindow;
+}
+
+namespace ui {
+class Event;
+}
+
+namespace ash {
+class Launcher;
+
+// A special menu model which keeps track of an "active" menu item.
+class ASH_EXPORT LauncherMenuModel : public ui::SimpleMenuModel {
+ public:
+ explicit LauncherMenuModel(ui::SimpleMenuModel::Delegate* delegate)
+ : ui::SimpleMenuModel(delegate) {}
+
+ // Returns |true| when the given |command_id| is active and needs to be drawn
+ // in a special state.
+ virtual bool IsCommandActive(int command_id) const = 0;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(LauncherMenuModel);
+};
+
+// Delegate for the Launcher.
+class ASH_EXPORT LauncherDelegate {
+ public:
+ // Launcher owns the delegate.
+ virtual ~LauncherDelegate() {}
+
+ // Invoked when the user clicks on a window entry in the launcher.
+ // |event| is the click event. The |event| is dispatched by a view
+ // and has an instance of |views::View| as the event target
+ // but not |aura::Window|. If the |event| is of type KeyEvent, it is assumed
+ // that this was triggered by keyboard action (Alt+<number>) and special
+ // handling might happen (PerApp launcher).
+ virtual void ItemSelected(const LauncherItem& item,
+ const ui::Event& event) = 0;
+
+ // Returns the title to display for the specified launcher item.
+ virtual base::string16 GetTitle(const LauncherItem& item) = 0;
+
+ // Returns the context menumodel for the specified item on
+ // |root_window|. Return NULL if there should be no context
+ // menu. The caller takes ownership of the returned model.
+ virtual ui::MenuModel* CreateContextMenu(const LauncherItem& item,
+ aura::RootWindow* root_window) = 0;
+
+ // Returns the application menu model for the specified item. There are three
+ // possible return values:
+ // - A return of NULL indicates that no menu is wanted for this item.
+ // - A return of a menu with one item means that only the name of the
+ // application/item was added and there are no active applications.
+ // Note: This is useful for hover menus which also show context help.
+ // - A list containing the title and the active list of items.
+ // The caller takes ownership of the returned model.
+ // |event_flags| specifies the flags of the event which triggered this menu.
+ virtual LauncherMenuModel* CreateApplicationMenu(
+ const LauncherItem& item,
+ int event_flags) = 0;
+
+ // Returns the id of the item associated with the specified window, or 0 if
+ // there isn't one.
+ virtual LauncherID GetIDByWindow(aura::Window* window) = 0;
+
+ // Whether the given launcher item is draggable.
+ virtual bool IsDraggable(const LauncherItem& item) = 0;
+
+ // Returns true if a tooltip should be shown for the item.
+ virtual bool ShouldShowTooltip(const LauncherItem& item) = 0;
+
+ // Callback used to allow delegate to perform initialization actions that
+ // depend on the Launcher being in a known state.
+ virtual void OnLauncherCreated(Launcher* launcher) = 0;
+
+ // Callback used to inform the delegate that a specific launcher no longer
+ // exists.
+ virtual void OnLauncherDestroyed(Launcher* launcher) = 0;
+
+ // True if the running launcher is the per application launcher.
+ virtual bool IsPerAppLauncher() = 0;
+
+ // Get the launcher ID from an application ID.
+ virtual LauncherID GetLauncherIDForAppID(const std::string& app_id) = 0;
+
+ // Pins an app with |app_id| to launcher. A running instance will get pinned.
+ // In case there is no running instance a new launcher item is created and
+ // pinned.
+ virtual void PinAppWithID(const std::string& app_id) = 0;
+
+ // Check if the app with |app_id_| is pinned to the launcher.
+ virtual bool IsAppPinned(const std::string& app_id) = 0;
+
+ // Unpins any app item(s) whose id is |app_id|. The new launcher will collect
+ // all items under one item, the old launcher might have multiple items.
+ virtual void UnpinAppsWithID(const std::string& app_id) = 0;
+};
+
+} // namespace ash
+
+#endif // ASH_LAUNCHER_LAUNCHER_DELEGATE_H_
diff --git a/chromium/ash/launcher/launcher_icon_observer.h b/chromium/ash/launcher/launcher_icon_observer.h
new file mode 100644
index 00000000000..d452934c7ce
--- /dev/null
+++ b/chromium/ash/launcher/launcher_icon_observer.h
@@ -0,0 +1,24 @@
+// Copyright (c) 2012 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 ASH_LAUNCHER_LAUNCHER_ICON_OBSERVER_H_
+#define ASH_LAUNCHER_LAUNCHER_ICON_OBSERVER_H_
+
+#include "ash/ash_export.h"
+#include "base/basictypes.h"
+
+namespace ash {
+
+class ASH_EXPORT LauncherIconObserver {
+ public:
+ // Invoked when any icon on launcher changes position.
+ virtual void OnLauncherIconPositionsChanged() = 0;
+
+ protected:
+ virtual ~LauncherIconObserver() {}
+};
+
+} // namespace ash
+
+#endif // ASH_LAUNCHER_LAUNCHER_ICON_OBSERVER_H_
diff --git a/chromium/ash/launcher/launcher_model.cc b/chromium/ash/launcher/launcher_model.cc
new file mode 100644
index 00000000000..9607de6fcfe
--- /dev/null
+++ b/chromium/ash/launcher/launcher_model.cc
@@ -0,0 +1,184 @@
+// Copyright (c) 2012 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.
+
+#include "ash/launcher/launcher_model.h"
+
+#include <algorithm>
+
+#include "ash/ash_switches.h"
+#include "ash/launcher/launcher_model_observer.h"
+
+namespace ash {
+
+namespace {
+
+int LauncherItemTypeToWeight(LauncherItemType type) {
+ if (ash::switches::UseAlternateShelfLayout()) {
+ switch (type) {
+ case TYPE_APP_LIST:
+ return 0;
+ case TYPE_BROWSER_SHORTCUT:
+ case TYPE_APP_SHORTCUT:
+ case TYPE_WINDOWED_APP:
+ return 1;
+ case TYPE_TABBED:
+ case TYPE_PLATFORM_APP:
+ return 2;
+ case TYPE_APP_PANEL:
+ return 3;
+ }
+ } else {
+ switch (type) {
+ case TYPE_BROWSER_SHORTCUT:
+ case TYPE_APP_SHORTCUT:
+ case TYPE_WINDOWED_APP:
+ return 0;
+ case TYPE_TABBED:
+ case TYPE_PLATFORM_APP:
+ return 1;
+ case TYPE_APP_LIST:
+ return 2;
+ case TYPE_APP_PANEL:
+ return 3;
+ }
+ }
+
+ NOTREACHED() << "Invalid type " << type;
+ return 1;
+}
+
+bool CompareByWeight(const LauncherItem& a, const LauncherItem& b) {
+ return LauncherItemTypeToWeight(a.type) < LauncherItemTypeToWeight(b.type);
+}
+
+} // namespace
+
+LauncherModel::LauncherModel() : next_id_(1), status_(STATUS_NORMAL) {
+ LauncherItem app_list;
+ app_list.type = TYPE_APP_LIST;
+ app_list.is_incognito = false;
+ AddAt(0, app_list);
+}
+
+LauncherModel::~LauncherModel() {
+}
+
+int LauncherModel::Add(const LauncherItem& item) {
+ return AddAt(items_.size(), item);
+}
+
+int LauncherModel::AddAt(int index, const LauncherItem& item) {
+ index = ValidateInsertionIndex(item.type, index);
+ items_.insert(items_.begin() + index, item);
+ items_[index].id = next_id_++;
+ FOR_EACH_OBSERVER(LauncherModelObserver, observers_,
+ LauncherItemAdded(index));
+ return index;
+}
+
+void LauncherModel::RemoveItemAt(int index) {
+ DCHECK(index >= 0 && index < item_count());
+ // The app list and browser shortcut can't be removed.
+ DCHECK(items_[index].type != TYPE_APP_LIST &&
+ items_[index].type != TYPE_BROWSER_SHORTCUT);
+ LauncherID id = items_[index].id;
+ items_.erase(items_.begin() + index);
+ FOR_EACH_OBSERVER(LauncherModelObserver, observers_,
+ LauncherItemRemoved(index, id));
+}
+
+void LauncherModel::Move(int index, int target_index) {
+ if (index == target_index)
+ return;
+ // TODO: this needs to enforce valid ranges.
+ LauncherItem item(items_[index]);
+ items_.erase(items_.begin() + index);
+ items_.insert(items_.begin() + target_index, item);
+ FOR_EACH_OBSERVER(LauncherModelObserver, observers_,
+ LauncherItemMoved(index, target_index));
+}
+
+void LauncherModel::Set(int index, const LauncherItem& item) {
+ DCHECK(index >= 0 && index < item_count());
+ int new_index = item.type == items_[index].type ?
+ index : ValidateInsertionIndex(item.type, index);
+
+ LauncherItem old_item(items_[index]);
+ items_[index] = item;
+ items_[index].id = old_item.id;
+ FOR_EACH_OBSERVER(LauncherModelObserver, observers_,
+ LauncherItemChanged(index, old_item));
+
+ // If the type changes confirm that the item is still in the right order.
+ if (new_index != index) {
+ // The move function works by removing one item and then inserting it at the
+ // new location. However - by removing the item first the order will change
+ // so that our target index needs to be corrected.
+ // TODO(skuhne): Moving this into the Move function breaks lots of unit
+ // tests. So several functions were already using this incorrectly.
+ // That needs to be cleaned up.
+ if (index < new_index)
+ new_index--;
+
+ Move(index, new_index);
+ }
+}
+
+int LauncherModel::ItemIndexByID(LauncherID id) const {
+ LauncherItems::const_iterator i = ItemByID(id);
+ return i == items_.end() ? -1 : static_cast<int>(i - items_.begin());
+}
+
+LauncherItems::const_iterator LauncherModel::ItemByID(int id) const {
+ for (LauncherItems::const_iterator i = items_.begin();
+ i != items_.end(); ++i) {
+ if (i->id == id)
+ return i;
+ }
+ return items_.end();
+}
+
+int LauncherModel::FirstPanelIndex() const {
+ LauncherItem weight_dummy;
+ weight_dummy.type = TYPE_APP_PANEL;
+ return std::lower_bound(items_.begin(), items_.end(), weight_dummy,
+ CompareByWeight) - items_.begin();
+}
+
+void LauncherModel::SetStatus(Status status) {
+ if (status_ == status)
+ return;
+
+ status_ = status;
+ FOR_EACH_OBSERVER(LauncherModelObserver, observers_,
+ LauncherStatusChanged());
+}
+
+void LauncherModel::AddObserver(LauncherModelObserver* observer) {
+ observers_.AddObserver(observer);
+}
+
+void LauncherModel::RemoveObserver(LauncherModelObserver* observer) {
+ observers_.RemoveObserver(observer);
+}
+
+int LauncherModel::ValidateInsertionIndex(LauncherItemType type,
+ int index) const {
+ DCHECK(index >= 0 && index <= item_count() +
+ (ash::switches::UseAlternateShelfLayout() ? 1 : 0));
+
+ // Clamp |index| to the allowed range for the type as determined by |weight|.
+ LauncherItem weight_dummy;
+ weight_dummy.type = type;
+ index = std::max(std::lower_bound(items_.begin(), items_.end(), weight_dummy,
+ CompareByWeight) - items_.begin(),
+ static_cast<LauncherItems::difference_type>(index));
+ index = std::min(std::upper_bound(items_.begin(), items_.end(), weight_dummy,
+ CompareByWeight) - items_.begin(),
+ static_cast<LauncherItems::difference_type>(index));
+
+ return index;
+}
+
+} // namespace ash
diff --git a/chromium/ash/launcher/launcher_model.h b/chromium/ash/launcher/launcher_model.h
new file mode 100644
index 00000000000..0d544866b97
--- /dev/null
+++ b/chromium/ash/launcher/launcher_model.h
@@ -0,0 +1,91 @@
+// Copyright (c) 2012 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 ASH_LAUNCHER_LAUNCHER_MODEL_H_
+#define ASH_LAUNCHER_LAUNCHER_MODEL_H_
+
+#include "ash/ash_export.h"
+#include "ash/launcher/launcher_types.h"
+#include "base/basictypes.h"
+#include "base/observer_list.h"
+
+namespace ash {
+
+class LauncherModelObserver;
+
+// Model used by LauncherView.
+class ASH_EXPORT LauncherModel {
+ public:
+ enum Status {
+ STATUS_NORMAL,
+ // A status that indicates apps are syncing/loading.
+ STATUS_LOADING,
+ };
+
+ LauncherModel();
+ ~LauncherModel();
+
+ // Adds a new item to the model. Returns the resulting index.
+ int Add(const LauncherItem& item);
+
+ // Adds the item. |index| is the requested insertion index, which may be
+ // modified to meet type-based ordering. Returns the actual insertion index.
+ int AddAt(int index, const LauncherItem& item);
+
+ // Removes the item at |index|.
+ void RemoveItemAt(int index);
+
+ // Moves the item at |index| to |target_index|. |target_index| is in terms
+ // of the model *after* the item at |index| is removed.
+ void Move(int index, int target_index);
+
+ // Resets the item at the specified index. The item maintains its existing
+ // id and type.
+ void Set(int index, const LauncherItem& item);
+
+ // Returns the index of the item by id.
+ int ItemIndexByID(LauncherID id) const;
+
+ // Returns the index of the first panel or the index where the first panel
+ // would go if there are no panels.
+ int FirstPanelIndex() const;
+
+ // Returns the id assigned to the next item added.
+ LauncherID next_id() const { return next_id_; }
+
+ // Returns a reserved id which will not be used by the |LauncherModel|.
+ LauncherID reserve_external_id() { return next_id_++; }
+
+ // Returns an iterator into items() for the item with the specified id, or
+ // items().end() if there is no item with the specified id.
+ LauncherItems::const_iterator ItemByID(LauncherID id) const;
+
+ const LauncherItems& items() const { return items_; }
+ int item_count() const { return static_cast<int>(items_.size()); }
+
+ void SetStatus(Status status);
+ Status status() const { return status_; }
+
+ void AddObserver(LauncherModelObserver* observer);
+ void RemoveObserver(LauncherModelObserver* observer);
+
+ private:
+ // Makes sure |index| is in line with the type-based order of items. If that
+ // is not the case, adjusts index by shifting it to the valid range and
+ // returns the new value.
+ int ValidateInsertionIndex(LauncherItemType type, int index) const;
+
+ // ID assigned to the next item.
+ LauncherID next_id_;
+
+ LauncherItems items_;
+ Status status_;
+ ObserverList<LauncherModelObserver> observers_;
+
+ DISALLOW_COPY_AND_ASSIGN(LauncherModel);
+};
+
+} // namespace ash
+
+#endif // ASH_LAUNCHER_LAUNCHER_MODEL_H_
diff --git a/chromium/ash/launcher/launcher_model_observer.h b/chromium/ash/launcher/launcher_model_observer.h
new file mode 100644
index 00000000000..16067c02efd
--- /dev/null
+++ b/chromium/ash/launcher/launcher_model_observer.h
@@ -0,0 +1,41 @@
+// Copyright (c) 2012 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 ASH_LAUNCHER_LAUNCHER_MODEL_OBSERVER_H_
+#define ASH_LAUNCHER_LAUNCHER_MODEL_OBSERVER_H_
+
+#include "ash/ash_export.h"
+#include "ash/launcher/launcher_types.h"
+
+namespace ash {
+
+struct LauncherItem;
+
+class ASH_EXPORT LauncherModelObserver {
+ public:
+ // Invoked after an item has been added to the model.
+ virtual void LauncherItemAdded(int index) = 0;
+
+ // Invoked after an item has been removed. |index| is the index the item was
+ // at.
+ virtual void LauncherItemRemoved(int index, LauncherID id) = 0;
+
+ // Invoked after an item has been moved. See LauncherModel::Move() for details
+ // of the arguments.
+ virtual void LauncherItemMoved(int start_index, int target_index) = 0;
+
+ // Invoked when the state of an item changes. |old_item| is the item
+ // before the change.
+ virtual void LauncherItemChanged(int index, const LauncherItem& old_item) = 0;
+
+ // Invoked when launcher status is changed.
+ virtual void LauncherStatusChanged() = 0;
+
+ protected:
+ virtual ~LauncherModelObserver() {}
+};
+
+} // namespace ash
+
+#endif // ASH_LAUNCHER_LAUNCHER_MODEL_OBSERVER_H_
diff --git a/chromium/ash/launcher/launcher_model_unittest.cc b/chromium/ash/launcher/launcher_model_unittest.cc
new file mode 100644
index 00000000000..c91a040b265
--- /dev/null
+++ b/chromium/ash/launcher/launcher_model_unittest.cc
@@ -0,0 +1,278 @@
+// Copyright (c) 2012 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.
+
+#include "ash/launcher/launcher_model.h"
+
+#include <set>
+#include <string>
+
+#include "ash/launcher/launcher_model_observer.h"
+#include "base/strings/stringprintf.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace ash {
+
+namespace {
+
+// LauncherModelObserver implementation that tracks what message are invoked.
+class TestLauncherModelObserver : public LauncherModelObserver {
+ public:
+ TestLauncherModelObserver()
+ : added_count_(0),
+ removed_count_(0),
+ changed_count_(0),
+ moved_count_(0) {
+ }
+
+ // Returns a string description of the changes that have occurred since this
+ // was last invoked. Resets state to initial state.
+ std::string StateStringAndClear() {
+ std::string result;
+ AddToResult("added=%d", added_count_, &result);
+ AddToResult("removed=%d", removed_count_, &result);
+ AddToResult("changed=%d", changed_count_, &result);
+ AddToResult("moved=%d", moved_count_, &result);
+ added_count_ = removed_count_ = changed_count_ = moved_count_ = 0;
+ return result;
+ }
+
+ // LauncherModelObserver overrides:
+ virtual void LauncherItemAdded(int index) OVERRIDE {
+ added_count_++;
+ }
+ virtual void LauncherItemRemoved(int index, LauncherID id) OVERRIDE {
+ removed_count_++;
+ }
+ virtual void LauncherItemChanged(int index,
+ const LauncherItem& old_item) OVERRIDE {
+ changed_count_++;
+ }
+ virtual void LauncherItemMoved(int start_index, int target_index) OVERRIDE {
+ moved_count_++;
+ }
+ virtual void LauncherStatusChanged() OVERRIDE {
+ }
+
+ private:
+ void AddToResult(const std::string& format, int count, std::string* result) {
+ if (!count)
+ return;
+ if (!result->empty())
+ *result += " ";
+ *result += base::StringPrintf(format.c_str(), count);
+ }
+
+ int added_count_;
+ int removed_count_;
+ int changed_count_;
+ int moved_count_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestLauncherModelObserver);
+};
+
+} // namespace
+
+TEST(LauncherModel, BasicAssertions) {
+ TestLauncherModelObserver observer;
+ LauncherModel model;
+
+ // Model is initially populated with item.
+ EXPECT_EQ(1, model.item_count());
+
+ // Add an item.
+ model.AddObserver(&observer);
+ LauncherItem item;
+ int index = model.Add(item);
+ EXPECT_EQ(2, model.item_count());
+ EXPECT_EQ("added=1", observer.StateStringAndClear());
+
+ // Verifies all the items get unique ids.
+ std::set<LauncherID> ids;
+ for (int i = 0; i < model.item_count(); ++i)
+ ids.insert(model.items()[i].id);
+ EXPECT_EQ(model.item_count(), static_cast<int>(ids.size()));
+
+ // Change a tabbed image.
+ LauncherID original_id = model.items()[index].id;
+ model.Set(index, LauncherItem());
+ EXPECT_EQ(original_id, model.items()[index].id);
+ EXPECT_EQ("changed=1", observer.StateStringAndClear());
+ EXPECT_EQ(TYPE_TABBED, model.items()[index].type);
+
+ // Remove the item.
+ model.RemoveItemAt(index);
+ EXPECT_EQ(1, model.item_count());
+ EXPECT_EQ("removed=1", observer.StateStringAndClear());
+
+ // Add an app item.
+ item.type = TYPE_APP_SHORTCUT;
+ index = model.Add(item);
+ observer.StateStringAndClear();
+
+ // Change everything.
+ model.Set(index, item);
+ EXPECT_EQ("changed=1", observer.StateStringAndClear());
+ EXPECT_EQ(TYPE_APP_SHORTCUT, model.items()[index].type);
+
+ // Add another item.
+ item.type = TYPE_APP_SHORTCUT;
+ model.Add(item);
+ observer.StateStringAndClear();
+
+ // Move the third to the second.
+ model.Move(2, 1);
+ EXPECT_EQ("moved=1", observer.StateStringAndClear());
+
+ // And back.
+ model.Move(1, 2);
+ EXPECT_EQ("moved=1", observer.StateStringAndClear());
+}
+
+// Assertions around where items are added.
+TEST(LauncherModel, AddIndices) {
+ TestLauncherModelObserver observer;
+ LauncherModel model;
+
+ // Model is initially populated with one item.
+ EXPECT_EQ(1, model.item_count());
+
+ // Insert browser short cut at index 0.
+ LauncherItem browser_shortcut;
+ browser_shortcut.type = TYPE_BROWSER_SHORTCUT;
+ int browser_shortcut_index = model.Add(browser_shortcut);
+ EXPECT_EQ(0, browser_shortcut_index);
+
+ // Tabbed items should be after browser shortcut.
+ LauncherItem item;
+ int tabbed_index1 = model.Add(item);
+ EXPECT_EQ(1, tabbed_index1);
+
+ // Add another tabbed item, it should follow first.
+ int tabbed_index2 = model.Add(item);
+ EXPECT_EQ(2, tabbed_index2);
+
+ // APP_SHORTCUT's priority is higher than TABBED but same as
+ // BROWSER_SHORTCUT. So APP_SHORTCUT is located after BROWSER_SHORCUT.
+ item.type = TYPE_APP_SHORTCUT;
+ int app_shortcut_index1 = model.Add(item);
+ EXPECT_EQ(1, app_shortcut_index1);
+
+ item.type = TYPE_APP_SHORTCUT;
+ int app_shortcut_index2 = model.Add(item);
+ EXPECT_EQ(2, app_shortcut_index2);
+
+ // Check that AddAt() figures out the correct indexes for app shortcuts.
+ // APP_SHORTCUT and BROWSER_SHORTCUT has the same weight.
+ // So APP_SHORTCUT is located at index 0. And, BROWSER_SHORTCUT is located at
+ // index 1.
+ item.type = TYPE_APP_SHORTCUT;
+ int app_shortcut_index3 = model.AddAt(0, item);
+ EXPECT_EQ(0, app_shortcut_index3);
+
+ item.type = TYPE_APP_SHORTCUT;
+ int app_shortcut_index4 = model.AddAt(5, item);
+ EXPECT_EQ(4, app_shortcut_index4);
+
+ item.type = TYPE_APP_SHORTCUT;
+ int app_shortcut_index5 = model.AddAt(2, item);
+ EXPECT_EQ(2, app_shortcut_index5);
+
+ // Before there are any panels, no icons should be right aligned.
+ EXPECT_EQ(model.item_count(), model.FirstPanelIndex());
+
+ // Check that AddAt() figures out the correct indexes for tabs and panels.
+ item.type = TYPE_TABBED;
+ int tabbed_index3 = model.AddAt(2, item);
+ EXPECT_EQ(6, tabbed_index3);
+
+ item.type = TYPE_APP_PANEL;
+ int app_panel_index1 = model.AddAt(2, item);
+ EXPECT_EQ(10, app_panel_index1);
+
+ item.type = TYPE_TABBED;
+ int tabbed_index4 = model.AddAt(11, item);
+ EXPECT_EQ(9, tabbed_index4);
+
+ item.type = TYPE_APP_PANEL;
+ int app_panel_index2 = model.AddAt(12, item);
+ EXPECT_EQ(12, app_panel_index2);
+
+ item.type = TYPE_TABBED;
+ int tabbed_index5 = model.AddAt(7, item);
+ EXPECT_EQ(7, tabbed_index5);
+
+ item.type = TYPE_APP_PANEL;
+ int app_panel_index3 = model.AddAt(13, item);
+ EXPECT_EQ(13, app_panel_index3);
+
+ // Right aligned index should be the first app panel index.
+ EXPECT_EQ(12, model.FirstPanelIndex());
+
+ EXPECT_EQ(TYPE_BROWSER_SHORTCUT, model.items()[1].type);
+ EXPECT_EQ(TYPE_APP_LIST, model.items()[model.FirstPanelIndex() - 1].type);
+}
+
+// Assertions around id generation and usage.
+TEST(LauncherModel, LauncherIDTests) {
+ TestLauncherModelObserver observer;
+ LauncherModel model;
+
+ EXPECT_EQ(1, model.item_count());
+
+ // Get the next to use ID counter.
+ LauncherID id = model.next_id();
+
+ // Calling this function multiple times does not change the returned ID.
+ EXPECT_EQ(model.next_id(), id);
+
+ // Check that when we reserve a value it will be the previously retrieved ID,
+ // but it will not change the item count and retrieving the next ID should
+ // produce something new.
+ EXPECT_EQ(model.reserve_external_id(), id);
+ EXPECT_EQ(1, model.item_count());
+ LauncherID id2 = model.next_id();
+ EXPECT_NE(id2, id);
+
+ // Adding another item to the list should also produce a new ID.
+ LauncherItem item;
+ item.type = TYPE_TABBED;
+ model.Add(item);
+ EXPECT_NE(model.next_id(), id2);
+}
+
+// This verifies that converting an existing item into a lower weight category
+// (e.g. shortcut to running but not pinned app) will move it to the proper
+// location. See crbug.com/248769.
+TEST(LauncherModel, CorrectMoveItemsWhenStateChange) {
+ LauncherModel model;
+
+ // The app list should be the last item in the list.
+ EXPECT_EQ(1, model.item_count());
+
+ // The first item is the browser.
+ LauncherItem browser_shortcut;
+ browser_shortcut.type = TYPE_BROWSER_SHORTCUT;
+ int browser_shortcut_index = model.Add(browser_shortcut);
+ EXPECT_EQ(0, browser_shortcut_index);
+
+ // Add three shortcuts. They should all be moved between the two.
+ LauncherItem item;
+ item.type = TYPE_APP_SHORTCUT;
+ int app1_index = model.Add(item);
+ EXPECT_EQ(1, app1_index);
+ int app2_index = model.Add(item);
+ EXPECT_EQ(2, app2_index);
+ int app3_index = model.Add(item);
+ EXPECT_EQ(3, app3_index);
+
+ // Now change the type of the second item and make sure that it is moving
+ // behind the shortcuts.
+ item.type = TYPE_PLATFORM_APP;
+ model.Set(app2_index, item);
+
+ // The item should have moved in front of the app launcher.
+ EXPECT_EQ(TYPE_PLATFORM_APP, model.items()[3].type);
+}
+
+} // namespace ash
diff --git a/chromium/ash/launcher/launcher_navigator.cc b/chromium/ash/launcher/launcher_navigator.cc
new file mode 100644
index 00000000000..f1745e0002b
--- /dev/null
+++ b/chromium/ash/launcher/launcher_navigator.cc
@@ -0,0 +1,72 @@
+// Copyright (c) 2012 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.
+
+#include "ash/launcher/launcher_navigator.h"
+
+#include "ash/launcher/launcher_model.h"
+
+namespace ash {
+
+namespace {
+
+// Returns true if accelerator processing should skip the launcher item with
+// the specified type.
+bool ShouldSkip(ash::LauncherItemType type) {
+ return type == ash::TYPE_APP_LIST ||
+ type == ash::TYPE_BROWSER_SHORTCUT ||
+ type == ash::TYPE_APP_SHORTCUT ||
+ type == ash::TYPE_WINDOWED_APP;
+}
+
+} // namespace
+
+int GetNextActivatedItemIndex(const LauncherModel& model,
+ CycleDirection direction) {
+ const ash::LauncherItems& items = model.items();
+ int item_count = model.item_count();
+ int current_index = -1;
+ int first_running = -1;
+
+ for (int i = 0; i < item_count; ++i) {
+ const ash::LauncherItem& item = items[i];
+ if (ShouldSkip(item.type))
+ continue;
+
+ if (item.status == ash::STATUS_RUNNING && first_running < 0)
+ first_running = i;
+
+ if (item.status == ash::STATUS_ACTIVE) {
+ current_index = i;
+ break;
+ }
+ }
+
+ // If nothing is active, try to active the first running item.
+ if (current_index < 0) {
+ if (first_running >= 0)
+ return first_running;
+ else
+ return -1;
+ }
+
+ int step = (direction == CYCLE_FORWARD) ? 1 : -1;
+
+ // Find the next item and activate it.
+ for (int i = (current_index + step + item_count) % item_count;
+ i != current_index; i = (i + step + item_count) % item_count) {
+ const ash::LauncherItem& item = items[i];
+ if (ShouldSkip(item.type))
+ continue;
+
+ // Skip already active item.
+ if (item.status == ash::STATUS_ACTIVE)
+ continue;
+
+ return i;
+ }
+
+ return -1;
+}
+
+} // namespace ash
diff --git a/chromium/ash/launcher/launcher_navigator.h b/chromium/ash/launcher/launcher_navigator.h
new file mode 100644
index 00000000000..f3b338f20ed
--- /dev/null
+++ b/chromium/ash/launcher/launcher_navigator.h
@@ -0,0 +1,24 @@
+// Copyright (c) 2012 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 ASH_LAUNCHER_LAUNCHER_NAVIGATOR_H_
+#define ASH_LAUNCHER_LAUNCHER_NAVIGATOR_H_
+
+#include "ash/ash_export.h"
+#include "ash/launcher/launcher_types.h"
+#include "base/basictypes.h"
+
+namespace ash {
+
+class LauncherModel;
+
+// Scans the current launcher item and returns the index of the launcher item
+// which should be activated next for the specified |direction|. Returns -1
+// if fails to find such item.
+ASH_EXPORT int GetNextActivatedItemIndex(const LauncherModel& model,
+ CycleDirection direction);
+
+} // namespace ash
+
+#endif // ASH_LAUNCHER_LAUNCHER_NAVIGATOR_H_
diff --git a/chromium/ash/launcher/launcher_navigator_unittest.cc b/chromium/ash/launcher/launcher_navigator_unittest.cc
new file mode 100644
index 00000000000..60133909a71
--- /dev/null
+++ b/chromium/ash/launcher/launcher_navigator_unittest.cc
@@ -0,0 +1,123 @@
+// Copyright (c) 2012 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.
+
+#include "ash/launcher/launcher_navigator.h"
+
+#include "ash/launcher/launcher.h"
+#include "ash/launcher/launcher_model.h"
+#include "ash/launcher/launcher_types.h"
+#include "base/compiler_specific.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace ash {
+
+namespace {
+
+class LauncherNavigatorTest : public testing::Test {
+ public:
+ LauncherNavigatorTest() {}
+
+ protected:
+ virtual void SetUp() OVERRIDE {
+ model_.reset(new LauncherModel);
+
+ // Initially, applist launcher item is only created.
+ int total_num = model_->item_count();
+ EXPECT_EQ(1, total_num);
+ EXPECT_TRUE(model_->items()[0].type == TYPE_APP_LIST);
+
+ // Add BROWSER_SHORTCUT for test.
+ LauncherItem browser_shortcut;
+ browser_shortcut.type = TYPE_BROWSER_SHORTCUT;
+ model_->Add(browser_shortcut);
+ }
+
+ void SetupMockLauncherModel(LauncherItemType* types,
+ int types_length,
+ int focused_index) {
+ for (int i = 0; i < types_length; ++i) {
+ LauncherItem new_item;
+ new_item.type = types[i];
+ new_item.status =
+ (types[i] == TYPE_TABBED) ? STATUS_RUNNING : STATUS_CLOSED;
+ model_->Add(new_item);
+ }
+
+ // Set the focused item.
+ if (focused_index >= 0) {
+ LauncherItem focused_item =model_->items()[focused_index];
+ if (focused_item.type == TYPE_TABBED) {
+ focused_item.status = STATUS_ACTIVE;
+ model_->Set(focused_index, focused_item);
+ }
+ }
+ }
+
+ const LauncherModel& model() { return *model_.get(); }
+
+ private:
+ scoped_ptr<LauncherModel> model_;
+
+ DISALLOW_COPY_AND_ASSIGN(LauncherNavigatorTest);
+};
+
+} // namespace
+
+TEST_F(LauncherNavigatorTest, BasicCycle) {
+ // An app shortcut and three windows
+ LauncherItemType types[] = {
+ TYPE_APP_SHORTCUT, TYPE_TABBED, TYPE_TABBED, TYPE_TABBED,
+ };
+ // LauncherModel automatically adds BROWSER_SHORTCUT item at the
+ // beginning, so '2' refers the first TYPE_TABBED item.
+ SetupMockLauncherModel(types, arraysize(types), 2);
+
+ EXPECT_EQ(3, GetNextActivatedItemIndex(model(), CYCLE_FORWARD));
+
+ // Fourth one. It will skip the APP_SHORTCUT at the beginning of the list and
+ // the APP_LIST item which is automatically added at the end of items.
+ EXPECT_EQ(4, GetNextActivatedItemIndex(model(), CYCLE_BACKWARD));
+}
+
+TEST_F(LauncherNavigatorTest, WrapToBeginning) {
+ LauncherItemType types[] = {
+ TYPE_APP_SHORTCUT, TYPE_TABBED, TYPE_TABBED, TYPE_TABBED,
+ };
+ SetupMockLauncherModel(types, arraysize(types), 4);
+
+ // Second one. It skips the APP_LIST item at the end of the list,
+ // wraps to the beginning, and skips BROWSER_SHORTCUT and APP_SHORTCUT
+ // at the beginning of the list.
+ EXPECT_EQ(2, GetNextActivatedItemIndex(model(), CYCLE_FORWARD));
+}
+
+TEST_F(LauncherNavigatorTest, Empty) {
+ SetupMockLauncherModel(NULL, 0, -1);
+ EXPECT_EQ(-1, GetNextActivatedItemIndex(model(), CYCLE_FORWARD));
+ EXPECT_EQ(-1, GetNextActivatedItemIndex(model(), CYCLE_BACKWARD));
+}
+
+TEST_F(LauncherNavigatorTest, SingleEntry) {
+ LauncherItemType type = TYPE_TABBED;
+ SetupMockLauncherModel(&type, 1, 1);
+
+ // If there's only one item there and it is already active, there's no item
+ // to be activated next.
+ EXPECT_EQ(-1, GetNextActivatedItemIndex(model(), CYCLE_FORWARD));
+ EXPECT_EQ(-1, GetNextActivatedItemIndex(model(), CYCLE_BACKWARD));
+}
+
+TEST_F(LauncherNavigatorTest, NoActive) {
+ LauncherItemType types[] = {
+ TYPE_TABBED, TYPE_TABBED,
+ };
+ // Special case: no items are 'STATUS_ACTIVE'.
+ SetupMockLauncherModel(types, arraysize(types), -1);
+
+ // If there are no active status, pick the first running item as a fallback.
+ EXPECT_EQ(1, GetNextActivatedItemIndex(model(), CYCLE_FORWARD));
+ EXPECT_EQ(1, GetNextActivatedItemIndex(model(), CYCLE_BACKWARD));
+}
+
+} // namespace ash
diff --git a/chromium/ash/launcher/launcher_tooltip_manager.cc b/chromium/ash/launcher/launcher_tooltip_manager.cc
new file mode 100644
index 00000000000..2d92e0f5e3b
--- /dev/null
+++ b/chromium/ash/launcher/launcher_tooltip_manager.cc
@@ -0,0 +1,376 @@
+// Copyright (c) 2012 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.
+
+#include "ash/launcher/launcher_tooltip_manager.h"
+
+#include "ash/launcher/launcher_view.h"
+#include "ash/shelf/shelf_layout_manager.h"
+#include "ash/shell.h"
+#include "ash/shell_window_ids.h"
+#include "ash/wm/window_animations.h"
+#include "base/bind.h"
+#include "base/message_loop/message_loop.h"
+#include "base/time/time.h"
+#include "base/timer/timer.h"
+#include "ui/aura/root_window.h"
+#include "ui/aura/window.h"
+#include "ui/base/events/event.h"
+#include "ui/base/events/event_constants.h"
+#include "ui/gfx/insets.h"
+#include "ui/views/bubble/bubble_delegate.h"
+#include "ui/views/bubble/bubble_frame_view.h"
+#include "ui/views/controls/label.h"
+#include "ui/views/layout/fill_layout.h"
+#include "ui/views/widget/widget.h"
+
+namespace ash {
+namespace internal {
+namespace {
+const int kTooltipTopBottomMargin = 3;
+const int kTooltipLeftRightMargin = 10;
+const int kTooltipAppearanceDelay = 200; // msec
+const int kTooltipMinHeight = 29 - 2 * kTooltipTopBottomMargin;
+const SkColor kTooltipTextColor = SkColorSetRGB(0x22, 0x22, 0x22);
+
+// The maximum width of the tooltip bubble. Borrowed the value from
+// ash/tooltip/tooltip_controller.cc
+const int kTooltipMaxWidth = 250;
+
+// The offset for the tooltip bubble - making sure that the bubble is flush
+// with the shelf. The offset includes the arrow size in pixels as well as
+// the activation bar and other spacing elements.
+const int kArrowOffsetLeftRight = 11;
+const int kArrowOffsetTopBottom = 7;
+
+} // namespace
+
+// The implementation of tooltip of the launcher.
+class LauncherTooltipManager::LauncherTooltipBubble
+ : public views::BubbleDelegateView {
+ public:
+ LauncherTooltipBubble(views::View* anchor,
+ views::BubbleBorder::Arrow arrow,
+ LauncherTooltipManager* host);
+
+ void SetText(const base::string16& text);
+ void Close();
+
+ private:
+ // views::WidgetDelegate overrides:
+ virtual void WindowClosing() OVERRIDE;
+
+ // views::View overrides:
+ virtual gfx::Size GetPreferredSize() OVERRIDE;
+
+ LauncherTooltipManager* host_;
+ views::Label* label_;
+
+ DISALLOW_COPY_AND_ASSIGN(LauncherTooltipBubble);
+};
+
+LauncherTooltipManager::LauncherTooltipBubble::LauncherTooltipBubble(
+ views::View* anchor,
+ views::BubbleBorder::Arrow arrow,
+ LauncherTooltipManager* host)
+ : views::BubbleDelegateView(anchor, arrow),
+ host_(host) {
+ // Make sure that the bubble follows the animation of the shelf.
+ set_move_with_anchor(true);
+ gfx::Insets insets = gfx::Insets(kArrowOffsetTopBottom,
+ kArrowOffsetLeftRight,
+ kArrowOffsetTopBottom,
+ kArrowOffsetLeftRight);
+ // Launcher items can have an asymmetrical border for spacing reasons.
+ // Adjust anchor location for this.
+ if (anchor->border())
+ insets += anchor->border()->GetInsets();
+
+ set_anchor_view_insets(insets);
+ set_close_on_esc(false);
+ set_close_on_deactivate(false);
+ set_use_focusless(true);
+ set_accept_events(false);
+ set_margins(gfx::Insets(kTooltipTopBottomMargin, kTooltipLeftRightMargin,
+ kTooltipTopBottomMargin, kTooltipLeftRightMargin));
+ set_shadow(views::BubbleBorder::SMALL_SHADOW);
+ SetLayoutManager(new views::FillLayout());
+ // The anchor may not have the widget in tests.
+ if (anchor->GetWidget() && anchor->GetWidget()->GetNativeView()) {
+ aura::RootWindow* root_window =
+ anchor->GetWidget()->GetNativeView()->GetRootWindow();
+ set_parent_window(ash::Shell::GetInstance()->GetContainer(
+ root_window, ash::internal::kShellWindowId_SettingBubbleContainer));
+ }
+ label_ = new views::Label;
+ label_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
+ label_->SetEnabledColor(kTooltipTextColor);
+ AddChildView(label_);
+ views::BubbleDelegateView::CreateBubble(this);
+}
+
+void LauncherTooltipManager::LauncherTooltipBubble::SetText(
+ const base::string16& text) {
+ label_->SetText(text);
+ SizeToContents();
+}
+
+void LauncherTooltipManager::LauncherTooltipBubble::Close() {
+ if (GetWidget()) {
+ host_ = NULL;
+ GetWidget()->Close();
+ }
+}
+
+void LauncherTooltipManager::LauncherTooltipBubble::WindowClosing() {
+ views::BubbleDelegateView::WindowClosing();
+ if (host_)
+ host_->OnBubbleClosed(this);
+}
+
+gfx::Size LauncherTooltipManager::LauncherTooltipBubble::GetPreferredSize() {
+ gfx::Size pref_size = views::BubbleDelegateView::GetPreferredSize();
+ if (pref_size.height() < kTooltipMinHeight)
+ pref_size.set_height(kTooltipMinHeight);
+ if (pref_size.width() > kTooltipMaxWidth)
+ pref_size.set_width(kTooltipMaxWidth);
+ return pref_size;
+}
+
+LauncherTooltipManager::LauncherTooltipManager(
+ ShelfLayoutManager* shelf_layout_manager,
+ LauncherView* launcher_view)
+ : view_(NULL),
+ widget_(NULL),
+ anchor_(NULL),
+ shelf_layout_manager_(shelf_layout_manager),
+ launcher_view_(launcher_view),
+ weak_factory_(this) {
+ if (shelf_layout_manager)
+ shelf_layout_manager->AddObserver(this);
+ if (Shell::HasInstance())
+ Shell::GetInstance()->AddPreTargetHandler(this);
+}
+
+LauncherTooltipManager::~LauncherTooltipManager() {
+ CancelHidingAnimation();
+ Close();
+ if (shelf_layout_manager_)
+ shelf_layout_manager_->RemoveObserver(this);
+ if (Shell::HasInstance())
+ Shell::GetInstance()->RemovePreTargetHandler(this);
+}
+
+void LauncherTooltipManager::ShowDelayed(views::View* anchor,
+ const base::string16& text) {
+ if (view_) {
+ if (timer_.get() && timer_->IsRunning()) {
+ return;
+ } else {
+ CancelHidingAnimation();
+ Close();
+ }
+ }
+
+ if (shelf_layout_manager_ && !shelf_layout_manager_->IsVisible())
+ return;
+
+ CreateBubble(anchor, text);
+ ResetTimer();
+}
+
+void LauncherTooltipManager::ShowImmediately(views::View* anchor,
+ const base::string16& text) {
+ if (view_) {
+ if (timer_.get() && timer_->IsRunning())
+ StopTimer();
+ CancelHidingAnimation();
+ Close();
+ }
+
+ if (shelf_layout_manager_ && !shelf_layout_manager_->IsVisible())
+ return;
+
+ CreateBubble(anchor, text);
+ ShowInternal();
+}
+
+void LauncherTooltipManager::Close() {
+ StopTimer();
+ if (view_) {
+ view_->Close();
+ view_ = NULL;
+ widget_ = NULL;
+ }
+}
+
+void LauncherTooltipManager::OnBubbleClosed(views::BubbleDelegateView* view) {
+ if (view == view_) {
+ view_ = NULL;
+ widget_ = NULL;
+ }
+}
+
+void LauncherTooltipManager::UpdateArrow() {
+ if (view_) {
+ CancelHidingAnimation();
+ Close();
+ ShowImmediately(anchor_, text_);
+ }
+}
+
+void LauncherTooltipManager::ResetTimer() {
+ if (timer_.get() && timer_->IsRunning()) {
+ timer_->Reset();
+ return;
+ }
+
+ // We don't start the timer if the shelf isn't visible.
+ if (shelf_layout_manager_ && !shelf_layout_manager_->IsVisible())
+ return;
+
+ CreateTimer(kTooltipAppearanceDelay);
+}
+
+void LauncherTooltipManager::StopTimer() {
+ timer_.reset();
+}
+
+bool LauncherTooltipManager::IsVisible() {
+ if (timer_.get() && timer_->IsRunning())
+ return false;
+
+ return widget_ && widget_->IsVisible();
+}
+
+void LauncherTooltipManager::CreateZeroDelayTimerForTest() {
+ CreateTimer(0);
+}
+
+void LauncherTooltipManager::OnMouseEvent(ui::MouseEvent* event) {
+ DCHECK(event);
+ DCHECK(event->target());
+ if (!widget_ || !widget_->IsVisible())
+ return;
+
+ DCHECK(view_);
+ DCHECK(launcher_view_);
+
+ aura::Window* target = static_cast<aura::Window*>(event->target());
+ if (widget_->GetNativeWindow()->GetRootWindow() != target->GetRootWindow()) {
+ CloseSoon();
+ return;
+ }
+
+ gfx::Point location_in_launcher_view = event->location();
+ aura::Window::ConvertPointToTarget(
+ target, launcher_view_->GetWidget()->GetNativeWindow(),
+ &location_in_launcher_view);
+
+ if (launcher_view_->ShouldHideTooltip(location_in_launcher_view)) {
+ // Because this mouse event may arrive to |view_|, here we just schedule
+ // the closing event rather than directly calling Close().
+ CloseSoon();
+ }
+}
+
+void LauncherTooltipManager::OnTouchEvent(ui::TouchEvent* event) {
+ aura::Window* target = static_cast<aura::Window*>(event->target());
+ if (widget_ && widget_->IsVisible() && widget_->GetNativeWindow() != target)
+ Close();
+}
+
+void LauncherTooltipManager::OnGestureEvent(ui::GestureEvent* event) {
+ if (widget_ && widget_->IsVisible()) {
+ // Because this mouse event may arrive to |view_|, here we just schedule
+ // the closing event rather than directly calling Close().
+ CloseSoon();
+ }
+}
+
+void LauncherTooltipManager::OnCancelMode(ui::CancelModeEvent* event) {
+ Close();
+}
+
+void LauncherTooltipManager::WillDeleteShelf() {
+ shelf_layout_manager_ = NULL;
+}
+
+void LauncherTooltipManager::WillChangeVisibilityState(
+ ShelfVisibilityState new_state) {
+ if (new_state == SHELF_HIDDEN) {
+ StopTimer();
+ Close();
+ }
+}
+
+void LauncherTooltipManager::OnAutoHideStateChanged(
+ ShelfAutoHideState new_state) {
+ if (new_state == SHELF_AUTO_HIDE_HIDDEN) {
+ StopTimer();
+ // AutoHide state change happens during an event filter, so immediate close
+ // may cause a crash in the HandleMouseEvent() after the filter. So we just
+ // schedule the Close here.
+ CloseSoon();
+ }
+}
+
+void LauncherTooltipManager::CancelHidingAnimation() {
+ if (!widget_ || !widget_->GetNativeView())
+ return;
+
+ gfx::NativeView native_view = widget_->GetNativeView();
+ views::corewm::SetWindowVisibilityAnimationTransition(
+ native_view, views::corewm::ANIMATE_NONE);
+}
+
+void LauncherTooltipManager::CloseSoon() {
+ base::MessageLoopForUI::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&LauncherTooltipManager::Close, weak_factory_.GetWeakPtr()));
+}
+
+void LauncherTooltipManager::ShowInternal() {
+ if (view_)
+ view_->GetWidget()->Show();
+
+ timer_.reset();
+}
+
+void LauncherTooltipManager::CreateBubble(views::View* anchor,
+ const base::string16& text) {
+ DCHECK(!view_);
+
+ anchor_ = anchor;
+ text_ = text;
+ views::BubbleBorder::Arrow arrow =
+ shelf_layout_manager_->SelectValueForShelfAlignment(
+ views::BubbleBorder::BOTTOM_CENTER,
+ views::BubbleBorder::LEFT_CENTER,
+ views::BubbleBorder::RIGHT_CENTER,
+ views::BubbleBorder::TOP_CENTER);
+
+ view_ = new LauncherTooltipBubble(anchor, arrow, this);
+ widget_ = view_->GetWidget();
+ view_->SetText(text_);
+
+ gfx::NativeView native_view = widget_->GetNativeView();
+ views::corewm::SetWindowVisibilityAnimationType(
+ native_view, views::corewm::WINDOW_VISIBILITY_ANIMATION_TYPE_VERTICAL);
+ views::corewm::SetWindowVisibilityAnimationTransition(
+ native_view, views::corewm::ANIMATE_HIDE);
+}
+
+void LauncherTooltipManager::CreateTimer(int delay_in_ms) {
+ base::OneShotTimer<LauncherTooltipManager>* new_timer =
+ new base::OneShotTimer<LauncherTooltipManager>();
+ new_timer->Start(
+ FROM_HERE,
+ base::TimeDelta::FromMilliseconds(delay_in_ms),
+ this,
+ &LauncherTooltipManager::ShowInternal);
+ timer_.reset(new_timer);
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/launcher/launcher_tooltip_manager.h b/chromium/ash/launcher/launcher_tooltip_manager.h
new file mode 100644
index 00000000000..27d3554f207
--- /dev/null
+++ b/chromium/ash/launcher/launcher_tooltip_manager.h
@@ -0,0 +1,121 @@
+// Copyright (c) 2012 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 ASH_LAUNCHER_LAUNCHER_TOOLTIP_MANAGER_H_
+#define ASH_LAUNCHER_LAUNCHER_TOOLTIP_MANAGER_H_
+
+#include "ash/ash_export.h"
+#include "ash/shelf/shelf_layout_manager_observer.h"
+#include "ash/shelf/shelf_types.h"
+#include "base/basictypes.h"
+#include "base/memory/weak_ptr.h"
+#include "base/strings/string16.h"
+#include "ui/base/events/event_handler.h"
+#include "ui/gfx/rect.h"
+#include "ui/views/bubble/bubble_border.h"
+#include "ui/views/bubble/bubble_delegate.h"
+
+namespace base {
+class Timer;
+}
+
+namespace views {
+class BubbleDelegateView;
+class Label;
+}
+
+namespace ash {
+namespace test {
+class LauncherTooltipManagerTest;
+class LauncherViewTest;
+}
+
+namespace internal {
+class LauncherView;
+class ShelfLayoutManager;
+
+// LauncherTooltipManager manages the tooltip balloon poping up on launcher
+// items.
+class ASH_EXPORT LauncherTooltipManager : public ui::EventHandler,
+ public ShelfLayoutManagerObserver {
+ public:
+ LauncherTooltipManager(ShelfLayoutManager* shelf_layout_manager,
+ LauncherView* launcher_view);
+ virtual ~LauncherTooltipManager();
+
+ ShelfLayoutManager* shelf_layout_manager() {
+ return shelf_layout_manager_;
+ }
+
+ // Called when the bubble is closed.
+ void OnBubbleClosed(views::BubbleDelegateView* view);
+
+ // Shows the tooltip after a delay. It also has the appearing animation.
+ void ShowDelayed(views::View* anchor, const base::string16& text);
+
+ // Shows the tooltip immediately. It omits the appearing animation.
+ void ShowImmediately(views::View* anchor, const base::string16& text);
+
+ // Closes the tooltip.
+ void Close();
+
+ // Changes the arrow location of the tooltip in case that the launcher
+ // arrangement has changed.
+ void UpdateArrow();
+
+ // Resets the timer for the delayed showing |view_|. If the timer isn't
+ // running, it starts a new timer.
+ void ResetTimer();
+
+ // Stops the timer for the delayed showing |view_|.
+ void StopTimer();
+
+ // Returns true if the tooltip is currently visible.
+ bool IsVisible();
+
+ // Create an instant timer for test purposes.
+ void CreateZeroDelayTimerForTest();
+
+protected:
+ // ui::EventHandler overrides:
+ virtual void OnMouseEvent(ui::MouseEvent* event) OVERRIDE;
+ virtual void OnTouchEvent(ui::TouchEvent* event) OVERRIDE;
+ virtual void OnGestureEvent(ui::GestureEvent* event) OVERRIDE;
+ virtual void OnCancelMode(ui::CancelModeEvent* event) OVERRIDE;
+
+ // ShelfLayoutManagerObserver overrides:
+ virtual void WillDeleteShelf() OVERRIDE;
+ virtual void WillChangeVisibilityState(
+ ShelfVisibilityState new_state) OVERRIDE;
+ virtual void OnAutoHideStateChanged(ShelfAutoHideState new_state) OVERRIDE;
+
+ private:
+ class LauncherTooltipBubble;
+ friend class test::LauncherViewTest;
+ friend class test::LauncherTooltipManagerTest;
+
+ void CancelHidingAnimation();
+ void CloseSoon();
+ void ShowInternal();
+ void CreateBubble(views::View* anchor, const base::string16& text);
+ void CreateTimer(int delay_in_ms);
+
+ LauncherTooltipBubble* view_;
+ views::Widget* widget_;
+ views::View* anchor_;
+ base::string16 text_;
+ scoped_ptr<base::Timer> timer_;
+
+ ShelfLayoutManager* shelf_layout_manager_;
+ LauncherView* launcher_view_;
+
+ base::WeakPtrFactory<LauncherTooltipManager> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(LauncherTooltipManager);
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_LAUNCHER_LAUNCHER_TOOLTIP_MANAGER_H_
diff --git a/chromium/ash/launcher/launcher_tooltip_manager_unittest.cc b/chromium/ash/launcher/launcher_tooltip_manager_unittest.cc
new file mode 100644
index 00000000000..5df58a6de33
--- /dev/null
+++ b/chromium/ash/launcher/launcher_tooltip_manager_unittest.cc
@@ -0,0 +1,249 @@
+// Copyright (c) 2012 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.
+
+#include "ash/launcher/launcher_tooltip_manager.h"
+
+#include "ash/root_window_controller.h"
+#include "ash/shelf/shelf_layout_manager.h"
+#include "ash/shelf/shelf_widget.h"
+#include "ash/shell.h"
+#include "ash/shell_window_ids.h"
+#include "ash/test/ash_test_base.h"
+#include "ash/wm/window_util.h"
+#include "base/strings/string16.h"
+#include "base/time/time.h"
+#include "ui/aura/root_window.h"
+#include "ui/base/events/event.h"
+#include "ui/base/events/event_constants.h"
+#include "ui/base/events/event_handler.h"
+#include "ui/base/keycodes/keyboard_codes.h"
+#include "ui/views/widget/widget.h"
+
+namespace {
+
+void SetEventTarget(ui::EventTarget* target,
+ ui::Event* event) {
+ ui::Event::DispatcherApi dispatch_helper(event);
+ dispatch_helper.set_target(target);
+}
+
+}
+
+namespace ash {
+namespace test {
+
+class LauncherTooltipManagerTest : public AshTestBase {
+ public:
+ LauncherTooltipManagerTest() {}
+ virtual ~LauncherTooltipManagerTest() {}
+
+ virtual void SetUp() OVERRIDE {
+ AshTestBase::SetUp();
+ internal::RootWindowController* controller =
+ Shell::GetPrimaryRootWindowController();
+ tooltip_manager_.reset(new internal::LauncherTooltipManager(
+ controller->GetShelfLayoutManager(),
+ controller->shelf()->launcher()->GetLauncherViewForTest()));
+ }
+
+ virtual void TearDown() OVERRIDE {
+ tooltip_manager_.reset();
+ AshTestBase::TearDown();
+ }
+
+ void ShowDelayed() {
+ CreateWidget();
+ tooltip_manager_->ShowDelayed(dummy_anchor_.get(), base::string16());
+ }
+
+ void ShowImmediately() {
+ CreateWidget();
+ tooltip_manager_->ShowImmediately(dummy_anchor_.get(), base::string16());
+ }
+
+ bool TooltipIsVisible() {
+ return tooltip_manager_->IsVisible();
+ }
+
+ bool IsTimerRunning() {
+ return tooltip_manager_->timer_.get() != NULL;
+ }
+
+ ui::EventHandler* GetEventHandler() {
+ return tooltip_manager_.get();
+ }
+
+ views::Widget* GetTooltipWidget() {
+ return tooltip_manager_->widget_;
+ }
+
+ protected:
+ scoped_ptr<views::Widget> widget_;
+ scoped_ptr<views::View> dummy_anchor_;
+ scoped_ptr<internal::LauncherTooltipManager> tooltip_manager_;
+
+ private:
+ void CreateWidget() {
+ dummy_anchor_.reset(new views::View);
+
+ widget_.reset(new views::Widget);
+ views::Widget::InitParams params(
+ views::Widget::InitParams::TYPE_WINDOW_FRAMELESS);
+ params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW;
+ params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
+ params.parent = Shell::GetContainer(
+ Shell::GetPrimaryRootWindow(),
+ ash::internal::kShellWindowId_ShelfContainer);
+
+ widget_->Init(params);
+ widget_->SetContentsView(dummy_anchor_.get());
+ }
+
+ DISALLOW_COPY_AND_ASSIGN(LauncherTooltipManagerTest);
+};
+
+TEST_F(LauncherTooltipManagerTest, ShowingBasics) {
+ // ShowDelayed() should just start the timer instead of showing immediately.
+ ShowDelayed();
+ EXPECT_FALSE(TooltipIsVisible());
+ EXPECT_TRUE(IsTimerRunning());
+
+ ShowImmediately();
+ EXPECT_TRUE(TooltipIsVisible());
+ EXPECT_FALSE(IsTimerRunning());
+}
+
+TEST_F(LauncherTooltipManagerTest, HideWhenShelfIsHidden) {
+ ShowImmediately();
+ ASSERT_TRUE(TooltipIsVisible());
+
+ // Create a full-screen window to hide the shelf.
+ scoped_ptr<views::Widget> widget(new views::Widget);
+ views::Widget::InitParams params(views::Widget::InitParams::TYPE_WINDOW);
+ params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
+ params.context = CurrentContext();
+ widget->Init(params);
+ widget->SetFullscreen(true);
+ widget->Show();
+
+ // Once the shelf is hidden, the tooltip should be invisible.
+ ASSERT_EQ(
+ SHELF_HIDDEN,
+ Shell::GetPrimaryRootWindowController()->
+ GetShelfLayoutManager()->visibility_state());
+ EXPECT_FALSE(TooltipIsVisible());
+
+ // Do not show the view if the shelf is hidden.
+ ShowImmediately();
+ EXPECT_FALSE(TooltipIsVisible());
+
+ // ShowDelayed() doesn't even start the timer for the hidden shelf.
+ ShowDelayed();
+ EXPECT_FALSE(IsTimerRunning());
+}
+
+TEST_F(LauncherTooltipManagerTest, HideWhenShelfIsAutoHide) {
+ // Create a visible window so auto-hide behavior is enforced.
+ views::Widget* dummy = new views::Widget;
+ views::Widget::InitParams params(views::Widget::InitParams::TYPE_WINDOW);
+ params.bounds = gfx::Rect(0, 0, 200, 200);
+ params.context = CurrentContext();
+ dummy->Init(params);
+ dummy->Show();
+
+ ShowImmediately();
+ ASSERT_TRUE(TooltipIsVisible());
+
+ internal::ShelfLayoutManager* shelf =
+ Shell::GetPrimaryRootWindowController()->GetShelfLayoutManager();
+ shelf->SetAutoHideBehavior(SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS);
+ shelf->UpdateAutoHideState();
+ ASSERT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->auto_hide_state());
+
+ // Tooltip visibility change for auto hide may take time.
+ EXPECT_TRUE(TooltipIsVisible());
+ RunAllPendingInMessageLoop();
+ EXPECT_FALSE(TooltipIsVisible());
+
+ // Do not show the view if the shelf is hidden.
+ ShowImmediately();
+ EXPECT_FALSE(TooltipIsVisible());
+
+ // ShowDelayed doesn't even run the timer for the hidden shelf.
+ ShowDelayed();
+ EXPECT_FALSE(IsTimerRunning());
+}
+
+TEST_F(LauncherTooltipManagerTest, ShouldHideForEvents) {
+ ShowImmediately();
+ ASSERT_TRUE(TooltipIsVisible());
+
+ aura::RootWindow* root_window = Shell::GetInstance()->GetPrimaryRootWindow();
+ ui::EventHandler* event_handler = GetEventHandler();
+
+ // Should not hide for key events.
+ ui::KeyEvent key_event(ui::ET_KEY_PRESSED, ui::VKEY_A, ui::EF_NONE, false);
+ SetEventTarget(root_window, &key_event);
+ event_handler->OnKeyEvent(&key_event);
+ EXPECT_FALSE(key_event.handled());
+ EXPECT_TRUE(TooltipIsVisible());
+
+ // Should hide for touch events.
+ ui::TouchEvent touch_event(
+ ui::ET_TOUCH_PRESSED, gfx::Point(), 0, base::TimeDelta());
+ SetEventTarget(root_window, &touch_event);
+ event_handler->OnTouchEvent(&touch_event);
+ EXPECT_FALSE(touch_event.handled());
+ EXPECT_FALSE(TooltipIsVisible());
+
+ // Shouldn't hide if the touch happens on the tooltip.
+ ShowImmediately();
+ views::Widget* tooltip_widget = GetTooltipWidget();
+ SetEventTarget(tooltip_widget->GetNativeWindow(), &touch_event);
+ event_handler->OnTouchEvent(&touch_event);
+ EXPECT_FALSE(touch_event.handled());
+ EXPECT_TRUE(TooltipIsVisible());
+
+ // Should hide for gesture events.
+ ui::GestureEvent gesture_event(
+ ui::ET_GESTURE_BEGIN, 0, 0, ui::EF_NONE,
+ base::TimeDelta::FromMilliseconds(base::Time::Now().ToDoubleT() * 1000),
+ ui::GestureEventDetails(ui::ET_GESTURE_BEGIN, 0.0f, 0.0f), 0);
+ SetEventTarget(tooltip_widget->GetNativeWindow(), &gesture_event);
+ event_handler->OnGestureEvent(&gesture_event);
+ EXPECT_FALSE(gesture_event.handled());
+ RunAllPendingInMessageLoop();
+ EXPECT_FALSE(TooltipIsVisible());
+}
+
+TEST_F(LauncherTooltipManagerTest, HideForMouseEvent) {
+ ShowImmediately();
+ ASSERT_TRUE(TooltipIsVisible());
+
+ aura::RootWindow* root_window = Shell::GetInstance()->GetPrimaryRootWindow();
+ ui::EventHandler* event_handler = GetEventHandler();
+
+ gfx::Rect tooltip_rect = GetTooltipWidget()->GetNativeWindow()->bounds();
+ ASSERT_FALSE(tooltip_rect.IsEmpty());
+
+ // Shouldn't hide if the mouse is in the tooltip.
+ ui::MouseEvent mouse_event(ui::ET_MOUSE_MOVED, tooltip_rect.CenterPoint(),
+ tooltip_rect.CenterPoint(), ui::EF_NONE);
+ ui::LocatedEvent::TestApi test_api(&mouse_event);
+
+ SetEventTarget(root_window, &mouse_event);
+ event_handler->OnMouseEvent(&mouse_event);
+ EXPECT_FALSE(mouse_event.handled());
+ EXPECT_TRUE(TooltipIsVisible());
+
+ // Should hide if the mouse is out of the tooltip.
+ test_api.set_location(tooltip_rect.origin() + gfx::Vector2d(-1, -1));
+ event_handler->OnMouseEvent(&mouse_event);
+ EXPECT_FALSE(mouse_event.handled());
+ RunAllPendingInMessageLoop();
+ EXPECT_FALSE(TooltipIsVisible());
+}
+
+} // namespace test
+} // namespace ash
diff --git a/chromium/ash/launcher/launcher_types.cc b/chromium/ash/launcher/launcher_types.cc
new file mode 100644
index 00000000000..a6080b0b666
--- /dev/null
+++ b/chromium/ash/launcher/launcher_types.cc
@@ -0,0 +1,22 @@
+// Copyright (c) 2012 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.
+
+#include "ash/launcher/launcher_types.h"
+
+namespace ash {
+
+const int kLauncherPreferredSize = 48;
+const int kLauncherBackgroundAlpha = 204;
+
+LauncherItem::LauncherItem()
+ : type(TYPE_TABBED),
+ is_incognito(false),
+ id(0),
+ status(STATUS_CLOSED) {
+}
+
+LauncherItem::~LauncherItem() {
+}
+
+} // namespace ash
diff --git a/chromium/ash/launcher/launcher_types.h b/chromium/ash/launcher/launcher_types.h
new file mode 100644
index 00000000000..f4eed671ede
--- /dev/null
+++ b/chromium/ash/launcher/launcher_types.h
@@ -0,0 +1,91 @@
+// Copyright (c) 2012 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 ASH_LAUNCHER_LAUNCHER_TYPES_H_
+#define ASH_LAUNCHER_LAUNCHER_TYPES_H_
+
+#include <vector>
+
+#include "ash/ash_export.h"
+#include "ui/gfx/image/image_skia.h"
+
+namespace ash {
+
+typedef int LauncherID;
+
+// Height of the Launcher. Hard coded to avoid resizing as items are
+// added/removed.
+ASH_EXPORT extern const int kLauncherPreferredSize;
+
+// Max alpha of the launcher background.
+ASH_EXPORT extern const int kLauncherBackgroundAlpha;
+
+// Type the LauncherItem represents.
+enum LauncherItemType {
+ // Represents a tabbed browser.
+ TYPE_TABBED,
+
+ // Represents a running app panel.
+ TYPE_APP_PANEL,
+
+ // Represents a pinned shortcut to an app.
+ TYPE_APP_SHORTCUT,
+
+ // Toggles visiblity of the app list.
+ TYPE_APP_LIST,
+
+ // The browser shortcut button.
+ TYPE_BROWSER_SHORTCUT,
+
+ // Represents a platform app.
+ TYPE_PLATFORM_APP,
+
+ // Represents a windowed V1 browser app.
+ TYPE_WINDOWED_APP,
+};
+
+// Represents the status of pinned or running app launcher items.
+enum LauncherItemStatus {
+ // A closed LauncherItem, i.e. has no live instance.
+ STATUS_CLOSED,
+ // A LauncherItem that has live instance.
+ STATUS_RUNNING,
+ // An active LauncherItem that has focus.
+ STATUS_ACTIVE,
+ // A LauncherItem that needs user's attention.
+ STATUS_ATTENTION,
+};
+
+struct ASH_EXPORT LauncherItem {
+ LauncherItem();
+ ~LauncherItem();
+
+ LauncherItemType type;
+
+ // Whether it is drawn as an incognito icon or not. Only used if this is
+ // TYPE_TABBED. Note: This cannot be used for identifying incognito windows.
+ bool is_incognito;
+
+ // Image to display in the launcher. If this item is TYPE_TABBED the image is
+ // a favicon image.
+ gfx::ImageSkia image;
+
+ // Assigned by the model when the item is added.
+ LauncherID id;
+
+ // Running status.
+ LauncherItemStatus status;
+};
+
+typedef std::vector<LauncherItem> LauncherItems;
+
+// The direction of the focus cycling.
+enum CycleDirection {
+ CYCLE_FORWARD,
+ CYCLE_BACKWARD
+};
+
+} // namespace ash
+
+#endif // ASH_LAUNCHER_LAUNCHER_TYPES_H_
diff --git a/chromium/ash/launcher/launcher_unittest.cc b/chromium/ash/launcher/launcher_unittest.cc
new file mode 100644
index 00000000000..b1798e3c96c
--- /dev/null
+++ b/chromium/ash/launcher/launcher_unittest.cc
@@ -0,0 +1,119 @@
+// Copyright (c) 2012 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.
+
+#include "ash/launcher/launcher.h"
+#include "ash/launcher/launcher_button.h"
+#include "ash/launcher/launcher_model.h"
+#include "ash/launcher/launcher_view.h"
+
+#include "ash/shelf/shelf_widget.h"
+#include "ash/shell.h"
+#include "ash/test/ash_test_base.h"
+#include "ash/test/launcher_view_test_api.h"
+#include "ash/wm/window_util.h"
+#include "ui/aura/root_window.h"
+#include "ui/gfx/display.h"
+#include "ui/gfx/screen.h"
+#include "ui/views/corewm/corewm_switches.h"
+#include "ui/views/view.h"
+#include "ui/views/widget/widget.h"
+
+#if defined(OS_WIN)
+#include "base/win/windows_version.h"
+#endif
+
+typedef ash::test::AshTestBase LauncherTest;
+using ash::internal::LauncherView;
+using ash::internal::LauncherButton;
+
+namespace ash {
+
+// Confirm that launching a browser gets the appropriate state reflected in
+// its button.
+TEST_F(LauncherTest, OpenBrowser) {
+ Launcher* launcher = Launcher::ForPrimaryDisplay();
+ ASSERT_TRUE(launcher);
+ LauncherView* launcher_view = launcher->GetLauncherViewForTest();
+ test::LauncherViewTestAPI test(launcher_view);
+ LauncherModel* model = launcher_view->model();
+
+ // Initially we have the app list and chrome icon.
+ int button_count = test.GetButtonCount();
+
+ // Add running tab.
+ LauncherItem item;
+ item.type = TYPE_TABBED;
+ item.status = STATUS_RUNNING;
+ int index = model->Add(item);
+ ASSERT_EQ(++button_count, test.GetButtonCount());
+ LauncherButton* button = test.GetButton(index);
+ EXPECT_EQ(LauncherButton::STATE_RUNNING, button->state());
+
+ // Remove it.
+ model->RemoveItemAt(index);
+ ASSERT_EQ(--button_count, test.GetButtonCount());
+}
+
+// Confirm that using the menu will clear the hover attribute. To avoid another
+// browser test we check this here.
+TEST_F(LauncherTest, checkHoverAfterMenu) {
+ Launcher* launcher = Launcher::ForPrimaryDisplay();
+ ASSERT_TRUE(launcher);
+ LauncherView* launcher_view = launcher->GetLauncherViewForTest();
+ test::LauncherViewTestAPI test(launcher_view);
+ LauncherModel* model = launcher_view->model();
+
+ // Initially we have the app list and chrome icon.
+ int button_count = test.GetButtonCount();
+
+ // Add running tab.
+ LauncherItem item;
+ item.type = TYPE_TABBED;
+ item.status = STATUS_RUNNING;
+ int index = model->Add(item);
+ ASSERT_EQ(++button_count, test.GetButtonCount());
+ LauncherButton* button = test.GetButton(index);
+ button->AddState(LauncherButton::STATE_HOVERED);
+ button->ShowContextMenu(gfx::Point(), ui::MENU_SOURCE_MOUSE);
+ EXPECT_FALSE(button->state() & LauncherButton::STATE_HOVERED);
+
+ // Remove it.
+ model->RemoveItemAt(index);
+}
+
+TEST_F(LauncherTest, ShowOverflowBubble) {
+ Launcher* launcher = Launcher::ForPrimaryDisplay();
+ ASSERT_TRUE(launcher);
+
+ LauncherView* launcher_view = launcher->GetLauncherViewForTest();
+ test::LauncherViewTestAPI test(launcher_view);
+
+ LauncherModel* model = launcher_view->model();
+ LauncherID first_item_id = model->next_id();
+
+ // Add tabbed browser until overflow.
+ int items_added = 0;
+ while (!test.IsOverflowButtonVisible()) {
+ LauncherItem item;
+ item.type = TYPE_TABBED;
+ item.status = STATUS_RUNNING;
+ model->Add(item);
+
+ ++items_added;
+ ASSERT_LT(items_added, 10000);
+ }
+
+ // Shows overflow bubble.
+ test.ShowOverflowBubble();
+ EXPECT_TRUE(launcher->IsShowingOverflowBubble());
+
+ // Removes the first item in main launcher view.
+ model->RemoveItemAt(model->ItemIndexByID(first_item_id));
+
+ // Waits for all transitions to finish and there should be no crash.
+ test.RunMessageLoopUntilAnimationsDone();
+ EXPECT_FALSE(launcher->IsShowingOverflowBubble());
+}
+
+} // namespace ash
diff --git a/chromium/ash/launcher/launcher_util.cc b/chromium/ash/launcher/launcher_util.cc
new file mode 100644
index 00000000000..f7ba77f0bfc
--- /dev/null
+++ b/chromium/ash/launcher/launcher_util.cc
@@ -0,0 +1,22 @@
+// Copyright (c) 2012 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.
+
+#include "ash/launcher/launcher_util.h"
+
+#include "ash/launcher/launcher_model.h"
+#include "ash/launcher/launcher_types.h"
+
+namespace ash {
+namespace launcher {
+
+int GetBrowserItemIndex(const LauncherModel& launcher_model) {
+ for (size_t i = 0; i < launcher_model.items().size(); i++) {
+ if (launcher_model.items()[i].type == ash::TYPE_BROWSER_SHORTCUT)
+ return i;
+ }
+ return -1;
+}
+
+} // namespace launcher
+} // namespace ash
diff --git a/chromium/ash/launcher/launcher_util.h b/chromium/ash/launcher/launcher_util.h
new file mode 100644
index 00000000000..89551f064ab
--- /dev/null
+++ b/chromium/ash/launcher/launcher_util.h
@@ -0,0 +1,21 @@
+// Copyright (c) 2012 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 ASH_LAUNCHER_LAUNCHER_UTIL_H_
+#define ASH_LAUNCHER_LAUNCHER_UTIL_H_
+
+#include "ash/ash_export.h"
+
+namespace ash {
+class LauncherModel;
+
+namespace launcher {
+
+// Return the index of the browser item from a given |launcher_model|.
+ASH_EXPORT int GetBrowserItemIndex(const LauncherModel& launcher_model);
+
+} // namespace launcher
+} // namespace ash
+
+#endif // ASH_LAUNCHER_LAUNCHER_UTIL_H_
diff --git a/chromium/ash/launcher/launcher_view.cc b/chromium/ash/launcher/launcher_view.cc
new file mode 100644
index 00000000000..1ec3e9461eb
--- /dev/null
+++ b/chromium/ash/launcher/launcher_view.cc
@@ -0,0 +1,1715 @@
+// Copyright (c) 2012 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.
+
+#include "ash/launcher/launcher_view.h"
+
+#include <algorithm>
+
+#include "ash/ash_constants.h"
+#include "ash/ash_switches.h"
+#include "ash/drag_drop/drag_image_view.h"
+#include "ash/launcher/alternate_app_list_button.h"
+#include "ash/launcher/app_list_button.h"
+#include "ash/launcher/launcher_button.h"
+#include "ash/launcher/launcher_delegate.h"
+#include "ash/launcher/launcher_icon_observer.h"
+#include "ash/launcher/launcher_model.h"
+#include "ash/launcher/launcher_tooltip_manager.h"
+#include "ash/launcher/overflow_bubble.h"
+#include "ash/launcher/overflow_button.h"
+#include "ash/launcher/tabbed_launcher_button.h"
+#include "ash/root_window_controller.h"
+#include "ash/scoped_target_root_window.h"
+#include "ash/shelf/shelf_layout_manager.h"
+#include "ash/shelf/shelf_widget.h"
+#include "ash/shell_delegate.h"
+#include "base/auto_reset.h"
+#include "base/memory/scoped_ptr.h"
+#include "grit/ash_resources.h"
+#include "grit/ash_strings.h"
+#include "ui/aura/client/screen_position_client.h"
+#include "ui/aura/root_window.h"
+#include "ui/aura/window.h"
+#include "ui/base/accessibility/accessible_view_state.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/base/models/simple_menu_model.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "ui/compositor/layer.h"
+#include "ui/compositor/layer_animator.h"
+#include "ui/compositor/scoped_animation_duration_scale_mode.h"
+#include "ui/gfx/canvas.h"
+#include "ui/gfx/point.h"
+#include "ui/views/animation/bounds_animator.h"
+#include "ui/views/border.h"
+#include "ui/views/controls/button/image_button.h"
+#include "ui/views/controls/menu/menu_model_adapter.h"
+#include "ui/views/controls/menu/menu_runner.h"
+#include "ui/views/focus/focus_search.h"
+#include "ui/views/focus_border.h"
+#include "ui/views/view_model.h"
+#include "ui/views/view_model_utils.h"
+#include "ui/views/widget/widget.h"
+
+using ui::Animation;
+using views::View;
+
+namespace ash {
+namespace internal {
+
+// Default amount content is inset on the left edge.
+const int kDefaultLeadingInset = 8;
+
+// Minimum distance before drag starts.
+const int kMinimumDragDistance = 8;
+
+// Size between the buttons.
+const int kButtonSpacing = 4;
+const int kAlternateButtonSpacing = 10;
+
+// Size allocated to for each button.
+const int kButtonSize = 44;
+
+// Additional spacing for the left and right side of icons.
+const int kHorizontalIconSpacing = 2;
+
+// Inset for items which do not have an icon.
+const int kHorizontalNoIconInsetSpacing =
+ kHorizontalIconSpacing + kDefaultLeadingInset;
+
+// The proportion of the launcher space reserved for non-panel icons. Panels
+// may flow into this space but will be put into the overflow bubble if there
+// is contention for the space.
+const float kReservedNonPanelIconProportion = 0.67f;
+
+// This is the command id of the menu item which contains the name of the menu.
+const int kCommandIdOfMenuName = 0;
+
+// The background color of the active item in the list.
+const SkColor kActiveListItemBackgroundColor = SkColorSetRGB(203 , 219, 241);
+
+// The background color of the active & hovered item in the list.
+const SkColor kFocusedActiveListItemBackgroundColor =
+ SkColorSetRGB(193, 211, 236);
+
+// The text color of the caption item in a list.
+const SkColor kCaptionItemForegroundColor = SK_ColorBLACK;
+
+// The maximum allowable length of a menu line of an application menu in pixels.
+const int kMaximumAppMenuItemLength = 350;
+
+namespace {
+
+// The MenuModelAdapter gets slightly changed to adapt the menu appearance to
+// our requirements.
+class LauncherMenuModelAdapter
+ : public views::MenuModelAdapter {
+ public:
+ explicit LauncherMenuModelAdapter(ash::LauncherMenuModel* menu_model);
+
+ // Overriding MenuModelAdapter's MenuDelegate implementation.
+ virtual const gfx::Font* GetLabelFont(int command_id) const OVERRIDE;
+ virtual bool IsCommandEnabled(int id) const OVERRIDE;
+ virtual void GetHorizontalIconMargins(int id,
+ int icon_size,
+ int* left_margin,
+ int* right_margin) const OVERRIDE;
+ virtual bool GetForegroundColor(int command_id,
+ bool is_hovered,
+ SkColor* override_color) const OVERRIDE;
+ virtual bool GetBackgroundColor(int command_id,
+ bool is_hovered,
+ SkColor* override_color) const OVERRIDE;
+ virtual int GetMaxWidthForMenu(views::MenuItemView* menu) OVERRIDE;
+ virtual bool ShouldReserveSpaceForSubmenuIndicator() const OVERRIDE;
+
+ private:
+ ash::LauncherMenuModel* launcher_menu_model_;
+
+ DISALLOW_COPY_AND_ASSIGN(LauncherMenuModelAdapter);
+};
+
+
+LauncherMenuModelAdapter::LauncherMenuModelAdapter(
+ ash::LauncherMenuModel* menu_model)
+ : MenuModelAdapter(menu_model),
+ launcher_menu_model_(menu_model) {}
+
+const gfx::Font* LauncherMenuModelAdapter::GetLabelFont(
+ int command_id) const {
+ if (command_id != kCommandIdOfMenuName)
+ return MenuModelAdapter::GetLabelFont(command_id);
+
+ ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
+ return &rb.GetFont(ui::ResourceBundle::BoldFont);
+}
+
+bool LauncherMenuModelAdapter::IsCommandEnabled(int id) const {
+ return id != kCommandIdOfMenuName;
+}
+
+bool LauncherMenuModelAdapter::GetForegroundColor(
+ int command_id,
+ bool is_hovered,
+ SkColor* override_color) const {
+ if (command_id != kCommandIdOfMenuName)
+ return false;
+
+ *override_color = kCaptionItemForegroundColor;
+ return true;
+}
+
+bool LauncherMenuModelAdapter::GetBackgroundColor(
+ int command_id,
+ bool is_hovered,
+ SkColor* override_color) const {
+ if (!launcher_menu_model_->IsCommandActive(command_id))
+ return false;
+
+ *override_color = is_hovered ? kFocusedActiveListItemBackgroundColor :
+ kActiveListItemBackgroundColor;
+ return true;
+}
+
+void LauncherMenuModelAdapter::GetHorizontalIconMargins(
+ int command_id,
+ int icon_size,
+ int* left_margin,
+ int* right_margin) const {
+ *left_margin = kHorizontalIconSpacing;
+ *right_margin = (command_id != kCommandIdOfMenuName) ?
+ kHorizontalIconSpacing : -(icon_size + kHorizontalNoIconInsetSpacing);
+}
+
+int LauncherMenuModelAdapter::GetMaxWidthForMenu(views::MenuItemView* menu) {
+ return kMaximumAppMenuItemLength;
+}
+
+bool LauncherMenuModelAdapter::ShouldReserveSpaceForSubmenuIndicator() const {
+ return false;
+}
+
+// Custom FocusSearch used to navigate the launcher in the order items are in
+// the ViewModel.
+class LauncherFocusSearch : public views::FocusSearch {
+ public:
+ explicit LauncherFocusSearch(views::ViewModel* view_model)
+ : FocusSearch(NULL, true, true),
+ view_model_(view_model) {}
+ virtual ~LauncherFocusSearch() {}
+
+ // views::FocusSearch overrides:
+ virtual View* FindNextFocusableView(
+ View* starting_view,
+ bool reverse,
+ Direction direction,
+ bool check_starting_view,
+ views::FocusTraversable** focus_traversable,
+ View** focus_traversable_view) OVERRIDE {
+ int index = view_model_->GetIndexOfView(starting_view);
+ if (index == -1)
+ return view_model_->view_at(0);
+
+ if (reverse) {
+ --index;
+ if (index < 0)
+ index = view_model_->view_size() - 1;
+ } else {
+ ++index;
+ if (index >= view_model_->view_size())
+ index = 0;
+ }
+ return view_model_->view_at(index);
+ }
+
+ private:
+ views::ViewModel* view_model_;
+
+ DISALLOW_COPY_AND_ASSIGN(LauncherFocusSearch);
+};
+
+class LauncherButtonFocusBorder : public views::FocusBorder {
+ public:
+ LauncherButtonFocusBorder() {}
+ virtual ~LauncherButtonFocusBorder() {}
+
+ private:
+ // views::FocusBorder overrides:
+ virtual void Paint(const View& view, gfx::Canvas* canvas) const OVERRIDE {
+ gfx::Rect rect(view.GetLocalBounds());
+ rect.Inset(1, 1);
+ canvas->DrawRect(rect, kFocusBorderColor);
+ }
+
+ DISALLOW_COPY_AND_ASSIGN(LauncherButtonFocusBorder);
+};
+
+// AnimationDelegate that deletes a view when done. This is used when a launcher
+// item is removed, which triggers a remove animation. When the animation is
+// done we delete the view.
+class DeleteViewAnimationDelegate
+ : public views::BoundsAnimator::OwnedAnimationDelegate {
+ public:
+ explicit DeleteViewAnimationDelegate(views::View* view) : view_(view) {}
+ virtual ~DeleteViewAnimationDelegate() {}
+
+ private:
+ scoped_ptr<views::View> view_;
+
+ DISALLOW_COPY_AND_ASSIGN(DeleteViewAnimationDelegate);
+};
+
+// AnimationDelegate used when inserting a new item. This steadily increases the
+// opacity of the layer as the animation progress.
+class FadeInAnimationDelegate
+ : public views::BoundsAnimator::OwnedAnimationDelegate {
+ public:
+ explicit FadeInAnimationDelegate(views::View* view) : view_(view) {}
+ virtual ~FadeInAnimationDelegate() {}
+
+ // AnimationDelegate overrides:
+ virtual void AnimationProgressed(const Animation* animation) OVERRIDE {
+ view_->layer()->SetOpacity(animation->GetCurrentValue());
+ view_->layer()->ScheduleDraw();
+ }
+ virtual void AnimationEnded(const Animation* animation) OVERRIDE {
+ view_->layer()->SetOpacity(1.0f);
+ view_->layer()->ScheduleDraw();
+ }
+ virtual void AnimationCanceled(const Animation* animation) OVERRIDE {
+ view_->layer()->SetOpacity(1.0f);
+ view_->layer()->ScheduleDraw();
+ }
+
+ private:
+ views::View* view_;
+
+ DISALLOW_COPY_AND_ASSIGN(FadeInAnimationDelegate);
+};
+
+void ReflectItemStatus(const ash::LauncherItem& item,
+ LauncherButton* button) {
+ switch (item.status) {
+ case STATUS_CLOSED:
+ button->ClearState(LauncherButton::STATE_ACTIVE);
+ button->ClearState(LauncherButton::STATE_RUNNING);
+ button->ClearState(LauncherButton::STATE_ATTENTION);
+ break;
+ case STATUS_RUNNING:
+ button->ClearState(LauncherButton::STATE_ACTIVE);
+ button->AddState(LauncherButton::STATE_RUNNING);
+ button->ClearState(LauncherButton::STATE_ATTENTION);
+ break;
+ case STATUS_ACTIVE:
+ button->AddState(LauncherButton::STATE_ACTIVE);
+ button->ClearState(LauncherButton::STATE_RUNNING);
+ button->ClearState(LauncherButton::STATE_ATTENTION);
+ break;
+ case STATUS_ATTENTION:
+ button->ClearState(LauncherButton::STATE_ACTIVE);
+ button->ClearState(LauncherButton::STATE_RUNNING);
+ button->AddState(LauncherButton::STATE_ATTENTION);
+ break;
+ }
+}
+
+// Get the event location in screen coordinates.
+gfx::Point GetPositionInScreen(const gfx::Point& root_location,
+ views::View* view) {
+ gfx::Point root_location_in_screen = root_location;
+ aura::RootWindow* root_window =
+ view->GetWidget()->GetNativeWindow()->GetRootWindow();
+ aura::client::GetScreenPositionClient(root_window->GetRootWindow())->
+ ConvertPointToScreen(root_window, &root_location_in_screen);
+ return root_location_in_screen;
+}
+
+} // namespace
+
+// AnimationDelegate used when deleting an item. This steadily decreased the
+// opacity of the layer as the animation progress.
+class LauncherView::FadeOutAnimationDelegate
+ : public views::BoundsAnimator::OwnedAnimationDelegate {
+ public:
+ FadeOutAnimationDelegate(LauncherView* host, views::View* view)
+ : launcher_view_(host),
+ view_(view) {}
+ virtual ~FadeOutAnimationDelegate() {}
+
+ // AnimationDelegate overrides:
+ virtual void AnimationProgressed(const Animation* animation) OVERRIDE {
+ view_->layer()->SetOpacity(1 - animation->GetCurrentValue());
+ view_->layer()->ScheduleDraw();
+ }
+ virtual void AnimationEnded(const Animation* animation) OVERRIDE {
+ launcher_view_->OnFadeOutAnimationEnded();
+ }
+ virtual void AnimationCanceled(const Animation* animation) OVERRIDE {
+ }
+
+ private:
+ LauncherView* launcher_view_;
+ scoped_ptr<views::View> view_;
+
+ DISALLOW_COPY_AND_ASSIGN(FadeOutAnimationDelegate);
+};
+
+// AnimationDelegate used to trigger fading an element in. When an item is
+// inserted this delegate is attached to the animation that expands the size of
+// the item. When done it kicks off another animation to fade the item in.
+class LauncherView::StartFadeAnimationDelegate
+ : public views::BoundsAnimator::OwnedAnimationDelegate {
+ public:
+ StartFadeAnimationDelegate(LauncherView* host,
+ views::View* view)
+ : launcher_view_(host),
+ view_(view) {}
+ virtual ~StartFadeAnimationDelegate() {}
+
+ // AnimationDelegate overrides:
+ virtual void AnimationEnded(const Animation* animation) OVERRIDE {
+ launcher_view_->FadeIn(view_);
+ }
+ virtual void AnimationCanceled(const Animation* animation) OVERRIDE {
+ view_->layer()->SetOpacity(1.0f);
+ }
+
+ private:
+ LauncherView* launcher_view_;
+ views::View* view_;
+
+ DISALLOW_COPY_AND_ASSIGN(StartFadeAnimationDelegate);
+};
+
+LauncherView::LauncherView(LauncherModel* model,
+ LauncherDelegate* delegate,
+ ShelfLayoutManager* shelf_layout_manager)
+ : model_(model),
+ delegate_(delegate),
+ view_model_(new views::ViewModel),
+ first_visible_index_(0),
+ last_visible_index_(-1),
+ overflow_button_(NULL),
+ drag_pointer_(NONE),
+ drag_view_(NULL),
+ drag_offset_(0),
+ start_drag_index_(-1),
+ context_menu_id_(0),
+ leading_inset_(kDefaultLeadingInset),
+ cancelling_drag_model_changed_(false),
+ last_hidden_index_(0),
+ closing_event_time_(base::TimeDelta()),
+ got_deleted_(NULL),
+ drag_and_drop_item_pinned_(false),
+ drag_and_drop_launcher_id_(0) {
+ DCHECK(model_);
+ bounds_animator_.reset(new views::BoundsAnimator(this));
+ bounds_animator_->AddObserver(this);
+ set_context_menu_controller(this);
+ focus_search_.reset(new LauncherFocusSearch(view_model_.get()));
+ tooltip_.reset(new LauncherTooltipManager(
+ shelf_layout_manager, this));
+}
+
+LauncherView::~LauncherView() {
+ bounds_animator_->RemoveObserver(this);
+ model_->RemoveObserver(this);
+ // If we are inside the MenuRunner, we need to know if we were getting
+ // deleted while it was running.
+ if (got_deleted_)
+ *got_deleted_ = true;
+}
+
+void LauncherView::Init() {
+ model_->AddObserver(this);
+
+ const LauncherItems& items(model_->items());
+ for (LauncherItems::const_iterator i = items.begin(); i != items.end(); ++i) {
+ views::View* child = CreateViewForItem(*i);
+ child->SetPaintToLayer(true);
+ view_model_->Add(child, static_cast<int>(i - items.begin()));
+ AddChildView(child);
+ }
+ LauncherStatusChanged();
+ overflow_button_ = new OverflowButton(this);
+ overflow_button_->set_context_menu_controller(this);
+ ConfigureChildView(overflow_button_);
+ AddChildView(overflow_button_);
+ UpdateFirstButtonPadding();
+
+ // We'll layout when our bounds change.
+}
+
+void LauncherView::OnShelfAlignmentChanged() {
+ UpdateFirstButtonPadding();
+ overflow_button_->OnShelfAlignmentChanged();
+ LayoutToIdealBounds();
+ for (int i=0; i < view_model_->view_size(); ++i) {
+ // TODO: remove when AppIcon is a Launcher Button.
+ if (TYPE_APP_LIST == model_->items()[i].type &&
+ !ash::switches::UseAlternateShelfLayout()) {
+ ShelfLayoutManager* shelf = tooltip_->shelf_layout_manager();
+ static_cast<AppListButton*>(view_model_->view_at(i))->SetImageAlignment(
+ shelf->SelectValueForShelfAlignment(
+ views::ImageButton::ALIGN_CENTER,
+ views::ImageButton::ALIGN_LEFT,
+ views::ImageButton::ALIGN_RIGHT,
+ views::ImageButton::ALIGN_CENTER),
+ shelf->SelectValueForShelfAlignment(
+ views::ImageButton::ALIGN_TOP,
+ views::ImageButton::ALIGN_MIDDLE,
+ views::ImageButton::ALIGN_MIDDLE,
+ views::ImageButton::ALIGN_BOTTOM));
+ }
+ if (i >= first_visible_index_ && i <= last_visible_index_)
+ view_model_->view_at(i)->Layout();
+ }
+ tooltip_->UpdateArrow();
+ if (overflow_bubble_)
+ overflow_bubble_->Hide();
+}
+
+void LauncherView::SchedulePaintForAllButtons() {
+ for (int i = 0; i < view_model_->view_size(); ++i) {
+ if (i >= first_visible_index_ && i <= last_visible_index_)
+ view_model_->view_at(i)->SchedulePaint();
+ }
+ if (overflow_button_ && overflow_button_->visible())
+ overflow_button_->SchedulePaint();
+}
+
+gfx::Rect LauncherView::GetIdealBoundsOfItemIcon(LauncherID id) {
+ int index = model_->ItemIndexByID(id);
+ if (index == -1 || (index > last_visible_index_ &&
+ index < model_->FirstPanelIndex()))
+ return gfx::Rect();
+ const gfx::Rect& ideal_bounds(view_model_->ideal_bounds(index));
+ DCHECK_NE(TYPE_APP_LIST, model_->items()[index].type);
+ LauncherButton* button =
+ static_cast<LauncherButton*>(view_model_->view_at(index));
+ gfx::Rect icon_bounds = button->GetIconBounds();
+ return gfx::Rect(GetMirroredXWithWidthInView(
+ ideal_bounds.x() + icon_bounds.x(), icon_bounds.width()),
+ ideal_bounds.y() + icon_bounds.y(),
+ icon_bounds.width(),
+ icon_bounds.height());
+}
+
+void LauncherView::UpdatePanelIconPosition(LauncherID id,
+ const gfx::Point& midpoint) {
+ int current_index = model_->ItemIndexByID(id);
+ int first_panel_index = model_->FirstPanelIndex();
+ if (current_index < first_panel_index)
+ return;
+
+ gfx::Point midpoint_in_view(GetMirroredXInView(midpoint.x()),
+ midpoint.y());
+ ShelfLayoutManager* shelf = tooltip_->shelf_layout_manager();
+ int target_index = current_index;
+ while (target_index > first_panel_index &&
+ shelf->PrimaryAxisValue(view_model_->ideal_bounds(target_index).x(),
+ view_model_->ideal_bounds(target_index).y()) >
+ shelf->PrimaryAxisValue(midpoint_in_view.x(), midpoint_in_view.y())) {
+ --target_index;
+ }
+ while (target_index < view_model_->view_size() - 1 &&
+ shelf->PrimaryAxisValue(
+ view_model_->ideal_bounds(target_index).right(),
+ view_model_->ideal_bounds(target_index).bottom()) <
+ shelf->PrimaryAxisValue(midpoint_in_view.x(), midpoint_in_view.y())) {
+ ++target_index;
+ }
+ if (current_index != target_index)
+ model_->Move(current_index, target_index);
+}
+
+bool LauncherView::IsShowingMenu() const {
+#if !defined(OS_MACOSX)
+ return (launcher_menu_runner_.get() &&
+ launcher_menu_runner_->IsRunning());
+#endif
+ return false;
+}
+
+bool LauncherView::IsShowingOverflowBubble() const {
+ return overflow_bubble_.get() && overflow_bubble_->IsShowing();
+}
+
+views::View* LauncherView::GetAppListButtonView() const {
+ for (int i = 0; i < model_->item_count(); ++i) {
+ if (model_->items()[i].type == TYPE_APP_LIST)
+ return view_model_->view_at(i);
+ }
+
+ NOTREACHED() << "Applist button not found";
+ return NULL;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// LauncherView, FocusTraversable implementation:
+
+views::FocusSearch* LauncherView::GetFocusSearch() {
+ return focus_search_.get();
+}
+
+views::FocusTraversable* LauncherView::GetFocusTraversableParent() {
+ return parent()->GetFocusTraversable();
+}
+
+View* LauncherView::GetFocusTraversableParentView() {
+ return this;
+}
+
+void LauncherView::CreateDragIconProxy(
+ const gfx::Point& location_in_screen_coordinates,
+ const gfx::ImageSkia& icon,
+ views::View* replaced_view,
+ const gfx::Vector2d& cursor_offset_from_center,
+ float scale_factor) {
+ drag_replaced_view_ = replaced_view;
+ drag_image_.reset(new ash::internal::DragImageView(
+ drag_replaced_view_->GetWidget()->GetNativeWindow()->GetRootWindow()));
+ drag_image_->SetImage(icon);
+ gfx::Size size = drag_image_->GetPreferredSize();
+ size.set_width(size.width() * scale_factor);
+ size.set_height(size.height() * scale_factor);
+ drag_image_offset_ = gfx::Vector2d(size.width() / 2, size.height() / 2) +
+ cursor_offset_from_center;
+ gfx::Rect drag_image_bounds(
+ GetPositionInScreen(location_in_screen_coordinates,
+ drag_replaced_view_) - drag_image_offset_, size);
+ drag_image_->SetBoundsInScreen(drag_image_bounds);
+ drag_image_->SetWidgetVisible(true);
+}
+
+void LauncherView::UpdateDragIconProxy(
+ const gfx::Point& location_in_screen_coordinates) {
+ drag_image_->SetScreenPosition(
+ GetPositionInScreen(location_in_screen_coordinates,
+ drag_replaced_view_) - drag_image_offset_);
+}
+
+void LauncherView::DestroyDragIconProxy() {
+ drag_image_.reset();
+ drag_image_offset_ = gfx::Vector2d(0, 0);
+}
+
+bool LauncherView::StartDrag(const std::string& app_id,
+ const gfx::Point& location_in_screen_coordinates) {
+ // Bail if an operation is already going on - or the cursor is not inside.
+ // This could happen if mouse / touch operations overlap.
+ if (drag_and_drop_launcher_id_ ||
+ !GetBoundsInScreen().Contains(location_in_screen_coordinates))
+ return false;
+
+ // If the AppsGridView (which was dispatching this event) was opened by our
+ // button, LauncherView dragging operations are locked and we have to unlock.
+ CancelDrag(-1);
+ drag_and_drop_item_pinned_ = false;
+ drag_and_drop_app_id_ = app_id;
+ drag_and_drop_launcher_id_ =
+ delegate_->GetLauncherIDForAppID(drag_and_drop_app_id_);
+ // Check if the application is known and pinned - if not, we have to pin it so
+ // that we can re-arrange the launcher order accordingly. Note that items have
+ // to be pinned to give them the same (order) possibilities as a shortcut.
+ if (!drag_and_drop_launcher_id_ || !delegate_->IsAppPinned(app_id)) {
+ delegate_->PinAppWithID(app_id);
+ drag_and_drop_launcher_id_ =
+ delegate_->GetLauncherIDForAppID(drag_and_drop_app_id_);
+ if (!drag_and_drop_launcher_id_)
+ return false;
+ drag_and_drop_item_pinned_ = true;
+ }
+ views::View* drag_and_drop_view = view_model_->view_at(
+ model_->ItemIndexByID(drag_and_drop_launcher_id_));
+ DCHECK(drag_and_drop_view);
+
+ // Since there is already an icon presented by the caller, we hide this item
+ // for now. That has to be done by reducing the size since the visibility will
+ // change once a regrouping animation is performed.
+ pre_drag_and_drop_size_ = drag_and_drop_view->size();
+ drag_and_drop_view->SetSize(gfx::Size());
+
+ // First we have to center the mouse cursor over the item.
+ gfx::Point pt = drag_and_drop_view->GetBoundsInScreen().CenterPoint();
+ views::View::ConvertPointFromScreen(drag_and_drop_view, &pt);
+ ui::MouseEvent event(ui::ET_MOUSE_PRESSED,
+ pt, location_in_screen_coordinates, 0);
+ PointerPressedOnButton(
+ drag_and_drop_view, LauncherButtonHost::DRAG_AND_DROP, event);
+
+ // Drag the item where it really belongs.
+ Drag(location_in_screen_coordinates);
+ return true;
+}
+
+bool LauncherView::Drag(const gfx::Point& location_in_screen_coordinates) {
+ if (!drag_and_drop_launcher_id_ ||
+ !GetBoundsInScreen().Contains(location_in_screen_coordinates))
+ return false;
+
+ gfx::Point pt = location_in_screen_coordinates;
+ views::View* drag_and_drop_view = view_model_->view_at(
+ model_->ItemIndexByID(drag_and_drop_launcher_id_));
+ views::View::ConvertPointFromScreen(drag_and_drop_view, &pt);
+
+ ui::MouseEvent event(ui::ET_MOUSE_DRAGGED, pt, gfx::Point(), 0);
+ PointerDraggedOnButton(
+ drag_and_drop_view, LauncherButtonHost::DRAG_AND_DROP, event);
+ return true;
+}
+
+void LauncherView::EndDrag(bool cancel) {
+ if (!drag_and_drop_launcher_id_)
+ return;
+
+ views::View* drag_and_drop_view = view_model_->view_at(
+ model_->ItemIndexByID(drag_and_drop_launcher_id_));
+ PointerReleasedOnButton(
+ drag_and_drop_view, LauncherButtonHost::DRAG_AND_DROP, cancel);
+
+ // Either destroy the temporarily created item - or - make the item visible.
+ if (drag_and_drop_item_pinned_ && cancel)
+ delegate_->UnpinAppsWithID(drag_and_drop_app_id_);
+ else if (drag_and_drop_view)
+ drag_and_drop_view->SetSize(pre_drag_and_drop_size_);
+
+ drag_and_drop_launcher_id_ = 0;
+}
+
+void LauncherView::LayoutToIdealBounds() {
+ IdealBounds ideal_bounds;
+ CalculateIdealBounds(&ideal_bounds);
+
+ if (bounds_animator_->IsAnimating())
+ AnimateToIdealBounds();
+ else
+ views::ViewModelUtils::SetViewBoundsToIdealBounds(*view_model_);
+
+ overflow_button_->SetBoundsRect(ideal_bounds.overflow_bounds);
+}
+
+void LauncherView::CalculateIdealBounds(IdealBounds* bounds) {
+ ShelfLayoutManager* shelf = tooltip_->shelf_layout_manager();
+
+ int available_size = shelf->PrimaryAxisValue(width(), height());
+ DCHECK(model_->item_count() == view_model_->view_size());
+ if (!available_size)
+ return;
+
+ int first_panel_index = model_->FirstPanelIndex();
+ int last_button_index = first_panel_index - 1;
+
+ // Initial x,y values account both leading_inset in primary
+ // coordinate and secondary coordinate based on the dynamic edge of the
+ // launcher (eg top edge on bottom-aligned launcher).
+ int inset = ash::switches::UseAlternateShelfLayout() ? 0 : leading_inset();
+ int x = shelf->SelectValueForShelfAlignment(inset, 0, 0, inset);
+ int y = shelf->SelectValueForShelfAlignment(0, inset, inset, 0);
+
+ int button_size = ash::switches::UseAlternateShelfLayout() ?
+ kButtonSize : kLauncherPreferredSize;
+ int button_spacing = ash::switches::UseAlternateShelfLayout() ?
+ kAlternateButtonSpacing : kButtonSpacing;
+
+ int w = shelf->PrimaryAxisValue(button_size, width());
+ int h = shelf->PrimaryAxisValue(height(), button_size);
+ for (int i = 0; i < view_model_->view_size(); ++i) {
+ if (i < first_visible_index_) {
+ view_model_->set_ideal_bounds(i, gfx::Rect(x, y, 0, 0));
+ continue;
+ }
+
+ view_model_->set_ideal_bounds(i, gfx::Rect(x, y, w, h));
+ if (i != last_button_index) {
+ x = shelf->PrimaryAxisValue(x + w + button_spacing, x);
+ y = shelf->PrimaryAxisValue(y, y + h + button_spacing);
+ }
+ }
+
+ if (is_overflow_mode()) {
+ DCHECK_LT(last_visible_index_, view_model_->view_size());
+ for (int i = 0; i < view_model_->view_size(); ++i) {
+ bool visible = i >= first_visible_index_ &&
+ i <= last_visible_index_;
+ if (!ash::switches::UseAlternateShelfLayout())
+ visible &= i != last_button_index;
+ view_model_->view_at(i)->SetVisible(visible);
+ }
+ return;
+ }
+
+ // To address Fitt's law, we make the first launcher button include the
+ // leading inset (if there is one).
+ if (!ash::switches::UseAlternateShelfLayout()) {
+ if (view_model_->view_size() > 0) {
+ view_model_->set_ideal_bounds(0, gfx::Rect(gfx::Size(
+ shelf->PrimaryAxisValue(inset + w, w),
+ shelf->PrimaryAxisValue(h, inset + h))));
+ }
+ }
+
+ // Right aligned icons.
+ int end_position = available_size - button_spacing;
+ x = shelf->PrimaryAxisValue(end_position, 0);
+ y = shelf->PrimaryAxisValue(0, end_position);
+ for (int i = view_model_->view_size() - 1;
+ i >= first_panel_index; --i) {
+ x = shelf->PrimaryAxisValue(x - w - button_spacing, x);
+ y = shelf->PrimaryAxisValue(y, y - h - button_spacing);
+ view_model_->set_ideal_bounds(i, gfx::Rect(x, y, w, h));
+ end_position = shelf->PrimaryAxisValue(x, y);
+ }
+
+ // Icons on the left / top are guaranteed up to kLeftIconProportion of
+ // the available space.
+ int last_icon_position = shelf->PrimaryAxisValue(
+ view_model_->ideal_bounds(last_button_index).right(),
+ view_model_->ideal_bounds(last_button_index).bottom())
+ + button_size + inset;
+ if (!ash::switches::UseAlternateShelfLayout())
+ last_icon_position += button_size;
+ int reserved_icon_space = available_size * kReservedNonPanelIconProportion;
+ if (last_icon_position < reserved_icon_space)
+ end_position = last_icon_position;
+ else
+ end_position = std::max(end_position, reserved_icon_space);
+
+ bounds->overflow_bounds.set_size(gfx::Size(
+ shelf->PrimaryAxisValue(w, width()),
+ shelf->PrimaryAxisValue(height(), h)));
+ if (ash::switches::UseAlternateShelfLayout())
+ last_visible_index_ = DetermineLastVisibleIndex(
+ end_position - button_size);
+ else
+ last_visible_index_ = DetermineLastVisibleIndex(
+ end_position - inset - 2 * button_size);
+ last_hidden_index_ = DetermineFirstVisiblePanelIndex(end_position) - 1;
+ bool show_overflow =
+ ((ash::switches::UseAlternateShelfLayout() ? 0 : 1) +
+ last_visible_index_ < last_button_index ||
+ last_hidden_index_ >= first_panel_index);
+
+ // Create Space for the overflow button
+ if (show_overflow && ash::switches::UseAlternateShelfLayout() &&
+ last_visible_index_ > 0)
+ --last_visible_index_;
+ for (int i = 0; i < view_model_->view_size(); ++i) {
+ bool visible = i <= last_visible_index_ || i > last_hidden_index_;
+ // Always show the app list.
+ if (!ash::switches::UseAlternateShelfLayout())
+ visible |= (i == last_button_index);
+ view_model_->view_at(i)->SetVisible(visible);
+ }
+
+ overflow_button_->SetVisible(show_overflow);
+ if (show_overflow) {
+ DCHECK_NE(0, view_model_->view_size());
+ if (last_visible_index_ == -1) {
+ x = shelf->SelectValueForShelfAlignment(inset, 0, 0, inset);
+ y = shelf->SelectValueForShelfAlignment(0, inset, inset, 0);
+ } else if (last_visible_index_ == last_button_index) {
+ x = view_model_->ideal_bounds(last_visible_index_).x();
+ y = view_model_->ideal_bounds(last_visible_index_).y();
+ } else {
+ x = shelf->PrimaryAxisValue(
+ view_model_->ideal_bounds(last_visible_index_).right(),
+ view_model_->ideal_bounds(last_visible_index_).x());
+ y = shelf->PrimaryAxisValue(
+ view_model_->ideal_bounds(last_visible_index_).y(),
+ view_model_->ideal_bounds(last_visible_index_).bottom());
+ }
+ // Set all hidden panel icon positions to be on the overflow button.
+ for (int i = first_panel_index; i <= last_hidden_index_; ++i)
+ view_model_->set_ideal_bounds(i, gfx::Rect(x, y, w, h));
+
+ bounds->overflow_bounds.set_x(x);
+ bounds->overflow_bounds.set_y(y);
+ if (!ash::switches::UseAlternateShelfLayout()) {
+ // Position app list after overflow button.
+ gfx::Rect app_list_bounds = view_model_->ideal_bounds(last_button_index);
+
+ x = shelf->PrimaryAxisValue(x + w + button_spacing, x);
+ y = shelf->PrimaryAxisValue(y, y + h + button_spacing);
+ app_list_bounds.set_x(x);
+ app_list_bounds.set_y(y);
+ view_model_->set_ideal_bounds(last_button_index, app_list_bounds);
+ }
+ if (overflow_bubble_.get() && overflow_bubble_->IsShowing())
+ UpdateOverflowRange(overflow_bubble_->launcher_view());
+ } else {
+ if (overflow_bubble_)
+ overflow_bubble_->Hide();
+ }
+}
+
+int LauncherView::DetermineLastVisibleIndex(int max_value) const {
+ ShelfLayoutManager* shelf = tooltip_->shelf_layout_manager();
+
+ int index = model_->FirstPanelIndex() - 1;
+ while (index >= 0 &&
+ shelf->PrimaryAxisValue(
+ view_model_->ideal_bounds(index).right(),
+ view_model_->ideal_bounds(index).bottom()) > max_value) {
+ index--;
+ }
+ return index;
+}
+
+int LauncherView::DetermineFirstVisiblePanelIndex(int min_value) const {
+ ShelfLayoutManager* shelf = tooltip_->shelf_layout_manager();
+
+ int index = model_->FirstPanelIndex();
+ while (index < view_model_->view_size() &&
+ shelf->PrimaryAxisValue(
+ view_model_->ideal_bounds(index).right(),
+ view_model_->ideal_bounds(index).bottom()) < min_value) {
+ ++index;
+ }
+ return index;
+}
+
+void LauncherView::AddIconObserver(LauncherIconObserver* observer) {
+ observers_.AddObserver(observer);
+}
+
+void LauncherView::RemoveIconObserver(LauncherIconObserver* observer) {
+ observers_.RemoveObserver(observer);
+}
+
+void LauncherView::AnimateToIdealBounds() {
+ IdealBounds ideal_bounds;
+ CalculateIdealBounds(&ideal_bounds);
+ for (int i = 0; i < view_model_->view_size(); ++i) {
+ View* view = view_model_->view_at(i);
+ bounds_animator_->AnimateViewTo(view, view_model_->ideal_bounds(i));
+ // Now that the item animation starts, we have to make sure that the
+ // padding of the first gets properly transferred to the new first item.
+ if (i && view->border())
+ view->set_border(NULL);
+ else if (!i && !view->border())
+ UpdateFirstButtonPadding();
+ }
+ overflow_button_->SetBoundsRect(ideal_bounds.overflow_bounds);
+}
+
+views::View* LauncherView::CreateViewForItem(const LauncherItem& item) {
+ views::View* view = NULL;
+ switch (item.type) {
+ case TYPE_TABBED: {
+ TabbedLauncherButton* button =
+ TabbedLauncherButton::Create(
+ this,
+ this,
+ tooltip_->shelf_layout_manager(),
+ item.is_incognito ?
+ TabbedLauncherButton::STATE_INCOGNITO :
+ TabbedLauncherButton::STATE_NOT_INCOGNITO);
+ button->SetTabImage(item.image);
+ ReflectItemStatus(item, button);
+ view = button;
+ break;
+ }
+
+ case TYPE_BROWSER_SHORTCUT:
+ case TYPE_APP_SHORTCUT:
+ case TYPE_WINDOWED_APP:
+ case TYPE_PLATFORM_APP:
+ case TYPE_APP_PANEL: {
+ LauncherButton* button = LauncherButton::Create(
+ this, this, tooltip_->shelf_layout_manager());
+ button->SetImage(item.image);
+ ReflectItemStatus(item, button);
+ view = button;
+ break;
+ }
+
+ case TYPE_APP_LIST: {
+ if (ash::switches::UseAlternateShelfLayout()) {
+ view = new AlternateAppListButton(this, this,
+ tooltip_->shelf_layout_manager()->shelf_widget());
+ } else {
+ // TODO(dave): turn this into a LauncherButton too.
+ AppListButton* button = new AppListButton(this, this);
+ ShelfLayoutManager* shelf = tooltip_->shelf_layout_manager();
+ button->SetImageAlignment(
+ shelf->SelectValueForShelfAlignment(
+ views::ImageButton::ALIGN_CENTER,
+ views::ImageButton::ALIGN_LEFT,
+ views::ImageButton::ALIGN_RIGHT,
+ views::ImageButton::ALIGN_CENTER),
+ shelf->SelectValueForShelfAlignment(
+ views::ImageButton::ALIGN_TOP,
+ views::ImageButton::ALIGN_MIDDLE,
+ views::ImageButton::ALIGN_MIDDLE,
+ views::ImageButton::ALIGN_BOTTOM));
+ view = button;
+ }
+ break;
+ }
+
+ default:
+ break;
+ }
+ view->set_context_menu_controller(this);
+ view->set_focus_border(new LauncherButtonFocusBorder);
+
+ DCHECK(view);
+ ConfigureChildView(view);
+ return view;
+}
+
+void LauncherView::FadeIn(views::View* view) {
+ view->SetVisible(true);
+ view->layer()->SetOpacity(0);
+ AnimateToIdealBounds();
+ bounds_animator_->SetAnimationDelegate(
+ view, new FadeInAnimationDelegate(view), true);
+}
+
+void LauncherView::PrepareForDrag(Pointer pointer,
+ const ui::LocatedEvent& event) {
+ DCHECK(!dragging());
+ DCHECK(drag_view_);
+ drag_pointer_ = pointer;
+ start_drag_index_ = view_model_->GetIndexOfView(drag_view_);
+
+ // If the item is no longer draggable, bail out.
+ if (start_drag_index_ == -1 ||
+ !delegate_->IsDraggable(model_->items()[start_drag_index_])) {
+ CancelDrag(-1);
+ return;
+ }
+
+ // Move the view to the front so that it appears on top of other views.
+ ReorderChildView(drag_view_, -1);
+ bounds_animator_->StopAnimatingView(drag_view_);
+}
+
+void LauncherView::ContinueDrag(const ui::LocatedEvent& event) {
+ ShelfLayoutManager* shelf = tooltip_->shelf_layout_manager();
+
+ // TODO: I don't think this works correctly with RTL.
+ gfx::Point drag_point(event.location());
+ views::View::ConvertPointToTarget(drag_view_, this, &drag_point);
+ int current_index = view_model_->GetIndexOfView(drag_view_);
+ DCHECK_NE(-1, current_index);
+
+ // If the item is no longer draggable, bail out.
+ if (current_index == -1 ||
+ !delegate_->IsDraggable(model_->items()[current_index])) {
+ CancelDrag(-1);
+ return;
+ }
+
+ // Constrain the location to the range of valid indices for the type.
+ std::pair<int, int> indices(GetDragRange(current_index));
+ int first_drag_index = indices.first;
+ int last_drag_index = indices.second;
+ // If the last index isn't valid, we're overflowing. Constrain to the app list
+ // (which is the last visible item).
+ if (first_drag_index < model_->FirstPanelIndex() &&
+ last_drag_index > last_visible_index_)
+ last_drag_index = last_visible_index_;
+ int x = 0, y = 0;
+ if (shelf->IsHorizontalAlignment()) {
+ x = std::max(view_model_->ideal_bounds(indices.first).x(),
+ drag_point.x() - drag_offset_);
+ x = std::min(view_model_->ideal_bounds(last_drag_index).right() -
+ view_model_->ideal_bounds(current_index).width(),
+ x);
+ if (drag_view_->x() == x)
+ return;
+ drag_view_->SetX(x);
+ } else {
+ y = std::max(view_model_->ideal_bounds(indices.first).y(),
+ drag_point.y() - drag_offset_);
+ y = std::min(view_model_->ideal_bounds(last_drag_index).bottom() -
+ view_model_->ideal_bounds(current_index).height(),
+ y);
+ if (drag_view_->y() == y)
+ return;
+ drag_view_->SetY(y);
+ }
+
+ int target_index =
+ views::ViewModelUtils::DetermineMoveIndex(
+ *view_model_, drag_view_,
+ shelf->IsHorizontalAlignment() ?
+ views::ViewModelUtils::HORIZONTAL :
+ views::ViewModelUtils::VERTICAL,
+ x, y);
+ target_index =
+ std::min(indices.second, std::max(target_index, indices.first));
+ if (target_index == current_index)
+ return;
+
+ // Change the model, the LauncherItemMoved() callback will handle the
+ // |view_model_| update.
+ model_->Move(current_index, target_index);
+ bounds_animator_->StopAnimatingView(drag_view_);
+}
+
+bool LauncherView::SameDragType(LauncherItemType typea,
+ LauncherItemType typeb) const {
+ switch (typea) {
+ case TYPE_TABBED:
+ case TYPE_PLATFORM_APP:
+ return (typeb == TYPE_TABBED || typeb == TYPE_PLATFORM_APP);
+ case TYPE_APP_SHORTCUT:
+ case TYPE_BROWSER_SHORTCUT:
+ return (typeb == TYPE_APP_SHORTCUT || typeb == TYPE_BROWSER_SHORTCUT);
+ case TYPE_WINDOWED_APP:
+ case TYPE_APP_LIST:
+ case TYPE_APP_PANEL:
+ return typeb == typea;
+ }
+ NOTREACHED();
+ return false;
+}
+
+std::pair<int, int> LauncherView::GetDragRange(int index) {
+ int min_index = -1;
+ int max_index = -1;
+ LauncherItemType type = model_->items()[index].type;
+ for (int i = 0; i < model_->item_count(); ++i) {
+ if (SameDragType(model_->items()[i].type, type)) {
+ if (min_index == -1)
+ min_index = i;
+ max_index = i;
+ }
+ }
+ return std::pair<int, int>(min_index, max_index);
+}
+
+void LauncherView::ConfigureChildView(views::View* view) {
+ view->SetPaintToLayer(true);
+ view->layer()->SetFillsBoundsOpaquely(false);
+}
+
+void LauncherView::ToggleOverflowBubble() {
+ if (IsShowingOverflowBubble()) {
+ overflow_bubble_->Hide();
+ return;
+ }
+
+ if (!overflow_bubble_)
+ overflow_bubble_.reset(new OverflowBubble());
+
+ LauncherView* overflow_view = new LauncherView(
+ model_, delegate_, tooltip_->shelf_layout_manager());
+ overflow_view->Init();
+ overflow_view->OnShelfAlignmentChanged();
+ UpdateOverflowRange(overflow_view);
+
+ overflow_bubble_->Show(overflow_button_, overflow_view);
+
+ Shell::GetInstance()->UpdateShelfVisibility();
+}
+
+void LauncherView::UpdateFirstButtonPadding() {
+ if (ash::switches::UseAlternateShelfLayout())
+ return;
+
+ ShelfLayoutManager* shelf = tooltip_->shelf_layout_manager();
+
+ // Creates an empty border for first launcher button to make included leading
+ // inset act as the button's padding. This is only needed on button creation
+ // and when shelf alignment changes.
+ if (view_model_->view_size() > 0) {
+ view_model_->view_at(0)->set_border(views::Border::CreateEmptyBorder(
+ shelf->PrimaryAxisValue(0, leading_inset()),
+ shelf->PrimaryAxisValue(leading_inset(), 0),
+ 0,
+ 0));
+ }
+}
+
+void LauncherView::OnFadeOutAnimationEnded() {
+ AnimateToIdealBounds();
+
+ // If overflow button is visible and there is a valid new last item, fading
+ // the new last item in after sliding animation is finished.
+ if (overflow_button_->visible() && last_visible_index_ >= 0) {
+ views::View* last_visible_view = view_model_->view_at(last_visible_index_);
+ last_visible_view->layer()->SetOpacity(0);
+ bounds_animator_->SetAnimationDelegate(
+ last_visible_view,
+ new LauncherView::StartFadeAnimationDelegate(this, last_visible_view),
+ true);
+ }
+}
+
+void LauncherView::UpdateOverflowRange(LauncherView* overflow_view) {
+ const int first_overflow_index = last_visible_index_ + 1;
+ const int last_overflow_index = last_hidden_index_;
+ DCHECK_LE(first_overflow_index, last_overflow_index);
+ DCHECK_LT(last_overflow_index, view_model_->view_size());
+
+ overflow_view->first_visible_index_ = first_overflow_index;
+ overflow_view->last_visible_index_ = last_overflow_index;
+}
+
+bool LauncherView::ShouldHideTooltip(const gfx::Point& cursor_location) {
+ gfx::Rect active_bounds;
+
+ for (int i = 0; i < child_count(); ++i) {
+ views::View* child = child_at(i);
+ if (child == overflow_button_)
+ continue;
+ if (!ShouldShowTooltipForView(child))
+ continue;
+
+ gfx::Rect child_bounds = child->GetMirroredBounds();
+ active_bounds.Union(child_bounds);
+ }
+
+ return !active_bounds.Contains(cursor_location);
+}
+
+int LauncherView::CancelDrag(int modified_index) {
+ if (!drag_view_)
+ return modified_index;
+ bool was_dragging = dragging();
+ int drag_view_index = view_model_->GetIndexOfView(drag_view_);
+ drag_pointer_ = NONE;
+ drag_view_ = NULL;
+ if (drag_view_index == modified_index) {
+ // The view that was being dragged is being modified. Don't do anything.
+ return modified_index;
+ }
+ if (!was_dragging)
+ return modified_index;
+
+ // Restore previous position, tracking the position of the modified view.
+ bool at_end = modified_index == view_model_->view_size();
+ views::View* modified_view =
+ (modified_index >= 0 && !at_end) ?
+ view_model_->view_at(modified_index) : NULL;
+ model_->Move(drag_view_index, start_drag_index_);
+
+ // If the modified view will be at the end of the list, return the new end of
+ // the list.
+ if (at_end)
+ return view_model_->view_size();
+ return modified_view ? view_model_->GetIndexOfView(modified_view) : -1;
+}
+
+gfx::Size LauncherView::GetPreferredSize() {
+ IdealBounds ideal_bounds;
+ CalculateIdealBounds(&ideal_bounds);
+
+ const int app_list_index = view_model_->view_size() - 1;
+ const int last_button_index = is_overflow_mode() ?
+ last_visible_index_ : app_list_index;
+ const gfx::Rect last_button_bounds =
+ last_button_index >= first_visible_index_ ?
+ view_model_->view_at(last_button_index)->bounds() :
+ gfx::Rect(gfx::Size(kLauncherPreferredSize,
+ kLauncherPreferredSize));
+
+ ShelfLayoutManager* shelf = tooltip_->shelf_layout_manager();
+
+ if (shelf->IsHorizontalAlignment()) {
+ return gfx::Size(last_button_bounds.right() + leading_inset(),
+ kLauncherPreferredSize);
+ }
+
+ return gfx::Size(kLauncherPreferredSize,
+ last_button_bounds.bottom() + leading_inset());
+}
+
+void LauncherView::OnBoundsChanged(const gfx::Rect& previous_bounds) {
+ LayoutToIdealBounds();
+ FOR_EACH_OBSERVER(LauncherIconObserver, observers_,
+ OnLauncherIconPositionsChanged());
+
+ if (IsShowingOverflowBubble())
+ overflow_bubble_->Hide();
+}
+
+views::FocusTraversable* LauncherView::GetPaneFocusTraversable() {
+ return this;
+}
+
+void LauncherView::GetAccessibleState(ui::AccessibleViewState* state) {
+ state->role = ui::AccessibilityTypes::ROLE_TOOLBAR;
+ state->name = l10n_util::GetStringUTF16(IDS_ASH_SHELF_ACCESSIBLE_NAME);
+}
+
+void LauncherView::OnGestureEvent(ui::GestureEvent* event) {
+ if (gesture_handler_.ProcessGestureEvent(*event))
+ event->StopPropagation();
+}
+
+void LauncherView::LauncherItemAdded(int model_index) {
+ {
+ base::AutoReset<bool> cancelling_drag(
+ &cancelling_drag_model_changed_, true);
+ model_index = CancelDrag(model_index);
+ }
+ views::View* view = CreateViewForItem(model_->items()[model_index]);
+ AddChildView(view);
+ // Hide the view, it'll be made visible when the animation is done. Using
+ // opacity 0 here to avoid messing with CalculateIdealBounds which touches
+ // the view's visibility.
+ view->layer()->SetOpacity(0);
+ view_model_->Add(view, model_index);
+
+ // Give the button its ideal bounds. That way if we end up animating the
+ // button before this animation completes it doesn't appear at some random
+ // spot (because it was in the middle of animating from 0,0 0x0 to its
+ // target).
+ IdealBounds ideal_bounds;
+ CalculateIdealBounds(&ideal_bounds);
+ view->SetBoundsRect(view_model_->ideal_bounds(model_index));
+
+ // The first animation moves all the views to their target position. |view|
+ // is hidden, so it visually appears as though we are providing space for
+ // it. When done we'll fade the view in.
+ AnimateToIdealBounds();
+ if (model_index <= last_visible_index_ ||
+ model_index >= model_->FirstPanelIndex()) {
+ bounds_animator_->SetAnimationDelegate(
+ view, new StartFadeAnimationDelegate(this, view), true);
+ } else {
+ // Undo the hiding if animation does not run.
+ view->layer()->SetOpacity(1.0f);
+ }
+}
+
+void LauncherView::LauncherItemRemoved(int model_index, LauncherID id) {
+#if !defined(OS_MACOSX)
+ if (id == context_menu_id_)
+ launcher_menu_runner_.reset();
+#endif
+ {
+ base::AutoReset<bool> cancelling_drag(
+ &cancelling_drag_model_changed_, true);
+ model_index = CancelDrag(model_index);
+ }
+ views::View* view = view_model_->view_at(model_index);
+ view_model_->Remove(model_index);
+ // The first animation fades out the view. When done we'll animate the rest of
+ // the views to their target location.
+ bounds_animator_->AnimateViewTo(view, view->bounds());
+ bounds_animator_->SetAnimationDelegate(
+ view, new FadeOutAnimationDelegate(this, view), true);
+
+ // If overflow bubble is visible, sanitize overflow range first and when the
+ // above animation finishes, CalculateIdealBounds will be called to get
+ // correct overflow range. CalculateIdealBounds could hide overflow bubble
+ // and triggers LauncherItemChanged. And since we are still in the middle
+ // of LauncherItemRemoved, LauncherView in overflow bubble is not synced
+ // with LauncherModel and will crash.
+ if (overflow_bubble_ && overflow_bubble_->IsShowing()) {
+ last_hidden_index_ = std::min(last_hidden_index_,
+ view_model_->view_size() - 1);
+ UpdateOverflowRange(overflow_bubble_->launcher_view());
+ }
+}
+
+void LauncherView::LauncherItemChanged(int model_index,
+ const ash::LauncherItem& old_item) {
+ const LauncherItem& item(model_->items()[model_index]);
+ if (old_item.type != item.type) {
+ // Type changed, swap the views.
+ model_index = CancelDrag(model_index);
+ scoped_ptr<views::View> old_view(view_model_->view_at(model_index));
+ bounds_animator_->StopAnimatingView(old_view.get());
+ view_model_->Remove(model_index);
+ views::View* new_view = CreateViewForItem(item);
+ AddChildView(new_view);
+ view_model_->Add(new_view, model_index);
+ new_view->SetBoundsRect(old_view->bounds());
+ return;
+ }
+
+ views::View* view = view_model_->view_at(model_index);
+ switch (item.type) {
+ case TYPE_TABBED: {
+ TabbedLauncherButton* button = static_cast<TabbedLauncherButton*>(view);
+ gfx::Size pref = button->GetPreferredSize();
+ button->SetTabImage(item.image);
+ if (pref != button->GetPreferredSize())
+ AnimateToIdealBounds();
+ else
+ button->SchedulePaint();
+ ReflectItemStatus(item, button);
+ break;
+ }
+ case TYPE_BROWSER_SHORTCUT:
+ // Fallthrough for the new Launcher since it needs to show the activation
+ // change as well.
+ case TYPE_APP_SHORTCUT:
+ case TYPE_WINDOWED_APP:
+ case TYPE_PLATFORM_APP:
+ case TYPE_APP_PANEL: {
+ LauncherButton* button = static_cast<LauncherButton*>(view);
+ ReflectItemStatus(item, button);
+ // The browser shortcut is currently not a "real" item and as such the
+ // the image is bogous as well. We therefore keep the image as is for it.
+ if (item.type != TYPE_BROWSER_SHORTCUT)
+ button->SetImage(item.image);
+ button->SchedulePaint();
+ break;
+ }
+
+ default:
+ break;
+ }
+}
+
+void LauncherView::LauncherItemMoved(int start_index, int target_index) {
+ view_model_->Move(start_index, target_index);
+ // When cancelling a drag due to a launcher item being added, the currently
+ // dragged item is moved back to its initial position. AnimateToIdealBounds
+ // will be called again when the new item is added to the |view_model_| but
+ // at this time the |view_model_| is inconsistent with the |model_|.
+ if (!cancelling_drag_model_changed_)
+ AnimateToIdealBounds();
+}
+
+void LauncherView::LauncherStatusChanged() {
+ if (ash::switches::UseAlternateShelfLayout())
+ return;
+ AppListButton* app_list_button =
+ static_cast<AppListButton*>(GetAppListButtonView());
+ if (model_->status() == LauncherModel::STATUS_LOADING)
+ app_list_button->StartLoadingAnimation();
+ else
+ app_list_button->StopLoadingAnimation();
+}
+
+void LauncherView::PointerPressedOnButton(views::View* view,
+ Pointer pointer,
+ const ui::LocatedEvent& event) {
+ if (drag_view_)
+ return;
+
+ tooltip_->Close();
+ int index = view_model_->GetIndexOfView(view);
+ if (index == -1 ||
+ view_model_->view_size() <= 1 ||
+ !delegate_->IsDraggable(model_->items()[index]))
+ return; // View is being deleted or not draggable, ignore request.
+
+ ShelfLayoutManager* shelf = tooltip_->shelf_layout_manager();
+
+ drag_view_ = view;
+ drag_offset_ = shelf->PrimaryAxisValue(event.x(), event.y());
+}
+
+void LauncherView::PointerDraggedOnButton(views::View* view,
+ Pointer pointer,
+ const ui::LocatedEvent& event) {
+ ShelfLayoutManager* shelf = tooltip_->shelf_layout_manager();
+ if (!dragging() && drag_view_ &&
+ shelf->PrimaryAxisValue(abs(event.x() - drag_offset_),
+ abs(event.y() - drag_offset_)) >=
+ kMinimumDragDistance) {
+ PrepareForDrag(pointer, event);
+ }
+ if (drag_pointer_ == pointer)
+ ContinueDrag(event);
+}
+
+void LauncherView::PointerReleasedOnButton(views::View* view,
+ Pointer pointer,
+ bool canceled) {
+ if (canceled) {
+ CancelDrag(-1);
+ } else if (drag_pointer_ == pointer) {
+ drag_pointer_ = NONE;
+ AnimateToIdealBounds();
+ }
+ // If the drag pointer is NONE, no drag operation is going on and the
+ // drag_view can be released.
+ if (drag_pointer_ == NONE)
+ drag_view_ = NULL;
+}
+
+void LauncherView::MouseMovedOverButton(views::View* view) {
+ if (!ShouldShowTooltipForView(view))
+ return;
+
+ if (!tooltip_->IsVisible())
+ tooltip_->ResetTimer();
+}
+
+void LauncherView::MouseEnteredButton(views::View* view) {
+ if (!ShouldShowTooltipForView(view))
+ return;
+
+ if (tooltip_->IsVisible()) {
+ tooltip_->ShowImmediately(view, GetAccessibleName(view));
+ } else {
+ tooltip_->ShowDelayed(view, GetAccessibleName(view));
+ }
+}
+
+void LauncherView::MouseExitedButton(views::View* view) {
+ if (!tooltip_->IsVisible())
+ tooltip_->StopTimer();
+}
+
+base::string16 LauncherView::GetAccessibleName(const views::View* view) {
+ int view_index = view_model_->GetIndexOfView(view);
+ // May be -1 while in the process of animating closed.
+ if (view_index == -1)
+ return base::string16();
+
+ switch (model_->items()[view_index].type) {
+ case TYPE_TABBED:
+ case TYPE_APP_PANEL:
+ case TYPE_APP_SHORTCUT:
+ case TYPE_WINDOWED_APP:
+ case TYPE_PLATFORM_APP:
+ case TYPE_BROWSER_SHORTCUT:
+ return delegate_->GetTitle(model_->items()[view_index]);
+
+ case TYPE_APP_LIST:
+ return model_->status() == LauncherModel::STATUS_LOADING ?
+ l10n_util::GetStringUTF16(IDS_AURA_APP_LIST_SYNCING_TITLE) :
+ l10n_util::GetStringUTF16(IDS_AURA_APP_LIST_TITLE);
+ }
+ return base::string16();
+}
+
+void LauncherView::ButtonPressed(views::Button* sender,
+ const ui::Event& event) {
+ // Do not handle mouse release during drag.
+ if (dragging())
+ return;
+
+ tooltip_->Close();
+
+ if (sender == overflow_button_) {
+ ToggleOverflowBubble();
+ return;
+ }
+
+ int view_index = view_model_->GetIndexOfView(sender);
+ // May be -1 while in the process of animating closed.
+ if (view_index == -1)
+ return;
+
+ // If the previous menu was closed by the same event as this one, we ignore
+ // the call.
+ if (!IsUsableEvent(event))
+ return;
+
+ {
+ ScopedTargetRootWindow scoped_target(
+ sender->GetWidget()->GetNativeView()->GetRootWindow());
+ // Slow down activation animations if shift key is pressed.
+ scoped_ptr<ui::ScopedAnimationDurationScaleMode> slowing_animations;
+ if (event.IsShiftDown()) {
+ slowing_animations.reset(new ui::ScopedAnimationDurationScaleMode(
+ ui::ScopedAnimationDurationScaleMode::SLOW_DURATION));
+ }
+
+ // Collect usage statistics before we decide what to do with the click.
+ switch (model_->items()[view_index].type) {
+ case TYPE_APP_SHORTCUT:
+ case TYPE_WINDOWED_APP:
+ case TYPE_PLATFORM_APP:
+ case TYPE_BROWSER_SHORTCUT:
+ Shell::GetInstance()->delegate()->RecordUserMetricsAction(
+ UMA_LAUNCHER_CLICK_ON_APP);
+ // Fallthrough
+ case TYPE_TABBED:
+ case TYPE_APP_PANEL:
+ delegate_->ItemSelected(model_->items()[view_index], event);
+ // Don't show the menu when the user creates a new browser using ctrl
+ // click.
+ if (model_->items()[view_index].type != TYPE_BROWSER_SHORTCUT ||
+ !(event.flags() & ui::EF_CONTROL_DOWN))
+ ShowListMenuForView(model_->items()[view_index], sender, event);
+ break;
+
+ case TYPE_APP_LIST:
+ Shell::GetInstance()->delegate()->RecordUserMetricsAction(
+ UMA_LAUNCHER_CLICK_ON_APPLIST_BUTTON);
+ Shell::GetInstance()->ToggleAppList(GetWidget()->GetNativeView());
+ break;
+ }
+ }
+}
+
+bool LauncherView::ShowListMenuForView(const LauncherItem& item,
+ views::View* source,
+ const ui::Event& event) {
+ scoped_ptr<ash::LauncherMenuModel> menu_model;
+ menu_model.reset(delegate_->CreateApplicationMenu(item, event.flags()));
+
+ // Make sure we have a menu and it has at least two items in addition to the
+ // application title and the 3 spacing separators.
+ if (!menu_model.get() || menu_model->GetItemCount() <= 5)
+ return false;
+
+ ShowMenu(scoped_ptr<views::MenuModelAdapter>(
+ new LauncherMenuModelAdapter(menu_model.get())),
+ source,
+ gfx::Point(),
+ false,
+ ui::GetMenuSourceTypeForEvent(event));
+ return true;
+}
+
+void LauncherView::ShowContextMenuForView(views::View* source,
+ const gfx::Point& point,
+ ui:: MenuSourceType source_type) {
+ int view_index = view_model_->GetIndexOfView(source);
+ if (view_index != -1 &&
+ model_->items()[view_index].type == TYPE_APP_LIST) {
+ view_index = -1;
+ }
+
+ tooltip_->Close();
+
+ if (view_index == -1) {
+ Shell::GetInstance()->ShowContextMenu(point, source_type);
+ return;
+ }
+ scoped_ptr<ui::MenuModel> menu_model(delegate_->CreateContextMenu(
+ model_->items()[view_index],
+ source->GetWidget()->GetNativeView()->GetRootWindow()));
+ if (!menu_model)
+ return;
+ base::AutoReset<LauncherID> reseter(
+ &context_menu_id_,
+ view_index == -1 ? 0 : model_->items()[view_index].id);
+
+ ShowMenu(scoped_ptr<views::MenuModelAdapter>(
+ new views::MenuModelAdapter(menu_model.get())),
+ source,
+ point,
+ true,
+ source_type);
+}
+
+void LauncherView::ShowMenu(
+ scoped_ptr<views::MenuModelAdapter> menu_model_adapter,
+ views::View* source,
+ const gfx::Point& click_point,
+ bool context_menu,
+ ui::MenuSourceType source_type) {
+ closing_event_time_ = base::TimeDelta();
+ launcher_menu_runner_.reset(
+ new views::MenuRunner(menu_model_adapter->CreateMenu()));
+
+ ScopedTargetRootWindow scoped_target(
+ source->GetWidget()->GetNativeView()->GetRootWindow());
+
+ // Determine the menu alignment dependent on the shelf.
+ views::MenuItemView::AnchorPosition menu_alignment =
+ views::MenuItemView::TOPLEFT;
+ gfx::Rect anchor_point = gfx::Rect(click_point, gfx::Size());
+
+ ShelfWidget* shelf = RootWindowController::ForLauncher(
+ GetWidget()->GetNativeView())->shelf();
+ if (!context_menu) {
+ // Application lists use a bubble.
+ ash::ShelfAlignment align = shelf->GetAlignment();
+ anchor_point = source->GetBoundsInScreen();
+
+ // It is possible to invoke the menu while it is sliding into view. To cover
+ // that case, the screen coordinates are offsetted by the animation delta.
+ gfx::Vector2d offset =
+ source->GetWidget()->GetNativeWindow()->bounds().origin() -
+ source->GetWidget()->GetNativeWindow()->GetTargetBounds().origin();
+ anchor_point.set_x(anchor_point.x() - offset.x());
+ anchor_point.set_y(anchor_point.y() - offset.y());
+
+ // Launcher items can have an asymmetrical border for spacing reasons.
+ // Adjust anchor location for this.
+ if (source->border())
+ anchor_point.Inset(source->border()->GetInsets());
+
+ switch (align) {
+ case ash::SHELF_ALIGNMENT_BOTTOM:
+ menu_alignment = views::MenuItemView::BUBBLE_ABOVE;
+ break;
+ case ash::SHELF_ALIGNMENT_LEFT:
+ menu_alignment = views::MenuItemView::BUBBLE_RIGHT;
+ break;
+ case ash::SHELF_ALIGNMENT_RIGHT:
+ menu_alignment = views::MenuItemView::BUBBLE_LEFT;
+ break;
+ case ash::SHELF_ALIGNMENT_TOP:
+ menu_alignment = views::MenuItemView::BUBBLE_BELOW;
+ break;
+ }
+ }
+ // If this gets deleted while we are in the menu, the launcher will be gone
+ // as well.
+ bool got_deleted = false;
+ got_deleted_ = &got_deleted;
+
+ shelf->ForceUndimming(true);
+ // NOTE: if you convert to HAS_MNEMONICS be sure and update menu building
+ // code.
+ if (launcher_menu_runner_->RunMenuAt(
+ source->GetWidget(),
+ NULL,
+ anchor_point,
+ menu_alignment,
+ source_type,
+ context_menu ? views::MenuRunner::CONTEXT_MENU : 0) ==
+ views::MenuRunner::MENU_DELETED) {
+ if (!got_deleted) {
+ got_deleted_ = NULL;
+ shelf->ForceUndimming(false);
+ }
+ return;
+ }
+ got_deleted_ = NULL;
+ shelf->ForceUndimming(false);
+
+ // Unpinning an item will reset the |launcher_menu_runner_| before coming
+ // here.
+ if (launcher_menu_runner_)
+ closing_event_time_ = launcher_menu_runner_->closing_event_time();
+ Shell::GetInstance()->UpdateShelfVisibility();
+}
+
+void LauncherView::OnBoundsAnimatorProgressed(views::BoundsAnimator* animator) {
+ FOR_EACH_OBSERVER(LauncherIconObserver, observers_,
+ OnLauncherIconPositionsChanged());
+ PreferredSizeChanged();
+}
+
+void LauncherView::OnBoundsAnimatorDone(views::BoundsAnimator* animator) {
+}
+
+bool LauncherView::IsUsableEvent(const ui::Event& event) {
+ if (closing_event_time_ == base::TimeDelta())
+ return true;
+
+ base::TimeDelta delta =
+ base::TimeDelta(event.time_stamp() - closing_event_time_);
+ closing_event_time_ = base::TimeDelta();
+ // TODO(skuhne): This time seems excessive, but it appears that the reposting
+ // takes that long. Need to come up with a better way of doing this.
+ return (delta.InMilliseconds() < 0 || delta.InMilliseconds() > 130);
+}
+
+const LauncherItem* LauncherView::LauncherItemForView(
+ const views::View* view) const {
+ int view_index = view_model_->GetIndexOfView(view);
+ if (view_index == -1)
+ return NULL;
+ return &(model_->items()[view_index]);
+}
+
+bool LauncherView::ShouldShowTooltipForView(const views::View* view) const {
+ if (view == GetAppListButtonView() &&
+ Shell::GetInstance()->GetAppListWindow())
+ return false;
+ const LauncherItem* item = LauncherItemForView(view);
+ return (!item || delegate_->ShouldShowTooltip(*item));
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/launcher/launcher_view.h b/chromium/ash/launcher/launcher_view.h
new file mode 100644
index 00000000000..507c7687fa6
--- /dev/null
+++ b/chromium/ash/launcher/launcher_view.h
@@ -0,0 +1,381 @@
+// Copyright (c) 2012 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 ASH_LAUNCHER_LAUNCHER_VIEW_H_
+#define ASH_LAUNCHER_LAUNCHER_VIEW_H_
+
+#include <utility>
+#include <vector>
+
+#include "ash/launcher/launcher_button_host.h"
+#include "ash/launcher/launcher_model_observer.h"
+#include "ash/wm/gestures/shelf_gesture_handler.h"
+#include "base/observer_list.h"
+#include "ui/app_list/views/app_list_drag_and_drop_host.h"
+#include "ui/views/animation/bounds_animator_observer.h"
+#include "ui/views/context_menu_controller.h"
+#include "ui/views/controls/button/button.h"
+#include "ui/views/focus/focus_manager.h"
+#include "ui/views/view.h"
+
+namespace views {
+class BoundsAnimator;
+class MenuModelAdapter;
+class MenuRunner;
+class ViewModel;
+}
+
+namespace ash {
+
+namespace test {
+class LauncherViewTestAPI;
+}
+
+class LauncherDelegate;
+struct LauncherItem;
+class LauncherIconObserver;
+class LauncherModel;
+
+namespace internal {
+
+class DragImageView;
+class LauncherButton;
+class LauncherTooltipManager;
+class ShelfLayoutManager;
+class OverflowBubble;
+class OverflowButton;
+
+class ASH_EXPORT LauncherView : public views::View,
+ public LauncherModelObserver,
+ public views::ButtonListener,
+ public LauncherButtonHost,
+ public views::ContextMenuController,
+ public views::FocusTraversable,
+ public views::BoundsAnimatorObserver,
+ public app_list::ApplicationDragAndDropHost {
+ public:
+ LauncherView(LauncherModel* model,
+ LauncherDelegate* delegate,
+ ShelfLayoutManager* shelf_layout_manager);
+ virtual ~LauncherView();
+
+ LauncherTooltipManager* tooltip_manager() { return tooltip_.get(); }
+
+ LauncherModel* model() { return model_; }
+
+ void Init();
+
+ void OnShelfAlignmentChanged();
+ void SchedulePaintForAllButtons();
+
+ // Returns the ideal bounds of the specified item, or an empty rect if id
+ // isn't know.
+ gfx::Rect GetIdealBoundsOfItemIcon(LauncherID id);
+
+ // Repositions the icon for the specified item by the midpoint of the window.
+ void UpdatePanelIconPosition(LauncherID id, const gfx::Point& midpoint);
+
+ void AddIconObserver(LauncherIconObserver* observer);
+ void RemoveIconObserver(LauncherIconObserver* observer);
+
+ // Returns true if we're showing a menu.
+ bool IsShowingMenu() const;
+
+ // Returns true if overflow bubble is shown.
+ bool IsShowingOverflowBubble() const;
+
+ views::View* GetAppListButtonView() const;
+
+ // Returns true if the mouse cursor exits the area for launcher tooltip.
+ // There are thin gaps between launcher buttons but the tooltip shouldn't hide
+ // in the gaps, but the tooltip should hide if the mouse moved totally outside
+ // of the buttons area.
+ bool ShouldHideTooltip(const gfx::Point& cursor_location);
+
+ int leading_inset() const { return leading_inset_; }
+ void set_leading_inset(int leading_inset) { leading_inset_ = leading_inset; }
+
+ // Overridden from FocusTraversable:
+ virtual views::FocusSearch* GetFocusSearch() OVERRIDE;
+ virtual FocusTraversable* GetFocusTraversableParent() OVERRIDE;
+ virtual View* GetFocusTraversableParentView() OVERRIDE;
+
+ // Overridden from app_list::ApplicationDragAndDropHost:
+ virtual void CreateDragIconProxy(
+ const gfx::Point& location_in_screen_coordinates,
+ const gfx::ImageSkia& icon,
+ views::View* replaced_view,
+ const gfx::Vector2d& cursor_offset_from_center,
+ float scale_factor) OVERRIDE;
+ virtual void UpdateDragIconProxy(
+ const gfx::Point& location_in_screen_coordinates) OVERRIDE;
+ virtual void DestroyDragIconProxy() OVERRIDE;
+ virtual bool StartDrag(
+ const std::string& app_id,
+ const gfx::Point& location_in_screen_coordinates) OVERRIDE;
+ virtual bool Drag(const gfx::Point& location_in_screen_coordinates) OVERRIDE;
+ virtual void EndDrag(bool cancel) OVERRIDE;
+
+ // Return the view model for test purposes.
+ const views::ViewModel* const view_model_for_test() const {
+ return view_model_.get();
+ }
+
+ private:
+ friend class ash::test::LauncherViewTestAPI;
+
+ class FadeOutAnimationDelegate;
+ class StartFadeAnimationDelegate;
+
+ struct IdealBounds {
+ gfx::Rect overflow_bounds;
+ };
+
+ bool is_overflow_mode() const {
+ return first_visible_index_ > 0;
+ }
+
+ bool dragging() const {
+ return drag_pointer_ != NONE;
+ }
+
+ // Sets the bounds of each view to its ideal bounds.
+ void LayoutToIdealBounds();
+
+ // Calculates the ideal bounds. The bounds of each button corresponding to an
+ // item in the model is set in |view_model_|.
+ void CalculateIdealBounds(IdealBounds* bounds);
+
+ // Returns the index of the last view whose max primary axis coordinate is
+ // less than |max_value|. Returns -1 if nothing fits, or there are no views.
+ int DetermineLastVisibleIndex(int max_value) const;
+
+ // Returns the index of the first panel whose min primary axis coordinate is
+ // at least |min_value|. Returns the index past the last panel if none fit.
+ int DetermineFirstVisiblePanelIndex(int min_value) const;
+
+ // Animates the bounds of each view to its ideal bounds.
+ void AnimateToIdealBounds();
+
+ // Creates the view used to represent |item|.
+ views::View* CreateViewForItem(const LauncherItem& item);
+
+ // Fades |view| from an opacity of 0 to 1. This is when adding a new item.
+ void FadeIn(views::View* view);
+
+ // Invoked when the pointer has moved enough to trigger a drag. Sets
+ // internal state in preparation for the drag.
+ void PrepareForDrag(Pointer pointer, const ui::LocatedEvent& event);
+
+ // Invoked when the mouse is dragged. Updates the models as appropriate.
+ void ContinueDrag(const ui::LocatedEvent& event);
+
+ // Returns true if |typea| and |typeb| should be in the same drag range.
+ bool SameDragType(LauncherItemType typea, LauncherItemType typeb) const;
+
+ // Returns the range (in the model) the item at the specified index can be
+ // dragged to.
+ std::pair<int, int> GetDragRange(int index);
+
+ // If there is a drag operation in progress it's canceled. If |modified_index|
+ // is valid, the new position of the corresponding item is returned.
+ int CancelDrag(int modified_index);
+
+ // Common setup done for all children.
+ void ConfigureChildView(views::View* view);
+
+ // Toggles the overflow menu.
+ void ToggleOverflowBubble();
+
+ // Update first launcher button's padding. This method adds padding to the
+ // first button to include the leading inset. It needs to be called once on
+ // button creation and every time when shelf alignment is changed.
+ void UpdateFirstButtonPadding();
+
+ // Invoked after the fading out animation for item deletion is ended.
+ void OnFadeOutAnimationEnded();
+
+ // Updates the visible range of overflow items in |overflow_view|.
+ void UpdateOverflowRange(LauncherView* overflow_view);
+
+ // Overridden from views::View:
+ virtual gfx::Size GetPreferredSize() OVERRIDE;
+ virtual void OnBoundsChanged(const gfx::Rect& previous_bounds) OVERRIDE;
+ virtual FocusTraversable* GetPaneFocusTraversable() OVERRIDE;
+ virtual void GetAccessibleState(ui::AccessibleViewState* state) OVERRIDE;
+
+ // Overridden from ui::EventHandler:
+ virtual void OnGestureEvent(ui::GestureEvent* event) OVERRIDE;
+
+ // Overridden from LauncherModelObserver:
+ virtual void LauncherItemAdded(int model_index) OVERRIDE;
+ virtual void LauncherItemRemoved(int model_index, LauncherID id) OVERRIDE;
+ virtual void LauncherItemChanged(int model_index,
+ const ash::LauncherItem& old_item) OVERRIDE;
+ virtual void LauncherItemMoved(int start_index, int target_index) OVERRIDE;
+ virtual void LauncherStatusChanged() OVERRIDE;
+
+ // Overridden from LauncherButtonHost:
+ virtual void PointerPressedOnButton(
+ views::View* view,
+ Pointer pointer,
+ const ui::LocatedEvent& event) OVERRIDE;
+ virtual void PointerDraggedOnButton(
+ views::View* view,
+ Pointer pointer,
+ const ui::LocatedEvent& event) OVERRIDE;
+ virtual void PointerReleasedOnButton(views::View* view,
+ Pointer pointer,
+ bool canceled) OVERRIDE;
+ virtual void MouseMovedOverButton(views::View* view) OVERRIDE;
+ virtual void MouseEnteredButton(views::View* view) OVERRIDE;
+ virtual void MouseExitedButton(views::View* view) OVERRIDE;
+ virtual base::string16 GetAccessibleName(const views::View* view) OVERRIDE;
+
+ // Overridden from views::ButtonListener:
+ virtual void ButtonPressed(views::Button* sender,
+ const ui::Event& event) OVERRIDE;
+
+ // Show the list of all running items for this |item|. It will return true
+ // when the menu was shown and false if there were no possible items to
+ // choose from. |source| specifies the view which is responsible for showing
+ // the menu, and the bubble will point towards it.
+ // The |event_flags| are the flags of the event which triggered this menu.
+ bool ShowListMenuForView(const LauncherItem& item,
+ views::View* source,
+ const ui::Event& event);
+
+ // Overridden from views::ContextMenuController:
+ virtual void ShowContextMenuForView(views::View* source,
+ const gfx::Point& point,
+ ui::MenuSourceType source_type) OVERRIDE;
+
+ // Show either a context or normal click menu of given |menu_model_adapter|.
+ // If |context_menu| is set, the displayed menu is a context menu and not
+ // a menu listing one or more running applications.
+ // The |click_point| is only used for |context_menu|'s.
+ void ShowMenu(scoped_ptr<views::MenuModelAdapter> menu_model_adapter,
+ views::View* source,
+ const gfx::Point& click_point,
+ bool context_menu,
+ ui::MenuSourceType source_type);
+
+ // Overridden from views::BoundsAnimatorObserver:
+ virtual void OnBoundsAnimatorProgressed(
+ views::BoundsAnimator* animator) OVERRIDE;
+ virtual void OnBoundsAnimatorDone(views::BoundsAnimator* animator) OVERRIDE;
+
+ // Returns false if the click which closed the previous menu is the click
+ // which triggered this event.
+ bool IsUsableEvent(const ui::Event& event);
+
+ // Convenience accessor to model_->items().
+ const LauncherItem* LauncherItemForView(const views::View* view) const;
+
+ // Returns true if a tooltip should be shown for |view|.
+ bool ShouldShowTooltipForView(const views::View* view) const;
+
+ // The model; owned by Launcher.
+ LauncherModel* model_;
+
+ // Delegate; owned by Launcher.
+ LauncherDelegate* delegate_;
+
+ // Used to manage the set of active launcher buttons. There is a view per
+ // item in |model_|.
+ scoped_ptr<views::ViewModel> view_model_;
+
+ // Index of first visible launcher item. When it it greater than 0,
+ // LauncherView is hosted in an overflow bubble. In this mode, it does not
+ // show browser, app list and overflow button.
+ int first_visible_index_;
+
+ // Last index of a launcher button that is visible
+ // (does not go into overflow).
+ int last_visible_index_;
+
+ scoped_ptr<views::BoundsAnimator> bounds_animator_;
+
+ OverflowButton* overflow_button_;
+
+ scoped_ptr<OverflowBubble> overflow_bubble_;
+
+ scoped_ptr<LauncherTooltipManager> tooltip_;
+
+ // Pointer device that initiated the current drag operation. If there is no
+ // current dragging operation, this is NONE.
+ Pointer drag_pointer_;
+
+ // The view being dragged. This is set immediately when the mouse is pressed.
+ // |dragging_| is set only if the mouse is dragged far enough.
+ views::View* drag_view_;
+
+ // X coordinate of the mouse down event in |drag_view_|s coordinates.
+ int drag_offset_;
+
+ // Index |drag_view_| was initially at.
+ int start_drag_index_;
+
+ // Used for the context menu of a particular item.
+ LauncherID context_menu_id_;
+
+ scoped_ptr<views::FocusSearch> focus_search_;
+
+#if !defined(OS_MACOSX)
+ scoped_ptr<views::MenuRunner> launcher_menu_runner_;
+#endif
+
+ ObserverList<LauncherIconObserver> observers_;
+
+ // Amount content is inset on the left edge (or top edge for vertical
+ // alignment).
+ int leading_inset_;
+
+ ShelfGestureHandler gesture_handler_;
+
+ // True when an item being inserted or removed in the model cancels a drag.
+ bool cancelling_drag_model_changed_;
+
+ // Index of the last hidden launcher item. If there are no hidden items this
+ // will be equal to last_visible_index_ + 1.
+ int last_hidden_index_;
+
+ // The timestamp of the event which closed the last menu - or 0.
+ base::TimeDelta closing_event_time_;
+
+ // When this object gets deleted while a menu is shown, this pointed
+ // element will be set to false.
+ bool* got_deleted_;
+
+ // True if a drag and drop operation created/pinned the item in the launcher
+ // and it needs to be deleted/unpinned again if the operation gets cancelled.
+ bool drag_and_drop_item_pinned_;
+
+ // The launcher item which is currently used for a drag and a drop operation
+ // or 0 otherwise.
+ LauncherID drag_and_drop_launcher_id_;
+
+ // The application ID of the application which we drag and drop.
+ std::string drag_and_drop_app_id_;
+
+ // The original launcher item's size before the dragging operation.
+ gfx::Size pre_drag_and_drop_size_;
+
+ // The image proxy for drag operations when a drag and drop host exists and
+ // the item can be dragged outside the app grid.
+ scoped_ptr<ash::internal::DragImageView> drag_image_;
+
+ // The cursor offset to the middle of the dragged item.
+ gfx::Vector2d drag_image_offset_;
+
+ // The view which gets replaced by our drag icon proxy.
+ views::View* drag_replaced_view_;
+
+ DISALLOW_COPY_AND_ASSIGN(LauncherView);
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_LAUNCHER_LAUNCHER_VIEW_H_
diff --git a/chromium/ash/launcher/launcher_view_unittest.cc b/chromium/ash/launcher/launcher_view_unittest.cc
new file mode 100644
index 00000000000..77316553cf1
--- /dev/null
+++ b/chromium/ash/launcher/launcher_view_unittest.cc
@@ -0,0 +1,1147 @@
+// Copyright (c) 2012 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.
+
+#include "ash/launcher/launcher_view.h"
+
+#include <algorithm>
+#include <vector>
+
+#include "ash/ash_switches.h"
+#include "ash/launcher/launcher.h"
+#include "ash/launcher/launcher_button.h"
+#include "ash/launcher/launcher_icon_observer.h"
+#include "ash/launcher/launcher_model.h"
+#include "ash/launcher/launcher_tooltip_manager.h"
+#include "ash/launcher/launcher_types.h"
+#include "ash/root_window_controller.h"
+#include "ash/shelf/shelf_layout_manager.h"
+#include "ash/shelf/shelf_widget.h"
+#include "ash/shell.h"
+#include "ash/shell_window_ids.h"
+#include "ash/test/ash_test_base.h"
+#include "ash/test/launcher_view_test_api.h"
+#include "ash/test/shell_test_api.h"
+#include "ash/test/test_launcher_delegate.h"
+#include "base/basictypes.h"
+#include "base/command_line.h"
+#include "base/compiler_specific.h"
+#include "base/memory/scoped_ptr.h"
+#include "grit/ash_resources.h"
+#include "ui/aura/root_window.h"
+#include "ui/aura/test/aura_test_base.h"
+#include "ui/aura/test/event_generator.h"
+#include "ui/aura/window.h"
+#include "ui/base/events/event.h"
+#include "ui/base/events/event_constants.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/compositor/layer.h"
+#include "ui/views/view_model.h"
+#include "ui/views/widget/widget.h"
+#include "ui/views/widget/widget_delegate.h"
+
+namespace ash {
+namespace test {
+
+////////////////////////////////////////////////////////////////////////////////
+// LauncherIconObserver tests.
+
+class TestLauncherIconObserver : public LauncherIconObserver {
+ public:
+ explicit TestLauncherIconObserver(Launcher* launcher)
+ : launcher_(launcher),
+ change_notified_(false) {
+ if (launcher_)
+ launcher_->AddIconObserver(this);
+ }
+
+ virtual ~TestLauncherIconObserver() {
+ if (launcher_)
+ launcher_->RemoveIconObserver(this);
+ }
+
+ // LauncherIconObserver implementation.
+ virtual void OnLauncherIconPositionsChanged() OVERRIDE {
+ change_notified_ = true;
+ }
+
+ int change_notified() const { return change_notified_; }
+ void Reset() { change_notified_ = false; }
+
+ private:
+ Launcher* launcher_;
+ bool change_notified_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestLauncherIconObserver);
+};
+
+class LauncherViewIconObserverTest : public ash::test::AshTestBase {
+ public:
+ LauncherViewIconObserverTest() {}
+ virtual ~LauncherViewIconObserverTest() {}
+
+ virtual void SetUp() OVERRIDE {
+ AshTestBase::SetUp();
+ Launcher* launcher = Launcher::ForPrimaryDisplay();
+ observer_.reset(new TestLauncherIconObserver(launcher));
+
+ launcher_view_test_.reset(new LauncherViewTestAPI(
+ launcher->GetLauncherViewForTest()));
+ launcher_view_test_->SetAnimationDuration(1);
+ }
+
+ virtual void TearDown() OVERRIDE {
+ observer_.reset();
+ AshTestBase::TearDown();
+ }
+
+ TestLauncherIconObserver* observer() { return observer_.get(); }
+
+ LauncherViewTestAPI* launcher_view_test() {
+ return launcher_view_test_.get();
+ }
+
+ Launcher* LauncherForSecondaryDisplay() {
+ return Launcher::ForWindow(Shell::GetAllRootWindows()[1]);
+ }
+
+ private:
+ scoped_ptr<TestLauncherIconObserver> observer_;
+ scoped_ptr<LauncherViewTestAPI> launcher_view_test_;
+
+ DISALLOW_COPY_AND_ASSIGN(LauncherViewIconObserverTest);
+};
+
+TEST_F(LauncherViewIconObserverTest, AddRemove) {
+ ash::test::TestLauncherDelegate* launcher_delegate =
+ ash::test::TestLauncherDelegate::instance();
+ ASSERT_TRUE(launcher_delegate);
+
+ views::Widget::InitParams params(views::Widget::InitParams::TYPE_WINDOW);
+ params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
+ params.bounds = gfx::Rect(0, 0, 200, 200);
+ params.context = CurrentContext();
+
+ scoped_ptr<views::Widget> widget(new views::Widget());
+ widget->Init(params);
+ launcher_delegate->AddLauncherItem(widget->GetNativeWindow());
+ launcher_view_test()->RunMessageLoopUntilAnimationsDone();
+ EXPECT_TRUE(observer()->change_notified());
+ observer()->Reset();
+
+ widget->Show();
+ widget->GetNativeWindow()->parent()->RemoveChild(widget->GetNativeWindow());
+ launcher_view_test()->RunMessageLoopUntilAnimationsDone();
+ EXPECT_TRUE(observer()->change_notified());
+ observer()->Reset();
+}
+
+// Sometimes fails on trybots on win7_aura. http://crbug.com/177135
+#if defined(OS_WIN)
+#define MAYBE_AddRemoveWithMultipleDisplays \
+ DISABLED_AddRemoveWithMultipleDisplays
+#else
+#define MAYBE_AddRemoveWithMultipleDisplays \
+ AddRemoveWithMultipleDisplays
+#endif
+// Make sure creating/deleting an window on one displays notifies a
+// launcher on external display as well as one on primary.
+TEST_F(LauncherViewIconObserverTest, MAYBE_AddRemoveWithMultipleDisplays) {
+ UpdateDisplay("400x400,400x400");
+ TestLauncherIconObserver second_observer(LauncherForSecondaryDisplay());
+
+ ash::test::TestLauncherDelegate* launcher_delegate =
+ ash::test::TestLauncherDelegate::instance();
+ ASSERT_TRUE(launcher_delegate);
+
+ views::Widget::InitParams params(views::Widget::InitParams::TYPE_WINDOW);
+ params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
+ params.bounds = gfx::Rect(0, 0, 200, 200);
+ params.context = CurrentContext();
+
+ scoped_ptr<views::Widget> widget(new views::Widget());
+ widget->Init(params);
+ launcher_delegate->AddLauncherItem(widget->GetNativeWindow());
+ launcher_view_test()->RunMessageLoopUntilAnimationsDone();
+ EXPECT_TRUE(observer()->change_notified());
+ EXPECT_TRUE(second_observer.change_notified());
+ observer()->Reset();
+ second_observer.Reset();
+
+ widget->GetNativeWindow()->parent()->RemoveChild(widget->GetNativeWindow());
+ launcher_view_test()->RunMessageLoopUntilAnimationsDone();
+ EXPECT_TRUE(observer()->change_notified());
+ EXPECT_TRUE(second_observer.change_notified());
+
+ observer()->Reset();
+ second_observer.Reset();
+}
+
+TEST_F(LauncherViewIconObserverTest, BoundsChanged) {
+ ash::ShelfWidget* shelf = Shell::GetPrimaryRootWindowController()->shelf();
+ Launcher* launcher = Launcher::ForPrimaryDisplay();
+ gfx::Size shelf_size =
+ shelf->GetWindowBoundsInScreen().size();
+ shelf_size.set_width(shelf_size.width() / 2);
+ ASSERT_GT(shelf_size.width(), 0);
+ launcher->SetLauncherViewBounds(gfx::Rect(shelf_size));
+ // No animation happens for LauncherView bounds change.
+ EXPECT_TRUE(observer()->change_notified());
+ observer()->Reset();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// LauncherView tests.
+
+class LauncherViewTest : public AshTestBase {
+ public:
+ LauncherViewTest() : model_(NULL), launcher_view_(NULL) {}
+ virtual ~LauncherViewTest() {}
+
+ virtual void SetUp() OVERRIDE {
+ AshTestBase::SetUp();
+ test::ShellTestApi test_api(Shell::GetInstance());
+ model_ = test_api.launcher_model();
+ Launcher* launcher = Launcher::ForPrimaryDisplay();
+ launcher_view_ = launcher->GetLauncherViewForTest();
+
+ // The bounds should be big enough for 4 buttons + overflow chevron.
+ launcher_view_->SetBounds(0, 0, 500, 50);
+
+ test_api_.reset(new LauncherViewTestAPI(launcher_view_));
+ test_api_->SetAnimationDuration(1); // Speeds up animation for test.
+
+ // Add browser shortcut launcher item at index 0 for test.
+ AddBrowserShortcut();
+ }
+
+ virtual void TearDown() OVERRIDE {
+ test_api_.reset();
+ AshTestBase::TearDown();
+ }
+
+ protected:
+ LauncherID AddBrowserShortcut() {
+ LauncherItem browser_shortcut;
+ browser_shortcut.type = TYPE_BROWSER_SHORTCUT;
+ browser_shortcut.is_incognito = false;
+
+ LauncherID id = model_->next_id();
+ model_->AddAt(0, browser_shortcut);
+ test_api_->RunMessageLoopUntilAnimationsDone();
+ return id;
+ }
+
+ LauncherID AddAppShortcut() {
+ LauncherItem item;
+ item.type = TYPE_APP_SHORTCUT;
+ item.status = STATUS_CLOSED;
+
+ LauncherID id = model_->next_id();
+ model_->Add(item);
+ test_api_->RunMessageLoopUntilAnimationsDone();
+ return id;
+ }
+
+ LauncherID AddTabbedBrowserNoWait() {
+ LauncherItem item;
+ item.type = TYPE_TABBED;
+ item.status = STATUS_RUNNING;
+
+ LauncherID id = model_->next_id();
+ model_->Add(item);
+ return id;
+ }
+
+ LauncherID AddTabbedBrowser() {
+ LauncherID id = AddTabbedBrowserNoWait();
+ test_api_->RunMessageLoopUntilAnimationsDone();
+ return id;
+ }
+
+ LauncherID AddPanel() {
+ LauncherID id = AddPanelNoWait();
+ test_api_->RunMessageLoopUntilAnimationsDone();
+ return id;
+ }
+
+ LauncherID AddPlatformAppNoWait() {
+ LauncherItem item;
+ item.type = TYPE_PLATFORM_APP;
+ item.status = STATUS_RUNNING;
+
+ LauncherID id = model_->next_id();
+ model_->Add(item);
+ return id;
+ }
+
+ LauncherID AddPanelNoWait() {
+ LauncherItem item;
+ item.type = TYPE_APP_PANEL;
+ item.status = STATUS_RUNNING;
+
+ LauncherID id = model_->next_id();
+ model_->Add(item);
+ return id;
+ }
+
+ LauncherID AddPlatformApp() {
+ LauncherID id = AddPlatformAppNoWait();
+ test_api_->RunMessageLoopUntilAnimationsDone();
+ return id;
+ }
+
+ void RemoveByID(LauncherID id) {
+ model_->RemoveItemAt(model_->ItemIndexByID(id));
+ test_api_->RunMessageLoopUntilAnimationsDone();
+ }
+
+ internal::LauncherButton* GetButtonByID(LauncherID id) {
+ int index = model_->ItemIndexByID(id);
+ return test_api_->GetButton(index);
+ }
+
+ LauncherItem GetItemByID(LauncherID id) {
+ LauncherItems::const_iterator items = model_->ItemByID(id);
+ return *items;
+ }
+
+ void CheckModelIDs(
+ const std::vector<std::pair<LauncherID, views::View*> >& id_map) {
+ size_t map_index = 0;
+ for (size_t model_index = 0;
+ model_index < model_->items().size();
+ ++model_index) {
+ ash::LauncherItem item = model_->items()[model_index];
+ ash::LauncherID id = item.id;
+ EXPECT_EQ(id_map[map_index].first, id);
+ EXPECT_EQ(id_map[map_index].second, GetButtonByID(id));
+ ++map_index;
+ }
+ ASSERT_EQ(map_index, id_map.size());
+ }
+
+ void VerifyLauncherItemBoundsAreValid() {
+ for (int i=0;i <= test_api_->GetLastVisibleIndex(); ++i) {
+ if (test_api_->GetButton(i)) {
+ gfx::Rect launcher_view_bounds = launcher_view_->GetLocalBounds();
+ gfx::Rect item_bounds = test_api_->GetBoundsByIndex(i);
+ EXPECT_TRUE(item_bounds.x() >= 0);
+ EXPECT_TRUE(item_bounds.y() >= 0);
+ EXPECT_TRUE(item_bounds.right() <= launcher_view_bounds.width());
+ EXPECT_TRUE(item_bounds.bottom() <= launcher_view_bounds.height());
+ }
+ }
+ }
+
+ views::View* SimulateButtonPressed(
+ internal::LauncherButtonHost::Pointer pointer,
+ int button_index) {
+ internal::LauncherButtonHost* button_host = launcher_view_;
+ views::View* button = test_api_->GetButton(button_index);
+ ui::MouseEvent click_event(ui::ET_MOUSE_PRESSED,
+ button->bounds().origin(),
+ button->bounds().origin(), 0);
+ button_host->PointerPressedOnButton(button, pointer, click_event);
+ return button;
+ }
+
+ views::View* SimulateClick(internal::LauncherButtonHost::Pointer pointer,
+ int button_index) {
+ internal::LauncherButtonHost* button_host = launcher_view_;
+ views::View* button = SimulateButtonPressed(pointer, button_index);
+ button_host->PointerReleasedOnButton(button,
+ internal::LauncherButtonHost::MOUSE,
+ false);
+ return button;
+ }
+
+ views::View* SimulateDrag(internal::LauncherButtonHost::Pointer pointer,
+ int button_index,
+ int destination_index) {
+ internal::LauncherButtonHost* button_host = launcher_view_;
+ views::View* button = SimulateButtonPressed(pointer, button_index);
+
+ // Drag.
+ views::View* destination = test_api_->GetButton(destination_index);
+ ui::MouseEvent drag_event(ui::ET_MOUSE_DRAGGED,
+ destination->bounds().origin(),
+ destination->bounds().origin(), 0);
+ button_host->PointerDraggedOnButton(button, pointer, drag_event);
+ return button;
+ }
+
+ void SetupForDragTest(
+ std::vector<std::pair<LauncherID, views::View*> >* id_map) {
+ // Initialize |id_map| with the automatically-created launcher buttons.
+ for (size_t i = 0; i < model_->items().size(); ++i) {
+ internal::LauncherButton* button = test_api_->GetButton(i);
+ id_map->push_back(std::make_pair(model_->items()[i].id, button));
+ }
+ ASSERT_NO_FATAL_FAILURE(CheckModelIDs(*id_map));
+
+ // Add 5 app launcher buttons for testing.
+ for (int i = 0; i < 5; ++i) {
+ LauncherID id = AddAppShortcut();
+ // browser shortcut is located at index 0. So we should start to add app
+ // shortcut at index 1.
+ id_map->insert(id_map->begin() + (i + 1),
+ std::make_pair(id, GetButtonByID(id)));
+ }
+ ASSERT_NO_FATAL_FAILURE(CheckModelIDs(*id_map));
+ }
+
+ views::View* GetTooltipAnchorView() {
+ return launcher_view_->tooltip_manager()->anchor_;
+ }
+
+ void ShowTooltip() {
+ launcher_view_->tooltip_manager()->ShowInternal();
+ }
+
+ LauncherModel* model_;
+ internal::LauncherView* launcher_view_;
+
+ scoped_ptr<LauncherViewTestAPI> test_api_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(LauncherViewTest);
+};
+
+class LauncherViewTextDirectionTest
+ : public LauncherViewTest,
+ public testing::WithParamInterface<bool> {
+ public:
+ LauncherViewTextDirectionTest() : is_rtl_(GetParam()) {}
+ virtual ~LauncherViewTextDirectionTest() {}
+
+ virtual void SetUp() OVERRIDE {
+ LauncherViewTest::SetUp();
+ original_locale_ = l10n_util::GetApplicationLocale(std::string());
+ if (is_rtl_)
+ base::i18n::SetICUDefaultLocale("he");
+ ASSERT_EQ(is_rtl_, base::i18n::IsRTL());
+ }
+
+ virtual void TearDown() OVERRIDE {
+ if (is_rtl_)
+ base::i18n::SetICUDefaultLocale(original_locale_);
+ LauncherViewTest::TearDown();
+ }
+
+ private:
+ bool is_rtl_;
+ std::string original_locale_;
+
+ DISALLOW_COPY_AND_ASSIGN(LauncherViewTextDirectionTest);
+};
+
+// Checks that the ideal item icon bounds match the view's bounds in the screen
+// in both LTR and RTL.
+TEST_P(LauncherViewTextDirectionTest, IdealBoundsOfItemIcon) {
+ LauncherID id = AddTabbedBrowser();
+ internal::LauncherButton* button = GetButtonByID(id);
+ gfx::Rect item_bounds = button->GetBoundsInScreen();
+ gfx::Point icon_offset = button->GetIconBounds().origin();
+ item_bounds.Offset(icon_offset.OffsetFromOrigin());
+ gfx::Rect ideal_bounds = launcher_view_->GetIdealBoundsOfItemIcon(id);
+ gfx::Point screen_origin;
+ views::View::ConvertPointToScreen(launcher_view_, &screen_origin);
+ ideal_bounds.Offset(screen_origin.x(), screen_origin.y());
+ EXPECT_EQ(item_bounds.x(), ideal_bounds.x());
+ EXPECT_EQ(item_bounds.y(), ideal_bounds.y());
+}
+
+// Checks that launcher view contents are considered in the correct drag group.
+TEST_F(LauncherViewTest, EnforceDragType) {
+ EXPECT_TRUE(test_api_->SameDragType(TYPE_TABBED, TYPE_TABBED));
+ EXPECT_TRUE(test_api_->SameDragType(TYPE_TABBED, TYPE_PLATFORM_APP));
+ EXPECT_FALSE(test_api_->SameDragType(TYPE_TABBED, TYPE_APP_SHORTCUT));
+ EXPECT_FALSE(test_api_->SameDragType(TYPE_TABBED, TYPE_BROWSER_SHORTCUT));
+ EXPECT_FALSE(test_api_->SameDragType(TYPE_TABBED, TYPE_WINDOWED_APP));
+ EXPECT_FALSE(test_api_->SameDragType(TYPE_TABBED, TYPE_APP_LIST));
+ EXPECT_FALSE(test_api_->SameDragType(TYPE_TABBED, TYPE_APP_PANEL));
+
+ EXPECT_TRUE(test_api_->SameDragType(TYPE_PLATFORM_APP, TYPE_PLATFORM_APP));
+ EXPECT_FALSE(test_api_->SameDragType(TYPE_PLATFORM_APP, TYPE_APP_SHORTCUT));
+ EXPECT_FALSE(test_api_->SameDragType(TYPE_PLATFORM_APP,
+ TYPE_BROWSER_SHORTCUT));
+ EXPECT_FALSE(test_api_->SameDragType(TYPE_PLATFORM_APP, TYPE_WINDOWED_APP));
+ EXPECT_FALSE(test_api_->SameDragType(TYPE_PLATFORM_APP, TYPE_APP_LIST));
+ EXPECT_FALSE(test_api_->SameDragType(TYPE_PLATFORM_APP, TYPE_APP_PANEL));
+
+ EXPECT_TRUE(test_api_->SameDragType(TYPE_APP_SHORTCUT, TYPE_APP_SHORTCUT));
+ EXPECT_TRUE(test_api_->SameDragType(TYPE_APP_SHORTCUT,
+ TYPE_BROWSER_SHORTCUT));
+ EXPECT_FALSE(test_api_->SameDragType(TYPE_APP_SHORTCUT,
+ TYPE_WINDOWED_APP));
+ EXPECT_FALSE(test_api_->SameDragType(TYPE_APP_SHORTCUT, TYPE_APP_LIST));
+ EXPECT_FALSE(test_api_->SameDragType(TYPE_APP_SHORTCUT, TYPE_APP_PANEL));
+
+ EXPECT_TRUE(test_api_->SameDragType(TYPE_BROWSER_SHORTCUT,
+ TYPE_BROWSER_SHORTCUT));
+ EXPECT_FALSE(test_api_->SameDragType(TYPE_BROWSER_SHORTCUT,
+ TYPE_WINDOWED_APP));
+ EXPECT_FALSE(test_api_->SameDragType(TYPE_BROWSER_SHORTCUT, TYPE_APP_LIST));
+ EXPECT_FALSE(test_api_->SameDragType(TYPE_BROWSER_SHORTCUT, TYPE_APP_PANEL));
+
+ EXPECT_TRUE(test_api_->SameDragType(TYPE_WINDOWED_APP, TYPE_WINDOWED_APP));
+ EXPECT_FALSE(test_api_->SameDragType(TYPE_WINDOWED_APP, TYPE_APP_LIST));
+ EXPECT_FALSE(test_api_->SameDragType(TYPE_WINDOWED_APP, TYPE_APP_PANEL));
+
+ EXPECT_TRUE(test_api_->SameDragType(TYPE_APP_LIST, TYPE_APP_LIST));
+ EXPECT_FALSE(test_api_->SameDragType(TYPE_APP_LIST, TYPE_APP_PANEL));
+
+ EXPECT_TRUE(test_api_->SameDragType(TYPE_APP_PANEL, TYPE_APP_PANEL));
+}
+
+// Adds browser button until overflow and verifies that the last added browser
+// button is hidden.
+TEST_F(LauncherViewTest, AddBrowserUntilOverflow) {
+ // All buttons should be visible.
+ ASSERT_EQ(test_api_->GetLastVisibleIndex() + 1,
+ test_api_->GetButtonCount());
+
+ // Add tabbed browser until overflow.
+ int items_added = 0;
+ LauncherID last_added = AddTabbedBrowser();
+ while (!test_api_->IsOverflowButtonVisible()) {
+ // Added button is visible after animation while in this loop.
+ EXPECT_TRUE(GetButtonByID(last_added)->visible());
+
+ last_added = AddTabbedBrowser();
+ ++items_added;
+ ASSERT_LT(items_added, 10000);
+ }
+
+ // The last added button should be invisible.
+ EXPECT_FALSE(GetButtonByID(last_added)->visible());
+}
+
+// Adds one browser button then adds app shortcut until overflow. Verifies that
+// the browser button gets hidden on overflow and last added app shortcut is
+// still visible.
+TEST_F(LauncherViewTest, AddAppShortcutWithBrowserButtonUntilOverflow) {
+ // All buttons should be visible.
+ ASSERT_EQ(test_api_->GetLastVisibleIndex() + 1,
+ test_api_->GetButtonCount());
+
+ LauncherID browser_button_id = AddTabbedBrowser();
+
+ // Add app shortcut until overflow.
+ int items_added = 0;
+ LauncherID last_added = AddAppShortcut();
+ while (!test_api_->IsOverflowButtonVisible()) {
+ // Added button is visible after animation while in this loop.
+ EXPECT_TRUE(GetButtonByID(last_added)->visible());
+
+ last_added = AddAppShortcut();
+ ++items_added;
+ ASSERT_LT(items_added, 10000);
+ }
+
+ // The last added app short button should be visible.
+ EXPECT_TRUE(GetButtonByID(last_added)->visible());
+ // And the browser button is invisible.
+ EXPECT_FALSE(GetButtonByID(browser_button_id)->visible());
+}
+
+TEST_F(LauncherViewTest, AddPanelHidesTabbedBrowser) {
+ ASSERT_EQ(test_api_->GetLastVisibleIndex() + 1,
+ test_api_->GetButtonCount());
+
+ // Add tabbed browser until overflow, remember last visible tabbed browser.
+ int items_added = 0;
+ LauncherID first_added = AddTabbedBrowser();
+ EXPECT_TRUE(GetButtonByID(first_added)->visible());
+ LauncherID last_visible = first_added;
+ while (true) {
+ LauncherID added = AddTabbedBrowser();
+ if (test_api_->IsOverflowButtonVisible()) {
+ EXPECT_FALSE(GetButtonByID(added)->visible());
+ break;
+ }
+ last_visible = added;
+ ++items_added;
+ ASSERT_LT(items_added, 10000);
+ }
+
+ LauncherID panel = AddPanel();
+ EXPECT_TRUE(GetButtonByID(panel)->visible());
+ EXPECT_FALSE(GetButtonByID(last_visible)->visible());
+
+ RemoveByID(panel);
+ EXPECT_TRUE(GetButtonByID(last_visible)->visible());
+}
+
+// When there are more panels then browsers we should hide panels rather
+// than browsers.
+TEST_F(LauncherViewTest, BrowserHidesExcessPanels) {
+ ASSERT_EQ(test_api_->GetLastVisibleIndex() + 1,
+ test_api_->GetButtonCount());
+
+ // Add tabbed browser.
+ LauncherID browser = AddTabbedBrowser();
+ LauncherID first_panel = AddPanel();
+
+ EXPECT_TRUE(GetButtonByID(browser)->visible());
+ EXPECT_TRUE(GetButtonByID(first_panel)->visible());
+
+ // Add panels until there is an overflow.
+ LauncherID last_panel = first_panel;
+ int items_added = 0;
+ while (!test_api_->IsOverflowButtonVisible()) {
+ last_panel = AddPanel();
+ ++items_added;
+ ASSERT_LT(items_added, 10000);
+ }
+
+ // The first panel should now be hidden by the new browsers needing space.
+ EXPECT_FALSE(GetButtonByID(first_panel)->visible());
+ EXPECT_TRUE(GetButtonByID(last_panel)->visible());
+ EXPECT_TRUE(GetButtonByID(browser)->visible());
+
+ // Adding browsers should eventually begin to hide browsers. We will add
+ // browsers until either the last panel or browser is hidden.
+ items_added = 0;
+ while (GetButtonByID(browser)->visible() &&
+ GetButtonByID(last_panel)->visible()) {
+ browser = AddTabbedBrowser();
+ ++items_added;
+ ASSERT_LT(items_added, 10000);
+ }
+ EXPECT_TRUE(GetButtonByID(last_panel)->visible());
+ EXPECT_FALSE(GetButtonByID(browser)->visible());
+}
+
+// Adds button until overflow then removes first added one. Verifies that
+// the last added one changes from invisible to visible and overflow
+// chevron is gone.
+TEST_F(LauncherViewTest, RemoveButtonRevealsOverflowed) {
+ // All buttons should be visible.
+ ASSERT_EQ(test_api_->GetLastVisibleIndex() + 1,
+ test_api_->GetButtonCount());
+
+ // Add tabbed browser until overflow.
+ int items_added = 0;
+ LauncherID first_added = AddTabbedBrowser();
+ LauncherID last_added = first_added;
+ while (!test_api_->IsOverflowButtonVisible()) {
+ last_added = AddTabbedBrowser();
+ ++items_added;
+ ASSERT_LT(items_added, 10000);
+ }
+
+ // Expect add more than 1 button. First added is visible and last is not.
+ EXPECT_NE(first_added, last_added);
+ EXPECT_TRUE(GetButtonByID(first_added)->visible());
+ EXPECT_FALSE(GetButtonByID(last_added)->visible());
+
+ // Remove first added.
+ RemoveByID(first_added);
+
+ // Last added button becomes visible and overflow chevron is gone.
+ EXPECT_TRUE(GetButtonByID(last_added)->visible());
+ EXPECT_EQ(1.0f, GetButtonByID(last_added)->layer()->opacity());
+ EXPECT_FALSE(test_api_->IsOverflowButtonVisible());
+}
+
+// Verifies that remove last overflowed button should hide overflow chevron.
+TEST_F(LauncherViewTest, RemoveLastOverflowed) {
+ // All buttons should be visible.
+ ASSERT_EQ(test_api_->GetLastVisibleIndex() + 1,
+ test_api_->GetButtonCount());
+
+ // Add tabbed browser until overflow.
+ int items_added = 0;
+ LauncherID last_added = AddTabbedBrowser();
+ while (!test_api_->IsOverflowButtonVisible()) {
+ last_added = AddTabbedBrowser();
+ ++items_added;
+ ASSERT_LT(items_added, 10000);
+ }
+
+ RemoveByID(last_added);
+ EXPECT_FALSE(test_api_->IsOverflowButtonVisible());
+}
+
+// Adds browser button without waiting for animation to finish and verifies
+// that all added buttons are visible.
+TEST_F(LauncherViewTest, AddButtonQuickly) {
+ // All buttons should be visible.
+ ASSERT_EQ(test_api_->GetLastVisibleIndex() + 1,
+ test_api_->GetButtonCount());
+
+ // Add a few tabbed browser quickly without wait for animation.
+ int added_count = 0;
+ while (!test_api_->IsOverflowButtonVisible()) {
+ AddTabbedBrowserNoWait();
+ ++added_count;
+ ASSERT_LT(added_count, 10000);
+ }
+
+ // LauncherView should be big enough to hold at least 3 new buttons.
+ ASSERT_GE(added_count, 3);
+
+ // Wait for the last animation to finish.
+ test_api_->RunMessageLoopUntilAnimationsDone();
+
+ // Verifies non-overflow buttons are visible.
+ for (int i = 0; i <= test_api_->GetLastVisibleIndex(); ++i) {
+ internal::LauncherButton* button = test_api_->GetButton(i);
+ if (button) {
+ EXPECT_TRUE(button->visible()) << "button index=" << i;
+ EXPECT_EQ(1.0f, button->layer()->opacity()) << "button index=" << i;
+ }
+ }
+}
+
+// Check that model changes are handled correctly while a launcher icon is being
+// dragged.
+TEST_F(LauncherViewTest, ModelChangesWhileDragging) {
+ internal::LauncherButtonHost* button_host = launcher_view_;
+
+ std::vector<std::pair<LauncherID, views::View*> > id_map;
+ SetupForDragTest(&id_map);
+
+ // Dragging browser shortcut at index 0.
+ EXPECT_TRUE(model_->items()[0].type == TYPE_BROWSER_SHORTCUT);
+ views::View* dragged_button = SimulateDrag(
+ internal::LauncherButtonHost::MOUSE, 0, 2);
+ std::rotate(id_map.begin(),
+ id_map.begin() + 1,
+ id_map.begin() + 3);
+ ASSERT_NO_FATAL_FAILURE(CheckModelIDs(id_map));
+ button_host->PointerReleasedOnButton(dragged_button,
+ internal::LauncherButtonHost::MOUSE,
+ false);
+ EXPECT_TRUE(model_->items()[2].type == TYPE_BROWSER_SHORTCUT);
+
+ // Dragging changes model order.
+ dragged_button = SimulateDrag(
+ internal::LauncherButtonHost::MOUSE, 0, 2);
+ std::rotate(id_map.begin(),
+ id_map.begin() + 1,
+ id_map.begin() + 3);
+ ASSERT_NO_FATAL_FAILURE(CheckModelIDs(id_map));
+
+ // Cancelling the drag operation restores previous order.
+ button_host->PointerReleasedOnButton(dragged_button,
+ internal::LauncherButtonHost::MOUSE,
+ true);
+ std::rotate(id_map.begin(),
+ id_map.begin() + 2,
+ id_map.begin() + 3);
+ ASSERT_NO_FATAL_FAILURE(CheckModelIDs(id_map));
+
+ // Deleting an item keeps the remaining intact.
+ dragged_button = SimulateDrag(internal::LauncherButtonHost::MOUSE, 0, 2);
+ model_->RemoveItemAt(1);
+ id_map.erase(id_map.begin() + 1);
+ ASSERT_NO_FATAL_FAILURE(CheckModelIDs(id_map));
+ button_host->PointerReleasedOnButton(dragged_button,
+ internal::LauncherButtonHost::MOUSE,
+ false);
+
+ // Adding a launcher item cancels the drag and respects the order.
+ dragged_button = SimulateDrag(internal::LauncherButtonHost::MOUSE, 0, 2);
+ LauncherID new_id = AddAppShortcut();
+ id_map.insert(id_map.begin() + 5,
+ std::make_pair(new_id, GetButtonByID(new_id)));
+ ASSERT_NO_FATAL_FAILURE(CheckModelIDs(id_map));
+ button_host->PointerReleasedOnButton(dragged_button,
+ internal::LauncherButtonHost::MOUSE,
+ false);
+
+ // Adding a launcher item at the end (i.e. a panel) canels drag and respects
+ // the order.
+ dragged_button = SimulateDrag(internal::LauncherButtonHost::MOUSE, 0, 2);
+ new_id = AddPanel();
+ id_map.insert(id_map.begin() + 7,
+ std::make_pair(new_id, GetButtonByID(new_id)));
+ ASSERT_NO_FATAL_FAILURE(CheckModelIDs(id_map));
+ button_host->PointerReleasedOnButton(dragged_button,
+ internal::LauncherButtonHost::MOUSE,
+ false);
+}
+
+// Check that 2nd drag from the other pointer would be ignored.
+TEST_F(LauncherViewTest, SimultaneousDrag) {
+ internal::LauncherButtonHost* button_host = launcher_view_;
+
+ std::vector<std::pair<LauncherID, views::View*> > id_map;
+ SetupForDragTest(&id_map);
+
+ // Start a mouse drag.
+ views::View* dragged_button_mouse = SimulateDrag(
+ internal::LauncherButtonHost::MOUSE, 0, 2);
+ std::rotate(id_map.begin(),
+ id_map.begin() + 1,
+ id_map.begin() + 3);
+ ASSERT_NO_FATAL_FAILURE(CheckModelIDs(id_map));
+ // Attempt a touch drag before the mouse drag finishes.
+ views::View* dragged_button_touch = SimulateDrag(
+ internal::LauncherButtonHost::TOUCH, 3, 1);
+
+ // Nothing changes since 2nd drag is ignored.
+ ASSERT_NO_FATAL_FAILURE(CheckModelIDs(id_map));
+
+ // Finish the mouse drag.
+ button_host->PointerReleasedOnButton(dragged_button_mouse,
+ internal::LauncherButtonHost::MOUSE,
+ false);
+ ASSERT_NO_FATAL_FAILURE(CheckModelIDs(id_map));
+
+ // Now start a touch drag.
+ dragged_button_touch = SimulateDrag(
+ internal::LauncherButtonHost::TOUCH, 3, 1);
+ std::rotate(id_map.begin() + 2,
+ id_map.begin() + 3,
+ id_map.begin() + 4);
+ ASSERT_NO_FATAL_FAILURE(CheckModelIDs(id_map));
+
+ // And attempt a mouse drag before the touch drag finishes.
+ dragged_button_mouse = SimulateDrag(
+ internal::LauncherButtonHost::MOUSE, 0, 1);
+
+ // Nothing changes since 2nd drag is ignored.
+ ASSERT_NO_FATAL_FAILURE(CheckModelIDs(id_map));
+
+ button_host->PointerReleasedOnButton(dragged_button_touch,
+ internal::LauncherButtonHost::TOUCH,
+ false);
+ ASSERT_NO_FATAL_FAILURE(CheckModelIDs(id_map));
+}
+
+// Check that clicking first on one item and then dragging another works as
+// expected.
+TEST_F(LauncherViewTest, ClickOneDragAnother) {
+ internal::LauncherButtonHost* button_host = launcher_view_;
+
+ std::vector<std::pair<LauncherID, views::View*> > id_map;
+ SetupForDragTest(&id_map);
+
+ // A click on item 1 is simulated.
+ SimulateClick(internal::LauncherButtonHost::MOUSE, 1);
+
+ // Dragging browser index at 0 should change the model order correctly.
+ EXPECT_TRUE(model_->items()[0].type == TYPE_BROWSER_SHORTCUT);
+ views::View* dragged_button = SimulateDrag(
+ internal::LauncherButtonHost::MOUSE, 0, 2);
+ std::rotate(id_map.begin(),
+ id_map.begin() + 1,
+ id_map.begin() + 3);
+ ASSERT_NO_FATAL_FAILURE(CheckModelIDs(id_map));
+ button_host->PointerReleasedOnButton(dragged_button,
+ internal::LauncherButtonHost::MOUSE,
+ false);
+ EXPECT_TRUE(model_->items()[2].type == TYPE_BROWSER_SHORTCUT);
+}
+
+// Confirm that item status changes are reflected in the buttons.
+TEST_F(LauncherViewTest, LauncherItemStatus) {
+ ASSERT_EQ(test_api_->GetLastVisibleIndex() + 1,
+ test_api_->GetButtonCount());
+
+ // Add tabbed browser.
+ LauncherID last_added = AddTabbedBrowser();
+ LauncherItem item = GetItemByID(last_added);
+ int index = model_->ItemIndexByID(last_added);
+ internal::LauncherButton* button = GetButtonByID(last_added);
+ ASSERT_EQ(internal::LauncherButton::STATE_RUNNING, button->state());
+ item.status = ash::STATUS_ACTIVE;
+ model_->Set(index, item);
+ ASSERT_EQ(internal::LauncherButton::STATE_ACTIVE, button->state());
+ item.status = ash::STATUS_ATTENTION;
+ model_->Set(index, item);
+ ASSERT_EQ(internal::LauncherButton::STATE_ATTENTION, button->state());
+}
+
+TEST_F(LauncherViewTest, LauncherItemPositionReflectedOnStateChanged) {
+ ASSERT_EQ(test_api_->GetLastVisibleIndex() + 1,
+ test_api_->GetButtonCount());
+
+ // Add 2 items to the launcher.
+ LauncherID item1_id = AddTabbedBrowser();
+ LauncherID item2_id = AddPlatformAppNoWait();
+ internal::LauncherButton* item1_button = GetButtonByID(item1_id);
+ internal::LauncherButton* item2_button = GetButtonByID(item2_id);
+
+ internal::LauncherButton::State state_mask =
+ static_cast<internal::LauncherButton::State>
+ (internal::LauncherButton::STATE_NORMAL |
+ internal::LauncherButton::STATE_HOVERED |
+ internal::LauncherButton::STATE_RUNNING |
+ internal::LauncherButton::STATE_ACTIVE |
+ internal::LauncherButton::STATE_ATTENTION |
+ internal::LauncherButton::STATE_FOCUSED);
+
+ // Clear the button states.
+ item1_button->ClearState(state_mask);
+ item2_button->ClearState(state_mask);
+
+ // Since default alignment in tests is bottom, state is reflected in y-axis.
+ ASSERT_EQ(item1_button->GetIconBounds().y(),
+ item2_button->GetIconBounds().y());
+ item1_button->AddState(internal::LauncherButton::STATE_HOVERED);
+ ASSERT_NE(item1_button->GetIconBounds().y(),
+ item2_button->GetIconBounds().y());
+ item1_button->ClearState(internal::LauncherButton::STATE_HOVERED);
+
+ // Enable the alternate shelf layout.
+ CommandLine::ForCurrentProcess()->AppendSwitch(
+ ash::switches::kAshUseAlternateShelfLayout);
+ launcher_view_->Layout();
+
+ // Since default alignment in tests is bottom, state is reflected in y-axis.
+ // In alternate shelf layout there is no visible hovered state.
+ ASSERT_EQ(item1_button->GetIconBounds().y(),
+ item2_button->GetIconBounds().y());
+ item1_button->AddState(internal::LauncherButton::STATE_HOVERED);
+ ASSERT_EQ(item1_button->GetIconBounds().y(),
+ item2_button->GetIconBounds().y());
+}
+
+// Confirm that item status changes are reflected in the buttons
+// for platform apps.
+TEST_F(LauncherViewTest, LauncherItemStatusPlatformApp) {
+ ASSERT_EQ(test_api_->GetLastVisibleIndex() + 1,
+ test_api_->GetButtonCount());
+
+ // Add tabbed browser.
+ LauncherID last_added = AddPlatformApp();
+ LauncherItem item = GetItemByID(last_added);
+ int index = model_->ItemIndexByID(last_added);
+ internal::LauncherButton* button = GetButtonByID(last_added);
+ ASSERT_EQ(internal::LauncherButton::STATE_RUNNING, button->state());
+ item.status = ash::STATUS_ACTIVE;
+ model_->Set(index, item);
+ ASSERT_EQ(internal::LauncherButton::STATE_ACTIVE, button->state());
+ item.status = ash::STATUS_ATTENTION;
+ model_->Set(index, item);
+ ASSERT_EQ(internal::LauncherButton::STATE_ATTENTION, button->state());
+}
+
+// Confirm that launcher item bounds are correctly updated on shelf changes.
+TEST_F(LauncherViewTest, LauncherItemBoundsCheck) {
+ internal::ShelfLayoutManager* shelf_layout_manager =
+ Shell::GetPrimaryRootWindowController()->shelf()->shelf_layout_manager();
+ VerifyLauncherItemBoundsAreValid();
+ shelf_layout_manager->SetAutoHideBehavior(SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS);
+ test_api_->RunMessageLoopUntilAnimationsDone();
+ VerifyLauncherItemBoundsAreValid();
+ shelf_layout_manager->SetAutoHideBehavior(SHELF_AUTO_HIDE_BEHAVIOR_NEVER);
+ test_api_->RunMessageLoopUntilAnimationsDone();
+ VerifyLauncherItemBoundsAreValid();
+}
+
+TEST_F(LauncherViewTest, LauncherTooltipTest) {
+ ASSERT_EQ(test_api_->GetLastVisibleIndex() + 1,
+ test_api_->GetButtonCount());
+
+ // Prepare some items to the launcher.
+ LauncherID app_button_id = AddAppShortcut();
+ LauncherID tab_button_id = AddTabbedBrowser();
+
+ internal::LauncherButton* app_button = GetButtonByID(app_button_id);
+ internal::LauncherButton* tab_button = GetButtonByID(tab_button_id);
+
+ internal::LauncherButtonHost* button_host = launcher_view_;
+ internal::LauncherTooltipManager* tooltip_manager =
+ launcher_view_->tooltip_manager();
+
+ button_host->MouseEnteredButton(app_button);
+ // There's a delay to show the tooltip, so it's not visible yet.
+ EXPECT_FALSE(tooltip_manager->IsVisible());
+ EXPECT_EQ(app_button, GetTooltipAnchorView());
+
+ ShowTooltip();
+ EXPECT_TRUE(tooltip_manager->IsVisible());
+
+ // Once it's visible, it keeps visibility and is pointing to the same
+ // item.
+ button_host->MouseExitedButton(app_button);
+ EXPECT_TRUE(tooltip_manager->IsVisible());
+ EXPECT_EQ(app_button, GetTooltipAnchorView());
+
+ // When entered to another item, it switches to the new item. There is no
+ // delay for the visibility.
+ button_host->MouseEnteredButton(tab_button);
+ EXPECT_TRUE(tooltip_manager->IsVisible());
+ EXPECT_EQ(tab_button, GetTooltipAnchorView());
+
+ button_host->MouseExitedButton(tab_button);
+ tooltip_manager->Close();
+
+ // Next time: enter app_button -> move immediately to tab_button.
+ button_host->MouseEnteredButton(app_button);
+ button_host->MouseExitedButton(app_button);
+ button_host->MouseEnteredButton(tab_button);
+ EXPECT_FALSE(tooltip_manager->IsVisible());
+ EXPECT_EQ(tab_button, GetTooltipAnchorView());
+}
+
+TEST_F(LauncherViewTest, ShouldHideTooltipTest) {
+ LauncherID app_button_id = AddAppShortcut();
+ LauncherID tab_button_id = AddTabbedBrowser();
+
+ // The tooltip shouldn't hide if the mouse is on normal buttons.
+ for (int i = 0; i < test_api_->GetButtonCount(); i++) {
+ internal::LauncherButton* button = test_api_->GetButton(i);
+ if (!button)
+ continue;
+
+ EXPECT_FALSE(launcher_view_->ShouldHideTooltip(
+ button->GetMirroredBounds().CenterPoint()))
+ << "LauncherView tries to hide on button " << i;
+ }
+
+ // The tooltip should not hide on the app-list button.
+ views::View* app_list_button = launcher_view_->GetAppListButtonView();
+ EXPECT_FALSE(launcher_view_->ShouldHideTooltip(
+ app_list_button->GetMirroredBounds().CenterPoint()));
+
+ // The tooltip shouldn't hide if the mouse is in the gap between two buttons.
+ gfx::Rect app_button_rect = GetButtonByID(app_button_id)->GetMirroredBounds();
+ gfx::Rect tab_button_rect = GetButtonByID(tab_button_id)->GetMirroredBounds();
+ ASSERT_FALSE(app_button_rect.Intersects(tab_button_rect));
+ EXPECT_FALSE(launcher_view_->ShouldHideTooltip(
+ gfx::UnionRects(app_button_rect, tab_button_rect).CenterPoint()));
+
+ // The tooltip should hide if it's outside of all buttons.
+ gfx::Rect all_area;
+ for (int i = 0; i < test_api_->GetButtonCount(); i++) {
+ internal::LauncherButton* button = test_api_->GetButton(i);
+ if (!button)
+ continue;
+
+ all_area.Union(button->GetMirroredBounds());
+ }
+ all_area.Union(launcher_view_->GetAppListButtonView()->GetMirroredBounds());
+ EXPECT_FALSE(launcher_view_->ShouldHideTooltip(all_area.origin()));
+ EXPECT_FALSE(launcher_view_->ShouldHideTooltip(
+ gfx::Point(all_area.right() - 1, all_area.bottom() - 1)));
+ EXPECT_TRUE(launcher_view_->ShouldHideTooltip(
+ gfx::Point(all_area.right(), all_area.y())));
+ EXPECT_TRUE(launcher_view_->ShouldHideTooltip(
+ gfx::Point(all_area.x() - 1, all_area.y())));
+ EXPECT_TRUE(launcher_view_->ShouldHideTooltip(
+ gfx::Point(all_area.x(), all_area.y() - 1)));
+ EXPECT_TRUE(launcher_view_->ShouldHideTooltip(
+ gfx::Point(all_area.x(), all_area.bottom())));
+}
+
+TEST_F(LauncherViewTest, ShouldHideTooltipWithAppListWindowTest) {
+ Shell::GetInstance()->ToggleAppList(NULL);
+ ASSERT_TRUE(Shell::GetInstance()->GetAppListWindow());
+
+ // The tooltip shouldn't hide if the mouse is on normal buttons.
+ for (int i = 1; i < test_api_->GetButtonCount(); i++) {
+ internal::LauncherButton* button = test_api_->GetButton(i);
+ if (!button)
+ continue;
+
+ EXPECT_FALSE(launcher_view_->ShouldHideTooltip(
+ button->GetMirroredBounds().CenterPoint()))
+ << "LauncherView tries to hide on button " << i;
+ }
+
+ // The tooltip should hide on the app-list button.
+ views::View* app_list_button = launcher_view_->GetAppListButtonView();
+ EXPECT_TRUE(launcher_view_->ShouldHideTooltip(
+ app_list_button->GetMirroredBounds().CenterPoint()));
+}
+
+// Test that by moving the mouse cursor off the button onto the bubble it closes
+// the bubble.
+TEST_F(LauncherViewTest, ShouldHideTooltipWhenHoveringOnTooltip) {
+ internal::LauncherTooltipManager* tooltip_manager =
+ launcher_view_->tooltip_manager();
+ tooltip_manager->CreateZeroDelayTimerForTest();
+ aura::test::EventGenerator generator(Shell::GetPrimaryRootWindow());
+
+ // Move the mouse off any item and check that no tooltip is shown.
+ generator.MoveMouseTo(gfx::Point(0, 0));
+ EXPECT_FALSE(tooltip_manager->IsVisible());
+
+ // Move the mouse over the button and check that it is visible.
+ views::View* app_list_button = launcher_view_->GetAppListButtonView();
+ gfx::Rect bounds = app_list_button->GetBoundsInScreen();
+ generator.MoveMouseTo(bounds.CenterPoint());
+ // Wait for the timer to go off.
+ RunAllPendingInMessageLoop();
+ EXPECT_TRUE(tooltip_manager->IsVisible());
+
+ // Move the mouse cursor slightly to the right of the item. The tooltip should
+ // stay open.
+ generator.MoveMouseBy(-(bounds.width() / 2 + 5), 0);
+ // Make sure there is no delayed close.
+ RunAllPendingInMessageLoop();
+ EXPECT_TRUE(tooltip_manager->IsVisible());
+
+ // Move back - it should still stay open.
+ generator.MoveMouseBy(bounds.width() / 2 + 5, 0);
+ // Make sure there is no delayed close.
+ RunAllPendingInMessageLoop();
+ EXPECT_TRUE(tooltip_manager->IsVisible());
+
+ // Now move the mouse cursor slightly above the item - so that it is over the
+ // tooltip bubble. Now it should disappear.
+ generator.MoveMouseBy(0, -(bounds.height() / 2 + 5));
+ // Wait until the delayed close kicked in.
+ RunAllPendingInMessageLoop();
+ EXPECT_FALSE(tooltip_manager->IsVisible());
+}
+
+// Resizing launcher view while an add animation without fade-in is running,
+// which happens when overflow happens. App list button should end up in its
+// new ideal bounds.
+TEST_F(LauncherViewTest, ResizeDuringOverflowAddAnimation) {
+ // All buttons should be visible.
+ ASSERT_EQ(test_api_->GetLastVisibleIndex() + 1,
+ test_api_->GetButtonCount());
+
+ // Add buttons until overflow. Let the non-overflow add animations finish but
+ // leave the last running.
+ int items_added = 0;
+ AddTabbedBrowserNoWait();
+ while (!test_api_->IsOverflowButtonVisible()) {
+ test_api_->RunMessageLoopUntilAnimationsDone();
+ AddTabbedBrowserNoWait();
+ ++items_added;
+ ASSERT_LT(items_added, 10000);
+ }
+
+ // Resize launcher view with that animation running and stay overflown.
+ gfx::Rect bounds = launcher_view_->bounds();
+ bounds.set_width(bounds.width() - kLauncherPreferredSize);
+ launcher_view_->SetBoundsRect(bounds);
+ ASSERT_TRUE(test_api_->IsOverflowButtonVisible());
+
+ // Finish the animation.
+ test_api_->RunMessageLoopUntilAnimationsDone();
+
+ // App list button should ends up in its new ideal bounds.
+ const int app_list_button_index = test_api_->GetButtonCount() - 1;
+ const gfx::Rect& app_list_ideal_bounds =
+ test_api_->GetIdealBoundsByIndex(app_list_button_index);
+ const gfx::Rect& app_list_bounds =
+ test_api_->GetBoundsByIndex(app_list_button_index);
+ EXPECT_EQ(app_list_bounds, app_list_ideal_bounds);
+}
+
+// Check that the first item in the list follows Fitt's law by including the
+// first pixel and being therefore bigger then the others.
+TEST_F(LauncherViewTest, CheckFittsLaw) {
+ // All buttons should be visible.
+ ASSERT_EQ(test_api_->GetLastVisibleIndex() + 1,
+ test_api_->GetButtonCount());
+ gfx::Rect ideal_bounds_0 = test_api_->GetIdealBoundsByIndex(0);
+ gfx::Rect ideal_bounds_1 = test_api_->GetIdealBoundsByIndex(1);
+ EXPECT_GT(ideal_bounds_0.width(), ideal_bounds_1.width());
+}
+
+INSTANTIATE_TEST_CASE_P(LtrRtl, LauncherViewTextDirectionTest, testing::Bool());
+
+} // namespace test
+} // namespace ash
diff --git a/chromium/ash/launcher/overflow_bubble.cc b/chromium/ash/launcher/overflow_bubble.cc
new file mode 100644
index 00000000000..7d84d1ef0cb
--- /dev/null
+++ b/chromium/ash/launcher/overflow_bubble.cc
@@ -0,0 +1,285 @@
+// Copyright (c) 2012 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.
+
+#include "ash/launcher/overflow_bubble.h"
+
+#include <algorithm>
+
+#include "ash/launcher/launcher_types.h"
+#include "ash/launcher/launcher_view.h"
+#include "ash/root_window_controller.h"
+#include "ash/shelf/shelf_layout_manager.h"
+#include "ash/shelf/shelf_widget.h"
+#include "ash/shell.h"
+#include "ash/system/tray/system_tray.h"
+#include "ui/aura/root_window.h"
+#include "ui/gfx/insets.h"
+#include "ui/gfx/screen.h"
+#include "ui/views/bubble/bubble_delegate.h"
+#include "ui/views/bubble/bubble_frame_view.h"
+#include "ui/views/widget/widget.h"
+
+namespace ash {
+namespace internal {
+
+namespace {
+
+// Max bubble size to screen size ratio.
+const float kMaxBubbleSizeToScreenRatio = 0.5f;
+
+// Inner padding in pixels for launcher view inside bubble.
+const int kPadding = 2;
+
+// Padding space in pixels between LauncherView's left/top edge to its contents.
+const int kLauncherViewLeadingInset = 8;
+
+////////////////////////////////////////////////////////////////////////////////
+// OverflowBubbleView
+// OverflowBubbleView hosts a LauncherView to display overflown items.
+
+class OverflowBubbleView : public views::BubbleDelegateView {
+ public:
+ OverflowBubbleView();
+ virtual ~OverflowBubbleView();
+
+ void InitOverflowBubble(views::View* anchor, LauncherView* launcher_view);
+
+ private:
+ bool IsHorizontalAlignment() const {
+ return GetShelfLayoutManagerForLauncher()->IsHorizontalAlignment();
+ }
+
+ const gfx::Size GetContentsSize() const {
+ return static_cast<views::View*>(launcher_view_)->GetPreferredSize();
+ }
+
+ // Gets arrow location based on shelf alignment.
+ views::BubbleBorder::Arrow GetBubbleArrow() const {
+ return GetShelfLayoutManagerForLauncher()->SelectValueForShelfAlignment(
+ views::BubbleBorder::BOTTOM_LEFT,
+ views::BubbleBorder::LEFT_TOP,
+ views::BubbleBorder::RIGHT_TOP,
+ views::BubbleBorder::TOP_LEFT);
+ }
+
+ void ScrollByXOffset(int x_offset);
+ void ScrollByYOffset(int y_offset);
+
+ // views::View overrides:
+ virtual gfx::Size GetPreferredSize() OVERRIDE;
+ virtual void Layout() OVERRIDE;
+ virtual void ChildPreferredSizeChanged(views::View* child) OVERRIDE;
+ virtual bool OnMouseWheel(const ui::MouseWheelEvent& event) OVERRIDE;
+
+ // ui::EventHandler overrides:
+ virtual void OnScrollEvent(ui::ScrollEvent* event) OVERRIDE;
+
+ // views::BubbleDelegate overrides:
+ virtual gfx::Rect GetBubbleBounds() OVERRIDE;
+
+ ShelfLayoutManager* GetShelfLayoutManagerForLauncher() const {
+ return ShelfLayoutManager::ForLauncher(
+ anchor_view()->GetWidget()->GetNativeView());
+ }
+
+ LauncherView* launcher_view_; // Owned by views hierarchy.
+ gfx::Vector2d scroll_offset_;
+
+ DISALLOW_COPY_AND_ASSIGN(OverflowBubbleView);
+};
+
+OverflowBubbleView::OverflowBubbleView()
+ : launcher_view_(NULL) {
+}
+
+OverflowBubbleView::~OverflowBubbleView() {
+}
+
+void OverflowBubbleView::InitOverflowBubble(views::View* anchor,
+ LauncherView* launcher_view) {
+ // set_anchor_view needs to be called before GetShelfLayoutManagerForLauncher
+ // can be called.
+ set_anchor_view(anchor);
+ set_arrow(GetBubbleArrow());
+ set_background(NULL);
+ set_color(SkColorSetARGB(kLauncherBackgroundAlpha, 0, 0, 0));
+ set_margins(gfx::Insets(kPadding, kPadding, kPadding, kPadding));
+ set_move_with_anchor(true);
+
+ // Makes bubble view has a layer and clip its children layers.
+ SetPaintToLayer(true);
+ SetFillsBoundsOpaquely(false);
+ layer()->SetMasksToBounds(true);
+
+ launcher_view_ = launcher_view;
+ AddChildView(launcher_view_);
+
+ views::BubbleDelegateView::CreateBubble(this);
+}
+
+void OverflowBubbleView::ScrollByXOffset(int x_offset) {
+ const gfx::Rect visible_bounds(GetContentsBounds());
+ const gfx::Size contents_size(GetContentsSize());
+
+ int x = std::min(contents_size.width() - visible_bounds.width(),
+ std::max(0, scroll_offset_.x() + x_offset));
+ scroll_offset_.set_x(x);
+}
+
+void OverflowBubbleView::ScrollByYOffset(int y_offset) {
+ const gfx::Rect visible_bounds(GetContentsBounds());
+ const gfx::Size contents_size(GetContentsSize());
+
+ int y = std::min(contents_size.height() - visible_bounds.height(),
+ std::max(0, scroll_offset_.y() + y_offset));
+ scroll_offset_.set_y(y);
+}
+
+gfx::Size OverflowBubbleView::GetPreferredSize() {
+ gfx::Size preferred_size = GetContentsSize();
+
+ const gfx::Rect monitor_rect = Shell::GetScreen()->GetDisplayNearestPoint(
+ GetAnchorRect().CenterPoint()).work_area();
+ if (!monitor_rect.IsEmpty()) {
+ if (IsHorizontalAlignment()) {
+ preferred_size.set_width(std::min(
+ preferred_size.width(),
+ static_cast<int>(monitor_rect.width() *
+ kMaxBubbleSizeToScreenRatio)));
+ } else {
+ preferred_size.set_height(std::min(
+ preferred_size.height(),
+ static_cast<int>(monitor_rect.height() *
+ kMaxBubbleSizeToScreenRatio)));
+ }
+ }
+
+ return preferred_size;
+}
+
+void OverflowBubbleView::Layout() {
+ launcher_view_->SetBoundsRect(gfx::Rect(
+ gfx::PointAtOffsetFromOrigin(-scroll_offset_), GetContentsSize()));
+}
+
+void OverflowBubbleView::ChildPreferredSizeChanged(views::View* child) {
+ // Ensures |launch_view_| is still visible.
+ ScrollByXOffset(0);
+ ScrollByYOffset(0);
+ Layout();
+
+ SizeToContents();
+}
+
+bool OverflowBubbleView::OnMouseWheel(const ui::MouseWheelEvent& event) {
+ // The MouseWheelEvent was changed to support both X and Y offsets
+ // recently, but the behavior of this function was retained to continue
+ // using Y offsets only. Might be good to simply scroll in both
+ // directions as in OverflowBubbleView::OnScrollEvent.
+ if (IsHorizontalAlignment())
+ ScrollByXOffset(-event.y_offset());
+ else
+ ScrollByYOffset(-event.y_offset());
+ Layout();
+
+ return true;
+}
+
+void OverflowBubbleView::OnScrollEvent(ui::ScrollEvent* event) {
+ ScrollByXOffset(-event->x_offset());
+ ScrollByYOffset(-event->y_offset());
+ Layout();
+ event->SetHandled();
+}
+
+gfx::Rect OverflowBubbleView::GetBubbleBounds() {
+ views::BubbleBorder* border = GetBubbleFrameView()->bubble_border();
+ gfx::Insets bubble_insets = border->GetInsets();
+
+ const int border_size =
+ views::BubbleBorder::is_arrow_on_horizontal(arrow()) ?
+ bubble_insets.left() : bubble_insets.top();
+ const int arrow_offset = border_size + kPadding + kLauncherViewLeadingInset +
+ ShelfLayoutManager::GetPreferredShelfSize() / 2;
+
+ const gfx::Size content_size = GetPreferredSize();
+ border->set_arrow_offset(arrow_offset);
+
+ const gfx::Rect anchor_rect = GetAnchorRect();
+ gfx::Rect bubble_rect = GetBubbleFrameView()->GetUpdatedWindowBounds(
+ anchor_rect,
+ content_size,
+ false);
+
+ gfx::Rect monitor_rect = Shell::GetScreen()->GetDisplayNearestPoint(
+ anchor_rect.CenterPoint()).work_area();
+
+ int offset = 0;
+ if (views::BubbleBorder::is_arrow_on_horizontal(arrow())) {
+ if (bubble_rect.x() < monitor_rect.x())
+ offset = monitor_rect.x() - bubble_rect.x();
+ else if (bubble_rect.right() > monitor_rect.right())
+ offset = monitor_rect.right() - bubble_rect.right();
+
+ bubble_rect.Offset(offset, 0);
+ border->set_arrow_offset(anchor_rect.CenterPoint().x() - bubble_rect.x());
+ } else {
+ if (bubble_rect.y() < monitor_rect.y())
+ offset = monitor_rect.y() - bubble_rect.y();
+ else if (bubble_rect.bottom() > monitor_rect.bottom())
+ offset = monitor_rect.bottom() - bubble_rect.bottom();
+
+ bubble_rect.Offset(0, offset);
+ border->set_arrow_offset(anchor_rect.CenterPoint().y() - bubble_rect.y());
+ }
+
+ GetBubbleFrameView()->SchedulePaint();
+ return bubble_rect;
+}
+
+} // namespace
+
+OverflowBubble::OverflowBubble()
+ : bubble_(NULL),
+ launcher_view_(NULL) {
+}
+
+OverflowBubble::~OverflowBubble() {
+ Hide();
+}
+
+void OverflowBubble::Show(views::View* anchor, LauncherView* launcher_view) {
+ Hide();
+
+ OverflowBubbleView* bubble_view = new OverflowBubbleView();
+ bubble_view->InitOverflowBubble(anchor, launcher_view);
+ launcher_view_ = launcher_view;
+
+ bubble_ = bubble_view;
+ RootWindowController::ForWindow(anchor->GetWidget()->GetNativeView())->
+ GetSystemTray()->InitializeBubbleAnimations(bubble_->GetWidget());
+ bubble_->GetWidget()->AddObserver(this);
+ bubble_->GetWidget()->Show();
+}
+
+void OverflowBubble::Hide() {
+ if (!IsShowing())
+ return;
+
+ bubble_->GetWidget()->RemoveObserver(this);
+ bubble_->GetWidget()->Close();
+ bubble_ = NULL;
+ launcher_view_ = NULL;
+}
+
+void OverflowBubble::OnWidgetDestroying(views::Widget* widget) {
+ DCHECK(widget == bubble_->GetWidget());
+ bubble_ = NULL;
+ launcher_view_ = NULL;
+ ShelfLayoutManager::ForLauncher(
+ widget->GetNativeView())->shelf_widget()->launcher()->SchedulePaint();
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/launcher/overflow_bubble.h b/chromium/ash/launcher/overflow_bubble.h
new file mode 100644
index 00000000000..84fbcc12649
--- /dev/null
+++ b/chromium/ash/launcher/overflow_bubble.h
@@ -0,0 +1,52 @@
+// Copyright (c) 2012 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 ASH_LAUNCHER_OVERFLOW_BUBBLE_H_
+#define ASH_LAUNCHER_OVERFLOW_BUBBLE_H_
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "ui/views/widget/widget_observer.h"
+
+namespace views {
+class View;
+}
+
+namespace ash {
+
+class LauncherDelegate;
+class LauncherModel;
+
+namespace internal {
+
+class LauncherView;
+
+// OverflowBubble displays the overflown launcher items in a bubble.
+class OverflowBubble : public views::WidgetObserver {
+ public:
+ OverflowBubble();
+ virtual ~OverflowBubble();
+
+ // Shows an bubble pointing to |anchor| with |launcher_view| as its content.
+ void Show(views::View* anchor, LauncherView* launcher_view);
+
+ void Hide();
+
+ bool IsShowing() const { return !!bubble_; }
+ LauncherView* launcher_view() { return launcher_view_; }
+
+ private:
+ // Overridden from views::WidgetObserver:
+ virtual void OnWidgetDestroying(views::Widget* widget) OVERRIDE;
+
+ views::View* bubble_; // Owned by views hierarchy.
+ LauncherView* launcher_view_; // Owned by |bubble_|.
+
+ DISALLOW_COPY_AND_ASSIGN(OverflowBubble);
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_LAUNCHER_OVERFLOW_BUBBLE_H_
diff --git a/chromium/ash/launcher/overflow_button.cc b/chromium/ash/launcher/overflow_button.cc
new file mode 100644
index 00000000000..c3d5a6891d0
--- /dev/null
+++ b/chromium/ash/launcher/overflow_button.cc
@@ -0,0 +1,178 @@
+// Copyright (c) 2012 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.
+
+#include "ash/launcher/overflow_button.h"
+
+#include "ash/ash_switches.h"
+#include "ash/shelf/shelf_layout_manager.h"
+#include "ash/shelf/shelf_widget.h"
+#include "grit/ash_resources.h"
+#include "grit/ash_strings.h"
+#include "third_party/skia/include/core/SkPaint.h"
+#include "third_party/skia/include/core/SkPath.h"
+#include "ui/base/animation/throb_animation.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "ui/gfx/canvas.h"
+#include "ui/gfx/image/image_skia_operations.h"
+#include "ui/gfx/skbitmap_operations.h"
+#include "ui/gfx/skia_util.h"
+#include "ui/gfx/transform.h"
+#include "ui/views/widget/widget.h"
+
+namespace ash {
+namespace internal {
+
+namespace {
+
+const int kButtonHoverAlpha = 150;
+
+const int kButtonCornerRadius = 2;
+
+const int kButtonHoverSize = 28;
+
+const int kBackgroundOffset = (48 - kButtonHoverSize) / 2;
+
+// Padding from the inner edge of the shelf (towards center of display) to
+// the edge of the background image of the overflow button.
+const int kImagePaddingFromShelf = 5;
+
+} // namesapce
+
+OverflowButton::OverflowButton(views::ButtonListener* listener)
+ : CustomButton(listener),
+ bottom_image_(NULL) {
+ ResourceBundle& rb = ResourceBundle::GetSharedInstance();
+ bottom_image_ = rb.GetImageNamed(IDR_AURA_LAUNCHER_OVERFLOW).ToImageSkia();
+
+
+ set_accessibility_focusable(true);
+ SetAccessibleName(l10n_util::GetStringUTF16(IDS_ASH_SHELF_OVERFLOW_NAME));
+}
+
+OverflowButton::~OverflowButton() {}
+
+void OverflowButton::OnShelfAlignmentChanged() {
+ SchedulePaint();
+}
+
+void OverflowButton::PaintBackground(gfx::Canvas* canvas, int alpha) {
+ gfx::Rect bounds(GetContentsBounds());
+ gfx::Rect rect(0, 0, kButtonHoverSize, kButtonHoverSize);
+ ShelfLayoutManager* shelf =
+ ShelfLayoutManager::ForLauncher(GetWidget()->GetNativeView());
+
+ // Nudge the background a little to line up right.
+ if (shelf->IsHorizontalAlignment()) {
+ rect.set_origin(gfx::Point(
+ bounds.x() + ((bounds.width() - kButtonHoverSize) / 2) - 1,
+ bounds.y() + kBackgroundOffset - 1));
+
+ } else {
+ rect.set_origin(gfx::Point(
+ bounds.x() + kBackgroundOffset - 1,
+ bounds.y() + ((bounds.height() - kButtonHoverSize) / 2) - 1));
+ }
+
+ SkPaint paint;
+ paint.setAntiAlias(true);
+ paint.setStyle(SkPaint::kFill_Style);
+ paint.setColor(SkColorSetARGB(
+ kButtonHoverAlpha * hover_animation_->GetCurrentValue(),
+ 0, 0, 0));
+
+ const SkScalar radius = SkIntToScalar(kButtonCornerRadius);
+ SkPath path;
+ path.addRoundRect(gfx::RectToSkRect(rect), radius, radius);
+ canvas->DrawPath(path, paint);
+}
+
+void OverflowButton::OnPaint(gfx::Canvas* canvas) {
+ ShelfLayoutManager* layout_manager = ShelfLayoutManager::ForLauncher(
+ GetWidget()->GetNativeView());
+ ShelfAlignment alignment = layout_manager->GetAlignment();
+
+ gfx::Rect bounds(GetContentsBounds());
+ if (ash::switches::UseAlternateShelfLayout()) {
+ ResourceBundle& rb = ResourceBundle::GetSharedInstance();
+ int background_image_id = 0;
+ if (layout_manager->shelf_widget()->launcher()->IsShowingOverflowBubble())
+ background_image_id = IDR_AURA_NOTIFICATION_BACKGROUND_PRESSED;
+ else if(layout_manager->shelf_widget()->GetDimsShelf())
+ background_image_id = IDR_AURA_NOTIFICATION_BACKGROUND_ON_BLACK;
+ else
+ background_image_id = IDR_AURA_NOTIFICATION_BACKGROUND_NORMAL;
+
+ const gfx::ImageSkia* background =
+ rb.GetImageNamed(background_image_id).ToImageSkia();
+ if (alignment == SHELF_ALIGNMENT_LEFT) {
+ bounds = gfx::Rect(
+ bounds.right() - background->width() - kImagePaddingFromShelf,
+ bounds.y() + (bounds.height() - background->height()) / 2,
+ background->width(), background->height());
+ } else if (alignment == SHELF_ALIGNMENT_RIGHT) {
+ bounds = gfx::Rect(
+ bounds.x() + kImagePaddingFromShelf,
+ bounds.y() + (bounds.height() - background->height()) / 2,
+ background->width(), background->height());
+ } else {
+ bounds = gfx::Rect(
+ bounds.x() + (bounds.width() - background->width()) / 2,
+ bounds.y() + kImagePaddingFromShelf,
+ background->width(), background->height());
+ }
+ canvas->DrawImageInt(*background, bounds.x(), bounds.y());
+ } else {
+ if (alignment == SHELF_ALIGNMENT_BOTTOM) {
+ bounds = gfx::Rect(
+ bounds.x() + ((bounds.width() - kButtonHoverSize) / 2) - 1,
+ bounds.y() + kBackgroundOffset - 1,
+ kButtonHoverSize, kButtonHoverSize);
+ } else {
+ bounds = gfx::Rect(
+ bounds.x() + kBackgroundOffset -1,
+ bounds.y() + ((bounds.height() - kButtonHoverSize) / 2) -1,
+ kButtonHoverSize, kButtonHoverSize);
+ }
+ if (hover_animation_->is_animating()) {
+ PaintBackground(
+ canvas,
+ kButtonHoverAlpha * hover_animation_->GetCurrentValue());
+ } else if (state() == STATE_HOVERED || state() == STATE_PRESSED) {
+ PaintBackground(canvas, kButtonHoverAlpha);
+ }
+ }
+
+ if (height() < kButtonHoverSize)
+ return;
+
+ const gfx::ImageSkia* image = NULL;
+
+ switch(alignment) {
+ case SHELF_ALIGNMENT_LEFT:
+ if (left_image_.isNull()) {
+ left_image_ = gfx::ImageSkiaOperations::CreateRotatedImage(
+ *bottom_image_, SkBitmapOperations::ROTATION_90_CW);
+ }
+ image = &left_image_;
+ break;
+ case SHELF_ALIGNMENT_RIGHT:
+ if (right_image_.isNull()) {
+ right_image_ = gfx::ImageSkiaOperations::CreateRotatedImage(
+ *bottom_image_, SkBitmapOperations::ROTATION_270_CW);
+ }
+ image = &right_image_;
+ break;
+ default:
+ image = bottom_image_;
+ break;
+ }
+
+ canvas->DrawImageInt(*image,
+ bounds.x() + ((bounds.width() - image->width()) / 2),
+ bounds.y() + ((bounds.height() - image->height()) / 2));
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/launcher/overflow_button.h b/chromium/ash/launcher/overflow_button.h
new file mode 100644
index 00000000000..0b225aaa29e
--- /dev/null
+++ b/chromium/ash/launcher/overflow_button.h
@@ -0,0 +1,44 @@
+// Copyright (c) 2012 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 ASH_LAUNCHER_OVERFLOW_BUTTON_H_
+#define ASH_LAUNCHER_OVERFLOW_BUTTON_H_
+
+#include "ash/shelf/shelf_types.h"
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "ui/gfx/image/image_skia.h"
+#include "ui/views/controls/button/custom_button.h"
+
+namespace ash {
+namespace internal {
+
+// Launcher overflow chevron button.
+class OverflowButton : public views::CustomButton {
+ public:
+ explicit OverflowButton(views::ButtonListener* listener);
+ virtual ~OverflowButton();
+
+ void OnShelfAlignmentChanged();
+
+ private:
+ void PaintBackground(gfx::Canvas* canvas, int alpha);
+
+ // views::View overrides:
+ virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE;
+
+ // Left and right images are rotations of bottom_image and are
+ // owned by the overflow button.
+ gfx::ImageSkia left_image_;
+ gfx::ImageSkia right_image_;
+ // Bottom image is owned by the resource bundle.
+ const gfx::ImageSkia* bottom_image_;
+
+ DISALLOW_COPY_AND_ASSIGN(OverflowButton);
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_LAUNCHER_OVERFLOW_BUTTON_H_
diff --git a/chromium/ash/launcher/scoped_observer_with_duplicated_sources.h b/chromium/ash/launcher/scoped_observer_with_duplicated_sources.h
new file mode 100644
index 00000000000..60b10f9fae8
--- /dev/null
+++ b/chromium/ash/launcher/scoped_observer_with_duplicated_sources.h
@@ -0,0 +1,70 @@
+// Copyright 2013 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 ASH_LAUNCHER_SCOPED_OBSERVER_WITH_DUPLICATED_SOURCES_H_
+#define ASH_LAUNCHER_SCOPED_OBSERVER_WITH_DUPLICATED_SOURCES_H_
+
+#include <map>
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+
+// ScopedObserverWithDuplicatedSources is used to keep track of the set of
+// sources an object has attached itself to as an observer. When
+// ScopedObserverWithDuplicatedSources is destroyed it removes the object as an
+// observer from all sources it has been added to.
+// ScopedObserverWithDuplicatedSources adds |observer| once for a particular
+// source, no matter how many times Add() is invoked. Additionaly |observer| is
+// only removed once Remove() is invoked the same number of times as Add().
+
+template <class Source, class Observer>
+class ScopedObserverWithDuplicatedSources {
+ public:
+ explicit ScopedObserverWithDuplicatedSources(Observer* observer)
+ : observer_(observer) {}
+
+ ~ScopedObserverWithDuplicatedSources() {
+ typename SourceToAddCountMap::const_iterator iter =
+ counted_sources_.begin();
+ for (; iter != counted_sources_.end(); ++iter)
+ iter->first->RemoveObserver(observer_);
+ }
+
+ // Adds the object passed to the constructor only once as an observer on
+ // |source|.
+ void Add(Source* source) {
+ if (counted_sources_.find(source) == counted_sources_.end())
+ source->AddObserver(observer_);
+ counted_sources_[source]++;
+ }
+
+ // Only remove the object passed to the constructor as an observer from
+ // |source| when Remove() is invoked the same number of times as Add().
+ void Remove(Source* source) {
+ typename SourceToAddCountMap::iterator iter =
+ counted_sources_.find(source);
+ DCHECK(iter != counted_sources_.end() && iter->second > 0);
+
+ if (--iter->second == 0) {
+ counted_sources_.erase(source);
+ source->RemoveObserver(observer_);
+ }
+ }
+
+ bool IsObserving(Source* source) const {
+ return counted_sources_.find(source) != counted_sources_.end();
+ }
+
+ private:
+ typedef std::map<Source*, int> SourceToAddCountMap;
+
+ Observer* observer_;
+
+ // Map Source and its adding count.
+ std::map<Source*, int> counted_sources_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScopedObserverWithDuplicatedSources);
+};
+
+#endif // ASH_LAUNCHER_SCOPED_OBSERVER_WITH_DUPLICATED_SOURCES_H_
diff --git a/chromium/ash/launcher/scoped_observer_with_duplicated_sources_unittest.cc b/chromium/ash/launcher/scoped_observer_with_duplicated_sources_unittest.cc
new file mode 100644
index 00000000000..d7d13f3602a
--- /dev/null
+++ b/chromium/ash/launcher/scoped_observer_with_duplicated_sources_unittest.cc
@@ -0,0 +1,82 @@
+// Copyright 2013 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.
+
+#include "ash/launcher/scoped_observer_with_duplicated_sources.h"
+
+#include "base/memory/scoped_ptr.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+class TestObserver {
+ public:
+ TestObserver() {}
+ ~TestObserver() {}
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(TestObserver);
+};
+
+class TestSource {
+ public:
+ TestSource() : observer_count_(0) {}
+ ~TestSource() {}
+
+ void AddObserver(TestObserver* observer) {
+ observer_count_++;
+ }
+ void RemoveObserver(TestObserver* observer) {
+ observer_count_--;
+ }
+
+ int GetObserverCount() {
+ return observer_count_;
+ }
+
+ private:
+ int observer_count_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestSource);
+};
+
+TEST(ScopedObserverWithDuplicatedSourcesTest, DuplicatedSource) {
+ TestObserver observer;
+ TestSource source1;
+ TestSource source2;
+
+ ScopedObserverWithDuplicatedSources<TestSource, TestObserver>
+ observers(&observer);
+ EXPECT_EQ(0, source1.GetObserverCount());
+ EXPECT_FALSE(observers.IsObserving(&source1));
+ EXPECT_EQ(0, source2.GetObserverCount());
+ EXPECT_FALSE(observers.IsObserving(&source2));
+
+ // Add |source1|.
+ observers.Add(&source1);
+ EXPECT_EQ(1, source1.GetObserverCount());
+ EXPECT_TRUE(observers.IsObserving(&source1));
+ // AddObserver of TestSource is called only once.
+ observers.Add(&source1);
+ EXPECT_EQ(1, source1.GetObserverCount());
+ EXPECT_TRUE(observers.IsObserving(&source1));
+
+ // Add |source2|.
+ observers.Add(&source2);
+ EXPECT_EQ(1, source2.GetObserverCount());
+ EXPECT_TRUE(observers.IsObserving(&source2));
+
+ // Remove |source1|.
+ observers.Remove(&source1);
+ EXPECT_EQ(1, source1.GetObserverCount());
+ EXPECT_TRUE(observers.IsObserving(&source1));
+
+ // Remove |source2|.
+ observers.Remove(&source2);
+ EXPECT_EQ(0, source2.GetObserverCount());
+ EXPECT_FALSE(observers.IsObserving(&source2));
+
+ // Remove |source1| again.
+ observers.Remove(&source1);
+ // In this time, |observer| is removed from |source1|.
+ EXPECT_EQ(0, source1.GetObserverCount());
+ EXPECT_FALSE(observers.IsObserving(&source1));
+}
diff --git a/chromium/ash/launcher/tabbed_launcher_button.cc b/chromium/ash/launcher/tabbed_launcher_button.cc
new file mode 100644
index 00000000000..517607c4ace
--- /dev/null
+++ b/chromium/ash/launcher/tabbed_launcher_button.cc
@@ -0,0 +1,153 @@
+// Copyright (c) 2012 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.
+
+#include "ash/launcher/tabbed_launcher_button.h"
+
+#include <algorithm>
+
+#include "ash/launcher/launcher_button_host.h"
+#include "ash/launcher/launcher_types.h"
+#include "grit/ash_resources.h"
+#include "ui/base/accessibility/accessible_view_state.h"
+#include "ui/base/animation/multi_animation.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "ui/gfx/canvas.h"
+#include "ui/gfx/image/image.h"
+#include "ui/gfx/insets.h"
+
+namespace {
+const int kIconOffsetY = 7;
+}
+
+namespace ash {
+namespace internal {
+
+TabbedLauncherButton::IconView::IconView(
+ TabbedLauncherButton* host)
+ : host_(host) {
+ if (!browser_image_) {
+ ResourceBundle& rb = ResourceBundle::GetSharedInstance();
+
+ browser_image_ = rb.GetImageNamed(IDR_AURA_LAUNCHER_BROWSER).ToImageSkia();
+ incognito_browser_image_ =
+ rb.GetImageNamed(IDR_AURA_LAUNCHER_INCOGNITO_BROWSER).ToImageSkia();
+ browser_panel_image_ =
+ rb.GetImageNamed(IDR_AURA_LAUNCHER_BROWSER_PANEL).ToImageSkia();
+ incognito_browser_panel_image_ =
+ rb.GetImageNamed(
+ IDR_AURA_LAUNCHER_INCOGNITO_BROWSER_PANEL).ToImageSkia();
+ }
+ set_icon_size(0);
+ if (host->is_incognito() == STATE_NOT_INCOGNITO)
+ LauncherButton::IconView::SetImage(*browser_image_);
+ else
+ LauncherButton::IconView::SetImage(*incognito_browser_image_);
+}
+
+TabbedLauncherButton::IconView::~IconView() {
+}
+
+void TabbedLauncherButton::IconView::AnimationEnded(
+ const ui::Animation* animation) {
+ AnimationProgressed(animation);
+ animating_image_ = gfx::ImageSkia();
+}
+
+void TabbedLauncherButton::IconView::AnimationProgressed(
+ const ui::Animation* animation) {
+ if (animation_->current_part_index() == 1)
+ SchedulePaint();
+}
+
+void TabbedLauncherButton::IconView::SetTabImage(const gfx::ImageSkia& image) {
+ if (image.isNull()) {
+ if (!image_.isNull()) {
+ // Pause for 500ms, then ease out for 200ms.
+ ui::MultiAnimation::Parts animation_parts;
+ animation_parts.push_back(ui::MultiAnimation::Part(500, ui::Tween::ZERO));
+ animation_parts.push_back(
+ ui::MultiAnimation::Part(200, ui::Tween::EASE_OUT));
+ animation_.reset(new ui::MultiAnimation(
+ animation_parts,
+ ui::MultiAnimation::GetDefaultTimerInterval()));
+ animation_->set_continuous(false);
+ animation_->set_delegate(this);
+ animation_->Start();
+ animating_image_ = image_;
+ image_ = image;
+ }
+ } else {
+ animation_.reset();
+ SchedulePaint();
+ image_ = image;
+ }
+}
+
+void TabbedLauncherButton::IconView::OnPaint(gfx::Canvas* canvas) {
+ LauncherButton::IconView::OnPaint(canvas);
+
+ // Only non incognito icons show the tab image.
+ if (host_->is_incognito() != STATE_NOT_INCOGNITO)
+ return;
+
+ if ((animation_.get() && animation_->is_animating() &&
+ animation_->current_part_index() == 1)) {
+ int x = (width() - animating_image_.width()) / 2;
+ canvas->SaveLayerAlpha(animation_->CurrentValueBetween(255, 0));
+ canvas->DrawImageInt(animating_image_, x, kIconOffsetY);
+ canvas->Restore();
+ } else {
+ int x = (width() - image_.width()) / 2;
+ canvas->DrawImageInt(image_, x, kIconOffsetY);
+ }
+}
+
+// static
+const gfx::ImageSkia* TabbedLauncherButton::IconView::browser_image_ = NULL;
+const gfx::ImageSkia* TabbedLauncherButton::IconView::incognito_browser_image_ =
+ NULL;
+const gfx::ImageSkia* TabbedLauncherButton::IconView::browser_panel_image_ =
+ NULL;
+const gfx::ImageSkia*
+ TabbedLauncherButton::IconView::incognito_browser_panel_image_ = NULL;
+
+TabbedLauncherButton* TabbedLauncherButton::Create(
+ views::ButtonListener* listener,
+ LauncherButtonHost* host,
+ ShelfLayoutManager* shelf_layout_manager,
+ IncognitoState is_incognito) {
+ TabbedLauncherButton* button = new TabbedLauncherButton(
+ listener, host, shelf_layout_manager, is_incognito);
+ button->Init();
+ return button;
+}
+
+TabbedLauncherButton::TabbedLauncherButton(
+ views::ButtonListener* listener,
+ LauncherButtonHost* host,
+ ShelfLayoutManager* shelf_layout_manager,
+ IncognitoState is_incognito)
+ : LauncherButton(listener, host, shelf_layout_manager),
+ is_incognito_(is_incognito) {
+ set_accessibility_focusable(true);
+}
+
+TabbedLauncherButton::~TabbedLauncherButton() {
+}
+
+void TabbedLauncherButton::SetTabImage(const gfx::ImageSkia& image) {
+ tabbed_icon_view()->SetTabImage(image);
+}
+
+void TabbedLauncherButton::GetAccessibleState(ui::AccessibleViewState* state) {
+ state->role = ui::AccessibilityTypes::ROLE_PUSHBUTTON;
+ state->name = host()->GetAccessibleName(this);
+}
+
+LauncherButton::IconView* TabbedLauncherButton::CreateIconView() {
+ return new IconView(this);
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/launcher/tabbed_launcher_button.h b/chromium/ash/launcher/tabbed_launcher_button.h
new file mode 100644
index 00000000000..0ac2c3ba8d9
--- /dev/null
+++ b/chromium/ash/launcher/tabbed_launcher_button.h
@@ -0,0 +1,112 @@
+// Copyright (c) 2012 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 ASH_LAUNCHER_TABBED_LAUNCHER_BUTTON_H_
+#define ASH_LAUNCHER_TABBED_LAUNCHER_BUTTON_H_
+
+#include "ash/launcher/launcher_button.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/timer/timer.h"
+#include "ui/base/animation/animation_delegate.h"
+#include "ui/views/controls/button/image_button.h"
+#include "ui/views/controls/glow_hover_controller.h"
+
+namespace gfx {
+class ImageSkia;
+}
+
+namespace ui {
+class MultiAnimation;
+}
+
+namespace ash {
+
+struct LauncherItem;
+
+namespace internal {
+
+// Button used for items on the launcher corresponding to tabbed windows.
+class TabbedLauncherButton : public LauncherButton {
+ public:
+ // Indicates if this button is incognito or not.
+ enum IncognitoState {
+ STATE_INCOGNITO,
+ STATE_NOT_INCOGNITO,
+ };
+
+ static TabbedLauncherButton* Create(views::ButtonListener* listener,
+ LauncherButtonHost* host,
+ ShelfLayoutManager* shelf_layout_manager,
+ IncognitoState is_incognito);
+ virtual ~TabbedLauncherButton();
+
+ // Sets the images to display for this entry.
+ void SetTabImage(const gfx::ImageSkia& image);
+
+ // This only defines how the icon is drawn. Do not use it for other purposes.
+ IncognitoState is_incognito() const { return is_incognito_; }
+
+ protected:
+ TabbedLauncherButton(views::ButtonListener* listener,
+ LauncherButtonHost* host,
+ ShelfLayoutManager* shelf_layout_manager,
+ IncognitoState is_incognito);
+ // View override.
+ virtual void GetAccessibleState(ui::AccessibleViewState* state) OVERRIDE;
+
+ // LauncherButton override.
+ virtual IconView* CreateIconView() OVERRIDE;
+
+ private:
+ // Used as the delegate for |animation_|.
+ class IconView : public LauncherButton::IconView,
+ public ui::AnimationDelegate {
+ public:
+ explicit IconView(TabbedLauncherButton* host);
+ virtual ~IconView();
+
+ // ui::AnimationDelegateImpl overrides:
+ virtual void AnimationEnded(const ui::Animation* animation) OVERRIDE;
+ virtual void AnimationProgressed(const ui::Animation* animation) OVERRIDE;
+
+ // Sets the image to display for this entry.
+ void SetTabImage(const gfx::ImageSkia& image);
+
+ protected:
+ // View override.
+ virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE;
+
+ private:
+ TabbedLauncherButton* host_;
+ gfx::ImageSkia image_;
+ gfx::ImageSkia animating_image_;
+
+ // Used to animate image.
+ scoped_ptr<ui::MultiAnimation> animation_;
+
+ // Background images. Which one is chosen depends on the type of the window.
+ static const gfx::ImageSkia* browser_image_;
+ static const gfx::ImageSkia* incognito_browser_image_;
+ // TODO[dave] implement panel specific image.
+ static const gfx::ImageSkia* browser_panel_image_;
+ static const gfx::ImageSkia* incognito_browser_panel_image_;
+
+ DISALLOW_COPY_AND_ASSIGN(IconView);
+ };
+
+ IconView* tabbed_icon_view() {
+ return static_cast<IconView*>(icon_view());
+ }
+
+ // Indicates how the icon is drawn. If true an Incognito symbol will be
+ // drawn. It does not necessarily indicate if the window is 'incognito'.
+ const IncognitoState is_incognito_;
+
+ DISALLOW_COPY_AND_ASSIGN(TabbedLauncherButton);
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_LAUNCHER_TABBED_LAUNCHER_BUTTON_H_
diff --git a/chromium/ash/magnifier/magnification_controller.cc b/chromium/ash/magnifier/magnification_controller.cc
new file mode 100644
index 00000000000..c8becce3f00
--- /dev/null
+++ b/chromium/ash/magnifier/magnification_controller.cc
@@ -0,0 +1,631 @@
+// Copyright (c) 2012 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.
+
+#include "ash/magnifier/magnification_controller.h"
+
+#include "ash/display/root_window_transformers.h"
+#include "ash/shell.h"
+#include "ash/shell_delegate.h"
+#include "ash/system/tray/system_tray_delegate.h"
+#include "base/synchronization/waitable_event.h"
+#include "ui/aura/client/cursor_client.h"
+#include "ui/aura/root_window.h"
+#include "ui/aura/root_window_transformer.h"
+#include "ui/aura/window.h"
+#include "ui/aura/window_property.h"
+#include "ui/base/events/event.h"
+#include "ui/base/events/event_handler.h"
+#include "ui/compositor/dip_util.h"
+#include "ui/compositor/layer.h"
+#include "ui/compositor/layer_animation_observer.h"
+#include "ui/compositor/scoped_layer_animation_settings.h"
+#include "ui/gfx/point3_f.h"
+#include "ui/gfx/point_conversions.h"
+#include "ui/gfx/point_f.h"
+#include "ui/gfx/rect_conversions.h"
+#include "ui/gfx/screen.h"
+#include "ui/views/corewm/compound_event_filter.h"
+
+namespace {
+
+const float kMaxMagnifiedScale = 4.0f;
+const float kMaxMagnifiedScaleThreshold = 4.0f;
+const float kMinMagnifiedScaleThreshold = 1.1f;
+const float kNonMagnifiedScale = 1.0f;
+
+const float kInitialMagnifiedScale = 2.0f;
+const float kScrollScaleChangeFactor = 0.05f;
+
+// Threadshold of panning. If the cursor moves to within pixels (in DIP) of
+// |kPanningMergin| from the edge, the view-port moves.
+const int kPanningMergin = 100;
+
+void MoveCursorTo(aura::RootWindow* root_window,
+ const gfx::Point& root_location) {
+ gfx::Point3F host_location_3f(root_location);
+ root_window->GetRootTransform().TransformPoint(host_location_3f);
+ root_window->MoveCursorToHostLocation(
+ gfx::ToCeiledPoint(host_location_3f.AsPointF()));
+}
+
+} // namespace
+
+namespace ash {
+
+////////////////////////////////////////////////////////////////////////////////
+// MagnificationControllerImpl:
+
+class MagnificationControllerImpl : virtual public MagnificationController,
+ public ui::EventHandler,
+ public ui::ImplicitAnimationObserver,
+ public aura::WindowObserver {
+ public:
+ MagnificationControllerImpl();
+ virtual ~MagnificationControllerImpl();
+
+ // MagnificationController overrides:
+ virtual void SetEnabled(bool enabled) OVERRIDE;
+ virtual bool IsEnabled() const OVERRIDE;
+ virtual void SetScale(float scale, bool animate) OVERRIDE;
+ virtual float GetScale() const OVERRIDE { return scale_; }
+ virtual void MoveWindow(int x, int y, bool animate) OVERRIDE;
+ virtual void MoveWindow(const gfx::Point& point, bool animate) OVERRIDE;
+ virtual gfx::Point GetWindowPosition() const OVERRIDE {
+ return gfx::ToFlooredPoint(origin_);
+ }
+ virtual void EnsureRectIsVisible(const gfx::Rect& rect,
+ bool animate) OVERRIDE;
+ virtual void EnsurePointIsVisible(const gfx::Point& point,
+ bool animate) OVERRIDE;
+ // For test
+ virtual gfx::Point GetPointOfInterestForTesting() OVERRIDE {
+ return point_of_interest_;
+ }
+
+ private:
+ // ui::ImplicitAnimationObserver overrides:
+ virtual void OnImplicitAnimationsCompleted() OVERRIDE;
+
+ // aura::WindowObserver overrides:
+ virtual void OnWindowDestroying(aura::Window* root_window) OVERRIDE;
+ virtual void OnWindowBoundsChanged(aura::Window* window,
+ const gfx::Rect& old_bounds,
+ const gfx::Rect& new_bounds) OVERRIDE;
+
+ // Redraws the magnification window with the given origin position and the
+ // given scale. Returns true if the window is changed; otherwise, false.
+ // These methods should be called internally just after the scale and/or
+ // the position are changed to redraw the window.
+ bool Redraw(const gfx::PointF& position, float scale, bool animate);
+ bool RedrawDIP(const gfx::PointF& position, float scale, bool animate);
+
+ // Redraw with the given zoom scale keeping the mouse cursor location. In
+ // other words, zoom (or unzoom) centering around the cursor.
+ void RedrawKeepingMousePosition(float scale, bool animate);
+
+ // Ensures that the given point, rect or last mouse location is inside
+ // magnification window. If not, the controller moves the window to contain
+ // the given point/rect.
+ void EnsureRectIsVisibleWithScale(const gfx::Rect& target_rect,
+ float scale,
+ bool animate);
+ void EnsureRectIsVisibleDIP(const gfx::Rect& target_rect_in_dip,
+ float scale,
+ bool animate);
+ void EnsurePointIsVisibleWithScale(const gfx::Point& point,
+ float scale,
+ bool animate);
+ void OnMouseMove(const gfx::Point& location);
+
+ // Move the mouse cursot to the given point. Actual move will be done when
+ // the animation is completed. This should be called after animation is
+ // started.
+ void AfterAnimationMoveCursorTo(const gfx::Point& location);
+
+ // Switch Magnified RootWindow to |new_root_window|. This does following:
+ // - Unzoom the current root_window.
+ // - Zoom the given new root_window |new_root_window|.
+ // - Switch the target window from current window to |new_root_window|.
+ void SwitchTargetRootWindow(aura::RootWindow* new_root_window,
+ bool redraw_original_root_window);
+
+ // Returns if the magnification scale is 1.0 or not (larger then 1.0).
+ bool IsMagnified() const;
+
+ // Returns the rect of the magnification window.
+ gfx::RectF GetWindowRectDIP(float scale) const;
+ // Returns the size of the root window.
+ gfx::Size GetHostSizeDIP() const;
+
+ // Correct the givin scale value if nessesary.
+ void ValidateScale(float* scale);
+
+ // ui::EventHandler overrides:
+ virtual void OnMouseEvent(ui::MouseEvent* event) OVERRIDE;
+ virtual void OnScrollEvent(ui::ScrollEvent* event) OVERRIDE;
+ virtual void OnTouchEvent(ui::TouchEvent* event) OVERRIDE;
+
+ // Target root window. This must not be NULL.
+ aura::RootWindow* root_window_;
+
+ // True if the magnified window is currently animating a change. Otherwise,
+ // false.
+ bool is_on_animation_;
+
+ bool is_enabled_;
+
+ // True if the cursor needs to move the given position after the animation
+ // will be finished. When using this, set |position_after_animation_| as well.
+ bool move_cursor_after_animation_;
+ // Stores the position of cursor to be moved after animation.
+ gfx::Point position_after_animation_;
+
+ // Stores the last mouse cursor (or last touched) location. This value is
+ // used on zooming to keep this location visible.
+ gfx::Point point_of_interest_;
+
+ // Current scale, origin (left-top) position of the magnification window.
+ float scale_;
+ gfx::PointF origin_;
+
+ DISALLOW_COPY_AND_ASSIGN(MagnificationControllerImpl);
+};
+
+////////////////////////////////////////////////////////////////////////////////
+// MagnificationControllerImpl:
+
+MagnificationControllerImpl::MagnificationControllerImpl()
+ : root_window_(ash::Shell::GetPrimaryRootWindow()),
+ is_on_animation_(false),
+ is_enabled_(false),
+ move_cursor_after_animation_(false),
+ scale_(kNonMagnifiedScale) {
+ Shell::GetInstance()->AddPreTargetHandler(this);
+ root_window_->AddObserver(this);
+ point_of_interest_ = root_window_->bounds().CenterPoint();
+}
+
+MagnificationControllerImpl::~MagnificationControllerImpl() {
+ root_window_->RemoveObserver(this);
+
+ Shell::GetInstance()->RemovePreTargetHandler(this);
+}
+
+void MagnificationControllerImpl::RedrawKeepingMousePosition(
+ float scale, bool animate) {
+ gfx::Point mouse_in_root = point_of_interest_;
+
+ // mouse_in_root is invalid value when the cursor is hidden.
+ if (!root_window_->bounds().Contains(mouse_in_root))
+ mouse_in_root = root_window_->bounds().CenterPoint();
+
+ const gfx::PointF origin =
+ gfx::PointF(mouse_in_root.x() -
+ (scale_ / scale) * (mouse_in_root.x() - origin_.x()),
+ mouse_in_root.y() -
+ (scale_ / scale) * (mouse_in_root.y() - origin_.y()));
+ bool changed = RedrawDIP(origin, scale, animate);
+ if (changed)
+ AfterAnimationMoveCursorTo(mouse_in_root);
+}
+
+bool MagnificationControllerImpl::Redraw(const gfx::PointF& position,
+ float scale,
+ bool animate) {
+ const gfx::PointF position_in_dip =
+ ui::ConvertPointToDIP(root_window_->layer(), position);
+ return RedrawDIP(position_in_dip, scale, animate);
+}
+
+bool MagnificationControllerImpl::RedrawDIP(const gfx::PointF& position_in_dip,
+ float scale,
+ bool animate) {
+ DCHECK(root_window_);
+
+ float x = position_in_dip.x();
+ float y = position_in_dip.y();
+
+ ValidateScale(&scale);
+
+ if (x < 0)
+ x = 0;
+ if (y < 0)
+ y = 0;
+
+ const gfx::Size host_size_in_dip = GetHostSizeDIP();
+ const gfx::SizeF window_size_in_dip = GetWindowRectDIP(scale).size();
+ float max_x = host_size_in_dip.width() - window_size_in_dip.width();
+ float max_y = host_size_in_dip.height() - window_size_in_dip.height();
+ if (x > max_x)
+ x = max_x;
+ if (y > max_y)
+ y = max_y;
+
+ // Does nothing if both the origin and the scale are not changed.
+ if (origin_.x() == x &&
+ origin_.y() == y &&
+ scale == scale_) {
+ return false;
+ }
+
+ origin_.set_x(x);
+ origin_.set_y(y);
+ scale_ = scale;
+
+ // Creates transform matrix.
+ gfx::Transform transform;
+ // Flips the signs intentionally to convert them from the position of the
+ // magnification window.
+ transform.Scale(scale_, scale_);
+ transform.Translate(-origin_.x(), -origin_.y());
+
+ ui::ScopedLayerAnimationSettings settings(
+ root_window_->layer()->GetAnimator());
+ settings.AddObserver(this);
+ settings.SetPreemptionStrategy(
+ ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
+ settings.SetTweenType(ui::Tween::EASE_OUT);
+ settings.SetTransitionDuration(
+ base::TimeDelta::FromMilliseconds(animate ? 100 : 0));
+
+ gfx::Display display =
+ Shell::GetScreen()->GetDisplayNearestWindow(root_window_);
+ scoped_ptr<aura::RootWindowTransformer> transformer(
+ internal::CreateRootWindowTransformerForDisplay(root_window_, display));
+ root_window_->SetRootWindowTransformer(transformer.Pass());
+
+ if (animate)
+ is_on_animation_ = true;
+
+ return true;
+}
+
+void MagnificationControllerImpl::EnsureRectIsVisibleWithScale(
+ const gfx::Rect& target_rect,
+ float scale,
+ bool animate) {
+ const gfx::Rect target_rect_in_dip =
+ ui::ConvertRectToDIP(root_window_->layer(), target_rect);
+ EnsureRectIsVisibleDIP(target_rect_in_dip, scale, animate);
+}
+
+void MagnificationControllerImpl::EnsureRectIsVisibleDIP(
+ const gfx::Rect& target_rect,
+ float scale,
+ bool animate) {
+ ValidateScale(&scale);
+
+ const gfx::Rect window_rect = gfx::ToEnclosingRect(GetWindowRectDIP(scale));
+ if (scale == scale_ && window_rect.Contains(target_rect))
+ return;
+
+ // TODO(yoshiki): Un-zoom and change the scale if the magnification window
+ // can't contain the whole given rect.
+
+ gfx::Rect rect = window_rect;
+ if (target_rect.width() > rect.width())
+ rect.set_x(target_rect.CenterPoint().x() - rect.x() / 2);
+ else if (target_rect.right() < rect.x())
+ rect.set_x(target_rect.right());
+ else if (rect.right() < target_rect.x())
+ rect.set_x(target_rect.x() - rect.width());
+
+ if (rect.height() > window_rect.height())
+ rect.set_y(target_rect.CenterPoint().y() - rect.y() / 2);
+ else if (target_rect.bottom() < rect.y())
+ rect.set_y(target_rect.bottom());
+ else if (rect.bottom() < target_rect.y())
+ rect.set_y(target_rect.y() - rect.height());
+
+ RedrawDIP(rect.origin(), scale, animate);
+}
+
+void MagnificationControllerImpl::EnsurePointIsVisibleWithScale(
+ const gfx::Point& point,
+ float scale,
+ bool animate) {
+ EnsureRectIsVisibleWithScale(gfx::Rect(point, gfx::Size(0, 0)),
+ scale,
+ animate);
+}
+
+void MagnificationControllerImpl::OnMouseMove(const gfx::Point& location) {
+ DCHECK(root_window_);
+
+ gfx::Point mouse(location);
+
+ int x = origin_.x();
+ int y = origin_.y();
+ bool start_zoom = false;
+
+ const gfx::Rect window_rect = gfx::ToEnclosingRect(GetWindowRectDIP(scale_));
+ const int left = window_rect.x();
+ const int right = window_rect.right();
+ int margin = kPanningMergin / scale_; // No need to consider DPI.
+
+ int x_diff = 0;
+
+ if (mouse.x() < left + margin) {
+ // Panning left.
+ x_diff = mouse.x() - (left + margin);
+ start_zoom = true;
+ } else if (right - margin < mouse.x()) {
+ // Panning right.
+ x_diff = mouse.x() - (right - margin);
+ start_zoom = true;
+ }
+ x = left + x_diff;
+
+ const int top = window_rect.y();
+ const int bottom = window_rect.bottom();
+
+ int y_diff = 0;
+ if (mouse.y() < top + margin) {
+ // Panning up.
+ y_diff = mouse.y() - (top + margin);
+ start_zoom = true;
+ } else if (bottom - margin < mouse.y()) {
+ // Panning down.
+ y_diff = mouse.y() - (bottom - margin);
+ start_zoom = true;
+ }
+ y = top + y_diff;
+
+ if (start_zoom && !is_on_animation_) {
+ // No animation on panning.
+ bool animate = false;
+ bool ret = RedrawDIP(gfx::Point(x, y), scale_, animate);
+
+ if (ret) {
+ // If the magnified region is moved, hides the mouse cursor and moves it.
+ if (x_diff != 0 || y_diff != 0)
+ MoveCursorTo(root_window_, mouse);
+ }
+ }
+}
+
+void MagnificationControllerImpl::AfterAnimationMoveCursorTo(
+ const gfx::Point& location) {
+ DCHECK(root_window_);
+
+ aura::client::CursorClient* cursor_client =
+ aura::client::GetCursorClient(root_window_);
+ if (cursor_client) {
+ // When cursor is invisible, do not move or show the cursor after the
+ // animation.
+ if (!cursor_client->IsCursorVisible())
+ return;
+ cursor_client->DisableMouseEvents();
+ }
+ move_cursor_after_animation_ = true;
+ position_after_animation_ = location;
+}
+
+gfx::Size MagnificationControllerImpl::GetHostSizeDIP() const {
+ return root_window_->bounds().size();
+}
+
+gfx::RectF MagnificationControllerImpl::GetWindowRectDIP(float scale) const {
+ const gfx::Size size_in_dip = root_window_->bounds().size();
+ const float width = size_in_dip.width() / scale;
+ const float height = size_in_dip.height() / scale;
+
+ return gfx::RectF(origin_.x(), origin_.y(), width, height);
+}
+
+bool MagnificationControllerImpl::IsMagnified() const {
+ return scale_ >= kMinMagnifiedScaleThreshold;
+}
+
+void MagnificationControllerImpl::ValidateScale(float* scale) {
+ // Adjust the scale to just |kNonMagnifiedScale| if scale is smaller than
+ // |kMinMagnifiedScaleThreshold|;
+ if (*scale < kMinMagnifiedScaleThreshold)
+ *scale = kNonMagnifiedScale;
+
+ // Adjust the scale to just |kMinMagnifiedScale| if scale is bigger than
+ // |kMinMagnifiedScaleThreshold|;
+ if (*scale > kMaxMagnifiedScaleThreshold)
+ *scale = kMaxMagnifiedScale;
+
+ DCHECK(kNonMagnifiedScale <= *scale && *scale <= kMaxMagnifiedScale);
+}
+
+void MagnificationControllerImpl::OnImplicitAnimationsCompleted() {
+ if (!is_on_animation_)
+ return;
+
+ if (move_cursor_after_animation_) {
+ MoveCursorTo(root_window_, position_after_animation_);
+ move_cursor_after_animation_ = false;
+
+ aura::client::CursorClient* cursor_client =
+ aura::client::GetCursorClient(root_window_);
+ if (cursor_client)
+ cursor_client->EnableMouseEvents();
+ }
+
+ is_on_animation_ = false;
+}
+
+void MagnificationControllerImpl::OnWindowDestroying(
+ aura::Window* root_window) {
+ if (root_window == root_window_) {
+ // There must be at least one root window because this controller is
+ // destroyed before the root windows get destroyed.
+ DCHECK(root_window);
+
+ aura::RootWindow* active_root_window = Shell::GetActiveRootWindow();
+ CHECK(active_root_window);
+
+ // The destroyed root window must not be active.
+ CHECK_NE(active_root_window, root_window);
+ // Don't redraw the old root window as it's being destroyed.
+ SwitchTargetRootWindow(active_root_window, false);
+ point_of_interest_ = active_root_window->bounds().CenterPoint();
+ }
+}
+
+void MagnificationControllerImpl::OnWindowBoundsChanged(
+ aura::Window* window,
+ const gfx::Rect& old_bounds,
+ const gfx::Rect& new_bounds) {
+ // TODO(yoshiki): implement here. crbug.com/230979
+}
+
+void MagnificationControllerImpl::SwitchTargetRootWindow(
+ aura::RootWindow* new_root_window,
+ bool redraw_original_root_window) {
+ DCHECK(new_root_window);
+
+ if (new_root_window == root_window_)
+ return;
+
+ // Stores the previous scale.
+ float scale = GetScale();
+
+ // Unmagnify the previous root window.
+ root_window_->RemoveObserver(this);
+ if (redraw_original_root_window)
+ RedrawKeepingMousePosition(1.0f, true);
+
+ root_window_ = new_root_window;
+ RedrawKeepingMousePosition(scale, true);
+ root_window_->AddObserver(this);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// MagnificationControllerImpl: MagnificationController implementation
+
+void MagnificationControllerImpl::SetScale(float scale, bool animate) {
+ if (!is_enabled_)
+ return;
+
+ ValidateScale(&scale);
+ ash::Shell::GetInstance()->delegate()->SaveScreenMagnifierScale(scale);
+ RedrawKeepingMousePosition(scale, animate);
+}
+
+void MagnificationControllerImpl::MoveWindow(int x, int y, bool animate) {
+ if (!is_enabled_)
+ return;
+
+ Redraw(gfx::Point(x, y), scale_, animate);
+}
+
+void MagnificationControllerImpl::MoveWindow(const gfx::Point& point,
+ bool animate) {
+ if (!is_enabled_)
+ return;
+
+ Redraw(point, scale_, animate);
+}
+
+void MagnificationControllerImpl::EnsureRectIsVisible(
+ const gfx::Rect& target_rect,
+ bool animate) {
+ if (!is_enabled_)
+ return;
+
+ EnsureRectIsVisibleWithScale(target_rect, scale_, animate);
+}
+
+void MagnificationControllerImpl::EnsurePointIsVisible(
+ const gfx::Point& point,
+ bool animate) {
+ if (!is_enabled_)
+ return;
+
+ EnsurePointIsVisibleWithScale(point, scale_, animate);
+}
+
+void MagnificationControllerImpl::SetEnabled(bool enabled) {
+ if (enabled) {
+ float scale =
+ ash::Shell::GetInstance()->delegate()->GetSavedScreenMagnifierScale();
+ if (scale <= 0.0f)
+ scale = kInitialMagnifiedScale;
+ ValidateScale(&scale);
+
+ // Do nothing, if already enabled with same scale.
+ if (is_enabled_ && scale == scale_)
+ return;
+
+ is_enabled_ = enabled;
+ RedrawKeepingMousePosition(scale, true);
+ ash::Shell::GetInstance()->delegate()->SaveScreenMagnifierScale(scale);
+ } else {
+ // Do nothing, if already disabled.
+ if (!is_enabled_)
+ return;
+
+ RedrawKeepingMousePosition(kNonMagnifiedScale, true);
+ is_enabled_ = enabled;
+ }
+}
+
+bool MagnificationControllerImpl::IsEnabled() const {
+ return is_enabled_;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// MagnificationControllerImpl: aura::EventFilter implementation
+
+void MagnificationControllerImpl::OnMouseEvent(ui::MouseEvent* event) {
+ aura::Window* target = static_cast<aura::Window*>(event->target());
+ aura::RootWindow* current_root = target->GetRootWindow();
+ gfx::Rect root_bounds = current_root->bounds();
+
+ if (root_bounds.Contains(event->root_location())) {
+ // This must be before |SwitchTargetRootWindow()|.
+ point_of_interest_ = event->root_location();
+
+ if (current_root != root_window_) {
+ DCHECK(current_root);
+ SwitchTargetRootWindow(current_root, true);
+ }
+
+ if (IsMagnified() && event->type() == ui::ET_MOUSE_MOVED)
+ OnMouseMove(event->root_location());
+ }
+}
+
+void MagnificationControllerImpl::OnScrollEvent(ui::ScrollEvent* event) {
+ if (event->IsAltDown() && event->IsControlDown()) {
+ if (event->type() == ui::ET_SCROLL_FLING_START ||
+ event->type() == ui::ET_SCROLL_FLING_CANCEL) {
+ event->StopPropagation();
+ return;
+ }
+
+ if (event->type() == ui::ET_SCROLL) {
+ ui::ScrollEvent* scroll_event = static_cast<ui::ScrollEvent*>(event);
+ float scale = GetScale();
+ scale += scroll_event->y_offset() * kScrollScaleChangeFactor;
+ SetScale(scale, true);
+ event->StopPropagation();
+ return;
+ }
+ }
+}
+
+void MagnificationControllerImpl::OnTouchEvent(ui::TouchEvent* event) {
+ aura::Window* target = static_cast<aura::Window*>(event->target());
+ aura::RootWindow* current_root = target->GetRootWindow();
+ if (current_root == root_window_) {
+ gfx::Rect root_bounds = current_root->bounds();
+ if (root_bounds.Contains(event->root_location()))
+ point_of_interest_ = event->root_location();
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// MagnificationController:
+
+// static
+MagnificationController* MagnificationController::CreateInstance() {
+ return new MagnificationControllerImpl();
+}
+
+} // namespace ash
diff --git a/chromium/ash/magnifier/magnification_controller.h b/chromium/ash/magnifier/magnification_controller.h
new file mode 100644
index 00000000000..75d1b2f8f3d
--- /dev/null
+++ b/chromium/ash/magnifier/magnification_controller.h
@@ -0,0 +1,61 @@
+// Copyright (c) 2012 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 ASH_MAGNIFIER_MAGNIFICATION_CONTROLLER_H_
+#define ASH_MAGNIFIER_MAGNIFICATION_CONTROLLER_H_
+
+#include "base/compiler_specific.h"
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "ui/gfx/point.h"
+#include "ui/gfx/rect.h"
+
+namespace aura {
+class RootWindow;
+}
+
+namespace ash {
+
+class MagnificationController {
+ public:
+ virtual ~MagnificationController() {}
+
+ // Creates a new MagnificationController. The caller takes ownership of the
+ // returned object.
+ static MagnificationController* CreateInstance();
+
+ // Enables (or disables if |enabled| is false) screen magnifier feature.
+ virtual void SetEnabled(bool enabled) = 0;
+
+ // Returns if the screen magnifier is enabled or not.
+ virtual bool IsEnabled() const = 0;
+
+ // Sets the magnification ratio. 1.0f means no magnification.
+ virtual void SetScale(float scale, bool animate) = 0;
+ // Returns the current magnification ratio.
+ virtual float GetScale() const = 0;
+
+ // Set the top-left point of the magnification window.
+ virtual void MoveWindow(int x, int y, bool animate) = 0;
+ virtual void MoveWindow(const gfx::Point& point, bool animate) = 0;
+ // Returns the current top-left point of the magnification window.
+ virtual gfx::Point GetWindowPosition() const = 0;
+
+ // Ensures that the given point/rect is inside the magnification window. If
+ // not, the controller moves the window to contain the given point/rect.
+ virtual void EnsureRectIsVisible(const gfx::Rect& rect, bool animate) = 0;
+ virtual void EnsurePointIsVisible(const gfx::Point& point, bool animate) = 0;
+
+ // Returns |point_of_interest_| in MagnificationControllerImpl. This is
+ // the internal variable to stores the last mouse cursor (or last touched)
+ // location. This method is only for test purpose.
+ virtual gfx::Point GetPointOfInterestForTesting() = 0;
+
+ protected:
+ MagnificationController() {}
+};
+
+} // namespace ash
+
+#endif // ASH_MAGNIFIER_MAGNIFICATION_CONTROLLER_H_
diff --git a/chromium/ash/magnifier/magnification_controller_unittest.cc b/chromium/ash/magnifier/magnification_controller_unittest.cc
new file mode 100644
index 00000000000..f3695868009
--- /dev/null
+++ b/chromium/ash/magnifier/magnification_controller_unittest.cc
@@ -0,0 +1,442 @@
+// Copyright (c) 2013 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.
+
+#include "ash/magnifier/magnification_controller.h"
+#include "ash/magnifier/magnifier_constants.h"
+#include "ash/shell.h"
+#include "ash/test/ash_test_base.h"
+#include "base/strings/stringprintf.h"
+#include "ui/aura/client/aura_constants.h"
+#include "ui/aura/env.h"
+#include "ui/aura/root_window.h"
+#include "ui/aura/test/event_generator.h"
+#include "ui/gfx/rect_conversions.h"
+#include "ui/gfx/screen.h"
+
+namespace ash {
+namespace internal {
+namespace {
+
+const int kRootHeight = 600;
+const int kRootWidth = 800;
+
+} // namespace
+
+class MagnificationControllerTest: public test::AshTestBase {
+ public:
+ MagnificationControllerTest() {}
+ virtual ~MagnificationControllerTest() {}
+
+ virtual void SetUp() OVERRIDE {
+ AshTestBase::SetUp();
+ UpdateDisplay(base::StringPrintf("%dx%d", kRootWidth, kRootHeight));
+
+ aura::RootWindow* root = GetRootWindow();
+ gfx::Rect root_bounds(root->bounds());
+
+#if defined(OS_WIN)
+ // RootWindow and Display can't resize on Windows Ash.
+ // http://crbug.com/165962
+ EXPECT_EQ(kRootHeight, root_bounds.height());
+ EXPECT_EQ(kRootWidth, root_bounds.width());
+#endif
+ }
+
+ virtual void TearDown() OVERRIDE {
+ AshTestBase::TearDown();
+ }
+
+ protected:
+ aura::RootWindow* GetRootWindow() const {
+ return Shell::GetPrimaryRootWindow();
+ }
+
+ std::string GetHostMouseLocation() {
+ gfx::Point point;
+ GetRootWindow()->QueryMouseLocationForTest(&point);
+ return point.ToString();
+ }
+
+ ash::MagnificationController* GetMagnificationController() const {
+ return ash::Shell::GetInstance()->magnification_controller();
+ }
+
+ gfx::Rect GetViewport() const {
+ gfx::RectF bounds(0, 0, kRootWidth, kRootHeight);
+ GetRootWindow()->layer()->transform().TransformRectReverse(&bounds);
+ return gfx::ToEnclosingRect(bounds);
+ }
+
+ std::string CurrentPointOfInterest() const {
+ return GetMagnificationController()->
+ GetPointOfInterestForTesting().ToString();
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MagnificationControllerTest);
+};
+
+TEST_F(MagnificationControllerTest, EnableAndDisable) {
+ // Confirms the magnifier is disabled.
+ EXPECT_TRUE(GetRootWindow()->layer()->transform().IsIdentity());
+ EXPECT_EQ(1.0f, GetMagnificationController()->GetScale());
+ EXPECT_EQ("0,0 800x600", GetViewport().ToString());
+
+ // Enables magnifier.
+ GetMagnificationController()->SetEnabled(true);
+ EXPECT_FALSE(GetRootWindow()->layer()->transform().IsIdentity());
+ EXPECT_EQ(2.0f, GetMagnificationController()->GetScale());
+ EXPECT_EQ("200,150 400x300", GetViewport().ToString());
+
+ // Disables magnifier.
+ GetMagnificationController()->SetEnabled(false);
+ EXPECT_TRUE(GetRootWindow()->layer()->transform().IsIdentity());
+ EXPECT_EQ(1.0f, GetMagnificationController()->GetScale());
+ EXPECT_EQ("0,0 800x600", GetViewport().ToString());
+
+ // Confirms the the scale can't be changed.
+ GetMagnificationController()->SetScale(4.0f, false);
+ EXPECT_TRUE(GetRootWindow()->layer()->transform().IsIdentity());
+ EXPECT_EQ(1.0f, GetMagnificationController()->GetScale());
+ EXPECT_EQ("0,0 800x600", GetViewport().ToString());
+}
+
+TEST_F(MagnificationControllerTest, MagnifyAndUnmagnify) {
+ // Enables magnifier and confirms the default scale is 2.0x.
+ GetMagnificationController()->SetEnabled(true);
+ EXPECT_FALSE(GetRootWindow()->layer()->transform().IsIdentity());
+ EXPECT_EQ(2.0f, GetMagnificationController()->GetScale());
+ EXPECT_EQ("200,150 400x300", GetViewport().ToString());
+ EXPECT_EQ("400,300", CurrentPointOfInterest());
+
+ // Changes the scale.
+ GetMagnificationController()->SetScale(4.0f, false);
+ EXPECT_EQ(4.0f, GetMagnificationController()->GetScale());
+ EXPECT_EQ("300,225 200x150", GetViewport().ToString());
+ EXPECT_EQ("400,300", CurrentPointOfInterest());
+
+ GetMagnificationController()->SetScale(1.0f, false);
+ EXPECT_EQ(1.0f, GetMagnificationController()->GetScale());
+ EXPECT_EQ("0,0 800x600", GetViewport().ToString());
+ EXPECT_EQ("400,300", CurrentPointOfInterest());
+
+ GetMagnificationController()->SetScale(3.0f, false);
+ EXPECT_EQ(3.0f, GetMagnificationController()->GetScale());
+ EXPECT_EQ("266,200 267x200", GetViewport().ToString());
+ EXPECT_EQ("400,300", CurrentPointOfInterest());
+}
+
+TEST_F(MagnificationControllerTest, MoveWindow) {
+ // Enables magnifier and confirm the viewport is at center.
+ GetMagnificationController()->SetEnabled(true);
+ EXPECT_EQ(2.0f, GetMagnificationController()->GetScale());
+ EXPECT_EQ("200,150 400x300", GetViewport().ToString());
+
+ // Move the viewport.
+ GetMagnificationController()->MoveWindow(0, 0, false);
+ EXPECT_EQ("0,0 400x300", GetViewport().ToString());
+
+ GetMagnificationController()->MoveWindow(200, 300, false);
+ EXPECT_EQ("200,300 400x300", GetViewport().ToString());
+
+ GetMagnificationController()->MoveWindow(400, 0, false);
+ EXPECT_EQ("400,0 400x300", GetViewport().ToString());
+
+ GetMagnificationController()->MoveWindow(400, 300, false);
+ EXPECT_EQ("400,300 400x300", GetViewport().ToString());
+
+ // Confirms that the viewport can't across the top-left border.
+ GetMagnificationController()->MoveWindow(-100, 0, false);
+ EXPECT_EQ("0,0 400x300", GetViewport().ToString());
+
+ GetMagnificationController()->MoveWindow(0, -100, false);
+ EXPECT_EQ("0,0 400x300", GetViewport().ToString());
+
+ GetMagnificationController()->MoveWindow(-100, -100, false);
+ EXPECT_EQ("0,0 400x300", GetViewport().ToString());
+
+ // Confirms that the viewport can't across the bittom-right border.
+ GetMagnificationController()->MoveWindow(800, 0, false);
+ EXPECT_EQ("400,0 400x300", GetViewport().ToString());
+
+ GetMagnificationController()->MoveWindow(0, 400, false);
+ EXPECT_EQ("0,300 400x300", GetViewport().ToString());
+
+ GetMagnificationController()->MoveWindow(200, 400, false);
+ EXPECT_EQ("200,300 400x300", GetViewport().ToString());
+
+ GetMagnificationController()->MoveWindow(1000, 1000, false);
+ EXPECT_EQ("400,300 400x300", GetViewport().ToString());
+}
+
+TEST_F(MagnificationControllerTest, PointOfInterest) {
+ aura::test::EventGenerator generator(Shell::GetPrimaryRootWindow());
+
+ generator.MoveMouseToInHost(gfx::Point(0, 0));
+ EXPECT_EQ("0,0", CurrentPointOfInterest());
+
+ generator.MoveMouseToInHost(gfx::Point(799, 599));
+ EXPECT_EQ("799,599", CurrentPointOfInterest());
+
+ generator.MoveMouseToInHost(gfx::Point(400, 300));
+ EXPECT_EQ("400,300", CurrentPointOfInterest());
+
+ GetMagnificationController()->SetEnabled(true);
+ EXPECT_EQ("400,300", CurrentPointOfInterest());
+
+ generator.MoveMouseToInHost(gfx::Point(500, 400));
+ EXPECT_EQ("450,350", CurrentPointOfInterest());
+}
+
+TEST_F(MagnificationControllerTest, PanWindow2xLeftToRight) {
+ const aura::Env* env = aura::Env::GetInstance();
+ aura::test::EventGenerator generator(Shell::GetPrimaryRootWindow());
+
+ generator.MoveMouseToInHost(gfx::Point(0, 0));
+ EXPECT_EQ(1.f, GetMagnificationController()->GetScale());
+ EXPECT_EQ("0,0 800x600", GetViewport().ToString());
+ EXPECT_EQ("0,0", env->last_mouse_location().ToString());
+
+ // Enables magnifier and confirm the viewport is at center.
+ GetMagnificationController()->SetEnabled(true);
+ EXPECT_EQ(2.0f, GetMagnificationController()->GetScale());
+
+ GetMagnificationController()->MoveWindow(0, 0, false);
+ generator.MoveMouseToInHost(gfx::Point(0, 0));
+ EXPECT_EQ("0,0", env->last_mouse_location().ToString());
+ EXPECT_EQ("0,0 400x300", GetViewport().ToString());
+
+ generator.MoveMouseToInHost(gfx::Point(300, 150));
+ EXPECT_EQ("150,75", env->last_mouse_location().ToString());
+ EXPECT_EQ("0,0 400x300", GetViewport().ToString());
+
+ generator.MoveMouseToInHost(gfx::Point(700, 150));
+ EXPECT_EQ("350,75", env->last_mouse_location().ToString());
+ EXPECT_EQ("0,0 400x300", GetViewport().ToString());
+
+ generator.MoveMouseToInHost(gfx::Point(701, 150));
+ EXPECT_EQ("350,75", env->last_mouse_location().ToString());
+ EXPECT_EQ("0,0 400x300", GetViewport().ToString());
+
+ generator.MoveMouseToInHost(gfx::Point(702, 150));
+ EXPECT_EQ("351,75", env->last_mouse_location().ToString());
+ EXPECT_EQ("1,0 400x300", GetViewport().ToString());
+
+ generator.MoveMouseToInHost(gfx::Point(703, 150));
+ EXPECT_EQ("352,75", env->last_mouse_location().ToString());
+ EXPECT_EQ("2,0 400x300", GetViewport().ToString());
+
+ generator.MoveMouseToInHost(gfx::Point(704, 150));
+ EXPECT_EQ("354,75", env->last_mouse_location().ToString());
+ EXPECT_EQ("4,0 400x300", GetViewport().ToString());
+
+ generator.MoveMouseToInHost(gfx::Point(712, 150));
+ EXPECT_EQ("360,75", env->last_mouse_location().ToString());
+ EXPECT_EQ("10,0 400x300", GetViewport().ToString());
+
+ generator.MoveMouseToInHost(gfx::Point(600, 150));
+ EXPECT_EQ("310,75", env->last_mouse_location().ToString());
+ EXPECT_EQ("10,0 400x300", GetViewport().ToString());
+
+ generator.MoveMouseToInHost(gfx::Point(720, 150));
+ EXPECT_EQ("370,75", env->last_mouse_location().ToString());
+ EXPECT_EQ("20,0 400x300", GetViewport().ToString());
+
+ generator.MoveMouseToInHost(gfx::Point(780, 150));
+ EXPECT_EQ("410,75", env->last_mouse_location().ToString());
+ EXPECT_EQ("410,75", CurrentPointOfInterest());
+ EXPECT_EQ("60,0 400x300", GetViewport().ToString());
+
+ generator.MoveMouseToInHost(gfx::Point(799, 150));
+ EXPECT_EQ("459,75", env->last_mouse_location().ToString());
+ EXPECT_EQ("109,0 400x300", GetViewport().ToString());
+
+ generator.MoveMouseToInHost(gfx::Point(702, 150));
+ EXPECT_EQ("460,75", env->last_mouse_location().ToString());
+ EXPECT_EQ("110,0 400x300", GetViewport().ToString());
+
+ generator.MoveMouseToInHost(gfx::Point(780, 150));
+ EXPECT_EQ("500,75", env->last_mouse_location().ToString());
+ EXPECT_EQ("150,0 400x300", GetViewport().ToString());
+
+ generator.MoveMouseToInHost(gfx::Point(780, 150));
+ EXPECT_EQ("540,75", env->last_mouse_location().ToString());
+ EXPECT_EQ("190,0 400x300", GetViewport().ToString());
+
+ generator.MoveMouseToInHost(gfx::Point(780, 150));
+ EXPECT_EQ("580,75", env->last_mouse_location().ToString());
+ EXPECT_EQ("230,0 400x300", GetViewport().ToString());
+
+ generator.MoveMouseToInHost(gfx::Point(780, 150));
+ EXPECT_EQ("620,75", env->last_mouse_location().ToString());
+ EXPECT_EQ("270,0 400x300", GetViewport().ToString());
+
+ generator.MoveMouseToInHost(gfx::Point(780, 150));
+ EXPECT_EQ("660,75", env->last_mouse_location().ToString());
+ EXPECT_EQ("310,0 400x300", GetViewport().ToString());
+
+ generator.MoveMouseToInHost(gfx::Point(780, 150));
+ EXPECT_EQ("700,75", env->last_mouse_location().ToString());
+ EXPECT_EQ("350,0 400x300", GetViewport().ToString());
+
+ generator.MoveMouseToInHost(gfx::Point(780, 150));
+ EXPECT_EQ("740,75", env->last_mouse_location().ToString());
+ EXPECT_EQ("390,0 400x300", GetViewport().ToString());
+
+ generator.MoveMouseToInHost(gfx::Point(780, 150));
+ EXPECT_EQ("780,75", env->last_mouse_location().ToString());
+ EXPECT_EQ("400,0 400x300", GetViewport().ToString());
+
+ generator.MoveMouseToInHost(gfx::Point(799, 150));
+ EXPECT_EQ("799,75", env->last_mouse_location().ToString());
+ EXPECT_EQ("400,0 400x300", GetViewport().ToString());
+}
+
+TEST_F(MagnificationControllerTest, PanWindow2xRightToLeft) {
+ const aura::Env* env = aura::Env::GetInstance();
+ aura::test::EventGenerator generator(Shell::GetPrimaryRootWindow());
+
+ generator.MoveMouseToInHost(gfx::Point(799, 300));
+ EXPECT_EQ(1.f, GetMagnificationController()->GetScale());
+ EXPECT_EQ("0,0 800x600", GetViewport().ToString());
+ EXPECT_EQ("799,300", env->last_mouse_location().ToString());
+
+ // Enables magnifier and confirm the viewport is at center.
+ GetMagnificationController()->SetEnabled(true);
+
+ generator.MoveMouseToInHost(gfx::Point(799, 300));
+ EXPECT_EQ("798,300", env->last_mouse_location().ToString());
+ EXPECT_EQ("400,150 400x300", GetViewport().ToString());
+
+ generator.MoveMouseToInHost(gfx::Point(0, 300));
+ EXPECT_EQ("400,300", env->last_mouse_location().ToString());
+ EXPECT_EQ("350,150 400x300", GetViewport().ToString());
+
+ generator.MoveMouseToInHost(gfx::Point(0, 300));
+ EXPECT_EQ("350,300", env->last_mouse_location().ToString());
+ EXPECT_EQ("300,150 400x300", GetViewport().ToString());
+
+ generator.MoveMouseToInHost(gfx::Point(0, 300));
+ EXPECT_EQ("300,300", env->last_mouse_location().ToString());
+ EXPECT_EQ("250,150 400x300", GetViewport().ToString());
+
+ generator.MoveMouseToInHost(gfx::Point(0, 300));
+ EXPECT_EQ("250,300", env->last_mouse_location().ToString());
+ EXPECT_EQ("200,150 400x300", GetViewport().ToString());
+
+ generator.MoveMouseToInHost(gfx::Point(0, 300));
+ EXPECT_EQ("200,300", env->last_mouse_location().ToString());
+ EXPECT_EQ("150,150 400x300", GetViewport().ToString());
+
+ generator.MoveMouseToInHost(gfx::Point(0, 300));
+ EXPECT_EQ("150,300", env->last_mouse_location().ToString());
+ EXPECT_EQ("100,150 400x300", GetViewport().ToString());
+
+ generator.MoveMouseToInHost(gfx::Point(0, 300));
+ EXPECT_EQ("100,300", env->last_mouse_location().ToString());
+ EXPECT_EQ("50,150 400x300", GetViewport().ToString());
+
+ generator.MoveMouseToInHost(gfx::Point(0, 300));
+ EXPECT_EQ("50,300", env->last_mouse_location().ToString());
+ EXPECT_EQ("0,150 400x300", GetViewport().ToString());
+
+ generator.MoveMouseToInHost(gfx::Point(0, 300));
+ EXPECT_EQ("0,300", env->last_mouse_location().ToString());
+ EXPECT_EQ("0,150 400x300", GetViewport().ToString());
+}
+
+TEST_F(MagnificationControllerTest, PanWindowToRight) {
+ const aura::Env* env = aura::Env::GetInstance();
+ aura::test::EventGenerator generator(Shell::GetPrimaryRootWindow());
+
+ generator.MoveMouseToInHost(gfx::Point(400, 300));
+ EXPECT_EQ(1.f, GetMagnificationController()->GetScale());
+ EXPECT_EQ("0,0 800x600", GetViewport().ToString());
+ EXPECT_EQ("400,300", env->last_mouse_location().ToString());
+
+ float scale = 2.f;
+
+ // Enables magnifier and confirm the viewport is at center.
+ GetMagnificationController()->SetEnabled(true);
+ EXPECT_FLOAT_EQ(2.f, GetMagnificationController()->GetScale());
+
+ scale *= kMagnificationScaleFactor;
+ GetMagnificationController()->SetScale(scale, false);
+ EXPECT_FLOAT_EQ(2.3784142, GetMagnificationController()->GetScale());
+ generator.MoveMouseToInHost(gfx::Point(400, 300));
+ EXPECT_EQ("400,300", env->last_mouse_location().ToString());
+ generator.MoveMouseToInHost(gfx::Point(799, 300));
+ EXPECT_EQ("566,299", env->last_mouse_location().ToString());
+ EXPECT_EQ("705,300", GetHostMouseLocation());
+
+ scale *= kMagnificationScaleFactor;
+ GetMagnificationController()->SetScale(scale, false);
+ EXPECT_FLOAT_EQ(2.8284268, GetMagnificationController()->GetScale());
+ generator.MoveMouseToInHost(gfx::Point(799, 300));
+ EXPECT_EQ("599,299", env->last_mouse_location().ToString());
+ EXPECT_EQ("702,300", GetHostMouseLocation());
+
+ scale *= kMagnificationScaleFactor;
+ GetMagnificationController()->SetScale(scale, false);
+ EXPECT_FLOAT_EQ(3.3635852, GetMagnificationController()->GetScale());
+ generator.MoveMouseToInHost(gfx::Point(799, 300));
+ EXPECT_EQ("627,298", env->last_mouse_location().ToString());
+ EXPECT_EQ("707,300", GetHostMouseLocation());
+
+ scale *= kMagnificationScaleFactor;
+ GetMagnificationController()->SetScale(scale, false);
+ EXPECT_FLOAT_EQ(4.f, GetMagnificationController()->GetScale());
+ generator.MoveMouseToInHost(gfx::Point(799, 300));
+ EXPECT_EQ("649,298", env->last_mouse_location().ToString());
+ EXPECT_EQ("704,300", GetHostMouseLocation());
+}
+
+TEST_F(MagnificationControllerTest, PanWindowToLeft) {
+ const aura::Env* env = aura::Env::GetInstance();
+ aura::test::EventGenerator generator(Shell::GetPrimaryRootWindow());
+
+ generator.MoveMouseToInHost(gfx::Point(400, 300));
+ EXPECT_EQ(1.f, GetMagnificationController()->GetScale());
+ EXPECT_EQ("0,0 800x600", GetViewport().ToString());
+ EXPECT_EQ("400,300", env->last_mouse_location().ToString());
+
+ float scale = 2.f;
+
+ // Enables magnifier and confirm the viewport is at center.
+ GetMagnificationController()->SetEnabled(true);
+ EXPECT_FLOAT_EQ(2.f, GetMagnificationController()->GetScale());
+
+ scale *= kMagnificationScaleFactor;
+ GetMagnificationController()->SetScale(scale, false);
+ EXPECT_FLOAT_EQ(2.3784142, GetMagnificationController()->GetScale());
+ generator.MoveMouseToInHost(gfx::Point(400, 300));
+ EXPECT_EQ("400,300", env->last_mouse_location().ToString());
+ generator.MoveMouseToInHost(gfx::Point(0, 300));
+ EXPECT_EQ("231,299", env->last_mouse_location().ToString());
+ EXPECT_EQ("100,300", GetHostMouseLocation());
+
+ scale *= kMagnificationScaleFactor;
+ GetMagnificationController()->SetScale(scale, false);
+ EXPECT_FLOAT_EQ(2.8284268, GetMagnificationController()->GetScale());
+ generator.MoveMouseToInHost(gfx::Point(0, 300));
+ EXPECT_EQ("195,299", env->last_mouse_location().ToString());
+ EXPECT_EQ("99,300", GetHostMouseLocation());
+
+ scale *= kMagnificationScaleFactor;
+ GetMagnificationController()->SetScale(scale, false);
+ EXPECT_FLOAT_EQ(3.3635852, GetMagnificationController()->GetScale());
+ generator.MoveMouseToInHost(gfx::Point(0, 300));
+ EXPECT_EQ("165,298", env->last_mouse_location().ToString());
+ EXPECT_EQ("98,300", GetHostMouseLocation());
+
+ scale *= kMagnificationScaleFactor;
+ GetMagnificationController()->SetScale(scale, false);
+ EXPECT_FLOAT_EQ(4.f, GetMagnificationController()->GetScale());
+ generator.MoveMouseToInHost(gfx::Point(0, 300));
+ EXPECT_EQ("140,298", env->last_mouse_location().ToString());
+ EXPECT_EQ("100,300", GetHostMouseLocation());
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/magnifier/magnifier_constants.h b/chromium/ash/magnifier/magnifier_constants.h
new file mode 100644
index 00000000000..b286299aace
--- /dev/null
+++ b/chromium/ash/magnifier/magnifier_constants.h
@@ -0,0 +1,27 @@
+// Copyright (c) 2012 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 ASH_MAGNIFIER_MAGNIFIER_CONSTANTS_H_
+#define ASH_MAGNIFIER_MAGNIFIER_CONSTANTS_H_
+
+namespace ash {
+
+// Note: Do not change these values; UMA and prefs depend on them.
+enum MagnifierType {
+ MAGNIFIER_FULL = 1,
+ MAGNIFIER_PARTIAL = 2,
+};
+
+const int kMaxMagnifierType = 2;
+
+const MagnifierType kDefaultMagnifierType = MAGNIFIER_FULL;
+
+// Factor of magnification scale. For example, when this value is 1.189, scale
+// value will be changed x1.000, x1.189, x1.414, x1.681, x2.000, ...
+// Note: this value is 2.0 ^ (1 / 4).
+const float kMagnificationScaleFactor = 1.18920712f;
+
+} // namespace ash
+
+#endif // ASH_MAGNIFIER_MAGNIFIER_CONSTANTS_H_
diff --git a/chromium/ash/magnifier/partial_magnification_controller.cc b/chromium/ash/magnifier/partial_magnification_controller.cc
new file mode 100644
index 00000000000..d9138568409
--- /dev/null
+++ b/chromium/ash/magnifier/partial_magnification_controller.cc
@@ -0,0 +1,211 @@
+// Copyright (c) 2012 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.
+
+#include "ash/magnifier/partial_magnification_controller.h"
+
+#include "ash/shell.h"
+#include "ash/shell_window_ids.h"
+#include "ui/aura/root_window.h"
+#include "ui/views/corewm/compound_event_filter.h"
+#include "ui/aura/window.h"
+#include "ui/aura/window_property.h"
+#include "ui/gfx/screen.h"
+#include "ui/compositor/layer.h"
+#include "ui/views/layout/fill_layout.h"
+#include "ui/views/widget/widget.h"
+#include "ui/views/widget/widget_delegate.h"
+
+namespace {
+
+const float kMinPartialMagnifiedScaleThreshold = 1.1f;
+
+// Number of pixels to make the border of the magnified area.
+const int kZoomInset = 16;
+
+// Width of the magnified area.
+const int kMagnifierWidth = 200;
+
+// Height of the magnified area.
+const int kMagnifierHeight = 200;
+
+// Name of the magnifier window.
+const char kPartialMagniferWindowName[] = "PartialMagnifierWindow";
+
+} // namespace
+
+namespace ash {
+
+PartialMagnificationController::PartialMagnificationController()
+ : is_on_zooming_(false),
+ is_enabled_(false),
+ scale_(kNonPartialMagnifiedScale),
+ zoom_widget_(NULL) {
+ Shell::GetInstance()->AddPreTargetHandler(this);
+}
+
+PartialMagnificationController::~PartialMagnificationController() {
+ CloseMagnifierWindow();
+
+ Shell::GetInstance()->RemovePreTargetHandler(this);
+}
+
+void PartialMagnificationController::SetScale(float scale) {
+ if (!is_enabled_)
+ return;
+
+ scale_ = scale;
+
+ if (IsPartialMagnified()) {
+ CreateMagnifierWindow();
+ } else {
+ CloseMagnifierWindow();
+ }
+}
+
+void PartialMagnificationController::SetEnabled(bool enabled) {
+ if (enabled) {
+ is_enabled_ = enabled;
+ SetScale(kDefaultPartialMagnifiedScale);
+ } else {
+ SetScale(kNonPartialMagnifiedScale);
+ is_enabled_ = enabled;
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// PartialMagnificationController: ui::EventHandler implementation
+
+void PartialMagnificationController::OnMouseEvent(ui::MouseEvent* event) {
+ if (IsPartialMagnified() && event->type() == ui::ET_MOUSE_MOVED) {
+ aura::Window* target = static_cast<aura::Window*>(event->target());
+ aura::RootWindow* current_root = target->GetRootWindow();
+ // TODO(zork): Handle the case where the event is captured on a different
+ // display, such as when a menu is opened.
+ gfx::Rect root_bounds = current_root->bounds();
+
+ if (root_bounds.Contains(event->root_location())) {
+ SwitchTargetRootWindow(current_root);
+
+ OnMouseMove(event->root_location());
+ }
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// PartialMagnificationController: aura::WindowObserver implementation
+
+void PartialMagnificationController::OnWindowDestroying(
+ aura::Window* window) {
+ CloseMagnifierWindow();
+
+ aura::RootWindow* new_root_window = GetCurrentRootWindow();
+ if (new_root_window != window)
+ SwitchTargetRootWindow(new_root_window);
+}
+
+void PartialMagnificationController::OnWidgetDestroying(
+ views::Widget* widget) {
+ DCHECK_EQ(widget, zoom_widget_);
+ RemoveZoomWidgetObservers();
+ zoom_widget_ = NULL;
+}
+
+void PartialMagnificationController::OnMouseMove(
+ const gfx::Point& location_in_root) {
+ gfx::Point origin(location_in_root);
+
+ origin.Offset(-kMagnifierWidth / 2, -kMagnifierHeight / 2);
+
+ if (zoom_widget_) {
+ zoom_widget_->SetBounds(gfx::Rect(origin.x(), origin.y(),
+ kMagnifierWidth, kMagnifierHeight));
+ }
+}
+
+bool PartialMagnificationController::IsPartialMagnified() const {
+ return scale_ >= kMinPartialMagnifiedScaleThreshold;
+}
+
+void PartialMagnificationController::CreateMagnifierWindow() {
+ if (zoom_widget_)
+ return;
+
+ aura::RootWindow* root_window = GetCurrentRootWindow();
+ if (!root_window)
+ return;
+
+ root_window->AddObserver(this);
+
+ gfx::Point mouse(root_window->GetLastMouseLocationInRoot());
+
+ zoom_widget_ = new views::Widget;
+ views::Widget::InitParams params(
+ views::Widget::InitParams::TYPE_WINDOW_FRAMELESS);
+ params.can_activate = false;
+ params.accept_events = false;
+ params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW;
+ params.parent = root_window;
+ zoom_widget_->Init(params);
+ zoom_widget_->SetBounds(gfx::Rect(mouse.x() - kMagnifierWidth / 2,
+ mouse.y() - kMagnifierHeight / 2,
+ kMagnifierWidth, kMagnifierHeight));
+ zoom_widget_->set_focus_on_creation(false);
+ zoom_widget_->Show();
+
+ aura::Window* window = zoom_widget_->GetNativeView();
+ window->SetName(kPartialMagniferWindowName);
+
+ zoom_widget_->GetNativeView()->layer()->SetBounds(
+ gfx::Rect(0, 0,
+ kMagnifierWidth,
+ kMagnifierHeight));
+ zoom_widget_->GetNativeView()->layer()->SetBackgroundZoom(
+ scale_,
+ kZoomInset);
+
+ zoom_widget_->AddObserver(this);
+}
+
+void PartialMagnificationController::CloseMagnifierWindow() {
+ if (zoom_widget_) {
+ RemoveZoomWidgetObservers();
+ zoom_widget_->Close();
+ zoom_widget_ = NULL;
+ }
+}
+
+void PartialMagnificationController::RemoveZoomWidgetObservers() {
+ DCHECK(zoom_widget_);
+ zoom_widget_->RemoveObserver(this);
+ aura::RootWindow* root_window =
+ zoom_widget_->GetNativeView()->GetRootWindow();
+ DCHECK(root_window);
+ root_window->RemoveObserver(this);
+}
+
+void PartialMagnificationController::SwitchTargetRootWindow(
+ aura::RootWindow* new_root_window) {
+ if (zoom_widget_ &&
+ new_root_window == zoom_widget_->GetNativeView()->GetRootWindow())
+ return;
+
+ CloseMagnifierWindow();
+
+ // Recreate the magnifier window by updating the scale factor.
+ SetScale(GetScale());
+}
+
+aura::RootWindow* PartialMagnificationController::GetCurrentRootWindow() {
+ Shell::RootWindowList root_windows = Shell::GetAllRootWindows();
+ for (Shell::RootWindowList::const_iterator iter = root_windows.begin();
+ iter != root_windows.end(); ++iter) {
+ aura::RootWindow* root_window = *iter;
+ if (root_window->ContainsPointInRoot(
+ root_window->GetLastMouseLocationInRoot()))
+ return root_window;
+ }
+ return NULL;
+}
+
+} // namespace ash
diff --git a/chromium/ash/magnifier/partial_magnification_controller.h b/chromium/ash/magnifier/partial_magnification_controller.h
new file mode 100644
index 00000000000..1b8c8fb77bd
--- /dev/null
+++ b/chromium/ash/magnifier/partial_magnification_controller.h
@@ -0,0 +1,95 @@
+// Copyright (c) 2012 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 ASH_MAGNIFIER_PARTIAL_MAGNIFICATION_CONTROLLER_H_
+#define ASH_MAGNIFIER_PARTIAL_MAGNIFICATION_CONTROLLER_H_
+
+#include "ui/aura/window_observer.h"
+#include "ui/base/events/event_handler.h"
+#include "ui/gfx/point.h"
+#include "ui/views/widget/widget_observer.h"
+
+namespace aura {
+class RootWindow;
+}
+
+namespace ash {
+
+const float kDefaultPartialMagnifiedScale = 1.5f;
+const float kNonPartialMagnifiedScale = 1.0f;
+
+// Controls the partial screen magnifier, which is a small area of the screen
+// which is zoomed in. The zoomed area follows the mouse cursor when enabled.
+class PartialMagnificationController
+ : public ui::EventHandler,
+ public aura::WindowObserver,
+ public views::WidgetObserver {
+ public:
+ PartialMagnificationController();
+ virtual ~PartialMagnificationController();
+
+ // Enables (or disables if |enabled| is false) partial screen magnifier
+ // feature.
+ virtual void SetEnabled(bool enabled);
+
+ bool is_enabled() const { return is_enabled_; }
+
+ // Sets the magnification ratio. 1.0f means no magnification.
+ void SetScale(float scale);
+
+ // Returns the current magnification ratio.
+ float GetScale() const { return scale_; }
+
+ private:
+ void OnMouseMove(const gfx::Point& location_in_root);
+
+ // Switch PartialMagnified RootWindow to |new_root_window|. This does
+ // following:
+ // - Remove the magnifier from the current root window.
+ // - Create a magnifier in the new root_window |new_root_window|.
+ // - Switch the target window from current window to |new_root_window|.
+ void SwitchTargetRootWindow(aura::RootWindow* new_root_window);
+
+ // Returns the root window that contains the mouse cursor.
+ aura::RootWindow* GetCurrentRootWindow();
+
+ // Return true if the magnification scale > kMinPartialMagnifiedScaleThreshold
+ bool IsPartialMagnified() const;
+
+ // Create the magnifier window.
+ void CreateMagnifierWindow();
+
+ // Cleans up the window if needed.
+ void CloseMagnifierWindow();
+
+ // Removes this as an observer of the zoom widget and the root window.
+ void RemoveZoomWidgetObservers();
+
+ // ui::EventHandler overrides:
+ virtual void OnMouseEvent(ui::MouseEvent* event) OVERRIDE;
+
+ // Overridden from WindowObserver:
+ virtual void OnWindowDestroying(aura::Window* window) OVERRIDE;
+
+ // Overridden from WidgetObserver:
+ virtual void OnWidgetDestroying(views::Widget* widget) OVERRIDE;
+
+ // True if the magnified window is in motion of zooming or un-zooming effect.
+ // Otherwise, false.
+ bool is_on_zooming_;
+
+ bool is_enabled_;
+
+ // Current scale, origin (left-top) position of the magnification window.
+ float scale_;
+ gfx::Point origin_;
+
+ views::Widget* zoom_widget_;
+
+ DISALLOW_COPY_AND_ASSIGN(PartialMagnificationController);
+};
+
+} // namespace ash
+
+#endif // ASH_MAGNIFIER_PARTIAL_MAGNIFICATION_CONTROLLER_H_
diff --git a/chromium/ash/popup_message.cc b/chromium/ash/popup_message.cc
new file mode 100644
index 00000000000..bd519387f4d
--- /dev/null
+++ b/chromium/ash/popup_message.cc
@@ -0,0 +1,226 @@
+// Copyright (c) 2013 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.
+
+#include "ash/popup_message.h"
+
+#include "ash/wm/window_animations.h"
+#include "grit/ash_resources.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "ui/gfx/insets.h"
+#include "ui/views/bubble/bubble_delegate.h"
+#include "ui/views/bubble/bubble_frame_view.h"
+#include "ui/views/controls/image_view.h"
+#include "ui/views/controls/label.h"
+#include "ui/views/layout/box_layout.h"
+#include "ui/views/widget/widget.h"
+
+namespace ash {
+namespace {
+const int kMessageTopBottomMargin = 10;
+const int kMessageLeftRightMargin = 10;
+const int kMessageAppearanceDelayMs = 200;
+const int kMessageMinHeight = 29 - 2 * kMessageTopBottomMargin;
+const SkColor kMessageTextColor = SkColorSetRGB(0x22, 0x22, 0x22);
+
+// The maximum width of the Message bubble. Borrowed the value from
+// ash/message/message_controller.cc
+const int kMessageMaxWidth = 250;
+
+// The offset for the Message bubble - making sure that the bubble is flush
+// with the shelf. The offset includes the arrow size in pixels as well as
+// the activation bar and other spacing elements.
+const int kArrowOffsetLeftRight = 11;
+const int kArrowOffsetTopBottom = 7;
+
+// The number of pixels between the icon and the text.
+const int kHorizontalPopupPaddingBetweenItems = 10;
+
+// The number of pixels between the text items.
+const int kVerticalPopupPaddingBetweenItems = 10;
+} // namespace
+
+// The implementation of Message of the launcher.
+class PopupMessage::MessageBubble : public views::BubbleDelegateView {
+ public:
+ MessageBubble(const base::string16& caption,
+ const base::string16& message,
+ IconType message_type,
+ views::View* anchor,
+ views::BubbleBorder::Arrow arrow_orientation,
+ const gfx::Size& size_override,
+ int arrow_offset);
+
+ void Close();
+
+ private:
+ // views::View overrides:
+ virtual gfx::Size GetPreferredSize() OVERRIDE;
+
+ // Each component (width/height) can force a size override for that component
+ // if not 0.
+ gfx::Size size_override_;
+
+ DISALLOW_COPY_AND_ASSIGN(MessageBubble);
+};
+
+PopupMessage::MessageBubble::MessageBubble(const base::string16& caption,
+ const base::string16& message,
+ IconType message_type,
+ views::View* anchor,
+ views::BubbleBorder::Arrow arrow,
+ const gfx::Size& size_override,
+ int arrow_offset)
+ : views::BubbleDelegateView(anchor, arrow),
+ size_override_(size_override) {
+ gfx::Insets insets = gfx::Insets(kArrowOffsetTopBottom,
+ kArrowOffsetLeftRight,
+ kArrowOffsetTopBottom,
+ kArrowOffsetLeftRight);
+ // An anchor can have an asymmetrical border for spacing reasons. Adjust the
+ // anchor location for this.
+ if (anchor->border())
+ insets += anchor->border()->GetInsets();
+
+ set_anchor_view_insets(insets);
+ set_close_on_esc(false);
+ set_close_on_deactivate(false);
+ set_use_focusless(true);
+ set_accept_events(false);
+
+ set_margins(gfx::Insets(kMessageTopBottomMargin, kMessageLeftRightMargin,
+ kMessageTopBottomMargin, kMessageLeftRightMargin));
+ set_shadow(views::BubbleBorder::SMALL_SHADOW);
+
+ ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance();
+ SetLayoutManager(new views::BoxLayout(views::BoxLayout::kHorizontal, 0, 0,
+ kHorizontalPopupPaddingBetweenItems));
+
+ // Here is the layout:
+ // arrow_offset (if not 0)
+ // |-------------|
+ // | ^
+ // +-------------------------------------------------+
+ // -| |-
+ // icon | [!] Caption in bold which can be multi line | caption_label
+ // -| |-
+ // | Message text which can be multi line | message_label
+ // | as well. |
+ // | |-
+ // +-------------------------------------------------+
+ // |------------details container--------------|
+ // Note that the icon, caption and massage are optional.
+
+ // Add the icon to the first column (if there is one).
+ if (message_type != ICON_NONE) {
+ views::ImageView* icon = new views::ImageView();
+ icon->SetImage(
+ bundle.GetImageNamed(IDR_AURA_WARNING_ICON).ToImageSkia());
+ icon->SetVerticalAlignment(views::ImageView::LEADING);
+ AddChildView(icon);
+ }
+
+ // Create a container for the text items and use it as second column.
+ views::View* details = new views::View();
+ AddChildView(details);
+ details->SetLayoutManager(new views::BoxLayout(
+ views::BoxLayout::kVertical, 0, 0, kVerticalPopupPaddingBetweenItems));
+
+ // The caption label.
+ if (!caption.empty()) {
+ views::Label* caption_label = new views::Label(caption);
+ caption_label->SetMultiLine(true);
+ caption_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
+ caption_label->SetFont(bundle.GetFont(ui::ResourceBundle::BoldFont));
+ caption_label->SetEnabledColor(kMessageTextColor);
+ details->AddChildView(caption_label);
+ }
+
+ // The message label.
+ if (!message.empty()) {
+ views::Label* message_label = new views::Label(message);
+ message_label->SetMultiLine(true);
+ message_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
+ message_label->SetEnabledColor(kMessageTextColor);
+ details->AddChildView(message_label);
+ }
+ views::BubbleDelegateView::CreateBubble(this);
+
+ // Change the arrow offset if needed.
+ if (arrow_offset) {
+ // With the creation of the bubble, the bubble got already placed (and
+ // possibly re-oriented to fit on the screen). Since it is not possible to
+ // set the arrow offset before the creation, we need to set the offset,
+ // and the orientation variables again and force a re-placement.
+ GetBubbleFrameView()->bubble_border()->set_arrow_offset(arrow_offset);
+ GetBubbleFrameView()->bubble_border()->set_arrow(arrow);
+ SetAlignment(views::BubbleBorder::ALIGN_ARROW_TO_MID_ANCHOR);
+ }
+}
+
+void PopupMessage::MessageBubble::Close() {
+ if (GetWidget())
+ GetWidget()->Close();
+}
+
+gfx::Size PopupMessage::MessageBubble::GetPreferredSize() {
+ gfx::Size pref_size = views::BubbleDelegateView::GetPreferredSize();
+ // Override the size with either the provided size or adjust it to not
+ // violate our minimum / maximum sizes.
+ if (size_override_.height())
+ pref_size.set_height(size_override_.height());
+ else if (pref_size.height() < kMessageMinHeight)
+ pref_size.set_height(kMessageMinHeight);
+
+ if (size_override_.width())
+ pref_size.set_width(size_override_.width());
+ else if (pref_size.width() > kMessageMaxWidth)
+ pref_size.set_width(kMessageMaxWidth);
+
+ return pref_size;
+}
+
+PopupMessage::PopupMessage(const base::string16& caption,
+ const base::string16& message,
+ IconType message_type,
+ views::View* anchor,
+ views::BubbleBorder::Arrow arrow,
+ const gfx::Size& size_override,
+ int arrow_offset)
+ : view_(NULL) {
+ view_ = new MessageBubble(
+ caption, message, message_type, anchor, arrow, size_override,
+ arrow_offset);
+ widget_ = view_->GetWidget();
+
+ gfx::NativeView native_view = widget_->GetNativeView();
+ views::corewm::SetWindowVisibilityAnimationType(
+ native_view, views::corewm::WINDOW_VISIBILITY_ANIMATION_TYPE_VERTICAL);
+ views::corewm::SetWindowVisibilityAnimationTransition(
+ native_view, views::corewm::ANIMATE_HIDE);
+ view_->GetWidget()->Show();
+}
+
+PopupMessage::~PopupMessage() {
+ CancelHidingAnimation();
+ Close();
+}
+
+void PopupMessage::Close() {
+ if (view_) {
+ view_->Close();
+ view_ = NULL;
+ widget_ = NULL;
+ }
+}
+
+void PopupMessage::CancelHidingAnimation() {
+ if (!widget_ || !widget_->GetNativeView())
+ return;
+
+ gfx::NativeView native_view = widget_->GetNativeView();
+ views::corewm::SetWindowVisibilityAnimationTransition(
+ native_view, views::corewm::ANIMATE_NONE);
+}
+
+} // namespace ash
diff --git a/chromium/ash/popup_message.h b/chromium/ash/popup_message.h
new file mode 100644
index 00000000000..040b9fe7e16
--- /dev/null
+++ b/chromium/ash/popup_message.h
@@ -0,0 +1,83 @@
+// Copyright 2013 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 ASH_POPUP_MESSAGE_H_
+#define ASH_POPUP_MESSAGE_H_
+
+#include "ash/ash_export.h"
+#include "base/basictypes.h"
+#include "base/strings/string16.h"
+#include "ui/gfx/rect.h"
+#include "ui/views/bubble/bubble_border.h"
+
+namespace views {
+class BubbleDelegateView;
+}
+
+namespace ash {
+
+// PopupMessage shows a message to the user. Since the user is not able to
+// dismiss it, the calling code needs to explictly close and destroy it.
+class ASH_EXPORT PopupMessage {
+ public:
+ enum IconType {
+ ICON_WARNING,
+ ICON_NONE
+ };
+
+ // Creates a message pointing towards |anchor| with the requested
+ // |arrow_orientation|. The message contains an optional |caption| which is
+ // drawn in bold and an optional |message| together with an optional icon of
+ // shape |message_type|. If a component in |size_override| is not 0 the value
+ // is the used as output size. If |arrow_offset| is not 0, the number is the
+ // arrow offset in pixels from the border.
+ //
+ // Here is the layout (arrow given as TOP_LEFT):
+ // |--------|
+ // | Anchor |
+ // |--------|
+ // |-arrow_offset---^
+ // +-------------------------------------------------+
+ // -| |-
+ // icon | [!] Caption in bold which can be multi line | caption_label
+ // -| |-
+ // | Message text which can be multi line | message_label
+ // | as well. |
+ // | |-
+ // +-------------------------------------------------+
+ PopupMessage(const base::string16& caption,
+ const base::string16& message,
+ IconType message_type,
+ views::View* anchor,
+ views::BubbleBorder::Arrow arrow,
+ const gfx::Size& size_override,
+ int arrow_offset);
+ // If the message was not explicitly closed before, it closes the message
+ // without animation.
+ virtual ~PopupMessage();
+
+ // Closes the message with a fade out animation.
+ void Close();
+
+ private:
+ class MessageBubble;
+
+ void CancelHidingAnimation();
+
+ MessageBubble* view_;
+ views::Widget* widget_;
+
+ // Variables of the construction time.
+ views::View* anchor_;
+ base::string16 caption_;
+ base::string16 message_;
+ IconType message_type_;
+ views::BubbleBorder::Arrow arrow_orientation_;
+
+ DISALLOW_COPY_AND_ASSIGN(PopupMessage);
+};
+
+} // namespace ash
+
+#endif // ASH_POPUP_MESSAGE_H_
diff --git a/chromium/ash/resources/OWNERS b/chromium/ash/resources/OWNERS
new file mode 100644
index 00000000000..92f3fbb9210
--- /dev/null
+++ b/chromium/ash/resources/OWNERS
@@ -0,0 +1 @@
+oshima@chromium.org
diff --git a/chromium/ash/resources/PRESUBMIT.py b/chromium/ash/resources/PRESUBMIT.py
new file mode 100644
index 00000000000..9d070e4a5a3
--- /dev/null
+++ b/chromium/ash/resources/PRESUBMIT.py
@@ -0,0 +1,47 @@
+# Copyright (c) 2012 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.
+
+"""Presubmit script for Chromium Ash resources.
+
+See http://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts
+for more details about the presubmit API built into gcl/git cl, and see
+http://www.chromium.org/developers/web-development-style-guide for the rules
+we're checking against here.
+"""
+
+
+def CheckChangeOnUpload(input_api, output_api):
+ return _CommonChecks(input_api, output_api)
+
+
+def CheckChangeOnCommit(input_api, output_api):
+ return _CommonChecks(input_api, output_api)
+
+
+def _CommonChecks(input_api, output_api):
+ """Checks common to both upload and commit."""
+ results = []
+ resources = input_api.os_path.join(input_api.PresubmitLocalPath(),
+ '../../ui/resources')
+
+ # List of paths with their associated scale factor. This is used to verify
+ # that the images modified in one are the correct scale of the other.
+ path_scales = [
+ [(100, 'default_100_percent/'), (200, 'default_200_percent/')],
+ ]
+
+ import sys
+ old_path = sys.path
+
+ try:
+ sys.path = [resources] + old_path
+ from resource_check import resource_scale_factors
+
+ for paths in path_scales:
+ results.extend(resource_scale_factors.ResourceScaleFactors(
+ input_api, output_api, paths).RunChecks())
+ finally:
+ sys.path = old_path
+
+ return results
diff --git a/chromium/ash/resources/ash_resources.grd b/chromium/ash/resources/ash_resources.grd
new file mode 100644
index 00000000000..991c895a8d0
--- /dev/null
+++ b/chromium/ash/resources/ash_resources.grd
@@ -0,0 +1,286 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<grit latest_public_release="0" current_release="1">
+ <outputs>
+ <output filename="grit/ash_resources.h" type="rc_header" context="default_100_percent">
+ <emit emit_type='prepend'></emit>
+ </output>
+ <output filename="grit/ash_resources_map.cc" type="resource_map_source" context="default_100_percent" />
+ <output filename="grit/ash_resources_map.h" type="resource_map_header" context="default_100_percent" />
+ <output filename="ash_resources_100_percent.pak" type="data_package" context="default_100_percent" />
+ <output filename="ash_resources_200_percent.pak" type="data_package" context="default_200_percent" />
+ </outputs>
+ <release seq="1">
+ <includes>
+ <!-- Fallback wallpaper used by Ash. -->
+ <include name="IDR_AURA_WALLPAPER_DEFAULT_LARGE" file="wallpaper/default_large.jpg" type="BINDATA" />
+ <include name="IDR_AURA_WALLPAPER_DEFAULT_SMALL" file="wallpaper/default_small.jpg" type="BINDATA" />
+ </includes>
+ <structures fallback_to_low_resolution="true">
+ <!-- KEEP THESE IN ALPHABETICAL ORDER! DO NOT ADD TO RANDOM PLACES JUST
+ BECAUSE YOUR RESOURCES ARE FUNCTIONALLY RELATED OR FALL UNDER THE
+ SAME CONDITIONALS. -->
+ <structure type="chrome_scaled_image" name="IDR_AURA_LAUNCHER_BACKGROUND" file="common/launcher/launcher_background.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_LAUNCHER_BROWSER" file="common/launcher/launcher_browser.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_LAUNCHER_BROWSER_PANEL" file="common/launcher/launcher_browser_panel.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_LAUNCHER_BROWSER_SHORTCUT" file="common/chromium-32.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_LAUNCHER_DIMMING" file="common/launcher/launcher_dimming.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_LAUNCHER_ICON_APPLIST" file="common/launcher/launcher_appmenu.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_LAUNCHER_ICON_APPLIST_ALTERNATE" file="common/alt_launcher/status_app_menu_icon.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_LAUNCHER_ICON_APPLIST_HOT" file="common/launcher/launcher_appmenu_hover.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_LAUNCHER_ICON_APPLIST_PUSHED" file="common/launcher/launcher_appmenu_pressed.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_LAUNCHER_INCOGNITO_BROWSER" file="common/launcher/launcher_incognito_browser.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_LAUNCHER_INCOGNITO_BROWSER_PANEL" file="common/launcher/launcher_incognito_browser_panel.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_LAUNCHER_LIST_BROWSER" file="common/launcher/window_switcher_icon_normal.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_LAUNCHER_LIST_INCOGNITO_BROWSER" file="common/launcher/window_switcher_icon_incognito.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_LAUNCHER_OVERFLOW" file="common/launcher/launcher_overflow.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_LAUNCHER_UNDERLINE_ACTIVE" file="common/launcher/launcher_underline_bottom_active.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_LAUNCHER_UNDERLINE_ACTIVE_ALTERNATE" file="common/alt_launcher/launcher_underline_active.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_LAUNCHER_UNDERLINE_HOVER" file="common/launcher/launcher_underline_bottom_hover.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_LAUNCHER_UNDERLINE_RUNNING" file="common/launcher/launcher_underline_bottom_running.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_LAUNCHER_UNDERLINE_RUNNING_ALTERNATE" file="common/alt_launcher/launcher_underline_running.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_LAUNCHER_ICON_TASK_MANAGER" file="common/launcher/task_manager.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_MULTI_WINDOW_RESIZE_H" file="common/multi_window_resize_horizontal.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_MULTI_WINDOW_RESIZE_V" file="common/multi_window_resize_vertical.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_NOTIFICATION_BACKGROUND_NORMAL" file="common/alt_launcher/status_icon_background_normal.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_NOTIFICATION_BACKGROUND_ON_BLACK" file="common/alt_launcher/status_icon_background_onblack_normal.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_NOTIFICATION_BACKGROUND_PRESSED" file="common/alt_launcher/status_icon_background_pressed.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_RESIZE_SHADOW_BOTTOM" file="common/resize_shadow_bottom.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_RESIZE_SHADOW_BOTTOM_LEFT" file="common/resize_shadow_bottom_left.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_RESIZE_SHADOW_BOTTOM_RIGHT" file="common/resize_shadow_bottom_right.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_RESIZE_SHADOW_LEFT" file="common/resize_shadow_left.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_RESIZE_SHADOW_RIGHT" file="common/resize_shadow_right.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_RESIZE_SHADOW_TOP" file="common/resize_shadow_top.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_RESIZE_SHADOW_TOP_LEFT" file="common/resize_shadow_top_left.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_RESIZE_SHADOW_TOP_RIGHT" file="common/resize_shadow_top_right.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_TRAY_BG_HORIZ_CENTER" file="common/alt_launcher/status_tray_normal_center.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_TRAY_BG_HORIZ_CENTER_ONBLACK" file="common/alt_launcher/status_tray_normal_onblack_center.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_TRAY_BG_HORIZ_CENTER_PRESSED" file="common/alt_launcher/status_tray_pressed_center.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_TRAY_BG_HORIZ_LEFT" file="common/alt_launcher/status_tray_normal_left.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_TRAY_BG_HORIZ_LEFT_ONBLACK" file="common/alt_launcher/status_tray_normal_onblack_left.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_TRAY_BG_HORIZ_LEFT_PRESSED" file="common/alt_launcher/status_tray_pressed_left.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_TRAY_BG_HORIZ_RIGHT" file="common/alt_launcher/status_tray_normal_right.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_TRAY_BG_HORIZ_RIGHT_ONBLACK" file="common/alt_launcher/status_tray_normal_onblack_right.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_TRAY_BG_HORIZ_RIGHT_PRESSED" file="common/alt_launcher/status_tray_pressed_right.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_TRAY_BG_VERTICAL_BOTTOM" file="common/alt_launcher/status_tray_vertical_normal_bottom.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_TRAY_BG_VERTICAL_BOTTOM_ONBLACK" file="common/alt_launcher/status_tray_vertical_normal_onblack_bottom.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_TRAY_BG_VERTICAL_BOTTOM_PRESSED" file="common/alt_launcher/status_tray_vertical_pressed_bottom.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_TRAY_BG_VERTICAL_TOP" file="common/alt_launcher/status_tray_vertical_normal_top.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_TRAY_BG_VERTICAL_TOP_ONBLACK" file="common/alt_launcher/status_tray_vertical_normal_onblack_top.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_TRAY_BG_VERTICAL_TOP_PRESSED" file="common/alt_launcher/status_tray_vertical_pressed_top.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_TRAY_BG_VERTICAL_CENTER" file="common/alt_launcher/status_tray_vertical_normal_center.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_TRAY_BG_VERTICAL_CENTER_ONBLACK" file="common/alt_launcher/status_tray_vertical_normal_onblack_center.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_TRAY_BG_VERTICAL_CENTER_PRESSED" file="common/alt_launcher/status_tray_vertical_pressed_center.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_TRAY_POPUP_LABEL_BUTTON_BORDER" file="common/tray_popup_label_button_border.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_TRAY_POPUP_LABEL_BUTTON_HOVER_BACKGROUND" file="common/tray_popup_label_button_hover_background.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_TRAY_POPUP_LABEL_BUTTON_NORMAL_BACKGROUND" file="common/tray_popup_label_button_normal_background.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER" file="common/tray_popup_public_account_logout_button_border.png" />
+
+ <structure type="chrome_scaled_image" name="IDR_AURA_CROS_DEFAULT_THROBBER" file="cros/common/default_throbber.png" />
+
+ <structure type="chrome_scaled_image" name="IDR_AURA_WARNING_ICON" file="common/alert_small.png" />
+
+ <structure type="chrome_scaled_image" name="IDR_AURA_UBER_TRAY_ACCESSIBILITY" file="cros/status/status_accessibility_mode.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_UBER_TRAY_ACCESSIBILITY_DARK" file="cros/status/status_accessibility_dark.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_UBER_TRAY_BLUETOOTH" file="cros/status/status_bluetooth.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_UBER_TRAY_BLUETOOTH_DISABLED" file="cros/status/status_bluetooth_disabled.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_UBER_TRAY_BLUETOOTH_DISABLED_HOVER" file="cros/status/status_bluetooth_disabled_hover.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_UBER_TRAY_BLUETOOTH_ENABLED" file="cros/status/status_bluetooth_enabled.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_UBER_TRAY_BLUETOOTH_ENABLED_HOVER" file="cros/status/status_bluetooth_enabled_hover.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_UBER_TRAY_BRIGHTNESS" file="cros/status/status_brightness.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_UBER_TRAY_CAPS_LOCK" file="cros/status/status_capslock.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_UBER_TRAY_CAPS_LOCK_DARK" file="cros/status/status_capslock_dark.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_UBER_TRAY_CELLULAR_DISABLED" file="cros/network/status_cellular_disabled.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_UBER_TRAY_CELLULAR_DISABLED_HOVER" file="cros/network/status_cellular_disabled_hover.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_UBER_TRAY_CELLULAR_ENABLED" file="cros/network/status_cellular_enabled.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_UBER_TRAY_CELLULAR_ENABLED_HOVER" file="cros/network/status_cellular_enabled_hover.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_UBER_TRAY_DISPLAY" file="cros/status/status_display_dark.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_UBER_TRAY_DISPLAY_LIGHT" file="cros/status/status_display.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_UBER_TRAY_SCREEN_SHARE_DARK" file="cros/status/status_screen_share_dark.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_UBER_TRAY_SCREEN_SHARE_LIGHT" file="cros/status/status_screen_share_light.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_UBER_TRAY_DRIVE" file="cros/status/status_drive.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_UBER_TRAY_DRIVE_CANCEL" file="cros/status/status_drive_item_cancel.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_UBER_TRAY_DRIVE_CANCEL_HOVER" file="cros/status/status_drive_item_cancel_hover.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_UBER_TRAY_DRIVE_DONE" file="cros/status/status_drive_item_done.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_UBER_TRAY_DRIVE_FAILED" file="cros/status/status_drive_item_failed.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_UBER_TRAY_DRIVE_LIGHT" file="cros/status/status_drive_light.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_UBER_TRAY_HELP" file="cros/status/status_help.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_UBER_TRAY_HELP_HOVER" file="cros/status/status_help_hover.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_UBER_TRAY_IME" file="cros/status/status_ime.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_UBER_TRAY_LESS" file="cros/status/status_less.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_UBER_TRAY_LOCALE" file="cros/status/status_locale.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_UBER_TRAY_LOCKSCREEN" file="cros/status/status_lockscreen.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_UBER_TRAY_LOCKSCREEN_HOVER" file="cros/status/status_lockscreen_hover.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_UBER_TRAY_GUEST_ICON" file="cros/status/status_guest_icon.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_UBER_TRAY_GUEST_ICON_LARGE" file="common/alt_launcher/status_guest_icon.png" />
+
+ <structure type="chrome_scaled_image" name="IDR_AURA_UBER_TRAY_LOGOUT_BUTTON_PUSHED_BOTTOM" file="cros/status/status_logout_button_pushed_bottom.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_UBER_TRAY_LOGOUT_BUTTON_PUSHED_BOTTOM_LEFT" file="cros/status/status_logout_button_pushed_bottom_left.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_UBER_TRAY_LOGOUT_BUTTON_PUSHED_BOTTOM_RIGHT" file="cros/status/status_logout_button_pushed_bottom_right.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_UBER_TRAY_LOGOUT_BUTTON_PUSHED_CENTER" file="cros/status/status_logout_button_pushed_center.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_UBER_TRAY_LOGOUT_BUTTON_PUSHED_LEFT" file="cros/status/status_logout_button_pushed_left.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_UBER_TRAY_LOGOUT_BUTTON_PUSHED_RIGHT" file="cros/status/status_logout_button_pushed_right.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_UBER_TRAY_LOGOUT_BUTTON_PUSHED_TOP" file="cros/status/status_logout_button_pushed_top.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_UBER_TRAY_LOGOUT_BUTTON_PUSHED_TOP_LEFT" file="cros/status/status_logout_button_pushed_top_left.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_UBER_TRAY_LOGOUT_BUTTON_PUSHED_TOP_RIGHT" file="cros/status/status_logout_button_pushed_top_right.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_UBER_TRAY_LOGOUT_BUTTON_NORMAL_BOTTOM" file="cros/status/status_logout_button_normal_bottom.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_UBER_TRAY_LOGOUT_BUTTON_NORMAL_BOTTOM_LEFT" file="cros/status/status_logout_button_normal_bottom_left.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_UBER_TRAY_LOGOUT_BUTTON_NORMAL_BOTTOM_RIGHT" file="cros/status/status_logout_button_normal_bottom_right.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_UBER_TRAY_LOGOUT_BUTTON_NORMAL_CENTER" file="cros/status/status_logout_button_normal_center.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_UBER_TRAY_LOGOUT_BUTTON_NORMAL_LEFT" file="cros/status/status_logout_button_normal_left.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_UBER_TRAY_LOGOUT_BUTTON_NORMAL_RIGHT" file="cros/status/status_logout_button_normal_right.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_UBER_TRAY_LOGOUT_BUTTON_NORMAL_TOP" file="cros/status/status_logout_button_normal_top.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_UBER_TRAY_LOGOUT_BUTTON_NORMAL_TOP_LEFT" file="cros/status/status_logout_button_normal_top_left.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_UBER_TRAY_LOGOUT_BUTTON_NORMAL_TOP_RIGHT" file="cros/status/status_logout_button_normal_top_right.png" />
+
+ <structure type="chrome_scaled_image" name="IDR_AURA_UBER_TRAY_ADD_MULTIPROFILE_USER" file="cros/status/multiprofiles_add.png" />
+
+ <structure type="chrome_scaled_image" name="IDR_AURA_UBER_TRAY_MORE" file="cros/status/status_more.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_UBER_TRAY_CELLULAR_NETWORK_FAILED" file="cros/network/status_cellular_failed.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_UBER_TRAY_NETWORK_FAILED" file="cros/network/status_network_failed.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_UBER_TRAY_NETWORK_INFO" file="cros/network/status_network_info.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_UBER_TRAY_NETWORK_INFO_HOVER" file="cros/network/status_network_info_hover.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_UBER_TRAY_NOTIFICATION_3G" file="cros/network/notification_3g.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_UBER_TRAY_NOTIFICATION_LTE" file="cros/network/notification_lte.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_UBER_TRAY_POWER_SMALL" file="cros/status/status_power_small_all.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_UBER_TRAY_POWER_SMALL_DARK" file="cros/status/status_power_small_all_dark.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_UBER_TRAY_POWER_SMALL_CHARGING_UNRELIABLE" file="cros/status/status_power_small_all_fluctuating.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_UBER_TRAY_POWER_SMALL_CHARGING_UNRELIABLE_DARK" file="cros/status/status_power_small_all_dark_fluctuating.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_UBER_TRAY_SESSION_LENGTH_LIMIT_TIMER" file="cros/status/status_session_length_limit_timer.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_UBER_TRAY_SETTINGS" file="cros/status/status_settings.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_UBER_TRAY_SHUTDOWN" file="cros/status/status_shutdown.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_UBER_TRAY_SHUTDOWN_HOVER" file="cros/status/status_shutdown_hover.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_UBER_TRAY_SMS" file="cros/status/status_sms.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_UBER_TRAY_TRACING" file="cros/status/status_tracing.png" />
+
+ <!-- ChromeOS specific icons -->
+ <if expr="pp_ifdef('chromeos')">
+ <structure type="chrome_scaled_image" name="IDR_AURA_NOTIFICATION_LOW_POWER_CHARGER" file="cros/notification/notification_low_power_charger.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_UBER_TRAY_ENTERPRISE_DARK" file="cros/status/status_managed.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_UBER_TRAY_MANAGED_USER" file="cros/status/status_managed_mode_user.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_UBER_TRAY_NETWORK_1X" file="cros/network/statusbar_network_1x.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_UBER_TRAY_NETWORK_3G_DARK" file="cros/network/statusbar_network_3g_dark.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_UBER_TRAY_NETWORK_3G_LIGHT" file="cros/network/statusbar_network_3g_light.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_UBER_TRAY_NETWORK_4G_DARK" file="cros/network/statusbar_network_4g_dark.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_UBER_TRAY_NETWORK_4G_LIGHT" file="cros/network/statusbar_network_4g_light.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_UBER_TRAY_NETWORK_ARCS_DARK" file="cros/network/statusbar_network_arcs_dark.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_UBER_TRAY_NETWORK_ARCS_LIGHT" file="cros/network/statusbar_network_arcs_light.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_UBER_TRAY_NETWORK_BARS_DARK" file="cros/network/statusbar_network_bars_dark.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_UBER_TRAY_NETWORK_BARS_LIGHT" file="cros/network/statusbar_network_bars_light.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_UBER_TRAY_NETWORK_EDGE_DARK" file="cros/network/statusbar_network_edge_dark.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_UBER_TRAY_NETWORK_EDGE_LIGHT" file="cros/network/statusbar_network_edge_light.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_UBER_TRAY_NETWORK_EVDO_DARK" file="cros/network/statusbar_network_evdo_dark.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_UBER_TRAY_NETWORK_EVDO_LIGHT" file="cros/network/statusbar_network_evdo_light.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_UBER_TRAY_NETWORK_GPRS_DARK" file="cros/network/statusbar_network_gprs_dark.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_UBER_TRAY_NETWORK_GPRS_LIGHT" file="cros/network/statusbar_network_gprs_light.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_UBER_TRAY_NETWORK_HSPA_DARK" file="cros/network/statusbar_network_hspa_dark.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_UBER_TRAY_NETWORK_HSPA_LIGHT" file="cros/network/statusbar_network_hspa_light.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_UBER_TRAY_NETWORK_HSPA_PLUS_DARK" file="cros/network/statusbar_network_hspa_plus_dark.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_UBER_TRAY_NETWORK_HSPA_PLUS_LIGHT" file="cros/network/statusbar_network_hspa_plus_light.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_UBER_TRAY_NETWORK_LTE_DARK" file="cros/network/statusbar_network_lte_dark.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_UBER_TRAY_NETWORK_LTE_LIGHT" file="cros/network/statusbar_network_lte_light.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_UBER_TRAY_NETWORK_LTE_ADVANCED_DARK" file="cros/network/statusbar_network_lte_advanced_dark.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_UBER_TRAY_NETWORK_LTE_ADVANCED_LIGHT" file="cros/network/statusbar_network_lte_advanced_light.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_UBER_TRAY_NETWORK_ROAMING_DARK" file="cros/network/statusbar_network_roaming_dark.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_UBER_TRAY_NETWORK_ROAMING_LIGHT" file="cros/network/statusbar_network_roaming_light.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_UBER_TRAY_NETWORK_SECURE_DARK" file="cros/network/statusbar_network_secure_dark.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_UBER_TRAY_NETWORK_VPN" file="cros/network/statusbar_vpn_dark.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_UBER_TRAY_NETWORK_VPN_BADGE" file="cros/network/statusbar_network_vpn_badge.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_UBER_TRAY_NETWORK_WIRED" file="cros/network/statusbar_wired.png" />
+ </if>
+
+ <structure type="chrome_scaled_image" name="IDR_AURA_UBER_TRAY_UPDATE" file="cros/status/status_update.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_UBER_TRAY_UPDATE_GREEN" file="cros/status/status_update_green.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_UBER_TRAY_UPDATE_ORANGE" file="cros/status/status_update_orange.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_UBER_TRAY_UPDATE_RED" file="cros/status/status_update_red.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_UBER_TRAY_UPDATE_DARK" file="cros/status/status_update_dark.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_UBER_TRAY_UPDATE_DARK_GREEN" file="cros/status/status_update_dark_green.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_UBER_TRAY_UPDATE_DARK_ORANGE" file="cros/status/status_update_dark_orange.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_UBER_TRAY_UPDATE_DARK_RED" file="cros/status/status_update_dark_red.png" />
+
+ <structure type="chrome_scaled_image" name="IDR_AURA_UBER_TRAY_VOLUME_LEVELS" file="cros/status/status_volume_dark.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_UBER_TRAY_VOLUME_MUTE" file="cros/status/status_volume_mute.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_UBER_TRAY_AUDIO_HEADPHONE" file="cros/status/status_audio_device_headphones.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_UBER_TRAY_AUDIO_USB" file="cros/status/status_audio_device_usb.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_UBER_TRAY_AUDIO_BLUETOOTH" file="cros/status/status_audio_device_bluetooth.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_UBER_TRAY_AUDIO_HDMI" file="cros/status/status_audio_device_hdmi.png" />
+
+ <structure type="chrome_scaled_image" name="IDR_AURA_UBER_TRAY_WIFI_DISABLED" file="cros/network/status_wifi_disabled.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_UBER_TRAY_WIFI_DISABLED_HOVER" file="cros/network/status_wifi_disabled_hover.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_UBER_TRAY_WIFI_ENABLED" file="cros/network/status_wifi_enabled.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_UBER_TRAY_WIFI_ENABLED_HOVER" file="cros/network/status_wifi_enabled_hover.png" />
+
+ <structure type="chrome_scaled_image" name="IDR_AURA_WINDOW_BUTTON_SEPARATOR" file="common/window_button_separator.png" />
+
+ <structure type="chrome_scaled_image" name="IDR_AURA_WINDOW_FULLSCREEN_CLOSE" file="common/window_close_fullscreen_normal.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_WINDOW_FULLSCREEN_CLOSE_H" file="common/window_close_fullscreen_hover.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_WINDOW_FULLSCREEN_CLOSE_P" file="common/window_close_fullscreen_pressed.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_WINDOW_FULLSCREEN_RESTORE" file="common/window_size_fullscreen_normal.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_WINDOW_FULLSCREEN_RESTORE_H" file="common/window_size_fullscreen_hover.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_WINDOW_FULLSCREEN_RESTORE_P" file="common/window_size_fullscreen_pressed.png" />
+
+ <structure type="chrome_scaled_image" name="IDR_AURA_WINDOW_FULLSCREEN_SHADOW" file="common/window_fullscreen_shadow.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_WINDOW_FULLSCREEN_SHADOW_RTL" file="common/window_fullscreen_shadow_rtl.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_WINDOW_HEADER_BASE_ACTIVE" file="common/window_header_base_active.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_WINDOW_HEADER_BASE_INACTIVE" file="common/window_header_base_inactive.png" />
+ <!-- TODO(jamescook): We need updated incognito art for Aura. -->
+ <structure type="chrome_scaled_image" name="IDR_AURA_WINDOW_HEADER_BASE_INCOGNITO_ACTIVE" file="common/window_header_base_incognito_active.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_WINDOW_HEADER_BASE_INCOGNITO_INACTIVE" file="common/window_header_base_incognito_inactive.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_WINDOW_HEADER_BASE_MINIMAL" file="common/window_header_base_minimal.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_WINDOW_HEADER_SHADE_LEFT" file="common/window_header_shade_left.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_WINDOW_HEADER_SHADE_MIDDLE" file="common/window_header_shade_middle.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_WINDOW_HEADER_SHADE_RIGHT" file="common/window_header_shade_right.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_WINDOW_HEADER_SHADE_TOP" file="common/window_header_shade_top.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_WINDOW_HEADER_SHADE_TOP_LEFT" file="common/window_header_shade_top_left.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_WINDOW_HEADER_SHADE_TOP_RIGHT" file="common/window_header_shade_top_right.png" />
+
+ <!-- Art for experimental "immersive mode" button at top of screen. crbug.com/157612 -->
+ <structure type="chrome_scaled_image" name="IDR_AURA_WINDOW_IMMERSIVE_ENTER" file="common/window_immersive_enter_normal.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_WINDOW_IMMERSIVE_ENTER_H" file="common/window_immersive_enter_hover.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_WINDOW_IMMERSIVE_ENTER_P" file="common/window_immersive_enter_pressed.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_WINDOW_IMMERSIVE_EXIT" file="common/window_immersive_exit_normal.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_WINDOW_IMMERSIVE_EXIT_H" file="common/window_immersive_exit_hover.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_WINDOW_IMMERSIVE_EXIT_P" file="common/window_immersive_exit_pressed.png" />
+
+ <structure type="chrome_scaled_image" name="IDR_AURA_WINDOW_CLOSE" file="common/window_close_tall_normal.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_WINDOW_CLOSE_H" file="common/window_close_tall_hover.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_WINDOW_CLOSE_P" file="common/window_close_tall_pressed.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_WINDOW_MAXIMIZED_CLOSE" file="common/window_close_short_normal.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_WINDOW_MAXIMIZED_CLOSE_H" file="common/window_close_short_hover.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_WINDOW_MAXIMIZED_CLOSE_P" file="common/window_close_short_pressed.png" />
+ <!-- These are temporary, when workspace2 is the default they'll replace IDR_AURA_WINDOW_MAXIMIZED_CLOSE -->
+ <structure type="chrome_scaled_image" name="IDR_AURA_WINDOW_MAXIMIZED_CLOSE2" file="common/window_close_short_black_normal.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_WINDOW_MAXIMIZED_CLOSE2_H" file="common/window_close_short_black_hover.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_WINDOW_MAXIMIZED_CLOSE2_P" file="common/window_close_short_black_pressed.png" />
+
+ <structure type="chrome_scaled_image" name="IDR_AURA_WINDOW_MAXIMIZE" file="common/window_size_tall_normal.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_WINDOW_MAXIMIZE_H" file="common/window_size_tall_hover.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_WINDOW_MAXIMIZE_P" file="common/window_size_tall_pressed.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_WINDOW_MAXIMIZED_RESTORE" file="common/window_size_short_normal.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_WINDOW_MAXIMIZED_RESTORE_H" file="common/window_size_short_hover.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_WINDOW_MAXIMIZED_RESTORE_P" file="common/window_size_short_pressed.png" />
+ <!-- These are temporary, when workspace2 is the default they'll replace IDR_AURA_WINDOW_MAXIMIZED_RESTORE -->
+ <structure type="chrome_scaled_image" name="IDR_AURA_WINDOW_MAXIMIZED_RESTORE2" file="common/window_size_short_black_normal.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_WINDOW_MAXIMIZED_RESTORE2_H" file="common/window_size_short_black_hover.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_WINDOW_MAXIMIZED_RESTORE2_P" file="common/window_size_short_black_pressed.png" />
+
+ <structure type="chrome_scaled_image" name="IDR_AURA_WINDOW_MINIMIZE_SHORT" file="common/window_minimize_short_normal.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_WINDOW_MINIMIZE_SHORT_H" file="common/window_minimize_short_hover.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_WINDOW_MINIMIZE_SHORT_P" file="common/window_minimize_short_pressed.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_WINDOW_POSITION_LEFT" file="common/window_position_left_normal.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_WINDOW_POSITION_LEFT_H" file="common/window_position_left_hover.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_WINDOW_POSITION_LEFT_P" file="common/window_position_left_pressed.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_WINDOW_POSITION_MIDDLE" file="common/window_position_middle_normal.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_WINDOW_POSITION_MIDDLE_H" file="common/window_position_middle_hover.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_WINDOW_POSITION_MIDDLE_P" file="common/window_position_middle_pressed.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_WINDOW_POSITION_RIGHT" file="common/window_position_right_normal.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_WINDOW_POSITION_RIGHT_H" file="common/window_position_right_hover.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_WINDOW_POSITION_RIGHT_P" file="common/window_position_right_pressed.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_WINDOW_POSITION_LEFT_RESTORE" file="common/window_position_left_normal_restore.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_WINDOW_POSITION_LEFT_RESTORE_H" file="common/window_position_left_hover_restore.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_WINDOW_POSITION_LEFT_RESTORE_P" file="common/window_position_left_pressed_restore.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_WINDOW_POSITION_RIGHT_RESTORE" file="common/window_position_right_normal_restore.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_WINDOW_POSITION_RIGHT_RESTORE_H" file="common/window_position_right_hover_restore.png" />
+ <structure type="chrome_scaled_image" name="IDR_AURA_WINDOW_POSITION_RIGHT_RESTORE_P" file="common/window_position_right_pressed_restore.png" />
+ </structures>
+ </release>
+</grit>
diff --git a/chromium/ash/resources/default_100_percent/common/alert_small.png b/chromium/ash/resources/default_100_percent/common/alert_small.png
new file mode 100644
index 00000000000..7b76b49a9aa
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/common/alert_small.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/common/alt_launcher/launcher_background.png b/chromium/ash/resources/default_100_percent/common/alt_launcher/launcher_background.png
new file mode 100644
index 00000000000..45b06ed7464
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/common/alt_launcher/launcher_background.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/common/alt_launcher/launcher_underline_active.png b/chromium/ash/resources/default_100_percent/common/alt_launcher/launcher_underline_active.png
new file mode 100644
index 00000000000..6a69d17bdeb
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/common/alt_launcher/launcher_underline_active.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/common/alt_launcher/launcher_underline_running.png b/chromium/ash/resources/default_100_percent/common/alt_launcher/launcher_underline_running.png
new file mode 100644
index 00000000000..9bcff86ef7e
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/common/alt_launcher/launcher_underline_running.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/common/alt_launcher/status_app_menu_icon.png b/chromium/ash/resources/default_100_percent/common/alt_launcher/status_app_menu_icon.png
new file mode 100644
index 00000000000..5e5f18b4f55
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/common/alt_launcher/status_app_menu_icon.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/common/alt_launcher/status_guest_icon.png b/chromium/ash/resources/default_100_percent/common/alt_launcher/status_guest_icon.png
new file mode 100644
index 00000000000..12ac2bb3941
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/common/alt_launcher/status_guest_icon.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/common/alt_launcher/status_icon_background_normal.png b/chromium/ash/resources/default_100_percent/common/alt_launcher/status_icon_background_normal.png
new file mode 100644
index 00000000000..565c5010dde
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/common/alt_launcher/status_icon_background_normal.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/common/alt_launcher/status_icon_background_onblack_normal.png b/chromium/ash/resources/default_100_percent/common/alt_launcher/status_icon_background_onblack_normal.png
new file mode 100644
index 00000000000..41ba35c526b
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/common/alt_launcher/status_icon_background_onblack_normal.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/common/alt_launcher/status_icon_background_pressed.png b/chromium/ash/resources/default_100_percent/common/alt_launcher/status_icon_background_pressed.png
new file mode 100644
index 00000000000..d8cdd77291a
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/common/alt_launcher/status_icon_background_pressed.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/common/alt_launcher/status_notification_icon.png b/chromium/ash/resources/default_100_percent/common/alt_launcher/status_notification_icon.png
new file mode 100644
index 00000000000..a22e1361468
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/common/alt_launcher/status_notification_icon.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/common/alt_launcher/status_tray_normal_center.png b/chromium/ash/resources/default_100_percent/common/alt_launcher/status_tray_normal_center.png
new file mode 100644
index 00000000000..7f895ba31fa
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/common/alt_launcher/status_tray_normal_center.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/common/alt_launcher/status_tray_normal_left.png b/chromium/ash/resources/default_100_percent/common/alt_launcher/status_tray_normal_left.png
new file mode 100644
index 00000000000..8b00443089f
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/common/alt_launcher/status_tray_normal_left.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/common/alt_launcher/status_tray_normal_onblack_center.png b/chromium/ash/resources/default_100_percent/common/alt_launcher/status_tray_normal_onblack_center.png
new file mode 100644
index 00000000000..0d26eb2d529
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/common/alt_launcher/status_tray_normal_onblack_center.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/common/alt_launcher/status_tray_normal_onblack_left.png b/chromium/ash/resources/default_100_percent/common/alt_launcher/status_tray_normal_onblack_left.png
new file mode 100644
index 00000000000..34841271995
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/common/alt_launcher/status_tray_normal_onblack_left.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/common/alt_launcher/status_tray_normal_onblack_right.png b/chromium/ash/resources/default_100_percent/common/alt_launcher/status_tray_normal_onblack_right.png
new file mode 100644
index 00000000000..5787128a603
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/common/alt_launcher/status_tray_normal_onblack_right.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/common/alt_launcher/status_tray_normal_right.png b/chromium/ash/resources/default_100_percent/common/alt_launcher/status_tray_normal_right.png
new file mode 100644
index 00000000000..850e80f2b82
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/common/alt_launcher/status_tray_normal_right.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/common/alt_launcher/status_tray_pressed_center.png b/chromium/ash/resources/default_100_percent/common/alt_launcher/status_tray_pressed_center.png
new file mode 100644
index 00000000000..3eb461d3b8b
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/common/alt_launcher/status_tray_pressed_center.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/common/alt_launcher/status_tray_pressed_left.png b/chromium/ash/resources/default_100_percent/common/alt_launcher/status_tray_pressed_left.png
new file mode 100644
index 00000000000..3c97b3e2fa2
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/common/alt_launcher/status_tray_pressed_left.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/common/alt_launcher/status_tray_pressed_right.png b/chromium/ash/resources/default_100_percent/common/alt_launcher/status_tray_pressed_right.png
new file mode 100644
index 00000000000..8ec72272d0b
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/common/alt_launcher/status_tray_pressed_right.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/common/alt_launcher/status_tray_vertical_normal_bottom.png b/chromium/ash/resources/default_100_percent/common/alt_launcher/status_tray_vertical_normal_bottom.png
new file mode 100644
index 00000000000..0b6bd703106
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/common/alt_launcher/status_tray_vertical_normal_bottom.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/common/alt_launcher/status_tray_vertical_normal_center.png b/chromium/ash/resources/default_100_percent/common/alt_launcher/status_tray_vertical_normal_center.png
new file mode 100644
index 00000000000..91674cc42f6
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/common/alt_launcher/status_tray_vertical_normal_center.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/common/alt_launcher/status_tray_vertical_normal_onblack_bottom.png b/chromium/ash/resources/default_100_percent/common/alt_launcher/status_tray_vertical_normal_onblack_bottom.png
new file mode 100644
index 00000000000..2b1b6590931
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/common/alt_launcher/status_tray_vertical_normal_onblack_bottom.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/common/alt_launcher/status_tray_vertical_normal_onblack_center.png b/chromium/ash/resources/default_100_percent/common/alt_launcher/status_tray_vertical_normal_onblack_center.png
new file mode 100644
index 00000000000..9e3bb3cdc72
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/common/alt_launcher/status_tray_vertical_normal_onblack_center.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/common/alt_launcher/status_tray_vertical_normal_onblack_top.png b/chromium/ash/resources/default_100_percent/common/alt_launcher/status_tray_vertical_normal_onblack_top.png
new file mode 100644
index 00000000000..d59b1ef1d83
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/common/alt_launcher/status_tray_vertical_normal_onblack_top.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/common/alt_launcher/status_tray_vertical_normal_top.png b/chromium/ash/resources/default_100_percent/common/alt_launcher/status_tray_vertical_normal_top.png
new file mode 100644
index 00000000000..e0e0c5ac123
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/common/alt_launcher/status_tray_vertical_normal_top.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/common/alt_launcher/status_tray_vertical_pressed_bottom.png b/chromium/ash/resources/default_100_percent/common/alt_launcher/status_tray_vertical_pressed_bottom.png
new file mode 100644
index 00000000000..b2762ba13d2
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/common/alt_launcher/status_tray_vertical_pressed_bottom.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/common/alt_launcher/status_tray_vertical_pressed_center.png b/chromium/ash/resources/default_100_percent/common/alt_launcher/status_tray_vertical_pressed_center.png
new file mode 100644
index 00000000000..77271932048
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/common/alt_launcher/status_tray_vertical_pressed_center.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/common/alt_launcher/status_tray_vertical_pressed_top.png b/chromium/ash/resources/default_100_percent/common/alt_launcher/status_tray_vertical_pressed_top.png
new file mode 100644
index 00000000000..96453a6f57a
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/common/alt_launcher/status_tray_vertical_pressed_top.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/common/chromium-32.png b/chromium/ash/resources/default_100_percent/common/chromium-32.png
new file mode 100644
index 00000000000..e7084472a34
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/common/chromium-32.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/common/launcher/launcher_appmenu.png b/chromium/ash/resources/default_100_percent/common/launcher/launcher_appmenu.png
new file mode 100644
index 00000000000..719d77212fd
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/common/launcher/launcher_appmenu.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/common/launcher/launcher_appmenu_hover.png b/chromium/ash/resources/default_100_percent/common/launcher/launcher_appmenu_hover.png
new file mode 100644
index 00000000000..a41d0e48e7a
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/common/launcher/launcher_appmenu_hover.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/common/launcher/launcher_appmenu_pressed.png b/chromium/ash/resources/default_100_percent/common/launcher/launcher_appmenu_pressed.png
new file mode 100644
index 00000000000..98750f87fdf
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/common/launcher/launcher_appmenu_pressed.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/common/launcher/launcher_background.png b/chromium/ash/resources/default_100_percent/common/launcher/launcher_background.png
new file mode 100644
index 00000000000..212c0de3941
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/common/launcher/launcher_background.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/common/launcher/launcher_background_left.png b/chromium/ash/resources/default_100_percent/common/launcher/launcher_background_left.png
new file mode 100644
index 00000000000..aabd3b90d23
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/common/launcher/launcher_background_left.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/common/launcher/launcher_background_right.png b/chromium/ash/resources/default_100_percent/common/launcher/launcher_background_right.png
new file mode 100644
index 00000000000..dea5d31bd32
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/common/launcher/launcher_background_right.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/common/launcher/launcher_browser.png b/chromium/ash/resources/default_100_percent/common/launcher/launcher_browser.png
new file mode 100644
index 00000000000..669a3a989c6
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/common/launcher/launcher_browser.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/common/launcher/launcher_browser_panel.png b/chromium/ash/resources/default_100_percent/common/launcher/launcher_browser_panel.png
new file mode 100644
index 00000000000..b974cb38ddb
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/common/launcher/launcher_browser_panel.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/common/launcher/launcher_dimming.png b/chromium/ash/resources/default_100_percent/common/launcher/launcher_dimming.png
new file mode 100644
index 00000000000..cead58f8d58
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/common/launcher/launcher_dimming.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/common/launcher/launcher_dimming_left.png b/chromium/ash/resources/default_100_percent/common/launcher/launcher_dimming_left.png
new file mode 100644
index 00000000000..42a41068b60
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/common/launcher/launcher_dimming_left.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/common/launcher/launcher_dimming_right.png b/chromium/ash/resources/default_100_percent/common/launcher/launcher_dimming_right.png
new file mode 100644
index 00000000000..f8852ac72c8
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/common/launcher/launcher_dimming_right.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/common/launcher/launcher_incognito_browser.png b/chromium/ash/resources/default_100_percent/common/launcher/launcher_incognito_browser.png
new file mode 100644
index 00000000000..f9c49182f69
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/common/launcher/launcher_incognito_browser.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/common/launcher/launcher_incognito_browser_panel.png b/chromium/ash/resources/default_100_percent/common/launcher/launcher_incognito_browser_panel.png
new file mode 100644
index 00000000000..3bfdcc3c3cc
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/common/launcher/launcher_incognito_browser_panel.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/common/launcher/launcher_overflow.png b/chromium/ash/resources/default_100_percent/common/launcher/launcher_overflow.png
new file mode 100644
index 00000000000..05cc71d077c
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/common/launcher/launcher_overflow.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/common/launcher/launcher_underline_bottom_active.png b/chromium/ash/resources/default_100_percent/common/launcher/launcher_underline_bottom_active.png
new file mode 100644
index 00000000000..bdf9f4e75c7
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/common/launcher/launcher_underline_bottom_active.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/common/launcher/launcher_underline_bottom_hover.png b/chromium/ash/resources/default_100_percent/common/launcher/launcher_underline_bottom_hover.png
new file mode 100644
index 00000000000..93dd4a1ebbd
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/common/launcher/launcher_underline_bottom_hover.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/common/launcher/launcher_underline_bottom_running.png b/chromium/ash/resources/default_100_percent/common/launcher/launcher_underline_bottom_running.png
new file mode 100644
index 00000000000..ac5bccb2407
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/common/launcher/launcher_underline_bottom_running.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/common/launcher/launcher_underline_left_active.png b/chromium/ash/resources/default_100_percent/common/launcher/launcher_underline_left_active.png
new file mode 100644
index 00000000000..f232976db65
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/common/launcher/launcher_underline_left_active.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/common/launcher/launcher_underline_left_hover.png b/chromium/ash/resources/default_100_percent/common/launcher/launcher_underline_left_hover.png
new file mode 100644
index 00000000000..f232976db65
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/common/launcher/launcher_underline_left_hover.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/common/launcher/launcher_underline_left_running.png b/chromium/ash/resources/default_100_percent/common/launcher/launcher_underline_left_running.png
new file mode 100644
index 00000000000..6a4294fe580
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/common/launcher/launcher_underline_left_running.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/common/launcher/launcher_underline_right_active.png b/chromium/ash/resources/default_100_percent/common/launcher/launcher_underline_right_active.png
new file mode 100644
index 00000000000..82572f20ca2
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/common/launcher/launcher_underline_right_active.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/common/launcher/launcher_underline_right_hover.png b/chromium/ash/resources/default_100_percent/common/launcher/launcher_underline_right_hover.png
new file mode 100644
index 00000000000..82572f20ca2
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/common/launcher/launcher_underline_right_hover.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/common/launcher/launcher_underline_right_running.png b/chromium/ash/resources/default_100_percent/common/launcher/launcher_underline_right_running.png
new file mode 100644
index 00000000000..24de3c042f8
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/common/launcher/launcher_underline_right_running.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/common/launcher/task_manager.png b/chromium/ash/resources/default_100_percent/common/launcher/task_manager.png
new file mode 100644
index 00000000000..293bc769004
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/common/launcher/task_manager.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/common/launcher/window_switcher_icon_incognito.png b/chromium/ash/resources/default_100_percent/common/launcher/window_switcher_icon_incognito.png
new file mode 100644
index 00000000000..47ed4cd55b5
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/common/launcher/window_switcher_icon_incognito.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/common/launcher/window_switcher_icon_normal.png b/chromium/ash/resources/default_100_percent/common/launcher/window_switcher_icon_normal.png
new file mode 100644
index 00000000000..cb0656cfe3d
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/common/launcher/window_switcher_icon_normal.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/common/multi_window_resize_horizontal.png b/chromium/ash/resources/default_100_percent/common/multi_window_resize_horizontal.png
new file mode 100644
index 00000000000..0f17aa9d981
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/common/multi_window_resize_horizontal.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/common/multi_window_resize_vertical.png b/chromium/ash/resources/default_100_percent/common/multi_window_resize_vertical.png
new file mode 100644
index 00000000000..7dceda426b3
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/common/multi_window_resize_vertical.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/common/resize_shadow_bottom.png b/chromium/ash/resources/default_100_percent/common/resize_shadow_bottom.png
new file mode 100644
index 00000000000..c0d2011c8a3
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/common/resize_shadow_bottom.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/common/resize_shadow_bottom_left.png b/chromium/ash/resources/default_100_percent/common/resize_shadow_bottom_left.png
new file mode 100644
index 00000000000..a469177c3b8
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/common/resize_shadow_bottom_left.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/common/resize_shadow_bottom_right.png b/chromium/ash/resources/default_100_percent/common/resize_shadow_bottom_right.png
new file mode 100644
index 00000000000..ecf05385be5
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/common/resize_shadow_bottom_right.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/common/resize_shadow_left.png b/chromium/ash/resources/default_100_percent/common/resize_shadow_left.png
new file mode 100644
index 00000000000..1f859c16cf8
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/common/resize_shadow_left.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/common/resize_shadow_right.png b/chromium/ash/resources/default_100_percent/common/resize_shadow_right.png
new file mode 100644
index 00000000000..1f859c16cf8
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/common/resize_shadow_right.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/common/resize_shadow_top.png b/chromium/ash/resources/default_100_percent/common/resize_shadow_top.png
new file mode 100644
index 00000000000..c0d2011c8a3
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/common/resize_shadow_top.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/common/resize_shadow_top_left.png b/chromium/ash/resources/default_100_percent/common/resize_shadow_top_left.png
new file mode 100644
index 00000000000..50e959c9318
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/common/resize_shadow_top_left.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/common/resize_shadow_top_right.png b/chromium/ash/resources/default_100_percent/common/resize_shadow_top_right.png
new file mode 100644
index 00000000000..5a0be1a2e76
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/common/resize_shadow_top_right.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/common/tray_popup_label_button_border.png b/chromium/ash/resources/default_100_percent/common/tray_popup_label_button_border.png
new file mode 100644
index 00000000000..6677ed94689
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/common/tray_popup_label_button_border.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/common/tray_popup_label_button_hover_background.png b/chromium/ash/resources/default_100_percent/common/tray_popup_label_button_hover_background.png
new file mode 100644
index 00000000000..6cd3bc7d176
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/common/tray_popup_label_button_hover_background.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/common/tray_popup_label_button_normal_background.png b/chromium/ash/resources/default_100_percent/common/tray_popup_label_button_normal_background.png
new file mode 100644
index 00000000000..22d48a8fbbb
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/common/tray_popup_label_button_normal_background.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/common/tray_popup_public_account_logout_button_border.png b/chromium/ash/resources/default_100_percent/common/tray_popup_public_account_logout_button_border.png
new file mode 100644
index 00000000000..db4b22f0fea
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/common/tray_popup_public_account_logout_button_border.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/common/window_button_separator.png b/chromium/ash/resources/default_100_percent/common/window_button_separator.png
new file mode 100644
index 00000000000..ad3a15dfbf8
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/common/window_button_separator.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/common/window_close_fullscreen_hover.png b/chromium/ash/resources/default_100_percent/common/window_close_fullscreen_hover.png
new file mode 100644
index 00000000000..bd6cffa7892
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/common/window_close_fullscreen_hover.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/common/window_close_fullscreen_normal.png b/chromium/ash/resources/default_100_percent/common/window_close_fullscreen_normal.png
new file mode 100644
index 00000000000..d9b19f19bb3
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/common/window_close_fullscreen_normal.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/common/window_close_fullscreen_pressed.png b/chromium/ash/resources/default_100_percent/common/window_close_fullscreen_pressed.png
new file mode 100644
index 00000000000..aa9219dcc3a
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/common/window_close_fullscreen_pressed.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/common/window_close_short_black_hover.png b/chromium/ash/resources/default_100_percent/common/window_close_short_black_hover.png
new file mode 100644
index 00000000000..3d464870a93
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/common/window_close_short_black_hover.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/common/window_close_short_black_normal.png b/chromium/ash/resources/default_100_percent/common/window_close_short_black_normal.png
new file mode 100644
index 00000000000..23010ea7d64
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/common/window_close_short_black_normal.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/common/window_close_short_black_pressed.png b/chromium/ash/resources/default_100_percent/common/window_close_short_black_pressed.png
new file mode 100644
index 00000000000..703fce26cea
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/common/window_close_short_black_pressed.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/common/window_close_short_hover.png b/chromium/ash/resources/default_100_percent/common/window_close_short_hover.png
new file mode 100644
index 00000000000..2e27e58ee32
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/common/window_close_short_hover.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/common/window_close_short_normal.png b/chromium/ash/resources/default_100_percent/common/window_close_short_normal.png
new file mode 100644
index 00000000000..a49779aaa9a
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/common/window_close_short_normal.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/common/window_close_short_pressed.png b/chromium/ash/resources/default_100_percent/common/window_close_short_pressed.png
new file mode 100644
index 00000000000..31f9b6e17f8
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/common/window_close_short_pressed.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/common/window_close_tall_hover.png b/chromium/ash/resources/default_100_percent/common/window_close_tall_hover.png
new file mode 100644
index 00000000000..400a398e130
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/common/window_close_tall_hover.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/common/window_close_tall_normal.png b/chromium/ash/resources/default_100_percent/common/window_close_tall_normal.png
new file mode 100644
index 00000000000..a3384f887d4
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/common/window_close_tall_normal.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/common/window_close_tall_pressed.png b/chromium/ash/resources/default_100_percent/common/window_close_tall_pressed.png
new file mode 100644
index 00000000000..323135da731
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/common/window_close_tall_pressed.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/common/window_fullscreen_shadow.png b/chromium/ash/resources/default_100_percent/common/window_fullscreen_shadow.png
new file mode 100644
index 00000000000..8a6e37f3543
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/common/window_fullscreen_shadow.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/common/window_fullscreen_shadow_rtl.png b/chromium/ash/resources/default_100_percent/common/window_fullscreen_shadow_rtl.png
new file mode 100644
index 00000000000..6439bdc4dbd
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/common/window_fullscreen_shadow_rtl.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/common/window_header_base_active.png b/chromium/ash/resources/default_100_percent/common/window_header_base_active.png
new file mode 100644
index 00000000000..3881ec2553a
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/common/window_header_base_active.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/common/window_header_base_inactive.png b/chromium/ash/resources/default_100_percent/common/window_header_base_inactive.png
new file mode 100644
index 00000000000..e5a84899326
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/common/window_header_base_inactive.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/common/window_header_base_incognito_active.png b/chromium/ash/resources/default_100_percent/common/window_header_base_incognito_active.png
new file mode 100644
index 00000000000..d6de6253b15
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/common/window_header_base_incognito_active.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/common/window_header_base_incognito_inactive.png b/chromium/ash/resources/default_100_percent/common/window_header_base_incognito_inactive.png
new file mode 100644
index 00000000000..7850d9c9638
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/common/window_header_base_incognito_inactive.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/common/window_header_base_minimal.png b/chromium/ash/resources/default_100_percent/common/window_header_base_minimal.png
new file mode 100644
index 00000000000..c480496b1f2
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/common/window_header_base_minimal.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/common/window_header_shade_left.png b/chromium/ash/resources/default_100_percent/common/window_header_shade_left.png
new file mode 100644
index 00000000000..6e0dde8de15
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/common/window_header_shade_left.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/common/window_header_shade_middle.png b/chromium/ash/resources/default_100_percent/common/window_header_shade_middle.png
new file mode 100644
index 00000000000..7c1448a5250
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/common/window_header_shade_middle.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/common/window_header_shade_right.png b/chromium/ash/resources/default_100_percent/common/window_header_shade_right.png
new file mode 100644
index 00000000000..6e0dde8de15
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/common/window_header_shade_right.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/common/window_header_shade_top.png b/chromium/ash/resources/default_100_percent/common/window_header_shade_top.png
new file mode 100644
index 00000000000..9b40fee7f3a
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/common/window_header_shade_top.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/common/window_header_shade_top_left.png b/chromium/ash/resources/default_100_percent/common/window_header_shade_top_left.png
new file mode 100644
index 00000000000..021e017883d
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/common/window_header_shade_top_left.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/common/window_header_shade_top_right.png b/chromium/ash/resources/default_100_percent/common/window_header_shade_top_right.png
new file mode 100644
index 00000000000..53025879eed
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/common/window_header_shade_top_right.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/common/window_immersive_enter_hover.png b/chromium/ash/resources/default_100_percent/common/window_immersive_enter_hover.png
new file mode 100644
index 00000000000..e9c8aefd5d7
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/common/window_immersive_enter_hover.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/common/window_immersive_enter_normal.png b/chromium/ash/resources/default_100_percent/common/window_immersive_enter_normal.png
new file mode 100644
index 00000000000..bdb7c2a1c1c
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/common/window_immersive_enter_normal.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/common/window_immersive_enter_pressed.png b/chromium/ash/resources/default_100_percent/common/window_immersive_enter_pressed.png
new file mode 100644
index 00000000000..095a17f895d
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/common/window_immersive_enter_pressed.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/common/window_immersive_exit_hover.png b/chromium/ash/resources/default_100_percent/common/window_immersive_exit_hover.png
new file mode 100644
index 00000000000..a0d6e04a4c2
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/common/window_immersive_exit_hover.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/common/window_immersive_exit_normal.png b/chromium/ash/resources/default_100_percent/common/window_immersive_exit_normal.png
new file mode 100644
index 00000000000..4b6fd378cbf
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/common/window_immersive_exit_normal.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/common/window_immersive_exit_pressed.png b/chromium/ash/resources/default_100_percent/common/window_immersive_exit_pressed.png
new file mode 100644
index 00000000000..cc903e9ce46
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/common/window_immersive_exit_pressed.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/common/window_minimize_short_hover.png b/chromium/ash/resources/default_100_percent/common/window_minimize_short_hover.png
new file mode 100644
index 00000000000..ad54e15e28d
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/common/window_minimize_short_hover.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/common/window_minimize_short_normal.png b/chromium/ash/resources/default_100_percent/common/window_minimize_short_normal.png
new file mode 100644
index 00000000000..e4eb416d07d
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/common/window_minimize_short_normal.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/common/window_minimize_short_pressed.png b/chromium/ash/resources/default_100_percent/common/window_minimize_short_pressed.png
new file mode 100644
index 00000000000..32bccbebec1
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/common/window_minimize_short_pressed.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/common/window_position_left_hover.png b/chromium/ash/resources/default_100_percent/common/window_position_left_hover.png
new file mode 100644
index 00000000000..420d392031a
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/common/window_position_left_hover.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/common/window_position_left_hover_restore.png b/chromium/ash/resources/default_100_percent/common/window_position_left_hover_restore.png
new file mode 100644
index 00000000000..f94002b8461
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/common/window_position_left_hover_restore.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/common/window_position_left_normal.png b/chromium/ash/resources/default_100_percent/common/window_position_left_normal.png
new file mode 100644
index 00000000000..c082951044a
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/common/window_position_left_normal.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/common/window_position_left_normal_restore.png b/chromium/ash/resources/default_100_percent/common/window_position_left_normal_restore.png
new file mode 100644
index 00000000000..67312ab1604
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/common/window_position_left_normal_restore.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/common/window_position_left_pressed.png b/chromium/ash/resources/default_100_percent/common/window_position_left_pressed.png
new file mode 100644
index 00000000000..4201d2a9042
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/common/window_position_left_pressed.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/common/window_position_left_pressed_restore.png b/chromium/ash/resources/default_100_percent/common/window_position_left_pressed_restore.png
new file mode 100644
index 00000000000..88415533c1f
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/common/window_position_left_pressed_restore.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/common/window_position_middle_hover.png b/chromium/ash/resources/default_100_percent/common/window_position_middle_hover.png
new file mode 100644
index 00000000000..aaff1c59c82
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/common/window_position_middle_hover.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/common/window_position_middle_normal.png b/chromium/ash/resources/default_100_percent/common/window_position_middle_normal.png
new file mode 100644
index 00000000000..090b6699fe3
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/common/window_position_middle_normal.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/common/window_position_middle_pressed.png b/chromium/ash/resources/default_100_percent/common/window_position_middle_pressed.png
new file mode 100644
index 00000000000..66d29b0d95a
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/common/window_position_middle_pressed.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/common/window_position_right_hover.png b/chromium/ash/resources/default_100_percent/common/window_position_right_hover.png
new file mode 100644
index 00000000000..e424d950125
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/common/window_position_right_hover.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/common/window_position_right_hover_restore.png b/chromium/ash/resources/default_100_percent/common/window_position_right_hover_restore.png
new file mode 100644
index 00000000000..7583d26b72a
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/common/window_position_right_hover_restore.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/common/window_position_right_normal.png b/chromium/ash/resources/default_100_percent/common/window_position_right_normal.png
new file mode 100644
index 00000000000..d2c44791362
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/common/window_position_right_normal.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/common/window_position_right_normal_restore.png b/chromium/ash/resources/default_100_percent/common/window_position_right_normal_restore.png
new file mode 100644
index 00000000000..9da076101c4
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/common/window_position_right_normal_restore.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/common/window_position_right_pressed.png b/chromium/ash/resources/default_100_percent/common/window_position_right_pressed.png
new file mode 100644
index 00000000000..7fa9303bfb1
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/common/window_position_right_pressed.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/common/window_position_right_pressed_restore.png b/chromium/ash/resources/default_100_percent/common/window_position_right_pressed_restore.png
new file mode 100644
index 00000000000..790ee0e572b
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/common/window_position_right_pressed_restore.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/common/window_size_fullscreen_hover.png b/chromium/ash/resources/default_100_percent/common/window_size_fullscreen_hover.png
new file mode 100644
index 00000000000..a85c2b78831
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/common/window_size_fullscreen_hover.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/common/window_size_fullscreen_normal.png b/chromium/ash/resources/default_100_percent/common/window_size_fullscreen_normal.png
new file mode 100644
index 00000000000..500e8a4c47f
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/common/window_size_fullscreen_normal.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/common/window_size_fullscreen_pressed.png b/chromium/ash/resources/default_100_percent/common/window_size_fullscreen_pressed.png
new file mode 100644
index 00000000000..6afc6012069
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/common/window_size_fullscreen_pressed.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/common/window_size_short_black_hover.png b/chromium/ash/resources/default_100_percent/common/window_size_short_black_hover.png
new file mode 100644
index 00000000000..a0cdd53bd10
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/common/window_size_short_black_hover.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/common/window_size_short_black_normal.png b/chromium/ash/resources/default_100_percent/common/window_size_short_black_normal.png
new file mode 100644
index 00000000000..9b5a81d3dc2
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/common/window_size_short_black_normal.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/common/window_size_short_black_pressed.png b/chromium/ash/resources/default_100_percent/common/window_size_short_black_pressed.png
new file mode 100644
index 00000000000..bd87956471f
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/common/window_size_short_black_pressed.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/common/window_size_short_hover.png b/chromium/ash/resources/default_100_percent/common/window_size_short_hover.png
new file mode 100644
index 00000000000..c52c4aa183d
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/common/window_size_short_hover.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/common/window_size_short_normal.png b/chromium/ash/resources/default_100_percent/common/window_size_short_normal.png
new file mode 100644
index 00000000000..ebe911fc3d2
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/common/window_size_short_normal.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/common/window_size_short_pressed.png b/chromium/ash/resources/default_100_percent/common/window_size_short_pressed.png
new file mode 100644
index 00000000000..cf7cde8131a
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/common/window_size_short_pressed.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/common/window_size_tall_hover.png b/chromium/ash/resources/default_100_percent/common/window_size_tall_hover.png
new file mode 100644
index 00000000000..393beafb34d
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/common/window_size_tall_hover.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/common/window_size_tall_normal.png b/chromium/ash/resources/default_100_percent/common/window_size_tall_normal.png
new file mode 100644
index 00000000000..bba5130201b
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/common/window_size_tall_normal.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/common/window_size_tall_pressed.png b/chromium/ash/resources/default_100_percent/common/window_size_tall_pressed.png
new file mode 100644
index 00000000000..9047b4a9484
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/common/window_size_tall_pressed.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/cros/common/default_throbber.png b/chromium/ash/resources/default_100_percent/cros/common/default_throbber.png
new file mode 100644
index 00000000000..47eeb2c15e8
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/cros/common/default_throbber.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/cros/network/notification_3g.png b/chromium/ash/resources/default_100_percent/cros/network/notification_3g.png
new file mode 100644
index 00000000000..ced1c67827f
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/cros/network/notification_3g.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/cros/network/notification_lte.png b/chromium/ash/resources/default_100_percent/cros/network/notification_lte.png
new file mode 100644
index 00000000000..613962342c6
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/cros/network/notification_lte.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/cros/network/status_cellular_disabled.png b/chromium/ash/resources/default_100_percent/cros/network/status_cellular_disabled.png
new file mode 100644
index 00000000000..a00aeccf0ec
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/cros/network/status_cellular_disabled.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/cros/network/status_cellular_disabled_hover.png b/chromium/ash/resources/default_100_percent/cros/network/status_cellular_disabled_hover.png
new file mode 100644
index 00000000000..348682bc43c
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/cros/network/status_cellular_disabled_hover.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/cros/network/status_cellular_enabled.png b/chromium/ash/resources/default_100_percent/cros/network/status_cellular_enabled.png
new file mode 100644
index 00000000000..cc5b733c514
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/cros/network/status_cellular_enabled.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/cros/network/status_cellular_enabled_hover.png b/chromium/ash/resources/default_100_percent/cros/network/status_cellular_enabled_hover.png
new file mode 100644
index 00000000000..77879ed9f8e
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/cros/network/status_cellular_enabled_hover.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/cros/network/status_cellular_failed.png b/chromium/ash/resources/default_100_percent/cros/network/status_cellular_failed.png
new file mode 100644
index 00000000000..412d9d4180d
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/cros/network/status_cellular_failed.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/cros/network/status_data_low.png b/chromium/ash/resources/default_100_percent/cros/network/status_data_low.png
new file mode 100644
index 00000000000..b757e5fbb96
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/cros/network/status_data_low.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/cros/network/status_data_none.png b/chromium/ash/resources/default_100_percent/cros/network/status_data_none.png
new file mode 100644
index 00000000000..26d502875ff
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/cros/network/status_data_none.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/cros/network/status_network_failed.png b/chromium/ash/resources/default_100_percent/cros/network/status_network_failed.png
new file mode 100644
index 00000000000..c6d2710e7c5
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/cros/network/status_network_failed.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/cros/network/status_network_info.png b/chromium/ash/resources/default_100_percent/cros/network/status_network_info.png
new file mode 100644
index 00000000000..8ee22eea18c
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/cros/network/status_network_info.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/cros/network/status_network_info_hover.png b/chromium/ash/resources/default_100_percent/cros/network/status_network_info_hover.png
new file mode 100644
index 00000000000..51fb984f299
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/cros/network/status_network_info_hover.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/cros/network/status_wifi_disabled.png b/chromium/ash/resources/default_100_percent/cros/network/status_wifi_disabled.png
new file mode 100644
index 00000000000..dd3ff1d8790
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/cros/network/status_wifi_disabled.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/cros/network/status_wifi_disabled_hover.png b/chromium/ash/resources/default_100_percent/cros/network/status_wifi_disabled_hover.png
new file mode 100644
index 00000000000..70580161171
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/cros/network/status_wifi_disabled_hover.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/cros/network/status_wifi_enabled.png b/chromium/ash/resources/default_100_percent/cros/network/status_wifi_enabled.png
new file mode 100644
index 00000000000..11768b16eda
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/cros/network/status_wifi_enabled.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/cros/network/status_wifi_enabled_hover.png b/chromium/ash/resources/default_100_percent/cros/network/status_wifi_enabled_hover.png
new file mode 100644
index 00000000000..de634efa40f
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/cros/network/status_wifi_enabled_hover.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/cros/network/statusbar_network_1x.png b/chromium/ash/resources/default_100_percent/cros/network/statusbar_network_1x.png
new file mode 100644
index 00000000000..5a25e1c92fe
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/cros/network/statusbar_network_1x.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/cros/network/statusbar_network_1x_error.png b/chromium/ash/resources/default_100_percent/cros/network/statusbar_network_1x_error.png
new file mode 100644
index 00000000000..4b6defe6f2a
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/cros/network/statusbar_network_1x_error.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/cros/network/statusbar_network_1x_unknown.png b/chromium/ash/resources/default_100_percent/cros/network/statusbar_network_1x_unknown.png
new file mode 100644
index 00000000000..3d1b1a421dc
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/cros/network/statusbar_network_1x_unknown.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/cros/network/statusbar_network_3g_dark.png b/chromium/ash/resources/default_100_percent/cros/network/statusbar_network_3g_dark.png
new file mode 100644
index 00000000000..8b9b92a719f
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/cros/network/statusbar_network_3g_dark.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/cros/network/statusbar_network_3g_error.png b/chromium/ash/resources/default_100_percent/cros/network/statusbar_network_3g_error.png
new file mode 100644
index 00000000000..9894e7c4f85
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/cros/network/statusbar_network_3g_error.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/cros/network/statusbar_network_3g_light.png b/chromium/ash/resources/default_100_percent/cros/network/statusbar_network_3g_light.png
new file mode 100644
index 00000000000..83b5921e72e
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/cros/network/statusbar_network_3g_light.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/cros/network/statusbar_network_3g_unknown.png b/chromium/ash/resources/default_100_percent/cros/network/statusbar_network_3g_unknown.png
new file mode 100644
index 00000000000..a57300b093d
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/cros/network/statusbar_network_3g_unknown.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/cros/network/statusbar_network_4g_dark.png b/chromium/ash/resources/default_100_percent/cros/network/statusbar_network_4g_dark.png
new file mode 100644
index 00000000000..daa9f7e4b91
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/cros/network/statusbar_network_4g_dark.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/cros/network/statusbar_network_4g_light.png b/chromium/ash/resources/default_100_percent/cros/network/statusbar_network_4g_light.png
new file mode 100644
index 00000000000..c199a897e18
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/cros/network/statusbar_network_4g_light.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/cros/network/statusbar_network_arcs_dark.png b/chromium/ash/resources/default_100_percent/cros/network/statusbar_network_arcs_dark.png
new file mode 100644
index 00000000000..29b6c725d99
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/cros/network/statusbar_network_arcs_dark.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/cros/network/statusbar_network_arcs_light.png b/chromium/ash/resources/default_100_percent/cros/network/statusbar_network_arcs_light.png
new file mode 100644
index 00000000000..2d783f6ed15
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/cros/network/statusbar_network_arcs_light.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/cros/network/statusbar_network_bars_dark.png b/chromium/ash/resources/default_100_percent/cros/network/statusbar_network_bars_dark.png
new file mode 100644
index 00000000000..9facf9fea87
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/cros/network/statusbar_network_bars_dark.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/cros/network/statusbar_network_bars_light.png b/chromium/ash/resources/default_100_percent/cros/network/statusbar_network_bars_light.png
new file mode 100644
index 00000000000..4f77026ce0e
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/cros/network/statusbar_network_bars_light.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/cros/network/statusbar_network_edge_dark.png b/chromium/ash/resources/default_100_percent/cros/network/statusbar_network_edge_dark.png
new file mode 100644
index 00000000000..8fa7443a917
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/cros/network/statusbar_network_edge_dark.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/cros/network/statusbar_network_edge_light.png b/chromium/ash/resources/default_100_percent/cros/network/statusbar_network_edge_light.png
new file mode 100644
index 00000000000..c9a525cf809
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/cros/network/statusbar_network_edge_light.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/cros/network/statusbar_network_evdo_dark.png b/chromium/ash/resources/default_100_percent/cros/network/statusbar_network_evdo_dark.png
new file mode 100644
index 00000000000..85c0378b815
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/cros/network/statusbar_network_evdo_dark.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/cros/network/statusbar_network_evdo_light.png b/chromium/ash/resources/default_100_percent/cros/network/statusbar_network_evdo_light.png
new file mode 100644
index 00000000000..1ae959b312c
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/cros/network/statusbar_network_evdo_light.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/cros/network/statusbar_network_gprs_dark.png b/chromium/ash/resources/default_100_percent/cros/network/statusbar_network_gprs_dark.png
new file mode 100644
index 00000000000..03357a6e1d5
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/cros/network/statusbar_network_gprs_dark.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/cros/network/statusbar_network_gprs_light.png b/chromium/ash/resources/default_100_percent/cros/network/statusbar_network_gprs_light.png
new file mode 100644
index 00000000000..3953406b7a8
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/cros/network/statusbar_network_gprs_light.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/cros/network/statusbar_network_hspa_dark.png b/chromium/ash/resources/default_100_percent/cros/network/statusbar_network_hspa_dark.png
new file mode 100644
index 00000000000..bad20a762ba
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/cros/network/statusbar_network_hspa_dark.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/cros/network/statusbar_network_hspa_light.png b/chromium/ash/resources/default_100_percent/cros/network/statusbar_network_hspa_light.png
new file mode 100644
index 00000000000..1eedc069d42
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/cros/network/statusbar_network_hspa_light.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/cros/network/statusbar_network_hspa_plus_dark.png b/chromium/ash/resources/default_100_percent/cros/network/statusbar_network_hspa_plus_dark.png
new file mode 100644
index 00000000000..2b34e394463
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/cros/network/statusbar_network_hspa_plus_dark.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/cros/network/statusbar_network_hspa_plus_light.png b/chromium/ash/resources/default_100_percent/cros/network/statusbar_network_hspa_plus_light.png
new file mode 100644
index 00000000000..2ee0809b1e0
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/cros/network/statusbar_network_hspa_plus_light.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/cros/network/statusbar_network_lte_advanced_dark.png b/chromium/ash/resources/default_100_percent/cros/network/statusbar_network_lte_advanced_dark.png
new file mode 100644
index 00000000000..db3ab77ca76
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/cros/network/statusbar_network_lte_advanced_dark.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/cros/network/statusbar_network_lte_advanced_light.png b/chromium/ash/resources/default_100_percent/cros/network/statusbar_network_lte_advanced_light.png
new file mode 100644
index 00000000000..66d25d373c9
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/cros/network/statusbar_network_lte_advanced_light.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/cros/network/statusbar_network_lte_dark.png b/chromium/ash/resources/default_100_percent/cros/network/statusbar_network_lte_dark.png
new file mode 100644
index 00000000000..84b7c7ccc63
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/cros/network/statusbar_network_lte_dark.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/cros/network/statusbar_network_lte_light.png b/chromium/ash/resources/default_100_percent/cros/network/statusbar_network_lte_light.png
new file mode 100644
index 00000000000..29cca1d7f05
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/cros/network/statusbar_network_lte_light.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/cros/network/statusbar_network_roaming_dark.png b/chromium/ash/resources/default_100_percent/cros/network/statusbar_network_roaming_dark.png
new file mode 100644
index 00000000000..f4d9becb8eb
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/cros/network/statusbar_network_roaming_dark.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/cros/network/statusbar_network_roaming_light.png b/chromium/ash/resources/default_100_percent/cros/network/statusbar_network_roaming_light.png
new file mode 100644
index 00000000000..70544b63ab5
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/cros/network/statusbar_network_roaming_light.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/cros/network/statusbar_network_secure_dark.png b/chromium/ash/resources/default_100_percent/cros/network/statusbar_network_secure_dark.png
new file mode 100644
index 00000000000..98b0b51777d
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/cros/network/statusbar_network_secure_dark.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/cros/network/statusbar_network_secure_light.png b/chromium/ash/resources/default_100_percent/cros/network/statusbar_network_secure_light.png
new file mode 100644
index 00000000000..8c66a67a47f
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/cros/network/statusbar_network_secure_light.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/cros/network/statusbar_network_vpn_badge.png b/chromium/ash/resources/default_100_percent/cros/network/statusbar_network_vpn_badge.png
new file mode 100644
index 00000000000..906276e6828
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/cros/network/statusbar_network_vpn_badge.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/cros/network/statusbar_vpn_dark.png b/chromium/ash/resources/default_100_percent/cros/network/statusbar_vpn_dark.png
new file mode 100644
index 00000000000..a060df37f3b
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/cros/network/statusbar_vpn_dark.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/cros/network/statusbar_wired.png b/chromium/ash/resources/default_100_percent/cros/network/statusbar_wired.png
new file mode 100644
index 00000000000..14335f7556c
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/cros/network/statusbar_wired.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/cros/notification/notification_low_power_charger.png b/chromium/ash/resources/default_100_percent/cros/notification/notification_low_power_charger.png
new file mode 100644
index 00000000000..d410c4ba830
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/cros/notification/notification_low_power_charger.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/cros/status/multiprofiles_add.png b/chromium/ash/resources/default_100_percent/cros/status/multiprofiles_add.png
new file mode 100644
index 00000000000..0e8a7a16889
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/cros/status/multiprofiles_add.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/cros/status/status_accessibility_dark.png b/chromium/ash/resources/default_100_percent/cros/status/status_accessibility_dark.png
new file mode 100644
index 00000000000..acfb0f0c133
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/cros/status/status_accessibility_dark.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/cros/status/status_accessibility_mode.png b/chromium/ash/resources/default_100_percent/cros/status/status_accessibility_mode.png
new file mode 100644
index 00000000000..e3bf6ca896d
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/cros/status/status_accessibility_mode.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/cros/status/status_audio_device_bluetooth.png b/chromium/ash/resources/default_100_percent/cros/status/status_audio_device_bluetooth.png
new file mode 100644
index 00000000000..073b8213972
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/cros/status/status_audio_device_bluetooth.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/cros/status/status_audio_device_hdmi.png b/chromium/ash/resources/default_100_percent/cros/status/status_audio_device_hdmi.png
new file mode 100644
index 00000000000..4112442f666
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/cros/status/status_audio_device_hdmi.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/cros/status/status_audio_device_headphones.png b/chromium/ash/resources/default_100_percent/cros/status/status_audio_device_headphones.png
new file mode 100644
index 00000000000..93267c7e189
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/cros/status/status_audio_device_headphones.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/cros/status/status_audio_device_usb.png b/chromium/ash/resources/default_100_percent/cros/status/status_audio_device_usb.png
new file mode 100644
index 00000000000..b09326815fc
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/cros/status/status_audio_device_usb.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/cros/status/status_bluetooth.png b/chromium/ash/resources/default_100_percent/cros/status/status_bluetooth.png
new file mode 100644
index 00000000000..25fbff092ff
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/cros/status/status_bluetooth.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/cros/status/status_bluetooth_disabled.png b/chromium/ash/resources/default_100_percent/cros/status/status_bluetooth_disabled.png
new file mode 100644
index 00000000000..4f8cae037b2
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/cros/status/status_bluetooth_disabled.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/cros/status/status_bluetooth_disabled_hover.png b/chromium/ash/resources/default_100_percent/cros/status/status_bluetooth_disabled_hover.png
new file mode 100644
index 00000000000..66b1dd2a55a
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/cros/status/status_bluetooth_disabled_hover.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/cros/status/status_bluetooth_enabled.png b/chromium/ash/resources/default_100_percent/cros/status/status_bluetooth_enabled.png
new file mode 100644
index 00000000000..c06841b46a1
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/cros/status/status_bluetooth_enabled.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/cros/status/status_bluetooth_enabled_hover.png b/chromium/ash/resources/default_100_percent/cros/status/status_bluetooth_enabled_hover.png
new file mode 100644
index 00000000000..0bf56fb86c8
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/cros/status/status_bluetooth_enabled_hover.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/cros/status/status_brightness.png b/chromium/ash/resources/default_100_percent/cros/status/status_brightness.png
new file mode 100644
index 00000000000..08ce57ada32
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/cros/status/status_brightness.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/cros/status/status_capslock.png b/chromium/ash/resources/default_100_percent/cros/status/status_capslock.png
new file mode 100644
index 00000000000..8b0e095ed9c
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/cros/status/status_capslock.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/cros/status/status_capslock_dark.png b/chromium/ash/resources/default_100_percent/cros/status/status_capslock_dark.png
new file mode 100644
index 00000000000..0b3b26927ce
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/cros/status/status_capslock_dark.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/cros/status/status_display.png b/chromium/ash/resources/default_100_percent/cros/status/status_display.png
new file mode 100644
index 00000000000..df3f63dcf91
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/cros/status/status_display.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/cros/status/status_display_dark.png b/chromium/ash/resources/default_100_percent/cros/status/status_display_dark.png
new file mode 100644
index 00000000000..6bb251b9df2
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/cros/status/status_display_dark.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/cros/status/status_drive.png b/chromium/ash/resources/default_100_percent/cros/status/status_drive.png
new file mode 100644
index 00000000000..173c45c18c5
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/cros/status/status_drive.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/cros/status/status_drive_item_cancel.png b/chromium/ash/resources/default_100_percent/cros/status/status_drive_item_cancel.png
new file mode 100644
index 00000000000..766bbcecc03
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/cros/status/status_drive_item_cancel.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/cros/status/status_drive_item_cancel_hover.png b/chromium/ash/resources/default_100_percent/cros/status/status_drive_item_cancel_hover.png
new file mode 100644
index 00000000000..5c746c505fb
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/cros/status/status_drive_item_cancel_hover.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/cros/status/status_drive_item_done.png b/chromium/ash/resources/default_100_percent/cros/status/status_drive_item_done.png
new file mode 100644
index 00000000000..ae2cfcd14c1
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/cros/status/status_drive_item_done.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/cros/status/status_drive_item_failed.png b/chromium/ash/resources/default_100_percent/cros/status/status_drive_item_failed.png
new file mode 100644
index 00000000000..09e0c812791
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/cros/status/status_drive_item_failed.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/cros/status/status_drive_light.png b/chromium/ash/resources/default_100_percent/cros/status/status_drive_light.png
new file mode 100644
index 00000000000..0b767e1b7dc
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/cros/status/status_drive_light.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/cros/status/status_guest_icon.png b/chromium/ash/resources/default_100_percent/cros/status/status_guest_icon.png
new file mode 100644
index 00000000000..53113b8a368
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/cros/status/status_guest_icon.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/cros/status/status_help.png b/chromium/ash/resources/default_100_percent/cros/status/status_help.png
new file mode 100644
index 00000000000..9410cbd36ac
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/cros/status/status_help.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/cros/status/status_help_hover.png b/chromium/ash/resources/default_100_percent/cros/status/status_help_hover.png
new file mode 100644
index 00000000000..49e3ad27e9b
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/cros/status/status_help_hover.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/cros/status/status_ime.png b/chromium/ash/resources/default_100_percent/cros/status/status_ime.png
new file mode 100644
index 00000000000..b40d41ee97c
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/cros/status/status_ime.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/cros/status/status_less.png b/chromium/ash/resources/default_100_percent/cros/status/status_less.png
new file mode 100644
index 00000000000..4289b9da7b8
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/cros/status/status_less.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/cros/status/status_locale.png b/chromium/ash/resources/default_100_percent/cros/status/status_locale.png
new file mode 100644
index 00000000000..ef84488f7be
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/cros/status/status_locale.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/cros/status/status_lockscreen.png b/chromium/ash/resources/default_100_percent/cros/status/status_lockscreen.png
new file mode 100644
index 00000000000..51db39506f1
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/cros/status/status_lockscreen.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/cros/status/status_lockscreen_hover.png b/chromium/ash/resources/default_100_percent/cros/status/status_lockscreen_hover.png
new file mode 100644
index 00000000000..b707840cc34
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/cros/status/status_lockscreen_hover.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/cros/status/status_logout_button_normal_bottom.png b/chromium/ash/resources/default_100_percent/cros/status/status_logout_button_normal_bottom.png
new file mode 100644
index 00000000000..48a9b785df4
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/cros/status/status_logout_button_normal_bottom.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/cros/status/status_logout_button_normal_bottom_left.png b/chromium/ash/resources/default_100_percent/cros/status/status_logout_button_normal_bottom_left.png
new file mode 100644
index 00000000000..ed08895c1dc
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/cros/status/status_logout_button_normal_bottom_left.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/cros/status/status_logout_button_normal_bottom_right.png b/chromium/ash/resources/default_100_percent/cros/status/status_logout_button_normal_bottom_right.png
new file mode 100644
index 00000000000..5c411664aba
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/cros/status/status_logout_button_normal_bottom_right.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/cros/status/status_logout_button_normal_center.png b/chromium/ash/resources/default_100_percent/cros/status/status_logout_button_normal_center.png
new file mode 100644
index 00000000000..674a2caf3ed
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/cros/status/status_logout_button_normal_center.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/cros/status/status_logout_button_normal_left.png b/chromium/ash/resources/default_100_percent/cros/status/status_logout_button_normal_left.png
new file mode 100644
index 00000000000..0ca8c152c87
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/cros/status/status_logout_button_normal_left.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/cros/status/status_logout_button_normal_right.png b/chromium/ash/resources/default_100_percent/cros/status/status_logout_button_normal_right.png
new file mode 100644
index 00000000000..107f25ee038
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/cros/status/status_logout_button_normal_right.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/cros/status/status_logout_button_normal_top.png b/chromium/ash/resources/default_100_percent/cros/status/status_logout_button_normal_top.png
new file mode 100644
index 00000000000..0c882a30355
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/cros/status/status_logout_button_normal_top.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/cros/status/status_logout_button_normal_top_left.png b/chromium/ash/resources/default_100_percent/cros/status/status_logout_button_normal_top_left.png
new file mode 100644
index 00000000000..a2885849256
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/cros/status/status_logout_button_normal_top_left.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/cros/status/status_logout_button_normal_top_right.png b/chromium/ash/resources/default_100_percent/cros/status/status_logout_button_normal_top_right.png
new file mode 100644
index 00000000000..ac7c0dffecc
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/cros/status/status_logout_button_normal_top_right.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/cros/status/status_logout_button_pushed_bottom.png b/chromium/ash/resources/default_100_percent/cros/status/status_logout_button_pushed_bottom.png
new file mode 100644
index 00000000000..07b1a81db81
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/cros/status/status_logout_button_pushed_bottom.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/cros/status/status_logout_button_pushed_bottom_left.png b/chromium/ash/resources/default_100_percent/cros/status/status_logout_button_pushed_bottom_left.png
new file mode 100644
index 00000000000..a0c58bbca50
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/cros/status/status_logout_button_pushed_bottom_left.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/cros/status/status_logout_button_pushed_bottom_right.png b/chromium/ash/resources/default_100_percent/cros/status/status_logout_button_pushed_bottom_right.png
new file mode 100644
index 00000000000..f20a29d47e6
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/cros/status/status_logout_button_pushed_bottom_right.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/cros/status/status_logout_button_pushed_center.png b/chromium/ash/resources/default_100_percent/cros/status/status_logout_button_pushed_center.png
new file mode 100644
index 00000000000..948a9225cbc
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/cros/status/status_logout_button_pushed_center.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/cros/status/status_logout_button_pushed_left.png b/chromium/ash/resources/default_100_percent/cros/status/status_logout_button_pushed_left.png
new file mode 100644
index 00000000000..e896e8105e6
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/cros/status/status_logout_button_pushed_left.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/cros/status/status_logout_button_pushed_right.png b/chromium/ash/resources/default_100_percent/cros/status/status_logout_button_pushed_right.png
new file mode 100644
index 00000000000..47e1e4ba51f
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/cros/status/status_logout_button_pushed_right.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/cros/status/status_logout_button_pushed_top.png b/chromium/ash/resources/default_100_percent/cros/status/status_logout_button_pushed_top.png
new file mode 100644
index 00000000000..6e304f22192
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/cros/status/status_logout_button_pushed_top.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/cros/status/status_logout_button_pushed_top_left.png b/chromium/ash/resources/default_100_percent/cros/status/status_logout_button_pushed_top_left.png
new file mode 100644
index 00000000000..aecbe19888c
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/cros/status/status_logout_button_pushed_top_left.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/cros/status/status_logout_button_pushed_top_right.png b/chromium/ash/resources/default_100_percent/cros/status/status_logout_button_pushed_top_right.png
new file mode 100644
index 00000000000..535677029ef
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/cros/status/status_logout_button_pushed_top_right.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/cros/status/status_managed.png b/chromium/ash/resources/default_100_percent/cros/status/status_managed.png
new file mode 100644
index 00000000000..88155e7537c
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/cros/status/status_managed.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/cros/status/status_managed_mode_user.png b/chromium/ash/resources/default_100_percent/cros/status/status_managed_mode_user.png
new file mode 100644
index 00000000000..51104172bbd
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/cros/status/status_managed_mode_user.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/cros/status/status_managed_tray.png b/chromium/ash/resources/default_100_percent/cros/status/status_managed_tray.png
new file mode 100644
index 00000000000..709546ec516
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/cros/status/status_managed_tray.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/cros/status/status_more.png b/chromium/ash/resources/default_100_percent/cros/status/status_more.png
new file mode 100644
index 00000000000..41b5995041a
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/cros/status/status_more.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/cros/status/status_power_small_all.png b/chromium/ash/resources/default_100_percent/cros/status/status_power_small_all.png
new file mode 100644
index 00000000000..f09e1fc909c
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/cros/status/status_power_small_all.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/cros/status/status_power_small_all_dark.png b/chromium/ash/resources/default_100_percent/cros/status/status_power_small_all_dark.png
new file mode 100644
index 00000000000..c5f4a8633ea
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/cros/status/status_power_small_all_dark.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/cros/status/status_power_small_all_dark_discharging.png b/chromium/ash/resources/default_100_percent/cros/status/status_power_small_all_dark_discharging.png
new file mode 100644
index 00000000000..19bcaa13f5b
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/cros/status/status_power_small_all_dark_discharging.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/cros/status/status_power_small_all_dark_fluctuating.png b/chromium/ash/resources/default_100_percent/cros/status/status_power_small_all_dark_fluctuating.png
new file mode 100644
index 00000000000..d8a10e8d2bd
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/cros/status/status_power_small_all_dark_fluctuating.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/cros/status/status_power_small_all_discharging.png b/chromium/ash/resources/default_100_percent/cros/status/status_power_small_all_discharging.png
new file mode 100644
index 00000000000..108d98ba045
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/cros/status/status_power_small_all_discharging.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/cros/status/status_power_small_all_fluctuating.png b/chromium/ash/resources/default_100_percent/cros/status/status_power_small_all_fluctuating.png
new file mode 100644
index 00000000000..3c37c5977c4
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/cros/status/status_power_small_all_fluctuating.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/cros/status/status_screen_share_dark.png b/chromium/ash/resources/default_100_percent/cros/status/status_screen_share_dark.png
new file mode 100644
index 00000000000..173c45c18c5
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/cros/status/status_screen_share_dark.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/cros/status/status_screen_share_light.png b/chromium/ash/resources/default_100_percent/cros/status/status_screen_share_light.png
new file mode 100644
index 00000000000..0b767e1b7dc
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/cros/status/status_screen_share_light.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/cros/status/status_session_length_limit_timer.png b/chromium/ash/resources/default_100_percent/cros/status/status_session_length_limit_timer.png
new file mode 100644
index 00000000000..2e0fd04485e
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/cros/status/status_session_length_limit_timer.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/cros/status/status_settings.png b/chromium/ash/resources/default_100_percent/cros/status/status_settings.png
new file mode 100644
index 00000000000..8de16dc9e5d
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/cros/status/status_settings.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/cros/status/status_shutdown.png b/chromium/ash/resources/default_100_percent/cros/status/status_shutdown.png
new file mode 100644
index 00000000000..00328f97b2d
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/cros/status/status_shutdown.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/cros/status/status_shutdown_hover.png b/chromium/ash/resources/default_100_percent/cros/status/status_shutdown_hover.png
new file mode 100644
index 00000000000..065865205de
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/cros/status/status_shutdown_hover.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/cros/status/status_sms.png b/chromium/ash/resources/default_100_percent/cros/status/status_sms.png
new file mode 100644
index 00000000000..093ce5852b1
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/cros/status/status_sms.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/cros/status/status_tracing.png b/chromium/ash/resources/default_100_percent/cros/status/status_tracing.png
new file mode 100644
index 00000000000..eaba620c959
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/cros/status/status_tracing.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/cros/status/status_update.png b/chromium/ash/resources/default_100_percent/cros/status/status_update.png
new file mode 100644
index 00000000000..98256a8eb2b
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/cros/status/status_update.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/cros/status/status_update_dark.png b/chromium/ash/resources/default_100_percent/cros/status/status_update_dark.png
new file mode 100644
index 00000000000..397bd6308c1
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/cros/status/status_update_dark.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/cros/status/status_update_dark_green.png b/chromium/ash/resources/default_100_percent/cros/status/status_update_dark_green.png
new file mode 100644
index 00000000000..dd5d363a7a2
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/cros/status/status_update_dark_green.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/cros/status/status_update_dark_orange.png b/chromium/ash/resources/default_100_percent/cros/status/status_update_dark_orange.png
new file mode 100644
index 00000000000..4ad80793406
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/cros/status/status_update_dark_orange.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/cros/status/status_update_dark_red.png b/chromium/ash/resources/default_100_percent/cros/status/status_update_dark_red.png
new file mode 100644
index 00000000000..444cdacbe50
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/cros/status/status_update_dark_red.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/cros/status/status_update_green.png b/chromium/ash/resources/default_100_percent/cros/status/status_update_green.png
new file mode 100644
index 00000000000..cd94452271e
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/cros/status/status_update_green.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/cros/status/status_update_orange.png b/chromium/ash/resources/default_100_percent/cros/status/status_update_orange.png
new file mode 100644
index 00000000000..43d61c7e4b6
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/cros/status/status_update_orange.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/cros/status/status_update_red.png b/chromium/ash/resources/default_100_percent/cros/status/status_update_red.png
new file mode 100644
index 00000000000..a73b85a7544
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/cros/status/status_update_red.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/cros/status/status_volume_dark.png b/chromium/ash/resources/default_100_percent/cros/status/status_volume_dark.png
new file mode 100644
index 00000000000..76dc1857ae3
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/cros/status/status_volume_dark.png
Binary files differ
diff --git a/chromium/ash/resources/default_100_percent/cros/status/status_volume_mute.png b/chromium/ash/resources/default_100_percent/cros/status/status_volume_mute.png
new file mode 100644
index 00000000000..04aded3ba1f
--- /dev/null
+++ b/chromium/ash/resources/default_100_percent/cros/status/status_volume_mute.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/common/alert_small.png b/chromium/ash/resources/default_200_percent/common/alert_small.png
new file mode 100644
index 00000000000..544f98ae96c
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/common/alert_small.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/common/alt_launcher/launcher_background.png b/chromium/ash/resources/default_200_percent/common/alt_launcher/launcher_background.png
new file mode 100644
index 00000000000..5a6220414aa
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/common/alt_launcher/launcher_background.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/common/alt_launcher/launcher_underline_active.png b/chromium/ash/resources/default_200_percent/common/alt_launcher/launcher_underline_active.png
new file mode 100644
index 00000000000..f89d8f5e2f3
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/common/alt_launcher/launcher_underline_active.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/common/alt_launcher/launcher_underline_running.png b/chromium/ash/resources/default_200_percent/common/alt_launcher/launcher_underline_running.png
new file mode 100644
index 00000000000..ede3d2d75ae
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/common/alt_launcher/launcher_underline_running.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/common/alt_launcher/status_app_menu_icon.png b/chromium/ash/resources/default_200_percent/common/alt_launcher/status_app_menu_icon.png
new file mode 100644
index 00000000000..2e1a70f7dc5
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/common/alt_launcher/status_app_menu_icon.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/common/alt_launcher/status_guest_icon.png b/chromium/ash/resources/default_200_percent/common/alt_launcher/status_guest_icon.png
new file mode 100644
index 00000000000..11aca56a942
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/common/alt_launcher/status_guest_icon.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/common/alt_launcher/status_icon_background_normal.png b/chromium/ash/resources/default_200_percent/common/alt_launcher/status_icon_background_normal.png
new file mode 100644
index 00000000000..cc2d1b75ccb
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/common/alt_launcher/status_icon_background_normal.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/common/alt_launcher/status_icon_background_onblack_normal.png b/chromium/ash/resources/default_200_percent/common/alt_launcher/status_icon_background_onblack_normal.png
new file mode 100644
index 00000000000..55bac5893e7
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/common/alt_launcher/status_icon_background_onblack_normal.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/common/alt_launcher/status_icon_background_pressed.png b/chromium/ash/resources/default_200_percent/common/alt_launcher/status_icon_background_pressed.png
new file mode 100644
index 00000000000..498b80bce22
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/common/alt_launcher/status_icon_background_pressed.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/common/alt_launcher/status_notification_icon.png b/chromium/ash/resources/default_200_percent/common/alt_launcher/status_notification_icon.png
new file mode 100644
index 00000000000..19322c5c064
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/common/alt_launcher/status_notification_icon.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/common/alt_launcher/status_tray_normal_center.png b/chromium/ash/resources/default_200_percent/common/alt_launcher/status_tray_normal_center.png
new file mode 100644
index 00000000000..9b072370ab5
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/common/alt_launcher/status_tray_normal_center.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/common/alt_launcher/status_tray_normal_left.png b/chromium/ash/resources/default_200_percent/common/alt_launcher/status_tray_normal_left.png
new file mode 100644
index 00000000000..dba3b692b4c
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/common/alt_launcher/status_tray_normal_left.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/common/alt_launcher/status_tray_normal_onblack_center.png b/chromium/ash/resources/default_200_percent/common/alt_launcher/status_tray_normal_onblack_center.png
new file mode 100644
index 00000000000..ea177511b81
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/common/alt_launcher/status_tray_normal_onblack_center.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/common/alt_launcher/status_tray_normal_onblack_left.png b/chromium/ash/resources/default_200_percent/common/alt_launcher/status_tray_normal_onblack_left.png
new file mode 100644
index 00000000000..ff4e1efe73b
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/common/alt_launcher/status_tray_normal_onblack_left.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/common/alt_launcher/status_tray_normal_onblack_right.png b/chromium/ash/resources/default_200_percent/common/alt_launcher/status_tray_normal_onblack_right.png
new file mode 100644
index 00000000000..9284fa8f83c
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/common/alt_launcher/status_tray_normal_onblack_right.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/common/alt_launcher/status_tray_normal_right.png b/chromium/ash/resources/default_200_percent/common/alt_launcher/status_tray_normal_right.png
new file mode 100644
index 00000000000..bb129f5bf53
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/common/alt_launcher/status_tray_normal_right.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/common/alt_launcher/status_tray_pressed_center.png b/chromium/ash/resources/default_200_percent/common/alt_launcher/status_tray_pressed_center.png
new file mode 100644
index 00000000000..953e1edd366
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/common/alt_launcher/status_tray_pressed_center.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/common/alt_launcher/status_tray_pressed_left.png b/chromium/ash/resources/default_200_percent/common/alt_launcher/status_tray_pressed_left.png
new file mode 100644
index 00000000000..8c6f84c003b
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/common/alt_launcher/status_tray_pressed_left.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/common/alt_launcher/status_tray_pressed_right.png b/chromium/ash/resources/default_200_percent/common/alt_launcher/status_tray_pressed_right.png
new file mode 100644
index 00000000000..27207a13835
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/common/alt_launcher/status_tray_pressed_right.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/common/alt_launcher/status_tray_vertical_normal_bottom.png b/chromium/ash/resources/default_200_percent/common/alt_launcher/status_tray_vertical_normal_bottom.png
new file mode 100644
index 00000000000..0804518dfdf
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/common/alt_launcher/status_tray_vertical_normal_bottom.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/common/alt_launcher/status_tray_vertical_normal_center.png b/chromium/ash/resources/default_200_percent/common/alt_launcher/status_tray_vertical_normal_center.png
new file mode 100644
index 00000000000..96859ea6824
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/common/alt_launcher/status_tray_vertical_normal_center.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/common/alt_launcher/status_tray_vertical_normal_onblack_bottom.png b/chromium/ash/resources/default_200_percent/common/alt_launcher/status_tray_vertical_normal_onblack_bottom.png
new file mode 100644
index 00000000000..85bd1f1e766
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/common/alt_launcher/status_tray_vertical_normal_onblack_bottom.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/common/alt_launcher/status_tray_vertical_normal_onblack_center.png b/chromium/ash/resources/default_200_percent/common/alt_launcher/status_tray_vertical_normal_onblack_center.png
new file mode 100644
index 00000000000..d880b32eecd
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/common/alt_launcher/status_tray_vertical_normal_onblack_center.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/common/alt_launcher/status_tray_vertical_normal_onblack_top.png b/chromium/ash/resources/default_200_percent/common/alt_launcher/status_tray_vertical_normal_onblack_top.png
new file mode 100644
index 00000000000..18848265b75
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/common/alt_launcher/status_tray_vertical_normal_onblack_top.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/common/alt_launcher/status_tray_vertical_normal_top.png b/chromium/ash/resources/default_200_percent/common/alt_launcher/status_tray_vertical_normal_top.png
new file mode 100644
index 00000000000..fed0cab22d8
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/common/alt_launcher/status_tray_vertical_normal_top.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/common/alt_launcher/status_tray_vertical_pressed_bottom.png b/chromium/ash/resources/default_200_percent/common/alt_launcher/status_tray_vertical_pressed_bottom.png
new file mode 100644
index 00000000000..53b3a0d2058
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/common/alt_launcher/status_tray_vertical_pressed_bottom.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/common/alt_launcher/status_tray_vertical_pressed_center.png b/chromium/ash/resources/default_200_percent/common/alt_launcher/status_tray_vertical_pressed_center.png
new file mode 100644
index 00000000000..4ec633ff514
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/common/alt_launcher/status_tray_vertical_pressed_center.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/common/alt_launcher/status_tray_vertical_pressed_top.png b/chromium/ash/resources/default_200_percent/common/alt_launcher/status_tray_vertical_pressed_top.png
new file mode 100644
index 00000000000..e47b08106d3
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/common/alt_launcher/status_tray_vertical_pressed_top.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/common/launcher/launcher_appmenu.png b/chromium/ash/resources/default_200_percent/common/launcher/launcher_appmenu.png
new file mode 100644
index 00000000000..8a3ba66a002
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/common/launcher/launcher_appmenu.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/common/launcher/launcher_appmenu_hover.png b/chromium/ash/resources/default_200_percent/common/launcher/launcher_appmenu_hover.png
new file mode 100644
index 00000000000..7762f27b061
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/common/launcher/launcher_appmenu_hover.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/common/launcher/launcher_appmenu_pressed.png b/chromium/ash/resources/default_200_percent/common/launcher/launcher_appmenu_pressed.png
new file mode 100644
index 00000000000..82ca4b26f1e
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/common/launcher/launcher_appmenu_pressed.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/common/launcher/launcher_background.png b/chromium/ash/resources/default_200_percent/common/launcher/launcher_background.png
new file mode 100644
index 00000000000..3a3e6a17440
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/common/launcher/launcher_background.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/common/launcher/launcher_background_left.png b/chromium/ash/resources/default_200_percent/common/launcher/launcher_background_left.png
new file mode 100644
index 00000000000..165f6b8f3cb
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/common/launcher/launcher_background_left.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/common/launcher/launcher_background_right.png b/chromium/ash/resources/default_200_percent/common/launcher/launcher_background_right.png
new file mode 100644
index 00000000000..593780b98a2
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/common/launcher/launcher_background_right.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/common/launcher/launcher_browser.png b/chromium/ash/resources/default_200_percent/common/launcher/launcher_browser.png
new file mode 100644
index 00000000000..0f022212a57
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/common/launcher/launcher_browser.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/common/launcher/launcher_browser_panel.png b/chromium/ash/resources/default_200_percent/common/launcher/launcher_browser_panel.png
new file mode 100644
index 00000000000..457026e4940
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/common/launcher/launcher_browser_panel.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/common/launcher/launcher_dimming.png b/chromium/ash/resources/default_200_percent/common/launcher/launcher_dimming.png
new file mode 100644
index 00000000000..6843219c71e
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/common/launcher/launcher_dimming.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/common/launcher/launcher_dimming_left.png b/chromium/ash/resources/default_200_percent/common/launcher/launcher_dimming_left.png
new file mode 100644
index 00000000000..30d20e87945
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/common/launcher/launcher_dimming_left.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/common/launcher/launcher_dimming_right.png b/chromium/ash/resources/default_200_percent/common/launcher/launcher_dimming_right.png
new file mode 100644
index 00000000000..72f5a6f4dd0
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/common/launcher/launcher_dimming_right.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/common/launcher/launcher_incognito_browser.png b/chromium/ash/resources/default_200_percent/common/launcher/launcher_incognito_browser.png
new file mode 100644
index 00000000000..f6d48210129
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/common/launcher/launcher_incognito_browser.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/common/launcher/launcher_incognito_browser_panel.png b/chromium/ash/resources/default_200_percent/common/launcher/launcher_incognito_browser_panel.png
new file mode 100644
index 00000000000..4ba2a864bcf
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/common/launcher/launcher_incognito_browser_panel.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/common/launcher/launcher_overflow.png b/chromium/ash/resources/default_200_percent/common/launcher/launcher_overflow.png
new file mode 100644
index 00000000000..bddc06cb7cd
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/common/launcher/launcher_overflow.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/common/launcher/launcher_underline_bottom_active.png b/chromium/ash/resources/default_200_percent/common/launcher/launcher_underline_bottom_active.png
new file mode 100644
index 00000000000..21181153dc2
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/common/launcher/launcher_underline_bottom_active.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/common/launcher/launcher_underline_bottom_hover.png b/chromium/ash/resources/default_200_percent/common/launcher/launcher_underline_bottom_hover.png
new file mode 100644
index 00000000000..4062f829e7b
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/common/launcher/launcher_underline_bottom_hover.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/common/launcher/launcher_underline_bottom_running.png b/chromium/ash/resources/default_200_percent/common/launcher/launcher_underline_bottom_running.png
new file mode 100644
index 00000000000..7b7e3e2d65c
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/common/launcher/launcher_underline_bottom_running.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/common/launcher/launcher_underline_left_active.png b/chromium/ash/resources/default_200_percent/common/launcher/launcher_underline_left_active.png
new file mode 100644
index 00000000000..c2937e7edc5
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/common/launcher/launcher_underline_left_active.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/common/launcher/launcher_underline_left_hover.png b/chromium/ash/resources/default_200_percent/common/launcher/launcher_underline_left_hover.png
new file mode 100644
index 00000000000..c2937e7edc5
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/common/launcher/launcher_underline_left_hover.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/common/launcher/launcher_underline_left_running.png b/chromium/ash/resources/default_200_percent/common/launcher/launcher_underline_left_running.png
new file mode 100644
index 00000000000..d7ac0494e57
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/common/launcher/launcher_underline_left_running.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/common/launcher/launcher_underline_right_active.png b/chromium/ash/resources/default_200_percent/common/launcher/launcher_underline_right_active.png
new file mode 100644
index 00000000000..2f9ac00c252
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/common/launcher/launcher_underline_right_active.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/common/launcher/launcher_underline_right_hover.png b/chromium/ash/resources/default_200_percent/common/launcher/launcher_underline_right_hover.png
new file mode 100644
index 00000000000..2f9ac00c252
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/common/launcher/launcher_underline_right_hover.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/common/launcher/launcher_underline_right_running.png b/chromium/ash/resources/default_200_percent/common/launcher/launcher_underline_right_running.png
new file mode 100644
index 00000000000..8fade1943d4
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/common/launcher/launcher_underline_right_running.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/common/launcher/task_manager.png b/chromium/ash/resources/default_200_percent/common/launcher/task_manager.png
new file mode 100644
index 00000000000..63b18213173
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/common/launcher/task_manager.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/common/launcher/window_switcher_icon_incognito.png b/chromium/ash/resources/default_200_percent/common/launcher/window_switcher_icon_incognito.png
new file mode 100644
index 00000000000..d1ab17c6eec
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/common/launcher/window_switcher_icon_incognito.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/common/launcher/window_switcher_icon_normal.png b/chromium/ash/resources/default_200_percent/common/launcher/window_switcher_icon_normal.png
new file mode 100644
index 00000000000..a718a458b85
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/common/launcher/window_switcher_icon_normal.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/common/multi_window_resize_vertical.png b/chromium/ash/resources/default_200_percent/common/multi_window_resize_vertical.png
new file mode 100644
index 00000000000..80f7d62e264
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/common/multi_window_resize_vertical.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/common/tray_popup_label_button_border.png b/chromium/ash/resources/default_200_percent/common/tray_popup_label_button_border.png
new file mode 100644
index 00000000000..ecb62a8659f
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/common/tray_popup_label_button_border.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/common/tray_popup_label_button_hover_background.png b/chromium/ash/resources/default_200_percent/common/tray_popup_label_button_hover_background.png
new file mode 100644
index 00000000000..afa5af9fa42
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/common/tray_popup_label_button_hover_background.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/common/tray_popup_label_button_normal_background.png b/chromium/ash/resources/default_200_percent/common/tray_popup_label_button_normal_background.png
new file mode 100644
index 00000000000..61ff347c3ef
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/common/tray_popup_label_button_normal_background.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/common/tray_popup_public_account_logout_button_border.png b/chromium/ash/resources/default_200_percent/common/tray_popup_public_account_logout_button_border.png
new file mode 100644
index 00000000000..36d409f2858
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/common/tray_popup_public_account_logout_button_border.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/common/window_close_fullscreen_hover.png b/chromium/ash/resources/default_200_percent/common/window_close_fullscreen_hover.png
new file mode 100644
index 00000000000..450c886cd4f
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/common/window_close_fullscreen_hover.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/common/window_close_fullscreen_normal.png b/chromium/ash/resources/default_200_percent/common/window_close_fullscreen_normal.png
new file mode 100644
index 00000000000..c077c437ba9
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/common/window_close_fullscreen_normal.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/common/window_close_fullscreen_pressed.png b/chromium/ash/resources/default_200_percent/common/window_close_fullscreen_pressed.png
new file mode 100644
index 00000000000..5c2ad3feb9d
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/common/window_close_fullscreen_pressed.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/common/window_close_short_black_hover.png b/chromium/ash/resources/default_200_percent/common/window_close_short_black_hover.png
new file mode 100644
index 00000000000..13445f12842
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/common/window_close_short_black_hover.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/common/window_close_short_black_normal.png b/chromium/ash/resources/default_200_percent/common/window_close_short_black_normal.png
new file mode 100644
index 00000000000..12aa99337d2
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/common/window_close_short_black_normal.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/common/window_close_short_black_pressed.png b/chromium/ash/resources/default_200_percent/common/window_close_short_black_pressed.png
new file mode 100644
index 00000000000..42c3ca74515
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/common/window_close_short_black_pressed.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/common/window_close_short_hover.png b/chromium/ash/resources/default_200_percent/common/window_close_short_hover.png
new file mode 100644
index 00000000000..ad35cc769db
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/common/window_close_short_hover.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/common/window_close_short_normal.png b/chromium/ash/resources/default_200_percent/common/window_close_short_normal.png
new file mode 100644
index 00000000000..d75809f10af
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/common/window_close_short_normal.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/common/window_close_short_pressed.png b/chromium/ash/resources/default_200_percent/common/window_close_short_pressed.png
new file mode 100644
index 00000000000..711c6de48c7
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/common/window_close_short_pressed.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/common/window_close_tall_hover.png b/chromium/ash/resources/default_200_percent/common/window_close_tall_hover.png
new file mode 100644
index 00000000000..011e670bb46
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/common/window_close_tall_hover.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/common/window_close_tall_normal.png b/chromium/ash/resources/default_200_percent/common/window_close_tall_normal.png
new file mode 100644
index 00000000000..dd86f4ac291
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/common/window_close_tall_normal.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/common/window_close_tall_pressed.png b/chromium/ash/resources/default_200_percent/common/window_close_tall_pressed.png
new file mode 100644
index 00000000000..b6cfd646816
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/common/window_close_tall_pressed.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/common/window_fullscreen_shadow.png b/chromium/ash/resources/default_200_percent/common/window_fullscreen_shadow.png
new file mode 100644
index 00000000000..9c41fc0acca
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/common/window_fullscreen_shadow.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/common/window_header_base_active.png b/chromium/ash/resources/default_200_percent/common/window_header_base_active.png
new file mode 100644
index 00000000000..fe086b61478
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/common/window_header_base_active.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/common/window_header_base_inactive.png b/chromium/ash/resources/default_200_percent/common/window_header_base_inactive.png
new file mode 100644
index 00000000000..9403b7e01bf
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/common/window_header_base_inactive.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/common/window_header_base_incognito_active.png b/chromium/ash/resources/default_200_percent/common/window_header_base_incognito_active.png
new file mode 100644
index 00000000000..2ac535df9e4
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/common/window_header_base_incognito_active.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/common/window_header_base_incognito_inactive.png b/chromium/ash/resources/default_200_percent/common/window_header_base_incognito_inactive.png
new file mode 100644
index 00000000000..adac9cebed1
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/common/window_header_base_incognito_inactive.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/common/window_header_base_minimal.png b/chromium/ash/resources/default_200_percent/common/window_header_base_minimal.png
new file mode 100644
index 00000000000..32294b3df5f
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/common/window_header_base_minimal.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/common/window_header_shade_left.png b/chromium/ash/resources/default_200_percent/common/window_header_shade_left.png
new file mode 100644
index 00000000000..46f341ee757
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/common/window_header_shade_left.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/common/window_header_shade_middle.png b/chromium/ash/resources/default_200_percent/common/window_header_shade_middle.png
new file mode 100644
index 00000000000..8634bef0b25
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/common/window_header_shade_middle.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/common/window_header_shade_right.png b/chromium/ash/resources/default_200_percent/common/window_header_shade_right.png
new file mode 100644
index 00000000000..c1383614e0f
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/common/window_header_shade_right.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/common/window_header_shade_top.png b/chromium/ash/resources/default_200_percent/common/window_header_shade_top.png
new file mode 100644
index 00000000000..de77c847efa
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/common/window_header_shade_top.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/common/window_header_shade_top_left.png b/chromium/ash/resources/default_200_percent/common/window_header_shade_top_left.png
new file mode 100644
index 00000000000..18c277ec4a3
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/common/window_header_shade_top_left.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/common/window_header_shade_top_right.png b/chromium/ash/resources/default_200_percent/common/window_header_shade_top_right.png
new file mode 100644
index 00000000000..2224a28ea10
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/common/window_header_shade_top_right.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/common/window_immersive_enter_hover.png b/chromium/ash/resources/default_200_percent/common/window_immersive_enter_hover.png
new file mode 100644
index 00000000000..fb796a04bf1
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/common/window_immersive_enter_hover.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/common/window_immersive_enter_normal.png b/chromium/ash/resources/default_200_percent/common/window_immersive_enter_normal.png
new file mode 100644
index 00000000000..e2e0ee29a4e
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/common/window_immersive_enter_normal.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/common/window_immersive_enter_pressed.png b/chromium/ash/resources/default_200_percent/common/window_immersive_enter_pressed.png
new file mode 100644
index 00000000000..042178802ab
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/common/window_immersive_enter_pressed.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/common/window_immersive_exit_hover.png b/chromium/ash/resources/default_200_percent/common/window_immersive_exit_hover.png
new file mode 100644
index 00000000000..d267969954b
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/common/window_immersive_exit_hover.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/common/window_immersive_exit_normal.png b/chromium/ash/resources/default_200_percent/common/window_immersive_exit_normal.png
new file mode 100644
index 00000000000..bcf30506a16
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/common/window_immersive_exit_normal.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/common/window_immersive_exit_pressed.png b/chromium/ash/resources/default_200_percent/common/window_immersive_exit_pressed.png
new file mode 100644
index 00000000000..724f7f2da89
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/common/window_immersive_exit_pressed.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/common/window_minimize_short_hover.png b/chromium/ash/resources/default_200_percent/common/window_minimize_short_hover.png
new file mode 100644
index 00000000000..61cd35426dc
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/common/window_minimize_short_hover.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/common/window_minimize_short_normal.png b/chromium/ash/resources/default_200_percent/common/window_minimize_short_normal.png
new file mode 100644
index 00000000000..29f847039e0
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/common/window_minimize_short_normal.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/common/window_minimize_short_pressed.png b/chromium/ash/resources/default_200_percent/common/window_minimize_short_pressed.png
new file mode 100644
index 00000000000..7511d7dea19
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/common/window_minimize_short_pressed.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/common/window_position_left_hover.png b/chromium/ash/resources/default_200_percent/common/window_position_left_hover.png
new file mode 100644
index 00000000000..4d79bb222fd
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/common/window_position_left_hover.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/common/window_position_left_hover_restore.png b/chromium/ash/resources/default_200_percent/common/window_position_left_hover_restore.png
new file mode 100644
index 00000000000..61c53876870
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/common/window_position_left_hover_restore.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/common/window_position_left_normal.png b/chromium/ash/resources/default_200_percent/common/window_position_left_normal.png
new file mode 100644
index 00000000000..58e21b99678
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/common/window_position_left_normal.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/common/window_position_left_normal_restore.png b/chromium/ash/resources/default_200_percent/common/window_position_left_normal_restore.png
new file mode 100644
index 00000000000..bba0ff421ad
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/common/window_position_left_normal_restore.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/common/window_position_left_pressed.png b/chromium/ash/resources/default_200_percent/common/window_position_left_pressed.png
new file mode 100644
index 00000000000..b7fae089645
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/common/window_position_left_pressed.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/common/window_position_left_pressed_restore.png b/chromium/ash/resources/default_200_percent/common/window_position_left_pressed_restore.png
new file mode 100644
index 00000000000..0f9e0c92ac3
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/common/window_position_left_pressed_restore.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/common/window_position_middle_hover.png b/chromium/ash/resources/default_200_percent/common/window_position_middle_hover.png
new file mode 100644
index 00000000000..fe65289b18b
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/common/window_position_middle_hover.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/common/window_position_middle_normal.png b/chromium/ash/resources/default_200_percent/common/window_position_middle_normal.png
new file mode 100644
index 00000000000..0cda265b274
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/common/window_position_middle_normal.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/common/window_position_middle_pressed.png b/chromium/ash/resources/default_200_percent/common/window_position_middle_pressed.png
new file mode 100644
index 00000000000..f6648674933
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/common/window_position_middle_pressed.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/common/window_position_right_hover.png b/chromium/ash/resources/default_200_percent/common/window_position_right_hover.png
new file mode 100644
index 00000000000..e31c7294ab7
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/common/window_position_right_hover.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/common/window_position_right_hover_restore.png b/chromium/ash/resources/default_200_percent/common/window_position_right_hover_restore.png
new file mode 100644
index 00000000000..e5a5be33ca3
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/common/window_position_right_hover_restore.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/common/window_position_right_normal.png b/chromium/ash/resources/default_200_percent/common/window_position_right_normal.png
new file mode 100644
index 00000000000..1a8e7a24b94
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/common/window_position_right_normal.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/common/window_position_right_normal_restore.png b/chromium/ash/resources/default_200_percent/common/window_position_right_normal_restore.png
new file mode 100644
index 00000000000..3cf2d4b783b
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/common/window_position_right_normal_restore.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/common/window_position_right_pressed.png b/chromium/ash/resources/default_200_percent/common/window_position_right_pressed.png
new file mode 100644
index 00000000000..b5e533657ec
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/common/window_position_right_pressed.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/common/window_position_right_pressed_restore.png b/chromium/ash/resources/default_200_percent/common/window_position_right_pressed_restore.png
new file mode 100644
index 00000000000..62bb151bddc
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/common/window_position_right_pressed_restore.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/common/window_size_fullscreen_hover.png b/chromium/ash/resources/default_200_percent/common/window_size_fullscreen_hover.png
new file mode 100644
index 00000000000..1c466bd83e2
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/common/window_size_fullscreen_hover.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/common/window_size_fullscreen_normal.png b/chromium/ash/resources/default_200_percent/common/window_size_fullscreen_normal.png
new file mode 100644
index 00000000000..3773c24f3dd
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/common/window_size_fullscreen_normal.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/common/window_size_fullscreen_pressed.png b/chromium/ash/resources/default_200_percent/common/window_size_fullscreen_pressed.png
new file mode 100644
index 00000000000..ca097161b8b
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/common/window_size_fullscreen_pressed.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/common/window_size_short_black_hover.png b/chromium/ash/resources/default_200_percent/common/window_size_short_black_hover.png
new file mode 100644
index 00000000000..99a64dd5a4e
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/common/window_size_short_black_hover.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/common/window_size_short_black_normal.png b/chromium/ash/resources/default_200_percent/common/window_size_short_black_normal.png
new file mode 100644
index 00000000000..d7cd09ecad7
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/common/window_size_short_black_normal.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/common/window_size_short_black_pressed.png b/chromium/ash/resources/default_200_percent/common/window_size_short_black_pressed.png
new file mode 100644
index 00000000000..24d36b2b565
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/common/window_size_short_black_pressed.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/common/window_size_short_hover.png b/chromium/ash/resources/default_200_percent/common/window_size_short_hover.png
new file mode 100644
index 00000000000..39c31794f2d
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/common/window_size_short_hover.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/common/window_size_short_normal.png b/chromium/ash/resources/default_200_percent/common/window_size_short_normal.png
new file mode 100644
index 00000000000..a318359967c
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/common/window_size_short_normal.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/common/window_size_short_pressed.png b/chromium/ash/resources/default_200_percent/common/window_size_short_pressed.png
new file mode 100644
index 00000000000..1aca3dbb531
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/common/window_size_short_pressed.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/common/window_size_tall_hover.png b/chromium/ash/resources/default_200_percent/common/window_size_tall_hover.png
new file mode 100644
index 00000000000..cc7508cc1c4
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/common/window_size_tall_hover.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/common/window_size_tall_normal.png b/chromium/ash/resources/default_200_percent/common/window_size_tall_normal.png
new file mode 100644
index 00000000000..928eb01eca0
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/common/window_size_tall_normal.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/common/window_size_tall_pressed.png b/chromium/ash/resources/default_200_percent/common/window_size_tall_pressed.png
new file mode 100644
index 00000000000..3e988f39fe1
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/common/window_size_tall_pressed.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/cros/common/default_throbber.png b/chromium/ash/resources/default_200_percent/cros/common/default_throbber.png
new file mode 100644
index 00000000000..1538e41d9e1
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/cros/common/default_throbber.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/cros/network/notification_3g.png b/chromium/ash/resources/default_200_percent/cros/network/notification_3g.png
new file mode 100644
index 00000000000..6c04076791c
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/cros/network/notification_3g.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/cros/network/notification_lte.png b/chromium/ash/resources/default_200_percent/cros/network/notification_lte.png
new file mode 100644
index 00000000000..0ca53f71ec5
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/cros/network/notification_lte.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/cros/network/status_cellular_disabled.png b/chromium/ash/resources/default_200_percent/cros/network/status_cellular_disabled.png
new file mode 100644
index 00000000000..cd10247e4bd
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/cros/network/status_cellular_disabled.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/cros/network/status_cellular_disabled_hover.png b/chromium/ash/resources/default_200_percent/cros/network/status_cellular_disabled_hover.png
new file mode 100644
index 00000000000..18ffd31edd9
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/cros/network/status_cellular_disabled_hover.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/cros/network/status_cellular_enabled.png b/chromium/ash/resources/default_200_percent/cros/network/status_cellular_enabled.png
new file mode 100644
index 00000000000..5817ab5dfec
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/cros/network/status_cellular_enabled.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/cros/network/status_cellular_enabled_hover.png b/chromium/ash/resources/default_200_percent/cros/network/status_cellular_enabled_hover.png
new file mode 100644
index 00000000000..f9c0c6071b0
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/cros/network/status_cellular_enabled_hover.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/cros/network/status_cellular_failed.png b/chromium/ash/resources/default_200_percent/cros/network/status_cellular_failed.png
new file mode 100644
index 00000000000..282cc07a18d
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/cros/network/status_cellular_failed.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/cros/network/status_data_low.png b/chromium/ash/resources/default_200_percent/cros/network/status_data_low.png
new file mode 100644
index 00000000000..e32346c62f3
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/cros/network/status_data_low.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/cros/network/status_data_none.png b/chromium/ash/resources/default_200_percent/cros/network/status_data_none.png
new file mode 100644
index 00000000000..3f20d4acd3f
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/cros/network/status_data_none.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/cros/network/status_network_failed.png b/chromium/ash/resources/default_200_percent/cros/network/status_network_failed.png
new file mode 100644
index 00000000000..803a48e1a5b
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/cros/network/status_network_failed.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/cros/network/status_network_info.png b/chromium/ash/resources/default_200_percent/cros/network/status_network_info.png
new file mode 100644
index 00000000000..98a920958b6
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/cros/network/status_network_info.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/cros/network/status_network_info_hover.png b/chromium/ash/resources/default_200_percent/cros/network/status_network_info_hover.png
new file mode 100644
index 00000000000..db3e2662c09
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/cros/network/status_network_info_hover.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/cros/network/status_wifi_disabled.png b/chromium/ash/resources/default_200_percent/cros/network/status_wifi_disabled.png
new file mode 100644
index 00000000000..a9d54c7215b
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/cros/network/status_wifi_disabled.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/cros/network/status_wifi_disabled_hover.png b/chromium/ash/resources/default_200_percent/cros/network/status_wifi_disabled_hover.png
new file mode 100644
index 00000000000..b485baed04d
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/cros/network/status_wifi_disabled_hover.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/cros/network/status_wifi_enabled.png b/chromium/ash/resources/default_200_percent/cros/network/status_wifi_enabled.png
new file mode 100644
index 00000000000..e951ff3e77f
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/cros/network/status_wifi_enabled.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/cros/network/status_wifi_enabled_hover.png b/chromium/ash/resources/default_200_percent/cros/network/status_wifi_enabled_hover.png
new file mode 100644
index 00000000000..7a728844a80
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/cros/network/status_wifi_enabled_hover.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/cros/network/statusbar_network_3g_dark.png b/chromium/ash/resources/default_200_percent/cros/network/statusbar_network_3g_dark.png
new file mode 100644
index 00000000000..e58ed2ada82
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/cros/network/statusbar_network_3g_dark.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/cros/network/statusbar_network_3g_light.png b/chromium/ash/resources/default_200_percent/cros/network/statusbar_network_3g_light.png
new file mode 100644
index 00000000000..095a8894941
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/cros/network/statusbar_network_3g_light.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/cros/network/statusbar_network_4g_dark.png b/chromium/ash/resources/default_200_percent/cros/network/statusbar_network_4g_dark.png
new file mode 100644
index 00000000000..d17ceb76a6c
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/cros/network/statusbar_network_4g_dark.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/cros/network/statusbar_network_4g_light.png b/chromium/ash/resources/default_200_percent/cros/network/statusbar_network_4g_light.png
new file mode 100644
index 00000000000..e7835960ec5
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/cros/network/statusbar_network_4g_light.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/cros/network/statusbar_network_arcs_dark.png b/chromium/ash/resources/default_200_percent/cros/network/statusbar_network_arcs_dark.png
new file mode 100644
index 00000000000..f3c281f2eab
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/cros/network/statusbar_network_arcs_dark.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/cros/network/statusbar_network_arcs_light.png b/chromium/ash/resources/default_200_percent/cros/network/statusbar_network_arcs_light.png
new file mode 100644
index 00000000000..108249c7154
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/cros/network/statusbar_network_arcs_light.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/cros/network/statusbar_network_bars_dark.png b/chromium/ash/resources/default_200_percent/cros/network/statusbar_network_bars_dark.png
new file mode 100644
index 00000000000..548ef5f4e0f
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/cros/network/statusbar_network_bars_dark.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/cros/network/statusbar_network_bars_light.png b/chromium/ash/resources/default_200_percent/cros/network/statusbar_network_bars_light.png
new file mode 100644
index 00000000000..90f2232b91d
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/cros/network/statusbar_network_bars_light.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/cros/network/statusbar_network_edge_dark.png b/chromium/ash/resources/default_200_percent/cros/network/statusbar_network_edge_dark.png
new file mode 100644
index 00000000000..fae8b09c761
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/cros/network/statusbar_network_edge_dark.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/cros/network/statusbar_network_edge_light.png b/chromium/ash/resources/default_200_percent/cros/network/statusbar_network_edge_light.png
new file mode 100644
index 00000000000..445bc95234f
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/cros/network/statusbar_network_edge_light.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/cros/network/statusbar_network_evdo_dark.png b/chromium/ash/resources/default_200_percent/cros/network/statusbar_network_evdo_dark.png
new file mode 100644
index 00000000000..71351465888
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/cros/network/statusbar_network_evdo_dark.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/cros/network/statusbar_network_evdo_light.png b/chromium/ash/resources/default_200_percent/cros/network/statusbar_network_evdo_light.png
new file mode 100644
index 00000000000..55b89af5b6a
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/cros/network/statusbar_network_evdo_light.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/cros/network/statusbar_network_gprs_dark.png b/chromium/ash/resources/default_200_percent/cros/network/statusbar_network_gprs_dark.png
new file mode 100644
index 00000000000..c45081d13fc
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/cros/network/statusbar_network_gprs_dark.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/cros/network/statusbar_network_gprs_light.png b/chromium/ash/resources/default_200_percent/cros/network/statusbar_network_gprs_light.png
new file mode 100644
index 00000000000..4277cdfa79d
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/cros/network/statusbar_network_gprs_light.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/cros/network/statusbar_network_hspa_dark.png b/chromium/ash/resources/default_200_percent/cros/network/statusbar_network_hspa_dark.png
new file mode 100644
index 00000000000..bf424a31486
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/cros/network/statusbar_network_hspa_dark.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/cros/network/statusbar_network_hspa_light.png b/chromium/ash/resources/default_200_percent/cros/network/statusbar_network_hspa_light.png
new file mode 100644
index 00000000000..ca2f70790c6
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/cros/network/statusbar_network_hspa_light.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/cros/network/statusbar_network_hspa_plus_dark.png b/chromium/ash/resources/default_200_percent/cros/network/statusbar_network_hspa_plus_dark.png
new file mode 100644
index 00000000000..a3522894ce7
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/cros/network/statusbar_network_hspa_plus_dark.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/cros/network/statusbar_network_hspa_plus_light.png b/chromium/ash/resources/default_200_percent/cros/network/statusbar_network_hspa_plus_light.png
new file mode 100644
index 00000000000..6e2b38b2fa6
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/cros/network/statusbar_network_hspa_plus_light.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/cros/network/statusbar_network_lte_advanced_dark.png b/chromium/ash/resources/default_200_percent/cros/network/statusbar_network_lte_advanced_dark.png
new file mode 100644
index 00000000000..50c496ae1a0
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/cros/network/statusbar_network_lte_advanced_dark.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/cros/network/statusbar_network_lte_advanced_light.png b/chromium/ash/resources/default_200_percent/cros/network/statusbar_network_lte_advanced_light.png
new file mode 100644
index 00000000000..ef9f7ce044b
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/cros/network/statusbar_network_lte_advanced_light.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/cros/network/statusbar_network_lte_dark.png b/chromium/ash/resources/default_200_percent/cros/network/statusbar_network_lte_dark.png
new file mode 100644
index 00000000000..1c72085ed86
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/cros/network/statusbar_network_lte_dark.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/cros/network/statusbar_network_lte_light.png b/chromium/ash/resources/default_200_percent/cros/network/statusbar_network_lte_light.png
new file mode 100644
index 00000000000..53f9ab11326
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/cros/network/statusbar_network_lte_light.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/cros/network/statusbar_network_roaming_dark.png b/chromium/ash/resources/default_200_percent/cros/network/statusbar_network_roaming_dark.png
new file mode 100644
index 00000000000..f59974f8aeb
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/cros/network/statusbar_network_roaming_dark.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/cros/network/statusbar_network_roaming_light.png b/chromium/ash/resources/default_200_percent/cros/network/statusbar_network_roaming_light.png
new file mode 100644
index 00000000000..ae7fade4eea
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/cros/network/statusbar_network_roaming_light.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/cros/network/statusbar_network_secure_dark.png b/chromium/ash/resources/default_200_percent/cros/network/statusbar_network_secure_dark.png
new file mode 100644
index 00000000000..10893076c47
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/cros/network/statusbar_network_secure_dark.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/cros/network/statusbar_network_secure_light.png b/chromium/ash/resources/default_200_percent/cros/network/statusbar_network_secure_light.png
new file mode 100644
index 00000000000..022dd6631b4
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/cros/network/statusbar_network_secure_light.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/cros/network/statusbar_network_vpn_badge.png b/chromium/ash/resources/default_200_percent/cros/network/statusbar_network_vpn_badge.png
new file mode 100644
index 00000000000..07a07564791
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/cros/network/statusbar_network_vpn_badge.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/cros/network/statusbar_vpn_dark.png b/chromium/ash/resources/default_200_percent/cros/network/statusbar_vpn_dark.png
new file mode 100644
index 00000000000..5503851e97e
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/cros/network/statusbar_vpn_dark.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/cros/network/statusbar_wired.png b/chromium/ash/resources/default_200_percent/cros/network/statusbar_wired.png
new file mode 100644
index 00000000000..2db7fa0ac0b
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/cros/network/statusbar_wired.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/cros/notification/notification_low_power_charger.png b/chromium/ash/resources/default_200_percent/cros/notification/notification_low_power_charger.png
new file mode 100644
index 00000000000..78a6b889c81
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/cros/notification/notification_low_power_charger.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/cros/status/multiprofiles_add.png b/chromium/ash/resources/default_200_percent/cros/status/multiprofiles_add.png
new file mode 100644
index 00000000000..2fb62cdd301
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/cros/status/multiprofiles_add.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/cros/status/status_accessibility_dark.png b/chromium/ash/resources/default_200_percent/cros/status/status_accessibility_dark.png
new file mode 100644
index 00000000000..d1943715595
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/cros/status/status_accessibility_dark.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/cros/status/status_audio_device_bluetooth.png b/chromium/ash/resources/default_200_percent/cros/status/status_audio_device_bluetooth.png
new file mode 100644
index 00000000000..28d36d0d376
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/cros/status/status_audio_device_bluetooth.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/cros/status/status_audio_device_hdmi.png b/chromium/ash/resources/default_200_percent/cros/status/status_audio_device_hdmi.png
new file mode 100644
index 00000000000..d6ddc0925ab
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/cros/status/status_audio_device_hdmi.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/cros/status/status_audio_device_headphones.png b/chromium/ash/resources/default_200_percent/cros/status/status_audio_device_headphones.png
new file mode 100644
index 00000000000..4c0e648b663
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/cros/status/status_audio_device_headphones.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/cros/status/status_audio_device_usb.png b/chromium/ash/resources/default_200_percent/cros/status/status_audio_device_usb.png
new file mode 100644
index 00000000000..f17edcf9e3e
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/cros/status/status_audio_device_usb.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/cros/status/status_bluetooth.png b/chromium/ash/resources/default_200_percent/cros/status/status_bluetooth.png
new file mode 100644
index 00000000000..d4cf7c7290e
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/cros/status/status_bluetooth.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/cros/status/status_bluetooth_disabled.png b/chromium/ash/resources/default_200_percent/cros/status/status_bluetooth_disabled.png
new file mode 100644
index 00000000000..6676cf97007
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/cros/status/status_bluetooth_disabled.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/cros/status/status_bluetooth_disabled_hover.png b/chromium/ash/resources/default_200_percent/cros/status/status_bluetooth_disabled_hover.png
new file mode 100644
index 00000000000..66f368dccdb
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/cros/status/status_bluetooth_disabled_hover.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/cros/status/status_bluetooth_enabled.png b/chromium/ash/resources/default_200_percent/cros/status/status_bluetooth_enabled.png
new file mode 100644
index 00000000000..d9ee935a84c
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/cros/status/status_bluetooth_enabled.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/cros/status/status_bluetooth_enabled_hover.png b/chromium/ash/resources/default_200_percent/cros/status/status_bluetooth_enabled_hover.png
new file mode 100644
index 00000000000..a4fa7eb8626
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/cros/status/status_bluetooth_enabled_hover.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/cros/status/status_brightness.png b/chromium/ash/resources/default_200_percent/cros/status/status_brightness.png
new file mode 100644
index 00000000000..547982efb84
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/cros/status/status_brightness.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/cros/status/status_capslock.png b/chromium/ash/resources/default_200_percent/cros/status/status_capslock.png
new file mode 100644
index 00000000000..7ceed8363cd
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/cros/status/status_capslock.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/cros/status/status_capslock_dark.png b/chromium/ash/resources/default_200_percent/cros/status/status_capslock_dark.png
new file mode 100644
index 00000000000..1d78d5d7bd4
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/cros/status/status_capslock_dark.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/cros/status/status_display.png b/chromium/ash/resources/default_200_percent/cros/status/status_display.png
new file mode 100644
index 00000000000..b89a12031e1
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/cros/status/status_display.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/cros/status/status_display_dark.png b/chromium/ash/resources/default_200_percent/cros/status/status_display_dark.png
new file mode 100644
index 00000000000..9d28ca2c104
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/cros/status/status_display_dark.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/cros/status/status_drive.png b/chromium/ash/resources/default_200_percent/cros/status/status_drive.png
new file mode 100644
index 00000000000..43e687727cd
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/cros/status/status_drive.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/cros/status/status_drive_item_cancel.png b/chromium/ash/resources/default_200_percent/cros/status/status_drive_item_cancel.png
new file mode 100644
index 00000000000..7169edcfcac
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/cros/status/status_drive_item_cancel.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/cros/status/status_drive_item_cancel_hover.png b/chromium/ash/resources/default_200_percent/cros/status/status_drive_item_cancel_hover.png
new file mode 100644
index 00000000000..9b02e5897c5
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/cros/status/status_drive_item_cancel_hover.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/cros/status/status_drive_item_done.png b/chromium/ash/resources/default_200_percent/cros/status/status_drive_item_done.png
new file mode 100644
index 00000000000..d397f77f9f0
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/cros/status/status_drive_item_done.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/cros/status/status_drive_item_failed.png b/chromium/ash/resources/default_200_percent/cros/status/status_drive_item_failed.png
new file mode 100644
index 00000000000..3ed87e11d2a
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/cros/status/status_drive_item_failed.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/cros/status/status_drive_light.png b/chromium/ash/resources/default_200_percent/cros/status/status_drive_light.png
new file mode 100644
index 00000000000..bfea7bdd69e
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/cros/status/status_drive_light.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/cros/status/status_guest_icon.png b/chromium/ash/resources/default_200_percent/cros/status/status_guest_icon.png
new file mode 100644
index 00000000000..c2921bb9cb3
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/cros/status/status_guest_icon.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/cros/status/status_help.png b/chromium/ash/resources/default_200_percent/cros/status/status_help.png
new file mode 100644
index 00000000000..a8a99776621
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/cros/status/status_help.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/cros/status/status_help_hover.png b/chromium/ash/resources/default_200_percent/cros/status/status_help_hover.png
new file mode 100644
index 00000000000..a9b5233f5df
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/cros/status/status_help_hover.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/cros/status/status_ime.png b/chromium/ash/resources/default_200_percent/cros/status/status_ime.png
new file mode 100644
index 00000000000..f0104f0e697
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/cros/status/status_ime.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/cros/status/status_less.png b/chromium/ash/resources/default_200_percent/cros/status/status_less.png
new file mode 100644
index 00000000000..8c5e81f094a
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/cros/status/status_less.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/cros/status/status_locale.png b/chromium/ash/resources/default_200_percent/cros/status/status_locale.png
new file mode 100644
index 00000000000..dab0994a96f
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/cros/status/status_locale.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/cros/status/status_lockscreen.png b/chromium/ash/resources/default_200_percent/cros/status/status_lockscreen.png
new file mode 100644
index 00000000000..9d7fdcd5b76
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/cros/status/status_lockscreen.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/cros/status/status_lockscreen_hover.png b/chromium/ash/resources/default_200_percent/cros/status/status_lockscreen_hover.png
new file mode 100644
index 00000000000..e4aa68a3d2f
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/cros/status/status_lockscreen_hover.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/cros/status/status_logout_button_normal_bottom.png b/chromium/ash/resources/default_200_percent/cros/status/status_logout_button_normal_bottom.png
new file mode 100644
index 00000000000..679ebe905cd
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/cros/status/status_logout_button_normal_bottom.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/cros/status/status_logout_button_normal_bottom_left.png b/chromium/ash/resources/default_200_percent/cros/status/status_logout_button_normal_bottom_left.png
new file mode 100644
index 00000000000..7b615e65c83
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/cros/status/status_logout_button_normal_bottom_left.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/cros/status/status_logout_button_normal_bottom_right.png b/chromium/ash/resources/default_200_percent/cros/status/status_logout_button_normal_bottom_right.png
new file mode 100644
index 00000000000..149b4a9c5dd
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/cros/status/status_logout_button_normal_bottom_right.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/cros/status/status_logout_button_normal_center.png b/chromium/ash/resources/default_200_percent/cros/status/status_logout_button_normal_center.png
new file mode 100644
index 00000000000..436c2ac18a1
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/cros/status/status_logout_button_normal_center.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/cros/status/status_logout_button_normal_left.png b/chromium/ash/resources/default_200_percent/cros/status/status_logout_button_normal_left.png
new file mode 100644
index 00000000000..c58ee946b2e
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/cros/status/status_logout_button_normal_left.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/cros/status/status_logout_button_normal_right.png b/chromium/ash/resources/default_200_percent/cros/status/status_logout_button_normal_right.png
new file mode 100644
index 00000000000..5f7d6500ff8
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/cros/status/status_logout_button_normal_right.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/cros/status/status_logout_button_normal_top.png b/chromium/ash/resources/default_200_percent/cros/status/status_logout_button_normal_top.png
new file mode 100644
index 00000000000..cc114446f6a
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/cros/status/status_logout_button_normal_top.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/cros/status/status_logout_button_normal_top_left.png b/chromium/ash/resources/default_200_percent/cros/status/status_logout_button_normal_top_left.png
new file mode 100644
index 00000000000..05dfa2f90cd
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/cros/status/status_logout_button_normal_top_left.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/cros/status/status_logout_button_normal_top_right.png b/chromium/ash/resources/default_200_percent/cros/status/status_logout_button_normal_top_right.png
new file mode 100644
index 00000000000..bf15dcdec00
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/cros/status/status_logout_button_normal_top_right.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/cros/status/status_logout_button_pushed_bottom.png b/chromium/ash/resources/default_200_percent/cros/status/status_logout_button_pushed_bottom.png
new file mode 100644
index 00000000000..0e23e565c4c
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/cros/status/status_logout_button_pushed_bottom.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/cros/status/status_logout_button_pushed_bottom_left.png b/chromium/ash/resources/default_200_percent/cros/status/status_logout_button_pushed_bottom_left.png
new file mode 100644
index 00000000000..79f2792aa6b
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/cros/status/status_logout_button_pushed_bottom_left.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/cros/status/status_logout_button_pushed_bottom_right.png b/chromium/ash/resources/default_200_percent/cros/status/status_logout_button_pushed_bottom_right.png
new file mode 100644
index 00000000000..d6aa1a4f511
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/cros/status/status_logout_button_pushed_bottom_right.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/cros/status/status_logout_button_pushed_center.png b/chromium/ash/resources/default_200_percent/cros/status/status_logout_button_pushed_center.png
new file mode 100644
index 00000000000..41a39c92caf
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/cros/status/status_logout_button_pushed_center.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/cros/status/status_logout_button_pushed_left.png b/chromium/ash/resources/default_200_percent/cros/status/status_logout_button_pushed_left.png
new file mode 100644
index 00000000000..1617280c4f7
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/cros/status/status_logout_button_pushed_left.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/cros/status/status_logout_button_pushed_right.png b/chromium/ash/resources/default_200_percent/cros/status/status_logout_button_pushed_right.png
new file mode 100644
index 00000000000..c5a5730fdfa
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/cros/status/status_logout_button_pushed_right.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/cros/status/status_logout_button_pushed_top.png b/chromium/ash/resources/default_200_percent/cros/status/status_logout_button_pushed_top.png
new file mode 100644
index 00000000000..b340b1bc287
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/cros/status/status_logout_button_pushed_top.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/cros/status/status_logout_button_pushed_top_left.png b/chromium/ash/resources/default_200_percent/cros/status/status_logout_button_pushed_top_left.png
new file mode 100644
index 00000000000..787ef70d361
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/cros/status/status_logout_button_pushed_top_left.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/cros/status/status_logout_button_pushed_top_right.png b/chromium/ash/resources/default_200_percent/cros/status/status_logout_button_pushed_top_right.png
new file mode 100644
index 00000000000..5d5f6349fa6
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/cros/status/status_logout_button_pushed_top_right.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/cros/status/status_managed.png b/chromium/ash/resources/default_200_percent/cros/status/status_managed.png
new file mode 100644
index 00000000000..19161c10e48
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/cros/status/status_managed.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/cros/status/status_managed_mode_user.png b/chromium/ash/resources/default_200_percent/cros/status/status_managed_mode_user.png
new file mode 100644
index 00000000000..b0b522db0a2
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/cros/status/status_managed_mode_user.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/cros/status/status_managed_tray.png b/chromium/ash/resources/default_200_percent/cros/status/status_managed_tray.png
new file mode 100644
index 00000000000..5019650bd4c
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/cros/status/status_managed_tray.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/cros/status/status_more.png b/chromium/ash/resources/default_200_percent/cros/status/status_more.png
new file mode 100644
index 00000000000..a33620bd36e
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/cros/status/status_more.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/cros/status/status_power_small_all.png b/chromium/ash/resources/default_200_percent/cros/status/status_power_small_all.png
new file mode 100644
index 00000000000..42b706dc23b
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/cros/status/status_power_small_all.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/cros/status/status_power_small_all_dark.png b/chromium/ash/resources/default_200_percent/cros/status/status_power_small_all_dark.png
new file mode 100644
index 00000000000..d0ea331dc8b
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/cros/status/status_power_small_all_dark.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/cros/status/status_power_small_all_dark_discharging.png b/chromium/ash/resources/default_200_percent/cros/status/status_power_small_all_dark_discharging.png
new file mode 100644
index 00000000000..3c5557e8c7a
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/cros/status/status_power_small_all_dark_discharging.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/cros/status/status_power_small_all_dark_fluctuating.png b/chromium/ash/resources/default_200_percent/cros/status/status_power_small_all_dark_fluctuating.png
new file mode 100644
index 00000000000..8be069fb325
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/cros/status/status_power_small_all_dark_fluctuating.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/cros/status/status_power_small_all_discharging.png b/chromium/ash/resources/default_200_percent/cros/status/status_power_small_all_discharging.png
new file mode 100644
index 00000000000..8c8490f696c
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/cros/status/status_power_small_all_discharging.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/cros/status/status_power_small_all_fluctuating.png b/chromium/ash/resources/default_200_percent/cros/status/status_power_small_all_fluctuating.png
new file mode 100644
index 00000000000..be20d9b5fc8
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/cros/status/status_power_small_all_fluctuating.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/cros/status/status_screen_share_dark.png b/chromium/ash/resources/default_200_percent/cros/status/status_screen_share_dark.png
new file mode 100644
index 00000000000..392b99de0e5
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/cros/status/status_screen_share_dark.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/cros/status/status_screen_share_light.png b/chromium/ash/resources/default_200_percent/cros/status/status_screen_share_light.png
new file mode 100644
index 00000000000..4845e0abf0a
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/cros/status/status_screen_share_light.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/cros/status/status_session_length_limit_timer.png b/chromium/ash/resources/default_200_percent/cros/status/status_session_length_limit_timer.png
new file mode 100644
index 00000000000..0dbae9835b4
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/cros/status/status_session_length_limit_timer.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/cros/status/status_settings.png b/chromium/ash/resources/default_200_percent/cros/status/status_settings.png
new file mode 100644
index 00000000000..a5655d6512b
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/cros/status/status_settings.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/cros/status/status_shutdown.png b/chromium/ash/resources/default_200_percent/cros/status/status_shutdown.png
new file mode 100644
index 00000000000..15ce5e16abf
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/cros/status/status_shutdown.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/cros/status/status_shutdown_hover.png b/chromium/ash/resources/default_200_percent/cros/status/status_shutdown_hover.png
new file mode 100644
index 00000000000..f19ca3ce651
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/cros/status/status_shutdown_hover.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/cros/status/status_sms.png b/chromium/ash/resources/default_200_percent/cros/status/status_sms.png
new file mode 100644
index 00000000000..101156b52ad
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/cros/status/status_sms.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/cros/status/status_tracing.png b/chromium/ash/resources/default_200_percent/cros/status/status_tracing.png
new file mode 100644
index 00000000000..4680f3f033e
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/cros/status/status_tracing.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/cros/status/status_update.png b/chromium/ash/resources/default_200_percent/cros/status/status_update.png
new file mode 100644
index 00000000000..47332d528c7
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/cros/status/status_update.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/cros/status/status_update_dark.png b/chromium/ash/resources/default_200_percent/cros/status/status_update_dark.png
new file mode 100644
index 00000000000..4cba931f2b7
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/cros/status/status_update_dark.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/cros/status/status_update_dark_green.png b/chromium/ash/resources/default_200_percent/cros/status/status_update_dark_green.png
new file mode 100644
index 00000000000..c9ca473bcee
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/cros/status/status_update_dark_green.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/cros/status/status_update_dark_orange.png b/chromium/ash/resources/default_200_percent/cros/status/status_update_dark_orange.png
new file mode 100644
index 00000000000..467b3f1c37b
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/cros/status/status_update_dark_orange.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/cros/status/status_update_dark_red.png b/chromium/ash/resources/default_200_percent/cros/status/status_update_dark_red.png
new file mode 100644
index 00000000000..e861ab62615
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/cros/status/status_update_dark_red.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/cros/status/status_update_green.png b/chromium/ash/resources/default_200_percent/cros/status/status_update_green.png
new file mode 100644
index 00000000000..fa875dfd6d1
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/cros/status/status_update_green.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/cros/status/status_update_orange.png b/chromium/ash/resources/default_200_percent/cros/status/status_update_orange.png
new file mode 100644
index 00000000000..02e04695b55
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/cros/status/status_update_orange.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/cros/status/status_update_red.png b/chromium/ash/resources/default_200_percent/cros/status/status_update_red.png
new file mode 100644
index 00000000000..ef49b2dd739
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/cros/status/status_update_red.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/cros/status/status_volume_dark.png b/chromium/ash/resources/default_200_percent/cros/status/status_volume_dark.png
new file mode 100644
index 00000000000..b8593a65587
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/cros/status/status_volume_dark.png
Binary files differ
diff --git a/chromium/ash/resources/default_200_percent/cros/status/status_volume_mute.png b/chromium/ash/resources/default_200_percent/cros/status/status_volume_mute.png
new file mode 100644
index 00000000000..7a6abc8ab95
--- /dev/null
+++ b/chromium/ash/resources/default_200_percent/cros/status/status_volume_mute.png
Binary files differ
diff --git a/chromium/ash/resources/wallpaper/default_large.jpg b/chromium/ash/resources/wallpaper/default_large.jpg
new file mode 100644
index 00000000000..003f3f754b2
--- /dev/null
+++ b/chromium/ash/resources/wallpaper/default_large.jpg
Binary files differ
diff --git a/chromium/ash/resources/wallpaper/default_small.jpg b/chromium/ash/resources/wallpaper/default_small.jpg
new file mode 100644
index 00000000000..d8641f36ca4
--- /dev/null
+++ b/chromium/ash/resources/wallpaper/default_small.jpg
Binary files differ
diff --git a/chromium/ash/root_window_controller.cc b/chromium/ash/root_window_controller.cc
new file mode 100644
index 00000000000..dd237436cff
--- /dev/null
+++ b/chromium/ash/root_window_controller.cc
@@ -0,0 +1,788 @@
+// Copyright (c) 2012 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.
+
+#include "ash/root_window_controller.h"
+
+#include <vector>
+
+#include "ash/ash_constants.h"
+#include "ash/ash_switches.h"
+#include "ash/desktop_background/desktop_background_widget_controller.h"
+#include "ash/desktop_background/user_wallpaper_delegate.h"
+#include "ash/display/display_manager.h"
+#include "ash/focus_cycler.h"
+#include "ash/session_state_delegate.h"
+#include "ash/shelf/shelf_layout_manager.h"
+#include "ash/shelf/shelf_types.h"
+#include "ash/shelf/shelf_widget.h"
+#include "ash/shell.h"
+#include "ash/shell_delegate.h"
+#include "ash/shell_factory.h"
+#include "ash/shell_window_ids.h"
+#include "ash/system/status_area_widget.h"
+#include "ash/system/tray/system_tray_delegate.h"
+#include "ash/touch/touch_hud_debug.h"
+#include "ash/touch/touch_hud_projection.h"
+#include "ash/touch/touch_observer_hud.h"
+#include "ash/wm/always_on_top_controller.h"
+#include "ash/wm/base_layout_manager.h"
+#include "ash/wm/dock/docked_window_layout_manager.h"
+#include "ash/wm/panels/panel_layout_manager.h"
+#include "ash/wm/panels/panel_window_event_handler.h"
+#include "ash/wm/property_util.h"
+#include "ash/wm/root_window_layout_manager.h"
+#include "ash/wm/screen_dimmer.h"
+#include "ash/wm/stacking_controller.h"
+#include "ash/wm/status_area_layout_manager.h"
+#include "ash/wm/system_background_controller.h"
+#include "ash/wm/system_modal_container_layout_manager.h"
+#include "ash/wm/toplevel_window_event_handler.h"
+#include "ash/wm/window_properties.h"
+#include "ash/wm/window_util.h"
+#include "ash/wm/workspace_controller.h"
+#include "base/command_line.h"
+#include "base/time/time.h"
+#include "ui/aura/client/aura_constants.h"
+#include "ui/aura/client/tooltip_client.h"
+#include "ui/aura/root_window.h"
+#include "ui/aura/window.h"
+#include "ui/aura/window_delegate.h"
+#include "ui/aura/window_observer.h"
+#include "ui/base/hit_test.h"
+#include "ui/base/models/menu_model.h"
+#include "ui/gfx/screen.h"
+#include "ui/keyboard/keyboard_controller.h"
+#include "ui/keyboard/keyboard_util.h"
+#include "ui/views/controls/menu/menu_runner.h"
+#include "ui/views/corewm/visibility_controller.h"
+#include "ui/views/view_model.h"
+#include "ui/views/view_model_utils.h"
+
+#if defined(OS_CHROMEOS)
+#include "ash/wm/boot_splash_screen_chromeos.h"
+#endif
+
+namespace ash {
+namespace {
+
+#if defined(OS_CHROMEOS)
+// Duration for the animation that hides the boot splash screen, in
+// milliseconds. This should be short enough in relation to
+// wm/window_animation.cc's brightness/grayscale fade animation that the login
+// background image animation isn't hidden by the splash screen animation.
+const int kBootSplashScreenHideDurationMs = 500;
+#endif
+
+// Creates a new window for use as a container.
+aura::Window* CreateContainer(int window_id,
+ const char* name,
+ aura::Window* parent) {
+ aura::Window* container = new aura::Window(NULL);
+ container->set_id(window_id);
+ container->SetName(name);
+ container->Init(ui::LAYER_NOT_DRAWN);
+ parent->AddChild(container);
+ if (window_id != internal::kShellWindowId_UnparentedControlContainer)
+ container->Show();
+ return container;
+}
+
+// Reparents |window| to |new_parent|.
+void ReparentWindow(aura::Window* window, aura::Window* new_parent) {
+ // Update the restore bounds to make it relative to the display.
+ gfx::Rect restore_bounds(GetRestoreBoundsInParent(window));
+ new_parent->AddChild(window);
+ if (!restore_bounds.IsEmpty())
+ SetRestoreBoundsInParent(window, restore_bounds);
+}
+
+// Reparents the appropriate set of windows from |src| to |dst|.
+void ReparentAllWindows(aura::RootWindow* src, aura::RootWindow* dst) {
+ // Set of windows to move.
+ const int kContainerIdsToMove[] = {
+ internal::kShellWindowId_DefaultContainer,
+ internal::kShellWindowId_DockedContainer,
+ internal::kShellWindowId_PanelContainer,
+ internal::kShellWindowId_AlwaysOnTopContainer,
+ internal::kShellWindowId_SystemModalContainer,
+ internal::kShellWindowId_LockSystemModalContainer,
+ internal::kShellWindowId_InputMethodContainer,
+ internal::kShellWindowId_UnparentedControlContainer,
+ };
+ for (size_t i = 0; i < arraysize(kContainerIdsToMove); i++) {
+ int id = kContainerIdsToMove[i];
+ aura::Window* src_container = Shell::GetContainer(src, id);
+ aura::Window* dst_container = Shell::GetContainer(dst, id);
+ while (!src_container->children().empty()) {
+ // Restart iteration from the source container windows each time as they
+ // may change as a result of moving other windows.
+ aura::Window::Windows::const_iterator iter =
+ src_container->children().begin();
+ while (iter != src_container->children().end() &&
+ internal::SystemModalContainerLayoutManager::IsModalBackground(
+ *iter)) {
+ ++iter;
+ }
+ // If the entire window list is modal background windows then stop.
+ if (iter == src_container->children().end())
+ break;
+ ReparentWindow(*iter, dst_container);
+ }
+ }
+}
+
+// Mark the container window so that a widget added to this container will
+// use the virtual screeen coordinates instead of parent.
+void SetUsesScreenCoordinates(aura::Window* container) {
+ container->SetProperty(internal::kUsesScreenCoordinatesKey, true);
+}
+
+// Mark the container window so that a widget added to this container will
+// say in the same root window regardless of the bounds specified.
+void DescendantShouldStayInSameRootWindow(aura::Window* container) {
+ container->SetProperty(internal::kStayInSameRootWindowKey, true);
+}
+
+// A window delegate which does nothing. Used to create a window that
+// is a event target, but do nothing.
+class EmptyWindowDelegate : public aura::WindowDelegate {
+ public:
+ EmptyWindowDelegate() {}
+ virtual ~EmptyWindowDelegate() {}
+
+ // aura::WindowDelegate overrides:
+ virtual gfx::Size GetMinimumSize() const OVERRIDE {
+ return gfx::Size();
+ }
+ virtual gfx::Size GetMaximumSize() const OVERRIDE {
+ return gfx::Size();
+ }
+ virtual void OnBoundsChanged(const gfx::Rect& old_bounds,
+ const gfx::Rect& new_bounds) OVERRIDE {
+ }
+ virtual gfx::NativeCursor GetCursor(const gfx::Point& point) OVERRIDE {
+ return gfx::kNullCursor;
+ }
+ virtual int GetNonClientComponent(
+ const gfx::Point& point) const OVERRIDE {
+ return HTNOWHERE;
+ }
+ virtual bool ShouldDescendIntoChildForEventHandling(
+ aura::Window* child,
+ const gfx::Point& location) OVERRIDE {
+ return false;
+ }
+ virtual bool CanFocus() OVERRIDE {
+ return false;
+ }
+ virtual void OnCaptureLost() OVERRIDE {
+ }
+ virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE {
+ }
+ virtual void OnDeviceScaleFactorChanged(
+ float device_scale_factor) OVERRIDE {
+ }
+ virtual void OnWindowDestroying() OVERRIDE {}
+ virtual void OnWindowDestroyed() OVERRIDE {
+ delete this;
+ }
+ virtual void OnWindowTargetVisibilityChanged(bool visible) OVERRIDE {
+ }
+ virtual bool HasHitTestMask() const OVERRIDE {
+ return false;
+ }
+ virtual void GetHitTestMask(gfx::Path* mask) const OVERRIDE {}
+ virtual scoped_refptr<ui::Texture> CopyTexture() OVERRIDE {
+ NOTREACHED();
+ return scoped_refptr<ui::Texture>();
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(EmptyWindowDelegate);
+};
+
+} // namespace
+
+namespace internal {
+
+RootWindowController::RootWindowController(aura::RootWindow* root_window)
+ : root_window_(root_window),
+ root_window_layout_(NULL),
+ docked_layout_manager_(NULL),
+ panel_layout_manager_(NULL),
+ touch_hud_debug_(NULL),
+ touch_hud_projection_(NULL) {
+ SetRootWindowController(root_window, this);
+ screen_dimmer_.reset(new ScreenDimmer(root_window));
+
+ stacking_controller_.reset(new StackingController);
+ aura::client::SetStackingClient(root_window, stacking_controller_.get());
+}
+
+RootWindowController::~RootWindowController() {
+ Shutdown();
+ root_window_.reset();
+}
+
+// static
+RootWindowController* RootWindowController::ForLauncher(aura::Window* window) {
+ return GetRootWindowController(window->GetRootWindow());
+}
+
+// static
+RootWindowController* RootWindowController::ForWindow(
+ const aura::Window* window) {
+ return GetRootWindowController(window->GetRootWindow());
+}
+
+// static
+RootWindowController* RootWindowController::ForActiveRootWindow() {
+ return GetRootWindowController(Shell::GetActiveRootWindow());
+}
+
+void RootWindowController::SetWallpaperController(
+ DesktopBackgroundWidgetController* controller) {
+ wallpaper_controller_.reset(controller);
+}
+
+void RootWindowController::SetAnimatingWallpaperController(
+ AnimatingDesktopController* controller) {
+ if (animating_wallpaper_controller_.get())
+ animating_wallpaper_controller_->StopAnimating();
+ animating_wallpaper_controller_.reset(controller);
+}
+
+void RootWindowController::Shutdown() {
+ Shell::GetInstance()->RemoveShellObserver(this);
+
+ if (animating_wallpaper_controller_.get())
+ animating_wallpaper_controller_->StopAnimating();
+ wallpaper_controller_.reset();
+ animating_wallpaper_controller_.reset();
+
+ CloseChildWindows();
+ if (Shell::GetActiveRootWindow() == root_window_) {
+ Shell::GetInstance()->set_active_root_window(
+ Shell::GetPrimaryRootWindow() == root_window_.get() ?
+ NULL : Shell::GetPrimaryRootWindow());
+ }
+ SetRootWindowController(root_window_.get(), NULL);
+ screen_dimmer_.reset();
+ workspace_controller_.reset();
+ // Forget with the display ID so that display lookup
+ // ends up with invalid display.
+ root_window_->ClearProperty(kDisplayIdKey);
+ // And this root window should no longer process events.
+ root_window_->PrepareForShutdown();
+
+ system_background_.reset();
+}
+
+SystemModalContainerLayoutManager*
+RootWindowController::GetSystemModalLayoutManager(aura::Window* window) {
+ aura::Window* container = NULL;
+ if (window) {
+ if (window->parent() &&
+ window->parent()->id() >= kShellWindowId_LockScreenContainer) {
+ container = GetContainer(kShellWindowId_LockSystemModalContainer);
+ } else {
+ container = GetContainer(kShellWindowId_SystemModalContainer);
+ }
+ } else {
+ int modal_window_id = Shell::GetInstance()->session_state_delegate()
+ ->IsUserSessionBlocked() ? kShellWindowId_LockSystemModalContainer :
+ kShellWindowId_SystemModalContainer;
+ container = GetContainer(modal_window_id);
+ }
+ return container ? static_cast<SystemModalContainerLayoutManager*>(
+ container->layout_manager()) : NULL;
+}
+
+aura::Window* RootWindowController::GetContainer(int container_id) {
+ return root_window_->GetChildById(container_id);
+}
+
+const aura::Window* RootWindowController::GetContainer(int container_id) const {
+ return root_window_->GetChildById(container_id);
+}
+
+void RootWindowController::Init(bool first_run_after_boot) {
+ root_window_->SetCursor(ui::kCursorPointer);
+ CreateContainersInRootWindow(root_window_.get());
+ CreateSystemBackground(first_run_after_boot);
+
+ InitLayoutManagers();
+ InitKeyboard();
+ InitTouchHuds();
+
+ if (Shell::GetPrimaryRootWindowController()->
+ GetSystemModalLayoutManager(NULL)->has_modal_background()) {
+ GetSystemModalLayoutManager(NULL)->CreateModalBackground();
+ }
+
+ Shell::GetInstance()->AddShellObserver(this);
+}
+
+void RootWindowController::ShowLauncher() {
+ if (!shelf_->launcher())
+ return;
+ shelf_->launcher()->SetVisible(true);
+ shelf_->status_area_widget()->Show();
+}
+
+void RootWindowController::OnLauncherCreated() {
+ if (panel_layout_manager_)
+ panel_layout_manager_->SetLauncher(shelf_->launcher());
+ if (docked_layout_manager_) {
+ docked_layout_manager_->SetLauncher(shelf_->launcher());
+ if (shelf_->shelf_layout_manager())
+ docked_layout_manager_->AddObserver(shelf_->shelf_layout_manager());
+ }
+}
+
+void RootWindowController::UpdateAfterLoginStatusChange(
+ user::LoginStatus status) {
+ if (status != user::LOGGED_IN_NONE)
+ mouse_event_target_.reset();
+ if (shelf_->status_area_widget())
+ shelf_->status_area_widget()->UpdateAfterLoginStatusChange(status);
+}
+
+void RootWindowController::HandleInitialDesktopBackgroundAnimationStarted() {
+#if defined(OS_CHROMEOS)
+ if (CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kAshAnimateFromBootSplashScreen) &&
+ boot_splash_screen_.get()) {
+ // Make the splash screen fade out so it doesn't obscure the desktop
+ // wallpaper's brightness/grayscale animation.
+ boot_splash_screen_->StartHideAnimation(
+ base::TimeDelta::FromMilliseconds(kBootSplashScreenHideDurationMs));
+ }
+#endif
+}
+
+void RootWindowController::OnWallpaperAnimationFinished(views::Widget* widget) {
+ // Make sure the wallpaper is visible.
+ system_background_->SetColor(SK_ColorBLACK);
+#if defined(OS_CHROMEOS)
+ boot_splash_screen_.reset();
+#endif
+
+ Shell::GetInstance()->user_wallpaper_delegate()->
+ OnWallpaperAnimationFinished();
+ // Only removes old component when wallpaper animation finished. If we
+ // remove the old one before the new wallpaper is done fading in there will
+ // be a white flash during the animation.
+ if (animating_wallpaper_controller()) {
+ DesktopBackgroundWidgetController* controller =
+ animating_wallpaper_controller()->GetController(true);
+ // |desktop_widget_| should be the same animating widget we try to move
+ // to |kDesktopController|. Otherwise, we may close |desktop_widget_|
+ // before move it to |kDesktopController|.
+ DCHECK_EQ(controller->widget(), widget);
+ // Release the old controller and close its background widget.
+ SetWallpaperController(controller);
+ }
+}
+
+void RootWindowController::CloseChildWindows() {
+ mouse_event_target_.reset();
+
+ if (!shelf_.get())
+ return;
+ // panel_layout_manager_ needs to be shut down before windows are destroyed.
+ if (panel_layout_manager_) {
+ panel_layout_manager_->Shutdown();
+ panel_layout_manager_ = NULL;
+ }
+ // docked_layout_manager_ needs to be shut down before windows are destroyed.
+ if (docked_layout_manager_) {
+ if (shelf_->shelf_layout_manager())
+ docked_layout_manager_->RemoveObserver(shelf_->shelf_layout_manager());
+ docked_layout_manager_->Shutdown();
+ docked_layout_manager_ = NULL;
+ }
+
+ // TODO(harrym): Remove when Status Area Widget is a child view.
+ shelf_->ShutdownStatusAreaWidget();
+
+ if (shelf_->shelf_layout_manager())
+ shelf_->shelf_layout_manager()->PrepareForShutdown();
+
+ // Close background widget first as it depends on tooltip.
+ wallpaper_controller_.reset();
+ animating_wallpaper_controller_.reset();
+
+ workspace_controller_.reset();
+ aura::client::SetTooltipClient(root_window_.get(), NULL);
+
+ while (!root_window_->children().empty()) {
+ aura::Window* child = root_window_->children()[0];
+ delete child;
+ }
+
+ shelf_.reset(NULL);
+}
+
+void RootWindowController::MoveWindowsTo(aura::RootWindow* dst) {
+ // Forget the shelf early so that shelf don't update itself using wrong
+ // display info.
+ workspace_controller_->SetShelf(NULL);
+ ReparentAllWindows(root_window_.get(), dst);
+}
+
+ShelfLayoutManager* RootWindowController::GetShelfLayoutManager() {
+ return shelf_->shelf_layout_manager();
+}
+
+SystemTray* RootWindowController::GetSystemTray() {
+ // We assume in throughout the code that this will not return NULL. If code
+ // triggers this for valid reasons, it should test status_area_widget first.
+ CHECK(shelf_->status_area_widget());
+ return shelf_->status_area_widget()->system_tray();
+}
+
+void RootWindowController::ShowContextMenu(const gfx::Point& location_in_screen,
+ ui::MenuSourceType source_type) {
+ DCHECK(Shell::GetInstance()->delegate());
+ scoped_ptr<ui::MenuModel> menu_model(
+ Shell::GetInstance()->delegate()->CreateContextMenu(root_window()));
+ if (!menu_model)
+ return;
+
+ // Background controller may not be set yet if user clicked on status are
+ // before initial animation completion. See crbug.com/222218
+ if (!wallpaper_controller_.get())
+ return;
+
+ views::MenuRunner menu_runner(menu_model.get());
+ if (menu_runner.RunMenuAt(wallpaper_controller_->widget(),
+ NULL, gfx::Rect(location_in_screen, gfx::Size()),
+ views::MenuItemView::TOPLEFT, source_type,
+ views::MenuRunner::CONTEXT_MENU) ==
+ views::MenuRunner::MENU_DELETED) {
+ return;
+ }
+
+ Shell::GetInstance()->UpdateShelfVisibility();
+}
+
+void RootWindowController::UpdateShelfVisibility() {
+ shelf_->shelf_layout_manager()->UpdateVisibilityState();
+}
+
+const aura::Window* RootWindowController::GetFullscreenWindow() const {
+ const aura::Window* container = GetContainer(kShellWindowId_DefaultContainer);
+ for (size_t i = 0; i < container->children().size(); ++i) {
+ aura::Window* child = container->children()[i];
+ if (wm::IsWindowFullscreen(child))
+ return child;
+ }
+ return NULL;
+}
+
+void RootWindowController::InitKeyboard() {
+ if (keyboard::IsKeyboardEnabled()) {
+ aura::Window* parent = root_window();
+
+ keyboard::KeyboardControllerProxy* proxy =
+ Shell::GetInstance()->delegate()->CreateKeyboardControllerProxy();
+ keyboard_controller_.reset(
+ new keyboard::KeyboardController(proxy));
+
+ keyboard_controller_->AddObserver(shelf()->shelf_layout_manager());
+ keyboard_controller_->AddObserver(panel_layout_manager_);
+
+ aura::Window* keyboard_container =
+ keyboard_controller_->GetContainerWindow();
+ keyboard_container->set_id(kShellWindowId_VirtualKeyboardContainer);
+ parent->AddChild(keyboard_container);
+ keyboard_container->SetBounds(parent->bounds());
+ }
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+// RootWindowController, private:
+
+void RootWindowController::InitLayoutManagers() {
+ root_window_layout_ =
+ new RootWindowLayoutManager(root_window_.get());
+ root_window_->SetLayoutManager(root_window_layout_);
+
+ aura::Window* default_container =
+ GetContainer(kShellWindowId_DefaultContainer);
+ // Workspace manager has its own layout managers.
+ workspace_controller_.reset(
+ new WorkspaceController(default_container));
+
+ aura::Window* always_on_top_container =
+ GetContainer(kShellWindowId_AlwaysOnTopContainer);
+ always_on_top_container->SetLayoutManager(
+ new BaseLayoutManager(
+ always_on_top_container->GetRootWindow()));
+ always_on_top_controller_.reset(new internal::AlwaysOnTopController);
+ always_on_top_controller_->SetAlwaysOnTopContainer(always_on_top_container);
+
+ DCHECK(!shelf_.get());
+ aura::Window* shelf_container =
+ GetContainer(internal::kShellWindowId_ShelfContainer);
+ // TODO(harrym): Remove when status area is view.
+ aura::Window* status_container =
+ GetContainer(internal::kShellWindowId_StatusContainer);
+ shelf_.reset(new ShelfWidget(
+ shelf_container, status_container, workspace_controller()));
+
+ if (!Shell::GetInstance()->session_state_delegate()->
+ IsActiveUserSessionStarted()) {
+ // This window exists only to be a event target on login screen.
+ // It does not have to handle events, nor be visible.
+ mouse_event_target_.reset(new aura::Window(new EmptyWindowDelegate));
+ mouse_event_target_->Init(ui::LAYER_NOT_DRAWN);
+
+ aura::Window* lock_background_container =
+ GetContainer(internal::kShellWindowId_LockScreenBackgroundContainer);
+ lock_background_container->AddChild(mouse_event_target_.get());
+ mouse_event_target_->Show();
+ }
+
+ // Create Docked windows layout manager
+ aura::Window* docked_container = GetContainer(
+ internal::kShellWindowId_DockedContainer);
+ docked_layout_manager_ =
+ new internal::DockedWindowLayoutManager(docked_container);
+ docked_container_handler_.reset(
+ new ToplevelWindowEventHandler(docked_container));
+ docked_container->SetLayoutManager(docked_layout_manager_);
+
+ // Create Panel layout manager
+ aura::Window* panel_container = GetContainer(
+ internal::kShellWindowId_PanelContainer);
+ panel_layout_manager_ =
+ new internal::PanelLayoutManager(panel_container);
+ panel_container_handler_.reset(
+ new PanelWindowEventHandler(panel_container));
+ panel_container->SetLayoutManager(panel_layout_manager_);
+}
+
+void RootWindowController::InitTouchHuds() {
+ CommandLine* command_line = CommandLine::ForCurrentProcess();
+ if (command_line->HasSwitch(switches::kAshTouchHud))
+ set_touch_hud_debug(new TouchHudDebug(root_window_.get()));
+ if (Shell::GetInstance()->is_touch_hud_projection_enabled())
+ EnableTouchHudProjection();
+}
+
+void RootWindowController::CreateSystemBackground(
+ bool is_first_run_after_boot) {
+ SkColor color = SK_ColorBLACK;
+#if defined(OS_CHROMEOS)
+ if (is_first_run_after_boot)
+ color = kChromeOsBootColor;
+#endif
+ system_background_.reset(
+ new SystemBackgroundController(root_window_.get(), color));
+
+#if defined(OS_CHROMEOS)
+ // Make a copy of the system's boot splash screen so we can composite it
+ // onscreen until the desktop background is ready.
+ if (is_first_run_after_boot &&
+ (CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kAshCopyHostBackgroundAtBoot) ||
+ CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kAshAnimateFromBootSplashScreen)))
+ boot_splash_screen_.reset(new BootSplashScreen(root_window_.get()));
+#endif
+}
+
+void RootWindowController::CreateContainersInRootWindow(
+ aura::RootWindow* root_window) {
+ // These containers are just used by PowerButtonController to animate groups
+ // of containers simultaneously without messing up the current transformations
+ // on those containers. These are direct children of the root window; all of
+ // the other containers are their children.
+
+ // The desktop background container is not part of the lock animation, so it
+ // is not included in those animate groups.
+ // When screen is locked desktop background is moved to lock screen background
+ // container (moved back on unlock). We want to make sure that there's an
+ // opaque layer occluding the non-lock-screen layers.
+ aura::Window* desktop_background_container = CreateContainer(
+ kShellWindowId_DesktopBackgroundContainer,
+ "DesktopBackgroundContainer",
+ root_window);
+ views::corewm::SetChildWindowVisibilityChangesAnimated(
+ desktop_background_container);
+
+ aura::Window* non_lock_screen_containers = CreateContainer(
+ kShellWindowId_NonLockScreenContainersContainer,
+ "NonLockScreenContainersContainer",
+ root_window);
+
+ aura::Window* lock_background_containers = CreateContainer(
+ kShellWindowId_LockScreenBackgroundContainer,
+ "LockScreenBackgroundContainer",
+ root_window);
+ views::corewm::SetChildWindowVisibilityChangesAnimated(
+ lock_background_containers);
+
+ aura::Window* lock_screen_containers = CreateContainer(
+ kShellWindowId_LockScreenContainersContainer,
+ "LockScreenContainersContainer",
+ root_window);
+ aura::Window* lock_screen_related_containers = CreateContainer(
+ kShellWindowId_LockScreenRelatedContainersContainer,
+ "LockScreenRelatedContainersContainer",
+ root_window);
+
+ CreateContainer(kShellWindowId_UnparentedControlContainer,
+ "UnparentedControlContainer",
+ non_lock_screen_containers);
+
+ aura::Window* default_container = CreateContainer(
+ kShellWindowId_DefaultContainer,
+ "DefaultContainer",
+ non_lock_screen_containers);
+ views::corewm::SetChildWindowVisibilityChangesAnimated(default_container);
+ SetUsesScreenCoordinates(default_container);
+
+ aura::Window* always_on_top_container = CreateContainer(
+ kShellWindowId_AlwaysOnTopContainer,
+ "AlwaysOnTopContainer",
+ non_lock_screen_containers);
+ always_on_top_container_handler_.reset(
+ new ToplevelWindowEventHandler(always_on_top_container));
+ views::corewm::SetChildWindowVisibilityChangesAnimated(
+ always_on_top_container);
+ SetUsesScreenCoordinates(always_on_top_container);
+
+ aura::Window* docked_container = CreateContainer(
+ kShellWindowId_DockedContainer,
+ "DockedContainer",
+ non_lock_screen_containers);
+ SetUsesScreenCoordinates(docked_container);
+
+ aura::Window* panel_container = CreateContainer(
+ kShellWindowId_PanelContainer,
+ "PanelContainer",
+ non_lock_screen_containers);
+ SetUsesScreenCoordinates(panel_container);
+
+ aura::Window* shelf_container =
+ CreateContainer(kShellWindowId_ShelfContainer,
+ "ShelfContainer",
+ non_lock_screen_containers);
+ SetUsesScreenCoordinates(shelf_container);
+ DescendantShouldStayInSameRootWindow(shelf_container);
+
+ aura::Window* app_list_container =
+ CreateContainer(kShellWindowId_AppListContainer,
+ "AppListContainer",
+ non_lock_screen_containers);
+ SetUsesScreenCoordinates(app_list_container);
+
+ aura::Window* modal_container = CreateContainer(
+ kShellWindowId_SystemModalContainer,
+ "SystemModalContainer",
+ non_lock_screen_containers);
+ modal_container_handler_.reset(
+ new ToplevelWindowEventHandler(modal_container));
+ modal_container->SetLayoutManager(
+ new SystemModalContainerLayoutManager(modal_container));
+ views::corewm::SetChildWindowVisibilityChangesAnimated(modal_container);
+ SetUsesScreenCoordinates(modal_container);
+
+ aura::Window* input_method_container = CreateContainer(
+ kShellWindowId_InputMethodContainer,
+ "InputMethodContainer",
+ non_lock_screen_containers);
+ SetUsesScreenCoordinates(input_method_container);
+
+ // TODO(beng): Figure out if we can make this use
+ // SystemModalContainerEventFilter instead of stops_event_propagation.
+ aura::Window* lock_container = CreateContainer(
+ kShellWindowId_LockScreenContainer,
+ "LockScreenContainer",
+ lock_screen_containers);
+ lock_container->SetLayoutManager(
+ new BaseLayoutManager(root_window));
+ SetUsesScreenCoordinates(lock_container);
+ // TODO(beng): stopsevents
+
+ aura::Window* lock_modal_container = CreateContainer(
+ kShellWindowId_LockSystemModalContainer,
+ "LockSystemModalContainer",
+ lock_screen_containers);
+ lock_modal_container_handler_.reset(
+ new ToplevelWindowEventHandler(lock_modal_container));
+ lock_modal_container->SetLayoutManager(
+ new SystemModalContainerLayoutManager(lock_modal_container));
+ views::corewm::SetChildWindowVisibilityChangesAnimated(lock_modal_container);
+ SetUsesScreenCoordinates(lock_modal_container);
+
+ aura::Window* status_container =
+ CreateContainer(kShellWindowId_StatusContainer,
+ "StatusContainer",
+ lock_screen_related_containers);
+ SetUsesScreenCoordinates(status_container);
+ DescendantShouldStayInSameRootWindow(status_container);
+
+ aura::Window* settings_bubble_container = CreateContainer(
+ kShellWindowId_SettingBubbleContainer,
+ "SettingBubbleContainer",
+ lock_screen_related_containers);
+ views::corewm::SetChildWindowVisibilityChangesAnimated(
+ settings_bubble_container);
+ SetUsesScreenCoordinates(settings_bubble_container);
+ DescendantShouldStayInSameRootWindow(settings_bubble_container);
+
+ aura::Window* menu_container = CreateContainer(
+ kShellWindowId_MenuContainer,
+ "MenuContainer",
+ lock_screen_related_containers);
+ views::corewm::SetChildWindowVisibilityChangesAnimated(menu_container);
+ SetUsesScreenCoordinates(menu_container);
+
+ aura::Window* drag_drop_container = CreateContainer(
+ kShellWindowId_DragImageAndTooltipContainer,
+ "DragImageAndTooltipContainer",
+ lock_screen_related_containers);
+ views::corewm::SetChildWindowVisibilityChangesAnimated(drag_drop_container);
+ SetUsesScreenCoordinates(drag_drop_container);
+
+ aura::Window* overlay_container = CreateContainer(
+ kShellWindowId_OverlayContainer,
+ "OverlayContainer",
+ lock_screen_related_containers);
+ SetUsesScreenCoordinates(overlay_container);
+
+ CreateContainer(kShellWindowId_PowerButtonAnimationContainer,
+ "PowerButtonAnimationContainer", root_window) ;
+}
+
+void RootWindowController::EnableTouchHudProjection() {
+ if (touch_hud_projection_)
+ return;
+ set_touch_hud_projection(new TouchHudProjection(root_window_.get()));
+}
+
+void RootWindowController::DisableTouchHudProjection() {
+ if (!touch_hud_projection_)
+ return;
+ touch_hud_projection_->Remove();
+}
+
+void RootWindowController::OnLoginStateChanged(user::LoginStatus status) {
+ shelf_->shelf_layout_manager()->UpdateVisibilityState();
+}
+
+void RootWindowController::OnTouchHudProjectionToggled(bool enabled) {
+ if (enabled)
+ EnableTouchHudProjection();
+ else
+ DisableTouchHudProjection();
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/root_window_controller.h b/chromium/ash/root_window_controller.h
new file mode 100644
index 00000000000..140a7d26c17
--- /dev/null
+++ b/chromium/ash/root_window_controller.h
@@ -0,0 +1,291 @@
+// Copyright (c) 2012 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 ASH_ROOT_WINDOW_CONTROLLER_H_
+#define ASH_ROOT_WINDOW_CONTROLLER_H_
+
+#include <map>
+
+#include "ash/ash_export.h"
+#include "ash/shelf/shelf_types.h"
+#include "ash/shell_observer.h"
+#include "ash/system/user/login_status.h"
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "ui/base/ui_base_types.h"
+
+class SkBitmap;
+
+namespace aura {
+class EventFilter;
+class RootWindow;
+class Window;
+}
+
+namespace gfx {
+class Point;
+}
+
+namespace views {
+class Widget;
+
+namespace corewm {
+class InputMethodEventFilter;
+class RootWindowEventFilter;
+}
+}
+
+namespace keyboard {
+class KeyboardController;
+}
+
+namespace ash {
+class StackingController;
+class ShelfWidget;
+class SystemTray;
+class ToplevelWindowEventHandler;
+
+namespace internal {
+
+class AlwaysOnTopController;
+class AnimatingDesktopController;
+class DesktopBackgroundWidgetController;
+class DockedWindowLayoutManager;
+class PanelLayoutManager;
+class RootWindowLayoutManager;
+class ScreenDimmer;
+class ShelfLayoutManager;
+class StatusAreaWidget;
+class SystemBackgroundController;
+class SystemModalContainerLayoutManager;
+class TouchHudDebug;
+class TouchHudProjection;
+class WorkspaceController;
+
+#if defined(USE_X11)
+class BootSplashScreen;
+#endif
+
+// This class maintains the per root window state for ash. This class
+// owns the root window and other dependent objects that should be
+// deleted upon the deletion of the root window. The RootWindowController
+// for particular root window is stored as a property and can be obtained
+// using |GetRootWindowController(aura::RootWindow*)| function.
+class ASH_EXPORT RootWindowController : public ShellObserver {
+ public:
+ explicit RootWindowController(aura::RootWindow* root_window);
+ virtual ~RootWindowController();
+
+ // Returns a RootWindowController that has a launcher for given
+ // |window|. This returns the RootWindowController for the |window|'s
+ // root window when multiple launcher mode is enabled, or the primary
+ // RootWindowController otherwise.
+ static RootWindowController* ForLauncher(aura::Window* window);
+
+ // Returns a RootWindowController of the window's root window.
+ static RootWindowController* ForWindow(const aura::Window* window);
+
+ // Returns the RootWindowController of the active root window.
+ static internal::RootWindowController* ForActiveRootWindow();
+
+ aura::RootWindow* root_window() { return root_window_.get(); }
+
+ RootWindowLayoutManager* root_window_layout() { return root_window_layout_; }
+
+ WorkspaceController* workspace_controller() {
+ return workspace_controller_.get();
+ }
+
+ AlwaysOnTopController* always_on_top_controller() {
+ return always_on_top_controller_.get();
+ }
+
+ ScreenDimmer* screen_dimmer() { return screen_dimmer_.get(); }
+
+ // Access the shelf associated with this root window controller,
+ // NULL if no such shelf exists.
+ ShelfWidget* shelf() { return shelf_.get(); }
+
+ // Get touch HUDs associated with this root window controller.
+ TouchHudDebug* touch_hud_debug() const {
+ return touch_hud_debug_;
+ }
+ TouchHudProjection* touch_hud_projection() const {
+ return touch_hud_projection_;
+ }
+
+ // Set touch HUDs for this root window controller. The root window controller
+ // will not own the HUDs; their lifetimes are managed by themselves. Whenever
+ // the widget showing a HUD is being destroyed (e.g. because of detaching a
+ // display), the HUD deletes itself.
+ void set_touch_hud_debug(TouchHudDebug* hud) {
+ touch_hud_debug_ = hud;
+ }
+ void set_touch_hud_projection(TouchHudProjection* hud) {
+ touch_hud_projection_ = hud;
+ }
+
+ DesktopBackgroundWidgetController* wallpaper_controller() {
+ return wallpaper_controller_.get();
+ }
+ void SetWallpaperController(DesktopBackgroundWidgetController* controller);
+ AnimatingDesktopController* animating_wallpaper_controller() {
+ return animating_wallpaper_controller_.get();
+ }
+ void SetAnimatingWallpaperController(AnimatingDesktopController* controller);
+
+ // Access the shelf layout manager associated with this root
+ // window controller, NULL if no such shelf exists.
+ ShelfLayoutManager* GetShelfLayoutManager();
+
+ // Returns the system tray on this root window. Note that
+ // calling this on the root window that doesn't have a launcher will
+ // lead to a crash.
+ SystemTray* GetSystemTray();
+
+ // Shows context menu at the |location_in_screen|. This uses
+ // |ShellDelegate::CreateContextMenu| to define the content of the menu.
+ void ShowContextMenu(const gfx::Point& location_in_screen,
+ ui::MenuSourceType source_type);
+
+ // Returns the layout-manager for the appropriate modal-container. If the
+ // window is inside the lockscreen modal container, then the layout manager
+ // for that is returned. Otherwise the layout manager for the default modal
+ // container is returned.
+ // If no window is specified (i.e. |window| is NULL), then the lockscreen
+ // modal container is used if the screen is currently locked. Otherwise, the
+ // default modal container is used.
+ SystemModalContainerLayoutManager* GetSystemModalLayoutManager(
+ aura::Window* window);
+
+ aura::Window* GetContainer(int container_id);
+ const aura::Window* GetContainer(int container_id) const;
+
+ // Initializes the RootWindowController. |first_run_after_boot| is
+ // set to true only for primary root window after boot.
+ void Init(bool first_run_after_boot);
+
+ // Show launcher view if it was created hidden (before session has started).
+ void ShowLauncher();
+
+ // Called when the launcher associated with this root window is created.
+ void OnLauncherCreated();
+
+ // Called when the login status changes after login (such as lock/unlock).
+ // TODO(oshima): Investigate if we can merge this and |OnLoginStateChanged|.
+ void UpdateAfterLoginStatusChange(user::LoginStatus status);
+
+ // Called when the brightness/grayscale animation from white to the login
+ // desktop background image has started. Starts |boot_splash_screen_|'s
+ // hiding animation (if the screen is non-NULL).
+ void HandleInitialDesktopBackgroundAnimationStarted();
+
+ // Called when the wallpaper ainmation is finished. Updates |background_|
+ // to be black and drops |boot_splash_screen_| and moves the wallpaper
+ // controller into the root window controller. |widget| holds the wallpaper
+ // image, or NULL if the background is a solid color.
+ void OnWallpaperAnimationFinished(views::Widget* widget);
+
+ // Deletes associated objects and clears the state, but doesn't delete
+ // the root window yet. This is used to delete a secondary displays'
+ // root window safely when the display disconnect signal is received,
+ // which may come while we're in the nested message loop.
+ void Shutdown();
+
+ // Deletes all child windows and performs necessary cleanup.
+ void CloseChildWindows();
+
+ // Moves child windows to |dest|.
+ void MoveWindowsTo(aura::RootWindow* dest);
+
+ // Force the shelf to query for it's current visibility state.
+ void UpdateShelfVisibility();
+
+ // Initialize touch HUDs if necessary.
+ void InitTouchHuds();
+
+ // Returns the window, if any, which is in fullscreen mode in the active
+ // workspace. Exposed here so clients of Ash don't need to know the details
+ // of workspace management.
+ const aura::Window* GetFullscreenWindow() const;
+
+ private:
+ void InitLayoutManagers();
+
+ // Initializes |system_background_| and possibly also |boot_splash_screen_|.
+ // |is_first_run_after_boot| determines the background's initial color.
+ void CreateSystemBackground(bool is_first_run_after_boot);
+
+ // Creates each of the special window containers that holds windows of various
+ // types in the shell UI.
+ void CreateContainersInRootWindow(aura::RootWindow* root_window);
+
+ // Initializes the virtual keyboard.
+ void InitKeyboard();
+
+ // Enables projection touch HUD.
+ void EnableTouchHudProjection();
+
+ // Disables projection touch HUD.
+ void DisableTouchHudProjection();
+
+ // Overridden from ShellObserver.
+ virtual void OnLoginStateChanged(user::LoginStatus status) OVERRIDE;
+ virtual void OnTouchHudProjectionToggled(bool enabled) OVERRIDE;
+
+ scoped_ptr<aura::RootWindow> root_window_;
+ RootWindowLayoutManager* root_window_layout_;
+
+ scoped_ptr<StackingController> stacking_controller_;
+
+ scoped_ptr<keyboard::KeyboardController> keyboard_controller_;
+
+ // The shelf for managing the launcher and the status widget.
+ scoped_ptr<ShelfWidget> shelf_;
+
+ // An invisible/empty window used as a event target for
+ // |MouseCursorEventFilter| before a user logs in.
+ // (crbug.com/266987)
+ // Its container is |LockScreenBackgroundContainer| and
+ // this must be deleted before the container is deleted.
+ scoped_ptr<aura::Window> mouse_event_target_;
+
+ // Manages layout of docked windows. Owned by DockedContainer.
+ DockedWindowLayoutManager* docked_layout_manager_;
+
+ // Manages layout of panels. Owned by PanelContainer.
+ PanelLayoutManager* panel_layout_manager_;
+
+ scoped_ptr<SystemBackgroundController> system_background_;
+#if defined(USE_X11)
+ scoped_ptr<BootSplashScreen> boot_splash_screen_;
+#endif
+
+ scoped_ptr<ScreenDimmer> screen_dimmer_;
+ scoped_ptr<WorkspaceController> workspace_controller_;
+ scoped_ptr<AlwaysOnTopController> always_on_top_controller_;
+
+ // Heads-up displays for touch events. These HUDs are not owned by the root
+ // window controller and manage their own lifetimes.
+ TouchHudDebug* touch_hud_debug_;
+ TouchHudProjection* touch_hud_projection_;
+
+ // We need to own event handlers for various containers.
+ scoped_ptr<ToplevelWindowEventHandler> default_container_handler_;
+ scoped_ptr<ToplevelWindowEventHandler> always_on_top_container_handler_;
+ scoped_ptr<ToplevelWindowEventHandler> modal_container_handler_;
+ scoped_ptr<ToplevelWindowEventHandler> lock_modal_container_handler_;
+ scoped_ptr<ToplevelWindowEventHandler> panel_container_handler_;
+ scoped_ptr<ToplevelWindowEventHandler> docked_container_handler_;
+
+ scoped_ptr<DesktopBackgroundWidgetController> wallpaper_controller_;
+ scoped_ptr<AnimatingDesktopController> animating_wallpaper_controller_;
+
+ DISALLOW_COPY_AND_ASSIGN(RootWindowController);
+};
+
+} // namespace internal
+} // ash
+
+#endif // ASH_ROOT_WINDOW_CONTROLLER_H_
diff --git a/chromium/ash/root_window_controller_unittest.cc b/chromium/ash/root_window_controller_unittest.cc
new file mode 100644
index 00000000000..b4c16e35f2a
--- /dev/null
+++ b/chromium/ash/root_window_controller_unittest.cc
@@ -0,0 +1,575 @@
+// Copyright (c) 2012 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.
+
+#include "ash/root_window_controller.h"
+
+#include "ash/session_state_delegate.h"
+#include "ash/shelf/shelf_layout_manager.h"
+#include "ash/shell.h"
+#include "ash/shell_window_ids.h"
+#include "ash/system/tray/system_tray_delegate.h"
+#include "ash/test/ash_test_base.h"
+#include "ash/wm/system_modal_container_layout_manager.h"
+#include "ash/wm/window_properties.h"
+#include "ash/wm/window_util.h"
+#include "base/command_line.h"
+#include "ui/aura/client/focus_change_observer.h"
+#include "ui/aura/client/focus_client.h"
+#include "ui/aura/env.h"
+#include "ui/aura/root_window.h"
+#include "ui/aura/test/event_generator.h"
+#include "ui/aura/test/test_window_delegate.h"
+#include "ui/aura/test/test_windows.h"
+#include "ui/aura/window.h"
+#include "ui/aura/window_tracker.h"
+#include "ui/keyboard/keyboard_switches.h"
+#include "ui/views/controls/menu/menu_controller.h"
+#include "ui/views/widget/widget.h"
+#include "ui/views/widget/widget_delegate.h"
+
+using aura::Window;
+using views::Widget;
+
+namespace ash {
+namespace {
+
+class TestDelegate : public views::WidgetDelegateView {
+ public:
+ explicit TestDelegate(bool system_modal) : system_modal_(system_modal) {}
+ virtual ~TestDelegate() {}
+
+ // Overridden from views::WidgetDelegate:
+ virtual views::View* GetContentsView() OVERRIDE {
+ return this;
+ }
+
+ virtual ui::ModalType GetModalType() const OVERRIDE {
+ return system_modal_ ? ui::MODAL_TYPE_SYSTEM : ui::MODAL_TYPE_NONE;
+ }
+
+ private:
+ bool system_modal_;
+ DISALLOW_COPY_AND_ASSIGN(TestDelegate);
+};
+
+class DeleteOnBlurDelegate : public aura::test::TestWindowDelegate,
+ public aura::client::FocusChangeObserver {
+ public:
+ DeleteOnBlurDelegate() : window_(NULL) {}
+ virtual ~DeleteOnBlurDelegate() {}
+
+ void SetWindow(aura::Window* window) {
+ window_ = window;
+ aura::client::SetFocusChangeObserver(window_, this);
+ }
+
+ private:
+ // aura::test::TestWindowDelegate overrides:
+ virtual bool CanFocus() OVERRIDE {
+ return true;
+ }
+
+ // aura::client::FocusChangeObserver implementation:
+ virtual void OnWindowFocused(aura::Window* gained_focus,
+ aura::Window* lost_focus) OVERRIDE {
+ if (window_ == lost_focus)
+ delete window_;
+ }
+
+ aura::Window* window_;
+
+ DISALLOW_COPY_AND_ASSIGN(DeleteOnBlurDelegate);
+};
+
+class ClickTestWindow : public views::WidgetDelegateView {
+ public:
+ ClickTestWindow() : mouse_presses_(0) {}
+ virtual ~ClickTestWindow() {}
+
+ // Overridden from views::WidgetDelegate:
+ virtual views::View* GetContentsView() OVERRIDE {
+ return this;
+ }
+
+ aura::Window* CreateTestWindowWithParent(aura::Window* parent) {
+ DCHECK(parent);
+ views::Widget* widget = Widget::CreateWindowWithParent(this, parent);
+ return widget->GetNativeView();
+ }
+
+ // Overridden from views::View:
+ virtual bool OnMousePressed(const ui::MouseEvent& event) OVERRIDE {
+ mouse_presses_++;
+ return false;
+ }
+
+ int mouse_presses() const { return mouse_presses_; }
+
+ private:
+ int mouse_presses_;
+
+ DISALLOW_COPY_AND_ASSIGN(ClickTestWindow);
+};
+
+} // namespace
+
+namespace test {
+
+class RootWindowControllerTest : public test::AshTestBase {
+ public:
+ views::Widget* CreateTestWidget(const gfx::Rect& bounds) {
+ views::Widget* widget = views::Widget::CreateWindowWithContextAndBounds(
+ NULL, CurrentContext(), bounds);
+ widget->Show();
+ return widget;
+ }
+
+ views::Widget* CreateModalWidget(const gfx::Rect& bounds) {
+ views::Widget* widget = views::Widget::CreateWindowWithContextAndBounds(
+ new TestDelegate(true), CurrentContext(), bounds);
+ widget->Show();
+ return widget;
+ }
+
+ views::Widget* CreateModalWidgetWithParent(const gfx::Rect& bounds,
+ gfx::NativeWindow parent) {
+ views::Widget* widget =
+ views::Widget::CreateWindowWithParentAndBounds(new TestDelegate(true),
+ parent,
+ bounds);
+ widget->Show();
+ return widget;
+ }
+
+ aura::Window* GetModalContainer(aura::RootWindow* root_window) {
+ return Shell::GetContainer(
+ root_window,
+ ash::internal::kShellWindowId_SystemModalContainer);
+ }
+};
+
+TEST_F(RootWindowControllerTest, MoveWindows_Basic) {
+ if (!SupportsMultipleDisplays())
+ return;
+
+ UpdateDisplay("600x600,500x500");
+ Shell::RootWindowList root_windows = Shell::GetAllRootWindows();
+ internal::RootWindowController* controller =
+ Shell::GetPrimaryRootWindowController();
+ internal::ShelfLayoutManager* shelf_layout_manager =
+ controller->GetShelfLayoutManager();
+ shelf_layout_manager->SetAutoHideBehavior(
+ ash::SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS);
+
+ views::Widget* normal = CreateTestWidget(gfx::Rect(650, 10, 100, 100));
+ EXPECT_EQ(root_windows[1], normal->GetNativeView()->GetRootWindow());
+ EXPECT_EQ("650,10 100x100", normal->GetWindowBoundsInScreen().ToString());
+ EXPECT_EQ("50,10 100x100",
+ normal->GetNativeView()->GetBoundsInRootWindow().ToString());
+
+ views::Widget* maximized = CreateTestWidget(gfx::Rect(700, 10, 100, 100));
+ maximized->Maximize();
+ EXPECT_EQ(root_windows[1], maximized->GetNativeView()->GetRootWindow());
+ EXPECT_EQ("600,0 500x452", maximized->GetWindowBoundsInScreen().ToString());
+ EXPECT_EQ("0,0 500x452",
+ maximized->GetNativeView()->GetBoundsInRootWindow().ToString());
+
+ views::Widget* minimized = CreateTestWidget(gfx::Rect(800, 10, 100, 100));
+ minimized->Minimize();
+ EXPECT_EQ(root_windows[1], minimized->GetNativeView()->GetRootWindow());
+ EXPECT_EQ("800,10 100x100",
+ minimized->GetWindowBoundsInScreen().ToString());
+
+ views::Widget* fullscreen = CreateTestWidget(gfx::Rect(900, 10, 100, 100));
+ fullscreen->SetFullscreen(true);
+ EXPECT_EQ(root_windows[1], fullscreen->GetNativeView()->GetRootWindow());
+
+ EXPECT_EQ("600,0 500x500",
+ fullscreen->GetWindowBoundsInScreen().ToString());
+ EXPECT_EQ("0,0 500x500",
+ fullscreen->GetNativeView()->GetBoundsInRootWindow().ToString());
+
+ views::Widget* unparented_control = new Widget;
+ Widget::InitParams params;
+ params.bounds = gfx::Rect(650, 10, 100, 100);
+ params.context = CurrentContext();
+ params.type = Widget::InitParams::TYPE_CONTROL;
+ unparented_control->Init(params);
+ EXPECT_EQ(root_windows[1],
+ unparented_control->GetNativeView()->GetRootWindow());
+ EXPECT_EQ(internal::kShellWindowId_UnparentedControlContainer,
+ unparented_control->GetNativeView()->parent()->id());
+
+ aura::Window* panel = CreateTestWindowInShellWithDelegateAndType(
+ NULL, aura::client::WINDOW_TYPE_PANEL, 0, gfx::Rect(700, 100, 100, 100));
+ EXPECT_EQ(root_windows[1], panel->GetRootWindow());
+ EXPECT_EQ(internal::kShellWindowId_PanelContainer, panel->parent()->id());
+
+ // Make sure a window that will delete itself when losing focus
+ // will not crash.
+ aura::WindowTracker tracker;
+ DeleteOnBlurDelegate delete_on_blur_delegate;
+ aura::Window* d2 = CreateTestWindowInShellWithDelegate(
+ &delete_on_blur_delegate, 0, gfx::Rect(50, 50, 100, 100));
+ delete_on_blur_delegate.SetWindow(d2);
+ aura::client::GetFocusClient(root_windows[0])->FocusWindow(d2);
+ tracker.Add(d2);
+
+ UpdateDisplay("600x600");
+
+ // d2 must have been deleted.
+ EXPECT_FALSE(tracker.Contains(d2));
+
+ EXPECT_EQ(root_windows[0], normal->GetNativeView()->GetRootWindow());
+ EXPECT_EQ("50,10 100x100", normal->GetWindowBoundsInScreen().ToString());
+ EXPECT_EQ("50,10 100x100",
+ normal->GetNativeView()->GetBoundsInRootWindow().ToString());
+
+ // Maximized area on primary display has 3px (given as
+ // kAutoHideSize in shelf_layout_manager.cc) inset at the bottom.
+
+ // First clear fullscreen status, since both fullscreen and maximized windows
+ // share the same desktop workspace, which cancels the shelf status.
+ fullscreen->SetFullscreen(false);
+ EXPECT_EQ(root_windows[0], maximized->GetNativeView()->GetRootWindow());
+ EXPECT_EQ("0,0 600x597",
+ maximized->GetWindowBoundsInScreen().ToString());
+ EXPECT_EQ("0,0 600x597",
+ maximized->GetNativeView()->GetBoundsInRootWindow().ToString());
+
+ // Set fullscreen to true. In that case the 3px inset becomes invisible so
+ // the maximized window can also use the area fully.
+ fullscreen->SetFullscreen(true);
+ EXPECT_EQ(root_windows[0], maximized->GetNativeView()->GetRootWindow());
+ EXPECT_EQ("0,0 600x600",
+ maximized->GetWindowBoundsInScreen().ToString());
+ EXPECT_EQ("0,0 600x600",
+ maximized->GetNativeView()->GetBoundsInRootWindow().ToString());
+
+ EXPECT_EQ(root_windows[0], minimized->GetNativeView()->GetRootWindow());
+ EXPECT_EQ("200,10 100x100",
+ minimized->GetWindowBoundsInScreen().ToString());
+
+ EXPECT_EQ(root_windows[0], fullscreen->GetNativeView()->GetRootWindow());
+ EXPECT_TRUE(fullscreen->IsFullscreen());
+ EXPECT_EQ("0,0 600x600",
+ fullscreen->GetWindowBoundsInScreen().ToString());
+ EXPECT_EQ("0,0 600x600",
+ fullscreen->GetNativeView()->GetBoundsInRootWindow().ToString());
+
+ // Test if the restore bounds are correctly updated.
+ wm::RestoreWindow(maximized->GetNativeView());
+ EXPECT_EQ("100,10 100x100", maximized->GetWindowBoundsInScreen().ToString());
+ EXPECT_EQ("100,10 100x100",
+ maximized->GetNativeView()->GetBoundsInRootWindow().ToString());
+
+ fullscreen->SetFullscreen(false);
+ EXPECT_EQ("300,10 100x100",
+ fullscreen->GetWindowBoundsInScreen().ToString());
+ EXPECT_EQ("300,10 100x100",
+ fullscreen->GetNativeView()->GetBoundsInRootWindow().ToString());
+
+ // Test if the unparented widget has moved.
+ EXPECT_EQ(root_windows[0],
+ unparented_control->GetNativeView()->GetRootWindow());
+ EXPECT_EQ(internal::kShellWindowId_UnparentedControlContainer,
+ unparented_control->GetNativeView()->parent()->id());
+
+ // Test if the panel has moved.
+ EXPECT_EQ(root_windows[0], panel->GetRootWindow());
+ EXPECT_EQ(internal::kShellWindowId_PanelContainer, panel->parent()->id());
+}
+
+TEST_F(RootWindowControllerTest, MoveWindows_Modal) {
+ if (!SupportsMultipleDisplays())
+ return;
+
+ UpdateDisplay("500x500,500x500");
+
+ Shell::RootWindowList root_windows = Shell::GetAllRootWindows();
+ // Emulate virtual screen coordinate system.
+ root_windows[0]->SetBounds(gfx::Rect(0, 0, 500, 500));
+ root_windows[1]->SetBounds(gfx::Rect(500, 0, 500, 500));
+
+ views::Widget* normal = CreateTestWidget(gfx::Rect(300, 10, 100, 100));
+ EXPECT_EQ(root_windows[0], normal->GetNativeView()->GetRootWindow());
+ EXPECT_TRUE(wm::IsActiveWindow(normal->GetNativeView()));
+
+ views::Widget* modal = CreateModalWidget(gfx::Rect(650, 10, 100, 100));
+ EXPECT_EQ(root_windows[1], modal->GetNativeView()->GetRootWindow());
+ EXPECT_TRUE(GetModalContainer(root_windows[1])->Contains(
+ modal->GetNativeView()));
+ EXPECT_TRUE(wm::IsActiveWindow(modal->GetNativeView()));
+
+ aura::test::EventGenerator generator_1st(root_windows[0]);
+ generator_1st.ClickLeftButton();
+ EXPECT_TRUE(wm::IsActiveWindow(modal->GetNativeView()));
+
+ UpdateDisplay("500x500");
+ EXPECT_EQ(root_windows[0], modal->GetNativeView()->GetRootWindow());
+ EXPECT_TRUE(wm::IsActiveWindow(modal->GetNativeView()));
+ generator_1st.ClickLeftButton();
+ EXPECT_TRUE(wm::IsActiveWindow(modal->GetNativeView()));
+}
+
+TEST_F(RootWindowControllerTest, ModalContainer) {
+ UpdateDisplay("600x600");
+ Shell* shell = Shell::GetInstance();
+ internal::RootWindowController* controller =
+ shell->GetPrimaryRootWindowController();
+ EXPECT_EQ(user::LOGGED_IN_USER,
+ shell->system_tray_delegate()->GetUserLoginStatus());
+ EXPECT_EQ(Shell::GetContainer(controller->root_window(),
+ internal::kShellWindowId_SystemModalContainer)->layout_manager(),
+ controller->GetSystemModalLayoutManager(NULL));
+
+ views::Widget* session_modal_widget =
+ CreateModalWidget(gfx::Rect(300, 10, 100, 100));
+ EXPECT_EQ(Shell::GetContainer(controller->root_window(),
+ internal::kShellWindowId_SystemModalContainer)->layout_manager(),
+ controller->GetSystemModalLayoutManager(
+ session_modal_widget->GetNativeView()));
+
+ shell->session_state_delegate()->LockScreen();
+ EXPECT_EQ(user::LOGGED_IN_LOCKED,
+ shell->system_tray_delegate()->GetUserLoginStatus());
+ EXPECT_EQ(Shell::GetContainer(controller->root_window(),
+ internal::kShellWindowId_LockSystemModalContainer)->layout_manager(),
+ controller->GetSystemModalLayoutManager(NULL));
+
+ aura::Window* lock_container =
+ Shell::GetContainer(controller->root_window(),
+ internal::kShellWindowId_LockScreenContainer);
+ views::Widget* lock_modal_widget =
+ CreateModalWidgetWithParent(gfx::Rect(300, 10, 100, 100), lock_container);
+ EXPECT_EQ(Shell::GetContainer(controller->root_window(),
+ internal::kShellWindowId_LockSystemModalContainer)->layout_manager(),
+ controller->GetSystemModalLayoutManager(
+ lock_modal_widget->GetNativeView()));
+ EXPECT_EQ(Shell::GetContainer(controller->root_window(),
+ internal::kShellWindowId_SystemModalContainer)->layout_manager(),
+ controller->GetSystemModalLayoutManager(
+ session_modal_widget->GetNativeView()));
+
+ shell->session_state_delegate()->UnlockScreen();
+}
+
+TEST_F(RootWindowControllerTest, ModalContainerNotLoggedInLoggedIn) {
+ UpdateDisplay("600x600");
+ Shell* shell = Shell::GetInstance();
+
+ // Configure login screen environment.
+ SetUserLoggedIn(false);
+ EXPECT_EQ(user::LOGGED_IN_NONE,
+ shell->system_tray_delegate()->GetUserLoginStatus());
+ EXPECT_EQ(0, shell->session_state_delegate()->NumberOfLoggedInUsers());
+ EXPECT_FALSE(shell->session_state_delegate()->IsActiveUserSessionStarted());
+
+ internal::RootWindowController* controller =
+ shell->GetPrimaryRootWindowController();
+ EXPECT_EQ(Shell::GetContainer(controller->root_window(),
+ internal::kShellWindowId_LockSystemModalContainer)->layout_manager(),
+ controller->GetSystemModalLayoutManager(NULL));
+
+ aura::Window* lock_container =
+ Shell::GetContainer(controller->root_window(),
+ internal::kShellWindowId_LockScreenContainer);
+ views::Widget* login_modal_widget =
+ CreateModalWidgetWithParent(gfx::Rect(300, 10, 100, 100), lock_container);
+ EXPECT_EQ(Shell::GetContainer(controller->root_window(),
+ internal::kShellWindowId_LockSystemModalContainer)->layout_manager(),
+ controller->GetSystemModalLayoutManager(
+ login_modal_widget->GetNativeView()));
+ login_modal_widget->Close();
+
+ // Configure user session environment.
+ SetUserLoggedIn(true);
+ SetSessionStarted(true);
+ EXPECT_EQ(user::LOGGED_IN_USER,
+ shell->system_tray_delegate()->GetUserLoginStatus());
+ EXPECT_EQ(1, shell->session_state_delegate()->NumberOfLoggedInUsers());
+ EXPECT_TRUE(shell->session_state_delegate()->IsActiveUserSessionStarted());
+ EXPECT_EQ(Shell::GetContainer(controller->root_window(),
+ internal::kShellWindowId_SystemModalContainer)->layout_manager(),
+ controller->GetSystemModalLayoutManager(NULL));
+
+ views::Widget* session_modal_widget =
+ CreateModalWidget(gfx::Rect(300, 10, 100, 100));
+ EXPECT_EQ(Shell::GetContainer(controller->root_window(),
+ internal::kShellWindowId_SystemModalContainer)->layout_manager(),
+ controller->GetSystemModalLayoutManager(
+ session_modal_widget->GetNativeView()));
+}
+
+TEST_F(RootWindowControllerTest, ModalContainerBlockedSession) {
+ UpdateDisplay("600x600");
+ Shell* shell = Shell::GetInstance();
+ internal::RootWindowController* controller =
+ shell->GetPrimaryRootWindowController();
+ aura::Window* lock_container =
+ Shell::GetContainer(controller->root_window(),
+ internal::kShellWindowId_LockScreenContainer);
+ for (int block_reason = FIRST_BLOCK_REASON;
+ block_reason < NUMBER_OF_BLOCK_REASONS;
+ ++block_reason) {
+ views::Widget* session_modal_widget =
+ CreateModalWidget(gfx::Rect(300, 10, 100, 100));
+ EXPECT_EQ(Shell::GetContainer(controller->root_window(),
+ internal::kShellWindowId_SystemModalContainer)->layout_manager(),
+ controller->GetSystemModalLayoutManager(
+ session_modal_widget->GetNativeView()));
+ EXPECT_EQ(Shell::GetContainer(controller->root_window(),
+ internal::kShellWindowId_SystemModalContainer)->layout_manager(),
+ controller->GetSystemModalLayoutManager(NULL));
+ session_modal_widget->Close();
+
+ BlockUserSession(static_cast<UserSessionBlockReason>(block_reason));
+
+ EXPECT_EQ(Shell::GetContainer(controller->root_window(),
+ internal::kShellWindowId_LockSystemModalContainer)->layout_manager(),
+ controller->GetSystemModalLayoutManager(NULL));
+
+ views::Widget* lock_modal_widget =
+ CreateModalWidgetWithParent(gfx::Rect(300, 10, 100, 100),
+ lock_container);
+ EXPECT_EQ(Shell::GetContainer(controller->root_window(),
+ internal::kShellWindowId_LockSystemModalContainer)->layout_manager(),
+ controller->GetSystemModalLayoutManager(
+ lock_modal_widget->GetNativeView()));
+
+ session_modal_widget =
+ CreateModalWidget(gfx::Rect(300, 10, 100, 100));
+ EXPECT_EQ(Shell::GetContainer(controller->root_window(),
+ internal::kShellWindowId_SystemModalContainer)->layout_manager(),
+ controller->GetSystemModalLayoutManager(
+ session_modal_widget->GetNativeView()));
+ session_modal_widget->Close();
+
+ lock_modal_widget->Close();
+ UnblockUserSession();
+ }
+}
+
+// Test that GetFullscreenWindow() returns a fullscreen window only if the
+// fullscreen window is in the active workspace.
+TEST_F(RootWindowControllerTest, GetFullscreenWindow) {
+ UpdateDisplay("600x600");
+ internal::RootWindowController* controller =
+ Shell::GetInstance()->GetPrimaryRootWindowController();
+
+ Widget* w1 = CreateTestWidget(gfx::Rect(0, 0, 100, 100));
+ w1->Maximize();
+ Widget* w2 = CreateTestWidget(gfx::Rect(0, 0, 100, 100));
+ w2->SetFullscreen(true);
+ // |w3| is a transient child of |w2|.
+ Widget* w3 = Widget::CreateWindowWithParentAndBounds(NULL,
+ w2->GetNativeWindow(), gfx::Rect(0, 0, 100, 100));
+
+ // Test that GetFullscreenWindow() finds the fullscreen window when one of
+ // its transient children is active.
+ w3->Activate();
+ EXPECT_EQ(w2->GetNativeWindow(), controller->GetFullscreenWindow());
+
+ // Since there's only one desktop workspace, it always returns the same
+ // fullscreen window.
+ w1->Activate();
+ EXPECT_EQ(w2->GetNativeWindow(), controller->GetFullscreenWindow());
+}
+
+// Test that user session window can't be focused if user session blocked by
+// some overlapping UI.
+TEST_F(RootWindowControllerTest, FocusBlockedWindow) {
+ UpdateDisplay("600x600");
+ internal::RootWindowController* controller =
+ Shell::GetInstance()->GetPrimaryRootWindowController();
+ aura::Window* lock_container =
+ Shell::GetContainer(controller->root_window(),
+ internal::kShellWindowId_LockScreenContainer);
+ aura::Window* lock_window = Widget::CreateWindowWithParentAndBounds(NULL,
+ lock_container, gfx::Rect(0, 0, 100, 100))->GetNativeView();
+ lock_window->Show();
+ aura::Window* session_window =
+ CreateTestWidget(gfx::Rect(0, 0, 100, 100))->GetNativeView();
+ session_window->Show();
+
+ for (int block_reason = FIRST_BLOCK_REASON;
+ block_reason < NUMBER_OF_BLOCK_REASONS;
+ ++block_reason) {
+ BlockUserSession(static_cast<UserSessionBlockReason>(block_reason));
+ lock_window->Focus();
+ EXPECT_TRUE(lock_window->HasFocus());
+ session_window->Focus();
+ EXPECT_FALSE(session_window->HasFocus());
+ UnblockUserSession();
+ }
+}
+
+typedef test::NoSessionAshTestBase NoSessionRootWindowControllerTest;
+
+// Make sure that an event handler exists for entire display area.
+TEST_F(NoSessionRootWindowControllerTest, Event) {
+ aura::RootWindow* root = Shell::GetPrimaryRootWindow();
+ const gfx::Size size = root->bounds().size();
+ aura::Window* event_target = root->GetEventHandlerForPoint(gfx::Point(0, 0));
+ EXPECT_TRUE(event_target);
+ EXPECT_EQ(event_target,
+ root->GetEventHandlerForPoint(gfx::Point(0, size.height() - 1)));
+ EXPECT_EQ(event_target,
+ root->GetEventHandlerForPoint(gfx::Point(size.width() - 1, 0)));
+ EXPECT_EQ(event_target,
+ root->GetEventHandlerForPoint(gfx::Point(0, size.height() - 1)));
+ EXPECT_EQ(event_target,
+ root->GetEventHandlerForPoint(
+ gfx::Point(size.width() - 1, size.height() - 1)));
+}
+
+class VirtualKeyboardRootWindowControllerTest : public test::AshTestBase {
+ public:
+ VirtualKeyboardRootWindowControllerTest() {};
+ virtual ~VirtualKeyboardRootWindowControllerTest() {};
+
+ virtual void SetUp() OVERRIDE {
+ CommandLine::ForCurrentProcess()->AppendSwitch(
+ keyboard::switches::kEnableVirtualKeyboard);
+ test::AshTestBase::SetUp();
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(VirtualKeyboardRootWindowControllerTest);
+};
+
+// Test for http://crbug.com/263599. Virtual keyboard should be able to receive
+// events at blocked user session.
+TEST_F(VirtualKeyboardRootWindowControllerTest,
+ ClickVirtualKeyboardInBlockedWindow) {
+ aura::RootWindow* root_window = ash::Shell::GetPrimaryRootWindow();
+ aura::Window* keyboard_container = Shell::GetContainer(root_window,
+ internal::kShellWindowId_VirtualKeyboardContainer);
+ ASSERT_TRUE(keyboard_container);
+ keyboard_container->Show();
+
+ ClickTestWindow* main_delegate = new ClickTestWindow();
+ scoped_ptr<aura::Window> keyboard_window(
+ main_delegate->CreateTestWindowWithParent(keyboard_container));
+ keyboard_container->layout_manager()->OnWindowResized();
+ keyboard_window->Show();
+ aura::test::EventGenerator event_generator(root_window,
+ keyboard_window.get());
+ event_generator.ClickLeftButton();
+ int expected_mouse_presses = 1;
+ EXPECT_EQ(expected_mouse_presses, main_delegate->mouse_presses());
+
+ for (int block_reason = FIRST_BLOCK_REASON;
+ block_reason < NUMBER_OF_BLOCK_REASONS;
+ ++block_reason) {
+ BlockUserSession(static_cast<UserSessionBlockReason>(block_reason));
+ event_generator.ClickLeftButton();
+ expected_mouse_presses++;
+ EXPECT_EQ(expected_mouse_presses, main_delegate->mouse_presses());
+ UnblockUserSession();
+ }
+}
+
+} // namespace test
+} // namespace ash
diff --git a/chromium/ash/rotator/screen_rotation.cc b/chromium/ash/rotator/screen_rotation.cc
new file mode 100644
index 00000000000..248394a2938
--- /dev/null
+++ b/chromium/ash/rotator/screen_rotation.cc
@@ -0,0 +1,132 @@
+// Copyright (c) 2012 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.
+
+#include "ash/rotator/screen_rotation.h"
+
+#include "base/time/time.h"
+#include "ui/compositor/layer.h"
+#include "ui/gfx/interpolated_transform.h"
+#include "ui/gfx/rect.h"
+#include "ui/gfx/transform.h"
+
+namespace ash {
+
+namespace {
+
+const int k90DegreeTransitionDurationMs = 350;
+const int k180DegreeTransitionDurationMs = 550;
+const int k360DegreeTransitionDurationMs = 750;
+
+base::TimeDelta GetTransitionDuration(int degrees) {
+ if (degrees == 360)
+ return base::TimeDelta::FromMilliseconds(k360DegreeTransitionDurationMs);
+ if (degrees == 180)
+ return base::TimeDelta::FromMilliseconds(k180DegreeTransitionDurationMs);
+ if (degrees == 0)
+ return base::TimeDelta::FromMilliseconds(0);
+ return base::TimeDelta::FromMilliseconds(k90DegreeTransitionDurationMs);
+}
+
+} // namespace
+
+ScreenRotation::ScreenRotation(int degrees, ui::Layer* layer)
+ : ui::LayerAnimationElement(GetProperties(),
+ GetTransitionDuration(degrees)),
+ degrees_(degrees) {
+ InitTransform(layer);
+}
+
+ScreenRotation::~ScreenRotation() {
+}
+
+void ScreenRotation::InitTransform(ui::Layer* layer) {
+ // No rotation required, use the identity transform.
+ if (degrees_ == 0) {
+ interpolated_transform_.reset(
+ new ui::InterpolatedConstantTransform(gfx::Transform()));
+ return;
+ }
+
+ // Use the target transform/bounds in case the layer is already animating.
+ const gfx::Transform& current_transform = layer->GetTargetTransform();
+ const gfx::Rect& bounds = layer->GetTargetBounds();
+
+ gfx::Point old_pivot;
+ gfx::Point new_pivot;
+
+ int width = bounds.width();
+ int height = bounds.height();
+
+ switch (degrees_) {
+ case 90:
+ new_origin_ = new_pivot = gfx::Point(width, 0);
+ break;
+ case -90:
+ new_origin_ = new_pivot = gfx::Point(0, height);
+ break;
+ case 180:
+ case 360:
+ new_pivot = old_pivot = gfx::Point(width / 2, height / 2);
+ new_origin_.SetPoint(width, height);
+ break;
+ }
+
+ // Convert points to world space.
+ current_transform.TransformPoint(old_pivot);
+ current_transform.TransformPoint(new_pivot);
+ current_transform.TransformPoint(new_origin_);
+
+ scoped_ptr<ui::InterpolatedTransform> rotation(
+ new ui::InterpolatedTransformAboutPivot(
+ old_pivot,
+ new ui::InterpolatedRotation(0, degrees_)));
+
+ scoped_ptr<ui::InterpolatedTransform> translation(
+ new ui::InterpolatedTranslation(
+ gfx::Point(0, 0),
+ gfx::Point(new_pivot.x() - old_pivot.x(),
+ new_pivot.y() - old_pivot.y())));
+
+ float scale_factor = 0.9f;
+ scoped_ptr<ui::InterpolatedTransform> scale_down(
+ new ui::InterpolatedScale(1.0f, scale_factor, 0.0f, 0.5f));
+
+ scoped_ptr<ui::InterpolatedTransform> scale_up(
+ new ui::InterpolatedScale(1.0f, 1.0f / scale_factor, 0.5f, 1.0f));
+
+ interpolated_transform_.reset(
+ new ui::InterpolatedConstantTransform(current_transform));
+
+ scale_up->SetChild(scale_down.release());
+ translation->SetChild(scale_up.release());
+ rotation->SetChild(translation.release());
+ interpolated_transform_->SetChild(rotation.release());
+}
+
+void ScreenRotation::OnStart(ui::LayerAnimationDelegate* delegate) {
+}
+
+bool ScreenRotation::OnProgress(double t,
+ ui::LayerAnimationDelegate* delegate) {
+ delegate->SetTransformFromAnimation(interpolated_transform_->Interpolate(t));
+ return true;
+}
+
+void ScreenRotation::OnGetTarget(TargetValue* target) const {
+ target->transform = interpolated_transform_->Interpolate(1.0);
+}
+
+void ScreenRotation::OnAbort(ui::LayerAnimationDelegate* delegate) {
+}
+
+// static
+const ui::LayerAnimationElement::AnimatableProperties&
+ScreenRotation::GetProperties() {
+ static ui::LayerAnimationElement::AnimatableProperties properties;
+ if (properties.empty())
+ properties.insert(ui::LayerAnimationElement::TRANSFORM);
+ return properties;
+}
+
+} // namespace ash
diff --git a/chromium/ash/rotator/screen_rotation.h b/chromium/ash/rotator/screen_rotation.h
new file mode 100644
index 00000000000..2f9616a648f
--- /dev/null
+++ b/chromium/ash/rotator/screen_rotation.h
@@ -0,0 +1,64 @@
+// Copyright (c) 2012 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 ASH_SCREEN_ROTATION_H_
+#define ASH_SCREEN_ROTATION_H_
+
+#include "ash/ash_export.h"
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "ui/compositor/layer_animation_element.h"
+#include "ui/gfx/point.h"
+
+namespace ui {
+class InterpolatedTransform;
+class Layer;
+}
+
+namespace aura {
+class RootWindow;
+}
+
+namespace ash {
+
+// A screen rotation represents a single transition from one screen orientation
+// to another. The intended usage is that a new instance of the class is
+// created for every transition. It is possible to update the target orientation
+// in the middle of a transition.
+class ASH_EXPORT ScreenRotation : public ui::LayerAnimationElement {
+ public:
+ // |degrees| are clockwise. |layer| is the target of the animation. Does not
+ // take ownership of |layer|.
+ ScreenRotation(int degrees, ui::Layer* layer);
+ virtual ~ScreenRotation();
+
+ private:
+ // Generates the intermediate transformation matrices used during the
+ // animation.
+ void InitTransform(ui::Layer* layer);
+
+ // Implementation of ui::LayerAnimationDelegate
+ virtual void OnStart(ui::LayerAnimationDelegate* delegate) OVERRIDE;
+ virtual bool OnProgress(double t,
+ ui::LayerAnimationDelegate* delegate) OVERRIDE;
+ virtual void OnGetTarget(TargetValue* target) const OVERRIDE;
+ virtual void OnAbort(ui::LayerAnimationDelegate* delegate) OVERRIDE;
+
+ static const ui::LayerAnimationElement::AnimatableProperties&
+ GetProperties();
+
+ scoped_ptr<ui::InterpolatedTransform> interpolated_transform_;
+
+ // The number of degrees to rotate.
+ int degrees_;
+
+ // The target origin.
+ gfx::Point new_origin_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScreenRotation);
+};
+
+} // namespace ash
+
+#endif // ASH_SCREEN_ROTATION_H_
diff --git a/chromium/ash/scoped_target_root_window.cc b/chromium/ash/scoped_target_root_window.cc
new file mode 100644
index 00000000000..b488a6f9342
--- /dev/null
+++ b/chromium/ash/scoped_target_root_window.cc
@@ -0,0 +1,21 @@
+// Copyright 2013 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.
+#include "ash/scoped_target_root_window.h"
+
+#include "ash/shell.h"
+
+namespace ash {
+namespace internal {
+
+ScopedTargetRootWindow::ScopedTargetRootWindow(
+ aura::RootWindow* root_window) {
+ Shell::GetInstance()->scoped_target_root_window_ = root_window;
+}
+
+ScopedTargetRootWindow::~ScopedTargetRootWindow() {
+ Shell::GetInstance()->scoped_target_root_window_ = NULL;
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/scoped_target_root_window.h b/chromium/ash/scoped_target_root_window.h
new file mode 100644
index 00000000000..ab695be6b6e
--- /dev/null
+++ b/chromium/ash/scoped_target_root_window.h
@@ -0,0 +1,33 @@
+// Copyright 2013 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 ASH_SCOPED_TARGET_ROOT_WINDOW_H_
+#define ASH_SCOPED_TARGET_ROOT_WINDOW_H_
+
+#include "base/basictypes.h"
+
+namespace aura {
+class RootWindow;
+}
+
+namespace ash {
+namespace internal {
+
+// Constructing a ScopedTargetRootWindow allows temporarily
+// switching a target root window so that a new window gets created
+// in the same window where a user interaction happened.
+// An example usage is to specify the target root window when creating
+// a new window using launcher's icon.
+class ScopedTargetRootWindow {
+ public:
+ explicit ScopedTargetRootWindow(aura::RootWindow* root_window);
+ ~ScopedTargetRootWindow();
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ScopedTargetRootWindow);
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_SCOPED_TARGET_ROOT_WINDOW_H_
diff --git a/chromium/ash/screen_ash.cc b/chromium/ash/screen_ash.cc
new file mode 100644
index 00000000000..f2217d7b0fb
--- /dev/null
+++ b/chromium/ash/screen_ash.cc
@@ -0,0 +1,154 @@
+// Copyright (c) 2012 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.
+
+#include "ash/screen_ash.h"
+
+#include "ash/display/display_controller.h"
+#include "ash/display/display_manager.h"
+#include "ash/root_window_controller.h"
+#include "ash/shelf/shelf_layout_manager.h"
+#include "ash/shelf/shelf_widget.h"
+#include "ash/shell.h"
+#include "ash/wm/property_util.h"
+#include "ash/wm/coordinate_conversion.h"
+#include "base/logging.h"
+#include "ui/aura/client/screen_position_client.h"
+#include "ui/aura/env.h"
+#include "ui/aura/root_window.h"
+#include "ui/gfx/display.h"
+#include "ui/gfx/screen.h"
+
+namespace ash {
+
+namespace {
+internal::DisplayManager* GetDisplayManager() {
+ return Shell::GetInstance()->display_manager();
+}
+
+DisplayController* GetDisplayController() {
+ return Shell::GetInstance()->display_controller();
+}
+} // namespace
+
+ScreenAsh::ScreenAsh() {
+}
+
+ScreenAsh::~ScreenAsh() {
+}
+
+// static
+gfx::Display ScreenAsh::FindDisplayContainingPoint(const gfx::Point& point) {
+ return GetDisplayManager()->FindDisplayContainingPoint(point);
+}
+
+// static
+gfx::Rect ScreenAsh::GetMaximizedWindowBoundsInParent(aura::Window* window) {
+ if (GetRootWindowController(window->GetRootWindow())->shelf())
+ return GetDisplayWorkAreaBoundsInParent(window);
+ else
+ return GetDisplayBoundsInParent(window);
+}
+
+// static
+gfx::Rect ScreenAsh::GetDisplayBoundsInParent(aura::Window* window) {
+ return ConvertRectFromScreen(
+ window->parent(),
+ Shell::GetScreen()->GetDisplayNearestWindow(window).bounds());
+}
+
+// static
+gfx::Rect ScreenAsh::GetDisplayWorkAreaBoundsInParent(aura::Window* window) {
+ return ConvertRectFromScreen(
+ window->parent(),
+ Shell::GetScreen()->GetDisplayNearestWindow(window).work_area());
+}
+
+// static
+gfx::Rect ScreenAsh::ConvertRectToScreen(aura::Window* window,
+ const gfx::Rect& rect) {
+ gfx::Point point = rect.origin();
+ aura::client::GetScreenPositionClient(window->GetRootWindow())->
+ ConvertPointToScreen(window, &point);
+ return gfx::Rect(point, rect.size());
+}
+
+// static
+gfx::Rect ScreenAsh::ConvertRectFromScreen(aura::Window* window,
+ const gfx::Rect& rect) {
+ gfx::Point point = rect.origin();
+ aura::client::GetScreenPositionClient(window->GetRootWindow())->
+ ConvertPointFromScreen(window, &point);
+ return gfx::Rect(point, rect.size());
+}
+
+// static
+const gfx::Display& ScreenAsh::GetSecondaryDisplay() {
+ internal::DisplayManager* display_manager = GetDisplayManager();
+ CHECK_EQ(2U, display_manager->GetNumDisplays());
+ return display_manager->GetDisplayAt(0).id() ==
+ DisplayController::GetPrimaryDisplay().id() ?
+ display_manager->GetDisplayAt(1) : display_manager->GetDisplayAt(0);
+}
+
+// static
+const gfx::Display& ScreenAsh::GetDisplayForId(int64 display_id) {
+ return GetDisplayManager()->GetDisplayForId(display_id);
+}
+
+void ScreenAsh::NotifyBoundsChanged(const gfx::Display& display) {
+ FOR_EACH_OBSERVER(gfx::DisplayObserver, observers_,
+ OnDisplayBoundsChanged(display));
+}
+
+void ScreenAsh::NotifyDisplayAdded(const gfx::Display& display) {
+ FOR_EACH_OBSERVER(gfx::DisplayObserver, observers_, OnDisplayAdded(display));
+}
+
+void ScreenAsh::NotifyDisplayRemoved(const gfx::Display& display) {
+ FOR_EACH_OBSERVER(
+ gfx::DisplayObserver, observers_, OnDisplayRemoved(display));
+}
+
+bool ScreenAsh::IsDIPEnabled() {
+ return true;
+}
+
+gfx::Point ScreenAsh::GetCursorScreenPoint() {
+ return aura::Env::GetInstance()->last_mouse_location();
+}
+
+gfx::NativeWindow ScreenAsh::GetWindowAtCursorScreenPoint() {
+ const gfx::Point point = Shell::GetScreen()->GetCursorScreenPoint();
+ return wm::GetRootWindowAt(point)->GetTopWindowContainingPoint(point);
+}
+
+int ScreenAsh::GetNumDisplays() {
+ return DisplayController::GetNumDisplays();
+}
+
+gfx::Display ScreenAsh::GetDisplayNearestWindow(gfx::NativeView window) const {
+ return GetDisplayController()->GetDisplayNearestWindow(window);
+}
+
+gfx::Display ScreenAsh::GetDisplayNearestPoint(const gfx::Point& point) const {
+ return GetDisplayController()->GetDisplayNearestPoint(point);
+}
+
+gfx::Display ScreenAsh::GetDisplayMatching(const gfx::Rect& match_rect) const {
+ return GetDisplayController()->GetDisplayMatching(match_rect);
+}
+
+gfx::Display ScreenAsh::GetPrimaryDisplay() const {
+ return DisplayController::GetPrimaryDisplay();
+}
+
+void ScreenAsh::AddObserver(gfx::DisplayObserver* observer) {
+ observers_.AddObserver(observer);
+}
+
+void ScreenAsh::RemoveObserver(gfx::DisplayObserver* observer) {
+ observers_.RemoveObserver(observer);
+}
+
+} // namespace ash
diff --git a/chromium/ash/screen_ash.h b/chromium/ash/screen_ash.h
new file mode 100644
index 00000000000..649c3d9f570
--- /dev/null
+++ b/chromium/ash/screen_ash.h
@@ -0,0 +1,88 @@
+// Copyright (c) 2012 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 ASH_SCREEN_ASH_H_
+#define ASH_SCREEN_ASH_H_
+
+#include "ash/ash_export.h"
+#include "base/compiler_specific.h"
+#include "base/observer_list.h"
+#include "ui/gfx/screen.h"
+
+namespace gfx {
+class Rect;
+}
+
+namespace ash {
+
+// Aura implementation of gfx::Screen. Implemented here to avoid circular
+// dependencies.
+class ASH_EXPORT ScreenAsh : public gfx::Screen {
+ public:
+ ScreenAsh();
+ virtual ~ScreenAsh();
+
+ // Finds the display that contains |point| in screeen coordinates.
+ // Returns invalid display if there is no display that can satisfy
+ // the condition.
+ static gfx::Display FindDisplayContainingPoint(const gfx::Point& point);
+
+ // Returns the bounds for maximized windows in parent coordinates.
+ // Maximized windows trigger auto-hiding the shelf.
+ static gfx::Rect GetMaximizedWindowBoundsInParent(aura::Window* window);
+
+ // Returns the display bounds in parent coordinates.
+ static gfx::Rect GetDisplayBoundsInParent(aura::Window* window);
+
+ // Returns the display's work area bounds in parent coordinates.
+ static gfx::Rect GetDisplayWorkAreaBoundsInParent(aura::Window* window);
+
+ // Converts |rect| from |window|'s coordinates to the virtual screen
+ // coordinates.
+ static gfx::Rect ConvertRectToScreen(aura::Window* window,
+ const gfx::Rect& rect);
+
+ // Converts |rect| from virtual screen coordinates to the |window|'s
+ // coordinates.
+ static gfx::Rect ConvertRectFromScreen(aura::Window* window,
+ const gfx::Rect& rect);
+
+ // Returns a gfx::Display object for secondary display. Returns
+ // invalid display if there is no secondary display connected.
+ static const gfx::Display& GetSecondaryDisplay();
+
+ // Returns a gfx::Display object for the specified id. Returns
+ // invalid display if no such display is connected.
+ static const gfx::Display& GetDisplayForId(int64 display_id);
+
+ // Notifies observers of display configuration changes.
+ void NotifyBoundsChanged(const gfx::Display& display);
+ void NotifyDisplayAdded(const gfx::Display& display);
+ void NotifyDisplayRemoved(const gfx::Display& display);
+
+ protected:
+ // gfx::Screen overrides:
+ virtual bool IsDIPEnabled() OVERRIDE;
+ virtual gfx::Point GetCursorScreenPoint() OVERRIDE;
+ virtual gfx::NativeWindow GetWindowAtCursorScreenPoint() OVERRIDE;
+ virtual int GetNumDisplays() OVERRIDE;
+ virtual gfx::Display GetDisplayNearestWindow(
+ gfx::NativeView view) const OVERRIDE;
+ virtual gfx::Display GetDisplayNearestPoint(
+ const gfx::Point& point) const OVERRIDE;
+ virtual gfx::Display GetDisplayMatching(
+ const gfx::Rect& match_rect) const OVERRIDE;
+ virtual gfx::Display GetPrimaryDisplay() const OVERRIDE;
+ virtual void AddObserver(gfx::DisplayObserver* observer) OVERRIDE;
+ virtual void RemoveObserver(gfx::DisplayObserver* observer) OVERRIDE;
+
+ private:
+ ObserverList<gfx::DisplayObserver> observers_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScreenAsh);
+};
+
+} // namespace ash
+
+#endif // ASH_SCREEN_ASH_H_
diff --git a/chromium/ash/screen_ash_unittest.cc b/chromium/ash/screen_ash_unittest.cc
new file mode 100644
index 00000000000..22437423e0a
--- /dev/null
+++ b/chromium/ash/screen_ash_unittest.cc
@@ -0,0 +1,116 @@
+// Copyright (c) 2012 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.
+
+#include "ash/screen_ash.h"
+
+#include "ash/root_window_controller.h"
+#include "ash/shelf/shelf_layout_manager.h"
+#include "ash/shelf/shelf_widget.h"
+#include "ash/shell.h"
+#include "ash/test/ash_test_base.h"
+#include "ash/wm/window_util.h"
+#include "ui/aura/env.h"
+#include "ui/aura/root_window.h"
+#include "ui/aura/window.h"
+#include "ui/views/widget/widget.h"
+#include "ui/views/widget/widget_delegate.h"
+
+namespace ash {
+namespace test {
+
+typedef test::AshTestBase ScreenAshTest;
+
+TEST_F(ScreenAshTest, Bounds) {
+ if (!SupportsMultipleDisplays())
+ return;
+
+ UpdateDisplay("600x600,500x500");
+ Shell::GetPrimaryRootWindowController()->GetShelfLayoutManager()->
+ SetAutoHideBehavior(ash::SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS);
+
+ views::Widget* primary = views::Widget::CreateWindowWithContextAndBounds(
+ NULL, CurrentContext(), gfx::Rect(10, 10, 100, 100));
+ primary->Show();
+ views::Widget* secondary = views::Widget::CreateWindowWithContextAndBounds(
+ NULL, CurrentContext(), gfx::Rect(610, 10, 100, 100));
+ secondary->Show();
+
+ // Maximized bounds
+ EXPECT_EQ("0,0 600x597",
+ ScreenAsh::GetMaximizedWindowBoundsInParent(
+ primary->GetNativeView()).ToString());
+ EXPECT_EQ("0,0 500x452",
+ ScreenAsh::GetMaximizedWindowBoundsInParent(
+ secondary->GetNativeView()).ToString());
+
+ // Display bounds
+ EXPECT_EQ("0,0 600x600",
+ ScreenAsh::GetDisplayBoundsInParent(
+ primary->GetNativeView()).ToString());
+ EXPECT_EQ("0,0 500x500",
+ ScreenAsh::GetDisplayBoundsInParent(
+ secondary->GetNativeView()).ToString());
+
+ // Work area bounds
+ EXPECT_EQ("0,0 600x597",
+ ScreenAsh::GetDisplayWorkAreaBoundsInParent(
+ primary->GetNativeView()).ToString());
+ EXPECT_EQ("0,0 500x452",
+ ScreenAsh::GetDisplayWorkAreaBoundsInParent(
+ secondary->GetNativeView()).ToString());
+}
+
+// Test verifies a stable handling of secondary screen widget changes
+// (crbug.com/226132).
+TEST_F(ScreenAshTest, StabilityTest) {
+ if (!SupportsMultipleDisplays())
+ return;
+
+ UpdateDisplay("600x600,500x500");
+ views::Widget* secondary = views::Widget::CreateWindowWithContextAndBounds(
+ NULL, CurrentContext(), gfx::Rect(610, 10, 100, 100));
+ EXPECT_EQ(Shell::GetAllRootWindows()[1],
+ secondary->GetNativeView()->GetRootWindow());
+ secondary->Show();
+ secondary->Maximize();
+ secondary->Show();
+ secondary->SetFullscreen(true);
+ secondary->Hide();
+ secondary->Close();
+}
+
+TEST_F(ScreenAshTest, ConvertRect) {
+ if (!SupportsMultipleDisplays())
+ return;
+
+ UpdateDisplay("600x600,500x500");
+
+ views::Widget* primary = views::Widget::CreateWindowWithContextAndBounds(
+ NULL, CurrentContext(), gfx::Rect(10, 10, 100, 100));
+ primary->Show();
+ views::Widget* secondary = views::Widget::CreateWindowWithContextAndBounds(
+ NULL, CurrentContext(), gfx::Rect(610, 10, 100, 100));
+ secondary->Show();
+
+ EXPECT_EQ(
+ "0,0 100x100",
+ ScreenAsh::ConvertRectFromScreen(
+ primary->GetNativeView(), gfx::Rect(10, 10, 100, 100)).ToString());
+ EXPECT_EQ(
+ "10,10 100x100",
+ ScreenAsh::ConvertRectFromScreen(
+ secondary->GetNativeView(), gfx::Rect(620, 20, 100, 100)).ToString());
+
+ EXPECT_EQ(
+ "40,40 100x100",
+ ScreenAsh::ConvertRectToScreen(
+ primary->GetNativeView(), gfx::Rect(30, 30, 100, 100)).ToString());
+ EXPECT_EQ(
+ "650,50 100x100",
+ ScreenAsh::ConvertRectToScreen(
+ secondary->GetNativeView(), gfx::Rect(40, 40, 100, 100)).ToString());
+}
+
+} // namespace test
+} // namespace ash
diff --git a/chromium/ash/screensaver/DEPS b/chromium/ash/screensaver/DEPS
new file mode 100644
index 00000000000..1c35d9ca694
--- /dev/null
+++ b/chromium/ash/screensaver/DEPS
@@ -0,0 +1,3 @@
+include_rules = [
+ "+content/public/browser",
+]
diff --git a/chromium/ash/screensaver/OWNERS b/chromium/ash/screensaver/OWNERS
new file mode 100644
index 00000000000..6a2cb03fd3e
--- /dev/null
+++ b/chromium/ash/screensaver/OWNERS
@@ -0,0 +1 @@
+rkc@chromium.org
diff --git a/chromium/ash/screensaver/screensaver_view.cc b/chromium/ash/screensaver/screensaver_view.cc
new file mode 100644
index 00000000000..6c2c1e03d7d
--- /dev/null
+++ b/chromium/ash/screensaver/screensaver_view.cc
@@ -0,0 +1,170 @@
+// Copyright (c) 2012 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.
+
+#include "ash/screensaver/screensaver_view.h"
+
+#include "ash/shell.h"
+#include "ash/shell_delegate.h"
+#include "base/bind.h"
+#include "base/logging.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/web_contents.h"
+#include "ui/aura/root_window.h"
+#include "ui/gfx/screen.h"
+#include "ui/views/controls/webview/webview.h"
+#include "ui/views/layout/fill_layout.h"
+#include "ui/views/widget/widget.h"
+
+using content::BrowserThread;
+
+namespace {
+
+ash::internal::ScreensaverView* g_instance = NULL;
+
+// Do not restart the screensaver again if it has
+// terminated kMaxTerminations times already.
+const int kMaxTerminations = 3;
+
+} // namespace
+
+namespace ash {
+
+void ShowScreensaver(const GURL& url) {
+ internal::ScreensaverView::ShowScreensaver(url);
+}
+
+void CloseScreensaver() {
+ internal::ScreensaverView::CloseScreensaver();
+}
+
+bool IsScreensaverShown() {
+ return internal::ScreensaverView::IsScreensaverShown();
+}
+
+namespace internal {
+
+// static
+void ScreensaverView::ShowScreensaver(const GURL& url) {
+ if (!g_instance) {
+ g_instance = new ScreensaverView(url);
+ g_instance->Show();
+ }
+}
+
+// static
+void ScreensaverView::CloseScreensaver() {
+ if (g_instance) {
+ g_instance->Close();
+ g_instance = NULL;
+ }
+}
+
+// static
+bool ScreensaverView::IsScreensaverShown() {
+ return g_instance && g_instance->IsScreensaverShowingURL(g_instance->url_);
+}
+
+bool ScreensaverView::IsScreensaverShowingURL(const GURL& url) {
+ return screensaver_webview_ &&
+ screensaver_webview_->web_contents() &&
+ (screensaver_webview_->web_contents()->GetURL() == url);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// ScreensaverView, views::WidgetDelegateView implementation.
+views::View* ScreensaverView::GetContentsView() {
+ return this;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// ScreensaverView, content::WebContentsObserver implementation.
+void ScreensaverView::RenderProcessGone(
+ base::TerminationStatus status) {
+ LOG(ERROR) << "Screensaver terminated with status " << status;
+ termination_count_++;
+
+ if (termination_count_ < kMaxTerminations) {
+ LOG(ERROR) << termination_count_
+ << " terminations is under the threshold of "
+ << kMaxTerminations
+ << "; reloading Screensaver.";
+ LoadScreensaver();
+ } else {
+ LOG(ERROR) << "Exceeded termination threshold, closing Screensaver.";
+ ScreensaverView::CloseScreensaver();
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// ScreensaverView private methods.
+ScreensaverView::ScreensaverView(const GURL& url)
+ : url_(url),
+ termination_count_(0),
+ screensaver_webview_(NULL),
+ container_window_(NULL) {
+}
+
+ScreensaverView::~ScreensaverView() {
+}
+
+void ScreensaverView::Show() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ // Add the WebView to our view.
+ AddChildWebContents();
+ // Show the window.
+ ShowWindow();
+}
+
+void ScreensaverView::Close() {
+ DCHECK(GetWidget());
+ GetWidget()->Close();
+}
+
+void ScreensaverView::AddChildWebContents() {
+ content::BrowserContext* context =
+ Shell::GetInstance()->delegate()->GetCurrentBrowserContext();
+ screensaver_webview_ = new views::WebView(context);
+ SetLayoutManager(new views::FillLayout);
+ AddChildView(screensaver_webview_);
+
+ LoadScreensaver();
+ content::WebContentsObserver::Observe(
+ screensaver_webview_->GetWebContents());
+}
+
+void ScreensaverView::LoadScreensaver() {
+ screensaver_webview_->GetWebContents()->GetController().LoadURL(
+ url_,
+ content::Referrer(),
+ content::PAGE_TRANSITION_AUTO_TOPLEVEL,
+ std::string());
+}
+
+void ScreensaverView::ShowWindow() {
+ aura::RootWindow* root_window = ash::Shell::GetPrimaryRootWindow();
+ gfx::Rect screen_rect =
+ Shell::GetScreen()->GetDisplayNearestWindow(root_window).bounds();
+
+ // We want to be the fullscreen topmost child of the root window.
+ // There should be nothing ever really that should show up on top of us.
+ container_window_ = new views::Widget();
+ views::Widget::InitParams params(
+ views::Widget::InitParams::TYPE_WINDOW_FRAMELESS);
+ params.delegate = this;
+ params.parent = root_window;
+ container_window_->Init(params);
+
+ container_window_->StackAtTop();
+ container_window_->SetBounds(screen_rect);
+ container_window_->Show();
+}
+
+// static
+ScreensaverView* ScreensaverView::GetInstance() {
+ return g_instance;
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/screensaver/screensaver_view.h b/chromium/ash/screensaver/screensaver_view.h
new file mode 100644
index 00000000000..2e27c4e4b28
--- /dev/null
+++ b/chromium/ash/screensaver/screensaver_view.h
@@ -0,0 +1,94 @@
+// Copyright (c) 2012 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 ASH_SCREENSAVER_SCREENSAVER_VIEW_H_
+#define ASH_SCREENSAVER_SCREENSAVER_VIEW_H_
+
+#include "ash/ash_export.h"
+#include "base/callback.h"
+#include "content/public/browser/web_contents_observer.h"
+#include "ui/views/widget/widget_delegate.h"
+#include "url/gurl.h"
+
+namespace content {
+class BrowserContent;
+}
+
+namespace views {
+class WebView;
+}
+
+namespace ash {
+
+namespace test {
+class ScreensaverViewTest;
+}
+
+ASH_EXPORT void ShowScreensaver(const GURL& url);
+ASH_EXPORT void CloseScreensaver();
+ASH_EXPORT bool IsScreensaverShown();
+
+typedef
+ base::Callback<views::WebView*(content::BrowserContext*)> WebViewFactory;
+
+namespace internal {
+
+// Shows a URL as a screensaver. The screensaver window is fullscreen,
+// always on top of every other window and will reload the URL if the
+// renderer crashes for any reason.
+class ScreensaverView : public views::WidgetDelegateView,
+ public content::WebContentsObserver {
+ public:
+ static void ShowScreensaver(const GURL& url);
+ static void CloseScreensaver();
+
+ static bool IsScreensaverShown();
+
+ private:
+ friend class test::ScreensaverViewTest;
+
+ explicit ScreensaverView(const GURL& url);
+ virtual ~ScreensaverView();
+
+ // views::WidgetDelegate overrides.
+ virtual views::View* GetContentsView() OVERRIDE;
+
+ // content::WebContentsObserver overrides.
+ virtual void RenderProcessGone(base::TerminationStatus status) OVERRIDE;
+
+ void Show();
+ void Close();
+
+ // Creates and adds web contents to our view.
+ void AddChildWebContents();
+ // Load the screensaver in the WebView's webcontent. If the webcontents
+ // don't exist, they'll be created by WebView.
+ void LoadScreensaver();
+ // Creates and shows a frameless full screen window containing our view.
+ void ShowWindow();
+
+ // For testing purposes.
+ static ASH_EXPORT ScreensaverView* GetInstance();
+ ASH_EXPORT bool IsScreensaverShowingURL(const GURL& url);
+
+ // URL to show in the screensaver.
+ GURL url_;
+
+ // Number of times the screensaver has been terminated (usually this will be
+ // synonymous with the number of times it has crashed).
+ int termination_count_;
+
+ // Host for the extension that implements this dialog.
+ views::WebView* screensaver_webview_;
+
+ // Window that holds the screensaver webview.
+ views::Widget* container_window_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScreensaverView);
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_SCREENSAVER_SCREENSAVER_VIEW_H_
diff --git a/chromium/ash/screensaver/screensaver_view_unittest.cc b/chromium/ash/screensaver/screensaver_view_unittest.cc
new file mode 100644
index 00000000000..0901ba4e6ca
--- /dev/null
+++ b/chromium/ash/screensaver/screensaver_view_unittest.cc
@@ -0,0 +1,81 @@
+// Copyright (c) 2012 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.
+
+#include "ash/screensaver/screensaver_view.h"
+
+#include "ash/test/ash_test_base.h"
+#include "base/bind.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/web_contents.h"
+#include "ui/views/controls/webview/webview.h"
+#include "ui/views/test/webview_test_helper.h"
+
+namespace ash {
+namespace test {
+
+class ScreensaverViewTest : public ash::test::AshTestBase {
+ public:
+ ScreensaverViewTest() {
+ url_ = GURL("http://www.google.com");
+ views_delegate_.reset(new AshTestViewsDelegate());
+ webview_test_helper_.reset(new views::WebViewTestHelper());
+ }
+
+ virtual ~ScreensaverViewTest() {}
+
+ virtual void SetUp() OVERRIDE {
+ AshTestBase::SetUp();
+ RunAllPendingInMessageLoop();
+ }
+
+ void ExpectOpenScreensaver() {
+ internal::ScreensaverView* screensaver =
+ internal::ScreensaverView::GetInstance();
+ EXPECT_TRUE(screensaver != NULL);
+ if (!screensaver) return;
+ EXPECT_TRUE(screensaver->IsScreensaverShowingURL(url_));
+ }
+
+ void ExpectClosedScreensaver() {
+ EXPECT_TRUE(internal::ScreensaverView::GetInstance() == NULL);
+ }
+
+ protected:
+ GURL url_;
+
+ private:
+ scoped_ptr<AshTestViewsDelegate> views_delegate_;
+ scoped_ptr<views::WebViewTestHelper> webview_test_helper_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScreensaverViewTest);
+};
+
+TEST_F(ScreensaverViewTest, ShowScreensaverAndClose) {
+ ash::ShowScreensaver(url_);
+ RunAllPendingInMessageLoop();
+ ExpectOpenScreensaver();
+
+ ash::CloseScreensaver();
+ ExpectClosedScreensaver();
+}
+
+TEST_F(ScreensaverViewTest, OutOfOrderMultipleShowAndClose) {
+ ash::CloseScreensaver();
+ ExpectClosedScreensaver();
+
+ ash::ShowScreensaver(url_);
+ ExpectOpenScreensaver();
+ RunAllPendingInMessageLoop();
+ ash::ShowScreensaver(url_);
+ ExpectOpenScreensaver();
+ RunAllPendingInMessageLoop();
+
+ ash::CloseScreensaver();
+ ExpectClosedScreensaver();
+ ash::CloseScreensaver();
+ ExpectClosedScreensaver();
+}
+
+} // namespace test
+} // namespace ash
diff --git a/chromium/ash/screenshot_delegate.h b/chromium/ash/screenshot_delegate.h
new file mode 100644
index 00000000000..9e7af06134f
--- /dev/null
+++ b/chromium/ash/screenshot_delegate.h
@@ -0,0 +1,37 @@
+// Copyright (c) 2012 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 ASH_SCREENSHOT_DELEGATE_H_
+#define ASH_SCREENSHOT_DELEGATE_H_
+
+namespace aura {
+class Window;
+} // namespace aura
+
+namespace gfx {
+class Rect;
+} // namespace gfx
+
+namespace ash {
+
+// Delegate for taking screenshots.
+class ScreenshotDelegate {
+ public:
+ virtual ~ScreenshotDelegate() {}
+
+ // The actual task of taking a screenshot for each root window.
+ // This method is called when the user wants to take a screenshot manually.
+ virtual void HandleTakeScreenshotForAllRootWindows() = 0;
+
+ // The actual task of taking a partial screenshot for the given
+ // window.
+ virtual void HandleTakePartialScreenshot(
+ aura::Window* window, const gfx::Rect& rect) = 0;
+
+ // Returns true if the system is ready to take screenshot.
+ virtual bool CanTakeScreenshot() = 0;
+};
+} // namespace ash
+
+#endif // ASH_SCREENSHOT_DELEGATE_H_
diff --git a/chromium/ash/session_state_delegate.h b/chromium/ash/session_state_delegate.h
new file mode 100644
index 00000000000..0f2c569c512
--- /dev/null
+++ b/chromium/ash/session_state_delegate.h
@@ -0,0 +1,90 @@
+// Copyright (c) 2013 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 ASH_SESSION_STATE_DELEGATE_H_
+#define ASH_SESSION_STATE_DELEGATE_H_
+
+#include <string>
+#include <vector>
+
+#include "ash/ash_export.h"
+#include "base/strings/string16.h"
+
+namespace gfx {
+class ImageSkia;
+} // namespace gfx
+
+namespace ash {
+
+class SessionStateObserver;
+
+// The index for the multi-profile item to use. The list is always LRU sorted
+// So that the index #0 is the currently active user.
+typedef int MultiProfileIndex;
+
+// A list of user_id.
+typedef std::vector<std::string> UserIdList;
+
+// Delegate for checking and modifying the session state.
+class ASH_EXPORT SessionStateDelegate {
+ public:
+ virtual ~SessionStateDelegate() {};
+
+ // Returns the maximum possible number of logged in users.
+ virtual int GetMaximumNumberOfLoggedInUsers() const = 0;
+
+ // Returns the number of signed in users. If 0 is returned, there is either
+ // no session in progress or no active user.
+ virtual int NumberOfLoggedInUsers() const = 0;
+
+ // Returns |true| if the session has been fully started for the active user.
+ // When a user becomes active, the profile and browser UI are not immediately
+ // available. Only once this method starts returning |true| is the browser
+ // startup complete and both profile and UI are fully available.
+ virtual bool IsActiveUserSessionStarted() const = 0;
+
+ // Returns true if the screen can be locked.
+ virtual bool CanLockScreen() const = 0;
+
+ // Returns true if the screen is currently locked.
+ virtual bool IsScreenLocked() const = 0;
+
+ // Locks the screen. The locking happens asynchronously.
+ virtual void LockScreen() = 0;
+
+ // Unlocks the screen.
+ virtual void UnlockScreen() = 0;
+
+ // Returns |true| if user session blocked by some overlying UI. It can be
+ // login screen, lock screen or screen for adding users into multi-profile
+ // session.
+ virtual bool IsUserSessionBlocked() const = 0;
+
+ // Gets the displayed name for the user with the given |index|.
+ // Note that |index| can at maximum be |NumberOfLoggedInUsers() - 1|.
+ virtual const base::string16 GetUserDisplayName(
+ MultiProfileIndex index) const = 0;
+
+ // Gets the email address for the user with the given |index|.
+ // Note that |index| can at maximum be |NumberOfLoggedInUsers() - 1|.
+ virtual const std::string GetUserEmail(MultiProfileIndex index) const = 0;
+
+ // Gets the avatar image for the user with the given |index|.
+ // Note that |index| can at maximum be |NumberOfLoggedInUsers() - 1|.
+ virtual const gfx::ImageSkia& GetUserImage(MultiProfileIndex index) const = 0;
+
+ // Returns a list of all logged in users.
+ virtual void GetLoggedInUsers(UserIdList* users) = 0;
+
+ // Switches to another active user (if that user has already signed in).
+ virtual void SwitchActiveUser(const std::string& user_id) = 0;
+
+ // Adds or removes sessions state observer.
+ virtual void AddSessionStateObserver(SessionStateObserver* observer) = 0;
+ virtual void RemoveSessionStateObserver(SessionStateObserver* observer) = 0;
+};
+
+} // namespace ash
+
+#endif // ASH_SESSION_STATE_DELEGATE_H_
diff --git a/chromium/ash/session_state_delegate_stub.cc b/chromium/ash/session_state_delegate_stub.cc
new file mode 100644
index 00000000000..31f734518ae
--- /dev/null
+++ b/chromium/ash/session_state_delegate_stub.cc
@@ -0,0 +1,84 @@
+// Copyright (c) 2013 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.
+
+#include "ash/session_state_delegate_stub.h"
+
+#include "ash/shell.h"
+#include "ash/shell/example_factory.h"
+#include "base/strings/string16.h"
+#include "base/strings/utf_string_conversions.h"
+
+namespace ash {
+
+SessionStateDelegateStub::SessionStateDelegateStub() : screen_locked_(false) {
+}
+
+SessionStateDelegateStub::~SessionStateDelegateStub() {
+}
+
+int SessionStateDelegateStub::GetMaximumNumberOfLoggedInUsers() const {
+ return 3;
+}
+
+int SessionStateDelegateStub::NumberOfLoggedInUsers() const {
+ return 1;
+}
+
+bool SessionStateDelegateStub::IsActiveUserSessionStarted() const {
+ return true;
+}
+
+bool SessionStateDelegateStub::CanLockScreen() const {
+ return true;
+}
+
+bool SessionStateDelegateStub::IsScreenLocked() const {
+ return screen_locked_;
+}
+
+void SessionStateDelegateStub::LockScreen() {
+ shell::CreateLockScreen();
+ screen_locked_ = true;
+ Shell::GetInstance()->UpdateShelfVisibility();
+}
+
+void SessionStateDelegateStub::UnlockScreen() {
+ screen_locked_ = false;
+ Shell::GetInstance()->UpdateShelfVisibility();
+}
+
+bool SessionStateDelegateStub::IsUserSessionBlocked() const {
+ return !IsActiveUserSessionStarted() || IsScreenLocked();
+}
+
+const base::string16 SessionStateDelegateStub::GetUserDisplayName(
+ MultiProfileIndex index) const {
+ return UTF8ToUTF16("stub-user");
+}
+
+const std::string SessionStateDelegateStub::GetUserEmail(
+ MultiProfileIndex index) const {
+ return "stub-user@domain.com";
+}
+
+const gfx::ImageSkia& SessionStateDelegateStub::GetUserImage(
+ MultiProfileIndex index) const {
+ return null_image_;
+}
+
+void SessionStateDelegateStub::GetLoggedInUsers(UserIdList* users) {
+}
+
+void SessionStateDelegateStub::SwitchActiveUser(const std::string& user_id) {
+}
+
+void SessionStateDelegateStub::AddSessionStateObserver(
+ ash::SessionStateObserver* observer) {
+}
+
+void SessionStateDelegateStub::RemoveSessionStateObserver(
+ ash::SessionStateObserver* observer) {
+}
+
+} // namespace ash
diff --git a/chromium/ash/session_state_delegate_stub.h b/chromium/ash/session_state_delegate_stub.h
new file mode 100644
index 00000000000..1843383bb2b
--- /dev/null
+++ b/chromium/ash/session_state_delegate_stub.h
@@ -0,0 +1,54 @@
+// Copyright (c) 2013 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 ASH_SESSION_STATE_DELEGATE_STUB_H_
+#define ASH_SESSION_STATE_DELEGATE_STUB_H_
+
+#include "ash/session_state_delegate.h"
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "ui/gfx/image/image_skia.h"
+
+namespace ash {
+
+// Stub implementation of SessionStateDelegate for testing.
+class SessionStateDelegateStub : public SessionStateDelegate {
+ public:
+ SessionStateDelegateStub();
+ virtual ~SessionStateDelegateStub();
+
+ // SessionStateDelegate:
+ virtual int GetMaximumNumberOfLoggedInUsers() const OVERRIDE;
+ virtual int NumberOfLoggedInUsers() const OVERRIDE;
+ virtual bool IsActiveUserSessionStarted() const OVERRIDE;
+ virtual bool CanLockScreen() const OVERRIDE;
+ virtual bool IsScreenLocked() const OVERRIDE;
+ virtual void LockScreen() OVERRIDE;
+ virtual void UnlockScreen() OVERRIDE;
+ virtual bool IsUserSessionBlocked() const OVERRIDE;
+ virtual const base::string16 GetUserDisplayName(
+ ash::MultiProfileIndex index) const OVERRIDE;
+ virtual const std::string GetUserEmail(
+ ash::MultiProfileIndex index) const OVERRIDE;
+ virtual const gfx::ImageSkia& GetUserImage(
+ ash::MultiProfileIndex index) const OVERRIDE;
+ virtual void GetLoggedInUsers(UserIdList* users) OVERRIDE;
+ virtual void SwitchActiveUser(const std::string& user_id) OVERRIDE;
+ virtual void AddSessionStateObserver(
+ ash::SessionStateObserver* observer) OVERRIDE;
+ virtual void RemoveSessionStateObserver(
+ ash::SessionStateObserver* observer) OVERRIDE;
+
+ private:
+ bool screen_locked_;
+
+ // A pseudo user image.
+ gfx::ImageSkia null_image_;
+
+ DISALLOW_COPY_AND_ASSIGN(SessionStateDelegateStub);
+};
+
+} // namespace ash
+
+#endif // ASH_SESSION_STATE_DELEGATE_STUB_H_
diff --git a/chromium/ash/session_state_observer.h b/chromium/ash/session_state_observer.h
new file mode 100644
index 00000000000..f4fad9dbf91
--- /dev/null
+++ b/chromium/ash/session_state_observer.h
@@ -0,0 +1,25 @@
+// Copyright (c) 2013 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 ASH_SESSION_STATE_OBSERVER_H_
+#define ASH_SESSION_STATE_OBSERVER_H_
+
+#include <string>
+
+#include "ash/ash_export.h"
+
+namespace ash {
+
+class ASH_EXPORT SessionStateObserver {
+ public:
+ // Called when active user has changed.
+ virtual void ActiveUserChanged(const std::string& user_id) {}
+
+ protected:
+ virtual ~SessionStateObserver() {}
+};
+
+} // namespace ash
+
+#endif // ASH_SESSION_STATE_OBSERVER_H_
diff --git a/chromium/ash/shelf/background_animator.cc b/chromium/ash/shelf/background_animator.cc
new file mode 100644
index 00000000000..dedae6fa1df
--- /dev/null
+++ b/chromium/ash/shelf/background_animator.cc
@@ -0,0 +1,61 @@
+// Copyright (c) 2012 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.
+
+#include "ash/shelf/background_animator.h"
+
+
+namespace ash {
+namespace internal {
+
+namespace {
+
+// Duration of the background animation.
+const int kBackgroundDurationMS = 1000;
+
+}
+
+BackgroundAnimator::BackgroundAnimator(BackgroundAnimatorDelegate* delegate,
+ int min_alpha,
+ int max_alpha)
+ : delegate_(delegate),
+ min_alpha_(min_alpha),
+ max_alpha_(max_alpha),
+ animation_(this),
+ paints_background_(false),
+ alpha_(min_alpha) {
+ animation_.SetSlideDuration(kBackgroundDurationMS);
+}
+
+BackgroundAnimator::~BackgroundAnimator() {
+}
+
+void BackgroundAnimator::SetDuration(int time_in_ms) {
+ animation_.SetSlideDuration(time_in_ms);
+}
+
+void BackgroundAnimator::SetPaintsBackground(bool value, ChangeType type) {
+ if (paints_background_ == value)
+ return;
+ paints_background_ = value;
+ if (type == CHANGE_IMMEDIATE && !animation_.is_animating()) {
+ animation_.Reset(value ? 1.0f : 0.0f);
+ AnimationProgressed(&animation_);
+ return;
+ }
+ if (paints_background_)
+ animation_.Show();
+ else
+ animation_.Hide();
+}
+
+void BackgroundAnimator::AnimationProgressed(const ui::Animation* animation) {
+ int alpha = animation->CurrentValueBetween(min_alpha_, max_alpha_);
+ if (alpha_ == alpha)
+ return;
+ alpha_ = alpha;
+ delegate_->UpdateBackground(alpha_);
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/shelf/background_animator.h b/chromium/ash/shelf/background_animator.h
new file mode 100644
index 00000000000..dbe495a2bc3
--- /dev/null
+++ b/chromium/ash/shelf/background_animator.h
@@ -0,0 +1,74 @@
+// Copyright (c) 2012 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 ASH_SHELF_BACKGROUND_ANIMATOR_H_
+#define ASH_SHELF_BACKGROUND_ANIMATOR_H_
+
+#include "ash/ash_export.h"
+#include "base/basictypes.h"
+#include "ui/base/animation/animation_delegate.h"
+#include "ui/base/animation/slide_animation.h"
+
+namespace ash {
+namespace internal {
+
+// Delegate is notified any time the background changes.
+class ASH_EXPORT BackgroundAnimatorDelegate {
+ public:
+ virtual void UpdateBackground(int alpha) = 0;
+
+ protected:
+ virtual ~BackgroundAnimatorDelegate() {}
+};
+
+// BackgroundAnimator is used by the shelf to animate the background (alpha).
+class ASH_EXPORT BackgroundAnimator : public ui::AnimationDelegate {
+ public:
+ // How the background can be changed.
+ enum ChangeType {
+ CHANGE_ANIMATE,
+ CHANGE_IMMEDIATE
+ };
+
+ BackgroundAnimator(BackgroundAnimatorDelegate* delegate,
+ int min_alpha,
+ int max_alpha);
+ virtual ~BackgroundAnimator();
+
+ // Sets the transition time in ms.
+ void SetDuration(int time_in_ms);
+
+ // Sets whether a background is rendered. Initial value is false. If |type|
+ // is |CHANGE_IMMEDIATE| and an animation is not in progress this notifies
+ // the delegate immediately (synchronously from this method).
+ void SetPaintsBackground(bool value, ChangeType type);
+ bool paints_background() const { return paints_background_; }
+
+ // Current alpha.
+ int alpha() const { return alpha_; }
+
+ // ui::AnimationDelegate overrides:
+ virtual void AnimationProgressed(const ui::Animation* animation) OVERRIDE;
+
+ private:
+ BackgroundAnimatorDelegate* delegate_;
+
+ const int min_alpha_;
+ const int max_alpha_;
+
+ ui::SlideAnimation animation_;
+
+ // Whether the background is painted.
+ bool paints_background_;
+
+ // Current alpha value of the background.
+ int alpha_;
+
+ DISALLOW_COPY_AND_ASSIGN(BackgroundAnimator);
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_SHELF_BACKGROUND_ANIMATOR_H_
diff --git a/chromium/ash/shelf/shelf_bezel_event_filter.cc b/chromium/ash/shelf/shelf_bezel_event_filter.cc
new file mode 100644
index 00000000000..12ec940ec7b
--- /dev/null
+++ b/chromium/ash/shelf/shelf_bezel_event_filter.cc
@@ -0,0 +1,73 @@
+// Copyright 2013 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.
+
+#include "ash/shelf/shelf_bezel_event_filter.h"
+
+#include "ash/shelf/shelf_layout_manager.h"
+#include "ash/shell.h"
+
+namespace ash {
+namespace internal {
+
+ShelfBezelEventFilter::ShelfBezelEventFilter(
+ ShelfLayoutManager* shelf)
+ : shelf_(shelf),
+ in_touch_drag_(false) {
+ Shell::GetInstance()->AddPreTargetHandler(this);
+}
+
+ShelfBezelEventFilter::~ShelfBezelEventFilter() {
+ Shell::GetInstance()->RemovePreTargetHandler(this);
+}
+
+void ShelfBezelEventFilter::OnGestureEvent(
+ ui::GestureEvent* event) {
+ gfx::Rect screen =
+ Shell::GetScreen()->GetDisplayNearestPoint(event->location()).bounds();
+ if ((!screen.Contains(event->location()) &&
+ IsShelfOnBezel(screen, event->location())) ||
+ in_touch_drag_) {
+ if (gesture_handler_.ProcessGestureEvent(*event)) {
+ switch (event->type()) {
+ case ui::ET_GESTURE_SCROLL_BEGIN:
+ in_touch_drag_ = true;
+ break;
+ case ui::ET_GESTURE_SCROLL_END:
+ case ui::ET_SCROLL_FLING_START:
+ in_touch_drag_ = false;
+ break;
+ default:
+ break;
+ }
+ event->StopPropagation();
+ }
+ }
+}
+
+bool ShelfBezelEventFilter::IsShelfOnBezel(
+ const gfx::Rect& screen,
+ const gfx::Point& point) const{
+ switch (shelf_->GetAlignment()) {
+ case SHELF_ALIGNMENT_BOTTOM:
+ if (point.y() >= screen.bottom())
+ return true;
+ break;
+ case SHELF_ALIGNMENT_LEFT:
+ if (point.x() <= screen.x())
+ return true;
+ break;
+ case SHELF_ALIGNMENT_TOP:
+ if (point.y() <= screen.y())
+ return true;
+ break;
+ case SHELF_ALIGNMENT_RIGHT:
+ if (point.x() >= screen.right())
+ return true;
+ break;
+ }
+ return false;
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/shelf/shelf_bezel_event_filter.h b/chromium/ash/shelf/shelf_bezel_event_filter.h
new file mode 100644
index 00000000000..5390c4ea150
--- /dev/null
+++ b/chromium/ash/shelf/shelf_bezel_event_filter.h
@@ -0,0 +1,39 @@
+// Copyright 2013 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 ASH_SHELF_SHELF_BEZEL_EVENT_FILTER_H_
+#define ASH_SHELF_SHELF_BEZEL_EVENT_FILTER_H_
+
+#include "ash/wm/gestures/shelf_gesture_handler.h"
+#include "ui/base/events/event_handler.h"
+#include "ui/gfx/rect.h"
+
+namespace ash {
+namespace internal {
+class ShelfLayoutManager;
+
+// Detects and forwards touch gestures that occur on a bezel sensor to the
+// shelf.
+class ShelfBezelEventFilter : public ui::EventHandler {
+ public:
+ explicit ShelfBezelEventFilter(ShelfLayoutManager* shelf);
+ virtual ~ShelfBezelEventFilter();
+
+ // Overridden from ui::EventHandler:
+ virtual void OnGestureEvent(ui::GestureEvent* event) OVERRIDE;
+
+ private:
+ bool IsShelfOnBezel(const gfx::Rect& screen,
+ const gfx::Point& point) const;
+
+ ShelfLayoutManager* shelf_; // non-owned
+ bool in_touch_drag_;
+ ShelfGestureHandler gesture_handler_;
+ DISALLOW_COPY_AND_ASSIGN(ShelfBezelEventFilter);
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_SHELF_SHELF_BEZEL_EVENT_FILTER_H_
diff --git a/chromium/ash/shelf/shelf_layout_manager.cc b/chromium/ash/shelf/shelf_layout_manager.cc
new file mode 100644
index 00000000000..a507da648a8
--- /dev/null
+++ b/chromium/ash/shelf/shelf_layout_manager.cc
@@ -0,0 +1,1130 @@
+// Copyright (c) 2012 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.
+
+#include "ash/shelf/shelf_layout_manager.h"
+
+#include <algorithm>
+#include <cmath>
+#include <cstring>
+#include <string>
+#include <vector>
+
+#include "ash/ash_switches.h"
+#include "ash/launcher/launcher.h"
+#include "ash/launcher/launcher_types.h"
+#include "ash/root_window_controller.h"
+#include "ash/screen_ash.h"
+#include "ash/session_state_delegate.h"
+#include "ash/shelf/shelf_bezel_event_filter.h"
+#include "ash/shelf/shelf_layout_manager_observer.h"
+#include "ash/shelf/shelf_widget.h"
+#include "ash/shell.h"
+#include "ash/shell_window_ids.h"
+#include "ash/system/status_area_widget.h"
+#include "ash/wm/gestures/shelf_gesture_handler.h"
+#include "ash/wm/mru_window_tracker.h"
+#include "ash/wm/property_util.h"
+#include "ash/wm/window_animations.h"
+#include "ash/wm/window_properties.h"
+#include "ash/wm/window_util.h"
+#include "ash/wm/workspace_controller.h"
+#include "base/auto_reset.h"
+#include "base/command_line.h"
+#include "base/command_line.h"
+#include "base/i18n/rtl.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "ui/aura/client/activation_client.h"
+#include "ui/aura/client/cursor_client.h"
+#include "ui/aura/root_window.h"
+#include "ui/base/events/event.h"
+#include "ui/base/events/event_handler.h"
+#include "ui/base/ui_base_switches.h"
+#include "ui/compositor/layer.h"
+#include "ui/compositor/layer_animation_observer.h"
+#include "ui/compositor/layer_animator.h"
+#include "ui/compositor/scoped_layer_animation_settings.h"
+#include "ui/gfx/screen.h"
+#include "ui/views/widget/widget.h"
+
+namespace ash {
+namespace internal {
+
+namespace {
+
+// Delay before showing the launcher. This is after the mouse stops moving.
+const int kAutoHideDelayMS = 200;
+
+// To avoid hiding the shelf when the mouse transitions from a message bubble
+// into the shelf, the hit test area is enlarged by this amount of pixels to
+// keep the shelf from hiding.
+const int kNotificationBubbleGapHeight = 6;
+
+// The maximum size of the region on the display opposing the shelf managed by
+// this ShelfLayoutManager which can trigger showing the shelf.
+// For instance:
+// - Primary display is left of secondary display.
+// - Shelf is left aligned
+// - This ShelfLayoutManager manages the shelf for the secondary display.
+// |kMaxAutoHideShowShelfRegionSize| refers to the maximum size of the region
+// from the right edge of the primary display which can trigger showing the
+// auto hidden shelf. The region is used to make it easier to trigger showing
+// the auto hidden shelf when the shelf is on the boundary between displays.
+const int kMaxAutoHideShowShelfRegionSize = 10;
+
+// Const inset from the edget of the shelf to the edget of the status area.
+const int kStatusAreaInset = 3;
+
+ui::Layer* GetLayer(views::Widget* widget) {
+ return widget->GetNativeView()->layer();
+}
+
+bool IsDraggingTrayEnabled() {
+ static bool dragging_tray_allowed = CommandLine::ForCurrentProcess()->
+ HasSwitch(ash::switches::kAshEnableTrayDragging);
+ return dragging_tray_allowed;
+}
+
+} // namespace
+
+// static
+const int ShelfLayoutManager::kWorkspaceAreaVisibleInset = 2;
+
+// static
+const int ShelfLayoutManager::kWorkspaceAreaAutoHideInset = 5;
+
+// static
+const int ShelfLayoutManager::kAutoHideSize = 3;
+
+// static
+const int ShelfLayoutManager::kShelfSize = 47;
+
+int ShelfLayoutManager::GetPreferredShelfSize() {
+ return ash::switches::UseAlternateShelfLayout() ?
+ ShelfLayoutManager::kShelfSize : kLauncherPreferredSize;
+}
+
+// ShelfLayoutManager::AutoHideEventFilter -------------------------------------
+
+// Notifies ShelfLayoutManager any time the mouse moves.
+class ShelfLayoutManager::AutoHideEventFilter : public ui::EventHandler {
+ public:
+ explicit AutoHideEventFilter(ShelfLayoutManager* shelf);
+ virtual ~AutoHideEventFilter();
+
+ // Returns true if the last mouse event was a mouse drag.
+ bool in_mouse_drag() const { return in_mouse_drag_; }
+
+ // Overridden from ui::EventHandler:
+ virtual void OnMouseEvent(ui::MouseEvent* event) OVERRIDE;
+ virtual void OnGestureEvent(ui::GestureEvent* event) OVERRIDE;
+
+ private:
+ ShelfLayoutManager* shelf_;
+ bool in_mouse_drag_;
+ ShelfGestureHandler gesture_handler_;
+ DISALLOW_COPY_AND_ASSIGN(AutoHideEventFilter);
+};
+
+ShelfLayoutManager::AutoHideEventFilter::AutoHideEventFilter(
+ ShelfLayoutManager* shelf)
+ : shelf_(shelf),
+ in_mouse_drag_(false) {
+ Shell::GetInstance()->AddPreTargetHandler(this);
+}
+
+ShelfLayoutManager::AutoHideEventFilter::~AutoHideEventFilter() {
+ Shell::GetInstance()->RemovePreTargetHandler(this);
+}
+
+void ShelfLayoutManager::AutoHideEventFilter::OnMouseEvent(
+ ui::MouseEvent* event) {
+ // This also checks IsShelfWindow() to make sure we don't attempt to hide the
+ // shelf if the mouse down occurs on the shelf.
+ in_mouse_drag_ = (event->type() == ui::ET_MOUSE_DRAGGED ||
+ (in_mouse_drag_ && event->type() != ui::ET_MOUSE_RELEASED &&
+ event->type() != ui::ET_MOUSE_CAPTURE_CHANGED)) &&
+ !shelf_->IsShelfWindow(static_cast<aura::Window*>(event->target()));
+ if (event->type() == ui::ET_MOUSE_MOVED)
+ shelf_->UpdateAutoHideState();
+ return;
+}
+
+void ShelfLayoutManager::AutoHideEventFilter::OnGestureEvent(
+ ui::GestureEvent* event) {
+ if (shelf_->IsShelfWindow(static_cast<aura::Window*>(event->target()))) {
+ if (gesture_handler_.ProcessGestureEvent(*event))
+ event->StopPropagation();
+ }
+}
+
+// ShelfLayoutManager:UpdateShelfObserver --------------------------------------
+
+// UpdateShelfObserver is used to delay updating the background until the
+// animation completes.
+class ShelfLayoutManager::UpdateShelfObserver
+ : public ui::ImplicitAnimationObserver {
+ public:
+ explicit UpdateShelfObserver(ShelfLayoutManager* shelf) : shelf_(shelf) {
+ shelf_->update_shelf_observer_ = this;
+ }
+
+ void Detach() {
+ shelf_ = NULL;
+ }
+
+ virtual void OnImplicitAnimationsCompleted() OVERRIDE {
+ if (shelf_) {
+ shelf_->UpdateShelfBackground(BackgroundAnimator::CHANGE_ANIMATE);
+ }
+ delete this;
+ }
+
+ private:
+ virtual ~UpdateShelfObserver() {
+ if (shelf_)
+ shelf_->update_shelf_observer_ = NULL;
+ }
+
+ // Shelf we're in. NULL if deleted before we're deleted.
+ ShelfLayoutManager* shelf_;
+
+ DISALLOW_COPY_AND_ASSIGN(UpdateShelfObserver);
+};
+
+// ShelfLayoutManager ----------------------------------------------------------
+
+ShelfLayoutManager::ShelfLayoutManager(ShelfWidget* shelf)
+ : root_window_(shelf->GetNativeView()->GetRootWindow()),
+ updating_bounds_(false),
+ auto_hide_behavior_(SHELF_AUTO_HIDE_BEHAVIOR_NEVER),
+ alignment_(SHELF_ALIGNMENT_BOTTOM),
+ shelf_(shelf),
+ workspace_controller_(NULL),
+ window_overlaps_shelf_(false),
+ mouse_over_shelf_when_auto_hide_timer_started_(false),
+ bezel_event_filter_(new ShelfBezelEventFilter(this)),
+ gesture_drag_status_(GESTURE_DRAG_NONE),
+ gesture_drag_amount_(0.f),
+ gesture_drag_auto_hide_state_(SHELF_AUTO_HIDE_SHOWN),
+ update_shelf_observer_(NULL) {
+ Shell::GetInstance()->AddShellObserver(this);
+ aura::client::GetActivationClient(root_window_)->AddObserver(this);
+}
+
+ShelfLayoutManager::~ShelfLayoutManager() {
+ if (update_shelf_observer_)
+ update_shelf_observer_->Detach();
+
+ FOR_EACH_OBSERVER(ShelfLayoutManagerObserver, observers_, WillDeleteShelf());
+ Shell::GetInstance()->RemoveShellObserver(this);
+ aura::client::GetActivationClient(root_window_)->RemoveObserver(this);
+}
+
+void ShelfLayoutManager::SetAutoHideBehavior(ShelfAutoHideBehavior behavior) {
+ if (auto_hide_behavior_ == behavior)
+ return;
+ auto_hide_behavior_ = behavior;
+ UpdateVisibilityState();
+ FOR_EACH_OBSERVER(ShelfLayoutManagerObserver, observers_,
+ OnAutoHideBehaviorChanged(root_window_,
+ auto_hide_behavior_));
+}
+
+void ShelfLayoutManager::PrepareForShutdown() {
+ // Clear all event filters, otherwise sometimes those filters may catch
+ // synthesized mouse event and cause crashes during the shutdown.
+ set_workspace_controller(NULL);
+ auto_hide_event_filter_.reset();
+ bezel_event_filter_.reset();
+}
+
+bool ShelfLayoutManager::IsVisible() const {
+ // status_area_widget() may be NULL during the shutdown.
+ return shelf_->status_area_widget() &&
+ shelf_->status_area_widget()->IsVisible() &&
+ (state_.visibility_state == SHELF_VISIBLE ||
+ (state_.visibility_state == SHELF_AUTO_HIDE &&
+ state_.auto_hide_state == SHELF_AUTO_HIDE_SHOWN));
+}
+
+bool ShelfLayoutManager::SetAlignment(ShelfAlignment alignment) {
+ if (alignment_ == alignment)
+ return false;
+
+ alignment_ = alignment;
+ shelf_->SetAlignment(alignment);
+ LayoutShelf();
+ return true;
+}
+
+gfx::Rect ShelfLayoutManager::GetIdealBounds() {
+ gfx::Rect bounds(
+ ScreenAsh::GetDisplayBoundsInParent(shelf_->GetNativeView()));
+ int width = 0, height = 0;
+ GetShelfSize(&width, &height);
+ return SelectValueForShelfAlignment(
+ gfx::Rect(bounds.x(), bounds.bottom() - height, bounds.width(), height),
+ gfx::Rect(bounds.x(), bounds.y(), width, bounds.height()),
+ gfx::Rect(bounds.right() - width, bounds.y(), width, bounds.height()),
+ gfx::Rect(bounds.x(), bounds.y(), bounds.width(), height));
+}
+
+void ShelfLayoutManager::LayoutShelf() {
+ TargetBounds target_bounds;
+ CalculateTargetBounds(state_, &target_bounds);
+ UpdateBoundsAndOpacity(target_bounds, false, NULL);
+
+ if (shelf_->launcher()) {
+ // This is not part of UpdateBoundsAndOpacity() because
+ // SetLauncherViewBounds() sets the bounds immediately and does not animate.
+ // The height of the LauncherView for a horizontal shelf and the width of
+ // the LauncherView for a vertical shelf are set when |shelf_|'s bounds
+ // are changed via UpdateBoundsAndOpacity(). This sets the origin and the
+ // dimension in the other direction.
+ shelf_->launcher()->SetLauncherViewBounds(
+ target_bounds.launcher_bounds_in_shelf);
+ }
+}
+
+ShelfVisibilityState ShelfLayoutManager::CalculateShelfVisibility() {
+ switch(auto_hide_behavior_) {
+ case SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS:
+ return SHELF_AUTO_HIDE;
+ case SHELF_AUTO_HIDE_BEHAVIOR_NEVER:
+ return SHELF_VISIBLE;
+ case SHELF_AUTO_HIDE_ALWAYS_HIDDEN:
+ return SHELF_HIDDEN;
+ }
+ return SHELF_VISIBLE;
+}
+
+void ShelfLayoutManager::UpdateVisibilityState() {
+ if (Shell::GetInstance()->session_state_delegate()->IsScreenLocked()) {
+ SetState(SHELF_VISIBLE);
+ } else {
+ // TODO(zelidrag): Verify shelf drag animation still shows on the device
+ // when we are in SHELF_AUTO_HIDE_ALWAYS_HIDDEN.
+ WorkspaceWindowState window_state(workspace_controller_->GetWindowState());
+ switch (window_state) {
+ case WORKSPACE_WINDOW_STATE_FULL_SCREEN:
+ if (FullscreenWithMinimalChrome()) {
+ SetState(SHELF_AUTO_HIDE);
+ } else {
+ SetState(SHELF_HIDDEN);
+ }
+ break;
+ case WORKSPACE_WINDOW_STATE_MAXIMIZED:
+ SetState(CalculateShelfVisibility());
+ break;
+ case WORKSPACE_WINDOW_STATE_WINDOW_OVERLAPS_SHELF:
+ case WORKSPACE_WINDOW_STATE_DEFAULT:
+ SetState(CalculateShelfVisibility());
+ SetWindowOverlapsShelf(window_state ==
+ WORKSPACE_WINDOW_STATE_WINDOW_OVERLAPS_SHELF);
+ break;
+ }
+ }
+}
+
+void ShelfLayoutManager::UpdateAutoHideState() {
+ ShelfAutoHideState auto_hide_state =
+ CalculateAutoHideState(state_.visibility_state);
+ if (auto_hide_state != state_.auto_hide_state) {
+ if (auto_hide_state == SHELF_AUTO_HIDE_HIDDEN) {
+ // Hides happen immediately.
+ SetState(state_.visibility_state);
+ } else {
+ if (!auto_hide_timer_.IsRunning()) {
+ mouse_over_shelf_when_auto_hide_timer_started_ =
+ shelf_->GetWindowBoundsInScreen().Contains(
+ Shell::GetScreen()->GetCursorScreenPoint());
+ }
+ auto_hide_timer_.Start(
+ FROM_HERE,
+ base::TimeDelta::FromMilliseconds(kAutoHideDelayMS),
+ this, &ShelfLayoutManager::UpdateAutoHideStateNow);
+ }
+ } else {
+ StopAutoHideTimer();
+ }
+}
+
+void ShelfLayoutManager::SetWindowOverlapsShelf(bool value) {
+ window_overlaps_shelf_ = value;
+ UpdateShelfBackground(BackgroundAnimator::CHANGE_ANIMATE);
+}
+
+void ShelfLayoutManager::AddObserver(ShelfLayoutManagerObserver* observer) {
+ observers_.AddObserver(observer);
+}
+
+void ShelfLayoutManager::RemoveObserver(ShelfLayoutManagerObserver* observer) {
+ observers_.RemoveObserver(observer);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// ShelfLayoutManager, Gesture dragging:
+
+void ShelfLayoutManager::StartGestureDrag(const ui::GestureEvent& gesture) {
+ gesture_drag_status_ = GESTURE_DRAG_IN_PROGRESS;
+ gesture_drag_amount_ = 0.f;
+ gesture_drag_auto_hide_state_ = visibility_state() == SHELF_AUTO_HIDE ?
+ auto_hide_state() : SHELF_AUTO_HIDE_SHOWN;
+ UpdateShelfBackground(BackgroundAnimator::CHANGE_ANIMATE);
+}
+
+ShelfLayoutManager::DragState ShelfLayoutManager::UpdateGestureDrag(
+ const ui::GestureEvent& gesture) {
+ bool horizontal = IsHorizontalAlignment();
+ gesture_drag_amount_ += horizontal ? gesture.details().scroll_y() :
+ gesture.details().scroll_x();
+ LayoutShelf();
+
+ // Start reveling the status menu when:
+ // - dragging up on an already visible shelf
+ // - dragging up on a hidden shelf, but it is currently completely visible.
+ if (horizontal && gesture.details().scroll_y() < 0) {
+ int min_height = 0;
+ if (gesture_drag_auto_hide_state_ == SHELF_AUTO_HIDE_HIDDEN && shelf_)
+ min_height = shelf_->GetContentsView()->GetPreferredSize().height();
+
+ if (min_height < shelf_->GetWindowBoundsInScreen().height() &&
+ gesture.root_location().x() >=
+ shelf_->status_area_widget()->GetWindowBoundsInScreen().x() &&
+ IsDraggingTrayEnabled())
+ return DRAG_TRAY;
+ }
+
+ return DRAG_SHELF;
+}
+
+void ShelfLayoutManager::CompleteGestureDrag(const ui::GestureEvent& gesture) {
+ bool horizontal = IsHorizontalAlignment();
+ bool should_change = false;
+ if (gesture.type() == ui::ET_GESTURE_SCROLL_END) {
+ // The visibility of the shelf changes only if the shelf was dragged X%
+ // along the correct axis. If the shelf was already visible, then the
+ // direction of the drag does not matter.
+ const float kDragHideThreshold = 0.4f;
+ gfx::Rect bounds = GetIdealBounds();
+ float drag_ratio = fabs(gesture_drag_amount_) /
+ (horizontal ? bounds.height() : bounds.width());
+ if (gesture_drag_auto_hide_state_ == SHELF_AUTO_HIDE_SHOWN) {
+ should_change = drag_ratio > kDragHideThreshold;
+ } else {
+ bool correct_direction = false;
+ switch (alignment_) {
+ case SHELF_ALIGNMENT_BOTTOM:
+ case SHELF_ALIGNMENT_RIGHT:
+ correct_direction = gesture_drag_amount_ < 0;
+ break;
+ case SHELF_ALIGNMENT_LEFT:
+ case SHELF_ALIGNMENT_TOP:
+ correct_direction = gesture_drag_amount_ > 0;
+ break;
+ }
+ should_change = correct_direction && drag_ratio > kDragHideThreshold;
+ }
+ } else if (gesture.type() == ui::ET_SCROLL_FLING_START) {
+ if (gesture_drag_auto_hide_state_ == SHELF_AUTO_HIDE_SHOWN) {
+ should_change = horizontal ? fabs(gesture.details().velocity_y()) > 0 :
+ fabs(gesture.details().velocity_x()) > 0;
+ } else {
+ should_change = SelectValueForShelfAlignment(
+ gesture.details().velocity_y() < 0,
+ gesture.details().velocity_x() > 0,
+ gesture.details().velocity_x() < 0,
+ gesture.details().velocity_y() > 0);
+ }
+ } else {
+ NOTREACHED();
+ }
+
+ if (!should_change) {
+ CancelGestureDrag();
+ return;
+ }
+ if (shelf_) {
+ shelf_->Deactivate();
+ shelf_->status_area_widget()->Deactivate();
+ }
+ gesture_drag_auto_hide_state_ =
+ gesture_drag_auto_hide_state_ == SHELF_AUTO_HIDE_SHOWN ?
+ SHELF_AUTO_HIDE_HIDDEN : SHELF_AUTO_HIDE_SHOWN;
+ ShelfAutoHideBehavior new_auto_hide_behavior =
+ gesture_drag_auto_hide_state_ == SHELF_AUTO_HIDE_SHOWN ?
+ SHELF_AUTO_HIDE_BEHAVIOR_NEVER : SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS;
+
+ // In fullscreen with minimal chrome, the auto hide behavior affects neither
+ // the visibility state nor the auto hide state. Set |gesture_drag_status_|
+ // to GESTURE_DRAG_COMPLETE_IN_PROGRESS to set the auto hide state to
+ // |gesture_drag_auto_hide_state_|.
+ gesture_drag_status_ = GESTURE_DRAG_COMPLETE_IN_PROGRESS;
+ if (auto_hide_behavior_ != new_auto_hide_behavior)
+ SetAutoHideBehavior(new_auto_hide_behavior);
+ else
+ UpdateVisibilityState();
+ gesture_drag_status_ = GESTURE_DRAG_NONE;
+}
+
+void ShelfLayoutManager::CancelGestureDrag() {
+ gesture_drag_status_ = GESTURE_DRAG_CANCEL_IN_PROGRESS;
+ UpdateVisibilityState();
+ gesture_drag_status_ = GESTURE_DRAG_NONE;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// ShelfLayoutManager, aura::LayoutManager implementation:
+
+void ShelfLayoutManager::OnWindowResized() {
+ LayoutShelf();
+}
+
+void ShelfLayoutManager::OnWindowAddedToLayout(aura::Window* child) {
+}
+
+void ShelfLayoutManager::OnWillRemoveWindowFromLayout(aura::Window* child) {
+}
+
+void ShelfLayoutManager::OnWindowRemovedFromLayout(aura::Window* child) {
+}
+
+void ShelfLayoutManager::OnChildWindowVisibilityChanged(aura::Window* child,
+ bool visible) {
+}
+
+void ShelfLayoutManager::SetChildBounds(aura::Window* child,
+ const gfx::Rect& requested_bounds) {
+ SetChildBoundsDirect(child, requested_bounds);
+ // We may contain other widgets (such as frame maximize bubble) but they don't
+ // effect the layout in anyway.
+ if (!updating_bounds_ &&
+ ((shelf_->GetNativeView() == child) ||
+ (shelf_->status_area_widget()->GetNativeView() == child))) {
+ LayoutShelf();
+ }
+}
+
+void ShelfLayoutManager::OnLockStateChanged(bool locked) {
+ UpdateVisibilityState();
+}
+
+void ShelfLayoutManager::OnWindowActivated(aura::Window* gained_active,
+ aura::Window* lost_active) {
+ UpdateAutoHideStateNow();
+}
+
+bool ShelfLayoutManager::IsHorizontalAlignment() const {
+ return alignment_ == SHELF_ALIGNMENT_BOTTOM ||
+ alignment_ == SHELF_ALIGNMENT_TOP;
+}
+
+bool ShelfLayoutManager::FullscreenWithMinimalChrome() const {
+ RootWindowController* controller = GetRootWindowController(root_window_);
+ if (!controller)
+ return false;
+ const aura::Window* window = controller->GetFullscreenWindow();
+ if (!window)
+ return false;
+ if (!window->GetProperty(kFullscreenUsesMinimalChromeKey))
+ return false;
+ return true;
+}
+
+// static
+ShelfLayoutManager* ShelfLayoutManager::ForLauncher(aura::Window* window) {
+ ShelfWidget* shelf = RootWindowController::ForLauncher(window)->shelf();
+ return shelf ? shelf->shelf_layout_manager() : NULL;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// ShelfLayoutManager, private:
+
+ShelfLayoutManager::TargetBounds::TargetBounds() : opacity(0.0f) {}
+ShelfLayoutManager::TargetBounds::~TargetBounds() {}
+
+void ShelfLayoutManager::SetState(ShelfVisibilityState visibility_state) {
+ if (!shelf_->GetNativeView())
+ return;
+
+ State state;
+ state.visibility_state = visibility_state;
+ state.auto_hide_state = CalculateAutoHideState(visibility_state);
+ state.is_screen_locked =
+ Shell::GetInstance()->session_state_delegate()->IsScreenLocked();
+ state.window_state = workspace_controller_ ?
+ workspace_controller_->GetWindowState() : WORKSPACE_WINDOW_STATE_DEFAULT;
+
+ // Force an update because gesture drags affect the shelf bounds and we
+ // should animate back to the normal bounds at the end of a gesture.
+ bool force_update =
+ (gesture_drag_status_ == GESTURE_DRAG_CANCEL_IN_PROGRESS ||
+ gesture_drag_status_ == GESTURE_DRAG_COMPLETE_IN_PROGRESS);
+
+ if (!force_update && state_.Equals(state))
+ return; // Nothing changed.
+
+ FOR_EACH_OBSERVER(ShelfLayoutManagerObserver, observers_,
+ WillChangeVisibilityState(visibility_state));
+
+ if (state.visibility_state == SHELF_AUTO_HIDE) {
+ // When state is SHELF_AUTO_HIDE we need to track when the mouse is over the
+ // launcher to unhide the shelf. AutoHideEventFilter does that for us.
+ if (!auto_hide_event_filter_)
+ auto_hide_event_filter_.reset(new AutoHideEventFilter(this));
+ } else {
+ auto_hide_event_filter_.reset(NULL);
+ }
+
+ StopAutoHideTimer();
+
+ State old_state = state_;
+ state_ = state;
+
+ BackgroundAnimator::ChangeType change_type =
+ BackgroundAnimator::CHANGE_ANIMATE;
+ bool delay_background_change = false;
+
+ // Do not animate the background when:
+ // - Going from a hidden / auto hidden shelf in fullscreen to a visible shelf
+ // in maximized mode.
+ // - Going from an auto hidden shelf in maximized mode to a visible shelf in
+ // maximized mode.
+ if (state.visibility_state == SHELF_VISIBLE &&
+ state.window_state == WORKSPACE_WINDOW_STATE_MAXIMIZED &&
+ old_state.visibility_state != SHELF_VISIBLE) {
+ change_type = BackgroundAnimator::CHANGE_IMMEDIATE;
+ } else {
+ // Delay the animation when the shelf was hidden, and has just been made
+ // visible (e.g. using a gesture-drag).
+ if (state.visibility_state == SHELF_VISIBLE &&
+ old_state.visibility_state == SHELF_AUTO_HIDE &&
+ old_state.auto_hide_state == SHELF_AUTO_HIDE_HIDDEN) {
+ delay_background_change = true;
+ }
+ }
+
+ if (delay_background_change) {
+ if (update_shelf_observer_)
+ update_shelf_observer_->Detach();
+ // UpdateShelfBackground deletes itself when the animation is done.
+ update_shelf_observer_ = new UpdateShelfObserver(this);
+ } else {
+ UpdateShelfBackground(change_type);
+ }
+
+ shelf_->SetDimsShelf(
+ state.visibility_state == SHELF_VISIBLE &&
+ state.window_state == WORKSPACE_WINDOW_STATE_MAXIMIZED);
+
+ TargetBounds target_bounds;
+ CalculateTargetBounds(state_, &target_bounds);
+ UpdateBoundsAndOpacity(target_bounds, true,
+ delay_background_change ? update_shelf_observer_ : NULL);
+
+ // OnAutoHideStateChanged Should be emitted when:
+ // - firstly state changed to auto-hide from other state
+ // - or, auto_hide_state has changed
+ if ((old_state.visibility_state != state_.visibility_state &&
+ state_.visibility_state == SHELF_AUTO_HIDE) ||
+ old_state.auto_hide_state != state_.auto_hide_state) {
+ FOR_EACH_OBSERVER(ShelfLayoutManagerObserver, observers_,
+ OnAutoHideStateChanged(state_.auto_hide_state));
+ }
+}
+
+void ShelfLayoutManager::UpdateBoundsAndOpacity(
+ const TargetBounds& target_bounds,
+ bool animate,
+ ui::ImplicitAnimationObserver* observer) {
+ base::AutoReset<bool> auto_reset_updating_bounds(&updating_bounds_, true);
+
+ ui::ScopedLayerAnimationSettings launcher_animation_setter(
+ GetLayer(shelf_)->GetAnimator());
+ ui::ScopedLayerAnimationSettings status_animation_setter(
+ GetLayer(shelf_->status_area_widget())->GetAnimator());
+ if (animate) {
+ launcher_animation_setter.SetTransitionDuration(
+ base::TimeDelta::FromMilliseconds(kCrossFadeDurationMS));
+ launcher_animation_setter.SetTweenType(ui::Tween::EASE_OUT);
+ launcher_animation_setter.SetPreemptionStrategy(
+ ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
+ status_animation_setter.SetTransitionDuration(
+ base::TimeDelta::FromMilliseconds(kCrossFadeDurationMS));
+ status_animation_setter.SetTweenType(ui::Tween::EASE_OUT);
+ status_animation_setter.SetPreemptionStrategy(
+ ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
+ } else {
+ StopAnimating();
+ launcher_animation_setter.SetTransitionDuration(base::TimeDelta());
+ status_animation_setter.SetTransitionDuration(base::TimeDelta());
+ }
+ if (observer)
+ status_animation_setter.AddObserver(observer);
+
+ GetLayer(shelf_)->SetOpacity(target_bounds.opacity);
+ shelf_->SetBounds(ScreenAsh::ConvertRectToScreen(
+ shelf_->GetNativeView()->parent(),
+ target_bounds.shelf_bounds_in_root));
+
+ GetLayer(shelf_->status_area_widget())->SetOpacity(
+ target_bounds.status_opacity);
+ // TODO(harrym): Once status area widget is a child view of shelf
+ // this can be simplified.
+ gfx::Rect status_bounds = target_bounds.status_bounds_in_shelf;
+ status_bounds.set_x(status_bounds.x() +
+ target_bounds.shelf_bounds_in_root.x());
+ status_bounds.set_y(status_bounds.y() +
+ target_bounds.shelf_bounds_in_root.y());
+ shelf_->status_area_widget()->SetBounds(
+ ScreenAsh::ConvertRectToScreen(
+ shelf_->status_area_widget()->GetNativeView()->parent(),
+ status_bounds));
+ Shell::GetInstance()->SetDisplayWorkAreaInsets(
+ root_window_, target_bounds.work_area_insets);
+ UpdateHitTestBounds();
+}
+
+void ShelfLayoutManager::StopAnimating() {
+ GetLayer(shelf_)->GetAnimator()->StopAnimating();
+ GetLayer(shelf_->status_area_widget())->GetAnimator()->StopAnimating();
+}
+
+void ShelfLayoutManager::GetShelfSize(int* width, int* height) {
+ *width = *height = 0;
+ gfx::Size status_size(
+ shelf_->status_area_widget()->GetWindowBoundsInScreen().size());
+ if (IsHorizontalAlignment())
+ *height = GetPreferredShelfSize();
+ else
+ *width = GetPreferredShelfSize();
+}
+
+void ShelfLayoutManager::AdjustBoundsBasedOnAlignment(int inset,
+ gfx::Rect* bounds) const {
+ bounds->Inset(SelectValueForShelfAlignment(
+ gfx::Insets(0, 0, inset, 0),
+ gfx::Insets(0, inset, 0, 0),
+ gfx::Insets(0, 0, 0, inset),
+ gfx::Insets(inset, 0, 0, 0)));
+}
+
+void ShelfLayoutManager::CalculateTargetBounds(
+ const State& state,
+ TargetBounds* target_bounds) {
+ const gfx::Rect available_bounds(GetAvailableBounds());
+ gfx::Rect status_size(
+ shelf_->status_area_widget()->GetWindowBoundsInScreen().size());
+ int shelf_width = 0, shelf_height = 0;
+ GetShelfSize(&shelf_width, &shelf_height);
+ if (IsHorizontalAlignment())
+ shelf_width = available_bounds.width();
+ else
+ shelf_height = available_bounds.height();
+
+ if (state.visibility_state == SHELF_AUTO_HIDE &&
+ state.auto_hide_state == SHELF_AUTO_HIDE_HIDDEN) {
+ // Auto-hidden shelf always starts with the default size. If a gesture-drag
+ // is in progress, then the call to UpdateTargetBoundsForGesture() below
+ // takes care of setting the height properly.
+ if (IsHorizontalAlignment())
+ shelf_height = kAutoHideSize;
+ else
+ shelf_width = kAutoHideSize;
+ } else if (state.visibility_state == SHELF_HIDDEN ||
+ !keyboard_bounds_.IsEmpty()) {
+ if (IsHorizontalAlignment())
+ shelf_height = 0;
+ else
+ shelf_width = 0;
+ }
+
+ target_bounds->shelf_bounds_in_root = SelectValueForShelfAlignment(
+ gfx::Rect(available_bounds.x(), available_bounds.bottom() - shelf_height,
+ available_bounds.width(), shelf_height),
+ gfx::Rect(available_bounds.x(), available_bounds.y(),
+ shelf_width, available_bounds.height()),
+ gfx::Rect(available_bounds.right() - shelf_width, available_bounds.y(),
+ shelf_width, available_bounds.height()),
+ gfx::Rect(available_bounds.x(), available_bounds.y(),
+ available_bounds.width(), shelf_height));
+
+ int status_inset = std::max(0, GetPreferredShelfSize() -
+ PrimaryAxisValue(status_size.height(), status_size.width()));
+
+ if (ash::switches::UseAlternateShelfLayout())
+ status_inset = kStatusAreaInset;
+
+ target_bounds->status_bounds_in_shelf = SelectValueForShelfAlignment(
+ gfx::Rect(base::i18n::IsRTL() ? 0 : shelf_width - status_size.width(),
+ status_inset, status_size.width(), status_size.height()),
+ gfx::Rect(shelf_width - (status_size.width() + status_inset),
+ shelf_height - status_size.height(), status_size.width(),
+ status_size.height()),
+ gfx::Rect(status_inset, shelf_height - status_size.height(),
+ status_size.width(), status_size.height()),
+ gfx::Rect(base::i18n::IsRTL() ? 0 : shelf_width - status_size.width(),
+ shelf_height - (status_size.height() + status_inset),
+ status_size.width(), status_size.height()));
+
+ target_bounds->work_area_insets = SelectValueForShelfAlignment(
+ gfx::Insets(0, 0, GetWorkAreaSize(state, shelf_height), 0),
+ gfx::Insets(0, GetWorkAreaSize(state, shelf_width), 0, 0),
+ gfx::Insets(0, 0, 0, GetWorkAreaSize(state, shelf_width)),
+ gfx::Insets(GetWorkAreaSize(state, shelf_height), 0, 0, 0));
+
+ // TODO(varkha): The functionality of managing insets for display areas
+ // should probably be pushed to a separate component. This would simplify or
+ // remove entirely the dependency on keyboard and dock.
+
+ // Also push in the work area inset for the keyboard if it is visible.
+ if (!keyboard_bounds_.IsEmpty()) {
+ gfx::Insets keyboard_insets(0, 0, keyboard_bounds_.height(), 0);
+ target_bounds->work_area_insets += keyboard_insets;
+ }
+
+ // Also push in the work area inset for the dock if it is visible.
+ if (!dock_bounds_.IsEmpty()) {
+ gfx::Insets dock_insets(
+ 0, (dock_bounds_.x() > 0 ? 0 : dock_bounds_.width()),
+ 0, (dock_bounds_.x() > 0 ? dock_bounds_.width() : 0));
+ target_bounds->work_area_insets += dock_insets;
+ }
+
+ target_bounds->opacity =
+ (gesture_drag_status_ == GESTURE_DRAG_IN_PROGRESS ||
+ state.visibility_state == SHELF_VISIBLE ||
+ state.visibility_state == SHELF_AUTO_HIDE) ? 1.0f : 0.0f;
+ target_bounds->status_opacity =
+ (state.visibility_state == SHELF_AUTO_HIDE &&
+ state.auto_hide_state == SHELF_AUTO_HIDE_HIDDEN &&
+ gesture_drag_status_ != GESTURE_DRAG_IN_PROGRESS) ?
+ 0.0f : target_bounds->opacity;
+
+ if (gesture_drag_status_ == GESTURE_DRAG_IN_PROGRESS)
+ UpdateTargetBoundsForGesture(target_bounds);
+
+ // This needs to happen after calling UpdateTargetBoundsForGesture(), because
+ // that can change the size of the shelf.
+ target_bounds->launcher_bounds_in_shelf = SelectValueForShelfAlignment(
+ gfx::Rect(base::i18n::IsRTL() ? status_size.width() : 0, 0,
+ shelf_width - status_size.width(),
+ target_bounds->shelf_bounds_in_root.height()),
+ gfx::Rect(0, 0, target_bounds->shelf_bounds_in_root.width(),
+ shelf_height - status_size.height()),
+ gfx::Rect(0, 0, target_bounds->shelf_bounds_in_root.width(),
+ shelf_height - status_size.height()),
+ gfx::Rect(base::i18n::IsRTL() ? status_size.width() : 0, 0,
+ shelf_width - status_size.width(),
+ target_bounds->shelf_bounds_in_root.height()));
+}
+
+void ShelfLayoutManager::UpdateTargetBoundsForGesture(
+ TargetBounds* target_bounds) const {
+ CHECK_EQ(GESTURE_DRAG_IN_PROGRESS, gesture_drag_status_);
+ bool horizontal = IsHorizontalAlignment();
+ const gfx::Rect& available_bounds(root_window_->bounds());
+ int resistance_free_region = 0;
+
+ if (gesture_drag_auto_hide_state_ == SHELF_AUTO_HIDE_HIDDEN &&
+ visibility_state() == SHELF_AUTO_HIDE &&
+ auto_hide_state() != SHELF_AUTO_HIDE_SHOWN) {
+ // If the shelf was hidden when the drag started (and the state hasn't
+ // changed since then, e.g. because the tray-menu was shown because of the
+ // drag), then allow the drag some resistance-free region at first to make
+ // sure the shelf sticks with the finger until the shelf is visible.
+ resistance_free_region = GetPreferredShelfSize() - kAutoHideSize;
+ }
+
+ bool resist = SelectValueForShelfAlignment(
+ gesture_drag_amount_ < -resistance_free_region,
+ gesture_drag_amount_ > resistance_free_region,
+ gesture_drag_amount_ < -resistance_free_region,
+ gesture_drag_amount_ > resistance_free_region);
+
+ float translate = 0.f;
+ if (resist) {
+ float diff = fabsf(gesture_drag_amount_) - resistance_free_region;
+ diff = std::min(diff, sqrtf(diff));
+ if (gesture_drag_amount_ < 0)
+ translate = -resistance_free_region - diff;
+ else
+ translate = resistance_free_region + diff;
+ } else {
+ translate = gesture_drag_amount_;
+ }
+
+ if (horizontal) {
+ // Move and size the launcher with the gesture.
+ int shelf_height = target_bounds->shelf_bounds_in_root.height() - translate;
+ shelf_height = std::max(shelf_height, kAutoHideSize);
+ target_bounds->shelf_bounds_in_root.set_height(shelf_height);
+ if (alignment_ == SHELF_ALIGNMENT_BOTTOM) {
+ target_bounds->shelf_bounds_in_root.set_y(
+ available_bounds.bottom() - shelf_height);
+ }
+
+ if (ash::switches::UseAlternateShelfLayout()) {
+ target_bounds->status_bounds_in_shelf.set_y(kStatusAreaInset);
+ } else {
+ // The statusbar should be in the center of the shelf.
+ gfx::Rect status_y = target_bounds->shelf_bounds_in_root;
+ status_y.set_y(0);
+ status_y.ClampToCenteredSize(
+ target_bounds->status_bounds_in_shelf.size());
+ target_bounds->status_bounds_in_shelf.set_y(status_y.y());
+ }
+ } else {
+ // Move and size the launcher with the gesture.
+ int shelf_width = target_bounds->shelf_bounds_in_root.width();
+ if (alignment_ == SHELF_ALIGNMENT_RIGHT)
+ shelf_width -= translate;
+ else
+ shelf_width += translate;
+ shelf_width = std::max(shelf_width, kAutoHideSize);
+ target_bounds->shelf_bounds_in_root.set_width(shelf_width);
+ if (alignment_ == SHELF_ALIGNMENT_RIGHT) {
+ target_bounds->shelf_bounds_in_root.set_x(
+ available_bounds.right() - shelf_width);
+ }
+
+ if (ash::switches::UseAlternateShelfLayout()) {
+ if (alignment_ == SHELF_ALIGNMENT_RIGHT) {
+ target_bounds->shelf_bounds_in_root.set_x(
+ available_bounds.right() - shelf_width + kStatusAreaInset);
+ } else {
+ target_bounds->shelf_bounds_in_root.set_x(kStatusAreaInset);
+ }
+ } else {
+ // The statusbar should be in the center of the shelf.
+ gfx::Rect status_x = target_bounds->shelf_bounds_in_root;
+ status_x.set_x(0);
+ status_x.ClampToCenteredSize(
+ target_bounds->status_bounds_in_shelf.size());
+ target_bounds->status_bounds_in_shelf.set_x(status_x.x());
+ }
+ }
+}
+
+void ShelfLayoutManager::UpdateShelfBackground(
+ BackgroundAnimator::ChangeType type) {
+ shelf_->SetPaintsBackground(GetShelfBackgroundType(), type);
+}
+
+ShelfBackgroundType ShelfLayoutManager::GetShelfBackgroundType() const {
+ if (state_.visibility_state != SHELF_AUTO_HIDE &&
+ state_.window_state == WORKSPACE_WINDOW_STATE_MAXIMIZED) {
+ return SHELF_BACKGROUND_MAXIMIZED;
+ }
+
+ if (gesture_drag_status_ == GESTURE_DRAG_IN_PROGRESS ||
+ (!state_.is_screen_locked && window_overlaps_shelf_) ||
+ (state_.visibility_state == SHELF_AUTO_HIDE)) {
+ return SHELF_BACKGROUND_OVERLAP;
+ }
+
+ return SHELF_BACKGROUND_DEFAULT;
+}
+
+void ShelfLayoutManager::UpdateAutoHideStateNow() {
+ SetState(state_.visibility_state);
+
+ // If the state did not change, the auto hide timer may still be running.
+ StopAutoHideTimer();
+}
+
+void ShelfLayoutManager::StopAutoHideTimer() {
+ auto_hide_timer_.Stop();
+ mouse_over_shelf_when_auto_hide_timer_started_ = false;
+}
+
+gfx::Rect ShelfLayoutManager::GetAutoHideShowShelfRegionInScreen() const {
+ gfx::Rect shelf_bounds_in_screen = shelf_->GetWindowBoundsInScreen();
+ gfx::Vector2d offset = SelectValueForShelfAlignment(
+ gfx::Vector2d(0, shelf_bounds_in_screen.height()),
+ gfx::Vector2d(-kMaxAutoHideShowShelfRegionSize, 0),
+ gfx::Vector2d(shelf_bounds_in_screen.width(), 0),
+ gfx::Vector2d(0, -kMaxAutoHideShowShelfRegionSize));
+
+ gfx::Rect show_shelf_region_in_screen = shelf_bounds_in_screen;
+ show_shelf_region_in_screen += offset;
+ if (IsHorizontalAlignment())
+ show_shelf_region_in_screen.set_height(kMaxAutoHideShowShelfRegionSize);
+ else
+ show_shelf_region_in_screen.set_width(kMaxAutoHideShowShelfRegionSize);
+
+ // TODO: Figure out if we need any special handling when the keyboard is
+ // visible.
+ return show_shelf_region_in_screen;
+}
+
+ShelfAutoHideState ShelfLayoutManager::CalculateAutoHideState(
+ ShelfVisibilityState visibility_state) const {
+ if (visibility_state != SHELF_AUTO_HIDE || !shelf_)
+ return SHELF_AUTO_HIDE_HIDDEN;
+
+ Shell* shell = Shell::GetInstance();
+ if (shell->GetAppListTargetVisibility())
+ return SHELF_AUTO_HIDE_SHOWN;
+
+ if (shelf_->status_area_widget() &&
+ shelf_->status_area_widget()->ShouldShowLauncher())
+ return SHELF_AUTO_HIDE_SHOWN;
+
+ if (shelf_->launcher() && shelf_->launcher()->IsShowingMenu())
+ return SHELF_AUTO_HIDE_SHOWN;
+
+ if (shelf_->launcher() && shelf_->launcher()->IsShowingOverflowBubble())
+ return SHELF_AUTO_HIDE_SHOWN;
+
+ if (shelf_->IsActive() || shelf_->status_area_widget()->IsActive())
+ return SHELF_AUTO_HIDE_SHOWN;
+
+ const std::vector<aura::Window*> windows =
+ ash::MruWindowTracker::BuildWindowList(false);
+
+ // Process the window list and check if there are any visible windows.
+ bool visible_window = false;
+ for (size_t i = 0; i < windows.size(); ++i) {
+ if (windows[i] && windows[i]->IsVisible() &&
+ !ash::wm::IsWindowMinimized(windows[i]) &&
+ root_window_ == windows[i]->GetRootWindow()) {
+ visible_window = true;
+ break;
+ }
+ }
+ // If there are no visible windows do not hide the shelf.
+ if (!visible_window)
+ return SHELF_AUTO_HIDE_SHOWN;
+
+ if (gesture_drag_status_ == GESTURE_DRAG_COMPLETE_IN_PROGRESS)
+ return gesture_drag_auto_hide_state_;
+
+ // Don't show if the user is dragging the mouse.
+ if (auto_hide_event_filter_.get() && auto_hide_event_filter_->in_mouse_drag())
+ return SHELF_AUTO_HIDE_HIDDEN;
+
+ // Ignore the mouse position if mouse events are disabled.
+ aura::client::CursorClient* cursor_client = aura::client::GetCursorClient(
+ shelf_->GetNativeWindow()->GetRootWindow());
+ if (!cursor_client->IsMouseEventsEnabled())
+ return SHELF_AUTO_HIDE_HIDDEN;
+
+ gfx::Rect shelf_region = shelf_->GetWindowBoundsInScreen();
+ if (shelf_->status_area_widget() &&
+ shelf_->status_area_widget()->IsMessageBubbleShown() &&
+ IsVisible()) {
+ // Increase the the hit test area to prevent the shelf from disappearing
+ // when the mouse is over the bubble gap.
+ shelf_region.Inset(alignment_ == SHELF_ALIGNMENT_RIGHT ?
+ -kNotificationBubbleGapHeight : 0,
+ alignment_ == SHELF_ALIGNMENT_BOTTOM ?
+ -kNotificationBubbleGapHeight : 0,
+ alignment_ == SHELF_ALIGNMENT_LEFT ?
+ -kNotificationBubbleGapHeight : 0,
+ alignment_ == SHELF_ALIGNMENT_TOP ?
+ -kNotificationBubbleGapHeight : 0);
+ }
+
+ gfx::Point cursor_position_in_screen =
+ Shell::GetScreen()->GetCursorScreenPoint();
+ if (shelf_region.Contains(cursor_position_in_screen))
+ return SHELF_AUTO_HIDE_SHOWN;
+
+ // When the shelf is auto hidden and the shelf is on the boundary between two
+ // displays, it is hard to trigger showing the shelf. For instance, if a
+ // user's primary display is left of their secondary display, it is hard to
+ // unautohide a left aligned shelf on the secondary display.
+ // It is hard because:
+ // - It is hard to stop the cursor in the shelf "light bar" and not overshoot.
+ // - The cursor is warped to the other display if the cursor gets to the edge
+ // of the display.
+ // Show the shelf if the cursor started on the shelf and the user overshot the
+ // shelf slightly to make it easier to show the shelf in this situation. We
+ // do not check |auto_hide_timer_|.IsRunning() because it returns false when
+ // the timer's task is running.
+ if ((state_.auto_hide_state == SHELF_AUTO_HIDE_SHOWN ||
+ mouse_over_shelf_when_auto_hide_timer_started_) &&
+ GetAutoHideShowShelfRegionInScreen().Contains(
+ cursor_position_in_screen)) {
+ return SHELF_AUTO_HIDE_SHOWN;
+ }
+
+ return SHELF_AUTO_HIDE_HIDDEN;
+}
+
+void ShelfLayoutManager::UpdateHitTestBounds() {
+ gfx::Insets mouse_insets;
+ gfx::Insets touch_insets;
+ if (state_.visibility_state == SHELF_VISIBLE) {
+ // Let clicks at the very top of the launcher through so windows can be
+ // resized with the bottom-right corner and bottom edge.
+ mouse_insets = GetInsetsForAlignment(kWorkspaceAreaVisibleInset);
+ } else if (state_.visibility_state == SHELF_AUTO_HIDE) {
+ // Extend the touch hit target out a bit to allow users to drag shelf out
+ // while hidden.
+ touch_insets = GetInsetsForAlignment(-kWorkspaceAreaAutoHideInset);
+ }
+
+ if (shelf_ && shelf_->GetNativeWindow())
+ shelf_->GetNativeWindow()->SetHitTestBoundsOverrideOuter(mouse_insets,
+ touch_insets);
+ shelf_->status_area_widget()->GetNativeWindow()->
+ SetHitTestBoundsOverrideOuter(mouse_insets, touch_insets);
+}
+
+bool ShelfLayoutManager::IsShelfWindow(aura::Window* window) {
+ if (!window)
+ return false;
+ return (shelf_ && shelf_->GetNativeWindow()->Contains(window)) ||
+ (shelf_->status_area_widget() &&
+ shelf_->status_area_widget()->GetNativeWindow()->Contains(window));
+}
+
+int ShelfLayoutManager::GetWorkAreaSize(const State& state, int size) const {
+ if (state.visibility_state == SHELF_VISIBLE)
+ return size;
+ if (state.visibility_state == SHELF_AUTO_HIDE)
+ return kAutoHideSize;
+ return 0;
+}
+
+gfx::Rect ShelfLayoutManager::GetAvailableBounds() const {
+ gfx::Rect bounds(root_window_->bounds());
+ bounds.set_height(bounds.height() - keyboard_bounds_.height());
+ return bounds;
+}
+
+void ShelfLayoutManager::OnKeyboardBoundsChanging(
+ const gfx::Rect& keyboard_bounds) {
+ keyboard_bounds_ = keyboard_bounds;
+ OnWindowResized();
+}
+
+void ShelfLayoutManager::OnDockBoundsChanging(
+ const gfx::Rect& dock_bounds) {
+ if (dock_bounds_ != dock_bounds) {
+ dock_bounds_ = dock_bounds;
+ OnWindowResized();
+ }
+}
+
+gfx::Insets ShelfLayoutManager::GetInsetsForAlignment(int distance) const {
+ switch (alignment_) {
+ case SHELF_ALIGNMENT_BOTTOM:
+ return gfx::Insets(distance, 0, 0, 0);
+ case SHELF_ALIGNMENT_LEFT:
+ return gfx::Insets(0, 0, 0, distance);
+ case SHELF_ALIGNMENT_RIGHT:
+ return gfx::Insets(0, distance, 0, 0);
+ case SHELF_ALIGNMENT_TOP:
+ return gfx::Insets(0, 0, distance, 0);
+ }
+ NOTREACHED();
+ return gfx::Insets();
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/shelf/shelf_layout_manager.h b/chromium/ash/shelf/shelf_layout_manager.h
new file mode 100644
index 00000000000..56d7032f3db
--- /dev/null
+++ b/chromium/ash/shelf/shelf_layout_manager.h
@@ -0,0 +1,401 @@
+// Copyright (c) 2012 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 ASH_SHELF_SHELF_LAYOUT_MANAGER_H_
+#define ASH_SHELF_SHELF_LAYOUT_MANAGER_H_
+
+#include <vector>
+
+#include "ash/ash_export.h"
+#include "ash/launcher/launcher.h"
+#include "ash/shelf/background_animator.h"
+#include "ash/shelf/shelf_types.h"
+#include "ash/shell_observer.h"
+#include "ash/system/status_area_widget.h"
+#include "ash/wm/dock/docked_window_layout_manager_observer.h"
+#include "ash/wm/workspace/workspace_types.h"
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/logging.h"
+#include "base/observer_list.h"
+#include "base/timer/timer.h"
+#include "ui/aura/client/activation_change_observer.h"
+#include "ui/aura/layout_manager.h"
+#include "ui/gfx/insets.h"
+#include "ui/gfx/rect.h"
+#include "ui/keyboard/keyboard_controller.h"
+#include "ui/keyboard/keyboard_controller_observer.h"
+
+namespace aura {
+class RootWindow;
+}
+
+namespace ui {
+class GestureEvent;
+class ImplicitAnimationObserver;
+}
+
+namespace ash {
+class ScreenAsh;
+class ShelfLayoutManagerObserver;
+class ShelfWidget;
+FORWARD_DECLARE_TEST(WebNotificationTrayTest, PopupAndFullscreen);
+
+namespace internal {
+
+class PanelLayoutManagerTest;
+class ShelfBezelEventFilter;
+class ShelfLayoutManagerTest;
+class StatusAreaWidget;
+class WorkspaceController;
+
+// ShelfLayoutManager is the layout manager responsible for the launcher and
+// status widgets. The launcher is given the total available width and told the
+// width of the status area. This allows the launcher to draw the background and
+// layout to the status area.
+// To respond to bounds changes in the status area StatusAreaLayoutManager works
+// closely with ShelfLayoutManager.
+class ASH_EXPORT ShelfLayoutManager :
+ public aura::LayoutManager,
+ public ash::ShellObserver,
+ public aura::client::ActivationChangeObserver,
+ public DockedWindowLayoutManagerObserver,
+ public keyboard::KeyboardControllerObserver {
+ public:
+
+ // We reserve a small area on the edge of the workspace area to ensure that
+ // the resize handle at the edge of the window can be hit.
+ static const int kWorkspaceAreaVisibleInset;
+
+ // When autohidden we extend the touch hit target onto the screen so that the
+ // user can drag the shelf out.
+ static const int kWorkspaceAreaAutoHideInset;
+
+ // Size of the shelf when auto-hidden.
+ static const int kAutoHideSize;
+
+ // The size of the shelf when shown (currently only used in alternate
+ // settings see ash::switches::UseAlternateShelfLayout).
+ static const int kShelfSize;
+
+ // Returns the preferred size for the shelf (either kLauncherPreferredSize or
+ // kShelfSize).
+ static int GetPreferredShelfSize();
+
+ explicit ShelfLayoutManager(ShelfWidget* shelf);
+ virtual ~ShelfLayoutManager();
+
+ // Sets the ShelfAutoHideBehavior. See enum description for details.
+ void SetAutoHideBehavior(ShelfAutoHideBehavior behavior);
+ ShelfAutoHideBehavior auto_hide_behavior() const {
+ return auto_hide_behavior_;
+ }
+
+ // Sets the alignment. Returns true if the alignment is changed. Otherwise,
+ // returns false.
+ bool SetAlignment(ShelfAlignment alignment);
+ ShelfAlignment GetAlignment() const { return alignment_; }
+
+ void set_workspace_controller(WorkspaceController* controller) {
+ workspace_controller_ = controller;
+ }
+
+ bool updating_bounds() const { return updating_bounds_; }
+
+ // Clears internal data for shutdown process.
+ void PrepareForShutdown();
+
+ // Returns whether the shelf and its contents (launcher, status) are visible
+ // on the screen.
+ bool IsVisible() const;
+
+ // Returns the ideal bounds of the shelf assuming it is visible.
+ gfx::Rect GetIdealBounds();
+
+ // Stops any animations and sets the bounds of the launcher and status
+ // widgets.
+ void LayoutShelf();
+
+ // Returns shelf visibility state based on current value of auto hide
+ // behavior setting.
+ ShelfVisibilityState CalculateShelfVisibility();
+
+ // Updates the visibility state.
+ void UpdateVisibilityState();
+
+ // Invoked by the shelf/launcher when the auto-hide state may have changed.
+ void UpdateAutoHideState();
+
+ ShelfVisibilityState visibility_state() const {
+ return state_.visibility_state;
+ }
+ ShelfAutoHideState auto_hide_state() const { return state_.auto_hide_state; }
+
+ ShelfWidget* shelf_widget() { return shelf_; }
+
+ // Sets whether any windows overlap the shelf. If a window overlaps the shelf
+ // the shelf renders slightly differently.
+ void SetWindowOverlapsShelf(bool value);
+ bool window_overlaps_shelf() const { return window_overlaps_shelf_; }
+
+ void AddObserver(ShelfLayoutManagerObserver* observer);
+ void RemoveObserver(ShelfLayoutManagerObserver* observer);
+
+ // Gesture dragging related functions:
+ void StartGestureDrag(const ui::GestureEvent& gesture);
+ enum DragState {
+ DRAG_SHELF,
+ DRAG_TRAY
+ };
+ // Returns DRAG_SHELF if the gesture should continue to drag the entire shelf.
+ // Returns DRAG_TRAY if the gesture can start dragging the tray-bubble from
+ // this point on.
+ DragState UpdateGestureDrag(const ui::GestureEvent& gesture);
+ void CompleteGestureDrag(const ui::GestureEvent& gesture);
+ void CancelGestureDrag();
+
+ // Overridden from aura::LayoutManager:
+ virtual void OnWindowResized() OVERRIDE;
+ virtual void OnWindowAddedToLayout(aura::Window* child) OVERRIDE;
+ virtual void OnWillRemoveWindowFromLayout(aura::Window* child) OVERRIDE;
+ virtual void OnWindowRemovedFromLayout(aura::Window* child) OVERRIDE;
+ virtual void OnChildWindowVisibilityChanged(aura::Window* child,
+ bool visible) OVERRIDE;
+ virtual void SetChildBounds(aura::Window* child,
+ const gfx::Rect& requested_bounds) OVERRIDE;
+
+ // Overridden from ash::ShellObserver:
+ virtual void OnLockStateChanged(bool locked) OVERRIDE;
+
+ // Overriden from aura::client::ActivationChangeObserver:
+ virtual void OnWindowActivated(aura::Window* gained_active,
+ aura::Window* lost_active) OVERRIDE;
+
+ // TODO(harrym|oshima): These templates will be moved to
+ // new Shelf class.
+ // A helper function that provides a shortcut for choosing
+ // values specific to a shelf alignment.
+ template<typename T>
+ T SelectValueForShelfAlignment(T bottom, T left, T right, T top) const {
+ switch (alignment_) {
+ case SHELF_ALIGNMENT_BOTTOM:
+ return bottom;
+ case SHELF_ALIGNMENT_LEFT:
+ return left;
+ case SHELF_ALIGNMENT_RIGHT:
+ return right;
+ case SHELF_ALIGNMENT_TOP:
+ return top;
+ }
+ NOTREACHED();
+ return right;
+ }
+
+ template<typename T>
+ T PrimaryAxisValue(T horizontal, T vertical) const {
+ return IsHorizontalAlignment() ? horizontal : vertical;
+ }
+
+ // Is the shelf's alignment horizontal?
+ bool IsHorizontalAlignment() const;
+
+ // Tests if the browser is currently in fullscreen mode with minimal
+ // Chrome. When minimal Chrome is present the shelf should be displayed.
+ bool FullscreenWithMinimalChrome() const;
+
+ // Returns a ShelfLayoutManager on the display which has a launcher for
+ // given |window|. See RootWindowController::ForLauncher for more info.
+ static ShelfLayoutManager* ForLauncher(aura::Window* window);
+
+ private:
+ class AutoHideEventFilter;
+ class UpdateShelfObserver;
+ friend class ash::ScreenAsh;
+ friend class PanelLayoutManagerTest;
+ friend class ShelfLayoutManagerTest;
+ FRIEND_TEST_ALL_PREFIXES(ash::WebNotificationTrayTest, PopupAndFullscreen);
+
+ struct TargetBounds {
+ TargetBounds();
+ ~TargetBounds();
+
+ float opacity;
+ float status_opacity;
+ gfx::Rect shelf_bounds_in_root;
+ gfx::Rect launcher_bounds_in_shelf;
+ gfx::Rect status_bounds_in_shelf;
+ gfx::Insets work_area_insets;
+ };
+
+ struct State {
+ State() : visibility_state(SHELF_VISIBLE),
+ auto_hide_state(SHELF_AUTO_HIDE_HIDDEN),
+ window_state(WORKSPACE_WINDOW_STATE_DEFAULT),
+ is_screen_locked(false) {}
+
+ // Returns true if the two states are considered equal. As
+ // |auto_hide_state| only matters if |visibility_state| is
+ // |SHELF_AUTO_HIDE|, Equals() ignores the |auto_hide_state| as
+ // appropriate.
+ bool Equals(const State& other) const {
+ return other.visibility_state == visibility_state &&
+ (visibility_state != SHELF_AUTO_HIDE ||
+ other.auto_hide_state == auto_hide_state) &&
+ other.window_state == window_state &&
+ other.is_screen_locked == is_screen_locked;
+ }
+
+ ShelfVisibilityState visibility_state;
+ ShelfAutoHideState auto_hide_state;
+ WorkspaceWindowState window_state;
+ bool is_screen_locked;
+ };
+
+ // Sets the visibility of the shelf to |state|.
+ void SetState(ShelfVisibilityState visibility_state);
+
+ // Updates the bounds and opacity of the launcher and status widgets.
+ // If |observer| is specified, it will be called back when the animations, if
+ // any, are complete.
+ void UpdateBoundsAndOpacity(const TargetBounds& target_bounds,
+ bool animate,
+ ui::ImplicitAnimationObserver* observer);
+
+ // Stops any animations and progresses them to the end.
+ void StopAnimating();
+
+ // Returns the width (if aligned to the side) or height (if aligned to the
+ // bottom).
+ void GetShelfSize(int* width, int* height);
+
+ // Insets |bounds| by |inset| on the edge the shelf is aligned to.
+ void AdjustBoundsBasedOnAlignment(int inset, gfx::Rect* bounds) const;
+
+ // Calculates the target bounds assuming visibility of |visible|.
+ void CalculateTargetBounds(const State& state, TargetBounds* target_bounds);
+
+ // Updates the target bounds if a gesture-drag is in progress. This is only
+ // used by |CalculateTargetBounds()|.
+ void UpdateTargetBoundsForGesture(TargetBounds* target_bounds) const;
+
+ // Updates the background of the shelf.
+ void UpdateShelfBackground(BackgroundAnimator::ChangeType type);
+
+ // Returns how the shelf background is painted.
+ ShelfBackgroundType GetShelfBackgroundType() const;
+
+ // Updates the auto hide state immediately.
+ void UpdateAutoHideStateNow();
+
+ // Stops the auto hide timer and clears
+ // |mouse_over_shelf_when_auto_hide_timer_started_|.
+ void StopAutoHideTimer();
+
+ // Returns the bounds of an additional region which can trigger showing the
+ // shelf. This region exists to make it easier to trigger showing the shelf
+ // when the shelf is auto hidden and the shelf is on the boundary between
+ // two displays.
+ gfx::Rect GetAutoHideShowShelfRegionInScreen() const;
+
+ // Returns the AutoHideState. This value is determined from the launcher and
+ // tray.
+ ShelfAutoHideState CalculateAutoHideState(
+ ShelfVisibilityState visibility_state) const;
+
+ // Updates the hit test bounds override for launcher and status area.
+ void UpdateHitTestBounds();
+
+ // Returns true if |window| is a descendant of the shelf.
+ bool IsShelfWindow(aura::Window* window);
+
+ int GetWorkAreaSize(const State& state, int size) const;
+
+ // Return the bounds available in the parent, taking into account the bounds
+ // of the keyboard if necessary.
+ gfx::Rect GetAvailableBounds() const;
+
+ // Overridden from keyboard::KeyboardControllerObserver:
+ virtual void OnKeyboardBoundsChanging(
+ const gfx::Rect& keyboard_bounds) OVERRIDE;
+
+ // Overridden from dock::DockObserver:
+ virtual void OnDockBoundsChanging(const gfx::Rect& dock_bounds) OVERRIDE;
+
+ // Generates insets for inward edge based on the current shelf alignment.
+ gfx::Insets GetInsetsForAlignment(int distance) const;
+
+ // The RootWindow is cached so that we don't invoke Shell::GetInstance() from
+ // our destructor. We avoid that as at the time we're deleted Shell is being
+ // deleted too.
+ aura::RootWindow* root_window_;
+
+ // True when inside UpdateBoundsAndOpacity() method. Used to prevent calling
+ // UpdateBoundsAndOpacity() again from SetChildBounds().
+ bool updating_bounds_;
+
+ // See description above setter.
+ ShelfAutoHideBehavior auto_hide_behavior_;
+
+ ShelfAlignment alignment_;
+
+ // Current state.
+ State state_;
+
+ ShelfWidget* shelf_;
+
+ WorkspaceController* workspace_controller_;
+
+ // Do any windows overlap the shelf? This is maintained by WorkspaceManager.
+ bool window_overlaps_shelf_;
+
+ base::OneShotTimer<ShelfLayoutManager> auto_hide_timer_;
+
+ // Whether the mouse was over the shelf when the auto hide timer started.
+ // False when neither the auto hide timer nor the timer task are running.
+ bool mouse_over_shelf_when_auto_hide_timer_started_;
+
+ // EventFilter used to detect when user moves the mouse over the launcher to
+ // trigger showing the launcher.
+ scoped_ptr<AutoHideEventFilter> auto_hide_event_filter_;
+
+ // EventFilter used to detect when user issues a gesture on a bezel sensor.
+ scoped_ptr<ShelfBezelEventFilter> bezel_event_filter_;
+
+ ObserverList<ShelfLayoutManagerObserver> observers_;
+
+ // The shelf reacts to gesture-drags, and can be set to auto-hide for certain
+ // gestures. Some shelf behaviour (e.g. visibility state, background color
+ // etc.) are affected by various stages of the drag. The enum keeps track of
+ // the present status of the gesture drag.
+ enum GestureDragStatus {
+ GESTURE_DRAG_NONE,
+ GESTURE_DRAG_IN_PROGRESS,
+ GESTURE_DRAG_CANCEL_IN_PROGRESS,
+ GESTURE_DRAG_COMPLETE_IN_PROGRESS
+ };
+ GestureDragStatus gesture_drag_status_;
+
+ // Tracks the amount of the drag. The value is only valid when
+ // |gesture_drag_status_| is set to GESTURE_DRAG_IN_PROGRESS.
+ float gesture_drag_amount_;
+
+ // Manage the auto-hide state during the gesture.
+ ShelfAutoHideState gesture_drag_auto_hide_state_;
+
+ // Used to delay updating shelf background.
+ UpdateShelfObserver* update_shelf_observer_;
+
+ // The bounds of the keyboard.
+ gfx::Rect keyboard_bounds_;
+
+ // The bounds of the dock.
+ gfx::Rect dock_bounds_;
+
+ DISALLOW_COPY_AND_ASSIGN(ShelfLayoutManager);
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_SHELF_SHELF_LAYOUT_MANAGER_H_
diff --git a/chromium/ash/shelf/shelf_layout_manager_observer.h b/chromium/ash/shelf/shelf_layout_manager_observer.h
new file mode 100644
index 00000000000..328e2095d48
--- /dev/null
+++ b/chromium/ash/shelf/shelf_layout_manager_observer.h
@@ -0,0 +1,37 @@
+// Copyright 2013 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 ASH_SHELF_SHELF_LAYOUT_MANAGER_OBSERVER_H_
+#define ASH_SHELF_SHELF_LAYOUT_MANAGER_OBSERVER_H_
+
+#include "ash/ash_export.h"
+#include "ash/shelf/shelf_types.h"
+
+namespace aura {
+class RootWindow;
+}
+
+namespace ash {
+
+class ASH_EXPORT ShelfLayoutManagerObserver {
+ public:
+ virtual ~ShelfLayoutManagerObserver() {}
+
+ // Called when the target ShelfLayoutManager will be deleted.
+ virtual void WillDeleteShelf() {}
+
+ // Called when the visibility change is scheduled.
+ virtual void WillChangeVisibilityState(ShelfVisibilityState new_state) {}
+
+ // Called when the auto hide state is changed.
+ virtual void OnAutoHideStateChanged(ShelfAutoHideState new_state) {}
+
+ // Called when the auto hide behavior is changed.
+ virtual void OnAutoHideBehaviorChanged(aura::RootWindow* root_window,
+ ShelfAutoHideBehavior new_behavior) {}
+};
+
+} // namespace ash
+
+#endif // ASH_SHELF_SHELF_LAYOUT_MANAGER_OBSERVER_H_
diff --git a/chromium/ash/shelf/shelf_layout_manager_unittest.cc b/chromium/ash/shelf/shelf_layout_manager_unittest.cc
new file mode 100644
index 00000000000..e44fe347a68
--- /dev/null
+++ b/chromium/ash/shelf/shelf_layout_manager_unittest.cc
@@ -0,0 +1,1833 @@
+// Copyright (c) 2012 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.
+
+#include "ash/shelf/shelf_layout_manager.h"
+
+#include "ash/accelerators/accelerator_controller.h"
+#include "ash/accelerators/accelerator_table.h"
+#include "ash/ash_switches.h"
+#include "ash/display/display_controller.h"
+#include "ash/display/display_manager.h"
+#include "ash/focus_cycler.h"
+#include "ash/launcher/launcher.h"
+#include "ash/launcher/launcher_view.h"
+#include "ash/root_window_controller.h"
+#include "ash/screen_ash.h"
+#include "ash/session_state_delegate.h"
+#include "ash/shelf/shelf_layout_manager_observer.h"
+#include "ash/shelf/shelf_widget.h"
+#include "ash/shell.h"
+#include "ash/shell_window_ids.h"
+#include "ash/system/status_area_widget.h"
+#include "ash/system/tray/system_tray.h"
+#include "ash/system/tray/system_tray_item.h"
+#include "ash/test/ash_test_base.h"
+#include "ash/wm/window_properties.h"
+#include "ash/wm/window_util.h"
+#include "base/command_line.h"
+#include "base/strings/utf_string_conversions.h"
+#include "ui/aura/client/aura_constants.h"
+#include "ui/aura/root_window.h"
+#include "ui/aura/test/event_generator.h"
+#include "ui/aura/window.h"
+#include "ui/base/animation/animation_container_element.h"
+#include "ui/compositor/layer.h"
+#include "ui/compositor/layer_animator.h"
+#include "ui/compositor/scoped_animation_duration_scale_mode.h"
+#include "ui/gfx/display.h"
+#include "ui/gfx/screen.h"
+#include "ui/views/controls/label.h"
+#include "ui/views/layout/fill_layout.h"
+#include "ui/views/view.h"
+#include "ui/views/widget/widget.h"
+
+#if defined(OS_WIN)
+#include "base/win/windows_version.h"
+#endif
+
+namespace ash {
+namespace internal {
+
+namespace {
+
+void StepWidgetLayerAnimatorToEnd(views::Widget* widget) {
+ ui::AnimationContainerElement* element =
+ static_cast<ui::AnimationContainerElement*>(
+ widget->GetNativeView()->layer()->GetAnimator());
+ element->Step(base::TimeTicks::Now() + base::TimeDelta::FromSeconds(1));
+}
+
+ShelfWidget* GetShelfWidget() {
+ return Shell::GetPrimaryRootWindowController()->shelf();
+}
+
+ShelfLayoutManager* GetShelfLayoutManager() {
+ return Shell::GetPrimaryRootWindowController()->GetShelfLayoutManager();
+}
+
+SystemTray* GetSystemTray() {
+ return Shell::GetPrimaryRootWindowController()->GetSystemTray();
+}
+
+// Class which waits till the shelf finishes animating to the target size and
+// counts the number of animation steps.
+class ShelfAnimationWaiter : views::WidgetObserver {
+ public:
+ explicit ShelfAnimationWaiter(const gfx::Rect& target_bounds)
+ : target_bounds_(target_bounds),
+ animation_steps_(0),
+ done_waiting_(false) {
+ GetShelfWidget()->AddObserver(this);
+ }
+
+ virtual ~ShelfAnimationWaiter() {
+ GetShelfWidget()->RemoveObserver(this);
+ }
+
+ // Wait till the shelf finishes animating to its expected bounds.
+ void WaitTillDoneAnimating() {
+ if (IsDoneAnimating())
+ done_waiting_ = true;
+ else
+ base::MessageLoop::current()->Run();
+ }
+
+ // Returns true if the animation has completed and it was valid.
+ bool WasValidAnimation() const {
+ return done_waiting_ && animation_steps_ > 0;
+ }
+
+ private:
+ // Returns true if shelf has finished animating to the target size.
+ bool IsDoneAnimating() const {
+ ShelfLayoutManager* layout_manager = GetShelfLayoutManager();
+ gfx::Rect current_bounds = GetShelfWidget()->GetWindowBoundsInScreen();
+ int size = layout_manager->PrimaryAxisValue(current_bounds.height(),
+ current_bounds.width());
+ int desired_size = layout_manager->PrimaryAxisValue(target_bounds_.height(),
+ target_bounds_.width());
+ return (size == desired_size);
+ }
+
+ // views::WidgetObserver override.
+ virtual void OnWidgetBoundsChanged(views::Widget* widget,
+ const gfx::Rect& new_bounds) OVERRIDE {
+ if (done_waiting_)
+ return;
+
+ ++animation_steps_;
+ if (IsDoneAnimating()) {
+ done_waiting_ = true;
+ base::MessageLoop::current()->Quit();
+ }
+ }
+
+ gfx::Rect target_bounds_;
+ int animation_steps_;
+ bool done_waiting_;
+
+ DISALLOW_COPY_AND_ASSIGN(ShelfAnimationWaiter);
+};
+
+class ShelfDragCallback {
+ public:
+ ShelfDragCallback(const gfx::Rect& not_visible, const gfx::Rect& visible)
+ : not_visible_bounds_(not_visible),
+ visible_bounds_(visible),
+ was_visible_on_drag_start_(false) {
+ EXPECT_EQ(not_visible_bounds_.bottom(), visible_bounds_.bottom());
+ }
+
+ virtual ~ShelfDragCallback() {
+ }
+
+ void ProcessScroll(ui::EventType type, const gfx::Vector2dF& delta) {
+ if (GetShelfLayoutManager()->visibility_state() == ash::SHELF_HIDDEN)
+ return;
+
+ if (type == ui::ET_GESTURE_SCROLL_BEGIN) {
+ scroll_ = gfx::Vector2dF();
+ was_visible_on_drag_start_ = GetShelfLayoutManager()->IsVisible();
+ return;
+ }
+
+ // The state of the shelf at the end of the gesture is tested separately.
+ if (type == ui::ET_GESTURE_SCROLL_END)
+ return;
+
+ if (type == ui::ET_GESTURE_SCROLL_UPDATE)
+ scroll_.Add(delta);
+
+ gfx::Rect shelf_bounds = GetShelfWidget()->GetWindowBoundsInScreen();
+ if (GetShelfLayoutManager()->IsHorizontalAlignment()) {
+ EXPECT_EQ(not_visible_bounds_.bottom(), shelf_bounds.bottom());
+ EXPECT_EQ(visible_bounds_.bottom(), shelf_bounds.bottom());
+ } else if (SHELF_ALIGNMENT_RIGHT ==
+ GetShelfLayoutManager()->GetAlignment()){
+ EXPECT_EQ(not_visible_bounds_.right(), shelf_bounds.right());
+ EXPECT_EQ(visible_bounds_.right(), shelf_bounds.right());
+ } else if (SHELF_ALIGNMENT_LEFT ==
+ GetShelfLayoutManager()->GetAlignment()) {
+ EXPECT_EQ(not_visible_bounds_.x(), shelf_bounds.x());
+ EXPECT_EQ(visible_bounds_.x(), shelf_bounds.x());
+ }
+
+ // if the shelf is being dimmed test dimmer bounds as well.
+ if (GetShelfWidget()->GetDimsShelf())
+ EXPECT_EQ(GetShelfWidget()->GetWindowBoundsInScreen(),
+ GetShelfWidget()->GetDimmerBoundsForTest());
+
+ // The shelf should never be smaller than the hidden state.
+ EXPECT_GE(shelf_bounds.height(), not_visible_bounds_.height());
+ float scroll_delta = GetShelfLayoutManager()->PrimaryAxisValue(
+ scroll_.y(),
+ scroll_.x());
+ bool increasing_drag =
+ GetShelfLayoutManager()->SelectValueForShelfAlignment(
+ scroll_delta < 0,
+ scroll_delta > 0,
+ scroll_delta < 0,
+ scroll_delta > 0);
+ int shelf_size = GetShelfLayoutManager()->PrimaryAxisValue(
+ shelf_bounds.height(),
+ shelf_bounds.width());
+ int visible_bounds_size = GetShelfLayoutManager()->PrimaryAxisValue(
+ visible_bounds_.height(),
+ visible_bounds_.width());
+ int not_visible_bounds_size = GetShelfLayoutManager()->PrimaryAxisValue(
+ not_visible_bounds_.height(),
+ not_visible_bounds_.width());
+ if (was_visible_on_drag_start_) {
+ if (increasing_drag) {
+ // If dragging inwards from the visible state, then the shelf should
+ // increase in size, but not more than the scroll delta.
+ EXPECT_LE(visible_bounds_size, shelf_size);
+ EXPECT_LE(abs(shelf_size - visible_bounds_size),
+ abs(scroll_delta));
+ } else {
+ if (shelf_size > not_visible_bounds_size) {
+ // If dragging outwards from the visible state, then the shelf
+ // should decrease in size, until it reaches the minimum size.
+ EXPECT_EQ(shelf_size, visible_bounds_size - abs(scroll_delta));
+ }
+ }
+ } else {
+ if (fabs(scroll_delta) <
+ visible_bounds_size - not_visible_bounds_size) {
+ // Tests that the shelf sticks with the touch point during the drag
+ // until the shelf is completely visible.
+ EXPECT_EQ(shelf_size, not_visible_bounds_size + abs(scroll_delta));
+ } else {
+ // Tests that after the shelf is completely visible, the shelf starts
+ // resisting the drag.
+ EXPECT_LT(shelf_size, not_visible_bounds_size + abs(scroll_delta));
+ }
+ }
+ }
+
+ private:
+ const gfx::Rect not_visible_bounds_;
+ const gfx::Rect visible_bounds_;
+ gfx::Vector2dF scroll_;
+ bool was_visible_on_drag_start_;
+
+ DISALLOW_COPY_AND_ASSIGN(ShelfDragCallback);
+};
+
+class ShelfLayoutObserverTest : public ShelfLayoutManagerObserver {
+ public:
+ ShelfLayoutObserverTest()
+ : changed_auto_hide_state_(false) {
+ }
+
+ virtual ~ShelfLayoutObserverTest() {}
+
+ bool changed_auto_hide_state() const { return changed_auto_hide_state_; }
+
+ private:
+ virtual void OnAutoHideStateChanged(
+ ShelfAutoHideState new_state) OVERRIDE {
+ changed_auto_hide_state_ = true;
+ }
+
+ bool changed_auto_hide_state_;
+
+ DISALLOW_COPY_AND_ASSIGN(ShelfLayoutObserverTest);
+};
+
+// Trivial item implementation that tracks its views for testing.
+class TestItem : public SystemTrayItem {
+ public:
+ TestItem()
+ : SystemTrayItem(GetSystemTray()),
+ tray_view_(NULL),
+ default_view_(NULL),
+ detailed_view_(NULL),
+ notification_view_(NULL) {}
+
+ virtual views::View* CreateTrayView(user::LoginStatus status) OVERRIDE {
+ tray_view_ = new views::View;
+ // Add a label so it has non-zero width.
+ tray_view_->SetLayoutManager(new views::FillLayout);
+ tray_view_->AddChildView(new views::Label(UTF8ToUTF16("Tray")));
+ return tray_view_;
+ }
+
+ virtual views::View* CreateDefaultView(user::LoginStatus status) OVERRIDE {
+ default_view_ = new views::View;
+ default_view_->SetLayoutManager(new views::FillLayout);
+ default_view_->AddChildView(new views::Label(UTF8ToUTF16("Default")));
+ return default_view_;
+ }
+
+ virtual views::View* CreateDetailedView(user::LoginStatus status) OVERRIDE {
+ detailed_view_ = new views::View;
+ detailed_view_->SetLayoutManager(new views::FillLayout);
+ detailed_view_->AddChildView(new views::Label(UTF8ToUTF16("Detailed")));
+ return detailed_view_;
+ }
+
+ virtual views::View* CreateNotificationView(
+ user::LoginStatus status) OVERRIDE {
+ notification_view_ = new views::View;
+ return notification_view_;
+ }
+
+ virtual void DestroyTrayView() OVERRIDE {
+ tray_view_ = NULL;
+ }
+
+ virtual void DestroyDefaultView() OVERRIDE {
+ default_view_ = NULL;
+ }
+
+ virtual void DestroyDetailedView() OVERRIDE {
+ detailed_view_ = NULL;
+ }
+
+ virtual void DestroyNotificationView() OVERRIDE {
+ notification_view_ = NULL;
+ }
+
+ virtual void UpdateAfterLoginStatusChange(
+ user::LoginStatus status) OVERRIDE {}
+
+ views::View* tray_view() const { return tray_view_; }
+ views::View* default_view() const { return default_view_; }
+ views::View* detailed_view() const { return detailed_view_; }
+ views::View* notification_view() const { return notification_view_; }
+
+ private:
+ views::View* tray_view_;
+ views::View* default_view_;
+ views::View* detailed_view_;
+ views::View* notification_view_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestItem);
+};
+
+} // namespace
+
+class ShelfLayoutManagerTest : public ash::test::AshTestBase {
+ public:
+ ShelfLayoutManagerTest() {}
+
+ void SetState(ShelfLayoutManager* shelf,
+ ShelfVisibilityState state) {
+ shelf->SetState(state);
+ }
+
+ void UpdateAutoHideStateNow() {
+ GetShelfLayoutManager()->UpdateAutoHideStateNow();
+ }
+
+ aura::Window* CreateTestWindow() {
+ aura::Window* window = new aura::Window(NULL);
+ window->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_NORMAL);
+ window->SetType(aura::client::WINDOW_TYPE_NORMAL);
+ window->Init(ui::LAYER_TEXTURED);
+ SetDefaultParentByPrimaryRootWindow(window);
+ return window;
+ }
+
+ views::Widget* CreateTestWidgetWithParams(
+ const views::Widget::InitParams& params) {
+ views::Widget* out = new views::Widget;
+ out->Init(params);
+ out->Show();
+ return out;
+ }
+
+ // Create a simple widget attached to the current context (will
+ // delete on TearDown).
+ views::Widget* CreateTestWidget() {
+ views::Widget::InitParams params(views::Widget::InitParams::TYPE_WINDOW);
+ params.bounds = gfx::Rect(0, 0, 200, 200);
+ params.context = CurrentContext();
+ return CreateTestWidgetWithParams(params);
+ }
+
+ // Overridden from AshTestBase:
+ virtual void SetUp() OVERRIDE {
+ CommandLine::ForCurrentProcess()->AppendSwitch(
+ ash::switches::kAshEnableTrayDragging);
+ test::AshTestBase::SetUp();
+ }
+
+ void RunGestureDragTests(gfx::Vector2d);
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ShelfLayoutManagerTest);
+};
+
+void ShelfLayoutManagerTest::RunGestureDragTests(gfx::Vector2d delta) {
+ ShelfLayoutManager* shelf = GetShelfLayoutManager();
+ shelf->SetAutoHideBehavior(SHELF_AUTO_HIDE_BEHAVIOR_NEVER);
+ views::Widget* widget = new views::Widget;
+ views::Widget::InitParams params(views::Widget::InitParams::TYPE_WINDOW);
+ params.bounds = gfx::Rect(0, 0, 200, 200);
+ params.context = CurrentContext();
+ widget->Init(params);
+ widget->Show();
+ widget->Maximize();
+
+ aura::Window* window = widget->GetNativeWindow();
+ shelf->LayoutShelf();
+
+ gfx::Rect shelf_shown = GetShelfWidget()->GetWindowBoundsInScreen();
+ gfx::Rect bounds_shelf = window->bounds();
+ EXPECT_EQ(SHELF_VISIBLE, shelf->visibility_state());
+
+ shelf->SetAutoHideBehavior(SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS);
+ shelf->LayoutShelf();
+ EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->auto_hide_state());
+
+ gfx::Rect bounds_noshelf = window->bounds();
+ gfx::Rect shelf_hidden = GetShelfWidget()->GetWindowBoundsInScreen();
+
+ shelf->SetAutoHideBehavior(SHELF_AUTO_HIDE_BEHAVIOR_NEVER);
+ shelf->LayoutShelf();
+
+ aura::test::EventGenerator generator(Shell::GetPrimaryRootWindow());
+ const int kNumScrollSteps = 10;
+ ShelfDragCallback handler(shelf_hidden, shelf_shown);
+
+ // Swipe up on the shelf. This should not change any state.
+ gfx::Point start = GetShelfWidget()->GetWindowBoundsInScreen().CenterPoint();
+ gfx::Point end = start + delta;
+
+ // Swipe down on the shelf to hide it.
+ generator.GestureScrollSequenceWithCallback(start, end,
+ base::TimeDelta::FromMilliseconds(10), kNumScrollSteps,
+ base::Bind(&ShelfDragCallback::ProcessScroll,
+ base::Unretained(&handler)));
+ EXPECT_EQ(SHELF_AUTO_HIDE, shelf->visibility_state());
+ EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->auto_hide_state());
+ EXPECT_EQ(SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS, shelf->auto_hide_behavior());
+ EXPECT_NE(bounds_shelf.ToString(), window->bounds().ToString());
+ EXPECT_NE(shelf_shown.ToString(),
+ GetShelfWidget()->GetWindowBoundsInScreen().ToString());
+
+ // Swipe up to show the shelf.
+ generator.GestureScrollSequenceWithCallback(end, start,
+ base::TimeDelta::FromMilliseconds(10), kNumScrollSteps,
+ base::Bind(&ShelfDragCallback::ProcessScroll,
+ base::Unretained(&handler)));
+ EXPECT_EQ(SHELF_VISIBLE, shelf->visibility_state());
+ EXPECT_EQ(SHELF_AUTO_HIDE_BEHAVIOR_NEVER, shelf->auto_hide_behavior());
+ EXPECT_EQ(bounds_shelf.ToString(), window->bounds().ToString());
+ EXPECT_EQ(GetShelfWidget()->GetDimmerBoundsForTest(),
+ GetShelfWidget()->GetWindowBoundsInScreen());
+ EXPECT_EQ(shelf_shown.ToString(),
+ GetShelfWidget()->GetWindowBoundsInScreen().ToString());
+
+ // Swipe up again. The shelf should hide.
+ end = start - delta;
+ generator.GestureScrollSequenceWithCallback(start, end,
+ base::TimeDelta::FromMilliseconds(10), kNumScrollSteps,
+ base::Bind(&ShelfDragCallback::ProcessScroll,
+ base::Unretained(&handler)));
+ EXPECT_EQ(SHELF_AUTO_HIDE, shelf->visibility_state());
+ EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->auto_hide_state());
+ EXPECT_EQ(SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS, shelf->auto_hide_behavior());
+ EXPECT_EQ(shelf_hidden.ToString(),
+ GetShelfWidget()->GetWindowBoundsInScreen().ToString());
+
+ // Swipe up yet again to show it.
+ end = start + delta;
+ generator.GestureScrollSequenceWithCallback(end, start,
+ base::TimeDelta::FromMilliseconds(10), kNumScrollSteps,
+ base::Bind(&ShelfDragCallback::ProcessScroll,
+ base::Unretained(&handler)));
+
+ // Swipe down very little. It shouldn't change any state.
+ if (GetShelfLayoutManager()->IsHorizontalAlignment())
+ end.set_y(start.y() + shelf_shown.height() * 3 / 10);
+ else if (SHELF_ALIGNMENT_LEFT == GetShelfLayoutManager()->GetAlignment())
+ end.set_x(start.x() - shelf_shown.width() * 3 / 10);
+ else if (SHELF_ALIGNMENT_RIGHT == GetShelfLayoutManager()->GetAlignment())
+ end.set_x(start.x() + shelf_shown.width() * 3 / 10);
+ generator.GestureScrollSequenceWithCallback(start, end,
+ base::TimeDelta::FromMilliseconds(100), 1,
+ base::Bind(&ShelfDragCallback::ProcessScroll,
+ base::Unretained(&handler)));
+ EXPECT_EQ(SHELF_VISIBLE, shelf->visibility_state());
+ EXPECT_EQ(SHELF_AUTO_HIDE_BEHAVIOR_NEVER, shelf->auto_hide_behavior());
+ EXPECT_EQ(bounds_shelf.ToString(), window->bounds().ToString());
+ EXPECT_EQ(shelf_shown.ToString(),
+ GetShelfWidget()->GetWindowBoundsInScreen().ToString());
+
+ // Swipe down again to hide.
+ end = start + delta;
+ generator.GestureScrollSequenceWithCallback(start, end,
+ base::TimeDelta::FromMilliseconds(10), kNumScrollSteps,
+ base::Bind(&ShelfDragCallback::ProcessScroll,
+ base::Unretained(&handler)));
+ EXPECT_EQ(SHELF_AUTO_HIDE, shelf->visibility_state());
+ EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->auto_hide_state());
+ EXPECT_EQ(SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS, shelf->auto_hide_behavior());
+ EXPECT_EQ(GetShelfWidget()->GetDimmerBoundsForTest(), gfx::Rect());
+ EXPECT_EQ(bounds_noshelf.ToString(), window->bounds().ToString());
+ EXPECT_EQ(shelf_hidden.ToString(),
+ GetShelfWidget()->GetWindowBoundsInScreen().ToString());
+
+ // Swipe up in extended hit region to show it.
+ gfx::Point extended_start = start;
+ if (GetShelfLayoutManager()->IsHorizontalAlignment())
+ extended_start.set_y(GetShelfWidget()->GetWindowBoundsInScreen().y() -1);
+ else if (SHELF_ALIGNMENT_LEFT == GetShelfLayoutManager()->GetAlignment())
+ extended_start.set_x(
+ GetShelfWidget()->GetWindowBoundsInScreen().right() + 1);
+ else if (SHELF_ALIGNMENT_RIGHT == GetShelfLayoutManager()->GetAlignment())
+ extended_start.set_x(GetShelfWidget()->GetWindowBoundsInScreen().x() - 1);
+ end = extended_start - delta;
+ generator.GestureScrollSequenceWithCallback(extended_start, end,
+ base::TimeDelta::FromMilliseconds(10), kNumScrollSteps,
+ base::Bind(&ShelfDragCallback::ProcessScroll,
+ base::Unretained(&handler)));
+ EXPECT_EQ(SHELF_VISIBLE, shelf->visibility_state());
+ EXPECT_EQ(SHELF_AUTO_HIDE_BEHAVIOR_NEVER, shelf->auto_hide_behavior());
+ EXPECT_EQ(bounds_shelf.ToString(), window->bounds().ToString());
+ EXPECT_EQ(GetShelfWidget()->GetDimmerBoundsForTest(),
+ GetShelfWidget()->GetWindowBoundsInScreen());
+ EXPECT_EQ(shelf_shown.ToString(),
+ GetShelfWidget()->GetWindowBoundsInScreen().ToString());
+
+ // Swipe down again to hide.
+ end = start + delta;
+ generator.GestureScrollSequenceWithCallback(start, end,
+ base::TimeDelta::FromMilliseconds(10), kNumScrollSteps,
+ base::Bind(&ShelfDragCallback::ProcessScroll,
+ base::Unretained(&handler)));
+ EXPECT_EQ(SHELF_AUTO_HIDE, shelf->visibility_state());
+ EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->auto_hide_state());
+ EXPECT_EQ(SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS, shelf->auto_hide_behavior());
+ EXPECT_EQ(GetShelfWidget()->GetDimmerBoundsForTest(), gfx::Rect());
+ EXPECT_EQ(bounds_noshelf.ToString(), window->bounds().ToString());
+ EXPECT_EQ(shelf_hidden.ToString(),
+ GetShelfWidget()->GetWindowBoundsInScreen().ToString());
+
+ // Swipe up outside the hit area. This should not change anything.
+ gfx::Point outside_start = gfx::Point(
+ (GetShelfWidget()->GetWindowBoundsInScreen().x() +
+ GetShelfWidget()->GetWindowBoundsInScreen().right())/2,
+ GetShelfWidget()->GetWindowBoundsInScreen().y() - 50);
+ end = outside_start + delta;
+ generator.GestureScrollSequence(outside_start,
+ end,
+ base::TimeDelta::FromMilliseconds(10),
+ kNumScrollSteps);
+ EXPECT_EQ(SHELF_AUTO_HIDE, shelf->visibility_state());
+ EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->auto_hide_state());
+ EXPECT_EQ(SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS, shelf->auto_hide_behavior());
+ EXPECT_EQ(shelf_hidden.ToString(),
+ GetShelfWidget()->GetWindowBoundsInScreen().ToString());
+
+ // Swipe up from below the shelf where a bezel would be, this should show the
+ // shelf.
+ gfx::Point below_start = start;
+ if (GetShelfLayoutManager()->IsHorizontalAlignment())
+ below_start.set_y(GetShelfWidget()->GetWindowBoundsInScreen().bottom() + 1);
+ else if (SHELF_ALIGNMENT_LEFT == GetShelfLayoutManager()->GetAlignment())
+ below_start.set_x(
+ GetShelfWidget()->GetWindowBoundsInScreen().x() - 1);
+ else if (SHELF_ALIGNMENT_RIGHT == GetShelfLayoutManager()->GetAlignment())
+ below_start.set_x(GetShelfWidget()->GetWindowBoundsInScreen().right() + 1);
+ end = below_start - delta;
+ generator.GestureScrollSequence(below_start,
+ end,
+ base::TimeDelta::FromMilliseconds(10),
+ kNumScrollSteps);
+ EXPECT_EQ(SHELF_VISIBLE, shelf->visibility_state());
+ EXPECT_EQ(SHELF_AUTO_HIDE_BEHAVIOR_NEVER, shelf->auto_hide_behavior());
+ EXPECT_EQ(bounds_shelf.ToString(), window->bounds().ToString());
+ EXPECT_EQ(GetShelfWidget()->GetDimmerBoundsForTest(),
+ GetShelfWidget()->GetWindowBoundsInScreen());
+ EXPECT_EQ(shelf_shown.ToString(),
+ GetShelfWidget()->GetWindowBoundsInScreen().ToString());
+
+ // Swipe down again to hide.
+ end = start + delta;
+ generator.GestureScrollSequenceWithCallback(start, end,
+ base::TimeDelta::FromMilliseconds(10), kNumScrollSteps,
+ base::Bind(&ShelfDragCallback::ProcessScroll,
+ base::Unretained(&handler)));
+ EXPECT_EQ(SHELF_AUTO_HIDE, shelf->visibility_state());
+ EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->auto_hide_state());
+ EXPECT_EQ(SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS, shelf->auto_hide_behavior());
+ EXPECT_EQ(GetShelfWidget()->GetDimmerBoundsForTest(), gfx::Rect());
+ EXPECT_EQ(bounds_noshelf.ToString(), window->bounds().ToString());
+ EXPECT_EQ(shelf_hidden.ToString(),
+ GetShelfWidget()->GetWindowBoundsInScreen().ToString());
+
+ // Enter into fullscreen with minimal chrome (immersive fullscreen).
+ widget->SetFullscreen(true);
+ window->SetProperty(ash::internal::kFullscreenUsesMinimalChromeKey, true);
+ shelf->UpdateVisibilityState();
+
+ gfx::Rect bounds_fullscreen = window->bounds();
+ EXPECT_TRUE(widget->IsFullscreen());
+ EXPECT_NE(bounds_noshelf.ToString(), bounds_fullscreen.ToString());
+
+ // Swipe up. This should show the shelf.
+ end = below_start - delta;
+ generator.GestureScrollSequenceWithCallback(below_start, end,
+ base::TimeDelta::FromMilliseconds(10), kNumScrollSteps,
+ base::Bind(&ShelfDragCallback::ProcessScroll,
+ base::Unretained(&handler)));
+ EXPECT_EQ(SHELF_AUTO_HIDE, shelf->visibility_state());
+ EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->auto_hide_state());
+ EXPECT_EQ(SHELF_AUTO_HIDE_BEHAVIOR_NEVER, shelf->auto_hide_behavior());
+ EXPECT_EQ(shelf_shown.ToString(),
+ GetShelfWidget()->GetWindowBoundsInScreen().ToString());
+ EXPECT_EQ(bounds_fullscreen.ToString(), window->bounds().ToString());
+
+ // Swipe up again. This should hide the shelf.
+ generator.GestureScrollSequenceWithCallback(below_start, end,
+ base::TimeDelta::FromMilliseconds(10), kNumScrollSteps,
+ base::Bind(&ShelfDragCallback::ProcessScroll,
+ base::Unretained(&handler)));
+ EXPECT_EQ(SHELF_AUTO_HIDE, shelf->visibility_state());
+ EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->auto_hide_state());
+ EXPECT_EQ(SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS, shelf->auto_hide_behavior());
+ EXPECT_EQ(shelf_hidden.ToString(),
+ GetShelfWidget()->GetWindowBoundsInScreen().ToString());
+ EXPECT_EQ(bounds_fullscreen.ToString(), window->bounds().ToString());
+
+ // Put the window into fullscreen without any chrome at all (eg tab
+ // fullscreen).
+ window->SetProperty(ash::internal::kFullscreenUsesMinimalChromeKey, false);
+ shelf->UpdateVisibilityState();
+ EXPECT_EQ(SHELF_HIDDEN, shelf->visibility_state());
+ EXPECT_EQ(SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS, shelf->auto_hide_behavior());
+
+ // Swipe-up. This should not change anything.
+ end = start - delta;
+ generator.GestureScrollSequenceWithCallback(below_start, end,
+ base::TimeDelta::FromMilliseconds(10), kNumScrollSteps,
+ base::Bind(&ShelfDragCallback::ProcessScroll,
+ base::Unretained(&handler)));
+ EXPECT_EQ(SHELF_HIDDEN, shelf->visibility_state());
+ EXPECT_EQ(SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS, shelf->auto_hide_behavior());
+ EXPECT_EQ(bounds_fullscreen.ToString(), window->bounds().ToString());
+
+ // Close actually, otherwise further event may be affected since widget
+ // is fullscreen status.
+ widget->Close();
+ RunAllPendingInMessageLoop();
+
+ // The shelf should be shown because there are no more visible windows.
+ EXPECT_EQ(SHELF_AUTO_HIDE, shelf->visibility_state());
+ EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->auto_hide_state());
+ EXPECT_EQ(SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS, shelf->auto_hide_behavior());
+
+ // Swipe-up to hide. This should have no effect because there are no visible
+ // windows.
+ end = below_start - delta;
+ generator.GestureScrollSequenceWithCallback(below_start, end,
+ base::TimeDelta::FromMilliseconds(10), kNumScrollSteps,
+ base::Bind(&ShelfDragCallback::ProcessScroll,
+ base::Unretained(&handler)));
+ EXPECT_EQ(SHELF_AUTO_HIDE, shelf->visibility_state());
+ EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->auto_hide_state());
+ EXPECT_EQ(SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS, shelf->auto_hide_behavior());
+}
+
+// Fails on Mac only. Need to be implemented. http://crbug.com/111279.
+#if defined(OS_MACOSX) || defined(OS_WIN)
+#define MAYBE_SetVisible DISABLED_SetVisible
+#else
+#define MAYBE_SetVisible SetVisible
+#endif
+// Makes sure SetVisible updates work area and widget appropriately.
+TEST_F(ShelfLayoutManagerTest, MAYBE_SetVisible) {
+ ShelfWidget* shelf = GetShelfWidget();
+ ShelfLayoutManager* manager = shelf->shelf_layout_manager();
+ // Force an initial layout.
+ manager->LayoutShelf();
+ EXPECT_EQ(SHELF_VISIBLE, manager->visibility_state());
+
+ gfx::Rect status_bounds(
+ shelf->status_area_widget()->GetWindowBoundsInScreen());
+ gfx::Rect launcher_bounds(
+ shelf->GetWindowBoundsInScreen());
+ int shelf_height = manager->GetIdealBounds().height();
+ gfx::Screen* screen = Shell::GetScreen();
+ gfx::Display display = screen->GetDisplayNearestWindow(
+ Shell::GetPrimaryRootWindow());
+ ASSERT_NE(-1, display.id());
+ // Bottom inset should be the max of widget heights.
+ EXPECT_EQ(shelf_height, display.GetWorkAreaInsets().bottom());
+
+ // Hide the shelf.
+ SetState(manager, SHELF_HIDDEN);
+ // Run the animation to completion.
+ StepWidgetLayerAnimatorToEnd(shelf);
+ StepWidgetLayerAnimatorToEnd(shelf->status_area_widget());
+ EXPECT_EQ(SHELF_HIDDEN, manager->visibility_state());
+ display = screen->GetDisplayNearestWindow(
+ Shell::GetPrimaryRootWindow());
+
+ EXPECT_EQ(0, display.GetWorkAreaInsets().bottom());
+
+ // Make sure the bounds of the two widgets changed.
+ EXPECT_GE(shelf->GetNativeView()->bounds().y(),
+ screen->GetPrimaryDisplay().bounds().bottom());
+ EXPECT_GE(shelf->status_area_widget()->GetNativeView()->bounds().y(),
+ screen->GetPrimaryDisplay().bounds().bottom());
+
+ // And show it again.
+ SetState(manager, SHELF_VISIBLE);
+ // Run the animation to completion.
+ StepWidgetLayerAnimatorToEnd(shelf);
+ StepWidgetLayerAnimatorToEnd(shelf->status_area_widget());
+ EXPECT_EQ(SHELF_VISIBLE, manager->visibility_state());
+ display = screen->GetDisplayNearestWindow(
+ Shell::GetPrimaryRootWindow());
+ EXPECT_EQ(shelf_height, display.GetWorkAreaInsets().bottom());
+
+ // Make sure the bounds of the two widgets changed.
+ launcher_bounds = shelf->GetNativeView()->bounds();
+ int bottom =
+ screen->GetPrimaryDisplay().bounds().bottom() - shelf_height;
+ EXPECT_EQ(launcher_bounds.y(),
+ bottom + (manager->GetIdealBounds().height() -
+ launcher_bounds.height()) / 2);
+ status_bounds = shelf->status_area_widget()->GetNativeView()->bounds();
+ EXPECT_EQ(status_bounds.y(),
+ bottom + shelf_height - status_bounds.height());
+}
+
+// Makes sure LayoutShelf invoked while animating cleans things up.
+TEST_F(ShelfLayoutManagerTest, LayoutShelfWhileAnimating) {
+ ShelfWidget* shelf = GetShelfWidget();
+ // Force an initial layout.
+ shelf->shelf_layout_manager()->LayoutShelf();
+ EXPECT_EQ(SHELF_VISIBLE, shelf->shelf_layout_manager()->visibility_state());
+
+ // Hide the shelf.
+ SetState(shelf->shelf_layout_manager(), SHELF_HIDDEN);
+ shelf->shelf_layout_manager()->LayoutShelf();
+ EXPECT_EQ(SHELF_HIDDEN, shelf->shelf_layout_manager()->visibility_state());
+ gfx::Display display = Shell::GetScreen()->GetDisplayNearestWindow(
+ Shell::GetPrimaryRootWindow());
+ EXPECT_EQ(0, display.GetWorkAreaInsets().bottom());
+
+ // Make sure the bounds of the two widgets changed.
+ EXPECT_GE(shelf->GetNativeView()->bounds().y(),
+ Shell::GetScreen()->GetPrimaryDisplay().bounds().bottom());
+ EXPECT_GE(shelf->status_area_widget()->GetNativeView()->bounds().y(),
+ Shell::GetScreen()->GetPrimaryDisplay().bounds().bottom());
+}
+
+// Test that switching to a different visibility state does not restart the
+// shelf show / hide animation if it is already running. (crbug.com/250918)
+TEST_F(ShelfLayoutManagerTest, SetStateWhileAnimating) {
+ ShelfWidget* shelf = GetShelfWidget();
+ SetState(shelf->shelf_layout_manager(), SHELF_VISIBLE);
+ gfx::Rect initial_shelf_bounds = shelf->GetWindowBoundsInScreen();
+ gfx::Rect initial_status_bounds =
+ shelf->status_area_widget()->GetWindowBoundsInScreen();
+
+ ui::ScopedAnimationDurationScaleMode normal_animation_duration(
+ ui::ScopedAnimationDurationScaleMode::SLOW_DURATION);
+ SetState(shelf->shelf_layout_manager(), SHELF_HIDDEN);
+ SetState(shelf->shelf_layout_manager(), SHELF_VISIBLE);
+
+ gfx::Rect current_shelf_bounds = shelf->GetWindowBoundsInScreen();
+ gfx::Rect current_status_bounds =
+ shelf->status_area_widget()->GetWindowBoundsInScreen();
+
+ const int small_change = initial_shelf_bounds.height() / 2;
+ EXPECT_LE(
+ std::abs(initial_shelf_bounds.height() - current_shelf_bounds.height()),
+ small_change);
+ EXPECT_LE(
+ std::abs(initial_status_bounds.height() - current_status_bounds.height()),
+ small_change);
+}
+
+// Makes sure the launcher is sized when the status area changes size.
+TEST_F(ShelfLayoutManagerTest, LauncherUpdatedWhenStatusAreaChangesSize) {
+ Launcher* launcher = Launcher::ForPrimaryDisplay();
+ ASSERT_TRUE(launcher);
+ ShelfWidget* shelf_widget = GetShelfWidget();
+ ASSERT_TRUE(shelf_widget);
+ ASSERT_TRUE(shelf_widget->status_area_widget());
+ shelf_widget->status_area_widget()->SetBounds(
+ gfx::Rect(0, 0, 200, 200));
+ EXPECT_EQ(200, shelf_widget->GetContentsView()->width() -
+ launcher->GetLauncherViewForTest()->width());
+}
+
+
+#if defined(OS_WIN)
+// RootWindow and Display can't resize on Windows Ash. http://crbug.com/165962
+#define MAYBE_AutoHide DISABLED_AutoHide
+#else
+#define MAYBE_AutoHide AutoHide
+#endif
+
+// Various assertions around auto-hide.
+TEST_F(ShelfLayoutManagerTest, MAYBE_AutoHide) {
+ aura::RootWindow* root = Shell::GetPrimaryRootWindow();
+ aura::test::EventGenerator generator(root, root);
+ generator.MoveMouseTo(0, 0);
+
+ ShelfLayoutManager* shelf = GetShelfLayoutManager();
+ shelf->SetAutoHideBehavior(ash::SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS);
+ views::Widget* widget = new views::Widget;
+ views::Widget::InitParams params(views::Widget::InitParams::TYPE_WINDOW);
+ params.bounds = gfx::Rect(0, 0, 200, 200);
+ params.context = CurrentContext();
+ // Widget is now owned by the parent window.
+ widget->Init(params);
+ widget->Maximize();
+ widget->Show();
+ EXPECT_EQ(SHELF_AUTO_HIDE, shelf->visibility_state());
+ EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->auto_hide_state());
+
+ // LayoutShelf() forces the animation to completion, at which point the
+ // launcher should go off the screen.
+ shelf->LayoutShelf();
+ EXPECT_EQ(root->bounds().bottom() - ShelfLayoutManager::kAutoHideSize,
+ GetShelfWidget()->GetWindowBoundsInScreen().y());
+ EXPECT_EQ(root->bounds().bottom() - ShelfLayoutManager::kAutoHideSize,
+ Shell::GetScreen()->GetDisplayNearestWindow(
+ root).work_area().bottom());
+
+ // Move the mouse to the bottom of the screen.
+ generator.MoveMouseTo(0, root->bounds().bottom() - 1);
+
+ // Shelf should be shown again (but it shouldn't have changed the work area).
+ SetState(shelf, SHELF_AUTO_HIDE);
+ EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->auto_hide_state());
+ shelf->LayoutShelf();
+ EXPECT_EQ(root->bounds().bottom() - shelf->GetIdealBounds().height(),
+ GetShelfWidget()->GetWindowBoundsInScreen().y());
+ EXPECT_EQ(root->bounds().bottom() - ShelfLayoutManager::kAutoHideSize,
+ Shell::GetScreen()->GetDisplayNearestWindow(
+ root).work_area().bottom());
+
+ // Move mouse back up.
+ generator.MoveMouseTo(0, 0);
+ SetState(shelf, SHELF_AUTO_HIDE);
+ EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->auto_hide_state());
+ shelf->LayoutShelf();
+ EXPECT_EQ(root->bounds().bottom() - ShelfLayoutManager::kAutoHideSize,
+ GetShelfWidget()->GetWindowBoundsInScreen().y());
+
+ // Drag mouse to bottom of screen.
+ generator.PressLeftButton();
+ generator.MoveMouseTo(0, root->bounds().bottom() - 1);
+ UpdateAutoHideStateNow();
+ EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->auto_hide_state());
+
+ generator.ReleaseLeftButton();
+ generator.MoveMouseTo(1, root->bounds().bottom() - 1);
+ UpdateAutoHideStateNow();
+ EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->auto_hide_state());
+ generator.PressLeftButton();
+ generator.MoveMouseTo(1, root->bounds().bottom() - 1);
+ UpdateAutoHideStateNow();
+ EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->auto_hide_state());
+}
+
+// Test the behavior of the shelf when it is auto hidden and it is on the
+// boundary between the primary and the secondary display.
+TEST_F(ShelfLayoutManagerTest, AutoHideShelfOnScreenBoundary) {
+ if (!SupportsMultipleDisplays())
+ return;
+
+ UpdateDisplay("800x600,800x600");
+ DisplayLayout display_layout(DisplayLayout::RIGHT, 0);
+ Shell::GetInstance()->display_controller()->SetLayoutForCurrentDisplays(
+ display_layout);
+ // Put the primary monitor's shelf on the display boundary.
+ ShelfLayoutManager* shelf = GetShelfLayoutManager();
+ shelf->SetAlignment(SHELF_ALIGNMENT_RIGHT);
+
+ // Create a window because the shelf is always shown when no windows are
+ // visible.
+ CreateTestWidget();
+
+ Shell::RootWindowList root_windows = Shell::GetAllRootWindows();
+ ASSERT_EQ(root_windows[0],
+ GetShelfWidget()->GetNativeWindow()->GetRootWindow());
+
+ shelf->SetAutoHideBehavior(ash::SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS);
+ EXPECT_EQ(SHELF_AUTO_HIDE, shelf->visibility_state());
+
+ int right_edge = root_windows[0]->GetBoundsInScreen().right() - 1;
+ int y = root_windows[0]->GetBoundsInScreen().y();
+
+ // Start off the mouse nowhere near the shelf; the shelf should be hidden.
+ aura::test::EventGenerator& generator(GetEventGenerator());
+ generator.MoveMouseTo(right_edge - 50, y);
+ UpdateAutoHideStateNow();
+ EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->auto_hide_state());
+
+ // Moving the mouse over the light bar (but not to the edge of the screen)
+ // should show the shelf.
+ generator.MoveMouseTo(right_edge - 1, y);
+ UpdateAutoHideStateNow();
+ EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->auto_hide_state());
+ EXPECT_EQ(right_edge - 1, Shell::GetScreen()->GetCursorScreenPoint().x());
+
+ // Moving the mouse off the light bar should hide the shelf.
+ generator.MoveMouseTo(right_edge - 50, y);
+ UpdateAutoHideStateNow();
+ EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->auto_hide_state());
+
+ // Moving the mouse to the right edge of the screen crossing the light bar
+ // should show the shelf despite the mouse cursor getting warped to the
+ // secondary display.
+ generator.MoveMouseTo(right_edge - 1, y);
+ generator.MoveMouseTo(right_edge, y);
+ UpdateAutoHideStateNow();
+ EXPECT_NE(right_edge - 1, Shell::GetScreen()->GetCursorScreenPoint().x());
+ EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->auto_hide_state());
+
+ // Hide the shelf.
+ generator.MoveMouseTo(right_edge - 50, y);
+ UpdateAutoHideStateNow();
+ EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->auto_hide_state());
+
+ // Moving the mouse to the right edge of the screen crossing the light bar and
+ // overshooting by a lot should keep the shelf hidden.
+ generator.MoveMouseTo(right_edge - 1, y);
+ generator.MoveMouseTo(right_edge + 50, y);
+ UpdateAutoHideStateNow();
+ EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->auto_hide_state());
+
+ // Moving the mouse to the right edge of the screen crossing the light bar and
+ // overshooting a bit should show the shelf.
+ generator.MoveMouseTo(right_edge - 1, y);
+ generator.MoveMouseTo(right_edge + 2, y);
+ UpdateAutoHideStateNow();
+ EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->auto_hide_state());
+
+ // Keeping the mouse close to the left edge of the secondary display after the
+ // shelf is shown should keep the shelf shown.
+ generator.MoveMouseTo(right_edge + 2, y + 1);
+ UpdateAutoHideStateNow();
+ EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->auto_hide_state());
+
+ // Moving the mouse far from the left edge of the secondary display should
+ // hide the shelf.
+ generator.MoveMouseTo(right_edge + 50, y);
+ UpdateAutoHideStateNow();
+ EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->auto_hide_state());
+
+ // Moving to the left edge of the secondary display without first crossing
+ // the primary display's right aligned shelf first should not show the shelf.
+ generator.MoveMouseTo(right_edge + 2, y);
+ UpdateAutoHideStateNow();
+ EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->auto_hide_state());
+}
+
+// Assertions around the lock screen showing.
+TEST_F(ShelfLayoutManagerTest, VisibleWhenLockScreenShowing) {
+ // Since ShelfLayoutManager queries for mouse location, move the mouse so
+ // it isn't over the shelf.
+ aura::test::EventGenerator generator(
+ Shell::GetPrimaryRootWindow(), gfx::Point());
+ generator.MoveMouseTo(0, 0);
+
+ ShelfLayoutManager* shelf = GetShelfLayoutManager();
+ shelf->SetAutoHideBehavior(ash::SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS);
+ views::Widget* widget = new views::Widget;
+ views::Widget::InitParams params(views::Widget::InitParams::TYPE_WINDOW);
+ params.bounds = gfx::Rect(0, 0, 200, 200);
+ params.context = CurrentContext();
+ // Widget is now owned by the parent window.
+ widget->Init(params);
+ widget->Maximize();
+ widget->Show();
+ EXPECT_EQ(SHELF_AUTO_HIDE, shelf->visibility_state());
+ EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->auto_hide_state());
+
+ aura::RootWindow* root = Shell::GetPrimaryRootWindow();
+ // LayoutShelf() forces the animation to completion, at which point the
+ // launcher should go off the screen.
+ shelf->LayoutShelf();
+ EXPECT_EQ(root->bounds().bottom() - ShelfLayoutManager::kAutoHideSize,
+ GetShelfWidget()->GetWindowBoundsInScreen().y());
+
+ aura::Window* lock_container = Shell::GetContainer(
+ Shell::GetPrimaryRootWindow(),
+ internal::kShellWindowId_LockScreenContainer);
+
+ views::Widget* lock_widget = new views::Widget;
+ views::Widget::InitParams lock_params(
+ views::Widget::InitParams::TYPE_WINDOW);
+ lock_params.bounds = gfx::Rect(0, 0, 200, 200);
+ params.context = CurrentContext();
+ lock_params.parent = lock_container;
+ // Widget is now owned by the parent window.
+ lock_widget->Init(lock_params);
+ lock_widget->Maximize();
+ lock_widget->Show();
+
+ // Lock the screen.
+ Shell::GetInstance()->session_state_delegate()->LockScreen();
+ shelf->UpdateVisibilityState();
+ // Showing a widget in the lock screen should force the shelf to be visibile.
+ EXPECT_EQ(SHELF_VISIBLE, shelf->visibility_state());
+
+ Shell::GetInstance()->session_state_delegate()->UnlockScreen();
+ shelf->UpdateVisibilityState();
+ EXPECT_EQ(SHELF_AUTO_HIDE, shelf->visibility_state());
+}
+
+// Assertions around SetAutoHideBehavior.
+TEST_F(ShelfLayoutManagerTest, SetAutoHideBehavior) {
+ // Since ShelfLayoutManager queries for mouse location, move the mouse so
+ // it isn't over the shelf.
+ aura::test::EventGenerator generator(
+ Shell::GetPrimaryRootWindow(), gfx::Point());
+ generator.MoveMouseTo(0, 0);
+
+ ShelfLayoutManager* shelf = GetShelfLayoutManager();
+ views::Widget* widget = new views::Widget;
+ views::Widget::InitParams params(views::Widget::InitParams::TYPE_WINDOW);
+ params.bounds = gfx::Rect(0, 0, 200, 200);
+ params.context = CurrentContext();
+ // Widget is now owned by the parent window.
+ widget->Init(params);
+ widget->Show();
+ aura::Window* window = widget->GetNativeWindow();
+ gfx::Rect display_bounds(
+ Shell::GetScreen()->GetDisplayNearestWindow(window).bounds());
+
+ shelf->SetAutoHideBehavior(SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS);
+ EXPECT_EQ(SHELF_AUTO_HIDE, shelf->visibility_state());
+
+ shelf->SetAutoHideBehavior(SHELF_AUTO_HIDE_BEHAVIOR_NEVER);
+ EXPECT_EQ(SHELF_VISIBLE, shelf->visibility_state());
+
+ widget->Maximize();
+ EXPECT_EQ(SHELF_VISIBLE, shelf->visibility_state());
+ EXPECT_EQ(Shell::GetScreen()->GetDisplayNearestWindow(
+ window).work_area().bottom(),
+ widget->GetWorkAreaBoundsInScreen().bottom());
+
+ shelf->SetAutoHideBehavior(SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS);
+ EXPECT_EQ(SHELF_AUTO_HIDE, shelf->visibility_state());
+ EXPECT_EQ(Shell::GetScreen()->GetDisplayNearestWindow(
+ window).work_area().bottom(),
+ widget->GetWorkAreaBoundsInScreen().bottom());
+
+ shelf->SetAutoHideBehavior(SHELF_AUTO_HIDE_BEHAVIOR_NEVER);
+ EXPECT_EQ(SHELF_VISIBLE, shelf->visibility_state());
+ EXPECT_EQ(Shell::GetScreen()->GetDisplayNearestWindow(
+ window).work_area().bottom(),
+ widget->GetWorkAreaBoundsInScreen().bottom());
+}
+
+// Basic assertions around the dimming of the shelf.
+TEST_F(ShelfLayoutManagerTest, TestDimmingBehavior) {
+ // Since ShelfLayoutManager queries for mouse location, move the mouse so
+ // it isn't over the shelf.
+ aura::test::EventGenerator generator(
+ Shell::GetPrimaryRootWindow(), gfx::Point());
+ generator.MoveMouseTo(0, 0);
+
+ ShelfLayoutManager* shelf = GetShelfLayoutManager();
+ shelf->shelf_widget()->DisableDimmingAnimationsForTest();
+
+ views::Widget* widget = new views::Widget;
+ views::Widget::InitParams params(views::Widget::InitParams::TYPE_WINDOW);
+ params.bounds = gfx::Rect(0, 0, 200, 200);
+ params.context = CurrentContext();
+ // Widget is now owned by the parent window.
+ widget->Init(params);
+ widget->Show();
+ aura::Window* window = widget->GetNativeWindow();
+ gfx::Rect display_bounds(
+ Shell::GetScreen()->GetDisplayNearestWindow(window).bounds());
+
+ gfx::Point off_shelf = display_bounds.CenterPoint();
+ gfx::Point on_shelf =
+ shelf->shelf_widget()->GetWindowBoundsInScreen().CenterPoint();
+
+ // Test there is no dimming object active at this point.
+ generator.MoveMouseTo(on_shelf.x(), on_shelf.y());
+ EXPECT_EQ(-1, shelf->shelf_widget()->GetDimmingAlphaForTest());
+ generator.MoveMouseTo(off_shelf.x(), off_shelf.y());
+ EXPECT_EQ(-1, shelf->shelf_widget()->GetDimmingAlphaForTest());
+
+ // After maximization, the shelf should be visible and the dimmer created.
+ widget->Maximize();
+
+ on_shelf = shelf->shelf_widget()->GetWindowBoundsInScreen().CenterPoint();
+ EXPECT_LT(0, shelf->shelf_widget()->GetDimmingAlphaForTest());
+
+ // Moving the mouse off the shelf should dim the bar.
+ generator.MoveMouseTo(off_shelf.x(), off_shelf.y());
+ EXPECT_LT(0, shelf->shelf_widget()->GetDimmingAlphaForTest());
+
+ // Adding touch events outside the shelf should still keep the shelf in
+ // dimmed state.
+ generator.PressTouch();
+ generator.MoveTouch(off_shelf);
+ EXPECT_LT(0, shelf->shelf_widget()->GetDimmingAlphaForTest());
+ // Move the touch into the shelf area should undim.
+ generator.MoveTouch(on_shelf);
+ EXPECT_EQ(0, shelf->shelf_widget()->GetDimmingAlphaForTest());
+ generator.ReleaseTouch();
+ // And a release dims again.
+ EXPECT_LT(0, shelf->shelf_widget()->GetDimmingAlphaForTest());
+
+ // Moving the mouse on the shelf should undim the bar.
+ generator.MoveMouseTo(on_shelf);
+ EXPECT_EQ(0, shelf->shelf_widget()->GetDimmingAlphaForTest());
+
+ // No matter what the touch events do, the shelf should stay undimmed.
+ generator.PressTouch();
+ generator.MoveTouch(off_shelf);
+ EXPECT_EQ(0, shelf->shelf_widget()->GetDimmingAlphaForTest());
+ generator.MoveTouch(on_shelf);
+ EXPECT_EQ(0, shelf->shelf_widget()->GetDimmingAlphaForTest());
+ generator.MoveTouch(off_shelf);
+ EXPECT_EQ(0, shelf->shelf_widget()->GetDimmingAlphaForTest());
+ generator.MoveTouch(on_shelf);
+ generator.ReleaseTouch();
+
+ // After restore, the dimming object should be deleted again.
+ widget->Restore();
+ EXPECT_EQ(-1, shelf->shelf_widget()->GetDimmingAlphaForTest());
+}
+
+// Assertions around the dimming of the shelf in conjunction with menus.
+TEST_F(ShelfLayoutManagerTest, TestDimmingBehaviorWithMenus) {
+ // Since ShelfLayoutManager queries for mouse location, move the mouse so
+ // it isn't over the shelf.
+ aura::test::EventGenerator generator(
+ Shell::GetPrimaryRootWindow(), gfx::Point());
+ generator.MoveMouseTo(0, 0);
+
+ ShelfLayoutManager* shelf = GetShelfLayoutManager();
+ shelf->shelf_widget()->DisableDimmingAnimationsForTest();
+
+ views::Widget* widget = new views::Widget;
+ views::Widget::InitParams params(views::Widget::InitParams::TYPE_WINDOW);
+ params.bounds = gfx::Rect(0, 0, 200, 200);
+ params.context = CurrentContext();
+ // Widget is now owned by the parent window.
+ widget->Init(params);
+ widget->Show();
+ aura::Window* window = widget->GetNativeWindow();
+ gfx::Rect display_bounds(
+ Shell::GetScreen()->GetDisplayNearestWindow(window).bounds());
+
+ // After maximization, the shelf should be visible and the dimmer created.
+ widget->Maximize();
+
+ gfx::Point off_shelf = display_bounds.CenterPoint();
+ gfx::Point on_shelf =
+ shelf->shelf_widget()->GetWindowBoundsInScreen().CenterPoint();
+
+ // Moving the mouse on the shelf should undim the bar.
+ generator.MoveMouseTo(on_shelf.x(), on_shelf.y());
+ EXPECT_EQ(0, shelf->shelf_widget()->GetDimmingAlphaForTest());
+
+ // Simulate a menu opening.
+ shelf->shelf_widget()->ForceUndimming(true);
+
+ // Moving the mouse off the shelf should not dim the bar.
+ generator.MoveMouseTo(off_shelf.x(), off_shelf.y());
+ EXPECT_EQ(0, shelf->shelf_widget()->GetDimmingAlphaForTest());
+
+ // No matter what the touch events do, the shelf should stay undimmed.
+ generator.PressTouch();
+ generator.MoveTouch(off_shelf);
+ EXPECT_EQ(0, shelf->shelf_widget()->GetDimmingAlphaForTest());
+ generator.MoveTouch(on_shelf);
+ EXPECT_EQ(0, shelf->shelf_widget()->GetDimmingAlphaForTest());
+ generator.MoveTouch(off_shelf);
+ EXPECT_EQ(0, shelf->shelf_widget()->GetDimmingAlphaForTest());
+ generator.ReleaseTouch();
+ EXPECT_EQ(0, shelf->shelf_widget()->GetDimmingAlphaForTest());
+
+ // "Closing the menu" should now turn off the menu since no event is inside
+ // the shelf any longer.
+ shelf->shelf_widget()->ForceUndimming(false);
+ EXPECT_LT(0, shelf->shelf_widget()->GetDimmingAlphaForTest());
+
+ // Moving the mouse again on the shelf which should undim the bar again.
+ // This time we check that the bar stays undimmed when the mouse remains on
+ // the bar and the "menu gets closed".
+ generator.MoveMouseTo(on_shelf.x(), on_shelf.y());
+ EXPECT_EQ(0, shelf->shelf_widget()->GetDimmingAlphaForTest());
+ shelf->shelf_widget()->ForceUndimming(true);
+ generator.MoveMouseTo(off_shelf.x(), off_shelf.y());
+ EXPECT_EQ(0, shelf->shelf_widget()->GetDimmingAlphaForTest());
+ generator.MoveMouseTo(on_shelf.x(), on_shelf.y());
+ EXPECT_EQ(0, shelf->shelf_widget()->GetDimmingAlphaForTest());
+ shelf->shelf_widget()->ForceUndimming(true);
+ EXPECT_EQ(0, shelf->shelf_widget()->GetDimmingAlphaForTest());
+}
+
+// Verifies the shelf is visible when status/launcher is focused.
+TEST_F(ShelfLayoutManagerTest, VisibleWhenStatusOrLauncherFocused) {
+ // Since ShelfLayoutManager queries for mouse location, move the mouse so
+ // it isn't over the shelf.
+ aura::test::EventGenerator generator(
+ Shell::GetPrimaryRootWindow(), gfx::Point());
+ generator.MoveMouseTo(0, 0);
+
+ ShelfLayoutManager* shelf = GetShelfLayoutManager();
+ views::Widget* widget = new views::Widget;
+ views::Widget::InitParams params(views::Widget::InitParams::TYPE_WINDOW);
+ params.bounds = gfx::Rect(0, 0, 200, 200);
+ params.context = CurrentContext();
+ // Widget is now owned by the parent window.
+ widget->Init(params);
+ widget->Show();
+ shelf->SetAutoHideBehavior(SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS);
+ EXPECT_EQ(SHELF_AUTO_HIDE, shelf->visibility_state());
+ EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->auto_hide_state());
+
+ // Focus the launcher. Have to go through the focus cycler as normal focus
+ // requests to it do nothing.
+ GetShelfWidget()->GetFocusCycler()->RotateFocus(FocusCycler::FORWARD);
+ EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->auto_hide_state());
+
+ widget->Activate();
+ EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->auto_hide_state());
+
+ // Trying to activate the status should fail, since we only allow activating
+ // it when the user is using the keyboard (i.e. through FocusCycler).
+ GetShelfWidget()->status_area_widget()->Activate();
+ EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->auto_hide_state());
+
+ GetShelfWidget()->GetFocusCycler()->RotateFocus(FocusCycler::FORWARD);
+ EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->auto_hide_state());
+}
+
+// Makes sure shelf will be visible when app list opens as shelf is in
+// SHELF_VISIBLE state,and toggling app list won't change shelf
+// visibility state.
+TEST_F(ShelfLayoutManagerTest, OpenAppListWithShelfVisibleState) {
+ Shell* shell = Shell::GetInstance();
+ ShelfLayoutManager* shelf = GetShelfLayoutManager();
+ shelf->LayoutShelf();
+ shelf->SetAutoHideBehavior(SHELF_AUTO_HIDE_BEHAVIOR_NEVER);
+
+ // Create a normal unmaximized windowm shelf should be visible.
+ aura::Window* window = CreateTestWindow();
+ window->SetBounds(gfx::Rect(0, 0, 100, 100));
+ window->Show();
+ EXPECT_FALSE(shell->GetAppListTargetVisibility());
+ EXPECT_EQ(SHELF_VISIBLE, shelf->visibility_state());
+
+ // Toggle app list to show, and the shelf stays visible.
+ shell->ToggleAppList(NULL);
+ EXPECT_TRUE(shell->GetAppListTargetVisibility());
+ EXPECT_EQ(SHELF_VISIBLE, shelf->visibility_state());
+
+ // Toggle app list to hide, and the shelf stays visible.
+ shell->ToggleAppList(NULL);
+ EXPECT_FALSE(shell->GetAppListTargetVisibility());
+ EXPECT_EQ(SHELF_VISIBLE, shelf->visibility_state());
+}
+
+// Makes sure shelf will be shown with SHELF_AUTO_HIDE_SHOWN state
+// when app list opens as shelf is in SHELF_AUTO_HIDE state, and
+// toggling app list won't change shelf visibility state.
+TEST_F(ShelfLayoutManagerTest, OpenAppListWithShelfAutoHideState) {
+ Shell* shell = Shell::GetInstance();
+ ShelfLayoutManager* shelf = GetShelfLayoutManager();
+ shelf->LayoutShelf();
+ shelf->SetAutoHideBehavior(SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS);
+
+ // Create a window and show it in maximized state.
+ aura::Window* window = CreateTestWindow();
+ window->SetBounds(gfx::Rect(0, 0, 100, 100));
+ window->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MAXIMIZED);
+ window->Show();
+ wm::ActivateWindow(window);
+
+ EXPECT_FALSE(shell->GetAppListTargetVisibility());
+ EXPECT_EQ(SHELF_AUTO_HIDE, shelf->visibility_state());
+
+ // Toggle app list to show.
+ shell->ToggleAppList(NULL);
+ // The shelf's auto hide state won't be changed until the timer fires, so
+ // calling shell->UpdateShelfVisibility() is kind of manually helping it to
+ // update the state.
+ shell->UpdateShelfVisibility();
+ EXPECT_TRUE(shell->GetAppListTargetVisibility());
+ EXPECT_EQ(SHELF_AUTO_HIDE, shelf->visibility_state());
+ EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->auto_hide_state());
+
+ // Toggle app list to hide.
+ shell->ToggleAppList(NULL);
+ EXPECT_FALSE(shell->GetAppListTargetVisibility());
+ EXPECT_EQ(SHELF_AUTO_HIDE, shelf->visibility_state());
+}
+
+// Makes sure shelf will be hidden when app list opens as shelf is in HIDDEN
+// state, and toggling app list won't change shelf visibility state.
+TEST_F(ShelfLayoutManagerTest, OpenAppListWithShelfHiddenState) {
+ Shell* shell = Shell::GetInstance();
+ ShelfLayoutManager* shelf = GetShelfLayoutManager();
+ // For shelf to be visible, app list is not open in initial state.
+ shelf->LayoutShelf();
+
+ // Create a window and make it full screen.
+ aura::Window* window = CreateTestWindow();
+ window->SetBounds(gfx::Rect(0, 0, 100, 100));
+ window->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_FULLSCREEN);
+ window->Show();
+ wm::ActivateWindow(window);
+
+ // App list and shelf is not shown.
+ EXPECT_FALSE(shell->GetAppListTargetVisibility());
+ EXPECT_EQ(SHELF_HIDDEN, shelf->visibility_state());
+
+ // Toggle app list to show.
+ shell->ToggleAppList(NULL);
+ EXPECT_TRUE(shell->GetAppListTargetVisibility());
+ EXPECT_EQ(SHELF_HIDDEN, shelf->visibility_state());
+
+ // Toggle app list to hide.
+ shell->ToggleAppList(NULL);
+ EXPECT_FALSE(shell->GetAppListTargetVisibility());
+ EXPECT_EQ(SHELF_HIDDEN, shelf->visibility_state());
+}
+
+#if defined(OS_WIN)
+// RootWindow and Display can't resize on Windows Ash. http://crbug.com/165962
+#define MAYBE_SetAlignment DISABLED_SetAlignment
+#else
+#define MAYBE_SetAlignment SetAlignment
+#endif
+
+// Tests SHELF_ALIGNMENT_(LEFT, RIGHT, TOP).
+TEST_F(ShelfLayoutManagerTest, MAYBE_SetAlignment) {
+ ShelfLayoutManager* shelf = GetShelfLayoutManager();
+ // Force an initial layout.
+ shelf->SetAutoHideBehavior(SHELF_AUTO_HIDE_BEHAVIOR_NEVER);
+ shelf->LayoutShelf();
+ EXPECT_EQ(SHELF_VISIBLE, shelf->visibility_state());
+
+ shelf->SetAlignment(SHELF_ALIGNMENT_LEFT);
+ gfx::Rect launcher_bounds(
+ GetShelfWidget()->GetWindowBoundsInScreen());
+ const gfx::Screen* screen = Shell::GetScreen();
+ gfx::Display display =
+ screen->GetDisplayNearestWindow(Shell::GetPrimaryRootWindow());
+ ASSERT_NE(-1, display.id());
+ EXPECT_EQ(shelf->GetIdealBounds().width(),
+ display.GetWorkAreaInsets().left());
+ EXPECT_GE(
+ launcher_bounds.width(),
+ GetShelfWidget()->GetContentsView()->GetPreferredSize().width());
+ EXPECT_EQ(SHELF_ALIGNMENT_LEFT, GetSystemTray()->shelf_alignment());
+ StatusAreaWidget* status_area_widget = GetShelfWidget()->status_area_widget();
+ gfx::Rect status_bounds(status_area_widget->GetWindowBoundsInScreen());
+ EXPECT_GE(status_bounds.width(),
+ status_area_widget->GetContentsView()->GetPreferredSize().width());
+ EXPECT_EQ(shelf->GetIdealBounds().width(),
+ display.GetWorkAreaInsets().left());
+ EXPECT_EQ(0, display.GetWorkAreaInsets().top());
+ EXPECT_EQ(0, display.GetWorkAreaInsets().bottom());
+ EXPECT_EQ(0, display.GetWorkAreaInsets().right());
+ EXPECT_EQ(display.bounds().x(), launcher_bounds.x());
+ EXPECT_EQ(display.bounds().y(), launcher_bounds.y());
+ EXPECT_EQ(display.bounds().height(), launcher_bounds.height());
+ shelf->SetAutoHideBehavior(SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS);
+ display = screen->GetDisplayNearestWindow(Shell::GetPrimaryRootWindow());
+ EXPECT_EQ(ShelfLayoutManager::kAutoHideSize,
+ display.GetWorkAreaInsets().left());
+ EXPECT_EQ(ShelfLayoutManager::kAutoHideSize, display.work_area().x());
+
+ shelf->SetAutoHideBehavior(SHELF_AUTO_HIDE_BEHAVIOR_NEVER);
+ shelf->SetAlignment(SHELF_ALIGNMENT_RIGHT);
+ display = screen->GetDisplayNearestWindow(Shell::GetPrimaryRootWindow());
+ launcher_bounds = GetShelfWidget()->GetWindowBoundsInScreen();
+ display = screen->GetDisplayNearestWindow(Shell::GetPrimaryRootWindow());
+ ASSERT_NE(-1, display.id());
+ EXPECT_EQ(shelf->GetIdealBounds().width(),
+ display.GetWorkAreaInsets().right());
+ EXPECT_GE(launcher_bounds.width(),
+ GetShelfWidget()->GetContentsView()->GetPreferredSize().width());
+ EXPECT_EQ(SHELF_ALIGNMENT_RIGHT, GetSystemTray()->shelf_alignment());
+ status_bounds = gfx::Rect(status_area_widget->GetWindowBoundsInScreen());
+ EXPECT_GE(status_bounds.width(),
+ status_area_widget->GetContentsView()->GetPreferredSize().width());
+ EXPECT_EQ(shelf->GetIdealBounds().width(),
+ display.GetWorkAreaInsets().right());
+ EXPECT_EQ(0, display.GetWorkAreaInsets().top());
+ EXPECT_EQ(0, display.GetWorkAreaInsets().bottom());
+ EXPECT_EQ(0, display.GetWorkAreaInsets().left());
+ EXPECT_EQ(display.work_area().right(), launcher_bounds.x());
+ EXPECT_EQ(display.bounds().y(), launcher_bounds.y());
+ EXPECT_EQ(display.bounds().height(), launcher_bounds.height());
+ shelf->SetAutoHideBehavior(SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS);
+ display = screen->GetDisplayNearestWindow(Shell::GetPrimaryRootWindow());
+ EXPECT_EQ(ShelfLayoutManager::kAutoHideSize,
+ display.GetWorkAreaInsets().right());
+ EXPECT_EQ(ShelfLayoutManager::kAutoHideSize,
+ display.bounds().right() - display.work_area().right());
+
+ shelf->SetAutoHideBehavior(SHELF_AUTO_HIDE_BEHAVIOR_NEVER);
+ shelf->SetAlignment(SHELF_ALIGNMENT_TOP);
+ display = screen->GetDisplayNearestWindow(Shell::GetPrimaryRootWindow());
+ launcher_bounds = GetShelfWidget()->GetWindowBoundsInScreen();
+ display = screen->GetDisplayNearestWindow(Shell::GetPrimaryRootWindow());
+ ASSERT_NE(-1, display.id());
+ EXPECT_EQ(shelf->GetIdealBounds().height(),
+ display.GetWorkAreaInsets().top());
+ EXPECT_GE(launcher_bounds.height(),
+ GetShelfWidget()->GetContentsView()->GetPreferredSize().height());
+ EXPECT_EQ(SHELF_ALIGNMENT_TOP, GetSystemTray()->shelf_alignment());
+ status_bounds = gfx::Rect(status_area_widget->GetWindowBoundsInScreen());
+ EXPECT_GE(status_bounds.height(),
+ status_area_widget->GetContentsView()->GetPreferredSize().height());
+ EXPECT_EQ(shelf->GetIdealBounds().height(),
+ display.GetWorkAreaInsets().top());
+ EXPECT_EQ(0, display.GetWorkAreaInsets().right());
+ EXPECT_EQ(0, display.GetWorkAreaInsets().bottom());
+ EXPECT_EQ(0, display.GetWorkAreaInsets().left());
+ EXPECT_EQ(display.work_area().y(), launcher_bounds.bottom());
+ EXPECT_EQ(display.bounds().x(), launcher_bounds.x());
+ EXPECT_EQ(display.bounds().width(), launcher_bounds.width());
+ shelf->SetAutoHideBehavior(SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS);
+ display = screen->GetDisplayNearestWindow(Shell::GetPrimaryRootWindow());
+ EXPECT_EQ(ShelfLayoutManager::kAutoHideSize,
+ display.GetWorkAreaInsets().top());
+ EXPECT_EQ(ShelfLayoutManager::kAutoHideSize,
+ display.work_area().y() - display.bounds().y());
+}
+
+#if defined(OS_WIN)
+// RootWindow and Display can't resize on Windows Ash. http://crbug.com/165962
+#define MAYBE_GestureDrag DISABLED_GestureDrag
+#else
+#define MAYBE_GestureDrag GestureDrag
+#endif
+
+TEST_F(ShelfLayoutManagerTest, MAYBE_GestureDrag) {
+ ShelfLayoutManager* shelf = GetShelfLayoutManager();
+ {
+ SCOPED_TRACE("BOTTOM");
+ RunGestureDragTests(gfx::Vector2d(0, 100));
+ }
+
+ {
+ SCOPED_TRACE("LEFT");
+ shelf->SetAlignment(SHELF_ALIGNMENT_LEFT);
+ RunGestureDragTests(gfx::Vector2d(-100, 0));
+ }
+
+ {
+ SCOPED_TRACE("RIGHT");
+ shelf->SetAlignment(SHELF_ALIGNMENT_RIGHT);
+ RunGestureDragTests(gfx::Vector2d(100, 0));
+ }
+}
+
+TEST_F(ShelfLayoutManagerTest, WindowVisibilityDisablesAutoHide) {
+ if (!SupportsMultipleDisplays())
+ return;
+
+ UpdateDisplay("800x600,800x600");
+ ShelfLayoutManager* shelf = GetShelfLayoutManager();
+ shelf->LayoutShelf();
+ shelf->SetAutoHideBehavior(SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS);
+
+ // Create a visible window so auto-hide behavior is enforced
+ views::Widget* dummy = CreateTestWidget();
+
+ // Window visible => auto hide behaves normally.
+ shelf->UpdateVisibilityState();
+ EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->auto_hide_state());
+
+ // Window minimized => auto hide disabled.
+ dummy->Minimize();
+ EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->auto_hide_state());
+
+ // Window closed => auto hide disabled.
+ dummy->CloseNow();
+ EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->auto_hide_state());
+
+ // Multiple window test
+ views::Widget* window1 = CreateTestWidget();
+ views::Widget* window2 = CreateTestWidget();
+
+ // both visible => normal autohide
+ EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->auto_hide_state());
+
+ // either minimzed => normal autohide
+ window2->Minimize();
+ EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->auto_hide_state());
+ window2->Restore();
+ window1->Minimize();
+ EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->auto_hide_state());
+
+ // both minimized => disable auto hide
+ window2->Minimize();
+ EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->auto_hide_state());
+
+ // Test moving windows to/from other display.
+ window2->Restore();
+ EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->auto_hide_state());
+ // Move to second display.
+ window2->SetBounds(gfx::Rect(850, 50, 50, 50));
+ EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->auto_hide_state());
+ // Move back to primary display.
+ window2->SetBounds(gfx::Rect(50, 50, 50, 50));
+ EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->auto_hide_state());
+}
+
+// Test that the shelf animates back to its normal position upon a user
+// completing a gesture drag.
+TEST_F(ShelfLayoutManagerTest, ShelfAnimatesWhenGestureComplete) {
+ if (!SupportsHostWindowResize())
+ return;
+
+ // Test the shelf animates back to its original visible bounds when it is
+ // dragged when there are no visible windows.
+ ShelfLayoutManager* shelf = GetShelfLayoutManager();
+ shelf->SetAutoHideBehavior(SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS);
+ EXPECT_EQ(SHELF_AUTO_HIDE, shelf->visibility_state());
+ EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->auto_hide_state());
+ gfx::Rect visible_bounds = GetShelfWidget()->GetWindowBoundsInScreen();
+ {
+ // Enable animations so that we can make sure that they occur.
+ ui::ScopedAnimationDurationScaleMode regular_animations(
+ ui::ScopedAnimationDurationScaleMode::NORMAL_DURATION);
+
+ aura::test::EventGenerator generator(Shell::GetPrimaryRootWindow());
+ gfx::Rect shelf_bounds_in_screen =
+ GetShelfWidget()->GetWindowBoundsInScreen();
+ gfx::Point start(shelf_bounds_in_screen.CenterPoint());
+ gfx::Point end(start.x(), shelf_bounds_in_screen.bottom());
+ generator.GestureScrollSequence(start, end,
+ base::TimeDelta::FromMilliseconds(10), 1);
+ EXPECT_EQ(SHELF_AUTO_HIDE, shelf->visibility_state());
+ EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->auto_hide_state());
+
+ ShelfAnimationWaiter waiter(visible_bounds);
+ // Wait till the animation completes and check that it occurred.
+ waiter.WaitTillDoneAnimating();
+ EXPECT_TRUE(waiter.WasValidAnimation());
+ }
+
+ // Create a visible window so auto-hide behavior is enforced.
+ CreateTestWidget();
+
+ // Get the bounds of the shelf when it is hidden.
+ EXPECT_EQ(SHELF_AUTO_HIDE, shelf->visibility_state());
+ EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->auto_hide_state());
+ gfx::Rect auto_hidden_bounds = GetShelfWidget()->GetWindowBoundsInScreen();
+
+ {
+ // Enable the animations so that we can make sure they do occur.
+ ui::ScopedAnimationDurationScaleMode regular_animations(
+ ui::ScopedAnimationDurationScaleMode::NORMAL_DURATION);
+
+ gfx::Point start =
+ GetShelfWidget()->GetWindowBoundsInScreen().CenterPoint();
+ gfx::Point end(start.x(), start.y() - 100);
+ aura::test::EventGenerator generator(Shell::GetPrimaryRootWindow());
+
+ // Test that the shelf animates to the visible bounds after a swipe up on
+ // the auto hidden shelf.
+ generator.GestureScrollSequence(start, end,
+ base::TimeDelta::FromMilliseconds(10), 1);
+ EXPECT_EQ(SHELF_VISIBLE, shelf->visibility_state());
+ ShelfAnimationWaiter waiter1(visible_bounds);
+ waiter1.WaitTillDoneAnimating();
+ EXPECT_TRUE(waiter1.WasValidAnimation());
+
+ // Test that the shelf animates to the auto hidden bounds after a swipe up
+ // on the visible shelf.
+ EXPECT_EQ(SHELF_VISIBLE, shelf->visibility_state());
+ generator.GestureScrollSequence(start, end,
+ base::TimeDelta::FromMilliseconds(10), 1);
+ EXPECT_EQ(SHELF_AUTO_HIDE, shelf->visibility_state());
+ EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->auto_hide_state());
+ ShelfAnimationWaiter waiter2(auto_hidden_bounds);
+ waiter2.WaitTillDoneAnimating();
+ EXPECT_TRUE(waiter2.WasValidAnimation());
+ }
+}
+
+TEST_F(ShelfLayoutManagerTest, GestureRevealsTrayBubble) {
+ if (!SupportsHostWindowResize())
+ return;
+
+ ShelfLayoutManager* shelf = GetShelfLayoutManager();
+ shelf->LayoutShelf();
+
+ // Create a visible window so auto-hide behavior is enforced.
+ CreateTestWidget();
+
+ aura::test::EventGenerator generator(Shell::GetPrimaryRootWindow());
+ SystemTray* tray = GetSystemTray();
+
+ // First, make sure the shelf is visible.
+ shelf->SetAutoHideBehavior(SHELF_AUTO_HIDE_BEHAVIOR_NEVER);
+ EXPECT_FALSE(tray->HasSystemBubble());
+
+ // Now, drag up on the tray to show the bubble.
+ gfx::Point start = GetShelfWidget()->status_area_widget()->
+ GetWindowBoundsInScreen().CenterPoint();
+ gfx::Point end(start.x(), start.y() - 100);
+ generator.GestureScrollSequence(start, end,
+ base::TimeDelta::FromMilliseconds(10), 1);
+ EXPECT_TRUE(tray->HasSystemBubble());
+ tray->CloseSystemBubble();
+ RunAllPendingInMessageLoop();
+ EXPECT_FALSE(tray->HasSystemBubble());
+
+ // Drag again, but only a small amount, and slowly. The bubble should not be
+ // visible.
+ end.set_y(start.y() - 30);
+ generator.GestureScrollSequence(start, end,
+ base::TimeDelta::FromMilliseconds(500), 100);
+ EXPECT_FALSE(tray->HasSystemBubble());
+
+ // Now, hide the shelf.
+ shelf->SetAutoHideBehavior(SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS);
+
+ // Start a drag from the bezel, and drag up to show both the shelf and the
+ // tray bubble.
+ start.set_y(start.y() + 100);
+ end.set_y(start.y() - 400);
+ generator.GestureScrollSequence(start, end,
+ base::TimeDelta::FromMilliseconds(10), 1);
+ EXPECT_EQ(SHELF_VISIBLE, shelf->visibility_state());
+ EXPECT_TRUE(tray->HasSystemBubble());
+}
+
+TEST_F(ShelfLayoutManagerTest, ShelfFlickerOnTrayActivation) {
+ ShelfLayoutManager* shelf = GetShelfLayoutManager();
+
+ // Create a visible window so auto-hide behavior is enforced.
+ CreateTestWidget();
+
+ // Turn on auto-hide for the shelf.
+ shelf->SetAutoHideBehavior(SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS);
+ EXPECT_EQ(SHELF_AUTO_HIDE, shelf->visibility_state());
+ EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->auto_hide_state());
+
+ // Show the status menu. That should make the shelf visible again.
+ Shell::GetInstance()->accelerator_controller()->PerformAction(
+ SHOW_SYSTEM_TRAY_BUBBLE, ui::Accelerator());
+ EXPECT_EQ(SHELF_AUTO_HIDE, shelf->visibility_state());
+ EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->auto_hide_state());
+ EXPECT_TRUE(GetSystemTray()->HasSystemBubble());
+
+ // Now activate the tray (using the keyboard, instead of using the mouse to
+ // make sure the mouse does not alter the auto-hide state in the shelf).
+ // This should not trigger any auto-hide state change in the shelf.
+ ShelfLayoutObserverTest observer;
+ shelf->AddObserver(&observer);
+
+ aura::test::EventGenerator generator(Shell::GetPrimaryRootWindow());
+ generator.PressKey(ui::VKEY_SPACE, 0);
+ generator.ReleaseKey(ui::VKEY_SPACE, 0);
+ EXPECT_TRUE(GetSystemTray()->HasSystemBubble());
+ EXPECT_EQ(SHELF_AUTO_HIDE, shelf->visibility_state());
+ EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->auto_hide_state());
+ EXPECT_FALSE(observer.changed_auto_hide_state());
+
+ shelf->RemoveObserver(&observer);
+}
+
+TEST_F(ShelfLayoutManagerTest, WorkAreaChangeWorkspace) {
+ // Make sure the shelf is always visible.
+ ShelfLayoutManager* shelf = GetShelfLayoutManager();
+ shelf->SetAutoHideBehavior(SHELF_AUTO_HIDE_BEHAVIOR_NEVER);
+ shelf->LayoutShelf();
+
+ views::Widget::InitParams params(views::Widget::InitParams::TYPE_WINDOW);
+ params.bounds = gfx::Rect(0, 0, 200, 200);
+ params.context = CurrentContext();
+ views::Widget* widget_one = CreateTestWidgetWithParams(params);
+ widget_one->Maximize();
+
+ views::Widget* widget_two = CreateTestWidgetWithParams(params);
+ widget_two->Maximize();
+ widget_two->Activate();
+
+ // Both windows are maximized. They should be of the same size.
+ EXPECT_EQ(widget_one->GetNativeWindow()->bounds().ToString(),
+ widget_two->GetNativeWindow()->bounds().ToString());
+ int area_when_shelf_shown =
+ widget_one->GetNativeWindow()->bounds().size().GetArea();
+
+ // Now hide the shelf.
+ shelf->SetAutoHideBehavior(SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS);
+
+ // Both windows should be resized according to the shelf status.
+ EXPECT_EQ(widget_one->GetNativeWindow()->bounds().ToString(),
+ widget_two->GetNativeWindow()->bounds().ToString());
+ // Resized to small.
+ EXPECT_LT(area_when_shelf_shown,
+ widget_one->GetNativeWindow()->bounds().size().GetArea());
+
+ // Now show the shelf.
+ shelf->SetAutoHideBehavior(SHELF_AUTO_HIDE_BEHAVIOR_NEVER);
+
+ // Again both windows should be of the same size.
+ EXPECT_EQ(widget_one->GetNativeWindow()->bounds().ToString(),
+ widget_two->GetNativeWindow()->bounds().ToString());
+ EXPECT_EQ(area_when_shelf_shown,
+ widget_one->GetNativeWindow()->bounds().size().GetArea());
+}
+
+// Confirm that the shelf is dimmed only when content is maximized and
+// shelf is not autohidden.
+TEST_F(ShelfLayoutManagerTest, Dimming) {
+ GetShelfLayoutManager()->SetAutoHideBehavior(SHELF_AUTO_HIDE_BEHAVIOR_NEVER);
+ scoped_ptr<aura::Window> w1(CreateTestWindow());
+ w1->Show();
+ wm::ActivateWindow(w1.get());
+
+ // Normal window doesn't dim shelf.
+ w1->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_NORMAL);
+ ShelfWidget* shelf = GetShelfWidget();
+ EXPECT_FALSE(shelf->GetDimsShelf());
+
+ // Maximized window does.
+ w1->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MAXIMIZED);
+ EXPECT_TRUE(shelf->GetDimsShelf());
+
+ // Change back to normal stops dimming.
+ w1->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_NORMAL);
+ EXPECT_FALSE(shelf->GetDimsShelf());
+
+ // Changing back to maximized dims again.
+ w1->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MAXIMIZED);
+ EXPECT_TRUE(shelf->GetDimsShelf());
+
+ // Changing shelf to autohide stops dimming.
+ GetShelfLayoutManager()->SetAutoHideBehavior(SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS);
+ EXPECT_FALSE(shelf->GetDimsShelf());
+}
+
+// Make sure that the shelf will not hide if the mouse is between a bubble and
+// the shelf.
+TEST_F(ShelfLayoutManagerTest, BubbleEnlargesShelfMouseHitArea) {
+ ShelfLayoutManager* shelf = GetShelfLayoutManager();
+ StatusAreaWidget* status_area_widget =
+ Shell::GetPrimaryRootWindowController()->shelf()->status_area_widget();
+ SystemTray* tray = GetSystemTray();
+
+ // Create a visible window so auto-hide behavior is enforced.
+ CreateTestWidget();
+
+ shelf->LayoutShelf();
+ aura::test::EventGenerator generator(Shell::GetPrimaryRootWindow());
+
+ // Make two iterations - first without a message bubble which should make
+ // the shelf disappear and then with a message bubble which should keep it
+ // visible.
+ for (int i = 0; i < 2; i++) {
+ // Make sure the shelf is visible and position the mouse over it. Then
+ // allow auto hide.
+ shelf->SetAutoHideBehavior(SHELF_AUTO_HIDE_BEHAVIOR_NEVER);
+ EXPECT_FALSE(status_area_widget->IsMessageBubbleShown());
+ gfx::Point center =
+ status_area_widget->GetWindowBoundsInScreen().CenterPoint();
+ generator.MoveMouseTo(center.x(), center.y());
+ shelf->SetAutoHideBehavior(SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS);
+ EXPECT_TRUE(shelf->IsVisible());
+ if (!i) {
+ // In our first iteration we make sure there is no bubble.
+ tray->CloseSystemBubble();
+ EXPECT_FALSE(status_area_widget->IsMessageBubbleShown());
+ } else {
+ // In our second iteration we show a bubble.
+ TestItem *item = new TestItem;
+ tray->AddTrayItem(item);
+ tray->ShowNotificationView(item);
+ EXPECT_TRUE(status_area_widget->IsMessageBubbleShown());
+ }
+ // Move the pointer over the edge of the shelf.
+ generator.MoveMouseTo(
+ center.x(), status_area_widget->GetWindowBoundsInScreen().y() - 8);
+ shelf->UpdateVisibilityState();
+ if (i) {
+ EXPECT_TRUE(shelf->IsVisible());
+ EXPECT_TRUE(status_area_widget->IsMessageBubbleShown());
+ } else {
+ EXPECT_FALSE(shelf->IsVisible());
+ EXPECT_FALSE(status_area_widget->IsMessageBubbleShown());
+ }
+ }
+}
+
+TEST_F(ShelfLayoutManagerTest, ShelfBackgroundColor) {
+ EXPECT_EQ(SHELF_BACKGROUND_DEFAULT, GetShelfWidget()->GetBackgroundType());
+
+ scoped_ptr<aura::Window> w1(CreateTestWindow());
+ w1->Show();
+ wm::ActivateWindow(w1.get());
+ EXPECT_EQ(SHELF_BACKGROUND_DEFAULT, GetShelfWidget()->GetBackgroundType());
+ w1->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MAXIMIZED);
+ EXPECT_EQ(SHELF_BACKGROUND_MAXIMIZED, GetShelfWidget()->GetBackgroundType());
+
+ scoped_ptr<aura::Window> w2(CreateTestWindow());
+ w2->Show();
+ wm::ActivateWindow(w2.get());
+ // Overlaps with shelf.
+ w2->SetBounds(GetShelfLayoutManager()->GetIdealBounds());
+
+ // Still background is 'maximized'.
+ EXPECT_EQ(SHELF_BACKGROUND_MAXIMIZED, GetShelfWidget()->GetBackgroundType());
+
+ w1->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MINIMIZED);
+ EXPECT_EQ(SHELF_BACKGROUND_OVERLAP, GetShelfWidget()->GetBackgroundType());
+ w2->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MINIMIZED);
+ EXPECT_EQ(SHELF_BACKGROUND_DEFAULT, GetShelfWidget()->GetBackgroundType());
+
+ w1->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MAXIMIZED);
+ EXPECT_EQ(SHELF_BACKGROUND_MAXIMIZED, GetShelfWidget()->GetBackgroundType());
+ w1.reset();
+ EXPECT_EQ(SHELF_BACKGROUND_DEFAULT, GetShelfWidget()->GetBackgroundType());
+}
+
+// Verify that the shelf doesn't have the opaque background if it's auto-hide
+// status.
+TEST_F(ShelfLayoutManagerTest, ShelfBackgroundColorAutoHide) {
+ EXPECT_EQ(SHELF_BACKGROUND_DEFAULT, GetShelfWidget ()->GetBackgroundType());
+
+ GetShelfLayoutManager()->SetAutoHideBehavior(SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS);
+ scoped_ptr<aura::Window> w1(CreateTestWindow());
+ w1->Show();
+ wm::ActivateWindow(w1.get());
+ EXPECT_EQ(SHELF_BACKGROUND_OVERLAP, GetShelfWidget()->GetBackgroundType());
+ w1->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MAXIMIZED);
+ EXPECT_EQ(SHELF_BACKGROUND_OVERLAP, GetShelfWidget()->GetBackgroundType());
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/shelf/shelf_types.h b/chromium/ash/shelf/shelf_types.h
new file mode 100644
index 00000000000..4ee3b94c09a
--- /dev/null
+++ b/chromium/ash/shelf/shelf_types.h
@@ -0,0 +1,57 @@
+// Copyright (c) 2012 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 ASH_SHELF_SHELF_TYPES_H_
+#define ASH_SHELF_SHELF_TYPES_H_
+
+namespace ash {
+
+enum ShelfAlignment {
+ SHELF_ALIGNMENT_BOTTOM,
+ SHELF_ALIGNMENT_LEFT,
+ SHELF_ALIGNMENT_RIGHT,
+ SHELF_ALIGNMENT_TOP,
+};
+
+enum ShelfAutoHideBehavior {
+ // Always auto-hide.
+ SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS,
+
+ // Never auto-hide.
+ SHELF_AUTO_HIDE_BEHAVIOR_NEVER,
+
+ // Always hide.
+ SHELF_AUTO_HIDE_ALWAYS_HIDDEN,
+};
+
+enum ShelfVisibilityState {
+ // Always visible.
+ SHELF_VISIBLE,
+
+ // A couple of pixels are reserved at the bottom for the shelf.
+ SHELF_AUTO_HIDE,
+
+ // Nothing is shown. Used for fullscreen windows.
+ SHELF_HIDDEN,
+};
+
+enum ShelfAutoHideState {
+ SHELF_AUTO_HIDE_SHOWN,
+ SHELF_AUTO_HIDE_HIDDEN,
+};
+
+enum ShelfBackgroundType {
+ // The default transparent background.
+ SHELF_BACKGROUND_DEFAULT,
+
+ // The background when a window is overlapping.
+ SHELF_BACKGROUND_OVERLAP,
+
+ // The background when a window is maximized.
+ SHELF_BACKGROUND_MAXIMIZED,
+};
+
+} // namespace ash
+
+#endif // ASH_SHELF_SHELF_TYPES_H_
diff --git a/chromium/ash/shelf/shelf_widget.cc b/chromium/ash/shelf/shelf_widget.cc
new file mode 100644
index 00000000000..8c7733ff2d6
--- /dev/null
+++ b/chromium/ash/shelf/shelf_widget.cc
@@ -0,0 +1,656 @@
+// Copyright (c) 2012 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.
+
+#include "ash/shelf/shelf_widget.h"
+
+#include "ash/ash_switches.h"
+#include "ash/focus_cycler.h"
+#include "ash/launcher/launcher_delegate.h"
+#include "ash/launcher/launcher_model.h"
+#include "ash/launcher/launcher_navigator.h"
+#include "ash/launcher/launcher_view.h"
+#include "ash/root_window_controller.h"
+#include "ash/session_state_delegate.h"
+#include "ash/shelf/shelf_layout_manager.h"
+#include "ash/shelf/shelf_widget.h"
+#include "ash/shell.h"
+#include "ash/shell_window_ids.h"
+#include "ash/system/tray/test_system_tray_delegate.h"
+#include "ash/wm/property_util.h"
+#include "ash/wm/status_area_layout_manager.h"
+#include "ash/wm/window_properties.h"
+#include "ash/wm/workspace_controller.h"
+#include "grit/ash_resources.h"
+#include "ui/aura/client/activation_client.h"
+#include "ui/aura/root_window.h"
+#include "ui/aura/window.h"
+#include "ui/aura/window_observer.h"
+#include "ui/base/events/event_constants.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "ui/compositor/layer.h"
+#include "ui/compositor/scoped_layer_animation_settings.h"
+#include "ui/gfx/canvas.h"
+#include "ui/gfx/image/image.h"
+#include "ui/gfx/image/image_skia_operations.h"
+#include "ui/gfx/skbitmap_operations.h"
+#include "ui/views/accessible_pane_view.h"
+#include "ui/views/widget/widget.h"
+#include "ui/views/widget/widget_delegate.h"
+
+namespace {
+// Size of black border at bottom (or side) of launcher.
+const int kNumBlackPixels = 3;
+// Alpha to paint dimming image with.
+const int kDimAlpha = 128;
+
+// The time to dim and un-dim.
+const int kTimeToDimMs = 3000; // Slow in dimming.
+const int kTimeToUnDimMs = 200; // Fast in activating.
+const int kTimeToSwitchBackgroundMs = 1000;
+
+// Class used to slightly dim shelf items when maximized and visible.
+class DimmerView : public views::View,
+ public views::WidgetDelegate,
+ ash::internal::BackgroundAnimatorDelegate {
+ public:
+ // If |disable_dimming_animations_for_test| is set, all alpha animations will
+ // be performed instantly.
+ DimmerView(ash::ShelfWidget* shelf_widget,
+ bool disable_dimming_animations_for_test);
+ virtual ~DimmerView();
+
+ // Called by |DimmerEventFilter| when the mouse |hovered| state changes.
+ void SetHovered(bool hovered);
+
+ // Force the dimmer to be undimmed.
+ void ForceUndimming(bool force);
+
+ // views::WidgetDelegate overrides:
+ virtual views::Widget* GetWidget() OVERRIDE {
+ return View::GetWidget();
+ }
+ virtual const views::Widget* GetWidget() const OVERRIDE {
+ return View::GetWidget();
+ }
+
+ // ash::internal::BackgroundAnimatorDelegate overrides:
+ virtual void UpdateBackground(int alpha) OVERRIDE {
+ alpha_ = alpha;
+ SchedulePaint();
+ }
+
+ // views::View overrides:
+ virtual void OnPaintBackground(gfx::Canvas* canvas) OVERRIDE;
+
+ // A function to test the current alpha used.
+ int get_dimming_alpha_for_test() { return alpha_; }
+
+ private:
+ // This class monitors mouse events to see if it is on top of the launcher.
+ class DimmerEventFilter : public ui::EventHandler {
+ public:
+ explicit DimmerEventFilter(DimmerView* owner);
+ virtual ~DimmerEventFilter();
+
+ // Overridden from ui::EventHandler:
+ virtual void OnMouseEvent(ui::MouseEvent* event) OVERRIDE;
+ virtual void OnTouchEvent(ui::TouchEvent* event) OVERRIDE;
+
+ private:
+ // The owning class.
+ DimmerView* owner_;
+
+ // TRUE if the mouse is inside the shelf.
+ bool mouse_inside_;
+
+ // TRUE if a touch event is inside the shelf.
+ bool touch_inside_;
+
+ DISALLOW_COPY_AND_ASSIGN(DimmerEventFilter);
+ };
+
+ // The owning shelf.
+ ash::ShelfWidget* shelf_;
+
+ // The alpha to use for covering the shelf.
+ int alpha_;
+
+ // True if the event filter claims that we should not be dimmed.
+ bool is_hovered_;
+
+ // True if someone forces us not to be dimmed (e.g. a menu is open).
+ bool force_hovered_;
+
+ // True if animations should be suppressed for a test.
+ bool disable_dimming_animations_for_test_;
+
+ // The animator for the background transitions.
+ ash::internal::BackgroundAnimator background_animator_;
+
+ // Notification of entering / exiting of the shelf area by mouse.
+ scoped_ptr<DimmerEventFilter> event_filter_;
+
+ DISALLOW_COPY_AND_ASSIGN(DimmerView);
+};
+
+DimmerView::DimmerView(ash::ShelfWidget* shelf_widget,
+ bool disable_dimming_animations_for_test)
+ : shelf_(shelf_widget),
+ alpha_(kDimAlpha),
+ is_hovered_(false),
+ force_hovered_(false),
+ disable_dimming_animations_for_test_(disable_dimming_animations_for_test),
+ background_animator_(this, 0, kDimAlpha) {
+ event_filter_.reset(new DimmerEventFilter(this));
+ // Make sure it is undimmed at the beginning and then fire off the dimming
+ // animation.
+ background_animator_.SetPaintsBackground(false,
+ ash::internal::BackgroundAnimator::CHANGE_IMMEDIATE);
+ SetHovered(false);
+}
+
+DimmerView::~DimmerView() {
+}
+
+void DimmerView::SetHovered(bool hovered) {
+ // Remember the hovered state so that we can correct the state once a
+ // possible force state has disappeared.
+ is_hovered_ = hovered;
+ // Undimm also if we were forced to by e.g. an open menu.
+ hovered |= force_hovered_;
+ background_animator_.SetDuration(hovered ? kTimeToUnDimMs : kTimeToDimMs);
+ background_animator_.SetPaintsBackground(!hovered,
+ disable_dimming_animations_for_test_ ?
+ ash::internal::BackgroundAnimator::CHANGE_IMMEDIATE :
+ ash::internal::BackgroundAnimator::CHANGE_ANIMATE);
+}
+
+void DimmerView::ForceUndimming(bool force) {
+ bool previous = force_hovered_;
+ force_hovered_ = force;
+ // If the forced change does change the result we apply the change.
+ if (is_hovered_ || force_hovered_ != is_hovered_ || previous)
+ SetHovered(is_hovered_);
+}
+
+void DimmerView::OnPaintBackground(gfx::Canvas* canvas) {
+ SkPaint paint;
+ ResourceBundle& rb = ResourceBundle::GetSharedInstance();
+ gfx::ImageSkia launcher_background =
+ *rb.GetImageNamed(IDR_AURA_LAUNCHER_DIMMING).ToImageSkia();
+
+ if (shelf_->GetAlignment() != ash::SHELF_ALIGNMENT_BOTTOM) {
+ launcher_background = gfx::ImageSkiaOperations::CreateRotatedImage(
+ launcher_background,
+ shelf_->shelf_layout_manager()->SelectValueForShelfAlignment(
+ SkBitmapOperations::ROTATION_90_CW,
+ SkBitmapOperations::ROTATION_90_CW,
+ SkBitmapOperations::ROTATION_270_CW,
+ SkBitmapOperations::ROTATION_180_CW));
+ }
+ paint.setAlpha(alpha_);
+ canvas->DrawImageInt(
+ launcher_background,
+ 0, 0, launcher_background.width(), launcher_background.height(),
+ 0, 0, width(), height(),
+ false,
+ paint);
+}
+
+DimmerView::DimmerEventFilter::DimmerEventFilter(DimmerView* owner)
+ : owner_(owner),
+ mouse_inside_(false),
+ touch_inside_(false) {
+ ash::Shell::GetInstance()->AddPreTargetHandler(this);
+}
+
+DimmerView::DimmerEventFilter::~DimmerEventFilter() {
+ ash::Shell::GetInstance()->RemovePreTargetHandler(this);
+}
+
+void DimmerView::DimmerEventFilter::OnMouseEvent(ui::MouseEvent* event) {
+ if (event->type() != ui::ET_MOUSE_MOVED &&
+ event->type() != ui::ET_MOUSE_DRAGGED)
+ return;
+ bool inside = owner_->GetBoundsInScreen().Contains(event->root_location());
+ if (mouse_inside_ || touch_inside_ != inside || touch_inside_)
+ owner_->SetHovered(inside || touch_inside_);
+ mouse_inside_ = inside;
+}
+
+void DimmerView::DimmerEventFilter::OnTouchEvent(ui::TouchEvent* event) {
+ bool touch_inside = false;
+ if (event->type() != ui::ET_TOUCH_RELEASED &&
+ event->type() != ui::ET_TOUCH_CANCELLED)
+ touch_inside = owner_->GetBoundsInScreen().Contains(event->root_location());
+
+ if (mouse_inside_ || touch_inside_ != mouse_inside_ || touch_inside)
+ owner_->SetHovered(mouse_inside_ || touch_inside);
+ touch_inside_ = touch_inside;
+}
+
+} // namespace
+
+namespace ash {
+
+// The contents view of the Shelf. This view contains LauncherView and
+// sizes it to the width of the shelf minus the size of the status area.
+class ShelfWidget::DelegateView : public views::WidgetDelegate,
+ public views::AccessiblePaneView,
+ public internal::BackgroundAnimatorDelegate {
+ public:
+ explicit DelegateView(ShelfWidget* shelf);
+ virtual ~DelegateView();
+
+ void set_focus_cycler(internal::FocusCycler* focus_cycler) {
+ focus_cycler_ = focus_cycler;
+ }
+ internal::FocusCycler* focus_cycler() {
+ return focus_cycler_;
+ }
+
+ ui::Layer* opaque_background() { return &opaque_background_; }
+
+ // Set if the shelf area is dimmed (eg when a window is maximized).
+ void SetDimmed(bool dimmed);
+ bool GetDimmed() const;
+
+ void SetParentLayer(ui::Layer* layer);
+
+ // views::View overrides:
+ virtual void OnPaintBackground(gfx::Canvas* canvas) OVERRIDE;
+
+ // views::WidgetDelegateView overrides:
+ virtual views::Widget* GetWidget() OVERRIDE {
+ return View::GetWidget();
+ }
+ virtual const views::Widget* GetWidget() const OVERRIDE {
+ return View::GetWidget();
+ }
+
+ virtual bool CanActivate() const OVERRIDE;
+ virtual void Layout() OVERRIDE;
+ virtual void ReorderChildLayers(ui::Layer* parent_layer) OVERRIDE;
+ virtual void OnBoundsChanged(const gfx::Rect& old_bounds) OVERRIDE;
+
+ // BackgroundAnimatorDelegate overrides:
+ virtual void UpdateBackground(int alpha) OVERRIDE;
+
+ // Force the shelf to be presented in an undimmed state.
+ void ForceUndimming(bool force);
+
+ // A function to test the current alpha used by the dimming bar. If there is
+ // no dimmer active, the function will return -1.
+ int GetDimmingAlphaForTest();
+
+ // A function to test the bounds of the dimming bar. Returns gfx::Rect() if
+ // the dimmer is inactive.
+ gfx::Rect GetDimmerBoundsForTest();
+
+ // Disable dimming animations for running tests. This needs to be called
+ // prior to the creation of of the |dimmer_|.
+ void disable_dimming_animations_for_test() {
+ disable_dimming_animations_for_test_ = true;
+ }
+
+ private:
+ ShelfWidget* shelf_;
+ scoped_ptr<views::Widget> dimmer_;
+ internal::FocusCycler* focus_cycler_;
+ int alpha_;
+ ui::Layer opaque_background_;
+
+ // The view which does the dimming.
+ DimmerView* dimmer_view_;
+
+ // True if dimming animations should be turned off.
+ bool disable_dimming_animations_for_test_;
+
+ DISALLOW_COPY_AND_ASSIGN(DelegateView);
+};
+
+ShelfWidget::DelegateView::DelegateView(ShelfWidget* shelf)
+ : shelf_(shelf),
+ focus_cycler_(NULL),
+ alpha_(0),
+ opaque_background_(ui::LAYER_SOLID_COLOR),
+ dimmer_view_(NULL),
+ disable_dimming_animations_for_test_(false) {
+ set_allow_deactivate_on_esc(true);
+ opaque_background_.SetColor(SK_ColorBLACK);
+ opaque_background_.SetBounds(GetLocalBounds());
+ opaque_background_.SetOpacity(0.0f);
+}
+
+ShelfWidget::DelegateView::~DelegateView() {
+}
+
+void ShelfWidget::DelegateView::SetDimmed(bool value) {
+ if (value == (dimmer_.get() != NULL))
+ return;
+
+ if (value) {
+ dimmer_.reset(new views::Widget);
+ views::Widget::InitParams params(
+ views::Widget::InitParams::TYPE_WINDOW_FRAMELESS);
+ params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW;
+ params.can_activate = false;
+ params.accept_events = false;
+ params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
+ params.parent = shelf_->GetNativeView();
+ dimmer_->Init(params);
+ dimmer_->GetNativeWindow()->SetName("ShelfDimmer");
+ dimmer_->SetBounds(shelf_->GetWindowBoundsInScreen());
+ // The launcher should not take focus when it is initially shown.
+ dimmer_->set_focus_on_creation(false);
+ dimmer_view_ = new DimmerView(shelf_, disable_dimming_animations_for_test_);
+ dimmer_->SetContentsView(dimmer_view_);
+ dimmer_->GetNativeView()->SetName("ShelfDimmerView");
+ dimmer_->Show();
+ } else {
+ dimmer_view_ = NULL;
+ dimmer_.reset(NULL);
+ }
+}
+
+bool ShelfWidget::DelegateView::GetDimmed() const {
+ return dimmer_.get() && dimmer_->IsVisible();
+}
+
+void ShelfWidget::DelegateView::SetParentLayer(ui::Layer* layer) {
+ layer->Add(&opaque_background_);
+ ReorderLayers();
+}
+
+void ShelfWidget::DelegateView::OnPaintBackground(gfx::Canvas* canvas) {
+ ResourceBundle& rb = ResourceBundle::GetSharedInstance();
+ gfx::ImageSkia launcher_background =
+ *rb.GetImageSkiaNamed(IDR_AURA_LAUNCHER_BACKGROUND);
+ if (SHELF_ALIGNMENT_BOTTOM != shelf_->GetAlignment())
+ launcher_background = gfx::ImageSkiaOperations::CreateRotatedImage(
+ launcher_background,
+ shelf_->shelf_layout_manager()->SelectValueForShelfAlignment(
+ SkBitmapOperations::ROTATION_90_CW,
+ SkBitmapOperations::ROTATION_90_CW,
+ SkBitmapOperations::ROTATION_270_CW,
+ SkBitmapOperations::ROTATION_180_CW));
+
+ gfx::Rect black_rect =
+ shelf_->shelf_layout_manager()->SelectValueForShelfAlignment(
+ gfx::Rect(0, height() - kNumBlackPixels, width(), kNumBlackPixels),
+ gfx::Rect(0, 0, kNumBlackPixels, height()),
+ gfx::Rect(width() - kNumBlackPixels, 0, kNumBlackPixels, height()),
+ gfx::Rect(0, 0, width(), kNumBlackPixels));
+
+ SkPaint paint;
+ paint.setAlpha(alpha_);
+ canvas->DrawImageInt(
+ launcher_background,
+ 0, 0, launcher_background.width(), launcher_background.height(),
+ 0, 0, width(), height(),
+ false,
+ paint);
+ canvas->FillRect(black_rect, SK_ColorBLACK);
+}
+
+bool ShelfWidget::DelegateView::CanActivate() const {
+ // Allow to activate as fallback.
+ if (shelf_->activating_as_fallback_)
+ return true;
+ // Allow to activate from the focus cycler.
+ if (focus_cycler_ && focus_cycler_->widget_activating() == GetWidget())
+ return true;
+ // Disallow activating in other cases, especially when using mouse.
+ return false;
+}
+
+void ShelfWidget::DelegateView::Layout() {
+ for(int i = 0; i < child_count(); ++i) {
+ if (shelf_->shelf_layout_manager()->IsHorizontalAlignment()) {
+ child_at(i)->SetBounds(child_at(i)->x(), child_at(i)->y(),
+ child_at(i)->width(), height());
+ } else {
+ child_at(i)->SetBounds(child_at(i)->x(), child_at(i)->y(),
+ width(), child_at(i)->height());
+ }
+ }
+}
+
+void ShelfWidget::DelegateView::ReorderChildLayers(ui::Layer* parent_layer) {
+ views::View::ReorderChildLayers(parent_layer);
+ parent_layer->StackAtBottom(&opaque_background_);
+}
+
+void ShelfWidget::DelegateView::OnBoundsChanged(const gfx::Rect& old_bounds) {
+ opaque_background_.SetBounds(GetLocalBounds());
+ if (dimmer_)
+ dimmer_->SetBounds(GetBoundsInScreen());
+}
+
+void ShelfWidget::DelegateView::ForceUndimming(bool force) {
+ if (GetDimmed())
+ dimmer_view_->ForceUndimming(force);
+}
+
+int ShelfWidget::DelegateView::GetDimmingAlphaForTest() {
+ if (GetDimmed())
+ return dimmer_view_->get_dimming_alpha_for_test();
+ return -1;
+}
+
+gfx::Rect ShelfWidget::DelegateView::GetDimmerBoundsForTest() {
+ if (GetDimmed())
+ return dimmer_view_->GetBoundsInScreen();
+ return gfx::Rect();
+}
+
+void ShelfWidget::DelegateView::UpdateBackground(int alpha) {
+ alpha_ = alpha;
+ SchedulePaint();
+}
+
+ShelfWidget::ShelfWidget(aura::Window* shelf_container,
+ aura::Window* status_container,
+ internal::WorkspaceController* workspace_controller)
+ : delegate_view_(new DelegateView(this)),
+ background_animator_(delegate_view_, 0, kLauncherBackgroundAlpha),
+ activating_as_fallback_(false),
+ window_container_(shelf_container) {
+ views::Widget::InitParams params(
+ views::Widget::InitParams::TYPE_WINDOW_FRAMELESS);
+ params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW;
+ params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
+ params.parent = shelf_container;
+ params.delegate = delegate_view_;
+ Init(params);
+
+ // The shelf should not take focus when initially shown.
+ set_focus_on_creation(false);
+ SetContentsView(delegate_view_);
+ delegate_view_->SetParentLayer(GetLayer());
+
+ status_area_widget_ = new internal::StatusAreaWidget(status_container);
+ status_area_widget_->CreateTrayViews();
+ if (Shell::GetInstance()->session_state_delegate()->
+ IsActiveUserSessionStarted()) {
+ status_area_widget_->Show();
+ }
+ Shell::GetInstance()->focus_cycler()->AddWidget(status_area_widget_);
+
+ shelf_layout_manager_ = new internal::ShelfLayoutManager(this);
+ shelf_container->SetLayoutManager(shelf_layout_manager_);
+ shelf_layout_manager_->set_workspace_controller(workspace_controller);
+ workspace_controller->SetShelf(shelf_layout_manager_);
+
+ status_container->SetLayoutManager(
+ new internal::StatusAreaLayoutManager(this));
+
+ views::Widget::AddObserver(this);
+}
+
+ShelfWidget::~ShelfWidget() {
+ RemoveObserver(this);
+}
+
+void ShelfWidget::SetPaintsBackground(
+ ShelfBackgroundType background_type,
+ internal::BackgroundAnimator::ChangeType change_type) {
+ ui::Layer* opaque_background = delegate_view_->opaque_background();
+ float target_opacity =
+ (background_type == SHELF_BACKGROUND_MAXIMIZED) ? 1.0f : 0.0f;
+ scoped_ptr<ui::ScopedLayerAnimationSettings> opaque_background_animation;
+ if (change_type != internal::BackgroundAnimator::CHANGE_IMMEDIATE) {
+ opaque_background_animation.reset(new ui::ScopedLayerAnimationSettings(
+ opaque_background->GetAnimator()));
+ opaque_background_animation->SetTransitionDuration(
+ base::TimeDelta::FromMilliseconds(kTimeToSwitchBackgroundMs));
+ }
+ opaque_background->SetOpacity(target_opacity);
+
+ // TODO(mukai): use ui::Layer on both opaque_background and normal background
+ // retire background_animator_ at all. It would be simpler.
+ background_animator_.SetPaintsBackground(
+ background_type != SHELF_BACKGROUND_DEFAULT,
+ change_type);
+}
+
+ShelfBackgroundType ShelfWidget::GetBackgroundType() const {
+ if (delegate_view_->opaque_background()->GetTargetOpacity() == 1.0f)
+ return SHELF_BACKGROUND_MAXIMIZED;
+ if (background_animator_.paints_background())
+ return SHELF_BACKGROUND_OVERLAP;
+
+ return SHELF_BACKGROUND_DEFAULT;
+}
+
+// static
+bool ShelfWidget::ShelfAlignmentAllowed() {
+ if (!ash::switches::ShowShelfAlignmentMenu())
+ return false;
+ user::LoginStatus login_status =
+ Shell::GetInstance()->system_tray_delegate()->GetUserLoginStatus();
+
+ switch (login_status) {
+ case user::LOGGED_IN_USER:
+ case user::LOGGED_IN_OWNER:
+ return true;
+ case user::LOGGED_IN_LOCKED:
+ case user::LOGGED_IN_PUBLIC:
+ case user::LOGGED_IN_LOCALLY_MANAGED:
+ case user::LOGGED_IN_GUEST:
+ case user::LOGGED_IN_RETAIL_MODE:
+ case user::LOGGED_IN_KIOSK_APP:
+ case user::LOGGED_IN_NONE:
+ return false;
+ }
+
+ DCHECK(false);
+ return false;
+}
+
+ShelfAlignment ShelfWidget::GetAlignment() const {
+ return shelf_layout_manager_->GetAlignment();
+}
+
+void ShelfWidget::SetAlignment(ShelfAlignment alignment) {
+ if (launcher_)
+ launcher_->SetAlignment(alignment);
+ status_area_widget_->SetShelfAlignment(alignment);
+ delegate_view_->SchedulePaint();
+}
+
+void ShelfWidget::SetDimsShelf(bool dimming) {
+ delegate_view_->SetDimmed(dimming);
+ // Repaint all children, allowing updates to reflect dimmed state eg:
+ // status area background, app list button and overflow button.
+ if (launcher_)
+ launcher_->SchedulePaint();
+ status_area_widget_->GetContentsView()->SchedulePaint();
+}
+
+bool ShelfWidget::GetDimsShelf() const {
+ return delegate_view_->GetDimmed();
+}
+
+void ShelfWidget::CreateLauncher() {
+ if (launcher_)
+ return;
+
+ Shell* shell = Shell::GetInstance();
+ // This needs to be called before launcher_model().
+ LauncherDelegate* launcher_delegate = shell->GetLauncherDelegate();
+ if (!launcher_delegate)
+ return; // Not ready to create Launcher
+
+ launcher_.reset(new Launcher(shell->launcher_model(),
+ shell->GetLauncherDelegate(),
+ this));
+ SetFocusCycler(shell->focus_cycler());
+
+ // Inform the root window controller.
+ internal::RootWindowController::ForWindow(window_container_)->
+ OnLauncherCreated();
+
+ launcher_->SetVisible(
+ shell->session_state_delegate()->IsActiveUserSessionStarted());
+ shelf_layout_manager_->LayoutShelf();
+ Show();
+}
+
+bool ShelfWidget::IsLauncherVisible() const {
+ return launcher_.get() && launcher_->IsVisible();
+}
+
+void ShelfWidget::SetLauncherVisibility(bool visible) {
+ if (launcher_)
+ launcher_->SetVisible(visible);
+}
+
+void ShelfWidget::SetFocusCycler(internal::FocusCycler* focus_cycler) {
+ delegate_view_->set_focus_cycler(focus_cycler);
+ if (focus_cycler)
+ focus_cycler->AddWidget(this);
+}
+
+internal::FocusCycler* ShelfWidget::GetFocusCycler() {
+ return delegate_view_->focus_cycler();
+}
+
+void ShelfWidget::ShutdownStatusAreaWidget() {
+ if (status_area_widget_)
+ status_area_widget_->Shutdown();
+ status_area_widget_ = NULL;
+}
+
+void ShelfWidget::ForceUndimming(bool force) {
+ delegate_view_->ForceUndimming(force);
+}
+
+void ShelfWidget::OnWidgetActivationChanged(views::Widget* widget,
+ bool active) {
+ activating_as_fallback_ = false;
+ if (active)
+ delegate_view_->SetPaneFocusAndFocusDefault();
+ else
+ delegate_view_->GetFocusManager()->ClearFocus();
+}
+
+int ShelfWidget::GetDimmingAlphaForTest() {
+ if (delegate_view_)
+ return delegate_view_->GetDimmingAlphaForTest();
+ return -1;
+}
+
+gfx::Rect ShelfWidget::GetDimmerBoundsForTest() {
+ if (delegate_view_)
+ return delegate_view_->GetDimmerBoundsForTest();
+ return gfx::Rect();
+}
+
+void ShelfWidget::DisableDimmingAnimationsForTest() {
+ DCHECK(delegate_view_);
+ return delegate_view_->disable_dimming_animations_for_test();
+}
+
+} // namespace ash
diff --git a/chromium/ash/shelf/shelf_widget.h b/chromium/ash/shelf/shelf_widget.h
new file mode 100644
index 00000000000..c7adb742555
--- /dev/null
+++ b/chromium/ash/shelf/shelf_widget.h
@@ -0,0 +1,117 @@
+// Copyright (c) 2012 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 ASH_SHELF_SHELF_WIDGET_H_
+#define ASH_SHELF_SHELF_WIDGET_H_
+
+#include "ash/ash_export.h"
+#include "ash/shelf/background_animator.h"
+#include "ash/shelf/shelf_types.h"
+#include "ui/views/widget/widget.h"
+#include "ui/views/widget/widget_observer.h"
+
+namespace aura {
+class Window;
+}
+
+namespace ash {
+class Launcher;
+
+namespace internal {
+class FocusCycler;
+class StatusAreaWidget;
+class ShelfLayoutManager;
+class WorkspaceController;
+}
+
+class ASH_EXPORT ShelfWidget : public views::Widget,
+ public views::WidgetObserver {
+ public:
+ ShelfWidget(
+ aura::Window* shelf_container,
+ aura::Window* status_container,
+ internal::WorkspaceController* workspace_controller);
+ virtual ~ShelfWidget();
+
+ // Returns if shelf alignment option is enabled, and the user is able
+ // to adjust the alignment (guest and supervised mode users cannot for
+ // example).
+ static bool ShelfAlignmentAllowed();
+
+ void SetAlignment(ShelfAlignment alignmnet);
+ ShelfAlignment GetAlignment() const;
+
+ // Sets the shelf's background type.
+ void SetPaintsBackground(
+ ShelfBackgroundType background_type,
+ internal::BackgroundAnimator::ChangeType change_type);
+ ShelfBackgroundType GetBackgroundType() const;
+
+ // Causes shelf items to be slightly dimmed (eg when a window is maximized).
+ void SetDimsShelf(bool dimming);
+ bool GetDimsShelf() const;
+
+ internal::ShelfLayoutManager* shelf_layout_manager() {
+ return shelf_layout_manager_;
+ }
+ Launcher* launcher() const { return launcher_.get(); }
+ internal::StatusAreaWidget* status_area_widget() const {
+ return status_area_widget_;
+ }
+
+ void CreateLauncher();
+
+ // Set visibility of the launcher component of the shelf.
+ void SetLauncherVisibility(bool visible);
+ bool IsLauncherVisible() const;
+
+ // Sets the focus cycler. Also adds the launcher to the cycle.
+ void SetFocusCycler(internal::FocusCycler* focus_cycler);
+ internal::FocusCycler* GetFocusCycler();
+
+ // Called by the activation delegate, before the launcher is activated
+ // when no other windows are visible.
+ void WillActivateAsFallback() { activating_as_fallback_ = true; }
+
+ aura::Window* window_container() { return window_container_; }
+
+ // TODO(harrym): Remove when Status Area Widget is a child view.
+ void ShutdownStatusAreaWidget();
+
+ // Force the shelf to be presented in an undimmed state.
+ void ForceUndimming(bool force);
+
+ // Overridden from views::WidgetObserver:
+ virtual void OnWidgetActivationChanged(
+ views::Widget* widget, bool active) OVERRIDE;
+
+ // A function to test the current alpha used by the dimming bar. If there is
+ // no dimmer active, the function will return -1.
+ int GetDimmingAlphaForTest();
+
+ // A function to test the bounds of the dimming bar. Returns gfx::Rect() if
+ // the dimmer is inactive.
+ gfx::Rect GetDimmerBoundsForTest();
+
+ // Disable dimming animations for running tests.
+ void DisableDimmingAnimationsForTest();
+
+ private:
+ class DelegateView;
+
+ internal::ShelfLayoutManager* shelf_layout_manager_;
+ scoped_ptr<Launcher> launcher_;
+ internal::StatusAreaWidget* status_area_widget_;
+
+ // delegate_view_ is attached to window_container_ and is cleaned up
+ // during CloseChildWindows of the associated RootWindowController.
+ DelegateView* delegate_view_;
+ internal::BackgroundAnimator background_animator_;
+ bool activating_as_fallback_;
+ aura::Window* window_container_;
+};
+
+} // namespace ash
+
+#endif // ASH_SHELF_SHELF_WIDGET_H_
diff --git a/chromium/ash/shelf/shelf_widget_unittest.cc b/chromium/ash/shelf/shelf_widget_unittest.cc
new file mode 100644
index 00000000000..f043f342fe6
--- /dev/null
+++ b/chromium/ash/shelf/shelf_widget_unittest.cc
@@ -0,0 +1,195 @@
+// Copyright (c) 2013 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.
+
+#include "ash/shelf/shelf_widget.h"
+
+#include "ash/launcher/launcher.h"
+#include "ash/launcher/launcher_button.h"
+#include "ash/launcher/launcher_model.h"
+#include "ash/launcher/launcher_view.h"
+#include "ash/root_window_controller.h"
+#include "ash/shelf/shelf_layout_manager.h"
+#include "ash/shell.h"
+#include "ash/test/ash_test_base.h"
+#include "ash/test/launcher_view_test_api.h"
+#include "ash/wm/window_util.h"
+#include "ui/aura/root_window.h"
+#include "ui/gfx/display.h"
+#include "ui/gfx/screen.h"
+#include "ui/views/corewm/corewm_switches.h"
+#include "ui/views/view.h"
+#include "ui/views/widget/widget.h"
+
+namespace ash {
+
+namespace {
+
+ShelfWidget* GetShelfWidget() {
+ return Launcher::ForPrimaryDisplay()->shelf_widget();
+}
+
+internal::ShelfLayoutManager* GetShelfLayoutManager() {
+ return GetShelfWidget()->shelf_layout_manager();
+}
+
+} // namespace
+
+typedef test::AshTestBase ShelfWidgetTest;
+
+// Launcher can't be activated on mouse click, but it is activable from
+// the focus cycler or as fallback.
+TEST_F(ShelfWidgetTest, ActivateAsFallback) {
+ // TODO(mtomasz): make this test work with the FocusController.
+ if (views::corewm::UseFocusController())
+ return;
+
+ Launcher* launcher = Launcher::ForPrimaryDisplay();
+ ShelfWidget* shelf_widget = launcher->shelf_widget();
+ EXPECT_FALSE(shelf_widget->CanActivate());
+
+ shelf_widget->WillActivateAsFallback();
+ EXPECT_TRUE(shelf_widget->CanActivate());
+
+ wm::ActivateWindow(shelf_widget->GetNativeWindow());
+ EXPECT_FALSE(shelf_widget->CanActivate());
+}
+
+void TestLauncherAlignment(aura::RootWindow* root,
+ ShelfAlignment alignment,
+ const std::string& expected) {
+ Shell::GetInstance()->SetShelfAlignment(alignment, root);
+ gfx::Screen* screen = gfx::Screen::GetScreenFor(root);
+ EXPECT_EQ(expected,
+ screen->GetDisplayNearestWindow(root).work_area().ToString());
+}
+
+TEST_F(ShelfWidgetTest, TestAlignment) {
+ Launcher* launcher = Launcher::ForPrimaryDisplay();
+ UpdateDisplay("400x400");
+ ASSERT_TRUE(launcher);
+ {
+ SCOPED_TRACE("Single Bottom");
+ TestLauncherAlignment(Shell::GetPrimaryRootWindow(),
+ SHELF_ALIGNMENT_BOTTOM,
+ "0,0 400x352");
+ }
+ {
+ SCOPED_TRACE("Single Right");
+ TestLauncherAlignment(Shell::GetPrimaryRootWindow(),
+ SHELF_ALIGNMENT_RIGHT,
+ "0,0 352x400");
+ }
+ {
+ SCOPED_TRACE("Single Left");
+ TestLauncherAlignment(Shell::GetPrimaryRootWindow(),
+ SHELF_ALIGNMENT_LEFT,
+ "48,0 352x400");
+ }
+ if (!SupportsMultipleDisplays())
+ return;
+
+ UpdateDisplay("300x300,500x500");
+ Shell::RootWindowList root_windows = Shell::GetAllRootWindows();
+ {
+ SCOPED_TRACE("Primary Bottom");
+ TestLauncherAlignment(root_windows[0],
+ SHELF_ALIGNMENT_BOTTOM,
+ "0,0 300x252");
+ }
+ {
+ SCOPED_TRACE("Primary Right");
+ TestLauncherAlignment(root_windows[0],
+ SHELF_ALIGNMENT_RIGHT,
+ "0,0 252x300");
+ }
+ {
+ SCOPED_TRACE("Primary Left");
+ TestLauncherAlignment(root_windows[0],
+ SHELF_ALIGNMENT_LEFT,
+ "48,0 252x300");
+ }
+ {
+ SCOPED_TRACE("Secondary Bottom");
+ TestLauncherAlignment(root_windows[1],
+ SHELF_ALIGNMENT_BOTTOM,
+ "300,0 500x452");
+ }
+ {
+ SCOPED_TRACE("Secondary Right");
+ TestLauncherAlignment(root_windows[1],
+ SHELF_ALIGNMENT_RIGHT,
+ "300,0 452x500");
+ }
+ {
+ SCOPED_TRACE("Secondary Left");
+ TestLauncherAlignment(root_windows[1],
+ SHELF_ALIGNMENT_LEFT,
+ "348,0 452x500");
+ }
+}
+
+// Makes sure the launcher is initially sized correctly.
+TEST_F(ShelfWidgetTest, LauncherInitiallySized) {
+ ShelfWidget* shelf_widget = GetShelfWidget();
+ Launcher* launcher = shelf_widget->launcher();
+ ASSERT_TRUE(launcher);
+ internal::ShelfLayoutManager* shelf_layout_manager = GetShelfLayoutManager();
+ ASSERT_TRUE(shelf_layout_manager);
+ ASSERT_TRUE(shelf_widget->status_area_widget());
+ int status_width = shelf_widget->status_area_widget()->
+ GetWindowBoundsInScreen().width();
+ // Test only makes sense if the status is > 0, which it better be.
+ EXPECT_GT(status_width, 0);
+ EXPECT_EQ(status_width, shelf_widget->GetContentsView()->width() -
+ launcher->GetLauncherViewForTest()->width());
+}
+
+// Verifies when the shell is deleted with a full screen window we don't crash.
+TEST_F(ShelfWidgetTest, DontReferenceLauncherAfterDeletion) {
+ views::Widget* widget = new views::Widget;
+ views::Widget::InitParams params(views::Widget::InitParams::TYPE_WINDOW);
+ params.bounds = gfx::Rect(0, 0, 200, 200);
+ params.context = CurrentContext();
+ // Widget is now owned by the parent window.
+ widget->Init(params);
+ widget->SetFullscreen(true);
+}
+
+#if defined(OS_CHROMEOS)
+// Verifies launcher is created with correct size after user login and when its
+// container and status widget has finished sizing.
+// See http://crbug.com/252533
+TEST_F(ShelfWidgetTest, LauncherInitiallySizedAfterLogin) {
+ SetUserLoggedIn(false);
+ UpdateDisplay("300x200,400x300");
+
+ ShelfWidget* shelf = NULL;
+ Shell::RootWindowControllerList controllers(
+ Shell::GetAllRootWindowControllers());
+ for (Shell::RootWindowControllerList::const_iterator i = controllers.begin();
+ i != controllers.end();
+ ++i) {
+ if (!(*i)->shelf()->launcher()) {
+ shelf = (*i)->shelf();
+ break;
+ }
+ }
+ ASSERT_TRUE(shelf != NULL);
+
+ SetUserLoggedIn(true);
+ Shell::GetInstance()->CreateLauncher();
+
+ Launcher* launcher = shelf->launcher();
+ ASSERT_TRUE(launcher != NULL);
+
+ const int status_width =
+ shelf->status_area_widget()->GetWindowBoundsInScreen().width();
+ EXPECT_GT(status_width, 0);
+ EXPECT_EQ(status_width,
+ shelf->GetContentsView()->width() -
+ launcher->GetLauncherViewForTest()->width());
+}
+#endif
+
+} // namespace ash
diff --git a/chromium/ash/shell.cc b/chromium/ash/shell.cc
new file mode 100644
index 00000000000..996c817213c
--- /dev/null
+++ b/chromium/ash/shell.cc
@@ -0,0 +1,993 @@
+// Copyright (c) 2012 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.
+
+#include "ash/shell.h"
+
+#include <algorithm>
+#include <string>
+
+#include "ash/accelerators/focus_manager_factory.h"
+#include "ash/ash_switches.h"
+#include "ash/caps_lock_delegate.h"
+#include "ash/desktop_background/desktop_background_controller.h"
+#include "ash/desktop_background/desktop_background_view.h"
+#include "ash/desktop_background/user_wallpaper_delegate.h"
+#include "ash/display/display_controller.h"
+#include "ash/display/display_manager.h"
+#include "ash/display/event_transformation_handler.h"
+#include "ash/display/mouse_cursor_event_filter.h"
+#include "ash/display/resolution_notification_controller.h"
+#include "ash/display/screen_position_controller.h"
+#include "ash/drag_drop/drag_drop_controller.h"
+#include "ash/focus_cycler.h"
+#include "ash/high_contrast/high_contrast_controller.h"
+#include "ash/host/root_window_host_factory.h"
+#include "ash/launcher/launcher_delegate.h"
+#include "ash/launcher/launcher_model.h"
+#include "ash/magnifier/magnification_controller.h"
+#include "ash/magnifier/partial_magnification_controller.h"
+#include "ash/root_window_controller.h"
+#include "ash/screen_ash.h"
+#include "ash/session_state_delegate.h"
+#include "ash/shelf/shelf_layout_manager.h"
+#include "ash/shelf/shelf_widget.h"
+#include "ash/shell_delegate.h"
+#include "ash/shell_factory.h"
+#include "ash/shell_window_ids.h"
+#include "ash/system/locale/locale_notification_controller.h"
+#include "ash/system/status_area_widget.h"
+#include "ash/system/tray/system_tray_delegate.h"
+#include "ash/system/tray/system_tray_notifier.h"
+#include "ash/wm/activation_controller.h"
+#include "ash/wm/app_list_controller.h"
+#include "ash/wm/ash_activation_controller.h"
+#include "ash/wm/ash_focus_rules.h"
+#include "ash/wm/ash_native_cursor_manager.h"
+#include "ash/wm/base_layout_manager.h"
+#include "ash/wm/capture_controller.h"
+#include "ash/wm/coordinate_conversion.h"
+#include "ash/wm/custom_frame_view_ash.h"
+#include "ash/wm/event_client_impl.h"
+#include "ash/wm/event_rewriter_event_filter.h"
+#include "ash/wm/lock_state_controller.h"
+#include "ash/wm/lock_state_controller_impl2.h"
+#include "ash/wm/mru_window_tracker.h"
+#include "ash/wm/overlay_event_filter.h"
+#include "ash/wm/power_button_controller.h"
+#include "ash/wm/property_util.h"
+#include "ash/wm/resize_shadow_controller.h"
+#include "ash/wm/root_window_layout_manager.h"
+#include "ash/wm/screen_dimmer.h"
+#include "ash/wm/session_state_controller_impl.h"
+#include "ash/wm/system_gesture_event_filter.h"
+#include "ash/wm/system_modal_container_event_filter.h"
+#include "ash/wm/system_modal_container_layout_manager.h"
+#include "ash/wm/user_activity_detector.h"
+#include "ash/wm/video_detector.h"
+#include "ash/wm/window_animations.h"
+#include "ash/wm/window_cycle_controller.h"
+#include "ash/wm/window_properties.h"
+#include "ash/wm/window_selector_controller.h"
+#include "ash/wm/window_util.h"
+#include "ash/wm/workspace_controller.h"
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "base/debug/leak_annotations.h"
+#include "ui/aura/client/aura_constants.h"
+#include "ui/aura/client/user_action_client.h"
+#include "ui/aura/env.h"
+#include "ui/aura/focus_manager.h"
+#include "ui/aura/layout_manager.h"
+#include "ui/aura/root_window.h"
+#include "ui/aura/window.h"
+#include "ui/base/ui_base_switches.h"
+#include "ui/compositor/layer.h"
+#include "ui/compositor/layer_animator.h"
+#include "ui/gfx/display.h"
+#include "ui/gfx/image/image_skia.h"
+#include "ui/gfx/screen.h"
+#include "ui/gfx/size.h"
+#include "ui/keyboard/keyboard.h"
+#include "ui/keyboard/keyboard_util.h"
+#include "ui/message_center/message_center.h"
+#include "ui/views/corewm/compound_event_filter.h"
+#include "ui/views/corewm/corewm_switches.h"
+#include "ui/views/corewm/focus_controller.h"
+#include "ui/views/corewm/input_method_event_filter.h"
+#include "ui/views/corewm/shadow_controller.h"
+#include "ui/views/corewm/tooltip_controller.h"
+#include "ui/views/corewm/visibility_controller.h"
+#include "ui/views/corewm/window_modality_controller.h"
+#include "ui/views/focus/focus_manager_factory.h"
+#include "ui/views/widget/native_widget_aura.h"
+#include "ui/views/widget/widget.h"
+
+#if !defined(OS_MACOSX)
+#include "ash/accelerators/accelerator_controller.h"
+#include "ash/accelerators/accelerator_filter.h"
+#include "ash/accelerators/nested_dispatcher_controller.h"
+#endif
+
+#if defined(OS_CHROMEOS)
+#if defined(USE_X11)
+#include "ash/ash_constants.h"
+#include "ash/display/display_change_observer_x11.h"
+#include "ash/display/display_error_observer.h"
+#include "ash/display/output_configurator_animation.h"
+#include "base/chromeos/chromeos_version.h"
+#include "base/message_loop/message_pump_aurax11.h"
+#include "chromeos/display/output_configurator.h"
+#include "content/public/browser/gpu_data_manager.h"
+#include "content/public/common/content_switches.h"
+#include "gpu/config/gpu_feature_type.h"
+#endif // defined(USE_X11)
+#include "ash/system/chromeos/power/power_status.h"
+#endif // defined(OS_CHROMEOS)
+
+namespace ash {
+
+namespace {
+
+using aura::Window;
+using views::Widget;
+
+// This dummy class is used for shell unit tests. We dont have chrome delegate
+// in these tests.
+class DummyUserWallpaperDelegate : public UserWallpaperDelegate {
+ public:
+ DummyUserWallpaperDelegate() {}
+
+ virtual ~DummyUserWallpaperDelegate() {}
+
+ virtual int GetAnimationType() OVERRIDE {
+ return views::corewm::WINDOW_VISIBILITY_ANIMATION_TYPE_FADE;
+ }
+
+ virtual bool ShouldShowInitialAnimation() OVERRIDE {
+ return false;
+ }
+
+ virtual void UpdateWallpaper() OVERRIDE {
+ }
+
+ virtual void InitializeWallpaper() OVERRIDE {
+ ash::Shell::GetInstance()->desktop_background_controller()->
+ CreateEmptyWallpaper();
+ }
+
+ virtual void OpenSetWallpaperPage() OVERRIDE {
+ }
+
+ virtual bool CanOpenSetWallpaperPage() OVERRIDE {
+ return false;
+ }
+
+ virtual void OnWallpaperAnimationFinished() OVERRIDE {
+ }
+
+ virtual void OnWallpaperBootAnimationFinished() OVERRIDE {
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(DummyUserWallpaperDelegate);
+};
+
+// A Corewm VisibilityController subclass that calls the Ash animation routine
+// so we can pick up our extended animations. See ash/wm/window_animations.h.
+class AshVisibilityController : public views::corewm::VisibilityController {
+ public:
+ AshVisibilityController() {}
+ virtual ~AshVisibilityController() {}
+
+ private:
+ // Overridden from views::corewm::VisibilityController:
+ virtual bool CallAnimateOnChildWindowVisibilityChanged(
+ aura::Window* window,
+ bool visible) OVERRIDE {
+ return AnimateOnChildWindowVisibilityChanged(window, visible);
+ }
+
+ DISALLOW_COPY_AND_ASSIGN(AshVisibilityController);
+};
+
+} // namespace
+
+// static
+Shell* Shell::instance_ = NULL;
+// static
+bool Shell::initially_hide_cursor_ = false;
+
+////////////////////////////////////////////////////////////////////////////////
+// Shell, public:
+
+Shell::Shell(ShellDelegate* delegate)
+ : screen_(new ScreenAsh),
+ target_root_window_(NULL),
+ scoped_target_root_window_(NULL),
+ delegate_(delegate),
+ activation_client_(NULL),
+#if defined(OS_CHROMEOS) && defined(USE_X11)
+ output_configurator_(new chromeos::OutputConfigurator()),
+#endif // defined(OS_CHROMEOS)
+ native_cursor_manager_(new AshNativeCursorManager),
+ cursor_manager_(scoped_ptr<views::corewm::NativeCursorManager>(
+ native_cursor_manager_)),
+ browser_context_(NULL),
+ simulate_modal_window_open_for_testing_(false),
+ is_touch_hud_projection_enabled_(false) {
+ DCHECK(delegate_.get());
+ display_manager_.reset(new internal::DisplayManager);
+
+ ANNOTATE_LEAKING_OBJECT_PTR(screen_); // see crbug.com/156466
+ gfx::Screen::SetScreenInstance(gfx::SCREEN_TYPE_ALTERNATE, screen_);
+ if (!gfx::Screen::GetScreenByType(gfx::SCREEN_TYPE_NATIVE))
+ gfx::Screen::SetScreenInstance(gfx::SCREEN_TYPE_NATIVE, screen_);
+ display_controller_.reset(new DisplayController);
+#if defined(OS_CHROMEOS) && defined(USE_X11)
+ bool is_panel_fitting_disabled =
+ content::GpuDataManager::GetInstance()->IsFeatureBlacklisted(
+ gpu::GPU_FEATURE_TYPE_PANEL_FITTING) ||
+ CommandLine::ForCurrentProcess()->HasSwitch(
+ ::switches::kDisablePanelFitting);
+
+ output_configurator_->Init(!is_panel_fitting_disabled);
+
+ base::MessagePumpAuraX11::Current()->AddDispatcherForRootWindow(
+ output_configurator());
+ // We can't do this with a root window listener because XI_HierarchyChanged
+ // messages don't have a target window.
+ base::MessagePumpAuraX11::Current()->AddObserver(output_configurator());
+#endif // defined(OS_CHROMEOS)
+ AddPreTargetHandler(this);
+
+#if defined(OS_CHROMEOS)
+ internal::PowerStatus::Initialize();
+#endif
+}
+
+Shell::~Shell() {
+ views::FocusManagerFactory::Install(NULL);
+
+ // Remove the focus from any window. This will prevent overhead and side
+ // effects (e.g. crashes) from changing focus during shutdown.
+ // See bug crbug.com/134502.
+ aura::client::GetFocusClient(GetPrimaryRootWindow())->FocusWindow(NULL);
+
+ // Please keep in same order as in Init() because it's easy to miss one.
+ RemovePreTargetHandler(event_rewriter_filter_.get());
+ RemovePreTargetHandler(user_activity_detector_.get());
+ RemovePreTargetHandler(overlay_filter_.get());
+ RemovePreTargetHandler(input_method_filter_.get());
+ RemovePreTargetHandler(window_modality_controller_.get());
+ if (mouse_cursor_filter_)
+ RemovePreTargetHandler(mouse_cursor_filter_.get());
+ RemovePreTargetHandler(system_gesture_filter_.get());
+ RemovePreTargetHandler(event_transformation_handler_.get());
+#if !defined(OS_MACOSX)
+ RemovePreTargetHandler(accelerator_filter_.get());
+#endif
+
+ // TooltipController is deleted with the Shell so removing its references.
+ RemovePreTargetHandler(tooltip_controller_.get());
+
+ // AppList needs to be released before shelf layout manager, which is
+ // destroyed with launcher container in the loop below. However, app list
+ // container is now on top of launcher container and released after it.
+ // TODO(xiyuan): Move it back when app list container is no longer needed.
+ app_list_controller_.reset();
+
+ // Destroy SystemTrayDelegate before destroying the status area(s).
+ system_tray_delegate_->Shutdown();
+ system_tray_delegate_.reset();
+
+ locale_notification_controller_.reset();
+
+ // Destroy all child windows including widgets.
+ display_controller_->CloseChildWindows();
+
+ // Destroy SystemTrayNotifier after destroying SystemTray as TrayItems
+ // needs to remove observers from it.
+ system_tray_notifier_.reset();
+
+ // These need a valid Shell instance to clean up properly, so explicitly
+ // delete them before invalidating the instance.
+ // Alphabetical. TODO(oshima): sort.
+ drag_drop_controller_.reset();
+ magnification_controller_.reset();
+ partial_magnification_controller_.reset();
+ resize_shadow_controller_.reset();
+ shadow_controller_.reset();
+ tooltip_controller_.reset();
+ event_client_.reset();
+ window_cycle_controller_.reset();
+ capture_controller_.reset();
+ nested_dispatcher_controller_.reset();
+ user_action_client_.reset();
+ visibility_controller_.reset();
+ launcher_delegate_.reset();
+ launcher_model_.reset();
+ video_detector_.reset();
+
+ power_button_controller_.reset();
+ lock_state_controller_.reset();
+ mru_window_tracker_.reset();
+
+ resolution_notification_controller_.reset();
+
+ // This also deletes all RootWindows. Note that we invoke Shutdown() on
+ // DisplayController before resetting |display_controller_|, since destruction
+ // of its owned RootWindowControllers relies on the value.
+ display_controller_->Shutdown();
+ display_controller_.reset();
+ screen_position_controller_.reset();
+
+ // Delete the activation controller after other controllers and launcher
+ // because they might have registered ActivationChangeObserver.
+ activation_controller_.reset();
+
+#if defined(OS_CHROMEOS) && defined(USE_X11)
+ if (display_change_observer_)
+ output_configurator_->RemoveObserver(display_change_observer_.get());
+ if (output_configurator_animation_)
+ output_configurator_->RemoveObserver(output_configurator_animation_.get());
+ if (display_error_observer_)
+ output_configurator_->RemoveObserver(display_error_observer_.get());
+ base::MessagePumpAuraX11::Current()->RemoveDispatcherForRootWindow(
+ output_configurator());
+ base::MessagePumpAuraX11::Current()->RemoveObserver(output_configurator());
+ display_change_observer_.reset();
+#endif // defined(OS_CHROMEOS)
+
+#if defined(OS_CHROMEOS)
+ internal::PowerStatus::Shutdown();
+#endif
+
+ DCHECK(instance_ == this);
+ instance_ = NULL;
+}
+
+// static
+Shell* Shell::CreateInstance(ShellDelegate* delegate) {
+ CHECK(!instance_);
+ instance_ = new Shell(delegate);
+ instance_->Init();
+ return instance_;
+}
+
+// static
+Shell* Shell::GetInstance() {
+ DCHECK(instance_);
+ return instance_;
+}
+
+// static
+bool Shell::HasInstance() {
+ return !!instance_;
+}
+
+// static
+void Shell::DeleteInstance() {
+ delete instance_;
+ instance_ = NULL;
+}
+
+// static
+internal::RootWindowController* Shell::GetPrimaryRootWindowController() {
+ return GetRootWindowController(GetPrimaryRootWindow());
+}
+
+// static
+Shell::RootWindowControllerList Shell::GetAllRootWindowControllers() {
+ return Shell::GetInstance()->display_controller()->
+ GetAllRootWindowControllers();
+}
+
+// static
+aura::RootWindow* Shell::GetPrimaryRootWindow() {
+ return GetInstance()->display_controller()->GetPrimaryRootWindow();
+}
+
+// static
+aura::RootWindow* Shell::GetActiveRootWindow() {
+ Shell* shell = GetInstance();
+ if (shell->scoped_target_root_window_)
+ return shell->scoped_target_root_window_;
+ return shell->target_root_window_;
+}
+
+// static
+gfx::Screen* Shell::GetScreen() {
+ return gfx::Screen::GetScreenByType(gfx::SCREEN_TYPE_ALTERNATE);
+}
+
+// static
+Shell::RootWindowList Shell::GetAllRootWindows() {
+ return Shell::GetInstance()->display_controller()->
+ GetAllRootWindows();
+}
+
+// static
+aura::Window* Shell::GetContainer(aura::RootWindow* root_window,
+ int container_id) {
+ return root_window->GetChildById(container_id);
+}
+
+// static
+const aura::Window* Shell::GetContainer(const aura::RootWindow* root_window,
+ int container_id) {
+ return root_window->GetChildById(container_id);
+}
+
+// static
+std::vector<aura::Window*> Shell::GetContainersFromAllRootWindows(
+ int container_id,
+ aura::RootWindow* priority_root) {
+ std::vector<aura::Window*> containers;
+ RootWindowList root_windows = GetAllRootWindows();
+ for (RootWindowList::const_iterator it = root_windows.begin();
+ it != root_windows.end(); ++it) {
+ aura::Window* container = (*it)->GetChildById(container_id);
+ if (container) {
+ if (priority_root && priority_root->Contains(container))
+ containers.insert(containers.begin(), container);
+ else
+ containers.push_back(container);
+ }
+ }
+ return containers;
+}
+
+// static
+bool Shell::IsForcedMaximizeMode() {
+ CommandLine* command_line = CommandLine::ForCurrentProcess();
+ return command_line->HasSwitch(switches::kForcedMaximizeMode);
+}
+
+void Shell::Init() {
+ CommandLine* command_line = CommandLine::ForCurrentProcess();
+
+ delegate_->PreInit();
+ bool display_initialized = false;
+#if defined(OS_CHROMEOS) && defined(USE_X11)
+ output_configurator_animation_.reset(
+ new internal::OutputConfiguratorAnimation());
+ output_configurator_->AddObserver(output_configurator_animation_.get());
+ if (base::chromeos::IsRunningOnChromeOS()) {
+ display_change_observer_.reset(new internal::DisplayChangeObserverX11);
+ // Register |display_change_observer_| first so that the rest of
+ // observer gets invoked after the root windows are configured.
+ output_configurator_->AddObserver(display_change_observer_.get());
+ display_error_observer_.reset(new internal::DisplayErrorObserver());
+ output_configurator_->AddObserver(display_error_observer_.get());
+ output_configurator_->set_state_controller(display_change_observer_.get());
+ if (!command_line->HasSwitch(ash::switches::kAshDisableSoftwareMirroring))
+ output_configurator_->set_mirroring_controller(display_manager_.get());
+ output_configurator_->Start(
+ delegate_->IsFirstRunAfterBoot() ? kChromeOsBootColor : 0);
+ display_initialized = true;
+ }
+#endif
+ if (!display_initialized)
+ display_manager_->InitFromCommandLine();
+
+ // Install the custom factory first so that views::FocusManagers for Tray,
+ // Launcher, and WallPaper could be created by the factory.
+ views::FocusManagerFactory::Install(new AshFocusManagerFactory);
+
+ env_filter_.reset(new views::corewm::CompoundEventFilter);
+ AddPreTargetHandler(env_filter_.get());
+
+ // Env creates the compositor. Historically it seems to have been implicitly
+ // initialized first by the ActivationController, but now that FocusController
+ // no longer does this we need to do it explicitly.
+ aura::Env::GetInstance();
+ if (views::corewm::UseFocusController()) {
+ views::corewm::FocusController* focus_controller =
+ new views::corewm::FocusController(new wm::AshFocusRules);
+ focus_client_.reset(focus_controller);
+ activation_client_ = focus_controller;
+ activation_client_->AddObserver(this);
+ } else {
+ focus_client_.reset(new aura::FocusManager);
+ activation_controller_.reset(
+ new internal::ActivationController(
+ focus_client_.get(),
+ new internal::AshActivationController));
+ activation_client_ = activation_controller_.get();
+ AddPreTargetHandler(activation_controller_.get());
+ }
+
+ focus_cycler_.reset(new internal::FocusCycler());
+
+ screen_position_controller_.reset(new internal::ScreenPositionController);
+ root_window_host_factory_.reset(delegate_->CreateRootWindowHostFactory());
+
+ display_controller_->Start();
+ display_controller_->InitPrimaryDisplay();
+ aura::RootWindow* root_window = display_controller_->GetPrimaryRootWindow();
+ target_root_window_ = root_window;
+
+ resolution_notification_controller_.reset(
+ new internal::ResolutionNotificationController);
+
+ cursor_manager_.SetDisplay(DisplayController::GetPrimaryDisplay());
+
+#if !defined(OS_MACOSX)
+ nested_dispatcher_controller_.reset(new NestedDispatcherController);
+ accelerator_controller_.reset(new AcceleratorController);
+#endif
+
+ // The order in which event filters are added is significant.
+ event_rewriter_filter_.reset(new internal::EventRewriterEventFilter);
+ AddPreTargetHandler(event_rewriter_filter_.get());
+
+ // UserActivityDetector passes events to observers, so let them get
+ // rewritten first.
+ user_activity_detector_.reset(new UserActivityDetector);
+ AddPreTargetHandler(user_activity_detector_.get());
+
+ overlay_filter_.reset(new internal::OverlayEventFilter);
+ AddPreTargetHandler(overlay_filter_.get());
+ AddShellObserver(overlay_filter_.get());
+
+ input_method_filter_.reset(new views::corewm::InputMethodEventFilter(
+ root_window->GetAcceleratedWidget()));
+ AddPreTargetHandler(input_method_filter_.get());
+
+#if !defined(OS_MACOSX)
+ accelerator_filter_.reset(new internal::AcceleratorFilter);
+ AddPreTargetHandler(accelerator_filter_.get());
+#endif
+
+ event_transformation_handler_.reset(new internal::EventTransformationHandler);
+ AddPreTargetHandler(event_transformation_handler_.get());
+
+ system_gesture_filter_.reset(new internal::SystemGestureEventFilter);
+ AddPreTargetHandler(system_gesture_filter_.get());
+
+ capture_controller_.reset(new internal::CaptureController);
+
+ // The keyboard system must be initialized before the RootWindowController is
+ // created.
+ if (keyboard::IsKeyboardEnabled())
+ keyboard::InitializeKeyboard();
+
+ if (command_line->HasSwitch(ash::switches::kAshDisableNewLockAnimations))
+ lock_state_controller_.reset(new SessionStateControllerImpl);
+ else
+ lock_state_controller_.reset(new LockStateControllerImpl2);
+ power_button_controller_.reset(new PowerButtonController(
+ lock_state_controller_.get()));
+ AddShellObserver(lock_state_controller_.get());
+
+ drag_drop_controller_.reset(new internal::DragDropController);
+ mouse_cursor_filter_.reset(new internal::MouseCursorEventFilter());
+ PrependPreTargetHandler(mouse_cursor_filter_.get());
+
+ // Create Controllers that may need root window.
+ // TODO(oshima): Move as many controllers before creating
+ // RootWindowController as possible.
+ visibility_controller_.reset(new AshVisibilityController);
+ user_action_client_.reset(delegate_->CreateUserActionClient());
+ window_modality_controller_.reset(
+ new views::corewm::WindowModalityController);
+ AddPreTargetHandler(window_modality_controller_.get());
+
+ magnification_controller_.reset(
+ MagnificationController::CreateInstance());
+ mru_window_tracker_.reset(new MruWindowTracker(activation_client_));
+
+ partial_magnification_controller_.reset(
+ new PartialMagnificationController());
+
+ high_contrast_controller_.reset(new HighContrastController);
+ video_detector_.reset(new VideoDetector);
+ window_cycle_controller_.reset(new WindowCycleController());
+ window_selector_controller_.reset(new WindowSelectorController());
+
+ tooltip_controller_.reset(new views::corewm::TooltipController(
+ gfx::SCREEN_TYPE_ALTERNATE));
+ AddPreTargetHandler(tooltip_controller_.get());
+
+ event_client_.reset(new internal::EventClientImpl);
+
+ // This controller needs to be set before SetupManagedWindowMode.
+ desktop_background_controller_.reset(new DesktopBackgroundController());
+ user_wallpaper_delegate_.reset(delegate_->CreateUserWallpaperDelegate());
+ if (!user_wallpaper_delegate_)
+ user_wallpaper_delegate_.reset(new DummyUserWallpaperDelegate());
+
+ // StatusAreaWidget uses Shell's CapsLockDelegate.
+ caps_lock_delegate_.reset(delegate_->CreateCapsLockDelegate());
+
+ session_state_delegate_.reset(delegate_->CreateSessionStateDelegate());
+
+ if (!command_line->HasSwitch(views::corewm::switches::kNoDropShadows)) {
+ resize_shadow_controller_.reset(new internal::ResizeShadowController());
+ shadow_controller_.reset(
+ new views::corewm::ShadowController(activation_client_));
+ }
+
+ // Create system_tray_notifier_ before the delegate.
+ system_tray_notifier_.reset(new ash::SystemTrayNotifier());
+
+ // Initialize system_tray_delegate_ before initializing StatusAreaWidget.
+ system_tray_delegate_.reset(delegate()->CreateSystemTrayDelegate());
+ if (!system_tray_delegate_)
+ system_tray_delegate_.reset(SystemTrayDelegate::CreateDummyDelegate());
+
+ internal::RootWindowController* root_window_controller =
+ new internal::RootWindowController(root_window);
+ InitRootWindowController(root_window_controller,
+ delegate_->IsFirstRunAfterBoot());
+
+ locale_notification_controller_.reset(
+ new internal::LocaleNotificationController);
+
+ // Initialize system_tray_delegate_ after StatusAreaWidget is created.
+ system_tray_delegate_->Initialize();
+
+ display_controller_->InitSecondaryDisplays();
+
+ // Force Layout
+ root_window_controller->root_window_layout()->OnWindowResized();
+
+ // It needs to be created after OnWindowResized has been called, otherwise the
+ // widget will not paint when restoring after a browser crash. Also it needs
+ // to be created after InitSecondaryDisplays() to initialize the wallpapers in
+ // the correct size.
+ user_wallpaper_delegate_->InitializeWallpaper();
+
+ if (initially_hide_cursor_)
+ cursor_manager_.HideCursor();
+ cursor_manager_.SetCursor(ui::kCursorPointer);
+
+ if (!cursor_manager_.IsCursorVisible()) {
+ // Cursor might have been hidden by something other than chrome.
+ // Let the first mouse event show the cursor.
+ env_filter_->set_cursor_hidden_by_filter(true);
+ }
+}
+
+void Shell::ShowContextMenu(const gfx::Point& location_in_screen,
+ ui::MenuSourceType source_type) {
+ // No context menus if there is no session with an active user.
+ if (!session_state_delegate_->NumberOfLoggedInUsers())
+ return;
+ // No context menus when screen is locked.
+ if (session_state_delegate_->IsScreenLocked())
+ return;
+
+ aura::RootWindow* root =
+ wm::GetRootWindowMatching(gfx::Rect(location_in_screen, gfx::Size()));
+ // TODO(oshima): The root and root window controller shouldn't be
+ // NULL even for the out-of-bounds |location_in_screen| (It should
+ // return the primary root). Investigate why/how this is
+ // happening. crbug.com/165214.
+ internal::RootWindowController* rwc = GetRootWindowController(root);
+ CHECK(rwc) << "root=" << root
+ << ", location:" << location_in_screen.ToString();
+ if (rwc)
+ rwc->ShowContextMenu(location_in_screen, source_type);
+}
+
+void Shell::ToggleAppList(aura::Window* window) {
+ // If the context window is not given, show it on the active root window.
+ if (!window)
+ window = GetActiveRootWindow();
+ if (!app_list_controller_)
+ app_list_controller_.reset(new internal::AppListController);
+ app_list_controller_->SetVisible(!app_list_controller_->IsVisible(), window);
+}
+
+bool Shell::GetAppListTargetVisibility() const {
+ return app_list_controller_.get() &&
+ app_list_controller_->GetTargetVisibility();
+}
+
+aura::Window* Shell::GetAppListWindow() {
+ return app_list_controller_.get() ? app_list_controller_->GetWindow() : NULL;
+}
+
+bool Shell::IsSystemModalWindowOpen() const {
+ if (simulate_modal_window_open_for_testing_)
+ return true;
+ const std::vector<aura::Window*> containers = GetContainersFromAllRootWindows(
+ internal::kShellWindowId_SystemModalContainer, NULL);
+ for (std::vector<aura::Window*>::const_iterator cit = containers.begin();
+ cit != containers.end(); ++cit) {
+ for (aura::Window::Windows::const_iterator wit = (*cit)->children().begin();
+ wit != (*cit)->children().end(); ++wit) {
+ if ((*wit)->GetProperty(aura::client::kModalKey) ==
+ ui::MODAL_TYPE_SYSTEM && (*wit)->TargetVisibility()) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+views::NonClientFrameView* Shell::CreateDefaultNonClientFrameView(
+ views::Widget* widget) {
+ // Use translucent-style window frames for dialogs.
+ CustomFrameViewAsh* frame_view = new CustomFrameViewAsh;
+ frame_view->Init(widget);
+ return frame_view;
+}
+
+void Shell::RotateFocus(Direction direction) {
+ focus_cycler_->RotateFocus(
+ direction == FORWARD ? internal::FocusCycler::FORWARD :
+ internal::FocusCycler::BACKWARD);
+}
+
+void Shell::SetDisplayWorkAreaInsets(Window* contains,
+ const gfx::Insets& insets) {
+ if (!display_controller_->UpdateWorkAreaOfDisplayNearestWindow(
+ contains, insets)) {
+ return;
+ }
+ FOR_EACH_OBSERVER(ShellObserver, observers_,
+ OnDisplayWorkAreaInsetsChanged());
+}
+
+void Shell::OnLoginStateChanged(user::LoginStatus status) {
+ FOR_EACH_OBSERVER(ShellObserver, observers_, OnLoginStateChanged(status));
+}
+
+void Shell::UpdateAfterLoginStatusChange(user::LoginStatus status) {
+ RootWindowControllerList controllers = GetAllRootWindowControllers();
+ for (RootWindowControllerList::iterator iter = controllers.begin();
+ iter != controllers.end(); ++iter)
+ (*iter)->UpdateAfterLoginStatusChange(status);
+}
+
+void Shell::OnAppTerminating() {
+ FOR_EACH_OBSERVER(ShellObserver, observers_, OnAppTerminating());
+}
+
+void Shell::OnLockStateChanged(bool locked) {
+ FOR_EACH_OBSERVER(ShellObserver, observers_, OnLockStateChanged(locked));
+#ifndef NDEBUG
+ // Make sure that there is no system modal in Lock layer when unlocked.
+ if (!locked) {
+ std::vector<aura::Window*> containers = GetContainersFromAllRootWindows(
+ internal::kShellWindowId_LockSystemModalContainer,
+ GetPrimaryRootWindow());
+ for (std::vector<aura::Window*>::const_iterator iter = containers.begin();
+ iter != containers.end(); ++iter) {
+ DCHECK_EQ(0u, (*iter)->children().size());
+ }
+ }
+#endif
+}
+
+void Shell::CreateLauncher() {
+ RootWindowControllerList controllers = GetAllRootWindowControllers();
+ for (RootWindowControllerList::iterator iter = controllers.begin();
+ iter != controllers.end(); ++iter)
+ (*iter)->shelf()->CreateLauncher();
+}
+
+void Shell::ShowLauncher() {
+ RootWindowControllerList controllers = GetAllRootWindowControllers();
+ for (RootWindowControllerList::iterator iter = controllers.begin();
+ iter != controllers.end(); ++iter)
+ (*iter)->ShowLauncher();
+}
+
+void Shell::AddShellObserver(ShellObserver* observer) {
+ observers_.AddObserver(observer);
+}
+
+void Shell::RemoveShellObserver(ShellObserver* observer) {
+ observers_.RemoveObserver(observer);
+}
+
+void Shell::UpdateShelfVisibility() {
+ RootWindowControllerList controllers = GetAllRootWindowControllers();
+ for (RootWindowControllerList::iterator iter = controllers.begin();
+ iter != controllers.end(); ++iter)
+ if ((*iter)->shelf())
+ (*iter)->UpdateShelfVisibility();
+}
+
+void Shell::SetShelfAutoHideBehavior(ShelfAutoHideBehavior behavior,
+ aura::RootWindow* root_window) {
+ ash::internal::ShelfLayoutManager::ForLauncher(root_window)->
+ SetAutoHideBehavior(behavior);
+}
+
+ShelfAutoHideBehavior Shell::GetShelfAutoHideBehavior(
+ aura::RootWindow* root_window) const {
+ return ash::internal::ShelfLayoutManager::ForLauncher(root_window)->
+ auto_hide_behavior();
+}
+
+void Shell::SetShelfAlignment(ShelfAlignment alignment,
+ aura::RootWindow* root_window) {
+ if (ash::internal::ShelfLayoutManager::ForLauncher(root_window)->
+ SetAlignment(alignment)) {
+ FOR_EACH_OBSERVER(
+ ShellObserver, observers_, OnShelfAlignmentChanged(root_window));
+ }
+}
+
+ShelfAlignment Shell::GetShelfAlignment(aura::RootWindow* root_window) {
+ return GetRootWindowController(root_window)->
+ GetShelfLayoutManager()->GetAlignment();
+}
+
+void Shell::SetDimming(bool should_dim) {
+ RootWindowControllerList controllers = GetAllRootWindowControllers();
+ for (RootWindowControllerList::iterator iter = controllers.begin();
+ iter != controllers.end(); ++iter)
+ (*iter)->screen_dimmer()->SetDimming(should_dim);
+}
+
+void Shell::CreateModalBackground(aura::Window* window) {
+ if (!modality_filter_) {
+ modality_filter_.reset(new internal::SystemModalContainerEventFilter(this));
+ AddPreTargetHandler(modality_filter_.get());
+ }
+ RootWindowControllerList controllers = GetAllRootWindowControllers();
+ for (RootWindowControllerList::iterator iter = controllers.begin();
+ iter != controllers.end(); ++iter)
+ (*iter)->GetSystemModalLayoutManager(window)->CreateModalBackground();
+}
+
+void Shell::OnModalWindowRemoved(aura::Window* removed) {
+ RootWindowControllerList controllers = GetAllRootWindowControllers();
+ bool activated = false;
+ for (RootWindowControllerList::iterator iter = controllers.begin();
+ iter != controllers.end() && !activated; ++iter) {
+ activated = (*iter)->GetSystemModalLayoutManager(removed)->
+ ActivateNextModalWindow();
+ }
+ if (!activated) {
+ RemovePreTargetHandler(modality_filter_.get());
+ modality_filter_.reset();
+ for (RootWindowControllerList::iterator iter = controllers.begin();
+ iter != controllers.end(); ++iter)
+ (*iter)->GetSystemModalLayoutManager(removed)->DestroyModalBackground();
+ }
+}
+
+WebNotificationTray* Shell::GetWebNotificationTray() {
+ return GetPrimaryRootWindowController()->shelf()->
+ status_area_widget()->web_notification_tray();
+}
+
+bool Shell::HasPrimaryStatusArea() {
+ ShelfWidget* shelf = GetPrimaryRootWindowController()->shelf();
+ return shelf && shelf->status_area_widget();
+}
+
+SystemTray* Shell::GetPrimarySystemTray() {
+ return GetPrimaryRootWindowController()->GetSystemTray();
+}
+
+LauncherDelegate* Shell::GetLauncherDelegate() {
+ if (!launcher_delegate_) {
+ launcher_model_.reset(new LauncherModel);
+ launcher_delegate_.reset(
+ delegate_->CreateLauncherDelegate(launcher_model_.get()));
+ }
+ return launcher_delegate_.get();
+}
+
+void Shell::SetTouchHudProjectionEnabled(bool enabled) {
+ if (is_touch_hud_projection_enabled_ == enabled)
+ return;
+
+ is_touch_hud_projection_enabled_ = enabled;
+ FOR_EACH_OBSERVER(ShellObserver, observers_,
+ OnTouchHudProjectionToggled(enabled));
+}
+
+void Shell::InitRootWindowForSecondaryDisplay(aura::RootWindow* root) {
+ internal::RootWindowController* controller =
+ new internal::RootWindowController(root);
+ // Pass false for the |is_first_run_after_boot| parameter so we'll show a
+ // black background on this display instead of trying to mimic the boot splash
+ // screen.
+ InitRootWindowController(controller, false);
+
+ controller->root_window_layout()->OnWindowResized();
+ desktop_background_controller_->OnRootWindowAdded(root);
+ high_contrast_controller_->OnRootWindowAdded(root);
+ root->ShowRootWindow();
+ // Activate new root for testing.
+ // TODO(oshima): remove this.
+ target_root_window_ = root;
+
+ // Create a launcher if a user is already logged.
+ if (Shell::GetInstance()->session_state_delegate()->NumberOfLoggedInUsers())
+ controller->shelf()->CreateLauncher();
+}
+
+void Shell::DoInitialWorkspaceAnimation() {
+ return GetPrimaryRootWindowController()->workspace_controller()->
+ DoInitialAnimation();
+}
+
+void Shell::InitRootWindowController(
+ internal::RootWindowController* controller,
+ bool first_run_after_boot) {
+
+ aura::RootWindow* root_window = controller->root_window();
+ DCHECK(activation_client_);
+ DCHECK(visibility_controller_.get());
+ DCHECK(drag_drop_controller_.get());
+ DCHECK(capture_controller_.get());
+ DCHECK(window_cycle_controller_.get());
+
+ aura::client::SetFocusClient(root_window, focus_client_.get());
+ input_method_filter_->SetInputMethodPropertyInRootWindow(root_window);
+ aura::client::SetActivationClient(root_window, activation_client_);
+ if (views::corewm::UseFocusController()) {
+ views::corewm::FocusController* controller =
+ static_cast<views::corewm::FocusController*>(activation_client_);
+ root_window->AddPreTargetHandler(controller);
+ }
+ aura::client::SetVisibilityClient(root_window, visibility_controller_.get());
+ aura::client::SetDragDropClient(root_window, drag_drop_controller_.get());
+ aura::client::SetCaptureClient(root_window, capture_controller_.get());
+ aura::client::SetScreenPositionClient(root_window,
+ screen_position_controller_.get());
+ aura::client::SetCursorClient(root_window, &cursor_manager_);
+ aura::client::SetTooltipClient(root_window, tooltip_controller_.get());
+ aura::client::SetEventClient(root_window, event_client_.get());
+
+ if (nested_dispatcher_controller_) {
+ aura::client::SetDispatcherClient(root_window,
+ nested_dispatcher_controller_.get());
+ }
+ if (user_action_client_)
+ aura::client::SetUserActionClient(root_window, user_action_client_.get());
+
+ controller->Init(first_run_after_boot);
+
+ mru_window_tracker_->OnRootWindowAdded(root_window);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Shell, private:
+
+bool Shell::CanWindowReceiveEvents(aura::Window* window) {
+ RootWindowControllerList controllers = GetAllRootWindowControllers();
+ for (RootWindowControllerList::iterator iter = controllers.begin();
+ iter != controllers.end(); ++iter) {
+ internal::SystemModalContainerLayoutManager* layout_manager =
+ (*iter)->GetSystemModalLayoutManager(window);
+ if (layout_manager && layout_manager->CanWindowReceiveEvents(window))
+ return true;
+ }
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Shell, ui::EventTarget overrides:
+
+bool Shell::CanAcceptEvent(const ui::Event& event) {
+ return true;
+}
+
+ui::EventTarget* Shell::GetParentTarget() {
+ return NULL;
+}
+
+void Shell::OnEvent(ui::Event* event) {
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Shell, aura::client::ActivationChangeObserver implementation:
+
+void Shell::OnWindowActivated(aura::Window* gained_active,
+ aura::Window* lost_active) {
+ if (gained_active)
+ target_root_window_ = gained_active->GetRootWindow();
+}
+
+} // namespace ash
diff --git a/chromium/ash/shell.h b/chromium/ash/shell.h
new file mode 100644
index 00000000000..ae08e7b534a
--- /dev/null
+++ b/chromium/ash/shell.h
@@ -0,0 +1,639 @@
+// Copyright (c) 2012 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 ASH_SHELL_H_
+#define ASH_SHELL_H_
+
+#include <utility>
+#include <vector>
+
+#include "ash/ash_export.h"
+#include "ash/shelf/shelf_types.h"
+#include "ash/system/user/login_status.h"
+#include "ash/wm/system_modal_container_event_filter_delegate.h"
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/gtest_prod_util.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/observer_list.h"
+#include "ui/aura/client/activation_change_observer.h"
+#include "ui/base/events/event_target.h"
+#include "ui/base/ui_base_types.h"
+#include "ui/gfx/insets.h"
+#include "ui/gfx/screen.h"
+#include "ui/gfx/size.h"
+#include "ui/views/corewm/cursor_manager.h"
+
+class CommandLine;
+
+namespace aura {
+class EventFilter;
+class RootWindow;
+class Window;
+namespace client {
+class ActivationClient;
+class FocusClient;
+class UserActionClient;
+}
+}
+namespace chromeos {
+class OutputConfigurator;
+}
+namespace content {
+class BrowserContext;
+}
+
+namespace gfx {
+class ImageSkia;
+class Point;
+class Rect;
+}
+namespace ui {
+class Layer;
+}
+namespace views {
+class NonClientFrameView;
+class Widget;
+namespace corewm {
+class CompoundEventFilter;
+class InputMethodEventFilter;
+class ShadowController;
+class TooltipController;
+class VisibilityController;
+class WindowModalityController;
+}
+}
+
+namespace ash {
+
+class AcceleratorController;
+class AshNativeCursorManager;
+class CapsLockDelegate;
+class DesktopBackgroundController;
+class DisplayController;
+class HighContrastController;
+class Launcher;
+class LauncherDelegate;
+class LauncherModel;
+class MagnificationController;
+class MruWindowTracker;
+class NestedDispatcherController;
+class PartialMagnificationController;
+class PowerButtonController;
+class RootWindowHostFactory;
+class ScreenAsh;
+class LockStateController;
+class SessionStateDelegate;
+class ShellDelegate;
+class ShellObserver;
+class SystemTray;
+class SystemTrayDelegate;
+class SystemTrayNotifier;
+class UserActivityDetector;
+class UserWallpaperDelegate;
+class VideoDetector;
+class WebNotificationTray;
+class WindowCycleController;
+class WindowSelectorController;
+
+namespace internal {
+class AcceleratorFilter;
+class ActivationController;
+class AppListController;
+class CaptureController;
+class DisplayChangeObserverX11;
+class DisplayErrorObserver;
+class DisplayManager;
+class DragDropController;
+class EventClientImpl;
+class EventRewriterEventFilter;
+class EventTransformationHandler;
+class FocusCycler;
+class LocaleNotificationController;
+class MouseCursorEventFilter;
+class OutputConfiguratorAnimation;
+class OverlayEventFilter;
+class ResizeShadowController;
+class ResolutionNotificationController;
+class RootWindowController;
+class RootWindowLayoutManager;
+class ScopedTargetRootWindow;
+class ScreenPositionController;
+class SlowAnimationEventFilter;
+class StatusAreaWidget;
+class SystemGestureEventFilter;
+class SystemModalContainerEventFilter;
+class TouchObserverHUD;
+}
+
+namespace shell {
+class WindowWatcher;
+}
+
+namespace test {
+class ShellTestApi;
+}
+
+// Shell is a singleton object that presents the Shell API and implements the
+// RootWindow's delegate interface.
+//
+// Upon creation, the Shell sets itself as the RootWindow's delegate, which
+// takes ownership of the Shell.
+class ASH_EXPORT Shell
+ : public internal::SystemModalContainerEventFilterDelegate,
+ public ui::EventTarget,
+ public aura::client::ActivationChangeObserver {
+ public:
+ typedef std::vector<aura::RootWindow*> RootWindowList;
+ typedef std::vector<internal::RootWindowController*> RootWindowControllerList;
+
+ enum Direction {
+ FORWARD,
+ BACKWARD
+ };
+
+ // A shell must be explicitly created so that it can call |Init()| with the
+ // delegate set. |delegate| can be NULL (if not required for initialization).
+ // Takes ownership of |delegate|.
+ static Shell* CreateInstance(ShellDelegate* delegate);
+
+ // Should never be called before |CreateInstance()|.
+ static Shell* GetInstance();
+
+ // Returns true if the ash shell has been instantiated.
+ static bool HasInstance();
+
+ static void DeleteInstance();
+
+ // Returns the root window controller for the primary root window.
+ // TODO(oshima): move this to |RootWindowController|
+ static internal::RootWindowController* GetPrimaryRootWindowController();
+
+ // Returns all root window controllers.
+ // TODO(oshima): move this to |RootWindowController|
+ static RootWindowControllerList GetAllRootWindowControllers();
+
+ // Returns the primary RootWindow. The primary RootWindow is the one
+ // that has a launcher.
+ static aura::RootWindow* GetPrimaryRootWindow();
+
+ // Returns a RootWindow when used as a target when creating a new window.
+ // The root window of the active window is used in most cases, but can
+ // be overridden by using ScopedTargetRootWindow().
+ // If you want to get a RootWindow of the active window, just use
+ // |wm::GetActiveWindow()->GetRootWindow()|.
+ // TODO(oshima): Rename to GetTargetRootWindow() crbug.com/266378.
+ static aura::RootWindow* GetActiveRootWindow();
+
+ // Returns the global Screen object that's always active in ash.
+ static gfx::Screen* GetScreen();
+
+ // Returns all root windows.
+ static RootWindowList GetAllRootWindows();
+
+ static aura::Window* GetContainer(aura::RootWindow* root_window,
+ int container_id);
+ static const aura::Window* GetContainer(const aura::RootWindow* root_window,
+ int container_id);
+
+ // Returns the list of containers that match |container_id| in
+ // all root windows. If |priority_root| is given, the container
+ // in the |priority_root| will be inserted at the top of the list.
+ static std::vector<aura::Window*> GetContainersFromAllRootWindows(
+ int container_id,
+ aura::RootWindow* priority_root);
+
+ // True if an experimental maximize mode is enabled which forces browser and
+ // application windows to be maximized only.
+ static bool IsForcedMaximizeMode();
+
+ void set_active_root_window(aura::RootWindow* target_root_window) {
+ target_root_window_ = target_root_window;
+ }
+
+ // Shows the context menu for the background and launcher at
+ // |location_in_screen| (in screen coordinates).
+ void ShowContextMenu(const gfx::Point& location_in_screen,
+ ui::MenuSourceType source_type);
+
+ // Toggles the app list. |window| specifies in which display the app
+ // list should be shown. If this is NULL, the active root window
+ // will be used.
+ void ToggleAppList(aura::Window* anchor);
+
+ // Returns app list target visibility.
+ bool GetAppListTargetVisibility() const;
+
+ // Returns app list window or NULL if it is not visible.
+ aura::Window* GetAppListWindow();
+
+ // Returns true if a system-modal dialog window is currently open.
+ bool IsSystemModalWindowOpen() const;
+
+ // For testing only: set simulation that a modal window is open
+ void SimulateModalWindowOpenForTesting(bool modal_window_open) {
+ simulate_modal_window_open_for_testing_ = modal_window_open;
+ }
+
+ // Creates a default views::NonClientFrameView for use by windows in the
+ // Ash environment.
+ views::NonClientFrameView* CreateDefaultNonClientFrameView(
+ views::Widget* widget);
+
+ // Rotates focus through containers that can receive focus.
+ void RotateFocus(Direction direction);
+
+ // Sets the work area insets of the display that contains |window|,
+ // this notifies observers too.
+ // TODO(sky): this no longer really replicates what happens and is unreliable.
+ // Remove this.
+ void SetDisplayWorkAreaInsets(aura::Window* window,
+ const gfx::Insets& insets);
+
+ // Called when the user logs in.
+ void OnLoginStateChanged(user::LoginStatus status);
+
+ // Called when the login status changes.
+ // TODO(oshima): Investigate if we can merge this and |OnLoginStateChanged|.
+ void UpdateAfterLoginStatusChange(user::LoginStatus status);
+
+ // Called when the application is exiting.
+ void OnAppTerminating();
+
+ // Called when the screen is locked (after the lock window is visible) or
+ // unlocked.
+ void OnLockStateChanged(bool locked);
+
+ // Initializes |launcher_|. Does nothing if it's already initialized.
+ void CreateLauncher();
+
+ // Show launcher view if it was created hidden (before session has started).
+ void ShowLauncher();
+
+ // Adds/removes observer.
+ void AddShellObserver(ShellObserver* observer);
+ void RemoveShellObserver(ShellObserver* observer);
+
+#if !defined(OS_MACOSX)
+ AcceleratorController* accelerator_controller() {
+ return accelerator_controller_.get();
+ }
+#endif // !defined(OS_MACOSX)
+
+ internal::DisplayManager* display_manager() {
+ return display_manager_.get();
+ }
+ views::corewm::InputMethodEventFilter* input_method_filter() {
+ return input_method_filter_.get();
+ }
+ views::corewm::CompoundEventFilter* env_filter() {
+ return env_filter_.get();
+ }
+ views::corewm::TooltipController* tooltip_controller() {
+ return tooltip_controller_.get();
+ }
+ internal::EventRewriterEventFilter* event_rewriter_filter() {
+ return event_rewriter_filter_.get();
+ }
+ internal::OverlayEventFilter* overlay_filter() {
+ return overlay_filter_.get();
+ }
+ DesktopBackgroundController* desktop_background_controller() {
+ return desktop_background_controller_.get();
+ }
+ PowerButtonController* power_button_controller() {
+ return power_button_controller_.get();
+ }
+ LockStateController* lock_state_controller() {
+ return lock_state_controller_.get();
+ }
+ MruWindowTracker* mru_window_tracker() {
+ return mru_window_tracker_.get();
+ }
+ UserActivityDetector* user_activity_detector() {
+ return user_activity_detector_.get();
+ }
+ VideoDetector* video_detector() {
+ return video_detector_.get();
+ }
+ WindowCycleController* window_cycle_controller() {
+ return window_cycle_controller_.get();
+ }
+ WindowSelectorController* window_selector_controller() {
+ return window_selector_controller_.get();
+ }
+ internal::FocusCycler* focus_cycler() {
+ return focus_cycler_.get();
+ }
+ DisplayController* display_controller() {
+ return display_controller_.get();
+ }
+ internal::MouseCursorEventFilter* mouse_cursor_filter() {
+ return mouse_cursor_filter_.get();
+ }
+ internal::EventTransformationHandler* event_transformation_handler() {
+ return event_transformation_handler_.get();
+ }
+ views::corewm::CursorManager* cursor_manager() { return &cursor_manager_; }
+
+ ShellDelegate* delegate() { return delegate_.get(); }
+
+ UserWallpaperDelegate* user_wallpaper_delegate() {
+ return user_wallpaper_delegate_.get();
+ }
+
+ CapsLockDelegate* caps_lock_delegate() {
+ return caps_lock_delegate_.get();
+ }
+
+ SessionStateDelegate* session_state_delegate() {
+ return session_state_delegate_.get();
+ }
+
+ HighContrastController* high_contrast_controller() {
+ return high_contrast_controller_.get();
+ }
+
+ MagnificationController* magnification_controller() {
+ return magnification_controller_.get();
+ }
+
+ PartialMagnificationController* partial_magnification_controller() {
+ return partial_magnification_controller_.get();
+ }
+ aura::client::ActivationClient* activation_client() {
+ return activation_client_;
+ }
+
+ ScreenAsh* screen() { return screen_; }
+
+ // Force the shelf to query for it's current visibility state.
+ void UpdateShelfVisibility();
+
+ // TODO(oshima): Define an interface to access shelf/launcher
+ // state, or just use Launcher.
+
+ // Sets/gets the shelf auto-hide behavior on |root_window|.
+ void SetShelfAutoHideBehavior(ShelfAutoHideBehavior behavior,
+ aura::RootWindow* root_window);
+ ShelfAutoHideBehavior GetShelfAutoHideBehavior(
+ aura::RootWindow* root_window) const;
+
+ // Sets/gets shelf's alignment on |root_window|.
+ void SetShelfAlignment(ShelfAlignment alignment,
+ aura::RootWindow* root_window);
+ ShelfAlignment GetShelfAlignment(aura::RootWindow* root_window);
+
+ // Dims or undims the screen.
+ void SetDimming(bool should_dim);
+
+ // Creates a modal background (a partially-opaque fullscreen window)
+ // on all displays for |window|.
+ void CreateModalBackground(aura::Window* window);
+
+ // Called when a modal window is removed. It will activate
+ // another modal window if any, or remove modal screens
+ // on all displays.
+ void OnModalWindowRemoved(aura::Window* removed);
+
+ // Returns WebNotificationTray on the primary root window.
+ WebNotificationTray* GetWebNotificationTray();
+
+ // Does the primary display have status area?
+ bool HasPrimaryStatusArea();
+
+ // Returns the system tray on primary display.
+ SystemTray* GetPrimarySystemTray();
+
+ SystemTrayDelegate* system_tray_delegate() {
+ return system_tray_delegate_.get();
+ }
+
+ SystemTrayNotifier* system_tray_notifier() {
+ return system_tray_notifier_.get();
+ }
+
+ static void set_initially_hide_cursor(bool hide) {
+ initially_hide_cursor_ = hide;
+ }
+
+ internal::ResizeShadowController* resize_shadow_controller() {
+ return resize_shadow_controller_.get();
+ }
+
+ // Made available for tests.
+ views::corewm::ShadowController* shadow_controller() {
+ return shadow_controller_.get();
+ }
+
+ content::BrowserContext* browser_context() { return browser_context_; }
+ void set_browser_context(content::BrowserContext* browser_context) {
+ browser_context_ = browser_context;
+ }
+
+ // Initializes the root window to be used for a secondary display.
+ void InitRootWindowForSecondaryDisplay(aura::RootWindow* root);
+
+ // Starts the animation that occurs on first login.
+ void DoInitialWorkspaceAnimation();
+
+#if defined(OS_CHROMEOS) && defined(USE_X11)
+ // TODO(oshima): Move these objects to DisplayController.
+ chromeos::OutputConfigurator* output_configurator() {
+ return output_configurator_.get();
+ }
+ internal::OutputConfiguratorAnimation* output_configurator_animation() {
+ return output_configurator_animation_.get();
+ }
+ internal::DisplayErrorObserver* display_error_observer() {
+ return display_error_observer_.get();
+ }
+#endif // defined(OS_CHROMEOS) && defined(USE_X11)
+
+ internal::ResolutionNotificationController*
+ resolution_notification_controller() {
+ return resolution_notification_controller_.get();
+ }
+
+ RootWindowHostFactory* root_window_host_factory() {
+ return root_window_host_factory_.get();
+ }
+
+ LauncherModel* launcher_model() {
+ return launcher_model_.get();
+ }
+
+ // Returns the launcher delegate, creating if necesary.
+ LauncherDelegate* GetLauncherDelegate();
+
+ void SetTouchHudProjectionEnabled(bool enabled);
+
+ bool is_touch_hud_projection_enabled() const {
+ return is_touch_hud_projection_enabled_;
+ }
+
+ private:
+ FRIEND_TEST_ALL_PREFIXES(ExtendedDesktopTest, TestCursor);
+ FRIEND_TEST_ALL_PREFIXES(WindowManagerTest, MouseEventCursors);
+ FRIEND_TEST_ALL_PREFIXES(WindowManagerTest, TransformActivate);
+ friend class internal::RootWindowController;
+ friend class internal::ScopedTargetRootWindow;
+ friend class test::ShellTestApi;
+ friend class shell::WindowWatcher;
+
+ typedef std::pair<aura::Window*, gfx::Rect> WindowAndBoundsPair;
+
+ // Takes ownership of |delegate|.
+ explicit Shell(ShellDelegate* delegate);
+ virtual ~Shell();
+
+ void Init();
+
+ // Initializes the root window and root window controller so that it
+ // can host browser windows. |first_run_after_boot| is true for the
+ // primary display only first time after boot.
+ void InitRootWindowController(internal::RootWindowController* root,
+ bool first_run_after_boot);
+
+ // ash::internal::SystemModalContainerEventFilterDelegate overrides:
+ virtual bool CanWindowReceiveEvents(aura::Window* window) OVERRIDE;
+
+ // Overridden from ui::EventTarget:
+ virtual bool CanAcceptEvent(const ui::Event& event) OVERRIDE;
+ virtual EventTarget* GetParentTarget() OVERRIDE;
+ virtual void OnEvent(ui::Event* event) OVERRIDE;
+
+ // Overridden from aura::client::ActivationChangeObserver:
+ virtual void OnWindowActivated(aura::Window* gained_active,
+ aura::Window* lost_active) OVERRIDE;
+
+ static Shell* instance_;
+
+ // If set before the Shell is initialized, the mouse cursor will be hidden
+ // when the screen is initially created.
+ static bool initially_hide_cursor_;
+
+ ScreenAsh* screen_;
+
+ // When no explicit target display/RootWindow is given, new windows are
+ // created on |scoped_target_root_window_| , unless NULL in
+ // which case they are created on |target_root_window_|.
+ // |target_root_window_| never becomes NULL during the session.
+ aura::RootWindow* target_root_window_;
+ aura::RootWindow* scoped_target_root_window_;
+
+ // The CompoundEventFilter owned by aura::Env object.
+ scoped_ptr<views::corewm::CompoundEventFilter> env_filter_;
+
+ std::vector<WindowAndBoundsPair> to_restore_;
+
+#if !defined(OS_MACOSX)
+ scoped_ptr<NestedDispatcherController> nested_dispatcher_controller_;
+
+ scoped_ptr<AcceleratorController> accelerator_controller_;
+#endif // !defined(OS_MACOSX)
+
+ scoped_ptr<ShellDelegate> delegate_;
+ scoped_ptr<SystemTrayDelegate> system_tray_delegate_;
+ scoped_ptr<SystemTrayNotifier> system_tray_notifier_;
+ scoped_ptr<UserWallpaperDelegate> user_wallpaper_delegate_;
+ scoped_ptr<CapsLockDelegate> caps_lock_delegate_;
+ scoped_ptr<SessionStateDelegate> session_state_delegate_;
+ scoped_ptr<LauncherDelegate> launcher_delegate_;
+
+ scoped_ptr<LauncherModel> launcher_model_;
+
+ scoped_ptr<internal::AppListController> app_list_controller_;
+
+ scoped_ptr<internal::ActivationController> activation_controller_;
+ scoped_ptr<internal::CaptureController> capture_controller_;
+ scoped_ptr<internal::DragDropController> drag_drop_controller_;
+ scoped_ptr<internal::ResizeShadowController> resize_shadow_controller_;
+ scoped_ptr<views::corewm::ShadowController> shadow_controller_;
+ scoped_ptr<views::corewm::VisibilityController> visibility_controller_;
+ scoped_ptr<views::corewm::WindowModalityController>
+ window_modality_controller_;
+ scoped_ptr<views::corewm::TooltipController> tooltip_controller_;
+ scoped_ptr<DesktopBackgroundController> desktop_background_controller_;
+ scoped_ptr<PowerButtonController> power_button_controller_;
+ scoped_ptr<LockStateController> lock_state_controller_;
+ scoped_ptr<MruWindowTracker> mru_window_tracker_;
+ scoped_ptr<UserActivityDetector> user_activity_detector_;
+ scoped_ptr<VideoDetector> video_detector_;
+ scoped_ptr<WindowCycleController> window_cycle_controller_;
+ scoped_ptr<WindowSelectorController> window_selector_controller_;
+ scoped_ptr<internal::FocusCycler> focus_cycler_;
+ scoped_ptr<DisplayController> display_controller_;
+ scoped_ptr<HighContrastController> high_contrast_controller_;
+ scoped_ptr<MagnificationController> magnification_controller_;
+ scoped_ptr<PartialMagnificationController> partial_magnification_controller_;
+ scoped_ptr<aura::client::FocusClient> focus_client_;
+ scoped_ptr<aura::client::UserActionClient> user_action_client_;
+ aura::client::ActivationClient* activation_client_;
+ scoped_ptr<internal::MouseCursorEventFilter> mouse_cursor_filter_;
+ scoped_ptr<internal::ScreenPositionController> screen_position_controller_;
+ scoped_ptr<internal::SystemModalContainerEventFilter> modality_filter_;
+ scoped_ptr<internal::EventClientImpl> event_client_;
+ scoped_ptr<internal::EventTransformationHandler>
+ event_transformation_handler_;
+ scoped_ptr<RootWindowHostFactory> root_window_host_factory_;
+
+ // An event filter that rewrites or drops an event.
+ scoped_ptr<internal::EventRewriterEventFilter> event_rewriter_filter_;
+
+ // An event filter that pre-handles key events while the partial
+ // screenshot UI or the keyboard overlay is active.
+ scoped_ptr<internal::OverlayEventFilter> overlay_filter_;
+
+ // An event filter which handles system level gestures
+ scoped_ptr<internal::SystemGestureEventFilter> system_gesture_filter_;
+
+#if !defined(OS_MACOSX)
+ // An event filter that pre-handles global accelerators.
+ scoped_ptr<internal::AcceleratorFilter> accelerator_filter_;
+#endif
+
+ // An event filter that pre-handles all key events to send them to an IME.
+ scoped_ptr<views::corewm::InputMethodEventFilter> input_method_filter_;
+
+ scoped_ptr<internal::DisplayManager> display_manager_;
+
+ scoped_ptr<internal::LocaleNotificationController>
+ locale_notification_controller_;
+
+#if defined(OS_CHROMEOS) && defined(USE_X11)
+ // Controls video output device state.
+ scoped_ptr<chromeos::OutputConfigurator> output_configurator_;
+ scoped_ptr<internal::OutputConfiguratorAnimation>
+ output_configurator_animation_;
+ scoped_ptr<internal::DisplayErrorObserver> display_error_observer_;
+
+ // Receives output change events and udpates the display manager.
+ scoped_ptr<internal::DisplayChangeObserverX11> display_change_observer_;
+#endif // defined(OS_CHROMEOS) && defined(USE_X11)
+
+ scoped_ptr<internal::ResolutionNotificationController>
+ resolution_notification_controller_;
+
+ // |native_cursor_manager_| is owned by |cursor_manager_|, but we keep a
+ // pointer to vend to test code.
+ AshNativeCursorManager* native_cursor_manager_;
+ views::corewm::CursorManager cursor_manager_;
+
+ ObserverList<ShellObserver> observers_;
+
+ // Used by ash/shell.
+ content::BrowserContext* browser_context_;
+
+ // For testing only: simulate that a modal window is open
+ bool simulate_modal_window_open_for_testing_;
+
+ bool is_touch_hud_projection_enabled_;
+
+ DISALLOW_COPY_AND_ASSIGN(Shell);
+};
+
+} // namespace ash
+
+#endif // ASH_SHELL_H_
diff --git a/chromium/ash/shell/DEPS b/chromium/ash/shell/DEPS
new file mode 100644
index 00000000000..24f7ff132d7
--- /dev/null
+++ b/chromium/ash/shell/DEPS
@@ -0,0 +1,4 @@
+include_rules = [
+ "+content",
+ "+sandbox",
+]
diff --git a/chromium/ash/shell/app_list.cc b/chromium/ash/shell/app_list.cc
new file mode 100644
index 00000000000..dd7adc59639
--- /dev/null
+++ b/chromium/ash/shell/app_list.cc
@@ -0,0 +1,337 @@
+// Copyright (c) 2012 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.
+
+#include <string>
+
+#include "ash/session_state_delegate.h"
+#include "ash/shell.h"
+#include "ash/shell/example_factory.h"
+#include "ash/shell/toplevel_window.h"
+#include "base/basictypes.h"
+#include "base/callback.h"
+#include "base/files/file_path.h"
+#include "base/i18n/case_conversion.h"
+#include "base/i18n/string_search.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "ui/app_list/app_list_item_model.h"
+#include "ui/app_list/app_list_model.h"
+#include "ui/app_list/app_list_view_delegate.h"
+#include "ui/app_list/search_box_model.h"
+#include "ui/app_list/search_result.h"
+#include "ui/gfx/canvas.h"
+#include "ui/gfx/font.h"
+#include "ui/gfx/image/image_skia.h"
+#include "ui/gfx/rect.h"
+#include "ui/views/examples/examples_window_with_content.h"
+
+namespace ash {
+namespace shell {
+
+namespace {
+
+// WindowTypeLauncherItem is an app item of app list. It carries a window
+// launch type and launches corresponding example window when activated.
+class WindowTypeLauncherItem : public app_list::AppListItemModel {
+ public:
+ enum Type {
+ TOPLEVEL_WINDOW = 0,
+ NON_RESIZABLE_WINDOW,
+ LOCK_SCREEN,
+ WIDGETS_WINDOW,
+ EXAMPLES_WINDOW,
+ LAST_TYPE,
+ };
+
+ explicit WindowTypeLauncherItem(Type type) : type_(type) {
+ SetIcon(GetIcon(type), false);
+ SetTitle(GetTitle(type));
+ }
+
+ static gfx::ImageSkia GetIcon(Type type) {
+ static const SkColor kColors[] = {
+ SK_ColorRED,
+ SK_ColorGREEN,
+ SK_ColorBLUE,
+ SK_ColorYELLOW,
+ SK_ColorCYAN,
+ };
+
+ const int kIconSize = 128;
+ SkBitmap icon;
+ icon.setConfig(SkBitmap::kARGB_8888_Config, kIconSize, kIconSize);
+ icon.allocPixels();
+ icon.eraseColor(kColors[static_cast<int>(type) % arraysize(kColors)]);
+ return gfx::ImageSkia::CreateFrom1xBitmap(icon);
+ }
+
+ // The text below is not localized as this is an example code.
+ static std::string GetTitle(Type type) {
+ switch (type) {
+ case TOPLEVEL_WINDOW:
+ return "Create Window";
+ case NON_RESIZABLE_WINDOW:
+ return "Create Non-Resizable Window";
+ case LOCK_SCREEN:
+ return "Lock Screen";
+ case WIDGETS_WINDOW:
+ return "Show Example Widgets";
+ case EXAMPLES_WINDOW:
+ return "Open Views Examples Window";
+ default:
+ return "Unknown window type.";
+ }
+ }
+
+ // The text below is not localized as this is an example code.
+ static std::string GetDetails(Type type) {
+ // Assigns details only to some types so that we see both one-line
+ // and two-line results.
+ switch (type) {
+ case WIDGETS_WINDOW:
+ return "Creates a window to show example widgets";
+ case EXAMPLES_WINDOW:
+ return "Creates a window to show views example.";
+ default:
+ return std::string();
+ }
+ }
+
+ static void Activate(Type type, int event_flags) {
+ switch (type) {
+ case TOPLEVEL_WINDOW: {
+ ToplevelWindow::CreateParams params;
+ params.can_resize = true;
+ ToplevelWindow::CreateToplevelWindow(params);
+ break;
+ }
+ case NON_RESIZABLE_WINDOW: {
+ ToplevelWindow::CreateToplevelWindow(ToplevelWindow::CreateParams());
+ break;
+ }
+ case LOCK_SCREEN: {
+ Shell::GetInstance()->session_state_delegate()->LockScreen();
+ break;
+ }
+ case WIDGETS_WINDOW: {
+ CreateWidgetsWindow();
+ break;
+ }
+ case EXAMPLES_WINDOW: {
+#if !defined(OS_MACOSX)
+ views::examples::ShowExamplesWindowWithContent(
+ views::examples::DO_NOTHING_ON_CLOSE,
+ ash::Shell::GetInstance()->browser_context());
+#endif
+ break;
+ }
+ default:
+ break;
+ }
+ }
+
+ void Activate(int event_flags) {
+ Activate(type_, event_flags);
+ }
+
+ private:
+ Type type_;
+
+ DISALLOW_COPY_AND_ASSIGN(WindowTypeLauncherItem);
+};
+
+// ExampleSearchResult is an app list search result. It provides what icon to
+// show, what should title and details text look like. It also carries the
+// matching window launch type so that AppListViewDelegate knows how to open
+// it.
+class ExampleSearchResult : public app_list::SearchResult {
+ public:
+ ExampleSearchResult(WindowTypeLauncherItem::Type type,
+ const base::string16& query)
+ : type_(type) {
+ SetIcon(WindowTypeLauncherItem::GetIcon(type_));
+
+ base::string16 title = UTF8ToUTF16(WindowTypeLauncherItem::GetTitle(type_));
+ set_title(title);
+
+ Tags title_tags;
+ const size_t match_len = query.length();
+
+ // Highlight matching parts in title with bold.
+ // Note the following is not a proper way to handle i18n string.
+ title = base::i18n::ToLower(title);
+ size_t match_start = title.find(query);
+ while (match_start != base::string16::npos) {
+ title_tags.push_back(Tag(Tag::MATCH,
+ match_start,
+ match_start + match_len));
+ match_start = title.find(query, match_start + match_len);
+ }
+ set_title_tags(title_tags);
+
+ base::string16 details =
+ UTF8ToUTF16(WindowTypeLauncherItem::GetDetails(type_));
+ set_details(details);
+ Tags details_tags;
+ details_tags.push_back(Tag(Tag::DIM, 0, details.length()));
+ set_details_tags(details_tags);
+ }
+
+ WindowTypeLauncherItem::Type type() const { return type_; }
+
+ private:
+ WindowTypeLauncherItem::Type type_;
+
+ DISALLOW_COPY_AND_ASSIGN(ExampleSearchResult);
+};
+
+class ExampleAppListViewDelegate : public app_list::AppListViewDelegate {
+ public:
+ ExampleAppListViewDelegate() : model_(NULL) {}
+
+ private:
+ void PopulateApps(app_list::AppListModel::Apps* apps) {
+ for (int i = 0;
+ i < static_cast<int>(WindowTypeLauncherItem::LAST_TYPE);
+ ++i) {
+ WindowTypeLauncherItem::Type type =
+ static_cast<WindowTypeLauncherItem::Type>(i);
+ apps->Add(new WindowTypeLauncherItem(type));
+ }
+ }
+
+ gfx::ImageSkia CreateSearchBoxIcon() {
+ const base::string16 icon_text = ASCIIToUTF16("ash");
+ const gfx::Size icon_size(32, 32);
+
+ gfx::Canvas canvas(icon_size, ui::SCALE_FACTOR_100P,
+ false /* is_opaque */);
+ canvas.DrawStringInt(icon_text,
+ gfx::Font(),
+ SK_ColorBLACK,
+ 0, 0, icon_size.width(), icon_size.height(),
+ gfx::Canvas::TEXT_ALIGN_CENTER |
+ gfx::Canvas::NO_SUBPIXEL_RENDERING);
+
+ return gfx::ImageSkia(canvas.ExtractImageRep());
+ }
+
+ void DecorateSearchBox(app_list::SearchBoxModel* search_box_model) {
+ search_box_model->SetIcon(CreateSearchBoxIcon());
+ search_box_model->SetHintText(ASCIIToUTF16("Type to search..."));
+ }
+
+ // Overridden from ash::AppListViewDelegate:
+ virtual void SetModel(app_list::AppListModel* model) OVERRIDE {
+ model_ = model;
+ PopulateApps(model_->apps());
+ DecorateSearchBox(model_->search_box());
+ }
+
+ virtual app_list::SigninDelegate* GetSigninDelegate() OVERRIDE {
+ return NULL;
+ }
+
+ virtual void GetShortcutPathForApp(
+ const std::string& app_id,
+ const base::Callback<void(const base::FilePath&)>& callback) OVERRIDE {
+ }
+
+ virtual void ActivateAppListItem(app_list::AppListItemModel* item,
+ int event_flags) OVERRIDE {
+ static_cast<WindowTypeLauncherItem*>(item)->Activate(event_flags);
+ }
+
+ virtual void OpenSearchResult(app_list::SearchResult* result,
+ int event_flags) OVERRIDE {
+ const ExampleSearchResult* example_result =
+ static_cast<const ExampleSearchResult*>(result);
+ WindowTypeLauncherItem::Activate(example_result->type(), event_flags);
+ }
+
+ virtual void InvokeSearchResultAction(app_list::SearchResult* result,
+ int action_index,
+ int event_flags) OVERRIDE {
+ NOTIMPLEMENTED();
+ }
+
+ virtual void StartSearch() OVERRIDE {
+ base::string16 query;
+ TrimWhitespace(model_->search_box()->text(), TRIM_ALL, &query);
+ query = base::i18n::ToLower(query);
+
+ model_->results()->DeleteAll();
+ if (query.empty())
+ return;
+
+ for (int i = 0;
+ i < static_cast<int>(WindowTypeLauncherItem::LAST_TYPE);
+ ++i) {
+ WindowTypeLauncherItem::Type type =
+ static_cast<WindowTypeLauncherItem::Type>(i);
+
+ base::string16 title =
+ UTF8ToUTF16(WindowTypeLauncherItem::GetTitle(type));
+ if (base::i18n::StringSearchIgnoringCaseAndAccents(
+ query, title, NULL, NULL)) {
+ model_->results()->Add(new ExampleSearchResult(type, query));
+ }
+ }
+ }
+
+ virtual void StopSearch() OVERRIDE {
+ // Nothing needs to be done.
+ }
+
+ virtual void Dismiss() OVERRIDE {
+ DCHECK(ash::Shell::HasInstance());
+ if (Shell::GetInstance()->GetAppListTargetVisibility())
+ Shell::GetInstance()->ToggleAppList(NULL);
+ }
+
+ virtual void ViewClosing() OVERRIDE {
+ // Nothing needs to be done.
+ }
+
+ virtual void ViewActivationChanged(bool active) OVERRIDE {
+ // Nothing needs to be done.
+ }
+
+ virtual gfx::ImageSkia GetWindowIcon() OVERRIDE {
+ return gfx::ImageSkia();
+ }
+
+ virtual base::string16 GetCurrentUserName() OVERRIDE {
+ return base::string16();
+ }
+
+ virtual base::string16 GetCurrentUserEmail() OVERRIDE {
+ return base::string16();
+ }
+
+ virtual void OpenSettings() OVERRIDE {
+ // Nothing needs to be done.
+ }
+
+ virtual void OpenHelp() OVERRIDE {
+ // Nothing needs to be done.
+ }
+
+ virtual void OpenFeedback() OVERRIDE {
+ // Nothing needs to be done.
+ }
+
+ app_list::AppListModel* model_;
+
+ DISALLOW_COPY_AND_ASSIGN(ExampleAppListViewDelegate);
+};
+
+} // namespace
+
+app_list::AppListViewDelegate* CreateAppListViewDelegate() {
+ return new ExampleAppListViewDelegate;
+}
+
+} // namespace shell
+} // namespace ash
diff --git a/chromium/ash/shell/bubble.cc b/chromium/ash/shell/bubble.cc
new file mode 100644
index 00000000000..c39f76a60f0
--- /dev/null
+++ b/chromium/ash/shell/bubble.cc
@@ -0,0 +1,47 @@
+// Copyright (c) 2011 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.
+
+#include "base/strings/utf_string_conversions.h"
+#include "ui/views/bubble/bubble_border.h"
+#include "ui/views/bubble/bubble_delegate.h"
+#include "ui/views/controls/label.h"
+#include "ui/views/layout/fill_layout.h"
+#include "ui/views/widget/widget.h"
+
+namespace ash {
+namespace shell {
+
+struct BubbleConfig {
+ base::string16 label;
+ views::View* anchor_view;
+ views::BubbleBorder::Arrow arrow;
+};
+
+class ExampleBubbleDelegateView : public views::BubbleDelegateView {
+ public:
+ ExampleBubbleDelegateView(const BubbleConfig& config)
+ : BubbleDelegateView(config.anchor_view, config.arrow),
+ label_(config.label) {}
+
+ virtual void Init() OVERRIDE {
+ SetLayoutManager(new views::FillLayout());
+ views::Label* label = new views::Label(label_);
+ AddChildView(label);
+ }
+
+ private:
+ base::string16 label_;
+};
+
+void CreatePointyBubble(views::View* anchor_view) {
+ BubbleConfig config;
+ config.label = ASCIIToUTF16("PointyBubble");
+ config.anchor_view = anchor_view;
+ config.arrow = views::BubbleBorder::TOP_LEFT;
+ ExampleBubbleDelegateView* bubble = new ExampleBubbleDelegateView(config);
+ views::BubbleDelegateView::CreateBubble(bubble)->Show();
+}
+
+} // namespace shell
+} // namespace ash
diff --git a/chromium/ash/shell/cocoa/app-Info.plist b/chromium/ash/shell/cocoa/app-Info.plist
new file mode 100644
index 00000000000..9f04fc71469
--- /dev/null
+++ b/chromium/ash/shell/cocoa/app-Info.plist
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>CFBundleDevelopmentRegion</key>
+ <string>en</string>
+ <key>CFBundleExecutable</key>
+ <string>${EXECUTABLE_NAME}</string>
+ <key>CFBundleIconFile</key>
+ <string>app.icns</string>
+ <key>CFBundleIdentifier</key>
+ <string>org.chromium.AuraShell</string>
+ <key>CFBundleInfoDictionaryVersion</key>
+ <string>6.0</string>
+ <key>CFBundleName</key>
+ <string>${PRODUCT_NAME}</string>
+ <key>CFBundlePackageType</key>
+ <string>APPL</string>
+ <key>CFBundleShortVersionString</key>
+ <string>1.0</string>
+ <key>CFBundleSignature</key>
+ <string>Ash8</string>
+ <key>CFBundleVersion</key>
+ <string>1</string>
+ <key>LSMinimumSystemVersion</key>
+ <string>${MACOSX_DEPLOYMENT_TARGET}.0</string>
+ <key>NSHumanReadableCopyright</key>
+ <string>Copyright © 2012 Google Inc. All rights reserved.</string>
+ <key>NSMainNibFile</key>
+ <string>MainMenu</string>
+ <key>NSPrincipalClass</key>
+ <string>NSApplication</string>
+</dict>
+</plist>
diff --git a/chromium/ash/shell/cocoa/app.icns b/chromium/ash/shell/cocoa/app.icns
new file mode 100644
index 00000000000..055e46b934f
--- /dev/null
+++ b/chromium/ash/shell/cocoa/app.icns
Binary files differ
diff --git a/chromium/ash/shell/cocoa/nibs/MainMenu.xib b/chromium/ash/shell/cocoa/nibs/MainMenu.xib
new file mode 100644
index 00000000000..9190616eb05
--- /dev/null
+++ b/chromium/ash/shell/cocoa/nibs/MainMenu.xib
@@ -0,0 +1,2163 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<archive type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="7.10">
+ <data>
+ <int key="IBDocument.SystemTarget">1070</int>
+ <string key="IBDocument.SystemVersion">11C74</string>
+ <string key="IBDocument.InterfaceBuilderVersion">1900</string>
+ <string key="IBDocument.AppKitVersion">1138.23</string>
+ <string key="IBDocument.HIToolboxVersion">567.00</string>
+ <object class="NSMutableDictionary" key="IBDocument.PluginVersions">
+ <string key="NS.key.0">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string key="NS.object.0">1900</string>
+ </object>
+ <object class="NSArray" key="IBDocument.IntegratedClassDependencies">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <string>NSMenu</string>
+ <string>NSMenuItem</string>
+ <string>NSCustomObject</string>
+ </object>
+ <object class="NSArray" key="IBDocument.PluginDependencies">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
+ </object>
+ <object class="NSMutableDictionary" key="IBDocument.Metadata">
+ <string key="NS.key.0">PluginDependencyRecalculationVersion</string>
+ <integer value="1" key="NS.object.0"/>
+ </object>
+ <object class="NSMutableArray" key="IBDocument.RootObjects" id="1048">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <object class="NSCustomObject" id="1021">
+ <string key="NSClassName">NSApplication</string>
+ </object>
+ <object class="NSCustomObject" id="1014">
+ <string key="NSClassName">FirstResponder</string>
+ </object>
+ <object class="NSCustomObject" id="1050">
+ <string key="NSClassName">NSApplication</string>
+ </object>
+ <object class="NSMenu" id="649796088">
+ <string key="NSTitle">AMainMenu</string>
+ <object class="NSMutableArray" key="NSMenuItems">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <object class="NSMenuItem" id="694149608">
+ <reference key="NSMenu" ref="649796088"/>
+ <string key="NSTitle">Aura Shell</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <object class="NSCustomResource" key="NSOnImage" id="35465992">
+ <string key="NSClassName">NSImage</string>
+ <string key="NSResourceName">NSMenuCheckmark</string>
+ </object>
+ <object class="NSCustomResource" key="NSMixedImage" id="502551668">
+ <string key="NSClassName">NSImage</string>
+ <string key="NSResourceName">NSMenuMixedState</string>
+ </object>
+ <string key="NSAction">submenuAction:</string>
+ <object class="NSMenu" key="NSSubmenu" id="110575045">
+ <string key="NSTitle">Aura Shell</string>
+ <object class="NSMutableArray" key="NSMenuItems">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <object class="NSMenuItem" id="238522557">
+ <reference key="NSMenu" ref="110575045"/>
+ <string key="NSTitle">About Aura Shell</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="304266470">
+ <reference key="NSMenu" ref="110575045"/>
+ <bool key="NSIsDisabled">YES</bool>
+ <bool key="NSIsSeparator">YES</bool>
+ <string key="NSTitle"/>
+ <string key="NSKeyEquiv"/>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="609285721">
+ <reference key="NSMenu" ref="110575045"/>
+ <string key="NSTitle">Preferences…</string>
+ <string key="NSKeyEquiv">,</string>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="481834944">
+ <reference key="NSMenu" ref="110575045"/>
+ <bool key="NSIsDisabled">YES</bool>
+ <bool key="NSIsSeparator">YES</bool>
+ <string key="NSTitle"/>
+ <string key="NSKeyEquiv"/>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="1046388886">
+ <reference key="NSMenu" ref="110575045"/>
+ <string key="NSTitle">Services</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ <string key="NSAction">submenuAction:</string>
+ <object class="NSMenu" key="NSSubmenu" id="752062318">
+ <string key="NSTitle">Services</string>
+ <object class="NSMutableArray" key="NSMenuItems">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ </object>
+ <string key="NSName">_NSServicesMenu</string>
+ </object>
+ </object>
+ <object class="NSMenuItem" id="646227648">
+ <reference key="NSMenu" ref="110575045"/>
+ <bool key="NSIsDisabled">YES</bool>
+ <bool key="NSIsSeparator">YES</bool>
+ <string key="NSTitle"/>
+ <string key="NSKeyEquiv"/>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="755159360">
+ <reference key="NSMenu" ref="110575045"/>
+ <string key="NSTitle">Hide Aura Shell</string>
+ <string key="NSKeyEquiv">h</string>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="342932134">
+ <reference key="NSMenu" ref="110575045"/>
+ <string key="NSTitle">Hide Others</string>
+ <string key="NSKeyEquiv">h</string>
+ <int key="NSKeyEquivModMask">1572864</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="908899353">
+ <reference key="NSMenu" ref="110575045"/>
+ <string key="NSTitle">Show All</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="1056857174">
+ <reference key="NSMenu" ref="110575045"/>
+ <bool key="NSIsDisabled">YES</bool>
+ <bool key="NSIsSeparator">YES</bool>
+ <string key="NSTitle"/>
+ <string key="NSKeyEquiv"/>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="632727374">
+ <reference key="NSMenu" ref="110575045"/>
+ <string key="NSTitle">Quit Aura Shell</string>
+ <string key="NSKeyEquiv">q</string>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ </object>
+ <string key="NSName">_NSAppleMenu</string>
+ </object>
+ </object>
+ <object class="NSMenuItem" id="379814623">
+ <reference key="NSMenu" ref="649796088"/>
+ <string key="NSTitle">File</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ <string key="NSAction">submenuAction:</string>
+ <object class="NSMenu" key="NSSubmenu" id="720053764">
+ <string key="NSTitle">File</string>
+ <object class="NSMutableArray" key="NSMenuItems">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <object class="NSMenuItem" id="705341025">
+ <reference key="NSMenu" ref="720053764"/>
+ <string key="NSTitle">New</string>
+ <string key="NSKeyEquiv">n</string>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="722745758">
+ <reference key="NSMenu" ref="720053764"/>
+ <string key="NSTitle">Open…</string>
+ <string key="NSKeyEquiv">o</string>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="1025936716">
+ <reference key="NSMenu" ref="720053764"/>
+ <string key="NSTitle">Open Recent</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ <string key="NSAction">submenuAction:</string>
+ <object class="NSMenu" key="NSSubmenu" id="1065607017">
+ <string key="NSTitle">Open Recent</string>
+ <object class="NSMutableArray" key="NSMenuItems">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <object class="NSMenuItem" id="759406840">
+ <reference key="NSMenu" ref="1065607017"/>
+ <string key="NSTitle">Clear Menu</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ </object>
+ <string key="NSName">_NSRecentDocumentsMenu</string>
+ </object>
+ </object>
+ <object class="NSMenuItem" id="425164168">
+ <reference key="NSMenu" ref="720053764"/>
+ <bool key="NSIsDisabled">YES</bool>
+ <bool key="NSIsSeparator">YES</bool>
+ <string key="NSTitle"/>
+ <string key="NSKeyEquiv"/>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="776162233">
+ <reference key="NSMenu" ref="720053764"/>
+ <string key="NSTitle">Close</string>
+ <string key="NSKeyEquiv">w</string>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="1023925487">
+ <reference key="NSMenu" ref="720053764"/>
+ <string key="NSTitle">Save…</string>
+ <string key="NSKeyEquiv">s</string>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="579971712">
+ <reference key="NSMenu" ref="720053764"/>
+ <string key="NSTitle">Revert to Saved</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="1010469920">
+ <reference key="NSMenu" ref="720053764"/>
+ <bool key="NSIsDisabled">YES</bool>
+ <bool key="NSIsSeparator">YES</bool>
+ <string key="NSTitle"/>
+ <string key="NSKeyEquiv"/>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="294629803">
+ <reference key="NSMenu" ref="720053764"/>
+ <string key="NSTitle">Page Setup...</string>
+ <string key="NSKeyEquiv">P</string>
+ <int key="NSKeyEquivModMask">1179648</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ <string key="NSToolTip"/>
+ </object>
+ <object class="NSMenuItem" id="49223823">
+ <reference key="NSMenu" ref="720053764"/>
+ <string key="NSTitle">Print…</string>
+ <string key="NSKeyEquiv">p</string>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ </object>
+ </object>
+ </object>
+ <object class="NSMenuItem" id="952259628">
+ <reference key="NSMenu" ref="649796088"/>
+ <string key="NSTitle">Edit</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ <string key="NSAction">submenuAction:</string>
+ <object class="NSMenu" key="NSSubmenu" id="789758025">
+ <string key="NSTitle">Edit</string>
+ <object class="NSMutableArray" key="NSMenuItems">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <object class="NSMenuItem" id="1058277027">
+ <reference key="NSMenu" ref="789758025"/>
+ <string key="NSTitle">Undo</string>
+ <string key="NSKeyEquiv">z</string>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="790794224">
+ <reference key="NSMenu" ref="789758025"/>
+ <string key="NSTitle">Redo</string>
+ <string key="NSKeyEquiv">Z</string>
+ <int key="NSKeyEquivModMask">1179648</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="1040322652">
+ <reference key="NSMenu" ref="789758025"/>
+ <bool key="NSIsDisabled">YES</bool>
+ <bool key="NSIsSeparator">YES</bool>
+ <string key="NSTitle"/>
+ <string key="NSKeyEquiv"/>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="296257095">
+ <reference key="NSMenu" ref="789758025"/>
+ <string key="NSTitle">Cut</string>
+ <string key="NSKeyEquiv">x</string>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="860595796">
+ <reference key="NSMenu" ref="789758025"/>
+ <string key="NSTitle">Copy</string>
+ <string key="NSKeyEquiv">c</string>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="29853731">
+ <reference key="NSMenu" ref="789758025"/>
+ <string key="NSTitle">Paste</string>
+ <string key="NSKeyEquiv">v</string>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="82994268">
+ <reference key="NSMenu" ref="789758025"/>
+ <string key="NSTitle">Paste and Match Style</string>
+ <string key="NSKeyEquiv">V</string>
+ <int key="NSKeyEquivModMask">1572864</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="437104165">
+ <reference key="NSMenu" ref="789758025"/>
+ <string key="NSTitle">Delete</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="583158037">
+ <reference key="NSMenu" ref="789758025"/>
+ <string key="NSTitle">Select All</string>
+ <string key="NSKeyEquiv">a</string>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="212016141">
+ <reference key="NSMenu" ref="789758025"/>
+ <bool key="NSIsDisabled">YES</bool>
+ <bool key="NSIsSeparator">YES</bool>
+ <string key="NSTitle"/>
+ <string key="NSKeyEquiv"/>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="892235320">
+ <reference key="NSMenu" ref="789758025"/>
+ <string key="NSTitle">Find</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ <string key="NSAction">submenuAction:</string>
+ <object class="NSMenu" key="NSSubmenu" id="963351320">
+ <string key="NSTitle">Find</string>
+ <object class="NSMutableArray" key="NSMenuItems">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <object class="NSMenuItem" id="447796847">
+ <reference key="NSMenu" ref="963351320"/>
+ <string key="NSTitle">Find…</string>
+ <string key="NSKeyEquiv">f</string>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ <int key="NSTag">1</int>
+ </object>
+ <object class="NSMenuItem" id="738670835">
+ <reference key="NSMenu" ref="963351320"/>
+ <string key="NSTitle">Find and Replace…</string>
+ <string key="NSKeyEquiv">f</string>
+ <int key="NSKeyEquivModMask">1572864</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ <int key="NSTag">12</int>
+ </object>
+ <object class="NSMenuItem" id="326711663">
+ <reference key="NSMenu" ref="963351320"/>
+ <string key="NSTitle">Find Next</string>
+ <string key="NSKeyEquiv">g</string>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ <int key="NSTag">2</int>
+ </object>
+ <object class="NSMenuItem" id="270902937">
+ <reference key="NSMenu" ref="963351320"/>
+ <string key="NSTitle">Find Previous</string>
+ <string key="NSKeyEquiv">G</string>
+ <int key="NSKeyEquivModMask">1179648</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ <int key="NSTag">3</int>
+ </object>
+ <object class="NSMenuItem" id="159080638">
+ <reference key="NSMenu" ref="963351320"/>
+ <string key="NSTitle">Use Selection for Find</string>
+ <string key="NSKeyEquiv">e</string>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ <int key="NSTag">7</int>
+ </object>
+ <object class="NSMenuItem" id="88285865">
+ <reference key="NSMenu" ref="963351320"/>
+ <string key="NSTitle">Jump to Selection</string>
+ <string key="NSKeyEquiv">j</string>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ </object>
+ </object>
+ </object>
+ <object class="NSMenuItem" id="972420730">
+ <reference key="NSMenu" ref="789758025"/>
+ <string key="NSTitle">Spelling and Grammar</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ <string key="NSAction">submenuAction:</string>
+ <object class="NSMenu" key="NSSubmenu" id="769623530">
+ <string key="NSTitle">Spelling and Grammar</string>
+ <object class="NSMutableArray" key="NSMenuItems">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <object class="NSMenuItem" id="679648819">
+ <reference key="NSMenu" ref="769623530"/>
+ <string key="NSTitle">Show Spelling and Grammar</string>
+ <string key="NSKeyEquiv">:</string>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="96193923">
+ <reference key="NSMenu" ref="769623530"/>
+ <string key="NSTitle">Check Document Now</string>
+ <string key="NSKeyEquiv">;</string>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="859480356">
+ <reference key="NSMenu" ref="769623530"/>
+ <bool key="NSIsDisabled">YES</bool>
+ <bool key="NSIsSeparator">YES</bool>
+ <string key="NSTitle"/>
+ <string key="NSKeyEquiv"/>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="948374510">
+ <reference key="NSMenu" ref="769623530"/>
+ <string key="NSTitle">Check Spelling While Typing</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="967646866">
+ <reference key="NSMenu" ref="769623530"/>
+ <string key="NSTitle">Check Grammar With Spelling</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="795346622">
+ <reference key="NSMenu" ref="769623530"/>
+ <string key="NSTitle">Correct Spelling Automatically</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ </object>
+ </object>
+ </object>
+ <object class="NSMenuItem" id="507821607">
+ <reference key="NSMenu" ref="789758025"/>
+ <string key="NSTitle">Substitutions</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ <string key="NSAction">submenuAction:</string>
+ <object class="NSMenu" key="NSSubmenu" id="698887838">
+ <string key="NSTitle">Substitutions</string>
+ <object class="NSMutableArray" key="NSMenuItems">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <object class="NSMenuItem" id="65139061">
+ <reference key="NSMenu" ref="698887838"/>
+ <string key="NSTitle">Show Substitutions</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="19036812">
+ <reference key="NSMenu" ref="698887838"/>
+ <bool key="NSIsDisabled">YES</bool>
+ <bool key="NSIsSeparator">YES</bool>
+ <string key="NSTitle"/>
+ <string key="NSKeyEquiv"/>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="605118523">
+ <reference key="NSMenu" ref="698887838"/>
+ <string key="NSTitle">Smart Copy/Paste</string>
+ <string key="NSKeyEquiv">f</string>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ <int key="NSTag">1</int>
+ </object>
+ <object class="NSMenuItem" id="197661976">
+ <reference key="NSMenu" ref="698887838"/>
+ <string key="NSTitle">Smart Quotes</string>
+ <string key="NSKeyEquiv">g</string>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ <int key="NSTag">2</int>
+ </object>
+ <object class="NSMenuItem" id="672708820">
+ <reference key="NSMenu" ref="698887838"/>
+ <string key="NSTitle">Smart Dashes</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="708854459">
+ <reference key="NSMenu" ref="698887838"/>
+ <string key="NSTitle">Smart Links</string>
+ <string key="NSKeyEquiv">G</string>
+ <int key="NSKeyEquivModMask">1179648</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ <int key="NSTag">3</int>
+ </object>
+ <object class="NSMenuItem" id="537092702">
+ <reference key="NSMenu" ref="698887838"/>
+ <string key="NSTitle">Text Replacement</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ </object>
+ </object>
+ </object>
+ <object class="NSMenuItem" id="288088188">
+ <reference key="NSMenu" ref="789758025"/>
+ <string key="NSTitle">Transformations</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ <string key="NSAction">submenuAction:</string>
+ <object class="NSMenu" key="NSSubmenu" id="579392910">
+ <string key="NSTitle">Transformations</string>
+ <object class="NSMutableArray" key="NSMenuItems">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <object class="NSMenuItem" id="1060694897">
+ <reference key="NSMenu" ref="579392910"/>
+ <string key="NSTitle">Make Upper Case</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="879586729">
+ <reference key="NSMenu" ref="579392910"/>
+ <string key="NSTitle">Make Lower Case</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="56570060">
+ <reference key="NSMenu" ref="579392910"/>
+ <string key="NSTitle">Capitalize</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ </object>
+ </object>
+ </object>
+ <object class="NSMenuItem" id="676164635">
+ <reference key="NSMenu" ref="789758025"/>
+ <string key="NSTitle">Speech</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ <string key="NSAction">submenuAction:</string>
+ <object class="NSMenu" key="NSSubmenu" id="785027613">
+ <string key="NSTitle">Speech</string>
+ <object class="NSMutableArray" key="NSMenuItems">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <object class="NSMenuItem" id="731782645">
+ <reference key="NSMenu" ref="785027613"/>
+ <string key="NSTitle">Start Speaking</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="680220178">
+ <reference key="NSMenu" ref="785027613"/>
+ <string key="NSTitle">Stop Speaking</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ </object>
+ </object>
+ </object>
+ </object>
+ </object>
+ </object>
+ <object class="NSMenuItem" id="586577488">
+ <reference key="NSMenu" ref="649796088"/>
+ <string key="NSTitle">View</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ <string key="NSAction">submenuAction:</string>
+ <object class="NSMenu" key="NSSubmenu" id="466310130">
+ <string key="NSTitle">View</string>
+ <object class="NSMutableArray" key="NSMenuItems">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <object class="NSMenuItem" id="102151532">
+ <reference key="NSMenu" ref="466310130"/>
+ <string key="NSTitle">Show Toolbar</string>
+ <string key="NSKeyEquiv">t</string>
+ <int key="NSKeyEquivModMask">1572864</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="237841660">
+ <reference key="NSMenu" ref="466310130"/>
+ <string key="NSTitle">Customize Toolbar…</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ </object>
+ </object>
+ </object>
+ <object class="NSMenuItem" id="713487014">
+ <reference key="NSMenu" ref="649796088"/>
+ <string key="NSTitle">Window</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ <string key="NSAction">submenuAction:</string>
+ <object class="NSMenu" key="NSSubmenu" id="835318025">
+ <string key="NSTitle">Window</string>
+ <object class="NSMutableArray" key="NSMenuItems">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <object class="NSMenuItem" id="1011231497">
+ <reference key="NSMenu" ref="835318025"/>
+ <string key="NSTitle">Minimize</string>
+ <string key="NSKeyEquiv">m</string>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="575023229">
+ <reference key="NSMenu" ref="835318025"/>
+ <string key="NSTitle">Zoom</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="299356726">
+ <reference key="NSMenu" ref="835318025"/>
+ <bool key="NSIsDisabled">YES</bool>
+ <bool key="NSIsSeparator">YES</bool>
+ <string key="NSTitle"/>
+ <string key="NSKeyEquiv"/>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ <object class="NSMenuItem" id="625202149">
+ <reference key="NSMenu" ref="835318025"/>
+ <string key="NSTitle">Bring All to Front</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ </object>
+ <string key="NSName">_NSWindowsMenu</string>
+ </object>
+ </object>
+ <object class="NSMenuItem" id="448692316">
+ <reference key="NSMenu" ref="649796088"/>
+ <string key="NSTitle">Help</string>
+ <string key="NSKeyEquiv"/>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ <string key="NSAction">submenuAction:</string>
+ <object class="NSMenu" key="NSSubmenu" id="992780483">
+ <string key="NSTitle">Help</string>
+ <object class="NSMutableArray" key="NSMenuItems">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <object class="NSMenuItem" id="105068016">
+ <reference key="NSMenu" ref="992780483"/>
+ <string key="NSTitle">Aura Shell Help</string>
+ <string key="NSKeyEquiv">?</string>
+ <int key="NSKeyEquivModMask">1048576</int>
+ <int key="NSMnemonicLoc">2147483647</int>
+ <reference key="NSOnImage" ref="35465992"/>
+ <reference key="NSMixedImage" ref="502551668"/>
+ </object>
+ </object>
+ <string key="NSName">_NSHelpMenu</string>
+ </object>
+ </object>
+ </object>
+ <string key="NSName">_NSMainMenu</string>
+ </object>
+ </object>
+ <object class="IBObjectContainer" key="IBDocument.Objects">
+ <object class="NSMutableArray" key="connectionRecords">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">performMiniaturize:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="1011231497"/>
+ </object>
+ <int key="connectionID">37</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">arrangeInFront:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="625202149"/>
+ </object>
+ <int key="connectionID">39</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">print:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="49223823"/>
+ </object>
+ <int key="connectionID">86</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">runPageLayout:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="294629803"/>
+ </object>
+ <int key="connectionID">87</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">clearRecentDocuments:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="759406840"/>
+ </object>
+ <int key="connectionID">127</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">orderFrontStandardAboutPanel:</string>
+ <reference key="source" ref="1021"/>
+ <reference key="destination" ref="238522557"/>
+ </object>
+ <int key="connectionID">142</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">performClose:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="776162233"/>
+ </object>
+ <int key="connectionID">193</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">toggleContinuousSpellChecking:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="948374510"/>
+ </object>
+ <int key="connectionID">222</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">undo:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="1058277027"/>
+ </object>
+ <int key="connectionID">223</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">copy:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="860595796"/>
+ </object>
+ <int key="connectionID">224</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">checkSpelling:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="96193923"/>
+ </object>
+ <int key="connectionID">225</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">paste:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="29853731"/>
+ </object>
+ <int key="connectionID">226</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">stopSpeaking:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="680220178"/>
+ </object>
+ <int key="connectionID">227</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">cut:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="296257095"/>
+ </object>
+ <int key="connectionID">228</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">showGuessPanel:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="679648819"/>
+ </object>
+ <int key="connectionID">230</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">redo:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="790794224"/>
+ </object>
+ <int key="connectionID">231</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">selectAll:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="583158037"/>
+ </object>
+ <int key="connectionID">232</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">startSpeaking:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="731782645"/>
+ </object>
+ <int key="connectionID">233</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">delete:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="437104165"/>
+ </object>
+ <int key="connectionID">235</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">performZoom:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="575023229"/>
+ </object>
+ <int key="connectionID">240</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">performFindPanelAction:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="447796847"/>
+ </object>
+ <int key="connectionID">241</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">centerSelectionInVisibleArea:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="88285865"/>
+ </object>
+ <int key="connectionID">245</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">toggleGrammarChecking:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="967646866"/>
+ </object>
+ <int key="connectionID">347</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">toggleSmartInsertDelete:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="605118523"/>
+ </object>
+ <int key="connectionID">355</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">toggleAutomaticQuoteSubstitution:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="197661976"/>
+ </object>
+ <int key="connectionID">356</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">toggleAutomaticLinkDetection:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="708854459"/>
+ </object>
+ <int key="connectionID">357</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">saveDocument:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="1023925487"/>
+ </object>
+ <int key="connectionID">362</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">revertDocumentToSaved:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="579971712"/>
+ </object>
+ <int key="connectionID">364</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">runToolbarCustomizationPalette:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="237841660"/>
+ </object>
+ <int key="connectionID">365</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">toggleToolbarShown:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="102151532"/>
+ </object>
+ <int key="connectionID">366</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">hide:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="755159360"/>
+ </object>
+ <int key="connectionID">367</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">hideOtherApplications:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="342932134"/>
+ </object>
+ <int key="connectionID">368</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">unhideAllApplications:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="908899353"/>
+ </object>
+ <int key="connectionID">370</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">newDocument:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="705341025"/>
+ </object>
+ <int key="connectionID">373</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">openDocument:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="722745758"/>
+ </object>
+ <int key="connectionID">374</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">terminate:</string>
+ <reference key="source" ref="1050"/>
+ <reference key="destination" ref="632727374"/>
+ </object>
+ <int key="connectionID">449</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">toggleAutomaticSpellingCorrection:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="795346622"/>
+ </object>
+ <int key="connectionID">456</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">orderFrontSubstitutionsPanel:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="65139061"/>
+ </object>
+ <int key="connectionID">458</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">toggleAutomaticDashSubstitution:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="672708820"/>
+ </object>
+ <int key="connectionID">461</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">toggleAutomaticTextReplacement:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="537092702"/>
+ </object>
+ <int key="connectionID">463</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">uppercaseWord:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="1060694897"/>
+ </object>
+ <int key="connectionID">464</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">capitalizeWord:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="56570060"/>
+ </object>
+ <int key="connectionID">467</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">lowercaseWord:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="879586729"/>
+ </object>
+ <int key="connectionID">468</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">pasteAsPlainText:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="82994268"/>
+ </object>
+ <int key="connectionID">486</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">performFindPanelAction:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="326711663"/>
+ </object>
+ <int key="connectionID">487</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">performFindPanelAction:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="270902937"/>
+ </object>
+ <int key="connectionID">488</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">performFindPanelAction:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="159080638"/>
+ </object>
+ <int key="connectionID">489</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">showHelp:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="105068016"/>
+ </object>
+ <int key="connectionID">493</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBActionConnection" key="connection">
+ <string key="label">performFindPanelAction:</string>
+ <reference key="source" ref="1014"/>
+ <reference key="destination" ref="738670835"/>
+ </object>
+ <int key="connectionID">535</int>
+ </object>
+ </object>
+ <object class="IBMutableOrderedSet" key="objectRecords">
+ <object class="NSArray" key="orderedObjects">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <object class="IBObjectRecord">
+ <int key="objectID">0</int>
+ <object class="NSArray" key="object" id="0">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ </object>
+ <reference key="children" ref="1048"/>
+ <nil key="parent"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">-2</int>
+ <reference key="object" ref="1021"/>
+ <reference key="parent" ref="0"/>
+ <string key="objectName">File's Owner</string>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">-1</int>
+ <reference key="object" ref="1014"/>
+ <reference key="parent" ref="0"/>
+ <string key="objectName">First Responder</string>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">-3</int>
+ <reference key="object" ref="1050"/>
+ <reference key="parent" ref="0"/>
+ <string key="objectName">Application</string>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">29</int>
+ <reference key="object" ref="649796088"/>
+ <object class="NSMutableArray" key="children">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <reference ref="713487014"/>
+ <reference ref="694149608"/>
+ <reference ref="952259628"/>
+ <reference ref="379814623"/>
+ <reference ref="586577488"/>
+ <reference ref="448692316"/>
+ </object>
+ <reference key="parent" ref="0"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">19</int>
+ <reference key="object" ref="713487014"/>
+ <object class="NSMutableArray" key="children">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <reference ref="835318025"/>
+ </object>
+ <reference key="parent" ref="649796088"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">56</int>
+ <reference key="object" ref="694149608"/>
+ <object class="NSMutableArray" key="children">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <reference ref="110575045"/>
+ </object>
+ <reference key="parent" ref="649796088"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">217</int>
+ <reference key="object" ref="952259628"/>
+ <object class="NSMutableArray" key="children">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <reference ref="789758025"/>
+ </object>
+ <reference key="parent" ref="649796088"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">83</int>
+ <reference key="object" ref="379814623"/>
+ <object class="NSMutableArray" key="children">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <reference ref="720053764"/>
+ </object>
+ <reference key="parent" ref="649796088"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">81</int>
+ <reference key="object" ref="720053764"/>
+ <object class="NSMutableArray" key="children">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <reference ref="1023925487"/>
+ <reference ref="49223823"/>
+ <reference ref="722745758"/>
+ <reference ref="705341025"/>
+ <reference ref="1025936716"/>
+ <reference ref="294629803"/>
+ <reference ref="776162233"/>
+ <reference ref="425164168"/>
+ <reference ref="579971712"/>
+ <reference ref="1010469920"/>
+ </object>
+ <reference key="parent" ref="379814623"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">75</int>
+ <reference key="object" ref="1023925487"/>
+ <reference key="parent" ref="720053764"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">78</int>
+ <reference key="object" ref="49223823"/>
+ <reference key="parent" ref="720053764"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">72</int>
+ <reference key="object" ref="722745758"/>
+ <reference key="parent" ref="720053764"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">82</int>
+ <reference key="object" ref="705341025"/>
+ <reference key="parent" ref="720053764"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">124</int>
+ <reference key="object" ref="1025936716"/>
+ <object class="NSMutableArray" key="children">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <reference ref="1065607017"/>
+ </object>
+ <reference key="parent" ref="720053764"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">77</int>
+ <reference key="object" ref="294629803"/>
+ <reference key="parent" ref="720053764"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">73</int>
+ <reference key="object" ref="776162233"/>
+ <reference key="parent" ref="720053764"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">79</int>
+ <reference key="object" ref="425164168"/>
+ <reference key="parent" ref="720053764"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">112</int>
+ <reference key="object" ref="579971712"/>
+ <reference key="parent" ref="720053764"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">74</int>
+ <reference key="object" ref="1010469920"/>
+ <reference key="parent" ref="720053764"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">125</int>
+ <reference key="object" ref="1065607017"/>
+ <object class="NSMutableArray" key="children">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <reference ref="759406840"/>
+ </object>
+ <reference key="parent" ref="1025936716"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">126</int>
+ <reference key="object" ref="759406840"/>
+ <reference key="parent" ref="1065607017"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">205</int>
+ <reference key="object" ref="789758025"/>
+ <object class="NSMutableArray" key="children">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <reference ref="437104165"/>
+ <reference ref="583158037"/>
+ <reference ref="1058277027"/>
+ <reference ref="212016141"/>
+ <reference ref="296257095"/>
+ <reference ref="29853731"/>
+ <reference ref="860595796"/>
+ <reference ref="1040322652"/>
+ <reference ref="790794224"/>
+ <reference ref="892235320"/>
+ <reference ref="972420730"/>
+ <reference ref="676164635"/>
+ <reference ref="507821607"/>
+ <reference ref="288088188"/>
+ <reference ref="82994268"/>
+ </object>
+ <reference key="parent" ref="952259628"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">202</int>
+ <reference key="object" ref="437104165"/>
+ <reference key="parent" ref="789758025"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">198</int>
+ <reference key="object" ref="583158037"/>
+ <reference key="parent" ref="789758025"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">207</int>
+ <reference key="object" ref="1058277027"/>
+ <reference key="parent" ref="789758025"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">214</int>
+ <reference key="object" ref="212016141"/>
+ <reference key="parent" ref="789758025"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">199</int>
+ <reference key="object" ref="296257095"/>
+ <reference key="parent" ref="789758025"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">203</int>
+ <reference key="object" ref="29853731"/>
+ <reference key="parent" ref="789758025"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">197</int>
+ <reference key="object" ref="860595796"/>
+ <reference key="parent" ref="789758025"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">206</int>
+ <reference key="object" ref="1040322652"/>
+ <reference key="parent" ref="789758025"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">215</int>
+ <reference key="object" ref="790794224"/>
+ <reference key="parent" ref="789758025"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">218</int>
+ <reference key="object" ref="892235320"/>
+ <object class="NSMutableArray" key="children">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <reference ref="963351320"/>
+ </object>
+ <reference key="parent" ref="789758025"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">216</int>
+ <reference key="object" ref="972420730"/>
+ <object class="NSMutableArray" key="children">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <reference ref="769623530"/>
+ </object>
+ <reference key="parent" ref="789758025"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">200</int>
+ <reference key="object" ref="769623530"/>
+ <object class="NSMutableArray" key="children">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <reference ref="948374510"/>
+ <reference ref="96193923"/>
+ <reference ref="679648819"/>
+ <reference ref="967646866"/>
+ <reference ref="859480356"/>
+ <reference ref="795346622"/>
+ </object>
+ <reference key="parent" ref="972420730"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">219</int>
+ <reference key="object" ref="948374510"/>
+ <reference key="parent" ref="769623530"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">201</int>
+ <reference key="object" ref="96193923"/>
+ <reference key="parent" ref="769623530"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">204</int>
+ <reference key="object" ref="679648819"/>
+ <reference key="parent" ref="769623530"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">220</int>
+ <reference key="object" ref="963351320"/>
+ <object class="NSMutableArray" key="children">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <reference ref="270902937"/>
+ <reference ref="88285865"/>
+ <reference ref="159080638"/>
+ <reference ref="326711663"/>
+ <reference ref="447796847"/>
+ <reference ref="738670835"/>
+ </object>
+ <reference key="parent" ref="892235320"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">213</int>
+ <reference key="object" ref="270902937"/>
+ <reference key="parent" ref="963351320"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">210</int>
+ <reference key="object" ref="88285865"/>
+ <reference key="parent" ref="963351320"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">221</int>
+ <reference key="object" ref="159080638"/>
+ <reference key="parent" ref="963351320"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">208</int>
+ <reference key="object" ref="326711663"/>
+ <reference key="parent" ref="963351320"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">209</int>
+ <reference key="object" ref="447796847"/>
+ <reference key="parent" ref="963351320"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">57</int>
+ <reference key="object" ref="110575045"/>
+ <object class="NSMutableArray" key="children">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <reference ref="238522557"/>
+ <reference ref="755159360"/>
+ <reference ref="908899353"/>
+ <reference ref="632727374"/>
+ <reference ref="646227648"/>
+ <reference ref="609285721"/>
+ <reference ref="481834944"/>
+ <reference ref="304266470"/>
+ <reference ref="1046388886"/>
+ <reference ref="1056857174"/>
+ <reference ref="342932134"/>
+ </object>
+ <reference key="parent" ref="694149608"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">58</int>
+ <reference key="object" ref="238522557"/>
+ <reference key="parent" ref="110575045"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">134</int>
+ <reference key="object" ref="755159360"/>
+ <reference key="parent" ref="110575045"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">150</int>
+ <reference key="object" ref="908899353"/>
+ <reference key="parent" ref="110575045"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">136</int>
+ <reference key="object" ref="632727374"/>
+ <reference key="parent" ref="110575045"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">144</int>
+ <reference key="object" ref="646227648"/>
+ <reference key="parent" ref="110575045"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">129</int>
+ <reference key="object" ref="609285721"/>
+ <reference key="parent" ref="110575045"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">143</int>
+ <reference key="object" ref="481834944"/>
+ <reference key="parent" ref="110575045"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">236</int>
+ <reference key="object" ref="304266470"/>
+ <reference key="parent" ref="110575045"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">131</int>
+ <reference key="object" ref="1046388886"/>
+ <object class="NSMutableArray" key="children">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <reference ref="752062318"/>
+ </object>
+ <reference key="parent" ref="110575045"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">149</int>
+ <reference key="object" ref="1056857174"/>
+ <reference key="parent" ref="110575045"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">145</int>
+ <reference key="object" ref="342932134"/>
+ <reference key="parent" ref="110575045"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">130</int>
+ <reference key="object" ref="752062318"/>
+ <reference key="parent" ref="1046388886"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">24</int>
+ <reference key="object" ref="835318025"/>
+ <object class="NSMutableArray" key="children">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <reference ref="299356726"/>
+ <reference ref="625202149"/>
+ <reference ref="575023229"/>
+ <reference ref="1011231497"/>
+ </object>
+ <reference key="parent" ref="713487014"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">92</int>
+ <reference key="object" ref="299356726"/>
+ <reference key="parent" ref="835318025"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">5</int>
+ <reference key="object" ref="625202149"/>
+ <reference key="parent" ref="835318025"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">239</int>
+ <reference key="object" ref="575023229"/>
+ <reference key="parent" ref="835318025"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">23</int>
+ <reference key="object" ref="1011231497"/>
+ <reference key="parent" ref="835318025"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">295</int>
+ <reference key="object" ref="586577488"/>
+ <object class="NSMutableArray" key="children">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <reference ref="466310130"/>
+ </object>
+ <reference key="parent" ref="649796088"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">296</int>
+ <reference key="object" ref="466310130"/>
+ <object class="NSMutableArray" key="children">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <reference ref="102151532"/>
+ <reference ref="237841660"/>
+ </object>
+ <reference key="parent" ref="586577488"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">297</int>
+ <reference key="object" ref="102151532"/>
+ <reference key="parent" ref="466310130"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">298</int>
+ <reference key="object" ref="237841660"/>
+ <reference key="parent" ref="466310130"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">211</int>
+ <reference key="object" ref="676164635"/>
+ <object class="NSMutableArray" key="children">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <reference ref="785027613"/>
+ </object>
+ <reference key="parent" ref="789758025"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">212</int>
+ <reference key="object" ref="785027613"/>
+ <object class="NSMutableArray" key="children">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <reference ref="680220178"/>
+ <reference ref="731782645"/>
+ </object>
+ <reference key="parent" ref="676164635"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">195</int>
+ <reference key="object" ref="680220178"/>
+ <reference key="parent" ref="785027613"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">196</int>
+ <reference key="object" ref="731782645"/>
+ <reference key="parent" ref="785027613"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">346</int>
+ <reference key="object" ref="967646866"/>
+ <reference key="parent" ref="769623530"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">348</int>
+ <reference key="object" ref="507821607"/>
+ <object class="NSMutableArray" key="children">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <reference ref="698887838"/>
+ </object>
+ <reference key="parent" ref="789758025"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">349</int>
+ <reference key="object" ref="698887838"/>
+ <object class="NSMutableArray" key="children">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <reference ref="605118523"/>
+ <reference ref="197661976"/>
+ <reference ref="708854459"/>
+ <reference ref="65139061"/>
+ <reference ref="19036812"/>
+ <reference ref="672708820"/>
+ <reference ref="537092702"/>
+ </object>
+ <reference key="parent" ref="507821607"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">350</int>
+ <reference key="object" ref="605118523"/>
+ <reference key="parent" ref="698887838"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">351</int>
+ <reference key="object" ref="197661976"/>
+ <reference key="parent" ref="698887838"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">354</int>
+ <reference key="object" ref="708854459"/>
+ <reference key="parent" ref="698887838"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">450</int>
+ <reference key="object" ref="288088188"/>
+ <object class="NSMutableArray" key="children">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <reference ref="579392910"/>
+ </object>
+ <reference key="parent" ref="789758025"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">451</int>
+ <reference key="object" ref="579392910"/>
+ <object class="NSMutableArray" key="children">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <reference ref="1060694897"/>
+ <reference ref="879586729"/>
+ <reference ref="56570060"/>
+ </object>
+ <reference key="parent" ref="288088188"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">452</int>
+ <reference key="object" ref="1060694897"/>
+ <reference key="parent" ref="579392910"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">453</int>
+ <reference key="object" ref="859480356"/>
+ <reference key="parent" ref="769623530"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">454</int>
+ <reference key="object" ref="795346622"/>
+ <reference key="parent" ref="769623530"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">457</int>
+ <reference key="object" ref="65139061"/>
+ <reference key="parent" ref="698887838"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">459</int>
+ <reference key="object" ref="19036812"/>
+ <reference key="parent" ref="698887838"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">460</int>
+ <reference key="object" ref="672708820"/>
+ <reference key="parent" ref="698887838"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">462</int>
+ <reference key="object" ref="537092702"/>
+ <reference key="parent" ref="698887838"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">465</int>
+ <reference key="object" ref="879586729"/>
+ <reference key="parent" ref="579392910"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">466</int>
+ <reference key="object" ref="56570060"/>
+ <reference key="parent" ref="579392910"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">485</int>
+ <reference key="object" ref="82994268"/>
+ <reference key="parent" ref="789758025"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">490</int>
+ <reference key="object" ref="448692316"/>
+ <object class="NSMutableArray" key="children">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <reference ref="992780483"/>
+ </object>
+ <reference key="parent" ref="649796088"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">491</int>
+ <reference key="object" ref="992780483"/>
+ <object class="NSMutableArray" key="children">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <reference ref="105068016"/>
+ </object>
+ <reference key="parent" ref="448692316"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">492</int>
+ <reference key="object" ref="105068016"/>
+ <reference key="parent" ref="992780483"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">534</int>
+ <reference key="object" ref="738670835"/>
+ <reference key="parent" ref="963351320"/>
+ </object>
+ </object>
+ </object>
+ <object class="NSMutableDictionary" key="flattenedProperties">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <object class="NSArray" key="dict.sortedKeys">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <string>-1.IBPluginDependency</string>
+ <string>-2.IBPluginDependency</string>
+ <string>-3.IBPluginDependency</string>
+ <string>112.IBPluginDependency</string>
+ <string>124.IBPluginDependency</string>
+ <string>125.IBPluginDependency</string>
+ <string>126.IBPluginDependency</string>
+ <string>129.IBPluginDependency</string>
+ <string>130.IBPluginDependency</string>
+ <string>131.IBPluginDependency</string>
+ <string>134.IBPluginDependency</string>
+ <string>136.IBPluginDependency</string>
+ <string>143.IBPluginDependency</string>
+ <string>144.IBPluginDependency</string>
+ <string>145.IBPluginDependency</string>
+ <string>149.IBPluginDependency</string>
+ <string>150.IBPluginDependency</string>
+ <string>19.IBPluginDependency</string>
+ <string>195.IBPluginDependency</string>
+ <string>196.IBPluginDependency</string>
+ <string>197.IBPluginDependency</string>
+ <string>198.IBPluginDependency</string>
+ <string>199.IBPluginDependency</string>
+ <string>200.IBPluginDependency</string>
+ <string>201.IBPluginDependency</string>
+ <string>202.IBPluginDependency</string>
+ <string>203.IBPluginDependency</string>
+ <string>204.IBPluginDependency</string>
+ <string>205.IBPluginDependency</string>
+ <string>206.IBPluginDependency</string>
+ <string>207.IBPluginDependency</string>
+ <string>208.IBPluginDependency</string>
+ <string>209.IBPluginDependency</string>
+ <string>210.IBPluginDependency</string>
+ <string>211.IBPluginDependency</string>
+ <string>212.IBPluginDependency</string>
+ <string>213.IBPluginDependency</string>
+ <string>214.IBPluginDependency</string>
+ <string>215.IBPluginDependency</string>
+ <string>216.IBPluginDependency</string>
+ <string>217.IBPluginDependency</string>
+ <string>218.IBPluginDependency</string>
+ <string>219.IBPluginDependency</string>
+ <string>220.IBPluginDependency</string>
+ <string>221.IBPluginDependency</string>
+ <string>23.IBPluginDependency</string>
+ <string>236.IBPluginDependency</string>
+ <string>239.IBPluginDependency</string>
+ <string>24.IBPluginDependency</string>
+ <string>29.IBPluginDependency</string>
+ <string>295.IBPluginDependency</string>
+ <string>296.IBPluginDependency</string>
+ <string>297.IBPluginDependency</string>
+ <string>298.IBPluginDependency</string>
+ <string>346.IBPluginDependency</string>
+ <string>348.IBPluginDependency</string>
+ <string>349.IBPluginDependency</string>
+ <string>350.IBPluginDependency</string>
+ <string>351.IBPluginDependency</string>
+ <string>354.IBPluginDependency</string>
+ <string>450.IBPluginDependency</string>
+ <string>451.IBPluginDependency</string>
+ <string>452.IBPluginDependency</string>
+ <string>453.IBPluginDependency</string>
+ <string>454.IBPluginDependency</string>
+ <string>457.IBPluginDependency</string>
+ <string>459.IBPluginDependency</string>
+ <string>460.IBPluginDependency</string>
+ <string>462.IBPluginDependency</string>
+ <string>465.IBPluginDependency</string>
+ <string>466.IBPluginDependency</string>
+ <string>485.IBPluginDependency</string>
+ <string>490.IBPluginDependency</string>
+ <string>491.IBPluginDependency</string>
+ <string>492.IBPluginDependency</string>
+ <string>5.IBPluginDependency</string>
+ <string>534.IBPluginDependency</string>
+ <string>56.IBPluginDependency</string>
+ <string>57.IBPluginDependency</string>
+ <string>58.IBPluginDependency</string>
+ <string>72.IBPluginDependency</string>
+ <string>73.IBPluginDependency</string>
+ <string>74.IBPluginDependency</string>
+ <string>75.IBPluginDependency</string>
+ <string>77.IBPluginDependency</string>
+ <string>78.IBPluginDependency</string>
+ <string>79.IBPluginDependency</string>
+ <string>81.IBPluginDependency</string>
+ <string>82.IBPluginDependency</string>
+ <string>83.IBPluginDependency</string>
+ <string>92.IBPluginDependency</string>
+ </object>
+ <object class="NSMutableArray" key="dict.values">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
+ </object>
+ </object>
+ <object class="NSMutableDictionary" key="unlocalizedProperties">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <reference key="dict.sortedKeys" ref="0"/>
+ <reference key="dict.values" ref="0"/>
+ </object>
+ <nil key="activeLocalization"/>
+ <object class="NSMutableDictionary" key="localizations">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <reference key="dict.sortedKeys" ref="0"/>
+ <reference key="dict.values" ref="0"/>
+ </object>
+ <nil key="sourceID"/>
+ <int key="maxID">535</int>
+ </object>
+ <object class="IBClassDescriber" key="IBDocument.Classes">
+ <object class="NSMutableArray" key="referencedPartialClassDescriptions">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <object class="IBPartialClassDescription">
+ <string key="className">NSDocument</string>
+ <object class="NSMutableDictionary" key="actions">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <object class="NSArray" key="dict.sortedKeys">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <string>printDocument:</string>
+ <string>revertDocumentToSaved:</string>
+ <string>runPageLayout:</string>
+ <string>saveDocument:</string>
+ <string>saveDocumentAs:</string>
+ <string>saveDocumentTo:</string>
+ </object>
+ <object class="NSMutableArray" key="dict.values">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <string>id</string>
+ <string>id</string>
+ <string>id</string>
+ <string>id</string>
+ <string>id</string>
+ <string>id</string>
+ </object>
+ </object>
+ <object class="NSMutableDictionary" key="actionInfosByName">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <object class="NSArray" key="dict.sortedKeys">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <string>printDocument:</string>
+ <string>revertDocumentToSaved:</string>
+ <string>runPageLayout:</string>
+ <string>saveDocument:</string>
+ <string>saveDocumentAs:</string>
+ <string>saveDocumentTo:</string>
+ </object>
+ <object class="NSMutableArray" key="dict.values">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <object class="IBActionInfo">
+ <string key="name">printDocument:</string>
+ <string key="candidateClassName">id</string>
+ </object>
+ <object class="IBActionInfo">
+ <string key="name">revertDocumentToSaved:</string>
+ <string key="candidateClassName">id</string>
+ </object>
+ <object class="IBActionInfo">
+ <string key="name">runPageLayout:</string>
+ <string key="candidateClassName">id</string>
+ </object>
+ <object class="IBActionInfo">
+ <string key="name">saveDocument:</string>
+ <string key="candidateClassName">id</string>
+ </object>
+ <object class="IBActionInfo">
+ <string key="name">saveDocumentAs:</string>
+ <string key="candidateClassName">id</string>
+ </object>
+ <object class="IBActionInfo">
+ <string key="name">saveDocumentTo:</string>
+ <string key="candidateClassName">id</string>
+ </object>
+ </object>
+ </object>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBProjectSource</string>
+ <string key="minorKey">./Classes/NSDocument.h</string>
+ </object>
+ </object>
+ </object>
+ </object>
+ <int key="IBDocument.localizationMode">0</int>
+ <string key="IBDocument.TargetRuntimeIdentifier">IBCocoaFramework</string>
+ <object class="NSMutableDictionary" key="IBDocument.PluginDeclaredDevelopmentDependencies">
+ <string key="NS.key.0">com.apple.InterfaceBuilder.CocoaPlugin.InterfaceBuilder3</string>
+ <integer value="3000" key="NS.object.0"/>
+ </object>
+ <bool key="IBDocument.PluginDeclaredDependenciesTrackSystemTargetVersion">YES</bool>
+ <int key="IBDocument.defaultPropertyAccessControl">3</int>
+ <object class="NSMutableDictionary" key="IBDocument.LastKnownImageSizes">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <object class="NSArray" key="dict.sortedKeys">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <string>NSMenuCheckmark</string>
+ <string>NSMenuMixedState</string>
+ </object>
+ <object class="NSMutableArray" key="dict.values">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <string>{9, 8}</string>
+ <string>{7, 2}</string>
+ </object>
+ </object>
+ </data>
+</archive>
diff --git a/chromium/ash/shell/cocoa/nibs/RootWindow.xib b/chromium/ash/shell/cocoa/nibs/RootWindow.xib
new file mode 100644
index 00000000000..8ed21006b40
--- /dev/null
+++ b/chromium/ash/shell/cocoa/nibs/RootWindow.xib
@@ -0,0 +1,169 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<archive type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="7.10">
+ <data>
+ <int key="IBDocument.SystemTarget">1070</int>
+ <string key="IBDocument.SystemVersion">11C74</string>
+ <string key="IBDocument.InterfaceBuilderVersion">1900</string>
+ <string key="IBDocument.AppKitVersion">1138.23</string>
+ <string key="IBDocument.HIToolboxVersion">567.00</string>
+ <object class="NSMutableDictionary" key="IBDocument.PluginVersions">
+ <string key="NS.key.0">com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string key="NS.object.0">1900</string>
+ </object>
+ <object class="NSArray" key="IBDocument.IntegratedClassDependencies">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <string>NSWindowTemplate</string>
+ <string>NSView</string>
+ <string>NSCustomObject</string>
+ </object>
+ <object class="NSArray" key="IBDocument.PluginDependencies">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
+ </object>
+ <object class="NSMutableDictionary" key="IBDocument.Metadata">
+ <string key="NS.key.0">PluginDependencyRecalculationVersion</string>
+ <integer value="1" key="NS.object.0"/>
+ </object>
+ <object class="NSMutableArray" key="IBDocument.RootObjects" id="1000">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <object class="NSCustomObject" id="1001">
+ <string key="NSClassName">NSWindowController</string>
+ </object>
+ <object class="NSCustomObject" id="1003">
+ <string key="NSClassName">FirstResponder</string>
+ </object>
+ <object class="NSCustomObject" id="1004">
+ <string key="NSClassName">NSApplication</string>
+ </object>
+ <object class="NSWindowTemplate" id="1005">
+ <int key="NSWindowStyleMask">15</int>
+ <int key="NSWindowBacking">2</int>
+ <string key="NSWindowRect">{{196, 240}, {672, 666}}</string>
+ <int key="NSWTFlags">544735232</int>
+ <string key="NSWindowTitle">Aura Shell</string>
+ <string key="NSWindowClass">RootWindowMac</string>
+ <nil key="NSViewClass"/>
+ <nil key="NSUserInterfaceItemIdentifier"/>
+ <object class="NSView" key="NSWindowView" id="1006">
+ <reference key="NSNextResponder"/>
+ <int key="NSvFlags">256</int>
+ <string key="NSFrameSize">{672, 666}</string>
+ <reference key="NSSuperview"/>
+ <reference key="NSWindow"/>
+ <reference key="NSNextKeyView"/>
+ </object>
+ <string key="NSScreenRect">{{0, 0}, {1680, 1028}}</string>
+ <string key="NSMaxSize">{10000000000000, 10000000000000}</string>
+ <bool key="NSWindowIsRestorable">YES</bool>
+ </object>
+ </object>
+ <object class="IBObjectContainer" key="IBDocument.Objects">
+ <object class="NSMutableArray" key="connectionRecords">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <object class="IBConnectionRecord">
+ <object class="IBOutletConnection" key="connection">
+ <string key="label">window</string>
+ <reference key="source" ref="1001"/>
+ <reference key="destination" ref="1005"/>
+ </object>
+ <int key="connectionID">5</int>
+ </object>
+ </object>
+ <object class="IBMutableOrderedSet" key="objectRecords">
+ <object class="NSArray" key="orderedObjects">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <object class="IBObjectRecord">
+ <int key="objectID">0</int>
+ <object class="NSArray" key="object" id="1002">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ </object>
+ <reference key="children" ref="1000"/>
+ <nil key="parent"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">-2</int>
+ <reference key="object" ref="1001"/>
+ <reference key="parent" ref="1002"/>
+ <string key="objectName">File's Owner</string>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">-1</int>
+ <reference key="object" ref="1003"/>
+ <reference key="parent" ref="1002"/>
+ <string key="objectName">First Responder</string>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">-3</int>
+ <reference key="object" ref="1004"/>
+ <reference key="parent" ref="1002"/>
+ <string key="objectName">Application</string>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">1</int>
+ <reference key="object" ref="1005"/>
+ <object class="NSMutableArray" key="children">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <reference ref="1006"/>
+ </object>
+ <reference key="parent" ref="1002"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">2</int>
+ <reference key="object" ref="1006"/>
+ <object class="NSMutableArray" key="children">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ </object>
+ <reference key="parent" ref="1005"/>
+ </object>
+ </object>
+ </object>
+ <object class="NSMutableDictionary" key="flattenedProperties">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <object class="NSArray" key="dict.sortedKeys">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <string>-1.IBPluginDependency</string>
+ <string>-2.IBPluginDependency</string>
+ <string>-3.IBPluginDependency</string>
+ <string>1.IBPluginDependency</string>
+ <string>1.IBWindowTemplateEditedContentRect</string>
+ <string>1.NSWindowTemplate.visibleAtLaunch</string>
+ <string>2.CustomClassName</string>
+ <string>2.IBPluginDependency</string>
+ </object>
+ <object class="NSMutableArray" key="dict.values">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string>{{357, 418}, {480, 270}}</string>
+ <integer value="1"/>
+ <string>RootWindowView</string>
+ <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
+ </object>
+ </object>
+ <object class="NSMutableDictionary" key="unlocalizedProperties">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <reference key="dict.sortedKeys" ref="1002"/>
+ <reference key="dict.values" ref="1002"/>
+ </object>
+ <nil key="activeLocalization"/>
+ <object class="NSMutableDictionary" key="localizations">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <reference key="dict.sortedKeys" ref="1002"/>
+ <reference key="dict.values" ref="1002"/>
+ </object>
+ <nil key="sourceID"/>
+ <int key="maxID">5</int>
+ </object>
+ <object class="IBClassDescriber" key="IBDocument.Classes"/>
+ <int key="IBDocument.localizationMode">0</int>
+ <string key="IBDocument.TargetRuntimeIdentifier">IBCocoaFramework</string>
+ <object class="NSMutableDictionary" key="IBDocument.PluginDeclaredDevelopmentDependencies">
+ <string key="NS.key.0">com.apple.InterfaceBuilder.CocoaPlugin.InterfaceBuilder3</string>
+ <integer value="3000" key="NS.object.0"/>
+ </object>
+ <bool key="IBDocument.PluginDeclaredDependenciesTrackSystemTargetVersion">YES</bool>
+ <int key="IBDocument.defaultPropertyAccessControl">3</int>
+ </data>
+</archive>
diff --git a/chromium/ash/shell/content_client/DEPS b/chromium/ash/shell/content_client/DEPS
new file mode 100644
index 00000000000..60dbcf4960d
--- /dev/null
+++ b/chromium/ash/shell/content_client/DEPS
@@ -0,0 +1,3 @@
+include_rules = [
+ "+content",
+]
diff --git a/chromium/ash/shell/content_client/shell_browser_main_parts.cc b/chromium/ash/shell/content_client/shell_browser_main_parts.cc
new file mode 100644
index 00000000000..fae491f2acc
--- /dev/null
+++ b/chromium/ash/shell/content_client/shell_browser_main_parts.cc
@@ -0,0 +1,174 @@
+// Copyright (c) 2012 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.
+
+#include "ash/shell/content_client/shell_browser_main_parts.h"
+
+#include "ash/ash_switches.h"
+#include "ash/desktop_background/desktop_background_controller.h"
+#include "ash/shell.h"
+#include "ash/shell/shell_delegate_impl.h"
+#include "ash/shell/window_watcher.h"
+#include "ash/system/user/login_status.h"
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "base/i18n/icu_util.h"
+#include "base/message_loop/message_loop.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/threading/thread.h"
+#include "base/threading/thread_restrictions.h"
+#include "content/public/common/content_switches.h"
+#include "content/shell/shell_browser_context.h"
+#include "content/shell/shell_net_log.h"
+#include "net/base/net_module.h"
+#include "ui/aura/client/stacking_client.h"
+#include "ui/aura/env.h"
+#include "ui/aura/root_window.h"
+#include "ui/aura/window.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "ui/base/ui_base_paths.h"
+#include "ui/compositor/compositor.h"
+#include "ui/gfx/screen.h"
+#include "ui/message_center/message_center.h"
+#include "ui/views/focus/accelerator_handler.h"
+#include "ui/views/test/test_views_delegate.h"
+
+#if defined(USE_X11)
+#include "ui/base/touch/touch_factory_x11.h"
+#endif
+
+#if defined(OS_CHROMEOS)
+#include "chromeos/audio/cras_audio_handler.h"
+#include "chromeos/dbus/dbus_thread_manager.h"
+#endif
+
+namespace ash {
+namespace shell {
+void InitWindowTypeLauncher();
+
+namespace {
+
+class ShellViewsDelegate : public views::TestViewsDelegate {
+ public:
+ ShellViewsDelegate() {}
+ virtual ~ShellViewsDelegate() {}
+
+ // Overridden from views::TestViewsDelegate:
+ virtual views::NonClientFrameView* CreateDefaultNonClientFrameView(
+ views::Widget* widget) OVERRIDE {
+ return ash::Shell::GetInstance()->CreateDefaultNonClientFrameView(widget);
+ }
+ virtual bool UseTransparentWindows() const OVERRIDE {
+ // Ash uses transparent window frames.
+ return true;
+ }
+ virtual void OnBeforeWidgetInit(
+ views::Widget::InitParams* params,
+ views::internal::NativeWidgetDelegate* delegate) OVERRIDE {
+ if (params->native_widget)
+ return;
+
+ if (!params->parent && !params->context && params->top_level)
+ params->context = Shell::GetPrimaryRootWindow();
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ShellViewsDelegate);
+};
+
+} // namespace
+
+ShellBrowserMainParts::ShellBrowserMainParts(
+ const content::MainFunctionParams& parameters)
+ : BrowserMainParts(),
+ delegate_(NULL) {
+}
+
+ShellBrowserMainParts::~ShellBrowserMainParts() {
+}
+
+#if !defined(OS_MACOSX)
+void ShellBrowserMainParts::PreMainMessageLoopStart() {
+#if defined(USE_X11)
+ ui::TouchFactory::SetTouchDeviceListFromCommandLine();
+#endif
+}
+#endif
+
+void ShellBrowserMainParts::PostMainMessageLoopStart() {
+#if defined(OS_CHROMEOS)
+ chromeos::DBusThreadManager::Initialize();
+#endif
+}
+
+void ShellBrowserMainParts::PreMainMessageLoopRun() {
+ net_log_.reset(new content::ShellNetLog());
+ browser_context_.reset(new content::ShellBrowserContext(
+ false, net_log_.get()));
+
+ // A ViewsDelegate is required.
+ if (!views::ViewsDelegate::views_delegate)
+ views::ViewsDelegate::views_delegate = new ShellViewsDelegate;
+
+ delegate_ = new ash::shell::ShellDelegateImpl;
+ // The global message center state must be initialized absent
+ // g_browser_process.
+ message_center::MessageCenter::Initialize();
+
+#if defined(OS_CHROMEOS)
+ // Create CrasAudioHandler for testing since g_browser_process
+ // is absent.
+ chromeos::CrasAudioHandler::InitializeForTesting();
+#endif
+
+ ash::Shell::CreateInstance(delegate_);
+ ash::Shell::GetInstance()->set_browser_context(browser_context_.get());
+ ash::Shell::GetInstance()->CreateLauncher();
+ ash::Shell::GetInstance()->UpdateAfterLoginStatusChange(
+ user::LOGGED_IN_USER);
+
+ window_watcher_.reset(new ash::shell::WindowWatcher);
+ gfx::Screen* screen = Shell::GetInstance()->GetScreen();
+ screen->AddObserver(window_watcher_.get());
+ delegate_->SetWatcher(window_watcher_.get());
+
+ ash::shell::InitWindowTypeLauncher();
+
+ Shell::GetInstance()->desktop_background_controller()->SetDefaultWallpaper(
+ false /* is_guest */);
+
+ ash::Shell::GetPrimaryRootWindow()->ShowRootWindow();
+}
+
+void ShellBrowserMainParts::PostMainMessageLoopRun() {
+ gfx::Screen* screen = Shell::GetInstance()->GetScreen();
+ screen->RemoveObserver(window_watcher_.get());
+
+ window_watcher_.reset();
+ delegate_->SetWatcher(NULL);
+ delegate_ = NULL;
+ ash::Shell::DeleteInstance();
+ // The global message center state must be shutdown absent
+ // g_browser_process.
+ message_center::MessageCenter::Shutdown();
+
+#if defined(OS_CHROMEOS)
+ chromeos::CrasAudioHandler::Shutdown();
+#endif
+
+ aura::Env::DeleteInstance();
+
+ // The keyboard may have created a WebContents. The WebContents is destroyed
+ // with the UI, and it needs the BrowserContext to be alive during its
+ // destruction. So destroy all of the UI elements before destroying the
+ // browser context.
+ browser_context_.reset();
+}
+
+bool ShellBrowserMainParts::MainMessageLoopRun(int* result_code) {
+ base::MessageLoopForUI::current()->Run();
+ return true;
+}
+
+} // namespace shell
+} // namespace ash
diff --git a/chromium/ash/shell/content_client/shell_browser_main_parts.h b/chromium/ash/shell/content_client/shell_browser_main_parts.h
new file mode 100644
index 00000000000..768ee5e6c3c
--- /dev/null
+++ b/chromium/ash/shell/content_client/shell_browser_main_parts.h
@@ -0,0 +1,60 @@
+// Copyright (c) 2012 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 ASH_SHELL_CONTENT_CLIENT_EXAMPLES_BROWSER_MAIN_PARTS_H_
+#define ASH_SHELL_CONTENT_CLIENT_EXAMPLES_BROWSER_MAIN_PARTS_H_
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "content/public/browser/browser_main_parts.h"
+
+namespace base {
+class Thread;
+}
+
+namespace content {
+class ShellBrowserContext;
+struct MainFunctionParams;
+}
+
+namespace net {
+class NetLog;
+}
+
+namespace ash {
+namespace shell {
+
+class ShellDelegateImpl;
+class WindowWatcher;
+
+class ShellBrowserMainParts : public content::BrowserMainParts {
+ public:
+ explicit ShellBrowserMainParts(
+ const content::MainFunctionParams& parameters);
+ virtual ~ShellBrowserMainParts();
+
+ // Overridden from content::BrowserMainParts:
+ virtual void PreMainMessageLoopStart() OVERRIDE;
+ virtual void PostMainMessageLoopStart() OVERRIDE;
+ virtual void PreMainMessageLoopRun() OVERRIDE;
+ virtual bool MainMessageLoopRun(int* result_code) OVERRIDE;
+ virtual void PostMainMessageLoopRun() OVERRIDE;
+
+ content::ShellBrowserContext* browser_context() {
+ return browser_context_.get();
+ }
+
+ private:
+ scoped_ptr<net::NetLog> net_log_;
+ scoped_ptr<content::ShellBrowserContext> browser_context_;
+ scoped_ptr<ash::shell::WindowWatcher> window_watcher_;
+ ShellDelegateImpl* delegate_; // owned by Shell
+
+ DISALLOW_COPY_AND_ASSIGN(ShellBrowserMainParts);
+};
+
+} // namespace shell
+} // namespace ash
+
+#endif // ASH_SHELL_CONTENT_CLIENT_EXAMPLES_BROWSER_MAIN_PARTS_H_
diff --git a/chromium/ash/shell/content_client/shell_content_browser_client.cc b/chromium/ash/shell/content_client/shell_content_browser_client.cc
new file mode 100644
index 00000000000..93fc834e0b3
--- /dev/null
+++ b/chromium/ash/shell/content_client/shell_content_browser_client.cc
@@ -0,0 +1,40 @@
+// Copyright (c) 2012 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.
+
+#include "ash/shell/content_client/shell_content_browser_client.h"
+
+#include "ash/shell/content_client/shell_browser_main_parts.h"
+#include "content/shell/shell_browser_context.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+
+namespace ash {
+namespace shell {
+
+ShellContentBrowserClient::ShellContentBrowserClient()
+ : shell_browser_main_parts_(NULL) {
+}
+
+ShellContentBrowserClient::~ShellContentBrowserClient() {
+}
+
+content::BrowserMainParts* ShellContentBrowserClient::CreateBrowserMainParts(
+ const content::MainFunctionParams& parameters) {
+ shell_browser_main_parts_ = new ShellBrowserMainParts(parameters);
+ return shell_browser_main_parts_;
+}
+
+net::URLRequestContextGetter* ShellContentBrowserClient::CreateRequestContext(
+ content::BrowserContext* content_browser_context,
+ content::ProtocolHandlerMap* protocol_handlers) {
+ content::ShellBrowserContext* shell_context =
+ static_cast<content::ShellBrowserContext*>(content_browser_context);
+ return shell_context->CreateRequestContext(protocol_handlers);
+}
+
+content::ShellBrowserContext* ShellContentBrowserClient::browser_context() {
+ return shell_browser_main_parts_->browser_context();
+}
+
+} // namespace examples
+} // namespace views
diff --git a/chromium/ash/shell/content_client/shell_content_browser_client.h b/chromium/ash/shell/content_client/shell_content_browser_client.h
new file mode 100644
index 00000000000..d90909aa656
--- /dev/null
+++ b/chromium/ash/shell/content_client/shell_content_browser_client.h
@@ -0,0 +1,47 @@
+// Copyright (c) 2012 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 ASH_SHELL_CONTENT_CLIENT_SHELL_CONTENT_BROWSER_CLIENT_H_
+#define ASH_SHELL_CONTENT_CLIENT_SHELL_CONTENT_BROWSER_CLIENT_H_
+
+#include <string>
+
+#include "base/compiler_specific.h"
+#include "content/public/browser/content_browser_client.h"
+
+namespace content {
+class ShellBrowserContext;
+class ShellBrowserMainParts;
+class ShellResourceDispatcherHostDelegate;
+}
+
+namespace ash {
+namespace shell {
+
+class ShellBrowserMainParts;
+
+class ShellContentBrowserClient : public content::ContentBrowserClient {
+ public:
+ ShellContentBrowserClient();
+ virtual ~ShellContentBrowserClient();
+
+ // Overridden from content::ContentBrowserClient:
+ virtual content::BrowserMainParts* CreateBrowserMainParts(
+ const content::MainFunctionParams& parameters) OVERRIDE;
+ virtual net::URLRequestContextGetter* CreateRequestContext(
+ content::BrowserContext* browser_context,
+ content::ProtocolHandlerMap* protocol_handlers) OVERRIDE;
+
+ content::ShellBrowserContext* browser_context();
+
+ private:
+ ShellBrowserMainParts* shell_browser_main_parts_;
+
+ DISALLOW_COPY_AND_ASSIGN(ShellContentBrowserClient);
+};
+
+} // namespace shell
+} // namespace ash
+
+#endif // ASH_SHELL_CONTENT_CLIENT_SHELL_CONTENT_BROWSER_CLIENT_H_
diff --git a/chromium/ash/shell/content_client/shell_main_delegate.cc b/chromium/ash/shell/content_client/shell_main_delegate.cc
new file mode 100644
index 00000000000..014fae1a2b8
--- /dev/null
+++ b/chromium/ash/shell/content_client/shell_main_delegate.cc
@@ -0,0 +1,45 @@
+// Copyright (c) 2012 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.
+
+#include "ash/shell/content_client/shell_main_delegate.h"
+
+#include "ash/shell/content_client/shell_content_browser_client.h"
+#include "base/command_line.h"
+#include "content/public/common/content_switches.h"
+#include "ui/base/resource/resource_bundle.h"
+
+namespace ash {
+namespace shell {
+
+ShellMainDelegate::ShellMainDelegate() {
+}
+
+ShellMainDelegate::~ShellMainDelegate() {
+}
+
+bool ShellMainDelegate::BasicStartupComplete(int* exit_code) {
+ const CommandLine& command_line = *CommandLine::ForCurrentProcess();
+ std::string process_type =
+ command_line.GetSwitchValueASCII(switches::kProcessType);
+
+ content::SetContentClient(&content_client_);
+
+ return false;
+}
+
+void ShellMainDelegate::PreSandboxStartup() {
+ InitializeResourceBundle();
+}
+
+content::ContentBrowserClient* ShellMainDelegate::CreateContentBrowserClient() {
+ browser_client_.reset(new ShellContentBrowserClient);
+ return browser_client_.get();
+}
+
+void ShellMainDelegate::InitializeResourceBundle() {
+ ui::ResourceBundle::InitSharedInstanceWithLocale("en-US", NULL);
+}
+
+} // namespace shell
+} // namespace ash
diff --git a/chromium/ash/shell/content_client/shell_main_delegate.h b/chromium/ash/shell/content_client/shell_main_delegate.h
new file mode 100644
index 00000000000..2ac00514546
--- /dev/null
+++ b/chromium/ash/shell/content_client/shell_main_delegate.h
@@ -0,0 +1,45 @@
+// Copyright (c) 2012 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 ASH_SHELL_CONTENT_CLIENT_SHELL_MAIN_DELEGATE_H_
+#define ASH_SHELL_CONTENT_CLIENT_SHELL_MAIN_DELEGATE_H_
+
+#include "base/compiler_specific.h"
+#include "base/memory/scoped_ptr.h"
+#include "content/public/app/content_main_delegate.h"
+#include "content/shell/common/shell_content_client.h"
+
+namespace content {
+class ShellContentRendererClient;
+class ShellContentPluginClient;
+class ShellContentUtilityClient;
+}
+
+namespace ash {
+namespace shell {
+
+class ShellContentBrowserClient;
+
+class ShellMainDelegate : public content::ContentMainDelegate {
+ public:
+ ShellMainDelegate();
+ virtual ~ShellMainDelegate();
+
+ virtual bool BasicStartupComplete(int* exit_code) OVERRIDE;
+ virtual void PreSandboxStartup() OVERRIDE;
+ virtual content::ContentBrowserClient* CreateContentBrowserClient() OVERRIDE;
+
+ private:
+ void InitializeResourceBundle();
+
+ scoped_ptr<ShellContentBrowserClient> browser_client_;
+ content::ShellContentClient content_client_;
+
+ DISALLOW_COPY_AND_ASSIGN(ShellMainDelegate);
+};
+
+} // namespace shell
+} // namespace ash
+
+#endif // ASH_SHELL_CONTENT_CLIENT_SHELL_MAIN_DELEGATE_H_
diff --git a/chromium/ash/shell/context_menu.cc b/chromium/ash/shell/context_menu.cc
new file mode 100644
index 00000000000..e1a62d1f911
--- /dev/null
+++ b/chromium/ash/shell/context_menu.cc
@@ -0,0 +1,70 @@
+// Copyright (c) 2012 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.
+
+#include "ash/shell/context_menu.h"
+
+#include "ash/launcher/launcher.h"
+#include "ash/root_window_controller.h"
+#include "ash/shelf/shelf_types.h"
+#include "ash/shell.h"
+#include "grit/ash_strings.h"
+#include "ui/base/l10n/l10n_util.h"
+
+namespace ash {
+namespace shell {
+
+ContextMenu::ContextMenu(aura::RootWindow* root)
+ : ui::SimpleMenuModel(NULL),
+ root_window_(root),
+ alignment_menu_(root) {
+ DCHECK(root_window_);
+ set_delegate(this);
+ AddCheckItemWithStringId(MENU_AUTO_HIDE,
+ IDS_ASH_SHELF_CONTEXT_MENU_AUTO_HIDE);
+ AddSubMenuWithStringId(MENU_ALIGNMENT_MENU,
+ IDS_ASH_SHELF_CONTEXT_MENU_POSITION,
+ &alignment_menu_);
+}
+
+ContextMenu::~ContextMenu() {
+}
+
+bool ContextMenu::IsCommandIdChecked(int command_id) const {
+ switch (command_id) {
+ case MENU_AUTO_HIDE:
+ return Shell::GetInstance()->GetShelfAutoHideBehavior(root_window_) ==
+ ash::SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS;
+ default:
+ return false;
+ }
+}
+
+bool ContextMenu::IsCommandIdEnabled(int command_id) const {
+ return true;
+}
+
+bool ContextMenu::GetAcceleratorForCommandId(
+ int command_id,
+ ui::Accelerator* accelerator) {
+ return false;
+}
+
+void ContextMenu::ExecuteCommand(int command_id, int event_flags) {
+ Shell* shell = Shell::GetInstance();
+ switch (static_cast<MenuItem>(command_id)) {
+ case MENU_AUTO_HIDE:
+ shell->SetShelfAutoHideBehavior(
+ shell->GetShelfAutoHideBehavior(root_window_) ==
+ SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS ?
+ SHELF_AUTO_HIDE_BEHAVIOR_NEVER :
+ SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS,
+ root_window_);
+ break;
+ case MENU_ALIGNMENT_MENU:
+ break;
+ }
+}
+
+} // namespace shell
+} // namespace ash
diff --git a/chromium/ash/shell/context_menu.h b/chromium/ash/shell/context_menu.h
new file mode 100644
index 00000000000..34d20428ae2
--- /dev/null
+++ b/chromium/ash/shell/context_menu.h
@@ -0,0 +1,51 @@
+// Copyright (c) 2012 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 ASH_SHELL_CONTEXT_MENU_H_
+#define ASH_SHELL_CONTEXT_MENU_H_
+
+#include "ash/launcher/launcher_alignment_menu.h"
+#include "ash/shelf/shelf_types.h"
+#include "base/basictypes.h"
+#include "ui/base/models/simple_menu_model.h"
+
+namespace aura {
+class RootWindow;
+}
+
+namespace ash {
+namespace shell {
+
+// Context menu for the ash_shell.
+class ContextMenu : public ui::SimpleMenuModel,
+ public ui::SimpleMenuModel::Delegate {
+ public:
+ explicit ContextMenu(aura::RootWindow* root);
+ virtual ~ContextMenu();
+
+ // ui::SimpleMenuModel::Delegate overrides:
+ virtual bool IsCommandIdChecked(int command_id) const OVERRIDE;
+ virtual bool IsCommandIdEnabled(int command_id) const OVERRIDE;
+ virtual bool GetAcceleratorForCommandId(
+ int command_id,
+ ui::Accelerator* accelerator) OVERRIDE;
+ virtual void ExecuteCommand(int command_id, int event_flags) OVERRIDE;
+
+ private:
+ enum MenuItem {
+ MENU_AUTO_HIDE,
+ MENU_ALIGNMENT_MENU,
+ };
+
+ aura::RootWindow* root_window_;
+
+ LauncherAlignmentMenu alignment_menu_;
+
+ DISALLOW_COPY_AND_ASSIGN(ContextMenu);
+};
+
+} // namespace shell
+} // namespace ash
+
+#endif // ASH_SHELL_CONTEXT_MENU_H_
diff --git a/chromium/ash/shell/example_factory.h b/chromium/ash/shell/example_factory.h
new file mode 100644
index 00000000000..7510be14cb5
--- /dev/null
+++ b/chromium/ash/shell/example_factory.h
@@ -0,0 +1,31 @@
+// Copyright (c) 2012 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 ASH_SHELL_EXAMPLE_FACTORY_H_
+#define ASH_SHELL_EXAMPLE_FACTORY_H_
+
+namespace app_list {
+class AppListViewDelegate;
+}
+
+namespace views {
+class View;
+}
+
+namespace ash {
+namespace shell {
+
+void CreatePointyBubble(views::View* anchor_view);
+
+void CreateLockScreen();
+
+// Creates a window showing samples of commonly used widgets.
+void CreateWidgetsWindow();
+
+app_list::AppListViewDelegate* CreateAppListViewDelegate();
+
+} // namespace shell
+} // namespace ash
+
+#endif // ASH_SHELL_EXAMPLE_FACTORY_H_
diff --git a/chromium/ash/shell/launcher_delegate_impl.cc b/chromium/ash/shell/launcher_delegate_impl.cc
new file mode 100644
index 00000000000..51f6171f8c9
--- /dev/null
+++ b/chromium/ash/shell/launcher_delegate_impl.cc
@@ -0,0 +1,87 @@
+// Copyright (c) 2012 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.
+
+#include "ash/shell/launcher_delegate_impl.h"
+
+#include "ash/launcher/launcher_util.h"
+#include "ash/shell/toplevel_window.h"
+#include "ash/shell/window_watcher.h"
+#include "ash/wm/window_util.h"
+#include "grit/ash_resources.h"
+#include "ui/aura/window.h"
+
+namespace ash {
+namespace shell {
+
+LauncherDelegateImpl::LauncherDelegateImpl(WindowWatcher* watcher)
+ : watcher_(watcher) {
+}
+
+LauncherDelegateImpl::~LauncherDelegateImpl() {
+}
+
+void LauncherDelegateImpl::ItemSelected(const ash::LauncherItem& item,
+ const ui::Event& event) {
+ aura::Window* window = watcher_->GetWindowByID(item.id);
+ if (window->type() == aura::client::WINDOW_TYPE_PANEL)
+ ash::wm::MoveWindowToEventRoot(window, event);
+ window->Show();
+ ash::wm::ActivateWindow(window);
+}
+
+base::string16 LauncherDelegateImpl::GetTitle(const ash::LauncherItem& item) {
+ return watcher_->GetWindowByID(item.id)->title();
+}
+
+ui::MenuModel* LauncherDelegateImpl::CreateContextMenu(
+ const ash::LauncherItem& item,
+ aura::RootWindow* root_window) {
+ return NULL;
+}
+
+ash::LauncherMenuModel* LauncherDelegateImpl::CreateApplicationMenu(
+ const ash::LauncherItem& item,
+ int event_flags) {
+ return NULL;
+}
+
+ash::LauncherID LauncherDelegateImpl::GetIDByWindow(aura::Window* window) {
+ return watcher_ ? watcher_->GetIDByWindow(window) : 0;
+}
+
+bool LauncherDelegateImpl::IsDraggable(const ash::LauncherItem& item) {
+ return true;
+}
+
+bool LauncherDelegateImpl::ShouldShowTooltip(const ash::LauncherItem& item) {
+ return true;
+}
+
+void LauncherDelegateImpl::OnLauncherCreated(Launcher* launcher) {
+}
+
+void LauncherDelegateImpl::OnLauncherDestroyed(Launcher* launcher) {
+}
+
+bool LauncherDelegateImpl::IsPerAppLauncher() {
+ return false;
+}
+
+LauncherID LauncherDelegateImpl::GetLauncherIDForAppID(
+ const std::string& app_id) {
+ return 0;
+}
+
+void LauncherDelegateImpl::PinAppWithID(const std::string& app_id) {
+}
+
+bool LauncherDelegateImpl::IsAppPinned(const std::string& app_id) {
+ return false;
+}
+
+void LauncherDelegateImpl::UnpinAppsWithID(const std::string& app_id) {
+}
+
+} // namespace shell
+} // namespace ash
diff --git a/chromium/ash/shell/launcher_delegate_impl.h b/chromium/ash/shell/launcher_delegate_impl.h
new file mode 100644
index 00000000000..47880293e46
--- /dev/null
+++ b/chromium/ash/shell/launcher_delegate_impl.h
@@ -0,0 +1,58 @@
+// Copyright (c) 2012 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 ASH_SHELL_LAUNCHER_DELEGATE_IMPL_H_
+#define ASH_SHELL_LAUNCHER_DELEGATE_IMPL_H_
+
+#include "ash/launcher/launcher_delegate.h"
+#include "base/compiler_specific.h"
+
+namespace aura {
+class Window;
+}
+
+namespace ash {
+namespace shell {
+
+class WindowWatcher;
+
+class LauncherDelegateImpl : public ash::LauncherDelegate {
+ public:
+ explicit LauncherDelegateImpl(WindowWatcher* watcher);
+ virtual ~LauncherDelegateImpl();
+
+ void set_watcher(WindowWatcher* watcher) { watcher_ = watcher; }
+
+ // LauncherDelegate overrides:
+ virtual void ItemSelected(const ash::LauncherItem& item,
+ const ui::Event& event) OVERRIDE;
+ virtual base::string16 GetTitle(const ash::LauncherItem& item) OVERRIDE;
+ virtual ui::MenuModel* CreateContextMenu(
+ const ash::LauncherItem& item,
+ aura::RootWindow* root) OVERRIDE;
+ virtual ash::LauncherMenuModel* CreateApplicationMenu(
+ const ash::LauncherItem&,
+ int event_flags) OVERRIDE;
+ virtual ash::LauncherID GetIDByWindow(aura::Window* window) OVERRIDE;
+ virtual bool IsDraggable(const ash::LauncherItem& item) OVERRIDE;
+ virtual bool ShouldShowTooltip(const LauncherItem& item) OVERRIDE;
+ virtual void OnLauncherCreated(Launcher* launcher) OVERRIDE;
+ virtual void OnLauncherDestroyed(Launcher* launcher) OVERRIDE;
+ virtual bool IsPerAppLauncher() OVERRIDE;
+ virtual LauncherID GetLauncherIDForAppID(const std::string& app_id) OVERRIDE;
+ virtual void PinAppWithID(const std::string& app_id) OVERRIDE;
+ virtual bool IsAppPinned(const std::string& app_id) OVERRIDE;
+ virtual void UnpinAppsWithID(const std::string& app_id) OVERRIDE;
+
+ private:
+ // Used to update Launcher. Owned by main.
+ WindowWatcher* watcher_;
+
+ DISALLOW_COPY_AND_ASSIGN(LauncherDelegateImpl);
+};
+
+} // namespace shell
+} // namespace ash
+
+#endif // ASH_SHELL_LAUNCHER_DELEGATE_IMPL_H_
diff --git a/chromium/ash/shell/lock_view.cc b/chromium/ash/shell/lock_view.cc
new file mode 100644
index 00000000000..aee013328aa
--- /dev/null
+++ b/chromium/ash/shell/lock_view.cc
@@ -0,0 +1,109 @@
+// Copyright (c) 2012 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.
+
+#include "ash/session_state_delegate.h"
+#include "ash/shell.h"
+#include "ash/shell_window_ids.h"
+#include "ash/shell/example_factory.h"
+#include "base/strings/utf_string_conversions.h"
+#include "ui/aura/root_window.h"
+#include "ui/aura/window.h"
+#include "ui/gfx/canvas.h"
+#include "ui/gfx/font.h"
+#include "ui/views/controls/button/label_button.h"
+#include "ui/views/corewm/tooltip_controller.h"
+#include "ui/views/widget/widget.h"
+#include "ui/views/widget/widget_delegate.h"
+
+using ash::Shell;
+
+namespace ash {
+namespace shell {
+
+class LockView : public views::WidgetDelegateView,
+ public views::ButtonListener {
+ public:
+ LockView()
+ : unlock_button_(new views::LabelButton(this, ASCIIToUTF16("Unlock"))) {
+ unlock_button_->SetStyle(views::Button::STYLE_NATIVE_TEXTBUTTON);
+ AddChildView(unlock_button_);
+ unlock_button_->set_focusable(true);
+ }
+ virtual ~LockView() {}
+
+ // Overridden from views::View:
+ virtual gfx::Size GetPreferredSize() OVERRIDE {
+ return gfx::Size(500, 400);
+ }
+
+ private:
+ // Overridden from views::View:
+ virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE {
+ canvas->FillRect(GetLocalBounds(), SK_ColorYELLOW);
+ base::string16 text = ASCIIToUTF16("LOCKED!");
+ int string_width = font_.GetStringWidth(text);
+ canvas->DrawStringInt(text, font_, SK_ColorRED, (width() - string_width)/ 2,
+ (height() - font_.GetHeight()) / 2,
+ string_width, font_.GetHeight());
+ }
+ virtual void Layout() OVERRIDE {
+ gfx::Rect bounds = GetLocalBounds();
+ gfx::Size ps = unlock_button_->GetPreferredSize();
+ bounds.set_y(bounds.bottom() - ps.height() - 5);
+ bounds.set_x((bounds.width() - ps.width()) / 2);
+ bounds.set_size(ps);
+ unlock_button_->SetBoundsRect(bounds);
+ }
+ virtual void ViewHierarchyChanged(
+ const ViewHierarchyChangedDetails& details) OVERRIDE {
+ if (details.is_add && details.child == this)
+ unlock_button_->RequestFocus();
+ }
+
+ // Overridden from views::WidgetDelegateView:
+ virtual void WindowClosing() OVERRIDE {
+ Shell::GetInstance()->session_state_delegate()->UnlockScreen();
+ }
+
+ // Overridden from views::ButtonListener:
+ virtual void ButtonPressed(views::Button* sender,
+ const ui::Event& event) OVERRIDE {
+ DCHECK(sender == unlock_button_);
+ GetWidget()->Close();
+ }
+
+ gfx::Font font_;
+ views::LabelButton* unlock_button_;
+
+ DISALLOW_COPY_AND_ASSIGN(LockView);
+};
+
+void CreateLockScreen() {
+ LockView* lock_view = new LockView;
+ views::Widget* widget = new views::Widget;
+ views::Widget::InitParams params(
+ views::Widget::InitParams::TYPE_WINDOW_FRAMELESS);
+ gfx::Size ps = lock_view->GetPreferredSize();
+
+ gfx::Size root_window_size = Shell::GetPrimaryRootWindow()->bounds().size();
+ params.bounds = gfx::Rect((root_window_size.width() - ps.width()) / 2,
+ (root_window_size.height() - ps.height()) / 2,
+ ps.width(), ps.height());
+ params.delegate = lock_view;
+ params.parent = Shell::GetContainer(
+ Shell::GetPrimaryRootWindow(),
+ internal::kShellWindowId_LockScreenContainer);
+ widget->Init(params);
+ widget->SetContentsView(lock_view);
+ widget->Show();
+ widget->GetNativeView()->SetName("LockView");
+ widget->GetNativeView()->Focus();
+
+ // TODO: it shouldn't be necessary to invoke UpdateTooltip() here.
+ Shell::GetInstance()->tooltip_controller()->UpdateTooltip(
+ widget->GetNativeView());
+}
+
+} // namespace shell
+} // namespace ash
diff --git a/chromium/ash/shell/panel_window.cc b/chromium/ash/shell/panel_window.cc
new file mode 100644
index 00000000000..886d0c1149d
--- /dev/null
+++ b/chromium/ash/shell/panel_window.cc
@@ -0,0 +1,89 @@
+// Copyright (c) 2012 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.
+
+#include "ash/shell/panel_window.h"
+
+#include "ash/screen_ash.h"
+#include "ash/shell.h"
+#include "ash/wm/panels/panel_frame_view.h"
+#include "base/strings/utf_string_conversions.h"
+#include "ui/aura/root_window.h"
+#include "ui/aura/window.h"
+#include "ui/gfx/canvas.h"
+#include "ui/views/widget/widget.h"
+
+namespace {
+const int kMinWidth = 100;
+const int kMinHeight = 100;
+const int kDefaultWidth = 200;
+const int kDefaultHeight = 300;
+}
+
+namespace ash {
+
+// static
+views::Widget* PanelWindow::CreatePanelWindow(const gfx::Rect& rect) {
+ PanelWindow* panel_window = new PanelWindow("Example Panel Window");
+ panel_window->params().bounds = rect;
+ panel_window->params().context = Shell::GetPrimaryRootWindow();
+ return panel_window->CreateWidget();
+}
+
+PanelWindow::PanelWindow(const std::string& name)
+ : name_(name),
+ params_(views::Widget::InitParams::TYPE_PANEL) {
+ params_.delegate = this;
+}
+
+PanelWindow::~PanelWindow() {
+}
+
+views::Widget* PanelWindow::CreateWidget() {
+ views::Widget* widget = new views::Widget;
+
+ if (params().bounds.width() == 0)
+ params().bounds.set_width(kDefaultWidth);
+ if (params().bounds.height() == 0)
+ params().bounds.set_height(kDefaultHeight);
+ params().bounds = ScreenAsh::ConvertRectToScreen(
+ Shell::GetActiveRootWindow(),
+ params().bounds);
+
+ widget->Init(params());
+ widget->GetNativeView()->SetName(name_);
+ widget->Show();
+
+ return widget;
+}
+
+gfx::Size PanelWindow::GetPreferredSize() {
+ return gfx::Size(kMinWidth, kMinHeight);
+}
+
+void PanelWindow::OnPaint(gfx::Canvas* canvas) {
+ canvas->FillRect(GetLocalBounds(), SK_ColorGREEN);
+}
+
+base::string16 PanelWindow::GetWindowTitle() const {
+ return ASCIIToUTF16(name_);
+}
+
+views::View* PanelWindow::GetContentsView() {
+ return this;
+}
+
+bool PanelWindow::CanResize() const {
+ return true;
+}
+
+bool PanelWindow::CanMaximize() const {
+ return false;
+}
+
+views::NonClientFrameView* PanelWindow::CreateNonClientFrameView(
+ views::Widget* widget) {
+ return new PanelFrameView(widget, PanelFrameView::FRAME_NONE);
+}
+
+} // namespace ash
diff --git a/chromium/ash/shell/panel_window.h b/chromium/ash/shell/panel_window.h
new file mode 100644
index 00000000000..eefe5e3f4aa
--- /dev/null
+++ b/chromium/ash/shell/panel_window.h
@@ -0,0 +1,55 @@
+// Copyright (c) 2012 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 ASH_SHELL_PANEL_WINDOW_H_
+#define ASH_SHELL_PANEL_WINDOW_H_
+
+#include "base/basictypes.h"
+#include "ui/aura/aura_export.h"
+#include "ui/views/widget/widget.h"
+#include "ui/views/widget/widget_delegate.h"
+
+namespace ash {
+
+class PanelFrameView;
+
+// Example Class for panel windows (Widget::InitParams::TYPE_PANEL).
+// Instances of PanelWindow will get added to the PanelContainer top level
+// window which manages the panel layout through PanelLayoutManager.
+class PanelWindow : public views::WidgetDelegateView {
+ public:
+ explicit PanelWindow(const std::string& name);
+ virtual ~PanelWindow();
+
+ // Creates the widget for the panel window using |params_|.
+ views::Widget* CreateWidget();
+
+ const std::string& name() { return name_; }
+ views::Widget::InitParams& params() { return params_; }
+
+ // Creates a panel window and returns the associated widget.
+ static views::Widget* CreatePanelWindow(const gfx::Rect& rect);
+
+ private:
+ // Overridden from views::View:
+ virtual gfx::Size GetPreferredSize() OVERRIDE;
+ virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE;
+
+ // Overridden from views::WidgetDelegate:
+ virtual base::string16 GetWindowTitle() const OVERRIDE;
+ virtual View* GetContentsView() OVERRIDE;
+ virtual bool CanResize() const OVERRIDE;
+ virtual bool CanMaximize() const OVERRIDE;
+ virtual views::NonClientFrameView* CreateNonClientFrameView(
+ views::Widget* widget) OVERRIDE;
+
+ std::string name_;
+ views::Widget::InitParams params_;
+
+ DISALLOW_COPY_AND_ASSIGN(PanelWindow);
+};
+
+} // namespace ash
+
+#endif // ASH_SHELL_PANEL_WINDOW_H_
diff --git a/chromium/ash/shell/shell_delegate_impl.cc b/chromium/ash/shell/shell_delegate_impl.cc
new file mode 100644
index 00000000000..471f5acbb50
--- /dev/null
+++ b/chromium/ash/shell/shell_delegate_impl.cc
@@ -0,0 +1,225 @@
+// Copyright (c) 2012 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.
+
+#include "ash/shell/shell_delegate_impl.h"
+
+#include <limits>
+
+#include "ash/caps_lock_delegate_stub.h"
+#include "ash/host/root_window_host_factory.h"
+#include "ash/keyboard_controller_proxy_stub.h"
+#include "ash/session_state_delegate.h"
+#include "ash/session_state_delegate_stub.h"
+#include "ash/shell/context_menu.h"
+#include "ash/shell/example_factory.h"
+#include "ash/shell/launcher_delegate_impl.h"
+#include "ash/shell/toplevel_window.h"
+#include "ash/shell_window_ids.h"
+#include "ash/wm/window_util.h"
+#include "base/message_loop/message_loop.h"
+#include "ui/aura/window.h"
+#include "ui/views/corewm/input_method_event_filter.h"
+
+namespace ash {
+namespace shell {
+
+ShellDelegateImpl::ShellDelegateImpl()
+ : watcher_(NULL),
+ launcher_delegate_(NULL),
+ spoken_feedback_enabled_(false),
+ high_contrast_enabled_(false),
+ screen_magnifier_enabled_(false),
+ screen_magnifier_type_(kDefaultMagnifierType),
+ large_cursor_enabled_(false) {
+}
+
+ShellDelegateImpl::~ShellDelegateImpl() {
+}
+
+void ShellDelegateImpl::SetWatcher(WindowWatcher* watcher) {
+ watcher_ = watcher;
+ if (launcher_delegate_)
+ launcher_delegate_->set_watcher(watcher);
+}
+
+bool ShellDelegateImpl::IsFirstRunAfterBoot() const {
+ return false;
+}
+
+bool ShellDelegateImpl::IsMultiProfilesEnabled() const {
+ return false;
+}
+
+bool ShellDelegateImpl::IsRunningInForcedAppMode() const {
+ return false;
+}
+
+void ShellDelegateImpl::PreInit() {
+}
+
+void ShellDelegateImpl::Shutdown() {
+}
+
+void ShellDelegateImpl::Exit() {
+ base::MessageLoopForUI::current()->Quit();
+}
+
+void ShellDelegateImpl::NewTab() {
+}
+
+void ShellDelegateImpl::NewWindow(bool incognito) {
+ ash::shell::ToplevelWindow::CreateParams create_params;
+ create_params.can_resize = true;
+ create_params.can_maximize = true;
+ ash::shell::ToplevelWindow::CreateToplevelWindow(create_params);
+}
+
+void ShellDelegateImpl::ToggleFullscreen() {
+ ToggleMaximized();
+}
+
+void ShellDelegateImpl::ToggleMaximized() {
+ aura::Window* window = ash::wm::GetActiveWindow();
+ if (window)
+ ash::wm::ToggleMaximizedWindow(window);
+}
+
+void ShellDelegateImpl::OpenFileManager(bool as_dialog) {
+}
+
+void ShellDelegateImpl::OpenCrosh() {
+}
+
+void ShellDelegateImpl::RestoreTab() {
+}
+
+void ShellDelegateImpl::ShowKeyboardOverlay() {
+}
+
+keyboard::KeyboardControllerProxy*
+ ShellDelegateImpl::CreateKeyboardControllerProxy() {
+ return new KeyboardControllerProxyStub();
+}
+
+void ShellDelegateImpl::ShowTaskManager() {
+}
+
+content::BrowserContext* ShellDelegateImpl::GetCurrentBrowserContext() {
+ return Shell::GetInstance()->browser_context();
+}
+
+void ShellDelegateImpl::ToggleSpokenFeedback(
+ AccessibilityNotificationVisibility notify) {
+ spoken_feedback_enabled_ = !spoken_feedback_enabled_;
+}
+
+bool ShellDelegateImpl::IsSpokenFeedbackEnabled() const {
+ return spoken_feedback_enabled_;
+}
+
+void ShellDelegateImpl::ToggleHighContrast() {
+ high_contrast_enabled_ = !high_contrast_enabled_;
+}
+
+bool ShellDelegateImpl::IsHighContrastEnabled() const {
+ return high_contrast_enabled_;
+}
+
+void ShellDelegateImpl::SetMagnifierEnabled(bool enabled) {
+ screen_magnifier_enabled_ = enabled;
+}
+
+void ShellDelegateImpl::SetMagnifierType(MagnifierType type) {
+ screen_magnifier_type_ = type;
+}
+
+bool ShellDelegateImpl::IsMagnifierEnabled() const {
+ return screen_magnifier_enabled_;
+}
+
+MagnifierType ShellDelegateImpl::GetMagnifierType() const {
+ return screen_magnifier_type_;
+}
+
+void ShellDelegateImpl::SetLargeCursorEnabled(bool enabled) {
+ large_cursor_enabled_ = enabled;
+}
+
+bool ShellDelegateImpl::IsLargeCursorEnabled() const {
+ return large_cursor_enabled_;
+}
+
+bool ShellDelegateImpl::ShouldAlwaysShowAccessibilityMenu() const {
+ return false;
+}
+
+void ShellDelegateImpl::SilenceSpokenFeedback() const {
+}
+
+app_list::AppListViewDelegate* ShellDelegateImpl::CreateAppListViewDelegate() {
+ return ash::shell::CreateAppListViewDelegate();
+}
+
+ash::LauncherDelegate* ShellDelegateImpl::CreateLauncherDelegate(
+ ash::LauncherModel* model) {
+ launcher_delegate_ = new LauncherDelegateImpl(watcher_);
+ return launcher_delegate_;
+}
+
+ash::SystemTrayDelegate* ShellDelegateImpl::CreateSystemTrayDelegate() {
+ return NULL;
+}
+
+ash::UserWallpaperDelegate* ShellDelegateImpl::CreateUserWallpaperDelegate() {
+ return NULL;
+}
+
+ash::CapsLockDelegate* ShellDelegateImpl::CreateCapsLockDelegate() {
+ return new CapsLockDelegateStub;
+}
+
+ash::SessionStateDelegate* ShellDelegateImpl::CreateSessionStateDelegate() {
+ return new SessionStateDelegateStub;
+}
+
+aura::client::UserActionClient* ShellDelegateImpl::CreateUserActionClient() {
+ return NULL;
+}
+
+void ShellDelegateImpl::OpenFeedbackPage() {
+}
+
+void ShellDelegateImpl::RecordUserMetricsAction(UserMetricsAction action) {
+}
+
+void ShellDelegateImpl::HandleMediaNextTrack() {
+}
+
+void ShellDelegateImpl::HandleMediaPlayPause() {
+}
+
+void ShellDelegateImpl::HandleMediaPrevTrack() {
+}
+
+void ShellDelegateImpl::SaveScreenMagnifierScale(double scale) {
+}
+
+double ShellDelegateImpl::GetSavedScreenMagnifierScale() {
+ return std::numeric_limits<double>::min();
+}
+
+ui::MenuModel* ShellDelegateImpl::CreateContextMenu(aura::RootWindow* root) {
+ return new ContextMenu(root);
+}
+
+RootWindowHostFactory* ShellDelegateImpl::CreateRootWindowHostFactory() {
+ return RootWindowHostFactory::Create();
+}
+
+base::string16 ShellDelegateImpl::GetProductName() const {
+ return base::string16();
+}
+
+} // namespace shell
+} // namespace ash
diff --git a/chromium/ash/shell/shell_delegate_impl.h b/chromium/ash/shell/shell_delegate_impl.h
new file mode 100644
index 00000000000..7a4e5d6f41d
--- /dev/null
+++ b/chromium/ash/shell/shell_delegate_impl.h
@@ -0,0 +1,99 @@
+// Copyright (c) 2012 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 ASH_SHELL_SHELL_DELEGATE_IMPL_H_
+#define ASH_SHELL_SHELL_DELEGATE_IMPL_H_
+
+#include <string>
+
+#include "ash/shell_delegate.h"
+#include "base/compiler_specific.h"
+
+namespace keyboard {
+class KeyboardControllerProxy;
+}
+
+namespace ash {
+namespace shell {
+
+class LauncherDelegateImpl;
+class WindowWatcher;
+
+class ShellDelegateImpl : public ash::ShellDelegate {
+ public:
+ ShellDelegateImpl();
+ virtual ~ShellDelegateImpl();
+
+ void SetWatcher(WindowWatcher* watcher);
+
+ virtual bool IsFirstRunAfterBoot() const OVERRIDE;
+ virtual bool IsMultiProfilesEnabled() const OVERRIDE;
+ virtual bool IsRunningInForcedAppMode() const OVERRIDE;
+ virtual void PreInit() OVERRIDE;
+ virtual void Shutdown() OVERRIDE;
+ virtual void Exit() OVERRIDE;
+ virtual void NewTab() OVERRIDE;
+ virtual void NewWindow(bool incognito) OVERRIDE;
+ virtual void ToggleFullscreen() OVERRIDE;
+ virtual void ToggleMaximized() OVERRIDE;
+ virtual void OpenFileManager(bool as_dialog) OVERRIDE;
+ virtual void OpenCrosh() OVERRIDE;
+ virtual void RestoreTab() OVERRIDE;
+ virtual void ShowKeyboardOverlay() OVERRIDE;
+ virtual keyboard::KeyboardControllerProxy*
+ CreateKeyboardControllerProxy() OVERRIDE;
+ virtual void ShowTaskManager() OVERRIDE;
+ virtual content::BrowserContext* GetCurrentBrowserContext() OVERRIDE;
+ virtual void ToggleSpokenFeedback(
+ AccessibilityNotificationVisibility notify) OVERRIDE;
+ virtual bool IsSpokenFeedbackEnabled() const OVERRIDE;
+ virtual void ToggleHighContrast() OVERRIDE;
+ virtual bool IsHighContrastEnabled() const OVERRIDE;
+ virtual void SetMagnifierEnabled(bool enabled) OVERRIDE;
+ virtual void SetMagnifierType(MagnifierType type) OVERRIDE;
+ virtual bool IsMagnifierEnabled() const OVERRIDE;
+ virtual MagnifierType GetMagnifierType() const OVERRIDE;
+ virtual void SetLargeCursorEnabled(bool enabled) OVERRIDE;
+ virtual bool IsLargeCursorEnabled() const OVERRIDE;
+ virtual bool ShouldAlwaysShowAccessibilityMenu() const OVERRIDE;
+ virtual void SilenceSpokenFeedback() const OVERRIDE;
+ virtual app_list::AppListViewDelegate* CreateAppListViewDelegate() OVERRIDE;
+ virtual ash::LauncherDelegate* CreateLauncherDelegate(
+ ash::LauncherModel* model) OVERRIDE;
+ virtual ash::SystemTrayDelegate* CreateSystemTrayDelegate() OVERRIDE;
+ virtual ash::UserWallpaperDelegate* CreateUserWallpaperDelegate() OVERRIDE;
+ virtual ash::CapsLockDelegate* CreateCapsLockDelegate() OVERRIDE;
+ virtual ash::SessionStateDelegate* CreateSessionStateDelegate() OVERRIDE;
+ virtual aura::client::UserActionClient* CreateUserActionClient() OVERRIDE;
+ virtual void OpenFeedbackPage() OVERRIDE;
+ virtual void RecordUserMetricsAction(UserMetricsAction action) OVERRIDE;
+ virtual void HandleMediaNextTrack() OVERRIDE;
+ virtual void HandleMediaPlayPause() OVERRIDE;
+ virtual void HandleMediaPrevTrack() OVERRIDE;
+ virtual void SaveScreenMagnifierScale(double scale) OVERRIDE;
+ virtual double GetSavedScreenMagnifierScale() OVERRIDE;
+ virtual ui::MenuModel* CreateContextMenu(
+ aura::RootWindow* root_window) OVERRIDE;
+ virtual RootWindowHostFactory* CreateRootWindowHostFactory() OVERRIDE;
+ virtual base::string16 GetProductName() const OVERRIDE;
+
+ private:
+ // Used to update Launcher. Owned by main.
+ WindowWatcher* watcher_;
+
+ LauncherDelegateImpl* launcher_delegate_;
+
+ bool spoken_feedback_enabled_;
+ bool high_contrast_enabled_;
+ bool screen_magnifier_enabled_;
+ MagnifierType screen_magnifier_type_;
+ bool large_cursor_enabled_;
+
+ DISALLOW_COPY_AND_ASSIGN(ShellDelegateImpl);
+};
+
+} // namespace shell
+} // namespace ash
+
+#endif // ASH_SHELL_SHELL_DELEGATE_IMPL_H_
diff --git a/chromium/ash/shell/shell_main.cc b/chromium/ash/shell/shell_main.cc
new file mode 100644
index 00000000000..5648c814464
--- /dev/null
+++ b/chromium/ash/shell/shell_main.cc
@@ -0,0 +1,25 @@
+// Copyright (c) 2012 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.
+
+#include "content/public/app/content_main.h"
+#include "sandbox/win/src/sandbox_types.h"
+#include "ash/shell/content_client/shell_main_delegate.h"
+
+#if defined(OS_WIN)
+#include "content/public/app/startup_helper_win.h"
+#endif
+
+#if defined(OS_WIN)
+int APIENTRY wWinMain(HINSTANCE instance, HINSTANCE, wchar_t*, int) {
+ sandbox::SandboxInterfaceInfo sandbox_info = {0};
+ content::InitializeSandboxInfo(&sandbox_info);
+ ash::shell::ShellMainDelegate delegate;
+ return content::ContentMain(instance, &sandbox_info, &delegate);
+}
+#else
+int main(int argc, const char** argv) {
+ ash::shell::ShellMainDelegate delegate;
+ return content::ContentMain(argc, argv, &delegate);
+}
+#endif
diff --git a/chromium/ash/shell/shell_main_parts.cc b/chromium/ash/shell/shell_main_parts.cc
new file mode 100644
index 00000000000..739950308bf
--- /dev/null
+++ b/chromium/ash/shell/shell_main_parts.cc
@@ -0,0 +1,21 @@
+// Copyright (c) 2012 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.
+
+#include "ash/shell/shell_main_parts.h"
+
+#include "base/i18n/icu_util.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "ui/base/ui_base_paths.h"
+
+namespace ash {
+namespace shell {
+
+void PreMainMessageLoopStart() {
+ ui::RegisterPathProvider();
+ icu_util::Initialize();
+ ResourceBundle::InitSharedInstanceWithLocale("en-US", NULL);
+}
+
+} // namespace ash
+} // namespace shell
diff --git a/chromium/ash/shell/shell_main_parts.h b/chromium/ash/shell/shell_main_parts.h
new file mode 100644
index 00000000000..c0452e95d31
--- /dev/null
+++ b/chromium/ash/shell/shell_main_parts.h
@@ -0,0 +1,17 @@
+// Copyright (c) 2012 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 ASH_SHELL_SHELL_MAIN_PARTS_H_
+#define ASH_SHELL_SHELL_MAIN_PARTS_H_
+
+namespace ash {
+namespace shell {
+
+// Platform initializations prior to the start of the main message loop.
+void PreMainMessageLoopStart();
+
+} // namespace shell
+} // namespace ash
+
+#endif // ASH_SHELL_SHELL_MAIN_PARTS_H_
diff --git a/chromium/ash/shell/shell_main_parts_mac.mm b/chromium/ash/shell/shell_main_parts_mac.mm
new file mode 100644
index 00000000000..292e1d5ff89
--- /dev/null
+++ b/chromium/ash/shell/shell_main_parts_mac.mm
@@ -0,0 +1,30 @@
+// Copyright (c) 2012 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.
+
+#include "ash/shell/shell_main_parts.h"
+
+#import <Cocoa/Cocoa.h>
+
+#include "base/i18n/icu_util.h"
+#include "base/mac/bundle_locations.h"
+#include "base/mac/scoped_nsobject.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "ui/base/ui_base_paths.h"
+
+namespace ash {
+namespace shell {
+
+void PreMainMessageLoopStart() {
+ ui::RegisterPathProvider();
+ icu_util::Initialize();
+ ResourceBundle::InitSharedInstanceWithLocale("en-US", NULL);
+
+ base::scoped_nsobject<NSNib> nib(
+ [[NSNib alloc] initWithNibNamed:@"MainMenu"
+ bundle:base::mac::FrameworkBundle()]);
+ [nib instantiateNibWithOwner:NSApp topLevelObjects:nil];
+}
+
+} // namespace shell
+} // namespace ash
diff --git a/chromium/ash/shell/toplevel_window.cc b/chromium/ash/shell/toplevel_window.cc
new file mode 100644
index 00000000000..d6b83889fc5
--- /dev/null
+++ b/chromium/ash/shell/toplevel_window.cc
@@ -0,0 +1,69 @@
+// Copyright (c) 2012 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.
+
+#include "ash/shell/toplevel_window.h"
+
+#include "ash/display/display_controller.h"
+#include "ash/screen_ash.h"
+#include "ash/shell.h"
+#include "ash/wm/property_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "ui/aura/root_window.h"
+#include "ui/aura/window.h"
+#include "ui/gfx/canvas.h"
+#include "ui/views/widget/widget.h"
+
+namespace ash {
+namespace shell {
+
+ToplevelWindow::CreateParams::CreateParams()
+ : can_resize(false),
+ can_maximize(false) {
+}
+
+// static
+void ToplevelWindow::CreateToplevelWindow(const CreateParams& params) {
+ static int count = 0;
+ int x = count == 0 ? 150 : 750;
+ count = (count + 1) % 2;
+
+ gfx::Rect bounds(x, 150, 300, 300);
+ gfx::Display display =
+ ash::Shell::GetScreen()->GetDisplayMatching(bounds);
+ aura::RootWindow* root = ash::Shell::GetInstance()->display_controller()->
+ GetRootWindowForDisplayId(display.id());
+ views::Widget* widget = views::Widget::CreateWindowWithContextAndBounds(
+ new ToplevelWindow(params), root, bounds);
+ widget->GetNativeView()->SetName("Examples:ToplevelWindow");
+ widget->Show();
+}
+
+ToplevelWindow::ToplevelWindow(const CreateParams& params) : params_(params) {
+}
+
+ToplevelWindow::~ToplevelWindow() {
+}
+
+void ToplevelWindow::OnPaint(gfx::Canvas* canvas) {
+ canvas->FillRect(GetLocalBounds(), SK_ColorDKGRAY);
+}
+
+base::string16 ToplevelWindow::GetWindowTitle() const {
+ return ASCIIToUTF16("Examples: Toplevel Window");
+}
+
+views::View* ToplevelWindow::GetContentsView() {
+ return this;
+}
+
+bool ToplevelWindow::CanResize() const {
+ return params_.can_resize;
+}
+
+bool ToplevelWindow::CanMaximize() const {
+ return params_.can_maximize;
+}
+
+} // namespace shell
+} // namespace ash
diff --git a/chromium/ash/shell/toplevel_window.h b/chromium/ash/shell/toplevel_window.h
new file mode 100644
index 00000000000..4e4c1a9c439
--- /dev/null
+++ b/chromium/ash/shell/toplevel_window.h
@@ -0,0 +1,44 @@
+// Copyright (c) 2012 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 ASH_SHELL_TOPLEVEL_WINDOW_H_
+#define ASH_SHELL_TOPLEVEL_WINDOW_H_
+
+#include "ui/views/widget/widget_delegate.h"
+
+namespace ash {
+namespace shell {
+
+class ToplevelWindow : public views::WidgetDelegateView {
+ public:
+ struct CreateParams {
+ CreateParams();
+
+ bool can_resize;
+ bool can_maximize;
+ };
+ static void CreateToplevelWindow(const CreateParams& params);
+
+ private:
+ explicit ToplevelWindow(const CreateParams& params);
+ virtual ~ToplevelWindow();
+
+ // Overridden from views::View:
+ virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE;
+
+ // Overridden from views::WidgetDelegate:
+ virtual base::string16 GetWindowTitle() const OVERRIDE;
+ virtual View* GetContentsView() OVERRIDE;
+ virtual bool CanResize() const OVERRIDE;
+ virtual bool CanMaximize() const OVERRIDE;
+
+ const CreateParams params_;
+
+ DISALLOW_COPY_AND_ASSIGN(ToplevelWindow);
+};
+
+} // namespace shell
+} // namespace ash
+
+#endif // ASH_SHELL_TOPLEVEL_WINDOW_H_
diff --git a/chromium/ash/shell/widgets.cc b/chromium/ash/shell/widgets.cc
new file mode 100644
index 00000000000..01654906fbc
--- /dev/null
+++ b/chromium/ash/shell/widgets.cc
@@ -0,0 +1,145 @@
+// Copyright (c) 2012 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.
+
+#include "ash/shell.h"
+#include "base/strings/utf_string_conversions.h" // ASCIIToUTF16
+#include "ui/aura/root_window.h"
+#include "ui/aura/window.h"
+#include "ui/gfx/canvas.h"
+#include "ui/views/controls/button/checkbox.h"
+#include "ui/views/controls/button/label_button.h"
+#include "ui/views/controls/button/radio_button.h"
+#include "ui/views/widget/widget.h"
+#include "ui/views/widget/widget_delegate.h"
+
+namespace {
+
+// Default window position.
+const int kWindowLeft = 170;
+const int kWindowTop = 200;
+
+// Default window size.
+const int kWindowWidth = 400;
+const int kWindowHeight = 400;
+
+// A window showing samples of commonly used widgets.
+class WidgetsWindow : public views::WidgetDelegateView {
+ public:
+ WidgetsWindow();
+ virtual ~WidgetsWindow();
+
+ // Overridden from views::View:
+ virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE;
+ virtual void Layout() OVERRIDE;
+ virtual gfx::Size GetPreferredSize() OVERRIDE;
+
+ // Overridden from views::WidgetDelegate:
+ virtual views::View* GetContentsView() OVERRIDE;
+ virtual base::string16 GetWindowTitle() const OVERRIDE;
+ virtual bool CanResize() const OVERRIDE;
+
+ private:
+ views::LabelButton* button_;
+ views::LabelButton* disabled_button_;
+ views::Checkbox* checkbox_;
+ views::Checkbox* checkbox_disabled_;
+ views::Checkbox* checkbox_checked_;
+ views::Checkbox* checkbox_checked_disabled_;
+ views::RadioButton* radio_button_;
+ views::RadioButton* radio_button_disabled_;
+ views::RadioButton* radio_button_selected_;
+ views::RadioButton* radio_button_selected_disabled_;
+};
+
+WidgetsWindow::WidgetsWindow()
+ : button_(new views::LabelButton(NULL, ASCIIToUTF16("Button"))),
+ disabled_button_(
+ new views::LabelButton(NULL, ASCIIToUTF16("Disabled button"))),
+ checkbox_(new views::Checkbox(ASCIIToUTF16("Checkbox"))),
+ checkbox_disabled_(new views::Checkbox(
+ ASCIIToUTF16("Checkbox disabled"))),
+ checkbox_checked_(new views::Checkbox(ASCIIToUTF16("Checkbox checked"))),
+ checkbox_checked_disabled_(new views::Checkbox(
+ ASCIIToUTF16("Checkbox checked disabled"))),
+ radio_button_(new views::RadioButton(ASCIIToUTF16("Radio button"), 0)),
+ radio_button_disabled_(new views::RadioButton(
+ ASCIIToUTF16("Radio button disabled"), 0)),
+ radio_button_selected_(new views::RadioButton(
+ ASCIIToUTF16("Radio button selected"), 0)),
+ radio_button_selected_disabled_(new views::RadioButton(
+ ASCIIToUTF16("Radio button selected disabled"), 1)) {
+ button_->SetStyle(views::Button::STYLE_NATIVE_TEXTBUTTON);
+ AddChildView(button_);
+ disabled_button_->SetEnabled(false);
+ disabled_button_->SetStyle(views::Button::STYLE_NATIVE_TEXTBUTTON);
+ AddChildView(disabled_button_);
+ AddChildView(checkbox_);
+ checkbox_disabled_->SetEnabled(false);
+ AddChildView(checkbox_disabled_);
+ checkbox_checked_->SetChecked(true);
+ AddChildView(checkbox_checked_);
+ checkbox_checked_disabled_->SetChecked(true);
+ checkbox_checked_disabled_->SetEnabled(false);
+ AddChildView(checkbox_checked_disabled_);
+ AddChildView(radio_button_);
+ radio_button_disabled_->SetEnabled(false);
+ AddChildView(radio_button_disabled_);
+ radio_button_selected_->SetChecked(true);
+ AddChildView(radio_button_selected_);
+ radio_button_selected_disabled_->SetChecked(true);
+ radio_button_selected_disabled_->SetEnabled(false);
+ AddChildView(radio_button_selected_disabled_);
+}
+
+WidgetsWindow::~WidgetsWindow() {
+}
+
+void WidgetsWindow::OnPaint(gfx::Canvas* canvas) {
+ canvas->FillRect(GetLocalBounds(), SK_ColorWHITE);
+}
+
+void WidgetsWindow::Layout() {
+ const int kVerticalPad = 5;
+ int left = 5;
+ int top = kVerticalPad;
+ for (int i = 0; i < child_count(); ++i) {
+ views::View* view = child_at(i);
+ gfx::Size preferred = view->GetPreferredSize();
+ view->SetBounds(left, top, preferred.width(), preferred.height());
+ top += preferred.height() + kVerticalPad;
+ }
+}
+
+gfx::Size WidgetsWindow::GetPreferredSize() {
+ return gfx::Size(kWindowWidth, kWindowHeight);
+}
+
+views::View* WidgetsWindow::GetContentsView() {
+ return this;
+}
+
+base::string16 WidgetsWindow::GetWindowTitle() const {
+ return ASCIIToUTF16("Examples: Widgets");
+}
+
+bool WidgetsWindow::CanResize() const {
+ return true;
+}
+
+} // namespace
+
+namespace ash {
+namespace shell {
+
+void CreateWidgetsWindow() {
+ gfx::Rect bounds(kWindowLeft, kWindowTop, kWindowWidth, kWindowHeight);
+ views::Widget* widget =
+ views::Widget::CreateWindowWithContextAndBounds(
+ new WidgetsWindow, Shell::GetPrimaryRootWindow(), bounds);
+ widget->GetNativeView()->SetName("WidgetsWindow");
+ widget->Show();
+}
+
+} // namespace shell
+} // namespace ash
diff --git a/chromium/ash/shell/window_type_launcher.cc b/chromium/ash/shell/window_type_launcher.cc
new file mode 100644
index 00000000000..799c34cb54e
--- /dev/null
+++ b/chromium/ash/shell/window_type_launcher.cc
@@ -0,0 +1,405 @@
+// Copyright (c) 2012 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.
+
+#include "ash/shell/window_type_launcher.h"
+
+#include "ash/root_window_controller.h"
+#include "ash/screensaver/screensaver_view.h"
+#include "ash/session_state_delegate.h"
+#include "ash/shelf/shelf_widget.h"
+#include "ash/shell.h"
+#include "ash/shell/example_factory.h"
+#include "ash/shell/panel_window.h"
+#include "ash/shell/toplevel_window.h"
+#include "ash/shell_window_ids.h"
+#include "ash/system/status_area_widget.h"
+#include "ash/system/web_notification/web_notification_tray.h"
+#include "base/bind.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/time/time.h"
+#include "content/public/browser/browser_thread.h"
+#include "ui/aura/root_window.h"
+#include "ui/aura/window.h"
+#include "ui/compositor/layer.h"
+#include "ui/gfx/canvas.h"
+#include "ui/message_center/message_center.h"
+#include "ui/message_center/notification_types.h"
+#include "ui/views/controls/button/label_button.h"
+#include "ui/views/controls/menu/menu_item_view.h"
+#include "ui/views/controls/menu/menu_runner.h"
+#include "ui/views/corewm/shadow_types.h"
+#include "ui/views/examples/examples_window_with_content.h"
+#include "ui/views/layout/grid_layout.h"
+#include "ui/views/test/child_modal_window.h"
+#include "ui/views/widget/widget.h"
+
+using views::MenuItemView;
+using views::MenuRunner;
+
+namespace ash {
+namespace shell {
+
+namespace {
+
+SkColor g_colors[] = { SK_ColorRED,
+ SK_ColorYELLOW,
+ SK_ColorBLUE,
+ SK_ColorGREEN };
+int g_color_index = 0;
+
+class ModalWindow : public views::WidgetDelegateView,
+ public views::ButtonListener {
+ public:
+ explicit ModalWindow(ui::ModalType modal_type)
+ : modal_type_(modal_type),
+ color_(g_colors[g_color_index]),
+ open_button_(new views::LabelButton(this, ASCIIToUTF16("Moar!"))) {
+ ++g_color_index %= arraysize(g_colors);
+ open_button_->SetStyle(views::Button::STYLE_NATIVE_TEXTBUTTON);
+ AddChildView(open_button_);
+ }
+ virtual ~ModalWindow() {
+ }
+
+ static void OpenModalWindow(aura::Window* parent, ui::ModalType modal_type) {
+ views::Widget* widget =
+ views::Widget::CreateWindowWithParent(new ModalWindow(modal_type),
+ parent);
+ widget->GetNativeView()->SetName("ModalWindow");
+ widget->Show();
+ }
+
+ // Overridden from views::View:
+ virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE {
+ canvas->FillRect(GetLocalBounds(), color_);
+ }
+ virtual gfx::Size GetPreferredSize() OVERRIDE {
+ return gfx::Size(200, 200);
+ }
+ virtual void Layout() OVERRIDE {
+ gfx::Size open_ps = open_button_->GetPreferredSize();
+ gfx::Rect local_bounds = GetLocalBounds();
+ open_button_->SetBounds(
+ 5, local_bounds.bottom() - open_ps.height() - 5,
+ open_ps.width(), open_ps.height());
+ }
+
+ // Overridden from views::WidgetDelegate:
+ virtual views::View* GetContentsView() OVERRIDE {
+ return this;
+ }
+ virtual bool CanResize() const OVERRIDE {
+ return true;
+ }
+ virtual base::string16 GetWindowTitle() const OVERRIDE {
+ return ASCIIToUTF16("Modal Window");
+ }
+ virtual ui::ModalType GetModalType() const OVERRIDE {
+ return modal_type_;
+ }
+
+ // Overridden from views::ButtonListener:
+ virtual void ButtonPressed(views::Button* sender,
+ const ui::Event& event) OVERRIDE {
+ DCHECK(sender == open_button_);
+ OpenModalWindow(GetWidget()->GetNativeView(), modal_type_);
+ }
+
+ private:
+ ui::ModalType modal_type_;
+ SkColor color_;
+ views::LabelButton* open_button_;
+
+ DISALLOW_COPY_AND_ASSIGN(ModalWindow);
+};
+
+class NonModalTransient : public views::WidgetDelegateView {
+ public:
+ NonModalTransient()
+ : color_(g_colors[g_color_index]) {
+ ++g_color_index %= arraysize(g_colors);
+ }
+ virtual ~NonModalTransient() {
+ }
+
+ static void OpenNonModalTransient(aura::Window* parent) {
+ views::Widget* widget =
+ views::Widget::CreateWindowWithParent(new NonModalTransient, parent);
+ widget->GetNativeView()->SetName("NonModalTransient");
+ widget->Show();
+ }
+
+ static void ToggleNonModalTransient(aura::Window* parent) {
+ if (!non_modal_transient_) {
+ non_modal_transient_ =
+ views::Widget::CreateWindowWithParent(new NonModalTransient, parent);
+ non_modal_transient_->GetNativeView()->SetName("NonModalTransient");
+ }
+ if (non_modal_transient_->IsVisible())
+ non_modal_transient_->Hide();
+ else
+ non_modal_transient_->Show();
+ }
+
+ // Overridden from views::View:
+ virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE {
+ canvas->FillRect(GetLocalBounds(), color_);
+ }
+ virtual gfx::Size GetPreferredSize() OVERRIDE {
+ return gfx::Size(250, 250);
+ }
+
+ // Overridden from views::WidgetDelegate:
+ virtual views::View* GetContentsView() OVERRIDE {
+ return this;
+ }
+ virtual bool CanResize() const OVERRIDE {
+ return true;
+ }
+ virtual base::string16 GetWindowTitle() const OVERRIDE {
+ return ASCIIToUTF16("Non-Modal Transient");
+ }
+ virtual void DeleteDelegate() OVERRIDE {
+ if (GetWidget() == non_modal_transient_)
+ non_modal_transient_ = NULL;
+
+ delete this;
+ }
+
+ private:
+ SkColor color_;
+
+ static views::Widget* non_modal_transient_;
+
+ DISALLOW_COPY_AND_ASSIGN(NonModalTransient);
+};
+
+// static
+views::Widget* NonModalTransient::non_modal_transient_ = NULL;
+
+void AddViewToLayout(views::GridLayout* layout, views::View* view) {
+ layout->StartRow(0, 0);
+ layout->AddView(view);
+ layout->AddPaddingRow(0, 5);
+}
+
+} // namespace
+
+void InitWindowTypeLauncher() {
+ views::Widget* widget =
+ views::Widget::CreateWindowWithContextAndBounds(
+ new WindowTypeLauncher,
+ Shell::GetPrimaryRootWindow(),
+ gfx::Rect(120, 150, 300, 410));
+ widget->GetNativeView()->SetName("WindowTypeLauncher");
+ views::corewm::SetShadowType(widget->GetNativeView(),
+ views::corewm::SHADOW_TYPE_RECTANGULAR);
+ widget->Show();
+}
+
+WindowTypeLauncher::WindowTypeLauncher()
+ : create_button_(new views::LabelButton(
+ this, ASCIIToUTF16("Create Window"))),
+ panel_button_(new views::LabelButton(
+ this, ASCIIToUTF16("Create Panel"))),
+ create_nonresizable_button_(new views::LabelButton(
+ this, ASCIIToUTF16("Create Non-Resizable Window"))),
+ bubble_button_(new views::LabelButton(
+ this, ASCIIToUTF16("Create Pointy Bubble"))),
+ lock_button_(new views::LabelButton(
+ this, ASCIIToUTF16("Lock Screen"))),
+ widgets_button_(new views::LabelButton(
+ this, ASCIIToUTF16("Show Example Widgets"))),
+ system_modal_button_(new views::LabelButton(
+ this, ASCIIToUTF16("Open System Modal Window"))),
+ window_modal_button_(new views::LabelButton(
+ this, ASCIIToUTF16("Open Window Modal Window"))),
+ child_modal_button_(new views::LabelButton(
+ this, ASCIIToUTF16("Open Child Modal Window"))),
+ transient_button_(new views::LabelButton(
+ this, ASCIIToUTF16("Open Non-Modal Transient Window"))),
+ examples_button_(new views::LabelButton(
+ this, ASCIIToUTF16("Open Views Examples Window"))),
+ show_hide_window_button_(new views::LabelButton(
+ this, ASCIIToUTF16("Show/Hide a Window"))),
+ show_screensaver_(new views::LabelButton(
+ this, ASCIIToUTF16("Show the Screensaver [for 5 seconds]"))),
+ show_web_notification_(new views::LabelButton(
+ this, ASCIIToUTF16("Show a web/app notification"))) {
+ create_button_->SetStyle(views::Button::STYLE_NATIVE_TEXTBUTTON);
+ panel_button_->SetStyle(views::Button::STYLE_NATIVE_TEXTBUTTON);
+ create_nonresizable_button_->SetStyle(views::Button::STYLE_NATIVE_TEXTBUTTON);
+ bubble_button_->SetStyle(views::Button::STYLE_NATIVE_TEXTBUTTON);
+ lock_button_->SetStyle(views::Button::STYLE_NATIVE_TEXTBUTTON);
+ widgets_button_->SetStyle(views::Button::STYLE_NATIVE_TEXTBUTTON);
+ system_modal_button_->SetStyle(views::Button::STYLE_NATIVE_TEXTBUTTON);
+ window_modal_button_->SetStyle(views::Button::STYLE_NATIVE_TEXTBUTTON);
+ child_modal_button_->SetStyle(views::Button::STYLE_NATIVE_TEXTBUTTON);
+ transient_button_->SetStyle(views::Button::STYLE_NATIVE_TEXTBUTTON);
+ examples_button_->SetStyle(views::Button::STYLE_NATIVE_TEXTBUTTON);
+ show_hide_window_button_->SetStyle(views::Button::STYLE_NATIVE_TEXTBUTTON);
+ show_screensaver_->SetStyle(views::Button::STYLE_NATIVE_TEXTBUTTON);
+ show_web_notification_->SetStyle(views::Button::STYLE_NATIVE_TEXTBUTTON);
+
+ views::GridLayout* layout = new views::GridLayout(this);
+ layout->SetInsets(5, 5, 5, 5);
+ SetLayoutManager(layout);
+ views::ColumnSet* column_set = layout->AddColumnSet(0);
+ column_set->AddColumn(views::GridLayout::LEADING,
+ views::GridLayout::CENTER,
+ 0,
+ views::GridLayout::USE_PREF,
+ 0,
+ 0);
+ AddViewToLayout(layout, create_button_);
+ AddViewToLayout(layout, panel_button_);
+ AddViewToLayout(layout, create_nonresizable_button_);
+ AddViewToLayout(layout, bubble_button_);
+ AddViewToLayout(layout, lock_button_);
+ AddViewToLayout(layout, widgets_button_);
+ AddViewToLayout(layout, system_modal_button_);
+ AddViewToLayout(layout, window_modal_button_);
+ AddViewToLayout(layout, child_modal_button_);
+ AddViewToLayout(layout, transient_button_);
+ AddViewToLayout(layout, examples_button_);
+ AddViewToLayout(layout, show_hide_window_button_);
+ AddViewToLayout(layout, show_screensaver_);
+ AddViewToLayout(layout, show_web_notification_);
+#if !defined(OS_MACOSX)
+ set_context_menu_controller(this);
+#endif
+}
+
+WindowTypeLauncher::~WindowTypeLauncher() {
+}
+
+void WindowTypeLauncher::OnPaint(gfx::Canvas* canvas) {
+ canvas->FillRect(GetLocalBounds(), SK_ColorWHITE);
+}
+
+bool WindowTypeLauncher::OnMousePressed(const ui::MouseEvent& event) {
+ // Overridden so we get OnMouseReleased and can show the context menu.
+ return true;
+}
+
+views::View* WindowTypeLauncher::GetContentsView() {
+ return this;
+}
+
+bool WindowTypeLauncher::CanResize() const {
+ return true;
+}
+
+base::string16 WindowTypeLauncher::GetWindowTitle() const {
+ return ASCIIToUTF16("Examples: Window Builder");
+}
+
+bool WindowTypeLauncher::CanMaximize() const {
+ return true;
+}
+
+void WindowTypeLauncher::ButtonPressed(views::Button* sender,
+ const ui::Event& event) {
+ if (sender == create_button_) {
+ ToplevelWindow::CreateParams params;
+ params.can_resize = true;
+ params.can_maximize = true;
+ ToplevelWindow::CreateToplevelWindow(params);
+ } else if (sender == panel_button_) {
+ PanelWindow::CreatePanelWindow(gfx::Rect());
+ } else if (sender == create_nonresizable_button_) {
+ ToplevelWindow::CreateToplevelWindow(ToplevelWindow::CreateParams());
+ } else if (sender == bubble_button_) {
+ CreatePointyBubble(sender);
+ } else if (sender == lock_button_) {
+ Shell::GetInstance()->session_state_delegate()->LockScreen();
+ } else if (sender == widgets_button_) {
+ CreateWidgetsWindow();
+ } else if (sender == system_modal_button_) {
+ ModalWindow::OpenModalWindow(GetWidget()->GetNativeView(),
+ ui::MODAL_TYPE_SYSTEM);
+ } else if (sender == window_modal_button_) {
+ ModalWindow::OpenModalWindow(GetWidget()->GetNativeView(),
+ ui::MODAL_TYPE_WINDOW);
+ } else if (sender == child_modal_button_) {
+ views::test::CreateChildModalParent(
+ GetWidget()->GetNativeView()->GetRootWindow());
+ } else if (sender == transient_button_) {
+ NonModalTransient::OpenNonModalTransient(GetWidget()->GetNativeView());
+ } else if (sender == show_hide_window_button_) {
+ NonModalTransient::ToggleNonModalTransient(GetWidget()->GetNativeView());
+ } else if (sender == show_screensaver_) {
+ ash::ShowScreensaver(GURL("http://www.google.com"));
+ content::BrowserThread::PostDelayedTask(content::BrowserThread::UI,
+ FROM_HERE,
+ base::Bind(&ash::CloseScreensaver),
+ base::TimeDelta::FromSeconds(5));
+
+ } else if (sender == show_web_notification_) {
+ scoped_ptr<message_center::Notification> notification;
+ notification.reset(new message_center::Notification(
+ message_center::NOTIFICATION_TYPE_SIMPLE,
+ "id0",
+ ASCIIToUTF16("Test Shell Web Notification"),
+ ASCIIToUTF16("Notification message body."),
+ gfx::Image(),
+ ASCIIToUTF16("www.testshell.org"),
+ "" /* extension id */,
+ message_center::RichNotificationData(),
+ NULL /* delegate */));
+
+ ash::Shell::GetPrimaryRootWindowController()->shelf()->status_area_widget()
+ ->web_notification_tray()->message_center()
+ ->AddNotification(notification.Pass());
+ }
+#if !defined(OS_MACOSX)
+ else if (sender == examples_button_) {
+ views::examples::ShowExamplesWindowWithContent(
+ views::examples::DO_NOTHING_ON_CLOSE,
+ ash::Shell::GetInstance()->browser_context());
+ }
+#endif // !defined(OS_MACOSX)
+}
+
+#if !defined(OS_MACOSX)
+void WindowTypeLauncher::ExecuteCommand(int id, int event_flags) {
+ switch (id) {
+ case COMMAND_NEW_WINDOW:
+ InitWindowTypeLauncher();
+ break;
+ case COMMAND_TOGGLE_FULLSCREEN:
+ GetWidget()->SetFullscreen(!GetWidget()->IsFullscreen());
+ break;
+ default:
+ break;
+ }
+}
+#endif // !defined(OS_MACOSX)
+
+#if !defined(OS_MACOSX)
+void WindowTypeLauncher::ShowContextMenuForView(
+ views::View* source,
+ const gfx::Point& point,
+ ui::MenuSourceType source_type) {
+ MenuItemView* root = new MenuItemView(this);
+ root->AppendMenuItem(COMMAND_NEW_WINDOW,
+ ASCIIToUTF16("New Window"),
+ MenuItemView::NORMAL);
+ root->AppendMenuItem(COMMAND_TOGGLE_FULLSCREEN,
+ ASCIIToUTF16("Toggle FullScreen"),
+ MenuItemView::NORMAL);
+ // MenuRunner takes ownership of root.
+ menu_runner_.reset(new MenuRunner(root));
+ if (menu_runner_->RunMenuAt(GetWidget(), NULL,
+ gfx::Rect(point, gfx::Size()),
+ MenuItemView::TOPLEFT,
+ source_type,
+ MenuRunner::HAS_MNEMONICS | views::MenuRunner::CONTEXT_MENU) ==
+ MenuRunner::MENU_DELETED)
+ return;
+}
+#endif // !defined(OS_MACOSX)
+
+} // namespace shell
+} // namespace ash
diff --git a/chromium/ash/shell/window_type_launcher.h b/chromium/ash/shell/window_type_launcher.h
new file mode 100644
index 00000000000..29d703d115d
--- /dev/null
+++ b/chromium/ash/shell/window_type_launcher.h
@@ -0,0 +1,93 @@
+// Copyright (c) 2012 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 ASH_SHELL_WINDOW_TYPE_LAUNCHER_H_
+#define ASH_SHELL_WINDOW_TYPE_LAUNCHER_H_
+
+#include "ui/views/context_menu_controller.h"
+#include "ui/views/controls/button/button.h"
+#include "ui/views/controls/menu/menu_delegate.h"
+#include "ui/views/widget/widget_delegate.h"
+
+namespace views {
+class MenuRunner;
+class LabelButton;
+}
+
+namespace ash {
+namespace shell {
+
+// The contents view/delegate of a window that shows some buttons that create
+// various window types.
+#if defined(OS_MACOSX)
+class WindowTypeLauncher : public views::WidgetDelegateView,
+ public views::ButtonListener {
+#else
+class WindowTypeLauncher : public views::WidgetDelegateView,
+ public views::ButtonListener,
+ public views::MenuDelegate,
+ public views::ContextMenuController {
+#endif // defined(OS_MACOSX)
+ public:
+ WindowTypeLauncher();
+ virtual ~WindowTypeLauncher();
+
+ private:
+ typedef std::pair<aura::Window*, gfx::Rect> WindowAndBoundsPair;
+
+ enum MenuCommands {
+ COMMAND_NEW_WINDOW = 1,
+ COMMAND_TOGGLE_FULLSCREEN = 3,
+ };
+
+ // Overridden from views::View:
+ virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE;
+ virtual bool OnMousePressed(const ui::MouseEvent& event) OVERRIDE;
+
+ // Overridden from views::WidgetDelegate:
+ virtual views::View* GetContentsView() OVERRIDE;
+ virtual bool CanResize() const OVERRIDE;
+ virtual base::string16 GetWindowTitle() const OVERRIDE;
+ virtual bool CanMaximize() const OVERRIDE;
+
+ // Overridden from views::ButtonListener:
+ virtual void ButtonPressed(views::Button* sender,
+ const ui::Event& event) OVERRIDE;
+
+#if !defined(OS_MACOSX)
+ // Overridden from views::MenuDelegate:
+ virtual void ExecuteCommand(int id, int event_flags) OVERRIDE;
+
+ // Override from views::ContextMenuController:
+ virtual void ShowContextMenuForView(views::View* source,
+ const gfx::Point& point,
+ ui::MenuSourceType source_type) OVERRIDE;
+#endif // !defined(OS_MACOSX)
+
+ views::LabelButton* create_button_;
+ views::LabelButton* create_persistant_button_;
+ views::LabelButton* panel_button_;
+ views::LabelButton* create_nonresizable_button_;
+ views::LabelButton* bubble_button_;
+ views::LabelButton* lock_button_;
+ views::LabelButton* widgets_button_;
+ views::LabelButton* system_modal_button_;
+ views::LabelButton* window_modal_button_;
+ views::LabelButton* child_modal_button_;
+ views::LabelButton* transient_button_;
+ views::LabelButton* examples_button_;
+ views::LabelButton* show_hide_window_button_;
+ views::LabelButton* show_screensaver_;
+ views::LabelButton* show_web_notification_;
+#if !defined(OS_MACOSX)
+ scoped_ptr<views::MenuRunner> menu_runner_;
+#endif
+
+ DISALLOW_COPY_AND_ASSIGN(WindowTypeLauncher);
+};
+
+} // namespace shell
+} // namespace ash
+
+#endif // ASH_SHELL_WINDOW_TYPE_LAUNCHER_H_
diff --git a/chromium/ash/shell/window_watcher.cc b/chromium/ash/shell/window_watcher.cc
new file mode 100644
index 00000000000..0963eb7cc96
--- /dev/null
+++ b/chromium/ash/shell/window_watcher.cc
@@ -0,0 +1,156 @@
+// Copyright (c) 2012 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.
+
+#include "ash/shell/window_watcher.h"
+
+#include "ash/display/display_controller.h"
+#include "ash/launcher/launcher.h"
+#include "ash/launcher/launcher_model.h"
+#include "ash/shelf/shelf_widget.h"
+#include "ash/shell.h"
+#include "ash/shell_window_ids.h"
+#include "ui/aura/root_window.h"
+#include "ui/aura/window.h"
+#include "ui/gfx/display.h"
+
+namespace ash {
+namespace shell {
+
+class WindowWatcher::WorkspaceWindowWatcher : public aura::WindowObserver {
+ public:
+ explicit WorkspaceWindowWatcher(WindowWatcher* watcher) : watcher_(watcher) {
+ }
+
+ virtual ~WorkspaceWindowWatcher() {
+ }
+
+ virtual void OnWindowAdded(aura::Window* new_window) OVERRIDE {
+ new_window->AddObserver(watcher_);
+ }
+
+ virtual void OnWillRemoveWindow(aura::Window* window) OVERRIDE {
+ DCHECK(window->children().empty());
+ window->RemoveObserver(watcher_);
+ }
+
+ void RootWindowAdded(aura::RootWindow* root) {
+ aura::Window* panel_container = ash::Shell::GetContainer(
+ root,
+ internal::kShellWindowId_PanelContainer);
+ panel_container->AddObserver(watcher_);
+
+ aura::Window* container =
+ Launcher::ForWindow(root)->shelf_widget()->window_container();
+ container->AddObserver(this);
+ for (size_t i = 0; i < container->children().size(); ++i)
+ container->children()[i]->AddObserver(watcher_);
+ }
+
+ void RootWindowRemoved(aura::RootWindow* root) {
+ aura::Window* panel_container = ash::Shell::GetContainer(
+ root,
+ internal::kShellWindowId_PanelContainer);
+ panel_container->RemoveObserver(watcher_);
+
+ aura::Window* container =
+ Launcher::ForWindow(root)->shelf_widget()->window_container();
+ container->RemoveObserver(this);
+ for (size_t i = 0; i < container->children().size(); ++i)
+ container->children()[i]->RemoveObserver(watcher_);
+ }
+
+ private:
+ WindowWatcher* watcher_;
+
+ DISALLOW_COPY_AND_ASSIGN(WorkspaceWindowWatcher);
+};
+
+WindowWatcher::WindowWatcher() {
+ workspace_window_watcher_.reset(new WorkspaceWindowWatcher(this));
+ Shell::RootWindowList root_windows = Shell::GetAllRootWindows();
+ for (Shell::RootWindowList::iterator iter = root_windows.begin();
+ iter != root_windows.end(); ++ iter) {
+ workspace_window_watcher_->RootWindowAdded(*iter);
+ }
+}
+
+WindowWatcher::~WindowWatcher() {
+ Shell::RootWindowList root_windows = Shell::GetAllRootWindows();
+ for (Shell::RootWindowList::iterator iter = root_windows.begin();
+ iter != root_windows.end(); ++ iter) {
+ workspace_window_watcher_->RootWindowRemoved(*iter);
+ }
+}
+
+aura::Window* WindowWatcher::GetWindowByID(ash::LauncherID id) {
+ IDToWindow::const_iterator i = id_to_window_.find(id);
+ return i != id_to_window_.end() ? i->second : NULL;
+}
+
+ash::LauncherID WindowWatcher::GetIDByWindow(aura::Window* window) const {
+ for (IDToWindow::const_iterator i = id_to_window_.begin();
+ i != id_to_window_.end(); ++i) {
+ if (i->second == window)
+ return i->first;
+ }
+ return 0; // TODO: add a constant for this.
+}
+
+// aura::WindowObserver overrides:
+void WindowWatcher::OnWindowAdded(aura::Window* new_window) {
+ if (new_window->type() != aura::client::WINDOW_TYPE_NORMAL &&
+ new_window->type() != aura::client::WINDOW_TYPE_PANEL)
+ return;
+
+ static int image_count = 0;
+ ash::LauncherModel* model = Shell::GetInstance()->launcher_model();
+ ash::LauncherItem item;
+ item.type = new_window->type() == aura::client::WINDOW_TYPE_PANEL ?
+ ash::TYPE_APP_PANEL : ash::TYPE_TABBED;
+ id_to_window_[model->next_id()] = new_window;
+
+ SkBitmap icon_bitmap;
+ icon_bitmap.setConfig(SkBitmap::kARGB_8888_Config, 16, 16);
+ icon_bitmap.allocPixels();
+ icon_bitmap.eraseARGB(255,
+ image_count == 0 ? 255 : 0,
+ image_count == 1 ? 255 : 0,
+ image_count == 2 ? 255 : 0);
+ image_count = (image_count + 1) % 3;
+ item.image = gfx::ImageSkia(gfx::ImageSkiaRep(icon_bitmap,
+ ui::SCALE_FACTOR_100P));
+
+ model->Add(item);
+}
+
+void WindowWatcher::OnWillRemoveWindow(aura::Window* window) {
+ for (IDToWindow::iterator i = id_to_window_.begin();
+ i != id_to_window_.end(); ++i) {
+ if (i->second == window) {
+ ash::LauncherModel* model = Shell::GetInstance()->launcher_model();
+ int index = model->ItemIndexByID(i->first);
+ DCHECK_NE(-1, index);
+ model->RemoveItemAt(index);
+ id_to_window_.erase(i);
+ break;
+ }
+ }
+}
+
+void WindowWatcher::OnDisplayBoundsChanged(const gfx::Display& display) {
+}
+
+void WindowWatcher::OnDisplayAdded(const gfx::Display& new_display) {
+ aura::RootWindow* root = Shell::GetInstance()->display_controller()->
+ GetRootWindowForDisplayId(new_display.id());
+ workspace_window_watcher_->RootWindowAdded(root);
+}
+
+void WindowWatcher::OnDisplayRemoved(const gfx::Display& old_display) {
+ // All windows in the display has already been removed, so no need to
+ // remove observers.
+}
+
+} // namespace shell
+} // namespace ash
diff --git a/chromium/ash/shell/window_watcher.h b/chromium/ash/shell/window_watcher.h
new file mode 100644
index 00000000000..7a2c7c0c9bd
--- /dev/null
+++ b/chromium/ash/shell/window_watcher.h
@@ -0,0 +1,62 @@
+// Copyright (c) 2012 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 ASH_SHELL_WINDOW_WATCHER_H_
+#define ASH_SHELL_WINDOW_WATCHER_H_
+
+#include <map>
+
+#include "ash/launcher/launcher_types.h"
+#include "base/compiler_specific.h"
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "ui/aura/window_observer.h"
+#include "ui/gfx/display_observer.h"
+
+namespace aura {
+class Window;
+}
+
+namespace ash {
+namespace shell {
+
+// TODO(sky): fix this class, its a bit broke with workspace2.
+
+// WindowWatcher is responsible for listening for newly created windows and
+// creating items on the Launcher for them.
+class WindowWatcher : public aura::WindowObserver,
+ public gfx::DisplayObserver {
+ public:
+ WindowWatcher();
+ virtual ~WindowWatcher();
+
+ aura::Window* GetWindowByID(ash::LauncherID id);
+ ash::LauncherID GetIDByWindow(aura::Window* window) const;
+
+ // aura::WindowObserver overrides:
+ virtual void OnWindowAdded(aura::Window* new_window) OVERRIDE;
+ virtual void OnWillRemoveWindow(aura::Window* window) OVERRIDE;
+
+ // gfx::DisplayObserver overrides:
+ virtual void OnDisplayBoundsChanged(const gfx::Display& display) OVERRIDE;
+ virtual void OnDisplayAdded(const gfx::Display& new_display) OVERRIDE;
+ virtual void OnDisplayRemoved(const gfx::Display& old_display) OVERRIDE;
+
+ private:
+ class WorkspaceWindowWatcher;
+
+ typedef std::map<ash::LauncherID, aura::Window*> IDToWindow;
+
+ // Maps from window to the id we gave it.
+ IDToWindow id_to_window_;
+
+ scoped_ptr<WorkspaceWindowWatcher> workspace_window_watcher_;
+
+ DISALLOW_COPY_AND_ASSIGN(WindowWatcher);
+};
+
+} // namespace shell
+} // namespace ash
+
+#endif // ASH_SHELL_WINDOW_WATCHER_H_
diff --git a/chromium/ash/shell/window_watcher_unittest.cc b/chromium/ash/shell/window_watcher_unittest.cc
new file mode 100644
index 00000000000..dfc7e6baa0d
--- /dev/null
+++ b/chromium/ash/shell/window_watcher_unittest.cc
@@ -0,0 +1,39 @@
+// Copyright (c) 2012 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.
+
+#include "ash/shell/window_watcher.h"
+
+#include "ash/shell.h"
+#include "ash/shell_delegate.h"
+#include "ash/shell/shell_delegate_impl.h"
+#include "ash/system/user/login_status.h"
+#include "ash/test/ash_test_base.h"
+#include "ui/aura/root_window.h"
+
+namespace ash {
+
+typedef test::AshTestBase WindowWatcherTest;
+
+// This test verifies that shell can be torn down without causing failures
+// bug http://code.google.com/p/chromium/issues/detail?id=130332
+TEST_F(WindowWatcherTest, ShellDeleteInstance) {
+ scoped_ptr<ash::shell::WindowWatcher> window_watcher;
+ Shell::DeleteInstance();
+
+ shell::ShellDelegateImpl* delegate = new ash::shell::ShellDelegateImpl;
+ Shell::CreateInstance(delegate);
+ Shell::GetPrimaryRootWindow()->ShowRootWindow();
+ Shell::GetInstance()->CreateLauncher();
+ Shell::GetInstance()->UpdateAfterLoginStatusChange(
+ user::LOGGED_IN_USER);
+
+ window_watcher.reset(new ash::shell::WindowWatcher);
+
+ delegate->SetWatcher(window_watcher.get());
+ Shell::GetPrimaryRootWindow()->Hide();
+ window_watcher.reset();
+ delegate->SetWatcher(NULL);
+}
+
+} // namespace ash
diff --git a/chromium/ash/shell_delegate.h b/chromium/ash/shell_delegate.h
new file mode 100644
index 00000000000..38164f2add8
--- /dev/null
+++ b/chromium/ash/shell_delegate.h
@@ -0,0 +1,257 @@
+// Copyright (c) 2012 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 ASH_SHELL_DELEGATE_H_
+#define ASH_SHELL_DELEGATE_H_
+
+#include <string>
+
+#include "ash/ash_export.h"
+#include "ash/magnifier/magnifier_constants.h"
+#include "ash/shell.h"
+#include "base/callback.h"
+#include "base/strings/string16.h"
+
+namespace app_list {
+class AppListViewDelegate;
+}
+
+namespace aura {
+class RootWindow;
+class Window;
+namespace client {
+class UserActionClient;
+}
+}
+
+namespace ui {
+class MenuModel;
+}
+
+namespace views {
+class Widget;
+}
+
+namespace keyboard {
+class KeyboardControllerProxy;
+}
+
+namespace ash {
+
+class CapsLockDelegate;
+class LauncherDelegate;
+class LauncherModel;
+struct LauncherItem;
+class RootWindowHostFactory;
+class SessionStateDelegate;
+class SystemTrayDelegate;
+class UserWallpaperDelegate;
+
+enum UserMetricsAction {
+ UMA_ACCEL_KEYBOARD_BRIGHTNESS_DOWN_F6,
+ UMA_ACCEL_KEYBOARD_BRIGHTNESS_UP_F7,
+ UMA_ACCEL_LOCK_SCREEN_L,
+ UMA_ACCEL_LOCK_SCREEN_LOCK_BUTTON,
+ UMA_ACCEL_LOCK_SCREEN_POWER_BUTTON,
+ UMA_ACCEL_FULLSCREEN_F4,
+ UMA_ACCEL_MAXIMIZE_RESTORE_F4,
+ UMA_ACCEL_NEWTAB_T,
+ UMA_ACCEL_NEXTWINDOW_F5,
+ UMA_ACCEL_NEXTWINDOW_TAB,
+ UMA_ACCEL_OVERVIEW_F5,
+ UMA_ACCEL_PREVWINDOW_F5,
+ UMA_ACCEL_PREVWINDOW_TAB,
+ UMA_ACCEL_EXIT_FIRST_Q,
+ UMA_ACCEL_EXIT_SECOND_Q,
+ UMA_ACCEL_SEARCH_LWIN,
+ UMA_ACCEL_SHUT_DOWN_POWER_BUTTON,
+ UMA_CLOSE_THROUGH_CONTEXT_MENU,
+ UMA_LAUNCHER_CLICK_ON_APP,
+ UMA_LAUNCHER_CLICK_ON_APPLIST_BUTTON,
+ UMA_MINIMIZE_PER_KEY,
+ UMA_MOUSE_DOWN,
+ UMA_TOGGLE_MAXIMIZE_CAPTION_CLICK,
+ UMA_TOGGLE_MAXIMIZE_CAPTION_GESTURE,
+ UMA_TOUCHSCREEN_TAP_DOWN,
+ UMA_TRAY_HELP,
+ UMA_TRAY_LOCK_SCREEN,
+ UMA_TRAY_SHUT_DOWN,
+ UMA_WINDOW_APP_CLOSE_BUTTON_CLICK,
+ UMA_WINDOW_CLOSE_BUTTON_CLICK,
+ UMA_WINDOW_MAXIMIZE_BUTTON_CLICK_EXIT_FULLSCREEN,
+ UMA_WINDOW_MAXIMIZE_BUTTON_CLICK_MAXIMIZE,
+ UMA_WINDOW_MAXIMIZE_BUTTON_CLICK_MINIMIZE,
+ UMA_WINDOW_MAXIMIZE_BUTTON_CLICK_RESTORE,
+ UMA_WINDOW_MAXIMIZE_BUTTON_MAXIMIZE,
+ UMA_WINDOW_MAXIMIZE_BUTTON_MAXIMIZE_LEFT,
+ UMA_WINDOW_MAXIMIZE_BUTTON_MAXIMIZE_RIGHT,
+ UMA_WINDOW_MAXIMIZE_BUTTON_MINIMIZE,
+ UMA_WINDOW_MAXIMIZE_BUTTON_RESTORE,
+ UMA_WINDOW_MAXIMIZE_BUTTON_SHOW_BUBBLE,
+};
+
+enum AccessibilityNotificationVisibility {
+ A11Y_NOTIFICATION_NONE,
+ A11Y_NOTIFICATION_SHOW,
+};
+
+// Delegate of the Shell.
+class ASH_EXPORT ShellDelegate {
+ public:
+ // The Shell owns the delegate.
+ virtual ~ShellDelegate() {}
+
+ // Returns true if this is the first time that the shell has been run after
+ // the system has booted. false is returned after the shell has been
+ // restarted, typically due to logging in as a guest or logging out.
+ virtual bool IsFirstRunAfterBoot() const = 0;
+
+ // Returns true if multi-profiles feature is enabled.
+ virtual bool IsMultiProfilesEnabled() const = 0;
+
+ // Returns true if we're running in forced app mode.
+ virtual bool IsRunningInForcedAppMode() const = 0;
+
+ // Called before processing |Shell::Init()| so that the delegate
+ // can perform tasks necessary before the shell is initialized.
+ virtual void PreInit() = 0;
+
+ // Shuts down the environment.
+ virtual void Shutdown() = 0;
+
+ // Invoked when the user uses Ctrl-Shift-Q to close chrome.
+ virtual void Exit() = 0;
+
+ // Invoked when the user uses Ctrl+T to open a new tab.
+ virtual void NewTab() = 0;
+
+ // Invoked when the user uses Ctrl-N or Ctrl-Shift-N to open a new window.
+ virtual void NewWindow(bool incognito) = 0;
+
+ // Invoked when the user uses Shift+F4 to toggle the window fullscreen state.
+ virtual void ToggleFullscreen() = 0;
+
+ // Invoked when the user uses F4 to toggle window maximized state.
+ virtual void ToggleMaximized() = 0;
+
+ // Invoked when an accelerator is used to open the file manager.
+ virtual void OpenFileManager(bool as_dialog) = 0;
+
+ // Invoked when the user opens Crosh.
+ virtual void OpenCrosh() = 0;
+
+ // Invoked when the user uses Shift+Ctrl+T to restore the closed tab.
+ virtual void RestoreTab() = 0;
+
+ // Shows the keyboard shortcut overlay.
+ virtual void ShowKeyboardOverlay() = 0;
+
+ // Create a shell-specific keyboard::KeyboardControllerProxy
+ virtual keyboard::KeyboardControllerProxy*
+ CreateKeyboardControllerProxy() = 0;
+
+ // Shows the task manager window.
+ virtual void ShowTaskManager() = 0;
+
+ // Get the current browser context. This will get us the current profile.
+ virtual content::BrowserContext* GetCurrentBrowserContext() = 0;
+
+ // Invoked to toggle spoken feedback for accessibility
+ virtual void ToggleSpokenFeedback(
+ AccessibilityNotificationVisibility notify) = 0;
+
+ // Returns true if spoken feedback is enabled.
+ virtual bool IsSpokenFeedbackEnabled() const = 0;
+
+ // Invoked to toggle high contrast for accessibility.
+ virtual void ToggleHighContrast() = 0;
+
+ // Returns true if high contrast mode is enabled.
+ virtual bool IsHighContrastEnabled() const = 0;
+
+ // Invoked to enable the screen magnifier.
+ virtual void SetMagnifierEnabled(bool enabled) = 0;
+
+ // Invoked to change the type of the screen magnifier.
+ virtual void SetMagnifierType(MagnifierType type) = 0;
+
+ // Returns if the screen magnifier is enabled or not.
+ virtual bool IsMagnifierEnabled() const = 0;
+
+ // Returns the current screen magnifier mode.
+ virtual MagnifierType GetMagnifierType() const = 0;
+
+ // Invoked to enable Large Cursor.
+ virtual void SetLargeCursorEnabled(bool enabled) = 0;
+
+ // Returns if Large Cursor is enabled or not.
+ virtual bool IsLargeCursorEnabled() const = 0;
+
+ // Returns true if the user want to show accesibility menu even when all the
+ // accessibility features are disabled.
+ virtual bool ShouldAlwaysShowAccessibilityMenu() const = 0;
+
+ // Cancel all current and queued speech immediately.
+ virtual void SilenceSpokenFeedback() const = 0;
+
+ // Invoked to create an AppListViewDelegate. Shell takes the ownership of
+ // the created delegate.
+ virtual app_list::AppListViewDelegate* CreateAppListViewDelegate() = 0;
+
+ // Creates a new LauncherDelegate. Shell takes ownership of the returned
+ // value.
+ virtual LauncherDelegate* CreateLauncherDelegate(
+ ash::LauncherModel* model) = 0;
+
+ // Creates a system-tray delegate. Shell takes ownership of the delegate.
+ virtual SystemTrayDelegate* CreateSystemTrayDelegate() = 0;
+
+ // Creates a user wallpaper delegate. Shell takes ownership of the delegate.
+ virtual UserWallpaperDelegate* CreateUserWallpaperDelegate() = 0;
+
+ // Creates a caps lock delegate. Shell takes ownership of the delegate.
+ virtual CapsLockDelegate* CreateCapsLockDelegate() = 0;
+
+ // Creates a session state delegate. Shell takes ownership of the delegate.
+ virtual SessionStateDelegate* CreateSessionStateDelegate() = 0;
+
+ // Creates a user action client. Shell takes ownership of the object.
+ virtual aura::client::UserActionClient* CreateUserActionClient() = 0;
+
+ // Opens the feedback page for "Report Issue".
+ virtual void OpenFeedbackPage() = 0;
+
+ // Records that the user performed an action.
+ virtual void RecordUserMetricsAction(UserMetricsAction action) = 0;
+
+ // Handles the Next Track Media shortcut key.
+ virtual void HandleMediaNextTrack() = 0;
+
+ // Handles the Play/Pause Toggle Media shortcut key.
+ virtual void HandleMediaPlayPause() = 0;
+
+ // Handles the Previous Track Media shortcut key.
+ virtual void HandleMediaPrevTrack() = 0;
+
+ // Saves the zoom scale of the full screen magnifier.
+ virtual void SaveScreenMagnifierScale(double scale) = 0;
+
+ // Gets a saved value of the zoom scale of full screen magnifier. If a value
+ // is not saved, return a negative value.
+ virtual double GetSavedScreenMagnifierScale() = 0;
+
+ // Creates a menu model of the context for the |root_window|.
+ virtual ui::MenuModel* CreateContextMenu(aura::RootWindow* root_window) = 0;
+
+ // Creates a root window host factory. Shell takes ownership of the returned
+ // value.
+ virtual RootWindowHostFactory* CreateRootWindowHostFactory() = 0;
+
+ // Get the product name.
+ virtual base::string16 GetProductName() const = 0;
+};
+
+} // namespace ash
+
+#endif // ASH_SHELL_DELEGATE_H_
diff --git a/chromium/ash/shell_factory.h b/chromium/ash/shell_factory.h
new file mode 100644
index 00000000000..401a54fc7b7
--- /dev/null
+++ b/chromium/ash/shell_factory.h
@@ -0,0 +1,37 @@
+// Copyright (c) 2012 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 ASH_SHELL_FACTORY_H_
+#define ASH_SHELL_FACTORY_H_
+
+#include "ash/ash_export.h"
+
+namespace aura {
+class RootWindow;
+}
+
+namespace gfx {
+class ImageSkia;
+}
+
+namespace views {
+class View;
+class Widget;
+}
+
+// Declarations of shell component factory functions.
+
+namespace ash {
+
+namespace internal {
+views::Widget* CreateDesktopBackground(aura::RootWindow* root_window,
+ int container_id);
+
+ASH_EXPORT views::Widget* CreateStatusArea(views::View* contents);
+} // namespace internal
+
+} // namespace ash
+
+
+#endif // ASH_SHELL_FACTORY_H_
diff --git a/chromium/ash/shell_observer.h b/chromium/ash/shell_observer.h
new file mode 100644
index 00000000000..060e30afc4b
--- /dev/null
+++ b/chromium/ash/shell_observer.h
@@ -0,0 +1,44 @@
+// Copyright (c) 2012 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 ASH_SHELL_OBSERVER_H_
+#define ASH_SHELL_OBSERVER_H_
+
+#include "ash/ash_export.h"
+#include "ash/system/user/login_status.h"
+
+namespace aura {
+class RootWindow;
+}
+
+namespace ash {
+
+class ASH_EXPORT ShellObserver {
+ public:
+ // Invoked after the screen's work area insets changes.
+ virtual void OnDisplayWorkAreaInsetsChanged() {}
+
+ // Invoked when the user logs in.
+ virtual void OnLoginStateChanged(user::LoginStatus status) {}
+
+ // Invoked when the application is exiting.
+ virtual void OnAppTerminating() {}
+
+ // Invoked when the screen is locked (after the lock window is visible) or
+ // unlocked.
+ virtual void OnLockStateChanged(bool locked) {}
+
+ // Invoked when the shelf alignment in |root_window| is changed.
+ virtual void OnShelfAlignmentChanged(aura::RootWindow* root_window) {}
+
+ // Invoked when the projection touch HUD is toggled.
+ virtual void OnTouchHudProjectionToggled(bool enabled) {}
+
+ protected:
+ virtual ~ShellObserver() {}
+};
+
+} // namespace ash
+
+#endif // ASH_SHELL_OBSERVER_H_
diff --git a/chromium/ash/shell_unittest.cc b/chromium/ash/shell_unittest.cc
new file mode 100644
index 00000000000..842eb5dcd2e
--- /dev/null
+++ b/chromium/ash/shell_unittest.cc
@@ -0,0 +1,463 @@
+// Copyright (c) 2012 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.
+
+#include "ash/shell.h"
+
+#include <algorithm>
+#include <vector>
+
+#include "ash/ash_switches.h"
+#include "ash/desktop_background/desktop_background_widget_controller.h"
+#include "ash/display/mouse_cursor_event_filter.h"
+#include "ash/drag_drop/drag_drop_controller.h"
+#include "ash/launcher/launcher.h"
+#include "ash/root_window_controller.h"
+#include "ash/session_state_delegate.h"
+#include "ash/shelf/shelf_layout_manager.h"
+#include "ash/shelf/shelf_widget.h"
+#include "ash/shell_window_ids.h"
+#include "ash/test/ash_test_base.h"
+#include "ash/test/shell_test_api.h"
+#include "ash/wm/root_window_layout_manager.h"
+#include "ash/wm/window_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "ui/aura/client/aura_constants.h"
+#include "ui/aura/root_window.h"
+#include "ui/aura/window.h"
+#include "ui/gfx/size.h"
+#include "ui/views/widget/widget.h"
+#include "ui/views/widget/widget_delegate.h"
+#include "ui/views/window/dialog_delegate.h"
+
+using aura::RootWindow;
+
+namespace ash {
+
+namespace {
+
+aura::Window* GetDefaultContainer() {
+ return Shell::GetContainer(
+ Shell::GetPrimaryRootWindow(),
+ internal::kShellWindowId_DefaultContainer);
+}
+
+aura::Window* GetAlwaysOnTopContainer() {
+ return Shell::GetContainer(
+ Shell::GetPrimaryRootWindow(),
+ internal::kShellWindowId_AlwaysOnTopContainer);
+}
+
+// Expect ALL the containers!
+void ExpectAllContainers() {
+ aura::RootWindow* root_window = Shell::GetPrimaryRootWindow();
+ EXPECT_TRUE(Shell::GetContainer(
+ root_window, internal::kShellWindowId_DesktopBackgroundContainer));
+ EXPECT_TRUE(Shell::GetContainer(
+ root_window, internal::kShellWindowId_DefaultContainer));
+ EXPECT_TRUE(Shell::GetContainer(
+ root_window, internal::kShellWindowId_AlwaysOnTopContainer));
+ EXPECT_TRUE(Shell::GetContainer(
+ root_window, internal::kShellWindowId_PanelContainer));
+ EXPECT_TRUE(Shell::GetContainer(
+ root_window, internal::kShellWindowId_ShelfContainer));
+ EXPECT_TRUE(Shell::GetContainer(
+ root_window, internal::kShellWindowId_SystemModalContainer));
+ EXPECT_TRUE(Shell::GetContainer(
+ root_window, internal::kShellWindowId_LockScreenBackgroundContainer));
+ EXPECT_TRUE(Shell::GetContainer(
+ root_window, internal::kShellWindowId_LockScreenContainer));
+ EXPECT_TRUE(Shell::GetContainer(
+ root_window, internal::kShellWindowId_LockSystemModalContainer));
+ EXPECT_TRUE(Shell::GetContainer(
+ root_window, internal::kShellWindowId_StatusContainer));
+ EXPECT_TRUE(Shell::GetContainer(
+ root_window, internal::kShellWindowId_MenuContainer));
+ EXPECT_TRUE(Shell::GetContainer(
+ root_window, internal::kShellWindowId_DragImageAndTooltipContainer));
+ EXPECT_TRUE(Shell::GetContainer(
+ root_window, internal::kShellWindowId_SettingBubbleContainer));
+ EXPECT_TRUE(Shell::GetContainer(
+ root_window, internal::kShellWindowId_OverlayContainer));
+}
+
+class ModalWindow : public views::WidgetDelegateView {
+ public:
+ ModalWindow() {}
+ virtual ~ModalWindow() {}
+
+ // Overridden from views::WidgetDelegate:
+ virtual views::View* GetContentsView() OVERRIDE {
+ return this;
+ }
+ virtual bool CanResize() const OVERRIDE {
+ return true;
+ }
+ virtual base::string16 GetWindowTitle() const OVERRIDE {
+ return ASCIIToUTF16("Modal Window");
+ }
+ virtual ui::ModalType GetModalType() const OVERRIDE {
+ return ui::MODAL_TYPE_SYSTEM;
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ModalWindow);
+};
+
+} // namespace
+
+class ShellTest : public test::AshTestBase {
+ public:
+ views::Widget* CreateTestWindow(views::Widget::InitParams params) {
+ views::Widget* widget = new views::Widget;
+ params.context = CurrentContext();
+ widget->Init(params);
+ return widget;
+ }
+
+ void TestCreateWindow(views::Widget::InitParams::Type type,
+ bool always_on_top,
+ aura::Window* expected_container) {
+ views::Widget::InitParams widget_params(type);
+ widget_params.keep_on_top = always_on_top;
+
+ views::Widget* widget = CreateTestWindow(widget_params);
+ widget->Show();
+
+ EXPECT_TRUE(
+ expected_container->Contains(widget->GetNativeWindow()->parent())) <<
+ "TestCreateWindow: type=" << type << ", always_on_top=" <<
+ always_on_top;
+
+ widget->Close();
+}
+
+};
+
+TEST_F(ShellTest, CreateWindow) {
+ // Normal window should be created in default container.
+ TestCreateWindow(views::Widget::InitParams::TYPE_WINDOW,
+ false, // always_on_top
+ GetDefaultContainer());
+ TestCreateWindow(views::Widget::InitParams::TYPE_POPUP,
+ false, // always_on_top
+ GetDefaultContainer());
+
+ // Always-on-top window and popup are created in always-on-top container.
+ TestCreateWindow(views::Widget::InitParams::TYPE_WINDOW,
+ true, // always_on_top
+ GetAlwaysOnTopContainer());
+ TestCreateWindow(views::Widget::InitParams::TYPE_POPUP,
+ true, // always_on_top
+ GetAlwaysOnTopContainer());
+}
+
+TEST_F(ShellTest, ChangeAlwaysOnTop) {
+ views::Widget::InitParams widget_params(
+ views::Widget::InitParams::TYPE_WINDOW);
+
+ // Creates a normal window
+ views::Widget* widget = CreateTestWindow(widget_params);
+ widget->Show();
+
+ // It should be in default container.
+ EXPECT_TRUE(GetDefaultContainer()->Contains(
+ widget->GetNativeWindow()->parent()));
+
+ // Flip always-on-top flag.
+ widget->SetAlwaysOnTop(true);
+ // And it should in always on top container now.
+ EXPECT_EQ(GetAlwaysOnTopContainer(), widget->GetNativeWindow()->parent());
+
+ // Flip always-on-top flag.
+ widget->SetAlwaysOnTop(false);
+ // It should go back to default container.
+ EXPECT_TRUE(GetDefaultContainer()->Contains(
+ widget->GetNativeWindow()->parent()));
+
+ // Set the same always-on-top flag again.
+ widget->SetAlwaysOnTop(false);
+ // Should have no effect and we are still in the default container.
+ EXPECT_TRUE(GetDefaultContainer()->Contains(
+ widget->GetNativeWindow()->parent()));
+
+ widget->Close();
+}
+
+TEST_F(ShellTest, CreateModalWindow) {
+ views::Widget::InitParams widget_params(
+ views::Widget::InitParams::TYPE_WINDOW);
+
+ // Create a normal window.
+ views::Widget* widget = CreateTestWindow(widget_params);
+ widget->Show();
+
+ // It should be in default container.
+ EXPECT_TRUE(GetDefaultContainer()->Contains(
+ widget->GetNativeWindow()->parent()));
+
+ // Create a modal window.
+ views::Widget* modal_widget = views::Widget::CreateWindowWithParent(
+ new ModalWindow(), widget->GetNativeView());
+ modal_widget->Show();
+
+ // It should be in modal container.
+ aura::Window* modal_container = Shell::GetContainer(
+ Shell::GetPrimaryRootWindow(),
+ internal::kShellWindowId_SystemModalContainer);
+ EXPECT_EQ(modal_container, modal_widget->GetNativeWindow()->parent());
+
+ modal_widget->Close();
+ widget->Close();
+}
+
+class TestModalDialogDelegate : public views::DialogDelegateView {
+ public:
+ TestModalDialogDelegate() {}
+
+ // Overridden from views::WidgetDelegate:
+ virtual ui::ModalType GetModalType() const OVERRIDE {
+ return ui::MODAL_TYPE_SYSTEM;
+ }
+};
+
+TEST_F(ShellTest, CreateLockScreenModalWindow) {
+ views::Widget::InitParams widget_params(
+ views::Widget::InitParams::TYPE_WINDOW);
+
+ // Create a normal window.
+ views::Widget* widget = CreateTestWindow(widget_params);
+ widget->Show();
+ EXPECT_TRUE(widget->GetNativeView()->HasFocus());
+
+ // It should be in default container.
+ EXPECT_TRUE(GetDefaultContainer()->Contains(
+ widget->GetNativeWindow()->parent()));
+
+ Shell::GetInstance()->session_state_delegate()->LockScreen();
+ // Create a LockScreen window.
+ views::Widget* lock_widget = CreateTestWindow(widget_params);
+ ash::Shell::GetContainer(
+ Shell::GetPrimaryRootWindow(),
+ ash::internal::kShellWindowId_LockScreenContainer)->
+ AddChild(lock_widget->GetNativeView());
+ lock_widget->Show();
+ EXPECT_TRUE(lock_widget->GetNativeView()->HasFocus());
+
+ // It should be in LockScreen container.
+ aura::Window* lock_screen = Shell::GetContainer(
+ Shell::GetPrimaryRootWindow(),
+ ash::internal::kShellWindowId_LockScreenContainer);
+ EXPECT_EQ(lock_screen, lock_widget->GetNativeWindow()->parent());
+
+ // Create a modal window with a lock window as parent.
+ views::Widget* lock_modal_widget = views::Widget::CreateWindowWithParent(
+ new ModalWindow(), lock_widget->GetNativeView());
+ lock_modal_widget->Show();
+ EXPECT_TRUE(lock_modal_widget->GetNativeView()->HasFocus());
+
+ // It should be in LockScreen modal container.
+ aura::Window* lock_modal_container = Shell::GetContainer(
+ Shell::GetPrimaryRootWindow(),
+ ash::internal::kShellWindowId_LockSystemModalContainer);
+ EXPECT_EQ(lock_modal_container,
+ lock_modal_widget->GetNativeWindow()->parent());
+
+ // Create a modal window with a normal window as parent.
+ views::Widget* modal_widget = views::Widget::CreateWindowWithParent(
+ new ModalWindow(), widget->GetNativeView());
+ modal_widget->Show();
+ // Window on lock screen shouldn't lost focus.
+ EXPECT_FALSE(modal_widget->GetNativeView()->HasFocus());
+ EXPECT_TRUE(lock_modal_widget->GetNativeView()->HasFocus());
+
+ // It should be in non-LockScreen modal container.
+ aura::Window* modal_container = Shell::GetContainer(
+ Shell::GetPrimaryRootWindow(),
+ ash::internal::kShellWindowId_SystemModalContainer);
+ EXPECT_EQ(modal_container, modal_widget->GetNativeWindow()->parent());
+
+ // Modal dialog without parent, caused crash see crbug.com/226141
+ views::Widget* modal_dialog = views::DialogDelegate::CreateDialogWidget(
+ new TestModalDialogDelegate(), CurrentContext(), NULL);
+
+ modal_dialog->Show();
+ EXPECT_FALSE(modal_dialog->GetNativeView()->HasFocus());
+ EXPECT_TRUE(lock_modal_widget->GetNativeView()->HasFocus());
+
+ modal_dialog->Close();
+ modal_widget->Close();
+ modal_widget->Close();
+ lock_modal_widget->Close();
+ lock_widget->Close();
+ widget->Close();
+}
+
+TEST_F(ShellTest, IsScreenLocked) {
+ SessionStateDelegate* delegate =
+ Shell::GetInstance()->session_state_delegate();
+ delegate->LockScreen();
+ EXPECT_TRUE(delegate->IsScreenLocked());
+ delegate->UnlockScreen();
+ EXPECT_FALSE(delegate->IsScreenLocked());
+}
+
+// Fails on Mac, see http://crbug.com/115662
+#if defined(OS_MACOSX)
+#define MAYBE_ManagedWindowModeBasics DISABLED_ManagedWindowModeBasics
+#else
+#define MAYBE_ManagedWindowModeBasics ManagedWindowModeBasics
+#endif
+TEST_F(ShellTest, MAYBE_ManagedWindowModeBasics) {
+ Shell* shell = Shell::GetInstance();
+ Shell::TestApi test_api(shell);
+
+ // We start with the usual window containers.
+ ExpectAllContainers();
+ // Launcher is visible.
+ ShelfWidget* launcher_widget = Launcher::ForPrimaryDisplay()->shelf_widget();
+ EXPECT_TRUE(launcher_widget->IsVisible());
+ // Launcher is at bottom-left of screen.
+ EXPECT_EQ(0, launcher_widget->GetWindowBoundsInScreen().x());
+ EXPECT_EQ(Shell::GetPrimaryRootWindow()->GetHostSize().height(),
+ launcher_widget->GetWindowBoundsInScreen().bottom());
+ // We have a desktop background but not a bare layer.
+ // TODO (antrim): enable once we find out why it fails component build.
+ // internal::DesktopBackgroundWidgetController* background =
+ // Shell::GetPrimaryRootWindow()->
+ // GetProperty(internal::kWindowDesktopComponent);
+ // EXPECT_TRUE(background);
+ // EXPECT_TRUE(background->widget());
+ // EXPECT_FALSE(background->layer());
+
+ // Create a normal window. It is not maximized.
+ views::Widget::InitParams widget_params(
+ views::Widget::InitParams::TYPE_WINDOW);
+ widget_params.bounds.SetRect(11, 22, 300, 400);
+ views::Widget* widget = CreateTestWindow(widget_params);
+ widget->Show();
+ EXPECT_FALSE(widget->IsMaximized());
+
+ // Clean up.
+ widget->Close();
+}
+
+TEST_F(ShellTest, FullscreenWindowHidesShelf) {
+ ExpectAllContainers();
+
+ // Create a normal window. It is not maximized.
+ views::Widget::InitParams widget_params(
+ views::Widget::InitParams::TYPE_WINDOW);
+ widget_params.bounds.SetRect(11, 22, 300, 400);
+ views::Widget* widget = CreateTestWindow(widget_params);
+ widget->Show();
+ EXPECT_FALSE(widget->IsMaximized());
+
+ // Shelf defaults to visible.
+ EXPECT_EQ(
+ SHELF_VISIBLE,
+ Shell::GetPrimaryRootWindowController()->
+ GetShelfLayoutManager()->visibility_state());
+
+ // Fullscreen window hides it.
+ widget->SetFullscreen(true);
+ EXPECT_EQ(
+ SHELF_HIDDEN,
+ Shell::GetPrimaryRootWindowController()->
+ GetShelfLayoutManager()->visibility_state());
+
+ // Restoring the window restores it.
+ widget->Restore();
+ EXPECT_EQ(
+ SHELF_VISIBLE,
+ Shell::GetPrimaryRootWindowController()->
+ GetShelfLayoutManager()->visibility_state());
+
+ // Clean up.
+ widget->Close();
+}
+
+namespace {
+
+// Builds the list of parents from |window| to the root. The returned vector is
+// in reverse order (|window| is first).
+std::vector<aura::Window*> BuildPathToRoot(aura::Window* window) {
+ std::vector<aura::Window*> results;
+ while (window) {
+ results.push_back(window);
+ window = window->parent();
+ }
+ return results;
+}
+
+} // namespace
+
+// Various assertions around SetShelfAutoHideBehavior() and
+// GetShelfAutoHideBehavior().
+TEST_F(ShellTest, ToggleAutoHide) {
+ scoped_ptr<aura::Window> window(new aura::Window(NULL));
+ window->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_NORMAL);
+ window->SetType(aura::client::WINDOW_TYPE_NORMAL);
+ window->Init(ui::LAYER_TEXTURED);
+ SetDefaultParentByPrimaryRootWindow(window.get());
+ window->Show();
+ wm::ActivateWindow(window.get());
+
+ Shell* shell = Shell::GetInstance();
+ aura::RootWindow* root_window = Shell::GetPrimaryRootWindow();
+ shell->SetShelfAutoHideBehavior(ash::SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS,
+ root_window);
+ EXPECT_EQ(ash::SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS,
+ shell->GetShelfAutoHideBehavior(root_window));
+ shell->SetShelfAutoHideBehavior(ash::SHELF_AUTO_HIDE_BEHAVIOR_NEVER,
+ root_window);
+ EXPECT_EQ(ash::SHELF_AUTO_HIDE_BEHAVIOR_NEVER,
+ shell->GetShelfAutoHideBehavior(root_window));
+ window->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MAXIMIZED);
+ EXPECT_EQ(ash::SHELF_AUTO_HIDE_BEHAVIOR_NEVER,
+ shell->GetShelfAutoHideBehavior(root_window));
+ shell->SetShelfAutoHideBehavior(ash::SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS,
+ root_window);
+ EXPECT_EQ(ash::SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS,
+ shell->GetShelfAutoHideBehavior(root_window));
+ shell->SetShelfAutoHideBehavior(ash::SHELF_AUTO_HIDE_BEHAVIOR_NEVER,
+ root_window);
+ EXPECT_EQ(ash::SHELF_AUTO_HIDE_BEHAVIOR_NEVER,
+ shell->GetShelfAutoHideBehavior(root_window));
+}
+
+TEST_F(ShellTest, TestPreTargetHandlerOrder) {
+ Shell* shell = Shell::GetInstance();
+ Shell::TestApi test_api(shell);
+ test::ShellTestApi shell_test_api(shell);
+
+ const ui::EventHandlerList& handlers = test_api.pre_target_handlers();
+ EXPECT_EQ(handlers[0], shell->mouse_cursor_filter());
+ EXPECT_EQ(handlers[1], shell_test_api.drag_drop_controller());
+}
+
+// This verifies WindowObservers are removed when a window is destroyed after
+// the Shell is destroyed. This scenario (aura::Windows being deleted after the
+// Shell) occurs if someone is holding a reference to an unparented Window, as
+// is the case with a RenderWidgetHostViewAura that isn't on screen. As long as
+// everything is ok, we won't crash. If there is a bug, window's destructor will
+// notify some deleted object (say VideoDetector or ActivationController) and
+// this will crash.
+class ShellTest2 : public test::AshTestBase {
+ public:
+ ShellTest2() {}
+ virtual ~ShellTest2() {}
+
+ protected:
+ scoped_ptr<aura::Window> window_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ShellTest2);
+};
+
+TEST_F(ShellTest2, DontCrashWhenWindowDeleted) {
+ window_.reset(new aura::Window(NULL));
+ window_->Init(ui::LAYER_NOT_DRAWN);
+}
+
+} // namespace ash
diff --git a/chromium/ash/shell_window_ids.h b/chromium/ash/shell_window_ids.h
new file mode 100644
index 00000000000..1c1c396cbbc
--- /dev/null
+++ b/chromium/ash/shell_window_ids.h
@@ -0,0 +1,107 @@
+// Copyright (c) 2012 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 ASH_SHELL_WINDOW_IDS_H_
+#define ASH_SHELL_WINDOW_IDS_H_
+
+// Declarations of ids of special shell windows.
+
+namespace ash {
+
+// TODO: we're using this in random places outside of ash, it shouldn't be in
+// internal.
+namespace internal {
+
+// A higher-level container that holds all of the containers stacked below
+// kShellWindowId_LockScreenContainer. Only used by PowerButtonController for
+// animating lower-level containers.
+const int kShellWindowId_NonLockScreenContainersContainer = 0;
+
+// A higher-level container that holds containers that hold lock-screen
+// windows. Only used by PowerButtonController for animating lower-level
+// containers.
+const int kShellWindowId_LockScreenContainersContainer = 1;
+
+// A higher-level container that holds containers that hold lock-screen-related
+// windows (which we want to display while the screen is locked; effectively
+// containers stacked above kShellWindowId_LockSystemModalContainer). Only used
+// by PowerButtonController for animating lower-level containers.
+const int kShellWindowId_LockScreenRelatedContainersContainer = 2;
+
+// A container used for windows of WINDOW_TYPE_CONTROL that have no parent.
+// This container is not visible.
+const int kShellWindowId_UnparentedControlContainer = 3;
+
+// The desktop background window.
+const int kShellWindowId_DesktopBackgroundContainer = 4;
+
+// The virtual keyboard container.
+const int kShellWindowId_VirtualKeyboardContainer = 5;
+
+// TODO(sky): rename kShellWindowId_DefaultContainer.
+
+// The container for standard top-level windows.
+const int kShellWindowId_DefaultContainer = 6;
+
+// The container for top-level windows with the 'always-on-top' flag set.
+const int kShellWindowId_AlwaysOnTopContainer = 7;
+
+// The container for windows docked to either side of the desktop.
+const int kShellWindowId_DockedContainer = 8;
+
+// The container for panel windows.
+const int kShellWindowId_PanelContainer = 9;
+
+// The container for the shelf.
+const int kShellWindowId_ShelfContainer = 10;
+
+// The container for the app list.
+const int kShellWindowId_AppListContainer = 11;
+
+// The container for user-specific modal windows.
+const int kShellWindowId_SystemModalContainer = 12;
+
+// The container for input method components such like candidate windows. They
+// are almost panels but have no activations/focus, and they should appear over
+// the AppList and SystemModal dialogs.
+const int kShellWindowId_InputMethodContainer = 13;
+
+// The container for the lock screen background.
+const int kShellWindowId_LockScreenBackgroundContainer = 14;
+
+// The container for the lock screen.
+const int kShellWindowId_LockScreenContainer = 15;
+
+// The container for the lock screen modal windows.
+const int kShellWindowId_LockSystemModalContainer = 16;
+
+// The container for the status area.
+const int kShellWindowId_StatusContainer = 17;
+
+// The container for menus.
+const int kShellWindowId_MenuContainer = 18;
+
+// The container for drag/drop images and tooltips.
+const int kShellWindowId_DragImageAndTooltipContainer = 19;
+
+// The container for bubbles briefly overlaid onscreen to show settings changes
+// (volume, brightness, etc.).
+const int kShellWindowId_SettingBubbleContainer = 20;
+
+// The container for special components overlaid onscreen, such as the
+// region selector for partial screenshots.
+const int kShellWindowId_OverlayContainer = 21;
+
+// ID of the window created by PhantomWindowController or DragWindowController.
+const int kShellWindowId_PhantomWindow = 22;
+
+// The topmost container, used for power off animation.
+const int kShellWindowId_PowerButtonAnimationContainer = 23;
+
+} // namespace internal
+
+} // namespace ash
+
+
+#endif // ASH_SHELL_WINDOW_IDS_H_
diff --git a/chromium/ash/strings/ash_strings_am.xtb b/chromium/ash/strings/ash_strings_am.xtb
new file mode 100644
index 00000000000..b0c6c3803be
--- /dev/null
+++ b/chromium/ash/strings/ash_strings_am.xtb
@@ -0,0 +1,201 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="am">
+<translation id="3595596368722241419">ባትሪ ሙሉ ነው</translation>
+<translation id="5250713215130379958">አስጀማሪን ራስ-ደብቅ</translation>
+<translation id="7814236020522506259"><ph name="HOUR"/> እና <ph name="MINUTE"/></translation>
+<translation id="7880025619322806991">የመተላለፊያ ሁኔታ</translation>
+<translation id="30155388420722288">የትርፍ ፍሰት አዝራር</translation>
+<translation id="5571066253365925590">ብሉቱዝ ነቅቷል</translation>
+<translation id="9074739597929991885">ብሉቱዝ</translation>
+<translation id="2268130516524549846">ብሉቱዝ ተሰናክሏል</translation>
+<translation id="3775358506042162758">በአንድ ጊዜ ብዙ መግባት ላይ እስከ ሶስት መለያዎች ድረስ ብቻ ነው ሊኖርዎ የሚችለው።</translation>
+<translation id="370649949373421643">Wi-Fi ያንቁ</translation>
+<translation id="3626281679859535460">ብሩህነት</translation>
+<translation id="8054466585765276473">የባትሪ ጊዜን በማስላት ላይ።</translation>
+<translation id="7982789257301363584">አውታረ መረብ</translation>
+<translation id="5565793151875479467">ተኪ...</translation>
+<translation id="938582441709398163">የቁልፍ ሰሌዳ ተደራቢ</translation>
+<translation id="4387004326333427325">የማረጋገጫ ምስክር ወረቀት በርቀት ተቀባይነት አላገኘም</translation>
+<translation id="6979158407327259162">Google Drive</translation>
+<translation id="6943836128787782965">ኤች ቲ ቲ ፒ ማግኘት አልተሳካም</translation>
+<translation id="2297568595583585744">የሁኔታ መሳቢያ</translation>
+<translation id="1661867754829461514">ፒን ይጎድላል</translation>
+<translation id="4508225577814909926"><ph name="NAME"/>፦ በመገናኘት ላይ...</translation>
+<translation id="4237016987259239829">የአውታረመረብ ግንኙነት ስህተት</translation>
+<translation id="2946640296642327832">ብሉቱዝን ያንቁ</translation>
+<translation id="6459472438155181876">ማያ ገጽ ወደ <ph name="DISPLAY_NAME"/> በመቀጠል ላይ</translation>
+<translation id="8206859287963243715">ሴሉላር</translation>
+<translation id="6596816719288285829">IP አድራሻ</translation>
+<translation id="4508265954913339219">ማግበር አልተሳካም</translation>
+<translation id="3621712662352432595">የድምጽ ቅንብሮች</translation>
+<translation id="1812696562331527143">የግብዓት ስልትዎ ወደ <ph name="INPUT_METHOD_ID"/> ተቀይሯል*(<ph name="BEGIN_LINK"/>3ኛ ወገን<ph name="END_LINK"/>)።
+ለመቀየር Shift + Alt ይጫኑ።</translation>
+<translation id="2127372758936585790">አነስተኛ ኃይል ያለው ባትሪ መሙያ</translation>
+<translation id="3846575436967432996">ምንም የአውታረ መረብ መረጃ አይገኝም</translation>
+<translation id="3026237328237090306">የተንቀሳቃሽ ስልክ ውሂብ ያዋቅሩ</translation>
+<translation id="785750925697875037">የተንቀሳቃሽ መለያ ይመልከቱ</translation>
+<translation id="153454903766751181">ተንቀሳቃሽ ሞደምን በማስጀመር ላይ...</translation>
+<translation id="4628814525959230255">የማያ ገጽዎ ቁጥጥር በHangouts በኩል ለ<ph name="HELPER_NAME"/> በማጋራት ላይ።</translation>
+<translation id="8343941333792395995"><ph name="DISPLAY_NAME"/> ዞሯል</translation>
+<translation id="7864539943188674973">ብሉቱዝን ያሰናክሉ</translation>
+<translation id="939252827960237676">ቅጽበታዊ ገጽ እይታን ማስቀመጥ አልተቻለም</translation>
+<translation id="3126069444801937830">ለማዘመን ዳግም ያስጀምሩ</translation>
+<translation id="2268813581635650749">ሁሉንም ዘግተህ ውጣ</translation>
+<translation id="735745346212279324">የቪ ፒ ኤን ግንኙነት ተቋርጧል</translation>
+<translation id="7320906967354320621">ስራ ፈት</translation>
+<translation id="6303423059719347535">ባትሪ <ph name="PERCENTAGE"/>% ሙሉ ነው</translation>
+<translation id="15373452373711364">ትልቅ የመዳፊት ጠቋሚ</translation>
+<translation id="2778346081696727092">በተሰጠው የተጠቃሚ ስም ወይም ይለፍ ቃል ማረጋገጥ አልተቻለም</translation>
+<translation id="3294437725009624529">እንግዳ</translation>
+<translation id="8190698733819146287">ቋንቋዎችን እና ግብአቶችን አብጅ...</translation>
+<translation id="2903907270192926896">ግብዓት</translation>
+<translation id="8676770494376880701">አነስተኛ ኃይል ያለው ባትሪ መሙያ ተገናኝቷል</translation>
+<translation id="7170041865419449892">ከክልል ውጪ</translation>
+<translation id="4804818685124855865">ግንኙነት አቋርጥ</translation>
+<translation id="2544853746127077729">የማረጋገጫ ምስክር ወረቀት በአውታረ መረቡ ተቀባይነት አላገኘም</translation>
+<translation id="5222676887888702881">ዘግተህ ውጣ</translation>
+<translation id="2688477613306174402">ውቅር</translation>
+<translation id="1272079795634619415">አቁም</translation>
+<translation id="4957722034734105353">ተጨማሪ ለመረዳት...</translation>
+<translation id="2964193600955408481">Wi-Fiን አሰናክል</translation>
+<translation id="811680302244032017">መሣሪያ ያክሉ...</translation>
+<translation id="4279490309300973883">በማንጸባረቅ ላይ</translation>
+<translation id="2509468283778169019">CAPS LOCK በርቷል</translation>
+<translation id="3892641579809465218">የውስጥ ማሳያ</translation>
+<translation id="7823564328645135659">ቅንብሮችዎ ከተመሳሰሉ በኋላ ቋንቋው ከ«<ph name="FROM_LOCALE"/> ወደ «<ph name="TO_LOCALE"/>» ተቀይሯል።</translation>
+<translation id="3368922792935385530">ተያይዟል</translation>
+<translation id="8340999562596018839">የተነገረ ግብረ መልስ</translation>
+<translation id="8654520615680304441">Wi-Fi አብራ...</translation>
+<translation id="5825747213122829519">የግቤት ስልትዎ ወደ <ph name="INPUT_METHOD_ID"/> ተቀይሯል።
+ለመቀየር Shift + Alt ይጫኑ።</translation>
+<translation id="2562916301614567480">የግል አውታረ መረብ</translation>
+<translation id="6549021752953852991">ምንም የተንቀሳቃሽ ሞደም አውታረ መረብ አይገኝም</translation>
+<translation id="4379753398862151997">ውድ ማሳያ፣ ልንግባባ አልቻልንም። (ያ ማሳያ አይደገፍም)</translation>
+<translation id="6426039856985689743">የተንቀሳቃሽ ስልክ ውሂብን ያሰናክሉ</translation>
+<translation id="3087734570205094154">ግርጌ</translation>
+<translation id="3742055079367172538">ቅጽበታዊ ገጽ እይታ ተነስቷል</translation>
+<translation id="8878886163241303700">ማያ ገጽ በማስቀጠል ላይ</translation>
+<translation id="5271016907025319479">VPN አልተዋቀረም።</translation>
+<translation id="372094107052732682">ለመውጣት Ctrl+Shift+Qን ይጫኑ</translation>
+<translation id="6803622936009808957">ምንም የሚደገፉ ጥራቶች ስላልተገኙ ማሳያዎችን ማሳየት አልተቻለም። ይልቁንስ ወደ የተስፋፋ ዴስክቶፕ ሁነታ ተገብቷል።</translation>
+<translation id="1480041086352807611">የማሳያ ሁነታ</translation>
+<translation id="3626637461649818317"><ph name="PERCENTAGE"/>% ይቀራል</translation>
+<translation id="9089416786594320554">የግቤት ስልቶች</translation>
+<translation id="6247708409970142803"><ph name="PERCENTAGE"/>%</translation>
+<translation id="2614835198358683673">የእርስዎ Chromebook በርቶ ሳለ ባትሪ ላይሞላ ይችላል። ዋናውን ባትሪ መሙያ መጠቀሙን ያስቡበት።</translation>
+<translation id="1895658205118569222">አጥፋ</translation>
+<translation id="4430019312045809116">ድምፅ</translation>
+<translation id="4442424173763614572">የዲ ኤን ኤስ ፍለጋ አልተሳካም</translation>
+<translation id="6356500677799115505">ባትሪ ሙሉ እና ኃይል እየሞላ ነው።</translation>
+<translation id="7874779702599364982">የድምጸ ተያያዥ ሞደም አውታረ መረቦችን በመፈለግ ላይ...</translation>
+<translation id="583281660410589416">ያልታወቀ</translation>
+<translation id="1383876407941801731">ፍለጋ</translation>
+<translation id="7468789844759750875">ተጨማሪ ውሂብ ለመግዛት የ<ph name="NAME"/> ማስገበሪያ መተላለፊያውን ይጎብኙ።</translation>
+<translation id="3901991538546252627">ከ<ph name="NAME"/> ጋር በመገናኘት ላይ</translation>
+<translation id="2204305834655267233">የአውታረ መረብ መረጃ</translation>
+<translation id="1621499497873603021">ባትሪ ባዶ እስኪሆን ድረስ የቀረው ጊዜ፣ <ph name="TIME_LEFT"/></translation>
+<translation id="5980301590375426705">ከእንግዳ ውጣ</translation>
+<translation id="4471417012762451363">ባትሪ <ph name="PERCENTAGE"/>% ሙሉ እና ኃይል እየሞላ ነው</translation>
+<translation id="8308637677604853869">ቀዳሚ ምናሌ</translation>
+<translation id="4666297444214622512">ወደ ሌላ መለያ መግባት አይቻልም።</translation>
+<translation id="1346748346194534595">ቀኝ</translation>
+<translation id="1773212559869067373">የማረጋገጫ ምስክር ወረቀት በአካባቢው ተቀባይነት አላገኘም</translation>
+<translation id="8528322925433439945">ተንቀሳቃሽ ስልክ ...</translation>
+<translation id="7049357003967926684">ማህበር</translation>
+<translation id="8428213095426709021">ቅንብሮች</translation>
+<translation id="2372145515558759244">መተግበሪያዎችን በማመሳሰል ላይ...</translation>
+<translation id="7256405249507348194">ያልታወቀ ስህተት፦ <ph name="DESC"/></translation>
+<translation id="7925247922861151263">የAAA ማረጋገጥ አልተሳካም</translation>
+<translation id="8456362689280298700"><ph name="HOUR"/>:<ph name="MINUTE"/> ሙሉ እስኪሆን ድረስ</translation>
+<translation id="5787281376604286451">የተነገረ ግብረ መልስ ነቅቷል።
+ለማሰናከል Ctrl+Alt+Z ይጫኑ።</translation>
+<translation id="4479639480957787382">ኢተርኔት</translation>
+<translation id="6312403991423642364">ያልታወቀ የአውታረ መረብ ስህተት</translation>
+<translation id="1467432559032391204">ግራ</translation>
+<translation id="5543001071567407895">ኤስ ኤም ኤስ</translation>
+<translation id="2354174487190027830"><ph name="NAME"/>ን በማግበር ላይ</translation>
+<translation id="8814190375133053267">Wi-Fi</translation>
+<translation id="1398853756734560583">አስፋ</translation>
+<translation id="2692809339924654275"><ph name="BLUETOOTH"/>፦ በመገናኘት ላይ...</translation>
+<translation id="252373100621549798">ያልታወቀ ማሳያ</translation>
+<translation id="1882897271359938046">ወደ <ph name="DISPLAY_NAME"/> በማንጸባረቅ ላይ</translation>
+<translation id="2727977024730340865">አነስተኛ ኃይል ወዳለው ባትሪ መሙያ ተሰክቷል። የባትሪ መሙላት አስተማማኝ ላይሆን ይችላል።</translation>
+<translation id="3784455785234192852">ቆልፍ</translation>
+<translation id="2805756323405976993">መተግበሪያዎች</translation>
+<translation id="8871072142849158571">የ<ph name="DISPLAY_NAME"/> መጠን ወደ <ph name="RESOLUTION"/> ተቀይሯል</translation>
+<translation id="1512064327686280138">የማግበር አለመሳካት</translation>
+<translation id="5097002363526479830">ከአውታረ መረብ «<ph name="NAME"/>» ጋር መገናኘት አልተሳካም፦ <ph name="DETAILS"/></translation>
+<translation id="1850504506766569011">Wi-Fi ጠፍቷል።</translation>
+<translation id="8132793192354020517">ከ<ph name="NAME"/> ጋር ተገናኝቷል</translation>
+<translation id="7052914147756339792">ልጣፍ አዘጋጅ...</translation>
+<translation id="8678698760965522072">የመስመር ላይ ሁኔታ</translation>
+<translation id="2532589005999780174">ባለከፍተኛ ንፅፅር ሁነታ</translation>
+<translation id="1119447706177454957">ውስጣዊ ስህተት</translation>
+<translation id="3019353588588144572">ባትሪ ሙሉ ኃይል እስኪኖረው ድረስ የሚቀረው ጊዜ፣ <ph name="TIME_REMAINING"/></translation>
+<translation id="3473479545200714844">የማያ ገጽ ማጉያ</translation>
+<translation id="7005812687360380971">አለመሳካት</translation>
+<translation id="882279321799040148">ለማየት ጠቅ ያድርጉ</translation>
+<translation id="5045550434625856497">ትክክል ያልሆነ የይለፍ ቃል</translation>
+<translation id="1602076796624386989">የተንቀሳቃሽ ስልክ ውሂብን ያንቁ</translation>
+<translation id="6981982820502123353">ተደራሽነት</translation>
+<translation id="3157931365184549694">እነበረበት መልስ</translation>
+<translation id="4274292172790327596">ያልታወቀ ስህተት</translation>
+<translation id="4032485810211612751"><ph name="HOURS"/>:<ph name="MINUTES"/>:<ph name="SECONDS"/></translation>
+<translation id="225680501294068881">መሣሪያዎችን በመቃኘት ላይ...</translation>
+<translation id="5597451508971090205"><ph name="SHORT_WEEKDAY"/>፣ <ph name="DATE"/></translation>
+<translation id="4448844063988177157">የWi-Fi አውታረ መረቦችን በመፈለግ ላይ…</translation>
+<translation id="8401662262483418323">ከ«<ph name="NAME"/>» ጋር መገናኘት አልተቻለም፦ <ph name="DETAILS"/>
+የአገልጋይ መልዕክት፦ <ph name="SERVER_MSG"/></translation>
+<translation id="2475982808118771221">ስህተት ተከስቷል</translation>
+<translation id="7229570126336867161">ኢቪዲኦ ያስፈልጋል</translation>
+<translation id="2999742336789313416"><ph name="DISPLAY_NAME"/> በ<ph name="DOMAIN"/> የሚቀናበር ይፋዊ ክፍለ ጊዜ ነው</translation>
+<translation id="7029814467594812963">ከክፍለ-ጊዜ ውጣ</translation>
+<translation id="8454013096329229812">Wi-Fi በርቷል።</translation>
+<translation id="4872237917498892622">Alt+Search ወይም Shift</translation>
+<translation id="2983818520079887040">ቅንብሮች ...</translation>
+<translation id="1717216362413677834">የመትከያ ሁነታ</translation>
+<translation id="8927026611342028580">መገናኘት ተጠይቋል</translation>
+<translation id="8300849813060516376">OTASP አልተሳካም</translation>
+<translation id="2792498699870441125">Alt+Search</translation>
+<translation id="8660803626959853127"><ph name="COUNT"/> ፋይል/ፋይሎችን በማመሳሰል ላይ</translation>
+<translation id="3709443003275901162">9+</translation>
+<translation id="639644700271529076">CAPS LOCK ጠፍቷል</translation>
+<translation id="6248847161401822652">ለመተው Control Shift Qን ሁለት ጊዜ ይጫኑ።</translation>
+<translation id="6267036997247669271"><ph name="NAME"/>፦ በማግበር ላይ...</translation>
+<translation id="1391854757121130358">የተንቀሳቃሽ ስልክ ውሂብ ገደብዎን ጨርሰው ሊሆን ይችላል።</translation>
+<translation id="5413208160176941586">በአካባቢው የሚተዳደር ተጠቃሚ</translation>
+<translation id="1059194134494239015"><ph name="DISPLAY_NAME"/>፦ <ph name="RESOLUTION"/></translation>
+<translation id="4864165860509564259">የአስጀማሪው ቦታ</translation>
+<translation id="7593891976182323525">Search ወይም Shift</translation>
+<translation id="7649070708921625228">እገዛ</translation>
+<translation id="3050422059534974565">CAPS LOCK በርቷል።
+ለመሰረዝ Search ወይም Shift ይጫኑ።</translation>
+<translation id="397105322502079400">በማስላት ላይ...</translation>
+<translation id="158849752021629804">የቤት አውታረ መረብ ያስፈልጋል</translation>
+<translation id="6857811139397017780"><ph name="NETWORKSERVICE"/>ን አግብር</translation>
+<translation id="5864471791310927901">የDHCP ፍለጋ አልተሳካም</translation>
+<translation id="5812035014844949013">ውፅዓት</translation>
+<translation id="6692173217867674490">መጥፎ የይለፍ ሐረግ</translation>
+<translation id="6165508094623778733">ተጨማሪ ለመረዳት</translation>
+<translation id="9046895021617826162">ማገናኘት አልተሳካም</translation>
+<translation id="973896785707726617">ይህ ክፍለ ጊዜ በ<ph name="SESSION_TIME_REMAINING"/> ጊዜ ውስጥ ያልቃል። በራስ-ሰር እንዲወጡ ይደረጋሉ።</translation>
+<translation id="8372369524088641025">መጥፎ የWEP ቁልፍ</translation>
+<translation id="6636709850131805001">ያልታወቀ ሁኔታ</translation>
+<translation id="3573179567135747900">ወደ «<ph name="FROM_LOCALE"/>» መልሰህ ቀይር (ዳግም ማስጀመር ይፈልጋል)</translation>
+<translation id="8103386449138765447">የኤስ ኤም ኤስ መልዕክቶች፦ <ph name="MESSAGE_COUNT"/></translation>
+<translation id="5045002648206642691">የGoogle Drive ቅንብሮች...</translation>
+<translation id="1510238584712386396">ማስጀመሪያ</translation>
+<translation id="7209101170223508707">CAPS LOCK በርቷል።
+ለመሰረዝ Alt+Search ወይም Shift ይጫኑ።</translation>
+<translation id="8940956008527784070">ባትሪ ዝቅተኛ ነው (<ph name="PERCENTAGE"/>%)</translation>
+<translation id="5102001756192215136"><ph name="HOUR"/>:<ph name="MINUTE"/> ቀርቷል</translation>
+<translation id="520760366042891468">የማያ ገጽዎን ቁጥጥር በHangouts በኩል በማጋራት ላይ።</translation>
+<translation id="8000066093800657092">ምንም አውታረ መረብ የለም</translation>
+<translation id="4015692727874266537">ሌላ መለያ ያስገቡ...</translation>
+<translation id="5941711191222866238">አሳንስ</translation>
+<translation id="6911468394164995108">ሌላ ይቀላቀሉ...</translation>
+<translation id="412065659894267608">ሙሉ እስኪሆን ድረስ <ph name="HOUR"/>ሰ <ph name="MINUTE"/>ደ</translation>
+<translation id="6359806961507272919">ኤስ ኤም ኤስ ከ<ph name="PHONE_NUMBER"/></translation>
+<translation id="1244147615850840081">ድምጸ ተያያዥ ሞደም</translation>
+</translationbundle> \ No newline at end of file
diff --git a/chromium/ash/strings/ash_strings_ar.xtb b/chromium/ash/strings/ash_strings_ar.xtb
new file mode 100644
index 00000000000..7be62a6e682
--- /dev/null
+++ b/chromium/ash/strings/ash_strings_ar.xtb
@@ -0,0 +1,198 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="ar">
+<translation id="3595596368722241419">البطارية ممتلئة</translation>
+<translation id="5250713215130379958">إخفاء المشغل تلقائيًا</translation>
+<translation id="7814236020522506259"><ph name="HOUR"/> و<ph name="MINUTE"/></translation>
+<translation id="7880025619322806991">حالة المدخل</translation>
+<translation id="30155388420722288">زر التدفق</translation>
+<translation id="5571066253365925590">تم تمكين البلوتوث</translation>
+<translation id="9074739597929991885">بلوتوث</translation>
+<translation id="2268130516524549846">تم تعطيل البلوتوث</translation>
+<translation id="3775358506042162758">يُمكنك تسجيل الدخول المتعدد باستخدام ثلاثة حسابات كحد أقصى.</translation>
+<translation id="370649949373421643">تمكين Wi-Fi</translation>
+<translation id="3626281679859535460">السطوع</translation>
+<translation id="8054466585765276473">يجري حساب وقت البطارية.</translation>
+<translation id="7982789257301363584">الشبكة</translation>
+<translation id="5565793151875479467">الخادم الوكيل...</translation>
+<translation id="938582441709398163">تراكب لوحة المفاتيح</translation>
+<translation id="4387004326333427325">تم رفض شهادة المصادقة عن بُعد</translation>
+<translation id="6979158407327259162">Google Drive</translation>
+<translation id="6943836128787782965">أخفق الحصول على HTTP</translation>
+<translation id="2297568595583585744">علبة الحالة</translation>
+<translation id="1661867754829461514">رقم التعريف الشخصي غير موجود</translation>
+<translation id="4508225577814909926"><ph name="NAME"/>: جارٍ الاتصال...</translation>
+<translation id="4237016987259239829">خطأ في اتصال الشبكة</translation>
+<translation id="2946640296642327832">تمكين البلوتوث</translation>
+<translation id="6459472438155181876">تمديد الشاشة إلى <ph name="DISPLAY_NAME"/></translation>
+<translation id="8206859287963243715">خلوي</translation>
+<translation id="6596816719288285829">عنوان IP</translation>
+<translation id="4508265954913339219">أخفقت عملية التنشيط</translation>
+<translation id="3621712662352432595">إعدادات الصوت</translation>
+<translation id="1812696562331527143">تم تغيير أسلوب الإدخال إلى <ph name="INPUT_METHOD_ID"/>*(<ph name="BEGIN_LINK"/>جهة خارجية<ph name="END_LINK"/>).
+اضغط على Shift + Alt للتبديل.</translation>
+<translation id="2127372758936585790">شاحن منخفض الطاقة</translation>
+<translation id="3846575436967432996">لا توجد معلومات متاحة حول الشبكة</translation>
+<translation id="3026237328237090306">إعداد بيانات الجوال</translation>
+<translation id="785750925697875037">عرض حساب الجوال</translation>
+<translation id="153454903766751181">جارٍ تهيئة المودم الخلوي...</translation>
+<translation id="4628814525959230255">مشاركة التحكم في الشاشة مع <ph name="HELPER_NAME"/> عبر Hangouts.</translation>
+<translation id="8343941333792395995">تمّ تدوير <ph name="DISPLAY_NAME"/></translation>
+<translation id="7864539943188674973">تعطيل البلوتوث</translation>
+<translation id="939252827960237676">أخفق حفظ لقطة الشاشة</translation>
+<translation id="3126069444801937830">إعادة التشغيل للتحديث</translation>
+<translation id="2268813581635650749">خروج الجميع</translation>
+<translation id="735745346212279324">تم قطع اتصال الشبكة الظاهرية الخاصة</translation>
+<translation id="7320906967354320621">في وضع الخمول</translation>
+<translation id="6303423059719347535">اكتمل شحن <ph name="PERCENTAGE"/>% من البطارية</translation>
+<translation id="15373452373711364">مؤشر الماوس الكبير</translation>
+<translation id="2778346081696727092">أخفقت المصادقة باستخدام اسم المستخدم أو كلمة المرور المقدمين</translation>
+<translation id="3294437725009624529">ضيف</translation>
+<translation id="8190698733819146287">تخصيص اللغات والإدخال...</translation>
+<translation id="2903907270192926896">الإرسال</translation>
+<translation id="8676770494376880701">تمّ توصيل شاحن منخفض الطاقة</translation>
+<translation id="7170041865419449892">خارج النطاق</translation>
+<translation id="4804818685124855865">قطع الاتصال</translation>
+<translation id="2544853746127077729">تمّ رفض شهادة المصادقة بواسطة الشبكة</translation>
+<translation id="5222676887888702881">الخروج</translation>
+<translation id="2688477613306174402">تهيئة</translation>
+<translation id="1272079795634619415">إيقاف</translation>
+<translation id="4957722034734105353">مزيد من المعلومات...</translation>
+<translation id="2964193600955408481">تعطيل Wi-Fi</translation>
+<translation id="811680302244032017">إضافة جهاز...</translation>
+<translation id="4279490309300973883">النسخ المطابق</translation>
+<translation id="2509468283778169019">مفتاح CAPS LOCK قيد التشغيل</translation>
+<translation id="3892641579809465218">العرض الداخلي</translation>
+<translation id="7823564328645135659">تم تغيير اللغة من &quot;<ph name="FROM_LOCALE"/>&quot; إلى &quot;<ph name="TO_LOCALE"/>&quot; بعد مزامنة إعداداتك.</translation>
+<translation id="3368922792935385530">متصل</translation>
+<translation id="8340999562596018839">التعليقات المنطوقة</translation>
+<translation id="8654520615680304441">تشغيل شبكة Wi-Fi...</translation>
+<translation id="5825747213122829519">تم تغيير أسلوب الإدخال إلى <ph name="INPUT_METHOD_ID"/>.
+اضغط على Shift + Alt للتبديل.</translation>
+<translation id="2562916301614567480">الشبكة الخاصة</translation>
+<translation id="6549021752953852991">لا تتوفر شبكة خلوية</translation>
+<translation id="4379753398862151997">الشاشة لا تعمل. (هذه الشاشة غير متوافقة)</translation>
+<translation id="6426039856985689743">تعطيل بيانات الجوال</translation>
+<translation id="3087734570205094154">أسفل</translation>
+<translation id="3742055079367172538">تم التقاط لقطة الشاشة</translation>
+<translation id="8878886163241303700">توسيع الشاشة</translation>
+<translation id="5271016907025319479">لم تتم تهيئة الشبكة الظاهرية الخاصة.</translation>
+<translation id="372094107052732682">للإنهاء، اضغط على Ctrl+Shift+Q مرتين.</translation>
+<translation id="6803622936009808957">تعذر إجراء النسخ المطابق للعروض نظرًا لعدم العثور على درجات دقة متوافقة. تم الدخول إلى سطح المكتب الممتد بدلاً من ذلك.</translation>
+<translation id="1480041086352807611">الوضع التجريبي</translation>
+<translation id="3626637461649818317">باقٍ <ph name="PERCENTAGE"/>%</translation>
+<translation id="9089416786594320554">أساليب الإدخال</translation>
+<translation id="6247708409970142803">%<ph name="PERCENTAGE"/></translation>
+<translation id="2614835198358683673">قد لا يستجيب جهاز Chromebook لعملية الشحن وهو قيد التشغيل. مع مراعاة استخدام الشاحن المخصص للجهاز.</translation>
+<translation id="1895658205118569222">إيقاف التشغيل</translation>
+<translation id="4430019312045809116">مستوى الصوت</translation>
+<translation id="4442424173763614572">أخفق البحث في نظام أسماء النطاقات</translation>
+<translation id="6356500677799115505">البطارية مملوءة ويتم شحنها.</translation>
+<translation id="7874779702599364982">جارٍ البحث عن شبكات للهاتف الجوال...</translation>
+<translation id="583281660410589416">غير محدّد</translation>
+<translation id="1383876407941801731">البحث</translation>
+<translation id="7468789844759750875">انتقل إلى مدخل التنشيط <ph name="NAME"/> لشراء المزيد من البيانات.</translation>
+<translation id="3901991538546252627">جارٍ الاتصال بـ <ph name="NAME"/></translation>
+<translation id="2204305834655267233">معلومات الشبكة</translation>
+<translation id="1621499497873603021">الوقت المتبقي حتى تصبح البطارية فارغة <ph name="TIME_LEFT"/></translation>
+<translation id="5980301590375426705">إنهاء جلسة الضيف</translation>
+<translation id="4471417012762451363">اكتمل شحن <ph name="PERCENTAGE"/>% من البطارية ويتم شحنها</translation>
+<translation id="8308637677604853869">القائمة السابقة</translation>
+<translation id="4666297444214622512">يتعذّر تسجيل الدخول إلى حساب آخر.</translation>
+<translation id="1346748346194534595">يمين</translation>
+<translation id="1773212559869067373">تمّ رفض شهادة المصادقة محليًا</translation>
+<translation id="8528322925433439945">الجوال ...</translation>
+<translation id="7049357003967926684">اقتران</translation>
+<translation id="8428213095426709021">الإعدادات</translation>
+<translation id="2372145515558759244">جارٍ مزامنة التطبيقات...</translation>
+<translation id="7256405249507348194">خطأ غير معروف: <ph name="DESC"/></translation>
+<translation id="7925247922861151263">أخفق فحص AAA</translation>
+<translation id="8456362689280298700">حتى الاكتمال: <ph name="HOUR"/>:<ph name="MINUTE"/> ‏</translation>
+<translation id="5787281376604286451">تم تمكين التعليقات المنطوقة.
+يمكنك الضغط على Ctrl+Alt+Z لتعطيلها.</translation>
+<translation id="4479639480957787382">إيثرنت</translation>
+<translation id="6312403991423642364">خطأ غير معروف</translation>
+<translation id="1467432559032391204">اليسار</translation>
+<translation id="5543001071567407895">الرسائل القصيرة SMS</translation>
+<translation id="2354174487190027830">تنشيط <ph name="NAME"/></translation>
+<translation id="8814190375133053267">لاسلكي، Wi-Fi</translation>
+<translation id="1398853756734560583">تكبير</translation>
+<translation id="2692809339924654275"><ph name="BLUETOOTH"/>: جارٍ التوصيل...</translation>
+<translation id="252373100621549798">شاشة عرض غير معروفة</translation>
+<translation id="1882897271359938046">نسخ إلى <ph name="DISPLAY_NAME"/></translation>
+<translation id="2727977024730340865">تمّ توصيل شاحن منخفض الطاقة. لذا قد لا تكون عملية شحن البطارية جديرة بالثقة.</translation>
+<translation id="3784455785234192852">قفل</translation>
+<translation id="2805756323405976993">تطبيقات</translation>
+<translation id="8871072142849158571">تمّ تغيير حجم <ph name="DISPLAY_NAME"/> إلى <ph name="RESOLUTION"/></translation>
+<translation id="1512064327686280138">إخفاق في عملية التنشيط</translation>
+<translation id="5097002363526479830">أخفق الاتصال بشبكة &quot;<ph name="NAME"/>&quot;: <ph name="DETAILS"/></translation>
+<translation id="1850504506766569011">تم إيقاف تشغيل Wi-Fi.</translation>
+<translation id="8132793192354020517">تم الاتصال بالموقع <ph name="NAME"/></translation>
+<translation id="7052914147756339792">تعيين خلفية...</translation>
+<translation id="8678698760965522072">الحالة متصل</translation>
+<translation id="2532589005999780174">وضع التباين العالي</translation>
+<translation id="1119447706177454957">خطأ داخلي</translation>
+<translation id="3019353588588144572">الوقت المتبقي حتى يتم شحن البطارية بالكامل <ph name="TIME_REMAINING"/></translation>
+<translation id="3473479545200714844">مكبّر الشاشة</translation>
+<translation id="7005812687360380971">إخفاق</translation>
+<translation id="882279321799040148">انقر للعرض</translation>
+<translation id="5045550434625856497">كلمة مرور غير صحيحة</translation>
+<translation id="1602076796624386989">تمكين بيانات الجوال</translation>
+<translation id="6981982820502123353">إمكانية الدخول</translation>
+<translation id="3157931365184549694">استعادة</translation>
+<translation id="4274292172790327596">خطأ غير معروف</translation>
+<translation id="4032485810211612751"><ph name="HOURS"/>:<ph name="MINUTES"/>:<ph name="SECONDS"/></translation>
+<translation id="225680501294068881">جارٍ البحث عن أجهزة...</translation>
+<translation id="5597451508971090205"><ph name="SHORT_WEEKDAY"/>، <ph name="DATE"/></translation>
+<translation id="4448844063988177157">جارِ البحث عن شبكات Wi-Fi...</translation>
+<translation id="8401662262483418323">أخفق الاتصال بـ &quot;<ph name="NAME"/>&quot;: <ph name="DETAILS"/> رسالة الخادم: <ph name="SERVER_MSG"/></translation>
+<translation id="2475982808118771221">حدث خطأ</translation>
+<translation id="7229570126336867161">يلزم توفر EVDO</translation>
+<translation id="2999742336789313416"><ph name="DISPLAY_NAME"/> هي جلسة عامة يديرها <ph name="DOMAIN"/></translation>
+<translation id="7029814467594812963">إنهاء الجلسة</translation>
+<translation id="8454013096329229812">تم تشغيل Wi-Fi.</translation>
+<translation id="4872237917498892622">Alt+مفتاح البحث أو Shift</translation>
+<translation id="2983818520079887040">الإعدادات...</translation>
+<translation id="1717216362413677834">وضع الإرساء</translation>
+<translation id="8927026611342028580">الاتصال مطلوب</translation>
+<translation id="8300849813060516376">أخفقت OTASP</translation>
+<translation id="2792498699870441125">Alt+مفتاح البحث</translation>
+<translation id="8660803626959853127">جارٍ مزامنة <ph name="COUNT"/> من الملفات</translation>
+<translation id="3709443003275901162">‎9+‎‏</translation>
+<translation id="639644700271529076">مفتاح CAPS LOCK قيد الإيقاف</translation>
+<translation id="6248847161401822652">للإنهاء، اضغط على Control Shift Q مرتين.</translation>
+<translation id="6267036997247669271"><ph name="NAME"/>: جارٍ التنشيط...</translation>
+<translation id="1391854757121130358">ربما تكون قد استخدمت حصة بيانات الجوال المخصصة لك.</translation>
+<translation id="5413208160176941586">حساب مستخدم يخضع لإدارة محلية</translation>
+<translation id="1059194134494239015"><ph name="DISPLAY_NAME"/>: <ph name="RESOLUTION"/></translation>
+<translation id="4864165860509564259">موضع المشغل</translation>
+<translation id="7593891976182323525">مفتاح البحث أو Shift</translation>
+<translation id="7649070708921625228">مساعدة</translation>
+<translation id="3050422059534974565">المفتاح CAPS LOCK في وضع التشغيل، اضغط على مفتاح البحث أو المفتاح Shift لإلغاء التشغيل.</translation>
+<translation id="397105322502079400">جارٍ الحساب...</translation>
+<translation id="158849752021629804">يلزم توفر الشبكة الرئيسية</translation>
+<translation id="6857811139397017780">تنشيط <ph name="NETWORKSERVICE"/></translation>
+<translation id="5864471791310927901">أخفق بحث DHCP</translation>
+<translation id="5812035014844949013">الاستماع</translation>
+<translation id="6692173217867674490">عبارة مرور غير صالحة</translation>
+<translation id="6165508094623778733">مزيد من المعلومات</translation>
+<translation id="9046895021617826162">أخفق الاتصال</translation>
+<translation id="973896785707726617">ستنتهي هذه الجلسة في <ph name="SESSION_TIME_REMAINING"/>. سيتم الخروج تلقائيًا.</translation>
+<translation id="8372369524088641025">مفتاح WEP غير صالح</translation>
+<translation id="6636709850131805001">حالة غير معروفة</translation>
+<translation id="3573179567135747900">التغيير مرة أخرى إلى &quot;<ph name="FROM_LOCALE"/>&quot; (يتطلب إعادة التشغيل)</translation>
+<translation id="8103386449138765447">الرسائل القصيرة SMS: <ph name="MESSAGE_COUNT"/></translation>
+<translation id="5045002648206642691">إعدادات Google Drive...</translation>
+<translation id="1510238584712386396">المشغّل</translation>
+<translation id="7209101170223508707">المفتاح CAPS LOCK في وضع التشغيل. اضغط على Alt+مفتاح البحث، أو اضغط على المفتاح Shift لإلغاء التشغيل.</translation>
+<translation id="8940956008527784070">طاقة البطارية منخفضة (<ph name="PERCENTAGE"/>%)</translation>
+<translation id="5102001756192215136">الوقت المتبقي: <ph name="HOUR"/>:<ph name="MINUTE"/> ‏</translation>
+<translation id="520760366042891468">مشاركة التحكم في الشاشة عبر Hangouts.</translation>
+<translation id="8000066093800657092">لا شبكة</translation>
+<translation id="4015692727874266537">تسجيل الدخول لحساب آخر...</translation>
+<translation id="5941711191222866238">تصغير</translation>
+<translation id="6911468394164995108">الانضمام إلى شبكة أخرى...</translation>
+<translation id="412065659894267608"><ph name="HOUR"/>س <ph name="MINUTE"/>د حتى الاكتمال</translation>
+<translation id="6359806961507272919">رسالة قصيرة SMS من الهاتف رقم <ph name="PHONE_NUMBER"/></translation>
+<translation id="1244147615850840081">شركة اتصالات</translation>
+</translationbundle> \ No newline at end of file
diff --git a/chromium/ash/strings/ash_strings_bg.xtb b/chromium/ash/strings/ash_strings_bg.xtb
new file mode 100644
index 00000000000..b7c4b204a5a
--- /dev/null
+++ b/chromium/ash/strings/ash_strings_bg.xtb
@@ -0,0 +1,201 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="bg">
+<translation id="3595596368722241419">Батерията е заредена</translation>
+<translation id="5250713215130379958">Автоматично скриване на стартовия панел</translation>
+<translation id="7814236020522506259"><ph name="HOUR"/> и <ph name="MINUTE"/></translation>
+<translation id="7880025619322806991">Състояние на портала</translation>
+<translation id="30155388420722288">Бутон „Препълване“</translation>
+<translation id="5571066253365925590">Bluetooth е активиран</translation>
+<translation id="9074739597929991885">Bluetooth</translation>
+<translation id="2268130516524549846">Bluetooth е деактивиран</translation>
+<translation id="3775358506042162758">В централизирания вход можете да имате най-много три профила.</translation>
+<translation id="370649949373421643">Активиране на Wi-Fi</translation>
+<translation id="3626281679859535460">Яркост</translation>
+<translation id="8054466585765276473">Издръжливостта на батерията се изчислява.</translation>
+<translation id="7982789257301363584">Мрежа</translation>
+<translation id="5565793151875479467">Прокси сървър...</translation>
+<translation id="938582441709398163">Наслагване върху клавиатурата</translation>
+<translation id="4387004326333427325">Сертификатът за удостоверяване бе отхвърлен отдалечено</translation>
+<translation id="6979158407327259162">Google Диск</translation>
+<translation id="6943836128787782965">Получаването на HTTP не бе успешно</translation>
+<translation id="2297568595583585744">Област на състоянието</translation>
+<translation id="1661867754829461514">Липсва PIN</translation>
+<translation id="4508225577814909926"><ph name="NAME"/>: Установява се връзка...</translation>
+<translation id="4237016987259239829">Грешка при свързване към мрежата</translation>
+<translation id="2946640296642327832">Активиране на Bluetooth</translation>
+<translation id="6459472438155181876">Екранът се разширява на „<ph name="DISPLAY_NAME"/>“</translation>
+<translation id="8206859287963243715">Клетъчно</translation>
+<translation id="6596816719288285829">IP адрес</translation>
+<translation id="4508265954913339219">Активирането не бе успешно</translation>
+<translation id="3621712662352432595">Настройки за звука</translation>
+<translation id="1812696562331527143">Методът ви на въвеждане се промени на <ph name="INPUT_METHOD_ID"/>*(<ph name="BEGIN_LINK"/>трета страна<ph name="END_LINK"/>).
+Натиснете „Shift + Alt“, за да превключите.</translation>
+<translation id="2127372758936585790">Зарядно устройство с малка мощност</translation>
+<translation id="3846575436967432996">Не е налице информация за мрежата</translation>
+<translation id="3026237328237090306">Настройка на мобилните данни</translation>
+<translation id="785750925697875037">Преглед на мобилния профил</translation>
+<translation id="153454903766751181">Клетъчният модем се подготвя за работа...</translation>
+<translation id="4628814525959230255">Контролът върху екрана ви се споделя с/ъс <ph name="HELPER_NAME"/> чрез Hangouts.</translation>
+<translation id="8343941333792395995"><ph name="DISPLAY_NAME"/> бе завъртян</translation>
+<translation id="7864539943188674973">Деактивиране на Bluetooth</translation>
+<translation id="939252827960237676">Запазването на екранната снимка не бе успешно</translation>
+<translation id="3126069444801937830">Рестартирайте, за да актуализирате</translation>
+<translation id="2268813581635650749">Изход за всички</translation>
+<translation id="735745346212279324">Връзката с виртуалната частна мрежа (VPN) е прекъсната</translation>
+<translation id="7320906967354320621">Неактивна</translation>
+<translation id="6303423059719347535">Батерията е <ph name="PERCENTAGE"/>% пълна</translation>
+<translation id="15373452373711364">Голям курсор на мишката</translation>
+<translation id="2778346081696727092">Удостоверяването с предоставеното потребителско име или парола не бе успешно</translation>
+<translation id="3294437725009624529">Гост</translation>
+<translation id="8190698733819146287">Персонализиране на езиците и въвеждането...</translation>
+<translation id="2903907270192926896">ВХОД</translation>
+<translation id="8676770494376880701">Свързано е зарядно устройство с малка мощност</translation>
+<translation id="7170041865419449892">Извън обхват</translation>
+<translation id="4804818685124855865">Изключване</translation>
+<translation id="2544853746127077729">Сертификатът за удостоверяване бе отхвърлен от мрежата</translation>
+<translation id="5222676887888702881">Изход</translation>
+<translation id="2688477613306174402">Конфигурация</translation>
+<translation id="1272079795634619415">Стоп</translation>
+<translation id="4957722034734105353">Научете повече...</translation>
+<translation id="2964193600955408481">Деактивиране на Wi-Fi</translation>
+<translation id="811680302244032017">Добавяне на устройство...</translation>
+<translation id="4279490309300973883">Дублиране</translation>
+<translation id="2509468283778169019">„CAPS LOCK“ е включен</translation>
+<translation id="3892641579809465218">Показване на вътрешна информация</translation>
+<translation id="7823564328645135659">Езикът се промени от „<ph name="FROM_LOCALE"/>“ на „<ph name="TO_LOCALE"/>“ след синхронизирането на настройките ви.</translation>
+<translation id="3368922792935385530">Установена е връзка</translation>
+<translation id="8340999562596018839">Обратна връзка с говор</translation>
+<translation id="8654520615680304441">Включване на Wi-Fi...</translation>
+<translation id="5825747213122829519">Методът ви на въвеждане се промени на <ph name="INPUT_METHOD_ID"/>.
+Натиснете „Shift + Alt“, за да превключите.</translation>
+<translation id="2562916301614567480">Частна мрежа</translation>
+<translation id="6549021752953852991">Няма налична клетъчна мрежа</translation>
+<translation id="4379753398862151997">Уважаеми мониторе, нещата между нас не се получават. (Този монитор не се поддържа)</translation>
+<translation id="6426039856985689743">Деактивиране на мобилните данни</translation>
+<translation id="3087734570205094154">Най-долу</translation>
+<translation id="3742055079367172538">Направена бе екранна снимка</translation>
+<translation id="8878886163241303700">Разгъване на екрана</translation>
+<translation id="5271016907025319479">Виртуалната частна мрежа (VPN) не е конфигурирана.</translation>
+<translation id="372094107052732682">За изход натиснете два пъти Ctrl+Shift+Q.</translation>
+<translation id="6803622936009808957">Дисплеите не можаха да се дублират, тъй като не бяха намерени поддържани разделителни способности. Вместо това влязохте в режима за разширен работен плот.</translation>
+<translation id="1480041086352807611">Демонстрационен режим</translation>
+<translation id="3626637461649818317">Остава/т <ph name="PERCENTAGE"/>%</translation>
+<translation id="9089416786594320554">Методи за въвеждане</translation>
+<translation id="6247708409970142803"><ph name="PERCENTAGE"/>%</translation>
+<translation id="2614835198358683673">Възможно е вашият Chromebook да не се зарежда, докато е включен. Помислете за използване на официалното зарядно устройство.</translation>
+<translation id="1895658205118569222">Изключване</translation>
+<translation id="4430019312045809116">Звук</translation>
+<translation id="4442424173763614572">Търсенето в DNS не бе успешно</translation>
+<translation id="6356500677799115505">Батерията е пълна и се зарежда.</translation>
+<translation id="7874779702599364982">Търсят се мобилни мрежи...</translation>
+<translation id="583281660410589416">Неизвестно</translation>
+<translation id="1383876407941801731">Търсене</translation>
+<translation id="7468789844759750875">Посетете портала за активиране на <ph name="NAME"/>, за да купите още данни.</translation>
+<translation id="3901991538546252627">Установява се връзка със: <ph name="NAME"/></translation>
+<translation id="2204305834655267233">Информация за мрежата</translation>
+<translation id="1621499497873603021">Оставащо време до изразходването на батерията: <ph name="TIME_LEFT"/></translation>
+<translation id="5980301590375426705">Изход от сесията като гост</translation>
+<translation id="4471417012762451363">Батерията е <ph name="PERCENTAGE"/>% пълна и се зарежда</translation>
+<translation id="8308637677604853869">Предишно меню</translation>
+<translation id="4666297444214622512">Не може да влезете в друг профил.</translation>
+<translation id="1346748346194534595">Надясно</translation>
+<translation id="1773212559869067373">Сертификатът за удостоверяване бе отхвърлен локално</translation>
+<translation id="8528322925433439945">Мобилни мрежи...</translation>
+<translation id="7049357003967926684">Връзка</translation>
+<translation id="8428213095426709021">Настройки</translation>
+<translation id="2372145515558759244">Приложенията се синхронизират...</translation>
+<translation id="7256405249507348194">Неразпозната грешка: <ph name="DESC"/></translation>
+<translation id="7925247922861151263">Проверката за AAA не бе успешна</translation>
+<translation id="8456362689280298700"><ph name="HOUR"/>:<ph name="MINUTE"/> до пълно зареждане</translation>
+<translation id="5787281376604286451">Обратната връзка с говор е активирана.
+Натиснете „Ctrl+Alt+Z“, за да я деактивирате.</translation>
+<translation id="4479639480957787382">Ethernet</translation>
+<translation id="6312403991423642364">Неизвестна мрежова грешка</translation>
+<translation id="1467432559032391204">Наляво</translation>
+<translation id="5543001071567407895">SMS</translation>
+<translation id="2354174487190027830">„<ph name="NAME"/>“ се активира</translation>
+<translation id="8814190375133053267">Wi-Fi</translation>
+<translation id="1398853756734560583">Увеличаване</translation>
+<translation id="2692809339924654275"><ph name="BLUETOOTH"/>: Установява се връзка...</translation>
+<translation id="252373100621549798">Неизвестен дисплей</translation>
+<translation id="1882897271359938046">Дублира се на „<ph name="DISPLAY_NAME"/>“</translation>
+<translation id="2727977024730340865">Използва се зарядно устройство с малка мощност. Зареждането на батерията може да не е надеждно.</translation>
+<translation id="3784455785234192852">Заключване</translation>
+<translation id="2805756323405976993">Приложения</translation>
+<translation id="8871072142849158571"><ph name="DISPLAY_NAME"/> бе преоразмерен на <ph name="RESOLUTION"/></translation>
+<translation id="1512064327686280138">Неуспех при активирането</translation>
+<translation id="5097002363526479830">Свързването с мрежата „<ph name="NAME"/>“ не бе успешно: <ph name="DETAILS"/></translation>
+<translation id="1850504506766569011">Wi-Fi е изключен.</translation>
+<translation id="8132793192354020517">Установена е връзка с/ъс <ph name="NAME"/></translation>
+<translation id="7052914147756339792">Задаване на тапет...</translation>
+<translation id="8678698760965522072">Състояние: Онлайн</translation>
+<translation id="2532589005999780174">Режим на висок контраст</translation>
+<translation id="1119447706177454957">Вътрешна грешка</translation>
+<translation id="3019353588588144572">Оставащо време до пълното зареждане на батерията: <ph name="TIME_REMAINING"/></translation>
+<translation id="3473479545200714844">Екранна лупа</translation>
+<translation id="7005812687360380971">Неуспех</translation>
+<translation id="882279321799040148">Кликнете, за да прегледате</translation>
+<translation id="5045550434625856497">Неправилна парола</translation>
+<translation id="1602076796624386989">Активиране на мобилните данни</translation>
+<translation id="6981982820502123353">Достъпност</translation>
+<translation id="3157931365184549694">Възстановяване</translation>
+<translation id="4274292172790327596">Неразпозната грешка</translation>
+<translation id="4032485810211612751"><ph name="HOURS"/>:<ph name="MINUTES"/>:<ph name="SECONDS"/></translation>
+<translation id="225680501294068881">Сканира се за устройства...</translation>
+<translation id="5597451508971090205"><ph name="SHORT_WEEKDAY"/>, <ph name="DATE"/></translation>
+<translation id="4448844063988177157">Търсят се Wi-Fi мрежи...</translation>
+<translation id="8401662262483418323">Свързването с/ъс „<ph name="NAME"/>“ не бе успешно: <ph name="DETAILS"/>
+Съобщение от сървъра: <ph name="SERVER_MSG"/></translation>
+<translation id="2475982808118771221">Възникна грешка</translation>
+<translation id="7229570126336867161">Необходим е EVDO</translation>
+<translation id="2999742336789313416">„<ph name="DISPLAY_NAME"/>“ е обществена сесия, управлявана от <ph name="DOMAIN"/></translation>
+<translation id="7029814467594812963">Изход от сесията</translation>
+<translation id="8454013096329229812">Wi-Fi е включен.</translation>
+<translation id="4872237917498892622">„Alt + търсене“ или „Shift“</translation>
+<translation id="2983818520079887040">Настройки...</translation>
+<translation id="1717216362413677834">Режим на работа с докинг станция</translation>
+<translation id="8927026611342028580">Заявено е свързване</translation>
+<translation id="8300849813060516376">Безжичното осигуряване на услуга не бе успешно</translation>
+<translation id="2792498699870441125">Alt + търсене</translation>
+<translation id="8660803626959853127">Синхронизира/т се <ph name="COUNT"/> файл/а</translation>
+<translation id="3709443003275901162">9+</translation>
+<translation id="639644700271529076">„CAPS LOCK“ е изключен</translation>
+<translation id="6248847161401822652">За изход натиснете два пъти Ctrl+Shift+Q.</translation>
+<translation id="6267036997247669271">„<ph name="NAME"/>“: Активира се...</translation>
+<translation id="1391854757121130358">Може да сте изразходили отпуснатите ви мобилни данни.</translation>
+<translation id="5413208160176941586">Локално управляван потребител</translation>
+<translation id="1059194134494239015"><ph name="DISPLAY_NAME"/>: <ph name="RESOLUTION"/></translation>
+<translation id="4864165860509564259">Позиция на стартовия панел</translation>
+<translation id="7593891976182323525">„търсене“ или „Shift“</translation>
+<translation id="7649070708921625228">Помощ</translation>
+<translation id="3050422059534974565">„CAPS LOCK“ е включен.
+Натиснете „търсене“ или „Shift“, за да анулирате.</translation>
+<translation id="397105322502079400">Изчислява се...</translation>
+<translation id="158849752021629804">Необходима е собствена мрежа</translation>
+<translation id="6857811139397017780">Активиране на <ph name="NETWORKSERVICE"/></translation>
+<translation id="5864471791310927901">Търсенето на DHCP сървър не бе успешно</translation>
+<translation id="5812035014844949013">ИЗХОД</translation>
+<translation id="6692173217867674490">Паролата е неправилна</translation>
+<translation id="6165508094623778733">Научете повече</translation>
+<translation id="9046895021617826162">Свързването не бе успешно</translation>
+<translation id="973896785707726617">Тази сесия ще приключи след <ph name="SESSION_TIME_REMAINING"/>. Ще излезете автоматично от нея.</translation>
+<translation id="8372369524088641025">Ключът за WEP е неправилен</translation>
+<translation id="6636709850131805001">Неразпознато състояние</translation>
+<translation id="3573179567135747900">Връщане към „<ph name="FROM_LOCALE"/>“ (изисква рестартиране)</translation>
+<translation id="8103386449138765447">SMS съобщения: <ph name="MESSAGE_COUNT"/></translation>
+<translation id="5045002648206642691">Настройки за Google Диск...</translation>
+<translation id="1510238584712386396">Стартов панел</translation>
+<translation id="7209101170223508707">„CAPS LOCK“ е включен.
+Натиснете „Alt + търсене“ или „Shift“, за да анулирате.</translation>
+<translation id="8940956008527784070">Батерията е изтощена (<ph name="PERCENTAGE"/>%)</translation>
+<translation id="5102001756192215136">Остава/т <ph name="HOUR"/>:<ph name="MINUTE"/></translation>
+<translation id="520760366042891468">Контролът върху екрана ви се споделя чрез Hangouts.</translation>
+<translation id="8000066093800657092">Няма мрежа</translation>
+<translation id="4015692727874266537">Вход в друг профил...</translation>
+<translation id="5941711191222866238">Намаляване</translation>
+<translation id="6911468394164995108">Присъединяване другаде...</translation>
+<translation id="412065659894267608"><ph name="HOUR"/> ч <ph name="MINUTE"/> м до пълно зареждане</translation>
+<translation id="6359806961507272919">SMS от <ph name="PHONE_NUMBER"/></translation>
+<translation id="1244147615850840081">Оператор</translation>
+</translationbundle> \ No newline at end of file
diff --git a/chromium/ash/strings/ash_strings_bn.xtb b/chromium/ash/strings/ash_strings_bn.xtb
new file mode 100644
index 00000000000..44496f3ec74
--- /dev/null
+++ b/chromium/ash/strings/ash_strings_bn.xtb
@@ -0,0 +1,201 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="bn">
+<translation id="3595596368722241419">ব্যাটারি পুরো চার্জ</translation>
+<translation id="5250713215130379958">লঞ্চার স্বয়ংক্রিয়ভাবে লুকান</translation>
+<translation id="7814236020522506259"><ph name="HOUR"/> এবং <ph name="MINUTE"/></translation>
+<translation id="7880025619322806991">পোর্টাল স্থিতি</translation>
+<translation id="30155388420722288">ওভারফ্লো বোতাম</translation>
+<translation id="5571066253365925590">ব্লুটুথ সক্ষমিত</translation>
+<translation id="9074739597929991885">Bluetooth</translation>
+<translation id="2268130516524549846">ব্লুটুথ অক্ষমিত</translation>
+<translation id="3775358506042162758">একাধিক সাইন ইনে আপনার কেবলমাত্র তিনটি অ্যাকাউন্ট থাকতে পারে৷</translation>
+<translation id="370649949373421643">Wi-fi সক্ষম করুন</translation>
+<translation id="3626281679859535460">উজ্জ্বলতা</translation>
+<translation id="8054466585765276473">ব্যাটারি সময় গণনা করা হচ্ছে।</translation>
+<translation id="7982789257301363584">নেটওয়ার্ক</translation>
+<translation id="5565793151875479467">প্রক্সি...</translation>
+<translation id="938582441709398163">কীবোর্ড ওভারলে</translation>
+<translation id="4387004326333427325">দূরবর্তী অবস্থান থেকে প্রমাণীকরণ শংসাপত্র প্রত্যাখ্যান করা হয়েছে</translation>
+<translation id="6979158407327259162">Google ড্রাইভ</translation>
+<translation id="6943836128787782965">HTTP ব্যর্থ হয়েছে</translation>
+<translation id="2297568595583585744">স্থিতি ট্রে</translation>
+<translation id="1661867754829461514">PIN হারিয়েছে</translation>
+<translation id="4508225577814909926"><ph name="NAME"/>: সংযুক্ত হচ্ছে...</translation>
+<translation id="4237016987259239829">নেটওয়ার্ক সংযোগ ত্রুটি</translation>
+<translation id="2946640296642327832">ব্লুটুথ সক্ষম করুন</translation>
+<translation id="6459472438155181876">স্ক্রীন <ph name="DISPLAY_NAME"/> তে প্রসারিত হচ্ছে</translation>
+<translation id="8206859287963243715">সেলুলার</translation>
+<translation id="6596816719288285829">IP ঠিকানা</translation>
+<translation id="4508265954913339219">সক্রিয়করণ ব্যর্থ</translation>
+<translation id="3621712662352432595">অডিও সেটিংস</translation>
+<translation id="1812696562331527143">আপনার ইনপুট পদ্ধতি <ph name="INPUT_METHOD_ID"/>*(<ph name="BEGIN_LINK"/>৩য় পক্ষ<ph name="END_LINK"/>) এ পরিবর্তিত হয়েছে৷
+স্যুইচ করতে Shift + Alt টিপুন৷</translation>
+<translation id="2127372758936585790">নিম্ন শক্তির চার্জার</translation>
+<translation id="3846575436967432996">কোনো নেটওয়ার্ক সংক্রান্ত তথ্য উপলব্ধ নেই</translation>
+<translation id="3026237328237090306">মোবাইল ডেটা সেটআপ করুন</translation>
+<translation id="785750925697875037">মোবাইল অ্যাকাউন্ট দেখুন</translation>
+<translation id="153454903766751181">সেলুলার মোডেম আরম্ভ করা হচ্ছে...</translation>
+<translation id="4628814525959230255">Hangouts এর মাধ্যমে <ph name="HELPER_NAME"/> এর সঙ্গে আপনার স্ক্রীন নিয়ন্ত্রণ ভাগ করা হচ্ছে৷</translation>
+<translation id="8343941333792395995"><ph name="DISPLAY_NAME"/> ঘোরানো হয়েছে</translation>
+<translation id="7864539943188674973">ব্লুটুথ অক্ষম করুন</translation>
+<translation id="939252827960237676">স্ক্রীনশট সংরক্ষণ করতে ব্যর্থ হয়েছে</translation>
+<translation id="3126069444801937830">আপডেট করার জন্য পুনরারম্ভ করুন</translation>
+<translation id="2268813581635650749">সবগুলি সাইন আউট করুন</translation>
+<translation id="735745346212279324">VPN সংযোগ বিচ্ছিন্ন করা হয়েছে</translation>
+<translation id="7320906967354320621">নিষ্ক্রিয়</translation>
+<translation id="6303423059719347535">ব্যাটারি <ph name="PERCENTAGE"/>% পরিপূর্ণ</translation>
+<translation id="15373452373711364">বড় মাউস কার্সার</translation>
+<translation id="2778346081696727092">সরবরাহ করা ব্যবহারকারীর নাম বা পাসওয়ার্ড সহ প্রমাণীকরণ ব্যর্থ</translation>
+<translation id="3294437725009624529">অতিথি</translation>
+<translation id="8190698733819146287">ভাষা এবং ইনপুট কাস্টমাইজ করুন...</translation>
+<translation id="2903907270192926896">ইনপুট</translation>
+<translation id="8676770494376880701">নিম্ন শক্তির চার্জার সংযুক্ত করা হয়েছে</translation>
+<translation id="7170041865419449892">সীমার বাইরে</translation>
+<translation id="4804818685124855865">সংযোগ বিচ্ছিন্ন</translation>
+<translation id="2544853746127077729">নেটওয়ার্কের দ্বারা প্রমাণীকরণ শংসাপত্র প্রত্যাখ্যান করা হয়েছে</translation>
+<translation id="5222676887888702881">সাইন আউট</translation>
+<translation id="2688477613306174402">কনফিগারেশন</translation>
+<translation id="1272079795634619415">বন্ধ</translation>
+<translation id="4957722034734105353">আরো জানুন...</translation>
+<translation id="2964193600955408481">Wi-Fi অক্ষম করুন</translation>
+<translation id="811680302244032017">ডিভাইস জুড়ুন...</translation>
+<translation id="4279490309300973883">অনুকরণ করা হচ্ছে</translation>
+<translation id="2509468283778169019">CAPS LOCK চালু</translation>
+<translation id="3892641579809465218">অভ্যন্তরীণ প্রদর্শন</translation>
+<translation id="7823564328645135659">আপনার সেটিংস সিঙ্ক করার পরে ভাষা &quot;<ph name="FROM_LOCALE"/>&quot; থেকে &quot;<ph name="TO_LOCALE"/>&quot; এ পরিবর্তন করা হয়েছে৷</translation>
+<translation id="3368922792935385530">সংযুক্ত</translation>
+<translation id="8340999562596018839">কথ্য প্রতিক্রিয়া</translation>
+<translation id="8654520615680304441">Wi-Fi চালু করুন...</translation>
+<translation id="5825747213122829519">আপনার ইনপুট পদ্ধতি <ph name="INPUT_METHOD_ID"/> এ পরিবর্তিত হয়েছে৷
+স্যুইচ করতে Shift + Alt টিপুন৷</translation>
+<translation id="2562916301614567480">ব্যক্তিগত নেটওয়ার্ক</translation>
+<translation id="6549021752953852991">কোনো সেলুলার নেটওয়ার্ক উপলব্ধ নেই</translation>
+<translation id="4379753398862151997">Dear Monitor, it's not working out between us. (এই মনিটরটি সমর্থিত নয়)</translation>
+<translation id="6426039856985689743">মোবাইল ডেটা নিষ্ক্রিয় করুন</translation>
+<translation id="3087734570205094154">নীচে</translation>
+<translation id="3742055079367172538">স্ক্রীনশট নেওয়া হয়েছে</translation>
+<translation id="8878886163241303700">স্ক্রীন সম্প্রসারণ করা হচ্ছে</translation>
+<translation id="5271016907025319479">VPN কনফিগার করা নেই৷</translation>
+<translation id="372094107052732682">প্রস্থান করার জন্য দুবার Ctrl+Shift+Q টিপুন৷</translation>
+<translation id="6803622936009808957">সমর্থিত রেসুলিউশানগুলি খুঁজে না পাওয়ায় মিরর প্রদর্শনগুলি করতে পারেনি৷ পরিবর্তে প্রসারিত ডেস্কটপ সক্ষম করা হয়েছে৷</translation>
+<translation id="1480041086352807611">নমুনা মোড</translation>
+<translation id="3626637461649818317"><ph name="PERCENTAGE"/>% বাকি আছে</translation>
+<translation id="9089416786594320554">ইনপুট পদ্ধতিসমূহ</translation>
+<translation id="6247708409970142803"><ph name="PERCENTAGE"/>%</translation>
+<translation id="2614835198358683673">চালু থাকার সময় আপনার Chromebook চার্জ নাও হতে পারে৷ এটির নিজস্ব চার্জার ব্যবহার করার কথা বিবেচনা করুন৷</translation>
+<translation id="1895658205118569222">শাটডাউন</translation>
+<translation id="4430019312045809116">ভলিউম</translation>
+<translation id="4442424173763614572">DNS খোঁজ ব্যর্থ হয়েছে</translation>
+<translation id="6356500677799115505">ব্যাটারি পরিপূর্ণ এবং চার্জ হচ্ছে৷</translation>
+<translation id="7874779702599364982">সেলুলার নেটওয়ার্কগুলির জন্য অনুসন্ধান করছে...</translation>
+<translation id="583281660410589416">অজানা</translation>
+<translation id="1383876407941801731">অনুসন্ধান</translation>
+<translation id="7468789844759750875">আরো ডেটা কেনার জন্য <ph name="NAME"/> সক্রিয়করণ পোর্টাল ঘুরে দেখুন৷</translation>
+<translation id="3901991538546252627"><ph name="NAME"/>-এ সংযোগ করা হচ্ছে</translation>
+<translation id="2204305834655267233">নেটওয়ার্ক তথ্য</translation>
+<translation id="1621499497873603021">ব্যাটারি শেষ হতে <ph name="TIME_LEFT"/> সময় বাকি আছে</translation>
+<translation id="5980301590375426705">অতিথি থেকে প্রস্থান</translation>
+<translation id="4471417012762451363">ব্যাটারি <ph name="PERCENTAGE"/>% পরিপূর্ণ এবং চার্জ হচ্ছে</translation>
+<translation id="8308637677604853869">পূর্ববর্তী মেনু</translation>
+<translation id="4666297444214622512">অন্য একটি অ্যাকাউন্টে সাইন ইন করতে পারবেন না৷</translation>
+<translation id="1346748346194534595">ডান</translation>
+<translation id="1773212559869067373">স্থানীয়ভাবে প্রমাণীকরণ শংসাপত্র প্রত্যাখ্যান করা হয়েছে</translation>
+<translation id="8528322925433439945">মোবাইল ...</translation>
+<translation id="7049357003967926684">সমিতি</translation>
+<translation id="8428213095426709021">সেটিংস</translation>
+<translation id="2372145515558759244">অ্যাপ্লিকেশনগুলি সিঙ্ক হচ্ছে...</translation>
+<translation id="7256405249507348194">অস্বীকৃত ত্রুটি: <ph name="DESC"/></translation>
+<translation id="7925247922861151263">AAA যাচাই ব্যর্থ</translation>
+<translation id="8456362689280298700"><ph name="HOUR"/>:<ph name="MINUTE"/> বাকি পুরো চার্জ হতে</translation>
+<translation id="5787281376604286451">কথ্য প্রতিক্রিয়া সক্ষমিত৷
+অক্ষম করতে Ctrl + Alt + Z টিপুন৷</translation>
+<translation id="4479639480957787382">ইথারনেট</translation>
+<translation id="6312403991423642364">অজানা নেটওয়ার্ক ত্রুটি</translation>
+<translation id="1467432559032391204">বাম</translation>
+<translation id="5543001071567407895">SMS</translation>
+<translation id="2354174487190027830"><ph name="NAME"/> সক্রিয় করা হচ্ছে</translation>
+<translation id="8814190375133053267">Wi-Fi</translation>
+<translation id="1398853756734560583">বড় করুন</translation>
+<translation id="2692809339924654275"><ph name="BLUETOOTH"/>: সংযুক্ত হচ্ছে...</translation>
+<translation id="252373100621549798">অজানা প্রদর্শন</translation>
+<translation id="1882897271359938046"><ph name="DISPLAY_NAME"/> তে প্রতিবিম্বিত হচ্ছে</translation>
+<translation id="2727977024730340865">একটি নিম্ন শক্তির চার্জার প্লাগইন করা হয়েছে৷ বিশ্বস্ত ব্যাটারি চার্জ নাও হতে পারে৷</translation>
+<translation id="3784455785234192852">লক করুন</translation>
+<translation id="2805756323405976993">Apps</translation>
+<translation id="8871072142849158571"><ph name="DISPLAY_NAME"/> এর আকার পরিবর্তন করে <ph name="RESOLUTION"/> করা হয়েছে</translation>
+<translation id="1512064327686280138">সক্রিয়করণে ব্যর্থতা</translation>
+<translation id="5097002363526479830">নেটওয়ার্কের সাথে সংযোগ করতে ব্যর্থ '<ph name="NAME"/>': <ph name="DETAILS"/></translation>
+<translation id="1850504506766569011">Wi-Fi বন্ধ আছে৷</translation>
+<translation id="8132793192354020517"><ph name="NAME"/> তে সংযুক্ত</translation>
+<translation id="7052914147756339792">ওয়ালপেপার সেট করুন...</translation>
+<translation id="8678698760965522072">অনলাইন স্থিতি</translation>
+<translation id="2532589005999780174">উচ্চ কনট্রাস্ট মোড</translation>
+<translation id="1119447706177454957">অভ্যন্তরীণ ত্রুটি</translation>
+<translation id="3019353588588144572">ব্যাটারি পরিপূর্ণ চার্জ হতে, <ph name="TIME_REMAINING"/> সময় বাকি আছে</translation>
+<translation id="3473479545200714844">স্ক্রীন ম্যাগনিফায়ার</translation>
+<translation id="7005812687360380971">ব্যর্থতা</translation>
+<translation id="882279321799040148">দেখার জন্য ক্লিক করুন</translation>
+<translation id="5045550434625856497">ভুল পাসওয়ার্ড</translation>
+<translation id="1602076796624386989">মোবাইল ডেটা সক্রিয় করুন</translation>
+<translation id="6981982820502123353">অ্যাক্সেযোগ্যতা</translation>
+<translation id="3157931365184549694">পুনরুদ্ধার করুন</translation>
+<translation id="4274292172790327596">অস্বীকৃত ত্রুটি</translation>
+<translation id="4032485810211612751"><ph name="HOURS"/>:<ph name="MINUTES"/>:<ph name="SECONDS"/></translation>
+<translation id="225680501294068881">ডিভাইসগুলির জন্য স্ক্যান করা হচ্ছে...</translation>
+<translation id="5597451508971090205"><ph name="SHORT_WEEKDAY"/>, <ph name="DATE"/></translation>
+<translation id="4448844063988177157">Wi-Fi নেটওয়ার্কগুলির জন্য অনুসন্ধান করুন...</translation>
+<translation id="8401662262483418323">'<ph name="NAME"/>' এর সাথে সংযোগ করা ব্যর্থ হয়েছে: <ph name="DETAILS"/>
+সার্ভার বার্তা: <ph name="SERVER_MSG"/></translation>
+<translation id="2475982808118771221">একটি ত্রুটি ঘটেছে</translation>
+<translation id="7229570126336867161">EVDO এর প্রয়োজন</translation>
+<translation id="2999742336789313416"><ph name="DISPLAY_NAME"/> হল <ph name="DOMAIN"/> এর দ্বারা পরিচালিত একটি সর্বজনীন সেশন</translation>
+<translation id="7029814467594812963">সেশন থেকে প্রস্থান</translation>
+<translation id="8454013096329229812">Wi-Fi চালু আছে৷</translation>
+<translation id="4872237917498892622">Alt+Search অথবা Shift</translation>
+<translation id="2983818520079887040">সেটিংস...</translation>
+<translation id="1717216362413677834">ডক মোড</translation>
+<translation id="8927026611342028580">সংযুক্ত করার অনুরোধ করা হয়েছে</translation>
+<translation id="8300849813060516376">OTASP ব্যর্থ</translation>
+<translation id="2792498699870441125">Alt+Search</translation>
+<translation id="8660803626959853127"><ph name="COUNT"/>টি ফাইল (গুলি) সিঙ্ক হচ্ছে</translation>
+<translation id="3709443003275901162">৯+</translation>
+<translation id="639644700271529076">CAPS LOCK বন্ধ আছে</translation>
+<translation id="6248847161401822652">প্রস্থান করার জন্য দুবার Control Shift Q টিপুন৷</translation>
+<translation id="6267036997247669271"><ph name="NAME"/>: সক্রিয় করা হচ্ছে...</translation>
+<translation id="1391854757121130358">আপনি হয়তো আপনার মোবাইল ডেটা অ্যালাউন্স ব্যবহার করেছেন৷</translation>
+<translation id="5413208160176941586">স্থানীয়ভাবে পরিচালিত ব্যবহারকারী</translation>
+<translation id="1059194134494239015"><ph name="DISPLAY_NAME"/>: <ph name="RESOLUTION"/></translation>
+<translation id="4864165860509564259">লঞ্চার অবস্থান</translation>
+<translation id="7593891976182323525">Search অথবা Shift</translation>
+<translation id="7649070708921625228">সহায়তা</translation>
+<translation id="3050422059534974565">CAPS LOCK চালু আছে৷
+Search অথবা বাতিল করতে Shift টিপুন৷</translation>
+<translation id="397105322502079400">গণনা করা হচ্ছে...</translation>
+<translation id="158849752021629804">হোম নেটওয়ার্কের প্রয়োজন</translation>
+<translation id="6857811139397017780">সক্রিয় করুন <ph name="NETWORKSERVICE"/></translation>
+<translation id="5864471791310927901">DHCP লুকআপ ব্যর্থ</translation>
+<translation id="5812035014844949013">আউটপুট</translation>
+<translation id="6692173217867674490">খারাপ পাসফ্রেজ</translation>
+<translation id="6165508094623778733">আরো জানুন</translation>
+<translation id="9046895021617826162">সংযোগ ব্যর্থ হয়েছে</translation>
+<translation id="973896785707726617">এই সেশনটি <ph name="SESSION_TIME_REMAINING"/> এর মধ্যে সমাপ্ত হবে৷ আপনি স্বয়ংক্রিয়ভাবে সাইন আউট হয়ে যাবেন৷</translation>
+<translation id="8372369524088641025">খারাপ WEP কী</translation>
+<translation id="6636709850131805001">অস্বীকৃত স্থিতি</translation>
+<translation id="3573179567135747900">&quot;<ph name="FROM_LOCALE"/>&quot; তে ফেরান (পুনর্সূচনা প্রয়োজন)</translation>
+<translation id="8103386449138765447">SMS বার্তা: <ph name="MESSAGE_COUNT"/></translation>
+<translation id="5045002648206642691">Google ড্রাইভ সেটিংস ...</translation>
+<translation id="1510238584712386396">লঞ্চার</translation>
+<translation id="7209101170223508707">CAPS LOCK চালু আছে৷
+Alt+Search অথবা বাতিল করতে Shift টিপুন৷</translation>
+<translation id="8940956008527784070">(<ph name="PERCENTAGE"/>%) কম ব্যাটারি</translation>
+<translation id="5102001756192215136"><ph name="HOUR"/>:<ph name="MINUTE"/> বাকি</translation>
+<translation id="520760366042891468">Hangouts এর মাধ্যমে আপনার স্ক্রীন নিয়ন্ত্রণ ভাগ করা হচ্ছে৷</translation>
+<translation id="8000066093800657092">কোনও নেটওয়ার্ক নেই</translation>
+<translation id="4015692727874266537">অন্য একটি অ্যাকাউন্টে সাইন ইন করুন...</translation>
+<translation id="5941711191222866238">ছোট করুন</translation>
+<translation id="6911468394164995108">অন্যান্য যোগদান ...</translation>
+<translation id="412065659894267608"><ph name="HOUR"/>ঘন্টা <ph name="MINUTE"/> মিনিট পর্যন্ত পূর্ণ</translation>
+<translation id="6359806961507272919"><ph name="PHONE_NUMBER"/>-এর থেকে SMS...</translation>
+<translation id="1244147615850840081">কেরিয়ার</translation>
+</translationbundle> \ No newline at end of file
diff --git a/chromium/ash/strings/ash_strings_ca.xtb b/chromium/ash/strings/ash_strings_ca.xtb
new file mode 100644
index 00000000000..325713c2dff
--- /dev/null
+++ b/chromium/ash/strings/ash_strings_ca.xtb
@@ -0,0 +1,201 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="ca">
+<translation id="3595596368722241419">Bateria carregada.</translation>
+<translation id="5250713215130379958">Oculta automàticament la barra d'execució ràpida</translation>
+<translation id="7814236020522506259"><ph name="HOUR"/> i <ph name="MINUTE"/></translation>
+<translation id="7880025619322806991">Estat del portal</translation>
+<translation id="30155388420722288">Botó de desbordament</translation>
+<translation id="5571066253365925590">S'ha activat el Bluetooth</translation>
+<translation id="9074739597929991885">Bluetooth</translation>
+<translation id="2268130516524549846">S'ha desactivat el Bluetooth</translation>
+<translation id="3775358506042162758">Només podeu tenir tres comptes com a màxim en un inici de sessió múltiple.</translation>
+<translation id="370649949373421643">Activa la Wi-Fi</translation>
+<translation id="3626281679859535460">Brillantor</translation>
+<translation id="8054466585765276473">S'està calculant el temps de la bateria.</translation>
+<translation id="7982789257301363584">Xarxa</translation>
+<translation id="5565793151875479467">Servidor intermediari...</translation>
+<translation id="938582441709398163">Superposició de teclat</translation>
+<translation id="4387004326333427325">S'ha rebutjat el certificat d'autenticació de manera remota</translation>
+<translation id="6979158407327259162">Google Drive</translation>
+<translation id="6943836128787782965">La sol·licitud HTTP ha fallat</translation>
+<translation id="2297568595583585744">Safata d'estat</translation>
+<translation id="1661867754829461514">Falta el PIN</translation>
+<translation id="4508225577814909926"><ph name="NAME"/>: s'està connectant...</translation>
+<translation id="4237016987259239829">Error de connexió a la xarxa</translation>
+<translation id="2946640296642327832">Activa el Bluetooth</translation>
+<translation id="6459472438155181876">S'està ampliant la pantalla a <ph name="DISPLAY_NAME"/></translation>
+<translation id="8206859287963243715">Cel·lular</translation>
+<translation id="6596816719288285829">Adreça IP</translation>
+<translation id="4508265954913339219">S'ha produït un error en l'activació.</translation>
+<translation id="3621712662352432595">Configuració d'àudio</translation>
+<translation id="1812696562331527143">El mètode d'entrada ha canviat a <ph name="INPUT_METHOD_ID"/>* (<ph name="BEGIN_LINK"/>tercers<ph name="END_LINK"/>).
+Premeu Maj+Alt per canviar-lo.</translation>
+<translation id="2127372758936585790">Carregador de baix consum</translation>
+<translation id="3846575436967432996">No hi ha informació de xarxa disponible</translation>
+<translation id="3026237328237090306">Configura les dades mòbils</translation>
+<translation id="785750925697875037">Mostra el compte mòbil</translation>
+<translation id="153454903766751181">S'està inicialitzant el mòdem mòbil...</translation>
+<translation id="4628814525959230255">Comparteix el control de la pantalla amb <ph name="HELPER_NAME"/> mitjançant Hangouts.</translation>
+<translation id="8343941333792395995">S'ha girat <ph name="DISPLAY_NAME"/></translation>
+<translation id="7864539943188674973">Desactiva el Bluetooth</translation>
+<translation id="939252827960237676">S'ha produït un error en desar la captura de pantalla.</translation>
+<translation id="3126069444801937830">Reinicia per actualitzar</translation>
+<translation id="2268813581635650749">Tanca la sessió de tots els usuaris</translation>
+<translation id="735745346212279324">VPN desconnectada</translation>
+<translation id="7320906967354320621">Inactiu</translation>
+<translation id="6303423059719347535">La bateria està carregada fins al <ph name="PERCENTAGE"/>%</translation>
+<translation id="15373452373711364">Cursor del ratolí gran</translation>
+<translation id="2778346081696727092">No s'ha pogut autenticar amb el nom d'usuari o amb la contrasenya que heu proporcionat</translation>
+<translation id="3294437725009624529">Convidat</translation>
+<translation id="8190698733819146287">Personalitza els idiomes i l'entrada...</translation>
+<translation id="2903907270192926896">ENTRADA</translation>
+<translation id="8676770494376880701">S'ha connectat un carregador de baix consum</translation>
+<translation id="7170041865419449892">Fora de l'interval</translation>
+<translation id="4804818685124855865">Desconnecta</translation>
+<translation id="2544853746127077729">La xarxa ha rebutjat el certificat d'autenticació</translation>
+<translation id="5222676887888702881">Tanca la sessió</translation>
+<translation id="2688477613306174402">Configuració</translation>
+<translation id="1272079795634619415">Atura</translation>
+<translation id="4957722034734105353">Més informació...</translation>
+<translation id="2964193600955408481">Desactiva la Wi-Fi</translation>
+<translation id="811680302244032017">Afegeix un dispositiu...</translation>
+<translation id="4279490309300973883">S'està creant una rèplica</translation>
+<translation id="2509468283778169019">BLOQ MAJ està activat.</translation>
+<translation id="3892641579809465218">Pantalla interna</translation>
+<translation id="7823564328645135659">L'idioma ha canviat de &quot;<ph name="FROM_LOCALE"/>&quot; a &quot;<ph name="TO_LOCALE"/>&quot; després de sincronitzar la vostra configuració.</translation>
+<translation id="3368922792935385530">Connectat</translation>
+<translation id="8340999562596018839">Comentaris de veu</translation>
+<translation id="8654520615680304441">Activa la Wi-Fi...</translation>
+<translation id="5825747213122829519">El mètode d'entrada ha canviat a <ph name="INPUT_METHOD_ID"/>.
+Premeu Maj+Alt per canviar-lo.</translation>
+<translation id="2562916301614567480">Xarxa privada</translation>
+<translation id="6549021752953852991">No hi ha cap xarxa mòbil disponible</translation>
+<translation id="4379753398862151997">Estimat monitor, això no funciona. (El monitor no és compatible)</translation>
+<translation id="6426039856985689743">Desactiva les dades mòbils</translation>
+<translation id="3087734570205094154">Part inferior</translation>
+<translation id="3742055079367172538">Captura de pantalla feta</translation>
+<translation id="8878886163241303700">Ampliació de la pantalla</translation>
+<translation id="5271016907025319479">La VPN no està configurada.</translation>
+<translation id="372094107052732682">Premeu Ctrl+Maj+Q dues vegades per sortir.</translation>
+<translation id="6803622936009808957">No s'han pogut reflectir les pantalles, perquè no s'ha trobat cap resolució compatible. S'està entrant al mode d'escriptori ampliat.</translation>
+<translation id="1480041086352807611">Mode de demostració</translation>
+<translation id="3626637461649818317">Queda un <ph name="PERCENTAGE"/>%</translation>
+<translation id="9089416786594320554">Mètodes d'introducció</translation>
+<translation id="6247708409970142803"><ph name="PERCENTAGE"/>%</translation>
+<translation id="2614835198358683673">És possible que el vostre Chromebook no es carregui mentre estigui encès. Proveu de fer servir un carregador oficial.</translation>
+<translation id="1895658205118569222">Aturada</translation>
+<translation id="4430019312045809116">Volum</translation>
+<translation id="4442424173763614572">La cerca de DNS ha fallat</translation>
+<translation id="6356500677799115505">La bateria és plena i s'està carregant.</translation>
+<translation id="7874779702599364982">S'estan cercant xarxes mòbils...</translation>
+<translation id="583281660410589416">Desconegut</translation>
+<translation id="1383876407941801731">Cerca</translation>
+<translation id="7468789844759750875">Visiteu el portal d'activació de <ph name="NAME"/> per comprar més dades.</translation>
+<translation id="3901991538546252627">S'està connectant amb <ph name="NAME"/></translation>
+<translation id="2204305834655267233">Informació de la xarxa</translation>
+<translation id="1621499497873603021">Temps que queda fins que no s'esgoti la bateria: <ph name="TIME_LEFT"/></translation>
+<translation id="5980301590375426705">Surt de la sessió de convidat</translation>
+<translation id="4471417012762451363">La bateria està carregada fins al <ph name="PERCENTAGE"/>% i s'està carregant</translation>
+<translation id="8308637677604853869">Menú anterior</translation>
+<translation id="4666297444214622512">No podeu iniciar la sessió en un altre compte.</translation>
+<translation id="1346748346194534595">A la dreta</translation>
+<translation id="1773212559869067373">S'ha rebutjat el certificat d'autenticació de manera local</translation>
+<translation id="8528322925433439945">Xarxes mòbils...</translation>
+<translation id="7049357003967926684">Associació</translation>
+<translation id="8428213095426709021">Configuració</translation>
+<translation id="2372145515558759244">S'estan sincronitzant les aplicacions...</translation>
+<translation id="7256405249507348194">Error no reconegut: <ph name="DESC"/></translation>
+<translation id="7925247922861151263">Error en la comprovació d'AAA</translation>
+<translation id="8456362689280298700">Temps per a càrrega completa: <ph name="HOUR"/>:<ph name="MINUTE"/></translation>
+<translation id="5787281376604286451">Els comentaris de veu estan activats.
+Premeu Ctrl+Alt+Z per desactivar-los.</translation>
+<translation id="4479639480957787382">Ethernet</translation>
+<translation id="6312403991423642364">Error de xarxa desconegut</translation>
+<translation id="1467432559032391204">A l'esquerra</translation>
+<translation id="5543001071567407895">SMS</translation>
+<translation id="2354174487190027830">S'està activant <ph name="NAME"/></translation>
+<translation id="8814190375133053267">Wi-Fi</translation>
+<translation id="1398853756734560583">Maximitza</translation>
+<translation id="2692809339924654275"><ph name="BLUETOOTH"/>: s'està connectant...</translation>
+<translation id="252373100621549798">Pantalla desconeguda</translation>
+<translation id="1882897271359938046">S'està replicant <ph name="DISPLAY_NAME"/></translation>
+<translation id="2727977024730340865">S'ha connectat a un carregador de baix consum. És possible que la càrrega de la bateria no sigui fiable.</translation>
+<translation id="3784455785234192852">Bloqueja</translation>
+<translation id="2805756323405976993">Aplicacions</translation>
+<translation id="8871072142849158571">S'ha canviat la resolució de <ph name="DISPLAY_NAME"/> a <ph name="RESOLUTION"/></translation>
+<translation id="1512064327686280138">Error d'activació</translation>
+<translation id="5097002363526479830">S'ha produït un error en connectar amb la xarxa &quot;<ph name="NAME"/>&quot;: <ph name="DETAILS"/></translation>
+<translation id="1850504506766569011">La Wi-Fi està desactivada.</translation>
+<translation id="8132793192354020517">Connectat a <ph name="NAME"/></translation>
+<translation id="7052914147756339792">Defineix l'empaperat...</translation>
+<translation id="8678698760965522072">Estat en línia</translation>
+<translation id="2532589005999780174">Mode de contrast elevat</translation>
+<translation id="1119447706177454957">Error intern</translation>
+<translation id="3019353588588144572">Temps restant fins que la bateria no estigui totalment carregada: <ph name="TIME_REMAINING"/></translation>
+<translation id="3473479545200714844">Augment de pantalla</translation>
+<translation id="7005812687360380971">Error</translation>
+<translation id="882279321799040148">Feu clic per visualitzar-la.</translation>
+<translation id="5045550434625856497">Contrasenya incorrecta</translation>
+<translation id="1602076796624386989">Activa les dades mòbils</translation>
+<translation id="6981982820502123353">Accessibilitat</translation>
+<translation id="3157931365184549694">Restaura</translation>
+<translation id="4274292172790327596">Error no reconegut</translation>
+<translation id="4032485810211612751"><ph name="HOURS"/>:<ph name="MINUTES"/>:<ph name="SECONDS"/></translation>
+<translation id="225680501294068881">S'estan cercant dispositius...</translation>
+<translation id="5597451508971090205"><ph name="SHORT_WEEKDAY"/>, <ph name="DATE"/></translation>
+<translation id="4448844063988177157">S'estan cercant xarxes Wi-Fi...</translation>
+<translation id="8401662262483418323">No s'ha pogut connectar amb &quot;<ph name="NAME"/>&quot;: <ph name="DETAILS"/>
+Missatge del servidor: <ph name="SERVER_MSG"/></translation>
+<translation id="2475982808118771221">S'ha produït un error</translation>
+<translation id="7229570126336867161">Es necessita EVDO</translation>
+<translation id="2999742336789313416"><ph name="DISPLAY_NAME"/> és una sessió pública gestionada per <ph name="DOMAIN"/>.</translation>
+<translation id="7029814467594812963">Tanca la sessió</translation>
+<translation id="8454013096329229812">La Wi-Fi està activada.</translation>
+<translation id="4872237917498892622">Alt+Cerca o Maj</translation>
+<translation id="2983818520079887040">Configuració...</translation>
+<translation id="1717216362413677834">Mode connectat</translation>
+<translation id="8927026611342028580">S'ha sol·licitat la connexió</translation>
+<translation id="8300849813060516376">Error d'OTASP</translation>
+<translation id="2792498699870441125">Alt+Cerca</translation>
+<translation id="8660803626959853127">Fitxers que s'estan sincronitzant: <ph name="COUNT"/></translation>
+<translation id="3709443003275901162">Més de 9</translation>
+<translation id="639644700271529076">Bloq Maj està desactivat</translation>
+<translation id="6248847161401822652">Premeu Control+Maj+Q dues vegades per sortir.</translation>
+<translation id="6267036997247669271"><ph name="NAME"/>: s'està activant...</translation>
+<translation id="1391854757121130358">Pot ser que hàgiu esgotat les dades mòbils.</translation>
+<translation id="5413208160176941586">Usuari gestionat localment</translation>
+<translation id="1059194134494239015"><ph name="DISPLAY_NAME"/>: <ph name="RESOLUTION"/></translation>
+<translation id="4864165860509564259">Posició de la barra d'execució ràpida</translation>
+<translation id="7593891976182323525">Cerca o Maj</translation>
+<translation id="7649070708921625228">Ajuda</translation>
+<translation id="3050422059534974565">Bloq Maj està activat.
+Premeu Cerca o Maj per cancel·lar.</translation>
+<translation id="397105322502079400">S’està calculant...</translation>
+<translation id="158849752021629804">Es necessita una xarxa domèstica</translation>
+<translation id="6857811139397017780">Activa <ph name="NETWORKSERVICE"/></translation>
+<translation id="5864471791310927901">Error en la cerca de DHCP</translation>
+<translation id="5812035014844949013">SORTIDA</translation>
+<translation id="6692173217867674490">Contrasenya no vàlida</translation>
+<translation id="6165508094623778733">Més informació</translation>
+<translation id="9046895021617826162">S'ha produït un error en la connexió</translation>
+<translation id="973896785707726617">La sessió finalitzarà d'aquí a <ph name="SESSION_TIME_REMAINING"/>. Es tancarà la sessió automàticament.</translation>
+<translation id="8372369524088641025">Clau WEP no vàlida</translation>
+<translation id="6636709850131805001">Estat no reconegut</translation>
+<translation id="3573179567135747900">Torna a canviar a &quot;<ph name="FROM_LOCALE"/>&quot; (requereix reiniciar)</translation>
+<translation id="8103386449138765447">Missatges SMS: <ph name="MESSAGE_COUNT"/></translation>
+<translation id="5045002648206642691">Configuració de Google Drive...</translation>
+<translation id="1510238584712386396">Menú d'aplicacions</translation>
+<translation id="7209101170223508707">Bloq Maj està activat.
+Premeu Alt+Cerca o Maj per cancel·lar.</translation>
+<translation id="8940956008527784070">Bateria baixa (<ph name="PERCENTAGE"/>%)</translation>
+<translation id="5102001756192215136">Temps d'autonomia: <ph name="HOUR"/>:<ph name="MINUTE"/></translation>
+<translation id="520760366042891468">Compartiu el control de la pantalla mitjançant Hangouts.</translation>
+<translation id="8000066093800657092">No hi ha xarxa</translation>
+<translation id="4015692727874266537">Inicia la sessió amb un altre compte...</translation>
+<translation id="5941711191222866238">Minimitza</translation>
+<translation id="6911468394164995108">Uneix-te a una altra...</translation>
+<translation id="412065659894267608">Falten <ph name="HOUR"/> h <ph name="MINUTE"/> min fins que estigui carregada completament</translation>
+<translation id="6359806961507272919">SMS de <ph name="PHONE_NUMBER"/></translation>
+<translation id="1244147615850840081">Operador</translation>
+</translationbundle> \ No newline at end of file
diff --git a/chromium/ash/strings/ash_strings_cs.xtb b/chromium/ash/strings/ash_strings_cs.xtb
new file mode 100644
index 00000000000..cc7cafc6f7f
--- /dev/null
+++ b/chromium/ash/strings/ash_strings_cs.xtb
@@ -0,0 +1,198 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="cs">
+<translation id="3595596368722241419">Baterie je nabitá</translation>
+<translation id="5250713215130379958">Automaticky skrýt spouštěč</translation>
+<translation id="7814236020522506259"><ph name="HOUR"/> a <ph name="MINUTE"/></translation>
+<translation id="7880025619322806991">Stav portálu</translation>
+<translation id="30155388420722288">Tlačítko přetečení</translation>
+<translation id="5571066253365925590">Rozhraní Bluetooth aktivováno</translation>
+<translation id="9074739597929991885">Bluetooth</translation>
+<translation id="2268130516524549846">Rozhraní Bluetooth deaktivováno</translation>
+<translation id="3775358506042162758">V rámci vícenásobného přihlášení lze používat maximálně tři účty.</translation>
+<translation id="370649949373421643">Povolit Wi-Fi</translation>
+<translation id="3626281679859535460">Jas</translation>
+<translation id="8054466585765276473">Výpočet doby výdrže baterie.</translation>
+<translation id="7982789257301363584">Síť</translation>
+<translation id="5565793151875479467">Server proxy...</translation>
+<translation id="938582441709398163">Překryvná klávesnice</translation>
+<translation id="4387004326333427325">Ověřovací certifikát byl vzdáleně odmítnut</translation>
+<translation id="6979158407327259162">Disk Google</translation>
+<translation id="6943836128787782965">Příkaz GET protokolu HTTP se nezdařil.</translation>
+<translation id="2297568595583585744">Stavový panel</translation>
+<translation id="1661867754829461514">Chybí kód PIN</translation>
+<translation id="4508225577814909926"><ph name="NAME"/>: Připojování...</translation>
+<translation id="4237016987259239829">Chyba připojení k síti</translation>
+<translation id="2946640296642327832">Zapnout Bluetooth</translation>
+<translation id="6459472438155181876">Rozšíření obrazovky na displej <ph name="DISPLAY_NAME"/></translation>
+<translation id="8206859287963243715">Mobil</translation>
+<translation id="6596816719288285829">IP adresa</translation>
+<translation id="4508265954913339219">Aktivace se nezdařila</translation>
+<translation id="3621712662352432595">Nastavení zvuku</translation>
+<translation id="1812696562331527143">Metoda zadávání se změnila na metodu <ph name="INPUT_METHOD_ID"/>* <ph name="BEGIN_LINK"/>třetí strany<ph name="END_LINK"/>. Přepnout ji můžete stisknutím klávesové zkratky Shift + Alt.</translation>
+<translation id="2127372758936585790">Nabíječka má příliš nízký výkon</translation>
+<translation id="3846575436967432996">Informace o síti nejsou k dispozici</translation>
+<translation id="3026237328237090306">Nastavení mobilního datového připojení</translation>
+<translation id="785750925697875037">Zobrazit mobilní účet</translation>
+<translation id="153454903766751181">Inicializace mobilního modemu...</translation>
+<translation id="4628814525959230255">Sdílíte ovládání obrazovky s uživatelem <ph name="HELPER_NAME"/> (prostřednictvím služby Hangouts).</translation>
+<translation id="8343941333792395995">Displej <ph name="DISPLAY_NAME"/> byl pootočen</translation>
+<translation id="7864539943188674973">Vypnout Bluetooth</translation>
+<translation id="939252827960237676">Uložení snímku obrazovky se nezdařilo</translation>
+<translation id="3126069444801937830">Restartovat a aktualizovat</translation>
+<translation id="2268813581635650749">Odhlásit vše</translation>
+<translation id="735745346212279324">Síť VPN je odpojena</translation>
+<translation id="7320906967354320621">Nečinnost</translation>
+<translation id="6303423059719347535">Baterie je nabitá na <ph name="PERCENTAGE"/> %</translation>
+<translation id="15373452373711364">Velký kurzor myši</translation>
+<translation id="2778346081696727092">Ověření prostřednictvím uvedeného uživatelského jména nebo hesla se nepodařilo.</translation>
+<translation id="3294437725009624529">Host</translation>
+<translation id="8190698733819146287">Personalizovat jazyky a zadávání...</translation>
+<translation id="2903907270192926896">VSTUP</translation>
+<translation id="8676770494376880701">Byla připojena nabíječka s nízkým napětím</translation>
+<translation id="7170041865419449892">Mimo dosah</translation>
+<translation id="4804818685124855865">Odpojit</translation>
+<translation id="2544853746127077729">Ověřovací certifikát byl sítí odmítnut</translation>
+<translation id="5222676887888702881">Odhlásit se</translation>
+<translation id="2688477613306174402">Konfigurace</translation>
+<translation id="1272079795634619415">Zastavit</translation>
+<translation id="4957722034734105353">Další informace...</translation>
+<translation id="2964193600955408481">Vypnout Wi-Fi</translation>
+<translation id="811680302244032017">Přidat zařízení...</translation>
+<translation id="4279490309300973883">Zrcadlení</translation>
+<translation id="2509468283778169019">CAPS LOCK je zapnutý</translation>
+<translation id="3892641579809465218">Interní displej</translation>
+<translation id="7823564328645135659">Jazyk prohlížeče Chrome se po synchronizaci nastavení změnil z jazyka <ph name="FROM_LOCALE"/> na jazyk <ph name="TO_LOCALE"/>.</translation>
+<translation id="3368922792935385530">Připojeno</translation>
+<translation id="8340999562596018839">Hlasová odezva</translation>
+<translation id="8654520615680304441">Zapnout Wi-Fi...</translation>
+<translation id="5825747213122829519">Metoda zadávání se změnila na metodu <ph name="INPUT_METHOD_ID"/>. Přepnout ji můžete stisknutím klávesové zkratky Shift + Alt.</translation>
+<translation id="2562916301614567480">Soukromá síť</translation>
+<translation id="6549021752953852991">Není k dispozici žádná mobilní síť.</translation>
+<translation id="4379753398862151997">Drahý monitore, nějak to mezi námi nefunguje. (Monitor není podporován.)</translation>
+<translation id="6426039856985689743">Zakázat mobilní datové připojení</translation>
+<translation id="3087734570205094154">Až dolů</translation>
+<translation id="3742055079367172538">Byl vytvořen snímek obrazovky</translation>
+<translation id="8878886163241303700">Rozšíření obrazovky</translation>
+<translation id="5271016907025319479">Síť VPN není nakonfigurována.</translation>
+<translation id="372094107052732682">Práci ukončíte dvojitým stisknutím kombinace kláves Ctrl+Shift+Q.</translation>
+<translation id="6803622936009808957">Zobrazení nelze zrcadlit, protože nebyla nalezena podporovaná rozlišení. Místo toho se spustil režim rozšířené pracovní plochy.</translation>
+<translation id="1480041086352807611">Režim ukázky</translation>
+<translation id="3626637461649818317">Zbývá <ph name="PERCENTAGE"/> %</translation>
+<translation id="9089416786594320554">Metody zadávání dat</translation>
+<translation id="6247708409970142803"><ph name="PERCENTAGE"/> %</translation>
+<translation id="2614835198358683673">Když bude Chromebook zapnutý, možná se nebude nabíjet. Doporučujeme použít oficiální nabíječku.</translation>
+<translation id="1895658205118569222">Vypnout počítač</translation>
+<translation id="4430019312045809116">Hlasitost</translation>
+<translation id="4442424173763614572">Nepodařilo se nalézt server DNS.</translation>
+<translation id="6356500677799115505">Baterie je plně nabitá a nabíjí se.</translation>
+<translation id="7874779702599364982">Vyhledávání mobilních sítí...</translation>
+<translation id="583281660410589416">Neznámý</translation>
+<translation id="1383876407941801731">Vyhledávání</translation>
+<translation id="7468789844759750875">Chcete-li koupit více dat, navštivte aktivační portál <ph name="NAME"/>.</translation>
+<translation id="3901991538546252627">Připojování k síti <ph name="NAME"/></translation>
+<translation id="2204305834655267233">Informace o síti</translation>
+<translation id="1621499497873603021">Čas zbývající do vybití baterie: <ph name="TIME_LEFT"/></translation>
+<translation id="5980301590375426705">Odhlásit hosta</translation>
+<translation id="4471417012762451363">Baterie je nabitá na <ph name="PERCENTAGE"/> % a nabíjí se</translation>
+<translation id="8308637677604853869">Předchozí nabídka</translation>
+<translation id="4666297444214622512">Nelze se přihlásit k jinému účtu.</translation>
+<translation id="1346748346194534595">Doprava</translation>
+<translation id="1773212559869067373">Ověřovací certifikát byl místně odmítnut</translation>
+<translation id="8528322925433439945">Mobilní sítě...</translation>
+<translation id="7049357003967926684">Přidružení</translation>
+<translation id="8428213095426709021">Nastavení</translation>
+<translation id="2372145515558759244">Synchronizace aplikací...</translation>
+<translation id="7256405249507348194">Neznámá chyba: <ph name="DESC"/></translation>
+<translation id="7925247922861151263">Selhala bezpečnostní kontrola AAA</translation>
+<translation id="8456362689280298700"><ph name="HOUR"/>:<ph name="MINUTE"/> do úplného nabití</translation>
+<translation id="5787281376604286451">Hlasová odezva je aktivní
+Stiskem kláves Ctrl+Alt+Z ji deaktivujete.</translation>
+<translation id="4479639480957787382">Ethernet</translation>
+<translation id="6312403991423642364">Neznámá chyba sítě</translation>
+<translation id="1467432559032391204">Doleva</translation>
+<translation id="5543001071567407895">SMS</translation>
+<translation id="2354174487190027830">Aktivace sítě <ph name="NAME"/></translation>
+<translation id="8814190375133053267">Wi-Fi</translation>
+<translation id="1398853756734560583">Maximalizovat</translation>
+<translation id="2692809339924654275"><ph name="BLUETOOTH"/>: Připojování...</translation>
+<translation id="252373100621549798">Neznámý displej</translation>
+<translation id="1882897271359938046">Zrcadlení na displej <ph name="DISPLAY_NAME"/></translation>
+<translation id="2727977024730340865">Byla připojena nabíječka s nízkým výkonem. Nabíjení baterie nemusí probíhat spolehlivě.</translation>
+<translation id="3784455785234192852">Uzamknout</translation>
+<translation id="2805756323405976993">Aplikace</translation>
+<translation id="8871072142849158571">Rozlišení displeje <ph name="DISPLAY_NAME"/> bylo změněno na <ph name="RESOLUTION"/></translation>
+<translation id="1512064327686280138">Aktivace se nezdařila</translation>
+<translation id="5097002363526479830">Připojení k síti <ph name="NAME"/> se nezdařilo: <ph name="DETAILS"/></translation>
+<translation id="1850504506766569011">Připojení Wi-Fi je vypnuto.</translation>
+<translation id="8132793192354020517">Připojeno k síti <ph name="NAME"/></translation>
+<translation id="7052914147756339792">Nastavení tapety...</translation>
+<translation id="8678698760965522072">Stav online</translation>
+<translation id="2532589005999780174">Režim vysokého kontrastu</translation>
+<translation id="1119447706177454957">Interní chyba</translation>
+<translation id="3019353588588144572">Čas zbývající do úplného nabití baterie: <ph name="TIME_REMAINING"/></translation>
+<translation id="3473479545200714844">Lupa obrazovky</translation>
+<translation id="7005812687360380971">Selhání</translation>
+<translation id="882279321799040148">Kliknutím jej zobrazíte</translation>
+<translation id="5045550434625856497">Nesprávné heslo</translation>
+<translation id="1602076796624386989">Povolit mobilní datové připojení</translation>
+<translation id="6981982820502123353">Usnadnění</translation>
+<translation id="3157931365184549694">Obnovit</translation>
+<translation id="4274292172790327596">Neznámá chyba</translation>
+<translation id="4032485810211612751"><ph name="HOURS"/>:<ph name="MINUTES"/>:<ph name="SECONDS"/></translation>
+<translation id="225680501294068881">Vyhledávání zařízení…</translation>
+<translation id="5597451508971090205"><ph name="SHORT_WEEKDAY"/>, <ph name="DATE"/></translation>
+<translation id="4448844063988177157">Vyhledávání sítí Wi-Fi...</translation>
+<translation id="8401662262483418323">Připojení k položce <ph name="NAME"/> se nezdařilo: <ph name="DETAILS"/>
+Zpráva serveru: <ph name="SERVER_MSG"/></translation>
+<translation id="2475982808118771221">Došlo k chybě</translation>
+<translation id="7229570126336867161">Je zapotřebí technologie EVDO.</translation>
+<translation id="2999742336789313416"><ph name="DISPLAY_NAME"/> je veřejná relace spravovaná doménou <ph name="DOMAIN"/>.</translation>
+<translation id="7029814467594812963">Ukončit relaci</translation>
+<translation id="8454013096329229812">Připojení Wi-Fi je zapnuto.</translation>
+<translation id="4872237917498892622">Alt + Vyhledávání nebo Shift</translation>
+<translation id="2983818520079887040">Nastavení...</translation>
+<translation id="1717216362413677834">Režim doku</translation>
+<translation id="8927026611342028580">Je vyžadováno připojení</translation>
+<translation id="8300849813060516376">Selhání OTASP</translation>
+<translation id="2792498699870441125">Alt + Vyhledávání</translation>
+<translation id="8660803626959853127">Synchronizace souborů (<ph name="COUNT"/>)</translation>
+<translation id="3709443003275901162">&gt;9</translation>
+<translation id="639644700271529076">CAPS LOCK je vypnutý</translation>
+<translation id="6248847161401822652">Práci ukončíte dvojitým stisknutím kombinace kláves Ctrl+Shift+Q.</translation>
+<translation id="6267036997247669271"><ph name="NAME"/>: Probíhá aktivace...</translation>
+<translation id="1391854757121130358">Pravděpodobně jste vyčerpali povolený objem mobilních datových přenosů.</translation>
+<translation id="5413208160176941586">Místně spravovaný uživatel</translation>
+<translation id="1059194134494239015"><ph name="DISPLAY_NAME"/>: <ph name="RESOLUTION"/></translation>
+<translation id="4864165860509564259">Pozice spouštěče</translation>
+<translation id="7593891976182323525">Vyhledávání nebo Shift</translation>
+<translation id="7649070708921625228">Nápověda</translation>
+<translation id="3050422059534974565">CAPS LOCK je zapnutý. Vypnete jej stisknutím klávesy Vyhledávání nebo Shift.</translation>
+<translation id="397105322502079400">Probíhá výpočet…</translation>
+<translation id="158849752021629804">Je potřeba domácí síť</translation>
+<translation id="6857811139397017780">Aktivovat: <ph name="NETWORKSERVICE"/></translation>
+<translation id="5864471791310927901">Vyhledávání serveru DHCP selhalo.</translation>
+<translation id="5812035014844949013">VÝSTUP</translation>
+<translation id="6692173217867674490">Chybné přístupové heslo</translation>
+<translation id="6165508094623778733">Další informace</translation>
+<translation id="9046895021617826162">Připojení selhalo</translation>
+<translation id="973896785707726617">Relace bude ukončena za <ph name="SESSION_TIME_REMAINING"/>. Poté budete automaticky odhlášeni.</translation>
+<translation id="8372369524088641025">Chybný klíč WEP</translation>
+<translation id="6636709850131805001">Neznámý stav</translation>
+<translation id="3573179567135747900">Změnit zpět na jazyk <ph name="FROM_LOCALE"/> (vyžaduje restart)</translation>
+<translation id="8103386449138765447">Zprávy SMS: <ph name="MESSAGE_COUNT"/></translation>
+<translation id="5045002648206642691">Nastavení Disku Google...</translation>
+<translation id="1510238584712386396">Spouštěč</translation>
+<translation id="7209101170223508707">CAPS LOCK je zapnutý.
+Vypnete jej stisknutím kombinace kláves Alt + Vyhledávání nebo Shift.</translation>
+<translation id="8940956008527784070">Slabá baterie (<ph name="PERCENTAGE"/> %)</translation>
+<translation id="5102001756192215136">Zbývá: <ph name="HOUR"/>:<ph name="MINUTE"/></translation>
+<translation id="520760366042891468">Sdílíte ovládání obrazovky (prostřednictvím služby Hangouts).</translation>
+<translation id="8000066093800657092">Žádná síť</translation>
+<translation id="4015692727874266537">Přihlásit jiný účet...</translation>
+<translation id="5941711191222866238">Minimalizovat</translation>
+<translation id="6911468394164995108">Připojit k jiné...</translation>
+<translation id="412065659894267608"><ph name="HOUR"/> h <ph name="MINUTE"/> min do nabití</translation>
+<translation id="6359806961507272919">SMS z čísla <ph name="PHONE_NUMBER"/></translation>
+<translation id="1244147615850840081">Operátor</translation>
+</translationbundle> \ No newline at end of file
diff --git a/chromium/ash/strings/ash_strings_da.xtb b/chromium/ash/strings/ash_strings_da.xtb
new file mode 100644
index 00000000000..5b25908a370
--- /dev/null
+++ b/chromium/ash/strings/ash_strings_da.xtb
@@ -0,0 +1,201 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="da">
+<translation id="3595596368722241419">Batteri fuldt</translation>
+<translation id="5250713215130379958">Skjul automatisk applikationslisten</translation>
+<translation id="7814236020522506259"><ph name="HOUR"/> og <ph name="MINUTE"/></translation>
+<translation id="7880025619322806991">Tilstand for portal</translation>
+<translation id="30155388420722288">Knappen Overflow</translation>
+<translation id="5571066253365925590">Bluetooth er aktiveret</translation>
+<translation id="9074739597929991885">Bluetooth</translation>
+<translation id="2268130516524549846">Bluetooth er deaktiveret</translation>
+<translation id="3775358506042162758">Du kan kun have op til tre konti i samlet login fra flere konti.</translation>
+<translation id="370649949373421643">Aktivér Wi-Fi</translation>
+<translation id="3626281679859535460">Lysstyrke</translation>
+<translation id="8054466585765276473">Beregner batteritid.</translation>
+<translation id="7982789257301363584">Netværk</translation>
+<translation id="5565793151875479467">Proxy...</translation>
+<translation id="938582441709398163">Tastaturoverlejring</translation>
+<translation id="4387004326333427325">Godkendelsescertifikatet blev afvist eksternt</translation>
+<translation id="6979158407327259162">Google Drev</translation>
+<translation id="6943836128787782965">Det lykkedes ikke at hente HTTP</translation>
+<translation id="2297568595583585744">Statusbakke</translation>
+<translation id="1661867754829461514">Pinkode mangler</translation>
+<translation id="4508225577814909926"><ph name="NAME"/> : Opretter forbindelse...</translation>
+<translation id="4237016987259239829">Netværkforbindelsesfejl</translation>
+<translation id="2946640296642327832">Aktivér Bluetooth</translation>
+<translation id="6459472438155181876">Udvider skærmen til <ph name="DISPLAY_NAME"/></translation>
+<translation id="8206859287963243715">Mobil</translation>
+<translation id="6596816719288285829">IP-adresse</translation>
+<translation id="4508265954913339219">Aktiveringen mislykkedes</translation>
+<translation id="3621712662352432595">Lydindstillinger</translation>
+<translation id="1812696562331527143">Din inputmetode er ændret til <ph name="INPUT_METHOD_ID"/>*(<ph name="BEGIN_LINK"/>tredjepart<ph name="END_LINK"/>).
+Tryk på Shift+Alt for at ændre den.</translation>
+<translation id="2127372758936585790">Oplader ved lav kraft</translation>
+<translation id="3846575436967432996">Der er ingen tilgængelige netværksoplysninger</translation>
+<translation id="3026237328237090306">Konfigurer mobildata</translation>
+<translation id="785750925697875037">Vis mobilkonto</translation>
+<translation id="153454903766751181">Initialiserer mobilmodem...</translation>
+<translation id="4628814525959230255">Deler kontrollen over din skærm med <ph name="HELPER_NAME"/> via Hangouts.</translation>
+<translation id="8343941333792395995"><ph name="DISPLAY_NAME"/> er roteret</translation>
+<translation id="7864539943188674973">Deaktiver Bluetooth</translation>
+<translation id="939252827960237676">Skærmbilledet kunne ikke gemmes</translation>
+<translation id="3126069444801937830">Genstart for at opdatere</translation>
+<translation id="2268813581635650749">Log alle ud</translation>
+<translation id="735745346212279324">VPN afbrudt</translation>
+<translation id="7320906967354320621">Ikke aktiv</translation>
+<translation id="6303423059719347535">Batteriet er <ph name="PERCENTAGE"/> % fuldt</translation>
+<translation id="15373452373711364">Stor musemarkør</translation>
+<translation id="2778346081696727092">Der kunne ikke godkendes med det angivne brugernavn eller adgangskoden</translation>
+<translation id="3294437725009624529">Gæst</translation>
+<translation id="8190698733819146287">Tilpas sprog og indtastning...</translation>
+<translation id="2903907270192926896">INPUT</translation>
+<translation id="8676770494376880701">Oplader med lav kraft er tilsluttet</translation>
+<translation id="7170041865419449892">Intet signal</translation>
+<translation id="4804818685124855865">Afbryd</translation>
+<translation id="2544853746127077729">Godkendelsescertifikatet blev afvist af netværk</translation>
+<translation id="5222676887888702881">Log ud</translation>
+<translation id="2688477613306174402">Konfiguration</translation>
+<translation id="1272079795634619415">Stop</translation>
+<translation id="4957722034734105353">Flere oplysninger...</translation>
+<translation id="2964193600955408481">Deaktiver Wi-Fi</translation>
+<translation id="811680302244032017">Tilføj enhed...</translation>
+<translation id="4279490309300973883">Spejling</translation>
+<translation id="2509468283778169019">CAPS LOCK er slået til</translation>
+<translation id="3892641579809465218">Internt display</translation>
+<translation id="7823564328645135659">Sproget er blevet ændret fra &quot;<ph name="FROM_LOCALE"/>&quot; til &quot;<ph name="TO_LOCALE"/>&quot;, efter at du har synkroniseret dine indstillinger.</translation>
+<translation id="3368922792935385530">Tilsluttet</translation>
+<translation id="8340999562596018839">Talefeedback</translation>
+<translation id="8654520615680304441">Slå Wi-Fi til...</translation>
+<translation id="5825747213122829519">Din inputmetode er ændret til <ph name="INPUT_METHOD_ID"/>.
+Tryk på Shift+Alt for at ændre den.</translation>
+<translation id="2562916301614567480">Privat netværk</translation>
+<translation id="6549021752953852991">Der er ingen tilgængelige mobilnetværk</translation>
+<translation id="4379753398862151997">Kære Skærm. Det fungerer ikke mellem os. (Denne skærm understøttes ikke)</translation>
+<translation id="6426039856985689743">Deaktiver mobildata</translation>
+<translation id="3087734570205094154">Bund</translation>
+<translation id="3742055079367172538">Skærmbilledet blev taget</translation>
+<translation id="8878886163241303700">Udvider skærm</translation>
+<translation id="5271016907025319479">VPN er ikke konfigureret.</translation>
+<translation id="372094107052732682">Tryk på Ctrl+Shift+Q to gange for at afslutte.</translation>
+<translation id="6803622936009808957">Skærmene kunne ikke spejles, da der ikke fandtes en understøttet opløsning. I stedet anvendes Udvidet skrivebord.</translation>
+<translation id="1480041086352807611">Demotilstand</translation>
+<translation id="3626637461649818317"><ph name="PERCENTAGE"/> % tilbage</translation>
+<translation id="9089416786594320554">Indtastningsmetoder</translation>
+<translation id="6247708409970142803"><ph name="PERCENTAGE"/> %</translation>
+<translation id="2614835198358683673">Din Chromebook oplades muligvis ikke, når den er tændt. Anvend eventuelt den officielle oplader.</translation>
+<translation id="1895658205118569222">Nedlukning</translation>
+<translation id="4430019312045809116">Lydstyrke</translation>
+<translation id="4442424173763614572">DNS-opslag mislykkedes</translation>
+<translation id="6356500677799115505">Batteriet er fuldt opladet og oplader.</translation>
+<translation id="7874779702599364982">Søger efter mobilnetværk...</translation>
+<translation id="583281660410589416">Ukendt</translation>
+<translation id="1383876407941801731">Søgning</translation>
+<translation id="7468789844759750875">Gå til aktiveringsportalen <ph name="NAME"/> for at købe mere data.</translation>
+<translation id="3901991538546252627">Opretter forbindelse til <ph name="NAME"/></translation>
+<translation id="2204305834655267233">Netværksoplysninger</translation>
+<translation id="1621499497873603021">Tid tilbage, indtil batteriet er tomt, <ph name="TIME_LEFT"/></translation>
+<translation id="5980301590375426705">Afslut gæstesession</translation>
+<translation id="4471417012762451363">Batteriet er <ph name="PERCENTAGE"/> % fuldt og oplader</translation>
+<translation id="8308637677604853869">Forrige menu</translation>
+<translation id="4666297444214622512">Kan ikke logge ind på en anden konto.</translation>
+<translation id="1346748346194534595">Højre</translation>
+<translation id="1773212559869067373">Godkendelsescertifikatet blev afvist lokalt</translation>
+<translation id="8528322925433439945">Mobil...</translation>
+<translation id="7049357003967926684">Tilknytning</translation>
+<translation id="8428213095426709021">Indstillinger</translation>
+<translation id="2372145515558759244">Synkroniserer apps...</translation>
+<translation id="7256405249507348194">Ukendt fejl: <ph name="DESC"/></translation>
+<translation id="7925247922861151263">AAA-kontrol mislykkedes</translation>
+<translation id="8456362689280298700"><ph name="HOUR"/>:<ph name="MINUTE"/> indtil fuldt opladet</translation>
+<translation id="5787281376604286451">Talefeedback er aktiveret.
+Tryk på Ctrl+Alt+Z for at deaktivere dette.</translation>
+<translation id="4479639480957787382">Ethernet</translation>
+<translation id="6312403991423642364">Ukendt netværksfejl</translation>
+<translation id="1467432559032391204">Venstre</translation>
+<translation id="5543001071567407895">SMS</translation>
+<translation id="2354174487190027830">Aktiverer <ph name="NAME"/></translation>
+<translation id="8814190375133053267">Wi-Fi</translation>
+<translation id="1398853756734560583">Maksimer</translation>
+<translation id="2692809339924654275"><ph name="BLUETOOTH"/>: Opretter forbindelse...</translation>
+<translation id="252373100621549798">Ukendt skærm</translation>
+<translation id="1882897271359938046">Spejler mod <ph name="DISPLAY_NAME"/></translation>
+<translation id="2727977024730340865">Tilsluttet en oplader med lav kraft. Batteriopladningen er muligvis ikke pålidelig.</translation>
+<translation id="3784455785234192852">Lås</translation>
+<translation id="2805756323405976993">Applikationer</translation>
+<translation id="8871072142849158571"><ph name="DISPLAY_NAME"/> er blevet ændret til <ph name="RESOLUTION"/></translation>
+<translation id="1512064327686280138">Aktiveringsfejl:</translation>
+<translation id="5097002363526479830">Der kunne ikke oprettes forbindelse til netværket &quot;<ph name="NAME"/>&quot;: <ph name="DETAILS"/></translation>
+<translation id="1850504506766569011">Wi-Fi er slået fra.</translation>
+<translation id="8132793192354020517">Forbundet til <ph name="NAME"/></translation>
+<translation id="7052914147756339792">Angiv baggrundsbillede...</translation>
+<translation id="8678698760965522072">Onlinetilstand</translation>
+<translation id="2532589005999780174">Tilstanden Høj kontrast</translation>
+<translation id="1119447706177454957">Intern fejl</translation>
+<translation id="3019353588588144572">Resterende tid, indtil batteriet er fuldt opladet, <ph name="TIME_REMAINING"/></translation>
+<translation id="3473479545200714844">Skærmforstørrer</translation>
+<translation id="7005812687360380971">Fejl:</translation>
+<translation id="882279321799040148">Klik for at se det</translation>
+<translation id="5045550434625856497">Ugyldig adgangskode</translation>
+<translation id="1602076796624386989">Aktivér mobildata</translation>
+<translation id="6981982820502123353">Tilgængelighed</translation>
+<translation id="3157931365184549694">Gendan</translation>
+<translation id="4274292172790327596">Fejlen genkendes ikke</translation>
+<translation id="4032485810211612751"><ph name="HOURS"/>:<ph name="MINUTES"/>:<ph name="SECONDS"/></translation>
+<translation id="225680501294068881">Scanner efter enheder...</translation>
+<translation id="5597451508971090205"><ph name="SHORT_WEEKDAY"/> d. <ph name="DATE"/></translation>
+<translation id="4448844063988177157">Søger efter Wi-Fi-netværk...</translation>
+<translation id="8401662262483418323">Der kunne ikke oprettes forbindelse til &quot;<ph name="NAME"/>&quot;: <ph name="DETAILS"/>
+Servermeddelelse: <ph name="SERVER_MSG"/></translation>
+<translation id="2475982808118771221">Der opstod en fejl</translation>
+<translation id="7229570126336867161">EVDO mangler</translation>
+<translation id="2999742336789313416"><ph name="DISPLAY_NAME"/> er en offentlig session administreret af <ph name="DOMAIN"/></translation>
+<translation id="7029814467594812963">Afslut session</translation>
+<translation id="8454013096329229812">Wi-Fi er slået til.</translation>
+<translation id="4872237917498892622">Alt+Søg eller Skift</translation>
+<translation id="2983818520079887040">Indstillinger...</translation>
+<translation id="1717216362413677834">Docktilstand</translation>
+<translation id="8927026611342028580">Der er anmodet om forbindelse</translation>
+<translation id="8300849813060516376">OTASP mislykkedes</translation>
+<translation id="2792498699870441125">Alt+Søg</translation>
+<translation id="8660803626959853127">Synkroniserer <ph name="COUNT"/> fil(er)</translation>
+<translation id="3709443003275901162">9 eller flere</translation>
+<translation id="639644700271529076">CAPS LOCK er deaktiveret</translation>
+<translation id="6248847161401822652">Tryk på Ctrl+Shift+Q to gange for at afslutte.</translation>
+<translation id="6267036997247669271"><ph name="NAME"/>: Aktiverer...</translation>
+<translation id="1391854757121130358">Du har muligvis nået din mobildatagrænse.</translation>
+<translation id="5413208160176941586">Lokalt administreret bruger</translation>
+<translation id="1059194134494239015"><ph name="DISPLAY_NAME"/>: <ph name="RESOLUTION"/></translation>
+<translation id="4864165860509564259">Applikationslistens placering</translation>
+<translation id="7593891976182323525">Søg eller skift</translation>
+<translation id="7649070708921625228">Hjælp</translation>
+<translation id="3050422059534974565">CAPS LOCK er slået til.
+Tryk på Søg eller Skift for at annullere.</translation>
+<translation id="397105322502079400">Beregner...</translation>
+<translation id="158849752021629804">Hjemmenetværk mangler</translation>
+<translation id="6857811139397017780">Aktiver <ph name="NETWORKSERVICE"/></translation>
+<translation id="5864471791310927901">DHCP-opslag mislykkedes</translation>
+<translation id="5812035014844949013">OUTPUT</translation>
+<translation id="6692173217867674490">Ugyldig adgangssætning</translation>
+<translation id="6165508094623778733">Flere oplysninger</translation>
+<translation id="9046895021617826162">Forbindelsen mislykkedes</translation>
+<translation id="973896785707726617">Denne session afsluttes om <ph name="SESSION_TIME_REMAINING"/>. Du logges automatisk ud.</translation>
+<translation id="8372369524088641025">Ugyldig WEP-nøgle</translation>
+<translation id="6636709850131805001">Tilstanden genkendes ikke</translation>
+<translation id="3573179567135747900">Skift tilbage til &quot;<ph name="FROM_LOCALE"/>&quot; (kræver genstart)</translation>
+<translation id="8103386449138765447">SMS-beskeder: <ph name="MESSAGE_COUNT"/></translation>
+<translation id="5045002648206642691">Google Drev-indstillinger...</translation>
+<translation id="1510238584712386396">Applikationsliste</translation>
+<translation id="7209101170223508707">CAPS LOCK er slået til.
+Tryk på Alt+Søg eller Skift for at annullere.</translation>
+<translation id="8940956008527784070">Batteriniveauet er lavt (<ph name="PERCENTAGE"/> %)</translation>
+<translation id="5102001756192215136"><ph name="HOUR"/>:<ph name="MINUTE"/> tilbage</translation>
+<translation id="520760366042891468">Deler kontrollen over din skærm via Hangouts.</translation>
+<translation id="8000066093800657092">Intet netværk</translation>
+<translation id="4015692727874266537">Log ind på en anden konto...</translation>
+<translation id="5941711191222866238">Minimer</translation>
+<translation id="6911468394164995108">Find andre... </translation>
+<translation id="412065659894267608"><ph name="HOUR"/> t <ph name="MINUTE"/> m, indtil det er fuldt opladet</translation>
+<translation id="6359806961507272919">Sms fra <ph name="PHONE_NUMBER"/></translation>
+<translation id="1244147615850840081">Mobilselskab</translation>
+</translationbundle> \ No newline at end of file
diff --git a/chromium/ash/strings/ash_strings_de.xtb b/chromium/ash/strings/ash_strings_de.xtb
new file mode 100644
index 00000000000..aed761ffbb5
--- /dev/null
+++ b/chromium/ash/strings/ash_strings_de.xtb
@@ -0,0 +1,201 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="de">
+<translation id="3595596368722241419">Akku voll</translation>
+<translation id="5250713215130379958">Übersicht automatisch ausblenden</translation>
+<translation id="7814236020522506259"><ph name="HOUR"/> und <ph name="MINUTE"/></translation>
+<translation id="7880025619322806991">Portal-Status</translation>
+<translation id="30155388420722288">Überlaufschaltfläche</translation>
+<translation id="5571066253365925590">Bluetooth aktiviert</translation>
+<translation id="9074739597929991885">Bluetooth</translation>
+<translation id="2268130516524549846">Bluetooth deaktiviert</translation>
+<translation id="3775358506042162758">Bei der Mehrfachanmeldung sind maximal drei Konten zulässig.</translation>
+<translation id="370649949373421643">WLAN aktivieren</translation>
+<translation id="3626281679859535460">Helligkeit</translation>
+<translation id="8054466585765276473">Akku-Laufzeit wird berechnet.</translation>
+<translation id="7982789257301363584">Netzwerk</translation>
+<translation id="5565793151875479467">Proxy...</translation>
+<translation id="938582441709398163">Tastatur-Overlay</translation>
+<translation id="4387004326333427325">Remoteablehnung des Authentifizierungszertifikats</translation>
+<translation id="6979158407327259162">Google Drive</translation>
+<translation id="6943836128787782965">HTTP-Abruf fehlgeschlagen</translation>
+<translation id="2297568595583585744">Statusleiste</translation>
+<translation id="1661867754829461514">PIN fehlt</translation>
+<translation id="4508225577814909926"><ph name="NAME"/>: Verbindung wird hergestellt...</translation>
+<translation id="4237016987259239829">Fehler bei der Netzwerkverbindung</translation>
+<translation id="2946640296642327832">Bluetooth aktivieren</translation>
+<translation id="6459472438155181876">Bildschirm wird auf <ph name="DISPLAY_NAME"/> erweitert...</translation>
+<translation id="8206859287963243715">Mobil</translation>
+<translation id="6596816719288285829">IP-Adresse</translation>
+<translation id="4508265954913339219">Aktivierung fehlgeschlagen</translation>
+<translation id="3621712662352432595">Audioeinstellungen</translation>
+<translation id="1812696562331527143">Ihre Eingabemethode hat sich in <ph name="INPUT_METHOD_ID"/>*(<ph name="BEGIN_LINK"/>Drittanbieter<ph name="END_LINK"/>) geändert.
+Drücken Sie zum Wechseln Umschalt+Alt.</translation>
+<translation id="2127372758936585790">Schwachstrom-Ladegerät</translation>
+<translation id="3846575436967432996">Keine Netzwerkinformationen verfügbar</translation>
+<translation id="3026237328237090306">Mobilfunk einrichten</translation>
+<translation id="785750925697875037">Mobiles Konto aufrufen</translation>
+<translation id="153454903766751181">Mobilfunkmodem wird initialisiert...</translation>
+<translation id="4628814525959230255">Bildschirmfreigabe für <ph name="HELPER_NAME"/> über Hangouts</translation>
+<translation id="8343941333792395995"><ph name="DISPLAY_NAME"/> wurde gedreht.</translation>
+<translation id="7864539943188674973">Bluetooth deaktivieren</translation>
+<translation id="939252827960237676">Screenshot konnte nicht gespeichert werden.</translation>
+<translation id="3126069444801937830">Zum Aktualisieren neu starten</translation>
+<translation id="2268813581635650749">Alle abmelden</translation>
+<translation id="735745346212279324">VPN-Verbindung getrennt</translation>
+<translation id="7320906967354320621">Inaktiv</translation>
+<translation id="6303423059719347535">Akku ist zu <ph name="PERCENTAGE"/> % geladen.</translation>
+<translation id="15373452373711364">Großer Cursor</translation>
+<translation id="2778346081696727092">Fehler beim Authentifizieren mit dem angegebenen Nutzernamen oder Passwort</translation>
+<translation id="3294437725009624529">Gast</translation>
+<translation id="8190698733819146287">Sprache und Eingabe anpassen...</translation>
+<translation id="2903907270192926896">Eingang</translation>
+<translation id="8676770494376880701">Schwachstrom-Ladegerät angeschlossen</translation>
+<translation id="7170041865419449892">Außerhalb des Bereichs</translation>
+<translation id="4804818685124855865">Verbindung trennen</translation>
+<translation id="2544853746127077729">Ablehnung des Authentifizierungszertifikats durch das Netzwerk</translation>
+<translation id="5222676887888702881">Abmelden</translation>
+<translation id="2688477613306174402">Konfiguration</translation>
+<translation id="1272079795634619415">Stopp</translation>
+<translation id="4957722034734105353">Weitere Informationen...</translation>
+<translation id="2964193600955408481">WLAN deaktivieren</translation>
+<translation id="811680302244032017">Gerät hinzufügen...</translation>
+<translation id="4279490309300973883">Spiegelung</translation>
+<translation id="2509468283778169019">Feststelltaste An</translation>
+<translation id="3892641579809465218">Interne Anzeige</translation>
+<translation id="7823564328645135659">Nach der Synchronisierung Ihrer Einstellungen wurde die Sprache von &quot;<ph name="FROM_LOCALE"/>&quot; in &quot;<ph name="TO_LOCALE"/>&quot; geändert.</translation>
+<translation id="3368922792935385530">Verbunden</translation>
+<translation id="8340999562596018839">Gesprochenes Feedback</translation>
+<translation id="8654520615680304441">WLAN aktivieren...</translation>
+<translation id="5825747213122829519">Ihre Eingabemethode hat sich in <ph name="INPUT_METHOD_ID"/> geändert.
+Drücken Sie zum Wechseln Umschalt+Alt.</translation>
+<translation id="2562916301614567480">Privates Netzwerk</translation>
+<translation id="6549021752953852991">Kein Mobilfunknetz verfügbar</translation>
+<translation id="4379753398862151997">Der Monitor wird nicht unterstützt.</translation>
+<translation id="6426039856985689743">Mobilfunk deaktivieren</translation>
+<translation id="3087734570205094154">Unten</translation>
+<translation id="3742055079367172538">Screenshot erstellt</translation>
+<translation id="8878886163241303700">Bildschirmerweiterung</translation>
+<translation id="5271016907025319479">VPN ist nicht konfiguriert.</translation>
+<translation id="372094107052732682">Drücken Sie zum Beenden zweimal Strg+Shift+Q.</translation>
+<translation id="6803622936009808957">Der Bildschirm konnte nicht gespiegelt werden, da die Auflösung nicht unterstützt wird. Stattdessen wurde der Modus für den erweiterten Desktop gestartet.</translation>
+<translation id="1480041086352807611">Demo-Modus</translation>
+<translation id="3626637461649818317"><ph name="PERCENTAGE"/> % verbleibend</translation>
+<translation id="9089416786594320554">Eingabemethoden</translation>
+<translation id="6247708409970142803"><ph name="PERCENTAGE"/> %</translation>
+<translation id="2614835198358683673">Ihr Chromebook kann möglicherweise nicht geladen werden, während es eingeschaltet ist. Wir empfehlen die Verwendung des Originalladegeräts.</translation>
+<translation id="1895658205118569222">Herunterfahren</translation>
+<translation id="4430019312045809116">Lautstärke</translation>
+<translation id="4442424173763614572">DNS-Suche fehlgeschlagen</translation>
+<translation id="6356500677799115505">Akku ist vollständig geladen und wird noch geladen.</translation>
+<translation id="7874779702599364982">Suche nach Mobilfunknetzen läuft...</translation>
+<translation id="583281660410589416">Unbekannt</translation>
+<translation id="1383876407941801731">Suche</translation>
+<translation id="7468789844759750875">Besuchen Sie das <ph name="NAME"/>-Aktivierungsportal, um zusätzliches Datenvolumen zu kaufen.</translation>
+<translation id="3901991538546252627">Verbindung mit <ph name="NAME"/> wird hergestellt.</translation>
+<translation id="2204305834655267233">Netzwerkinformationen</translation>
+<translation id="1621499497873603021">Verbleibende Akku-Laufzeit: <ph name="TIME_LEFT"/></translation>
+<translation id="5980301590375426705">Gastsitzung beenden</translation>
+<translation id="4471417012762451363">Akku ist zu <ph name="PERCENTAGE"/> % geladen und wird geladen.</translation>
+<translation id="8308637677604853869">Vorheriges Menü</translation>
+<translation id="4666297444214622512">Anmeldung in weiterem Konto nicht möglich</translation>
+<translation id="1346748346194534595">Nach rechts</translation>
+<translation id="1773212559869067373">Lokale Ablehnung des Authentifizierungszertifikats</translation>
+<translation id="8528322925433439945">Mobil...</translation>
+<translation id="7049357003967926684">Verbindung</translation>
+<translation id="8428213095426709021">Einstellungen</translation>
+<translation id="2372145515558759244">Apps werden synchronisiert...</translation>
+<translation id="7256405249507348194">Unbekannter Fehler: <ph name="DESC"/></translation>
+<translation id="7925247922861151263">AAA-Prüfung fehlgeschlagen</translation>
+<translation id="8456362689280298700"><ph name="HOUR"/>:<ph name="MINUTE"/> bis voll aufgeladen</translation>
+<translation id="5787281376604286451">Gesprochenes Feedback ist aktiviert.
+Zum Deaktivieren Strg+Alt+Z drücken</translation>
+<translation id="4479639480957787382">Ethernet</translation>
+<translation id="6312403991423642364">Unbekannter Netzwerkfehler</translation>
+<translation id="1467432559032391204">Nach links</translation>
+<translation id="5543001071567407895">SMS</translation>
+<translation id="2354174487190027830"><ph name="NAME"/> wird aktiviert</translation>
+<translation id="8814190375133053267">WLAN</translation>
+<translation id="1398853756734560583">Vergrößern</translation>
+<translation id="2692809339924654275"><ph name="BLUETOOTH"/>: Verbindung wird hergestellt...</translation>
+<translation id="252373100621549798">Display unbekannt</translation>
+<translation id="1882897271359938046">Wird auf <ph name="DISPLAY_NAME"/> gespiegelt...</translation>
+<translation id="2727977024730340865">Das Gerät ist an ein Schwachstrom-Ladegerät angeschlossen. Möglicherweise kann der Akku nicht zuverlässig aufgeladen werden.</translation>
+<translation id="3784455785234192852">Sperren</translation>
+<translation id="2805756323405976993">Apps</translation>
+<translation id="8871072142849158571">Die Größe von <ph name="DISPLAY_NAME"/> wurde auf <ph name="RESOLUTION"/> angepasst.</translation>
+<translation id="1512064327686280138">Aktivierungsfehler</translation>
+<translation id="5097002363526479830">Fehler beim Herstellen einer Verbindung mit dem Netzwerk &quot;<ph name="NAME"/>&quot;: <ph name="DETAILS"/></translation>
+<translation id="1850504506766569011">WLAN ist deaktiviert.</translation>
+<translation id="8132793192354020517">Verbunden mit <ph name="NAME"/></translation>
+<translation id="7052914147756339792">Hintergrund festlegen</translation>
+<translation id="8678698760965522072">Online-Status</translation>
+<translation id="2532589005999780174">Modus mit hohem Kontrast</translation>
+<translation id="1119447706177454957">Interner Fehler</translation>
+<translation id="3019353588588144572">Verbleibende Zeit, bis der Akku vollständig geladen ist: <ph name="TIME_REMAINING"/></translation>
+<translation id="3473479545200714844">Lupe</translation>
+<translation id="7005812687360380971">Fehler</translation>
+<translation id="882279321799040148">Zum Anzeigen klicken</translation>
+<translation id="5045550434625856497">Falsches Passwort</translation>
+<translation id="1602076796624386989">Mobilfunk aktivieren</translation>
+<translation id="6981982820502123353">Bedienungshilfen</translation>
+<translation id="3157931365184549694">Wiederherstellen</translation>
+<translation id="4274292172790327596">Unbekannter Fehler</translation>
+<translation id="4032485810211612751"><ph name="HOURS"/>:<ph name="MINUTES"/>:<ph name="SECONDS"/></translation>
+<translation id="225680501294068881">Nach Geräten wird gesucht...</translation>
+<translation id="5597451508971090205"><ph name="SHORT_WEEKDAY"/>, <ph name="DATE"/></translation>
+<translation id="4448844063988177157">Suche nach WLAN-Netzwerken läuft...</translation>
+<translation id="8401662262483418323">Fehler bei der Verbindung mit &quot;<ph name="NAME"/>&quot;: <ph name="DETAILS"/>
+Servernachricht: <ph name="SERVER_MSG"/></translation>
+<translation id="2475982808118771221">Ein Fehler ist aufgetreten.</translation>
+<translation id="7229570126336867161">EVDO erforderlich</translation>
+<translation id="2999742336789313416"><ph name="DISPLAY_NAME"/> ist eine öffentliche Sitzung, die von <ph name="DOMAIN"/> verwaltet wird.</translation>
+<translation id="7029814467594812963">Sitzung beenden</translation>
+<translation id="8454013096329229812">WLAN ist aktiviert.</translation>
+<translation id="4872237917498892622">Alt+Such- oder Umschalttaste</translation>
+<translation id="2983818520079887040">Einstellungen...</translation>
+<translation id="1717216362413677834">Dock-Modus</translation>
+<translation id="8927026611342028580">Verbindung angefordert</translation>
+<translation id="8300849813060516376">OTASP fehlgeschlagen</translation>
+<translation id="2792498699870441125">Alt+Suchtaste</translation>
+<translation id="8660803626959853127"><ph name="COUNT"/> Datei(en) wird bzw. werden synchronisiert.</translation>
+<translation id="3709443003275901162">9+</translation>
+<translation id="639644700271529076">Feststelltaste Aus</translation>
+<translation id="6248847161401822652">Drücken Sie zum Beenden zweimal Steuerung-Shift-Q.</translation>
+<translation id="6267036997247669271"><ph name="NAME"/>: Wird aktiviert...</translation>
+<translation id="1391854757121130358">Sie haben möglicherweise Ihr mobiles Datenvolumen aufgebraucht.</translation>
+<translation id="5413208160176941586">Lokal verwalteter Nutzer</translation>
+<translation id="1059194134494239015"><ph name="DISPLAY_NAME"/>: <ph name="RESOLUTION"/></translation>
+<translation id="4864165860509564259">Position der Übersicht</translation>
+<translation id="7593891976182323525">Such- oder Umschalttaste</translation>
+<translation id="7649070708921625228">Hilfe</translation>
+<translation id="3050422059534974565">Die Feststelltaste ist aktiviert.
+Drücken Sie die Such- oder die Umschalttaste, um die Aktivierung aufzuheben.</translation>
+<translation id="397105322502079400">Wird berechnet...</translation>
+<translation id="158849752021629804">Heimnetzwerk erforderlich</translation>
+<translation id="6857811139397017780"><ph name="NETWORKSERVICE"/> aktivieren</translation>
+<translation id="5864471791310927901">DHCP-Suche fehlgeschlagen</translation>
+<translation id="5812035014844949013">Ausgang</translation>
+<translation id="6692173217867674490">Ungültige Passphrase</translation>
+<translation id="6165508094623778733">Weitere Informationen</translation>
+<translation id="9046895021617826162">Verbindungsaufbau fehlgeschlagen</translation>
+<translation id="973896785707726617">Die Sitzung wird in <ph name="SESSION_TIME_REMAINING"/> beendet. Sie werden dann automatisch abgemeldet.</translation>
+<translation id="8372369524088641025">Ungültiger WEP-Schlüssel</translation>
+<translation id="6636709850131805001">Unbekannter Status</translation>
+<translation id="3573179567135747900">Zurücksetzen auf &quot;<ph name="FROM_LOCALE"/>&quot; (Neustart erforderlich)</translation>
+<translation id="8103386449138765447">SMS: <ph name="MESSAGE_COUNT"/></translation>
+<translation id="5045002648206642691">Google Drive-Einstellungen...</translation>
+<translation id="1510238584712386396">Übersicht</translation>
+<translation id="7209101170223508707">Die Feststelltaste ist aktiviert.
+Drücken Sie Alt+Such- oder Umschalttaste, um die Aktivierung aufzuheben.</translation>
+<translation id="8940956008527784070">Niedriger Akkustand (<ph name="PERCENTAGE"/> %)</translation>
+<translation id="5102001756192215136"><ph name="HOUR"/>:<ph name="MINUTE"/> verbleiben</translation>
+<translation id="520760366042891468">Bildschirmfreigabe über Hangouts</translation>
+<translation id="8000066093800657092">Nicht verbunden</translation>
+<translation id="4015692727874266537">Anderes Konto anmelden...</translation>
+<translation id="5941711191222866238">Verkleinern</translation>
+<translation id="6911468394164995108">Andere Netzwerke...</translation>
+<translation id="412065659894267608">In <ph name="HOUR"/> Std. <ph name="MINUTE"/> Min. vollständig aufgeladen</translation>
+<translation id="6359806961507272919">SMS von <ph name="PHONE_NUMBER"/></translation>
+<translation id="1244147615850840081">Mobilfunkanbieter</translation>
+</translationbundle> \ No newline at end of file
diff --git a/chromium/ash/strings/ash_strings_el.xtb b/chromium/ash/strings/ash_strings_el.xtb
new file mode 100644
index 00000000000..b088fb0ee51
--- /dev/null
+++ b/chromium/ash/strings/ash_strings_el.xtb
@@ -0,0 +1,200 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="el">
+<translation id="3595596368722241419">Μπαταρία πλήρης</translation>
+<translation id="5250713215130379958">Αυτόματη απόκρυψη της λειτουργίας εκκίνησης</translation>
+<translation id="7814236020522506259"><ph name="HOUR"/> και <ph name="MINUTE"/></translation>
+<translation id="7880025619322806991">Κατάσταση πύλης</translation>
+<translation id="30155388420722288">Κουμπί επιπρόσθετης ροής</translation>
+<translation id="5571066253365925590">Το Bluetooth έχει ενεργοποιηθεί</translation>
+<translation id="9074739597929991885">Bluetooth</translation>
+<translation id="2268130516524549846">Το Bluetooth έχει απενεργοποιηθεί</translation>
+<translation id="3775358506042162758">Μπορείτε να συνδέεστε ταυτόχρονα με έως και τρεις λογαριασμούς.</translation>
+<translation id="370649949373421643">Ενεργοποίηση Wi-Fi</translation>
+<translation id="3626281679859535460">Φωτεινότητα</translation>
+<translation id="8054466585765276473">Υπολογισμός χρόνου μπαταρίας που απομένει.</translation>
+<translation id="7982789257301363584">Δίκτυο</translation>
+<translation id="5565793151875479467">Διακομιστής μεσολάβησης...</translation>
+<translation id="938582441709398163">Επικάλυψη πληκτρολογίου</translation>
+<translation id="4387004326333427325">Το πιστοποιητικό ελέγχου ταυτότητας απορρίφθηκε απομακρυσμένα</translation>
+<translation id="6979158407327259162">Google Drive</translation>
+<translation id="6943836128787782965">Η λήψη HTTP απέτυχε</translation>
+<translation id="2297568595583585744">Δίσκος κατάστασης</translation>
+<translation id="1661867754829461514">Απουσιάζει το PIN </translation>
+<translation id="4508225577814909926"><ph name="NAME"/>: Σύνδεση…</translation>
+<translation id="4237016987259239829">Σφάλμα σύνδεσης δικτύου</translation>
+<translation id="2946640296642327832">Ενεργοποίηση Bluetooth</translation>
+<translation id="6459472438155181876">Επέκταση οθόνης σε <ph name="DISPLAY_NAME"/></translation>
+<translation id="8206859287963243715">Κινητό</translation>
+<translation id="6596816719288285829">Διεύθυνση IP</translation>
+<translation id="4508265954913339219">Η ενεργοποίηση απέτυχε</translation>
+<translation id="3621712662352432595">Ρυθμίσεις ήχου</translation>
+<translation id="1812696562331527143">Η μέθοδος εισαγωγής σας έχει αλλάξει σε <ph name="INPUT_METHOD_ID"/>*(<ph name="BEGIN_LINK"/>τρίτου μέρους<ph name="END_LINK"/>).
+Πατήστε Shift + Alt για εναλλαγή.</translation>
+<translation id="2127372758936585790">Χαμηλή ισχύς φορτιστή</translation>
+<translation id="3846575436967432996">Δεν υπάρχουν διαθέσιμες πληροφορίες δικτύου</translation>
+<translation id="3026237328237090306">Ρύθμιση δεδομένων κινητής τηλεφωνίας</translation>
+<translation id="785750925697875037">Προβολή λογαριασμού κινητής τηλεφωνίας</translation>
+<translation id="153454903766751181">Εκκίνηση μόντεμ δικτύου κινητής τηλεφωνίας…</translation>
+<translation id="4628814525959230255">Μοιράζεστε τον έλεγχο της οθόνης σας με το χρήστη <ph name="HELPER_NAME"/> μέσω του Hangouts.</translation>
+<translation id="8343941333792395995">Η οθόνη <ph name="DISPLAY_NAME"/> έχει περιστραφεί</translation>
+<translation id="7864539943188674973">Απενεργοποίηση Bluetooth</translation>
+<translation id="939252827960237676">Αποτυχία αποθήκευσης στιγμιότυπου οθόνης</translation>
+<translation id="3126069444801937830">Επανεκκίνηση για ενημέρωση</translation>
+<translation id="2268813581635650749">Αποσύνδεση όλων</translation>
+<translation id="735745346212279324">Το VPN αποσυνδέθηκε</translation>
+<translation id="7320906967354320621">Αδρανές</translation>
+<translation id="6303423059719347535">Η μπαταρία είναι πλήρης <ph name="PERCENTAGE"/>%</translation>
+<translation id="15373452373711364">Μεγάλος δείκτης ποντικιού</translation>
+<translation id="2778346081696727092">Δεν είναι δυνατός ο έλεγχος ταυτότητας με το όνομα χρήστη ή τον κωδικό πρόσβασης που παρέχεται</translation>
+<translation id="3294437725009624529">Επισκέπτης</translation>
+<translation id="8190698733819146287">Προσαρμογή γλωσσών και εισόδου...</translation>
+<translation id="2903907270192926896">ΕΙΣΟΔΟΣ</translation>
+<translation id="8676770494376880701">Ο συνδεδεμένος φορτιστής παρέχει χαμηλή ισχύ</translation>
+<translation id="7170041865419449892">Εκτός εύρους τιμών</translation>
+<translation id="4804818685124855865">Αποσύνδεση</translation>
+<translation id="2544853746127077729">Το πιστοποιητικό ελέγχου ταυτότητας απορρίφθηκε από το δίκτυο</translation>
+<translation id="5222676887888702881">Έξοδος</translation>
+<translation id="2688477613306174402">Διαμόρφωση</translation>
+<translation id="1272079795634619415">Διακοπή</translation>
+<translation id="4957722034734105353">Μάθετε περισσότερα…</translation>
+<translation id="2964193600955408481">Απενεργοποίηση Wi-Fi</translation>
+<translation id="811680302244032017">Προσθήκη συσκευής…</translation>
+<translation id="4279490309300973883">Κατοπτρισμός</translation>
+<translation id="2509468283778169019">Το CAPS LOCK είναι ενεργοποιημένο</translation>
+<translation id="3892641579809465218">Εσωτερική οθόνη</translation>
+<translation id="7823564328645135659">Η γλώσσα του άλλαξε από &quot;<ph name="FROM_LOCALE"/>&quot; σε &quot;<ph name="TO_LOCALE"/>&quot; μετά τον συγχρονισμό των ρυθμίσεών σας.</translation>
+<translation id="3368922792935385530">Σε σύνδεση</translation>
+<translation id="8340999562596018839">Προφορικά σχόλια</translation>
+<translation id="8654520615680304441">Ενεργοποίηση Wi-Fi…</translation>
+<translation id="5825747213122829519">Η μέθοδος εισαγωγής σας έχει αλλάξει σε <ph name="INPUT_METHOD_ID"/>.
+Πατήστε Shift + Alt για εναλλαγή.</translation>
+<translation id="2562916301614567480">Ιδιωτικό δίκτυο</translation>
+<translation id="6549021752953852991">Δεν υπάρχει διαθέσιμο δίκτυο κινητής τηλεφωνίας</translation>
+<translation id="4379753398862151997">Αγαπητή οθόνη, κάτι δεν πάει καλά με εμάς. (Αυτή η οθόνη δεν υποστηρίζεται)</translation>
+<translation id="6426039856985689743">Απενεργοποίηση δεδομένων κινητής τηλεφωνίας</translation>
+<translation id="3087734570205094154">Κάτω</translation>
+<translation id="3742055079367172538">Το στιγμιότυπο οθόνης έχει ληφθεί</translation>
+<translation id="8878886163241303700">Επέκταση οθόνης</translation>
+<translation id="5271016907025319479">Το VPN δεν έχει διαμορφωθεί.</translation>
+<translation id="372094107052732682">Πατήστε Ctrl + Shift + Q δύο φορές για έξοδο.</translation>
+<translation id="6803622936009808957">Δεν ήταν δυνατός ο αντικατοπτρισμός των οθονών καθώς δεν βρέθηκαν υποστηριζόμενες αναλύσεις. Έχει ενεργοποιηθεί εναλλακτικά η εκτεταμένη επιφάνεια εργασίας.</translation>
+<translation id="1480041086352807611">Λειτουργία επίδειξης</translation>
+<translation id="3626637461649818317">Υπολείπεται <ph name="PERCENTAGE"/>%</translation>
+<translation id="9089416786594320554">Μέθοδοι εισαγωγής</translation>
+<translation id="6247708409970142803"><ph name="PERCENTAGE"/>%</translation>
+<translation id="2614835198358683673">Το Chromebook ενδέχεται να μη φορτίζει ενώ είναι ενεργοποιημένο. Χρησιμοποιήστε τον αυθεντικό φορτιστή.</translation>
+<translation id="1895658205118569222">Τερματισμός λειτουργίας</translation>
+<translation id="4430019312045809116">Ένταση</translation>
+<translation id="4442424173763614572">Η αναζήτηση DNS απέτυχε</translation>
+<translation id="6356500677799115505">Η μπαταρία είναι πλήρης και φορτίζει.</translation>
+<translation id="7874779702599364982">Αναζήτηση για δίκτυα κινητής τηλεφωνίας…</translation>
+<translation id="583281660410589416">Άγνωστο</translation>
+<translation id="1383876407941801731">Αναζήτηση</translation>
+<translation id="7468789844759750875">Επισκεφτείτε την πύλη ενεργοποίησης <ph name="NAME"/> για να αγοράσετε περισσότερα δεδομένα.</translation>
+<translation id="3901991538546252627">Σύνδεση με <ph name="NAME"/></translation>
+<translation id="2204305834655267233">Πληροφορίες δικτύου</translation>
+<translation id="1621499497873603021">Χρόνος που απομένει μέχρι να αδειάσει η μπαταρία, <ph name="TIME_LEFT"/></translation>
+<translation id="5980301590375426705">Έξοδος επισκέπτη</translation>
+<translation id="4471417012762451363">Η μπαταρία είναι πλήρης <ph name="PERCENTAGE"/>% και φορτίζει</translation>
+<translation id="8308637677604853869">Προηγούμενο μενού</translation>
+<translation id="4666297444214622512">Δεν είναι δυνατή η σύνδεση σε άλλο λογαριασμό.</translation>
+<translation id="1346748346194534595">Δεξιά</translation>
+<translation id="1773212559869067373">Το πιστοποιητικό ελέγχου ταυτότητας απορρίφθηκε τοπικά</translation>
+<translation id="8528322925433439945">Κινητή τηλεφωνία...</translation>
+<translation id="7049357003967926684">Συσχετισμός</translation>
+<translation id="8428213095426709021">Ρυθμίσεις</translation>
+<translation id="2372145515558759244">Συγχρονισμός εφαρμογών…</translation>
+<translation id="7256405249507348194">Μη αναγνωρίσιμο σφάλμα: <ph name="DESC"/></translation>
+<translation id="7925247922861151263">Ο έλεγχος ΑΑΑ απέτυχε</translation>
+<translation id="8456362689280298700"><ph name="HOUR"/>:<ph name="MINUTE"/> μέχρι να γεμίσει</translation>
+<translation id="5787281376604286451">Τα προφορικά σχόλια έχουν ενεργοποιηθεί.
+Πατήστε Ctrl+Alt+Z για απενεργοποίηση.</translation>
+<translation id="4479639480957787382">Ethernet</translation>
+<translation id="6312403991423642364">Άγνωστο σφάλμα δικτύου</translation>
+<translation id="1467432559032391204">Αριστερά</translation>
+<translation id="5543001071567407895">SMS</translation>
+<translation id="2354174487190027830">Ενεργοποίηση <ph name="NAME"/></translation>
+<translation id="8814190375133053267">Wi-Fi</translation>
+<translation id="1398853756734560583">Μεγιστοποίηση</translation>
+<translation id="2692809339924654275"><ph name="BLUETOOTH"/>: Σύνδεση…</translation>
+<translation id="252373100621549798">Άγνωστη οθόνη</translation>
+<translation id="1882897271359938046">Κατοπτρισμός σε <ph name="DISPLAY_NAME"/></translation>
+<translation id="2727977024730340865">Σύνδεση με φορτιστή χαμηλής ισχύος. Η φόρτιση της μπαταρίας ενδέχεται να μη γίνεται με αξιόπιστο τρόπο.</translation>
+<translation id="3784455785234192852">Κλείδωμα</translation>
+<translation id="2805756323405976993">Εφαρμογές</translation>
+<translation id="8871072142849158571">Η ανάλυση της οθόνης <ph name="DISPLAY_NAME"/> έχει αλλάξει σε <ph name="RESOLUTION"/></translation>
+<translation id="1512064327686280138">Αποτυχία ενεργοποίησης</translation>
+<translation id="5097002363526479830">Δεν ήταν δυνατή η σύνδεση στο δίκτυο '<ph name="NAME"/>': <ph name="DETAILS"/></translation>
+<translation id="1850504506766569011">Το Wi-Fi έχει απενεργοποιηθεί.</translation>
+<translation id="8132793192354020517">Έγινε σύνδεση με το δίκτυο <ph name="NAME"/></translation>
+<translation id="7052914147756339792">Ορισμός ταπετσαρίας...</translation>
+<translation id="8678698760965522072">Κατάσταση &quot;Σε σύνδεση&quot;</translation>
+<translation id="2532589005999780174">Λειτουργία υψηλής αντίθεσης</translation>
+<translation id="1119447706177454957">Εσωτερικό σφάλμα</translation>
+<translation id="3019353588588144572">Χρόνος που απομένει μέχρι να φορτιστεί πλήρως η μπαταρία, <ph name="TIME_REMAINING"/></translation>
+<translation id="3473479545200714844">Μεγεθυντής οθόνης</translation>
+<translation id="7005812687360380971">Αποτυχία</translation>
+<translation id="882279321799040148">Κάντε κλικ για προβολή</translation>
+<translation id="5045550434625856497">Λανθασμένος κωδικός πρόσβασης</translation>
+<translation id="1602076796624386989">Ενεργοποίηση δεδομένων κινητής τηλεφωνίας</translation>
+<translation id="6981982820502123353">Προσβασιμότητα</translation>
+<translation id="3157931365184549694">Επαναφορά</translation>
+<translation id="4274292172790327596">Μη αναγνωρίσιμο σφάλμα</translation>
+<translation id="4032485810211612751"><ph name="HOURS"/>:<ph name="MINUTES"/>:<ph name="SECONDS"/></translation>
+<translation id="225680501294068881">Σάρωση για συσκευές…</translation>
+<translation id="5597451508971090205"><ph name="SHORT_WEEKDAY"/>, <ph name="DATE"/></translation>
+<translation id="4448844063988177157">Αναζήτηση για δίκτυα Wi-Fi...</translation>
+<translation id="8401662262483418323">Αποτυχία σύνδεσης στο δίκτυο '<ph name="NAME"/>': <ph name="DETAILS"/> μήνυμα διακομιστή: <ph name="SERVER_MSG"/></translation>
+<translation id="2475982808118771221">Παρουσιάστηκε σφάλμα</translation>
+<translation id="7229570126336867161">Απαιτείται EVDO</translation>
+<translation id="2999742336789313416">Το <ph name="DISPLAY_NAME"/> είναι μια δημόσια περίοδος σύνδεσης που διαχειρίζεται το <ph name="DOMAIN"/></translation>
+<translation id="7029814467594812963">Έξοδος από συνεδρία</translation>
+<translation id="8454013096329229812">Το Wi-Fi έχει ενεργοποιηθεί.</translation>
+<translation id="4872237917498892622">Alt+Search ή Shift</translation>
+<translation id="2983818520079887040">Ρυθμίσεις...</translation>
+<translation id="1717216362413677834">Λειτουργία Dock</translation>
+<translation id="8927026611342028580">Έχει υποβληθεί αίτημα σύνδεσης</translation>
+<translation id="8300849813060516376">Αποτυχία OTASP</translation>
+<translation id="2792498699870441125">Alt+Search</translation>
+<translation id="8660803626959853127">Συγχρονισμός <ph name="COUNT"/> αρχείων</translation>
+<translation id="3709443003275901162">9+</translation>
+<translation id="639644700271529076">Το CAPS LOCK είναι απενεργοποιημένο</translation>
+<translation id="6248847161401822652">Πατήστε Control Shift Q δύο φορές για έξοδο.</translation>
+<translation id="6267036997247669271"><ph name="NAME"/>: Ενεργοποίηση…</translation>
+<translation id="1391854757121130358">Ενδέχεται να έχετε χρησιμοποιήσει το πρόγραμμά δεδομένων της κινητής τηλεφωνίας σας.</translation>
+<translation id="5413208160176941586">Χρήστης τοπικής διαχείρισης</translation>
+<translation id="1059194134494239015"><ph name="DISPLAY_NAME"/>: <ph name="RESOLUTION"/></translation>
+<translation id="4864165860509564259">Θέση λειτουργίας εκκίνησης</translation>
+<translation id="7593891976182323525">Search ή Shift</translation>
+<translation id="7649070708921625228">Βοήθεια</translation>
+<translation id="3050422059534974565">Το πλήκτρο CAPS LOCK έχει ενεργοποιηθεί.
+Πατήστε Search ή Shift για ακύρωση.</translation>
+<translation id="397105322502079400">Υπολογισμός…</translation>
+<translation id="158849752021629804">Απαιτείται εγχώριο δίκτυο</translation>
+<translation id="6857811139397017780">Ενεργοποίηση <ph name="NETWORKSERVICE"/></translation>
+<translation id="5864471791310927901">Αποτυχία αναζήτησης DHCP</translation>
+<translation id="5812035014844949013">ΕΞΟΔΟΣ</translation>
+<translation id="6692173217867674490">Εσφαλμένη κωδική φράση</translation>
+<translation id="6165508094623778733">Μάθετε περισσότερα</translation>
+<translation id="9046895021617826162">Η σύνδεση απέτυχε</translation>
+<translation id="973896785707726617">Αυτή η περίοδος σύνδεσης θα λήξει αυτόματα σε <ph name="SESSION_TIME_REMAINING"/>. Θα αποσυνδεθείτε αυτόματα.</translation>
+<translation id="8372369524088641025">Εσφαλμένο κλειδί WEP</translation>
+<translation id="6636709850131805001">Μη αναγνωρίσιμη κατάσταση</translation>
+<translation id="3573179567135747900">Αλλάξτε το πάλι σε &quot;<ph name="FROM_LOCALE"/>&quot; (απαιτείται επανεκκίνηση)</translation>
+<translation id="8103386449138765447">Μηνύματα SMS: <ph name="MESSAGE_COUNT"/></translation>
+<translation id="5045002648206642691">Ρυθμίσεις Google Drive...</translation>
+<translation id="1510238584712386396">Λειτουργία εκκίνησης</translation>
+<translation id="7209101170223508707">Το πλήκτρο CAPS LOCK έχει ενεργοποιηθεί.
+Πατήστε Alt+Search ή Shift για ακύρωση.</translation>
+<translation id="8940956008527784070">Χαμηλή στάθμη μπαταρίας (<ph name="PERCENTAGE"/>%)</translation>
+<translation id="5102001756192215136">Απομένουν <ph name="HOUR"/>:<ph name="MINUTE"/></translation>
+<translation id="520760366042891468">Γίνεται κοινή χρήση της οθόνης σας μέσω Hangouts.</translation>
+<translation id="8000066093800657092">Κανένα δίκτυο</translation>
+<translation id="4015692727874266537">Σύνδεση σε άλλο λογαριασμό…</translation>
+<translation id="5941711191222866238">Ελαχιστοποίηση</translation>
+<translation id="6911468394164995108">Συμμετοχή σε άλλο…</translation>
+<translation id="412065659894267608"><ph name="HOUR"/>ω <ph name="MINUTE"/>λ μέχρι να ολοκληρωθεί η φόρτιση</translation>
+<translation id="6359806961507272919">SMS από <ph name="PHONE_NUMBER"/></translation>
+<translation id="1244147615850840081">Εταιρεία κινητής τηλεφωνίας</translation>
+</translationbundle> \ No newline at end of file
diff --git a/chromium/ash/strings/ash_strings_en-GB.xtb b/chromium/ash/strings/ash_strings_en-GB.xtb
new file mode 100644
index 00000000000..c0e1ded60ce
--- /dev/null
+++ b/chromium/ash/strings/ash_strings_en-GB.xtb
@@ -0,0 +1,201 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="en-GB">
+<translation id="3595596368722241419">Battery full</translation>
+<translation id="5250713215130379958">Auto-hide launcher</translation>
+<translation id="7814236020522506259"><ph name="HOUR"/> and <ph name="MINUTE"/></translation>
+<translation id="7880025619322806991">Portal state</translation>
+<translation id="30155388420722288">Overflow Button</translation>
+<translation id="5571066253365925590">Bluetooth enabled</translation>
+<translation id="9074739597929991885">Bluetooth</translation>
+<translation id="2268130516524549846">Bluetooth disabled</translation>
+<translation id="3775358506042162758">You can only have up to three accounts in multiple sign-in.</translation>
+<translation id="370649949373421643">Enable Wi-Fi</translation>
+<translation id="3626281679859535460">Brightness</translation>
+<translation id="8054466585765276473">Calculating battery time.</translation>
+<translation id="7982789257301363584">Network</translation>
+<translation id="5565793151875479467">Proxy...</translation>
+<translation id="938582441709398163">Keyboard Overlay</translation>
+<translation id="4387004326333427325">Authentication certificate rejected remotely</translation>
+<translation id="6979158407327259162">Google Drive</translation>
+<translation id="6943836128787782965">HTTP get failed</translation>
+<translation id="2297568595583585744">Status tray</translation>
+<translation id="1661867754829461514">PIN missing</translation>
+<translation id="4508225577814909926"><ph name="NAME"/>: Connecting...</translation>
+<translation id="4237016987259239829">Network Connection Error</translation>
+<translation id="2946640296642327832">Enable Bluetooth</translation>
+<translation id="6459472438155181876">Extending screen to <ph name="DISPLAY_NAME"/></translation>
+<translation id="8206859287963243715">Mobile</translation>
+<translation id="6596816719288285829">IP Address</translation>
+<translation id="4508265954913339219">Activation failed</translation>
+<translation id="3621712662352432595">Audio Settings</translation>
+<translation id="1812696562331527143">Your input method has changed to <ph name="INPUT_METHOD_ID"/>*(<ph name="BEGIN_LINK"/>3rd party<ph name="END_LINK"/>).
+Press Shift + Alt to switch.</translation>
+<translation id="2127372758936585790">Low-power charger</translation>
+<translation id="3846575436967432996">No network information available</translation>
+<translation id="3026237328237090306">Set up mobile data</translation>
+<translation id="785750925697875037">View mobile account</translation>
+<translation id="153454903766751181">Initialising mobile modem...</translation>
+<translation id="4628814525959230255">Sharing control of your screen with <ph name="HELPER_NAME"/> via Hangouts.</translation>
+<translation id="8343941333792395995"><ph name="DISPLAY_NAME"/> has been rotated</translation>
+<translation id="7864539943188674973">Disable Bluetooth</translation>
+<translation id="939252827960237676">Failed to save screenshot</translation>
+<translation id="3126069444801937830">Restart to update</translation>
+<translation id="2268813581635650749">Sign out all</translation>
+<translation id="735745346212279324">VPN disconnected</translation>
+<translation id="7320906967354320621">Idle</translation>
+<translation id="6303423059719347535">Battery is <ph name="PERCENTAGE"/>% full</translation>
+<translation id="15373452373711364">Large mouse cursor</translation>
+<translation id="2778346081696727092">Failed to authenticate with the provided username or password</translation>
+<translation id="3294437725009624529">Guest</translation>
+<translation id="8190698733819146287">Customise languages and input...</translation>
+<translation id="2903907270192926896">INPUT</translation>
+<translation id="8676770494376880701">Low-power charger connected</translation>
+<translation id="7170041865419449892">Out of range</translation>
+<translation id="4804818685124855865">Disconnect</translation>
+<translation id="2544853746127077729">Authentication certificate rejected by network</translation>
+<translation id="5222676887888702881">Sign out</translation>
+<translation id="2688477613306174402">Configuration</translation>
+<translation id="1272079795634619415">Stop</translation>
+<translation id="4957722034734105353">Learn more...</translation>
+<translation id="2964193600955408481">Disable Wi-Fi</translation>
+<translation id="811680302244032017">Add device ...</translation>
+<translation id="4279490309300973883">Mirroring</translation>
+<translation id="2509468283778169019">CAPS LOCK is on</translation>
+<translation id="3892641579809465218">Internal Display</translation>
+<translation id="7823564328645135659">The language has changed from &quot;<ph name="FROM_LOCALE"/>&quot; to &quot;<ph name="TO_LOCALE"/>&quot; after syncing your settings.</translation>
+<translation id="3368922792935385530">Connected</translation>
+<translation id="8340999562596018839">Spoken feedback</translation>
+<translation id="8654520615680304441">Turn Wi-Fi on...</translation>
+<translation id="5825747213122829519">Your input method has changed to <ph name="INPUT_METHOD_ID"/>.
+Press Shift + Alt to switch.</translation>
+<translation id="2562916301614567480">Private Network</translation>
+<translation id="6549021752953852991">No mobile network available</translation>
+<translation id="4379753398862151997">Dear Monitor, it's not working out between us. (That monitor is not supported)</translation>
+<translation id="6426039856985689743">Disable mobile data</translation>
+<translation id="3087734570205094154">Bottom</translation>
+<translation id="3742055079367172538">Screenshot taken</translation>
+<translation id="8878886163241303700">Extending screen</translation>
+<translation id="5271016907025319479">VPN is not configured.</translation>
+<translation id="372094107052732682">Press Ctrl+Shift+Q twice to exit.</translation>
+<translation id="6803622936009808957">Could not mirror displays since no supported resolutions found. Entered extended desktop instead.</translation>
+<translation id="1480041086352807611">Demo mode</translation>
+<translation id="3626637461649818317"><ph name="PERCENTAGE"/>% remaining</translation>
+<translation id="9089416786594320554">Input methods</translation>
+<translation id="6247708409970142803"><ph name="PERCENTAGE"/>%</translation>
+<translation id="2614835198358683673">Your Chromebook may not charge while it is turned on. Consider using the official charger.</translation>
+<translation id="1895658205118569222">Shutdown</translation>
+<translation id="4430019312045809116">volume</translation>
+<translation id="4442424173763614572">DNS lookup failed</translation>
+<translation id="6356500677799115505">Battery is full and charging.</translation>
+<translation id="7874779702599364982">Searching for cellular networks...</translation>
+<translation id="583281660410589416">Unknown</translation>
+<translation id="1383876407941801731">Search</translation>
+<translation id="7468789844759750875">Visit the <ph name="NAME"/> activation portal to buy more data.</translation>
+<translation id="3901991538546252627">Connecting to <ph name="NAME"/></translation>
+<translation id="2204305834655267233">Network Info</translation>
+<translation id="1621499497873603021">Time left until battery is empty, <ph name="TIME_LEFT"/></translation>
+<translation id="5980301590375426705">Exit guest</translation>
+<translation id="4471417012762451363">Battery is <ph name="PERCENTAGE"/>% full and charging</translation>
+<translation id="8308637677604853869">Previous menu</translation>
+<translation id="4666297444214622512">Can't sign into another account.</translation>
+<translation id="1346748346194534595">Right</translation>
+<translation id="1773212559869067373">Authentication certificate rejected locally</translation>
+<translation id="8528322925433439945">Mobile ...</translation>
+<translation id="7049357003967926684">Association</translation>
+<translation id="8428213095426709021">Settings</translation>
+<translation id="2372145515558759244">Syncing apps...</translation>
+<translation id="7256405249507348194">Unrecognised error: <ph name="DESC"/></translation>
+<translation id="7925247922861151263">AAA check failed</translation>
+<translation id="8456362689280298700"><ph name="HOUR"/>:<ph name="MINUTE"/> until full</translation>
+<translation id="5787281376604286451">Spoken feedback is enabled.
+Press Ctrl+Alt+Z to disable.</translation>
+<translation id="4479639480957787382">Ethernet</translation>
+<translation id="6312403991423642364">Unknown network error</translation>
+<translation id="1467432559032391204">Left</translation>
+<translation id="5543001071567407895">SMS</translation>
+<translation id="2354174487190027830">Activating <ph name="NAME"/></translation>
+<translation id="8814190375133053267">Wi-Fi</translation>
+<translation id="1398853756734560583">Maximise</translation>
+<translation id="2692809339924654275"><ph name="BLUETOOTH"/>: Connecting...</translation>
+<translation id="252373100621549798">Unknown Display</translation>
+<translation id="1882897271359938046">Mirroring to <ph name="DISPLAY_NAME"/></translation>
+<translation id="2727977024730340865">Plugged in to a low-power charger. Battery charging may not be reliable.</translation>
+<translation id="3784455785234192852">Lock</translation>
+<translation id="2805756323405976993">Apps</translation>
+<translation id="8871072142849158571"><ph name="DISPLAY_NAME"/> has been resized to <ph name="RESOLUTION"/></translation>
+<translation id="1512064327686280138">Activation failure</translation>
+<translation id="5097002363526479830">Failed to connect to the network '<ph name="NAME"/>': <ph name="DETAILS"/></translation>
+<translation id="1850504506766569011">Wi-Fi is turned off.</translation>
+<translation id="8132793192354020517">Connected to <ph name="NAME"/></translation>
+<translation id="7052914147756339792">Set wallpaper...</translation>
+<translation id="8678698760965522072">Online state</translation>
+<translation id="2532589005999780174">High contrast mode</translation>
+<translation id="1119447706177454957">Internal error</translation>
+<translation id="3019353588588144572">Time remaining until battery is fully charged, <ph name="TIME_REMAINING"/></translation>
+<translation id="3473479545200714844">Screen magnifier</translation>
+<translation id="7005812687360380971">Failure</translation>
+<translation id="882279321799040148">Click to view</translation>
+<translation id="5045550434625856497">Incorrect password</translation>
+<translation id="1602076796624386989">Enable mobile data</translation>
+<translation id="6981982820502123353">Accessibility</translation>
+<translation id="3157931365184549694">Restore</translation>
+<translation id="4274292172790327596">Unrecognised error</translation>
+<translation id="4032485810211612751"><ph name="HOURS"/>:<ph name="MINUTES"/>:<ph name="SECONDS"/></translation>
+<translation id="225680501294068881">Scanning for devices...</translation>
+<translation id="5597451508971090205"><ph name="SHORT_WEEKDAY"/>, <ph name="DATE"/></translation>
+<translation id="4448844063988177157">Searching for Wi-Fi networks...</translation>
+<translation id="8401662262483418323">Failed to connect to '<ph name="NAME"/>': <ph name="DETAILS"/>
+Server message: <ph name="SERVER_MSG"/></translation>
+<translation id="2475982808118771221">An error occurred</translation>
+<translation id="7229570126336867161">Need EVDO</translation>
+<translation id="2999742336789313416"><ph name="DISPLAY_NAME"/> is a public session managed by <ph name="DOMAIN"/></translation>
+<translation id="7029814467594812963">Exit session</translation>
+<translation id="8454013096329229812">Wi-Fi is turned on.</translation>
+<translation id="4872237917498892622">Alt+Search or Shift</translation>
+<translation id="2983818520079887040">Settings...</translation>
+<translation id="1717216362413677834">Dock mode</translation>
+<translation id="8927026611342028580">Connect Requested</translation>
+<translation id="8300849813060516376">OTASP failed</translation>
+<translation id="2792498699870441125">Alt+Search</translation>
+<translation id="8660803626959853127">Syncing <ph name="COUNT"/> file(s)</translation>
+<translation id="3709443003275901162">9+</translation>
+<translation id="639644700271529076">CAPS LOCK is off</translation>
+<translation id="6248847161401822652">Press Control Shift Q twice to exit.</translation>
+<translation id="6267036997247669271"><ph name="NAME"/>: Activating...</translation>
+<translation id="1391854757121130358">You may have used up your mobile data allowance.</translation>
+<translation id="5413208160176941586">Locally managed user</translation>
+<translation id="1059194134494239015"><ph name="DISPLAY_NAME"/>: <ph name="RESOLUTION"/></translation>
+<translation id="4864165860509564259">Launcher position</translation>
+<translation id="7593891976182323525">Search or Shift</translation>
+<translation id="7649070708921625228">Help</translation>
+<translation id="3050422059534974565">CAPS LOCK is on.
+Press Search or Shift to cancel.</translation>
+<translation id="397105322502079400">Calculating...</translation>
+<translation id="158849752021629804">Need home network</translation>
+<translation id="6857811139397017780">Activate <ph name="NETWORKSERVICE"/></translation>
+<translation id="5864471791310927901">DHCP look-up failed</translation>
+<translation id="5812035014844949013">OUTPUT</translation>
+<translation id="6692173217867674490">Bad passphrase</translation>
+<translation id="6165508094623778733">Learn more</translation>
+<translation id="9046895021617826162">Connection failed</translation>
+<translation id="973896785707726617">This session will end in <ph name="SESSION_TIME_REMAINING"/>. You will be automatically signed out.</translation>
+<translation id="8372369524088641025">Bad WEP key</translation>
+<translation id="6636709850131805001">Unrecognised state</translation>
+<translation id="3573179567135747900">Change back to &quot;<ph name="FROM_LOCALE"/>&quot; (requires restart)</translation>
+<translation id="8103386449138765447">SMS messages: <ph name="MESSAGE_COUNT"/></translation>
+<translation id="5045002648206642691">Google Drive settings</translation>
+<translation id="1510238584712386396">Launcher</translation>
+<translation id="7209101170223508707">CAPS LOCK is on.
+Press Alt+Search or Shift to cancel.</translation>
+<translation id="8940956008527784070">Battery low (<ph name="PERCENTAGE"/>%)</translation>
+<translation id="5102001756192215136"><ph name="HOUR"/>:<ph name="MINUTE"/> left</translation>
+<translation id="520760366042891468">Sharing control of your screen via Hangouts.</translation>
+<translation id="8000066093800657092">No network</translation>
+<translation id="4015692727874266537">Sign in another account...</translation>
+<translation id="5941711191222866238">Minimise</translation>
+<translation id="6911468394164995108">Join other ...</translation>
+<translation id="412065659894267608"><ph name="HOUR"/>h <ph name="MINUTE"/>m until full</translation>
+<translation id="6359806961507272919">SMS from <ph name="PHONE_NUMBER"/></translation>
+<translation id="1244147615850840081">Operator</translation>
+</translationbundle> \ No newline at end of file
diff --git a/chromium/ash/strings/ash_strings_es-419.xtb b/chromium/ash/strings/ash_strings_es-419.xtb
new file mode 100644
index 00000000000..b752b8e297c
--- /dev/null
+++ b/chromium/ash/strings/ash_strings_es-419.xtb
@@ -0,0 +1,201 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="es-419">
+<translation id="3595596368722241419">Batería completa</translation>
+<translation id="5250713215130379958">Ocultar automáticamente la barra de aplicaciones</translation>
+<translation id="7814236020522506259"><ph name="HOUR"/> y <ph name="MINUTE"/></translation>
+<translation id="7880025619322806991">Estado de portal</translation>
+<translation id="30155388420722288">Botón de desbordamiento</translation>
+<translation id="5571066253365925590">Bluetooth activado</translation>
+<translation id="9074739597929991885">Bluetooth</translation>
+<translation id="2268130516524549846">Bluetooth desactivado</translation>
+<translation id="3775358506042162758">Solo puedes tener un máximo de tres cuentas en un acceso múltiple.</translation>
+<translation id="370649949373421643">Habilitar Wi-Fi</translation>
+<translation id="3626281679859535460">Brillo</translation>
+<translation id="8054466585765276473">Calculando duración de la batería...</translation>
+<translation id="7982789257301363584">Red</translation>
+<translation id="5565793151875479467">Proxy...</translation>
+<translation id="938582441709398163">Superposición del teclado</translation>
+<translation id="4387004326333427325">Certificado de autenticación rechazado de forma remota</translation>
+<translation id="6979158407327259162">Google Drive</translation>
+<translation id="6943836128787782965">Error al obtener HTTP</translation>
+<translation id="2297568595583585744">Bandeja de estado</translation>
+<translation id="1661867754829461514">Falta el número de PIN </translation>
+<translation id="4508225577814909926"><ph name="NAME"/>: Conectando...</translation>
+<translation id="4237016987259239829">Error de conexión de red</translation>
+<translation id="2946640296642327832">Activar Bluetooth</translation>
+<translation id="6459472438155181876">Ampliando pantalla para <ph name="DISPLAY_NAME"/></translation>
+<translation id="8206859287963243715">Celular</translation>
+<translation id="6596816719288285829">Dirección IP</translation>
+<translation id="4508265954913339219">Falló la activación</translation>
+<translation id="3621712662352432595">Configuración de audio</translation>
+<translation id="1812696562331527143">Tu método de introducción cambió a <ph name="INPUT_METHOD_ID"/>*(<ph name="BEGIN_LINK"/>tercero<ph name="END_LINK"/>).
+Para cambiarlo, presiona Shift + Alt.</translation>
+<translation id="2127372758936585790">Cargador de baja potencia</translation>
+<translation id="3846575436967432996">No hay información de red disponible.</translation>
+<translation id="3026237328237090306">Configurar datos de dispositivos móviles</translation>
+<translation id="785750925697875037">Ver cuenta móvil</translation>
+<translation id="153454903766751181">Iniciando módem celular...</translation>
+<translation id="4628814525959230255">Compartir el control de la pantalla con <ph name="HELPER_NAME"/> a través de Hangouts</translation>
+<translation id="8343941333792395995">Se rotó <ph name="DISPLAY_NAME"/>.</translation>
+<translation id="7864539943188674973">Desactivar Bluetooth</translation>
+<translation id="939252827960237676">No se pudo guardar la captura de pantalla.</translation>
+<translation id="3126069444801937830">Reinicia para actualizar.</translation>
+<translation id="2268813581635650749">Salir de todo</translation>
+<translation id="735745346212279324">VPN desconectada</translation>
+<translation id="7320906967354320621">Inactivo</translation>
+<translation id="6303423059719347535"><ph name="PERCENTAGE"/>% de batería</translation>
+<translation id="15373452373711364">Cursor del mouse grande</translation>
+<translation id="2778346081696727092">Error al autenticar con el nombre de usuario o la contraseña proporcionados.</translation>
+<translation id="3294437725009624529">Invitado</translation>
+<translation id="8190698733819146287">Personalizar idiomas y la entrada de datos</translation>
+<translation id="2903907270192926896">ENTRADA</translation>
+<translation id="8676770494376880701">Cargador de baja potencia conectado</translation>
+<translation id="7170041865419449892">Fuera de alcance</translation>
+<translation id="4804818685124855865">Desconectar</translation>
+<translation id="2544853746127077729">Certificado de autenticación rechazado por la red</translation>
+<translation id="5222676887888702881">Salir</translation>
+<translation id="2688477613306174402">Configuración</translation>
+<translation id="1272079795634619415">Interrumpir</translation>
+<translation id="4957722034734105353">Más información...</translation>
+<translation id="2964193600955408481">Desactivar Wi-Fi</translation>
+<translation id="811680302244032017">Agregar dispositivo...</translation>
+<translation id="4279490309300973883">Duplicando</translation>
+<translation id="2509468283778169019">BLOQ MAYÚS está activado.</translation>
+<translation id="3892641579809465218">Pantalla interna</translation>
+<translation id="7823564328645135659">Después de sincronizar tu configuración, el idioma se cambió de &quot;<ph name="FROM_LOCALE"/>&quot; a &quot;<ph name="TO_LOCALE"/>&quot;.</translation>
+<translation id="3368922792935385530">Conectado</translation>
+<translation id="8340999562596018839">Comentarios por voz</translation>
+<translation id="8654520615680304441">Encender Wi-Fi...</translation>
+<translation id="5825747213122829519">Tu método de introducción cambió a <ph name="INPUT_METHOD_ID"/>.
+Para cambiarlo, presiona Shift + Alt.</translation>
+<translation id="2562916301614567480">Red privada</translation>
+<translation id="6549021752953852991">No hay redes celulares disponibles.</translation>
+<translation id="4379753398862151997">Querido Monitor, lo nuestro no funciona. (Este monitor no es compatible).</translation>
+<translation id="6426039856985689743">Inhabilitar datos de dispositivos móviles</translation>
+<translation id="3087734570205094154">Inferior</translation>
+<translation id="3742055079367172538">Captura de pantalla tomada</translation>
+<translation id="8878886163241303700">Ampliando pantalla</translation>
+<translation id="5271016907025319479">La VPN no está configurada.</translation>
+<translation id="372094107052732682">Presiona Ctrl+Mayús+Q dos veces para salir.</translation>
+<translation id="6803622936009808957">No se pudieron reflejar las pantallas porque no se encontraron resoluciones compatibles. En su lugar, se activó el escritorio extendido.</translation>
+<translation id="1480041086352807611">Modo demo</translation>
+<translation id="3626637461649818317"><ph name="PERCENTAGE"/>% restante</translation>
+<translation id="9089416786594320554">Métodos de entrada</translation>
+<translation id="6247708409970142803"><ph name="PERCENTAGE"/>%</translation>
+<translation id="2614835198358683673">Es posible que tu Chromebook no se cargue mientras esté encendida. Te recomendamos que utilices el cargador oficial.</translation>
+<translation id="1895658205118569222">Cierre</translation>
+<translation id="4430019312045809116">Volumen</translation>
+<translation id="4442424173763614572">Error al buscar DNS</translation>
+<translation id="6356500677799115505">Batería completa y cargando</translation>
+<translation id="7874779702599364982">Buscando redes para celulares...</translation>
+<translation id="583281660410589416">Desconocido</translation>
+<translation id="1383876407941801731">Buscar</translation>
+<translation id="7468789844759750875">Visita el portal de activación de <ph name="NAME"/> para comprar más datos.</translation>
+<translation id="3901991538546252627">Conectando con: <ph name="NAME"/></translation>
+<translation id="2204305834655267233">Información de red</translation>
+<translation id="1621499497873603021">Tiempo restante hasta que se agote la batería: <ph name="TIME_LEFT"/></translation>
+<translation id="5980301590375426705">Salir de la sesión de invitado</translation>
+<translation id="4471417012762451363"><ph name="PERCENTAGE"/>% de batería y cargando</translation>
+<translation id="8308637677604853869">Menú anterior</translation>
+<translation id="4666297444214622512">No puedes acceder a otra cuenta.</translation>
+<translation id="1346748346194534595">Derecha</translation>
+<translation id="1773212559869067373">Certificado de autenticación rechazado de forma local</translation>
+<translation id="8528322925433439945">Dispositivos móviles...</translation>
+<translation id="7049357003967926684">Asociación</translation>
+<translation id="8428213095426709021">Configuración</translation>
+<translation id="2372145515558759244">Sincronizando aplicaciones...</translation>
+<translation id="7256405249507348194">Error desconocido: <ph name="DESC"/></translation>
+<translation id="7925247922861151263">Falló la verificación de AAA</translation>
+<translation id="8456362689280298700"><ph name="HOUR"/>:<ph name="MINUTE"/> hasta completar</translation>
+<translation id="5787281376604286451">Los comentarios de voz están habilitados.
+Para inhabilitar esta opción, presiona Ctrl+Alt+Z.</translation>
+<translation id="4479639480957787382">Ethernet</translation>
+<translation id="6312403991423642364">Error de red desconocido</translation>
+<translation id="1467432559032391204">Izquierda</translation>
+<translation id="5543001071567407895">SMS</translation>
+<translation id="2354174487190027830">Activación de <ph name="NAME"/></translation>
+<translation id="8814190375133053267">Wi-Fi</translation>
+<translation id="1398853756734560583">Maximizar</translation>
+<translation id="2692809339924654275"><ph name="BLUETOOTH"/>: Conectando...</translation>
+<translation id="252373100621549798">Pantalla desconocida</translation>
+<translation id="1882897271359938046">Copiando en <ph name="DISPLAY_NAME"/></translation>
+<translation id="2727977024730340865">Conexión a un cargador de baja potencia. Es posible que la carga de la batería no sea confiable.</translation>
+<translation id="3784455785234192852">Bloquear</translation>
+<translation id="2805756323405976993">Aplicaciones</translation>
+<translation id="8871072142849158571">Se cambió la resolución de <ph name="DISPLAY_NAME"/> a <ph name="RESOLUTION"/>.</translation>
+<translation id="1512064327686280138">Fallo en la activación</translation>
+<translation id="5097002363526479830">Error al conectar a la red &quot;<ph name="NAME"/>&quot;: <ph name="DETAILS"/></translation>
+<translation id="1850504506766569011">Wi-Fi desactivada</translation>
+<translation id="8132793192354020517">Conectado a <ph name="NAME"/></translation>
+<translation id="7052914147756339792">Establecer fondo de pantalla...</translation>
+<translation id="8678698760965522072">Estado en línea</translation>
+<translation id="2532589005999780174">Modo de contraste alto</translation>
+<translation id="1119447706177454957">Error interno</translation>
+<translation id="3019353588588144572">Tiempo restante hasta que la batería esté completamente cargada: <ph name="TIME_REMAINING"/></translation>
+<translation id="3473479545200714844">Lupa</translation>
+<translation id="7005812687360380971">Fallo</translation>
+<translation id="882279321799040148">Haz clic para verla.</translation>
+<translation id="5045550434625856497">Contraseña incorrecta</translation>
+<translation id="1602076796624386989">Habilitar datos de dispositivos móviles</translation>
+<translation id="6981982820502123353">Accesibilidad</translation>
+<translation id="3157931365184549694">Restaurar</translation>
+<translation id="4274292172790327596">Error no reconocido</translation>
+<translation id="4032485810211612751"><ph name="HOURS"/>:<ph name="MINUTES"/>:<ph name="SECONDS"/></translation>
+<translation id="225680501294068881">Buscando dispositivos...</translation>
+<translation id="5597451508971090205"><ph name="SHORT_WEEKDAY"/>, <ph name="DATE"/></translation>
+<translation id="4448844063988177157">Buscando redes Wi-Fi...</translation>
+<translation id="8401662262483418323">Error al conectarse a &quot;<ph name="NAME"/>&quot;: <ph name="DETAILS"/>
+Mensaje del servidor: <ph name="SERVER_MSG"/></translation>
+<translation id="2475982808118771221">Se ha producido un error</translation>
+<translation id="7229570126336867161">Se necesita EVDO</translation>
+<translation id="2999742336789313416"><ph name="DISPLAY_NAME"/> es una sesión pública administrada por <ph name="DOMAIN"/>.</translation>
+<translation id="7029814467594812963">Salir de la sesión</translation>
+<translation id="8454013096329229812">Wi-Fi activada</translation>
+<translation id="4872237917498892622">Alt+tecla de búsqueda o Mayús</translation>
+<translation id="2983818520079887040">Configuración...</translation>
+<translation id="1717216362413677834">Modo de conector</translation>
+<translation id="8927026611342028580">Conexión solicitada</translation>
+<translation id="8300849813060516376">OTASP falló</translation>
+<translation id="2792498699870441125">Alt+tecla de búsqueda</translation>
+<translation id="8660803626959853127">Sincronizando <ph name="COUNT"/> archivo(s)</translation>
+<translation id="3709443003275901162">Más de 9</translation>
+<translation id="639644700271529076">El bloqueo de mayúsculas está desactivado.</translation>
+<translation id="6248847161401822652">Presiona Control+Mayús+Q dos veces para salir.</translation>
+<translation id="6267036997247669271"><ph name="NAME"/>: Activando...</translation>
+<translation id="1391854757121130358">Es posible que hayas agotado tu cuota de datos móviles.</translation>
+<translation id="5413208160176941586">Usuario administrado de forma local</translation>
+<translation id="1059194134494239015"><ph name="DISPLAY_NAME"/>: <ph name="RESOLUTION"/></translation>
+<translation id="4864165860509564259">Posición del selector</translation>
+<translation id="7593891976182323525">Tecla de búsqueda o Mayús</translation>
+<translation id="7649070708921625228">Ayuda</translation>
+<translation id="3050422059534974565">El BLOQUEO DE MAYÚSCULAS está activado.
+Presiona Mayús o la tecla de búsqueda para cancelar la operación.</translation>
+<translation id="397105322502079400">Calculando...</translation>
+<translation id="158849752021629804">Se necesita red local</translation>
+<translation id="6857811139397017780">Activar <ph name="NETWORKSERVICE"/></translation>
+<translation id="5864471791310927901">Falló la búsqueda de DHCP</translation>
+<translation id="5812035014844949013">SALIDA</translation>
+<translation id="6692173217867674490">Frase de contraseña no válida</translation>
+<translation id="6165508094623778733">Más información</translation>
+<translation id="9046895021617826162">No se pudo conectar</translation>
+<translation id="973896785707726617">Esta sesión finalizará en <ph name="SESSION_TIME_REMAINING"/>. Saldrás automáticamente.</translation>
+<translation id="8372369524088641025">Clave de WEP no válida</translation>
+<translation id="6636709850131805001">Estado no reconocido</translation>
+<translation id="3573179567135747900">Volver a &quot;<ph name="FROM_LOCALE"/>&quot; (debes reiniciar).</translation>
+<translation id="8103386449138765447">Mensajes SMS: <ph name="MESSAGE_COUNT"/></translation>
+<translation id="5045002648206642691">Configuración de Google Drive...</translation>
+<translation id="1510238584712386396">Selector</translation>
+<translation id="7209101170223508707">El BLOQUEO DE MAYÚSCULAS está activado.
+Presiona Alt y la tecla de búsqueda o Mayús para cancelar la operación.</translation>
+<translation id="8940956008527784070">Batería baja (<ph name="PERCENTAGE"/>%)</translation>
+<translation id="5102001756192215136"><ph name="HOUR"/>:<ph name="MINUTE"/> restante</translation>
+<translation id="520760366042891468">Compartir el control de la pantalla a través de Hangouts</translation>
+<translation id="8000066093800657092">Sin red</translation>
+<translation id="4015692727874266537">Acceder a otra cuenta</translation>
+<translation id="5941711191222866238">Minimizar</translation>
+<translation id="6911468394164995108">Conectarte a otra red...</translation>
+<translation id="412065659894267608"><ph name="HOUR"/>h <ph name="MINUTE"/>min para completar la carga</translation>
+<translation id="6359806961507272919">SMS de <ph name="PHONE_NUMBER"/></translation>
+<translation id="1244147615850840081">Proveedor de servicio celular</translation>
+</translationbundle> \ No newline at end of file
diff --git a/chromium/ash/strings/ash_strings_es.xtb b/chromium/ash/strings/ash_strings_es.xtb
new file mode 100644
index 00000000000..d3cc029c9b8
--- /dev/null
+++ b/chromium/ash/strings/ash_strings_es.xtb
@@ -0,0 +1,201 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="es">
+<translation id="3595596368722241419">Batería al máximo</translation>
+<translation id="5250713215130379958">Ocultar automáticamente la barra de aplicaciones</translation>
+<translation id="7814236020522506259"><ph name="HOUR"/> y <ph name="MINUTE"/></translation>
+<translation id="7880025619322806991">Conectado vía portal</translation>
+<translation id="30155388420722288">Botón de flujo excesivo</translation>
+<translation id="5571066253365925590">Bluetooth habilitado</translation>
+<translation id="9074739597929991885">Bluetooth</translation>
+<translation id="2268130516524549846">Bluetooth inhabilitado</translation>
+<translation id="3775358506042162758">Solo puedes utilizar un máximo de tres cuentas en el inicio de sesión múltiple.</translation>
+<translation id="370649949373421643">Habilitar Wi-Fi</translation>
+<translation id="3626281679859535460">Brillo</translation>
+<translation id="8054466585765276473">Calculando duración de la batería...</translation>
+<translation id="7982789257301363584">Red</translation>
+<translation id="5565793151875479467">Proxy...</translation>
+<translation id="938582441709398163">Superposición de teclado</translation>
+<translation id="4387004326333427325">Certificado de autenticación rechazado de forma remota</translation>
+<translation id="6979158407327259162">Google Drive</translation>
+<translation id="6943836128787782965">Error al obtener HTTP</translation>
+<translation id="2297568595583585744">Bandeja de estado</translation>
+<translation id="1661867754829461514">Falta el PIN.</translation>
+<translation id="4508225577814909926"><ph name="NAME"/>: conectando...</translation>
+<translation id="4237016987259239829">Error de conexión de red</translation>
+<translation id="2946640296642327832">Habilitar Bluetooth</translation>
+<translation id="6459472438155181876">Ampliando pantalla para <ph name="DISPLAY_NAME"/></translation>
+<translation id="8206859287963243715">Móvil</translation>
+<translation id="6596816719288285829">Dirección IP</translation>
+<translation id="4508265954913339219">Error de activación</translation>
+<translation id="3621712662352432595">Configuración de audio</translation>
+<translation id="1812696562331527143">Tu método de entrada ha cambiado a <ph name="INPUT_METHOD_ID"/>*(<ph name="BEGIN_LINK"/>externo<ph name="END_LINK"/>).
+Para cambiarlo, pulsa Mayús + Alt.</translation>
+<translation id="2127372758936585790">Cargador de baja potencia</translation>
+<translation id="3846575436967432996">No hay información de red disponible.</translation>
+<translation id="3026237328237090306">Configurar datos móviles</translation>
+<translation id="785750925697875037">Ver cuenta móvil</translation>
+<translation id="153454903766751181">Iniciando módem móvil...</translation>
+<translation id="4628814525959230255">Comparte el control de tu pantalla con <ph name="HELPER_NAME"/> a través de Hangouts.</translation>
+<translation id="8343941333792395995">Se ha girado <ph name="DISPLAY_NAME"/></translation>
+<translation id="7864539943188674973">Inhabilitar Bluetooth</translation>
+<translation id="939252827960237676">Error al guardar captura de pantalla</translation>
+<translation id="3126069444801937830">Reinicia el sistema para actualizarlo.</translation>
+<translation id="2268813581635650749">Cerrar todas las sesiones</translation>
+<translation id="735745346212279324">VPN desconectada</translation>
+<translation id="7320906967354320621">Inactiva</translation>
+<translation id="6303423059719347535"><ph name="PERCENTAGE"/>% de batería</translation>
+<translation id="15373452373711364">Cursor del ratón grande</translation>
+<translation id="2778346081696727092">Se ha producido un error al autenticar la contraseña o el nombre de usuario proporcionados.</translation>
+<translation id="3294437725009624529">Invitado</translation>
+<translation id="8190698733819146287">Personalizar idiomas...</translation>
+<translation id="2903907270192926896">ENTRADA</translation>
+<translation id="8676770494376880701">Cargador de baja potencia conectado</translation>
+<translation id="7170041865419449892">Fuera del alcance</translation>
+<translation id="4804818685124855865">Desvincular</translation>
+<translation id="2544853746127077729">Certificado de autenticación rechazado por la red</translation>
+<translation id="5222676887888702881">Cerrar sesión</translation>
+<translation id="2688477613306174402">Configuración</translation>
+<translation id="1272079795634619415">Interrumpir</translation>
+<translation id="4957722034734105353">Más información...</translation>
+<translation id="2964193600955408481">Inhabilitar Wi-Fi</translation>
+<translation id="811680302244032017">Añadir dispositivo...</translation>
+<translation id="4279490309300973883">Duplicando</translation>
+<translation id="2509468283778169019">Bloqueo de mayúsculas activado</translation>
+<translation id="3892641579809465218">Pantalla interna</translation>
+<translation id="7823564328645135659">El idioma ha cambiado de &quot;<ph name="FROM_LOCALE"/>&quot; a &quot;<ph name="TO_LOCALE"/>&quot; después de sincronizar tu configuración.</translation>
+<translation id="3368922792935385530">Con conexión</translation>
+<translation id="8340999562596018839">Mensajes de voz</translation>
+<translation id="8654520615680304441">Activar Wi-Fi...</translation>
+<translation id="5825747213122829519">Tu método de entrada ha cambiado a <ph name="INPUT_METHOD_ID"/>.
+Para cambiarlo, pulsa Mayús + Alt.</translation>
+<translation id="2562916301614567480">Red privada</translation>
+<translation id="6549021752953852991">No hay ninguna red móvil disponible.</translation>
+<translation id="4379753398862151997">Querido monitor, lo nuestro no funciona... (No se admite el uso de ese monitor).</translation>
+<translation id="6426039856985689743">Inhabilitar datos móviles</translation>
+<translation id="3087734570205094154">Inferior</translation>
+<translation id="3742055079367172538">Captura de pantalla hecha</translation>
+<translation id="8878886163241303700">Ampliando pantalla</translation>
+<translation id="5271016907025319479">VPN no configurada</translation>
+<translation id="372094107052732682">Pulsa Ctrl+Mayús+Q dos veces para salir.</translation>
+<translation id="6803622936009808957">No se han podido duplicar las pantallas porque no se han encontrado resoluciones compatibles. Se ha utilizado el modo de escritorio ampliado en su lugar.</translation>
+<translation id="1480041086352807611">Modo demo</translation>
+<translation id="3626637461649818317"><ph name="PERCENTAGE"/>% restante</translation>
+<translation id="9089416786594320554">Métodos de entrada</translation>
+<translation id="6247708409970142803"><ph name="PERCENTAGE"/>%</translation>
+<translation id="2614835198358683673">Es posible que tu Chromebook no se cargue mientras esté activado. Te recomendamos que utilices el cargador oficial.</translation>
+<translation id="1895658205118569222">Cierre del navegador</translation>
+<translation id="4430019312045809116">Volumen</translation>
+<translation id="4442424173763614572">Error al buscar DNS</translation>
+<translation id="6356500677799115505">Batería completa y cargándose</translation>
+<translation id="7874779702599364982">Buscando redes móviles...</translation>
+<translation id="583281660410589416">Desconocido</translation>
+<translation id="1383876407941801731">Búsqueda</translation>
+<translation id="7468789844759750875">Visita el portal de activación de <ph name="NAME"/> para comprar más datos.</translation>
+<translation id="3901991538546252627">Conectando con: <ph name="NAME"/></translation>
+<translation id="2204305834655267233">Información de red</translation>
+<translation id="1621499497873603021">Tiempo restante hasta que se agote la batería: <ph name="TIME_LEFT"/></translation>
+<translation id="5980301590375426705">Salir de la sesión de invitado</translation>
+<translation id="4471417012762451363"><ph name="PERCENTAGE"/>% de batería y cargándose</translation>
+<translation id="8308637677604853869">Menú anterior</translation>
+<translation id="4666297444214622512">No puedo iniciar sesión con otra cuenta.</translation>
+<translation id="1346748346194534595">Derecha</translation>
+<translation id="1773212559869067373">Certificado de autenticación rechazado de forma local</translation>
+<translation id="8528322925433439945">Redes móviles...</translation>
+<translation id="7049357003967926684">Asociación</translation>
+<translation id="8428213095426709021">Configuración</translation>
+<translation id="2372145515558759244">Sincronizando aplicaciones...</translation>
+<translation id="7256405249507348194">Error no reconocido: <ph name="DESC"/></translation>
+<translation id="7925247922861151263">Error de comprobación de AAA</translation>
+<translation id="8456362689280298700"><ph name="HOUR"/>:<ph name="MINUTE"/> hasta cargarse</translation>
+<translation id="5787281376604286451">Los mensajes de voz están habilitados.
+Para inhabilitar esta opción, pulsa Ctrl+Alt+Z.</translation>
+<translation id="4479639480957787382">Ethernet</translation>
+<translation id="6312403991423642364">Error desconocido de red</translation>
+<translation id="1467432559032391204">Izquierda</translation>
+<translation id="5543001071567407895">SMS</translation>
+<translation id="2354174487190027830">Activación de <ph name="NAME"/></translation>
+<translation id="8814190375133053267">Wi-Fi</translation>
+<translation id="1398853756734560583">Maximizar</translation>
+<translation id="2692809339924654275"><ph name="BLUETOOTH"/>: conectando...</translation>
+<translation id="252373100621549798">Pantalla desconocida</translation>
+<translation id="1882897271359938046">Copiando en <ph name="DISPLAY_NAME"/></translation>
+<translation id="2727977024730340865">Conectado a un cargador de baja potencia. Es posible que la carga de la batería no sea fiable.</translation>
+<translation id="3784455785234192852">Bloquear</translation>
+<translation id="2805756323405976993">Aplicaciones</translation>
+<translation id="8871072142849158571">Se ha cambiado la resolución de <ph name="DISPLAY_NAME"/> a <ph name="RESOLUTION"/>.</translation>
+<translation id="1512064327686280138">Error de activación</translation>
+<translation id="5097002363526479830">Error al establecer conexión con la red &quot;<ph name="NAME"/>&quot;: <ph name="DETAILS"/></translation>
+<translation id="1850504506766569011">La conexión Wi-Fi está desactivada.</translation>
+<translation id="8132793192354020517">Conectado a <ph name="NAME"/></translation>
+<translation id="7052914147756339792">Establecer fondo de pantalla...</translation>
+<translation id="8678698760965522072">Estado online</translation>
+<translation id="2532589005999780174">Modo de contraste alto</translation>
+<translation id="1119447706177454957">Error interno</translation>
+<translation id="3019353588588144572">Tiempo restante hasta que se cargue la batería por completo: <ph name="TIME_REMAINING"/></translation>
+<translation id="3473479545200714844">Lupa</translation>
+<translation id="7005812687360380971">Error</translation>
+<translation id="882279321799040148">Haz clic para verla</translation>
+<translation id="5045550434625856497">Contraseña incorrecta</translation>
+<translation id="1602076796624386989">Habilitar datos móviles</translation>
+<translation id="6981982820502123353">Accesibilidad</translation>
+<translation id="3157931365184549694">Restaurar</translation>
+<translation id="4274292172790327596">Error desconocido</translation>
+<translation id="4032485810211612751"><ph name="HOURS"/>:<ph name="MINUTES"/>:<ph name="SECONDS"/></translation>
+<translation id="225680501294068881">Buscando dispositivos...</translation>
+<translation id="5597451508971090205"><ph name="SHORT_WEEKDAY"/>, <ph name="DATE"/></translation>
+<translation id="4448844063988177157">Buscando redes Wi-Fi...</translation>
+<translation id="8401662262483418323">Error al conectarse a &quot;<ph name="NAME"/>&quot;: <ph name="DETAILS"/>
+Mensaje del servidor: <ph name="SERVER_MSG"/></translation>
+<translation id="2475982808118771221">Se ha producido un error.</translation>
+<translation id="7229570126336867161">Es necesario el estándar EVDO.</translation>
+<translation id="2999742336789313416"><ph name="DISPLAY_NAME"/> es una sesión pública administrada por <ph name="DOMAIN"/>.</translation>
+<translation id="7029814467594812963">Cerrar sesión</translation>
+<translation id="8454013096329229812">La conexión Wi-Fi está activada.</translation>
+<translation id="4872237917498892622">Alt+tecla de búsqueda o Mayús</translation>
+<translation id="2983818520079887040">Configuración...</translation>
+<translation id="1717216362413677834">Modo de conector</translation>
+<translation id="8927026611342028580">Conexión solicitada</translation>
+<translation id="8300849813060516376">Error de OTASP</translation>
+<translation id="2792498699870441125">Alt+tecla de búsqueda</translation>
+<translation id="8660803626959853127">Sincronizando <ph name="COUNT"/> archivo(s)</translation>
+<translation id="3709443003275901162">+9</translation>
+<translation id="639644700271529076">Bloqueo de mayúsculas desactivado</translation>
+<translation id="6248847161401822652">Pulsa Ctrl+Mayús+Q dos veces para salir.</translation>
+<translation id="6267036997247669271"><ph name="NAME"/>: Activando...</translation>
+<translation id="1391854757121130358">Es posible que hayas agotado los datos de tu plan de datos móviles.</translation>
+<translation id="5413208160176941586">Usuario administrado de forma local</translation>
+<translation id="1059194134494239015"><ph name="DISPLAY_NAME"/>: <ph name="RESOLUTION"/></translation>
+<translation id="4864165860509564259">Posición de la barra de aplicaciones</translation>
+<translation id="7593891976182323525">Tecla de búsqueda o Mayús</translation>
+<translation id="7649070708921625228">Ayuda</translation>
+<translation id="3050422059534974565">El BLOQUEO DE MAYÚSCULAS está activado.
+Pulsa Mayús o la tecla de búsqueda para cancelar la operación.</translation>
+<translation id="397105322502079400">Calculando...</translation>
+<translation id="158849752021629804">Es necesaria una red doméstica.</translation>
+<translation id="6857811139397017780">Activar <ph name="NETWORKSERVICE"/></translation>
+<translation id="5864471791310927901">Error de búsqueda de DHCP</translation>
+<translation id="5812035014844949013">SALIDA</translation>
+<translation id="6692173217867674490">Frase de contraseña incorrecta</translation>
+<translation id="6165508094623778733">Más información</translation>
+<translation id="9046895021617826162">Error de conexión</translation>
+<translation id="973896785707726617">Esta sesión finalizará en <ph name="SESSION_TIME_REMAINING"/>. La sesión se cerrará automáticamente.</translation>
+<translation id="8372369524088641025">Clave WEP incorrecta</translation>
+<translation id="6636709850131805001">Estado desconocido</translation>
+<translation id="3573179567135747900">Cambiar de nuevo por &quot;<ph name="FROM_LOCALE"/>&quot; (requiere reiniciar)</translation>
+<translation id="8103386449138765447">Mensajes SMS: <ph name="MESSAGE_COUNT"/></translation>
+<translation id="5045002648206642691">Configuración de Google Drive...</translation>
+<translation id="1510238584712386396">Menú de aplicaciones</translation>
+<translation id="7209101170223508707">El BLOQUEO DE MAYÚSCULAS está activado.
+Pulsa Alt y la tecla de búsqueda o Mayús para cancelar la operación.</translation>
+<translation id="8940956008527784070">Poca batería (<ph name="PERCENTAGE"/>%)</translation>
+<translation id="5102001756192215136">Queda para <ph name="HOUR"/>:<ph name="MINUTE"/></translation>
+<translation id="520760366042891468">Comparte el control de tu pantalla a través de Hangouts.</translation>
+<translation id="8000066093800657092">Ninguna red</translation>
+<translation id="4015692727874266537">Inicia sesión con otra cuenta...</translation>
+<translation id="5941711191222866238">Minimizar</translation>
+<translation id="6911468394164995108">Conectarse a otra red...</translation>
+<translation id="412065659894267608"><ph name="HOUR"/> h y <ph name="MINUTE"/> min para completar la carga</translation>
+<translation id="6359806961507272919">SMS de <ph name="PHONE_NUMBER"/></translation>
+<translation id="1244147615850840081">Operador</translation>
+</translationbundle> \ No newline at end of file
diff --git a/chromium/ash/strings/ash_strings_et.xtb b/chromium/ash/strings/ash_strings_et.xtb
new file mode 100644
index 00000000000..67871d1640a
--- /dev/null
+++ b/chromium/ash/strings/ash_strings_et.xtb
@@ -0,0 +1,201 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="et">
+<translation id="3595596368722241419">Aku on täis</translation>
+<translation id="5250713215130379958">Peida käiviti automaatselt</translation>
+<translation id="7814236020522506259"><ph name="HOUR"/> ja <ph name="MINUTE"/></translation>
+<translation id="7880025619322806991">Portaali olek</translation>
+<translation id="30155388420722288">Ülevoolunupp</translation>
+<translation id="5571066253365925590">Bluetooth on lubatud</translation>
+<translation id="9074739597929991885">Bluetooth</translation>
+<translation id="2268130516524549846">Bluetooth on keelatud</translation>
+<translation id="3775358506042162758">Saate korraga sisse logida kuni kolmele kontole.</translation>
+<translation id="370649949373421643">Luba WiFi</translation>
+<translation id="3626281679859535460">Eredus</translation>
+<translation id="8054466585765276473">Aku tööaja arvutamine.</translation>
+<translation id="7982789257301363584">Võrk</translation>
+<translation id="5565793151875479467">Puhverserver ...</translation>
+<translation id="938582441709398163">Klaviatuuri ülekate</translation>
+<translation id="4387004326333427325">Autentimissertifikaat lükati kaugühenduse kaudu tagasi</translation>
+<translation id="6979158407327259162">Google Drive</translation>
+<translation id="6943836128787782965">HTTP hankimine nurjus</translation>
+<translation id="2297568595583585744">Olekusalv</translation>
+<translation id="1661867754829461514">PIN-kood puudub</translation>
+<translation id="4508225577814909926"><ph name="NAME"/>: ühendamine ...</translation>
+<translation id="4237016987259239829">Võrguühenduse viga</translation>
+<translation id="2946640296642327832">Luba Bluetooth</translation>
+<translation id="6459472438155181876">Ekraani laiendamine seadmesse <ph name="DISPLAY_NAME"/></translation>
+<translation id="8206859287963243715">Mobiil</translation>
+<translation id="6596816719288285829">IP-aadress</translation>
+<translation id="4508265954913339219">Aktiveerimine nurjus</translation>
+<translation id="3621712662352432595">Heliseaded</translation>
+<translation id="1812696562331527143">Teie sisestusmeetod on nüüd <ph name="INPUT_METHOD_ID"/>*(<ph name="BEGIN_LINK"/>kolmas osapool<ph name="END_LINK"/>).
+Selle muutmiseks vajutage klahve Tõstuklahv + Alt.</translation>
+<translation id="2127372758936585790">Väikese energiakuluga laadija</translation>
+<translation id="3846575436967432996">Võrguteave ei ole saadaval</translation>
+<translation id="3026237328237090306">Seadista mobiilne andmeside</translation>
+<translation id="785750925697875037">Kuva mobiilikonto</translation>
+<translation id="153454903766751181">Mobiilimodemi lähtestamine ...</translation>
+<translation id="4628814525959230255">Teie ekraani juhtimist jagatakse Hangoutsi kaudu kasutajaga <ph name="HELPER_NAME"/>.</translation>
+<translation id="8343941333792395995"><ph name="DISPLAY_NAME"/> on pööratud</translation>
+<translation id="7864539943188674973">Keela Bluetooth</translation>
+<translation id="939252827960237676">Ekraanipilti ei õnnestunud salvestada</translation>
+<translation id="3126069444801937830">Taaskäivitage värskendamiseks</translation>
+<translation id="2268813581635650749">Logi kõik kasutajad välja</translation>
+<translation id="735745346212279324">VPN-i ühendus on katkestatud</translation>
+<translation id="7320906967354320621">Tegevusetu</translation>
+<translation id="6303423059719347535">Aku on <ph name="PERCENTAGE"/>% täis</translation>
+<translation id="15373452373711364">Suur hiirekursor</translation>
+<translation id="2778346081696727092">Autentimine esitatud kasutajanime või parooliga ebaõnnestus</translation>
+<translation id="3294437725009624529">Külaline</translation>
+<translation id="8190698733819146287">Keelte ja sisendi kohandamine...</translation>
+<translation id="2903907270192926896">SISEND</translation>
+<translation id="8676770494376880701">Väikese energiakuluga laadija on ühendatud</translation>
+<translation id="7170041865419449892">Vahemikust väljas</translation>
+<translation id="4804818685124855865">Katkesta ühendus</translation>
+<translation id="2544853746127077729">Võrk lükkas autentimissertifikaadi tagasi</translation>
+<translation id="5222676887888702881">Logi välja</translation>
+<translation id="2688477613306174402">Konfigureerimine</translation>
+<translation id="1272079795634619415">Peata</translation>
+<translation id="4957722034734105353">Lisateave ...</translation>
+<translation id="2964193600955408481">Keela WiFi</translation>
+<translation id="811680302244032017">Lisa seade ...</translation>
+<translation id="4279490309300973883">Peegeldamine</translation>
+<translation id="2509468283778169019">SUURTÄHELUKK on sisse lülitatud</translation>
+<translation id="3892641579809465218">Sisemine kuva</translation>
+<translation id="7823564328645135659">Pärast seadete sünkroonimist asendati <ph name="FROM_LOCALE"/> keel <ph name="TO_LOCALE"/> keelega.</translation>
+<translation id="3368922792935385530">Ühendatud</translation>
+<translation id="8340999562596018839">Suuline tagasiside</translation>
+<translation id="8654520615680304441">Lülita WiFi sisse ...</translation>
+<translation id="5825747213122829519">Teie sisestusmeetod on nüüd <ph name="INPUT_METHOD_ID"/>.
+Selle muutmiseks vajutage klahve Tõstuklahv + Alt.</translation>
+<translation id="2562916301614567480">Privaatvõrk</translation>
+<translation id="6549021752953852991">Mobiilivõrk pole saadaval</translation>
+<translation id="4379753398862151997">Monitor, kahjuks ei tule meie koostööst midagi välja. (Seda monitori ei toetata)</translation>
+<translation id="6426039856985689743">Keela mobiilne andmeside</translation>
+<translation id="3087734570205094154">Alaserv</translation>
+<translation id="3742055079367172538">Ekraanipilt on tehtud</translation>
+<translation id="8878886163241303700">Ekraani laiendamine</translation>
+<translation id="5271016907025319479">VPN on seadistamata.</translation>
+<translation id="372094107052732682">Väljumiseks vajutage kaks korda klahvikombinatsiooni Ctrl + tõstuklahv + Q.</translation>
+<translation id="6803622936009808957">Ei saanud kuvasid peegeldada, kuna toetatud eraldusvõimeid ei leitud. Selle asemel siseneti laiendatud töölaua režiimi.</translation>
+<translation id="1480041086352807611">Demorežiim</translation>
+<translation id="3626637461649818317"><ph name="PERCENTAGE"/>% on jäänud</translation>
+<translation id="9089416786594320554">Sisestusviisid</translation>
+<translation id="6247708409970142803"><ph name="PERCENTAGE"/>%</translation>
+<translation id="2614835198358683673">Teie Chromebooki ei pruugita laadida, kui see on sisse lülitatud. Kaaluge ametliku laadija kasutamist.</translation>
+<translation id="1895658205118569222">Sulgemine</translation>
+<translation id="4430019312045809116">Helitugevus</translation>
+<translation id="4442424173763614572">DNS-i otsing nurjus</translation>
+<translation id="6356500677799115505">Aku on täis ja seda laetakse.</translation>
+<translation id="7874779702599364982">Mobiilsidevõrkude otsimine ...</translation>
+<translation id="583281660410589416">Tundmatu</translation>
+<translation id="1383876407941801731">Otsing</translation>
+<translation id="7468789844759750875">Külastage võrgu <ph name="NAME"/> aktiveerimisportaali, et rohkem andmemahtu osta.</translation>
+<translation id="3901991538546252627">Võrguga <ph name="NAME"/> ühenduse loomine</translation>
+<translation id="2204305834655267233">Võrguteave</translation>
+<translation id="1621499497873603021">Aku tühjenemiseni on aega <ph name="TIME_LEFT"/></translation>
+<translation id="5980301590375426705">Välju külastajaseansist</translation>
+<translation id="4471417012762451363">Aku on <ph name="PERCENTAGE"/>% täis ja seda laetakse</translation>
+<translation id="8308637677604853869">Eelmine menüü</translation>
+<translation id="4666297444214622512">Teisele kontole ei saa sisse logida.</translation>
+<translation id="1346748346194534595">Paremale</translation>
+<translation id="1773212559869067373">Autentimissertifikaat lükati kohalikult tagasi</translation>
+<translation id="8528322925433439945">Mobiil ...</translation>
+<translation id="7049357003967926684">Sidumine</translation>
+<translation id="8428213095426709021">Seaded</translation>
+<translation id="2372145515558759244">Rakenduste sünkroonimine ...</translation>
+<translation id="7256405249507348194">Tundmatu viga: <ph name="DESC"/></translation>
+<translation id="7925247922861151263">AAA kontrollimine nurjus</translation>
+<translation id="8456362689280298700"><ph name="HOUR"/>:<ph name="MINUTE"/> täislaadimiseni</translation>
+<translation id="5787281376604286451">Suuline tagasiside on lubatud.
+Keelamiseks vajutage klahvikombinatsiooni Ctrl+Alt+Z.</translation>
+<translation id="4479639480957787382">Ethernet</translation>
+<translation id="6312403991423642364">Tundmatu võrguviga</translation>
+<translation id="1467432559032391204">Vasakule</translation>
+<translation id="5543001071567407895">SMS</translation>
+<translation id="2354174487190027830">Võrgu <ph name="NAME"/> aktiveerimine</translation>
+<translation id="8814190375133053267">WiFi</translation>
+<translation id="1398853756734560583">Maksimeeri</translation>
+<translation id="2692809339924654275"><ph name="BLUETOOTH"/>: ühendamine ...</translation>
+<translation id="252373100621549798">Tundmatu ekraan</translation>
+<translation id="1882897271359938046">Peegeldamine asukohta <ph name="DISPLAY_NAME"/></translation>
+<translation id="2727977024730340865">Ühendatud väikese energiakuluga laadijaga. Aku laadimine võib olla ebastabiilne.</translation>
+<translation id="3784455785234192852">Lukusta</translation>
+<translation id="2805756323405976993">Rakendused</translation>
+<translation id="8871072142849158571">Kuva <ph name="DISPLAY_NAME"/> eraldusvõimeks valiti <ph name="RESOLUTION"/></translation>
+<translation id="1512064327686280138">Aktiveerimise tõrge</translation>
+<translation id="5097002363526479830">Võrguga „<ph name="NAME"/>” ühenduse loomine ebaõnnestus: <ph name="DETAILS"/></translation>
+<translation id="1850504506766569011">WiFi on välja lülitatud.</translation>
+<translation id="8132793192354020517">Ühendus <ph name="NAME"/>iga</translation>
+<translation id="7052914147756339792">Määra taustapilt ...</translation>
+<translation id="8678698760965522072">Võrguühenduse olek</translation>
+<translation id="2532589005999780174">Suure kontrastsusega režiim</translation>
+<translation id="1119447706177454957">Sisemine viga</translation>
+<translation id="3019353588588144572">Aku täitumiseni on aega <ph name="TIME_REMAINING"/></translation>
+<translation id="3473479545200714844">Ekraanisuurendi</translation>
+<translation id="7005812687360380971">Tõrge</translation>
+<translation id="882279321799040148">Vaatamiseks klõpsake</translation>
+<translation id="5045550434625856497">Vale parool</translation>
+<translation id="1602076796624386989">Luba mobiilne andmeside</translation>
+<translation id="6981982820502123353">Juurdepääsetavus</translation>
+<translation id="3157931365184549694">Taasta</translation>
+<translation id="4274292172790327596">Tundmatu viga</translation>
+<translation id="4032485810211612751"><ph name="HOURS"/>:<ph name="MINUTES"/>:<ph name="SECONDS"/></translation>
+<translation id="225680501294068881">Seadmete skannimine ...</translation>
+<translation id="5597451508971090205"><ph name="SHORT_WEEKDAY"/>, <ph name="DATE"/></translation>
+<translation id="4448844063988177157">WiFi võrkude otsimine ...</translation>
+<translation id="8401662262483418323">Võrguga <ph name="NAME"/> ühenduse loomine ebaõnnestus: <ph name="DETAILS"/>
+Serveri teade: <ph name="SERVER_MSG"/></translation>
+<translation id="2475982808118771221">Tekkis viga</translation>
+<translation id="7229570126336867161">Vajalik EVDO</translation>
+<translation id="2999742336789313416"><ph name="DISPLAY_NAME"/> on avalik seanss, mida haldab <ph name="DOMAIN"/></translation>
+<translation id="7029814467594812963">Välju seansist</translation>
+<translation id="8454013096329229812">WiFi on sisse lülitatud.</translation>
+<translation id="4872237917498892622">Alt + otsinguklahv või tõstuklahv</translation>
+<translation id="2983818520079887040">Seaded...</translation>
+<translation id="1717216362413677834">Dokirežiim</translation>
+<translation id="8927026611342028580">Ühenduse taotlus</translation>
+<translation id="8300849813060516376">OTASP nurjus</translation>
+<translation id="2792498699870441125">Alt + otsinguklahv</translation>
+<translation id="8660803626959853127"><ph name="COUNT"/> faili sünkroonimine</translation>
+<translation id="3709443003275901162">9+</translation>
+<translation id="639644700271529076">SUURTÄHELUKK on välja lülitatud</translation>
+<translation id="6248847161401822652">Väljumiseks vajutage kaks korda klahvikombinatsiooni Ctrl + tõstuklahv + Q.</translation>
+<translation id="6267036997247669271"><ph name="NAME"/>: aktiveerimine ...</translation>
+<translation id="1391854757121130358">Tundub, et olete oma mobiilse andmesidemahu ära kasutanud.</translation>
+<translation id="5413208160176941586">Kohalikult hallatud kasutaja</translation>
+<translation id="1059194134494239015"><ph name="DISPLAY_NAME"/>: <ph name="RESOLUTION"/></translation>
+<translation id="4864165860509564259">Käiviti positsioon</translation>
+<translation id="7593891976182323525">Otsinguklahv või tõstuklahv</translation>
+<translation id="7649070708921625228">Abi</translation>
+<translation id="3050422059534974565">SUURTÄHELUKK on sisse lülitatud.
+Tühistamiseks vajutage otsinguklahvi või tõstuklahvi</translation>
+<translation id="397105322502079400">Arvutamine ...</translation>
+<translation id="158849752021629804">Vajalik koduvõrk</translation>
+<translation id="6857811139397017780">Aktiveeri <ph name="NETWORKSERVICE"/></translation>
+<translation id="5864471791310927901">DHCP otsing nurjus</translation>
+<translation id="5812035014844949013">VÄLJUND</translation>
+<translation id="6692173217867674490">Halb parool</translation>
+<translation id="6165508094623778733">Lisateave</translation>
+<translation id="9046895021617826162">Ühendamine nurjus</translation>
+<translation id="973896785707726617">Selle seansi lõpuni on jäänud <ph name="SESSION_TIME_REMAINING"/>. Teid logitakse automaatselt välja.</translation>
+<translation id="8372369524088641025">Halb WEP-võti</translation>
+<translation id="6636709850131805001">Tundmatu olek</translation>
+<translation id="3573179567135747900">Muuda tagasi seadele <ph name="FROM_LOCALE"/> (nõuab taaskäivitust)</translation>
+<translation id="8103386449138765447">SMS-id: <ph name="MESSAGE_COUNT"/></translation>
+<translation id="5045002648206642691">Google Drive'i seaded ...</translation>
+<translation id="1510238584712386396">Käivitaja</translation>
+<translation id="7209101170223508707">SUURTÄHELUKK on sisse lülitatud.
+Tühistamiseks vajutage klahvikombinatsiooni Alt + otsinguklahv või tõstuklahv</translation>
+<translation id="8940956008527784070">Aku tühjeneb (<ph name="PERCENTAGE"/>%)</translation>
+<translation id="5102001756192215136"><ph name="HOUR"/>:<ph name="MINUTE"/> alles</translation>
+<translation id="520760366042891468">Teie ekraani juhtimist jagatakse Hangoutsi kaudu.</translation>
+<translation id="8000066093800657092">Võrku pole</translation>
+<translation id="4015692727874266537">Logige sisse teise kontoga ...</translation>
+<translation id="5941711191222866238">Minimeeri</translation>
+<translation id="6911468394164995108">Liitu muu võrguga ...</translation>
+<translation id="412065659894267608"><ph name="HOUR"/> h <ph name="MINUTE"/> min aku täitumiseni</translation>
+<translation id="6359806961507272919">SMS numbrilt <ph name="PHONE_NUMBER"/></translation>
+<translation id="1244147615850840081">Mobiilioperaator</translation>
+</translationbundle> \ No newline at end of file
diff --git a/chromium/ash/strings/ash_strings_fa.xtb b/chromium/ash/strings/ash_strings_fa.xtb
new file mode 100644
index 00000000000..9639c888882
--- /dev/null
+++ b/chromium/ash/strings/ash_strings_fa.xtb
@@ -0,0 +1,199 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="fa">
+<translation id="3595596368722241419">باتری پر است</translation>
+<translation id="5250713215130379958">مخفی کردن خودکار راه انداز</translation>
+<translation id="7814236020522506259"><ph name="HOUR"/> و <ph name="MINUTE"/></translation>
+<translation id="7880025619322806991">وضعیت سایت</translation>
+<translation id="30155388420722288">دکمه سرریز</translation>
+<translation id="5571066253365925590">بلوتوث فعال شد</translation>
+<translation id="9074739597929991885">بلوتوث</translation>
+<translation id="2268130516524549846">بلوتوث غیرفعال است</translation>
+<translation id="3775358506042162758">در ورود چندگانه به سیستم حداکثر می‌توانید از سه حساب استفاده کنید.</translation>
+<translation id="370649949373421643">فعال کردن Wi-Fi</translation>
+<translation id="3626281679859535460">روشنایی</translation>
+<translation id="8054466585765276473">درحال محاسبه زمان شارژ باتری.</translation>
+<translation id="7982789257301363584">شبکه</translation>
+<translation id="5565793151875479467">پراکسی...</translation>
+<translation id="938582441709398163">هم پوشانی صفحه‌کلید</translation>
+<translation id="4387004326333427325">گواهینامه تأیید اعتبار، از راه دور، رد شد</translation>
+<translation id="6979158407327259162">Google Drive</translation>
+<translation id="6943836128787782965">HTTP با خطا مواجه شد</translation>
+<translation id="2297568595583585744">سینی وضعیت</translation>
+<translation id="1661867754829461514">پین جا افتاده</translation>
+<translation id="4508225577814909926"><ph name="NAME"/>: در حال اتصال...</translation>
+<translation id="4237016987259239829">خطای اتصال شبکه</translation>
+<translation id="2946640296642327832">فعال کردن بلوتوث</translation>
+<translation id="6459472438155181876">گسترش صفحه به <ph name="DISPLAY_NAME"/></translation>
+<translation id="8206859287963243715">سلولی</translation>
+<translation id="6596816719288285829">آدرس IP</translation>
+<translation id="4508265954913339219">فعالسازی انجام نشد</translation>
+<translation id="3621712662352432595">تنظیمات صوتی</translation>
+<translation id="1812696562331527143">روش ورودی شما به <ph name="INPUT_METHOD_ID"/>*(<ph name="BEGIN_LINK"/>شخص ثالث<ph name="END_LINK"/>) تغییر یافت. Shift + Alt را برای تعویض فشار دهید.</translation>
+<translation id="2127372758936585790">شارژر برق ضعیف</translation>
+<translation id="3846575436967432996">اطلاعات شبکه در دسترس نیست</translation>
+<translation id="3026237328237090306">تنظیم اطلاعات تلفن همراه</translation>
+<translation id="785750925697875037">مشاهده حساب تلفن همراه</translation>
+<translation id="153454903766751181">در حال راه‌اندازی مودم سلولی...</translation>
+<translation id="4628814525959230255">اشتراک‌گذاری کنترل صفحه نمایش‌تان با <ph name="HELPER_NAME"/> از طریق Hangouts.</translation>
+<translation id="8343941333792395995"><ph name="DISPLAY_NAME"/> چرخانده شده است</translation>
+<translation id="7864539943188674973">غیرفعال کردن بلوتوث</translation>
+<translation id="939252827960237676">عکس از صفحه نمایش ذخیره نشد</translation>
+<translation id="3126069444801937830">راه‌اندازی مجدد برای به‌روزرسانی</translation>
+<translation id="2268813581635650749">خروج همه از سیستم</translation>
+<translation id="735745346212279324">VPN قطع شد</translation>
+<translation id="7320906967354320621">بدون فعالیت</translation>
+<translation id="6303423059719347535">باتری <ph name="PERCENTAGE"/> درصد پر است</translation>
+<translation id="15373452373711364">نشانگر موشواره بزرگ</translation>
+<translation id="2778346081696727092">تأیید اعتبار با نام کاربری و گذرواژه ارائه شده انجام نشد</translation>
+<translation id="3294437725009624529">مهمان</translation>
+<translation id="8190698733819146287">سفارشی کردن زبان‌ها و ورودی...</translation>
+<translation id="2903907270192926896">ورودی</translation>
+<translation id="8676770494376880701">شارژر برق متصل شده ضعیف است</translation>
+<translation id="7170041865419449892">خارج از محدوده</translation>
+<translation id="4804818685124855865">قطع اتصال</translation>
+<translation id="2544853746127077729">گواهینامه تأیید اعتبار توسط شبکه رد شد</translation>
+<translation id="5222676887888702881">خروج از سیستم</translation>
+<translation id="2688477613306174402">پیکربندی</translation>
+<translation id="1272079795634619415">توقف</translation>
+<translation id="4957722034734105353">اطلاعات بیشتر...</translation>
+<translation id="2964193600955408481">غیرفعال کردن Wi-Fi</translation>
+<translation id="811680302244032017">افزودن دستگاه…</translation>
+<translation id="4279490309300973883">بازتاب می‌شود</translation>
+<translation id="2509468283778169019">CAPS LOCK روشن است</translation>
+<translation id="3892641579809465218">صفحه نمایش داخلی</translation>
+<translation id="7823564328645135659">بعد از همگام‌سازی تنظیمات شما، زبان از «<ph name="FROM_LOCALE"/>» به «<ph name="TO_LOCALE"/>» تغییر کرد.</translation>
+<translation id="3368922792935385530">متصل</translation>
+<translation id="8340999562596018839">بازخورد گفتاری</translation>
+<translation id="8654520615680304441">روشن کردن Wi-Fi در...</translation>
+<translation id="5825747213122829519">روش ورودی شما به <ph name="INPUT_METHOD_ID"/> تغییر یافت. Shift + Alt را برای تعویض فشار دهید.</translation>
+<translation id="2562916301614567480">شبکه خصوصی</translation>
+<translation id="6549021752953852991">هیچ شبکه سلولی دردسترس نیست</translation>
+<translation id="4379753398862151997">نمایشگر عزیز، ما برای هم ساخته نشده‌ایم. (این نمایشگر پشتیبانی نمی‌شود)</translation>
+<translation id="6426039856985689743">غیرفعال کردن اطلاعات تلفن همراه</translation>
+<translation id="3087734570205094154">پایین</translation>
+<translation id="3742055079367172538">عکس از صفحه نمایش گرفته شد</translation>
+<translation id="8878886163241303700">صفحه گسترش یافته است</translation>
+<translation id="5271016907025319479">VPN پیکربندی نشده است.</translation>
+<translation id="372094107052732682">برای خروج Ctrl+Shift+Q را دو بار فشار دهید.</translation>
+<translation id="6803622936009808957">نمایش یک تصویر واحد در چند صفحه نمایش ممکن نیست زیرا وضوح تصویر پشتیبانی شده‌ای وجود ندارد. بجای آن حالت نمایش دسک‌تاپ چند بخشی استفاده می‌شود.</translation>
+<translation id="1480041086352807611">حالت نمایش</translation>
+<translation id="3626637461649818317"><ph name="PERCENTAGE"/>٪ باقیمانده</translation>
+<translation id="9089416786594320554">روش‌های ورودی</translation>
+<translation id="6247708409970142803">%<ph name="PERCENTAGE"/></translation>
+<translation id="2614835198358683673">وقتی Chromebook روشن است ممکن است شارژ نشود. از شارژر مخصوص دستگاه استفاده کنید.</translation>
+<translation id="1895658205118569222">بسته شدن</translation>
+<translation id="4430019312045809116">میزان صدا</translation>
+<translation id="4442424173763614572">جستجوی DNS انجام نشد</translation>
+<translation id="6356500677799115505">باتری پر است و شارژ می‌شود.</translation>
+<translation id="7874779702599364982">جستجو برای شبکه‌های تلفن همراه ...</translation>
+<translation id="583281660410589416">ناشناخته</translation>
+<translation id="1383876407941801731">جستجو</translation>
+<translation id="7468789844759750875">از پورتال فعال‌سازی <ph name="NAME"/> برای خرید داده‌های بیشتر بازدید کنید.</translation>
+<translation id="3901991538546252627">در حال اتصال به <ph name="NAME"/></translation>
+<translation id="2204305834655267233">اطلاعات شبکه</translation>
+<translation id="1621499497873603021">زمان باقیمانده تا خالی‌شدن شارژ باتری، <ph name="TIME_LEFT"/></translation>
+<translation id="5980301590375426705">خروج از مهمان</translation>
+<translation id="4471417012762451363">باتری <ph name="PERCENTAGE"/> درصد پر است و شارژ می‌شود</translation>
+<translation id="8308637677604853869">منوی قبلی</translation>
+<translation id="4666297444214622512">ورود به حساب دیگر ممکن نیست.</translation>
+<translation id="1346748346194534595">راست</translation>
+<translation id="1773212559869067373">گواهینامه تأیید اعتبار به صورت محلی رد شد</translation>
+<translation id="8528322925433439945">تلفن همراه...</translation>
+<translation id="7049357003967926684">همراه کردن</translation>
+<translation id="8428213095426709021">تنظیمات</translation>
+<translation id="2372145515558759244">در حال همگام‌سازی برنامه‌ها…</translation>
+<translation id="7256405249507348194">خطای نامشخص: <ph name="DESC"/></translation>
+<translation id="7925247922861151263">بررسی AAA انجام نشد</translation>
+<translation id="8456362689280298700"><ph name="HOUR"/>:<ph name="MINUTE"/> تا پر شود</translation>
+<translation id="5787281376604286451">بازخورد گفتاری فعال است.
+Ctrl+Alt+Z را فشار دید تا غیرفعال شود.</translation>
+<translation id="4479639480957787382">اترنت</translation>
+<translation id="6312403991423642364">خطای شبکه ناشناخته</translation>
+<translation id="1467432559032391204">چپ</translation>
+<translation id="5543001071567407895">پیامک</translation>
+<translation id="2354174487190027830">فعال‌سازی <ph name="NAME"/></translation>
+<translation id="8814190375133053267">Wi-Fi</translation>
+<translation id="1398853756734560583">بزرگ کردن</translation>
+<translation id="2692809339924654275"><ph name="BLUETOOTH"/>: در حال اتصال...</translation>
+<translation id="252373100621549798">نمایش ناشناخته</translation>
+<translation id="1882897271359938046">بازتاب به <ph name="DISPLAY_NAME"/></translation>
+<translation id="2727977024730340865">باتری به شارژر برق ضعیف متصل است. شارژ باتری ممکن است قابل اطمینان نباشد.</translation>
+<translation id="3784455785234192852">قفل</translation>
+<translation id="2805756323405976993">برنامه‌ها</translation>
+<translation id="8871072142849158571">اندازه <ph name="DISPLAY_NAME"/> به <ph name="RESOLUTION"/> تغییر داده شد</translation>
+<translation id="1512064327686280138">نقص در فعالسازی</translation>
+<translation id="5097002363526479830">اتصال ناموفق به شبکه &quot;<ph name="NAME"/>&quot;:<ph name="DETAILS"/></translation>
+<translation id="1850504506766569011">Wi-Fi خاموش است.</translation>
+<translation id="8132793192354020517">متصل به <ph name="NAME"/></translation>
+<translation id="7052914147756339792">تنظیم کاغذدیواری...</translation>
+<translation id="8678698760965522072">حالت آنلاین</translation>
+<translation id="2532589005999780174">حالت کنتراست بالا</translation>
+<translation id="1119447706177454957">خطای داخلی</translation>
+<translation id="3019353588588144572">زمان باقی مانده تا شارژ کامل باتری، <ph name="TIME_REMAINING"/></translation>
+<translation id="3473479545200714844">ذره‌بین صفحه</translation>
+<translation id="7005812687360380971">نقص</translation>
+<translation id="882279321799040148">برای مشاهده کلیک کنید</translation>
+<translation id="5045550434625856497">گذرواژه نامعتبر</translation>
+<translation id="1602076796624386989">فعال کردن اطلاعات تلفن همراه</translation>
+<translation id="6981982820502123353">قابلیت دسترسی</translation>
+<translation id="3157931365184549694">بازیابی</translation>
+<translation id="4274292172790327596">خطای ناشناس</translation>
+<translation id="4032485810211612751"><ph name="HOURS"/>:<ph name="MINUTES"/>:<ph name="SECONDS"/></translation>
+<translation id="225680501294068881">درحال جستجو برای دستگاه‌ها...</translation>
+<translation id="5597451508971090205"><ph name="SHORT_WEEKDAY"/>، <ph name="DATE"/></translation>
+<translation id="4448844063988177157">در حال جستجوی شبکه‌های Wi-Fi...</translation>
+<translation id="8401662262483418323">اتصال به «<ph name="NAME"/>» ناموفق بود: <ph name="DETAILS"/>
+پیام سرور: <ph name="SERVER_MSG"/></translation>
+<translation id="2475982808118771221">یک خطا روی داد</translation>
+<translation id="7229570126336867161">EVDO مورد نیاز است</translation>
+<translation id="2999742336789313416"><ph name="DISPLAY_NAME"/> یک جلسه عمومی مدیریت‌شده توسط <ph name="DOMAIN"/> است</translation>
+<translation id="7029814467594812963">خروج از جلسه</translation>
+<translation id="8454013096329229812">Wi-Fi روشن است.</translation>
+<translation id="4872237917498892622">Alt+جستجو یا Shift</translation>
+<translation id="2983818520079887040">تنظیمات...</translation>
+<translation id="1717216362413677834">حالت جایگاه اتصال</translation>
+<translation id="8927026611342028580">درخواست اتصال</translation>
+<translation id="8300849813060516376">OTASP انجام نشد</translation>
+<translation id="2792498699870441125">Alt+جستجو</translation>
+<translation id="8660803626959853127">در حال همگام‌سازی فایل(های) <ph name="COUNT"/></translation>
+<translation id="3709443003275901162">+۹</translation>
+<translation id="639644700271529076">CAPS LOCK خاموش است</translation>
+<translation id="6248847161401822652">برای خروج Control‏، Shift و Q را دو بار فشار دهید.</translation>
+<translation id="6267036997247669271"><ph name="NAME"/>: در حال فعال‌سازی…</translation>
+<translation id="1391854757121130358">ممکن است حجم مجاز داده تلفن همراه خود را مصرف کرده باشید.</translation>
+<translation id="5413208160176941586">کاربر مدیریت شده به صورت محلی</translation>
+<translation id="1059194134494239015"><ph name="DISPLAY_NAME"/>:‏ <ph name="RESOLUTION"/></translation>
+<translation id="4864165860509564259">موقعیت راه‌انداز</translation>
+<translation id="7593891976182323525">جستجو یا Shift</translation>
+<translation id="7649070708921625228">راهنما</translation>
+<translation id="3050422059534974565">CAPS LOCK روشن است.
+جستجو یا Shift را برای لغو فشار دهید.</translation>
+<translation id="397105322502079400">در حال محاسبه…</translation>
+<translation id="158849752021629804">شبکه خانگی مورد نیاز است</translation>
+<translation id="6857811139397017780">فعال سازی <ph name="NETWORKSERVICE"/></translation>
+<translation id="5864471791310927901">بررسی DHCP انجام نشد</translation>
+<translation id="5812035014844949013">خروجی</translation>
+<translation id="6692173217867674490">کلمه عبور نادرست</translation>
+<translation id="6165508094623778733">بیشتر بیاموزید</translation>
+<translation id="9046895021617826162">اتصال برقرار نشد</translation>
+<translation id="973896785707726617">این جلسه در <ph name="SESSION_TIME_REMAINING"/> به اتمام خواهد رسید. به طور خودکار از سیستم خارج خواهید شد.</translation>
+<translation id="8372369524088641025">کلید WEP نادرست</translation>
+<translation id="6636709850131805001">حالت ناشناس</translation>
+<translation id="3573179567135747900">به &quot;<ph name="FROM_LOCALE"/>&quot; تغییر دهید (به راه‌اندازی دوباره نیاز دارد)</translation>
+<translation id="8103386449138765447">پیامک‌ها: <ph name="MESSAGE_COUNT"/></translation>
+<translation id="5045002648206642691">تنظیمات Google Drive...</translation>
+<translation id="1510238584712386396">راه‌انداز</translation>
+<translation id="7209101170223508707">CAPS LOCK روشن است.
+Alt+جستجو یا Shift را برای لغو فشار دهید.</translation>
+<translation id="8940956008527784070">باتری ضعیف است (<ph name="PERCENTAGE"/>٪)</translation>
+<translation id="5102001756192215136"><ph name="HOUR"/>:<ph name="MINUTE"/> باقیمانده است</translation>
+<translation id="520760366042891468">اشتراک‌گذاری کنترل صفحه نمایش شما از طریق Hangouts.</translation>
+<translation id="8000066093800657092">بدون شبکه</translation>
+<translation id="4015692727874266537">ورود به سیستم با حسابی دیگر...</translation>
+<translation id="5941711191222866238">کوچک کردن</translation>
+<translation id="6911468394164995108">پیوستن به شبکه دیگر…</translation>
+<translation id="412065659894267608"><ph name="HOUR"/>ساعت <ph name="MINUTE"/>دقیقه مانده تا باتری شارژ شود</translation>
+<translation id="6359806961507272919">پیامک از <ph name="PHONE_NUMBER"/></translation>
+<translation id="1244147615850840081">شرکت مخابراتی</translation>
+</translationbundle> \ No newline at end of file
diff --git a/chromium/ash/strings/ash_strings_fi.xtb b/chromium/ash/strings/ash_strings_fi.xtb
new file mode 100644
index 00000000000..d9793f2b869
--- /dev/null
+++ b/chromium/ash/strings/ash_strings_fi.xtb
@@ -0,0 +1,199 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="fi">
+<translation id="3595596368722241419">Akku täynnä</translation>
+<translation id="5250713215130379958">Piilota käynnistyspalkki</translation>
+<translation id="7814236020522506259"><ph name="HOUR"/> <ph name="MINUTE"/></translation>
+<translation id="7880025619322806991">Portaalitila</translation>
+<translation id="30155388420722288">Overflow-painike</translation>
+<translation id="5571066253365925590">Bluetooth käytössä</translation>
+<translation id="9074739597929991885">Bluetooth</translation>
+<translation id="2268130516524549846">Bluetooth pois käytöstä</translation>
+<translation id="3775358506042162758">Sinulla voi olla enintään kolme tiliä useaan tiliin kirjautuessasi.</translation>
+<translation id="370649949373421643">Ota wifi käyttöön</translation>
+<translation id="3626281679859535460">Kirkkaus</translation>
+<translation id="8054466585765276473">Lasketaan akun kesto.</translation>
+<translation id="7982789257301363584">Verkko</translation>
+<translation id="5565793151875479467">Välityspalvelin...</translation>
+<translation id="938582441709398163">Näppäimistön peitto</translation>
+<translation id="4387004326333427325">Todennusvarmenne on hylätty etäyhteyden kautta</translation>
+<translation id="6979158407327259162">Google Drive</translation>
+<translation id="6943836128787782965">HTTP get -toiminto epäonnistui</translation>
+<translation id="2297568595583585744">Tila-alue</translation>
+<translation id="1661867754829461514">PIN-koodi puuttuu</translation>
+<translation id="4508225577814909926"><ph name="NAME"/>: Yhdistetään…</translation>
+<translation id="4237016987259239829">Verkon yhteysvirhe</translation>
+<translation id="2946640296642327832">Ota Bluetooth käyttöön</translation>
+<translation id="6459472438155181876">Ruutua laajennetaan: <ph name="DISPLAY_NAME"/></translation>
+<translation id="8206859287963243715">Matkapuhelin</translation>
+<translation id="6596816719288285829">IP-osoite</translation>
+<translation id="4508265954913339219">Aktivointi epäonnistui</translation>
+<translation id="3621712662352432595">Ääniasetukset</translation>
+<translation id="1812696562331527143">Syöttötapa on vaihtunut. Uusi syöttötapa on <ph name="INPUT_METHOD_ID"/>*(<ph name="BEGIN_LINK"/>kolmas osapuoli<ph name="END_LINK"/>).
+Vaihda syöttötapaa painamalla Shift + Alt.</translation>
+<translation id="2127372758936585790">Pienitehoinen laturi</translation>
+<translation id="3846575436967432996">Verkon tietoja ei saatavilla</translation>
+<translation id="3026237328237090306">Määritä mobiilitiedonsiirron asetukset</translation>
+<translation id="785750925697875037">Näytä mobiilitili</translation>
+<translation id="153454903766751181">Alustetaan matkapuhelinmodeemia…</translation>
+<translation id="4628814525959230255">Näytönhallinnan jakaminen henkilön <ph name="HELPER_NAME"/> kanssa Hangout-keskustelujen kautta.</translation>
+<translation id="8343941333792395995"><ph name="DISPLAY_NAME"/> on käännetty</translation>
+<translation id="7864539943188674973">Poista Bluetooth käytöstä</translation>
+<translation id="939252827960237676">Kuvakaappauksen tallentaminen epäonnistui</translation>
+<translation id="3126069444801937830">Päivitä käynnistämällä uudelleen</translation>
+<translation id="2268813581635650749">Kirjaa kaikki ulos</translation>
+<translation id="735745346212279324">VPN-yhteys katkaistu</translation>
+<translation id="7320906967354320621">Ei käytössä</translation>
+<translation id="6303423059719347535">Akussa on virtaa <ph name="PERCENTAGE"/> %</translation>
+<translation id="15373452373711364">Suuri hiiren osoitin</translation>
+<translation id="2778346081696727092">Todennus annetulla käyttäjänimellä ja salasanalla epäonnistui.</translation>
+<translation id="3294437725009624529">Vieras</translation>
+<translation id="8190698733819146287">Muokkaa kieliä ja syötettä...</translation>
+<translation id="2903907270192926896">ÄÄNITULO</translation>
+<translation id="8676770494376880701">Pienitehoinen laturi kytketty</translation>
+<translation id="7170041865419449892">Katvealueella</translation>
+<translation id="4804818685124855865">Katkaise yhteys</translation>
+<translation id="2544853746127077729">Verkko on hylännyt todennusvarmenteen</translation>
+<translation id="5222676887888702881">Kirjaudu ulos</translation>
+<translation id="2688477613306174402">Määritykset</translation>
+<translation id="1272079795634619415">Pysäytä</translation>
+<translation id="4957722034734105353">Lisätietoja...</translation>
+<translation id="2964193600955408481">Wifi pois käytöstä</translation>
+<translation id="811680302244032017">Lisää laite...</translation>
+<translation id="4279490309300973883">Peilaus päällä</translation>
+<translation id="2509468283778169019">CAPS LOCK on päällä</translation>
+<translation id="3892641579809465218">Sisäinen näyttö</translation>
+<translation id="7823564328645135659">Käyttökieli on muutettu kielestä <ph name="FROM_LOCALE"/> kieleksi <ph name="TO_LOCALE"/> asetustesi synkronoinnin yhteydessä.</translation>
+<translation id="3368922792935385530">Yhdistetty</translation>
+<translation id="8340999562596018839">Äänipalaute</translation>
+<translation id="8654520615680304441">Ota wifi käyttöön…</translation>
+<translation id="5825747213122829519">Syöttötapa on vaihtunut. Uusi syöttötapa on <ph name="INPUT_METHOD_ID"/>.
+Vaihda syöttötapaa painamalla Shift + Alt.</translation>
+<translation id="2562916301614567480">Yksityinen verkko</translation>
+<translation id="6549021752953852991">Matkapuhelinverkkoja ei ole käytettävissä</translation>
+<translation id="4379753398862151997">Arvoisa näyttö, suhteemme ei toimi. (Näyttöä ei tueta)</translation>
+<translation id="6426039856985689743">Poista mobiilitiedonsiirto käytöstä</translation>
+<translation id="3087734570205094154">Alaosa</translation>
+<translation id="3742055079367172538">Kuvakaappaus otettu</translation>
+<translation id="8878886163241303700">Laajennettu näyttö</translation>
+<translation id="5271016907025319479">VPN-verkon asetuksia ei ole määritetty.</translation>
+<translation id="372094107052732682">Lopeta painamalla kahdesti Ctrl+Shift+Q.</translation>
+<translation id="6803622936009808957">Näyttöjen peilaaminen ei onnistunut, sillä tuettua resoluutiota ei löytynyt. Sen sijaan valittiin työpöydän laajennus.</translation>
+<translation id="1480041086352807611">Esittelytila</translation>
+<translation id="3626637461649818317"><ph name="PERCENTAGE"/> % jäljellä</translation>
+<translation id="9089416786594320554">Syöttötavat</translation>
+<translation id="6247708409970142803"><ph name="PERCENTAGE"/> %</translation>
+<translation id="2614835198358683673">Chromebookisi ei välttämättä lataudu sen ollessa päällä. Harkitse virallisen laturin käyttämistä.</translation>
+<translation id="1895658205118569222">Sulkeminen</translation>
+<translation id="4430019312045809116">Äänenvoimakkuus</translation>
+<translation id="4442424173763614572">DNS-haku epäonnistui</translation>
+<translation id="6356500677799115505">Akku on täynnä ja laite on kytketty laturiin.</translation>
+<translation id="7874779702599364982">Haetaan matkapuhelinverkkoja…</translation>
+<translation id="583281660410589416">Tuntematon</translation>
+<translation id="1383876407941801731">Haku</translation>
+<translation id="7468789844759750875">Osta lisää tiedonsiirtoa palvelussa <ph name="NAME"/>.</translation>
+<translation id="3901991538546252627">Yhdistetään verkkoon <ph name="NAME"/></translation>
+<translation id="2204305834655267233">Verkon tiedot</translation>
+<translation id="1621499497873603021">Akku on tyhjä <ph name="TIME_LEFT"/> kuluttua</translation>
+<translation id="5980301590375426705">Sulje vierastila</translation>
+<translation id="4471417012762451363">Akussa on virtaa <ph name="PERCENTAGE"/> % ja laite on kytketty laturiin</translation>
+<translation id="8308637677604853869">Edellinen valikko</translation>
+<translation id="4666297444214622512">Toiseen tiliin kirjautuminen ei onnistunut.</translation>
+<translation id="1346748346194534595">Oikealle</translation>
+<translation id="1773212559869067373">Todennusvarmenne on hylätty paikallisesti</translation>
+<translation id="8528322925433439945">Mobiiliverkot...</translation>
+<translation id="7049357003967926684">Yhdistäminen</translation>
+<translation id="8428213095426709021">Asetukset</translation>
+<translation id="2372145515558759244">Synkronoidaan sovelluksia...</translation>
+<translation id="7256405249507348194">Tunnistamaton virhe: <ph name="DESC"/></translation>
+<translation id="7925247922861151263">AAA-testi epäonnistui</translation>
+<translation id="8456362689280298700"><ph name="HOUR"/>:<ph name="MINUTE"/> kunnes akku on ladattu</translation>
+<translation id="5787281376604286451">Äänipalaute on käytössä.
+Poista se käytöstä painamalla Ctrl+Alt+Z.</translation>
+<translation id="4479639480957787382">Ethernet</translation>
+<translation id="6312403991423642364">Tuntematon verkkovirhe</translation>
+<translation id="1467432559032391204">Vasemmalle</translation>
+<translation id="5543001071567407895">Tekstiviesti</translation>
+<translation id="2354174487190027830">Aktivoidaan <ph name="NAME"/></translation>
+<translation id="8814190375133053267">Wifi</translation>
+<translation id="1398853756734560583">Suurenna</translation>
+<translation id="2692809339924654275"><ph name="BLUETOOTH"/>: Yhdistetään…</translation>
+<translation id="252373100621549798">Tuntematon näyttö</translation>
+<translation id="1882897271359938046">Peilataan: <ph name="DISPLAY_NAME"/></translation>
+<translation id="2727977024730340865">Pienitehoinen laturi kytketty. Akku ei ehkä lataudu luotettavasti.</translation>
+<translation id="3784455785234192852">Lukitse</translation>
+<translation id="2805756323405976993">Sovellukset</translation>
+<translation id="8871072142849158571"><ph name="DISPLAY_NAME"/> on muutettu kokoon <ph name="RESOLUTION"/></translation>
+<translation id="1512064327686280138">Aktivointivirhe</translation>
+<translation id="5097002363526479830">Yhteyden muodostaminen verkkoon &quot;<ph name="NAME"/>&quot; epäonnistui: <ph name="DETAILS"/></translation>
+<translation id="1850504506766569011">Wifi ei ole käytössä.</translation>
+<translation id="8132793192354020517">Yhteys muodostettu verkkoon <ph name="NAME"/></translation>
+<translation id="7052914147756339792">Aseta taustakuva...</translation>
+<translation id="8678698760965522072">Online-tila</translation>
+<translation id="2532589005999780174">Suuri kontrasti -tila</translation>
+<translation id="1119447706177454957">Sisäinen virhe</translation>
+<translation id="3019353588588144572">Akku on ladattu <ph name="TIME_REMAINING"/> kuluttua</translation>
+<translation id="3473479545200714844">Ruudun suurentaminen</translation>
+<translation id="7005812687360380971">Virhe</translation>
+<translation id="882279321799040148">Näytä klikkaamalla</translation>
+<translation id="5045550434625856497">Väärä salasana</translation>
+<translation id="1602076796624386989">Ota mobiilitiedonsiirto käyttöön</translation>
+<translation id="6981982820502123353">Esteettömyys</translation>
+<translation id="3157931365184549694">Palauta</translation>
+<translation id="4274292172790327596">Tunnistamaton virhe</translation>
+<translation id="4032485810211612751"><ph name="HOURS"/>.<ph name="MINUTES"/>.<ph name="SECONDS"/></translation>
+<translation id="225680501294068881">Etsitään laitteita...</translation>
+<translation id="5597451508971090205"><ph name="SHORT_WEEKDAY"/>, <ph name="DATE"/></translation>
+<translation id="4448844063988177157">Haetaan wifi-verkkoja...</translation>
+<translation id="8401662262483418323">Yhteyden muodostaminen kohteeseen <ph name="NAME"/> ei onnistu: <ph name="DETAILS"/>
+Palvelimen viesti: <ph name="SERVER_MSG"/></translation>
+<translation id="2475982808118771221">Tapahtui virhe</translation>
+<translation id="7229570126336867161">EVDO tarvitaan</translation>
+<translation id="2999742336789313416"><ph name="DISPLAY_NAME"/> on julkinen käyttökerta, jota hallinnoi <ph name="DOMAIN"/></translation>
+<translation id="7029814467594812963">Sulje käyttökerta</translation>
+<translation id="8454013096329229812">Wifi on käytössä.</translation>
+<translation id="4872237917498892622">Alt + haku tai Shift</translation>
+<translation id="2983818520079887040">Asetukset...</translation>
+<translation id="1717216362413677834">Telakointitila</translation>
+<translation id="8927026611342028580">Yhdistä pyydetyt</translation>
+<translation id="8300849813060516376">OTASP epäonnistui</translation>
+<translation id="2792498699870441125">Alt + haku</translation>
+<translation id="8660803626959853127">Synkronoidaan <ph name="COUNT"/> tiedosto(a)</translation>
+<translation id="3709443003275901162">Yli 9</translation>
+<translation id="639644700271529076">CAPS LOCK on pois päältä</translation>
+<translation id="6248847161401822652">Lopeta painamalla kahdesti Control Shift Q.</translation>
+<translation id="6267036997247669271"><ph name="NAME"/>: aktivoidaan…</translation>
+<translation id="1391854757121130358">Olet ehkä käyttänyt mobiilitiedonsiirron kiintiösi.</translation>
+<translation id="5413208160176941586">Paikallisesti hallinnoitu käyttäjä</translation>
+<translation id="1059194134494239015"><ph name="DISPLAY_NAME"/>: <ph name="RESOLUTION"/></translation>
+<translation id="4864165860509564259">Käynnistyspalkin sijainti</translation>
+<translation id="7593891976182323525">Haku tai Shift</translation>
+<translation id="7649070708921625228">Ohje</translation>
+<translation id="3050422059534974565">CAPS LOCK on päällä. Peruuta painamalla haku- tai Shift-näppäintä.</translation>
+<translation id="397105322502079400">Lasketaan...</translation>
+<translation id="158849752021629804">Kotiverkko tarvitaan</translation>
+<translation id="6857811139397017780">Aktivoi <ph name="NETWORKSERVICE"/></translation>
+<translation id="5864471791310927901">DHCP-haku epäonnistui</translation>
+<translation id="5812035014844949013">ÄÄNENTOISTO</translation>
+<translation id="6692173217867674490">Väärä tunnuslause</translation>
+<translation id="6165508094623778733">Lisätietoja</translation>
+<translation id="9046895021617826162">Yhdistäminen epäonnistui</translation>
+<translation id="973896785707726617">Istunnon loppumiseen on <ph name="SESSION_TIME_REMAINING"/>. Sinut kirjataan automaattisesti ulos.</translation>
+<translation id="8372369524088641025">Väärä WEP-avain</translation>
+<translation id="6636709850131805001">Tunnistamaton tila</translation>
+<translation id="3573179567135747900">Vaihda takaisin kieleksi <ph name="FROM_LOCALE"/> (vaatii uudelleenkäynnistyksen)</translation>
+<translation id="8103386449138765447">Tekstiviestit: <ph name="MESSAGE_COUNT"/></translation>
+<translation id="5045002648206642691">Google Driven asetukset...</translation>
+<translation id="1510238584712386396">Käynnistysohjelma</translation>
+<translation id="7209101170223508707">CAPS LOCK on päällä. Peruuta painamalla Alt + hakupainike tai Shift.</translation>
+<translation id="8940956008527784070">Akku vähissä (<ph name="PERCENTAGE"/> %)</translation>
+<translation id="5102001756192215136"><ph name="HOUR"/>.<ph name="MINUTE"/> jäljellä</translation>
+<translation id="520760366042891468">Näytön hallinnan jakaminen Hangout-keskustelujen kautta.</translation>
+<translation id="8000066093800657092">Ei verkkoa</translation>
+<translation id="4015692727874266537">Kirjaudu sisään toiseen tiliin…</translation>
+<translation id="5941711191222866238">Pienennä</translation>
+<translation id="6911468394164995108">Liity muuhun verkkoon...</translation>
+<translation id="412065659894267608">Akku täynnä <ph name="HOUR"/> t <ph name="MINUTE"/> min kuluttua</translation>
+<translation id="6359806961507272919">Tekstiviesti lähettäjältä <ph name="PHONE_NUMBER"/></translation>
+<translation id="1244147615850840081">Operaattori</translation>
+</translationbundle> \ No newline at end of file
diff --git a/chromium/ash/strings/ash_strings_fil.xtb b/chromium/ash/strings/ash_strings_fil.xtb
new file mode 100644
index 00000000000..d3c61d5a6ff
--- /dev/null
+++ b/chromium/ash/strings/ash_strings_fil.xtb
@@ -0,0 +1,201 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="fil">
+<translation id="3595596368722241419">Puno na ang baterya</translation>
+<translation id="5250713215130379958">Awtomatikong itago ang launcher</translation>
+<translation id="7814236020522506259"><ph name="HOUR"/> at <ph name="MINUTE"/></translation>
+<translation id="7880025619322806991">Katayuan ng portal</translation>
+<translation id="30155388420722288">Button na Overflow</translation>
+<translation id="5571066253365925590">Pinapagana ang Bluetooth</translation>
+<translation id="9074739597929991885">Bluetooth</translation>
+<translation id="2268130516524549846">Hindi pinagana ang Bluetooth</translation>
+<translation id="3775358506042162758">Maaari ka lang magkaroon ng hanggang sa tatlong account sa multiple na pag-sign-in.</translation>
+<translation id="370649949373421643">Paganahin ang Wi-Fi</translation>
+<translation id="3626281679859535460">Tingkad</translation>
+<translation id="8054466585765276473">Kinakalkula ang oras ng baterya.</translation>
+<translation id="7982789257301363584">Network</translation>
+<translation id="5565793151875479467">Proxy...</translation>
+<translation id="938582441709398163">Overlay ng Keyboard</translation>
+<translation id="4387004326333427325">Remote na tinanggihan ang certificate sa pagpapatunay</translation>
+<translation id="6979158407327259162">Google Drive</translation>
+<translation id="6943836128787782965">Nabigo ang pagkuha ng HTTP</translation>
+<translation id="2297568595583585744">Tray ng katayuan</translation>
+<translation id="1661867754829461514">Nawawala ang PIN</translation>
+<translation id="4508225577814909926"><ph name="NAME"/>: Kumokonekta...</translation>
+<translation id="4237016987259239829">Error sa Koneksyon ng Network</translation>
+<translation id="2946640296642327832">Paganahin ang Bluetooth</translation>
+<translation id="6459472438155181876">Pinapalawak ang screen sa <ph name="DISPLAY_NAME"/></translation>
+<translation id="8206859287963243715">Cellular</translation>
+<translation id="6596816719288285829">IP Address</translation>
+<translation id="4508265954913339219">Nabigo ang pag-activate</translation>
+<translation id="3621712662352432595">Mga Setting ng Audio</translation>
+<translation id="1812696562331527143">Naging <ph name="INPUT_METHOD_ID"/>*(<ph name="BEGIN_LINK"/>3rd party<ph name="END_LINK"/>) ang iyong pamamaraan ng pag-input.
+Pindutin ang Shift + Alt upang magpalit.</translation>
+<translation id="2127372758936585790">Low-power charger</translation>
+<translation id="3846575436967432996">Walang available na impormasyon sa network</translation>
+<translation id="3026237328237090306">I-setup ang mobile data</translation>
+<translation id="785750925697875037">Tingnan ang account sa mobile</translation>
+<translation id="153454903766751181">Sinisimulan ang cellular na modem...</translation>
+<translation id="4628814525959230255">Ibinahagi ang kontrol sa iyong screen kay <ph name="HELPER_NAME"/> sa pamamagitan ng Hangouts.</translation>
+<translation id="8343941333792395995">Na-rotate na ang <ph name="DISPLAY_NAME"/></translation>
+<translation id="7864539943188674973">Huwag Paganahin ang Bluetooth</translation>
+<translation id="939252827960237676">Nabigong i-save ang screenshot</translation>
+<translation id="3126069444801937830">I-restart upang mag-update</translation>
+<translation id="2268813581635650749">I-sign out ang lahat</translation>
+<translation id="735745346212279324">Nakadiskonekta ang VPN</translation>
+<translation id="7320906967354320621">Hindi Ginagamit</translation>
+<translation id="6303423059719347535">Ang baterya ay <ph name="PERCENTAGE"/>% na puno</translation>
+<translation id="15373452373711364">Malaking mouse cursor</translation>
+<translation id="2778346081696727092">Nabigong patotohanan gamit ang ibinigay na username o password</translation>
+<translation id="3294437725009624529">Bisita</translation>
+<translation id="8190698733819146287">I-customize ang mga wika at input...</translation>
+<translation id="2903907270192926896">INPUT</translation>
+<translation id="8676770494376880701">Nakakabit ang low-power charger</translation>
+<translation id="7170041865419449892">Wala sa sakop</translation>
+<translation id="4804818685124855865">I-disconnect</translation>
+<translation id="2544853746127077729">Tinanggihan ng network ang certificate sa pagpapatunay</translation>
+<translation id="5222676887888702881">Mag-sign out</translation>
+<translation id="2688477613306174402">Configuration</translation>
+<translation id="1272079795634619415">Stop</translation>
+<translation id="4957722034734105353">Matuto nang higit pa...</translation>
+<translation id="2964193600955408481">Huwag paganahin ang Wi-Fi</translation>
+<translation id="811680302244032017">Magdagdag ng device...</translation>
+<translation id="4279490309300973883">Nagmi-mirror</translation>
+<translation id="2509468283778169019">Naka-on ang CAPS LOCK</translation>
+<translation id="3892641579809465218">Panloob na Display</translation>
+<translation id="7823564328645135659">Nagbago ang wika mula &quot;<ph name="FROM_LOCALE"/>&quot; patungong &quot;<ph name="TO_LOCALE"/>&quot; pagkatapos i-sync ang iyong mga setting.</translation>
+<translation id="3368922792935385530">Nakakonekta</translation>
+<translation id="8340999562596018839">Pasalitang feedback</translation>
+<translation id="8654520615680304441">I-on ang Wi-Fi...</translation>
+<translation id="5825747213122829519">Naging <ph name="INPUT_METHOD_ID"/> ang iyong pamamaraan ng pag-input.
+Pindutin ang Shift + Alt upang magpalit.</translation>
+<translation id="2562916301614567480">Pribadong Network</translation>
+<translation id="6549021752953852991">Walang available na cellular na network</translation>
+<translation id="4379753398862151997">Dear Monitor, it's not working out between us. (Hindi sinusuportahan ang monitor na iyan)</translation>
+<translation id="6426039856985689743">Huwag paganahin ang mobile data</translation>
+<translation id="3087734570205094154">Sa ilalim</translation>
+<translation id="3742055079367172538">Nakakuha na ng screenshot</translation>
+<translation id="8878886163241303700">Pinapalawak ang screen</translation>
+<translation id="5271016907025319479">Hindi naka-configure ang VPN.</translation>
+<translation id="372094107052732682">Pindutin ang Ctrl+Shift+Q nang dalawang beses upang lumabas.</translation>
+<translation id="6803622936009808957">Hindi ma-mirror ang mga display dahil walang mga sinusuportahang resolusyon na nakita. Pumasok na lang sa pinalawak na desktop.</translation>
+<translation id="1480041086352807611">Mode ng demo</translation>
+<translation id="3626637461649818317"><ph name="PERCENTAGE"/>% ang natitira</translation>
+<translation id="9089416786594320554">Mga pamamaraan ng pag-input</translation>
+<translation id="6247708409970142803"><ph name="PERCENTAGE"/>%</translation>
+<translation id="2614835198358683673">Maaaring hindi mag-charge ang iyong Chromebook habang naka-on ito. Pag-isipang gamitin ang opisyal na charger.</translation>
+<translation id="1895658205118569222">Shutdown</translation>
+<translation id="4430019312045809116">Volume</translation>
+<translation id="4442424173763614572">Nabigo ang paghahanap sa DNS</translation>
+<translation id="6356500677799115505">Ang baterya ay puno at nagcha-charge.</translation>
+<translation id="7874779702599364982">Naghahanap ng mga cellular network...</translation>
+<translation id="583281660410589416">Hindi kilala</translation>
+<translation id="1383876407941801731">Paghahanap</translation>
+<translation id="7468789844759750875">Bisitahin ang portal sa pag-activate ng <ph name="NAME"/> upang bumili ng higit pang data.</translation>
+<translation id="3901991538546252627">Kumokonekta sa <ph name="NAME"/></translation>
+<translation id="2204305834655267233">Impormasyon ng Network</translation>
+<translation id="1621499497873603021">Natitirang oras bago maubos ang baterya, <ph name="TIME_LEFT"/></translation>
+<translation id="5980301590375426705">Lumabas sa session ng bisita</translation>
+<translation id="4471417012762451363">Ang baterya ay <ph name="PERCENTAGE"/>% na puno at nagcha-charge</translation>
+<translation id="8308637677604853869">Nakaraang menu</translation>
+<translation id="4666297444214622512">Hindi makaka-sign in sa isa pang account.</translation>
+<translation id="1346748346194534595">Kanan</translation>
+<translation id="1773212559869067373">Lokal na tinanggihan ang certificate ng pagpapatunay</translation>
+<translation id="8528322925433439945">Mobile ...</translation>
+<translation id="7049357003967926684">Kaugnayan</translation>
+<translation id="8428213095426709021">Mga Setting</translation>
+<translation id="2372145515558759244">Nagsi-sync ng apps...</translation>
+<translation id="7256405249507348194">Hindi nakikilalang error: <ph name="DESC"/></translation>
+<translation id="7925247922861151263">Nabigo ang pagsusuri sa AAA</translation>
+<translation id="8456362689280298700"><ph name="HOUR"/>:<ph name="MINUTE"/> hanggang mapuno</translation>
+<translation id="5787281376604286451">Pinagana ang pasalitang feedback.
+Pindutin ang Ctrl+Alt+Z upang huwag paganahin.</translation>
+<translation id="4479639480957787382">Ethernet</translation>
+<translation id="6312403991423642364">Hindi alam na error sa network</translation>
+<translation id="1467432559032391204">Kaliwa</translation>
+<translation id="5543001071567407895">SMS</translation>
+<translation id="2354174487190027830">Ina-activate ang <ph name="NAME"/></translation>
+<translation id="8814190375133053267">Wi-Fi</translation>
+<translation id="1398853756734560583">Maximize</translation>
+<translation id="2692809339924654275"><ph name="BLUETOOTH"/>: Kumokonekta...</translation>
+<translation id="252373100621549798">Hindi Kilalang Display</translation>
+<translation id="1882897271359938046">Nagmi-mirror sa <ph name="DISPLAY_NAME"/></translation>
+<translation id="2727977024730340865">Naka-saksak sa isang low-power charger. Maaaring hindi maging tiyak ang pag-charge ng baterya.</translation>
+<translation id="3784455785234192852">I-lock</translation>
+<translation id="2805756323405976993">Apps</translation>
+<translation id="8871072142849158571">na-resize ang <ph name="DISPLAY_NAME"/> sa <ph name="RESOLUTION"/></translation>
+<translation id="1512064327686280138">Pagkabigo ng pag-activate</translation>
+<translation id="5097002363526479830">Nabigong kumonekta sa network na '<ph name="NAME"/>': <ph name="DETAILS"/></translation>
+<translation id="1850504506766569011">Naka-off ang Wi-Fi.</translation>
+<translation id="8132793192354020517">Kumukonekta sa <ph name="NAME"/></translation>
+<translation id="7052914147756339792">Magtakda ng wallpaper...</translation>
+<translation id="8678698760965522072">Katayuan online</translation>
+<translation id="2532589005999780174">High contrast mode</translation>
+<translation id="1119447706177454957">Panloob na error</translation>
+<translation id="3019353588588144572">Natitirang oras bago ganap na ma-charge ang baterya, <ph name="TIME_REMAINING"/></translation>
+<translation id="3473479545200714844">Screen magnifier</translation>
+<translation id="7005812687360380971">Bigo</translation>
+<translation id="882279321799040148">I-click upang tingnan</translation>
+<translation id="5045550434625856497">Hindi wastong password</translation>
+<translation id="1602076796624386989">Paganahin ang mobile data</translation>
+<translation id="6981982820502123353">Accessibility</translation>
+<translation id="3157931365184549694">Ipanumbalik</translation>
+<translation id="4274292172790327596">Di-kilalang error</translation>
+<translation id="4032485810211612751"><ph name="HOURS"/>:<ph name="MINUTES"/>:<ph name="SECONDS"/></translation>
+<translation id="225680501294068881">Nag-i-scan para sa mga device...</translation>
+<translation id="5597451508971090205"><ph name="SHORT_WEEKDAY"/>, <ph name="DATE"/></translation>
+<translation id="4448844063988177157">Naghahanap ng mga network na Wi-Fi...</translation>
+<translation id="8401662262483418323">Nabigong kumonekta sa '<ph name="NAME"/>': <ph name="DETAILS"/>
+Mensahe mula sa server: <ph name="SERVER_MSG"/></translation>
+<translation id="2475982808118771221">May isang naganap na error</translation>
+<translation id="7229570126336867161">Kailangan ng EVDO</translation>
+<translation id="2999742336789313416">Ang <ph name="DISPLAY_NAME"/> ay isang pampublikong session na pinamamahalaan ng <ph name="DOMAIN"/></translation>
+<translation id="7029814467594812963">Lumabas sa session</translation>
+<translation id="8454013096329229812">Naka-on ang Wi-Fi.</translation>
+<translation id="4872237917498892622">Alt+Search o Shift</translation>
+<translation id="2983818520079887040">Mga Setting...</translation>
+<translation id="1717216362413677834">Dock mode</translation>
+<translation id="8927026611342028580">Hiniling ang Koneksyon</translation>
+<translation id="8300849813060516376">Nabigo ang OTASP</translation>
+<translation id="2792498699870441125">Alt+Search</translation>
+<translation id="8660803626959853127">Nagsi-sync ng <ph name="COUNT"/> (na) file</translation>
+<translation id="3709443003275901162">9+</translation>
+<translation id="639644700271529076">Naka-off ang CAPS LOCK</translation>
+<translation id="6248847161401822652">Pindutin ang Control Shift Q nang dalawang beses upang lumabas.</translation>
+<translation id="6267036997247669271"><ph name="NAME"/>: Ina-activate...</translation>
+<translation id="1391854757121130358">Maaaring naubos mo na ang mobile data na nakalaan sa iyo.</translation>
+<translation id="5413208160176941586">Lokal na pinapamahalaang user</translation>
+<translation id="1059194134494239015"><ph name="DISPLAY_NAME"/>: <ph name="RESOLUTION"/></translation>
+<translation id="4864165860509564259">Posisyon ng launcher</translation>
+<translation id="7593891976182323525">Search or Shift</translation>
+<translation id="7649070708921625228">Tulong</translation>
+<translation id="3050422059534974565">Naka-on ang CAPS LOCK.
+Pindutin ang Search o Shift upang kanselahin.</translation>
+<translation id="397105322502079400">Kinakalkula...</translation>
+<translation id="158849752021629804">Kailangan ng home network</translation>
+<translation id="6857811139397017780">I-activate <ph name="NETWORKSERVICE"/></translation>
+<translation id="5864471791310927901">Nabigo ang paghanap ng DHCP</translation>
+<translation id="5812035014844949013">OUTPUT</translation>
+<translation id="6692173217867674490">Mahinang passphrase</translation>
+<translation id="6165508094623778733">Matuto nang higit pa</translation>
+<translation id="9046895021617826162">Nabigo ang pagkonekta</translation>
+<translation id="973896785707726617">Magtatapos ang session na ito sa <ph name="SESSION_TIME_REMAINING"/>. Awtomatiko kang masa-sign out.</translation>
+<translation id="8372369524088641025">Mahinang WEP key</translation>
+<translation id="6636709850131805001">Di-kilalang katayuan</translation>
+<translation id="3573179567135747900">Palitan pabalik sa &quot;<ph name="FROM_LOCALE"/>&quot; (kailangang i-restart)</translation>
+<translation id="8103386449138765447">Mga mensaheng SMS: <ph name="MESSAGE_COUNT"/></translation>
+<translation id="5045002648206642691">Mga setting ng Google Drive ...</translation>
+<translation id="1510238584712386396">Launcher</translation>
+<translation id="7209101170223508707">Naka-on ang CAPS LOCK.
+Pindutin ang Alt+Search o Shift upang kanselahin.</translation>
+<translation id="8940956008527784070">Mahina na ang baterya (<ph name="PERCENTAGE"/>%)</translation>
+<translation id="5102001756192215136"><ph name="HOUR"/>:<ph name="MINUTE"/> ang natitira</translation>
+<translation id="520760366042891468">Ibinahagi ang kontrol sa iyong screen sa pamamagitan ng Hangouts.</translation>
+<translation id="8000066093800657092">Walang network</translation>
+<translation id="4015692727874266537">Mag-sign sa isa pang account...</translation>
+<translation id="5941711191222866238">Minimize</translation>
+<translation id="6911468394164995108">Sumali sa iba...</translation>
+<translation id="412065659894267608"><ph name="HOUR"/>o <ph name="MINUTE"/>m hanggang mapuno</translation>
+<translation id="6359806961507272919">SMS mula kay <ph name="PHONE_NUMBER"/></translation>
+<translation id="1244147615850840081">Carrier</translation>
+</translationbundle> \ No newline at end of file
diff --git a/chromium/ash/strings/ash_strings_fr.xtb b/chromium/ash/strings/ash_strings_fr.xtb
new file mode 100644
index 00000000000..05c9f7ce73d
--- /dev/null
+++ b/chromium/ash/strings/ash_strings_fr.xtb
@@ -0,0 +1,201 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="fr">
+<translation id="3595596368722241419">Batterie pleine</translation>
+<translation id="5250713215130379958">Masquer automatiquement le lanceur d'applications</translation>
+<translation id="7814236020522506259"><ph name="HOUR"/> et <ph name="MINUTE"/></translation>
+<translation id="7880025619322806991">État du portail</translation>
+<translation id="30155388420722288">Bouton de dépassement de capacité</translation>
+<translation id="5571066253365925590">Bluetooth activé</translation>
+<translation id="9074739597929991885">Bluetooth</translation>
+<translation id="2268130516524549846">Bluetooth désactivé</translation>
+<translation id="3775358506042162758">Vous ne pouvez vous connecter qu'à trois comptes au maximum dans le cadre de la connexion multicompte.</translation>
+<translation id="370649949373421643">Activer le Wi-Fi</translation>
+<translation id="3626281679859535460">Luminosité</translation>
+<translation id="8054466585765276473">Calcul de l'autonomie de la batterie en cours…</translation>
+<translation id="7982789257301363584">Réseau</translation>
+<translation id="5565793151875479467">Proxy…</translation>
+<translation id="938582441709398163">Clavier en superposition</translation>
+<translation id="4387004326333427325">Certificat d'authentification rejeté à distance.</translation>
+<translation id="6979158407327259162">Google Drive</translation>
+<translation id="6943836128787782965">Échec de l'obtention HTTP.</translation>
+<translation id="2297568595583585744">Barre d'état</translation>
+<translation id="1661867754829461514">Code secret manquant</translation>
+<translation id="4508225577814909926"><ph name="NAME"/> : Connexion en cours…</translation>
+<translation id="4237016987259239829">Erreur de connexion réseau</translation>
+<translation id="2946640296642327832">Activer le Bluetooth</translation>
+<translation id="6459472438155181876">Extension de l'écran pour <ph name="DISPLAY_NAME"/></translation>
+<translation id="8206859287963243715">Mobile</translation>
+<translation id="6596816719288285829">Adresse IP</translation>
+<translation id="4508265954913339219">Échec de l'activation</translation>
+<translation id="3621712662352432595">Paramètres audio</translation>
+<translation id="1812696562331527143">Votre mode de saisie a été remplacé par <ph name="INPUT_METHOD_ID"/>* (<ph name="BEGIN_LINK"/>tiers<ph name="END_LINK"/>).
+Appuyez sur Maj + Alt pour en utiliser un autre.</translation>
+<translation id="2127372758936585790">Chargeur de faible puissance</translation>
+<translation id="3846575436967432996">Aucune information disponible concernant le réseau</translation>
+<translation id="3026237328237090306">Configurer les données mobiles</translation>
+<translation id="785750925697875037">Afficher le compte mobile</translation>
+<translation id="153454903766751181">Initialisation du modem cellulaire en cours…</translation>
+<translation id="4628814525959230255">Partage du contrôle de votre écran avec <ph name="HELPER_NAME"/> via Hangouts</translation>
+<translation id="8343941333792395995">Rotation de l'écran &quot;<ph name="DISPLAY_NAME"/>&quot;</translation>
+<translation id="7864539943188674973">Désactiver le Bluetooth</translation>
+<translation id="939252827960237676">Échec d'enregistrement de la capture d'écran.</translation>
+<translation id="3126069444801937830">Redémarrez pour mettre à jour</translation>
+<translation id="2268813581635650749">Déconnecter tous les utilisateurs</translation>
+<translation id="735745346212279324">VPN déconnecté</translation>
+<translation id="7320906967354320621">Inactif</translation>
+<translation id="6303423059719347535">La batterie est chargée à <ph name="PERCENTAGE"/> %.</translation>
+<translation id="15373452373711364">Grand curseur</translation>
+<translation id="2778346081696727092">Échec de l'authentification à l'aide du nom d'utilisateur ou du mot de passe indiqués.</translation>
+<translation id="3294437725009624529">Invité</translation>
+<translation id="8190698733819146287">Personnaliser les langues et la saisie...</translation>
+<translation id="2903907270192926896">ENTRÉE</translation>
+<translation id="8676770494376880701">Chargeur de faible puissance connecté</translation>
+<translation id="7170041865419449892">Hors de portée</translation>
+<translation id="4804818685124855865">Se déconnecter</translation>
+<translation id="2544853746127077729">Certificat d'authentification rejeté par le réseau.</translation>
+<translation id="5222676887888702881">Déconnexion</translation>
+<translation id="2688477613306174402">Configuration en cours</translation>
+<translation id="1272079795634619415">Arrêter</translation>
+<translation id="4957722034734105353">En savoir plus…</translation>
+<translation id="2964193600955408481">Désactiver le réseau Wi-Fi</translation>
+<translation id="811680302244032017">Ajouter un appareil…</translation>
+<translation id="4279490309300973883">Mise en miroir</translation>
+<translation id="2509468283778169019">Touche VERR MAJ activée</translation>
+<translation id="3892641579809465218">Affichage interne</translation>
+<translation id="7823564328645135659">La langue utilisée est passée de &quot;<ph name="FROM_LOCALE"/>&quot; à &quot;<ph name="TO_LOCALE"/>&quot; après la synchronisation de vos paramètres.</translation>
+<translation id="3368922792935385530">Connecté</translation>
+<translation id="8340999562596018839">Commentaires audio</translation>
+<translation id="8654520615680304441">Activer le Wi-Fi…</translation>
+<translation id="5825747213122829519">Votre mode de saisie a été remplacé par <ph name="INPUT_METHOD_ID"/>.
+Appuyez sur Maj + Alt pour en utiliser un autre.</translation>
+<translation id="2562916301614567480">Réseau privé</translation>
+<translation id="6549021752953852991">Aucun réseau mobile disponible</translation>
+<translation id="4379753398862151997">Moniteur non compatible.</translation>
+<translation id="6426039856985689743">Désactiver les données mobiles</translation>
+<translation id="3087734570205094154">Bas</translation>
+<translation id="3742055079367172538">Capture d'écran réalisée</translation>
+<translation id="8878886163241303700">Extension de l'écran</translation>
+<translation id="5271016907025319479">VPN non configuré</translation>
+<translation id="372094107052732682">Pour quitter, appuyez deux fois sur Ctrl+Maj+Q.</translation>
+<translation id="6803622936009808957">Impossible de dupliquer les écrans, car aucune résolution compatible n'a été détectée. Le bureau étendu a été activé à la place.</translation>
+<translation id="1480041086352807611">Mode démonstration</translation>
+<translation id="3626637461649818317"><ph name="PERCENTAGE"/> % restant(s)</translation>
+<translation id="9089416786594320554">Modes de saisie</translation>
+<translation id="6247708409970142803"><ph name="PERCENTAGE"/> %</translation>
+<translation id="2614835198358683673">Votre Chromebook risque de ne pas charger lorsqu'il est allumé. Utilisez plutôt le chargeur officiel.</translation>
+<translation id="1895658205118569222">Arrêt.</translation>
+<translation id="4430019312045809116">Volume</translation>
+<translation id="4442424173763614572">Échec de la résolution DNS.</translation>
+<translation id="6356500677799115505">La batterie est pleine et en charge.</translation>
+<translation id="7874779702599364982">Recherche de réseaux cellulaires en cours…</translation>
+<translation id="583281660410589416">Inconnu</translation>
+<translation id="1383876407941801731">Recherche</translation>
+<translation id="7468789844759750875">Pour acheter plus de données, consultez le portail d'activation <ph name="NAME"/>.</translation>
+<translation id="3901991538546252627">Connexion à <ph name="NAME"/> en cours…</translation>
+<translation id="2204305834655267233">Informations réseau</translation>
+<translation id="1621499497873603021">Temps restant avant que la batterie ne soit vide : <ph name="TIME_LEFT"/>.</translation>
+<translation id="5980301590375426705">Fermer la session Invité</translation>
+<translation id="4471417012762451363">La batterie est chargée à <ph name="PERCENTAGE"/> % et en charge.</translation>
+<translation id="8308637677604853869">Menu précédent</translation>
+<translation id="4666297444214622512">Impossible de se connecter à un autre compte.</translation>
+<translation id="1346748346194534595">Vers la droite</translation>
+<translation id="1773212559869067373">Certificat d'authentification rejeté en local.</translation>
+<translation id="8528322925433439945">Mobile…</translation>
+<translation id="7049357003967926684">Association</translation>
+<translation id="8428213095426709021">Paramètres</translation>
+<translation id="2372145515558759244">Synchronisation des applications…</translation>
+<translation id="7256405249507348194">Erreur non reconnue : <ph name="DESC"/>.</translation>
+<translation id="7925247922861151263">Échec de la vérification AAA</translation>
+<translation id="8456362689280298700"><ph name="HOUR"/>:<ph name="MINUTE"/> de chargement</translation>
+<translation id="5787281376604286451">Les commentaires audio sont activés.
+Appuyez sur les touches Ctrl+Alt+Z pour les désactiver.</translation>
+<translation id="4479639480957787382">Ethernet </translation>
+<translation id="6312403991423642364">Erreur de réseau inconnue.</translation>
+<translation id="1467432559032391204">Vers la gauche</translation>
+<translation id="5543001071567407895">SMS</translation>
+<translation id="2354174487190027830">Activation du réseau <ph name="NAME"/> en cours…</translation>
+<translation id="8814190375133053267">Wi-Fi </translation>
+<translation id="1398853756734560583">Agrandir</translation>
+<translation id="2692809339924654275"><ph name="BLUETOOTH"/> : Connexion en cours…</translation>
+<translation id="252373100621549798">Écran inconnu</translation>
+<translation id="1882897271359938046">Mise en miroir pour <ph name="DISPLAY_NAME"/></translation>
+<translation id="2727977024730340865">L'appareil est branché à un chargeur de faible puissance. Il se peut que la charge ne soit pas fiable.</translation>
+<translation id="3784455785234192852">Verrouiller</translation>
+<translation id="2805756323405976993">Applications</translation>
+<translation id="8871072142849158571">La résolution de l'écran &quot;<ph name="DISPLAY_NAME"/>&quot; est désormais la suivante : <ph name="RESOLUTION"/>.</translation>
+<translation id="1512064327686280138">Échec de l'activation</translation>
+<translation id="5097002363526479830">Échec de la connexion au réseau &quot;<ph name="NAME"/>&quot; : <ph name="DETAILS"/>.</translation>
+<translation id="1850504506766569011">Le Wi-Fi est désactivé.</translation>
+<translation id="8132793192354020517">Connecté à <ph name="NAME"/></translation>
+<translation id="7052914147756339792">Définir un fond d'écran…</translation>
+<translation id="8678698760965522072">En ligne</translation>
+<translation id="2532589005999780174">Mode Contraste élevé</translation>
+<translation id="1119447706177454957">Erreur interne.</translation>
+<translation id="3019353588588144572">Temps restant avant chargement complet de la batterie : <ph name="TIME_REMAINING"/>.</translation>
+<translation id="3473479545200714844">Loupe</translation>
+<translation id="7005812687360380971">Défaillance</translation>
+<translation id="882279321799040148">Cliquer ici pour afficher la capture d'écran</translation>
+<translation id="5045550434625856497">Mot de passe incorrect.</translation>
+<translation id="1602076796624386989">Activer les données mobiles</translation>
+<translation id="6981982820502123353">Accessibilité</translation>
+<translation id="3157931365184549694">Restaurer</translation>
+<translation id="4274292172790327596">Erreur non reconnue</translation>
+<translation id="4032485810211612751"><ph name="HOURS"/>:<ph name="MINUTES"/>:<ph name="SECONDS"/></translation>
+<translation id="225680501294068881">Recherche d'appareils en cours…</translation>
+<translation id="5597451508971090205"><ph name="SHORT_WEEKDAY"/> <ph name="DATE"/></translation>
+<translation id="4448844063988177157">Recherche de réseaux Wi-Fi...</translation>
+<translation id="8401662262483418323">Échec de la connexion à &quot;<ph name="NAME"/>&quot; : <ph name="DETAILS"/>.
+Message du serveur : <ph name="SERVER_MSG"/></translation>
+<translation id="2475982808118771221">Une erreur s'est produite.</translation>
+<translation id="7229570126336867161">Technologie EvDo requise</translation>
+<translation id="2999742336789313416"><ph name="DISPLAY_NAME"/> est une session publique gérée par <ph name="DOMAIN"/></translation>
+<translation id="7029814467594812963">Quitter la session</translation>
+<translation id="8454013096329229812">Le Wi-Fi est activé.</translation>
+<translation id="4872237917498892622">Alt + Recherche ou Maj</translation>
+<translation id="2983818520079887040">Paramètres...</translation>
+<translation id="1717216362413677834">Mode Ancrage</translation>
+<translation id="8927026611342028580">Connexion demandée</translation>
+<translation id="8300849813060516376">Échec de l'opération OTASP</translation>
+<translation id="2792498699870441125">Alt + Recherche</translation>
+<translation id="8660803626959853127">Synchronisation de <ph name="COUNT"/> fichier(s) en cours…</translation>
+<translation id="3709443003275901162">Plus de 9</translation>
+<translation id="639644700271529076">La touche de verrouillage des majuscules est désactivée.</translation>
+<translation id="6248847161401822652">Pour quitter, appuyez deux fois sur Ctrl+Maj+Q.</translation>
+<translation id="6267036997247669271"><ph name="NAME"/> : activation en cours…</translation>
+<translation id="1391854757121130358">Vous avez peut-être épuisé votre forfait de données mobiles.</translation>
+<translation id="5413208160176941586">Utilisateur géré localement</translation>
+<translation id="1059194134494239015"><ph name="DISPLAY_NAME"/> : <ph name="RESOLUTION"/></translation>
+<translation id="4864165860509564259">Position du lanceur</translation>
+<translation id="7593891976182323525">Recherche ou Maj</translation>
+<translation id="7649070708921625228">Aide</translation>
+<translation id="3050422059534974565">Le VERROUILLAGE DES MAJUSCULES est activé.
+Appuyez sur Search ou Maj pour le désactiver.</translation>
+<translation id="397105322502079400">Calcul en cours…</translation>
+<translation id="158849752021629804">Réseau domestique requis</translation>
+<translation id="6857811139397017780">Activer <ph name="NETWORKSERVICE"/></translation>
+<translation id="5864471791310927901">Échec de la vérification DHCP</translation>
+<translation id="5812035014844949013">SORTIE</translation>
+<translation id="6692173217867674490">Mot de passe multiterme erroné</translation>
+<translation id="6165508094623778733">En savoir plus</translation>
+<translation id="9046895021617826162">Échec de la connexion</translation>
+<translation id="973896785707726617">Cette session se terminera dans <ph name="SESSION_TIME_REMAINING"/>. Vous serez automatiquement déconnecté.</translation>
+<translation id="8372369524088641025">Clé WEP incorrecte</translation>
+<translation id="6636709850131805001">État non reconnu</translation>
+<translation id="3573179567135747900">Revenir à &quot;<ph name="FROM_LOCALE"/>&quot; (redémarrage requis)</translation>
+<translation id="8103386449138765447">Messages SMS : <ph name="MESSAGE_COUNT"/></translation>
+<translation id="5045002648206642691">Paramètres Google Drive…</translation>
+<translation id="1510238584712386396">Lanceur d'applications</translation>
+<translation id="7209101170223508707">Le VERROUILLAGE DES MAJUSCULES est activé.
+Appuyez sur Alt + Recherche ou Maj pour le désactiver.</translation>
+<translation id="8940956008527784070">Batterie faible (<ph name="PERCENTAGE"/> %)</translation>
+<translation id="5102001756192215136"><ph name="HOUR"/>:<ph name="MINUTE"/> d'autonomie</translation>
+<translation id="520760366042891468">Partage du contrôle de votre écran via Hangouts</translation>
+<translation id="8000066093800657092">Aucun réseau détecté</translation>
+<translation id="4015692727874266537">Connecter un autre compte…</translation>
+<translation id="5941711191222866238">Réduire</translation>
+<translation id="6911468394164995108">Autre réseau…</translation>
+<translation id="412065659894267608">Encore <ph name="HOUR"/> h <ph name="MINUTE"/> min de chargement</translation>
+<translation id="6359806961507272919">SMS de <ph name="PHONE_NUMBER"/></translation>
+<translation id="1244147615850840081">Opérateur</translation>
+</translationbundle> \ No newline at end of file
diff --git a/chromium/ash/strings/ash_strings_gu.xtb b/chromium/ash/strings/ash_strings_gu.xtb
new file mode 100644
index 00000000000..7e3858cabca
--- /dev/null
+++ b/chromium/ash/strings/ash_strings_gu.xtb
@@ -0,0 +1,200 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="gu">
+<translation id="3595596368722241419">બૅટરી પૂર્ણ ચાર્જ</translation>
+<translation id="5250713215130379958">સ્વતઃછુપાવો લૉન્ચર</translation>
+<translation id="7814236020522506259"><ph name="HOUR"/> અને <ph name="MINUTE"/></translation>
+<translation id="7880025619322806991">પોર્ટલ સ્ટેટ</translation>
+<translation id="30155388420722288">ઓવરફ્લો બટન</translation>
+<translation id="5571066253365925590">Bluetooth સક્ષમ છે</translation>
+<translation id="9074739597929991885">Bluetooth</translation>
+<translation id="2268130516524549846">Bluetooth અક્ષમ છે</translation>
+<translation id="3775358506042162758">તમે બહુવિધ સાઇન-ઇનમાં માત્ર ત્રણ એકાઉન્ટ્સ સુધી રાખી શકો છો.</translation>
+<translation id="370649949373421643">Wi-Fi સક્ષમ કરો</translation>
+<translation id="3626281679859535460">તેજ</translation>
+<translation id="8054466585765276473">બેટરી સમયની ગણના કરે છે.</translation>
+<translation id="7982789257301363584">નેટવર્ક</translation>
+<translation id="5565793151875479467">પ્રોક્સી...</translation>
+<translation id="938582441709398163">કીબોર્ડ ઓવરલે</translation>
+<translation id="4387004326333427325">પ્રમાણીકરણ પ્રમાણપત્ર રિમોટલી નકારવામાં આવ્યું છે</translation>
+<translation id="6979158407327259162">Google ડ્રાઇવ</translation>
+<translation id="6943836128787782965">HTTP નિષ્ફળ ગયું</translation>
+<translation id="2297568595583585744">સ્થિતિ ટ્રે</translation>
+<translation id="1661867754829461514">PIN ખૂટે છે</translation>
+<translation id="4508225577814909926"><ph name="NAME"/>: કનેક્ટ કરી રહ્યું છે...</translation>
+<translation id="4237016987259239829">નેટવર્ક કનેક્શન ભૂલ</translation>
+<translation id="2946640296642327832">Bluetooth સક્ષમ કરો</translation>
+<translation id="6459472438155181876">સ્ક્રીનને <ph name="DISPLAY_NAME"/> પર વિસ્તૃત કરી રહ્યાં છે</translation>
+<translation id="8206859287963243715">સેલ્યુલર</translation>
+<translation id="6596816719288285829">IP સરનામું</translation>
+<translation id="4508265954913339219">સક્રિયતા નિષ્ફળ</translation>
+<translation id="3621712662352432595">ઑડિઓ સેટિંગ્સ</translation>
+<translation id="1812696562331527143">તમારી ઇનપુટ પદ્ધતિ <ph name="INPUT_METHOD_ID"/>*(<ph name="BEGIN_LINK"/>3જા પક્ષ<ph name="END_LINK"/>)માં બદલાઇ ગયેલ છે.
+સ્વિચ કરવા માટે Shift + Alt દબાવો.</translation>
+<translation id="2127372758936585790">નિમ્ન-પાવર ચાર્જર</translation>
+<translation id="3846575436967432996">કોઈ નેટવર્ક માહિતી ઉપલબ્ધ નથી</translation>
+<translation id="3026237328237090306">મોબાઇલ ડેટા સેટ કરો</translation>
+<translation id="785750925697875037">મોબાઇલ એકાઉન્ટ જુઓ</translation>
+<translation id="153454903766751181">સેલ્યુલર મોડેમનો પ્રારંભ કરી રહ્યાં છે...</translation>
+<translation id="4628814525959230255">Hangouts દ્વારા <ph name="HELPER_NAME"/> સાથે તમારી સ્ક્રીનનું નિયંત્રણ શેર કરવું.</translation>
+<translation id="8343941333792395995"><ph name="DISPLAY_NAME"/> ને ફેરવવામાં આવ્યું છે</translation>
+<translation id="7864539943188674973">Bluetooth અક્ષમ કરો</translation>
+<translation id="939252827960237676">સ્ક્રીનશૉટ સાચવવામાં નિષ્ફળ રહ્યું</translation>
+<translation id="3126069444801937830">અપડેટ કરવા માટે પુનઃપ્રારંભ કરો</translation>
+<translation id="2268813581635650749">બધામાંથી સાઇન આઉટ કરો</translation>
+<translation id="735745346212279324">VPN ડિસ્કનેક્ટ કર્યું છે</translation>
+<translation id="7320906967354320621">નિષ્ક્રિય</translation>
+<translation id="6303423059719347535">બેટરી <ph name="PERCENTAGE"/> % પૂર્ણ છે</translation>
+<translation id="15373452373711364">મોટું માઉસ કર્સર</translation>
+<translation id="2778346081696727092">આપેલા વપરાશકર્તાનામ અથવા પાસવર્ડ સાથે અધિકૃત કરવામાં નિષ્ફળ રહ્યું</translation>
+<translation id="3294437725009624529">અતિથિ</translation>
+<translation id="8190698733819146287">ભાષાઓ અને ઇનપુટને કસ્ટમાઇઝ કરો...</translation>
+<translation id="2903907270192926896">ઇનપુટ</translation>
+<translation id="8676770494376880701">નિમ્ન-પાવર ચાર્જર કનેક્ટ કર્યું છે</translation>
+<translation id="7170041865419449892">પહોંચ બહાર</translation>
+<translation id="4804818685124855865">ડિસ્કનેક્ટ કરો</translation>
+<translation id="2544853746127077729">નેટવર્ક દ્વારા પ્રમાણીકરણ પ્રમાણપત્ર નકારવામાં આવ્યું</translation>
+<translation id="5222676887888702881">સાઇન આઉટ</translation>
+<translation id="2688477613306174402">કન્ફિગરેશન</translation>
+<translation id="1272079795634619415">રોકો</translation>
+<translation id="4957722034734105353">વધુ જાણો...</translation>
+<translation id="2964193600955408481">Wi-Fi ને અક્ષમ કરો</translation>
+<translation id="811680302244032017">ઉપકરણ ઉમેરો...</translation>
+<translation id="4279490309300973883">પ્રતિબિંબત થઈ રહ્યું છે</translation>
+<translation id="2509468283778169019">CAPS LOCK ચાલુ છે</translation>
+<translation id="3892641579809465218">આંતરિક પ્રદર્શન</translation>
+<translation id="7823564328645135659">તમારી સેટિંગ્સ સમન્વયિત કર્યા પછી ભાષા &quot;<ph name="FROM_LOCALE"/>&quot; થી &quot;<ph name="TO_LOCALE"/>&quot; માં બદલાઈ ગઈ છે.</translation>
+<translation id="3368922792935385530">કનેક્ટેડ</translation>
+<translation id="8340999562596018839">બોલાયેલ પ્રતિસાદ</translation>
+<translation id="8654520615680304441">Wi-Fi ચાલુ કરો...</translation>
+<translation id="5825747213122829519">તમારી ઇનપુટ પદ્ધતિ <ph name="INPUT_METHOD_ID"/> માં બદલાઇ ગયેલ છે.
+સ્વિચ કરવા માટે Shift + Alt દબાવો.</translation>
+<translation id="2562916301614567480">ખાનગી નેટવર્ક</translation>
+<translation id="6549021752953852991">કોઇ સેલ્યુલર નેટવર્ક ઉપલબ્ધ નથી</translation>
+<translation id="4379753398862151997">પ્રિય મોનિટર, તે અમારી વચ્ચે કાર્ય કરી રહ્યું નથી. (તે મોનિટર સમર્થિત નથી)</translation>
+<translation id="6426039856985689743">મોબાઇલ ડેટાને અક્ષમ કરો</translation>
+<translation id="3087734570205094154">તળિયું</translation>
+<translation id="3742055079367172538">સ્ક્રીનશૉટ લેવાયો</translation>
+<translation id="8878886163241303700">સ્ક્રીનને વિસ્તૃત કરી રહ્યું છે</translation>
+<translation id="5271016907025319479">VPN ગોઠવેલું નથી.</translation>
+<translation id="372094107052732682">છોડવા માટે બે વાર Ctrl+Shift+Q દબાવો.</translation>
+<translation id="6803622936009808957">કોઈ સમર્થિત રિઝોલ્યૂશન મળ્યું ન હોવાથી, પ્રદર્શનોને પ્રતિબિંબિત કરી શકાયા નથી. તેને બદલે વિસ્તૃત ડેસ્કટૉપ દાખલ કર્યું.</translation>
+<translation id="1480041086352807611">ડેમો મોડ</translation>
+<translation id="3626637461649818317"><ph name="PERCENTAGE"/>% બાકી</translation>
+<translation id="9089416786594320554">ઇનપુટ પદ્ધતિઓ</translation>
+<translation id="6247708409970142803"><ph name="PERCENTAGE"/>%</translation>
+<translation id="2614835198358683673">જ્યારે તમારી Chromebook ચાલુ હોય ત્યારે તેને ચાર્જ કરી શકાશે નહીં. અધિકૃત ચાર્જરનો ઉપયોગ કરવાનું વિચારો.</translation>
+<translation id="1895658205118569222">બંધ કરો</translation>
+<translation id="4430019312045809116">વૉલ્યૂમ</translation>
+<translation id="4442424173763614572">DNS લુકઅપ નિષ્ફળ ગયું</translation>
+<translation id="6356500677799115505">બેટરી સંપૂર્ણ છે અને ચાર્જ થઈ રહી છે.</translation>
+<translation id="7874779702599364982">સેલ્યુલર નેટવર્ક્સ માટે શોધી રહ્યું છે...</translation>
+<translation id="583281660410589416">અજ્ઞાત</translation>
+<translation id="1383876407941801731">શોધ</translation>
+<translation id="7468789844759750875">વધુ ડેટા ખરીદવા માટે <ph name="NAME"/> સક્રિયકરણ પોર્ટલની મુલાકાત લો.</translation>
+<translation id="3901991538546252627"><ph name="NAME"/> થી કનેક્ટ કરી રહ્યું છે</translation>
+<translation id="2204305834655267233">નેટવર્ક માહિતી</translation>
+<translation id="1621499497873603021">બેટરી ખાલી થવામાં બાકી સમય, <ph name="TIME_LEFT"/></translation>
+<translation id="5980301590375426705">અતિથિથી બહાર નીકળો</translation>
+<translation id="4471417012762451363">બેટરી <ph name="PERCENTAGE"/> % પૂર્ણ અને ચાર્જ થઈ રહી છે</translation>
+<translation id="8308637677604853869">પહેલાનું મેનૂ</translation>
+<translation id="4666297444214622512">બીજા એકાઉન્ટમાં સાઇન ઇન કરી શકતા નથી.</translation>
+<translation id="1346748346194534595">જમણે</translation>
+<translation id="1773212559869067373">પ્રમાણીકરણ પ્રમાણપત્રને સ્થાનિક રૂપે નકારવામાં આવ્યું છે</translation>
+<translation id="8528322925433439945">મોબાઇલ ...</translation>
+<translation id="7049357003967926684">સંસ્થા</translation>
+<translation id="8428213095426709021">સેટિંગ્સ</translation>
+<translation id="2372145515558759244">એપ્લિકેશન્સને સમન્વયિત કરી રહ્યું છે...</translation>
+<translation id="7256405249507348194">અજ્ઞાત ભૂલ: <ph name="DESC"/></translation>
+<translation id="7925247922861151263">AAA તપાસ નિષ્ફળ</translation>
+<translation id="8456362689280298700">પૂર્ણ થવામાં <ph name="HOUR"/>:<ph name="MINUTE"/> બાકી</translation>
+<translation id="5787281376604286451">બોલાયેલ પ્રતિસાદ સક્ષમ છે. અક્ષમ કરવા માટે Ctrl + Alt + Z દબાવો.</translation>
+<translation id="4479639480957787382">ઇથરનેટ</translation>
+<translation id="6312403991423642364">અજ્ઞાત નેટવર્ક ભૂલ</translation>
+<translation id="1467432559032391204">ડાબું</translation>
+<translation id="5543001071567407895">SMS</translation>
+<translation id="2354174487190027830"><ph name="NAME"/> ને સક્રિય કરી રહ્યું છે</translation>
+<translation id="8814190375133053267">Wi-Fi</translation>
+<translation id="1398853756734560583">મોટું કરો</translation>
+<translation id="2692809339924654275"><ph name="BLUETOOTH"/>: કનેક્ટ કરી રહ્યું છે...</translation>
+<translation id="252373100621549798">અજ્ઞાત પ્રદર્શન</translation>
+<translation id="1882897271359938046"><ph name="DISPLAY_NAME"/> પર પ્રતિબિંબિત થઈ રહ્યું છે</translation>
+<translation id="2727977024730340865">નિમ્ન-પાવર ચાર્જરમાં પ્લગ કરેલું છે. બૅટરી ચાર્જિંગ વિશ્વસનીય હશે નહીં.</translation>
+<translation id="3784455785234192852">લૉક</translation>
+<translation id="2805756323405976993">એપ્લિકેશન્સ</translation>
+<translation id="8871072142849158571"><ph name="DISPLAY_NAME"/> નું કદ <ph name="RESOLUTION"/> માં બદલવામાં આવ્યું છે</translation>
+<translation id="1512064327686280138">સક્રિયતા નિષ્ફળ</translation>
+<translation id="5097002363526479830">નેટવર્ક '<ph name="NAME"/>' થી કનેક્ટ કરવામાં નિષ્ફળ: <ph name="DETAILS"/></translation>
+<translation id="1850504506766569011">Wi-Fi બંધ છે.</translation>
+<translation id="8132793192354020517"><ph name="NAME"/> થી કનેક્ટેડ છે</translation>
+<translation id="7052914147756339792">વૉલપેપર સેટ કરો...</translation>
+<translation id="8678698760965522072">ઓનલાઇન સ્ટેટ</translation>
+<translation id="2532589005999780174">ઉચ્ચ કોન્ટ્રાસ્ટ મોડ</translation>
+<translation id="1119447706177454957">આંતરિક ભૂલ</translation>
+<translation id="3019353588588144572">બેટરી સંપૂર્ણપણે ચાર્જ થવામાં બાકી સમય, <ph name="TIME_REMAINING"/></translation>
+<translation id="3473479545200714844">સ્ક્રીન બૃહદદર્શક</translation>
+<translation id="7005812687360380971">નિષ્ફળતા</translation>
+<translation id="882279321799040148">જોવા માટે ક્લિક કરો</translation>
+<translation id="5045550434625856497">ખોટો પાસવર્ડ</translation>
+<translation id="1602076796624386989">મોબાઇલ ડેટા સક્ષમ કરો</translation>
+<translation id="6981982820502123353">ઍક્સેસિબિલિટી</translation>
+<translation id="3157931365184549694">પુનઃસ્થાપિત કરો</translation>
+<translation id="4274292172790327596">અપરિચિત ભૂલ</translation>
+<translation id="4032485810211612751"><ph name="HOURS"/> : <ph name="MINUTES"/> : <ph name="SECONDS"/></translation>
+<translation id="225680501294068881">ઉપકરણો માટે સ્કેન કરી રહ્યું છે...</translation>
+<translation id="5597451508971090205"><ph name="SHORT_WEEKDAY"/>, <ph name="DATE"/></translation>
+<translation id="4448844063988177157"> Wi-Fi નેટવર્ક્સ માટે શોધી રહ્યું છે... </translation>
+<translation id="8401662262483418323">'<ph name="NAME"/>' થી કનેક્ટ કરવામાં નિષ્ફળ થયું: <ph name="DETAILS"/>
+સર્વર સંદેશ: <ph name="SERVER_MSG"/></translation>
+<translation id="2475982808118771221">કોઈ ભૂલ આવી છે</translation>
+<translation id="7229570126336867161">EVDO ની જરૂર છે</translation>
+<translation id="2999742336789313416"><ph name="DISPLAY_NAME"/> એ <ph name="DOMAIN"/> દ્વારા સંચાલિત સાર્વજનિક સત્ર છે</translation>
+<translation id="7029814467594812963">સત્રમાંથી બહાર નીકળો</translation>
+<translation id="8454013096329229812">Wi-Fi ચાલુ છે.</translation>
+<translation id="4872237917498892622">Alt+Search અથવા Shift</translation>
+<translation id="2983818520079887040">સેટિંગ્સ...</translation>
+<translation id="1717216362413677834">ડૉક મોડ</translation>
+<translation id="8927026611342028580">કનેક્ટ કરવાની વિનંતી કરી છે</translation>
+<translation id="8300849813060516376">OTASP નિષ્ફળ</translation>
+<translation id="2792498699870441125">Alt+Search</translation>
+<translation id="8660803626959853127"><ph name="COUNT"/> ફાઇલને સમન્વયિત કરી રહ્યું છે</translation>
+<translation id="3709443003275901162">9+</translation>
+<translation id="639644700271529076">CAPS LOCK બંધ છે</translation>
+<translation id="6248847161401822652">છોડવા માટે બે વાર Control Shift Q દબાવો.</translation>
+<translation id="6267036997247669271"><ph name="NAME"/>: સક્રિય કરી રહ્યું છે...</translation>
+<translation id="1391854757121130358">તમે તમારા મોબાઇલ ડેટા ભથ્થાનો ઉપયોગ કરી લીધો હશે.</translation>
+<translation id="5413208160176941586">સ્થાનિક રીતે સંચાલિત વપરાશકર્તા</translation>
+<translation id="1059194134494239015"><ph name="DISPLAY_NAME"/>: <ph name="RESOLUTION"/></translation>
+<translation id="4864165860509564259">લૉન્ચર સ્થિતિ</translation>
+<translation id="7593891976182323525">Search અથવા Shift</translation>
+<translation id="7649070708921625228">સહાય</translation>
+<translation id="3050422059534974565">CAPS LOCK ચાલુ છે.
+રદ કરવા માટે Search અથવા Shift દબાવો.</translation>
+<translation id="397105322502079400">ગણના કરી રહ્યું છે...</translation>
+<translation id="158849752021629804">હોમ નેટવર્કની આવશ્યકતા છે</translation>
+<translation id="6857811139397017780">સક્રિય કરો <ph name="NETWORKSERVICE"/></translation>
+<translation id="5864471791310927901">DHCP લુકઅપ નિષ્ફળ</translation>
+<translation id="5812035014844949013">આઉટપુટ</translation>
+<translation id="6692173217867674490">ખરાબ પાસફ્રેઝ</translation>
+<translation id="6165508094623778733">વધુ જાણો</translation>
+<translation id="9046895021617826162">કનેક્ટ કરવું નિષ્ફળ</translation>
+<translation id="973896785707726617">આ સત્ર <ph name="SESSION_TIME_REMAINING"/> માં સમાપ્ત થશે. તમને આપમેળે સાઇન આઉટ કરવામાં આવશે.</translation>
+<translation id="8372369524088641025">ખરાબ WEP કી</translation>
+<translation id="6636709850131805001">અપરિચિત સ્થિતિ</translation>
+<translation id="3573179567135747900">&quot;<ph name="FROM_LOCALE"/>&quot; પર પાછાં જાઓ (પુનર્પ્રારંભની જરૂર છે)</translation>
+<translation id="8103386449138765447">SMS સંદેશા: <ph name="MESSAGE_COUNT"/></translation>
+<translation id="5045002648206642691">Google ડ્રાઇવ સેટિંગ્સ...</translation>
+<translation id="1510238584712386396">લૉન્ચર</translation>
+<translation id="7209101170223508707">CAPS LOCK ચાલુ છે.
+રદ કરવા માટે Alt+Search અથવા Shift દબાવો.</translation>
+<translation id="8940956008527784070">બૅટરી ઓછી (<ph name="PERCENTAGE"/>%)</translation>
+<translation id="5102001756192215136"><ph name="HOUR"/>:<ph name="MINUTE"/> બાકી</translation>
+<translation id="520760366042891468">Hangouts દ્વારા તમારી સ્ક્રીનનું નિયંત્રણ શેર કરી રહ્યું છે.</translation>
+<translation id="8000066093800657092">નેટવર્ક નથી</translation>
+<translation id="4015692727874266537">બીજા એકાઉન્ટમાં સાઇન ઇન કરો...</translation>
+<translation id="5941711191222866238">નાનું કરો</translation>
+<translation id="6911468394164995108">અન્યથી જોડાઓ...</translation>
+<translation id="412065659894267608"><ph name="HOUR"/>ક <ph name="MINUTE"/>મિ સુધીમાં પૂર્ણ</translation>
+<translation id="6359806961507272919"><ph name="PHONE_NUMBER"/> તરફથી SMS</translation>
+<translation id="1244147615850840081">વાહક</translation>
+</translationbundle> \ No newline at end of file
diff --git a/chromium/ash/strings/ash_strings_hi.xtb b/chromium/ash/strings/ash_strings_hi.xtb
new file mode 100644
index 00000000000..d12758fd96d
--- /dev/null
+++ b/chromium/ash/strings/ash_strings_hi.xtb
@@ -0,0 +1,201 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="hi">
+<translation id="3595596368722241419">बैटरी पूर्ण</translation>
+<translation id="5250713215130379958">लॉन्चर को स्वत: छिपाएं</translation>
+<translation id="7814236020522506259"><ph name="HOUR"/> और <ph name="MINUTE"/></translation>
+<translation id="7880025619322806991">पोर्टल स्थिति</translation>
+<translation id="30155388420722288">ओवरफ़्लो बटन</translation>
+<translation id="5571066253365925590">Bluetooth सक्षम किया गया</translation>
+<translation id="9074739597929991885">Bluetooth</translation>
+<translation id="2268130516524549846">Bluetooth अक्षम किया गया</translation>
+<translation id="3775358506042162758">एकाधिक साइन-इन में आप तीन तक खाते रख सकते हैं.</translation>
+<translation id="370649949373421643">Wi-Fi सक्षम करें</translation>
+<translation id="3626281679859535460">चमक</translation>
+<translation id="8054466585765276473">बैटरी समय की गणना की जा रही है.</translation>
+<translation id="7982789257301363584">नेटवर्क</translation>
+<translation id="5565793151875479467">प्रॉक्सी...</translation>
+<translation id="938582441709398163">कीबोर्ड ओवरले</translation>
+<translation id="4387004326333427325">प्रमाणीकरण प्रमाणपत्र को दूरस्थ रूप से अस्वीकार किया गया</translation>
+<translation id="6979158407327259162">Google डिस्क</translation>
+<translation id="6943836128787782965">HTTP विफल हुआ</translation>
+<translation id="2297568595583585744">स्थिति ट्रे</translation>
+<translation id="1661867754829461514">पिन गुम</translation>
+<translation id="4508225577814909926"><ph name="NAME"/>: कनेक्ट हो रहा है...</translation>
+<translation id="4237016987259239829">नेटवर्क कनेक्शन त्रुटि</translation>
+<translation id="2946640296642327832">Bluetooth सक्षम करें</translation>
+<translation id="6459472438155181876"><ph name="DISPLAY_NAME"/> पर स्क्रीन विस्तृत कर रहा है</translation>
+<translation id="8206859287963243715">सेलुलर</translation>
+<translation id="6596816719288285829">IP पता</translation>
+<translation id="4508265954913339219">सक्रियण विफल</translation>
+<translation id="3621712662352432595">ऑडियो सेटिंग</translation>
+<translation id="1812696562331527143">आपकी इनपुट विधि <ph name="INPUT_METHOD_ID"/>*(<ph name="BEGIN_LINK"/>तृतीय पक्ष<ph name="END_LINK"/>) में बदल गई है.
+स्विच करने के लिए Shift + Alt दबाएं.</translation>
+<translation id="2127372758936585790">कम-शक्ति वाला चार्जर</translation>
+<translation id="3846575436967432996">कोई नेटवर्क जानकारी उपलब्ध नहीं</translation>
+<translation id="3026237328237090306">मोबाइल डेटा सेट करें</translation>
+<translation id="785750925697875037">मोबाइल खाते देखें</translation>
+<translation id="153454903766751181">सेल्युलर मॉडम प्रारंभ हो रहा है...</translation>
+<translation id="4628814525959230255">Hangout के माध्यम से अपनी स्क्रीन <ph name="HELPER_NAME"/> के साथ साझा करना.</translation>
+<translation id="8343941333792395995"><ph name="DISPLAY_NAME"/> को घुमा दिया गया है</translation>
+<translation id="7864539943188674973">Bluetooth अक्षम करें</translation>
+<translation id="939252827960237676">स्क्रीनशॉट सहेजने में विफल</translation>
+<translation id="3126069444801937830">अपडेट करने के लिए पुनरारंभ करें</translation>
+<translation id="2268813581635650749">सभी साइन आउट करें</translation>
+<translation id="735745346212279324">VPN डिस्कनेक्ट है</translation>
+<translation id="7320906967354320621">निष्क्रिय</translation>
+<translation id="6303423059719347535">बैटरी <ph name="PERCENTAGE"/>% भर गई है</translation>
+<translation id="15373452373711364">बड़ा माउस कर्सर</translation>
+<translation id="2778346081696727092">प्रदान किए गए उपयोगकर्तानाम या पासवर्ड से प्रमाणीकृत करने में विफ़ल रहा</translation>
+<translation id="3294437725009624529">अतिथि</translation>
+<translation id="8190698733819146287">भाषाएं और इनपुट कस्टमाइज़ करें...</translation>
+<translation id="2903907270192926896">इनपुट</translation>
+<translation id="8676770494376880701">कम-शक्ति वाला चार्जर</translation>
+<translation id="7170041865419449892">सीमा से बाहर</translation>
+<translation id="4804818685124855865">डिस्कनेक्ट करें</translation>
+<translation id="2544853746127077729">नेटवर्क द्वारा प्रमाणीकरण प्रमाणपत्र अस्वीकार किया गया</translation>
+<translation id="5222676887888702881">साइन आउट करें</translation>
+<translation id="2688477613306174402">कॉन्फ़िगरेशन</translation>
+<translation id="1272079795634619415">रोकें</translation>
+<translation id="4957722034734105353">और जानें...</translation>
+<translation id="2964193600955408481">Wi-Fi अक्षम करें</translation>
+<translation id="811680302244032017">डिवाइस जोड़ें...</translation>
+<translation id="4279490309300973883">मिरर करना</translation>
+<translation id="2509468283778169019">CAPS LOCK चालू है</translation>
+<translation id="3892641579809465218">आंतरिक डिस्प्ले</translation>
+<translation id="7823564328645135659">आपकी सेटिंग समन्वयित करने के बाद भाषा को &quot;<ph name="FROM_LOCALE"/>&quot; से &quot;<ph name="TO_LOCALE"/>&quot; में बदल दिया गया है.</translation>
+<translation id="3368922792935385530">कनेक्टेड</translation>
+<translation id="8340999562596018839">बोला जाने वाला फ़ीडबैक</translation>
+<translation id="8654520615680304441">Wi-Fi चालू करें...</translation>
+<translation id="5825747213122829519">आपकी इनपुट विधि <ph name="INPUT_METHOD_ID"/> में बदल गई है.
+स्विच करने के लिए Shift + Alt दबाएं.</translation>
+<translation id="2562916301614567480">निजी नेटवर्क</translation>
+<translation id="6549021752953852991">कोई सेल्युलर नेटवर्क उपलब्ध नहीं</translation>
+<translation id="4379753398862151997">Dear Monitor, it's not working out between us. (वह मॉनीटर समर्थित नहीं है)</translation>
+<translation id="6426039856985689743">मोबाइल डेटा अक्षम करें</translation>
+<translation id="3087734570205094154">नीचे</translation>
+<translation id="3742055079367172538">स्क्रीनशॉट लिया गया</translation>
+<translation id="8878886163241303700">स्क्रीन का विस्तार करना</translation>
+<translation id="5271016907025319479">VPN कॉन्फ़िगर नहीं किया गया है.</translation>
+<translation id="372094107052732682">छोड़ने के लिए Ctrl+Shift+Q दो बार दबाएं.</translation>
+<translation id="6803622936009808957">प्रदर्शनों को मिरर नहीं किया जा सका क्योंकि कोई समर्थित रिज़ॉल्यूशन नहीं मिला. इसके बजाय विस्तारित डेस्कटॉप में चला गया है.</translation>
+<translation id="1480041086352807611">डेमो मोड</translation>
+<translation id="3626637461649818317"><ph name="PERCENTAGE"/>% शेष है</translation>
+<translation id="9089416786594320554">इनपुट पद्धतियां</translation>
+<translation id="6247708409970142803"><ph name="PERCENTAGE"/>%</translation>
+<translation id="2614835198358683673">हो सकता है चालू होने पर आपका Chromebook चार्ज न हो. आधिकारिक चार्जर उपयोग करें.</translation>
+<translation id="1895658205118569222">बंद करें</translation>
+<translation id="4430019312045809116">मात्रा</translation>
+<translation id="4442424173763614572">DNS लुकअप विफल</translation>
+<translation id="6356500677799115505">बैटरी भर गई है और चार्ज हो रही है.</translation>
+<translation id="7874779702599364982">सेलुलर नेटवर्क खोज रहा है...</translation>
+<translation id="583281660410589416">अज्ञात</translation>
+<translation id="1383876407941801731">खोज</translation>
+<translation id="7468789844759750875">अधिक डेटा खरीदने के लिए <ph name="NAME"/> सक्रियण पोर्टल पर जाएं.</translation>
+<translation id="3901991538546252627"><ph name="NAME"/> से कनेक्‍ट हो रहा है</translation>
+<translation id="2204305834655267233">नेटवर्क जानकारी</translation>
+<translation id="1621499497873603021">बैटरी के खाली होने में शेष समय, <ph name="TIME_LEFT"/></translation>
+<translation id="5980301590375426705">अतिथि सत्र से बाहर निकलें</translation>
+<translation id="4471417012762451363">बैटरी <ph name="PERCENTAGE"/>% भर गई है और चार्ज हो रही है</translation>
+<translation id="8308637677604853869">पिछला मेनू</translation>
+<translation id="4666297444214622512">अन्य खाते में साइन इन नहीं कर सकते.</translation>
+<translation id="1346748346194534595">दाएं</translation>
+<translation id="1773212559869067373">प्रमाणीकरण प्रमाणपत्र को स्थानीय रूप से अस्वीकार कर दिया गया</translation>
+<translation id="8528322925433439945">मोबाइल ...</translation>
+<translation id="7049357003967926684">संबद्धता</translation>
+<translation id="8428213095426709021">सेटिंग</translation>
+<translation id="2372145515558759244">एप्लिकेशन समन्वयित किए जा रहे हैं...</translation>
+<translation id="7256405249507348194">अपरिचित त्रुटि: <ph name="DESC"/></translation>
+<translation id="7925247922861151263">AAA जांच विफल</translation>
+<translation id="8456362689280298700">पूरा होने में <ph name="HOUR"/>:<ph name="MINUTE"/> शेष</translation>
+<translation id="5787281376604286451">बोला जाने वाला फ़ीडबैक सक्षम है.
+अक्षम करने के लिए Ctrl+Alt+Z दबाएं.</translation>
+<translation id="4479639480957787382">इथरनेट</translation>
+<translation id="6312403991423642364">अज्ञात नेटवर्क त्रुटि</translation>
+<translation id="1467432559032391204">बाएं</translation>
+<translation id="5543001071567407895">SMS</translation>
+<translation id="2354174487190027830"><ph name="NAME"/> सक्रिय हो रहा है</translation>
+<translation id="8814190375133053267">Wi-Fi</translation>
+<translation id="1398853756734560583">बड़ा करें</translation>
+<translation id="2692809339924654275"><ph name="BLUETOOTH"/>: कनेक्ट हो रहा है...</translation>
+<translation id="252373100621549798">अज्ञात डिस्प्ले</translation>
+<translation id="1882897271359938046"><ph name="DISPLAY_NAME"/> पर मिरर कर रहा है</translation>
+<translation id="2727977024730340865">कम-शक्ति वाले चार्जर में प्लग इन करें. बैटरी चार्ज करना संभवत: विश्वसनीय नहीं होगा.</translation>
+<translation id="3784455785234192852">लॉक करें</translation>
+<translation id="2805756323405976993">एप्लिकेशन</translation>
+<translation id="8871072142849158571"><ph name="DISPLAY_NAME"/> का आकार बदलकर <ph name="RESOLUTION"/> कर दिया गया है</translation>
+<translation id="1512064327686280138">सक्रियण विफलता</translation>
+<translation id="5097002363526479830">नेटवर्क से कनेक्ट करने में विफल '<ph name="NAME"/>': <ph name="DETAILS"/></translation>
+<translation id="1850504506766569011">Wi-Fi बंद है.</translation>
+<translation id="8132793192354020517"><ph name="NAME"/> से कनेक्ट है</translation>
+<translation id="7052914147756339792">वॉलपेपर सेट करें...</translation>
+<translation id="8678698760965522072">ऑनलाइन स्थिति</translation>
+<translation id="2532589005999780174">उच्च कंट्रास्ट मोड</translation>
+<translation id="1119447706177454957">आंतरिक त्रुटि</translation>
+<translation id="3019353588588144572">बैटरी के पूरी तरह से चार्ज होने में शेष समय, <ph name="TIME_REMAINING"/></translation>
+<translation id="3473479545200714844">स्क्रीन आवर्द्धक</translation>
+<translation id="7005812687360380971">विफलता</translation>
+<translation id="882279321799040148">देखने के लिए क्लिक करें</translation>
+<translation id="5045550434625856497">गलत पासवर्ड</translation>
+<translation id="1602076796624386989">मोबाइल डेटा सक्षम करें</translation>
+<translation id="6981982820502123353">पहुंच क्षमता</translation>
+<translation id="3157931365184549694">पुनर्स्थापित करें</translation>
+<translation id="4274292172790327596">अपरिचित त्रुटि</translation>
+<translation id="4032485810211612751"><ph name="HOURS"/>:<ph name="MINUTES"/>:<ph name="SECONDS"/></translation>
+<translation id="225680501294068881">उपकरण स्कैन किए जा रहे हैं...</translation>
+<translation id="5597451508971090205"><ph name="SHORT_WEEKDAY"/>, <ph name="DATE"/></translation>
+<translation id="4448844063988177157">Wi-Fi नेटवर्क खोज रहा है...</translation>
+<translation id="8401662262483418323">'<ph name="NAME"/>' से कनेक्ट करने में असफल: <ph name="DETAILS"/>
+सर्वर संदेश: <ph name="SERVER_MSG"/></translation>
+<translation id="2475982808118771221">कोई त्रुटि आई</translation>
+<translation id="7229570126336867161">EVDO की आवश्यकता है</translation>
+<translation id="2999742336789313416"><ph name="DISPLAY_NAME"/>, <ph name="DOMAIN"/> के द्वारा प्रबंधित एक सार्वजनिक सत्र है</translation>
+<translation id="7029814467594812963">सत्र से बाहर निकलें</translation>
+<translation id="8454013096329229812">Wi-Fi चालू है.</translation>
+<translation id="4872237917498892622">Alt+Search या Shift</translation>
+<translation id="2983818520079887040">सेटिंग...</translation>
+<translation id="1717216362413677834">डॉक मोड</translation>
+<translation id="8927026611342028580">कनेक्ट करने का अनुरोध किया गया</translation>
+<translation id="8300849813060516376">OTASP विफल</translation>
+<translation id="2792498699870441125">Alt+Search</translation>
+<translation id="8660803626959853127"><ph name="COUNT"/> फ़ाइल/फ़ाइलें समन्वयित हो रही है/हो रही हैं</translation>
+<translation id="3709443003275901162">9 से अधिक</translation>
+<translation id="639644700271529076">CAPS LOCK बंद है</translation>
+<translation id="6248847161401822652">छोड़ने के लिए Control Shift Q दो बार दबाएं.</translation>
+<translation id="6267036997247669271"><ph name="NAME"/>: सक्रिय हो रहा है...</translation>
+<translation id="1391854757121130358">संभवत: आपने अपने मोबाइल डेटा सीमा का पूर्ण उपयोग कर लिया है.</translation>
+<translation id="5413208160176941586">स्थानीय रूप से प्रबंधित उपयोगकर्ता</translation>
+<translation id="1059194134494239015"><ph name="DISPLAY_NAME"/>: <ph name="RESOLUTION"/></translation>
+<translation id="4864165860509564259">लॉन्चर स्थिति</translation>
+<translation id="7593891976182323525">Search या Shift</translation>
+<translation id="7649070708921625228">सहायता</translation>
+<translation id="3050422059534974565">CAPS LOCK चालू है.
+रद्द करने के लिए Search या Shift दबाएं.</translation>
+<translation id="397105322502079400">गणना की जा रही है...</translation>
+<translation id="158849752021629804">होम नेटवर्क की आवश्यकता है</translation>
+<translation id="6857811139397017780"><ph name="NETWORKSERVICE"/> को सक्रिय करें</translation>
+<translation id="5864471791310927901">DHCP लुकअप विफल</translation>
+<translation id="5812035014844949013">आउटपुट</translation>
+<translation id="6692173217867674490">ख़राब पासफ़्रेज़</translation>
+<translation id="6165508094623778733">अधिक जानें</translation>
+<translation id="9046895021617826162">कनेक्ट करना विफल</translation>
+<translation id="973896785707726617">यह सत्र <ph name="SESSION_TIME_REMAINING"/> में समाप्त हो जाएगा. आपको स्वचालित रूप से साइन आउट कर दिया जाएगा.</translation>
+<translation id="8372369524088641025">ख़राब WEP कुंजी</translation>
+<translation id="6636709850131805001">अपरिचित अवस्था</translation>
+<translation id="3573179567135747900">&quot;<ph name="FROM_LOCALE"/>&quot; में पुन: बदलें (पुनः आरंभ करने की आवश्यकता है)</translation>
+<translation id="8103386449138765447">SMS संदेश: <ph name="MESSAGE_COUNT"/></translation>
+<translation id="5045002648206642691">Google डिस्क सेटिंग...</translation>
+<translation id="1510238584712386396">लॉन्चर</translation>
+<translation id="7209101170223508707">CAPS LOCK चालू है.
+रद्द करने के लिए Alt+Search या Shift दबाएं.</translation>
+<translation id="8940956008527784070">बैटरी कम (<ph name="PERCENTAGE"/>%)</translation>
+<translation id="5102001756192215136"><ph name="HOUR"/>:<ph name="MINUTE"/> शेष</translation>
+<translation id="520760366042891468">Hangout के माध्यम से अपनी स्क्रीन साझा करना.</translation>
+<translation id="8000066093800657092">नेटवर्क नहीं है</translation>
+<translation id="4015692727874266537">अन्य खाते में साइन इन करें...</translation>
+<translation id="5941711191222866238">छोटा करें</translation>
+<translation id="6911468394164995108">अन्य में शामिल हों...</translation>
+<translation id="412065659894267608">पूरी तरह से चार्ज होने में <ph name="HOUR"/>घं <ph name="MINUTE"/>मि शेष</translation>
+<translation id="6359806961507272919"><ph name="PHONE_NUMBER"/> से SMS</translation>
+<translation id="1244147615850840081">कैरियर</translation>
+</translationbundle> \ No newline at end of file
diff --git a/chromium/ash/strings/ash_strings_hr.xtb b/chromium/ash/strings/ash_strings_hr.xtb
new file mode 100644
index 00000000000..b16f82def36
--- /dev/null
+++ b/chromium/ash/strings/ash_strings_hr.xtb
@@ -0,0 +1,201 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="hr">
+<translation id="3595596368722241419">Baterija je puna</translation>
+<translation id="5250713215130379958">Automatski sakrij pokretač</translation>
+<translation id="7814236020522506259"><ph name="HOUR"/> i <ph name="MINUTE"/></translation>
+<translation id="7880025619322806991">Stanje mreže: Portal</translation>
+<translation id="30155388420722288">Gumb padajućeg izbornika</translation>
+<translation id="5571066253365925590">Bluetooth omogućen</translation>
+<translation id="9074739597929991885">Bluetooth</translation>
+<translation id="2268130516524549846">Bluetooth onemogućen</translation>
+<translation id="3775358506042162758">Možete imati najviše tri računa u višestrukoj prijavi.</translation>
+<translation id="370649949373421643">Omogući Wi-Fi</translation>
+<translation id="3626281679859535460">Svjetlina</translation>
+<translation id="8054466585765276473">Izračun vremena baterije.</translation>
+<translation id="7982789257301363584">Mreža</translation>
+<translation id="5565793151875479467">Proxy...</translation>
+<translation id="938582441709398163">Preklapanje tipkovnice</translation>
+<translation id="4387004326333427325">Certifikat za autentifikaciju odbijen je daljinski</translation>
+<translation id="6979158407327259162">Google disk</translation>
+<translation id="6943836128787782965">HTTP GET neuspješan</translation>
+<translation id="2297568595583585744">Ladica statusa</translation>
+<translation id="1661867754829461514">Nedostaje PIN</translation>
+<translation id="4508225577814909926"><ph name="NAME"/>: povezivanje...</translation>
+<translation id="4237016987259239829">Pogreška mrežne veze</translation>
+<translation id="2946640296642327832">Omogući Bluetooth</translation>
+<translation id="6459472438155181876">Proširivanje zaslona na zaslon <ph name="DISPLAY_NAME"/></translation>
+<translation id="8206859287963243715">Mobitel</translation>
+<translation id="6596816719288285829">IP adresa</translation>
+<translation id="4508265954913339219">Aktivacija nije uspjela</translation>
+<translation id="3621712662352432595">Postavke zvuka</translation>
+<translation id="1812696562331527143">Vaš je način unosa promijenjen u <ph name="INPUT_METHOD_ID"/>*(<ph name="BEGIN_LINK"/>treća strana<ph name="END_LINK"/>).
+Pritisnite Shift + Alt za promjenu.</translation>
+<translation id="2127372758936585790">Punjač male snage</translation>
+<translation id="3846575436967432996">Informacije o mreži nisu dostupne</translation>
+<translation id="3026237328237090306">Postavi mobilne podatke</translation>
+<translation id="785750925697875037">Prikaz mobilnog računa</translation>
+<translation id="153454903766751181">Inicijaliziranje modema mobilne mreže...</translation>
+<translation id="4628814525959230255">Dijelite kontrolu nad zaslonom s korisnikom <ph name="HELPER_NAME"/> putem značajke Hangouts.</translation>
+<translation id="8343941333792395995">Prikaz <ph name="DISPLAY_NAME"/> zakrenut je</translation>
+<translation id="7864539943188674973">Onemogući Bluetooth</translation>
+<translation id="939252827960237676">Snimka zaslona nije spremljena</translation>
+<translation id="3126069444801937830">Ponovo pokrenite za ažuriranje</translation>
+<translation id="2268813581635650749">Odjavi sve</translation>
+<translation id="735745346212279324">Veza s VPN-om prekinuta</translation>
+<translation id="7320906967354320621">U mirovanju</translation>
+<translation id="6303423059719347535">Baterija je <ph name="PERCENTAGE"/>% puna</translation>
+<translation id="15373452373711364">Veliki pokazivač miša</translation>
+<translation id="2778346081696727092">Autentifikacija s priloženim korisničkim imenom i zaporkom nije uspjela</translation>
+<translation id="3294437725009624529">Gost</translation>
+<translation id="8190698733819146287">Prilagodi jezike i unos...</translation>
+<translation id="2903907270192926896">ULAZ</translation>
+<translation id="8676770494376880701">Priključen je punjač male snage</translation>
+<translation id="7170041865419449892">Izvan raspona</translation>
+<translation id="4804818685124855865">Prekini vezu</translation>
+<translation id="2544853746127077729">Mreža je odbila certifikat za autentifikaciju</translation>
+<translation id="5222676887888702881">Odjava</translation>
+<translation id="2688477613306174402">Konfiguracija</translation>
+<translation id="1272079795634619415">Zaustavi</translation>
+<translation id="4957722034734105353">Saznajte više...</translation>
+<translation id="2964193600955408481">Onemogući Wi-Fi</translation>
+<translation id="811680302244032017">Dodajte uređaj...</translation>
+<translation id="4279490309300973883">Zrcaljenje</translation>
+<translation id="2509468283778169019">Opcija CAPS LOCK uključena</translation>
+<translation id="3892641579809465218">Unutarnji zaslon</translation>
+<translation id="7823564328645135659">Jezik je promijenjen iz: &quot;<ph name="FROM_LOCALE"/>&quot; u: &quot;<ph name="TO_LOCALE"/>&quot; nakon sinkronizacije vaših postavki.</translation>
+<translation id="3368922792935385530">Spojeno</translation>
+<translation id="8340999562596018839">Govorne povratne informacije</translation>
+<translation id="8654520615680304441">Uključite Wi-Fi...</translation>
+<translation id="5825747213122829519">Vaš je način unosa promijenjen u <ph name="INPUT_METHOD_ID"/>.
+Pritisnite Shift + Alt za promjenu.</translation>
+<translation id="2562916301614567480">Privatna mreža</translation>
+<translation id="6549021752953852991">Mobilne mreže nisu dostupne</translation>
+<translation id="4379753398862151997">Dragi monitoru, naša veza neće uspjeti. (Taj monitor nije podržan)</translation>
+<translation id="6426039856985689743">Onemogući mobilne podatke</translation>
+<translation id="3087734570205094154">Donji</translation>
+<translation id="3742055079367172538">Izrađena je snimka zaslona</translation>
+<translation id="8878886163241303700">Produljenje zaslona</translation>
+<translation id="5271016907025319479">VPN nije konfiguriran.</translation>
+<translation id="372094107052732682">Pritisnite Ctrl + Shift + Q dvaput da biste odustali.</translation>
+<translation id="6803622936009808957">Nije bilo moguće zrcaliti zaslone jer nije pronađena nijedna podržana razlučivost. Umjesto toga proširena je radna površina.</translation>
+<translation id="1480041086352807611">Demonstracijski način</translation>
+<translation id="3626637461649818317">Preostalo <ph name="PERCENTAGE"/> %</translation>
+<translation id="9089416786594320554">Načini unosa</translation>
+<translation id="6247708409970142803"><ph name="PERCENTAGE"/>%</translation>
+<translation id="2614835198358683673">Chromebook se možda neće puniti dok je uključen. Savjetujemo upotrebu službenog punjača.</translation>
+<translation id="1895658205118569222">Isključivanje</translation>
+<translation id="4430019312045809116">Glasnoća</translation>
+<translation id="4442424173763614572">Nije uspjelo pretraživanje DNS poslužitelja</translation>
+<translation id="6356500677799115505">Baterija je puna i puni se.</translation>
+<translation id="7874779702599364982">Traženje mobilnih mreža...</translation>
+<translation id="583281660410589416">Nepoznato</translation>
+<translation id="1383876407941801731">Pretraživanje</translation>
+<translation id="7468789844759750875">Posjetite aktivacijski portal <ph name="NAME"/> da biste kupili više podataka.</translation>
+<translation id="3901991538546252627">Povezivanje s mrežom <ph name="NAME"/></translation>
+<translation id="2204305834655267233">Podaci o mreži</translation>
+<translation id="1621499497873603021">Baterija će se isprazniti za <ph name="TIME_LEFT"/></translation>
+<translation id="5980301590375426705">Izlazak iz sesije gosta</translation>
+<translation id="4471417012762451363">Baterija je <ph name="PERCENTAGE"/>% puna i puni se</translation>
+<translation id="8308637677604853869">Prethodni izbornik</translation>
+<translation id="4666297444214622512">Prijava na još jedan račun nije moguća.</translation>
+<translation id="1346748346194534595">Udesno</translation>
+<translation id="1773212559869067373">Certifikat za autentifikaciju odbijen je lokalno</translation>
+<translation id="8528322925433439945">Mobilne mreže...</translation>
+<translation id="7049357003967926684">Udruživanje</translation>
+<translation id="8428213095426709021">Postavke</translation>
+<translation id="2372145515558759244">Sinkroniziranje aplikacija...</translation>
+<translation id="7256405249507348194">Neprepoznata pogreška: <ph name="DESC"/></translation>
+<translation id="7925247922861151263">Provjera AAA nije uspjela</translation>
+<translation id="8456362689280298700"><ph name="HOUR"/>:<ph name="MINUTE"/> do potpune napunjenosti</translation>
+<translation id="5787281376604286451">Omogućene su govorne povratne informacije.
+Pritisnite Ctrl + Alt + Z da biste ih onemogućili.</translation>
+<translation id="4479639480957787382">Eternet</translation>
+<translation id="6312403991423642364">Nepoznata mrežna pogreška</translation>
+<translation id="1467432559032391204">Ulijevo</translation>
+<translation id="5543001071567407895">SMS</translation>
+<translation id="2354174487190027830">Aktiviranje mreže <ph name="NAME"/></translation>
+<translation id="8814190375133053267">Wi-Fi</translation>
+<translation id="1398853756734560583">Maksimiziraj</translation>
+<translation id="2692809339924654275"><ph name="BLUETOOTH"/>: povezivanje...</translation>
+<translation id="252373100621549798">Nepoznati zaslon</translation>
+<translation id="1882897271359938046">Zrcaljenje na zaslon <ph name="DISPLAY_NAME"/></translation>
+<translation id="2727977024730340865">Uređaj je priključen na punjač male snage. Punjenje baterije možda nije pouzdano.</translation>
+<translation id="3784455785234192852">Zaključaj</translation>
+<translation id="2805756323405976993">Apps</translation>
+<translation id="8871072142849158571">Veličina prikaza <ph name="DISPLAY_NAME"/> promijenjena je na <ph name="RESOLUTION"/></translation>
+<translation id="1512064327686280138">Neuspjela aktivacija</translation>
+<translation id="5097002363526479830">Neuspješno povezivanje s mrežom &quot;<ph name="NAME"/>&quot;: <ph name="DETAILS"/></translation>
+<translation id="1850504506766569011">Wi-Fi je isključen.</translation>
+<translation id="8132793192354020517">Povezano s <ph name="NAME"/></translation>
+<translation id="7052914147756339792">Postavljanje pozadinske slike...</translation>
+<translation id="8678698760965522072">Stanje na mreži</translation>
+<translation id="2532589005999780174">Način visokog kontrasta</translation>
+<translation id="1119447706177454957">Interna pogreška</translation>
+<translation id="3019353588588144572">Baterija će se napuniti za <ph name="TIME_REMAINING"/></translation>
+<translation id="3473479545200714844">Povećalo</translation>
+<translation id="7005812687360380971">Neuspjeh</translation>
+<translation id="882279321799040148">Kliknite za prikaz</translation>
+<translation id="5045550434625856497">Netočna zaporka</translation>
+<translation id="1602076796624386989">Omogući mobilne podatke</translation>
+<translation id="6981982820502123353">Dostupnost</translation>
+<translation id="3157931365184549694">Vrati</translation>
+<translation id="4274292172790327596">Neprepoznata pogreška</translation>
+<translation id="4032485810211612751"><ph name="HOURS"/>:<ph name="MINUTES"/>:<ph name="SECONDS"/></translation>
+<translation id="225680501294068881">Pretraživanje uređaja...</translation>
+<translation id="5597451508971090205"><ph name="SHORT_WEEKDAY"/>, <ph name="DATE"/></translation>
+<translation id="4448844063988177157">Traženje Wi-Fi mreža...</translation>
+<translation id="8401662262483418323">Povezivanje s mrežom &quot;<ph name="NAME"/>&quot; nije uspjelo: <ph name="DETAILS"/>
+Poruka poslužitelja: <ph name="SERVER_MSG"/></translation>
+<translation id="2475982808118771221">Došlo je do pogreške</translation>
+<translation id="7229570126336867161">Potreban je EVDO</translation>
+<translation id="2999742336789313416"><ph name="DISPLAY_NAME"/> predstavlja javnu sesiju kojom upravlja domena <ph name="DOMAIN"/></translation>
+<translation id="7029814467594812963">Izlazak iz sesije</translation>
+<translation id="8454013096329229812">Wi-Fi je uključen.</translation>
+<translation id="4872237917498892622">Alt + Pretraživanje ili Shift</translation>
+<translation id="2983818520079887040">Postavke...</translation>
+<translation id="1717216362413677834">Način rada na priključnoj stanici</translation>
+<translation id="8927026611342028580">Podnesen je zahtjev za povezivanje</translation>
+<translation id="8300849813060516376">OTASP nije uspio</translation>
+<translation id="2792498699870441125">Alt + Pretraživanje</translation>
+<translation id="8660803626959853127">Sinkroniziranje datoteka (<ph name="COUNT"/>)</translation>
+<translation id="3709443003275901162">više od 9</translation>
+<translation id="639644700271529076">Tipka CAPS LOCK isključena</translation>
+<translation id="6248847161401822652">Pritisnite tipke Control, Shift i Q dvaput da biste odustali.</translation>
+<translation id="6267036997247669271"><ph name="NAME"/>: aktiviranje...</translation>
+<translation id="1391854757121130358">Možda ste potrošili dopuštenu količinu mobilnih podataka.</translation>
+<translation id="5413208160176941586">Lokalno upravljani korisnik</translation>
+<translation id="1059194134494239015"><ph name="DISPLAY_NAME"/>: <ph name="RESOLUTION"/></translation>
+<translation id="4864165860509564259">Položaj pokretača</translation>
+<translation id="7593891976182323525">Pretraživanje ili Shift</translation>
+<translation id="7649070708921625228">Pomoć</translation>
+<translation id="3050422059534974565">Uključena je opcija CAPS LOCK.
+Pritisnite tipke Pretraživanje ili Shift da biste ju isključili.</translation>
+<translation id="397105322502079400">Izračun u tijeku…</translation>
+<translation id="158849752021629804">Potrebna je matična mreža</translation>
+<translation id="6857811139397017780">Aktiviraj <ph name="NETWORKSERVICE"/></translation>
+<translation id="5864471791310927901">DHCP pretraživanje nije uspjelo</translation>
+<translation id="5812035014844949013">IZLAZ</translation>
+<translation id="6692173217867674490">Pogrešna zaporka</translation>
+<translation id="6165508094623778733">Saznajte više</translation>
+<translation id="9046895021617826162">Neuspjelo povezivanje</translation>
+<translation id="973896785707726617">Sesija će završiti za <ph name="SESSION_TIME_REMAINING"/>. Bit ćete automatski odjavljeni.</translation>
+<translation id="8372369524088641025">Neispravan WEP ključ</translation>
+<translation id="6636709850131805001">Neprepoznato stanje</translation>
+<translation id="3573179567135747900">Vratite na &quot;<ph name="FROM_LOCALE"/>&quot; (zahtijeva ponovno pokretanje)</translation>
+<translation id="8103386449138765447">SMS poruke: <ph name="MESSAGE_COUNT"/></translation>
+<translation id="5045002648206642691">Postavke Google diska...</translation>
+<translation id="1510238584712386396">Pokretač</translation>
+<translation id="7209101170223508707">Uključena je opcija CAPS LOCK.
+Pritisnite tipke Alt + Pretraživanje ili Shift da biste ju isključili.</translation>
+<translation id="8940956008527784070">Baterija je skoro prazna (<ph name="PERCENTAGE"/>%)</translation>
+<translation id="5102001756192215136">Preostalo <ph name="HOUR"/>:<ph name="MINUTE"/></translation>
+<translation id="520760366042891468">Dijelite kontrolu nad zaslonom putem značajke Hangouts.</translation>
+<translation id="8000066093800657092">Nema mreže</translation>
+<translation id="4015692727874266537">Prijavite se na još jedan račun...</translation>
+<translation id="5941711191222866238">Minimiziraj</translation>
+<translation id="6911468394164995108">Pridruži se drugoj...</translation>
+<translation id="412065659894267608"><ph name="HOUR"/> h <ph name="MINUTE"/> min do završetka punjenja</translation>
+<translation id="6359806961507272919">SMS šalje <ph name="PHONE_NUMBER"/></translation>
+<translation id="1244147615850840081">Davatelj usluge</translation>
+</translationbundle> \ No newline at end of file
diff --git a/chromium/ash/strings/ash_strings_hu.xtb b/chromium/ash/strings/ash_strings_hu.xtb
new file mode 100644
index 00000000000..560b64d7f45
--- /dev/null
+++ b/chromium/ash/strings/ash_strings_hu.xtb
@@ -0,0 +1,201 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="hu">
+<translation id="3595596368722241419">Akkumulátor feltöltve</translation>
+<translation id="5250713215130379958">Indító automatikus elrejtése</translation>
+<translation id="7814236020522506259"><ph name="HOUR"/> és <ph name="MINUTE"/></translation>
+<translation id="7880025619322806991">Portál állapota</translation>
+<translation id="30155388420722288">Túlcsordulás gomb</translation>
+<translation id="5571066253365925590">Bluetooth engedélyezve</translation>
+<translation id="9074739597929991885">Bluetooth</translation>
+<translation id="2268130516524549846">Bluetooth letiltva</translation>
+<translation id="3775358506042162758">Legfeljebb három fiókot használhat a többfiókos bejelentkezés során.</translation>
+<translation id="370649949373421643">Wi-Fi engedélyezése</translation>
+<translation id="3626281679859535460">Fényerő</translation>
+<translation id="8054466585765276473">Akkumulátor-időtartam kiszámítása.</translation>
+<translation id="7982789257301363584">Hálózat</translation>
+<translation id="5565793151875479467">Proxy...</translation>
+<translation id="938582441709398163">Billentyűzetkiosztás</translation>
+<translation id="4387004326333427325">A hitelesítési tanúsítvány távolról elutasítva</translation>
+<translation id="6979158407327259162">Google Drive</translation>
+<translation id="6943836128787782965">A HTTP-lekérés nem sikerült</translation>
+<translation id="2297568595583585744">Állapottálca</translation>
+<translation id="1661867754829461514">Hiányzó PIN kód</translation>
+<translation id="4508225577814909926"><ph name="NAME"/>: csatlakozás...</translation>
+<translation id="4237016987259239829">Hálózati kapcsolat hibája</translation>
+<translation id="2946640296642327832">Bluetooth engedélyezése</translation>
+<translation id="6459472438155181876">Képernyő kiterjesztése erre: <ph name="DISPLAY_NAME"/></translation>
+<translation id="8206859287963243715">Mobil</translation>
+<translation id="6596816719288285829">IP-cím</translation>
+<translation id="4508265954913339219">Aktiválás sikertelen</translation>
+<translation id="3621712662352432595">Hangbeállítások</translation>
+<translation id="1812696562331527143">A beviteli mód a következőre változott: <ph name="INPUT_METHOD_ID"/>*(<ph name="BEGIN_LINK"/>harmadik fél<ph name="END_LINK"/>).
+A váltáshoz nyomja meg a Shift + Alt billentyűkódot.</translation>
+<translation id="2127372758936585790">Kis teljesítményű töltő</translation>
+<translation id="3846575436967432996">Nem áll rendelkezésre hálózati információ</translation>
+<translation id="3026237328237090306">Mobiladatok beállítása</translation>
+<translation id="785750925697875037">Mobil fiók megtekintése</translation>
+<translation id="153454903766751181">Mobilmodem inicializálása...</translation>
+<translation id="4628814525959230255"><ph name="HELPER_NAME"/> segéddel való képernyőmegosztás vezérlése a Hangoutson keresztül.</translation>
+<translation id="8343941333792395995"><ph name="DISPLAY_NAME"/> elforgatva</translation>
+<translation id="7864539943188674973">Bluetooth letiltása</translation>
+<translation id="939252827960237676">Nem sikerült menteni a képernyőképet.</translation>
+<translation id="3126069444801937830">Indítsa újra a frissítéshez</translation>
+<translation id="2268813581635650749">Összes kijelentkeztetése</translation>
+<translation id="735745346212279324">A VPN nincs csatlakoztatva</translation>
+<translation id="7320906967354320621">Tétlen</translation>
+<translation id="6303423059719347535">Az akkumulátor töltöttsége: <ph name="PERCENTAGE"/>%</translation>
+<translation id="15373452373711364">Nagy egérmutató</translation>
+<translation id="2778346081696727092">A hitelesítés nem sikerült a megadott felhasználónévvel vagy jelszóval</translation>
+<translation id="3294437725009624529">Vendég</translation>
+<translation id="8190698733819146287">Nyelvek és beviteli módok személyre szabása...</translation>
+<translation id="2903907270192926896">BEMENET</translation>
+<translation id="8676770494376880701">Kis teljesítményű töltő csatlakoztatva</translation>
+<translation id="7170041865419449892">Tartományon kívül</translation>
+<translation id="4804818685124855865">Kapcsolat bontása</translation>
+<translation id="2544853746127077729">A hálózat elutasította a hitelesítési tanúsítványt</translation>
+<translation id="5222676887888702881">Kijelentkezés</translation>
+<translation id="2688477613306174402">Konfiguráció</translation>
+<translation id="1272079795634619415">Leállítás</translation>
+<translation id="4957722034734105353">További információ...</translation>
+<translation id="2964193600955408481">Wi-Fi letiltása</translation>
+<translation id="811680302244032017">Eszköz hozzáadása...</translation>
+<translation id="4279490309300973883">Tükrözés</translation>
+<translation id="2509468283778169019">A CAPS LOCK be van kapcsolva</translation>
+<translation id="3892641579809465218">Belső kijelző</translation>
+<translation id="7823564328645135659">A beállítások szinkronizálását követően &quot;<ph name="FROM_LOCALE"/>&quot; nyelvről &quot;<ph name="TO_LOCALE"/>&quot; nyelvre változott a nyelvi beállítás.</translation>
+<translation id="3368922792935385530">Kapcsolódva</translation>
+<translation id="8340999562596018839">Hangos visszajelzés</translation>
+<translation id="8654520615680304441">Wi-Fi bekapcsolása...</translation>
+<translation id="5825747213122829519">A beviteli mód a következőre változott: <ph name="INPUT_METHOD_ID"/>.
+A váltáshoz nyomja meg a Shift + Alt billentyűkódot.</translation>
+<translation id="2562916301614567480">Magánhálózat</translation>
+<translation id="6549021752953852991">Nem érhető el mobilhálózat</translation>
+<translation id="4379753398862151997">Kedves Monitor, mi nem illünk össze. (A monitor nem támogatott.)</translation>
+<translation id="6426039856985689743">Mobiladatok letiltása</translation>
+<translation id="3087734570205094154">Alja</translation>
+<translation id="3742055079367172538">Képernyőkép elkészítve</translation>
+<translation id="8878886163241303700">Kibővített képernyő</translation>
+<translation id="5271016907025319479">Nincs konfigurálva VPN.</translation>
+<translation id="372094107052732682">A kilépéshez nyomja meg kétszer a Ctrl+Shift+Q billentyűkódot.</translation>
+<translation id="6803622936009808957">A kijelzők tükrözése sikertelen, mivel nem található támogatott felbontás. Ehelyett kiterjesztett asztal módba váltott a rendszer.</translation>
+<translation id="1480041086352807611">Demó üzemmód</translation>
+<translation id="3626637461649818317"><ph name="PERCENTAGE"/>% maradt</translation>
+<translation id="9089416786594320554">Beviteli módszerek</translation>
+<translation id="6247708409970142803"><ph name="PERCENTAGE"/>%</translation>
+<translation id="2614835198358683673">Előfordulhat, hogy a Chromebook nem töltődik, amíg be van kapcsolva. Vegye fontolóra a gyári töltő használatát.</translation>
+<translation id="1895658205118569222">Kikapcsolás</translation>
+<translation id="4430019312045809116">Hangerő</translation>
+<translation id="4442424173763614572">A DNS keresése sikertelen</translation>
+<translation id="6356500677799115505">Az akkumulátor teljesen fel van töltve, és töltődik.</translation>
+<translation id="7874779702599364982">Mobilhálózatok keresése...</translation>
+<translation id="583281660410589416">Ismeretlen</translation>
+<translation id="1383876407941801731">Keresés</translation>
+<translation id="7468789844759750875">Látogasson el a(z) <ph name="NAME"/> aktivációs portálra további adatforgalom vásárlásához.</translation>
+<translation id="3901991538546252627">Csatlakozás a következőhöz: <ph name="NAME"/></translation>
+<translation id="2204305834655267233">Hálózatinformáció</translation>
+<translation id="1621499497873603021">Akkumulátor lemerüléséig hátralévő idő: <ph name="TIME_LEFT"/></translation>
+<translation id="5980301590375426705">Kilépés a vendég munkamenetből</translation>
+<translation id="4471417012762451363">Az akkumulátor töltöttsége <ph name="PERCENTAGE"/>%, és töltődik</translation>
+<translation id="8308637677604853869">Előző menü</translation>
+<translation id="4666297444214622512">Nem lehet bejelentkezni még egy fiókba.</translation>
+<translation id="1346748346194534595">Jobbra</translation>
+<translation id="1773212559869067373">A hitelesítési tanúsítvány helyileg elutasítva</translation>
+<translation id="8528322925433439945">Mobil...</translation>
+<translation id="7049357003967926684">Társaság</translation>
+<translation id="8428213095426709021">Beállítások</translation>
+<translation id="2372145515558759244">Alkalmazások szinkronizálása...</translation>
+<translation id="7256405249507348194">Ismeretlen hiba: <ph name="DESC"/></translation>
+<translation id="7925247922861151263">AAA-ellenőrzés sikertelen</translation>
+<translation id="8456362689280298700"><ph name="HOUR"/>:<ph name="MINUTE"/> a teljes feltöltésig</translation>
+<translation id="5787281376604286451">Hangos visszajelzés engedélyezett.
+A tiltásához nyomja le a Ctrl+Alt+Z billentyűkombinációt.</translation>
+<translation id="4479639480957787382">Ethernet</translation>
+<translation id="6312403991423642364">Ismeretlen hálózati hiba</translation>
+<translation id="1467432559032391204">Balra</translation>
+<translation id="5543001071567407895">SMS</translation>
+<translation id="2354174487190027830"><ph name="NAME"/> aktiválása</translation>
+<translation id="8814190375133053267">Wi-Fi</translation>
+<translation id="1398853756734560583">Teljes méret</translation>
+<translation id="2692809339924654275"><ph name="BLUETOOTH"/>: csatlakozás...</translation>
+<translation id="252373100621549798">Ismeretlen kijelző</translation>
+<translation id="1882897271359938046">Tükrözés: <ph name="DISPLAY_NAME"/></translation>
+<translation id="2727977024730340865">Kis teljesítményű töltőt csatlakoztatott. Az akkumulátor töltése nem megbízható.</translation>
+<translation id="3784455785234192852">Zárolás</translation>
+<translation id="2805756323405976993">Programok</translation>
+<translation id="8871072142849158571"><ph name="DISPLAY_NAME"/> átméretezve: <ph name="RESOLUTION"/></translation>
+<translation id="1512064327686280138">Aktiválási hiba</translation>
+<translation id="5097002363526479830">Nem sikerült csatlakozni a(z) <ph name="NAME"/> hálózathoz: <ph name="DETAILS"/></translation>
+<translation id="1850504506766569011">Wi-Fi kikapcsolva.</translation>
+<translation id="8132793192354020517">Csatlakozás a következőhöz megtörtént: <ph name="NAME"/></translation>
+<translation id="7052914147756339792">Háttérkép beállítása...</translation>
+<translation id="8678698760965522072">Online</translation>
+<translation id="2532589005999780174">Nagy kontrasztú mód</translation>
+<translation id="1119447706177454957">Belső hiba</translation>
+<translation id="3019353588588144572">Akkumulátor teljes feltöltéséig hátralévő idő: <ph name="TIME_REMAINING"/></translation>
+<translation id="3473479545200714844">Képernyőnagyító</translation>
+<translation id="7005812687360380971">Sikertelen</translation>
+<translation id="882279321799040148">Kattintson a megtekintéshez</translation>
+<translation id="5045550434625856497">Téves jelszó</translation>
+<translation id="1602076796624386989">Mobiladatok engedélyezése</translation>
+<translation id="6981982820502123353">Kisegítő lehetőségek</translation>
+<translation id="3157931365184549694">Visszaállítás</translation>
+<translation id="4274292172790327596">Azonosítatlan hiba</translation>
+<translation id="4032485810211612751"><ph name="HOURS"/>:<ph name="MINUTES"/>:<ph name="SECONDS"/></translation>
+<translation id="225680501294068881">Eszközök keresése...</translation>
+<translation id="5597451508971090205"><ph name="SHORT_WEEKDAY"/>, <ph name="DATE"/></translation>
+<translation id="4448844063988177157">Wi-Fi hálózatok keresése...</translation>
+<translation id="8401662262483418323">Nem sikerült a csatlakozás a következőhöz: „<ph name="NAME"/>”: <ph name="DETAILS"/>
+Szerverüzenet: <ph name="SERVER_MSG"/></translation>
+<translation id="2475982808118771221">Hiba történt</translation>
+<translation id="7229570126336867161">EVDO szükséges</translation>
+<translation id="2999742336789313416">A(z) <ph name="DISPLAY_NAME"/> egy <ph name="DOMAIN"/> által kezelt nyilvános munkamenet</translation>
+<translation id="7029814467594812963">Kilépés a munkamenetből</translation>
+<translation id="8454013096329229812">Wi-Fi bekapcsolva.</translation>
+<translation id="4872237917498892622">Alt + Keresés vagy Shift</translation>
+<translation id="2983818520079887040">Beállítások...</translation>
+<translation id="1717216362413677834">Dokkolt mód</translation>
+<translation id="8927026611342028580">Csatlakozás kérelmezve</translation>
+<translation id="8300849813060516376">OTASP sikertelen</translation>
+<translation id="2792498699870441125">Alt + Keresés</translation>
+<translation id="8660803626959853127"><ph name="COUNT"/> fájl szinkronizálása</translation>
+<translation id="3709443003275901162">9+</translation>
+<translation id="639644700271529076">A CAPS LOCK ki van kapcsolva</translation>
+<translation id="6248847161401822652">A kilépéshez nyomja meg kétszer a Ctrl Shift Q billentyűkódot.</translation>
+<translation id="6267036997247669271"><ph name="NAME"/>: aktiválás...</translation>
+<translation id="1391854757121130358">Elképzelhető, hogy mobil adatforgalmi kerete elfogyott.</translation>
+<translation id="5413208160176941586">Helyileg felügyelt felhasználó</translation>
+<translation id="1059194134494239015"><ph name="DISPLAY_NAME"/>: <ph name="RESOLUTION"/></translation>
+<translation id="4864165860509564259">Pozíció az Indítóban</translation>
+<translation id="7593891976182323525">Keresés vagy Shift</translation>
+<translation id="7649070708921625228">Súgó</translation>
+<translation id="3050422059534974565">A CAPS LOCK be van kapcsolva.
+Kikapcsolásához nyomja meg a Keresés vagy a Shift billentyűt.</translation>
+<translation id="397105322502079400">Számítás…</translation>
+<translation id="158849752021629804">Otthoni hálózat szükséges</translation>
+<translation id="6857811139397017780"><ph name="NETWORKSERVICE"/> aktiválása</translation>
+<translation id="5864471791310927901">DHCP-keresés sikertelen</translation>
+<translation id="5812035014844949013">KIMENET</translation>
+<translation id="6692173217867674490">Rossz összetett jelszó</translation>
+<translation id="6165508094623778733">További információ</translation>
+<translation id="9046895021617826162">Csatlakozás sikertelen</translation>
+<translation id="973896785707726617">A munkamenet <ph name="SESSION_TIME_REMAINING"/> múlva véget ér. Ekkor a rendszer automatikusan kijelentkezteti.</translation>
+<translation id="8372369524088641025">Hibás WEP kulcs</translation>
+<translation id="6636709850131805001">Azonosítatlan állam</translation>
+<translation id="3573179567135747900">Visszatérés ehhez: &quot;<ph name="FROM_LOCALE"/>&quot; (újraindítás szükséges)</translation>
+<translation id="8103386449138765447">SMS-üzenetek: <ph name="MESSAGE_COUNT"/></translation>
+<translation id="5045002648206642691">A Google Drive beállításai...</translation>
+<translation id="1510238584712386396">Indító</translation>
+<translation id="7209101170223508707">A CAPS LOCK be van kapcsolva.
+Kikapcsolásához nyomja meg az Alt + Keresés vagy a Shift billentyűt.</translation>
+<translation id="8940956008527784070">Alacsony akkumulátortöltöttség (<ph name="PERCENTAGE"/>%)</translation>
+<translation id="5102001756192215136"><ph name="HOUR"/>:<ph name="MINUTE"/> van hátra</translation>
+<translation id="520760366042891468">Képernyő megosztásának vezérlése a Hangoutson keresztül.</translation>
+<translation id="8000066093800657092">Nincs hálózat</translation>
+<translation id="4015692727874266537">Bejelentkezés másik fiókba...</translation>
+<translation id="5941711191222866238">Kicsinyítés</translation>
+<translation id="6911468394164995108">Csatlakozás másik hálózathoz...</translation>
+<translation id="412065659894267608"><ph name="HOUR"/> ó <ph name="MINUTE"/> p a teljes feltöltésig</translation>
+<translation id="6359806961507272919">SMS innen: <ph name="PHONE_NUMBER"/></translation>
+<translation id="1244147615850840081">Szállító</translation>
+</translationbundle> \ No newline at end of file
diff --git a/chromium/ash/strings/ash_strings_id.xtb b/chromium/ash/strings/ash_strings_id.xtb
new file mode 100644
index 00000000000..3007b2f92bd
--- /dev/null
+++ b/chromium/ash/strings/ash_strings_id.xtb
@@ -0,0 +1,201 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="id">
+<translation id="3595596368722241419">Baterai penuh</translation>
+<translation id="5250713215130379958">Sembunyikan peluncur secara otomatis</translation>
+<translation id="7814236020522506259"><ph name="HOUR"/> dan <ph name="MINUTE"/></translation>
+<translation id="7880025619322806991">Status portal</translation>
+<translation id="30155388420722288">Tombol Luapan</translation>
+<translation id="5571066253365925590">Bluetooth diaktifkan</translation>
+<translation id="9074739597929991885">Bluetooth</translation>
+<translation id="2268130516524549846">Bluetooth dinonaktifkan</translation>
+<translation id="3775358506042162758">Anda hanya dapat memiliki paling banyak tiga akun dalam fitur masuk multipel.</translation>
+<translation id="370649949373421643">Aktifkan Wi-Fi</translation>
+<translation id="3626281679859535460">Kecerahan</translation>
+<translation id="8054466585765276473">Menghitung masa pakai baterai.</translation>
+<translation id="7982789257301363584">Jaringan</translation>
+<translation id="5565793151875479467">Proxy...</translation>
+<translation id="938582441709398163">Hamparan Keyboard</translation>
+<translation id="4387004326333427325">Sertifikat atutentikasi ditolak dari jarak jauh</translation>
+<translation id="6979158407327259162">Google Drive</translation>
+<translation id="6943836128787782965">HTTP gagal</translation>
+<translation id="2297568595583585744">Baki status</translation>
+<translation id="1661867754829461514">PIN hilang</translation>
+<translation id="4508225577814909926"><ph name="NAME"/>: Menyambung...</translation>
+<translation id="4237016987259239829">Kesalahan Koneksi Jaringan</translation>
+<translation id="2946640296642327832">Aktifkan Bluetooth</translation>
+<translation id="6459472438155181876">Memperpanjang layar ke <ph name="DISPLAY_NAME"/></translation>
+<translation id="8206859287963243715">Seluler</translation>
+<translation id="6596816719288285829">Alamat IP</translation>
+<translation id="4508265954913339219">Aktivasi gagal</translation>
+<translation id="3621712662352432595">Setelan Audio</translation>
+<translation id="1812696562331527143">Metode masukan Anda telah berubah menjadi <ph name="INPUT_METHOD_ID"/>*(<ph name="BEGIN_LINK"/>pihak ketiga<ph name="END_LINK"/>).
+Tekan Shift + Alt untuk beralih.</translation>
+<translation id="2127372758936585790">Pengisi daya rendah</translation>
+<translation id="3846575436967432996">Tidak tersedia informasi jaringan</translation>
+<translation id="3026237328237090306">Siapkan data seluler</translation>
+<translation id="785750925697875037">Lihat akun seluler</translation>
+<translation id="153454903766751181">Memulai modem seluler...</translation>
+<translation id="4628814525959230255">Berbagi kontrol layar Anda dengan <ph name="HELPER_NAME"/> melalui Hangouts.</translation>
+<translation id="8343941333792395995"><ph name="DISPLAY_NAME"/> telah diputar</translation>
+<translation id="7864539943188674973">Nonaktifkan Bluetooth</translation>
+<translation id="939252827960237676">Gagal menyimpan tangkapan layar</translation>
+<translation id="3126069444801937830">Mulai ulang untuk memperbarui</translation>
+<translation id="2268813581635650749">Keluarkan semua pengguna</translation>
+<translation id="735745346212279324">VPN terputus</translation>
+<translation id="7320906967354320621">Menganggur</translation>
+<translation id="6303423059719347535">Baterai terisi <ph name="PERCENTAGE"/>%</translation>
+<translation id="15373452373711364">Kursor mouse besar</translation>
+<translation id="2778346081696727092">Gagal mengautentikasi dengan nama pengguna atau sandi yang diberikan</translation>
+<translation id="3294437725009624529">Tamu</translation>
+<translation id="8190698733819146287">Sesuaikan bahasa dan masukan...</translation>
+<translation id="2903907270192926896">MASUKAN</translation>
+<translation id="8676770494376880701">Pengisi daya rendah terpasang</translation>
+<translation id="7170041865419449892">Di luar jangkauan</translation>
+<translation id="4804818685124855865">Putuskan</translation>
+<translation id="2544853746127077729">Sertifikat autentikasi ditolak oleh jaringan</translation>
+<translation id="5222676887888702881">Keluar</translation>
+<translation id="2688477613306174402">Konfigurasi</translation>
+<translation id="1272079795634619415">Berhenti</translation>
+<translation id="4957722034734105353">Pelajari selengkapnya...</translation>
+<translation id="2964193600955408481">Nonaktifkan Wi-Fi</translation>
+<translation id="811680302244032017">Tambahkan perangkat...</translation>
+<translation id="4279490309300973883">Mencerminkan</translation>
+<translation id="2509468283778169019">CAPS LOCK aktif</translation>
+<translation id="3892641579809465218">Tampilan Internal</translation>
+<translation id="7823564328645135659">Bahasa telah diubah dari &quot;<ph name="FROM_LOCALE"/>&quot; menjadi &quot;<ph name="TO_LOCALE"/>&quot; setelah menyinkronkan setelan Anda.</translation>
+<translation id="3368922792935385530">Tersambung</translation>
+<translation id="8340999562596018839">Masukan lisan</translation>
+<translation id="8654520615680304441">Aktifkan Wi-Fi...</translation>
+<translation id="5825747213122829519">Metode masukan Anda telah berubah menjadi <ph name="INPUT_METHOD_ID"/>.
+Tekan Shift + Alt untuk beralih.</translation>
+<translation id="2562916301614567480">Jaringan Pribadi</translation>
+<translation id="6549021752953852991">Jaringan seluler tidak tersedia</translation>
+<translation id="4379753398862151997">Monitor, sayang sekali kita tidak bisa bekerja sama. (Monitor tersebut tidak didukung)</translation>
+<translation id="6426039856985689743">Nonaktifkan data seluler</translation>
+<translation id="3087734570205094154">Bawah</translation>
+<translation id="3742055079367172538">Tangkapan layar telah diambil</translation>
+<translation id="8878886163241303700">Memperluas layar</translation>
+<translation id="5271016907025319479">VPN belum dikonfigurasi.</translation>
+<translation id="372094107052732682">Tekan Ctrl+Shift+Q dua kali untuk keluar.</translation>
+<translation id="6803622936009808957">Tidak dapat menggandakan tampilan karena tidak ditemukan resolusi yang didukung. Memasuki desktop yang diperluas sebagai gantinya.</translation>
+<translation id="1480041086352807611">Mode demo</translation>
+<translation id="3626637461649818317">Sisa <ph name="PERCENTAGE"/>%</translation>
+<translation id="9089416786594320554">Metode masukan</translation>
+<translation id="6247708409970142803"><ph name="PERCENTAGE"/>%</translation>
+<translation id="2614835198358683673">Chromebook Anda mungkin tidak mengisi daya saat sedang menyala. Pertimbangkan untuk menggunakan pengisi daya resmi.</translation>
+<translation id="1895658205118569222">Mati</translation>
+<translation id="4430019312045809116">Volume</translation>
+<translation id="4442424173763614572">Pencarian DNS gagal</translation>
+<translation id="6356500677799115505">Baterai sudah penuh dan masih mengisi.</translation>
+<translation id="7874779702599364982">Menelusuri jaringan seluler...</translation>
+<translation id="583281660410589416">Tidak diketahui</translation>
+<translation id="1383876407941801731">Penelusuran</translation>
+<translation id="7468789844759750875">Kunjungi portal pengaktifan <ph name="NAME"/> untuk membeli lebih banyak data.</translation>
+<translation id="3901991538546252627">Menyambung ke <ph name="NAME"/></translation>
+<translation id="2204305834655267233">Info Jaringan</translation>
+<translation id="1621499497873603021">Waktu yang tersisa hingga baterai kosong, <ph name="TIME_LEFT"/></translation>
+<translation id="5980301590375426705">Keluar dari sesi tamu</translation>
+<translation id="4471417012762451363">Baterai sudah terisi <ph name="PERCENTAGE"/>% dan masih mengisi</translation>
+<translation id="8308637677604853869">Menu sebelumnya</translation>
+<translation id="4666297444214622512">Tidak dapat masuk ke akun lain.</translation>
+<translation id="1346748346194534595">Kanan</translation>
+<translation id="1773212559869067373">Sertifikat autentikasi ditolak secara lokal</translation>
+<translation id="8528322925433439945">Seluler ...</translation>
+<translation id="7049357003967926684">Kaitan</translation>
+<translation id="8428213095426709021">Setelan</translation>
+<translation id="2372145515558759244">Menyinkronkan aplikasi...</translation>
+<translation id="7256405249507348194">Kesalahan tidak dikenal: <ph name="DESC"/></translation>
+<translation id="7925247922861151263">Pemeriksaan AAA gagal</translation>
+<translation id="8456362689280298700"><ph name="HOUR"/>.<ph name="MINUTE"/> sampai penuh</translation>
+<translation id="5787281376604286451">Masukan lisan diaktifkan.
+Tekan Ctrl+Alt+Z untuk menonaktifkan.</translation>
+<translation id="4479639480957787382">Ethernet</translation>
+<translation id="6312403991423642364">Kesalahan jaringan tidak dikenal</translation>
+<translation id="1467432559032391204">Kiri</translation>
+<translation id="5543001071567407895">SMS</translation>
+<translation id="2354174487190027830">Mengaktifkan <ph name="NAME"/></translation>
+<translation id="8814190375133053267">Wi-Fi</translation>
+<translation id="1398853756734560583">Perbesar</translation>
+<translation id="2692809339924654275"><ph name="BLUETOOTH"/>: Menyambung...</translation>
+<translation id="252373100621549798">Tampilan yang Tidak Diketahui</translation>
+<translation id="1882897271359938046">Mencerminkan ke <ph name="DISPLAY_NAME"/></translation>
+<translation id="2727977024730340865">Dipasang ke pengisi daya rendah. Pengisian daya baterai mungkin tidak dapat diandalkan.</translation>
+<translation id="3784455785234192852">Kunci</translation>
+<translation id="2805756323405976993">Apl</translation>
+<translation id="8871072142849158571">Ukuran <ph name="DISPLAY_NAME"/> telah diubah ke <ph name="RESOLUTION"/></translation>
+<translation id="1512064327686280138">Kegagalan aktivasi</translation>
+<translation id="5097002363526479830">Gagal menyambung ke jaringan '<ph name="NAME"/>': <ph name="DETAILS"/></translation>
+<translation id="1850504506766569011">Wi-Fi dinonaktifkan.</translation>
+<translation id="8132793192354020517">Tersambung ke <ph name="NAME"/></translation>
+<translation id="7052914147756339792">Setel wallpaper...</translation>
+<translation id="8678698760965522072">Status online</translation>
+<translation id="2532589005999780174">Mode kontras tinggi</translation>
+<translation id="1119447706177454957">Kesalahan internal</translation>
+<translation id="3019353588588144572">Waktu yang tersisa hingga baterai terisi penuh, <ph name="TIME_REMAINING"/></translation>
+<translation id="3473479545200714844">Kaca pembesar layar</translation>
+<translation id="7005812687360380971">Kegagalan</translation>
+<translation id="882279321799040148">Klik untuk melihat</translation>
+<translation id="5045550434625856497">Sandi salah</translation>
+<translation id="1602076796624386989">Aktifkan data seluler</translation>
+<translation id="6981982820502123353">Aksesibilitas</translation>
+<translation id="3157931365184549694">Pulihkan</translation>
+<translation id="4274292172790327596">Kesalahan tak dikenal</translation>
+<translation id="4032485810211612751"><ph name="HOURS"/>.<ph name="MINUTES"/>.<ph name="SECONDS"/></translation>
+<translation id="225680501294068881">Memindai perangkat...</translation>
+<translation id="5597451508971090205"><ph name="SHORT_WEEKDAY"/>, <ph name="DATE"/></translation>
+<translation id="4448844063988177157">Menelusuri jaringan Wi-Fi...</translation>
+<translation id="8401662262483418323">Gagal tersambung ke '<ph name="NAME"/>': <ph name="DETAILS"/>
+Pesan server: <ph name="SERVER_MSG"/></translation>
+<translation id="2475982808118771221">Terjadi kesalahan</translation>
+<translation id="7229570126336867161">Memerlukan EVDO</translation>
+<translation id="2999742336789313416"><ph name="DISPLAY_NAME"/> adalah sesi publik yang dikelola oleh <ph name="DOMAIN"/></translation>
+<translation id="7029814467594812963">Keluar dari sesi</translation>
+<translation id="8454013096329229812">Wi-Fi diaktifkan.</translation>
+<translation id="4872237917498892622">Alt+Telusuri atau Shift</translation>
+<translation id="2983818520079887040">Setelan...</translation>
+<translation id="1717216362413677834">Mode dok</translation>
+<translation id="8927026611342028580">Sambungan Diminta</translation>
+<translation id="8300849813060516376">OTASP gagal</translation>
+<translation id="2792498699870441125">Alt+Telusuri</translation>
+<translation id="8660803626959853127">Menyinkronkan <ph name="COUNT"/> file</translation>
+<translation id="3709443003275901162">9+</translation>
+<translation id="639644700271529076">CAPS LOCK tidak aktif</translation>
+<translation id="6248847161401822652">Tekan Control Shift Q dua kali untuk keluar.</translation>
+<translation id="6267036997247669271"><ph name="NAME"/>: Mengaktifkan...</translation>
+<translation id="1391854757121130358">Anda mungkin telah menghabiskan jatah data seluler.</translation>
+<translation id="5413208160176941586">Pengguna yang dikelola secara lokal</translation>
+<translation id="1059194134494239015"><ph name="DISPLAY_NAME"/>: <ph name="RESOLUTION"/></translation>
+<translation id="4864165860509564259">Posisi peluncur</translation>
+<translation id="7593891976182323525">Telusuri atau Shift</translation>
+<translation id="7649070708921625228">Bantuan</translation>
+<translation id="3050422059534974565">CAPS LOCK aktif.
+Tekan Telusuri atau Shift untuk membatalkan.</translation>
+<translation id="397105322502079400">Menghitung...</translation>
+<translation id="158849752021629804">Memerlukan jaringan rumah</translation>
+<translation id="6857811139397017780">Aktifkan <ph name="NETWORKSERVICE"/></translation>
+<translation id="5864471791310927901">Pencarian DHCP gagal</translation>
+<translation id="5812035014844949013">KELUARAN</translation>
+<translation id="6692173217867674490">Frasa sandi yang buruk</translation>
+<translation id="6165508094623778733">Pelajari lebih lanjut</translation>
+<translation id="9046895021617826162">Gagal menyambung</translation>
+<translation id="973896785707726617">Sesi ini akan berakhir dalam <ph name="SESSION_TIME_REMAINING"/>. Anda akan otomatis dikeluarkan.</translation>
+<translation id="8372369524088641025">Kunci WEP yang buruk</translation>
+<translation id="6636709850131805001">Keadaan yang tidak dikenal</translation>
+<translation id="3573179567135747900">Ubah kembali ke &quot;<ph name="FROM_LOCALE"/>&quot; (harus dinyalakan ulang)</translation>
+<translation id="8103386449138765447">Pesan SMS: <ph name="MESSAGE_COUNT"/></translation>
+<translation id="5045002648206642691">Setelan Google Drive...</translation>
+<translation id="1510238584712386396">Peluncur</translation>
+<translation id="7209101170223508707">CAPS LOCK aktif.
+Tekan Alt+Telusuri atau Shift untuk membatalkan.</translation>
+<translation id="8940956008527784070">Baterai lemah (<ph name="PERCENTAGE"/>%)</translation>
+<translation id="5102001756192215136">Sisa <ph name="HOUR"/>:<ph name="MINUTE"/></translation>
+<translation id="520760366042891468">Berbagi kontrol layar Anda melalui Hangouts.</translation>
+<translation id="8000066093800657092">Tidak ada jaringan</translation>
+<translation id="4015692727874266537">Masuk ke akun lain...</translation>
+<translation id="5941711191222866238">Perkecil</translation>
+<translation id="6911468394164995108">Bergabung dengan lainnya...</translation>
+<translation id="412065659894267608"><ph name="HOUR"/>j <ph name="MINUTE"/>m sampai penuh</translation>
+<translation id="6359806961507272919">SMS dari <ph name="PHONE_NUMBER"/></translation>
+<translation id="1244147615850840081">Operator</translation>
+</translationbundle> \ No newline at end of file
diff --git a/chromium/ash/strings/ash_strings_it.xtb b/chromium/ash/strings/ash_strings_it.xtb
new file mode 100644
index 00000000000..faa8420e5ff
--- /dev/null
+++ b/chromium/ash/strings/ash_strings_it.xtb
@@ -0,0 +1,201 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="it">
+<translation id="3595596368722241419">Batteria carica</translation>
+<translation id="5250713215130379958">Nascondi automaticamente Avvio applicazioni</translation>
+<translation id="7814236020522506259"><ph name="HOUR"/> e <ph name="MINUTE"/></translation>
+<translation id="7880025619322806991">Stato portale</translation>
+<translation id="30155388420722288">Pulsante Overflow</translation>
+<translation id="5571066253365925590">Bluetooth attivo</translation>
+<translation id="9074739597929991885">Bluetooth</translation>
+<translation id="2268130516524549846">Bluetooth disattivo</translation>
+<translation id="3775358506042162758">Puoi avere massimo tre account per l'accesso simultaneo.</translation>
+<translation id="370649949373421643">Attiva Wi-Fi</translation>
+<translation id="3626281679859535460">Luminosità</translation>
+<translation id="8054466585765276473">Calcolo della durata della batteria.</translation>
+<translation id="7982789257301363584">Rete</translation>
+<translation id="5565793151875479467">Proxy...</translation>
+<translation id="938582441709398163">Overlay tastiera</translation>
+<translation id="4387004326333427325">Certificato di autenticazione rifiutato da remoto</translation>
+<translation id="6979158407327259162">Google Drive</translation>
+<translation id="6943836128787782965">Recupero HTTP non riuscito</translation>
+<translation id="2297568595583585744">Barra di stato</translation>
+<translation id="1661867754829461514">PIN mancante</translation>
+<translation id="4508225577814909926"><ph name="NAME"/>: connessione...</translation>
+<translation id="4237016987259239829">Errore di connessione di rete</translation>
+<translation id="2946640296642327832">Attiva Bluetooth</translation>
+<translation id="6459472438155181876">Estensione dello schermo su <ph name="DISPLAY_NAME"/></translation>
+<translation id="8206859287963243715">Cellulare</translation>
+<translation id="6596816719288285829">Indirizzo IP</translation>
+<translation id="4508265954913339219">Attivazione non riuscita</translation>
+<translation id="3621712662352432595">Impostazioni audio</translation>
+<translation id="1812696562331527143">Il metodo di immissione è stato cambiato in <ph name="INPUT_METHOD_ID"/>*(<ph name="BEGIN_LINK"/>3a parte<ph name="END_LINK"/>).
+Premi Maiusc+Alt per cambiare metodo.</translation>
+<translation id="2127372758936585790">Caricabatterie a basso consumo</translation>
+<translation id="3846575436967432996">Nessuna informazione di rete disponibile</translation>
+<translation id="3026237328237090306">Configura dati mobili</translation>
+<translation id="785750925697875037">Visualizza account per cellulari</translation>
+<translation id="153454903766751181">Inizializzazione del modem per cellulari...</translation>
+<translation id="4628814525959230255">Condivisione del controllo dello schermo con <ph name="HELPER_NAME"/> tramite Hangouts.</translation>
+<translation id="8343941333792395995"><ph name="DISPLAY_NAME"/> ruotato</translation>
+<translation id="7864539943188674973">Disattiva Bluetooth</translation>
+<translation id="939252827960237676">Salvataggio dello screenshot non riuscito</translation>
+<translation id="3126069444801937830">Riavvia per aggiornare</translation>
+<translation id="2268813581635650749">Disconnetti tutti</translation>
+<translation id="735745346212279324">VPN scollegata</translation>
+<translation id="7320906967354320621">In pausa</translation>
+<translation id="6303423059719347535">La batteria è carica al <ph name="PERCENTAGE"/>%</translation>
+<translation id="15373452373711364">Puntatore del mouse grande</translation>
+<translation id="2778346081696727092">Autenticazione non riuscita con il nome utente o la password forniti</translation>
+<translation id="3294437725009624529">Ospite</translation>
+<translation id="8190698733819146287">Personalizza lingue e immissione...</translation>
+<translation id="2903907270192926896">INGRESSO</translation>
+<translation id="8676770494376880701">Caricabatterie a basso consumo collegato</translation>
+<translation id="7170041865419449892">Fuori dal raggio d'azione</translation>
+<translation id="4804818685124855865">Disconnetti</translation>
+<translation id="2544853746127077729">Certificato di autenticazione rifiutato dalla rete</translation>
+<translation id="5222676887888702881">Esci</translation>
+<translation id="2688477613306174402">Configurazione</translation>
+<translation id="1272079795634619415">Interrompi</translation>
+<translation id="4957722034734105353">Ulteriori informazioni...</translation>
+<translation id="2964193600955408481">Disattiva Wi-Fi</translation>
+<translation id="811680302244032017">Aggiungi dispositivo...</translation>
+<translation id="4279490309300973883">Mirroring</translation>
+<translation id="2509468283778169019">BLOC MAIUSC è attivo</translation>
+<translation id="3892641579809465218">Display interno</translation>
+<translation id="7823564328645135659">La lingua è stata modificata da &quot;<ph name="FROM_LOCALE"/>&quot; a &quot;<ph name="TO_LOCALE"/>&quot; dopo la sincronizzazione delle impostazioni.</translation>
+<translation id="3368922792935385530">Connessa</translation>
+<translation id="8340999562596018839">Feedback vocale</translation>
+<translation id="8654520615680304441">Attiva Wi-Fi...</translation>
+<translation id="5825747213122829519">Il metodo di immissione è stato cambiato in <ph name="INPUT_METHOD_ID"/>.
+Premi Maiusc+Alt per cambiare metodo.</translation>
+<translation id="2562916301614567480">Rete privata</translation>
+<translation id="6549021752953852991">Nessuna rete cellulare disponibile</translation>
+<translation id="4379753398862151997">Caro monitor, non funziona tra di noi (il monitor non è supportato).</translation>
+<translation id="6426039856985689743">Disattiva dati mobili</translation>
+<translation id="3087734570205094154">In basso</translation>
+<translation id="3742055079367172538">Screenshot acquisito</translation>
+<translation id="8878886163241303700">Estensione schermo</translation>
+<translation id="5271016907025319479">VPN non configurata.</translation>
+<translation id="372094107052732682">Per uscire premi due volte Ctrl+Maiusc+Q.</translation>
+<translation id="6803622936009808957">Impossibile duplicare i display perché non sono state trovate risoluzioni supportate. È stato attivato il desktop esteso.</translation>
+<translation id="1480041086352807611">Modalità Demo</translation>
+<translation id="3626637461649818317"><ph name="PERCENTAGE"/>% rimanente</translation>
+<translation id="9089416786594320554">Metodi di immissione</translation>
+<translation id="6247708409970142803"><ph name="PERCENTAGE"/>%</translation>
+<translation id="2614835198358683673">Il Chromebook potrebbe non ricaricarsi mentre è accesso. Prova a utilizzare il caricabatterie ufficiale.</translation>
+<translation id="1895658205118569222">Chiusura</translation>
+<translation id="4430019312045809116">Volume</translation>
+<translation id="4442424173763614572">Ricerca DNS non riuscita</translation>
+<translation id="6356500677799115505">La batteria è carica e in ricarica.</translation>
+<translation id="7874779702599364982">Ricerca reti cellulari...</translation>
+<translation id="583281660410589416">Sconosciuto</translation>
+<translation id="1383876407941801731">Ricerca</translation>
+<translation id="7468789844759750875">Visita il portale di attivazione <ph name="NAME"/> per acquistare altri dati.</translation>
+<translation id="3901991538546252627">Connessione a: <ph name="NAME"/></translation>
+<translation id="2204305834655267233">Informazioni di rete</translation>
+<translation id="1621499497873603021">Tempo rimanente all'esaurimento della batteria: <ph name="TIME_LEFT"/></translation>
+<translation id="5980301590375426705">Esci da sessione Ospite</translation>
+<translation id="4471417012762451363">La batteria è carica al <ph name="PERCENTAGE"/>% e in ricarica</translation>
+<translation id="8308637677604853869">Menu precedente</translation>
+<translation id="4666297444214622512">Impossibile accedere a un altro account.</translation>
+<translation id="1346748346194534595">Destra</translation>
+<translation id="1773212559869067373">Certificato di autenticazione rifiutato localmente</translation>
+<translation id="8528322925433439945">Reti mobili...</translation>
+<translation id="7049357003967926684">Associazione</translation>
+<translation id="8428213095426709021">Impostazioni</translation>
+<translation id="2372145515558759244">Sincronizzazione applicazioni...</translation>
+<translation id="7256405249507348194">Errore non riconosciuto: <ph name="DESC"/></translation>
+<translation id="7925247922861151263">Controllo AAA non riuscito</translation>
+<translation id="8456362689280298700"><ph name="HOUR"/>:<ph name="MINUTE"/> al completamento</translation>
+<translation id="5787281376604286451">Feedback vocale attivato.
+Premi Ctrl+Alt+Z per disattivarlo.</translation>
+<translation id="4479639480957787382">Ethernet</translation>
+<translation id="6312403991423642364">Errore di rete sconosciuto</translation>
+<translation id="1467432559032391204">Sinistra</translation>
+<translation id="5543001071567407895">SMS</translation>
+<translation id="2354174487190027830">Attivazione di <ph name="NAME"/></translation>
+<translation id="8814190375133053267">Wi-Fi</translation>
+<translation id="1398853756734560583">Ingrandisci</translation>
+<translation id="2692809339924654275"><ph name="BLUETOOTH"/>: connessione...</translation>
+<translation id="252373100621549798">Display sconosciuto</translation>
+<translation id="1882897271359938046">Mirroring su <ph name="DISPLAY_NAME"/></translation>
+<translation id="2727977024730340865">Collegato a un caricabatterie a basso consumo. La carica della batteria potrebbe non essere affidabile.</translation>
+<translation id="3784455785234192852">Blocca</translation>
+<translation id="2805756323405976993">Applicazioni</translation>
+<translation id="8871072142849158571"><ph name="DISPLAY_NAME"/> è stato ridimensionato a <ph name="RESOLUTION"/></translation>
+<translation id="1512064327686280138">Errore di attivazione</translation>
+<translation id="5097002363526479830">Connessione alla rete &quot;<ph name="NAME"/>&quot; non riuscita: <ph name="DETAILS"/></translation>
+<translation id="1850504506766569011">Wi-Fi non attivo.</translation>
+<translation id="8132793192354020517">Connesso a <ph name="NAME"/></translation>
+<translation id="7052914147756339792">Imposta sfondo...</translation>
+<translation id="8678698760965522072">Stato online</translation>
+<translation id="2532589005999780174">Modalità ad alto contrasto</translation>
+<translation id="1119447706177454957">Errore interno</translation>
+<translation id="3019353588588144572">Tempo rimanente al caricamento completo della batteria: <ph name="TIME_REMAINING"/></translation>
+<translation id="3473479545200714844">Ingrandimento dello schermo</translation>
+<translation id="7005812687360380971">Errore</translation>
+<translation id="882279321799040148">Fai clic per visualizzare</translation>
+<translation id="5045550434625856497">Password non corretta</translation>
+<translation id="1602076796624386989">Attiva dati mobili</translation>
+<translation id="6981982820502123353">Accessibilità</translation>
+<translation id="3157931365184549694">Ripristina</translation>
+<translation id="4274292172790327596">Errore non riconosciuto</translation>
+<translation id="4032485810211612751"><ph name="HOURS"/>:<ph name="MINUTES"/>:<ph name="SECONDS"/></translation>
+<translation id="225680501294068881">Ricerca dispositivi in corso...</translation>
+<translation id="5597451508971090205"><ph name="SHORT_WEEKDAY"/>, <ph name="DATE"/></translation>
+<translation id="4448844063988177157">Ricerca di reti Wi-Fi in corso...</translation>
+<translation id="8401662262483418323">Impossibile collegarsi a &quot;<ph name="NAME"/>&quot;: <ph name="DETAILS"/>
+Messaggio del server: <ph name="SERVER_MSG"/></translation>
+<translation id="2475982808118771221">Si è verificato un errore</translation>
+<translation id="7229570126336867161">Occorre EVDO</translation>
+<translation id="2999742336789313416"><ph name="DISPLAY_NAME"/> è una sessione pubblica gestita da <ph name="DOMAIN"/></translation>
+<translation id="7029814467594812963">Esci da sessione</translation>
+<translation id="8454013096329229812">Wi-Fi attivo.</translation>
+<translation id="4872237917498892622">Alt+tasto per la ricerca o Maiusc</translation>
+<translation id="2983818520079887040">Impostazioni...</translation>
+<translation id="1717216362413677834">Modalità dock</translation>
+<translation id="8927026611342028580">Connessione richiesta</translation>
+<translation id="8300849813060516376">OTASP non riuscito</translation>
+<translation id="2792498699870441125">Alt+tasto per la ricerca</translation>
+<translation id="8660803626959853127">Sincronizzazione di <ph name="COUNT"/> file in corso</translation>
+<translation id="3709443003275901162">Più di 9</translation>
+<translation id="639644700271529076">Funzione BLOC MAIUSC non attiva</translation>
+<translation id="6248847161401822652">Per uscire premi due volte Ctrl+Maiusc+Q.</translation>
+<translation id="6267036997247669271"><ph name="NAME"/>: attivazione in corso...</translation>
+<translation id="1391854757121130358">Potresti avere esaurito la tua quota di dati mobili.</translation>
+<translation id="5413208160176941586">Utente gestito localmente</translation>
+<translation id="1059194134494239015"><ph name="DISPLAY_NAME"/>: <ph name="RESOLUTION"/></translation>
+<translation id="4864165860509564259">Posizione Avvio applicazioni</translation>
+<translation id="7593891976182323525">Tasto per la ricerca o Maiusc</translation>
+<translation id="7649070708921625228">Guida</translation>
+<translation id="3050422059534974565">La funzione BLOC MAIUSC è attiva.
+Premi il tasto per la ricerca o Maiusc per annullare.</translation>
+<translation id="397105322502079400">Calcolo in corso...</translation>
+<translation id="158849752021629804">Occorre una rete domestica</translation>
+<translation id="6857811139397017780">Attiva <ph name="NETWORKSERVICE"/></translation>
+<translation id="5864471791310927901">Ricerca DHCP non riuscita</translation>
+<translation id="5812035014844949013">USCITA</translation>
+<translation id="6692173217867674490">Passphrase non valida</translation>
+<translation id="6165508094623778733">Ulteriori informazioni</translation>
+<translation id="9046895021617826162">Connessione non riuscita</translation>
+<translation id="973896785707726617">Questa sessione terminerà fra <ph name="SESSION_TIME_REMAINING"/>. Verrà eseguita automaticamente la disconnessione.</translation>
+<translation id="8372369524088641025">Chiave WEP non valida</translation>
+<translation id="6636709850131805001">Stato non riconosciuto</translation>
+<translation id="3573179567135747900">Torna a &quot;<ph name="FROM_LOCALE"/>&quot; (è necessario riavviare)</translation>
+<translation id="8103386449138765447">Messaggi SMS: <ph name="MESSAGE_COUNT"/></translation>
+<translation id="5045002648206642691">Impostazioni Google Drive...</translation>
+<translation id="1510238584712386396">Avvio applicazioni</translation>
+<translation id="7209101170223508707">La funzione BLOC MAIUSC è attiva.
+Premi Alt+tasto per la ricerca o Maiusc per annullare.</translation>
+<translation id="8940956008527784070">Batteria in esaurimento (<ph name="PERCENTAGE"/>%)</translation>
+<translation id="5102001756192215136"><ph name="HOUR"/>:<ph name="MINUTE"/> rimanenti</translation>
+<translation id="520760366042891468">Condivisione del controllo dello schermo tramite Hangouts.</translation>
+<translation id="8000066093800657092">Nessuna rete</translation>
+<translation id="4015692727874266537">Accedi a un altro account...</translation>
+<translation id="5941711191222866238">Riduci a icona</translation>
+<translation id="6911468394164995108">Connetti a un'altra...</translation>
+<translation id="412065659894267608"><ph name="HOUR"/> h e <ph name="MINUTE"/> m per completare la ricarica</translation>
+<translation id="6359806961507272919">SMS da <ph name="PHONE_NUMBER"/></translation>
+<translation id="1244147615850840081">Gestore</translation>
+</translationbundle> \ No newline at end of file
diff --git a/chromium/ash/strings/ash_strings_iw.xtb b/chromium/ash/strings/ash_strings_iw.xtb
new file mode 100644
index 00000000000..a71f1be87f8
--- /dev/null
+++ b/chromium/ash/strings/ash_strings_iw.xtb
@@ -0,0 +1,201 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="iw">
+<translation id="3595596368722241419">סוללה מלאה</translation>
+<translation id="5250713215130379958">הסתר אוטומטית את המפעיל</translation>
+<translation id="7814236020522506259"><ph name="HOUR"/> ו-<ph name="MINUTE"/></translation>
+<translation id="7880025619322806991">מצב הפורטל</translation>
+<translation id="30155388420722288">לחצן גלישה</translation>
+<translation id="5571066253365925590">Bluetooth מופעל</translation>
+<translation id="9074739597929991885">Bluetooth</translation>
+<translation id="2268130516524549846">Bluetooth מושבת</translation>
+<translation id="3775358506042162758">ניתן לכלול עד שלושה חשבונות בלבד בכניסה עם מספר חשבונות.</translation>
+<translation id="370649949373421643">הפעל Wi-Fi</translation>
+<translation id="3626281679859535460">בהירות</translation>
+<translation id="8054466585765276473">מחשב זמן סוללה</translation>
+<translation id="7982789257301363584">רשת</translation>
+<translation id="5565793151875479467">שרת proxy...</translation>
+<translation id="938582441709398163">שכבת על של מקלדת</translation>
+<translation id="4387004326333427325">אישור האימות נדחה מרחוק</translation>
+<translation id="6979158407327259162">Google Drive</translation>
+<translation id="6943836128787782965">פעולת get של HTTP נכשלה</translation>
+<translation id="2297568595583585744">מגש סטטוס</translation>
+<translation id="1661867754829461514">חסר PIN</translation>
+<translation id="4508225577814909926"><ph name="NAME"/>: מתחבר...</translation>
+<translation id="4237016987259239829">שגיאת חיבור רשת</translation>
+<translation id="2946640296642327832">הפעל Bluetooth</translation>
+<translation id="6459472438155181876">מרחיב את המסך אל <ph name="DISPLAY_NAME"/></translation>
+<translation id="8206859287963243715">סלולארי</translation>
+<translation id="6596816719288285829">כתובת IP</translation>
+<translation id="4508265954913339219">ההפעלה נכשלה</translation>
+<translation id="3621712662352432595">הגדרות אודיו</translation>
+<translation id="1812696562331527143">שיטת הקלט שלך השתנתה ל-‏<ph name="INPUT_METHOD_ID"/>*(<ph name="BEGIN_LINK"/>צד שלישי<ph name="END_LINK"/>)‎.
+ הקש Shift + Alt כדי להחליף.</translation>
+<translation id="2127372758936585790">מטען בעל מתח נמוך</translation>
+<translation id="3846575436967432996">אין מידע רשת זמין</translation>
+<translation id="3026237328237090306">הגדר נתונים לנייד</translation>
+<translation id="785750925697875037">הצג את חשבון הנייד</translation>
+<translation id="153454903766751181">מאתחל מודם סלולרי...</translation>
+<translation id="4628814525959230255">משתף את השליטה במסך שלך עם <ph name="HELPER_NAME"/> באמצעות Hangouts.</translation>
+<translation id="8343941333792395995">בוצע סיבוב של <ph name="DISPLAY_NAME"/></translation>
+<translation id="7864539943188674973">השבת Bluetooth</translation>
+<translation id="939252827960237676">שמירת צילום המסך נכשלה</translation>
+<translation id="3126069444801937830">הפעל מחדש כדי לעדכן</translation>
+<translation id="2268813581635650749">הוצא את כולם</translation>
+<translation id="735745346212279324">VPN מנותק</translation>
+<translation id="7320906967354320621">לא פעילה</translation>
+<translation id="6303423059719347535">הסוללה טעונה ב-‎<ph name="PERCENTAGE"/>%‎</translation>
+<translation id="15373452373711364">סמן עכבר גדול</translation>
+<translation id="2778346081696727092">האימות נכשל עם שם המשתמש או הסיסמה שסופקו</translation>
+<translation id="3294437725009624529">אורח</translation>
+<translation id="8190698733819146287">התאם אישית שפה וקלט...</translation>
+<translation id="2903907270192926896">קלט</translation>
+<translation id="8676770494376880701">חובר מטען בעל מתח נמוך</translation>
+<translation id="7170041865419449892">מחוץ לטווח</translation>
+<translation id="4804818685124855865">נתק</translation>
+<translation id="2544853746127077729">אישור האימות נדחה על ידי הרשת</translation>
+<translation id="5222676887888702881">יציאה</translation>
+<translation id="2688477613306174402">תצורה</translation>
+<translation id="1272079795634619415">הפסק</translation>
+<translation id="4957722034734105353">למידע נוסף...</translation>
+<translation id="2964193600955408481">השבת Wi-Fi</translation>
+<translation id="811680302244032017">הוסף את המכשיר...</translation>
+<translation id="4279490309300973883">שיקוף</translation>
+<translation id="2509468283778169019">CAPS LOCK מופעל</translation>
+<translation id="3892641579809465218">תצוגה פנימית</translation>
+<translation id="7823564328645135659">שפת Chrome השתנתה מ&quot;<ph name="FROM_LOCALE"/>&quot; ל&quot;<ph name="TO_LOCALE"/>&quot; לאחר סנכרון ההגדרות.</translation>
+<translation id="3368922792935385530">מחובר</translation>
+<translation id="8340999562596018839">משוב קולי</translation>
+<translation id="8654520615680304441">הפעל את ה-Wi-Fi...</translation>
+<translation id="5825747213122829519">שיטת הקלט שלך השתנתה ל-<ph name="INPUT_METHOD_ID"/>.
+ הקש Shift + Alt כדי להחליף.</translation>
+<translation id="2562916301614567480">רשת פרטית</translation>
+<translation id="6549021752953852991">אין רשת סלולרית זמינה</translation>
+<translation id="4379753398862151997">צג יקר, זה לא עובד בינינו. (הצג אינו נתמך)</translation>
+<translation id="6426039856985689743">השבת נתונים לנייד</translation>
+<translation id="3087734570205094154">תחתית</translation>
+<translation id="3742055079367172538">צילום המסך בוצע</translation>
+<translation id="8878886163241303700">מסך מתרחב</translation>
+<translation id="5271016907025319479">לא הוגדר VPN.</translation>
+<translation id="372094107052732682">לחץ על Ctrl+Shift+Q פעמיים כדי לצאת.</translation>
+<translation id="6803622936009808957">לא ניתן היה לשקף מסכים מכיוון שלא נמצאה רזולוציה נתמכת. במקום זאת התצוגה עברה למצב שולחן עבודה מורחב.</translation>
+<translation id="1480041086352807611">מצב הדגמה</translation>
+<translation id="3626637461649818317"><ph name="PERCENTAGE"/>%‎ נותרו</translation>
+<translation id="9089416786594320554">שיטות קלט</translation>
+<translation id="6247708409970142803">%<ph name="PERCENTAGE"/></translation>
+<translation id="2614835198358683673">ייתכן שה-Chromebook שלך לא ייטען בזמן שהוא מופעל. מומלץ להשתמש במטען הראשי.</translation>
+<translation id="1895658205118569222">כיבוי</translation>
+<translation id="4430019312045809116">עוצמת קול</translation>
+<translation id="4442424173763614572">חיפוש ה-DNS נכשל</translation>
+<translation id="6356500677799115505">הסוללה מלאה ובטעינה.</translation>
+<translation id="7874779702599364982">מחפש רשתות סלולריות...</translation>
+<translation id="583281660410589416">לא ידוע</translation>
+<translation id="1383876407941801731">חיפוש</translation>
+<translation id="7468789844759750875">בקר בפורטל ההפעלה של <ph name="NAME"/> כדי לקנות עוד נתונים.</translation>
+<translation id="3901991538546252627">מתחבר אל: <ph name="NAME"/></translation>
+<translation id="2204305834655267233">פרטי רשת</translation>
+<translation id="1621499497873603021">הזמן שנותר עד להתרוקנות הסוללה, <ph name="TIME_LEFT"/></translation>
+<translation id="5980301590375426705">צא מהפעלת אורח</translation>
+<translation id="4471417012762451363">הסוללה טעונה ב-‎<ph name="PERCENTAGE"/>%‎ ונמצאת בטעינה</translation>
+<translation id="8308637677604853869">התפריט הקודם</translation>
+<translation id="4666297444214622512">לא ניתן להיכנס לחשבון אחר.</translation>
+<translation id="1346748346194534595">ימינה</translation>
+<translation id="1773212559869067373">אישור האימות נדחה מקומית</translation>
+<translation id="8528322925433439945">נייד ...</translation>
+<translation id="7049357003967926684">שיוך</translation>
+<translation id="8428213095426709021">הגדרות</translation>
+<translation id="2372145515558759244">מסנכרן יישומים...</translation>
+<translation id="7256405249507348194">שגיאה לא מזוהה: <ph name="DESC"/></translation>
+<translation id="7925247922861151263">בדיקת AAA נכשלה</translation>
+<translation id="8456362689280298700"><ph name="HOUR"/>:<ph name="MINUTE"/> עד שמתמלא</translation>
+<translation id="5787281376604286451">משוב קולי מופעל.
+הקש Ctrl+Alt+Z כדי להשבית אותו.</translation>
+<translation id="4479639480957787382">אתרנט</translation>
+<translation id="6312403991423642364">שגיאת רשת לא ידועה</translation>
+<translation id="1467432559032391204">שמאלה</translation>
+<translation id="5543001071567407895">SMS</translation>
+<translation id="2354174487190027830">מפעיל את <ph name="NAME"/></translation>
+<translation id="8814190375133053267">Wi-Fi</translation>
+<translation id="1398853756734560583">הגדל</translation>
+<translation id="2692809339924654275"><ph name="BLUETOOTH"/>: מתחבר...</translation>
+<translation id="252373100621549798">תצוגה לא ידועה</translation>
+<translation id="1882897271359938046">משקף אל <ph name="DISPLAY_NAME"/></translation>
+<translation id="2727977024730340865">מחובר למטען בעל מתח נמוך. ייתכן שטעינת הסוללה לא תהיה אמינה.</translation>
+<translation id="3784455785234192852">נעילה</translation>
+<translation id="2805756323405976993">יישומים</translation>
+<translation id="8871072142849158571">הגודל של <ph name="DISPLAY_NAME"/> השתנה ל-<ph name="RESOLUTION"/></translation>
+<translation id="1512064327686280138">כשל בהפעלה</translation>
+<translation id="5097002363526479830">ההתחברות לרשת נכשלה '<ph name="NAME"/>': <ph name="DETAILS"/></translation>
+<translation id="1850504506766569011">Wi-Fi כבוי.</translation>
+<translation id="8132793192354020517">מחובר ל<ph name="NAME"/></translation>
+<translation id="7052914147756339792">הגדר טפט...</translation>
+<translation id="8678698760965522072">מצב מקוון</translation>
+<translation id="2532589005999780174">מצב ניגודיות גבוהה</translation>
+<translation id="1119447706177454957">שגיאה פנימית</translation>
+<translation id="3019353588588144572">הזמן שנותר עד לטעינה מלאה של הסוללה, <ph name="TIME_REMAINING"/></translation>
+<translation id="3473479545200714844">מגדיל התצוגה</translation>
+<translation id="7005812687360380971">כשל</translation>
+<translation id="882279321799040148">לחץ להצגה</translation>
+<translation id="5045550434625856497">סיסמה שגויה</translation>
+<translation id="1602076796624386989">הפעל נתונים לנייד</translation>
+<translation id="6981982820502123353">נגישות</translation>
+<translation id="3157931365184549694">שחזר</translation>
+<translation id="4274292172790327596">שגיאה לא מזוהה</translation>
+<translation id="4032485810211612751"><ph name="HOURS"/>:<ph name="MINUTES"/>:<ph name="SECONDS"/></translation>
+<translation id="225680501294068881">סורק לאיתור מכשירים...</translation>
+<translation id="5597451508971090205"><ph name="SHORT_WEEKDAY"/>, <ph name="DATE"/></translation>
+<translation id="4448844063988177157">מחפש רשתות Wi-Fi...</translation>
+<translation id="8401662262483418323">הניסיון להתחבר אל '<ph name="NAME"/>' נכשל: <ph name="DETAILS"/>
+הודעת השרת: <ph name="SERVER_MSG"/></translation>
+<translation id="2475982808118771221">אירעה שגיאה</translation>
+<translation id="7229570126336867161">יש צורך ב-EVDO</translation>
+<translation id="2999742336789313416"><ph name="DISPLAY_NAME"/> היא הפעלה ציבורית המנוהלת על ידי <ph name="DOMAIN"/></translation>
+<translation id="7029814467594812963">צא מההפעלה</translation>
+<translation id="8454013096329229812">Wi-Fi מופעל.</translation>
+<translation id="4872237917498892622">Alt + חיפוש או Shift</translation>
+<translation id="2983818520079887040">הגדרות...</translation>
+<translation id="1717216362413677834">מצב מעגן</translation>
+<translation id="8927026611342028580">נשלחה בקשה לחיבור</translation>
+<translation id="8300849813060516376">OTASP נכשל</translation>
+<translation id="2792498699870441125">Alt + חיפוש</translation>
+<translation id="8660803626959853127">מסנכרן <ph name="COUNT"/> קבצים</translation>
+<translation id="3709443003275901162">‎9+‎</translation>
+<translation id="639644700271529076">CAPS LOCK כבוי</translation>
+<translation id="6248847161401822652">לחץ פעמיים על Control Shift Q כדי לצאת.</translation>
+<translation id="6267036997247669271"><ph name="NAME"/>: מפעיל...</translation>
+<translation id="1391854757121130358">ייתכן שכבר השתמשת בכל הקצבת הנתונים לנייד.</translation>
+<translation id="5413208160176941586">משתמש המנוהל באופן מקומי</translation>
+<translation id="1059194134494239015"><ph name="DISPLAY_NAME"/>: <ph name="RESOLUTION"/></translation>
+<translation id="4864165860509564259">מיקום המפעיל</translation>
+<translation id="7593891976182323525">חיפוש או Shift</translation>
+<translation id="7649070708921625228">עזרה</translation>
+<translation id="3050422059534974565">CAPS LOCK פועל.
+הקש על 'חיפוש' או על Shift כדי לבטל.</translation>
+<translation id="397105322502079400">מחשב...</translation>
+<translation id="158849752021629804">יש צורך ברשת ביתית</translation>
+<translation id="6857811139397017780">הפעל את <ph name="NETWORKSERVICE"/></translation>
+<translation id="5864471791310927901">חיפוש DHCP נכשל</translation>
+<translation id="5812035014844949013">פלט</translation>
+<translation id="6692173217867674490">משפט-סיסמה גרוע</translation>
+<translation id="6165508094623778733">למידע נוסף</translation>
+<translation id="9046895021617826162">החיבור נכשל</translation>
+<translation id="973896785707726617">הפעילות הזו באתר תסתיים בעוד <ph name="SESSION_TIME_REMAINING"/>. תנותק אוטומטית.</translation>
+<translation id="8372369524088641025">מקש WEP גרוע</translation>
+<translation id="6636709850131805001">מצב לא מזוהה</translation>
+<translation id="3573179567135747900">שנה בחזרה ל&quot;<ph name="FROM_LOCALE"/>&quot; (דורש הפעלה מחדש)</translation>
+<translation id="8103386449138765447">הודעות SMS‏: <ph name="MESSAGE_COUNT"/></translation>
+<translation id="5045002648206642691">הגדרות Google Drive...</translation>
+<translation id="1510238584712386396">מפעיל</translation>
+<translation id="7209101170223508707">CAPS LOCK פועל.
+הקש על Alt + 'חיפוש' או Shift כדי לבטל.</translation>
+<translation id="8940956008527784070">סוללה חלשה (‎<ph name="PERCENTAGE"/>%‎)</translation>
+<translation id="5102001756192215136">נותרו <ph name="HOUR"/> <ph name="MINUTE"/></translation>
+<translation id="520760366042891468">משתף את השליטה במסך שלך באמצעות Hangouts.</translation>
+<translation id="8000066093800657092">אין רשת</translation>
+<translation id="4015692727874266537">הכנס חשבון אחר...</translation>
+<translation id="5941711191222866238">מזער</translation>
+<translation id="6911468394164995108">הצטרף לרשת אחרת...</translation>
+<translation id="412065659894267608"><ph name="HOUR"/> שעות ו-<ph name="MINUTE"/> דקות עד לטעינה מלאה</translation>
+<translation id="6359806961507272919">SMS מאת <ph name="PHONE_NUMBER"/> </translation>
+<translation id="1244147615850840081">ספק</translation>
+</translationbundle> \ No newline at end of file
diff --git a/chromium/ash/strings/ash_strings_ja.xtb b/chromium/ash/strings/ash_strings_ja.xtb
new file mode 100644
index 00000000000..c98ccb31fe1
--- /dev/null
+++ b/chromium/ash/strings/ash_strings_ja.xtb
@@ -0,0 +1,201 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="ja">
+<translation id="3595596368722241419">バッテリー残量: 満</translation>
+<translation id="5250713215130379958">ランチャーを自動的に隠す</translation>
+<translation id="7814236020522506259"><ph name="HOUR"/> <ph name="MINUTE"/></translation>
+<translation id="7880025619322806991">ポータル状態</translation>
+<translation id="30155388420722288">オーバーフロー ボタン</translation>
+<translation id="5571066253365925590">Bluetooth オン</translation>
+<translation id="9074739597929991885">Bluetooth</translation>
+<translation id="2268130516524549846">Bluetooth オフ</translation>
+<translation id="3775358506042162758">マルチ ログインできるアカウントは 3 つまでです。</translation>
+<translation id="370649949373421643">Wi-Fi を有効にする</translation>
+<translation id="3626281679859535460">輝度</translation>
+<translation id="8054466585765276473">バッテリーの残り時間を計算しています。</translation>
+<translation id="7982789257301363584">ネットワーク</translation>
+<translation id="5565793151875479467">プロキシ...</translation>
+<translation id="938582441709398163">キーボード オーバーレイ</translation>
+<translation id="4387004326333427325">認証証明書がリモートで拒否されました</translation>
+<translation id="6979158407327259162">Google ドライブ</translation>
+<translation id="6943836128787782965">HTTP を取得できませんでした</translation>
+<translation id="2297568595583585744">ステータス トレイ</translation>
+<translation id="1661867754829461514">PIN がありません</translation>
+<translation id="4508225577814909926"><ph name="NAME"/>: 接続しています...</translation>
+<translation id="4237016987259239829">ネットワーク接続エラー</translation>
+<translation id="2946640296642327832">Bluetooth を有効にする</translation>
+<translation id="6459472438155181876"><ph name="DISPLAY_NAME"/> へ画面を拡張しています</translation>
+<translation id="8206859287963243715">携帯電話</translation>
+<translation id="6596816719288285829">IP アドレス</translation>
+<translation id="4508265954913339219">起動に失敗しました</translation>
+<translation id="3621712662352432595">オーディオ設定</translation>
+<translation id="1812696562331527143">入力方法を <ph name="INPUT_METHOD_ID"/>*(<ph name="BEGIN_LINK"/>サードパーティ<ph name="END_LINK"/>)に変更しました。
+切り替えるには Shift+Alt キーを押します。</translation>
+<translation id="2127372758936585790">低電力の充電器</translation>
+<translation id="3846575436967432996">利用可能なネットワーク情報がありません</translation>
+<translation id="3026237328237090306">モバイル データをセットアップ</translation>
+<translation id="785750925697875037">モバイル アカウントを表示</translation>
+<translation id="153454903766751181">セルラー モデムを初期化しています...</translation>
+<translation id="4628814525959230255">ハングアウトを介して画面の制御を <ph name="HELPER_NAME"/> さんと共有しています。</translation>
+<translation id="8343941333792395995"><ph name="DISPLAY_NAME"/> は回転されました</translation>
+<translation id="7864539943188674973">Bluetooth を無効にする</translation>
+<translation id="939252827960237676">スクリーンショットを保存できませんでした</translation>
+<translation id="3126069444801937830">再起動して更新</translation>
+<translation id="2268813581635650749">すべてログアウト</translation>
+<translation id="735745346212279324">VPN が切断されました</translation>
+<translation id="7320906967354320621">待機中</translation>
+<translation id="6303423059719347535">バッテリー残量: <ph name="PERCENTAGE"/>%</translation>
+<translation id="15373452373711364">大きいマウス カーソル</translation>
+<translation id="2778346081696727092">入力されたユーザー名またはパスワードで認証できませんでした</translation>
+<translation id="3294437725009624529">ゲスト</translation>
+<translation id="8190698733819146287">言語と入力方法をカスタマイズ...</translation>
+<translation id="2903907270192926896">入力</translation>
+<translation id="8676770494376880701">低電力の充電器に接続されています</translation>
+<translation id="7170041865419449892">圏外</translation>
+<translation id="4804818685124855865">切断</translation>
+<translation id="2544853746127077729">認証証明書がネットワークによって拒否されました</translation>
+<translation id="5222676887888702881">ログアウト</translation>
+<translation id="2688477613306174402">設定</translation>
+<translation id="1272079795634619415">中止</translation>
+<translation id="4957722034734105353">詳細...</translation>
+<translation id="2964193600955408481">Wi-Fi を無効にする</translation>
+<translation id="811680302244032017">デバイスを追加...</translation>
+<translation id="4279490309300973883">ミラーリングしています</translation>
+<translation id="2509468283778169019">Caps Lock がオンになっています</translation>
+<translation id="3892641579809465218">内蔵ディスプレイ</translation>
+<translation id="7823564328645135659">設定の同期後に言語が「<ph name="FROM_LOCALE"/>」から「<ph name="TO_LOCALE"/>」に変更されました。</translation>
+<translation id="3368922792935385530">接続済み</translation>
+<translation id="8340999562596018839">音声フィードバック</translation>
+<translation id="8654520615680304441">Wi-Fi をオンにしています...</translation>
+<translation id="5825747213122829519">入力方法を <ph name="INPUT_METHOD_ID"/> に変更しました。
+切り替えるには Shift+Alt キーを押します。</translation>
+<translation id="2562916301614567480">プライベート ネットワーク</translation>
+<translation id="6549021752953852991">利用可能なセルラー ネットワークがありません</translation>
+<translation id="4379753398862151997">Dear Monitor, it's not working out between us.(このモニターはサポートされていません)</translation>
+<translation id="6426039856985689743">モバイル データを無効にする</translation>
+<translation id="3087734570205094154">下</translation>
+<translation id="3742055079367172538">スクリーンショット撮影完了</translation>
+<translation id="8878886163241303700">画面を拡張しています</translation>
+<translation id="5271016907025319479">VPN が設定されていません。</translation>
+<translation id="372094107052732682">終了するには Ctrl+Shift+Q を 2 回押してください。</translation>
+<translation id="6803622936009808957">サポートされている解像度が見つからなかったため、ディスプレイをミラーリングできませんでした。代わりに拡張デスクトップ モードに切り替えました。</translation>
+<translation id="1480041086352807611">デモ モード</translation>
+<translation id="3626637461649818317">残り <ph name="PERCENTAGE"/>%</translation>
+<translation id="9089416786594320554">入力方法</translation>
+<translation id="6247708409970142803"><ph name="PERCENTAGE"/>%</translation>
+<translation id="2614835198358683673">この充電器がオンになっている間は、Chromebook が充電されない可能性があります。正規の充電器の使用をご検討ください。</translation>
+<translation id="1895658205118569222">シャットダウン</translation>
+<translation id="4430019312045809116">音量</translation>
+<translation id="4442424173763614572">DNS を検索できませんでした</translation>
+<translation id="6356500677799115505">バッテリー残量: 満(充電中)</translation>
+<translation id="7874779702599364982">携帯電話ネットワークを検索しています...</translation>
+<translation id="583281660410589416">不明</translation>
+<translation id="1383876407941801731">検索</translation>
+<translation id="7468789844759750875">データを追加購入するには、<ph name="NAME"/> の有効化ポータルにアクセスしてください。</translation>
+<translation id="3901991538546252627"><ph name="NAME"/> に接続しています</translation>
+<translation id="2204305834655267233">ネットワーク情報</translation>
+<translation id="1621499497873603021">バッテリーが空になるまであと: <ph name="TIME_LEFT"/></translation>
+<translation id="5980301590375426705">ゲスト セッションを終了</translation>
+<translation id="4471417012762451363">バッテリー残量: <ph name="PERCENTAGE"/>%(充電中)</translation>
+<translation id="8308637677604853869">前のメニュー</translation>
+<translation id="4666297444214622512">別のアカウントにログインすることはできません。</translation>
+<translation id="1346748346194534595">右</translation>
+<translation id="1773212559869067373">認証証明書がローカルで拒否されました</translation>
+<translation id="8528322925433439945">モバイル...</translation>
+<translation id="7049357003967926684">アソシエーション</translation>
+<translation id="8428213095426709021">設定</translation>
+<translation id="2372145515558759244">アプリの同期...</translation>
+<translation id="7256405249507348194">不明なエラー: <ph name="DESC"/></translation>
+<translation id="7925247922861151263">AAA を確認できませんでした</translation>
+<translation id="8456362689280298700">フル充電まで: <ph name="HOUR"/>:<ph name="MINUTE"/></translation>
+<translation id="5787281376604286451">音声フィードバックが有効です。
+無効にするには Ctrl+Alt+Z を押してください。</translation>
+<translation id="4479639480957787382">イーサネット</translation>
+<translation id="6312403991423642364">ネットワークが不明なためエラーが発生しました</translation>
+<translation id="1467432559032391204">左</translation>
+<translation id="5543001071567407895">SMS</translation>
+<translation id="2354174487190027830"><ph name="NAME"/> を有効にしています</translation>
+<translation id="8814190375133053267">Wi-Fi</translation>
+<translation id="1398853756734560583">最大化</translation>
+<translation id="2692809339924654275"><ph name="BLUETOOTH"/>: 接続しています...</translation>
+<translation id="252373100621549798">不明なディスプレイ</translation>
+<translation id="1882897271359938046"><ph name="DISPLAY_NAME"/> へミラーリング</translation>
+<translation id="2727977024730340865">低電力の充電器に接続しています。バッテリーが充電されない可能性があります。</translation>
+<translation id="3784455785234192852">ロック</translation>
+<translation id="2805756323405976993">アプリ</translation>
+<translation id="8871072142849158571"><ph name="DISPLAY_NAME"/> のサイズが <ph name="RESOLUTION"/> に変更されました</translation>
+<translation id="1512064327686280138">起動失敗</translation>
+<translation id="5097002363526479830">ネットワーク「<ph name="NAME"/>」に接続できませんでした: <ph name="DETAILS"/></translation>
+<translation id="1850504506766569011">Wi-Fi が無効になりました。</translation>
+<translation id="8132793192354020517"><ph name="NAME"/> に接続しました</translation>
+<translation id="7052914147756339792">壁紙を設定...</translation>
+<translation id="8678698760965522072">オンライン状態</translation>
+<translation id="2532589005999780174">ハイコントラスト モード</translation>
+<translation id="1119447706177454957">内部エラー</translation>
+<translation id="3019353588588144572">バッテリーがフル充電されるまであと: <ph name="TIME_REMAINING"/></translation>
+<translation id="3473479545200714844">画面拡大鏡</translation>
+<translation id="7005812687360380971">失敗</translation>
+<translation id="882279321799040148">クリックして表示</translation>
+<translation id="5045550434625856497">パスワードが正しくありません</translation>
+<translation id="1602076796624386989">モバイル データを有効にする</translation>
+<translation id="6981982820502123353">アクセシビリティ</translation>
+<translation id="3157931365184549694">復元</translation>
+<translation id="4274292172790327596">不明なエラー</translation>
+<translation id="4032485810211612751"><ph name="HOURS"/>:<ph name="MINUTES"/>:<ph name="SECONDS"/></translation>
+<translation id="225680501294068881">デバイスをスキャンしています...</translation>
+<translation id="5597451508971090205"><ph name="DATE"/> (<ph name="SHORT_WEEKDAY"/>)</translation>
+<translation id="4448844063988177157">Wi-Fi ネットワークを検出しています...</translation>
+<translation id="8401662262483418323">'<ph name="NAME"/>' への接続に失敗しました: <ph name="DETAILS"/>
+サーバー メッセージ: <ph name="SERVER_MSG"/></translation>
+<translation id="2475982808118771221">エラーが発生しました</translation>
+<translation id="7229570126336867161">EVDO が必要です</translation>
+<translation id="2999742336789313416"><ph name="DISPLAY_NAME"/> は <ph name="DOMAIN"/> が管理する公開セッションです</translation>
+<translation id="7029814467594812963">セッションを終了</translation>
+<translation id="8454013096329229812">Wi-Fi が有効になりました。</translation>
+<translation id="4872237917498892622">Alt+ 検索/Shift</translation>
+<translation id="2983818520079887040">設定...</translation>
+<translation id="1717216362413677834">ドック モード</translation>
+<translation id="8927026611342028580">接続をリクエスト済み</translation>
+<translation id="8300849813060516376">OTASP に失敗しました</translation>
+<translation id="2792498699870441125">Alt+ 検索</translation>
+<translation id="8660803626959853127"><ph name="COUNT"/> 個のファイルを同期中</translation>
+<translation id="3709443003275901162">9+</translation>
+<translation id="639644700271529076">CapsLock 機能はオフになっています</translation>
+<translation id="6248847161401822652">終了するには Ctrl+Shift+Q を 2 回押してください。</translation>
+<translation id="6267036997247669271"><ph name="NAME"/>: 有効にしています...</translation>
+<translation id="1391854757121130358">モバイル データの使用量上限に達した可能性があります。</translation>
+<translation id="5413208160176941586">ローカルの管理対象ユーザー</translation>
+<translation id="1059194134494239015"><ph name="DISPLAY_NAME"/>: <ph name="RESOLUTION"/></translation>
+<translation id="4864165860509564259">ランチャーの位置</translation>
+<translation id="7593891976182323525">検索/Shift</translation>
+<translation id="7649070708921625228">ヘルプ</translation>
+<translation id="3050422059534974565">CapsLock がオンになっています。
+検索/Shift キーを押すと解除されます。</translation>
+<translation id="397105322502079400">計算しています...</translation>
+<translation id="158849752021629804">ホーム ネットワークが必要です</translation>
+<translation id="6857811139397017780"><ph name="NETWORKSERVICE"/> を有効にする</translation>
+<translation id="5864471791310927901">DHCP を検索できませんでした</translation>
+<translation id="5812035014844949013">出力</translation>
+<translation id="6692173217867674490">パスフレーズが正しくありません</translation>
+<translation id="6165508094623778733">詳しく見る</translation>
+<translation id="9046895021617826162">接続に失敗しました</translation>
+<translation id="973896785707726617">このセッションはあと <ph name="SESSION_TIME_REMAINING"/>で終了します。終了すると、自動的にログアウトします。</translation>
+<translation id="8372369524088641025">WEP キーが正しくありません</translation>
+<translation id="6636709850131805001">不明な状態</translation>
+<translation id="3573179567135747900">「<ph name="FROM_LOCALE"/>」に戻します(再起動が必要です)</translation>
+<translation id="8103386449138765447">SMS メッセージ: <ph name="MESSAGE_COUNT"/></translation>
+<translation id="5045002648206642691">Google ドライブの設定...</translation>
+<translation id="1510238584712386396">ランチャー</translation>
+<translation id="7209101170223508707">CapsLock がオンになっています。
+Alt+ 検索/Shift キーを押すと解除されます。</translation>
+<translation id="8940956008527784070">バッテリー残量: 少(<ph name="PERCENTAGE"/>%)</translation>
+<translation id="5102001756192215136">残り時間: <ph name="HOUR"/>:<ph name="MINUTE"/></translation>
+<translation id="520760366042891468">ハングアウトを介して画面の制御を共有しています。</translation>
+<translation id="8000066093800657092">ネットワーク接続なし</translation>
+<translation id="4015692727874266537">別のアカウントにログイン...</translation>
+<translation id="5941711191222866238">最小化</translation>
+<translation id="6911468394164995108">他のネットワークに接続...</translation>
+<translation id="412065659894267608">フル充電まで <ph name="HOUR"/> 時間 <ph name="MINUTE"/> 分</translation>
+<translation id="6359806961507272919"><ph name="PHONE_NUMBER"/> の SMS</translation>
+<translation id="1244147615850840081">通信会社</translation>
+</translationbundle> \ No newline at end of file
diff --git a/chromium/ash/strings/ash_strings_kn.xtb b/chromium/ash/strings/ash_strings_kn.xtb
new file mode 100644
index 00000000000..1b3adb9487e
--- /dev/null
+++ b/chromium/ash/strings/ash_strings_kn.xtb
@@ -0,0 +1,201 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="kn">
+<translation id="3595596368722241419">ಬ್ಯಾಟರಿ ಭರ್ತಿಯಾಗಿದೆ</translation>
+<translation id="5250713215130379958">ಲಾಂಚರ್ ಅನ್ನು ಸ್ವಯಂಮರೆಮಾಡಿ</translation>
+<translation id="7814236020522506259"><ph name="HOUR"/> ಮತ್ತು <ph name="MINUTE"/></translation>
+<translation id="7880025619322806991">ಪೋರ್ಟಲ್ ಸ್ಥಿತಿ</translation>
+<translation id="30155388420722288">ಅತ್ಯಧಿಕ ಬಟನ್</translation>
+<translation id="5571066253365925590">Bluetooth ಸಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆ</translation>
+<translation id="9074739597929991885">Bluetooth</translation>
+<translation id="2268130516524549846">Bluetooth ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆ</translation>
+<translation id="3775358506042162758">ಬಹು ಸೈನ್-ಇನ್‌ನಲ್ಲಿ ನೀವು ಮೂರರಷ್ಟು ಖಾತೆಗಳನ್ನು ಮಾತ್ರ ಹೊಂದಬಹುದು.</translation>
+<translation id="370649949373421643">Wi-Fi ಅನ್ನು ಸಕ್ರಿಯಗೊಳಿಸಿ</translation>
+<translation id="3626281679859535460">ಪ್ರಕಾಶಮಾನ</translation>
+<translation id="8054466585765276473">ಬ್ಯಾಟರಿ ಸಮಯವನ್ನು ಲೆಕ್ಕಾಚಾರ ಮಾಡಲಾಗುತ್ತಿದೆ.</translation>
+<translation id="7982789257301363584">ನೆಟ್‌ವರ್ಕ್</translation>
+<translation id="5565793151875479467">ಪ್ರಾಕ್ಸಿ...</translation>
+<translation id="938582441709398163">ಕೀಬೋರ್ಡ್ ಒವರ್‌ಲೇ</translation>
+<translation id="4387004326333427325">ದೃಢೀಕರಣ ಪ್ರಮಾಣಪತ್ರವನ್ನು ರಿಮೋಟ್ ಆಗಿ ತಿರಸ್ಕರಿಸಲಾಗಿದೆ</translation>
+<translation id="6979158407327259162">Google ಡ್ರೈವ್‌‌</translation>
+<translation id="6943836128787782965">HTTP ವಿಫಲವಾಗಿದೆ</translation>
+<translation id="2297568595583585744">ಸ್ಥಿತಿ ಟ್ರೆ</translation>
+<translation id="1661867754829461514">PIN ಕಾಣೆಯಾಗಿದೆ</translation>
+<translation id="4508225577814909926"><ph name="NAME"/>: ಸಂಪರ್ಕಿಸಲಾಗುತ್ತಿದೆ...</translation>
+<translation id="4237016987259239829">ನೆಟ್‌ವರ್ಕ್ ಸಂಪರ್ಕದ ದೋಷ</translation>
+<translation id="2946640296642327832">bluetooth ಸಕ್ರಿಯಗೊಳಿಸಿ</translation>
+<translation id="6459472438155181876"><ph name="DISPLAY_NAME"/> ಗೆ ಪರದೆಯನ್ನು ವಿಸ್ತರಿಸಲಾಗುತ್ತಿದೆ</translation>
+<translation id="8206859287963243715">ಸೆಲ್ಯುಲಾರ್</translation>
+<translation id="6596816719288285829">IP ವಿಳಾಸ</translation>
+<translation id="4508265954913339219">ಸಕ್ರಿಯಗೊಳಿಸುವಿಕೆ ವಿಫಲವಾಗಿದೆ</translation>
+<translation id="3621712662352432595">ಆಡಿಯೊ ಸೆಟ್ಟಿಂಗ್‌ಗಳು</translation>
+<translation id="1812696562331527143">ನಿಮ್ಮ ಇನ್‌ಪುಟ್ ವಿಧಾನವನ್ನು <ph name="INPUT_METHOD_ID"/>* ಗೆ ಬದಲಾಯಿಸಲಾಗಿದೆ (<ph name="BEGIN_LINK"/>3ನೇ ವ್ಯಕ್ತಿ<ph name="END_LINK"/>).
+ಬದಲಿಸಲು Shift + Alt ಅನ್ನು ಒತ್ತಿರಿ.</translation>
+<translation id="2127372758936585790">ಕಡಿಮೆ ವಿದ್ಯುತ್ ಚಾರ್ಜರ್</translation>
+<translation id="3846575436967432996">ನೆಟ್‌ವರ್ಕ್ ಮಾಹಿತಿ ಲಭ್ಯವಿಲ್ಲ</translation>
+<translation id="3026237328237090306">ಮೊಬೈಲ್ ಡೇಟಾವನ್ನು ಹೊಂದಿಸಿ</translation>
+<translation id="785750925697875037">ಮೊಬೈಲ್ ಖಾತೆಯನ್ನು ವೀಕ್ಷಿಸಿ</translation>
+<translation id="153454903766751181">ಸೆಲ್ಯುಲಾರ್ ಮೋಡೆಮ್ ಅನ್ನು ಪ್ರಾರಂಭಿಸಲಾಗುತ್ತಿದೆ...</translation>
+<translation id="4628814525959230255">Hangouts ಮೂಲಕ <ph name="HELPER_NAME"/> ಅವರೊಂದಿಗೆ ನಿಮ್ಮ ಪರದೆಯ ನಿಯಂತ್ರಣವನ್ನು ಹಂಚಲಾಗುತ್ತಿದೆ.</translation>
+<translation id="8343941333792395995"><ph name="DISPLAY_NAME"/> ಅನ್ನು ತಿರುಗಿಸಲಾಗಿದೆ</translation>
+<translation id="7864539943188674973">bluetooth ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಿ</translation>
+<translation id="939252827960237676">ಸ್ಕ್ರೀನ್‌ಶಾಟ್ ಉಳಿಸುವಲ್ಲಿ ವಿಫಲವಾಗಿದೆ</translation>
+<translation id="3126069444801937830">ನವೀಕರಿಸಲು ಮರುಪ್ರಾರಂಭಿಸಿ</translation>
+<translation id="2268813581635650749">ಎಲ್ಲವನ್ನೂ ಸೈನ್ ಔಟ್ ಮಾಡಿ</translation>
+<translation id="735745346212279324">VPN ಸಂಪರ್ಕ ಕಡಿತಗೊಳಿಸಲಾಗಿದೆ</translation>
+<translation id="7320906967354320621">ಕಾರ್ಯನಿರತವಾಗಿಲ್ಲ</translation>
+<translation id="6303423059719347535">ಬ್ಯಾಟರಿ <ph name="PERCENTAGE"/>% ಪೂರ್ಣವಾಗಿದೆ</translation>
+<translation id="15373452373711364">ದೊಡ್ಡ ಮೌಸ್ ಕರ್ಸರ್</translation>
+<translation id="2778346081696727092">ಒದಗಿಸಲಾದ ಬಳಕೆದಾರಹೆಸರು ಅಥವಾ ಪಾಸ್‌ವರ್ಡ್‌ನೊಂದಿಗಿನ ದೃಢೀಕರಣವು ವಿಫಲಗೊಂಡಿದೆ</translation>
+<translation id="3294437725009624529">ಅತಿಥಿ</translation>
+<translation id="8190698733819146287">ಭಾಷೆಗಳು ಮತ್ತು ಇನ್‌ಪುಟ್ ಅನ್ನು ಕಸ್ಟಮೈಸ್ ಮಾಡಿ...</translation>
+<translation id="2903907270192926896">ಇನ್‌ಪುಟ್</translation>
+<translation id="8676770494376880701">ಕಡಿಮೆ ವಿದ್ಯುತ್ ಚಾರ್ಜರ್ ಸಂಪರ್ಕಪಡಿಸಲಾಗಿದೆ</translation>
+<translation id="7170041865419449892">ವ್ಯಾಪ್ತಿಯ ಹೊರಗೆ</translation>
+<translation id="4804818685124855865">ಡಿಸ್‌ಕನೆಕ್ಟ್</translation>
+<translation id="2544853746127077729">ದೃಢೀಕರಣ ಪ್ರಮಾಣಪತ್ರವನ್ನು ನೆಟ್‌ವರ್ಕ್‌ನಿಂದ ತಿರಸ್ಕರಿಸಲಾಗಿದೆ</translation>
+<translation id="5222676887888702881">ಸೈನ್ ಔಟ್</translation>
+<translation id="2688477613306174402">ಕಾನ್ಫಿಗರೇಶನ್</translation>
+<translation id="1272079795634619415">ನಿಲ್ಲಿಸು</translation>
+<translation id="4957722034734105353">ಇನ್ನಷ್ಟು ತಿಳಿದುಕೊಳ್ಳಿ...</translation>
+<translation id="2964193600955408481">Wi-Fi ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಿ</translation>
+<translation id="811680302244032017">ಸಾಧನ ಸೇರಿಸಿ...</translation>
+<translation id="4279490309300973883">ಪ್ರತಿಬಿಂಬಿಸುವಿಕೆ</translation>
+<translation id="2509468283778169019">CAPS LOCK ಆನ್ ಆಗಿದೆ</translation>
+<translation id="3892641579809465218">ಆಂತರಿಕ ಪ್ರದರ್ಶನ</translation>
+<translation id="7823564328645135659">ನಿಮ್ಮ ಸೆಟ್ಟಿಂಗ್‌ಗಳನ್ನು ಸಿಂಕ್ ಮಾಡಿದ ನಂತರ ಭಾಷೆಯನ್ನು &quot;<ph name="FROM_LOCALE"/>&quot; ನಿಂದ &quot;<ph name="TO_LOCALE"/>&quot; ಗೆ ಬದಲಾಯಿಸಲಾಗಿದೆ.</translation>
+<translation id="3368922792935385530">ಸಂಪರ್ಕಿಸಲಾಗಿದೆ</translation>
+<translation id="8340999562596018839">ಮಾತನಾಡುವ ಪ್ರತಿಕ್ರಿಯೆ</translation>
+<translation id="8654520615680304441">Wi-Fi ಆನ್ ಮಾಡಿ...</translation>
+<translation id="5825747213122829519"><ph name="INPUT_METHOD_ID"/> ಗೆ ನಿಮ್ಮ ಇನ್‌ಪುಟ್ ವಿಧಾನವನ್ನು ಬದಲಾಯಿಸಲಾಗಿದೆ.
+ ಬದಲಿಸಲು Shift + Alt ಕೀಲಿಯನ್ನು ಒತ್ತಿರಿ.</translation>
+<translation id="2562916301614567480">ಖಾಸಗಿ ನೆಟ್‌ವರ್ಕ್‌</translation>
+<translation id="6549021752953852991">ಯಾವುದೇ ಸೆಲ್ಯುಲಾರ್ ನೆಟ್‌ವರ್ಕ್ ಲಭ್ಯವಿಲ್ಲ</translation>
+<translation id="4379753398862151997">ಆತ್ಮೀಯ ಮಾನಿಟರ್, ನಮ್ಮ ನಡುವೆ ಸರಿಹೊಂದುತ್ತಿಲ್ಲ. (ಆ ಮಾನಿಟರ್ ಬೆಂಬಲಿಸುವುದಿಲ್ಲ)</translation>
+<translation id="6426039856985689743">ಮೊಬೈಲ್ ಡೇಟಾವನ್ನು ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಿ</translation>
+<translation id="3087734570205094154">ಕೆಳಗೆ</translation>
+<translation id="3742055079367172538">ಸ್ಕ್ರಿನ್‌ಶಾಟ್ ತೆಗೆದುಕೊಳ್ಳಲಾಗಿದೆ</translation>
+<translation id="8878886163241303700">ಪರದೆಯನ್ನು ವಿಸ್ತರಿಸಲಾಗುತ್ತಿದೆ</translation>
+<translation id="5271016907025319479">VPN ಕಾನ್ಫಿಗರ್ ಮಾಡಲಾಗಿಲ್ಲ.</translation>
+<translation id="372094107052732682">ತೊರೆಯಲು Ctrl+Shift+Q ಅನ್ನು ಎರಡು ಬಾರಿ ಒತ್ತಿರಿ.</translation>
+<translation id="6803622936009808957">ಯಾವುದೇ ಬೆಂಬಲಿತ ಪರಿಹಾರಗಳು ಕಂಡುಬರದ ಕಾರಣ ಪ್ರದರ್ಶನಗಳನ್ನು ಪ್ರತಿಬಿಂಬಿಸಲಾಗಲಿಲ್ಲ. ಬದಲಿಗೆ ವಿಸ್ತರಿತ ಡೆಸ್ಕ್‌ಟಾಪ್ ಅನ್ನು ನಮೂದಿಸಲಾಗಿದೆ.</translation>
+<translation id="1480041086352807611">ಡೆಮೋ ಮೋಡ್</translation>
+<translation id="3626637461649818317"><ph name="PERCENTAGE"/>% ಉಳಿದಿದೆ</translation>
+<translation id="9089416786594320554">ಇನ್‌ಪುಟ್ ವಿಧಾನಗಳು</translation>
+<translation id="6247708409970142803"><ph name="PERCENTAGE"/>%</translation>
+<translation id="2614835198358683673">ನಿಮ್ಮ Chromebook ಆನ್ ಆಗಿರುವಾಗ ಅದು ಚಾರ್ಜ್ ಆಗುವುದಿಲ್ಲ. ಅಧಿಕೃತ ಚಾರ್ಜರ್ ಬಳಸಿ.</translation>
+<translation id="1895658205118569222">ಶಟ್‌ಡೌನ್</translation>
+<translation id="4430019312045809116">ವಾಲ್ಯೂಮ್</translation>
+<translation id="4442424173763614572">DNS ಲುಕಪ್ ವಿಫಲವಾಗಿದೆ</translation>
+<translation id="6356500677799115505">ಬ್ಯಾಟರಿ ಪೂರ್ಣಗೊಂಡಿದೆ ಮತ್ತು ಚಾರ್ಜ್ ಆಗುತ್ತಿದೆ.</translation>
+<translation id="7874779702599364982">ಸೆಲ್ಯುಲರ್ ನೆಟ್‌ವರ್ಕ್‌ಗಳಿಗಾಗಿ ಹುಡುಕುತ್ತಿದೆ...</translation>
+<translation id="583281660410589416">ಅಜ್ಞಾತ</translation>
+<translation id="1383876407941801731">ಹುಡುಕಾಟ</translation>
+<translation id="7468789844759750875">ಹೆಚ್ಚಿನ ಡೇಟಾವನ್ನು ಖರೀದಿಸಲು <ph name="NAME"/> ಸಕ್ರಿಯ ಪೋರ್ಟಲ್ ಅನ್ನು ಭೇಟಿ ಮಾಡಿ.</translation>
+<translation id="3901991538546252627"><ph name="NAME"/> ಗೆ ಸಂಪರ್ಕಿಸಲಾಗುತ್ತಿದೆ</translation>
+<translation id="2204305834655267233">ನೆಟ್‌ವರ್ಕ್ ಮಾಹಿತಿ</translation>
+<translation id="1621499497873603021">ಬ್ಯಾಟರಿ ಖಾಲಿ ಆಗುವವರೆಗೆ ಉಳಿದಿರುವ ಸಮಯ, <ph name="TIME_LEFT"/></translation>
+<translation id="5980301590375426705">ಅತಿಥಿ ಸೆಶೆನ್‌ನಿಂದ ನಿರ್ಗಮಿಸು</translation>
+<translation id="4471417012762451363">ಬ್ಯಾಟರಿ <ph name="PERCENTAGE"/>% ಪೂರ್ಣವಾಗಿದೆ ಮತ್ತು ಚಾರ್ಜ್ ಆಗುತ್ತಿದೆ</translation>
+<translation id="8308637677604853869">ಹಿಂದಿನ ಮೆನು</translation>
+<translation id="4666297444214622512">ಮತ್ತೊಂದು ಖಾತೆಗೆ ಸೈನ್ ಇನ್ ಮಾಡಲಾಗುವುದಿಲ್ಲ.</translation>
+<translation id="1346748346194534595">ಬಲಕ್ಕೆ</translation>
+<translation id="1773212559869067373">ದೃಢೀಕರಣ ಪ್ರಮಾಣಪತ್ರವನ್ನು ಸ್ಥಳೀಯವಾಗಿ ತಿರಸ್ಕರಿಸಲಾಗಿದೆ</translation>
+<translation id="8528322925433439945">ಮೊಬೈಲ್...</translation>
+<translation id="7049357003967926684">ಅಸೋಸಿಯೇಷನ್</translation>
+<translation id="8428213095426709021">ಸೆಟ್ಟಿಂಗ್‌ಗಳು</translation>
+<translation id="2372145515558759244">ಅಪ್ಲಿಕೇಶನ್‌ಗಳನ್ನು ಸಿಂಕ್ ಮಾಡಲಾಗುತ್ತಿದೆ...</translation>
+<translation id="7256405249507348194">ಗುರುತಿಸದಿರುವ ದೋಷ: <ph name="DESC"/></translation>
+<translation id="7925247922861151263">AAA ಪರಿಶೀಲನೆ ವಿಫಲವಾಗಿದೆ</translation>
+<translation id="8456362689280298700"><ph name="HOUR"/>:<ph name="MINUTE"/> ಪೂರ್ಣಗೊಳ್ಳುವವರೆಗೆ</translation>
+<translation id="5787281376604286451">ಮಾತನಾಡುವ ಪ್ರತಿಕ್ರಿಯೆ ಸಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆ.
+ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಲು Ctrl+Alt+Z ಒತ್ತಿರಿ.</translation>
+<translation id="4479639480957787382">ಈಥರ್ನೆಟ್</translation>
+<translation id="6312403991423642364">ಅಜ್ಞಾತ ನೆಟ್‌ವರ್ಕ್ ದೋಷ</translation>
+<translation id="1467432559032391204">ಎಡಕ್ಕೆ</translation>
+<translation id="5543001071567407895">SMS</translation>
+<translation id="2354174487190027830"><ph name="NAME"/> ಅನ್ನು ಸಕ್ರಿಯಗೊಳಿಸಲಾಗುತ್ತಿದೆ</translation>
+<translation id="8814190375133053267">Wi-Fi</translation>
+<translation id="1398853756734560583">ಗರಿಷ್ಠಗೊಳಿಸು</translation>
+<translation id="2692809339924654275"><ph name="BLUETOOTH"/>: ಸಂಪರ್ಕಿಸಲಾಗುತ್ತಿದೆ...</translation>
+<translation id="252373100621549798">ಅಜ್ಞಾತ ಪ್ರದರ್ಶನ</translation>
+<translation id="1882897271359938046"><ph name="DISPLAY_NAME"/> ಗೆ ಪ್ರತಿಬಿಂಬಿಸುತ್ತಿದೆ</translation>
+<translation id="2727977024730340865">ಕಡಿಮೆ ವಿದ್ಯುತ್ ಚಾರ್ಜರ್‌ಗೆ ಪ್ಲಗ್ ಮಾಡಲಾಗಿದೆ. ಬ್ಯಾಟರಿ ಚಾರ್ಜಿಂಗ್ ವಿಶ್ವಾಸಾರ್ಹವಾಗಿಲ್ಲದಿರಬಹುದು.</translation>
+<translation id="3784455785234192852">ಲಾಕ್ ಮಾಡಿ</translation>
+<translation id="2805756323405976993">ಅಪ್ಲಿಕೇಶನ್‌ಗಳು</translation>
+<translation id="8871072142849158571"><ph name="DISPLAY_NAME"/> ಅನ್ನು <ph name="RESOLUTION"/> ಗೆ ಮರುಗಾತ್ರಗೊಳಿಸಲಾಗಿದೆ</translation>
+<translation id="1512064327686280138">ಸಕ್ರಿಯಗೊಳಿಸುವಿಕೆ ವಿಫಲವಾಗಿದೆ</translation>
+<translation id="5097002363526479830">'<ph name="NAME"/>' ನೆಟ್‌ವರ್ಕ್‌ಗೆ ಸಂಪರ್ಕಿಸಲು ವಿಫಲವಾಗಿದೆ: <ph name="DETAILS"/></translation>
+<translation id="1850504506766569011">Wi-Fi ಆಫ್ ಮಾಡಲಾಗಿದೆ.</translation>
+<translation id="8132793192354020517"><ph name="NAME"/> ಗೆ ಸಂಪರ್ಕಗೊಂಡಿದೆ</translation>
+<translation id="7052914147756339792">ವಾಲ್‌ಪೇಪರ್ ಅನ್ನು ಹೊಂದಿಸಿ...</translation>
+<translation id="8678698760965522072">ಆನ್‌ಲೈನ್ ಸ್ಥಿತಿ</translation>
+<translation id="2532589005999780174">ಉನ್ನತ ಕಾಂಟ್ರಾಸ್ಟ್ ಮೋಡ್</translation>
+<translation id="1119447706177454957">ಆಂತರಿಕ ದೋಷ</translation>
+<translation id="3019353588588144572">ಬ್ಯಾಟರಿ ಪೂರ್ಣವಾಗಿ ಚಾರ್ಜ್ ಆಗುವವರೆಗೆ ಉಳಿದಿರುವ ಸಮಯ, <ph name="TIME_REMAINING"/></translation>
+<translation id="3473479545200714844">ಪರದೆ ವರ್ಧಕ</translation>
+<translation id="7005812687360380971">ವೈಫಲ್ಯ</translation>
+<translation id="882279321799040148">ವೀಕ್ಷಿಸಲು ಕ್ಲಿಕ್ ಮಾಡಿ</translation>
+<translation id="5045550434625856497">ತಪ್ಪಾದ ಪಾಸ್‌ವರ್ಡ್</translation>
+<translation id="1602076796624386989">ಮೊಬೈಲ್ ಡೇಟಾವನ್ನು ಸಕ್ರಿಯಗೊಳಿಸಿ</translation>
+<translation id="6981982820502123353">ಪ್ರವೇಶಿಸುವಿಕೆ</translation>
+<translation id="3157931365184549694">ಪುನಃಸ್ಥಾಪನೆ</translation>
+<translation id="4274292172790327596">ಗುರುತಿಸದೆ ಇರುವ ದೋಷ</translation>
+<translation id="4032485810211612751"><ph name="HOURS"/>:<ph name="MINUTES"/>:<ph name="SECONDS"/></translation>
+<translation id="225680501294068881">ಸಾಧನಗಳಿಗಾಗಿ ಸ್ಕ್ಯಾನ್ ಮಾಡಲಾಗುತ್ತಿದೆ...</translation>
+<translation id="5597451508971090205"><ph name="SHORT_WEEKDAY"/>, <ph name="DATE"/></translation>
+<translation id="4448844063988177157">Wi-Fi ನೆಟ್‌ವರ್ಕ್‌ಗಳಿಗಾಗಿ ಹುಡುಕಲಾಗುತ್ತಿದೆ...</translation>
+<translation id="8401662262483418323">'<ph name="NAME"/>' ಗೆ ಸಂಪರ್ಕಪಡಿಸಲು ವಿಫಲವಾಗಿದೆ: <ph name="DETAILS"/>
+ಸರ್ವರ್ ಸಂದೇಶ: <ph name="SERVER_MSG"/></translation>
+<translation id="2475982808118771221">ದೋಷವೊಂದು ಕಾಣಿಸಿಕೊಂಡಿದೆ</translation>
+<translation id="7229570126336867161">EVDO ಅಗತ್ಯವಿದೆ</translation>
+<translation id="2999742336789313416"><ph name="DOMAIN"/> ಮೂಲಕ ನಿರ್ವಹಿಸಲಾದ ಸಾರ್ವಜನಿಕ ಸೆಶನ್ <ph name="DISPLAY_NAME"/> ಆಗಿದೆ</translation>
+<translation id="7029814467594812963">ಸೆಶನ್‌ನಿಂದ ನಿರ್ಗಮಿಸು</translation>
+<translation id="8454013096329229812">Wi-Fi ಆನ್ ಮಾಡಲಾಗಿದೆ.</translation>
+<translation id="4872237917498892622">Alt+ಹುಡುಕಾಟ ಅಥವಾ Shift</translation>
+<translation id="2983818520079887040">ಸೆಟ್ಟಿಂಗ್‌ಗಳು...</translation>
+<translation id="1717216362413677834">ಡಾಕ್ ಮೋಡ್</translation>
+<translation id="8927026611342028580">ಸಂಪರ್ಕವನ್ನು ವಿನಂತಿಸಲಾಗಿದೆ</translation>
+<translation id="8300849813060516376">OTASP ವಿಫಲವಾಗಿದೆ</translation>
+<translation id="2792498699870441125">Alt+ಹುಡುಕಾಟ</translation>
+<translation id="8660803626959853127"><ph name="COUNT"/> ಫೈಲ್‌(ಗಳ) ಅನ್ನು ಸಿಂಕ್ ಮಾಡಲಾಗುತ್ತಿದೆ</translation>
+<translation id="3709443003275901162">9+</translation>
+<translation id="639644700271529076">CAPS LOCK ಆಫ್ ಆಗಿದೆ</translation>
+<translation id="6248847161401822652">ತೊರೆಯಲು Control Shift Q ಅನ್ನು ಎರಡು ಬಾರಿ ಒತ್ತಿರಿ.</translation>
+<translation id="6267036997247669271"><ph name="NAME"/>: ಸಕ್ರಿಯಗೊಳಿಸಲಾಗುತ್ತಿದೆ...</translation>
+<translation id="1391854757121130358">ನಿಮ್ಮ ಮೊಬೈಲ್ ಡೇಟಾ ಭತ್ಯೆಯನ್ನು ನೀವು ಬಳಸಿರಬಹುದು.</translation>
+<translation id="5413208160176941586">ಸ್ಥಳೀಯವಾಗಿ ನಿರ್ವಹಿಸಲಾದ ಬಳಕೆದಾರ</translation>
+<translation id="1059194134494239015"><ph name="DISPLAY_NAME"/>: <ph name="RESOLUTION"/></translation>
+<translation id="4864165860509564259">ಲಾಂಚರ್ ಸ್ಥಾನ</translation>
+<translation id="7593891976182323525">ಹುಡುಕಾಟ ಅಥವಾ Shift</translation>
+<translation id="7649070708921625228">ಸಹಾಯ</translation>
+<translation id="3050422059534974565">CAPS LOCK ಆನ್ ಆಗಿದೆ.
+ರದ್ದುಗೊಳಿಸಲು ಹುಡುಕಾಟ ಅಥವಾ Shift ಕೀಲಿಯನ್ನು ಒತ್ತಿರಿ.</translation>
+<translation id="397105322502079400">ಎಣಿಸಲಾಗುತ್ತಿದೆ...</translation>
+<translation id="158849752021629804">ಹೋಮ್ ನೆಟ್‌ವರ್ಕ್ ಅಗತ್ಯವಿದೆ</translation>
+<translation id="6857811139397017780">ಸಕ್ರಿಯಗೊಳಿಸಿ<ph name="NETWORKSERVICE"/></translation>
+<translation id="5864471791310927901">DHCP ಲುಕಪ್ ವಿಫಲವಾಗಿದೆ</translation>
+<translation id="5812035014844949013">ಔಟ್‌ಪುಟ್</translation>
+<translation id="6692173217867674490">ಕೆಟ್ಟ ಪಾಸ್‌ಫ್ರೇಸ್</translation>
+<translation id="6165508094623778733">ಇನ್ನಷ್ಟು ತಿಳಿಯಿರಿ</translation>
+<translation id="9046895021617826162">ಸಂಪರ್ಕವು ವಿಫಲವಾಗಿದೆ</translation>
+<translation id="973896785707726617">ಈ ಸೆಷನ್ <ph name="SESSION_TIME_REMAINING"/> ರಲ್ಲಿ ಮುಕ್ತಾಯಗೊಳ್ಳುತ್ತದೆ. ನಿಮ್ಮನ್ನು ಸ್ವಯಂಚಾಲಿತವಾಗಿ ಸೈನ್ ಔಟ್ ಮಾಡಲಾಗುತ್ತದೆ.</translation>
+<translation id="8372369524088641025">ಕೆಟ್ಟ WEP ಕೀ</translation>
+<translation id="6636709850131805001">ಅಂಗೀಕಾರವಲ್ಲದ ರಾಜ್ಯ</translation>
+<translation id="3573179567135747900">&quot;<ph name="FROM_LOCALE"/>&quot; ಗೆ ಮರುಬದಲಾಯಿಸಿ (ಮರುಪ್ರಾರಂಭಿಸುವ ಅಗತ್ಯವಿದೆ)</translation>
+<translation id="8103386449138765447">SMS ಸಂದೇಶಗಳು: <ph name="MESSAGE_COUNT"/></translation>
+<translation id="5045002648206642691">Google ಡ್ರೈವ್ ಸೆಟ್ಟಿಂಗ್‌ಗಳು...</translation>
+<translation id="1510238584712386396">ಲಾಂಚರ್</translation>
+<translation id="7209101170223508707">CAPS LOCK ಆನ್ ಆಗಿದೆ.
+ರದ್ದುಗೊಳಿಸಲು Alt+ಹುಡುಕಾಟ ಅಥವಾ Shift ಕೀಲಿಯನ್ನು ಒತ್ತಿರಿ</translation>
+<translation id="8940956008527784070">ಬ್ಯಾಟರಿ ಕಡಿಮೆ (<ph name="PERCENTAGE"/>%)</translation>
+<translation id="5102001756192215136"><ph name="HOUR"/>:<ph name="MINUTE"/> ಉಳಿದಿದೆ</translation>
+<translation id="520760366042891468">Hangouts ಮೂಲಕ ನಿಮ್ಮ ಪರದೆಯ ನಿಯಂತ್ರಣವನ್ನು ಹಂಚಲಾಗುತ್ತಿದೆ.</translation>
+<translation id="8000066093800657092">ನೆಟ್‌ವರ್ಕ್ ಇಲ್ಲ</translation>
+<translation id="4015692727874266537">ಮತ್ತೊಂದು ಖಾತೆಗೆ ಸೈನ್ ಇನ್ ಮಾಡಿ...</translation>
+<translation id="5941711191222866238">ಕುಗ್ಗಿಸು</translation>
+<translation id="6911468394164995108">ಇತರರನ್ನು ಸೇರಿ...</translation>
+<translation id="412065659894267608">ಪೂರ್ಣವಾಗುವವರಗೆ <ph name="HOUR"/>ಗಂ <ph name="MINUTE"/>ನಿ</translation>
+<translation id="6359806961507272919"><ph name="PHONE_NUMBER"/> ನಿಂದ SMS</translation>
+<translation id="1244147615850840081">ವಾಹಕ</translation>
+</translationbundle> \ No newline at end of file
diff --git a/chromium/ash/strings/ash_strings_ko.xtb b/chromium/ash/strings/ash_strings_ko.xtb
new file mode 100644
index 00000000000..bb158ca6a3e
--- /dev/null
+++ b/chromium/ash/strings/ash_strings_ko.xtb
@@ -0,0 +1,201 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="ko">
+<translation id="3595596368722241419">배터리 충전 완료</translation>
+<translation id="5250713215130379958">실행기 자동으로 숨기기</translation>
+<translation id="7814236020522506259"><ph name="HOUR"/>시간 <ph name="MINUTE"/>분</translation>
+<translation id="7880025619322806991">포털 상태</translation>
+<translation id="30155388420722288">오버플로 버튼</translation>
+<translation id="5571066253365925590">블루투스를 사용함</translation>
+<translation id="9074739597929991885">블루투스</translation>
+<translation id="2268130516524549846">블루투스를 사용 안 함</translation>
+<translation id="3775358506042162758">멀티 로그인 시 계정은 최대 3개까지만 사용할 수 있습니다.</translation>
+<translation id="370649949373421643">Wi-Fi 사용</translation>
+<translation id="3626281679859535460">밝기</translation>
+<translation id="8054466585765276473">배터리 시간 계산 중</translation>
+<translation id="7982789257301363584">네트워크</translation>
+<translation id="5565793151875479467">프록시...</translation>
+<translation id="938582441709398163">키보드 오버레이</translation>
+<translation id="4387004326333427325">인증서가 원격으로 거부됨</translation>
+<translation id="6979158407327259162">Google 드라이브</translation>
+<translation id="6943836128787782965">HTTP 실패</translation>
+<translation id="2297568595583585744">상태 표시줄</translation>
+<translation id="1661867754829461514">PIN이 없습니다.</translation>
+<translation id="4508225577814909926"><ph name="NAME"/>: 연결하는 중...</translation>
+<translation id="4237016987259239829">네트워크 연결 오류</translation>
+<translation id="2946640296642327832">블루투스 사용</translation>
+<translation id="6459472438155181876">화면을 <ph name="DISPLAY_NAME"/>(으)로 확장</translation>
+<translation id="8206859287963243715">휴대전화</translation>
+<translation id="6596816719288285829">IP 주소</translation>
+<translation id="4508265954913339219">활성화 실패</translation>
+<translation id="3621712662352432595">오디오 설정</translation>
+<translation id="1812696562331527143">입력 방법이 <ph name="INPUT_METHOD_ID"/>*(<ph name="BEGIN_LINK"/>타사<ph name="END_LINK"/>)(으)로 변경되었습니다.
+전환하려면 Shift+Alt를 누르세요.</translation>
+<translation id="2127372758936585790">저출력 충전기</translation>
+<translation id="3846575436967432996">네트워크 정보 없음</translation>
+<translation id="3026237328237090306">모바일 데이터 설정</translation>
+<translation id="785750925697875037">모바일 계정 표시</translation>
+<translation id="153454903766751181">휴대전화 모뎀 초기화하는 중...</translation>
+<translation id="4628814525959230255">행아웃을 통해 화면 제어를 <ph name="HELPER_NAME"/>님과 공유 중</translation>
+<translation id="8343941333792395995"><ph name="DISPLAY_NAME"/>이(가) 회전됨</translation>
+<translation id="7864539943188674973">블루투스 사용 안 함</translation>
+<translation id="939252827960237676">캡처화면을 저장하지 못했습니다.</translation>
+<translation id="3126069444801937830">업데이트하려면 다시 시작</translation>
+<translation id="2268813581635650749">모두 로그아웃</translation>
+<translation id="735745346212279324">VPN 연결 끊김</translation>
+<translation id="7320906967354320621">대기</translation>
+<translation id="6303423059719347535">배터리가 <ph name="PERCENTAGE"/>% 충전됨</translation>
+<translation id="15373452373711364">큰 마우스 커서</translation>
+<translation id="2778346081696727092">제공한 사용자 이름 또는 비밀번호로 인증하지 못했습니다.</translation>
+<translation id="3294437725009624529">손님</translation>
+<translation id="8190698733819146287">언어 및 입력 설정...</translation>
+<translation id="2903907270192926896">입력:</translation>
+<translation id="8676770494376880701">저출력 충전기 연결됨</translation>
+<translation id="7170041865419449892">범위를 벗어났습니다.</translation>
+<translation id="4804818685124855865">연결 해제</translation>
+<translation id="2544853746127077729">인증서가 네트워크에 의해 거부됨</translation>
+<translation id="5222676887888702881">로그아웃</translation>
+<translation id="2688477613306174402">설정</translation>
+<translation id="1272079795634619415">중지</translation>
+<translation id="4957722034734105353">자세히 알아보기...</translation>
+<translation id="2964193600955408481">Wi-Fi 사용 안 함</translation>
+<translation id="811680302244032017">기기 추가...</translation>
+<translation id="4279490309300973883">미러링</translation>
+<translation id="2509468283778169019">CAPS LOCK이 켜져 있습니다.</translation>
+<translation id="3892641579809465218">내부 디스플레이</translation>
+<translation id="7823564328645135659">설정을 동기화한 뒤 Chrome의 언어가 '<ph name="FROM_LOCALE"/>'에서 '<ph name="TO_LOCALE"/>'(으)로 변경되었습니다.</translation>
+<translation id="3368922792935385530">연결됨</translation>
+<translation id="8340999562596018839">음성 피드백</translation>
+<translation id="8654520615680304441">Wi-Fi 사용...</translation>
+<translation id="5825747213122829519">입력 방법이 <ph name="INPUT_METHOD_ID"/>(으)로 변경되었습니다.
+전환하려면 Shift+Alt를 누르세요.</translation>
+<translation id="2562916301614567480">사설 네트워크</translation>
+<translation id="6549021752953852991">사용 가능한 휴대전화 네트워크가 없음</translation>
+<translation id="4379753398862151997">확장 데스크톱에 어울리지 않는 모니터네요. (지원되지 않는 모니터입니다.)</translation>
+<translation id="6426039856985689743">모바일 데이터 사용 안함</translation>
+<translation id="3087734570205094154">맨 아래</translation>
+<translation id="3742055079367172538">찍은 캡처화면</translation>
+<translation id="8878886163241303700">화면 확대</translation>
+<translation id="5271016907025319479">VPN이 구성되지 않았습니다.</translation>
+<translation id="372094107052732682">종료하려면 Ctrl+Shift+Q를 두 번 누릅니다.</translation>
+<translation id="6803622936009808957">지원되는 해상도가 없으므로 디스플레이를 그대로 반영할 수 없습니다. 대신 확장 데스크톱을 시작했습니다.</translation>
+<translation id="1480041086352807611">데모 모드</translation>
+<translation id="3626637461649818317"><ph name="PERCENTAGE"/>% 남음</translation>
+<translation id="9089416786594320554">입력 방법</translation>
+<translation id="6247708409970142803"><ph name="PERCENTAGE"/>%</translation>
+<translation id="2614835198358683673">크롬북이 켜져 있으면 충전되지 않을 수 있습니다. 공식 충전기를 사용해보세요.</translation>
+<translation id="1895658205118569222">종료</translation>
+<translation id="4430019312045809116">볼륨</translation>
+<translation id="4442424173763614572">DNS 조회 실패</translation>
+<translation id="6356500677799115505">배터리 충전이 완료되었으며 충전 중입니다.</translation>
+<translation id="7874779702599364982">휴대전화 네트워크를 검색하는 중...</translation>
+<translation id="583281660410589416">알 수 없음</translation>
+<translation id="1383876407941801731">검색</translation>
+<translation id="7468789844759750875">추가 데이터를 구입하려면 <ph name="NAME"/> 활성화 포탈을 방문하세요.</translation>
+<translation id="3901991538546252627"><ph name="NAME"/>에 연결 중</translation>
+<translation id="2204305834655267233">네트워크 정보</translation>
+<translation id="1621499497873603021">남은 배터리 사용 시간은 <ph name="TIME_LEFT"/>입니다.</translation>
+<translation id="5980301590375426705">손님 세션 종료</translation>
+<translation id="4471417012762451363">배터리가 <ph name="PERCENTAGE"/>% 충전되었으며 충전 중</translation>
+<translation id="8308637677604853869">이전 메뉴</translation>
+<translation id="4666297444214622512">다른 계정에 로그인할 수 없습니다.</translation>
+<translation id="1346748346194534595">오른쪽</translation>
+<translation id="1773212559869067373">인증서가 로컬로 거부됨</translation>
+<translation id="8528322925433439945">모바일 ...</translation>
+<translation id="7049357003967926684">연결</translation>
+<translation id="8428213095426709021">설정</translation>
+<translation id="2372145515558759244">앱 동기화...</translation>
+<translation id="7256405249507348194">알 수 없는 오류(<ph name="DESC"/>)입니다.</translation>
+<translation id="7925247922861151263">AAA 확인 실패</translation>
+<translation id="8456362689280298700">충전 완료까지 <ph name="HOUR"/>:<ph name="MINUTE"/> 남음</translation>
+<translation id="5787281376604286451">음성 피드백을 사용할 수 있습니다.
+사용중지하려면 Ctrl+Alt+Z를 누르세요.</translation>
+<translation id="4479639480957787382">이더넷</translation>
+<translation id="6312403991423642364">알려지지 않은 네트워크 오류</translation>
+<translation id="1467432559032391204">왼쪽</translation>
+<translation id="5543001071567407895">SMS</translation>
+<translation id="2354174487190027830"><ph name="NAME"/> 활성화 중</translation>
+<translation id="8814190375133053267">Wi-Fi</translation>
+<translation id="1398853756734560583">최대화</translation>
+<translation id="2692809339924654275"><ph name="BLUETOOTH"/>: 연결하는 중...</translation>
+<translation id="252373100621549798">알 수 없는 디스플레이</translation>
+<translation id="1882897271359938046"><ph name="DISPLAY_NAME"/>에 미러링</translation>
+<translation id="2727977024730340865">저출력 충전기에 연결되었습니다. 배터리 충전 상태가 불안정합니다.</translation>
+<translation id="3784455785234192852">잠금</translation>
+<translation id="2805756323405976993">애플리케이션</translation>
+<translation id="8871072142849158571"><ph name="DISPLAY_NAME"/>의 해상도가 <ph name="RESOLUTION"/>(으)로 변경됨</translation>
+<translation id="1512064327686280138">활성화 실패</translation>
+<translation id="5097002363526479830">'<ph name="NAME"/>' 네트워크에 연결하지 못했습니다: <ph name="DETAILS"/></translation>
+<translation id="1850504506766569011">Wi-Fi가 꺼져 있습니다.</translation>
+<translation id="8132793192354020517"><ph name="NAME"/>에 연결됨</translation>
+<translation id="7052914147756339792">배경화면 설정...</translation>
+<translation id="8678698760965522072">온라인 상태</translation>
+<translation id="2532589005999780174">고대비 모드</translation>
+<translation id="1119447706177454957">내부 오류</translation>
+<translation id="3019353588588144572">배터리 충전이 완료될 때까지 남은 시간은 <ph name="TIME_REMAINING"/>입니다.</translation>
+<translation id="3473479545200714844">화면 돋보기</translation>
+<translation id="7005812687360380971">실패</translation>
+<translation id="882279321799040148">클릭하여 보기</translation>
+<translation id="5045550434625856497">비밀번호가 잘못되었습니다.</translation>
+<translation id="1602076796624386989">모바일 데이터 사용</translation>
+<translation id="6981982820502123353">접근성</translation>
+<translation id="3157931365184549694">복구</translation>
+<translation id="4274292172790327596">인식할 수 없는 오류</translation>
+<translation id="4032485810211612751"><ph name="HOURS"/>:<ph name="MINUTES"/>:<ph name="SECONDS"/></translation>
+<translation id="225680501294068881">기기 검색 중...</translation>
+<translation id="5597451508971090205"><ph name="DATE"/> <ph name="SHORT_WEEKDAY"/></translation>
+<translation id="4448844063988177157">Wi-Fi 네트워크를 검색하는 중...</translation>
+<translation id="8401662262483418323">'<ph name="NAME"/>'에 연결하지 못함: <ph name="DETAILS"/>
+서버 메시지: <ph name="SERVER_MSG"/></translation>
+<translation id="2475982808118771221">오류가 발생했습니다.</translation>
+<translation id="7229570126336867161">EVDO가 필요합니다.</translation>
+<translation id="2999742336789313416"><ph name="DISPLAY_NAME"/>은(는) <ph name="DOMAIN"/>에서 관리하는 공개 세션입니다.</translation>
+<translation id="7029814467594812963">세션 종료</translation>
+<translation id="8454013096329229812">Wi-Fi가 켜져 있습니다.</translation>
+<translation id="4872237917498892622">Alt+검색 또는 Shift 키</translation>
+<translation id="2983818520079887040">설정...</translation>
+<translation id="1717216362413677834">도크 모드</translation>
+<translation id="8927026611342028580">연결 요청됨</translation>
+<translation id="8300849813060516376">OTASP 실패</translation>
+<translation id="2792498699870441125">Alt+검색 키</translation>
+<translation id="8660803626959853127">파일 <ph name="COUNT"/>개를 동기화 중</translation>
+<translation id="3709443003275901162">9+</translation>
+<translation id="639644700271529076">CAPS LOCK이 꺼져 있음</translation>
+<translation id="6248847161401822652">종료하려면 Ctrl+Shift+Q를 두 번 누릅니다.</translation>
+<translation id="6267036997247669271"><ph name="NAME"/>: 활성화 중...</translation>
+<translation id="1391854757121130358">모바일 데이터 사용량을 모두 사용했을 수 있습니다.</translation>
+<translation id="5413208160176941586">로컬 관리 사용자</translation>
+<translation id="1059194134494239015"><ph name="DISPLAY_NAME"/>: <ph name="RESOLUTION"/></translation>
+<translation id="4864165860509564259">실행기 위치</translation>
+<translation id="7593891976182323525">검색 또는 Shift 키</translation>
+<translation id="7649070708921625228">도움말</translation>
+<translation id="3050422059534974565">CAPS LOCK이 켜져 있습니다.
+취소하려면 검색 또는 Shift 키를 누릅니다.</translation>
+<translation id="397105322502079400">계산 중...</translation>
+<translation id="158849752021629804">홈 네크워크가 필요합니다.</translation>
+<translation id="6857811139397017780"><ph name="NETWORKSERVICE"/> 활성화</translation>
+<translation id="5864471791310927901">DHCP 조회 실패</translation>
+<translation id="5812035014844949013">출력</translation>
+<translation id="6692173217867674490">잘못된 암호</translation>
+<translation id="6165508094623778733">자세히 알아보기</translation>
+<translation id="9046895021617826162">연결 실패</translation>
+<translation id="973896785707726617">이 세션은 <ph name="SESSION_TIME_REMAINING"/> 후에 종료되어 자동으로 로그아웃됩니다.</translation>
+<translation id="8372369524088641025">잘못된 WEP 키</translation>
+<translation id="6636709850131805001">인식할 수 없는 상태</translation>
+<translation id="3573179567135747900">'<ph name="FROM_LOCALE"/>'(으)로 다시 변경(다시 시작해야 함)</translation>
+<translation id="8103386449138765447">SMS 메시지: <ph name="MESSAGE_COUNT"/>개</translation>
+<translation id="5045002648206642691">Google 문서함 설정...</translation>
+<translation id="1510238584712386396">실행기</translation>
+<translation id="7209101170223508707">CAPS LOCK이 켜져 있습니다.
+취소하려면 Alt+검색 또는 Shift 키를 누릅니다.</translation>
+<translation id="8940956008527784070">배터리 부족(<ph name="PERCENTAGE"/>%)</translation>
+<translation id="5102001756192215136"><ph name="HOUR"/>:<ph name="MINUTE"/> 남음</translation>
+<translation id="520760366042891468">행아웃을 통해 화면 제어 권한 공유 중</translation>
+<translation id="8000066093800657092">네트워크 없음</translation>
+<translation id="4015692727874266537">다른 계정에 로그인...</translation>
+<translation id="5941711191222866238">최소화</translation>
+<translation id="6911468394164995108">다른 네트워크에 연결</translation>
+<translation id="412065659894267608">충전 완료까지 <ph name="HOUR"/>시간 <ph name="MINUTE"/>분 남음</translation>
+<translation id="6359806961507272919"><ph name="PHONE_NUMBER"/>에서 전송된 SMS</translation>
+<translation id="1244147615850840081">네트워크 사업자</translation>
+</translationbundle> \ No newline at end of file
diff --git a/chromium/ash/strings/ash_strings_lt.xtb b/chromium/ash/strings/ash_strings_lt.xtb
new file mode 100644
index 00000000000..611b1ea5ed3
--- /dev/null
+++ b/chromium/ash/strings/ash_strings_lt.xtb
@@ -0,0 +1,201 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="lt">
+<translation id="3595596368722241419">Akumuliatorius įkrautas</translation>
+<translation id="5250713215130379958">Automatiškai slėpti paleidimo priemonę</translation>
+<translation id="7814236020522506259"><ph name="HOUR"/>:<ph name="MINUTE"/></translation>
+<translation id="7880025619322806991">Portalo būsena</translation>
+<translation id="30155388420722288">Perpildymo mygtukas</translation>
+<translation id="5571066253365925590">„Bluetooth“ įgalinta</translation>
+<translation id="9074739597929991885">Bluetooth</translation>
+<translation id="2268130516524549846">„Bluetooth“ neleidžiama</translation>
+<translation id="3775358506042162758">Naudodami kelių paskyrų funkciją, galite turėti iki trijų paskyrų.</translation>
+<translation id="370649949373421643">Įgalinti „Wi-Fi“</translation>
+<translation id="3626281679859535460">Skaistis</translation>
+<translation id="8054466585765276473">Apskaičiuojamas laikas, likęs iki akumuliatoriaus išsikrovimo.</translation>
+<translation id="7982789257301363584">Tinklas</translation>
+<translation id="5565793151875479467">Įgaliotasis serveris...</translation>
+<translation id="938582441709398163">Klaviatūros perdanga</translation>
+<translation id="4387004326333427325">Autentifikavimo sertifikatas atmestas nuotoliniu būdu</translation>
+<translation id="6979158407327259162">„Google“ diskas</translation>
+<translation id="6943836128787782965">Įvyko HTTP klaida</translation>
+<translation id="2297568595583585744">Būsenos dėklas</translation>
+<translation id="1661867754829461514">Trūksta PIN kodo</translation>
+<translation id="4508225577814909926">„<ph name="NAME"/>“: jungiamasi...</translation>
+<translation id="4237016987259239829">Tinklo ryšio klaida</translation>
+<translation id="2946640296642327832">Įgalinti „Bluetooth“</translation>
+<translation id="6459472438155181876">Ekranas išplečiamas į <ph name="DISPLAY_NAME"/></translation>
+<translation id="8206859287963243715">Mobilusis</translation>
+<translation id="6596816719288285829">IP adresas</translation>
+<translation id="4508265954913339219">Nepavyko suaktyvinti</translation>
+<translation id="3621712662352432595">Garso nustatymai</translation>
+<translation id="1812696562331527143">Įvesties metodas pakeistas į <ph name="INPUT_METHOD_ID"/>* (<ph name="BEGIN_LINK"/>trečioji šalis<ph name="END_LINK"/>).
+ Jei norite perjungti, paspauskite „Shift“ + „Alt“.</translation>
+<translation id="2127372758936585790">Mažos galios įkroviklis</translation>
+<translation id="3846575436967432996">Nėra tinklo informacijos</translation>
+<translation id="3026237328237090306">Nustatyti duomenis mobiliesiems</translation>
+<translation id="785750925697875037">Žiūrėti paskyrą mobiliesiems</translation>
+<translation id="153454903766751181">Inicijuojamas korinio ryšio modemas...</translation>
+<translation id="4628814525959230255">Jūsų ekrano valdymas bendrinamas su <ph name="HELPER_NAME"/> per „Hangout“.</translation>
+<translation id="8343941333792395995"><ph name="DISPLAY_NAME"/> pasuktas</translation>
+<translation id="7864539943188674973">Neleisti „Bluetooth“</translation>
+<translation id="939252827960237676">Išsaugant ekrano kopiją įvyko klaida</translation>
+<translation id="3126069444801937830">Paleisti iš naujo, kad būtų atnaujinta</translation>
+<translation id="2268813581635650749">Atjungti visus</translation>
+<translation id="735745346212279324">VPN atjungtas</translation>
+<translation id="7320906967354320621">Neveikia</translation>
+<translation id="6303423059719347535">Likusi akumuliatoriaus įkrova: <ph name="PERCENTAGE"/> %</translation>
+<translation id="15373452373711364">Didelis pelės žymeklis</translation>
+<translation id="2778346081696727092">Nepavyko autentifikuoti naudojant pateiktą naudotojo vardą ar slaptažodį</translation>
+<translation id="3294437725009624529">Svečias</translation>
+<translation id="8190698733819146287">Tinkinti kalbas ir įvestį...</translation>
+<translation id="2903907270192926896">ĮVESTIS</translation>
+<translation id="8676770494376880701">Prijungtas mažos galios įkroviklis</translation>
+<translation id="7170041865419449892">Nepasiekiama</translation>
+<translation id="4804818685124855865">Atsijungti</translation>
+<translation id="2544853746127077729">Autentifikavimo sertifikatą atmetė tinklas</translation>
+<translation id="5222676887888702881">Atsijungti</translation>
+<translation id="2688477613306174402">Konfigūracija</translation>
+<translation id="1272079795634619415">Sustabdyti</translation>
+<translation id="4957722034734105353">Sužinokite daugiau...</translation>
+<translation id="2964193600955408481">Neleisti „Wi-Fi“</translation>
+<translation id="811680302244032017">Pridėti įrenginį...</translation>
+<translation id="4279490309300973883">Dubliuojama</translation>
+<translation id="2509468283778169019">DIDŽIŲJŲ RAIDŽIŲ RAŠYMAS įjungtas</translation>
+<translation id="3892641579809465218">Vidinė pateiktis</translation>
+<translation id="7823564328645135659">Po nustatymų sinchronizavimo kalba pakeista iš <ph name="FROM_LOCALE"/> į <ph name="TO_LOCALE"/>.</translation>
+<translation id="3368922792935385530">Prijungta</translation>
+<translation id="8340999562596018839">Žodiniai atsiliepimai</translation>
+<translation id="8654520615680304441">Įjungti „Wi-Fi“...</translation>
+<translation id="5825747213122829519">Įvesties metodas pakeistas į <ph name="INPUT_METHOD_ID"/>.
+ Jei norite perjungti, paspauskite „Shift“ + „Alt“.</translation>
+<translation id="2562916301614567480">Privatus tinklas</translation>
+<translation id="6549021752953852991">Nėra jokių pasiekiamų korinio ryšio tinklų</translation>
+<translation id="4379753398862151997">Mielas monitoriau, mums nepavyksta bendradarbiauti. (Tas monitorius nepalaikomas)</translation>
+<translation id="6426039856985689743">Neleisti duomenų mobiliesiems</translation>
+<translation id="3087734570205094154">Apačia</translation>
+<translation id="3742055079367172538">Ekrano kopija padaryta</translation>
+<translation id="8878886163241303700">Išplečiamas ekranas</translation>
+<translation id="5271016907025319479">VPN nesukonfigūruotas.</translation>
+<translation id="372094107052732682">Jei norite išeiti, du kartus paspauskite „Ctrl“ + „Shift“ + Q.</translation>
+<translation id="6803622936009808957">Nepavyko dubliuoti vaizdų, nes nepavyko rasti palaikomų skyrų. Vietoje to įjungtas išplėstinio darbalaukio režimas.</translation>
+<translation id="1480041086352807611">Demonstracinės versijos režimas</translation>
+<translation id="3626637461649818317">Liko <ph name="PERCENTAGE"/> proc.</translation>
+<translation id="9089416786594320554">Įvesties metodai</translation>
+<translation id="6247708409970142803"><ph name="PERCENTAGE"/> %</translation>
+<translation id="2614835198358683673">„Chromebook“ gali nebūti įkraunamas, kai jis įjungtas. Apsvarstykite galimybę naudoti originalų įkroviklį.</translation>
+<translation id="1895658205118569222">Išjungimas</translation>
+<translation id="4430019312045809116">Apimtis</translation>
+<translation id="4442424173763614572">Įvyko DNS paieškos klaida</translation>
+<translation id="6356500677799115505">Akumuliatorius įkrautas ir vis įkraunamas.</translation>
+<translation id="7874779702599364982">Ieškoma korinio ryšio tinklų...</translation>
+<translation id="583281660410589416">Nežinoma</translation>
+<translation id="1383876407941801731">Ieškoti</translation>
+<translation id="7468789844759750875">Apsilankykite „<ph name="NAME"/>“ aktyvinimo portale, kad nusipirktumėte daugiau duomenų.</translation>
+<translation id="3901991538546252627">Jungiamasi prie „<ph name="NAME"/>“</translation>
+<translation id="2204305834655267233">Tinklo informacija</translation>
+<translation id="1621499497873603021">Laikas, likęs iki akumuliatoriaus išsikrovimo: <ph name="TIME_LEFT"/></translation>
+<translation id="5980301590375426705">Išeiti iš svečio režimo</translation>
+<translation id="4471417012762451363">Likusi akumuliatoriaus įkrova: <ph name="PERCENTAGE"/> %. Jis įkraunamas.</translation>
+<translation id="8308637677604853869">Ankstesnis meniu</translation>
+<translation id="4666297444214622512">Negalima prisijungti prie kitos paskyros.</translation>
+<translation id="1346748346194534595">Dešinė</translation>
+<translation id="1773212559869067373">Autentifikavimo sertifikatas atmestas vietiniu mastu</translation>
+<translation id="8528322925433439945">Mobilusis...</translation>
+<translation id="7049357003967926684">Bendrovė</translation>
+<translation id="8428213095426709021">Nustatymai</translation>
+<translation id="2372145515558759244">Sinchronizuojamos programos...</translation>
+<translation id="7256405249507348194">Neatpažinta klaida: <ph name="DESC"/></translation>
+<translation id="7925247922861151263">AAA patikra nepavyko</translation>
+<translation id="8456362689280298700"><ph name="HOUR"/>.<ph name="MINUTE"/> iki visiško įkrovimo</translation>
+<translation id="5787281376604286451">Žodiniai atsiliepimai įgalinti.
+Jei norite neleisti, paspauskite „Ctrl“ + „Alt“ + Z.</translation>
+<translation id="4479639480957787382">Eternetas</translation>
+<translation id="6312403991423642364">Nežinoma tinklo klaida</translation>
+<translation id="1467432559032391204">Kairė</translation>
+<translation id="5543001071567407895">SMS</translation>
+<translation id="2354174487190027830">Aktyvinamas „<ph name="NAME"/>“</translation>
+<translation id="8814190375133053267">WI-Fi</translation>
+<translation id="1398853756734560583">Išskleisti</translation>
+<translation id="2692809339924654275">„<ph name="BLUETOOTH"/>“: jungiamasi...</translation>
+<translation id="252373100621549798">Nežinoma pateiktis</translation>
+<translation id="1882897271359938046">Dubliuojama <ph name="DISPLAY_NAME"/></translation>
+<translation id="2727977024730340865">Prijungtas mažos galios įkroviklis. Akumuliatoriaus įkrovimas gali būti nepatikimas.</translation>
+<translation id="3784455785234192852">Užrakinti</translation>
+<translation id="2805756323405976993">Programos</translation>
+<translation id="8871072142849158571"><ph name="DISPLAY_NAME"/> dydis pakeistas į <ph name="RESOLUTION"/></translation>
+<translation id="1512064327686280138">Aktyvinimo triktis</translation>
+<translation id="5097002363526479830">Nepavyko prisijungti prie tinklo „<ph name="NAME"/>“: <ph name="DETAILS"/></translation>
+<translation id="1850504506766569011">„Wi-Fi“ ryšys išjungtas.</translation>
+<translation id="8132793192354020517">Prisijungta prie „<ph name="NAME"/>“</translation>
+<translation id="7052914147756339792">Nustatyti darbalaukio foną...</translation>
+<translation id="8678698760965522072">Būsena „Prisijungus“</translation>
+<translation id="2532589005999780174">Didelio kontrasto režimas</translation>
+<translation id="1119447706177454957">Vidinė klaida</translation>
+<translation id="3019353588588144572">Laikas, likęs iki akumuliatoriaus įkrovimo: <ph name="TIME_REMAINING"/></translation>
+<translation id="3473479545200714844">Ekrano didintuvas</translation>
+<translation id="7005812687360380971">Nepavyko</translation>
+<translation id="882279321799040148">Jei norite peržiūrėti, spustelėkite</translation>
+<translation id="5045550434625856497">Netinkamas slaptažodis</translation>
+<translation id="1602076796624386989">Įgalinti duomenis mobiliesiems</translation>
+<translation id="6981982820502123353">Pritaikymas neįgaliesiems</translation>
+<translation id="3157931365184549694">Atkurti</translation>
+<translation id="4274292172790327596">Neatpažįstama klaida</translation>
+<translation id="4032485810211612751"><ph name="HOURS"/>:<ph name="MINUTES"/>:<ph name="SECONDS"/></translation>
+<translation id="225680501294068881">Ieškoma įrenginių...</translation>
+<translation id="5597451508971090205"><ph name="DATE"/>, <ph name="SHORT_WEEKDAY"/></translation>
+<translation id="4448844063988177157">Ieškoma „Wi-Fi“ tinklų...</translation>
+<translation id="8401662262483418323">Nepavyko prisijungti prie „<ph name="NAME"/>“: <ph name="DETAILS"/>
+Serverio pranešimas: <ph name="SERVER_MSG"/></translation>
+<translation id="2475982808118771221">Įvyko klaida</translation>
+<translation id="7229570126336867161">Reikia EVDO</translation>
+<translation id="2999742336789313416"><ph name="DISPLAY_NAME"/> yra vieša sesija, valdoma <ph name="DOMAIN"/></translation>
+<translation id="7029814467594812963">Išeiti iš sesijos</translation>
+<translation id="8454013096329229812">„Wi-Fi“ ryšys įjungtas.</translation>
+<translation id="4872237917498892622">„Alt“ + paieškos arba antrojo lygio klavišas</translation>
+<translation id="2983818520079887040">Nustatymai...</translation>
+<translation id="1717216362413677834">Doko režimas</translation>
+<translation id="8927026611342028580">Pateikta prisijungimo užklausa</translation>
+<translation id="8300849813060516376">OTASP nepavyko</translation>
+<translation id="2792498699870441125">„Alt“ + paieškos klavišas</translation>
+<translation id="8660803626959853127">Sinchronizuojamas (-i) <ph name="COUNT"/> failas (-ai)</translation>
+<translation id="3709443003275901162">Daugiau nei 9</translation>
+<translation id="639644700271529076">DIDŽIŲJŲ RAIDŽIŲ RAŠYMAS išjungtas</translation>
+<translation id="6248847161401822652">Jei norite išeiti, du kartus paspauskite „Control“ + „Shift“ + Q.</translation>
+<translation id="6267036997247669271">„<ph name="NAME"/>“: aktyvinama...</translation>
+<translation id="1391854757121130358">Galbūt išnaudojote savo mobiliojo ryšio duomenų normą.</translation>
+<translation id="5413208160176941586">Vietoje tvarkoma naudotojo paskyra</translation>
+<translation id="1059194134494239015"><ph name="DISPLAY_NAME"/>: <ph name="RESOLUTION"/></translation>
+<translation id="4864165860509564259">Paleidiklio pozicija</translation>
+<translation id="7593891976182323525">Paieškos arba antrojo lygio klavišas</translation>
+<translation id="7649070708921625228">Žinynas</translation>
+<translation id="3050422059534974565">DIDŽIŲJŲ RAIDŽIŲ RAŠYMAS įjungtas.
+Jei norite atšaukti, paspauskite paieškos arba antrojo lygio klavišą.</translation>
+<translation id="397105322502079400">Skaičiuojama...</translation>
+<translation id="158849752021629804">Reikalingas namų tinklas</translation>
+<translation id="6857811139397017780">Suaktyvinti „<ph name="NETWORKSERVICE"/>“</translation>
+<translation id="5864471791310927901">DHCP paieška nepavyko</translation>
+<translation id="5812035014844949013">IŠVESTIS</translation>
+<translation id="6692173217867674490">Netinkama slaptafrazė</translation>
+<translation id="6165508094623778733">Sužinokite daugiau</translation>
+<translation id="9046895021617826162">Nepavyko prisijungti</translation>
+<translation id="973896785707726617">Ši sesija baigsis po <ph name="SESSION_TIME_REMAINING"/>. Būsite automatiškai atjungti.</translation>
+<translation id="8372369524088641025">Netinkamas WEP raktas</translation>
+<translation id="6636709850131805001">Neatpažinta būsena</translation>
+<translation id="3573179567135747900">Pakeisti atgal į „<ph name="FROM_LOCALE"/>“ (reikia paleisti iš naujo)</translation>
+<translation id="8103386449138765447">SMS pranešimų: <ph name="MESSAGE_COUNT"/></translation>
+<translation id="5045002648206642691">„Google“ disko nustatymai...</translation>
+<translation id="1510238584712386396">Paleidimo priemonė</translation>
+<translation id="7209101170223508707">DIDŽIŲJŲ RAIDŽIŲ RAŠYMAS įjungtas.
+Jei norite atšaukti, paspauskite „Alt“ + paieškos klavišas arba „Alt“ + antrojo lygio klavišas.</translation>
+<translation id="8940956008527784070">Akumuliatorius senka (<ph name="PERCENTAGE"/> %)</translation>
+<translation id="5102001756192215136">Liko <ph name="HOUR"/>.<ph name="MINUTE"/></translation>
+<translation id="520760366042891468">Jūsų ekrano valdymas bendrinamas per „Hangout“.</translation>
+<translation id="8000066093800657092">Tinklo nėra</translation>
+<translation id="4015692727874266537">Prisijungti prie kitos paskyros...</translation>
+<translation id="5941711191222866238">Sumažinti</translation>
+<translation id="6911468394164995108">Prisijungti prie kito...</translation>
+<translation id="412065659894267608"><ph name="HOUR"/> val. <ph name="MINUTE"/> min. iki visiško įkrovimo</translation>
+<translation id="6359806961507272919">SMS iš <ph name="PHONE_NUMBER"/></translation>
+<translation id="1244147615850840081">Operatorius</translation>
+</translationbundle> \ No newline at end of file
diff --git a/chromium/ash/strings/ash_strings_lv.xtb b/chromium/ash/strings/ash_strings_lv.xtb
new file mode 100644
index 00000000000..5d8f38c5087
--- /dev/null
+++ b/chromium/ash/strings/ash_strings_lv.xtb
@@ -0,0 +1,201 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="lv">
+<translation id="3595596368722241419">Akumulators pilns</translation>
+<translation id="5250713215130379958">Automātiski paslēpt palaišanas lapu</translation>
+<translation id="7814236020522506259"><ph name="HOUR"/> un <ph name="MINUTE"/></translation>
+<translation id="7880025619322806991">Portāla statuss</translation>
+<translation id="30155388420722288">Pārpildes poga</translation>
+<translation id="5571066253365925590">Bluetooth iespējots</translation>
+<translation id="9074739597929991885">Bluetooth</translation>
+<translation id="2268130516524549846">Bluetooth atspējots</translation>
+<translation id="3775358506042162758">Vairākkārtējas pierakstīšanās laikā var izmantot ne vairāk kā trīs kontus.</translation>
+<translation id="370649949373421643">Iespējot Wi-Fi</translation>
+<translation id="3626281679859535460">Spilgtums</translation>
+<translation id="8054466585765276473">Notiek akumulatora darbības ilguma aprēķināšana.</translation>
+<translation id="7982789257301363584">Tīkls</translation>
+<translation id="5565793151875479467">Starpniekserveris...</translation>
+<translation id="938582441709398163">Tastatūras pārklājums</translation>
+<translation id="4387004326333427325">Autentifikācijas sertifikāts tika attāli noraidīts.</translation>
+<translation id="6979158407327259162">Google disks</translation>
+<translation id="6943836128787782965">Neizdevās iegūt HTTP</translation>
+<translation id="2297568595583585744">Statusa tekne</translation>
+<translation id="1661867754829461514">Trūkst PIN koda</translation>
+<translation id="4508225577814909926"><ph name="NAME"/>: notiek savienojuma izveide...</translation>
+<translation id="4237016987259239829">Tīkla savienojuma kļūda</translation>
+<translation id="2946640296642327832">Iespējot Bluetooth</translation>
+<translation id="6459472438155181876">Paplašina ekrānu uz: <ph name="DISPLAY_NAME"/></translation>
+<translation id="8206859287963243715">Mobilais tālrunis</translation>
+<translation id="6596816719288285829">IP adrese</translation>
+<translation id="4508265954913339219">Aktivizācija neizdevās</translation>
+<translation id="3621712662352432595">Audio iestatījumi</translation>
+<translation id="1812696562331527143">Ievades metode ir mainīta uz <ph name="INPUT_METHOD_ID"/>*(<ph name="BEGIN_LINK"/>trešā puse<ph name="END_LINK"/>).
+Lai to pārslēgtu, nospiediet taustiņu kombināciju Shift+Alt.</translation>
+<translation id="2127372758936585790">Lādētājs ar mazu strāvas padevi</translation>
+<translation id="3846575436967432996">Tīkla informācija nav pieejama.</translation>
+<translation id="3026237328237090306">Iestatīt mobilo datu pārraidi</translation>
+<translation id="785750925697875037">Skatīt mobilo kontu</translation>
+<translation id="153454903766751181">Notiek mobilā modema inicializēšana...</translation>
+<translation id="4628814525959230255">Ekrāna vadības kopīgošana ar lietotāju <ph name="HELPER_NAME"/>, izmantojot funkciju Hangouts.</translation>
+<translation id="8343941333792395995"><ph name="DISPLAY_NAME"/> ir pagriezts.</translation>
+<translation id="7864539943188674973">Atspējot Bluetooth</translation>
+<translation id="939252827960237676">Neizdevās saglabāt ekrānuzņēmumu.</translation>
+<translation id="3126069444801937830">Restartēt, lai atjauninātu</translation>
+<translation id="2268813581635650749">Izrakstīt visus</translation>
+<translation id="735745346212279324">VPN ir atvienots</translation>
+<translation id="7320906967354320621">Dīkstāve</translation>
+<translation id="6303423059719347535">Akumulatora uzlādes līmenis: <ph name="PERCENTAGE"/>%</translation>
+<translation id="15373452373711364">Liels peles kursors</translation>
+<translation id="2778346081696727092">Neizdevās autentificēt ievadīto lietotājvārdu un paroli</translation>
+<translation id="3294437725009624529">Viesis</translation>
+<translation id="8190698733819146287">Pielāgot valodas un ievadi...</translation>
+<translation id="2903907270192926896">IEEJA</translation>
+<translation id="8676770494376880701">Pievienots lādētājs ar mazu strāvas padevi</translation>
+<translation id="7170041865419449892">Ārpus diapazona</translation>
+<translation id="4804818685124855865">Atvienot</translation>
+<translation id="2544853746127077729">Tīkls noraidīja autentifikācijas sertifikātu.</translation>
+<translation id="5222676887888702881">Izrakstīties</translation>
+<translation id="2688477613306174402">Konfigurācija</translation>
+<translation id="1272079795634619415">Apturēt</translation>
+<translation id="4957722034734105353">Uzzināt vairāk...</translation>
+<translation id="2964193600955408481">Atspējot Wi-Fi</translation>
+<translation id="811680302244032017">Pievienot ierīci...</translation>
+<translation id="4279490309300973883">Spoguļošana</translation>
+<translation id="2509468283778169019">Funkcija Caps Lock ir ieslēgta</translation>
+<translation id="3892641579809465218">Iekšējais displejs</translation>
+<translation id="7823564328645135659">Pēc jūsu iestatījumu sinhronizēšanas valoda ir mainīta no <ph name="FROM_LOCALE"/> uz <ph name="TO_LOCALE"/>.</translation>
+<translation id="3368922792935385530">pievienots</translation>
+<translation id="8340999562596018839">Balss komentāri</translation>
+<translation id="8654520615680304441">Ieslēgt Wi-Fi...</translation>
+<translation id="5825747213122829519">Ievades metode ir mainīta uz <ph name="INPUT_METHOD_ID"/>.
+Lai to pārslēgtu, nospiediet taustiņu kombināciju Shift+Alt.</translation>
+<translation id="2562916301614567480">Privāts tīkls</translation>
+<translation id="6549021752953852991">Mobilais tīkls nav pieejams.</translation>
+<translation id="4379753398862151997">Diemžēl monitoru nevar izmantot (tas netiek atbalstīts).</translation>
+<translation id="6426039856985689743">Atspējot mobilo datu pārraidi</translation>
+<translation id="3087734570205094154">Apakša</translation>
+<translation id="3742055079367172538">Ekrānuzņēmums ir uzņemts</translation>
+<translation id="8878886163241303700">Notiek ekrāna izvēršana</translation>
+<translation id="5271016907025319479">VPN nav konfigurēts.</translation>
+<translation id="372094107052732682">Lai izietu, divas reizes nospiediet taustiņu kombināciju Ctrl+Shift+Q.</translation>
+<translation id="6803622936009808957">Nevarēja spoguļot displejus, jo netika atrasta atbalstīta izšķirtspēja. Tā vietā tika aktivizēts paplašinātās darbvirsmas režīms.</translation>
+<translation id="1480041086352807611">Demonstrācijas režīms</translation>
+<translation id="3626637461649818317">Atlikums: <ph name="PERCENTAGE"/>%</translation>
+<translation id="9089416786594320554">Ievades metodes</translation>
+<translation id="6247708409970142803"><ph name="PERCENTAGE"/>%</translation>
+<translation id="2614835198358683673">Jūsu Chromebook dators, iespējams, netiks uzlādēts, kamēr tas ir ieslēgts. Ieteicams izmantot oriģinālo lādētāju.</translation>
+<translation id="1895658205118569222">Izslēgšana</translation>
+<translation id="4430019312045809116">Skaļums</translation>
+<translation id="4442424173763614572">DNS uzmeklēšana neizdevās</translation>
+<translation id="6356500677799115505">Akumulators ir pilnībā uzlādēts, un tiek turpināta tā uzlāde.</translation>
+<translation id="7874779702599364982">Notiek mobilo sakaru tīklu meklēšana...</translation>
+<translation id="583281660410589416">Nezināms</translation>
+<translation id="1383876407941801731">Meklēšana</translation>
+<translation id="7468789844759750875">Lai iegādātos citus datus, apmeklējiet <ph name="NAME"/> aktivizācijas portālu.</translation>
+<translation id="3901991538546252627">Notiek savienojuma izveide ar <ph name="NAME"/></translation>
+<translation id="2204305834655267233">Tīkla informācija</translation>
+<translation id="1621499497873603021">Atlikušais akumulatora darbības laiks: <ph name="TIME_LEFT"/>.</translation>
+<translation id="5980301590375426705">Aizvērt viesa sesiju</translation>
+<translation id="4471417012762451363">Akumulatora uzlādes līmenis: <ph name="PERCENTAGE"/>%; tiek turpināta tā uzlāde.</translation>
+<translation id="8308637677604853869">Iepriekšējā izvēlne</translation>
+<translation id="4666297444214622512">Nevar pierakstīties citā kontā.</translation>
+<translation id="1346748346194534595">Pa labi</translation>
+<translation id="1773212559869067373">Autentifikācijas sertifikāts tika lokāli noraidīts.</translation>
+<translation id="8528322925433439945">Mobilās ierīces...</translation>
+<translation id="7049357003967926684">Saistība</translation>
+<translation id="8428213095426709021">Iestatījumi</translation>
+<translation id="2372145515558759244">Notiek lietotņu sinhronizēšana...</translation>
+<translation id="7256405249507348194">Neatpazīta kļūda: <ph name="DESC"/></translation>
+<translation id="7925247922861151263">AAA pārbaude neizdevās</translation>
+<translation id="8456362689280298700"><ph name="HOUR"/>:<ph name="MINUTE"/> jālādē</translation>
+<translation id="5787281376604286451">Balss komentāri ir iespējoti.
+Nospiediet Ctrl+Alt+Z, lai tos atspējotu.</translation>
+<translation id="4479639480957787382">tīkls Ethernet</translation>
+<translation id="6312403991423642364">Nezināma tīkla kļūda</translation>
+<translation id="1467432559032391204">Pa kreisi</translation>
+<translation id="5543001071567407895">Īsziņa</translation>
+<translation id="2354174487190027830">Notiek <ph name="NAME"/> aktivizēšana.</translation>
+<translation id="8814190375133053267">Wi-Fi</translation>
+<translation id="1398853756734560583">Maksimizēt</translation>
+<translation id="2692809339924654275"><ph name="BLUETOOTH"/>: notiek savienojuma izveide...</translation>
+<translation id="252373100621549798">Nezināms displejs</translation>
+<translation id="1882897271359938046">Spoguļo šeit: <ph name="DISPLAY_NAME"/></translation>
+<translation id="2727977024730340865">Pievienots lādētājs ar mazu strāvas padevi. Akumulatora uzlāde var nebūt uzticama.</translation>
+<translation id="3784455785234192852">Bloķēt</translation>
+<translation id="2805756323405976993">Lietotnes</translation>
+<translation id="8871072142849158571"><ph name="DISPLAY_NAME"/> lielums ir mainīts uz <ph name="RESOLUTION"/></translation>
+<translation id="1512064327686280138">Aktivizācijas kļūme</translation>
+<translation id="5097002363526479830">Neizdevās izveidot savienojumu ar tīklu <ph name="NAME"/>: <ph name="DETAILS"/></translation>
+<translation id="1850504506766569011">Wi-Fi tīkls ir izslēgts.</translation>
+<translation id="8132793192354020517">Savienots ar <ph name="NAME"/></translation>
+<translation id="7052914147756339792">Iestatīt fona tapeti...</translation>
+<translation id="8678698760965522072">Tiešsaistes statuss</translation>
+<translation id="2532589005999780174">Augsta kontrasta režīms</translation>
+<translation id="1119447706177454957">Iekšēja kļūda</translation>
+<translation id="3019353588588144572">Pilnīgais uzlādei nepieciešamais laiks: <ph name="TIME_REMAINING"/>.</translation>
+<translation id="3473479545200714844">Ekrāna lupa</translation>
+<translation id="7005812687360380971">Kļūme</translation>
+<translation id="882279321799040148">Noklikšķiniet, lai skatītu.</translation>
+<translation id="5045550434625856497">Nepareiza parole</translation>
+<translation id="1602076796624386989">Iespējot mobilo datu pārraidi</translation>
+<translation id="6981982820502123353">Pieejamība</translation>
+<translation id="3157931365184549694">Atjaunot</translation>
+<translation id="4274292172790327596">Neatpazīta kļūda</translation>
+<translation id="4032485810211612751"><ph name="HOURS"/>:<ph name="MINUTES"/>:<ph name="SECONDS"/></translation>
+<translation id="225680501294068881">Notiek ierīču meklēšana...</translation>
+<translation id="5597451508971090205"><ph name="SHORT_WEEKDAY"/>, <ph name="DATE"/></translation>
+<translation id="4448844063988177157">Notiek Wi-Fi tīklu meklēšana...</translation>
+<translation id="8401662262483418323">Neizdevās izveidot savienojumu ar “<ph name="NAME"/>”: <ph name="DETAILS"/>
+Servera ziņojums: <ph name="SERVER_MSG"/></translation>
+<translation id="2475982808118771221">Radās kļūda</translation>
+<translation id="7229570126336867161">Nepieciešams EVDO</translation>
+<translation id="2999742336789313416"><ph name="DISPLAY_NAME"/> ir publiska sesija, kas tiek pārvaldīta domēnā <ph name="DOMAIN"/>.</translation>
+<translation id="7029814467594812963">Iziet no sesijas</translation>
+<translation id="8454013096329229812">Wi-Fi tīkls ir ieslēgts.</translation>
+<translation id="4872237917498892622">Alt+Meklēt vai Shift</translation>
+<translation id="2983818520079887040">Iestatījumi...</translation>
+<translation id="1717216362413677834">Dokošanas režīms</translation>
+<translation id="8927026611342028580">Ir pieprasīta savienojuma izveide.</translation>
+<translation id="8300849813060516376">OTASP neizdevās</translation>
+<translation id="2792498699870441125">Alt+Meklēt</translation>
+<translation id="8660803626959853127">Notiek <ph name="COUNT"/> faila(-u) sinhronizēšana</translation>
+<translation id="3709443003275901162">9+</translation>
+<translation id="639644700271529076">Funkcija CAPS LOCK ir izslēgta.</translation>
+<translation id="6248847161401822652">Lai izietu, divas reizes nospiediet taustiņu kombināciju Ctrl+Shift+Q.</translation>
+<translation id="6267036997247669271"><ph name="NAME"/>: notiek aktivizēšana...</translation>
+<translation id="1391854757121130358">Iespējams, esat jau izmantojis savu mobilo datu atļauju.</translation>
+<translation id="5413208160176941586">Vietēji pārvaldīts lietotājs</translation>
+<translation id="1059194134494239015"><ph name="DISPLAY_NAME"/>: <ph name="RESOLUTION"/></translation>
+<translation id="4864165860509564259">Palaidēja novietojums</translation>
+<translation id="7593891976182323525">Meklēt vai Shift</translation>
+<translation id="7649070708921625228">Palīdzība</translation>
+<translation id="3050422059534974565">Ir ieslēgts BURTSLĒGS.
+Lai atceltu tā funkcionalitāti, nospiediet Meklēt vai Shift.</translation>
+<translation id="397105322502079400">Aprēķina...</translation>
+<translation id="158849752021629804">Nepieciešams mājas tīkls</translation>
+<translation id="6857811139397017780">Aktivizēt <ph name="NETWORKSERVICE"/></translation>
+<translation id="5864471791310927901">DHCP uzmeklēšana neizdevās</translation>
+<translation id="5812035014844949013">IZEJA</translation>
+<translation id="6692173217867674490">Neatbilstoša ieejas frāze</translation>
+<translation id="6165508094623778733">Uzziniet vairāk</translation>
+<translation id="9046895021617826162">Savienojums neizdevās</translation>
+<translation id="973896785707726617">Atlikušais laiks līdz šīs sesijas beigām: <ph name="SESSION_TIME_REMAINING"/>. Jūs tiksiet automātiski izrakstīts.</translation>
+<translation id="8372369524088641025">Neatbilstoša WEP atslēga</translation>
+<translation id="6636709850131805001">Neatpazīts stāvoklis</translation>
+<translation id="3573179567135747900">Mainīt atpakaļ uz <ph name="FROM_LOCALE"/> (nepieciešama restartēšana)</translation>
+<translation id="8103386449138765447">Īsziņas: <ph name="MESSAGE_COUNT"/></translation>
+<translation id="5045002648206642691">Google diska iestatījumi...</translation>
+<translation id="1510238584712386396">Lietojumprogrammu palaidējs</translation>
+<translation id="7209101170223508707">Ir ieslēgts BURTSLĒGS.
+Lai atceltu tā funkcionalitāti, nospiediet Alt+Meklēt vai Shift.</translation>
+<translation id="8940956008527784070">Akumulators gandrīz tukšs (<ph name="PERCENTAGE"/>%)</translation>
+<translation id="5102001756192215136">Atlicis: <ph name="HOUR"/>:<ph name="MINUTE"/></translation>
+<translation id="520760366042891468">Ekrāna vadības kopīgošana, izmantojot funkciju Hangouts.</translation>
+<translation id="8000066093800657092">Nav tīkla</translation>
+<translation id="4015692727874266537">Pierakstīties citā kontā...</translation>
+<translation id="5941711191222866238">Minimizēt</translation>
+<translation id="6911468394164995108">Pievienoties citam...</translation>
+<translation id="412065659894267608">Līdz pilnīgai uzlādei atlikušais laiks: <ph name="HOUR"/> h <ph name="MINUTE"/> min</translation>
+<translation id="6359806961507272919">SMS no <ph name="PHONE_NUMBER"/></translation>
+<translation id="1244147615850840081">Mobilo sakaru operators</translation>
+</translationbundle> \ No newline at end of file
diff --git a/chromium/ash/strings/ash_strings_ml.xtb b/chromium/ash/strings/ash_strings_ml.xtb
new file mode 100644
index 00000000000..df9b8e869c0
--- /dev/null
+++ b/chromium/ash/strings/ash_strings_ml.xtb
@@ -0,0 +1,200 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="ml">
+<translation id="3595596368722241419">ബാറ്ററി നിറഞ്ഞു</translation>
+<translation id="5250713215130379958">ലോഞ്ചർ യാന്ത്രികമായി മറയ്‌ക്കുക</translation>
+<translation id="7814236020522506259"><ph name="HOUR"/>, <ph name="MINUTE"/></translation>
+<translation id="7880025619322806991">പോര്‍ട്ടല്‍ അവസ്ഥ</translation>
+<translation id="30155388420722288">ഓവർഫ്ലോ ബട്ടൺ</translation>
+<translation id="5571066253365925590">Bluetooth പ്രാപ്‌തമാക്കി</translation>
+<translation id="9074739597929991885">Bluetooth</translation>
+<translation id="2268130516524549846">Bluetooth അപ്രാപ്‌തമാക്കി</translation>
+<translation id="3775358506042162758">ഒന്നിലധികം സൈൻ ഇന്നുകളിൽ നിങ്ങൾക്ക് മൂന്ന് അക്കൗണ്ടുകൾ വരെ മാത്രമേ ഉണ്ടായിരിക്കാൻ പാടുള്ളൂ.</translation>
+<translation id="370649949373421643">Wi-fi പ്രാപ്‌തമാക്കുക</translation>
+<translation id="3626281679859535460">മിഴിവ്</translation>
+<translation id="8054466585765276473">ബാറ്ററി സമയം കണക്കാക്കുന്നു.</translation>
+<translation id="7982789257301363584">നെറ്റ്വര്‍ക്ക്</translation>
+<translation id="5565793151875479467">പ്രോക്‌സി...</translation>
+<translation id="938582441709398163">കീബോര്‍ഡ് ഓവര്‍ലേ</translation>
+<translation id="4387004326333427325">പ്രാമാണീകരണ സർട്ടിഫിക്കറ്റ് വിദൂരമായി നിരസിച്ചു</translation>
+<translation id="6979158407327259162">Google ഡ്രൈവ്</translation>
+<translation id="6943836128787782965">HTTP പരാജയപ്പെട്ടു</translation>
+<translation id="2297568595583585744">സ്റ്റാറ്റസ് ട്രേ</translation>
+<translation id="1661867754829461514">PIN കാണാനില്ല</translation>
+<translation id="4508225577814909926"><ph name="NAME"/>: കണക്റ്റുചെയ്യുന്നു...</translation>
+<translation id="4237016987259239829">നെറ്റ്വര്‍ക്ക് കണക്ഷന്‍ പിശക്</translation>
+<translation id="2946640296642327832">Bluetooth പ്രാപ്‌തമാക്കുക</translation>
+<translation id="6459472438155181876">സ്‌ക്രീൻ <ph name="DISPLAY_NAME"/> എന്നതിലേക്ക് വികസിപ്പിക്കുന്നു</translation>
+<translation id="8206859287963243715">സെല്ലുലാര്‍‌</translation>
+<translation id="6596816719288285829">IP വിലാസം</translation>
+<translation id="4508265954913339219">സജീവമാക്കല്‍ പരാജയപ്പെട്ടു</translation>
+<translation id="3621712662352432595">ഓഡിയോ ക്രമീകരണങ്ങൾ</translation>
+<translation id="1812696562331527143">നിങ്ങളുടെ ഇൻപുട്ട് രീതി <ph name="INPUT_METHOD_ID"/>*(<ph name="BEGIN_LINK"/>മൂന്നാം കക്ഷി<ph name="END_LINK"/>) എന്നതിലേയ്‌ക്ക് മാറ്റി.
+സ്വിച്ചുചെയ്യുന്നതിന് Shift + Alt അമർത്തുക.</translation>
+<translation id="2127372758936585790">കുറഞ്ഞ തോതിൽ വൈദ്യുതി പ്രവഹിക്കുന്ന ചാർജർ</translation>
+<translation id="3846575436967432996">നെറ്റ്‌വർക്ക് വിവരങ്ങളൊന്നും ലഭ്യമല്ല</translation>
+<translation id="3026237328237090306">മൊബൈൽ ഡാറ്റ സജ്ജമാക്കുക</translation>
+<translation id="785750925697875037">മൊബൈൽ അക്കൗണ്ട് കാണുക</translation>
+<translation id="153454903766751181">സെല്ലുലാർ മോഡം സമാരംഭിക്കുന്നു...</translation>
+<translation id="4628814525959230255">നിങ്ങളുടെ സ്‌ക്രീനിന്റെ നിയന്ത്രണം Hangouts വഴി <ph name="HELPER_NAME"/> എന്നതുമായി പങ്കിടുന്നു.</translation>
+<translation id="8343941333792395995"><ph name="DISPLAY_NAME"/> തിരിച്ചു</translation>
+<translation id="7864539943188674973">Bluetooth അപ്രാപ്‌തമാക്കുക</translation>
+<translation id="939252827960237676">സ്‌ക്രീൻഷോട്ട് സംരക്ഷിക്കുന്നതിൽ പരാജയപ്പെട്ടു</translation>
+<translation id="3126069444801937830">അപ്‌ഡേറ്റുചെയ്യുന്നതിന് പുനരാരംഭിക്കുക</translation>
+<translation id="2268813581635650749">എല്ലാം സൈൻ ഔട്ട് ചെയ്യുക</translation>
+<translation id="735745346212279324">VPN വിച്ഛേദിച്ചു</translation>
+<translation id="7320906967354320621">നിഷ്ക്രിയം</translation>
+<translation id="6303423059719347535">ബാറ്ററി <ph name="PERCENTAGE"/>% പൂർണ്ണമാണ്</translation>
+<translation id="15373452373711364">വലിയ മൗസ് കഴ്‌സർ</translation>
+<translation id="2778346081696727092">നൽകിയ ഉപയോക്തൃനാമം അല്ലെങ്കിൽ പാസ്‌വേഡ് ഉപയോഗിച്ച് പ്രാമാണീകരിക്കുന്നത് പരാജയപ്പെട്ടു</translation>
+<translation id="3294437725009624529">അതിഥി</translation>
+<translation id="8190698733819146287">ഭാഷകള്‍‌ ഇച്ഛാനുസൃതമാക്കി നല്‍‌കുക...</translation>
+<translation id="2903907270192926896">ഇൻപുട്ട്</translation>
+<translation id="8676770494376880701">കുറഞ്ഞ തോതിൽ വൈദ്യുതി പ്രവഹിക്കുന്ന ചാർജർ കണക്റ്റുചെയ്‌തു</translation>
+<translation id="7170041865419449892">പരിധിയ്ക്ക് പുറത്താണ്</translation>
+<translation id="4804818685124855865">വിച്ഛേദിക്കുക</translation>
+<translation id="2544853746127077729">നെറ്റ്‌വർക്ക്, പ്രാമാണീകരണ സർട്ടിഫിക്കറ്റ് നിരസിച്ചു</translation>
+<translation id="5222676887888702881">പുറത്തുകടക്കുക</translation>
+<translation id="2688477613306174402">ക്രമീകരണം</translation>
+<translation id="1272079795634619415">നിര്‍ത്തുക</translation>
+<translation id="4957722034734105353">കൂടുതലറിയുക...</translation>
+<translation id="2964193600955408481">Wi-Fi അപ്രാപ്‌തമാക്കുക</translation>
+<translation id="811680302244032017">ഉപകരണം ചേർക്കുക...</translation>
+<translation id="4279490309300973883">മിററിംഗ്</translation>
+<translation id="2509468283778169019">CAPS LOCK ഓൺ ആണ്</translation>
+<translation id="3892641579809465218">ആന്തരിക പ്രദർശനം</translation>
+<translation id="7823564328645135659">നിങ്ങളുടെ ക്രമീകരണങ്ങള്‍ സമന്വയിപ്പിച്ചതിന് ശേഷം ഭാഷ &quot;<ph name="FROM_LOCALE"/>&quot; എന്നതില്‍ നിന്ന് &quot;<ph name="TO_LOCALE"/>&quot; എന്നതിലേക്ക് മാറി.</translation>
+<translation id="3368922792935385530">ബന്ധിപ്പിച്ചു</translation>
+<translation id="8340999562596018839">സംഭാഷണ ഫീഡ്‌ബാക്ക്</translation>
+<translation id="8654520615680304441">Wi-Fi ഓണാക്കുക...</translation>
+<translation id="5825747213122829519">നിങ്ങളുടെ ഇൻപുട്ട് രീതി <ph name="INPUT_METHOD_ID"/> എന്നതിലേയ്‌ക്ക് മാറ്റി. സ്വിച്ചുചെയ്യുന്നതിന് Shift + Alt അമർത്തുക.</translation>
+<translation id="2562916301614567480">സ്വകാര്യ നെറ്റ്‌വർക്ക്</translation>
+<translation id="6549021752953852991">സെല്ലുലാർ നെറ്റ്‌വർക്കൊന്നും ലഭ്യമല്ല</translation>
+<translation id="4379753398862151997">പ്രിയ മോണിറ്റർ, ഇത് നമുക്കിടയിൽ പ്രവർത്തിക്കുന്നില്ല. (ആ മോണിറ്റർ പിന്തുണയ്‌ക്കുന്നില്ല)</translation>
+<translation id="6426039856985689743">മൊബൈൽ ഡാറ്റ അപ്രാപ്‌തമാക്കുക</translation>
+<translation id="3087734570205094154">താഴെ</translation>
+<translation id="3742055079367172538">സ്‌ക്രീൻഷോട്ട് എടുത്തു</translation>
+<translation id="8878886163241303700">സ്‌ക്രീൻ വിപുലീകരിക്കുന്നു</translation>
+<translation id="5271016907025319479">VPN കോൺഫിഗർ ചെയ്‌തില്ല.</translation>
+<translation id="372094107052732682">പുറത്തുപോകുന്നതിന് രണ്ടുതവണ Ctrl+Shift+Q അമർത്തുക.</translation>
+<translation id="6803622936009808957">പിന്തുണയ്‌ക്കുന്ന മിഴിവുകൾ കണ്ടെത്താത്തതിനാൽ പ്രദർശനങ്ങൾ പ്രതിഫലിപ്പിക്കാനായില്ല. പകരം വിപുലീകൃത ഡെസ്‌ക്‌ടോപ്പ് നൽകി.</translation>
+<translation id="1480041086352807611">ഡെമോ മോഡ്</translation>
+<translation id="3626637461649818317"><ph name="PERCENTAGE"/>% ശേഷിക്കുന്നു</translation>
+<translation id="9089416786594320554">ഇൻപുട്ട് രീതികൾ</translation>
+<translation id="6247708409970142803"><ph name="PERCENTAGE"/>%</translation>
+<translation id="2614835198358683673">ഓണായിരിക്കുമ്പോൾ നിങ്ങളുടെ Chromebook ചാർജ് ചെയ്യാനിടയില്ല. ഔദ്യോഗിക ചാർജ്ജർ ഉപയോഗിക്കുന്നത് പരിഗണിക്കുക.</translation>
+<translation id="1895658205118569222">ഷട്ട്‌ഡൗൺ ചെയ്യുക</translation>
+<translation id="4430019312045809116">അളവ്</translation>
+<translation id="4442424173763614572">DNS തിരയല്‍ പരാജയപ്പെട്ടു</translation>
+<translation id="6356500677799115505">ബാറ്ററി പൂർണ്ണവും ചാർജ്ജ് ചെയ്യുകയുമാണ്.</translation>
+<translation id="7874779702599364982">സെല്ലുലാർ നെറ്റ്‌വർക്കുകൾക്കായി തിരയുന്നു...</translation>
+<translation id="583281660410589416">അജ്ഞാതം</translation>
+<translation id="1383876407941801731">തിരയൂ</translation>
+<translation id="7468789844759750875">കൂടുതൽ ഡാറ്റ വാങ്ങുന്നതിനായി <ph name="NAME"/> സജീവമാക്കൽ പോർട്ടൽ സന്ദർശിക്കുക.</translation>
+<translation id="3901991538546252627"><ph name="NAME"/> എന്നതിലേക്ക് കണക്റ്റുചെയ്യുന്നു</translation>
+<translation id="2204305834655267233">നെറ്റ്‌വർക്ക് വിവരം</translation>
+<translation id="1621499497873603021">ബാറ്ററി ശൂന്യമാകുന്നതിന് ശേഷിക്കുന്ന സമയം, <ph name="TIME_LEFT"/></translation>
+<translation id="5980301590375426705">അതിഥി സെഷനിൽ നിന്നും പുറത്തുകടക്കുക</translation>
+<translation id="4471417012762451363">ബാറ്ററി <ph name="PERCENTAGE"/>% പൂർണ്ണവും ചാർജ്ജ് ചെയ്യുകയുമാണ്</translation>
+<translation id="8308637677604853869">മുൻ മെനു</translation>
+<translation id="4666297444214622512">മറ്റൊരു അക്കൗണ്ടിൽ സൈൻ ഇൻ ചെയ്യാനാകില്ല.</translation>
+<translation id="1346748346194534595">ശരി</translation>
+<translation id="1773212559869067373">പ്രാമാണീകരണ സർട്ടിഫിക്കറ്റ് പ്രാദേശികമായി നിരസിച്ചു</translation>
+<translation id="8528322925433439945">മൊബൈൽ ...</translation>
+<translation id="7049357003967926684">അസ്സോസിയേഷന്‍</translation>
+<translation id="8428213095426709021">ക്രമീകരണം</translation>
+<translation id="2372145515558759244">അപ്ലിക്കേഷനുകൾ സമന്വയിപ്പിക്കുന്നു...</translation>
+<translation id="7256405249507348194">തിരിച്ചറിയാനാകാത്ത പിശക്: <ph name="DESC"/></translation>
+<translation id="7925247922861151263">AAA പരിശോധന പരാജയപ്പെട്ടു</translation>
+<translation id="8456362689280298700"><ph name="HOUR"/>:<ph name="MINUTE"/> നിറയാൻ</translation>
+<translation id="5787281376604286451">സംഭാഷണ ഫീഡ്‌ബാക്ക് പ്രാപ്‌തമാക്കിയിരിക്കുന്നു.
+ഇത് അപ്രാപ്‌തമാക്കാൻ Ctrl+Alt+Z അമർത്തുക.</translation>
+<translation id="4479639480957787382">എതെര്‍‌നെറ്റ്</translation>
+<translation id="6312403991423642364">അറിയാത്ത നെറ്റ്‌വര്‍ക്ക് പിശക്</translation>
+<translation id="1467432559032391204">ഇടത്</translation>
+<translation id="5543001071567407895">SMS</translation>
+<translation id="2354174487190027830"><ph name="NAME"/> സജീവമാക്കുന്നു</translation>
+<translation id="8814190375133053267">Wi-Fi</translation>
+<translation id="1398853756734560583">വലുതാക്കുക</translation>
+<translation id="2692809339924654275"><ph name="BLUETOOTH"/>: കണക്റ്റുചെയ്യുന്നു...</translation>
+<translation id="252373100621549798">അജ്ഞാത പ്രദർശനം</translation>
+<translation id="1882897271359938046"><ph name="DISPLAY_NAME"/> എന്നതിലേക്ക് മിറർചെയ്യുന്നു</translation>
+<translation id="2727977024730340865">കുറഞ്ഞ തോതിൽ വൈദ്യുതി പ്രവഹിക്കുന്ന ചാർജ്ജറിലേക്ക് പ്ലഗ് ചെയ്‌തിരിക്കുന്നു. ബാറ്ററി ചാർജുചെയ്യൽ വിശ്വസനീയമാകണമെന്നില്ല.</translation>
+<translation id="3784455785234192852">ലോക്കുചെയ്യുക</translation>
+<translation id="2805756323405976993">അപ്ലിക്കേഷനുകള്‍</translation>
+<translation id="8871072142849158571"><ph name="DISPLAY_NAME"/>, <ph name="RESOLUTION"/> എന്നതിലേക്ക് വലുപ്പം മാറ്റി</translation>
+<translation id="1512064327686280138">സജീവമാക്കല്‍ പരാജയപ്പെട്ടു</translation>
+<translation id="5097002363526479830">'<ph name="NAME"/>' നെറ്റ്‌വര്‍‌ക്കിലേക്ക് ബന്ധിപ്പിക്കുന്നതിൽ പരാജയപ്പെട്ടു: <ph name="DETAILS"/></translation>
+<translation id="1850504506766569011">Wi-Fi ഓഫുചെയ്‌തു.</translation>
+<translation id="8132793192354020517"><ph name="NAME"/> എന്നതിലേക്ക് ബന്ധിപ്പിച്ചു</translation>
+<translation id="7052914147756339792">വാൾപേപ്പർ സജ്ജമാക്കുക...</translation>
+<translation id="8678698760965522072">ഓണ്‍ലൈന്‍ അവസ്ഥ</translation>
+<translation id="2532589005999780174">ഉയർന്ന ദൃശ്യതീവ്രത മോഡ്</translation>
+<translation id="1119447706177454957">ആന്തരിക പിശക്</translation>
+<translation id="3019353588588144572">ബാറ്ററി പൂർണ്ണമായി ചാർജ്ജാകുന്നതിന് ശേഷിക്കുന്ന സമയം, <ph name="TIME_REMAINING"/></translation>
+<translation id="3473479545200714844">സ്‌ക്രീൻ മാഗ്‌നിഫയർ</translation>
+<translation id="7005812687360380971">പരാജയം</translation>
+<translation id="882279321799040148">കാണുന്നതിന് ക്ലിക്കുചെയ്യുക</translation>
+<translation id="5045550434625856497">പാസ്‌വേഡ് തെറ്റാണ്</translation>
+<translation id="1602076796624386989">മൊബൈൽ ഡാറ്റ പ്രാപ്‌തമാക്കുക</translation>
+<translation id="6981982820502123353">പ്രവേശനക്ഷമത</translation>
+<translation id="3157931365184549694">പുനഃസ്ഥാപിക്കുക</translation>
+<translation id="4274292172790327596">തിരിച്ചറിയാത്ത പിശക്</translation>
+<translation id="4032485810211612751"><ph name="HOURS"/>:<ph name="MINUTES"/>:<ph name="SECONDS"/></translation>
+<translation id="225680501294068881">ഉപകരണങ്ങൾക്കായി സ്‌കാൻ ചെയ്യുന്നു...</translation>
+<translation id="5597451508971090205"><ph name="SHORT_WEEKDAY"/>, <ph name="DATE"/></translation>
+<translation id="4448844063988177157">Wi-Fi ശൃംഖലകള്‍ക്കായി തിരയുന്നു...</translation>
+<translation id="8401662262483418323">'<ph name="NAME"/>' എന്നതിലേക്ക് കണക്റ്റുചെയ്യുന്നതിൽ പരാജയപ്പെട്ടു: <ph name="DETAILS"/>
+സെർവർ സന്ദേശം: <ph name="SERVER_MSG"/></translation>
+<translation id="2475982808118771221">ഒരു പിശക് സംഭവിച്ചു</translation>
+<translation id="7229570126336867161">EVDO ആവശ്യമുണ്ട്</translation>
+<translation id="2999742336789313416"><ph name="DOMAIN"/> നിയന്ത്രിക്കുന്ന എല്ലാവർക്കുമുള്ള ഒരു സെഷനാണ് <ph name="DISPLAY_NAME"/></translation>
+<translation id="7029814467594812963">സെഷനിൽ നിന്ന് പുറത്തുകടക്കുക</translation>
+<translation id="8454013096329229812">Wi-Fi ഓൺ ചെയ്‌തു.</translation>
+<translation id="4872237917498892622">Alt+തിരയൽ അല്ലെങ്കിൽ Shift</translation>
+<translation id="2983818520079887040">ക്രമീകരണങ്ങള്‍...</translation>
+<translation id="1717216362413677834">ഡോക്ക് മോഡ്</translation>
+<translation id="8927026611342028580">കണക്റ്റുചെയ്യാൻ അഭ്യർത്ഥിച്ചു</translation>
+<translation id="8300849813060516376">OTASP പരാജയപ്പെട്ടു</translation>
+<translation id="2792498699870441125">Alt+തിരയൽ</translation>
+<translation id="8660803626959853127"><ph name="COUNT"/> ഫയൽ(കൾ) സമന്വയിപ്പിക്കുന്നു</translation>
+<translation id="3709443003275901162">9+</translation>
+<translation id="639644700271529076">CAPS LOCK ഓഫാണ്</translation>
+<translation id="6248847161401822652">പുറത്തുപോകുന്നതിന് രണ്ടുതവണ Control Shift Q അമർത്തുക.</translation>
+<translation id="6267036997247669271"><ph name="NAME"/>: സജീവമാക്കുന്നു...</translation>
+<translation id="1391854757121130358">നിങ്ങൾ മൊബൈൽ ഡാറ്റ അലവൻസ് ഉപയോഗിച്ചിരിക്കാനിടയുണ്ട്.</translation>
+<translation id="5413208160176941586">പ്രാദേശികമായി നിയന്ത്രിക്കപ്പെടുന്ന ഉപയോക്താവ്</translation>
+<translation id="1059194134494239015"><ph name="DISPLAY_NAME"/>: <ph name="RESOLUTION"/></translation>
+<translation id="4864165860509564259">ലോഞ്ചറിന്റെ സ്ഥാനം</translation>
+<translation id="7593891976182323525">തിരയൽ അല്ലെങ്കിൽ Shift</translation>
+<translation id="7649070708921625228">സഹായം</translation>
+<translation id="3050422059534974565">CAPS LOCK ഓൺ ആണ്.
+റദ്ദാക്കുന്നതിന് തിരയൽ അല്ലെങ്കിൽ Shift അമർത്തുക.</translation>
+<translation id="397105322502079400">കണക്കാക്കുന്നു...</translation>
+<translation id="158849752021629804">ഹോം നെറ്റ്‍വര്‍ക്ക് ആവശ്യമുണ്ട്</translation>
+<translation id="6857811139397017780"><ph name="NETWORKSERVICE"/> സജീവമാക്കുക</translation>
+<translation id="5864471791310927901">DHCP തിരയല്‍ പരാജയപ്പെട്ടു</translation>
+<translation id="5812035014844949013">ഔട്ട്‌പുട്ട്</translation>
+<translation id="6692173217867674490">മോശം പാസ്ഫ്രെയ്സ്</translation>
+<translation id="6165508094623778733">കൂടുതല്‍ മനസിലാക്കുക</translation>
+<translation id="9046895021617826162">ബന്ധിപ്പിക്കല്‍ പരാജയപ്പെട്ടു</translation>
+<translation id="973896785707726617"><ph name="SESSION_TIME_REMAINING"/> ആകുമ്പോൾ ഈ സെഷൻ അവസാനിക്കും. നിങ്ങൾ യാന്ത്രികമായി സൈൻ ഔട്ടാകും.</translation>
+<translation id="8372369524088641025">മോശം WEP കീ</translation>
+<translation id="6636709850131805001">തിരിച്ചറിയാത്ത അവസ്ഥ</translation>
+<translation id="3573179567135747900">&quot;<ph name="FROM_LOCALE"/>&quot; എന്നതിലേക്ക് തിരികെ മാറുക (റീസ്റ്റാര്‍ട്ട് ആവശ്യമാണ്)</translation>
+<translation id="8103386449138765447">SMS സന്ദേശങ്ങൾ: <ph name="MESSAGE_COUNT"/></translation>
+<translation id="5045002648206642691">Google ഡ്രൈവ് ക്രമീകരണങ്ങൾ...</translation>
+<translation id="1510238584712386396">ലോഞ്ചർ</translation>
+<translation id="7209101170223508707">CAPS LOCK ഓണാണ്.
+റദ്ദാക്കുന്നതിന് Alt+തിരയൽ അല്ലെങ്കിൽ Shift അമർത്തുക.</translation>
+<translation id="8940956008527784070">ബാറ്ററി കുറവാണ് (<ph name="PERCENTAGE"/>%)</translation>
+<translation id="5102001756192215136"><ph name="HOUR"/>:<ph name="MINUTE"/> ശേഷിക്കുന്നു</translation>
+<translation id="520760366042891468">നിങ്ങളുടെ സ്‌ക്രീനിന്റെ നിയന്ത്രണം Hangouts വഴി പങ്കിടുന്നു.</translation>
+<translation id="8000066093800657092">നെറ്റ്‍വര്‍ക്ക് ഇല്ല</translation>
+<translation id="4015692727874266537">മറ്റൊരു അക്കൗണ്ടിൽ സൈൻ ഇൻ ചെയ്യുക...</translation>
+<translation id="5941711191222866238">ചെറുതാക്കുക‍</translation>
+<translation id="6911468394164995108">മറ്റുള്ളവ ചേർക്കുക...</translation>
+<translation id="412065659894267608">പൂർണ്ണമായും ചാർജാകുന്നതിന് <ph name="HOUR"/>മ <ph name="MINUTE"/>മി</translation>
+<translation id="6359806961507272919"><ph name="PHONE_NUMBER"/> എന്നതില്‍ നിന്നുള്ള SMS</translation>
+<translation id="1244147615850840081">കാരിയര്‍</translation>
+</translationbundle> \ No newline at end of file
diff --git a/chromium/ash/strings/ash_strings_mr.xtb b/chromium/ash/strings/ash_strings_mr.xtb
new file mode 100644
index 00000000000..4705f008a35
--- /dev/null
+++ b/chromium/ash/strings/ash_strings_mr.xtb
@@ -0,0 +1,201 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="mr">
+<translation id="3595596368722241419">बॅटरी पूर्ण चार्ज</translation>
+<translation id="5250713215130379958">लाँचर स्वयं लपवा</translation>
+<translation id="7814236020522506259"><ph name="HOUR"/> आणि <ph name="MINUTE"/></translation>
+<translation id="7880025619322806991">पोर्टल राज्य</translation>
+<translation id="30155388420722288">ओव्हरफ्लो बटण</translation>
+<translation id="5571066253365925590">Bluetooth सक्षम</translation>
+<translation id="9074739597929991885">Bluetooth</translation>
+<translation id="2268130516524549846">Bluetooth अक्षम</translation>
+<translation id="3775358506042162758">आपल्याकडे एकाधिक साइन इनमध्ये फक्त तीन पर्यंत खाती असू शकतात.</translation>
+<translation id="370649949373421643">Wi-fi सक्षम करा</translation>
+<translation id="3626281679859535460">ब्राइटनेस</translation>
+<translation id="8054466585765276473">बॅटरी वेळ गणना करत आहे.</translation>
+<translation id="7982789257301363584">नेटवर्क</translation>
+<translation id="5565793151875479467">प्रॉक्सी...</translation>
+<translation id="938582441709398163">कीबोर्ड आच्छादन</translation>
+<translation id="4387004326333427325">प्रमाणीकरण प्रमाणपत्र दूरस्थपणे नाकारले</translation>
+<translation id="6979158407327259162">Google ड्राइव्ह</translation>
+<translation id="6943836128787782965">HTTP अयशस्वी झाले</translation>
+<translation id="2297568595583585744">स्थिती ट्रे</translation>
+<translation id="1661867754829461514">PIN गहाळ आहे </translation>
+<translation id="4508225577814909926"><ph name="NAME"/>: कनेक्ट करत आहे...</translation>
+<translation id="4237016987259239829">नेटवर्क कनेक्शन त्रुटी</translation>
+<translation id="2946640296642327832">Bluetooth सक्षम करा</translation>
+<translation id="6459472438155181876"><ph name="DISPLAY_NAME"/> मध्ये स्क्रीन विस्तृत करत आहे</translation>
+<translation id="8206859287963243715">सेल्यूलर</translation>
+<translation id="6596816719288285829">IP पत्ता</translation>
+<translation id="4508265954913339219">सक्रियन अयशस्वी</translation>
+<translation id="3621712662352432595">ऑडिओ सेटिंग्ज</translation>
+<translation id="1812696562331527143">आपली इनपुट पद्धत <ph name="INPUT_METHOD_ID"/>*(<ph name="BEGIN_LINK"/>तृतीय पक्षावर<ph name="END_LINK"/>)बदलली.
+स्‍विच करण्‍यासाठी Shift + Alt दाबा.</translation>
+<translation id="2127372758936585790">निम्न-उर्जेचे चार्जर</translation>
+<translation id="3846575436967432996">कोणतीही नेटवर्क माहिती उपलब्ध नाही</translation>
+<translation id="3026237328237090306">मोबाइल डेटा सेटअप करा</translation>
+<translation id="785750925697875037">मोबाइल खाते पहा</translation>
+<translation id="153454903766751181">सेल्युलर मोडेम आरंभ करत आहे...</translation>
+<translation id="4628814525959230255">Hangouts द्वारे <ph name="HELPER_NAME"/> सह आपल्या स्क्रीनचे सामायिकरण नियंत्रण.</translation>
+<translation id="8343941333792395995"><ph name="DISPLAY_NAME"/> फिरवले गेले आ</translation>
+<translation id="7864539943188674973">Bluetooth अक्षम करा</translation>
+<translation id="939252827960237676">स्क्रीनशॉट जतन करण्यात अयशस्वी</translation>
+<translation id="3126069444801937830">अद्यतनासाठी पुनर्प्रारंभ करा</translation>
+<translation id="2268813581635650749">सर्व साइन आउट करा</translation>
+<translation id="735745346212279324">VPN डिस्कनेक्ट केले</translation>
+<translation id="7320906967354320621">निष्क्रिय</translation>
+<translation id="6303423059719347535">बॅटरी <ph name="PERCENTAGE"/>% भरली आहे</translation>
+<translation id="15373452373711364">मोठा माउस कर्सर</translation>
+<translation id="2778346081696727092">प्रदान केलेल्या वापरकर्तानाव किंवा संकेतशब्दासह प्रमाणिकरण करण्‍यात अयशस्वी झाले</translation>
+<translation id="3294437725009624529">अतिथी</translation>
+<translation id="8190698733819146287">भाषा आणि इनपुट सानुकूलित करा...</translation>
+<translation id="2903907270192926896">इनपुट</translation>
+<translation id="8676770494376880701">निम्न-उर्जेचे चार्जर कनेक्ट केले</translation>
+<translation id="7170041865419449892">परिक्षेत्राबाहेर</translation>
+<translation id="4804818685124855865">‍डिस्कनेक्ट</translation>
+<translation id="2544853746127077729">प्रमाणीकरण प्रमाणपत्र नेटवर्कद्वारे नाकारले</translation>
+<translation id="5222676887888702881">साइन आउट करा</translation>
+<translation id="2688477613306174402">कॉन्फिगरेशन</translation>
+<translation id="1272079795634619415">थांबा</translation>
+<translation id="4957722034734105353">अधिक जाणून घ्या...</translation>
+<translation id="2964193600955408481">Wi-Fi अक्षम करा</translation>
+<translation id="811680302244032017">डिव्हाइस जोडा...</translation>
+<translation id="4279490309300973883">मिररिंग</translation>
+<translation id="2509468283778169019">CAPS LOCK सुरु आहे</translation>
+<translation id="3892641579809465218">अंतर्गत डिस्प्ले</translation>
+<translation id="7823564328645135659">आपली सेटिंग्ज समक्रमित केल्यानंतर भाषा &quot;<ph name="FROM_LOCALE"/>&quot; मधून &quot;<ph name="TO_LOCALE"/>&quot; मध्ये बदलली आहे.</translation>
+<translation id="3368922792935385530">कनेक्ट केले</translation>
+<translation id="8340999562596018839">संभाषण अभिप्राय</translation>
+<translation id="8654520615680304441">Wi-Fi चालू करा...</translation>
+<translation id="5825747213122829519">आपली इनपुट पद्धत <ph name="INPUT_METHOD_ID"/> मध्ये बदलली आहे.
+स्विच करण्यासाठी Shift + Alt दाबा.</translation>
+<translation id="2562916301614567480">खाजगी नेटवर्क</translation>
+<translation id="6549021752953852991">कोणतेही सेल्युलर नेटवर्क उपलब्ध नाही</translation>
+<translation id="4379753398862151997">प्रिय मॉनिटर, हे आपल्या दरम्यान कार्य करत नाही. (तो मॉनिटर समर्थित नाही)</translation>
+<translation id="6426039856985689743">मोबाइल डेटा अक्षम करा</translation>
+<translation id="3087734570205094154">तळाकडील</translation>
+<translation id="3742055079367172538">स्क्रीनशॉट घेतला</translation>
+<translation id="8878886163241303700">स्क्रीन विस्तृत करत आहे</translation>
+<translation id="5271016907025319479">VPN कॉन्फिगर केलेले नाही.</translation>
+<translation id="372094107052732682">बाहेर पडण्यासाठी Ctrl+Shift+Q दोनदा दाबा.</translation>
+<translation id="6803622936009808957">समर्थित रिजोल्यूशन न आढळल्यामुळे प्रदर्शने मिरर करू शकली नाहीत. त्याऐवजी विस्तारित डेस्कटॉप प्रविष्ट केला.</translation>
+<translation id="1480041086352807611">डेमो मोड</translation>
+<translation id="3626637461649818317"><ph name="PERCENTAGE"/>% उर्वरित</translation>
+<translation id="9089416786594320554">इनपुट पद्धती</translation>
+<translation id="6247708409970142803"><ph name="PERCENTAGE"/>%</translation>
+<translation id="2614835198358683673">आपले Chromebook चालू असताना ते चार्ज होऊ शकत नाही. अधिकृत चार्जर वापरण्याचा विचार करा.</translation>
+<translation id="1895658205118569222">बंद करा</translation>
+<translation id="4430019312045809116">व्हॉल्यूम</translation>
+<translation id="4442424173763614572">DNS लुकअप अयश्सवी</translation>
+<translation id="6356500677799115505">बॅटरी भरली आहे आणि चार्ज होत आहे</translation>
+<translation id="7874779702599364982">सेल्यूलर नेटवर्कसाठी शोधत आहे...</translation>
+<translation id="583281660410589416">अज्ञात</translation>
+<translation id="1383876407941801731">शोध</translation>
+<translation id="7468789844759750875">अधिक डेटा विकत घेण्यासाठी <ph name="NAME"/> सक्रियण पोर्टलला भेट द्या.</translation>
+<translation id="3901991538546252627"><ph name="NAME"/> वर कनेक्ट करीत आहे</translation>
+<translation id="2204305834655267233">नेटवर्क माहिती</translation>
+<translation id="1621499497873603021">बॅटरी रिक्त होईपर्यंत शिल्लक वेळ, <ph name="TIME_LEFT"/></translation>
+<translation id="5980301590375426705">अतिथी निर्गमन करा</translation>
+<translation id="4471417012762451363">बॅटरी <ph name="PERCENTAGE"/>% भरली आहे आणि चार्ज होत आहे</translation>
+<translation id="8308637677604853869">मागील मेनू</translation>
+<translation id="4666297444214622512">दुसर्‍या खात्यामध्ये साइन इन करू शकत नाही.</translation>
+<translation id="1346748346194534595">उजवे</translation>
+<translation id="1773212559869067373">प्रमाणीकरण प्रमाणपत्र स्थानिकपणे नाकारले</translation>
+<translation id="8528322925433439945">मोबाइल ...</translation>
+<translation id="7049357003967926684">संघटना</translation>
+<translation id="8428213095426709021">सेटिंग्ज</translation>
+<translation id="2372145515558759244">अ‍ॅप्स समक्रमित करत आहे...</translation>
+<translation id="7256405249507348194">अपरिचित त्रुटी: <ph name="DESC"/></translation>
+<translation id="7925247922861151263">AAA तपास अयशस्वी</translation>
+<translation id="8456362689280298700"><ph name="HOUR"/>:<ph name="MINUTE"/> बाकी पूर्ण होण्यात</translation>
+<translation id="5787281376604286451">संभाषण अभिप्राय सक्षम केला आहे.
+अक्षम करण्‍यासाठी Ctrl+Alt+Z दाबा.</translation>
+<translation id="4479639480957787382">इथरनेट</translation>
+<translation id="6312403991423642364">अज्ञात नेटवर्क त्रुटी</translation>
+<translation id="1467432559032391204">डावे</translation>
+<translation id="5543001071567407895">SMS</translation>
+<translation id="2354174487190027830"><ph name="NAME"/> सक्रिय करत आहे</translation>
+<translation id="8814190375133053267">Wi-Fi</translation>
+<translation id="1398853756734560583">वाढवा</translation>
+<translation id="2692809339924654275"><ph name="BLUETOOTH"/>: कनेक्ट करत आहे...</translation>
+<translation id="252373100621549798">अज्ञात प्रदर्शन</translation>
+<translation id="1882897271359938046"><ph name="DISPLAY_NAME"/> वर मिरर करत आहे</translation>
+<translation id="2727977024730340865">एका निम्न-उर्जेच्या चार्जरवर प्लग इन केले. बॅटरी चार्जिंग विश्वसनीय असू शकत नाही.</translation>
+<translation id="3784455785234192852">लॉक करा</translation>
+<translation id="2805756323405976993">अनुप्रयोग</translation>
+<translation id="8871072142849158571"><ph name="DISPLAY_NAME"/> चा आकार <ph name="RESOLUTION"/> मध्ये बदलण्यात आला</translation>
+<translation id="1512064327686280138">सक्रियन अयशस्वी</translation>
+<translation id="5097002363526479830">'<ph name="NAME"/>': नेटवर्कशी कनेक्ट करण्यात अयशस्वी. <ph name="DETAILS"/></translation>
+<translation id="1850504506766569011">Wi-Fi बंद आहे.</translation>
+<translation id="8132793192354020517"><ph name="NAME"/> शी कनेक्ट केलेले</translation>
+<translation id="7052914147756339792">वॉलपेपर सेट करा...</translation>
+<translation id="8678698760965522072">ऑनलाइन राज्य</translation>
+<translation id="2532589005999780174">उच्च तीव्रता मोड</translation>
+<translation id="1119447706177454957">अंतर्गत त्रुटी</translation>
+<translation id="3019353588588144572">बॅटरी पूर्णपणे चार्ज होईपर्यंत उर्वरित वेळ, <ph name="TIME_REMAINING"/></translation>
+<translation id="3473479545200714844">स्क्रीन भिंग</translation>
+<translation id="7005812687360380971">बिघाड</translation>
+<translation id="882279321799040148">पाहण्यासाठी क्लिक करा</translation>
+<translation id="5045550434625856497">अयोग्य संकेतशब्द</translation>
+<translation id="1602076796624386989">मोबाइल डेटा सक्षम करा</translation>
+<translation id="6981982820502123353">प्रवेशयोग्यता</translation>
+<translation id="3157931365184549694">पुनर्संचयित करा</translation>
+<translation id="4274292172790327596">अपरिचित त्रुटी</translation>
+<translation id="4032485810211612751"><ph name="HOURS"/>:<ph name="MINUTES"/>:<ph name="SECONDS"/></translation>
+<translation id="225680501294068881">डिव्हाइसेससाठी स्कॅन करत आहे...</translation>
+<translation id="5597451508971090205"><ph name="SHORT_WEEKDAY"/>, <ph name="DATE"/></translation>
+<translation id="4448844063988177157">Wi-Fi नेटवर्कचा शोधत आहे...</translation>
+<translation id="8401662262483418323">'<ph name="NAME"/>' शी कनेक्ट करण्यात अयशस्वी: <ph name="DETAILS"/>
+सर्व्हर संदेश: <ph name="SERVER_MSG"/></translation>
+<translation id="2475982808118771221">एक त्रुटी आली आहे</translation>
+<translation id="7229570126336867161">EVDO आवश्यक आहे</translation>
+<translation id="2999742336789313416"><ph name="DISPLAY_NAME"/> हे <ph name="DOMAIN"/> द्वारे व्यवस्थापित कलेले एक सावर्जनिक सत्र आहे</translation>
+<translation id="7029814467594812963">सत्र निर्गमन करा</translation>
+<translation id="8454013096329229812">Wi-Fi चालू आहे.</translation>
+<translation id="4872237917498892622">Alt+Search किंवा Shift</translation>
+<translation id="2983818520079887040">सेटिंग्ज...</translation>
+<translation id="1717216362413677834">डॉक मोड</translation>
+<translation id="8927026611342028580">विनंती केलेले कनेक्ट करा</translation>
+<translation id="8300849813060516376">OTASP बिघडले</translation>
+<translation id="2792498699870441125">Alt+Search</translation>
+<translation id="8660803626959853127"><ph name="COUNT"/> फाईल(ली) समक्रमित करीत आहे</translation>
+<translation id="3709443003275901162">9+</translation>
+<translation id="639644700271529076">CAPS LOCK बंद आहे</translation>
+<translation id="6248847161401822652">बाहेर पडण्यासाठी Control Shift Q दोनदा दाबा.</translation>
+<translation id="6267036997247669271"><ph name="NAME"/>: सक्रिय करत आहे...</translation>
+<translation id="1391854757121130358">आपण आपल्या मोबाईल डेटा भत्त्याचा वापर केला असेल.</translation>
+<translation id="5413208160176941586">स्थानिकरित्या व्यवस्थापित वापरकर्ता</translation>
+<translation id="1059194134494239015"><ph name="DISPLAY_NAME"/>: <ph name="RESOLUTION"/></translation>
+<translation id="4864165860509564259">लाँचर स्थिती</translation>
+<translation id="7593891976182323525">Search किंवा Shift</translation>
+<translation id="7649070708921625228">मदत</translation>
+<translation id="3050422059534974565">CAPS LOCK चालू आहे.
+रद्द करण्यासाठी Search किंवा Shift दाबा.</translation>
+<translation id="397105322502079400">गणना करत आहे...</translation>
+<translation id="158849752021629804">मुख्यपृष्ठ नेटवर्क आवश्यक</translation>
+<translation id="6857811139397017780">सक्रिय करा<ph name="NETWORKSERVICE"/></translation>
+<translation id="5864471791310927901">DHCP लुकअप अयशस्वी</translation>
+<translation id="5812035014844949013">आउटपुट</translation>
+<translation id="6692173217867674490">चुकीचा सांकेतिक वाक्यांश</translation>
+<translation id="6165508094623778733">अधिक जाणून घ्या</translation>
+<translation id="9046895021617826162">कनेक्ट करण्यात अयशस्वी</translation>
+<translation id="973896785707726617">हे सत्र <ph name="SESSION_TIME_REMAINING"/> मध्ये समाप्त होईल. आपल्याला स्वयंचलितपणे साइन आउट केले जाईल.</translation>
+<translation id="8372369524088641025">खराब WEP की</translation>
+<translation id="6636709850131805001">अपरिचित राज्य</translation>
+<translation id="3573179567135747900">&quot;<ph name="FROM_LOCALE"/>&quot; मध्ये परत बदला (रीस्टार्ट करणे आवश्यक)</translation>
+<translation id="8103386449138765447">SMS संदेश: <ph name="MESSAGE_COUNT"/></translation>
+<translation id="5045002648206642691">Google ड्राइव्ह सेटिंग्ज...</translation>
+<translation id="1510238584712386396">लाँचर</translation>
+<translation id="7209101170223508707">CAPS LOCK चालू आहे.
+रद्द करण्यासाठी Alt+Search किंवा Shift दाबा.</translation>
+<translation id="8940956008527784070">बॅटरी कमी झाली (<ph name="PERCENTAGE"/>%)</translation>
+<translation id="5102001756192215136"><ph name="HOUR"/>:<ph name="MINUTE"/> शिल्‍लक</translation>
+<translation id="520760366042891468">Hangouts द्वारे आपल्या स्क्रीनचे नियंत्रण सामायिक करत आहे.</translation>
+<translation id="8000066093800657092">कोणतेही नेटवर्क नाही</translation>
+<translation id="4015692727874266537">दुसऱ्या खात्यात साइन इन करा...</translation>
+<translation id="5941711191222866238">लहान करा</translation>
+<translation id="6911468394164995108">दुसरीकडे सामील व्हा...</translation>
+<translation id="412065659894267608">पूर्ण होईपर्यंत <ph name="HOUR"/>ता <ph name="MINUTE"/>मि</translation>
+<translation id="6359806961507272919"><ph name="PHONE_NUMBER"/> कडून SMS</translation>
+<translation id="1244147615850840081">कॅरियर</translation>
+</translationbundle> \ No newline at end of file
diff --git a/chromium/ash/strings/ash_strings_ms.xtb b/chromium/ash/strings/ash_strings_ms.xtb
new file mode 100644
index 00000000000..e701bf92582
--- /dev/null
+++ b/chromium/ash/strings/ash_strings_ms.xtb
@@ -0,0 +1,200 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="ms">
+<translation id="3595596368722241419">Bateri penuh</translation>
+<translation id="5250713215130379958">Autosembunyi pelancar</translation>
+<translation id="7814236020522506259"><ph name="HOUR"/> dan <ph name="MINUTE"/></translation>
+<translation id="7880025619322806991">Keadaan portal</translation>
+<translation id="30155388420722288">Butang Limpahan</translation>
+<translation id="5571066253365925590">Bluetooth didayakan</translation>
+<translation id="9074739597929991885">Bluetooth</translation>
+<translation id="2268130516524549846">Bluetooth dilumpuhkan</translation>
+<translation id="3775358506042162758">Anda hanya boleh mempunyai sehingga tiga akaun dalam log masuk berbilang.</translation>
+<translation id="370649949373421643">Dayakan Wi-Fi</translation>
+<translation id="3626281679859535460">Kecerahan</translation>
+<translation id="8054466585765276473">Mengira tempoh bateri.</translation>
+<translation id="7982789257301363584">Rangkaian</translation>
+<translation id="5565793151875479467">Proksi...</translation>
+<translation id="938582441709398163">Tindihan Papan Kekunci</translation>
+<translation id="4387004326333427325">Sijil pengesahan ditolak dari jauh</translation>
+<translation id="6979158407327259162">Google Drive</translation>
+<translation id="6943836128787782965">HTTP gagal</translation>
+<translation id="2297568595583585744">Dulang status</translation>
+<translation id="1661867754829461514">PIN tiada</translation>
+<translation id="4508225577814909926"><ph name="NAME"/>: Menyambung...</translation>
+<translation id="4237016987259239829">Ralat Sambungan Rangkaian</translation>
+<translation id="2946640296642327832">Dayakan Bluetooth</translation>
+<translation id="6459472438155181876">Melanjutkan skrin ke <ph name="DISPLAY_NAME"/></translation>
+<translation id="8206859287963243715">Selular</translation>
+<translation id="6596816719288285829">Alamat IP</translation>
+<translation id="4508265954913339219">Pengaktifan gagal</translation>
+<translation id="3621712662352432595">Tetapan Audio</translation>
+<translation id="1812696562331527143">Kaedah masukan anda telah ditukar kepada <ph name="INPUT_METHOD_ID"/>*(<ph name="BEGIN_LINK"/>pihak ke-3<ph name="END_LINK"/>).
+Tekan Shift + Alt untuk menukar.</translation>
+<translation id="2127372758936585790">Pengecas berkuasa rendah</translation>
+<translation id="3846575436967432996">Tiada maklumat rangkaian tersedia</translation>
+<translation id="3026237328237090306">Sediakan data mudah alih</translation>
+<translation id="785750925697875037">Lihat akaun mudah alih</translation>
+<translation id="153454903766751181">Memulakan modem selular...</translation>
+<translation id="4628814525959230255">Berkongsi kawalan skrin anda dengan <ph name="HELPER_NAME"/> melalui Hangouts.</translation>
+<translation id="8343941333792395995"><ph name="DISPLAY_NAME"/> telah diputar</translation>
+<translation id="7864539943188674973">Lumpuhkan Bluetooth</translation>
+<translation id="939252827960237676">Gagal menyimpan tangkapan skrin</translation>
+<translation id="3126069444801937830">Mulakan semula untuk mengemas kini</translation>
+<translation id="2268813581635650749">Log keluar semua</translation>
+<translation id="735745346212279324">VPN diputuskan sambungan</translation>
+<translation id="7320906967354320621">Melahu</translation>
+<translation id="6303423059719347535">Bateri <ph name="PERCENTAGE"/>% penuh</translation>
+<translation id="15373452373711364">Kursor tetikus besar</translation>
+<translation id="2778346081696727092">Gagal untuk mengesahkan dengan nama pengguna atau kata laluan yang disediakan</translation>
+<translation id="3294437725009624529">Tetamu</translation>
+<translation id="8190698733819146287">Sesuaikan bahasa dan input...</translation>
+<translation id="2903907270192926896">INPUT</translation>
+<translation id="8676770494376880701">Pengecas berkuasa rendah disambungkan</translation>
+<translation id="7170041865419449892">Di luar lingkungan</translation>
+<translation id="4804818685124855865">Putuskan sambungan</translation>
+<translation id="2544853746127077729">Sijil pengesahan ditolak oleh rangkaian</translation>
+<translation id="5222676887888702881">Log keluar</translation>
+<translation id="2688477613306174402">Konfigurasi</translation>
+<translation id="1272079795634619415">Berhenti</translation>
+<translation id="4957722034734105353">Ketahui lebih lanjut...</translation>
+<translation id="2964193600955408481">Lumpuhkan Wi-Fi</translation>
+<translation id="811680302244032017">Tambah peranti...</translation>
+<translation id="4279490309300973883">Pencerminan</translation>
+<translation id="2509468283778169019">Kekunci CAPS LOCK dihidupkan</translation>
+<translation id="3892641579809465218">Paparan Dalaman</translation>
+<translation id="7823564328645135659">Bahasa telah ditukar daripada &quot;<ph name="FROM_LOCALE"/>&quot; kepada &quot;<ph name="TO_LOCALE"/>&quot; selepas menyegerakkan tetapan anda.</translation>
+<translation id="3368922792935385530">Disambungkan</translation>
+<translation id="8340999562596018839">Maklum balas dituturkan</translation>
+<translation id="8654520615680304441">Hidupkan Wi-Fi...</translation>
+<translation id="5825747213122829519">Kaedah masukan anda telah ditukar kepada <ph name="INPUT_METHOD_ID"/>.
+Tekan Shift + Alt untuk menukar.</translation>
+<translation id="2562916301614567480">Rangkaian Persendirian</translation>
+<translation id="6549021752953852991">Tiada rangkaian selular tersedia</translation>
+<translation id="4379753398862151997">Malang sekali, tidak wujud keserasian antara kita, Monitor. (Monitor itu tidak disokong)</translation>
+<translation id="6426039856985689743">Lumpuhkan data mudah alih</translation>
+<translation id="3087734570205094154">Bawah</translation>
+<translation id="3742055079367172538">Tangkapan skrin diambil</translation>
+<translation id="8878886163241303700">Meluaskan skrin</translation>
+<translation id="5271016907025319479">VPN tidak dikonfigurasi.</translation>
+<translation id="372094107052732682">Tekan Ctrl+Shift+Q dua kali untuk keluar.</translation>
+<translation id="6803622936009808957">Tidak dapat membalikkan paparan memandangkan tiada peleraian disokong ditemui. Sebaliknya, memasuki mod desktop yang dilanjutkan.</translation>
+<translation id="1480041086352807611">Mod tunjuk cara</translation>
+<translation id="3626637461649818317">Tinggal <ph name="PERCENTAGE"/>%</translation>
+<translation id="9089416786594320554">Kaedah input</translation>
+<translation id="6247708409970142803"><ph name="PERCENTAGE"/>%</translation>
+<translation id="2614835198358683673">Chromebook anda tidak boleh dicas semasa dihidupkan. Pertimbangkan untuk menggunakan pengecas rasmi.</translation>
+<translation id="1895658205118569222">Tutup</translation>
+<translation id="4430019312045809116">Kelantangan</translation>
+<translation id="4442424173763614572">Carian DNS gagal</translation>
+<translation id="6356500677799115505">Bateri penuh dan sedang dicas.</translation>
+<translation id="7874779702599364982">Mencari rangkaian selular...</translation>
+<translation id="583281660410589416">Tidak diketahui</translation>
+<translation id="1383876407941801731">Cari</translation>
+<translation id="7468789844759750875">Lawati portal pengaktifan <ph name="NAME"/> untuk membeli lagi data.</translation>
+<translation id="3901991538546252627">Menyambung ke <ph name="NAME"/></translation>
+<translation id="2204305834655267233">Maklumat Rangkaian</translation>
+<translation id="1621499497873603021">Masa yang tinggal sehingga bateri kosong, <ph name="TIME_LEFT"/></translation>
+<translation id="5980301590375426705">Keluar dari tetamu</translation>
+<translation id="4471417012762451363">Bateri <ph name="PERCENTAGE"/>% penuh dan sedang dicas</translation>
+<translation id="8308637677604853869">Menu sebelumnya</translation>
+<translation id="4666297444214622512">Tidak boleh log masuk ke akaun lain.</translation>
+<translation id="1346748346194534595">Kanan</translation>
+<translation id="1773212559869067373">Sijil pengesahan ditolak secara setempat</translation>
+<translation id="8528322925433439945">Mudah alih ...</translation>
+<translation id="7049357003967926684">Persekutuan</translation>
+<translation id="8428213095426709021">Tetapan</translation>
+<translation id="2372145515558759244">Menyegerakkan aplikasi...</translation>
+<translation id="7256405249507348194">Ralat yang tidak dikenal pasti: <ph name="DESC"/></translation>
+<translation id="7925247922861151263">Gagal periksa AAA</translation>
+<translation id="8456362689280298700"><ph name="HOUR"/>:<ph name="MINUTE"/> sehingga penuh</translation>
+<translation id="5787281376604286451">Maklum balas dituturkan didayakan.
+Tekan Ctrl+Alt+Z untuk melumpuhkan.</translation>
+<translation id="4479639480957787382">Ethernet</translation>
+<translation id="6312403991423642364">Ralat rangkaian tidak diketahui</translation>
+<translation id="1467432559032391204">Kiri</translation>
+<translation id="5543001071567407895">SMS</translation>
+<translation id="2354174487190027830">Mengaktifkan <ph name="NAME"/></translation>
+<translation id="8814190375133053267">Wi-Fi</translation>
+<translation id="1398853756734560583">Maksimumkan</translation>
+<translation id="2692809339924654275"><ph name="BLUETOOTH"/>: Menyambung...</translation>
+<translation id="252373100621549798">Paparan Tidak Diketahui</translation>
+<translation id="1882897271359938046">Mencerminkan <ph name="DISPLAY_NAME"/></translation>
+<translation id="2727977024730340865">Dipalamkan pada pengecas berkuasa rendah. Pengecasan bateri mungkin tidak boleh diharapkan.</translation>
+<translation id="3784455785234192852">Kunci</translation>
+<translation id="2805756323405976993">Apl</translation>
+<translation id="8871072142849158571"><ph name="DISPLAY_NAME"/> telah diubah saiz kepada <ph name="RESOLUTION"/></translation>
+<translation id="1512064327686280138">Gagal pengaktifan</translation>
+<translation id="5097002363526479830">Gagal untuk bersambung ke rangkaian '<ph name="NAME"/>': <ph name="DETAILS"/></translation>
+<translation id="1850504506766569011">Wi-Fi dimatikan.</translation>
+<translation id="8132793192354020517">Disambungkan ke <ph name="NAME"/></translation>
+<translation id="7052914147756339792">Tetapkan kertas dinding...</translation>
+<translation id="8678698760965522072">Keadaan dalam talian</translation>
+<translation id="2532589005999780174">Mod kontras tinggi</translation>
+<translation id="1119447706177454957">Ralat dalaman</translation>
+<translation id="3019353588588144572">Masa yang tinggal sehingga bateri dicas sepenuhnya, <ph name="TIME_REMAINING"/></translation>
+<translation id="3473479545200714844">Penggadang skrin</translation>
+<translation id="7005812687360380971">Kegagalan</translation>
+<translation id="882279321799040148">Klik untuk melihat</translation>
+<translation id="5045550434625856497">Kata laluan tidak sah</translation>
+<translation id="1602076796624386989">Dayakan data mudah alih</translation>
+<translation id="6981982820502123353">Kebolehcapaian</translation>
+<translation id="3157931365184549694">Pulihkan</translation>
+<translation id="4274292172790327596">Ralat tidak dikenali</translation>
+<translation id="4032485810211612751"><ph name="HOURS"/>:<ph name="MINUTES"/>:<ph name="SECONDS"/></translation>
+<translation id="225680501294068881">Mengimbas untuk peranti...</translation>
+<translation id="5597451508971090205"><ph name="SHORT_WEEKDAY"/>, <ph name="DATE"/></translation>
+<translation id="4448844063988177157">Mencari rangkaian Wi-Fi...</translation>
+<translation id="8401662262483418323">Gagal untuk menyambung ke '<ph name="NAME"/>': <ph name="DETAILS"/>
+Mesej pelayan: <ph name="SERVER_MSG"/></translation>
+<translation id="2475982808118771221">Ralat berlaku</translation>
+<translation id="7229570126336867161">Perlukan EVDO</translation>
+<translation id="2999742336789313416"><ph name="DISPLAY_NAME"/> ialah sesi awam yang diurus oleh <ph name="DOMAIN"/></translation>
+<translation id="7029814467594812963">Keluar dari sesi</translation>
+<translation id="8454013096329229812">Wi-Fi dihidupkan.</translation>
+<translation id="4872237917498892622">Alt+Search atau Shift</translation>
+<translation id="2983818520079887040">Tetapan...</translation>
+<translation id="1717216362413677834">Mod dok</translation>
+<translation id="8927026611342028580">Sambungan Diminta</translation>
+<translation id="8300849813060516376">OTASP gagal</translation>
+<translation id="2792498699870441125">Alt+Search</translation>
+<translation id="8660803626959853127">Menyegerakkan <ph name="COUNT"/> fail</translation>
+<translation id="3709443003275901162">9+</translation>
+<translation id="639644700271529076">Kekunci CAPS LOCK dimatikan</translation>
+<translation id="6248847161401822652">Tekan Control Shift Q dua kali untuk keluar.</translation>
+<translation id="6267036997247669271"><ph name="NAME"/>: Mengaktifkan...</translation>
+<translation id="1391854757121130358">Anda mungkin telah menghabiskan peruntukan data mudah alih anda.</translation>
+<translation id="5413208160176941586">Pengguna terurus setempat</translation>
+<translation id="1059194134494239015"><ph name="DISPLAY_NAME"/>: <ph name="RESOLUTION"/></translation>
+<translation id="4864165860509564259">Kedudukan pelancar</translation>
+<translation id="7593891976182323525">Search atau Shift</translation>
+<translation id="7649070708921625228">Bantuan</translation>
+<translation id="3050422059534974565">CAPS LOCK dihidupkan. Tekan Search atau Shift untuk membatalkan.</translation>
+<translation id="397105322502079400">Mengira...</translation>
+<translation id="158849752021629804">Perlukan rangkaian rumah</translation>
+<translation id="6857811139397017780">Aktifkan <ph name="NETWORKSERVICE"/></translation>
+<translation id="5864471791310927901">Carian DHCP gagal</translation>
+<translation id="5812035014844949013">OUTPUT</translation>
+<translation id="6692173217867674490">Frasa laluan teruk</translation>
+<translation id="6165508094623778733">Ketahui lebih lanjut</translation>
+<translation id="9046895021617826162">Gagal disambungkan</translation>
+<translation id="973896785707726617">Sesi ini akan berakhir dalam <ph name="SESSION_TIME_REMAINING"/>. Anda akan dilog keluar secara automatik.</translation>
+<translation id="8372369524088641025">Kekunci WEP teruk</translation>
+<translation id="6636709850131805001">Keadaan tidak dikenali</translation>
+<translation id="3573179567135747900">Tukar kembali kepada &quot;<ph name="FROM_LOCALE"/>&quot; (perlu dimulakan semula)</translation>
+<translation id="8103386449138765447">Mesej SMS: <ph name="MESSAGE_COUNT"/></translation>
+<translation id="5045002648206642691">Tetapan Google Drive</translation>
+<translation id="1510238584712386396">Pelancar</translation>
+<translation id="7209101170223508707">CAPS LOCK dihidupkan.
+Tekan Alt+Search atau Shift untuk membatalkan.</translation>
+<translation id="8940956008527784070">Bateri lemah (<ph name="PERCENTAGE"/>%)</translation>
+<translation id="5102001756192215136"><ph name="HOUR"/>:<ph name="MINUTE"/> tinggal</translation>
+<translation id="520760366042891468">Berkongsi kawalan skrin anda melalui Hangouts.</translation>
+<translation id="8000066093800657092">Tiada rangkaian</translation>
+<translation id="4015692727874266537">Log masuk akaun lain...</translation>
+<translation id="5941711191222866238">Minimumkan</translation>
+<translation id="6911468394164995108">Sertai yang lain...</translation>
+<translation id="412065659894267608"><ph name="HOUR"/>j <ph name="MINUTE"/>m sehingga penuh</translation>
+<translation id="6359806961507272919">SMS daripada <ph name="PHONE_NUMBER"/></translation>
+<translation id="1244147615850840081">Pembawa</translation>
+</translationbundle> \ No newline at end of file
diff --git a/chromium/ash/strings/ash_strings_nl.xtb b/chromium/ash/strings/ash_strings_nl.xtb
new file mode 100644
index 00000000000..e92a4a594e3
--- /dev/null
+++ b/chromium/ash/strings/ash_strings_nl.xtb
@@ -0,0 +1,201 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="nl">
+<translation id="3595596368722241419">Accu is vol</translation>
+<translation id="5250713215130379958">Opstartprogramma automatisch verbergen</translation>
+<translation id="7814236020522506259"><ph name="HOUR"/> en <ph name="MINUTE"/></translation>
+<translation id="7880025619322806991">Status van portal</translation>
+<translation id="30155388420722288">Overloopknop</translation>
+<translation id="5571066253365925590">Bluetooth ingeschakeld</translation>
+<translation id="9074739597929991885">Bluetooth</translation>
+<translation id="2268130516524549846">Bluetooth uitgeschakeld</translation>
+<translation id="3775358506042162758">U kunt maximaal drie accounts hebben in Toegang tot meerdere accounts</translation>
+<translation id="370649949373421643">Wifi inschakelen</translation>
+<translation id="3626281679859535460">Helderheid</translation>
+<translation id="8054466585765276473">Accuduur berekenen.</translation>
+<translation id="7982789257301363584">Netwerk</translation>
+<translation id="5565793151875479467">Proxy...</translation>
+<translation id="938582441709398163">Overlay voor toetsenbord</translation>
+<translation id="4387004326333427325">Verificatiecertificaat afgewezen op afstand</translation>
+<translation id="6979158407327259162">Google Drive</translation>
+<translation id="6943836128787782965">Ophalen van HTTP mislukt</translation>
+<translation id="2297568595583585744">Statussysteemvak</translation>
+<translation id="1661867754829461514">Pincode ontbreekt</translation>
+<translation id="4508225577814909926"><ph name="NAME"/>: verbinden...</translation>
+<translation id="4237016987259239829">Fout bij netwerkverbinding</translation>
+<translation id="2946640296642327832">Bluetooth inschakelen</translation>
+<translation id="6459472438155181876">Scherm uitbreiden naar <ph name="DISPLAY_NAME"/></translation>
+<translation id="8206859287963243715">Mobiel</translation>
+<translation id="6596816719288285829">IP-adres</translation>
+<translation id="4508265954913339219">Activering mislukt</translation>
+<translation id="3621712662352432595">Audio-instellingen</translation>
+<translation id="1812696562331527143">Uw invoermethode is gewijzigd in <ph name="INPUT_METHOD_ID"/>*(<ph name="BEGIN_LINK"/>derden<ph name="END_LINK"/>).
+Druk op Shift + Alt om te schakelen.j</translation>
+<translation id="2127372758936585790">Laag-vermogen-lader</translation>
+<translation id="3846575436967432996">Geen netwerkinformatie beschikbaar</translation>
+<translation id="3026237328237090306">Mobiele gegevens instellen</translation>
+<translation id="785750925697875037">Mobiel account weergeven</translation>
+<translation id="153454903766751181">Mobiele modem initialiseren...</translation>
+<translation id="4628814525959230255">Controle over uw scherm delen met <ph name="HELPER_NAME"/> via Hangouts.</translation>
+<translation id="8343941333792395995"><ph name="DISPLAY_NAME"/> is gedraaid</translation>
+<translation id="7864539943188674973">Bluetooth uitschakelen</translation>
+<translation id="939252827960237676">Kan screenshot niet opslaan</translation>
+<translation id="3126069444801937830">Opnieuw starten voor bijwerken</translation>
+<translation id="2268813581635650749">Alle gebruikers uitloggen</translation>
+<translation id="735745346212279324">Verbinding met VPN verbroken</translation>
+<translation id="7320906967354320621">Inactief</translation>
+<translation id="6303423059719347535">De accu is <ph name="PERCENTAGE"/>% vol</translation>
+<translation id="15373452373711364">Grote muisaanwijzer</translation>
+<translation id="2778346081696727092">Kan niet verifiëren met deze gebruikersnaam of dit wachtwoord</translation>
+<translation id="3294437725009624529">Gast</translation>
+<translation id="8190698733819146287">Talen en invoer aanpassen...</translation>
+<translation id="2903907270192926896">INVOER</translation>
+<translation id="8676770494376880701">Laag-vermogen-lader aangesloten</translation>
+<translation id="7170041865419449892">Geen bereik</translation>
+<translation id="4804818685124855865">Verbinding verbreken</translation>
+<translation id="2544853746127077729">Verificatiecertificaat geweigerd door netwerk</translation>
+<translation id="5222676887888702881">Uitloggen</translation>
+<translation id="2688477613306174402">Configuratie</translation>
+<translation id="1272079795634619415">Stop</translation>
+<translation id="4957722034734105353">Meer informatie...</translation>
+<translation id="2964193600955408481">Wifi uitschakelen</translation>
+<translation id="811680302244032017">Apparaat toevoegen...</translation>
+<translation id="4279490309300973883">Mirroring</translation>
+<translation id="2509468283778169019">CAPS LOCK is ingeschakeld</translation>
+<translation id="3892641579809465218">Interne display</translation>
+<translation id="7823564328645135659">Na het synchroniseren met uw instellingen, is de taal gewijzigd van '<ph name="FROM_LOCALE"/>' in '<ph name="TO_LOCALE"/>'.</translation>
+<translation id="3368922792935385530">Verbonden</translation>
+<translation id="8340999562596018839">Gesproken feedback</translation>
+<translation id="8654520615680304441">Wifi inschakelen...</translation>
+<translation id="5825747213122829519">Uw invoermethode is gewijzigd in <ph name="INPUT_METHOD_ID"/>.
+Druk op Shift + Alt om te schakelen.</translation>
+<translation id="2562916301614567480">Privénetwerk</translation>
+<translation id="6549021752953852991">Geen mobiel netwerk beschikbaar</translation>
+<translation id="4379753398862151997">Het lijkt erop dat dit niet werkt. Het beeldscherm wordt niet ondersteund.</translation>
+<translation id="6426039856985689743">Mobiele gegevens uitschakelen</translation>
+<translation id="3087734570205094154">Onderaan</translation>
+<translation id="3742055079367172538">Screenshot gemaakt</translation>
+<translation id="8878886163241303700">Uitgebreid scherm</translation>
+<translation id="5271016907025319479">VPN is niet geconfigureerd.</translation>
+<translation id="372094107052732682">Druk twee keer op Ctrl+Shift+Q om te stoppen.</translation>
+<translation id="6803622936009808957">Kan schermen niet spiegelen, omdat er geen ondersteunde resoluties zijn gevonden. Het uitgebreide bureaublad is geactiveerd.</translation>
+<translation id="1480041086352807611">Demomodus</translation>
+<translation id="3626637461649818317"><ph name="PERCENTAGE"/>% resterend</translation>
+<translation id="9089416786594320554">Invoermethoden</translation>
+<translation id="6247708409970142803"><ph name="PERCENTAGE"/>%</translation>
+<translation id="2614835198358683673">Uw Chromebook wordt mogelijk niet opgeladen wanneer deze is ingeschakeld. Overweeg het gebruik van een officiële lader.</translation>
+<translation id="1895658205118569222">Uitschakeling</translation>
+<translation id="4430019312045809116">Volume</translation>
+<translation id="4442424173763614572">DNS-lookup mislukt</translation>
+<translation id="6356500677799115505">De accu is vol en wordt opgeladen.</translation>
+<translation id="7874779702599364982">Zoeken naar mobiele netwerken...</translation>
+<translation id="583281660410589416">Onbekend</translation>
+<translation id="1383876407941801731">Zoeken</translation>
+<translation id="7468789844759750875">Ga naar de activeringsportal <ph name="NAME"/> om meer gegevens te kopen.</translation>
+<translation id="3901991538546252627">Verbinding maken met <ph name="NAME"/></translation>
+<translation id="2204305834655267233">Netwerkinfo</translation>
+<translation id="1621499497873603021">Resterende tijd totdat de accu leeg is: <ph name="TIME_LEFT"/></translation>
+<translation id="5980301590375426705">Gastsessie sluiten</translation>
+<translation id="4471417012762451363">De accu is <ph name="PERCENTAGE"/>% vol en wordt opgeladen</translation>
+<translation id="8308637677604853869">Vorig menu</translation>
+<translation id="4666297444214622512">Kan niet inloggen op een ander account.</translation>
+<translation id="1346748346194534595">Rechts</translation>
+<translation id="1773212559869067373">Verificatiecertificaat lokaal geweigerd</translation>
+<translation id="8528322925433439945">Mobiel...</translation>
+<translation id="7049357003967926684">Verbinding</translation>
+<translation id="8428213095426709021">Instellingen</translation>
+<translation id="2372145515558759244">Apps synchroniseren...</translation>
+<translation id="7256405249507348194">Onbekende fout: <ph name="DESC"/></translation>
+<translation id="7925247922861151263">AAA-controle mislukt</translation>
+<translation id="8456362689280298700"><ph name="HOUR"/>:<ph name="MINUTE"/> tot vol</translation>
+<translation id="5787281376604286451">Gesproken feedback is ingeschakeld.
+Druk op Ctrl+Alt+Z om uit te schakelen.</translation>
+<translation id="4479639480957787382">Ethernet</translation>
+<translation id="6312403991423642364">Onbekende netwerkfout</translation>
+<translation id="1467432559032391204">Links</translation>
+<translation id="5543001071567407895">SMS</translation>
+<translation id="2354174487190027830"><ph name="NAME"/> activeren</translation>
+<translation id="8814190375133053267">Wifi</translation>
+<translation id="1398853756734560583">Maximaliseren</translation>
+<translation id="2692809339924654275"><ph name="BLUETOOTH"/>: verbinden...</translation>
+<translation id="252373100621549798">Onbekend display</translation>
+<translation id="1882897271359938046">Spiegelen naar <ph name="DISPLAY_NAME"/></translation>
+<translation id="2727977024730340865">Aangesloten op een laag-vermogen-lader. Opladen van de batterij mogelijk niet betrouwbaar.</translation>
+<translation id="3784455785234192852">Vergrendelen</translation>
+<translation id="2805756323405976993">Applicaties</translation>
+<translation id="8871072142849158571">Het formaat van <ph name="DISPLAY_NAME"/> is aangepast naar <ph name="RESOLUTION"/></translation>
+<translation id="1512064327686280138">Activering mislukt</translation>
+<translation id="5097002363526479830">Kan geen verbinding maken met het netwerk '<ph name="NAME"/>': <ph name="DETAILS"/></translation>
+<translation id="1850504506766569011">Wifi is uitgeschakeld.</translation>
+<translation id="8132793192354020517">Verbonden met <ph name="NAME"/></translation>
+<translation id="7052914147756339792">Achtergrond instellen...</translation>
+<translation id="8678698760965522072">Online status</translation>
+<translation id="2532589005999780174">Modus voor hoog contrast</translation>
+<translation id="1119447706177454957">Interne fout</translation>
+<translation id="3019353588588144572">Resterende tijd totdat de accu volledig is opgeladen: <ph name="TIME_REMAINING"/></translation>
+<translation id="3473479545200714844">Vergrootglas</translation>
+<translation id="7005812687360380971">Mislukt</translation>
+<translation id="882279321799040148">Klik om te bekijken</translation>
+<translation id="5045550434625856497">Onjuist wachtwoord</translation>
+<translation id="1602076796624386989">Mobiele gegevens inschakelen</translation>
+<translation id="6981982820502123353">Toegankelijkheid</translation>
+<translation id="3157931365184549694">Herstellen</translation>
+<translation id="4274292172790327596">Onbekende fout</translation>
+<translation id="4032485810211612751"><ph name="HOURS"/>:<ph name="MINUTES"/>:<ph name="SECONDS"/></translation>
+<translation id="225680501294068881">Scannen naar apparaten...</translation>
+<translation id="5597451508971090205"><ph name="SHORT_WEEKDAY"/>, <ph name="DATE"/></translation>
+<translation id="4448844063988177157">Zoeken naar wifi-netwerken...</translation>
+<translation id="8401662262483418323">Verbinding maken met '<ph name="NAME"/>' is mislukt: <ph name="DETAILS"/>
+Melding van de server: <ph name="SERVER_MSG"/></translation>
+<translation id="2475982808118771221">Er is een fout opgetreden</translation>
+<translation id="7229570126336867161">EVDO vereist</translation>
+<translation id="2999742336789313416"><ph name="DISPLAY_NAME"/> is een openbare sessie die wordt beheerd door <ph name="DOMAIN"/></translation>
+<translation id="7029814467594812963">Sessie sluiten</translation>
+<translation id="8454013096329229812">Wifi is ingeschakeld.</translation>
+<translation id="4872237917498892622">Alt+Zoeken of Shift</translation>
+<translation id="2983818520079887040">Instellingen...</translation>
+<translation id="1717216362413677834">Dockmodus</translation>
+<translation id="8927026611342028580">Verbinding aangevraagd</translation>
+<translation id="8300849813060516376">OTASP mislukt</translation>
+<translation id="2792498699870441125">Alt+Zoeken</translation>
+<translation id="8660803626959853127"><ph name="COUNT"/> bestand(en) synchroniseren</translation>
+<translation id="3709443003275901162">9+</translation>
+<translation id="639644700271529076">CAPS LOCK is uitgeschakeld</translation>
+<translation id="6248847161401822652">Druk twee keer op Control+Shift+Q om te stoppen.</translation>
+<translation id="6267036997247669271"><ph name="NAME"/>: activeren...</translation>
+<translation id="1391854757121130358">U heeft mogelijk uw quotum voor mobiele gegevens verbruikt.</translation>
+<translation id="5413208160176941586">Lokaal beheerde gebruiker</translation>
+<translation id="1059194134494239015"><ph name="DISPLAY_NAME"/>: <ph name="RESOLUTION"/></translation>
+<translation id="4864165860509564259">Positie van launcher</translation>
+<translation id="7593891976182323525">Zoeken of Shift</translation>
+<translation id="7649070708921625228">Help</translation>
+<translation id="3050422059534974565">CAPS LOCK is ingeschakeld.
+Druk op Zoeken of Shift om te annuleren.</translation>
+<translation id="397105322502079400">Berekenen...</translation>
+<translation id="158849752021629804">Thuisnetwerk vereist</translation>
+<translation id="6857811139397017780"><ph name="NETWORKSERVICE"/> activeren</translation>
+<translation id="5864471791310927901">Opzoeken van DHCP mislukt</translation>
+<translation id="5812035014844949013">UITVOER</translation>
+<translation id="6692173217867674490">Slechte wachtwoordzin</translation>
+<translation id="6165508094623778733">Meer informatie</translation>
+<translation id="9046895021617826162">Verbinding mislukt</translation>
+<translation id="973896785707726617">Deze sessie loopt af over <ph name="SESSION_TIME_REMAINING"/>. U wordt automatisch uitgelogd.</translation>
+<translation id="8372369524088641025">Slechte WEP-sleutel</translation>
+<translation id="6636709850131805001">Niet-herkende staat</translation>
+<translation id="3573179567135747900">Teruggaan naar '<ph name="FROM_LOCALE"/>' (opnieuw starten vereist)</translation>
+<translation id="8103386449138765447">SMS-berichten: <ph name="MESSAGE_COUNT"/></translation>
+<translation id="5045002648206642691">Instellingen voor Google Drive...</translation>
+<translation id="1510238584712386396">Opstartprogramma</translation>
+<translation id="7209101170223508707">CAPS LOCK is ingeschakeld.
+Druk op Alt+Zoeken of Shift om te annuleren.</translation>
+<translation id="8940956008527784070">Accu is bijna leeg (<ph name="PERCENTAGE"/>%)</translation>
+<translation id="5102001756192215136"><ph name="HOUR"/>:<ph name="MINUTE"/> resterend</translation>
+<translation id="520760366042891468">Controle over uw scherm delen via Hangouts.</translation>
+<translation id="8000066093800657092">Geen netwerk</translation>
+<translation id="4015692727874266537">Inloggen op ander account...</translation>
+<translation id="5941711191222866238">Minimaliseren</translation>
+<translation id="6911468394164995108">Verbinding met ander netwerk maken...</translation>
+<translation id="412065659894267608"><ph name="HOUR"/>u <ph name="MINUTE"/>m tot volledig opgeladen</translation>
+<translation id="6359806961507272919">Sms van <ph name="PHONE_NUMBER"/></translation>
+<translation id="1244147615850840081">Provider</translation>
+</translationbundle> \ No newline at end of file
diff --git a/chromium/ash/strings/ash_strings_no.xtb b/chromium/ash/strings/ash_strings_no.xtb
new file mode 100644
index 00000000000..06b5745a658
--- /dev/null
+++ b/chromium/ash/strings/ash_strings_no.xtb
@@ -0,0 +1,201 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="no">
+<translation id="3595596368722241419">Batteriet er fullt</translation>
+<translation id="5250713215130379958">Skjul programlisten automatisk</translation>
+<translation id="7814236020522506259"><ph name="HOUR"/> og <ph name="MINUTE"/></translation>
+<translation id="7880025619322806991">Portaltilstand</translation>
+<translation id="30155388420722288">Overflyt-knappen</translation>
+<translation id="5571066253365925590">Bluetooth er aktivert</translation>
+<translation id="9074739597929991885">Bluetooth</translation>
+<translation id="2268130516524549846">Bluetooth er deaktivert</translation>
+<translation id="3775358506042162758">Du kan bare ha opptil tre kontoer i flerpålogging.</translation>
+<translation id="370649949373421643">Aktivér Wi-Fi</translation>
+<translation id="3626281679859535460">Lysstyrke</translation>
+<translation id="8054466585765276473">Beregner batteritid.</translation>
+<translation id="7982789257301363584">Nettverk</translation>
+<translation id="5565793151875479467">Mellomtjener</translation>
+<translation id="938582441709398163">Tastaturbelegg</translation>
+<translation id="4387004326333427325">Autentiseringssertifikatet ble avvist eksternt</translation>
+<translation id="6979158407327259162">Google Disk</translation>
+<translation id="6943836128787782965">Henting av HTTP mislyktes</translation>
+<translation id="2297568595583585744">Status-felt</translation>
+<translation id="1661867754829461514">Personlig kode mangler</translation>
+<translation id="4508225577814909926"><ph name="NAME"/>: kobler til ...</translation>
+<translation id="4237016987259239829">Feil i nettverkstilkobling</translation>
+<translation id="2946640296642327832">Aktiver Bluetooth</translation>
+<translation id="6459472438155181876">Utvider skjermen til <ph name="DISPLAY_NAME"/></translation>
+<translation id="8206859287963243715">Mobil</translation>
+<translation id="6596816719288285829">IP-adresse</translation>
+<translation id="4508265954913339219">Aktiveringen mislyktes</translation>
+<translation id="3621712662352432595">Lydinnstillinger</translation>
+<translation id="1812696562331527143">Inndatametoden din er endret til <ph name="INPUT_METHOD_ID"/>*(<ph name="BEGIN_LINK"/>tredjepart<ph name="END_LINK"/>).
+Trykk på Shift + Alt for å bytte.</translation>
+<translation id="2127372758936585790">Lading med lav effekt</translation>
+<translation id="3846575436967432996">Ingen nettverksinformasjon tilgjengelig</translation>
+<translation id="3026237328237090306">Konfigurer mobildata</translation>
+<translation id="785750925697875037">Se mobilkontoen</translation>
+<translation id="153454903766751181">Starter mobilmodemet ...</translation>
+<translation id="4628814525959230255">Deling av skjermkontrollen din med <ph name="HELPER_NAME"/> via Hangouts.</translation>
+<translation id="8343941333792395995"><ph name="DISPLAY_NAME"/> er rotert</translation>
+<translation id="7864539943188674973">Deaktiver Bluetooth</translation>
+<translation id="939252827960237676">Lagringen av skjermdumpen mislyktes</translation>
+<translation id="3126069444801937830">Start på nytt for å oppdatere</translation>
+<translation id="2268813581635650749">Logg alle av</translation>
+<translation id="735745346212279324">VPN frakoblet</translation>
+<translation id="7320906967354320621">Ikke aktiv</translation>
+<translation id="6303423059719347535">Batteriet er <ph name="PERCENTAGE"/> % fullt</translation>
+<translation id="15373452373711364">Stor markør</translation>
+<translation id="2778346081696727092">Kunne ikke autentisere med oppgitt brukernavn eller passord</translation>
+<translation id="3294437725009624529">Gjest</translation>
+<translation id="8190698733819146287">Tilpass språk og inndata</translation>
+<translation id="2903907270192926896">INNDATA</translation>
+<translation id="8676770494376880701">Laveffektslader er tilkoblet</translation>
+<translation id="7170041865419449892">Utenfor rekkevidde</translation>
+<translation id="4804818685124855865">Koble fra</translation>
+<translation id="2544853746127077729">Autentiseringssertifikatet ble avvist av nettverket</translation>
+<translation id="5222676887888702881">Logg av</translation>
+<translation id="2688477613306174402">Konfigurasjon</translation>
+<translation id="1272079795634619415">Stopp</translation>
+<translation id="4957722034734105353">Finn ut mer</translation>
+<translation id="2964193600955408481">Deaktiver Wi-Fi</translation>
+<translation id="811680302244032017">Legg til enhet</translation>
+<translation id="4279490309300973883">Speiling</translation>
+<translation id="2509468283778169019">Caps Lock er på</translation>
+<translation id="3892641579809465218">Innebygd skjerm</translation>
+<translation id="7823564328645135659">Språket er endret fra <ph name="FROM_LOCALE"/> til <ph name="TO_LOCALE"/> etter synkronisering av innstillingene dine.</translation>
+<translation id="3368922792935385530">Tilkoblet</translation>
+<translation id="8340999562596018839">Talerespons</translation>
+<translation id="8654520615680304441">Slå på Wi-Fi</translation>
+<translation id="5825747213122829519">Inndatametoden din er endret til <ph name="INPUT_METHOD_ID"/>.
+Trykk på Shift + Alt for å bytte.</translation>
+<translation id="2562916301614567480">Privat nettverk</translation>
+<translation id="6549021752953852991">Ingen tilgjengelige mobilnettverk.</translation>
+<translation id="4379753398862151997">Kjære skjerm, det går ikke så bra mellom oss. (Skjermen støttes ikke)</translation>
+<translation id="6426039856985689743">Deaktiver mobildata</translation>
+<translation id="3087734570205094154">Bunn</translation>
+<translation id="3742055079367172538">Skjermdump tatt</translation>
+<translation id="8878886163241303700">Utvidet skjerm</translation>
+<translation id="5271016907025319479">VPN er ikke konfigurert.</translation>
+<translation id="372094107052732682">Trykk Ctrl+Shift+Q to ganger for å avslutte.</translation>
+<translation id="6803622936009808957">Kunne ikke speile skjermene fordi ingen støttede oppløsninger ble funnet. Utvidet skrivebord ble brukt i stedet.</translation>
+<translation id="1480041086352807611">Demo-modus</translation>
+<translation id="3626637461649818317"><ph name="PERCENTAGE"/>% igjen</translation>
+<translation id="9089416786594320554">Inndatametoder</translation>
+<translation id="6247708409970142803"><ph name="PERCENTAGE"/> %</translation>
+<translation id="2614835198358683673">Chromebook kan ikke lades mens den er slått på. Vurder å bruke den offisielle laderen.</translation>
+<translation id="1895658205118569222">Avslutning</translation>
+<translation id="4430019312045809116">Volum</translation>
+<translation id="4442424173763614572">DNS-søk mislyktes</translation>
+<translation id="6356500677799115505">Batteriet er fullt og til lading.</translation>
+<translation id="7874779702599364982">Søker etter mobilnettverk ...</translation>
+<translation id="583281660410589416">Ukjent</translation>
+<translation id="1383876407941801731">Søk</translation>
+<translation id="7468789844759750875">Gå til <ph name="NAME"/>-aktiveringsportalen for å kjøpe mer mobildata.</translation>
+<translation id="3901991538546252627">Kobler til <ph name="NAME"/></translation>
+<translation id="2204305834655267233">Nettverksinformasjon</translation>
+<translation id="1621499497873603021">Gjenværende tid til batteriet er tomt – <ph name="TIME_LEFT"/></translation>
+<translation id="5980301590375426705">Avslutt gjesteøkten</translation>
+<translation id="4471417012762451363">Batteriet er <ph name="PERCENTAGE"/> % fullt og til lading</translation>
+<translation id="8308637677604853869">Forrige meny</translation>
+<translation id="4666297444214622512">Kan ikke logge på en annen konto.</translation>
+<translation id="1346748346194534595">Høyre</translation>
+<translation id="1773212559869067373">Autentiseringssertifikatet ble avvist lokalt</translation>
+<translation id="8528322925433439945">Mobil</translation>
+<translation id="7049357003967926684">Tilknytning</translation>
+<translation id="8428213095426709021">Innstillinger</translation>
+<translation id="2372145515558759244">Synkroniserer programmer …</translation>
+<translation id="7256405249507348194">Ukjent feil: <ph name="DESC"/></translation>
+<translation id="7925247922861151263">AAA-kontroll mislyktes</translation>
+<translation id="8456362689280298700"><ph name="HOUR"/>.<ph name="MINUTE"/> til fulladet</translation>
+<translation id="5787281376604286451">Talerespons er aktivert.
+Trykk på Ctrl+Alt+Z for å deaktivere.</translation>
+<translation id="4479639480957787382">Ethernet</translation>
+<translation id="6312403991423642364">Ukjent nettverksfeil</translation>
+<translation id="1467432559032391204">Venstre</translation>
+<translation id="5543001071567407895">SMS</translation>
+<translation id="2354174487190027830">Aktiverer <ph name="NAME"/></translation>
+<translation id="8814190375133053267">Wi-Fi</translation>
+<translation id="1398853756734560583">Maksimer</translation>
+<translation id="2692809339924654275"><ph name="BLUETOOTH"/>: kobler til ...</translation>
+<translation id="252373100621549798">Ukjent skjerm</translation>
+<translation id="1882897271359938046">Speiler <ph name="DISPLAY_NAME"/></translation>
+<translation id="2727977024730340865">Tilkoblet laveffektslader. Batteriladingen kan være upålitelig.</translation>
+<translation id="3784455785234192852">Lås</translation>
+<translation id="2805756323405976993">Programmer</translation>
+<translation id="8871072142849158571"><ph name="DISPLAY_NAME"/> har endret oppløsning til <ph name="RESOLUTION"/></translation>
+<translation id="1512064327686280138">Aktiveringen mislyktes</translation>
+<translation id="5097002363526479830">Kunne ikke koble til nettverket «<ph name="NAME"/>»: <ph name="DETAILS"/></translation>
+<translation id="1850504506766569011">Wi-Fi er slått av.</translation>
+<translation id="8132793192354020517">Tilkoblet <ph name="NAME"/></translation>
+<translation id="7052914147756339792">Angi bakgrunnsbilde</translation>
+<translation id="8678698760965522072">Tilkoblet tilstand</translation>
+<translation id="2532589005999780174">Høykontrastmodus</translation>
+<translation id="1119447706177454957">Intern feil</translation>
+<translation id="3019353588588144572">Tid som gjenstår til batteriet er fulladet – <ph name="TIME_REMAINING"/></translation>
+<translation id="3473479545200714844">Skjermforstørrer</translation>
+<translation id="7005812687360380971">Feil</translation>
+<translation id="882279321799040148">Klikk for å se den</translation>
+<translation id="5045550434625856497">Feil passord</translation>
+<translation id="1602076796624386989">Aktivér mobildata</translation>
+<translation id="6981982820502123353">Tilgjengelighet</translation>
+<translation id="3157931365184549694">Gjenopprett</translation>
+<translation id="4274292172790327596">Ukjent feil</translation>
+<translation id="4032485810211612751"><ph name="HOURS"/>:<ph name="MINUTES"/>:<ph name="SECONDS"/></translation>
+<translation id="225680501294068881">Leter etter enheter ...</translation>
+<translation id="5597451508971090205"><ph name="SHORT_WEEKDAY"/> <ph name="DATE"/></translation>
+<translation id="4448844063988177157">Søker etter Wi-Fi-nettverk ...</translation>
+<translation id="8401662262483418323">Kunne ikke koble til «<ph name="NAME"/>»: <ph name="DETAILS"/>
+Tjenermelding: <ph name="SERVER_MSG"/></translation>
+<translation id="2475982808118771221">Det oppstod en feil</translation>
+<translation id="7229570126336867161">Krever EVDO</translation>
+<translation id="2999742336789313416"><ph name="DISPLAY_NAME"/> er en offentlig økt administrert av <ph name="DOMAIN"/></translation>
+<translation id="7029814467594812963">Avslutt økten</translation>
+<translation id="8454013096329229812">Wi-Fi er slått på.</translation>
+<translation id="4872237917498892622">Alt + Søk eller Shift</translation>
+<translation id="2983818520079887040">Innstillinger</translation>
+<translation id="1717216362413677834">Dokkmodus</translation>
+<translation id="8927026611342028580">Tilkobling har blitt forespurt</translation>
+<translation id="8300849813060516376">OTASP mislyktes</translation>
+<translation id="2792498699870441125">Alt + Søk</translation>
+<translation id="8660803626959853127">Synkroniserer <ph name="COUNT"/> fil(er)</translation>
+<translation id="3709443003275901162">9+</translation>
+<translation id="639644700271529076">CAPS LOCK er av</translation>
+<translation id="6248847161401822652">Trykk Control+Shift+Q to ganger for å avslutte.</translation>
+<translation id="6267036997247669271"><ph name="NAME"/>: aktiveres …</translation>
+<translation id="1391854757121130358">Du kan ha brukt opp mobildatakvoten din.</translation>
+<translation id="5413208160176941586">Lokalt administrert bruker</translation>
+<translation id="1059194134494239015"><ph name="DISPLAY_NAME"/>: <ph name="RESOLUTION"/></translation>
+<translation id="4864165860509564259">Programlisteplassering</translation>
+<translation id="7593891976182323525">Søk eller Shift</translation>
+<translation id="7649070708921625228">Hjelp</translation>
+<translation id="3050422059534974565">Caps Lock er på.
+Trykk på Søk eller Shift for å avbryte.</translation>
+<translation id="397105322502079400">Beregner …</translation>
+<translation id="158849752021629804">Trenger hjemmenettverk</translation>
+<translation id="6857811139397017780">Aktiver <ph name="NETWORKSERVICE"/></translation>
+<translation id="5864471791310927901">DHCP-oppslag mislyktes</translation>
+<translation id="5812035014844949013">UTDATA</translation>
+<translation id="6692173217867674490">Feil passordfrase</translation>
+<translation id="6165508094623778733">Les mer</translation>
+<translation id="9046895021617826162">Tilkoblingen mislyktes</translation>
+<translation id="973896785707726617">Denne økten slutter om <ph name="SESSION_TIME_REMAINING"/>. Du logges ut automatisk.</translation>
+<translation id="8372369524088641025">Feil WEP-nøkkel</translation>
+<translation id="6636709850131805001">Ikke gjenkjent tilstand</translation>
+<translation id="3573179567135747900">Endre tilbake til <ph name="FROM_LOCALE"/> (krever omstart)</translation>
+<translation id="8103386449138765447">SMS-meldinger: <ph name="MESSAGE_COUNT"/></translation>
+<translation id="5045002648206642691">Innstillinger for Google Disk</translation>
+<translation id="1510238584712386396">Appvelger</translation>
+<translation id="7209101170223508707">Caps Lock er på.
+Trykk på Alt + Søk eller Shift for å avbryte.</translation>
+<translation id="8940956008527784070">Lavt batterinivå (<ph name="PERCENTAGE"/> %)</translation>
+<translation id="5102001756192215136"><ph name="HOUR"/>.<ph name="MINUTE"/> gjenstår</translation>
+<translation id="520760366042891468">Deler skjermkontrollen din via Hangouts.</translation>
+<translation id="8000066093800657092">Ingen nettverk</translation>
+<translation id="4015692727874266537">Logg på en annen konto</translation>
+<translation id="5941711191222866238">Minimer</translation>
+<translation id="6911468394164995108">Koble til annet</translation>
+<translation id="412065659894267608"><ph name="HOUR"/> t og <ph name="MINUTE"/> m til batteriet er ferdigladet</translation>
+<translation id="6359806961507272919">Tekstmelding fra <ph name="PHONE_NUMBER"/></translation>
+<translation id="1244147615850840081">Operatør</translation>
+</translationbundle> \ No newline at end of file
diff --git a/chromium/ash/strings/ash_strings_pl.xtb b/chromium/ash/strings/ash_strings_pl.xtb
new file mode 100644
index 00000000000..2258bf53677
--- /dev/null
+++ b/chromium/ash/strings/ash_strings_pl.xtb
@@ -0,0 +1,201 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="pl">
+<translation id="3595596368722241419">Bateria naładowana</translation>
+<translation id="5250713215130379958">Automatycznie ukrywaj program uruchamiający</translation>
+<translation id="7814236020522506259"><ph name="HOUR"/> i <ph name="MINUTE"/></translation>
+<translation id="7880025619322806991">Stan portalu</translation>
+<translation id="30155388420722288">Przycisk akcji</translation>
+<translation id="5571066253365925590">Bluetooth włączony</translation>
+<translation id="9074739597929991885">Bluetooth</translation>
+<translation id="2268130516524549846">Bluetooth wyłączony</translation>
+<translation id="3775358506042162758">W wielokrotnym logowaniu możesz mieć najwyżej trzy konta.</translation>
+<translation id="370649949373421643">Włącz Wi-Fi</translation>
+<translation id="3626281679859535460">Jasność</translation>
+<translation id="8054466585765276473">Obliczanie czasu pracy na baterii.</translation>
+<translation id="7982789257301363584">Sieć</translation>
+<translation id="5565793151875479467">Serwer proxy...</translation>
+<translation id="938582441709398163">Nakładka klawiatury</translation>
+<translation id="4387004326333427325">Certyfikat uwierzytelniania został odrzucony zdalnie</translation>
+<translation id="6979158407327259162">Dysk Google</translation>
+<translation id="6943836128787782965">Wykonanie metody GET protokołu HTTP nie powiodło się.</translation>
+<translation id="2297568595583585744">Zasobnik stanu</translation>
+<translation id="1661867754829461514">Brak kodu PIN</translation>
+<translation id="4508225577814909926"><ph name="NAME"/>: łączę...</translation>
+<translation id="4237016987259239829">Błąd połączenia z siecią</translation>
+<translation id="2946640296642327832">Włącz Bluetooth</translation>
+<translation id="6459472438155181876">Rozszerzanie ekranu na <ph name="DISPLAY_NAME"/></translation>
+<translation id="8206859287963243715">Komórka</translation>
+<translation id="6596816719288285829">Adres IP</translation>
+<translation id="4508265954913339219">Aktywacja nie powiodła się</translation>
+<translation id="3621712662352432595">Ustawienia audio</translation>
+<translation id="1812696562331527143">Metoda wprowadzania została zmieniona na <ph name="INPUT_METHOD_ID"/>*(<ph name="BEGIN_LINK"/>innej firmy<ph name="END_LINK"/>).
+Naciśnij Shift + Alt, by ją przełączyć.</translation>
+<translation id="2127372758936585790">Ładowarka o małej mocy</translation>
+<translation id="3846575436967432996">Brak informacji o sieciach</translation>
+<translation id="3026237328237090306">Skonfiguruj komórkową transmisję danych</translation>
+<translation id="785750925697875037">Wyświetl konto dla telefonów komórkowych</translation>
+<translation id="153454903766751181">Inicjuję modem komórkowy...</translation>
+<translation id="4628814525959230255">Udostępniasz ekran w Hangouts – <ph name="HELPER_NAME"/> kontroluje Twój komputer.</translation>
+<translation id="8343941333792395995">Ekran <ph name="DISPLAY_NAME"/> został obrócony</translation>
+<translation id="7864539943188674973">Wyłącz Bluetooth</translation>
+<translation id="939252827960237676">Nie można zapisać zrzutu ekranu</translation>
+<translation id="3126069444801937830">Uruchom ponownie i zaktualizuj</translation>
+<translation id="2268813581635650749">Wyloguj wszystkich</translation>
+<translation id="735745346212279324">VPN odłączona</translation>
+<translation id="7320906967354320621">Bezczynna</translation>
+<translation id="6303423059719347535">Naładowanie baterii: <ph name="PERCENTAGE"/>%</translation>
+<translation id="15373452373711364">Duży kursor myszy</translation>
+<translation id="2778346081696727092">Nie powiodło się uwierzytelnienie przy użyciu podanej nazwy użytkownika i hasła.</translation>
+<translation id="3294437725009624529">Gość</translation>
+<translation id="8190698733819146287">Dostosuj języki i metody wprowadzania</translation>
+<translation id="2903907270192926896">WEJŚCIE</translation>
+<translation id="8676770494376880701">Podłączono ładowarkę o małej mocy</translation>
+<translation id="7170041865419449892">Poza zasięgiem</translation>
+<translation id="4804818685124855865">Rozłącz</translation>
+<translation id="2544853746127077729">Certyfikat uwierzytelniania został odrzucony przez sieć</translation>
+<translation id="5222676887888702881">Wyloguj się</translation>
+<translation id="2688477613306174402">Konfiguracja</translation>
+<translation id="1272079795634619415">Zatrzymaj</translation>
+<translation id="4957722034734105353">Więcej informacji...</translation>
+<translation id="2964193600955408481">Wyłącz Wi-Fi</translation>
+<translation id="811680302244032017">Dodaj urządzenie...</translation>
+<translation id="4279490309300973883">Odbicie lustrzane</translation>
+<translation id="2509468283778169019">CAPS LOCK jest włączony.</translation>
+<translation id="3892641579809465218">Wyświetlacz wewnętrzny</translation>
+<translation id="7823564328645135659">Po zsynchronizowaniu ustawień zmieniono język z „<ph name="FROM_LOCALE"/>” na „<ph name="TO_LOCALE"/>”.</translation>
+<translation id="3368922792935385530">Połączone</translation>
+<translation id="8340999562596018839">Potwierdzenia głosowe</translation>
+<translation id="8654520615680304441">Włącz Wi-Fi...</translation>
+<translation id="5825747213122829519">Metoda wprowadzania została zmieniona na <ph name="INPUT_METHOD_ID"/>.
+Naciśnij Shift + Alt, by ją przełączyć.</translation>
+<translation id="2562916301614567480">Sieć prywatna</translation>
+<translation id="6549021752953852991">Brak dostępnych sieci komórkowych</translation>
+<translation id="4379753398862151997">Drogi monitorze, nie układa się między nami. (Ten monitor jest nieobsługiwany)</translation>
+<translation id="6426039856985689743">Wyłącz komórkową transmisję danych</translation>
+<translation id="3087734570205094154">Na dół</translation>
+<translation id="3742055079367172538">Wykonano zrzut ekranu</translation>
+<translation id="8878886163241303700">Rozszerzony ekran</translation>
+<translation id="5271016907025319479">Sieć VPN jest nieskonfigurowana.</translation>
+<translation id="372094107052732682">Naciśnij dwukrotnie Ctrl+Shift+Q, by zakończyć.</translation>
+<translation id="6803622936009808957">Nie można wyświetlić odbicia lustrzanego, ponieważ nie znaleziono obsługiwanych rozdzielczości. Zamiast tego został włączony pulpit rozszerzony.</translation>
+<translation id="1480041086352807611">Tryb demo</translation>
+<translation id="3626637461649818317">Pozostało <ph name="PERCENTAGE"/>%</translation>
+<translation id="9089416786594320554">Wprowadzanie tekstu</translation>
+<translation id="6247708409970142803"><ph name="PERCENTAGE"/>%</translation>
+<translation id="2614835198358683673">Włączony Chromebook może nie być ładowany. Zalecamy użycie oryginalnej ładowarki.</translation>
+<translation id="1895658205118569222">Wyłączenie</translation>
+<translation id="4430019312045809116">Głośność</translation>
+<translation id="4442424173763614572">Wyszukiwanie DNS nie powiodło się.</translation>
+<translation id="6356500677799115505">Bateria jest pełna i trwa ładowanie.</translation>
+<translation id="7874779702599364982">Szukam sieci komórkowych...</translation>
+<translation id="583281660410589416">Nieznany</translation>
+<translation id="1383876407941801731">Wyszukiwanie</translation>
+<translation id="7468789844759750875">Odwiedź portal aktywacji <ph name="NAME"/>, aby wykupić więcej transferu danych.</translation>
+<translation id="3901991538546252627">Łączę z: <ph name="NAME"/></translation>
+<translation id="2204305834655267233">Informacje o sieci</translation>
+<translation id="1621499497873603021">Czas pozostały do wyczerpania baterii: <ph name="TIME_LEFT"/></translation>
+<translation id="5980301590375426705">Zakończ sesję gościa</translation>
+<translation id="4471417012762451363">Naładowanie baterii: <ph name="PERCENTAGE"/>%. Trwa ładowanie</translation>
+<translation id="8308637677604853869">Poprzednie menu</translation>
+<translation id="4666297444214622512">Nie można zalogować się na kolejne konto.</translation>
+<translation id="1346748346194534595">W prawo</translation>
+<translation id="1773212559869067373">Certyfikat uwierzytelniania został odrzucony lokalnie</translation>
+<translation id="8528322925433439945">Komórkowe...</translation>
+<translation id="7049357003967926684">Powiązanie</translation>
+<translation id="8428213095426709021">Ustawienia</translation>
+<translation id="2372145515558759244">Synchronizuję aplikacje...</translation>
+<translation id="7256405249507348194">Nierozpoznany błąd: <ph name="DESC"/></translation>
+<translation id="7925247922861151263">Sprawdzenie AAA nie powiodło się</translation>
+<translation id="8456362689280298700">Do naładowania: <ph name="HOUR"/>:<ph name="MINUTE"/></translation>
+<translation id="5787281376604286451">Potwierdzenia głosowe są włączone.
+Aby wyłączyć, naciśnij Ctrl+Alt+Z.</translation>
+<translation id="4479639480957787382">Ethernet</translation>
+<translation id="6312403991423642364">Nieznany błąd sieci</translation>
+<translation id="1467432559032391204">W lewo</translation>
+<translation id="5543001071567407895">SMS</translation>
+<translation id="2354174487190027830">Aktywuję <ph name="NAME"/></translation>
+<translation id="8814190375133053267">Wi-Fi</translation>
+<translation id="1398853756734560583">Maksymalizuj</translation>
+<translation id="2692809339924654275"><ph name="BLUETOOTH"/>: łączę...</translation>
+<translation id="252373100621549798">Nieznany wyświetlacz</translation>
+<translation id="1882897271359938046">Kopia na <ph name="DISPLAY_NAME"/></translation>
+<translation id="2727977024730340865">Podłączono ładowarkę o małej mocy. Ładowanie baterii może być nieprawidłowe.</translation>
+<translation id="3784455785234192852">Zablokuj</translation>
+<translation id="2805756323405976993">Aplikacje</translation>
+<translation id="8871072142849158571">Rozdzielczość ekranu <ph name="DISPLAY_NAME"/> została zmieniona na <ph name="RESOLUTION"/></translation>
+<translation id="1512064327686280138">Niepowodzenie aktywacji</translation>
+<translation id="5097002363526479830">Nie udało się połączyć z siecią „<ph name="NAME"/>”: <ph name="DETAILS"/></translation>
+<translation id="1850504506766569011">Wi-Fi wyłączone.</translation>
+<translation id="8132793192354020517">Połączono z <ph name="NAME"/></translation>
+<translation id="7052914147756339792">Ustaw tapetę...</translation>
+<translation id="8678698760965522072">Online</translation>
+<translation id="2532589005999780174">Tryb wysokiego kontrastu</translation>
+<translation id="1119447706177454957">Błąd wewnętrzny</translation>
+<translation id="3019353588588144572">Czas pozostały do pełnego naładowania baterii: <ph name="TIME_REMAINING"/></translation>
+<translation id="3473479545200714844">Lupa</translation>
+<translation id="7005812687360380971">Niepowodzenie</translation>
+<translation id="882279321799040148">Kliknij, by wyświetlić</translation>
+<translation id="5045550434625856497">Nieprawidłowe hasło</translation>
+<translation id="1602076796624386989">Włącz komórkową transmisję danych</translation>
+<translation id="6981982820502123353">Ułatwienia dostępu</translation>
+<translation id="3157931365184549694">Przywróć</translation>
+<translation id="4274292172790327596">Nierozpoznany błąd</translation>
+<translation id="4032485810211612751"><ph name="HOURS"/>:<ph name="MINUTES"/>:<ph name="SECONDS"/></translation>
+<translation id="225680501294068881">Skanowanie w poszukiwaniu urządzeń...</translation>
+<translation id="5597451508971090205"><ph name="SHORT_WEEKDAY"/>, <ph name="DATE"/></translation>
+<translation id="4448844063988177157">Trwa wyszukiwanie sieci Wi-Fi...</translation>
+<translation id="8401662262483418323">Nie udało się połączyć z „<ph name="NAME"/>”: <ph name="DETAILS"/>
+Komunikat serwera: <ph name="SERVER_MSG"/></translation>
+<translation id="2475982808118771221">Wystąpił błąd</translation>
+<translation id="7229570126336867161">Wymagana technologia EVDO</translation>
+<translation id="2999742336789313416"><ph name="DISPLAY_NAME"/> jest publiczną sesją zarządzaną przez <ph name="DOMAIN"/></translation>
+<translation id="7029814467594812963">Zakończ sesję</translation>
+<translation id="8454013096329229812">Wi-Fi włączone.</translation>
+<translation id="4872237917498892622">Alt+Szukaj lub Shift</translation>
+<translation id="2983818520079887040">Ustawienia</translation>
+<translation id="1717216362413677834">Tryb doku</translation>
+<translation id="8927026611342028580">Poproszono o połączenie</translation>
+<translation id="8300849813060516376">Dostarczanie OTASP nie powiodło się</translation>
+<translation id="2792498699870441125">Alt+Szukaj</translation>
+<translation id="8660803626959853127">Synchronizacja: <ph name="COUNT"/> pliki(ów)</translation>
+<translation id="3709443003275901162">Ponad 9</translation>
+<translation id="639644700271529076">CAPS LOCK jest wyłączony</translation>
+<translation id="6248847161401822652">Naciśnij dwukrotnie Control Shift Q, by zakończyć.</translation>
+<translation id="6267036997247669271"><ph name="NAME"/>: aktywuję...</translation>
+<translation id="1391854757121130358">Być może został wyczerpany limit komórkowej transmisji danych.</translation>
+<translation id="5413208160176941586">Użytkownik zarządzany lokalnie</translation>
+<translation id="1059194134494239015"><ph name="DISPLAY_NAME"/>: <ph name="RESOLUTION"/></translation>
+<translation id="4864165860509564259">Pozycja programu uruchamiającego</translation>
+<translation id="7593891976182323525">Szukaj lub Shift</translation>
+<translation id="7649070708921625228">Pomoc</translation>
+<translation id="3050422059534974565">CAPS LOCK jest włączony.
+Naciśnij Szukaj lub Shift, by anulować.</translation>
+<translation id="397105322502079400">Obliczanie...</translation>
+<translation id="158849752021629804">Wymagana sieć macierzysta</translation>
+<translation id="6857811139397017780">Aktywuj usługę <ph name="NETWORKSERVICE"/></translation>
+<translation id="5864471791310927901">Wyszukiwanie DHCP nie powiodło się</translation>
+<translation id="5812035014844949013">WYJŚCIE</translation>
+<translation id="6692173217867674490">Błędne hasło</translation>
+<translation id="6165508094623778733">Więcej informacji</translation>
+<translation id="9046895021617826162">Łączenie nie powiodło się</translation>
+<translation id="973896785707726617">Ta sesja zakończy się za <ph name="SESSION_TIME_REMAINING"/>. Nastąpi automatyczne wylogowanie.</translation>
+<translation id="8372369524088641025">Błędny klucz WEP</translation>
+<translation id="6636709850131805001">Nierozpoznany stan</translation>
+<translation id="3573179567135747900">Zmień z powrotem na „<ph name="FROM_LOCALE"/>” (wymaga ponownego uruchomienia)</translation>
+<translation id="8103386449138765447">SMS-y: <ph name="MESSAGE_COUNT"/></translation>
+<translation id="5045002648206642691">Ustawienia Dysku Google...</translation>
+<translation id="1510238584712386396">Program uruchamiający</translation>
+<translation id="7209101170223508707">CAPS LOCK jest włączony.
+Naciśnij Alt+Szukaj lub Shift, by anulować.</translation>
+<translation id="8940956008527784070">Niski stan baterii (<ph name="PERCENTAGE"/>%)</translation>
+<translation id="5102001756192215136">Pozostało <ph name="HOUR"/>:<ph name="MINUTE"/></translation>
+<translation id="520760366042891468">Udostępniasz ekran i kontrolę nad komputerem w Hangouts.</translation>
+<translation id="8000066093800657092">Brak sieci</translation>
+<translation id="4015692727874266537">Zaloguj się na kolejne konto...</translation>
+<translation id="5941711191222866238">Minimalizuj</translation>
+<translation id="6911468394164995108">Połącz z inną...</translation>
+<translation id="412065659894267608"><ph name="HOUR"/> godz. <ph name="MINUTE"/> min do pełnego naładowania</translation>
+<translation id="6359806961507272919">Wiadomość SMS z numeru <ph name="PHONE_NUMBER"/></translation>
+<translation id="1244147615850840081">Operator</translation>
+</translationbundle> \ No newline at end of file
diff --git a/chromium/ash/strings/ash_strings_pt-BR.xtb b/chromium/ash/strings/ash_strings_pt-BR.xtb
new file mode 100644
index 00000000000..b351f45c0cd
--- /dev/null
+++ b/chromium/ash/strings/ash_strings_pt-BR.xtb
@@ -0,0 +1,199 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="pt-BR">
+<translation id="3595596368722241419">Bateria carregada</translation>
+<translation id="5250713215130379958">Ocultar automaticamente o iniciador</translation>
+<translation id="7814236020522506259"><ph name="HOUR"/> e <ph name="MINUTE"/></translation>
+<translation id="7880025619322806991">Estado do portal</translation>
+<translation id="30155388420722288">Botão de estouro</translation>
+<translation id="5571066253365925590">Bluetooth ativado</translation>
+<translation id="9074739597929991885">Bluetooth</translation>
+<translation id="2268130516524549846">Bluetooth desativado</translation>
+<translation id="3775358506042162758">É possível ter até três contas em login múltiplo.</translation>
+<translation id="370649949373421643">Ativar Wi-Fi</translation>
+<translation id="3626281679859535460">Brilho</translation>
+<translation id="8054466585765276473">Calculando duração da bateria.</translation>
+<translation id="7982789257301363584">Rede</translation>
+<translation id="5565793151875479467">Proxy...</translation>
+<translation id="938582441709398163">Sobreposição do teclado</translation>
+<translation id="4387004326333427325">Certificado de autenticação rejeitado remotamente</translation>
+<translation id="6979158407327259162">Google Drive</translation>
+<translation id="6943836128787782965">HTTP falhou</translation>
+<translation id="2297568595583585744">Bandeja de status</translation>
+<translation id="1661867754829461514">PIN ausente</translation>
+<translation id="4508225577814909926"><ph name="NAME"/>: conectando...</translation>
+<translation id="4237016987259239829">Erro de conexão da rede</translation>
+<translation id="2946640296642327832">Ativar bluetooth</translation>
+<translation id="6459472438155181876">Estendendo tela para <ph name="DISPLAY_NAME"/></translation>
+<translation id="8206859287963243715">Celular</translation>
+<translation id="6596816719288285829">Endereço IP</translation>
+<translation id="4508265954913339219">Falha na ativação</translation>
+<translation id="3621712662352432595">Configurações de áudio</translation>
+<translation id="1812696562331527143">Seu método de entrada mudou para <ph name="INPUT_METHOD_ID"/>*(<ph name="BEGIN_LINK"/>terceiros<ph name="END_LINK"/>).
+Pressione Shift + Alt para alternar.</translation>
+<translation id="2127372758936585790">Carregador de baixa potência</translation>
+<translation id="3846575436967432996">Não há informações de rede disponíveis</translation>
+<translation id="3026237328237090306">Configurar dados móveis</translation>
+<translation id="785750925697875037">Exibir conta de celular</translation>
+<translation id="153454903766751181">Inicializando modem celular...</translation>
+<translation id="4628814525959230255">Compartilhando o controle de sua tela com <ph name="HELPER_NAME"/> por meio do Hangouts.</translation>
+<translation id="8343941333792395995"><ph name="DISPLAY_NAME"/> foi girada</translation>
+<translation id="7864539943188674973">Desativar bluetooth</translation>
+<translation id="939252827960237676">Falha ao salvar a captura de tela</translation>
+<translation id="3126069444801937830">Reiniciar para atualizar</translation>
+<translation id="2268813581635650749">Desconectar todos</translation>
+<translation id="735745346212279324">VPN desconectada</translation>
+<translation id="7320906967354320621">Inativo</translation>
+<translation id="6303423059719347535">A bateria está <ph name="PERCENTAGE"/>% cheia</translation>
+<translation id="15373452373711364">Cursor grande do mouse</translation>
+<translation id="2778346081696727092">Falha na autenticação com nome de usuário ou senha fornecidos</translation>
+<translation id="3294437725009624529">Visitante</translation>
+<translation id="8190698733819146287">Personalizar idiomas e entrada...</translation>
+<translation id="2903907270192926896">ENTRADA</translation>
+<translation id="8676770494376880701">Carregador de baixa potência conectado</translation>
+<translation id="7170041865419449892">Fora de alcance</translation>
+<translation id="4804818685124855865">Desconectar</translation>
+<translation id="2544853746127077729">Certificado de autenticação rejeitado pela rede</translation>
+<translation id="5222676887888702881">Sair</translation>
+<translation id="2688477613306174402">Configuração</translation>
+<translation id="1272079795634619415">Parar</translation>
+<translation id="4957722034734105353">Saiba mais...</translation>
+<translation id="2964193600955408481">Desativar Wi-Fi</translation>
+<translation id="811680302244032017">Adicionar dispositivo...</translation>
+<translation id="4279490309300973883">Espelhamento</translation>
+<translation id="2509468283778169019">CAPS LOCK está ativado</translation>
+<translation id="3892641579809465218">Display interno</translation>
+<translation id="7823564328645135659">O idioma foi alterado de &quot;<ph name="FROM_LOCALE"/>&quot; para &quot;<ph name="TO_LOCALE"/>&quot; após a sincronização de suas configurações.</translation>
+<translation id="3368922792935385530">Conectado</translation>
+<translation id="8340999562596018839">Feedback falado</translation>
+<translation id="8654520615680304441">Ativar Wi-Fi...</translation>
+<translation id="5825747213122829519">Seu método de entrada mudou para <ph name="INPUT_METHOD_ID"/>.
+Pressione Shift + Alt para alternar.</translation>
+<translation id="2562916301614567480">Rede privada</translation>
+<translation id="6549021752953852991">Nenhuma rede celular disponível</translation>
+<translation id="4379753398862151997">Prezado monitor, as coisas não estão dando certo entre nós (este monitor não é suportado).</translation>
+<translation id="6426039856985689743">Desativar dados móveis</translation>
+<translation id="3087734570205094154">Parte inferior</translation>
+<translation id="3742055079367172538">Captura de tela realizada</translation>
+<translation id="8878886163241303700">Tela ampla</translation>
+<translation id="5271016907025319479">A VPN não está configurada.</translation>
+<translation id="372094107052732682">Pressione Ctrl+Shift+Q duas vezes para sair.</translation>
+<translation id="6803622936009808957">Não foi possível espelhar os displays porque não foram encontradas resoluções suportadas. Em vez disso, foi ativada a área de trabalho estendida.</translation>
+<translation id="1480041086352807611">Modo de demonstração</translation>
+<translation id="3626637461649818317"><ph name="PERCENTAGE"/>% restante</translation>
+<translation id="9089416786594320554">Métodos de entrada</translation>
+<translation id="6247708409970142803"><ph name="PERCENTAGE"/>%</translation>
+<translation id="2614835198358683673">Seu Chromebook pode não carregar enquanto estiver ligado. Considere usar o carregador oficial.</translation>
+<translation id="1895658205118569222">Encerramento</translation>
+<translation id="4430019312045809116">Volume</translation>
+<translation id="4442424173763614572">A busca de DNS falhou</translation>
+<translation id="6356500677799115505">A bateria está cheia e carregando.</translation>
+<translation id="7874779702599364982">Procurando redes de celular...</translation>
+<translation id="583281660410589416">Desconhecido</translation>
+<translation id="1383876407941801731">Pesquisa</translation>
+<translation id="7468789844759750875">Visite o portal de ativação do <ph name="NAME"/> para adquirir mais dados.</translation>
+<translation id="3901991538546252627">Conectando-se a <ph name="NAME"/></translation>
+<translation id="2204305834655267233">Informações de rede</translation>
+<translation id="1621499497873603021">O tempo restante até que a bateria se esgote é de <ph name="TIME_LEFT"/></translation>
+<translation id="5980301590375426705">Sair da sessão de visitante</translation>
+<translation id="4471417012762451363">A bateria está <ph name="PERCENTAGE"/>% cheia e carregando</translation>
+<translation id="8308637677604853869">Menu anterior</translation>
+<translation id="4666297444214622512">Não é possível fazer login em outra conta.</translation>
+<translation id="1346748346194534595">Para a direita</translation>
+<translation id="1773212559869067373">Certificado de autenticação rejeitado localmente</translation>
+<translation id="8528322925433439945">Celular...</translation>
+<translation id="7049357003967926684">Associação</translation>
+<translation id="8428213095426709021">Configurações</translation>
+<translation id="2372145515558759244">Sincronizando aplicativos...</translation>
+<translation id="7256405249507348194">Erro não reconhecido: <ph name="DESC"/></translation>
+<translation id="7925247922861151263">Falha ao verificar AAA</translation>
+<translation id="8456362689280298700"><ph name="HOUR"/>:<ph name="MINUTE"/> para carga completa</translation>
+<translation id="5787281376604286451">O feedback falado está ativado.
+Pressione Ctrl+Alt+Z para desativá-lo.</translation>
+<translation id="4479639480957787382">Ethernet</translation>
+<translation id="6312403991423642364">Erro de rede desconhecido</translation>
+<translation id="1467432559032391204">Para a esquerda</translation>
+<translation id="5543001071567407895">SMS</translation>
+<translation id="2354174487190027830">Ativando <ph name="NAME"/></translation>
+<translation id="8814190375133053267">Wi-Fi</translation>
+<translation id="1398853756734560583">Maximizar</translation>
+<translation id="2692809339924654275"><ph name="BLUETOOTH"/>: conectando...</translation>
+<translation id="252373100621549798">Exibição desconhecida</translation>
+<translation id="1882897271359938046">Espelhamento de <ph name="DISPLAY_NAME"/></translation>
+<translation id="2727977024730340865">Conectado a um carregador de baixa potência. O carregamento da bateria pode não ser confiável.</translation>
+<translation id="3784455785234192852">Bloquear</translation>
+<translation id="2805756323405976993">Aplicativos</translation>
+<translation id="8871072142849158571"><ph name="DISPLAY_NAME"/> foi redimensionada para <ph name="RESOLUTION"/></translation>
+<translation id="1512064327686280138">Falha na ativação</translation>
+<translation id="5097002363526479830">Falha na conexão à rede &quot;<ph name="NAME"/>&quot;: <ph name="DETAILS"/></translation>
+<translation id="1850504506766569011">O Wi-Fi está desligado.</translation>
+<translation id="8132793192354020517">Conectado à <ph name="NAME"/></translation>
+<translation id="7052914147756339792">Definir plano de fundo...</translation>
+<translation id="8678698760965522072">Estado on-line</translation>
+<translation id="2532589005999780174">Modo de alto contraste</translation>
+<translation id="1119447706177454957">Erro interno</translation>
+<translation id="3019353588588144572">O tempo restante até que a bateria esteja totalmente carregada é de <ph name="TIME_REMAINING"/></translation>
+<translation id="3473479545200714844">Lupa</translation>
+<translation id="7005812687360380971">Falha</translation>
+<translation id="882279321799040148">Clique para visualizar</translation>
+<translation id="5045550434625856497">Senha incorreta</translation>
+<translation id="1602076796624386989">Ativar dados móveis</translation>
+<translation id="6981982820502123353">Acessibilidade</translation>
+<translation id="3157931365184549694">Restaurar</translation>
+<translation id="4274292172790327596">Erro desconhecido</translation>
+<translation id="4032485810211612751"><ph name="HOURS"/>:<ph name="MINUTES"/>:<ph name="SECONDS"/></translation>
+<translation id="225680501294068881">Procurando dispositivos...</translation>
+<translation id="5597451508971090205"><ph name="SHORT_WEEKDAY"/>, <ph name="DATE"/></translation>
+<translation id="4448844063988177157">Procurando redes Wi-Fi...</translation>
+<translation id="8401662262483418323">Falha ao se conectar a &quot;<ph name="NAME"/>&quot;: <ph name="DETAILS"/>
+Mensagem do servidor: <ph name="SERVER_MSG"/></translation>
+<translation id="2475982808118771221">Ocorreu um erro</translation>
+<translation id="7229570126336867161">EVDO ausente</translation>
+<translation id="2999742336789313416"><ph name="DISPLAY_NAME"/> é uma sessão pública gerenciada por <ph name="DOMAIN"/></translation>
+<translation id="7029814467594812963">Sair da sessão</translation>
+<translation id="8454013096329229812">O Wi-Fi está ligado.</translation>
+<translation id="4872237917498892622">Alt + Pesquisar ou Shift</translation>
+<translation id="2983818520079887040">Configurações...</translation>
+<translation id="1717216362413677834">Modo dock</translation>
+<translation id="8927026611342028580">Conexão solicitada</translation>
+<translation id="8300849813060516376">Falha no OTASP</translation>
+<translation id="2792498699870441125">Alt + Pesquisar</translation>
+<translation id="8660803626959853127">Sincronizando <ph name="COUNT"/> arquivos</translation>
+<translation id="3709443003275901162">Mais de 9</translation>
+<translation id="639644700271529076">CAPS LOCK está desativado</translation>
+<translation id="6248847161401822652">Pressione Control+Shift+Q duas vezes para sair.</translation>
+<translation id="6267036997247669271"><ph name="NAME"/>: Ativando...</translation>
+<translation id="1391854757121130358">Você pode ter esgotado sua permissão de dados móveis.</translation>
+<translation id="5413208160176941586">Usuário gerenciado localmente</translation>
+<translation id="1059194134494239015"><ph name="DISPLAY_NAME"/>: <ph name="RESOLUTION"/></translation>
+<translation id="4864165860509564259">Posição do iniciador</translation>
+<translation id="7593891976182323525">Pesquisar ou Shift</translation>
+<translation id="7649070708921625228">Ajuda</translation>
+<translation id="3050422059534974565">A tecla CAPS LOCK está ativada. Pressione Pesquisar ou Shift para cancelar.</translation>
+<translation id="397105322502079400">Calculando...</translation>
+<translation id="158849752021629804">Rede doméstica ausente</translation>
+<translation id="6857811139397017780">Ativar <ph name="NETWORKSERVICE"/></translation>
+<translation id="5864471791310927901">Falha ao procurar DHCP</translation>
+<translation id="5812035014844949013">SAÍDA</translation>
+<translation id="6692173217867674490">Senha incorreta</translation>
+<translation id="6165508094623778733">Saiba mais</translation>
+<translation id="9046895021617826162">Falha na conexão</translation>
+<translation id="973896785707726617">Esta sessão terminará em <ph name="SESSION_TIME_REMAINING"/>. Você será automaticamente desconectado.</translation>
+<translation id="8372369524088641025">Chave WEP incorreta</translation>
+<translation id="6636709850131805001">Estado não reconhecido</translation>
+<translation id="3573179567135747900">Voltar a &quot;<ph name="FROM_LOCALE"/>&quot; (exige reinicialização)</translation>
+<translation id="8103386449138765447">Mensagens SMS: <ph name="MESSAGE_COUNT"/></translation>
+<translation id="5045002648206642691">Configurações do Google Drive...</translation>
+<translation id="1510238584712386396">Iniciador</translation>
+<translation id="7209101170223508707">A tecla CAPS LOCK está ativada. Pressione Alt + Pesquisar ou Shift para cancelar.</translation>
+<translation id="8940956008527784070">Nível de bateria baixo (<ph name="PERCENTAGE"/>%)</translation>
+<translation id="5102001756192215136"><ph name="HOUR"/>:<ph name="MINUTE"/> restantes</translation>
+<translation id="520760366042891468">Compartilhando o controle de sua tela por meio do Hangouts.</translation>
+<translation id="8000066093800657092">Sem rede</translation>
+<translation id="4015692727874266537">Fazer login em outra conta...</translation>
+<translation id="5941711191222866238">Minimizar</translation>
+<translation id="6911468394164995108">Conectar-se a outra...</translation>
+<translation id="412065659894267608"><ph name="HOUR"/>h<ph name="MINUTE"/>m até a carga total</translation>
+<translation id="6359806961507272919">SMS de <ph name="PHONE_NUMBER"/></translation>
+<translation id="1244147615850840081">Operadora</translation>
+</translationbundle> \ No newline at end of file
diff --git a/chromium/ash/strings/ash_strings_pt-PT.xtb b/chromium/ash/strings/ash_strings_pt-PT.xtb
new file mode 100644
index 00000000000..cf5df2d6704
--- /dev/null
+++ b/chromium/ash/strings/ash_strings_pt-PT.xtb
@@ -0,0 +1,201 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="pt-PT">
+<translation id="3595596368722241419">Bateria carregada</translation>
+<translation id="5250713215130379958">Ocultar automaticamente iniciador</translation>
+<translation id="7814236020522506259"><ph name="HOUR"/> e <ph name="MINUTE"/></translation>
+<translation id="7880025619322806991">Estado do portal</translation>
+<translation id="30155388420722288">Botão de Sobrecarga</translation>
+<translation id="5571066253365925590">Bluetooth ativado</translation>
+<translation id="9074739597929991885">Bluetooth</translation>
+<translation id="2268130516524549846">Bluetooth desativado</translation>
+<translation id="3775358506042162758">Só pode ter três contas no máximo no início de sessão integrado.</translation>
+<translation id="370649949373421643">Ativar Wi-Fi</translation>
+<translation id="3626281679859535460">Brilho</translation>
+<translation id="8054466585765276473">A calcular tempo da bateria.</translation>
+<translation id="7982789257301363584">Rede</translation>
+<translation id="5565793151875479467">Proxy...</translation>
+<translation id="938582441709398163">Sobreposição do teclado</translation>
+<translation id="4387004326333427325">Certificado de autenticação rejeitado remotamente</translation>
+<translation id="6979158407327259162">Google Drive</translation>
+<translation id="6943836128787782965">A obtenção de HTTP falhou</translation>
+<translation id="2297568595583585744">Tabuleiro de estado</translation>
+<translation id="1661867754829461514">Falta o PIN</translation>
+<translation id="4508225577814909926"><ph name="NAME"/>: A ligar...</translation>
+<translation id="4237016987259239829">Erro de ligação à rede</translation>
+<translation id="2946640296642327832">Ativar Bluetooth</translation>
+<translation id="6459472438155181876">A prolongar ecrã para <ph name="DISPLAY_NAME"/></translation>
+<translation id="8206859287963243715">Telemóvel</translation>
+<translation id="6596816719288285829">Endereço IP</translation>
+<translation id="4508265954913339219">A activação falhou</translation>
+<translation id="3621712662352432595">Definições de Áudio</translation>
+<translation id="1812696562331527143">O seu método de introdução foi alterado para <ph name="INPUT_METHOD_ID"/>*(<ph name="BEGIN_LINK"/>terceiros<ph name="END_LINK"/>).
+Prima Shift + Alt para mudar.</translation>
+<translation id="2127372758936585790">Carregador de baixo consumo</translation>
+<translation id="3846575436967432996">Não existem informações de rede disponíveis</translation>
+<translation id="3026237328237090306">Configurar dados móveis</translation>
+<translation id="785750925697875037">Ver conta do telemóvel</translation>
+<translation id="153454903766751181">A inicializar o modem celular...</translation>
+<translation id="4628814525959230255">Partilhar o controlo do seu ecrã com <ph name="HELPER_NAME"/> através do Hangouts.</translation>
+<translation id="8343941333792395995"><ph name="DISPLAY_NAME"/> foi rodado</translation>
+<translation id="7864539943188674973">Desativar Bluetooth</translation>
+<translation id="939252827960237676">Não foi possível guardar a captura de ecrã</translation>
+<translation id="3126069444801937830">Reiniciar para atualizar</translation>
+<translation id="2268813581635650749">Terminar sessão de todos</translation>
+<translation id="735745346212279324">VPN desligado</translation>
+<translation id="7320906967354320621">Inactiva</translation>
+<translation id="6303423059719347535">A bateria está <ph name="PERCENTAGE"/>% cheia</translation>
+<translation id="15373452373711364">Cursor do rato grande</translation>
+<translation id="2778346081696727092">Falha ao autenticar com o nome de utilizador ou palavra-passe fornecidos</translation>
+<translation id="3294437725009624529">Convidado</translation>
+<translation id="8190698733819146287">Personalizar idiomas e introdução...</translation>
+<translation id="2903907270192926896">ENTRADA</translation>
+<translation id="8676770494376880701">Carregador de baixo consumo ligado</translation>
+<translation id="7170041865419449892">Fora de alcance</translation>
+<translation id="4804818685124855865">Desligar</translation>
+<translation id="2544853746127077729">Certificado de autenticação rejeitado pela rede</translation>
+<translation id="5222676887888702881">Terminar sessão</translation>
+<translation id="2688477613306174402">Configuração</translation>
+<translation id="1272079795634619415">Parar</translation>
+<translation id="4957722034734105353">Saiba mais...</translation>
+<translation id="2964193600955408481">Desativar Wi-Fi</translation>
+<translation id="811680302244032017">Adicionar aparelho...</translation>
+<translation id="4279490309300973883">Espelhamento</translation>
+<translation id="2509468283778169019">CAPS LOCK está ativado</translation>
+<translation id="3892641579809465218">Apresentação Interna</translation>
+<translation id="7823564328645135659">O idioma foi alterado de <ph name="FROM_LOCALE"/> para <ph name="TO_LOCALE"/> depois de sincronizar as suas definições.</translation>
+<translation id="3368922792935385530">Ligado</translation>
+<translation id="8340999562596018839">Respostas faladas</translation>
+<translation id="8654520615680304441">Ligar Wi-Fi...</translation>
+<translation id="5825747213122829519">O seu método de introdução foi alterado para <ph name="INPUT_METHOD_ID"/>.
+Prima Shift + Alt para mudar.</translation>
+<translation id="2562916301614567480">Rede Privada</translation>
+<translation id="6549021752953852991">Sem rede celular disponível</translation>
+<translation id="4379753398862151997">Caro Monitor, não está a resultar entre nós. (Esse monitor não é suportado)</translation>
+<translation id="6426039856985689743">Desativar dados móveis</translation>
+<translation id="3087734570205094154">Parte inferior</translation>
+<translation id="3742055079367172538">Captura de ecrã efetuada</translation>
+<translation id="8878886163241303700">Ecrã alargado</translation>
+<translation id="5271016907025319479">A VPN não está configurada.</translation>
+<translation id="372094107052732682">Prima Ctrl+Shift+Q duas vezes para sair.</translation>
+<translation id="6803622936009808957">Não foi possível espelhar os ecrãs, porque não foram encontradas resoluções suportadas. Em vez disso, entrou no ambiente de trabalho expandido.</translation>
+<translation id="1480041086352807611">Modo de demonstração</translation>
+<translation id="3626637461649818317"><ph name="PERCENTAGE"/> % restante</translation>
+<translation id="9089416786594320554">Métodos de introdução</translation>
+<translation id="6247708409970142803"><ph name="PERCENTAGE"/>%</translation>
+<translation id="2614835198358683673">É possível que não consiga carregar o seu Chromebook enquanto este se encontrar ligado. Considere utilizar o carregador oficial.</translation>
+<translation id="1895658205118569222">Encerrar</translation>
+<translation id="4430019312045809116">Volume</translation>
+<translation id="4442424173763614572">A procura de DNS falhou</translation>
+<translation id="6356500677799115505">A bateria está cheia e a carregar.</translation>
+<translation id="7874779702599364982">A procurar redes celulares...</translation>
+<translation id="583281660410589416">Desconhecido</translation>
+<translation id="1383876407941801731">Pesquisa</translation>
+<translation id="7468789844759750875">Visite o portal de ativação de <ph name="NAME"/> para comprar mais dados.</translation>
+<translation id="3901991538546252627">A ligar a <ph name="NAME"/></translation>
+<translation id="2204305834655267233">Informações da Rede</translation>
+<translation id="1621499497873603021">Tempo restante até a bateria terminar, <ph name="TIME_LEFT"/></translation>
+<translation id="5980301590375426705">Sair da sessão de convidado</translation>
+<translation id="4471417012762451363">A bateria está <ph name="PERCENTAGE"/>% cheia e a carregar</translation>
+<translation id="8308637677604853869">Menu anterior</translation>
+<translation id="4666297444214622512">Não é possível iniciar sessão noutra conta.</translation>
+<translation id="1346748346194534595">Direita</translation>
+<translation id="1773212559869067373">Certificado de autenticação rejeitado localmente</translation>
+<translation id="8528322925433439945">Telemóvel...</translation>
+<translation id="7049357003967926684">Associação</translation>
+<translation id="8428213095426709021">Definições</translation>
+<translation id="2372145515558759244">A sincronizar aplicações...</translation>
+<translation id="7256405249507348194">Erro não reconhecido: <ph name="DESC"/></translation>
+<translation id="7925247922861151263">A verificação AAA falhou</translation>
+<translation id="8456362689280298700"><ph name="HOUR"/>:<ph name="MINUTE"/> até estar carregada</translation>
+<translation id="5787281376604286451">As respostas faladas estão ativadas.
+Prima Ctrl+Alt+Z para desativar.</translation>
+<translation id="4479639480957787382">Ethernet</translation>
+<translation id="6312403991423642364">Erro de rede desconhecido</translation>
+<translation id="1467432559032391204">Esquerda</translation>
+<translation id="5543001071567407895">SMS</translation>
+<translation id="2354174487190027830">A ativar <ph name="NAME"/></translation>
+<translation id="8814190375133053267">Wi-Fi</translation>
+<translation id="1398853756734560583">Maximizar</translation>
+<translation id="2692809339924654275"><ph name="BLUETOOTH"/>: A ligar...</translation>
+<translation id="252373100621549798">Apresentação Desconhecida</translation>
+<translation id="1882897271359938046">A espelhar para <ph name="DISPLAY_NAME"/></translation>
+<translation id="2727977024730340865">Ligado a um carregador de baixo consumo. O carregamento da bateria pode não ser fiável.</translation>
+<translation id="3784455785234192852">Bloquear</translation>
+<translation id="2805756323405976993">Aplicações</translation>
+<translation id="8871072142849158571"><ph name="DISPLAY_NAME"/> foi redimensionado para <ph name="RESOLUTION"/></translation>
+<translation id="1512064327686280138">Falha na activação</translation>
+<translation id="5097002363526479830">Falha ao ligar à rede &quot;<ph name="NAME"/>&quot;: <ph name="DETAILS"/></translation>
+<translation id="1850504506766569011">A ligação Wi-Fi está desativada.</translation>
+<translation id="8132793192354020517">Ligado a <ph name="NAME"/></translation>
+<translation id="7052914147756339792">Definir imagem de fundo...</translation>
+<translation id="8678698760965522072">Estado on-line</translation>
+<translation id="2532589005999780174">Modo de alto contraste</translation>
+<translation id="1119447706177454957">Erro interno</translation>
+<translation id="3019353588588144572">Tempo restante até a bateria estar totalmente carregada: <ph name="TIME_REMAINING"/></translation>
+<translation id="3473479545200714844">Lupa</translation>
+<translation id="7005812687360380971">Falha</translation>
+<translation id="882279321799040148">Clique para ver</translation>
+<translation id="5045550434625856497">Palavra-passe incorreta</translation>
+<translation id="1602076796624386989">Ativar dados móveis</translation>
+<translation id="6981982820502123353">Acessibilidade</translation>
+<translation id="3157931365184549694">Restaurar</translation>
+<translation id="4274292172790327596">Erro não reconhecido</translation>
+<translation id="4032485810211612751"><ph name="HOURS"/>:<ph name="MINUTES"/>:<ph name="SECONDS"/></translation>
+<translation id="225680501294068881">A procurar dispositivos...</translation>
+<translation id="5597451508971090205"><ph name="SHORT_WEEKDAY"/>, <ph name="DATE"/></translation>
+<translation id="4448844063988177157">A pesquisar redes Wi-Fi...</translation>
+<translation id="8401662262483418323">A ligação a &quot;<ph name="NAME"/>&quot; falhou: <ph name="DETAILS"/>
+Mensagem do servidor: <ph name="SERVER_MSG"/></translation>
+<translation id="2475982808118771221">Ocorreu um erro</translation>
+<translation id="7229570126336867161">Requer EVDO</translation>
+<translation id="2999742336789313416"><ph name="DISPLAY_NAME"/> é uma sessão pública gerida por <ph name="DOMAIN"/></translation>
+<translation id="7029814467594812963">Sair da sessão</translation>
+<translation id="8454013096329229812">A ligação Wi-Fi está ativada.</translation>
+<translation id="4872237917498892622">Alt + Pesquisar ou Shift</translation>
+<translation id="2983818520079887040">Definições...</translation>
+<translation id="1717216362413677834">Modo da estação de ancoragem</translation>
+<translation id="8927026611342028580">Ligação Solicitada</translation>
+<translation id="8300849813060516376">O OTASP falhou</translation>
+<translation id="2792498699870441125">Alt + Pesquisar</translation>
+<translation id="8660803626959853127">A sincronizar <ph name="COUNT"/> ficheiro(s)</translation>
+<translation id="3709443003275901162">9+</translation>
+<translation id="639644700271529076">CAPS LOCK está desativado</translation>
+<translation id="6248847161401822652">Prima Ctrl+Shift+Q duas vezes para sair.</translation>
+<translation id="6267036997247669271"><ph name="NAME"/>: a ativar...</translation>
+<translation id="1391854757121130358">Poderá ter utilizado toda a sua bonificação de dados móveis.</translation>
+<translation id="5413208160176941586">Utilizador gerido localmente</translation>
+<translation id="1059194134494239015"><ph name="DISPLAY_NAME"/>: <ph name="RESOLUTION"/></translation>
+<translation id="4864165860509564259">Posição do iniciador</translation>
+<translation id="7593891976182323525">Pesquisar ou Shift</translation>
+<translation id="7649070708921625228">Ajuda</translation>
+<translation id="3050422059534974565">CAPS LOCK ativado.
+Prima Pesquisar ou Shift para cancelar.</translation>
+<translation id="397105322502079400">A calcular...</translation>
+<translation id="158849752021629804">Requer rede doméstica</translation>
+<translation id="6857811139397017780">Activar <ph name="NETWORKSERVICE"/></translation>
+<translation id="5864471791310927901">A procura DHCP falhou</translation>
+<translation id="5812035014844949013">SAÍDA</translation>
+<translation id="6692173217867674490">Frase de acesso incorrecta</translation>
+<translation id="6165508094623778733">Saiba mais</translation>
+<translation id="9046895021617826162">A ligação falhou</translation>
+<translation id="973896785707726617">Esta sessão irá terminar em <ph name="SESSION_TIME_REMAINING"/>. A sua sessão será automaticamente terminada.</translation>
+<translation id="8372369524088641025">Chave WEP incorrecta</translation>
+<translation id="6636709850131805001">Estado não reconhecido</translation>
+<translation id="3573179567135747900">Reverter alteração para <ph name="FROM_LOCALE"/> (requer reinicio)</translation>
+<translation id="8103386449138765447">Mensagens SMS: <ph name="MESSAGE_COUNT"/></translation>
+<translation id="5045002648206642691">Definições do Google Drive...</translation>
+<translation id="1510238584712386396">Iniciador</translation>
+<translation id="7209101170223508707">CAPS LOCK ativado.
+Prima Alt + Pesquisar ou Shift para cancelar.</translation>
+<translation id="8940956008527784070">Bateria fraca (<ph name="PERCENTAGE"/>%)</translation>
+<translation id="5102001756192215136">Restam <ph name="HOUR"/>:<ph name="MINUTE"/></translation>
+<translation id="520760366042891468">Partilhar o controlo do seu ecrã através dos Hangouts.</translation>
+<translation id="8000066093800657092">Sem rede</translation>
+<translation id="4015692727874266537">Iniciar sessão numa conta adicional...</translation>
+<translation id="5941711191222866238">Minimizar</translation>
+<translation id="6911468394164995108">Aderir a outra...</translation>
+<translation id="412065659894267608"><ph name="HOUR"/>h <ph name="MINUTE"/>m até ficar completa</translation>
+<translation id="6359806961507272919">SMS de <ph name="PHONE_NUMBER"/></translation>
+<translation id="1244147615850840081">Operador</translation>
+</translationbundle> \ No newline at end of file
diff --git a/chromium/ash/strings/ash_strings_ro.xtb b/chromium/ash/strings/ash_strings_ro.xtb
new file mode 100644
index 00000000000..2412434089a
--- /dev/null
+++ b/chromium/ash/strings/ash_strings_ro.xtb
@@ -0,0 +1,201 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="ro">
+<translation id="3595596368722241419">Baterie încărcată</translation>
+<translation id="5250713215130379958">Ascundeți automat lansatorul</translation>
+<translation id="7814236020522506259"><ph name="HOUR"/>:<ph name="MINUTE"/></translation>
+<translation id="7880025619322806991">Stare portal</translation>
+<translation id="30155388420722288">Butonul Overflow</translation>
+<translation id="5571066253365925590">Bluetooth activat</translation>
+<translation id="9074739597929991885">Bluetooth</translation>
+<translation id="2268130516524549846">Bluetooth dezactivat</translation>
+<translation id="3775358506042162758">Conectarea multiplă acceptă maximum trei conturi.</translation>
+<translation id="370649949373421643">Activați Wi-Fi</translation>
+<translation id="3626281679859535460">Luminozitate</translation>
+<translation id="8054466585765276473">Se calculează durata bateriei.</translation>
+<translation id="7982789257301363584">Rețea</translation>
+<translation id="5565793151875479467">Proxy...</translation>
+<translation id="938582441709398163">Suprapunere a tastaturii</translation>
+<translation id="4387004326333427325">Certificatul de autentificare a fost respins de la distanță</translation>
+<translation id="6979158407327259162">Google Drive</translation>
+<translation id="6943836128787782965">Metoda GET a protocolului HTTP a eșuat</translation>
+<translation id="2297568595583585744">Bara de stare</translation>
+<translation id="1661867754829461514">Codul PIN lipsește</translation>
+<translation id="4508225577814909926"><ph name="NAME"/>: se conectează...</translation>
+<translation id="4237016987259239829">Eroare de conectare la rețea</translation>
+<translation id="2946640296642327832">Activați Bluetooth</translation>
+<translation id="6459472438155181876">Se extinde ecranul pe <ph name="DISPLAY_NAME"/></translation>
+<translation id="8206859287963243715">Celular</translation>
+<translation id="6596816719288285829">Adresă IP</translation>
+<translation id="4508265954913339219">Activarea nu a reușit</translation>
+<translation id="3621712662352432595">Setări audio</translation>
+<translation id="1812696562331527143">Metoda de introducere s-a schimbat la <ph name="INPUT_METHOD_ID"/>*(<ph name="BEGIN_LINK"/>terță parte<ph name="END_LINK"/>).
+ Apăsați pe Shift + Alt pentru a comuta.</translation>
+<translation id="2127372758936585790">Încărcător de putere joasă</translation>
+<translation id="3846575436967432996">Nu sunt disponibile informații despre rețele</translation>
+<translation id="3026237328237090306">Configurați datele mobile</translation>
+<translation id="785750925697875037">Afișați contul mobil</translation>
+<translation id="153454903766751181">Se inițializează modemul mobil...</translation>
+<translation id="4628814525959230255">În prezent, <ph name="HELPER_NAME"/> vă poate controla ecranul prin intermediul Hangouts.</translation>
+<translation id="8343941333792395995"><ph name="DISPLAY_NAME"/> a fost rotit</translation>
+<translation id="7864539943188674973">Dezactivați Bluetooth</translation>
+<translation id="939252827960237676">Captura de ecran nu a putut fi salvată.</translation>
+<translation id="3126069444801937830">Reporniți pentru a actualiza</translation>
+<translation id="2268813581635650749">Deconectați toți utilizatorii</translation>
+<translation id="735745346212279324">Rețea VPN deconectată</translation>
+<translation id="7320906967354320621">Inactivă</translation>
+<translation id="6303423059719347535">Nivelul bateriei este de <ph name="PERCENTAGE"/> %</translation>
+<translation id="15373452373711364">Cursor de mouse mare</translation>
+<translation id="2778346081696727092">Autentificarea cu numele de utilizator sau parola furnizate a eșuat</translation>
+<translation id="3294437725009624529">Invitat</translation>
+<translation id="8190698733819146287">Personalizați limbile și modul de introducere...</translation>
+<translation id="2903907270192926896">INTRARE</translation>
+<translation id="8676770494376880701">A fost conectat un încărcător de putere joasă</translation>
+<translation id="7170041865419449892">Fără acoperire</translation>
+<translation id="4804818685124855865">Deconectați-vă</translation>
+<translation id="2544853746127077729">Certificatul de autentificare a fost respins de rețea</translation>
+<translation id="5222676887888702881">Deconectați-vă</translation>
+<translation id="2688477613306174402">Configurare</translation>
+<translation id="1272079795634619415">Opriți</translation>
+<translation id="4957722034734105353">Aflați mai multe...</translation>
+<translation id="2964193600955408481">Dezactivați Wi-Fi</translation>
+<translation id="811680302244032017">Adăugați un dispozitiv...</translation>
+<translation id="4279490309300973883">Oglindire</translation>
+<translation id="2509468283778169019">Tasta CAPS LOCK este activată</translation>
+<translation id="3892641579809465218">Afișaj intern</translation>
+<translation id="7823564328645135659">După sincronizarea setărilor, limba a fost modificată de la „<ph name="FROM_LOCALE"/>” la „<ph name="TO_LOCALE"/>”.</translation>
+<translation id="3368922792935385530">Conectat</translation>
+<translation id="8340999562596018839">Feedback vocal</translation>
+<translation id="8654520615680304441">Activați Wi-Fi...</translation>
+<translation id="5825747213122829519">Metoda de introducere s-a schimbat la <ph name="INPUT_METHOD_ID"/>.
+ Apăsați pe Shift + Alt pentru a comuta.</translation>
+<translation id="2562916301614567480">Rețea privată</translation>
+<translation id="6549021752953852991">Nicio rețea mobilă disponibilă</translation>
+<translation id="4379753398862151997">Dear Monitor, it's not working out between us. (Monitorul respectiv nu este acceptat)</translation>
+<translation id="6426039856985689743">Dezactivați datele mobile</translation>
+<translation id="3087734570205094154">Jos</translation>
+<translation id="3742055079367172538">Captură de ecran efectuată</translation>
+<translation id="8878886163241303700">Ecran extins</translation>
+<translation id="5271016907025319479">Rețeaua VPN nu este configurată.</translation>
+<translation id="372094107052732682">Apăsați de două ori Ctrl+Shift+Q pentru a ieși.</translation>
+<translation id="6803622936009808957">Afișajele nu au putut fi oglindite, deoarece nu au fost găsite rezoluții acceptate. Ați intrat, în schimb, în modul monitor extins.</translation>
+<translation id="1480041086352807611">Modul demonstrativ</translation>
+<translation id="3626637461649818317">Nivel disponibil: <ph name="PERCENTAGE"/>%</translation>
+<translation id="9089416786594320554">Metode de introducere</translation>
+<translation id="6247708409970142803"><ph name="PERCENTAGE"/> %</translation>
+<translation id="2614835198358683673">Este posibil ca laptopul Chromebook să nu se încarce în timp ce este pornit. Se recomandă să utilizați încărcătorul original.</translation>
+<translation id="1895658205118569222">Închideți</translation>
+<translation id="4430019312045809116">Volum</translation>
+<translation id="4442424173763614572">Căutarea DNS a eșuat</translation>
+<translation id="6356500677799115505">Bateria este plină și se încarcă.</translation>
+<translation id="7874779702599364982">Se caută rețele mobile...</translation>
+<translation id="583281660410589416">Necunoscut</translation>
+<translation id="1383876407941801731">Căutare</translation>
+<translation id="7468789844759750875">Accesați portalul de activare <ph name="NAME"/> pentru a achiziționa mai multe date.</translation>
+<translation id="3901991538546252627">Se conectează la <ph name="NAME"/></translation>
+<translation id="2204305834655267233">Informații despre rețea</translation>
+<translation id="1621499497873603021">Timp rămas până la descărcarea bateriei: <ph name="TIME_LEFT"/></translation>
+<translation id="5980301590375426705">Ieșiți din sesiunea pentru invitați</translation>
+<translation id="4471417012762451363">Nivelul bateriei este de <ph name="PERCENTAGE"/> % și se încarcă</translation>
+<translation id="8308637677604853869">Meniul anterior</translation>
+<translation id="4666297444214622512">Nu vă mai puteți conecta la alt cont.</translation>
+<translation id="1346748346194534595">Dreapta</translation>
+<translation id="1773212559869067373">Certificatul de autentificare a fost respins local</translation>
+<translation id="8528322925433439945">Rețele mobile...</translation>
+<translation id="7049357003967926684">Asociație</translation>
+<translation id="8428213095426709021">Setări</translation>
+<translation id="2372145515558759244">Se sincronizează aplicațiile...</translation>
+<translation id="7256405249507348194">Eroare nerecunoscută: <ph name="DESC"/></translation>
+<translation id="7925247922861151263">Verificarea AAA a eșuat</translation>
+<translation id="8456362689280298700">Se încarcă în: <ph name="HOUR"/>:<ph name="MINUTE"/></translation>
+<translation id="5787281376604286451">Este activat feedbackul vocal.
+Apăsați Ctrl+Alt+Z pentru a-l dezactiva.</translation>
+<translation id="4479639480957787382">Ethernet</translation>
+<translation id="6312403991423642364">Eroare de rețea necunoscută</translation>
+<translation id="1467432559032391204">Stânga</translation>
+<translation id="5543001071567407895">SMS</translation>
+<translation id="2354174487190027830">Se activează <ph name="NAME"/></translation>
+<translation id="8814190375133053267">Wi-Fi</translation>
+<translation id="1398853756734560583">Maximizați</translation>
+<translation id="2692809339924654275"><ph name="BLUETOOTH"/>: se conectează...</translation>
+<translation id="252373100621549798">Afișaj necunoscut</translation>
+<translation id="1882897271359938046">Se oglindește pe <ph name="DISPLAY_NAME"/></translation>
+<translation id="2727977024730340865">V-ați conectat la un încărcător de putere joasă. Încărcarea bateriei poate fi nesigură.</translation>
+<translation id="3784455785234192852">Blocați</translation>
+<translation id="2805756323405976993">Google Apps</translation>
+<translation id="8871072142849158571"><ph name="DISPLAY_NAME"/> a fost redimensionat la <ph name="RESOLUTION"/></translation>
+<translation id="1512064327686280138">Activare nereușită</translation>
+<translation id="5097002363526479830">A eșuat conectarea la rețeaua „<ph name="NAME"/>”: <ph name="DETAILS"/></translation>
+<translation id="1850504506766569011">Conexiunea Wi-Fi este dezactivată.</translation>
+<translation id="8132793192354020517">Conectat la <ph name="NAME"/></translation>
+<translation id="7052914147756339792">Setați o imagine de fundal...</translation>
+<translation id="8678698760965522072">Stare online</translation>
+<translation id="2532589005999780174">Mod de contrast ridicat</translation>
+<translation id="1119447706177454957">Eroare internă</translation>
+<translation id="3019353588588144572">Timp rămas până la încărcarea completă a bateriei: <ph name="TIME_REMAINING"/></translation>
+<translation id="3473479545200714844">Lupă de ecran</translation>
+<translation id="7005812687360380971">Nereușită</translation>
+<translation id="882279321799040148">Dați clic pentru afișare</translation>
+<translation id="5045550434625856497">Parolă incorectă</translation>
+<translation id="1602076796624386989">Activați datele mobile</translation>
+<translation id="6981982820502123353">Accesibilitate</translation>
+<translation id="3157931365184549694">Restabiliți</translation>
+<translation id="4274292172790327596">Eroare nerecunoscută</translation>
+<translation id="4032485810211612751"><ph name="HOURS"/>:<ph name="MINUTES"/>:<ph name="SECONDS"/></translation>
+<translation id="225680501294068881">Se caută gadgeturi...</translation>
+<translation id="5597451508971090205"><ph name="SHORT_WEEKDAY"/>, <ph name="DATE"/></translation>
+<translation id="4448844063988177157">Se caută rețele Wi-Fi...</translation>
+<translation id="8401662262483418323">Nu s-a putut stabili conexiunea la „<ph name="NAME"/>”: <ph name="DETAILS"/>
+Mesaj server: <ph name="SERVER_MSG"/></translation>
+<translation id="2475982808118771221">A apărut o eroare</translation>
+<translation id="7229570126336867161">Este necesar EVDO</translation>
+<translation id="2999742336789313416"><ph name="DISPLAY_NAME"/> este o sesiune publică gestionată de <ph name="DOMAIN"/></translation>
+<translation id="7029814467594812963">Ieșiți din sesiune</translation>
+<translation id="8454013096329229812">Conexiunea Wi-Fi este activată.</translation>
+<translation id="4872237917498892622">Alt+Căutare sau Shift</translation>
+<translation id="2983818520079887040">Setări...</translation>
+<translation id="1717216362413677834">Mod de andocare</translation>
+<translation id="8927026611342028580">Conectare solicitată</translation>
+<translation id="8300849813060516376">OTASP a eșuat</translation>
+<translation id="2792498699870441125">Alt+Căutare</translation>
+<translation id="8660803626959853127">Se sincronizează <ph name="COUNT"/> (de) fișiere</translation>
+<translation id="3709443003275901162">Peste 9</translation>
+<translation id="639644700271529076">Tasta CAPS LOCK este dezactivată</translation>
+<translation id="6248847161401822652">Apăsați de două ori Control Shift Q pentru a ieși.</translation>
+<translation id="6267036997247669271"><ph name="NAME"/>: se activează...</translation>
+<translation id="1391854757121130358">Este posibil să fi consumat complet alocarea datelor mobile.</translation>
+<translation id="5413208160176941586">Utilizator gestionat local</translation>
+<translation id="1059194134494239015"><ph name="DISPLAY_NAME"/>: <ph name="RESOLUTION"/></translation>
+<translation id="4864165860509564259">Poziția lansatorului</translation>
+<translation id="7593891976182323525">Căutare sau Shift</translation>
+<translation id="7649070708921625228">Ajutor</translation>
+<translation id="3050422059534974565">Tasta CAPS LOCK este activată.
+Apăsați Căutare sau Shift pentru a anula.</translation>
+<translation id="397105322502079400">Se calculează...</translation>
+<translation id="158849752021629804">Este necesară rețeaua de domiciliu</translation>
+<translation id="6857811139397017780">Activați <ph name="NETWORKSERVICE"/></translation>
+<translation id="5864471791310927901">Căutarea DHCP a eșuat</translation>
+<translation id="5812035014844949013">IEȘIRE</translation>
+<translation id="6692173217867674490">Expresie de acces greșită</translation>
+<translation id="6165508094623778733">Aflați mai multe</translation>
+<translation id="9046895021617826162">Conectarea a eșuat</translation>
+<translation id="973896785707726617">Această sesiune se va încheia în <ph name="SESSION_TIME_REMAINING"/>. Veți fi deconectat(ă) automat.</translation>
+<translation id="8372369524088641025">Cheie WEP greșită</translation>
+<translation id="6636709850131805001">Stare nerecunoscută</translation>
+<translation id="3573179567135747900">Modificați înapoi la „<ph name="FROM_LOCALE"/>” (este necesară repornirea)</translation>
+<translation id="8103386449138765447">Mesaje SMS: <ph name="MESSAGE_COUNT"/></translation>
+<translation id="5045002648206642691">Setări Disc Google...</translation>
+<translation id="1510238584712386396">Lansator</translation>
+<translation id="7209101170223508707">Tasta CAPS LOCK este activată.
+Apăsați Alt+Căutare sau Shift pentru a anula.</translation>
+<translation id="8940956008527784070">Baterie slabă (<ph name="PERCENTAGE"/> %)</translation>
+<translation id="5102001756192215136">Timp rămas: <ph name="HOUR"/>:<ph name="MINUTE"/></translation>
+<translation id="520760366042891468">În prezent, permiteți controlul la ecran prin intermediul Hangouts.</translation>
+<translation id="8000066093800657092">Nicio rețea</translation>
+<translation id="4015692727874266537">Conectați-vă la alt cont...</translation>
+<translation id="5941711191222866238">Minimizați</translation>
+<translation id="6911468394164995108">Conectați-vă la altă rețea...</translation>
+<translation id="412065659894267608"><ph name="HOUR"/> h. <ph name="MINUTE"/> min. până la încărcare completă</translation>
+<translation id="6359806961507272919">SMS de la <ph name="PHONE_NUMBER"/></translation>
+<translation id="1244147615850840081">Operator</translation>
+</translationbundle> \ No newline at end of file
diff --git a/chromium/ash/strings/ash_strings_ru.xtb b/chromium/ash/strings/ash_strings_ru.xtb
new file mode 100644
index 00000000000..c248abcaa21
--- /dev/null
+++ b/chromium/ash/strings/ash_strings_ru.xtb
@@ -0,0 +1,200 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="ru">
+<translation id="3595596368722241419">Аккумулятор заряжен</translation>
+<translation id="5250713215130379958">Автоматически скрывать панель запуска</translation>
+<translation id="7814236020522506259"><ph name="HOUR"/> <ph name="MINUTE"/></translation>
+<translation id="7880025619322806991">Состояние портала</translation>
+<translation id="30155388420722288">Кнопка переполнения</translation>
+<translation id="5571066253365925590">Bluetooth включен</translation>
+<translation id="9074739597929991885">Bluetooth</translation>
+<translation id="2268130516524549846">Bluetooth отключен</translation>
+<translation id="3775358506042162758">Множественный вход поддерживает не более трех аккаунтов.</translation>
+<translation id="370649949373421643">Включить Wi-Fi</translation>
+<translation id="3626281679859535460">Яркость</translation>
+<translation id="8054466585765276473">Подсчет оставшегося времени работы от батареи…</translation>
+<translation id="7982789257301363584">Сеть</translation>
+<translation id="5565793151875479467">Прокси-сервер…</translation>
+<translation id="938582441709398163">Накладка на клавиатуру</translation>
+<translation id="4387004326333427325">Сертификат аутентификации отклонен удаленно</translation>
+<translation id="6979158407327259162">Google Диск</translation>
+<translation id="6943836128787782965">Произошла ошибка запроса HTTP GET</translation>
+<translation id="2297568595583585744">Строка состояния</translation>
+<translation id="1661867754829461514">PIN-код отсутствует</translation>
+<translation id="4508225577814909926"><ph name="NAME"/>: соединение...</translation>
+<translation id="4237016987259239829">Ошибка сетевого подключения</translation>
+<translation id="2946640296642327832">Включить Bluetooth</translation>
+<translation id="6459472438155181876">Расширение экрана на <ph name="DISPLAY_NAME"/></translation>
+<translation id="8206859287963243715">Сотовый</translation>
+<translation id="6596816719288285829">IP-адрес</translation>
+<translation id="4508265954913339219">Активация завершилась со сбоем</translation>
+<translation id="3621712662352432595">Настройки звука</translation>
+<translation id="1812696562331527143">Способ ввода изменен на <ph name="INPUT_METHOD_ID"/>*(<ph name="BEGIN_LINK"/>независимый разработчик<ph name="END_LINK"/>).
+Для переключения нажмите Shift + Alt.</translation>
+<translation id="2127372758936585790">Маломощное зарядное устройство</translation>
+<translation id="3846575436967432996">Информация о сетях недоступна</translation>
+<translation id="3026237328237090306">Настроить мобильную передачу данных</translation>
+<translation id="785750925697875037">Просмотр мобильного аккаунта</translation>
+<translation id="153454903766751181">Инициализация сотового модема…</translation>
+<translation id="4628814525959230255">Доступ к экрану для <ph name="HELPER_NAME"/> в Hangouts.</translation>
+<translation id="8343941333792395995">Экран <ph name="DISPLAY_NAME"/> перевернут</translation>
+<translation id="7864539943188674973">Отключить Bluetooth</translation>
+<translation id="939252827960237676">Не удалось сохранить скриншот</translation>
+<translation id="3126069444801937830">Перезагрузите, чтобы обновить</translation>
+<translation id="2268813581635650749">Выйти из всех аккаунтов</translation>
+<translation id="735745346212279324">VPN-соединение прервано</translation>
+<translation id="7320906967354320621">Не активно</translation>
+<translation id="6303423059719347535">Батарея заряжена на <ph name="PERCENTAGE"/>%.</translation>
+<translation id="15373452373711364">Большой курсор мыши</translation>
+<translation id="2778346081696727092">Не удалось выполнить аутентификацию</translation>
+<translation id="3294437725009624529">Гость</translation>
+<translation id="8190698733819146287">Настройка языков и ввода...</translation>
+<translation id="2903907270192926896">ВХОД</translation>
+<translation id="8676770494376880701">Подключено маломощное зарядное устройство</translation>
+<translation id="7170041865419449892">Выход за рамки диапазона</translation>
+<translation id="4804818685124855865">Отключиться</translation>
+<translation id="2544853746127077729">Сертификат аутентификации отклонен сетью</translation>
+<translation id="5222676887888702881">Выйти</translation>
+<translation id="2688477613306174402">Конфигурация</translation>
+<translation id="1272079795634619415">Остановить</translation>
+<translation id="4957722034734105353">Подробнее…</translation>
+<translation id="2964193600955408481">Отключить Wi-Fi</translation>
+<translation id="811680302244032017">Добавить устройство</translation>
+<translation id="4279490309300973883">Отражение</translation>
+<translation id="2509468283778169019">Включен режим CAPS LOCK</translation>
+<translation id="3892641579809465218">Встроенный дисплей</translation>
+<translation id="7823564328645135659">В результате синхронизации настроек язык изменен. Теперь используется <ph name="TO_LOCALE"/>, а не <ph name="FROM_LOCALE"/>.</translation>
+<translation id="3368922792935385530">Подключено</translation>
+<translation id="8340999562596018839">Голосовое сопровождение</translation>
+<translation id="8654520615680304441">Включение Wi-Fi...</translation>
+<translation id="5825747213122829519">Способ ввода изменен на <ph name="INPUT_METHOD_ID"/>.
+Для переключения нажмите Shift + Alt.</translation>
+<translation id="2562916301614567480">Частная сеть</translation>
+<translation id="6549021752953852991">Сеть не найдена</translation>
+<translation id="4379753398862151997">Не удалось выполнить операцию.</translation>
+<translation id="6426039856985689743">Отключить мобильную передачу данных</translation>
+<translation id="3087734570205094154">Низ</translation>
+<translation id="3742055079367172538">Сделан скриншот</translation>
+<translation id="8878886163241303700">Раскрытый экран</translation>
+<translation id="5271016907025319479">VPN не настроена.</translation>
+<translation id="372094107052732682">Чтобы выйти, дважды нажмите Ctrl + Shift + Q.</translation>
+<translation id="6803622936009808957">Не удалось дублировать изображение экрана, т. к. указанное разрешение не поддерживается. Включен режим расширенного рабочего стола.</translation>
+<translation id="1480041086352807611">Демонстрационный режим</translation>
+<translation id="3626637461649818317">Осталось <ph name="PERCENTAGE"/>%</translation>
+<translation id="9089416786594320554">Методы ввода</translation>
+<translation id="6247708409970142803"><ph name="PERCENTAGE"/>%</translation>
+<translation id="2614835198358683673">Возможны проблемы при зарядке Chromebook. Рекомендуем использовать комплектное зарядное устройство.</translation>
+<translation id="1895658205118569222">Завершение работы</translation>
+<translation id="4430019312045809116">Объем</translation>
+<translation id="4442424173763614572">Произошла ошибка при поиске сервера DNS</translation>
+<translation id="6356500677799115505">Батарея заряжена и подключена к источнику питания.</translation>
+<translation id="7874779702599364982">Поиск сетей мобильной связи...</translation>
+<translation id="583281660410589416">неизвестно</translation>
+<translation id="1383876407941801731">Поиск</translation>
+<translation id="7468789844759750875">Чтобы приобрести дополнительный пакет данных, перейдите на портал активации <ph name="NAME"/>.</translation>
+<translation id="3901991538546252627">Подключение к <ph name="NAME"/></translation>
+<translation id="2204305834655267233">Информация о сети</translation>
+<translation id="1621499497873603021">Оставшееся время работы от батареи: <ph name="TIME_LEFT"/>.</translation>
+<translation id="5980301590375426705">Выйти из гостевого режима</translation>
+<translation id="4471417012762451363">Батарея заряжена на <ph name="PERCENTAGE"/>% и подключена к источнику питания.</translation>
+<translation id="8308637677604853869">Предыдущее меню</translation>
+<translation id="4666297444214622512">Не удается войти ещё в один аккаунт.</translation>
+<translation id="1346748346194534595">Вправо</translation>
+<translation id="1773212559869067373">Сертификат аутентификации отклонен локально</translation>
+<translation id="8528322925433439945">Мобильные сети…</translation>
+<translation id="7049357003967926684">Связь</translation>
+<translation id="8428213095426709021">Настройки</translation>
+<translation id="2372145515558759244">Синхронизация приложений…</translation>
+<translation id="7256405249507348194">Неопознанная ошибка: <ph name="DESC"/></translation>
+<translation id="7925247922861151263">Сбой при проверке AAA</translation>
+<translation id="8456362689280298700"><ph name="HOUR"/>:<ph name="MINUTE"/> до заполнения</translation>
+<translation id="5787281376604286451">Голосовое сопровождение включено. Чтобы отключить его, нажмите Ctrl + Alt + Z.</translation>
+<translation id="4479639480957787382">Ethernet</translation>
+<translation id="6312403991423642364">Неизвестная ошибка сети</translation>
+<translation id="1467432559032391204">Влево</translation>
+<translation id="5543001071567407895">SMS</translation>
+<translation id="2354174487190027830">Активация <ph name="NAME"/></translation>
+<translation id="8814190375133053267">Wi-Fi</translation>
+<translation id="1398853756734560583">Развернуть</translation>
+<translation id="2692809339924654275"><ph name="BLUETOOTH"/>: соединение...</translation>
+<translation id="252373100621549798">Неизвестный дисплей</translation>
+<translation id="1882897271359938046">Дублирование экрана в <ph name="DISPLAY_NAME"/></translation>
+<translation id="2727977024730340865">Подключено маломощное зарядное устройство. Возможны проблемы при зарядке.</translation>
+<translation id="3784455785234192852">Заблокировать</translation>
+<translation id="2805756323405976993">Приложения</translation>
+<translation id="8871072142849158571">Установлено новое разрешение экрана <ph name="DISPLAY_NAME"/>: <ph name="RESOLUTION"/></translation>
+<translation id="1512064327686280138">Сбой активации</translation>
+<translation id="5097002363526479830">Не удалось подключиться к сети <ph name="NAME"/>: <ph name="DETAILS"/></translation>
+<translation id="1850504506766569011">Wi-Fi отключен</translation>
+<translation id="8132793192354020517">Подключено к сети <ph name="NAME"/></translation>
+<translation id="7052914147756339792">Выбрать обои</translation>
+<translation id="8678698760965522072">Состояние &quot;В сети&quot;</translation>
+<translation id="2532589005999780174">Режим высокой контрастности</translation>
+<translation id="1119447706177454957">Внутренняя ошибка</translation>
+<translation id="3019353588588144572">Оставшееся время до полной зарядки батареи: <ph name="TIME_REMAINING"/>.</translation>
+<translation id="3473479545200714844">Лупа</translation>
+<translation id="7005812687360380971">Сбой</translation>
+<translation id="882279321799040148">Посмотреть</translation>
+<translation id="5045550434625856497">Неправильный пароль</translation>
+<translation id="1602076796624386989">Включить мобильную передачу данных</translation>
+<translation id="6981982820502123353">Специальные возможности</translation>
+<translation id="3157931365184549694">Восстановить</translation>
+<translation id="4274292172790327596">Нераспознанная ошибка</translation>
+<translation id="4032485810211612751"><ph name="HOURS"/>:<ph name="MINUTES"/>:<ph name="SECONDS"/></translation>
+<translation id="225680501294068881">Поиск устройств…</translation>
+<translation id="5597451508971090205"><ph name="SHORT_WEEKDAY"/>, <ph name="DATE"/></translation>
+<translation id="4448844063988177157">Поиск сетей Wi-Fi...</translation>
+<translation id="8401662262483418323">Сбой подключения к службе &quot;<ph name="NAME"/>&quot;: <ph name="DETAILS"/>
+Сообщение сервера: <ph name="SERVER_MSG"/></translation>
+<translation id="2475982808118771221">Произошел сбой.</translation>
+<translation id="7229570126336867161">Необходимо наличие EV-DO</translation>
+<translation id="2999742336789313416">Открытый сеанс <ph name="DISPLAY_NAME"/> выполняется в домене <ph name="DOMAIN"/></translation>
+<translation id="7029814467594812963">Завершить сеанс</translation>
+<translation id="8454013096329229812">Wi-Fi включен</translation>
+<translation id="4872237917498892622">Alt + Search или Shift</translation>
+<translation id="2983818520079887040">Настройки...</translation>
+<translation id="1717216362413677834">Закрепленный режим</translation>
+<translation id="8927026611342028580">Запрос на подключение отправлен</translation>
+<translation id="8300849813060516376">Сбой OTASP</translation>
+<translation id="2792498699870441125">Alt + Search</translation>
+<translation id="8660803626959853127">Синхронизация файлов (<ph name="COUNT"/>)</translation>
+<translation id="3709443003275901162">более 9</translation>
+<translation id="639644700271529076">CAPS LOCK отключен</translation>
+<translation id="6248847161401822652">Чтобы выйти, дважды нажмите Control + Shift + Q.</translation>
+<translation id="6267036997247669271"><ph name="NAME"/>: выполняется активация...</translation>
+<translation id="1391854757121130358">Вероятно, вы использовали весь объем данных, предусмотренный тарифным планом.</translation>
+<translation id="5413208160176941586">Локально управляемый профиль</translation>
+<translation id="1059194134494239015">Разрешение экрана <ph name="DISPLAY_NAME"/>: <ph name="RESOLUTION"/></translation>
+<translation id="4864165860509564259">Положение панели запуска</translation>
+<translation id="7593891976182323525">Search или Shift</translation>
+<translation id="7649070708921625228">Справка</translation>
+<translation id="3050422059534974565">Включен режим CAPS LOCK.
+Чтобы отключить его, нажмите Search или Shift.</translation>
+<translation id="397105322502079400">Вычисление…</translation>
+<translation id="158849752021629804">Необходима домашняя сеть</translation>
+<translation id="6857811139397017780">Активировать <ph name="NETWORKSERVICE"/></translation>
+<translation id="5864471791310927901">Поиск DHCP завершен со сбоем</translation>
+<translation id="5812035014844949013">ВЫХОД</translation>
+<translation id="6692173217867674490">Неправильная кодовая фраза</translation>
+<translation id="6165508094623778733">Подробнее...</translation>
+<translation id="9046895021617826162">Сбой подключения</translation>
+<translation id="973896785707726617">Сеанс будет завершен через <ph name="SESSION_TIME_REMAINING"/>. Произойдет автоматический выход из системы.</translation>
+<translation id="8372369524088641025">Недопустимый ключ WEP</translation>
+<translation id="6636709850131805001">Нераспознанное состояние</translation>
+<translation id="3573179567135747900">Вернуться к языку: &quot;<ph name="FROM_LOCALE"/>&quot; (потребуется перезагрузка)</translation>
+<translation id="8103386449138765447">SMS: <ph name="MESSAGE_COUNT"/></translation>
+<translation id="5045002648206642691">Настройки Диска Google…</translation>
+<translation id="1510238584712386396">Панель запуска</translation>
+<translation id="7209101170223508707">Включен режим CAPS LOCK.
+Чтобы отключить его, нажмите Alt + Search или Shift.</translation>
+<translation id="8940956008527784070">Низкий заряд батареи (<ph name="PERCENTAGE"/>%)</translation>
+<translation id="5102001756192215136">Осталось <ph name="HOUR"/>:<ph name="MINUTE"/></translation>
+<translation id="520760366042891468">Доступ к экрану в Hangouts.</translation>
+<translation id="8000066093800657092">Нет сети</translation>
+<translation id="4015692727874266537">Войти ещё в один аккаунт...</translation>
+<translation id="5941711191222866238">Свернуть</translation>
+<translation id="6911468394164995108">Подключиться к другой сети...</translation>
+<translation id="412065659894267608">До полной зарядки: <ph name="HOUR"/> ч <ph name="MINUTE"/> мин</translation>
+<translation id="6359806961507272919">SMS от <ph name="PHONE_NUMBER"/></translation>
+<translation id="1244147615850840081">Оператор связи</translation>
+</translationbundle> \ No newline at end of file
diff --git a/chromium/ash/strings/ash_strings_sk.xtb b/chromium/ash/strings/ash_strings_sk.xtb
new file mode 100644
index 00000000000..02ba009f4e1
--- /dev/null
+++ b/chromium/ash/strings/ash_strings_sk.xtb
@@ -0,0 +1,201 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="sk">
+<translation id="3595596368722241419">Batéria je nabitá</translation>
+<translation id="5250713215130379958">Automaticky skryť spúšťač</translation>
+<translation id="7814236020522506259"><ph name="HOUR"/> a <ph name="MINUTE"/></translation>
+<translation id="7880025619322806991">Stav portálu</translation>
+<translation id="30155388420722288">Tlačidlo pretečenia</translation>
+<translation id="5571066253365925590">Rozhranie Bluetooth je povolené</translation>
+<translation id="9074739597929991885">Bluetooth</translation>
+<translation id="2268130516524549846">Rozhranie Bluetooth je zakázané</translation>
+<translation id="3775358506042162758">V rámci viacnásobného prihlásenia môžete mať maximálne tri účty.</translation>
+<translation id="370649949373421643">Povoliť Wi-Fi</translation>
+<translation id="3626281679859535460">Jas</translation>
+<translation id="8054466585765276473">Výpočet času výdrže batérie.</translation>
+<translation id="7982789257301363584">Sieť</translation>
+<translation id="5565793151875479467">Server proxy...</translation>
+<translation id="938582441709398163">Prekryvná vrstva klávesnice</translation>
+<translation id="4387004326333427325">Certifikát na overenie totožnosti bol zamietnutý na diaľku</translation>
+<translation id="6979158407327259162">Disk Google</translation>
+<translation id="6943836128787782965">Príkaz get protokolu HTTP zlyhal</translation>
+<translation id="2297568595583585744">Stavový panel</translation>
+<translation id="1661867754829461514">Chýba kód PIN</translation>
+<translation id="4508225577814909926"><ph name="NAME"/>: pripája sa...</translation>
+<translation id="4237016987259239829">Chyba sieťového pripojenia</translation>
+<translation id="2946640296642327832">Povoliť rozhranie Bluetooth</translation>
+<translation id="6459472438155181876">Rozšírenie obrazovky na displej <ph name="DISPLAY_NAME"/></translation>
+<translation id="8206859287963243715">Mobilné</translation>
+<translation id="6596816719288285829">Adresa IP</translation>
+<translation id="4508265954913339219">Aktivácia zlyhala</translation>
+<translation id="3621712662352432595">Nastavenia zvuku</translation>
+<translation id="1812696562331527143">Metóda vstupu sa zmenila na <ph name="INPUT_METHOD_ID"/>*(<ph name="BEGIN_LINK"/>tretia strana<ph name="END_LINK"/>).
+ Prepnete ju stlačením klávesov Shift + Alt.</translation>
+<translation id="2127372758936585790">Nabíjačka s nízkym výkonom</translation>
+<translation id="3846575436967432996">Informácie o sieti nie sú k dispozícii</translation>
+<translation id="3026237328237090306">Nastavenie mobilného dátového pripojenia</translation>
+<translation id="785750925697875037">Zobraziť mobilný účet</translation>
+<translation id="153454903766751181">Inicializácia mobilného modemu...</translation>
+<translation id="4628814525959230255">Zdieľanie ovládania obrazovky s používateľom <ph name="HELPER_NAME"/> prostredníctvom služby Hangouts.</translation>
+<translation id="8343941333792395995">Obrazovka <ph name="DISPLAY_NAME"/> sa otočila</translation>
+<translation id="7864539943188674973">Zakázať rozhranie Bluetooth</translation>
+<translation id="939252827960237676">Uloženie snímky obrazovky zlyhalo.</translation>
+<translation id="3126069444801937830">Reštartovaním vykonáte aktualizáciu</translation>
+<translation id="2268813581635650749">Odhlásiť všetkých</translation>
+<translation id="735745346212279324">Sieť VPN je odpojená</translation>
+<translation id="7320906967354320621">Nečinná</translation>
+<translation id="6303423059719347535">Batéria je nabitá na <ph name="PERCENTAGE"/> %</translation>
+<translation id="15373452373711364">Veľký kurzor myši</translation>
+<translation id="2778346081696727092">Overenie totožnosti pomocou zadaného používateľského mena a hesla zlyhalo</translation>
+<translation id="3294437725009624529">Hosť</translation>
+<translation id="8190698733819146287">Prebieha prispôsobenie jazykov a vstupu...</translation>
+<translation id="2903907270192926896">VSTUP</translation>
+<translation id="8676770494376880701">Pripojila sa nabíjačka s nízkym výkonom</translation>
+<translation id="7170041865419449892">Mimo rozsah</translation>
+<translation id="4804818685124855865">Odpojiť</translation>
+<translation id="2544853746127077729">Certifikát na overenie totožnosti bol zamietnutý sieťou</translation>
+<translation id="5222676887888702881">Odhlásiť sa</translation>
+<translation id="2688477613306174402">Konfigurácia</translation>
+<translation id="1272079795634619415">Zastaviť</translation>
+<translation id="4957722034734105353">Viac informácií...</translation>
+<translation id="2964193600955408481">Zakázať sieť Wi-Fi</translation>
+<translation id="811680302244032017">Pridať zariadenie ...</translation>
+<translation id="4279490309300973883">Zrkadlenie</translation>
+<translation id="2509468283778169019">Kláves CAPS LOCK je zapnutý</translation>
+<translation id="3892641579809465218">Interný displej</translation>
+<translation id="7823564328645135659">Po synchronizácii vašich nastavení bol zmenený jazyk „<ph name="FROM_LOCALE"/>“ na jazyk „<ph name="TO_LOCALE"/>“.</translation>
+<translation id="3368922792935385530">Pripojené</translation>
+<translation id="8340999562596018839">Hlasová odozva</translation>
+<translation id="8654520615680304441">Zapnúť sieť Wi-Fi...</translation>
+<translation id="5825747213122829519">Metóda vstupu sa zmenila na <ph name="INPUT_METHOD_ID"/>.
+ Prepnete ju stlačením klávesov Shift + Alt.</translation>
+<translation id="2562916301614567480">Súkromná sieť</translation>
+<translation id="6549021752953852991">K dispozícii nie je žiadna mobilná sieť</translation>
+<translation id="4379753398862151997">Milý monitor, medzi nami to nefunguje. (Tento monitor sa nepodporuje)</translation>
+<translation id="6426039856985689743">Zakázať mobilné dátové pripojenie</translation>
+<translation id="3087734570205094154">Spodok</translation>
+<translation id="3742055079367172538">Vytvorila sa snímka obrazovky</translation>
+<translation id="8878886163241303700">Rozšírenie obrazovky</translation>
+<translation id="5271016907025319479">Sieť VPN nie je nakonfigurovaná.</translation>
+<translation id="372094107052732682">Ak chcete skončiť, stlačte dvakrát kombináciu kláves Ctrl+Shift+Q.</translation>
+<translation id="6803622936009808957">Obraz na monitoroch sa nedá zrkadliť, pretože sa nenašli podporované rozlíšenia. Namiesto toho sa spustil režim rozšírenej pracovnej plochy.</translation>
+<translation id="1480041086352807611">Režim ukážky</translation>
+<translation id="3626637461649818317">Zostáva <ph name="PERCENTAGE"/> %</translation>
+<translation id="9089416786594320554">Metódy vstupu</translation>
+<translation id="6247708409970142803"><ph name="PERCENTAGE"/> %</translation>
+<translation id="2614835198358683673">Chromebook sa nesmie dobíjať v čase, keď je zapnutý. Zvážte použitie oficiálnej nabíjačky.</translation>
+<translation id="1895658205118569222">Vypnúť</translation>
+<translation id="4430019312045809116">Hlasitosť</translation>
+<translation id="4442424173763614572">Vyhľadanie DNS zlyhalo</translation>
+<translation id="6356500677799115505">Batéria je úplne nabitá a nabíja sa.</translation>
+<translation id="7874779702599364982">Prebieha vyhľadávanie mobilných sietí...</translation>
+<translation id="583281660410589416">Neznámy</translation>
+<translation id="1383876407941801731">Vyhľadávanie</translation>
+<translation id="7468789844759750875">Ak chcete kúpiť ďalšie dáta, navštívte aktivačný portál <ph name="NAME"/>.</translation>
+<translation id="3901991538546252627">Pripája sa k sieti <ph name="NAME"/></translation>
+<translation id="2204305834655267233">Informácie o sieti</translation>
+<translation id="1621499497873603021">Čas zostávajúci do vybitia batérie: <ph name="TIME_LEFT"/></translation>
+<translation id="5980301590375426705">Ukončiť reláciu hosťa</translation>
+<translation id="4471417012762451363">Batéria je nabitá na <ph name="PERCENTAGE"/> % a nabíja sa</translation>
+<translation id="8308637677604853869">Predchádzajúca ponuka</translation>
+<translation id="4666297444214622512">Nepodarilo sa prihlásiť do iného účtu.</translation>
+<translation id="1346748346194534595">Doprava</translation>
+<translation id="1773212559869067373">Certifikát na overenie totožnosti bol zamietnutý miestne</translation>
+<translation id="8528322925433439945">Mobilné siete...</translation>
+<translation id="7049357003967926684">Asociácia</translation>
+<translation id="8428213095426709021">Nastavenia</translation>
+<translation id="2372145515558759244">Prebieha synchronizácia aplikácií...</translation>
+<translation id="7256405249507348194">Nerozpoznaná chyba: <ph name="DESC"/></translation>
+<translation id="7925247922861151263">Kontrola AAA zlyhala</translation>
+<translation id="8456362689280298700">čas do úplného nabitia: <ph name="HOUR"/>:<ph name="MINUTE"/></translation>
+<translation id="5787281376604286451">Hlasová odozva je povolená.
+Zakážete ju stlačením klávesov Ctrl+Alt+Z.</translation>
+<translation id="4479639480957787382">Ethernet</translation>
+<translation id="6312403991423642364">Neznáma chyba siete</translation>
+<translation id="1467432559032391204">Doľava</translation>
+<translation id="5543001071567407895">SMS</translation>
+<translation id="2354174487190027830">Aktivujte sa sieť <ph name="NAME"/></translation>
+<translation id="8814190375133053267">Wi-Fi</translation>
+<translation id="1398853756734560583">Maximalizovať</translation>
+<translation id="2692809339924654275"><ph name="BLUETOOTH"/>: pripája sa...</translation>
+<translation id="252373100621549798">Neznáma obrazovka</translation>
+<translation id="1882897271359938046">Zrkadlenie na displej <ph name="DISPLAY_NAME"/></translation>
+<translation id="2727977024730340865">Batéria je zapojená do nabíjačky s nízkym výkonom. Nabíjanie batérie nemusí byť spoľahlivé.</translation>
+<translation id="3784455785234192852">Uzamknúť</translation>
+<translation id="2805756323405976993">Aplikácie</translation>
+<translation id="8871072142849158571">Veľkosť obrazovky <ph name="DISPLAY_NAME"/> sa zmenila na veľkosť <ph name="RESOLUTION"/></translation>
+<translation id="1512064327686280138">Zlyhanie aktivácie</translation>
+<translation id="5097002363526479830">K sieti „<ph name="NAME"/>“ sa nepodarilo pripojiť: <ph name="DETAILS"/></translation>
+<translation id="1850504506766569011">Pripojenie Wi-Fi je vypnuté.</translation>
+<translation id="8132793192354020517">Pripojené k stránke <ph name="NAME"/></translation>
+<translation id="7052914147756339792">Nastavenie tapety...</translation>
+<translation id="8678698760965522072">Stav online</translation>
+<translation id="2532589005999780174">Režim s vysokým kontrastom</translation>
+<translation id="1119447706177454957">Interná chyba</translation>
+<translation id="3019353588588144572">Čas zostávajúci do úplného nabitia batérie: <ph name="TIME_REMAINING"/></translation>
+<translation id="3473479545200714844">Lupa</translation>
+<translation id="7005812687360380971">Zlyhanie</translation>
+<translation id="882279321799040148">Zobrazíte ju kliknutím tu</translation>
+<translation id="5045550434625856497">Nesprávne heslo</translation>
+<translation id="1602076796624386989">Povoliť mobilné dátové pripojenie</translation>
+<translation id="6981982820502123353">Dostupnosť</translation>
+<translation id="3157931365184549694">Obnoviť</translation>
+<translation id="4274292172790327596">Nerozpoznaná chyba</translation>
+<translation id="4032485810211612751"><ph name="HOURS"/>:<ph name="MINUTES"/>:<ph name="SECONDS"/></translation>
+<translation id="225680501294068881">Hľadajú sa zariadenia...</translation>
+<translation id="5597451508971090205"><ph name="SHORT_WEEKDAY"/>, <ph name="DATE"/></translation>
+<translation id="4448844063988177157">Prebieha vyhľadávanie sietí Wi-Fi...</translation>
+<translation id="8401662262483418323">Nepodarilo sa pripojiť k účtu <ph name="NAME"/>: <ph name="DETAILS"/>
+Správa zo servera: <ph name="SERVER_MSG"/></translation>
+<translation id="2475982808118771221">Vyskytla sa chyba</translation>
+<translation id="7229570126336867161">Je potrebné pripojenie EVDO</translation>
+<translation id="2999742336789313416"><ph name="DISPLAY_NAME"/> je verejná relácia spravovaná stránkami <ph name="DOMAIN"/></translation>
+<translation id="7029814467594812963">Ukončiť reláciu</translation>
+<translation id="8454013096329229812">Pripojenie Wi-Fi je zapnuté.</translation>
+<translation id="4872237917498892622">Alt + Hľadať alebo Shift</translation>
+<translation id="2983818520079887040">Nastavenia...</translation>
+<translation id="1717216362413677834">Režim doku</translation>
+<translation id="8927026611342028580">Vyžaduje sa pripojenie</translation>
+<translation id="8300849813060516376">Zlyhanie služby OTASP</translation>
+<translation id="2792498699870441125">Alt + Hľadať</translation>
+<translation id="8660803626959853127">Synchronizácia súborov (<ph name="COUNT"/>)</translation>
+<translation id="3709443003275901162">viac ako 9</translation>
+<translation id="639644700271529076">CAPS LOCK je vypnutý</translation>
+<translation id="6248847161401822652">Ak chcete skončiť, stlačte dvakrát kombináciu kláves Ctrl+Shift+Q.</translation>
+<translation id="6267036997247669271"><ph name="NAME"/>: Aktivuje sa...</translation>
+<translation id="1391854757121130358">Možno ste dosiahli limit povolených mobilných dát.</translation>
+<translation id="5413208160176941586">Miestne spravovaný používateľ</translation>
+<translation id="1059194134494239015"><ph name="DISPLAY_NAME"/>: <ph name="RESOLUTION"/></translation>
+<translation id="4864165860509564259">Pozícia spúťača</translation>
+<translation id="7593891976182323525">Hľadať alebo Shift</translation>
+<translation id="7649070708921625228">Pomocník</translation>
+<translation id="3050422059534974565">Kláves CAPS LOCK je zapnutý.
+Ak ho chcete zrušiť, stlačte klávesy Hľadať alebo Shift.</translation>
+<translation id="397105322502079400">Prebieha výpočet...</translation>
+<translation id="158849752021629804">Je potrebná domáca sieť</translation>
+<translation id="6857811139397017780">Aktivovať zariadenie <ph name="NETWORKSERVICE"/></translation>
+<translation id="5864471791310927901">Vyhľadanie servera DHCP zlyhalo</translation>
+<translation id="5812035014844949013">VÝSTUP</translation>
+<translation id="6692173217867674490">Zlá prístupová fráza</translation>
+<translation id="6165508094623778733">Viac informácií</translation>
+<translation id="9046895021617826162">Zlyhanie pripojenia</translation>
+<translation id="973896785707726617">Relácia sa ukončí o <ph name="SESSION_TIME_REMAINING"/>. Automaticky dôjde k odhláseniu.</translation>
+<translation id="8372369524088641025">Zlý kľúč WEP</translation>
+<translation id="6636709850131805001">Nerozpoznaný stav</translation>
+<translation id="3573179567135747900">Zmeniť späť na miestne nastavenie „<ph name="FROM_LOCALE"/>“ (vyžaduje sa reštart)</translation>
+<translation id="8103386449138765447">Správy SMS: <ph name="MESSAGE_COUNT"/></translation>
+<translation id="5045002648206642691">Nastavenia služby Disk Google...</translation>
+<translation id="1510238584712386396">Spúšťač</translation>
+<translation id="7209101170223508707">Kláves CAPS LOCK je zapnutý.
+Ak ho chcete zrušiť, stlačte klávesy Alt + Hľadať alebo Shift.</translation>
+<translation id="8940956008527784070">Kapacita batérie je nízka (<ph name="PERCENTAGE"/> %)</translation>
+<translation id="5102001756192215136">zostáva <ph name="HOUR"/>:<ph name="MINUTE"/></translation>
+<translation id="520760366042891468">Zdieľanie ovládania obrazovky prostredníctvom služby Hangouts.</translation>
+<translation id="8000066093800657092">Žiadna sieť</translation>
+<translation id="4015692727874266537">Prihláste sa do iného účtu...</translation>
+<translation id="5941711191222866238">Minimalizovať</translation>
+<translation id="6911468394164995108">Pripojiť k ďalšej...</translation>
+<translation id="412065659894267608"><ph name="HOUR"/>h <ph name="MINUTE"/>min do nabitia</translation>
+<translation id="6359806961507272919">SMS z č. <ph name="PHONE_NUMBER"/></translation>
+<translation id="1244147615850840081">Operátor</translation>
+</translationbundle> \ No newline at end of file
diff --git a/chromium/ash/strings/ash_strings_sl.xtb b/chromium/ash/strings/ash_strings_sl.xtb
new file mode 100644
index 00000000000..3ac1f59d5b2
--- /dev/null
+++ b/chromium/ash/strings/ash_strings_sl.xtb
@@ -0,0 +1,201 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="sl">
+<translation id="3595596368722241419">Baterija je polna</translation>
+<translation id="5250713215130379958">Samodejno skrij zaganjalnik</translation>
+<translation id="7814236020522506259"><ph name="HOUR"/> in <ph name="MINUTE"/></translation>
+<translation id="7880025619322806991">Stanje portala</translation>
+<translation id="30155388420722288">Gumb za presežek</translation>
+<translation id="5571066253365925590">Bluetooth omogočen</translation>
+<translation id="9074739597929991885">Bluetooth</translation>
+<translation id="2268130516524549846">Bluetooth onemogočen</translation>
+<translation id="3775358506042162758">S prijavo z več računi lahko uporabljate samo tri račune.</translation>
+<translation id="370649949373421643">Omogoči Wi-Fi</translation>
+<translation id="3626281679859535460">Svetlost</translation>
+<translation id="8054466585765276473">Izračunavanje časa trajanja akumulatorja.</translation>
+<translation id="7982789257301363584">Omrežje</translation>
+<translation id="5565793151875479467">Proxy ...</translation>
+<translation id="938582441709398163">Prekrivna tipkovnica</translation>
+<translation id="4387004326333427325">Potrdilo za preverjanje pristnosti je bilo zavrnjeno na oddaljeni lokaciji</translation>
+<translation id="6979158407327259162">Google Drive</translation>
+<translation id="6943836128787782965">Ukaz HTTP get ni uspel</translation>
+<translation id="2297568595583585744">Pladenj stanja</translation>
+<translation id="1661867754829461514">Manjka PIN</translation>
+<translation id="4508225577814909926"><ph name="NAME"/>: vzpostavljanje povezave ...</translation>
+<translation id="4237016987259239829">Napaka omrežne povezave</translation>
+<translation id="2946640296642327832">Omogoči Bluetooth</translation>
+<translation id="6459472438155181876">Razširitev zaslon na <ph name="DISPLAY_NAME"/></translation>
+<translation id="8206859287963243715">Prenosni</translation>
+<translation id="6596816719288285829">Naslov IP</translation>
+<translation id="4508265954913339219">Aktiviranje ni uspelo</translation>
+<translation id="3621712662352432595">Nastavitve zvoka</translation>
+<translation id="1812696562331527143">Način vnosa se je spremenil v <ph name="INPUT_METHOD_ID"/>*(<ph name="BEGIN_LINK"/>drug ponudnik<ph name="END_LINK"/>).
+Pritisnite Shift + Alt, da ga preklopite.</translation>
+<translation id="2127372758936585790">Nizkoenergijski polnilnik</translation>
+<translation id="3846575436967432996">Ni podatkov o omrežju</translation>
+<translation id="3026237328237090306">Nastavitev mobilne podatkovne povezave</translation>
+<translation id="785750925697875037">Prikaz mobilnega računa</translation>
+<translation id="153454903766751181">Inicializacija modema za mobilno omrežje ...</translation>
+<translation id="4628814525959230255">Skupni nadzor zaslona z osebo <ph name="HELPER_NAME"/> prek klepetalnic Hangouts.</translation>
+<translation id="8343941333792395995">Zaslon <ph name="DISPLAY_NAME"/> je zasukan</translation>
+<translation id="7864539943188674973">Onemogoči Bluetooth</translation>
+<translation id="939252827960237676">Posnetka zaslona ni bilo mogoče shraniti</translation>
+<translation id="3126069444801937830">Znova zaženite za posodobitev</translation>
+<translation id="2268813581635650749">Odjava vseh</translation>
+<translation id="735745346212279324">Povezava z navideznim zasebnim omrežjem je prekinjena</translation>
+<translation id="7320906967354320621">Nedejavno</translation>
+<translation id="6303423059719347535">Napolnjenost akumulatorja: <ph name="PERCENTAGE"/> %</translation>
+<translation id="15373452373711364">Velik miškin kazalec</translation>
+<translation id="2778346081696727092">Preverjanje pristnosti vnesenega uporabniškega imena ali gesla ni uspelo</translation>
+<translation id="3294437725009624529">Gost</translation>
+<translation id="8190698733819146287">Prilagajanje jezikov in vnosa ...</translation>
+<translation id="2903907270192926896">VHOD</translation>
+<translation id="8676770494376880701">Priključen je nizkoenergijski polnilnik</translation>
+<translation id="7170041865419449892">Zunaj dosega</translation>
+<translation id="4804818685124855865">Prekini povezavo</translation>
+<translation id="2544853746127077729">Omrežje je zavrnilo potrdilo za preverjanje pristnosti</translation>
+<translation id="5222676887888702881">Odjava</translation>
+<translation id="2688477613306174402">Konfiguracija</translation>
+<translation id="1272079795634619415">Ustavi</translation>
+<translation id="4957722034734105353">Več o tem ...</translation>
+<translation id="2964193600955408481">Onemogoči Wi-Fi</translation>
+<translation id="811680302244032017">Dodaj napravo ...</translation>
+<translation id="4279490309300973883">Zrcaljenje</translation>
+<translation id="2509468283778169019">Tipka CAPS LOCK je vklopljena</translation>
+<translation id="3892641579809465218">Notranji zaslon</translation>
+<translation id="7823564328645135659">Po sinhronizaciji nastavitev se je jezik spremenil iz jezika »<ph name="FROM_LOCALE"/>« v jezik »<ph name="TO_LOCALE"/>«.</translation>
+<translation id="3368922792935385530">Povezano</translation>
+<translation id="8340999562596018839">Glasovni odziv</translation>
+<translation id="8654520615680304441">Vklop omrežja Wi-Fi ...</translation>
+<translation id="5825747213122829519">Način vnosa se je spremenil v <ph name="INPUT_METHOD_ID"/>.
+Pritisnite Shift + Alt, da ga preklopite.</translation>
+<translation id="2562916301614567480">Zasebno omrežje</translation>
+<translation id="6549021752953852991">Mobilno omrežje ni na voljo</translation>
+<translation id="4379753398862151997">Dragi monitor, med nama se ne bo obneslo. (Ta monitor ni podprt)</translation>
+<translation id="6426039856985689743">Onemogoči mobilno podatkovno povezavo</translation>
+<translation id="3087734570205094154">Na dno</translation>
+<translation id="3742055079367172538">Posnetek zaslona je narejen</translation>
+<translation id="8878886163241303700">Razširjanje zaslona</translation>
+<translation id="5271016907025319479">VPN ni konfiguriran.</translation>
+<translation id="372094107052732682">Dvakrat pritisnite Ctrl + Shift + Q, če želite končati.</translation>
+<translation id="6803622936009808957">Zaslonov ni bilo mogoče zrcaliti, ker ni bilo najdene nobene podprte ločljivosti. Uporabljeno je razširjeno namizje.</translation>
+<translation id="1480041086352807611">Predstavitveni način</translation>
+<translation id="3626637461649818317">Preostane še <ph name="PERCENTAGE"/> %</translation>
+<translation id="9089416786594320554">Načini vnosa</translation>
+<translation id="6247708409970142803"><ph name="PERCENTAGE"/> %</translation>
+<translation id="2614835198358683673">Vaš Chromebook se ne more polniti, ko je vklopljen. Priporočamo uporabo uradnega polnilnika.</translation>
+<translation id="1895658205118569222">Zaprt</translation>
+<translation id="4430019312045809116">Glasnost</translation>
+<translation id="4442424173763614572">Iskanje DNS ni uspelo</translation>
+<translation id="6356500677799115505">Akumulator je poln in se polni.</translation>
+<translation id="7874779702599364982">Iskanje mobilnih omrežij ...</translation>
+<translation id="583281660410589416">Neznano</translation>
+<translation id="1383876407941801731">Iskanje</translation>
+<translation id="7468789844759750875">Če želite kupiti dodatne podatke, obiščite aktivacijski portal <ph name="NAME"/>.</translation>
+<translation id="3901991538546252627">Vzpostavljanje povezave z omrežjem <ph name="NAME"/></translation>
+<translation id="2204305834655267233">Podatki o omrežju</translation>
+<translation id="1621499497873603021">Preostali čas do izpraznitve akumulatorja, <ph name="TIME_LEFT"/></translation>
+<translation id="5980301590375426705">Zapusti sejo gosta</translation>
+<translation id="4471417012762451363">Napolnjenost akumulatorja: <ph name="PERCENTAGE"/> % in se polni</translation>
+<translation id="8308637677604853869">Prejšnji meni</translation>
+<translation id="4666297444214622512">V drug račun se ni mogoče prijaviti.</translation>
+<translation id="1346748346194534595">V desno</translation>
+<translation id="1773212559869067373">Potrdilo za preverjanje pristnosti je bilo zavrnjeno lokalno</translation>
+<translation id="8528322925433439945">Mobilna ...</translation>
+<translation id="7049357003967926684">Povezava</translation>
+<translation id="8428213095426709021">Nastavitve</translation>
+<translation id="2372145515558759244">Sinhronizacija aplikacij ...</translation>
+<translation id="7256405249507348194">Neprepoznana napaka: <ph name="DESC"/></translation>
+<translation id="7925247922861151263">Preverjanje AAA ni uspelo</translation>
+<translation id="8456362689280298700">Čas polnjenja: še <ph name="HOUR"/>:<ph name="MINUTE"/></translation>
+<translation id="5787281376604286451">Glasovni odziv je omogočen.
+Če ga želite onemogočiti, pritisnite Ctrl + Alt + Z.</translation>
+<translation id="4479639480957787382">Ethernet</translation>
+<translation id="6312403991423642364">Neznana napaka v omrežju</translation>
+<translation id="1467432559032391204">V levo</translation>
+<translation id="5543001071567407895">SMS</translation>
+<translation id="2354174487190027830">Aktiviranje omrežja <ph name="NAME"/></translation>
+<translation id="8814190375133053267">Wi-Fi</translation>
+<translation id="1398853756734560583">Povečaj</translation>
+<translation id="2692809339924654275"><ph name="BLUETOOTH"/>: vzpostavljanje povezave ...</translation>
+<translation id="252373100621549798">Neznan prikaz</translation>
+<translation id="1882897271359938046">Zrcaljenje na <ph name="DISPLAY_NAME"/></translation>
+<translation id="2727977024730340865">Priključen je nizkoenergijski polnilnik. Polnjenje akumulatorja morda ne bo zanesljivo.</translation>
+<translation id="3784455785234192852">Zakleni</translation>
+<translation id="2805756323405976993">Google Apps</translation>
+<translation id="8871072142849158571">Ločljivost zaslona <ph name="DISPLAY_NAME"/> je spremenjena na <ph name="RESOLUTION"/></translation>
+<translation id="1512064327686280138">Napaka pri aktiviranju</translation>
+<translation id="5097002363526479830">Povezava z omrežjem »<ph name="NAME"/>« ni uspela: <ph name="DETAILS"/></translation>
+<translation id="1850504506766569011">Wi-Fi je izklopljen.</translation>
+<translation id="8132793192354020517">Povezava z <ph name="NAME"/> je vzpostavljena </translation>
+<translation id="7052914147756339792">Nastavi sliko za ozadje ...</translation>
+<translation id="8678698760965522072">Stanje s povezavo</translation>
+<translation id="2532589005999780174">Visokokontrastni način</translation>
+<translation id="1119447706177454957">Notranja napaka</translation>
+<translation id="3019353588588144572">Preostali čas do napolnitve akumulatorja, <ph name="TIME_REMAINING"/></translation>
+<translation id="3473479545200714844">Lupa</translation>
+<translation id="7005812687360380971">Napaka</translation>
+<translation id="882279321799040148">Kliknite za prikaz</translation>
+<translation id="5045550434625856497">Napačno geslo</translation>
+<translation id="1602076796624386989">Omogoči mobilno podatkovno povezavo</translation>
+<translation id="6981982820502123353">Pripomočki za osebe s posebnimi potrebami</translation>
+<translation id="3157931365184549694">Obnovi</translation>
+<translation id="4274292172790327596">Neprepoznana napaka</translation>
+<translation id="4032485810211612751"><ph name="HOURS"/>:<ph name="MINUTES"/>:<ph name="SECONDS"/></translation>
+<translation id="225680501294068881">Iskanje naprav ...</translation>
+<translation id="5597451508971090205"><ph name="SHORT_WEEKDAY"/>, <ph name="DATE"/></translation>
+<translation id="4448844063988177157">Iskanje omrežij Wi-Fi</translation>
+<translation id="8401662262483418323">Povezava s/z »<ph name="NAME"/>« ni bila mogoča: <ph name="DETAILS"/>
+Sporočilo strežnika: <ph name="SERVER_MSG"/></translation>
+<translation id="2475982808118771221">Prišlo je do napake</translation>
+<translation id="7229570126336867161">Potreben je EVDO</translation>
+<translation id="2999742336789313416"><ph name="DISPLAY_NAME"/> je javna seja, ki jo upravlja <ph name="DOMAIN"/></translation>
+<translation id="7029814467594812963">Zapusti sejo</translation>
+<translation id="8454013096329229812">Wi-Fi je vklopljen.</translation>
+<translation id="4872237917498892622">Alt + iskanje ali Shift</translation>
+<translation id="2983818520079887040">Nastavitve ...</translation>
+<translation id="1717216362413677834">Zasidrani način</translation>
+<translation id="8927026611342028580">Povezava zahtevana</translation>
+<translation id="8300849813060516376">Storitev OTASP ni uspela</translation>
+<translation id="2792498699870441125">Alt + iskanje</translation>
+<translation id="8660803626959853127">Sinhroniziranje toliko datotek: <ph name="COUNT"/> ...</translation>
+<translation id="3709443003275901162">Več kot 9</translation>
+<translation id="639644700271529076">Tipka CAPS LOCK je izklopljena</translation>
+<translation id="6248847161401822652">Dvakrat pritisnite Ctrl + Shift + Q, če želite končati.</translation>
+<translation id="6267036997247669271"><ph name="NAME"/>: Aktiviranje ...</translation>
+<translation id="1391854757121130358">Morda ste porabili dovoljeno količino mobilnih podatkov.</translation>
+<translation id="5413208160176941586">Lokalno upravljani uporabnik</translation>
+<translation id="1059194134494239015"><ph name="DISPLAY_NAME"/>: <ph name="RESOLUTION"/></translation>
+<translation id="4864165860509564259">Položaj zaganjalnika</translation>
+<translation id="7593891976182323525">Iskanje ali Shift</translation>
+<translation id="7649070708921625228">Pomoč</translation>
+<translation id="3050422059534974565">Tipka CAPS LOCK je vklopljena.
+Pritisnite tipko za iskanje ali Shift, da jo prekličete.</translation>
+<translation id="397105322502079400">Izračunavanje …</translation>
+<translation id="158849752021629804">Potrebno je domače omrežje</translation>
+<translation id="6857811139397017780">Aktiviraj <ph name="NETWORKSERVICE"/></translation>
+<translation id="5864471791310927901">Iskanje DHCP-ja ni uspelo</translation>
+<translation id="5812035014844949013">IZHOD</translation>
+<translation id="6692173217867674490">Napačno geslo</translation>
+<translation id="6165508094623778733">Več o tem</translation>
+<translation id="9046895021617826162">Vzpostavljanje povezave ni uspelo</translation>
+<translation id="973896785707726617">Ta seja se bo končala čez <ph name="SESSION_TIME_REMAINING"/>. Samodejno boste odjavljeni.</translation>
+<translation id="8372369524088641025">Napačen ključ WEP</translation>
+<translation id="6636709850131805001">Neprepoznano stanje</translation>
+<translation id="3573179567135747900">Spremeni nazaj v jezik »<ph name="FROM_LOCALE"/>« (potreben vnovični zagon)</translation>
+<translation id="8103386449138765447">Sporočila SMS: <ph name="MESSAGE_COUNT"/></translation>
+<translation id="5045002648206642691">Nastavitve za Google Drive ...</translation>
+<translation id="1510238584712386396">Zaganjalnik</translation>
+<translation id="7209101170223508707">Tipka CAPS LOCK je vklopljena.
+Pritisnite Alt in tipko za iskanje ali Shift, da jo prekličete.</translation>
+<translation id="8940956008527784070">Akumulator je skoraj prazen (<ph name="PERCENTAGE"/> %)</translation>
+<translation id="5102001756192215136">Še <ph name="HOUR"/>.<ph name="MINUTE"/></translation>
+<translation id="520760366042891468">Skupni nadzor zaslona prek klepetalnic Hangouts.</translation>
+<translation id="8000066093800657092">Ni omrežja</translation>
+<translation id="4015692727874266537">Prijava z drugim računom ...</translation>
+<translation id="5941711191222866238">Pomanjšaj</translation>
+<translation id="6911468394164995108">Pridružite se drugemu ...</translation>
+<translation id="412065659894267608"><ph name="HOUR"/> h <ph name="MINUTE"/> min do napolnjenosti</translation>
+<translation id="6359806961507272919">SMS od <ph name="PHONE_NUMBER"/></translation>
+<translation id="1244147615850840081">Operater</translation>
+</translationbundle> \ No newline at end of file
diff --git a/chromium/ash/strings/ash_strings_sr.xtb b/chromium/ash/strings/ash_strings_sr.xtb
new file mode 100644
index 00000000000..dba64929d63
--- /dev/null
+++ b/chromium/ash/strings/ash_strings_sr.xtb
@@ -0,0 +1,201 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="sr">
+<translation id="3595596368722241419">Батерија је пуна</translation>
+<translation id="5250713215130379958">Аутоматски сакриј покретач</translation>
+<translation id="7814236020522506259"><ph name="HOUR"/> и <ph name="MINUTE"/></translation>
+<translation id="7880025619322806991">Стање Портал</translation>
+<translation id="30155388420722288">Дугме за додатне опције</translation>
+<translation id="5571066253365925590">Bluetooth је омогућен</translation>
+<translation id="9074739597929991885">Bluetooth</translation>
+<translation id="2268130516524549846">Bluetooth је онемогућен</translation>
+<translation id="3775358506042162758">Можете да имате највише три налога за вишеструко пријављивање.</translation>
+<translation id="370649949373421643">Омогући Wi-Fi</translation>
+<translation id="3626281679859535460">Осветљеност</translation>
+<translation id="8054466585765276473">Израчунавање времена трајања батерије.</translation>
+<translation id="7982789257301363584">Мрежа</translation>
+<translation id="5565793151875479467">Прокси...</translation>
+<translation id="938582441709398163">Постављени елемент тастатуре</translation>
+<translation id="4387004326333427325">Сертификат за потврду аутентичности је одбијен даљински</translation>
+<translation id="6979158407327259162">Google диск</translation>
+<translation id="6943836128787782965">HTTP get није успео</translation>
+<translation id="2297568595583585744">Палета статуса</translation>
+<translation id="1661867754829461514">Недостаје PIN</translation>
+<translation id="4508225577814909926"><ph name="NAME"/>: Повезивање...</translation>
+<translation id="4237016987259239829">Грешка мрежне везе</translation>
+<translation id="2946640296642327832">Омогући Bluetooth</translation>
+<translation id="6459472438155181876">Проширивање екрана у <ph name="DISPLAY_NAME"/></translation>
+<translation id="8206859287963243715">Мобилни уређај</translation>
+<translation id="6596816719288285829">IP адреса</translation>
+<translation id="4508265954913339219">Активација није успела</translation>
+<translation id="3621712662352432595">Подешавања звука</translation>
+<translation id="1812696562331527143">Метод уноса је промењен у <ph name="INPUT_METHOD_ID"/>*(<ph name="BEGIN_LINK"/>трећа страна<ph name="END_LINK"/>).
+Притисните Shift + Alt да бисте пребацили.</translation>
+<translation id="2127372758936585790">Пуњач мале снаге</translation>
+<translation id="3846575436967432996">Нису доступне информације о мрежи</translation>
+<translation id="3026237328237090306">Подеси податке за мобилне уређаје</translation>
+<translation id="785750925697875037">Прикажи налог за мобилне уређаје</translation>
+<translation id="153454903766751181">Покретање модема за мобилну мрежу...</translation>
+<translation id="4628814525959230255">Контрола над екраном се дели са корисником <ph name="HELPER_NAME"/> преко Hangouts-а.</translation>
+<translation id="8343941333792395995">Екран <ph name="DISPLAY_NAME"/> је ротиран</translation>
+<translation id="7864539943188674973">Онемогући Bluetooth</translation>
+<translation id="939252827960237676">Чување снимка екрана није успело</translation>
+<translation id="3126069444801937830">Покрените поново да бисте ажурирали</translation>
+<translation id="2268813581635650749">Одјави све</translation>
+<translation id="735745346212279324">Веза са VPN-ом је прекинута</translation>
+<translation id="7320906967354320621">Неактивно</translation>
+<translation id="6303423059719347535">Батерија је <ph name="PERCENTAGE"/>% пуна</translation>
+<translation id="15373452373711364">Велики показивач миша</translation>
+<translation id="2778346081696727092">Потврда аутентичности помоћу наведеног корисничког имена или лозинке није успела</translation>
+<translation id="3294437725009624529">Гост</translation>
+<translation id="8190698733819146287">Прилагоди језике и унос...</translation>
+<translation id="2903907270192926896">УЛАЗ</translation>
+<translation id="8676770494376880701">Повезан је пуњач мале снаге</translation>
+<translation id="7170041865419449892">Изван опсега</translation>
+<translation id="4804818685124855865">Прекини везу</translation>
+<translation id="2544853746127077729">Мрежа је одбила сертификат за потврду аутентичности</translation>
+<translation id="5222676887888702881">Одјави ме</translation>
+<translation id="2688477613306174402">Конфигурација</translation>
+<translation id="1272079795634619415">Заустави</translation>
+<translation id="4957722034734105353">Сазнајте више...</translation>
+<translation id="2964193600955408481">Онемогући Wi-Fi</translation>
+<translation id="811680302244032017">Додај уређај...</translation>
+<translation id="4279490309300973883">Пресликавање</translation>
+<translation id="2509468283778169019">CAPS LOCK је укључен</translation>
+<translation id="3892641579809465218">Интерни екран</translation>
+<translation id="7823564328645135659">Језик је промењен са језика „<ph name="FROM_LOCALE"/>“ на „<ph name="TO_LOCALE"/>“ након синхронизације подешавања.</translation>
+<translation id="3368922792935385530">Повезан</translation>
+<translation id="8340999562596018839">Говорне повратне информације</translation>
+<translation id="8654520615680304441">Укључи Wi-Fi...</translation>
+<translation id="5825747213122829519">Метод уноса је промењен у <ph name="INPUT_METHOD_ID"/>.
+Притисните Shift + Alt да бисте пребацили.</translation>
+<translation id="2562916301614567480">Приватна мрежа</translation>
+<translation id="6549021752953852991">Није доступна ниједна мобилна мрежа</translation>
+<translation id="4379753398862151997">Драги мониторе, не иде нам. (Тај монитор није подржан)</translation>
+<translation id="6426039856985689743">Онемогући податке за мобилне уређаје</translation>
+<translation id="3087734570205094154">Дно</translation>
+<translation id="3742055079367172538">Снимак екрана је направљен</translation>
+<translation id="8878886163241303700">Проширени екран</translation>
+<translation id="5271016907025319479">VPN није конфигурисан.</translation>
+<translation id="372094107052732682">Притисните Ctrl+Shift+Q двапут да бисте изашли.</translation>
+<translation id="6803622936009808957">Није могуће пресликати екране зато што није пронађена ниједна подржана резолуција. Уместо тога, приказује се проширена радна површина.</translation>
+<translation id="1480041086352807611">Режим демонстрације</translation>
+<translation id="3626637461649818317">Преостало је <ph name="PERCENTAGE"/>%</translation>
+<translation id="9089416786594320554">Методи уноса</translation>
+<translation id="6247708409970142803"><ph name="PERCENTAGE"/>%</translation>
+<translation id="2614835198358683673">Chromebook се можда неће пунити док је укључен. Размислите о коришћењу оригиналног пуњача.</translation>
+<translation id="1895658205118569222">Затварање</translation>
+<translation id="4430019312045809116">Јачина звука</translation>
+<translation id="4442424173763614572">DNS претрага није успела</translation>
+<translation id="6356500677799115505">Батерија је пуна и још увек се пуни.</translation>
+<translation id="7874779702599364982">Претраживање мобилних мрежа...</translation>
+<translation id="583281660410589416">Непознато</translation>
+<translation id="1383876407941801731">Претрага</translation>
+<translation id="7468789844759750875">Посетите активациони портал <ph name="NAME"/> да бисте купили још података.</translation>
+<translation id="3901991538546252627">Повезивање са мрежом <ph name="NAME"/></translation>
+<translation id="2204305834655267233">Информације о мрежи</translation>
+<translation id="1621499497873603021">Време које је преостало док се батерија не испразни, <ph name="TIME_LEFT"/></translation>
+<translation id="5980301590375426705">Изађи из сесије госта</translation>
+<translation id="4471417012762451363">Батерија је <ph name="PERCENTAGE"/>% пуна и још увек се пуни</translation>
+<translation id="8308637677604853869">Претходни мени</translation>
+<translation id="4666297444214622512">Не можете да се пријавите на други налог.</translation>
+<translation id="1346748346194534595">Удесно</translation>
+<translation id="1773212559869067373">Сертификат за потврду аутентичности је одбијен локално</translation>
+<translation id="8528322925433439945">Мобилни ...</translation>
+<translation id="7049357003967926684">Повезивање</translation>
+<translation id="8428213095426709021">Подешавања</translation>
+<translation id="2372145515558759244">Синхронизовање апликација...</translation>
+<translation id="7256405249507348194">Непозната грешка: <ph name="DESC"/></translation>
+<translation id="7925247922861151263">Провера потврде идентитета, овлашћења и приступа није успела</translation>
+<translation id="8456362689280298700">Још <ph name="HOUR"/>:<ph name="MINUTE"/> док се не напуни</translation>
+<translation id="5787281376604286451">Говорне повратне информације су омогућене.
+Притисните Ctrl+Alt+Z да бисте их онемогућили.</translation>
+<translation id="4479639480957787382">Етернет</translation>
+<translation id="6312403991423642364">Непозната грешка на мрежи</translation>
+<translation id="1467432559032391204">Улево</translation>
+<translation id="5543001071567407895">SMS</translation>
+<translation id="2354174487190027830">Активирање мреже <ph name="NAME"/></translation>
+<translation id="8814190375133053267">Wi-Fi</translation>
+<translation id="1398853756734560583">Увећај</translation>
+<translation id="2692809339924654275"><ph name="BLUETOOTH"/>: Повезивање...</translation>
+<translation id="252373100621549798">Непознати приказ</translation>
+<translation id="1882897271359938046">Пресликавање у <ph name="DISPLAY_NAME"/></translation>
+<translation id="2727977024730340865">Уређај је укључен у пуњач мале снаге. Пуњење батерије можда неће бити поуздано.</translation>
+<translation id="3784455785234192852">Закључај</translation>
+<translation id="2805756323405976993">Apps</translation>
+<translation id="8871072142849158571">Величина екрана <ph name="DISPLAY_NAME"/> је промењена у <ph name="RESOLUTION"/></translation>
+<translation id="1512064327686280138">Активација није успела</translation>
+<translation id="5097002363526479830">Повезивање са мрежом „<ph name="NAME"/>“ није успело: <ph name="DETAILS"/></translation>
+<translation id="1850504506766569011">Wi-Fi је искључен.</translation>
+<translation id="8132793192354020517">Успостављена је веза са <ph name="NAME"/></translation>
+<translation id="7052914147756339792">Подеси позадину...</translation>
+<translation id="8678698760965522072">Стање На мрежи</translation>
+<translation id="2532589005999780174">Режим високог контраста</translation>
+<translation id="1119447706177454957">Интерна грешка</translation>
+<translation id="3019353588588144572">Време које је преостало док се батерија у потпуности не напуни, <ph name="TIME_REMAINING"/></translation>
+<translation id="3473479545200714844">Лупа екрана</translation>
+<translation id="7005812687360380971">Није успело</translation>
+<translation id="882279321799040148">Кликните за приказ</translation>
+<translation id="5045550434625856497">Нетачна лозинка</translation>
+<translation id="1602076796624386989">Омогући податке за мобилне уређаје</translation>
+<translation id="6981982820502123353">Приступачност</translation>
+<translation id="3157931365184549694">Поново отвори</translation>
+<translation id="4274292172790327596">Непозната грешка</translation>
+<translation id="4032485810211612751"><ph name="HOURS"/>:<ph name="MINUTES"/>:<ph name="SECONDS"/></translation>
+<translation id="225680501294068881">Скенирање уређаја...</translation>
+<translation id="5597451508971090205"><ph name="SHORT_WEEKDAY"/> <ph name="DATE"/></translation>
+<translation id="4448844063988177157">Тражење Wi-Fi мрежа...</translation>
+<translation id="8401662262483418323">Повезивање са „<ph name="NAME"/>“ није успело: <ph name="DETAILS"/>
+Порука сервера: <ph name="SERVER_MSG"/></translation>
+<translation id="2475982808118771221">Дошло је до грешке</translation>
+<translation id="7229570126336867161">Потребан је EVDO</translation>
+<translation id="2999742336789313416"><ph name="DISPLAY_NAME"/> је јавна сесија којом управља <ph name="DOMAIN"/></translation>
+<translation id="7029814467594812963">Изађи из сесије</translation>
+<translation id="8454013096329229812">Wi-Fi је укључен.</translation>
+<translation id="4872237917498892622">Alt + тастер за претрагу или Shift</translation>
+<translation id="2983818520079887040">Подешавања...</translation>
+<translation id="1717216362413677834">Режим базне станице</translation>
+<translation id="8927026611342028580">Захтева се повезивање</translation>
+<translation id="8300849813060516376">OTASP није успео</translation>
+<translation id="2792498699870441125">Alt + тастер за претрагу</translation>
+<translation id="8660803626959853127">Синхронизовање <ph name="COUNT"/> датотеке(а)</translation>
+<translation id="3709443003275901162">9+</translation>
+<translation id="639644700271529076">CAPS LOCK је искључен</translation>
+<translation id="6248847161401822652">Притисните Control Shift Q двапут да бисте изашли.</translation>
+<translation id="6267036997247669271"><ph name="NAME"/>: Активирање...</translation>
+<translation id="1391854757121130358">Можда сте искористили додељени пакет података за мобилне уређаје.</translation>
+<translation id="5413208160176941586">Корисник којим се локално управља</translation>
+<translation id="1059194134494239015"><ph name="DISPLAY_NAME"/>: <ph name="RESOLUTION"/></translation>
+<translation id="4864165860509564259">Положај покретача</translation>
+<translation id="7593891976182323525">Тастер за претрагу или Shift</translation>
+<translation id="7649070708921625228">Помоћ</translation>
+<translation id="3050422059534974565">CAPS LOCK је укључен.
+Притисните тастер за претрагу или Shift да бисте га отказали.</translation>
+<translation id="397105322502079400">Израчунавање...</translation>
+<translation id="158849752021629804">Потребна је матична мрежа</translation>
+<translation id="6857811139397017780">Активирај <ph name="NETWORKSERVICE"/></translation>
+<translation id="5864471791310927901">Проналажење DHCP-а није успело</translation>
+<translation id="5812035014844949013">ИЗЛАЗ</translation>
+<translation id="6692173217867674490">Неисправна приступна фраза</translation>
+<translation id="6165508094623778733">Сазнајте више</translation>
+<translation id="9046895021617826162">Повезивање није успело</translation>
+<translation id="973896785707726617">Ова сесија ће се завршити за <ph name="SESSION_TIME_REMAINING"/>. Бићете аутоматски одјављени.</translation>
+<translation id="8372369524088641025">Неисправна WEP шифра</translation>
+<translation id="6636709850131805001">Непознато стање</translation>
+<translation id="3573179567135747900">Врати на „<ph name="FROM_LOCALE"/>“ (потребно је поновно покретање)</translation>
+<translation id="8103386449138765447">SMS поруке: <ph name="MESSAGE_COUNT"/></translation>
+<translation id="5045002648206642691">Подешавања Google диска...</translation>
+<translation id="1510238584712386396">Покретач</translation>
+<translation id="7209101170223508707">CAPS LOCK је укључен.
+Притисните Alt + тастер за претрагу или Shift да бисте га отказали.</translation>
+<translation id="8940956008527784070">Батерија је скоро празна (<ph name="PERCENTAGE"/>%)</translation>
+<translation id="5102001756192215136">Преостало је <ph name="HOUR"/>:<ph name="MINUTE"/></translation>
+<translation id="520760366042891468">Контрола над екраном се дели преко Hangouts-а.</translation>
+<translation id="8000066093800657092">Нема мреже</translation>
+<translation id="4015692727874266537">Пријави ме на други налог...</translation>
+<translation id="5941711191222866238">Смањи</translation>
+<translation id="6911468394164995108">Придружи ме другој...</translation>
+<translation id="412065659894267608"><ph name="HOUR"/> с <ph name="MINUTE"/> м до краја пуњења</translation>
+<translation id="6359806961507272919">SMS са броја <ph name="PHONE_NUMBER"/></translation>
+<translation id="1244147615850840081">Мобилни оператер</translation>
+</translationbundle> \ No newline at end of file
diff --git a/chromium/ash/strings/ash_strings_sv.xtb b/chromium/ash/strings/ash_strings_sv.xtb
new file mode 100644
index 00000000000..7418d0e765a
--- /dev/null
+++ b/chromium/ash/strings/ash_strings_sv.xtb
@@ -0,0 +1,199 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="sv">
+<translation id="3595596368722241419">Batteriet är fulladdat</translation>
+<translation id="5250713215130379958">Dölj startfältet automatiskt</translation>
+<translation id="7814236020522506259"><ph name="HOUR"/> och <ph name="MINUTE"/></translation>
+<translation id="7880025619322806991">Portalläge</translation>
+<translation id="30155388420722288">Överflödsknapp</translation>
+<translation id="5571066253365925590">Bluetooth aktiverad</translation>
+<translation id="9074739597929991885">Bluetooth</translation>
+<translation id="2268130516524549846">Bluetooth inaktiverad</translation>
+<translation id="3775358506042162758">Du kan högst använda tre konton för multiinloggning.</translation>
+<translation id="370649949373421643">Aktivera Wi-Fi</translation>
+<translation id="3626281679859535460">Ljusstyrka</translation>
+<translation id="8054466585765276473">Batteritiden beräknas.</translation>
+<translation id="7982789257301363584">Nätverk</translation>
+<translation id="5565793151875479467">Proxy ...</translation>
+<translation id="938582441709398163">Tangentbordsöverlägg</translation>
+<translation id="4387004326333427325">Autentiseringscertifikatet godkändes inte av fjärrvärden</translation>
+<translation id="6979158407327259162">Google Drive</translation>
+<translation id="6943836128787782965">HTTP GET misslyckades</translation>
+<translation id="2297568595583585744">Statusfält</translation>
+<translation id="1661867754829461514">PIN saknas</translation>
+<translation id="4508225577814909926"><ph name="NAME"/>: ansluter ...</translation>
+<translation id="4237016987259239829">Fel vid nätverksanslutning</translation>
+<translation id="2946640296642327832">Aktivera Bluetooth</translation>
+<translation id="6459472438155181876">Utöka skärmen till <ph name="DISPLAY_NAME"/></translation>
+<translation id="8206859287963243715">Mobil</translation>
+<translation id="6596816719288285829">IP-adress</translation>
+<translation id="4508265954913339219">Aktiveringen misslyckades</translation>
+<translation id="3621712662352432595">Ljudinställningar</translation>
+<translation id="1812696562331527143">Inmatningsmetoden har ändrats till <ph name="INPUT_METHOD_ID"/>*(<ph name="BEGIN_LINK"/>tredje part<ph name="END_LINK"/>).
+ Tryck på Skift + Alt om du vill byta.</translation>
+<translation id="2127372758936585790">Laddning med låg effekt</translation>
+<translation id="3846575436967432996">Det finns ingen nätverksinformation</translation>
+<translation id="3026237328237090306">Konfigurera mobildata</translation>
+<translation id="785750925697875037">Visa mobilkonto</translation>
+<translation id="153454903766751181">Mobilt modem initieras ...</translation>
+<translation id="4628814525959230255">Delar kontrollen över din skärm med <ph name="HELPER_NAME"/> via Hangouts.</translation>
+<translation id="8343941333792395995"><ph name="DISPLAY_NAME"/> har roterats</translation>
+<translation id="7864539943188674973">Inaktivera Bluetooth</translation>
+<translation id="939252827960237676">Det gick inte att spara skärmdumpen</translation>
+<translation id="3126069444801937830">Starta om för att uppdatera</translation>
+<translation id="2268813581635650749">Logga ut alla</translation>
+<translation id="735745346212279324">VPN frånkopplat</translation>
+<translation id="7320906967354320621">Inaktiv</translation>
+<translation id="6303423059719347535">Batteriet är fullt till <ph name="PERCENTAGE"/> %</translation>
+<translation id="15373452373711364">Stor muspekare</translation>
+<translation id="2778346081696727092">Det gick inte att autentisera med användarnamnet eller lösenordet som angavs</translation>
+<translation id="3294437725009624529">Gäst</translation>
+<translation id="8190698733819146287">Anpassa språk och inmatning...</translation>
+<translation id="2903907270192926896">INDATA</translation>
+<translation id="8676770494376880701">Laddare med låg effekt ansluten</translation>
+<translation id="7170041865419449892">Utanför intervallet</translation>
+<translation id="4804818685124855865">Koppla från</translation>
+<translation id="2544853746127077729">Autentiseringscertifikatet godkändes inte av nätverket</translation>
+<translation id="5222676887888702881">Logga ut</translation>
+<translation id="2688477613306174402">Konfiguration</translation>
+<translation id="1272079795634619415">Stopp</translation>
+<translation id="4957722034734105353">Läs mer ...</translation>
+<translation id="2964193600955408481">Inaktivera Wi-Fi</translation>
+<translation id="811680302244032017">Lägg till enhet ...</translation>
+<translation id="4279490309300973883">Spegling</translation>
+<translation id="2509468283778169019">CAPS LOCK är på</translation>
+<translation id="3892641579809465218">Intern bildskärm</translation>
+<translation id="7823564328645135659">Chromes språk har ändrats från <ph name="FROM_LOCALE"/> till <ph name="TO_LOCALE"/> efter synkronisering av dina inställningar.</translation>
+<translation id="3368922792935385530">Ansluten</translation>
+<translation id="8340999562596018839">Talad feedback</translation>
+<translation id="8654520615680304441">Aktivera Wi-Fi ...</translation>
+<translation id="5825747213122829519">Inmatningsmetoden har ändrats till <ph name="INPUT_METHOD_ID"/>.
+ Tryck på Skift + Alt om du vill byta.</translation>
+<translation id="2562916301614567480">Privat nätverk</translation>
+<translation id="6549021752953852991">Det finns inget tillgängligt mobilt nätverk</translation>
+<translation id="4379753398862151997">Det fungerar inte med den här skärmen. (Skärmen stöds inte.)</translation>
+<translation id="6426039856985689743">Inaktivera mobildata</translation>
+<translation id="3087734570205094154">Nederst</translation>
+<translation id="3742055079367172538">Skärmbilden har tagits</translation>
+<translation id="8878886163241303700">Utökad skärm</translation>
+<translation id="5271016907025319479">VPN är inte konfigurerat.</translation>
+<translation id="372094107052732682">Avsluta genom att trycka på Ctrl + Skift + Q två gånger.</translation>
+<translation id="6803622936009808957">Det gick inte att spegla visningar eftersom inga upplösningar som stöds hittades. Utökat skrivbordsläge används i stället.</translation>
+<translation id="1480041086352807611">Demoläge</translation>
+<translation id="3626637461649818317"><ph name="PERCENTAGE"/> % återstår</translation>
+<translation id="9089416786594320554">Inmatningsmetoder</translation>
+<translation id="6247708409970142803"><ph name="PERCENTAGE"/> %</translation>
+<translation id="2614835198358683673">Din Chromebook kanske inte laddas medan den är på. Överväg att använda den officiella laddaren.</translation>
+<translation id="1895658205118569222">Stängning</translation>
+<translation id="4430019312045809116">Volym</translation>
+<translation id="4442424173763614572">DNS-sökning misslyckades</translation>
+<translation id="6356500677799115505">Batteriet är fullt och laddas.</translation>
+<translation id="7874779702599364982">Söker efter mobilnätverk ...</translation>
+<translation id="583281660410589416">Okänt</translation>
+<translation id="1383876407941801731">Sökning</translation>
+<translation id="7468789844759750875">Besök aktiveringsportalen för <ph name="NAME"/> om du vill köpa mer data.</translation>
+<translation id="3901991538546252627">Ansluter till <ph name="NAME"/></translation>
+<translation id="2204305834655267233">Nätverksinformation</translation>
+<translation id="1621499497873603021">Tid som återstår tills batteriet är tomt: <ph name="TIME_LEFT"/></translation>
+<translation id="5980301590375426705">Avsluta gästsession</translation>
+<translation id="4471417012762451363">Batteriet är fullt till <ph name="PERCENTAGE"/> % och laddas</translation>
+<translation id="8308637677604853869">Föregående meny</translation>
+<translation id="4666297444214622512">Det går inte att logga in på ett annat konto.</translation>
+<translation id="1346748346194534595">Höger</translation>
+<translation id="1773212559869067373">Autentiseringscertifikatet godkändes inte lokalt</translation>
+<translation id="8528322925433439945">Mobil ...</translation>
+<translation id="7049357003967926684">Association</translation>
+<translation id="8428213095426709021">Inställningar</translation>
+<translation id="2372145515558759244">Synkronisera appar ...</translation>
+<translation id="7256405249507348194">Okänt fel: <ph name="DESC"/></translation>
+<translation id="7925247922861151263">Kontroll med AAA misslyckades</translation>
+<translation id="8456362689280298700"><ph name="HOUR"/>:<ph name="MINUTE"/> till fulladdat batteri</translation>
+<translation id="5787281376604286451">Talad feedback är aktiverad.
+Inaktivera genom att trycka Ctrl+Alt+Z.</translation>
+<translation id="4479639480957787382">Ethernet</translation>
+<translation id="6312403991423642364">Okänt nätverksfel</translation>
+<translation id="1467432559032391204">Vänster</translation>
+<translation id="5543001071567407895">SMS</translation>
+<translation id="2354174487190027830">Aktiverar <ph name="NAME"/></translation>
+<translation id="8814190375133053267">Wi-Fi</translation>
+<translation id="1398853756734560583">Maximera</translation>
+<translation id="2692809339924654275"><ph name="BLUETOOTH"/>: ansluter ...</translation>
+<translation id="252373100621549798">Okänd visning</translation>
+<translation id="1882897271359938046">Spegling av <ph name="DISPLAY_NAME"/></translation>
+<translation id="2727977024730340865">Ansluten till en laddare med låg effekt. Batteriet kanske inte laddas ordentligt.</translation>
+<translation id="3784455785234192852">Lås</translation>
+<translation id="2805756323405976993">Appar</translation>
+<translation id="8871072142849158571">Storleken på <ph name="DISPLAY_NAME"/> har ändrats till <ph name="RESOLUTION"/></translation>
+<translation id="1512064327686280138">Aktiveringsfel</translation>
+<translation id="5097002363526479830">Det gick inte att ansluta till nätverket <ph name="NAME"/>: <ph name="DETAILS"/></translation>
+<translation id="1850504506766569011">Wi-Fi är inaktiverat.</translation>
+<translation id="8132793192354020517">Ansluten till <ph name="NAME"/></translation>
+<translation id="7052914147756339792">Ange bakgrund ...</translation>
+<translation id="8678698760965522072">Onlineläge</translation>
+<translation id="2532589005999780174">Högkontrastläge</translation>
+<translation id="1119447706177454957">Internt fel</translation>
+<translation id="3019353588588144572">Tid som återstår tills batteriet är fulladdat: <ph name="TIME_REMAINING"/></translation>
+<translation id="3473479545200714844">Skärmförstorare</translation>
+<translation id="7005812687360380971">Misslyckades</translation>
+<translation id="882279321799040148">Klicka för att visa</translation>
+<translation id="5045550434625856497">Felaktigt lösenord</translation>
+<translation id="1602076796624386989">Aktivera mobildata</translation>
+<translation id="6981982820502123353">Tillgänglighet</translation>
+<translation id="3157931365184549694">Återställ</translation>
+<translation id="4274292172790327596">Okänt fel</translation>
+<translation id="4032485810211612751"><ph name="HOURS"/>:<ph name="MINUTES"/>:<ph name="SECONDS"/></translation>
+<translation id="225680501294068881">Söker efter enheter ...</translation>
+<translation id="5597451508971090205"><ph name="SHORT_WEEKDAY"/>, <ph name="DATE"/></translation>
+<translation id="4448844063988177157">Söker efter Wi-Fi-nätverk...</translation>
+<translation id="8401662262483418323">Det gick inte att ansluta till <ph name="NAME"/>: <ph name="DETAILS"/>
+Meddelande från servern: <ph name="SERVER_MSG"/></translation>
+<translation id="2475982808118771221">Det uppstod ett fel</translation>
+<translation id="7229570126336867161">Behöver EVDO</translation>
+<translation id="2999742336789313416"><ph name="DISPLAY_NAME"/> är en offentlig session som hanteras av <ph name="DOMAIN"/></translation>
+<translation id="7029814467594812963">Avsluta session</translation>
+<translation id="8454013096329229812">Wi-Fi är aktiverat.</translation>
+<translation id="4872237917498892622">Alt + Sök eller Skift</translation>
+<translation id="2983818520079887040">Inställningar...</translation>
+<translation id="1717216362413677834">Dockningsläge</translation>
+<translation id="8927026611342028580">Anslutning begärd</translation>
+<translation id="8300849813060516376">Det gick inte att etablera tjänsten over-the-air.</translation>
+<translation id="2792498699870441125">Alt + Sök</translation>
+<translation id="8660803626959853127">Synkroniserar <ph name="COUNT"/> filer</translation>
+<translation id="3709443003275901162">9+</translation>
+<translation id="639644700271529076">CAPS LOCK är av</translation>
+<translation id="6248847161401822652">Avsluta genom att trycka på Ctrl + Skift + Q två gånger.</translation>
+<translation id="6267036997247669271"><ph name="NAME"/>: Aktiverar ...</translation>
+<translation id="1391854757121130358">Du kanske har slut på mobildata.</translation>
+<translation id="5413208160176941586">Lokalt hanterad användare</translation>
+<translation id="1059194134494239015"><ph name="DISPLAY_NAME"/>: <ph name="RESOLUTION"/></translation>
+<translation id="4864165860509564259">Startfältets position</translation>
+<translation id="7593891976182323525">Sök eller Skift</translation>
+<translation id="7649070708921625228">Hjälp</translation>
+<translation id="3050422059534974565">CAPS LOCK är på. Avbryt genom att trycka på Sök eller Shift.</translation>
+<translation id="397105322502079400">Beräknar ...</translation>
+<translation id="158849752021629804">Behöver hemnätverk</translation>
+<translation id="6857811139397017780">Aktivera <ph name="NETWORKSERVICE"/></translation>
+<translation id="5864471791310927901">DHCP-förfrågan misslyckades</translation>
+<translation id="5812035014844949013">UTDATA</translation>
+<translation id="6692173217867674490">Ogiltig lösenfras</translation>
+<translation id="6165508094623778733">Läs mer</translation>
+<translation id="9046895021617826162">Kunde inte ansluta</translation>
+<translation id="973896785707726617">Sessionen avslutas om <ph name="SESSION_TIME_REMAINING"/>. Du kommer att loggas ut automatiskt.</translation>
+<translation id="8372369524088641025">Felaktig WEP-nyckel</translation>
+<translation id="6636709850131805001">Okänt tillstånd</translation>
+<translation id="3573179567135747900">Byt tillbaka till &quot;<ph name="FROM_LOCALE"/>&quot; (kräver omstart)</translation>
+<translation id="8103386449138765447">SMS-meddelanden: <ph name="MESSAGE_COUNT"/></translation>
+<translation id="5045002648206642691">Inställningar för Google Drive ...</translation>
+<translation id="1510238584712386396">Startprogram</translation>
+<translation id="7209101170223508707">CAPS LOCK är på. Avbryt genom att trycka på Alt + Sök eller Skift.</translation>
+<translation id="8940956008527784070">Låg batterinivå (<ph name="PERCENTAGE"/> %)</translation>
+<translation id="5102001756192215136"><ph name="HOUR"/>:<ph name="MINUTE"/> kvar</translation>
+<translation id="520760366042891468">Delar kontrollen över din skärm via Hangouts.</translation>
+<translation id="8000066093800657092">Inget nätverk</translation>
+<translation id="4015692727874266537">Logga in på ett annat konto …</translation>
+<translation id="5941711191222866238">Minimera</translation>
+<translation id="6911468394164995108">Anslut till andra ...</translation>
+<translation id="412065659894267608"><ph name="HOUR"/> h <ph name="MINUTE"/> m till fulladdat</translation>
+<translation id="6359806961507272919">SMS från <ph name="PHONE_NUMBER"/></translation>
+<translation id="1244147615850840081">Operatör</translation>
+</translationbundle> \ No newline at end of file
diff --git a/chromium/ash/strings/ash_strings_sw.xtb b/chromium/ash/strings/ash_strings_sw.xtb
new file mode 100644
index 00000000000..8ee3b5f47f3
--- /dev/null
+++ b/chromium/ash/strings/ash_strings_sw.xtb
@@ -0,0 +1,201 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="sw">
+<translation id="3595596368722241419">Betri imejaa</translation>
+<translation id="5250713215130379958">Kizinduzi cha Kuficha otomatiki</translation>
+<translation id="7814236020522506259"><ph name="HOUR"/> na <ph name="MINUTE"/></translation>
+<translation id="7880025619322806991">Hali ya lango</translation>
+<translation id="30155388420722288">Kitufe Jalizi</translation>
+<translation id="5571066253365925590">Bluetooth imewezeshwa</translation>
+<translation id="9074739597929991885">Bluetooth</translation>
+<translation id="2268130516524549846">Bluetooth imelemazwa</translation>
+<translation id="3775358506042162758">Unaweza tu kuwa na hadi akaunti tatu zilizoingiwa kwa wakati mmoja</translation>
+<translation id="370649949373421643">Wezesha Wi-Fi</translation>
+<translation id="3626281679859535460">Ung'aavu</translation>
+<translation id="8054466585765276473">Inakokotoa muda wa betri.</translation>
+<translation id="7982789257301363584">Mtandao</translation>
+<translation id="5565793151875479467">Proksi...</translation>
+<translation id="938582441709398163">Mtandazo wa Kibodi</translation>
+<translation id="4387004326333427325">Cheti cha uthibitishaji kimekataliwa kwa mbali</translation>
+<translation id="6979158407327259162">Hifadhi ya Google</translation>
+<translation id="6943836128787782965">HTTP imeshindikana</translation>
+<translation id="2297568595583585744">Treya ya hali</translation>
+<translation id="1661867754829461514">PIN inakosekana</translation>
+<translation id="4508225577814909926"><ph name="NAME"/>: Inaunganisha...</translation>
+<translation id="4237016987259239829">Hitilafu ya Muunganisho wa Mtandao</translation>
+<translation id="2946640296642327832">Wezesha Bluetooth</translation>
+<translation id="6459472438155181876">Inapanua skrini kwenye <ph name="DISPLAY_NAME"/></translation>
+<translation id="8206859287963243715">Simu ya Mkononi</translation>
+<translation id="6596816719288285829">Anwani ya IP</translation>
+<translation id="4508265954913339219">Uamilisho umeshindikana</translation>
+<translation id="3621712662352432595">Mipangilio ya Sauti</translation>
+<translation id="1812696562331527143">Mbinu yako ingizo imebadilika hadi <ph name="INPUT_METHOD_ID"/>*(<ph name="BEGIN_LINK"/>mhusika mwingine<ph name="END_LINK"/>).
+Bonyeza Shift + Alt ili kubadilisha.</translation>
+<translation id="2127372758936585790">Chaja ya nguvu ya chini</translation>
+<translation id="3846575436967432996">Hakuna maelezo ya mtandao yanayopatikana</translation>
+<translation id="3026237328237090306">Sanidi data ya simu</translation>
+<translation id="785750925697875037">Ona akaunti ya simu ya mkononi</translation>
+<translation id="153454903766751181">Inaanzisha modemu ya simu za mkononi...</translation>
+<translation id="4628814525959230255">Inashiriki udhibiti wa skrini yako na <ph name="HELPER_NAME"/> kupitia Hangouts.</translation>
+<translation id="8343941333792395995"><ph name="DISPLAY_NAME"/> limezungushwa</translation>
+<translation id="7864539943188674973">Lemaza Bluetooth</translation>
+<translation id="939252827960237676">Imeshindwa kuhifadhi picha ya skrini</translation>
+<translation id="3126069444801937830">Anzisha upya ili kusasisha</translation>
+<translation id="2268813581635650749">Ondoa wote</translation>
+<translation id="735745346212279324">VPN imekatwa muunganisho</translation>
+<translation id="7320906967354320621">Tulivu</translation>
+<translation id="6303423059719347535">Betri imejaa <ph name="PERCENTAGE"/>%</translation>
+<translation id="15373452373711364">Kishale kikubwa cha kipanya</translation>
+<translation id="2778346081696727092">Imeshindwa kuthibitisha kwa jina la mtumiaji au nenosiri lililotolewa</translation>
+<translation id="3294437725009624529">Mgeni</translation>
+<translation id="8190698733819146287">Geuza lugha na uingizaji kukufaa...</translation>
+<translation id="2903907270192926896">INGIZO</translation>
+<translation id="8676770494376880701">Chaja ya nguvu ya chini imeunganishwa</translation>
+<translation id="7170041865419449892">Nje ya eneo</translation>
+<translation id="4804818685124855865">Tenganisha</translation>
+<translation id="2544853746127077729">Cheti cha uthibitishaji kimekataliwa na mtandao</translation>
+<translation id="5222676887888702881">Ondoka</translation>
+<translation id="2688477613306174402">Usanidi</translation>
+<translation id="1272079795634619415">Simamisha</translation>
+<translation id="4957722034734105353">Pata maelezo zaidi...</translation>
+<translation id="2964193600955408481">Lemaza Wi-Fi</translation>
+<translation id="811680302244032017">Ongeza kifaa...</translation>
+<translation id="4279490309300973883">Kuakisi</translation>
+<translation id="2509468283778169019">Caps Lock imewashwa.</translation>
+<translation id="3892641579809465218">Onyesho la Ndani</translation>
+<translation id="7823564328645135659">Lugha imebadilika kutoka &quot;<ph name="FROM_LOCALE"/> &quot;na kuwa&quot; <ph name="TO_LOCALE"/>&quot; baada ya kulinganisha mipangilio yako.</translation>
+<translation id="3368922792935385530">Umeunganishwa</translation>
+<translation id="8340999562596018839">Maoni ya yaliyotamkwa</translation>
+<translation id="8654520615680304441">Washa Wi-Fi...</translation>
+<translation id="5825747213122829519">Mbinu ingizo yako imebadilika hadi <ph name="INPUT_METHOD_ID"/>.
+Bonyeza Shift + Alt ili kubadili.</translation>
+<translation id="2562916301614567480">Mtandao Binafsi</translation>
+<translation id="6549021752953852991">Hakuna mtandao wa simu za mkononi unaopatikana</translation>
+<translation id="4379753398862151997">Mpendwa Kionyeshi, hali sio nzuri kati yetu. (Kionyeshi hiki hakiwezi kutumiwa)</translation>
+<translation id="6426039856985689743">Lemaza data ya simu</translation>
+<translation id="3087734570205094154">Chini</translation>
+<translation id="3742055079367172538">Picha ya skrini imepigwa</translation>
+<translation id="8878886163241303700">Kuongeza skrini</translation>
+<translation id="5271016907025319479">VPN haijasanidiwa.</translation>
+<translation id="372094107052732682">Bofya Ctrl + Shift + Q mara mbili ili kuacha.</translation>
+<translation id="6803622936009808957">Haikuweza kuakisi maonyesho kwa kuwa hakuna misongo inayoweza kutumiwa iliyopatikana. Badala yake imeingia eneo-kazi lililopanuliwa.</translation>
+<translation id="1480041086352807611">Modi ya kuonyesha</translation>
+<translation id="3626637461649818317"><ph name="PERCENTAGE"/>% inayobaki</translation>
+<translation id="9089416786594320554">Mbinu Ingizo</translation>
+<translation id="6247708409970142803"><ph name="PERCENTAGE"/>%</translation>
+<translation id="2614835198358683673">Chromebook yako huenda isichaji ikiwa imewashwa. Fikiria kutumia chaja rasmi.</translation>
+<translation id="1895658205118569222">Funga</translation>
+<translation id="4430019312045809116">Kiwango</translation>
+<translation id="4442424173763614572">Mwonekano wa DNS umeshindikana</translation>
+<translation id="6356500677799115505">Betri imejaa na inachajiwa.</translation>
+<translation id="7874779702599364982">Inatafuta mitandao ya simu za mkononi...</translation>
+<translation id="583281660410589416">Siojulikana</translation>
+<translation id="1383876407941801731">Tafuta</translation>
+<translation id="7468789844759750875">Tembelea tovuti kuu ya kuwezesha ya <ph name="NAME"/> ili ununue data zaidi.</translation>
+<translation id="3901991538546252627">Inaunganisha kwenye <ph name="NAME"/></translation>
+<translation id="2204305834655267233">Maelezo ya Mtandao</translation>
+<translation id="1621499497873603021">Muda unaosalia mpaka betri inapoisha, <ph name="TIME_LEFT"/></translation>
+<translation id="5980301590375426705">Maliza Ugeni</translation>
+<translation id="4471417012762451363">Betri imejaa <ph name="PERCENTAGE"/>% na inachaji</translation>
+<translation id="8308637677604853869">Menyu ya awali</translation>
+<translation id="4666297444214622512">Huwezi kuingia katika akaunti nyingine.</translation>
+<translation id="1346748346194534595">Kulia</translation>
+<translation id="1773212559869067373">Cheti cha uthibitishaji kimekataliwa kindani</translation>
+<translation id="8528322925433439945">Simu ya mkononi ...</translation>
+<translation id="7049357003967926684">Muungano</translation>
+<translation id="8428213095426709021">Mipangilio</translation>
+<translation id="2372145515558759244">Inalandanisha programu...</translation>
+<translation id="7256405249507348194">Hitilafu isiyotambulika: <ph name="DESC"/></translation>
+<translation id="7925247922861151263">Ukaguzi wa AAA umeshindikana</translation>
+<translation id="8456362689280298700"><ph name="HOUR"/>:<ph name="MINUTE"/> mpaka ijae</translation>
+<translation id="5787281376604286451">Maoni yaliyotamkwa yamewashwa.
+Bonyeza Ctrl+Alt+Z ili ufunge.</translation>
+<translation id="4479639480957787382">Ethernet</translation>
+<translation id="6312403991423642364">Hitilafu isiyojulikana ya mtandao</translation>
+<translation id="1467432559032391204">Kushoto</translation>
+<translation id="5543001071567407895">SMS</translation>
+<translation id="2354174487190027830">Inaanza kutumia <ph name="NAME"/></translation>
+<translation id="8814190375133053267">Wi-Fi</translation>
+<translation id="1398853756734560583">Tanua</translation>
+<translation id="2692809339924654275"><ph name="BLUETOOTH"/>: Inaunganisha...</translation>
+<translation id="252373100621549798">Onyesho Lisilojulikana</translation>
+<translation id="1882897271359938046">Inaakisi kwenye <ph name="DISPLAY_NAME"/></translation>
+<translation id="2727977024730340865">Imechomekwa katika chaja ya kawi ya chini. Huenda kuchaji kwa betri hakutakuwa kuzuri.</translation>
+<translation id="3784455785234192852">Funga</translation>
+<translation id="2805756323405976993">Programu</translation>
+<translation id="8871072142849158571"><ph name="DISPLAY_NAME"/> imebadilishwa ukubwa kuwa <ph name="RESOLUTION"/></translation>
+<translation id="1512064327686280138">Kushindwa kwa uamilishaji</translation>
+<translation id="5097002363526479830">Imeshindwa kuunganisha kwenye mtandao '<ph name="NAME"/>': <ph name="DETAILS"/></translation>
+<translation id="1850504506766569011">Wi-Fi imezimwa.</translation>
+<translation id="8132793192354020517">Imeunganishwa kwenye <ph name="NAME"/></translation>
+<translation id="7052914147756339792">Weka Mandhari...</translation>
+<translation id="8678698760965522072">Hali ya mtandaoni</translation>
+<translation id="2532589005999780174">Hali ya juu ya utofautishaji</translation>
+<translation id="1119447706177454957">Hitilafu ya ndani</translation>
+<translation id="3019353588588144572">Muda unaosalia hadi betri itakapochajiwa kikamilifu, <ph name="TIME_REMAINING"/></translation>
+<translation id="3473479545200714844">Kikuza skrini</translation>
+<translation id="7005812687360380971">Imeshindwa</translation>
+<translation id="882279321799040148">Bofya ili kutazama</translation>
+<translation id="5045550434625856497">Nenosiri lisilo sahihi</translation>
+<translation id="1602076796624386989">Wezesha data ya simu</translation>
+<translation id="6981982820502123353">Ufikiaji</translation>
+<translation id="3157931365184549694">Rejesha</translation>
+<translation id="4274292172790327596">Hitilafu Isiyotambulika</translation>
+<translation id="4032485810211612751"><ph name="HOURS"/>:<ph name="MINUTES"/>:<ph name="SECONDS"/></translation>
+<translation id="225680501294068881">Inatambazaa vifaa...</translation>
+<translation id="5597451508971090205"><ph name="SHORT_WEEKDAY"/>, <ph name="DATE"/></translation>
+<translation id="4448844063988177157">Inatafuta mitandao ya Wi-Fi…</translation>
+<translation id="8401662262483418323">Ilishindwa kuunganisha kwenye ' <ph name="NAME"/> ': <ph name="DETAILS"/>
+Ujumbe wa seza: <ph name="SERVER_MSG"/></translation>
+<translation id="2475982808118771221">Hitilafu imetokea</translation>
+<translation id="7229570126336867161">Inahitaji EVDO</translation>
+<translation id="2999742336789313416"><ph name="DISPLAY_NAME"/> ni kipindi cha kila mtu kinachodhibitiwa na <ph name="DOMAIN"/></translation>
+<translation id="7029814467594812963">Toka kwenye kipindi</translation>
+<translation id="8454013096329229812">Wi-Fi imewashwa.</translation>
+<translation id="4872237917498892622">Alt + Utafutaji au Hama</translation>
+<translation id="2983818520079887040">Mipangilio...</translation>
+<translation id="1717216362413677834">Hali ya kufungwa</translation>
+<translation id="8927026611342028580">Muunganisho Umeombwa</translation>
+<translation id="8300849813060516376">OTASP imeshindikana</translation>
+<translation id="2792498699870441125">Alt + Utafutaji</translation>
+<translation id="8660803626959853127">Inalinganisha faili <ph name="COUNT"/></translation>
+<translation id="3709443003275901162">9+</translation>
+<translation id="639644700271529076">CAPS LOCK imezimwa</translation>
+<translation id="6248847161401822652">Bofya &quot;Control&quot; na &quot;Shift&quot; na Q kwa pamoja mara mbili ili kuacha.</translation>
+<translation id="6267036997247669271"><ph name="NAME"/>: Inaanza kutumia...</translation>
+<translation id="1391854757121130358">Inawezekana umemaliza mgawo wako wa data ya simu ya mkononi.</translation>
+<translation id="5413208160176941586">Mtumiaji anayedhibitiwa karibu</translation>
+<translation id="1059194134494239015"><ph name="DISPLAY_NAME"/>: <ph name="RESOLUTION"/></translation>
+<translation id="4864165860509564259">Sehemu ya kizinduzi</translation>
+<translation id="7593891976182323525">Utafutaji au Hama</translation>
+<translation id="7649070708921625228">Usaidizi</translation>
+<translation id="3050422059534974565">CAPS LOCK imeamilishwa.
+Bonyeza Alt + Utafutaji au Hama ili kughairi.</translation>
+<translation id="397105322502079400">Inakokotoa...</translation>
+<translation id="158849752021629804">Inahitaji mtandao wa nyumbani</translation>
+<translation id="6857811139397017780">Amilisha <ph name="NETWORKSERVICE"/></translation>
+<translation id="5864471791310927901">Mwonekano wa DHCP umeshindikana</translation>
+<translation id="5812035014844949013">TOWE</translation>
+<translation id="6692173217867674490">Kaulisiri mbovu</translation>
+<translation id="6165508094623778733">Pata maelezo zaidi</translation>
+<translation id="9046895021617826162">Muunganisho umeshindikana</translation>
+<translation id="973896785707726617">Kipindi hiki kitaisha katika <ph name="SESSION_TIME_REMAINING"/>. Utaondolewa kiotomatiki.</translation>
+<translation id="8372369524088641025">Kitufe kibovu cha WEP</translation>
+<translation id="6636709850131805001">Hali isiyotambulika</translation>
+<translation id="3573179567135747900">Badilisha hadi &quot;<ph name="FROM_LOCALE"/>&quot; (inahitaji uzime na uwashe)</translation>
+<translation id="8103386449138765447">Ujumbe wa SMS: <ph name="MESSAGE_COUNT"/></translation>
+<translation id="5045002648206642691">Mipangilio ya Hifadhi ya Google...</translation>
+<translation id="1510238584712386396">Kizindua</translation>
+<translation id="7209101170223508707">CAPS LOCK imeamilishwa.
+Bonyeza Alt + Utafutaji au Hama ili kughairi.</translation>
+<translation id="8940956008527784070">Betri inaisha (<ph name="PERCENTAGE"/>%)</translation>
+<translation id="5102001756192215136">Imesalia <ph name="HOUR"/>:<ph name="MINUTE"/></translation>
+<translation id="520760366042891468">Kushiriki udhibiti wa skrini yako kupitia Hangouts.</translation>
+<translation id="8000066093800657092">Hakuna mtandao</translation>
+<translation id="4015692727874266537">Ingia katika akaunti nyingine...</translation>
+<translation id="5941711191222866238">Punguza</translation>
+<translation id="6911468394164995108">Jiunge na mwingine...</translation>
+<translation id="412065659894267608">Saa<ph name="HOUR"/> dakika<ph name="MINUTE"/> ili ijae</translation>
+<translation id="6359806961507272919">SMS kutoka <ph name="PHONE_NUMBER"/></translation>
+<translation id="1244147615850840081">Mtoa huduma</translation>
+</translationbundle> \ No newline at end of file
diff --git a/chromium/ash/strings/ash_strings_ta.xtb b/chromium/ash/strings/ash_strings_ta.xtb
new file mode 100644
index 00000000000..dce0b3ef07c
--- /dev/null
+++ b/chromium/ash/strings/ash_strings_ta.xtb
@@ -0,0 +1,201 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="ta">
+<translation id="3595596368722241419">பேட்டரி நிரம்பியது</translation>
+<translation id="5250713215130379958">தொடக்கத்தை தானாக மறை</translation>
+<translation id="7814236020522506259"><ph name="HOUR"/> மற்றும் <ph name="MINUTE"/></translation>
+<translation id="7880025619322806991">போர்ட்டல் நிலை</translation>
+<translation id="30155388420722288">மிகைப்படுத்தி பொத்தான்</translation>
+<translation id="5571066253365925590">Bluetooth இயக்கப்பட்டது</translation>
+<translation id="9074739597929991885">Bluetooth</translation>
+<translation id="2268130516524549846">Bluetooth முடக்கப்பட்டது</translation>
+<translation id="3775358506042162758">பல உள்நுழைவில் மூன்று கணக்குகள் வரை மட்டுமே வைத்திருக்கலாம்.</translation>
+<translation id="370649949373421643">Wi-fi ஐ இயக்கு</translation>
+<translation id="3626281679859535460">ஒளிர்வு</translation>
+<translation id="8054466585765276473">பேட்டரி நேரத்தைக் கணக்கிடுகிறது.</translation>
+<translation id="7982789257301363584">நெட்வொர்க்</translation>
+<translation id="5565793151875479467">ப்ராக்ஸி...</translation>
+<translation id="938582441709398163">விசைப்பலகை மேல்தோற்றம்</translation>
+<translation id="4387004326333427325">அங்கீகரிப்புச் சான்றிதழ் தொலைநிலையில் நிராகரிக்கப்பட்டது</translation>
+<translation id="6979158407327259162">Google இயக்ககம்</translation>
+<translation id="6943836128787782965">HTTP தோல்வியடைந்தது</translation>
+<translation id="2297568595583585744">நிலைத் தட்டு</translation>
+<translation id="1661867754829461514">PIN இல்லை</translation>
+<translation id="4508225577814909926"><ph name="NAME"/>: இணைக்கிறது...</translation>
+<translation id="4237016987259239829">பிணைய இணைப்புப் பிழை</translation>
+<translation id="2946640296642327832">Bluetooth ஐ இயக்கு</translation>
+<translation id="6459472438155181876"><ph name="DISPLAY_NAME"/> க்கு திரை விரிவாக்கப்படுகிறது</translation>
+<translation id="8206859287963243715">செல்லுலர்</translation>
+<translation id="6596816719288285829">IP முகவரி</translation>
+<translation id="4508265954913339219">செயலாக்கம் தோல்வியுற்றது</translation>
+<translation id="3621712662352432595">ஆடியோ அமைப்புகள்</translation>
+<translation id="1812696562331527143">உங்கள் உள்ளீட்டு முறையானது <ph name="INPUT_METHOD_ID"/>*(<ph name="BEGIN_LINK"/>மூன்றாம் தரப்பு<ph name="END_LINK"/>) க்கு மாற்றப்பட்டது.
+மாற்ற Shift + Alt ஐ அழுத்தவும்.</translation>
+<translation id="2127372758936585790">குறைந்த சக்திகொண்ட சார்ஜர்</translation>
+<translation id="3846575436967432996">நெட்வொர்க் தகவல் எதுவும் இல்லை</translation>
+<translation id="3026237328237090306">மொபைல் தரவை அமை</translation>
+<translation id="785750925697875037">மொபைல் கணக்கைப் பார்க்கவும்</translation>
+<translation id="153454903766751181">செல்லுலார் பயன்முறையைத் துவக்குகிறது...</translation>
+<translation id="4628814525959230255">Hangouts வழியாக <ph name="HELPER_NAME"/> உடன் உங்கள் திரையின் கட்டுப்பாட்டைப் பகிர்கிறது.</translation>
+<translation id="8343941333792395995"><ph name="DISPLAY_NAME"/> சுழற்றப்பட்டது</translation>
+<translation id="7864539943188674973">Bluetooth ஐ முடக்கு</translation>
+<translation id="939252827960237676">ஸ்கிரீன் ஷாட்டை சேமிப்பதில் தோல்வி</translation>
+<translation id="3126069444801937830">புதுப்பிக்க மீண்டும் தொடங்குக</translation>
+<translation id="2268813581635650749">அனைவரையும் வெளியேற்று</translation>
+<translation id="735745346212279324">VPN துண்டிக்கப்பட்டது</translation>
+<translation id="7320906967354320621">செயலின்றி</translation>
+<translation id="6303423059719347535"><ph name="PERCENTAGE"/>% பேட்டரி நிரம்பியது</translation>
+<translation id="15373452373711364">பெரிய மவுஸ் இடஞ்சுட்டி</translation>
+<translation id="2778346081696727092">வழங்கப்பட்ட பயனர் பெயர் அல்லது கடவுச்சொல்லை அங்கீகரிப்பதில் தோல்வி</translation>
+<translation id="3294437725009624529">விருந்தினர்</translation>
+<translation id="8190698733819146287">மொழிகள் மற்றும் உள்ளீடைத் தனிப்பயனாக்கு...</translation>
+<translation id="2903907270192926896">உள்ளீடு</translation>
+<translation id="8676770494376880701">குறைந்த சக்தியிலான சார்ஜர் இணைக்கப்பட்டுள்ளது</translation>
+<translation id="7170041865419449892">வரம்புக்கு வெளியே</translation>
+<translation id="4804818685124855865">தொடர்பைத் துண்டி</translation>
+<translation id="2544853746127077729">அங்கீகரிப்புச் சான்றிதழ் பிணையத்தால் நிராகரிக்கப்பட்டது</translation>
+<translation id="5222676887888702881">வெளியேறு</translation>
+<translation id="2688477613306174402">உள்ளமைவு</translation>
+<translation id="1272079795634619415">நிறுத்து</translation>
+<translation id="4957722034734105353">மேலும் அறிக...</translation>
+<translation id="2964193600955408481">Wi-Fi ஐ முடக்கு</translation>
+<translation id="811680302244032017">சாதனத்தைச் சேர்க்கவும்...</translation>
+<translation id="4279490309300973883">பிரதிபலிக்கிறது</translation>
+<translation id="2509468283778169019">CAPS LOCK இயக்கத்தில்</translation>
+<translation id="3892641579809465218">இணையக் காட்சி</translation>
+<translation id="7823564328645135659">உங்கள் அமைப்புகளை ஒத்திசைத்த பிறகு, மொழியானது &quot;<ph name="FROM_LOCALE"/>&quot; இலிருந்து &quot;<ph name="TO_LOCALE"/>&quot; க்கு மாற்றப்பட்டுள்ளது.</translation>
+<translation id="3368922792935385530">இணைக்கப்பட்டது</translation>
+<translation id="8340999562596018839">பேச்சுவடிவ கருத்து</translation>
+<translation id="8654520615680304441">Wi-Fi ஐ இயக்கு...</translation>
+<translation id="5825747213122829519">உங்கள் உள்ளீட்டு முறையானது <ph name="INPUT_METHOD_ID"/> க்கு மாற்றப்பட்டது.
+மாற்ற Shift + Alt ஐ அழுத்தவும்.</translation>
+<translation id="2562916301614567480">தனிப்பட்ட நெட்வொர்க்</translation>
+<translation id="6549021752953852991">செல்லுலார் நெட்வொர்க் இல்லை</translation>
+<translation id="4379753398862151997">அந்த மானிட்டர் ஆதரிக்கப்படவில்லை.</translation>
+<translation id="6426039856985689743">மொபைல் தரவை முடக்கு</translation>
+<translation id="3087734570205094154">கீழே</translation>
+<translation id="3742055079367172538">ஸ்கிரீன் ஷாட் எடுக்கப்பட்டது</translation>
+<translation id="8878886163241303700">திரையை விரிவாக்குகிறது</translation>
+<translation id="5271016907025319479">VPN உள்ளமைக்கப்படவில்லை.</translation>
+<translation id="372094107052732682">வெளியேற Ctrl+Shift+Q ஐ இருமுறை அழுத்தவும்.</translation>
+<translation id="6803622936009808957">ஆதரிக்கும் தெளிவுகள் கிடைக்காததால் காட்சிகளைப் பிரதிபலிக்க முடியவில்லை. பதிலாக நீட்டிக்கப்பட்ட டெஸ்க்டாப்பிற்குச் சென்றது.</translation>
+<translation id="1480041086352807611">டெமோ பயன்முறை</translation>
+<translation id="3626637461649818317"><ph name="PERCENTAGE"/>% மீதமுள்ளது</translation>
+<translation id="9089416786594320554">உள்ளீட்டு முறைகள்</translation>
+<translation id="6247708409970142803"><ph name="PERCENTAGE"/>%</translation>
+<translation id="2614835198358683673">உங்கள் Chromebook இயக்கத்தில் இருக்கும்போது சார்ஜ் செய்ய முடியாது. அதிகாரப்பூர்வ சார்ஜரைப் பயன்படுத்தவும்.</translation>
+<translation id="1895658205118569222">நிறுத்தம்</translation>
+<translation id="4430019312045809116">அளவு</translation>
+<translation id="4442424173763614572">DNS தேடுதல் தோல்வி</translation>
+<translation id="6356500677799115505">பேட்டரி நிரம்பியது மேலும் சார்ஜ் ஆகிறது.</translation>
+<translation id="7874779702599364982">செல்லுலார் நெட்வொர்க்குகளைத் தேடுகிறது...</translation>
+<translation id="583281660410589416">அறியப்படாத</translation>
+<translation id="1383876407941801731">தேடு</translation>
+<translation id="7468789844759750875">கூடுதல் தரவை வாங்குவதற்கு <ph name="NAME"/> செயல்படுத்தல் போர்ட்டலைப் பார்வையிடவும்.</translation>
+<translation id="3901991538546252627"><ph name="NAME"/> க்கு இணைக்கிறது</translation>
+<translation id="2204305834655267233">பிணைய தகவல்</translation>
+<translation id="1621499497873603021">இன்னும் <ph name="TIME_LEFT"/> இல் பேட்டரி காலியாகிவிடும்</translation>
+<translation id="5980301590375426705">விருந்தினரிலிருந்து வெளியேறவும்</translation>
+<translation id="4471417012762451363"><ph name="PERCENTAGE"/>% பேட்டரி நிரம்பியது மேலும் சார்ஜ் ஆகிறது</translation>
+<translation id="8308637677604853869">முந்தைய மெனு</translation>
+<translation id="4666297444214622512">இன்னொரு கணக்கில் உள்நுழைய முடியாது.</translation>
+<translation id="1346748346194534595">வலது</translation>
+<translation id="1773212559869067373">அங்கீகரிப்புச் சான்றிதழ் உள்ளிடையாக நிராகரிக்கப்பட்டது</translation>
+<translation id="8528322925433439945">மொபைல் ...</translation>
+<translation id="7049357003967926684">சங்கம்</translation>
+<translation id="8428213095426709021">அமைப்புகள்</translation>
+<translation id="2372145515558759244">பயன்பாடுகளை ஒத்திசைக்கிறது...</translation>
+<translation id="7256405249507348194">அறியப்படாத பிழை: <ph name="DESC"/></translation>
+<translation id="7925247922861151263">AAA சோதனை தோல்வியுற்றது</translation>
+<translation id="8456362689280298700">நிரம்ப <ph name="HOUR"/>:<ph name="MINUTE"/> உள்ளது</translation>
+<translation id="5787281376604286451">பேச்சுவடிவ கருத்து செயலாக்கப்பட்டது.
+முடக்க Ctrl+Alt+Z அழுத்தவும்.</translation>
+<translation id="4479639480957787382">ஈத்தர்நெட்</translation>
+<translation id="6312403991423642364">அறியப்படாத பிணையப் பிழை</translation>
+<translation id="1467432559032391204">இடது</translation>
+<translation id="5543001071567407895">SMS</translation>
+<translation id="2354174487190027830"><ph name="NAME"/> ஐச் செயல்படுத்துகிறது</translation>
+<translation id="8814190375133053267">Wi-Fi</translation>
+<translation id="1398853756734560583">பெரிதாக்கு</translation>
+<translation id="2692809339924654275"><ph name="BLUETOOTH"/>: இணைக்கிறது...</translation>
+<translation id="252373100621549798">அறியாதது</translation>
+<translation id="1882897271359938046"><ph name="DISPLAY_NAME"/> ஐப் பிரதிபலிக்கிறது</translation>
+<translation id="2727977024730340865">குறைந்த சக்தியிலான சார்ஜர் செருகப்பட்டுள்ளது. பேட்டரி சார்ஜிங் நம்பகமானதாக இல்லாமல் இருக்கலாம்.</translation>
+<translation id="3784455785234192852">பூட்டு</translation>
+<translation id="2805756323405976993">ஆப்ஸ்</translation>
+<translation id="8871072142849158571"><ph name="DISPLAY_NAME"/> ஆனது <ph name="RESOLUTION"/> க்கு மாற்றியமைக்கப்பட்டது</translation>
+<translation id="1512064327686280138">செயலாக்கம் தோல்வி</translation>
+<translation id="5097002363526479830">'<ph name="NAME"/>' நெட்வொர்க்குடன் இணைப்பதில் தோல்வி: <ph name="DETAILS"/> </translation>
+<translation id="1850504506766569011">Wi-Fi முடக்கத்தில் உள்ளது.</translation>
+<translation id="8132793192354020517"><ph name="NAME"/> உடன் இணைக்கப்பட்டது</translation>
+<translation id="7052914147756339792">வால்பேப்பரை அமை...</translation>
+<translation id="8678698760965522072">ஆன்லைன் நிலை</translation>
+<translation id="2532589005999780174">அதிக ஒளி மாறுபாட்டுப் பயன்முறை</translation>
+<translation id="1119447706177454957">அகப் பிழை</translation>
+<translation id="3019353588588144572"><ph name="TIME_REMAINING"/> இல் பேட்டரி முழுவதும் சார்ஜ் ஆகிவிடும்</translation>
+<translation id="3473479545200714844">திரை உருப்பெருக்கி</translation>
+<translation id="7005812687360380971">தோல்வி</translation>
+<translation id="882279321799040148">காண கிளிக் செய்க</translation>
+<translation id="5045550434625856497">தவறான கடவுச்சொல்</translation>
+<translation id="1602076796624386989">மொபைல் தரவை இயக்கு</translation>
+<translation id="6981982820502123353">அணுகல்தன்மை</translation>
+<translation id="3157931365184549694">மீட்டமை</translation>
+<translation id="4274292172790327596">அறியப்படாத பிழை</translation>
+<translation id="4032485810211612751"><ph name="HOURS"/>:<ph name="MINUTES"/>:<ph name="SECONDS"/></translation>
+<translation id="225680501294068881">சாதனங்களைக் கண்டறிகிறது...</translation>
+<translation id="5597451508971090205"><ph name="SHORT_WEEKDAY"/>, <ph name="DATE"/></translation>
+<translation id="4448844063988177157">Wi-Fi பிணையங்களைத் தேடுகிறது...</translation>
+<translation id="8401662262483418323">'<ph name="NAME"/>' உடன் இணைக்கத் தவறியது: <ph name="DETAILS"/>
+சேவையகச் செய்தி: <ph name="SERVER_MSG"/></translation>
+<translation id="2475982808118771221">ஒரு பிழை ஏற்பட்டது</translation>
+<translation id="7229570126336867161">EVDO தேவை</translation>
+<translation id="2999742336789313416"><ph name="DISPLAY_NAME"/> ஆனது <ph name="DOMAIN"/> ஆல் நிர்வகிக்கப்படும் பொது அமர்வாகும்</translation>
+<translation id="7029814467594812963">அமர்விலிருந்து வெளியேறவும்</translation>
+<translation id="8454013096329229812">Wi-Fi இயக்கத்தில் உள்ளது.</translation>
+<translation id="4872237917498892622">Alt+Search அல்லது Shift</translation>
+<translation id="2983818520079887040">அமைப்புகள்...</translation>
+<translation id="1717216362413677834">டாக் பயன்முறை</translation>
+<translation id="8927026611342028580">இணைக்க கோரப்பட்டது</translation>
+<translation id="8300849813060516376">OTASP தோல்வியுற்றது</translation>
+<translation id="2792498699870441125">Alt+Search</translation>
+<translation id="8660803626959853127"><ph name="COUNT"/> கோப்பு(களை) ஒத்திசைக்கிறது</translation>
+<translation id="3709443003275901162">9+</translation>
+<translation id="639644700271529076">CAPS LOCK முடக்கப்பட்டுள்ளது</translation>
+<translation id="6248847161401822652">வெளியேற Control Shift Q ஐ இருமுறை அழுத்தவும்.</translation>
+<translation id="6267036997247669271"><ph name="NAME"/>: செயல்படுத்துகிறது...</translation>
+<translation id="1391854757121130358">உங்களின் அனுமதிக்கப்பட்ட மொபைல் தரவை நீங்கள் பயன்படுத்தி இருக்கலாம்.</translation>
+<translation id="5413208160176941586">உட்புறமாக நிர்வகிக்கப்படும் பயனர்</translation>
+<translation id="1059194134494239015"><ph name="DISPLAY_NAME"/>: <ph name="RESOLUTION"/></translation>
+<translation id="4864165860509564259">துவக்கி நிலை</translation>
+<translation id="7593891976182323525">Search அல்லது Shift</translation>
+<translation id="7649070708921625228">உதவி</translation>
+<translation id="3050422059534974565">CAPS LOCK இயக்கத்தில் உள்ளது.
+ரத்துசெய்ய Search அல்லது Shift ஐ அழுத்தவும்.</translation>
+<translation id="397105322502079400">கணக்கிடுகிறது...</translation>
+<translation id="158849752021629804">உள்ளூர் பிணையம் தேவை</translation>
+<translation id="6857811139397017780"><ph name="NETWORKSERVICE"/> ஐ செயல்படுத்து</translation>
+<translation id="5864471791310927901">DHCP பார்வையிடுதல் தோல்வி</translation>
+<translation id="5812035014844949013">வெளியீடு</translation>
+<translation id="6692173217867674490">மோசமான கடவுச்சொற்றொடர்</translation>
+<translation id="6165508094623778733">மேலும் அறிக</translation>
+<translation id="9046895021617826162">இணைப்பு தோல்வியடைந்தது</translation>
+<translation id="973896785707726617">இந்த அமர்வு <ph name="SESSION_TIME_REMAINING"/> நிமிடங்களில் முடியும். நீங்கள் தானாகவே வெளியேற்றப்படுவீர்கள்.</translation>
+<translation id="8372369524088641025">மோசமான WEP விசை</translation>
+<translation id="6636709850131805001">அறியப்படாத நிலை</translation>
+<translation id="3573179567135747900">&quot;<ph name="FROM_LOCALE"/>&quot; க்கு மீண்டும் மாற்று (மறுதொடக்கம் தேவை)</translation>
+<translation id="8103386449138765447">SMS செய்திகள்: <ph name="MESSAGE_COUNT"/></translation>
+<translation id="5045002648206642691">Google இயக்கக அமைப்புகள்...</translation>
+<translation id="1510238584712386396">துவக்கி</translation>
+<translation id="7209101170223508707">CAPS LOCK இயக்கத்தில் உள்ளது.
+ரத்துசெய்ய Alt+Search அல்லது Shift ஐ அழுத்தவும்.</translation>
+<translation id="8940956008527784070">பேட்டரி குறைவு (<ph name="PERCENTAGE"/>%)</translation>
+<translation id="5102001756192215136"><ph name="HOUR"/>:<ph name="MINUTE"/> உள்ளது</translation>
+<translation id="520760366042891468">Hangouts மூலமாக உங்கள் திரையின் கட்டுப்பாட்டைப் பகிர்கிறது.</translation>
+<translation id="8000066093800657092">நெட்வொர்க் இல்லை</translation>
+<translation id="4015692727874266537">இன்னொரு கணக்கில் உள்நுழைக...</translation>
+<translation id="5941711191222866238">சிறிதாக்கு</translation>
+<translation id="6911468394164995108">மற்றொன்றில் சேர்...</translation>
+<translation id="412065659894267608">முழுவதும் சார்ஜ் ஆகும் நேரம் - <ph name="HOUR"/>ம <ph name="MINUTE"/>நி</translation>
+<translation id="6359806961507272919"><ph name="PHONE_NUMBER"/> என்ற எண்ணிலிருந்து வந்த SMS</translation>
+<translation id="1244147615850840081">சேவை வழங்குநர்</translation>
+</translationbundle> \ No newline at end of file
diff --git a/chromium/ash/strings/ash_strings_te.xtb b/chromium/ash/strings/ash_strings_te.xtb
new file mode 100644
index 00000000000..9e37d6251b1
--- /dev/null
+++ b/chromium/ash/strings/ash_strings_te.xtb
@@ -0,0 +1,201 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="te">
+<translation id="3595596368722241419">బ్యాటరీ నిండింది</translation>
+<translation id="5250713215130379958">లాంచర్‌ను స్వయంచాలకంగా దాచు</translation>
+<translation id="7814236020522506259"><ph name="HOUR"/> మరియు <ph name="MINUTE"/></translation>
+<translation id="7880025619322806991">పోర్టల్ స్థితి</translation>
+<translation id="30155388420722288">అతివ్యాప్తి బటన్</translation>
+<translation id="5571066253365925590">Bluetooth ప్రారంభించబడింది</translation>
+<translation id="9074739597929991885">Bluetooth</translation>
+<translation id="2268130516524549846">Bluetooth నిలిపివేయబడింది</translation>
+<translation id="3775358506042162758">మీరు బహుళ సైన్-ఇన్‌లో గరిష్టంగా మూడు ఖాతాలను మాత్రమే కలిగి ఉండవచ్చు.</translation>
+<translation id="370649949373421643">Wi-fiని ప్రారంభించు</translation>
+<translation id="3626281679859535460">ప్రకాశం</translation>
+<translation id="8054466585765276473">బ్యాటరీ సమయాన్ని లెక్కిస్తోంది.</translation>
+<translation id="7982789257301363584">నెట్‌వర్క్</translation>
+<translation id="5565793151875479467">ప్రాక్సీ...</translation>
+<translation id="938582441709398163">కీబోర్డ్ అవలోకనం</translation>
+<translation id="4387004326333427325">ప్రామాణీకరణ ప్రమాణపత్రం రిమోట్‌లో తిరస్కరించబడింది</translation>
+<translation id="6979158407327259162">Google డిస్క్</translation>
+<translation id="6943836128787782965">HTTP పొందడంలో విఫలమైంది</translation>
+<translation id="2297568595583585744">స్థితి ట్రే</translation>
+<translation id="1661867754829461514">PIN లేదు</translation>
+<translation id="4508225577814909926"><ph name="NAME"/>: కనెక్ట్ అవుతోంది...</translation>
+<translation id="4237016987259239829">నెట్‌వర్క్ కనెక్షన్ లోపం</translation>
+<translation id="2946640296642327832">Bluetoothని ప్రారంభించు</translation>
+<translation id="6459472438155181876"><ph name="DISPLAY_NAME"/>కు స్క్రీన్‌ను విస్తరిస్తోంది</translation>
+<translation id="8206859287963243715">సెల్యులార్</translation>
+<translation id="6596816719288285829">IP చిరునామా</translation>
+<translation id="4508265954913339219">సక్రియం చేయడం విఫలమైంది</translation>
+<translation id="3621712662352432595">ఆడియో సెట్టింగ్‌‍లు</translation>
+<translation id="1812696562331527143">మీ ఇన్‌పుట్ పద్ధతి <ph name="INPUT_METHOD_ID"/>*(<ph name="BEGIN_LINK"/>3వ పక్షం<ph name="END_LINK"/>)కు మార్చబడింది.
+మారడానికి Shift + Altను నొక్కండి.</translation>
+<translation id="2127372758936585790">తక్కువ-పవర్ గల ఛార్జర్</translation>
+<translation id="3846575436967432996">నెట్‌వర్క్ సమాచారం అందుబాటులో లేదు</translation>
+<translation id="3026237328237090306">మొబైల్ డేటాను సెటప్ చేయి</translation>
+<translation id="785750925697875037">మొబైల్ ఖాతాని వీక్షించండి</translation>
+<translation id="153454903766751181">సెల్యులార్ మోడెమ్‌ను ప్రారంభిస్తోంది...</translation>
+<translation id="4628814525959230255">మీ స్క్రీన్ యొక్క నియంత్రణ Hangouts ద్వారా <ph name="HELPER_NAME"/>తో భాగస్వామ్యం చేయబడుతోంది.</translation>
+<translation id="8343941333792395995"><ph name="DISPLAY_NAME"/> తిప్పబడింది</translation>
+<translation id="7864539943188674973">Bluetoothని నిలిపివేయి</translation>
+<translation id="939252827960237676">స్క్రీన్‌షాట్‌ను సేవ్ చేయడంలో విఫలమైంది</translation>
+<translation id="3126069444801937830">నవీకరించడానికి పునఃప్రారంభించండి</translation>
+<translation id="2268813581635650749">అందరినీ సైన్ అవుట్ చేయి</translation>
+<translation id="735745346212279324">VPN డిస్‌కనెక్ట్ చేయబడింది</translation>
+<translation id="7320906967354320621">ఖాళీగా ఉంది</translation>
+<translation id="6303423059719347535">బ్యాటరీ <ph name="PERCENTAGE"/>% నిండింది</translation>
+<translation id="15373452373711364">పెద్ద మౌస్ కర్సర్</translation>
+<translation id="2778346081696727092">అందించిన వినియోగదారు పేరుతో లేదా పాస్‌వర్డ్‌తో ప్రామాణీకరించడం విఫలమైంది</translation>
+<translation id="3294437725009624529">అతిథి</translation>
+<translation id="8190698733819146287">భాషలను అనుకూలీకరించి, ఇన్‌పుట్ చెయ్యి...</translation>
+<translation id="2903907270192926896">ఇన్‌పుట్</translation>
+<translation id="8676770494376880701">తక్కువ-పవర్ గల ఛార్జర్ కనెక్ట్ చేయబడింది</translation>
+<translation id="7170041865419449892">పరిధిని దాటింది</translation>
+<translation id="4804818685124855865">డిస్‌కనెక్ట్ చెయ్యి</translation>
+<translation id="2544853746127077729">ప్రామాణీకరణ ప్రమాణపత్రం నెట్‌వర్క్ ద్వారా తిరస్కరించబడింది</translation>
+<translation id="5222676887888702881">సైన్ ఔట్</translation>
+<translation id="2688477613306174402">కాన్ఫిగరేషన్</translation>
+<translation id="1272079795634619415">ఆపు</translation>
+<translation id="4957722034734105353">మరింత తెలుసుకోండి...</translation>
+<translation id="2964193600955408481">Wi-Fiని నిలిపివేయి</translation>
+<translation id="811680302244032017">పరికరాన్ని జోడించు...</translation>
+<translation id="4279490309300973883">ప్రతిబింబిస్తుంది</translation>
+<translation id="2509468283778169019">CAPS LOCK ఆన్‌లో ఉంది</translation>
+<translation id="3892641579809465218">అంతర్గత ప్రదర్శన</translation>
+<translation id="7823564328645135659">మీ సెట్టింగ్‌లను సమకాలీకరించిన తర్వాత Chrome యొక్క భాష &quot;<ph name="FROM_LOCALE"/>&quot; నుండి &quot;<ph name="TO_LOCALE"/>&quot;కి మార్చబడింది.</translation>
+<translation id="3368922792935385530">కనెక్ట్ అయింది</translation>
+<translation id="8340999562596018839">చదవబడే అభిప్రాయం</translation>
+<translation id="8654520615680304441">Wi-Fiని ప్రారంభించు...</translation>
+<translation id="5825747213122829519">మీ ఇన్‌పుట్ పద్ధతి <ph name="INPUT_METHOD_ID"/>కు మార్చబడింది.
+మారడానికి Shift + Altను నొక్కండి.</translation>
+<translation id="2562916301614567480">ప్రైవేట్ నెట్‌వర్క్</translation>
+<translation id="6549021752953852991">సెల్యులార్ నెట్‌వర్క్ అందుబాటులో లేదు</translation>
+<translation id="4379753398862151997">ప్రియమైన మానిటర్, ఇది మన మధ్య పని చేయడం లేదు. (ఆ మానిటర్‌కు మద్దతు లేదు)</translation>
+<translation id="6426039856985689743">మొబైల్ డేటాను నిలిపివేయి</translation>
+<translation id="3087734570205094154">దిగువ</translation>
+<translation id="3742055079367172538">స్క్రీన్‌షాట్ తీసినప్పుడు</translation>
+<translation id="8878886163241303700">స్క్రీన్ విస్తరించబడుతోంది</translation>
+<translation id="5271016907025319479">VPN కాన్ఫిగర్ చేయబడలేదు.</translation>
+<translation id="372094107052732682">నిష్క్రమించడానికి రెండుసార్లు Ctrl+Shift+Q నొక్కండి.</translation>
+<translation id="6803622936009808957">మద్దతు ఉన్న రిజల్యూషన్‌లు కనుగొనబడనందున ప్రదర్శనలను ప్రతిబింబించడం సాధ్యపడలేదు. దానికి బదులుగా విస్తారిత డెస్క్‌టాప్‌కు మారారు.</translation>
+<translation id="1480041086352807611">డెమో మోడ్</translation>
+<translation id="3626637461649818317"><ph name="PERCENTAGE"/>% మిగిలి ఉంది</translation>
+<translation id="9089416786594320554">ఇన్‌పుట్ పద్ధతులు</translation>
+<translation id="6247708409970142803"><ph name="PERCENTAGE"/>%</translation>
+<translation id="2614835198358683673">మీ Chromebook ప్రారంభించబడినప్పుడు ఛార్జ్ కాకపోవచ్చు. అధికారిక ఛార్జర్‌ను ఉపయోగించడానికి ప్రయత్నించండి.</translation>
+<translation id="1895658205118569222">షట్‌డౌన్</translation>
+<translation id="4430019312045809116">వాల్యూమ్</translation>
+<translation id="4442424173763614572">DNS శోధన విఫలమైంది</translation>
+<translation id="6356500677799115505">బ్యాటరీ నిండింది మరియు చార్జ్ అవుతోంది.</translation>
+<translation id="7874779702599364982">సెల్యులార్ నెట్‌వర్క్‌ల కోసం శోధిస్తోంది...</translation>
+<translation id="583281660410589416">తెలియనిది</translation>
+<translation id="1383876407941801731">శోధన</translation>
+<translation id="7468789844759750875">మరింత డేటాను కొనుగోలు చేయడానికి <ph name="NAME"/> సక్రియ పోర్టల్‌ను సందర్శించండి.</translation>
+<translation id="3901991538546252627"><ph name="NAME"/>కి కనెక్ట్ చేస్తోంది</translation>
+<translation id="2204305834655267233">నెట్‌వర్క్ సమాచారం</translation>
+<translation id="1621499497873603021">బ్యాటరీ ఖాళీ కావడానికి మిగిలి ఉన్న సమయం, <ph name="TIME_LEFT"/></translation>
+<translation id="5980301590375426705">అతిథిగా నిష్క్రమించు</translation>
+<translation id="4471417012762451363">బ్యాటరీ <ph name="PERCENTAGE"/>% నిండింది మరియు చార్జ్ అవుతోంది</translation>
+<translation id="8308637677604853869">మునుపటి మెను</translation>
+<translation id="4666297444214622512">మరో ఖాతాకు సైన్ ఇన్ చేయలేరు.</translation>
+<translation id="1346748346194534595">కుడి</translation>
+<translation id="1773212559869067373">ప్రామాణీకరణ ప్రమాణపత్రం స్థానికంగా తిరస్కరించబడింది</translation>
+<translation id="8528322925433439945">మొబైల్ ...</translation>
+<translation id="7049357003967926684">అసోసియేషన్</translation>
+<translation id="8428213095426709021">సెట్టింగ్‌లు</translation>
+<translation id="2372145515558759244">అనువర్తనాలను సమకాలీకరిస్తోంది...</translation>
+<translation id="7256405249507348194">గుర్తించబడని లోపం: <ph name="DESC"/></translation>
+<translation id="7925247922861151263">AAA తనిఖీ విఫలమైంది</translation>
+<translation id="8456362689280298700"><ph name="HOUR"/>:<ph name="MINUTE"/>లో పూర్తవుతుంది</translation>
+<translation id="5787281376604286451">చదవబడే అభిప్రాయం ప్రారంభించబడింది.
+నిలిపివేయడానికి Ctrl+Alt+Zను నొక్కండి.</translation>
+<translation id="4479639480957787382">ఈథర్నెట్</translation>
+<translation id="6312403991423642364">తెలియని నెట్‌వర్క్ లోపం</translation>
+<translation id="1467432559032391204">ఎడమ</translation>
+<translation id="5543001071567407895">SMS</translation>
+<translation id="2354174487190027830"><ph name="NAME"/>ని సక్రియం చేస్తోంది</translation>
+<translation id="8814190375133053267">Wi-Fi</translation>
+<translation id="1398853756734560583">గరిష్ఠీకరించు</translation>
+<translation id="2692809339924654275"><ph name="BLUETOOTH"/>: కనెక్ట్ అవుతోంది...</translation>
+<translation id="252373100621549798">తెలియని ప్రదర్శన</translation>
+<translation id="1882897271359938046"><ph name="DISPLAY_NAME"/>కు దర్పణం చేస్తోంది</translation>
+<translation id="2727977024730340865">తక్కువ-పవర్ గల ఛార్జర్‌కు ప్లగిన్ చేయబడింది. బ్యాటరీ ఛార్జింగ్ విశ్వసనీయంగా ఉండకపోవచ్చు.</translation>
+<translation id="3784455785234192852">లాక్ చేయి</translation>
+<translation id="2805756323405976993">Apps</translation>
+<translation id="8871072142849158571"><ph name="DISPLAY_NAME"/> పరిమాణం <ph name="RESOLUTION"/>కి మార్చబడింది</translation>
+<translation id="1512064327686280138">సక్రియా విఫలం</translation>
+<translation id="5097002363526479830">'<ph name="NAME"/>'కు నెట్‌వర్క్‌కు కనెక్ట్ చేయడానికి విఫలమైంది: <ph name="DETAILS"/></translation>
+<translation id="1850504506766569011">Wi-Fi నిలిపివేయబడింది.</translation>
+<translation id="8132793192354020517"><ph name="NAME"/>కు కనెక్ట్ చేయబడింది</translation>
+<translation id="7052914147756339792">వాల్‌పేపర్‌ను సెట్ చేయి...</translation>
+<translation id="8678698760965522072">ఆన్‌లైన్ స్థితి</translation>
+<translation id="2532589005999780174">అధిక కాంట్రాస్ట్ మోడ్</translation>
+<translation id="1119447706177454957">అంతర్గత లోపం</translation>
+<translation id="3019353588588144572">బ్యాటరీ నిండటానికి పట్టే సమయం, <ph name="TIME_REMAINING"/></translation>
+<translation id="3473479545200714844">స్క్రీన్ మాగ్నిఫైయర్</translation>
+<translation id="7005812687360380971">విఫలం</translation>
+<translation id="882279321799040148">వీక్షించడానికి క్లిక్ చేసినప్పుడు</translation>
+<translation id="5045550434625856497">తప్పు పాస్‌వర్డ్</translation>
+<translation id="1602076796624386989">మొబైల్ డేటాను ప్రారంభించు</translation>
+<translation id="6981982820502123353">ప్రాప్యత</translation>
+<translation id="3157931365184549694">పునరుద్ధరించు</translation>
+<translation id="4274292172790327596">గుర్తించబడని లోపం</translation>
+<translation id="4032485810211612751"><ph name="HOURS"/>:<ph name="MINUTES"/>:<ph name="SECONDS"/></translation>
+<translation id="225680501294068881">పరికరాల కోసం స్కాన్ చేస్తోంది...</translation>
+<translation id="5597451508971090205"><ph name="SHORT_WEEKDAY"/>, <ph name="DATE"/></translation>
+<translation id="4448844063988177157">Wi-Fi నెట్‌వర్క్‌ల కోసం శోధిస్తోంది...</translation>
+<translation id="8401662262483418323">'<ph name="NAME"/>'కు కనెక్ట్ చేయడంలో విఫలమైంది: <ph name="DETAILS"/>
+సర్వర్ సందేశం: <ph name="SERVER_MSG"/></translation>
+<translation id="2475982808118771221">ఒక లోపం సంభవించింది</translation>
+<translation id="7229570126336867161">EVDO అవసరం</translation>
+<translation id="2999742336789313416"><ph name="DISPLAY_NAME"/> అనేది <ph name="DOMAIN"/> ద్వారా నిర్వహించబడుతున్న పబ్లిక్ సెషన్</translation>
+<translation id="7029814467594812963">సెషన్‌ని నిష్క్రమించు</translation>
+<translation id="8454013096329229812">Wi-Fi ప్రారంభించబడింది.</translation>
+<translation id="4872237917498892622">Alt+Search లేదా Shift</translation>
+<translation id="2983818520079887040">సెట్టింగ్‌లు...</translation>
+<translation id="1717216362413677834">డాక్ మోడ్</translation>
+<translation id="8927026611342028580">కనెక్ట్ చేయడం అభ్యర్థించబడింది</translation>
+<translation id="8300849813060516376">OTASP విఫలమైంది</translation>
+<translation id="2792498699870441125">Alt+Search</translation>
+<translation id="8660803626959853127"><ph name="COUNT"/> ఫైల్(ల)ను సమకాలీకరిస్తోంది</translation>
+<translation id="3709443003275901162">9+</translation>
+<translation id="639644700271529076">CAPS LOCK ఆపివేయబడింది</translation>
+<translation id="6248847161401822652">నిష్క్రమించడానికి రెండుసార్లు Control Shift Q నొక్కండి.</translation>
+<translation id="6267036997247669271"><ph name="NAME"/>: సక్రియం చేస్తోంది...</translation>
+<translation id="1391854757121130358">మీరు మీ మొబైల్ డేటా కేటాయింపును ఉపయోగించి ఉండవచ్చు.</translation>
+<translation id="5413208160176941586">స్థానికంగా నిర్వహించబడే వినియోగదారు</translation>
+<translation id="1059194134494239015"><ph name="DISPLAY_NAME"/>: <ph name="RESOLUTION"/></translation>
+<translation id="4864165860509564259">లాంచర్ స్థానం</translation>
+<translation id="7593891976182323525">Search లేదా Shift</translation>
+<translation id="7649070708921625228">సహాయం</translation>
+<translation id="3050422059534974565">CAPS LOCK ఆన్‌లో ఉంది.
+రద్దు చేయడానికి Search లేదా Shiftని నొక్కండి.</translation>
+<translation id="397105322502079400">గణిస్తోంది...</translation>
+<translation id="158849752021629804"> హోమ్ నెట్‌వర్క్ అవసరం</translation>
+<translation id="6857811139397017780"><ph name="NETWORKSERVICE"/>ని సక్రియం చెయ్యి</translation>
+<translation id="5864471791310927901">DHCP లుక్‌అప్ విఫలమైంది</translation>
+<translation id="5812035014844949013">అవుట్‌పుట్</translation>
+<translation id="6692173217867674490">తప్పుడు పాస్‌ఫ్రేజ్</translation>
+<translation id="6165508094623778733">మరింత తెలుసుకోండి</translation>
+<translation id="9046895021617826162">కనెక్ట్ విఫలమైంది</translation>
+<translation id="973896785707726617">ఈ సెషన్ <ph name="SESSION_TIME_REMAINING"/> తర్వాత ముగుస్తుంది. మీరు స్వయంచాలకంగా సైన్ అవుట్ చేయబడతారు.</translation>
+<translation id="8372369524088641025">తప్పుడు WEP కీ</translation>
+<translation id="6636709850131805001">గుర్తించబడని రాష్ట్రం</translation>
+<translation id="3573179567135747900">&quot;<ph name="FROM_LOCALE"/>&quot;కు వెనుకకి మార్చండి (పునఃప్రారంభం అవసరం)</translation>
+<translation id="8103386449138765447">SMS సందేశాలు: <ph name="MESSAGE_COUNT"/></translation>
+<translation id="5045002648206642691">Google డిస్క్ సెట్టింగ్‌లు...</translation>
+<translation id="1510238584712386396">లాంచర్</translation>
+<translation id="7209101170223508707">CAPS LOCK ఆన్‌లో ఉంది.
+రద్దు చేయడానికి Alt+Search లేదా Shiftని నొక్కండి.</translation>
+<translation id="8940956008527784070">బ్యాటరీ తక్కువగా ఉంది (<ph name="PERCENTAGE"/>%)</translation>
+<translation id="5102001756192215136"><ph name="HOUR"/>:<ph name="MINUTE"/> మిగిలి ఉంది</translation>
+<translation id="520760366042891468">మీ స్క్రీన్ యొక్క నియంత్రణ Hangouts ద్వారా భాగస్వామ్యం చేయబడుతోంది.</translation>
+<translation id="8000066093800657092">ఏ నెట్‌వర్క్ లేదు</translation>
+<translation id="4015692727874266537">మరో ఖాతాతో సైన్ ఇన్ చేయి...</translation>
+<translation id="5941711191222866238">కనిష్టీకరించు</translation>
+<translation id="6911468394164995108">మరొక దానిలో చేరండి...</translation>
+<translation id="412065659894267608">నిండే వరకు <ph name="HOUR"/>h <ph name="MINUTE"/>m అవుతుంది</translation>
+<translation id="6359806961507272919"><ph name="PHONE_NUMBER"/> నుండి SMS</translation>
+<translation id="1244147615850840081">కారియర్</translation>
+</translationbundle> \ No newline at end of file
diff --git a/chromium/ash/strings/ash_strings_th.xtb b/chromium/ash/strings/ash_strings_th.xtb
new file mode 100644
index 00000000000..a749f023b66
--- /dev/null
+++ b/chromium/ash/strings/ash_strings_th.xtb
@@ -0,0 +1,201 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="th">
+<translation id="3595596368722241419">แบตเตอรี่เต็ม</translation>
+<translation id="5250713215130379958">ตัวเรียกใช้งานแบบซ่อนอัตโนมัติ</translation>
+<translation id="7814236020522506259"><ph name="HOUR"/> กับ <ph name="MINUTE"/></translation>
+<translation id="7880025619322806991">สถานะพอร์ทัล</translation>
+<translation id="30155388420722288">ปุ่มแบบโอเวอร์โฟลว์</translation>
+<translation id="5571066253365925590">เปิดใช้งานบลูทูธแล้ว</translation>
+<translation id="9074739597929991885">บลูทูธ</translation>
+<translation id="2268130516524549846">ปิดใช้งานบลูทูธแล้ว</translation>
+<translation id="3775358506042162758">คุณสามารถมีได้มากสุดสามบัญชีเท่านั้นในการลงชื่อเข้าสู่ระบบพร้อมกันหลายบัญชี</translation>
+<translation id="370649949373421643">เปิดใช้งาน Wi-Fi</translation>
+<translation id="3626281679859535460">ความสว่าง</translation>
+<translation id="8054466585765276473">กำลังคำนวณเวลาใช้งานแบตเตอรี่</translation>
+<translation id="7982789257301363584">เครือข่าย</translation>
+<translation id="5565793151875479467">พร็อกซี...</translation>
+<translation id="938582441709398163">การวางซ้อนแป้นพิมพ์</translation>
+<translation id="4387004326333427325">ใบรับรองการตรวจสอบสิทธิ์ได้รับการปฏิเสธจากระยะไกล</translation>
+<translation id="6979158407327259162">Google ไดรฟ์</translation>
+<translation id="6943836128787782965">การรับ HTTP ล้มเหลว</translation>
+<translation id="2297568595583585744">ถาดสถานะ</translation>
+<translation id="1661867754829461514">ไม่พบ PIN</translation>
+<translation id="4508225577814909926"><ph name="NAME"/>: กำลังเชื่อมต่อ...</translation>
+<translation id="4237016987259239829">ข้อผิดพลาดการเชื่อมต่อเครือข่าย</translation>
+<translation id="2946640296642327832">เปิดใช้งานบลูทูธ</translation>
+<translation id="6459472438155181876">ขยายหน้าจอไปยัง <ph name="DISPLAY_NAME"/></translation>
+<translation id="8206859287963243715">โทรศัพท์มือถือ</translation>
+<translation id="6596816719288285829">ที่อยู่ IP</translation>
+<translation id="4508265954913339219">การเปิดใช้งานล้มเหลว</translation>
+<translation id="3621712662352432595">การตั้งค่าเสียง</translation>
+<translation id="1812696562331527143">วิธีการป้อนข้อมูลของคุณเปลี่ยนแปลงเป็น <ph name="INPUT_METHOD_ID"/>* (<ph name="BEGIN_LINK"/>บุคคลที่สาม<ph name="END_LINK"/>)
+กด Shift + Alt เพื่อสลับ</translation>
+<translation id="2127372758936585790">ที่ชาร์จพลังงานต่ำ</translation>
+<translation id="3846575436967432996">ไม่มีข้อมูลเครือข่ายที่สามารถใช้งานได้</translation>
+<translation id="3026237328237090306">ตั้งค่าข้อมูลมือถือ</translation>
+<translation id="785750925697875037">ดูบัญชีมือถือ</translation>
+<translation id="153454903766751181">กำลังเริ่มต้นโมเด็มมือถือ...</translation>
+<translation id="4628814525959230255">กำลังแชร์การควบคุมหน้าจอของคุณด้วย <ph name="HELPER_NAME"/> ผ่านแฮงเอาท์</translation>
+<translation id="8343941333792395995"><ph name="DISPLAY_NAME"/> ถูกหมุนเวียน</translation>
+<translation id="7864539943188674973">ปิดใช้งานบลูทูธ</translation>
+<translation id="939252827960237676">ไม่สามารถบันทึกภาพหน้าจอ</translation>
+<translation id="3126069444801937830">รีสตาร์ทเพื่ออัปเดต</translation>
+<translation id="2268813581635650749">ออกจากระบบทั้งหมด</translation>
+<translation id="735745346212279324">ยกเลิกการเชื่อมต่อ VPN แล้ว</translation>
+<translation id="7320906967354320621">ไม่ทำงาน</translation>
+<translation id="6303423059719347535">มีแบตเตอรี่ <ph name="PERCENTAGE"/>%</translation>
+<translation id="15373452373711364">เคอร์เซอร์เมาส์ขนาดใหญ่</translation>
+<translation id="2778346081696727092">ไม่สามารถตรวจสอบสิทธิ์ชื่อผู้ใช้หรือรหัสผ่านที่ให้มาได้</translation>
+<translation id="3294437725009624529">ผู้มาเยือน</translation>
+<translation id="8190698733819146287">กำหนดค่าภาษาและการป้อนข้อมูล... </translation>
+<translation id="2903907270192926896">อินพุต</translation>
+<translation id="8676770494376880701">เชื่อมต่อกับที่ชาร์จพลังงานต่ำ</translation>
+<translation id="7170041865419449892">อยู่นอกระยะสัญญาณ</translation>
+<translation id="4804818685124855865">ตัดการเชื่อมต่อ</translation>
+<translation id="2544853746127077729">ใบรับรองการตรวจสอบสิทธิ์ได้รับการปฏิเสธจากเครือข่าย</translation>
+<translation id="5222676887888702881">ออกจากระบบ</translation>
+<translation id="2688477613306174402">การกำหนดค่า</translation>
+<translation id="1272079795634619415">หยุด</translation>
+<translation id="4957722034734105353">เรียนรู้เพิ่มเติม...</translation>
+<translation id="2964193600955408481">ปิดใช้งาน WiFi</translation>
+<translation id="811680302244032017">เพิ่มอุปกรณ์...</translation>
+<translation id="4279490309300973883">กำลังแสดงผล</translation>
+<translation id="2509468283778169019">Caps Lock เปิดอยู่</translation>
+<translation id="3892641579809465218">จอแสดงผลภายใน</translation>
+<translation id="7823564328645135659">เปลี่ยนภาษาจาก &quot;<ph name="FROM_LOCALE"/>&quot; เป็น &quot;<ph name="TO_LOCALE"/>&quot; หลังจากซิงค์การตั้งค่าของคุณ</translation>
+<translation id="3368922792935385530">เชื่อมต่อแล้ว</translation>
+<translation id="8340999562596018839">การตอบสนองด้วยเสียง</translation>
+<translation id="8654520615680304441">เปิด WiFi...</translation>
+<translation id="5825747213122829519">วิธีการป้อนข้อมูลของคุณเปลี่ยนแปลงเป็น <ph name="INPUT_METHOD_ID"/> แล้ว
+กด Shift + Alt เพื่อสลับ</translation>
+<translation id="2562916301614567480">เครือข่ายส่วนบุคคล</translation>
+<translation id="6549021752953852991">ไม่มีเครือข่ายมือถือที่ใช้งานได้</translation>
+<translation id="4379753398862151997">จอภาพเอ๋ย เราร่วมงานกันไม่ได้ (จอภาพนั้นไม่ได้รับการสนับสนุน)</translation>
+<translation id="6426039856985689743">ปิดการใช้งานข้อมูลมือถือ</translation>
+<translation id="3087734570205094154">ด้านล่าง</translation>
+<translation id="3742055079367172538">ภาพหน้าจอที่บันทึก</translation>
+<translation id="8878886163241303700">กำลังขยายหน้าจอ</translation>
+<translation id="5271016907025319479">ไม่ได้กำหนดค่า VPN</translation>
+<translation id="372094107052732682">กด Ctrl+Shift+Q สองครั้งเพื่อออก</translation>
+<translation id="6803622936009808957">ไม่สามารถแสดงผลคู่ขนานได้เนื่องจากไม่พบความละเอียดที่สนับสนุน เข้าสู่เดสก์ท็อปแบบขยายแทน</translation>
+<translation id="1480041086352807611">โหมดสาธิต</translation>
+<translation id="3626637461649818317">เหลืออีก <ph name="PERCENTAGE"/>%</translation>
+<translation id="9089416786594320554">วิธีการป้อนข้อมูล</translation>
+<translation id="6247708409970142803"><ph name="PERCENTAGE"/>%</translation>
+<translation id="2614835198358683673">Chromebook ของคุณอาจไม่ได้ชาร์จในขณะที่เปิดอยู่ ลองใช้ที่ชาร์จมาตรฐาน</translation>
+<translation id="1895658205118569222">ปิด</translation>
+<translation id="4430019312045809116">ระดับเสียง</translation>
+<translation id="4442424173763614572">การค้นหา DNS ล้มเหลว</translation>
+<translation id="6356500677799115505">แบตเตอรี่เต็มและกำลังชาร์จ</translation>
+<translation id="7874779702599364982">กำลังค้นหาเครือข่ายโทรศัพท์มือถือ...</translation>
+<translation id="583281660410589416">ไม่รู้จัก</translation>
+<translation id="1383876407941801731">เครื่องมือค้นหา</translation>
+<translation id="7468789844759750875">ไปที่พอร์ทัลการเปิดใช้งาน <ph name="NAME"/> เพื่อซื้อข้อมูลเพิ่มเติม</translation>
+<translation id="3901991538546252627">กำลังเชื่อมต่อกับ <ph name="NAME"/></translation>
+<translation id="2204305834655267233">ข้อมูลเครือข่าย</translation>
+<translation id="1621499497873603021">เวลาที่เหลือกว่าแบตเตอรี่จะหมด, <ph name="TIME_LEFT"/></translation>
+<translation id="5980301590375426705">ออกจากเซสชันผู้มาเยือน</translation>
+<translation id="4471417012762451363">มีแบตเตอรี่ <ph name="PERCENTAGE"/>% และกำลังชาร์จ</translation>
+<translation id="8308637677604853869">เมนูก่อนหน้า</translation>
+<translation id="4666297444214622512">ไม่สามารถลงชื่อเข้าใช้บัญชีอื่น</translation>
+<translation id="1346748346194534595">ขวา</translation>
+<translation id="1773212559869067373">ใบรับรองการตรวจสอบสิทธิ์ได้รับการปฏิเสธในท้องถิ่น</translation>
+<translation id="8528322925433439945">มือถือ ...</translation>
+<translation id="7049357003967926684">การเชื่อมโยง</translation>
+<translation id="8428213095426709021">การตั้งค่า</translation>
+<translation id="2372145515558759244">กำลังซิงค์แอปพลิเคชัน...</translation>
+<translation id="7256405249507348194">ข้อผิดพลาดที่ไม่รู้จัก: <ph name="DESC"/></translation>
+<translation id="7925247922861151263">การตรวจสอบ AAA ล้มเหลว</translation>
+<translation id="8456362689280298700">อีก <ph name="HOUR"/>:<ph name="MINUTE"/> จึงจะเต็ม</translation>
+<translation id="5787281376604286451">เปิดใช้งานการตอบสนองด้วยเสียงอยู่
+กด Ctrl+Alt+Z เพื่อปิดใช้งาน</translation>
+<translation id="4479639480957787382">อีเทอร์เน็ต</translation>
+<translation id="6312403991423642364">ข้อผิดพลาดเครือข่ายที่ไม่รู้จัก</translation>
+<translation id="1467432559032391204">ซ้าย</translation>
+<translation id="5543001071567407895">SMS</translation>
+<translation id="2354174487190027830">กำลังเปิดใช้งาน <ph name="NAME"/></translation>
+<translation id="8814190375133053267">Wi-Fi</translation>
+<translation id="1398853756734560583">ย่อ</translation>
+<translation id="2692809339924654275"><ph name="BLUETOOTH"/>: กำลังเชื่อมต่อ...</translation>
+<translation id="252373100621549798">หน้าจอที่ไม่รู้จัก</translation>
+<translation id="1882897271359938046">กำลังแสดงผลไปที่ <ph name="DISPLAY_NAME"/></translation>
+<translation id="2727977024730340865">เสียบอยู่กับที่ชาร์จพลังงานต่ำ การชาร์จแบตเตอรี่อาจไม่น่าเชื่อถือ</translation>
+<translation id="3784455785234192852">ล็อก</translation>
+<translation id="2805756323405976993">Apps</translation>
+<translation id="8871072142849158571"><ph name="DISPLAY_NAME"/> ถูกปรับขนาดเป็น <ph name="RESOLUTION"/></translation>
+<translation id="1512064327686280138">การเปิดใช้งานล้มเหลว</translation>
+<translation id="5097002363526479830">ไม่สามารถเชื่อมต่อเครือข่าย &quot;<ph name="NAME"/>&quot;: <ph name="DETAILS"/></translation>
+<translation id="1850504506766569011">WiFi ปิดอยู่</translation>
+<translation id="8132793192354020517">เชื่อมต่อกับ <ph name="NAME"/></translation>
+<translation id="7052914147756339792">ตั้งค่าวอลเปเปอร์...</translation>
+<translation id="8678698760965522072">สถานะออนไลน์</translation>
+<translation id="2532589005999780174">โหมดคอนทราสต์สูง</translation>
+<translation id="1119447706177454957">ข้อผิดพลาดภายใน</translation>
+<translation id="3019353588588144572">เวลาที่เหลือกว่าจะชาร์จแบตเตอรี่เต็ม, <ph name="TIME_REMAINING"/></translation>
+<translation id="3473479545200714844">แว่นขยายหน้าจอ</translation>
+<translation id="7005812687360380971">ล้มเหลว</translation>
+<translation id="882279321799040148">คลิกเพื่อดู</translation>
+<translation id="5045550434625856497">รหัสผ่านไม่ถูกต้อง</translation>
+<translation id="1602076796624386989">เปิดใช้งานข้อมูลมือถือ</translation>
+<translation id="6981982820502123353">การเข้าถึง</translation>
+<translation id="3157931365184549694">คืนค่า</translation>
+<translation id="4274292172790327596">ข้อผิดพลาดที่ไม่รู้จัก</translation>
+<translation id="4032485810211612751"><ph name="HOURS"/>:<ph name="MINUTES"/>:<ph name="SECONDS"/></translation>
+<translation id="225680501294068881">กำลังสแกนหาอุปกรณ์...</translation>
+<translation id="5597451508971090205"><ph name="SHORT_WEEKDAY"/>, <ph name="DATE"/></translation>
+<translation id="4448844063988177157">กำลังค้นหาเครือข่าย Wi-Fi...</translation>
+<translation id="8401662262483418323">การเชื่อมต่อกับ &quot;<ph name="NAME"/>&quot; ล้มเหลว: <ph name="DETAILS"/>
+ข้อความเซิร์ฟเวอร์: <ph name="SERVER_MSG"/></translation>
+<translation id="2475982808118771221">มีข้อผิดพลาดเกิดขึ้น</translation>
+<translation id="7229570126336867161">ต้องใช้ EVDO</translation>
+<translation id="2999742336789313416"><ph name="DISPLAY_NAME"/> เป็นเซสชันสาธารณะซึ่งจัดการโดย <ph name="DOMAIN"/></translation>
+<translation id="7029814467594812963">ออกจากเซสชัน</translation>
+<translation id="8454013096329229812">WiFi เปิดอยู่</translation>
+<translation id="4872237917498892622">Alt+ค้นหา หรือ Shift</translation>
+<translation id="2983818520079887040">การตั้งค่า...</translation>
+<translation id="1717216362413677834">โหมดแท่นชาร์จ</translation>
+<translation id="8927026611342028580">ขอเชื่อมต่อ</translation>
+<translation id="8300849813060516376">OTASP ล้มเหลว</translation>
+<translation id="2792498699870441125">Alt+ค้นหา</translation>
+<translation id="8660803626959853127">กำลังซิงค์ <ph name="COUNT"/> ไฟล์</translation>
+<translation id="3709443003275901162">9+</translation>
+<translation id="639644700271529076">CAPS LOCK ปิดอยู่</translation>
+<translation id="6248847161401822652">กด Control Shift Q สองครั้งเพื่อออก</translation>
+<translation id="6267036997247669271"><ph name="NAME"/> กำลังเปิดใช้งาน...</translation>
+<translation id="1391854757121130358">คุณอาจใช้ข้อมูลมือถือถึงขีดจำกัดแล้ว</translation>
+<translation id="5413208160176941586">ผู้ใช้ที่ได้รับการจัดการในเครื่อง</translation>
+<translation id="1059194134494239015"><ph name="DISPLAY_NAME"/>: <ph name="RESOLUTION"/></translation>
+<translation id="4864165860509564259">ตำแหน่งของตัวเรียกใช้งาน</translation>
+<translation id="7593891976182323525">ค้นหาหรือ Shift</translation>
+<translation id="7649070708921625228">ช่วยเหลือ</translation>
+<translation id="3050422059534974565">CAPS LOCK เปิดอยู่
+กด &quot;ค้นหา&quot; หรือ Shift เพื่อยกเิลิก</translation>
+<translation id="397105322502079400">กำลังคำนวณ...</translation>
+<translation id="158849752021629804">ต้องใช้เครือข่ายในประเทศ</translation>
+<translation id="6857811139397017780">เปิดใช้งาน <ph name="NETWORKSERVICE"/></translation>
+<translation id="5864471791310927901">การค้นหา DHCP ล้มเหลว</translation>
+<translation id="5812035014844949013">เอาต์พุต</translation>
+<translation id="6692173217867674490">ข้อความรหัสผ่านไม่ถูกต้อง</translation>
+<translation id="6165508094623778733">เรียนรู้เพิ่มเติม</translation>
+<translation id="9046895021617826162">การเชื่อมต่อล้มเหลว</translation>
+<translation id="973896785707726617">เซสชันนี้จะสิ้นสุดใน <ph name="SESSION_TIME_REMAINING"/> ระบบจะลงชื่อออกให้คุณโดยอัตโนมัติ</translation>
+<translation id="8372369524088641025">คีย์ WEP ไม่ถูกต้อง</translation>
+<translation id="6636709850131805001">สถานะที่ไม่รู้จัก</translation>
+<translation id="3573179567135747900">เปลี่ยนกลับเป็น &quot;<ph name="FROM_LOCALE"/>&quot; (จำเป็นต้องรีสตาร์ต)</translation>
+<translation id="8103386449138765447">ข้อความ SMS: <ph name="MESSAGE_COUNT"/></translation>
+<translation id="5045002648206642691">การตั้งค่า Google ไดรฟ์...</translation>
+<translation id="1510238584712386396">ตัวเรียกใช้งาน</translation>
+<translation id="7209101170223508707">CAPS LOCK เปิดอยู่
+กด Alt+ค้นหา หรือ Shift เพื่อยกเลิก</translation>
+<translation id="8940956008527784070">แบตเตอรี่ต่ำ (<ph name="PERCENTAGE"/>%)</translation>
+<translation id="5102001756192215136">เหลืออีก <ph name="HOUR"/>:<ph name="MINUTE"/></translation>
+<translation id="520760366042891468">กำลังแชร์การควบคุมหน้าจอผ่านแฮงเอาท์</translation>
+<translation id="8000066093800657092">ไม่มีเครือข่าย</translation>
+<translation id="4015692727874266537">ลงชื่อเข้าใช้บัญชีอื่น...</translation>
+<translation id="5941711191222866238">ย่อ</translation>
+<translation id="6911468394164995108">เชื่อมต่อเครือข่ายอื่น...</translation>
+<translation id="412065659894267608">อีก <ph name="HOUR"/>ชม. <ph name="MINUTE"/>นาทีจึงจะเต็ม</translation>
+<translation id="6359806961507272919">SMS จาก <ph name="PHONE_NUMBER"/></translation>
+<translation id="1244147615850840081">ผู้ให้บริการ</translation>
+</translationbundle> \ No newline at end of file
diff --git a/chromium/ash/strings/ash_strings_tr.xtb b/chromium/ash/strings/ash_strings_tr.xtb
new file mode 100644
index 00000000000..b6580301792
--- /dev/null
+++ b/chromium/ash/strings/ash_strings_tr.xtb
@@ -0,0 +1,201 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="tr">
+<translation id="3595596368722241419">Pil tam dolu</translation>
+<translation id="5250713215130379958">Başlatıcıyı otomatik gizle</translation>
+<translation id="7814236020522506259"><ph name="HOUR"/> ve <ph name="MINUTE"/></translation>
+<translation id="7880025619322806991">Portal durumu</translation>
+<translation id="30155388420722288">Taşma Düğmesi</translation>
+<translation id="5571066253365925590">Bluetooth etkin</translation>
+<translation id="9074739597929991885">Bluetooth</translation>
+<translation id="2268130516524549846">Bluetooth devre dışı</translation>
+<translation id="3775358506042162758">Çoklu oturum açmada en fazla üç hesabınız olabilir.</translation>
+<translation id="370649949373421643">Kablosuzu Etkinleştir</translation>
+<translation id="3626281679859535460">Parlaklık</translation>
+<translation id="8054466585765276473">Pilin süresi hesaplanıyor.</translation>
+<translation id="7982789257301363584">Ağ</translation>
+<translation id="5565793151875479467">Proxy...</translation>
+<translation id="938582441709398163">Klavye Yer Paylaşımı</translation>
+<translation id="4387004326333427325">Kimlik doğrulama sertifikası uzaktan reddedildi</translation>
+<translation id="6979158407327259162">Google Drive</translation>
+<translation id="6943836128787782965">HTTP get işlevi başarısız oldu</translation>
+<translation id="2297568595583585744">Durum tepsisi</translation>
+<translation id="1661867754829461514">PIN eksik</translation>
+<translation id="4508225577814909926"><ph name="NAME"/>: Bağlanıyor...</translation>
+<translation id="4237016987259239829">Ağ Bağlantısı Hatası</translation>
+<translation id="2946640296642327832">Bluetooth'u etkinleştir</translation>
+<translation id="6459472438155181876">Ekran şuraya genişletiliyor: <ph name="DISPLAY_NAME"/></translation>
+<translation id="8206859287963243715">Cep telefonu</translation>
+<translation id="6596816719288285829">IP Adresi</translation>
+<translation id="4508265954913339219">Etkinleştirme başarısız oldu</translation>
+<translation id="3621712662352432595">Ses Ayarları</translation>
+<translation id="1812696562331527143">Giriş yönteminiz <ph name="INPUT_METHOD_ID"/>*(<ph name="BEGIN_LINK"/>3. taraf<ph name="END_LINK"/>) olarak değiştirildi.
+Geçiş yapmak için ÜstKrktr + Alt tuşlarına basın.</translation>
+<translation id="2127372758936585790">Düşük güçlü şarj cihazı</translation>
+<translation id="3846575436967432996">Hiçbir ağ bilgisi yok</translation>
+<translation id="3026237328237090306">Mobil verileri ayarla</translation>
+<translation id="785750925697875037">Mobil hesabı görüntüle</translation>
+<translation id="153454903766751181">Hücresel modem başlatılıyor...</translation>
+<translation id="4628814525959230255">Ekranınızın denetimi Hangouts üzerinden <ph name="HELPER_NAME"/> ile paylaşılıyor.</translation>
+<translation id="8343941333792395995"><ph name="DISPLAY_NAME"/> döndürüldü</translation>
+<translation id="7864539943188674973">Bluetooth'u devre dışı bırak</translation>
+<translation id="939252827960237676">Ekran görüntüsü kaydedilemedi</translation>
+<translation id="3126069444801937830">Güncellemek için yeniden başlat</translation>
+<translation id="2268813581635650749">Tüm kullanıcıların oturumunu kapat</translation>
+<translation id="735745346212279324">VPN bağlantısı kesildi</translation>
+<translation id="7320906967354320621">Boşta</translation>
+<translation id="6303423059719347535">Pil %<ph name="PERCENTAGE"/> dolu</translation>
+<translation id="15373452373711364">Büyük fare imleci</translation>
+<translation id="2778346081696727092">Sağlanan kullanıcı adı ve şifreyle kimlik doğrulanamadı</translation>
+<translation id="3294437725009624529">Misafir</translation>
+<translation id="8190698733819146287">Dilleri ve girişi özelleştir...</translation>
+<translation id="2903907270192926896">GİRİŞ</translation>
+<translation id="8676770494376880701">Düşük güçlü şarj cihazı bağlandı</translation>
+<translation id="7170041865419449892">Aralık dışında</translation>
+<translation id="4804818685124855865">Bağlantıyı kes</translation>
+<translation id="2544853746127077729">Kimlik doğrulama sertifikası ağ tarafından reddedildi</translation>
+<translation id="5222676887888702881">Çıkış</translation>
+<translation id="2688477613306174402">Yapılandırma</translation>
+<translation id="1272079795634619415">Durdur</translation>
+<translation id="4957722034734105353">Daha fazla bilgi edinin...</translation>
+<translation id="2964193600955408481">Kablosuz bağlantıyı devre dışı bırak</translation>
+<translation id="811680302244032017">Cihaz ekle...</translation>
+<translation id="4279490309300973883">Yansıtılıyor</translation>
+<translation id="2509468283778169019">CAPS LOCK açık</translation>
+<translation id="3892641579809465218">Dahili Ekran</translation>
+<translation id="7823564328645135659">Ayarlarınız senkronize edildikten sonra &quot;<ph name="FROM_LOCALE"/>&quot; olan dil &quot;<ph name="TO_LOCALE"/>&quot; olarak değiştirildi.</translation>
+<translation id="3368922792935385530">Bağlı</translation>
+<translation id="8340999562596018839">Sesli geri bildirim</translation>
+<translation id="8654520615680304441">Kablosuz'u aç...</translation>
+<translation id="5825747213122829519">Giriş yönteminiz <ph name="INPUT_METHOD_ID"/> olarak değiştirildi.
+Geçiş yapmak için ÜstKrktr + Alt tuşlarına basın.</translation>
+<translation id="2562916301614567480">Özel Ağ</translation>
+<translation id="6549021752953852991">Kullanılabilir hücresel ağ yok</translation>
+<translation id="4379753398862151997">Sevgili Monitör, aramızdaki bu ilişki yürümüyor. (Bu monitör desteklenmiyor)</translation>
+<translation id="6426039856985689743">Mobil verileri devre dışı bırak</translation>
+<translation id="3087734570205094154">Alt</translation>
+<translation id="3742055079367172538">Ekran görüntüsü alındı</translation>
+<translation id="8878886163241303700">Genişletilmiş ekran</translation>
+<translation id="5271016907025319479">VPN yapılandırılmadı.</translation>
+<translation id="372094107052732682">Çıkmak için Ctrl+ÜstKrktr+Q tuşlarına iki kez basın.</translation>
+<translation id="6803622936009808957">Desteklenen bir çözünürlük bulunamadığı için ekranlar yansıtılamıyor. Bunun yerine genişletilmiş masaüstüne geçiliyor.</translation>
+<translation id="1480041086352807611">Demo modu</translation>
+<translation id="3626637461649818317">%<ph name="PERCENTAGE"/> kaldı</translation>
+<translation id="9089416786594320554">Giriş yöntemleri</translation>
+<translation id="6247708409970142803">%<ph name="PERCENTAGE"/></translation>
+<translation id="2614835198358683673">Chromebook'unuz açıkken şarj edilemeyebilir. Orijinal şarj cihazını kullanmayı düşünün.</translation>
+<translation id="1895658205118569222">Kapat</translation>
+<translation id="4430019312045809116">Ses</translation>
+<translation id="4442424173763614572">DNS arama başarısız oldu</translation>
+<translation id="6356500677799115505">Pil dolu ve şarj oluyor.</translation>
+<translation id="7874779702599364982">Hücresel ağlar aranıyor...</translation>
+<translation id="583281660410589416">Bilinmiyor</translation>
+<translation id="1383876407941801731">Arama</translation>
+<translation id="7468789844759750875">Daha fazla veri satın almak için <ph name="NAME"/> etkinleştirme portalını ziyaret edin.</translation>
+<translation id="3901991538546252627"><ph name="NAME"/> ağına bağlanılıyor</translation>
+<translation id="2204305834655267233">Ağ Bilgisi</translation>
+<translation id="1621499497873603021">Pilin boşalması için kalan süre, <ph name="TIME_LEFT"/></translation>
+<translation id="5980301590375426705">Misafir oturumundan çık</translation>
+<translation id="4471417012762451363">Pil %<ph name="PERCENTAGE"/> dolu ve şarj oluyor</translation>
+<translation id="8308637677604853869">Önceki menü</translation>
+<translation id="4666297444214622512">Başka bir hesapta oturum açılamıyor.</translation>
+<translation id="1346748346194534595">Sağa</translation>
+<translation id="1773212559869067373">Kimlik doğrulama sertifikası yerel olarak reddedildi</translation>
+<translation id="8528322925433439945">Mobile...</translation>
+<translation id="7049357003967926684">İlişki</translation>
+<translation id="8428213095426709021">Ayarlar</translation>
+<translation id="2372145515558759244">Uygulamalar senkronize ediliyor...</translation>
+<translation id="7256405249507348194">Tanınmayan hata: <ph name="DESC"/></translation>
+<translation id="7925247922861151263">AAA kontrolü başarısız oldu</translation>
+<translation id="8456362689280298700">Dolması için gereken süre: <ph name="HOUR"/>:<ph name="MINUTE"/> </translation>
+<translation id="5787281376604286451">Sesli geri bildirim etkin.
+Devre dışı bırakmak için Ctrl+Alt+Z tuşlarına basın.</translation>
+<translation id="4479639480957787382">Ethernet</translation>
+<translation id="6312403991423642364">Bilinmeyen ağ hatası</translation>
+<translation id="1467432559032391204">Sola</translation>
+<translation id="5543001071567407895">SMS</translation>
+<translation id="2354174487190027830"><ph name="NAME"/> etkinleştiriliyor</translation>
+<translation id="8814190375133053267">Kablosuz</translation>
+<translation id="1398853756734560583">Büyüt</translation>
+<translation id="2692809339924654275"><ph name="BLUETOOTH"/>: Bağlanıyor...</translation>
+<translation id="252373100621549798">Bilinmeyen Görünüm</translation>
+<translation id="1882897271359938046">Şuraya yansıtılıyor: <ph name="DISPLAY_NAME"/></translation>
+<translation id="2727977024730340865">Düşük güçlü bir şarj cihazına takıldı. Şarj durumu güvenilir olmayabilir.</translation>
+<translation id="3784455785234192852">Kilitle</translation>
+<translation id="2805756323405976993">Uygulamalar</translation>
+<translation id="8871072142849158571"><ph name="DISPLAY_NAME"/> ekranı <ph name="RESOLUTION"/> olarak yeniden boyutlandırıldı</translation>
+<translation id="1512064327686280138">Etkinleştirme hatası</translation>
+<translation id="5097002363526479830">'<ph name="NAME"/>' ağına bağlanamadı: <ph name="DETAILS"/></translation>
+<translation id="1850504506766569011">Kablosuz kapalı.</translation>
+<translation id="8132793192354020517"><ph name="NAME"/> ağına bağlanıldı</translation>
+<translation id="7052914147756339792">Duvar kağıdını ayarla...</translation>
+<translation id="8678698760965522072">Çevrimiçi durumu</translation>
+<translation id="2532589005999780174">Yüksek kontrast modu</translation>
+<translation id="1119447706177454957">Dahili hata</translation>
+<translation id="3019353588588144572">Pilin tam olarak şarj olması için kalan süre, <ph name="TIME_REMAINING"/></translation>
+<translation id="3473479545200714844">Ekran büyüteci</translation>
+<translation id="7005812687360380971">Hata</translation>
+<translation id="882279321799040148">Görüntülemek için tıklayın</translation>
+<translation id="5045550434625856497">Yanlış şifre</translation>
+<translation id="1602076796624386989">Mobil verileri etkinleştir</translation>
+<translation id="6981982820502123353">Erişilebilirlik</translation>
+<translation id="3157931365184549694">Geri yükle</translation>
+<translation id="4274292172790327596">Tanınmayan hata</translation>
+<translation id="4032485810211612751"><ph name="HOURS"/>:<ph name="MINUTES"/>:<ph name="SECONDS"/></translation>
+<translation id="225680501294068881">Cihazlar taranıyor...</translation>
+<translation id="5597451508971090205"><ph name="SHORT_WEEKDAY"/>, <ph name="DATE"/></translation>
+<translation id="4448844063988177157">Kablosuz ağlar aranıyor...</translation>
+<translation id="8401662262483418323">Şununla bağlantı kurulamadı: &quot;<ph name="NAME"/>&quot;: <ph name="DETAILS"/>
+Sunucu mesajı: <ph name="SERVER_MSG"/></translation>
+<translation id="2475982808118771221">Bir hata oluştu</translation>
+<translation id="7229570126336867161">EVDO gerekli</translation>
+<translation id="2999742336789313416"><ph name="DISPLAY_NAME"/>, <ph name="DOMAIN"/> tarafından yönetilen herkese açık bir oturumdur</translation>
+<translation id="7029814467594812963">Oturumdan çık</translation>
+<translation id="8454013096329229812">Kablosuz açık.</translation>
+<translation id="4872237917498892622">Alt+Arama veya Üst Karakter</translation>
+<translation id="2983818520079887040">Ayarlar...</translation>
+<translation id="1717216362413677834">Yuva modu</translation>
+<translation id="8927026611342028580">Bağlantı İstendi</translation>
+<translation id="8300849813060516376">OTASP başarısız oldu</translation>
+<translation id="2792498699870441125">Alt+Arama</translation>
+<translation id="8660803626959853127"><ph name="COUNT"/> dosya senkronize ediliyor</translation>
+<translation id="3709443003275901162">9+</translation>
+<translation id="639644700271529076">CAPS LOCK kapalı</translation>
+<translation id="6248847161401822652">Çıkmak için Ctrl+ÜstKrktr+Q tuşlarına iki kez basın.</translation>
+<translation id="6267036997247669271"><ph name="NAME"/>: Etkinleştiriliyor...</translation>
+<translation id="1391854757121130358">Mobil veri hakkınızı bitirmiş olabilirsiniz.</translation>
+<translation id="5413208160176941586">Yerel olarak yönetilen kullanıcı</translation>
+<translation id="1059194134494239015"><ph name="DISPLAY_NAME"/>: <ph name="RESOLUTION"/></translation>
+<translation id="4864165860509564259">Başlatıcı konumu</translation>
+<translation id="7593891976182323525">Arama veya Üst Karakter</translation>
+<translation id="7649070708921625228">Yardım</translation>
+<translation id="3050422059534974565">CAPS LOCK açık.
+İptal için Arama veya Üst Karakter tuşlarına basın.</translation>
+<translation id="397105322502079400">Hesaplanııyor...</translation>
+<translation id="158849752021629804">Ev ağı gerekli</translation>
+<translation id="6857811139397017780">Şunu etkinleştir: <ph name="NETWORKSERVICE"/></translation>
+<translation id="5864471791310927901">DHCP araması başarısız oldu</translation>
+<translation id="5812035014844949013">ÇIKIŞ</translation>
+<translation id="6692173217867674490">Hatalı parola</translation>
+<translation id="6165508094623778733">Daha fazla bilgi edinin</translation>
+<translation id="9046895021617826162">Bağlantı başarısız oldu</translation>
+<translation id="973896785707726617">Bu oturum <ph name="SESSION_TIME_REMAINING"/> içinde sona erecek. Oturumunuz otomatik olarak kapatılacaktır.</translation>
+<translation id="8372369524088641025">Hatalı WEP anahtarı</translation>
+<translation id="6636709850131805001">Tanınmayan durum</translation>
+<translation id="3573179567135747900">Tekrar &quot;<ph name="FROM_LOCALE"/>&quot; ayarına dön (yeniden başlatmak gerekir)</translation>
+<translation id="8103386449138765447">SMS mesajları: <ph name="MESSAGE_COUNT"/></translation>
+<translation id="5045002648206642691">Google Drive ayarları...</translation>
+<translation id="1510238584712386396">Başlatıcı</translation>
+<translation id="7209101170223508707">CAPS LOCK açık.
+İptal için Alt+Arama veya Üst Karakter tuşlarına basın.</translation>
+<translation id="8940956008527784070">Pil gücü az (%<ph name="PERCENTAGE"/>)</translation>
+<translation id="5102001756192215136"><ph name="HOUR"/>:<ph name="MINUTE"/> kaldı</translation>
+<translation id="520760366042891468">Ekranınızın denetimi Hangouts üzerinden paylaşılıyor.</translation>
+<translation id="8000066093800657092">Ağ yok</translation>
+<translation id="4015692727874266537">Başka bir hesapta oturum açın...</translation>
+<translation id="5941711191222866238">Simge durumuna küçült</translation>
+<translation id="6911468394164995108">Başka ağa katıl...</translation>
+<translation id="412065659894267608">Tam dolana kadar <ph name="HOUR"/> sa <ph name="MINUTE"/> dk var</translation>
+<translation id="6359806961507272919"><ph name="PHONE_NUMBER"/> numaradan SMS alındı</translation>
+<translation id="1244147615850840081">Operatör</translation>
+</translationbundle> \ No newline at end of file
diff --git a/chromium/ash/strings/ash_strings_uk.xtb b/chromium/ash/strings/ash_strings_uk.xtb
new file mode 100644
index 00000000000..711cb63da8f
--- /dev/null
+++ b/chromium/ash/strings/ash_strings_uk.xtb
@@ -0,0 +1,201 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="uk">
+<translation id="3595596368722241419">Акумулятор заряджено</translation>
+<translation id="5250713215130379958">Автоматично ховати панель запуску</translation>
+<translation id="7814236020522506259"><ph name="HOUR"/> і <ph name="MINUTE"/></translation>
+<translation id="7880025619322806991">Стан порталу</translation>
+<translation id="30155388420722288">Кнопка переповнення</translation>
+<translation id="5571066253365925590">Bluetooth увімкнено</translation>
+<translation id="9074739597929991885">Bluetooth</translation>
+<translation id="2268130516524549846">Bluetooth вимкнено</translation>
+<translation id="3775358506042162758">Паралельно можна входити не більше ніж у три облікові записи.</translation>
+<translation id="370649949373421643">Увімкнути Wi-Fi</translation>
+<translation id="3626281679859535460">Яскравість</translation>
+<translation id="8054466585765276473">Обчислення часу роботи акумулятора.</translation>
+<translation id="7982789257301363584">Мережа</translation>
+<translation id="5565793151875479467">Проксі-сервер...</translation>
+<translation id="938582441709398163">Розкладка клавіатури</translation>
+<translation id="4387004326333427325">Сертифікат автентифікації відхилений дистанційно</translation>
+<translation id="6979158407327259162">Диск Google</translation>
+<translation id="6943836128787782965">Помилка HTTP</translation>
+<translation id="2297568595583585744">Контейтер стану</translation>
+<translation id="1661867754829461514">Відсутній PIN-код</translation>
+<translation id="4508225577814909926"><ph name="NAME"/>: Під’єднання…</translation>
+<translation id="4237016987259239829">Помилка з'єднання з мережею</translation>
+<translation id="2946640296642327832">Увімкнути Bluetooth</translation>
+<translation id="6459472438155181876">Розширення екрана на <ph name="DISPLAY_NAME"/></translation>
+<translation id="8206859287963243715">Мобільний</translation>
+<translation id="6596816719288285829">ІР-адреса</translation>
+<translation id="4508265954913339219">Помилка активації</translation>
+<translation id="3621712662352432595">Налаштування звуку</translation>
+<translation id="1812696562331527143">Метод введення змінено на <ph name="INPUT_METHOD_ID"/>* (<ph name="BEGIN_LINK"/>третя сторона<ph name="END_LINK"/>).
+Щоб переключитися, натисніть Shift + Alt.</translation>
+<translation id="2127372758936585790">Зарядний пристрій низької потужності</translation>
+<translation id="3846575436967432996">Інформація про мережу не доступна</translation>
+<translation id="3026237328237090306">Налаштувати передавання мобільних даних</translation>
+<translation id="785750925697875037">Переглянути обліковий запис для мобільних пристроїв</translation>
+<translation id="153454903766751181">Ініціалізація мобільного модема…</translation>
+<translation id="4628814525959230255">Спільний доступ до екрана для користувача <ph name="HELPER_NAME"/> через Hangouts.</translation>
+<translation id="8343941333792395995">Екран <ph name="DISPLAY_NAME"/> обернено</translation>
+<translation id="7864539943188674973">Вимкнути Bluetooth</translation>
+<translation id="939252827960237676">Не вдалося зберегти знімок екрана</translation>
+<translation id="3126069444801937830">Перезапустіть, щоб оновити</translation>
+<translation id="2268813581635650749">Вийти з усіх облікових записів</translation>
+<translation id="735745346212279324">VPN від’єднано</translation>
+<translation id="7320906967354320621">Простій</translation>
+<translation id="6303423059719347535">Акумулятор заряджений на <ph name="PERCENTAGE"/>%</translation>
+<translation id="15373452373711364">Великий курсор миші</translation>
+<translation id="2778346081696727092">Помилка автентифікації за допомогою введеного імені користувача чи пароля</translation>
+<translation id="3294437725009624529">Гість</translation>
+<translation id="8190698733819146287">Налаштувати мови та введення тексту...</translation>
+<translation id="2903907270192926896">ВХІД</translation>
+<translation id="8676770494376880701">Зарядний пристрій низької потужності підключено</translation>
+<translation id="7170041865419449892">За межами досяжності</translation>
+<translation id="4804818685124855865">Від'єднатися</translation>
+<translation id="2544853746127077729">Сертифікат автентифікації відхилений мережею</translation>
+<translation id="5222676887888702881">Вийти</translation>
+<translation id="2688477613306174402">Конфігурація</translation>
+<translation id="1272079795634619415">Зупинити</translation>
+<translation id="4957722034734105353">Докладніше...</translation>
+<translation id="2964193600955408481">Вимкнути Wi-Fi</translation>
+<translation id="811680302244032017">Додати пристрій...</translation>
+<translation id="4279490309300973883">Дзеркальне відображення</translation>
+<translation id="2509468283778169019">Режим CAPS LOCK увімкнено</translation>
+<translation id="3892641579809465218">Внутрішній екран</translation>
+<translation id="7823564328645135659">Після синхронізації налаштувань мову змінено. Попередня: <ph name="FROM_LOCALE"/>, нова: <ph name="TO_LOCALE"/>.</translation>
+<translation id="3368922792935385530">Підключено</translation>
+<translation id="8340999562596018839">Голосові підказки</translation>
+<translation id="8654520615680304441">Увімкнення Wi-Fi…</translation>
+<translation id="5825747213122829519">Метод введення змінено на <ph name="INPUT_METHOD_ID"/>.
+Щоб переключитися, натисніть Shift + Alt.</translation>
+<translation id="2562916301614567480">Приватна мережа</translation>
+<translation id="6549021752953852991">Мобільна мережа недоступна</translation>
+<translation id="4379753398862151997">Прикро, але контакту з монітором немає. (Цей монітор не підтримується)</translation>
+<translation id="6426039856985689743">Вимкнути передавання мобільних даних</translation>
+<translation id="3087734570205094154">Низ</translation>
+<translation id="3742055079367172538">Знімок екрана зроблено</translation>
+<translation id="8878886163241303700">Розширення екрана</translation>
+<translation id="5271016907025319479">VPN не налаштовано.</translation>
+<translation id="372094107052732682">Двічі натисніть комбінацію клавіш Ctrl+Shift+Q, щоб вийти.</translation>
+<translation id="6803622936009808957">Не вдалося дублювати зображення екранів, оскільки не знайдено підтримувані значення роздільної здатності. Натомість запущено режим розширеного робочого столу.</translation>
+<translation id="1480041086352807611">Демонстраційний режим</translation>
+<translation id="3626637461649818317">Залишилося <ph name="PERCENTAGE"/>%</translation>
+<translation id="9089416786594320554">Методи введення</translation>
+<translation id="6247708409970142803"><ph name="PERCENTAGE"/>%</translation>
+<translation id="2614835198358683673">Ваш Chromebook може не заряджатися, якщо ввімкнений. Спробуйте скористатися офіційним зарядним пристроєм.</translation>
+<translation id="1895658205118569222">Завершення роботи</translation>
+<translation id="4430019312045809116">Гучність</translation>
+<translation id="4442424173763614572">Помилка пошуку DNS</translation>
+<translation id="6356500677799115505">Акумулятор заряджений і продовжує заряджатися.</translation>
+<translation id="7874779702599364982">Пошук мобільних мереж…</translation>
+<translation id="583281660410589416">Невідомо</translation>
+<translation id="1383876407941801731">Пошук</translation>
+<translation id="7468789844759750875">Перейти на портал активації <ph name="NAME"/>, щоб придбати більший обсяг даних.</translation>
+<translation id="3901991538546252627">Під’єднання до <ph name="NAME"/></translation>
+<translation id="2204305834655267233">Інформація про мережу</translation>
+<translation id="1621499497873603021">До розрядження акумулятора залишилося <ph name="TIME_LEFT"/></translation>
+<translation id="5980301590375426705">Завершити сеанс у режимі гостя</translation>
+<translation id="4471417012762451363">Акумулятор заряджений на <ph name="PERCENTAGE"/>% і продовжує заряджатися</translation>
+<translation id="8308637677604853869">Попереднє меню</translation>
+<translation id="4666297444214622512">Неможливо ввійти в інший обліковий запис.</translation>
+<translation id="1346748346194534595">Праворуч</translation>
+<translation id="1773212559869067373">Сертифікат автентифікації відхилений локально</translation>
+<translation id="8528322925433439945">Мобільні ...</translation>
+<translation id="7049357003967926684">Пов'язування</translation>
+<translation id="8428213095426709021">Налаштування</translation>
+<translation id="2372145515558759244">Синхронізація програм...</translation>
+<translation id="7256405249507348194">Нерозпізнана помилка: <ph name="DESC"/></translation>
+<translation id="7925247922861151263">Помилка перевірки AAA</translation>
+<translation id="8456362689280298700"><ph name="HOUR"/>:<ph name="MINUTE"/> до повного зарядження</translation>
+<translation id="5787281376604286451">Голосові підказки ввімкнено.
+Натисніть Ctrl+Alt+Z, щоб вимкнути.</translation>
+<translation id="4479639480957787382">Ethernet</translation>
+<translation id="6312403991423642364">Невідома помилка мережі</translation>
+<translation id="1467432559032391204">Ліворуч</translation>
+<translation id="5543001071567407895">SMS</translation>
+<translation id="2354174487190027830">Активація <ph name="NAME"/></translation>
+<translation id="8814190375133053267">Wi-Fi</translation>
+<translation id="1398853756734560583">Збільшити</translation>
+<translation id="2692809339924654275"><ph name="BLUETOOTH"/>: Під’єднання…</translation>
+<translation id="252373100621549798">Невідомий дисплей</translation>
+<translation id="1882897271359938046">Дзеркалювання на <ph name="DISPLAY_NAME"/></translation>
+<translation id="2727977024730340865">Підключено до зарядного пристрою низької потужності. Акумулятор може заряджатися неналежним чином.</translation>
+<translation id="3784455785234192852">Заблокувати</translation>
+<translation id="2805756323405976993">Програми</translation>
+<translation id="8871072142849158571">Роздільну здатність екрана <ph name="DISPLAY_NAME"/> змінено на <ph name="RESOLUTION"/></translation>
+<translation id="1512064327686280138">Помилка активації</translation>
+<translation id="5097002363526479830">Не вдалося під’єднатися до мережі &quot;<ph name="NAME"/>&quot;: <ph name="DETAILS"/></translation>
+<translation id="1850504506766569011">Wi-Fi вимкнено.</translation>
+<translation id="8132793192354020517">З'єднано з <ph name="NAME"/></translation>
+<translation id="7052914147756339792">Установити фоновий малюнок...</translation>
+<translation id="8678698760965522072">Статус онлайн</translation>
+<translation id="2532589005999780174">Режим високого контрасту</translation>
+<translation id="1119447706177454957">Внутрішня помилка</translation>
+<translation id="3019353588588144572">До повного зарядження акумулятора залишилося <ph name="TIME_REMAINING"/></translation>
+<translation id="3473479545200714844">Лупа</translation>
+<translation id="7005812687360380971">Помилка</translation>
+<translation id="882279321799040148">Натисніть, щоб переглянути</translation>
+<translation id="5045550434625856497">Неправильний пароль</translation>
+<translation id="1602076796624386989">Увімкнути передавання мобільних даних</translation>
+<translation id="6981982820502123353">Доступність</translation>
+<translation id="3157931365184549694">Відновити</translation>
+<translation id="4274292172790327596">Нерозпізнана помилка</translation>
+<translation id="4032485810211612751"><ph name="HOURS"/>:<ph name="MINUTES"/>:<ph name="SECONDS"/></translation>
+<translation id="225680501294068881">Пошук пристроїв...</translation>
+<translation id="5597451508971090205"><ph name="SHORT_WEEKDAY"/>, <ph name="DATE"/></translation>
+<translation id="4448844063988177157">Пошук Wi-Fi мереж...</translation>
+<translation id="8401662262483418323">Помилка під’єднання до мережі &quot;<ph name="NAME"/>&quot;: <ph name="DETAILS"/>
+Повідомлення сервера: <ph name="SERVER_MSG"/></translation>
+<translation id="2475982808118771221">Сталася помилка</translation>
+<translation id="7229570126336867161">Потрібна мережа EVDO</translation>
+<translation id="2999742336789313416"><ph name="DISPLAY_NAME"/> – загальнодоступний сеанс, керований доменом <ph name="DOMAIN"/></translation>
+<translation id="7029814467594812963">Завершити сеанс</translation>
+<translation id="8454013096329229812">Wi-Fi увімкнено.</translation>
+<translation id="4872237917498892622">Alt+клавіша пошуку або Shift</translation>
+<translation id="2983818520079887040">Налаштування...</translation>
+<translation id="1717216362413677834">Режим док-станції</translation>
+<translation id="8927026611342028580">Подано запит на під’єднання</translation>
+<translation id="8300849813060516376">Помилка OTASP</translation>
+<translation id="2792498699870441125">Alt+клавіша пошуку</translation>
+<translation id="8660803626959853127">Синхронізація файлів (<ph name="COUNT"/>)</translation>
+<translation id="3709443003275901162">9+</translation>
+<translation id="639644700271529076">Режим CAPS LOCK вимкнено</translation>
+<translation id="6248847161401822652">Двічі натисніть комбінацію клавіш Control Shift Q, щоб вийти.</translation>
+<translation id="6267036997247669271"><ph name="NAME"/>: активація…</translation>
+<translation id="1391854757121130358">Можливо, ви використали свій ліміт мобільного передавання даних.</translation>
+<translation id="5413208160176941586">Локально керований користувач</translation>
+<translation id="1059194134494239015"><ph name="DISPLAY_NAME"/>: <ph name="RESOLUTION"/></translation>
+<translation id="4864165860509564259">Положення панелі запуску</translation>
+<translation id="7593891976182323525">Клавіша пошуку або Shift</translation>
+<translation id="7649070708921625228">Довідка</translation>
+<translation id="3050422059534974565">Клавішу CAPS LOCK увімкнено.
+Натисніть клавішу пошуку або Shift, щоб скасувати.</translation>
+<translation id="397105322502079400">Обчислення...</translation>
+<translation id="158849752021629804">Потрібна домашня мережа</translation>
+<translation id="6857811139397017780">Активувати <ph name="NETWORKSERVICE"/></translation>
+<translation id="5864471791310927901">Помилка пошуку DHCP</translation>
+<translation id="5812035014844949013">ВИХІД</translation>
+<translation id="6692173217867674490">Погана парольна фраза</translation>
+<translation id="6165508094623778733">Докладніше</translation>
+<translation id="9046895021617826162">Помилка з'єднання</translation>
+<translation id="973896785707726617">Сеанс закінчиться за <ph name="SESSION_TIME_REMAINING"/>. Ви вийдете автоматично.</translation>
+<translation id="8372369524088641025">Поганий WEP-ключ</translation>
+<translation id="6636709850131805001">Нерозпізнаний стан</translation>
+<translation id="3573179567135747900">Повернутися до мови &quot;<ph name="FROM_LOCALE"/>&quot; (потрібно перезавантажитися)</translation>
+<translation id="8103386449138765447">SMS повідомлень: <ph name="MESSAGE_COUNT"/></translation>
+<translation id="5045002648206642691">Налаштування Диска Google...</translation>
+<translation id="1510238584712386396">Панель запуску</translation>
+<translation id="7209101170223508707">Клавішу CAPS LOCK увімкнено.
+Натисніть Alt+клавішу пошуку або Shift, щоб скасувати.</translation>
+<translation id="8940956008527784070">Низький заряд акумулятора (<ph name="PERCENTAGE"/>%)</translation>
+<translation id="5102001756192215136">Залишилося <ph name="HOUR"/>:<ph name="MINUTE"/></translation>
+<translation id="520760366042891468">Спільний доступ до екрана через Hangouts.</translation>
+<translation id="8000066093800657092">Немає мережі</translation>
+<translation id="4015692727874266537">Увійти в інший обліковий запис…</translation>
+<translation id="5941711191222866238">Зменшити</translation>
+<translation id="6911468394164995108">Під’єднатися до іншої...</translation>
+<translation id="412065659894267608"><ph name="HOUR"/> год. <ph name="MINUTE"/> хв. до повного зарядження</translation>
+<translation id="6359806961507272919">SMS-повідомлення від <ph name="PHONE_NUMBER"/></translation>
+<translation id="1244147615850840081">Постачальник</translation>
+</translationbundle> \ No newline at end of file
diff --git a/chromium/ash/strings/ash_strings_vi.xtb b/chromium/ash/strings/ash_strings_vi.xtb
new file mode 100644
index 00000000000..b0affdb44ce
--- /dev/null
+++ b/chromium/ash/strings/ash_strings_vi.xtb
@@ -0,0 +1,201 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="vi">
+<translation id="3595596368722241419">Pin đầy</translation>
+<translation id="5250713215130379958">Tự động ẩn trình khởi chạy</translation>
+<translation id="7814236020522506259"><ph name="HOUR"/> và <ph name="MINUTE"/></translation>
+<translation id="7880025619322806991">Trạng thái cổng</translation>
+<translation id="30155388420722288">Nút tràn</translation>
+<translation id="5571066253365925590">Đã bật bluetooth</translation>
+<translation id="9074739597929991885">Bluetooth</translation>
+<translation id="2268130516524549846">Đã tắt bluetooth</translation>
+<translation id="3775358506042162758">Bạn chỉ có thể có tối đa ba tài khoản khi đăng nhập nhiều tài khoản.</translation>
+<translation id="370649949373421643">Bật Wi-Fi</translation>
+<translation id="3626281679859535460">Độ sáng</translation>
+<translation id="8054466585765276473">Đang tính toán thời lượng pin.</translation>
+<translation id="7982789257301363584">Mạng</translation>
+<translation id="5565793151875479467">Proxy...</translation>
+<translation id="938582441709398163">Lớp phủ bàn phím</translation>
+<translation id="4387004326333427325">Chứng chỉ xác thực bị từ chối từ xa</translation>
+<translation id="6979158407327259162">Google Drive</translation>
+<translation id="6943836128787782965">HTTP gặp lỗi</translation>
+<translation id="2297568595583585744">Khay trạng thái</translation>
+<translation id="1661867754829461514">Thiếu PIN</translation>
+<translation id="4508225577814909926"><ph name="NAME"/>: Đang kết nối...</translation>
+<translation id="4237016987259239829">Lỗi Kết nối Mạng</translation>
+<translation id="2946640296642327832">Bật bluetooth</translation>
+<translation id="6459472438155181876">Đang mở rộng màn hình tới <ph name="DISPLAY_NAME"/></translation>
+<translation id="8206859287963243715">Di động</translation>
+<translation id="6596816719288285829">Địa chỉ IP</translation>
+<translation id="4508265954913339219">Kích hoạt không thành công</translation>
+<translation id="3621712662352432595">Cài đặt âm thanh</translation>
+<translation id="1812696562331527143">Phương thức nhập của bạn đã thay đổi thành <ph name="INPUT_METHOD_ID"/>*(<ph name="BEGIN_LINK"/>bên thứ ba<ph name="END_LINK"/>).
+Nhấn Shift + Alt để chuyển đổi.</translation>
+<translation id="2127372758936585790">Bộ sạc công suất thấp</translation>
+<translation id="3846575436967432996">Không có thông tin mạng</translation>
+<translation id="3026237328237090306">Thiết lập dữ liệu di động</translation>
+<translation id="785750925697875037">Xem tài khoản di động</translation>
+<translation id="153454903766751181">Đang khởi chạy modem di động...</translation>
+<translation id="4628814525959230255">Chia sẻ quyền kiểm soát màn hình của bạn với <ph name="HELPER_NAME"/> qua Hangouts.</translation>
+<translation id="8343941333792395995"><ph name="DISPLAY_NAME"/> đã được xoay</translation>
+<translation id="7864539943188674973">Tắt bluetooth</translation>
+<translation id="939252827960237676">Không lưu được ảnh chụp màn hình</translation>
+<translation id="3126069444801937830">Khởi động lại để cập nhật</translation>
+<translation id="2268813581635650749">Đăng xuất tất cả</translation>
+<translation id="735745346212279324">Đã ngắt kết nối VPN</translation>
+<translation id="7320906967354320621">Rảnh</translation>
+<translation id="6303423059719347535">Pin <ph name="PERCENTAGE"/>% đầy</translation>
+<translation id="15373452373711364">Con trỏ chuột lớn</translation>
+<translation id="2778346081696727092">Không thể xác thực với tên người dùng và mật khẩu đã cung cấp</translation>
+<translation id="3294437725009624529">Khách</translation>
+<translation id="8190698733819146287">Tùy chỉnh ngôn ngữ và dữ liệu nhập...</translation>
+<translation id="2903907270192926896">ĐẦU VÀO</translation>
+<translation id="8676770494376880701">Đã kết nối bộ sạc công suất thấp</translation>
+<translation id="7170041865419449892">Ngoài vùng phủ sóng</translation>
+<translation id="4804818685124855865">Ngắt kết nối</translation>
+<translation id="2544853746127077729">Chứng chỉ xác thực bị mạng từ chối</translation>
+<translation id="5222676887888702881">Đăng xuất</translation>
+<translation id="2688477613306174402">Cấu hình</translation>
+<translation id="1272079795634619415">Dừng</translation>
+<translation id="4957722034734105353">Tìm hiểu thêm...</translation>
+<translation id="2964193600955408481">Tắt Wi-Fi</translation>
+<translation id="811680302244032017">Thêm thiết bị...</translation>
+<translation id="4279490309300973883">Đang phản chiếu</translation>
+<translation id="2509468283778169019">Đang bật CAPS LOCK</translation>
+<translation id="3892641579809465218">Màn hình nội bộ</translation>
+<translation id="7823564328645135659">Ngôn ngữ đã chuyển từ &quot;<ph name="FROM_LOCALE"/>&quot; thành &quot;<ph name="TO_LOCALE"/>&quot; sau khi đồng bộ hóa cài đặt của bạn.</translation>
+<translation id="3368922792935385530">Đã kết nối</translation>
+<translation id="8340999562596018839">Phản hồi nói</translation>
+<translation id="8654520615680304441">Bật Wi-Fi...</translation>
+<translation id="5825747213122829519">Phương thức nhập của bạn đã thay đổi thành <ph name="INPUT_METHOD_ID"/>.
+Nhấn Shift + Alt để chuyển đổi.</translation>
+<translation id="2562916301614567480">Mạng riêng</translation>
+<translation id="6549021752953852991">Không có mạng di động nào</translation>
+<translation id="4379753398862151997">Rất tiếc, hệ thống không hoạt động giữa hai màn hình. (Màn hình đó không được hỗ trợ)</translation>
+<translation id="6426039856985689743">Tắt dữ liệu di động</translation>
+<translation id="3087734570205094154">Bên dưới</translation>
+<translation id="3742055079367172538">Ảnh màn hình đã chụp</translation>
+<translation id="8878886163241303700">Mở rộng màn hình</translation>
+<translation id="5271016907025319479">Mạng riêng ảo không được định cấu hình.</translation>
+<translation id="372094107052732682">Nhấn Ctrl+Shift+Q hai lần để thoát.</translation>
+<translation id="6803622936009808957">Không thể phản chiếu màn hình do không tìm thấy độ phân giải được hỗ trợ. Thay vào đó, đã chuyển sang chế độ màn hình mở rộng.</translation>
+<translation id="1480041086352807611">Chế độ trình diễn</translation>
+<translation id="3626637461649818317">Còn lại <ph name="PERCENTAGE"/>%</translation>
+<translation id="9089416786594320554">Phương thức nhập</translation>
+<translation id="6247708409970142803"><ph name="PERCENTAGE"/>%</translation>
+<translation id="2614835198358683673">Chromebook của bạn có thể không sạc khi đang bật. Hãy xem xét sử dụng bộ sạc chính thức.</translation>
+<translation id="1895658205118569222">Tắt</translation>
+<translation id="4430019312045809116">Âm lượng</translation>
+<translation id="4442424173763614572">Tìm kiếm DNS không thành công</translation>
+<translation id="6356500677799115505">Pin đầy và đang sạc.</translation>
+<translation id="7874779702599364982">Đang tìm kiếm mạng di động...</translation>
+<translation id="583281660410589416">Không biết</translation>
+<translation id="1383876407941801731">Tìm kiếm</translation>
+<translation id="7468789844759750875">Truy cập cổng kích hoạt <ph name="NAME"/> để mua thêm dữ liệu.</translation>
+<translation id="3901991538546252627">Đang kết nối với <ph name="NAME"/></translation>
+<translation id="2204305834655267233">Thông tin mạng</translation>
+<translation id="1621499497873603021">Thời gian còn lại cho đến khi pin hết, <ph name="TIME_LEFT"/></translation>
+<translation id="5980301590375426705">Thoát khỏi phiên khách</translation>
+<translation id="4471417012762451363">Pin <ph name="PERCENTAGE"/>% đầy và đang sạc</translation>
+<translation id="8308637677604853869">Trình đơn trước</translation>
+<translation id="4666297444214622512">Không thể đăng nhập tài khoản khác.</translation>
+<translation id="1346748346194534595">Phải</translation>
+<translation id="1773212559869067373">Chứng chỉ xác thực bị từ chối cục bộ</translation>
+<translation id="8528322925433439945">Di động ...</translation>
+<translation id="7049357003967926684">Liên kết</translation>
+<translation id="8428213095426709021">Cài đặt</translation>
+<translation id="2372145515558759244">Đang đồng bộ hóa ứng dụng...</translation>
+<translation id="7256405249507348194">Lỗi không xác định: <ph name="DESC"/></translation>
+<translation id="7925247922861151263">Không thể kiểm tra AAA</translation>
+<translation id="8456362689280298700"><ph name="HOUR"/>:<ph name="MINUTE"/> cho đến khi sạc đầy</translation>
+<translation id="5787281376604286451">Phản hồi nói được bật.
+Nhấn Ctrl+Alt+Z để tắt.</translation>
+<translation id="4479639480957787382">Ethernet</translation>
+<translation id="6312403991423642364">Lỗi mạng không xác định</translation>
+<translation id="1467432559032391204">Trái</translation>
+<translation id="5543001071567407895">SMS</translation>
+<translation id="2354174487190027830">Kích hoạt <ph name="NAME"/></translation>
+<translation id="8814190375133053267">Wi-Fi</translation>
+<translation id="1398853756734560583">Phóng to</translation>
+<translation id="2692809339924654275"><ph name="BLUETOOTH"/>: Đang kết nối...</translation>
+<translation id="252373100621549798">Màn hình không xác định</translation>
+<translation id="1882897271359938046">Đang phản chiếu tới <ph name="DISPLAY_NAME"/></translation>
+<translation id="2727977024730340865">Được cắm vào bộ sạc công suất thấp. Việc sạc pin có thể không được đảm bảo.</translation>
+<translation id="3784455785234192852">Khóa</translation>
+<translation id="2805756323405976993">Ứng dụng</translation>
+<translation id="8871072142849158571"><ph name="DISPLAY_NAME"/> đã được đổi kích thước thành <ph name="RESOLUTION"/></translation>
+<translation id="1512064327686280138">Lỗi kích hoạt</translation>
+<translation id="5097002363526479830">Không thể kết nối với mạng '<ph name="NAME"/>': <ph name="DETAILS"/></translation>
+<translation id="1850504506766569011">Wi-Fi đang tắt.</translation>
+<translation id="8132793192354020517">Đã kết nối với <ph name="NAME"/></translation>
+<translation id="7052914147756339792">Đặt hình nền...</translation>
+<translation id="8678698760965522072">Trạng thái trực tuyến</translation>
+<translation id="2532589005999780174">Chế độ tương phản cao</translation>
+<translation id="1119447706177454957">Lỗi nội bộ</translation>
+<translation id="3019353588588144572">Thời gian còn lại cho đến khi pin được sạc đầy, <ph name="TIME_REMAINING"/></translation>
+<translation id="3473479545200714844">Phóng to màn hình</translation>
+<translation id="7005812687360380971">Lỗi</translation>
+<translation id="882279321799040148">Nhấp để xem</translation>
+<translation id="5045550434625856497">Mật khẩu sai</translation>
+<translation id="1602076796624386989">Bật dữ liệu di động</translation>
+<translation id="6981982820502123353">Khả năng truy cập</translation>
+<translation id="3157931365184549694">Khôi phục</translation>
+<translation id="4274292172790327596">Lỗi chưa được xác định</translation>
+<translation id="4032485810211612751"><ph name="HOURS"/>:<ph name="MINUTES"/>:<ph name="SECONDS"/></translation>
+<translation id="225680501294068881">Đang quét tìm thiết bị...</translation>
+<translation id="5597451508971090205"><ph name="SHORT_WEEKDAY"/>, <ph name="DATE"/></translation>
+<translation id="4448844063988177157">Đang tìm kiếm mạng Wi-Fi...</translation>
+<translation id="8401662262483418323">Không kết nối được với '<ph name="NAME"/>': <ph name="DETAILS"/>
+Thông báo máy chủ: <ph name="SERVER_MSG"/></translation>
+<translation id="2475982808118771221">Đã xảy ra lôi</translation>
+<translation id="7229570126336867161">Cần có EVDO</translation>
+<translation id="2999742336789313416"><ph name="DISPLAY_NAME"/> là phiên công khai được quản lý bởi <ph name="DOMAIN"/></translation>
+<translation id="7029814467594812963">Thoát khỏi phiên</translation>
+<translation id="8454013096329229812">Wi-Fi đang bật.</translation>
+<translation id="4872237917498892622">Alt+Search hoặc Shift</translation>
+<translation id="2983818520079887040">Cài đặt...</translation>
+<translation id="1717216362413677834">Chế độ gắn đế</translation>
+<translation id="8927026611342028580">Yêu cầu kết nối</translation>
+<translation id="8300849813060516376">OTASP không thành công</translation>
+<translation id="2792498699870441125">Alt+Search</translation>
+<translation id="8660803626959853127">Đang đồng bộ hóa <ph name="COUNT"/> tệp</translation>
+<translation id="3709443003275901162">9+</translation>
+<translation id="639644700271529076">CAPS LOCK tắt</translation>
+<translation id="6248847161401822652">Nhấn Control Shift Q hai lần để thoát.</translation>
+<translation id="6267036997247669271"><ph name="NAME"/>: Đang kích hoạt...</translation>
+<translation id="1391854757121130358">Có thể bạn đã sử dụng hết dữ liệu di động được phép.</translation>
+<translation id="5413208160176941586">Người dùng được quản lý cục bộ</translation>
+<translation id="1059194134494239015"><ph name="DISPLAY_NAME"/>: <ph name="RESOLUTION"/></translation>
+<translation id="4864165860509564259">Vị trí trình khởi chạy</translation>
+<translation id="7593891976182323525">Search hoặc Shift</translation>
+<translation id="7649070708921625228">Trợ giúp</translation>
+<translation id="3050422059534974565">CAPS LOCK đang bật.
+Nhấn Search hoặc Shift để hủy.</translation>
+<translation id="397105322502079400">Đang tính...</translation>
+<translation id="158849752021629804">Cần mạng gia đình</translation>
+<translation id="6857811139397017780">Kích hoạt <ph name="NETWORKSERVICE"/></translation>
+<translation id="5864471791310927901">Không thể tra cứu DHCP</translation>
+<translation id="5812035014844949013">ĐẦU RA</translation>
+<translation id="6692173217867674490">Cụm mật khẩu sai</translation>
+<translation id="6165508094623778733">Tìm hiểu thêm</translation>
+<translation id="9046895021617826162">Kết nối không thành công</translation>
+<translation id="973896785707726617">Phiên này sẽ kết thúc sau <ph name="SESSION_TIME_REMAINING"/>. Bạn sẽ tự động bị đăng xuất.</translation>
+<translation id="8372369524088641025">Khóa WEP sai</translation>
+<translation id="6636709850131805001">Trạng thái không xác định</translation>
+<translation id="3573179567135747900">Thay đổi lại thành &quot;<ph name="FROM_LOCALE"/>&quot; (yêu cầu khởi động lại)</translation>
+<translation id="8103386449138765447">Tin nhắn SMS: <ph name="MESSAGE_COUNT"/></translation>
+<translation id="5045002648206642691">Cài đặt Google Drive...</translation>
+<translation id="1510238584712386396">Trình khởi chạy</translation>
+<translation id="7209101170223508707">CAPS LOCK đang bật.
+Nhấn Alt+Search hoặc Shift để hủy.</translation>
+<translation id="8940956008527784070">Pin yếu (<ph name="PERCENTAGE"/>%)</translation>
+<translation id="5102001756192215136">Còn lại <ph name="HOUR"/>:<ph name="MINUTE"/></translation>
+<translation id="520760366042891468">Chia sẻ quyền kiểm soát màn hình của bạn qua Hangouts.</translation>
+<translation id="8000066093800657092">Không có mạng nào</translation>
+<translation id="4015692727874266537">Đăng nhập tài khoản khác...</translation>
+<translation id="5941711191222866238">Thu nhỏ</translation>
+<translation id="6911468394164995108">Tham gia mạng khác...</translation>
+<translation id="412065659894267608"><ph name="HOUR"/>g <ph name="MINUTE"/>p cho đến khi đầy</translation>
+<translation id="6359806961507272919">SMS từ <ph name="PHONE_NUMBER"/></translation>
+<translation id="1244147615850840081">Nhà cung cấp</translation>
+</translationbundle> \ No newline at end of file
diff --git a/chromium/ash/strings/ash_strings_zh-CN.xtb b/chromium/ash/strings/ash_strings_zh-CN.xtb
new file mode 100644
index 00000000000..97af7e7a548
--- /dev/null
+++ b/chromium/ash/strings/ash_strings_zh-CN.xtb
@@ -0,0 +1,201 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="zh-CN">
+<translation id="3595596368722241419">电池已充满</translation>
+<translation id="5250713215130379958">自动隐藏启动程序</translation>
+<translation id="7814236020522506259"><ph name="HOUR"/> <ph name="MINUTE"/></translation>
+<translation id="7880025619322806991">“门户网站”状态</translation>
+<translation id="30155388420722288">溢出按钮</translation>
+<translation id="5571066253365925590">蓝牙已启用</translation>
+<translation id="9074739597929991885">蓝牙</translation>
+<translation id="2268130516524549846">蓝牙已停用</translation>
+<translation id="3775358506042162758">使用多帐户登录时,最多只能有 3 个帐户。</translation>
+<translation id="370649949373421643">启用 Wi-Fi</translation>
+<translation id="3626281679859535460">亮度</translation>
+<translation id="8054466585765276473">正在计算续航时间。</translation>
+<translation id="7982789257301363584">网络</translation>
+<translation id="5565793151875479467">代理...</translation>
+<translation id="938582441709398163">Overlay 键盘</translation>
+<translation id="4387004326333427325">身份验证证书遭到远程拒绝</translation>
+<translation id="6979158407327259162">Google 云端硬盘</translation>
+<translation id="6943836128787782965">HTTP 获取请求失败</translation>
+<translation id="2297568595583585744">状态栏</translation>
+<translation id="1661867754829461514">缺少 PIN</translation>
+<translation id="4508225577814909926"><ph name="NAME"/>:正在连接...</translation>
+<translation id="4237016987259239829">网络连接错误</translation>
+<translation id="2946640296642327832">启用蓝牙</translation>
+<translation id="6459472438155181876">正在将屏幕扩展到<ph name="DISPLAY_NAME"/></translation>
+<translation id="8206859287963243715">蜂窝网络设备</translation>
+<translation id="6596816719288285829">IP 地址</translation>
+<translation id="4508265954913339219">激活失败</translation>
+<translation id="3621712662352432595">音频设置</translation>
+<translation id="1812696562331527143">您的输入法已更改为“<ph name="INPUT_METHOD_ID"/>”*(<ph name="BEGIN_LINK"/>第三方<ph name="END_LINK"/>)。
+按 Shift + Alt 键可切换。</translation>
+<translation id="2127372758936585790">低功率充电器</translation>
+<translation id="3846575436967432996">没有可用的网络信息</translation>
+<translation id="3026237328237090306">设置移动数据</translation>
+<translation id="785750925697875037">查看移动帐户</translation>
+<translation id="153454903766751181">正在初始化蜂窝调制解调器...</translation>
+<translation id="4628814525959230255">通过环聊与<ph name="HELPER_NAME"/>共享您屏幕的控制权。</translation>
+<translation id="8343941333792395995">已旋转<ph name="DISPLAY_NAME"/></translation>
+<translation id="7864539943188674973">停用蓝牙</translation>
+<translation id="939252827960237676">无法保存屏幕截图</translation>
+<translation id="3126069444801937830">重新启动以进行更新</translation>
+<translation id="2268813581635650749">全部退出</translation>
+<translation id="735745346212279324">VPN 连接已断开</translation>
+<translation id="7320906967354320621">空闲</translation>
+<translation id="6303423059719347535">电池电量为 <ph name="PERCENTAGE"/>%</translation>
+<translation id="15373452373711364">大号鼠标光标</translation>
+<translation id="2778346081696727092">无法使用提供的用户名或密码进行身份验证</translation>
+<translation id="3294437725009624529">访客</translation>
+<translation id="8190698733819146287">自定义语言和输入法...</translation>
+<translation id="2903907270192926896">输入</translation>
+<translation id="8676770494376880701">已连接低功率充电器</translation>
+<translation id="7170041865419449892">超出范围</translation>
+<translation id="4804818685124855865">断开连接</translation>
+<translation id="2544853746127077729">身份验证证书遭到网络拒绝</translation>
+<translation id="5222676887888702881">退出</translation>
+<translation id="2688477613306174402">配置</translation>
+<translation id="1272079795634619415">停止</translation>
+<translation id="4957722034734105353">了解详情...</translation>
+<translation id="2964193600955408481">停用 Wi-Fi</translation>
+<translation id="811680302244032017">添加设备...</translation>
+<translation id="4279490309300973883">正在镜像</translation>
+<translation id="2509468283778169019">CAPS LOCK 已打开</translation>
+<translation id="3892641579809465218">内部显示</translation>
+<translation id="7823564328645135659">同步了您的设置后,该语言已由“<ph name="FROM_LOCALE"/>”更改为“<ph name="TO_LOCALE"/>”。</translation>
+<translation id="3368922792935385530">已连接</translation>
+<translation id="8340999562596018839">语音反馈</translation>
+<translation id="8654520615680304441">启用 Wi-Fi...</translation>
+<translation id="5825747213122829519">您的输入法已更改为“<ph name="INPUT_METHOD_ID"/>”。
+按 Shift + Alt 键可进行切换。</translation>
+<translation id="2562916301614567480">专用网</translation>
+<translation id="6549021752953852991">没有可用的蜂窝网络</translation>
+<translation id="4379753398862151997">嗨!显示器,咱们合不来。(系统不支持该显示器)</translation>
+<translation id="6426039856985689743">停用移动数据</translation>
+<translation id="3087734570205094154">底部</translation>
+<translation id="3742055079367172538">已完成屏幕截图</translation>
+<translation id="8878886163241303700">正在扩展屏幕</translation>
+<translation id="5271016907025319479">未配置 VPN。</translation>
+<translation id="372094107052732682">连按两次 Ctrl+Shift+Q 即可退出。</translation>
+<translation id="6803622936009808957">找不到系统支持的分辨率,因此无法镜像显示屏。已改为进入扩展桌面。</translation>
+<translation id="1480041086352807611">演示模式</translation>
+<translation id="3626637461649818317">剩余电量:<ph name="PERCENTAGE"/>%</translation>
+<translation id="9089416786594320554">输入法</translation>
+<translation id="6247708409970142803"><ph name="PERCENTAGE"/>%</translation>
+<translation id="2614835198358683673">您的 Chromebook 在开启期间可能无法充电。建议您使用产品原装的充电器。</translation>
+<translation id="1895658205118569222">关闭</translation>
+<translation id="4430019312045809116">音量</translation>
+<translation id="4442424173763614572">DNS 查找失败</translation>
+<translation id="6356500677799115505">电池电量已满并处于充电状态。</translation>
+<translation id="7874779702599364982">正在搜索蜂窝网络...</translation>
+<translation id="583281660410589416">未知</translation>
+<translation id="1383876407941801731">搜索</translation>
+<translation id="7468789844759750875">访问“<ph name="NAME"/>”激活门户网站,购买更多数据。</translation>
+<translation id="3901991538546252627">正在连接:<ph name="NAME"/></translation>
+<translation id="2204305834655267233">网络信息</translation>
+<translation id="1621499497873603021">电池电量将在 <ph name="TIME_LEFT"/>后耗尽</translation>
+<translation id="5980301590375426705">退出访客模式</translation>
+<translation id="4471417012762451363">电池电量为 <ph name="PERCENTAGE"/>% 并处于充电状态</translation>
+<translation id="8308637677604853869">上一菜单</translation>
+<translation id="4666297444214622512">无法登录到其他帐户。</translation>
+<translation id="1346748346194534595">向右</translation>
+<translation id="1773212559869067373">身份验证证书遭到本地拒绝</translation>
+<translation id="8528322925433439945">移动...</translation>
+<translation id="7049357003967926684">联盟</translation>
+<translation id="8428213095426709021">设置</translation>
+<translation id="2372145515558759244">正在同步应用...</translation>
+<translation id="7256405249507348194">无法识别的错误:<ph name="DESC"/></translation>
+<translation id="7925247922861151263">AAA 检查失败</translation>
+<translation id="8456362689280298700">电池充满还需 <ph name="HOUR"/>:<ph name="MINUTE"/></translation>
+<translation id="5787281376604286451">已启用语音反馈。
+按 Ctrl+Alt+Z 停用。</translation>
+<translation id="4479639480957787382">以太网</translation>
+<translation id="6312403991423642364">未知网络错误</translation>
+<translation id="1467432559032391204">向左</translation>
+<translation id="5543001071567407895">短信</translation>
+<translation id="2354174487190027830">正在激活“<ph name="NAME"/>”</translation>
+<translation id="8814190375133053267">Wi-Fi</translation>
+<translation id="1398853756734560583">最大化</translation>
+<translation id="2692809339924654275"><ph name="BLUETOOTH"/>:正在连接...</translation>
+<translation id="252373100621549798">未知展示广告</translation>
+<translation id="1882897271359938046">正在镜像到<ph name="DISPLAY_NAME"/></translation>
+<translation id="2727977024730340865">已插入低功率充电器;可能无法保证充电成功。</translation>
+<translation id="3784455785234192852">锁定</translation>
+<translation id="2805756323405976993">应用</translation>
+<translation id="8871072142849158571"><ph name="DISPLAY_NAME"/>的分辨率已调整为 <ph name="RESOLUTION"/></translation>
+<translation id="1512064327686280138">激活失败</translation>
+<translation id="5097002363526479830">无法连接到网络“<ph name="NAME"/>”:<ph name="DETAILS"/></translation>
+<translation id="1850504506766569011">Wi-Fi 已关闭。</translation>
+<translation id="8132793192354020517">已连接到 <ph name="NAME"/></translation>
+<translation id="7052914147756339792">设置壁纸...</translation>
+<translation id="8678698760965522072">“在线”状态</translation>
+<translation id="2532589005999780174">高反差模式</translation>
+<translation id="1119447706177454957">内部错误</translation>
+<translation id="3019353588588144572">电池电量将在 <ph name="TIME_REMAINING"/>后充满</translation>
+<translation id="3473479545200714844">屏幕放大镜</translation>
+<translation id="7005812687360380971">失败</translation>
+<translation id="882279321799040148">点击即可查看</translation>
+<translation id="5045550434625856497">密码不正确</translation>
+<translation id="1602076796624386989">启用移动数据</translation>
+<translation id="6981982820502123353">辅助功能</translation>
+<translation id="3157931365184549694">恢复</translation>
+<translation id="4274292172790327596">未识别的错误</translation>
+<translation id="4032485810211612751"><ph name="HOURS"/>:<ph name="MINUTES"/>:<ph name="SECONDS"/></translation>
+<translation id="225680501294068881">正在查找设备...</translation>
+<translation id="5597451508971090205"><ph name="DATE"/><ph name="SHORT_WEEKDAY"/></translation>
+<translation id="4448844063988177157">正在搜索 Wi-Fi 网络...</translation>
+<translation id="8401662262483418323">无法连接到“<ph name="NAME"/>”:<ph name="DETAILS"/>
+服务器消息:<ph name="SERVER_MSG"/></translation>
+<translation id="2475982808118771221">出现错误</translation>
+<translation id="7229570126336867161">需要 EVDO</translation>
+<translation id="2999742336789313416">“<ph name="DISPLAY_NAME"/>”是由 <ph name="DOMAIN"/> 管理的公开会话</translation>
+<translation id="7029814467594812963">退出会话</translation>
+<translation id="8454013096329229812">Wi-Fi 已打开。</translation>
+<translation id="4872237917498892622">Alt + 搜索键或 Shift</translation>
+<translation id="2983818520079887040">设置...</translation>
+<translation id="1717216362413677834">基座模式</translation>
+<translation id="8927026611342028580">连接请求已发送</translation>
+<translation id="8300849813060516376">OTASP 失败</translation>
+<translation id="2792498699870441125">Alt + 搜索键</translation>
+<translation id="8660803626959853127">正在同步 <ph name="COUNT"/> 个文件</translation>
+<translation id="3709443003275901162">9+</translation>
+<translation id="639644700271529076">CAPS LOCK 已关闭</translation>
+<translation id="6248847161401822652">连按两次 Control+Shift+Q 即可退出。</translation>
+<translation id="6267036997247669271"><ph name="NAME"/>:正在激活…</translation>
+<translation id="1391854757121130358">您可能已用尽移动数据配额。</translation>
+<translation id="5413208160176941586">本地托管用户</translation>
+<translation id="1059194134494239015"><ph name="DISPLAY_NAME"/>:<ph name="RESOLUTION"/></translation>
+<translation id="4864165860509564259">启动器位置</translation>
+<translation id="7593891976182323525">搜索键或 Shift</translation>
+<translation id="7649070708921625228">帮助</translation>
+<translation id="3050422059534974565">CAPS LOCK 已开启。
+按搜索键或 Shift 可取消。</translation>
+<translation id="397105322502079400">正在计算...</translation>
+<translation id="158849752021629804">需要家庭网络</translation>
+<translation id="6857811139397017780">激活 <ph name="NETWORKSERVICE"/></translation>
+<translation id="5864471791310927901">DHCP 查找失败</translation>
+<translation id="5812035014844949013">输出</translation>
+<translation id="6692173217867674490">密码错误</translation>
+<translation id="6165508094623778733">了解详情</translation>
+<translation id="9046895021617826162">连接失败</translation>
+<translation id="973896785707726617">该会话将在 <ph name="SESSION_TIME_REMAINING"/>后结束,到时您将自动退出。</translation>
+<translation id="8372369524088641025">WEP 密钥错误</translation>
+<translation id="6636709850131805001">未知状态</translation>
+<translation id="3573179567135747900">重新更改为“<ph name="FROM_LOCALE"/>”(需要重启)</translation>
+<translation id="8103386449138765447">短信数:<ph name="MESSAGE_COUNT"/> 条</translation>
+<translation id="5045002648206642691">Google 云端硬盘设置...</translation>
+<translation id="1510238584712386396">启动器</translation>
+<translation id="7209101170223508707">Caps Lock 已开启。
+按 Alt + 搜索键或 Shift 可取消。</translation>
+<translation id="8940956008527784070">电池电量不足 (<ph name="PERCENTAGE"/>%)</translation>
+<translation id="5102001756192215136">可用时长:<ph name="HOUR"/>:<ph name="MINUTE"/></translation>
+<translation id="520760366042891468">通过环聊共享您屏幕的控制权。</translation>
+<translation id="8000066093800657092">未连接任何网络</translation>
+<translation id="4015692727874266537">登录其他帐户…</translation>
+<translation id="5941711191222866238">最小化</translation>
+<translation id="6911468394164995108">连接其他...</translation>
+<translation id="412065659894267608">还需要 <ph name="HOUR"/> 小时 <ph name="MINUTE"/> 分钟才能充满电</translation>
+<translation id="6359806961507272919"><ph name="PHONE_NUMBER"/> 发来的短信</translation>
+<translation id="1244147615850840081">运营商</translation>
+</translationbundle> \ No newline at end of file
diff --git a/chromium/ash/strings/ash_strings_zh-TW.xtb b/chromium/ash/strings/ash_strings_zh-TW.xtb
new file mode 100644
index 00000000000..83261ecbf75
--- /dev/null
+++ b/chromium/ash/strings/ash_strings_zh-TW.xtb
@@ -0,0 +1,201 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="zh-TW">
+<translation id="3595596368722241419">電池已充滿</translation>
+<translation id="5250713215130379958">自動隱藏啟動器</translation>
+<translation id="7814236020522506259"><ph name="HOUR"/> 小時 <ph name="MINUTE"/> 分鐘</translation>
+<translation id="7880025619322806991">入口網站狀態</translation>
+<translation id="30155388420722288">溢位按鈕</translation>
+<translation id="5571066253365925590">藍牙已啟用</translation>
+<translation id="9074739597929991885">藍牙</translation>
+<translation id="2268130516524549846">藍牙已停用</translation>
+<translation id="3775358506042162758">多帳戶登入功能一次只能登入最多 3 個帳戶。</translation>
+<translation id="370649949373421643">啟用 Wi-Fi</translation>
+<translation id="3626281679859535460">亮度</translation>
+<translation id="8054466585765276473">正在計算電池使用時間。</translation>
+<translation id="7982789257301363584">網路</translation>
+<translation id="5565793151875479467">Proxy...</translation>
+<translation id="938582441709398163">鍵盤自訂快速鍵</translation>
+<translation id="4387004326333427325">遠端已拒絕驗證憑證</translation>
+<translation id="6979158407327259162">Google 雲端硬碟</translation>
+<translation id="6943836128787782965">HTTP 擷取失敗</translation>
+<translation id="2297568595583585744">狀態匣</translation>
+<translation id="1661867754829461514">找不到 PIN</translation>
+<translation id="4508225577814909926"><ph name="NAME"/>:正在連線...</translation>
+<translation id="4237016987259239829">網路連線錯誤</translation>
+<translation id="2946640296642327832">啟用藍牙</translation>
+<translation id="6459472438155181876">正在擴充 <ph name="DISPLAY_NAME"/> 畫面</translation>
+<translation id="8206859287963243715">手機</translation>
+<translation id="6596816719288285829">IP 位址</translation>
+<translation id="4508265954913339219">啟用失敗</translation>
+<translation id="3621712662352432595">音訊設定</translation>
+<translation id="1812696562331527143">您的輸入法已變更為 <ph name="INPUT_METHOD_ID"/>*(<ph name="BEGIN_LINK"/>第三方<ph name="END_LINK"/>)。
+按下 Shift + Alt 鍵即可切換。</translation>
+<translation id="2127372758936585790">低功率充電器</translation>
+<translation id="3846575436967432996">沒有可用的網路資訊</translation>
+<translation id="3026237328237090306">設定行動數據</translation>
+<translation id="785750925697875037">查看行動帳戶</translation>
+<translation id="153454903766751181">正在初始化行動數據機...</translation>
+<translation id="4628814525959230255">透過 Hangouts 與 <ph name="HELPER_NAME"/> 分享螢幕控制權。</translation>
+<translation id="8343941333792395995">已旋轉 <ph name="DISPLAY_NAME"/></translation>
+<translation id="7864539943188674973">停用藍牙</translation>
+<translation id="939252827960237676">無法儲存螢幕擷取畫面</translation>
+<translation id="3126069444801937830">重新啟用即可更新</translation>
+<translation id="2268813581635650749">登出所有使用者</translation>
+<translation id="735745346212279324">已中斷 VPN 連線</translation>
+<translation id="7320906967354320621">閒置</translation>
+<translation id="6303423059719347535">電池電量為 <ph name="PERCENTAGE"/>%</translation>
+<translation id="15373452373711364">大型滑鼠游標</translation>
+<translation id="2778346081696727092">無法使用您所提供的使用者名稱或密碼進行驗證</translation>
+<translation id="3294437725009624529">訪客</translation>
+<translation id="8190698733819146287">自訂語言與輸入法...</translation>
+<translation id="2903907270192926896">輸入</translation>
+<translation id="8676770494376880701">已連接低功率充電器</translation>
+<translation id="7170041865419449892">超出範圍</translation>
+<translation id="4804818685124855865">中斷連線</translation>
+<translation id="2544853746127077729">網路已拒絕驗證憑證</translation>
+<translation id="5222676887888702881">登出</translation>
+<translation id="2688477613306174402">設定</translation>
+<translation id="1272079795634619415">停止</translation>
+<translation id="4957722034734105353">瞭解詳情...</translation>
+<translation id="2964193600955408481">停用 WiFi</translation>
+<translation id="811680302244032017">新增裝置...</translation>
+<translation id="4279490309300973883">鏡像</translation>
+<translation id="2509468283778169019">大寫鍵已啟用</translation>
+<translation id="3892641579809465218">內部畫面</translation>
+<translation id="7823564328645135659">同步處理您的設定後,系統已將語言從「<ph name="FROM_LOCALE"/>」變更為「<ph name="TO_LOCALE"/>」。</translation>
+<translation id="3368922792935385530">已連線</translation>
+<translation id="8340999562596018839">互動朗讀</translation>
+<translation id="8654520615680304441">開啟 Wi-Fi...</translation>
+<translation id="5825747213122829519">您的輸入法已變更為 <ph name="INPUT_METHOD_ID"/>。
+按下 Shift + Alt 鍵即可切換。</translation>
+<translation id="2562916301614567480">私人網路</translation>
+<translation id="6549021752953852991">沒有可用的行動網路</translation>
+<translation id="4379753398862151997">Dear Monitor, it's not working out between us. (系統無法支援該顯示器)</translation>
+<translation id="6426039856985689743">停用行動數據</translation>
+<translation id="3087734570205094154">置底</translation>
+<translation id="3742055079367172538">已拍攝螢幕擷取畫面</translation>
+<translation id="8878886163241303700">延伸螢幕</translation>
+<translation id="5271016907025319479">尚未設定 VPN。</translation>
+<translation id="372094107052732682">按兩下 Ctrl+Shift+Q 鍵即可結束。</translation>
+<translation id="6803622936009808957">找不到系統支援的解析度,因此無法顯示鏡像。已改為進入延伸桌面。</translation>
+<translation id="1480041086352807611">示範模式</translation>
+<translation id="3626637461649818317">剩餘電量:<ph name="PERCENTAGE"/>%</translation>
+<translation id="9089416786594320554">輸入法</translation>
+<translation id="6247708409970142803"><ph name="PERCENTAGE"/>%</translation>
+<translation id="2614835198358683673">您的 Chromebook 可能無法在開機時充電。建議您使用官方提供的充電器。</translation>
+<translation id="1895658205118569222">關閉</translation>
+<translation id="4430019312045809116">音量</translation>
+<translation id="4442424173763614572">DNS 查詢失敗</translation>
+<translation id="6356500677799115505">電池電量已滿 (充電中)。</translation>
+<translation id="7874779702599364982">正在搜尋行動網路...</translation>
+<translation id="583281660410589416">不明</translation>
+<translation id="1383876407941801731">搜尋</translation>
+<translation id="7468789844759750875">造訪 <ph name="NAME"/> 啟用入口網站即可購買更多數據量。</translation>
+<translation id="3901991538546252627">正在連線至:<ph name="NAME"/></translation>
+<translation id="2204305834655267233">網路資訊</translation>
+<translation id="1621499497873603021">電池剩餘使用時間:<ph name="TIME_LEFT"/></translation>
+<translation id="5980301590375426705">結束訪客工作階段</translation>
+<translation id="4471417012762451363">電池電量為 <ph name="PERCENTAGE"/>% (充電中)</translation>
+<translation id="8308637677604853869">前一個選單</translation>
+<translation id="4666297444214622512">無法登入其他帳戶。</translation>
+<translation id="1346748346194534595">向右</translation>
+<translation id="1773212559869067373">本機已拒絕驗證憑證</translation>
+<translation id="8528322925433439945">行動服務 ...</translation>
+<translation id="7049357003967926684">關聯</translation>
+<translation id="8428213095426709021">設定</translation>
+<translation id="2372145515558759244">正在同步處理應用程式...</translation>
+<translation id="7256405249507348194">無法辨識的錯誤:<ph name="DESC"/></translation>
+<translation id="7925247922861151263">AAA 檢查失敗</translation>
+<translation id="8456362689280298700">完成充電尚需 <ph name="HOUR"/> 小時 <ph name="MINUTE"/> 分鐘</translation>
+<translation id="5787281376604286451">互動朗讀功能已啟用。
+按下 Ctrl+Alt+Z 鍵即可停用。</translation>
+<translation id="4479639480957787382">乙太網路</translation>
+<translation id="6312403991423642364">不明的網路錯誤</translation>
+<translation id="1467432559032391204">向左</translation>
+<translation id="5543001071567407895">簡訊</translation>
+<translation id="2354174487190027830">正在啟用 <ph name="NAME"/></translation>
+<translation id="8814190375133053267">Wi-Fi</translation>
+<translation id="1398853756734560583">放到最大</translation>
+<translation id="2692809339924654275"><ph name="BLUETOOTH"/>:正在連線...</translation>
+<translation id="252373100621549798">顯示器不明</translation>
+<translation id="1882897271359938046">正在建立 <ph name="DISPLAY_NAME"/> 鏡像</translation>
+<translation id="2727977024730340865">使用低功率充電器,可能導致充電狀態不穩定。</translation>
+<translation id="3784455785234192852">鎖定</translation>
+<translation id="2805756323405976993">應用程式</translation>
+<translation id="8871072142849158571">已將 <ph name="DISPLAY_NAME"/> 的大小重新調整為 <ph name="RESOLUTION"/></translation>
+<translation id="1512064327686280138">啟用失敗</translation>
+<translation id="5097002363526479830">無法連線至「<ph name="NAME"/>」:<ph name="DETAILS"/></translation>
+<translation id="1850504506766569011">Wi-Fi 已關閉。</translation>
+<translation id="8132793192354020517">已連線至 <ph name="NAME"/></translation>
+<translation id="7052914147756339792">設定桌布...</translation>
+<translation id="8678698760965522072">線上狀態</translation>
+<translation id="2532589005999780174">高反差模式</translation>
+<translation id="1119447706177454957">內部錯誤</translation>
+<translation id="3019353588588144572">電池剩餘充電時間:<ph name="TIME_REMAINING"/></translation>
+<translation id="3473479545200714844">畫面放大鏡</translation>
+<translation id="7005812687360380971">失敗</translation>
+<translation id="882279321799040148">按這裡瀏覽</translation>
+<translation id="5045550434625856497">密碼不正確</translation>
+<translation id="1602076796624386989">啟用行動數據</translation>
+<translation id="6981982820502123353">協助工具</translation>
+<translation id="3157931365184549694">還原</translation>
+<translation id="4274292172790327596">不明錯誤</translation>
+<translation id="4032485810211612751"><ph name="HOURS"/>:<ph name="MINUTES"/>:<ph name="SECONDS"/></translation>
+<translation id="225680501294068881">正在掃描裝置...</translation>
+<translation id="5597451508971090205"><ph name="DATE"/><ph name="SHORT_WEEKDAY"/></translation>
+<translation id="4448844063988177157">正在搜尋 Wi-Fi 網路...</translation>
+<translation id="8401662262483418323">無法連線至「<ph name="NAME"/>」:<ph name="DETAILS"/>
+伺服器訊息:<ph name="SERVER_MSG"/></translation>
+<translation id="2475982808118771221">發生錯誤</translation>
+<translation id="7229570126336867161">需要 EVDO</translation>
+<translation id="2999742336789313416"><ph name="DISPLAY_NAME"/> 是受 <ph name="DOMAIN"/> 管理的公開工作階段</translation>
+<translation id="7029814467594812963">結束工作階段</translation>
+<translation id="8454013096329229812">Wi-Fi 已開啟。</translation>
+<translation id="4872237917498892622">Alt + 搜尋鍵或 Shift 鍵</translation>
+<translation id="2983818520079887040">設定...</translation>
+<translation id="1717216362413677834">座架模式</translation>
+<translation id="8927026611342028580">已要求連線</translation>
+<translation id="8300849813060516376">OTASP 失敗</translation>
+<translation id="2792498699870441125">Alt + 搜尋鍵</translation>
+<translation id="8660803626959853127">正在同步處理 <ph name="COUNT"/> 個檔案</translation>
+<translation id="3709443003275901162">9+</translation>
+<translation id="639644700271529076">大寫鍵已關閉</translation>
+<translation id="6248847161401822652">按兩下 Control、Shift 和 Q 鍵即可結束。</translation>
+<translation id="6267036997247669271"><ph name="NAME"/>:正在啟用...</translation>
+<translation id="1391854757121130358">您可能已用盡行動數據配額。</translation>
+<translation id="5413208160176941586">本機管理化環境下的使用者</translation>
+<translation id="1059194134494239015"><ph name="DISPLAY_NAME"/>:<ph name="RESOLUTION"/></translation>
+<translation id="4864165860509564259">啟動器位置</translation>
+<translation id="7593891976182323525">搜尋鍵或 Shift 鍵</translation>
+<translation id="7649070708921625228">說明</translation>
+<translation id="3050422059534974565">大寫鍵已啟用。
+按下搜尋鍵或 Shift 鍵即可取消。</translation>
+<translation id="397105322502079400">計算中…</translation>
+<translation id="158849752021629804">需要家用網路</translation>
+<translation id="6857811139397017780">啟用 <ph name="NETWORKSERVICE"/></translation>
+<translation id="5864471791310927901">DHCP 查閱失敗</translation>
+<translation id="5812035014844949013">輸出</translation>
+<translation id="6692173217867674490">通關密語有誤</translation>
+<translation id="6165508094623778733">瞭解詳情</translation>
+<translation id="9046895021617826162">連線失敗</translation>
+<translation id="973896785707726617">這個工作階段將在 <ph name="SESSION_TIME_REMAINING"/>後結束,系統會自動將您登出。</translation>
+<translation id="8372369524088641025">WEP 金鑰有誤</translation>
+<translation id="6636709850131805001">不明狀態</translation>
+<translation id="3573179567135747900">改回「<ph name="FROM_LOCALE"/>」(需要重新啟動)</translation>
+<translation id="8103386449138765447">簡訊數:<ph name="MESSAGE_COUNT"/></translation>
+<translation id="5045002648206642691">Google 雲端硬碟設定...</translation>
+<translation id="1510238584712386396">啟動器</translation>
+<translation id="7209101170223508707">大寫鍵已啟用。
+按下 Alt + 搜尋鍵或 Shift 鍵即可取消。</translation>
+<translation id="8940956008527784070">電池電量不足 (<ph name="PERCENTAGE"/>%)</translation>
+<translation id="5102001756192215136">電量剩餘時間:<ph name="HOUR"/>:<ph name="MINUTE"/></translation>
+<translation id="520760366042891468">透過 Hangouts 分享螢幕控制權。</translation>
+<translation id="8000066093800657092">沒有網路</translation>
+<translation id="4015692727874266537">使用其他帳戶登入...</translation>
+<translation id="5941711191222866238">縮到最小</translation>
+<translation id="6911468394164995108">加入其他網路...</translation>
+<translation id="412065659894267608">尚需 <ph name="HOUR"/> 小時 <ph name="MINUTE"/> 分鐘才能充滿電</translation>
+<translation id="6359806961507272919">來自 <ph name="PHONE_NUMBER"/> 的簡訊</translation>
+<translation id="1244147615850840081">通訊業者</translation>
+</translationbundle> \ No newline at end of file
diff --git a/chromium/ash/system/DEPS b/chromium/ash/system/DEPS
new file mode 100644
index 00000000000..abf5b5f48c4
--- /dev/null
+++ b/chromium/ash/system/DEPS
@@ -0,0 +1,3 @@
+include_rules = [
+ "-chromeos",
+]
diff --git a/chromium/ash/system/OWNERS b/chromium/ash/system/OWNERS
new file mode 100644
index 00000000000..de76ad8ce46
--- /dev/null
+++ b/chromium/ash/system/OWNERS
@@ -0,0 +1,3 @@
+sadrul@chromium.org
+stevenjb@chromium.org
+jennyz@chromium.org
diff --git a/chromium/ash/system/bluetooth/bluetooth_observer.h b/chromium/ash/system/bluetooth/bluetooth_observer.h
new file mode 100644
index 00000000000..1c1bf4fa4d0
--- /dev/null
+++ b/chromium/ash/system/bluetooth/bluetooth_observer.h
@@ -0,0 +1,20 @@
+// Copyright (c) 2012 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 ASH_SYSTEM_BLUETOOTH_BLUETOOTH_OBSERVER_H_
+#define ASH_SYSTEM_BLUETOOTH_BLUETOOTH_OBSERVER_H_
+
+namespace ash {
+
+class BluetoothObserver {
+ public:
+ virtual ~BluetoothObserver() {}
+
+ virtual void OnBluetoothRefresh() = 0;
+ virtual void OnBluetoothDiscoveringChanged() = 0;
+};
+
+} // namespace ash
+
+#endif // ASH_SYSTEM_BLUETOOTH_BLUETOOTH_OBSERVER_H_
diff --git a/chromium/ash/system/bluetooth/tray_bluetooth.cc b/chromium/ash/system/bluetooth/tray_bluetooth.cc
new file mode 100644
index 00000000000..5d75e945ab0
--- /dev/null
+++ b/chromium/ash/system/bluetooth/tray_bluetooth.cc
@@ -0,0 +1,463 @@
+// Copyright (c) 2012 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.
+
+#include "ash/system/bluetooth/tray_bluetooth.h"
+
+#include "ash/shell.h"
+#include "ash/system/tray/fixed_sized_scroll_view.h"
+#include "ash/system/tray/hover_highlight_view.h"
+#include "ash/system/tray/system_tray.h"
+#include "ash/system/tray/system_tray_delegate.h"
+#include "ash/system/tray/system_tray_notifier.h"
+#include "ash/system/tray/throbber_view.h"
+#include "ash/system/tray/tray_constants.h"
+#include "ash/system/tray/tray_details_view.h"
+#include "ash/system/tray/tray_item_more.h"
+#include "ash/system/tray/tray_popup_header_button.h"
+#include "grit/ash_resources.h"
+#include "grit/ash_strings.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "ui/gfx/image/image.h"
+#include "ui/views/controls/image_view.h"
+#include "ui/views/controls/label.h"
+#include "ui/views/layout/box_layout.h"
+
+namespace ash {
+namespace internal {
+
+namespace tray {
+
+namespace {
+
+// Updates bluetooth device |device| in the |list|. If it is new, append to the
+// end of the |list|; otherwise, keep it at the same place, but update the data
+// with new device info provided by |device|.
+void UpdateBluetoothDeviceList(BluetoothDeviceList* list,
+ const BluetoothDeviceInfo& device) {
+ for (BluetoothDeviceList::iterator it = list->begin(); it != list->end();
+ ++it) {
+ if ((*it).address == device.address) {
+ *it = device;
+ return;
+ }
+ }
+
+ list->push_back(device);
+}
+
+// Removes the obsolete BluetoothDevices from |list|, if they are not in the
+// |new_list|.
+void RemoveObsoleteBluetoothDevicesFromList(
+ BluetoothDeviceList* list,
+ const std::set<std::string>& new_list) {
+ for (BluetoothDeviceList::iterator it = list->begin(); it != list->end();
+ ++it) {
+ if (new_list.find((*it).address) == new_list.end()) {
+ it = list->erase(it);
+ if (it == list->end())
+ return;
+ }
+ }
+}
+
+} // namespace
+
+class BluetoothDefaultView : public TrayItemMore {
+ public:
+ BluetoothDefaultView(SystemTrayItem* owner, bool show_more)
+ : TrayItemMore(owner, show_more) {
+ ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance();
+ SetImage(bundle.GetImageNamed(IDR_AURA_UBER_TRAY_BLUETOOTH).ToImageSkia());
+ UpdateLabel();
+ }
+
+ virtual ~BluetoothDefaultView() {}
+
+ void UpdateLabel() {
+ ash::SystemTrayDelegate* delegate =
+ ash::Shell::GetInstance()->system_tray_delegate();
+ if (delegate->GetBluetoothAvailable()) {
+ ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
+ const base::string16 label =
+ rb.GetLocalizedString(delegate->GetBluetoothEnabled() ?
+ IDS_ASH_STATUS_TRAY_BLUETOOTH_ENABLED :
+ IDS_ASH_STATUS_TRAY_BLUETOOTH_DISABLED);
+ SetLabel(label);
+ SetAccessibleName(label);
+ SetVisible(true);
+ } else {
+ SetVisible(false);
+ }
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(BluetoothDefaultView);
+};
+
+class BluetoothDetailedView : public TrayDetailsView,
+ public ViewClickListener,
+ public views::ButtonListener {
+ public:
+ BluetoothDetailedView(SystemTrayItem* owner, user::LoginStatus login)
+ : TrayDetailsView(owner),
+ login_(login),
+ manage_devices_(NULL),
+ toggle_bluetooth_(NULL),
+ enable_bluetooth_(NULL),
+ bluetooth_discovering_(false) {
+ CreateItems();
+ Update();
+ }
+
+ virtual ~BluetoothDetailedView() {
+ // Stop discovering bluetooth devices when exiting BT detailed view.
+ BluetoothStopDiscovering();
+ }
+
+ void Update() {
+ BluetoothStartDiscovering();
+ UpdateBlueToothDeviceList();
+
+ // Update UI.
+ UpdateDeviceScrollList();
+ UpdateHeaderEntry();
+ Layout();
+ }
+
+ private:
+ void CreateItems() {
+ CreateScrollableList();
+ AppendSettingsEntries();
+ AppendHeaderEntry();
+ }
+
+ void BluetoothStartDiscovering() {
+ ash::SystemTrayDelegate* delegate =
+ ash::Shell::GetInstance()->system_tray_delegate();
+ bool bluetooth_enabled = delegate->GetBluetoothEnabled();
+ if (!bluetooth_discovering_ && bluetooth_enabled) {
+ bluetooth_discovering_ = true;
+ delegate->BluetoothStartDiscovering();
+ throbber_->Start();
+ } else if(!bluetooth_enabled) {
+ bluetooth_discovering_ = false;
+ throbber_->Stop();
+ }
+ }
+
+ void BluetoothStopDiscovering() {
+ ash::SystemTrayDelegate* delegate =
+ ash::Shell::GetInstance()->system_tray_delegate();
+ if (delegate && bluetooth_discovering_) {
+ bluetooth_discovering_ = false;
+ delegate->BluetoothStopDiscovering();
+ throbber_->Stop();
+ }
+ }
+
+ void UpdateBlueToothDeviceList() {
+ std::set<std::string> new_connecting_devices;
+ std::set<std::string> new_connected_devices;
+ std::set<std::string> new_paired_not_connected_devices;
+ std::set<std::string> new_discovered_not_paired_devices;
+
+ BluetoothDeviceList list;
+ Shell::GetInstance()->system_tray_delegate()->
+ GetAvailableBluetoothDevices(&list);
+ for (size_t i = 0; i < list.size(); ++i) {
+ if (list[i].connecting) {
+ list[i].display_name = l10n_util::GetStringFUTF16(
+ IDS_ASH_STATUS_TRAY_BLUETOOTH_CONNECTING, list[i].display_name);
+ new_connecting_devices.insert(list[i].address);
+ UpdateBluetoothDeviceList(&connecting_devices_, list[i]);
+ } else if (list[i].connected && list[i].paired) {
+ new_connected_devices.insert(list[i].address);
+ UpdateBluetoothDeviceList(&connected_devices_, list[i]);
+ } else if (list[i].paired) {
+ new_paired_not_connected_devices.insert(list[i].address);
+ UpdateBluetoothDeviceList(&paired_not_connected_devices_, list[i]);
+ } else {
+ new_discovered_not_paired_devices.insert(list[i].address);
+ UpdateBluetoothDeviceList(&discovered_not_paired_devices_, list[i]);
+ }
+ }
+ RemoveObsoleteBluetoothDevicesFromList(&connecting_devices_,
+ new_connecting_devices);
+ RemoveObsoleteBluetoothDevicesFromList(&connected_devices_,
+ new_connected_devices);
+ RemoveObsoleteBluetoothDevicesFromList(&paired_not_connected_devices_,
+ new_paired_not_connected_devices);
+ RemoveObsoleteBluetoothDevicesFromList(&discovered_not_paired_devices_,
+ new_discovered_not_paired_devices);
+ }
+
+ void AppendHeaderEntry() {
+ CreateSpecialRow(IDS_ASH_STATUS_TRAY_BLUETOOTH, this);
+
+ if (login_ == user::LOGGED_IN_LOCKED)
+ return;
+
+ throbber_ = new ThrobberView;
+ throbber_->SetTooltipText(
+ l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_BLUETOOTH_DISCOVERING));
+ footer()->AddThrobber(throbber_);
+
+ // Do not allow toggling bluetooth in the lock screen.
+ ash::SystemTrayDelegate* delegate =
+ ash::Shell::GetInstance()->system_tray_delegate();
+ toggle_bluetooth_ = new TrayPopupHeaderButton(this,
+ IDR_AURA_UBER_TRAY_BLUETOOTH_ENABLED,
+ IDR_AURA_UBER_TRAY_BLUETOOTH_DISABLED,
+ IDR_AURA_UBER_TRAY_BLUETOOTH_ENABLED_HOVER,
+ IDR_AURA_UBER_TRAY_BLUETOOTH_DISABLED_HOVER,
+ IDS_ASH_STATUS_TRAY_BLUETOOTH);
+ toggle_bluetooth_->SetToggled(!delegate->GetBluetoothEnabled());
+ toggle_bluetooth_->SetTooltipText(
+ l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_DISABLE_BLUETOOTH));
+ toggle_bluetooth_->SetToggledTooltipText(
+ l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_ENABLE_BLUETOOTH));
+ footer()->AddButton(toggle_bluetooth_);
+ }
+
+ void UpdateHeaderEntry() {
+ if (toggle_bluetooth_) {
+ toggle_bluetooth_->SetToggled(
+ !ash::Shell::GetInstance()->system_tray_delegate()->
+ GetBluetoothEnabled());
+ }
+ }
+
+ void UpdateDeviceScrollList() {
+ device_map_.clear();
+ scroll_content()->RemoveAllChildViews(true);
+ enable_bluetooth_ = NULL;
+
+ ash::SystemTrayDelegate* delegate =
+ ash::Shell::GetInstance()->system_tray_delegate();
+ bool bluetooth_enabled = delegate->GetBluetoothEnabled();
+ bool blueooth_available = delegate->GetBluetoothAvailable();
+ if (blueooth_available && !bluetooth_enabled &&
+ toggle_bluetooth_) {
+ enable_bluetooth_ =
+ AddScrollListItem(
+ l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_ENABLE_BLUETOOTH),
+ gfx::Font::NORMAL, false, true);
+ }
+
+ AppendSameTypeDevicesToScrollList(
+ connected_devices_, true, true, bluetooth_enabled);
+ AppendSameTypeDevicesToScrollList(
+ connecting_devices_, true, false, bluetooth_enabled);
+ AppendSameTypeDevicesToScrollList(
+ paired_not_connected_devices_, false, false, bluetooth_enabled);
+ if (discovered_not_paired_devices_.size() > 0)
+ AddScrollSeparator();
+ AppendSameTypeDevicesToScrollList(
+ discovered_not_paired_devices_, false, false, bluetooth_enabled);
+
+ // Show user Bluetooth state if there is no bluetooth devices in list.
+ if (device_map_.size() == 0) {
+ if (blueooth_available && bluetooth_enabled) {
+ AddScrollListItem(
+ l10n_util::GetStringUTF16(
+ IDS_ASH_STATUS_TRAY_BLUETOOTH_DISCOVERING),
+ gfx::Font::NORMAL, false, true);
+ }
+ }
+
+ scroll_content()->SizeToPreferredSize();
+ static_cast<views::View*>(scroller())->Layout();
+ }
+
+ void AppendSameTypeDevicesToScrollList(const BluetoothDeviceList& list,
+ bool bold,
+ bool checked,
+ bool enabled) {
+ for (size_t i = 0; i < list.size(); ++i) {
+ HoverHighlightView* container = AddScrollListItem(
+ list[i].display_name,
+ bold? gfx::Font::BOLD : gfx::Font::NORMAL,
+ checked, enabled);
+ device_map_[container] = list[i].address;
+ }
+ }
+
+ HoverHighlightView* AddScrollListItem(const base::string16& text,
+ gfx::Font::FontStyle style,
+ bool checked,
+ bool enabled) {
+ HoverHighlightView* container = new HoverHighlightView(this);
+ views::Label* label = container->AddCheckableLabel(text, style, checked);
+ label->SetEnabled(enabled);
+ scroll_content()->AddChildView(container);
+ return container;
+ }
+
+ // Add settings entries.
+ void AppendSettingsEntries() {
+ // Add bluetooth device requires a browser window, hide it for non logged in
+ // user.
+ if (login_ == user::LOGGED_IN_NONE ||
+ login_ == user::LOGGED_IN_LOCKED)
+ return;
+
+ ash::SystemTrayDelegate* delegate =
+ ash::Shell::GetInstance()->system_tray_delegate();
+ ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
+ HoverHighlightView* container = new HoverHighlightView(this);
+ container->AddLabel(
+ rb.GetLocalizedString(IDS_ASH_STATUS_TRAY_BLUETOOTH_MANAGE_DEVICES),
+ gfx::Font::NORMAL);
+ container->SetEnabled(delegate->GetBluetoothAvailable());
+ AddChildView(container);
+ manage_devices_ = container;
+ }
+
+ // Returns true if the device with |device_id| is found in |device_list|,
+ // and the display_name of the device will be returned in |display_name| if
+ // it's not NULL.
+ bool FoundDevice(const std::string& device_id,
+ const BluetoothDeviceList& device_list,
+ base::string16* display_name) {
+ for (size_t i = 0; i < device_list.size(); ++i) {
+ if (device_list[i].address == device_id) {
+ if (display_name)
+ *display_name = device_list[i].display_name;
+ return true;
+ }
+ }
+ return false;
+ }
+
+ // Updates UI of the clicked bluetooth device to show it is being connected
+ // or disconnected if such an operation is going to be performed underway.
+ void UpdateClickedDevice(std::string device_id, views::View* item_container) {
+ base::string16 display_name;
+ if (FoundDevice(device_id, paired_not_connected_devices_,
+ &display_name)) {
+ display_name = l10n_util::GetStringFUTF16(
+ IDS_ASH_STATUS_TRAY_BLUETOOTH_CONNECTING, display_name);
+
+ item_container->RemoveAllChildViews(true);
+ static_cast<HoverHighlightView*>(item_container)->
+ AddCheckableLabel(display_name, gfx::Font::BOLD, false);
+ scroll_content()->SizeToPreferredSize();
+ static_cast<views::View*>(scroller())->Layout();
+ }
+ }
+
+ // Overridden from ViewClickListener.
+ virtual void OnViewClicked(views::View* sender) OVERRIDE {
+ ash::SystemTrayDelegate* delegate =
+ ash::Shell::GetInstance()->system_tray_delegate();
+ if (sender == footer()->content()) {
+ owner()->system_tray()->ShowDefaultView(BUBBLE_USE_EXISTING);
+ } else if (sender == manage_devices_) {
+ delegate->ManageBluetoothDevices();
+ } else if (sender == enable_bluetooth_) {
+ delegate->ToggleBluetooth();
+ } else {
+ if (!delegate->GetBluetoothEnabled())
+ return;
+ std::map<views::View*, std::string>::iterator find;
+ find = device_map_.find(sender);
+ if (find == device_map_.end())
+ return;
+ std::string device_id = find->second;
+ if (FoundDevice(device_id, connecting_devices_, NULL))
+ return;
+ UpdateClickedDevice(device_id, sender);
+ delegate->ConnectToBluetoothDevice(device_id);
+ }
+ }
+
+ // Overridden from ButtonListener.
+ virtual void ButtonPressed(views::Button* sender,
+ const ui::Event& event) OVERRIDE {
+ ash::SystemTrayDelegate* delegate =
+ ash::Shell::GetInstance()->system_tray_delegate();
+ if (sender == toggle_bluetooth_)
+ delegate->ToggleBluetooth();
+ else
+ NOTREACHED();
+ }
+
+ user::LoginStatus login_;
+
+ std::map<views::View*, std::string> device_map_;
+ views::View* manage_devices_;
+ ThrobberView* throbber_;
+ TrayPopupHeaderButton* toggle_bluetooth_;
+ HoverHighlightView* enable_bluetooth_;
+ BluetoothDeviceList connected_devices_;
+ BluetoothDeviceList connecting_devices_;
+ BluetoothDeviceList paired_not_connected_devices_;
+ BluetoothDeviceList discovered_not_paired_devices_;
+ bool bluetooth_discovering_;
+
+ DISALLOW_COPY_AND_ASSIGN(BluetoothDetailedView);
+};
+
+} // namespace tray
+
+TrayBluetooth::TrayBluetooth(SystemTray* system_tray)
+ : SystemTrayItem(system_tray),
+ default_(NULL),
+ detailed_(NULL) {
+ Shell::GetInstance()->system_tray_notifier()->AddBluetoothObserver(this);
+}
+
+TrayBluetooth::~TrayBluetooth() {
+ Shell::GetInstance()->system_tray_notifier()->RemoveBluetoothObserver(this);
+}
+
+views::View* TrayBluetooth::CreateTrayView(user::LoginStatus status) {
+ return NULL;
+}
+
+views::View* TrayBluetooth::CreateDefaultView(user::LoginStatus status) {
+ CHECK(default_ == NULL);
+ default_ = new tray::BluetoothDefaultView(
+ this, status != user::LOGGED_IN_LOCKED);
+ return default_;
+}
+
+views::View* TrayBluetooth::CreateDetailedView(user::LoginStatus status) {
+ if (!Shell::GetInstance()->system_tray_delegate()->GetBluetoothAvailable())
+ return NULL;
+ CHECK(detailed_ == NULL);
+ detailed_ = new tray::BluetoothDetailedView(this, status);
+ return detailed_;
+}
+
+void TrayBluetooth::DestroyTrayView() {
+}
+
+void TrayBluetooth::DestroyDefaultView() {
+ default_ = NULL;
+}
+
+void TrayBluetooth::DestroyDetailedView() {
+ detailed_ = NULL;
+}
+
+void TrayBluetooth::UpdateAfterLoginStatusChange(user::LoginStatus status) {
+}
+
+void TrayBluetooth::OnBluetoothRefresh() {
+ if (default_)
+ default_->UpdateLabel();
+ else if (detailed_)
+ detailed_->Update();
+}
+
+void TrayBluetooth::OnBluetoothDiscoveringChanged() {
+ if (!detailed_)
+ return;
+ detailed_->Update();
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/system/bluetooth/tray_bluetooth.h b/chromium/ash/system/bluetooth/tray_bluetooth.h
new file mode 100644
index 00000000000..2e452c643d9
--- /dev/null
+++ b/chromium/ash/system/bluetooth/tray_bluetooth.h
@@ -0,0 +1,48 @@
+// Copyright (c) 2012 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 ASH_SYSTEM_BLUETOOTH_TRAY_BLUETOOTH_H_
+#define ASH_SYSTEM_BLUETOOTH_TRAY_BLUETOOTH_H_
+
+#include "ash/system/bluetooth/bluetooth_observer.h"
+#include "ash/system/tray/system_tray_item.h"
+
+namespace ash {
+namespace internal {
+
+namespace tray {
+class BluetoothDefaultView;
+class BluetoothDetailedView;
+}
+
+class TrayBluetooth : public SystemTrayItem,
+ public BluetoothObserver {
+ public:
+ explicit TrayBluetooth(SystemTray* system_tray);
+ virtual ~TrayBluetooth();
+
+ private:
+ // Overridden from SystemTrayItem.
+ virtual views::View* CreateTrayView(user::LoginStatus status) OVERRIDE;
+ virtual views::View* CreateDefaultView(user::LoginStatus status) OVERRIDE;
+ virtual views::View* CreateDetailedView(user::LoginStatus status) OVERRIDE;
+ virtual void DestroyTrayView() OVERRIDE;
+ virtual void DestroyDefaultView() OVERRIDE;
+ virtual void DestroyDetailedView() OVERRIDE;
+ virtual void UpdateAfterLoginStatusChange(user::LoginStatus status) OVERRIDE;
+
+ // Overridden from BluetoothObserver.
+ virtual void OnBluetoothRefresh() OVERRIDE;
+ virtual void OnBluetoothDiscoveringChanged() OVERRIDE;
+
+ tray::BluetoothDefaultView* default_;
+ tray::BluetoothDetailedView* detailed_;
+
+ DISALLOW_COPY_AND_ASSIGN(TrayBluetooth);
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_SYSTEM_BLUETOOTH_TRAY_BLUETOOTH_H_
diff --git a/chromium/ash/system/brightness/brightness_control_delegate.h b/chromium/ash/system/brightness/brightness_control_delegate.h
new file mode 100644
index 00000000000..0d9e721f5ca
--- /dev/null
+++ b/chromium/ash/system/brightness/brightness_control_delegate.h
@@ -0,0 +1,39 @@
+// Copyright (c) 2012 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 ASH_SYSTEM_BRIGHTNESS_BRIGHTNESS_CONTROL_DELEGATE_H_
+#define ASH_SYSTEM_BRIGHTNESS_BRIGHTNESS_CONTROL_DELEGATE_H_
+
+#include "base/callback.h"
+
+namespace ui {
+class Accelerator;
+} // namespace ui
+
+namespace ash {
+
+// Delegate for controlling the brightness.
+class BrightnessControlDelegate {
+ public:
+ virtual ~BrightnessControlDelegate() {}
+
+ // Handles an accelerator-driven request to decrease or increase the screen
+ // brightness.
+ virtual bool HandleBrightnessDown(const ui::Accelerator& accelerator) = 0;
+ virtual bool HandleBrightnessUp(const ui::Accelerator& accelerator) = 0;
+
+ // Requests that the brightness be set to |percent|, in the range
+ // [0.0, 100.0]. |gradual| specifies whether the transition to the new
+ // brightness should be animated or instantaneous.
+ virtual void SetBrightnessPercent(double percent, bool gradual) = 0;
+
+ // Asynchronously invokes |callback| with the current brightness, in the range
+ // [0.0, 100.0].
+ virtual void GetBrightnessPercent(
+ const base::Callback<void(double)>& callback) = 0;
+};
+
+} // namespace ash
+
+#endif // ASH_SYSTEM_BRIGHTNESS_BRIGHTNESS_CONTROL_DELEGATE_H_
diff --git a/chromium/ash/system/brightness/brightness_observer.h b/chromium/ash/system/brightness/brightness_observer.h
new file mode 100644
index 00000000000..b9b5d06a03f
--- /dev/null
+++ b/chromium/ash/system/brightness/brightness_observer.h
@@ -0,0 +1,22 @@
+// Copyright (c) 2012 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 ASH_SYSTEM_BRIGHTNESS_BRIGHTNESS_OBSERVER_H_
+#define ASH_SYSTEM_BRIGHTNESS_BRIGHTNESS_OBSERVER_H_
+
+#include "ash/ash_export.h"
+
+namespace ash {
+
+class ASH_EXPORT BrightnessObserver {
+ public:
+ virtual ~BrightnessObserver() {}
+
+ // |percent| is in the range [0.0, 100.0].
+ virtual void OnBrightnessChanged(double percent, bool user_initiated) = 0;
+};
+
+} // namespace ash
+
+#endif // ASH_SYSTEM_BRIGHTNESS_BRIGHTNESS_OBSERVER_H_
diff --git a/chromium/ash/system/brightness/tray_brightness.cc b/chromium/ash/system/brightness/tray_brightness.cc
new file mode 100644
index 00000000000..93e415fd2ed
--- /dev/null
+++ b/chromium/ash/system/brightness/tray_brightness.cc
@@ -0,0 +1,219 @@
+// Copyright (c) 2012 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.
+
+#include "ash/system/brightness/tray_brightness.h"
+
+#include "ash/accelerators/accelerator_controller.h"
+#include "ash/ash_constants.h"
+#include "ash/shell.h"
+#include "ash/system/brightness/brightness_control_delegate.h"
+#include "ash/system/tray/fixed_sized_image_view.h"
+#include "ash/system/tray/system_tray_delegate.h"
+#include "ash/system/tray/system_tray_notifier.h"
+#include "ash/system/tray/tray_constants.h"
+#include "base/bind.h"
+#include "base/message_loop/message_loop.h"
+#include "base/strings/utf_string_conversions.h"
+#include "grit/ash_resources.h"
+#include "grit/ash_strings.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "ui/gfx/image/image.h"
+#include "ui/views/controls/button/image_button.h"
+#include "ui/views/controls/image_view.h"
+#include "ui/views/controls/label.h"
+#include "ui/views/controls/slider.h"
+#include "ui/views/layout/box_layout.h"
+#include "ui/views/view.h"
+
+namespace ash {
+namespace internal {
+
+namespace tray {
+
+namespace {
+
+// We don't let the screen brightness go lower than this when it's being
+// adjusted via the slider. Otherwise, if the user doesn't know about the
+// brightness keys, they may turn the backlight off and not know how to turn it
+// back on.
+const double kMinBrightnessPercent = 5.0;
+
+} // namespace
+
+class BrightnessView : public views::View,
+ public views::SliderListener {
+ public:
+ explicit BrightnessView(double initial_percent)
+ : dragging_(false),
+ last_percent_(initial_percent) {
+ SetLayoutManager(new views::BoxLayout(views::BoxLayout::kHorizontal,
+ kTrayPopupPaddingHorizontal, 0, kTrayPopupPaddingBetweenItems));
+
+ views::ImageView* icon = new FixedSizedImageView(0, kTrayPopupItemHeight);
+ gfx::Image image = ui::ResourceBundle::GetSharedInstance().GetImageNamed(
+ IDR_AURA_UBER_TRAY_BRIGHTNESS);
+ icon->SetImage(image.ToImageSkia());
+ AddChildView(icon);
+
+ slider_ = new views::Slider(this, views::Slider::HORIZONTAL);
+ slider_->set_focus_border_color(kFocusBorderColor);
+ slider_->SetValue(static_cast<float>(initial_percent / 100.0));
+ slider_->SetAccessibleName(
+ ui::ResourceBundle::GetSharedInstance().GetLocalizedString(
+ IDS_ASH_STATUS_TRAY_BRIGHTNESS));
+ AddChildView(slider_);
+ }
+
+ virtual ~BrightnessView() {}
+
+ // |percent| is in the range [0.0, 100.0].
+ void SetBrightnessPercent(double percent) {
+ last_percent_ = percent;
+ if (!dragging_)
+ slider_->SetValue(static_cast<float>(percent / 100.0));
+ }
+
+ private:
+ // Overridden from views::View.
+ virtual void OnBoundsChanged(const gfx::Rect& old_bounds) OVERRIDE {
+ int w = width() - slider_->x();
+ slider_->SetSize(gfx::Size(w, slider_->height()));
+ }
+
+ // Overridden from views:SliderListener.
+ virtual void SliderValueChanged(views::Slider* sender,
+ float value,
+ float old_value,
+ views::SliderChangeReason reason) OVERRIDE {
+ DCHECK_EQ(sender, slider_);
+ if (reason != views::VALUE_CHANGED_BY_USER)
+ return;
+#if !defined(OS_MACOSX)
+ AcceleratorController* ac = Shell::GetInstance()->accelerator_controller();
+ if (ac->brightness_control_delegate()) {
+ double percent = std::max(value * 100.0, kMinBrightnessPercent);
+ ac->brightness_control_delegate()->SetBrightnessPercent(percent, true);
+ }
+#endif // OS_MACOSX
+ }
+
+ // Overridden from views:SliderListener.
+ virtual void SliderDragStarted(views::Slider* slider) OVERRIDE {
+ DCHECK_EQ(slider, slider_);
+ dragging_ = true;
+ }
+
+ // Overridden from views:SliderListener.
+ virtual void SliderDragEnded(views::Slider* slider) OVERRIDE {
+ DCHECK_EQ(slider, slider_);
+ dragging_ = false;
+ slider_->SetValue(static_cast<float>(last_percent_ / 100.0));
+ }
+
+ views::Slider* slider_;
+
+ // Is |slider_| currently being dragged?
+ bool dragging_;
+
+ // Last brightness level that we observed, in the range [0.0, 100.0].
+ double last_percent_;
+
+ DISALLOW_COPY_AND_ASSIGN(BrightnessView);
+};
+
+} // namespace tray
+
+TrayBrightness::TrayBrightness(SystemTray* system_tray)
+ : SystemTrayItem(system_tray),
+ weak_ptr_factory_(this),
+ brightness_view_(NULL),
+ is_default_view_(false),
+ current_percent_(100.0),
+ got_current_percent_(false) {
+ // Post a task to get the initial brightness; the BrightnessControlDelegate
+ // isn't created yet.
+ base::MessageLoopForUI::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&TrayBrightness::GetInitialBrightness,
+ weak_ptr_factory_.GetWeakPtr()));
+ Shell::GetInstance()->system_tray_notifier()->AddBrightnessObserver(this);
+}
+
+TrayBrightness::~TrayBrightness() {
+ Shell::GetInstance()->system_tray_notifier()->RemoveBrightnessObserver(this);
+}
+
+void TrayBrightness::GetInitialBrightness() {
+ BrightnessControlDelegate* delegate =
+ Shell::GetInstance()->accelerator_controller()->
+ brightness_control_delegate();
+ // Worrisome, but happens in unit tests, so don't log anything.
+ if (!delegate)
+ return;
+ delegate->GetBrightnessPercent(
+ base::Bind(&TrayBrightness::HandleInitialBrightness,
+ weak_ptr_factory_.GetWeakPtr()));
+}
+
+void TrayBrightness::HandleInitialBrightness(double percent) {
+ if (!got_current_percent_)
+ OnBrightnessChanged(percent, false);
+}
+
+views::View* TrayBrightness::CreateTrayView(user::LoginStatus status) {
+ return NULL;
+}
+
+views::View* TrayBrightness::CreateDefaultView(user::LoginStatus status) {
+ return NULL;
+}
+
+views::View* TrayBrightness::CreateDetailedView(user::LoginStatus status) {
+ CHECK(brightness_view_ == NULL);
+ brightness_view_ = new tray::BrightnessView(current_percent_);
+ is_default_view_ = false;
+ return brightness_view_;
+}
+
+void TrayBrightness::DestroyTrayView() {
+}
+
+void TrayBrightness::DestroyDefaultView() {
+ if (is_default_view_)
+ brightness_view_ = NULL;
+}
+
+void TrayBrightness::DestroyDetailedView() {
+ if (!is_default_view_)
+ brightness_view_ = NULL;
+}
+
+void TrayBrightness::UpdateAfterLoginStatusChange(user::LoginStatus status) {
+}
+
+bool TrayBrightness::ShouldHideArrow() const {
+ return true;
+}
+
+bool TrayBrightness::ShouldShowLauncher() const {
+ return false;
+}
+
+void TrayBrightness::OnBrightnessChanged(double percent, bool user_initiated) {
+ current_percent_ = percent;
+ got_current_percent_ = true;
+
+ if (brightness_view_)
+ brightness_view_->SetBrightnessPercent(percent);
+ if (!user_initiated)
+ return;
+
+ if (brightness_view_)
+ SetDetailedViewCloseDelay(kTrayPopupAutoCloseDelayInSeconds);
+ else
+ PopupDetailedView(kTrayPopupAutoCloseDelayInSeconds, false);
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/system/brightness/tray_brightness.h b/chromium/ash/system/brightness/tray_brightness.h
new file mode 100644
index 00000000000..ce62154404c
--- /dev/null
+++ b/chromium/ash/system/brightness/tray_brightness.h
@@ -0,0 +1,73 @@
+// Copyright (c) 2012 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 ASH_SYSTEM_BRIGHTNESS_TRAY_BRIGHTNESS_H_
+#define ASH_SYSTEM_BRIGHTNESS_TRAY_BRIGHTNESS_H_
+
+#include "ash/system/brightness/brightness_observer.h"
+#include "ash/system/tray/system_tray_item.h"
+#include "base/compiler_specific.h"
+#include "base/memory/weak_ptr.h"
+
+namespace ash {
+namespace internal {
+
+namespace tray {
+class BrightnessView;
+}
+
+class TrayBrightness : public SystemTrayItem,
+ public BrightnessObserver {
+ public:
+ explicit TrayBrightness(SystemTray* system_tray);
+ virtual ~TrayBrightness();
+
+ private:
+ // Sends a request to get the current screen brightness so |current_percent_|
+ // can be initialized.
+ void GetInitialBrightness();
+
+ // Updates |current_percent_| with the initial brightness requested by
+ // GetInitialBrightness(), if we haven't seen the brightness already in the
+ // meantime.
+ void HandleInitialBrightness(double percent);
+
+ // Overridden from SystemTrayItem.
+ virtual views::View* CreateTrayView(user::LoginStatus status) OVERRIDE;
+ virtual views::View* CreateDefaultView(user::LoginStatus status) OVERRIDE;
+ virtual views::View* CreateDetailedView(user::LoginStatus status) OVERRIDE;
+ virtual void DestroyTrayView() OVERRIDE;
+ virtual void DestroyDefaultView() OVERRIDE;
+ virtual void DestroyDetailedView() OVERRIDE;
+ virtual void UpdateAfterLoginStatusChange(user::LoginStatus status) OVERRIDE;
+ virtual bool ShouldHideArrow() const OVERRIDE;
+ virtual bool ShouldShowLauncher() const OVERRIDE;
+
+ // Overridden from BrightnessObserver.
+ virtual void OnBrightnessChanged(double percent,
+ bool user_initiated) OVERRIDE;
+
+ base::WeakPtrFactory<TrayBrightness> weak_ptr_factory_;
+
+ tray::BrightnessView* brightness_view_;
+
+ // Was |brightness_view_| created for CreateDefaultView() rather than
+ // CreateDetailedView()? Used to avoid resetting |brightness_view_|
+ // inappropriately in DestroyDefaultView() or DestroyDetailedView().
+ bool is_default_view_;
+
+ // Brightness level in the range [0.0, 100.0] that we've heard about most
+ // recently.
+ double current_percent_;
+
+ // Has |current_percent_| been initialized?
+ bool got_current_percent_;
+
+ DISALLOW_COPY_AND_ASSIGN(TrayBrightness);
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_SYSTEM_BRIGHTNESS_TRAY_BRIGHTNESS_H_
diff --git a/chromium/ash/system/chromeos/DEPS b/chromium/ash/system/chromeos/DEPS
new file mode 100644
index 00000000000..79b8e8ba13b
--- /dev/null
+++ b/chromium/ash/system/chromeos/DEPS
@@ -0,0 +1,3 @@
+include_rules = [
+ "+chromeos",
+]
diff --git a/chromium/ash/system/chromeos/audio/tray_audio.cc b/chromium/ash/system/chromeos/audio/tray_audio.cc
new file mode 100644
index 00000000000..6ad4362a441
--- /dev/null
+++ b/chromium/ash/system/chromeos/audio/tray_audio.cc
@@ -0,0 +1,606 @@
+// Copyright (c) 2012 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.
+
+#include "ash/system/chromeos/audio/tray_audio.h"
+
+#include <cmath>
+
+#include "ash/ash_constants.h"
+#include "ash/ash_switches.h"
+#include "ash/shell.h"
+#include "ash/system/tray/actionable_view.h"
+#include "ash/system/tray/fixed_sized_scroll_view.h"
+#include "ash/system/tray/hover_highlight_view.h"
+#include "ash/system/tray/system_tray.h"
+#include "ash/system/tray/system_tray_delegate.h"
+#include "ash/system/tray/system_tray_notifier.h"
+#include "ash/system/tray/tray_constants.h"
+#include "ash/volume_control_delegate.h"
+#include "base/strings/utf_string_conversions.h"
+#include "chromeos/audio/cras_audio_handler.h"
+#include "grit/ash_resources.h"
+#include "grit/ash_strings.h"
+#include "third_party/skia/include/core/SkCanvas.h"
+#include "third_party/skia/include/core/SkPaint.h"
+#include "third_party/skia/include/core/SkRect.h"
+#include "third_party/skia/include/effects/SkGradientShader.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "ui/gfx/canvas.h"
+#include "ui/gfx/image/image.h"
+#include "ui/gfx/image/image_skia_operations.h"
+#include "ui/views/controls/button/image_button.h"
+#include "ui/views/controls/image_view.h"
+#include "ui/views/controls/label.h"
+#include "ui/views/controls/slider.h"
+#include "ui/views/layout/box_layout.h"
+#include "ui/views/view.h"
+
+using chromeos::CrasAudioHandler;
+
+namespace ash {
+namespace internal {
+
+namespace {
+const int kVolumeImageWidth = 25;
+const int kVolumeImageHeight = 25;
+const int kBarSeparatorWidth = 25;
+const int kBarSeparatorHeight = 30;
+const int kSliderRightPaddingToVolumeViewEdge = 17;
+const int kExtraPaddingBetweenBarAndMore = 10;
+
+const int kNoAudioDeviceIcon = -1;
+
+// IDR_AURA_UBER_TRAY_VOLUME_LEVELS contains 5 images,
+// The one for mute is at the 0 index and the other
+// four are used for ascending volume levels.
+const int kVolumeLevels = 4;
+
+bool IsAudioMuted() {
+ return CrasAudioHandler::Get()->IsOutputMuted();
+}
+
+float GetVolumeLevel() {
+ return CrasAudioHandler::Get()->GetOutputVolumePercent() / 100.0f;
+}
+
+int GetAudioDeviceIconId(chromeos::AudioDeviceType type) {
+ if (type == chromeos::AUDIO_TYPE_HEADPHONE)
+ return IDR_AURA_UBER_TRAY_AUDIO_HEADPHONE;
+ else if (type == chromeos::AUDIO_TYPE_USB)
+ return IDR_AURA_UBER_TRAY_AUDIO_USB;
+ else if (type == chromeos::AUDIO_TYPE_BLUETOOTH)
+ return IDR_AURA_UBER_TRAY_AUDIO_BLUETOOTH;
+ else if (type == chromeos::AUDIO_TYPE_HDMI)
+ return IDR_AURA_UBER_TRAY_AUDIO_HDMI;
+ else
+ return kNoAudioDeviceIcon;
+}
+
+} // namespace
+
+namespace tray {
+
+class VolumeButton : public views::ToggleImageButton {
+ public:
+ explicit VolumeButton(views::ButtonListener* listener)
+ : views::ToggleImageButton(listener),
+ image_index_(-1) {
+ SetImageAlignment(ALIGN_CENTER, ALIGN_MIDDLE);
+ image_ = ui::ResourceBundle::GetSharedInstance().GetImageNamed(
+ IDR_AURA_UBER_TRAY_VOLUME_LEVELS);
+ SetPreferredSize(gfx::Size(kTrayPopupItemHeight, kTrayPopupItemHeight));
+ Update();
+ }
+
+ virtual ~VolumeButton() {}
+
+ void Update() {
+ float level = GetVolumeLevel();
+ int image_index = IsAudioMuted() ?
+ 0 : (level == 1.0 ?
+ kVolumeLevels :
+ std::max(1, int(std::ceil(level * (kVolumeLevels - 1)))));
+ if (image_index != image_index_) {
+ gfx::Rect region(0, image_index * kVolumeImageHeight,
+ kVolumeImageWidth, kVolumeImageHeight);
+ gfx::ImageSkia image_skia = gfx::ImageSkiaOperations::ExtractSubset(
+ *(image_.ToImageSkia()), region);
+ SetImage(views::CustomButton::STATE_NORMAL, &image_skia);
+ image_index_ = image_index;
+ }
+ SchedulePaint();
+ }
+
+ private:
+ // Overridden from views::View.
+ virtual gfx::Size GetPreferredSize() OVERRIDE {
+ gfx::Size size = views::ToggleImageButton::GetPreferredSize();
+ size.set_height(kTrayPopupItemHeight);
+ return size;
+ }
+
+ gfx::Image image_;
+ int image_index_;
+
+ DISALLOW_COPY_AND_ASSIGN(VolumeButton);
+};
+
+class VolumeSlider : public views::Slider {
+ public:
+ explicit VolumeSlider(views::SliderListener* listener)
+ : views::Slider(listener, views::Slider::HORIZONTAL) {
+ set_focus_border_color(kFocusBorderColor);
+ SetValue(GetVolumeLevel());
+ SetAccessibleName(
+ ui::ResourceBundle::GetSharedInstance().GetLocalizedString(
+ IDS_ASH_STATUS_TRAY_VOLUME));
+ Update();
+ }
+ virtual ~VolumeSlider() {}
+
+ void Update() {
+ UpdateState(!IsAudioMuted());
+ }
+
+ DISALLOW_COPY_AND_ASSIGN(VolumeSlider);
+};
+
+// Vertical bar separator that can be placed on the VolumeView.
+class BarSeparator : public views::View {
+ public:
+ BarSeparator() {}
+ virtual ~BarSeparator() {}
+
+ // Overriden from views::View.
+ virtual gfx::Size GetPreferredSize() OVERRIDE {
+ return gfx::Size(kBarSeparatorWidth, kBarSeparatorHeight);
+ }
+
+ private:
+ virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE {
+ canvas->FillRect(gfx::Rect(width() / 2, 0, 1, height()),
+ kButtonStrokeColor);
+ }
+
+ DISALLOW_COPY_AND_ASSIGN(BarSeparator);
+};
+
+class VolumeView : public ActionableView,
+ public views::ButtonListener,
+ public views::SliderListener {
+ public:
+ VolumeView(SystemTrayItem* owner, bool is_default_view)
+ : owner_(owner),
+ icon_(NULL),
+ slider_(NULL),
+ bar_(NULL),
+ device_type_(NULL),
+ more_(NULL),
+ is_default_view_(is_default_view) {
+ set_focusable(false);
+ SetLayoutManager(new views::BoxLayout(views::BoxLayout::kHorizontal,
+ kTrayPopupPaddingHorizontal, 0, kTrayPopupPaddingBetweenItems));
+
+ icon_ = new VolumeButton(this);
+ AddChildView(icon_);
+
+ slider_ = new VolumeSlider(this);
+ AddChildView(slider_);
+
+ bar_ = new BarSeparator;
+ AddChildView(bar_);
+
+ device_type_ = new views::ImageView;
+ AddChildView(device_type_);
+
+ more_ = new views::ImageView;
+ more_->EnableCanvasFlippingForRTLUI(true);
+ more_->SetImage(ui::ResourceBundle::GetSharedInstance().GetImageNamed(
+ IDR_AURA_UBER_TRAY_MORE).ToImageSkia());
+ AddChildView(more_);
+
+ Update();
+ }
+
+ virtual ~VolumeView() {}
+
+ void Update() {
+ icon_->Update();
+ slider_->Update();
+ UpdateDeviceTypeAndMore();
+ Layout();
+ }
+
+ // Sets volume level on slider_, |percent| is ranged from [0.00] to [1.00].
+ void SetVolumeLevel(float percent) {
+ // Slider's value is in finer granularity than audio volume level(0.01),
+ // there will be a small discrepancy between slider's value and volume level
+ // on audio side. To avoid the jittering in slider UI, do not set change
+ // slider value if the change is less than 1%.
+ if (std::abs(percent-slider_->value()) < 0.01)
+ return;
+ // The change in volume will be reflected via accessibility system events,
+ // so we prevent the UI event from being sent here.
+ slider_->set_enable_accessibility_events(false);
+ slider_->SetValue(percent);
+ // It is possible that the volume was (un)muted, but the actual volume level
+ // did not change. In that case, setting the value of the slider won't
+ // trigger an update. So explicitly trigger an update.
+ Update();
+ slider_->set_enable_accessibility_events(true);
+ }
+
+ private:
+ // Updates bar_, device_type_ icon, and more_ buttons.
+ void UpdateDeviceTypeAndMore() {
+ if (!ash::switches::ShowAudioDeviceMenu() || !is_default_view_) {
+ more_->SetVisible(false);
+ bar_->SetVisible(false);
+ device_type_->SetVisible(false);
+ return;
+ }
+
+ CrasAudioHandler* audio_handler = CrasAudioHandler::Get();
+ bool show_more = audio_handler->has_alternative_output() ||
+ audio_handler->has_alternative_input();
+ more_->SetVisible(show_more);
+
+ // Show output device icon if necessary.
+ chromeos::AudioDevice device;
+ if (!audio_handler->GetActiveOutputDevice(&device))
+ return;
+ int device_icon = GetAudioDeviceIconId(device.type);
+ bar_->SetVisible(show_more);
+ if (device_icon != kNoAudioDeviceIcon) {
+ device_type_->SetVisible(true);
+ device_type_->SetImage(
+ ui::ResourceBundle::GetSharedInstance().GetImageNamed(
+ device_icon).ToImageSkia());
+ } else {
+ device_type_->SetVisible(false);
+ }
+ }
+
+ void HandleVolumeUp(int volume) {
+ CrasAudioHandler* audio_handler = CrasAudioHandler::Get();
+ audio_handler->SetOutputVolumePercent(volume);
+ if (audio_handler->IsOutputMuted() &&
+ !audio_handler->IsOutputVolumeBelowDefaultMuteLvel())
+ audio_handler->SetOutputMute(false);
+ }
+
+ void HandleVolumeDown(int volume) {
+ CrasAudioHandler* audio_handler = CrasAudioHandler::Get();
+ audio_handler->SetOutputVolumePercent(volume);
+ if (audio_handler->IsOutputVolumeBelowDefaultMuteLvel() &&
+ !audio_handler->IsOutputMuted()) {
+ audio_handler->SetOutputMute(true);
+ } else if (!audio_handler->IsOutputVolumeBelowDefaultMuteLvel() &&
+ audio_handler->IsOutputMuted()) {
+ audio_handler->SetOutputMute(false);
+ }
+ }
+
+ // Overridden from views::View.
+ virtual void Layout() OVERRIDE {
+ views::View::Layout();
+
+ if (!more_->visible()) {
+ int w = width() - slider_->bounds().x() -
+ kSliderRightPaddingToVolumeViewEdge;
+ slider_->SetSize(gfx::Size(w, slider_->height()));
+ return;
+ }
+
+ // Make sure the chevron always has the full size.
+ gfx::Size size = more_->GetPreferredSize();
+ gfx::Rect bounds(size);
+ bounds.set_x(width() - size.width() - kTrayPopupPaddingBetweenItems);
+ bounds.set_y((height() - size.height()) / 2);
+ more_->SetBoundsRect(bounds);
+
+ // Layout either bar_ or device_type_ at the left of the more_ button.
+ views::View* view_left_to_more;
+ if (device_type_->visible())
+ view_left_to_more = device_type_;
+ else
+ view_left_to_more = bar_;
+ gfx::Size view_size = view_left_to_more->GetPreferredSize();
+ gfx::Rect view_bounds(view_size);
+ view_bounds.set_x(more_->bounds().x() - view_size.width() -
+ kExtraPaddingBetweenBarAndMore);
+ view_bounds.set_y((height() - view_size.height()) / 2);
+ view_left_to_more->SetBoundsRect(view_bounds);
+
+ // Layout vertical bar next to view_left_to_more if device_type_ is visible.
+ if (device_type_->visible()) {
+ gfx::Size bar_size = bar_->GetPreferredSize();
+ gfx::Rect bar_bounds(bar_size);
+ bar_bounds.set_x(view_left_to_more->bounds().x() - bar_size.width());
+ bar_bounds.set_y((height() - bar_size.height()) / 2);
+ bar_->SetBoundsRect(bar_bounds);
+ }
+
+ // Layout slider, calculate slider width.
+ gfx::Rect slider_bounds = slider_->bounds();
+ slider_bounds.set_width(
+ bar_->bounds().x()
+ - (device_type_->visible() ? 0 : kTrayPopupPaddingBetweenItems)
+ - slider_bounds.x());
+ slider_->SetBoundsRect(slider_bounds);
+ }
+
+ // Overridden from views::ButtonListener.
+ virtual void ButtonPressed(views::Button* sender,
+ const ui::Event& event) OVERRIDE {
+ CHECK(sender == icon_);
+ bool mute_on = !IsAudioMuted();
+ CrasAudioHandler::Get()->SetOutputMute(mute_on);
+ if (!mute_on)
+ CrasAudioHandler::Get()->AdjustOutputVolumeToAudibleLevel();
+ }
+
+ // Overridden from views:SliderListener.
+ virtual void SliderValueChanged(views::Slider* sender,
+ float value,
+ float old_value,
+ views::SliderChangeReason reason) OVERRIDE {
+ if (reason == views::VALUE_CHANGED_BY_USER) {
+ int volume = value * 100.0f;
+ int old_volume = CrasAudioHandler::Get()->GetOutputVolumePercent();
+ // Do not call change audio volume if the difference is less than
+ // 1%, which is beyond cras audio api's granularity for output volume.
+ if (std::abs(volume - old_volume) < 1)
+ return;
+ if (volume > old_volume)
+ HandleVolumeUp(volume);
+ else
+ HandleVolumeDown(volume);
+ }
+ icon_->Update();
+ }
+
+ // Overriden from ActinableView.
+ virtual bool PerformAction(const ui::Event& event) OVERRIDE {
+ if (!more_->visible())
+ return false;
+ owner_->TransitionDetailedView();
+ return true;
+ }
+
+ SystemTrayItem* owner_;
+ VolumeButton* icon_;
+ VolumeSlider* slider_;
+ BarSeparator* bar_;
+ views::ImageView* device_type_;
+ views::ImageView* more_;
+ bool is_default_view_;
+
+ DISALLOW_COPY_AND_ASSIGN(VolumeView);
+};
+
+class AudioDetailedView : public TrayDetailsView,
+ public ViewClickListener {
+ public:
+ AudioDetailedView(SystemTrayItem* owner, user::LoginStatus login)
+ : TrayDetailsView(owner),
+ login_(login) {
+ CreateItems();
+ Update();
+ }
+
+ virtual ~AudioDetailedView() {
+ }
+
+ void Update() {
+ UpdateAudioDevices();
+ Layout();
+ }
+
+ private:
+ void CreateItems() {
+ CreateScrollableList();
+ CreateHeaderEntry();
+ }
+
+ void CreateHeaderEntry() {
+ CreateSpecialRow(IDS_ASH_STATUS_TRAY_AUDIO, this);
+ }
+
+ void UpdateAudioDevices() {
+ output_devices_.clear();
+ input_devices_.clear();
+ chromeos::AudioDeviceList devices;
+ CrasAudioHandler::Get()->GetAudioDevices(&devices);
+ for (size_t i = 0; i < devices.size(); ++i) {
+ if (devices[i].is_input)
+ input_devices_.push_back(devices[i]);
+ else
+ output_devices_.push_back(devices[i]);
+ }
+ UpdateScrollableList();
+ }
+
+ void UpdateScrollableList() {
+ scroll_content()->RemoveAllChildViews(true);
+ device_map_.clear();
+
+ // Add audio output devices.
+ AddScrollListItem(
+ l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_AUDIO_OUTPUT),
+ gfx::Font::BOLD,
+ false); /* no checkmark */
+ for (size_t i = 0; i < output_devices_.size(); ++i) {
+ HoverHighlightView* container = AddScrollListItem(
+ UTF8ToUTF16(output_devices_[i].display_name),
+ gfx::Font::NORMAL,
+ output_devices_[i].active); /* checkmark if active */
+ device_map_[container] = output_devices_[i];
+ }
+
+ AddScrollSeparator();
+
+ // Add audio input devices.
+ AddScrollListItem(
+ l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_AUDIO_INPUT),
+ gfx::Font::BOLD,
+ false); /* no checkmark */
+ for (size_t i = 0; i < input_devices_.size(); ++i) {
+ HoverHighlightView* container = AddScrollListItem(
+ UTF8ToUTF16(input_devices_[i].display_name),
+ gfx::Font::NORMAL,
+ input_devices_[i].active); /* checkmark if active */
+ device_map_[container] = input_devices_[i];
+ }
+
+ scroll_content()->SizeToPreferredSize();
+ scroller()->Layout();
+ }
+
+ HoverHighlightView* AddScrollListItem(const string16& text,
+ gfx::Font::FontStyle style,
+ bool checked) {
+ HoverHighlightView* container = new HoverHighlightView(this);
+ container->AddCheckableLabel(text, style, checked);
+ scroll_content()->AddChildView(container);
+ return container;
+ }
+
+ // Overridden from ViewClickListener.
+ virtual void OnViewClicked(views::View* sender) OVERRIDE {
+ if (sender == footer()->content()) {
+ owner()->system_tray()->ShowDefaultView(BUBBLE_USE_EXISTING);
+ } else {
+ AudioDeviceMap::iterator iter = device_map_.find(sender);
+ if (iter == device_map_.end())
+ return;
+ chromeos::AudioDevice& device = iter->second;
+ CrasAudioHandler::Get()->SwitchToDevice(device);
+ }
+ }
+
+ typedef std::map<views::View*, chromeos::AudioDevice> AudioDeviceMap;
+
+ user::LoginStatus login_;
+ chromeos::AudioDeviceList output_devices_;
+ chromeos::AudioDeviceList input_devices_;
+ AudioDeviceMap device_map_;
+
+ DISALLOW_COPY_AND_ASSIGN(AudioDetailedView);
+};
+
+} // namespace tray
+
+TrayAudio::TrayAudio(SystemTray* system_tray)
+ : TrayImageItem(system_tray, IDR_AURA_UBER_TRAY_VOLUME_MUTE),
+ volume_view_(NULL),
+ audio_detail_(NULL),
+ pop_up_volume_view_(false) {
+ CrasAudioHandler::Get()->AddAudioObserver(this);
+}
+
+TrayAudio::~TrayAudio() {
+ if (CrasAudioHandler::IsInitialized())
+ CrasAudioHandler::Get()->RemoveAudioObserver(this);
+}
+
+bool TrayAudio::GetInitialVisibility() {
+ return IsAudioMuted();
+}
+
+views::View* TrayAudio::CreateDefaultView(user::LoginStatus status) {
+ volume_view_ = new tray::VolumeView(this, true);
+ return volume_view_;
+}
+
+views::View* TrayAudio::CreateDetailedView(user::LoginStatus status) {
+ if (!ash::switches::ShowAudioDeviceMenu() || pop_up_volume_view_) {
+ volume_view_ = new tray::VolumeView(this, false);
+ return volume_view_;
+ } else {
+ audio_detail_ = new tray::AudioDetailedView(this, status);
+ return audio_detail_;
+ }
+}
+
+void TrayAudio::DestroyDefaultView() {
+ volume_view_ = NULL;
+}
+
+void TrayAudio::DestroyDetailedView() {
+ if (audio_detail_) {
+ audio_detail_ = NULL;
+ } else if (volume_view_) {
+ volume_view_ = NULL;
+ pop_up_volume_view_ = false;
+ }
+}
+
+bool TrayAudio::ShouldHideArrow() const {
+ return true;
+}
+
+bool TrayAudio::ShouldShowLauncher() const {
+ return false;
+}
+
+void TrayAudio::OnOutputVolumeChanged() {
+ float percent = GetVolumeLevel();
+ if (tray_view())
+ tray_view()->SetVisible(GetInitialVisibility());
+
+ if (volume_view_) {
+ volume_view_->SetVolumeLevel(percent);
+ SetDetailedViewCloseDelay(kTrayPopupAutoCloseDelayInSeconds);
+ return;
+ }
+ pop_up_volume_view_ = true;
+ PopupDetailedView(kTrayPopupAutoCloseDelayInSeconds, false);
+}
+
+void TrayAudio::OnOutputMuteChanged() {
+ if (tray_view())
+ tray_view()->SetVisible(GetInitialVisibility());
+
+ if (volume_view_) {
+ volume_view_->Update();
+ SetDetailedViewCloseDelay(kTrayPopupAutoCloseDelayInSeconds);
+ } else {
+ pop_up_volume_view_ = true;
+ PopupDetailedView(kTrayPopupAutoCloseDelayInSeconds, false);
+ }
+}
+
+void TrayAudio::OnInputGainChanged() {
+}
+
+void TrayAudio::OnInputMuteChanged() {
+}
+
+void TrayAudio::OnAudioNodesChanged() {
+ Update();
+}
+
+void TrayAudio::OnActiveOutputNodeChanged() {
+ Update();
+}
+
+void TrayAudio::OnActiveInputNodeChanged() {
+ Update();
+}
+
+void TrayAudio::Update() {
+ if (tray_view())
+ tray_view()->SetVisible(GetInitialVisibility());
+ if (audio_detail_)
+ audio_detail_->Update();
+ if (volume_view_) {
+ volume_view_->SetVolumeLevel(GetVolumeLevel());
+ volume_view_->Update();
+ }
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/system/chromeos/audio/tray_audio.h b/chromium/ash/system/chromeos/audio/tray_audio.h
new file mode 100644
index 00000000000..d055a54b037
--- /dev/null
+++ b/chromium/ash/system/chromeos/audio/tray_audio.h
@@ -0,0 +1,61 @@
+// Copyright (c) 2012 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 ASH_SYSTEM_CHROMEOS_AUDIO_TRAY_AUDIO_H_
+#define ASH_SYSTEM_CHROMEOS_AUDIO_TRAY_AUDIO_H_
+
+#include "ash/system/tray/tray_image_item.h"
+#include "chromeos/audio/cras_audio_handler.h"
+
+namespace ash {
+namespace internal {
+
+namespace tray {
+class VolumeView;
+class AudioDetailedView;
+}
+
+class TrayAudio : public TrayImageItem,
+ public chromeos::CrasAudioHandler::AudioObserver {
+ public:
+ explicit TrayAudio(SystemTray* system_tray);
+ virtual ~TrayAudio();
+
+ private:
+ // Overridden from TrayImageItem.
+ virtual bool GetInitialVisibility() OVERRIDE;
+
+ // Overridden from SystemTrayItem.
+ virtual views::View* CreateDefaultView(user::LoginStatus status) OVERRIDE;
+ virtual views::View* CreateDetailedView(user::LoginStatus status) OVERRIDE;
+ virtual void DestroyDefaultView() OVERRIDE;
+ virtual void DestroyDetailedView() OVERRIDE;
+ virtual bool ShouldHideArrow() const OVERRIDE;
+ virtual bool ShouldShowLauncher() const OVERRIDE;
+
+ // Overridden from chromeos::CrasAudioHandler::AudioObserver.
+ virtual void OnOutputVolumeChanged() OVERRIDE;
+ virtual void OnOutputMuteChanged() OVERRIDE;
+ virtual void OnInputGainChanged() OVERRIDE;
+ virtual void OnInputMuteChanged() OVERRIDE;
+ virtual void OnAudioNodesChanged() OVERRIDE;
+ virtual void OnActiveOutputNodeChanged() OVERRIDE;
+ virtual void OnActiveInputNodeChanged() OVERRIDE;
+
+ void Update();
+
+ tray::VolumeView* volume_view_;
+ tray::AudioDetailedView* audio_detail_;
+
+ // True if VolumeView should be created for accelerator pop up;
+ // Otherwise, it should be created for detailed view in ash tray bubble.
+ bool pop_up_volume_view_;
+
+ DISALLOW_COPY_AND_ASSIGN(TrayAudio);
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_SYSTEM_CHROMEOS_AUDIO_TRAY_AUDIO_H_
diff --git a/chromium/ash/system/chromeos/enterprise/enterprise_domain_observer.h b/chromium/ash/system/chromeos/enterprise/enterprise_domain_observer.h
new file mode 100644
index 00000000000..5df8a4262d8
--- /dev/null
+++ b/chromium/ash/system/chromeos/enterprise/enterprise_domain_observer.h
@@ -0,0 +1,20 @@
+// Copyright (c) 2012 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 ASH_SYSTEM_CHROMEOS_ENTERPISE_ENTERPRISE_DOMAIN_OBSERVER_H_
+#define ASH_SYSTEM_CHROMEOS_ENTERPISE_ENTERPRISE_DOMAIN_OBSERVER_H_
+
+namespace ash {
+
+class EnterpriseDomainObserver {
+ public:
+ virtual ~EnterpriseDomainObserver() {}
+
+ virtual void OnEnterpriseDomainChanged() = 0;
+};
+
+} // namespace ash
+
+#endif // ASH_SYSTEM_CHROMEOS_ENTERPISE_ENTERPRISE_DOMAIN_OBSERVER_H_
+
diff --git a/chromium/ash/system/chromeos/enterprise/tray_enterprise.cc b/chromium/ash/system/chromeos/enterprise/tray_enterprise.cc
new file mode 100644
index 00000000000..05ff799283e
--- /dev/null
+++ b/chromium/ash/system/chromeos/enterprise/tray_enterprise.cc
@@ -0,0 +1,60 @@
+// Copyright (c) 2012 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.
+
+#include "ash/system/chromeos/enterprise/tray_enterprise.h"
+
+#include "ash/system/chromeos/label_tray_view.h"
+#include "ash/system/tray/system_tray_notifier.h"
+#include "ash/system/user/login_status.h"
+#include "base/logging.h"
+#include "grit/ash_resources.h"
+
+namespace ash {
+namespace internal {
+
+TrayEnterprise::TrayEnterprise(SystemTray* system_tray)
+ : SystemTrayItem(system_tray),
+ tray_view_(NULL) {
+ Shell::GetInstance()->system_tray_notifier()->
+ AddEnterpriseDomainObserver(this);
+}
+
+TrayEnterprise::~TrayEnterprise() {
+ Shell::GetInstance()->system_tray_notifier()->
+ RemoveEnterpriseDomainObserver(this);
+}
+
+void TrayEnterprise::UpdateEnterpriseMessage() {
+ base::string16 message = Shell::GetInstance()->system_tray_delegate()->
+ GetEnterpriseMessage();
+ if (tray_view_)
+ tray_view_->SetMessage(message);
+}
+
+views::View* TrayEnterprise::CreateDefaultView(user::LoginStatus status) {
+ CHECK(tray_view_ == NULL);
+ // For public accounts, enterprise ownership is indicated in the user details
+ // instead.
+ if (status == ash::user::LOGGED_IN_PUBLIC)
+ return NULL;
+ tray_view_ = new LabelTrayView(this, IDR_AURA_UBER_TRAY_ENTERPRISE_DARK);
+ UpdateEnterpriseMessage();
+ return tray_view_;
+}
+
+void TrayEnterprise::DestroyDefaultView() {
+ tray_view_ = NULL;
+}
+
+void TrayEnterprise::OnEnterpriseDomainChanged() {
+ UpdateEnterpriseMessage();
+}
+
+void TrayEnterprise::OnViewClicked(views::View* sender) {
+ Shell::GetInstance()->system_tray_delegate()->ShowEnterpriseInfo();
+}
+
+} // namespace internal
+} // namespace ash
+
diff --git a/chromium/ash/system/chromeos/enterprise/tray_enterprise.h b/chromium/ash/system/chromeos/enterprise/tray_enterprise.h
new file mode 100644
index 00000000000..697dfcd61d5
--- /dev/null
+++ b/chromium/ash/system/chromeos/enterprise/tray_enterprise.h
@@ -0,0 +1,52 @@
+// Copyright (c) 2012 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 ASH_SYSTEM_CHROMEOS_ENTERPRISE_TRAY_ENTERPRISE_H
+#define ASH_SYSTEM_CHROMEOS_ENTERPRISE_TRAY_ENTERPRISE_H
+
+#include "ash/system/chromeos/enterprise/enterprise_domain_observer.h"
+#include "ash/system/tray/system_tray_item.h"
+#include "ash/system/tray/view_click_listener.h"
+
+namespace ash {
+class SystemTray;
+}
+
+namespace ash {
+namespace internal {
+
+class LabelTrayView;
+
+class TrayEnterprise : public SystemTrayItem,
+ public ViewClickListener,
+ public EnterpriseDomainObserver {
+ public:
+ explicit TrayEnterprise(SystemTray* system_tray);
+ virtual ~TrayEnterprise();
+
+ // If message is not empty updates content of default view, otherwise hides
+ // tray items.
+ void UpdateEnterpriseMessage();
+
+ // Overridden from SystemTrayItem.
+ virtual views::View* CreateDefaultView(user::LoginStatus status) OVERRIDE;
+ virtual void DestroyDefaultView() OVERRIDE;
+
+ // Overridden from EnterpriseDomainObserver.
+ virtual void OnEnterpriseDomainChanged() OVERRIDE;
+
+ // Overridden from ViewClickListener.
+ virtual void OnViewClicked(views::View* sender) OVERRIDE;
+
+ private:
+ LabelTrayView* tray_view_;
+
+ DISALLOW_COPY_AND_ASSIGN(TrayEnterprise);
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_SYSTEM_CHROMEOS_ENTERPRISE_TRAY_ENTERPRISE_H
+
diff --git a/chromium/ash/system/chromeos/keyboard_brightness_controller.cc b/chromium/ash/system/chromeos/keyboard_brightness_controller.cc
new file mode 100644
index 00000000000..bad8d09cde4
--- /dev/null
+++ b/chromium/ash/system/chromeos/keyboard_brightness_controller.cc
@@ -0,0 +1,40 @@
+// Copyright (c) 2012 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.
+
+#include "ash/system/chromeos/keyboard_brightness_controller.h"
+
+#include "ash/shell.h"
+#include "ash/shell_delegate.h"
+#include "chromeos/dbus/dbus_thread_manager.h"
+#include "chromeos/dbus/power_manager_client.h"
+#include "content/public/browser/user_metrics.h"
+#include "ui/base/accelerators/accelerator.h"
+
+namespace ash {
+
+bool KeyboardBrightnessController::HandleKeyboardBrightnessDown(
+ const ui::Accelerator& accelerator) {
+ if (accelerator.key_code() == ui::VKEY_BRIGHTNESS_DOWN) {
+ Shell::GetInstance()->delegate()->RecordUserMetricsAction(
+ UMA_ACCEL_KEYBOARD_BRIGHTNESS_DOWN_F6);
+ }
+
+ chromeos::DBusThreadManager::Get()->GetPowerManagerClient()->
+ DecreaseKeyboardBrightness();
+ return true;
+}
+
+bool KeyboardBrightnessController::HandleKeyboardBrightnessUp(
+ const ui::Accelerator& accelerator) {
+ if (accelerator.key_code() == ui::VKEY_BRIGHTNESS_UP) {
+ Shell::GetInstance()->delegate()->RecordUserMetricsAction(
+ UMA_ACCEL_KEYBOARD_BRIGHTNESS_UP_F7);
+ }
+
+ chromeos::DBusThreadManager::Get()->GetPowerManagerClient()->
+ IncreaseKeyboardBrightness();
+ return true;
+}
+
+} // namespace ash
diff --git a/chromium/ash/system/chromeos/keyboard_brightness_controller.h b/chromium/ash/system/chromeos/keyboard_brightness_controller.h
new file mode 100644
index 00000000000..1afc94b1f6b
--- /dev/null
+++ b/chromium/ash/system/chromeos/keyboard_brightness_controller.h
@@ -0,0 +1,35 @@
+// Copyright (c) 2012 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 ASH_SYSTEM_CHROMEOS_KEYBOARD_BRIGHTNESS_CONTROLLER_H_
+#define ASH_SYSTEM_CHROMEOS_KEYBOARD_BRIGHTNESS_CONTROLLER_H_
+
+#include "ash/ash_export.h"
+#include "ash/system/keyboard_brightness/keyboard_brightness_control_delegate.h"
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+
+namespace ash {
+
+// A class which controls keyboard brightness when Alt+F6, Alt+F7 or a
+// multimedia key for keyboard brightness is pressed.
+class ASH_EXPORT KeyboardBrightnessController
+ : public KeyboardBrightnessControlDelegate {
+ public:
+ KeyboardBrightnessController() {}
+ virtual ~KeyboardBrightnessController() {}
+
+ private:
+ // Overridden from KeyboardBrightnessControlDelegate:
+ virtual bool HandleKeyboardBrightnessDown(
+ const ui::Accelerator& accelerator) OVERRIDE;
+ virtual bool HandleKeyboardBrightnessUp(
+ const ui::Accelerator& accelerator) OVERRIDE;
+
+ DISALLOW_COPY_AND_ASSIGN(KeyboardBrightnessController);
+};
+
+} // namespace ash
+
+#endif // ASH_SYSTEM_CHROMEOS_KEYBOARD_BRIGHTNESS_CONTROLLER_H_
diff --git a/chromium/ash/system/chromeos/label_tray_view.cc b/chromium/ash/system/chromeos/label_tray_view.cc
new file mode 100644
index 00000000000..5af294efe09
--- /dev/null
+++ b/chromium/ash/system/chromeos/label_tray_view.cc
@@ -0,0 +1,68 @@
+// Copyright (c) 2012 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.
+
+#include "ash/system/chromeos/label_tray_view.h"
+
+#include "ash/system/tray/hover_highlight_view.h"
+#include "ash/system/tray/tray_constants.h"
+#include "ash/system/tray/view_click_listener.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "ui/gfx/font.h"
+#include "ui/views/controls/label.h"
+#include "ui/views/layout/fill_layout.h"
+
+namespace ash {
+namespace internal {
+
+LabelTrayView::LabelTrayView(ViewClickListener* click_listener,
+ int icon_resource_id)
+ : click_listener_(click_listener),
+ icon_resource_id_(icon_resource_id) {
+ SetLayoutManager(new views::FillLayout());
+ SetVisible(false);
+}
+
+LabelTrayView::~LabelTrayView() {
+}
+
+void LabelTrayView::SetMessage(const base::string16& message) {
+ if (message_ == message)
+ return;
+
+ message_ = message;
+ RemoveAllChildViews(true);
+ if (!message_.empty()) {
+ AddChildView(CreateChildView(message_));
+ SetVisible(true);
+ } else {
+ SetVisible(false);
+ }
+}
+
+views::View* LabelTrayView::CreateChildView(
+ const base::string16& message) const {
+ HoverHighlightView* child = new HoverHighlightView(click_listener_);
+ if (icon_resource_id_) {
+ ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
+ const gfx::ImageSkia* icon = rb.GetImageSkiaNamed(icon_resource_id_);
+ child->AddIconAndLabel(*icon, message, gfx::Font::NORMAL);
+ child->set_border(
+ views::Border::CreateEmptyBorder(0, kTrayPopupPaddingHorizontal,
+ 0, kTrayPopupPaddingHorizontal));
+ child->text_label()->SetMultiLine(true);
+ child->text_label()->SizeToFit(kTrayNotificationContentsWidth);
+ } else {
+ child->AddLabel(message, gfx::Font::NORMAL);
+ child->text_label()->SetMultiLine(true);
+ child->text_label()->SizeToFit(kTrayNotificationContentsWidth +
+ kNotificationIconWidth);
+ }
+ child->text_label()->SetAllowCharacterBreak(true);
+ child->SetExpandable(true);
+ child->SetVisible(true);
+ return child;
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/system/chromeos/label_tray_view.h b/chromium/ash/system/chromeos/label_tray_view.h
new file mode 100644
index 00000000000..b29c37bee56
--- /dev/null
+++ b/chromium/ash/system/chromeos/label_tray_view.h
@@ -0,0 +1,37 @@
+// Copyright (c) 2012 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 ASH_SYSTEM_CHROMEOS_LABEL_TRAY_VIEW_H_
+#define ASH_SYSTEM_CHROMEOS_LABEL_TRAY_VIEW_H_
+
+#include "base/strings/string16.h"
+#include "ui/views/view.h"
+
+namespace ash {
+namespace internal {
+
+class ViewClickListener;
+
+// View for simple information in tray. Automatically hides when message is
+// empty. Supports multiline messages.
+
+class LabelTrayView : public views::View {
+ public:
+ LabelTrayView(ViewClickListener* click_listener, int icon_resource_id);
+ virtual ~LabelTrayView();
+ void SetMessage(const base::string16& message);
+ private:
+ views::View* CreateChildView(const base::string16& message) const;
+
+ ViewClickListener* click_listener_;
+ int icon_resource_id_;
+ base::string16 message_;
+
+ DISALLOW_COPY_AND_ASSIGN(LabelTrayView);
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_SYSTEM_CHROMEOS_LABEL_TRAY_VIEW_H_
diff --git a/chromium/ash/system/chromeos/managed/tray_locally_managed_user.cc b/chromium/ash/system/chromeos/managed/tray_locally_managed_user.cc
new file mode 100644
index 00000000000..49dea0c77d1
--- /dev/null
+++ b/chromium/ash/system/chromeos/managed/tray_locally_managed_user.cc
@@ -0,0 +1,95 @@
+// Copyright (c) 2012 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.
+
+#include "ash/system/chromeos/managed/tray_locally_managed_user.h"
+
+#include "ash/system/chromeos/label_tray_view.h"
+#include "ash/system/tray/system_tray_notifier.h"
+#include "ash/system/tray/tray_notification_view.h"
+#include "ash/system/user/login_status.h"
+#include "base/logging.h"
+#include "grit/ash_resources.h"
+#include "ui/message_center/message_center.h"
+#include "ui/message_center/notification.h"
+#include "ui/message_center/notification_delegate.h"
+
+using message_center::Notification;
+
+namespace ash {
+namespace internal {
+namespace {
+
+const char kLocallyManagedUserNotificationId[] =
+ "chrome://user/locally-managed";
+
+void CreateOrUpdateNotification(const base::string16& new_message) {
+ scoped_ptr<Notification> notification(new Notification(
+ message_center::NOTIFICATION_TYPE_SIMPLE,
+ kLocallyManagedUserNotificationId,
+ new_message,
+ base::string16() /* body is empty */,
+ gfx::Image() /* icon */,
+ base::string16() /* display_source */,
+ std::string() /* extension_id */,
+ message_center::RichNotificationData(),
+ NULL /* no delegate */));
+ notification->SetSystemPriority();
+ message_center::MessageCenter::Get()->AddNotification(notification.Pass());
+}
+
+} // namespace
+
+TrayLocallyManagedUser::TrayLocallyManagedUser(SystemTray* system_tray)
+ : SystemTrayItem(system_tray),
+ tray_view_(NULL),
+ status_(ash::user::LOGGED_IN_NONE) {
+}
+
+TrayLocallyManagedUser::~TrayLocallyManagedUser() {
+}
+
+void TrayLocallyManagedUser::UpdateMessage() {
+ base::string16 message = Shell::GetInstance()->system_tray_delegate()->
+ GetLocallyManagedUserMessage();
+ if (tray_view_)
+ tray_view_->SetMessage(message);
+ if (message_center::MessageCenter::Get()->HasNotification(
+ kLocallyManagedUserNotificationId)) {
+ CreateOrUpdateNotification(message);
+ }
+}
+
+views::View* TrayLocallyManagedUser::CreateDefaultView(
+ user::LoginStatus status) {
+ CHECK(tray_view_ == NULL);
+ if (status != ash::user::LOGGED_IN_LOCALLY_MANAGED)
+ return NULL;
+
+ tray_view_ = new LabelTrayView(this, IDR_AURA_UBER_TRAY_MANAGED_USER);
+ UpdateMessage();
+ return tray_view_;
+}
+
+void TrayLocallyManagedUser::DestroyDefaultView() {
+ tray_view_ = NULL;
+}
+
+void TrayLocallyManagedUser::OnViewClicked(views::View* sender) {
+ Shell::GetInstance()->system_tray_delegate()->ShowLocallyManagedUserInfo();
+}
+
+void TrayLocallyManagedUser::UpdateAfterLoginStatusChange(
+ user::LoginStatus status) {
+ if (status == status_)
+ return;
+ if (status == ash::user::LOGGED_IN_LOCALLY_MANAGED &&
+ status_ != ash::user::LOGGED_IN_LOCKED) {
+ SystemTrayDelegate* delegate = Shell::GetInstance()->system_tray_delegate();
+ CreateOrUpdateNotification(delegate->GetLocallyManagedUserMessage());
+ }
+ status_ = status;
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/system/chromeos/managed/tray_locally_managed_user.h b/chromium/ash/system/chromeos/managed/tray_locally_managed_user.h
new file mode 100644
index 00000000000..e02863461c0
--- /dev/null
+++ b/chromium/ash/system/chromeos/managed/tray_locally_managed_user.h
@@ -0,0 +1,49 @@
+// Copyright (c) 2012 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 ASH_SYSTEM_CHROMEOS_LOCALLY_MANAGED_TRAY_LOCALLY_MANAGED_USER_H
+#define ASH_SYSTEM_CHROMEOS_LOCALLY_MANAGED_TRAY_LOCALLY_MANAGED_USER_H
+
+#include "ash/system/tray/system_tray_item.h"
+#include "ash/system/tray/view_click_listener.h"
+
+namespace ash {
+class SystemTray;
+}
+
+namespace ash {
+namespace internal {
+
+class LabelTrayView;
+
+class TrayLocallyManagedUser : public SystemTrayItem,
+ public ViewClickListener {
+ public:
+ explicit TrayLocallyManagedUser(SystemTray* system_tray);
+ virtual ~TrayLocallyManagedUser();
+
+ // If message is not empty updates content of default view, otherwise hides
+ // tray items.
+ void UpdateMessage();
+
+ // Overridden from SystemTrayItem.
+ virtual views::View* CreateDefaultView(user::LoginStatus status) OVERRIDE;
+ virtual void DestroyDefaultView() OVERRIDE;
+ virtual void UpdateAfterLoginStatusChange(user::LoginStatus status) OVERRIDE;
+
+ // Overridden from ViewClickListener.
+ virtual void OnViewClicked(views::View* sender) OVERRIDE;
+
+ private:
+ LabelTrayView* tray_view_;
+ // Previous login status to avoid showing notification upon unlock.
+ user::LoginStatus status_;
+
+ DISALLOW_COPY_AND_ASSIGN(TrayLocallyManagedUser);
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_SYSTEM_CHROMEOS_LOCALLY_MANAGED_TRAY_LOCALLY_MANAGED_USER_H
diff --git a/chromium/ash/system/chromeos/network/network_connect.cc b/chromium/ash/system/chromeos/network/network_connect.cc
new file mode 100644
index 00000000000..3aca1622223
--- /dev/null
+++ b/chromium/ash/system/chromeos/network/network_connect.cc
@@ -0,0 +1,397 @@
+// Copyright (c) 2013 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.
+
+#include "ash/system/chromeos/network/network_connect.h"
+
+#include "ash/shell.h"
+#include "ash/system/chromeos/network/network_observer.h"
+#include "ash/system/chromeos/network/network_state_notifier.h"
+#include "ash/system/tray/system_tray_delegate.h"
+#include "ash/system/tray/system_tray_notifier.h"
+#include "base/bind.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/values.h"
+#include "chromeos/login/login_state.h"
+#include "chromeos/network/device_state.h"
+#include "chromeos/network/network_configuration_handler.h"
+#include "chromeos/network/network_connection_handler.h"
+#include "chromeos/network/network_event_log.h"
+#include "chromeos/network/network_handler_callbacks.h"
+#include "chromeos/network/network_profile.h"
+#include "chromeos/network/network_profile_handler.h"
+#include "chromeos/network/network_state.h"
+#include "chromeos/network/network_state_handler.h"
+#include "grit/ash_strings.h"
+#include "third_party/cros_system_api/dbus/service_constants.h"
+#include "ui/base/l10n/l10n_util.h"
+
+using chromeos::DeviceState;
+using chromeos::NetworkConfigurationHandler;
+using chromeos::NetworkConnectionHandler;
+using chromeos::NetworkHandler;
+using chromeos::NetworkProfile;
+using chromeos::NetworkProfileHandler;
+using chromeos::NetworkState;
+
+namespace ash {
+
+namespace {
+
+// TODO(stevenjb): This should be in service_constants.h
+const char kErrorInProgress[] = "org.chromium.flimflam.Error.InProgress";
+
+// Returns true for carriers that can be activated through Shill instead of
+// through a WebUI dialog.
+bool IsDirectActivatedCarrier(const std::string& carrier) {
+ if (carrier == shill::kCarrierSprint)
+ return true;
+ return false;
+}
+
+void ShowErrorNotification(const std::string& error,
+ const std::string& service_path) {
+ Shell::GetInstance()->system_tray_notifier()->network_state_notifier()->
+ ShowNetworkConnectError(error, service_path);
+}
+
+void OnConnectFailed(const std::string& service_path,
+ gfx::NativeWindow owning_window,
+ const std::string& error_name,
+ scoped_ptr<base::DictionaryValue> error_data) {
+ NET_LOG_ERROR("Connect Failed: " + error_name, service_path);
+
+ // If a new connect attempt canceled this connect, no need to notify the user.
+ if (error_name == NetworkConnectionHandler::kErrorConnectCanceled)
+ return;
+
+ if (error_name == NetworkConnectionHandler::kErrorPassphraseRequired ||
+ error_name == NetworkConnectionHandler::kErrorConfigurationRequired ||
+ error_name == NetworkConnectionHandler::kErrorAuthenticationRequired) {
+ ash::Shell::GetInstance()->system_tray_delegate()->ConfigureNetwork(
+ service_path);
+ return;
+ }
+
+ if (error_name == NetworkConnectionHandler::kErrorCertificateRequired) {
+ ash::Shell::GetInstance()->system_tray_delegate()->EnrollOrConfigureNetwork(
+ service_path, owning_window);
+ return;
+ }
+
+ if (error_name == NetworkConnectionHandler::kErrorActivationRequired) {
+ network_connect::ActivateCellular(service_path);
+ return;
+ }
+
+ if (error_name == NetworkConnectionHandler::kErrorConnected ||
+ error_name == NetworkConnectionHandler::kErrorConnecting) {
+ ash::Shell::GetInstance()->system_tray_delegate()->ShowNetworkSettings(
+ service_path);
+ return;
+ }
+
+ // ConnectFailed or unknown error; show a notification.
+ ShowErrorNotification(error_name, service_path);
+
+ // Show a configure dialog for ConnectFailed errors.
+ if (error_name != NetworkConnectionHandler::kErrorConnectFailed)
+ return;
+
+ // If Shill reports an InProgress error, don't try to configure the network.
+ std::string dbus_error_name;
+ error_data.get()->GetString(
+ chromeos::network_handler::kDbusErrorName, &dbus_error_name);
+ if (dbus_error_name == kErrorInProgress)
+ return;
+
+ ash::Shell::GetInstance()->system_tray_delegate()->ConfigureNetwork(
+ service_path);
+}
+
+void OnConnectSucceeded(const std::string& service_path) {
+ NET_LOG_USER("Connect Succeeded", service_path);
+ ash::Shell::GetInstance()->system_tray_notifier()->NotifyClearNetworkMessage(
+ NetworkObserver::ERROR_CONNECT_FAILED);
+}
+
+// If |check_error_state| is true, error state for the network is checked,
+// otherwise any current error state is ignored (e.g. for recently configured
+// networks or repeat connect attempts). |owning_window| will be used to parent
+// any configuration UI on failure and may be NULL (in which case the default
+// window will be used).
+void CallConnectToNetwork(const std::string& service_path,
+ bool check_error_state,
+ gfx::NativeWindow owning_window) {
+ NET_LOG_USER("ConnectToNetwork", service_path);
+
+ ash::Shell::GetInstance()->system_tray_notifier()->NotifyClearNetworkMessage(
+ NetworkObserver::ERROR_CONNECT_FAILED);
+
+ NetworkHandler::Get()->network_connection_handler()->ConnectToNetwork(
+ service_path,
+ base::Bind(&OnConnectSucceeded, service_path),
+ base::Bind(&OnConnectFailed, service_path, owning_window),
+ check_error_state);
+}
+
+void OnActivateFailed(const std::string& service_path,
+ const std::string& error_name,
+ scoped_ptr<base::DictionaryValue> error_data) {
+ NET_LOG_ERROR("Unable to activate network", service_path);
+ ShowErrorNotification(
+ NetworkConnectionHandler::kErrorActivateFailed, service_path);
+}
+
+void OnActivateSucceeded(const std::string& service_path) {
+ NET_LOG_USER("Activation Succeeded", service_path);
+}
+
+void OnConfigureFailed(const std::string& error_name,
+ scoped_ptr<base::DictionaryValue> error_data) {
+ NET_LOG_ERROR("Unable to configure network", "");
+ ShowErrorNotification(NetworkConnectionHandler::kErrorConfigureFailed, "");
+}
+
+void OnConfigureSucceeded(const std::string& service_path) {
+ NET_LOG_USER("Configure Succeeded", service_path);
+ // After configuring a network, ignore any (possibly stale) error state.
+ const bool check_error_state = false;
+ const gfx::NativeWindow owning_window = NULL;
+ CallConnectToNetwork(service_path, check_error_state, owning_window);
+}
+
+void SetPropertiesFailed(const std::string& desc,
+ const std::string& service_path,
+ const std::string& config_error_name,
+ scoped_ptr<base::DictionaryValue> error_data) {
+ NET_LOG_ERROR(desc + ": Failed: " + config_error_name, service_path);
+ ShowErrorNotification(
+ NetworkConnectionHandler::kErrorConfigureFailed, service_path);
+}
+
+void SetPropertiesToClear(base::DictionaryValue* properties_to_set,
+ std::vector<std::string>* properties_to_clear) {
+ // Move empty string properties to properties_to_clear.
+ for (base::DictionaryValue::Iterator iter(*properties_to_set);
+ !iter.IsAtEnd(); iter.Advance()) {
+ std::string value_str;
+ if (iter.value().GetAsString(&value_str) && value_str.empty())
+ properties_to_clear->push_back(iter.key());
+ }
+ // Remove cleared properties from properties_to_set.
+ for (std::vector<std::string>::iterator iter = properties_to_clear->begin();
+ iter != properties_to_clear->end(); ++iter) {
+ properties_to_set->RemoveWithoutPathExpansion(*iter, NULL);
+ }
+}
+
+void ClearPropertiesAndConnect(
+ const std::string& service_path,
+ const std::vector<std::string>& properties_to_clear) {
+ NET_LOG_USER("ClearPropertiesAndConnect", service_path);
+ // After configuring a network, ignore any (possibly stale) error state.
+ const bool check_error_state = false;
+ const gfx::NativeWindow owning_window = NULL;
+ NetworkHandler::Get()->network_configuration_handler()->ClearProperties(
+ service_path,
+ properties_to_clear,
+ base::Bind(&CallConnectToNetwork,
+ service_path, check_error_state,
+ owning_window),
+ base::Bind(&SetPropertiesFailed, "ClearProperties", service_path));
+}
+
+// Returns false if !shared and no valid profile is available, which will
+// trigger an error and abort.
+bool GetNetworkProfilePath(bool shared, std::string* profile_path) {
+ if (shared) {
+ *profile_path = NetworkProfileHandler::kSharedProfilePath;
+ return true;
+ }
+
+ if (!chromeos::LoginState::Get()->IsUserAuthenticated()) {
+ NET_LOG_ERROR("User profile specified before login", "");
+ return false;
+ }
+
+ const NetworkProfile* profile =
+ NetworkHandler::Get()->network_profile_handler()->
+ GetDefaultUserProfile();
+ if (!profile) {
+ NET_LOG_ERROR("No user profile for unshared network configuration", "");
+ return false;
+ }
+
+ *profile_path = profile->path;
+ return true;
+}
+
+void ConfigureSetProfileSucceeded(
+ const std::string& service_path,
+ scoped_ptr<base::DictionaryValue> properties_to_set) {
+ std::vector<std::string> properties_to_clear;
+ SetPropertiesToClear(properties_to_set.get(), &properties_to_clear);
+ NetworkHandler::Get()->network_configuration_handler()->SetProperties(
+ service_path,
+ *properties_to_set,
+ base::Bind(&ClearPropertiesAndConnect,
+ service_path,
+ properties_to_clear),
+ base::Bind(&SetPropertiesFailed, "SetProperties", service_path));
+}
+
+} // namespace
+
+namespace network_connect {
+
+void ConnectToNetwork(const std::string& service_path,
+ gfx::NativeWindow owning_window) {
+ const bool check_error_state = true;
+ CallConnectToNetwork(service_path, check_error_state, owning_window);
+}
+
+void ActivateCellular(const std::string& service_path) {
+ NET_LOG_USER("ActivateCellular", service_path);
+ const DeviceState* cellular_device =
+ NetworkHandler::Get()->network_state_handler()->
+ GetDeviceStateByType(flimflam::kTypeCellular);
+ if (!cellular_device) {
+ NET_LOG_ERROR("ActivateCellular with no Device", service_path);
+ return;
+ }
+ if (!IsDirectActivatedCarrier(cellular_device->carrier())) {
+ // For non direct activation, show the mobile setup dialog which can be
+ // used to activate the network.
+ ash::Shell::GetInstance()->system_tray_delegate()->ShowMobileSetup(
+ service_path);
+ return;
+ }
+ const NetworkState* cellular =
+ NetworkHandler::Get()->network_state_handler()->
+ GetNetworkState(service_path);
+ if (!cellular || cellular->type() != flimflam::kTypeCellular) {
+ NET_LOG_ERROR("ActivateCellular with no Service", service_path);
+ return;
+ }
+ if (cellular->activation_state() == flimflam::kActivationStateActivated) {
+ NET_LOG_ERROR("ActivateCellular for activated service", service_path);
+ return;
+ }
+
+ NetworkHandler::Get()->network_connection_handler()->ActivateNetwork(
+ service_path,
+ "", // carrier
+ base::Bind(&OnActivateSucceeded, service_path),
+ base::Bind(&OnActivateFailed, service_path));
+}
+
+void ConfigureNetworkAndConnect(const std::string& service_path,
+ const base::DictionaryValue& properties,
+ bool shared) {
+ NET_LOG_USER("ConfigureNetworkAndConnect", service_path);
+
+ scoped_ptr<base::DictionaryValue> properties_to_set(properties.DeepCopy());
+
+ std::string profile_path;
+ if (!GetNetworkProfilePath(shared, &profile_path)) {
+ ShowErrorNotification(
+ NetworkConnectionHandler::kErrorConfigureFailed, service_path);
+ return;
+ }
+ NetworkHandler::Get()->network_configuration_handler()->SetNetworkProfile(
+ service_path, profile_path,
+ base::Bind(&ConfigureSetProfileSucceeded,
+ service_path, base::Passed(&properties_to_set)),
+ base::Bind(&SetPropertiesFailed,
+ "SetProfile: " + profile_path, service_path));
+}
+
+void CreateConfigurationAndConnect(base::DictionaryValue* properties,
+ bool shared) {
+ NET_LOG_USER("CreateConfigurationAndConnect", "");
+ std::string profile_path;
+ if (!GetNetworkProfilePath(shared, &profile_path)) {
+ ShowErrorNotification(NetworkConnectionHandler::kErrorConfigureFailed, "");
+ return;
+ }
+ properties->SetStringWithoutPathExpansion(
+ flimflam::kProfileProperty, profile_path);
+ NetworkHandler::Get()->network_configuration_handler()->CreateConfiguration(
+ *properties,
+ base::Bind(&OnConfigureSucceeded),
+ base::Bind(&OnConfigureFailed));
+}
+
+string16 ErrorString(const std::string& error) {
+ if (error.empty())
+ return string16();
+ if (error == flimflam::kErrorOutOfRange)
+ return l10n_util::GetStringUTF16(IDS_CHROMEOS_NETWORK_ERROR_OUT_OF_RANGE);
+ if (error == flimflam::kErrorPinMissing)
+ return l10n_util::GetStringUTF16(IDS_CHROMEOS_NETWORK_ERROR_PIN_MISSING);
+ if (error == flimflam::kErrorDhcpFailed)
+ return l10n_util::GetStringUTF16(IDS_CHROMEOS_NETWORK_ERROR_DHCP_FAILED);
+ if (error == flimflam::kErrorConnectFailed)
+ return l10n_util::GetStringUTF16(IDS_CHROMEOS_NETWORK_ERROR_CONNECT_FAILED);
+ if (error == flimflam::kErrorBadPassphrase)
+ return l10n_util::GetStringUTF16(IDS_CHROMEOS_NETWORK_ERROR_BAD_PASSPHRASE);
+ if (error == flimflam::kErrorBadWEPKey)
+ return l10n_util::GetStringUTF16(IDS_CHROMEOS_NETWORK_ERROR_BAD_WEPKEY);
+ if (error == flimflam::kErrorActivationFailed) {
+ return l10n_util::GetStringUTF16(
+ IDS_CHROMEOS_NETWORK_ERROR_ACTIVATION_FAILED);
+ }
+ if (error == flimflam::kErrorNeedEvdo)
+ return l10n_util::GetStringUTF16(IDS_CHROMEOS_NETWORK_ERROR_NEED_EVDO);
+ if (error == flimflam::kErrorNeedHomeNetwork) {
+ return l10n_util::GetStringUTF16(
+ IDS_CHROMEOS_NETWORK_ERROR_NEED_HOME_NETWORK);
+ }
+ if (error == flimflam::kErrorOtaspFailed)
+ return l10n_util::GetStringUTF16(IDS_CHROMEOS_NETWORK_ERROR_OTASP_FAILED);
+ if (error == flimflam::kErrorAaaFailed)
+ return l10n_util::GetStringUTF16(IDS_CHROMEOS_NETWORK_ERROR_AAA_FAILED);
+ if (error == flimflam::kErrorInternal)
+ return l10n_util::GetStringUTF16(IDS_CHROMEOS_NETWORK_ERROR_INTERNAL);
+ if (error == flimflam::kErrorDNSLookupFailed) {
+ return l10n_util::GetStringUTF16(
+ IDS_CHROMEOS_NETWORK_ERROR_DNS_LOOKUP_FAILED);
+ }
+ if (error == flimflam::kErrorHTTPGetFailed) {
+ return l10n_util::GetStringUTF16(
+ IDS_CHROMEOS_NETWORK_ERROR_HTTP_GET_FAILED);
+ }
+ if (error == flimflam::kErrorIpsecPskAuthFailed) {
+ return l10n_util::GetStringUTF16(
+ IDS_CHROMEOS_NETWORK_ERROR_IPSEC_PSK_AUTH_FAILED);
+ }
+ if (error == flimflam::kErrorIpsecCertAuthFailed ||
+ error == shill::kErrorEapAuthenticationFailed) {
+ return l10n_util::GetStringUTF16(
+ IDS_CHROMEOS_NETWORK_ERROR_CERT_AUTH_FAILED);
+ }
+ if (error == shill::kErrorEapLocalTlsFailed) {
+ return l10n_util::GetStringUTF16(
+ IDS_CHROMEOS_NETWORK_ERROR_EAP_LOCAL_TLS_FAILED);
+ }
+ if (error == shill::kErrorEapRemoteTlsFailed) {
+ return l10n_util::GetStringUTF16(
+ IDS_CHROMEOS_NETWORK_ERROR_EAP_REMOTE_TLS_FAILED);
+ }
+ if (error == flimflam::kErrorPppAuthFailed) {
+ return l10n_util::GetStringUTF16(
+ IDS_CHROMEOS_NETWORK_ERROR_PPP_AUTH_FAILED);
+ }
+
+ if (StringToLowerASCII(error) ==
+ StringToLowerASCII(std::string(flimflam::kUnknownString))) {
+ return l10n_util::GetStringUTF16(IDS_CHROMEOS_NETWORK_ERROR_UNKNOWN);
+ }
+ return l10n_util::GetStringFUTF16(IDS_NETWORK_UNRECOGNIZED_ERROR,
+ UTF8ToUTF16(error));
+}
+
+} // network_connect
+} // ash
diff --git a/chromium/ash/system/chromeos/network/network_connect.h b/chromium/ash/system/chromeos/network/network_connect.h
new file mode 100644
index 00000000000..65c430d073e
--- /dev/null
+++ b/chromium/ash/system/chromeos/network/network_connect.h
@@ -0,0 +1,48 @@
+// Copyright (c) 2012 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 ASH_SYSTEM_CHROMEOS_NETWORK_NETWORK_CONNECT_H
+#define ASH_SYSTEM_CHROMEOS_NETWORK_NETWORK_CONNECT_H
+
+#include <string>
+
+#include "ash/ash_export.h"
+#include "base/strings/string16.h"
+#include "ui/gfx/native_widget_types.h" // gfx::NativeWindow
+
+namespace base {
+class DictionaryValue;
+}
+
+namespace ash {
+namespace network_connect {
+
+// Requests a network connection and handles any errors and notifications.
+// |owning_window| is used to parent any UI on failure (e.g. for certificate
+// enrollment). If NULL, the default window will be used.
+ASH_EXPORT void ConnectToNetwork(const std::string& service_path,
+ gfx::NativeWindow owning_window);
+
+// Requests network activation and handles any errors and notifications.
+ASH_EXPORT void ActivateCellular(const std::string& service_path);
+
+// Configures a network with a dictionary of Shill properties, then sends a
+// connect request. The profile is set according to 'shared' if allowed.
+ASH_EXPORT void ConfigureNetworkAndConnect(
+ const std::string& service_path,
+ const base::DictionaryValue& properties,
+ bool shared);
+
+// Requests a new network configuration to be created from a dictionary of
+// Shill properties. The profile used is determined by |shared|.
+ASH_EXPORT void CreateConfigurationAndConnect(base::DictionaryValue* properties,
+ bool shared);
+
+// Returns the localized string for shill error string |error|.
+ASH_EXPORT base::string16 ErrorString(const std::string& error);
+
+} // network_connect
+} // ash
+
+#endif // ASH_SYSTEM_CHROMEOS_NETWORK_NETWORK_CONNECT_H
diff --git a/chromium/ash/system/chromeos/network/network_detailed_view.h b/chromium/ash/system/chromeos/network/network_detailed_view.h
new file mode 100644
index 00000000000..fe7ac21db0e
--- /dev/null
+++ b/chromium/ash/system/chromeos/network/network_detailed_view.h
@@ -0,0 +1,55 @@
+// Copyright (c) 2012 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 ASH_SYSTEM_CHROMEOS_NETWORK_NETWORK_DETAILED_VIEW_H
+#define ASH_SYSTEM_CHROMEOS_NETWORK_NETWORK_DETAILED_VIEW_H
+
+#include "ash/system/tray/tray_details_view.h"
+#include "chromeos/network/network_state_handler.h"
+
+namespace ash {
+namespace internal {
+
+namespace tray {
+
+// Abstract base class for all NetworkDetailedView derived subclasses,
+// which includes NetworkWifiDetailedView and NetworkStateListDetailedView.
+class NetworkDetailedView : public TrayDetailsView {
+ public:
+ enum DetailedViewType {
+ LIST_VIEW,
+ STATE_LIST_VIEW,
+ WIFI_VIEW,
+ };
+
+ explicit NetworkDetailedView(SystemTrayItem* owner)
+ : TrayDetailsView(owner) {
+ }
+
+ virtual void Init() = 0;
+
+ virtual DetailedViewType GetViewType() const = 0;
+
+ // Called when network manager state has changed.
+ // (Generic update for NetworkTray <> AshSystemTrayDelegate interface).
+ virtual void ManagerChanged() = 0;
+
+ // Called when the contents of the network list have changed.
+ // (Called only from TrayNetworkStateObserver).
+ virtual void NetworkListChanged() = 0;
+
+ // Called when a network service property has changed.
+ // (Called only from TrayNetworkStateObserver).
+ virtual void NetworkServiceChanged(const chromeos::NetworkState* network) = 0;
+
+ protected:
+ virtual ~NetworkDetailedView() {}
+};
+
+} // namespace tray
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_SYSTEM_CHROMEOS_NETWORK_NETWORK_DETAILED_VIEW_H
diff --git a/chromium/ash/system/chromeos/network/network_icon.cc b/chromium/ash/system/chromeos/network/network_icon.cc
new file mode 100644
index 00000000000..efe7e07afc4
--- /dev/null
+++ b/chromium/ash/system/chromeos/network/network_icon.cc
@@ -0,0 +1,849 @@
+// Copyright (c) 2012 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.
+
+#include "ash/system/chromeos/network/network_icon.h"
+
+#include "ash/shell.h"
+#include "ash/system/chromeos/network/network_icon_animation.h"
+#include "ash/system/chromeos/network/network_icon_animation_observer.h"
+#include "base/strings/utf_string_conversions.h"
+#include "chromeos/network/device_state.h"
+#include "chromeos/network/network_connection_handler.h"
+#include "chromeos/network/network_state.h"
+#include "chromeos/network/network_state_handler.h"
+#include "grit/ash_resources.h"
+#include "grit/ash_strings.h"
+#include "third_party/cros_system_api/dbus/service_constants.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "ui/gfx/canvas.h"
+#include "ui/gfx/image/image_skia_operations.h"
+#include "ui/gfx/image/image_skia_source.h"
+#include "ui/gfx/rect.h"
+#include "ui/gfx/size_conversions.h"
+
+using chromeos::DeviceState;
+using chromeos::NetworkConnectionHandler;
+using chromeos::NetworkHandler;
+using chromeos::NetworkState;
+using chromeos::NetworkStateHandler;
+
+namespace ash {
+namespace network_icon {
+
+namespace {
+
+//------------------------------------------------------------------------------
+// Struct to pass icon badges to NetworkIconImageSource.
+struct Badges {
+ Badges()
+ : top_left(NULL),
+ top_right(NULL),
+ bottom_left(NULL),
+ bottom_right(NULL) {
+ }
+ const gfx::ImageSkia* top_left;
+ const gfx::ImageSkia* top_right;
+ const gfx::ImageSkia* bottom_left;
+ const gfx::ImageSkia* bottom_right;
+};
+
+//------------------------------------------------------------------------------
+// class used for maintaining a map of network state and images.
+class NetworkIconImpl {
+ public:
+ explicit NetworkIconImpl(IconType icon_type);
+
+ // Determines whether or not the associated network might be dirty and if so
+ // updates and generates the icon. Does nothing if network no longer exists.
+ void Update(const chromeos::NetworkState* network);
+
+ const gfx::ImageSkia& image() const { return image_; }
+
+ private:
+ // Updates |strength_index_| for wireless networks. Returns true if changed.
+ bool UpdateWirelessStrengthIndex(const chromeos::NetworkState* network);
+
+ // Updates the local state for cellular networks. Returns true if changed.
+ bool UpdateCellularState(const chromeos::NetworkState* network);
+
+ // Updates the VPN badge. Returns true if changed.
+ bool UpdateVPNBadge();
+
+ // Gets |badges| based on |network| and the current state.
+ void GetBadges(const NetworkState* network, Badges* badges);
+
+ // Gets the appropriate icon and badges and composites the image.
+ void GenerateImage(const chromeos::NetworkState* network);
+
+ // Defines color theme and VPN badging
+ const IconType icon_type_;
+
+ // Cached state of the network when the icon was last generated.
+ std::string state_;
+
+ // Cached strength index of the network when the icon was last generated.
+ int strength_index_;
+
+ // Cached technology badge for the network when the icon was last generated.
+ const gfx::ImageSkia* technology_badge_;
+
+ // Cached vpn badge for the network when the icon was last generated.
+ const gfx::ImageSkia* vpn_badge_;
+
+ // Cached roaming state of the network when the icon was last generated.
+ std::string roaming_state_;
+
+ // Generated icon image.
+ gfx::ImageSkia image_;
+
+ DISALLOW_COPY_AND_ASSIGN(NetworkIconImpl);
+};
+
+//------------------------------------------------------------------------------
+// Maintain a static (global) icon map. Note: Icons are never destroyed;
+// it is assumed that a finite and reasonable number of network icons will be
+// created during a session.
+
+typedef std::map<std::string, NetworkIconImpl*> NetworkIconMap;
+
+NetworkIconMap* GetIconMapInstance(IconType icon_type, bool create) {
+ typedef std::map<IconType, NetworkIconMap*> IconTypeMap;
+ static IconTypeMap* s_icon_map = NULL;
+ if (s_icon_map == NULL) {
+ if (!create)
+ return NULL;
+ s_icon_map = new IconTypeMap;
+ }
+ if (s_icon_map->count(icon_type) == 0) {
+ if (!create)
+ return NULL;
+ (*s_icon_map)[icon_type] = new NetworkIconMap;
+ }
+ return (*s_icon_map)[icon_type];
+}
+
+NetworkIconMap* GetIconMap(IconType icon_type) {
+ return GetIconMapInstance(icon_type, true);
+}
+
+void PurgeIconMap(IconType icon_type,
+ const std::set<std::string>& network_paths) {
+ NetworkIconMap* icon_map = GetIconMapInstance(icon_type, false);
+ if (!icon_map)
+ return;
+ for (NetworkIconMap::iterator loop_iter = icon_map->begin();
+ loop_iter != icon_map->end(); ) {
+ NetworkIconMap::iterator cur_iter = loop_iter++;
+ if (network_paths.count(cur_iter->first) == 0) {
+ delete cur_iter->second;
+ icon_map->erase(cur_iter);
+ }
+ }
+}
+
+//------------------------------------------------------------------------------
+// Utilities for generating icon images.
+
+// 'NONE' will default to ARCS behavior where appropriate (e.g. no network or
+// if a new type gets added).
+enum ImageType {
+ ARCS,
+ BARS,
+ NONE
+};
+
+// Amount to fade icons while connecting.
+const double kConnectingImageAlpha = 0.5;
+
+// Images for strength bars for wired networks.
+const int kNumBarsImages = 5;
+
+// Imagaes for strength arcs for wireless networks.
+const int kNumArcsImages = 5;
+
+// Number of discrete images to use for alpha fade animation
+const int kNumFadeImages = 10;
+
+//------------------------------------------------------------------------------
+// Classes for generating scaled images.
+
+const SkBitmap GetEmptyBitmap(const gfx::Size pixel_size) {
+ typedef std::pair<int, int> SizeKey;
+ typedef std::map<SizeKey, SkBitmap> SizeBitmapMap;
+ static SizeBitmapMap* s_empty_bitmaps = new SizeBitmapMap;
+
+ SizeKey key(pixel_size.width(), pixel_size.height());
+
+ SizeBitmapMap::iterator iter = s_empty_bitmaps->find(key);
+ if (iter != s_empty_bitmaps->end())
+ return iter->second;
+
+ SkBitmap empty;
+ empty.setConfig(SkBitmap::kARGB_8888_Config, key.first, key.second);
+ empty.allocPixels();
+ empty.eraseARGB(0, 0, 0, 0);
+ (*s_empty_bitmaps)[key] = empty;
+ return empty;
+}
+
+class EmptyImageSource: public gfx::ImageSkiaSource {
+ public:
+ explicit EmptyImageSource(const gfx::Size& size)
+ : size_(size) {
+ }
+
+ virtual gfx::ImageSkiaRep GetImageForScale(
+ ui::ScaleFactor scale_factor) OVERRIDE {
+ gfx::Size pixel_size = gfx::ToFlooredSize(
+ gfx::ScaleSize(size_, ui::GetScaleFactorScale(scale_factor)));
+ SkBitmap empty_bitmap = GetEmptyBitmap(pixel_size);
+ return gfx::ImageSkiaRep(empty_bitmap, scale_factor);
+ }
+ private:
+ const gfx::Size size_;
+
+ DISALLOW_COPY_AND_ASSIGN(EmptyImageSource);
+};
+
+// This defines how we assemble a network icon.
+class NetworkIconImageSource : public gfx::ImageSkiaSource {
+ public:
+ NetworkIconImageSource(const gfx::ImageSkia& icon, const Badges& badges)
+ : icon_(icon),
+ badges_(badges) {
+ }
+ virtual ~NetworkIconImageSource() {}
+
+ // TODO(pkotwicz): Figure out what to do when a new image resolution becomes
+ // available.
+ virtual gfx::ImageSkiaRep GetImageForScale(
+ ui::ScaleFactor scale_factor) OVERRIDE {
+ gfx::ImageSkiaRep icon_rep = icon_.GetRepresentation(scale_factor);
+ if (icon_rep.is_null())
+ return gfx::ImageSkiaRep();
+ gfx::Canvas canvas(icon_rep, false);
+ if (badges_.top_left)
+ canvas.DrawImageInt(*badges_.top_left, 0, 0);
+ if (badges_.top_right)
+ canvas.DrawImageInt(*badges_.top_right,
+ icon_.width() - badges_.top_right->width(), 0);
+ if (badges_.bottom_left) {
+ canvas.DrawImageInt(*badges_.bottom_left,
+ 0, icon_.height() - badges_.bottom_left->height());
+ }
+ if (badges_.bottom_right) {
+ canvas.DrawImageInt(*badges_.bottom_right,
+ icon_.width() - badges_.bottom_right->width(),
+ icon_.height() - badges_.bottom_right->height());
+ }
+ return canvas.ExtractImageRep();
+ }
+
+ private:
+ const gfx::ImageSkia icon_;
+ const Badges badges_;
+
+ DISALLOW_COPY_AND_ASSIGN(NetworkIconImageSource);
+};
+
+//------------------------------------------------------------------------------
+// Utilities for extracting icon images.
+
+bool IconTypeIsDark(IconType icon_type) {
+ return (icon_type != ICON_TYPE_TRAY);
+}
+
+bool IconTypeHasVPNBadge(IconType icon_type) {
+ return (icon_type != ICON_TYPE_LIST);
+}
+
+int NumImagesForType(ImageType type) {
+ return (type == BARS) ? kNumBarsImages : kNumArcsImages;
+}
+
+gfx::ImageSkia* BaseImageForType(ImageType image_type, IconType icon_type) {
+ gfx::ImageSkia* image;
+ if (image_type == BARS) {
+ image = ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
+ IconTypeIsDark(icon_type) ?
+ IDR_AURA_UBER_TRAY_NETWORK_BARS_DARK :
+ IDR_AURA_UBER_TRAY_NETWORK_BARS_LIGHT);
+ } else {
+ image = ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
+ IconTypeIsDark(icon_type) ?
+ IDR_AURA_UBER_TRAY_NETWORK_ARCS_DARK :
+ IDR_AURA_UBER_TRAY_NETWORK_ARCS_LIGHT);
+ }
+ return image;
+}
+
+ImageType ImageTypeForNetworkType(const std::string& type) {
+ if (type == flimflam::kTypeWifi)
+ return ARCS;
+ else if (type == flimflam::kTypeCellular || type == flimflam::kTypeWimax)
+ return BARS;
+ return NONE;
+}
+
+gfx::ImageSkia GetImageForIndex(ImageType image_type,
+ IconType icon_type,
+ int index) {
+ int num_images = NumImagesForType(image_type);
+ if (index < 0 || index >= num_images)
+ return gfx::ImageSkia();
+ gfx::ImageSkia* images = BaseImageForType(image_type, icon_type);
+ int width = images->width();
+ int height = images->height() / num_images;
+ return gfx::ImageSkiaOperations::ExtractSubset(*images,
+ gfx::Rect(0, index * height, width, height));
+}
+
+const gfx::ImageSkia GetConnectedImage(const std::string& type,
+ IconType icon_type) {
+ if (type == flimflam::kTypeVPN) {
+ return *ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
+ IDR_AURA_UBER_TRAY_NETWORK_VPN);
+ }
+ ImageType image_type = ImageTypeForNetworkType(type);
+ const int connected_index = NumImagesForType(image_type) - 1;
+ return GetImageForIndex(image_type, icon_type, connected_index);
+}
+
+const gfx::ImageSkia GetDisconnectedImage(const std::string& type,
+ IconType icon_type) {
+ if (type == flimflam::kTypeVPN) {
+ // Note: same as connected image, shouldn't normally be seen.
+ return *ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
+ IDR_AURA_UBER_TRAY_NETWORK_VPN);
+ }
+ ImageType image_type = ImageTypeForNetworkType(type);
+ const int disconnected_index = 0;
+ return GetImageForIndex(image_type, icon_type, disconnected_index);
+}
+
+gfx::ImageSkia* ConnectingWirelessImage(ImageType image_type,
+ IconType icon_type,
+ double animation) {
+ static gfx::ImageSkia* s_bars_images_dark[kNumBarsImages - 1];
+ static gfx::ImageSkia* s_bars_images_light[kNumBarsImages - 1];
+ static gfx::ImageSkia* s_arcs_images_dark[kNumArcsImages - 1];
+ static gfx::ImageSkia* s_arcs_images_light[kNumArcsImages - 1];
+ int image_count = NumImagesForType(image_type) - 1;
+ int index = animation * nextafter(static_cast<float>(image_count), 0);
+ index = std::max(std::min(index, image_count - 1), 0);
+ gfx::ImageSkia** images;
+ bool dark = IconTypeIsDark(icon_type);
+ if (image_type == BARS)
+ images = dark ? s_bars_images_dark : s_bars_images_light;
+ else
+ images = dark ? s_arcs_images_dark : s_arcs_images_light;
+ if (!images[index]) {
+ // Lazily cache images.
+ gfx::ImageSkia source = GetImageForIndex(image_type, icon_type, index + 1);
+ images[index] = new gfx::ImageSkia(
+ gfx::ImageSkiaOperations::CreateBlendedImage(
+ gfx::ImageSkia(new EmptyImageSource(source.size()), source.size()),
+ source,
+ kConnectingImageAlpha));
+ }
+ return images[index];
+}
+
+gfx::ImageSkia* ConnectingVpnImage(double animation) {
+ int index = animation * nextafter(static_cast<float>(kNumFadeImages), 0);
+ static gfx::ImageSkia* s_vpn_images[kNumFadeImages];
+ if (!s_vpn_images[index]) {
+ // Lazily cache images.
+ ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
+ gfx::ImageSkia* icon = rb.GetImageSkiaNamed(IDR_AURA_UBER_TRAY_NETWORK_VPN);
+ s_vpn_images[index] = new gfx::ImageSkia(
+ gfx::ImageSkiaOperations::CreateBlendedImage(
+ gfx::ImageSkia(new EmptyImageSource(icon->size()), icon->size()),
+ *icon,
+ animation));
+ }
+ return s_vpn_images[index];
+}
+
+gfx::ImageSkia* ConnectingVpnBadge(double animation) {
+ int index = animation * nextafter(static_cast<float>(kNumFadeImages), 0);
+ static gfx::ImageSkia* s_vpn_badges[kNumFadeImages];
+ if (!s_vpn_badges[index]) {
+ // Lazily cache images.
+ ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
+ gfx::ImageSkia* icon =
+ rb.GetImageSkiaNamed(IDR_AURA_UBER_TRAY_NETWORK_WIRED); // For size
+ gfx::ImageSkia* badge =
+ rb.GetImageSkiaNamed(IDR_AURA_UBER_TRAY_NETWORK_VPN_BADGE);
+ s_vpn_badges[index] = new gfx::ImageSkia(
+ gfx::ImageSkiaOperations::CreateBlendedImage(
+ gfx::ImageSkia(new EmptyImageSource(icon->size()), icon->size()),
+ *badge,
+ animation));
+ }
+ return s_vpn_badges[index];
+}
+
+int StrengthIndex(int strength, int count) {
+ // Return an index in the range [1, count-1].
+ const float findex = (static_cast<float>(strength) / 100.0f) *
+ nextafter(static_cast<float>(count - 1), 0);
+ int index = 1 + static_cast<int>(findex);
+ index = std::max(std::min(index, count - 1), 1);
+ return index;
+}
+
+int GetStrengthIndex(const NetworkState* network) {
+ ImageType image_type = ImageTypeForNetworkType(network->type());
+ if (image_type == ARCS)
+ return StrengthIndex(network->signal_strength(), kNumArcsImages);
+ else if (image_type == BARS)
+ return StrengthIndex(network->signal_strength(), kNumBarsImages);
+ return 0;
+}
+
+const gfx::ImageSkia* BadgeForNetworkTechnology(const NetworkState* network,
+ IconType icon_type) {
+ const int kUnknownBadgeType = -1;
+ int id = kUnknownBadgeType;
+ const std::string& technology = network->network_technology();
+ if (technology == flimflam::kNetworkTechnologyEvdo) {
+ id = IconTypeIsDark(icon_type) ?
+ IDR_AURA_UBER_TRAY_NETWORK_EVDO_DARK :
+ IDR_AURA_UBER_TRAY_NETWORK_EVDO_LIGHT;
+ } else if (technology == flimflam::kNetworkTechnology1Xrtt) {
+ id = IDR_AURA_UBER_TRAY_NETWORK_1X;
+ } else if (technology == flimflam::kNetworkTechnologyGprs) {
+ id = IconTypeIsDark(icon_type) ?
+ IDR_AURA_UBER_TRAY_NETWORK_GPRS_DARK :
+ IDR_AURA_UBER_TRAY_NETWORK_GPRS_LIGHT;
+ } else if (technology == flimflam::kNetworkTechnologyEdge) {
+ id = IconTypeIsDark(icon_type) ?
+ IDR_AURA_UBER_TRAY_NETWORK_EDGE_DARK :
+ IDR_AURA_UBER_TRAY_NETWORK_EDGE_LIGHT;
+ } else if (technology == flimflam::kNetworkTechnologyUmts) {
+ id = IconTypeIsDark(icon_type) ?
+ IDR_AURA_UBER_TRAY_NETWORK_3G_DARK :
+ IDR_AURA_UBER_TRAY_NETWORK_3G_LIGHT;
+ } else if (technology == flimflam::kNetworkTechnologyHspa) {
+ id = IconTypeIsDark(icon_type) ?
+ IDR_AURA_UBER_TRAY_NETWORK_HSPA_DARK :
+ IDR_AURA_UBER_TRAY_NETWORK_HSPA_LIGHT;
+ } else if (technology == flimflam::kNetworkTechnologyHspaPlus) {
+ id = IconTypeIsDark(icon_type) ?
+ IDR_AURA_UBER_TRAY_NETWORK_HSPA_PLUS_DARK :
+ IDR_AURA_UBER_TRAY_NETWORK_HSPA_PLUS_LIGHT;
+ } else if (technology == flimflam::kNetworkTechnologyLte) {
+ id = IconTypeIsDark(icon_type) ?
+ IDR_AURA_UBER_TRAY_NETWORK_LTE_DARK :
+ IDR_AURA_UBER_TRAY_NETWORK_LTE_LIGHT;
+ } else if (technology == flimflam::kNetworkTechnologyLteAdvanced) {
+ id = IconTypeIsDark(icon_type) ?
+ IDR_AURA_UBER_TRAY_NETWORK_LTE_ADVANCED_DARK :
+ IDR_AURA_UBER_TRAY_NETWORK_LTE_ADVANCED_LIGHT;
+ } else if (technology == flimflam::kNetworkTechnologyGsm) {
+ id = IconTypeIsDark(icon_type) ?
+ IDR_AURA_UBER_TRAY_NETWORK_GPRS_DARK :
+ IDR_AURA_UBER_TRAY_NETWORK_GPRS_LIGHT;
+ }
+ if (id == kUnknownBadgeType)
+ return NULL;
+ else
+ return ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(id);
+}
+
+const gfx::ImageSkia* BadgeForVPN(IconType icon_type) {
+ return ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
+ IDR_AURA_UBER_TRAY_NETWORK_VPN_BADGE);
+}
+
+gfx::ImageSkia GetIcon(const NetworkState* network,
+ IconType icon_type,
+ int strength_index) {
+ ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
+ const std::string& type = network->type();
+ if (type == flimflam::kTypeEthernet) {
+ return *rb.GetImageSkiaNamed(IDR_AURA_UBER_TRAY_NETWORK_WIRED);
+ } else if (type == flimflam::kTypeWifi ||
+ type == flimflam::kTypeWimax ||
+ type == flimflam::kTypeCellular) {
+ DCHECK(strength_index > 0);
+ return GetImageForIndex(
+ ImageTypeForNetworkType(type), icon_type, strength_index);
+ } else if (type == flimflam::kTypeVPN) {
+ return *rb.GetImageSkiaNamed(IDR_AURA_UBER_TRAY_NETWORK_VPN);
+ } else {
+ LOG(WARNING) << "Request for icon for unsupported type: " << type;
+ return *rb.GetImageSkiaNamed(IDR_AURA_UBER_TRAY_NETWORK_WIRED);
+ }
+}
+
+//------------------------------------------------------------------------------
+// Get connecting images
+
+gfx::ImageSkia GetConnectingVpnImage(IconType icon_type) {
+ NetworkStateHandler* handler = NetworkHandler::Get()->network_state_handler();
+ const NetworkState* connected_network = NULL;
+ if (icon_type == ICON_TYPE_TRAY) {
+ connected_network = handler->ConnectedNetworkByType(
+ NetworkStateHandler::kMatchTypeNonVirtual);
+ }
+ double animation = NetworkIconAnimation::GetInstance()->GetAnimation();
+
+ if (connected_network) {
+ gfx::ImageSkia icon = GetImageForNetwork(connected_network, icon_type);
+ Badges badges;
+ badges.bottom_left = ConnectingVpnBadge(animation);
+ return gfx::ImageSkia(
+ new NetworkIconImageSource(icon, badges), icon.size());
+ } else {
+ gfx::ImageSkia* icon = ConnectingVpnImage(animation);
+ return gfx::ImageSkia(
+ new NetworkIconImageSource(*icon, Badges()), icon->size());
+ }
+}
+
+gfx::ImageSkia GetConnectingImage(const std::string& network_type,
+ IconType icon_type) {
+ if (network_type == flimflam::kTypeVPN)
+ return GetConnectingVpnImage(icon_type);
+
+ ImageType image_type = ImageTypeForNetworkType(network_type);
+ double animation = NetworkIconAnimation::GetInstance()->GetAnimation();
+
+ gfx::ImageSkia* icon = ConnectingWirelessImage(
+ image_type, icon_type, animation);
+ return gfx::ImageSkia(
+ new NetworkIconImageSource(*icon, Badges()), icon->size());
+}
+
+} // namespace
+
+//------------------------------------------------------------------------------
+// NetworkIconImpl
+
+NetworkIconImpl::NetworkIconImpl(IconType icon_type)
+ : icon_type_(icon_type),
+ strength_index_(-1),
+ technology_badge_(NULL),
+ vpn_badge_(NULL) {
+ // Default image
+ image_ = GetDisconnectedImage(flimflam::kTypeWifi, icon_type);
+}
+
+void NetworkIconImpl::Update(const NetworkState* network) {
+ DCHECK(network);
+ // Determine whether or not we need to update the icon.
+ bool dirty = image_.isNull();
+
+ // If the network state has changed, the icon needs updating.
+ if (state_ != network->connection_state()) {
+ state_ = network->connection_state();
+ dirty = true;
+ }
+
+ const std::string& type = network->type();
+ if (type != flimflam::kTypeEthernet)
+ dirty |= UpdateWirelessStrengthIndex(network);
+
+ if (type == flimflam::kTypeCellular)
+ dirty |= UpdateCellularState(network);
+
+ if (IconTypeHasVPNBadge(icon_type_) && type != flimflam::kTypeVPN)
+ dirty |= UpdateVPNBadge();
+
+ if (dirty) {
+ // Set the icon and badges based on the network and generate the image.
+ GenerateImage(network);
+ }
+}
+
+bool NetworkIconImpl::UpdateWirelessStrengthIndex(const NetworkState* network) {
+ int index = GetStrengthIndex(network);
+ if (index != strength_index_) {
+ strength_index_ = index;
+ return true;
+ }
+ return false;
+}
+
+bool NetworkIconImpl::UpdateCellularState(const NetworkState* network) {
+ bool dirty = false;
+ const gfx::ImageSkia* technology_badge =
+ BadgeForNetworkTechnology(network, icon_type_);
+ if (technology_badge != technology_badge_) {
+ technology_badge_ = technology_badge;
+ dirty = true;
+ }
+ std::string roaming_state = network->roaming();
+ if (roaming_state != roaming_state_) {
+ roaming_state_ = roaming_state;
+ dirty = true;
+ }
+ return dirty;
+}
+
+bool NetworkIconImpl::UpdateVPNBadge() {
+ const NetworkState* vpn = NetworkHandler::Get()->network_state_handler()->
+ ConnectedNetworkByType(flimflam::kTypeVPN);
+ if (vpn && vpn_badge_ == NULL) {
+ vpn_badge_ = BadgeForVPN(icon_type_);
+ return true;
+ } else if (!vpn && vpn_badge_ != NULL) {
+ vpn_badge_ = NULL;
+ return true;
+ }
+ return false;
+}
+
+void NetworkIconImpl::GetBadges(const NetworkState* network, Badges* badges) {
+ DCHECK(network);
+ ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
+ NetworkStateHandler* handler = NetworkHandler::Get()->network_state_handler();
+
+ const std::string& type = network->type();
+ if (type == flimflam::kTypeWifi) {
+ if (network->security() != flimflam::kSecurityNone &&
+ IconTypeIsDark(icon_type_)) {
+ badges->bottom_right = rb.GetImageSkiaNamed(
+ IDR_AURA_UBER_TRAY_NETWORK_SECURE_DARK);
+ }
+ } else if (type == flimflam::kTypeWimax) {
+ technology_badge_ = rb.GetImageSkiaNamed(
+ IconTypeIsDark(icon_type_) ?
+ IDR_AURA_UBER_TRAY_NETWORK_4G_DARK :
+ IDR_AURA_UBER_TRAY_NETWORK_4G_LIGHT);
+ } else if (type == flimflam::kTypeCellular) {
+ if (network->roaming() == flimflam::kRoamingStateRoaming) {
+ // For networks that are always in roaming don't show roaming badge.
+ const DeviceState* device =
+ handler->GetDeviceState(network->device_path());
+ DCHECK(device);
+ if (!device->provider_requires_roaming()) {
+ badges->bottom_right = rb.GetImageSkiaNamed(
+ IconTypeIsDark(icon_type_) ?
+ IDR_AURA_UBER_TRAY_NETWORK_ROAMING_DARK :
+ IDR_AURA_UBER_TRAY_NETWORK_ROAMING_LIGHT);
+ }
+ }
+ }
+ if (!network->IsConnectingState()) {
+ badges->top_left = technology_badge_;
+ badges->bottom_left = vpn_badge_;
+ }
+}
+
+void NetworkIconImpl::GenerateImage(const NetworkState* network) {
+ DCHECK(network);
+ gfx::ImageSkia icon = GetIcon(network, icon_type_, strength_index_);
+ Badges badges;
+ GetBadges(network, &badges);
+ image_ = gfx::ImageSkia(
+ new NetworkIconImageSource(icon, badges), icon.size());
+}
+
+//------------------------------------------------------------------------------
+// Public interface
+
+gfx::ImageSkia GetImageForNetwork(const NetworkState* network,
+ IconType icon_type) {
+ DCHECK(network);
+ // Handle connecting icons.
+ if (network->IsConnectingState())
+ return GetConnectingImage(network->type(), icon_type);
+
+ // Find or add the icon.
+ NetworkIconMap* icon_map = GetIconMap(icon_type);
+ NetworkIconImpl* icon;
+ NetworkIconMap::iterator iter = icon_map->find(network->path());
+ if (iter == icon_map->end()) {
+ icon = new NetworkIconImpl(icon_type);
+ icon_map->insert(std::make_pair(network->path(), icon));
+ } else {
+ icon = iter->second;
+ }
+
+ // Update and return the icon's image.
+ icon->Update(network);
+ return icon->image();
+}
+
+gfx::ImageSkia GetImageForConnectedNetwork(IconType icon_type,
+ const std::string& network_type) {
+ return GetConnectedImage(network_type, icon_type);
+}
+
+gfx::ImageSkia GetImageForConnectingNetwork(IconType icon_type,
+ const std::string& network_type) {
+ return GetConnectingImage(network_type, icon_type);
+}
+
+gfx::ImageSkia GetImageForDisconnectedNetwork(IconType icon_type,
+ const std::string& network_type) {
+ return GetDisconnectedImage(network_type, icon_type);
+}
+
+base::string16 GetLabelForNetwork(const chromeos::NetworkState* network,
+ IconType icon_type) {
+ DCHECK(network);
+ std::string activation_state = network->activation_state();
+ if (icon_type == ICON_TYPE_LIST) {
+ // Show "<network>: [Connecting|Activating]..."
+ if (network->IsConnectingState()) {
+ return l10n_util::GetStringFUTF16(
+ IDS_ASH_STATUS_TRAY_NETWORK_LIST_CONNECTING,
+ UTF8ToUTF16(network->name()));
+ }
+ if (activation_state == flimflam::kActivationStateActivating) {
+ return l10n_util::GetStringFUTF16(
+ IDS_ASH_STATUS_TRAY_NETWORK_LIST_ACTIVATING,
+ UTF8ToUTF16(network->name()));
+ }
+ // Show "Activate <network>" in list view only.
+ if (activation_state == flimflam::kActivationStateNotActivated ||
+ activation_state == flimflam::kActivationStatePartiallyActivated) {
+ return l10n_util::GetStringFUTF16(
+ IDS_ASH_STATUS_TRAY_NETWORK_LIST_ACTIVATE,
+ UTF8ToUTF16(network->name()));
+ }
+ } else {
+ // Show "[Connected to|Connecting to|Activating] <network>" (non-list view).
+ if (network->IsConnectedState()) {
+ return l10n_util::GetStringFUTF16(
+ IDS_ASH_STATUS_TRAY_NETWORK_CONNECTED, UTF8ToUTF16(network->name()));
+ }
+ if (network->IsConnectingState()) {
+ return l10n_util::GetStringFUTF16(
+ IDS_ASH_STATUS_TRAY_NETWORK_CONNECTING, UTF8ToUTF16(network->name()));
+ }
+ if (activation_state == flimflam::kActivationStateActivating) {
+ return l10n_util::GetStringFUTF16(
+ IDS_ASH_STATUS_TRAY_NETWORK_ACTIVATING, UTF8ToUTF16(network->name()));
+ }
+ }
+
+ // Otherwise just show the network name or 'Ethernet'.
+ if (network->type() == flimflam::kTypeEthernet) {
+ return l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_ETHERNET);
+ } else {
+ return UTF8ToUTF16(network->name());
+ }
+}
+
+int GetCellularUninitializedMsg() {
+ static base::Time s_uninitialized_state_time;
+ static int s_uninitialized_msg(0);
+
+ NetworkStateHandler* handler = NetworkHandler::Get()->network_state_handler();
+ if (handler->GetTechnologyState(NetworkStateHandler::kMatchTypeMobile)
+ == NetworkStateHandler::TECHNOLOGY_UNINITIALIZED) {
+ s_uninitialized_msg = IDS_ASH_STATUS_TRAY_INITIALIZING_CELLULAR;
+ s_uninitialized_state_time = base::Time::Now();
+ return s_uninitialized_msg;
+ } else if (handler->GetScanningByType(
+ NetworkStateHandler::kMatchTypeMobile)) {
+ s_uninitialized_msg = IDS_ASH_STATUS_TRAY_CELLULAR_SCANNING;
+ s_uninitialized_state_time = base::Time::Now();
+ return s_uninitialized_msg;
+ }
+ // There can be a delay between leaving the Initializing state and when
+ // a Cellular device shows up, so keep showing the initializing
+ // animation for a bit to avoid flashing the disconnect icon.
+ const int kInitializingDelaySeconds = 1;
+ base::TimeDelta dtime = base::Time::Now() - s_uninitialized_state_time;
+ if (dtime.InSeconds() < kInitializingDelaySeconds)
+ return s_uninitialized_msg;
+ return 0;
+}
+
+void GetDefaultNetworkImageAndLabel(IconType icon_type,
+ gfx::ImageSkia* image,
+ base::string16* label,
+ bool* animating) {
+ NetworkStateHandler* state_handler =
+ NetworkHandler::Get()->network_state_handler();
+ NetworkConnectionHandler* connect_handler =
+ NetworkHandler::Get()->network_connection_handler();
+ const NetworkState* connected_network =
+ state_handler->ConnectedNetworkByType(
+ NetworkStateHandler::kMatchTypeNonVirtual);
+ const NetworkState* connecting_network =
+ state_handler->ConnectingNetworkByType(
+ NetworkStateHandler::kMatchTypeWireless);
+ if (!connecting_network && icon_type == ICON_TYPE_TRAY) {
+ connecting_network =
+ state_handler->ConnectingNetworkByType(flimflam::kTypeVPN);
+ }
+
+ const NetworkState* network;
+ // If we are connecting to a network, and there is either no connected
+ // network, or the connection was user requested, use the connecting
+ // network.
+ if (connecting_network &&
+ (!connected_network ||
+ connect_handler->HasConnectingNetwork(connecting_network->path()))) {
+ network = connecting_network;
+ } else {
+ network = connected_network;
+ }
+
+ // Don't show ethernet in the tray
+ if (icon_type == ICON_TYPE_TRAY &&
+ network && network->type() == flimflam::kTypeEthernet) {
+ *image = gfx::ImageSkia();
+ *animating = false;
+ return;
+ }
+
+ if (!network) {
+ // If no connecting network, check if we are activating a network.
+ const NetworkState* mobile_network = state_handler->FirstNetworkByType(
+ NetworkStateHandler::kMatchTypeMobile);
+ if (mobile_network && (mobile_network->activation_state() ==
+ flimflam::kActivationStateActivating)) {
+ network = mobile_network;
+ }
+ }
+ if (!network) {
+ // If no connecting network, check for cellular initializing.
+ int uninitialized_msg = GetCellularUninitializedMsg();
+ if (uninitialized_msg != 0) {
+ *image = GetImageForConnectingNetwork(icon_type, flimflam::kTypeCellular);
+ if (label)
+ *label = l10n_util::GetStringUTF16(uninitialized_msg);
+ *animating = true;
+ } else {
+ // Otherwise show the disconnected wifi icon.
+ *image = GetImageForDisconnectedNetwork(icon_type, flimflam::kTypeWifi);
+ if (label) {
+ *label = l10n_util::GetStringUTF16(
+ IDS_ASH_STATUS_TRAY_NETWORK_NOT_CONNECTED);
+ }
+ *animating = false;
+ }
+ return;
+ }
+ *animating = network->IsConnectingState();
+ // Get icon and label for connected or connecting network.
+ *image = GetImageForNetwork(network, icon_type);
+ if (label)
+ *label = GetLabelForNetwork(network, icon_type);
+}
+
+void PurgeNetworkIconCache() {
+ NetworkStateHandler::NetworkStateList networks;
+ NetworkHandler::Get()->network_state_handler()->GetNetworkList(&networks);
+ std::set<std::string> network_paths;
+ for (NetworkStateHandler::NetworkStateList::iterator iter = networks.begin();
+ iter != networks.end(); ++iter) {
+ network_paths.insert((*iter)->path());
+ }
+ PurgeIconMap(ICON_TYPE_TRAY, network_paths);
+ PurgeIconMap(ICON_TYPE_DEFAULT_VIEW, network_paths);
+ PurgeIconMap(ICON_TYPE_LIST, network_paths);
+}
+
+} // namespace network_icon
+} // namespace ash
diff --git a/chromium/ash/system/chromeos/network/network_icon.h b/chromium/ash/system/chromeos/network/network_icon.h
new file mode 100644
index 00000000000..1f9b2c7e387
--- /dev/null
+++ b/chromium/ash/system/chromeos/network/network_icon.h
@@ -0,0 +1,76 @@
+// Copyright (c) 2012 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 ASH_SYSTEM_CHROMEOS_NETWORK_NETWORK_ICON_H_
+#define ASH_SYSTEM_CHROMEOS_NETWORK_NETWORK_ICON_H_
+
+#include <string>
+
+#include "ash/ash_export.h"
+#include "base/strings/string16.h"
+#include "ui/gfx/image/image_skia.h"
+
+namespace chromeos {
+class NetworkState;
+}
+
+namespace ash {
+namespace network_icon {
+
+class AnimationObserver;
+
+// Type of icon which dictates color theme and VPN badging
+enum IconType {
+ ICON_TYPE_TRAY, // light icons with VPN badges
+ ICON_TYPE_DEFAULT_VIEW, // dark icons with VPN badges
+ ICON_TYPE_LIST, // dark icons without VPN badges
+};
+
+// Gets the image for the network associated with |service_path|. |network| must
+// not be NULL. |icon_type| determines the color theme and whether or not to
+// show the VPN badge. This caches badged icons per network per |icon_type|.
+ASH_EXPORT gfx::ImageSkia GetImageForNetwork(
+ const chromeos::NetworkState* network,
+ IconType icon_type);
+
+// Gets the fulls strength image for a connected network type.
+ASH_EXPORT gfx::ImageSkia GetImageForConnectedNetwork(
+ IconType icon_type,
+ const std::string& network_type);
+
+// Gets the image for a connecting network type.
+ASH_EXPORT gfx::ImageSkia GetImageForConnectingNetwork(
+ IconType icon_type,
+ const std::string& network_type);
+
+// Gets the image for a disconnected network type.
+ASH_EXPORT gfx::ImageSkia GetImageForDisconnectedNetwork(
+ IconType icon_type,
+ const std::string& network_type);
+
+// Returns the label for |network| based on |icon_type|. |network| can be NULL.
+ASH_EXPORT base::string16 GetLabelForNetwork(
+ const chromeos::NetworkState* network,
+ IconType icon_type);
+
+// Updates and returns the appropriate message id if the cellular network
+// is uninitialized.
+ASH_EXPORT int GetCellularUninitializedMsg();
+
+// Gets the correct icon and label for |icon_type|. Also sets |animating|
+// based on whether or not the icon is animating (i.e. connecting).
+ASH_EXPORT void GetDefaultNetworkImageAndLabel(IconType icon_type,
+ gfx::ImageSkia* image,
+ base::string16* label,
+ bool* animating);
+
+// Called when the list of networks changes. Retreives the list of networks
+// from the global NetworkStateHandler instance and removes cached entries
+// that are no longer in the list.
+ASH_EXPORT void PurgeNetworkIconCache();
+
+} // namespace network_icon
+} // namespace ash
+
+#endif // ASH_SYSTEM_CHROMEOS_NETWORK_NETWORK_ICON_H_
diff --git a/chromium/ash/system/chromeos/network/network_icon_animation.cc b/chromium/ash/system/chromeos/network/network_icon_animation.cc
new file mode 100644
index 00000000000..1169c67bb92
--- /dev/null
+++ b/chromium/ash/system/chromeos/network/network_icon_animation.cc
@@ -0,0 +1,60 @@
+// Copyright (c) 2013 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.
+
+#include "ash/system/chromeos/network/network_icon_animation.h"
+
+#include "ash/system/chromeos/network/network_icon_animation_observer.h"
+
+namespace {
+const int kThrobDurationMs = 750; // Animation cycle length.
+}
+
+namespace ash {
+namespace network_icon {
+
+NetworkIconAnimation::NetworkIconAnimation()
+ : animation_(this) {
+ // Set up the animation throbber.
+ animation_.SetThrobDuration(kThrobDurationMs);
+ animation_.SetTweenType(ui::Tween::LINEAR);
+}
+
+NetworkIconAnimation::~NetworkIconAnimation() {
+}
+
+void NetworkIconAnimation::AnimationProgressed(const ui::Animation* animation) {
+ if (animation != &animation_)
+ return;
+ FOR_EACH_OBSERVER(AnimationObserver, observers_, NetworkIconChanged());
+}
+
+double NetworkIconAnimation::GetAnimation() {
+ if (!animation_.is_animating()) {
+ animation_.Reset();
+ animation_.StartThrobbing(-1 /*throb indefinitely*/);
+ return 0;
+ }
+ return animation_.GetCurrentValue();
+}
+
+void NetworkIconAnimation::AddObserver(AnimationObserver* observer) {
+ if (!observers_.HasObserver(observer))
+ observers_.AddObserver(observer);
+}
+
+void NetworkIconAnimation::RemoveObserver(AnimationObserver* observer) {
+ observers_.RemoveObserver(observer);
+ if (observers_.size() == 0)
+ animation_.Reset(); // Stops the animation and resets the current value.
+}
+
+// static
+NetworkIconAnimation* NetworkIconAnimation::GetInstance() {
+ static NetworkIconAnimation* s_icon_animation =
+ new NetworkIconAnimation();
+ return s_icon_animation;
+}
+
+} // namespace network_icon
+} // namespace ash
diff --git a/chromium/ash/system/chromeos/network/network_icon_animation.h b/chromium/ash/system/chromeos/network/network_icon_animation.h
new file mode 100644
index 00000000000..e784f7d19f1
--- /dev/null
+++ b/chromium/ash/system/chromeos/network/network_icon_animation.h
@@ -0,0 +1,48 @@
+// Copyright (c) 2013 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 ASH_SYSTEM_CHROMEOS_NETWORK_NETWORK_ICON_ANIMATION_H_
+#define ASH_SYSTEM_CHROMEOS_NETWORK_NETWORK_ICON_ANIMATION_H_
+
+#include <set>
+#include <string>
+
+#include "ash/ash_export.h"
+#include "base/observer_list.h"
+#include "ui/base/animation/animation_delegate.h"
+#include "ui/base/animation/throb_animation.h"
+
+namespace ash {
+namespace network_icon {
+
+class AnimationObserver;
+
+// Single instance class to handle icon animations and keep them in sync.
+class ASH_EXPORT NetworkIconAnimation : public ui::AnimationDelegate {
+ public:
+ NetworkIconAnimation();
+ virtual ~NetworkIconAnimation();
+
+ // Returns the current animation value, [0-1].
+ double GetAnimation();
+
+ // The animation stops when all observers have been removed.
+ // Be sure to remove observers when no associated icons are animating.
+ void AddObserver(AnimationObserver* observer);
+ void RemoveObserver(AnimationObserver* observer);
+
+ // ui::AnimationDelegate implementation.
+ virtual void AnimationProgressed(const ui::Animation* animation) OVERRIDE;
+
+ static NetworkIconAnimation* GetInstance();
+
+ private:
+ ui::ThrobAnimation animation_;
+ ObserverList<AnimationObserver> observers_;
+};
+
+} // namespace network_icon
+} // namespace ash
+
+#endif // ASH_SYSTEM_CHROMEOS_NETWORK_NETWORK_ICON_ANIMATION_H_
diff --git a/chromium/ash/system/chromeos/network/network_icon_animation_observer.h b/chromium/ash/system/chromeos/network/network_icon_animation_observer.h
new file mode 100644
index 00000000000..28a5c735d1b
--- /dev/null
+++ b/chromium/ash/system/chromeos/network/network_icon_animation_observer.h
@@ -0,0 +1,27 @@
+// Copyright (c) 2012 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 ASH_SYSTEM_CHROMEOS_NETWORK_NETWORK_ICON_ANIMATION_OBSERVER_H_
+#define ASH_SYSTEM_CHROMEOS_NETWORK_NETWORK_ICON_ANIMATION_OBSERVER_H_
+
+#include "ash/ash_export.h"
+
+namespace ash {
+namespace network_icon {
+
+// Observer interface class for animating network icons.
+class ASH_EXPORT AnimationObserver {
+ public:
+ // Called when the image has changed due to animation. The callback should
+ // trigger a call to GetImageForNetwork() to retrieve the image.
+ virtual void NetworkIconChanged() = 0;
+
+ protected:
+ virtual ~AnimationObserver() {}
+};
+
+} // namespace network_icon
+} // namespace ash
+
+#endif // ASH_SYSTEM_CHROMEOS_NETWORK_NETWORK_ICON_ANIMATION_OBSERVER_H_
diff --git a/chromium/ash/system/chromeos/network/network_observer.cc b/chromium/ash/system/chromeos/network/network_observer.cc
new file mode 100644
index 00000000000..09f4f45c497
--- /dev/null
+++ b/chromium/ash/system/chromeos/network/network_observer.cc
@@ -0,0 +1,35 @@
+// Copyright (c) 2013 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.
+
+#include "ash/system/chromeos/network/network_observer.h"
+
+#include "chromeos/network/network_state.h"
+#include "third_party/cros_system_api/dbus/service_constants.h"
+
+namespace ash {
+
+//static
+NetworkObserver::NetworkType NetworkObserver::GetNetworkTypeForNetworkState(
+ const chromeos::NetworkState* network) {
+ if (!network)
+ return NETWORK_UNKNOWN;
+ const std::string& type = network->type();
+ if (type == flimflam::kTypeCellular) {
+ const std::string& technology = network->network_technology();
+ if (technology == flimflam::kNetworkTechnologyLte ||
+ technology == flimflam::kNetworkTechnologyLteAdvanced)
+ return NETWORK_CELLULAR_LTE;
+ else
+ return NETWORK_CELLULAR;
+ }
+ if (type == flimflam::kTypeEthernet)
+ return NETWORK_ETHERNET;
+ if (type == flimflam::kTypeWifi)
+ return NETWORK_WIFI;
+ if (type == flimflam::kTypeBluetooth)
+ return NETWORK_BLUETOOTH;
+ return NETWORK_UNKNOWN;
+}
+
+} // namespace ash
diff --git a/chromium/ash/system/chromeos/network/network_observer.h b/chromium/ash/system/chromeos/network/network_observer.h
new file mode 100644
index 00000000000..cff0c692ff3
--- /dev/null
+++ b/chromium/ash/system/chromeos/network/network_observer.h
@@ -0,0 +1,69 @@
+// Copyright (c) 2012 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 ASH_SYSTEM_CHROMEOS_NETWORK_NETWORK_OBSERVER_H
+#define ASH_SYSTEM_CHROMEOS_NETWORK_NETWORK_OBSERVER_H
+
+#include <vector>
+
+#include "base/strings/string16.h"
+
+namespace chromeos {
+class NetworkState;
+}
+
+namespace ash {
+
+struct NetworkIconInfo;
+class NetworkTrayDelegate;
+
+class NetworkObserver {
+ public:
+ enum MessageType {
+ // Priority order, highest to lowest.
+ ERROR_CONNECT_FAILED,
+ ERROR_OUT_OF_CREDITS,
+ MESSAGE_DATA_PROMO,
+ };
+
+ enum NetworkType {
+ // ash relevant subset of network_constants.h connection type.
+ NETWORK_ETHERNET,
+ NETWORK_CELLULAR,
+ NETWORK_CELLULAR_LTE,
+ NETWORK_WIFI,
+ NETWORK_BLUETOOTH,
+ NETWORK_UNKNOWN,
+ };
+
+ virtual ~NetworkObserver() {}
+
+ // Sets a network message notification.
+ // |message_type| identifies the type of message.
+ // |network_type| identifies the type of network involved.
+ // |delegate|->NotificationLinkClicked() will be called if any of the
+ // |links| are clicked (if supplied, |links| may be empty).
+ virtual void SetNetworkMessage(NetworkTrayDelegate* delegate,
+ MessageType message_type,
+ NetworkType network_type,
+ const base::string16& title,
+ const base::string16& message,
+ const std::vector<base::string16>& links) = 0;
+
+ // Clears the message notification for |message_type|.
+ virtual void ClearNetworkMessage(MessageType message_type) = 0;
+
+ // Called to request toggling Wi-Fi enable/disable, e.g. from an accelerator.
+ // NOTE: Toggling is asynchronous and subsequent calls to query the current
+ // state may return the old value.
+ virtual void RequestToggleWifi() = 0;
+
+ // Helper function to get the network type from NetworkState.
+ static NetworkType GetNetworkTypeForNetworkState(
+ const chromeos::NetworkState* network);
+};
+
+} // namespace ash
+
+#endif // ASH_SYSTEM_CHROMEOS_NETWORK_NETWORK_OBSERVER_H
diff --git a/chromium/ash/system/chromeos/network/network_state_list_detailed_view.cc b/chromium/ash/system/chromeos/network/network_state_list_detailed_view.cc
new file mode 100644
index 00000000000..64e6d3bc3ff
--- /dev/null
+++ b/chromium/ash/system/chromeos/network/network_state_list_detailed_view.cc
@@ -0,0 +1,882 @@
+// Copyright (c) 2012 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.
+
+#include "ash/system/chromeos/network/network_state_list_detailed_view.h"
+
+#include "ash/ash_switches.h"
+#include "ash/root_window_controller.h"
+#include "ash/shell.h"
+#include "ash/shell_window_ids.h"
+#include "ash/system/chromeos/network/network_connect.h"
+#include "ash/system/chromeos/network/network_icon.h"
+#include "ash/system/chromeos/network/network_icon_animation.h"
+#include "ash/system/chromeos/network/tray_network_state_observer.h"
+#include "ash/system/tray/fixed_sized_scroll_view.h"
+#include "ash/system/tray/hover_highlight_view.h"
+#include "ash/system/tray/system_tray.h"
+#include "ash/system/tray/system_tray_delegate.h"
+#include "ash/system/tray/tray_constants.h"
+#include "ash/system/tray/tray_details_view.h"
+#include "ash/system/tray/tray_popup_header_button.h"
+#include "ash/system/tray/tray_popup_label_button.h"
+#include "base/command_line.h"
+#include "base/message_loop/message_loop.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/time/time.h"
+#include "chromeos/chromeos_switches.h"
+#include "chromeos/network/device_state.h"
+#include "chromeos/network/favorite_state.h"
+#include "chromeos/network/network_configuration_handler.h"
+#include "chromeos/network/network_state.h"
+#include "chromeos/network/network_state_handler.h"
+#include "grit/ash_resources.h"
+#include "grit/ash_strings.h"
+#include "third_party/cros_system_api/dbus/service_constants.h"
+#include "ui/aura/window.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "ui/views/bubble/bubble_delegate.h"
+#include "ui/views/controls/label.h"
+#include "ui/views/layout/box_layout.h"
+#include "ui/views/layout/fill_layout.h"
+#include "ui/views/widget/widget.h"
+
+using chromeos::DeviceState;
+using chromeos::FavoriteState;
+using chromeos::NetworkHandler;
+using chromeos::NetworkState;
+using chromeos::NetworkStateHandler;
+
+namespace ash {
+namespace internal {
+namespace tray {
+
+namespace {
+
+// Height of the list of networks in the popup.
+const int kNetworkListHeight = 203;
+
+// Delay between scan requests.
+const int kRequestScanDelaySeconds = 10;
+
+// Create a label with the font size and color used in the network info bubble.
+views::Label* CreateInfoBubbleLabel(const base::string16& text) {
+ views::Label* label = new views::Label(text);
+ ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
+ label->SetFont(rb.GetFont(ui::ResourceBundle::SmallFont));
+ label->SetEnabledColor(SkColorSetARGB(127, 0, 0, 0));
+ return label;
+}
+
+// Create a label formatted for info items in the menu
+views::Label* CreateMenuInfoLabel(const base::string16& text) {
+ views::Label* label = new views::Label(text);
+ label->set_border(views::Border::CreateEmptyBorder(
+ ash::kTrayPopupPaddingBetweenItems,
+ ash::kTrayPopupPaddingHorizontal,
+ ash::kTrayPopupPaddingBetweenItems, 0));
+ label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
+ label->SetEnabledColor(SkColorSetARGB(192, 0, 0, 0));
+ return label;
+}
+
+// Create a row of labels for the network info bubble.
+views::View* CreateInfoBubbleLine(const base::string16& text_label,
+ const std::string& text_string) {
+ views::View* view = new views::View;
+ view->SetLayoutManager(
+ new views::BoxLayout(views::BoxLayout::kHorizontal, 0, 0, 1));
+ view->AddChildView(CreateInfoBubbleLabel(text_label));
+ view->AddChildView(CreateInfoBubbleLabel(UTF8ToUTF16(": ")));
+ view->AddChildView(CreateInfoBubbleLabel(UTF8ToUTF16(text_string)));
+ return view;
+}
+
+// A bubble that cannot be activated.
+class NonActivatableSettingsBubble : public views::BubbleDelegateView {
+ public:
+ NonActivatableSettingsBubble(views::View* anchor, views::View* content)
+ : views::BubbleDelegateView(anchor, views::BubbleBorder::TOP_RIGHT) {
+ set_use_focusless(true);
+ set_parent_window(ash::Shell::GetContainer(
+ anchor->GetWidget()->GetNativeWindow()->GetRootWindow(),
+ ash::internal::kShellWindowId_SettingBubbleContainer));
+ SetLayoutManager(new views::FillLayout());
+ AddChildView(content);
+ }
+
+ virtual ~NonActivatableSettingsBubble() {}
+
+ virtual bool CanActivate() const OVERRIDE { return false; }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(NonActivatableSettingsBubble);
+};
+
+} // namespace
+
+
+//------------------------------------------------------------------------------
+
+struct NetworkInfo {
+ NetworkInfo(const std::string& path)
+ : service_path(path),
+ disable(false),
+ highlight(false) {
+ }
+
+ std::string service_path;
+ base::string16 label;
+ gfx::ImageSkia image;
+ bool disable;
+ bool highlight;
+};
+
+//------------------------------------------------------------------------------
+// NetworkStateListDetailedView
+
+NetworkStateListDetailedView::NetworkStateListDetailedView(
+ SystemTrayItem* owner,
+ ListType list_type,
+ user::LoginStatus login)
+ : NetworkDetailedView(owner),
+ list_type_(list_type),
+ login_(login),
+ info_icon_(NULL),
+ button_wifi_(NULL),
+ button_mobile_(NULL),
+ other_wifi_(NULL),
+ turn_on_wifi_(NULL),
+ other_mobile_(NULL),
+ other_vpn_(NULL),
+ toggle_debug_preferred_networks_(NULL),
+ settings_(NULL),
+ proxy_settings_(NULL),
+ scanning_view_(NULL),
+ no_wifi_networks_view_(NULL),
+ no_cellular_networks_view_(NULL),
+ info_bubble_(NULL) {
+}
+
+NetworkStateListDetailedView::~NetworkStateListDetailedView() {
+ if (info_bubble_)
+ info_bubble_->GetWidget()->CloseNow();
+ network_icon::NetworkIconAnimation::GetInstance()->RemoveObserver(this);
+}
+
+void NetworkStateListDetailedView::ManagerChanged() {
+ UpdateNetworkList();
+ UpdateHeaderButtons();
+ UpdateNetworkExtra();
+ Layout();
+}
+
+void NetworkStateListDetailedView::NetworkListChanged() {
+ NetworkStateHandler* handler = NetworkHandler::Get()->network_state_handler();
+ if (list_type_ == LIST_TYPE_DEBUG_PREFERRED) {
+ NetworkStateHandler::FavoriteStateList favorite_list;
+ handler->GetFavoriteList(&favorite_list);
+ UpdatePreferred(favorite_list);
+ } else {
+ NetworkStateHandler::NetworkStateList network_list;
+ handler->GetNetworkList(&network_list);
+ UpdateNetworks(network_list);
+ }
+ UpdateNetworkList();
+ UpdateHeaderButtons();
+ UpdateNetworkExtra();
+ Layout();
+}
+
+void NetworkStateListDetailedView::NetworkServiceChanged(
+ const NetworkState* network) {
+ UpdateNetworkList();
+ Layout();
+}
+
+void NetworkStateListDetailedView::NetworkIconChanged() {
+ UpdateNetworkList();
+ Layout();
+}
+
+// Overridden from NetworkDetailedView:
+
+void NetworkStateListDetailedView::Init() {
+ Reset();
+ network_map_.clear();
+ service_path_map_.clear();
+ info_icon_ = NULL;
+ button_wifi_ = NULL;
+ button_mobile_ = NULL;
+ other_wifi_ = NULL;
+ turn_on_wifi_ = NULL;
+ other_mobile_ = NULL;
+ other_vpn_ = NULL;
+ toggle_debug_preferred_networks_ = NULL;
+ settings_ = NULL;
+ proxy_settings_ = NULL;
+ scanning_view_ = NULL;
+ no_wifi_networks_view_ = NULL;
+ no_cellular_networks_view_ = NULL;
+
+ CreateScrollableList();
+ CreateNetworkExtra();
+ CreateHeaderEntry();
+ CreateHeaderButtons();
+
+ NetworkListChanged();
+
+ CallRequestScan();
+}
+
+NetworkDetailedView::DetailedViewType
+NetworkStateListDetailedView::GetViewType() const {
+ return STATE_LIST_VIEW;
+}
+
+// Views overrides
+
+void NetworkStateListDetailedView::ButtonPressed(views::Button* sender,
+ const ui::Event& event) {
+ if (sender == info_icon_) {
+ ToggleInfoBubble();
+ return;
+ }
+
+ // If the info bubble was visible, close it when some other item is clicked.
+ ResetInfoBubble();
+
+ NetworkStateHandler* handler = NetworkHandler::Get()->network_state_handler();
+ ash::SystemTrayDelegate* delegate =
+ ash::Shell::GetInstance()->system_tray_delegate();
+ if (sender == button_wifi_) {
+ bool enabled = handler->IsTechnologyEnabled(flimflam::kTypeWifi);
+ handler->SetTechnologyEnabled(
+ flimflam::kTypeWifi, !enabled,
+ chromeos::network_handler::ErrorCallback());
+ } else if (sender == turn_on_wifi_) {
+ handler->SetTechnologyEnabled(
+ flimflam::kTypeWifi, true,
+ chromeos::network_handler::ErrorCallback());
+ } else if (sender == button_mobile_) {
+ ToggleMobile();
+ } else if (sender == settings_) {
+ delegate->ShowNetworkSettings("");
+ } else if (sender == proxy_settings_) {
+ delegate->ChangeProxySettings();
+ } else if (sender == other_mobile_) {
+ delegate->ShowOtherCellular();
+ } else if (sender == toggle_debug_preferred_networks_) {
+ list_type_ = (list_type_ == LIST_TYPE_NETWORK)
+ ? LIST_TYPE_DEBUG_PREFERRED : LIST_TYPE_NETWORK;
+ // Re-initialize this after processing the event.
+ base::MessageLoopForUI::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&NetworkStateListDetailedView::Init, AsWeakPtr()));
+ } else if (sender == other_wifi_) {
+ delegate->ShowOtherWifi();
+ } else if (sender == other_vpn_) {
+ delegate->ShowOtherVPN();
+ } else {
+ NOTREACHED();
+ }
+}
+
+void NetworkStateListDetailedView::OnViewClicked(views::View* sender) {
+ // If the info bubble was visible, close it when some other item is clicked.
+ ResetInfoBubble();
+
+ if (sender == footer()->content()) {
+ RootWindowController::ForWindow(GetWidget()->GetNativeView())->
+ GetSystemTray()->ShowDefaultView(BUBBLE_USE_EXISTING);
+ return;
+ }
+
+ if (login_ == user::LOGGED_IN_LOCKED)
+ return;
+
+ std::map<views::View*, std::string>::iterator found =
+ network_map_.find(sender);
+ if (found == network_map_.end())
+ return;
+
+ const std::string& service_path = found->second;
+ if (list_type_ == LIST_TYPE_DEBUG_PREFERRED) {
+ NetworkHandler::Get()->network_configuration_handler()->
+ RemoveConfiguration(service_path,
+ base::Bind(&base::DoNothing),
+ chromeos::network_handler::ErrorCallback());
+ return;
+ }
+
+ const NetworkState* network = NetworkHandler::Get()->network_state_handler()->
+ GetNetworkState(service_path);
+ if (!network || network->IsConnectedState() || network->IsConnectingState()) {
+ Shell::GetInstance()->system_tray_delegate()->ShowNetworkSettings(
+ service_path);
+ } else {
+ ash::network_connect::ConnectToNetwork(service_path, NULL);
+ }
+}
+
+// Create UI components.
+
+void NetworkStateListDetailedView::CreateHeaderEntry() {
+ CreateSpecialRow(IDS_ASH_STATUS_TRAY_NETWORK, this);
+}
+
+void NetworkStateListDetailedView::CreateHeaderButtons() {
+ if (list_type_ != LIST_TYPE_VPN) {
+ button_wifi_ = new TrayPopupHeaderButton(
+ this,
+ IDR_AURA_UBER_TRAY_WIFI_ENABLED,
+ IDR_AURA_UBER_TRAY_WIFI_DISABLED,
+ IDR_AURA_UBER_TRAY_WIFI_ENABLED_HOVER,
+ IDR_AURA_UBER_TRAY_WIFI_DISABLED_HOVER,
+ IDS_ASH_STATUS_TRAY_WIFI);
+ button_wifi_->SetTooltipText(
+ l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_DISABLE_WIFI));
+ button_wifi_->SetToggledTooltipText(
+ l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_ENABLE_WIFI));
+ footer()->AddButton(button_wifi_);
+
+ button_mobile_ = new TrayPopupHeaderButton(
+ this,
+ IDR_AURA_UBER_TRAY_CELLULAR_ENABLED,
+ IDR_AURA_UBER_TRAY_CELLULAR_DISABLED,
+ IDR_AURA_UBER_TRAY_CELLULAR_ENABLED_HOVER,
+ IDR_AURA_UBER_TRAY_CELLULAR_DISABLED_HOVER,
+ IDS_ASH_STATUS_TRAY_CELLULAR);
+ button_mobile_->SetTooltipText(
+ l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_DISABLE_MOBILE));
+ button_mobile_->SetToggledTooltipText(
+ l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_ENABLE_MOBILE));
+ footer()->AddButton(button_mobile_);
+ }
+
+ info_icon_ = new TrayPopupHeaderButton(
+ this,
+ IDR_AURA_UBER_TRAY_NETWORK_INFO,
+ IDR_AURA_UBER_TRAY_NETWORK_INFO,
+ IDR_AURA_UBER_TRAY_NETWORK_INFO_HOVER,
+ IDR_AURA_UBER_TRAY_NETWORK_INFO_HOVER,
+ IDS_ASH_STATUS_TRAY_NETWORK_INFO);
+ info_icon_->SetTooltipText(
+ l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_NETWORK_INFO));
+ footer()->AddButton(info_icon_);
+}
+
+void NetworkStateListDetailedView::CreateNetworkExtra() {
+ if (login_ == user::LOGGED_IN_LOCKED)
+ return;
+
+ ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
+
+ views::View* bottom_row = new views::View();
+ views::BoxLayout* layout = new views::BoxLayout(
+ views::BoxLayout::kHorizontal,
+ kTrayMenuBottomRowPadding,
+ kTrayMenuBottomRowPadding,
+ kTrayMenuBottomRowPaddingBetweenItems);
+ layout->set_spread_blank_space(true);
+ bottom_row->SetLayoutManager(layout);
+
+ if (list_type_ != LIST_TYPE_VPN) {
+ other_wifi_ = new TrayPopupLabelButton(
+ this, rb.GetLocalizedString(IDS_ASH_STATUS_TRAY_OTHER_WIFI));
+ bottom_row->AddChildView(other_wifi_);
+
+ turn_on_wifi_ = new TrayPopupLabelButton(
+ this, rb.GetLocalizedString(IDS_ASH_STATUS_TRAY_TURN_ON_WIFI));
+ bottom_row->AddChildView(turn_on_wifi_);
+
+ other_mobile_ = new TrayPopupLabelButton(
+ this, rb.GetLocalizedString(IDS_ASH_STATUS_TRAY_OTHER_MOBILE));
+ bottom_row->AddChildView(other_mobile_);
+
+ if (CommandLine::ForCurrentProcess()->HasSwitch(
+ ash::switches::kAshDebugShowPreferredNetworks)) {
+ // Debugging UI to view and remove favorites from the status area.
+ std::string toggle_debug_preferred_label =
+ (list_type_ == LIST_TYPE_DEBUG_PREFERRED) ? "Visible" : "Preferred";
+ toggle_debug_preferred_networks_ = new TrayPopupLabelButton(
+ this, UTF8ToUTF16(toggle_debug_preferred_label));
+ bottom_row->AddChildView(toggle_debug_preferred_networks_);
+ }
+ } else {
+ other_vpn_ = new TrayPopupLabelButton(
+ this,
+ ui::ResourceBundle::GetSharedInstance().GetLocalizedString(
+ IDS_ASH_STATUS_TRAY_OTHER_VPN));
+ bottom_row->AddChildView(other_vpn_);
+ }
+
+ CreateSettingsEntry();
+ DCHECK(settings_ || proxy_settings_);
+ bottom_row->AddChildView(settings_ ? settings_ : proxy_settings_);
+
+ AddChildView(bottom_row);
+}
+
+// Update UI components.
+
+void NetworkStateListDetailedView::UpdateHeaderButtons() {
+ NetworkStateHandler* handler = NetworkHandler::Get()->network_state_handler();
+ if (button_wifi_)
+ UpdateTechnologyButton(button_wifi_, flimflam::kTypeWifi);
+ if (button_mobile_) {
+ UpdateTechnologyButton(
+ button_mobile_, NetworkStateHandler::kMatchTypeMobile);
+ }
+ if (proxy_settings_)
+ proxy_settings_->SetEnabled(handler->DefaultNetwork() != NULL);
+
+ static_cast<views::View*>(footer())->Layout();
+}
+
+void NetworkStateListDetailedView::UpdateTechnologyButton(
+ TrayPopupHeaderButton* button,
+ const std::string& technology) {
+ NetworkStateHandler::TechnologyState state =
+ NetworkHandler::Get()->network_state_handler()->
+ GetTechnologyState(technology);
+ if (state == NetworkStateHandler::TECHNOLOGY_UNAVAILABLE) {
+ button->SetVisible(false);
+ return;
+ }
+ button->SetVisible(true);
+ if (state == NetworkStateHandler::TECHNOLOGY_AVAILABLE) {
+ button->SetEnabled(true);
+ button->SetToggled(true);
+ } else if (state == NetworkStateHandler::TECHNOLOGY_ENABLED) {
+ button->SetEnabled(true);
+ button->SetToggled(false);
+ } else if (state == NetworkStateHandler::TECHNOLOGY_ENABLING) {
+ button->SetEnabled(false);
+ button->SetToggled(false);
+ } else { // Initializing
+ button->SetEnabled(false);
+ button->SetToggled(true);
+ }
+}
+
+void NetworkStateListDetailedView::UpdateNetworks(
+ const NetworkStateHandler::NetworkStateList& networks) {
+ DCHECK(list_type_ != LIST_TYPE_DEBUG_PREFERRED);
+ network_list_.clear();
+ for (NetworkStateHandler::NetworkStateList::const_iterator iter =
+ networks.begin(); iter != networks.end(); ++iter) {
+ const NetworkState* network = *iter;
+ if ((list_type_ == LIST_TYPE_NETWORK &&
+ network->type() != flimflam::kTypeVPN) ||
+ (list_type_ == LIST_TYPE_VPN &&
+ network->type() == flimflam::kTypeVPN)) {
+ NetworkInfo* info = new NetworkInfo(network->path());
+ network_list_.push_back(info);
+ }
+ }
+}
+
+void NetworkStateListDetailedView::UpdatePreferred(
+ const NetworkStateHandler::FavoriteStateList& favorites) {
+ DCHECK(list_type_ == LIST_TYPE_DEBUG_PREFERRED);
+ network_list_.clear();
+ for (NetworkStateHandler::FavoriteStateList::const_iterator iter =
+ favorites.begin(); iter != favorites.end(); ++iter) {
+ const FavoriteState* favorite = *iter;
+ NetworkInfo* info = new NetworkInfo(favorite->path());
+ network_list_.push_back(info);
+ }
+}
+
+void NetworkStateListDetailedView::UpdateNetworkList() {
+ NetworkStateHandler* handler = NetworkHandler::Get()->network_state_handler();
+
+ // First, update state for all networks
+ bool animating = false;
+ for (size_t i = 0; i < network_list_.size(); ++i) {
+ NetworkInfo* info = network_list_[i];
+ const NetworkState* network =
+ handler->GetNetworkState(info->service_path);
+ if (network) {
+ info->image = network_icon::GetImageForNetwork(
+ network, network_icon::ICON_TYPE_LIST);
+ info->label = network_icon::GetLabelForNetwork(
+ network, network_icon::ICON_TYPE_LIST);
+ info->highlight =
+ network->IsConnectedState() || network->IsConnectingState();
+ info->disable =
+ network->activation_state() == flimflam::kActivationStateActivating;
+ if (!animating && network->IsConnectingState())
+ animating = true;
+ } else if (list_type_ == LIST_TYPE_DEBUG_PREFERRED) {
+ // Favorites that are visible will use the same display info as the
+ // visible network. Non visible favorites will show the disconnected
+ // icon and the name of the network.
+ const FavoriteState* favorite =
+ handler->GetFavoriteState(info->service_path);
+ if (favorite) {
+ info->image = network_icon::GetImageForDisconnectedNetwork(
+ network_icon::ICON_TYPE_LIST, favorite->type());
+ info->label = UTF8ToUTF16(favorite->name());
+ }
+ }
+ }
+ if (animating)
+ network_icon::NetworkIconAnimation::GetInstance()->AddObserver(this);
+ else
+ network_icon::NetworkIconAnimation::GetInstance()->RemoveObserver(this);
+
+ // Get the updated list entries
+ network_map_.clear();
+ std::set<std::string> new_service_paths;
+ bool needs_relayout = UpdateNetworkListEntries(&new_service_paths);
+
+ // Remove old children
+ std::set<std::string> remove_service_paths;
+ for (ServicePathMap::const_iterator it = service_path_map_.begin();
+ it != service_path_map_.end(); ++it) {
+ if (new_service_paths.find(it->first) == new_service_paths.end()) {
+ remove_service_paths.insert(it->first);
+ network_map_.erase(it->second);
+ scroll_content()->RemoveChildView(it->second);
+ needs_relayout = true;
+ }
+ }
+
+ for (std::set<std::string>::const_iterator remove_it =
+ remove_service_paths.begin();
+ remove_it != remove_service_paths.end(); ++remove_it) {
+ service_path_map_.erase(*remove_it);
+ }
+
+ if (needs_relayout) {
+ views::View* selected_view = NULL;
+ for (ServicePathMap::const_iterator iter = service_path_map_.begin();
+ iter != service_path_map_.end(); ++iter) {
+ if (iter->second->hover()) {
+ selected_view = iter->second;
+ break;
+ }
+ }
+ scroll_content()->SizeToPreferredSize();
+ static_cast<views::View*>(scroller())->Layout();
+ if (selected_view)
+ scroll_content()->ScrollRectToVisible(selected_view->bounds());
+ }
+}
+
+bool NetworkStateListDetailedView::CreateOrUpdateInfoLabel(
+ int index, const base::string16& text, views::Label** label) {
+ if (*label == NULL) {
+ *label = CreateMenuInfoLabel(text);
+ scroll_content()->AddChildViewAt(*label, index);
+ return true;
+ } else {
+ (*label)->SetText(text);
+ return OrderChild(*label, index);
+ }
+}
+
+bool NetworkStateListDetailedView::UpdateNetworkChild(int index,
+ const NetworkInfo* info) {
+ bool needs_relayout = false;
+ HoverHighlightView* container = NULL;
+ ServicePathMap::const_iterator found =
+ service_path_map_.find(info->service_path);
+ gfx::Font::FontStyle font =
+ info->highlight ? gfx::Font::BOLD : gfx::Font::NORMAL;
+ if (found == service_path_map_.end()) {
+ container = new HoverHighlightView(this);
+ container->AddIconAndLabel(info->image, info->label, font);
+ scroll_content()->AddChildViewAt(container, index);
+ container->set_border(views::Border::CreateEmptyBorder(
+ 0, kTrayPopupPaddingHorizontal, 0, 0));
+ needs_relayout = true;
+ } else {
+ container = found->second;
+ container->RemoveAllChildViews(true);
+ container->AddIconAndLabel(info->image, info->label, font);
+ container->Layout();
+ container->SchedulePaint();
+ needs_relayout = OrderChild(container, index);
+ }
+ if (info->disable)
+ container->SetEnabled(false);
+ network_map_[container] = info->service_path;
+ service_path_map_[info->service_path] = container;
+ return needs_relayout;
+}
+
+bool NetworkStateListDetailedView::OrderChild(views::View* view, int index) {
+ if (scroll_content()->child_at(index) != view) {
+ scroll_content()->ReorderChildView(view, index);
+ return true;
+ }
+ return false;
+}
+
+bool NetworkStateListDetailedView::UpdateNetworkListEntries(
+ std::set<std::string>* new_service_paths) {
+ bool needs_relayout = false;
+ ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
+ NetworkStateHandler* handler = NetworkHandler::Get()->network_state_handler();
+
+ // Insert child views
+ int index = 0;
+
+ // Highlighted networks
+ for (size_t i = 0; i < network_list_.size(); ++i) {
+ const NetworkInfo* info = network_list_[i];
+ if (info->highlight) {
+ if (UpdateNetworkChild(index++, info))
+ needs_relayout = true;
+ new_service_paths->insert(info->service_path);
+ }
+ }
+
+ if (list_type_ == LIST_TYPE_NETWORK) {
+ // Cellular initializing
+ int status_message_id = network_icon::GetCellularUninitializedMsg();
+ if (!status_message_id &&
+ handler->IsTechnologyEnabled(NetworkStateHandler::kMatchTypeMobile) &&
+ !handler->FirstNetworkByType(NetworkStateHandler::kMatchTypeMobile)) {
+ status_message_id = IDS_ASH_STATUS_TRAY_NO_CELLULAR_NETWORKS;
+ }
+ if (status_message_id) {
+ base::string16 text = rb.GetLocalizedString(status_message_id);
+ if (CreateOrUpdateInfoLabel(index++, text, &no_cellular_networks_view_))
+ needs_relayout = true;
+ } else if (no_cellular_networks_view_) {
+ scroll_content()->RemoveChildView(no_cellular_networks_view_);
+ no_cellular_networks_view_ = NULL;
+ needs_relayout = true;
+ }
+
+ // "Wifi Enabled / Disabled"
+ if (network_list_.empty()) {
+ int message_id = handler->IsTechnologyEnabled(flimflam::kTypeWifi) ?
+ IDS_ASH_STATUS_TRAY_NETWORK_WIFI_ENABLED :
+ IDS_ASH_STATUS_TRAY_NETWORK_WIFI_DISABLED;
+ base::string16 text = rb.GetLocalizedString(message_id);
+ if (CreateOrUpdateInfoLabel(index++, text, &no_wifi_networks_view_))
+ needs_relayout = true;
+ } else if (no_wifi_networks_view_) {
+ scroll_content()->RemoveChildView(no_wifi_networks_view_);
+ no_wifi_networks_view_ = NULL;
+ needs_relayout = true;
+ }
+
+ // "Wifi Scanning"
+ if (handler->GetScanningByType(flimflam::kTypeWifi)) {
+ base::string16 text =
+ rb.GetLocalizedString(IDS_ASH_STATUS_TRAY_WIFI_SCANNING_MESSAGE);
+ if (CreateOrUpdateInfoLabel(index++, text, &scanning_view_))
+ needs_relayout = true;
+ } else if (scanning_view_ != NULL) {
+ scroll_content()->RemoveChildView(scanning_view_);
+ scanning_view_ = NULL;
+ needs_relayout = true;
+ }
+ }
+
+ // Un-highlighted networks
+ for (size_t i = 0; i < network_list_.size(); ++i) {
+ const NetworkInfo* info = network_list_[i];
+ if (!info->highlight) {
+ if (UpdateNetworkChild(index++, info))
+ needs_relayout = true;
+ new_service_paths->insert(info->service_path);
+ }
+ }
+
+ // No networks or other messages (fallback)
+ if (index == 0) {
+ base::string16 text;
+ if (list_type_ == LIST_TYPE_VPN)
+ text = rb.GetLocalizedString(IDS_ASH_STATUS_TRAY_NETWORK_NO_VPN);
+ else
+ text = rb.GetLocalizedString(IDS_ASH_STATUS_TRAY_NO_NETWORKS);
+ if (CreateOrUpdateInfoLabel(index++, text, &scanning_view_))
+ needs_relayout = true;
+ }
+
+ return needs_relayout;
+}
+
+void NetworkStateListDetailedView::UpdateNetworkExtra() {
+ if (login_ == user::LOGGED_IN_LOCKED)
+ return;
+
+ View* layout_parent = NULL; // All these buttons have the same parent.
+ NetworkStateHandler* handler = NetworkHandler::Get()->network_state_handler();
+ if (other_wifi_) {
+ DCHECK(turn_on_wifi_);
+ NetworkStateHandler::TechnologyState state =
+ handler->GetTechnologyState(flimflam::kTypeWifi);
+ if (state == NetworkStateHandler::TECHNOLOGY_UNAVAILABLE) {
+ turn_on_wifi_->SetVisible(false);
+ other_wifi_->SetVisible(false);
+ } else {
+ if (state == NetworkStateHandler::TECHNOLOGY_AVAILABLE) {
+ turn_on_wifi_->SetVisible(true);
+ turn_on_wifi_->SetEnabled(true);
+ other_wifi_->SetVisible(false);
+ } else if (state == NetworkStateHandler::TECHNOLOGY_ENABLED) {
+ turn_on_wifi_->SetVisible(false);
+ other_wifi_->SetVisible(true);
+ } else {
+ // Initializing or Enabling
+ turn_on_wifi_->SetVisible(true);
+ turn_on_wifi_->SetEnabled(false);
+ other_wifi_->SetVisible(false);
+ }
+ }
+ layout_parent = other_wifi_->parent();
+ }
+
+ if (other_mobile_) {
+ bool show_other_mobile = false;
+ NetworkStateHandler::TechnologyState state =
+ handler->GetTechnologyState(NetworkStateHandler::kMatchTypeMobile);
+ if (state != NetworkStateHandler::TECHNOLOGY_UNAVAILABLE) {
+ const chromeos::DeviceState* device =
+ handler->GetDeviceStateByType(NetworkStateHandler::kMatchTypeMobile);
+ show_other_mobile = (device && device->support_network_scan());
+ }
+ if (show_other_mobile) {
+ other_mobile_->SetVisible(true);
+ other_mobile_->SetEnabled(
+ state == NetworkStateHandler::TECHNOLOGY_ENABLED);
+ } else {
+ other_mobile_->SetVisible(false);
+ }
+ if (!layout_parent)
+ layout_parent = other_wifi_->parent();
+ }
+
+ if (layout_parent)
+ layout_parent->Layout();
+}
+
+void NetworkStateListDetailedView::CreateSettingsEntry() {
+ ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
+ if (login_ != user::LOGGED_IN_NONE) {
+ // Settings, only if logged in.
+ settings_ = new TrayPopupLabelButton(
+ this, rb.GetLocalizedString(IDS_ASH_STATUS_TRAY_NETWORK_SETTINGS));
+ } else {
+ proxy_settings_ = new TrayPopupLabelButton(
+ this,
+ rb.GetLocalizedString(IDS_ASH_STATUS_TRAY_NETWORK_PROXY_SETTINGS));
+ }
+}
+
+void NetworkStateListDetailedView::ToggleInfoBubble() {
+ if (ResetInfoBubble())
+ return;
+
+ info_bubble_ = new NonActivatableSettingsBubble(
+ info_icon_, CreateNetworkInfoView());
+ views::BubbleDelegateView::CreateBubble(info_bubble_)->Show();
+}
+
+bool NetworkStateListDetailedView::ResetInfoBubble() {
+ if (!info_bubble_)
+ return false;
+ info_bubble_->GetWidget()->Close();
+ info_bubble_ = NULL;
+ return true;
+}
+
+views::View* NetworkStateListDetailedView::CreateNetworkInfoView() {
+ ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance();
+ NetworkStateHandler* handler = NetworkHandler::Get()->network_state_handler();
+
+ std::string ip_address("0.0.0.0");
+ const NetworkState* network = handler->DefaultNetwork();
+ if (network)
+ ip_address = network->ip_address();
+
+ views::View* container = new views::View;
+ container->SetLayoutManager(
+ new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 1));
+ container->set_border(views::Border::CreateEmptyBorder(0, 5, 0, 5));
+
+ std::string ethernet_address, wifi_address, vpn_address;
+ if (list_type_ != LIST_TYPE_VPN) {
+ ethernet_address =
+ handler->FormattedHardwareAddressForType(flimflam::kTypeEthernet);
+ wifi_address =
+ handler->FormattedHardwareAddressForType(flimflam::kTypeWifi);
+ } else {
+ vpn_address =
+ handler->FormattedHardwareAddressForType(flimflam::kTypeVPN);
+ }
+
+ if (!ip_address.empty()) {
+ container->AddChildView(CreateInfoBubbleLine(bundle.GetLocalizedString(
+ IDS_ASH_STATUS_TRAY_IP), ip_address));
+ }
+ if (!ethernet_address.empty()) {
+ container->AddChildView(CreateInfoBubbleLine(bundle.GetLocalizedString(
+ IDS_ASH_STATUS_TRAY_ETHERNET), ethernet_address));
+ }
+ if (!wifi_address.empty()) {
+ container->AddChildView(CreateInfoBubbleLine(bundle.GetLocalizedString(
+ IDS_ASH_STATUS_TRAY_WIFI), wifi_address));
+ }
+ if (!vpn_address.empty()) {
+ container->AddChildView(CreateInfoBubbleLine(bundle.GetLocalizedString(
+ IDS_ASH_STATUS_TRAY_VPN), vpn_address));
+ }
+
+ // Avoid an empty bubble in the unlikely event that there is no network
+ // information at all.
+ if (!container->has_children()) {
+ container->AddChildView(CreateInfoBubbleLabel(bundle.GetLocalizedString(
+ IDS_ASH_STATUS_TRAY_NO_NETWORKS)));
+ }
+
+ return container;
+}
+
+void NetworkStateListDetailedView::CallRequestScan() {
+ VLOG(1) << "Requesting Network Scan.";
+ NetworkHandler::Get()->network_state_handler()->RequestScan();
+ // Periodically request a scan while this UI is open.
+ base::MessageLoopForUI::current()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&NetworkStateListDetailedView::CallRequestScan, AsWeakPtr()),
+ base::TimeDelta::FromSeconds(kRequestScanDelaySeconds));
+}
+
+void NetworkStateListDetailedView::ToggleMobile() {
+ NetworkStateHandler* handler = NetworkHandler::Get()->network_state_handler();
+ bool enabled =
+ handler->IsTechnologyEnabled(NetworkStateHandler::kMatchTypeMobile);
+ if (enabled) {
+ handler->SetTechnologyEnabled(
+ NetworkStateHandler::kMatchTypeMobile, false,
+ chromeos::network_handler::ErrorCallback());
+ } else {
+ const DeviceState* mobile =
+ handler->GetDeviceStateByType(NetworkStateHandler::kMatchTypeMobile);
+ if (!mobile) {
+ LOG(ERROR) << "Mobile device not found.";
+ return;
+ }
+ if (!mobile->sim_lock_type().empty() || mobile->IsSimAbsent()) {
+ ash::Shell::GetInstance()->system_tray_delegate()->ShowMobileSimDialog();
+ } else {
+ handler->SetTechnologyEnabled(
+ NetworkStateHandler::kMatchTypeMobile, true,
+ chromeos::network_handler::ErrorCallback());
+ }
+ }
+}
+
+} // namespace tray
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/system/chromeos/network/network_state_list_detailed_view.h b/chromium/ash/system/chromeos/network/network_state_list_detailed_view.h
new file mode 100644
index 00000000000..421e2b89ef3
--- /dev/null
+++ b/chromium/ash/system/chromeos/network/network_state_list_detailed_view.h
@@ -0,0 +1,156 @@
+// Copyright (c) 2012 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 ASH_SYSTEM_CHROMEOS_NETWORK_NETWORK_STATE_LIST_DETAILED_VIEW_H
+#define ASH_SYSTEM_CHROMEOS_NETWORK_NETWORK_STATE_LIST_DETAILED_VIEW_H
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include "ash/system/chromeos/network/network_detailed_view.h"
+#include "ash/system/chromeos/network/network_icon.h"
+#include "ash/system/chromeos/network/network_icon_animation_observer.h"
+#include "ash/system/tray/view_click_listener.h"
+#include "ash/system/user/login_status.h"
+#include "base/memory/scoped_vector.h"
+#include "base/memory/weak_ptr.h"
+#include "ui/views/controls/button/button.h"
+
+namespace views {
+class BubbleDelegateView;
+}
+
+namespace ash {
+
+class SystemTrayItem;
+
+namespace internal {
+
+class HoverHighlightView;
+class TrayPopupLabelButton;
+
+namespace tray {
+
+struct NetworkInfo;
+
+class NetworkStateListDetailedView
+ : public NetworkDetailedView,
+ public views::ButtonListener,
+ public ViewClickListener,
+ public network_icon::AnimationObserver,
+ public base::SupportsWeakPtr<NetworkStateListDetailedView> {
+ public:
+ enum ListType {
+ LIST_TYPE_NETWORK,
+ LIST_TYPE_DEBUG_PREFERRED,
+ LIST_TYPE_VPN
+ };
+
+ NetworkStateListDetailedView(SystemTrayItem* owner,
+ ListType list_type,
+ user::LoginStatus login);
+ virtual ~NetworkStateListDetailedView();
+
+ // Overridden from NetworkDetailedView:
+ virtual void Init() OVERRIDE;
+ virtual DetailedViewType GetViewType() const OVERRIDE;
+ virtual void ManagerChanged() OVERRIDE;
+ virtual void NetworkListChanged() OVERRIDE;
+ virtual void NetworkServiceChanged(
+ const chromeos::NetworkState* network) OVERRIDE;
+
+ // network_icon::AnimationObserver overrides
+ virtual void NetworkIconChanged() OVERRIDE;
+
+ protected:
+ // Overridden from ButtonListener.
+ virtual void ButtonPressed(views::Button* sender,
+ const ui::Event& event) OVERRIDE;
+
+ // Overridden from ViewClickListener.
+ virtual void OnViewClicked(views::View* sender) OVERRIDE;
+
+ private:
+ typedef std::map<views::View*, std::string> NetworkMap;
+ typedef std::map<std::string, HoverHighlightView*> ServicePathMap;
+
+ // Create UI components.
+ void CreateHeaderEntry();
+ void CreateHeaderButtons();
+ void CreateNetworkExtra();
+
+ // Update UI components.
+ void UpdateHeaderButtons();
+ void UpdateTechnologyButton(TrayPopupHeaderButton* button,
+ const std::string& technology);
+
+ void UpdateNetworks(
+ const chromeos::NetworkStateHandler::NetworkStateList& networks);
+ void UpdatePreferred(
+ const chromeos::NetworkStateHandler::FavoriteStateList& favorites);
+ void UpdateNetworkList();
+ bool CreateOrUpdateInfoLabel(
+ int index, const base::string16& text, views::Label** label);
+ bool UpdateNetworkChild(int index, const NetworkInfo* info);
+ bool OrderChild(views::View* view, int index);
+ bool UpdateNetworkListEntries(std::set<std::string>* new_service_paths);
+ void UpdateNetworkExtra();
+
+ // Adds a settings entry when logged in, and an entry for changing proxy
+ // settings otherwise.
+ void CreateSettingsEntry();
+
+ // Create and manage the network info bubble.
+ void ToggleInfoBubble();
+ bool ResetInfoBubble();
+ views::View* CreateNetworkInfoView();
+
+ // Periodically request a network scan.
+ void CallRequestScan();
+
+ // Handle toggile mobile action
+ void ToggleMobile();
+
+ // Type of list (all networks or vpn)
+ ListType list_type_;
+
+ // Track login state.
+ user::LoginStatus login_;
+
+ // A map of child views to their network service path.
+ NetworkMap network_map_;
+
+ // A map of network service paths to their view.
+ ServicePathMap service_path_map_;
+
+ // An owned list of network info.
+ ScopedVector<NetworkInfo> network_list_;
+
+ // Child views.
+ TrayPopupHeaderButton* info_icon_;
+ TrayPopupHeaderButton* button_wifi_;
+ TrayPopupHeaderButton* button_mobile_;
+ TrayPopupLabelButton* other_wifi_;
+ TrayPopupLabelButton* turn_on_wifi_;
+ TrayPopupLabelButton* other_mobile_;
+ TrayPopupLabelButton* other_vpn_;
+ TrayPopupLabelButton* toggle_debug_preferred_networks_;
+ TrayPopupLabelButton* settings_;
+ TrayPopupLabelButton* proxy_settings_;
+ views::Label* scanning_view_;
+ views::Label* no_wifi_networks_view_;
+ views::Label* no_cellular_networks_view_;
+
+ // A small bubble for displaying network info.
+ views::BubbleDelegateView* info_bubble_;
+
+ DISALLOW_COPY_AND_ASSIGN(NetworkStateListDetailedView);
+};
+
+} // namespace tray
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_SYSTEM_CHROMEOS_NETWORK_NETWORK_STATE_LIST_DETAILED_VIEW
diff --git a/chromium/ash/system/chromeos/network/network_state_notifier.cc b/chromium/ash/system/chromeos/network/network_state_notifier.cc
new file mode 100644
index 00000000000..1c971a32a44
--- /dev/null
+++ b/chromium/ash/system/chromeos/network/network_state_notifier.cc
@@ -0,0 +1,184 @@
+// Copyright (c) 2012 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.
+
+#include "ash/system/chromeos/network/network_state_notifier.h"
+
+#include "ash/shell.h"
+#include "ash/system/chromeos/network/network_connect.h"
+#include "ash/system/chromeos/network/network_observer.h"
+#include "ash/system/tray/system_tray_notifier.h"
+#include "base/strings/string16.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "chromeos/network/network_connection_handler.h"
+#include "chromeos/network/network_event_log.h"
+#include "chromeos/network/network_state.h"
+#include "chromeos/network/network_state_handler.h"
+#include "grit/ash_strings.h"
+#include "third_party/cros_system_api/dbus/service_constants.h"
+#include "ui/base/l10n/l10n_util.h"
+
+using chromeos::NetworkConnectionHandler;
+using chromeos::NetworkHandler;
+using chromeos::NetworkState;
+using chromeos::NetworkStateHandler;
+
+namespace {
+
+const int kMinTimeBetweenOutOfCreditsNotifySeconds = 10 * 60;
+
+// Error messages based on |error_name|, not network_state->error().
+string16 GetConnectErrorString(const std::string& error_name) {
+ if (error_name == NetworkConnectionHandler::kErrorNotFound)
+ return l10n_util::GetStringUTF16(IDS_CHROMEOS_NETWORK_ERROR_CONNECT_FAILED);
+ if (error_name == NetworkConnectionHandler::kErrorConfigureFailed)
+ return l10n_util::GetStringUTF16(
+ IDS_CHROMEOS_NETWORK_ERROR_CONFIGURE_FAILED);
+ if (error_name == NetworkConnectionHandler::kErrorActivateFailed)
+ return l10n_util::GetStringUTF16(
+ IDS_CHROMEOS_NETWORK_ERROR_ACTIVATION_FAILED);
+ return string16();
+}
+
+} // namespace
+
+namespace ash {
+
+NetworkStateNotifier::NetworkStateNotifier()
+ : cellular_out_of_credits_(false) {
+ if (!NetworkHandler::IsInitialized())
+ return;
+ NetworkHandler::Get()->network_state_handler()->AddObserver(this, FROM_HERE);
+
+ // Initialize |last_active_network_|.
+ const NetworkState* default_network =
+ NetworkHandler::Get()->network_state_handler()->DefaultNetwork();
+ if (default_network && default_network->IsConnectedState())
+ last_active_network_ = default_network->path();
+}
+
+NetworkStateNotifier::~NetworkStateNotifier() {
+ if (!NetworkHandler::IsInitialized())
+ return;
+ NetworkHandler::Get()->network_state_handler()->RemoveObserver(
+ this, FROM_HERE);
+}
+
+void NetworkStateNotifier::NetworkListChanged() {
+ // Trigger any pending connect failed error if the network list changes
+ // (which indicates all NetworkState entries are up to date). This is in
+ // case a connect attempt fails because a network is no longer visible.
+ if (!connect_failed_network_.empty()) {
+ ShowNetworkConnectError(
+ NetworkConnectionHandler::kErrorConnectFailed, connect_failed_network_);
+ }
+}
+
+void NetworkStateNotifier::DefaultNetworkChanged(const NetworkState* network) {
+ if (!network || !network->IsConnectedState())
+ return;
+ if (network->path() != last_active_network_) {
+ last_active_network_ = network->path();
+ // Reset state for new connected network
+ cellular_out_of_credits_ = false;
+ }
+}
+
+void NetworkStateNotifier::NetworkPropertiesUpdated(
+ const NetworkState* network) {
+ DCHECK(network);
+ // Trigger a pending connect failed error for |network| when the Error
+ // property has been set.
+ if (network->path() == connect_failed_network_ && !network->error().empty()) {
+ ShowNetworkConnectError(
+ NetworkConnectionHandler::kErrorConnectFailed, connect_failed_network_);
+ }
+ // Trigger "Out of credits" notification if the cellular network is the most
+ // recent default network (i.e. we have not switched to another network).
+ if (network->type() == flimflam::kTypeCellular &&
+ network->path() == last_active_network_) {
+ cellular_network_ = network->path();
+ if (network->cellular_out_of_credits() &&
+ !cellular_out_of_credits_) {
+ cellular_out_of_credits_ = true;
+ base::TimeDelta dtime = base::Time::Now() - out_of_credits_notify_time_;
+ if (dtime.InSeconds() > kMinTimeBetweenOutOfCreditsNotifySeconds) {
+ out_of_credits_notify_time_ = base::Time::Now();
+ std::vector<string16> links;
+ links.push_back(
+ l10n_util::GetStringFUTF16(IDS_NETWORK_OUT_OF_CREDITS_LINK,
+ UTF8ToUTF16(network->name())));
+ ash::Shell::GetInstance()->system_tray_notifier()->
+ NotifySetNetworkMessage(
+ this,
+ NetworkObserver::ERROR_OUT_OF_CREDITS,
+ NetworkObserver::GetNetworkTypeForNetworkState(network),
+ l10n_util::GetStringUTF16(IDS_NETWORK_OUT_OF_CREDITS_TITLE),
+ l10n_util::GetStringUTF16(IDS_NETWORK_OUT_OF_CREDITS_BODY),
+ links);
+ }
+ }
+ }
+}
+
+void NetworkStateNotifier::NotificationLinkClicked(
+ NetworkObserver::MessageType message_type,
+ size_t link_index) {
+ if (message_type == NetworkObserver::ERROR_OUT_OF_CREDITS) {
+ if (!cellular_network_.empty()) {
+ // This will trigger the activation / portal code.
+ Shell::GetInstance()->system_tray_delegate()->ConfigureNetwork(
+ cellular_network_);
+ }
+ ash::Shell::GetInstance()->system_tray_notifier()->
+ NotifyClearNetworkMessage(message_type);
+ }
+}
+
+void NetworkStateNotifier::ShowNetworkConnectError(
+ const std::string& error_name,
+ const std::string& service_path) {
+ const NetworkState* network = NetworkHandler::Get()->network_state_handler()->
+ GetNetworkState(service_path);
+ if (error_name == NetworkConnectionHandler::kErrorConnectFailed &&
+ service_path != connect_failed_network_) {
+ // Shill may not have set the Error property yet. First request an update
+ // and wait for either the update to complete or the network list to be
+ // updated before displaying the error.
+ connect_failed_network_ = service_path;
+ return;
+ }
+ connect_failed_network_.clear();
+
+ string16 error = GetConnectErrorString(error_name);
+ if (error.empty() && network)
+ error = network_connect::ErrorString(network->error());
+ if (error.empty())
+ error = l10n_util::GetStringUTF16(IDS_CHROMEOS_NETWORK_ERROR_UNKNOWN);
+ NET_LOG_ERROR("Connect error notification: " + UTF16ToUTF8(error),
+ service_path);
+
+ std::string name = network ? network->name() : "";
+ string16 error_msg;
+ if (network && !network->error_details().empty()) {
+ error_msg = l10n_util::GetStringFUTF16(
+ IDS_NETWORK_CONNECTION_ERROR_MESSAGE_WITH_SERVER_MESSAGE,
+ UTF8ToUTF16(name), error, UTF8ToUTF16(network->error_details()));
+ } else {
+ error_msg = l10n_util::GetStringFUTF16(
+ IDS_NETWORK_CONNECTION_ERROR_MESSAGE_WITH_DETAILS,
+ UTF8ToUTF16(name), error);
+ }
+
+ std::vector<string16> no_links;
+ ash::Shell::GetInstance()->system_tray_notifier()->NotifySetNetworkMessage(
+ this,
+ NetworkObserver::ERROR_CONNECT_FAILED,
+ NetworkObserver::GetNetworkTypeForNetworkState(network),
+ l10n_util::GetStringUTF16(IDS_NETWORK_CONNECTION_ERROR_TITLE),
+ error_msg,
+ no_links);
+}
+
+} // namespace ash
diff --git a/chromium/ash/system/chromeos/network/network_state_notifier.h b/chromium/ash/system/chromeos/network/network_state_notifier.h
new file mode 100644
index 00000000000..30a41b35a60
--- /dev/null
+++ b/chromium/ash/system/chromeos/network/network_state_notifier.h
@@ -0,0 +1,73 @@
+// Copyright (c) 2012 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 ASH_SYSTEM_CHROMEOS_NETWORK_NETWORK_STATE_NOTIFIER_H_
+#define ASH_SYSTEM_CHROMEOS_NETWORK_NETWORK_STATE_NOTIFIER_H_
+
+#include <map>
+
+#include "ash/ash_export.h"
+#include "ash/system/chromeos/network/network_tray_delegate.h"
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/time/time.h"
+#include "chromeos/network/network_state_handler_observer.h"
+
+namespace chromeos {
+class NetworkState;
+}
+
+namespace ash {
+
+// This class has two purposes:
+// 1. ShowNetworkConnectError() gets called after any user initiated connect
+// failure. This will handle displaying an error notification.
+// NOTE: Because Shill sets the Error property of a Service *after*
+// the Connect call fails, this class will delay the notification if
+// necessary until the Error property is set so that the correct
+// message can be displayed.
+// TODO(stevenjb): convert this class to use the new MessageCenter
+// notification system, generate a notification immediately, and update
+// it when the Error property is set to guarantee that a notification is
+// displayed for every failure.
+// 2. It observes NetworkState changes to generate notifications when a
+// Cellular network is out of credits.
+class ASH_EXPORT NetworkStateNotifier :
+ public chromeos::NetworkStateHandlerObserver,
+ public NetworkTrayDelegate {
+ public:
+ NetworkStateNotifier();
+ virtual ~NetworkStateNotifier();
+
+ // NetworkStateHandlerObserver
+ virtual void NetworkListChanged() OVERRIDE;
+ virtual void DefaultNetworkChanged(
+ const chromeos::NetworkState* network) OVERRIDE;
+ virtual void NetworkPropertiesUpdated(
+ const chromeos::NetworkState* network) OVERRIDE;
+
+ // NetworkTrayDelegate
+ virtual void NotificationLinkClicked(
+ NetworkObserver::MessageType message_type,
+ size_t link_index) OVERRIDE;
+
+ // Show a connection error notification.
+ void ShowNetworkConnectError(const std::string& error_name,
+ const std::string& service_path);
+
+ private:
+ typedef std::map<std::string, std::string> CachedStateMap;
+
+ std::string last_active_network_;
+ std::string cellular_network_;
+ std::string connect_failed_network_;
+ bool cellular_out_of_credits_;
+ base::Time out_of_credits_notify_time_;
+
+ DISALLOW_COPY_AND_ASSIGN(NetworkStateNotifier);
+};
+
+} // namespace ash
+
+#endif // ASH_SYSTEM_CHROMEOS_NETWORK_NETWORK_STATE_NOTIFIER_H_
diff --git a/chromium/ash/system/chromeos/network/network_state_notifier_unittest.cc b/chromium/ash/system/chromeos/network/network_state_notifier_unittest.cc
new file mode 100644
index 00000000000..d8d036ad653
--- /dev/null
+++ b/chromium/ash/system/chromeos/network/network_state_notifier_unittest.cc
@@ -0,0 +1,97 @@
+// Copyright (c) 2012 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.
+
+#include "ash/system/chromeos/network/network_state_notifier.h"
+
+#include "ash/root_window_controller.h"
+#include "ash/shelf/shelf_widget.h"
+#include "ash/shell.h"
+#include "ash/system/chromeos/network/network_connect.h"
+#include "ash/system/status_area_widget.h"
+#include "ash/system/tray/system_tray.h"
+#include "ash/test/ash_test_base.h"
+#include "chromeos/dbus/dbus_thread_manager.h"
+#include "chromeos/dbus/shill_device_client.h"
+#include "chromeos/dbus/shill_service_client.h"
+#include "third_party/cros_system_api/dbus/service_constants.h"
+
+namespace {
+
+ash::SystemTray* GetSystemTray() {
+ return ash::Shell::GetPrimaryRootWindowController()->shelf()->
+ status_area_widget()->system_tray();
+}
+
+} // namespace
+
+using chromeos::DBusThreadManager;
+using chromeos::ShillDeviceClient;
+using chromeos::ShillServiceClient;
+
+namespace ash {
+namespace test {
+
+class NetworkStateNotifierTest : public AshTestBase {
+ public:
+ NetworkStateNotifierTest() {}
+ virtual ~NetworkStateNotifierTest() {}
+
+ virtual void SetUp() OVERRIDE {
+ DBusThreadManager::InitializeWithStub();
+ SetupDefaultShillState();
+ RunAllPendingInMessageLoop();
+ AshTestBase::SetUp();
+ }
+
+ virtual void TearDown() OVERRIDE {
+ AshTestBase::TearDown();
+ DBusThreadManager::Shutdown();
+ }
+
+ protected:
+ void SetupDefaultShillState() {
+ RunAllPendingInMessageLoop();
+ ShillDeviceClient::TestInterface* device_test =
+ DBusThreadManager::Get()->GetShillDeviceClient()->GetTestInterface();
+ device_test->ClearDevices();
+ device_test->AddDevice("/device/stub_wifi_device1",
+ flimflam::kTypeWifi, "stub_wifi_device1");
+ device_test->AddDevice("/device/stub_cellular_device1",
+ flimflam::kTypeCellular, "stub_cellular_device1");
+
+ ShillServiceClient::TestInterface* service_test =
+ DBusThreadManager::Get()->GetShillServiceClient()->GetTestInterface();
+ service_test->ClearServices();
+ const bool add_to_watchlist = true;
+ const bool add_to_visible = true;
+ // Create wifi and cellular networks and set to online.
+ service_test->AddService("wifi1", "wifi1",
+ flimflam::kTypeWifi, flimflam::kStateIdle,
+ add_to_visible, add_to_watchlist);
+ service_test->SetServiceProperty("wifi1",
+ flimflam::kSecurityProperty,
+ base::StringValue(flimflam::kSecurityWep));
+ service_test->SetServiceProperty("wifi1",
+ flimflam::kConnectableProperty,
+ base::FundamentalValue(true));
+ service_test->SetServiceProperty("wifi1",
+ flimflam::kPassphraseProperty,
+ base::StringValue("failure"));
+ RunAllPendingInMessageLoop();
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(NetworkStateNotifierTest);
+};
+
+TEST_F(NetworkStateNotifierTest, ConnectionFailure) {
+ EXPECT_FALSE(GetSystemTray()->HasNotificationBubble());
+ ash::network_connect::ConnectToNetwork("wifi1", NULL /* owning_window */);
+ RunAllPendingInMessageLoop();
+ // Failure should spawn a notification.
+ EXPECT_TRUE(GetSystemTray()->CloseNotificationBubbleForTest());
+}
+
+} // namespace test
+} // namespace ash
diff --git a/chromium/ash/system/chromeos/network/network_tray_delegate.h b/chromium/ash/system/chromeos/network/network_tray_delegate.h
new file mode 100644
index 00000000000..0ce54023bfa
--- /dev/null
+++ b/chromium/ash/system/chromeos/network/network_tray_delegate.h
@@ -0,0 +1,24 @@
+// Copyright (c) 2012 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 ASH_SYSTEM_CHROMEOS_NETWORK_NETWORK_TRAY_DELEGATE_H
+#define ASH_SYSTEM_CHROMEOS_NETWORK_NETWORK_TRAY_DELEGATE_H
+
+#include "ash/system/chromeos/network/network_observer.h"
+
+namespace ash {
+
+class NetworkTrayDelegate {
+ public:
+ virtual ~NetworkTrayDelegate() {}
+
+ // Notifies that the |index|-th link on the notification is clicked.
+ virtual void NotificationLinkClicked(
+ NetworkObserver::MessageType message_type,
+ size_t link_index) = 0;
+};
+
+} // namespace ash
+
+#endif // ASH_SYSTEM_CHROMEOS_NETWORK_NETWORK_TRAY_DELEGATE_H
diff --git a/chromium/ash/system/chromeos/network/tray_network.cc b/chromium/ash/system/chromeos/network/tray_network.cc
new file mode 100644
index 00000000000..e8c1683b420
--- /dev/null
+++ b/chromium/ash/system/chromeos/network/tray_network.cc
@@ -0,0 +1,559 @@
+// Copyright (c) 2012 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.
+
+#include "ash/system/chromeos/network/tray_network.h"
+
+#include "ash/ash_switches.h"
+#include "ash/shell.h"
+#include "ash/system/chromeos/network/network_icon_animation.h"
+#include "ash/system/chromeos/network/network_state_list_detailed_view.h"
+#include "ash/system/chromeos/network/network_tray_delegate.h"
+#include "ash/system/chromeos/network/tray_network_state_observer.h"
+#include "ash/system/tray/system_tray.h"
+#include "ash/system/tray/system_tray_delegate.h"
+#include "ash/system/tray/system_tray_notifier.h"
+#include "ash/system/tray/tray_constants.h"
+#include "ash/system/tray/tray_item_more.h"
+#include "ash/system/tray/tray_item_view.h"
+#include "ash/system/tray/tray_notification_view.h"
+#include "ash/system/tray/tray_utils.h"
+#include "base/command_line.h"
+#include "base/strings/utf_string_conversions.h"
+#include "chromeos/network/network_state.h"
+#include "chromeos/network/network_state_handler.h"
+#include "grit/ash_resources.h"
+#include "grit/ash_strings.h"
+#include "third_party/cros_system_api/dbus/service_constants.h"
+#include "ui/base/accessibility/accessible_view_state.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "ui/views/controls/image_view.h"
+#include "ui/views/controls/link.h"
+#include "ui/views/controls/link_listener.h"
+#include "ui/views/layout/box_layout.h"
+#include "ui/views/widget/widget.h"
+
+using chromeos::NetworkHandler;
+using chromeos::NetworkState;
+using chromeos::NetworkStateHandler;
+
+namespace ash {
+namespace internal {
+
+namespace {
+
+int GetMessageIcon(NetworkObserver::MessageType message_type,
+ NetworkObserver::NetworkType network_type) {
+ switch(message_type) {
+ case NetworkObserver::ERROR_CONNECT_FAILED:
+ if (NetworkObserver::NETWORK_CELLULAR == network_type)
+ return IDR_AURA_UBER_TRAY_CELLULAR_NETWORK_FAILED;
+ else
+ return IDR_AURA_UBER_TRAY_NETWORK_FAILED;
+ case NetworkObserver::ERROR_OUT_OF_CREDITS:
+ case NetworkObserver::MESSAGE_DATA_PROMO:
+ if (network_type == TrayNetwork::NETWORK_CELLULAR_LTE)
+ return IDR_AURA_UBER_TRAY_NOTIFICATION_LTE;
+ else
+ return IDR_AURA_UBER_TRAY_NOTIFICATION_3G;
+ }
+ NOTREACHED();
+ return 0;
+}
+
+} // namespace
+
+namespace tray {
+
+class NetworkMessages {
+ public:
+ struct Message {
+ Message() : delegate(NULL) {}
+ Message(NetworkTrayDelegate* in_delegate,
+ NetworkObserver::NetworkType network_type,
+ const base::string16& in_title,
+ const base::string16& in_message,
+ const std::vector<base::string16>& in_links) :
+ delegate(in_delegate),
+ network_type_(network_type),
+ title(in_title),
+ message(in_message),
+ links(in_links) {}
+ NetworkTrayDelegate* delegate;
+ NetworkObserver::NetworkType network_type_;
+ base::string16 title;
+ base::string16 message;
+ std::vector<base::string16> links;
+ };
+ typedef std::map<NetworkObserver::MessageType, Message> MessageMap;
+
+ MessageMap& messages() { return messages_; }
+ const MessageMap& messages() const { return messages_; }
+
+ private:
+ MessageMap messages_;
+};
+
+class NetworkTrayView : public TrayItemView,
+ public network_icon::AnimationObserver {
+ public:
+ explicit NetworkTrayView(TrayNetwork* network_tray)
+ : TrayItemView(network_tray),
+ network_tray_(network_tray) {
+ SetLayoutManager(
+ new views::BoxLayout(views::BoxLayout::kHorizontal, 0, 0, 0));
+
+ image_view_ = new views::ImageView;
+ AddChildView(image_view_);
+
+ UpdateNetworkStateHandlerIcon();
+ }
+
+ virtual ~NetworkTrayView() {
+ network_icon::NetworkIconAnimation::GetInstance()->RemoveObserver(this);
+ }
+
+ virtual const char* GetClassName() const OVERRIDE {
+ return "NetworkTrayView";
+ }
+
+ void UpdateNetworkStateHandlerIcon() {
+ NetworkStateHandler* handler =
+ NetworkHandler::Get()->network_state_handler();
+ gfx::ImageSkia image;
+ base::string16 name;
+ bool animating = false;
+ network_icon::GetDefaultNetworkImageAndLabel(
+ network_icon::ICON_TYPE_TRAY, &image, &name, &animating);
+ bool show_in_tray = !image.isNull();
+ UpdateIcon(show_in_tray, image);
+ if (animating)
+ network_icon::NetworkIconAnimation::GetInstance()->AddObserver(this);
+ else
+ network_icon::NetworkIconAnimation::GetInstance()->RemoveObserver(this);
+ // Update accessibility.
+ const NetworkState* connected_network = handler->ConnectedNetworkByType(
+ NetworkStateHandler::kMatchTypeNonVirtual);
+ if (connected_network)
+ UpdateConnectionStatus(UTF8ToUTF16(connected_network->name()), true);
+ else
+ UpdateConnectionStatus(base::string16(), false);
+ }
+
+ // views::View override.
+ virtual void GetAccessibleState(ui::AccessibleViewState* state) OVERRIDE {
+ state->name = connection_status_string_;
+ state->role = ui::AccessibilityTypes::ROLE_PUSHBUTTON;
+ }
+
+ // network_icon::AnimationObserver
+ virtual void NetworkIconChanged() OVERRIDE {
+ UpdateNetworkStateHandlerIcon();
+ }
+
+ private:
+ // Updates connection status and notifies accessibility event when necessary.
+ void UpdateConnectionStatus(const base::string16& network_name,
+ bool connected) {
+ base::string16 new_connection_status_string;
+ if (connected) {
+ new_connection_status_string = l10n_util::GetStringFUTF16(
+ IDS_ASH_STATUS_TRAY_NETWORK_CONNECTED, network_name);
+ }
+ if (new_connection_status_string != connection_status_string_) {
+ connection_status_string_ = new_connection_status_string;
+ if(!connection_status_string_.empty())
+ NotifyAccessibilityEvent(ui::AccessibilityTypes::EVENT_ALERT, true);
+ }
+ }
+
+ void UpdateIcon(bool tray_icon_visible, const gfx::ImageSkia& image) {
+ image_view_->SetImage(image);
+ SetVisible(tray_icon_visible);
+ SchedulePaint();
+ }
+
+ TrayNetwork* network_tray_;
+ views::ImageView* image_view_;
+ base::string16 connection_status_string_;
+
+ DISALLOW_COPY_AND_ASSIGN(NetworkTrayView);
+};
+
+class NetworkDefaultView : public TrayItemMore,
+ public network_icon::AnimationObserver {
+ public:
+ NetworkDefaultView(TrayNetwork* network_tray, bool show_more)
+ : TrayItemMore(network_tray, show_more),
+ network_tray_(network_tray) {
+ Update();
+ }
+
+ virtual ~NetworkDefaultView() {
+ network_icon::NetworkIconAnimation::GetInstance()->RemoveObserver(this);
+ }
+
+ void Update() {
+ gfx::ImageSkia image;
+ base::string16 label;
+ bool animating = false;
+ network_icon::GetDefaultNetworkImageAndLabel(
+ network_icon::ICON_TYPE_DEFAULT_VIEW, &image, &label, &animating);
+ if (animating)
+ network_icon::NetworkIconAnimation::GetInstance()->AddObserver(this);
+ else
+ network_icon::NetworkIconAnimation::GetInstance()->RemoveObserver(this);
+ SetImage(&image);
+ SetLabel(label);
+ SetAccessibleName(label);
+ }
+
+ // network_icon::AnimationObserver
+ virtual void NetworkIconChanged() OVERRIDE {
+ Update();
+ }
+
+ private:
+ TrayNetwork* network_tray_;
+
+ DISALLOW_COPY_AND_ASSIGN(NetworkDefaultView);
+};
+
+class NetworkWifiDetailedView : public NetworkDetailedView {
+ public:
+ explicit NetworkWifiDetailedView(SystemTrayItem* owner)
+ : NetworkDetailedView(owner) {
+ SetLayoutManager(new views::BoxLayout(views::BoxLayout::kHorizontal,
+ kTrayPopupPaddingHorizontal,
+ 10,
+ kTrayPopupPaddingBetweenItems));
+ image_view_ = new views::ImageView;
+ AddChildView(image_view_);
+
+ label_view_ = new views::Label();
+ label_view_->SetMultiLine(true);
+ label_view_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
+ AddChildView(label_view_);
+
+ Update();
+ }
+
+ virtual ~NetworkWifiDetailedView() {
+ }
+
+ // Overridden from NetworkDetailedView:
+
+ virtual void Init() OVERRIDE {
+ }
+
+ virtual NetworkDetailedView::DetailedViewType GetViewType() const OVERRIDE {
+ return NetworkDetailedView::WIFI_VIEW;
+ }
+
+ virtual void ManagerChanged() OVERRIDE {
+ Update();
+ }
+
+ virtual void NetworkListChanged() OVERRIDE {
+ Update();
+ }
+
+ virtual void NetworkServiceChanged(
+ const chromeos::NetworkState* network) OVERRIDE {
+ }
+
+ private:
+ virtual void Layout() OVERRIDE {
+ // Center both views vertically.
+ views::View::Layout();
+ image_view_->SetY(
+ (height() - image_view_->GetPreferredSize().height()) / 2);
+ label_view_->SetY(
+ (height() - label_view_->GetPreferredSize().height()) / 2);
+ }
+
+ void Update() {
+ bool wifi_enabled = NetworkHandler::Get()->network_state_handler()->
+ IsTechnologyEnabled(flimflam::kTypeWifi);
+ const int image_id = wifi_enabled ?
+ IDR_AURA_UBER_TRAY_WIFI_ENABLED : IDR_AURA_UBER_TRAY_WIFI_DISABLED;
+ ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance();
+ image_view_->SetImage(bundle.GetImageNamed(image_id).ToImageSkia());
+
+ const int string_id = wifi_enabled ?
+ IDS_ASH_STATUS_TRAY_NETWORK_WIFI_ENABLED :
+ IDS_ASH_STATUS_TRAY_NETWORK_WIFI_DISABLED;
+ label_view_->SetText(bundle.GetLocalizedString(string_id));
+ label_view_->SizeToFit(kTrayPopupMinWidth -
+ kTrayPopupPaddingHorizontal * 2 - kTrayPopupPaddingBetweenItems -
+ kTrayPopupDetailsIconWidth);
+ }
+
+ views::ImageView* image_view_;
+ views::Label* label_view_;
+
+ DISALLOW_COPY_AND_ASSIGN(NetworkWifiDetailedView);
+};
+
+class NetworkMessageView : public views::View,
+ public views::LinkListener {
+ public:
+ NetworkMessageView(TrayNetwork* tray_network,
+ NetworkObserver::MessageType message_type,
+ const NetworkMessages::Message& network_msg)
+ : tray_network_(tray_network),
+ message_type_(message_type),
+ network_type_(network_msg.network_type_) {
+ SetLayoutManager(
+ new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 1));
+
+ if (!network_msg.title.empty()) {
+ views::Label* title = new views::Label(network_msg.title);
+ title->SetHorizontalAlignment(gfx::ALIGN_LEFT);
+ title->SetFont(title->font().DeriveFont(0, gfx::Font::BOLD));
+ AddChildView(title);
+ }
+
+ if (!network_msg.message.empty()) {
+ views::Label* message = new views::Label(network_msg.message);
+ message->SetHorizontalAlignment(gfx::ALIGN_LEFT);
+ message->SetMultiLine(true);
+ message->SizeToFit(kTrayNotificationContentsWidth);
+ AddChildView(message);
+ }
+
+ if (!network_msg.links.empty()) {
+ for (size_t i = 0; i < network_msg.links.size(); ++i) {
+ views::Link* link = new views::Link(network_msg.links[i]);
+ link->set_id(i);
+ link->set_listener(this);
+ link->SetHorizontalAlignment(gfx::ALIGN_LEFT);
+ link->SetMultiLine(true);
+ link->SizeToFit(kTrayNotificationContentsWidth);
+ AddChildView(link);
+ }
+ }
+ }
+
+ virtual ~NetworkMessageView() {
+ }
+
+ // Overridden from views::LinkListener.
+ virtual void LinkClicked(views::Link* source, int event_flags) OVERRIDE {
+ tray_network_->LinkClicked(message_type_, source->id());
+ }
+
+ NetworkObserver::MessageType message_type() const { return message_type_; }
+ NetworkObserver::NetworkType network_type() const { return network_type_; }
+
+ private:
+ TrayNetwork* tray_network_;
+ NetworkObserver::MessageType message_type_;
+ NetworkObserver::NetworkType network_type_;
+
+ DISALLOW_COPY_AND_ASSIGN(NetworkMessageView);
+};
+
+class NetworkNotificationView : public TrayNotificationView {
+ public:
+ explicit NetworkNotificationView(TrayNetwork* tray_network)
+ : TrayNotificationView(tray_network, 0),
+ tray_network_(tray_network) {
+ CreateMessageView();
+ InitView(network_message_view_);
+ SetIconImage(*ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
+ GetMessageIcon(network_message_view_->message_type(),
+ network_message_view_->network_type())));
+ }
+
+ // Overridden from TrayNotificationView.
+ virtual void OnClose() OVERRIDE {
+ tray_network_->ClearNetworkMessage(network_message_view_->message_type());
+ }
+
+ virtual void OnClickAction() OVERRIDE {
+ if (network_message_view_->message_type() !=
+ TrayNetwork::MESSAGE_DATA_PROMO)
+ tray_network_->PopupDetailedView(0, true);
+ }
+
+ void Update() {
+ CreateMessageView();
+ UpdateViewAndImage(network_message_view_,
+ *ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
+ GetMessageIcon(network_message_view_->message_type(),
+ network_message_view_->network_type())));
+ }
+
+ private:
+ void CreateMessageView() {
+ // Display the first (highest priority) message.
+ CHECK(!tray_network_->messages()->messages().empty());
+ NetworkMessages::MessageMap::const_iterator iter =
+ tray_network_->messages()->messages().begin();
+ network_message_view_ =
+ new NetworkMessageView(tray_network_, iter->first, iter->second);
+ }
+
+ TrayNetwork* tray_network_;
+ tray::NetworkMessageView* network_message_view_;
+
+ DISALLOW_COPY_AND_ASSIGN(NetworkNotificationView);
+};
+
+} // namespace tray
+
+TrayNetwork::TrayNetwork(SystemTray* system_tray)
+ : SystemTrayItem(system_tray),
+ tray_(NULL),
+ default_(NULL),
+ detailed_(NULL),
+ notification_(NULL),
+ messages_(new tray::NetworkMessages()),
+ request_wifi_view_(false) {
+ network_state_observer_.reset(new TrayNetworkStateObserver(this));
+ Shell::GetInstance()->system_tray_notifier()->AddNetworkObserver(this);
+}
+
+TrayNetwork::~TrayNetwork() {
+ Shell::GetInstance()->system_tray_notifier()->RemoveNetworkObserver(this);
+}
+
+views::View* TrayNetwork::CreateTrayView(user::LoginStatus status) {
+ CHECK(tray_ == NULL);
+ if (!chromeos::NetworkHandler::IsInitialized())
+ return NULL;
+ tray_ = new tray::NetworkTrayView(this);
+ return tray_;
+}
+
+views::View* TrayNetwork::CreateDefaultView(user::LoginStatus status) {
+ CHECK(default_ == NULL);
+ if (!chromeos::NetworkHandler::IsInitialized())
+ return NULL;
+ CHECK(tray_ != NULL);
+ default_ = new tray::NetworkDefaultView(
+ this, status != user::LOGGED_IN_LOCKED);
+ return default_;
+}
+
+views::View* TrayNetwork::CreateDetailedView(user::LoginStatus status) {
+ CHECK(detailed_ == NULL);
+ if (!chromeos::NetworkHandler::IsInitialized())
+ return NULL;
+ // Clear any notifications when showing the detailed view.
+ messages_->messages().clear();
+ HideNotificationView();
+ if (request_wifi_view_) {
+ detailed_ = new tray::NetworkWifiDetailedView(this);
+ request_wifi_view_ = false;
+ } else {
+ detailed_ = new tray::NetworkStateListDetailedView(
+ this, tray::NetworkStateListDetailedView::LIST_TYPE_NETWORK, status);
+ detailed_->Init();
+ }
+ return detailed_;
+}
+
+views::View* TrayNetwork::CreateNotificationView(user::LoginStatus status) {
+ CHECK(notification_ == NULL);
+ if (messages_->messages().empty())
+ return NULL; // Message has already been cleared.
+ notification_ = new tray::NetworkNotificationView(this);
+ return notification_;
+}
+
+void TrayNetwork::DestroyTrayView() {
+ tray_ = NULL;
+}
+
+void TrayNetwork::DestroyDefaultView() {
+ default_ = NULL;
+}
+
+void TrayNetwork::DestroyDetailedView() {
+ detailed_ = NULL;
+}
+
+void TrayNetwork::DestroyNotificationView() {
+ notification_ = NULL;
+}
+
+void TrayNetwork::UpdateAfterLoginStatusChange(user::LoginStatus status) {
+}
+
+void TrayNetwork::UpdateAfterShelfAlignmentChange(ShelfAlignment alignment) {
+ if (tray_)
+ SetTrayImageItemBorder(tray_, alignment);
+}
+
+void TrayNetwork::SetNetworkMessage(NetworkTrayDelegate* delegate,
+ MessageType message_type,
+ NetworkType network_type,
+ const base::string16& title,
+ const base::string16& message,
+ const std::vector<base::string16>& links) {
+ messages_->messages()[message_type] = tray::NetworkMessages::Message(
+ delegate, network_type, title, message, links);
+ if (!Shell::GetInstance()->system_tray_delegate()->IsOobeCompleted())
+ return;
+ if (notification_)
+ notification_->Update();
+ else
+ ShowNotificationView();
+}
+
+void TrayNetwork::ClearNetworkMessage(MessageType message_type) {
+ messages_->messages().erase(message_type);
+ if (messages_->messages().empty()) {
+ HideNotificationView();
+ return;
+ }
+ if (notification_)
+ notification_->Update();
+ else
+ ShowNotificationView();
+}
+
+void TrayNetwork::RequestToggleWifi() {
+ // This will always be triggered by a user action (e.g. keyboard shortcut)
+ if (!detailed_ ||
+ detailed_->GetViewType() == tray::NetworkDetailedView::WIFI_VIEW) {
+ request_wifi_view_ = true;
+ PopupDetailedView(kTrayPopupAutoCloseDelayForTextInSeconds, false);
+ }
+ NetworkStateHandler* handler = NetworkHandler::Get()->network_state_handler();
+ bool enabled = handler->IsTechnologyEnabled(flimflam::kTypeWifi);
+ handler->SetTechnologyEnabled(
+ flimflam::kTypeWifi, !enabled,
+ chromeos::network_handler::ErrorCallback());
+}
+
+void TrayNetwork::NetworkStateChanged(bool list_changed) {
+ if (tray_)
+ tray_->UpdateNetworkStateHandlerIcon();
+ if (default_)
+ default_->Update();
+ if (detailed_) {
+ if (list_changed)
+ detailed_->NetworkListChanged();
+ else
+ detailed_->ManagerChanged();
+ }
+}
+
+void TrayNetwork::NetworkServiceChanged(const chromeos::NetworkState* network) {
+ if (detailed_)
+ detailed_->NetworkServiceChanged(network);
+}
+
+void TrayNetwork::LinkClicked(MessageType message_type, int link_id) {
+ tray::NetworkMessages::MessageMap::const_iterator iter =
+ messages()->messages().find(message_type);
+ if (iter != messages()->messages().end() && iter->second.delegate)
+ iter->second.delegate->NotificationLinkClicked(message_type, link_id);
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/system/chromeos/network/tray_network.h b/chromium/ash/system/chromeos/network/tray_network.h
new file mode 100644
index 00000000000..332db1d5a9d
--- /dev/null
+++ b/chromium/ash/system/chromeos/network/tray_network.h
@@ -0,0 +1,94 @@
+// Copyright (c) 2012 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 ASH_SYSTEM_CHROMEOS_NETWORK_TRAY_NETWORK_H
+#define ASH_SYSTEM_CHROMEOS_NETWORK_TRAY_NETWORK_H
+
+#include <set>
+
+#include "ash/system/chromeos/network/network_icon.h"
+#include "ash/system/chromeos/network/network_observer.h"
+#include "ash/system/chromeos/network/tray_network_state_observer.h"
+#include "ash/system/tray/system_tray_item.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/time/time.h"
+
+namespace chromeos {
+class NetworkState;
+}
+
+namespace ash {
+namespace internal {
+
+namespace tray {
+class NetworkDefaultView;
+class NetworkDetailedView;
+class NetworkMessages;
+class NetworkMessageView;
+class NetworkNotificationView;
+class NetworkTrayView;
+}
+
+class TrayNetwork : public SystemTrayItem,
+ public NetworkObserver,
+ public TrayNetworkStateObserver::Delegate {
+ public:
+ explicit TrayNetwork(SystemTray* system_tray);
+ virtual ~TrayNetwork();
+
+ tray::NetworkDetailedView* detailed() { return detailed_; }
+
+ // SystemTrayItem
+ virtual views::View* CreateTrayView(user::LoginStatus status) OVERRIDE;
+ virtual views::View* CreateDefaultView(user::LoginStatus status) OVERRIDE;
+ virtual views::View* CreateDetailedView(user::LoginStatus status) OVERRIDE;
+ virtual views::View* CreateNotificationView(
+ user::LoginStatus status) OVERRIDE;
+ virtual void DestroyTrayView() OVERRIDE;
+ virtual void DestroyDefaultView() OVERRIDE;
+ virtual void DestroyDetailedView() OVERRIDE;
+ virtual void DestroyNotificationView() OVERRIDE;
+ virtual void UpdateAfterLoginStatusChange(user::LoginStatus status) OVERRIDE;
+ virtual void UpdateAfterShelfAlignmentChange(
+ ShelfAlignment alignment) OVERRIDE;
+
+ // NetworkObserver
+ virtual void SetNetworkMessage(
+ NetworkTrayDelegate* delegate,
+ MessageType message_type,
+ NetworkType network_type,
+ const base::string16& title,
+ const base::string16& message,
+ const std::vector<base::string16>& links) OVERRIDE;
+ virtual void ClearNetworkMessage(MessageType message_type) OVERRIDE;
+ virtual void RequestToggleWifi() OVERRIDE;
+
+ // TrayNetworkStateObserver::Delegate
+ virtual void NetworkStateChanged(bool list_changed) OVERRIDE;
+ virtual void NetworkServiceChanged(
+ const chromeos::NetworkState* network) OVERRIDE;
+
+ private:
+ friend class tray::NetworkMessageView;
+ friend class tray::NetworkNotificationView;
+
+ void LinkClicked(MessageType message_type, int link_id);
+
+ const tray::NetworkMessages* messages() const { return messages_.get(); }
+
+ tray::NetworkTrayView* tray_;
+ tray::NetworkDefaultView* default_;
+ tray::NetworkDetailedView* detailed_;
+ tray::NetworkNotificationView* notification_;
+ scoped_ptr<tray::NetworkMessages> messages_;
+ bool request_wifi_view_;
+ scoped_ptr<TrayNetworkStateObserver> network_state_observer_;
+
+ DISALLOW_COPY_AND_ASSIGN(TrayNetwork);
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_SYSTEM_CHROMEOS_NETWORK_TRAY_NETWORK_H
diff --git a/chromium/ash/system/chromeos/network/tray_network_state_observer.cc b/chromium/ash/system/chromeos/network/tray_network_state_observer.cc
new file mode 100644
index 00000000000..49ba7389c8b
--- /dev/null
+++ b/chromium/ash/system/chromeos/network/tray_network_state_observer.cc
@@ -0,0 +1,63 @@
+// Copyright (c) 2012 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.
+
+#include "ash/system/chromeos/network/tray_network_state_observer.h"
+
+#include <set>
+#include <string>
+
+#include "ash/system/chromeos/network/network_icon.h"
+#include "base/location.h"
+#include "chromeos/network/network_state.h"
+#include "chromeos/network/network_state_handler.h"
+#include "third_party/cros_system_api/dbus/service_constants.h"
+
+using chromeos::NetworkHandler;
+
+namespace ash {
+namespace internal {
+
+TrayNetworkStateObserver::TrayNetworkStateObserver(Delegate* delegate)
+ : delegate_(delegate) {
+ if (NetworkHandler::IsInitialized()) {
+ NetworkHandler::Get()->network_state_handler()->AddObserver(
+ this, FROM_HERE);
+ }
+}
+
+TrayNetworkStateObserver::~TrayNetworkStateObserver() {
+ if (NetworkHandler::IsInitialized()) {
+ NetworkHandler::Get()->network_state_handler()->RemoveObserver(
+ this, FROM_HERE);
+ }
+}
+
+void TrayNetworkStateObserver::NetworkManagerChanged() {
+ delegate_->NetworkStateChanged(false);
+}
+
+void TrayNetworkStateObserver::NetworkListChanged() {
+ delegate_->NetworkStateChanged(true);
+ network_icon::PurgeNetworkIconCache();
+}
+
+void TrayNetworkStateObserver::DeviceListChanged() {
+ delegate_->NetworkStateChanged(false);
+}
+
+void TrayNetworkStateObserver::DefaultNetworkChanged(
+ const chromeos::NetworkState* network) {
+ delegate_->NetworkStateChanged(true);
+}
+
+void TrayNetworkStateObserver::NetworkPropertiesUpdated(
+ const chromeos::NetworkState* network) {
+ if (network ==
+ NetworkHandler::Get()->network_state_handler()->DefaultNetwork())
+ delegate_->NetworkStateChanged(true);
+ delegate_->NetworkServiceChanged(network);
+}
+
+} // namespace ash
+} // namespace internal
diff --git a/chromium/ash/system/chromeos/network/tray_network_state_observer.h b/chromium/ash/system/chromeos/network/tray_network_state_observer.h
new file mode 100644
index 00000000000..fc9faf9d094
--- /dev/null
+++ b/chromium/ash/system/chromeos/network/tray_network_state_observer.h
@@ -0,0 +1,54 @@
+// Copyright (c) 2012 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 ASH_SYSTEM_CHROMEOS_NETWORK_TRAY_NETWORK_STATE_OBSERVER_H
+#define ASH_SYSTEM_CHROMEOS_NETWORK_TRAY_NETWORK_STATE_OBSERVER_H
+
+#include <string>
+
+#include "base/compiler_specific.h"
+#include "chromeos/network/network_state_handler_observer.h"
+
+namespace ash {
+namespace internal {
+
+class TrayNetworkStateObserver : public chromeos::NetworkStateHandlerObserver {
+ public:
+ class Delegate {
+ public:
+ // Called when the network state may have changed. If |list_changed| is
+ // true then the list of networks may have changed.
+ virtual void NetworkStateChanged(bool list_changed) = 0;
+
+ // Called when the properties for |network| may have been updated.
+ virtual void NetworkServiceChanged(
+ const chromeos::NetworkState* network) = 0;
+
+ protected:
+ virtual ~Delegate() {}
+ };
+
+ explicit TrayNetworkStateObserver(Delegate* delegate);
+
+ virtual ~TrayNetworkStateObserver();
+
+ // NetworkStateHandlerObserver overrides.
+ virtual void NetworkManagerChanged() OVERRIDE;
+ virtual void NetworkListChanged() OVERRIDE;
+ virtual void DeviceListChanged() OVERRIDE;
+ virtual void DefaultNetworkChanged(
+ const chromeos::NetworkState* network) OVERRIDE;
+ virtual void NetworkPropertiesUpdated(
+ const chromeos::NetworkState* network) OVERRIDE;
+
+ private:
+ Delegate* delegate_;
+
+ DISALLOW_COPY_AND_ASSIGN(TrayNetworkStateObserver);
+};
+
+} // namespace ash
+} // namespace internal
+
+#endif // ASH_SYSTEM_CHROMEOS_NETWORK_TRAY_NETWORK_STATE_OBSERVER_H
diff --git a/chromium/ash/system/chromeos/network/tray_sms.cc b/chromium/ash/system/chromeos/network/tray_sms.cc
new file mode 100644
index 00000000000..008129faa5f
--- /dev/null
+++ b/chromium/ash/system/chromeos/network/tray_sms.cc
@@ -0,0 +1,416 @@
+// Copyright (c) 2012 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.
+
+#include "ash/system/chromeos/network/tray_sms.h"
+
+#include "ash/shell.h"
+#include "ash/system/tray/fixed_sized_scroll_view.h"
+#include "ash/system/tray/system_tray.h"
+#include "ash/system/tray/system_tray_bubble.h"
+#include "ash/system/tray/system_tray_notifier.h"
+#include "ash/system/tray/tray_constants.h"
+#include "ash/system/tray/tray_details_view.h"
+#include "ash/system/tray/tray_item_more.h"
+#include "ash/system/tray/tray_item_view.h"
+#include "ash/system/tray/tray_notification_view.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/utf_string_conversions.h"
+#include "chromeos/network/network_event_log.h"
+#include "chromeos/network/network_handler.h"
+#include "grit/ash_resources.h"
+#include "grit/ash_strings.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "ui/views/bubble/tray_bubble_view.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/fill_layout.h"
+#include "ui/views/layout/grid_layout.h"
+#include "ui/views/view.h"
+
+namespace {
+
+// Min height of the list of messages in the popup.
+const int kMessageListMinHeight = 200;
+// Top/bottom padding of the text items.
+const int kPaddingVertical = 10;
+
+const char kSmsNumberKey[] = "number";
+const char kSmsTextKey[] = "text";
+
+bool GetMessageFromDictionary(const base::DictionaryValue* message,
+ std::string* number,
+ std::string* text) {
+ if (!message->GetStringWithoutPathExpansion(kSmsNumberKey, number))
+ return false;
+ if (!message->GetStringWithoutPathExpansion(kSmsTextKey, text))
+ return false;
+ return true;
+}
+
+} // namespace
+
+namespace ash {
+namespace internal {
+
+class TraySms::SmsDefaultView : public TrayItemMore {
+ public:
+ explicit SmsDefaultView(TraySms* owner)
+ : TrayItemMore(owner, true) {
+ SetImage(ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
+ IDR_AURA_UBER_TRAY_SMS));
+ Update();
+ }
+
+ virtual ~SmsDefaultView() {}
+
+ void Update() {
+ int message_count = static_cast<TraySms*>(owner())->messages().GetSize();
+ base::string16 label = l10n_util::GetStringFUTF16(
+ IDS_ASH_STATUS_TRAY_SMS_MESSAGES, base::IntToString16(message_count));
+ SetLabel(label);
+ SetAccessibleName(label);
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(SmsDefaultView);
+};
+
+// An entry (row) in SmsDetailedView or NotificationView.
+class TraySms::SmsMessageView : public views::View,
+ public views::ButtonListener {
+ public:
+ enum ViewType {
+ VIEW_DETAILED,
+ VIEW_NOTIFICATION
+ };
+
+ SmsMessageView(TraySms* owner,
+ ViewType view_type,
+ size_t index,
+ const std::string& number,
+ const std::string& message)
+ : owner_(owner),
+ index_(index) {
+ number_label_ = new views::Label(
+ l10n_util::GetStringFUTF16(IDS_ASH_STATUS_TRAY_SMS_NUMBER,
+ UTF8ToUTF16(number)));
+ number_label_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
+ number_label_->SetFont(
+ number_label_->font().DeriveFont(0, gfx::Font::BOLD));
+
+ message_label_ = new views::Label(UTF8ToUTF16(message));
+ message_label_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
+ message_label_->SetMultiLine(true);
+
+ if (view_type == VIEW_DETAILED)
+ LayoutDetailedView();
+ else
+ LayoutNotificationView();
+ }
+
+ virtual ~SmsMessageView() {
+ }
+
+ // Overridden from ButtonListener.
+ virtual void ButtonPressed(views::Button* sender,
+ const ui::Event& event) OVERRIDE {
+ owner_->RemoveMessage(index_);
+ owner_->Update(false);
+ }
+
+ private:
+ void LayoutDetailedView() {
+ views::ImageButton* close_button = new views::ImageButton(this);
+ close_button->SetImage(views::CustomButton::STATE_NORMAL,
+ ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
+ IDR_AURA_WINDOW_CLOSE));
+ const int msg_width = owner_->system_tray()->GetSystemBubble()->
+ bubble_view()->GetPreferredSize().width() -
+ (kNotificationIconWidth + kTrayPopupPaddingHorizontal * 2);
+ message_label_->SizeToFit(msg_width);
+
+ views::GridLayout* layout = new views::GridLayout(this);
+ SetLayoutManager(layout);
+
+ views::ColumnSet* columns = layout->AddColumnSet(0);
+
+ // Message
+ columns->AddPaddingColumn(0, kTrayPopupPaddingHorizontal);
+ columns->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL,
+ 0 /* resize percent */,
+ views::GridLayout::FIXED, msg_width, msg_width);
+
+ // Close button
+ columns->AddColumn(views::GridLayout::TRAILING, views::GridLayout::CENTER,
+ 0, /* resize percent */
+ views::GridLayout::FIXED,
+ kNotificationIconWidth, kNotificationIconWidth);
+
+
+ layout->AddPaddingRow(0, kPaddingVertical);
+ layout->StartRow(0, 0);
+ layout->AddView(number_label_);
+ layout->AddView(close_button, 1, 2); // 2 rows for icon
+ layout->StartRow(0, 0);
+ layout->AddView(message_label_);
+
+ layout->AddPaddingRow(0, kPaddingVertical);
+ }
+
+ void LayoutNotificationView() {
+ SetLayoutManager(
+ new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 1));
+ AddChildView(number_label_);
+ message_label_->SizeToFit(kTrayNotificationContentsWidth);
+ AddChildView(message_label_);
+ }
+
+ TraySms* owner_;
+ size_t index_;
+ views::Label* number_label_;
+ views::Label* message_label_;
+
+ DISALLOW_COPY_AND_ASSIGN(SmsMessageView);
+};
+
+class TraySms::SmsDetailedView : public TrayDetailsView,
+ public ViewClickListener {
+ public:
+ explicit SmsDetailedView(TraySms* owner)
+ : TrayDetailsView(owner) {
+ Init();
+ Update();
+ }
+
+ virtual ~SmsDetailedView() {
+ }
+
+ void Init() {
+ CreateScrollableList();
+ CreateSpecialRow(IDS_ASH_STATUS_TRAY_SMS, this);
+ }
+
+ void Update() {
+ UpdateMessageList();
+ Layout();
+ SchedulePaint();
+ }
+
+ // Overridden from views::View.
+ virtual gfx::Size GetPreferredSize() OVERRIDE {
+ gfx::Size preferred_size = TrayDetailsView::GetPreferredSize();
+ if (preferred_size.height() < kMessageListMinHeight)
+ preferred_size.set_height(kMessageListMinHeight);
+ return preferred_size;
+ }
+
+ private:
+ void UpdateMessageList() {
+ const base::ListValue& messages =
+ static_cast<TraySms*>(owner())->messages();
+ scroll_content()->RemoveAllChildViews(true);
+ for (size_t index = 0; index < messages.GetSize(); ++index) {
+ const base::DictionaryValue* message = NULL;
+ if (!messages.GetDictionary(index, &message)) {
+ LOG(ERROR) << "SMS message not a dictionary at: " << index;
+ continue;
+ }
+ std::string number, text;
+ if (!GetMessageFromDictionary(message, &number, &text)) {
+ LOG(ERROR) << "Error parsing SMS message";
+ continue;
+ }
+ SmsMessageView* msgview = new SmsMessageView(
+ static_cast<TraySms*>(owner()), SmsMessageView::VIEW_DETAILED, index,
+ number, text);
+ scroll_content()->AddChildView(msgview);
+ }
+ scroller()->Layout();
+ }
+
+ // Overridden from ViewClickListener.
+ virtual void OnViewClicked(views::View* sender) OVERRIDE {
+ if (sender == footer()->content())
+ owner()->system_tray()->ShowDefaultView(BUBBLE_USE_EXISTING);
+ }
+
+ DISALLOW_COPY_AND_ASSIGN(SmsDetailedView);
+};
+
+class TraySms::SmsNotificationView : public TrayNotificationView {
+ public:
+ SmsNotificationView(TraySms* owner,
+ size_t message_index,
+ const std::string& number,
+ const std::string& text)
+ : TrayNotificationView(owner, IDR_AURA_UBER_TRAY_SMS),
+ message_index_(message_index) {
+ SmsMessageView* message_view = new SmsMessageView(
+ owner, SmsMessageView::VIEW_NOTIFICATION, message_index_, number, text);
+ InitView(message_view);
+ }
+
+ void Update(size_t message_index,
+ const std::string& number,
+ const std::string& text) {
+ SmsMessageView* message_view = new SmsMessageView(
+ tray_sms(), SmsMessageView::VIEW_NOTIFICATION,
+ message_index_, number, text);
+ UpdateView(message_view);
+ }
+
+ // Overridden from TrayNotificationView:
+ virtual void OnClose() OVERRIDE {
+ tray_sms()->RemoveMessage(message_index_);
+ }
+
+ virtual void OnClickAction() OVERRIDE {
+ owner()->PopupDetailedView(0, true);
+ }
+
+ private:
+ TraySms* tray_sms() {
+ return static_cast<TraySms*>(owner());
+ }
+
+ size_t message_index_;
+
+ DISALLOW_COPY_AND_ASSIGN(SmsNotificationView);
+};
+
+TraySms::TraySms(SystemTray* system_tray)
+ : SystemTrayItem(system_tray),
+ default_(NULL),
+ detailed_(NULL),
+ notification_(NULL) {
+ // TODO(armansito): SMS could be a special case for cellular that requires a
+ // user (perhaps the owner) to be logged in. If that is the case, then an
+ // additional check should be done before subscribing for SMS notifications.
+ chromeos::NetworkHandler::Get()->network_sms_handler()->AddObserver(this);
+}
+
+TraySms::~TraySms() {
+ chromeos::NetworkHandler::Get()->network_sms_handler()->RemoveObserver(this);
+}
+
+views::View* TraySms::CreateDefaultView(user::LoginStatus status) {
+ CHECK(default_ == NULL);
+ default_ = new SmsDefaultView(this);
+ default_->SetVisible(!messages_.empty());
+ return default_;
+}
+
+views::View* TraySms::CreateDetailedView(user::LoginStatus status) {
+ CHECK(detailed_ == NULL);
+ HideNotificationView();
+ if (messages_.empty())
+ return NULL;
+ detailed_ = new SmsDetailedView(this);
+ return detailed_;
+}
+
+views::View* TraySms::CreateNotificationView(user::LoginStatus status) {
+ CHECK(notification_ == NULL);
+ if (detailed_)
+ return NULL;
+ size_t index;
+ std::string number, text;
+ if (GetLatestMessage(&index, &number, &text))
+ notification_ = new SmsNotificationView(this, index, number, text);
+ return notification_;
+}
+
+void TraySms::DestroyDefaultView() {
+ default_ = NULL;
+}
+
+void TraySms::DestroyDetailedView() {
+ detailed_ = NULL;
+}
+
+void TraySms::DestroyNotificationView() {
+ notification_ = NULL;
+}
+
+void TraySms::MessageReceived(const base::DictionaryValue& message) {
+
+ std::string message_text;
+ if (!message.GetStringWithoutPathExpansion(
+ chromeos::NetworkSmsHandler::kTextKey, &message_text)) {
+ NET_LOG_ERROR("SMS message contains no content.", "");
+ return;
+ }
+ // TODO(armansito): A message might be due to a special "Message Waiting"
+ // state that the message is in. Once SMS handling moves to shill, such
+ // messages should be filtered there so that this check becomes unnecessary.
+ if (message_text.empty()) {
+ NET_LOG_DEBUG("SMS has empty content text. Ignoring.", "");
+ return;
+ }
+ std::string message_number;
+ if (!message.GetStringWithoutPathExpansion(
+ chromeos::NetworkSmsHandler::kNumberKey, &message_number)) {
+ NET_LOG_DEBUG("SMS contains no number. Ignoring.", "");
+ return;
+ }
+
+ NET_LOG_DEBUG("Received SMS from: " + message_number + " with text: " +
+ message_text, "");
+
+ base::DictionaryValue* dict = new base::DictionaryValue();
+ dict->SetString(kSmsNumberKey, message_number);
+ dict->SetString(kSmsTextKey, message_text);
+ messages_.Append(dict);
+ Update(true);
+}
+
+bool TraySms::GetLatestMessage(size_t* index,
+ std::string* number,
+ std::string* text) {
+ if (messages_.empty())
+ return false;
+ DictionaryValue* message;
+ size_t message_index = messages_.GetSize() - 1;
+ if (!messages_.GetDictionary(message_index, &message))
+ return false;
+ if (!GetMessageFromDictionary(message, number, text))
+ return false;
+ *index = message_index;
+ return true;
+}
+
+void TraySms::RemoveMessage(size_t index) {
+ if (index < messages_.GetSize())
+ messages_.Remove(index, NULL);
+}
+
+void TraySms::Update(bool notify) {
+ if (messages_.empty()) {
+ if (default_)
+ default_->SetVisible(false);
+ if (detailed_)
+ HideDetailedView();
+ HideNotificationView();
+ } else {
+ if (default_) {
+ default_->SetVisible(true);
+ default_->Update();
+ }
+ if (detailed_)
+ detailed_->Update();
+ if (notification_) {
+ size_t index;
+ std::string number, text;
+ if (GetLatestMessage(&index, &number, &text))
+ notification_->Update(index, number, text);
+ } else if (notify) {
+ ShowNotificationView();
+ }
+ }
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/system/chromeos/network/tray_sms.h b/chromium/ash/system/chromeos/network/tray_sms.h
new file mode 100644
index 00000000000..5a79360052a
--- /dev/null
+++ b/chromium/ash/system/chromeos/network/tray_sms.h
@@ -0,0 +1,66 @@
+// Copyright (c) 2012 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 ASH_SYSTEM_CHROMEOS_NETWORK_TRAY_SMS_H
+#define ASH_SYSTEM_CHROMEOS_NETWORK_TRAY_SMS_H
+
+#include <string>
+
+#include "ash/system/tray/system_tray_item.h"
+#include "base/values.h"
+#include "chromeos/network/network_sms_handler.h"
+
+namespace ash {
+namespace internal {
+
+class TraySms : public SystemTrayItem,
+ public chromeos::NetworkSmsHandler::Observer {
+ public:
+ explicit TraySms(SystemTray* system_tray);
+ virtual ~TraySms();
+
+ // Overridden from SystemTrayItem.
+ virtual views::View* CreateDefaultView(user::LoginStatus status) OVERRIDE;
+ virtual views::View* CreateDetailedView(user::LoginStatus status) OVERRIDE;
+ virtual views::View* CreateNotificationView(
+ user::LoginStatus status) OVERRIDE;
+ virtual void DestroyDefaultView() OVERRIDE;
+ virtual void DestroyDetailedView() OVERRIDE;
+ virtual void DestroyNotificationView() OVERRIDE;
+
+ // Overridden from chromeos::NetworkSmsHandler::Observer.
+ virtual void MessageReceived(const base::DictionaryValue& message) OVERRIDE;
+
+ protected:
+ class SmsDefaultView;
+ class SmsDetailedView;
+ class SmsMessageView;
+ class SmsNotificationView;
+
+ // Gets the most recent message. Returns false if no messages or unable to
+ // retrieve the numebr and text from the message.
+ bool GetLatestMessage(size_t* index, std::string* number, std::string* text);
+
+ // Removes message at |index| from message list.
+ void RemoveMessage(size_t index);
+
+ // Called when sms messages have changed (through
+ // chromeos::NetworkSmsHandler::Observer).
+ void Update(bool notify);
+
+ base::ListValue& messages() { return messages_; }
+
+ private:
+ SmsDefaultView* default_;
+ SmsDetailedView* detailed_;
+ SmsNotificationView* notification_;
+ base::ListValue messages_;
+
+ DISALLOW_COPY_AND_ASSIGN(TraySms);
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_SYSTEM_CHROMEOS_NETWORK_TRAY_SMS_H
diff --git a/chromium/ash/system/chromeos/network/tray_vpn.cc b/chromium/ash/system/chromeos/network/tray_vpn.cc
new file mode 100644
index 00000000000..31c6d6a76cc
--- /dev/null
+++ b/chromium/ash/system/chromeos/network/tray_vpn.cc
@@ -0,0 +1,182 @@
+// Copyright (c) 2012 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.
+
+#include "ash/system/chromeos/network/tray_vpn.h"
+
+#include "ash/shell.h"
+#include "ash/system/chromeos/network/network_icon_animation.h"
+#include "ash/system/chromeos/network/network_state_list_detailed_view.h"
+#include "ash/system/tray/system_tray.h"
+#include "ash/system/tray/system_tray_delegate.h"
+#include "ash/system/tray/system_tray_notifier.h"
+#include "ash/system/tray/tray_constants.h"
+#include "ash/system/tray/tray_item_more.h"
+#include "ash/system/tray/tray_popup_label_button.h"
+#include "chromeos/network/network_state.h"
+#include "chromeos/network/network_state_handler.h"
+#include "grit/ash_strings.h"
+#include "third_party/cros_system_api/dbus/service_constants.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/base/resource/resource_bundle.h"
+
+using chromeos::NetworkHandler;
+using chromeos::NetworkState;
+using chromeos::NetworkStateHandler;
+
+namespace ash {
+namespace internal {
+
+namespace tray {
+
+class VpnDefaultView : public TrayItemMore,
+ public network_icon::AnimationObserver {
+ public:
+ VpnDefaultView(SystemTrayItem* owner, bool show_more)
+ : TrayItemMore(owner, show_more) {
+ Update();
+ }
+
+ virtual ~VpnDefaultView() {
+ network_icon::NetworkIconAnimation::GetInstance()->RemoveObserver(this);
+ }
+
+ static bool ShouldShow() {
+ // Do not show VPN line in uber tray bubble if VPN is not configured.
+ NetworkStateHandler* handler =
+ NetworkHandler::Get()->network_state_handler();
+ const NetworkState* vpn = handler->FirstNetworkByType(flimflam::kTypeVPN);
+ return vpn != NULL;
+ }
+
+ void Update() {
+ gfx::ImageSkia image;
+ base::string16 label;
+ bool animating = false;
+ GetNetworkStateHandlerImageAndLabel(&image, &label, &animating);
+ if (animating)
+ network_icon::NetworkIconAnimation::GetInstance()->AddObserver(this);
+ else
+ network_icon::NetworkIconAnimation::GetInstance()->RemoveObserver(this);
+ SetImage(&image);
+ SetLabel(label);
+ SetAccessibleName(label);
+ }
+
+ // network_icon::AnimationObserver
+ virtual void NetworkIconChanged() OVERRIDE {
+ Update();
+ }
+
+ private:
+ void GetNetworkStateHandlerImageAndLabel(gfx::ImageSkia* image,
+ base::string16* label,
+ bool* animating) {
+ NetworkStateHandler* handler =
+ NetworkHandler::Get()->network_state_handler();
+ const NetworkState* vpn = handler->FirstNetworkByType(
+ flimflam::kTypeVPN);
+ if (!vpn || (vpn->connection_state() == flimflam::kStateIdle)) {
+ *image = network_icon::GetImageForDisconnectedNetwork(
+ network_icon::ICON_TYPE_DEFAULT_VIEW, flimflam::kTypeVPN);
+ if (label) {
+ *label = l10n_util::GetStringUTF16(
+ IDS_ASH_STATUS_TRAY_VPN_DISCONNECTED);
+ }
+ *animating = false;
+ return;
+ }
+ *animating = vpn->IsConnectingState();
+ *image = network_icon::GetImageForNetwork(
+ vpn, network_icon::ICON_TYPE_DEFAULT_VIEW);
+ if (label) {
+ *label = network_icon::GetLabelForNetwork(
+ vpn, network_icon::ICON_TYPE_DEFAULT_VIEW);
+ }
+ }
+
+ DISALLOW_COPY_AND_ASSIGN(VpnDefaultView);
+};
+
+} // namespace tray
+
+TrayVPN::TrayVPN(SystemTray* system_tray)
+ : SystemTrayItem(system_tray),
+ default_(NULL),
+ detailed_(NULL) {
+ network_state_observer_.reset(new TrayNetworkStateObserver(this));
+}
+
+TrayVPN::~TrayVPN() {
+}
+
+views::View* TrayVPN::CreateTrayView(user::LoginStatus status) {
+ return NULL;
+}
+
+views::View* TrayVPN::CreateDefaultView(user::LoginStatus status) {
+ CHECK(default_ == NULL);
+ if (!chromeos::NetworkHandler::IsInitialized())
+ return NULL;
+ if (status == user::LOGGED_IN_NONE)
+ return NULL;
+ if (!tray::VpnDefaultView::ShouldShow())
+ return NULL;
+
+ default_ = new tray::VpnDefaultView(this, status != user::LOGGED_IN_LOCKED);
+ return default_;
+}
+
+views::View* TrayVPN::CreateDetailedView(user::LoginStatus status) {
+ CHECK(detailed_ == NULL);
+ if (!chromeos::NetworkHandler::IsInitialized())
+ return NULL;
+
+ detailed_ = new tray::NetworkStateListDetailedView(
+ this, tray::NetworkStateListDetailedView::LIST_TYPE_VPN, status);
+ detailed_->Init();
+ return detailed_;
+}
+
+views::View* TrayVPN::CreateNotificationView(user::LoginStatus status) {
+ return NULL;
+}
+
+void TrayVPN::DestroyTrayView() {
+}
+
+void TrayVPN::DestroyDefaultView() {
+ default_ = NULL;
+}
+
+void TrayVPN::DestroyDetailedView() {
+ detailed_ = NULL;
+}
+
+void TrayVPN::DestroyNotificationView() {
+}
+
+void TrayVPN::UpdateAfterLoginStatusChange(user::LoginStatus status) {
+}
+
+void TrayVPN::UpdateAfterShelfAlignmentChange(ShelfAlignment alignment) {
+}
+
+void TrayVPN::NetworkStateChanged(bool list_changed) {
+ if (default_)
+ default_->Update();
+ if (detailed_) {
+ if (list_changed)
+ detailed_->NetworkListChanged();
+ else
+ detailed_->ManagerChanged();
+ }
+}
+
+void TrayVPN::NetworkServiceChanged(const chromeos::NetworkState* network) {
+ if (detailed_)
+ detailed_->NetworkServiceChanged(network);
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/system/chromeos/network/tray_vpn.h b/chromium/ash/system/chromeos/network/tray_vpn.h
new file mode 100644
index 00000000000..ece975bf388
--- /dev/null
+++ b/chromium/ash/system/chromeos/network/tray_vpn.h
@@ -0,0 +1,59 @@
+// Copyright (c) 2012 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 ASH_SYSTEM_CHROMEOS_NETWORK_TRAY_VPN_H
+#define ASH_SYSTEM_CHROMEOS_NETWORK_TRAY_VPN_H
+
+#include "ash/system/chromeos/network/tray_network_state_observer.h"
+#include "ash/system/tray/system_tray_item.h"
+#include "base/memory/scoped_ptr.h"
+
+namespace ash {
+namespace internal {
+
+class TrayNetworkStateObserver;
+
+namespace tray {
+class NetworkDetailedView;
+class VpnDefaultView;
+class VpnDetailedView;
+}
+
+class TrayVPN : public SystemTrayItem,
+ public TrayNetworkStateObserver::Delegate {
+ public:
+ explicit TrayVPN(SystemTray* system_tray);
+ virtual ~TrayVPN();
+
+ // SystemTrayItem
+ virtual views::View* CreateTrayView(user::LoginStatus status) OVERRIDE;
+ virtual views::View* CreateDefaultView(user::LoginStatus status) OVERRIDE;
+ virtual views::View* CreateDetailedView(user::LoginStatus status) OVERRIDE;
+ virtual views::View* CreateNotificationView(
+ user::LoginStatus status) OVERRIDE;
+ virtual void DestroyTrayView() OVERRIDE;
+ virtual void DestroyDefaultView() OVERRIDE;
+ virtual void DestroyDetailedView() OVERRIDE;
+ virtual void DestroyNotificationView() OVERRIDE;
+ virtual void UpdateAfterLoginStatusChange(user::LoginStatus status) OVERRIDE;
+ virtual void UpdateAfterShelfAlignmentChange(
+ ShelfAlignment alignment) OVERRIDE;
+
+ // TrayNetworkStateObserver::Delegate
+ virtual void NetworkStateChanged(bool list_changed) OVERRIDE;
+ virtual void NetworkServiceChanged(
+ const chromeos::NetworkState* network) OVERRIDE;
+
+ private:
+ tray::VpnDefaultView* default_;
+ tray::NetworkDetailedView* detailed_;
+ scoped_ptr<TrayNetworkStateObserver> network_state_observer_;
+
+ DISALLOW_COPY_AND_ASSIGN(TrayVPN);
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_SYSTEM_CHROMEOS_NETWORK_TRAY_VPN_H
diff --git a/chromium/ash/system/chromeos/power/power_status.cc b/chromium/ash/system/chromeos/power/power_status.cc
new file mode 100644
index 00000000000..0283a0c3fec
--- /dev/null
+++ b/chromium/ash/system/chromeos/power/power_status.cc
@@ -0,0 +1,294 @@
+// Copyright (c) 2013 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.
+
+#include "ash/system/chromeos/power/power_status.h"
+
+#include <algorithm>
+#include <cmath>
+
+#include "ash/shell.h"
+#include "ash/shell_delegate.h"
+#include "base/logging.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/utf_string_conversions.h"
+#include "chromeos/dbus/dbus_thread_manager.h"
+#include "chromeos/dbus/power_manager_client.h"
+#include "grit/ash_resources.h"
+#include "grit/ash_strings.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/base/l10n/time_format.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "ui/gfx/image/image.h"
+#include "ui/gfx/image/image_skia_operations.h"
+#include "ui/gfx/rect.h"
+
+namespace ash {
+namespace internal {
+
+namespace {
+
+// Updates |proto| to ensure that its fields are consistent.
+void SanitizeProto(power_manager::PowerSupplyProperties* proto) {
+ DCHECK(proto);
+
+ if (proto->battery_state() ==
+ power_manager::PowerSupplyProperties_BatteryState_FULL)
+ proto->set_battery_percent(100.0);
+
+ if (!proto->is_calculating_battery_time()) {
+ const bool on_line_power = proto->external_power() !=
+ power_manager::PowerSupplyProperties_ExternalPower_DISCONNECTED;
+ if ((on_line_power && proto->battery_time_to_full_sec() < 0) ||
+ (!on_line_power && proto->battery_time_to_empty_sec() < 0))
+ proto->set_is_calculating_battery_time(true);
+ }
+}
+
+base::string16 GetBatteryTimeAccessibilityString(int hour, int min) {
+ DCHECK(hour || min);
+ if (hour && !min) {
+ return ui::TimeFormat::TimeDurationLong(base::TimeDelta::FromHours(hour));
+ }
+ if (min && !hour) {
+ return ui::TimeFormat::TimeDurationLong(base::TimeDelta::FromMinutes(min));
+ }
+ return l10n_util::GetStringFUTF16(
+ IDS_ASH_STATUS_TRAY_BATTERY_TIME_ACCESSIBLE,
+ ui::TimeFormat::TimeDurationLong(base::TimeDelta::FromHours(hour)),
+ ui::TimeFormat::TimeDurationLong(base::TimeDelta::FromMinutes(min)));
+}
+
+static PowerStatus* g_power_status = NULL;
+
+// Minimum battery percentage rendered in UI.
+const int kMinBatteryPercent = 1;
+
+// Width and height of battery images.
+const int kBatteryImageHeight = 25;
+const int kBatteryImageWidth = 25;
+
+// Number of different power states.
+const int kNumPowerImages = 15;
+
+} // namespace
+
+const int PowerStatus::kMaxBatteryTimeToDisplaySec = 24 * 60 * 60;
+
+// static
+void PowerStatus::Initialize() {
+ CHECK(!g_power_status);
+ g_power_status = new PowerStatus();
+}
+
+// static
+void PowerStatus::Shutdown() {
+ CHECK(g_power_status);
+ delete g_power_status;
+ g_power_status = NULL;
+}
+
+// static
+bool PowerStatus::IsInitialized() {
+ return g_power_status != NULL;
+}
+
+// static
+PowerStatus* PowerStatus::Get() {
+ CHECK(g_power_status) << "PowerStatus::Get() called before Initialize().";
+ return g_power_status;
+}
+
+// static
+bool PowerStatus::ShouldDisplayBatteryTime(const base::TimeDelta& time) {
+ return time >= base::TimeDelta::FromMinutes(1) &&
+ time.InSeconds() <= kMaxBatteryTimeToDisplaySec;
+}
+
+// static
+void PowerStatus::SplitTimeIntoHoursAndMinutes(const base::TimeDelta& time,
+ int* hours,
+ int* minutes) {
+ DCHECK(hours);
+ DCHECK(minutes);
+ *hours = time.InHours();
+ *minutes = (time - base::TimeDelta::FromHours(*hours)).InMinutes();
+}
+
+void PowerStatus::AddObserver(Observer* observer) {
+ DCHECK(observer);
+ observers_.AddObserver(observer);
+}
+
+void PowerStatus::RemoveObserver(Observer* observer) {
+ DCHECK(observer);
+ observers_.RemoveObserver(observer);
+}
+
+void PowerStatus::RequestStatusUpdate() {
+ chromeos::DBusThreadManager::Get()->GetPowerManagerClient()->
+ RequestStatusUpdate();
+}
+
+bool PowerStatus::IsBatteryPresent() const {
+ return proto_.battery_state() !=
+ power_manager::PowerSupplyProperties_BatteryState_NOT_PRESENT;
+}
+
+bool PowerStatus::IsBatteryFull() const {
+ return proto_.battery_state() ==
+ power_manager::PowerSupplyProperties_BatteryState_FULL;
+}
+
+bool PowerStatus::IsBatteryCharging() const {
+ return proto_.battery_state() ==
+ power_manager::PowerSupplyProperties_BatteryState_CHARGING;
+}
+
+bool PowerStatus::IsBatteryDischargingOnLinePower() const {
+ return IsLinePowerConnected() && proto_.battery_state() ==
+ power_manager::PowerSupplyProperties_BatteryState_DISCHARGING;
+}
+
+double PowerStatus::GetBatteryPercent() const {
+ return proto_.battery_percent();
+}
+
+int PowerStatus::GetRoundedBatteryPercent() const {
+ return std::max(kMinBatteryPercent,
+ static_cast<int>(GetBatteryPercent() + 0.5));
+}
+
+bool PowerStatus::IsBatteryTimeBeingCalculated() const {
+ return proto_.is_calculating_battery_time();
+}
+
+base::TimeDelta PowerStatus::GetBatteryTimeToEmpty() const {
+ return base::TimeDelta::FromSeconds(proto_.battery_time_to_empty_sec());
+}
+
+base::TimeDelta PowerStatus::GetBatteryTimeToFull() const {
+ return base::TimeDelta::FromSeconds(proto_.battery_time_to_full_sec());
+}
+
+bool PowerStatus::IsLinePowerConnected() const {
+ return proto_.external_power() !=
+ power_manager::PowerSupplyProperties_ExternalPower_DISCONNECTED;
+}
+
+bool PowerStatus::IsMainsChargerConnected() const {
+ return proto_.external_power() ==
+ power_manager::PowerSupplyProperties_ExternalPower_AC;
+}
+
+bool PowerStatus::IsUsbChargerConnected() const {
+ return proto_.external_power() ==
+ power_manager::PowerSupplyProperties_ExternalPower_USB;
+}
+
+gfx::ImageSkia PowerStatus::GetBatteryImage(IconSet icon_set) const {
+ gfx::Image all;
+ if (IsUsbChargerConnected()) {
+ all = ui::ResourceBundle::GetSharedInstance().GetImageNamed(
+ icon_set == ICON_DARK ?
+ IDR_AURA_UBER_TRAY_POWER_SMALL_CHARGING_UNRELIABLE_DARK :
+ IDR_AURA_UBER_TRAY_POWER_SMALL_CHARGING_UNRELIABLE);
+ } else {
+ all = ui::ResourceBundle::GetSharedInstance().GetImageNamed(
+ icon_set == ICON_DARK ?
+ IDR_AURA_UBER_TRAY_POWER_SMALL_DARK : IDR_AURA_UBER_TRAY_POWER_SMALL);
+ }
+
+ // Get the horizontal offset in the battery icon array image. The USB /
+ // "unreliable charging" image has a single column of icons; the other
+ // image contains a "battery" column on the left and a "line power"
+ // column on the right.
+ int offset = IsUsbChargerConnected() ? 0 : (IsLinePowerConnected() ? 1 : 0);
+
+ // Get the vertical offset corresponding to the current battery level.
+ int index = -1;
+ if (GetBatteryPercent() >= 100.0) {
+ index = kNumPowerImages - 1;
+ } else if (!IsBatteryPresent()) {
+ index = kNumPowerImages;
+ } else {
+ index = static_cast<int>(
+ GetBatteryPercent() / 100.0 * (kNumPowerImages - 1));
+ index = std::max(std::min(index, kNumPowerImages - 2), 0);
+ }
+
+ gfx::Rect region(
+ offset * kBatteryImageWidth, index * kBatteryImageHeight,
+ kBatteryImageWidth, kBatteryImageHeight);
+ return gfx::ImageSkiaOperations::ExtractSubset(*all.ToImageSkia(), region);
+}
+
+base::string16 PowerStatus::GetAccessibleNameString() const {
+ ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
+ if (IsBatteryFull()) {
+ return rb.GetLocalizedString(
+ IDS_ASH_STATUS_TRAY_BATTERY_FULL_CHARGE_ACCESSIBLE);
+ }
+
+ base::string16 battery_percentage_accessible = l10n_util::GetStringFUTF16(
+ IsBatteryCharging() ?
+ IDS_ASH_STATUS_TRAY_BATTERY_PERCENT_CHARGING_ACCESSIBLE :
+ IDS_ASH_STATUS_TRAY_BATTERY_PERCENT_ACCESSIBLE,
+ base::IntToString16(GetRoundedBatteryPercent()));
+ base::string16 battery_time_accessible = base::string16();
+ const base::TimeDelta time = IsBatteryCharging() ? GetBatteryTimeToFull() :
+ GetBatteryTimeToEmpty();
+
+ if (IsUsbChargerConnected()) {
+ battery_time_accessible = rb.GetLocalizedString(
+ IDS_ASH_STATUS_TRAY_BATTERY_CHARGING_UNRELIABLE_ACCESSIBLE);
+ } else if (IsBatteryTimeBeingCalculated()) {
+ battery_time_accessible = rb.GetLocalizedString(
+ IDS_ASH_STATUS_TRAY_BATTERY_CALCULATING_ACCESSIBLE);
+ } else if (ShouldDisplayBatteryTime(time) &&
+ !IsBatteryDischargingOnLinePower()) {
+ int hour = 0, min = 0;
+ PowerStatus::SplitTimeIntoHoursAndMinutes(time, &hour, &min);
+ base::string16 minute = min < 10 ?
+ ASCIIToUTF16("0") + base::IntToString16(min) :
+ base::IntToString16(min);
+ battery_time_accessible =
+ l10n_util::GetStringFUTF16(
+ IsBatteryCharging() ?
+ IDS_ASH_STATUS_TRAY_BATTERY_TIME_UNTIL_FULL_ACCESSIBLE :
+ IDS_ASH_STATUS_TRAY_BATTERY_TIME_LEFT_ACCESSIBLE,
+ GetBatteryTimeAccessibilityString(hour, min));
+ }
+ return battery_time_accessible.empty() ?
+ battery_percentage_accessible :
+ battery_percentage_accessible + ASCIIToUTF16(". ") +
+ battery_time_accessible;
+}
+
+PowerStatus::PowerStatus() {
+ chromeos::DBusThreadManager::Get()->GetPowerManagerClient()->
+ AddObserver(this);
+ chromeos::DBusThreadManager::Get()->GetPowerManagerClient()->
+ RequestStatusUpdate();
+}
+
+PowerStatus::~PowerStatus() {
+ chromeos::DBusThreadManager::Get()->GetPowerManagerClient()->
+ RemoveObserver(this);
+}
+
+void PowerStatus::SetProtoForTesting(
+ const power_manager::PowerSupplyProperties& proto) {
+ proto_ = proto;
+ SanitizeProto(&proto_);
+}
+
+void PowerStatus::PowerChanged(
+ const power_manager::PowerSupplyProperties& proto) {
+ proto_ = proto;
+ SanitizeProto(&proto_);
+ FOR_EACH_OBSERVER(Observer, observers_, OnPowerStatusChanged());
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/system/chromeos/power/power_status.h b/chromium/ash/system/chromeos/power/power_status.h
new file mode 100644
index 00000000000..a5cf3aef5c9
--- /dev/null
+++ b/chromium/ash/system/chromeos/power/power_status.h
@@ -0,0 +1,152 @@
+// Copyright (c) 2013 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 ASH_SYSTEM_CHROMEOS_POWER_POWER_STATUS_H_
+#define ASH_SYSTEM_CHROMEOS_POWER_POWER_STATUS_H_
+
+#include "ash/ash_export.h"
+#include "base/basictypes.h"
+#include "base/observer_list.h"
+#include "base/strings/string16.h"
+#include "base/time/time.h"
+#include "chromeos/dbus/power_manager/power_supply_properties.pb.h"
+#include "chromeos/dbus/power_manager_client.h"
+#include "ui/gfx/image/image_skia.h"
+
+namespace ash {
+namespace internal {
+
+// PowerStatus is a singleton that receives updates about the system's
+// power status from chromeos::PowerManagerClient and makes the information
+// available to interested classes within Ash.
+class ASH_EXPORT PowerStatus : public chromeos::PowerManagerClient::Observer {
+ public:
+ // Different styles of battery icons.
+ enum IconSet {
+ ICON_LIGHT,
+ ICON_DARK
+ };
+
+ // Interface for classes that wish to be notified when the power status
+ // has changed.
+ class Observer {
+ public:
+ // Called when the power status changes.
+ virtual void OnPowerStatusChanged() = 0;
+
+ protected:
+ virtual ~Observer() {}
+ };
+
+ // Maximum battery time-to-full or time-to-empty that should be displayed
+ // in the UI. If the current is close to zero, battery time estimates can
+ // get very large; avoid displaying these large numbers.
+ static const int kMaxBatteryTimeToDisplaySec;
+
+ // Sets the global instance. Must be called before any calls to Get().
+ static void Initialize();
+
+ // Destroys the global instance.
+ static void Shutdown();
+
+ // Returns true if the global instance is initialized.
+ static bool IsInitialized();
+
+ // Gets the global instance. Initialize must be called first.
+ static PowerStatus* Get();
+
+ // Returns true if |time|, a time returned by GetBatteryTimeToEmpty() or
+ // GetBatteryTimeToFull(), should be displayed in the UI.
+ // Less-than-a-minute or very large values aren't displayed.
+ static bool ShouldDisplayBatteryTime(const base::TimeDelta& time);
+
+ // Copies the hour and minute components of |time| to |hours| and |minutes|.
+ static void SplitTimeIntoHoursAndMinutes(const base::TimeDelta& time,
+ int* hours,
+ int* minutes);
+
+ // Adds or removes an observer.
+ void AddObserver(Observer* observer);
+ void RemoveObserver(Observer* observer);
+
+ // Requests updated status from the power manager.
+ void RequestStatusUpdate();
+
+ // Returns true if a battery is present.
+ bool IsBatteryPresent() const;
+
+ // Returns true if the battery is full. This also implies that a charger
+ // is connected.
+ bool IsBatteryFull() const;
+
+ // Returns true if the battery is charging. Note that this implies that a
+ // charger is connected but the converse is not necessarily true: the
+ // battery may be discharging even while a (perhaps low-power) charger is
+ // connected. Use Is*Connected() to test for the presence of a charger
+ // and also see IsBatteryDischargingOnLinePower().
+ bool IsBatteryCharging() const;
+
+ // Returns true if the battery is discharging (or neither charging nor
+ // discharging while not being full) while line power is connected.
+ bool IsBatteryDischargingOnLinePower() const;
+
+ // Returns the battery's remaining charge as a value in the range [0.0,
+ // 100.0].
+ double GetBatteryPercent() const;
+
+ // Returns the battery's remaining charge, rounded to an integer with a
+ // maximum value of 100.
+ int GetRoundedBatteryPercent() const;
+
+ // Returns true if the battery's time-to-full and time-to-empty estimates
+ // should not be displayed because the power manager is still calculating
+ // them.
+ bool IsBatteryTimeBeingCalculated() const;
+
+ // Returns the estimated time until the battery is empty (if line power
+ // is disconnected) or full (if line power is connected). These estimates
+ // should only be used if IsBatteryTimeBeingCalculated() returns false.
+ base::TimeDelta GetBatteryTimeToEmpty() const;
+ base::TimeDelta GetBatteryTimeToFull() const;
+
+ // Returns true if line power (including a charger of any type) is connected.
+ bool IsLinePowerConnected() const;
+
+ // Returns true if an official, non-USB charger is connected.
+ bool IsMainsChargerConnected() const;
+
+ // Returns true if a USB charger (which is likely to only support a low
+ // charging rate) is connected.
+ bool IsUsbChargerConnected() const;
+
+ // Returns the image that should be shown for the battery's current state.
+ gfx::ImageSkia GetBatteryImage(IconSet icon_set) const;
+
+ // Returns an string describing the current state for accessibility.
+ base::string16 GetAccessibleNameString() const;
+
+ // Updates |proto_|. Does not notify observers.
+ void SetProtoForTesting(const power_manager::PowerSupplyProperties& proto);
+
+ protected:
+ PowerStatus();
+ virtual ~PowerStatus();
+
+ private:
+ // Overriden from PowerManagerClient::Observer.
+ virtual void PowerChanged(
+ const power_manager::PowerSupplyProperties& proto) OVERRIDE;
+
+ ObserverList<Observer> observers_;
+
+ // Current state.
+ power_manager::PowerSupplyProperties proto_;
+
+ DISALLOW_COPY_AND_ASSIGN(PowerStatus);
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_SYSTEM_CHROMEOS_POWER_POWER_STATUS_H_
diff --git a/chromium/ash/system/chromeos/power/power_status_unittest.cc b/chromium/ash/system/chromeos/power/power_status_unittest.cc
new file mode 100644
index 00000000000..baf4997ecf4
--- /dev/null
+++ b/chromium/ash/system/chromeos/power/power_status_unittest.cc
@@ -0,0 +1,135 @@
+// Copyright (c) 2013 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.
+
+#include "ash/system/chromeos/power/power_status.h"
+
+#include <set>
+#include <string>
+
+#include "base/command_line.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "chromeos/dbus/dbus_thread_manager.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/cros_system_api/dbus/service_constants.h"
+
+namespace ash {
+namespace internal {
+
+namespace {
+
+class TestObserver : public PowerStatus::Observer {
+ public:
+ TestObserver() : power_changed_count_(0) {}
+ virtual ~TestObserver() {}
+
+ int power_changed_count() const { return power_changed_count_; }
+
+ // PowerStatus::Observer overrides:
+ virtual void OnPowerStatusChanged() OVERRIDE { ++power_changed_count_; }
+
+ private:
+ int power_changed_count_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestObserver);
+};
+
+} // namespace
+
+class PowerStatusTest : public testing::Test {
+ public:
+ PowerStatusTest() : power_status_(NULL) {}
+ virtual ~PowerStatusTest() {}
+
+ virtual void SetUp() OVERRIDE {
+ chromeos::DBusThreadManager::InitializeWithStub();
+ PowerStatus::Initialize();
+ power_status_ = PowerStatus::Get();
+ test_observer_.reset(new TestObserver);
+ power_status_->AddObserver(test_observer_.get());
+ }
+
+ virtual void TearDown() OVERRIDE {
+ power_status_->RemoveObserver(test_observer_.get());
+ test_observer_.reset();
+ PowerStatus::Shutdown();
+ chromeos::DBusThreadManager::Shutdown();
+ }
+
+ protected:
+ base::MessageLoopForUI message_loop_;
+ PowerStatus* power_status_; // Not owned.
+ scoped_ptr<TestObserver> test_observer_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(PowerStatusTest);
+};
+
+TEST_F(PowerStatusTest, InitializeAndUpdate) {
+ // Test that the initial power supply state should be acquired after
+ // PowerStatus is instantiated. This depends on
+ // PowerManagerClientStubImpl, which responds to power status update
+ // requests, pretends there is a battery present, and generates some valid
+ // power supply status data.
+ message_loop_.RunUntilIdle();
+ EXPECT_EQ(1, test_observer_->power_changed_count());
+
+ // Test RequestUpdate, test_obsever_ should be notified for power suuply
+ // status change.
+ power_status_->RequestStatusUpdate();
+ message_loop_.RunUntilIdle();
+ EXPECT_EQ(2, test_observer_->power_changed_count());
+}
+
+TEST_F(PowerStatusTest, ShouldDisplayBatteryTime) {
+ EXPECT_FALSE(PowerStatus::ShouldDisplayBatteryTime(
+ base::TimeDelta::FromSeconds(-1)));
+ EXPECT_FALSE(PowerStatus::ShouldDisplayBatteryTime(
+ base::TimeDelta::FromSeconds(0)));
+ EXPECT_FALSE(PowerStatus::ShouldDisplayBatteryTime(
+ base::TimeDelta::FromSeconds(59)));
+ EXPECT_TRUE(PowerStatus::ShouldDisplayBatteryTime(
+ base::TimeDelta::FromSeconds(60)));
+ EXPECT_TRUE(PowerStatus::ShouldDisplayBatteryTime(
+ base::TimeDelta::FromSeconds(600)));
+ EXPECT_TRUE(PowerStatus::ShouldDisplayBatteryTime(
+ base::TimeDelta::FromSeconds(3600)));
+ EXPECT_TRUE(PowerStatus::ShouldDisplayBatteryTime(
+ base::TimeDelta::FromSeconds(
+ PowerStatus::kMaxBatteryTimeToDisplaySec)));
+ EXPECT_FALSE(PowerStatus::ShouldDisplayBatteryTime(
+ base::TimeDelta::FromSeconds(
+ PowerStatus::kMaxBatteryTimeToDisplaySec + 1)));
+}
+
+TEST_F(PowerStatusTest, SplitTimeIntoHoursAndMinutes) {
+ int hours = 0, minutes = 0;
+ PowerStatus::SplitTimeIntoHoursAndMinutes(
+ base::TimeDelta::FromSeconds(0), &hours, &minutes);
+ EXPECT_EQ(0, hours);
+ EXPECT_EQ(0, minutes);
+
+ PowerStatus::SplitTimeIntoHoursAndMinutes(
+ base::TimeDelta::FromSeconds(60), &hours, &minutes);
+ EXPECT_EQ(0, hours);
+ EXPECT_EQ(1, minutes);
+
+ PowerStatus::SplitTimeIntoHoursAndMinutes(
+ base::TimeDelta::FromSeconds(3600), &hours, &minutes);
+ EXPECT_EQ(1, hours);
+ EXPECT_EQ(0, minutes);
+
+ PowerStatus::SplitTimeIntoHoursAndMinutes(
+ base::TimeDelta::FromSeconds(3600 + 60), &hours, &minutes);
+ EXPECT_EQ(1, hours);
+ EXPECT_EQ(1, minutes);
+
+ PowerStatus::SplitTimeIntoHoursAndMinutes(
+ base::TimeDelta::FromSeconds(7 * 3600 + 23 * 60), &hours, &minutes);
+ EXPECT_EQ(7, hours);
+ EXPECT_EQ(23, minutes);
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/system/chromeos/power/power_status_view.cc b/chromium/ash/system/chromeos/power/power_status_view.cc
new file mode 100644
index 00000000000..5323f5c1a6d
--- /dev/null
+++ b/chromium/ash/system/chromeos/power/power_status_view.cc
@@ -0,0 +1,229 @@
+// Copyright (c) 2012 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.
+
+#include "ash/system/chromeos/power/power_status_view.h"
+
+#include "ash/shell.h"
+#include "ash/shell_delegate.h"
+#include "ash/system/chromeos/power/power_status.h"
+#include "ash/system/chromeos/power/tray_power.h"
+#include "ash/system/tray/fixed_sized_image_view.h"
+#include "ash/system/tray/tray_constants.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/utf_string_conversions.h"
+#include "grit/ash_strings.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/base/l10n/time_format.h"
+#include "ui/base/resource/resource_bundle.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/grid_layout.h"
+
+namespace ash {
+namespace internal {
+
+namespace {
+
+// Top/bottom padding of the text items.
+const int kPaddingVertical = 10;
+// Specify min width of status label for layout.
+const int kLabelMinWidth = 120;
+// Padding between battery status text and battery icon on default view.
+const int kPaddingBetweenBatteryStatusAndIcon = 3;
+} // namespace
+
+PowerStatusView::PowerStatusView(ViewType view_type,
+ bool default_view_right_align)
+ : default_view_right_align_(default_view_right_align),
+ status_label_(NULL),
+ time_label_(NULL),
+ time_status_label_(NULL),
+ percentage_label_(NULL),
+ icon_(NULL),
+ view_type_(view_type) {
+ PowerStatus::Get()->AddObserver(this);
+ if (view_type == VIEW_DEFAULT) {
+ time_status_label_ = new views::Label;
+ percentage_label_ = new views::Label;
+ percentage_label_->SetEnabledColor(kHeaderTextColorNormal);
+ LayoutDefaultView();
+ } else {
+ status_label_ = new views::Label;
+ time_label_ = new views::Label;
+ LayoutNotificationView();
+ }
+ OnPowerStatusChanged();
+}
+
+PowerStatusView::~PowerStatusView() {
+ PowerStatus::Get()->RemoveObserver(this);
+}
+
+void PowerStatusView::OnPowerStatusChanged() {
+ view_type_ == VIEW_DEFAULT ?
+ UpdateTextForDefaultView() : UpdateTextForNotificationView();
+
+ if (icon_) {
+ icon_->SetImage(
+ PowerStatus::Get()->GetBatteryImage(PowerStatus::ICON_DARK));
+ icon_->SetVisible(true);
+ }
+}
+
+void PowerStatusView::LayoutDefaultView() {
+ if (default_view_right_align_) {
+ views::BoxLayout* layout =
+ new views::BoxLayout(views::BoxLayout::kHorizontal, 0, 0,
+ kPaddingBetweenBatteryStatusAndIcon);
+ SetLayoutManager(layout);
+
+ AddChildView(percentage_label_);
+ AddChildView(time_status_label_);
+
+ icon_ = new views::ImageView;
+ AddChildView(icon_);
+ } else {
+ // PowerStatusView is left aligned on the system tray pop up item.
+ views::BoxLayout* layout =
+ new views::BoxLayout(views::BoxLayout::kHorizontal, 0, 0,
+ kTrayPopupPaddingBetweenItems);
+ SetLayoutManager(layout);
+
+ icon_ =
+ new ash::internal::FixedSizedImageView(0, ash::kTrayPopupItemHeight);
+ AddChildView(icon_);
+
+ AddChildView(percentage_label_);
+ AddChildView(time_status_label_);
+ }
+}
+
+void PowerStatusView::LayoutNotificationView() {
+ SetLayoutManager(
+ new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 1));
+ status_label_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
+ AddChildView(status_label_);
+
+ time_label_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
+ AddChildView(time_label_);
+}
+
+void PowerStatusView::UpdateTextForDefaultView() {
+ const PowerStatus& status = *PowerStatus::Get();
+ ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
+ base::string16 battery_percentage;
+ base::string16 battery_time_status;
+
+ if (status.IsBatteryFull()) {
+ battery_time_status =
+ rb.GetLocalizedString(IDS_ASH_STATUS_TRAY_BATTERY_FULL);
+ } else {
+ battery_percentage = l10n_util::GetStringFUTF16(
+ IDS_ASH_STATUS_TRAY_BATTERY_PERCENT_ONLY,
+ base::IntToString16(status.GetRoundedBatteryPercent()));
+ if (status.IsUsbChargerConnected()) {
+ battery_time_status = rb.GetLocalizedString(
+ IDS_ASH_STATUS_TRAY_BATTERY_CHARGING_UNRELIABLE);
+ } else if (status.IsBatteryTimeBeingCalculated()) {
+ battery_time_status =
+ rb.GetLocalizedString(IDS_ASH_STATUS_TRAY_BATTERY_CALCULATING);
+ } else {
+ base::TimeDelta time = status.IsBatteryCharging() ?
+ status.GetBatteryTimeToFull() : status.GetBatteryTimeToEmpty();
+ if (PowerStatus::ShouldDisplayBatteryTime(time) &&
+ !status.IsBatteryDischargingOnLinePower()) {
+ int hour = 0, min = 0;
+ PowerStatus::SplitTimeIntoHoursAndMinutes(time, &hour, &min);
+ base::string16 minute = min < 10 ?
+ ASCIIToUTF16("0") + base::IntToString16(min) :
+ base::IntToString16(min);
+ battery_time_status =
+ l10n_util::GetStringFUTF16(
+ status.IsBatteryCharging() ?
+ IDS_ASH_STATUS_TRAY_BATTERY_TIME_UNTIL_FULL_SHORT :
+ IDS_ASH_STATUS_TRAY_BATTERY_TIME_LEFT_SHORT,
+ base::IntToString16(hour),
+ minute);
+ }
+ }
+ battery_percentage = battery_time_status.empty() ?
+ battery_percentage : battery_percentage + ASCIIToUTF16(" - ");
+ }
+ percentage_label_->SetVisible(!battery_percentage.empty());
+ percentage_label_->SetText(battery_percentage);
+ time_status_label_->SetVisible(!battery_time_status.empty());
+ time_status_label_->SetText(battery_time_status);
+}
+
+void PowerStatusView::UpdateTextForNotificationView() {
+ const PowerStatus& status = *PowerStatus::Get();
+ if (status.IsBatteryFull()) {
+ status_label_->SetText(
+ ui::ResourceBundle::GetSharedInstance().GetLocalizedString(
+ IDS_ASH_STATUS_TRAY_BATTERY_FULL));
+ } else {
+ status_label_->SetText(
+ l10n_util::GetStringFUTF16(
+ IDS_ASH_STATUS_TRAY_BATTERY_PERCENT,
+ base::IntToString16(status.GetRoundedBatteryPercent())));
+ }
+
+ const base::TimeDelta time = status.IsBatteryCharging() ?
+ status.GetBatteryTimeToFull() : status.GetBatteryTimeToEmpty();
+
+ if (status.IsUsbChargerConnected()) {
+ time_label_->SetText(
+ ui::ResourceBundle::GetSharedInstance().GetLocalizedString(
+ IDS_ASH_STATUS_TRAY_BATTERY_CHARGING_UNRELIABLE));
+ } else if (status.IsBatteryTimeBeingCalculated()) {
+ time_label_->SetText(
+ ui::ResourceBundle::GetSharedInstance().GetLocalizedString(
+ IDS_ASH_STATUS_TRAY_BATTERY_CALCULATING));
+ } else if (PowerStatus::ShouldDisplayBatteryTime(time) &&
+ !status.IsBatteryDischargingOnLinePower()) {
+ int hour = 0, min = 0;
+ PowerStatus::SplitTimeIntoHoursAndMinutes(time, &hour, &min);
+ if (status.IsBatteryCharging()) {
+ time_label_->SetText(
+ l10n_util::GetStringFUTF16(
+ IDS_ASH_STATUS_TRAY_BATTERY_TIME_UNTIL_FULL,
+ base::IntToString16(hour),
+ base::IntToString16(min)));
+ } else {
+ // This is a low battery warning prompting the user in minutes.
+ min = hour * 60 + min;
+ time_label_->SetText(ui::TimeFormat::TimeRemaining(
+ base::TimeDelta::FromMinutes(min)));
+ }
+ } else {
+ time_label_->SetText(base::string16());
+ }
+}
+
+void PowerStatusView::ChildPreferredSizeChanged(views::View* child) {
+ PreferredSizeChanged();
+}
+
+gfx::Size PowerStatusView::GetPreferredSize() {
+ gfx::Size size = views::View::GetPreferredSize();
+ return gfx::Size(size.width(), kTrayPopupItemHeight);
+}
+
+int PowerStatusView::GetHeightForWidth(int width) {
+ return kTrayPopupItemHeight;
+}
+
+void PowerStatusView::Layout() {
+ views::View::Layout();
+
+ // Move the time_status_label_ closer to percentage_label_.
+ if (percentage_label_ && time_status_label_ &&
+ percentage_label_->visible() && time_status_label_->visible()) {
+ time_status_label_->SetX(percentage_label_->bounds().right() + 1);
+ }
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/system/chromeos/power/power_status_view.h b/chromium/ash/system/chromeos/power/power_status_view.h
new file mode 100644
index 00000000000..465da02b8a3
--- /dev/null
+++ b/chromium/ash/system/chromeos/power/power_status_view.h
@@ -0,0 +1,69 @@
+// Copyright (c) 2012 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 ASH_SYSTEM_CHROMEOS_POWER_POWER_STATUS_VIEW_H_
+#define ASH_SYSTEM_CHROMEOS_POWER_POWER_STATUS_VIEW_H_
+
+#include "ash/system/chromeos/power/power_status.h"
+#include "ui/views/view.h"
+
+namespace views {
+class ImageView;
+class Label;
+}
+
+namespace ash {
+namespace internal {
+
+class PowerStatusView : public views::View, public PowerStatus::Observer {
+ public:
+ enum ViewType {
+ VIEW_DEFAULT,
+ VIEW_NOTIFICATION
+ };
+
+ PowerStatusView(ViewType view_type, bool default_view_right_align);
+ virtual ~PowerStatusView();
+
+ // Overridden from views::View.
+ virtual gfx::Size GetPreferredSize() OVERRIDE;
+ virtual int GetHeightForWidth(int width) OVERRIDE;
+ virtual void Layout() OVERRIDE;
+
+ // Overridden from PowerStatus::Observer.
+ virtual void OnPowerStatusChanged() OVERRIDE;
+
+ private:
+ void LayoutDefaultView();
+ void LayoutNotificationView();
+ void UpdateTextForDefaultView();
+ void UpdateTextForNotificationView();
+
+ // Overridden from views::View.
+ virtual void ChildPreferredSizeChanged(views::View* child) OVERRIDE;
+
+ // Layout default view UI items on the right side of system tray pop up item
+ // if true; otherwise, layout the UI items on the left side.
+ bool default_view_right_align_;
+
+ // Labels used only for VIEW_NOTIFICATION.
+ views::Label* status_label_;
+ views::Label* time_label_;
+
+ // Labels used only for VIEW_DEFAULT.
+ views::Label* time_status_label_;
+ views::Label* percentage_label_;
+
+ // Battery status indicator icon.
+ views::ImageView* icon_;
+
+ ViewType view_type_;
+
+ DISALLOW_COPY_AND_ASSIGN(PowerStatusView);
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_SYSTEM_CHROMEOS_POWER_POWER_STATUS_VIEW_H_
diff --git a/chromium/ash/system/chromeos/power/tray_power.cc b/chromium/ash/system/chromeos/power/tray_power.cc
new file mode 100644
index 00000000000..cdf5300032e
--- /dev/null
+++ b/chromium/ash/system/chromeos/power/tray_power.cc
@@ -0,0 +1,306 @@
+// Copyright (c) 2012 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.
+
+#include "ash/system/chromeos/power/tray_power.h"
+
+#include "ash/ash_switches.h"
+#include "ash/system/chromeos/power/power_status_view.h"
+#include "ash/system/date/date_view.h"
+#include "ash/system/tray/tray_constants.h"
+#include "ash/system/tray/tray_notification_view.h"
+#include "ash/system/tray/tray_utils.h"
+#include "base/command_line.h"
+#include "grit/ash_resources.h"
+#include "grit/ash_strings.h"
+#include "third_party/icu/source/i18n/unicode/fieldpos.h"
+#include "third_party/icu/source/i18n/unicode/fmtable.h"
+#include "ui/base/accessibility/accessible_view_state.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "ui/message_center/message_center.h"
+#include "ui/message_center/notification.h"
+#include "ui/views/controls/button/button.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/fill_layout.h"
+#include "ui/views/layout/grid_layout.h"
+#include "ui/views/view.h"
+#include "ui/views/widget/widget.h"
+
+using message_center::MessageCenter;
+using message_center::Notification;
+
+namespace ash {
+namespace internal {
+
+namespace {
+// Top/bottom padding of the text items.
+const int kPaddingVertical = 10;
+// Specify min width of status label for layout.
+const int kLabelMinWidth = 120;
+// Notification times.
+const int kCriticalSeconds = 5 * 60;
+const int kLowPowerSeconds = 15 * 60;
+const int kNoWarningSeconds = 30 * 60;
+// Notification in battery percentage.
+const double kCriticalPercentage = 5.0;
+const double kLowPowerPercentage = 10.0;
+const double kNoWarningPercentage = 15.0;
+
+} // namespace
+
+namespace tray {
+
+// This view is used only for the tray.
+class PowerTrayView : public views::ImageView {
+ public:
+ PowerTrayView() {
+ UpdateImage();
+ }
+
+ virtual ~PowerTrayView() {
+ }
+
+ // Overriden from views::View.
+ virtual void GetAccessibleState(ui::AccessibleViewState* state) OVERRIDE {
+ state->name = accessible_name_;
+ state->role = ui::AccessibilityTypes::ROLE_PUSHBUTTON;
+ }
+
+ void UpdateStatus(bool battery_alert) {
+ UpdateImage();
+ SetVisible(PowerStatus::Get()->IsBatteryPresent());
+
+ if (battery_alert) {
+ accessible_name_ = PowerStatus::Get()->GetAccessibleNameString();
+ NotifyAccessibilityEvent(ui::AccessibilityTypes::EVENT_ALERT, true);
+ }
+ }
+
+ private:
+ void UpdateImage() {
+ SetImage(PowerStatus::Get()->GetBatteryImage(PowerStatus::ICON_LIGHT));
+ }
+
+ base::string16 accessible_name_;
+
+ DISALLOW_COPY_AND_ASSIGN(PowerTrayView);
+};
+
+class PowerNotificationView : public TrayNotificationView {
+ public:
+ explicit PowerNotificationView(TrayPower* owner)
+ : TrayNotificationView(owner, 0) {
+ power_status_view_ =
+ new PowerStatusView(PowerStatusView::VIEW_NOTIFICATION, true);
+ InitView(power_status_view_);
+ }
+
+ void UpdateStatus() {
+ SetIconImage(PowerStatus::Get()->GetBatteryImage(PowerStatus::ICON_DARK));
+ }
+
+ private:
+ PowerStatusView* power_status_view_;
+
+ DISALLOW_COPY_AND_ASSIGN(PowerNotificationView);
+};
+
+} // namespace tray
+
+using tray::PowerNotificationView;
+
+TrayPower::TrayPower(SystemTray* system_tray, MessageCenter* message_center)
+ : SystemTrayItem(system_tray),
+ message_center_(message_center),
+ power_tray_(NULL),
+ notification_view_(NULL),
+ notification_state_(NOTIFICATION_NONE),
+ usb_charger_was_connected_(false) {
+ PowerStatus::Get()->AddObserver(this);
+}
+
+TrayPower::~TrayPower() {
+ PowerStatus::Get()->RemoveObserver(this);
+}
+
+views::View* TrayPower::CreateTrayView(user::LoginStatus status) {
+ // There may not be enough information when this is created about whether
+ // there is a battery or not. So always create this, and adjust visibility as
+ // necessary.
+ CHECK(power_tray_ == NULL);
+ power_tray_ = new tray::PowerTrayView();
+ power_tray_->UpdateStatus(false);
+ return power_tray_;
+}
+
+views::View* TrayPower::CreateDefaultView(user::LoginStatus status) {
+ // Make sure icon status is up-to-date. (Also triggers stub activation).
+ PowerStatus::Get()->RequestStatusUpdate();
+ return NULL;
+}
+
+views::View* TrayPower::CreateNotificationView(user::LoginStatus status) {
+ CHECK(notification_view_ == NULL);
+ if (!PowerStatus::Get()->IsBatteryPresent())
+ return NULL;
+
+ notification_view_ = new PowerNotificationView(this);
+ notification_view_->UpdateStatus();
+
+ return notification_view_;
+}
+
+void TrayPower::DestroyTrayView() {
+ power_tray_ = NULL;
+}
+
+void TrayPower::DestroyDefaultView() {
+}
+
+void TrayPower::DestroyNotificationView() {
+ notification_view_ = NULL;
+}
+
+void TrayPower::UpdateAfterLoginStatusChange(user::LoginStatus status) {
+}
+
+void TrayPower::UpdateAfterShelfAlignmentChange(ShelfAlignment alignment) {
+ SetTrayImageItemBorder(power_tray_, alignment);
+}
+
+void TrayPower::OnPowerStatusChanged() {
+ bool battery_alert = UpdateNotificationState();
+ if (power_tray_)
+ power_tray_->UpdateStatus(battery_alert);
+ if (notification_view_)
+ notification_view_->UpdateStatus();
+
+ // Factory testing may place the battery into unusual states.
+ if (CommandLine::ForCurrentProcess()->HasSwitch(
+ ash::switches::kAshHideNotificationsForFactory))
+ return;
+
+ if (ash::switches::UseUsbChargerNotification())
+ MaybeShowUsbChargerNotification();
+
+ if (battery_alert)
+ ShowNotificationView();
+ else if (notification_state_ == NOTIFICATION_NONE)
+ HideNotificationView();
+
+ usb_charger_was_connected_ = PowerStatus::Get()->IsUsbChargerConnected();
+}
+
+bool TrayPower::MaybeShowUsbChargerNotification() {
+ ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
+ const char kNotificationId[] = "usb-charger";
+ bool usb_charger_is_connected = PowerStatus::Get()->IsUsbChargerConnected();
+
+ // Check for a USB charger being connected.
+ if (usb_charger_is_connected && !usb_charger_was_connected_) {
+ scoped_ptr<Notification> notification(new Notification(
+ message_center::NOTIFICATION_TYPE_SIMPLE,
+ kNotificationId,
+ rb.GetLocalizedString(IDS_ASH_STATUS_TRAY_LOW_POWER_CHARGER_TITLE),
+ rb.GetLocalizedString(IDS_ASH_STATUS_TRAY_LOW_POWER_CHARGER_MESSAGE),
+ rb.GetImageNamed(IDR_AURA_NOTIFICATION_LOW_POWER_CHARGER),
+ base::string16(),
+ std::string(),
+ message_center::RichNotificationData(),
+ NULL));
+ message_center_->AddNotification(notification.Pass());
+ return true;
+ }
+
+ // Check for unplug of a USB charger while the USB charger notification is
+ // showing.
+ if (!usb_charger_is_connected && usb_charger_was_connected_) {
+ message_center_->RemoveNotification(kNotificationId, false);
+ return true;
+ }
+ return false;
+}
+
+bool TrayPower::UpdateNotificationState() {
+ const PowerStatus& status = *PowerStatus::Get();
+ if (!status.IsBatteryPresent() ||
+ status.IsBatteryTimeBeingCalculated() ||
+ status.IsMainsChargerConnected()) {
+ notification_state_ = NOTIFICATION_NONE;
+ return false;
+ }
+
+ return status.IsUsbChargerConnected() ?
+ UpdateNotificationStateForRemainingPercentage() :
+ UpdateNotificationStateForRemainingTime();
+}
+
+bool TrayPower::UpdateNotificationStateForRemainingTime() {
+ const int remaining_seconds =
+ PowerStatus::Get()->GetBatteryTimeToEmpty().InSeconds();
+
+ if (remaining_seconds >= kNoWarningSeconds) {
+ notification_state_ = NOTIFICATION_NONE;
+ return false;
+ }
+
+ switch (notification_state_) {
+ case NOTIFICATION_NONE:
+ if (remaining_seconds <= kCriticalSeconds) {
+ notification_state_ = NOTIFICATION_CRITICAL;
+ return true;
+ }
+ if (remaining_seconds <= kLowPowerSeconds) {
+ notification_state_ = NOTIFICATION_LOW_POWER;
+ return true;
+ }
+ return false;
+ case NOTIFICATION_LOW_POWER:
+ if (remaining_seconds <= kCriticalSeconds) {
+ notification_state_ = NOTIFICATION_CRITICAL;
+ return true;
+ }
+ return false;
+ case NOTIFICATION_CRITICAL:
+ return false;
+ }
+ NOTREACHED();
+ return false;
+}
+
+bool TrayPower::UpdateNotificationStateForRemainingPercentage() {
+ const double remaining_percentage = PowerStatus::Get()->GetBatteryPercent();
+
+ if (remaining_percentage > kNoWarningPercentage) {
+ notification_state_ = NOTIFICATION_NONE;
+ return false;
+ }
+
+ switch (notification_state_) {
+ case NOTIFICATION_NONE:
+ if (remaining_percentage <= kCriticalPercentage) {
+ notification_state_ = NOTIFICATION_CRITICAL;
+ return true;
+ }
+ if (remaining_percentage <= kLowPowerPercentage) {
+ notification_state_ = NOTIFICATION_LOW_POWER;
+ return true;
+ }
+ return false;
+ case NOTIFICATION_LOW_POWER:
+ if (remaining_percentage <= kCriticalPercentage) {
+ notification_state_ = NOTIFICATION_CRITICAL;
+ return true;
+ }
+ return false;
+ case NOTIFICATION_CRITICAL:
+ return false;
+ }
+ NOTREACHED();
+ return false;
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/system/chromeos/power/tray_power.h b/chromium/ash/system/chromeos/power/tray_power.h
new file mode 100644
index 00000000000..2a66cd5e4ab
--- /dev/null
+++ b/chromium/ash/system/chromeos/power/tray_power.h
@@ -0,0 +1,90 @@
+// Copyright (c) 2012 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 ASH_SYSTEM_CHROMEOS_POWER_TRAY_POWER_H_
+#define ASH_SYSTEM_CHROMEOS_POWER_TRAY_POWER_H_
+
+#include "ash/system/chromeos/power/power_status.h"
+#include "ash/system/tray/system_tray_item.h"
+
+class SkBitmap;
+
+namespace gfx {
+class Image;
+class ImageSkia;
+}
+
+namespace message_center {
+class MessageCenter;
+}
+
+namespace ash {
+namespace internal {
+
+namespace tray {
+class PowerNotificationView;
+class PowerTrayView;
+}
+
+class ASH_EXPORT TrayPower : public SystemTrayItem,
+ public PowerStatus::Observer {
+ public:
+ // Visible for testing.
+ enum NotificationState {
+ NOTIFICATION_NONE,
+
+ // Low battery charge.
+ NOTIFICATION_LOW_POWER,
+
+ // Critically low battery charge.
+ NOTIFICATION_CRITICAL,
+ };
+
+ TrayPower(SystemTray* system_tray,
+ message_center::MessageCenter* message_center);
+ virtual ~TrayPower();
+
+ private:
+ friend class TrayPowerTest;
+
+ // Overridden from SystemTrayItem.
+ virtual views::View* CreateTrayView(user::LoginStatus status) OVERRIDE;
+ virtual views::View* CreateDefaultView(user::LoginStatus status) OVERRIDE;
+ virtual views::View* CreateNotificationView(
+ user::LoginStatus status) OVERRIDE;
+ virtual void DestroyTrayView() OVERRIDE;
+ virtual void DestroyDefaultView() OVERRIDE;
+ virtual void DestroyNotificationView() OVERRIDE;
+ virtual void UpdateAfterLoginStatusChange(user::LoginStatus status) OVERRIDE;
+ virtual void UpdateAfterShelfAlignmentChange(
+ ShelfAlignment alignment) OVERRIDE;
+
+ // Overridden from PowerStatus::Observer.
+ virtual void OnPowerStatusChanged() OVERRIDE;
+
+ // Show a notification that a low-power USB charger has been connected.
+ // Returns true if a notification was shown or explicitly hidden.
+ bool MaybeShowUsbChargerNotification();
+
+ // Sets |notification_state_|. Returns true if a notification should be shown.
+ bool UpdateNotificationState();
+ bool UpdateNotificationStateForRemainingTime();
+ bool UpdateNotificationStateForRemainingPercentage();
+
+ message_center::MessageCenter* message_center_; // Not owned.
+ tray::PowerTrayView* power_tray_;
+ tray::PowerNotificationView* notification_view_;
+ NotificationState notification_state_;
+
+ // Was a USB charger connected the last time OnPowerStatusChanged() was
+ // called?
+ bool usb_charger_was_connected_;
+
+ DISALLOW_COPY_AND_ASSIGN(TrayPower);
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_SYSTEM_CHROMEOS_POWER_TRAY_POWER_H_
diff --git a/chromium/ash/system/chromeos/power/tray_power_unittest.cc b/chromium/ash/system/chromeos/power/tray_power_unittest.cc
new file mode 100644
index 00000000000..82bac2754d2
--- /dev/null
+++ b/chromium/ash/system/chromeos/power/tray_power_unittest.cc
@@ -0,0 +1,173 @@
+// Copyright 2013 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.
+
+#include "ash/system/chromeos/power/tray_power.h"
+
+#include "ash/ash_switches.h"
+#include "ash/test/ash_test_base.h"
+#include "base/memory/scoped_ptr.h"
+#include "chromeos/dbus/power_manager/power_supply_properties.pb.h"
+#include "ui/message_center/fake_message_center.h"
+
+using message_center::Notification;
+using power_manager::PowerSupplyProperties;
+
+namespace {
+
+class MockMessageCenter : public message_center::FakeMessageCenter {
+ public:
+ MockMessageCenter() : add_count_(0), remove_count_(0) {}
+ virtual ~MockMessageCenter() {}
+
+ int add_count() const { return add_count_; }
+ int remove_count() const { return remove_count_; }
+
+ // message_center::FakeMessageCenter overrides:
+ virtual void AddNotification(scoped_ptr<Notification> notification) OVERRIDE {
+ add_count_++;
+ }
+ virtual void RemoveNotification(const std::string& id, bool by_user)
+ OVERRIDE {
+ remove_count_++;
+ }
+
+ private:
+ int add_count_;
+ int remove_count_;
+
+ DISALLOW_COPY_AND_ASSIGN(MockMessageCenter);
+};
+
+} // namespace
+
+namespace ash {
+namespace internal {
+
+class TrayPowerTest : public test::AshTestBase {
+ public:
+ TrayPowerTest() {}
+ virtual ~TrayPowerTest() {}
+
+ MockMessageCenter* message_center() { return message_center_.get(); }
+ TrayPower* tray_power() { return tray_power_.get(); }
+
+ // test::AshTestBase::SetUp() overrides:
+ virtual void SetUp() OVERRIDE {
+ test::AshTestBase::SetUp();
+ message_center_.reset(new MockMessageCenter());
+ tray_power_.reset(new TrayPower(NULL, message_center_.get()));
+ }
+
+ virtual void TearDown() OVERRIDE {
+ tray_power_.reset();
+ message_center_.reset();
+ test::AshTestBase::TearDown();
+ }
+
+ TrayPower::NotificationState notification_state() const {
+ return tray_power_->notification_state_;
+ }
+
+ bool MaybeShowUsbChargerNotification(const PowerSupplyProperties& proto) {
+ PowerStatus::Get()->SetProtoForTesting(proto);
+ return tray_power_->MaybeShowUsbChargerNotification();
+ }
+
+ bool UpdateNotificationState(const PowerSupplyProperties& proto) {
+ PowerStatus::Get()->SetProtoForTesting(proto);
+ return tray_power_->UpdateNotificationState();
+ }
+
+ void SetUsbChargerConnected(bool connected) {
+ tray_power_->usb_charger_was_connected_ = connected;
+ }
+
+ // Returns a discharging PowerSupplyProperties more appropriate for testing.
+ static PowerSupplyProperties DefaultPowerSupplyProperties() {
+ PowerSupplyProperties proto;
+ proto.set_external_power(
+ power_manager::PowerSupplyProperties_ExternalPower_DISCONNECTED);
+ proto.set_battery_state(
+ power_manager::PowerSupplyProperties_BatteryState_DISCHARGING);
+ proto.set_battery_percent(50.0);
+ proto.set_battery_time_to_empty_sec(3 * 60 * 60);
+ proto.set_battery_time_to_full_sec(2 * 60 * 60);
+ proto.set_is_calculating_battery_time(false);
+ return proto;
+ }
+
+ private:
+ scoped_ptr<MockMessageCenter> message_center_;
+ scoped_ptr<TrayPower> tray_power_;
+
+ DISALLOW_COPY_AND_ASSIGN(TrayPowerTest);
+};
+
+TEST_F(TrayPowerTest, MaybeShowUsbChargerNotification) {
+ PowerSupplyProperties discharging = DefaultPowerSupplyProperties();
+ EXPECT_FALSE(MaybeShowUsbChargerNotification(discharging));
+ EXPECT_EQ(0, message_center()->add_count());
+ EXPECT_EQ(0, message_center()->remove_count());
+
+ // Notification shows when connecting a USB charger.
+ PowerSupplyProperties usb_connected = DefaultPowerSupplyProperties();
+ usb_connected.set_external_power(
+ power_manager::PowerSupplyProperties_ExternalPower_USB);
+ EXPECT_TRUE(MaybeShowUsbChargerNotification(usb_connected));
+ EXPECT_EQ(1, message_center()->add_count());
+ EXPECT_EQ(0, message_center()->remove_count());
+
+ // Change in charge does not trigger the notification again.
+ PowerSupplyProperties more_charge = DefaultPowerSupplyProperties();
+ more_charge.set_external_power(
+ power_manager::PowerSupplyProperties_ExternalPower_USB);
+ more_charge.set_battery_time_to_full_sec(60 * 60);
+ more_charge.set_battery_percent(75.0);
+ SetUsbChargerConnected(true);
+ EXPECT_FALSE(MaybeShowUsbChargerNotification(more_charge));
+ EXPECT_EQ(1, message_center()->add_count());
+ EXPECT_EQ(0, message_center()->remove_count());
+
+ // Disconnecting a USB charger with the notification showing should close
+ // the notification.
+ EXPECT_TRUE(MaybeShowUsbChargerNotification(discharging));
+ EXPECT_EQ(1, message_center()->add_count());
+ EXPECT_EQ(1, message_center()->remove_count());
+}
+
+TEST_F(TrayPowerTest, UpdateNotificationState) {
+ // No notifications when no battery present.
+ PowerSupplyProperties no_battery = DefaultPowerSupplyProperties();
+ no_battery.set_external_power(
+ power_manager::PowerSupplyProperties_ExternalPower_AC);
+ no_battery.set_battery_state(
+ power_manager::PowerSupplyProperties_BatteryState_NOT_PRESENT);
+ EXPECT_FALSE(UpdateNotificationState(no_battery));
+ EXPECT_EQ(TrayPower::NOTIFICATION_NONE, notification_state());
+
+ // No notification when calculating remaining battery time.
+ PowerSupplyProperties calculating = DefaultPowerSupplyProperties();
+ calculating.set_is_calculating_battery_time(true);
+ EXPECT_FALSE(UpdateNotificationState(calculating));
+ EXPECT_EQ(TrayPower::NOTIFICATION_NONE, notification_state());
+
+ // No notification when charging.
+ PowerSupplyProperties charging = DefaultPowerSupplyProperties();
+ charging.set_external_power(
+ power_manager::PowerSupplyProperties_ExternalPower_AC);
+ charging.set_battery_state(
+ power_manager::PowerSupplyProperties_BatteryState_CHARGING);
+ EXPECT_FALSE(UpdateNotificationState(charging));
+ EXPECT_EQ(TrayPower::NOTIFICATION_NONE, notification_state());
+
+ // Critical low battery notification.
+ PowerSupplyProperties critical = DefaultPowerSupplyProperties();
+ critical.set_battery_time_to_empty_sec(60);
+ critical.set_battery_percent(2.0);
+ EXPECT_TRUE(UpdateNotificationState(critical));
+ EXPECT_EQ(TrayPower::NOTIFICATION_CRITICAL, notification_state());
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/system/chromeos/screen_security/screen_capture_observer.h b/chromium/ash/system/chromeos/screen_security/screen_capture_observer.h
new file mode 100644
index 00000000000..6fca492a16b
--- /dev/null
+++ b/chromium/ash/system/chromeos/screen_security/screen_capture_observer.h
@@ -0,0 +1,29 @@
+// Copyright 2013 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 ASH_SYSTEM_CHROMEOS_SCREEN_CAPTURE_SCREEN_CAPTURE_OBSERVER_H_
+#define ASH_SYSTEM_CHROMEOS_SCREEN_CAPTURE_SCREEN_CAPTURE_OBSERVER_H_
+
+#include "base/callback.h"
+#include "base/strings/string16.h"
+
+namespace ash {
+
+class ScreenCaptureObserver {
+ public:
+ // Called when screen capture is started.
+ virtual void OnScreenCaptureStart(
+ const base::Closure& stop_callback,
+ const base::string16& screen_capture_status) = 0;
+
+ // Called when screen capture is stopped.
+ virtual void OnScreenCaptureStop() = 0;
+
+ protected:
+ virtual ~ScreenCaptureObserver() {}
+};
+
+} // namespace ash
+
+#endif // ASH_SYSTEM_CHROMEOS_SCREEN_CAPTURE_SCREEN_CAPTURE_OBSERVER_H_
diff --git a/chromium/ash/system/chromeos/screen_security/screen_capture_tray_item.cc b/chromium/ash/system/chromeos/screen_security/screen_capture_tray_item.cc
new file mode 100644
index 00000000000..6563e4bad86
--- /dev/null
+++ b/chromium/ash/system/chromeos/screen_security/screen_capture_tray_item.cc
@@ -0,0 +1,91 @@
+// Copyright 2013 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.
+
+#include "ash/system/chromeos/screen_security/screen_capture_tray_item.h"
+
+#include "ash/shell.h"
+#include "grit/ash_resources.h"
+#include "grit/ash_strings.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "ui/message_center/message_center.h"
+#include "ui/message_center/notification.h"
+
+using message_center::Notification;
+
+namespace ash {
+namespace internal {
+namespace {
+
+const char kScreenCaptureNotificationId[] = "chrome://screen/capture";
+
+} // namespace
+
+ScreenCaptureTrayItem::ScreenCaptureTrayItem(SystemTray* system_tray)
+ : ScreenTrayItem(system_tray) {
+ Shell::GetInstance()->system_tray_notifier()->
+ AddScreenCaptureObserver(this);
+}
+
+ScreenCaptureTrayItem::~ScreenCaptureTrayItem() {
+ Shell::GetInstance()->system_tray_notifier()->
+ RemoveScreenCaptureObserver(this);
+}
+
+views::View* ScreenCaptureTrayItem::CreateTrayView(user::LoginStatus status) {
+ set_tray_view(
+ new tray::ScreenTrayView(this, IDR_AURA_UBER_TRAY_DISPLAY_LIGHT));
+ return tray_view();
+}
+
+views::View* ScreenCaptureTrayItem::CreateDefaultView(
+ user::LoginStatus status) {
+ set_default_view(new tray::ScreenStatusView(
+ this,
+ IDR_AURA_UBER_TRAY_DISPLAY,
+ screen_capture_status_,
+ l10n_util::GetStringUTF16(
+ IDS_ASH_STATUS_TRAY_SCREEN_CAPTURE_STOP)));
+ return default_view();
+}
+
+void ScreenCaptureTrayItem::CreateOrUpdateNotification() {
+ message_center::RichNotificationData data;
+ data.buttons.push_back(message_center::ButtonInfo(
+ l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_SCREEN_CAPTURE_STOP)));
+ ui::ResourceBundle& resource_bundle = ui::ResourceBundle::GetSharedInstance();
+ scoped_ptr<Notification> notification(new Notification(
+ message_center::NOTIFICATION_TYPE_SIMPLE,
+ kScreenCaptureNotificationId,
+ screen_capture_status_,
+ base::string16() /* body is blank */,
+ resource_bundle.GetImageNamed(IDR_AURA_UBER_TRAY_DISPLAY),
+ base::string16() /* display_source */,
+ std::string() /* extension_id */,
+ data,
+ new tray::ScreenNotificationDelegate(this)));
+ notification->SetSystemPriority();
+ message_center::MessageCenter::Get()->AddNotification(notification.Pass());
+}
+
+std::string ScreenCaptureTrayItem::GetNotificationId() {
+ return kScreenCaptureNotificationId;
+}
+
+void ScreenCaptureTrayItem::OnScreenCaptureStart(
+ const base::Closure& stop_callback,
+ const base::string16& screen_capture_status) {
+ screen_capture_status_ = screen_capture_status;
+ Start(stop_callback);
+}
+
+void ScreenCaptureTrayItem::OnScreenCaptureStop() {
+ // We do not need to run the stop callback when
+ // screen capture is stopped externally.
+ set_is_started(false);
+ Update();
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/system/chromeos/screen_security/screen_capture_tray_item.h b/chromium/ash/system/chromeos/screen_security/screen_capture_tray_item.h
new file mode 100644
index 00000000000..92f6c8a7117
--- /dev/null
+++ b/chromium/ash/system/chromeos/screen_security/screen_capture_tray_item.h
@@ -0,0 +1,47 @@
+// Copyright 2013 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 ASH_SYSTEM_CHROMEOS_SCREEN_CAPTURE_SCREEN_CAPTURE_TRAY_ITEM_H_
+#define ASH_SYSTEM_CHROMEOS_SCREEN_CAPTURE_SCREEN_CAPTURE_TRAY_ITEM_H_
+
+#include "ash/system/chromeos/screen_security/screen_capture_observer.h"
+#include "ash/system/chromeos/screen_security/screen_tray_item.h"
+
+namespace views {
+class View;
+}
+
+namespace ash {
+namespace internal {
+
+class ASH_EXPORT ScreenCaptureTrayItem : public ScreenTrayItem,
+ public ScreenCaptureObserver {
+ public:
+ explicit ScreenCaptureTrayItem(SystemTray* system_tray);
+ virtual ~ScreenCaptureTrayItem();
+
+ private:
+ // Overridden from SystemTrayItem.
+ virtual views::View* CreateTrayView(user::LoginStatus status) OVERRIDE;
+ virtual views::View* CreateDefaultView(user::LoginStatus status) OVERRIDE;
+
+ // Overridden from ScreenTrayItem.
+ virtual void CreateOrUpdateNotification() OVERRIDE;
+ virtual std::string GetNotificationId() OVERRIDE;
+
+ // Overridden from ScreenCaptureObserver.
+ virtual void OnScreenCaptureStart(
+ const base::Closure& stop_callback,
+ const base::string16& screen_capture_status) OVERRIDE;
+ virtual void OnScreenCaptureStop() OVERRIDE;
+
+ base::string16 screen_capture_status_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScreenCaptureTrayItem);
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_SYSTEM_CHROMEOS_SCREEN_CAPTURE_SCREEN_CAPTURE_TRAY_ITEM_H_
diff --git a/chromium/ash/system/chromeos/screen_security/screen_share_observer.h b/chromium/ash/system/chromeos/screen_security/screen_share_observer.h
new file mode 100644
index 00000000000..fb6a556fa2c
--- /dev/null
+++ b/chromium/ash/system/chromeos/screen_security/screen_share_observer.h
@@ -0,0 +1,29 @@
+// Copyright 2013 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 ASH_SYSTEM_CHROMEOS_SCREEN_SHARE_SCREEN_SHARE_OBSERVER_H_
+#define ASH_SYSTEM_CHROMEOS_SCREEN_SHARE_SCREEN_SHARE_OBSERVER_H_
+
+#include "base/callback.h"
+#include "base/strings/string16.h"
+
+namespace ash {
+
+class ScreenShareObserver {
+ public:
+ // Called when screen share is started.
+ virtual void OnScreenShareStart(
+ const base::Closure& stop_callback,
+ const base::string16& helper_name) = 0;
+
+ // Called when screen share is stopped.
+ virtual void OnScreenShareStop() = 0;
+
+ protected:
+ virtual ~ScreenShareObserver() {}
+};
+
+} // namespace ash
+
+#endif // ASH_SYSTEM_CHROMEOS_SCREEN_SHARE_SCREEN_SHARE_OBSERVER_H_
diff --git a/chromium/ash/system/chromeos/screen_security/screen_share_tray_item.cc b/chromium/ash/system/chromeos/screen_security/screen_share_tray_item.cc
new file mode 100644
index 00000000000..18dd317883c
--- /dev/null
+++ b/chromium/ash/system/chromeos/screen_security/screen_share_tray_item.cc
@@ -0,0 +1,101 @@
+// Copyright 2013 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.
+
+#include "ash/system/chromeos/screen_security/screen_share_tray_item.h"
+
+#include "ash/shell.h"
+#include "grit/ash_resources.h"
+#include "grit/ash_strings.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "ui/message_center/message_center.h"
+#include "ui/message_center/notification.h"
+
+using message_center::Notification;
+
+namespace ash {
+namespace internal {
+namespace {
+
+const char kScreenShareNotificationId[] = "chrome://screen/share";
+
+}
+
+ScreenShareTrayItem::ScreenShareTrayItem(SystemTray* system_tray)
+ : ScreenTrayItem(system_tray) {
+ Shell::GetInstance()->system_tray_notifier()->
+ AddScreenShareObserver(this);
+}
+
+ScreenShareTrayItem::~ScreenShareTrayItem() {
+ Shell::GetInstance()->system_tray_notifier()->
+ RemoveScreenShareObserver(this);
+}
+
+views::View* ScreenShareTrayItem::CreateTrayView(user::LoginStatus status) {
+ set_tray_view(
+ new tray::ScreenTrayView(this, IDR_AURA_UBER_TRAY_DISPLAY_LIGHT));
+ return tray_view();
+}
+
+views::View* ScreenShareTrayItem::CreateDefaultView(user::LoginStatus status) {
+ set_default_view(new tray::ScreenStatusView(
+ this,
+ IDR_AURA_UBER_TRAY_DISPLAY,
+ l10n_util::GetStringUTF16(
+ IDS_ASH_STATUS_TRAY_SCREEN_SHARE_BEING_HELPED),
+ l10n_util::GetStringUTF16(
+ IDS_ASH_STATUS_TRAY_SCREEN_SHARE_STOP)));
+ return default_view();
+}
+
+void ScreenShareTrayItem::CreateOrUpdateNotification() {
+ base::string16 help_label_text;
+ if (!helper_name_.empty()) {
+ help_label_text = l10n_util::GetStringFUTF16(
+ IDS_ASH_STATUS_TRAY_SCREEN_SHARE_BEING_HELPED_NAME,
+ helper_name_);
+ } else {
+ help_label_text = l10n_util::GetStringUTF16(
+ IDS_ASH_STATUS_TRAY_SCREEN_SHARE_BEING_HELPED);
+ }
+
+ message_center::RichNotificationData data;
+ data.buttons.push_back(message_center::ButtonInfo(
+ l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_SCREEN_SHARE_STOP)));
+ ui::ResourceBundle& resource_bundle = ui::ResourceBundle::GetSharedInstance();
+ scoped_ptr<Notification> notification(new Notification(
+ message_center::NOTIFICATION_TYPE_SIMPLE,
+ kScreenShareNotificationId,
+ help_label_text,
+ base::string16() /* body is blank */,
+ resource_bundle.GetImageNamed(IDR_AURA_UBER_TRAY_DISPLAY),
+ base::string16() /* display_source */,
+ std::string() /* extension_id */,
+ data,
+ new tray::ScreenNotificationDelegate(this)));
+ notification->SetSystemPriority();
+ message_center::MessageCenter::Get()->AddNotification(notification.Pass());
+}
+
+std::string ScreenShareTrayItem::GetNotificationId() {
+ return kScreenShareNotificationId;
+}
+
+void ScreenShareTrayItem::OnScreenShareStart(
+ const base::Closure& stop_callback,
+ const base::string16& helper_name) {
+ helper_name_ = helper_name;
+ Start(stop_callback);
+}
+
+void ScreenShareTrayItem::OnScreenShareStop() {
+ // We do not need to run the stop callback
+ // when screening is stopped externally.
+ set_is_started(false);
+ Update();
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/system/chromeos/screen_security/screen_share_tray_item.h b/chromium/ash/system/chromeos/screen_security/screen_share_tray_item.h
new file mode 100644
index 00000000000..14f26e5667f
--- /dev/null
+++ b/chromium/ash/system/chromeos/screen_security/screen_share_tray_item.h
@@ -0,0 +1,47 @@
+// Copyright 2013 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 ASH_SYSTEM_CHROMEOS_SCREEN_SHARE_SCREEN_SHARE_TRAY_ITEM_H_
+#define ASH_SYSTEM_CHROMEOS_SCREEN_SHARE_SCREEN_SHARE_TRAY_ITEM_H_
+
+#include "ash/system/chromeos/screen_security/screen_share_observer.h"
+#include "ash/system/chromeos/screen_security/screen_tray_item.h"
+
+namespace views {
+class View;
+}
+
+namespace ash {
+namespace internal {
+
+class ASH_EXPORT ScreenShareTrayItem : public ScreenTrayItem,
+ public ScreenShareObserver {
+ public:
+ explicit ScreenShareTrayItem(SystemTray* system_tray);
+ virtual ~ScreenShareTrayItem();
+
+ private:
+ // Overridden from SystemTrayItem.
+ virtual views::View* CreateTrayView(user::LoginStatus status) OVERRIDE;
+ virtual views::View* CreateDefaultView(user::LoginStatus status) OVERRIDE;
+
+ // Overridden from ScreenTrayItem.
+ virtual void CreateOrUpdateNotification() OVERRIDE;
+ virtual std::string GetNotificationId() OVERRIDE;
+
+ // Overridden from ScreenShareObserver.
+ virtual void OnScreenShareStart(
+ const base::Closure& stop_callback,
+ const base::string16& helper_name) OVERRIDE;
+ virtual void OnScreenShareStop() OVERRIDE;
+
+ base::string16 helper_name_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScreenShareTrayItem);
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_SYSTEM_CHROMEOS_SCREEN_SHARE_SCREEN_SHARE_TRAY_ITEM_H_
diff --git a/chromium/ash/system/chromeos/screen_security/screen_tray_item.cc b/chromium/ash/system/chromeos/screen_security/screen_tray_item.cc
new file mode 100644
index 00000000000..4b10a270723
--- /dev/null
+++ b/chromium/ash/system/chromeos/screen_security/screen_tray_item.cc
@@ -0,0 +1,200 @@
+// Copyright 2013 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.
+
+#include "ash/system/chromeos/screen_security/screen_tray_item.h"
+
+#include "ash/system/tray/fixed_sized_image_view.h"
+#include "ash/system/tray/tray_constants.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "ui/message_center/message_center.h"
+#include "ui/views/controls/label.h"
+#include "ui/views/layout/box_layout.h"
+
+namespace {
+const int kStopButtonRightPadding = 18;
+} // namespace
+
+namespace ash {
+namespace internal {
+
+namespace tray {
+
+// ScreenTrayView implementations.
+ScreenTrayView::ScreenTrayView(ScreenTrayItem* screen_tray_item, int icon_id)
+ : TrayItemView(screen_tray_item),
+ screen_tray_item_(screen_tray_item) {
+ CreateImageView();
+ image_view()->SetImage(ui::ResourceBundle::GetSharedInstance()
+ .GetImageNamed(icon_id).ToImageSkia());
+
+ Update();
+}
+
+ScreenTrayView::~ScreenTrayView() {
+}
+
+void ScreenTrayView::Update() {
+ SetVisible(screen_tray_item_->is_started());
+}
+
+
+// ScreenStatusView implementations.
+ScreenStatusView::ScreenStatusView(ScreenTrayItem* screen_tray_item,
+ int icon_id,
+ const base::string16& label_text,
+ const base::string16& stop_button_text)
+ : screen_tray_item_(screen_tray_item),
+ icon_(NULL),
+ label_(NULL),
+ stop_button_(NULL),
+ icon_id_(icon_id),
+ label_text_(label_text),
+ stop_button_text_(stop_button_text) {
+ CreateItems();
+ Update();
+}
+
+ScreenStatusView::~ScreenStatusView() {
+}
+
+void ScreenStatusView::Layout() {
+ views::View::Layout();
+
+ // Give the stop button the space it requests.
+ gfx::Size stop_size = stop_button_->GetPreferredSize();
+ gfx::Rect stop_bounds(stop_size);
+ stop_bounds.set_x(width() - stop_size.width() - kStopButtonRightPadding);
+ stop_bounds.set_y((height() - stop_size.height()) / 2);
+ stop_button_->SetBoundsRect(stop_bounds);
+
+ // Adjust the label's bounds in case it got cut off by |stop_button_|.
+ if (label_->bounds().Intersects(stop_button_->bounds())) {
+ gfx::Rect label_bounds = label_->bounds();
+ label_bounds.set_width(
+ stop_button_->x() - kTrayPopupPaddingBetweenItems - label_->x());
+ label_->SetBoundsRect(label_bounds);
+ }
+}
+
+void ScreenStatusView::ButtonPressed(
+ views::Button* sender,
+ const ui::Event& event) {
+ DCHECK(sender == stop_button_);
+ screen_tray_item_->Stop();
+}
+
+void ScreenStatusView::CreateItems() {
+ set_background(views::Background::CreateSolidBackground(kBackgroundColor));
+ ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance();
+ SetLayoutManager(new views::BoxLayout(views::BoxLayout::kHorizontal,
+ kTrayPopupPaddingHorizontal,
+ 0,
+ kTrayPopupPaddingBetweenItems));
+ icon_ = new FixedSizedImageView(0, kTrayPopupItemHeight);
+ icon_->SetImage(bundle.GetImageNamed(icon_id_).ToImageSkia());
+ AddChildView(icon_);
+ label_ = new views::Label;
+ label_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
+ label_->SetMultiLine(true);
+ label_->SetText(label_text_);
+ AddChildView(label_);
+
+ stop_button_ = new TrayPopupLabelButton(this, stop_button_text_);
+ AddChildView(stop_button_);
+}
+
+void ScreenStatusView::Update() {
+ // Hide the notification bubble when the ash tray bubble opens.
+ screen_tray_item_->HideNotificationView();
+ SetVisible(screen_tray_item_->is_started());
+}
+
+ScreenNotificationDelegate::ScreenNotificationDelegate(
+ ScreenTrayItem* screen_tray)
+ : screen_tray_(screen_tray) {
+}
+
+ScreenNotificationDelegate::~ScreenNotificationDelegate() {
+}
+
+void ScreenNotificationDelegate::Display() {
+}
+
+void ScreenNotificationDelegate::Error() {
+}
+
+void ScreenNotificationDelegate::Close(bool by_user) {
+}
+
+void ScreenNotificationDelegate::Click() {
+}
+
+void ScreenNotificationDelegate::ButtonClick(int button_index) {
+ DCHECK_EQ(0, button_index);
+ screen_tray_->Stop();
+}
+
+} // namespace tray
+
+ScreenTrayItem::ScreenTrayItem(SystemTray* system_tray)
+ : SystemTrayItem(system_tray),
+ tray_view_(NULL),
+ default_view_(NULL),
+ is_started_(false),
+ stop_callback_(base::Bind(&base::DoNothing)) {
+}
+
+ScreenTrayItem::~ScreenTrayItem() {}
+
+void ScreenTrayItem::Update() {
+ if (tray_view_)
+ tray_view_->Update();
+ if (default_view_)
+ default_view_->Update();
+ if (is_started_) {
+ CreateOrUpdateNotification();
+ } else {
+ message_center::MessageCenter::Get()->RemoveNotification(
+ GetNotificationId(), false /* by_user */);
+ }
+}
+
+void ScreenTrayItem::Start(const base::Closure& stop_callback) {
+ stop_callback_ = stop_callback;
+ is_started_ = true;
+
+ if (tray_view_)
+ tray_view_->Update();
+
+ if (default_view_)
+ default_view_->Update();
+
+ if (!system_tray()->HasSystemBubbleType(
+ SystemTrayBubble::BUBBLE_TYPE_DEFAULT)) {
+ CreateOrUpdateNotification();
+ }
+}
+
+void ScreenTrayItem::Stop() {
+ is_started_ = false;
+ Update();
+
+ if (stop_callback_.is_null())
+ return;
+
+ base::Closure callback = stop_callback_;
+ stop_callback_.Reset();
+ callback.Run();
+}
+
+void ScreenTrayItem::DestroyTrayView() {
+ tray_view_ = NULL;
+}
+
+void ScreenTrayItem::DestroyDefaultView() {
+ default_view_ = NULL;
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/system/chromeos/screen_security/screen_tray_item.h b/chromium/ash/system/chromeos/screen_security/screen_tray_item.h
new file mode 100644
index 00000000000..f078a39f0e2
--- /dev/null
+++ b/chromium/ash/system/chromeos/screen_security/screen_tray_item.h
@@ -0,0 +1,145 @@
+// Copyright 2013 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 ASH_SYSTEM_CHROMEOS_SCREEN_TRAY_ITEM_H_
+#define ASH_SYSTEM_CHROMEOS_SCREEN_TRAY_ITEM_H_
+
+#include "ash/system/tray/system_tray.h"
+#include "ash/system/tray/system_tray_item.h"
+#include "ash/system/tray/system_tray_notifier.h"
+#include "ash/system/tray/tray_item_view.h"
+#include "ash/system/tray/tray_notification_view.h"
+#include "ash/system/tray/tray_popup_label_button.h"
+#include "ui/message_center/notification_delegate.h"
+#include "ui/views/controls/button/button.h"
+#include "ui/views/controls/image_view.h"
+
+namespace views {
+class View;
+}
+
+namespace ash {
+namespace internal {
+
+class ScreenTrayItem;
+
+namespace tray {
+
+class ScreenTrayView : public TrayItemView {
+ public:
+ ScreenTrayView(ScreenTrayItem* screen_tray_item, int icon_id);
+ virtual ~ScreenTrayView();
+
+ void Update();
+
+ private:
+ ScreenTrayItem* screen_tray_item_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScreenTrayView);
+};
+
+class ScreenStatusView : public views::View,
+ public views::ButtonListener {
+ public:
+ ScreenStatusView(ScreenTrayItem* screen_tray_item,
+ int icon_id,
+ const base::string16& label_text,
+ const base::string16& stop_button_text);
+ virtual ~ScreenStatusView();
+
+ // Overridden from views::View.
+ virtual void Layout() OVERRIDE;
+
+ // Overridden from views::ButtonListener.
+ virtual void ButtonPressed(views::Button* sender,
+ const ui::Event& event) OVERRIDE;
+
+ void CreateItems();
+ void Update();
+
+ private:
+ ScreenTrayItem* screen_tray_item_;
+ views::ImageView* icon_;
+ views::Label* label_;
+ TrayPopupLabelButton* stop_button_;
+ int icon_id_;
+ base::string16 label_text_;
+ base::string16 stop_button_text_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScreenStatusView);
+};
+
+class ScreenNotificationDelegate : public message_center::NotificationDelegate {
+ public:
+ explicit ScreenNotificationDelegate(ScreenTrayItem* screen_tray);
+
+ // message_center::NotificationDelegate overrides:
+ virtual void Display() OVERRIDE;
+ virtual void Error() OVERRIDE;
+ virtual void Close(bool by_user) OVERRIDE;
+ virtual void Click() OVERRIDE;
+ virtual void ButtonClick(int button_index) OVERRIDE;
+
+ protected:
+ virtual ~ScreenNotificationDelegate();
+
+ private:
+ ScreenTrayItem* screen_tray_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScreenNotificationDelegate);
+};
+
+} // namespace tray
+
+
+// The base tray item for screen capture and screen sharing. The
+// Start method brings up a notification and a tray item, and the user
+// can stop the screen capture/sharing by pressing the stop button.
+class ASH_EXPORT ScreenTrayItem : public SystemTrayItem {
+ public:
+ explicit ScreenTrayItem(SystemTray* system_tray);
+ virtual ~ScreenTrayItem();
+
+ tray::ScreenTrayView* tray_view() { return tray_view_; }
+ void set_tray_view(tray::ScreenTrayView* tray_view) {
+ tray_view_ = tray_view;
+ }
+
+ tray::ScreenStatusView* default_view() { return default_view_; }
+ void set_default_view(tray::ScreenStatusView* default_view) {
+ default_view_ = default_view;
+ }
+
+ bool is_started() const { return is_started_; }
+ void set_is_started(bool is_started) { is_started_ = is_started; }
+
+ void Update();
+ void Start(const base::Closure& stop_callback);
+ void Stop();
+
+ // Creates or updates the notification for the tray item.
+ virtual void CreateOrUpdateNotification() = 0;
+
+ // Returns the id of the notification for the tray item.
+ virtual std::string GetNotificationId() = 0;
+
+ // Overridden from SystemTrayItem.
+ virtual views::View* CreateTrayView(user::LoginStatus status) OVERRIDE = 0;
+ virtual views::View* CreateDefaultView(user::LoginStatus status) OVERRIDE = 0;
+ virtual void DestroyTrayView() OVERRIDE;
+ virtual void DestroyDefaultView() OVERRIDE;
+
+ private:
+ tray::ScreenTrayView* tray_view_;
+ tray::ScreenStatusView* default_view_;
+ bool is_started_;
+ base::Closure stop_callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScreenTrayItem);
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_SYSTEM_CHROMEOS_SCREEN_TRAY_ITEM_H_
diff --git a/chromium/ash/system/chromeos/screen_security/screen_tray_item_unittest.cc b/chromium/ash/system/chromeos/screen_security/screen_tray_item_unittest.cc
new file mode 100644
index 00000000000..e51a91fa832
--- /dev/null
+++ b/chromium/ash/system/chromeos/screen_security/screen_tray_item_unittest.cc
@@ -0,0 +1,225 @@
+// Copyright 2013 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.
+
+#include "ash/system/chromeos/screen_security/screen_tray_item.h"
+
+#include "ash/system/chromeos/screen_security/screen_capture_tray_item.h"
+#include "ash/system/chromeos/screen_security/screen_share_tray_item.h"
+#include "ash/system/tray/tray_item_view.h"
+#include "ash/test/ash_test_base.h"
+#include "base/callback.h"
+#include "base/strings/utf_string_conversions.h"
+#include "ui/base/events/event.h"
+#include "ui/gfx/point.h"
+#include "ui/message_center/message_center.h"
+#include "ui/views/view.h"
+
+namespace ash {
+namespace internal {
+
+// Test with unicode strings.
+const char kTestScreenCaptureAppName[] =
+ "\xE0\xB2\xA0\x5F\xE0\xB2\xA0 (Screen Capture Test)";
+const char kTestScreenShareHelperName[] =
+ "\xE5\xAE\x8B\xE8\x85\xBE (Screen Share Test)";
+
+SystemTray* GetSystemTray() {
+ return Shell::GetInstance()->GetPrimarySystemTray();
+}
+
+SystemTrayNotifier* GetSystemTrayNotifier() {
+ return Shell::GetInstance()->system_tray_notifier();
+}
+
+void ClickViewCenter(views::View* view) {
+ gfx::Point click_location_in_local =
+ gfx::Point(view->width() / 2, view->height() / 2);
+ view->OnMousePressed(ui::MouseEvent(ui::ET_MOUSE_PRESSED,
+ click_location_in_local,
+ click_location_in_local,
+ ui::EF_NONE));
+}
+
+class ScreenTrayItemTest : public ash::test::AshTestBase {
+ public:
+ ScreenTrayItemTest()
+ : tray_item_(NULL), stop_callback_hit_count_(0) {}
+ virtual ~ScreenTrayItemTest() {}
+
+ ScreenTrayItem* tray_item() { return tray_item_; }
+ void set_tray_item(ScreenTrayItem* tray_item) { tray_item_ = tray_item; }
+
+ int stop_callback_hit_count() const { return stop_callback_hit_count_; }
+
+ virtual void SetUp() OVERRIDE {
+ test::AshTestBase::SetUp();
+ TrayItemView::DisableAnimationsForTest();
+ }
+
+ void StartSession() {
+ tray_item_->Start(
+ base::Bind(&ScreenTrayItemTest::StopCallback, base::Unretained(this)));
+ }
+
+ void StopSession() {
+ tray_item_->Stop();
+ }
+
+ void StopCallback() {
+ stop_callback_hit_count_++;
+ }
+
+ private:
+ ScreenTrayItem* tray_item_;
+ int stop_callback_hit_count_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScreenTrayItemTest);
+};
+
+class ScreenCaptureTest : public ScreenTrayItemTest {
+ public:
+ ScreenCaptureTest() {}
+ virtual ~ScreenCaptureTest() {}
+
+ virtual void SetUp() OVERRIDE {
+ ScreenTrayItemTest::SetUp();
+ // This tray item is owned by its parent system tray view and will
+ // be deleted automatically when its parent is destroyed in AshTestBase.
+ ScreenTrayItem* tray_item = new ScreenCaptureTrayItem(GetSystemTray());
+ GetSystemTray()->AddTrayItem(tray_item);
+ set_tray_item(tray_item);
+ }
+
+ DISALLOW_COPY_AND_ASSIGN(ScreenCaptureTest);
+};
+
+class ScreenShareTest : public ScreenTrayItemTest {
+ public:
+ ScreenShareTest() {}
+ virtual ~ScreenShareTest() {}
+
+ virtual void SetUp() OVERRIDE {
+ ScreenTrayItemTest::SetUp();
+ // This tray item is owned by its parent system tray view and will
+ // be deleted automatically when its parent is destroyed in AshTestBase.
+ ScreenTrayItem* tray_item = new ScreenShareTrayItem(GetSystemTray());
+ GetSystemTray()->AddTrayItem(tray_item);
+ set_tray_item(tray_item);
+ }
+
+ DISALLOW_COPY_AND_ASSIGN(ScreenShareTest);
+};
+
+void TestStartAndStop(ScreenTrayItemTest* test) {
+ ScreenTrayItem* tray_item = test->tray_item();
+
+ EXPECT_FALSE(tray_item->is_started());
+ EXPECT_EQ(0, test->stop_callback_hit_count());
+
+ test->StartSession();
+ EXPECT_TRUE(tray_item->is_started());
+
+ test->StopSession();
+ EXPECT_FALSE(tray_item->is_started());
+ EXPECT_EQ(1, test->stop_callback_hit_count());
+}
+
+TEST_F(ScreenCaptureTest, StartAndStop) { TestStartAndStop(this); }
+TEST_F(ScreenShareTest, StartAndStop) { TestStartAndStop(this); }
+
+void TestNotificationStartAndStop(ScreenTrayItemTest* test,
+ const base::Closure& start_function,
+ const base::Closure& stop_function) {
+ ScreenTrayItem* tray_item = test->tray_item();
+ EXPECT_FALSE(tray_item->is_started());
+
+ start_function.Run();
+ EXPECT_TRUE(tray_item->is_started());
+
+ // The stop callback shouldn't be called because we stopped
+ // through the notification system.
+ stop_function.Run();
+ EXPECT_FALSE(tray_item->is_started());
+ EXPECT_EQ(0, test->stop_callback_hit_count());
+}
+
+TEST_F(ScreenCaptureTest, NotificationStartAndStop) {
+ base::Closure start_function =
+ base::Bind(&SystemTrayNotifier::NotifyScreenCaptureStart,
+ base::Unretained(GetSystemTrayNotifier()),
+ base::Bind(&ScreenTrayItemTest::StopCallback,
+ base::Unretained(this)),
+ base::UTF8ToUTF16(kTestScreenCaptureAppName));
+
+ base::Closure stop_function =
+ base::Bind(&SystemTrayNotifier::NotifyScreenCaptureStop,
+ base::Unretained(GetSystemTrayNotifier()));
+
+ TestNotificationStartAndStop(this, start_function, stop_function);
+}
+
+TEST_F(ScreenShareTest, NotificationStartAndStop) {
+ base::Closure start_func =
+ base::Bind(&SystemTrayNotifier::NotifyScreenShareStart,
+ base::Unretained(GetSystemTrayNotifier()),
+ base::Bind(&ScreenTrayItemTest::StopCallback,
+ base::Unretained(this)),
+ base::UTF8ToUTF16(kTestScreenShareHelperName));
+
+ base::Closure stop_func =
+ base::Bind(&SystemTrayNotifier::NotifyScreenShareStop,
+ base::Unretained(GetSystemTrayNotifier()));
+
+ TestNotificationStartAndStop(this, start_func, stop_func);
+}
+
+void TestNotificationView(ScreenTrayItemTest* test) {
+ ScreenTrayItem* tray_item = test->tray_item();
+
+ test->StartSession();
+ message_center::MessageCenter* message_center =
+ message_center::MessageCenter::Get();
+ EXPECT_TRUE(message_center->HasNotification(tray_item->GetNotificationId()));
+ test->StopSession();
+}
+
+TEST_F(ScreenCaptureTest, NotificationView) { TestNotificationView(this); }
+TEST_F(ScreenShareTest, NotificationView) { TestNotificationView(this); }
+
+void TestSystemTrayInteraction(ScreenTrayItemTest* test) {
+ ScreenTrayItem* tray_item = test->tray_item();
+ EXPECT_FALSE(tray_item->tray_view()->visible());
+
+ const std::vector<SystemTrayItem*>& tray_items =
+ GetSystemTray()->GetTrayItems();
+ EXPECT_NE(std::find(tray_items.begin(), tray_items.end(), tray_item),
+ tray_items.end());
+
+ test->StartSession();
+ EXPECT_TRUE(tray_item->tray_view()->visible());
+
+ // The default view should be created in a new bubble.
+ GetSystemTray()->ShowDefaultView(BUBBLE_CREATE_NEW);
+ EXPECT_TRUE(tray_item->default_view());
+ GetSystemTray()->CloseSystemBubble();
+ EXPECT_FALSE(tray_item->default_view());
+
+ test->StopSession();
+ EXPECT_FALSE(tray_item->tray_view()->visible());
+
+ // The default view should not be visible because session is stopped.
+ GetSystemTray()->ShowDefaultView(BUBBLE_CREATE_NEW);
+ EXPECT_FALSE(tray_item->default_view()->visible());
+}
+
+TEST_F(ScreenCaptureTest, SystemTrayInteraction) {
+ TestSystemTrayInteraction(this);
+}
+
+TEST_F(ScreenShareTest, SystemTrayInteraction) {
+ TestSystemTrayInteraction(this);
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/system/chromeos/settings/tray_settings.cc b/chromium/ash/system/chromeos/settings/tray_settings.cc
new file mode 100644
index 00000000000..8153aeae81e
--- /dev/null
+++ b/chromium/ash/system/chromeos/settings/tray_settings.cc
@@ -0,0 +1,169 @@
+// Copyright (c) 2012 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.
+
+#include "ash/system/chromeos/settings/tray_settings.h"
+
+#include "ash/shell.h"
+#include "ash/system/chromeos/power/power_status.h"
+#include "ash/system/chromeos/power/power_status_view.h"
+#include "ash/system/tray/actionable_view.h"
+#include "ash/system/tray/fixed_sized_image_view.h"
+#include "ash/system/tray/system_tray_delegate.h"
+#include "ash/system/tray/tray_constants.h"
+#include "base/logging.h"
+#include "base/strings/utf_string_conversions.h"
+#include "grit/ash_resources.h"
+#include "grit/ash_strings.h"
+#include "third_party/skia/include/core/SkColor.h"
+#include "ui/base/accessibility/accessible_view_state.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "ui/gfx/image/image.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/fill_layout.h"
+#include "ui/views/view.h"
+
+namespace ash {
+namespace internal {
+
+namespace tray {
+
+class SettingsDefaultView : public ActionableView,
+ public PowerStatus::Observer {
+ public:
+ explicit SettingsDefaultView(user::LoginStatus status)
+ : login_status_(status),
+ label_(NULL),
+ power_status_view_(NULL) {
+ PowerStatus::Get()->AddObserver(this);
+ SetLayoutManager(new views::BoxLayout(views::BoxLayout::kHorizontal,
+ ash::kTrayPopupPaddingHorizontal, 0,
+ ash::kTrayPopupPaddingBetweenItems));
+
+ bool power_view_right_align = false;
+ if (login_status_ != user::LOGGED_IN_NONE &&
+ login_status_ != user::LOGGED_IN_LOCKED) {
+ ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
+ views::ImageView* icon =
+ new ash::internal::FixedSizedImageView(0, ash::kTrayPopupItemHeight);
+ icon->SetImage(
+ rb.GetImageNamed(IDR_AURA_UBER_TRAY_SETTINGS).ToImageSkia());
+ AddChildView(icon);
+
+ base::string16 text = rb.GetLocalizedString(IDS_ASH_STATUS_TRAY_SETTINGS);
+ label_ = new views::Label(text);
+ AddChildView(label_);
+ SetAccessibleName(text);
+
+ power_view_right_align = true;
+ }
+
+ if (PowerStatus::Get()->IsBatteryPresent()) {
+ power_status_view_ = new ash::internal::PowerStatusView(
+ ash::internal::PowerStatusView::VIEW_DEFAULT, power_view_right_align);
+ AddChildView(power_status_view_);
+ OnPowerStatusChanged();
+ }
+ }
+
+ virtual ~SettingsDefaultView() {
+ PowerStatus::Get()->RemoveObserver(this);
+ }
+
+ // Overridden from ash::internal::ActionableView.
+ virtual bool PerformAction(const ui::Event& event) OVERRIDE {
+ if (login_status_ == user::LOGGED_IN_NONE ||
+ login_status_ == user::LOGGED_IN_LOCKED)
+ return false;
+
+ ash::Shell::GetInstance()->system_tray_delegate()->ShowSettings();
+ return true;
+ }
+
+ // Overridden from views::View.
+ virtual void Layout() OVERRIDE {
+ views::View::Layout();
+
+ if (label_ && power_status_view_) {
+ // Let the box-layout do the layout first. Then move power_status_view_
+ // to right align if it is created.
+ gfx::Size size = power_status_view_->GetPreferredSize();
+ gfx::Rect bounds(size);
+ bounds.set_x(width() - size.width() - ash::kTrayPopupPaddingBetweenItems);
+ bounds.set_y((height() - size.height()) / 2);
+ power_status_view_->SetBoundsRect(bounds);
+ }
+ }
+
+ // Overridden from views::View.
+ virtual void ChildPreferredSizeChanged(views::View* child) OVERRIDE {
+ views::View::ChildPreferredSizeChanged(child);
+ Layout();
+ }
+
+ // Overridden from PowerStatus::Observer.
+ virtual void OnPowerStatusChanged() OVERRIDE {
+ if (!PowerStatus::Get()->IsBatteryPresent())
+ return;
+
+ base::string16 accessible_name = label_ ?
+ label_->text() + ASCIIToUTF16(", ") +
+ PowerStatus::Get()->GetAccessibleNameString() :
+ PowerStatus::Get()->GetAccessibleNameString();
+ SetAccessibleName(accessible_name);
+ }
+
+ private:
+ user::LoginStatus login_status_;
+ views::Label* label_;
+ ash::internal::PowerStatusView* power_status_view_;
+
+ DISALLOW_COPY_AND_ASSIGN(SettingsDefaultView);
+ };
+
+} // namespace tray
+
+TraySettings::TraySettings(SystemTray* system_tray)
+ : SystemTrayItem(system_tray),
+ default_view_(NULL) {
+}
+
+TraySettings::~TraySettings() {
+}
+
+views::View* TraySettings::CreateTrayView(user::LoginStatus status) {
+ return NULL;
+}
+
+views::View* TraySettings::CreateDefaultView(user::LoginStatus status) {
+ if ((status == user::LOGGED_IN_NONE || status == user::LOGGED_IN_LOCKED) &&
+ !PowerStatus::Get()->IsBatteryPresent())
+ return NULL;
+
+ CHECK(default_view_ == NULL);
+ default_view_ = new tray::SettingsDefaultView(status);
+ return default_view_;
+}
+
+views::View* TraySettings::CreateDetailedView(user::LoginStatus status) {
+ NOTIMPLEMENTED();
+ return NULL;
+}
+
+void TraySettings::DestroyTrayView() {
+}
+
+void TraySettings::DestroyDefaultView() {
+ default_view_ = NULL;
+}
+
+void TraySettings::DestroyDetailedView() {
+}
+
+void TraySettings::UpdateAfterLoginStatusChange(user::LoginStatus status) {
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/system/chromeos/settings/tray_settings.h b/chromium/ash/system/chromeos/settings/tray_settings.h
new file mode 100644
index 00000000000..e48aad3b55d
--- /dev/null
+++ b/chromium/ash/system/chromeos/settings/tray_settings.h
@@ -0,0 +1,40 @@
+// Copyright (c) 2012 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 ASH_SYSTEM_CHROMEOS_SETTINGS_TRAY_SETTINGS_H_
+#define ASH_SYSTEM_CHROMEOS_SETTINGS_TRAY_SETTINGS_H_
+
+#include "ash/system/tray/system_tray_item.h"
+
+namespace ash {
+namespace internal {
+
+namespace tray {
+class SettingsDefaultView;
+}
+
+class TraySettings : public SystemTrayItem {
+ public:
+ explicit TraySettings(SystemTray* system_tray);
+ virtual ~TraySettings();
+
+ private:
+ // Overridden from SystemTrayItem
+ virtual views::View* CreateTrayView(user::LoginStatus status) OVERRIDE;
+ virtual views::View* CreateDefaultView(user::LoginStatus status) OVERRIDE;
+ virtual views::View* CreateDetailedView(user::LoginStatus status) OVERRIDE;
+ virtual void DestroyTrayView() OVERRIDE;
+ virtual void DestroyDefaultView() OVERRIDE;
+ virtual void DestroyDetailedView() OVERRIDE;
+ virtual void UpdateAfterLoginStatusChange(user::LoginStatus status) OVERRIDE;
+
+ tray::SettingsDefaultView* default_view_;
+
+ DISALLOW_COPY_AND_ASSIGN(TraySettings);
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_SYSTEM_CHROMEOS_SETTINGS_TRAY_SETTINGS_H_
diff --git a/chromium/ash/system/chromeos/tray_display.cc b/chromium/ash/system/chromeos/tray_display.cc
new file mode 100644
index 00000000000..1f81f77670c
--- /dev/null
+++ b/chromium/ash/system/chromeos/tray_display.cc
@@ -0,0 +1,453 @@
+// Copyright (c) 2012 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.
+
+#include "ash/system/chromeos/tray_display.h"
+
+#include "ash/display/display_controller.h"
+#include "ash/display/display_manager.h"
+#include "ash/shell.h"
+#include "ash/system/tray/actionable_view.h"
+#include "ash/system/tray/fixed_sized_image_view.h"
+#include "ash/system/tray/system_tray.h"
+#include "ash/system/tray/system_tray_delegate.h"
+#include "ash/system/tray/tray_constants.h"
+#include "ash/system/tray/tray_notification_view.h"
+#include "base/bind.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "grit/ash_resources.h"
+#include "grit/ash_strings.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "ui/message_center/message_center.h"
+#include "ui/message_center/notification.h"
+#include "ui/message_center/notification_delegate.h"
+#include "ui/message_center/notification_list.h"
+#include "ui/views/controls/image_view.h"
+#include "ui/views/controls/label.h"
+#include "ui/views/layout/box_layout.h"
+
+using message_center::Notification;
+
+namespace ash {
+namespace internal {
+namespace {
+
+static const char kDisplayNotificationId[] = "chrome://settings/display";
+
+DisplayManager* GetDisplayManager() {
+ return Shell::GetInstance()->display_manager();
+}
+
+base::string16 GetDisplayName(int64 display_id) {
+ return UTF8ToUTF16(GetDisplayManager()->GetDisplayNameForId(display_id));
+}
+
+base::string16 GetDisplaySize(int64 display_id) {
+ DisplayManager* display_manager = GetDisplayManager();
+
+ const gfx::Display* display = &display_manager->GetDisplayForId(display_id);
+ if (display_manager->IsMirrored() &&
+ display_manager->mirrored_display().id() == display_id) {
+ display = &display_manager->mirrored_display();
+ }
+
+ DCHECK(display->is_valid());
+ return UTF8ToUTF16(display->size().ToString());
+}
+
+// Returns 1-line information for the specified display, like
+// "InternalDisplay: 1280x750"
+base::string16 GetDisplayInfoLine(int64 display_id) {
+ const DisplayInfo& display_info =
+ GetDisplayManager()->GetDisplayInfo(display_id);
+
+ base::string16 size_text = GetDisplaySize(display_id);
+ base::string16 display_data;
+ if (display_info.has_overscan()) {
+ display_data = l10n_util::GetStringFUTF16(
+ IDS_ASH_STATUS_TRAY_DISPLAY_ANNOTATION,
+ size_text,
+ l10n_util::GetStringUTF16(
+ IDS_ASH_STATUS_TRAY_DISPLAY_ANNOTATION_OVERSCAN));
+ } else {
+ display_data = size_text;
+ }
+
+ return l10n_util::GetStringFUTF16(
+ IDS_ASH_STATUS_TRAY_DISPLAY_SINGLE_DISPLAY,
+ GetDisplayName(display_id),
+ display_data);
+}
+
+base::string16 GetAllDisplayInfo() {
+ DisplayManager* display_manager = GetDisplayManager();
+ std::vector<base::string16> lines;
+ int64 internal_id = gfx::Display::kInvalidDisplayID;
+ // Make sure to show the internal display first.
+ if (display_manager->HasInternalDisplay() &&
+ display_manager->IsInternalDisplayId(
+ display_manager->first_display_id())) {
+ internal_id = display_manager->first_display_id();
+ lines.push_back(GetDisplayInfoLine(internal_id));
+ }
+
+ for (size_t i = 0; i < display_manager->GetNumDisplays(); ++i) {
+ int64 id = display_manager->GetDisplayAt(i).id();
+ if (id == internal_id)
+ continue;
+ lines.push_back(GetDisplayInfoLine(id));
+ }
+
+ return JoinString(lines, '\n');
+}
+
+// Returns the name of the currently connected external display.
+base::string16 GetExternalDisplayName() {
+ DisplayManager* display_manager = GetDisplayManager();
+ int64 external_id = display_manager->mirrored_display().id();
+
+ if (external_id == gfx::Display::kInvalidDisplayID) {
+ int64 internal_display_id = gfx::Display::InternalDisplayId();
+ for (size_t i = 0; i < display_manager->GetNumDisplays(); ++i) {
+ int64 id = display_manager->GetDisplayAt(i).id();
+ if (id != internal_display_id) {
+ external_id = id;
+ break;
+ }
+ }
+ }
+
+ if (external_id == gfx::Display::kInvalidDisplayID)
+ return l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_UNKNOWN_DISPLAY_NAME);
+
+ // The external display name may have an annotation of "(width x height)" in
+ // case that the display is rotated or its resolution is changed.
+ base::string16 name = GetDisplayName(external_id);
+ const DisplayInfo& display_info =
+ display_manager->GetDisplayInfo(external_id);
+ if (display_info.rotation() != gfx::Display::ROTATE_0 ||
+ display_info.ui_scale() != 1.0f ||
+ !display_info.overscan_insets_in_dip().empty()) {
+ name = l10n_util::GetStringFUTF16(
+ IDS_ASH_STATUS_TRAY_DISPLAY_ANNOTATED_NAME,
+ name, GetDisplaySize(external_id));
+ } else if (display_info.overscan_insets_in_dip().empty() &&
+ display_info.has_overscan()) {
+ name = l10n_util::GetStringFUTF16(
+ IDS_ASH_STATUS_TRAY_DISPLAY_ANNOTATED_NAME,
+ name, l10n_util::GetStringUTF16(
+ IDS_ASH_STATUS_TRAY_DISPLAY_ANNOTATION_OVERSCAN));
+ }
+
+ return name;
+}
+
+base::string16 GetTrayDisplayMessage() {
+ DisplayManager* display_manager = GetDisplayManager();
+ if (display_manager->GetNumDisplays() > 1) {
+ if (GetDisplayManager()->HasInternalDisplay()) {
+ return l10n_util::GetStringFUTF16(
+ IDS_ASH_STATUS_TRAY_DISPLAY_EXTENDED, GetExternalDisplayName());
+ }
+ return l10n_util::GetStringUTF16(
+ IDS_ASH_STATUS_TRAY_DISPLAY_EXTENDED_NO_INTERNAL);
+ }
+
+ if (display_manager->IsMirrored()) {
+ if (GetDisplayManager()->HasInternalDisplay()) {
+ return l10n_util::GetStringFUTF16(
+ IDS_ASH_STATUS_TRAY_DISPLAY_MIRRORING, GetExternalDisplayName());
+ }
+ return l10n_util::GetStringUTF16(
+ IDS_ASH_STATUS_TRAY_DISPLAY_MIRRORING_NO_INTERNAL);
+ }
+
+ int64 first_id = display_manager->first_display_id();
+ if (display_manager->HasInternalDisplay() &&
+ !display_manager->IsInternalDisplayId(first_id)) {
+ return l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_DISPLAY_DOCKED);
+ }
+
+ return base::string16();
+}
+
+void OpenSettings(user::LoginStatus login_status) {
+ if (login_status == ash::user::LOGGED_IN_USER ||
+ login_status == ash::user::LOGGED_IN_OWNER ||
+ login_status == ash::user::LOGGED_IN_GUEST) {
+ ash::Shell::GetInstance()->system_tray_delegate()->ShowDisplaySettings();
+ }
+}
+
+void UpdateDisplayNotification(const base::string16& message) {
+ // Always remove the notification to make sure the notification appears
+ // as a popup in any situation.
+ message_center::MessageCenter::Get()->RemoveNotification(
+ kDisplayNotificationId, false /* by_user */);
+
+ if (message.empty())
+ return;
+
+ ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance();
+ scoped_ptr<Notification> notification(new Notification(
+ message_center::NOTIFICATION_TYPE_SIMPLE,
+ kDisplayNotificationId,
+ message,
+ base::string16(), // body is intentionally empty, see crbug.com/265915
+ bundle.GetImageNamed(IDR_AURA_UBER_TRAY_DISPLAY),
+ base::string16(), // display_source
+ "", // extension_id
+ message_center::RichNotificationData(),
+ new message_center::HandleNotificationClickedDelegate(
+ base::Bind(&OpenSettings,
+ Shell::GetInstance()->system_tray_delegate()->
+ GetUserLoginStatus()))));
+ message_center::MessageCenter::Get()->AddNotification(notification.Pass());
+}
+
+} // namespace
+
+class DisplayView : public ash::internal::ActionableView {
+ public:
+ explicit DisplayView(user::LoginStatus login_status)
+ : login_status_(login_status) {
+ SetLayoutManager(new views::BoxLayout(
+ views::BoxLayout::kHorizontal,
+ ash::kTrayPopupPaddingHorizontal, 0,
+ ash::kTrayPopupPaddingBetweenItems));
+
+ ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance();
+ image_ =
+ new ash::internal::FixedSizedImageView(0, ash::kTrayPopupItemHeight);
+ image_->SetImage(
+ bundle.GetImageNamed(IDR_AURA_UBER_TRAY_DISPLAY).ToImageSkia());
+ AddChildView(image_);
+
+ label_ = new views::Label();
+ label_->SetMultiLine(true);
+ label_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
+ AddChildView(label_);
+ Update();
+ }
+
+ virtual ~DisplayView() {}
+
+ void Update() {
+ base::string16 message = GetTrayDisplayMessage();
+ if (message.empty() && ShouldShowFirstDisplayInfo())
+ message = GetDisplayInfoLine(GetDisplayManager()->first_display_id());
+ SetVisible(!message.empty());
+ label_->SetText(message);
+ }
+
+ views::Label* label() { return label_; }
+
+ // Overridden from views::View.
+ virtual bool GetTooltipText(const gfx::Point& p,
+ base::string16* tooltip) const OVERRIDE {
+ base::string16 tray_message = GetTrayDisplayMessage();
+ base::string16 display_message = GetAllDisplayInfo();
+ if (tray_message.empty() && display_message.empty())
+ return false;
+
+ *tooltip = tray_message + ASCIIToUTF16("\n") + display_message;
+ return true;
+ }
+
+ private:
+ bool ShouldShowFirstDisplayInfo() const {
+ const DisplayInfo& display_info = GetDisplayManager()->GetDisplayInfo(
+ GetDisplayManager()->first_display_id());
+ return display_info.rotation() != gfx::Display::ROTATE_0 ||
+ display_info.ui_scale() != 1.0f ||
+ !display_info.overscan_insets_in_dip().empty() ||
+ display_info.has_overscan();
+ }
+
+ // Overridden from ActionableView.
+ virtual bool PerformAction(const ui::Event& event) OVERRIDE {
+ OpenSettings(login_status_);
+ return true;
+ }
+
+ virtual void OnBoundsChanged(const gfx::Rect& previous_bounds) OVERRIDE {
+ int label_max_width = bounds().width() - kTrayPopupPaddingHorizontal * 2 -
+ kTrayPopupPaddingBetweenItems - image_->GetPreferredSize().width();
+ label_->SizeToFit(label_max_width);
+ PreferredSizeChanged();
+ }
+
+ user::LoginStatus login_status_;
+ views::ImageView* image_;
+ views::Label* label_;
+
+ DISALLOW_COPY_AND_ASSIGN(DisplayView);
+};
+
+class DisplayNotificationView : public TrayNotificationView {
+ public:
+ DisplayNotificationView(user::LoginStatus login_status,
+ TrayDisplay* tray_item,
+ const base::string16& message)
+ : TrayNotificationView(tray_item, IDR_AURA_UBER_TRAY_DISPLAY),
+ login_status_(login_status) {
+ StartAutoCloseTimer(kTrayPopupAutoCloseDelayForTextInSeconds);
+ Update(message);
+ }
+
+ virtual ~DisplayNotificationView() {}
+
+ void Update(const base::string16& message) {
+ if (message.empty()) {
+ owner()->HideNotificationView();
+ } else {
+ views::Label* label = new views::Label(message);
+ label->SetMultiLine(true);
+ label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
+ UpdateView(label);
+ RestartAutoCloseTimer();
+ }
+ }
+
+ // Overridden from TrayNotificationView:
+ virtual void OnClickAction() OVERRIDE {
+ OpenSettings(login_status_);
+ }
+
+ private:
+ user::LoginStatus login_status_;
+
+ DISALLOW_COPY_AND_ASSIGN(DisplayNotificationView);
+};
+
+TrayDisplay::TrayDisplay(SystemTray* system_tray)
+ : SystemTrayItem(system_tray),
+ default_(NULL) {
+ Shell::GetInstance()->display_controller()->AddObserver(this);
+ UpdateDisplayInfo(NULL);
+}
+
+TrayDisplay::~TrayDisplay() {
+ Shell::GetInstance()->display_controller()->RemoveObserver(this);
+}
+
+void TrayDisplay::UpdateDisplayInfo(TrayDisplay::DisplayInfoMap* old_info) {
+ if (old_info)
+ old_info->swap(display_info_);
+ display_info_.clear();
+
+ DisplayManager* display_manager = GetDisplayManager();
+ for (size_t i = 0; i < display_manager->GetNumDisplays(); ++i) {
+ int64 id = display_manager->GetDisplayAt(i).id();
+ display_info_[id] = display_manager->GetDisplayInfo(id);
+ }
+}
+
+bool TrayDisplay::GetDisplayMessageForNotification(
+ base::string16* message,
+ const TrayDisplay::DisplayInfoMap& old_info) {
+ // Display is added or removed. Use the same message as the one in
+ // the system tray.
+ if (display_info_.size() != old_info.size()) {
+ *message = GetTrayDisplayMessage();
+ return true;
+ }
+
+ for (DisplayInfoMap::const_iterator iter = display_info_.begin();
+ iter != display_info_.end(); ++iter) {
+ DisplayInfoMap::const_iterator old_iter = old_info.find(iter->first);
+ // The display's number is same but different displays. This happens
+ // for the transition between docked mode and mirrored display. Falls back
+ // to GetTrayDisplayMessage().
+ if (old_iter == old_info.end()) {
+ *message = GetTrayDisplayMessage();
+ return true;
+ }
+
+ if (iter->second.ui_scale() != old_iter->second.ui_scale()) {
+ *message = l10n_util::GetStringFUTF16(
+ IDS_ASH_STATUS_TRAY_DISPLAY_RESOLUTION_CHANGED,
+ GetDisplayName(iter->first),
+ GetDisplaySize(iter->first));
+ return true;
+ }
+ if (iter->second.rotation() != old_iter->second.rotation()) {
+ int rotation_text_id = 0;
+ switch (iter->second.rotation()) {
+ case gfx::Display::ROTATE_0:
+ rotation_text_id = IDS_ASH_STATUS_TRAY_DISPLAY_STANDARD_ORIENTATION;
+ break;
+ case gfx::Display::ROTATE_90:
+ rotation_text_id = IDS_ASH_STATUS_TRAY_DISPLAY_ORIENTATION_90;
+ break;
+ case gfx::Display::ROTATE_180:
+ rotation_text_id = IDS_ASH_STATUS_TRAY_DISPLAY_ORIENTATION_180;
+ break;
+ case gfx::Display::ROTATE_270:
+ rotation_text_id = IDS_ASH_STATUS_TRAY_DISPLAY_ORIENTATION_270;
+ break;
+ }
+ *message = l10n_util::GetStringFUTF16(
+ IDS_ASH_STATUS_TRAY_DISPLAY_ROTATED,
+ GetDisplayName(iter->first),
+ l10n_util::GetStringUTF16(rotation_text_id));
+ return true;
+ }
+ }
+
+ // Found nothing special
+ return false;
+}
+
+views::View* TrayDisplay::CreateDefaultView(user::LoginStatus status) {
+ DCHECK(default_ == NULL);
+ default_ = new DisplayView(status);
+ return default_;
+}
+
+void TrayDisplay::DestroyDefaultView() {
+ default_ = NULL;
+}
+
+void TrayDisplay::OnDisplayConfigurationChanged() {
+ DisplayInfoMap old_info;
+ UpdateDisplayInfo(&old_info);
+
+ if (!Shell::GetInstance()->system_tray_delegate()->
+ ShouldShowDisplayNotification()) {
+ return;
+ }
+
+ base::string16 message;
+ if (GetDisplayMessageForNotification(&message, old_info))
+ UpdateDisplayNotification(message);
+}
+
+base::string16 TrayDisplay::GetDefaultViewMessage() {
+ if (!default_ || !default_->visible())
+ return base::string16();
+
+ return static_cast<DisplayView*>(default_)->label()->text();
+}
+
+base::string16 TrayDisplay::GetNotificationMessage() {
+ message_center::NotificationList::Notifications notifications =
+ message_center::MessageCenter::Get()->GetNotifications();
+ for (message_center::NotificationList::Notifications::const_iterator iter =
+ notifications.begin(); iter != notifications.end(); ++iter) {
+ if ((*iter)->id() == kDisplayNotificationId)
+ return (*iter)->title();
+ }
+
+ return base::string16();
+}
+
+void TrayDisplay::CloseNotificationForTest() {
+ message_center::MessageCenter::Get()->RemoveNotification(
+ kDisplayNotificationId, false);
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/system/chromeos/tray_display.h b/chromium/ash/system/chromeos/tray_display.h
new file mode 100644
index 00000000000..328bce545bf
--- /dev/null
+++ b/chromium/ash/system/chromeos/tray_display.h
@@ -0,0 +1,71 @@
+// Copyright (c) 2012 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 ASH_SYSTEM_CHROMEOS_TRAY_DISPLAY_H_
+#define ASH_SYSTEM_CHROMEOS_TRAY_DISPLAY_H_
+
+#include <map>
+
+#include "ash/ash_export.h"
+#include "ash/display/display_controller.h"
+#include "ash/display/display_info.h"
+#include "ash/system/tray/system_tray_item.h"
+#include "base/strings/string16.h"
+#include "ui/views/view.h"
+
+namespace ash {
+namespace test {
+class AshTestBase;
+}
+
+namespace internal {
+
+class DisplayNotificationView;
+
+class ASH_EXPORT TrayDisplay : public SystemTrayItem,
+ public DisplayController::Observer {
+ public:
+ explicit TrayDisplay(SystemTray* system_tray);
+ virtual ~TrayDisplay();
+
+ // Overridden from DisplayControllerObserver:
+ virtual void OnDisplayConfigurationChanged() OVERRIDE;
+
+ private:
+ friend class TrayDisplayTest;
+
+ typedef std::map<int64, DisplayInfo> DisplayInfoMap;
+
+ // Scans the current display info and updates |display_info_|. Sets the
+ // previous data to |old_info| if it's not NULL.
+ void UpdateDisplayInfo(DisplayInfoMap* old_info);
+
+ // Compares the current display settings with |old_info| and determine what
+ // message should be shown for notification. Returns true if there's a
+ // meaningful change. Note that it's possible to return true and set |message|
+ // to empty, which means the notification should be removed.
+ bool GetDisplayMessageForNotification(
+ base::string16* message,
+ const DisplayInfoMap& old_info);
+
+ // Overridden from SystemTrayItem.
+ virtual views::View* CreateDefaultView(user::LoginStatus status) OVERRIDE;
+ virtual void DestroyDefaultView() OVERRIDE;
+
+ // Test accessors.
+ base::string16 GetDefaultViewMessage();
+ base::string16 GetNotificationMessage();
+ void CloseNotificationForTest();
+ views::View* default_view() { return default_; }
+
+ views::View* default_;
+ DisplayInfoMap display_info_;
+
+ DISALLOW_COPY_AND_ASSIGN(TrayDisplay);
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_SYSTEM_CHROMEOS_TRAY_DISPLAY_H_
diff --git a/chromium/ash/system/chromeos/tray_display_unittest.cc b/chromium/ash/system/chromeos/tray_display_unittest.cc
new file mode 100644
index 00000000000..c6fa9e949cb
--- /dev/null
+++ b/chromium/ash/system/chromeos/tray_display_unittest.cc
@@ -0,0 +1,460 @@
+// Copyright 2013 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.
+
+#include "ash/system/chromeos/tray_display.h"
+
+#include "ash/display/display_manager.h"
+#include "ash/root_window_controller.h"
+#include "ash/screen_ash.h"
+#include "ash/shell.h"
+#include "ash/system/tray/system_tray.h"
+#include "ash/system/tray/test_system_tray_delegate.h"
+#include "ash/test/ash_test_base.h"
+#include "base/strings/string16.h"
+#include "base/strings/utf_string_conversions.h"
+#include "grit/ash_strings.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/gfx/display.h"
+#include "ui/views/controls/label.h"
+
+namespace ash {
+namespace internal {
+
+base::string16 GetTooltipText(const base::string16& headline,
+ const base::string16& name1,
+ const std::string& data1,
+ const base::string16& name2,
+ const std::string& data2) {
+ std::vector<base::string16> lines;
+ lines.push_back(headline);
+ lines.push_back(l10n_util::GetStringFUTF16(
+ IDS_ASH_STATUS_TRAY_DISPLAY_SINGLE_DISPLAY,
+ name1, UTF8ToUTF16(data1)));
+ if (!name2.empty()) {
+ lines.push_back(l10n_util::GetStringFUTF16(
+ IDS_ASH_STATUS_TRAY_DISPLAY_SINGLE_DISPLAY,
+ name2, UTF8ToUTF16(data2)));
+ }
+ return JoinString(lines, '\n');
+}
+
+base::string16 GetMirroredTooltipText(const base::string16& headline,
+ const base::string16& name,
+ const std::string& data) {
+ return GetTooltipText(headline, name, data, base::string16(), "");
+}
+
+base::string16 GetFirstDisplayName() {
+ DisplayManager* display_manager = Shell::GetInstance()->display_manager();
+ return UTF8ToUTF16(display_manager->GetDisplayNameForId(
+ display_manager->first_display_id()));
+}
+
+base::string16 GetSecondDisplayName() {
+ return UTF8ToUTF16(
+ Shell::GetInstance()->display_manager()->GetDisplayNameForId(
+ ScreenAsh::GetSecondaryDisplay().id()));
+}
+
+base::string16 GetMirroredDisplayName() {
+ DisplayManager* display_manager = Shell::GetInstance()->display_manager();
+ return UTF8ToUTF16(display_manager->GetDisplayNameForId(
+ display_manager->mirrored_display().id()));
+}
+
+class TrayDisplayTest : public ash::test::AshTestBase {
+ public:
+ TrayDisplayTest();
+ virtual ~TrayDisplayTest();
+
+ virtual void SetUp() OVERRIDE;
+
+ protected:
+ SystemTray* tray() { return tray_; }
+ TrayDisplay* tray_display() { return tray_display_; }
+
+ void CloseNotification();
+ bool IsDisplayVisibleInTray();
+ base::string16 GetTrayDisplayText();
+ base::string16 GetTrayDisplayTooltipText();
+ base::string16 GetDisplayNotificationText();
+
+ private:
+ // Weak reference, owned by Shell.
+ SystemTray* tray_;
+
+ // Weak reference, owned by |tray_|.
+ TrayDisplay* tray_display_;
+
+ DISALLOW_COPY_AND_ASSIGN(TrayDisplayTest);
+};
+
+TrayDisplayTest::TrayDisplayTest() : tray_(NULL), tray_display_(NULL) {
+}
+
+TrayDisplayTest::~TrayDisplayTest() {
+}
+
+void TrayDisplayTest::SetUp() {
+ ash::test::AshTestBase::SetUp();
+ tray_ = Shell::GetPrimaryRootWindowController()->GetSystemTray();
+ tray_display_ = new TrayDisplay(tray_);
+ tray_->AddTrayItem(tray_display_);
+}
+
+void TrayDisplayTest::CloseNotification() {
+ tray_display_->CloseNotificationForTest();
+ RunAllPendingInMessageLoop();
+}
+
+bool TrayDisplayTest::IsDisplayVisibleInTray() {
+ return tray_display_->default_view() &&
+ tray_display_->default_view()->visible();
+}
+
+base::string16 TrayDisplayTest::GetTrayDisplayText() {
+ return tray_display_->GetDefaultViewMessage();
+}
+
+base::string16 TrayDisplayTest::GetTrayDisplayTooltipText() {
+ if (!tray_display_->default_view())
+ return base::string16();
+
+ base::string16 tooltip;
+ if (!tray_display_->default_view()->GetTooltipText(gfx::Point(), &tooltip))
+ return base::string16();
+ return tooltip;
+}
+
+base::string16 TrayDisplayTest::GetDisplayNotificationText() {
+ return tray_display_->GetNotificationMessage();
+}
+
+TEST_F(TrayDisplayTest, NoInternalDisplay) {
+ UpdateDisplay("400x400");
+ tray()->ShowDefaultView(BUBBLE_USE_EXISTING);
+ EXPECT_FALSE(IsDisplayVisibleInTray());
+
+ UpdateDisplay("400x400,200x200");
+ tray()->ShowDefaultView(BUBBLE_USE_EXISTING);
+ EXPECT_TRUE(IsDisplayVisibleInTray());
+ base::string16 expected = l10n_util::GetStringUTF16(
+ IDS_ASH_STATUS_TRAY_DISPLAY_EXTENDED_NO_INTERNAL);
+ base::string16 first_name = GetFirstDisplayName();
+ EXPECT_EQ(expected, GetTrayDisplayText());
+ EXPECT_EQ(GetTooltipText(expected, GetFirstDisplayName(), "400x400",
+ GetSecondDisplayName(), "200x200"),
+ GetTrayDisplayTooltipText());
+
+ // mirroring
+ Shell::GetInstance()->display_manager()->SetSoftwareMirroring(true);
+ UpdateDisplay("400x400,200x200");
+ tray()->ShowDefaultView(BUBBLE_USE_EXISTING);
+ EXPECT_TRUE(IsDisplayVisibleInTray());
+ expected = l10n_util::GetStringUTF16(
+ IDS_ASH_STATUS_TRAY_DISPLAY_MIRRORING_NO_INTERNAL);
+ EXPECT_EQ(expected, GetTrayDisplayText());
+ EXPECT_EQ(GetMirroredTooltipText(expected, GetFirstDisplayName(), "400x400"),
+ GetTrayDisplayTooltipText());
+}
+
+TEST_F(TrayDisplayTest, InternalDisplay) {
+ UpdateDisplay("400x400");
+ DisplayManager* display_manager = Shell::GetInstance()->display_manager();
+ gfx::Display::SetInternalDisplayId(display_manager->first_display_id());
+
+ tray()->ShowDefaultView(BUBBLE_USE_EXISTING);
+ EXPECT_FALSE(IsDisplayVisibleInTray());
+
+ // Extended
+ UpdateDisplay("400x400,200x200");
+ string16 expected = l10n_util::GetStringFUTF16(
+ IDS_ASH_STATUS_TRAY_DISPLAY_EXTENDED, GetSecondDisplayName());
+ tray()->ShowDefaultView(BUBBLE_USE_EXISTING);
+ EXPECT_TRUE(IsDisplayVisibleInTray());
+ EXPECT_EQ(expected, GetTrayDisplayText());
+ EXPECT_EQ(GetTooltipText(expected, GetFirstDisplayName(), "400x400",
+ GetSecondDisplayName(), "200x200"),
+ GetTrayDisplayTooltipText());
+
+ // Mirroring
+ display_manager->SetSoftwareMirroring(true);
+ UpdateDisplay("400x400,200x200");
+ tray()->ShowDefaultView(BUBBLE_USE_EXISTING);
+ EXPECT_TRUE(IsDisplayVisibleInTray());
+
+ expected = l10n_util::GetStringFUTF16(
+ IDS_ASH_STATUS_TRAY_DISPLAY_MIRRORING, GetMirroredDisplayName());
+ EXPECT_EQ(expected, GetTrayDisplayText());
+ EXPECT_EQ(GetMirroredTooltipText(expected, GetFirstDisplayName(), "400x400"),
+ GetTrayDisplayTooltipText());
+
+ // TODO(mukai): add test case for docked mode here.
+}
+
+TEST_F(TrayDisplayTest, InternalDisplayResized) {
+ UpdateDisplay("400x400@1.5");
+ DisplayManager* display_manager = Shell::GetInstance()->display_manager();
+ gfx::Display::SetInternalDisplayId(display_manager->first_display_id());
+
+ // Shows the tray_display even though there's a single-display.
+ tray()->ShowDefaultView(BUBBLE_USE_EXISTING);
+ EXPECT_TRUE(IsDisplayVisibleInTray());
+ base::string16 internal_info = l10n_util::GetStringFUTF16(
+ IDS_ASH_STATUS_TRAY_DISPLAY_SINGLE_DISPLAY,
+ GetFirstDisplayName(), UTF8ToUTF16("600x600"));
+ EXPECT_EQ(internal_info, GetTrayDisplayText());
+ EXPECT_EQ(GetTooltipText(base::string16(), GetFirstDisplayName(), "600x600",
+ base::string16(), std::string()),
+ GetTrayDisplayTooltipText());
+
+ // Extended
+ UpdateDisplay("400x400@1.5,200x200");
+ tray()->ShowDefaultView(BUBBLE_USE_EXISTING);
+ EXPECT_TRUE(IsDisplayVisibleInTray());
+ base::string16 expected = l10n_util::GetStringFUTF16(
+ IDS_ASH_STATUS_TRAY_DISPLAY_EXTENDED, GetSecondDisplayName());
+ EXPECT_EQ(expected, GetTrayDisplayText());
+ EXPECT_EQ(GetTooltipText(expected, GetFirstDisplayName(), "600x600",
+ GetSecondDisplayName(), "200x200"),
+ GetTrayDisplayTooltipText());
+
+ // Mirroring
+ display_manager->SetSoftwareMirroring(true);
+ UpdateDisplay("400x400@1.5,200x200");
+ tray()->ShowDefaultView(BUBBLE_USE_EXISTING);
+ EXPECT_TRUE(IsDisplayVisibleInTray());
+ expected = l10n_util::GetStringFUTF16(
+ IDS_ASH_STATUS_TRAY_DISPLAY_MIRRORING, GetMirroredDisplayName());
+ EXPECT_EQ(expected, GetTrayDisplayText());
+ EXPECT_EQ(GetMirroredTooltipText(expected, GetFirstDisplayName(), "600x600"),
+ GetTrayDisplayTooltipText());
+}
+
+TEST_F(TrayDisplayTest, ExternalDisplayResized) {
+ UpdateDisplay("400x400");
+ DisplayManager* display_manager = Shell::GetInstance()->display_manager();
+ gfx::Display::SetInternalDisplayId(display_manager->first_display_id());
+
+ // Shows the tray_display even though there's a single-display.
+ tray()->ShowDefaultView(BUBBLE_USE_EXISTING);
+ EXPECT_FALSE(IsDisplayVisibleInTray());
+
+ // Extended
+ UpdateDisplay("400x400,200x200@1.5");
+ const gfx::Display& secondary_display = ScreenAsh::GetSecondaryDisplay();
+
+ tray()->ShowDefaultView(BUBBLE_USE_EXISTING);
+ EXPECT_TRUE(IsDisplayVisibleInTray());
+ base::string16 expected = l10n_util::GetStringFUTF16(
+ IDS_ASH_STATUS_TRAY_DISPLAY_EXTENDED,
+ l10n_util::GetStringFUTF16(
+ IDS_ASH_STATUS_TRAY_DISPLAY_ANNOTATED_NAME,
+ GetSecondDisplayName(),
+ UTF8ToUTF16(secondary_display.size().ToString())));
+ EXPECT_EQ(expected, GetTrayDisplayText());
+ EXPECT_EQ(GetTooltipText(expected, GetFirstDisplayName(), "400x400",
+ GetSecondDisplayName(), "300x300"),
+ GetTrayDisplayTooltipText());
+
+ // Mirroring
+ display_manager->SetSoftwareMirroring(true);
+ UpdateDisplay("400x400,200x200@1.5");
+ base::string16 mirror_name = l10n_util::GetStringFUTF16(
+ IDS_ASH_STATUS_TRAY_DISPLAY_ANNOTATED_NAME,
+ GetMirroredDisplayName(), UTF8ToUTF16("300x300"));
+ tray()->ShowDefaultView(BUBBLE_USE_EXISTING);
+ EXPECT_TRUE(IsDisplayVisibleInTray());
+ expected = l10n_util::GetStringFUTF16(
+ IDS_ASH_STATUS_TRAY_DISPLAY_MIRRORING, mirror_name);
+ EXPECT_EQ(expected, GetTrayDisplayText());
+ EXPECT_EQ(GetMirroredTooltipText(expected, GetFirstDisplayName(), "400x400"),
+ GetTrayDisplayTooltipText());
+}
+
+TEST_F(TrayDisplayTest, OverscanDisplay) {
+ UpdateDisplay("400x400,300x300/o");
+ DisplayManager* display_manager = Shell::GetInstance()->display_manager();
+ gfx::Display::SetInternalDisplayId(display_manager->first_display_id());
+
+ tray()->ShowDefaultView(BUBBLE_USE_EXISTING);
+ EXPECT_TRUE(IsDisplayVisibleInTray());
+
+ // /o creates the default overscan, and if overscan is set, the annotation
+ // should be the size.
+ base::string16 overscan = l10n_util::GetStringUTF16(
+ IDS_ASH_STATUS_TRAY_DISPLAY_ANNOTATION_OVERSCAN);
+ base::string16 headline = l10n_util::GetStringFUTF16(
+ IDS_ASH_STATUS_TRAY_DISPLAY_EXTENDED,
+ l10n_util::GetStringFUTF16(
+ IDS_ASH_STATUS_TRAY_DISPLAY_ANNOTATED_NAME,
+ GetSecondDisplayName(), UTF8ToUTF16("286x286")));
+ std::string second_data = l10n_util::GetStringFUTF8(
+ IDS_ASH_STATUS_TRAY_DISPLAY_ANNOTATION,
+ UTF8ToUTF16("286x286"), overscan);
+ EXPECT_EQ(GetTooltipText(headline, GetFirstDisplayName(), "400x400",
+ GetSecondDisplayName(), second_data),
+ GetTrayDisplayTooltipText());
+
+ // reset the overscan.
+ display_manager->SetOverscanInsets(
+ ScreenAsh::GetSecondaryDisplay().id(), gfx::Insets());
+ headline = l10n_util::GetStringFUTF16(
+ IDS_ASH_STATUS_TRAY_DISPLAY_EXTENDED,
+ l10n_util::GetStringFUTF16(
+ IDS_ASH_STATUS_TRAY_DISPLAY_ANNOTATED_NAME,
+ GetSecondDisplayName(), overscan));
+ second_data = l10n_util::GetStringFUTF8(
+ IDS_ASH_STATUS_TRAY_DISPLAY_ANNOTATION,
+ UTF8ToUTF16("300x300"), overscan);
+ EXPECT_EQ(GetTooltipText(headline, GetFirstDisplayName(), "400x400",
+ GetSecondDisplayName(), second_data),
+ GetTrayDisplayTooltipText());
+}
+
+TEST_F(TrayDisplayTest, DisplayNotifications) {
+ test::TestSystemTrayDelegate* tray_delegate =
+ static_cast<test::TestSystemTrayDelegate*>(
+ Shell::GetInstance()->system_tray_delegate());
+ tray_delegate->set_should_show_display_notification(true);
+
+ UpdateDisplay("400x400");
+ DisplayManager* display_manager = Shell::GetInstance()->display_manager();
+ gfx::Display::SetInternalDisplayId(display_manager->first_display_id());
+ EXPECT_TRUE(GetDisplayNotificationText().empty());
+
+ // rotation.
+ UpdateDisplay("400x400/r");
+ EXPECT_EQ(
+ l10n_util::GetStringFUTF16(
+ IDS_ASH_STATUS_TRAY_DISPLAY_ROTATED, GetFirstDisplayName(),
+ l10n_util::GetStringUTF16(
+ IDS_ASH_STATUS_TRAY_DISPLAY_ORIENTATION_90)),
+ GetDisplayNotificationText());
+
+ CloseNotification();
+ UpdateDisplay("400x400");
+ EXPECT_EQ(
+ l10n_util::GetStringFUTF16(
+ IDS_ASH_STATUS_TRAY_DISPLAY_ROTATED, GetFirstDisplayName(),
+ l10n_util::GetStringUTF16(
+ IDS_ASH_STATUS_TRAY_DISPLAY_STANDARD_ORIENTATION)),
+ GetDisplayNotificationText());
+
+ // UI-scale
+ CloseNotification();
+ UpdateDisplay("400x400@1.5");
+ EXPECT_EQ(
+ l10n_util::GetStringFUTF16(
+ IDS_ASH_STATUS_TRAY_DISPLAY_RESOLUTION_CHANGED,
+ GetFirstDisplayName(), UTF8ToUTF16("600x600")),
+ GetDisplayNotificationText());
+
+ // UI-scale to 1.0
+ CloseNotification();
+ UpdateDisplay("400x400");
+ EXPECT_EQ(
+ l10n_util::GetStringFUTF16(
+ IDS_ASH_STATUS_TRAY_DISPLAY_RESOLUTION_CHANGED,
+ GetFirstDisplayName(), UTF8ToUTF16("400x400")),
+ GetDisplayNotificationText());
+
+ // No-update
+ CloseNotification();
+ UpdateDisplay("400x400");
+ EXPECT_TRUE(GetDisplayNotificationText().empty());
+
+ // Extended.
+ CloseNotification();
+ UpdateDisplay("400x400,200x200");
+ EXPECT_EQ(
+ l10n_util::GetStringFUTF16(
+ IDS_ASH_STATUS_TRAY_DISPLAY_EXTENDED, GetSecondDisplayName()),
+ GetDisplayNotificationText());
+
+ // Mirroring.
+ CloseNotification();
+ display_manager->SetSoftwareMirroring(true);
+ UpdateDisplay("400x400,200x200");
+ EXPECT_EQ(
+ l10n_util::GetStringFUTF16(
+ IDS_ASH_STATUS_TRAY_DISPLAY_MIRRORING, GetMirroredDisplayName()),
+ GetDisplayNotificationText());
+
+ // Back to extended.
+ CloseNotification();
+ display_manager->SetSoftwareMirroring(false);
+ UpdateDisplay("400x400,200x200");
+ EXPECT_EQ(
+ l10n_util::GetStringFUTF16(
+ IDS_ASH_STATUS_TRAY_DISPLAY_EXTENDED, GetSecondDisplayName()),
+ GetDisplayNotificationText());
+
+ // Resize the first display.
+ UpdateDisplay("400x400@1.5,200x200");
+ EXPECT_EQ(
+ l10n_util::GetStringFUTF16(
+ IDS_ASH_STATUS_TRAY_DISPLAY_RESOLUTION_CHANGED,
+ GetFirstDisplayName(), UTF8ToUTF16("600x600")),
+ GetDisplayNotificationText());
+
+ // rotate the second.
+ UpdateDisplay("400x400@1.5,200x200/r");
+ EXPECT_EQ(
+ l10n_util::GetStringFUTF16(
+ IDS_ASH_STATUS_TRAY_DISPLAY_ROTATED,
+ GetSecondDisplayName(),
+ l10n_util::GetStringUTF16(
+ IDS_ASH_STATUS_TRAY_DISPLAY_ORIENTATION_90)),
+ GetDisplayNotificationText());
+}
+
+TEST_F(TrayDisplayTest, DisplayConfigurationChangedTwice) {
+ test::TestSystemTrayDelegate* tray_delegate =
+ static_cast<test::TestSystemTrayDelegate*>(
+ Shell::GetInstance()->system_tray_delegate());
+ tray_delegate->set_should_show_display_notification(true);
+
+ UpdateDisplay("400x400,200x200");
+ EXPECT_EQ(
+ l10n_util::GetStringUTF16(
+ IDS_ASH_STATUS_TRAY_DISPLAY_EXTENDED_NO_INTERNAL),
+ GetDisplayNotificationText());
+
+ // OnDisplayConfigurationChanged() may be called more than once for a single
+ // update display in case of primary is swapped or recovered from dock mode.
+ // Should not remove the notification in such case.
+ tray_display()->OnDisplayConfigurationChanged();
+ EXPECT_EQ(
+ l10n_util::GetStringUTF16(
+ IDS_ASH_STATUS_TRAY_DISPLAY_EXTENDED_NO_INTERNAL),
+ GetDisplayNotificationText());
+
+ // Back to the single display. It SHOULD remove the notification since the
+ // information is stale.
+ UpdateDisplay("400x400");
+ EXPECT_TRUE(GetDisplayNotificationText().empty());
+}
+
+TEST_F(TrayDisplayTest, UpdateAfterSuppressDisplayNotification) {
+ UpdateDisplay("400x400,200x200");
+
+ test::TestSystemTrayDelegate* tray_delegate =
+ static_cast<test::TestSystemTrayDelegate*>(
+ Shell::GetInstance()->system_tray_delegate());
+ tray_delegate->set_should_show_display_notification(true);
+
+ // rotate the second.
+ UpdateDisplay("400x400,200x200/r");
+ EXPECT_EQ(
+ l10n_util::GetStringFUTF16(
+ IDS_ASH_STATUS_TRAY_DISPLAY_ROTATED,
+ GetSecondDisplayName(),
+ l10n_util::GetStringUTF16(
+ IDS_ASH_STATUS_TRAY_DISPLAY_ORIENTATION_90)),
+ GetDisplayNotificationText());
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/system/chromeos/tray_tracing.cc b/chromium/ash/system/chromeos/tray_tracing.cc
new file mode 100644
index 00000000000..764e9432b30
--- /dev/null
+++ b/chromium/ash/system/chromeos/tray_tracing.cc
@@ -0,0 +1,113 @@
+// Copyright 2013 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.
+
+#include "ash/system/chromeos/tray_tracing.h"
+
+#include "ash/system/tray/actionable_view.h"
+#include "ash/system/tray/fixed_sized_image_view.h"
+#include "ash/system/tray/system_tray.h"
+#include "ash/system/tray/system_tray_delegate.h"
+#include "ash/system/tray/system_tray_notifier.h"
+#include "ash/system/tray/tray_constants.h"
+#include "grit/ash_resources.h"
+#include "grit/ash_strings.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "ui/gfx/image/image.h"
+#include "ui/views/controls/image_view.h"
+#include "ui/views/controls/label.h"
+#include "ui/views/layout/box_layout.h"
+
+namespace ash {
+namespace internal {
+
+namespace tray {
+
+class DefaultTracingView : public ash::internal::ActionableView {
+ public:
+ DefaultTracingView() {
+ SetLayoutManager(new views::BoxLayout(
+ views::BoxLayout::kHorizontal,
+ ash::kTrayPopupPaddingHorizontal, 0,
+ ash::kTrayPopupPaddingBetweenItems));
+
+ ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance();
+ image_ =
+ new ash::internal::FixedSizedImageView(0, ash::kTrayPopupItemHeight);
+ image_->SetImage(
+ bundle.GetImageNamed(IDR_AURA_UBER_TRAY_TRACING).ToImageSkia());
+ AddChildView(image_);
+
+ label_ = new views::Label();
+ label_->SetMultiLine(true);
+ label_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
+ label_->SetText(bundle.GetLocalizedString(IDS_ASH_STATUS_TRAY_TRACING));
+ AddChildView(label_);
+ }
+
+ virtual ~DefaultTracingView() {}
+
+ private:
+ // Overridden from ActionableView.
+ virtual bool PerformAction(const ui::Event& event) OVERRIDE {
+ ash::Shell::GetInstance()->system_tray_delegate()->ShowChromeSlow();
+ return true;
+ }
+
+ views::ImageView* image_;
+ views::Label* label_;
+
+ DISALLOW_COPY_AND_ASSIGN(DefaultTracingView);
+};
+
+} // namespace tray
+
+////////////////////////////////////////////////////////////////////////////////
+// ash::internal::TrayTracing
+
+TrayTracing::TrayTracing(SystemTray* system_tray)
+ : TrayImageItem(system_tray, IDR_AURA_UBER_TRAY_TRACING),
+ default_(NULL) {
+ DCHECK(Shell::GetInstance()->delegate());
+ DCHECK(system_tray);
+ Shell::GetInstance()->system_tray_notifier()->AddTracingObserver(this);
+}
+
+TrayTracing::~TrayTracing() {
+ Shell::GetInstance()->system_tray_notifier()->RemoveTracingObserver(this);
+}
+
+void TrayTracing::SetTrayIconVisible(bool visible) {
+ if (tray_view())
+ tray_view()->SetVisible(visible);
+}
+
+bool TrayTracing::GetInitialVisibility() {
+ return false;
+}
+
+views::View* TrayTracing::CreateDefaultView(user::LoginStatus status) {
+ CHECK(default_ == NULL);
+ if (tray_view() && tray_view()->visible())
+ default_ = new tray::DefaultTracingView();
+ return default_;
+}
+
+views::View* TrayTracing::CreateDetailedView(user::LoginStatus status) {
+ return NULL;
+}
+
+void TrayTracing::DestroyDefaultView() {
+ default_ = NULL;
+}
+
+void TrayTracing::DestroyDetailedView() {
+}
+
+void TrayTracing::OnTracingModeChanged(bool value) {
+ SetTrayIconVisible(value);
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/system/chromeos/tray_tracing.h b/chromium/ash/system/chromeos/tray_tracing.h
new file mode 100644
index 00000000000..f9491e1d903
--- /dev/null
+++ b/chromium/ash/system/chromeos/tray_tracing.h
@@ -0,0 +1,57 @@
+// Copyright 2013 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 ASH_SYSTEM_TRAY_TRACING_H_
+#define ASH_SYSTEM_TRAY_TRACING_H_
+
+#include "ash/ash_export.h"
+#include "ash/system/tray/tray_image_item.h"
+
+namespace views {
+class View;
+}
+
+namespace ash {
+
+class ASH_EXPORT TracingObserver {
+ public:
+ virtual ~TracingObserver() {}
+
+ // Notifies when tracing mode changes.
+ virtual void OnTracingModeChanged(bool value) = 0;
+};
+
+namespace internal {
+
+// This is the item that displays when users enable performance tracing at
+// chrome://slow. It alerts them that this mode is running, and provides an
+// easy way to open the page to disable it.
+class TrayTracing : public TrayImageItem,
+ public TracingObserver {
+ public:
+ explicit TrayTracing(SystemTray* system_tray);
+ virtual ~TrayTracing();
+
+ private:
+ void SetTrayIconVisible(bool visible);
+
+ // Overridden from TrayImageItem.
+ virtual bool GetInitialVisibility() OVERRIDE;
+ virtual views::View* CreateDefaultView(user::LoginStatus status) OVERRIDE;
+ virtual views::View* CreateDetailedView(user::LoginStatus status) OVERRIDE;
+ virtual void DestroyDefaultView() OVERRIDE;
+ virtual void DestroyDetailedView() OVERRIDE;
+
+ // Overridden from TracingObserver.
+ virtual void OnTracingModeChanged(bool value) OVERRIDE;
+
+ views::View* default_;
+
+ DISALLOW_COPY_AND_ASSIGN(TrayTracing);
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_SYSTEM_TRAY_TRACING_H_
diff --git a/chromium/ash/system/date/clock_observer.h b/chromium/ash/system/date/clock_observer.h
new file mode 100644
index 00000000000..30465288bf7
--- /dev/null
+++ b/chromium/ash/system/date/clock_observer.h
@@ -0,0 +1,23 @@
+// Copyright (c) 2012 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 ASH_SYSTEM_DATE_CLOCK_OBSERVER_H_
+#define ASH_SYSTEM_DATE_CLOCK_OBSERVER_H_
+
+namespace ash {
+
+class ClockObserver {
+ public:
+ virtual ~ClockObserver() {}
+
+ virtual void OnDateFormatChanged() = 0;
+ virtual void OnSystemClockTimeUpdated() = 0;
+
+ // Force a refresh (e.g. after the system is resumed).
+ virtual void Refresh() = 0;
+};
+
+};
+
+#endif // ASH_SYSTEM_DATE_CLOCK_OBSERVER_H_
diff --git a/chromium/ash/system/date/date_view.cc b/chromium/ash/system/date/date_view.cc
new file mode 100644
index 00000000000..cc1552d9246
--- /dev/null
+++ b/chromium/ash/system/date/date_view.cc
@@ -0,0 +1,291 @@
+// Copyright (c) 2012 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.
+
+#include "ash/system/date/date_view.h"
+
+#include "ash/shell.h"
+#include "ash/system/tray/system_tray_delegate.h"
+#include "ash/system/tray/tray_constants.h"
+#include "ash/system/tray/tray_utils.h"
+#include "base/i18n/time_formatting.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/time/time.h"
+#include "grit/ash_strings.h"
+#include "third_party/icu/source/i18n/unicode/datefmt.h"
+#include "third_party/icu/source/i18n/unicode/dtptngen.h"
+#include "third_party/icu/source/i18n/unicode/smpdtfmt.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/views/controls/label.h"
+#include "ui/views/layout/box_layout.h"
+#include "ui/views/layout/grid_layout.h"
+#include "ui/views/widget/widget.h"
+
+namespace ash {
+namespace internal {
+namespace tray {
+
+namespace {
+
+// Amount of slop to add into the timer to make sure we're into the next minute
+// when the timer goes off.
+const int kTimerSlopSeconds = 1;
+
+// Top number text color of vertical clock.
+const SkColor kVerticalClockHourColor = SkColorSetRGB(0xBA, 0xBA, 0xBA);
+
+base::string16 FormatDate(const base::Time& time) {
+ icu::UnicodeString date_string;
+ scoped_ptr<icu::DateFormat> formatter(
+ icu::DateFormat::createDateInstance(icu::DateFormat::kMedium));
+ formatter->format(static_cast<UDate>(time.ToDoubleT() * 1000), date_string);
+ return base::string16(date_string.getBuffer(),
+ static_cast<size_t>(date_string.length()));
+}
+
+base::string16 FormatDayOfWeek(const base::Time& time) {
+ UErrorCode status = U_ZERO_ERROR;
+ scoped_ptr<icu::DateTimePatternGenerator> generator(
+ icu::DateTimePatternGenerator::createInstance(status));
+ DCHECK(U_SUCCESS(status));
+ const char kBasePattern[] = "EEE";
+ icu::UnicodeString generated_pattern =
+ generator->getBestPattern(icu::UnicodeString(kBasePattern), status);
+ DCHECK(U_SUCCESS(status));
+ icu::SimpleDateFormat simple_formatter(generated_pattern, status);
+ DCHECK(U_SUCCESS(status));
+ icu::UnicodeString date_string;
+ simple_formatter.format(
+ static_cast<UDate>(time.ToDoubleT() * 1000), date_string, status);
+ DCHECK(U_SUCCESS(status));
+ return base::string16(
+ date_string.getBuffer(), static_cast<size_t>(date_string.length()));
+}
+
+views::Label* CreateLabel() {
+ views::Label* label = new views::Label;
+ label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
+ label->SetBackgroundColor(SkColorSetARGB(0, 255, 255, 255));
+ return label;
+}
+
+} // namespace
+
+BaseDateTimeView::~BaseDateTimeView() {
+ timer_.Stop();
+}
+
+void BaseDateTimeView::UpdateText() {
+ base::Time now = base::Time::Now();
+ UpdateTextInternal(now);
+ SchedulePaint();
+ SetTimer(now);
+}
+
+BaseDateTimeView::BaseDateTimeView() {
+ SetTimer(base::Time::Now());
+}
+
+void BaseDateTimeView::SetTimer(const base::Time& now) {
+ // Try to set the timer to go off at the next change of the minute. We don't
+ // want to have the timer go off more than necessary since that will cause
+ // the CPU to wake up and consume power.
+ base::Time::Exploded exploded;
+ now.LocalExplode(&exploded);
+
+ // Often this will be called at minute boundaries, and we'll actually want
+ // 60 seconds from now.
+ int seconds_left = 60 - exploded.second;
+ if (seconds_left == 0)
+ seconds_left = 60;
+
+ // Make sure that the timer fires on the next minute. Without this, if it is
+ // called just a teeny bit early, then it will skip the next minute.
+ seconds_left += kTimerSlopSeconds;
+
+ timer_.Stop();
+ timer_.Start(
+ FROM_HERE, base::TimeDelta::FromSeconds(seconds_left),
+ this, &BaseDateTimeView::UpdateText);
+}
+
+void BaseDateTimeView::ChildPreferredSizeChanged(views::View* child) {
+ PreferredSizeChanged();
+}
+
+void BaseDateTimeView::OnLocaleChanged() {
+ UpdateText();
+}
+
+DateView::DateView() : actionable_(false) {
+ SetLayoutManager(
+ new views::BoxLayout(
+ views::BoxLayout::kVertical, 0, 0, 0));
+ date_label_ = CreateLabel();
+ date_label_->SetEnabledColor(kHeaderTextColorNormal);
+ UpdateTextInternal(base::Time::Now());
+ AddChildView(date_label_);
+ set_focusable(actionable_);
+}
+
+DateView::~DateView() {
+}
+
+void DateView::SetActionable(bool actionable) {
+ actionable_ = actionable;
+ set_focusable(actionable_);
+}
+
+void DateView::UpdateTextInternal(const base::Time& now) {
+ SetAccessibleName(
+ base::TimeFormatFriendlyDate(now) +
+ ASCIIToUTF16(",") +
+ base::TimeFormatTimeOfDayWithHourClockType(
+ now, base::k12HourClock, base:: kKeepAmPm));
+ date_label_->SetText(
+ l10n_util::GetStringFUTF16(
+ IDS_ASH_STATUS_TRAY_DATE, FormatDayOfWeek(now), FormatDate(now)));
+}
+
+bool DateView::PerformAction(const ui::Event& event) {
+ if (!actionable_)
+ return false;
+
+ ash::Shell::GetInstance()->system_tray_delegate()->ShowDateSettings();
+ return true;
+}
+
+void DateView::OnMouseEntered(const ui::MouseEvent& event) {
+ if (!actionable_)
+ return;
+ date_label_->SetEnabledColor(kHeaderTextColorHover);
+ SchedulePaint();
+}
+
+void DateView::OnMouseExited(const ui::MouseEvent& event) {
+ if (!actionable_)
+ return;
+ date_label_->SetEnabledColor(kHeaderTextColorNormal);
+ SchedulePaint();
+}
+
+TimeView::TimeView(TrayDate::ClockLayout clock_layout)
+ : hour_type_(ash::Shell::GetInstance()->system_tray_delegate()->
+ GetHourClockType()) {
+ SetupLabels();
+ UpdateTextInternal(base::Time::Now());
+ UpdateClockLayout(clock_layout);
+ set_focusable(false);
+}
+
+TimeView::~TimeView() {
+}
+
+void TimeView::UpdateTimeFormat() {
+ hour_type_ =
+ ash::Shell::GetInstance()->system_tray_delegate()->GetHourClockType();
+ UpdateText();
+}
+
+void TimeView::UpdateTextInternal(const base::Time& now) {
+ // Just in case |now| is null, do NOT update time; otherwise, it will
+ // crash icu code by calling into base::TimeFormatTimeOfDayWithHourClockType,
+ // see details in crbug.com/147570.
+ if (now.is_null()) {
+ LOG(ERROR) << "Received null value from base::Time |now| in argument";
+ return;
+ }
+
+ base::string16 current_time = base::TimeFormatTimeOfDayWithHourClockType(
+ now, hour_type_, base::kDropAmPm);
+ label_->SetText(current_time);
+ label_->SetTooltipText(base::TimeFormatFriendlyDate(now));
+
+ // Calculate vertical clock layout labels.
+ size_t colon_pos = current_time.find(ASCIIToUTF16(":"));
+ base::string16 hour = current_time.substr(0, colon_pos);
+ base::string16 minute = current_time.substr(colon_pos + 1);
+ label_hour_left_->SetText(hour.substr(0, 1));
+ label_hour_right_->SetText(hour.length() == 2 ?
+ hour.substr(1,1) : ASCIIToUTF16(":"));
+ label_minute_left_->SetText(minute.substr(0, 1));
+ label_minute_right_->SetText(minute.substr(1, 1));
+
+ Layout();
+}
+
+bool TimeView::PerformAction(const ui::Event& event) {
+ return false;
+}
+
+bool TimeView::OnMousePressed(const ui::MouseEvent& event) {
+ // Let the event fall through.
+ return false;
+}
+
+void TimeView::UpdateClockLayout(TrayDate::ClockLayout clock_layout){
+ SetBorder(clock_layout);
+ if (clock_layout == TrayDate::HORIZONTAL_CLOCK) {
+ RemoveChildView(label_hour_left_.get());
+ RemoveChildView(label_hour_right_.get());
+ RemoveChildView(label_minute_left_.get());
+ RemoveChildView(label_minute_right_.get());
+ SetLayoutManager(
+ new views::BoxLayout(views::BoxLayout::kHorizontal, 0, 0, 0));
+ AddChildView(label_.get());
+ } else {
+ RemoveChildView(label_.get());
+ views::GridLayout* layout = new views::GridLayout(this);
+ SetLayoutManager(layout);
+ views::ColumnSet* columns = layout->AddColumnSet(0);
+ columns->AddPaddingColumn(0, 6);
+ columns->AddColumn(views::GridLayout::CENTER, views::GridLayout::CENTER,
+ 0, views::GridLayout::USE_PREF, 0, 0);
+ columns->AddColumn(views::GridLayout::CENTER, views::GridLayout::CENTER,
+ 0, views::GridLayout::USE_PREF, 0, 0);
+ layout->AddPaddingRow(0, kTrayLabelItemVerticalPaddingVeriticalAlignment);
+ layout->StartRow(0, 0);
+ layout->AddView(label_hour_left_.get());
+ layout->AddView(label_hour_right_.get());
+ layout->StartRow(0, 0);
+ layout->AddView(label_minute_left_.get());
+ layout->AddView(label_minute_right_.get());
+ layout->AddPaddingRow(0, kTrayLabelItemVerticalPaddingVeriticalAlignment);
+ }
+ Layout();
+}
+
+void TimeView::SetBorder(TrayDate::ClockLayout clock_layout) {
+ if (clock_layout == TrayDate::HORIZONTAL_CLOCK)
+ set_border(views::Border::CreateEmptyBorder(
+ 0, kTrayLabelItemHorizontalPaddingBottomAlignment,
+ 0, kTrayLabelItemHorizontalPaddingBottomAlignment));
+ else
+ set_border(NULL);
+}
+
+void TimeView::SetupLabels() {
+ label_.reset(CreateLabel());
+ SetupLabel(label_.get());
+ label_hour_left_.reset(CreateLabel());
+ SetupLabel(label_hour_left_.get());
+ label_hour_right_.reset(CreateLabel());
+ SetupLabel(label_hour_right_.get());
+ label_minute_left_.reset(CreateLabel());
+ SetupLabel(label_minute_left_.get());
+ label_minute_right_.reset(CreateLabel());
+ SetupLabel(label_minute_right_.get());
+ label_hour_left_->SetEnabledColor(kVerticalClockHourColor);
+ label_hour_right_->SetEnabledColor(kVerticalClockHourColor);
+}
+
+void TimeView::SetupLabel(views::Label* label) {
+ label->set_owned_by_client();
+ SetupLabelForTray(label);
+ gfx::Font font = label->font();
+ label->SetFont(font.DeriveFont(0, font.GetStyle() & ~gfx::Font::BOLD));
+}
+
+} // namespace tray
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/system/date/date_view.h b/chromium/ash/system/date/date_view.h
new file mode 100644
index 00000000000..98bc45fb7bb
--- /dev/null
+++ b/chromium/ash/system/date/date_view.h
@@ -0,0 +1,127 @@
+// Copyright (c) 2012 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 ASH_SYSTEM_DATE_DATE_VIEW_H_
+#define ASH_SYSTEM_DATE_DATE_VIEW_H_
+
+#include "ash/system/date/tray_date.h"
+#include "ash/system/tray/actionable_view.h"
+#include "base/i18n/time_formatting.h"
+#include "base/timer/timer.h"
+#include "ui/views/view.h"
+
+namespace views {
+class Label;
+}
+
+namespace ash {
+namespace internal {
+namespace tray {
+
+// Abstract base class containing common updating and layout code for the
+// DateView popup and the TimeView tray icon.
+class BaseDateTimeView : public ActionableView {
+ public:
+ virtual ~BaseDateTimeView();
+
+ // Updates the displayed text for the current time and calls SetTimer().
+ void UpdateText();
+
+ protected:
+ BaseDateTimeView();
+
+ private:
+ // Starts |timer_| to schedule the next update.
+ void SetTimer(const base::Time& now);
+
+ // Updates labels to display the current time.
+ virtual void UpdateTextInternal(const base::Time& now) = 0;
+
+ // Overridden from views::View.
+ virtual void ChildPreferredSizeChanged(views::View* child) OVERRIDE;
+ virtual void OnLocaleChanged() OVERRIDE;
+
+ // Invokes UpdateText() when the displayed time should change.
+ base::OneShotTimer<BaseDateTimeView> timer_;
+
+ DISALLOW_COPY_AND_ASSIGN(BaseDateTimeView);
+};
+
+// Popup view used to display the date and day of week.
+class DateView : public BaseDateTimeView {
+ public:
+ DateView();
+ virtual ~DateView();
+
+ // Sets whether the view is actionable. An actionable date view gives visual
+ // feedback on hover, can be focused by keyboard, and clicking/pressing space
+ // or enter on the view shows date-related settings.
+ void SetActionable(bool actionable);
+
+ private:
+ // Overridden from BaseDateTimeView.
+ virtual void UpdateTextInternal(const base::Time& now) OVERRIDE;
+
+ // Overridden from ActionableView.
+ virtual bool PerformAction(const ui::Event& event) OVERRIDE;
+
+ // Overridden from views::View.
+ virtual void OnMouseEntered(const ui::MouseEvent& event) OVERRIDE;
+ virtual void OnMouseExited(const ui::MouseEvent& event) OVERRIDE;
+
+ views::Label* date_label_;
+
+ bool actionable_;
+
+ DISALLOW_COPY_AND_ASSIGN(DateView);
+};
+
+// Tray view used to display the current time.
+class TimeView : public BaseDateTimeView {
+ public:
+ TimeView(TrayDate::ClockLayout clock_layout);
+ virtual ~TimeView();
+
+ views::Label* label() const { return label_.get(); }
+ views::Label* label_hour_left() const { return label_hour_left_.get(); }
+ views::Label* label_hour_right() const { return label_hour_right_.get(); }
+ views::Label* label_minute_left() const { return label_minute_left_.get(); }
+ views::Label* label_minute_right() const { return label_minute_right_.get(); }
+
+ // Updates the format of the displayed time.
+ void UpdateTimeFormat();
+
+ // Updates clock layout.
+ void UpdateClockLayout(TrayDate::ClockLayout clock_layout);
+
+ private:
+ // Overridden from BaseDateTimeView.
+ virtual void UpdateTextInternal(const base::Time& now) OVERRIDE;
+
+ // Overridden from ActionableView.
+ virtual bool PerformAction(const ui::Event& event) OVERRIDE;
+
+ // Overridden from views::View.
+ virtual bool OnMousePressed(const ui::MouseEvent& event) OVERRIDE;
+
+ void SetBorder(TrayDate::ClockLayout clock_layout);
+ void SetupLabels();
+ void SetupLabel(views::Label* label);
+
+ scoped_ptr<views::Label> label_;
+ scoped_ptr<views::Label> label_hour_left_;
+ scoped_ptr<views::Label> label_hour_right_;
+ scoped_ptr<views::Label> label_minute_left_;
+ scoped_ptr<views::Label> label_minute_right_;
+
+ base::HourClockType hour_type_;
+
+ DISALLOW_COPY_AND_ASSIGN(TimeView);
+};
+
+} // namespace tray
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_SYSTEM_DATE_DATE_VIEW_H_
diff --git a/chromium/ash/system/date/tray_date.cc b/chromium/ash/system/date/tray_date.cc
new file mode 100644
index 00000000000..8cd22e5579f
--- /dev/null
+++ b/chromium/ash/system/date/tray_date.cc
@@ -0,0 +1,209 @@
+// Copyright (c) 2012 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.
+
+#include "ash/system/date/tray_date.h"
+
+#include "ash/session_state_delegate.h"
+#include "ash/shell.h"
+#include "ash/shell_delegate.h"
+#include "ash/system/date/date_view.h"
+#include "ash/system/tray/system_tray.h"
+#include "ash/system/tray/system_tray_delegate.h"
+#include "ash/system/tray/system_tray_notifier.h"
+#include "ash/system/tray/tray_constants.h"
+#include "ash/system/tray/tray_item_view.h"
+#include "ash/system/tray/tray_popup_header_button.h"
+#include "base/i18n/time_formatting.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/time/time.h"
+#include "base/timer/timer.h"
+#include "grit/ash_resources.h"
+#include "grit/ash_strings.h"
+#include "third_party/icu/source/i18n/unicode/datefmt.h"
+#include "third_party/icu/source/i18n/unicode/fieldpos.h"
+#include "third_party/icu/source/i18n/unicode/fmtable.h"
+#include "third_party/skia/include/core/SkRect.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "ui/gfx/image/image.h"
+#include "ui/gfx/image/image_skia.h"
+#include "ui/gfx/size.h"
+#include "ui/views/controls/button/button.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/fill_layout.h"
+#include "ui/views/painter.h"
+#include "ui/views/view.h"
+#include "ui/views/widget/widget.h"
+
+namespace {
+
+const int kPaddingVertical = 19;
+
+class DateDefaultView : public views::View,
+ public views::ButtonListener {
+ public:
+ explicit DateDefaultView(ash::user::LoginStatus login)
+ : help_(NULL),
+ shutdown_(NULL),
+ lock_(NULL) {
+ SetLayoutManager(new views::FillLayout);
+
+ ash::internal::tray::DateView* date_view =
+ new ash::internal::tray::DateView();
+ date_view->set_border(views::Border::CreateEmptyBorder(kPaddingVertical,
+ ash::kTrayPopupPaddingHorizontal,
+ 0,
+ 0));
+ ash::internal::SpecialPopupRow* view = new ash::internal::SpecialPopupRow();
+ view->SetContent(date_view);
+ AddChildView(view);
+
+ if (login == ash::user::LOGGED_IN_LOCKED ||
+ login == ash::user::LOGGED_IN_NONE)
+ return;
+
+ date_view->SetActionable(true);
+
+ help_ = new ash::internal::TrayPopupHeaderButton(this,
+ IDR_AURA_UBER_TRAY_HELP,
+ IDR_AURA_UBER_TRAY_HELP,
+ IDR_AURA_UBER_TRAY_HELP_HOVER,
+ IDR_AURA_UBER_TRAY_HELP_HOVER,
+ IDS_ASH_STATUS_TRAY_HELP);
+ help_->SetTooltipText(l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_HELP));
+ view->AddButton(help_);
+
+ if (login != ash::user::LOGGED_IN_LOCKED &&
+ login != ash::user::LOGGED_IN_RETAIL_MODE) {
+ shutdown_ = new ash::internal::TrayPopupHeaderButton(this,
+ IDR_AURA_UBER_TRAY_SHUTDOWN,
+ IDR_AURA_UBER_TRAY_SHUTDOWN,
+ IDR_AURA_UBER_TRAY_SHUTDOWN_HOVER,
+ IDR_AURA_UBER_TRAY_SHUTDOWN_HOVER,
+ IDS_ASH_STATUS_TRAY_SHUTDOWN);
+ shutdown_->SetTooltipText(
+ l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_SHUTDOWN));
+ view->AddButton(shutdown_);
+ }
+
+ if (ash::Shell::GetInstance()->session_state_delegate()->CanLockScreen()) {
+ lock_ = new ash::internal::TrayPopupHeaderButton(this,
+ IDR_AURA_UBER_TRAY_LOCKSCREEN,
+ IDR_AURA_UBER_TRAY_LOCKSCREEN,
+ IDR_AURA_UBER_TRAY_LOCKSCREEN_HOVER,
+ IDR_AURA_UBER_TRAY_LOCKSCREEN_HOVER,
+ IDS_ASH_STATUS_TRAY_LOCK);
+ lock_->SetTooltipText(
+ l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_LOCK));
+ view->AddButton(lock_);
+ }
+ }
+
+ virtual ~DateDefaultView() {}
+
+ private:
+ // Overridden from views::ButtonListener.
+ virtual void ButtonPressed(views::Button* sender,
+ const ui::Event& event) OVERRIDE {
+ ash::Shell* shell = ash::Shell::GetInstance();
+ ash::ShellDelegate* shell_delegate = shell->delegate();
+ ash::SystemTrayDelegate* tray_delegate = shell->system_tray_delegate();
+ if (sender == help_) {
+ shell_delegate->RecordUserMetricsAction(ash::UMA_TRAY_HELP);
+ tray_delegate->ShowHelp();
+ } else if (sender == shutdown_) {
+ shell_delegate->RecordUserMetricsAction(ash::UMA_TRAY_SHUT_DOWN);
+ tray_delegate->ShutDown();
+ } else if (sender == lock_) {
+ shell_delegate->RecordUserMetricsAction(ash::UMA_TRAY_LOCK_SCREEN);
+ tray_delegate->RequestLockScreen();
+ } else {
+ NOTREACHED();
+ }
+ }
+
+ ash::internal::TrayPopupHeaderButton* help_;
+ ash::internal::TrayPopupHeaderButton* shutdown_;
+ ash::internal::TrayPopupHeaderButton* lock_;
+
+ DISALLOW_COPY_AND_ASSIGN(DateDefaultView);
+};
+
+} // namespace
+
+namespace ash {
+namespace internal {
+
+TrayDate::TrayDate(SystemTray* system_tray)
+ : SystemTrayItem(system_tray),
+ time_tray_(NULL) {
+ Shell::GetInstance()->system_tray_notifier()->AddClockObserver(this);
+}
+
+TrayDate::~TrayDate() {
+ Shell::GetInstance()->system_tray_notifier()->RemoveClockObserver(this);
+}
+
+views::View* TrayDate::CreateTrayView(user::LoginStatus status) {
+ CHECK(time_tray_ == NULL);
+ ClockLayout clock_layout =
+ (system_tray()->shelf_alignment() == SHELF_ALIGNMENT_BOTTOM ||
+ system_tray()->shelf_alignment() == SHELF_ALIGNMENT_TOP) ?
+ HORIZONTAL_CLOCK : VERTICAL_CLOCK;
+ time_tray_ = new tray::TimeView(clock_layout);
+ views::View* view = new TrayItemView(this);
+ view->AddChildView(time_tray_);
+ return view;
+}
+
+views::View* TrayDate::CreateDefaultView(user::LoginStatus status) {
+ return new DateDefaultView(status);
+}
+
+views::View* TrayDate::CreateDetailedView(user::LoginStatus status) {
+ return NULL;
+}
+
+void TrayDate::DestroyTrayView() {
+ time_tray_ = NULL;
+}
+
+void TrayDate::DestroyDefaultView() {
+}
+
+void TrayDate::DestroyDetailedView() {
+}
+
+void TrayDate::UpdateAfterLoginStatusChange(user::LoginStatus status) {
+}
+
+void TrayDate::UpdateAfterShelfAlignmentChange(ShelfAlignment alignment) {
+ if (time_tray_) {
+ ClockLayout clock_layout = (alignment == SHELF_ALIGNMENT_BOTTOM ||
+ alignment == SHELF_ALIGNMENT_TOP) ?
+ HORIZONTAL_CLOCK : VERTICAL_CLOCK;
+ time_tray_->UpdateClockLayout(clock_layout);
+ }
+}
+
+void TrayDate::OnDateFormatChanged() {
+ if (time_tray_)
+ time_tray_->UpdateTimeFormat();
+}
+
+void TrayDate::OnSystemClockTimeUpdated() {
+ if (time_tray_)
+ time_tray_->UpdateTimeFormat();
+}
+
+void TrayDate::Refresh() {
+ if (time_tray_)
+ time_tray_->UpdateText();
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/system/date/tray_date.h b/chromium/ash/system/date/tray_date.h
new file mode 100644
index 00000000000..dc8988ca0a6
--- /dev/null
+++ b/chromium/ash/system/date/tray_date.h
@@ -0,0 +1,59 @@
+// Copyright (c) 2012 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 ASH_SYSTEM_DATE_TRAY_DATE_H_
+#define ASH_SYSTEM_DATE_TRAY_DATE_H_
+
+#include "ash/system/date/clock_observer.h"
+#include "ash/system/tray/system_tray_item.h"
+
+namespace views {
+class Label;
+}
+
+namespace ash {
+namespace internal {
+
+namespace tray {
+class TimeView;
+}
+
+class TrayDate : public SystemTrayItem,
+ public ClockObserver {
+ public:
+ enum ClockLayout {
+ HORIZONTAL_CLOCK,
+ VERTICAL_CLOCK,
+ };
+ explicit TrayDate(SystemTray* system_tray);
+ virtual ~TrayDate();
+
+ private:
+ // Overridden from SystemTrayItem.
+ virtual views::View* CreateTrayView(user::LoginStatus status) OVERRIDE;
+ virtual views::View* CreateDefaultView(user::LoginStatus status) OVERRIDE;
+ virtual views::View* CreateDetailedView(user::LoginStatus status) OVERRIDE;
+ virtual void DestroyTrayView() OVERRIDE;
+ virtual void DestroyDefaultView() OVERRIDE;
+ virtual void DestroyDetailedView() OVERRIDE;
+ virtual void UpdateAfterLoginStatusChange(user::LoginStatus status) OVERRIDE;
+ virtual void UpdateAfterShelfAlignmentChange(
+ ShelfAlignment alignment) OVERRIDE;
+
+ // Overridden from ClockObserver.
+ virtual void OnDateFormatChanged() OVERRIDE;
+ virtual void OnSystemClockTimeUpdated() OVERRIDE;
+ virtual void Refresh() OVERRIDE;
+
+ void SetupLabelForTimeTray(views::Label* label);
+
+ tray::TimeView* time_tray_;
+
+ DISALLOW_COPY_AND_ASSIGN(TrayDate);
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_SYSTEM_DATE_TRAY_DATE_H_
diff --git a/chromium/ash/system/drive/drive_observer.h b/chromium/ash/system/drive/drive_observer.h
new file mode 100644
index 00000000000..a721dd44fc7
--- /dev/null
+++ b/chromium/ash/system/drive/drive_observer.h
@@ -0,0 +1,22 @@
+// Copyright (c) 2012 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 ASH_SYSTEM_DRIVE_DRIVE_OBSERVER_H_
+#define ASH_SYSTEM_DRIVE_DRIVE_OBSERVER_H_
+
+#include "ash/system/tray/system_tray_delegate.h"
+
+namespace ash {
+
+class DriveObserver {
+ public:
+ virtual void OnDriveJobUpdated(const DriveOperationStatus& status) = 0;
+
+ protected:
+ virtual ~DriveObserver() {}
+};
+
+} // namespace ash
+
+#endif // ASH_SYSTEM_DRIVE_DRIVE_OBSERVER_H_
diff --git a/chromium/ash/system/drive/tray_drive.cc b/chromium/ash/system/drive/tray_drive.cc
new file mode 100644
index 00000000000..028aa29bb58
--- /dev/null
+++ b/chromium/ash/system/drive/tray_drive.cc
@@ -0,0 +1,512 @@
+// Copyright (c) 2012 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.
+
+#include "ash/system/drive/tray_drive.h"
+
+#include <vector>
+
+#include "ash/shell.h"
+#include "ash/system/tray/fixed_sized_scroll_view.h"
+#include "ash/system/tray/hover_highlight_view.h"
+#include "ash/system/tray/system_tray.h"
+#include "ash/system/tray/system_tray_delegate.h"
+#include "ash/system/tray/system_tray_notifier.h"
+#include "ash/system/tray/tray_constants.h"
+#include "ash/system/tray/tray_details_view.h"
+#include "ash/system/tray/tray_item_more.h"
+#include "ash/system/tray/tray_item_view.h"
+#include "base/logging.h"
+#include "base/stl_util.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/utf_string_conversions.h"
+#include "grit/ash_resources.h"
+#include "grit/ash_strings.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "ui/gfx/font.h"
+#include "ui/gfx/image/image.h"
+#include "ui/views/controls/button/image_button.h"
+#include "ui/views/controls/image_view.h"
+#include "ui/views/controls/label.h"
+#include "ui/views/controls/progress_bar.h"
+#include "ui/views/layout/box_layout.h"
+#include "ui/views/layout/grid_layout.h"
+#include "ui/views/widget/widget.h"
+
+namespace ash {
+
+namespace internal {
+
+namespace {
+
+const int kSidePadding = 8;
+const int kHorizontalPadding = 6;
+const int kVerticalPadding = 6;
+const int kTopPadding = 6;
+const int kBottomPadding = 10;
+const int kProgressBarWidth = 100;
+const int kProgressBarHeight = 11;
+const int64 kHideDelayInMs = 1000;
+
+base::string16 GetTrayLabel(const ash::DriveOperationStatusList& list) {
+ return l10n_util::GetStringFUTF16(IDS_ASH_STATUS_TRAY_DRIVE_SYNCING,
+ base::IntToString16(static_cast<int>(list.size())));
+}
+
+scoped_ptr<ash::DriveOperationStatusList> GetCurrentOperationList() {
+ ash::SystemTrayDelegate* delegate =
+ ash::Shell::GetInstance()->system_tray_delegate();
+ scoped_ptr<ash::DriveOperationStatusList> list(
+ new ash::DriveOperationStatusList);
+ delegate->GetDriveOperationStatusList(list.get());
+ return list.Pass();
+}
+
+}
+
+namespace tray {
+
+class DriveDefaultView : public TrayItemMore {
+ public:
+ DriveDefaultView(SystemTrayItem* owner,
+ const DriveOperationStatusList* list)
+ : TrayItemMore(owner, true) {
+ ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance();
+
+ SetImage(bundle.GetImageNamed(IDR_AURA_UBER_TRAY_DRIVE).ToImageSkia());
+ Update(list);
+ }
+
+ virtual ~DriveDefaultView() {}
+
+ void Update(const DriveOperationStatusList* list) {
+ DCHECK(list);
+ base::string16 label = GetTrayLabel(*list);
+ SetLabel(label);
+ SetAccessibleName(label);
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(DriveDefaultView);
+};
+
+class DriveDetailedView : public TrayDetailsView,
+ public ViewClickListener {
+ public:
+ DriveDetailedView(SystemTrayItem* owner,
+ const DriveOperationStatusList* list)
+ : TrayDetailsView(owner),
+ settings_(NULL),
+ in_progress_img_(NULL),
+ done_img_(NULL),
+ failed_img_(NULL) {
+ in_progress_img_ = ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
+ IDR_AURA_UBER_TRAY_DRIVE);
+ done_img_ = ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
+ IDR_AURA_UBER_TRAY_DRIVE_DONE);
+ failed_img_ = ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
+ IDR_AURA_UBER_TRAY_DRIVE_FAILED);
+
+ Update(list);
+ }
+
+ virtual ~DriveDetailedView() {
+ STLDeleteValues(&update_map_);
+ }
+
+ void Update(const DriveOperationStatusList* list) {
+ AppendOperationList(list);
+ AppendSettings();
+ AppendHeaderEntry(list);
+
+ SchedulePaint();
+ }
+
+ private:
+
+ class OperationProgressBar : public views::ProgressBar {
+ public:
+ OperationProgressBar() {}
+ private:
+
+ // Overridden from View:
+ virtual gfx::Size GetPreferredSize() OVERRIDE {
+ return gfx::Size(kProgressBarWidth, kProgressBarHeight);
+ }
+
+ DISALLOW_COPY_AND_ASSIGN(OperationProgressBar);
+ };
+
+ class RowView : public HoverHighlightView,
+ public views::ButtonListener {
+ public:
+ RowView(DriveDetailedView* parent,
+ ash::DriveOperationStatus::OperationState state,
+ double progress,
+ const base::FilePath& file_path,
+ int32 operation_id)
+ : HoverHighlightView(parent),
+ container_(parent),
+ status_img_(NULL),
+ label_container_(NULL),
+ progress_bar_(NULL),
+ cancel_button_(NULL),
+ operation_id_(operation_id) {
+ // Status image.
+ status_img_ = new views::ImageView();
+ AddChildView(status_img_);
+
+ label_container_ = new views::View();
+ label_container_->SetLayoutManager(new views::BoxLayout(
+ views::BoxLayout::kVertical, 0, 0, kVerticalPadding));
+#if defined(OS_POSIX)
+ base::string16 file_label = UTF8ToUTF16(file_path.BaseName().value());
+#elif defined(OS_WIN)
+ base::string16 file_label = WideToUTF16(file_path.BaseName().value());
+#endif
+ views::Label* label = new views::Label(file_label);
+ label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
+ label_container_->AddChildView(label);
+ // Add progress bar.
+ progress_bar_ = new OperationProgressBar();
+ label_container_->AddChildView(progress_bar_);
+
+ AddChildView(label_container_);
+
+ cancel_button_ = new views::ImageButton(this);
+ cancel_button_->SetImage(views::ImageButton::STATE_NORMAL,
+ ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
+ IDR_AURA_UBER_TRAY_DRIVE_CANCEL));
+ cancel_button_->SetImage(views::ImageButton::STATE_HOVERED,
+ ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
+ IDR_AURA_UBER_TRAY_DRIVE_CANCEL_HOVER));
+
+ UpdateStatus(state, progress);
+ AddChildView(cancel_button_);
+ }
+
+ void UpdateStatus(ash::DriveOperationStatus::OperationState state,
+ double progress) {
+ status_img_->SetImage(container_->GetImageForState(state));
+ progress_bar_->SetValue(progress);
+ cancel_button_->SetVisible(
+ state == ash::DriveOperationStatus::OPERATION_NOT_STARTED ||
+ state == ash::DriveOperationStatus::OPERATION_IN_PROGRESS);
+ }
+
+ private:
+
+ // views::View overrides.
+ virtual gfx::Size GetPreferredSize() OVERRIDE {
+ return gfx::Size(
+ status_img_->GetPreferredSize().width() +
+ label_container_->GetPreferredSize().width() +
+ cancel_button_->GetPreferredSize().width() +
+ 2 * kSidePadding + 2 * kHorizontalPadding,
+ std::max(status_img_->GetPreferredSize().height(),
+ std::max(label_container_->GetPreferredSize().height(),
+ cancel_button_->GetPreferredSize().height())) +
+ kTopPadding + kBottomPadding);
+ }
+
+ virtual void Layout() OVERRIDE {
+ gfx::Rect child_area(GetLocalBounds());
+ if (child_area.IsEmpty())
+ return;
+
+ int pos_x = child_area.x() + kSidePadding;
+ int pos_y = child_area.y() + kTopPadding;
+
+ gfx::Rect bounds_status(
+ gfx::Point(pos_x,
+ pos_y + (child_area.height() - kTopPadding -
+ kBottomPadding -
+ status_img_->GetPreferredSize().height())/2),
+ status_img_->GetPreferredSize());
+ status_img_->SetBoundsRect(
+ gfx::IntersectRects(bounds_status, child_area));
+ pos_x += status_img_->bounds().width() + kHorizontalPadding;
+
+ gfx::Rect bounds_label(pos_x,
+ pos_y,
+ child_area.width() - 2 * kSidePadding -
+ 2 * kHorizontalPadding -
+ status_img_->GetPreferredSize().width() -
+ cancel_button_->GetPreferredSize().width(),
+ label_container_->GetPreferredSize().height());
+ label_container_->SetBoundsRect(
+ gfx::IntersectRects(bounds_label, child_area));
+ pos_x += label_container_->bounds().width() + kHorizontalPadding;
+
+ gfx::Rect bounds_button(
+ gfx::Point(pos_x,
+ pos_y + (child_area.height() - kTopPadding -
+ kBottomPadding -
+ cancel_button_->GetPreferredSize().height())/2),
+ cancel_button_->GetPreferredSize());
+ cancel_button_->SetBoundsRect(
+ gfx::IntersectRects(bounds_button, child_area));
+ }
+
+ // views::ButtonListener overrides.
+ virtual void ButtonPressed(views::Button* sender,
+ const ui::Event& event) OVERRIDE {
+ DCHECK(sender == cancel_button_);
+ container_->OnCancelOperation(operation_id_);
+ }
+
+ DriveDetailedView* container_;
+ views::ImageView* status_img_;
+ views::View* label_container_;
+ views::ProgressBar* progress_bar_;
+ views::ImageButton* cancel_button_;
+ int32 operation_id_;
+
+ DISALLOW_COPY_AND_ASSIGN(RowView);
+ };
+
+ void AppendHeaderEntry(const DriveOperationStatusList* list) {
+ if (footer())
+ return;
+ CreateSpecialRow(IDS_ASH_STATUS_TRAY_DRIVE, this);
+ }
+
+ gfx::ImageSkia* GetImageForState(
+ ash::DriveOperationStatus::OperationState state) {
+ switch (state) {
+ case ash::DriveOperationStatus::OPERATION_NOT_STARTED:
+ case ash::DriveOperationStatus::OPERATION_IN_PROGRESS:
+ return in_progress_img_;
+ case ash::DriveOperationStatus::OPERATION_COMPLETED:
+ return done_img_;
+ case ash::DriveOperationStatus::OPERATION_FAILED:
+ return failed_img_;
+ }
+ return failed_img_;
+ }
+
+ void OnCancelOperation(int32 operation_id) {
+ SystemTrayDelegate* delegate = Shell::GetInstance()->system_tray_delegate();
+ delegate->CancelDriveOperation(operation_id);
+ }
+
+ void AppendOperationList(const DriveOperationStatusList* list) {
+ if (!scroller())
+ CreateScrollableList();
+
+ // Apply the update.
+ std::set<base::FilePath> new_set;
+ bool item_list_changed = false;
+ for (DriveOperationStatusList::const_iterator it = list->begin();
+ it != list->end(); ++it) {
+ const DriveOperationStatus& operation = *it;
+
+ new_set.insert(operation.file_path);
+ std::map<base::FilePath, RowView*>::iterator existing_item =
+ update_map_.find(operation.file_path);
+
+ if (existing_item != update_map_.end()) {
+ existing_item->second->UpdateStatus(operation.state,
+ operation.progress);
+ } else {
+ RowView* row_view = new RowView(this,
+ operation.state,
+ operation.progress,
+ operation.file_path,
+ operation.id);
+
+ update_map_[operation.file_path] = row_view;
+ scroll_content()->AddChildView(row_view);
+ item_list_changed = true;
+ }
+ }
+
+ // Remove items from the list that haven't been added or modified with this
+ // update batch.
+ std::set<base::FilePath> remove_set;
+ for (std::map<base::FilePath, RowView*>::iterator update_iter =
+ update_map_.begin();
+ update_iter != update_map_.end(); ++update_iter) {
+ if (new_set.find(update_iter->first) == new_set.end()) {
+ remove_set.insert(update_iter->first);
+ }
+ }
+
+ for (std::set<base::FilePath>::iterator removed_iter = remove_set.begin();
+ removed_iter != remove_set.end(); ++removed_iter) {
+ delete update_map_[*removed_iter];
+ update_map_.erase(*removed_iter);
+ item_list_changed = true;
+ }
+
+ if (item_list_changed)
+ scroller()->Layout();
+
+ // Close the details if there is really nothing to show there anymore.
+ if (new_set.empty() && GetWidget())
+ GetWidget()->Close();
+ }
+
+ void AppendSettings() {
+ if (settings_)
+ return;
+
+ HoverHighlightView* container = new HoverHighlightView(this);
+ container->AddLabel(ui::ResourceBundle::GetSharedInstance().
+ GetLocalizedString(IDS_ASH_STATUS_TRAY_DRIVE_SETTINGS),
+ gfx::Font::NORMAL);
+ AddChildView(container);
+ settings_ = container;
+ }
+
+ // Overridden from ViewClickListener.
+ virtual void OnViewClicked(views::View* sender) OVERRIDE {
+ SystemTrayDelegate* delegate = Shell::GetInstance()->system_tray_delegate();
+ if (sender == footer()->content()) {
+ owner()->system_tray()->ShowDefaultView(BUBBLE_USE_EXISTING);
+ } else if (sender == settings_) {
+ delegate->ShowDriveSettings();
+ }
+ }
+
+ // Maps operation entries to their file paths.
+ std::map<base::FilePath, RowView*> update_map_;
+ views::View* settings_;
+ gfx::ImageSkia* in_progress_img_;
+ gfx::ImageSkia* done_img_;
+ gfx::ImageSkia* failed_img_;
+
+ DISALLOW_COPY_AND_ASSIGN(DriveDetailedView);
+};
+
+} // namespace tray
+
+TrayDrive::TrayDrive(SystemTray* system_tray) :
+ TrayImageItem(system_tray, IDR_AURA_UBER_TRAY_DRIVE_LIGHT),
+ default_(NULL),
+ detailed_(NULL) {
+ Shell::GetInstance()->system_tray_notifier()->AddDriveObserver(this);
+}
+
+TrayDrive::~TrayDrive() {
+ Shell::GetInstance()->system_tray_notifier()->RemoveDriveObserver(this);
+}
+
+bool TrayDrive::GetInitialVisibility() {
+ return false;
+}
+
+views::View* TrayDrive::CreateDefaultView(user::LoginStatus status) {
+ DCHECK(!default_);
+
+ if (status != user::LOGGED_IN_USER && status != user::LOGGED_IN_OWNER)
+ return NULL;
+
+ // If the list is empty AND the tray icon is invisible (= not in the margin
+ // duration of delayed item hiding), don't show the item.
+ scoped_ptr<DriveOperationStatusList> list(GetCurrentOperationList());
+ if (list->empty() && !tray_view()->visible())
+ return NULL;
+
+ default_ = new tray::DriveDefaultView(this, list.get());
+ return default_;
+}
+
+views::View* TrayDrive::CreateDetailedView(user::LoginStatus status) {
+ DCHECK(!detailed_);
+
+ if (status != user::LOGGED_IN_USER && status != user::LOGGED_IN_OWNER)
+ return NULL;
+
+ // If the list is empty AND the tray icon is invisible (= not in the margin
+ // duration of delayed item hiding), don't show the item.
+ scoped_ptr<DriveOperationStatusList> list(GetCurrentOperationList());
+ if (list->empty() && !tray_view()->visible())
+ return NULL;
+
+ detailed_ = new tray::DriveDetailedView(this, list.get());
+ return detailed_;
+}
+
+void TrayDrive::DestroyDefaultView() {
+ default_ = NULL;
+}
+
+void TrayDrive::DestroyDetailedView() {
+ detailed_ = NULL;
+}
+
+void TrayDrive::UpdateAfterLoginStatusChange(user::LoginStatus status) {
+ if (status == user::LOGGED_IN_USER || status == user::LOGGED_IN_OWNER)
+ return;
+
+ tray_view()->SetVisible(false);
+ DestroyDefaultView();
+ DestroyDetailedView();
+}
+
+void TrayDrive::OnDriveJobUpdated(const DriveOperationStatus& status) {
+ // The Drive job list manager changed its notification interface *not* to send
+ // the whole list of operations each time, to clarify which operation is
+ // updated and to reduce redundancy.
+ //
+ // TrayDrive should be able to benefit from the change, but for now, to
+ // incrementally migrate to the new way with minimum diffs, we still get the
+ // list of operations each time the event is fired.
+ // TODO(kinaba) http://crbug.com/128079 clean it up.
+ scoped_ptr<DriveOperationStatusList> list(GetCurrentOperationList());
+ bool is_new_item = true;
+ for (size_t i = 0; i < list->size(); ++i) {
+ if ((*list)[i].id == status.id) {
+ (*list)[i] = status;
+ is_new_item = false;
+ break;
+ }
+ }
+ if (is_new_item)
+ list->push_back(status);
+
+ // Check if all the operations are in the finished state.
+ bool all_jobs_finished = true;
+ for (size_t i = 0; i < list->size(); ++i) {
+ if ((*list)[i].state != DriveOperationStatus::OPERATION_COMPLETED &&
+ (*list)[i].state != DriveOperationStatus::OPERATION_FAILED) {
+ all_jobs_finished = false;
+ break;
+ }
+ }
+
+ if (all_jobs_finished) {
+ // If all the jobs ended, the tray item will be hidden after a certain
+ // amount of delay. This is to avoid flashes between sequentially executed
+ // Drive operations (see crbug/165679).
+ hide_timer_.Start(FROM_HERE,
+ base::TimeDelta::FromMilliseconds(kHideDelayInMs),
+ this,
+ &TrayDrive::HideIfNoOperations);
+ return;
+ }
+
+ // If the list is non-empty, stop the hiding timer (if any).
+ hide_timer_.Stop();
+
+ tray_view()->SetVisible(true);
+ if (default_)
+ default_->Update(list.get());
+ if (detailed_)
+ detailed_->Update(list.get());
+}
+
+void TrayDrive::HideIfNoOperations() {
+ DriveOperationStatusList empty_list;
+
+ tray_view()->SetVisible(false);
+ if (default_)
+ default_->Update(&empty_list);
+ if (detailed_)
+ detailed_->Update(&empty_list);
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/system/drive/tray_drive.h b/chromium/ash/system/drive/tray_drive.h
new file mode 100644
index 00000000000..6550fdeda8e
--- /dev/null
+++ b/chromium/ash/system/drive/tray_drive.h
@@ -0,0 +1,58 @@
+// Copyright (c) 2012 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 ASH_SYSTEM_DRIVE_TRAY_DRIVE_H_
+#define ASH_SYSTEM_DRIVE_TRAY_DRIVE_H_
+
+#include "ash/system/drive/drive_observer.h"
+#include "ash/system/tray/tray_image_item.h"
+#include "base/timer/timer.h"
+
+namespace views {
+class Label;
+}
+
+namespace ash {
+namespace internal {
+
+namespace tray {
+class DriveTrayView;
+class DriveDefaultView;
+class DriveDetailedView;
+}
+
+class TrayDrive : public TrayImageItem,
+ public DriveObserver {
+ public:
+ explicit TrayDrive(SystemTray* system_tray);
+ virtual ~TrayDrive();
+
+ private:
+ // Overridden from TrayImageItem.
+ virtual bool GetInitialVisibility() OVERRIDE;
+
+ // Overridden from SystemTrayItem.
+ virtual views::View* CreateDefaultView(user::LoginStatus status) OVERRIDE;
+ virtual views::View* CreateDetailedView(user::LoginStatus status) OVERRIDE;
+ virtual void DestroyDefaultView() OVERRIDE;
+ virtual void DestroyDetailedView() OVERRIDE;
+ virtual void UpdateAfterLoginStatusChange(user::LoginStatus status) OVERRIDE;
+
+ // Overridden from DriveObserver.
+ virtual void OnDriveJobUpdated(const DriveOperationStatus& status) OVERRIDE;
+
+ // Delayed hiding of the tray item after encountering an empty operation list.
+ void HideIfNoOperations();
+
+ tray::DriveDefaultView* default_;
+ tray::DriveDetailedView* detailed_;
+ base::OneShotTimer<TrayDrive> hide_timer_;
+
+ DISALLOW_COPY_AND_ASSIGN(TrayDrive);
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_SYSTEM_DRIVE_TRAY_DRIVE_H_
diff --git a/chromium/ash/system/ime/ime_observer.h b/chromium/ash/system/ime/ime_observer.h
new file mode 100644
index 00000000000..aee74aea2ba
--- /dev/null
+++ b/chromium/ash/system/ime/ime_observer.h
@@ -0,0 +1,22 @@
+// Copyright (c) 2012 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 ASH_SYSTEM_IME_IME_OBSERVER_H_
+#define ASH_SYSTEM_IME_IME_OBSERVER_H_
+
+namespace ash {
+
+class IMEObserver {
+ public:
+ virtual ~IMEObserver() {}
+
+ // Notify the observer that the IME state has changed, and should be
+ // refreshed. |show_message| indicates whether the user should be alerted of
+ // the change.
+ virtual void OnIMERefresh(bool show_message) = 0;
+};
+
+} // namespace ash
+
+#endif // ASH_SYSTEM_IME_IME_OBSERVER_H_
diff --git a/chromium/ash/system/ime/tray_ime.cc b/chromium/ash/system/ime/tray_ime.cc
new file mode 100644
index 00000000000..6fac8fddb55
--- /dev/null
+++ b/chromium/ash/system/ime/tray_ime.cc
@@ -0,0 +1,311 @@
+// Copyright (c) 2012 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.
+
+#include "ash/system/ime/tray_ime.h"
+
+#include <vector>
+
+#include "ash/root_window_controller.h"
+#include "ash/shelf/shelf_widget.h"
+#include "ash/shell.h"
+#include "ash/system/tray/hover_highlight_view.h"
+#include "ash/system/tray/system_tray.h"
+#include "ash/system/tray/system_tray_delegate.h"
+#include "ash/system/tray/system_tray_notifier.h"
+#include "ash/system/tray/tray_constants.h"
+#include "ash/system/tray/tray_details_view.h"
+#include "ash/system/tray/tray_item_more.h"
+#include "ash/system/tray/tray_item_view.h"
+#include "ash/system/tray/tray_utils.h"
+#include "base/logging.h"
+#include "base/strings/utf_string_conversions.h"
+#include "grit/ash_resources.h"
+#include "grit/ash_strings.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "ui/gfx/font.h"
+#include "ui/gfx/image/image.h"
+#include "ui/message_center/message_center.h"
+#include "ui/message_center/notification.h"
+#include "ui/message_center/notification_delegate.h"
+#include "ui/views/controls/label.h"
+#include "ui/views/layout/box_layout.h"
+#include "ui/views/widget/widget.h"
+
+using message_center::Notification;
+
+namespace {
+
+const char kIMENotificationId[] = "chrome://settings/ime";
+
+} // namespace
+
+namespace ash {
+namespace internal {
+namespace tray {
+
+class IMEDefaultView : public TrayItemMore {
+ public:
+ explicit IMEDefaultView(SystemTrayItem* owner)
+ : TrayItemMore(owner, true) {
+ ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance();
+
+ SetImage(bundle.GetImageNamed(
+ IDR_AURA_UBER_TRAY_IME).ToImageSkia());
+
+ IMEInfo info;
+ Shell::GetInstance()->system_tray_delegate()->GetCurrentIME(&info);
+ UpdateLabel(info);
+ }
+
+ virtual ~IMEDefaultView() {}
+
+ void UpdateLabel(const IMEInfo& info) {
+ SetLabel(info.name);
+ SetAccessibleName(info.name);
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(IMEDefaultView);
+};
+
+class IMEDetailedView : public TrayDetailsView,
+ public ViewClickListener {
+ public:
+ IMEDetailedView(SystemTrayItem* owner, user::LoginStatus login)
+ : TrayDetailsView(owner),
+ login_(login) {
+ SystemTrayDelegate* delegate = Shell::GetInstance()->system_tray_delegate();
+ IMEInfoList list;
+ delegate->GetAvailableIMEList(&list);
+ IMEPropertyInfoList property_list;
+ delegate->GetCurrentIMEProperties(&property_list);
+ Update(list, property_list);
+ }
+
+ virtual ~IMEDetailedView() {}
+
+ void Update(const IMEInfoList& list,
+ const IMEPropertyInfoList& property_list) {
+ Reset();
+
+ AppendIMEList(list);
+ if (!property_list.empty())
+ AppendIMEProperties(property_list);
+ if (login_ != user::LOGGED_IN_NONE && login_ != user::LOGGED_IN_LOCKED)
+ AppendSettings();
+ AppendHeaderEntry();
+
+ Layout();
+ SchedulePaint();
+ }
+
+ private:
+ void AppendHeaderEntry() {
+ CreateSpecialRow(IDS_ASH_STATUS_TRAY_IME, this);
+ }
+
+ void AppendIMEList(const IMEInfoList& list) {
+ ime_map_.clear();
+ CreateScrollableList();
+ for (size_t i = 0; i < list.size(); i++) {
+ HoverHighlightView* container = new HoverHighlightView(this);
+ container->AddLabel(list[i].name,
+ list[i].selected ? gfx::Font::BOLD : gfx::Font::NORMAL);
+ scroll_content()->AddChildView(container);
+ ime_map_[container] = list[i].id;
+ }
+ }
+
+ void AppendIMEProperties(const IMEPropertyInfoList& property_list) {
+ property_map_.clear();
+ for (size_t i = 0; i < property_list.size(); i++) {
+ HoverHighlightView* container = new HoverHighlightView(this);
+ container->AddLabel(
+ property_list[i].name,
+ property_list[i].selected ? gfx::Font::BOLD : gfx::Font::NORMAL);
+ if (i == 0)
+ container->set_border(views::Border::CreateSolidSidedBorder(1, 0, 0, 0,
+ kBorderLightColor));
+ scroll_content()->AddChildView(container);
+ property_map_[container] = property_list[i].key;
+ }
+ }
+
+ void AppendSettings() {
+ HoverHighlightView* container = new HoverHighlightView(this);
+ container->AddLabel(ui::ResourceBundle::GetSharedInstance().
+ GetLocalizedString(IDS_ASH_STATUS_TRAY_IME_SETTINGS),
+ gfx::Font::NORMAL);
+ AddChildView(container);
+ settings_ = container;
+ }
+
+ // Overridden from ViewClickListener.
+ virtual void OnViewClicked(views::View* sender) OVERRIDE {
+ SystemTrayDelegate* delegate = Shell::GetInstance()->system_tray_delegate();
+ if (sender == footer()->content()) {
+ owner()->system_tray()->ShowDefaultView(BUBBLE_USE_EXISTING);
+ } else if (sender == settings_) {
+ delegate->ShowIMESettings();
+ } else {
+ std::map<views::View*, std::string>::const_iterator ime_find;
+ ime_find = ime_map_.find(sender);
+ if (ime_find != ime_map_.end()) {
+ std::string ime_id = ime_find->second;
+ delegate->SwitchIME(ime_id);
+ GetWidget()->Close();
+ } else {
+ std::map<views::View*, std::string>::const_iterator prop_find;
+ prop_find = property_map_.find(sender);
+ if (prop_find != property_map_.end()) {
+ const std::string key = prop_find->second;
+ delegate->ActivateIMEProperty(key);
+ GetWidget()->Close();
+ }
+ }
+ }
+ }
+
+ user::LoginStatus login_;
+
+ std::map<views::View*, std::string> ime_map_;
+ std::map<views::View*, std::string> property_map_;
+ views::View* settings_;
+
+ DISALLOW_COPY_AND_ASSIGN(IMEDetailedView);
+};
+
+} // namespace tray
+
+TrayIME::TrayIME(SystemTray* system_tray)
+ : SystemTrayItem(system_tray),
+ tray_label_(NULL),
+ default_(NULL),
+ detailed_(NULL),
+ message_shown_(false) {
+ Shell::GetInstance()->system_tray_notifier()->AddIMEObserver(this);
+}
+
+TrayIME::~TrayIME() {
+ Shell::GetInstance()->system_tray_notifier()->RemoveIMEObserver(this);
+ message_center::MessageCenter::Get()->RemoveNotification(
+ kIMENotificationId, false /* by_user */);
+}
+
+void TrayIME::UpdateTrayLabel(const IMEInfo& current, size_t count) {
+ if (tray_label_) {
+ if (current.third_party) {
+ tray_label_->label()->SetText(current.short_name + UTF8ToUTF16("*"));
+ } else {
+ tray_label_->label()->SetText(current.short_name);
+ }
+ tray_label_->SetVisible(count > 1);
+ SetTrayLabelItemBorder(tray_label_, system_tray()->shelf_alignment());
+ tray_label_->Layout();
+ }
+}
+
+void TrayIME::UpdateOrCreateNotification() {
+ message_center::MessageCenter* message_center =
+ message_center::MessageCenter::Get();
+
+ if (!message_center->HasNotification(kIMENotificationId) && message_shown_)
+ return;
+
+ SystemTrayDelegate* delegate = Shell::GetInstance()->system_tray_delegate();
+ IMEInfo current;
+ delegate->GetCurrentIME(&current);
+
+ scoped_ptr<Notification> notification(new Notification(
+ message_center::NOTIFICATION_TYPE_SIMPLE,
+ kIMENotificationId,
+ // TODO(zork): Use IDS_ASH_STATUS_TRAY_THIRD_PARTY_IME_TURNED_ON_BUBBLE
+ // for third party IMEs
+ l10n_util::GetStringFUTF16(
+ IDS_ASH_STATUS_TRAY_IME_TURNED_ON_BUBBLE,
+ current.medium_name),
+ base::string16(), // message
+ gfx::Image(), // icon
+ base::string16(), // display_source
+ "", // extension_id
+ message_center::RichNotificationData(),
+ new message_center::HandleNotificationClickedDelegate(
+ base::Bind(&TrayIME::PopupDetailedView,
+ base::Unretained(this), 0, true))));
+ message_center->AddNotification(notification.Pass());
+ message_shown_ = true;
+}
+
+views::View* TrayIME::CreateTrayView(user::LoginStatus status) {
+ CHECK(tray_label_ == NULL);
+ tray_label_ = new TrayItemView(this);
+ tray_label_->CreateLabel();
+ SetupLabelForTray(tray_label_->label());
+ // Hide IME tray when it is created, it will be updated when it is notified
+ // for IME refresh event.
+ tray_label_->SetVisible(false);
+ return tray_label_;
+}
+
+views::View* TrayIME::CreateDefaultView(user::LoginStatus status) {
+ SystemTrayDelegate* delegate = Shell::GetInstance()->system_tray_delegate();
+ IMEInfoList list;
+ IMEPropertyInfoList property_list;
+ delegate->GetAvailableIMEList(&list);
+ delegate->GetCurrentIMEProperties(&property_list);
+ if (list.size() <= 1 && property_list.size() <= 1)
+ return NULL;
+ CHECK(default_ == NULL);
+ default_ = new tray::IMEDefaultView(this);
+ return default_;
+}
+
+views::View* TrayIME::CreateDetailedView(user::LoginStatus status) {
+ CHECK(detailed_ == NULL);
+ detailed_ = new tray::IMEDetailedView(this, status);
+ return detailed_;
+}
+
+void TrayIME::DestroyTrayView() {
+ tray_label_ = NULL;
+}
+
+void TrayIME::DestroyDefaultView() {
+ default_ = NULL;
+}
+
+void TrayIME::DestroyDetailedView() {
+ detailed_ = NULL;
+}
+
+void TrayIME::UpdateAfterLoginStatusChange(user::LoginStatus status) {
+}
+
+void TrayIME::UpdateAfterShelfAlignmentChange(ShelfAlignment alignment) {
+ SetTrayLabelItemBorder(tray_label_, alignment);
+}
+
+void TrayIME::OnIMERefresh(bool show_message) {
+ SystemTrayDelegate* delegate = Shell::GetInstance()->system_tray_delegate();
+ IMEInfoList list;
+ IMEInfo current;
+ IMEPropertyInfoList property_list;
+ delegate->GetCurrentIME(&current);
+ delegate->GetAvailableIMEList(&list);
+ delegate->GetCurrentIMEProperties(&property_list);
+
+ UpdateTrayLabel(current, list.size());
+
+ if (default_)
+ default_->UpdateLabel(current);
+ if (detailed_)
+ detailed_->Update(list, property_list);
+
+ if (list.size() > 1 && show_message)
+ UpdateOrCreateNotification();
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/system/ime/tray_ime.h b/chromium/ash/system/ime/tray_ime.h
new file mode 100644
index 00000000000..b237ae15abc
--- /dev/null
+++ b/chromium/ash/system/ime/tray_ime.h
@@ -0,0 +1,70 @@
+// Copyright (c) 2012 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 ASH_SYSTEM_IME_TRAY_IME_H_
+#define ASH_SYSTEM_IME_TRAY_IME_H_
+
+#include "ash/system/ime/ime_observer.h"
+#include "ash/system/tray/system_tray_item.h"
+
+namespace views {
+class Label;
+}
+
+namespace ash {
+
+struct IMEInfo;
+
+namespace internal {
+
+namespace tray {
+class IMEDefaultView;
+class IMEDetailedView;
+class IMENotificationView;
+}
+
+class TrayItemView;
+
+class TrayIME : public SystemTrayItem,
+ public IMEObserver {
+ public:
+ explicit TrayIME(SystemTray* system_tray);
+ virtual ~TrayIME();
+
+ private:
+ void UpdateTrayLabel(const IMEInfo& info, size_t count);
+
+ // Update the content of the existing IME notification, or create a new one if
+ // necessary. IME notification should be created only once in a session, i.e.
+ // if an IME notification is created and removed already, it doesn't create a
+ // new one.
+ void UpdateOrCreateNotification();
+
+ // Overridden from SystemTrayItem.
+ virtual views::View* CreateTrayView(user::LoginStatus status) OVERRIDE;
+ virtual views::View* CreateDefaultView(user::LoginStatus status) OVERRIDE;
+ virtual views::View* CreateDetailedView(user::LoginStatus status) OVERRIDE;
+ virtual void DestroyTrayView() OVERRIDE;
+ virtual void DestroyDefaultView() OVERRIDE;
+ virtual void DestroyDetailedView() OVERRIDE;
+ virtual void UpdateAfterLoginStatusChange(user::LoginStatus status) OVERRIDE;
+ virtual void UpdateAfterShelfAlignmentChange(
+ ShelfAlignment alignment) OVERRIDE;
+
+ // Overridden from IMEObserver.
+ virtual void OnIMERefresh(bool show_message) OVERRIDE;
+
+ TrayItemView* tray_label_;
+ tray::IMEDefaultView* default_;
+ tray::IMEDetailedView* detailed_;
+
+ bool message_shown_;
+
+ DISALLOW_COPY_AND_ASSIGN(TrayIME);
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_SYSTEM_IME_TRAY_IME_H_
diff --git a/chromium/ash/system/keyboard_brightness/keyboard_brightness_control_delegate.h b/chromium/ash/system/keyboard_brightness/keyboard_brightness_control_delegate.h
new file mode 100644
index 00000000000..96edf84f4d5
--- /dev/null
+++ b/chromium/ash/system/keyboard_brightness/keyboard_brightness_control_delegate.h
@@ -0,0 +1,29 @@
+// Copyright (c) 2012 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 ASH_SYSTEM_KEYBOARD_BRIGHTNESS_KEYBOARD_BRIGHTNESS_CONTROL_DELEGATE_H_
+#define ASH_SYSTEM_KEYBOARD_BRIGHTNESS_KEYBOARD_BRIGHTNESS_CONTROL_DELEGATE_H_
+
+namespace ui {
+class Accelerator;
+} // namespace ui
+
+namespace ash {
+
+// Delegate for controlling the keyboard brightness.
+class KeyboardBrightnessControlDelegate {
+ public:
+ virtual ~KeyboardBrightnessControlDelegate() {}
+
+ // Handles an accelerator-driven request to decrease or increase the keyboard
+ // brightness. Returns true if the brightness is changed.
+ virtual bool HandleKeyboardBrightnessDown(
+ const ui::Accelerator& accelerator) = 0;
+ virtual bool HandleKeyboardBrightnessUp(
+ const ui::Accelerator& accelerator) = 0;
+};
+
+} // namespace ash
+
+#endif // ASH_SYSTEM_KEYBOARD_BRIGHTNESS_KEYBOARD_BRIGHTNESS_CONTROL_DELEGATE_H_
diff --git a/chromium/ash/system/locale/locale_notification_controller.cc b/chromium/ash/system/locale/locale_notification_controller.cc
new file mode 100644
index 00000000000..342c5a63525
--- /dev/null
+++ b/chromium/ash/system/locale/locale_notification_controller.cc
@@ -0,0 +1,124 @@
+// Copyright 2013 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.
+
+#include "ash/system/locale/locale_notification_controller.h"
+
+#include "ash/shell.h"
+#include "ash/system/tray/system_tray_notifier.h"
+#include "base/strings/string16.h"
+#include "grit/ash_strings.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/message_center/message_center.h"
+#include "ui/message_center/notification.h"
+#include "ui/message_center/notification_delegate.h"
+#include "ui/message_center/notification_types.h"
+
+using message_center::Notification;
+
+namespace ash {
+namespace internal {
+namespace {
+
+const char kLocaleChangeNotificationId[] = "chrome://settings/locale";
+
+class LocaleNotificationDelegate : public message_center::NotificationDelegate {
+ public:
+ explicit LocaleNotificationDelegate(LocaleObserver::Delegate* delegate);
+
+ protected:
+ virtual ~LocaleNotificationDelegate();
+
+ // message_center::NotificationDelegate overrides:
+ virtual void Display() OVERRIDE;
+ virtual void Error() OVERRIDE;
+ virtual void Close(bool by_user) OVERRIDE;
+ virtual bool HasClickedListener() OVERRIDE;
+ virtual void Click() OVERRIDE;
+ virtual void ButtonClick(int button_index) OVERRIDE;
+
+ private:
+ LocaleObserver::Delegate* delegate_;
+
+ DISALLOW_COPY_AND_ASSIGN(LocaleNotificationDelegate);
+};
+
+LocaleNotificationDelegate::LocaleNotificationDelegate(
+ LocaleObserver::Delegate* delegate)
+ : delegate_(delegate) {
+ DCHECK(delegate_);
+}
+
+LocaleNotificationDelegate::~LocaleNotificationDelegate() {
+}
+
+void LocaleNotificationDelegate::Display() {
+}
+
+void LocaleNotificationDelegate::Error() {
+}
+
+void LocaleNotificationDelegate::Close(bool by_user) {
+ delegate_->AcceptLocaleChange();
+}
+
+bool LocaleNotificationDelegate::HasClickedListener() {
+ return true;
+}
+
+void LocaleNotificationDelegate::Click() {
+ delegate_->AcceptLocaleChange();
+}
+
+void LocaleNotificationDelegate::ButtonClick(int button_index) {
+ DCHECK_EQ(0, button_index);
+ delegate_->RevertLocaleChange();
+}
+
+} // namespace
+
+LocaleNotificationController::LocaleNotificationController()
+ : delegate_(NULL) {
+ Shell::GetInstance()->system_tray_notifier()->AddLocaleObserver(this);
+}
+
+LocaleNotificationController::~LocaleNotificationController() {
+ Shell::GetInstance()->system_tray_notifier()->RemoveLocaleObserver(this);
+}
+
+void LocaleNotificationController::OnLocaleChanged(
+ LocaleObserver::Delegate* delegate,
+ const std::string& cur_locale,
+ const std::string& from_locale,
+ const std::string& to_locale) {
+ if (!delegate)
+ return;
+
+ base::string16 from = l10n_util::GetDisplayNameForLocale(
+ from_locale, cur_locale, true);
+ base::string16 to = l10n_util::GetDisplayNameForLocale(
+ to_locale, cur_locale, true);
+
+ message_center::RichNotificationData optional;
+ optional.buttons.push_back(message_center::ButtonInfo(
+ l10n_util::GetStringFUTF16(
+ IDS_ASH_STATUS_TRAY_LOCALE_REVERT_MESSAGE, from)));
+ optional.never_timeout = true;
+
+ scoped_ptr<Notification> notification(new Notification(
+ message_center::NOTIFICATION_TYPE_SIMPLE,
+ kLocaleChangeNotificationId,
+ l10n_util::GetStringFUTF16(
+ IDS_ASH_STATUS_TRAY_LOCALE_CHANGE_MESSAGE, from, to),
+ base::string16() /* message */,
+ // TODO(mukai): add the icon here. see crbug.com/262393
+ gfx::Image() /* icon */,
+ base::string16() /* display_source */,
+ std::string() /* extension_id */,
+ optional,
+ new LocaleNotificationDelegate(delegate)));
+ message_center::MessageCenter::Get()->AddNotification(notification.Pass());
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/system/locale/locale_notification_controller.h b/chromium/ash/system/locale/locale_notification_controller.h
new file mode 100644
index 00000000000..d23895c8206
--- /dev/null
+++ b/chromium/ash/system/locale/locale_notification_controller.h
@@ -0,0 +1,41 @@
+// Copyright 2013 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 ASH_SYSTEM_LOCALE_LOCALE_NOTIFICATION_CONTROLLER_H_
+#define ASH_SYSTEM_LOCALE_LOCALE_NOTIFICATION_CONTROLLER_H_
+
+#include <string>
+
+#include "ash/system/locale/locale_observer.h"
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+
+namespace ash {
+namespace internal {
+
+// Observes the locale change and creates rich notification for the change.
+class LocaleNotificationController : public LocaleObserver {
+ public:
+ LocaleNotificationController();
+ virtual ~LocaleNotificationController();
+
+ private:
+ // Overridden from LocaleObserver.
+ virtual void OnLocaleChanged(LocaleObserver::Delegate* delegate,
+ const std::string& cur_locale,
+ const std::string& from_locale,
+ const std::string& to_locale) OVERRIDE;
+
+ LocaleObserver::Delegate* delegate_;
+ std::string cur_locale_;
+ std::string from_locale_;
+ std::string to_locale_;
+
+ DISALLOW_COPY_AND_ASSIGN(LocaleNotificationController);
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_SYSTEM_LOCALE_LOCALE_NOTIFICATION_CONTROLLER_H_
diff --git a/chromium/ash/system/locale/locale_observer.h b/chromium/ash/system/locale/locale_observer.h
new file mode 100644
index 00000000000..939bd60faa9
--- /dev/null
+++ b/chromium/ash/system/locale/locale_observer.h
@@ -0,0 +1,32 @@
+// Copyright (c) 2012 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 ASH_SYSTEM_LOCALE_LOCALE_OBSERVER_H_
+#define ASH_SYSTEM_LOCALE_LOCALE_OBSERVER_H_
+
+#include "ash/ash_export.h"
+
+namespace ash {
+
+class ASH_EXPORT LocaleObserver {
+ public:
+ class Delegate {
+ public:
+ virtual ~Delegate() {}
+
+ virtual void AcceptLocaleChange() = 0;
+ virtual void RevertLocaleChange() = 0;
+ };
+
+ virtual ~LocaleObserver() {}
+
+ virtual void OnLocaleChanged(Delegate* delegate,
+ const std::string& cur_locale,
+ const std::string& from_locale,
+ const std::string& to_locale) = 0;
+};
+
+} // namespace ash
+
+#endif // ASH_SYSTEM_LOCALE_LOCALE_OBSERVER_H_
diff --git a/chromium/ash/system/logout_button/logout_button_observer.h b/chromium/ash/system/logout_button/logout_button_observer.h
new file mode 100644
index 00000000000..9234c7d758d
--- /dev/null
+++ b/chromium/ash/system/logout_button/logout_button_observer.h
@@ -0,0 +1,22 @@
+// Copyright (c) 2012 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 ASH_SYSTEM_LOGOUT_BUTTON_LOGOUT_BUTTON_OBSERVER_H_
+#define ASH_SYSTEM_LOGOUT_BUTTON_LOGOUT_BUTTON_OBSERVER_H_
+
+namespace ash {
+
+// Observer for the value of the kShowLogoutButtonInTray pref that determines
+// whether a logout button should be shown in the system tray during a session.
+class LogoutButtonObserver {
+ public:
+ virtual ~LogoutButtonObserver() {}
+
+ // Called when the value of the kShowLogoutButtonInTray pref changes.
+ virtual void OnShowLogoutButtonInTrayChanged(bool show) = 0;
+};
+
+} // namespace ash
+
+#endif // ASH_SYSTEM_LOGOUT_BUTTON_LOGOUT_BUTTON_OBSERVER_H_
diff --git a/chromium/ash/system/logout_button/logout_button_tray.cc b/chromium/ash/system/logout_button/logout_button_tray.cc
new file mode 100644
index 00000000000..f62921f1a08
--- /dev/null
+++ b/chromium/ash/system/logout_button/logout_button_tray.cc
@@ -0,0 +1,158 @@
+// Copyright 2013 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.
+
+#include "ash/system/logout_button/logout_button_tray.h"
+
+#include "ash/shelf/shelf_types.h"
+#include "ash/shell.h"
+#include "ash/system/status_area_widget.h"
+#include "ash/system/tray/system_tray_delegate.h"
+#include "ash/system/tray/system_tray_notifier.h"
+#include "ash/system/tray/tray_constants.h"
+#include "ash/system/tray/tray_utils.h"
+#include "base/logging.h"
+#include "grit/ash_resources.h"
+#include "third_party/skia/include/core/SkColor.h"
+#include "ui/base/events/event.h"
+#include "ui/gfx/font.h"
+#include "ui/gfx/insets.h"
+#include "ui/gfx/size.h"
+#include "ui/views/bubble/tray_bubble_view.h"
+#include "ui/views/controls/button/label_button.h"
+#include "ui/views/controls/button/label_button_border.h"
+#include "ui/views/painter.h"
+
+namespace ash {
+
+namespace internal {
+
+namespace {
+
+const int kLogoutButtonHorizontalExtraPadding = 7;
+
+const int kLogoutButtonNormalImages[] = {
+ IDR_AURA_UBER_TRAY_LOGOUT_BUTTON_NORMAL_TOP_LEFT,
+ IDR_AURA_UBER_TRAY_LOGOUT_BUTTON_NORMAL_TOP,
+ IDR_AURA_UBER_TRAY_LOGOUT_BUTTON_NORMAL_TOP_RIGHT,
+ IDR_AURA_UBER_TRAY_LOGOUT_BUTTON_NORMAL_LEFT,
+ IDR_AURA_UBER_TRAY_LOGOUT_BUTTON_NORMAL_CENTER,
+ IDR_AURA_UBER_TRAY_LOGOUT_BUTTON_NORMAL_RIGHT,
+ IDR_AURA_UBER_TRAY_LOGOUT_BUTTON_NORMAL_BOTTOM_LEFT,
+ IDR_AURA_UBER_TRAY_LOGOUT_BUTTON_NORMAL_BOTTOM,
+ IDR_AURA_UBER_TRAY_LOGOUT_BUTTON_NORMAL_BOTTOM_RIGHT
+};
+
+const int kLogoutButtonPushedImages[] = {
+ IDR_AURA_UBER_TRAY_LOGOUT_BUTTON_PUSHED_TOP_LEFT,
+ IDR_AURA_UBER_TRAY_LOGOUT_BUTTON_PUSHED_TOP,
+ IDR_AURA_UBER_TRAY_LOGOUT_BUTTON_PUSHED_TOP_RIGHT,
+ IDR_AURA_UBER_TRAY_LOGOUT_BUTTON_PUSHED_LEFT,
+ IDR_AURA_UBER_TRAY_LOGOUT_BUTTON_PUSHED_CENTER,
+ IDR_AURA_UBER_TRAY_LOGOUT_BUTTON_PUSHED_RIGHT,
+ IDR_AURA_UBER_TRAY_LOGOUT_BUTTON_PUSHED_BOTTOM_LEFT,
+ IDR_AURA_UBER_TRAY_LOGOUT_BUTTON_PUSHED_BOTTOM,
+ IDR_AURA_UBER_TRAY_LOGOUT_BUTTON_PUSHED_BOTTOM_RIGHT
+};
+
+class LogoutButton : public views::LabelButton {
+ public:
+ LogoutButton(views::ButtonListener* listener);
+ virtual ~LogoutButton();
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(LogoutButton);
+};
+
+} // namespace
+
+LogoutButton::LogoutButton(views::ButtonListener* listener)
+ : views::LabelButton(listener, base::string16()) {
+ SetupLabelForTray(label());
+ SetFont(GetFont().DeriveFont(0, gfx::Font::NORMAL));
+ for (size_t state = 0; state < views::Button::STATE_COUNT; ++state)
+ SetTextColor(static_cast<views::Button::ButtonState>(state), SK_ColorWHITE);
+
+ views::LabelButtonBorder* border =
+ new views::LabelButtonBorder(views::Button::STYLE_TEXTBUTTON);
+ border->SetPainter(false, views::Button::STATE_NORMAL,
+ views::Painter::CreateImageGridPainter(kLogoutButtonNormalImages));
+ border->SetPainter(false, views::Button::STATE_HOVERED,
+ views::Painter::CreateImageGridPainter(kLogoutButtonNormalImages));
+ border->SetPainter(false, views::Button::STATE_PRESSED,
+ views::Painter::CreateImageGridPainter(kLogoutButtonPushedImages));
+ gfx::Insets insets = border->GetInsets();
+ insets += gfx::Insets(0, kLogoutButtonHorizontalExtraPadding,
+ 0, kLogoutButtonHorizontalExtraPadding);
+ border->set_insets(insets);
+ set_border(border);
+ set_animate_on_state_change(false);
+
+ set_min_size(gfx::Size(0, GetShelfItemHeight()));
+}
+
+LogoutButton::~LogoutButton() {
+}
+
+LogoutButtonTray::LogoutButtonTray(StatusAreaWidget* status_area_widget)
+ : TrayBackgroundView(status_area_widget),
+ button_(NULL),
+ login_status_(user::LOGGED_IN_NONE),
+ show_logout_button_in_tray_(false) {
+ button_ = new LogoutButton(this);
+ tray_container()->AddChildView(button_);
+ tray_container()->set_border(NULL);
+ Shell::GetInstance()->system_tray_notifier()->AddLogoutButtonObserver(this);
+}
+
+LogoutButtonTray::~LogoutButtonTray() {
+ Shell::GetInstance()->system_tray_notifier()->
+ RemoveLogoutButtonObserver(this);
+}
+
+void LogoutButtonTray::SetShelfAlignment(ShelfAlignment alignment) {
+ TrayBackgroundView::SetShelfAlignment(alignment);
+ tray_container()->set_border(NULL);
+}
+
+base::string16 LogoutButtonTray::GetAccessibleNameForTray() {
+ return button_->GetText();
+}
+
+void LogoutButtonTray::HideBubbleWithView(
+ const views::TrayBubbleView* bubble_view) {
+}
+
+bool LogoutButtonTray::ClickedOutsideBubble() {
+ return false;
+}
+
+void LogoutButtonTray::OnShowLogoutButtonInTrayChanged(bool show) {
+ show_logout_button_in_tray_ = show;
+ UpdateVisibility();
+}
+
+void LogoutButtonTray::ButtonPressed(views::Button* sender,
+ const ui::Event& event) {
+ DCHECK_EQ(sender, button_);
+ Shell::GetInstance()->system_tray_delegate()->SignOut();
+}
+
+void LogoutButtonTray::UpdateAfterLoginStatusChange(
+ user::LoginStatus login_status) {
+ login_status_ = login_status;
+ const base::string16 title =
+ GetLocalizedSignOutStringForStatus(login_status, false);
+ button_->SetText(title);
+ button_->SetAccessibleName(title);
+ UpdateVisibility();
+}
+
+void LogoutButtonTray::UpdateVisibility() {
+ SetVisible(show_logout_button_in_tray_ &&
+ login_status_ != user::LOGGED_IN_NONE &&
+ login_status_ != user::LOGGED_IN_LOCKED);
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/system/logout_button/logout_button_tray.h b/chromium/ash/system/logout_button/logout_button_tray.h
new file mode 100644
index 00000000000..5683e5e2654
--- /dev/null
+++ b/chromium/ash/system/logout_button/logout_button_tray.h
@@ -0,0 +1,62 @@
+// Copyright 2013 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 ASH_SYSTEM_LOGOUT_BUTTON_LOGOUT_BUTTON_TRAY_H_
+#define ASH_SYSTEM_LOGOUT_BUTTON_LOGOUT_BUTTON_TRAY_H_
+
+#include "ash/system/logout_button/logout_button_observer.h"
+#include "ash/system/tray/tray_background_view.h"
+#include "ash/system/user/login_status.h"
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "ui/views/controls/button/button.h"
+
+namespace views {
+class LabelButton;
+}
+
+namespace ash {
+namespace internal {
+
+class StatusAreaWidget;
+
+// Adds a logout button to the launcher's status area if enabled by the
+// kShowLogoutButtonInTray pref.
+class LogoutButtonTray : public TrayBackgroundView,
+ public LogoutButtonObserver,
+ public views::ButtonListener {
+ public:
+ explicit LogoutButtonTray(StatusAreaWidget* status_area_widget);
+ virtual ~LogoutButtonTray();
+
+ // TrayBackgroundView:
+ virtual void SetShelfAlignment(ShelfAlignment alignment) OVERRIDE;
+ virtual base::string16 GetAccessibleNameForTray() OVERRIDE;
+ virtual void HideBubbleWithView(
+ const views::TrayBubbleView* bubble_view) OVERRIDE;
+ virtual bool ClickedOutsideBubble() OVERRIDE;
+
+ // LogoutButtonObserver:
+ virtual void OnShowLogoutButtonInTrayChanged(bool show) OVERRIDE;
+
+ // views::ButtonListener:
+ virtual void ButtonPressed(views::Button* sender,
+ const ui::Event& event) OVERRIDE;
+
+ void UpdateAfterLoginStatusChange(user::LoginStatus login_status);
+
+ private:
+ void UpdateVisibility();
+
+ views::LabelButton* button_; // Not owned.
+ user::LoginStatus login_status_;
+ bool show_logout_button_in_tray_;
+
+ DISALLOW_COPY_AND_ASSIGN(LogoutButtonTray);
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_SYSTEM_LOGOUT_BUTTON_LOGOUT_BUTTON_TRAY_H_
diff --git a/chromium/ash/system/monitor/tray_monitor.cc b/chromium/ash/system/monitor/tray_monitor.cc
new file mode 100644
index 00000000000..eb9a4dbb110
--- /dev/null
+++ b/chromium/ash/system/monitor/tray_monitor.cc
@@ -0,0 +1,98 @@
+// Copyright (c) 2012 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.
+
+#include "ash/system/monitor/tray_monitor.h"
+
+#include "ash/system/tray/tray_item_view.h"
+#include "base/process/memory.h"
+#include "base/process/process_metrics.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "content/public/browser/gpu_data_manager.h"
+#include "ui/base/text/bytes_formatting.h"
+#include "ui/views/border.h"
+#include "ui/views/controls/label.h"
+
+namespace {
+const int kRefreshTimeoutMs = 1000;
+}
+
+namespace ash {
+namespace internal {
+
+TrayMonitor::TrayMonitor(SystemTray* system_tray)
+ : SystemTrayItem(system_tray),
+ label_(NULL) {
+ refresh_timer_.Start(FROM_HERE,
+ base::TimeDelta::FromMilliseconds(kRefreshTimeoutMs),
+ this, &TrayMonitor::OnTimer);
+}
+
+TrayMonitor::~TrayMonitor() {
+ label_ = NULL;
+}
+
+views::View* TrayMonitor::CreateTrayView(user::LoginStatus status) {
+ TrayItemView* view = new TrayItemView(this);
+ view->CreateLabel();
+ label_ = view->label();
+ label_->SetAutoColorReadabilityEnabled(false);
+ label_->SetEnabledColor(SK_ColorWHITE);
+ label_->SetBackgroundColor(SkColorSetARGB(0, 255, 255, 255));
+ label_->SetShadowColors(SkColorSetARGB(64, 0, 0, 0),
+ SkColorSetARGB(64, 0, 0, 0));
+ label_->SetShadowOffset(0, 1);
+ label_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
+ label_->SetFont(label_->font().DeriveFont(-2));
+ return view;
+}
+
+void TrayMonitor::DestroyTrayView() {
+ label_ = NULL;
+}
+
+void TrayMonitor::OnTimer() {
+ content::GpuDataManager::GetGpuProcessHandlesCallback callback =
+ base::Bind(&TrayMonitor::OnGotHandles, base::Unretained(this));
+ refresh_timer_.Stop();
+ content::GpuDataManager::GetInstance()->GetGpuProcessHandles(callback);
+}
+
+void TrayMonitor::OnGotHandles(const std::list<base::ProcessHandle>& handles) {
+ base::SystemMemoryInfoKB mem_info;
+ base::GetSystemMemoryInfo(&mem_info);
+ std::string output;
+ base::string16 free_bytes =
+ ui::FormatBytes(static_cast<int64>(mem_info.free) * 1024);
+ output = base::StringPrintf("free: %s", UTF16ToUTF8(free_bytes).c_str());
+ if (mem_info.gem_size != -1) {
+ base::string16 gem_size = ui::FormatBytes(mem_info.gem_size);
+ output += base::StringPrintf(" gmem: %s", UTF16ToUTF8(gem_size).c_str());
+ if (mem_info.gem_objects != -1)
+ output += base::StringPrintf(" gobjects: %d", mem_info.gem_objects);
+ }
+ size_t total_private_bytes = 0, total_shared_bytes = 0;
+ for (std::list<base::ProcessHandle>::const_iterator i = handles.begin();
+ i != handles.end(); ++i) {
+ base::ProcessMetrics* pm = base::ProcessMetrics::CreateProcessMetrics(*i);
+ size_t private_bytes, shared_bytes;
+ pm->GetMemoryBytes(&private_bytes, &shared_bytes);
+ total_private_bytes += private_bytes;
+ total_shared_bytes += shared_bytes;
+ delete pm;
+ }
+ base::string16 private_size = ui::FormatBytes(total_private_bytes);
+ base::string16 shared_size = ui::FormatBytes(total_shared_bytes);
+
+ output += base::StringPrintf("\nGPU private: %s shared: %s",
+ UTF16ToUTF8(private_size).c_str(),
+ UTF16ToUTF8(shared_size).c_str());
+ label_->SetText(UTF8ToUTF16(output));
+ refresh_timer_.Start(FROM_HERE,
+ base::TimeDelta::FromMilliseconds(kRefreshTimeoutMs),
+ this, &TrayMonitor::OnTimer);
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/system/monitor/tray_monitor.h b/chromium/ash/system/monitor/tray_monitor.h
new file mode 100644
index 00000000000..31c4f146cda
--- /dev/null
+++ b/chromium/ash/system/monitor/tray_monitor.h
@@ -0,0 +1,43 @@
+// Copyright (c) 2012 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 ASH_SYSTEM_DATE_TRAY_MONITOR_H_
+#define ASH_SYSTEM_DATE_TRAY_MONITOR_H_
+
+#include <list>
+
+#include "ash/system/tray/system_tray_item.h"
+#include "base/process/process.h"
+#include "base/timer/timer.h"
+
+namespace views {
+class Label;
+}
+
+namespace ash {
+namespace internal {
+
+class TrayMonitor : public SystemTrayItem {
+ public:
+ explicit TrayMonitor(SystemTray* system_tray);
+ virtual ~TrayMonitor();
+
+ private:
+ // Overridden from SystemTrayItem.
+ virtual views::View* CreateTrayView(user::LoginStatus status) OVERRIDE;
+ virtual void DestroyTrayView() OVERRIDE;
+
+ void OnTimer();
+ void OnGotHandles(const std::list<base::ProcessHandle>& handles);
+
+ views::Label* label_;
+ base::RepeatingTimer<TrayMonitor> refresh_timer_;
+
+ DISALLOW_COPY_AND_ASSIGN(TrayMonitor);
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_SYSTEM_DATE_TRAY_MONITOR_H_
diff --git a/chromium/ash/system/session_length_limit/session_length_limit_observer.h b/chromium/ash/system/session_length_limit/session_length_limit_observer.h
new file mode 100644
index 00000000000..2a4f5275267
--- /dev/null
+++ b/chromium/ash/system/session_length_limit/session_length_limit_observer.h
@@ -0,0 +1,24 @@
+// Copyright (c) 2012 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 ASH_SYSTEM_SESSION_LENGTH_LIMIT_SESSION_LENGTH_LIMIT_OBSERVER_H_
+#define ASH_SYSTEM_SESSION_LENGTH_LIMIT_SESSION_LENGTH_LIMIT_OBSERVER_H_
+
+namespace ash {
+
+// Observer for the session length limit.
+class SessionLengthLimitObserver {
+ public:
+ virtual ~SessionLengthLimitObserver() {}
+
+ // Called when the session start time is updated.
+ virtual void OnSessionStartTimeChanged() = 0;
+
+ // Called when the session length limit is updated.
+ virtual void OnSessionLengthLimitChanged() = 0;
+};
+
+} // namespace ash
+
+#endif // ASH_SYSTEM_SESSION_LENGTH_LIMIT_SESSION_LENGTH_LIMIT_OBSERVER_H_
diff --git a/chromium/ash/system/session_length_limit/tray_session_length_limit.cc b/chromium/ash/system/session_length_limit/tray_session_length_limit.cc
new file mode 100644
index 00000000000..1b2b9446110
--- /dev/null
+++ b/chromium/ash/system/session_length_limit/tray_session_length_limit.cc
@@ -0,0 +1,398 @@
+// Copyright (c) 2012 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.
+
+#include "ash/system/session_length_limit/tray_session_length_limit.h"
+
+#include <algorithm>
+
+#include "ash/shelf/shelf_types.h"
+#include "ash/shell.h"
+#include "ash/system/tray/system_tray.h"
+#include "ash/system/tray/system_tray_delegate.h"
+#include "ash/system/tray/system_tray_notifier.h"
+#include "ash/system/tray/tray_constants.h"
+#include "ash/system/tray/tray_notification_view.h"
+#include "ash/system/tray/tray_utils.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/strings/string16.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/utf_string_conversions.h"
+#include "grit/ash_resources.h"
+#include "grit/ash_strings.h"
+#include "third_party/skia/include/core/SkColor.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/gfx/font.h"
+#include "ui/gfx/text_constants.h"
+#include "ui/views/border.h"
+#include "ui/views/controls/label.h"
+#include "ui/views/layout/box_layout.h"
+#include "ui/views/layout/grid_layout.h"
+#include "ui/views/view.h"
+
+namespace ash {
+namespace internal {
+
+namespace {
+
+// If the remaining session time falls below this threshold, the user should be
+// informed that the session is about to expire.
+const int kExpiringSoonThresholdInSeconds = 5 * 60; // 5 minutes.
+
+// Color in which the remaining session time is normally shown.
+const SkColor kRemainingTimeColor = SK_ColorWHITE;
+// Color in which the remaining session time is shown when it is expiring soon.
+const SkColor kRemainingTimeExpiringSoonColor = SK_ColorRED;
+
+views::Label* CreateAndSetupLabel() {
+ views::Label* label = new views::Label;
+ label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
+ SetupLabelForTray(label);
+ gfx::Font font = label->font();
+ label->SetFont(font.DeriveFont(0, font.GetStyle() & ~gfx::Font::BOLD));
+ return label;
+}
+
+base::string16 IntToTwoDigitString(int value) {
+ DCHECK_GE(value, 0);
+ DCHECK_LE(value, 99);
+ if (value < 10)
+ return ASCIIToUTF16("0") + base::IntToString16(value);
+ return base::IntToString16(value);
+}
+
+base::string16 FormatRemainingSessionTimeNotification(
+ const base::TimeDelta& remaining_session_time) {
+ return l10n_util::GetStringFUTF16(
+ IDS_ASH_STATUS_TRAY_REMAINING_SESSION_TIME_NOTIFICATION,
+ Shell::GetInstance()->system_tray_delegate()->
+ FormatTimeDuration(remaining_session_time));
+}
+
+} // namespace
+
+namespace tray {
+
+class RemainingSessionTimeNotificationView : public TrayNotificationView {
+ public:
+ explicit RemainingSessionTimeNotificationView(TraySessionLengthLimit* owner);
+ virtual ~RemainingSessionTimeNotificationView();
+
+ // TrayNotificationView:
+ virtual void ChildPreferredSizeChanged(views::View* child) OVERRIDE;
+
+ void Update();
+
+ private:
+ views::Label* label_;
+
+ DISALLOW_COPY_AND_ASSIGN(RemainingSessionTimeNotificationView);
+};
+
+class RemainingSessionTimeTrayView : public views::View {
+ public:
+ RemainingSessionTimeTrayView(const TraySessionLengthLimit* owner,
+ ShelfAlignment shelf_alignment);
+ virtual ~RemainingSessionTimeTrayView();
+
+ void UpdateClockLayout(ShelfAlignment shelf_alignment);
+ void Update();
+
+ private:
+ void SetBorder(ShelfAlignment shelf_alignment);
+
+ const TraySessionLengthLimit* owner_;
+
+ views::Label* horizontal_layout_label_;
+ views::Label* vertical_layout_label_hours_left_;
+ views::Label* vertical_layout_label_hours_right_;
+ views::Label* vertical_layout_label_minutes_left_;
+ views::Label* vertical_layout_label_minutes_right_;
+ views::Label* vertical_layout_label_seconds_left_;
+ views::Label* vertical_layout_label_seconds_right_;
+
+ DISALLOW_COPY_AND_ASSIGN(RemainingSessionTimeTrayView);
+};
+
+RemainingSessionTimeNotificationView::RemainingSessionTimeNotificationView(
+ TraySessionLengthLimit* owner)
+ : TrayNotificationView(owner,
+ IDR_AURA_UBER_TRAY_SESSION_LENGTH_LIMIT_TIMER) {
+ label_ = new views::Label;
+ label_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
+ label_->SetMultiLine(true);
+ Update();
+ InitView(label_);
+}
+
+RemainingSessionTimeNotificationView::~RemainingSessionTimeNotificationView() {
+}
+
+void RemainingSessionTimeNotificationView::ChildPreferredSizeChanged(
+ views::View* child) {
+ PreferredSizeChanged();
+}
+
+void RemainingSessionTimeNotificationView::Update() {
+ label_->SetText(FormatRemainingSessionTimeNotification(
+ reinterpret_cast<TraySessionLengthLimit*>(owner())->
+ GetRemainingSessionTime()));
+}
+
+RemainingSessionTimeTrayView::RemainingSessionTimeTrayView(
+ const TraySessionLengthLimit* owner,
+ ShelfAlignment shelf_alignment)
+ : owner_(owner),
+ horizontal_layout_label_(NULL),
+ vertical_layout_label_hours_left_(NULL),
+ vertical_layout_label_hours_right_(NULL),
+ vertical_layout_label_minutes_left_(NULL),
+ vertical_layout_label_minutes_right_(NULL),
+ vertical_layout_label_seconds_left_(NULL),
+ vertical_layout_label_seconds_right_(NULL) {
+ UpdateClockLayout(shelf_alignment);
+}
+
+RemainingSessionTimeTrayView::~RemainingSessionTimeTrayView() {
+}
+
+void RemainingSessionTimeTrayView::UpdateClockLayout(
+ ShelfAlignment shelf_alignment) {
+ SetBorder(shelf_alignment);
+ const bool horizontal_layout = (shelf_alignment == SHELF_ALIGNMENT_BOTTOM ||
+ shelf_alignment == SHELF_ALIGNMENT_TOP);
+ if (horizontal_layout && !horizontal_layout_label_) {
+ // Remove labels used for vertical layout.
+ RemoveAllChildViews(true);
+ vertical_layout_label_hours_left_ = NULL;
+ vertical_layout_label_hours_right_ = NULL;
+ vertical_layout_label_minutes_left_ = NULL;
+ vertical_layout_label_minutes_right_ = NULL;
+ vertical_layout_label_seconds_left_ = NULL;
+ vertical_layout_label_seconds_right_ = NULL;
+
+ // Create label used for horizontal layout.
+ horizontal_layout_label_ = CreateAndSetupLabel();
+
+ // Construct layout.
+ SetLayoutManager(
+ new views::BoxLayout(views::BoxLayout::kHorizontal, 0, 0, 0));
+ AddChildView(horizontal_layout_label_);
+
+ } else if (!horizontal_layout && horizontal_layout_label_) {
+ // Remove label used for horizontal layout.
+ RemoveAllChildViews(true);
+ horizontal_layout_label_ = NULL;
+
+ // Create labels used for vertical layout.
+ vertical_layout_label_hours_left_ = CreateAndSetupLabel();
+ vertical_layout_label_hours_right_ = CreateAndSetupLabel();
+ vertical_layout_label_minutes_left_ = CreateAndSetupLabel();
+ vertical_layout_label_minutes_right_ = CreateAndSetupLabel();
+ vertical_layout_label_seconds_left_ = CreateAndSetupLabel();
+ vertical_layout_label_seconds_right_ = CreateAndSetupLabel();
+
+ // Construct layout.
+ views::GridLayout* layout = new views::GridLayout(this);
+ SetLayoutManager(layout);
+ views::ColumnSet* columns = layout->AddColumnSet(0);
+ columns->AddPaddingColumn(0, 6);
+ columns->AddColumn(views::GridLayout::CENTER, views::GridLayout::CENTER,
+ 0, views::GridLayout::USE_PREF, 0, 0);
+ columns->AddColumn(views::GridLayout::CENTER, views::GridLayout::CENTER,
+ 0, views::GridLayout::USE_PREF, 0, 0);
+ layout->AddPaddingRow(0, kTrayLabelItemVerticalPaddingVeriticalAlignment);
+ layout->StartRow(0, 0);
+ layout->AddView(vertical_layout_label_hours_left_);
+ layout->AddView(vertical_layout_label_hours_right_);
+ layout->StartRow(0, 0);
+ layout->AddView(vertical_layout_label_minutes_left_);
+ layout->AddView(vertical_layout_label_minutes_right_);
+ layout->StartRow(0, 0);
+ layout->AddView(vertical_layout_label_seconds_left_);
+ layout->AddView(vertical_layout_label_seconds_right_);
+ layout->AddPaddingRow(0, kTrayLabelItemVerticalPaddingVeriticalAlignment);
+ }
+ Update();
+}
+
+void RemainingSessionTimeTrayView::Update() {
+ const TraySessionLengthLimit::LimitState limit_state =
+ owner_->GetLimitState();
+
+ if (limit_state == TraySessionLengthLimit::LIMIT_NONE) {
+ SetVisible(false);
+ return;
+ }
+
+ int seconds = owner_->GetRemainingSessionTime().InSeconds();
+ // If the remaining session time is 100 hours or more, show 99:59:59 instead.
+ seconds = std::min(seconds, 100 * 60 * 60 - 1); // 100 hours - 1 second.
+ int minutes = seconds / 60;
+ seconds %= 60;
+ const int hours = minutes / 60;
+ minutes %= 60;
+
+ const base::string16 hours_str = IntToTwoDigitString(hours);
+ const base::string16 minutes_str = IntToTwoDigitString(minutes);
+ const base::string16 seconds_str = IntToTwoDigitString(seconds);
+ const SkColor color =
+ limit_state == TraySessionLengthLimit::LIMIT_EXPIRING_SOON ?
+ kRemainingTimeExpiringSoonColor : kRemainingTimeColor;
+
+ if (horizontal_layout_label_) {
+ horizontal_layout_label_->SetText(l10n_util::GetStringFUTF16(
+ IDS_ASH_STATUS_TRAY_REMAINING_SESSION_TIME,
+ hours_str, minutes_str, seconds_str));
+ horizontal_layout_label_->SetEnabledColor(color);
+ } else if (vertical_layout_label_hours_left_) {
+ vertical_layout_label_hours_left_->SetText(hours_str.substr(0, 1));
+ vertical_layout_label_hours_right_->SetText(hours_str.substr(1, 1));
+ vertical_layout_label_minutes_left_->SetText(minutes_str.substr(0, 1));
+ vertical_layout_label_minutes_right_->SetText(minutes_str.substr(1, 1));
+ vertical_layout_label_seconds_left_->SetText(seconds_str.substr(0, 1));
+ vertical_layout_label_seconds_right_->SetText(seconds_str.substr(1, 1));
+ vertical_layout_label_hours_left_->SetEnabledColor(color);
+ vertical_layout_label_hours_right_->SetEnabledColor(color);
+ vertical_layout_label_minutes_left_->SetEnabledColor(color);
+ vertical_layout_label_minutes_right_->SetEnabledColor(color);
+ vertical_layout_label_seconds_left_->SetEnabledColor(color);
+ vertical_layout_label_seconds_right_->SetEnabledColor(color);
+ }
+
+ Layout();
+ SetVisible(true);
+}
+
+void RemainingSessionTimeTrayView::SetBorder(ShelfAlignment shelf_alignment) {
+ if (shelf_alignment == SHELF_ALIGNMENT_BOTTOM ||
+ shelf_alignment == SHELF_ALIGNMENT_TOP) {
+ set_border(views::Border::CreateEmptyBorder(
+ 0, kTrayLabelItemHorizontalPaddingBottomAlignment,
+ 0, kTrayLabelItemHorizontalPaddingBottomAlignment));
+ } else {
+ set_border(NULL);
+ }
+}
+
+} // namespace tray
+
+TraySessionLengthLimit::TraySessionLengthLimit(SystemTray* system_tray)
+ : SystemTrayItem(system_tray),
+ tray_view_(NULL),
+ notification_view_(NULL),
+ limit_state_(LIMIT_NONE) {
+ Shell::GetInstance()->system_tray_notifier()->
+ AddSessionLengthLimitObserver(this);
+ Update();
+}
+
+TraySessionLengthLimit::~TraySessionLengthLimit() {
+ Shell::GetInstance()->system_tray_notifier()->
+ RemoveSessionLengthLimitObserver(this);
+}
+
+views::View* TraySessionLengthLimit::CreateTrayView(user::LoginStatus status) {
+ CHECK(tray_view_ == NULL);
+ tray_view_ = new tray::RemainingSessionTimeTrayView(
+ this, system_tray()->shelf_alignment());
+ return tray_view_;
+}
+
+views::View* TraySessionLengthLimit::CreateNotificationView(
+ user::LoginStatus status) {
+ CHECK(notification_view_ == NULL);
+ notification_view_ = new tray::RemainingSessionTimeNotificationView(this);
+ return notification_view_;
+}
+
+void TraySessionLengthLimit::DestroyTrayView() {
+ tray_view_ = NULL;
+}
+
+void TraySessionLengthLimit::DestroyNotificationView() {
+ notification_view_ = NULL;
+}
+
+void TraySessionLengthLimit::UpdateAfterShelfAlignmentChange(
+ ShelfAlignment alignment) {
+ if (tray_view_)
+ tray_view_->UpdateClockLayout(alignment);
+}
+
+void TraySessionLengthLimit::OnSessionStartTimeChanged() {
+ Update();
+}
+
+void TraySessionLengthLimit::OnSessionLengthLimitChanged() {
+ Update();
+}
+
+TraySessionLengthLimit::LimitState
+ TraySessionLengthLimit::GetLimitState() const {
+ return limit_state_;
+}
+
+base::TimeDelta TraySessionLengthLimit::GetRemainingSessionTime() const {
+ return remaining_session_time_;
+}
+
+void TraySessionLengthLimit::ShowAndSpeakNotification() {
+ ShowNotificationView();
+ Shell::GetInstance()->system_tray_delegate()->MaybeSpeak(UTF16ToUTF8(
+ FormatRemainingSessionTimeNotification(remaining_session_time_)));
+}
+
+void TraySessionLengthLimit::Update() {
+ SystemTrayDelegate* delegate = Shell::GetInstance()->system_tray_delegate();
+ const LimitState previous_limit_state = limit_state_;
+ if (!delegate->GetSessionStartTime(&session_start_time_) ||
+ !delegate->GetSessionLengthLimit(&limit_)) {
+ remaining_session_time_ = base::TimeDelta();
+ limit_state_ = LIMIT_NONE;
+ timer_.reset();
+ } else {
+ remaining_session_time_ = std::max(
+ limit_ - (base::TimeTicks::Now() - session_start_time_),
+ base::TimeDelta());
+ limit_state_ = remaining_session_time_.InSeconds() <=
+ kExpiringSoonThresholdInSeconds ? LIMIT_EXPIRING_SOON : LIMIT_SET;
+ if (!timer_)
+ timer_.reset(new base::RepeatingTimer<TraySessionLengthLimit>);
+ if (!timer_->IsRunning()) {
+ // Start a timer that will update the remaining session time every second.
+ timer_->Start(FROM_HERE,
+ base::TimeDelta::FromSeconds(1),
+ this,
+ &TraySessionLengthLimit::Update);
+ }
+ }
+
+ if (notification_view_)
+ notification_view_->Update();
+
+ switch (limit_state_) {
+ case LIMIT_NONE:
+ HideNotificationView();
+ break;
+ case LIMIT_SET:
+ if (previous_limit_state == LIMIT_NONE)
+ ShowAndSpeakNotification();
+ break;
+ case LIMIT_EXPIRING_SOON:
+ if (previous_limit_state == LIMIT_NONE ||
+ previous_limit_state == LIMIT_SET) {
+ ShowAndSpeakNotification();
+ }
+ break;
+ }
+
+ // Update the tray view last so that it can check whether the notification
+ // view is currently visible or not.
+ if (tray_view_)
+ tray_view_->Update();
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/system/session_length_limit/tray_session_length_limit.h b/chromium/ash/system/session_length_limit/tray_session_length_limit.h
new file mode 100644
index 00000000000..1df4bb0fb98
--- /dev/null
+++ b/chromium/ash/system/session_length_limit/tray_session_length_limit.h
@@ -0,0 +1,72 @@
+// Copyright (c) 2012 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 ASH_SYSTEM_SESSION_LENGTH_LIMIT_TRAY_SESSION_LENGTH_LIMIT_H_
+#define ASH_SYSTEM_SESSION_LENGTH_LIMIT_TRAY_SESSION_LENGTH_LIMIT_H_
+
+#include "ash/system/session_length_limit/session_length_limit_observer.h"
+#include "ash/system/tray/system_tray_item.h"
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/time/time.h"
+#include "base/timer/timer.h"
+
+namespace ash {
+namespace internal {
+
+namespace tray {
+class RemainingSessionTimeNotificationView;
+class RemainingSessionTimeTrayView;
+}
+
+// Adds a countdown timer to the system tray if the session length is limited.
+class TraySessionLengthLimit : public SystemTrayItem,
+ public SessionLengthLimitObserver {
+ public:
+ enum LimitState {
+ LIMIT_NONE,
+ LIMIT_SET,
+ LIMIT_EXPIRING_SOON
+ };
+
+ explicit TraySessionLengthLimit(SystemTray* system_tray);
+ virtual ~TraySessionLengthLimit();
+
+ // SystemTrayItem:
+ virtual views::View* CreateTrayView(user::LoginStatus status) OVERRIDE;
+ virtual views::View* CreateNotificationView(
+ user::LoginStatus status) OVERRIDE;
+ virtual void DestroyTrayView() OVERRIDE;
+ virtual void DestroyNotificationView() OVERRIDE;
+ virtual void UpdateAfterShelfAlignmentChange(
+ ShelfAlignment alignment) OVERRIDE;
+
+ // SessionLengthLimitObserver:
+ virtual void OnSessionStartTimeChanged() OVERRIDE;
+ virtual void OnSessionLengthLimitChanged() OVERRIDE;
+
+ LimitState GetLimitState() const;
+ base::TimeDelta GetRemainingSessionTime() const;
+
+ private:
+ void ShowAndSpeakNotification();
+ void Update();
+
+ tray::RemainingSessionTimeTrayView* tray_view_;
+ tray::RemainingSessionTimeNotificationView* notification_view_;
+
+ LimitState limit_state_;
+ base::TimeTicks session_start_time_;
+ base::TimeDelta limit_;
+ base::TimeDelta remaining_session_time_;
+ scoped_ptr<base::RepeatingTimer<TraySessionLengthLimit> > timer_;
+
+ DISALLOW_COPY_AND_ASSIGN(TraySessionLengthLimit);
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_SYSTEM_SESSION_LENGTH_LIMIT_TRAY_SESSION_LENGTH_LIMIT_H_
diff --git a/chromium/ash/system/status_area_widget.cc b/chromium/ash/system/status_area_widget.cc
new file mode 100644
index 00000000000..ed31ba286fd
--- /dev/null
+++ b/chromium/ash/system/status_area_widget.cc
@@ -0,0 +1,156 @@
+// Copyright (c) 2012 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.
+
+#include "ash/system/status_area_widget.h"
+
+#include "ash/root_window_controller.h"
+#include "ash/shelf/shelf_layout_manager.h"
+#include "ash/shelf/shelf_widget.h"
+#include "ash/shell.h"
+#include "ash/shell_delegate.h"
+#include "ash/shell_window_ids.h"
+#include "ash/system/bluetooth/bluetooth_observer.h"
+#include "ash/system/logout_button/logout_button_tray.h"
+#include "ash/system/status_area_widget_delegate.h"
+#include "ash/system/tray/system_tray.h"
+#include "ash/system/tray/system_tray_delegate.h"
+#include "ash/system/web_notification/web_notification_tray.h"
+#include "ash/wm/window_properties.h"
+#include "base/i18n/time_formatting.h"
+#include "ui/aura/window.h"
+#include "ui/gfx/screen.h"
+
+namespace ash {
+
+namespace internal {
+
+const char StatusAreaWidget::kNativeViewName[] = "StatusAreaWidget";
+
+StatusAreaWidget::StatusAreaWidget(aura::Window* status_container)
+ : status_area_widget_delegate_(new internal::StatusAreaWidgetDelegate),
+ system_tray_(NULL),
+ web_notification_tray_(NULL),
+ logout_button_tray_(NULL),
+ login_status_(user::LOGGED_IN_NONE) {
+ views::Widget::InitParams params(
+ views::Widget::InitParams::TYPE_WINDOW_FRAMELESS);
+ params.delegate = status_area_widget_delegate_;
+ params.parent = status_container;
+ params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW;
+ Init(params);
+ set_focus_on_creation(false);
+ SetContentsView(status_area_widget_delegate_);
+ GetNativeView()->SetName(kNativeViewName);
+}
+
+StatusAreaWidget::~StatusAreaWidget() {
+}
+
+void StatusAreaWidget::CreateTrayViews() {
+ AddSystemTray();
+ AddWebNotificationTray();
+ AddLogoutButtonTray();
+ SystemTrayDelegate* delegate =
+ ash::Shell::GetInstance()->system_tray_delegate();
+ DCHECK(delegate);
+ // Initialize after all trays have been created.
+ if (system_tray_)
+ system_tray_->InitializeTrayItems(delegate);
+ if (web_notification_tray_)
+ web_notification_tray_->Initialize();
+ if (logout_button_tray_)
+ logout_button_tray_->Initialize();
+ UpdateAfterLoginStatusChange(delegate->GetUserLoginStatus());
+}
+
+void StatusAreaWidget::Shutdown() {
+ // Destroy the trays early, causing them to be removed from the view
+ // hierarchy. Do not used scoped pointers since we don't want to destroy them
+ // in the destructor if Shutdown() is not called (e.g. in tests).
+ delete logout_button_tray_;
+ logout_button_tray_ = NULL;
+ delete web_notification_tray_;
+ web_notification_tray_ = NULL;
+ delete system_tray_;
+ system_tray_ = NULL;
+}
+
+bool StatusAreaWidget::ShouldShowLauncher() const {
+ if ((system_tray_ && system_tray_->ShouldShowLauncher()) ||
+ (web_notification_tray_ &&
+ web_notification_tray_->ShouldBlockLauncherAutoHide()))
+ return true;
+
+ if (!RootWindowController::ForLauncher(GetNativeView())->shelf()->IsVisible())
+ return false;
+
+ // If the launcher is currently visible, don't hide the launcher if the mouse
+ // is in any of the notification bubbles.
+ return (system_tray_ && system_tray_->IsMouseInNotificationBubble()) ||
+ (web_notification_tray_ &&
+ web_notification_tray_->IsMouseInNotificationBubble());
+}
+
+bool StatusAreaWidget::IsMessageBubbleShown() const {
+ return ((system_tray_ && system_tray_->IsAnyBubbleVisible()) ||
+ (web_notification_tray_ &&
+ web_notification_tray_->IsMessageCenterBubbleVisible()));
+}
+
+void StatusAreaWidget::OnNativeWidgetActivationChanged(bool active) {
+ Widget::OnNativeWidgetActivationChanged(active);
+ if (active)
+ status_area_widget_delegate_->SetPaneFocusAndFocusDefault();
+}
+
+void StatusAreaWidget::AddSystemTray() {
+ system_tray_ = new SystemTray(this);
+ status_area_widget_delegate_->AddTray(system_tray_);
+}
+
+void StatusAreaWidget::AddWebNotificationTray() {
+ web_notification_tray_ = new WebNotificationTray(this);
+ status_area_widget_delegate_->AddTray(web_notification_tray_);
+}
+
+void StatusAreaWidget::AddLogoutButtonTray() {
+ logout_button_tray_ = new LogoutButtonTray(this);
+ status_area_widget_delegate_->AddTray(logout_button_tray_);
+}
+
+void StatusAreaWidget::SetShelfAlignment(ShelfAlignment alignment) {
+ status_area_widget_delegate_->set_alignment(alignment);
+ if (system_tray_)
+ system_tray_->SetShelfAlignment(alignment);
+ if (web_notification_tray_)
+ web_notification_tray_->SetShelfAlignment(alignment);
+ if (logout_button_tray_)
+ logout_button_tray_->SetShelfAlignment(alignment);
+ status_area_widget_delegate_->UpdateLayout();
+}
+
+void StatusAreaWidget::SetHideSystemNotifications(bool hide) {
+ if (system_tray_)
+ system_tray_->SetHideNotifications(hide);
+}
+
+bool StatusAreaWidget::ShouldShowWebNotifications() {
+ return !(system_tray_ && system_tray_->IsAnyBubbleVisible());
+}
+
+void StatusAreaWidget::UpdateAfterLoginStatusChange(
+ user::LoginStatus login_status) {
+ if (login_status_ == login_status)
+ return;
+ login_status_ = login_status;
+ if (system_tray_)
+ system_tray_->UpdateAfterLoginStatusChange(login_status);
+ if (web_notification_tray_)
+ web_notification_tray_->UpdateAfterLoginStatusChange(login_status);
+ if (logout_button_tray_)
+ logout_button_tray_->UpdateAfterLoginStatusChange(login_status);
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/system/status_area_widget.h b/chromium/ash/system/status_area_widget.h
new file mode 100644
index 00000000000..6a4cb2a40aa
--- /dev/null
+++ b/chromium/ash/system/status_area_widget.h
@@ -0,0 +1,91 @@
+// Copyright (c) 2012 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 ASH_SYSTEM_STATUS_AREA_WIDGET_H_
+#define ASH_SYSTEM_STATUS_AREA_WIDGET_H_
+
+#include "ash/ash_export.h"
+#include "ash/shelf/shelf_types.h"
+#include "ash/system/user/login_status.h"
+#include "ui/views/widget/widget.h"
+
+namespace ash {
+
+class ShellDelegate;
+class SystemTray;
+class WebNotificationTray;
+
+namespace internal {
+
+class LogoutButtonTray;
+class StatusAreaWidgetDelegate;
+
+class ASH_EXPORT StatusAreaWidget : public views::Widget {
+ public:
+ static const char kNativeViewName[];
+
+ explicit StatusAreaWidget(aura::Window* status_container);
+ virtual ~StatusAreaWidget();
+
+ // Creates the SystemTray, WebNotificationTray and LogoutButtonTray.
+ void CreateTrayViews();
+
+ // Destroys the system tray and web notification tray. Called before
+ // tearing down the windows to avoid shutdown ordering issues.
+ void Shutdown();
+
+ // Update the alignment of the widget and tray views.
+ void SetShelfAlignment(ShelfAlignment alignment);
+
+ // Set the visibility of system notifications.
+ void SetHideSystemNotifications(bool hide);
+
+ // Returns true if it is OK to show a non system notification.
+ bool ShouldShowWebNotifications();
+
+ // Called by the client when the login status changes. Caches login_status
+ // and calls UpdateAfterLoginStatusChange for the system tray and the web
+ // notification tray.
+ void UpdateAfterLoginStatusChange(user::LoginStatus login_status);
+
+ internal::StatusAreaWidgetDelegate* status_area_widget_delegate() {
+ return status_area_widget_delegate_;
+ }
+ SystemTray* system_tray() { return system_tray_; }
+ WebNotificationTray* web_notification_tray() {
+ return web_notification_tray_;
+ }
+
+ user::LoginStatus login_status() const { return login_status_; }
+
+ // Returns true if the launcher should be visible. This is used when the
+ // launcher is configured to auto-hide and test if the shelf should force
+ // the launcher to remain visible.
+ bool ShouldShowLauncher() const;
+
+ // True if any message bubble is shown.
+ bool IsMessageBubbleShown() const;
+
+ // Overridden from views::Widget:
+ virtual void OnNativeWidgetActivationChanged(bool active) OVERRIDE;
+
+ private:
+ void AddSystemTray();
+ void AddWebNotificationTray();
+ void AddLogoutButtonTray();
+
+ // Weak pointers to View classes that are parented to StatusAreaWidget:
+ internal::StatusAreaWidgetDelegate* status_area_widget_delegate_;
+ SystemTray* system_tray_;
+ WebNotificationTray* web_notification_tray_;
+ LogoutButtonTray* logout_button_tray_;
+ user::LoginStatus login_status_;
+
+ DISALLOW_COPY_AND_ASSIGN(StatusAreaWidget);
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_SYSTEM_STATUS_AREA_WIDGET_H_
diff --git a/chromium/ash/system/status_area_widget_delegate.cc b/chromium/ash/system/status_area_widget_delegate.cc
new file mode 100644
index 00000000000..7e15b7e61ce
--- /dev/null
+++ b/chromium/ash/system/status_area_widget_delegate.cc
@@ -0,0 +1,153 @@
+// Copyright (c) 2012 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.
+
+#include "ash/system/status_area_widget_delegate.h"
+
+#include "ash/ash_export.h"
+#include "ash/ash_switches.h"
+#include "ash/focus_cycler.h"
+#include "ash/shell.h"
+#include "ash/shell_window_ids.h"
+#include "ash/system/tray/tray_constants.h"
+#include "base/strings/utf_string_conversions.h"
+#include "ui/aura/root_window.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "ui/gfx/canvas.h"
+#include "ui/gfx/image/image.h"
+#include "ui/views/accessible_pane_view.h"
+#include "ui/views/layout/grid_layout.h"
+#include "ui/views/widget/widget.h"
+
+namespace ash {
+namespace internal {
+namespace {
+
+const int kStatusTrayOffsetFromScreenEdge = 4;
+
+}
+
+StatusAreaWidgetDelegate::StatusAreaWidgetDelegate()
+ : focus_cycler_for_testing_(NULL),
+ alignment_(SHELF_ALIGNMENT_BOTTOM) {
+ // Allow the launcher to surrender the focus to another window upon
+ // navigation completion by the user.
+ set_allow_deactivate_on_esc(true);
+}
+
+StatusAreaWidgetDelegate::~StatusAreaWidgetDelegate() {
+}
+
+void StatusAreaWidgetDelegate::SetFocusCyclerForTesting(
+ const FocusCycler* focus_cycler) {
+ focus_cycler_for_testing_ = focus_cycler;
+}
+
+views::View* StatusAreaWidgetDelegate::GetDefaultFocusableChild() {
+ return child_at(0);
+}
+
+views::Widget* StatusAreaWidgetDelegate::GetWidget() {
+ return View::GetWidget();
+}
+
+const views::Widget* StatusAreaWidgetDelegate::GetWidget() const {
+ return View::GetWidget();
+}
+
+void StatusAreaWidgetDelegate::OnGestureEvent(ui::GestureEvent* event) {
+ if (gesture_handler_.ProcessGestureEvent(*event))
+ event->StopPropagation();
+ else
+ views::AccessiblePaneView::OnGestureEvent(event);
+}
+
+bool StatusAreaWidgetDelegate::CanActivate() const {
+ // We don't want mouse clicks to activate us, but we need to allow
+ // activation when the user is using the keyboard (FocusCycler).
+ const FocusCycler* focus_cycler = focus_cycler_for_testing_ ?
+ focus_cycler_for_testing_ : Shell::GetInstance()->focus_cycler();
+ return focus_cycler->widget_activating() == GetWidget();
+}
+
+void StatusAreaWidgetDelegate::DeleteDelegate() {
+}
+
+void StatusAreaWidgetDelegate::AddTray(views::View* tray) {
+ SetLayoutManager(NULL); // Reset layout manager before adding a child.
+ AddChildView(tray);
+ // Set the layout manager with the new list of children.
+ UpdateLayout();
+}
+
+void StatusAreaWidgetDelegate::UpdateLayout() {
+ // Use a grid layout so that the trays can be centered in each cell, and
+ // so that the widget gets laid out correctly when tray sizes change.
+ views::GridLayout* layout = new views::GridLayout(this);
+ SetLayoutManager(layout);
+
+ views::ColumnSet* columns = layout->AddColumnSet(0);
+ if (alignment_ == SHELF_ALIGNMENT_BOTTOM ||
+ alignment_ == SHELF_ALIGNMENT_TOP) {
+ if (alignment_ == SHELF_ALIGNMENT_TOP)
+ layout->SetInsets(kStatusTrayOffsetFromScreenEdge, 0, 0, 0);
+ else
+ layout->SetInsets(0, 0, kStatusTrayOffsetFromScreenEdge, 0);
+ bool is_first_visible_child = true;
+ for (int c = 0; c < child_count(); ++c) {
+ views::View* child = child_at(c);
+ if (!child->visible())
+ continue;
+ if (!is_first_visible_child)
+ columns->AddPaddingColumn(0, GetTraySpacing());
+ is_first_visible_child = false;
+ columns->AddColumn(views::GridLayout::CENTER, views::GridLayout::FILL,
+ 0, /* resize percent */
+ views::GridLayout::USE_PREF, 0, 0);
+ }
+ layout->StartRow(0, 0);
+ for (int c = child_count() - 1; c >= 0; --c) {
+ views::View* child = child_at(c);
+ if (child->visible())
+ layout->AddView(child);
+ }
+ } else {
+ if (alignment_ == SHELF_ALIGNMENT_LEFT)
+ layout->SetInsets(0, kStatusTrayOffsetFromScreenEdge, 0, 0);
+ else
+ layout->SetInsets(0, 0, 0, kStatusTrayOffsetFromScreenEdge);
+ columns->AddColumn(views::GridLayout::FILL, views::GridLayout::CENTER,
+ 0, /* resize percent */
+ views::GridLayout::USE_PREF, 0, 0);
+ bool is_first_visible_child = true;
+ for (int c = child_count() - 1; c >= 0; --c) {
+ views::View* child = child_at(c);
+ if (!child->visible())
+ continue;
+ if (!is_first_visible_child)
+ layout->AddPaddingRow(0, GetTraySpacing());
+ is_first_visible_child = false;
+ layout->StartRow(0, 0);
+ layout->AddView(child);
+ }
+ }
+ Layout();
+ UpdateWidgetSize();
+}
+
+void StatusAreaWidgetDelegate::ChildPreferredSizeChanged(View* child) {
+ // Need to resize the window when trays or items are added/removed.
+ UpdateWidgetSize();
+}
+
+void StatusAreaWidgetDelegate::ChildVisibilityChanged(View* child) {
+ UpdateLayout();
+}
+
+void StatusAreaWidgetDelegate::UpdateWidgetSize() {
+ if (GetWidget())
+ GetWidget()->SetSize(GetPreferredSize());
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/system/status_area_widget_delegate.h b/chromium/ash/system/status_area_widget_delegate.h
new file mode 100644
index 00000000000..751c31beced
--- /dev/null
+++ b/chromium/ash/system/status_area_widget_delegate.h
@@ -0,0 +1,70 @@
+// Copyright (c) 2012 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 ASH_SYSTEM_STATUS_AREA_WIDGET_DELEGATE_H_
+#define ASH_SYSTEM_STATUS_AREA_WIDGET_DELEGATE_H_
+
+#include "ash/ash_export.h"
+#include "ash/shelf/shelf_types.h"
+#include "ash/wm/gestures/shelf_gesture_handler.h"
+#include "ui/gfx/image/image_skia.h"
+#include "ui/views/accessible_pane_view.h"
+#include "ui/views/widget/widget_delegate.h"
+
+namespace ash {
+namespace internal {
+
+class FocusCycler;
+
+class ASH_EXPORT StatusAreaWidgetDelegate : public views::AccessiblePaneView,
+ public views::WidgetDelegate {
+ public:
+ StatusAreaWidgetDelegate();
+ virtual ~StatusAreaWidgetDelegate();
+
+ // Add a tray view to the widget (e.g. system tray, web notifications).
+ void AddTray(views::View* tray);
+
+ // Called whenever layout might change (e.g. alignment changed).
+ void UpdateLayout();
+
+ // Sets the focus cycler.
+ void SetFocusCyclerForTesting(const FocusCycler* focus_cycler);
+
+ void set_alignment(ShelfAlignment alignment) { alignment_ = alignment; }
+
+ // Overridden from views::AccessiblePaneView.
+ virtual View* GetDefaultFocusableChild() OVERRIDE;
+
+ // Overridden from views::View:
+ virtual views::Widget* GetWidget() OVERRIDE;
+ virtual const views::Widget* GetWidget() const OVERRIDE;
+
+ // Overridden from ui::EventHandler:
+ virtual void OnGestureEvent(ui::GestureEvent* event) OVERRIDE;
+
+ // views::WidgetDelegate overrides:
+ virtual bool CanActivate() const OVERRIDE;
+ virtual void DeleteDelegate() OVERRIDE;
+
+ protected:
+ // Overridden from views::View:
+ virtual void ChildPreferredSizeChanged(views::View* child) OVERRIDE;
+ virtual void ChildVisibilityChanged(views::View* child) OVERRIDE;
+
+ private:
+ void UpdateWidgetSize();
+
+ const FocusCycler* focus_cycler_for_testing_;
+ ShelfAlignment alignment_;
+
+ ShelfGestureHandler gesture_handler_;
+
+ DISALLOW_COPY_AND_ASSIGN(StatusAreaWidgetDelegate);
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_SYSTEM_STATUS_AREA_WIDGET_DELEGATE_H_
diff --git a/chromium/ash/system/tray/actionable_view.cc b/chromium/ash/system/tray/actionable_view.cc
new file mode 100644
index 00000000000..dc9e9383716
--- /dev/null
+++ b/chromium/ash/system/tray/actionable_view.cc
@@ -0,0 +1,78 @@
+// Copyright 2012 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.
+
+#include "ash/system/tray/actionable_view.h"
+
+#include "ash/ash_constants.h"
+#include "ui/base/accessibility/accessible_view_state.h"
+#include "ui/gfx/canvas.h"
+
+namespace ash {
+namespace internal {
+
+// static
+const char ActionableView::kViewClassName[] = "tray/ActionableView";
+
+ActionableView::ActionableView()
+ : has_capture_(false) {
+ set_focusable(true);
+}
+
+ActionableView::~ActionableView() {
+}
+
+void ActionableView::DrawBorder(gfx::Canvas* canvas, const gfx::Rect& bounds) {
+ gfx::Rect rect = bounds;
+ rect.Inset(1, 1, 3, 3);
+ canvas->DrawRect(rect, kFocusBorderColor);
+}
+
+const char* ActionableView::GetClassName() const {
+ return kViewClassName;
+}
+
+bool ActionableView::OnKeyPressed(const ui::KeyEvent& event) {
+ if (event.key_code() == ui::VKEY_SPACE ||
+ event.key_code() == ui::VKEY_RETURN) {
+ return PerformAction(event);
+ }
+ return false;
+}
+
+bool ActionableView::OnMousePressed(const ui::MouseEvent& event) {
+ // Return true so that this view starts capturing the events.
+ has_capture_ = true;
+ return true;
+}
+
+void ActionableView::OnMouseReleased(const ui::MouseEvent& event) {
+ if (has_capture_ && GetLocalBounds().Contains(event.location()))
+ PerformAction(event);
+}
+
+void ActionableView::OnMouseCaptureLost() {
+ has_capture_ = false;
+}
+
+void ActionableView::SetAccessibleName(const base::string16& name) {
+ accessible_name_ = name;
+}
+
+void ActionableView::OnPaintFocusBorder(gfx::Canvas* canvas) {
+ if (HasFocus() && (focusable() || IsAccessibilityFocusable()))
+ DrawBorder(canvas, GetLocalBounds());
+}
+
+void ActionableView::OnGestureEvent(ui::GestureEvent* event) {
+ if (event->type() == ui::ET_GESTURE_TAP && PerformAction(*event))
+ event->SetHandled();
+}
+
+void ActionableView::GetAccessibleState(ui::AccessibleViewState* state) {
+ state->role = ui::AccessibilityTypes::ROLE_PUSHBUTTON;
+ state->name = accessible_name_;
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/system/tray/actionable_view.h b/chromium/ash/system/tray/actionable_view.h
new file mode 100644
index 00000000000..0d0f1a1c30a
--- /dev/null
+++ b/chromium/ash/system/tray/actionable_view.h
@@ -0,0 +1,63 @@
+// Copyright 2013 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 ASH_SYSTEM_TRAY_ACTIONABLE_VIEW_H_
+#define ASH_SYSTEM_TRAY_ACTIONABLE_VIEW_H_
+
+#include "ash/ash_export.h"
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "ui/views/view.h"
+
+namespace ash {
+namespace internal {
+
+// A focusable view that performs an action when user clicks on it, or presses
+// enter or space when focused. Note that the action is triggered on mouse-up,
+// instead of on mouse-down. So if user presses the mouse on the view, then
+// moves the mouse out of the view and then releases, then the action will not
+// be performed.
+// Exported for SystemTray.
+class ASH_EXPORT ActionableView : public views::View {
+ public:
+ ActionableView();
+
+ virtual ~ActionableView();
+
+ void SetAccessibleName(const base::string16& name);
+ const base::string16& accessible_name() const { return accessible_name_; }
+
+ static const char kViewClassName[];
+
+ protected:
+ void DrawBorder(gfx::Canvas* canvas, const gfx::Rect& bounds);
+
+ // Performs an action when user clicks on the view (on mouse-press event), or
+ // presses a key when this view is in focus. Returns true if the event has
+ // been handled and an action was performed. Returns false otherwise.
+ virtual bool PerformAction(const ui::Event& event) = 0;
+
+ // Overridden from views::View.
+ virtual const char* GetClassName() const OVERRIDE;
+ virtual bool OnKeyPressed(const ui::KeyEvent& event) OVERRIDE;
+ virtual bool OnMousePressed(const ui::MouseEvent& event) OVERRIDE;
+ virtual void OnMouseReleased(const ui::MouseEvent& event) OVERRIDE;
+ virtual void OnMouseCaptureLost() OVERRIDE;
+ virtual void GetAccessibleState(ui::AccessibleViewState* state) OVERRIDE;
+ virtual void OnPaintFocusBorder(gfx::Canvas* canvas) OVERRIDE;
+
+ // Overridden from ui::EventHandler.
+ virtual void OnGestureEvent(ui::GestureEvent* event) OVERRIDE;
+
+ private:
+ base::string16 accessible_name_;
+ bool has_capture_;
+
+ DISALLOW_COPY_AND_ASSIGN(ActionableView);
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_SYSTEM_TRAY_ACTIONABLE_VIEW_H_
diff --git a/chromium/ash/system/tray/fixed_sized_image_view.cc b/chromium/ash/system/tray/fixed_sized_image_view.cc
new file mode 100644
index 00000000000..d0d49b92f21
--- /dev/null
+++ b/chromium/ash/system/tray/fixed_sized_image_view.cc
@@ -0,0 +1,27 @@
+// Copyright 2013 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.
+
+#include "ash/system/tray/fixed_sized_image_view.h"
+
+namespace ash {
+namespace internal {
+
+FixedSizedImageView::FixedSizedImageView(int width, int height)
+ : width_(width),
+ height_(height) {
+ SetHorizontalAlignment(views::ImageView::CENTER);
+ SetVerticalAlignment(views::ImageView::CENTER);
+}
+
+FixedSizedImageView::~FixedSizedImageView() {
+}
+
+gfx::Size FixedSizedImageView::GetPreferredSize() {
+ gfx::Size size = views::ImageView::GetPreferredSize();
+ return gfx::Size(width_ ? width_ : size.width(),
+ height_ ? height_ : size.height());
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/system/tray/fixed_sized_image_view.h b/chromium/ash/system/tray/fixed_sized_image_view.h
new file mode 100644
index 00000000000..1e5c7cda785
--- /dev/null
+++ b/chromium/ash/system/tray/fixed_sized_image_view.h
@@ -0,0 +1,35 @@
+// Copyright 2013 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 ASH_SYSTEM_TRAY_FIXED_SIZED_IMAGE_VIEW_H_
+#define ASH_SYSTEM_TRAY_FIXED_SIZED_IMAGE_VIEW_H_
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "ui/views/controls/image_view.h"
+
+namespace ash {
+namespace internal {
+
+// An image view with a specified width and height (kTrayPopupDetailsIconWidth).
+// If the specified width or height is zero, then the image size is used for
+// that dimension.
+class FixedSizedImageView : public views::ImageView {
+ public:
+ FixedSizedImageView(int width, int height);
+ virtual ~FixedSizedImageView();
+
+ private:
+ virtual gfx::Size GetPreferredSize() OVERRIDE;
+
+ int width_;
+ int height_;
+
+ DISALLOW_COPY_AND_ASSIGN(FixedSizedImageView);
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_SYSTEM_TRAY_FIXED_SIZED_IMAGE_VIEW_H_
diff --git a/chromium/ash/system/tray/fixed_sized_scroll_view.cc b/chromium/ash/system/tray/fixed_sized_scroll_view.cc
new file mode 100644
index 00000000000..c773027d7fd
--- /dev/null
+++ b/chromium/ash/system/tray/fixed_sized_scroll_view.cc
@@ -0,0 +1,61 @@
+// Copyright 2013 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.
+
+#include "ash/system/tray/fixed_sized_scroll_view.h"
+
+namespace ash {
+namespace internal {
+
+FixedSizedScrollView::FixedSizedScrollView() {
+ set_notify_enter_exit_on_child(true);
+}
+
+FixedSizedScrollView::~FixedSizedScrollView() {
+}
+
+void FixedSizedScrollView::SetContentsView(views::View* view) {
+ SetContents(view);
+ view->SetBoundsRect(gfx::Rect(view->GetPreferredSize()));
+}
+
+void FixedSizedScrollView::SetFixedSize(const gfx::Size& size) {
+ if (fixed_size_ == size)
+ return;
+ fixed_size_ = size;
+ PreferredSizeChanged();
+}
+
+gfx::Size FixedSizedScrollView::GetPreferredSize() {
+ gfx::Size size = fixed_size_.IsEmpty() ?
+ contents()->GetPreferredSize() : fixed_size_;
+ gfx::Insets insets = GetInsets();
+ size.Enlarge(insets.width(), insets.height());
+ return size;
+}
+
+void FixedSizedScrollView::Layout() {
+ gfx::Rect bounds = gfx::Rect(contents()->GetPreferredSize());
+ bounds.set_width(std::max(0, width() - GetScrollBarWidth()));
+ contents()->SetBoundsRect(bounds);
+
+ views::ScrollView::Layout();
+ if (!vertical_scroll_bar()->visible()) {
+ gfx::Rect bounds = contents()->bounds();
+ bounds.set_width(bounds.width() + GetScrollBarWidth());
+ contents()->SetBoundsRect(bounds);
+ }
+}
+
+void FixedSizedScrollView::OnBoundsChanged(const gfx::Rect& previous_bounds) {
+ gfx::Rect bounds = gfx::Rect(contents()->GetPreferredSize());
+ bounds.set_width(std::max(0, width() - GetScrollBarWidth()));
+ contents()->SetBoundsRect(bounds);
+}
+
+void FixedSizedScrollView::OnPaintFocusBorder(gfx::Canvas* canvas) {
+ // Do not paint the focus border.
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/system/tray/fixed_sized_scroll_view.h b/chromium/ash/system/tray/fixed_sized_scroll_view.h
new file mode 100644
index 00000000000..c5d9bc0a099
--- /dev/null
+++ b/chromium/ash/system/tray/fixed_sized_scroll_view.h
@@ -0,0 +1,47 @@
+// Copyright 2013 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 ASH_SYSTEM_TRAY_FIXED_SIZED_SCROLL_VIEW_H_
+#define ASH_SYSTEM_TRAY_FIXED_SIZED_SCROLL_VIEW_H_
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "ui/views/controls/scroll_view.h"
+
+namespace ash {
+namespace internal {
+
+// A custom scroll-view that has a specified dimension.
+class FixedSizedScrollView : public views::ScrollView {
+ public:
+ FixedSizedScrollView();
+ virtual ~FixedSizedScrollView();
+
+ void SetContentsView(views::View* view);
+
+ // Change the fixed size of the view. Invalidates the layout (by calling
+ // PreferredSizeChanged()).
+ void SetFixedSize(const gfx::Size& size);
+
+ void set_fixed_size(const gfx::Size& size) { fixed_size_ = size; }
+
+ // Overridden from views::View:
+ virtual gfx::Size GetPreferredSize() OVERRIDE;
+ virtual void Layout() OVERRIDE;
+
+ protected:
+ // Overridden from views::View:
+ virtual void OnBoundsChanged(const gfx::Rect& previous_bounds) OVERRIDE;
+ virtual void OnPaintFocusBorder(gfx::Canvas* canvas) OVERRIDE;
+
+ private:
+ gfx::Size fixed_size_;
+
+ DISALLOW_COPY_AND_ASSIGN(FixedSizedScrollView);
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_SYSTEM_TRAY_FIXED_SIZED_SCROLL_VIEW_H_
diff --git a/chromium/ash/system/tray/hover_highlight_view.cc b/chromium/ash/system/tray/hover_highlight_view.cc
new file mode 100644
index 00000000000..3a511a24ace
--- /dev/null
+++ b/chromium/ash/system/tray/hover_highlight_view.cc
@@ -0,0 +1,173 @@
+// Copyright 2012 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.
+
+#include "ash/system/tray/hover_highlight_view.h"
+
+#include "ash/system/tray/fixed_sized_image_view.h"
+#include "ash/system/tray/tray_constants.h"
+#include "ash/system/tray/view_click_listener.h"
+#include "grit/ui_resources.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "ui/gfx/canvas.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/fill_layout.h"
+
+namespace {
+
+const int kPopupDetailLabelExtraLeftMargin = 8;
+const int kCheckLabelPadding = 4;
+
+} // namespace
+
+namespace ash {
+namespace internal {
+
+HoverHighlightView::HoverHighlightView(ViewClickListener* listener)
+ : listener_(listener),
+ text_label_(NULL),
+ highlight_color_(kHoverBackgroundColor),
+ default_color_(0),
+ text_highlight_color_(0),
+ text_default_color_(0),
+ hover_(false),
+ expandable_(false) {
+ set_notify_enter_exit_on_child(true);
+}
+
+HoverHighlightView::~HoverHighlightView() {
+}
+
+void HoverHighlightView::AddIconAndLabel(const gfx::ImageSkia& image,
+ const base::string16& text,
+ gfx::Font::FontStyle style) {
+ SetLayoutManager(new views::BoxLayout(
+ views::BoxLayout::kHorizontal, 0, 3, kTrayPopupPaddingBetweenItems));
+ views::ImageView* image_view =
+ new FixedSizedImageView(kTrayPopupDetailsIconWidth, 0);
+ image_view->SetImage(image);
+ AddChildView(image_view);
+
+ text_label_ = new views::Label(text);
+ text_label_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
+ text_label_->SetFont(text_label_->font().DeriveFont(0, style));
+ if (text_default_color_)
+ text_label_->SetEnabledColor(text_default_color_);
+ AddChildView(text_label_);
+
+ SetAccessibleName(text);
+}
+
+views::Label* HoverHighlightView::AddLabel(const base::string16& text,
+ gfx::Font::FontStyle style) {
+ SetLayoutManager(new views::FillLayout());
+ text_label_ = new views::Label(text);
+ int margin = kTrayPopupPaddingHorizontal + kPopupDetailLabelExtraLeftMargin;
+ int left_margin = 0;
+ int right_margin = 0;
+ if (base::i18n::IsRTL())
+ right_margin = margin;
+ else
+ left_margin = margin;
+ text_label_->set_border(
+ views::Border::CreateEmptyBorder(5, left_margin, 5, right_margin));
+ text_label_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
+ text_label_->SetFont(text_label_->font().DeriveFont(0, style));
+ // Do not set alpha value in disable color. It will have issue with elide
+ // blending filter in disabled state for rendering label text color.
+ text_label_->SetDisabledColor(SkColorSetARGB(255, 127, 127, 127));
+ if (text_default_color_)
+ text_label_->SetEnabledColor(text_default_color_);
+ AddChildView(text_label_);
+
+ SetAccessibleName(text);
+ return text_label_;
+}
+
+views::Label* HoverHighlightView::AddCheckableLabel(const base::string16& text,
+ gfx::Font::FontStyle style,
+ bool checked) {
+ if (checked) {
+ ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
+ const gfx::ImageSkia* check =
+ rb.GetImageNamed(IDR_MENU_CHECK).ToImageSkia();
+ int margin = kTrayPopupPaddingHorizontal + kPopupDetailLabelExtraLeftMargin
+ - kCheckLabelPadding;
+ SetLayoutManager(new views::BoxLayout(
+ views::BoxLayout::kHorizontal, 0, 3, kCheckLabelPadding));
+ views::ImageView* image_view = new FixedSizedImageView(margin, 0);
+ image_view->SetImage(check);
+ image_view->SetHorizontalAlignment(views::ImageView::TRAILING);
+ AddChildView(image_view);
+
+ text_label_ = new views::Label(text);
+ text_label_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
+ text_label_->SetFont(text_label_->font().DeriveFont(0, style));
+ text_label_->SetDisabledColor(SkColorSetARGB(127, 0, 0, 0));
+ if (text_default_color_)
+ text_label_->SetEnabledColor(text_default_color_);
+ AddChildView(text_label_);
+
+ SetAccessibleName(text);
+ return text_label_;
+ }
+ return AddLabel(text, style);
+}
+
+void HoverHighlightView::SetExpandable(bool expandable) {
+ if (expandable != expandable_) {
+ expandable_ = expandable;
+ InvalidateLayout();
+ }
+}
+
+bool HoverHighlightView::PerformAction(const ui::Event& event) {
+ if (!listener_)
+ return false;
+ listener_->OnViewClicked(this);
+ return true;
+}
+
+gfx::Size HoverHighlightView::GetPreferredSize() {
+ gfx::Size size = ActionableView::GetPreferredSize();
+ if (!expandable_ || size.height() < kTrayPopupItemHeight)
+ size.set_height(kTrayPopupItemHeight);
+ return size;
+}
+
+int HoverHighlightView::GetHeightForWidth(int width) {
+ return GetPreferredSize().height();
+}
+
+void HoverHighlightView::OnMouseEntered(const ui::MouseEvent& event) {
+ hover_ = true;
+ if (text_highlight_color_ && text_label_)
+ text_label_->SetEnabledColor(text_highlight_color_);
+ SchedulePaint();
+}
+
+void HoverHighlightView::OnMouseExited(const ui::MouseEvent& event) {
+ hover_ = false;
+ if (text_default_color_ && text_label_)
+ text_label_->SetEnabledColor(text_default_color_);
+ SchedulePaint();
+}
+
+void HoverHighlightView::OnEnabledChanged() {
+ for (int i = 0; i < child_count(); ++i)
+ child_at(i)->SetEnabled(enabled());
+}
+
+void HoverHighlightView::OnPaintBackground(gfx::Canvas* canvas) {
+ canvas->DrawColor(hover_ ? highlight_color_ : default_color_);
+}
+
+void HoverHighlightView::OnFocus() {
+ ScrollRectToVisible(gfx::Rect(gfx::Point(), size()));
+ ActionableView::OnFocus();
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/system/tray/hover_highlight_view.h b/chromium/ash/system/tray/hover_highlight_view.h
new file mode 100644
index 00000000000..13630ca8b7e
--- /dev/null
+++ b/chromium/ash/system/tray/hover_highlight_view.h
@@ -0,0 +1,90 @@
+// Copyright 2013 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 ASH_SYSTEM_TRAY_HOVER_HIGHLIGHT_VIEW_H_
+#define ASH_SYSTEM_TRAY_HOVER_HIGHLIGHT_VIEW_H_
+
+#include "ash/system/tray/actionable_view.h"
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "ui/gfx/font.h"
+
+namespace views {
+class Label;
+}
+
+namespace ash {
+namespace internal {
+
+class ViewClickListener;
+
+// A view that changes background color on hover, and triggers a callback in the
+// associated ViewClickListener on click. The view can also be forced to
+// maintain a fixed height.
+class HoverHighlightView : public ActionableView {
+ public:
+ explicit HoverHighlightView(ViewClickListener* listener);
+ virtual ~HoverHighlightView();
+
+ // Convenience function for adding an icon and a label. This also sets the
+ // accessible name.
+ void AddIconAndLabel(const gfx::ImageSkia& image,
+ const base::string16& text,
+ gfx::Font::FontStyle style);
+
+ // Convenience function for adding a label with padding on the left for a
+ // blank icon. This also sets the accessible name.
+ // Returns label after parenting it.
+ views::Label* AddLabel(const base::string16& text,
+ gfx::Font::FontStyle style);
+
+ // Convenience function for adding an optional check and a label. In the
+ // absence of a check, padding is added to align with checked items.
+ // Returns label after parenting it.
+ views::Label* AddCheckableLabel(const base::string16& text,
+ gfx::Font::FontStyle style,
+ bool checked);
+
+ // Allows view to expand its height.
+ // Size of unexapandable view is fixed and equals to kTrayPopupItemHeight.
+ void SetExpandable(bool expandable);
+
+ void set_highlight_color(SkColor color) { highlight_color_ = color; }
+ void set_default_color(SkColor color) { default_color_ = color; }
+ void set_text_highlight_color(SkColor c) { text_highlight_color_ = c; }
+ void set_text_default_color(SkColor color) { text_default_color_ = color; }
+
+ views::Label* text_label() { return text_label_; }
+
+ bool hover() const { return hover_; }
+
+ private:
+ // Overridden from ActionableView:
+ virtual bool PerformAction(const ui::Event& event) OVERRIDE;
+
+ // Overridden from views::View.
+ virtual gfx::Size GetPreferredSize() OVERRIDE;
+ virtual int GetHeightForWidth(int width) OVERRIDE;
+ virtual void OnMouseEntered(const ui::MouseEvent& event) OVERRIDE;
+ virtual void OnMouseExited(const ui::MouseEvent& event) OVERRIDE;
+ virtual void OnEnabledChanged() OVERRIDE;
+ virtual void OnPaintBackground(gfx::Canvas* canvas) OVERRIDE;
+ virtual void OnFocus() OVERRIDE;
+
+ ViewClickListener* listener_;
+ views::Label* text_label_;
+ SkColor highlight_color_;
+ SkColor default_color_;
+ SkColor text_highlight_color_;
+ SkColor text_default_color_;
+ bool hover_;
+ bool expandable_;
+
+ DISALLOW_COPY_AND_ASSIGN(HoverHighlightView);
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_SYSTEM_TRAY_HOVER_HIGHLIGHT_VIEW_H_
diff --git a/chromium/ash/system/tray/special_popup_row.cc b/chromium/ash/system/tray/special_popup_row.cc
new file mode 100644
index 00000000000..896d1516df9
--- /dev/null
+++ b/chromium/ash/system/tray/special_popup_row.cc
@@ -0,0 +1,133 @@
+// Copyright (c) 2013 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.
+
+#include "ash/system/tray/special_popup_row.h"
+
+#include "ash/system/tray/hover_highlight_view.h"
+#include "ash/system/tray/throbber_view.h"
+#include "ash/system/tray/tray_constants.h"
+#include "ash/system/tray/tray_popup_header_button.h"
+#include "grit/ash_resources.h"
+#include "grit/ash_strings.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "ui/gfx/canvas.h"
+#include "ui/gfx/rect.h"
+#include "ui/views/border.h"
+#include "ui/views/layout/box_layout.h"
+#include "ui/views/painter.h"
+
+namespace ash {
+namespace internal {
+
+namespace {
+
+const int kIconPaddingLeft = 5;
+const int kSpecialPopupRowHeight = 55;
+const int kBorderHeight = 1;
+const SkColor kBorderColor = SkColorSetRGB(0xaa, 0xaa, 0xaa);
+
+views::View* CreatePopupHeaderButtonsContainer() {
+ views::View* view = new views::View;
+ view->SetLayoutManager(new
+ views::BoxLayout(views::BoxLayout::kHorizontal, 0, 0, -1));
+ view->set_border(views::Border::CreateEmptyBorder(0, 0, 0, 5));
+ return view;
+}
+
+} // namespace
+
+SpecialPopupRow::SpecialPopupRow()
+ : content_(NULL),
+ button_container_(NULL) {
+ set_background(views::Background::CreateSolidBackground(
+ kHeaderBackgroundColor));
+ set_border(views::Border::CreateSolidSidedBorder(
+ kBorderHeight, 0, 0, 0, kBorderColor));
+ SetLayoutManager(
+ new views::BoxLayout(views::BoxLayout::kHorizontal, 0, 0, 0));
+}
+
+SpecialPopupRow::~SpecialPopupRow() {
+}
+
+void SpecialPopupRow::SetTextLabel(int string_id, ViewClickListener* listener) {
+ ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
+ HoverHighlightView* container = new HoverHighlightView(listener);
+ container->SetLayoutManager(new
+ views::BoxLayout(views::BoxLayout::kHorizontal, 0, 3, kIconPaddingLeft));
+
+ container->set_highlight_color(SkColorSetARGB(0, 0, 0, 0));
+ container->set_default_color(SkColorSetARGB(0, 0, 0, 0));
+ container->set_text_highlight_color(kHeaderTextColorHover);
+ container->set_text_default_color(kHeaderTextColorNormal);
+
+ container->AddIconAndLabel(
+ *rb.GetImageNamed(IDR_AURA_UBER_TRAY_LESS).ToImageSkia(),
+ rb.GetLocalizedString(string_id),
+ gfx::Font::BOLD);
+
+ container->set_border(views::Border::CreateEmptyBorder(0,
+ kTrayPopupPaddingHorizontal, 0, 0));
+
+ container->SetAccessibleName(
+ rb.GetLocalizedString(IDS_ASH_STATUS_TRAY_PREVIOUS_MENU));
+ SetContent(container);
+}
+
+void SpecialPopupRow::SetContent(views::View* view) {
+ CHECK(!content_);
+ content_ = view;
+ AddChildViewAt(content_, 0);
+}
+
+void SpecialPopupRow::AddButton(TrayPopupHeaderButton* button) {
+ if (!button_container_) {
+ button_container_ = CreatePopupHeaderButtonsContainer();
+ AddChildView(button_container_);
+ }
+ button_container_->AddChildView(button);
+}
+
+void SpecialPopupRow::AddThrobber(ThrobberView* throbber) {
+ if (!button_container_) {
+ button_container_ = CreatePopupHeaderButtonsContainer();
+ AddChildView(button_container_);
+ }
+ button_container_->AddChildView(throbber);
+}
+
+gfx::Size SpecialPopupRow::GetPreferredSize() {
+ gfx::Size size = views::View::GetPreferredSize();
+ size.set_height(kSpecialPopupRowHeight);
+ return size;
+}
+
+int SpecialPopupRow::GetHeightForWidth(int width) {
+ return kSpecialPopupRowHeight;
+}
+
+void SpecialPopupRow::Layout() {
+ views::View::Layout();
+ gfx::Rect content_bounds = GetContentsBounds();
+ if (content_bounds.IsEmpty())
+ return;
+ if (!button_container_) {
+ content_->SetBoundsRect(GetContentsBounds());
+ return;
+ }
+
+ gfx::Rect bounds(button_container_->GetPreferredSize());
+ bounds.set_height(content_bounds.height());
+ gfx::Rect container_bounds = content_bounds;
+ container_bounds.ClampToCenteredSize(bounds.size());
+ container_bounds.set_x(content_bounds.width() - container_bounds.width());
+ button_container_->SetBoundsRect(container_bounds);
+
+ bounds = content_->bounds();
+ bounds.set_width(button_container_->x());
+ content_->SetBoundsRect(bounds);
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/system/tray/special_popup_row.h b/chromium/ash/system/tray/special_popup_row.h
new file mode 100644
index 00000000000..ee068e4eb8a
--- /dev/null
+++ b/chromium/ash/system/tray/special_popup_row.h
@@ -0,0 +1,54 @@
+// Copyright (c) 2013 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 ASH_SYSTEM_TRAY_SPECIAL_POPUP_ROW_H_
+#define ASH_SYSTEM_TRAY_SPECIAL_POPUP_ROW_H_
+
+#include "ui/gfx/size.h"
+#include "ui/views/view.h"
+
+namespace views {
+class Label;
+}
+
+namespace ash {
+namespace internal {
+
+class ThrobberView;
+class TrayItemView;
+class TrayPopupHeaderButton;
+class ViewClickListener;
+
+// The 'special' looking row in the uber-tray popups. This is usually the bottom
+// row in the popups, and has a fixed height.
+class SpecialPopupRow : public views::View {
+ public:
+ SpecialPopupRow();
+ virtual ~SpecialPopupRow();
+
+ void SetTextLabel(int string_id, ViewClickListener* listener);
+ void SetContent(views::View* view);
+
+ void AddButton(TrayPopupHeaderButton* button);
+ void AddThrobber(ThrobberView* throbber);
+
+ views::View* content() const { return content_; }
+
+ private:
+ // Overridden from views::View.
+ virtual gfx::Size GetPreferredSize() OVERRIDE;
+ virtual int GetHeightForWidth(int width) OVERRIDE;
+ virtual void Layout() OVERRIDE;
+
+ views::View* content_;
+ views::View* button_container_;
+ views::Label* text_label_;
+
+ DISALLOW_COPY_AND_ASSIGN(SpecialPopupRow);
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_SYSTEM_TRAY_SPECIAL_POPUP_ROW_H_
diff --git a/chromium/ash/system/tray/system_tray.cc b/chromium/ash/system/tray/system_tray.cc
new file mode 100644
index 00000000000..47157ea46ff
--- /dev/null
+++ b/chromium/ash/system/tray/system_tray.cc
@@ -0,0 +1,661 @@
+// Copyright (c) 2012 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.
+
+#include "ash/system/tray/system_tray.h"
+
+#include "ash/ash_switches.h"
+#include "ash/shelf/shelf_layout_manager.h"
+#include "ash/shell.h"
+#include "ash/shell/panel_window.h"
+#include "ash/shell_window_ids.h"
+#include "ash/system/bluetooth/tray_bluetooth.h"
+#include "ash/system/brightness/tray_brightness.h"
+#include "ash/system/chromeos/tray_tracing.h"
+#include "ash/system/date/tray_date.h"
+#include "ash/system/drive/tray_drive.h"
+#include "ash/system/ime/tray_ime.h"
+#include "ash/system/monitor/tray_monitor.h"
+#include "ash/system/session_length_limit/tray_session_length_limit.h"
+#include "ash/system/status_area_widget.h"
+#include "ash/system/tray/system_tray_delegate.h"
+#include "ash/system/tray/system_tray_item.h"
+#include "ash/system/tray/tray_bubble_wrapper.h"
+#include "ash/system/tray/tray_constants.h"
+#include "ash/system/tray_accessibility.h"
+#include "ash/system/tray_caps_lock.h"
+#include "ash/system/tray_update.h"
+#include "ash/system/user/login_status.h"
+#include "ash/system/user/tray_user.h"
+#include "ash/system/web_notification/web_notification_tray.h"
+#include "base/command_line.h"
+#include "base/logging.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/timer/timer.h"
+#include "grit/ash_strings.h"
+#include "ui/aura/root_window.h"
+#include "ui/base/events/event_constants.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/compositor/layer.h"
+#include "ui/gfx/canvas.h"
+#include "ui/gfx/screen.h"
+#include "ui/gfx/skia_util.h"
+#include "ui/views/border.h"
+#include "ui/views/controls/label.h"
+#include "ui/views/layout/box_layout.h"
+#include "ui/views/layout/fill_layout.h"
+#include "ui/views/view.h"
+
+#if defined(OS_CHROMEOS)
+#include "ash/system/chromeos/audio/tray_audio.h"
+#include "ash/system/chromeos/enterprise/tray_enterprise.h"
+#include "ash/system/chromeos/managed/tray_locally_managed_user.h"
+#include "ash/system/chromeos/network/tray_network.h"
+#include "ash/system/chromeos/network/tray_sms.h"
+#include "ash/system/chromeos/network/tray_vpn.h"
+#include "ash/system/chromeos/power/tray_power.h"
+#include "ash/system/chromeos/screen_security/screen_capture_tray_item.h"
+#include "ash/system/chromeos/screen_security/screen_share_tray_item.h"
+#include "ash/system/chromeos/settings/tray_settings.h"
+#include "ash/system/chromeos/tray_display.h"
+#include "ui/message_center/message_center.h"
+#endif
+
+using views::TrayBubbleView;
+
+namespace ash {
+
+// The minimum width of the system tray menu width.
+const int kMinimumSystemTrayMenuWidth = 300;
+
+namespace internal {
+
+// Class to initialize and manage the SystemTrayBubble and TrayBubbleWrapper
+// instances for a bubble.
+
+class SystemBubbleWrapper {
+ public:
+ // Takes ownership of |bubble|.
+ explicit SystemBubbleWrapper(internal::SystemTrayBubble* bubble)
+ : bubble_(bubble) {
+ }
+
+ // Initializes the bubble view and creates |bubble_wrapper_|.
+ void InitView(TrayBackgroundView* tray,
+ views::View* anchor,
+ TrayBubbleView::InitParams* init_params) {
+ DCHECK(anchor);
+ user::LoginStatus login_status =
+ Shell::GetInstance()->system_tray_delegate()->GetUserLoginStatus();
+ bubble_->InitView(anchor, login_status, init_params);
+ bubble_wrapper_.reset(
+ new internal::TrayBubbleWrapper(tray, bubble_->bubble_view()));
+ if (ash::switches::UseAlternateShelfLayout()) {
+ // The system bubble should not have an arrow.
+ bubble_->bubble_view()->SetArrowPaintType(
+ views::BubbleBorder::PAINT_NONE);
+ }
+ }
+
+ // Convenience accessors:
+ SystemTrayBubble* bubble() const { return bubble_.get(); }
+ SystemTrayBubble::BubbleType bubble_type() const {
+ return bubble_->bubble_type();
+ }
+ TrayBubbleView* bubble_view() const { return bubble_->bubble_view(); }
+
+ private:
+ scoped_ptr<internal::SystemTrayBubble> bubble_;
+ scoped_ptr<internal::TrayBubbleWrapper> bubble_wrapper_;
+
+ DISALLOW_COPY_AND_ASSIGN(SystemBubbleWrapper);
+};
+
+} // namespace internal
+
+// SystemTray
+
+using internal::SystemTrayBubble;
+
+SystemTray::SystemTray(internal::StatusAreaWidget* status_area_widget)
+ : internal::TrayBackgroundView(status_area_widget),
+ items_(),
+ default_bubble_height_(0),
+ hide_notifications_(false),
+ full_system_tray_menu_(false),
+ tray_accessibility_(NULL) {
+ SetContentsBackground();
+}
+
+SystemTray::~SystemTray() {
+ // Destroy any child views that might have back pointers before ~View().
+ system_bubble_.reset();
+ notification_bubble_.reset();
+ for (std::vector<SystemTrayItem*>::iterator it = items_.begin();
+ it != items_.end();
+ ++it) {
+ (*it)->DestroyTrayView();
+ }
+}
+
+void SystemTray::InitializeTrayItems(SystemTrayDelegate* delegate) {
+ internal::TrayBackgroundView::Initialize();
+ CreateItems(delegate);
+}
+
+void SystemTray::CreateItems(SystemTrayDelegate* delegate) {
+#if !defined(OS_WIN)
+ AddTrayItem(new internal::TraySessionLengthLimit(this));
+ // In multi-profile user mode we can have multiple user tiles.
+ ash::Shell* shell = ash::Shell::GetInstance();
+ int maximum_user_profiles =
+ shell->delegate()->IsMultiProfilesEnabled() ?
+ shell->session_state_delegate()->GetMaximumNumberOfLoggedInUsers() :
+ 0;
+ // Note: We purposely use one more item then logged in users to account for
+ // the additional separator.
+ for (int i = 0; i <= maximum_user_profiles; i++)
+ AddTrayItem(new internal::TrayUser(this, i));
+
+#endif
+#if defined(OS_CHROMEOS)
+ AddTrayItem(new internal::TrayEnterprise(this));
+ AddTrayItem(new internal::TrayLocallyManagedUser(this));
+#endif
+ AddTrayItem(new internal::TrayIME(this));
+ tray_accessibility_ = new internal::TrayAccessibility(this);
+ AddTrayItem(tray_accessibility_);
+#if defined(OS_CHROMEOS)
+ AddTrayItem(new internal::TrayTracing(this));
+ AddTrayItem(
+ new internal::TrayPower(this, message_center::MessageCenter::Get()));
+#endif
+#if defined(OS_CHROMEOS)
+ AddTrayItem(new internal::TrayNetwork(this));
+ AddTrayItem(new internal::TrayVPN(this));
+ AddTrayItem(new internal::TraySms(this));
+#endif
+#if !defined(OS_WIN)
+ AddTrayItem(new internal::TrayBluetooth(this));
+#endif
+ AddTrayItem(new internal::TrayDrive(this));
+#if defined(OS_CHROMEOS)
+ AddTrayItem(new internal::TrayDisplay(this));
+ AddTrayItem(new internal::ScreenCaptureTrayItem(this));
+ AddTrayItem(new internal::ScreenShareTrayItem(this));
+ AddTrayItem(new internal::TrayAudio(this));
+#endif
+#if !defined(OS_WIN)
+ AddTrayItem(new internal::TrayBrightness(this));
+ AddTrayItem(new internal::TrayCapsLock(this));
+#endif
+#if defined(OS_CHROMEOS)
+ AddTrayItem(new internal::TraySettings(this));
+#endif
+ AddTrayItem(new internal::TrayUpdate(this));
+ AddTrayItem(new internal::TrayDate(this));
+
+#if defined(OS_LINUX)
+ // Add memory monitor if enabled.
+ CommandLine* cmd = CommandLine::ForCurrentProcess();
+ if (cmd->HasSwitch(ash::switches::kAshEnableMemoryMonitor))
+ AddTrayItem(new internal::TrayMonitor(this));
+#endif
+
+ SetVisible(ash::Shell::GetInstance()->system_tray_delegate()->
+ GetTrayVisibilityOnStartup());
+}
+
+void SystemTray::AddTrayItem(SystemTrayItem* item) {
+ items_.push_back(item);
+
+ SystemTrayDelegate* delegate = Shell::GetInstance()->system_tray_delegate();
+ views::View* tray_item = item->CreateTrayView(delegate->GetUserLoginStatus());
+ item->UpdateAfterShelfAlignmentChange(shelf_alignment());
+
+ if (tray_item) {
+ tray_container()->AddChildViewAt(tray_item, 0);
+ PreferredSizeChanged();
+ tray_item_map_[item] = tray_item;
+ }
+}
+
+void SystemTray::RemoveTrayItem(SystemTrayItem* item) {
+ NOTIMPLEMENTED();
+}
+
+const std::vector<SystemTrayItem*>& SystemTray::GetTrayItems() const {
+ return items_.get();
+}
+
+void SystemTray::ShowDefaultView(BubbleCreationType creation_type) {
+ ShowDefaultViewWithOffset(creation_type,
+ TrayBubbleView::InitParams::kArrowDefaultOffset);
+}
+
+void SystemTray::ShowDetailedView(SystemTrayItem* item,
+ int close_delay,
+ bool activate,
+ BubbleCreationType creation_type) {
+ std::vector<SystemTrayItem*> items;
+ items.push_back(item);
+ ShowItems(items, true, activate, creation_type, GetTrayXOffset(item));
+ if (system_bubble_)
+ system_bubble_->bubble()->StartAutoCloseTimer(close_delay);
+}
+
+void SystemTray::SetDetailedViewCloseDelay(int close_delay) {
+ if (HasSystemBubbleType(SystemTrayBubble::BUBBLE_TYPE_DETAILED))
+ system_bubble_->bubble()->StartAutoCloseTimer(close_delay);
+}
+
+void SystemTray::HideDetailedView(SystemTrayItem* item) {
+ if (item != detailed_item_)
+ return;
+ DestroySystemBubble();
+ UpdateNotificationBubble();
+}
+
+void SystemTray::ShowNotificationView(SystemTrayItem* item) {
+ if (std::find(notification_items_.begin(), notification_items_.end(), item)
+ != notification_items_.end())
+ return;
+ notification_items_.push_back(item);
+ UpdateNotificationBubble();
+}
+
+void SystemTray::HideNotificationView(SystemTrayItem* item) {
+ std::vector<SystemTrayItem*>::iterator found_iter =
+ std::find(notification_items_.begin(), notification_items_.end(), item);
+ if (found_iter == notification_items_.end())
+ return;
+ notification_items_.erase(found_iter);
+ // Only update the notification bubble if visible (i.e. don't create one).
+ if (notification_bubble_)
+ UpdateNotificationBubble();
+}
+
+void SystemTray::UpdateAfterLoginStatusChange(user::LoginStatus login_status) {
+ DestroySystemBubble();
+ UpdateNotificationBubble();
+
+ for (std::vector<SystemTrayItem*>::iterator it = items_.begin();
+ it != items_.end();
+ ++it) {
+ (*it)->UpdateAfterLoginStatusChange(login_status);
+ }
+
+ // Items default to SHELF_ALIGNMENT_BOTTOM. Update them if the initial
+ // position of the shelf differs.
+ if (shelf_alignment() != SHELF_ALIGNMENT_BOTTOM)
+ UpdateAfterShelfAlignmentChange(shelf_alignment());
+
+ SetVisible(true);
+ PreferredSizeChanged();
+}
+
+void SystemTray::UpdateAfterShelfAlignmentChange(ShelfAlignment alignment) {
+ for (std::vector<SystemTrayItem*>::iterator it = items_.begin();
+ it != items_.end();
+ ++it) {
+ (*it)->UpdateAfterShelfAlignmentChange(alignment);
+ }
+}
+
+void SystemTray::SetHideNotifications(bool hide_notifications) {
+ if (notification_bubble_)
+ notification_bubble_->bubble()->SetVisible(!hide_notifications);
+ hide_notifications_ = hide_notifications;
+}
+
+bool SystemTray::ShouldShowLauncher() const {
+ return system_bubble_.get() && system_bubble_->bubble()->ShouldShowLauncher();
+}
+
+bool SystemTray::HasSystemBubble() const {
+ return system_bubble_.get() != NULL;
+}
+
+bool SystemTray::HasNotificationBubble() const {
+ return notification_bubble_.get() != NULL;
+}
+
+bool SystemTray::IsPressed() {
+ // Only when a full system tray bubble gets shown true will be returned.
+ // Small bubbles (like audio modifications via keyboard) should return false.
+ // Since showing the e.g. network portion of the system tray menu will convert
+ // the |system_bubble_| from type |BUBBLE_TYPE_DEFAULT| into
+ // |BUBBLE_TYPE_DETAILED| the full tray cannot reliably be checked trhough the
+ // type. As such |full_system_tray_menu_| gets checked here.
+ return HasSystemBubble() && full_system_tray_menu_;
+}
+
+internal::SystemTrayBubble* SystemTray::GetSystemBubble() {
+ if (!system_bubble_)
+ return NULL;
+ return system_bubble_->bubble();
+}
+
+bool SystemTray::IsAnyBubbleVisible() const {
+ return ((system_bubble_.get() &&
+ system_bubble_->bubble()->IsVisible()) ||
+ (notification_bubble_.get() &&
+ notification_bubble_->bubble()->IsVisible()));
+}
+
+bool SystemTray::IsMouseInNotificationBubble() const {
+ if (!notification_bubble_)
+ return false;
+ return notification_bubble_->bubble_view()->GetBoundsInScreen().Contains(
+ Shell::GetScreen()->GetCursorScreenPoint());
+}
+
+bool SystemTray::CloseSystemBubble() const {
+ if (!system_bubble_)
+ return false;
+ system_bubble_->bubble()->Close();
+ return true;
+}
+
+bool SystemTray::CloseNotificationBubbleForTest() const {
+ if (!notification_bubble_)
+ return false;
+ notification_bubble_->bubble()->Close();
+ return true;
+}
+
+// Private methods.
+
+bool SystemTray::HasSystemBubbleType(SystemTrayBubble::BubbleType type) {
+ DCHECK(type != SystemTrayBubble::BUBBLE_TYPE_NOTIFICATION);
+ return system_bubble_.get() && system_bubble_->bubble_type() == type;
+}
+
+void SystemTray::DestroySystemBubble() {
+ system_bubble_.reset();
+ detailed_item_ = NULL;
+ UpdateWebNotifications();
+}
+
+void SystemTray::DestroyNotificationBubble() {
+ if (notification_bubble_) {
+ notification_bubble_.reset();
+ UpdateWebNotifications();
+ }
+}
+
+int SystemTray::GetTrayXOffset(SystemTrayItem* item) const {
+ // Don't attempt to align the arrow if the shelf is on the left or right.
+ if (shelf_alignment() != SHELF_ALIGNMENT_BOTTOM &&
+ shelf_alignment() != SHELF_ALIGNMENT_TOP)
+ return TrayBubbleView::InitParams::kArrowDefaultOffset;
+
+ std::map<SystemTrayItem*, views::View*>::const_iterator it =
+ tray_item_map_.find(item);
+ if (it == tray_item_map_.end())
+ return TrayBubbleView::InitParams::kArrowDefaultOffset;
+
+ const views::View* item_view = it->second;
+ if (item_view->bounds().IsEmpty()) {
+ // The bounds of item could be still empty if it does not have a visible
+ // tray view. In that case, use the default (minimum) offset.
+ return TrayBubbleView::InitParams::kArrowDefaultOffset;
+ }
+
+ gfx::Point point(item_view->width() / 2, 0);
+ ConvertPointToWidget(item_view, &point);
+ return point.x();
+}
+
+void SystemTray::ShowDefaultViewWithOffset(BubbleCreationType creation_type,
+ int arrow_offset) {
+ ShowItems(items_.get(), false, true, creation_type, arrow_offset);
+}
+
+void SystemTray::ShowItems(const std::vector<SystemTrayItem*>& items,
+ bool detailed,
+ bool can_activate,
+ BubbleCreationType creation_type,
+ int arrow_offset) {
+ // No system tray bubbles in kiosk mode.
+ if (Shell::GetInstance()->system_tray_delegate()->GetUserLoginStatus() ==
+ ash::user::LOGGED_IN_KIOSK_APP) {
+ return;
+ }
+
+ // Destroy any existing bubble and create a new one.
+ SystemTrayBubble::BubbleType bubble_type = detailed ?
+ SystemTrayBubble::BUBBLE_TYPE_DETAILED :
+ SystemTrayBubble::BUBBLE_TYPE_DEFAULT;
+
+ // Destroy the notification bubble here so that it doesn't get rebuilt
+ // while we add items to the main bubble_ (e.g. in HideNotificationView).
+ notification_bubble_.reset();
+ if (system_bubble_.get() && creation_type == BUBBLE_USE_EXISTING) {
+ system_bubble_->bubble()->UpdateView(items, bubble_type);
+ } else {
+ // Remember if the menu is a single property (like e.g. volume) or the
+ // full tray menu. Note that in case of the |BUBBLE_USE_EXISTING| case
+ // above, |full_system_tray_menu_| does not get changed since the fact that
+ // the menu is full (or not) doesn't change even if a "single property"
+ // (like network) replaces most of the menu.
+ full_system_tray_menu_ = items.size() > 1;
+ // The menu width is fixed, and it is a per language setting.
+ int menu_width = std::max(kMinimumSystemTrayMenuWidth,
+ Shell::GetInstance()->system_tray_delegate()->GetSystemTrayMenuWidth());
+
+ TrayBubbleView::InitParams init_params(TrayBubbleView::ANCHOR_TYPE_TRAY,
+ GetAnchorAlignment(),
+ menu_width,
+ kTrayPopupMaxWidth);
+ init_params.can_activate = can_activate;
+ init_params.first_item_has_no_margin =
+ ash::switches::UseAlternateShelfLayout();
+ if (detailed) {
+ // This is the case where a volume control or brightness control bubble
+ // is created.
+ init_params.max_height = default_bubble_height_;
+ init_params.arrow_color = kBackgroundColor;
+ } else {
+ init_params.arrow_color = kHeaderBackgroundColor;
+ }
+ init_params.arrow_offset = arrow_offset;
+ // For Volume and Brightness we don't want to show an arrow when
+ // they are shown in a bubble by themselves.
+ init_params.arrow_paint_type = views::BubbleBorder::PAINT_NORMAL;
+ if (items.size() == 1 && items[0]->ShouldHideArrow())
+ init_params.arrow_paint_type = views::BubbleBorder::PAINT_TRANSPARENT;
+ SystemTrayBubble* bubble = new SystemTrayBubble(this, items, bubble_type);
+ system_bubble_.reset(new internal::SystemBubbleWrapper(bubble));
+ system_bubble_->InitView(this, tray_container(), &init_params);
+ }
+ // Save height of default view for creating detailed views directly.
+ if (!detailed)
+ default_bubble_height_ = system_bubble_->bubble_view()->height();
+
+ if (detailed && items.size() > 0)
+ detailed_item_ = items[0];
+ else
+ detailed_item_ = NULL;
+
+ UpdateNotificationBubble(); // State changed, re-create notifications.
+ if (!notification_bubble_)
+ UpdateWebNotifications();
+ GetShelfLayoutManager()->UpdateAutoHideState();
+}
+
+void SystemTray::UpdateNotificationBubble() {
+ // Only show the notification bubble if we have notifications.
+ if (notification_items_.empty()) {
+ DestroyNotificationBubble();
+ return;
+ }
+ // Destroy the existing bubble before constructing a new one.
+ notification_bubble_.reset();
+ SystemTrayBubble* notification_bubble;
+ notification_bubble = new SystemTrayBubble(
+ this, notification_items_, SystemTrayBubble::BUBBLE_TYPE_NOTIFICATION);
+ views::View* anchor;
+ TrayBubbleView::AnchorType anchor_type;
+ // Tray items might want to show notifications while we are creating and
+ // initializing the |system_bubble_| - but it might not be fully initialized
+ // when coming here - this would produce a crashed like crbug.com/247416.
+ // As such we check the existence of the widget here.
+ if (system_bubble_.get() &&
+ system_bubble_->bubble_view() &&
+ system_bubble_->bubble_view()->GetWidget()) {
+ anchor = system_bubble_->bubble_view();
+ anchor_type = TrayBubbleView::ANCHOR_TYPE_BUBBLE;
+ } else {
+ anchor = tray_container();
+ anchor_type = TrayBubbleView::ANCHOR_TYPE_TRAY;
+ }
+ TrayBubbleView::InitParams init_params(anchor_type,
+ GetAnchorAlignment(),
+ kTrayPopupMinWidth,
+ kTrayPopupMaxWidth);
+ init_params.first_item_has_no_margin =
+ ash::switches::UseAlternateShelfLayout();
+ init_params.arrow_color = kBackgroundColor;
+ init_params.arrow_offset = GetTrayXOffset(notification_items_[0]);
+ notification_bubble_.reset(
+ new internal::SystemBubbleWrapper(notification_bubble));
+ notification_bubble_->InitView(this, anchor, &init_params);
+
+ if (notification_bubble->bubble_view()->child_count() == 0) {
+ // It is possible that none of the items generated actual notifications.
+ DestroyNotificationBubble();
+ return;
+ }
+ if (hide_notifications_)
+ notification_bubble->SetVisible(false);
+ else
+ UpdateWebNotifications();
+}
+
+void SystemTray::UpdateWebNotifications() {
+ TrayBubbleView* bubble_view = NULL;
+ if (notification_bubble_)
+ bubble_view = notification_bubble_->bubble_view();
+ else if (system_bubble_)
+ bubble_view = system_bubble_->bubble_view();
+
+ int height = 0;
+ if (bubble_view) {
+ gfx::Rect work_area = Shell::GetScreen()->GetDisplayNearestWindow(
+ bubble_view->GetWidget()->GetNativeView()).work_area();
+ if (GetShelfLayoutManager()->GetAlignment() != SHELF_ALIGNMENT_TOP) {
+ height = std::max(
+ 0, work_area.height() - bubble_view->GetBoundsInScreen().y());
+ } else {
+ height = std::max(
+ 0, bubble_view->GetBoundsInScreen().bottom() - work_area.y());
+ }
+ }
+ status_area_widget()->web_notification_tray()->SetSystemTrayHeight(height);
+}
+
+void SystemTray::SetShelfAlignment(ShelfAlignment alignment) {
+ if (alignment == shelf_alignment())
+ return;
+ internal::TrayBackgroundView::SetShelfAlignment(alignment);
+ UpdateAfterShelfAlignmentChange(alignment);
+ // Destroy any existing bubble so that it is rebuilt correctly.
+ system_bubble_.reset();
+ // Rebuild any notification bubble.
+ if (notification_bubble_) {
+ notification_bubble_.reset();
+ UpdateNotificationBubble();
+ }
+}
+
+void SystemTray::AnchorUpdated() {
+ if (notification_bubble_) {
+ notification_bubble_->bubble_view()->UpdateBubble();
+ // Ensure that the notification buble is above the launcher/status area.
+ notification_bubble_->bubble_view()->GetWidget()->StackAtTop();
+ UpdateBubbleViewArrow(notification_bubble_->bubble_view());
+ }
+ if (system_bubble_) {
+ system_bubble_->bubble_view()->UpdateBubble();
+ UpdateBubbleViewArrow(system_bubble_->bubble_view());
+ }
+}
+
+base::string16 SystemTray::GetAccessibleNameForTray() {
+ return l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_ACCESSIBLE_NAME);
+}
+
+void SystemTray::HideBubbleWithView(const TrayBubbleView* bubble_view) {
+ if (system_bubble_.get() && bubble_view == system_bubble_->bubble_view()) {
+ DestroySystemBubble();
+ UpdateNotificationBubble(); // State changed, re-create notifications.
+ GetShelfLayoutManager()->UpdateAutoHideState();
+ } else if (notification_bubble_.get() &&
+ bubble_view == notification_bubble_->bubble_view()) {
+ DestroyNotificationBubble();
+ }
+}
+
+bool SystemTray::ClickedOutsideBubble() {
+ if (!system_bubble_)
+ return false;
+ HideBubbleWithView(system_bubble_->bubble_view());
+ return true;
+}
+
+void SystemTray::BubbleViewDestroyed() {
+ if (system_bubble_) {
+ system_bubble_->bubble()->DestroyItemViews();
+ system_bubble_->bubble()->BubbleViewDestroyed();
+ }
+}
+
+void SystemTray::OnMouseEnteredView() {
+ if (system_bubble_)
+ system_bubble_->bubble()->StopAutoCloseTimer();
+}
+
+void SystemTray::OnMouseExitedView() {
+ if (system_bubble_)
+ system_bubble_->bubble()->RestartAutoCloseTimer();
+}
+
+base::string16 SystemTray::GetAccessibleNameForBubble() {
+ return GetAccessibleNameForTray();
+}
+
+gfx::Rect SystemTray::GetAnchorRect(
+ views::Widget* anchor_widget,
+ TrayBubbleView::AnchorType anchor_type,
+ TrayBubbleView::AnchorAlignment anchor_alignment) {
+ return GetBubbleAnchorRect(anchor_widget, anchor_type, anchor_alignment);
+}
+
+void SystemTray::HideBubble(const TrayBubbleView* bubble_view) {
+ HideBubbleWithView(bubble_view);
+}
+
+bool SystemTray::PerformAction(const ui::Event& event) {
+ // If we're already showing the default view, hide it; otherwise, show it
+ // (and hide any popup that's currently shown).
+ if (HasSystemBubbleType(SystemTrayBubble::BUBBLE_TYPE_DEFAULT)) {
+ system_bubble_->bubble()->Close();
+ } else {
+ int arrow_offset = TrayBubbleView::InitParams::kArrowDefaultOffset;
+ if (event.IsMouseEvent() || event.type() == ui::ET_GESTURE_TAP) {
+ const ui::LocatedEvent& located_event =
+ static_cast<const ui::LocatedEvent&>(event);
+ if (shelf_alignment() == SHELF_ALIGNMENT_BOTTOM ||
+ shelf_alignment() == SHELF_ALIGNMENT_TOP) {
+ gfx::Point point(located_event.x(), 0);
+ ConvertPointToWidget(this, &point);
+ arrow_offset = point.x();
+ }
+ }
+ ShowDefaultViewWithOffset(BUBBLE_CREATE_NEW, arrow_offset);
+ }
+ return true;
+}
+
+} // namespace ash
diff --git a/chromium/ash/system/tray/system_tray.h b/chromium/ash/system/tray/system_tray.h
new file mode 100644
index 00000000000..b37a643205a
--- /dev/null
+++ b/chromium/ash/system/tray/system_tray.h
@@ -0,0 +1,238 @@
+// Copyright (c) 2012 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 ASH_SYSTEM_TRAY_SYSTEM_TRAY_H_
+#define ASH_SYSTEM_TRAY_SYSTEM_TRAY_H_
+
+#include "ash/ash_export.h"
+#include "ash/system/tray/system_tray_bubble.h"
+#include "ash/system/tray/tray_background_view.h"
+#include "ash/system/user/login_status.h"
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/scoped_vector.h"
+#include "ui/views/bubble/tray_bubble_view.h"
+#include "ui/views/view.h"
+
+#include <map>
+#include <vector>
+
+namespace ash {
+
+class AccessibilityObserver;
+class AudioObserver;
+class BluetoothObserver;
+class BrightnessObserver;
+class CapsLockObserver;
+class ClockObserver;
+class DriveObserver;
+class IMEObserver;
+class LocaleObserver;
+class LogoutButtonObserver;
+class SystemTrayDelegate;
+class UpdateObserver;
+class UserObserver;
+#if defined(OS_CHROMEOS)
+class NetworkObserver;
+#endif
+
+class SystemTrayItem;
+
+namespace internal {
+class SystemBubbleWrapper;
+class SystemTrayContainer;
+class TrayAccessibility;
+class TrayGestureHandler;
+}
+
+// There are different methods for creating bubble views.
+enum BubbleCreationType {
+ BUBBLE_CREATE_NEW, // Closes any existing bubble and creates a new one.
+ BUBBLE_USE_EXISTING, // Uses any existing bubble, or creates a new one.
+};
+
+class ASH_EXPORT SystemTray : public internal::TrayBackgroundView,
+ public views::TrayBubbleView::Delegate {
+ public:
+ explicit SystemTray(internal::StatusAreaWidget* status_area_widget);
+ virtual ~SystemTray();
+
+ // Calls TrayBackgroundView::Initialize(), creates the tray items, and
+ // adds them to SystemTrayNotifier.
+ void InitializeTrayItems(SystemTrayDelegate* delegate);
+
+ // Adds a new item in the tray.
+ void AddTrayItem(SystemTrayItem* item);
+
+ // Removes an existing tray item.
+ void RemoveTrayItem(SystemTrayItem* item);
+
+ // Returns all tray items that has been added to system tray.
+ const std::vector<SystemTrayItem*>& GetTrayItems() const;
+
+ // Shows the default view of all items.
+ void ShowDefaultView(BubbleCreationType creation_type);
+
+ // Shows details of a particular item. If |close_delay_in_seconds| is
+ // non-zero, then the view is automatically closed after the specified time.
+ void ShowDetailedView(SystemTrayItem* item,
+ int close_delay_in_seconds,
+ bool activate,
+ BubbleCreationType creation_type);
+
+ // Continue showing the existing detailed view, if any, for |close_delay|
+ // seconds.
+ void SetDetailedViewCloseDelay(int close_delay);
+
+ // Hides the detailed view for |item|.
+ void HideDetailedView(SystemTrayItem* item);
+
+ // Shows the notification view for |item|.
+ void ShowNotificationView(SystemTrayItem* item);
+
+ // Hides the notification view for |item|.
+ void HideNotificationView(SystemTrayItem* item);
+
+ // Updates the items when the login status of the system changes.
+ void UpdateAfterLoginStatusChange(user::LoginStatus login_status);
+
+ // Updates the items when the shelf alignment changes.
+ void UpdateAfterShelfAlignmentChange(ShelfAlignment alignment);
+
+ // Temporarily hides/unhides the notification bubble.
+ void SetHideNotifications(bool hidden);
+
+ // Returns true if the launcher should be forced visible when auto-hidden.
+ bool ShouldShowLauncher() const;
+
+ // Returns true if there is a system bubble (already visible or in the process
+ // of being created).
+ bool HasSystemBubble() const;
+
+ // Returns true if there is a notification bubble.
+ bool HasNotificationBubble() const;
+
+ // Returns true if the system_bubble_ exists and is of type |type|.
+ bool HasSystemBubbleType(internal::SystemTrayBubble::BubbleType type);
+
+ // Returns a pointer to the system bubble or NULL if none.
+ internal::SystemTrayBubble* GetSystemBubble();
+
+ // Returns true if any bubble is visible.
+ bool IsAnyBubbleVisible() const;
+
+ // Returns true if the mouse is inside the notification bubble.
+ bool IsMouseInNotificationBubble() const;
+
+ // Closes system bubble and returns true if it did exist.
+ bool CloseSystemBubble() const;
+
+ // Accessors for testing.
+
+ // Returns true if the bubble exists.
+ bool CloseNotificationBubbleForTest() const;
+
+ // Overridden from TrayBackgroundView.
+ virtual void SetShelfAlignment(ShelfAlignment alignment) OVERRIDE;
+ virtual void AnchorUpdated() OVERRIDE;
+ virtual base::string16 GetAccessibleNameForTray() OVERRIDE;
+ virtual void HideBubbleWithView(
+ const views::TrayBubbleView* bubble_view) OVERRIDE;
+ virtual bool ClickedOutsideBubble() OVERRIDE;
+
+ // Overridden from message_center::TrayBubbleView::Delegate.
+ virtual void BubbleViewDestroyed() OVERRIDE;
+ virtual void OnMouseEnteredView() OVERRIDE;
+ virtual void OnMouseExitedView() OVERRIDE;
+ virtual base::string16 GetAccessibleNameForBubble() OVERRIDE;
+ virtual gfx::Rect GetAnchorRect(views::Widget* anchor_widget,
+ AnchorType anchor_type,
+ AnchorAlignment anchor_alignment) OVERRIDE;
+ virtual void HideBubble(const views::TrayBubbleView* bubble_view) OVERRIDE;
+
+ internal::TrayAccessibility* GetTrayAccessibilityForTest() {
+ return tray_accessibility_;
+ }
+
+ // Overridden from TrayBackgroundView.
+ virtual bool IsPressed() OVERRIDE;
+
+ private:
+ // Creates the default set of items for the sytem tray.
+ void CreateItems(SystemTrayDelegate* delegate);
+
+ // Resets |system_bubble_| and clears any related state.
+ void DestroySystemBubble();
+
+ // Resets |notification_bubble_| and clears any related state.
+ void DestroyNotificationBubble();
+
+ // Calculates the x-offset for the item in the tray. Returns -1 if its tray
+ // item view is not visible.
+ int GetTrayXOffset(SystemTrayItem* item) const;
+
+ // Shows the default view and its arrow position is shifted by |x_offset|.
+ void ShowDefaultViewWithOffset(BubbleCreationType creation_type,
+ int x_offset);
+
+ // Constructs or re-constructs |system_bubble_| and populates it with |items|.
+ // Specify |change_tray_status| to true if want to change the tray background
+ // status.
+ void ShowItems(const std::vector<SystemTrayItem*>& items,
+ bool details,
+ bool activate,
+ BubbleCreationType creation_type,
+ int x_offset);
+
+ // Constructs or re-constructs |notification_bubble_| and populates it with
+ // |notification_items_|, or destroys it if there are no notification items.
+ void UpdateNotificationBubble();
+
+ // Checks the current status of the system tray and updates the web
+ // notification tray according to the current status.
+ void UpdateWebNotifications();
+
+ const ScopedVector<SystemTrayItem>& items() const { return items_; }
+
+ // Overridden from internal::ActionableView.
+ virtual bool PerformAction(const ui::Event& event) OVERRIDE;
+
+ // Owned items.
+ ScopedVector<SystemTrayItem> items_;
+
+ // Pointers to members of |items_|.
+ SystemTrayItem* detailed_item_;
+ std::vector<SystemTrayItem*> notification_items_;
+
+ // Mappings of system tray item and it's view in the tray.
+ std::map<SystemTrayItem*, views::View*> tray_item_map_;
+
+ // Bubble for default and detailed views.
+ scoped_ptr<internal::SystemBubbleWrapper> system_bubble_;
+
+ // Bubble for notifications.
+ scoped_ptr<internal::SystemBubbleWrapper> notification_bubble_;
+
+ // Keep track of the default view height so that when we create detailed
+ // views directly (e.g. from a notification) we know what height to use.
+ int default_bubble_height_;
+
+ // Set to true when system notifications should be hidden (e.g. web
+ // notification bubble is visible).
+ bool hide_notifications_;
+
+ // This is true when the displayed system tray menu is a full tray menu,
+ // otherwise a single line item menu like the volume slider is shown.
+ // Note that the value is only valid when |system_bubble_| is true.
+ bool full_system_tray_menu_;
+
+ internal::TrayAccessibility* tray_accessibility_; // not owned
+
+ DISALLOW_COPY_AND_ASSIGN(SystemTray);
+};
+
+} // namespace ash
+
+#endif // ASH_SYSTEM_TRAY_SYSTEM_TRAY_H_
diff --git a/chromium/ash/system/tray/system_tray_bubble.cc b/chromium/ash/system/tray/system_tray_bubble.cc
new file mode 100644
index 00000000000..eff1ffd84ef
--- /dev/null
+++ b/chromium/ash/system/tray/system_tray_bubble.cc
@@ -0,0 +1,373 @@
+// Copyright (c) 2012 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.
+
+#include "ash/system/tray/system_tray_bubble.h"
+
+#include "ash/shell.h"
+#include "ash/system/tray/system_tray.h"
+#include "ash/system/tray/system_tray_delegate.h"
+#include "ash/system/tray/system_tray_item.h"
+#include "ash/system/tray/tray_bubble_wrapper.h"
+#include "ash/system/tray/tray_constants.h"
+#include "base/message_loop/message_loop.h"
+#include "ui/aura/window.h"
+#include "ui/compositor/layer.h"
+#include "ui/compositor/layer_animation_observer.h"
+#include "ui/compositor/scoped_layer_animation_settings.h"
+#include "ui/gfx/canvas.h"
+#include "ui/views/layout/box_layout.h"
+#include "ui/views/view.h"
+#include "ui/views/widget/widget.h"
+
+using views::TrayBubbleView;
+
+namespace ash {
+
+namespace {
+
+// Normally a detailed view is the same size as the default view. However,
+// when showing a detailed view directly (e.g. clicking on a notification),
+// we may not know the height of the default view, or the default view may
+// be too short, so we use this as a default and minimum height for any
+// detailed view.
+const int kDetailedBubbleMaxHeight = kTrayPopupItemHeight * 5;
+
+// Duration of swipe animation used when transitioning from a default to
+// detailed view or vice versa.
+const int kSwipeDelayMS = 150;
+
+// A view with some special behaviour for tray items in the popup:
+// - optionally changes background color on hover.
+class TrayPopupItemContainer : public views::View {
+ public:
+ TrayPopupItemContainer(views::View* view,
+ bool change_background,
+ bool draw_border)
+ : hover_(false),
+ change_background_(change_background) {
+ set_notify_enter_exit_on_child(true);
+ if (draw_border) {
+ set_border(
+ views::Border::CreateSolidSidedBorder(0, 0, 1, 0, kBorderLightColor));
+ }
+ views::BoxLayout* layout = new views::BoxLayout(
+ views::BoxLayout::kVertical, 0, 0, 0);
+ layout->set_spread_blank_space(true);
+ SetLayoutManager(layout);
+ SetPaintToLayer(view->layer() != NULL);
+ if (view->layer())
+ SetFillsBoundsOpaquely(view->layer()->fills_bounds_opaquely());
+ AddChildView(view);
+ SetVisible(view->visible());
+ }
+
+ virtual ~TrayPopupItemContainer() {}
+
+ private:
+ // Overridden from views::View.
+ virtual void ChildVisibilityChanged(View* child) OVERRIDE {
+ if (visible() == child->visible())
+ return;
+ SetVisible(child->visible());
+ PreferredSizeChanged();
+ }
+
+ virtual void ChildPreferredSizeChanged(View* child) OVERRIDE {
+ PreferredSizeChanged();
+ }
+
+ virtual void OnMouseEntered(const ui::MouseEvent& event) OVERRIDE {
+ hover_ = true;
+ SchedulePaint();
+ }
+
+ virtual void OnMouseExited(const ui::MouseEvent& event) OVERRIDE {
+ hover_ = false;
+ SchedulePaint();
+ }
+
+ virtual void OnPaintBackground(gfx::Canvas* canvas) OVERRIDE {
+ if (child_count() == 0)
+ return;
+
+ views::View* view = child_at(0);
+ if (!view->background()) {
+ canvas->FillRect(gfx::Rect(size()), (hover_ && change_background_) ?
+ kHoverBackgroundColor : kBackgroundColor);
+ }
+ }
+
+ bool hover_;
+ bool change_background_;
+
+ DISALLOW_COPY_AND_ASSIGN(TrayPopupItemContainer);
+};
+
+// Implicit animation observer that deletes itself and the layer at the end of
+// the animation.
+class AnimationObserverDeleteLayer : public ui::ImplicitAnimationObserver {
+ public:
+ explicit AnimationObserverDeleteLayer(ui::Layer* layer)
+ : layer_(layer) {
+ }
+
+ virtual ~AnimationObserverDeleteLayer() {
+ }
+
+ virtual void OnImplicitAnimationsCompleted() OVERRIDE {
+ base::MessageLoopForUI::current()->DeleteSoon(FROM_HERE, this);
+ }
+
+ private:
+ scoped_ptr<ui::Layer> layer_;
+
+ DISALLOW_COPY_AND_ASSIGN(AnimationObserverDeleteLayer);
+};
+
+} // namespace
+
+namespace internal {
+
+// SystemTrayBubble
+
+SystemTrayBubble::SystemTrayBubble(
+ ash::SystemTray* tray,
+ const std::vector<ash::SystemTrayItem*>& items,
+ BubbleType bubble_type)
+ : tray_(tray),
+ bubble_view_(NULL),
+ items_(items),
+ bubble_type_(bubble_type),
+ autoclose_delay_(0) {
+}
+
+SystemTrayBubble::~SystemTrayBubble() {
+ DestroyItemViews();
+ // Reset the host pointer in bubble_view_ in case its destruction is deferred.
+ if (bubble_view_)
+ bubble_view_->reset_delegate();
+}
+
+void SystemTrayBubble::UpdateView(
+ const std::vector<ash::SystemTrayItem*>& items,
+ BubbleType bubble_type) {
+ DCHECK(bubble_type != BUBBLE_TYPE_NOTIFICATION);
+
+ scoped_ptr<ui::Layer> scoped_layer;
+ if (bubble_type != bubble_type_) {
+ base::TimeDelta swipe_duration =
+ base::TimeDelta::FromMilliseconds(kSwipeDelayMS);
+ scoped_layer.reset(bubble_view_->RecreateLayer());
+ // Keep the reference to layer as we need it after releasing it.
+ ui::Layer* layer = scoped_layer.get();
+ DCHECK(layer);
+ layer->SuppressPaint();
+
+ // When transitioning from detailed view to default view, animate the
+ // existing view (slide out towards the right).
+ if (bubble_type == BUBBLE_TYPE_DEFAULT) {
+ ui::ScopedLayerAnimationSettings settings(layer->GetAnimator());
+ settings.AddObserver(
+ new AnimationObserverDeleteLayer(scoped_layer.release()));
+ settings.SetTransitionDuration(swipe_duration);
+ settings.SetTweenType(ui::Tween::EASE_OUT);
+ gfx::Transform transform;
+ transform.Translate(layer->bounds().width(), 0.0);
+ layer->SetTransform(transform);
+ }
+
+ {
+ // Add a shadow layer to make the old layer darker as the animation
+ // progresses.
+ ui::Layer* shadow = new ui::Layer(ui::LAYER_SOLID_COLOR);
+ shadow->SetColor(SK_ColorBLACK);
+ shadow->SetOpacity(0.01f);
+ shadow->SetBounds(layer->bounds());
+ layer->Add(shadow);
+ layer->StackAtTop(shadow);
+ {
+ // Animate the darkening effect a little longer than the swipe-in. This
+ // is to make sure the darkening animation does not end up finishing
+ // early, because the dark layer goes away at the end of the animation,
+ // and there is a brief moment when the old view is still visible, but
+ // it does not have the shadow layer on top.
+ ui::ScopedLayerAnimationSettings settings(shadow->GetAnimator());
+ settings.AddObserver(new AnimationObserverDeleteLayer(shadow));
+ settings.SetTransitionDuration(swipe_duration +
+ base::TimeDelta::FromMilliseconds(150));
+ settings.SetTweenType(ui::Tween::LINEAR);
+ shadow->SetOpacity(0.15f);
+ }
+ }
+ }
+
+ DestroyItemViews();
+ bubble_view_->RemoveAllChildViews(true);
+
+ items_ = items;
+ bubble_type_ = bubble_type;
+ CreateItemViews(
+ Shell::GetInstance()->system_tray_delegate()->GetUserLoginStatus());
+
+ // Close bubble view if we failed to create the item view.
+ if (!bubble_view_->has_children()) {
+ Close();
+ return;
+ }
+
+ bubble_view_->GetWidget()->GetContentsView()->Layout();
+ // Make sure that the bubble is large enough for the default view.
+ if (bubble_type_ == BUBBLE_TYPE_DEFAULT) {
+ bubble_view_->SetMaxHeight(0); // Clear max height limit.
+ }
+
+ if (scoped_layer) {
+ // When transitioning from default view to detailed view, animate the new
+ // view (slide in from the right).
+ if (bubble_type == BUBBLE_TYPE_DETAILED) {
+ ui::Layer* new_layer = bubble_view_->layer();
+
+ // Make sure the new layer is stacked above the old layer during the
+ // animation.
+ new_layer->parent()->StackAbove(new_layer, scoped_layer.get());
+
+ gfx::Rect bounds = new_layer->bounds();
+ gfx::Transform transform;
+ transform.Translate(bounds.width(), 0.0);
+ new_layer->SetTransform(transform);
+ {
+ ui::ScopedLayerAnimationSettings settings(new_layer->GetAnimator());
+ settings.AddObserver(
+ new AnimationObserverDeleteLayer(scoped_layer.release()));
+ settings.SetTransitionDuration(
+ base::TimeDelta::FromMilliseconds(kSwipeDelayMS));
+ settings.SetTweenType(ui::Tween::EASE_OUT);
+ new_layer->SetTransform(gfx::Transform());
+ }
+ }
+ }
+}
+
+void SystemTrayBubble::InitView(views::View* anchor,
+ user::LoginStatus login_status,
+ TrayBubbleView::InitParams* init_params) {
+ DCHECK(bubble_view_ == NULL);
+
+ if (bubble_type_ == BUBBLE_TYPE_DETAILED &&
+ init_params->max_height < kDetailedBubbleMaxHeight) {
+ init_params->max_height = kDetailedBubbleMaxHeight;
+ } else if (bubble_type_ == BUBBLE_TYPE_NOTIFICATION) {
+ init_params->close_on_deactivate = false;
+ }
+ bubble_view_ = TrayBubbleView::Create(
+ tray_->GetBubbleWindowContainer(), anchor, tray_, init_params);
+ bubble_view_->set_adjust_if_offscreen(false);
+ CreateItemViews(login_status);
+
+ if (bubble_view_->CanActivate()) {
+ bubble_view_->NotifyAccessibilityEvent(
+ ui::AccessibilityTypes::EVENT_ALERT, true);
+ }
+}
+
+void SystemTrayBubble::DestroyItemViews() {
+ for (std::vector<ash::SystemTrayItem*>::iterator it = items_.begin();
+ it != items_.end();
+ ++it) {
+ switch (bubble_type_) {
+ case BUBBLE_TYPE_DEFAULT:
+ (*it)->DestroyDefaultView();
+ break;
+ case BUBBLE_TYPE_DETAILED:
+ (*it)->DestroyDetailedView();
+ break;
+ case BUBBLE_TYPE_NOTIFICATION:
+ (*it)->DestroyNotificationView();
+ break;
+ }
+ }
+}
+
+void SystemTrayBubble::BubbleViewDestroyed() {
+ bubble_view_ = NULL;
+}
+
+void SystemTrayBubble::StartAutoCloseTimer(int seconds) {
+ autoclose_.Stop();
+ autoclose_delay_ = seconds;
+ if (autoclose_delay_) {
+ autoclose_.Start(FROM_HERE,
+ base::TimeDelta::FromSeconds(autoclose_delay_),
+ this, &SystemTrayBubble::Close);
+ }
+}
+
+void SystemTrayBubble::StopAutoCloseTimer() {
+ autoclose_.Stop();
+}
+
+void SystemTrayBubble::RestartAutoCloseTimer() {
+ if (autoclose_delay_)
+ StartAutoCloseTimer(autoclose_delay_);
+}
+
+void SystemTrayBubble::Close() {
+ tray_->HideBubbleWithView(bubble_view());
+}
+
+void SystemTrayBubble::SetVisible(bool is_visible) {
+ if (!bubble_view_)
+ return;
+ views::Widget* bubble_widget = bubble_view_->GetWidget();
+ if (is_visible)
+ bubble_widget->Show();
+ else
+ bubble_widget->Hide();
+}
+
+bool SystemTrayBubble::IsVisible() {
+ return bubble_view() && bubble_view()->GetWidget()->IsVisible();
+}
+
+bool SystemTrayBubble::ShouldShowLauncher() const {
+ for (std::vector<ash::SystemTrayItem*>::const_iterator it = items_.begin();
+ it != items_.end();
+ ++it) {
+ if ((*it)->ShouldShowLauncher())
+ return true;
+ }
+ return false;
+}
+
+void SystemTrayBubble::CreateItemViews(user::LoginStatus login_status) {
+ std::vector<views::View*> item_views;
+ for (size_t i = 0; i < items_.size(); ++i) {
+ views::View* view = NULL;
+ switch (bubble_type_) {
+ case BUBBLE_TYPE_DEFAULT:
+ view = items_[i]->CreateDefaultView(login_status);
+ break;
+ case BUBBLE_TYPE_DETAILED:
+ view = items_[i]->CreateDetailedView(login_status);
+ break;
+ case BUBBLE_TYPE_NOTIFICATION:
+ view = items_[i]->CreateNotificationView(login_status);
+ break;
+ }
+ if (view)
+ item_views.push_back(view);
+ }
+
+ bool is_default_bubble = bubble_type_ == BUBBLE_TYPE_DEFAULT;
+ for (size_t i = 0; i < item_views.size(); ++i) {
+ // For default view, draw bottom border for each item, except the last
+ // 2 items, which are the bottom header row and the one just above it.
+ bubble_view_->AddChildView(new TrayPopupItemContainer(
+ item_views[i], is_default_bubble,
+ is_default_bubble && (i < item_views.size() - 2)));
+ }
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/system/tray/system_tray_bubble.h b/chromium/ash/system/tray/system_tray_bubble.h
new file mode 100644
index 00000000000..61b1543ae45
--- /dev/null
+++ b/chromium/ash/system/tray/system_tray_bubble.h
@@ -0,0 +1,79 @@
+// Copyright (c) 2012 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 ASH_SYSTEM_TRAY_SYSTEM_TRAY_BUBBLE_H_
+#define ASH_SYSTEM_TRAY_SYSTEM_TRAY_BUBBLE_H_
+
+#include "ash/system/user/login_status.h"
+#include "base/base_export.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/timer/timer.h"
+#include "ui/views/bubble/tray_bubble_view.h"
+
+#include <vector>
+
+namespace ash {
+
+class SystemTray;
+class SystemTrayItem;
+
+namespace internal {
+
+class SystemTrayBubble {
+ public:
+ enum BubbleType {
+ BUBBLE_TYPE_DEFAULT,
+ BUBBLE_TYPE_DETAILED,
+ BUBBLE_TYPE_NOTIFICATION
+ };
+
+ SystemTrayBubble(ash::SystemTray* tray,
+ const std::vector<ash::SystemTrayItem*>& items,
+ BubbleType bubble_type);
+ virtual ~SystemTrayBubble();
+
+ // Change the items displayed in the bubble.
+ void UpdateView(const std::vector<ash::SystemTrayItem*>& items,
+ BubbleType bubble_type);
+
+ // Creates |bubble_view_| and a child views for each member of |items_|.
+ // Also creates |bubble_wrapper_|. |init_params| may be modified.
+ void InitView(views::View* anchor,
+ user::LoginStatus login_status,
+ views::TrayBubbleView::InitParams* init_params);
+
+ BubbleType bubble_type() const { return bubble_type_; }
+ views::TrayBubbleView* bubble_view() const { return bubble_view_; }
+
+ void DestroyItemViews();
+ void BubbleViewDestroyed();
+ void StartAutoCloseTimer(int seconds);
+ void StopAutoCloseTimer();
+ void RestartAutoCloseTimer();
+ void Close();
+ void SetVisible(bool is_visible);
+ bool IsVisible();
+
+ // Returns true if any of the SystemTrayItems return true from
+ // ShouldShowLauncher().
+ bool ShouldShowLauncher() const;
+
+ private:
+ void CreateItemViews(user::LoginStatus login_status);
+
+ ash::SystemTray* tray_;
+ views::TrayBubbleView* bubble_view_;
+ std::vector<ash::SystemTrayItem*> items_;
+ BubbleType bubble_type_;
+
+ int autoclose_delay_;
+ base::OneShotTimer<SystemTrayBubble> autoclose_;
+
+ DISALLOW_COPY_AND_ASSIGN(SystemTrayBubble);
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_SYSTEM_TRAY_SYSTEM_TRAY_BUBBLE_H_
diff --git a/chromium/ash/system/tray/system_tray_delegate.cc b/chromium/ash/system/tray/system_tray_delegate.cc
new file mode 100644
index 00000000000..f31e3144af7
--- /dev/null
+++ b/chromium/ash/system/tray/system_tray_delegate.cc
@@ -0,0 +1,62 @@
+// Copyright (c) 2012 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.
+
+#include "ash/system/tray/system_tray_delegate.h"
+
+#include "ash/system/tray/test_system_tray_delegate.h"
+
+namespace ash {
+
+NetworkIconInfo::NetworkIconInfo()
+ : connecting(false),
+ connected(false),
+ tray_icon_visible(true),
+ is_cellular(false) {
+}
+
+NetworkIconInfo::~NetworkIconInfo() {
+}
+
+BluetoothDeviceInfo::BluetoothDeviceInfo()
+ : connected(false),
+ connecting(false),
+ paired(false) {
+}
+
+BluetoothDeviceInfo::~BluetoothDeviceInfo() {
+}
+
+DriveOperationStatus::DriveOperationStatus()
+ : id(-1),
+ progress(0.0),
+ type(OPERATION_DOWNLOAD),
+ state(OPERATION_NOT_STARTED) {
+}
+
+DriveOperationStatus::~DriveOperationStatus() {
+}
+
+IMEInfo::IMEInfo()
+ : selected(false),
+ third_party(false) {
+}
+
+IMEInfo::~IMEInfo() {
+}
+
+IMEPropertyInfo::IMEPropertyInfo()
+ : selected(false) {
+}
+
+IMEPropertyInfo::~IMEPropertyInfo() {
+}
+
+// TODO(stevenjb/oshima): Remove this once Shell::delegate_ is guaranteed
+// to not be NULL and move TestSystemTrayDelegate -> ash/test. crbug.com/159693
+// static
+SystemTrayDelegate* SystemTrayDelegate::CreateDummyDelegate() {
+ return new test::TestSystemTrayDelegate;
+}
+
+} // namespace ash
diff --git a/chromium/ash/system/tray/system_tray_delegate.h b/chromium/ash/system/tray/system_tray_delegate.h
new file mode 100644
index 00000000000..a66726b8232
--- /dev/null
+++ b/chromium/ash/system/tray/system_tray_delegate.h
@@ -0,0 +1,325 @@
+// Copyright (c) 2012 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 ASH_SYSTEM_TRAY_SYSTEM_TRAY_DELEGATE_H_
+#define ASH_SYSTEM_TRAY_SYSTEM_TRAY_DELEGATE_H_
+
+#include <string>
+#include <vector>
+
+#include "ash/ash_export.h"
+#include "ash/system/user/login_status.h"
+#include "base/files/file_path.h"
+#include "base/i18n/time_formatting.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string16.h"
+#include "ui/gfx/image/image_skia.h"
+#include "ui/gfx/native_widget_types.h"
+
+namespace base {
+class TimeDelta;
+class TimeTicks;
+}
+
+namespace ash {
+
+struct ASH_EXPORT NetworkIconInfo {
+ NetworkIconInfo();
+ ~NetworkIconInfo();
+
+ bool highlight() const { return connected || connecting; }
+
+ bool connecting;
+ bool connected;
+ bool tray_icon_visible;
+ gfx::ImageSkia image;
+ base::string16 name;
+ base::string16 description;
+ std::string service_path;
+ bool is_cellular;
+};
+
+struct ASH_EXPORT BluetoothDeviceInfo {
+ BluetoothDeviceInfo();
+ ~BluetoothDeviceInfo();
+
+ std::string address;
+ base::string16 display_name;
+ bool connected;
+ bool connecting;
+ bool paired;
+};
+
+typedef std::vector<BluetoothDeviceInfo> BluetoothDeviceList;
+
+// Structure that packs progress information of each operation.
+struct ASH_EXPORT DriveOperationStatus {
+ enum OperationType {
+ OPERATION_UPLOAD,
+ OPERATION_DOWNLOAD
+ };
+
+ enum OperationState {
+ OPERATION_NOT_STARTED,
+ OPERATION_IN_PROGRESS,
+ OPERATION_COMPLETED,
+ OPERATION_FAILED,
+ };
+
+ DriveOperationStatus();
+ ~DriveOperationStatus();
+
+ // Unique ID for the operation.
+ int32 id;
+
+ // File path.
+ base::FilePath file_path;
+ // Current operation completion progress [0.0 - 1.0].
+ double progress;
+ OperationType type;
+ OperationState state;
+};
+
+typedef std::vector<DriveOperationStatus> DriveOperationStatusList;
+
+
+struct ASH_EXPORT IMEPropertyInfo {
+ IMEPropertyInfo();
+ ~IMEPropertyInfo();
+
+ bool selected;
+ std::string key;
+ base::string16 name;
+};
+
+typedef std::vector<IMEPropertyInfo> IMEPropertyInfoList;
+
+struct ASH_EXPORT IMEInfo {
+ IMEInfo();
+ ~IMEInfo();
+
+ bool selected;
+ bool third_party;
+ std::string id;
+ base::string16 name;
+ base::string16 medium_name;
+ base::string16 short_name;
+};
+
+typedef std::vector<IMEInfo> IMEInfoList;
+
+class VolumeControlDelegate;
+
+class SystemTrayDelegate {
+ public:
+ virtual ~SystemTrayDelegate() {}
+
+ // Called after SystemTray has been instantiated.
+ virtual void Initialize() = 0;
+
+ // Called before SystemTray is destroyed.
+ virtual void Shutdown() = 0;
+
+ // Returns true if system tray should be visible on startup.
+ virtual bool GetTrayVisibilityOnStartup() = 0;
+
+ // Gets information about the active user.
+ virtual user::LoginStatus GetUserLoginStatus() const = 0;
+ virtual bool IsOobeCompleted() const = 0;
+
+ // Shows UI for changing user's profile picture.
+ virtual void ChangeProfilePicture() = 0;
+
+ // Returns the domain that manages the device, if it is enterprise-enrolled.
+ virtual const std::string GetEnterpriseDomain() const = 0;
+
+ // Returns notification for enterprise enrolled devices.
+ virtual const base::string16 GetEnterpriseMessage() const = 0;
+
+ // Returns the display email of user that manages current
+ // locally managed user.
+ virtual const std::string GetLocallyManagedUserManager() const = 0;
+
+ // Returns the name of user that manages current locally managed user.
+ virtual const base::string16 GetLocallyManagedUserManagerName() const = 0;
+
+ // Returns notification for locally managed users.
+ virtual const base::string16 GetLocallyManagedUserMessage() const = 0;
+
+ // Returns whether a system upgrade is available.
+ virtual bool SystemShouldUpgrade() const = 0;
+
+ // Returns the desired hour clock type.
+ virtual base::HourClockType GetHourClockType() const = 0;
+
+ // Shows settings.
+ virtual void ShowSettings() = 0;
+
+ // Shows the settings related to date, timezone etc.
+ virtual void ShowDateSettings() = 0;
+
+ // Shows the settings related to network. If |service_path| is not empty,
+ // show the settings for that network.
+ virtual void ShowNetworkSettings(const std::string& service_path) = 0;
+
+ // Shows the settings related to bluetooth.
+ virtual void ShowBluetoothSettings() = 0;
+
+ // Shows settings related to multiple displays.
+ virtual void ShowDisplaySettings() = 0;
+
+ // Shows the page that lets you disable performance tracing.
+ virtual void ShowChromeSlow() = 0;
+
+ // Returns true if the notification for the display configuration change
+ // should appear.
+ virtual bool ShouldShowDisplayNotification() = 0;
+
+ // Shows settings related to Google Drive.
+ virtual void ShowDriveSettings() = 0;
+
+ // Shows settings related to input methods.
+ virtual void ShowIMESettings() = 0;
+
+ // Shows help.
+ virtual void ShowHelp() = 0;
+
+ // Show accessilibity help.
+ virtual void ShowAccessibilityHelp() = 0;
+
+ // Show the settings related to accessilibity.
+ virtual void ShowAccessibilitySettings() = 0;
+
+ // Shows more information about public account mode.
+ virtual void ShowPublicAccountInfo() = 0;
+
+ // Shows information about enterprise enrolled devices.
+ virtual void ShowEnterpriseInfo() = 0;
+
+ // Shows information about locally managed users.
+ virtual void ShowLocallyManagedUserInfo() = 0;
+
+ // Shows login UI to add other users to this session.
+ virtual void ShowUserLogin() = 0;
+
+ // Attempts to shut down the system.
+ virtual void ShutDown() = 0;
+
+ // Attempts to sign out the user.
+ virtual void SignOut() = 0;
+
+ // Attempts to lock the screen.
+ virtual void RequestLockScreen() = 0;
+
+ // Attempts to restart the system for update.
+ virtual void RequestRestartForUpdate() = 0;
+
+ // Returns a list of available bluetooth devices.
+ virtual void GetAvailableBluetoothDevices(BluetoothDeviceList* devices) = 0;
+
+ // Requests bluetooth start discovering devices.
+ virtual void BluetoothStartDiscovering() = 0;
+
+ // Requests bluetooth stop discovering devices.
+ virtual void BluetoothStopDiscovering() = 0;
+
+ // Connect to a specific bluetooth device.
+ virtual void ConnectToBluetoothDevice(const std::string& address) = 0;
+
+ // Returns true if bluetooth adapter is discovering bluetooth devices.
+ virtual bool IsBluetoothDiscovering() = 0;
+
+ // Returns the currently selected IME.
+ virtual void GetCurrentIME(IMEInfo* info) = 0;
+
+ // Returns a list of availble IMEs.
+ virtual void GetAvailableIMEList(IMEInfoList* list) = 0;
+
+ // Returns a list of properties for the currently selected IME.
+ virtual void GetCurrentIMEProperties(IMEPropertyInfoList* list) = 0;
+
+ // Switches to the selected input method.
+ virtual void SwitchIME(const std::string& ime_id) = 0;
+
+ // Activates an IME property.
+ virtual void ActivateIMEProperty(const std::string& key) = 0;
+
+ // Cancels ongoing drive operation.
+ virtual void CancelDriveOperation(int32 operation_id) = 0;
+
+ // Returns information about the ongoing drive operations.
+ virtual void GetDriveOperationStatusList(
+ DriveOperationStatusList* list) = 0;
+
+ // Shows UI to configure or activate the network specified by |network_id|.
+ virtual void ConfigureNetwork(const std::string& network_id) = 0;
+
+ // Shows UI to enroll the network specified by |network_id| if appropriate,
+ // otherwise behaves the same as ConfigureNetwork. |parent_window| is used
+ // to parent any configuration UI. If NULL a default window will be used.
+ virtual void EnrollOrConfigureNetwork(const std::string& network_id,
+ gfx::NativeWindow parent_window) = 0;
+
+ // Shows UI to manage bluetooth devices.
+ virtual void ManageBluetoothDevices() = 0;
+
+ // Toggles bluetooth.
+ virtual void ToggleBluetooth() = 0;
+
+ // Shows UI to unlock a mobile sim.
+ virtual void ShowMobileSimDialog() = 0;
+
+ // Shows UI to setup a mobile network.
+ virtual void ShowMobileSetup(const std::string& network_id) = 0;
+
+ // Shows UI to connect to an unlisted wifi network.
+ virtual void ShowOtherWifi() = 0;
+
+ // Shows UI to configure vpn.
+ virtual void ShowOtherVPN() = 0;
+
+ // Shows UI to search for cellular networks.
+ virtual void ShowOtherCellular() = 0;
+
+ // Returns whether bluetooth capability is available.
+ virtual bool GetBluetoothAvailable() = 0;
+
+ // Returns whether bluetooth is enabled.
+ virtual bool GetBluetoothEnabled() = 0;
+
+ // Shows UI for changing proxy settings.
+ virtual void ChangeProxySettings() = 0;
+
+ // Returns VolumeControlDelegate.
+ virtual VolumeControlDelegate* GetVolumeControlDelegate() const = 0;
+
+ // Sets VolumeControlDelegate.
+ virtual void SetVolumeControlDelegate(
+ scoped_ptr<VolumeControlDelegate> delegate) = 0;
+
+ // Retrieves the session start time. Returns |false| if the time is not set.
+ virtual bool GetSessionStartTime(base::TimeTicks* session_start_time) = 0;
+
+ // Retrieves the session length limit. Returns |false| if no limit is set.
+ virtual bool GetSessionLengthLimit(base::TimeDelta* session_length_limit) = 0;
+
+ // Get the system tray menu size in pixels (dependent on the language).
+ virtual int GetSystemTrayMenuWidth() = 0;
+
+ // Returns the duration formatted as a localized string.
+ // TODO(stevenjb): Move TimeFormat from src/chrome to src/ui so that it can be
+ // accessed without going through the delegate. crbug.com/222697
+ virtual base::string16 FormatTimeDuration(
+ const base::TimeDelta& delta) const = 0;
+
+ // Speaks the given text if spoken feedback is enabled.
+ virtual void MaybeSpeak(const std::string& utterance) const = 0;
+
+ // Creates a dummy delegate for testing.
+ static SystemTrayDelegate* CreateDummyDelegate();
+};
+
+} // namespace ash
+
+#endif // ASH_SYSTEM_TRAY_SYSTEM_TRAY_DELEGATE_H_
diff --git a/chromium/ash/system/tray/system_tray_item.cc b/chromium/ash/system/tray/system_tray_item.cc
new file mode 100644
index 00000000000..b2acf08c076
--- /dev/null
+++ b/chromium/ash/system/tray/system_tray_item.cc
@@ -0,0 +1,91 @@
+// Copyright (c) 2012 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.
+
+#include "ash/system/tray/system_tray_item.h"
+
+#include "ash/shell.h"
+#include "ash/system/tray/system_tray.h"
+#include "ash/system/tray/system_tray_delegate.h"
+#include "ui/views/view.h"
+
+namespace ash {
+
+SystemTrayItem::SystemTrayItem(SystemTray* system_tray)
+ : system_tray_(system_tray) {
+}
+
+SystemTrayItem::~SystemTrayItem() {
+}
+
+views::View* SystemTrayItem::CreateTrayView(user::LoginStatus status) {
+ return NULL;
+}
+
+views::View* SystemTrayItem::CreateDefaultView(user::LoginStatus status) {
+ return NULL;
+}
+
+views::View* SystemTrayItem::CreateDetailedView(user::LoginStatus status) {
+ return NULL;
+}
+
+views::View* SystemTrayItem::CreateNotificationView(user::LoginStatus status) {
+ return NULL;
+}
+
+void SystemTrayItem::DestroyTrayView() {
+}
+
+void SystemTrayItem::DestroyDefaultView() {
+}
+
+void SystemTrayItem::DestroyDetailedView() {
+}
+
+void SystemTrayItem::DestroyNotificationView() {
+}
+
+void SystemTrayItem::TransitionDetailedView() {
+ system_tray()->ShowDetailedView(this, 0, true, BUBBLE_USE_EXISTING);
+}
+
+void SystemTrayItem::UpdateAfterLoginStatusChange(user::LoginStatus status) {
+}
+
+void SystemTrayItem::UpdateAfterShelfAlignmentChange(ShelfAlignment alignment) {
+}
+
+void SystemTrayItem::PopupDetailedView(int for_seconds, bool activate) {
+ // Never show a detailed view during OOBE, e.g. from a notification.
+ if (!Shell::GetInstance()->system_tray_delegate()->IsOobeCompleted())
+ return;
+ system_tray()->ShowDetailedView(
+ this, for_seconds, activate, BUBBLE_CREATE_NEW);
+}
+
+void SystemTrayItem::SetDetailedViewCloseDelay(int for_seconds) {
+ system_tray()->SetDetailedViewCloseDelay(for_seconds);
+}
+
+void SystemTrayItem::HideDetailedView() {
+ system_tray()->HideDetailedView(this);
+}
+
+void SystemTrayItem::ShowNotificationView() {
+ system_tray()->ShowNotificationView(this);
+}
+
+void SystemTrayItem::HideNotificationView() {
+ system_tray()->HideNotificationView(this);
+}
+
+bool SystemTrayItem::ShouldHideArrow() const {
+ return false;
+}
+
+bool SystemTrayItem::ShouldShowLauncher() const {
+ return true;
+}
+
+} // namespace ash
diff --git a/chromium/ash/system/tray/system_tray_item.h b/chromium/ash/system/tray/system_tray_item.h
new file mode 100644
index 00000000000..a0d7d1b0e86
--- /dev/null
+++ b/chromium/ash/system/tray/system_tray_item.h
@@ -0,0 +1,119 @@
+// Copyright (c) 2012 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 ASH_SYSTEM_TRAY_SYSTEM_TRAY_ITEM_H_
+#define ASH_SYSTEM_TRAY_SYSTEM_TRAY_ITEM_H_
+
+#include "ash/ash_export.h"
+#include "ash/shelf/shelf_types.h"
+#include "ash/system/user/login_status.h"
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+
+namespace views {
+class View;
+}
+
+namespace ash {
+
+class SystemTray;
+
+namespace internal {
+class TrayItemView;
+}
+
+class ASH_EXPORT SystemTrayItem {
+ public:
+ explicit SystemTrayItem(SystemTray* system_tray);
+ virtual ~SystemTrayItem();
+
+ // Create* functions may return NULL if nothing should be displayed for the
+ // type of view. The default implementations return NULL.
+
+ // Returns a view to be displayed in the system tray. If this returns NULL,
+ // then this item is not displayed in the tray.
+ // NOTE: The returned view should almost always be a TrayItemView, which
+ // automatically resizes the widget when the size of the view changes, and
+ // adds animation when the visibility of the view changes. If a view wants to
+ // avoid this behavior, then it should not be a TrayItemView.
+ virtual views::View* CreateTrayView(user::LoginStatus status);
+
+ // Returns a view for the item to be displayed in the list. This view can be
+ // displayed with a number of other tray items, so this should not be too
+ // big.
+ virtual views::View* CreateDefaultView(user::LoginStatus status);
+
+ // Returns a detailed view for the item. This view is displayed standalone.
+ virtual views::View* CreateDetailedView(user::LoginStatus status);
+
+ // Returns a notification view for the item. This view is displayed with
+ // other notifications and should be the same size as default views.
+ virtual views::View* CreateNotificationView(user::LoginStatus status);
+
+ // These functions are called when the corresponding view item is about to be
+ // removed. An item should do appropriate cleanup in these functions.
+ // The default implementation does nothing.
+ virtual void DestroyTrayView();
+ virtual void DestroyDefaultView();
+ virtual void DestroyDetailedView();
+ virtual void DestroyNotificationView();
+
+ // Updates the tray view (if applicable) when the user's login status changes.
+ // It is not necessary the update the default or detailed view, since the
+ // default/detailed popup is closed when login status changes. The default
+ // implementation does nothing.
+ virtual void UpdateAfterLoginStatusChange(user::LoginStatus status);
+
+ // Updates the tray view (if applicable) when shelf's alignment changes.
+ // The default implementation does nothing.
+ virtual void UpdateAfterShelfAlignmentChange(ShelfAlignment alignment);
+
+ // Shows the detailed view for this item. If the main popup for the tray is
+ // currently visible, then making this call would use the existing window to
+ // display the detailed item. The detailed item will inherit the bounds of the
+ // existing window.
+ // If there is no existing view, then this is equivalent to calling
+ // PopupDetailedView(0, true).
+ void TransitionDetailedView();
+
+ // Pops up the detailed view for this item. An item can request to show its
+ // detailed view using this function (e.g. from an observer callback when
+ // something, e.g. volume, network availability etc. changes). If
+ // |for_seconds| is non-zero, then the popup is closed after the specified
+ // time.
+ void PopupDetailedView(int for_seconds, bool activate);
+
+ // Continue showing the currently-shown detailed view, if any, for
+ // |for_seconds| seconds. The caller is responsible for checking that the
+ // currently-shown view is for this item.
+ void SetDetailedViewCloseDelay(int for_seconds);
+
+ // Hides the detailed view for this item.
+ void HideDetailedView();
+
+ // Shows a notification for this item.
+ void ShowNotificationView();
+
+ // Hides the notification for this item.
+ void HideNotificationView();
+
+ // Returns true if item should hide the arrow.
+ virtual bool ShouldHideArrow() const;
+
+ // Returns true if this item needs to force the launcher to be visible when
+ // the launcher is in the auto-hide state. Default is true.
+ virtual bool ShouldShowLauncher() const;
+
+ // Returns the system tray that this item belongs to.
+ SystemTray* system_tray() const { return system_tray_; }
+
+ private:
+ SystemTray* system_tray_;
+
+ DISALLOW_COPY_AND_ASSIGN(SystemTrayItem);
+};
+
+} // namespace ash
+
+#endif // ASH_SYSTEM_TRAY_SYSTEM_TRAY_ITEM_H_
diff --git a/chromium/ash/system/tray/system_tray_notifier.cc b/chromium/ash/system/tray/system_tray_notifier.cc
new file mode 100644
index 00000000000..fcdb2d6d5bc
--- /dev/null
+++ b/chromium/ash/system/tray/system_tray_notifier.cc
@@ -0,0 +1,352 @@
+// Copyright (c) 2012 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.
+
+#include "ash/system/tray/system_tray_notifier.h"
+
+#if defined(OS_CHROMEOS)
+#include "ash/system/chromeos/network/network_state_notifier.h"
+#endif
+
+namespace ash {
+
+SystemTrayNotifier::SystemTrayNotifier() {
+#if defined(OS_CHROMEOS)
+ network_state_notifier_.reset(new NetworkStateNotifier());
+#endif
+}
+
+SystemTrayNotifier::~SystemTrayNotifier() {
+}
+
+void SystemTrayNotifier::AddAccessibilityObserver(
+ AccessibilityObserver* observer) {
+ accessibility_observers_.AddObserver(observer);
+}
+
+void SystemTrayNotifier::RemoveAccessibilityObserver(
+ AccessibilityObserver* observer) {
+ accessibility_observers_.RemoveObserver(observer);
+}
+
+void SystemTrayNotifier::AddBluetoothObserver(BluetoothObserver* observer) {
+ bluetooth_observers_.AddObserver(observer);
+}
+
+void SystemTrayNotifier::RemoveBluetoothObserver(BluetoothObserver* observer) {
+ bluetooth_observers_.RemoveObserver(observer);
+}
+
+void SystemTrayNotifier::AddBrightnessObserver(BrightnessObserver* observer) {
+ brightness_observers_.AddObserver(observer);
+}
+
+void SystemTrayNotifier::RemoveBrightnessObserver(
+ BrightnessObserver* observer) {
+ brightness_observers_.RemoveObserver(observer);
+}
+
+void SystemTrayNotifier::AddCapsLockObserver(CapsLockObserver* observer) {
+ caps_lock_observers_.AddObserver(observer);
+}
+
+void SystemTrayNotifier::RemoveCapsLockObserver(CapsLockObserver* observer) {
+ caps_lock_observers_.RemoveObserver(observer);
+}
+
+void SystemTrayNotifier::AddClockObserver(ClockObserver* observer) {
+ clock_observers_.AddObserver(observer);
+}
+
+void SystemTrayNotifier::RemoveClockObserver(ClockObserver* observer) {
+ clock_observers_.RemoveObserver(observer);
+}
+
+void SystemTrayNotifier::AddDriveObserver(DriveObserver* observer) {
+ drive_observers_.AddObserver(observer);
+}
+
+void SystemTrayNotifier::RemoveDriveObserver(DriveObserver* observer) {
+ drive_observers_.RemoveObserver(observer);
+}
+
+void SystemTrayNotifier::AddIMEObserver(IMEObserver* observer) {
+ ime_observers_.AddObserver(observer);
+}
+
+void SystemTrayNotifier::RemoveIMEObserver(IMEObserver* observer) {
+ ime_observers_.RemoveObserver(observer);
+}
+
+void SystemTrayNotifier::AddLocaleObserver(LocaleObserver* observer) {
+ locale_observers_.AddObserver(observer);
+}
+
+void SystemTrayNotifier::RemoveLocaleObserver(LocaleObserver* observer) {
+ locale_observers_.RemoveObserver(observer);
+}
+
+void SystemTrayNotifier::AddLogoutButtonObserver(
+ LogoutButtonObserver* observer) {
+ logout_button_observers_.AddObserver(observer);
+}
+
+void SystemTrayNotifier::RemoveLogoutButtonObserver(
+ LogoutButtonObserver* observer) {
+ logout_button_observers_.RemoveObserver(observer);
+}
+
+void SystemTrayNotifier::AddSessionLengthLimitObserver(
+ SessionLengthLimitObserver* observer) {
+ session_length_limit_observers_.AddObserver(observer);
+}
+
+void SystemTrayNotifier::RemoveSessionLengthLimitObserver(
+ SessionLengthLimitObserver* observer) {
+ session_length_limit_observers_.RemoveObserver(observer);
+}
+
+void SystemTrayNotifier::AddTracingObserver(TracingObserver* observer) {
+ tracing_observers_.AddObserver(observer);
+}
+
+void SystemTrayNotifier::RemoveTracingObserver(TracingObserver* observer) {
+ tracing_observers_.RemoveObserver(observer);
+}
+
+void SystemTrayNotifier::AddUpdateObserver(UpdateObserver* observer) {
+ update_observers_.AddObserver(observer);
+}
+
+void SystemTrayNotifier::RemoveUpdateObserver(UpdateObserver* observer) {
+ update_observers_.RemoveObserver(observer);
+}
+
+void SystemTrayNotifier::AddUserObserver(UserObserver* observer) {
+ user_observers_.AddObserver(observer);
+}
+
+void SystemTrayNotifier::RemoveUserObserver(UserObserver* observer) {
+ user_observers_.RemoveObserver(observer);
+}
+
+#if defined(OS_CHROMEOS)
+
+void SystemTrayNotifier::AddNetworkObserver(NetworkObserver* observer) {
+ network_observers_.AddObserver(observer);
+}
+
+void SystemTrayNotifier::RemoveNetworkObserver(NetworkObserver* observer) {
+ network_observers_.RemoveObserver(observer);
+}
+
+void SystemTrayNotifier::AddEnterpriseDomainObserver(
+ EnterpriseDomainObserver* observer) {
+ enterprise_domain_observers_.AddObserver(observer);
+}
+
+void SystemTrayNotifier::RemoveEnterpriseDomainObserver(
+ EnterpriseDomainObserver* observer) {
+ enterprise_domain_observers_.RemoveObserver(observer);
+}
+
+void SystemTrayNotifier::AddScreenCaptureObserver(
+ ScreenCaptureObserver* observer) {
+ screen_capture_observers_.AddObserver(observer);
+}
+
+void SystemTrayNotifier::RemoveScreenCaptureObserver(
+ ScreenCaptureObserver* observer) {
+ screen_capture_observers_.RemoveObserver(observer);
+}
+
+void SystemTrayNotifier::AddScreenShareObserver(
+ ScreenShareObserver* observer) {
+ screen_share_observers_.AddObserver(observer);
+}
+
+void SystemTrayNotifier::RemoveScreenShareObserver(
+ ScreenShareObserver* observer) {
+ screen_share_observers_.RemoveObserver(observer);
+}
+#endif
+
+void SystemTrayNotifier::NotifyAccessibilityModeChanged(
+ AccessibilityNotificationVisibility notify) {
+ FOR_EACH_OBSERVER(
+ AccessibilityObserver,
+ accessibility_observers_,
+ OnAccessibilityModeChanged(notify));
+}
+
+void SystemTrayNotifier::NotifyTracingModeChanged(bool value) {
+ FOR_EACH_OBSERVER(
+ TracingObserver,
+ tracing_observers_,
+ OnTracingModeChanged(value));
+}
+
+void SystemTrayNotifier::NotifyRefreshBluetooth() {
+ FOR_EACH_OBSERVER(BluetoothObserver,
+ bluetooth_observers_,
+ OnBluetoothRefresh());
+}
+
+void SystemTrayNotifier::NotifyBluetoothDiscoveringChanged() {
+ FOR_EACH_OBSERVER(BluetoothObserver,
+ bluetooth_observers_,
+ OnBluetoothDiscoveringChanged());
+}
+
+void SystemTrayNotifier::NotifyBrightnessChanged(double level,
+ bool user_initiated) {
+ FOR_EACH_OBSERVER(
+ BrightnessObserver,
+ brightness_observers_,
+ OnBrightnessChanged(level, user_initiated));
+}
+
+void SystemTrayNotifier::NotifyCapsLockChanged(
+ bool enabled,
+ bool search_mapped_to_caps_lock) {
+ FOR_EACH_OBSERVER(CapsLockObserver,
+ caps_lock_observers_,
+ OnCapsLockChanged(enabled, search_mapped_to_caps_lock));
+}
+
+void SystemTrayNotifier::NotifyRefreshClock() {
+ FOR_EACH_OBSERVER(ClockObserver, clock_observers_, Refresh());
+}
+
+void SystemTrayNotifier::NotifyDateFormatChanged() {
+ FOR_EACH_OBSERVER(ClockObserver,
+ clock_observers_,
+ OnDateFormatChanged());
+}
+
+void SystemTrayNotifier::NotifySystemClockTimeUpdated() {
+ FOR_EACH_OBSERVER(ClockObserver,
+ clock_observers_,
+ OnSystemClockTimeUpdated());
+}
+
+void SystemTrayNotifier::NotifyDriveJobUpdated(
+ const DriveOperationStatus& status) {
+ FOR_EACH_OBSERVER(DriveObserver,
+ drive_observers_,
+ OnDriveJobUpdated(status));
+}
+
+void SystemTrayNotifier::NotifyRefreshIME(bool show_message) {
+ FOR_EACH_OBSERVER(IMEObserver,
+ ime_observers_,
+ OnIMERefresh(show_message));
+}
+
+void SystemTrayNotifier::NotifyShowLoginButtonChanged(bool show_login_button) {
+ FOR_EACH_OBSERVER(LogoutButtonObserver,
+ logout_button_observers_,
+ OnShowLogoutButtonInTrayChanged(show_login_button));
+}
+
+void SystemTrayNotifier::NotifyLocaleChanged(
+ LocaleObserver::Delegate* delegate,
+ const std::string& cur_locale,
+ const std::string& from_locale,
+ const std::string& to_locale) {
+ FOR_EACH_OBSERVER(
+ LocaleObserver,
+ locale_observers_,
+ OnLocaleChanged(delegate, cur_locale, from_locale, to_locale));
+}
+
+void SystemTrayNotifier::NotifySessionStartTimeChanged() {
+ FOR_EACH_OBSERVER(SessionLengthLimitObserver,
+ session_length_limit_observers_,
+ OnSessionStartTimeChanged());
+}
+
+void SystemTrayNotifier::NotifySessionLengthLimitChanged() {
+ FOR_EACH_OBSERVER(SessionLengthLimitObserver,
+ session_length_limit_observers_,
+ OnSessionLengthLimitChanged());
+}
+
+void SystemTrayNotifier::NotifyUpdateRecommended(
+ UpdateObserver::UpdateSeverity severity) {
+ FOR_EACH_OBSERVER(UpdateObserver,
+ update_observers_,
+ OnUpdateRecommended(severity));
+}
+
+void SystemTrayNotifier::NotifyUserUpdate() {
+ FOR_EACH_OBSERVER(UserObserver,
+ user_observers_,
+ OnUserUpdate());
+}
+
+#if defined(OS_CHROMEOS)
+
+void SystemTrayNotifier::NotifySetNetworkMessage(
+ NetworkTrayDelegate* delegate,
+ NetworkObserver::MessageType message_type,
+ NetworkObserver::NetworkType network_type,
+ const base::string16& title,
+ const base::string16& message,
+ const std::vector<base::string16>& links) {
+ FOR_EACH_OBSERVER(NetworkObserver,
+ network_observers_,
+ SetNetworkMessage(
+ delegate,
+ message_type,
+ network_type,
+ title,
+ message,
+ links));
+}
+
+void SystemTrayNotifier::NotifyClearNetworkMessage(
+ NetworkObserver::MessageType message_type) {
+ FOR_EACH_OBSERVER(NetworkObserver,
+ network_observers_,
+ ClearNetworkMessage(message_type));
+}
+
+void SystemTrayNotifier::NotifyRequestToggleWifi() {
+ FOR_EACH_OBSERVER(NetworkObserver,
+ network_observers_,
+ RequestToggleWifi());
+}
+
+void SystemTrayNotifier::NotifyEnterpriseDomainChanged() {
+ FOR_EACH_OBSERVER(EnterpriseDomainObserver, enterprise_domain_observers_,
+ OnEnterpriseDomainChanged());
+}
+
+void SystemTrayNotifier::NotifyScreenCaptureStart(
+ const base::Closure& stop_callback,
+ const base::string16& sharing_app_name) {
+ FOR_EACH_OBSERVER(ScreenCaptureObserver, screen_capture_observers_,
+ OnScreenCaptureStart(stop_callback, sharing_app_name));
+}
+
+void SystemTrayNotifier::NotifyScreenCaptureStop() {
+ FOR_EACH_OBSERVER(ScreenCaptureObserver, screen_capture_observers_,
+ OnScreenCaptureStop());
+}
+
+void SystemTrayNotifier::NotifyScreenShareStart(
+ const base::Closure& stop_callback,
+ const base::string16& helper_name) {
+ FOR_EACH_OBSERVER(ScreenShareObserver, screen_share_observers_,
+ OnScreenShareStart(stop_callback, helper_name));
+}
+
+void SystemTrayNotifier::NotifyScreenShareStop() {
+ FOR_EACH_OBSERVER(ScreenShareObserver, screen_share_observers_,
+ OnScreenShareStop());
+}
+
+#endif // OS_CHROMEOS
+
+} // namespace ash
diff --git a/chromium/ash/system/tray/system_tray_notifier.h b/chromium/ash/system/tray/system_tray_notifier.h
new file mode 100644
index 00000000000..2ae739fe9e3
--- /dev/null
+++ b/chromium/ash/system/tray/system_tray_notifier.h
@@ -0,0 +1,169 @@
+// Copyright (c) 2012 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 ASH_SYSTEM_TRAY_SYSTEM_TRAY_NOTIFIER_H_
+#define ASH_SYSTEM_TRAY_SYSTEM_TRAY_NOTIFIER_H_
+
+#include <string>
+#include <vector>
+
+#include "ash/ash_export.h"
+#include "ash/system/bluetooth/bluetooth_observer.h"
+#include "ash/system/brightness/brightness_observer.h"
+#include "ash/system/chromeos/enterprise/enterprise_domain_observer.h"
+#include "ash/system/chromeos/network/network_observer.h"
+#include "ash/system/chromeos/tray_tracing.h"
+#include "ash/system/date/clock_observer.h"
+#include "ash/system/drive/drive_observer.h"
+#include "ash/system/ime/ime_observer.h"
+#include "ash/system/locale/locale_observer.h"
+#include "ash/system/logout_button/logout_button_observer.h"
+#include "ash/system/session_length_limit/session_length_limit_observer.h"
+#include "ash/system/tray_accessibility.h"
+#include "ash/system/tray_caps_lock.h"
+#include "ash/system/user/update_observer.h"
+#include "ash/system/user/user_observer.h"
+#include "base/observer_list.h"
+
+#if defined(OS_CHROMEOS)
+#include "ash/system/chromeos/network/network_observer.h"
+#include "ash/system/chromeos/screen_security/screen_capture_observer.h"
+#include "ash/system/chromeos/screen_security/screen_share_observer.h"
+#endif
+
+namespace ash {
+
+#if defined(OS_CHROMEOS)
+class NetworkStateNotifier;
+#endif
+
+class ASH_EXPORT SystemTrayNotifier {
+public:
+ SystemTrayNotifier();
+ ~SystemTrayNotifier();
+
+ void AddAccessibilityObserver(AccessibilityObserver* observer);
+ void RemoveAccessibilityObserver(AccessibilityObserver* observer);
+
+ void AddBluetoothObserver(BluetoothObserver* observer);
+ void RemoveBluetoothObserver(BluetoothObserver* observer);
+
+ void AddBrightnessObserver(BrightnessObserver* observer);
+ void RemoveBrightnessObserver(BrightnessObserver* observer);
+
+ void AddCapsLockObserver(CapsLockObserver* observer);
+ void RemoveCapsLockObserver(CapsLockObserver* observer);
+
+ void AddClockObserver(ClockObserver* observer);
+ void RemoveClockObserver(ClockObserver* observer);
+
+ void AddDriveObserver(DriveObserver* observer);
+ void RemoveDriveObserver(DriveObserver* observer);
+
+ void AddIMEObserver(IMEObserver* observer);
+ void RemoveIMEObserver(IMEObserver* observer);
+
+ void AddLocaleObserver(LocaleObserver* observer);
+ void RemoveLocaleObserver(LocaleObserver* observer);
+
+ void AddLogoutButtonObserver(LogoutButtonObserver* observer);
+ void RemoveLogoutButtonObserver(LogoutButtonObserver* observer);
+
+ void AddSessionLengthLimitObserver(SessionLengthLimitObserver* observer);
+ void RemoveSessionLengthLimitObserver(SessionLengthLimitObserver* observer);
+
+ void AddTracingObserver(TracingObserver* observer);
+ void RemoveTracingObserver(TracingObserver* observer);
+
+ void AddUpdateObserver(UpdateObserver* observer);
+ void RemoveUpdateObserver(UpdateObserver* observer);
+
+ void AddUserObserver(UserObserver* observer);
+ void RemoveUserObserver(UserObserver* observer);
+
+#if defined(OS_CHROMEOS)
+ void AddNetworkObserver(NetworkObserver* observer);
+ void RemoveNetworkObserver(NetworkObserver* observer);
+
+ void AddEnterpriseDomainObserver(EnterpriseDomainObserver* observer);
+ void RemoveEnterpriseDomainObserver(EnterpriseDomainObserver* observer);
+
+ void AddScreenCaptureObserver(ScreenCaptureObserver* observer);
+ void RemoveScreenCaptureObserver(ScreenCaptureObserver* observer);
+
+ void AddScreenShareObserver(ScreenShareObserver* observer);
+ void RemoveScreenShareObserver(ScreenShareObserver* observer);
+#endif
+
+ void NotifyAccessibilityModeChanged(
+ AccessibilityNotificationVisibility notify);
+ void NotifyTracingModeChanged(bool value);
+ void NotifyRefreshBluetooth();
+ void NotifyBluetoothDiscoveringChanged();
+ void NotifyBrightnessChanged(double level, bool user_initialted);
+ void NotifyCapsLockChanged(bool enabled, bool search_mapped_to_caps_lock);
+ void NotifyRefreshClock();
+ void NotifyDateFormatChanged();
+ void NotifySystemClockTimeUpdated();
+ void NotifyDriveJobUpdated(const DriveOperationStatus& status);
+ void NotifyRefreshIME(bool show_message);
+ void NotifyShowLoginButtonChanged(bool show_login_button);
+ void NotifyLocaleChanged(LocaleObserver::Delegate* delegate,
+ const std::string& cur_locale,
+ const std::string& from_locale,
+ const std::string& to_locale);
+ void NotifySessionStartTimeChanged();
+ void NotifySessionLengthLimitChanged();
+ void NotifyUpdateRecommended(UpdateObserver::UpdateSeverity severity);
+ void NotifyUserUpdate();
+#if defined(OS_CHROMEOS)
+ void NotifySetNetworkMessage(NetworkTrayDelegate* delegate,
+ NetworkObserver::MessageType message_type,
+ NetworkObserver::NetworkType network_type,
+ const base::string16& title,
+ const base::string16& message,
+ const std::vector<base::string16>& links);
+ void NotifyClearNetworkMessage(NetworkObserver::MessageType message_type);
+ void NotifyRequestToggleWifi();
+ void NotifyEnterpriseDomainChanged();
+ void NotifyScreenCaptureStart(const base::Closure& stop_callback,
+ const base::string16& sharing_app_name);
+ void NotifyScreenCaptureStop();
+ void NotifyScreenShareStart(const base::Closure& stop_callback,
+ const base::string16& helper_name);
+ void NotifyScreenShareStop();
+
+ NetworkStateNotifier* network_state_notifier() {
+ return network_state_notifier_.get();
+ }
+#endif
+
+ private:
+ ObserverList<AccessibilityObserver> accessibility_observers_;
+ ObserverList<BluetoothObserver> bluetooth_observers_;
+ ObserverList<BrightnessObserver> brightness_observers_;
+ ObserverList<CapsLockObserver> caps_lock_observers_;
+ ObserverList<ClockObserver> clock_observers_;
+ ObserverList<DriveObserver> drive_observers_;
+ ObserverList<IMEObserver> ime_observers_;
+ ObserverList<LocaleObserver> locale_observers_;
+ ObserverList<LogoutButtonObserver> logout_button_observers_;
+ ObserverList<SessionLengthLimitObserver> session_length_limit_observers_;
+ ObserverList<TracingObserver> tracing_observers_;
+ ObserverList<UpdateObserver> update_observers_;
+ ObserverList<UserObserver> user_observers_;
+#if defined(OS_CHROMEOS)
+ ObserverList<NetworkObserver> network_observers_;
+ ObserverList<EnterpriseDomainObserver> enterprise_domain_observers_;
+ ObserverList<ScreenCaptureObserver> screen_capture_observers_;
+ ObserverList<ScreenShareObserver> screen_share_observers_;
+ scoped_ptr<NetworkStateNotifier> network_state_notifier_;
+#endif
+
+ DISALLOW_COPY_AND_ASSIGN(SystemTrayNotifier);
+};
+
+} // namespace ash
+
+#endif // ASH_SYSTEM_TRAY_SYSTEM_TRAY_NOTIFIER_H_
diff --git a/chromium/ash/system/tray/system_tray_unittest.cc b/chromium/ash/system/tray/system_tray_unittest.cc
new file mode 100644
index 00000000000..6d0b4d9e822
--- /dev/null
+++ b/chromium/ash/system/tray/system_tray_unittest.cc
@@ -0,0 +1,339 @@
+// Copyright (c) 2012 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.
+
+#include "ash/system/tray/system_tray.h"
+
+#include <vector>
+
+#include "ash/root_window_controller.h"
+#include "ash/shelf/shelf_widget.h"
+#include "ash/shell.h"
+#include "ash/system/status_area_widget.h"
+#include "ash/system/tray/system_tray_item.h"
+#include "ash/test/ash_test_base.h"
+#include "base/strings/utf_string_conversions.h"
+#include "ui/views/controls/label.h"
+#include "ui/views/layout/fill_layout.h"
+#include "ui/views/view.h"
+#include "ui/views/widget/widget.h"
+
+#if defined(OS_WIN)
+#include "base/win/windows_version.h"
+#endif
+
+namespace ash {
+namespace test {
+
+namespace {
+
+const int kStatusTrayOffsetFromScreenEdgeForTest = 4;
+
+SystemTray* GetSystemTray() {
+ return Shell::GetPrimaryRootWindowController()->shelf()->
+ status_area_widget()->system_tray();
+}
+
+// Trivial item implementation that tracks its views for testing.
+class TestItem : public SystemTrayItem {
+ public:
+ TestItem() : SystemTrayItem(GetSystemTray()), tray_view_(NULL) {}
+
+ virtual views::View* CreateTrayView(user::LoginStatus status) OVERRIDE {
+ tray_view_ = new views::View;
+ // Add a label so it has non-zero width.
+ tray_view_->SetLayoutManager(new views::FillLayout);
+ tray_view_->AddChildView(new views::Label(UTF8ToUTF16("Tray")));
+ return tray_view_;
+ }
+
+ virtual views::View* CreateDefaultView(user::LoginStatus status) OVERRIDE {
+ default_view_ = new views::View;
+ default_view_->SetLayoutManager(new views::FillLayout);
+ default_view_->AddChildView(new views::Label(UTF8ToUTF16("Default")));
+ return default_view_;
+ }
+
+ virtual views::View* CreateDetailedView(user::LoginStatus status) OVERRIDE {
+ detailed_view_ = new views::View;
+ detailed_view_->SetLayoutManager(new views::FillLayout);
+ detailed_view_->AddChildView(new views::Label(UTF8ToUTF16("Detailed")));
+ return detailed_view_;
+ }
+
+ virtual views::View* CreateNotificationView(
+ user::LoginStatus status) OVERRIDE {
+ notification_view_ = new views::View;
+ return notification_view_;
+ }
+
+ virtual void DestroyTrayView() OVERRIDE {
+ tray_view_ = NULL;
+ }
+
+ virtual void DestroyDefaultView() OVERRIDE {
+ default_view_ = NULL;
+ }
+
+ virtual void DestroyDetailedView() OVERRIDE {
+ detailed_view_ = NULL;
+ }
+
+ virtual void DestroyNotificationView() OVERRIDE {
+ notification_view_ = NULL;
+ }
+
+ virtual void UpdateAfterLoginStatusChange(
+ user::LoginStatus status) OVERRIDE {
+ }
+
+ views::View* tray_view() const { return tray_view_; }
+ views::View* default_view() const { return default_view_; }
+ views::View* detailed_view() const { return detailed_view_; }
+ views::View* notification_view() const { return notification_view_; }
+
+ private:
+ views::View* tray_view_;
+ views::View* default_view_;
+ views::View* detailed_view_;
+ views::View* notification_view_;
+};
+
+// Trivial item implementation that returns NULL from tray/default/detailed
+// view creation methods.
+class TestNoViewItem : public SystemTrayItem {
+ public:
+ TestNoViewItem() : SystemTrayItem(GetSystemTray()) {}
+
+ virtual views::View* CreateTrayView(user::LoginStatus status) OVERRIDE {
+ return NULL;
+ }
+
+ virtual views::View* CreateDefaultView(user::LoginStatus status) OVERRIDE {
+ return NULL;
+ }
+
+ virtual views::View* CreateDetailedView(user::LoginStatus status) OVERRIDE {
+ return NULL;
+ }
+
+ virtual views::View* CreateNotificationView(
+ user::LoginStatus status) OVERRIDE {
+ return NULL;
+ }
+
+ virtual void DestroyTrayView() OVERRIDE {}
+ virtual void DestroyDefaultView() OVERRIDE {}
+ virtual void DestroyDetailedView() OVERRIDE {}
+ virtual void DestroyNotificationView() OVERRIDE {}
+ virtual void UpdateAfterLoginStatusChange(
+ user::LoginStatus status) OVERRIDE {
+ }
+};
+
+} // namespace
+
+typedef AshTestBase SystemTrayTest;
+
+TEST_F(SystemTrayTest, SystemTrayDefaultView) {
+ SystemTray* tray = GetSystemTray();
+ ASSERT_TRUE(tray->GetWidget());
+
+ tray->ShowDefaultView(BUBBLE_CREATE_NEW);
+
+ // Ensure that closing the bubble destroys it.
+ ASSERT_TRUE(tray->CloseSystemBubble());
+ RunAllPendingInMessageLoop();
+ ASSERT_FALSE(tray->CloseSystemBubble());
+}
+
+TEST_F(SystemTrayTest, SystemTrayTestItems) {
+ SystemTray* tray = GetSystemTray();
+ ASSERT_TRUE(tray->GetWidget());
+
+ TestItem* test_item = new TestItem;
+ TestItem* detailed_item = new TestItem;
+ tray->AddTrayItem(test_item);
+ tray->AddTrayItem(detailed_item);
+
+ // Check items have been added
+ const std::vector<SystemTrayItem*>& items = tray->GetTrayItems();
+ ASSERT_TRUE(
+ std::find(items.begin(), items.end(), test_item) != items.end());
+ ASSERT_TRUE(
+ std::find(items.begin(), items.end(), detailed_item) != items.end());
+
+ // Ensure the tray views are created.
+ ASSERT_TRUE(test_item->tray_view() != NULL);
+ ASSERT_TRUE(detailed_item->tray_view() != NULL);
+
+ // Ensure a default views are created.
+ tray->ShowDefaultView(BUBBLE_CREATE_NEW);
+ ASSERT_TRUE(test_item->default_view() != NULL);
+ ASSERT_TRUE(detailed_item->default_view() != NULL);
+
+ // Show the detailed view, ensure it's created and the default view destroyed.
+ tray->ShowDetailedView(detailed_item, 0, false, BUBBLE_CREATE_NEW);
+ RunAllPendingInMessageLoop();
+ ASSERT_TRUE(test_item->default_view() == NULL);
+ ASSERT_TRUE(detailed_item->detailed_view() != NULL);
+
+ // Show the default view, ensure it's created and the detailed view destroyed.
+ tray->ShowDefaultView(BUBBLE_CREATE_NEW);
+ RunAllPendingInMessageLoop();
+ ASSERT_TRUE(test_item->default_view() != NULL);
+ ASSERT_TRUE(detailed_item->detailed_view() == NULL);
+}
+
+TEST_F(SystemTrayTest, SystemTrayNoViewItems) {
+ SystemTray* tray = GetSystemTray();
+ ASSERT_TRUE(tray->GetWidget());
+
+ // Verify that no crashes occur on items lacking some views.
+ TestNoViewItem* no_view_item = new TestNoViewItem;
+ tray->AddTrayItem(no_view_item);
+ tray->ShowDefaultView(BUBBLE_CREATE_NEW);
+ tray->ShowDetailedView(no_view_item, 0, false, BUBBLE_USE_EXISTING);
+ RunAllPendingInMessageLoop();
+}
+
+TEST_F(SystemTrayTest, TrayWidgetAutoResizes) {
+ SystemTray* tray = GetSystemTray();
+ ASSERT_TRUE(tray->GetWidget());
+
+ // Add an initial tray item so that the tray gets laid out correctly.
+ TestItem* initial_item = new TestItem;
+ tray->AddTrayItem(initial_item);
+
+ gfx::Size initial_size = tray->GetWidget()->GetWindowBoundsInScreen().size();
+
+ TestItem* new_item = new TestItem;
+ tray->AddTrayItem(new_item);
+
+ gfx::Size new_size = tray->GetWidget()->GetWindowBoundsInScreen().size();
+
+ // Adding the new item should change the size of the tray.
+ EXPECT_NE(initial_size.ToString(), new_size.ToString());
+
+ // Hiding the tray view of the new item should also change the size of the
+ // tray.
+ new_item->tray_view()->SetVisible(false);
+ EXPECT_EQ(initial_size.ToString(),
+ tray->GetWidget()->GetWindowBoundsInScreen().size().ToString());
+
+ new_item->tray_view()->SetVisible(true);
+ EXPECT_EQ(new_size.ToString(),
+ tray->GetWidget()->GetWindowBoundsInScreen().size().ToString());
+}
+
+TEST_F(SystemTrayTest, SystemTrayNotifications) {
+ SystemTray* tray = GetSystemTray();
+ ASSERT_TRUE(tray->GetWidget());
+
+ TestItem* test_item = new TestItem;
+ TestItem* detailed_item = new TestItem;
+ tray->AddTrayItem(test_item);
+ tray->AddTrayItem(detailed_item);
+
+ // Ensure the tray views are created.
+ ASSERT_TRUE(test_item->tray_view() != NULL);
+ ASSERT_TRUE(detailed_item->tray_view() != NULL);
+
+ // Ensure a notification view is created.
+ tray->ShowNotificationView(test_item);
+ ASSERT_TRUE(test_item->notification_view() != NULL);
+
+ // Show the default view, notification view should remain.
+ tray->ShowDefaultView(BUBBLE_CREATE_NEW);
+ RunAllPendingInMessageLoop();
+ ASSERT_TRUE(test_item->notification_view() != NULL);
+
+ // Show the detailed view, ensure the notificaiton view remains.
+ tray->ShowDetailedView(detailed_item, 0, false, BUBBLE_CREATE_NEW);
+ RunAllPendingInMessageLoop();
+ ASSERT_TRUE(detailed_item->detailed_view() != NULL);
+ ASSERT_TRUE(test_item->notification_view() != NULL);
+
+ // Hide the detailed view, ensure the notification view still exists.
+ ASSERT_TRUE(tray->CloseSystemBubble());
+ RunAllPendingInMessageLoop();
+ ASSERT_TRUE(detailed_item->detailed_view() == NULL);
+ ASSERT_TRUE(test_item->notification_view() != NULL);
+}
+
+TEST_F(SystemTrayTest, BubbleCreationTypesTest) {
+ SystemTray* tray = GetSystemTray();
+ ASSERT_TRUE(tray->GetWidget());
+
+ TestItem* test_item = new TestItem;
+ tray->AddTrayItem(test_item);
+
+ // Ensure the tray views are created.
+ ASSERT_TRUE(test_item->tray_view() != NULL);
+
+ // Show the default view, ensure the notification view is destroyed.
+ tray->ShowDefaultView(BUBBLE_CREATE_NEW);
+ RunAllPendingInMessageLoop();
+
+ views::Widget* widget = test_item->default_view()->GetWidget();
+ gfx::Rect bubble_bounds = widget->GetWindowBoundsInScreen();
+
+ tray->ShowDetailedView(test_item, 0, true, BUBBLE_USE_EXISTING);
+ RunAllPendingInMessageLoop();
+
+ EXPECT_FALSE(test_item->default_view());
+
+ EXPECT_EQ(bubble_bounds.ToString(), test_item->detailed_view()->GetWidget()->
+ GetWindowBoundsInScreen().ToString());
+ EXPECT_EQ(widget, test_item->detailed_view()->GetWidget());
+
+ tray->ShowDefaultView(BUBBLE_USE_EXISTING);
+ RunAllPendingInMessageLoop();
+
+ EXPECT_EQ(bubble_bounds.ToString(), test_item->default_view()->GetWidget()->
+ GetWindowBoundsInScreen().ToString());
+ EXPECT_EQ(widget, test_item->default_view()->GetWidget());
+}
+
+// Tests that the tray is laid out properly in the widget to make sure that the
+// tray extends to the correct edge of the screen.
+TEST_F(SystemTrayTest, TrayBoundsInWidget) {
+ internal::StatusAreaWidget* widget =
+ Shell::GetPrimaryRootWindowController()->shelf()->status_area_widget();
+ SystemTray* tray = widget->system_tray();
+
+ // Test in bottom alignment. Bottom and right edges of the view should be
+ // aligned with the widget.
+ widget->SetShelfAlignment(SHELF_ALIGNMENT_BOTTOM);
+ gfx::Rect window_bounds = widget->GetWindowBoundsInScreen();
+ gfx::Rect tray_bounds = tray->GetBoundsInScreen();
+ EXPECT_EQ(window_bounds.bottom(),
+ tray_bounds.bottom() + kStatusTrayOffsetFromScreenEdgeForTest);
+ EXPECT_EQ(window_bounds.right(), tray_bounds.right());
+
+ // Test in the top alignment. Top and right edges should match.
+ widget->SetShelfAlignment(SHELF_ALIGNMENT_TOP);
+ window_bounds = widget->GetWindowBoundsInScreen();
+ tray_bounds = tray->GetBoundsInScreen();
+ EXPECT_EQ(window_bounds.y(),
+ tray_bounds.y() - kStatusTrayOffsetFromScreenEdgeForTest);
+ EXPECT_EQ(window_bounds.right(), tray_bounds.right());
+
+ // Test in the left alignment. Left and bottom edges should match.
+ widget->SetShelfAlignment(SHELF_ALIGNMENT_LEFT);
+ window_bounds = widget->GetWindowBoundsInScreen();
+ tray_bounds = tray->GetBoundsInScreen();
+ EXPECT_EQ(window_bounds.bottom(), tray_bounds.bottom());
+ EXPECT_EQ(window_bounds.x(),
+ tray_bounds.x() - kStatusTrayOffsetFromScreenEdgeForTest);
+
+ // Test in the right alignment. Right and bottom edges should match.
+ widget->SetShelfAlignment(SHELF_ALIGNMENT_LEFT);
+ window_bounds = widget->GetWindowBoundsInScreen();
+ tray_bounds = tray->GetBoundsInScreen();
+ EXPECT_EQ(window_bounds.bottom(), tray_bounds.bottom());
+ EXPECT_EQ(window_bounds.right(), tray_bounds.right());
+}
+
+} // namespace test
+} // namespace ash
diff --git a/chromium/ash/system/tray/test_system_tray_delegate.cc b/chromium/ash/system/tray/test_system_tray_delegate.cc
new file mode 100644
index 00000000000..44b21fa8ff9
--- /dev/null
+++ b/chromium/ash/system/tray/test_system_tray_delegate.cc
@@ -0,0 +1,294 @@
+// Copyright (c) 2012 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.
+
+#include "ash/system/tray/test_system_tray_delegate.h"
+
+#include <string>
+
+#include "ash/session_state_delegate.h"
+#include "ash/shell.h"
+#include "ash/volume_control_delegate.h"
+#include "base/message_loop/message_loop.h"
+#include "base/time/time.h"
+
+namespace ash {
+namespace test {
+
+namespace {
+
+class TestVolumeControlDelegate : public VolumeControlDelegate {
+ public:
+ TestVolumeControlDelegate() {}
+ virtual ~TestVolumeControlDelegate() {}
+
+ virtual bool HandleVolumeMute(const ui::Accelerator& accelerator) OVERRIDE {
+ return true;
+ }
+ virtual bool HandleVolumeDown(const ui::Accelerator& accelerator) OVERRIDE {
+ return true;
+ }
+ virtual bool HandleVolumeUp(const ui::Accelerator& accelerator) OVERRIDE {
+ return true;
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(TestVolumeControlDelegate);
+};
+
+} // namespace
+
+TestSystemTrayDelegate::TestSystemTrayDelegate()
+ : bluetooth_enabled_(true),
+ caps_lock_enabled_(false),
+ should_show_display_notification_(false),
+ volume_control_delegate_(new TestVolumeControlDelegate) {
+}
+
+TestSystemTrayDelegate::~TestSystemTrayDelegate() {
+}
+
+void TestSystemTrayDelegate::Initialize() {
+}
+
+void TestSystemTrayDelegate::Shutdown() {
+}
+
+bool TestSystemTrayDelegate::GetTrayVisibilityOnStartup() {
+ return true;
+}
+
+// Overridden from SystemTrayDelegate:
+user::LoginStatus TestSystemTrayDelegate::GetUserLoginStatus() const {
+ // At new user image screen manager->IsUserLoggedIn() would return true
+ // but there's no browser session available yet so use SessionStarted().
+ SessionStateDelegate* delegate =
+ Shell::GetInstance()->session_state_delegate();
+
+ if (!delegate->IsActiveUserSessionStarted())
+ return ash::user::LOGGED_IN_NONE;
+ if (delegate->IsScreenLocked())
+ return user::LOGGED_IN_LOCKED;
+ // TODO(nkostylev): Support LOGGED_IN_OWNER, LOGGED_IN_GUEST, LOGGED_IN_KIOSK,
+ // LOGGED_IN_PUBLIC.
+ return user::LOGGED_IN_USER;
+}
+
+bool TestSystemTrayDelegate::IsOobeCompleted() const {
+ return true;
+}
+
+void TestSystemTrayDelegate::ChangeProfilePicture() {
+}
+
+const std::string TestSystemTrayDelegate::GetEnterpriseDomain() const {
+ return std::string();
+}
+
+const base::string16 TestSystemTrayDelegate::GetEnterpriseMessage() const {
+ return string16();
+}
+
+const std::string TestSystemTrayDelegate::GetLocallyManagedUserManager() const {
+ return std::string();
+}
+
+const base::string16 TestSystemTrayDelegate::GetLocallyManagedUserManagerName()
+ const {
+ return string16();
+}
+
+const base::string16 TestSystemTrayDelegate::GetLocallyManagedUserMessage()
+ const {
+ return string16();
+}
+
+bool TestSystemTrayDelegate::SystemShouldUpgrade() const {
+ return true;
+}
+
+base::HourClockType TestSystemTrayDelegate::GetHourClockType() const {
+ return base::k24HourClock;
+}
+
+void TestSystemTrayDelegate::ShowSettings() {
+}
+
+void TestSystemTrayDelegate::ShowDateSettings() {
+}
+
+void TestSystemTrayDelegate::ShowNetworkSettings(
+ const std::string& service_path) {
+}
+
+void TestSystemTrayDelegate::ShowBluetoothSettings() {
+}
+
+void TestSystemTrayDelegate::ShowDisplaySettings() {
+}
+
+void TestSystemTrayDelegate::ShowChromeSlow() {
+}
+
+bool TestSystemTrayDelegate::ShouldShowDisplayNotification() {
+ return should_show_display_notification_;
+}
+
+void TestSystemTrayDelegate::ShowDriveSettings() {
+}
+
+void TestSystemTrayDelegate::ShowIMESettings() {
+}
+
+void TestSystemTrayDelegate::ShowHelp() {
+}
+
+void TestSystemTrayDelegate::ShowAccessibilityHelp() {
+}
+
+void TestSystemTrayDelegate::ShowAccessibilitySettings() {
+}
+
+void TestSystemTrayDelegate::ShowPublicAccountInfo() {
+}
+
+void TestSystemTrayDelegate::ShowEnterpriseInfo() {
+}
+
+void TestSystemTrayDelegate::ShowLocallyManagedUserInfo() {
+}
+
+void TestSystemTrayDelegate::ShowUserLogin() {
+}
+
+void TestSystemTrayDelegate::ShutDown() {
+ base::MessageLoop::current()->Quit();
+}
+
+void TestSystemTrayDelegate::SignOut() {
+ base::MessageLoop::current()->Quit();
+}
+
+void TestSystemTrayDelegate::RequestLockScreen() {
+}
+
+void TestSystemTrayDelegate::RequestRestartForUpdate() {
+}
+
+void TestSystemTrayDelegate::GetAvailableBluetoothDevices(
+ BluetoothDeviceList* list) {
+}
+
+void TestSystemTrayDelegate::BluetoothStartDiscovering() {
+}
+
+void TestSystemTrayDelegate::BluetoothStopDiscovering() {
+}
+
+void TestSystemTrayDelegate::ConnectToBluetoothDevice(
+ const std::string& address) {
+}
+
+void TestSystemTrayDelegate::GetCurrentIME(IMEInfo* info) {
+}
+
+void TestSystemTrayDelegate::GetAvailableIMEList(IMEInfoList* list) {
+}
+
+void TestSystemTrayDelegate::GetCurrentIMEProperties(
+ IMEPropertyInfoList* list) {
+}
+
+void TestSystemTrayDelegate::SwitchIME(const std::string& ime_id) {
+}
+
+void TestSystemTrayDelegate::ActivateIMEProperty(const std::string& key) {
+}
+
+void TestSystemTrayDelegate::CancelDriveOperation(int32 operation_id) {
+}
+
+void TestSystemTrayDelegate::GetDriveOperationStatusList(
+ ash::DriveOperationStatusList*) {
+}
+
+void TestSystemTrayDelegate::ConfigureNetwork(const std::string& network_id) {
+}
+
+void TestSystemTrayDelegate::EnrollOrConfigureNetwork(
+ const std::string& network_id,
+ gfx::NativeWindow parent_window) {
+}
+
+void TestSystemTrayDelegate::ManageBluetoothDevices() {
+}
+
+void TestSystemTrayDelegate::ToggleBluetooth() {
+ bluetooth_enabled_ = !bluetooth_enabled_;
+}
+
+bool TestSystemTrayDelegate::IsBluetoothDiscovering() {
+ return false;
+}
+
+void TestSystemTrayDelegate::ShowMobileSimDialog() {
+}
+
+void TestSystemTrayDelegate::ShowMobileSetup(const std::string& network_id) {
+}
+
+void TestSystemTrayDelegate::ShowOtherWifi() {
+}
+
+void TestSystemTrayDelegate::ShowOtherVPN() {
+}
+
+void TestSystemTrayDelegate::ShowOtherCellular() {
+}
+
+bool TestSystemTrayDelegate::GetBluetoothAvailable() {
+ return true;
+}
+
+bool TestSystemTrayDelegate::GetBluetoothEnabled() {
+ return bluetooth_enabled_;
+}
+
+void TestSystemTrayDelegate::ChangeProxySettings() {
+}
+
+VolumeControlDelegate* TestSystemTrayDelegate::GetVolumeControlDelegate()
+ const {
+ return volume_control_delegate_.get();
+}
+
+void TestSystemTrayDelegate::SetVolumeControlDelegate(
+ scoped_ptr<VolumeControlDelegate> delegate) {
+ volume_control_delegate_ = delegate.Pass();
+}
+
+bool TestSystemTrayDelegate::GetSessionStartTime(
+ base::TimeTicks* session_start_time) {
+ return false;
+}
+
+bool TestSystemTrayDelegate::GetSessionLengthLimit(
+ base::TimeDelta* session_length_limit) {
+ return false;
+}
+
+int TestSystemTrayDelegate::GetSystemTrayMenuWidth() {
+ // This is the default width for English languages.
+ return 300;
+}
+
+base::string16 TestSystemTrayDelegate::FormatTimeDuration(
+ const base::TimeDelta& delta) const {
+ return base::string16();
+}
+
+void TestSystemTrayDelegate::MaybeSpeak(const std::string& utterance) const {
+}
+
+} // namespace test
+} // namespace ash
diff --git a/chromium/ash/system/tray/test_system_tray_delegate.h b/chromium/ash/system/tray/test_system_tray_delegate.h
new file mode 100644
index 00000000000..9be9c8ac652
--- /dev/null
+++ b/chromium/ash/system/tray/test_system_tray_delegate.h
@@ -0,0 +1,115 @@
+// Copyright (c) 2012 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 ASH_TEST_TEST_SYSTEM_TRAY_DELEGATE_H_
+#define ASH_TEST_TEST_SYSTEM_TRAY_DELEGATE_H_
+
+#include "ash/system/tray/system_tray_delegate.h"
+#include "base/compiler_specific.h"
+#include "base/memory/scoped_ptr.h"
+
+// TODO(oshima/stevenjb): Move this to ash/test. crbug.com/159693.
+
+namespace ash {
+namespace test {
+
+class TestSystemTrayDelegate : public SystemTrayDelegate {
+ public:
+ TestSystemTrayDelegate();
+
+ virtual ~TestSystemTrayDelegate();
+
+ public:
+ virtual void Initialize() OVERRIDE;
+ virtual void Shutdown() OVERRIDE;
+ virtual bool GetTrayVisibilityOnStartup() OVERRIDE;
+
+ // Overridden from SystemTrayDelegate:
+ virtual user::LoginStatus GetUserLoginStatus() const OVERRIDE;
+ virtual bool IsOobeCompleted() const OVERRIDE;
+ virtual void ChangeProfilePicture() OVERRIDE;
+ virtual const std::string GetEnterpriseDomain() const OVERRIDE;
+ virtual const base::string16 GetEnterpriseMessage() const OVERRIDE;
+ virtual const std::string GetLocallyManagedUserManager() const OVERRIDE;
+ virtual const base::string16 GetLocallyManagedUserManagerName() const
+ OVERRIDE;
+ virtual const base::string16 GetLocallyManagedUserMessage() const OVERRIDE;
+ virtual bool SystemShouldUpgrade() const OVERRIDE;
+ virtual base::HourClockType GetHourClockType() const OVERRIDE;
+ virtual void ShowSettings() OVERRIDE;
+ virtual void ShowDateSettings() OVERRIDE;
+ virtual void ShowNetworkSettings(const std::string& service_path) OVERRIDE;
+ virtual void ShowBluetoothSettings() OVERRIDE;
+ virtual void ShowDisplaySettings() OVERRIDE;
+ virtual void ShowChromeSlow() OVERRIDE;
+ virtual bool ShouldShowDisplayNotification() OVERRIDE;
+ virtual void ShowDriveSettings() OVERRIDE;
+ virtual void ShowIMESettings() OVERRIDE;
+ virtual void ShowHelp() OVERRIDE;
+ virtual void ShowAccessibilityHelp() OVERRIDE;
+ virtual void ShowAccessibilitySettings() OVERRIDE;
+ virtual void ShowPublicAccountInfo() OVERRIDE;
+ virtual void ShowEnterpriseInfo() OVERRIDE;
+ virtual void ShowLocallyManagedUserInfo() OVERRIDE;
+ virtual void ShowUserLogin() OVERRIDE;
+ virtual void ShutDown() OVERRIDE;
+ virtual void SignOut() OVERRIDE;
+ virtual void RequestLockScreen() OVERRIDE;
+ virtual void RequestRestartForUpdate() OVERRIDE;
+ virtual void GetAvailableBluetoothDevices(BluetoothDeviceList* list) OVERRIDE;
+ virtual void BluetoothStartDiscovering() OVERRIDE;
+ virtual void BluetoothStopDiscovering() OVERRIDE;
+ virtual void ConnectToBluetoothDevice(const std::string& address) OVERRIDE;
+ virtual void GetCurrentIME(IMEInfo* info) OVERRIDE;
+ virtual void GetAvailableIMEList(IMEInfoList* list) OVERRIDE;
+ virtual void GetCurrentIMEProperties(IMEPropertyInfoList* list) OVERRIDE;
+ virtual void SwitchIME(const std::string& ime_id) OVERRIDE;
+ virtual void ActivateIMEProperty(const std::string& key) OVERRIDE;
+ virtual void CancelDriveOperation(int32 operation_id) OVERRIDE;
+ virtual void GetDriveOperationStatusList(
+ ash::DriveOperationStatusList*) OVERRIDE;
+ virtual void ConfigureNetwork(const std::string& network_id) OVERRIDE;
+ virtual void EnrollOrConfigureNetwork(
+ const std::string& network_id,
+ gfx::NativeWindow parent_window) OVERRIDE;
+ virtual void ManageBluetoothDevices() OVERRIDE;
+ virtual void ToggleBluetooth() OVERRIDE;
+ virtual bool IsBluetoothDiscovering() OVERRIDE;
+ virtual void ShowMobileSimDialog() OVERRIDE;
+ virtual void ShowMobileSetup(const std::string& network_id) OVERRIDE;
+ virtual void ShowOtherWifi() OVERRIDE;
+ virtual void ShowOtherVPN() OVERRIDE;
+ virtual void ShowOtherCellular() OVERRIDE;
+ virtual bool GetBluetoothAvailable() OVERRIDE;
+ virtual bool GetBluetoothEnabled() OVERRIDE;
+ virtual void ChangeProxySettings() OVERRIDE;
+ virtual VolumeControlDelegate* GetVolumeControlDelegate() const OVERRIDE;
+ virtual void SetVolumeControlDelegate(
+ scoped_ptr<VolumeControlDelegate> delegate) OVERRIDE;
+ virtual bool GetSessionStartTime(
+ base::TimeTicks* session_start_time) OVERRIDE;
+ virtual bool GetSessionLengthLimit(
+ base::TimeDelta* session_length_limit) OVERRIDE;
+ virtual int GetSystemTrayMenuWidth() OVERRIDE;
+ virtual base::string16 FormatTimeDuration(
+ const base::TimeDelta& delta) const OVERRIDE;
+ virtual void MaybeSpeak(const std::string& utterance) const OVERRIDE;
+
+ void set_should_show_display_notification(bool should_show) {
+ should_show_display_notification_ = should_show;
+ }
+
+ private:
+ bool bluetooth_enabled_;
+ bool caps_lock_enabled_;
+ bool should_show_display_notification_;
+ scoped_ptr<VolumeControlDelegate> volume_control_delegate_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestSystemTrayDelegate);
+};
+
+} // namespace test
+} // namespace ash
+
+#endif // ASH_TEST_TEST_SYSTEM_TRAY_DELEGATE_H_
diff --git a/chromium/ash/system/tray/throbber_view.cc b/chromium/ash/system/tray/throbber_view.cc
new file mode 100644
index 00000000000..2c2cc206904
--- /dev/null
+++ b/chromium/ash/system/tray/throbber_view.cc
@@ -0,0 +1,110 @@
+// Copyright (c) 2013 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.
+
+#include "ash/system/tray/throbber_view.h"
+
+#include "ash/system/tray/tray_constants.h"
+#include "grit/ash_resources.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "ui/compositor/layer.h"
+#include "ui/compositor/scoped_layer_animation_settings.h"
+
+namespace ash {
+namespace internal {
+
+namespace {
+
+// Time in ms per throbber frame.
+const int kThrobberFrameMs = 30;
+
+// Duration for showing/hiding animation in milliseconds.
+const int kThrobberAnimationDurationMs = 200;
+
+} // namespace
+
+SystemTrayThrobber::SystemTrayThrobber(int frame_delay_ms)
+ : views::SmoothedThrobber(frame_delay_ms) {
+}
+
+SystemTrayThrobber::~SystemTrayThrobber() {
+}
+
+void SystemTrayThrobber::SetTooltipText(const base::string16& tooltip_text) {
+ tooltip_text_ = tooltip_text;
+}
+
+bool SystemTrayThrobber::GetTooltipText(const gfx::Point& p,
+ base::string16* tooltip) const {
+ if (tooltip_text_.empty())
+ return false;
+
+ *tooltip = tooltip_text_;
+ return true;
+}
+
+ThrobberView::ThrobberView() {
+ throbber_ = new SystemTrayThrobber(kThrobberFrameMs);
+ throbber_->SetFrames(ui::ResourceBundle::GetSharedInstance().GetImageNamed(
+ IDR_AURA_CROS_DEFAULT_THROBBER).ToImageSkia());
+ throbber_->set_stop_delay_ms(kThrobberAnimationDurationMs);
+ AddChildView(throbber_);
+
+ SetPaintToLayer(true);
+ layer()->SetFillsBoundsOpaquely(false);
+ layer()->SetOpacity(0.0);
+}
+
+ThrobberView::~ThrobberView() {
+}
+
+gfx::Size ThrobberView::GetPreferredSize() {
+ return gfx::Size(ash::kTrayPopupItemHeight, ash::kTrayPopupItemHeight);
+}
+
+void ThrobberView::Layout() {
+ View* child = child_at(0);
+ gfx::Size ps = child->GetPreferredSize();
+ child->SetBounds((width() - ps.width()) / 2,
+ (height() - ps.height()) / 2,
+ ps.width(), ps.height());
+ SizeToPreferredSize();
+}
+
+bool ThrobberView::GetTooltipText(const gfx::Point& p,
+ base::string16* tooltip) const {
+ if (tooltip_text_.empty())
+ return false;
+
+ *tooltip = tooltip_text_;
+ return true;
+}
+
+void ThrobberView::Start() {
+ ScheduleAnimation(true);
+ throbber_->Start();
+}
+
+void ThrobberView::Stop() {
+ ScheduleAnimation(false);
+ throbber_->Stop();
+}
+
+void ThrobberView::SetTooltipText(const base::string16& tooltip_text) {
+ tooltip_text_ = tooltip_text;
+ throbber_->SetTooltipText(tooltip_text);
+}
+
+void ThrobberView::ScheduleAnimation(bool start_throbber) {
+ // Stop any previous animation.
+ layer()->GetAnimator()->StopAnimating();
+
+ ui::ScopedLayerAnimationSettings animation(layer()->GetAnimator());
+ animation.SetTransitionDuration(
+ base::TimeDelta::FromMilliseconds(kThrobberAnimationDurationMs));
+
+ layer()->SetOpacity(start_throbber ? 1.0 : 0.0);
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/system/tray/throbber_view.h b/chromium/ash/system/tray/throbber_view.h
new file mode 100644
index 00000000000..0f2663280e2
--- /dev/null
+++ b/chromium/ash/system/tray/throbber_view.h
@@ -0,0 +1,65 @@
+// Copyright (c) 2013 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 ASH_SYSTEM_TRAY_THROBBER_VIEW_H_
+#define ASH_SYSTEM_TRAY_THROBBER_VIEW_H_
+
+#include "ui/gfx/size.h"
+#include "ui/views/controls/throbber.h"
+#include "ui/views/view.h"
+
+namespace ash {
+namespace internal {
+
+// A SmoothedThrobber with tooltip.
+class SystemTrayThrobber : public views::SmoothedThrobber {
+ public:
+ SystemTrayThrobber(int frame_delay_ms);
+ virtual ~SystemTrayThrobber();
+
+ void SetTooltipText(const base::string16& tooltip_text);
+
+ // Overriden from views::View.
+ virtual bool GetTooltipText(
+ const gfx::Point& p, base::string16* tooltip) const OVERRIDE;
+
+ private:
+ // The current tooltip text.
+ base::string16 tooltip_text_;
+
+ DISALLOW_COPY_AND_ASSIGN(SystemTrayThrobber);
+};
+
+// A View containing a SystemTrayThrobber with animation for starting/stopping.
+class ThrobberView : public views::View {
+ public:
+ ThrobberView();
+ virtual ~ThrobberView();
+
+ void Start();
+ void Stop();
+ void SetTooltipText(const base::string16& tooltip_text);
+
+ // Overriden from views::View.
+ virtual gfx::Size GetPreferredSize() OVERRIDE;
+ virtual void Layout() OVERRIDE;
+ virtual bool GetTooltipText(
+ const gfx::Point& p, base::string16* tooltip) const OVERRIDE;
+
+ private:
+ // Schedules animation for starting/stopping throbber.
+ void ScheduleAnimation(bool start_throbber);
+
+ SystemTrayThrobber* throbber_;
+
+ // The current tooltip text.
+ base::string16 tooltip_text_;
+
+ DISALLOW_COPY_AND_ASSIGN(ThrobberView);
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_SYSTEM_TRAY_THROBBER_VIEW_H_
diff --git a/chromium/ash/system/tray/tray_background_view.cc b/chromium/ash/system/tray/tray_background_view.cc
new file mode 100644
index 00000000000..9f8432218bb
--- /dev/null
+++ b/chromium/ash/system/tray/tray_background_view.cc
@@ -0,0 +1,608 @@
+// Copyright (c) 2012 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.
+
+#include "ash/system/tray/tray_background_view.h"
+
+#include "ash/ash_switches.h"
+#include "ash/root_window_controller.h"
+#include "ash/screen_ash.h"
+#include "ash/shelf/shelf_layout_manager.h"
+#include "ash/shelf/shelf_widget.h"
+#include "ash/shell.h"
+#include "ash/shell_window_ids.h"
+#include "ash/system/status_area_widget.h"
+#include "ash/system/status_area_widget_delegate.h"
+#include "ash/system/tray/system_tray.h"
+#include "ash/system/tray/tray_constants.h"
+#include "ash/system/tray/tray_event_filter.h"
+#include "ash/wm/property_util.h"
+#include "ash/wm/window_animations.h"
+#include "grit/ash_resources.h"
+#include "ui/aura/root_window.h"
+#include "ui/aura/window.h"
+#include "ui/base/accessibility/accessible_view_state.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "ui/gfx/canvas.h"
+#include "ui/gfx/image/image_skia.h"
+#include "ui/gfx/image/image_skia_operations.h"
+#include "ui/gfx/rect.h"
+#include "ui/gfx/screen.h"
+#include "ui/gfx/skia_util.h"
+#include "ui/views/background.h"
+#include "ui/views/layout/box_layout.h"
+
+namespace {
+
+const int kTrayBackgroundAlpha = 100;
+const int kTrayBackgroundHoverAlpha = 150;
+const SkColor kTrayBackgroundPressedColor = SkColorSetRGB(66, 129, 244);
+
+// Adjust the size of TrayContainer with additional padding.
+const int kTrayContainerVerticalPaddingBottomAlignment = 1;
+const int kTrayContainerHorizontalPaddingBottomAlignment = 1;
+const int kTrayContainerVerticalPaddingVerticalAlignment = 1;
+const int kTrayContainerHorizontalPaddingVerticalAlignment = 1;
+
+const int kAnimationDurationForPopupMS = 200;
+
+} // namespace
+
+using views::TrayBubbleView;
+
+namespace ash {
+namespace internal {
+
+// static
+const char TrayBackgroundView::kViewClassName[] = "tray/TrayBackgroundView";
+
+// Used to track when the anchor widget changes position on screen so that the
+// bubble position can be updated.
+class TrayBackgroundView::TrayWidgetObserver : public views::WidgetObserver {
+ public:
+ explicit TrayWidgetObserver(TrayBackgroundView* host)
+ : host_(host) {
+ }
+
+ virtual void OnWidgetBoundsChanged(views::Widget* widget,
+ const gfx::Rect& new_bounds) OVERRIDE {
+ host_->AnchorUpdated();
+ }
+
+ virtual void OnWidgetVisibilityChanged(views::Widget* widget,
+ bool visible) OVERRIDE {
+ host_->AnchorUpdated();
+ }
+
+ private:
+ TrayBackgroundView* host_;
+
+ DISALLOW_COPY_AND_ASSIGN(TrayWidgetObserver);
+};
+
+class TrayBackground : public views::Background {
+ public:
+ const static int kImageTypeDefault = 0;
+ const static int kImageTypeOnBlack = 1;
+ const static int kImageTypePressed = 2;
+ const static int kNumStates = 3;
+
+ const static int kImageHorizontal = 0;
+ const static int kImageVertical = 1;
+ const static int kNumOrientations = 2;
+
+ explicit TrayBackground(TrayBackgroundView* tray_background_view) :
+ tray_background_view_(tray_background_view) {
+ set_alpha(kTrayBackgroundAlpha);
+ ResourceBundle& rb = ResourceBundle::GetSharedInstance();
+ leading_images_[kImageHorizontal][kImageTypeDefault] =
+ rb.GetImageNamed(IDR_AURA_TRAY_BG_HORIZ_LEFT).ToImageSkia();
+ middle_images_[kImageHorizontal][kImageTypeDefault] =
+ rb.GetImageNamed(IDR_AURA_TRAY_BG_HORIZ_CENTER).ToImageSkia();
+ trailing_images_[kImageHorizontal][kImageTypeDefault] =
+ rb.GetImageNamed(IDR_AURA_TRAY_BG_HORIZ_RIGHT).ToImageSkia();
+
+ leading_images_[kImageHorizontal][kImageTypeOnBlack] =
+ rb.GetImageNamed(IDR_AURA_TRAY_BG_HORIZ_LEFT_ONBLACK).ToImageSkia();
+ middle_images_[kImageHorizontal][kImageTypeOnBlack] =
+ rb.GetImageNamed(IDR_AURA_TRAY_BG_HORIZ_CENTER_ONBLACK).ToImageSkia();
+ trailing_images_[kImageHorizontal][kImageTypeOnBlack] =
+ rb.GetImageNamed(IDR_AURA_TRAY_BG_HORIZ_RIGHT_ONBLACK).ToImageSkia();
+
+ leading_images_[kImageHorizontal][kImageTypePressed] =
+ rb.GetImageNamed(IDR_AURA_TRAY_BG_HORIZ_LEFT_PRESSED).ToImageSkia();
+ middle_images_[kImageHorizontal][kImageTypePressed] =
+ rb.GetImageNamed(IDR_AURA_TRAY_BG_HORIZ_CENTER_PRESSED).ToImageSkia();
+ trailing_images_[kImageHorizontal][kImageTypePressed] =
+ rb.GetImageNamed(IDR_AURA_TRAY_BG_HORIZ_RIGHT_PRESSED).ToImageSkia();
+
+ leading_images_[kImageVertical][kImageTypeDefault] =
+ rb.GetImageNamed(IDR_AURA_TRAY_BG_VERTICAL_TOP).ToImageSkia();
+ middle_images_[kImageVertical][kImageTypeDefault] =
+ rb.GetImageNamed(
+ IDR_AURA_TRAY_BG_VERTICAL_CENTER).ToImageSkia();
+ trailing_images_[kImageVertical][kImageTypeDefault] =
+ rb.GetImageNamed(IDR_AURA_TRAY_BG_VERTICAL_BOTTOM).ToImageSkia();
+
+ leading_images_[kImageVertical][kImageTypeOnBlack] =
+ rb.GetImageNamed(IDR_AURA_TRAY_BG_VERTICAL_TOP_ONBLACK).ToImageSkia();
+ middle_images_[kImageVertical][kImageTypeOnBlack] =
+ rb.GetImageNamed(
+ IDR_AURA_TRAY_BG_VERTICAL_CENTER_ONBLACK).ToImageSkia();
+ trailing_images_[kImageVertical][kImageTypeOnBlack] =
+ rb.GetImageNamed(
+ IDR_AURA_TRAY_BG_VERTICAL_BOTTOM_ONBLACK).ToImageSkia();
+
+ leading_images_[kImageVertical][kImageTypePressed] =
+ rb.GetImageNamed(IDR_AURA_TRAY_BG_VERTICAL_TOP_PRESSED).ToImageSkia();
+ middle_images_[kImageVertical][kImageTypePressed] =
+ rb.GetImageNamed(
+ IDR_AURA_TRAY_BG_VERTICAL_CENTER_PRESSED).ToImageSkia();
+ trailing_images_[kImageVertical][kImageTypePressed] =
+ rb.GetImageNamed(
+ IDR_AURA_TRAY_BG_VERTICAL_BOTTOM_PRESSED).ToImageSkia();
+ }
+
+ virtual ~TrayBackground() {}
+
+ SkColor color() { return color_; }
+ void set_color(SkColor color) { color_ = color; }
+ void set_alpha(int alpha) { color_ = SkColorSetARGB(alpha, 0, 0, 0); }
+
+ private:
+ ShelfWidget* GetShelfWidget() const {
+ return RootWindowController::ForWindow(tray_background_view_->
+ status_area_widget()->GetNativeWindow())->shelf();
+ }
+
+ void PaintForAlternateShelf(gfx::Canvas* canvas, views::View* view) const {
+ int orientation = kImageHorizontal;
+ ShelfWidget* shelf_widget = GetShelfWidget();
+ if (shelf_widget &&
+ !shelf_widget->shelf_layout_manager()->IsHorizontalAlignment())
+ orientation = kImageVertical;
+
+ int state = kImageTypeDefault;
+ if (tray_background_view_->IsPressed())
+ state = kImageTypePressed;
+ else if (shelf_widget && shelf_widget->GetDimsShelf())
+ state = kImageTypeOnBlack;
+ else
+ state = kImageTypeDefault;
+
+ const gfx::ImageSkia* leading = leading_images_[orientation][state];
+ const gfx::ImageSkia* middle = middle_images_[orientation][state];
+ const gfx::ImageSkia* trailing = trailing_images_[orientation][state];
+
+ gfx::Rect bounds(view->GetLocalBounds());
+ gfx::Point leading_location, trailing_location;
+ gfx::Rect middle_bounds;
+
+ if (orientation == kImageHorizontal) {
+ leading_location = gfx::Point(0, 0);
+ trailing_location = gfx::Point(bounds.width() - trailing->width(), 0);
+ middle_bounds = gfx::Rect(
+ leading->width(),
+ 0,
+ bounds.width() - (leading->width() + trailing->width()),
+ bounds.height());
+ } else {
+ leading_location = gfx::Point(0, 0);
+ trailing_location = gfx::Point(0, bounds.height() - trailing->height());
+ middle_bounds = gfx::Rect(
+ 0,
+ leading->height(),
+ bounds.width(),
+ bounds.height() - (leading->height() + trailing->height()));
+ }
+
+ canvas->DrawImageInt(*leading,
+ leading_location.x(),
+ leading_location.y());
+
+ canvas->DrawImageInt(*trailing,
+ trailing_location.x(),
+ trailing_location.y());
+
+ canvas->TileImageInt(*middle,
+ middle_bounds.x(),
+ middle_bounds.y(),
+ middle_bounds.width(),
+ middle_bounds.height());
+ }
+
+ // Overridden from views::Background.
+ virtual void Paint(gfx::Canvas* canvas, views::View* view) const OVERRIDE {
+ if (ash::switches::UseAlternateShelfLayout()) {
+ PaintForAlternateShelf(canvas, view);
+ } else {
+ SkPaint paint;
+ paint.setAntiAlias(true);
+ paint.setStyle(SkPaint::kFill_Style);
+ paint.setColor(color_);
+ SkPath path;
+ gfx::Rect bounds(view->GetLocalBounds());
+ SkScalar radius = SkIntToScalar(kTrayRoundedBorderRadius);
+ path.addRoundRect(gfx::RectToSkRect(bounds), radius, radius);
+ canvas->DrawPath(path, paint);
+ }
+ }
+
+ SkColor color_;
+ // Reference to the TrayBackgroundView for which this is a background.
+ TrayBackgroundView* tray_background_view_;
+
+ // References to the images used as backgrounds, they are owned by the
+ // resource bundle class.
+ const gfx::ImageSkia* leading_images_[kNumOrientations][kNumStates];
+ const gfx::ImageSkia* middle_images_[kNumOrientations][kNumStates];
+ const gfx::ImageSkia* trailing_images_[kNumOrientations][kNumStates];
+
+ DISALLOW_COPY_AND_ASSIGN(TrayBackground);
+};
+
+TrayBackgroundView::TrayContainer::TrayContainer(ShelfAlignment alignment)
+ : alignment_(alignment) {
+ UpdateLayout();
+}
+
+void TrayBackgroundView::TrayContainer::SetAlignment(ShelfAlignment alignment) {
+ if (alignment_ == alignment)
+ return;
+ alignment_ = alignment;
+ UpdateLayout();
+}
+
+gfx::Size TrayBackgroundView::TrayContainer::GetPreferredSize() {
+ if (size_.IsEmpty())
+ return views::View::GetPreferredSize();
+ return size_;
+}
+
+void TrayBackgroundView::TrayContainer::ChildPreferredSizeChanged(
+ views::View* child) {
+ PreferredSizeChanged();
+}
+
+void TrayBackgroundView::TrayContainer::ChildVisibilityChanged(View* child) {
+ PreferredSizeChanged();
+}
+
+void TrayBackgroundView::TrayContainer::ViewHierarchyChanged(
+ const ViewHierarchyChangedDetails& details) {
+ if (details.parent == this)
+ PreferredSizeChanged();
+}
+
+void TrayBackgroundView::TrayContainer::UpdateLayout() {
+ // Adjust the size of status tray dark background by adding additional
+ // empty border.
+ if (alignment_ == SHELF_ALIGNMENT_BOTTOM ||
+ alignment_ == SHELF_ALIGNMENT_TOP) {
+ int vertical_padding = kTrayContainerVerticalPaddingBottomAlignment;
+ int horizontal_padding = kTrayContainerHorizontalPaddingBottomAlignment;
+ if (ash::switches::UseAlternateShelfLayout()) {
+ vertical_padding = kPaddingFromEdgeOfShelf;
+ horizontal_padding = kPaddingFromEdgeOfShelf;
+ }
+ set_border(views::Border::CreateEmptyBorder(
+ vertical_padding,
+ horizontal_padding,
+ vertical_padding,
+ horizontal_padding));
+
+ views::BoxLayout* layout =
+ new views::BoxLayout(views::BoxLayout::kHorizontal, 0, 0, 0);
+ layout->set_spread_blank_space(true);
+ views::View::SetLayoutManager(layout);
+ } else {
+ int vertical_padding = kTrayContainerVerticalPaddingVerticalAlignment;
+ int horizontal_padding = kTrayContainerHorizontalPaddingVerticalAlignment;
+ if (ash::switches::UseAlternateShelfLayout()) {
+ vertical_padding = kPaddingFromEdgeOfShelf;
+ horizontal_padding = kPaddingFromEdgeOfShelf;
+ }
+ set_border(views::Border::CreateEmptyBorder(
+ vertical_padding,
+ horizontal_padding,
+ vertical_padding,
+ horizontal_padding));
+
+ views::BoxLayout* layout =
+ new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 0);
+ layout->set_spread_blank_space(true);
+ views::View::SetLayoutManager(layout);
+ }
+ PreferredSizeChanged();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// TrayBackgroundView
+
+TrayBackgroundView::TrayBackgroundView(
+ internal::StatusAreaWidget* status_area_widget)
+ : status_area_widget_(status_area_widget),
+ tray_container_(NULL),
+ shelf_alignment_(SHELF_ALIGNMENT_BOTTOM),
+ background_(NULL),
+ hide_background_animator_(this, 0, kTrayBackgroundAlpha),
+ hover_background_animator_(
+ this, 0, kTrayBackgroundHoverAlpha - kTrayBackgroundAlpha),
+ hovered_(false),
+ pressed_(false),
+ widget_observer_(new TrayWidgetObserver(this)) {
+ set_notify_enter_exit_on_child(true);
+
+ // Initially we want to paint the background, but without the hover effect.
+ SetPaintsBackground(true, internal::BackgroundAnimator::CHANGE_IMMEDIATE);
+ hover_background_animator_.SetPaintsBackground(false,
+ internal::BackgroundAnimator::CHANGE_IMMEDIATE);
+
+ tray_container_ = new TrayContainer(shelf_alignment_);
+ SetContents(tray_container_);
+ tray_event_filter_.reset(new TrayEventFilter);
+}
+
+TrayBackgroundView::~TrayBackgroundView() {
+ if (GetWidget())
+ GetWidget()->RemoveObserver(widget_observer_.get());
+}
+
+void TrayBackgroundView::Initialize() {
+ GetWidget()->AddObserver(widget_observer_.get());
+ SetBorder();
+}
+
+const char* TrayBackgroundView::GetClassName() const {
+ return kViewClassName;
+}
+
+void TrayBackgroundView::OnMouseEntered(const ui::MouseEvent& event) {
+ hovered_ = true;
+ if (!background_)
+ return;
+ if (pressed_)
+ return;
+
+ hover_background_animator_.SetPaintsBackground(true,
+ internal::BackgroundAnimator::CHANGE_ANIMATE);
+}
+
+void TrayBackgroundView::OnMouseExited(const ui::MouseEvent& event) {
+ hovered_ = false;
+ if (!background_)
+ return;
+ if (pressed_)
+ return;
+
+ hover_background_animator_.SetPaintsBackground(false,
+ internal::BackgroundAnimator::CHANGE_ANIMATE);
+}
+
+void TrayBackgroundView::ChildPreferredSizeChanged(views::View* child) {
+ PreferredSizeChanged();
+}
+
+void TrayBackgroundView::OnPaintFocusBorder(gfx::Canvas* canvas) {
+ // The tray itself expands to the right and bottom edge of the screen to make
+ // sure clicking on the edges brings up the popup. However, the focus border
+ // should be only around the container.
+ if (HasFocus() && (focusable() || IsAccessibilityFocusable()))
+ DrawBorder(canvas, GetContentsBounds());
+}
+
+void TrayBackgroundView::GetAccessibleState(ui::AccessibleViewState* state) {
+ state->role = ui::AccessibilityTypes::ROLE_PUSHBUTTON;
+ state->name = GetAccessibleNameForTray();
+}
+
+void TrayBackgroundView::AboutToRequestFocusFromTabTraversal(bool reverse) {
+ // Return focus to the login view. See crbug.com/120500.
+ views::View* v = GetNextFocusableView();
+ if (v)
+ v->AboutToRequestFocusFromTabTraversal(reverse);
+}
+
+bool TrayBackgroundView::PerformAction(const ui::Event& event) {
+ return false;
+}
+
+void TrayBackgroundView::UpdateBackground(int alpha) {
+ if (!background_)
+ return;
+ if (pressed_)
+ return;
+
+ background_->set_alpha(hide_background_animator_.alpha() +
+ hover_background_animator_.alpha());
+ SchedulePaint();
+}
+
+void TrayBackgroundView::SetContents(views::View* contents) {
+ SetLayoutManager(new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 0));
+ AddChildView(contents);
+}
+
+void TrayBackgroundView::SetPaintsBackground(
+ bool value,
+ internal::BackgroundAnimator::ChangeType change_type) {
+ hide_background_animator_.SetPaintsBackground(value, change_type);
+}
+
+void TrayBackgroundView::SetContentsBackground() {
+ background_ = new internal::TrayBackground(this);
+ tray_container_->set_background(background_);
+}
+
+ShelfLayoutManager* TrayBackgroundView::GetShelfLayoutManager() {
+ return ShelfLayoutManager::ForLauncher(GetWidget()->GetNativeView());
+}
+
+void TrayBackgroundView::SetShelfAlignment(ShelfAlignment alignment) {
+ shelf_alignment_ = alignment;
+ SetBorder();
+ tray_container_->SetAlignment(alignment);
+}
+
+void TrayBackgroundView::SetBorder() {
+ views::View* parent = status_area_widget_->status_area_widget_delegate();
+
+ if (ash::switches::UseAlternateShelfLayout()) {
+ set_border(views::Border::CreateEmptyBorder(
+ 0,
+ 0,
+ ShelfLayoutManager::kAutoHideSize,
+ 0));
+ } else {
+ // Tray views are laid out right-to-left or bottom-to-top
+ int on_edge = (this == parent->child_at(0));
+ // Change the border padding for different shelf alignment.
+ if (shelf_alignment() == SHELF_ALIGNMENT_BOTTOM) {
+ set_border(views::Border::CreateEmptyBorder(
+ 0, 0,
+ on_edge ? kPaddingFromBottomOfScreenBottomAlignment :
+ kPaddingFromBottomOfScreenBottomAlignment - 1,
+ on_edge ? kPaddingFromRightEdgeOfScreenBottomAlignment : 0));
+ } else if (shelf_alignment() == SHELF_ALIGNMENT_TOP) {
+ set_border(views::Border::CreateEmptyBorder(
+ on_edge ? kPaddingFromBottomOfScreenBottomAlignment :
+ kPaddingFromBottomOfScreenBottomAlignment - 1,
+ 0, 0,
+ on_edge ? kPaddingFromRightEdgeOfScreenBottomAlignment : 0));
+ } else if (shelf_alignment() == SHELF_ALIGNMENT_LEFT) {
+ set_border(views::Border::CreateEmptyBorder(
+ 0, kPaddingFromOuterEdgeOfLauncherVerticalAlignment,
+ on_edge ? kPaddingFromBottomOfScreenVerticalAlignment : 0,
+ kPaddingFromInnerEdgeOfLauncherVerticalAlignment));
+ } else {
+ set_border(views::Border::CreateEmptyBorder(
+ 0, kPaddingFromInnerEdgeOfLauncherVerticalAlignment,
+ on_edge ? kPaddingFromBottomOfScreenVerticalAlignment : 0,
+ kPaddingFromOuterEdgeOfLauncherVerticalAlignment));
+ }
+ }
+}
+
+void TrayBackgroundView::InitializeBubbleAnimations(
+ views::Widget* bubble_widget) {
+ views::corewm::SetWindowVisibilityAnimationType(
+ bubble_widget->GetNativeWindow(),
+ views::corewm::WINDOW_VISIBILITY_ANIMATION_TYPE_FADE);
+ views::corewm::SetWindowVisibilityAnimationTransition(
+ bubble_widget->GetNativeWindow(),
+ views::corewm::ANIMATE_HIDE);
+ views::corewm::SetWindowVisibilityAnimationDuration(
+ bubble_widget->GetNativeWindow(),
+ base::TimeDelta::FromMilliseconds(kAnimationDurationForPopupMS));
+}
+
+bool TrayBackgroundView::IsPressed() {
+ return pressed_;
+}
+
+aura::Window* TrayBackgroundView::GetBubbleWindowContainer() const {
+ return ash::Shell::GetContainer(
+ tray_container()->GetWidget()->GetNativeWindow()->GetRootWindow(),
+ ash::internal::kShellWindowId_SettingBubbleContainer);
+}
+
+gfx::Rect TrayBackgroundView::GetBubbleAnchorRect(
+ views::Widget* anchor_widget,
+ TrayBubbleView::AnchorType anchor_type,
+ TrayBubbleView::AnchorAlignment anchor_alignment) const {
+ gfx::Rect rect;
+ if (anchor_widget && anchor_widget->IsVisible()) {
+ rect = anchor_widget->GetWindowBoundsInScreen();
+ if (anchor_type == TrayBubbleView::ANCHOR_TYPE_TRAY) {
+ if (anchor_alignment == TrayBubbleView::ANCHOR_ALIGNMENT_BOTTOM) {
+ bool rtl = base::i18n::IsRTL();
+ rect.Inset(
+ rtl ? kPaddingFromRightEdgeOfScreenBottomAlignment : 0,
+ kTrayBubbleAnchorTopInsetBottomAnchor,
+ rtl ? 0 : kPaddingFromRightEdgeOfScreenBottomAlignment,
+ kPaddingFromBottomOfScreenBottomAlignment);
+ } else if (anchor_alignment == TrayBubbleView::ANCHOR_ALIGNMENT_LEFT) {
+ rect.Inset(0, 0, kPaddingFromInnerEdgeOfLauncherVerticalAlignment + 5,
+ kPaddingFromBottomOfScreenVerticalAlignment);
+ } else {
+ rect.Inset(kPaddingFromInnerEdgeOfLauncherVerticalAlignment + 1,
+ 0, 0, kPaddingFromBottomOfScreenVerticalAlignment);
+ }
+ } else if (anchor_type == TrayBubbleView::ANCHOR_TYPE_BUBBLE) {
+ // Invert the offsets to align with the bubble below.
+ // Note that with the alternate shelf layout the tips are not shown and
+ // the offsets for left and right alignment do not need to be applied.
+ int vertical_alignment = ash::switches::UseAlternateShelfLayout() ?
+ 0 : kPaddingFromInnerEdgeOfLauncherVerticalAlignment;
+ if (anchor_alignment == TrayBubbleView::ANCHOR_ALIGNMENT_LEFT) {
+ rect.Inset(vertical_alignment,
+ 0, 0, kPaddingFromBottomOfScreenVerticalAlignment);
+ } else if (anchor_alignment == TrayBubbleView::ANCHOR_ALIGNMENT_RIGHT) {
+ rect.Inset(0, 0, vertical_alignment,
+ kPaddingFromBottomOfScreenVerticalAlignment);
+ }
+ }
+ }
+
+ // TODO(jennyz): May need to add left/right alignment in the following code.
+ if (rect.IsEmpty()) {
+ aura::RootWindow* target_root = anchor_widget ?
+ anchor_widget->GetNativeView()->GetRootWindow() :
+ Shell::GetPrimaryRootWindow();
+ rect = target_root->bounds();
+ rect = gfx::Rect(
+ base::i18n::IsRTL() ? kPaddingFromRightEdgeOfScreenBottomAlignment :
+ rect.width() - kPaddingFromRightEdgeOfScreenBottomAlignment,
+ rect.height() - kPaddingFromBottomOfScreenBottomAlignment,
+ 0, 0);
+ rect = ScreenAsh::ConvertRectToScreen(target_root, rect);
+ }
+ return rect;
+}
+
+TrayBubbleView::AnchorAlignment TrayBackgroundView::GetAnchorAlignment() const {
+ switch (shelf_alignment_) {
+ case SHELF_ALIGNMENT_BOTTOM:
+ return TrayBubbleView::ANCHOR_ALIGNMENT_BOTTOM;
+ case SHELF_ALIGNMENT_LEFT:
+ return TrayBubbleView::ANCHOR_ALIGNMENT_LEFT;
+ case SHELF_ALIGNMENT_RIGHT:
+ return TrayBubbleView::ANCHOR_ALIGNMENT_RIGHT;
+ case SHELF_ALIGNMENT_TOP:
+ return TrayBubbleView::ANCHOR_ALIGNMENT_TOP;
+ }
+ NOTREACHED();
+ return TrayBubbleView::ANCHOR_ALIGNMENT_BOTTOM;
+}
+
+void TrayBackgroundView::SetBubbleVisible(bool visible) {
+ pressed_ = visible;
+ if (!background_)
+ return;
+
+ // Do not change gradually, changing color between grey and blue is weird.
+ if (pressed_)
+ background_->set_color(kTrayBackgroundPressedColor);
+ else if (hovered_)
+ background_->set_alpha(kTrayBackgroundHoverAlpha);
+ else
+ background_->set_alpha(kTrayBackgroundAlpha);
+ SchedulePaint();
+}
+
+void TrayBackgroundView::UpdateBubbleViewArrow(
+ views::TrayBubbleView* bubble_view) {
+ if (switches::UseAlternateShelfLayout())
+ return;
+
+ aura::RootWindow* root_window =
+ bubble_view->GetWidget()->GetNativeView()->GetRootWindow();
+ ash::internal::ShelfLayoutManager* shelf =
+ ShelfLayoutManager::ForLauncher(root_window);
+ bubble_view->SetArrowPaintType(
+ shelf->IsVisible() ? views::BubbleBorder::PAINT_NORMAL :
+ views::BubbleBorder::PAINT_TRANSPARENT);
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/system/tray/tray_background_view.h b/chromium/ash/system/tray/tray_background_view.h
new file mode 100644
index 00000000000..b0f28067f61
--- /dev/null
+++ b/chromium/ash/system/tray/tray_background_view.h
@@ -0,0 +1,180 @@
+// Copyright (c) 2012 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 ASH_SYSTEM_TRAY_TRAY_BACKGROUND_VIEW_H_
+#define ASH_SYSTEM_TRAY_TRAY_BACKGROUND_VIEW_H_
+
+#include "ash/ash_export.h"
+#include "ash/shelf/background_animator.h"
+#include "ash/shelf/shelf_types.h"
+#include "ash/system/tray/actionable_view.h"
+#include "ui/views/bubble/tray_bubble_view.h"
+
+namespace ash {
+namespace internal {
+
+class ShelfLayoutManager;
+class StatusAreaWidget;
+class TrayEventFilter;
+class TrayBackground;
+
+// Base class for children of StatusAreaWidget: SystemTray, WebNotificationTray,
+// LogoutButtonTray.
+// This class handles setting and animating the background when the Launcher
+// his shown/hidden. It also inherits from ActionableView so that the tray
+// items can override PerformAction when clicked on.
+class ASH_EXPORT TrayBackgroundView : public ActionableView,
+ public BackgroundAnimatorDelegate {
+ public:
+ static const char kViewClassName[];
+
+ // Base class for tray containers. Sets the border and layout. The container
+ // auto-resizes the widget when necessary.
+ class TrayContainer : public views::View {
+ public:
+ explicit TrayContainer(ShelfAlignment alignment);
+ virtual ~TrayContainer() {}
+
+ void SetAlignment(ShelfAlignment alignment);
+
+ void set_size(const gfx::Size& size) { size_ = size; }
+
+ // Overridden from views::View.
+ virtual gfx::Size GetPreferredSize() OVERRIDE;
+
+ protected:
+ // Overridden from views::View.
+ virtual void ChildPreferredSizeChanged(views::View* child) OVERRIDE;
+ virtual void ChildVisibilityChanged(View* child) OVERRIDE;
+ virtual void ViewHierarchyChanged(
+ const ViewHierarchyChangedDetails& details) OVERRIDE;
+
+ private:
+ void UpdateLayout();
+
+ ShelfAlignment alignment_;
+ gfx::Size size_;
+
+ DISALLOW_COPY_AND_ASSIGN(TrayContainer);
+ };
+
+ explicit TrayBackgroundView(internal::StatusAreaWidget* status_area_widget);
+ virtual ~TrayBackgroundView();
+
+ // Called after the tray has been added to the widget containing it.
+ virtual void Initialize();
+
+ // Overridden from views::View.
+ virtual const char* GetClassName() const OVERRIDE;
+ virtual void OnMouseEntered(const ui::MouseEvent& event) OVERRIDE;
+ virtual void OnMouseExited(const ui::MouseEvent& event) OVERRIDE;
+ virtual void ChildPreferredSizeChanged(views::View* child) OVERRIDE;
+ virtual void OnPaintFocusBorder(gfx::Canvas* canvas) OVERRIDE;
+ virtual void GetAccessibleState(ui::AccessibleViewState* state) OVERRIDE;
+ virtual void AboutToRequestFocusFromTabTraversal(bool reverse) OVERRIDE;
+
+ // Overridden from internal::ActionableView.
+ virtual bool PerformAction(const ui::Event& event) OVERRIDE;
+
+ // Overridden from internal::BackgroundAnimatorDelegate.
+ virtual void UpdateBackground(int alpha) OVERRIDE;
+
+ // Called whenever the shelf alignment changes.
+ virtual void SetShelfAlignment(ShelfAlignment alignment);
+
+ // Called when the anchor (tray or bubble) may have moved or changed.
+ virtual void AnchorUpdated() {}
+
+ // Called from GetAccessibleState, must return a valid accessible name.
+ virtual base::string16 GetAccessibleNameForTray() = 0;
+
+ // Hides the bubble associated with |bubble_view|. Called when the widget
+ // is closed.
+ virtual void HideBubbleWithView(const views::TrayBubbleView* bubble_view) = 0;
+
+ // Called by the bubble wrapper when a click event occurs outside the bubble.
+ // May close the bubble. Returns true if the event is handled.
+ virtual bool ClickedOutsideBubble() = 0;
+
+ // Sets |contents| as a child.
+ void SetContents(views::View* contents);
+
+ // Creates and sets contents background to |background_|.
+ void SetContentsBackground();
+
+ // Sets whether the tray paints a background. Default is true, but is set to
+ // false if a window overlaps the shelf.
+ void SetPaintsBackground(
+ bool value,
+ internal::BackgroundAnimator::ChangeType change_type);
+
+ // Initializes animations for the bubble.
+ void InitializeBubbleAnimations(views::Widget* bubble_widget);
+
+ // Returns the window hosting the bubble.
+ aura::Window* GetBubbleWindowContainer() const;
+
+ // Returns the anchor rect for the bubble.
+ gfx::Rect GetBubbleAnchorRect(
+ views::Widget* anchor_widget,
+ views::TrayBubbleView::AnchorType anchor_type,
+ views::TrayBubbleView::AnchorAlignment anchor_alignment) const;
+
+ // Returns the bubble anchor alignment based on |shelf_alignment_|.
+ views::TrayBubbleView::AnchorAlignment GetAnchorAlignment() const;
+
+ // Updates the view visual based on the visibility of the bubble.
+ void SetBubbleVisible(bool visible);
+
+ StatusAreaWidget* status_area_widget() {
+ return status_area_widget_;
+ }
+ const StatusAreaWidget* status_area_widget() const {
+ return status_area_widget_;
+ }
+ TrayContainer* tray_container() const { return tray_container_; }
+ ShelfAlignment shelf_alignment() const { return shelf_alignment_; }
+ TrayEventFilter* tray_event_filter() { return tray_event_filter_.get(); }
+
+ ShelfLayoutManager* GetShelfLayoutManager();
+
+ // Updates the arrow visibilty based on the launcher visibilty.
+ void UpdateBubbleViewArrow(views::TrayBubbleView* bubble_view);
+
+ // Provides the background with a function to query for pressed state.
+ virtual bool IsPressed();
+
+ private:
+ class TrayWidgetObserver;
+
+ // Called from Initialize after all status area trays have been created.
+ // Sets the border based on the position of the view.
+ void SetBorder();
+
+ // Unowned pointer to parent widget.
+ StatusAreaWidget* status_area_widget_;
+
+ // Convenience pointer to the contents view.
+ TrayContainer* tray_container_;
+
+ // Shelf alignment.
+ ShelfAlignment shelf_alignment_;
+
+ // Owned by the view passed to SetContents().
+ internal::TrayBackground* background_;
+
+ internal::BackgroundAnimator hide_background_animator_;
+ internal::BackgroundAnimator hover_background_animator_;
+ bool hovered_;
+ bool pressed_;
+ scoped_ptr<TrayWidgetObserver> widget_observer_;
+ scoped_ptr<TrayEventFilter> tray_event_filter_;
+
+ DISALLOW_COPY_AND_ASSIGN(TrayBackgroundView);
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_SYSTEM_TRAY_TRAY_BACKGROUND_VIEW_H_
diff --git a/chromium/ash/system/tray/tray_bar_button_with_title.cc b/chromium/ash/system/tray/tray_bar_button_with_title.cc
new file mode 100644
index 00000000000..0fd94d84892
--- /dev/null
+++ b/chromium/ash/system/tray/tray_bar_button_with_title.cc
@@ -0,0 +1,113 @@
+// Copyright 2013 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.
+
+#include "ash/system/tray/tray_bar_button_with_title.h"
+
+#include "ash/system/tray/tray_constants.h"
+#include "base/memory/scoped_ptr.h"
+#include "grit/ui_resources.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "ui/gfx/image/image_skia.h"
+#include "ui/views/controls/label.h"
+#include "ui/views/painter.h"
+
+namespace ash {
+namespace internal {
+
+namespace {
+
+const int kBarImagesActive[] = {
+ IDR_SLIDER_ACTIVE_LEFT,
+ IDR_SLIDER_ACTIVE_CENTER,
+ IDR_SLIDER_ACTIVE_RIGHT,
+};
+
+const int kBarImagesDisabled[] = {
+ IDR_SLIDER_DISABLED_LEFT,
+ IDR_SLIDER_DISABLED_CENTER,
+ IDR_SLIDER_DISABLED_RIGHT,
+};
+
+} // namespace
+
+class TrayBarButtonWithTitle::TrayBarButton : public views::View {
+ public:
+ TrayBarButton(const int bar_active_images[], const int bar_disabled_images[])
+ : views::View(),
+ bar_active_images_(bar_active_images),
+ bar_disabled_images_(bar_disabled_images),
+ painter_(new views::HorizontalPainter(bar_active_images_)){
+ }
+ virtual ~TrayBarButton() {}
+
+ // Overriden from views::View:
+ virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE {
+ painter_->Paint(canvas, size());
+ }
+
+ void Update(bool control_on) {
+ painter_.reset(new views::HorizontalPainter(
+ control_on ? bar_active_images_ : bar_disabled_images_));
+ SchedulePaint();
+ }
+
+ private:
+ const int* bar_active_images_;
+ const int* bar_disabled_images_;
+ scoped_ptr<views::HorizontalPainter> painter_;
+
+ DISALLOW_COPY_AND_ASSIGN(TrayBarButton);
+};
+
+TrayBarButtonWithTitle::TrayBarButtonWithTitle(views::ButtonListener* listener,
+ int title_id,
+ int width)
+ : views::CustomButton(listener),
+ image_(new TrayBarButton(kBarImagesActive, kBarImagesDisabled)),
+ title_(NULL),
+ width_(width) {
+ AddChildView(image_);
+ if (title_id != -1) {
+ title_ = new views::Label;
+ ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
+ base::string16 text = rb.GetLocalizedString(title_id);
+ title_->SetText(text);
+ AddChildView(title_);
+ }
+
+ image_height_ = ui::ResourceBundle::GetSharedInstance().GetImageNamed(
+ kBarImagesActive[0]).ToImageSkia()->height();
+}
+
+TrayBarButtonWithTitle::~TrayBarButtonWithTitle() {}
+
+void TrayBarButtonWithTitle::UpdateButton(bool control_on) {
+ image_->Update(control_on);
+}
+
+gfx::Size TrayBarButtonWithTitle::GetPreferredSize() {
+ return gfx::Size(width_, kTrayPopupItemHeight);
+}
+
+void TrayBarButtonWithTitle::Layout() {
+ gfx::Rect rect(GetContentsBounds());
+ int bar_image_y = rect.height() / 2 - image_height_ / 2;
+ gfx::Rect bar_image_rect(rect.x(),
+ bar_image_y,
+ rect.width(),
+ image_height_);
+ image_->SetBoundsRect(bar_image_rect);
+ if (title_) {
+ // The image_ has some empty space below the bar image, move the title
+ // a little bit up to look closer to the bar.
+ gfx::Size title_size = title_->GetPreferredSize();
+ title_->SetBounds(rect.x(),
+ bar_image_y + image_height_ - 3,
+ rect.width(),
+ title_size.height());
+ }
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/system/tray/tray_bar_button_with_title.h b/chromium/ash/system/tray/tray_bar_button_with_title.h
new file mode 100644
index 00000000000..8b63d4f8fa7
--- /dev/null
+++ b/chromium/ash/system/tray/tray_bar_button_with_title.h
@@ -0,0 +1,49 @@
+// Copyright 2013 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 ASH_SYSTEM_TRAY_TRAY_BAR_BUTTON_WITH_TITLE_H_
+#define ASH_SYSTEM_TRAY_TRAY_BAR_BUTTON_WITH_TITLE_H_
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "ui/views/controls/button/custom_button.h"
+
+namespace views {
+class Label;
+}
+
+namespace ash {
+namespace internal {
+
+// A button with a bar image and title text below the bar image. These buttons
+// will be used in audio and brightness control UI, which can be toggled with
+// on/off states.
+class TrayBarButtonWithTitle : public views::CustomButton {
+ public:
+ TrayBarButtonWithTitle(views::ButtonListener* listener,
+ int title_id,
+ int width);
+ virtual ~TrayBarButtonWithTitle();
+
+ void UpdateButton(bool control_on);
+
+ private:
+ class TrayBarButton;
+
+ // Overridden from views::CustomButton:
+ virtual gfx::Size GetPreferredSize() OVERRIDE;
+ virtual void Layout() OVERRIDE;
+
+ TrayBarButton* image_;
+ views::Label* title_;
+ int width_;
+ int image_height_;
+
+ DISALLOW_COPY_AND_ASSIGN(TrayBarButtonWithTitle);
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_SYSTEM_TRAY_TRAY_BAR_BUTTON_WITH_TITLE_H_
diff --git a/chromium/ash/system/tray/tray_bubble_wrapper.cc b/chromium/ash/system/tray/tray_bubble_wrapper.cc
new file mode 100644
index 00000000000..5d641ee9190
--- /dev/null
+++ b/chromium/ash/system/tray/tray_bubble_wrapper.cc
@@ -0,0 +1,57 @@
+// Copyright (c) 2012 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.
+
+#include "ash/system/tray/tray_bubble_wrapper.h"
+
+#include "ash/system/tray/tray_background_view.h"
+#include "ash/system/tray/tray_event_filter.h"
+#include "ash/wm/window_properties.h"
+#include "ui/aura/client/capture_client.h"
+#include "ui/aura/window.h"
+#include "ui/views/bubble/tray_bubble_view.h"
+#include "ui/views/widget/widget.h"
+
+namespace ash {
+namespace internal {
+
+TrayBubbleWrapper::TrayBubbleWrapper(TrayBackgroundView* tray,
+ views::TrayBubbleView* bubble_view)
+ : tray_(tray),
+ bubble_view_(bubble_view) {
+ bubble_widget_ = views::BubbleDelegateView::CreateBubble(bubble_view_);
+ bubble_widget_->AddObserver(this);
+
+ tray_->InitializeBubbleAnimations(bubble_widget_);
+ tray_->UpdateBubbleViewArrow(bubble_view_);
+ bubble_view_->InitializeAndShowBubble();
+
+ tray->tray_event_filter()->AddWrapper(this);
+}
+
+TrayBubbleWrapper::~TrayBubbleWrapper() {
+ tray_->tray_event_filter()->RemoveWrapper(this);
+ if (bubble_widget_) {
+ bubble_widget_->RemoveObserver(this);
+ bubble_widget_->Close();
+ }
+}
+
+void TrayBubbleWrapper::OnWidgetDestroying(views::Widget* widget) {
+ CHECK_EQ(bubble_widget_, widget);
+ bubble_widget_->RemoveObserver(this);
+ bubble_widget_ = NULL;
+
+ // Although the bubble is already closed, the next mouse release event
+ // will invoke PerformAction which reopens the bubble again. To prevent the
+ // reopen, the mouse capture of |tray_| has to be released.
+ // See crbug.com/177075
+ aura::client::CaptureClient* capture_client = aura::client::GetCaptureClient(
+ tray_->GetWidget()->GetNativeView()->GetRootWindow());
+ if (capture_client)
+ capture_client->ReleaseCapture(tray_->GetWidget()->GetNativeView());
+ tray_->HideBubbleWithView(bubble_view_); // May destroy |bubble_view_|
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/system/tray/tray_bubble_wrapper.h b/chromium/ash/system/tray/tray_bubble_wrapper.h
new file mode 100644
index 00000000000..baa54164738
--- /dev/null
+++ b/chromium/ash/system/tray/tray_bubble_wrapper.h
@@ -0,0 +1,49 @@
+// Copyright (c) 2012 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 ASH_SYSTEM_TRAY_TRAY_BUBBLE_WRAPPER_H_
+#define ASH_SYSTEM_TRAY_TRAY_BUBBLE_WRAPPER_H_
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "ui/views/widget/widget_observer.h"
+
+namespace views {
+class TrayBubbleView;
+}
+
+namespace ash {
+namespace internal {
+
+class TrayBackgroundView;
+class TrayEventFilter;
+
+// Creates and manages the Widget and EventFilter components of a bubble.
+
+class TrayBubbleWrapper : public views::WidgetObserver {
+ public:
+ TrayBubbleWrapper(TrayBackgroundView* tray,
+ views::TrayBubbleView* bubble_view);
+ virtual ~TrayBubbleWrapper();
+
+ // views::WidgetObserver overrides:
+ virtual void OnWidgetDestroying(views::Widget* widget) OVERRIDE;
+
+ const TrayBackgroundView* tray() const { return tray_; }
+ TrayBackgroundView* tray() { return tray_; }
+ const views::TrayBubbleView* bubble_view() const { return bubble_view_; }
+ const views::Widget* bubble_widget() const { return bubble_widget_; }
+
+ private:
+ TrayBackgroundView* tray_;
+ views::TrayBubbleView* bubble_view_; // unowned
+ views::Widget* bubble_widget_;
+
+ DISALLOW_COPY_AND_ASSIGN(TrayBubbleWrapper);
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_SYSTEM_TRAY_TRAY_BUBBLE_WRAPPER_H_
diff --git a/chromium/ash/system/tray/tray_constants.cc b/chromium/ash/system/tray/tray_constants.cc
new file mode 100644
index 00000000000..6d4b2e6b90d
--- /dev/null
+++ b/chromium/ash/system/tray/tray_constants.cc
@@ -0,0 +1,83 @@
+// Copyright (c) 2012 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.
+
+#include "ash/system/tray/tray_constants.h"
+
+#include "ash/ash_switches.h"
+#include "third_party/skia/include/core/SkColor.h"
+
+namespace ash {
+
+const int kPaddingFromRightEdgeOfScreenBottomAlignment = 7;
+const int kPaddingFromBottomOfScreenBottomAlignment = 7;
+const int kPaddingFromOuterEdgeOfLauncherVerticalAlignment = 8;
+const int kPaddingFromInnerEdgeOfLauncherVerticalAlignment = 9;
+const int kPaddingFromBottomOfScreenVerticalAlignment = 10;
+
+// Inset between the edge of the shelf region and the status tray.
+const int kPaddingFromEdgeOfShelf = 3;
+
+// Top inset of system tray bubble for bottom anchor alignment.
+const int kTrayBubbleAnchorTopInsetBottomAnchor = 3;
+
+const int kTrayImageItemHorizontalPaddingBottomAlignment = 1;
+const int kTrayImageItemHorizontalPaddingVerticalAlignment = 1;
+const int kTrayImageItemVerticalPaddingVerticalAlignment = 1;
+
+const int kTrayLabelItemHorizontalPaddingBottomAlignment = 7;
+const int kTrayLabelItemVerticalPaddingVeriticalAlignment = 4;
+
+const int kTrayMenuBottomRowPadding = 5;
+const int kTrayMenuBottomRowPaddingBetweenItems = -1;
+
+const int kTrayPopupAutoCloseDelayInSeconds = 2;
+const int kTrayPopupAutoCloseDelayForTextInSeconds = 5;
+const int kTrayPopupPaddingHorizontal = 18;
+const int kTrayPopupPaddingBetweenItems = 10;
+const int kTrayPopupTextSpacingVertical = 4;
+
+const int kTrayPopupItemHeight = 48;
+const int kTrayPopupDetailsIconWidth = 25;
+const int kTrayPopupScrollSeparatorHeight = 15;
+const int kTrayRoundedBorderRadius = 2;
+const int kTrayBarButtonWidth = 39;
+
+const SkColor kBackgroundColor = SkColorSetRGB(0xfe, 0xfe, 0xfe);
+const SkColor kHoverBackgroundColor = SkColorSetRGB(0xf3, 0xf3, 0xf3);
+const SkColor kPublicAccountBackgroundColor = SkColorSetRGB(0xf8, 0xe5, 0xb6);
+const SkColor kPublicAccountUserCardTextColor = SkColorSetRGB(0x66, 0x66, 0x66);
+const SkColor kPublicAccountUserCardNameColor = SK_ColorBLACK;
+
+const SkColor kHeaderBackgroundColor = SkColorSetRGB(0xf5, 0xf5, 0xf5);
+
+const SkColor kBorderDarkColor = SkColorSetRGB(0xaa, 0xaa, 0xaa);
+const SkColor kBorderLightColor = SkColorSetRGB(0xeb, 0xeb, 0xeb);
+const SkColor kButtonStrokeColor = SkColorSetRGB(0xdd, 0xdd, 0xdd);
+
+const SkColor kHeaderTextColorNormal = SkColorSetARGB(0x7f, 0, 0, 0);
+const SkColor kHeaderTextColorHover = SkColorSetARGB(0xd3, 0, 0, 0);
+
+const int kTrayPopupMinWidth = 300;
+const int kTrayPopupMaxWidth = 500;
+const int kNotificationIconWidth = 40;
+const int kNotificationButtonWidth = 32;
+const int kTrayNotificationContentsWidth = kTrayPopupMinWidth -
+ (kNotificationIconWidth + kNotificationButtonWidth +
+ (kTrayPopupPaddingHorizontal / 2) * 3);
+const int kTraySpacing = 8;
+const int kAlternateTraySpacing = 4;
+const int kShelfItemHeight = 31;
+const int kAlternateShelfItemHeight = 38;
+
+int GetTraySpacing() {
+ return ash::switches::UseAlternateShelfLayout() ?
+ kAlternateTraySpacing : kTraySpacing;
+}
+
+int GetShelfItemHeight() {
+ return ash::switches::UseAlternateShelfLayout() ?
+ kAlternateShelfItemHeight : kShelfItemHeight;
+}
+
+} // namespace ash
diff --git a/chromium/ash/system/tray/tray_constants.h b/chromium/ash/system/tray/tray_constants.h
new file mode 100644
index 00000000000..50b2210bca0
--- /dev/null
+++ b/chromium/ash/system/tray/tray_constants.h
@@ -0,0 +1,75 @@
+// Copyright (c) 2012 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 ASH_SYSTEM_TRAY_TRAY_CONSTANTS_H_
+#define ASH_SYSTEM_TRAY_TRAY_CONSTANTS_H_
+
+typedef unsigned int SkColor;
+
+namespace ash {
+
+extern const int kPaddingFromRightEdgeOfScreenBottomAlignment;
+extern const int kPaddingFromBottomOfScreenBottomAlignment;
+extern const int kPaddingFromOuterEdgeOfLauncherVerticalAlignment;
+extern const int kPaddingFromInnerEdgeOfLauncherVerticalAlignment;
+extern const int kPaddingFromBottomOfScreenVerticalAlignment;
+
+extern const int kPaddingFromEdgeOfShelf;
+
+extern const int kTrayBubbleAnchorTopInsetBottomAnchor;
+
+extern const int kTrayImageItemHorizontalPaddingBottomAlignment;
+extern const int kTrayImageItemHorizontalPaddingVerticalAlignment;
+extern const int kTrayImageItemVerticalPaddingVerticalAlignment;
+
+extern const int kTrayLabelItemHorizontalPaddingBottomAlignment;
+extern const int kTrayLabelItemVerticalPaddingVeriticalAlignment;
+
+extern const int kTrayMenuBottomRowPadding;
+extern const int kTrayMenuBottomRowPaddingBetweenItems;
+
+extern const int kTrayPopupAutoCloseDelayInSeconds;
+extern const int kTrayPopupAutoCloseDelayForTextInSeconds;
+extern const int kTrayPopupPaddingHorizontal;
+extern const int kTrayPopupPaddingBetweenItems;
+extern const int kTrayPopupTextSpacingVertical;
+
+extern const int kTrayPopupItemHeight;
+extern const int kTrayPopupDetailsIconWidth;
+extern const int kTrayPopupScrollSeparatorHeight;
+extern const int kTrayRoundedBorderRadius;
+extern const int kTrayBarButtonWidth;
+
+extern const SkColor kBackgroundColor;
+extern const SkColor kHoverBackgroundColor;
+extern const SkColor kPublicAccountBackgroundColor;
+extern const SkColor kPublicAccountUserCardTextColor;
+extern const SkColor kPublicAccountUserCardNameColor;
+
+extern const SkColor kHeaderBackgroundColor;
+
+extern const SkColor kBorderDarkColor;
+extern const SkColor kBorderLightColor;
+extern const SkColor kButtonStrokeColor;
+
+extern const SkColor kHeaderTextColorNormal;
+extern const SkColor kHeaderTextColorHover;
+
+extern const int kTrayPopupMinWidth;
+extern const int kTrayPopupMaxWidth;
+extern const int kNotificationIconWidth;
+extern const int kNotificationButtonWidth;
+extern const int kTrayNotificationContentsWidth;
+
+// Returns kTraySpacing or kAlternateTraySpacing as applicable
+// (Determined by ash::switches::UseAlternateShelfLayout).
+int GetTraySpacing();
+
+// Returns kShelfItemHeight or kAlternateShelfItemHeight as applicable
+// (Determined by ash::switches::UseAlternateShelfLayout).
+int GetShelfItemHeight();
+
+} // namespace ash
+
+#endif // ASH_SYSTEM_TRAY_TRAY_CONSTANTS_H_
diff --git a/chromium/ash/system/tray/tray_details_view.cc b/chromium/ash/system/tray/tray_details_view.cc
new file mode 100644
index 00000000000..c51920f1390
--- /dev/null
+++ b/chromium/ash/system/tray/tray_details_view.cc
@@ -0,0 +1,149 @@
+// Copyright (c) 2012 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.
+
+#include "ash/system/tray/tray_details_view.h"
+
+#include "ash/system/tray/fixed_sized_scroll_view.h"
+#include "ash/system/tray/system_tray_item.h"
+#include "ash/system/tray/tray_constants.h"
+#include "ui/gfx/canvas.h"
+#include "ui/views/background.h"
+#include "ui/views/controls/scroll_view.h"
+#include "ui/views/layout/box_layout.h"
+
+namespace ash {
+namespace internal {
+
+class ScrollSeparator : public views::View {
+ public:
+ ScrollSeparator() {}
+
+ virtual ~ScrollSeparator() {}
+
+ private:
+ // Overriden from views::View.
+ virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE {
+ canvas->FillRect(gfx::Rect(0, height() / 2, width(), 1), kBorderLightColor);
+ }
+ virtual gfx::Size GetPreferredSize() OVERRIDE {
+ return gfx::Size(1, kTrayPopupScrollSeparatorHeight);
+ }
+
+ DISALLOW_COPY_AND_ASSIGN(ScrollSeparator);
+};
+
+class ScrollBorder : public views::Border {
+ public:
+ ScrollBorder() {}
+ virtual ~ScrollBorder() {}
+
+ void set_visible(bool visible) { visible_ = visible; }
+
+ private:
+ // Overridden from views::Border.
+ virtual void Paint(const views::View& view, gfx::Canvas* canvas) OVERRIDE {
+ if (!visible_)
+ return;
+ canvas->FillRect(gfx::Rect(0, view.height() - 1, view.width(), 1),
+ kBorderLightColor);
+ }
+
+ virtual gfx::Insets GetInsets() const OVERRIDE {
+ return gfx::Insets(0, 0, 1, 0);
+ }
+
+ bool visible_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScrollBorder);
+};
+
+TrayDetailsView::TrayDetailsView(SystemTrayItem* owner)
+ : owner_(owner),
+ footer_(NULL),
+ scroller_(NULL),
+ scroll_content_(NULL),
+ scroll_border_(NULL) {
+ SetLayoutManager(new views::BoxLayout(views::BoxLayout::kVertical,
+ 0, 0, 0));
+ set_background(views::Background::CreateSolidBackground(kBackgroundColor));
+}
+
+TrayDetailsView::~TrayDetailsView() {
+}
+
+void TrayDetailsView::CreateSpecialRow(int string_id,
+ ViewClickListener* listener) {
+ DCHECK(!footer_);
+ footer_ = new SpecialPopupRow();
+ footer_->SetTextLabel(string_id, listener);
+ AddChildViewAt(footer_, child_count());
+}
+
+void TrayDetailsView::CreateScrollableList() {
+ DCHECK(!scroller_);
+ scroll_content_ = new views::View;
+ scroll_content_->SetLayoutManager(new views::BoxLayout(
+ views::BoxLayout::kVertical, 0, 0, 1));
+ scroller_ = new FixedSizedScrollView;
+ scroller_->SetContentsView(scroll_content_);
+
+ // Note: |scroller_| takes ownership of |scroll_border_|.
+ scroll_border_ = new ScrollBorder;
+ scroller_->set_border(scroll_border_);
+
+ AddChildView(scroller_);
+}
+
+void TrayDetailsView::AddScrollSeparator() {
+ DCHECK(scroll_content_);
+ // Do not draw the separator if it is the very first item
+ // in the scrollable list.
+ if (scroll_content_->has_children())
+ scroll_content_->AddChildView(new ScrollSeparator);
+}
+
+void TrayDetailsView::Reset() {
+ RemoveAllChildViews(true);
+ footer_ = NULL;
+ scroller_ = NULL;
+ scroll_content_ = NULL;
+}
+
+void TrayDetailsView::Layout() {
+ if (!scroller_ || !footer_ || bounds().IsEmpty()) {
+ views::View::Layout();
+ return;
+ }
+
+ scroller_->set_fixed_size(gfx::Size());
+ gfx::Size size = GetPreferredSize();
+
+ // Set the scroller to fill the space above the bottom row, so that the
+ // bottom row of the detailed view will always stay just above the footer.
+ gfx::Size scroller_size = scroll_content_->GetPreferredSize();
+ scroller_->set_fixed_size(gfx::Size(
+ width() + scroller_->GetScrollBarWidth(),
+ scroller_size.height() - (size.height() - height())));
+
+ views::View::Layout();
+ // Always make sure the footer element is bottom aligned.
+ gfx::Rect fbounds = footer_->bounds();
+ fbounds.set_y(height() - footer_->height());
+ footer_->SetBoundsRect(fbounds);
+}
+
+void TrayDetailsView::OnPaintBorder(gfx::Canvas* canvas) {
+ if (scroll_border_) {
+ int index = GetIndexOf(scroller_);
+ if (index < child_count() - 1 && child_at(index + 1) != footer_)
+ scroll_border_->set_visible(true);
+ else
+ scroll_border_->set_visible(false);
+ }
+
+ views::View::OnPaintBorder(canvas);
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/system/tray/tray_details_view.h b/chromium/ash/system/tray/tray_details_view.h
new file mode 100644
index 00000000000..073873ad0d9
--- /dev/null
+++ b/chromium/ash/system/tray/tray_details_view.h
@@ -0,0 +1,67 @@
+// Copyright (c) 2012 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 ASH_SYSTEM_TRAY_TRAY_DETAILS_VIEW_H_
+#define ASH_SYSTEM_TRAY_TRAY_DETAILS_VIEW_H_
+
+#include "ash/system/tray/special_popup_row.h"
+#include "ui/views/view.h"
+
+namespace views {
+class ScrollView;
+}
+
+namespace ash {
+
+class SystemTrayItem;
+
+namespace internal {
+
+class FixedSizedScrollView;
+class ScrollBorder;
+class ViewClickListener;
+
+class TrayDetailsView : public views::View {
+ public:
+ explicit TrayDetailsView(SystemTrayItem* owner);
+ virtual ~TrayDetailsView();
+
+ // Creates a row with special highlighting etc. This is typically the
+ // bottom-most row in the popup.
+ void CreateSpecialRow(int string_id, ViewClickListener* listener);
+
+ // Creates a scrollable list. The list has a border at the bottom if there is
+ // any other view between the list and the footer row at the bottom.
+ void CreateScrollableList();
+
+ // Adds a separator in scrollable list.
+ void AddScrollSeparator();
+
+ // Removes (and destroys) all child views.
+ void Reset();
+
+ SystemTrayItem* owner() const { return owner_; }
+ SpecialPopupRow* footer() const { return footer_; }
+ FixedSizedScrollView* scroller() const { return scroller_; }
+ views::View* scroll_content() const { return scroll_content_; }
+
+ protected:
+ // Overridden from views::View.
+ virtual void Layout() OVERRIDE;
+ virtual void OnPaintBorder(gfx::Canvas* canvas) OVERRIDE;
+
+ private:
+ SystemTrayItem* owner_;
+ SpecialPopupRow* footer_;
+ FixedSizedScrollView* scroller_;
+ views::View* scroll_content_;
+ ScrollBorder* scroll_border_;
+
+ DISALLOW_COPY_AND_ASSIGN(TrayDetailsView);
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_SYSTEM_TRAY_TRAY_DETAILS_VIEW_H_
diff --git a/chromium/ash/system/tray/tray_empty.cc b/chromium/ash/system/tray/tray_empty.cc
new file mode 100644
index 00000000000..27fb7302ede
--- /dev/null
+++ b/chromium/ash/system/tray/tray_empty.cc
@@ -0,0 +1,67 @@
+// Copyright (c) 2012 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.
+
+#include "ash/system/tray/tray_empty.h"
+
+#include "ui/views/layout/box_layout.h"
+#include "ui/views/background.h"
+#include "ui/views/view.h"
+
+namespace {
+
+class EmptyBackground : public views::Background {
+ public:
+ EmptyBackground() {}
+ virtual ~EmptyBackground() {}
+
+ private:
+ virtual void Paint(gfx::Canvas* canvas, views::View* view) const OVERRIDE {
+ }
+
+ DISALLOW_COPY_AND_ASSIGN(EmptyBackground);
+};
+
+}
+
+namespace ash {
+namespace internal {
+
+TrayEmpty::TrayEmpty(SystemTray* system_tray)
+ : SystemTrayItem(system_tray) {
+}
+
+TrayEmpty::~TrayEmpty() {}
+
+views::View* TrayEmpty::CreateTrayView(user::LoginStatus status) {
+ return NULL;
+}
+
+views::View* TrayEmpty::CreateDefaultView(user::LoginStatus status) {
+ if (status == user::LOGGED_IN_NONE)
+ return NULL;
+
+ views::View* view = new views::View;
+ view->set_background(new EmptyBackground());
+ view->set_border(views::Border::CreateEmptyBorder(10, 0, 0, 0));
+ view->SetLayoutManager(new views::BoxLayout(views::BoxLayout::kVertical,
+ 0, 0, 0));
+ view->SetPaintToLayer(true);
+ view->SetFillsBoundsOpaquely(false);
+ return view;
+}
+
+views::View* TrayEmpty::CreateDetailedView(user::LoginStatus status) {
+ return NULL;
+}
+
+void TrayEmpty::DestroyTrayView() {}
+
+void TrayEmpty::DestroyDefaultView() {}
+
+void TrayEmpty::DestroyDetailedView() {}
+
+void TrayEmpty::UpdateAfterLoginStatusChange(user::LoginStatus status) {}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/system/tray/tray_empty.h b/chromium/ash/system/tray/tray_empty.h
new file mode 100644
index 00000000000..a7575cf46e3
--- /dev/null
+++ b/chromium/ash/system/tray/tray_empty.h
@@ -0,0 +1,34 @@
+// Copyright (c) 2012 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 ASH_SYSTEM_TRAY_TRAY_EMPTY_H_
+#define ASH_SYSTEM_TRAY_TRAY_EMPTY_H_
+
+#include "ash/system/tray/system_tray_item.h"
+
+namespace ash {
+namespace internal {
+
+class TrayEmpty : public SystemTrayItem {
+ public:
+ explicit TrayEmpty(SystemTray* system_tray);
+ virtual ~TrayEmpty();
+
+ private:
+ // Overridden from SystemTrayItem.
+ virtual views::View* CreateTrayView(user::LoginStatus status) OVERRIDE;
+ virtual views::View* CreateDefaultView(user::LoginStatus status) OVERRIDE;
+ virtual views::View* CreateDetailedView(user::LoginStatus status) OVERRIDE;
+ virtual void DestroyTrayView() OVERRIDE;
+ virtual void DestroyDefaultView() OVERRIDE;
+ virtual void DestroyDetailedView() OVERRIDE;
+ virtual void UpdateAfterLoginStatusChange(user::LoginStatus status) OVERRIDE;
+
+ DISALLOW_COPY_AND_ASSIGN(TrayEmpty);
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_SYSTEM_TRAY_TRAY_EMPTY_H_
diff --git a/chromium/ash/system/tray/tray_event_filter.cc b/chromium/ash/system/tray/tray_event_filter.cc
new file mode 100644
index 00000000000..949b2f93a17
--- /dev/null
+++ b/chromium/ash/system/tray/tray_event_filter.cc
@@ -0,0 +1,114 @@
+// Copyright (c) 2012 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.
+
+#include "ash/system/tray/tray_event_filter.h"
+
+#include "ash/root_window_controller.h"
+#include "ash/shelf/shelf_layout_manager.h"
+#include "ash/shell.h"
+#include "ash/shell_window_ids.h"
+#include "ash/system/tray/tray_background_view.h"
+#include "ash/system/tray/tray_bubble_wrapper.h"
+#include "ash/system/tray/tray_constants.h"
+#include "ash/system/tray/tray_event_filter.h"
+#include "ash/wm/property_util.h"
+#include "ui/aura/client/screen_position_client.h"
+#include "ui/aura/root_window.h"
+#include "ui/aura/window.h"
+#include "ui/views/widget/widget.h"
+
+namespace ash {
+namespace internal {
+
+TrayEventFilter::TrayEventFilter() {
+}
+
+TrayEventFilter::~TrayEventFilter() {
+ DCHECK(wrappers_.empty());
+}
+
+void TrayEventFilter::AddWrapper(TrayBubbleWrapper* wrapper) {
+ bool was_empty = wrappers_.empty();
+ wrappers_.insert(wrapper);
+ if (was_empty && !wrappers_.empty())
+ ash::Shell::GetInstance()->AddPreTargetHandler(this);
+}
+
+void TrayEventFilter::RemoveWrapper(TrayBubbleWrapper* wrapper) {
+ wrappers_.erase(wrapper);
+ if (wrappers_.empty())
+ ash::Shell::GetInstance()->RemovePreTargetHandler(this);
+}
+
+void TrayEventFilter::OnMouseEvent(ui::MouseEvent* event) {
+ if (event->type() == ui::ET_MOUSE_PRESSED &&
+ ProcessLocatedEvent(event)) {
+ event->StopPropagation();
+ }
+}
+
+void TrayEventFilter::OnTouchEvent(ui::TouchEvent* event) {
+ if (event->type() == ui::ET_TOUCH_PRESSED && ProcessLocatedEvent(event))
+ event->StopPropagation();
+}
+
+bool TrayEventFilter::ProcessLocatedEvent(ui::LocatedEvent* event) {
+ if (event->target()) {
+ aura::Window* target = static_cast<aura::Window*>(event->target());
+ // Don't process events that occurred inside an embedded menu.
+ ash::internal::RootWindowController* root_controller =
+ ash::GetRootWindowController(target->GetRootWindow());
+ if (root_controller && root_controller->GetContainer(
+ ash::internal::kShellWindowId_MenuContainer)->Contains(target)) {
+ return false;
+ }
+ }
+
+ // Check the boundary for all wrappers, and do not handle the event if it
+ // happens inside of any of those wrappers.
+ for (std::set<TrayBubbleWrapper*>::const_iterator iter = wrappers_.begin();
+ iter != wrappers_.end(); ++iter) {
+ const TrayBubbleWrapper* wrapper = *iter;
+ const views::Widget* bubble_widget = wrapper->bubble_widget();
+ if (!bubble_widget)
+ continue;
+
+ gfx::Rect bounds = bubble_widget->GetWindowBoundsInScreen();
+ bounds.Inset(wrapper->bubble_view()->GetBorderInsets());
+ aura::RootWindow* root = bubble_widget->GetNativeView()->GetRootWindow();
+ aura::client::ScreenPositionClient* screen_position_client =
+ aura::client::GetScreenPositionClient(root);
+ gfx::Point screen_point(event->root_location());
+ screen_position_client->ConvertPointToScreen(root, &screen_point);
+
+ if (bounds.Contains(screen_point))
+ return false;
+ if (wrapper->tray()) {
+ // If the user clicks on the parent tray, don't process the event here,
+ // let the tray logic handle the event and determine show/hide behavior.
+ bounds = wrapper->tray()->GetBoundsInScreen();
+ if (bounds.Contains(screen_point))
+ return false;
+ }
+ }
+
+ // Handle clicking outside the bubble and tray and return true if the
+ // event was handled.
+ // Cannot iterate |wrappers_| directly, because clicking outside will remove
+ // the wrapper, which shrinks |wrappers_| unsafely.
+ std::set<TrayBackgroundView*> trays;
+ for (std::set<TrayBubbleWrapper*>::iterator iter = wrappers_.begin();
+ iter != wrappers_.end(); ++iter) {
+ trays.insert((*iter)->tray());
+ }
+ bool handled = false;
+ for (std::set<TrayBackgroundView*>::iterator iter = trays.begin();
+ iter != trays.end(); ++iter) {
+ handled |= (*iter)->ClickedOutsideBubble();
+ }
+ return handled;
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/system/tray/tray_event_filter.h b/chromium/ash/system/tray/tray_event_filter.h
new file mode 100644
index 00000000000..5ee8e27540e
--- /dev/null
+++ b/chromium/ash/system/tray/tray_event_filter.h
@@ -0,0 +1,49 @@
+// Copyright (c) 2012 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 ASH_SYSTEM_TRAY_TRAY_EVENT_FILTER_H_
+#define ASH_SYSTEM_TRAY_TRAY_EVENT_FILTER_H_
+
+#include <set>
+
+#include "base/basictypes.h"
+#include "ui/base/events/event.h"
+#include "ui/base/events/event_handler.h"
+
+namespace aura {
+class Window;
+}
+
+namespace ash {
+namespace internal {
+
+class TrayBubbleWrapper;
+
+// Handles events for a tray bubble.
+
+class TrayEventFilter : public ui::EventHandler {
+ public:
+ explicit TrayEventFilter();
+ virtual ~TrayEventFilter();
+
+ void AddWrapper(TrayBubbleWrapper* wrapper);
+ void RemoveWrapper(TrayBubbleWrapper* wrapper);
+
+ // Overridden from ui::EventHandler.
+ virtual void OnMouseEvent(ui::MouseEvent* event) OVERRIDE;
+ virtual void OnTouchEvent(ui::TouchEvent* event) OVERRIDE;
+
+ private:
+ // Returns true if the event is handled.
+ bool ProcessLocatedEvent(ui::LocatedEvent* event);
+
+ std::set<TrayBubbleWrapper*> wrappers_;
+
+ DISALLOW_COPY_AND_ASSIGN(TrayEventFilter);
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_SYSTEM_TRAY_TRAY_EVENT_FILTER_H_
diff --git a/chromium/ash/system/tray/tray_image_item.cc b/chromium/ash/system/tray/tray_image_item.cc
new file mode 100644
index 00000000000..7c5e216c942
--- /dev/null
+++ b/chromium/ash/system/tray/tray_image_item.cc
@@ -0,0 +1,93 @@
+// Copyright (c) 2012 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.
+
+#include "ash/system/tray/tray_image_item.h"
+
+#include "ash/system/tray/system_tray.h"
+#include "ash/system/tray/tray_item_view.h"
+#include "ash/system/tray/tray_utils.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "ui/gfx/image/image.h"
+#include "ui/views/controls/image_view.h"
+#include "ui/views/layout/box_layout.h"
+
+namespace ash {
+namespace internal {
+
+TrayImageItem::TrayImageItem(SystemTray* system_tray, int resource_id)
+ : SystemTrayItem(system_tray),
+ resource_id_(resource_id),
+ tray_view_(NULL) {
+}
+
+TrayImageItem::~TrayImageItem() {}
+
+views::View* TrayImageItem::tray_view() {
+ return tray_view_;
+}
+
+void TrayImageItem::SetImageFromResourceId(int resource_id) {
+ resource_id_ = resource_id;
+ if (!tray_view_)
+ return;
+ tray_view_->image_view()->SetImage(ui::ResourceBundle::GetSharedInstance().
+ GetImageNamed(resource_id_).ToImageSkia());
+}
+
+views::View* TrayImageItem::CreateTrayView(user::LoginStatus status) {
+ CHECK(tray_view_ == NULL);
+ tray_view_ = new TrayItemView(this);
+ tray_view_->CreateImageView();
+ tray_view_->image_view()->SetImage(ui::ResourceBundle::GetSharedInstance().
+ GetImageNamed(resource_id_).ToImageSkia());
+ tray_view_->SetVisible(GetInitialVisibility());
+ SetItemAlignment(system_tray()->shelf_alignment());
+ return tray_view_;
+}
+
+views::View* TrayImageItem::CreateDefaultView(user::LoginStatus status) {
+ return NULL;
+}
+
+views::View* TrayImageItem::CreateDetailedView(user::LoginStatus status) {
+ return NULL;
+}
+
+void TrayImageItem::UpdateAfterLoginStatusChange(user::LoginStatus status) {
+}
+
+void TrayImageItem::UpdateAfterShelfAlignmentChange(ShelfAlignment alignment) {
+ SetTrayImageItemBorder(tray_view_, alignment);
+ SetItemAlignment(alignment);
+}
+
+void TrayImageItem::DestroyTrayView() {
+ tray_view_ = NULL;
+}
+
+void TrayImageItem::DestroyDefaultView() {
+}
+
+void TrayImageItem::DestroyDetailedView() {
+}
+
+void TrayImageItem::SetItemAlignment(ShelfAlignment alignment) {
+ // Center the item dependent on the orientation of the shelf.
+ views::BoxLayout::Orientation layout = views::BoxLayout::kHorizontal;
+ switch (alignment) {
+ case ash::SHELF_ALIGNMENT_BOTTOM:
+ case ash::SHELF_ALIGNMENT_TOP:
+ layout = views::BoxLayout::kHorizontal;
+ break;
+ case ash::SHELF_ALIGNMENT_LEFT:
+ case ash::SHELF_ALIGNMENT_RIGHT:
+ layout = views::BoxLayout::kVertical;
+ break;
+ }
+ tray_view_->SetLayoutManager(new views::BoxLayout(layout, 0, 0, 0));
+ tray_view_->Layout();
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/system/tray/tray_image_item.h b/chromium/ash/system/tray/tray_image_item.h
new file mode 100644
index 00000000000..6dea5c8372f
--- /dev/null
+++ b/chromium/ash/system/tray/tray_image_item.h
@@ -0,0 +1,56 @@
+// Copyright (c) 2012 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 ASH_SYSTEM_TRAY_TRAY_IMAGE_ITEM_H_
+#define ASH_SYSTEM_TRAY_TRAY_IMAGE_ITEM_H_
+
+#include "ash/system/tray/system_tray_item.h"
+
+namespace views {
+class ImageView;
+}
+
+namespace ash {
+namespace internal {
+
+class TrayItemView;
+
+class TrayImageItem : public SystemTrayItem {
+ public:
+ TrayImageItem(SystemTray* system_tray, int resource_id);
+ virtual ~TrayImageItem();
+
+ views::View* tray_view();
+
+ // Changes the icon of the tray-view to the specified resource.
+ void SetImageFromResourceId(int resource_id);
+
+ protected:
+ virtual bool GetInitialVisibility() = 0;
+
+ // Overridden from SystemTrayItem.
+ virtual views::View* CreateTrayView(user::LoginStatus status) OVERRIDE;
+ virtual views::View* CreateDefaultView(user::LoginStatus status) OVERRIDE;
+ virtual views::View* CreateDetailedView(user::LoginStatus status) OVERRIDE;
+ virtual void DestroyTrayView() OVERRIDE;
+ virtual void DestroyDefaultView() OVERRIDE;
+ virtual void DestroyDetailedView() OVERRIDE;
+ virtual void UpdateAfterLoginStatusChange(user::LoginStatus status) OVERRIDE;
+ virtual void UpdateAfterShelfAlignmentChange(
+ ShelfAlignment alignment) OVERRIDE;
+
+ private:
+ // Set the alignment of the image depending on the shelf alignment.
+ void SetItemAlignment(ShelfAlignment alignment);
+
+ int resource_id_;
+ TrayItemView* tray_view_;
+
+ DISALLOW_COPY_AND_ASSIGN(TrayImageItem);
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_SYSTEM_TRAY_TRAY_IMAGE_ITEM_H_
diff --git a/chromium/ash/system/tray/tray_item_more.cc b/chromium/ash/system/tray/tray_item_more.cc
new file mode 100644
index 00000000000..9284b47d053
--- /dev/null
+++ b/chromium/ash/system/tray/tray_item_more.cc
@@ -0,0 +1,107 @@
+// Copyright (c) 2012 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.
+
+#include "ash/system/tray/tray_item_more.h"
+
+#include "ash/system/tray/fixed_sized_image_view.h"
+#include "ash/system/tray/system_tray_item.h"
+#include "ash/system/tray/tray_constants.h"
+#include "grit/ash_resources.h"
+#include "ui/base/accessibility/accessible_view_state.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "ui/gfx/image/image.h"
+#include "ui/views/controls/image_view.h"
+#include "ui/views/controls/label.h"
+#include "ui/views/layout/box_layout.h"
+
+namespace ash {
+namespace internal {
+
+TrayItemMore::TrayItemMore(SystemTrayItem* owner, bool show_more)
+ : owner_(owner),
+ show_more_(show_more),
+ icon_(NULL),
+ label_(NULL),
+ more_(NULL) {
+ SetLayoutManager(new views::BoxLayout(views::BoxLayout::kHorizontal,
+ kTrayPopupPaddingHorizontal, 0, kTrayPopupPaddingBetweenItems));
+
+ icon_ = new FixedSizedImageView(0, kTrayPopupItemHeight);
+ AddChildView(icon_);
+
+ label_ = new views::Label;
+ label_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
+ AddChildView(label_);
+
+ if (show_more) {
+ more_ = new views::ImageView;
+ more_->EnableCanvasFlippingForRTLUI(true);
+ more_->SetImage(ui::ResourceBundle::GetSharedInstance().GetImageNamed(
+ IDR_AURA_UBER_TRAY_MORE).ToImageSkia());
+ AddChildView(more_);
+ }
+}
+
+TrayItemMore::~TrayItemMore() {
+}
+
+void TrayItemMore::SetLabel(const base::string16& label) {
+ label_->SetText(label);
+ Layout();
+ SchedulePaint();
+}
+
+void TrayItemMore::SetImage(const gfx::ImageSkia* image_skia) {
+ icon_->SetImage(image_skia);
+ SchedulePaint();
+}
+
+void TrayItemMore::SetAccessibleName(const base::string16& name) {
+ accessible_name_ = name;
+}
+
+void TrayItemMore::ReplaceIcon(views::View* view) {
+ delete icon_;
+ icon_ = NULL;
+ AddChildViewAt(view, 0);
+}
+
+bool TrayItemMore::PerformAction(const ui::Event& event) {
+ if (!show_more_)
+ return false;
+
+ owner()->TransitionDetailedView();
+ return true;
+}
+
+void TrayItemMore::Layout() {
+ // Let the box-layout do the layout first. Then move the '>' arrow to right
+ // align.
+ views::View::Layout();
+
+ if (!show_more_)
+ return;
+
+ // Make sure the chevron always has the full size.
+ gfx::Size size = more_->GetPreferredSize();
+ gfx::Rect bounds(size);
+ bounds.set_x(width() - size.width() - kTrayPopupPaddingBetweenItems);
+ bounds.set_y((height() - size.height()) / 2);
+ more_->SetBoundsRect(bounds);
+
+ // Adjust the label's bounds in case it got cut off by |more_|.
+ if (label_->bounds().Intersects(more_->bounds())) {
+ gfx::Rect bounds = label_->bounds();
+ bounds.set_width(more_->x() - kTrayPopupPaddingBetweenItems - label_->x());
+ label_->SetBoundsRect(bounds);
+ }
+}
+
+void TrayItemMore::GetAccessibleState(ui::AccessibleViewState* state) {
+ state->role = ui::AccessibilityTypes::ROLE_PUSHBUTTON;
+ state->name = accessible_name_;
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/system/tray/tray_item_more.h b/chromium/ash/system/tray/tray_item_more.h
new file mode 100644
index 00000000000..49287240d40
--- /dev/null
+++ b/chromium/ash/system/tray/tray_item_more.h
@@ -0,0 +1,64 @@
+// Copyright (c) 2012 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 ASH_SYSTEM_TRAY_TRAY_ITEM_MORE_H_
+#define ASH_SYSTEM_TRAY_TRAY_ITEM_MORE_H_
+
+#include "ash/system/tray/actionable_view.h"
+#include "ui/views/view.h"
+
+namespace views {
+class ImageView;
+class Label;
+class View;
+}
+
+namespace ash {
+
+class SystemTrayItem;
+
+namespace internal {
+
+// A view with a chevron ('>') on the right edge. Clicking on the view brings up
+// the detailed view of the tray-item that owns it.
+class TrayItemMore : public ActionableView {
+ public:
+ TrayItemMore(SystemTrayItem* owner, bool show_more);
+ virtual ~TrayItemMore();
+
+ SystemTrayItem* owner() const { return owner_; }
+
+ void SetLabel(const base::string16& label);
+ void SetImage(const gfx::ImageSkia* image_skia);
+ void SetAccessibleName(const base::string16& name);
+
+ protected:
+ // Replaces the default icon (on the left of the label), and allows a custom
+ // view to be placed there. Once the default icon is replaced, |SetImage|
+ // should never be called.
+ void ReplaceIcon(views::View* view);
+
+ private:
+ // Overridden from ActionableView.
+ virtual bool PerformAction(const ui::Event& event) OVERRIDE;
+
+ // Overridden from views::View.
+ virtual void Layout() OVERRIDE;
+ virtual void GetAccessibleState(ui::AccessibleViewState* state) OVERRIDE;
+
+ SystemTrayItem* owner_;
+ // True if |more_| should be shown.
+ bool show_more_;
+ views::ImageView* icon_;
+ views::Label* label_;
+ views::ImageView* more_;
+ base::string16 accessible_name_;
+
+ DISALLOW_COPY_AND_ASSIGN(TrayItemMore);
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_SYSTEM_TRAY_TRAY_ITEM_MORE_H_
diff --git a/chromium/ash/system/tray/tray_item_view.cc b/chromium/ash/system/tray/tray_item_view.cc
new file mode 100644
index 00000000000..f95ceb93570
--- /dev/null
+++ b/chromium/ash/system/tray/tray_item_view.cc
@@ -0,0 +1,129 @@
+// Copyright (c) 2012 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.
+
+#include "ash/system/tray/tray_item_view.h"
+
+#include "ash/shelf/shelf_types.h"
+#include "ash/system/tray/system_tray.h"
+#include "ash/system/tray/system_tray_item.h"
+#include "ui/base/animation/slide_animation.h"
+#include "ui/compositor/layer.h"
+#include "ui/views/controls/image_view.h"
+#include "ui/views/controls/label.h"
+#include "ui/views/layout/box_layout.h"
+#include "ui/views/widget/widget.h"
+
+namespace {
+const int kTrayIconHeight = 29;
+const int kTrayIconWidth = 29;
+const int kTrayItemAnimationDurationMS = 200;
+
+// Animations can be disabled for testing.
+bool animations_enabled = true;
+}
+
+namespace ash {
+namespace internal {
+
+TrayItemView::TrayItemView(SystemTrayItem* owner)
+ : owner_(owner),
+ label_(NULL),
+ image_view_(NULL) {
+ SetPaintToLayer(true);
+ SetFillsBoundsOpaquely(false);
+ SetLayoutManager(
+ new views::BoxLayout(views::BoxLayout::kHorizontal, 0, 0, 0));
+}
+
+TrayItemView::~TrayItemView() {}
+
+// static
+void TrayItemView::DisableAnimationsForTest() {
+ animations_enabled = false;
+}
+
+void TrayItemView::CreateLabel() {
+ label_ = new views::Label;
+ AddChildView(label_);
+}
+
+void TrayItemView::CreateImageView() {
+ image_view_ = new views::ImageView;
+ AddChildView(image_view_);
+}
+
+void TrayItemView::SetVisible(bool set_visible) {
+ if (!GetWidget() || !animations_enabled) {
+ views::View::SetVisible(set_visible);
+ return;
+ }
+
+ if (!animation_) {
+ animation_.reset(new ui::SlideAnimation(this));
+ animation_->SetSlideDuration(GetAnimationDurationMS());
+ animation_->SetTweenType(ui::Tween::LINEAR);
+ animation_->Reset(visible() ? 1.0 : 0.0);
+ }
+
+ if (!set_visible) {
+ animation_->Hide();
+ AnimationProgressed(animation_.get());
+ } else {
+ animation_->Show();
+ AnimationProgressed(animation_.get());
+ views::View::SetVisible(true);
+ }
+}
+
+gfx::Size TrayItemView::DesiredSize() {
+ return views::View::GetPreferredSize();
+}
+
+int TrayItemView::GetAnimationDurationMS() {
+ return kTrayItemAnimationDurationMS;
+}
+
+gfx::Size TrayItemView::GetPreferredSize() {
+ gfx::Size size = DesiredSize();
+ if (owner()->system_tray()->shelf_alignment() == SHELF_ALIGNMENT_BOTTOM ||
+ owner()->system_tray()->shelf_alignment() == SHELF_ALIGNMENT_TOP)
+ size.set_height(kTrayIconHeight);
+ else
+ size.set_width(kTrayIconWidth);
+ if (!animation_.get() || !animation_->is_animating())
+ return size;
+ size.set_width(std::max(1,
+ static_cast<int>(size.width() * animation_->GetCurrentValue())));
+ return size;
+}
+
+int TrayItemView::GetHeightForWidth(int width) {
+ return GetPreferredSize().height();
+}
+
+void TrayItemView::ChildPreferredSizeChanged(views::View* child) {
+ PreferredSizeChanged();
+}
+
+void TrayItemView::AnimationProgressed(const ui::Animation* animation) {
+ gfx::Transform transform;
+ transform.Translate(0, animation->CurrentValueBetween(
+ static_cast<double>(height()) / 2, 0.));
+ transform.Scale(animation->GetCurrentValue(),
+ animation->GetCurrentValue());
+ layer()->SetTransform(transform);
+ PreferredSizeChanged();
+}
+
+void TrayItemView::AnimationEnded(const ui::Animation* animation) {
+ if (animation->GetCurrentValue() < 0.1)
+ views::View::SetVisible(false);
+}
+
+void TrayItemView::AnimationCanceled(const ui::Animation* animation) {
+ AnimationEnded(animation);
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/system/tray/tray_item_view.h b/chromium/ash/system/tray/tray_item_view.h
new file mode 100644
index 00000000000..e0d8ec4d5af
--- /dev/null
+++ b/chromium/ash/system/tray/tray_item_view.h
@@ -0,0 +1,85 @@
+// Copyright (c) 2012 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 ASH_SYSTEM_TRAY_TRAY_ITEM_VIEW_H_
+#define ASH_SYSTEM_TRAY_TRAY_ITEM_VIEW_H_
+
+#include "ash/ash_export.h"
+#include "ui/base/animation/animation_delegate.h"
+#include "ui/views/view.h"
+
+namespace ui {
+class SlideAnimation;
+}
+
+namespace views {
+class ImageView;
+class Label;
+}
+
+namespace ash {
+
+class SystemTrayItem;
+
+namespace internal {
+
+// Base-class for items in the tray. It makes sure the widget is updated
+// correctly when the visibility/size of the tray item changes. It also adds
+// animation when showing/hiding the item in the tray.
+class ASH_EXPORT TrayItemView : public views::View,
+ public ui::AnimationDelegate {
+ public:
+ explicit TrayItemView(SystemTrayItem* owner);
+ virtual ~TrayItemView();
+
+ static void DisableAnimationsForTest();
+
+ // Convenience function for creating a child Label or ImageView.
+ void CreateLabel();
+ void CreateImageView();
+
+ SystemTrayItem* owner() const { return owner_; }
+ views::Label* label() const { return label_; }
+ views::ImageView* image_view() const { return image_view_; }
+
+ // Overridden from views::View.
+ virtual void SetVisible(bool visible) OVERRIDE;
+ virtual gfx::Size GetPreferredSize() OVERRIDE;
+ virtual int GetHeightForWidth(int width) OVERRIDE;
+
+ protected:
+ // Makes sure the widget relayouts after the size/visibility of the view
+ // changes.
+ void ApplyChange();
+
+ // This should return the desired size of the view. For most views, this
+ // returns GetPreferredSize. But since this class overrides GetPreferredSize
+ // for animation purposes, we allow a different way to get this size, and do
+ // not allow GetPreferredSize to be overridden.
+ virtual gfx::Size DesiredSize();
+
+ // The default animation duration is 200ms. But each view can customize this.
+ virtual int GetAnimationDurationMS();
+
+ private:
+ // Overridden from views::View.
+ virtual void ChildPreferredSizeChanged(View* child) OVERRIDE;
+
+ // Overridden from ui::AnimationDelegate.
+ virtual void AnimationProgressed(const ui::Animation* animation) OVERRIDE;
+ virtual void AnimationEnded(const ui::Animation* animation) OVERRIDE;
+ virtual void AnimationCanceled(const ui::Animation* animation) OVERRIDE;
+
+ SystemTrayItem* owner_;
+ scoped_ptr<ui::SlideAnimation> animation_;
+ views::Label* label_;
+ views::ImageView* image_view_;
+
+ DISALLOW_COPY_AND_ASSIGN(TrayItemView);
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_SYSTEM_TRAY_TRAY_ITEM_VIEW_H_
diff --git a/chromium/ash/system/tray/tray_notification_view.cc b/chromium/ash/system/tray/tray_notification_view.cc
new file mode 100644
index 00000000000..aa4b1d1a8ac
--- /dev/null
+++ b/chromium/ash/system/tray/tray_notification_view.cc
@@ -0,0 +1,171 @@
+// Copyright (c) 2012 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.
+
+#include "ash/system/tray/tray_notification_view.h"
+
+#include "ash/system/tray/system_tray_item.h"
+#include "ash/system/tray/tray_constants.h"
+#include "grit/ash_strings.h"
+#include "grit/ui_resources.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "ui/gfx/image/image_skia.h"
+#include "ui/views/controls/button/image_button.h"
+#include "ui/views/controls/image_view.h"
+#include "ui/views/layout/grid_layout.h"
+
+namespace ash {
+namespace internal {
+
+TrayNotificationView::TrayNotificationView(SystemTrayItem* owner, int icon_id)
+ : owner_(owner),
+ icon_id_(icon_id),
+ icon_(NULL) {
+}
+
+TrayNotificationView::~TrayNotificationView() {
+}
+
+void TrayNotificationView::InitView(views::View* contents) {
+ set_background(views::Background::CreateSolidBackground(kBackgroundColor));
+
+ views::GridLayout* layout = new views::GridLayout(this);
+ SetLayoutManager(layout);
+
+ views::ImageButton* close_button = new views::ImageButton(this);
+ close_button->SetImage(views::CustomButton::STATE_NORMAL,
+ ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
+ IDR_MESSAGE_CLOSE));
+ close_button->SetImageAlignment(views::ImageButton::ALIGN_CENTER,
+ views::ImageButton::ALIGN_MIDDLE);
+
+ icon_ = new views::ImageView;
+ if (icon_id_ != 0) {
+ icon_->SetImage(
+ ResourceBundle::GetSharedInstance().GetImageSkiaNamed(icon_id_));
+ }
+
+ views::ColumnSet* columns = layout->AddColumnSet(0);
+
+ columns->AddPaddingColumn(0, kTrayPopupPaddingHorizontal / 2);
+
+ // Icon
+ columns->AddColumn(views::GridLayout::CENTER, views::GridLayout::CENTER,
+ 0, /* resize percent */
+ views::GridLayout::FIXED,
+ kNotificationIconWidth, kNotificationIconWidth);
+
+ columns->AddPaddingColumn(0, kTrayPopupPaddingHorizontal / 2);
+
+ // Contents
+ columns->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL,
+ 100, /* resize percent */
+ views::GridLayout::FIXED,
+ kTrayNotificationContentsWidth,
+ kTrayNotificationContentsWidth);
+
+ columns->AddPaddingColumn(0, kTrayPopupPaddingHorizontal / 2);
+
+ // Close button
+ columns->AddColumn(views::GridLayout::CENTER, views::GridLayout::LEADING,
+ 0, /* resize percent */
+ views::GridLayout::FIXED,
+ kNotificationButtonWidth, kNotificationButtonWidth);
+
+ // Layout rows
+ layout->AddPaddingRow(0, kTrayPopupPaddingBetweenItems);
+ layout->StartRow(0, 0);
+ layout->AddView(icon_);
+ layout->AddView(contents);
+ layout->AddView(close_button);
+ layout->AddPaddingRow(0, kTrayPopupPaddingBetweenItems);
+}
+
+void TrayNotificationView::SetIconImage(const gfx::ImageSkia& image) {
+ icon_->SetImage(image);
+ SchedulePaint();
+}
+
+const gfx::ImageSkia& TrayNotificationView::GetIconImage() const {
+ return icon_->GetImage();
+}
+
+void TrayNotificationView::UpdateView(views::View* new_contents) {
+ RemoveAllChildViews(true);
+ InitView(new_contents);
+ Layout();
+ PreferredSizeChanged();
+ SchedulePaint();
+}
+
+void TrayNotificationView::UpdateViewAndImage(views::View* new_contents,
+ const gfx::ImageSkia& image) {
+ RemoveAllChildViews(true);
+ InitView(new_contents);
+ icon_->SetImage(image);
+ Layout();
+ PreferredSizeChanged();
+ SchedulePaint();
+}
+
+void TrayNotificationView::StartAutoCloseTimer(int seconds) {
+ autoclose_.Stop();
+ autoclose_delay_ = seconds;
+ if (autoclose_delay_) {
+ autoclose_.Start(FROM_HERE,
+ base::TimeDelta::FromSeconds(autoclose_delay_),
+ this, &TrayNotificationView::HandleClose);
+ }
+}
+
+void TrayNotificationView::StopAutoCloseTimer() {
+ autoclose_.Stop();
+}
+
+void TrayNotificationView::RestartAutoCloseTimer() {
+ if (autoclose_delay_)
+ StartAutoCloseTimer(autoclose_delay_);
+}
+
+void TrayNotificationView::ButtonPressed(views::Button* sender,
+ const ui::Event& event) {
+ HandleClose();
+}
+
+bool TrayNotificationView::OnMousePressed(const ui::MouseEvent& event) {
+ HandleClickAction();
+ return true;
+}
+
+void TrayNotificationView::OnGestureEvent(ui::GestureEvent* event) {
+ SlideOutView::OnGestureEvent(event);
+ if (event->handled())
+ return;
+ if (event->type() != ui::ET_GESTURE_TAP)
+ return;
+ HandleClickAction();
+ event->SetHandled();
+}
+
+void TrayNotificationView::OnClose() {
+}
+
+void TrayNotificationView::OnClickAction() {
+}
+
+void TrayNotificationView::OnSlideOut() {
+ owner_->HideNotificationView();
+}
+
+void TrayNotificationView::HandleClose() {
+ OnClose();
+ owner_->HideNotificationView();
+}
+
+void TrayNotificationView::HandleClickAction() {
+ HandleClose();
+ OnClickAction();
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/system/tray/tray_notification_view.h b/chromium/ash/system/tray/tray_notification_view.h
new file mode 100644
index 00000000000..314b8aad7d2
--- /dev/null
+++ b/chromium/ash/system/tray/tray_notification_view.h
@@ -0,0 +1,98 @@
+// Copyright (c) 2012 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 ASH_SYSTEM_TRAY_TRAY_NOTIFICATION_VIEW_H_
+#define ASH_SYSTEM_TRAY_TRAY_NOTIFICATION_VIEW_H_
+
+#include "base/timer/timer.h"
+#include "ui/views/controls/button/image_button.h"
+#include "ui/views/controls/slide_out_view.h"
+
+namespace gfx {
+class ImageSkia;
+}
+
+namespace views {
+class ImageView;
+}
+
+namespace ash {
+
+class SystemTrayItem;
+
+namespace internal {
+
+// A view for closable notification views, laid out like:
+// -------------------
+// | icon contents x |
+// ----------------v--
+// The close button will call OnClose() when clicked.
+class TrayNotificationView : public views::SlideOutView,
+ public views::ButtonListener {
+ public:
+ // If icon_id is 0, no icon image will be set. SetIconImage can be called
+ // to later set the icon image.
+ TrayNotificationView(SystemTrayItem* owner, int icon_id);
+ virtual ~TrayNotificationView();
+
+ // InitView must be called once with the contents to be displayed.
+ void InitView(views::View* contents);
+
+ // Sets/updates the icon image.
+ void SetIconImage(const gfx::ImageSkia& image);
+
+ // Gets the icons image.
+ const gfx::ImageSkia& GetIconImage() const;
+
+ // Replaces the contents view.
+ void UpdateView(views::View* new_contents);
+
+ // Replaces the contents view and updates the icon image.
+ void UpdateViewAndImage(views::View* new_contents,
+ const gfx::ImageSkia& image);
+
+ // Autoclose timer operations.
+ void StartAutoCloseTimer(int seconds);
+ void StopAutoCloseTimer();
+ void RestartAutoCloseTimer();
+
+ // Overridden from ButtonListener.
+ virtual void ButtonPressed(views::Button* sender,
+ const ui::Event& event) OVERRIDE;
+
+ // Overridden from views::View.
+ virtual bool OnMousePressed(const ui::MouseEvent& event) OVERRIDE;
+
+ // Overridden from ui::EventHandler.
+ virtual void OnGestureEvent(ui::GestureEvent* event) OVERRIDE;
+
+ protected:
+ // Called when the close button is pressed. Does nothing by default.
+ virtual void OnClose();
+ // Called when the notification is clicked on. Does nothing by default.
+ virtual void OnClickAction();
+
+ // Overridden from views::SlideOutView.
+ virtual void OnSlideOut() OVERRIDE;
+
+ SystemTrayItem* owner() { return owner_; }
+
+ private:
+ void HandleClose();
+ void HandleClickAction();
+
+ SystemTrayItem* owner_;
+ int icon_id_;
+ views::ImageView* icon_;
+
+ int autoclose_delay_;
+ base::OneShotTimer<TrayNotificationView> autoclose_;
+
+ DISALLOW_COPY_AND_ASSIGN(TrayNotificationView);
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_SYSTEM_TRAY_TRAY_NOTIFICATION_VIEW_H_
diff --git a/chromium/ash/system/tray/tray_popup_header_button.cc b/chromium/ash/system/tray/tray_popup_header_button.cc
new file mode 100644
index 00000000000..7e3ed448585
--- /dev/null
+++ b/chromium/ash/system/tray/tray_popup_header_button.cc
@@ -0,0 +1,72 @@
+// Copyright 2013 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.
+
+#include "ash/system/tray/tray_popup_header_button.h"
+
+#include "ash/ash_constants.h"
+#include "ash/system/tray/tray_constants.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "ui/gfx/canvas.h"
+
+namespace ash {
+namespace internal {
+
+// static
+const char TrayPopupHeaderButton::kViewClassName[] =
+ "tray/TrayPopupHeaderButton";
+
+TrayPopupHeaderButton::TrayPopupHeaderButton(views::ButtonListener* listener,
+ int enabled_resource_id,
+ int disabled_resource_id,
+ int enabled_resource_id_hover,
+ int disabled_resource_id_hover,
+ int accessible_name_id)
+ : views::ToggleImageButton(listener) {
+ ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance();
+ SetImage(views::Button::STATE_NORMAL,
+ bundle.GetImageNamed(enabled_resource_id).ToImageSkia());
+ SetToggledImage(views::Button::STATE_NORMAL,
+ bundle.GetImageNamed(disabled_resource_id).ToImageSkia());
+ SetImage(views::Button::STATE_HOVERED,
+ bundle.GetImageNamed(enabled_resource_id_hover).ToImageSkia());
+ SetToggledImage(views::Button::STATE_HOVERED,
+ bundle.GetImageNamed(disabled_resource_id_hover).ToImageSkia());
+ SetImageAlignment(views::ImageButton::ALIGN_CENTER,
+ views::ImageButton::ALIGN_MIDDLE);
+ SetAccessibleName(bundle.GetLocalizedString(accessible_name_id));
+ set_focusable(true);
+ set_request_focus_on_press(false);
+}
+
+TrayPopupHeaderButton::~TrayPopupHeaderButton() {}
+
+const char* TrayPopupHeaderButton::GetClassName() const {
+ return kViewClassName;
+}
+
+gfx::Size TrayPopupHeaderButton::GetPreferredSize() {
+ return gfx::Size(ash::kTrayPopupItemHeight, ash::kTrayPopupItemHeight);
+}
+
+void TrayPopupHeaderButton::OnPaintBorder(gfx::Canvas* canvas) {
+ // Just the left border.
+ const int kBorderHeight = 25;
+ int padding = (height() - kBorderHeight) / 2;
+ canvas->FillRect(gfx::Rect(0, padding, 1, height() - padding * 2),
+ ash::kBorderDarkColor);
+}
+
+void TrayPopupHeaderButton::OnPaintFocusBorder(gfx::Canvas* canvas) {
+ if (HasFocus() && (focusable() || IsAccessibilityFocusable())) {
+ canvas->DrawRect(gfx::Rect(2, 1, width() - 4, height() - 3),
+ kFocusBorderColor);
+ }
+}
+
+void TrayPopupHeaderButton::StateChanged() {
+ SchedulePaint();
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/system/tray/tray_popup_header_button.h b/chromium/ash/system/tray/tray_popup_header_button.h
new file mode 100644
index 00000000000..f9209c86c77
--- /dev/null
+++ b/chromium/ash/system/tray/tray_popup_header_button.h
@@ -0,0 +1,46 @@
+// Copyright 2013 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 ASH_SYSTEM_TRAY_TRAY_POPUP_HEADER_BUTTON_H_
+#define ASH_SYSTEM_TRAY_TRAY_POPUP_HEADER_BUTTON_H_
+
+#include "ash/ash_export.h"
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "ui/views/controls/button/image_button.h"
+
+namespace ash {
+namespace internal {
+
+// A ToggleImageButton with fixed size, paddings and hover effects. These
+// buttons are used in the header.
+class ASH_EXPORT TrayPopupHeaderButton : public views::ToggleImageButton {
+ public:
+ static const char kViewClassName[];
+
+ TrayPopupHeaderButton(views::ButtonListener* listener,
+ int enabled_resource_id,
+ int disabled_resource_id,
+ int enabled_resource_id_hover,
+ int disabled_resource_id_hover,
+ int accessible_name_id);
+ virtual ~TrayPopupHeaderButton();
+
+ private:
+ // Overridden from views::View:
+ virtual const char* GetClassName() const OVERRIDE;
+ virtual gfx::Size GetPreferredSize() OVERRIDE;
+ virtual void OnPaintBorder(gfx::Canvas* canvas) OVERRIDE;
+ virtual void OnPaintFocusBorder(gfx::Canvas* canvas) OVERRIDE;
+
+ // Overridden from views::CustomButton:
+ virtual void StateChanged() OVERRIDE;
+
+ DISALLOW_COPY_AND_ASSIGN(TrayPopupHeaderButton);
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_SYSTEM_TRAY_TRAY_POPUP_HEADER_BUTTON_H_
diff --git a/chromium/ash/system/tray/tray_popup_label_button.cc b/chromium/ash/system/tray/tray_popup_label_button.cc
new file mode 100644
index 00000000000..c8e7a6a3ad4
--- /dev/null
+++ b/chromium/ash/system/tray/tray_popup_label_button.cc
@@ -0,0 +1,35 @@
+// Copyright 2013 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.
+
+#include "ash/system/tray/tray_popup_label_button.h"
+
+#include "ash/ash_constants.h"
+#include "ash/system/tray/tray_popup_label_button_border.h"
+#include "ui/gfx/canvas.h"
+#include "ui/gfx/rect.h"
+
+namespace ash {
+namespace internal {
+
+TrayPopupLabelButton::TrayPopupLabelButton(views::ButtonListener* listener,
+ const base::string16& text)
+ : views::LabelButton(listener, text) {
+ set_border(new TrayPopupLabelButtonBorder);
+ set_focusable(true);
+ set_request_focus_on_press(false);
+ set_animate_on_state_change(false);
+ SetHorizontalAlignment(gfx::ALIGN_CENTER);
+}
+
+TrayPopupLabelButton::~TrayPopupLabelButton() {}
+
+void TrayPopupLabelButton::OnPaintFocusBorder(gfx::Canvas* canvas) {
+ if (HasFocus() && (focusable() || IsAccessibilityFocusable())) {
+ canvas->DrawRect(gfx::Rect(1, 1, width() - 3, height() - 3),
+ ash::kFocusBorderColor);
+ }
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/system/tray/tray_popup_label_button.h b/chromium/ash/system/tray/tray_popup_label_button.h
new file mode 100644
index 00000000000..8d21abd98af
--- /dev/null
+++ b/chromium/ash/system/tray/tray_popup_label_button.h
@@ -0,0 +1,33 @@
+// Copyright 2013 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 ASH_SYSTEM_TRAY_TRAY_POPUP_LABEL_BUTTON_H_
+#define ASH_SYSTEM_TRAY_TRAY_POPUP_LABEL_BUTTON_H_
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/strings/string16.h"
+#include "ui/views/controls/button/label_button.h"
+
+namespace ash {
+namespace internal {
+
+// A label button with custom alignment, border and focus border.
+class TrayPopupLabelButton : public views::LabelButton {
+ public:
+ TrayPopupLabelButton(views::ButtonListener* listener,
+ const base::string16& text);
+ virtual ~TrayPopupLabelButton();
+
+ private:
+ // Overridden from views::LabelButton:
+ virtual void OnPaintFocusBorder(gfx::Canvas* canvas) OVERRIDE;
+
+ DISALLOW_COPY_AND_ASSIGN(TrayPopupLabelButton);
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_SYSTEM_TRAY_TRAY_POPUP_LABEL_BUTTON_H_
diff --git a/chromium/ash/system/tray/tray_popup_label_button_border.cc b/chromium/ash/system/tray/tray_popup_label_button_border.cc
new file mode 100644
index 00000000000..275457dc256
--- /dev/null
+++ b/chromium/ash/system/tray/tray_popup_label_button_border.cc
@@ -0,0 +1,109 @@
+// Copyright 2013 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.
+
+#include "ash/system/tray/tray_popup_label_button_border.h"
+
+#include "base/i18n/rtl.h"
+#include "grit/ash_resources.h"
+#include "ui/gfx/canvas.h"
+#include "ui/gfx/vector2d.h"
+#include "ui/native_theme/native_theme.h"
+#include "ui/views/controls/button/label_button.h"
+#include "ui/views/native_theme_delegate.h"
+
+namespace ash {
+namespace internal {
+
+namespace {
+
+const int kTrayPopupLabelButtonPaddingHorizontal = 16;
+const int kTrayPopupLabelButtonPaddingVertical = 8;
+
+const int kTrayPopupLabelButtonBorderImagesNormal[] = {
+ IDR_AURA_TRAY_POPUP_LABEL_BUTTON_BORDER,
+ IDR_AURA_TRAY_POPUP_LABEL_BUTTON_NORMAL_BACKGROUND,
+ IDR_AURA_TRAY_POPUP_LABEL_BUTTON_NORMAL_BACKGROUND,
+ IDR_AURA_TRAY_POPUP_LABEL_BUTTON_BORDER,
+ IDR_AURA_TRAY_POPUP_LABEL_BUTTON_NORMAL_BACKGROUND,
+ IDR_AURA_TRAY_POPUP_LABEL_BUTTON_NORMAL_BACKGROUND,
+ IDR_AURA_TRAY_POPUP_LABEL_BUTTON_BORDER,
+ IDR_AURA_TRAY_POPUP_LABEL_BUTTON_NORMAL_BACKGROUND,
+ IDR_AURA_TRAY_POPUP_LABEL_BUTTON_NORMAL_BACKGROUND,
+};
+
+const int kTrayPopupLabelButtonBorderImagesHovered[] = {
+ IDR_AURA_TRAY_POPUP_LABEL_BUTTON_BORDER,
+ IDR_AURA_TRAY_POPUP_LABEL_BUTTON_BORDER,
+ IDR_AURA_TRAY_POPUP_LABEL_BUTTON_BORDER,
+ IDR_AURA_TRAY_POPUP_LABEL_BUTTON_BORDER,
+ IDR_AURA_TRAY_POPUP_LABEL_BUTTON_HOVER_BACKGROUND,
+ IDR_AURA_TRAY_POPUP_LABEL_BUTTON_BORDER,
+ IDR_AURA_TRAY_POPUP_LABEL_BUTTON_BORDER,
+ IDR_AURA_TRAY_POPUP_LABEL_BUTTON_BORDER,
+ IDR_AURA_TRAY_POPUP_LABEL_BUTTON_BORDER,
+};
+
+} // namespace
+
+TrayPopupLabelButtonBorder::TrayPopupLabelButtonBorder()
+ : LabelButtonBorder(views::Button::STYLE_TEXTBUTTON) {
+ SetPainter(false, views::Button::STATE_NORMAL,
+ views::Painter::CreateImageGridPainter(
+ kTrayPopupLabelButtonBorderImagesNormal));
+ SetPainter(false, views::Button::STATE_DISABLED,
+ views::Painter::CreateImageGridPainter(
+ kTrayPopupLabelButtonBorderImagesNormal));
+ SetPainter(false, views::Button::STATE_HOVERED,
+ views::Painter::CreateImageGridPainter(
+ kTrayPopupLabelButtonBorderImagesHovered));
+ SetPainter(false, views::Button::STATE_PRESSED,
+ views::Painter::CreateImageGridPainter(
+ kTrayPopupLabelButtonBorderImagesHovered));
+}
+
+TrayPopupLabelButtonBorder::~TrayPopupLabelButtonBorder() {}
+
+void TrayPopupLabelButtonBorder::Paint(const views::View& view,
+ gfx::Canvas* canvas) {
+ const views::NativeThemeDelegate* native_theme_delegate =
+ static_cast<const views::LabelButton*>(&view);
+ ui::NativeTheme::ExtraParams extra;
+ const ui::NativeTheme::State state =
+ native_theme_delegate->GetThemeState(&extra);
+ if (state == ui::NativeTheme::kNormal ||
+ state == ui::NativeTheme::kDisabled) {
+ // In normal and disabled state, the border is a vertical bar separating the
+ // button from the preceding sibling. If this button is its parent's first
+ // visible child, the separator bar should be omitted.
+ const views::View* first_visible_child = NULL;
+ for (int i = 0; i < view.parent()->child_count(); ++i) {
+ const views::View* child = view.parent()->child_at(i);
+ if (child->visible()) {
+ first_visible_child = child;
+ break;
+ }
+ }
+ if (first_visible_child == &view)
+ return;
+ }
+ if (base::i18n::IsRTL()) {
+ canvas->Save();
+ canvas->Translate(gfx::Vector2d(view.width(), 0));
+ canvas->Scale(-1, 1);
+ LabelButtonBorder::Paint(view, canvas);
+ canvas->Restore();
+ } else {
+ LabelButtonBorder::Paint(view, canvas);
+ }
+}
+
+gfx::Insets TrayPopupLabelButtonBorder::GetInsets() const {
+ return gfx::Insets(kTrayPopupLabelButtonPaddingVertical,
+ kTrayPopupLabelButtonPaddingHorizontal,
+ kTrayPopupLabelButtonPaddingVertical,
+ kTrayPopupLabelButtonPaddingHorizontal);
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/system/tray/tray_popup_label_button_border.h b/chromium/ash/system/tray/tray_popup_label_button_border.h
new file mode 100644
index 00000000000..abfe7936639
--- /dev/null
+++ b/chromium/ash/system/tray/tray_popup_label_button_border.h
@@ -0,0 +1,33 @@
+// Copyright 2013 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 ASH_SYSTEM_TRAY_TRAY_POPUP_LABEL_BUTTON_BORDER_H_
+#define ASH_SYSTEM_TRAY_TRAY_POPUP_LABEL_BUTTON_BORDER_H_
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "ui/views/controls/button/label_button_border.h"
+
+namespace ash {
+namespace internal {
+
+// A border for label buttons that paints a vertical separator in normal state
+// and a custom hover effect in hovered or pressed state.
+class TrayPopupLabelButtonBorder : public views::LabelButtonBorder {
+ public:
+ TrayPopupLabelButtonBorder();
+ virtual ~TrayPopupLabelButtonBorder();
+
+ // Overridden from views::LabelButtonBorder.
+ virtual void Paint(const views::View& view, gfx::Canvas* canvas) OVERRIDE;
+ virtual gfx::Insets GetInsets() const OVERRIDE;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(TrayPopupLabelButtonBorder);
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_SYSTEM_TRAY_TRAY_POPUP_LABEL_BUTTON_BORDER_H_
diff --git a/chromium/ash/system/tray/tray_utils.cc b/chromium/ash/system/tray/tray_utils.cc
new file mode 100644
index 00000000000..5f29a2e8f71
--- /dev/null
+++ b/chromium/ash/system/tray/tray_utils.cc
@@ -0,0 +1,65 @@
+// Copyright (c) 2012 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.
+
+#include "ash/system/tray/tray_utils.h"
+
+#include "ash/system/tray/tray_constants.h"
+#include "ash/system/tray/tray_item_view.h"
+#include "ui/gfx/font.h"
+#include "ui/views/border.h"
+#include "ui/views/controls/label.h"
+
+namespace ash {
+namespace internal {
+
+void SetupLabelForTray(views::Label* label) {
+ // Making label_font static to avoid the time penalty of DeriveFont for
+ // all but the first call.
+ static const gfx::Font label_font(gfx::Font().DeriveFont(1, gfx::Font::BOLD));
+ label->SetFont(label_font);
+ label->SetAutoColorReadabilityEnabled(false);
+ label->SetEnabledColor(SK_ColorWHITE);
+ label->SetBackgroundColor(SkColorSetARGB(0, 255, 255, 255));
+ label->SetShadowColors(SkColorSetARGB(64, 0, 0, 0),
+ SkColorSetARGB(64, 0, 0, 0));
+ label->SetShadowOffset(0, 1);
+}
+
+void SetTrayImageItemBorder(views::View* tray_view,
+ ShelfAlignment alignment) {
+ if (alignment == SHELF_ALIGNMENT_BOTTOM ||
+ alignment == SHELF_ALIGNMENT_TOP) {
+ tray_view->set_border(views::Border::CreateEmptyBorder(
+ 0, kTrayImageItemHorizontalPaddingBottomAlignment,
+ 0, kTrayImageItemHorizontalPaddingBottomAlignment));
+ } else {
+ tray_view->set_border(views::Border::CreateEmptyBorder(
+ kTrayImageItemVerticalPaddingVerticalAlignment,
+ kTrayImageItemHorizontalPaddingVerticalAlignment,
+ kTrayImageItemVerticalPaddingVerticalAlignment,
+ kTrayImageItemHorizontalPaddingVerticalAlignment));
+ }
+}
+
+void SetTrayLabelItemBorder(TrayItemView* tray_view,
+ ShelfAlignment alignment) {
+ if (alignment == SHELF_ALIGNMENT_BOTTOM ||
+ alignment == SHELF_ALIGNMENT_TOP) {
+ tray_view->set_border(views::Border::CreateEmptyBorder(
+ 0, kTrayLabelItemHorizontalPaddingBottomAlignment,
+ 0, kTrayLabelItemHorizontalPaddingBottomAlignment));
+ } else {
+ // Center the label for vertical launcher alignment.
+ int horizontal_padding = (tray_view->GetPreferredSize().width() -
+ tray_view->label()->GetPreferredSize().width()) / 2;
+ tray_view->set_border(views::Border::CreateEmptyBorder(
+ kTrayLabelItemVerticalPaddingVeriticalAlignment,
+ horizontal_padding,
+ kTrayLabelItemVerticalPaddingVeriticalAlignment,
+ horizontal_padding));
+ }
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/system/tray/tray_utils.h b/chromium/ash/system/tray/tray_utils.h
new file mode 100644
index 00000000000..412fb97d8da
--- /dev/null
+++ b/chromium/ash/system/tray/tray_utils.h
@@ -0,0 +1,35 @@
+// Copyright (c) 2012 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 ASH_SYSTEM_TRAY_TRAY_UTILS_H_
+#define ASH_SYSTEM_TRAY_TRAY_UTILS_H_
+
+#include "ash/shelf/shelf_types.h"
+
+namespace views {
+class Label;
+class View;
+}
+
+namespace ash {
+namespace internal {
+
+class TrayItemView;
+
+// Sets up a Label properly for the tray (sets color, font etc.).
+void SetupLabelForTray(views::Label* label);
+
+// TODO(jennyz): refactor these two functions to SystemTrayItem.
+// Sets the empty border of an image tray item for adjusting the space
+// around it.
+void SetTrayImageItemBorder(views::View* tray_view, ShelfAlignment alignment);
+// Sets the empty border around a label tray item for adjusting the space
+// around it.
+void SetTrayLabelItemBorder(TrayItemView* tray_view,
+ ShelfAlignment alignment);
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_SYSTEM_TRAY_TRAY_UTILS_H_
diff --git a/chromium/ash/system/tray/view_click_listener.h b/chromium/ash/system/tray/view_click_listener.h
new file mode 100644
index 00000000000..b5f4bb886f0
--- /dev/null
+++ b/chromium/ash/system/tray/view_click_listener.h
@@ -0,0 +1,26 @@
+// Copyright 2013 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 ASH_SYSTEM_TRAY_VIEW_CLICK_LISTENER_H_
+#define ASH_SYSTEM_TRAY_VIEW_CLICK_LISTENER_H_
+
+namespace views {
+class View;
+}
+
+namespace ash {
+namespace internal {
+
+class ViewClickListener {
+ public:
+ virtual void OnViewClicked(views::View* sender) = 0;
+
+ protected:
+ virtual ~ViewClickListener() {}
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_SYSTEM_TRAY_VIEW_CLICK_LISTENER_H_
diff --git a/chromium/ash/system/tray_accessibility.cc b/chromium/ash/system/tray_accessibility.cc
new file mode 100644
index 00000000000..933a5675544
--- /dev/null
+++ b/chromium/ash/system/tray_accessibility.cc
@@ -0,0 +1,355 @@
+// Copyright (c) 2012 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.
+
+#include "ash/system/tray_accessibility.h"
+
+#include "ash/shell.h"
+#include "ash/shell_delegate.h"
+#include "ash/system/tray/hover_highlight_view.h"
+#include "ash/system/tray/system_tray.h"
+#include "ash/system/tray/system_tray_delegate.h"
+#include "ash/system/tray/system_tray_notifier.h"
+#include "ash/system/tray/tray_constants.h"
+#include "ash/system/tray/tray_details_view.h"
+#include "ash/system/tray/tray_item_more.h"
+#include "ash/system/tray/tray_notification_view.h"
+#include "ash/system/tray/tray_popup_label_button.h"
+#include "grit/ash_resources.h"
+#include "grit/ash_strings.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "ui/gfx/image/image.h"
+#include "ui/views/controls/image_view.h"
+#include "ui/views/controls/label.h"
+#include "ui/views/layout/box_layout.h"
+#include "ui/views/widget/widget.h"
+
+namespace ash {
+namespace internal {
+
+namespace {
+const int kPaddingAroundBottomRow = 5;
+
+enum AccessibilityState {
+ A11Y_NONE = 0,
+ A11Y_SPOKEN_FEEDBACK = 1 << 0,
+ A11Y_HIGH_CONTRAST = 1 << 1,
+ A11Y_SCREEN_MAGNIFIER = 1 << 2,
+ A11Y_LARGE_CURSOR = 1 << 3,
+};
+
+uint32 GetAccessibilityState() {
+ ShellDelegate* shell_delegate = Shell::GetInstance()->delegate();
+ uint32 state = A11Y_NONE;
+ if (shell_delegate->IsSpokenFeedbackEnabled())
+ state |= A11Y_SPOKEN_FEEDBACK;
+ if (shell_delegate->IsHighContrastEnabled())
+ state |= A11Y_HIGH_CONTRAST;
+ if (shell_delegate->IsMagnifierEnabled())
+ state |= A11Y_SCREEN_MAGNIFIER;
+ if (shell_delegate->IsLargeCursorEnabled())
+ state |= A11Y_LARGE_CURSOR;
+ return state;
+}
+
+user::LoginStatus GetCurrentLoginStatus() {
+ return Shell::GetInstance()->system_tray_delegate()->GetUserLoginStatus();
+}
+
+} // namespace
+
+namespace tray {
+
+class DefaultAccessibilityView : public TrayItemMore {
+ public:
+ explicit DefaultAccessibilityView(SystemTrayItem* owner)
+ : TrayItemMore(owner, true) {
+ ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance();
+ SetImage(bundle.GetImageNamed(IDR_AURA_UBER_TRAY_ACCESSIBILITY_DARK).
+ ToImageSkia());
+ base::string16 label = bundle.GetLocalizedString(
+ IDS_ASH_STATUS_TRAY_ACCESSIBILITY);
+ SetLabel(label);
+ SetAccessibleName(label);
+ }
+
+ virtual ~DefaultAccessibilityView() {
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(DefaultAccessibilityView);
+};
+
+class AccessibilityPopupView : public TrayNotificationView {
+ public:
+ AccessibilityPopupView(SystemTrayItem* owner)
+ : TrayNotificationView(owner, IDR_AURA_UBER_TRAY_ACCESSIBILITY_DARK) {
+ InitView(GetLabel());
+ }
+
+ private:
+ views::Label* GetLabel() {
+ views::Label* label = new views::Label(
+ l10n_util::GetStringUTF16(
+ IDS_ASH_STATUS_TRAY_SPOKEN_FEEDBACK_ENABLED_BUBBLE));
+ label->SetMultiLine(true);
+ label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
+ return label;
+ }
+
+ DISALLOW_COPY_AND_ASSIGN(AccessibilityPopupView);
+};
+
+////////////////////////////////////////////////////////////////////////////////
+// ash::internal::tray::AccessibilityDetailedView
+
+AccessibilityDetailedView::AccessibilityDetailedView(
+ SystemTrayItem* owner, user::LoginStatus login) :
+ TrayDetailsView(owner),
+ spoken_feedback_view_(NULL),
+ high_contrast_view_(NULL),
+ screen_magnifier_view_(NULL),
+ large_cursor_view_(NULL),
+ help_view_(NULL),
+ settings_view_(NULL),
+ spoken_feedback_enabled_(false),
+ high_contrast_enabled_(false),
+ screen_magnifier_enabled_(false),
+ large_cursor_enabled_(false),
+ login_(login) {
+
+ Reset();
+
+ AppendAccessibilityList();
+ AppendHelpEntries();
+ CreateSpecialRow(IDS_ASH_STATUS_TRAY_ACCESSIBILITY_TITLE, this);
+
+ Layout();
+}
+
+void AccessibilityDetailedView::AppendAccessibilityList() {
+ CreateScrollableList();
+ ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance();
+
+ ShellDelegate* shell_delegate = Shell::GetInstance()->delegate();
+ spoken_feedback_enabled_ = shell_delegate->IsSpokenFeedbackEnabled();
+ spoken_feedback_view_ = AddScrollListItem(
+ bundle.GetLocalizedString(
+ IDS_ASH_STATUS_TRAY_ACCESSIBILITY_SPOKEN_FEEDBACK),
+ spoken_feedback_enabled_ ? gfx::Font::BOLD : gfx::Font::NORMAL,
+ spoken_feedback_enabled_);
+
+ // Large Cursor item is shown only in Login screen.
+ if (login_ == user::LOGGED_IN_NONE) {
+ large_cursor_enabled_ = shell_delegate->IsLargeCursorEnabled();
+ large_cursor_view_ = AddScrollListItem(
+ bundle.GetLocalizedString(
+ IDS_ASH_STATUS_TRAY_ACCESSIBILITY_LARGE_CURSOR),
+ large_cursor_enabled_ ? gfx::Font::BOLD : gfx::Font::NORMAL,
+ large_cursor_enabled_);
+ }
+
+ high_contrast_enabled_ = shell_delegate->IsHighContrastEnabled();
+ high_contrast_view_ = AddScrollListItem(
+ bundle.GetLocalizedString(
+ IDS_ASH_STATUS_TRAY_ACCESSIBILITY_HIGH_CONTRAST_MODE),
+ high_contrast_enabled_ ? gfx::Font::BOLD : gfx::Font::NORMAL,
+ high_contrast_enabled_);
+ screen_magnifier_enabled_ = shell_delegate->IsMagnifierEnabled();
+ screen_magnifier_view_ = AddScrollListItem(
+ bundle.GetLocalizedString(
+ IDS_ASH_STATUS_TRAY_ACCESSIBILITY_SCREEN_MAGNIFIER),
+ screen_magnifier_enabled_ ? gfx::Font::BOLD : gfx::Font::NORMAL,
+ screen_magnifier_enabled_);
+}
+
+void AccessibilityDetailedView::AppendHelpEntries() {
+ // Currently the help page requires a browser window.
+ // TODO(yoshiki): show this even on login/lock screen. crbug.com/158286
+ if (login_ == user::LOGGED_IN_NONE ||
+ login_ == user::LOGGED_IN_LOCKED)
+ return;
+
+ views::View* bottom_row = new View();
+ views::BoxLayout* layout = new
+ views::BoxLayout(views::BoxLayout::kHorizontal,
+ kTrayMenuBottomRowPadding,
+ kTrayMenuBottomRowPadding,
+ kTrayMenuBottomRowPaddingBetweenItems);
+ layout->set_spread_blank_space(true);
+ bottom_row->SetLayoutManager(layout);
+
+ ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance();
+
+ TrayPopupLabelButton* help = new TrayPopupLabelButton(
+ this,
+ bundle.GetLocalizedString(
+ IDS_ASH_STATUS_TRAY_ACCESSIBILITY_LEARN_MORE));
+ bottom_row->AddChildView(help);
+ help_view_ = help;
+
+ TrayPopupLabelButton* settings = new TrayPopupLabelButton(
+ this,
+ bundle.GetLocalizedString(
+ IDS_ASH_STATUS_TRAY_ACCESSIBILITY_SETTINGS));
+ bottom_row->AddChildView(settings);
+ settings_view_ = settings;
+
+ AddChildView(bottom_row);
+}
+
+HoverHighlightView* AccessibilityDetailedView::AddScrollListItem(
+ const base::string16& text,
+ gfx::Font::FontStyle style,
+ bool checked) {
+ HoverHighlightView* container = new HoverHighlightView(this);
+ container->AddCheckableLabel(text, style, checked);
+ scroll_content()->AddChildView(container);
+ return container;
+}
+
+void AccessibilityDetailedView::OnViewClicked(views::View* sender) {
+ ShellDelegate* shell_delegate = Shell::GetInstance()->delegate();
+ if (sender == footer()->content()) {
+ owner()->system_tray()->ShowDefaultView(BUBBLE_USE_EXISTING);
+ } else if (sender == spoken_feedback_view_) {
+ shell_delegate->ToggleSpokenFeedback(ash::A11Y_NOTIFICATION_NONE);
+ } else if (sender == high_contrast_view_) {
+ shell_delegate->ToggleHighContrast();
+ } else if (sender == screen_magnifier_view_) {
+ shell_delegate->SetMagnifierEnabled(!shell_delegate->IsMagnifierEnabled());
+ } else if (large_cursor_view_ && sender == large_cursor_view_) {
+ shell_delegate->
+ SetLargeCursorEnabled(!shell_delegate->IsLargeCursorEnabled());
+ }
+}
+
+void AccessibilityDetailedView::ButtonPressed(views::Button* sender,
+ const ui::Event& event) {
+ SystemTrayDelegate* tray_delegate =
+ Shell::GetInstance()->system_tray_delegate();
+ if (sender == help_view_)
+ tray_delegate->ShowAccessibilityHelp();
+ else if (sender == settings_view_)
+ tray_delegate->ShowAccessibilitySettings();
+}
+
+} // namespace tray
+
+////////////////////////////////////////////////////////////////////////////////
+// ash::internal::TrayAccessibility
+
+TrayAccessibility::TrayAccessibility(SystemTray* system_tray)
+ : TrayImageItem(system_tray, IDR_AURA_UBER_TRAY_ACCESSIBILITY),
+ default_(NULL),
+ detailed_popup_(NULL),
+ detailed_menu_(NULL),
+ request_popup_view_(false),
+ tray_icon_visible_(false),
+ login_(GetCurrentLoginStatus()),
+ previous_accessibility_state_(GetAccessibilityState()),
+ show_a11y_menu_on_lock_screen_(true) {
+ DCHECK(Shell::GetInstance()->delegate());
+ DCHECK(system_tray);
+ Shell::GetInstance()->system_tray_notifier()->AddAccessibilityObserver(this);
+}
+
+TrayAccessibility::~TrayAccessibility() {
+ Shell::GetInstance()->system_tray_notifier()->
+ RemoveAccessibilityObserver(this);
+}
+
+void TrayAccessibility::SetTrayIconVisible(bool visible) {
+ if (tray_view())
+ tray_view()->SetVisible(visible);
+ tray_icon_visible_ = visible;
+}
+
+tray::AccessibilityDetailedView* TrayAccessibility::CreateDetailedMenu() {
+ return new tray::AccessibilityDetailedView(this, login_);
+}
+
+bool TrayAccessibility::GetInitialVisibility() {
+ // Shows accessibility icon if any accessibility feature is enabled.
+ // Otherwise, doen't show it.
+ return GetAccessibilityState() != A11Y_NONE;
+}
+
+views::View* TrayAccessibility::CreateDefaultView(user::LoginStatus status) {
+ CHECK(default_ == NULL);
+
+ // Shows accessibility menu if:
+ // - on login screen (not logged in);
+ // - "Enable accessibility menu" on chrome://settings is checked;
+ // - or any of accessibility features is enabled
+ // Otherwise, not shows it.
+ ShellDelegate* delegate = Shell::GetInstance()->delegate();
+ if (login_ != user::LOGGED_IN_NONE &&
+ !delegate->ShouldAlwaysShowAccessibilityMenu() &&
+ // On login screen, keeps the initial visivility of the menu.
+ (status != user::LOGGED_IN_LOCKED || !show_a11y_menu_on_lock_screen_) &&
+ GetAccessibilityState() == A11Y_NONE)
+ return NULL;
+
+ CHECK(default_ == NULL);
+ default_ = new tray::DefaultAccessibilityView(this);
+
+ return default_;
+}
+
+views::View* TrayAccessibility::CreateDetailedView(user::LoginStatus status) {
+ CHECK(detailed_popup_ == NULL);
+ CHECK(detailed_menu_ == NULL);
+
+ if (request_popup_view_) {
+ detailed_popup_ = new tray::AccessibilityPopupView(this);
+ request_popup_view_ = false;
+ return detailed_popup_;
+ } else {
+ detailed_menu_ = CreateDetailedMenu();
+ return detailed_menu_;
+ }
+}
+
+void TrayAccessibility::DestroyDefaultView() {
+ default_ = NULL;
+}
+
+void TrayAccessibility::DestroyDetailedView() {
+ detailed_popup_ = NULL;
+ detailed_menu_ = NULL;
+}
+
+void TrayAccessibility::UpdateAfterLoginStatusChange(user::LoginStatus status) {
+ // Stores the a11y feature status on just entering the lock screen.
+ if (login_ != user::LOGGED_IN_LOCKED && status == user::LOGGED_IN_LOCKED)
+ show_a11y_menu_on_lock_screen_ = (GetAccessibilityState() != A11Y_NONE);
+
+ login_ = status;
+ SetTrayIconVisible(GetInitialVisibility());
+}
+
+void TrayAccessibility::OnAccessibilityModeChanged(
+ AccessibilityNotificationVisibility notify) {
+ SetTrayIconVisible(GetInitialVisibility());
+
+ uint32 accessibility_state = GetAccessibilityState();
+ if ((notify == ash::A11Y_NOTIFICATION_SHOW)&&
+ !(previous_accessibility_state_ & A11Y_SPOKEN_FEEDBACK) &&
+ (accessibility_state & A11Y_SPOKEN_FEEDBACK)) {
+ // Shows popup if |notify| is true and the spoken feedback is being enabled.
+ request_popup_view_ = true;
+ PopupDetailedView(kTrayPopupAutoCloseDelayForTextInSeconds, false);
+ } else {
+ if (detailed_popup_)
+ detailed_popup_->GetWidget()->Close();
+ if (detailed_menu_)
+ detailed_menu_->GetWidget()->Close();
+ }
+
+ previous_accessibility_state_ = accessibility_state;
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/system/tray_accessibility.h b/chromium/ash/system/tray_accessibility.h
new file mode 100644
index 00000000000..01713783c56
--- /dev/null
+++ b/chromium/ash/system/tray_accessibility.h
@@ -0,0 +1,135 @@
+// Copyright (c) 2012 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 ASH_SYSTEM_TRAY_ACCESSIBILITY_H_
+#define ASH_SYSTEM_TRAY_ACCESSIBILITY_H_
+
+#include "ash/shell_delegate.h"
+#include "ash/shell_observer.h"
+#include "ash/system/tray/tray_details_view.h"
+#include "ash/system/tray/tray_image_item.h"
+#include "ash/system/tray/view_click_listener.h"
+#include "base/gtest_prod_util.h"
+#include "ui/gfx/font.h"
+#include "ui/views/controls/button/button.h"
+
+namespace chromeos {
+class TrayAccessibilityTest;
+}
+
+namespace views {
+class Button;
+class ImageView;
+class View;
+}
+
+namespace ash {
+
+class SystemTrayItem;
+
+class ASH_EXPORT AccessibilityObserver {
+ public:
+ virtual ~AccessibilityObserver() {}
+
+ // Notifies when accessibilty mode changes.
+ virtual void OnAccessibilityModeChanged(
+ AccessibilityNotificationVisibility notify) = 0;
+};
+
+namespace internal {
+
+class HoverHighlightView;
+
+namespace tray {
+
+class AccessibilityPopupView;
+
+class AccessibilityDetailedView : public TrayDetailsView,
+ public ViewClickListener,
+ public views::ButtonListener,
+ public ShellObserver {
+ public:
+ explicit AccessibilityDetailedView(SystemTrayItem* owner,
+ user::LoginStatus login);
+ virtual ~AccessibilityDetailedView() {}
+
+ private:
+ // Add the accessibility feature list.
+ void AppendAccessibilityList();
+
+ // Add help entries.
+ void AppendHelpEntries();
+
+ HoverHighlightView* AddScrollListItem(const base::string16& text,
+ gfx::Font::FontStyle style,
+ bool checked);
+ // Overridden from ViewClickListener.
+ virtual void OnViewClicked(views::View* sender) OVERRIDE;
+ // Overridden from ButtonListener.
+ virtual void ButtonPressed(views::Button* sender,
+ const ui::Event& event) OVERRIDE;
+
+ views::View* spoken_feedback_view_;
+ views::View* high_contrast_view_;
+ views::View* screen_magnifier_view_;;
+ views::View* large_cursor_view_;;
+ views::View* help_view_;
+ views::View* settings_view_;
+
+ bool spoken_feedback_enabled_;
+ bool high_contrast_enabled_;
+ bool screen_magnifier_enabled_;
+ bool large_cursor_enabled_;
+ user::LoginStatus login_;
+
+ friend class chromeos::TrayAccessibilityTest;
+ DISALLOW_COPY_AND_ASSIGN(AccessibilityDetailedView);
+};
+
+} // namespace tray
+
+class TrayAccessibility : public TrayImageItem,
+ public AccessibilityObserver {
+ public:
+ explicit TrayAccessibility(SystemTray* system_tray);
+ virtual ~TrayAccessibility();
+
+ private:
+ void SetTrayIconVisible(bool visible);
+ tray::AccessibilityDetailedView* CreateDetailedMenu();
+
+ // Overridden from TrayImageItem.
+ virtual bool GetInitialVisibility() OVERRIDE;
+ virtual views::View* CreateDefaultView(user::LoginStatus status) OVERRIDE;
+ virtual views::View* CreateDetailedView(user::LoginStatus status) OVERRIDE;
+ virtual void DestroyDefaultView() OVERRIDE;
+ virtual void DestroyDetailedView() OVERRIDE;
+ virtual void UpdateAfterLoginStatusChange(user::LoginStatus status) OVERRIDE;
+
+ // Overridden from AccessibilityObserver.
+ virtual void OnAccessibilityModeChanged(
+ AccessibilityNotificationVisibility notify) OVERRIDE;
+
+ views::View* default_;
+ tray::AccessibilityPopupView* detailed_popup_;
+ tray::AccessibilityDetailedView* detailed_menu_;
+
+ bool request_popup_view_;
+ bool tray_icon_visible_;
+ user::LoginStatus login_;
+
+ // Bitmap of values from AccessibilityState enum.
+ uint32 previous_accessibility_state_;
+
+ // A11y feature status on just entering the lock screen.
+ bool show_a11y_menu_on_lock_screen_;
+
+ friend class chromeos::TrayAccessibilityTest;
+ DISALLOW_COPY_AND_ASSIGN(TrayAccessibility);
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_SYSTEM_TRAY_ACCESSIBILITY_H_
diff --git a/chromium/ash/system/tray_caps_lock.cc b/chromium/ash/system/tray_caps_lock.cc
new file mode 100644
index 00000000000..89f2747738e
--- /dev/null
+++ b/chromium/ash/system/tray_caps_lock.cc
@@ -0,0 +1,192 @@
+// Copyright (c) 2012 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.
+
+#include "ash/system/tray_caps_lock.h"
+
+#include "ash/caps_lock_delegate.h"
+#include "ash/shell.h"
+#include "ash/system/tray/actionable_view.h"
+#include "ash/system/tray/fixed_sized_image_view.h"
+#include "ash/system/tray/system_tray_notifier.h"
+#include "ash/system/tray/tray_constants.h"
+#include "grit/ash_resources.h"
+#include "grit/ash_strings.h"
+#include "ui/base/accessibility/accessible_view_state.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "ui/gfx/image/image.h"
+#include "ui/views/controls/image_view.h"
+#include "ui/views/controls/label.h"
+#include "ui/views/layout/box_layout.h"
+#include "ui/views/widget/widget.h"
+
+namespace ash {
+namespace internal {
+
+class CapsLockDefaultView : public ActionableView {
+ public:
+ CapsLockDefaultView()
+ : text_label_(new views::Label),
+ shortcut_label_(new views::Label) {
+ SetLayoutManager(new views::BoxLayout(views::BoxLayout::kHorizontal,
+ kTrayPopupPaddingHorizontal,
+ 0,
+ kTrayPopupPaddingBetweenItems));
+
+ ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance();
+ FixedSizedImageView* image =
+ new FixedSizedImageView(0, kTrayPopupItemHeight);
+ image->SetImage(bundle.GetImageNamed(IDR_AURA_UBER_TRAY_CAPS_LOCK_DARK).
+ ToImageSkia());
+ AddChildView(image);
+
+ text_label_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
+ AddChildView(text_label_);
+
+ shortcut_label_->SetEnabled(false);
+ AddChildView(shortcut_label_);
+ }
+
+ virtual ~CapsLockDefaultView() {}
+
+ // Updates the label text and the shortcut text.
+ void Update(bool caps_lock_enabled, bool search_mapped_to_caps_lock) {
+ ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance();
+ const int text_string_id = caps_lock_enabled ?
+ IDS_ASH_STATUS_TRAY_CAPS_LOCK_ENABLED :
+ IDS_ASH_STATUS_TRAY_CAPS_LOCK_DISABLED;
+ text_label_->SetText(bundle.GetLocalizedString(text_string_id));
+
+ int shortcut_string_id = 0;
+ if (caps_lock_enabled) {
+ shortcut_string_id = search_mapped_to_caps_lock ?
+ IDS_ASH_STATUS_TRAY_CAPS_LOCK_SHORTCUT_SEARCH_OR_SHIFT :
+ IDS_ASH_STATUS_TRAY_CAPS_LOCK_SHORTCUT_ALT_SEARCH_OR_SHIFT;
+ } else {
+ shortcut_string_id = search_mapped_to_caps_lock ?
+ IDS_ASH_STATUS_TRAY_CAPS_LOCK_SHORTCUT_SEARCH :
+ IDS_ASH_STATUS_TRAY_CAPS_LOCK_SHORTCUT_ALT_SEARCH;
+ }
+ shortcut_label_->SetText(bundle.GetLocalizedString(shortcut_string_id));
+
+ Layout();
+ }
+
+ private:
+ // Overridden from views::View:
+ virtual void Layout() OVERRIDE {
+ views::View::Layout();
+
+ // Align the shortcut text with the right end
+ const int old_x = shortcut_label_->x();
+ const int new_x =
+ width() - shortcut_label_->width() - kTrayPopupPaddingHorizontal;
+ shortcut_label_->SetX(new_x);
+ const gfx::Size text_size = text_label_->size();
+ text_label_->SetSize(gfx::Size(text_size.width() + new_x - old_x,
+ text_size.height()));
+ }
+
+ virtual void GetAccessibleState(ui::AccessibleViewState* state) OVERRIDE {
+ state->role = ui::AccessibilityTypes::ROLE_PUSHBUTTON;
+ state->name = text_label_->text();
+ }
+
+ // Overridden from ActionableView:
+ virtual bool PerformAction(const ui::Event& event) OVERRIDE {
+ Shell::GetInstance()->caps_lock_delegate()->ToggleCapsLock();
+ return true;
+ }
+
+ views::Label* text_label_;
+ views::Label* shortcut_label_;
+
+ DISALLOW_COPY_AND_ASSIGN(CapsLockDefaultView);
+};
+
+TrayCapsLock::TrayCapsLock(SystemTray* system_tray)
+ : TrayImageItem(system_tray, IDR_AURA_UBER_TRAY_CAPS_LOCK),
+ default_(NULL),
+ detailed_(NULL),
+ search_mapped_to_caps_lock_(false),
+ caps_lock_enabled_(
+ Shell::GetInstance()->caps_lock_delegate()->IsCapsLockEnabled()),
+ message_shown_(false) {
+ Shell::GetInstance()->system_tray_notifier()->AddCapsLockObserver(this);
+}
+
+TrayCapsLock::~TrayCapsLock() {
+ Shell::GetInstance()->system_tray_notifier()->RemoveCapsLockObserver(this);
+}
+
+bool TrayCapsLock::GetInitialVisibility() {
+ return Shell::GetInstance()->caps_lock_delegate()->IsCapsLockEnabled();
+}
+
+views::View* TrayCapsLock::CreateDefaultView(user::LoginStatus status) {
+ if (!caps_lock_enabled_)
+ return NULL;
+ DCHECK(default_ == NULL);
+ default_ = new CapsLockDefaultView;
+ default_->Update(caps_lock_enabled_, search_mapped_to_caps_lock_);
+ return default_;
+}
+
+views::View* TrayCapsLock::CreateDetailedView(user::LoginStatus status) {
+ DCHECK(detailed_ == NULL);
+ detailed_ = new views::View;
+
+ detailed_->SetLayoutManager(new
+ views::BoxLayout(views::BoxLayout::kHorizontal,
+ kTrayPopupPaddingHorizontal, 10, kTrayPopupPaddingBetweenItems));
+
+ ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance();
+ views::ImageView* image = new views::ImageView;
+ image->SetImage(bundle.GetImageNamed(IDR_AURA_UBER_TRAY_CAPS_LOCK_DARK).
+ ToImageSkia());
+
+ detailed_->AddChildView(image);
+
+ const int string_id = search_mapped_to_caps_lock_ ?
+ IDS_ASH_STATUS_TRAY_CAPS_LOCK_CANCEL_BY_SEARCH :
+ IDS_ASH_STATUS_TRAY_CAPS_LOCK_CANCEL_BY_ALT_SEARCH;
+ views::Label* label = new views::Label(bundle.GetLocalizedString(string_id));
+ label->SetMultiLine(true);
+ label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
+ detailed_->AddChildView(label);
+
+ return detailed_;
+}
+
+void TrayCapsLock::DestroyDefaultView() {
+ default_ = NULL;
+}
+
+void TrayCapsLock::DestroyDetailedView() {
+ detailed_ = NULL;
+}
+
+void TrayCapsLock::OnCapsLockChanged(bool enabled,
+ bool search_mapped_to_caps_lock) {
+ if (tray_view())
+ tray_view()->SetVisible(enabled);
+
+ caps_lock_enabled_ = enabled;
+ search_mapped_to_caps_lock_ = search_mapped_to_caps_lock;
+
+ if (default_) {
+ default_->Update(enabled, search_mapped_to_caps_lock);
+ } else {
+ if (enabled) {
+ if (!message_shown_) {
+ PopupDetailedView(kTrayPopupAutoCloseDelayForTextInSeconds, false);
+ message_shown_ = true;
+ }
+ } else if (detailed_) {
+ detailed_->GetWidget()->Close();
+ }
+ }
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/system/tray_caps_lock.h b/chromium/ash/system/tray_caps_lock.h
new file mode 100644
index 00000000000..a9e32b2bb73
--- /dev/null
+++ b/chromium/ash/system/tray_caps_lock.h
@@ -0,0 +1,60 @@
+// Copyright (c) 2012 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 ASH_SYSTEM_TRAY_CAPS_LOCK_H_
+#define ASH_SYSTEM_TRAY_CAPS_LOCK_H_
+
+#include "ash/system/tray/tray_image_item.h"
+
+namespace views {
+class ImageView;
+class View;
+}
+
+namespace ash {
+
+class ASH_EXPORT CapsLockObserver {
+ public:
+ virtual ~CapsLockObserver() {}
+
+ virtual void OnCapsLockChanged(bool enabled,
+ bool search_mapped_to_caps_lock) = 0;
+};
+
+namespace internal {
+
+class CapsLockDefaultView;
+
+class TrayCapsLock : public TrayImageItem,
+ public CapsLockObserver {
+ public:
+ explicit TrayCapsLock(SystemTray* system_tray);
+ virtual ~TrayCapsLock();
+
+ private:
+ // Overridden from TrayImageItem.
+ virtual bool GetInitialVisibility() OVERRIDE;
+ virtual views::View* CreateDefaultView(user::LoginStatus status) OVERRIDE;
+ virtual views::View* CreateDetailedView(user::LoginStatus status) OVERRIDE;
+ virtual void DestroyDefaultView() OVERRIDE;
+ virtual void DestroyDetailedView() OVERRIDE;
+
+ // Overridden from CapsLockObserver.
+ virtual void OnCapsLockChanged(bool enabled,
+ bool search_mapped_to_caps_lock) OVERRIDE;
+
+ CapsLockDefaultView* default_;
+ views::View* detailed_;
+
+ bool search_mapped_to_caps_lock_;
+ bool caps_lock_enabled_;
+ bool message_shown_;
+
+ DISALLOW_COPY_AND_ASSIGN(TrayCapsLock);
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_SYSTEM_TRAY_CAPS_LOCK_H_
diff --git a/chromium/ash/system/tray_update.cc b/chromium/ash/system/tray_update.cc
new file mode 100644
index 00000000000..e01dd97fcb2
--- /dev/null
+++ b/chromium/ash/system/tray_update.cc
@@ -0,0 +1,205 @@
+// Copyright (c) 2012 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.
+
+#include "ash/system/tray_update.h"
+
+#include "ash/root_window_controller.h"
+#include "ash/shelf/shelf_layout_manager.h"
+#include "ash/shelf/shelf_widget.h"
+#include "ash/shell.h"
+#include "ash/system/status_area_widget.h"
+#include "ash/system/tray/fixed_sized_image_view.h"
+#include "ash/system/tray/system_tray.h"
+#include "ash/system/tray/system_tray_delegate.h"
+#include "ash/system/tray/system_tray_notifier.h"
+#include "ash/system/tray/tray_constants.h"
+#include "base/time/time.h"
+#include "base/timer/timer.h"
+#include "grit/ash_resources.h"
+#include "grit/ash_strings.h"
+#include "ui/aura/window.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "ui/compositor/layer.h"
+#include "ui/compositor/layer_animation_observer.h"
+#include "ui/compositor/layer_animation_sequence.h"
+#include "ui/gfx/image/image.h"
+#include "ui/views/controls/image_view.h"
+#include "ui/views/controls/label.h"
+#include "ui/views/layout/box_layout.h"
+#include "ui/views/widget/widget.h"
+
+namespace {
+
+// How many seconds should we wait before showing the nag reminder?
+const int kUpdateNaggingTimeSeconds = 24 * 60 * 60;
+
+// How long should the nag reminder be displayed?
+const int kShowUpdateNaggerForSeconds = 15;
+
+int DecideResource(ash::UpdateObserver::UpdateSeverity severity, bool dark) {
+ switch (severity) {
+ case ash::UpdateObserver::UPDATE_NORMAL:
+ return dark ? IDR_AURA_UBER_TRAY_UPDATE_DARK:
+ IDR_AURA_UBER_TRAY_UPDATE;
+
+ case ash::UpdateObserver::UPDATE_LOW_GREEN:
+ return dark ? IDR_AURA_UBER_TRAY_UPDATE_DARK_GREEN :
+ IDR_AURA_UBER_TRAY_UPDATE_GREEN;
+
+ case ash::UpdateObserver::UPDATE_HIGH_ORANGE:
+ return dark ? IDR_AURA_UBER_TRAY_UPDATE_DARK_ORANGE :
+ IDR_AURA_UBER_TRAY_UPDATE_ORANGE;
+
+ case ash::UpdateObserver::UPDATE_SEVERE_RED:
+ return dark ? IDR_AURA_UBER_TRAY_UPDATE_DARK_RED :
+ IDR_AURA_UBER_TRAY_UPDATE_RED;
+ }
+
+ NOTREACHED() << "Unknown update severity level.";
+ return 0;
+}
+
+class UpdateView : public ash::internal::ActionableView {
+ public:
+ explicit UpdateView(ash::UpdateObserver::UpdateSeverity severity) {
+ SetLayoutManager(new
+ views::BoxLayout(views::BoxLayout::kHorizontal,
+ ash::kTrayPopupPaddingHorizontal, 0,
+ ash::kTrayPopupPaddingBetweenItems));
+
+ ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance();
+ views::ImageView* image =
+ new ash::internal::FixedSizedImageView(0, ash::kTrayPopupItemHeight);
+ image->SetImage(bundle.GetImageNamed(DecideResource(severity, true)).
+ ToImageSkia());
+
+ AddChildView(image);
+ AddChildView(new views::Label(
+ bundle.GetLocalizedString(IDS_ASH_STATUS_TRAY_UPDATE)));
+ SetAccessibleName(bundle.GetLocalizedString(IDS_ASH_STATUS_TRAY_UPDATE));
+ }
+
+ virtual ~UpdateView() {}
+
+ private:
+ // Overridden from ActionableView.
+ virtual bool PerformAction(const ui::Event& event) OVERRIDE {
+ ash::Shell::GetInstance()->
+ system_tray_delegate()->RequestRestartForUpdate();
+ return true;
+ }
+
+ DISALLOW_COPY_AND_ASSIGN(UpdateView);
+};
+
+}
+
+namespace ash {
+namespace internal {
+
+namespace tray {
+
+class UpdateNagger : public ui::LayerAnimationObserver {
+ public:
+ explicit UpdateNagger(SystemTrayItem* owner)
+ : owner_(owner) {
+ RestartTimer();
+ owner_->system_tray()->GetWidget()->GetNativeView()->layer()->
+ GetAnimator()->AddObserver(this);
+ }
+
+ virtual ~UpdateNagger() {
+ internal::StatusAreaWidget* status_area =
+ Shell::GetPrimaryRootWindowController()->shelf()->status_area_widget();
+ if (status_area) {
+ status_area->system_tray()->GetWidget()->GetNativeView()->layer()->
+ GetAnimator()->RemoveObserver(this);
+ }
+ }
+
+ void RestartTimer() {
+ timer_.Stop();
+ timer_.Start(FROM_HERE,
+ base::TimeDelta::FromSeconds(kUpdateNaggingTimeSeconds),
+ this,
+ &UpdateNagger::Nag);
+ }
+
+ private:
+ void Nag() {
+ owner_->PopupDetailedView(kShowUpdateNaggerForSeconds, false);
+ }
+
+ // Overridden from ui::LayerAnimationObserver.
+ virtual void OnLayerAnimationEnded(
+ ui::LayerAnimationSequence* sequence) OVERRIDE {
+ // TODO(oshima): Find out if the updator will be shown on non
+ // primary display.
+ if (Shell::GetPrimaryRootWindowController()->shelf()->IsVisible())
+ timer_.Stop();
+ else if (!timer_.IsRunning())
+ RestartTimer();
+ }
+
+ virtual void OnLayerAnimationAborted(
+ ui::LayerAnimationSequence* sequence) OVERRIDE {}
+
+ virtual void OnLayerAnimationScheduled(
+ ui::LayerAnimationSequence* sequence) OVERRIDE {}
+
+ SystemTrayItem* owner_;
+ base::OneShotTimer<UpdateNagger> timer_;
+
+ DISALLOW_COPY_AND_ASSIGN(UpdateNagger);
+};
+
+} // namespace tray
+
+TrayUpdate::TrayUpdate(SystemTray* system_tray)
+ : TrayImageItem(system_tray, IDR_AURA_UBER_TRAY_UPDATE),
+ severity_(UpdateObserver::UPDATE_NORMAL) {
+ Shell::GetInstance()->system_tray_notifier()->AddUpdateObserver(this);
+}
+
+TrayUpdate::~TrayUpdate() {
+ Shell::GetInstance()->system_tray_notifier()->RemoveUpdateObserver(this);
+}
+
+bool TrayUpdate::GetInitialVisibility() {
+ return Shell::GetInstance()->system_tray_delegate()->SystemShouldUpgrade();
+}
+
+views::View* TrayUpdate::CreateDefaultView(user::LoginStatus status) {
+ if (!Shell::GetInstance()->system_tray_delegate()->SystemShouldUpgrade())
+ return NULL;
+ return new UpdateView(severity_);
+}
+
+views::View* TrayUpdate::CreateDetailedView(user::LoginStatus status) {
+ return CreateDefaultView(status);
+}
+
+void TrayUpdate::DestroyDetailedView() {
+ if (nagger_) {
+ // The nagger was being displayed. Now that the detailed view is being
+ // closed, that means either the user clicks on it to restart, or the user
+ // didn't click on it to restart. In either case, start the timer to show
+ // the nag reminder again after the specified time.
+ nagger_->RestartTimer();
+ }
+}
+
+void TrayUpdate::OnUpdateRecommended(UpdateObserver::UpdateSeverity severity) {
+ severity_ = severity;
+ SetImageFromResourceId(DecideResource(severity_, false));
+ tray_view()->SetVisible(true);
+ if (!Shell::GetPrimaryRootWindowController()->shelf()->IsVisible() &&
+ !nagger_.get()) {
+ // The shelf is not visible, and there is no nagger scheduled.
+ nagger_.reset(new tray::UpdateNagger(this));
+ }
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/system/tray_update.h b/chromium/ash/system/tray_update.h
new file mode 100644
index 00000000000..123cde19f3b
--- /dev/null
+++ b/chromium/ash/system/tray_update.h
@@ -0,0 +1,51 @@
+// Copyright (c) 2012 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 ASH_SYSTEM_TRAY_UPDATE_H_
+#define ASH_SYSTEM_TRAY_UPDATE_H_
+
+#include "ash/system/tray/tray_image_item.h"
+#include "ash/system/user/update_observer.h"
+#include "base/memory/scoped_ptr.h"
+
+namespace views {
+class View;
+}
+
+namespace ash {
+namespace internal {
+
+namespace tray {
+class UpdateNagger;
+}
+
+class TrayUpdate : public TrayImageItem,
+ public UpdateObserver {
+ public:
+ explicit TrayUpdate(SystemTray* system_tray);
+ virtual ~TrayUpdate();
+
+ private:
+ // Overridden from TrayImageItem.
+ virtual bool GetInitialVisibility() OVERRIDE;
+ virtual views::View* CreateDefaultView(user::LoginStatus status) OVERRIDE;
+ virtual views::View* CreateDetailedView(user::LoginStatus status) OVERRIDE;
+ virtual void DestroyDetailedView() OVERRIDE;
+
+ // Overridden from UpdateObserver.
+ virtual void OnUpdateRecommended(UpdateSeverity severity) OVERRIDE;
+
+ // Used to nag the user in case the tray has been hidden too long with an
+ // unseen update notification.
+ scoped_ptr<tray::UpdateNagger> nagger_;
+
+ UpdateObserver::UpdateSeverity severity_;
+
+ DISALLOW_COPY_AND_ASSIGN(TrayUpdate);
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_SYSTEM_TRAY_UPDATE_H_
diff --git a/chromium/ash/system/user/login_status.cc b/chromium/ash/system/user/login_status.cc
new file mode 100644
index 00000000000..93678fd2d3d
--- /dev/null
+++ b/chromium/ash/system/user/login_status.cc
@@ -0,0 +1,51 @@
+// Copyright (c) 2012 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.
+
+#include "ash/system/user/login_status.h"
+
+#include "ash/session_state_delegate.h"
+#include "ash/shell.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "grit/ash_strings.h"
+#include "ui/base/resource/resource_bundle.h"
+
+namespace ash {
+namespace user {
+
+base::string16 GetLocalizedSignOutStringForStatus(LoginStatus status,
+ bool multiline) {
+ int message_id;
+ switch (status) {
+ case LOGGED_IN_GUEST:
+ message_id = IDS_ASH_STATUS_TRAY_EXIT_GUEST;
+ break;
+ case LOGGED_IN_RETAIL_MODE:
+ message_id = IDS_ASH_STATUS_TRAY_EXIT_KIOSK;
+ break;
+ case LOGGED_IN_PUBLIC:
+ message_id = IDS_ASH_STATUS_TRAY_EXIT_PUBLIC;
+ break;
+ default:
+ if (ash::Shell::GetInstance()->session_state_delegate()->
+ NumberOfLoggedInUsers() > 1) {
+ message_id = IDS_ASH_STATUS_TRAY_SIGN_OUT_ALL;
+ } else {
+ message_id = IDS_ASH_STATUS_TRAY_SIGN_OUT;
+ }
+ break;
+ }
+ base::string16 message =
+ ui::ResourceBundle::GetSharedInstance().GetLocalizedString(message_id);
+ // Desirable line breaking points are marked using \n. As the resource
+ // framework does not evaluate escape sequences, the \n need to be explicitly
+ // handled. Depending on the value of |multiline|, actual line breaks or
+ // spaces are substituted.
+ base::string16 newline = multiline ? ASCIIToUTF16("\n") : ASCIIToUTF16(" ");
+ ReplaceSubstringsAfterOffset(&message, 0, ASCIIToUTF16("\\n"), newline);
+ return message;
+}
+
+} // namespace user
+} // namespace ash
diff --git a/chromium/ash/system/user/login_status.h b/chromium/ash/system/user/login_status.h
new file mode 100644
index 00000000000..d08a1604a06
--- /dev/null
+++ b/chromium/ash/system/user/login_status.h
@@ -0,0 +1,31 @@
+// Copyright (c) 2012 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 ASH_SYSTEM_USER_LOGIN_STATUS_H_
+#define ASH_SYSTEM_USER_LOGIN_STATUS_H_
+
+#include "base/strings/string16.h"
+
+namespace ash {
+namespace user {
+
+enum LoginStatus {
+ LOGGED_IN_NONE, // Not logged in
+ LOGGED_IN_LOCKED, // A user has locked the screen
+ LOGGED_IN_USER, // A regular user is logged in
+ LOGGED_IN_OWNER, // The owner of the device is logged in
+ LOGGED_IN_GUEST, // A guest is logged in (i.e. incognito)
+ LOGGED_IN_RETAIL_MODE, // Is in retail mode
+ LOGGED_IN_PUBLIC, // A public account is logged in
+ LOGGED_IN_LOCALLY_MANAGED, // A locally managed user is logged in
+ LOGGED_IN_KIOSK_APP // Is in kiosk app mode
+};
+
+base::string16 GetLocalizedSignOutStringForStatus(LoginStatus status,
+ bool multiline);
+
+} // namespace user
+} // namespace ash
+
+#endif // ASH_SYSTEM_USER_LOGIN_STATUS_H_
diff --git a/chromium/ash/system/user/tray_user.cc b/chromium/ash/system/user/tray_user.cc
new file mode 100644
index 00000000000..94a85ea07a5
--- /dev/null
+++ b/chromium/ash/system/user/tray_user.cc
@@ -0,0 +1,1296 @@
+// Copyright (c) 2012 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.
+
+#include "ash/system/user/tray_user.h"
+
+#include <algorithm>
+#include <climits>
+#include <vector>
+
+#include "ash/ash_switches.h"
+#include "ash/popup_message.h"
+#include "ash/root_window_controller.h"
+#include "ash/session_state_delegate.h"
+#include "ash/shelf/shelf_layout_manager.h"
+#include "ash/shell.h"
+#include "ash/shell_delegate.h"
+#include "ash/system/tray/system_tray.h"
+#include "ash/system/tray/system_tray_delegate.h"
+#include "ash/system/tray/system_tray_notifier.h"
+#include "ash/system/tray/tray_constants.h"
+#include "ash/system/tray/tray_item_view.h"
+#include "ash/system/tray/tray_popup_label_button.h"
+#include "ash/system/tray/tray_popup_label_button_border.h"
+#include "ash/system/tray/tray_utils.h"
+#include "base/i18n/rtl.h"
+#include "base/logging.h"
+#include "base/memory/scoped_vector.h"
+#include "base/strings/string16.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "grit/ash_resources.h"
+#include "grit/ash_strings.h"
+#include "skia/ext/image_operations.h"
+#include "third_party/skia/include/core/SkCanvas.h"
+#include "third_party/skia/include/core/SkPaint.h"
+#include "third_party/skia/include/core/SkPath.h"
+#include "ui/aura/window.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/base/range/range.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "ui/base/text/text_elider.h"
+#include "ui/gfx/canvas.h"
+#include "ui/gfx/font.h"
+#include "ui/gfx/image/image.h"
+#include "ui/gfx/image/image_skia_operations.h"
+#include "ui/gfx/insets.h"
+#include "ui/gfx/rect.h"
+#include "ui/gfx/render_text.h"
+#include "ui/gfx/size.h"
+#include "ui/gfx/skia_util.h"
+#include "ui/views/border.h"
+#include "ui/views/bubble/tray_bubble_view.h"
+#include "ui/views/controls/button/button.h"
+#include "ui/views/controls/button/custom_button.h"
+#include "ui/views/controls/image_view.h"
+#include "ui/views/controls/label.h"
+#include "ui/views/controls/link.h"
+#include "ui/views/controls/link_listener.h"
+#include "ui/views/corewm/shadow_types.h"
+#include "ui/views/layout/box_layout.h"
+#include "ui/views/layout/fill_layout.h"
+#include "ui/views/mouse_watcher.h"
+#include "ui/views/painter.h"
+#include "ui/views/view.h"
+#include "ui/views/widget/widget.h"
+
+namespace {
+
+const int kUserDetailsVerticalPadding = 5;
+const int kUserCardVerticalPadding = 10;
+const int kInactiveUserCardVerticalPadding = 4;
+const int kProfileRoundedCornerRadius = 2;
+const int kUserIconSize = 27;
+const int kUserIconLargeSize = 32;
+const int kUserIconLargeCornerRadius = 2;
+const int kUserLabelToIconPadding = 5;
+
+// When a hover border is used, it is starting this many pixels before the icon
+// position.
+const int kTrayUserTileHoverBorderInset = 10;
+
+// The border color of the user button.
+const SkColor kBorderColor = 0xffdcdcdc;
+
+// The invisible word joiner character, used as a marker to indicate the start
+// and end of the user's display name in the public account user card's text.
+const char16 kDisplayNameMark[] = { 0x2060, 0 };
+
+const int kPublicAccountLogoutButtonBorderImagesNormal[] = {
+ IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER,
+ IDR_AURA_TRAY_POPUP_LABEL_BUTTON_NORMAL_BACKGROUND,
+ IDR_AURA_TRAY_POPUP_LABEL_BUTTON_NORMAL_BACKGROUND,
+ IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER,
+ IDR_AURA_TRAY_POPUP_LABEL_BUTTON_NORMAL_BACKGROUND,
+ IDR_AURA_TRAY_POPUP_LABEL_BUTTON_NORMAL_BACKGROUND,
+ IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER,
+ IDR_AURA_TRAY_POPUP_LABEL_BUTTON_NORMAL_BACKGROUND,
+ IDR_AURA_TRAY_POPUP_LABEL_BUTTON_NORMAL_BACKGROUND,
+};
+
+const int kPublicAccountLogoutButtonBorderImagesHovered[] = {
+ IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER,
+ IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER,
+ IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER,
+ IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER,
+ IDR_AURA_TRAY_POPUP_LABEL_BUTTON_HOVER_BACKGROUND,
+ IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER,
+ IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER,
+ IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER,
+ IDR_AURA_TRAY_POPUP_PUBLIC_ACCOUNT_LOGOUT_BUTTON_BORDER,
+};
+
+// Offsetting the popup message relative to the tray menu.
+const int kPopupMessageOffset = 25;
+
+} // namespace
+
+namespace ash {
+namespace internal {
+
+namespace tray {
+
+// A custom image view with rounded edges.
+class RoundedImageView : public views::View {
+ public:
+ // Constructs a new rounded image view with rounded corners of radius
+ // |corner_radius|. If |active_user| is set, the icon will be drawn in
+ // full colors - otherwise it will fade into the background.
+ RoundedImageView(int corner_radius, bool active_user);
+ virtual ~RoundedImageView();
+
+ // Set the image that should be displayed. The image contents is copied to the
+ // receiver's image.
+ void SetImage(const gfx::ImageSkia& img, const gfx::Size& size);
+
+ // Set the radii of the corners independantly.
+ void SetCornerRadii(int top_left,
+ int top_right,
+ int bottom_right,
+ int bottom_left);
+
+ private:
+ // Overridden from views::View.
+ virtual gfx::Size GetPreferredSize() OVERRIDE;
+ virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE;
+
+ gfx::ImageSkia image_;
+ gfx::ImageSkia resized_;
+ gfx::Size image_size_;
+ int corner_radius_[4];
+
+ // True if the given user is the active user and the icon should get
+ // painted as active.
+ bool active_user_;
+
+ DISALLOW_COPY_AND_ASSIGN(RoundedImageView);
+};
+
+// The user details shown in public account mode. This is essentially a label
+// but with custom painting code as the text is styled with multiple colors and
+// contains a link.
+class PublicAccountUserDetails : public views::View,
+ public views::LinkListener {
+ public:
+ PublicAccountUserDetails(SystemTrayItem* owner, int used_width);
+ virtual ~PublicAccountUserDetails();
+
+ private:
+ // Overridden from views::View.
+ virtual void Layout() OVERRIDE;
+ virtual gfx::Size GetPreferredSize() OVERRIDE;
+ virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE;
+
+ // Overridden from views::LinkListener.
+ virtual void LinkClicked(views::Link* source, int event_flags) OVERRIDE;
+
+ // Calculate a preferred size that ensures the label text and the following
+ // link do not wrap over more than three lines in total for aesthetic reasons
+ // if possible.
+ void CalculatePreferredSize(SystemTrayItem* owner, int used_width);
+
+ base::string16 text_;
+ views::Link* learn_more_;
+ gfx::Size preferred_size_;
+ ScopedVector<gfx::RenderText> lines_;
+
+ DISALLOW_COPY_AND_ASSIGN(PublicAccountUserDetails);
+};
+
+// The button which holds the user card in case of multi profile.
+class UserCard : public views::CustomButton {
+ public:
+ UserCard(views::ButtonListener* listener, bool active_user);
+ virtual ~UserCard();
+
+ // Called when the border should remain even in the non highlighted state.
+ void ForceBorderVisible(bool show);
+
+ // Overridden from views::View
+ virtual void OnMouseEntered(const ui::MouseEvent& event) OVERRIDE;
+ virtual void OnMouseExited(const ui::MouseEvent& event) OVERRIDE;
+
+ // Check if the item is hovered.
+ bool is_hovered_for_test() {return button_hovered_; }
+
+ private:
+ // Change the hover/active state of the "button" when the status changes.
+ void ShowActive();
+
+ // True if this is the active user.
+ bool is_active_user_;
+
+ // True if button is hovered.
+ bool button_hovered_;
+
+ // True if the border should be visible.
+ bool show_border_;
+
+ DISALLOW_COPY_AND_ASSIGN(UserCard);
+};
+
+class UserViewMouseWatcherHost : public views::MouseWatcherHost {
+public:
+ explicit UserViewMouseWatcherHost(const gfx::Rect& screen_area)
+ : screen_area_(screen_area) {}
+ virtual ~UserViewMouseWatcherHost() {}
+
+ // Implementation of MouseWatcherHost.
+ virtual bool Contains(const gfx::Point& screen_point,
+ views::MouseWatcherHost::MouseEventType type) OVERRIDE {
+ return screen_area_.Contains(screen_point);
+ }
+
+private:
+ gfx::Rect screen_area_;
+
+ DISALLOW_COPY_AND_ASSIGN(UserViewMouseWatcherHost);
+};
+
+// The view of a user item.
+class UserView : public views::View,
+ public views::ButtonListener,
+ public views::MouseWatcherListener {
+ public:
+ UserView(SystemTrayItem* owner,
+ ash::user::LoginStatus login,
+ MultiProfileIndex index);
+ virtual ~UserView();
+
+ // Overridden from MouseWatcherListener:
+ virtual void MouseMovedOutOfHost() OVERRIDE;
+
+ TrayUser::TestState GetStateForTest() const;
+ gfx::Rect GetBoundsInScreenOfUserButtonForTest();
+
+ private:
+ // Overridden from views::View.
+ virtual gfx::Size GetPreferredSize() OVERRIDE;
+ virtual int GetHeightForWidth(int width) OVERRIDE;
+ virtual void Layout() OVERRIDE;
+
+ // Overridden from views::ButtonListener.
+ virtual void ButtonPressed(views::Button* sender,
+ const ui::Event& event) OVERRIDE;
+
+ void AddLogoutButton(ash::user::LoginStatus login);
+ void AddUserCard(SystemTrayItem* owner, ash::user::LoginStatus login);
+
+ // Create a user icon representation for the user card.
+ views::View* CreateIconForUserCard(ash::user::LoginStatus login);
+
+ // Create the additional user card content for the retail logged in mode.
+ void AddLoggedInRetailModeUserCardContent();
+
+ // Create the additional user card content for the public mode.
+ void AddLoggedInPublicModeUserCardContent(SystemTrayItem* owner);
+
+ // Create the menu option to add another user. If |disabled| is set the user
+ // cannot actively click on the item.
+ void ToggleAddUserMenuOption();
+
+ MultiProfileIndex multiprofile_index_;
+ // The view of the user card.
+ views::View* user_card_view_;
+
+ // This is the owner system tray item of this view.
+ SystemTrayItem* owner_;
+
+ // True if |user_card_view_| is a |UserView| - otherwise it is only a
+ // |views::View|.
+ bool is_user_card_;
+ views::View* logout_button_;
+ scoped_ptr<ash::PopupMessage> popup_message_;
+ scoped_ptr<views::Widget> add_menu_option_;
+
+ // True when the add user panel is visible but not activatable.
+ bool add_user_visible_but_disabled_;
+
+ // The mouse watcher which takes care of out of window hover events.
+ scoped_ptr<views::MouseWatcher> mouse_watcher_;
+
+ DISALLOW_COPY_AND_ASSIGN(UserView);
+};
+
+// The menu item view which gets shown when the user clicks in multi profile
+// mode onto the user item.
+class AddUserView : public views::CustomButton,
+ public views::ButtonListener {
+ public:
+ // The |owner| is the view for which this view gets created. The |listener|
+ // will get notified when this item gets clicked.
+ AddUserView(UserCard* owner, views::ButtonListener* listener);
+ virtual ~AddUserView();
+
+ // Get the anchor view for a message.
+ views::View* anchor() { return anchor_; }
+
+ // Overridden from views::ButtonListener.
+ virtual void ButtonPressed(views::Button* sender,
+ const ui::Event& event) OVERRIDE;
+
+ private:
+ // Overridden from views::View.
+ virtual gfx::Size GetPreferredSize() OVERRIDE;
+ virtual int GetHeightForWidth(int width) OVERRIDE;
+ virtual void Layout() OVERRIDE;
+
+ // Create the additional client content for this item.
+ void AddContent();
+
+ // This is the content we create and show.
+ views::View* add_user_;
+
+ // This listener will get informed when someone clicks on this button.
+ views::ButtonListener* listener_;
+
+ // This is the owner view of this item.
+ UserCard* owner_;
+
+ // The anchor view for targetted bubble messages.
+ views::View* anchor_;
+
+ DISALLOW_COPY_AND_ASSIGN(AddUserView);
+};
+
+RoundedImageView::RoundedImageView(int corner_radius, bool active_user)
+ : active_user_(active_user) {
+ for (int i = 0; i < 4; ++i)
+ corner_radius_[i] = corner_radius;
+}
+
+RoundedImageView::~RoundedImageView() {}
+
+void RoundedImageView::SetImage(const gfx::ImageSkia& img,
+ const gfx::Size& size) {
+ image_ = img;
+ image_size_ = size;
+
+ // Try to get the best image quality for the avatar.
+ resized_ = gfx::ImageSkiaOperations::CreateResizedImage(image_,
+ skia::ImageOperations::RESIZE_BEST, size);
+ if (GetWidget() && visible()) {
+ PreferredSizeChanged();
+ SchedulePaint();
+ }
+}
+
+void RoundedImageView::SetCornerRadii(int top_left,
+ int top_right,
+ int bottom_right,
+ int bottom_left) {
+ corner_radius_[0] = top_left;
+ corner_radius_[1] = top_right;
+ corner_radius_[2] = bottom_right;
+ corner_radius_[3] = bottom_left;
+}
+
+gfx::Size RoundedImageView::GetPreferredSize() {
+ return gfx::Size(image_size_.width() + GetInsets().width(),
+ image_size_.height() + GetInsets().height());
+}
+
+void RoundedImageView::OnPaint(gfx::Canvas* canvas) {
+ View::OnPaint(canvas);
+ gfx::Rect image_bounds(size());
+ image_bounds.ClampToCenteredSize(GetPreferredSize());
+ image_bounds.Inset(GetInsets());
+ const SkScalar kRadius[8] = {
+ SkIntToScalar(corner_radius_[0]),
+ SkIntToScalar(corner_radius_[0]),
+ SkIntToScalar(corner_radius_[1]),
+ SkIntToScalar(corner_radius_[1]),
+ SkIntToScalar(corner_radius_[2]),
+ SkIntToScalar(corner_radius_[2]),
+ SkIntToScalar(corner_radius_[3]),
+ SkIntToScalar(corner_radius_[3])
+ };
+ SkPath path;
+ path.addRoundRect(gfx::RectToSkRect(image_bounds), kRadius);
+ SkPaint paint;
+ paint.setAntiAlias(true);
+ paint.setXfermodeMode(active_user_ ? SkXfermode::kSrcOver_Mode :
+ SkXfermode::kLuminosity_Mode);
+ canvas->DrawImageInPath(resized_, image_bounds.x(), image_bounds.y(),
+ path, paint);
+}
+
+PublicAccountUserDetails::PublicAccountUserDetails(SystemTrayItem* owner,
+ int used_width)
+ : learn_more_(NULL) {
+ const int inner_padding =
+ kTrayPopupPaddingHorizontal - kTrayPopupPaddingBetweenItems;
+ const bool rtl = base::i18n::IsRTL();
+ set_border(views::Border::CreateEmptyBorder(
+ kUserDetailsVerticalPadding, rtl ? 0 : inner_padding,
+ kUserDetailsVerticalPadding, rtl ? inner_padding : 0));
+
+ // Retrieve the user's display name and wrap it with markers.
+ // Note that since this is a public account it always has to be the primary
+ // user.
+ base::string16 display_name =
+ ash::Shell::GetInstance()->session_state_delegate()->
+ GetUserDisplayName(0);
+ RemoveChars(display_name, kDisplayNameMark, &display_name);
+ display_name = kDisplayNameMark[0] + display_name + kDisplayNameMark[0];
+ // Retrieve the domain managing the device and wrap it with markers.
+ base::string16 domain = UTF8ToUTF16(
+ ash::Shell::GetInstance()->system_tray_delegate()->GetEnterpriseDomain());
+ RemoveChars(domain, kDisplayNameMark, &domain);
+ base::i18n::WrapStringWithLTRFormatting(&domain);
+ // Retrieve the label text, inserting the display name and domain.
+ text_ = l10n_util::GetStringFUTF16(IDS_ASH_STATUS_TRAY_PUBLIC_LABEL,
+ display_name, domain);
+
+ learn_more_ = new views::Link(l10n_util::GetStringUTF16(IDS_ASH_LEARN_MORE));
+ learn_more_->SetUnderline(false);
+ learn_more_->set_listener(this);
+ AddChildView(learn_more_);
+
+ CalculatePreferredSize(owner, used_width);
+}
+
+PublicAccountUserDetails::~PublicAccountUserDetails() {}
+
+void PublicAccountUserDetails::Layout() {
+ lines_.clear();
+ const gfx::Rect contents_area = GetContentsBounds();
+ if (contents_area.IsEmpty())
+ return;
+
+ // Word-wrap the label text.
+ const gfx::Font font;
+ std::vector<base::string16> lines;
+ ui::ElideRectangleText(text_, font, contents_area.width(),
+ contents_area.height(), ui::ELIDE_LONG_WORDS, &lines);
+ // Loop through the lines, creating a renderer for each.
+ gfx::Point position = contents_area.origin();
+ ui::Range display_name(ui::Range::InvalidRange());
+ for (std::vector<base::string16>::const_iterator it = lines.begin();
+ it != lines.end(); ++it) {
+ gfx::RenderText* line = gfx::RenderText::CreateInstance();
+ line->SetDirectionalityMode(gfx::DIRECTIONALITY_FROM_UI);
+ line->SetText(*it);
+ const gfx::Size size(contents_area.width(), line->GetStringSize().height());
+ line->SetDisplayRect(gfx::Rect(position, size));
+ position.set_y(position.y() + size.height());
+
+ // Set the default text color for the line.
+ line->SetColor(kPublicAccountUserCardTextColor);
+
+ // If a range of the line contains the user's display name, apply a custom
+ // text color to it.
+ if (display_name.is_empty())
+ display_name.set_start(it->find(kDisplayNameMark));
+ if (!display_name.is_empty()) {
+ display_name.set_end(
+ it->find(kDisplayNameMark, display_name.start() + 1));
+ ui::Range line_range(0, it->size());
+ line->ApplyColor(kPublicAccountUserCardNameColor,
+ display_name.Intersect(line_range));
+ // Update the range for the next line.
+ if (display_name.end() >= line_range.end())
+ display_name.set_start(0);
+ else
+ display_name = ui::Range::InvalidRange();
+ }
+
+ lines_.push_back(line);
+ }
+
+ // Position the link after the label text, separated by a space. If it does
+ // not fit onto the last line of the text, wrap the link onto its own line.
+ const gfx::Size last_line_size = lines_.back()->GetStringSize();
+ const int space_width = font.GetStringWidth(ASCIIToUTF16(" "));
+ const gfx::Size link_size = learn_more_->GetPreferredSize();
+ if (contents_area.width() - last_line_size.width() >=
+ space_width + link_size.width()) {
+ position.set_x(position.x() + last_line_size.width() + space_width);
+ position.set_y(position.y() - last_line_size.height());
+ }
+ position.set_y(position.y() - learn_more_->GetInsets().top());
+ gfx::Rect learn_more_bounds(position, link_size);
+ learn_more_bounds.Intersect(contents_area);
+ if (base::i18n::IsRTL()) {
+ const gfx::Insets insets = GetInsets();
+ learn_more_bounds.Offset(insets.right() - insets.left(), 0);
+ }
+ learn_more_->SetBoundsRect(learn_more_bounds);
+}
+
+gfx::Size PublicAccountUserDetails::GetPreferredSize() {
+ return preferred_size_;
+}
+
+void PublicAccountUserDetails::OnPaint(gfx::Canvas* canvas) {
+ for (ScopedVector<gfx::RenderText>::const_iterator it = lines_.begin();
+ it != lines_.end(); ++it) {
+ (*it)->Draw(canvas);
+ }
+ views::View::OnPaint(canvas);
+}
+
+void PublicAccountUserDetails::LinkClicked(views::Link* source,
+ int event_flags) {
+ DCHECK_EQ(source, learn_more_);
+ ash::Shell::GetInstance()->system_tray_delegate()->ShowPublicAccountInfo();
+}
+
+void PublicAccountUserDetails::CalculatePreferredSize(SystemTrayItem* owner,
+ int used_width) {
+ const gfx::Font font;
+ const gfx::Size link_size = learn_more_->GetPreferredSize();
+ const int space_width = font.GetStringWidth(ASCIIToUTF16(" "));
+ const gfx::Insets insets = GetInsets();
+ views::TrayBubbleView* bubble_view =
+ owner->system_tray()->GetSystemBubble()->bubble_view();
+ int min_width = std::max(
+ link_size.width(),
+ bubble_view->GetPreferredSize().width() - (used_width + insets.width()));
+ int max_width = std::min(
+ font.GetStringWidth(text_) + space_width + link_size.width(),
+ bubble_view->GetMaximumSize().width() - (used_width + insets.width()));
+ // Do a binary search for the minimum width that ensures no more than three
+ // lines are needed. The lower bound is the minimum of the current bubble
+ // width and the width of the link (as no wrapping is permitted inside the
+ // link). The upper bound is the maximum of the largest allowed bubble width
+ // and the sum of the label text and link widths when put on a single line.
+ std::vector<base::string16> lines;
+ while (min_width < max_width) {
+ lines.clear();
+ const int width = (min_width + max_width) / 2;
+ const bool too_narrow = ui::ElideRectangleText(
+ text_, font, width, INT_MAX, ui::TRUNCATE_LONG_WORDS, &lines) != 0;
+ int line_count = lines.size();
+ if (!too_narrow && line_count == 3 &&
+ width - font.GetStringWidth(lines.back()) <=
+ space_width + link_size.width()) {
+ ++line_count;
+ }
+ if (too_narrow || line_count > 3)
+ min_width = width + 1;
+ else
+ max_width = width;
+ }
+
+ // Calculate the corresponding height and set the preferred size.
+ lines.clear();
+ ui::ElideRectangleText(
+ text_, font, min_width, INT_MAX, ui::TRUNCATE_LONG_WORDS, &lines);
+ int line_count = lines.size();
+ if (min_width - font.GetStringWidth(lines.back()) <=
+ space_width + link_size.width()) {
+ ++line_count;
+ }
+ const int line_height = font.GetHeight();
+ const int link_extra_height = std::max(
+ link_size.height() - learn_more_->GetInsets().top() - line_height, 0);
+ preferred_size_ = gfx::Size(
+ min_width + insets.width(),
+ line_count * line_height + link_extra_height + insets.height());
+
+ bubble_view->SetWidth(preferred_size_.width() + used_width);
+}
+
+UserCard::UserCard(views::ButtonListener* listener, bool active_user)
+ : CustomButton(listener),
+ is_active_user_(active_user),
+ button_hovered_(false),
+ show_border_(false) {
+ if (is_active_user_) {
+ set_background(
+ views::Background::CreateSolidBackground(kBackgroundColor));
+ ShowActive();
+ }
+}
+
+UserCard::~UserCard() {}
+
+void UserCard::ForceBorderVisible(bool show) {
+ show_border_ = show;
+ ShowActive();
+}
+
+void UserCard::OnMouseEntered(const ui::MouseEvent& event) {
+ if (is_active_user_) {
+ button_hovered_ = true;
+ background()->SetNativeControlColor(kHoverBackgroundColor);
+ ShowActive();
+ }
+}
+
+void UserCard::OnMouseExited(const ui::MouseEvent& event) {
+ if (is_active_user_) {
+ button_hovered_ = false;
+ background()->SetNativeControlColor(kBackgroundColor);
+ ShowActive();
+ }
+}
+
+void UserCard::ShowActive() {
+ int width = button_hovered_ || show_border_ ? 1 : 0;
+ set_border(views::Border::CreateSolidSidedBorder(width, width, width, 1,
+ kBorderColor));
+ SchedulePaint();
+}
+
+UserView::UserView(SystemTrayItem* owner,
+ ash::user::LoginStatus login,
+ MultiProfileIndex index)
+ : multiprofile_index_(index),
+ user_card_view_(NULL),
+ owner_(owner),
+ is_user_card_(false),
+ logout_button_(NULL),
+ add_user_visible_but_disabled_(false) {
+ CHECK_NE(ash::user::LOGGED_IN_NONE, login);
+ if (!index) {
+ // Only the logged in user will have a background. All other users will have
+ // to allow the TrayPopupContainer highlighting the menu line.
+ set_background(views::Background::CreateSolidBackground(
+ login == ash::user::LOGGED_IN_PUBLIC ? kPublicAccountBackgroundColor :
+ kBackgroundColor));
+ }
+ SetLayoutManager(new views::BoxLayout(views::BoxLayout::kHorizontal, 0, 0,
+ kTrayPopupPaddingBetweenItems));
+ // The logout button must be added before the user card so that the user card
+ // can correctly calculate the remaining available width.
+ // Note that only the current multiprofile user gets a button.
+ AddLogoutButton(!multiprofile_index_ ? login : ash::user::LOGGED_IN_LOCKED);
+ AddUserCard(owner, login);
+}
+
+UserView::~UserView() {}
+
+void UserView::MouseMovedOutOfHost() {
+ popup_message_.reset();
+ mouse_watcher_.reset();
+ add_menu_option_.reset();
+}
+
+TrayUser::TestState UserView::GetStateForTest() const {
+ if (add_menu_option_.get()) {
+ return add_user_visible_but_disabled_ ? TrayUser::ACTIVE_BUT_DISABLED :
+ TrayUser::ACTIVE;
+ }
+
+ if (!is_user_card_)
+ return TrayUser::SHOWN;
+
+ return static_cast<UserCard*>(user_card_view_)->is_hovered_for_test() ?
+ TrayUser::HOVERED : TrayUser::SHOWN;
+}
+
+gfx::Rect UserView::GetBoundsInScreenOfUserButtonForTest() {
+ DCHECK(user_card_view_);
+ return user_card_view_->GetBoundsInScreen();
+}
+
+gfx::Size UserView::GetPreferredSize() {
+ gfx::Size size = views::View::GetPreferredSize();
+ // Only the active user panel will be forced to a certain height.
+ if (!multiprofile_index_) {
+ size.set_height(std::max(size.height(),
+ kTrayPopupItemHeight + GetInsets().height()));
+ }
+ return size;
+}
+
+int UserView::GetHeightForWidth(int width) {
+ return GetPreferredSize().height();
+}
+
+void UserView::Layout() {
+ gfx::Rect contents_area(GetContentsBounds());
+ if (user_card_view_ && logout_button_) {
+ // Give the logout button the space it requests.
+ gfx::Rect logout_area = contents_area;
+ logout_area.ClampToCenteredSize(logout_button_->GetPreferredSize());
+ logout_area.set_x(contents_area.right() - logout_area.width());
+
+ // Give the remaining space to the user card.
+ gfx::Rect user_card_area = contents_area;
+ int remaining_width = contents_area.width() - logout_area.width();
+ if (ash::Shell::GetInstance()->delegate()->IsMultiProfilesEnabled()) {
+ // In multiprofile case |user_card_view_| and |logout_button_| have to
+ // have the same height.
+ int y = std::min(user_card_area.y(), logout_area.y());
+ int height = std::max(user_card_area.height(), logout_area.height());
+ logout_area.set_y(y);
+ logout_area.set_height(height);
+ user_card_area.set_y(y);
+ user_card_area.set_height(height);
+
+ // In multiprofile mode we have also to increase the size of the card by
+ // the size of the border to make it overlap with the logout button.
+ user_card_area.set_width(std::max(0, remaining_width + 1));
+
+ // To make the logout button symmetrical with the user card we also make
+ // the button longer by the same size the hover area in front of the icon
+ // got inset.
+ logout_area.set_width(logout_area.width() +
+ kTrayUserTileHoverBorderInset);
+ } else {
+ // In all other modes we have to make sure that there is enough spacing
+ // between the two.
+ remaining_width -= kTrayPopupPaddingBetweenItems;
+ }
+ user_card_area.set_width(remaining_width);
+ user_card_view_->SetBoundsRect(user_card_area);
+ logout_button_->SetBoundsRect(logout_area);
+ } else if (user_card_view_) {
+ user_card_view_->SetBoundsRect(contents_area);
+ } else if (logout_button_) {
+ logout_button_->SetBoundsRect(contents_area);
+ }
+}
+
+void UserView::ButtonPressed(views::Button* sender, const ui::Event& event) {
+ if (sender == logout_button_) {
+ ash::Shell::GetInstance()->system_tray_delegate()->SignOut();
+ } else if (sender == user_card_view_ &&
+ ash::Shell::GetInstance()->delegate()->IsMultiProfilesEnabled()) {
+ if (!multiprofile_index_) {
+ ToggleAddUserMenuOption();
+ } else {
+ ash::SessionStateDelegate* delegate =
+ ash::Shell::GetInstance()->session_state_delegate();
+ delegate->SwitchActiveUser(delegate->GetUserEmail(multiprofile_index_));
+ // Since the user list is about to change the system menu should get
+ // closed.
+ owner_->system_tray()->CloseSystemBubble();
+ }
+ } else if (add_menu_option_.get() &&
+ sender == add_menu_option_->GetContentsView()) {
+ // Let the user add another account to the session.
+ ash::Shell::GetInstance()->system_tray_delegate()->ShowUserLogin();
+ } else {
+ NOTREACHED();
+ }
+}
+
+void UserView::AddLogoutButton(ash::user::LoginStatus login) {
+ // A user should not be able to modify logged-in state when screen is
+ // locked.
+ if (login == ash::user::LOGGED_IN_LOCKED)
+ return;
+
+ const base::string16 title = ash::user::GetLocalizedSignOutStringForStatus(
+ login, true);
+ TrayPopupLabelButton* logout_button = new TrayPopupLabelButton(this, title);
+ logout_button->SetAccessibleName(title);
+ logout_button_ = logout_button;
+ // In public account mode, the logout button border has a custom color.
+ if (login == ash::user::LOGGED_IN_PUBLIC) {
+ TrayPopupLabelButtonBorder* border =
+ static_cast<TrayPopupLabelButtonBorder*>(logout_button_->border());
+ border->SetPainter(false, views::Button::STATE_NORMAL,
+ views::Painter::CreateImageGridPainter(
+ kPublicAccountLogoutButtonBorderImagesNormal));
+ border->SetPainter(false, views::Button::STATE_HOVERED,
+ views::Painter::CreateImageGridPainter(
+ kPublicAccountLogoutButtonBorderImagesHovered));
+ border->SetPainter(false, views::Button::STATE_PRESSED,
+ views::Painter::CreateImageGridPainter(
+ kPublicAccountLogoutButtonBorderImagesHovered));
+ }
+ AddChildView(logout_button_);
+}
+
+void UserView::AddUserCard(SystemTrayItem* owner,
+ ash::user::LoginStatus login) {
+ // Add padding around the panel.
+ set_border(views::Border::CreateEmptyBorder(
+ kUserCardVerticalPadding, kTrayPopupPaddingHorizontal,
+ kUserCardVerticalPadding, kTrayPopupPaddingHorizontal));
+
+ if (ash::Shell::GetInstance()->delegate()->IsMultiProfilesEnabled() &&
+ login != ash::user::LOGGED_IN_RETAIL_MODE) {
+ user_card_view_ = new UserCard(this, multiprofile_index_ == 0);
+ is_user_card_ = true;
+ } else {
+ user_card_view_ = new views::View();
+ is_user_card_ = false;
+ }
+
+ user_card_view_->SetLayoutManager(new views::BoxLayout(
+ views::BoxLayout::kHorizontal, 0, 0 , kTrayPopupPaddingBetweenItems));
+ AddChildViewAt(user_card_view_, 0);
+
+ if (login == ash::user::LOGGED_IN_RETAIL_MODE) {
+ AddLoggedInRetailModeUserCardContent();
+ return;
+ }
+
+ // The entire user card should trigger hover (the inner items get disabled).
+ user_card_view_->SetEnabled(true);
+ user_card_view_->set_notify_enter_exit_on_child(true);
+
+ if (login == ash::user::LOGGED_IN_PUBLIC) {
+ AddLoggedInPublicModeUserCardContent(owner);
+ return;
+ }
+
+ views::View* icon = CreateIconForUserCard(login);
+ user_card_view_->AddChildView(icon);
+
+ // To allow the border to start before the icon, reduce the size before and
+ // add an inset to the icon to get the spacing.
+ if (multiprofile_index_ == 0 &&
+ ash::Shell::GetInstance()->delegate()->IsMultiProfilesEnabled()) {
+ icon->set_border(views::Border::CreateEmptyBorder(
+ 0, kTrayUserTileHoverBorderInset, 0, 0));
+ set_border(views::Border::CreateEmptyBorder(
+ kUserCardVerticalPadding,
+ kTrayPopupPaddingHorizontal - kTrayUserTileHoverBorderInset,
+ kUserCardVerticalPadding,
+ kTrayPopupPaddingHorizontal));
+ }
+ ash::SessionStateDelegate* delegate =
+ ash::Shell::GetInstance()->session_state_delegate();
+ views::Label* username = NULL;
+ ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance();
+ if (!multiprofile_index_) {
+ base::string16 user_name_string =
+ login == ash::user::LOGGED_IN_GUEST ?
+ bundle.GetLocalizedString(IDS_ASH_STATUS_TRAY_GUEST_LABEL) :
+ delegate->GetUserDisplayName(multiprofile_index_);
+ if (!user_name_string.empty()) {
+ username = new views::Label(user_name_string);
+ username->SetHorizontalAlignment(gfx::ALIGN_LEFT);
+ }
+ }
+
+ views::Label* additional = NULL;
+ if (login != ash::user::LOGGED_IN_GUEST) {
+ base::string16 user_email_string =
+ login == ash::user::LOGGED_IN_LOCALLY_MANAGED ?
+ bundle.GetLocalizedString(
+ IDS_ASH_STATUS_TRAY_LOCALLY_MANAGED_LABEL) :
+ UTF8ToUTF16(delegate->GetUserEmail(multiprofile_index_));
+ if (!user_email_string.empty()) {
+ additional = new views::Label(user_email_string);
+ additional->SetFont(bundle.GetFont(ui::ResourceBundle::SmallFont));
+ additional->SetHorizontalAlignment(gfx::ALIGN_LEFT);
+ }
+ }
+
+ // Adjust text properties dependent on if it is an active or inactive user.
+ if (multiprofile_index_) {
+ // Fade the text of non active users to 50%.
+ SkColor text_color = additional->enabled_color();
+ text_color = SkColorSetA(text_color, SkColorGetA(text_color) / 2);
+ if (additional)
+ additional->SetDisabledColor(text_color);
+ if (username)
+ username->SetDisabledColor(text_color);
+ }
+
+ if (additional && username) {
+ views::View* details = new views::View;
+ details->SetLayoutManager(new views::BoxLayout(
+ views::BoxLayout::kVertical, 0, kUserDetailsVerticalPadding, 0));
+ details->AddChildView(username);
+ details->AddChildView(additional);
+ user_card_view_->AddChildView(details);
+ } else {
+ if (username)
+ user_card_view_->AddChildView(username);
+ if (additional)
+ user_card_view_->AddChildView(additional);
+ }
+}
+
+views::View* UserView::CreateIconForUserCard(ash::user::LoginStatus login) {
+ RoundedImageView* icon = new RoundedImageView(kProfileRoundedCornerRadius,
+ multiprofile_index_ == 0);
+ icon->SetEnabled(false);
+ if (login == ash::user::LOGGED_IN_GUEST) {
+ icon->SetImage(*ui::ResourceBundle::GetSharedInstance().
+ GetImageNamed(IDR_AURA_UBER_TRAY_GUEST_ICON).ToImageSkia(),
+ gfx::Size(kUserIconSize, kUserIconSize));
+ } else {
+ icon->SetImage(
+ ash::Shell::GetInstance()->session_state_delegate()->
+ GetUserImage(multiprofile_index_),
+ gfx::Size(kUserIconSize, kUserIconSize));
+ }
+ return icon;
+}
+
+void UserView::AddLoggedInRetailModeUserCardContent() {
+ views::Label* details = new views::Label;
+ ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance();
+ details->SetText(
+ bundle.GetLocalizedString(IDS_ASH_STATUS_TRAY_KIOSK_LABEL));
+ details->set_border(views::Border::CreateEmptyBorder(0, 4, 0, 1));
+ details->SetHorizontalAlignment(gfx::ALIGN_LEFT);
+ user_card_view_->AddChildView(details);
+}
+
+void UserView::AddLoggedInPublicModeUserCardContent(SystemTrayItem* owner) {
+ user_card_view_->AddChildView(
+ CreateIconForUserCard(ash::user::LOGGED_IN_PUBLIC));
+ user_card_view_->AddChildView(new PublicAccountUserDetails(
+ owner, GetPreferredSize().width() + kTrayPopupPaddingBetweenItems));
+}
+
+void UserView::ToggleAddUserMenuOption() {
+ if (add_menu_option_.get()) {
+ popup_message_.reset();
+ mouse_watcher_.reset();
+ add_menu_option_.reset();
+ return;
+ }
+
+ // Note: We do not need to install a global event handler to delete this
+ // item since it will destroyed automatically before the menu / user menu item
+ // gets destroyed..
+ const SessionStateDelegate* session_state_delegate =
+ ash::Shell::GetInstance()->session_state_delegate();
+ add_user_visible_but_disabled_ =
+ session_state_delegate->NumberOfLoggedInUsers() >=
+ session_state_delegate->GetMaximumNumberOfLoggedInUsers();
+ add_menu_option_.reset(new views::Widget);
+ views::Widget::InitParams params;
+ params.type = views::Widget::InitParams::TYPE_TOOLTIP;
+ params.keep_on_top = true;
+ params.context = this->GetWidget()->GetNativeWindow();
+ params.accept_events = true;
+ params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
+ params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW;
+ add_menu_option_->Init(params);
+ add_menu_option_->SetOpacity(0xFF);
+ add_menu_option_->GetNativeWindow()->set_owned_by_parent(false);
+ SetShadowType(add_menu_option_->GetNativeView(),
+ views::corewm::SHADOW_TYPE_NONE);
+
+ // Position it below our user card.
+ gfx::Rect bounds = user_card_view_->GetBoundsInScreen();
+ bounds.set_y(bounds.y() + bounds.height());
+ add_menu_option_->SetBounds(bounds);
+
+ // Show the content.
+ AddUserView* add_user_view = new AddUserView(
+ static_cast<UserCard*>(user_card_view_), this);
+ add_menu_option_->SetContentsView(add_user_view);
+ add_menu_option_->SetAlwaysOnTop(true);
+ add_menu_option_->Show();
+ if (add_user_visible_but_disabled_) {
+ ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance();
+ popup_message_.reset(new PopupMessage(
+ bundle.GetLocalizedString(IDS_ASH_STATUS_TRAY_CAPTION_CANNOT_ADD_USER),
+ bundle.GetLocalizedString(IDS_ASH_STATUS_TRAY_MESSAGE_CANNOT_ADD_USER),
+ PopupMessage::ICON_WARNING,
+ add_user_view->anchor(),
+ views::BubbleBorder::TOP_LEFT,
+ gfx::Size(parent()->bounds().width() - kPopupMessageOffset, 0),
+ 2 * kPopupMessageOffset));
+ }
+ // Find the screen area which encloses both elements and sets then a mouse
+ // watcher which will close the "menu".
+ gfx::Rect area = user_card_view_->GetBoundsInScreen();
+ area.set_height(2 * area.height());
+ mouse_watcher_.reset(new views::MouseWatcher(
+ new UserViewMouseWatcherHost(area),
+ this));
+ mouse_watcher_->Start();
+}
+
+AddUserView::AddUserView(UserCard* owner, views::ButtonListener* listener)
+ : CustomButton(listener_),
+ add_user_(NULL),
+ listener_(listener),
+ owner_(owner),
+ anchor_(NULL) {
+ AddContent();
+ owner_->ForceBorderVisible(true);
+}
+
+AddUserView::~AddUserView() {
+ owner_->ForceBorderVisible(false);
+}
+
+gfx::Size AddUserView::GetPreferredSize() {
+ return owner_->bounds().size();
+}
+
+int AddUserView::GetHeightForWidth(int width) {
+ return owner_->bounds().size().height();
+}
+
+void AddUserView::Layout() {
+ gfx::Rect contents_area(GetContentsBounds());
+ add_user_->SetBoundsRect(contents_area);
+}
+
+void AddUserView::ButtonPressed(views::Button* sender, const ui::Event& event) {
+ if (add_user_ == sender)
+ listener_->ButtonPressed(this, event);
+ else
+ NOTREACHED();
+}
+
+void AddUserView::AddContent() {
+ set_notify_enter_exit_on_child(true);
+
+ const SessionStateDelegate* delegate =
+ ash::Shell::GetInstance()->session_state_delegate();
+ bool enable = delegate->NumberOfLoggedInUsers() <
+ delegate->GetMaximumNumberOfLoggedInUsers();
+
+ SetLayoutManager(new views::FillLayout());
+ set_background(views::Background::CreateSolidBackground(kBackgroundColor));
+
+ // Add padding around the panel.
+ set_border(views::Border::CreateSolidBorder(1, kBorderColor));
+
+ add_user_ = new UserCard(this, enable);
+ add_user_->set_border(views::Border::CreateEmptyBorder(
+ kUserCardVerticalPadding,
+ kTrayPopupPaddingHorizontal- kTrayUserTileHoverBorderInset,
+ kUserCardVerticalPadding,
+ kTrayPopupPaddingHorizontal- kTrayUserTileHoverBorderInset));
+
+ add_user_->SetLayoutManager(new views::BoxLayout(
+ views::BoxLayout::kHorizontal, 0, 0 , kTrayPopupPaddingBetweenItems));
+ AddChildViewAt(add_user_, 0);
+
+ // Add the [+] icon which is also the anchor for messages.
+ ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance();
+ RoundedImageView* icon = new RoundedImageView(kProfileRoundedCornerRadius,
+ true);
+ anchor_ = icon;
+ icon->SetImage(*ui::ResourceBundle::GetSharedInstance().
+ GetImageNamed(IDR_AURA_UBER_TRAY_ADD_MULTIPROFILE_USER).ToImageSkia(),
+ gfx::Size(kUserIconSize, kUserIconSize));
+ add_user_->AddChildView(icon);
+
+ // Add the command text.
+ views::Label* command_label = new views::Label(
+ bundle.GetLocalizedString(IDS_ASH_STATUS_TRAY_SIGN_IN_ANOTHER_ACCOUNT));
+ command_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
+ add_user_->AddChildView(command_label);
+}
+
+} // namespace tray
+
+TrayUser::TrayUser(SystemTray* system_tray, MultiProfileIndex index)
+ : SystemTrayItem(system_tray),
+ multiprofile_index_(index),
+ user_(NULL),
+ layout_view_(NULL),
+ avatar_(NULL),
+ label_(NULL),
+ separator_shown_(false) {
+ Shell::GetInstance()->system_tray_notifier()->AddUserObserver(this);
+}
+
+TrayUser::~TrayUser() {
+ Shell::GetInstance()->system_tray_notifier()->RemoveUserObserver(this);
+}
+
+TrayUser::TestState TrayUser::GetStateForTest() const {
+ if (separator_shown_)
+ return SEPARATOR;
+ if (!user_)
+ return HIDDEN;
+ return user_->GetStateForTest();
+}
+
+gfx::Rect TrayUser::GetUserPanelBoundsInScreenForTest() const {
+ DCHECK(user_);
+ return user_->GetBoundsInScreenOfUserButtonForTest();
+}
+
+views::View* TrayUser::CreateTrayView(user::LoginStatus status) {
+ CHECK(layout_view_ == NULL);
+ // Only the current user gets an icon. All other users will only be
+ // represented in the tray menu.
+ if (multiprofile_index_)
+ return NULL;
+
+ layout_view_ = new views::View();
+ layout_view_->SetLayoutManager(
+ new views::BoxLayout(views::BoxLayout::kHorizontal,
+ 0, 0, kUserLabelToIconPadding));
+ UpdateAfterLoginStatusChange(status);
+ return layout_view_;
+}
+
+views::View* TrayUser::CreateDefaultView(user::LoginStatus status) {
+ if (status == user::LOGGED_IN_NONE)
+ return NULL;
+
+ CHECK(user_ == NULL);
+
+ const SessionStateDelegate* session_state_delegate =
+ ash::Shell::GetInstance()->session_state_delegate();
+ int logged_in_users = session_state_delegate->NumberOfLoggedInUsers();
+
+ // If there are multiple users logged in, the users will be separated from the
+ // rest of the menu by a separator.
+ if (multiprofile_index_ ==
+ session_state_delegate->GetMaximumNumberOfLoggedInUsers() &&
+ logged_in_users > 1) {
+ separator_shown_ = true;
+ return new views::View();
+ }
+
+ // Do not show more UserView's then there are logged in users.
+ if (multiprofile_index_ >= logged_in_users)
+ return NULL;
+
+ user_ = new tray::UserView(this, status, multiprofile_index_);
+ return user_;
+}
+
+views::View* TrayUser::CreateDetailedView(user::LoginStatus status) {
+ return NULL;
+}
+
+void TrayUser::DestroyTrayView() {
+ layout_view_ = NULL;
+ avatar_ = NULL;
+ label_ = NULL;
+ separator_shown_ = false;
+}
+
+void TrayUser::DestroyDefaultView() {
+ user_ = NULL;
+}
+
+void TrayUser::DestroyDetailedView() {
+}
+
+void TrayUser::UpdateAfterLoginStatusChange(user::LoginStatus status) {
+ // Only the active user is represented in the tray.
+ if (!layout_view_)
+ return;
+ bool need_label = false;
+ bool need_avatar = false;
+ switch (status) {
+ case user::LOGGED_IN_LOCKED:
+ case user::LOGGED_IN_USER:
+ case user::LOGGED_IN_OWNER:
+ case user::LOGGED_IN_PUBLIC:
+ need_avatar = true;
+ break;
+ case user::LOGGED_IN_LOCALLY_MANAGED:
+ need_avatar = true;
+ need_label = true;
+ break;
+ case user::LOGGED_IN_GUEST:
+ need_label = true;
+ break;
+ case user::LOGGED_IN_RETAIL_MODE:
+ case user::LOGGED_IN_KIOSK_APP:
+ case user::LOGGED_IN_NONE:
+ break;
+ }
+
+ if ((need_avatar != (avatar_ != NULL)) ||
+ (need_label != (label_ != NULL))) {
+ layout_view_->RemoveAllChildViews(true);
+ if (need_label) {
+ label_ = new views::Label;
+ SetupLabelForTray(label_);
+ layout_view_->AddChildView(label_);
+ } else {
+ label_ = NULL;
+ }
+ if (need_avatar) {
+ avatar_ = new tray::RoundedImageView(kProfileRoundedCornerRadius, true);
+ layout_view_->AddChildView(avatar_);
+ } else {
+ avatar_ = NULL;
+ }
+ }
+
+ ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance();
+ if (status == user::LOGGED_IN_LOCALLY_MANAGED) {
+ label_->SetText(
+ bundle.GetLocalizedString(IDS_ASH_STATUS_TRAY_LOCALLY_MANAGED_LABEL));
+ } else if (status == user::LOGGED_IN_GUEST) {
+ label_->SetText(bundle.GetLocalizedString(IDS_ASH_STATUS_TRAY_GUEST_LABEL));
+ }
+
+ if (avatar_ && ash::switches::UseAlternateShelfLayout()) {
+ avatar_->SetCornerRadii(0,
+ kUserIconLargeCornerRadius,
+ kUserIconLargeCornerRadius,
+ 0);
+ avatar_->set_border(NULL);
+ }
+ UpdateAvatarImage(status);
+
+ // Update layout after setting label_ and avatar_ with new login status.
+ if (Shell::GetPrimaryRootWindowController()->shelf())
+ UpdateAfterShelfAlignmentChange(
+ Shell::GetPrimaryRootWindowController()->GetShelfLayoutManager()->
+ GetAlignment());
+}
+
+void TrayUser::UpdateAfterShelfAlignmentChange(ShelfAlignment alignment) {
+ // Inactive users won't have a layout.
+ if (!layout_view_)
+ return;
+ if (alignment == SHELF_ALIGNMENT_BOTTOM ||
+ alignment == SHELF_ALIGNMENT_TOP) {
+ if (avatar_) {
+ if (ash::switches::UseAlternateShelfLayout()) {
+ avatar_->set_border(NULL);
+ avatar_->SetCornerRadii(0,
+ kUserIconLargeCornerRadius,
+ kUserIconLargeCornerRadius,
+ 0);
+ } else {
+ avatar_->set_border(views::Border::CreateEmptyBorder(
+ 0, kTrayImageItemHorizontalPaddingBottomAlignment + 2,
+ 0, kTrayImageItemHorizontalPaddingBottomAlignment));
+ }
+ }
+ if (label_) {
+ label_->set_border(views::Border::CreateEmptyBorder(
+ 0, kTrayLabelItemHorizontalPaddingBottomAlignment,
+ 0, kTrayLabelItemHorizontalPaddingBottomAlignment));
+ }
+ layout_view_->SetLayoutManager(
+ new views::BoxLayout(views::BoxLayout::kHorizontal,
+ 0, 0, kUserLabelToIconPadding));
+ } else {
+ if (avatar_) {
+ if (ash::switches::UseAlternateShelfLayout()) {
+ avatar_->set_border(NULL);
+ avatar_->SetCornerRadii(0,
+ 0,
+ kUserIconLargeCornerRadius,
+ kUserIconLargeCornerRadius);
+ } else {
+ SetTrayImageItemBorder(avatar_, alignment);
+ }
+ }
+ if (label_) {
+ label_->set_border(views::Border::CreateEmptyBorder(
+ kTrayLabelItemVerticalPaddingVeriticalAlignment,
+ kTrayLabelItemHorizontalPaddingBottomAlignment,
+ kTrayLabelItemVerticalPaddingVeriticalAlignment,
+ kTrayLabelItemHorizontalPaddingBottomAlignment));
+ }
+ layout_view_->SetLayoutManager(
+ new views::BoxLayout(views::BoxLayout::kVertical,
+ 0, 0, kUserLabelToIconPadding));
+ }
+}
+
+void TrayUser::OnUserUpdate() {
+ UpdateAvatarImage(Shell::GetInstance()->system_tray_delegate()->
+ GetUserLoginStatus());
+}
+
+void TrayUser::UpdateAvatarImage(user::LoginStatus status) {
+ if (!avatar_)
+ return;
+
+ int icon_size = ash::switches::UseAlternateShelfLayout() ?
+ kUserIconLargeSize : kUserIconSize;
+
+ avatar_->SetImage(
+ ash::Shell::GetInstance()->session_state_delegate()->GetUserImage(
+ multiprofile_index_),
+ gfx::Size(icon_size, icon_size));
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/system/user/tray_user.h b/chromium/ash/system/user/tray_user.h
new file mode 100644
index 00000000000..70b54bb9c4a
--- /dev/null
+++ b/chromium/ash/system/user/tray_user.h
@@ -0,0 +1,92 @@
+// Copyright (c) 2012 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 ASH_SYSTEM_USER_TRAY_USER_H_
+#define ASH_SYSTEM_USER_TRAY_USER_H_
+
+#include "ash/ash_export.h"
+#include "ash/session_state_delegate.h"
+#include "ash/system/tray/system_tray_item.h"
+#include "ash/system/user/user_observer.h"
+#include "base/compiler_specific.h"
+
+namespace gfx {
+class Rect;
+}
+
+namespace views {
+class ImageView;
+class Label;
+}
+
+namespace ash {
+namespace internal {
+
+namespace tray {
+class UserView;
+class RoundedImageView;
+}
+
+class ASH_EXPORT TrayUser : public SystemTrayItem,
+ public UserObserver {
+ public:
+ // The given |multiprofile_index| is the number of the user in a multi profile
+ // scenario. Index #0 is the running user, the other indices are other
+ // logged in users (if there are any). Only index #0 will add an icon to
+ // the system tray.
+ TrayUser(SystemTray* system_tray, MultiProfileIndex index);
+ virtual ~TrayUser();
+
+ // Allows unit tests to see if the item was created.
+ enum TestState {
+ HIDDEN, // The item is hidden.
+ SEPARATOR, // the item gets shown as a separator.
+ SHOWN, // The item gets presented to the user.
+ HOVERED, // The item is hovered and presented to the user.
+ ACTIVE, // The item was clicked and can add a user.
+ ACTIVE_BUT_DISABLED // The item was clicked anc cannot add a user.
+ };
+ TestState GetStateForTest() const;
+
+ // Returns the bounds of the user panel in screen coordinates.
+ // Note: This only works when the panel shown.
+ gfx::Rect GetUserPanelBoundsInScreenForTest() const;
+
+ private:
+ // Overridden from SystemTrayItem.
+ virtual views::View* CreateTrayView(user::LoginStatus status) OVERRIDE;
+ virtual views::View* CreateDefaultView(user::LoginStatus status) OVERRIDE;
+ virtual views::View* CreateDetailedView(user::LoginStatus status) OVERRIDE;
+ virtual void DestroyTrayView() OVERRIDE;
+ virtual void DestroyDefaultView() OVERRIDE;
+ virtual void DestroyDetailedView() OVERRIDE;
+ virtual void UpdateAfterLoginStatusChange(user::LoginStatus status) OVERRIDE;
+ virtual void UpdateAfterShelfAlignmentChange(
+ ShelfAlignment alignment) OVERRIDE;
+
+ // Overridden from UserObserver.
+ virtual void OnUserUpdate() OVERRIDE;
+
+ void UpdateAvatarImage(user::LoginStatus status);
+
+ // The user index to use.
+ MultiProfileIndex multiprofile_index_;
+
+ tray::UserView* user_;
+
+ // View that contains label and/or avatar.
+ views::View* layout_view_;
+ tray::RoundedImageView* avatar_;
+ views::Label* label_;
+
+ // True if this element is the separator and it is shown.
+ bool separator_shown_;
+
+ DISALLOW_COPY_AND_ASSIGN(TrayUser);
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_SYSTEM_USER_TRAY_USER_H_
diff --git a/chromium/ash/system/user/tray_user_unittest.cc b/chromium/ash/system/user/tray_user_unittest.cc
new file mode 100644
index 00000000000..7d7b066f438
--- /dev/null
+++ b/chromium/ash/system/user/tray_user_unittest.cc
@@ -0,0 +1,233 @@
+// Copyright 2013 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.
+
+#include <vector>
+
+#include "ash/root_window_controller.h"
+#include "ash/shelf/shelf_layout_manager.h"
+#include "ash/shell.h"
+#include "ash/shell_delegate.h"
+#include "ash/system/tray/system_tray.h"
+#include "ash/system/user/tray_user.h"
+#include "ash/test/ash_test_base.h"
+#include "ash/test/test_session_state_delegate.h"
+#include "ash/test/test_shell_delegate.h"
+#include "ui/aura/test/event_generator.h"
+#include "ui/base/animation/animation_container_element.h"
+#include "ui/views/view.h"
+#include "ui/views/widget/widget.h"
+
+namespace ash {
+namespace internal {
+
+class TrayUserTest : public ash::test::AshTestBase {
+ public:
+ TrayUserTest();
+
+ // testing::Test:
+ virtual void SetUp() OVERRIDE;
+
+ // This has to be called prior to first use with the proper configuration.
+ void InitializeParameters(int users_logged_in, bool multiprofile);
+
+ // Show the system tray menu using the provided event generator.
+ void ShowTrayMenu(aura::test::EventGenerator* generator);
+
+ // Move the mouse over the user item.
+ void MoveOverUserItem(aura::test::EventGenerator* generator, int index);
+
+ // Click on the user item. Note that the tray menu needs to be shown.
+ void ClickUserItem(aura::test::EventGenerator* generator, int index);
+
+ // Accessors to various system components.
+ ShelfLayoutManager* shelf() { return shelf_; }
+ SystemTray* tray() { return tray_; }
+ ash::test::TestSessionStateDelegate* delegate() { return delegate_; }
+ ash::internal::TrayUser* tray_user(int index) { return tray_user_[index]; }
+
+ private:
+ ShelfLayoutManager* shelf_;
+ SystemTray* tray_;
+ ash::test::TestSessionStateDelegate* delegate_;
+ // Note that the ownership of these items is on the shelf.
+ std::vector<ash::internal::TrayUser*> tray_user_;
+
+ DISALLOW_COPY_AND_ASSIGN(TrayUserTest);
+};
+
+TrayUserTest::TrayUserTest()
+ : shelf_(NULL),
+ tray_(NULL),
+ delegate_(NULL) {
+}
+
+void TrayUserTest::SetUp() {
+ ash::test::AshTestBase::SetUp();
+ shelf_ = Shell::GetPrimaryRootWindowController()->GetShelfLayoutManager();
+ tray_ = Shell::GetPrimaryRootWindowController()->GetSystemTray();
+ delegate_ = static_cast<ash::test::TestSessionStateDelegate*>(
+ ash::Shell::GetInstance()->session_state_delegate());
+}
+
+void TrayUserTest::InitializeParameters(int users_logged_in,
+ bool multiprofile) {
+ // Show the shelf.
+ shelf()->LayoutShelf();
+ shelf()->SetAutoHideBehavior(SHELF_AUTO_HIDE_BEHAVIOR_NEVER);
+
+ // Set our default assumptions. Note that it is sufficient to set these
+ // after everything was created.
+ delegate_->set_logged_in_users(users_logged_in);
+ ash::test::TestShellDelegate* shell_delegate =
+ static_cast<ash::test::TestShellDelegate*>(
+ ash::Shell::GetInstance()->delegate());
+ shell_delegate->set_multi_profiles_enabled(multiprofile);
+
+ // Instead of using the existing tray panels we create new ones which makes
+ // the access easier.
+ // Note that we create one more element then there can be users to show a
+ // separator.
+ for (int i = 0; i <= delegate_->GetMaximumNumberOfLoggedInUsers(); i++) {
+ tray_user_.push_back(new ash::internal::TrayUser(tray_, i));
+ tray_->AddTrayItem(tray_user_[i]);
+ }
+}
+
+void TrayUserTest::ShowTrayMenu(aura::test::EventGenerator* generator) {
+ gfx::Point center = tray()->GetBoundsInScreen().CenterPoint();
+
+ generator->MoveMouseTo(center.x(), center.y());
+ EXPECT_FALSE(tray()->IsAnyBubbleVisible());
+ generator->ClickLeftButton();
+}
+
+void TrayUserTest::MoveOverUserItem(aura::test::EventGenerator* generator,
+ int index) {
+ gfx::Point center =
+ tray_user(index)->GetUserPanelBoundsInScreenForTest().CenterPoint();
+
+ generator->MoveMouseTo(center.x(), center.y());
+}
+
+void TrayUserTest::ClickUserItem(aura::test::EventGenerator* generator,
+ int index) {
+ MoveOverUserItem(generator, index);
+ generator->ClickLeftButton();
+}
+
+// Make sure that in single user mode the user panel cannot be activated and no
+// separators are being created.
+TEST_F(TrayUserTest, SingleUserModeDoesNotAllowAddingUser) {
+ InitializeParameters(1, false);
+
+ // Move the mouse over the status area and click to open the status menu.
+ aura::test::EventGenerator generator(Shell::GetPrimaryRootWindow());
+
+ EXPECT_FALSE(tray()->IsAnyBubbleVisible());
+
+ for (int i = 0; i <= delegate()->GetMaximumNumberOfLoggedInUsers(); i++)
+ EXPECT_EQ(ash::internal::TrayUser::HIDDEN,
+ tray_user(i)->GetStateForTest());
+
+ ShowTrayMenu(&generator);
+
+ EXPECT_TRUE(tray()->HasSystemBubble());
+ EXPECT_TRUE(tray()->IsAnyBubbleVisible());
+
+ for (int i = 0; i <= delegate()->GetMaximumNumberOfLoggedInUsers(); i++)
+ EXPECT_EQ(i == 0 ? ash::internal::TrayUser::SHOWN :
+ ash::internal::TrayUser::HIDDEN,
+ tray_user(i)->GetStateForTest());
+
+ tray()->CloseSystemBubble();
+}
+
+// Make sure that in multi user mode the user panel can be activated and there
+// will be one panel for each user plus a separator.
+// Note: the mouse watcher (for automatic closing upon leave) cannot be tested
+// here since it does not work with the event system in unit tests.
+TEST_F(TrayUserTest, MutiUserModeDoesNotAllowToAddUser) {
+ InitializeParameters(1, true);
+
+ // Move the mouse over the status area and click to open the status menu.
+ aura::test::EventGenerator generator(Shell::GetPrimaryRootWindow());
+ generator.set_async(false);
+
+ int max_users = delegate()->GetMaximumNumberOfLoggedInUsers();
+ // Checking now for each amount of users that the correct is done.
+ for (int j = 1; j < max_users; j++) {
+ // Set the number of logged in users.
+ delegate()->set_logged_in_users(j);
+
+ // Verify that nothing is shown.
+ EXPECT_FALSE(tray()->IsAnyBubbleVisible());
+ for (int i = 0; i <= max_users; i++)
+ EXPECT_FALSE(tray_user(i)->GetStateForTest());
+
+ // After clicking on the tray the menu should get shown and for each logged
+ // in user we should get a visible item. In addition, the separator should
+ // show up when we reach more then one user.
+ ShowTrayMenu(&generator);
+
+ EXPECT_TRUE(tray()->HasSystemBubble());
+ EXPECT_TRUE(tray()->IsAnyBubbleVisible());
+ for (int i = 0; i < max_users; i++) {
+ EXPECT_EQ(i < j ? ash::internal::TrayUser::SHOWN :
+ ash::internal::TrayUser::HIDDEN,
+ tray_user(i)->GetStateForTest());
+ }
+
+ // Check the visibility of the separator.
+ EXPECT_EQ(j > 1 ? ash::internal::TrayUser::SEPARATOR :
+ ash::internal::TrayUser::HIDDEN,
+ tray_user(max_users)->GetStateForTest());
+
+ // Move the mouse over the user item and it should hover.
+ MoveOverUserItem(&generator, 0);
+ EXPECT_EQ(ash::internal::TrayUser::HOVERED,
+ tray_user(0)->GetStateForTest());
+ for (int i = 1; i < max_users; i++) {
+ EXPECT_EQ(i < j ? ash::internal::TrayUser::SHOWN :
+ ash::internal::TrayUser::HIDDEN,
+ tray_user(i)->GetStateForTest());
+ }
+
+ // Check that clicking the button allows to add item if we have still room
+ // for one more user.
+ ClickUserItem(&generator, 0);
+ EXPECT_EQ(j == max_users ?
+ ash::internal::TrayUser::ACTIVE_BUT_DISABLED :
+ ash::internal::TrayUser::ACTIVE,
+ tray_user(0)->GetStateForTest());
+
+ // Click the button again to see that the menu goes away.
+ ClickUserItem(&generator, 0);
+ EXPECT_EQ(ash::internal::TrayUser::HOVERED,
+ tray_user(0)->GetStateForTest());
+
+ // Close and check that everything is deleted.
+ tray()->CloseSystemBubble();
+ EXPECT_FALSE(tray()->IsAnyBubbleVisible());
+ for (int i = 0; i < delegate()->GetMaximumNumberOfLoggedInUsers(); i++)
+ EXPECT_EQ(ash::internal::TrayUser::HIDDEN,
+ tray_user(i)->GetStateForTest());
+ }
+}
+
+// Make sure that user changing gets properly executed.
+TEST_F(TrayUserTest, MutiUserModeButtonClicks) {
+ // Have two users.
+ InitializeParameters(2, true);
+ aura::test::EventGenerator generator(Shell::GetPrimaryRootWindow());
+ ShowTrayMenu(&generator);
+
+ // Switch to a new user.
+ ClickUserItem(&generator, 1);
+
+ EXPECT_EQ(delegate()->get_activated_user(), delegate()->GetUserEmail(1));
+ tray()->CloseSystemBubble();
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/system/user/update_observer.h b/chromium/ash/system/user/update_observer.h
new file mode 100644
index 00000000000..e509f62d3cb
--- /dev/null
+++ b/chromium/ash/system/user/update_observer.h
@@ -0,0 +1,28 @@
+// Copyright (c) 2012 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 ASH_SYSTEM_USER_UPDATE_OBSERVER_H_
+#define ASH_SYSTEM_USER_UPDATE_OBSERVER_H_
+
+#include "ash/ash_export.h"
+
+namespace ash {
+
+class ASH_EXPORT UpdateObserver {
+ public:
+ enum UpdateSeverity {
+ UPDATE_NORMAL,
+ UPDATE_LOW_GREEN,
+ UPDATE_HIGH_ORANGE,
+ UPDATE_SEVERE_RED,
+ };
+
+ virtual ~UpdateObserver() {}
+
+ virtual void OnUpdateRecommended(UpdateSeverity severity) = 0;
+};
+
+} // namespace ash
+
+#endif //ASH_SYSTEM_USER_UPDATE_OBSERVER_H_
diff --git a/chromium/ash/system/user/user_observer.h b/chromium/ash/system/user/user_observer.h
new file mode 100644
index 00000000000..2968d95b39c
--- /dev/null
+++ b/chromium/ash/system/user/user_observer.h
@@ -0,0 +1,21 @@
+// Copyright (c) 2012 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 ASH_SYSTEM_USER_USER_OBSERVER_H_
+#define ASH_SYSTEM_USER_USER_OBSERVER_H_
+
+#include "ash/ash_export.h"
+
+namespace ash {
+
+class ASH_EXPORT UserObserver {
+ public:
+ virtual ~UserObserver() {}
+
+ virtual void OnUserUpdate() = 0;
+};
+
+} // namespace ash
+
+#endif // ASH_SYSTEM_USER_USER_OBSERVER_H_
diff --git a/chromium/ash/system/web_notification/web_notification_tray.cc b/chromium/ash/system/web_notification/web_notification_tray.cc
new file mode 100644
index 00000000000..e6bd4925e3e
--- /dev/null
+++ b/chromium/ash/system/web_notification/web_notification_tray.cc
@@ -0,0 +1,599 @@
+// Copyright (c) 2012 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.
+
+#include "ash/system/web_notification/web_notification_tray.h"
+
+#include "ash/ash_switches.h"
+#include "ash/root_window_controller.h"
+#include "ash/shelf/shelf_layout_manager.h"
+#include "ash/shelf/shelf_layout_manager_observer.h"
+#include "ash/shelf/shelf_widget.h"
+#include "ash/shell.h"
+#include "ash/shell_window_ids.h"
+#include "ash/system/status_area_widget.h"
+#include "ash/system/tray/system_tray.h"
+#include "ash/system/tray/tray_background_view.h"
+#include "ash/system/tray/tray_bubble_wrapper.h"
+#include "ash/system/tray/tray_constants.h"
+#include "ash/system/tray/tray_utils.h"
+#include "base/auto_reset.h"
+#include "base/i18n/number_formatting.h"
+#include "base/i18n/rtl.h"
+#include "base/strings/utf_string_conversions.h"
+#include "grit/ash_strings.h"
+#include "grit/ui_strings.h"
+#include "ui/aura/root_window.h"
+#include "ui/aura/window.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/gfx/screen.h"
+#include "ui/message_center/message_center_style.h"
+#include "ui/message_center/message_center_tray_delegate.h"
+#include "ui/message_center/message_center_util.h"
+#include "ui/message_center/views/message_bubble_base.h"
+#include "ui/message_center/views/message_center_bubble.h"
+#include "ui/message_center/views/message_popup_collection.h"
+#include "ui/views/bubble/tray_bubble_view.h"
+#include "ui/views/controls/button/custom_button.h"
+#include "ui/views/controls/image_view.h"
+#include "ui/views/controls/label.h"
+#include "ui/views/controls/menu/menu_runner.h"
+#include "ui/views/layout/fill_layout.h"
+
+#if defined(OS_CHROMEOS)
+
+namespace message_center {
+
+MessageCenterTrayDelegate* CreateMessageCenterTray() {
+ // On Windows+Ash the Tray will not be hosted in ash::Shell.
+ NOTREACHED();
+ return NULL;
+}
+
+} // namespace message_center
+
+#endif // defined(OS_CHROMEOS)
+
+namespace ash {
+namespace internal {
+namespace {
+
+const SkColor kWebNotificationColorNoUnread = SkColorSetA(SK_ColorWHITE, 128);
+const SkColor kWebNotificationColorWithUnread = SK_ColorWHITE;
+
+}
+
+// Observes the change of work area (including temporary change by auto-hide)
+// and notifies MessagePopupCollection.
+class WorkAreaObserver : public ShelfLayoutManagerObserver,
+ public ShellObserver {
+ public:
+ WorkAreaObserver(message_center::MessagePopupCollection* collection,
+ ShelfLayoutManager* shelf);
+ virtual ~WorkAreaObserver();
+
+ void SetSystemTrayHeight(int height);
+
+ // Overridden from ShellObserver:
+ virtual void OnDisplayWorkAreaInsetsChanged() OVERRIDE;
+
+ // Overridden from ShelfLayoutManagerObserver:
+ virtual void OnAutoHideStateChanged(ShelfAutoHideState new_state) OVERRIDE;
+
+ private:
+ message_center::MessagePopupCollection* collection_;
+ ShelfLayoutManager* shelf_;
+ int system_tray_height_;
+
+ DISALLOW_COPY_AND_ASSIGN(WorkAreaObserver);
+};
+
+WorkAreaObserver::WorkAreaObserver(
+ message_center::MessagePopupCollection* collection,
+ ShelfLayoutManager* shelf)
+ : collection_(collection),
+ shelf_(shelf),
+ system_tray_height_(0) {
+ DCHECK(collection_);
+ shelf_->AddObserver(this);
+ Shell::GetInstance()->AddShellObserver(this);
+}
+
+WorkAreaObserver::~WorkAreaObserver() {
+ Shell::GetInstance()->RemoveShellObserver(this);
+ shelf_->RemoveObserver(this);
+}
+
+void WorkAreaObserver::SetSystemTrayHeight(int height) {
+ system_tray_height_ = height;
+
+ // If the shelf is shown during auto-hide state, the distance from the edge
+ // should be reduced by the height of shelf's shown height.
+ if (shelf_->visibility_state() == SHELF_AUTO_HIDE &&
+ shelf_->auto_hide_state() == SHELF_AUTO_HIDE_SHOWN) {
+ system_tray_height_ -= ShelfLayoutManager::GetPreferredShelfSize() -
+ ShelfLayoutManager::kAutoHideSize;
+ }
+
+ if (system_tray_height_ > 0 && ash::switches::UseAlternateShelfLayout())
+ system_tray_height_ += message_center::kMarginBetweenItems;
+
+ OnAutoHideStateChanged(shelf_->auto_hide_state());
+}
+
+void WorkAreaObserver::OnDisplayWorkAreaInsetsChanged() {
+ collection_->OnDisplayBoundsChanged(
+ Shell::GetScreen()->GetDisplayNearestWindow(
+ shelf_->shelf_widget()->GetNativeView()));
+}
+
+void WorkAreaObserver::OnAutoHideStateChanged(ShelfAutoHideState new_state) {
+ gfx::Display display = Shell::GetScreen()->GetDisplayNearestWindow(
+ shelf_->shelf_widget()->GetNativeView());
+ gfx::Rect work_area = display.work_area();
+ int width = 0;
+ if ((shelf_->visibility_state() == SHELF_AUTO_HIDE) &&
+ new_state == SHELF_AUTO_HIDE_SHOWN) {
+ // Since the work_area is already reduced by kAutoHideSize, the inset width
+ // should be just the difference.
+ width = ShelfLayoutManager::GetPreferredShelfSize() -
+ ShelfLayoutManager::kAutoHideSize;
+ }
+ work_area.Inset(shelf_->SelectValueForShelfAlignment(
+ gfx::Insets(0, 0, width, 0),
+ gfx::Insets(0, width, 0, 0),
+ gfx::Insets(0, 0, 0, width),
+ gfx::Insets(width, 0, 0, 0)));
+ if (system_tray_height_ > 0) {
+ work_area.set_height(
+ std::max(0, work_area.height() - system_tray_height_));
+ if (shelf_->GetAlignment() == SHELF_ALIGNMENT_TOP)
+ work_area.set_y(work_area.y() + system_tray_height_);
+ }
+ collection_->SetDisplayInfo(work_area, display.bounds());
+}
+
+// Class to initialize and manage the WebNotificationBubble and
+// TrayBubbleWrapper instances for a bubble.
+class WebNotificationBubbleWrapper {
+ public:
+ // Takes ownership of |bubble| and creates |bubble_wrapper_|.
+ WebNotificationBubbleWrapper(WebNotificationTray* tray,
+ message_center::MessageBubbleBase* bubble) {
+ bubble_.reset(bubble);
+ views::TrayBubbleView::AnchorAlignment anchor_alignment =
+ tray->GetAnchorAlignment();
+ views::TrayBubbleView::InitParams init_params =
+ bubble->GetInitParams(anchor_alignment);
+ views::View* anchor = tray->tray_container();
+ if (anchor_alignment == views::TrayBubbleView::ANCHOR_ALIGNMENT_BOTTOM) {
+ gfx::Point bounds(anchor->width() / 2, 0);
+ views::View::ConvertPointToWidget(anchor, &bounds);
+ init_params.arrow_offset = bounds.x();
+ }
+ views::TrayBubbleView* bubble_view = views::TrayBubbleView::Create(
+ tray->GetBubbleWindowContainer(), anchor, tray, &init_params);
+ if (ash::switches::UseAlternateShelfLayout())
+ bubble_view->SetArrowPaintType(views::BubbleBorder::PAINT_NONE);
+ bubble_wrapper_.reset(new TrayBubbleWrapper(tray, bubble_view));
+ bubble->InitializeContents(bubble_view);
+ }
+
+ message_center::MessageBubbleBase* bubble() const { return bubble_.get(); }
+
+ // Convenience accessors.
+ views::TrayBubbleView* bubble_view() const { return bubble_->bubble_view(); }
+
+ private:
+ scoped_ptr<message_center::MessageBubbleBase> bubble_;
+ scoped_ptr<internal::TrayBubbleWrapper> bubble_wrapper_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebNotificationBubbleWrapper);
+};
+
+class WebNotificationButton : public views::CustomButton {
+ public:
+ WebNotificationButton(views::ButtonListener* listener)
+ : views::CustomButton(listener),
+ is_bubble_visible_(false),
+ unread_count_(0) {
+ SetLayoutManager(new views::FillLayout);
+ unread_label_ = new views::Label();
+ SetupLabelForTray(unread_label_);
+ AddChildView(unread_label_);
+ }
+
+ void SetBubbleVisible(bool visible) {
+ if (visible == is_bubble_visible_)
+ return;
+
+ is_bubble_visible_ = visible;
+ UpdateIconVisibility();
+ }
+
+ void SetUnreadCount(int unread_count) {
+ // base::FormatNumber doesn't convert to arabic numeric characters.
+ // TODO(mukai): use ICU to support conversion for such locales.
+ unread_count_ = unread_count;
+ // TODO(mukai): move NINE_PLUS message to ui_strings, it doesn't need to be
+ // in ash_strings.
+ unread_label_->SetText((unread_count > 9) ?
+ l10n_util::GetStringUTF16(IDS_ASH_NOTIFICATION_UNREAD_COUNT_NINE_PLUS) :
+ base::FormatNumber(unread_count));
+ UpdateIconVisibility();
+ }
+
+ protected:
+ // Overridden from views::ImageButton:
+ virtual gfx::Size GetPreferredSize() OVERRIDE {
+ const int notification_item_size = GetShelfItemHeight();
+ return gfx::Size(notification_item_size, notification_item_size);
+ }
+
+ virtual int GetHeightForWidth(int width) OVERRIDE {
+ return GetPreferredSize().height();
+ }
+
+ private:
+ void UpdateIconVisibility() {
+ unread_label_->SetEnabledColor(
+ (!is_bubble_visible_ && unread_count_ > 0) ?
+ kWebNotificationColorWithUnread : kWebNotificationColorNoUnread);
+ SchedulePaint();
+ }
+
+ bool is_bubble_visible_;
+ int unread_count_;
+
+ views::Label* unread_label_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebNotificationButton);
+};
+
+} // namespace internal
+
+WebNotificationTray::WebNotificationTray(
+ internal::StatusAreaWidget* status_area_widget)
+ : TrayBackgroundView(status_area_widget),
+ button_(NULL),
+ show_message_center_on_unlock_(false),
+ should_update_tray_content_(false),
+ should_block_shelf_auto_hide_(false) {
+ button_ = new internal::WebNotificationButton(this);
+ button_->set_triggerable_event_flags(
+ ui::EF_LEFT_MOUSE_BUTTON | ui::EF_RIGHT_MOUSE_BUTTON);
+ tray_container()->AddChildView(button_);
+ SetContentsBackground();
+ tray_container()->set_border(NULL);
+ SetVisible(false);
+ message_center_tray_.reset(new message_center::MessageCenterTray(
+ this,
+ message_center::MessageCenter::Get()));
+ OnMessageCenterTrayChanged();
+}
+
+WebNotificationTray::~WebNotificationTray() {
+ // Release any child views that might have back pointers before ~View().
+ message_center_bubble_.reset();
+ popup_collection_.reset();
+ work_area_observer_.reset();
+}
+
+// Public methods.
+
+bool WebNotificationTray::ShowMessageCenterInternal(bool show_settings) {
+ if (!ShouldShowMessageCenter())
+ return false;
+
+ should_block_shelf_auto_hide_ = true;
+ message_center::MessageCenterBubble* message_center_bubble =
+ new message_center::MessageCenterBubble(
+ message_center(),
+ message_center_tray_.get(),
+ ash::switches::UseAlternateShelfLayout());
+
+ int max_height = 0;
+ aura::Window* status_area_window = status_area_widget()->GetNativeView();
+ switch (GetShelfLayoutManager()->GetAlignment()) {
+ case SHELF_ALIGNMENT_BOTTOM: {
+ gfx::Rect shelf_bounds = GetShelfLayoutManager()->GetIdealBounds();
+ max_height = shelf_bounds.y();
+ break;
+ }
+ case SHELF_ALIGNMENT_TOP: {
+ aura::RootWindow* root = status_area_window->GetRootWindow();
+ max_height =
+ root->bounds().height() - status_area_window->bounds().height();
+ break;
+ }
+ case SHELF_ALIGNMENT_LEFT:
+ case SHELF_ALIGNMENT_RIGHT: {
+ // Assume that the bottom line of the status area widget and the bubble
+ // are aligned.
+ max_height = status_area_window->GetBoundsInRootWindow().bottom();
+ break;
+ }
+ default:
+ NOTREACHED();
+ }
+
+ message_center_bubble->SetMaxHeight(std::max(0,
+ max_height - GetTraySpacing()));
+ if (show_settings)
+ message_center_bubble->SetSettingsVisible();
+ message_center_bubble_.reset(
+ new internal::WebNotificationBubbleWrapper(this, message_center_bubble));
+
+ status_area_widget()->SetHideSystemNotifications(true);
+ GetShelfLayoutManager()->UpdateAutoHideState();
+ button_->SetBubbleVisible(true);
+ return true;
+}
+
+bool WebNotificationTray::ShowMessageCenter() {
+ return ShowMessageCenterInternal(false /* show_settings */);
+}
+
+void WebNotificationTray::HideMessageCenter() {
+ if (!message_center_bubble())
+ return;
+ message_center_bubble_.reset();
+ should_block_shelf_auto_hide_ = false;
+ show_message_center_on_unlock_ = false;
+ status_area_widget()->SetHideSystemNotifications(false);
+ GetShelfLayoutManager()->UpdateAutoHideState();
+ button_->SetBubbleVisible(false);
+}
+
+void WebNotificationTray::SetSystemTrayHeight(int height) {
+ if (!work_area_observer_)
+ return;
+ work_area_observer_->SetSystemTrayHeight(height);
+}
+
+bool WebNotificationTray::ShowPopups() {
+ if (status_area_widget()->login_status() == user::LOGGED_IN_LOCKED ||
+ message_center_bubble() ||
+ !status_area_widget()->ShouldShowWebNotifications()) {
+ return false;
+ }
+
+ popup_collection_.reset(new message_center::MessagePopupCollection(
+ ash::Shell::GetContainer(
+ GetWidget()->GetNativeView()->GetRootWindow(),
+ internal::kShellWindowId_StatusContainer),
+ message_center(),
+ message_center_tray_.get(),
+ ash::switches::UseAlternateShelfLayout()));
+ work_area_observer_.reset(new internal::WorkAreaObserver(
+ popup_collection_.get(), GetShelfLayoutManager()));
+ return true;
+}
+
+void WebNotificationTray::HidePopups() {
+ popup_collection_.reset();
+ work_area_observer_.reset();
+}
+
+// Private methods.
+
+bool WebNotificationTray::ShouldShowMessageCenter() {
+ return status_area_widget()->login_status() != user::LOGGED_IN_LOCKED &&
+ !(status_area_widget()->system_tray() &&
+ status_area_widget()->system_tray()->HasNotificationBubble());
+}
+
+void WebNotificationTray::ShowQuietModeMenu(const ui::Event& event) {
+ base::AutoReset<bool> reset(&should_block_shelf_auto_hide_, true);
+ scoped_ptr<ui::MenuModel> menu_model(
+ message_center_tray_->CreateQuietModeMenu());
+ quiet_mode_menu_runner_.reset(new views::MenuRunner(menu_model.get()));
+ gfx::Point point;
+ views::View::ConvertPointToScreen(this, &point);
+ if (quiet_mode_menu_runner_->RunMenuAt(
+ GetWidget(),
+ NULL,
+ gfx::Rect(point, bounds().size()),
+ views::MenuItemView::BUBBLE_ABOVE,
+ ui::GetMenuSourceTypeForEvent(event),
+ views::MenuRunner::HAS_MNEMONICS) == views::MenuRunner::MENU_DELETED)
+ return;
+
+ quiet_mode_menu_runner_.reset();
+ GetShelfLayoutManager()->UpdateAutoHideState();
+}
+
+bool WebNotificationTray::ShouldShowQuietModeMenu(const ui::Event& event) {
+ // TODO(mukai): Add keyboard event handler.
+ if (!event.IsMouseEvent())
+ return false;
+
+ const ui::MouseEvent* mouse_event =
+ static_cast<const ui::MouseEvent*>(&event);
+
+ return mouse_event->IsRightMouseButton();
+}
+
+void WebNotificationTray::UpdateAfterLoginStatusChange(
+ user::LoginStatus login_status) {
+ if (login_status == user::LOGGED_IN_LOCKED) {
+ show_message_center_on_unlock_ =
+ message_center_tray_->HideMessageCenterBubble();
+ message_center_tray_->HidePopupBubble();
+ } else {
+ // Only try once to show the message center bubble on login status change,
+ // so always set |show_message_center_on_unlock_| to false.
+ if (show_message_center_on_unlock_)
+ message_center_tray_->ShowMessageCenterBubble();
+ show_message_center_on_unlock_ = false;
+ }
+ OnMessageCenterTrayChanged();
+}
+
+bool WebNotificationTray::ShouldBlockLauncherAutoHide() const {
+ return should_block_shelf_auto_hide_;
+}
+
+bool WebNotificationTray::IsMessageCenterBubbleVisible() const {
+ return (message_center_bubble() &&
+ message_center_bubble()->bubble()->IsVisible());
+}
+
+bool WebNotificationTray::IsMouseInNotificationBubble() const {
+ return false;
+}
+
+void WebNotificationTray::ShowMessageCenterBubble() {
+ if (!IsMessageCenterBubbleVisible())
+ message_center_tray_->ShowMessageCenterBubble();
+}
+
+void WebNotificationTray::SetShelfAlignment(ShelfAlignment alignment) {
+ if (alignment == shelf_alignment())
+ return;
+ internal::TrayBackgroundView::SetShelfAlignment(alignment);
+ tray_container()->set_border(NULL);
+ // Destroy any existing bubble so that it will be rebuilt correctly.
+ message_center_tray_->HideMessageCenterBubble();
+ message_center_tray_->HidePopupBubble();
+}
+
+void WebNotificationTray::AnchorUpdated() {
+ if (message_center_bubble()) {
+ message_center_bubble()->bubble_view()->UpdateBubble();
+ UpdateBubbleViewArrow(message_center_bubble()->bubble_view());
+ }
+}
+
+base::string16 WebNotificationTray::GetAccessibleNameForTray() {
+ return l10n_util::GetStringUTF16(
+ IDS_MESSAGE_CENTER_ACCESSIBLE_NAME);
+}
+
+void WebNotificationTray::HideBubbleWithView(
+ const views::TrayBubbleView* bubble_view) {
+ if (message_center_bubble() &&
+ bubble_view == message_center_bubble()->bubble_view()) {
+ message_center_tray_->HideMessageCenterBubble();
+ } else if (popup_collection_.get()) {
+ message_center_tray_->HidePopupBubble();
+ }
+}
+
+bool WebNotificationTray::PerformAction(const ui::Event& event) {
+ if (ShouldShowQuietModeMenu(event)) {
+ ShowQuietModeMenu(event);
+ return true;
+ }
+
+ if (message_center_bubble())
+ message_center_tray_->HideMessageCenterBubble();
+ else
+ message_center_tray_->ShowMessageCenterBubble();
+ return true;
+}
+
+void WebNotificationTray::BubbleViewDestroyed() {
+ if (message_center_bubble())
+ message_center_bubble()->bubble()->BubbleViewDestroyed();
+}
+
+void WebNotificationTray::OnMouseEnteredView() {}
+
+void WebNotificationTray::OnMouseExitedView() {}
+
+base::string16 WebNotificationTray::GetAccessibleNameForBubble() {
+ return GetAccessibleNameForTray();
+}
+
+gfx::Rect WebNotificationTray::GetAnchorRect(
+ views::Widget* anchor_widget,
+ views::TrayBubbleView::AnchorType anchor_type,
+ views::TrayBubbleView::AnchorAlignment anchor_alignment) {
+ return GetBubbleAnchorRect(anchor_widget, anchor_type, anchor_alignment);
+}
+
+void WebNotificationTray::HideBubble(const views::TrayBubbleView* bubble_view) {
+ HideBubbleWithView(bubble_view);
+}
+
+bool WebNotificationTray::ShowNotifierSettings() {
+ if (message_center_bubble()) {
+ static_cast<message_center::MessageCenterBubble*>(
+ message_center_bubble()->bubble())->SetSettingsVisible();
+ return true;
+ }
+ return ShowMessageCenterInternal(true /* show_settings */);
+}
+
+message_center::MessageCenterTray* WebNotificationTray::GetMessageCenterTray() {
+ return message_center_tray_.get();
+}
+
+bool WebNotificationTray::IsPressed() {
+ return IsMessageCenterBubbleVisible();
+}
+
+void WebNotificationTray::ButtonPressed(views::Button* sender,
+ const ui::Event& event) {
+ DCHECK_EQ(button_, sender);
+ PerformAction(event);
+}
+
+void WebNotificationTray::OnMessageCenterTrayChanged() {
+ // Do not update the tray contents directly. Multiple change events can happen
+ // consecutively, and calling Update in the middle of those events will show
+ // intermediate unread counts for a moment.
+ should_update_tray_content_ = true;
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&WebNotificationTray::UpdateTrayContent, AsWeakPtr()));
+}
+
+void WebNotificationTray::UpdateTrayContent() {
+ if (!should_update_tray_content_)
+ return;
+ should_update_tray_content_ = false;
+
+ message_center::MessageCenter* message_center =
+ message_center_tray_->message_center();
+ button_->SetUnreadCount(message_center->UnreadNotificationCount());
+ if (IsMessageCenterBubbleVisible())
+ button_->SetState(views::CustomButton::STATE_PRESSED);
+ else
+ button_->SetState(views::CustomButton::STATE_NORMAL);
+ SetVisible((status_area_widget()->login_status() != user::LOGGED_IN_NONE) &&
+ (status_area_widget()->login_status() != user::LOGGED_IN_LOCKED) &&
+ (message_center->NotificationCount() > 0));
+ Layout();
+ SchedulePaint();
+}
+
+bool WebNotificationTray::ClickedOutsideBubble() {
+ // Only hide the message center
+ if (!message_center_bubble())
+ return false;
+
+ message_center_tray_->HideMessageCenterBubble();
+ return true;
+}
+
+message_center::MessageCenter* WebNotificationTray::message_center() {
+ return message_center_tray_->message_center();
+}
+
+// Methods for testing
+
+bool WebNotificationTray::IsPopupVisible() const {
+ return message_center_tray_->popups_visible();
+}
+
+message_center::MessageCenterBubble*
+WebNotificationTray::GetMessageCenterBubbleForTest() {
+ if (!message_center_bubble())
+ return NULL;
+ return static_cast<message_center::MessageCenterBubble*>(
+ message_center_bubble()->bubble());
+}
+
+} // namespace ash
diff --git a/chromium/ash/system/web_notification/web_notification_tray.h b/chromium/ash/system/web_notification/web_notification_tray.h
new file mode 100644
index 00000000000..1fb9c4d3fbc
--- /dev/null
+++ b/chromium/ash/system/web_notification/web_notification_tray.h
@@ -0,0 +1,178 @@
+// Copyright (c) 2012 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 ASH_SYSTEM_WEB_NOTIFICATION_WEB_NOTIFICATION_TRAY_H_
+#define ASH_SYSTEM_WEB_NOTIFICATION_WEB_NOTIFICATION_TRAY_H_
+
+#include "ash/ash_export.h"
+#include "ash/system/tray/tray_background_view.h"
+#include "ash/system/user/login_status.h"
+#include "base/gtest_prod_util.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "ui/message_center/message_center_tray.h"
+#include "ui/message_center/message_center_tray_delegate.h"
+#include "ui/views/bubble/tray_bubble_view.h"
+#include "ui/views/controls/button/button.h"
+
+// Status area tray for showing browser and app notifications. This hosts
+// a MessageCenter class which manages the notification list. This class
+// contains the Ash specific tray implementation.
+//
+// Note: These are not related to system notifications (i.e NotificationView
+// generated by SystemTrayItem). Visibility of one notification type or other
+// is controlled by StatusAreaWidget.
+
+namespace views {
+class ImageButton;
+class MenuRunner;
+}
+
+namespace message_center {
+class MessageBubbleBase;
+class MessageCenter;
+class MessageCenterBubble;
+class MessagePopupCollection;
+}
+
+namespace ash {
+namespace internal {
+class StatusAreaWidget;
+class WebNotificationBubbleWrapper;
+class WebNotificationButton;
+class WorkAreaObserver;
+}
+
+class ASH_EXPORT WebNotificationTray
+ : public internal::TrayBackgroundView,
+ public views::TrayBubbleView::Delegate,
+ public message_center::MessageCenterTrayDelegate,
+ public views::ButtonListener,
+ public base::SupportsWeakPtr<WebNotificationTray> {
+ public:
+ explicit WebNotificationTray(
+ internal::StatusAreaWidget* status_area_widget);
+ virtual ~WebNotificationTray();
+
+ // Sets the height of the system tray from the edge of the work area so that
+ // the notification popups don't overlap with the tray. Passes 0 if no UI is
+ // shown in the system tray side.
+ void SetSystemTrayHeight(int height);
+
+ // Updates tray visibility login status of the system changes.
+ void UpdateAfterLoginStatusChange(user::LoginStatus login_status);
+
+ // Returns true if it should block the auto hide behavior of the launcher.
+ bool ShouldBlockLauncherAutoHide() const;
+
+ // Returns true if the message center bubble is visible.
+ bool IsMessageCenterBubbleVisible() const;
+
+ // Returns true if the mouse is inside the notification bubble.
+ bool IsMouseInNotificationBubble() const;
+
+ // Shows the message center bubble.
+ void ShowMessageCenterBubble();
+
+ // Overridden from TrayBackgroundView.
+ virtual void SetShelfAlignment(ShelfAlignment alignment) OVERRIDE;
+ virtual void AnchorUpdated() OVERRIDE;
+ virtual base::string16 GetAccessibleNameForTray() OVERRIDE;
+ virtual void HideBubbleWithView(
+ const views::TrayBubbleView* bubble_view) OVERRIDE;
+ virtual bool ClickedOutsideBubble() OVERRIDE;
+
+ // Overridden from internal::ActionableView.
+ virtual bool PerformAction(const ui::Event& event) OVERRIDE;
+
+ // Overridden from views::TrayBubbleView::Delegate.
+ virtual void BubbleViewDestroyed() OVERRIDE;
+ virtual void OnMouseEnteredView() OVERRIDE;
+ virtual void OnMouseExitedView() OVERRIDE;
+ virtual base::string16 GetAccessibleNameForBubble() OVERRIDE;
+ virtual gfx::Rect GetAnchorRect(views::Widget* anchor_widget,
+ AnchorType anchor_type,
+ AnchorAlignment anchor_alignment) OVERRIDE;
+ virtual void HideBubble(const views::TrayBubbleView* bubble_view) OVERRIDE;
+
+ // Overridden from ButtonListener.
+ virtual void ButtonPressed(views::Button* sender,
+ const ui::Event& event) OVERRIDE;
+
+ // Overridden from MessageCenterTrayDelegate.
+ virtual void OnMessageCenterTrayChanged() OVERRIDE;
+ virtual bool ShowMessageCenter() OVERRIDE;
+ virtual void HideMessageCenter() OVERRIDE;
+ virtual bool ShowPopups() OVERRIDE;
+ virtual void HidePopups() OVERRIDE;
+ virtual bool ShowNotifierSettings() OVERRIDE;
+ virtual message_center::MessageCenterTray* GetMessageCenterTray() OVERRIDE;
+
+ // Overridden from TrayBackgroundView.
+ virtual bool IsPressed() OVERRIDE;
+
+ message_center::MessageCenter* message_center();
+
+ private:
+ friend class WebNotificationTrayTest;
+
+ FRIEND_TEST_ALL_PREFIXES(WebNotificationTrayTest, WebNotifications);
+ FRIEND_TEST_ALL_PREFIXES(WebNotificationTrayTest, WebNotificationPopupBubble);
+ FRIEND_TEST_ALL_PREFIXES(WebNotificationTrayTest,
+ ManyMessageCenterNotifications);
+ FRIEND_TEST_ALL_PREFIXES(WebNotificationTrayTest, ManyPopupNotifications);
+ FRIEND_TEST_ALL_PREFIXES(WebNotificationTrayTest, PopupShownOnBothDisplays);
+ FRIEND_TEST_ALL_PREFIXES(WebNotificationTrayTest, PopupAndSystemTray);
+ FRIEND_TEST_ALL_PREFIXES(WebNotificationTrayTest, PopupAndAutoHideShelf);
+
+ void UpdateTrayContent();
+
+ // The actual process to show the message center. Set |show_settings| to true
+ // if the message center should be initialized with the settings visible.
+ // Returns true if the center is successfully created.
+ bool ShowMessageCenterInternal(bool show_settings);
+
+ // Queries login status and the status area widget to determine visibility of
+ // the message center.
+ bool ShouldShowMessageCenter();
+
+ // Returns true if it should show the quiet mode menu.
+ bool ShouldShowQuietModeMenu(const ui::Event& event);
+
+ // Shows the quiet mode menu.
+ void ShowQuietModeMenu(const ui::Event& event);
+
+ internal::WebNotificationBubbleWrapper* message_center_bubble() const {
+ return message_center_bubble_.get();
+ }
+
+ // Testing accessors.
+ bool IsPopupVisible() const;
+ message_center::MessageCenterBubble* GetMessageCenterBubbleForTest();
+
+ scoped_ptr<message_center::MessageCenterTray> message_center_tray_;
+ scoped_ptr<internal::WebNotificationBubbleWrapper> message_center_bubble_;
+ scoped_ptr<message_center::MessagePopupCollection> popup_collection_;
+ scoped_ptr<views::MenuRunner> quiet_mode_menu_runner_;
+ internal::WebNotificationButton* button_;
+
+ bool show_message_center_on_unlock_;
+
+ bool should_update_tray_content_;
+
+ // True when the shelf auto hide behavior has to be blocked. Previously
+ // this was done by checking |message_center_bubble_| but actually
+ // the check can be called when creating this object, so it would cause
+ // flickers of the shelf from hidden to shown. See: crbug.com/181213
+ bool should_block_shelf_auto_hide_;
+
+ // Observes the work area for |popup_collection_| and notifies to it.
+ scoped_ptr<internal::WorkAreaObserver> work_area_observer_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebNotificationTray);
+};
+
+} // namespace ash
+
+#endif // ASH_SYSTEM_WEB_NOTIFICATION_WEB_NOTIFICATION_TRAY_H_
diff --git a/chromium/ash/system/web_notification/web_notification_tray_unittest.cc b/chromium/ash/system/web_notification/web_notification_tray_unittest.cc
new file mode 100644
index 00000000000..5b67b7d72b4
--- /dev/null
+++ b/chromium/ash/system/web_notification/web_notification_tray_unittest.cc
@@ -0,0 +1,475 @@
+// Copyright (c) 2012 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.
+
+#include "ash/system/web_notification/web_notification_tray.h"
+
+#include <vector>
+
+#include "ash/display/display_manager.h"
+#include "ash/root_window_controller.h"
+#include "ash/shelf/shelf_layout_manager.h"
+#include "ash/shelf/shelf_widget.h"
+#include "ash/shell.h"
+#include "ash/system/status_area_widget.h"
+#include "ash/system/tray/system_tray.h"
+#include "ash/system/tray/system_tray_item.h"
+#include "ash/system/tray/test_system_tray_delegate.h"
+#include "ash/test/ash_test_base.h"
+#include "ash/wm/window_properties.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "ui/aura/client/aura_constants.h"
+#include "ui/aura/test/event_generator.h"
+#include "ui/aura/window.h"
+#include "ui/gfx/display.h"
+#include "ui/gfx/screen.h"
+#include "ui/message_center/message_center_style.h"
+#include "ui/message_center/message_center_tray.h"
+#include "ui/message_center/message_center_util.h"
+#include "ui/message_center/notification_list.h"
+#include "ui/message_center/notification_types.h"
+#include "ui/message_center/views/message_center_bubble.h"
+#include "ui/message_center/views/message_popup_collection.h"
+#include "ui/views/controls/label.h"
+#include "ui/views/layout/fill_layout.h"
+#include "ui/views/view.h"
+#include "ui/views/widget/widget.h"
+
+namespace ash {
+
+namespace {
+
+WebNotificationTray* GetTray() {
+ return Shell::GetPrimaryRootWindowController()->shelf()->
+ status_area_widget()->web_notification_tray();
+}
+
+WebNotificationTray* GetSecondaryTray() {
+ internal::RootWindowController* primary_controller =
+ Shell::GetPrimaryRootWindowController();
+ Shell::RootWindowControllerList controllers =
+ Shell::GetAllRootWindowControllers();
+ for (size_t i = 0; i < controllers.size(); ++i) {
+ if (controllers[i] != primary_controller) {
+ return controllers[i]->shelf()->
+ status_area_widget()->web_notification_tray();
+ }
+ }
+
+ return NULL;
+}
+
+message_center::MessageCenter* GetMessageCenter() {
+ return GetTray()->message_center();
+}
+
+SystemTray* GetSystemTray() {
+ return Shell::GetPrimaryRootWindowController()->shelf()->
+ status_area_widget()->system_tray();
+}
+
+// Trivial item implementation for testing PopupAndSystemTray test case.
+class TestItem : public SystemTrayItem {
+ public:
+ TestItem() : SystemTrayItem(GetSystemTray()) {}
+
+ virtual views::View* CreateDefaultView(user::LoginStatus status) OVERRIDE {
+ views::View* default_view = new views::View;
+ default_view->SetLayoutManager(new views::FillLayout);
+ default_view->AddChildView(new views::Label(UTF8ToUTF16("Default")));
+ return default_view;
+ }
+
+ virtual views::View* CreateNotificationView(
+ user::LoginStatus status) OVERRIDE {
+ return new views::View;
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(TestItem);
+};
+
+} // namespace
+
+class WebNotificationTrayTest : public test::AshTestBase {
+ public:
+ WebNotificationTrayTest() {}
+ virtual ~WebNotificationTrayTest() {}
+
+ virtual void TearDown() OVERRIDE {
+ GetMessageCenter()->RemoveAllNotifications(false);
+ test::AshTestBase::TearDown();
+ }
+
+ protected:
+ void AddNotification(const std::string& id) {
+ scoped_ptr<message_center::Notification> notification;
+ notification.reset(new message_center::Notification(
+ message_center::NOTIFICATION_TYPE_SIMPLE,
+ id,
+ ASCIIToUTF16("Test Web Notification"),
+ ASCIIToUTF16("Notification message body."),
+ gfx::Image(),
+ ASCIIToUTF16("www.test.org"),
+ "" /* extension id */,
+ message_center::RichNotificationData(),
+ NULL /* delegate */));
+ GetMessageCenter()->AddNotification(notification.Pass());
+ }
+
+ void UpdateNotification(const std::string& old_id,
+ const std::string& new_id) {
+ scoped_ptr<message_center::Notification> notification;
+ notification.reset(new message_center::Notification(
+ message_center::NOTIFICATION_TYPE_SIMPLE,
+ new_id,
+ ASCIIToUTF16("Updated Web Notification"),
+ ASCIIToUTF16("Updated message body."),
+ gfx::Image(),
+ ASCIIToUTF16("www.test.org"),
+ "" /* extension id */,
+ message_center::RichNotificationData(),
+ NULL /* delegate */));
+ GetMessageCenter()->UpdateNotification(old_id, notification.Pass());
+ }
+
+ void RemoveNotification(const std::string& id) {
+ GetMessageCenter()->RemoveNotification(id, false);
+ }
+
+ views::Widget* GetWidget() {
+ return GetTray()->GetWidget();
+ }
+
+ gfx::Rect GetPopupWorkArea() {
+ return GetPopupWorkAreaForTray(GetTray());
+ }
+
+ gfx::Rect GetPopupWorkAreaForTray(WebNotificationTray* tray) {
+ return tray->popup_collection_->work_area_;
+ }
+
+ bool IsPopupVisible() {
+ return GetTray()->IsPopupVisible();
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(WebNotificationTrayTest);
+};
+
+TEST_F(WebNotificationTrayTest, WebNotifications) {
+ // TODO(mukai): move this test case to ui/message_center.
+ ASSERT_TRUE(GetWidget());
+
+ // Add a notification.
+ AddNotification("test_id1");
+ EXPECT_EQ(1u, GetMessageCenter()->NotificationCount());
+ EXPECT_TRUE(GetMessageCenter()->HasNotification("test_id1"));
+ AddNotification("test_id2");
+ AddNotification("test_id2");
+ EXPECT_EQ(2u, GetMessageCenter()->NotificationCount());
+ EXPECT_TRUE(GetMessageCenter()->HasNotification("test_id2"));
+
+ // Ensure that updating a notification does not affect the count.
+ UpdateNotification("test_id2", "test_id3");
+ UpdateNotification("test_id3", "test_id3");
+ EXPECT_EQ(2u, GetMessageCenter()->NotificationCount());
+ EXPECT_FALSE(GetMessageCenter()->HasNotification("test_id2"));
+
+ // Ensure that Removing the first notification removes it from the tray.
+ RemoveNotification("test_id1");
+ EXPECT_FALSE(GetMessageCenter()->HasNotification("test_id1"));
+ EXPECT_EQ(1u, GetMessageCenter()->NotificationCount());
+
+ // Remove the remianing notification.
+ RemoveNotification("test_id3");
+ EXPECT_EQ(0u, GetMessageCenter()->NotificationCount());
+ EXPECT_FALSE(GetMessageCenter()->HasNotification("test_id3"));
+}
+
+TEST_F(WebNotificationTrayTest, WebNotificationPopupBubble) {
+ // TODO(mukai): move this test case to ui/message_center.
+ ASSERT_TRUE(GetWidget());
+
+ // Adding a notification should show the popup bubble.
+ AddNotification("test_id1");
+ EXPECT_TRUE(GetTray()->IsPopupVisible());
+
+ // Updating a notification should not hide the popup bubble.
+ AddNotification("test_id2");
+ UpdateNotification("test_id2", "test_id3");
+ EXPECT_TRUE(GetTray()->IsPopupVisible());
+
+ // Removing the first notification should not hide the popup bubble.
+ RemoveNotification("test_id1");
+ EXPECT_TRUE(GetTray()->IsPopupVisible());
+
+ // Removing the visible notification should hide the popup bubble.
+ RemoveNotification("test_id3");
+ EXPECT_FALSE(GetTray()->IsPopupVisible());
+}
+
+using message_center::NotificationList;
+
+
+// Flakily fails. http://crbug.com/229791
+TEST_F(WebNotificationTrayTest, DISABLED_ManyMessageCenterNotifications) {
+ // Add the max visible notifications +1, ensure the correct visible number.
+ size_t notifications_to_add =
+ message_center::kMaxVisibleMessageCenterNotifications + 1;
+ for (size_t i = 0; i < notifications_to_add; ++i) {
+ std::string id = base::StringPrintf("test_id%d", static_cast<int>(i));
+ AddNotification(id);
+ }
+ bool shown = GetTray()->message_center_tray_->ShowMessageCenterBubble();
+ EXPECT_TRUE(shown);
+ RunAllPendingInMessageLoop();
+ EXPECT_TRUE(GetTray()->message_center_bubble() != NULL);
+ EXPECT_EQ(notifications_to_add,
+ GetMessageCenter()->NotificationCount());
+ EXPECT_EQ(message_center::kMaxVisibleMessageCenterNotifications,
+ GetTray()->GetMessageCenterBubbleForTest()->
+ NumMessageViewsForTest());
+}
+
+// Flakily times out. http://crbug.com/229792
+TEST_F(WebNotificationTrayTest, DISABLED_ManyPopupNotifications) {
+ // Add the max visible popup notifications +1, ensure the correct num visible.
+ size_t notifications_to_add =
+ message_center::kMaxVisiblePopupNotifications + 1;
+ for (size_t i = 0; i < notifications_to_add; ++i) {
+ std::string id = base::StringPrintf("test_id%d", static_cast<int>(i));
+ AddNotification(id);
+ }
+ GetTray()->ShowPopups();
+ EXPECT_TRUE(GetTray()->IsPopupVisible());
+ EXPECT_EQ(notifications_to_add,
+ GetMessageCenter()->NotificationCount());
+ NotificationList::PopupNotifications popups =
+ GetMessageCenter()->GetPopupNotifications();
+ EXPECT_EQ(message_center::kMaxVisiblePopupNotifications, popups.size());
+}
+
+#if defined(OS_CHROMEOS)
+// Display notification is ChromeOS only.
+#define MAYBE_PopupShownOnBothDisplays PopupShownOnBothDisplays
+#define MAYBE_PopupAndSystemTrayMultiDisplay PopupAndSystemTrayMultiDisplay
+#else
+#define MAYBE_PopupShownOnBothDisplays DISABLED_PopupShownOnBothDisplays
+#define MAYBE_PopupAndSystemTrayMultiDisplay \
+ DISABLED_PopupAndSystemTrayMultiDisplay
+#endif
+
+// Verifies if the notification appears on both displays when extended mode.
+TEST_F(WebNotificationTrayTest, MAYBE_PopupShownOnBothDisplays) {
+ if (!SupportsMultipleDisplays())
+ return;
+
+ // Enables to appear the notification for display changes.
+ test::TestSystemTrayDelegate* tray_delegate =
+ static_cast<test::TestSystemTrayDelegate*>(
+ Shell::GetInstance()->system_tray_delegate());
+ tray_delegate->set_should_show_display_notification(true);
+
+ UpdateDisplay("400x400,200x200");
+ // UpdateDisplay() creates the display notifications, so popup is visible.
+ EXPECT_TRUE(GetTray()->IsPopupVisible());
+ WebNotificationTray* secondary_tray = GetSecondaryTray();
+ ASSERT_TRUE(secondary_tray);
+ EXPECT_TRUE(secondary_tray->IsPopupVisible());
+
+ // Transition to mirroring and then back to extended display, which recreates
+ // root window controller and shelf with having notifications. This code
+ // verifies it doesn't cause crash and popups are still visible. See
+ // http://crbug.com/263664
+ internal::DisplayManager* display_manager =
+ Shell::GetInstance()->display_manager();
+
+ display_manager->SetSoftwareMirroring(true);
+ UpdateDisplay("400x400,200x200");
+ EXPECT_TRUE(GetTray()->IsPopupVisible());
+ EXPECT_FALSE(GetSecondaryTray());
+
+ display_manager->SetSoftwareMirroring(false);
+ UpdateDisplay("400x400,200x200");
+ EXPECT_TRUE(GetTray()->IsPopupVisible());
+ secondary_tray = GetSecondaryTray();
+ ASSERT_TRUE(secondary_tray);
+ EXPECT_TRUE(secondary_tray->IsPopupVisible());
+}
+
+#if defined(OS_CHROMEOS)
+// PopupAndSystemTray may fail in platforms other than ChromeOS because the
+// RootWindow's bound can be bigger than gfx::Display's work area so that
+// openingsystem tray doesn't affect at all the work area of popups.
+#define MAYBE_PopupAndSystemTray PopupAndSystemTray
+#define MAYBE_PopupAndAutoHideShelf PopupAndAutoHideShelf
+#define MAYBE_PopupAndFullscreen PopupAndFullscreen
+#else
+#define MAYBE_PopupAndSystemTray DISABLED_PopupAndSystemTray
+#define MAYBE_PopupAndAutoHideShelf DISABLED_PopupAndAutoHideShelf
+#define MAYBE_PopupAndFullscreen DISABLED_PopupAndFullscreen
+#endif
+
+TEST_F(WebNotificationTrayTest, MAYBE_PopupAndSystemTray) {
+ TestItem* test_item = new TestItem;
+ GetSystemTray()->AddTrayItem(test_item);
+
+ AddNotification("test_id");
+ EXPECT_TRUE(GetTray()->IsPopupVisible());
+ gfx::Rect work_area = GetPopupWorkArea();
+
+ // System tray is created, the popup's work area should be narrowed but still
+ // visible.
+ GetSystemTray()->ShowDefaultView(BUBBLE_CREATE_NEW);
+ EXPECT_TRUE(GetTray()->IsPopupVisible());
+ gfx::Rect work_area_with_tray = GetPopupWorkArea();
+ EXPECT_GT(work_area.size().GetArea(), work_area_with_tray.size().GetArea());
+
+ // System tray notification is also created, the popup's work area is narrowed
+ // even more, but still visible.
+ GetSystemTray()->ShowNotificationView(test_item);
+ EXPECT_TRUE(GetTray()->IsPopupVisible());
+ gfx::Rect work_area_with_tray_notificaiton = GetPopupWorkArea();
+ EXPECT_GT(work_area.size().GetArea(),
+ work_area_with_tray_notificaiton.size().GetArea());
+ EXPECT_GT(work_area_with_tray.size().GetArea(),
+ work_area_with_tray_notificaiton.size().GetArea());
+
+ // Close system tray, only system tray notifications.
+ GetSystemTray()->ClickedOutsideBubble();
+ EXPECT_TRUE(GetTray()->IsPopupVisible());
+ gfx::Rect work_area_with_notification = GetPopupWorkArea();
+ EXPECT_GT(work_area.size().GetArea(),
+ work_area_with_notification.size().GetArea());
+ EXPECT_LT(work_area_with_tray_notificaiton.size().GetArea(),
+ work_area_with_notification.size().GetArea());
+
+ // Close the system tray notifications.
+ GetSystemTray()->HideNotificationView(test_item);
+ EXPECT_TRUE(GetTray()->IsPopupVisible());
+ EXPECT_EQ(work_area.ToString(), GetPopupWorkArea().ToString());
+}
+
+TEST_F(WebNotificationTrayTest, MAYBE_PopupAndAutoHideShelf) {
+ AddNotification("test_id");
+ EXPECT_TRUE(GetTray()->IsPopupVisible());
+ gfx::Rect work_area = GetPopupWorkArea();
+
+ // Shelf's auto-hide state won't be HIDDEN unless window exists.
+ scoped_ptr<aura::Window> window(
+ CreateTestWindowInShellWithBounds(gfx::Rect(1, 2, 3, 4)));
+ internal::ShelfLayoutManager* shelf =
+ Shell::GetPrimaryRootWindowController()->GetShelfLayoutManager();
+ shelf->SetAutoHideBehavior(SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS);
+
+ EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->auto_hide_state());
+ gfx::Rect work_area_auto_hidden = GetPopupWorkArea();
+ EXPECT_LT(work_area.size().GetArea(), work_area_auto_hidden.size().GetArea());
+
+ // Close the window, which shows the shelf.
+ window.reset();
+ EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->auto_hide_state());
+ gfx::Rect work_area_auto_shown = GetPopupWorkArea();
+ EXPECT_EQ(work_area.ToString(), work_area_auto_shown.ToString());
+
+ // Create the system tray during auto-hide.
+ window.reset(CreateTestWindowInShellWithBounds(gfx::Rect(1, 2, 3, 4)));
+ TestItem* test_item = new TestItem;
+ GetSystemTray()->AddTrayItem(test_item);
+ GetSystemTray()->ShowDefaultView(BUBBLE_CREATE_NEW);
+
+ EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->auto_hide_state());
+ EXPECT_TRUE(GetTray()->IsPopupVisible());
+ gfx::Rect work_area_with_tray = GetPopupWorkArea();
+ EXPECT_GT(work_area_auto_shown.size().GetArea(),
+ work_area_with_tray.size().GetArea());
+
+ // Create tray notification.
+ GetSystemTray()->ShowNotificationView(test_item);
+ EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->auto_hide_state());
+ gfx::Rect work_area_with_tray_notification = GetPopupWorkArea();
+ EXPECT_GT(work_area_with_tray.size().GetArea(),
+ work_area_with_tray_notification.size().GetArea());
+
+ // Close the system tray.
+ GetSystemTray()->ClickedOutsideBubble();
+ shelf->UpdateAutoHideState();
+ RunAllPendingInMessageLoop();
+ EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->auto_hide_state());
+ gfx::Rect work_area_hidden_with_tray_notification = GetPopupWorkArea();
+ EXPECT_LT(work_area_with_tray_notification.size().GetArea(),
+ work_area_hidden_with_tray_notification.size().GetArea());
+ EXPECT_GT(work_area_auto_hidden.size().GetArea(),
+ work_area_hidden_with_tray_notification.size().GetArea());
+
+ // Close the window again, which shows the shelf.
+ window.reset();
+ EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->auto_hide_state());
+ gfx::Rect work_area_shown_with_tray_notification = GetPopupWorkArea();
+ EXPECT_GT(work_area_hidden_with_tray_notification.size().GetArea(),
+ work_area_shown_with_tray_notification.size().GetArea());
+ EXPECT_GT(work_area_auto_shown.size().GetArea(),
+ work_area_shown_with_tray_notification.size().GetArea());
+}
+
+TEST_F(WebNotificationTrayTest, MAYBE_PopupAndFullscreen) {
+ AddNotification("test_id");
+ EXPECT_TRUE(IsPopupVisible());
+ gfx::Rect work_area = GetPopupWorkArea();
+
+ // Checks the work area for normal auto-hidden state.
+ scoped_ptr<aura::Window> window(
+ CreateTestWindowInShellWithBounds(gfx::Rect(1, 2, 3, 4)));
+ internal::ShelfLayoutManager* shelf =
+ Shell::GetPrimaryRootWindowController()->GetShelfLayoutManager();
+ shelf->SetAutoHideBehavior(SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS);
+ EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->auto_hide_state());
+ gfx::Rect work_area_auto_hidden = GetPopupWorkArea();
+ shelf->SetAutoHideBehavior(SHELF_AUTO_HIDE_BEHAVIOR_NEVER);
+
+ // Make the window to use immersive mode.
+ window->SetProperty(internal::kFullscreenUsesMinimalChromeKey, true);
+ window->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_FULLSCREEN);
+ RunAllPendingInMessageLoop();
+
+ // The work area for auto-hidden status of fullscreen is a bit larger
+ // since it doesn't even have the 3-pixel width.
+ EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->auto_hide_state());
+ gfx::Rect work_area_fullscreen_hidden = GetPopupWorkArea();
+ EXPECT_EQ(work_area_auto_hidden.ToString(),
+ work_area_fullscreen_hidden.ToString());
+
+ // Move the mouse cursor at the bottom, which shows the shelf.
+ aura::test::EventGenerator generator(Shell::GetPrimaryRootWindow());
+ gfx::Point bottom_right =
+ Shell::GetScreen()->GetPrimaryDisplay().bounds().bottom_right();
+ bottom_right.Offset(-1, -1);
+ generator.MoveMouseTo(bottom_right);
+ shelf->UpdateAutoHideStateNow();
+ EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->auto_hide_state());
+ EXPECT_EQ(work_area.ToString(), GetPopupWorkArea().ToString());
+
+ generator.MoveMouseTo(work_area.CenterPoint());
+ shelf->UpdateAutoHideStateNow();
+ EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->auto_hide_state());
+ EXPECT_EQ(work_area_auto_hidden.ToString(), GetPopupWorkArea().ToString());
+}
+
+TEST_F(WebNotificationTrayTest, MAYBE_PopupAndSystemTrayMultiDisplay) {
+ UpdateDisplay("800x600,600x400");
+
+ AddNotification("test_id");
+ gfx::Rect work_area = GetPopupWorkArea();
+ gfx::Rect work_area_second = GetPopupWorkAreaForTray(GetSecondaryTray());
+
+ // System tray is created on the primary display. The popups in the secondary
+ // tray aren't affected.
+ GetSystemTray()->ShowDefaultView(BUBBLE_CREATE_NEW);
+ EXPECT_GT(work_area.size().GetArea(), GetPopupWorkArea().size().GetArea());
+ EXPECT_EQ(work_area_second.ToString(),
+ GetPopupWorkAreaForTray(GetSecondaryTray()).ToString());
+}
+
+} // namespace ash
diff --git a/chromium/ash/tooltips/tooltip_controller_unittest.cc b/chromium/ash/tooltips/tooltip_controller_unittest.cc
new file mode 100644
index 00000000000..8cf0dafb59b
--- /dev/null
+++ b/chromium/ash/tooltips/tooltip_controller_unittest.cc
@@ -0,0 +1,182 @@
+// Copyright (c) 2012 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.
+
+#include "ash/shell.h"
+#include "ash/test/ash_test_base.h"
+#include "base/strings/utf_string_conversions.h"
+#include "ui/aura/client/tooltip_client.h"
+#include "ui/aura/env.h"
+#include "ui/aura/root_window.h"
+#include "ui/aura/test/event_generator.h"
+#include "ui/aura/window.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "ui/gfx/font.h"
+#include "ui/gfx/point.h"
+#include "ui/views/corewm/tooltip_controller.h"
+#include "ui/views/corewm/tooltip_controller_test_helper.h"
+#include "ui/views/view.h"
+#include "ui/views/widget/widget.h"
+
+using views::corewm::TooltipController;
+using views::corewm::test::TooltipTestView;
+using views::corewm::test::TooltipControllerTestHelper;
+
+// The tests in this file exercise bits of TooltipController that are hard to
+// test outside of ash. Meaning these tests require the shell and related things
+// to be installed.
+
+namespace ash {
+namespace test {
+
+namespace {
+
+views::Widget* CreateNewWidgetWithBoundsOn(int display,
+ const gfx::Rect& bounds) {
+ views::Widget* widget = new views::Widget;
+ views::Widget::InitParams params;
+ params.type = views::Widget::InitParams::TYPE_WINDOW_FRAMELESS;
+ params.accept_events = true;
+ params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
+ params.context = Shell::GetAllRootWindows().at(display);
+ params.child = true;
+ params.bounds = bounds;
+ widget->Init(params);
+ widget->Show();
+ return widget;
+}
+
+views::Widget* CreateNewWidgetOn(int display) {
+ return CreateNewWidgetWithBoundsOn(display, gfx::Rect());
+}
+
+void AddViewToWidgetAndResize(views::Widget* widget, views::View* view) {
+ if (!widget->GetContentsView()) {
+ views::View* contents_view = new views::View;
+ widget->SetContentsView(contents_view);
+ }
+
+ views::View* contents_view = widget->GetContentsView();
+ contents_view->AddChildView(view);
+ view->SetBounds(contents_view->width(), 0, 100, 100);
+ gfx::Rect contents_view_bounds = contents_view->bounds();
+ contents_view_bounds.Union(view->bounds());
+ contents_view->SetBoundsRect(contents_view_bounds);
+ widget->SetBounds(gfx::Rect(widget->GetWindowBoundsInScreen().origin(),
+ contents_view_bounds.size()));
+}
+
+TooltipController* GetController() {
+ return static_cast<TooltipController*>(
+ aura::client::GetTooltipClient(Shell::GetPrimaryRootWindow()));
+}
+
+gfx::Font GetDefaultFont() {
+ return ui::ResourceBundle::GetSharedInstance().GetFont(
+ ui::ResourceBundle::BaseFont);
+}
+
+} // namespace
+
+class TooltipControllerTest : public AshTestBase {
+ public:
+ TooltipControllerTest() {}
+ virtual ~TooltipControllerTest() {}
+
+ virtual void SetUp() OVERRIDE {
+ AshTestBase::SetUp();
+ helper_.reset(new TooltipControllerTestHelper(GetController()));
+ }
+
+ protected:
+ scoped_ptr<TooltipControllerTestHelper> helper_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(TooltipControllerTest);
+};
+
+TEST_F(TooltipControllerTest, NonNullTooltipClient) {
+ EXPECT_TRUE(aura::client::GetTooltipClient(Shell::GetPrimaryRootWindow())
+ != NULL);
+ EXPECT_EQ(base::string16(), helper_->GetTooltipText());
+ EXPECT_EQ(NULL, helper_->GetTooltipWindow());
+ EXPECT_FALSE(helper_->IsTooltipVisible());
+}
+
+TEST_F(TooltipControllerTest, HideTooltipWhenCursorHidden) {
+ scoped_ptr<views::Widget> widget(CreateNewWidgetOn(0));
+ TooltipTestView* view = new TooltipTestView;
+ AddViewToWidgetAndResize(widget.get(), view);
+ view->set_tooltip_text(ASCIIToUTF16("Tooltip Text"));
+ EXPECT_EQ(base::string16(), helper_->GetTooltipText());
+ EXPECT_EQ(NULL, helper_->GetTooltipWindow());
+
+ aura::test::EventGenerator generator(Shell::GetPrimaryRootWindow());
+ generator.MoveMouseRelativeTo(widget->GetNativeView(),
+ view->bounds().CenterPoint());
+ base::string16 expected_tooltip = ASCIIToUTF16("Tooltip Text");
+
+ // Fire tooltip timer so tooltip becomes visible.
+ helper_->FireTooltipTimer();
+ EXPECT_TRUE(helper_->IsTooltipVisible());
+
+ // Hide the cursor and check again.
+ ash::Shell::GetInstance()->cursor_manager()->DisableMouseEvents();
+ helper_->FireTooltipTimer();
+ EXPECT_FALSE(helper_->IsTooltipVisible());
+
+ // Show the cursor and re-check.
+ ash::Shell::GetInstance()->cursor_manager()->EnableMouseEvents();
+ helper_->FireTooltipTimer();
+ EXPECT_TRUE(helper_->IsTooltipVisible());
+}
+
+TEST_F(TooltipControllerTest, TooltipsOnMultiDisplayShouldNotCrash) {
+ if (!SupportsMultipleDisplays())
+ return;
+
+ UpdateDisplay("1000x600,600x400");
+ Shell::RootWindowList root_windows = Shell::GetAllRootWindows();
+ scoped_ptr<views::Widget> widget1(CreateNewWidgetWithBoundsOn(
+ 0, gfx::Rect(10, 10, 100, 100)));
+ TooltipTestView* view1 = new TooltipTestView;
+ AddViewToWidgetAndResize(widget1.get(), view1);
+ view1->set_tooltip_text(ASCIIToUTF16("Tooltip Text for view 1"));
+ EXPECT_EQ(widget1->GetNativeView()->GetRootWindow(), root_windows[0]);
+
+ scoped_ptr<views::Widget> widget2(CreateNewWidgetWithBoundsOn(
+ 1, gfx::Rect(1200, 10, 100, 100)));
+ TooltipTestView* view2 = new TooltipTestView;
+ AddViewToWidgetAndResize(widget2.get(), view2);
+ view2->set_tooltip_text(ASCIIToUTF16("Tooltip Text for view 2"));
+ EXPECT_EQ(widget2->GetNativeView()->GetRootWindow(), root_windows[1]);
+
+ // Show tooltip on second display.
+ aura::test::EventGenerator generator(root_windows[1]);
+ generator.MoveMouseRelativeTo(widget2->GetNativeView(),
+ view2->bounds().CenterPoint());
+ helper_->FireTooltipTimer();
+ EXPECT_TRUE(helper_->IsTooltipVisible());
+
+ // Get rid of secondary display. This destroy's the tooltip's aura window. If
+ // we have handled this case, we will not crash in the following statement.
+ UpdateDisplay("1000x600");
+#if !defined(OS_WIN)
+ // TODO(cpu): Detangle the window destruction notification. Currently
+ // the TooltipController::OnWindowDestroyed is not being called then the
+ // display is torn down so the tooltip is is still there.
+ EXPECT_FALSE(helper_->IsTooltipVisible());
+#endif
+ EXPECT_EQ(widget2->GetNativeView()->GetRootWindow(), root_windows[0]);
+
+ // The tooltip should create a new aura window for itself, so we should still
+ // be able to show tooltips on the primary display.
+ aura::test::EventGenerator generator1(root_windows[0]);
+ generator1.MoveMouseRelativeTo(widget1->GetNativeView(),
+ view1->bounds().CenterPoint());
+ helper_->FireTooltipTimer();
+ EXPECT_TRUE(helper_->IsTooltipVisible());
+}
+
+} // namespace test
+} // namespace ash
diff --git a/chromium/ash/touch/touch_hud_debug.cc b/chromium/ash/touch/touch_hud_debug.cc
new file mode 100644
index 00000000000..5b7c120d5cd
--- /dev/null
+++ b/chromium/ash/touch/touch_hud_debug.cc
@@ -0,0 +1,491 @@
+// Copyright 2013 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.
+
+#include "ash/touch/touch_hud_debug.h"
+
+#include "ash/display/display_manager.h"
+#include "ash/root_window_controller.h"
+#include "ash/shell.h"
+#include "ash/wm/property_util.h"
+#include "base/json/json_string_value_serializer.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "third_party/skia/include/core/SkPath.h"
+#include "ui/aura/root_window.h"
+#include "ui/base/animation/animation_delegate.h"
+#include "ui/base/events/event.h"
+#include "ui/gfx/canvas.h"
+#include "ui/gfx/display.h"
+#include "ui/gfx/size.h"
+#include "ui/gfx/transform.h"
+#include "ui/views/controls/label.h"
+#include "ui/views/layout/box_layout.h"
+#include "ui/views/widget/widget.h"
+
+#if defined(USE_X11)
+#include <X11/extensions/XInput2.h>
+#include <X11/Xlib.h>
+
+#include "ui/base/x/device_data_manager.h"
+#endif
+
+namespace ash {
+namespace internal {
+
+const int kPointRadius = 20;
+const SkColor kColors[] = {
+ SK_ColorYELLOW,
+ SK_ColorGREEN,
+ SK_ColorRED,
+ SK_ColorBLUE,
+ SK_ColorGRAY,
+ SK_ColorMAGENTA,
+ SK_ColorCYAN,
+ SK_ColorWHITE,
+ SK_ColorBLACK,
+ SkColorSetRGB(0xFF, 0x8C, 0x00),
+ SkColorSetRGB(0x8B, 0x45, 0x13),
+ SkColorSetRGB(0xFF, 0xDE, 0xAD),
+};
+const int kAlpha = 0x60;
+const int kMaxPaths = arraysize(kColors);
+const int kReducedScale = 10;
+
+const char* GetTouchEventLabel(ui::EventType type) {
+ switch (type) {
+ case ui::ET_UNKNOWN:
+ return " ";
+ case ui::ET_TOUCH_PRESSED:
+ return "P";
+ case ui::ET_TOUCH_MOVED:
+ return "M";
+ case ui::ET_TOUCH_RELEASED:
+ return "R";
+ case ui::ET_TOUCH_CANCELLED:
+ return "C";
+ default:
+ break;
+ }
+ return "?";
+}
+
+int GetTrackingId(const ui::TouchEvent& event) {
+ if (!event.HasNativeEvent())
+ return 0;
+#if defined(USE_XI2_MT)
+ ui::DeviceDataManager* manager = ui::DeviceDataManager::GetInstance();
+ double tracking_id;
+ if (manager->GetEventData(*event.native_event(),
+ ui::DeviceDataManager::DT_TOUCH_TRACKING_ID,
+ &tracking_id)) {
+ return static_cast<int>(tracking_id);
+ }
+#endif
+ return 0;
+}
+
+int GetSourceDeviceId(const ui::TouchEvent& event) {
+ if (!event.HasNativeEvent())
+ return 0;
+#if defined(USE_X11)
+ XEvent* xev = event.native_event();
+ return static_cast<XIDeviceEvent*>(xev->xcookie.data)->sourceid;
+#endif
+ return 0;
+}
+
+// A TouchPointLog represents a single touch-event of a touch point.
+struct TouchPointLog {
+ public:
+ explicit TouchPointLog(const ui::TouchEvent& touch)
+ : id(touch.touch_id()),
+ type(touch.type()),
+ location(touch.root_location()),
+ timestamp(touch.time_stamp().InMillisecondsF()),
+ radius_x(touch.radius_x()),
+ radius_y(touch.radius_y()),
+ pressure(touch.force()),
+ tracking_id(GetTrackingId(touch)),
+ source_device(GetSourceDeviceId(touch)) {
+ }
+
+ // Populates a dictionary value with all the information about the touch
+ // point.
+ scoped_ptr<DictionaryValue> GetAsDictionary() const {
+ scoped_ptr<DictionaryValue> value(new DictionaryValue());
+
+ value->SetInteger("id", id);
+ value->SetString("type", std::string(GetTouchEventLabel(type)));
+ value->SetString("location", location.ToString());
+ value->SetDouble("timestamp", timestamp);
+ value->SetDouble("radius_x", radius_x);
+ value->SetDouble("radius_y", radius_y);
+ value->SetDouble("pressure", pressure);
+ value->SetInteger("tracking_id", tracking_id);
+ value->SetInteger("source_device", source_device);
+
+ return value.Pass();
+ }
+
+ int id;
+ ui::EventType type;
+ gfx::Point location;
+ double timestamp;
+ float radius_x;
+ float radius_y;
+ float pressure;
+ int tracking_id;
+ int source_device;
+};
+
+// A TouchTrace keeps track of all the touch events of a single touch point
+// (starting from a touch-press and ending at a touch-release or touch-cancel).
+class TouchTrace {
+ public:
+ typedef std::vector<TouchPointLog>::iterator iterator;
+ typedef std::vector<TouchPointLog>::const_iterator const_iterator;
+ typedef std::vector<TouchPointLog>::reverse_iterator reverse_iterator;
+ typedef std::vector<TouchPointLog>::const_reverse_iterator
+ const_reverse_iterator;
+
+ TouchTrace() {
+ }
+
+ void AddTouchPoint(const ui::TouchEvent& touch) {
+ log_.push_back(TouchPointLog(touch));
+ }
+
+ const std::vector<TouchPointLog>& log() const { return log_; }
+
+ bool active() const {
+ return !log_.empty() && log_.back().type != ui::ET_TOUCH_RELEASED &&
+ log_.back().type != ui::ET_TOUCH_CANCELLED;
+ }
+
+ // Returns a list containing data from all events for the touch point.
+ scoped_ptr<ListValue> GetAsList() const {
+ scoped_ptr<ListValue> list(new ListValue());
+ for (const_iterator i = log_.begin(); i != log_.end(); ++i)
+ list->Append((*i).GetAsDictionary().release());
+ return list.Pass();
+ }
+
+ void Reset() {
+ log_.clear();
+ }
+
+ private:
+ std::vector<TouchPointLog> log_;
+
+ DISALLOW_COPY_AND_ASSIGN(TouchTrace);
+};
+
+// A TouchLog keeps track of all touch events of all touch points.
+class TouchLog {
+ public:
+ TouchLog() : next_trace_index_(0) {
+ }
+
+ void AddTouchPoint(const ui::TouchEvent& touch) {
+ if (touch.type() == ui::ET_TOUCH_PRESSED)
+ StartTrace(touch);
+ AddToTrace(touch);
+ }
+
+ void Reset() {
+ next_trace_index_ = 0;
+ for (int i = 0; i < kMaxPaths; ++i)
+ traces_[i].Reset();
+ }
+
+ scoped_ptr<ListValue> GetAsList() const {
+ scoped_ptr<ListValue> list(new ListValue());
+ for (int i = 0; i < kMaxPaths; ++i) {
+ if (!traces_[i].log().empty())
+ list->Append(traces_[i].GetAsList().release());
+ }
+ return list.Pass();
+ }
+
+ int GetTraceIndex(int touch_id) const {
+ return touch_id_to_trace_index_.at(touch_id);
+ }
+
+ const TouchTrace* traces() const {
+ return traces_;
+ }
+
+ private:
+ void StartTrace(const ui::TouchEvent& touch) {
+ // Find the first inactive spot; otherwise, overwrite the one
+ // |next_trace_index_| is pointing to.
+ int old_trace_index = next_trace_index_;
+ do {
+ if (!traces_[next_trace_index_].active())
+ break;
+ next_trace_index_ = (next_trace_index_ + 1) % kMaxPaths;
+ } while (next_trace_index_ != old_trace_index);
+ int touch_id = touch.touch_id();
+ traces_[next_trace_index_].Reset();
+ touch_id_to_trace_index_[touch_id] = next_trace_index_;
+ next_trace_index_ = (next_trace_index_ + 1) % kMaxPaths;
+ }
+
+ void AddToTrace(const ui::TouchEvent& touch) {
+ int touch_id = touch.touch_id();
+ int trace_index = touch_id_to_trace_index_[touch_id];
+ traces_[trace_index].AddTouchPoint(touch);
+ }
+
+ TouchTrace traces_[kMaxPaths];
+ int next_trace_index_;
+
+ std::map<int, int> touch_id_to_trace_index_;
+
+ DISALLOW_COPY_AND_ASSIGN(TouchLog);
+};
+
+// TouchHudCanvas draws touch traces in |FULLSCREEN| and |REDUCED_SCALE| modes.
+class TouchHudCanvas : public views::View {
+ public:
+ explicit TouchHudCanvas(const TouchLog& touch_log)
+ : touch_log_(touch_log),
+ scale_(1) {
+ SetPaintToLayer(true);
+ SetFillsBoundsOpaquely(false);
+
+ paint_.setStyle(SkPaint::kFill_Style);
+ }
+
+ virtual ~TouchHudCanvas() {}
+
+ void SetScale(int scale) {
+ if (scale_ == scale)
+ return;
+ scale_ = scale;
+ gfx::Transform transform;
+ transform.Scale(1. / scale_, 1. / scale_);
+ layer()->SetTransform(transform);
+ }
+
+ int scale() const { return scale_; }
+
+ void TouchPointAdded(int touch_id) {
+ int trace_index = touch_log_.GetTraceIndex(touch_id);
+ const TouchTrace& trace = touch_log_.traces()[trace_index];
+ const TouchPointLog& point = trace.log().back();
+ if (point.type == ui::ET_TOUCH_PRESSED)
+ StartedTrace(trace_index);
+ if (point.type != ui::ET_TOUCH_CANCELLED)
+ AddedPointToTrace(trace_index);
+ }
+
+ void Clear() {
+ for (int i = 0; i < kMaxPaths; ++i)
+ paths_[i].reset();
+
+ SchedulePaint();
+ }
+
+ private:
+ void StartedTrace(int trace_index) {
+ paths_[trace_index].reset();
+ colors_[trace_index] = SkColorSetA(kColors[trace_index], kAlpha);
+ }
+
+ void AddedPointToTrace(int trace_index) {
+ const TouchTrace& trace = touch_log_.traces()[trace_index];
+ const TouchPointLog& point = trace.log().back();
+ const gfx::Point& location = point.location;
+ SkScalar x = SkIntToScalar(location.x());
+ SkScalar y = SkIntToScalar(location.y());
+ SkPoint last;
+ if (!paths_[trace_index].getLastPt(&last) || x != last.x() ||
+ y != last.y()) {
+ paths_[trace_index].addCircle(x, y, SkIntToScalar(kPointRadius));
+ SchedulePaint();
+ }
+ }
+
+ // Overridden from views::View.
+ virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE {
+ for (int i = 0; i < kMaxPaths; ++i) {
+ if (paths_[i].countPoints() == 0)
+ continue;
+ paint_.setColor(colors_[i]);
+ canvas->DrawPath(paths_[i], paint_);
+ }
+ }
+
+ SkPaint paint_;
+
+ const TouchLog& touch_log_;
+ SkPath paths_[kMaxPaths];
+ SkColor colors_[kMaxPaths];
+
+ int scale_;
+
+ DISALLOW_COPY_AND_ASSIGN(TouchHudCanvas);
+};
+
+TouchHudDebug::TouchHudDebug(aura::RootWindow* initial_root)
+ : TouchObserverHUD(initial_root),
+ mode_(FULLSCREEN),
+ touch_log_(new TouchLog()),
+ canvas_(NULL),
+ label_container_(NULL) {
+ const gfx::Display& display =
+ Shell::GetInstance()->display_manager()->GetDisplayForId(display_id());
+
+ views::View* content = widget()->GetContentsView();
+
+ canvas_ = new TouchHudCanvas(*touch_log_);
+ content->AddChildView(canvas_);
+
+ const gfx::Size& display_size = display.size();
+ canvas_->SetSize(display_size);
+
+ label_container_ = new views::View;
+ label_container_->SetLayoutManager(new views::BoxLayout(
+ views::BoxLayout::kVertical, 0, 0, 0));
+
+ for (int i = 0; i < kMaxTouchPoints; ++i) {
+ touch_labels_[i] = new views::Label;
+ touch_labels_[i]->SetBackgroundColor(SkColorSetARGB(0, 255, 255, 255));
+ touch_labels_[i]->SetShadowColors(SK_ColorWHITE,
+ SK_ColorWHITE);
+ touch_labels_[i]->SetShadowOffset(1, 1);
+ label_container_->AddChildView(touch_labels_[i]);
+ }
+ label_container_->SetX(0);
+ label_container_->SetY(display_size.height() / kReducedScale);
+ label_container_->SetSize(label_container_->GetPreferredSize());
+ label_container_->SetVisible(false);
+ content->AddChildView(label_container_);
+}
+
+TouchHudDebug::~TouchHudDebug() {
+}
+
+// static
+scoped_ptr<DictionaryValue> TouchHudDebug::GetAllAsDictionary() {
+ scoped_ptr<DictionaryValue> value(new DictionaryValue());
+ Shell::RootWindowList roots = Shell::GetInstance()->GetAllRootWindows();
+ for (Shell::RootWindowList::iterator iter = roots.begin();
+ iter != roots.end(); ++iter) {
+ internal::RootWindowController* controller = GetRootWindowController(*iter);
+ internal::TouchHudDebug* hud = controller->touch_hud_debug();
+ if (hud) {
+ scoped_ptr<ListValue> list = hud->GetLogAsList();
+ if (!list->empty())
+ value->Set(base::Int64ToString(hud->display_id()), list.release());
+ }
+ }
+ return value.Pass();
+}
+
+void TouchHudDebug::ChangeToNextMode() {
+ switch (mode_) {
+ case FULLSCREEN:
+ SetMode(REDUCED_SCALE);
+ break;
+ case REDUCED_SCALE:
+ SetMode(INVISIBLE);
+ break;
+ case INVISIBLE:
+ SetMode(FULLSCREEN);
+ break;
+ }
+}
+
+scoped_ptr<ListValue> TouchHudDebug::GetLogAsList() const {
+ return touch_log_->GetAsList();
+}
+
+void TouchHudDebug::Clear() {
+ if (widget()->IsVisible()) {
+ canvas_->Clear();
+ for (int i = 0; i < kMaxTouchPoints; ++i)
+ touch_labels_[i]->SetText(string16());
+ label_container_->SetSize(label_container_->GetPreferredSize());
+ }
+}
+
+void TouchHudDebug::SetMode(Mode mode) {
+ if (mode_ == mode)
+ return;
+ mode_ = mode;
+ switch (mode) {
+ case FULLSCREEN:
+ label_container_->SetVisible(false);
+ canvas_->SetVisible(true);
+ canvas_->SetScale(1);
+ canvas_->SchedulePaint();
+ widget()->Show();
+ break;
+ case REDUCED_SCALE:
+ label_container_->SetVisible(true);
+ canvas_->SetVisible(true);
+ canvas_->SetScale(kReducedScale);
+ canvas_->SchedulePaint();
+ widget()->Show();
+ break;
+ case INVISIBLE:
+ widget()->Hide();
+ break;
+ }
+}
+
+void TouchHudDebug::UpdateTouchPointLabel(int index) {
+ int trace_index = touch_log_->GetTraceIndex(index);
+ const TouchTrace& trace = touch_log_->traces()[trace_index];
+ TouchTrace::const_reverse_iterator point = trace.log().rbegin();
+ ui::EventType touch_status = point->type;
+ float touch_radius = std::max(point->radius_x, point->radius_y);
+ while (point != trace.log().rend() && point->type == ui::ET_TOUCH_CANCELLED)
+ point++;
+ DCHECK(point != trace.log().rend());
+ gfx::Point touch_position = point->location;
+
+ std::string string = base::StringPrintf("%2d: %s %s (%.4f)",
+ index,
+ GetTouchEventLabel(touch_status),
+ touch_position.ToString().c_str(),
+ touch_radius);
+ touch_labels_[index]->SetText(UTF8ToUTF16(string));
+}
+
+void TouchHudDebug::OnTouchEvent(ui::TouchEvent* event) {
+ if (event->touch_id() >= kMaxTouchPoints)
+ return;
+
+ touch_log_->AddTouchPoint(*event);
+ canvas_->TouchPointAdded(event->touch_id());
+ UpdateTouchPointLabel(event->touch_id());
+ label_container_->SetSize(label_container_->GetPreferredSize());
+}
+
+void TouchHudDebug::OnDisplayBoundsChanged(const gfx::Display& display) {
+ TouchObserverHUD::OnDisplayBoundsChanged(display);
+
+ if (display.id() != display_id())
+ return;
+ const gfx::Size& size = display.size();
+ canvas_->SetSize(size);
+ label_container_->SetY(size.height() / kReducedScale);
+}
+
+void TouchHudDebug::SetHudForRootWindowController(
+ RootWindowController* controller) {
+ controller->set_touch_hud_debug(this);
+}
+
+void TouchHudDebug::UnsetHudForRootWindowController(
+ RootWindowController* controller) {
+ controller->set_touch_hud_debug(NULL);
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/touch/touch_hud_debug.h b/chromium/ash/touch/touch_hud_debug.h
new file mode 100644
index 00000000000..a02a888db6b
--- /dev/null
+++ b/chromium/ash/touch/touch_hud_debug.h
@@ -0,0 +1,87 @@
+// Copyright 2013 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 ASH_TOUCH_TOUCH_HUD_DEBUG_H_
+#define ASH_TOUCH_TOUCH_HUD_DEBUG_H_
+
+#include <map>
+
+#include "ash/ash_export.h"
+#include "ash/touch/touch_observer_hud.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/values.h"
+
+namespace views {
+class Label;
+class View;
+}
+
+namespace ash {
+namespace internal {
+
+class TouchHudCanvas;
+class TouchLog;
+
+// A heads-up display to show touch traces on the screen and log touch events.
+// As a derivative of TouchObserverHUD, objects of this class manage their own
+// lifetime.
+class ASH_EXPORT TouchHudDebug : public TouchObserverHUD {
+ public:
+ enum Mode {
+ FULLSCREEN,
+ REDUCED_SCALE,
+ INVISIBLE,
+ };
+
+ explicit TouchHudDebug(aura::RootWindow* initial_root);
+
+ // Returns the log of touch events for all displays as a dictionary mapping id
+ // of each display to its touch log.
+ static scoped_ptr<DictionaryValue> GetAllAsDictionary();
+
+ // Changes the display mode (e.g. scale, visibility). Calling this repeatedly
+ // cycles between a fixed number of display modes.
+ void ChangeToNextMode();
+
+ // Returns log of touch events as a list value. Each item in the list is a
+ // trace of one touch point.
+ scoped_ptr<ListValue> GetLogAsList() const;
+
+ Mode mode() const { return mode_; }
+
+ // Overriden from TouchObserverHUD.
+ virtual void Clear() OVERRIDE;
+
+ private:
+ virtual ~TouchHudDebug();
+
+ void SetMode(Mode mode);
+
+ void UpdateTouchPointLabel(int index);
+
+ // Overriden from TouchObserverHUD.
+ virtual void OnTouchEvent(ui::TouchEvent* event) OVERRIDE;
+ virtual void OnDisplayBoundsChanged(const gfx::Display& display) OVERRIDE;
+ virtual void SetHudForRootWindowController(
+ RootWindowController* controller) OVERRIDE;
+ virtual void UnsetHudForRootWindowController(
+ RootWindowController* controller) OVERRIDE;
+
+ static const int kMaxTouchPoints = 32;
+
+ Mode mode_;
+
+ scoped_ptr<TouchLog> touch_log_;
+
+ TouchHudCanvas* canvas_;
+ views::View* label_container_;
+ views::Label* touch_labels_[kMaxTouchPoints];
+
+ DISALLOW_COPY_AND_ASSIGN(TouchHudDebug);
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_TOUCH_TOUCH_HUD_DEBUG_H_
diff --git a/chromium/ash/touch/touch_hud_projection.cc b/chromium/ash/touch/touch_hud_projection.cc
new file mode 100644
index 00000000000..00d619336ef
--- /dev/null
+++ b/chromium/ash/touch/touch_hud_projection.cc
@@ -0,0 +1,187 @@
+// Copyright 2013 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.
+
+#include "ash/touch/touch_hud_projection.h"
+
+#include "ash/root_window_controller.h"
+#include "ash/shell.h"
+#include "ash/wm/property_util.h"
+#include "third_party/skia/include/effects/SkGradientShader.h"
+#include "ui/base/animation/animation_delegate.h"
+#include "ui/base/animation/linear_animation.h"
+#include "ui/base/events/event.h"
+#include "ui/gfx/canvas.h"
+#include "ui/gfx/size.h"
+#include "ui/views/widget/widget.h"
+
+namespace ash {
+namespace internal {
+
+const int kPointRadius = 20;
+const SkColor kProjectionFillColor = SkColorSetRGB(0xF5, 0xF5, 0xDC);
+const SkColor kProjectionStrokeColor = SK_ColorGRAY;
+const int kProjectionAlpha = 0xB0;
+const int kFadeoutDurationInMs = 250;
+const int kFadeoutFrameRate = 60;
+
+// TouchPointView draws a single touch point. This object manages its own
+// lifetime and deletes itself upon fade-out completion or whenever |Remove()|
+// is explicitly called.
+class TouchPointView : public views::View,
+ public ui::AnimationDelegate,
+ public views::WidgetObserver {
+ public:
+ explicit TouchPointView(views::Widget* parent_widget)
+ : circle_center_(kPointRadius + 1, kPointRadius + 1),
+ gradient_center_(SkPoint::Make(kPointRadius + 1,
+ kPointRadius + 1)) {
+ SetPaintToLayer(true);
+ SetFillsBoundsOpaquely(false);
+
+ SetSize(gfx::Size(2 * kPointRadius + 2, 2 * kPointRadius + 2));
+
+ stroke_paint_.setStyle(SkPaint::kStroke_Style);
+ stroke_paint_.setColor(kProjectionStrokeColor);
+
+ gradient_colors_[0] = kProjectionFillColor;
+ gradient_colors_[1] = kProjectionStrokeColor;
+
+ gradient_pos_[0] = SkFloatToScalar(0.9f);
+ gradient_pos_[1] = SkFloatToScalar(1.0f);
+
+ parent_widget->GetContentsView()->AddChildView(this);
+
+ parent_widget->AddObserver(this);
+ }
+
+ void UpdateTouch(const ui::TouchEvent& touch) {
+ if (touch.type() == ui::ET_TOUCH_RELEASED ||
+ touch.type() == ui::ET_TOUCH_CANCELLED) {
+ fadeout_.reset(new ui::LinearAnimation(kFadeoutDurationInMs,
+ kFadeoutFrameRate,
+ this));
+ fadeout_->Start();
+ } else {
+ SetX(parent()->GetMirroredXInView(touch.root_location().x()) -
+ kPointRadius - 1);
+ SetY(touch.root_location().y() - kPointRadius - 1);
+ }
+ }
+
+ void Remove() {
+ delete this;
+ }
+
+ private:
+ virtual ~TouchPointView() {
+ GetWidget()->RemoveObserver(this);
+ parent()->RemoveChildView(this);
+ }
+
+ // Overridden from views::View.
+ virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE {
+ int alpha = kProjectionAlpha;
+ if (fadeout_)
+ alpha = static_cast<int>(fadeout_->CurrentValueBetween(alpha, 0));
+ fill_paint_.setAlpha(alpha);
+ stroke_paint_.setAlpha(alpha);
+ SkShader* shader = SkGradientShader::CreateRadial(
+ gradient_center_,
+ SkIntToScalar(kPointRadius),
+ gradient_colors_,
+ gradient_pos_,
+ arraysize(gradient_colors_),
+ SkShader::kMirror_TileMode,
+ NULL);
+ fill_paint_.setShader(shader);
+ shader->unref();
+ canvas->DrawCircle(circle_center_, SkIntToScalar(kPointRadius),
+ fill_paint_);
+ canvas->DrawCircle(circle_center_, SkIntToScalar(kPointRadius),
+ stroke_paint_);
+ }
+
+ // Overridden from ui::AnimationDelegate.
+ virtual void AnimationEnded(const ui::Animation* animation) OVERRIDE {
+ DCHECK_EQ(fadeout_.get(), animation);
+ delete this;
+ }
+
+ virtual void AnimationProgressed(const ui::Animation* animation) OVERRIDE {
+ DCHECK_EQ(fadeout_.get(), animation);
+ SchedulePaint();
+ }
+
+ virtual void AnimationCanceled(const ui::Animation* animation) OVERRIDE {
+ AnimationEnded(animation);
+ }
+
+ // Overridden from views::WidgetObserver.
+ virtual void OnWidgetDestroying(views::Widget* widget) OVERRIDE {
+ fadeout_->Stop();
+ }
+
+ const gfx::Point circle_center_;
+ const SkPoint gradient_center_;
+
+ SkPaint fill_paint_;
+ SkPaint stroke_paint_;
+ SkColor gradient_colors_[2];
+ SkScalar gradient_pos_[2];
+
+ scoped_ptr<ui::Animation> fadeout_;
+
+ DISALLOW_COPY_AND_ASSIGN(TouchPointView);
+};
+
+TouchHudProjection::TouchHudProjection(aura::RootWindow* initial_root)
+ : TouchObserverHUD(initial_root) {
+}
+
+TouchHudProjection::~TouchHudProjection() {
+}
+
+void TouchHudProjection::Clear() {
+ for (std::map<int, TouchPointView*>::iterator iter = points_.begin();
+ iter != points_.end(); iter++)
+ iter->second->Remove();
+ points_.clear();
+}
+
+void TouchHudProjection::OnTouchEvent(ui::TouchEvent* event) {
+ if (event->type() == ui::ET_TOUCH_PRESSED) {
+ TouchPointView* point = new TouchPointView(widget());
+ point->UpdateTouch(*event);
+ std::pair<std::map<int, TouchPointView*>::iterator, bool> result =
+ points_.insert(std::make_pair(event->touch_id(), point));
+ // If a |TouchPointView| is already mapped to the touch id, remove it and
+ // replace it with the new one.
+ if (!result.second) {
+ result.first->second->Remove();
+ result.first->second = point;
+ }
+ } else {
+ std::map<int, TouchPointView*>::iterator iter =
+ points_.find(event->touch_id());
+ if (iter != points_.end()) {
+ iter->second->UpdateTouch(*event);
+ if (event->type() == ui::ET_TOUCH_RELEASED ||
+ event->type() == ui::ET_TOUCH_CANCELLED)
+ points_.erase(iter);
+ }
+ }
+}
+
+void TouchHudProjection::SetHudForRootWindowController(
+ RootWindowController* controller) {
+ controller->set_touch_hud_projection(this);
+}
+
+void TouchHudProjection::UnsetHudForRootWindowController(
+ RootWindowController* controller) {
+ controller->set_touch_hud_projection(NULL);
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/touch/touch_hud_projection.h b/chromium/ash/touch/touch_hud_projection.h
new file mode 100644
index 00000000000..29fddc39837
--- /dev/null
+++ b/chromium/ash/touch/touch_hud_projection.h
@@ -0,0 +1,44 @@
+// Copyright 2013 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 ASH_TOUCH_TOUCH_HUD_PROJECTION_H_
+#define ASH_TOUCH_TOUCH_HUD_PROJECTION_H_
+
+#include <map>
+
+#include "ash/touch/touch_observer_hud.h"
+
+namespace ash {
+namespace internal {
+
+class TouchPointView;
+
+// A heads-up display to show active touch points on the screen. As a derivative
+// of TouchObserverHUD, objects of this class manage their own lifetime.
+class TouchHudProjection : public TouchObserverHUD {
+ public:
+ explicit TouchHudProjection(aura::RootWindow* initial_root);
+
+ // Overriden from TouchObserverHUD.
+ virtual void Clear() OVERRIDE;
+
+ private:
+ virtual ~TouchHudProjection();
+
+ // Overriden from TouchObserverHUD.
+ virtual void OnTouchEvent(ui::TouchEvent* event) OVERRIDE;
+ virtual void SetHudForRootWindowController(
+ RootWindowController* controller) OVERRIDE;
+ virtual void UnsetHudForRootWindowController(
+ RootWindowController* controller) OVERRIDE;
+
+ std::map<int, TouchPointView*> points_;
+
+ DISALLOW_COPY_AND_ASSIGN(TouchHudProjection);
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_TOUCH_TOUCH_HUD_PROJECTION_H_
diff --git a/chromium/ash/touch/touch_observer_hud.cc b/chromium/ash/touch/touch_observer_hud.cc
new file mode 100644
index 00000000000..3daeba65707
--- /dev/null
+++ b/chromium/ash/touch/touch_observer_hud.cc
@@ -0,0 +1,150 @@
+// Copyright (c) 2012 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.
+
+#include "ash/touch/touch_observer_hud.h"
+
+#include "ash/root_window_controller.h"
+#include "ash/shell.h"
+#include "ash/shell_window_ids.h"
+#include "ash/wm/property_util.h"
+#include "ash/wm/window_properties.h"
+#include "ui/aura/root_window.h"
+#include "ui/gfx/display.h"
+#include "ui/gfx/rect.h"
+#include "ui/gfx/screen.h"
+#include "ui/gfx/size.h"
+#include "ui/views/widget/widget.h"
+
+namespace ash {
+namespace internal {
+
+TouchObserverHUD::TouchObserverHUD(aura::RootWindow* initial_root)
+ : display_id_(initial_root->GetProperty(kDisplayIdKey)),
+ root_window_(initial_root),
+ widget_(NULL) {
+ const gfx::Display& display =
+ Shell::GetInstance()->display_manager()->GetDisplayForId(display_id_);
+
+ views::View* content = new views::View;
+
+ const gfx::Size& display_size = display.size();
+ content->SetSize(display_size);
+
+ widget_ = new views::Widget();
+ views::Widget::InitParams
+ params(views::Widget::InitParams::TYPE_WINDOW_FRAMELESS);
+ params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW;
+ params.can_activate = false;
+ params.accept_events = false;
+ params.bounds = display.bounds();
+ params.parent = Shell::GetContainer(
+ root_window_,
+ internal::kShellWindowId_OverlayContainer);
+ widget_->Init(params);
+ widget_->SetContentsView(content);
+ widget_->StackAtTop();
+ widget_->Show();
+
+ widget_->AddObserver(this);
+
+ // Observe changes in display size and mode to update touch HUD.
+ Shell::GetScreen()->AddObserver(this);
+#if defined(OS_CHROMEOS)
+ Shell::GetInstance()->output_configurator()->AddObserver(this);
+#endif // defined(OS_CHROMEOS)
+
+ Shell::GetInstance()->display_controller()->AddObserver(this);
+ root_window_->AddPreTargetHandler(this);
+}
+
+TouchObserverHUD::~TouchObserverHUD() {
+ Shell::GetInstance()->display_controller()->RemoveObserver(this);
+
+#if defined(OS_CHROMEOS)
+ Shell::GetInstance()->output_configurator()->RemoveObserver(this);
+#endif // defined(OS_CHROMEOS)
+ Shell::GetScreen()->RemoveObserver(this);
+
+ widget_->RemoveObserver(this);
+}
+
+void TouchObserverHUD::Clear() {
+}
+
+void TouchObserverHUD::Remove() {
+ root_window_->RemovePreTargetHandler(this);
+
+ RootWindowController* controller = GetRootWindowController(root_window_);
+ UnsetHudForRootWindowController(controller);
+
+ widget_->CloseNow();
+}
+
+void TouchObserverHUD::OnTouchEvent(ui::TouchEvent* /*event*/) {
+}
+
+void TouchObserverHUD::OnWidgetDestroying(views::Widget* widget) {
+ DCHECK_EQ(widget, widget_);
+ delete this;
+}
+
+void TouchObserverHUD::OnDisplayBoundsChanged(const gfx::Display& display) {
+ if (display.id() != display_id_)
+ return;
+ widget_->SetSize(display.size());
+}
+
+void TouchObserverHUD::OnDisplayAdded(const gfx::Display& new_display) {}
+
+void TouchObserverHUD::OnDisplayRemoved(const gfx::Display& old_display) {
+ if (old_display.id() != display_id_)
+ return;
+ widget_->CloseNow();
+}
+
+#if defined(OS_CHROMEOS)
+void TouchObserverHUD::OnDisplayModeChanged() {
+ // Clear touch HUD for any change in display mode (single, dual extended, dual
+ // mirrored, ...).
+ Clear();
+}
+#endif // defined(OS_CHROMEOS)
+
+void TouchObserverHUD::OnDisplayConfigurationChanging() {
+ if (!root_window_)
+ return;
+
+ root_window_->RemovePreTargetHandler(this);
+
+ RootWindowController* controller = GetRootWindowController(root_window_);
+ UnsetHudForRootWindowController(controller);
+
+ views::Widget::ReparentNativeView(
+ widget_->GetNativeView(),
+ Shell::GetContainer(root_window_,
+ internal::kShellWindowId_UnparentedControlContainer));
+
+ root_window_ = NULL;
+}
+
+void TouchObserverHUD::OnDisplayConfigurationChanged() {
+ if (root_window_)
+ return;
+
+ root_window_ = Shell::GetInstance()->display_controller()->
+ GetRootWindowForDisplayId(display_id_);
+
+ views::Widget::ReparentNativeView(
+ widget_->GetNativeView(),
+ Shell::GetContainer(root_window_,
+ internal::kShellWindowId_OverlayContainer));
+
+ RootWindowController* controller = GetRootWindowController(root_window_);
+ SetHudForRootWindowController(controller);
+
+ root_window_->AddPreTargetHandler(this);
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/touch/touch_observer_hud.h b/chromium/ash/touch/touch_observer_hud.h
new file mode 100644
index 00000000000..850bc5e9202
--- /dev/null
+++ b/chromium/ash/touch/touch_observer_hud.h
@@ -0,0 +1,91 @@
+// Copyright (c) 2012 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 ASH_TOUCH_TOUCH_OBSERVER_HUD_H_
+#define ASH_TOUCH_TOUCH_OBSERVER_HUD_H_
+
+#include "ash/ash_export.h"
+#include "ash/display/display_controller.h"
+#include "ui/base/events/event_handler.h"
+#include "ui/gfx/display_observer.h"
+#include "ui/views/widget/widget_observer.h"
+
+#if defined(OS_CHROMEOS)
+#include "chromeos/display/output_configurator.h"
+#endif // defined(OS_CHROMEOS)
+
+namespace views {
+class Widget;
+}
+
+namespace ash {
+namespace internal {
+
+// An event filter which handles system level gesture events. Objects of this
+// class manage their own lifetime.
+class ASH_EXPORT TouchObserverHUD
+ : public ui::EventHandler,
+ public views::WidgetObserver,
+ public gfx::DisplayObserver,
+#if defined(OS_CHROMEOS)
+ public chromeos::OutputConfigurator::Observer,
+#endif // defined(OS_CHROMEOS)
+ public DisplayController::Observer {
+ public:
+ // Called to clear touch points and traces from the screen. Default
+ // implementation does nothing. Sub-classes should implement appropriately.
+ virtual void Clear();
+
+ // Removes the HUD from the screen.
+ void Remove();
+
+ int64 display_id() const { return display_id_; }
+
+ protected:
+ explicit TouchObserverHUD(aura::RootWindow* initial_root);
+
+ virtual ~TouchObserverHUD();
+
+ virtual void SetHudForRootWindowController(
+ RootWindowController* controller) = 0;
+ virtual void UnsetHudForRootWindowController(
+ RootWindowController* controller) = 0;
+
+ views::Widget* widget() { return widget_; }
+
+ // Overriden from ui::EventHandler.
+ virtual void OnTouchEvent(ui::TouchEvent* event) OVERRIDE;
+
+ // Overridden from views::WidgetObserver.
+ virtual void OnWidgetDestroying(views::Widget* widget) OVERRIDE;
+
+ // Overridden from gfx::DisplayObserver.
+ virtual void OnDisplayBoundsChanged(const gfx::Display& display) OVERRIDE;
+ virtual void OnDisplayAdded(const gfx::Display& new_display) OVERRIDE;
+ virtual void OnDisplayRemoved(const gfx::Display& old_display) OVERRIDE;
+
+#if defined(OS_CHROMEOS)
+ // Overriden from chromeos::OutputConfigurator::Observer.
+ virtual void OnDisplayModeChanged() OVERRIDE;
+#endif // defined(OS_CHROMEOS)
+
+ // Overriden form DisplayController::Observer.
+ virtual void OnDisplayConfigurationChanging() OVERRIDE;
+ virtual void OnDisplayConfigurationChanged() OVERRIDE;
+
+ private:
+ friend class TouchHudTest;
+
+ const int64 display_id_;
+ aura::RootWindow* root_window_;
+
+ views::Widget* widget_;
+
+ DISALLOW_COPY_AND_ASSIGN(TouchObserverHUD);
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_TOUCH_TOUCH_OBSERVER_HUD_H_
diff --git a/chromium/ash/touch/touch_observer_hud_unittest.cc b/chromium/ash/touch/touch_observer_hud_unittest.cc
new file mode 100644
index 00000000000..4d74982ae58
--- /dev/null
+++ b/chromium/ash/touch/touch_observer_hud_unittest.cc
@@ -0,0 +1,420 @@
+// Copyright (c) 2013 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.
+
+#include "ash/touch/touch_observer_hud.h"
+
+#include "ash/ash_switches.h"
+#include "ash/display/display_manager.h"
+#include "ash/root_window_controller.h"
+#include "ash/screen_ash.h"
+#include "ash/shell.h"
+#include "ash/test/ash_test_base.h"
+#include "ash/test/display_manager_test_api.h"
+#include "ash/touch/touch_hud_debug.h"
+#include "ash/wm/property_util.h"
+#include "base/command_line.h"
+#include "base/format_macros.h"
+#include "base/strings/stringprintf.h"
+#include "ui/aura/window.h"
+
+namespace ash {
+namespace internal {
+
+class TouchHudTest : public test::AshTestBase {
+ public:
+ TouchHudTest() {}
+ virtual ~TouchHudTest() {}
+
+ virtual void SetUp() OVERRIDE {
+ // Add ash-touch-hud flag to enable touch HUD. This flag should be set
+ // before Ash environment is set up, i.e., before
+ // test::AshTestBase::SetUp().
+ CommandLine::ForCurrentProcess()->AppendSwitch(
+ ash::switches::kAshTouchHud);
+
+ test::AshTestBase::SetUp();
+
+ // Initialize display infos. They should be initialized after Ash
+ // environment is set up, i.e., after test::AshTestBase::SetUp().
+ internal_display_id_ = test::DisplayManagerTestApi(GetDisplayManager()).
+ SetFirstDisplayAsInternalDisplay();
+ external_display_id_ = 10;
+ mirrored_display_id_ = 11;
+
+ internal_display_info_ =
+ CreateDisplayInfo(internal_display_id_, gfx::Rect(0, 0, 500, 500));
+ external_display_info_ =
+ CreateDisplayInfo(external_display_id_, gfx::Rect(1, 1, 100, 100));
+ mirrored_display_info_ =
+ CreateDisplayInfo(mirrored_display_id_, gfx::Rect(0, 0, 100, 100));
+ }
+
+ gfx::Display GetPrimaryDisplay() {
+ return Shell::GetScreen()->GetPrimaryDisplay();
+ }
+
+ const gfx::Display& GetSecondaryDisplay() {
+ return ScreenAsh::GetSecondaryDisplay();
+ }
+
+ void SetupSingleDisplay() {
+ display_info_list_.clear();
+ display_info_list_.push_back(internal_display_info_);
+ GetDisplayManager()->OnNativeDisplaysChanged(display_info_list_);
+ }
+
+ void SetupDualDisplays() {
+ display_info_list_.clear();
+ display_info_list_.push_back(internal_display_info_);
+ display_info_list_.push_back(external_display_info_);
+ GetDisplayManager()->OnNativeDisplaysChanged(display_info_list_);
+ }
+
+ void SetInternalAsPrimary() {
+ const gfx::Display& internal_display =
+ GetDisplayManager()->GetDisplayForId(internal_display_id_);
+ GetDisplayController()->SetPrimaryDisplay(internal_display);
+ }
+
+ void SetExternalAsPrimary() {
+ const gfx::Display& external_display =
+ GetDisplayManager()->GetDisplayForId(external_display_id_);
+ GetDisplayController()->SetPrimaryDisplay(external_display);
+ }
+
+ void MirrorDisplays() {
+ DCHECK_EQ(2U, display_info_list_.size());
+ DCHECK_EQ(internal_display_id_, display_info_list_[0].id());
+ DCHECK_EQ(external_display_id_, display_info_list_[1].id());
+ display_info_list_[1] = mirrored_display_info_;
+ GetDisplayManager()->OnNativeDisplaysChanged(display_info_list_);
+ }
+
+ void UnmirrorDisplays() {
+ DCHECK_EQ(2U, display_info_list_.size());
+ DCHECK_EQ(internal_display_id_, display_info_list_[0].id());
+ DCHECK_EQ(mirrored_display_id_, display_info_list_[1].id());
+ display_info_list_[1] = external_display_info_;
+ GetDisplayManager()->OnNativeDisplaysChanged(display_info_list_);
+ }
+
+ void RemoveInternalDisplay() {
+ DCHECK_LT(0U, display_info_list_.size());
+ DCHECK_EQ(internal_display_id_, display_info_list_[0].id());
+ display_info_list_.erase(display_info_list_.begin());
+ GetDisplayManager()->OnNativeDisplaysChanged(display_info_list_);
+ }
+
+ void RemoveExternalDisplay() {
+ DCHECK_EQ(2U, display_info_list_.size());
+ display_info_list_.pop_back();
+ GetDisplayManager()->OnNativeDisplaysChanged(display_info_list_);
+ }
+
+ void AddInternalDisplay() {
+ DCHECK_EQ(0U, display_info_list_.size());
+ display_info_list_.push_back(internal_display_info_);
+ GetDisplayManager()->OnNativeDisplaysChanged(display_info_list_);
+ }
+
+ void AddExternalDisplay() {
+ DCHECK_EQ(1U, display_info_list_.size());
+ display_info_list_.push_back(external_display_info_);
+ GetDisplayManager()->OnNativeDisplaysChanged(display_info_list_);
+ }
+
+ int64 internal_display_id() const {
+ return internal_display_id_;
+ }
+
+ int64 external_display_id() const {
+ return external_display_id_;
+ }
+
+ void CheckInternalDisplay() {
+ EXPECT_NE(static_cast<internal::TouchObserverHUD*>(NULL),
+ GetInternalTouchHud());
+ EXPECT_EQ(internal_display_id(), GetInternalTouchHud()->display_id_);
+ EXPECT_EQ(GetInternalRootWindow(), GetInternalTouchHud()->root_window_);
+ EXPECT_EQ(GetInternalRootWindow(),
+ GetInternalTouchHud()->widget_->GetNativeView()->GetRootWindow());
+ EXPECT_EQ(GetInternalDisplay().size(),
+ GetInternalTouchHud()->widget_->GetWindowBoundsInScreen().size());
+ }
+
+ void CheckExternalDisplay() {
+ EXPECT_NE(static_cast<internal::TouchObserverHUD*>(NULL),
+ GetExternalTouchHud());
+ EXPECT_EQ(external_display_id(), GetExternalTouchHud()->display_id_);
+ EXPECT_EQ(GetExternalRootWindow(), GetExternalTouchHud()->root_window_);
+ EXPECT_EQ(GetExternalRootWindow(),
+ GetExternalTouchHud()->widget_->GetNativeView()->GetRootWindow());
+ EXPECT_EQ(GetExternalDisplay().size(),
+ GetExternalTouchHud()->widget_->GetWindowBoundsInScreen().size());
+ }
+
+ private:
+ DisplayManager* GetDisplayManager() {
+ return Shell::GetInstance()->display_manager();
+ }
+
+ DisplayController* GetDisplayController() {
+ return Shell::GetInstance()->display_controller();
+ }
+
+ const gfx::Display& GetInternalDisplay() {
+ return GetDisplayManager()->GetDisplayForId(internal_display_id_);
+ }
+
+ const gfx::Display& GetExternalDisplay() {
+ return GetDisplayManager()->GetDisplayForId(external_display_id_);
+ }
+
+ aura::RootWindow* GetInternalRootWindow() {
+ return GetDisplayController()->GetRootWindowForDisplayId(
+ internal_display_id_);
+ }
+
+ aura::RootWindow* GetExternalRootWindow() {
+ return GetDisplayController()->GetRootWindowForDisplayId(
+ external_display_id_);
+ }
+
+ aura::RootWindow* GetPrimaryRootWindow() {
+ const gfx::Display& display = GetPrimaryDisplay();
+ return GetDisplayController()->GetRootWindowForDisplayId(display.id());
+ }
+
+ aura::RootWindow* GetSecondaryRootWindow() {
+ const gfx::Display& display = GetSecondaryDisplay();
+ return GetDisplayController()->GetRootWindowForDisplayId(display.id());
+ }
+
+ internal::RootWindowController* GetInternalRootController() {
+ aura::RootWindow* root = GetInternalRootWindow();
+ return GetRootWindowController(root);
+ }
+
+ internal::RootWindowController* GetExternalRootController() {
+ aura::RootWindow* root = GetExternalRootWindow();
+ return GetRootWindowController(root);
+ }
+
+ internal::RootWindowController* GetPrimaryRootController() {
+ aura::RootWindow* root = GetPrimaryRootWindow();
+ return GetRootWindowController(root);
+ }
+
+ internal::RootWindowController* GetSecondaryRootController() {
+ aura::RootWindow* root = GetSecondaryRootWindow();
+ return GetRootWindowController(root);
+ }
+
+ internal::TouchObserverHUD* GetInternalTouchHud() {
+ return GetInternalRootController()->touch_hud_debug();
+ }
+
+ internal::TouchObserverHUD* GetExternalTouchHud() {
+ return GetExternalRootController()->touch_hud_debug();
+ }
+
+ internal::TouchObserverHUD* GetPrimaryTouchHud() {
+ return GetPrimaryRootController()->touch_hud_debug();
+ }
+
+ internal::TouchObserverHUD* GetSecondaryTouchHud() {
+ return GetSecondaryRootController()->touch_hud_debug();
+ }
+
+ DisplayInfo CreateDisplayInfo(int64 id, const gfx::Rect& bounds) {
+ DisplayInfo info(id, base::StringPrintf("x-%" PRId64, id), false);
+ info.SetBounds(bounds);
+ return info;
+ }
+
+ int64 internal_display_id_;
+ int64 external_display_id_;
+ int64 mirrored_display_id_;
+ DisplayInfo internal_display_info_;
+ DisplayInfo external_display_info_;
+ DisplayInfo mirrored_display_info_;
+
+ std::vector<DisplayInfo> display_info_list_;
+
+ DISALLOW_COPY_AND_ASSIGN(TouchHudTest);
+};
+
+// Checks if touch HUDs are correctly initialized for displays.
+TEST_F(TouchHudTest, Basic) {
+ if (!SupportsMultipleDisplays())
+ return;
+
+ // Setup a dual display setting.
+ SetupDualDisplays();
+
+ // Check if touch HUDs are set correctly and associated with appropriate
+ // displays.
+ CheckInternalDisplay();
+ CheckExternalDisplay();
+}
+
+// Checks if touch HUDs are correctly handled when primary display is changed.
+TEST_F(TouchHudTest, SwapPrimaryDisplay) {
+ if (!SupportsMultipleDisplays())
+ return;
+
+ // Setup a dual display setting.
+ SetupDualDisplays();
+
+ // Set the primary display to the external one.
+ SetExternalAsPrimary();
+
+ // Check if displays' touch HUDs are not swapped as root windows are.
+ EXPECT_EQ(external_display_id(), GetPrimaryDisplay().id());
+ EXPECT_EQ(internal_display_id(), GetSecondaryDisplay().id());
+ CheckInternalDisplay();
+ CheckExternalDisplay();
+
+ // Set the primary display back to the internal one.
+ SetInternalAsPrimary();
+
+ // Check if displays' touch HUDs are not swapped back as root windows are.
+ EXPECT_EQ(internal_display_id(), GetPrimaryDisplay().id());
+ EXPECT_EQ(external_display_id(), GetSecondaryDisplay().id());
+ CheckInternalDisplay();
+ CheckExternalDisplay();
+}
+
+// Checks if touch HUDs are correctly handled when displays are mirrored.
+TEST_F(TouchHudTest, MirrorDisplays) {
+ if (!SupportsMultipleDisplays())
+ return;
+
+ // Setup a dual display setting.
+ SetupDualDisplays();
+
+ // Mirror displays.
+ MirrorDisplays();
+
+ // Check if the internal display is intact.
+ EXPECT_EQ(internal_display_id(), GetPrimaryDisplay().id());
+ CheckInternalDisplay();
+
+ // Unmirror displays.
+ UnmirrorDisplays();
+
+ // Check if external display is added back correctly.
+ EXPECT_EQ(internal_display_id(), GetPrimaryDisplay().id());
+ EXPECT_EQ(external_display_id(), GetSecondaryDisplay().id());
+ CheckInternalDisplay();
+ CheckExternalDisplay();
+}
+
+// Checks if touch HUDs are correctly handled when displays are mirrored after
+// setting the external display as the primary one.
+TEST_F(TouchHudTest, SwapPrimaryThenMirrorDisplays) {
+ if (!SupportsMultipleDisplays())
+ return;
+
+ // Setup a dual display setting.
+ SetupDualDisplays();
+
+ // Set the primary display to the external one.
+ SetExternalAsPrimary();
+
+ // Mirror displays.
+ MirrorDisplays();
+
+ // Check if the internal display is set as the primary one.
+ EXPECT_EQ(internal_display_id(), GetPrimaryDisplay().id());
+ CheckInternalDisplay();
+
+ // Unmirror displays.
+ UnmirrorDisplays();
+
+ // Check if the external display is added back as the primary display and
+ // touch HUDs are set correctly.
+ EXPECT_EQ(external_display_id(), GetPrimaryDisplay().id());
+ EXPECT_EQ(internal_display_id(), GetSecondaryDisplay().id());
+ CheckInternalDisplay();
+ CheckExternalDisplay();
+}
+
+// Checks if touch HUDs are correctly handled when the external display, which
+// is the secondary one, is removed.
+TEST_F(TouchHudTest, RemoveSecondaryDisplay) {
+ if (!SupportsMultipleDisplays())
+ return;
+
+ // Setup a dual display setting.
+ SetupDualDisplays();
+
+ // Remove external display which is the secondary one.
+ RemoveExternalDisplay();
+
+ // Check if the internal display is intact.
+ EXPECT_EQ(internal_display_id(), GetPrimaryDisplay().id());
+ CheckInternalDisplay();
+
+ // Add external display back.
+ AddExternalDisplay();
+
+ // Check if displays' touch HUDs are set correctly.
+ EXPECT_EQ(internal_display_id(), GetPrimaryDisplay().id());
+ EXPECT_EQ(external_display_id(), GetSecondaryDisplay().id());
+ CheckInternalDisplay();
+ CheckExternalDisplay();
+}
+
+// Checks if touch HUDs are correctly handled when the external display, which
+// is set as the primary display, is removed.
+TEST_F(TouchHudTest, RemovePrimaryDisplay) {
+ if (!SupportsMultipleDisplays())
+ return;
+
+ // Setup a dual display setting.
+ SetupDualDisplays();
+
+ // Set the primary display to the external one.
+ SetExternalAsPrimary();
+
+ // Remove the external display which is the primary display.
+ RemoveExternalDisplay();
+
+ // Check if the internal display is set as the primary one.
+ EXPECT_EQ(internal_display_id(), GetPrimaryDisplay().id());
+ CheckInternalDisplay();
+
+ // Add the external display back.
+ AddExternalDisplay();
+
+ // Check if the external display is set as primary and touch HUDs are set
+ // correctly.
+ EXPECT_EQ(external_display_id(), GetPrimaryDisplay().id());
+ EXPECT_EQ(internal_display_id(), GetSecondaryDisplay().id());
+ CheckInternalDisplay();
+ CheckExternalDisplay();
+}
+
+// Checks if touch HUDs are correctly handled when all displays are removed.
+TEST_F(TouchHudTest, Headless) {
+ if (!SupportsMultipleDisplays())
+ return;
+
+ // Setup a single display setting.
+ SetupSingleDisplay();
+
+ // Remove the only display which is the internal one.
+ RemoveInternalDisplay();
+
+ // Add the internal display back.
+ AddInternalDisplay();
+
+ // Check if the display's touch HUD is set correctly.
+ EXPECT_EQ(internal_display_id(), GetPrimaryDisplay().id());
+ CheckInternalDisplay();
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/touch/touch_uma.cc b/chromium/ash/touch/touch_uma.cc
new file mode 100644
index 00000000000..f8ab0f17d66
--- /dev/null
+++ b/chromium/ash/touch/touch_uma.cc
@@ -0,0 +1,437 @@
+// Copyright (c) 2012 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.
+
+#include "ash/touch/touch_uma.h"
+
+#include "ash/shell_delegate.h"
+#include "base/metrics/histogram.h"
+#include "base/strings/stringprintf.h"
+#include "ui/aura/env.h"
+#include "ui/aura/root_window.h"
+#include "ui/aura/window.h"
+#include "ui/aura/window_property.h"
+#include "ui/base/events/event.h"
+#include "ui/base/events/event_utils.h"
+#include "ui/gfx/point_conversions.h"
+
+#if defined(USE_XI2_MT)
+#include <X11/extensions/XInput2.h>
+#include <X11/Xlib.h>
+#endif
+
+namespace {
+
+enum UMAEventType {
+ UMA_ET_UNKNOWN,
+ UMA_ET_TOUCH_RELEASED,
+ UMA_ET_TOUCH_PRESSED,
+ UMA_ET_TOUCH_MOVED,
+ UMA_ET_TOUCH_STATIONARY,
+ UMA_ET_TOUCH_CANCELLED,
+ UMA_ET_GESTURE_SCROLL_BEGIN,
+ UMA_ET_GESTURE_SCROLL_END,
+ UMA_ET_GESTURE_SCROLL_UPDATE,
+ UMA_ET_GESTURE_TAP,
+ UMA_ET_GESTURE_TAP_DOWN,
+ UMA_ET_GESTURE_BEGIN,
+ UMA_ET_GESTURE_END,
+ UMA_ET_GESTURE_DOUBLE_TAP,
+ UMA_ET_GESTURE_TRIPLE_TAP,
+ UMA_ET_GESTURE_TWO_FINGER_TAP,
+ UMA_ET_GESTURE_PINCH_BEGIN,
+ UMA_ET_GESTURE_PINCH_END,
+ UMA_ET_GESTURE_PINCH_UPDATE,
+ UMA_ET_GESTURE_LONG_PRESS,
+ UMA_ET_GESTURE_MULTIFINGER_SWIPE,
+ UMA_ET_SCROLL,
+ UMA_ET_SCROLL_FLING_START,
+ UMA_ET_SCROLL_FLING_CANCEL,
+ UMA_ET_GESTURE_MULTIFINGER_SWIPE_3,
+ UMA_ET_GESTURE_MULTIFINGER_SWIPE_4P, // 4+ fingers
+ UMA_ET_GESTURE_SCROLL_UPDATE_2,
+ UMA_ET_GESTURE_SCROLL_UPDATE_3,
+ UMA_ET_GESTURE_SCROLL_UPDATE_4P,
+ UMA_ET_GESTURE_PINCH_UPDATE_3,
+ UMA_ET_GESTURE_PINCH_UPDATE_4P,
+ UMA_ET_GESTURE_LONG_TAP,
+ // NOTE: Add new event types only immediately above this line. Make sure to
+ // update the enum list in tools/histogram/histograms.xml accordingly.
+ UMA_ET_COUNT
+};
+
+struct WindowTouchDetails {
+ // Move and start times of the touch points. The key is the touch-id.
+ std::map<int, base::TimeDelta> last_move_time_;
+ std::map<int, base::TimeDelta> last_start_time_;
+
+ // The first and last positions of the touch points.
+ std::map<int, gfx::Point> start_touch_position_;
+ std::map<int, gfx::Point> last_touch_position_;
+
+ // Last time-stamp of the last touch-end event.
+ base::TimeDelta last_release_time_;
+
+ // Stores the time of the last touch released on this window (if there was a
+ // multi-touch gesture on the window, then this is the release-time of the
+ // last touch on the window).
+ base::TimeDelta last_mt_time_;
+};
+
+DEFINE_OWNED_WINDOW_PROPERTY_KEY(WindowTouchDetails,
+ kWindowTouchDetails,
+ NULL);
+
+
+UMAEventType UMAEventTypeFromEvent(const ui::Event& event) {
+ switch (event.type()) {
+ case ui::ET_TOUCH_RELEASED:
+ return UMA_ET_TOUCH_RELEASED;
+ case ui::ET_TOUCH_PRESSED:
+ return UMA_ET_TOUCH_PRESSED;
+ case ui::ET_TOUCH_MOVED:
+ return UMA_ET_TOUCH_MOVED;
+ case ui::ET_TOUCH_STATIONARY:
+ return UMA_ET_TOUCH_STATIONARY;
+ case ui::ET_TOUCH_CANCELLED:
+ return UMA_ET_TOUCH_CANCELLED;
+ case ui::ET_GESTURE_SCROLL_BEGIN:
+ return UMA_ET_GESTURE_SCROLL_BEGIN;
+ case ui::ET_GESTURE_SCROLL_END:
+ return UMA_ET_GESTURE_SCROLL_END;
+ case ui::ET_GESTURE_SCROLL_UPDATE: {
+ const ui::GestureEvent& gesture =
+ static_cast<const ui::GestureEvent&>(event);
+ if (gesture.details().touch_points() >= 4)
+ return UMA_ET_GESTURE_SCROLL_UPDATE_4P;
+ else if (gesture.details().touch_points() == 3)
+ return UMA_ET_GESTURE_SCROLL_UPDATE_3;
+ else if (gesture.details().touch_points() == 2)
+ return UMA_ET_GESTURE_SCROLL_UPDATE_2;
+ return UMA_ET_GESTURE_SCROLL_UPDATE;
+ }
+ case ui::ET_GESTURE_TAP: {
+ const ui::GestureEvent& gesture =
+ static_cast<const ui::GestureEvent&>(event);
+ int tap_count = gesture.details().tap_count();
+ if (tap_count == 1)
+ return UMA_ET_GESTURE_TAP;
+ if (tap_count == 2)
+ return UMA_ET_GESTURE_DOUBLE_TAP;
+ if (tap_count == 3)
+ return UMA_ET_GESTURE_TRIPLE_TAP;
+ NOTREACHED() << "Received tap with tapcount " << tap_count;
+ return UMA_ET_UNKNOWN;
+ }
+ case ui::ET_GESTURE_TAP_DOWN:
+ return UMA_ET_GESTURE_TAP_DOWN;
+ case ui::ET_GESTURE_BEGIN:
+ return UMA_ET_GESTURE_BEGIN;
+ case ui::ET_GESTURE_END:
+ return UMA_ET_GESTURE_END;
+ case ui::ET_GESTURE_TWO_FINGER_TAP:
+ return UMA_ET_GESTURE_TWO_FINGER_TAP;
+ case ui::ET_GESTURE_PINCH_BEGIN:
+ return UMA_ET_GESTURE_PINCH_BEGIN;
+ case ui::ET_GESTURE_PINCH_END:
+ return UMA_ET_GESTURE_PINCH_END;
+ case ui::ET_GESTURE_PINCH_UPDATE: {
+ const ui::GestureEvent& gesture =
+ static_cast<const ui::GestureEvent&>(event);
+ if (gesture.details().touch_points() >= 4)
+ return UMA_ET_GESTURE_PINCH_UPDATE_4P;
+ else if (gesture.details().touch_points() == 3)
+ return UMA_ET_GESTURE_PINCH_UPDATE_3;
+ return UMA_ET_GESTURE_PINCH_UPDATE;
+ }
+ case ui::ET_GESTURE_LONG_PRESS:
+ return UMA_ET_GESTURE_LONG_PRESS;
+ case ui::ET_GESTURE_LONG_TAP:
+ return UMA_ET_GESTURE_LONG_TAP;
+ case ui::ET_GESTURE_MULTIFINGER_SWIPE: {
+ const ui::GestureEvent& gesture =
+ static_cast<const ui::GestureEvent&>(event);
+ if (gesture.details().touch_points() >= 4)
+ return UMA_ET_GESTURE_MULTIFINGER_SWIPE_4P;
+ else if (gesture.details().touch_points() == 3)
+ return UMA_ET_GESTURE_MULTIFINGER_SWIPE_3;
+ return UMA_ET_GESTURE_MULTIFINGER_SWIPE;
+ }
+ case ui::ET_SCROLL:
+ return UMA_ET_SCROLL;
+ case ui::ET_SCROLL_FLING_START:
+ return UMA_ET_SCROLL_FLING_START;
+ case ui::ET_SCROLL_FLING_CANCEL:
+ return UMA_ET_SCROLL_FLING_CANCEL;
+ default:
+ return UMA_ET_UNKNOWN;
+ }
+}
+
+}
+
+namespace ash {
+
+// static
+TouchUMA* TouchUMA::GetInstance() {
+ return Singleton<TouchUMA>::get();
+}
+
+void TouchUMA::RecordGestureEvent(aura::Window* target,
+ const ui::GestureEvent& event) {
+ UMA_HISTOGRAM_ENUMERATION("Ash.GestureCreated",
+ UMAEventTypeFromEvent(event),
+ UMA_ET_COUNT);
+
+ GestureActionType action = FindGestureActionType(target, event);
+ RecordGestureAction(action);
+
+ if (event.type() == ui::ET_GESTURE_END &&
+ event.details().touch_points() == 2) {
+ WindowTouchDetails* details = target->GetProperty(kWindowTouchDetails);
+ if (!details) {
+ LOG(ERROR) << "Window received gesture events without receiving any touch"
+ " events";
+ return;
+ }
+ details->last_mt_time_ = event.time_stamp();
+ }
+}
+
+void TouchUMA::RecordGestureAction(GestureActionType action) {
+ if (action == GESTURE_UNKNOWN || action >= GESTURE_ACTION_COUNT)
+ return;
+ UMA_HISTOGRAM_ENUMERATION("Ash.GestureTarget", action,
+ GESTURE_ACTION_COUNT);
+}
+
+void TouchUMA::RecordTouchEvent(aura::Window* target,
+ const ui::TouchEvent& event) {
+ UMA_HISTOGRAM_CUSTOM_COUNTS("Ash.TouchRadius",
+ static_cast<int>(std::max(event.radius_x(), event.radius_y())),
+ 1, 500, 100);
+
+ UpdateBurstData(event);
+
+ WindowTouchDetails* details = target->GetProperty(kWindowTouchDetails);
+ if (!details) {
+ details = new WindowTouchDetails;
+ target->SetProperty(kWindowTouchDetails, details);
+ }
+
+ // Record the location of the touch points.
+ const int kBucketCountForLocation = 100;
+ const gfx::Rect bounds = target->GetRootWindow()->bounds();
+ const int bucket_size_x = std::max(1,
+ bounds.width() / kBucketCountForLocation);
+ const int bucket_size_y = std::max(1,
+ bounds.height() / kBucketCountForLocation);
+
+ gfx::Point position = event.root_location();
+
+ // Prefer raw event location (when available) over calibrated location.
+ if (event.HasNativeEvent()) {
+#if defined(USE_XI2_MT)
+ XEvent* xevent = event.native_event();
+ CHECK_EQ(GenericEvent, xevent->type);
+ XIEvent* xievent = static_cast<XIEvent*>(xevent->xcookie.data);
+ if (xievent->evtype == XI_TouchBegin ||
+ xievent->evtype == XI_TouchUpdate ||
+ xievent->evtype == XI_TouchEnd) {
+ XIDeviceEvent* device_event =
+ static_cast<XIDeviceEvent*>(xevent->xcookie.data);
+ position.SetPoint(static_cast<int>(device_event->event_x),
+ static_cast<int>(device_event->event_y));
+ } else {
+ position = ui::EventLocationFromNative(event.native_event());
+ }
+#else
+ position = ui::EventLocationFromNative(event.native_event());
+#endif
+ position = gfx::ToFlooredPoint(
+ gfx::ScalePoint(position, 1. / target->layer()->device_scale_factor()));
+ }
+
+ position.set_x(std::min(bounds.width() - 1, std::max(0, position.x())));
+ position.set_y(std::min(bounds.height() - 1, std::max(0, position.y())));
+
+ UMA_HISTOGRAM_CUSTOM_COUNTS("Ash.TouchPositionX",
+ position.x() / bucket_size_x,
+ 0, kBucketCountForLocation, kBucketCountForLocation + 1);
+ UMA_HISTOGRAM_CUSTOM_COUNTS("Ash.TouchPositionY",
+ position.y() / bucket_size_y,
+ 0, kBucketCountForLocation, kBucketCountForLocation + 1);
+
+ if (event.type() == ui::ET_TOUCH_PRESSED) {
+ Shell::GetInstance()->delegate()->RecordUserMetricsAction(
+ UMA_TOUCHSCREEN_TAP_DOWN);
+
+ details->last_start_time_[event.touch_id()] = event.time_stamp();
+ details->start_touch_position_[event.touch_id()] = event.root_location();
+ details->last_touch_position_[event.touch_id()] = event.location();
+
+ if (details->last_release_time_.ToInternalValue()) {
+ // Measuring the interval between a touch-release and the next
+ // touch-start is probably less useful when doing multi-touch (e.g.
+ // gestures, or multi-touch friendly apps). So count this only if the user
+ // hasn't done any multi-touch during the last 30 seconds.
+ base::TimeDelta diff = event.time_stamp() - details->last_mt_time_;
+ if (diff.InSeconds() > 30) {
+ base::TimeDelta gap = event.time_stamp() - details->last_release_time_;
+ UMA_HISTOGRAM_COUNTS_10000("Ash.TouchStartAfterEnd",
+ gap.InMilliseconds());
+ }
+ }
+
+ // Record the number of touch-points currently active for the window.
+ const int kMaxTouchPoints = 10;
+ UMA_HISTOGRAM_CUSTOM_COUNTS("Ash.ActiveTouchPoints",
+ details->last_start_time_.size(),
+ 1, kMaxTouchPoints, kMaxTouchPoints + 1);
+ } else if (event.type() == ui::ET_TOUCH_RELEASED) {
+ if (details->last_start_time_.count(event.touch_id())) {
+ base::TimeDelta duration = event.time_stamp() -
+ details->last_start_time_[event.touch_id()];
+ UMA_HISTOGRAM_COUNTS_100("Ash.TouchDuration", duration.InMilliseconds());
+
+ // Look for touches that were [almost] stationary for a long time.
+ const double kLongStationaryTouchDuration = 10;
+ const int kLongStationaryTouchDistanceSquared = 100;
+ if (duration.InSecondsF() > kLongStationaryTouchDuration) {
+ gfx::Vector2d distance = event.root_location() -
+ details->start_touch_position_[event.touch_id()];
+ if (distance.LengthSquared() < kLongStationaryTouchDistanceSquared) {
+ UMA_HISTOGRAM_CUSTOM_COUNTS("Ash.StationaryTouchDuration",
+ duration.InSeconds(),
+ kLongStationaryTouchDuration,
+ 1000,
+ 20);
+ }
+ }
+ }
+ details->last_start_time_.erase(event.touch_id());
+ details->last_move_time_.erase(event.touch_id());
+ details->start_touch_position_.erase(event.touch_id());
+ details->last_touch_position_.erase(event.touch_id());
+ details->last_release_time_ = event.time_stamp();
+ } else if (event.type() == ui::ET_TOUCH_MOVED) {
+ int distance = 0;
+ if (details->last_touch_position_.count(event.touch_id())) {
+ gfx::Point lastpos = details->last_touch_position_[event.touch_id()];
+ distance = abs(lastpos.x() - event.x()) + abs(lastpos.y() - event.y());
+ }
+
+ if (details->last_move_time_.count(event.touch_id())) {
+ base::TimeDelta move_delay = event.time_stamp() -
+ details->last_move_time_[event.touch_id()];
+ UMA_HISTOGRAM_CUSTOM_COUNTS("Ash.TouchMoveInterval",
+ move_delay.InMilliseconds(),
+ 1, 50, 25);
+ }
+
+ UMA_HISTOGRAM_CUSTOM_COUNTS("Ash.TouchMoveSteps", distance, 1, 1000, 50);
+
+ details->last_move_time_[event.touch_id()] = event.time_stamp();
+ details->last_touch_position_[event.touch_id()] = event.location();
+ }
+}
+
+TouchUMA::TouchUMA()
+ : touch_in_progress_(false),
+ burst_length_(0) {
+}
+
+TouchUMA::~TouchUMA() {
+}
+
+void TouchUMA::UpdateBurstData(const ui::TouchEvent& event) {
+ if (event.type() == ui::ET_TOUCH_PRESSED) {
+ if (!touch_in_progress_) {
+ base::TimeDelta difference = event.time_stamp() - last_touch_down_time_;
+ if (difference > base::TimeDelta::FromMilliseconds(250)) {
+ if (burst_length_) {
+ UMA_HISTOGRAM_COUNTS_100("Ash.TouchStartBurst",
+ std::min(burst_length_, 100));
+ }
+ burst_length_ = 1;
+ } else {
+ ++burst_length_;
+ }
+ }
+ touch_in_progress_ = true;
+ last_touch_down_time_ = event.time_stamp();
+ } else if (event.type() == ui::ET_TOUCH_RELEASED) {
+ if (!aura::Env::GetInstance()->is_touch_down())
+ touch_in_progress_ = false;
+ }
+}
+
+TouchUMA::GestureActionType TouchUMA::FindGestureActionType(
+ aura::Window* window,
+ const ui::GestureEvent& event) {
+ if (!window || window->GetRootWindow() == window) {
+ if (event.type() == ui::ET_GESTURE_SCROLL_BEGIN)
+ return GESTURE_BEZEL_SCROLL;
+ if (event.type() == ui::ET_GESTURE_BEGIN)
+ return GESTURE_BEZEL_DOWN;
+ return GESTURE_UNKNOWN;
+ }
+
+ std::string name = window ? window->name() : std::string();
+
+ const char kDesktopBackgroundView[] = "DesktopBackgroundView";
+ if (name == kDesktopBackgroundView) {
+ if (event.type() == ui::ET_GESTURE_SCROLL_BEGIN)
+ return GESTURE_DESKTOP_SCROLL;
+ if (event.type() == ui::ET_GESTURE_PINCH_BEGIN)
+ return GESTURE_DESKTOP_PINCH;
+ return GESTURE_UNKNOWN;
+ }
+
+ const char kWebPage[] = "RenderWidgetHostViewAura";
+ if (name == kWebPage) {
+ if (event.type() == ui::ET_GESTURE_PINCH_BEGIN)
+ return GESTURE_WEBPAGE_PINCH;
+ if (event.type() == ui::ET_GESTURE_SCROLL_BEGIN)
+ return GESTURE_WEBPAGE_SCROLL;
+ if (event.type() == ui::ET_GESTURE_TAP)
+ return GESTURE_WEBPAGE_TAP;
+ return GESTURE_UNKNOWN;
+ }
+
+ views::Widget* widget = views::Widget::GetWidgetForNativeView(window);
+ if (!widget)
+ return GESTURE_UNKNOWN;
+
+ views::View* view = widget->GetRootView()->
+ GetEventHandlerForPoint(event.location());
+ if (!view)
+ return GESTURE_UNKNOWN;
+
+ name = view->GetClassName();
+
+ const char kTabStrip[] = "TabStrip";
+ const char kTab[] = "BrowserTab";
+ if (name == kTabStrip || name == kTab) {
+ if (event.type() == ui::ET_GESTURE_SCROLL_BEGIN)
+ return GESTURE_TABSTRIP_SCROLL;
+ if (event.type() == ui::ET_GESTURE_PINCH_BEGIN)
+ return GESTURE_TABSTRIP_PINCH;
+ if (event.type() == ui::ET_GESTURE_TAP)
+ return GESTURE_TABSTRIP_TAP;
+ return GESTURE_UNKNOWN;
+ }
+
+ const char kOmnibox[] = "BrowserOmniboxViewViews";
+ if (name == kOmnibox) {
+ if (event.type() == ui::ET_GESTURE_SCROLL_BEGIN)
+ return GESTURE_OMNIBOX_SCROLL;
+ if (event.type() == ui::ET_GESTURE_PINCH_BEGIN)
+ return GESTURE_OMNIBOX_PINCH;
+ return GESTURE_UNKNOWN;
+ }
+
+ return GESTURE_UNKNOWN;
+}
+
+} // namespace ash
diff --git a/chromium/ash/touch/touch_uma.h b/chromium/ash/touch/touch_uma.h
new file mode 100644
index 00000000000..3e2c7fda3ec
--- /dev/null
+++ b/chromium/ash/touch/touch_uma.h
@@ -0,0 +1,83 @@
+// Copyright (c) 2012 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 ASH_TOUCH_TOUCH_OBSERVER_UMA_H_
+#define ASH_TOUCH_TOUCH_OBSERVER_UMA_H_
+
+#include <map>
+
+#include "ash/shell.h"
+#include "base/memory/singleton.h"
+#include "ui/gfx/point.h"
+#include "ui/views/widget/widget.h"
+
+namespace aura {
+class Window;
+}
+
+namespace ash {
+
+// Records some touch/gesture event specific details (e.g. what gestures are
+// targetted to which components etc.)
+class ASH_EXPORT TouchUMA {
+ public:
+ enum GestureActionType {
+ GESTURE_UNKNOWN,
+ GESTURE_OMNIBOX_PINCH,
+ GESTURE_OMNIBOX_SCROLL,
+ GESTURE_TABSTRIP_PINCH,
+ GESTURE_TABSTRIP_SCROLL,
+ GESTURE_BEZEL_SCROLL,
+ GESTURE_DESKTOP_SCROLL,
+ GESTURE_DESKTOP_PINCH,
+ GESTURE_WEBPAGE_PINCH,
+ GESTURE_WEBPAGE_SCROLL,
+ GESTURE_WEBPAGE_TAP,
+ GESTURE_TABSTRIP_TAP,
+ GESTURE_BEZEL_DOWN,
+ GESTURE_TABSWITCH_TAP,
+ GESTURE_TABNOSWITCH_TAP,
+ GESTURE_TABCLOSE_TAP,
+ GESTURE_NEWTAB_TAP,
+ GESTURE_ROOTVIEWTOP_TAP,
+ GESTURE_FRAMEMAXIMIZE_TAP,
+ GESTURE_FRAMEVIEW_TAP,
+ GESTURE_MAXIMIZE_DOUBLETAP,
+ // NOTE: Add new action types only immediately above this line. Also,
+ // make sure the enum list in tools/histogram/histograms.xml is
+ // updated with any change in here.
+ GESTURE_ACTION_COUNT
+ };
+
+ // Returns the singleton instance.
+ static TouchUMA* GetInstance();
+
+ void RecordGestureEvent(aura::Window* target,
+ const ui::GestureEvent& event);
+ void RecordGestureAction(GestureActionType action);
+ void RecordTouchEvent(aura::Window* target,
+ const ui::TouchEvent& event);
+
+ private:
+ friend struct DefaultSingletonTraits<TouchUMA>;
+
+ TouchUMA();
+ ~TouchUMA();
+
+ void UpdateBurstData(const ui::TouchEvent& event);
+ GestureActionType FindGestureActionType(aura::Window* window,
+ const ui::GestureEvent& event);
+
+ // These are used to measure the number of touch-start events we receive in a
+ // quick succession, regardless of the target window.
+ bool touch_in_progress_;
+ int burst_length_;
+ base::TimeDelta last_touch_down_time_;
+
+ DISALLOW_COPY_AND_ASSIGN(TouchUMA);
+};
+
+} // namespace ash
+
+#endif // ASH_TOUCH_TOUCH_OBSERVER_UMA_H_
diff --git a/chromium/ash/volume_control_delegate.h b/chromium/ash/volume_control_delegate.h
new file mode 100644
index 00000000000..a33a206d454
--- /dev/null
+++ b/chromium/ash/volume_control_delegate.h
@@ -0,0 +1,26 @@
+// Copyright (c) 2012 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 ASH_VOLUME_CONTROL_DELEGATE_H_
+#define ASH_VOLUME_CONTROL_DELEGATE_H_
+
+namespace ui {
+class Accelerator;
+} // namespace ui
+
+namespace ash {
+
+// Delegate for controlling the volume.
+class VolumeControlDelegate {
+ public:
+ virtual ~VolumeControlDelegate() {}
+
+ virtual bool HandleVolumeMute(const ui::Accelerator& accelerator) = 0;
+ virtual bool HandleVolumeDown(const ui::Accelerator& accelerator) = 0;
+ virtual bool HandleVolumeUp(const ui::Accelerator& accelerator) = 0;
+};
+
+} // namespace ash
+
+#endif // ASH_VOLUME_CONTROL_DELEGATE_H_
diff --git a/chromium/ash/wm/activation_controller.cc b/chromium/ash/wm/activation_controller.cc
new file mode 100644
index 00000000000..328c34cafa7
--- /dev/null
+++ b/chromium/ash/wm/activation_controller.cc
@@ -0,0 +1,413 @@
+// Copyright (c) 2012 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.
+
+#include "ash/wm/activation_controller.h"
+
+#include "ash/root_window_controller.h"
+#include "ash/shell.h"
+#include "ash/shell_window_ids.h"
+#include "ash/wm/activation_controller_delegate.h"
+#include "ash/wm/property_util.h"
+#include "ash/wm/window_util.h"
+#include "base/auto_reset.h"
+#include "ui/aura/client/activation_change_observer.h"
+#include "ui/aura/client/activation_delegate.h"
+#include "ui/aura/client/aura_constants.h"
+#include "ui/aura/client/focus_client.h"
+#include "ui/aura/env.h"
+#include "ui/aura/root_window.h"
+#include "ui/aura/window.h"
+#include "ui/aura/window_delegate.h"
+#include "ui/base/ui_base_types.h"
+#include "ui/compositor/layer.h"
+#include "ui/views/corewm/window_modality_controller.h"
+
+namespace ash {
+namespace internal {
+namespace {
+
+// These are the list of container ids of containers which may contain windows
+// that need to be activated in the order that they should be activated.
+const int kWindowContainerIds[] = {
+ kShellWindowId_LockSystemModalContainer,
+ kShellWindowId_SettingBubbleContainer,
+ kShellWindowId_LockScreenContainer,
+ kShellWindowId_SystemModalContainer,
+ kShellWindowId_AlwaysOnTopContainer,
+ kShellWindowId_AppListContainer,
+ kShellWindowId_DefaultContainer,
+
+ // Docked, panel, launcher and status are intentionally checked after other
+ // containers even though these layers are higher. The user expects their
+ // windows to be focused before these elements.
+ kShellWindowId_DockedContainer,
+ kShellWindowId_PanelContainer,
+ kShellWindowId_ShelfContainer,
+ kShellWindowId_StatusContainer,
+};
+
+bool BelongsToContainerWithEqualOrGreaterId(const aura::Window* window,
+ int container_id) {
+ for (; window; window = window->parent()) {
+ if (window->id() >= container_id)
+ return true;
+ }
+ return false;
+}
+
+// Returns true if children of |window| can be activated.
+// These are the only containers in which windows can receive focus.
+bool SupportsChildActivation(aura::Window* window) {
+ for (size_t i = 0; i < arraysize(kWindowContainerIds); i++) {
+ if (window->id() == kWindowContainerIds[i])
+ return true;
+ }
+ return false;
+}
+
+bool HasModalTransientChild(aura::Window* window) {
+ aura::Window::Windows::const_iterator it;
+ for (it = window->transient_children().begin();
+ it != window->transient_children().end();
+ ++it) {
+ if ((*it)->GetProperty(aura::client::kModalKey) == ui::MODAL_TYPE_WINDOW)
+ return true;
+ }
+ return false;
+}
+
+// See description in VisibilityMatches.
+enum ActivateVisibilityType {
+ TARGET_VISIBILITY,
+ CURRENT_VISIBILITY,
+};
+
+// Used by CanActivateWindowWithEvent() to test the visibility of a window.
+// This is used by two distinct code paths:
+// . when activating from an event we only care about the actual visibility.
+// . when activating because of a keyboard accelerator, in which case we
+// care about the TargetVisibility.
+bool VisibilityMatches(aura::Window* window, ActivateVisibilityType type) {
+ bool visible = (type == CURRENT_VISIBILITY) ? window->IsVisible() :
+ window->TargetVisibility();
+ return visible || wm::IsWindowMinimized(window) ||
+ (window->TargetVisibility() &&
+ (window->parent()->id() == kShellWindowId_DefaultContainer ||
+ window->parent()->id() == kShellWindowId_LockScreenContainer));
+}
+
+// Returns true if |window| can be activated or deactivated.
+// A window manager typically defines some notion of "top level window" that
+// supports activation/deactivation.
+bool CanActivateWindowWithEvent(aura::Window* window,
+ const ui::Event* event,
+ ActivateVisibilityType visibility_type) {
+ return window &&
+ VisibilityMatches(window, visibility_type) &&
+ (!aura::client::GetActivationDelegate(window) ||
+ aura::client::GetActivationDelegate(window)->ShouldActivate()) &&
+ SupportsChildActivation(window->parent()) &&
+ (BelongsToContainerWithEqualOrGreaterId(
+ window, kShellWindowId_SystemModalContainer) ||
+ !Shell::GetInstance()->IsSystemModalWindowOpen());
+}
+
+// When a modal window is activated, we bring its entire transient parent chain
+// to the front. This function must be called before the modal transient is
+// stacked at the top to ensure correct stacking order.
+void StackTransientParentsBelowModalWindow(aura::Window* window) {
+ if (window->GetProperty(aura::client::kModalKey) != ui::MODAL_TYPE_WINDOW)
+ return;
+
+ aura::Window* transient_parent = window->transient_parent();
+ while (transient_parent) {
+ transient_parent->parent()->StackChildAtTop(transient_parent);
+ transient_parent = transient_parent->transient_parent();
+ }
+}
+
+aura::Window* FindFocusableWindowFor(aura::Window* window) {
+ while (window && !window->CanFocus())
+ window = window->parent();
+ return window;
+}
+
+} // namespace
+
+////////////////////////////////////////////////////////////////////////////////
+// ActivationController, public:
+
+ActivationController::ActivationController(
+ aura::client::FocusClient* focus_client,
+ ActivationControllerDelegate* delegate)
+ : focus_client_(focus_client),
+ updating_activation_(false),
+ active_window_(NULL),
+ observer_manager_(this),
+ delegate_(delegate) {
+ aura::Env::GetInstance()->AddObserver(this);
+ focus_client_->AddObserver(this);
+}
+
+ActivationController::~ActivationController() {
+ aura::Env::GetInstance()->RemoveObserver(this);
+ focus_client_->RemoveObserver(this);
+}
+
+// static
+aura::Window* ActivationController::GetActivatableWindow(
+ aura::Window* window,
+ const ui::Event* event) {
+ aura::Window* parent = window->parent();
+ aura::Window* child = window;
+ while (parent) {
+ if (CanActivateWindowWithEvent(child, event, CURRENT_VISIBILITY))
+ return child;
+ // If |child| isn't activatable, but has transient parent, trace
+ // that path instead.
+ if (child->transient_parent())
+ return GetActivatableWindow(child->transient_parent(), event);
+ parent = parent->parent();
+ child = child->parent();
+ }
+ return NULL;
+}
+
+bool ActivationController::CanActivateWindow(aura::Window* window) const {
+ return CanActivateWindowWithEvent(window, NULL, TARGET_VISIBILITY) &&
+ !HasModalTransientChild(window);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// ActivationController, aura::client::ActivationClient implementation:
+
+void ActivationController::AddObserver(
+ aura::client::ActivationChangeObserver* observer) {
+ observers_.AddObserver(observer);
+}
+
+void ActivationController::RemoveObserver(
+ aura::client::ActivationChangeObserver* observer) {
+ observers_.RemoveObserver(observer);
+}
+
+void ActivationController::ActivateWindow(aura::Window* window) {
+ ActivateWindowWithEvent(window, NULL);
+}
+
+void ActivationController::DeactivateWindow(aura::Window* window) {
+ if (window)
+ ActivateNextWindow(window);
+}
+
+aura::Window* ActivationController::GetActiveWindow() {
+ return active_window_;
+}
+
+aura::Window* ActivationController::GetActivatableWindow(aura::Window* window) {
+ return GetActivatableWindow(window, NULL);
+}
+
+aura::Window* ActivationController::GetToplevelWindow(aura::Window* window) {
+ return GetActivatableWindow(window, NULL);
+}
+
+bool ActivationController::OnWillFocusWindow(aura::Window* window,
+ const ui::Event* event) {
+ return CanActivateWindowWithEvent(
+ GetActivatableWindow(window, event), event, CURRENT_VISIBILITY);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// ActivationController, aura::WindowObserver implementation:
+
+void ActivationController::OnWindowVisibilityChanged(aura::Window* window,
+ bool visible) {
+ if (!visible) {
+ aura::Window* next_window = ActivateNextWindow(window);
+ if (next_window && next_window->parent() == window->parent()) {
+ // Despite the activation change, we need to keep the window being hidden
+ // stacked above the new window so it stays on top as it animates away.
+ window->layer()->parent()->StackAbove(window->layer(),
+ next_window->layer());
+ }
+ }
+}
+
+void ActivationController::OnWindowDestroying(aura::Window* window) {
+ // Don't use wm::IsActiveWidnow in case the |window| has already been
+ // removed from the root tree.
+ if (active_window_ == window) {
+ active_window_ = NULL;
+ FOR_EACH_OBSERVER(aura::client::ActivationChangeObserver,
+ observers_,
+ OnWindowActivated(NULL, window));
+ ActivateWindow(GetTopmostWindowToActivate(window));
+ }
+ observer_manager_.Remove(window);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// ActivationController, aura::EnvObserver implementation:
+
+void ActivationController::OnWindowInitialized(aura::Window* window) {
+ observer_manager_.Add(window);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// ActivationController, aura::RootWindowObserver implementation:
+
+void ActivationController::OnWindowFocused(aura::Window* gained_focus,
+ aura::Window* lost_focus) {
+ if (gained_focus)
+ ActivateWindow(GetActivatableWindow(gained_focus, NULL));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// ActivationController, ui::EventHandler implementation:
+
+void ActivationController::OnKeyEvent(ui::KeyEvent* event) {
+}
+
+void ActivationController::OnMouseEvent(ui::MouseEvent* event) {
+ if (event->type() == ui::ET_MOUSE_PRESSED)
+ FocusWindowWithEvent(event);
+}
+
+void ActivationController::OnScrollEvent(ui::ScrollEvent* event) {
+}
+
+void ActivationController::OnTouchEvent(ui::TouchEvent* event) {
+ if (event->type() == ui::ET_TOUCH_PRESSED)
+ FocusWindowWithEvent(event);
+}
+
+void ActivationController::OnGestureEvent(ui::GestureEvent* event) {
+ if (event->type() == ui::ET_GESTURE_BEGIN &&
+ event->details().touch_points() == 1) {
+ FocusWindowWithEvent(event);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// ActivationController, private:
+
+void ActivationController::ActivateWindowWithEvent(aura::Window* window,
+ const ui::Event* event) {
+ // Prevent recursion when called from focus.
+ if (updating_activation_)
+ return;
+ base::AutoReset<bool> in_activate_window(&updating_activation_, true);
+
+ // We allow the delegate to change which window gets activated, or to prevent
+ // activation changes.
+ aura::Window* original_active_window = window;
+ window = delegate_->WillActivateWindow(window);
+ // TODO(beng): note that this breaks the previous behavior where an activation
+ // attempt by a window behind the lock screen would at least
+ // restack that window frontmost within its container. fix this.
+ if (!window && original_active_window != window)
+ return;
+
+ // TODO(beng): This encapsulates additional Ash-specific restrictions on
+ // whether activation can change. Should move to the delegate.
+ if (window && !CanActivateWindowWithEvent(window, event, CURRENT_VISIBILITY))
+ return;
+
+ if (active_window_ == window)
+ return;
+
+ aura::Window* old_active = active_window_;
+ active_window_ = window;
+
+ if (window &&
+ !window->Contains(aura::client::GetFocusClient(window)->
+ GetFocusedWindow())) {
+ aura::client::GetFocusClient(window)->FocusWindow(window);
+ }
+
+ if (window) {
+ StackTransientParentsBelowModalWindow(window);
+ window->parent()->StackChildAtTop(window);
+ }
+
+ FOR_EACH_OBSERVER(aura::client::ActivationChangeObserver,
+ observers_,
+ OnWindowActivated(window, old_active));
+ if (aura::client::GetActivationChangeObserver(old_active)) {
+ aura::client::GetActivationChangeObserver(old_active)->OnWindowActivated(
+ window, old_active);
+ }
+ if (aura::client::GetActivationChangeObserver(window)) {
+ aura::client::GetActivationChangeObserver(window)->OnWindowActivated(
+ window, old_active);
+ }
+}
+
+aura::Window* ActivationController::ActivateNextWindow(aura::Window* window) {
+ aura::Window* next_window = NULL;
+ if (wm::IsActiveWindow(window)) {
+ next_window = GetTopmostWindowToActivate(window);
+ ActivateWindow(next_window);
+ }
+ return next_window;
+}
+
+aura::Window* ActivationController::GetTopmostWindowToActivate(
+ aura::Window* ignore) const {
+ size_t current_container_index = 0;
+ // If the container of the window losing focus is in the list, start from that
+ // container.
+ aura::RootWindow* root = ignore->GetRootWindow();
+ if (!root)
+ root = Shell::GetActiveRootWindow();
+ for (size_t i = 0; ignore && i < arraysize(kWindowContainerIds); i++) {
+ aura::Window* container = Shell::GetContainer(root, kWindowContainerIds[i]);
+ if (container && container->Contains(ignore)) {
+ current_container_index = i;
+ break;
+ }
+ }
+
+ // Look for windows to focus in that container and below.
+ aura::Window* window = NULL;
+ for (; !window && current_container_index < arraysize(kWindowContainerIds);
+ current_container_index++) {
+ aura::Window::Windows containers = Shell::GetContainersFromAllRootWindows(
+ kWindowContainerIds[current_container_index],
+ root);
+ for (aura::Window::Windows::const_iterator iter = containers.begin();
+ iter != containers.end() && !window; ++iter) {
+ window = GetTopmostWindowToActivateInContainer((*iter), ignore);
+ }
+ }
+ return window;
+}
+
+aura::Window* ActivationController::GetTopmostWindowToActivateInContainer(
+ aura::Window* container,
+ aura::Window* ignore) const {
+ for (aura::Window::Windows::const_reverse_iterator i =
+ container->children().rbegin();
+ i != container->children().rend();
+ ++i) {
+ if (*i != ignore &&
+ CanActivateWindowWithEvent(*i, NULL, CURRENT_VISIBILITY) &&
+ !wm::IsWindowMinimized(*i))
+ return *i;
+ }
+ return NULL;
+}
+
+void ActivationController::FocusWindowWithEvent(const ui::Event* event) {
+ aura::Window* window = static_cast<aura::Window*>(event->target());
+ window = delegate_->WillFocusWindow(window);
+ if (GetActiveWindow() != window) {
+ aura::client::GetFocusClient(window)->FocusWindow(
+ FindFocusableWindowFor(window));
+ }
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/wm/activation_controller.h b/chromium/ash/wm/activation_controller.h
new file mode 100644
index 00000000000..11e09b98f6e
--- /dev/null
+++ b/chromium/ash/wm/activation_controller.h
@@ -0,0 +1,126 @@
+// Copyright (c) 2012 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 ASH_WM_ACTIVATION_CONTROLLER_H_
+#define ASH_WM_ACTIVATION_CONTROLLER_H_
+
+#include "ash/ash_export.h"
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/observer_list.h"
+#include "base/scoped_observer.h"
+#include "ui/aura/client/activation_client.h"
+#include "ui/aura/client/focus_change_observer.h"
+#include "ui/aura/env_observer.h"
+#include "ui/aura/window_observer.h"
+#include "ui/base/events/event_handler.h"
+
+namespace aura {
+namespace client {
+class ActivationChangeObserver;
+class FocusClient;
+}
+}
+
+namespace ash {
+namespace internal {
+
+class ActivationControllerDelegate;
+
+// Exported for unit tests.
+class ASH_EXPORT ActivationController
+ : public aura::client::ActivationClient,
+ public aura::WindowObserver,
+ public aura::EnvObserver,
+ public aura::client::FocusChangeObserver,
+ public ui::EventHandler {
+ public:
+ // The ActivationController takes ownership of |delegate|.
+ ActivationController(aura::client::FocusClient* focus_client,
+ ActivationControllerDelegate* delegate);
+ virtual ~ActivationController();
+
+ // Returns true if |window| exists within a container that supports
+ // activation. |event| is the event responsible for initiating the change, or
+ // NULL if there is no event.
+ static aura::Window* GetActivatableWindow(aura::Window* window,
+ const ui::Event* event);
+
+ // Overridden from aura::client::ActivationClient:
+ virtual void AddObserver(
+ aura::client::ActivationChangeObserver* observer) OVERRIDE;
+ virtual void RemoveObserver(
+ aura::client::ActivationChangeObserver* observer) OVERRIDE;
+ virtual void ActivateWindow(aura::Window* window) OVERRIDE;
+ virtual void DeactivateWindow(aura::Window* window) OVERRIDE;
+ virtual aura::Window* GetActiveWindow() OVERRIDE;
+ virtual aura::Window* GetActivatableWindow(aura::Window* window) OVERRIDE;
+ virtual aura::Window* GetToplevelWindow(aura::Window* window) OVERRIDE;
+ virtual bool OnWillFocusWindow(aura::Window* window,
+ const ui::Event* event) OVERRIDE;
+ virtual bool CanActivateWindow(aura::Window* window) const OVERRIDE;
+
+ // Overridden from aura::WindowObserver:
+ virtual void OnWindowVisibilityChanged(aura::Window* window,
+ bool visible) OVERRIDE;
+ virtual void OnWindowDestroying(aura::Window* window) OVERRIDE;
+
+ // Overridden from aura::EnvObserver:
+ virtual void OnWindowInitialized(aura::Window* window) OVERRIDE;
+
+ // Overridden from aura::client::FocusChangeObserver:
+ virtual void OnWindowFocused(aura::Window* gained_focus,
+ aura::Window* lost_focus) OVERRIDE;
+
+ private:
+ // Overridden from ui::EventHandler:
+ virtual void OnKeyEvent(ui::KeyEvent* event) OVERRIDE;
+ virtual void OnMouseEvent(ui::MouseEvent* event) OVERRIDE;
+ virtual void OnScrollEvent(ui::ScrollEvent* event) OVERRIDE;
+ virtual void OnTouchEvent(ui::TouchEvent* event) OVERRIDE;
+ virtual void OnGestureEvent(ui::GestureEvent* event) OVERRIDE;
+
+ // Implementation of ActivateWindow() with an Event.
+ void ActivateWindowWithEvent(aura::Window* window,
+ const ui::Event* event);
+
+ // Shifts activation to the next window, ignoring |window|. Returns the next
+ // window.
+ aura::Window* ActivateNextWindow(aura::Window* window);
+
+ // Returns the next window that should be activated, ignoring |ignore|.
+ aura::Window* GetTopmostWindowToActivate(aura::Window* ignore) const;
+
+ // Returns the next window that should be activated in |container| ignoring
+ // the window |ignore|.
+ aura::Window* GetTopmostWindowToActivateInContainer(
+ aura::Window* container,
+ aura::Window* ignore) const;
+
+ // Called from the ActivationController's event handler implementation to
+ // handle focus to the |event|'s target. Not all targets are focusable or
+ // result in focus changes.
+ void FocusWindowWithEvent(const ui::Event* event);
+
+ aura::client::FocusClient* focus_client_;
+
+ // True inside ActivateWindow(). Used to prevent recursion of focus
+ // change notifications causing activation.
+ bool updating_activation_;
+
+ aura::Window* active_window_;
+
+ ObserverList<aura::client::ActivationChangeObserver> observers_;
+
+ ScopedObserver<aura::Window, aura::WindowObserver> observer_manager_;
+
+ scoped_ptr<ActivationControllerDelegate> delegate_;
+
+ DISALLOW_COPY_AND_ASSIGN(ActivationController);
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_WM_ACTIVATION_CONTROLLER_H_
diff --git a/chromium/ash/wm/activation_controller_delegate.h b/chromium/ash/wm/activation_controller_delegate.h
new file mode 100644
index 00000000000..5cebfc6c8ae
--- /dev/null
+++ b/chromium/ash/wm/activation_controller_delegate.h
@@ -0,0 +1,37 @@
+// Copyright (c) 2012 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 ASH_WM_ACTIVATION_CONTROLLER_DELEGATE_H_
+#define ASH_WM_ACTIVATION_CONTROLLER_DELEGATE_H_
+
+#include "ash/ash_export.h"
+
+namespace aura {
+class Window;
+}
+
+namespace ash {
+namespace internal {
+
+class ASH_EXPORT ActivationControllerDelegate {
+ public:
+ virtual ~ActivationControllerDelegate() {}
+
+ // Called when the ActivationController is about to activate |window|. The
+ // delegate gets an opportunity to take action and modify activation.
+ // Modification occurs via the return value:
+ // Returning |window| will activate |window|.
+ // Returning some other window will activate that window instead.
+ // Returning NULL will not change activation.
+ virtual aura::Window* WillActivateWindow(aura::Window* window) = 0;
+
+ // Called when the ActivationController is about to focus |window|. Returns
+ // the window that should be focused instead.
+ virtual aura::Window* WillFocusWindow(aura::Window* window) = 0;
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_WM_ACTIVATION_CONTROLLER_DELEGATE_H_
diff --git a/chromium/ash/wm/activation_controller_unittest.cc b/chromium/ash/wm/activation_controller_unittest.cc
new file mode 100644
index 00000000000..74ad9b85351
--- /dev/null
+++ b/chromium/ash/wm/activation_controller_unittest.cc
@@ -0,0 +1,575 @@
+// Copyright (c) 2012 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.
+
+#include "ash/wm/activation_controller.h"
+
+#include "ash/shell.h"
+#include "ash/shell_window_ids.h"
+#include "ash/test/ash_test_base.h"
+#include "ash/test/test_activation_delegate.h"
+#include "ash/wm/window_util.h"
+#include "ui/aura/client/aura_constants.h"
+#include "ui/aura/root_window.h"
+#include "ui/aura/test/event_generator.h"
+#include "ui/aura/test/test_window_delegate.h"
+#include "ui/aura/test/test_windows.h"
+#include "ui/compositor/layer.h"
+#include "ui/views/corewm/corewm_switches.h"
+
+#if defined(OS_WIN)
+// Windows headers define macros for these function names which screw with us.
+#if defined(CreateWindow)
+#undef CreateWindow
+#endif
+#endif
+
+namespace {
+
+// Containers used for the tests.
+const int kDefaultContainerID = -1; // Used to identify the default container.
+const int c2 = ash::internal::kShellWindowId_AlwaysOnTopContainer;
+const int c3 = ash::internal::kShellWindowId_LockScreenContainer;
+
+} // namespace
+
+namespace ash {
+namespace test {
+
+typedef test::AshTestBase ActivationControllerTest;
+
+// Utilities for a set of tests that test
+// ActivationController::GetTopmostWindowToActivate().
+class GetTopmostWindowToActivateTest : public ActivationControllerTest {
+ public:
+ GetTopmostWindowToActivateTest() : ad_1_(false), ad_3_(false) {}
+ virtual ~GetTopmostWindowToActivateTest() {}
+
+ // Overridden from ActivationControllerTest:
+ virtual void SetUp() OVERRIDE {
+ ActivationControllerTest::SetUp();
+ CreateWindows();
+ }
+ virtual void TearDown() OVERRIDE {
+ DestroyWindows();
+ ActivationControllerTest::TearDown();
+ }
+
+ protected:
+ aura::Window* w1() { return w1_.get(); }
+ aura::Window* w2() { return w2_.get(); }
+ aura::Window* w3() { return w3_.get(); }
+ aura::Window* w4() { return w4_.get(); }
+ aura::Window* w5() { return w5_.get(); }
+ aura::Window* w6() { return w6_.get(); }
+ aura::Window* w7() { return w7_.get(); }
+
+ void DestroyWindow2() {
+ w2_.reset();
+ }
+
+ private:
+ void CreateWindows() {
+ // Create four windows, the first and third are not activatable, the second
+ // and fourth are.
+ w1_.reset(CreateWindowInShell(1, &ad_1_));
+ w2_.reset(CreateWindowInShell(2, &ad_2_));
+ w3_.reset(CreateWindowInShell(3, &ad_3_));
+ w4_.reset(CreateWindowInShell(4, &ad_4_));
+ w5_.reset(CreateWindowWithID(5, &ad_5_, c2));
+ w6_.reset(CreateWindowWithID(6, &ad_6_, c2));
+ w7_.reset(CreateWindowWithID(7, &ad_7_, c3));
+ }
+
+ aura::Window* CreateWindowInShell(int id,
+ TestActivationDelegate* delegate) {
+ aura::Window* window = CreateTestWindowInShellWithDelegate(
+ &delegate_,
+ id,
+ gfx::Rect());
+ delegate->SetWindow(window);
+ return window;
+ }
+
+ aura::Window* CreateWindowWithID(int id,
+ TestActivationDelegate* delegate,
+ int container_id) {
+ aura::Window* parent =
+ Shell::GetContainer(Shell::GetPrimaryRootWindow(), container_id);
+ aura::Window* window = aura::test::CreateTestWindowWithDelegate(
+ &delegate_,
+ id,
+ gfx::Rect(),
+ parent);
+ delegate->SetWindow(window);
+ return window;
+ }
+
+ void DestroyWindows() {
+ w1_.reset();
+ w2_.reset();
+ w3_.reset();
+ w4_.reset();
+ w5_.reset();
+ w6_.reset();
+ w7_.reset();
+ }
+
+ aura::test::TestWindowDelegate delegate_;
+ TestActivationDelegate ad_1_;
+ TestActivationDelegate ad_2_;
+ TestActivationDelegate ad_3_;
+ TestActivationDelegate ad_4_;
+ TestActivationDelegate ad_5_;
+ TestActivationDelegate ad_6_;
+ TestActivationDelegate ad_7_;
+ scoped_ptr<aura::Window> w1_; // Non-activatable.
+ scoped_ptr<aura::Window> w2_; // Activatable.
+ scoped_ptr<aura::Window> w3_; // Non-activatable.
+ scoped_ptr<aura::Window> w4_; // Activatable.
+ scoped_ptr<aura::Window> w5_; // Activatable - Always on top.
+ scoped_ptr<aura::Window> w6_; // Activatable - Always on top.
+ scoped_ptr<aura::Window> w7_; // Activatable - Lock screen window.
+
+ DISALLOW_COPY_AND_ASSIGN(GetTopmostWindowToActivateTest);
+};
+
+// Hiding the active window should activate the next valid activatable window.
+TEST_F(GetTopmostWindowToActivateTest, HideActivatesNext) {
+ wm::ActivateWindow(w2());
+ EXPECT_TRUE(wm::IsActiveWindow(w2()));
+
+ w2()->Hide();
+ EXPECT_TRUE(wm::IsActiveWindow(w4()));
+}
+
+// Destroying the active window should activate the next valid activatable
+// window.
+TEST_F(GetTopmostWindowToActivateTest, DestroyActivatesNext) {
+ wm::ActivateWindow(w2());
+ EXPECT_TRUE(wm::IsActiveWindow(w2()));
+
+ DestroyWindow2();
+ EXPECT_EQ(NULL, w2());
+ EXPECT_TRUE(wm::IsActiveWindow(w4()));
+}
+
+// Deactivating the active window should activate the next valid activatable
+// window.
+TEST_F(GetTopmostWindowToActivateTest, DeactivateActivatesNext) {
+ wm::ActivateWindow(w2());
+ EXPECT_TRUE(wm::IsActiveWindow(w2()));
+
+ wm::DeactivateWindow(w2());
+ EXPECT_TRUE(wm::IsActiveWindow(w4()));
+}
+
+// Test that hiding a window in a higher container will activate another window
+// in that container.
+TEST_F(GetTopmostWindowToActivateTest, HideActivatesSameContainer) {
+ wm::ActivateWindow(w6());
+ EXPECT_TRUE(wm::IsActiveWindow(w6()));
+
+ w6()->Hide();
+ EXPECT_TRUE(wm::IsActiveWindow(w5()));
+}
+
+// Test that hiding the lock window will activate a window from the next highest
+// container.
+TEST_F(GetTopmostWindowToActivateTest, UnlockActivatesNextHighestContainer) {
+ wm::ActivateWindow(w7());
+ EXPECT_TRUE(wm::IsActiveWindow(w7()));
+
+ w7()->Hide();
+ EXPECT_TRUE(wm::IsActiveWindow(w6()));
+}
+
+// Test that hiding a window in a higher container with no other windows will
+// activate a window in a lower container.
+TEST_F(GetTopmostWindowToActivateTest, HideActivatesNextHighestContainer) {
+ w5()->Hide();
+ wm::ActivateWindow(w6());
+ EXPECT_TRUE(wm::IsActiveWindow(w6()));
+
+ w6()->Hide();
+ EXPECT_TRUE(wm::IsActiveWindow(w4()));
+}
+
+// Test if the clicking on a menu picks the transient parent as activatable
+// window.
+TEST_F(ActivationControllerTest, ClickOnMenu) {
+ aura::test::TestWindowDelegate wd;
+ TestActivationDelegate ad1;
+ TestActivationDelegate ad2(false);
+
+ scoped_ptr<aura::Window> w1(CreateTestWindowInShellWithDelegate(
+ &wd, 1, gfx::Rect(100, 100)));
+ ad1.SetWindow(w1.get());
+ EXPECT_EQ(NULL, wm::GetActiveWindow());
+
+ // Clicking on an activatable window activates the window.
+ aura::test::EventGenerator& generator(GetEventGenerator());
+ generator.MoveMouseToCenterOf(w1.get());
+ generator.ClickLeftButton();
+ EXPECT_TRUE(wm::IsActiveWindow(w1.get()));
+
+ // Creates a menu that covers the transient parent.
+ scoped_ptr<aura::Window> menu(CreateTestWindowInShellWithDelegateAndType(
+ &wd, aura::client::WINDOW_TYPE_MENU, 2, gfx::Rect(100, 100)));
+ ad2.SetWindow(menu.get());
+ w1->AddTransientChild(menu.get());
+
+ // Clicking on a menu whose transient parent is active window shouldn't
+ // change the active window.
+ generator.ClickLeftButton();
+ EXPECT_TRUE(wm::IsActiveWindow(w1.get()));
+}
+
+// Various assertions for activating/deactivating.
+TEST_F(ActivationControllerTest, Deactivate) {
+ aura::test::TestWindowDelegate d1;
+ aura::test::TestWindowDelegate d2;
+ scoped_ptr<aura::Window> w1(CreateTestWindowInShellWithDelegate(
+ &d1, 1, gfx::Rect()));
+ scoped_ptr<aura::Window> w2(CreateTestWindowInShellWithDelegate(
+ &d2, 2, gfx::Rect()));
+ aura::Window* parent = w1->parent();
+ parent->Show();
+ ASSERT_TRUE(parent);
+ ASSERT_EQ(2u, parent->children().size());
+ // Activate w2 and make sure it's active and frontmost.
+ wm::ActivateWindow(w2.get());
+ EXPECT_TRUE(wm::IsActiveWindow(w2.get()));
+ EXPECT_FALSE(wm::IsActiveWindow(w1.get()));
+ EXPECT_EQ(w2.get(), parent->children()[1]);
+
+ // Activate w1 and make sure it's active and frontmost.
+ wm::ActivateWindow(w1.get());
+ EXPECT_TRUE(wm::IsActiveWindow(w1.get()));
+ EXPECT_FALSE(wm::IsActiveWindow(w2.get()));
+ EXPECT_EQ(w1.get(), parent->children()[1]);
+
+ // Deactivate w1 and make sure w2 becomes active and frontmost.
+ wm::DeactivateWindow(w1.get());
+ EXPECT_FALSE(wm::IsActiveWindow(w1.get()));
+ EXPECT_TRUE(wm::IsActiveWindow(w2.get()));
+ EXPECT_EQ(w2.get(), parent->children()[1]);
+}
+
+// Verifies that when WindowDelegate::OnLostActive is invoked the window is not
+// active.
+TEST_F(ActivationControllerTest, NotActiveInLostActive) {
+ // TODO(beng): remove this test once the new focus controller is on.
+ if (views::corewm::UseFocusController())
+ return;
+
+ TestActivationDelegate ad1;
+ aura::test::TestWindowDelegate wd;
+ scoped_ptr<aura::Window> w1(CreateTestWindowInShellWithDelegate(
+ &wd, 1, gfx::Rect(10, 10, 50, 50)));
+ ad1.SetWindow(w1.get());
+ scoped_ptr<aura::Window> w2(CreateTestWindowInShellWithDelegate(
+ NULL, 1, gfx::Rect(10, 10, 50, 50)));
+
+ // Activate w1.
+ wm::ActivateWindow(w1.get());
+ EXPECT_TRUE(wm::IsActiveWindow(w1.get()));
+ EXPECT_EQ(1, ad1.activated_count());
+ // Should not have gotten a OnLostActive yet.
+ EXPECT_EQ(0, ad1.lost_active_count());
+
+ // Deactivate the active window.
+ wm::DeactivateWindow(w1.get());
+ EXPECT_FALSE(wm::IsActiveWindow(w1.get()));
+ EXPECT_EQ(1, ad1.lost_active_count());
+ EXPECT_FALSE(ad1.window_was_active());
+
+ // Activate w1 again. w1 should have gotten OnActivated.
+ wm::ActivateWindow(w1.get());
+ EXPECT_EQ(2, ad1.activated_count());
+ EXPECT_EQ(1, ad1.lost_active_count());
+
+ // Reset the delegate.
+ ad1.Clear();
+
+ // Now activate another window.
+ wm::ActivateWindow(w2.get());
+
+ // Should have gotten OnLostActive and w1 shoouldn't have been
+ // active window in OnLostActive.
+ EXPECT_EQ(0, ad1.activated_count());
+ EXPECT_EQ(1, ad1.lost_active_count());
+ EXPECT_FALSE(ad1.window_was_active());
+}
+
+// Verifies that focusing another window or its children causes it to become
+// active.
+TEST_F(ActivationControllerTest, FocusTriggersActivation) {
+ aura::test::TestWindowDelegate wd;
+ scoped_ptr<aura::Window> w1(CreateTestWindowInShellWithDelegate(
+ &wd, -1, gfx::Rect(50, 50)));
+ scoped_ptr<aura::Window> w2(CreateTestWindowInShellWithDelegate(
+ &wd, -2, gfx::Rect(50, 50)));
+ scoped_ptr<aura::Window> w21(aura::test::CreateTestWindowWithDelegate(
+ &wd, -21, gfx::Rect(50, 50), w2.get()));
+
+ wm::ActivateWindow(w1.get());
+ EXPECT_TRUE(wm::IsActiveWindow(w1.get()));
+ EXPECT_TRUE(w1->HasFocus());
+
+ w2->Focus();
+ EXPECT_TRUE(wm::IsActiveWindow(w2.get()));
+ EXPECT_TRUE(w2->HasFocus());
+
+ wm::ActivateWindow(w1.get());
+ EXPECT_TRUE(wm::IsActiveWindow(w1.get()));
+ EXPECT_TRUE(w1->HasFocus());
+
+ w21->Focus();
+ EXPECT_TRUE(wm::IsActiveWindow(w2.get()));
+ EXPECT_TRUE(w21->HasFocus());
+}
+
+// Verifies that we prevent all attempts to focus a child of a non-activatable
+// window from claiming focus to that window.
+TEST_F(ActivationControllerTest, PreventFocusToNonActivatableWindow) {
+ aura::test::TestWindowDelegate wd;
+ scoped_ptr<aura::Window> w1(CreateTestWindowInShellWithDelegate(
+ &wd, -1, gfx::Rect(50, 50)));
+ // The RootWindow is a non-activatable parent.
+ scoped_ptr<aura::Window> w2(aura::test::CreateTestWindowWithDelegate(
+ &wd, -2, gfx::Rect(50, 50), Shell::GetPrimaryRootWindow()));
+ scoped_ptr<aura::Window> w21(aura::test::CreateTestWindowWithDelegate(
+ &wd, -21, gfx::Rect(50, 50), w2.get()));
+
+ wm::ActivateWindow(w1.get());
+ EXPECT_TRUE(wm::IsActiveWindow(w1.get()));
+ EXPECT_TRUE(w1->HasFocus());
+
+ // Try activating |w2|. It's not a child of an activatable container, so it
+ // should neither be activated nor get focus.
+ wm::ActivateWindow(w2.get());
+ EXPECT_FALSE(wm::IsActiveWindow(w2.get()));
+ EXPECT_FALSE(w2->HasFocus());
+ EXPECT_TRUE(wm::IsActiveWindow(w1.get()));
+ EXPECT_TRUE(w1->HasFocus());
+
+ // Try focusing |w2|. Same rules apply.
+ w2->Focus();
+ EXPECT_FALSE(wm::IsActiveWindow(w2.get()));
+ EXPECT_FALSE(w2->HasFocus());
+ EXPECT_TRUE(wm::IsActiveWindow(w1.get()));
+ EXPECT_TRUE(w1->HasFocus());
+
+ // Try focusing |w21|. Same rules apply.
+ EXPECT_FALSE(wm::IsActiveWindow(w2.get()));
+ EXPECT_FALSE(w21->HasFocus());
+ EXPECT_TRUE(wm::IsActiveWindow(w1.get()));
+ EXPECT_TRUE(w1->HasFocus());
+}
+
+TEST_F(ActivationControllerTest, CanActivateWindowIteselfTest)
+{
+ aura::test::TestWindowDelegate wd;
+
+ // Normal Window
+ scoped_ptr<aura::Window> w1(CreateTestWindowInShellWithDelegate(
+ &wd, -1, gfx::Rect(50, 50)));
+ EXPECT_TRUE(wm::CanActivateWindow(w1.get()));
+
+ // The RootWindow is a non-activatable parent.
+ scoped_ptr<aura::Window> w2(aura::test::CreateTestWindowWithDelegate(
+ &wd, -2, gfx::Rect(50, 50), Shell::GetPrimaryRootWindow()));
+ scoped_ptr<aura::Window> w21(aura::test::CreateTestWindowWithDelegate(
+ &wd, -21, gfx::Rect(50, 50), w2.get()));
+ EXPECT_FALSE(wm::CanActivateWindow(w2.get()));
+ EXPECT_FALSE(wm::CanActivateWindow(w21.get()));
+
+ // The window has a transient child.
+ scoped_ptr<aura::Window> w3(CreateTestWindowInShellWithDelegate(
+ &wd, -3, gfx::Rect(50, 50)));
+ scoped_ptr<aura::Window> w31(CreateTestWindowInShellWithDelegate(
+ &wd, -31, gfx::Rect(50, 50)));
+ w3->AddTransientChild(w31.get());
+ EXPECT_TRUE(wm::CanActivateWindow(w3.get()));
+ EXPECT_TRUE(wm::CanActivateWindow(w31.get()));
+
+ // The window has a transient window-modal child.
+ scoped_ptr<aura::Window> w4(CreateTestWindowInShellWithDelegate(
+ &wd, -4, gfx::Rect(50, 50)));
+ scoped_ptr<aura::Window> w41(CreateTestWindowInShellWithDelegate(
+ &wd, -41, gfx::Rect(50, 50)));
+ w4->AddTransientChild(w41.get());
+ w41->SetProperty(aura::client::kModalKey, ui::MODAL_TYPE_WINDOW);
+ EXPECT_FALSE(wm::CanActivateWindow(w4.get()));
+ EXPECT_TRUE(wm::CanActivateWindow(w41.get()));
+
+ // The window has a transient system-modal child.
+ scoped_ptr<aura::Window> w5(CreateTestWindowInShellWithDelegate(
+ &wd, -5, gfx::Rect(50, 50)));
+ scoped_ptr<aura::Window> w51(CreateTestWindowInShellWithDelegate(
+ &wd, -51, gfx::Rect(50, 50)));
+ w5->AddTransientChild(w51.get());
+ w51->SetProperty(aura::client::kModalKey, ui::MODAL_TYPE_SYSTEM);
+ EXPECT_FALSE(wm::CanActivateWindow(w5.get()));
+ EXPECT_TRUE(wm::CanActivateWindow(w51.get()));
+}
+
+// Verifies code in ActivationController::OnWindowVisibilityChanged() that keeps
+// hiding windows layers stacked above the newly active window while they
+// animate away.
+// TODO(beng): This test now duplicates a test in:
+// ui/views/corewm/focus_controller_unittest.cc
+// ...and can be removed once the new focus controller is enabled.
+TEST_F(ActivationControllerTest, AnimateHideMaintainsStacking) {
+ aura::test::TestWindowDelegate wd;
+ scoped_ptr<aura::Window> w1(CreateTestWindowInShellWithDelegate(
+ &wd, -1, gfx::Rect(50, 50, 50, 50)));
+ scoped_ptr<aura::Window> w2(CreateTestWindowInShellWithDelegate(
+ &wd, -2, gfx::Rect(75, 75, 50, 50)));
+ wm::ActivateWindow(w2.get());
+ EXPECT_TRUE(wm::IsActiveWindow(w2.get()));
+ w2->Hide();
+ typedef std::vector<ui::Layer*> Layers;
+ const Layers& children = w1->parent()->layer()->children();
+ Layers::const_iterator w1_iter =
+ std::find(children.begin(), children.end(), w1->layer());
+ Layers::const_iterator w2_iter =
+ std::find(children.begin(), children.end(), w2->layer());
+ EXPECT_TRUE(w2_iter > w1_iter);
+}
+
+// Verifies that activating a minimized window would restore it.
+TEST_F(ActivationControllerTest, ActivateMinimizedWindow) {
+ aura::test::TestWindowDelegate wd;
+ scoped_ptr<aura::Window> w1(CreateTestWindowInShellWithDelegate(
+ &wd, -1, gfx::Rect(50, 50)));
+
+ wm::MinimizeWindow(w1.get());
+ EXPECT_TRUE(wm::IsWindowMinimized(w1.get()));
+
+ wm::ActivateWindow(w1.get());
+ EXPECT_TRUE(wm::IsActiveWindow(w1.get()));
+ EXPECT_FALSE(wm::IsWindowMinimized(w1.get()));
+}
+
+// Verifies that a minimized window would not be automatically activated as
+// a replacement active window.
+TEST_F(ActivationControllerTest, NoAutoActivateMinimizedWindow) {
+ aura::test::TestWindowDelegate wd;
+ scoped_ptr<aura::Window> w1(CreateTestWindowInShellWithDelegate(
+ &wd, -1, gfx::Rect(50, 50, 50, 50)));
+ scoped_ptr<aura::Window> w2(CreateTestWindowInShellWithDelegate(
+ &wd, -2, gfx::Rect(75, 75, 50, 50)));
+
+ wm::MinimizeWindow(w1.get());
+ EXPECT_TRUE(wm::IsWindowMinimized(w1.get()));
+
+ wm::ActivateWindow(w2.get());
+ EXPECT_TRUE(wm::IsActiveWindow(w2.get()));
+
+ w2->Hide();
+
+ EXPECT_FALSE(wm::IsActiveWindow(w1.get()));
+ EXPECT_TRUE(wm::IsWindowMinimized(w1.get()));
+}
+
+// Verifies that a window with a hidden layer can be activated.
+TEST_F(ActivationControllerTest, ActivateWithHiddenLayer) {
+ aura::test::TestWindowDelegate wd;
+ scoped_ptr<aura::Window> w1(CreateTestWindowInShellWithDelegate(
+ &wd, -1, gfx::Rect(50, 50, 50, 50)));
+
+ EXPECT_TRUE(wm::CanActivateWindow(w1.get()));
+ w1->layer()->SetVisible(false);
+ EXPECT_TRUE(wm::CanActivateWindow(w1.get()));
+}
+
+// Verifies that a unrelated window cannot be activated when in a system modal
+// dialog.
+TEST_F(ActivationControllerTest, DontActivateWindowWhenInSystemModalDialog) {
+ scoped_ptr<aura::Window> normal_window(CreateTestWindowInShellWithId(-1));
+ EXPECT_FALSE(wm::IsActiveWindow(normal_window.get()));
+ wm::ActivateWindow(normal_window.get());
+ EXPECT_TRUE(wm::IsActiveWindow(normal_window.get()));
+
+ // Create and activate a system modal window.
+ aura::Window* modal_container =
+ ash::Shell::GetContainer(
+ Shell::GetPrimaryRootWindow(),
+ ash::internal::kShellWindowId_SystemModalContainer);
+ scoped_ptr<aura::Window> modal_window(
+ aura::test::CreateTestWindowWithId(-2, modal_container));
+ modal_window->SetProperty(aura::client::kModalKey, ui::MODAL_TYPE_SYSTEM);
+ wm::ActivateWindow(modal_window.get());
+ EXPECT_TRUE(ash::Shell::GetInstance()->IsSystemModalWindowOpen());
+ EXPECT_FALSE(wm::IsActiveWindow(normal_window.get()));
+ EXPECT_TRUE(wm::IsActiveWindow(modal_window.get()));
+
+ // We try to but cannot activate the normal window while we
+ // have the system modal window.
+ wm::ActivateWindow(normal_window.get());
+ EXPECT_TRUE(ash::Shell::GetInstance()->IsSystemModalWindowOpen());
+ EXPECT_FALSE(wm::IsActiveWindow(normal_window.get()));
+ EXPECT_TRUE(wm::IsActiveWindow(modal_window.get()));
+
+ modal_window->Hide();
+ modal_window.reset();
+ EXPECT_FALSE(ash::Shell::GetInstance()->IsSystemModalWindowOpen());
+ EXPECT_TRUE(wm::IsActiveWindow(normal_window.get()));
+}
+
+// Verifies that a lock window can get focus even if lock
+// container is not visible (e.g. before animating it).
+TEST_F(ActivationControllerTest, ActivateLockScreen) {
+ aura::Window* lock_container =
+ Shell::GetContainer(Shell::GetPrimaryRootWindow(), c3);
+ aura::test::TestWindowDelegate wd;
+ scoped_ptr<aura::Window> w1(aura::test::CreateTestWindowWithDelegate(
+ &wd, -1, gfx::Rect(50, 50, 50, 50), lock_container));
+
+ lock_container->layer()->SetVisible(false);
+ w1->Focus();
+ EXPECT_TRUE(w1->HasFocus());
+}
+
+// Verifies that a next active window is chosen from current
+// active display.
+TEST_F(ActivationControllerTest, NextActiveWindowOnMultipleDisplays) {
+ if (!SupportsMultipleDisplays())
+ return;
+
+ UpdateDisplay("300x300,300x300");
+ Shell::RootWindowList root_windows = Shell::GetAllRootWindows();
+
+ scoped_ptr<aura::Window> w1_d1(CreateTestWindowInShellWithBounds(
+ gfx::Rect(10, 10, 100, 100)));
+ scoped_ptr<aura::Window> w2_d1(CreateTestWindowInShellWithBounds(
+ gfx::Rect(20, 20, 100, 100)));
+
+ EXPECT_EQ(root_windows[0], w1_d1->GetRootWindow());
+ EXPECT_EQ(root_windows[0], w2_d1->GetRootWindow());
+
+ scoped_ptr<aura::Window> w3_d2(CreateTestWindowInShellWithBounds(
+ gfx::Rect(310, 10, 100, 100)));
+ scoped_ptr<aura::Window> w4_d2(CreateTestWindowInShellWithBounds(
+ gfx::Rect(320, 20, 100, 100)));
+ EXPECT_EQ(root_windows[1], w3_d2->GetRootWindow());
+ EXPECT_EQ(root_windows[1], w4_d2->GetRootWindow());
+
+ aura::client::ActivationClient* client =
+ aura::client::GetActivationClient(root_windows[0]);
+ client->ActivateWindow(w1_d1.get());
+ EXPECT_EQ(w1_d1.get(), client->GetActiveWindow());
+
+ w1_d1.reset();
+ EXPECT_EQ(w2_d1.get(), client->GetActiveWindow());
+
+ client->ActivateWindow(w3_d2.get());
+ EXPECT_EQ(w3_d2.get(), client->GetActiveWindow());
+ w3_d2.reset();
+ EXPECT_EQ(w4_d2.get(), client->GetActiveWindow());
+}
+
+} // namespace test
+} // namespace ash
diff --git a/chromium/ash/wm/always_on_top_controller.cc b/chromium/ash/wm/always_on_top_controller.cc
new file mode 100644
index 00000000000..f937e8803ef
--- /dev/null
+++ b/chromium/ash/wm/always_on_top_controller.cc
@@ -0,0 +1,73 @@
+// Copyright (c) 2012 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.
+
+#include "ash/wm/always_on_top_controller.h"
+
+#include "ash/shell.h"
+#include "ash/shell_window_ids.h"
+#include "ash/wm/property_util.h"
+#include "ui/aura/client/aura_constants.h"
+#include "ui/aura/window.h"
+
+namespace ash {
+namespace internal {
+
+AlwaysOnTopController::AlwaysOnTopController()
+ : always_on_top_container_(NULL) {
+}
+
+AlwaysOnTopController::~AlwaysOnTopController() {
+ if (always_on_top_container_)
+ always_on_top_container_->RemoveObserver(this);
+}
+
+void AlwaysOnTopController::SetAlwaysOnTopContainer(
+ aura::Window* always_on_top_container) {
+ // Container should be empty.
+ DCHECK(always_on_top_container->children().empty());
+
+ // We are not handling any containers yet.
+ DCHECK(always_on_top_container_ == NULL);
+
+ always_on_top_container_ = always_on_top_container;
+ always_on_top_container_->AddObserver(this);
+}
+
+aura::Window* AlwaysOnTopController::GetContainer(aura::Window* window) const {
+ DCHECK(always_on_top_container_);
+ if (window->GetProperty(aura::client::kAlwaysOnTopKey))
+ return always_on_top_container_;
+ return Shell::GetContainer(always_on_top_container_->GetRootWindow(),
+ kShellWindowId_DefaultContainer);
+}
+
+void AlwaysOnTopController::OnWindowAdded(aura::Window* child) {
+ // Observe direct child of the containers.
+ if (child->parent() == always_on_top_container_)
+ child->AddObserver(this);
+}
+
+void AlwaysOnTopController::OnWillRemoveWindow(aura::Window* child) {
+ child->RemoveObserver(this);
+}
+
+void AlwaysOnTopController::OnWindowPropertyChanged(aura::Window* window,
+ const void* key,
+ intptr_t old) {
+ if (key == aura::client::kAlwaysOnTopKey) {
+ DCHECK(window->type() == aura::client::WINDOW_TYPE_NORMAL ||
+ window->type() == aura::client::WINDOW_TYPE_POPUP);
+ aura::Window* container = GetContainer(window);
+ if (window->parent() != container)
+ container->AddChild(window);
+ }
+}
+
+void AlwaysOnTopController::OnWindowDestroyed(aura::Window* window) {
+ if (window == always_on_top_container_)
+ always_on_top_container_ = NULL;
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/wm/always_on_top_controller.h b/chromium/ash/wm/always_on_top_controller.h
new file mode 100644
index 00000000000..57fbf99e171
--- /dev/null
+++ b/chromium/ash/wm/always_on_top_controller.h
@@ -0,0 +1,51 @@
+// Copyright (c) 2012 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 ASH_WM_ALWAYS_ON_TOP_CONTROLLER_H_
+#define ASH_WM_ALWAYS_ON_TOP_CONTROLLER_H_
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "ui/aura/window_observer.h"
+
+namespace aura {
+class Window;
+}
+
+namespace ash {
+namespace internal {
+
+// AlwaysOnTopController puts window into proper containers based on its
+// 'AlwaysOnTop' property. That is, putting a window into the worskpace
+// container if its "AlwaysOnTop" property is false. Otherwise, put it in
+// |always_on_top_container_|.
+class AlwaysOnTopController : public aura::WindowObserver {
+ public:
+ AlwaysOnTopController();
+ virtual ~AlwaysOnTopController();
+
+ // Sets the container for always on top windows.
+ void SetAlwaysOnTopContainer(aura::Window* always_on_top_container);
+
+ // Gets container for given |window| based on its "AlwaysOnTop" property.
+ aura::Window* GetContainer(aura::Window* window) const;
+
+ private:
+ // Overridden from aura::WindowObserver:
+ virtual void OnWindowAdded(aura::Window* child) OVERRIDE;
+ virtual void OnWillRemoveWindow(aura::Window* child) OVERRIDE;
+ virtual void OnWindowPropertyChanged(aura::Window* window,
+ const void* key,
+ intptr_t old) OVERRIDE;
+ virtual void OnWindowDestroyed(aura::Window* window) OVERRIDE;
+
+ aura::Window* always_on_top_container_;
+
+ DISALLOW_COPY_AND_ASSIGN(AlwaysOnTopController);
+};
+
+} // namepsace internal
+} // namepsace ash
+
+#endif // ASH_WM_ALWAYS_ON_TOP_CONTROLLER_H_
diff --git a/chromium/ash/wm/app_list_controller.cc b/chromium/ash/wm/app_list_controller.cc
new file mode 100644
index 00000000000..ea5e4f4ce90
--- /dev/null
+++ b/chromium/ash/wm/app_list_controller.cc
@@ -0,0 +1,431 @@
+// Copyright (c) 2012 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.
+
+#include "ash/wm/app_list_controller.h"
+
+#include "ash/ash_switches.h"
+#include "ash/launcher/launcher.h"
+#include "ash/root_window_controller.h"
+#include "ash/shelf/shelf_layout_manager.h"
+#include "ash/shell.h"
+#include "ash/shell_delegate.h"
+#include "ash/shell_window_ids.h"
+#include "ash/wm/property_util.h"
+#include "base/command_line.h"
+#include "ui/app_list/app_list_constants.h"
+#include "ui/app_list/pagination_model.h"
+#include "ui/app_list/views/app_list_view.h"
+#include "ui/aura/client/focus_client.h"
+#include "ui/aura/root_window.h"
+#include "ui/aura/window.h"
+#include "ui/base/events/event.h"
+#include "ui/compositor/layer.h"
+#include "ui/compositor/scoped_layer_animation_settings.h"
+#include "ui/gfx/transform_util.h"
+#include "ui/views/widget/widget.h"
+
+namespace ash {
+namespace internal {
+
+namespace {
+
+// Duration for show/hide animation in milliseconds.
+const int kAnimationDurationMs = 200;
+
+// Offset in pixels to animation away/towards the launcher.
+const int kAnimationOffset = 8;
+
+// The maximum shift in pixels when over-scroll happens.
+const int kMaxOverScrollShift = 48;
+
+// The alternate shelf style adjusts the bubble to be flush with the shelf
+// when there is no bubble-tip. This is the tip height which needs to be
+// offsetted.
+const int kArrowTipHeight = 10;
+
+// The minimal anchor position offset to make sure that the bubble is still on
+// the screen with 8 pixels spacing on the left / right. This constant is a
+// result of minimal bubble arrow sizes and offsets.
+const int kMinimalAnchorPositionOffset = 57;
+
+ui::Layer* GetLayer(views::Widget* widget) {
+ return widget->GetNativeView()->layer();
+}
+
+// Gets arrow location based on shelf alignment.
+views::BubbleBorder::Arrow GetBubbleArrow(aura::Window* window) {
+ DCHECK(Shell::HasInstance());
+ return ShelfLayoutManager::ForLauncher(window)->
+ SelectValueForShelfAlignment(
+ views::BubbleBorder::BOTTOM_CENTER,
+ views::BubbleBorder::LEFT_CENTER,
+ views::BubbleBorder::RIGHT_CENTER,
+ views::BubbleBorder::TOP_CENTER);
+}
+
+// Offset given |rect| towards shelf.
+gfx::Rect OffsetTowardsShelf(const gfx::Rect& rect, views::Widget* widget) {
+ DCHECK(Shell::HasInstance());
+ ShelfAlignment shelf_alignment = Shell::GetInstance()->GetShelfAlignment(
+ widget->GetNativeView()->GetRootWindow());
+ gfx::Rect offseted(rect);
+ switch (shelf_alignment) {
+ case SHELF_ALIGNMENT_BOTTOM:
+ offseted.Offset(0, kAnimationOffset);
+ break;
+ case SHELF_ALIGNMENT_LEFT:
+ offseted.Offset(-kAnimationOffset, 0);
+ break;
+ case SHELF_ALIGNMENT_RIGHT:
+ offseted.Offset(kAnimationOffset, 0);
+ break;
+ case SHELF_ALIGNMENT_TOP:
+ offseted.Offset(0, -kAnimationOffset);
+ break;
+ }
+
+ return offseted;
+}
+
+// Using |button_bounds|, determine the anchor so that the bubble gets shown
+// above the shelf (used for the alternate shelf theme).
+gfx::Point GetAdjustAnchorPositionToShelf(
+ const gfx::Rect& button_bounds, views::Widget* widget) {
+ DCHECK(Shell::HasInstance());
+ ShelfAlignment shelf_alignment = Shell::GetInstance()->GetShelfAlignment(
+ widget->GetNativeView()->GetRootWindow());
+ gfx::Point anchor(button_bounds.CenterPoint());
+ switch (shelf_alignment) {
+ case SHELF_ALIGNMENT_TOP:
+ case SHELF_ALIGNMENT_BOTTOM:
+ {
+ if (base::i18n::IsRTL()) {
+ int screen_width = widget->GetWorkAreaBoundsInScreen().width();
+ anchor.set_x(std::min(screen_width - kMinimalAnchorPositionOffset,
+ anchor.x()));
+ } else {
+ anchor.set_x(std::max(kMinimalAnchorPositionOffset, anchor.x()));
+ }
+ int offset = button_bounds.height() / 2 - kArrowTipHeight;
+ if (shelf_alignment == SHELF_ALIGNMENT_TOP)
+ offset = -offset;
+ anchor.set_y(anchor.y() - offset);
+ }
+ break;
+ case SHELF_ALIGNMENT_LEFT:
+ anchor.set_x(button_bounds.right() - kArrowTipHeight);
+ anchor.set_y(std::max(kMinimalAnchorPositionOffset, anchor.y()));
+ break;
+ case SHELF_ALIGNMENT_RIGHT:
+ anchor.set_x(button_bounds.x() + kArrowTipHeight);
+ anchor.set_y(std::max(kMinimalAnchorPositionOffset, anchor.y()));
+ break;
+ }
+
+ return anchor;
+}
+
+} // namespace
+
+////////////////////////////////////////////////////////////////////////////////
+// AppListController, public:
+
+AppListController::AppListController()
+ : pagination_model_(new app_list::PaginationModel),
+ is_visible_(false),
+ view_(NULL),
+ should_snap_back_(false) {
+ Shell::GetInstance()->AddShellObserver(this);
+ pagination_model_->AddObserver(this);
+}
+
+AppListController::~AppListController() {
+ // Ensures app list view goes before the controller since pagination model
+ // lives in the controller and app list view would access it on destruction.
+ if (view_ && view_->GetWidget())
+ view_->GetWidget()->CloseNow();
+
+ Shell::GetInstance()->RemoveShellObserver(this);
+ pagination_model_->RemoveObserver(this);
+}
+
+void AppListController::SetVisible(bool visible, aura::Window* window) {
+ if (visible == is_visible_)
+ return;
+
+ is_visible_ = visible;
+
+ // App list needs to know the new shelf layout in order to calculate its
+ // UI layout when AppListView visibility changes.
+ Shell::GetPrimaryRootWindowController()->GetShelfLayoutManager()->
+ UpdateAutoHideState();
+
+ if (view_) {
+ ScheduleAnimation();
+ } else if (is_visible_) {
+ // AppListModel and AppListViewDelegate are owned by AppListView. They
+ // will be released with AppListView on close.
+ app_list::AppListView* view = new app_list::AppListView(
+ Shell::GetInstance()->delegate()->CreateAppListViewDelegate());
+ aura::Window* container = GetRootWindowController(window->GetRootWindow())->
+ GetContainer(kShellWindowId_AppListContainer);
+ if (ash::switches::UseAlternateShelfLayout()) {
+ gfx::Rect applist_button_bounds = Launcher::ForWindow(container)->
+ GetAppListButtonView()->GetBoundsInScreen();
+ view->InitAsBubble(
+ container,
+ pagination_model_.get(),
+ NULL,
+ GetAdjustAnchorPositionToShelf(applist_button_bounds,
+ Launcher::ForWindow(container)->GetAppListButtonView()->
+ GetWidget()),
+ GetBubbleArrow(container),
+ true /* border_accepts_events */);
+ view->SetArrowPaintType(views::BubbleBorder::PAINT_NONE);
+ } else {
+ view->InitAsBubble(
+ container,
+ pagination_model_.get(),
+ Launcher::ForWindow(container)->GetAppListButtonView(),
+ gfx::Point(),
+ GetBubbleArrow(container),
+ true /* border_accepts_events */);
+ }
+ SetView(view);
+ // By setting us as DnD recipient, the app list knows that we can
+ // handle items.
+ if (!CommandLine::ForCurrentProcess()->HasSwitch(
+ ash::switches::kAshDisableDragAndDropAppListToLauncher)) {
+ SetDragAndDropHostOfCurrentAppList(
+ Launcher::ForWindow(window)->GetDragAndDropHostForAppList());
+ }
+ }
+}
+
+bool AppListController::IsVisible() const {
+ return view_ && view_->GetWidget()->IsVisible();
+}
+
+aura::Window* AppListController::GetWindow() {
+ return is_visible_ && view_ ? view_->GetWidget()->GetNativeWindow() : NULL;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// AppListController, private:
+
+void AppListController::SetDragAndDropHostOfCurrentAppList(
+ app_list::ApplicationDragAndDropHost* drag_and_drop_host) {
+ if (view_ && is_visible_)
+ view_->SetDragAndDropHostOfCurrentAppList(drag_and_drop_host);
+}
+
+void AppListController::SetView(app_list::AppListView* view) {
+ DCHECK(view_ == NULL);
+ DCHECK(is_visible_);
+
+ view_ = view;
+ views::Widget* widget = view_->GetWidget();
+ widget->AddObserver(this);
+ Shell::GetInstance()->AddPreTargetHandler(this);
+ Launcher::ForWindow(widget->GetNativeWindow())->AddIconObserver(this);
+ widget->GetNativeView()->GetRootWindow()->AddObserver(this);
+ aura::client::GetFocusClient(widget->GetNativeView())->AddObserver(this);
+
+ view_->ShowWhenReady();
+}
+
+void AppListController::ResetView() {
+ if (!view_)
+ return;
+
+ views::Widget* widget = view_->GetWidget();
+ widget->RemoveObserver(this);
+ GetLayer(widget)->GetAnimator()->RemoveObserver(this);
+ Shell::GetInstance()->RemovePreTargetHandler(this);
+ Launcher::ForWindow(widget->GetNativeWindow())->RemoveIconObserver(this);
+ widget->GetNativeView()->GetRootWindow()->RemoveObserver(this);
+ aura::client::GetFocusClient(widget->GetNativeView())->RemoveObserver(this);
+ view_ = NULL;
+}
+
+void AppListController::ScheduleAnimation() {
+ // Stop observing previous animation.
+ StopObservingImplicitAnimations();
+
+ views::Widget* widget = view_->GetWidget();
+ ui::Layer* layer = GetLayer(widget);
+ layer->GetAnimator()->StopAnimating();
+
+ gfx::Rect target_bounds;
+ if (is_visible_) {
+ target_bounds = widget->GetWindowBoundsInScreen();
+ widget->SetBounds(OffsetTowardsShelf(target_bounds, widget));
+ } else {
+ target_bounds = OffsetTowardsShelf(widget->GetWindowBoundsInScreen(),
+ widget);
+ }
+
+ ui::ScopedLayerAnimationSettings animation(layer->GetAnimator());
+ animation.SetTransitionDuration(
+ base::TimeDelta::FromMilliseconds(
+ is_visible_ ? 0 : kAnimationDurationMs));
+ animation.AddObserver(this);
+
+ layer->SetOpacity(is_visible_ ? 1.0 : 0.0);
+ widget->SetBounds(target_bounds);
+}
+
+void AppListController::ProcessLocatedEvent(ui::LocatedEvent* event) {
+ // If the event happened on a menu, then the event should not close the app
+ // list.
+ aura::Window* target = static_cast<aura::Window*>(event->target());
+ if (target) {
+ RootWindowController* root_controller =
+ GetRootWindowController(target->GetRootWindow());
+ if (root_controller) {
+ aura::Window* menu_container = root_controller->GetContainer(
+ ash::internal::kShellWindowId_MenuContainer);
+ if (menu_container->Contains(target))
+ return;
+ }
+ }
+
+ if (view_ && is_visible_) {
+ aura::Window* window = view_->GetWidget()->GetNativeView();
+ gfx::Point window_local_point(event->root_location());
+ aura::Window::ConvertPointToTarget(window->GetRootWindow(),
+ window,
+ &window_local_point);
+ // Use HitTest to respect the hit test mask of the bubble.
+ if (!window->HitTest(window_local_point))
+ SetVisible(false, window);
+ }
+}
+
+void AppListController::UpdateBounds() {
+ if (view_ && is_visible_)
+ view_->UpdateBounds();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// AppListController, aura::EventFilter implementation:
+
+void AppListController::OnMouseEvent(ui::MouseEvent* event) {
+ if (event->type() == ui::ET_MOUSE_PRESSED)
+ ProcessLocatedEvent(event);
+}
+
+void AppListController::OnGestureEvent(ui::GestureEvent* event) {
+ if (event->type() == ui::ET_GESTURE_TAP_DOWN)
+ ProcessLocatedEvent(event);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// AppListController, aura::FocusObserver implementation:
+
+void AppListController::OnWindowFocused(aura::Window* gained_focus,
+ aura::Window* lost_focus) {
+ if (gained_focus && view_ && is_visible_) {
+ aura::Window* applist_container =
+ GetRootWindowController(gained_focus->GetRootWindow())->GetContainer(
+ kShellWindowId_AppListContainer);
+ if (gained_focus->parent() != applist_container)
+ SetVisible(false, gained_focus);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// AppListController, aura::WindowObserver implementation:
+void AppListController::OnWindowBoundsChanged(aura::Window* root,
+ const gfx::Rect& old_bounds,
+ const gfx::Rect& new_bounds) {
+ UpdateBounds();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// AppListController, ui::ImplicitAnimationObserver implementation:
+
+void AppListController::OnImplicitAnimationsCompleted() {
+ if (is_visible_ )
+ view_->GetWidget()->Activate();
+ else
+ view_->GetWidget()->Close();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// AppListController, views::WidgetObserver implementation:
+
+void AppListController::OnWidgetDestroying(views::Widget* widget) {
+ DCHECK(view_->GetWidget() == widget);
+ if (is_visible_)
+ SetVisible(false, widget->GetNativeView());
+ ResetView();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// AppListController, ShellObserver implementation:
+void AppListController::OnShelfAlignmentChanged(aura::RootWindow* root_window) {
+ if (view_)
+ view_->SetBubbleArrow(GetBubbleArrow(view_->GetWidget()->GetNativeView()));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// AppListController, LauncherIconObserver implementation:
+
+void AppListController::OnLauncherIconPositionsChanged() {
+ UpdateBounds();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// AppListController, PaginationModelObserver implementation:
+
+void AppListController::TotalPagesChanged() {
+}
+
+void AppListController::SelectedPageChanged(int old_selected,
+ int new_selected) {
+}
+
+void AppListController::TransitionStarted() {
+}
+
+void AppListController::TransitionChanged() {
+ // |view_| could be NULL when app list is closed with a running transition.
+ if (!view_)
+ return;
+
+ const app_list::PaginationModel::Transition& transition =
+ pagination_model_->transition();
+ if (pagination_model_->is_valid_page(transition.target_page))
+ return;
+
+ views::Widget* widget = view_->GetWidget();
+ ui::LayerAnimator* widget_animator = GetLayer(widget)->GetAnimator();
+ if (!pagination_model_->IsRevertingCurrentTransition()) {
+ // Update cached |view_bounds_| if it is the first over-scroll move and
+ // widget does not have running animations.
+ if (!should_snap_back_ && !widget_animator->is_animating())
+ view_bounds_ = widget->GetWindowBoundsInScreen();
+
+ const int current_page = pagination_model_->selected_page();
+ const int dir = transition.target_page > current_page ? -1 : 1;
+
+ const double progress = 1.0 - pow(1.0 - transition.progress, 4);
+ const int shift = kMaxOverScrollShift * progress * dir;
+
+ gfx::Rect shifted(view_bounds_);
+ shifted.set_x(shifted.x() + shift);
+ widget->SetBounds(shifted);
+ should_snap_back_ = true;
+ } else if (should_snap_back_) {
+ should_snap_back_ = false;
+ ui::ScopedLayerAnimationSettings animation(widget_animator);
+ animation.SetTransitionDuration(base::TimeDelta::FromMilliseconds(
+ app_list::kOverscrollPageTransitionDurationMs));
+ widget->SetBounds(view_bounds_);
+ }
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/wm/app_list_controller.h b/chromium/ash/wm/app_list_controller.h
new file mode 100644
index 00000000000..b2c7961a7fc
--- /dev/null
+++ b/chromium/ash/wm/app_list_controller.h
@@ -0,0 +1,136 @@
+// Copyright (c) 2012 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 ASH_WM_APP_LIST_CONTROLLER_H_
+#define ASH_WM_APP_LIST_CONTROLLER_H_
+
+#include "ash/launcher/launcher_icon_observer.h"
+#include "ash/shell_observer.h"
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/timer/timer.h"
+#include "ui/app_list/pagination_model_observer.h"
+#include "ui/aura/client/focus_change_observer.h"
+#include "ui/aura/window_observer.h"
+#include "ui/base/events/event_handler.h"
+#include "ui/compositor/layer_animation_observer.h"
+#include "ui/gfx/rect.h"
+#include "ui/views/widget/widget_observer.h"
+
+namespace app_list {
+class ApplicationDragAndDropHost;
+class AppListView;
+class PaginationModel;
+}
+
+namespace ui {
+class LocatedEvent;
+}
+
+namespace ash {
+namespace internal {
+
+// AppListController is a controller that manages app list UI for shell.
+// It creates AppListView and schedules showing/hiding animation.
+// While the UI is visible, it monitors things such as app list widget's
+// activation state and desktop mouse click to auto dismiss the UI.
+class AppListController : public ui::EventHandler,
+ public aura::client::FocusChangeObserver,
+ public aura::WindowObserver,
+ public ui::ImplicitAnimationObserver,
+ public views::WidgetObserver,
+ public ShellObserver,
+ public LauncherIconObserver,
+ public app_list::PaginationModelObserver {
+ public:
+ AppListController();
+ virtual ~AppListController();
+
+ // Show/hide app list window. The |window| is used to deterime in
+ // which display (in which the |window| exists) the app list should
+ // be shown.
+ void SetVisible(bool visible, aura::Window* window);
+
+ // Whether app list window is visible (shown or being shown).
+ bool IsVisible() const;
+
+ // Returns target visibility. This differs from IsVisible() if an animation
+ // is ongoing.
+ bool GetTargetVisibility() const { return is_visible_; }
+
+ // Returns app list window or NULL if it is not visible.
+ aura::Window* GetWindow();
+
+ private:
+ // If |drag_and_drop_host| is not NULL it will be called upon drag and drop
+ // operations outside the application list.
+ void SetDragAndDropHostOfCurrentAppList(
+ app_list::ApplicationDragAndDropHost* drag_and_drop_host);
+
+ // Sets the app list view and attempts to show it.
+ void SetView(app_list::AppListView* view);
+
+ // Forgets the view.
+ void ResetView();
+
+ // Starts show/hide animation.
+ void ScheduleAnimation();
+
+ void ProcessLocatedEvent(ui::LocatedEvent* event);
+
+ // Makes app list bubble update its bounds.
+ void UpdateBounds();
+
+ // ui::EventHandler overrides:
+ virtual void OnMouseEvent(ui::MouseEvent* event) OVERRIDE;
+ virtual void OnGestureEvent(ui::GestureEvent* event) OVERRIDE;
+
+ // aura::client::FocusChangeObserver overrides:
+ virtual void OnWindowFocused(aura::Window* gained_focus,
+ aura::Window* lost_focus) OVERRIDE;
+
+ // aura::WindowObserver overrides:
+ virtual void OnWindowBoundsChanged(aura::Window* root,
+ const gfx::Rect& old_bounds,
+ const gfx::Rect& new_bounds) OVERRIDE;
+
+ // ui::ImplicitAnimationObserver overrides:
+ virtual void OnImplicitAnimationsCompleted() OVERRIDE;
+
+ // views::WidgetObserver overrides:
+ virtual void OnWidgetDestroying(views::Widget* widget) OVERRIDE;
+
+ // ShellObserver overrides:
+ virtual void OnShelfAlignmentChanged(aura::RootWindow* root_window) OVERRIDE;
+
+ // LauncherIconObserver overrides:
+ virtual void OnLauncherIconPositionsChanged() OVERRIDE;
+
+ // app_list::PaginationModelObserver overrides:
+ virtual void TotalPagesChanged() OVERRIDE;
+ virtual void SelectedPageChanged(int old_selected, int new_selected) OVERRIDE;
+ virtual void TransitionStarted() OVERRIDE;
+ virtual void TransitionChanged() OVERRIDE;
+
+ scoped_ptr<app_list::PaginationModel> pagination_model_;
+
+ // Whether we should show or hide app list widget.
+ bool is_visible_;
+
+ // The AppListView this class manages, owned by its widget.
+ app_list::AppListView* view_;
+
+ // Cached bounds of |view_| for snapping back animation after over-scroll.
+ gfx::Rect view_bounds_;
+
+ // Whether should schedule snap back animation.
+ bool should_snap_back_;
+
+ DISALLOW_COPY_AND_ASSIGN(AppListController);
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_WM_APP_LIST_CONTROLLER_H_
diff --git a/chromium/ash/wm/ash_activation_controller.cc b/chromium/ash/wm/ash_activation_controller.cc
new file mode 100644
index 00000000000..146cf493083
--- /dev/null
+++ b/chromium/ash/wm/ash_activation_controller.cc
@@ -0,0 +1,96 @@
+// Copyright (c) 2012 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.
+
+#include "ash/wm/ash_activation_controller.h"
+
+#include "ash/launcher/launcher.h"
+#include "ash/root_window_controller.h"
+#include "ash/shelf/shelf_widget.h"
+#include "ash/shell.h"
+#include "ash/shell_delegate.h"
+#include "ash/wm/activation_controller.h"
+#include "ash/wm/property_util.h"
+#include "ash/wm/window_util.h"
+#include "ash/wm/workspace_controller.h"
+#include "ui/views/corewm/window_modality_controller.h"
+#include "ui/views/widget/widget.h"
+
+namespace ash {
+namespace internal {
+
+////////////////////////////////////////////////////////////////////////////////
+// AshActivationController, public:
+
+AshActivationController::AshActivationController() {
+}
+
+AshActivationController::~AshActivationController() {
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// AshActivationController, ActivationControllerDelegate implementation:
+
+aura::Window* AshActivationController::WillActivateWindow(
+ aura::Window* window) {
+ aura::Window* window_modal_transient =
+ views::corewm::GetModalTransient(window);
+ if (window_modal_transient)
+ return window_modal_transient;
+
+ // Fallback to launcher
+ if (!window)
+ window = PrepareToActivateLauncher();
+
+ // Restore minimized window. This needs to be done before CanReceiveEvents()
+ // is called as that function checks window visibility.
+ if (window && wm::IsWindowMinimized(window))
+ window->Show();
+
+ // If the screen is locked, just bring the window to top so that
+ // it will be activated when the lock window is destroyed.
+ // TODO(beng): Call EventClient directly here, rather than conflating with
+ // window visibility.
+ if (window && !window->CanReceiveEvents())
+ return NULL;
+
+ // TODO(beng): could probably move to being a ActivationChangeObserver on
+ // Shell.
+ if (window) {
+ DCHECK(window->GetRootWindow());
+ Shell::GetInstance()->set_active_root_window(window->GetRootWindow());
+ }
+ return window;
+}
+
+aura::Window* AshActivationController::WillFocusWindow(
+ aura::Window* window) {
+ aura::Window* window_modal_transient =
+ views::corewm::GetModalTransient(window);
+ if (window_modal_transient)
+ return window_modal_transient;
+ return window;
+}
+
+aura::Window* AshActivationController::PrepareToActivateLauncher() {
+ // If workspace controller is not available, then it means that the root
+ // window is being destroyed. We can't activate any window then.
+ if (!GetRootWindowController(
+ Shell::GetActiveRootWindow())->workspace_controller()) {
+ return NULL;
+ }
+ // Fallback to a launcher only when Spoken feedback is enabled.
+ if (!Shell::GetInstance()->delegate()->IsSpokenFeedbackEnabled())
+ return NULL;
+ ShelfWidget* shelf = GetRootWindowController(
+ Shell::GetActiveRootWindow())->shelf();
+ // Launcher's window may be already destroyed in shutting down process.
+ if (!shelf)
+ return NULL;
+ // Notify launcher to allow activation via CanActivate().
+ shelf->WillActivateAsFallback();
+ return shelf->GetNativeWindow();
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/wm/ash_activation_controller.h b/chromium/ash/wm/ash_activation_controller.h
new file mode 100644
index 00000000000..c3961f9a80f
--- /dev/null
+++ b/chromium/ash/wm/ash_activation_controller.h
@@ -0,0 +1,37 @@
+// Copyright (c) 2012 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 ASH_WM_ASH_ACTIVATION_CONTROLLER_H_
+#define ASH_WM_ASH_ACTIVATION_CONTROLLER_H_
+
+#include "ash/ash_export.h"
+#include "ash/wm/activation_controller_delegate.h"
+#include "base/compiler_specific.h"
+#include "base/basictypes.h"
+
+namespace ash {
+namespace internal {
+
+class ASH_EXPORT AshActivationController : public ActivationControllerDelegate {
+ public:
+ AshActivationController();
+ virtual ~AshActivationController();
+
+ private:
+ // Overridden from ActivationControllerDelegate:
+ virtual aura::Window* WillActivateWindow(aura::Window* window) OVERRIDE;
+ virtual aura::Window* WillFocusWindow(aura::Window* window) OVERRIDE;
+
+ // Returns a handle to the launcher on the active root window which will
+ // be activated as fallback. Also notifies the launcher, so it can return
+ // true from Launcher::CanActivate().
+ aura::Window* PrepareToActivateLauncher();
+
+ DISALLOW_COPY_AND_ASSIGN(AshActivationController);
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_WM_ASH_ACTIVATION_CONTROLLER_H_
diff --git a/chromium/ash/wm/ash_activation_controller_unittest.cc b/chromium/ash/wm/ash_activation_controller_unittest.cc
new file mode 100644
index 00000000000..e332990e19a
--- /dev/null
+++ b/chromium/ash/wm/ash_activation_controller_unittest.cc
@@ -0,0 +1,168 @@
+// Copyright (c) 2012 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.
+
+#include "ash/wm/ash_activation_controller.h"
+
+#include "ash/launcher/launcher.h"
+#include "ash/root_window_controller.h"
+#include "ash/shelf/shelf_widget.h"
+#include "ash/shell_delegate.h"
+#include "ash/test/ash_test_base.h"
+#include "ash/wm/property_util.h"
+#include "ash/wm/window_util.h"
+#include "ui/aura/window.h"
+#include "ui/views/corewm/corewm_switches.h"
+
+namespace ash {
+
+namespace wm {
+
+namespace {
+
+class AshActivationControllerTest : public test::AshTestBase {
+ public:
+ AshActivationControllerTest()
+ : launcher_(NULL), launcher_widget_(NULL), launcher_window_(NULL) {}
+ virtual ~AshActivationControllerTest() {}
+
+ virtual void SetUp() OVERRIDE {
+ test::AshTestBase::SetUp();
+ ash_activation_controller_.reset(new internal::AshActivationController());
+ launcher_ = Launcher::ForPrimaryDisplay();
+ ASSERT_TRUE(launcher_);
+ launcher_widget_ = launcher_->shelf_widget();
+ ASSERT_TRUE(launcher_widget_);
+ launcher_window_ = launcher_widget_->GetNativeWindow();
+ ASSERT_TRUE(launcher_window_);
+ }
+
+ void SetSpokenFeedbackState(bool enabled) {
+ if (Shell::GetInstance()->delegate()->IsSpokenFeedbackEnabled() !=
+ enabled) {
+ Shell::GetInstance()->delegate()->ToggleSpokenFeedback(
+ A11Y_NOTIFICATION_NONE);
+ }
+ }
+
+ protected:
+ scoped_ptr<internal::ActivationControllerDelegate> ash_activation_controller_;
+ ash::Launcher* launcher_;
+ views::Widget* launcher_widget_;
+ aura::Window* launcher_window_;
+
+ DISALLOW_COPY_AND_ASSIGN(AshActivationControllerTest);
+};
+
+TEST_F(AshActivationControllerTest, LauncherFallback) {
+ // When spoken feedback is disabled, then fallback should not occur.
+ {
+ SetSpokenFeedbackState(false);
+ aura::Window* result = ash_activation_controller_->WillActivateWindow(NULL);
+ EXPECT_EQ(NULL, result);
+ }
+
+ // When spoken feedback is enabled, then fallback should occur.
+ {
+ SetSpokenFeedbackState(true);
+ aura::Window* result = ash_activation_controller_->WillActivateWindow(NULL);
+ EXPECT_EQ(launcher_window_, result);
+ }
+
+ // No fallback when activating another window.
+ {
+ aura::Window* test_window = CreateTestWindowInShellWithId(0);
+ aura::Window* result = ash_activation_controller_->
+ WillActivateWindow(test_window);
+ EXPECT_EQ(test_window, result);
+ }
+}
+
+TEST_F(AshActivationControllerTest, LauncherFallbackOnShutdown) {
+ SetSpokenFeedbackState(true);
+ // While shutting down a root window controller, activation controller
+ // is notified about destroyed windows and therefore will try to activate
+ // a launcher as fallback, which would result in segmentation faults since
+ // the launcher's window or the workspace's controller may be already
+ // destroyed.
+ GetRootWindowController(Shell::GetActiveRootWindow())->CloseChildWindows();
+
+ aura::Window* result = ash_activation_controller_->WillActivateWindow(NULL);
+ EXPECT_EQ(NULL, result);
+}
+
+TEST_F(AshActivationControllerTest, LauncherEndToEndFallbackOnDestroyTest) {
+ // TODO(mtomasz): make this test work with the FocusController.
+ if (views::corewm::UseFocusController())
+ return;
+
+ // This test checks the whole fallback activation flow.
+ SetSpokenFeedbackState(true);
+
+ scoped_ptr<aura::Window> test_window(CreateTestWindowInShellWithId(0));
+ ActivateWindow(test_window.get());
+ ASSERT_EQ(test_window.get(), GetActiveWindow());
+
+ // Close the window.
+ test_window.reset();
+
+ // Verify if the launcher got activated as fallback.
+ ASSERT_EQ(launcher_window_, GetActiveWindow());
+}
+
+TEST_F(AshActivationControllerTest, LauncherEndToEndFallbackOnMinimizeTest) {
+ // TODO(mtomasz): make this test work with the FocusController.
+ if (views::corewm::UseFocusController())
+ return;
+
+ // This test checks the whole fallback activation flow.
+ SetSpokenFeedbackState(true);
+
+ scoped_ptr<aura::Window> test_window(CreateTestWindowInShellWithId(0));
+ ActivateWindow(test_window.get());
+ ASSERT_EQ(test_window.get(), GetActiveWindow());
+
+ // Minimize the window.
+ MinimizeWindow(test_window.get());
+
+ // Verify if the launcher got activated as fallback.
+ ASSERT_EQ(launcher_window_, GetActiveWindow());
+}
+
+TEST_F(AshActivationControllerTest, LauncherEndToEndNoFallbackOnDestroyTest) {
+ // This test checks the whole fallback activation flow when spoken feedback
+ // is disabled.
+ SetSpokenFeedbackState(false);
+
+ scoped_ptr<aura::Window> test_window(CreateTestWindowInShellWithId(0));
+ ActivateWindow(test_window.get());
+ ASSERT_EQ(test_window.get(), GetActiveWindow());
+
+ // Close the window.
+ test_window.reset();
+
+ // Verify if the launcher didn't get activated as fallback.
+ ASSERT_NE(launcher_window_, GetActiveWindow());
+}
+
+TEST_F(AshActivationControllerTest, LauncherEndToEndNoFallbackOnMinimizeTest) {
+ // This test checks the whole fallback activation flow when spoken feedback
+ // is disabled.
+ SetSpokenFeedbackState(false);
+
+ scoped_ptr<aura::Window> test_window(CreateTestWindowInShellWithId(0));
+ ActivateWindow(test_window.get());
+ ASSERT_EQ(test_window.get(), GetActiveWindow());
+
+ // Minimize the window.
+ MinimizeWindow(test_window.get());
+
+ // Verify if the launcher didn't get activated as fallback.
+ ASSERT_NE(launcher_window_, GetActiveWindow());
+}
+
+} // namespace
+
+} // namespace wm
+
+} // namespace ash
diff --git a/chromium/ash/wm/ash_focus_rules.cc b/chromium/ash/wm/ash_focus_rules.cc
new file mode 100644
index 00000000000..0b5aa3309eb
--- /dev/null
+++ b/chromium/ash/wm/ash_focus_rules.cc
@@ -0,0 +1,164 @@
+// Copyright (c) 2012 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.
+
+#include "ash/wm/ash_focus_rules.h"
+
+#include "ash/shell.h"
+#include "ash/shell_window_ids.h"
+#include "ash/wm/window_util.h"
+#include "ui/aura/window.h"
+
+namespace ash {
+namespace wm {
+namespace {
+
+// These are the list of container ids of containers which may contain windows
+// that need to be activated in the order that they should be activated.
+const int kWindowContainerIds[] = {
+ internal::kShellWindowId_LockSystemModalContainer,
+ internal::kShellWindowId_SettingBubbleContainer,
+ internal::kShellWindowId_LockScreenContainer,
+ internal::kShellWindowId_SystemModalContainer,
+ internal::kShellWindowId_AlwaysOnTopContainer,
+ internal::kShellWindowId_AppListContainer,
+ internal::kShellWindowId_DefaultContainer,
+
+ // Docked, panel, launcher and status are intentionally checked after other
+ // containers even though these layers are higher. The user expects their
+ // windows to be focused before these elements.
+ internal::kShellWindowId_DockedContainer,
+ internal::kShellWindowId_PanelContainer,
+ internal::kShellWindowId_ShelfContainer,
+ internal::kShellWindowId_StatusContainer,
+};
+
+bool BelongsToContainerWithEqualOrGreaterId(const aura::Window* window,
+ int container_id) {
+ for (; window; window = window->parent()) {
+ if (window->id() >= container_id)
+ return true;
+ }
+ return false;
+}
+
+} // namespace
+
+////////////////////////////////////////////////////////////////////////////////
+// AshFocusRules, public:
+
+AshFocusRules::AshFocusRules() {
+}
+
+AshFocusRules::~AshFocusRules() {
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// AshFocusRules, views::corewm::FocusRules:
+
+bool AshFocusRules::SupportsChildActivation(aura::Window* window) const {
+ if (window->id() == internal::kShellWindowId_DefaultContainer)
+ return true;
+
+ for (size_t i = 0; i < arraysize(kWindowContainerIds); i++) {
+ if (window->id() == kWindowContainerIds[i])
+ return true;
+ }
+ return false;
+}
+
+bool AshFocusRules::IsWindowConsideredVisibleForActivation(
+ aura::Window* window) const {
+ if (BaseFocusRules::IsWindowConsideredVisibleForActivation(window))
+ return true;
+
+ // Minimized windows are hidden in their minimized state, but they can always
+ // be activated.
+ if (wm::IsWindowMinimized(window))
+ return true;
+
+ return window->TargetVisibility() && (window->parent()->id() ==
+ internal::kShellWindowId_DefaultContainer || window->parent()->id() ==
+ internal::kShellWindowId_LockScreenContainer);
+}
+
+bool AshFocusRules::CanActivateWindow(aura::Window* window) const {
+ // Clearing activation is always permissible.
+ if (!window)
+ return true;
+
+ if (!BaseFocusRules::CanActivateWindow(window))
+ return false;
+
+ if (Shell::GetInstance()->IsSystemModalWindowOpen()) {
+ return BelongsToContainerWithEqualOrGreaterId(
+ window, internal::kShellWindowId_SystemModalContainer);
+ }
+
+ return true;
+}
+
+aura::Window* AshFocusRules::GetNextActivatableWindow(
+ aura::Window* ignore) const {
+ DCHECK(ignore);
+
+ int starting_container_index = 0;
+ // If the container of the window losing focus is in the list, start from that
+ // container.
+ aura::RootWindow* root = ignore->GetRootWindow();
+ if (!root)
+ root = Shell::GetActiveRootWindow();
+ int container_count = static_cast<int>(arraysize(kWindowContainerIds));
+ for (int i = 0; ignore && i < container_count; i++) {
+ aura::Window* container = Shell::GetContainer(root, kWindowContainerIds[i]);
+ if (container && container->Contains(ignore)) {
+ starting_container_index = i;
+ break;
+ }
+ }
+
+ // Look for windows to focus in |ignore|'s container. If none are found, we
+ // look in all the containers in front of |ignore|'s container, then all
+ // behind.
+ aura::Window* window = NULL;
+ for (int i = starting_container_index; !window && i < container_count; i++)
+ window = GetTopmostWindowToActivateForContainerIndex(i, ignore);
+ if (!window && starting_container_index > 0) {
+ for (int i = starting_container_index - 1; !window && i >= 0; i--)
+ window = GetTopmostWindowToActivateForContainerIndex(i, ignore);
+ }
+ return window;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// AshFocusRules, private:
+
+aura::Window* AshFocusRules::GetTopmostWindowToActivateForContainerIndex(
+ int index,
+ aura::Window* ignore) const {
+ aura::Window* window = NULL;
+ aura::RootWindow* root = ignore ? ignore->GetRootWindow() : NULL;
+ aura::Window::Windows containers = Shell::GetContainersFromAllRootWindows(
+ kWindowContainerIds[index], root);
+ for (aura::Window::Windows::const_iterator iter = containers.begin();
+ iter != containers.end() && !window; ++iter) {
+ window = GetTopmostWindowToActivateInContainer((*iter), ignore);
+ }
+ return window;
+}
+
+aura::Window* AshFocusRules::GetTopmostWindowToActivateInContainer(
+ aura::Window* container,
+ aura::Window* ignore) const {
+ for (aura::Window::Windows::const_reverse_iterator i =
+ container->children().rbegin();
+ i != container->children().rend();
+ ++i) {
+ if (*i != ignore && CanActivateWindow(*i) && !wm::IsWindowMinimized(*i))
+ return *i;
+ }
+ return NULL;
+}
+
+} // namespace wm
+} // namespace ash
diff --git a/chromium/ash/wm/ash_focus_rules.h b/chromium/ash/wm/ash_focus_rules.h
new file mode 100644
index 00000000000..f53a70b33a6
--- /dev/null
+++ b/chromium/ash/wm/ash_focus_rules.h
@@ -0,0 +1,43 @@
+// Copyright (c) 2012 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 ASH_WM_ASH_FOCUS_RULES_H_
+#define ASH_WM_ASH_FOCUS_RULES_H_
+
+#include "ash/ash_export.h"
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "ui/views/corewm/base_focus_rules.h"
+
+namespace ash {
+namespace wm {
+
+class ASH_EXPORT AshFocusRules : public views::corewm::BaseFocusRules {
+ public:
+ AshFocusRules();
+ virtual ~AshFocusRules();
+
+ private:
+ // Overridden from views::corewm::BaseFocusRules:
+ virtual bool SupportsChildActivation(aura::Window* window) const OVERRIDE;
+ virtual bool IsWindowConsideredVisibleForActivation(
+ aura::Window* window) const OVERRIDE;
+ virtual bool CanActivateWindow(aura::Window* window) const OVERRIDE;
+ virtual aura::Window* GetNextActivatableWindow(
+ aura::Window* ignore) const OVERRIDE;
+
+ aura::Window* GetTopmostWindowToActivateForContainerIndex(
+ int index,
+ aura::Window* ignore) const;
+ aura::Window* GetTopmostWindowToActivateInContainer(
+ aura::Window* container,
+ aura::Window* ignore) const;
+
+ DISALLOW_COPY_AND_ASSIGN(AshFocusRules);
+};
+
+} // namespace wm
+} // namespace ash
+
+#endif // ASH_WM_ASH_FOCUS_RULES_H_
diff --git a/chromium/ash/wm/ash_native_cursor_manager.cc b/chromium/ash/wm/ash_native_cursor_manager.cc
new file mode 100644
index 00000000000..28e0785135c
--- /dev/null
+++ b/chromium/ash/wm/ash_native_cursor_manager.cc
@@ -0,0 +1,124 @@
+// Copyright (c) 2012 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.
+
+#include "ash/wm/ash_native_cursor_manager.h"
+
+#include "ash/display/display_controller.h"
+#include "ash/display/mirror_window_controller.h"
+#include "ash/shell.h"
+#include "ash/wm/image_cursors.h"
+#include "base/logging.h"
+#include "ui/aura/env.h"
+#include "ui/aura/root_window.h"
+#include "ui/base/cursor/cursor.h"
+
+namespace ash {
+namespace {
+
+void SetCursorOnAllRootWindows(gfx::NativeCursor cursor) {
+ Shell::RootWindowList root_windows =
+ Shell::GetInstance()->GetAllRootWindows();
+ for (Shell::RootWindowList::iterator iter = root_windows.begin();
+ iter != root_windows.end(); ++iter)
+ (*iter)->SetCursor(cursor);
+#if defined(OS_CHROMEOS)
+ Shell::GetInstance()->display_controller()->
+ mirror_window_controller()->SetMirroredCursor(cursor);
+#endif
+}
+
+void NotifyCursorVisibilityChange(bool visible) {
+ Shell::RootWindowList root_windows =
+ Shell::GetInstance()->GetAllRootWindows();
+ for (Shell::RootWindowList::iterator iter = root_windows.begin();
+ iter != root_windows.end(); ++iter)
+ (*iter)->OnCursorVisibilityChanged(visible);
+#if defined(OS_CHROMEOS)
+ Shell::GetInstance()->display_controller()->mirror_window_controller()->
+ SetMirroredCursorVisibility(visible);
+#endif
+}
+
+void NotifyMouseEventsEnableStateChange(bool enabled) {
+ Shell::RootWindowList root_windows =
+ Shell::GetInstance()->GetAllRootWindows();
+ for (Shell::RootWindowList::iterator iter = root_windows.begin();
+ iter != root_windows.end(); ++iter)
+ (*iter)->OnMouseEventsEnableStateChanged(enabled);
+ // Mirror window never process events.
+}
+
+} // namespace
+
+AshNativeCursorManager::AshNativeCursorManager()
+ : image_cursors_(new ImageCursors) {
+}
+
+AshNativeCursorManager::~AshNativeCursorManager() {
+}
+
+void AshNativeCursorManager::SetDisplay(
+ const gfx::Display& display,
+ views::corewm::NativeCursorManagerDelegate* delegate) {
+ if (image_cursors_->SetDisplay(display))
+ SetCursor(delegate->GetCurrentCursor(), delegate);
+}
+
+void AshNativeCursorManager::SetCursor(
+ gfx::NativeCursor cursor,
+ views::corewm::NativeCursorManagerDelegate* delegate) {
+ gfx::NativeCursor new_cursor = cursor;
+ image_cursors_->SetPlatformCursor(&new_cursor);
+ new_cursor.set_device_scale_factor(
+ image_cursors_->GetDisplay().device_scale_factor());
+
+ delegate->CommitCursor(new_cursor);
+
+ if (delegate->GetCurrentVisibility())
+ SetCursorOnAllRootWindows(new_cursor);
+}
+
+void AshNativeCursorManager::SetScale(
+ float scale,
+ views::corewm::NativeCursorManagerDelegate* delegate) {
+ image_cursors_->SetScale(scale);
+ delegate->CommitScale(scale);
+
+ // Sets the cursor to refrect the scale change imidiately.
+ SetCursor(delegate->GetCurrentCursor(), delegate);
+}
+
+void AshNativeCursorManager::SetVisibility(
+ bool visible,
+ views::corewm::NativeCursorManagerDelegate* delegate) {
+ delegate->CommitVisibility(visible);
+
+ if (visible) {
+ SetCursor(delegate->GetCurrentCursor(), delegate);
+ } else {
+ gfx::NativeCursor invisible_cursor(ui::kCursorNone);
+ image_cursors_->SetPlatformCursor(&invisible_cursor);
+ SetCursorOnAllRootWindows(invisible_cursor);
+ }
+
+ NotifyCursorVisibilityChange(visible);
+}
+
+void AshNativeCursorManager::SetMouseEventsEnabled(
+ bool enabled,
+ views::corewm::NativeCursorManagerDelegate* delegate) {
+ delegate->CommitMouseEventsEnabled(enabled);
+
+ if (enabled) {
+ aura::Env::GetInstance()->set_last_mouse_location(
+ disabled_cursor_location_);
+ } else {
+ disabled_cursor_location_ = aura::Env::GetInstance()->last_mouse_location();
+ }
+
+ SetVisibility(delegate->GetCurrentVisibility(), delegate);
+ NotifyMouseEventsEnableStateChange(enabled);
+}
+
+} // namespace ash
diff --git a/chromium/ash/wm/ash_native_cursor_manager.h b/chromium/ash/wm/ash_native_cursor_manager.h
new file mode 100644
index 00000000000..5ea9ec9a41b
--- /dev/null
+++ b/chromium/ash/wm/ash_native_cursor_manager.h
@@ -0,0 +1,66 @@
+// Copyright (c) 2012 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 ASH_WM_ASH_NATIVE_CURSOR_MANAGER_H_
+#define ASH_WM_ASH_NATIVE_CURSOR_MANAGER_H_
+
+#include "ash/ash_export.h"
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string16.h"
+#include "ui/gfx/native_widget_types.h"
+#include "ui/gfx/point.h"
+#include "ui/views/corewm/native_cursor_manager.h"
+#include "ui/views/corewm/native_cursor_manager_delegate.h"
+
+namespace ash {
+
+namespace test {
+class CursorManagerTestApi;
+}
+
+class ImageCursors;
+
+// This does the ash-specific setting of cursor details like cursor
+// visibility. It communicates back with the CursorManager through the
+// NativeCursorManagerDelegate interface, which receives messages about what
+// changes were acted on.
+class ASH_EXPORT AshNativeCursorManager
+ : public views::corewm::NativeCursorManager {
+ public:
+ AshNativeCursorManager();
+ virtual ~AshNativeCursorManager();
+
+ private:
+ friend class test::CursorManagerTestApi;
+
+ // Overridden from views::corewm::NativeCursorManager:
+ virtual void SetDisplay(
+ const gfx::Display& display,
+ views::corewm::NativeCursorManagerDelegate* delegate) OVERRIDE;
+ virtual void SetCursor(
+ gfx::NativeCursor cursor,
+ views::corewm::NativeCursorManagerDelegate* delegate) OVERRIDE;
+ virtual void SetVisibility(
+ bool visible,
+ views::corewm::NativeCursorManagerDelegate* delegate) OVERRIDE;
+ virtual void SetScale(
+ float scale,
+ views::corewm::NativeCursorManagerDelegate* delegate) OVERRIDE;
+ virtual void SetMouseEventsEnabled(
+ bool enabled,
+ views::corewm::NativeCursorManagerDelegate* delegate) OVERRIDE;
+
+ // The cursor location where the cursor was disabled.
+ gfx::Point disabled_cursor_location_;
+
+ scoped_ptr<ImageCursors> image_cursors_;
+
+ DISALLOW_COPY_AND_ASSIGN(AshNativeCursorManager);
+};
+
+} // namespace ash
+
+#endif // ASH_WM_ASH_NATIVE_CURSOR_MANAGER_H_
diff --git a/chromium/ash/wm/ash_native_cursor_manager_unittest.cc b/chromium/ash/wm/ash_native_cursor_manager_unittest.cc
new file mode 100644
index 00000000000..bd748ed1944
--- /dev/null
+++ b/chromium/ash/wm/ash_native_cursor_manager_unittest.cc
@@ -0,0 +1,179 @@
+// Copyright (c) 2012 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.
+
+#include "ash/wm/ash_native_cursor_manager.h"
+
+#include "ash/shell.h"
+#include "ash/test/ash_test_base.h"
+#include "ash/test/cursor_manager_test_api.h"
+#include "ash/wm/image_cursors.h"
+#include "ui/aura/root_window.h"
+#include "ui/aura/test/test_window_delegate.h"
+#include "ui/aura/test/test_windows.h"
+#include "ui/aura/window.h"
+#include "ui/gfx/screen.h"
+
+#if defined(OS_WIN)
+#include "base/win/windows_version.h"
+#include "ui/base/cursor/cursor_loader_win.h"
+#endif
+
+using views::corewm::CursorManager;
+
+namespace ash {
+namespace test {
+
+namespace {
+
+// A delegate for recording a mouse event location.
+class MouseEventLocationDelegate : public aura::test::TestWindowDelegate {
+ public:
+ MouseEventLocationDelegate() {}
+ virtual ~MouseEventLocationDelegate() {}
+
+ gfx::Point GetMouseEventLocationAndReset() {
+ gfx::Point p = mouse_event_location_;
+ mouse_event_location_.SetPoint(-100, -100);
+ return p;
+ }
+
+ virtual void OnMouseEvent(ui::MouseEvent* event) OVERRIDE {
+ mouse_event_location_ = event->location();
+ event->SetHandled();
+ }
+
+ private:
+ gfx::Point mouse_event_location_;
+
+ DISALLOW_COPY_AND_ASSIGN(MouseEventLocationDelegate);
+};
+
+} // namespace
+
+typedef test::AshTestBase AshNativeCursorManagerTest;
+
+TEST_F(AshNativeCursorManagerTest, LockCursor) {
+ CursorManager* cursor_manager = Shell::GetInstance()->cursor_manager();
+ CursorManagerTestApi test_api(cursor_manager);
+ gfx::Display display(0);
+#if defined(OS_WIN)
+ ui::CursorLoaderWin::SetCursorResourceModule(L"ash_unittests.exe");
+#endif
+ cursor_manager->SetCursor(ui::kCursorCopy);
+ EXPECT_EQ(ui::kCursorCopy, test_api.GetCurrentCursor().native_type());
+ display.set_device_scale_factor(2.0f);
+ display.set_rotation(gfx::Display::ROTATE_90);
+ cursor_manager->SetScale(2.5f);
+ cursor_manager->SetDisplay(display);
+ EXPECT_EQ(2.5f, test_api.GetCurrentScale());
+ EXPECT_EQ(2.0f, test_api.GetDisplay().device_scale_factor());
+ EXPECT_EQ(gfx::Display::ROTATE_90, test_api.GetDisplay().rotation());
+ EXPECT_TRUE(test_api.GetCurrentCursor().platform());
+
+ cursor_manager->LockCursor();
+ EXPECT_TRUE(cursor_manager->is_cursor_locked());
+
+ // Cusror scale does change even while cursor is locked.
+ EXPECT_EQ(2.5f, test_api.GetCurrentScale());
+ cursor_manager->SetScale(1.f);
+ EXPECT_EQ(1.f, test_api.GetCurrentScale());
+ cursor_manager->SetScale(1.5f);
+ EXPECT_EQ(1.5f, test_api.GetCurrentScale());
+
+ // Cursor type does not change while cursor is locked.
+ cursor_manager->SetCursor(ui::kCursorPointer);
+ EXPECT_EQ(ui::kCursorCopy, test_api.GetCurrentCursor().native_type());
+
+ // Device scale factor and rotation do change even while cursor is locked.
+ display.set_device_scale_factor(1.0f);
+ display.set_rotation(gfx::Display::ROTATE_180);
+ cursor_manager->SetDisplay(display);
+ EXPECT_EQ(1.0f, test_api.GetDisplay().device_scale_factor());
+ EXPECT_EQ(gfx::Display::ROTATE_180, test_api.GetDisplay().rotation());
+
+ cursor_manager->UnlockCursor();
+ EXPECT_FALSE(cursor_manager->is_cursor_locked());
+
+ // Cursor type changes to the one specified while cursor is locked.
+ EXPECT_EQ(1.5f, test_api.GetCurrentScale());
+ EXPECT_EQ(ui::kCursorPointer, test_api.GetCurrentCursor().native_type());
+ EXPECT_EQ(1.0f, test_api.GetDisplay().device_scale_factor());
+ EXPECT_TRUE(test_api.GetCurrentCursor().platform());
+}
+
+TEST_F(AshNativeCursorManagerTest, SetCursor) {
+ CursorManager* cursor_manager = Shell::GetInstance()->cursor_manager();
+ CursorManagerTestApi test_api(cursor_manager);
+#if defined(OS_WIN)
+ ui::CursorLoaderWin::SetCursorResourceModule(L"ash_unittests.exe");
+#endif
+ cursor_manager->SetCursor(ui::kCursorCopy);
+ EXPECT_EQ(ui::kCursorCopy, test_api.GetCurrentCursor().native_type());
+ EXPECT_TRUE(test_api.GetCurrentCursor().platform());
+ cursor_manager->SetCursor(ui::kCursorPointer);
+ EXPECT_EQ(ui::kCursorPointer, test_api.GetCurrentCursor().native_type());
+ EXPECT_TRUE(test_api.GetCurrentCursor().platform());
+}
+
+TEST_F(AshNativeCursorManagerTest, SetScale) {
+ CursorManager* cursor_manager = Shell::GetInstance()->cursor_manager();
+ CursorManagerTestApi test_api(cursor_manager);
+
+ EXPECT_EQ(1.f, test_api.GetCurrentScale());
+
+ cursor_manager->SetScale(2.5f);
+ EXPECT_EQ(2.5f, test_api.GetCurrentScale());
+
+ cursor_manager->SetScale(1.f);
+ EXPECT_EQ(1.f, test_api.GetCurrentScale());
+}
+
+TEST_F(AshNativeCursorManagerTest, SetDeviceScaleFactorAndRotation) {
+ CursorManager* cursor_manager = Shell::GetInstance()->cursor_manager();
+ CursorManagerTestApi test_api(cursor_manager);
+
+ gfx::Display display(0);
+ display.set_device_scale_factor(2.0f);
+ cursor_manager->SetDisplay(display);
+ EXPECT_EQ(2.0f, test_api.GetDisplay().device_scale_factor());
+ EXPECT_EQ(gfx::Display::ROTATE_0, test_api.GetDisplay().rotation());
+
+ display.set_device_scale_factor(1.0f);
+ display.set_rotation(gfx::Display::ROTATE_270);
+ cursor_manager->SetDisplay(display);
+ EXPECT_EQ(1.0f, test_api.GetDisplay().device_scale_factor());
+ EXPECT_EQ(gfx::Display::ROTATE_270, test_api.GetDisplay().rotation());
+}
+
+TEST_F(AshNativeCursorManagerTest, DisabledQueryMouseLocation) {
+ aura::RootWindow* root_window = Shell::GetInstance()->GetPrimaryRootWindow();
+#if defined(OS_WIN)
+ if (base::win::GetVersion() < base::win::VERSION_WIN8)
+ return;
+ // On Windows 8 the ASH environment has two processes, the viewer process
+ // which runs in Windows 8 mode and the browser process. The initialization
+ // happens when the viewer process connects to the browser channel and sends
+ // the initial IPC message.
+ RunAllPendingInMessageLoop();
+#endif
+ root_window->MoveCursorTo(gfx::Point(10, 10));
+#if defined(OS_WIN)
+ // The MoveCursor operation on Windows 8 is implemented in the viewer process
+ // which is notified by an IPC message to perform the MoveCursor operation.
+ // We need to ensure that the IPC is delivered to the viewer process and it
+ // the ACK is sent back from the viewer indicating that the operation
+ // completed.
+ Sleep(100);
+ RunAllPendingInMessageLoop();
+#endif
+ gfx::Point mouse_location;
+ EXPECT_TRUE(root_window->QueryMouseLocationForTest(&mouse_location));
+ EXPECT_EQ("10,10", mouse_location.ToString());
+ Shell::GetInstance()->cursor_manager()->DisableMouseEvents();
+ EXPECT_FALSE(root_window->QueryMouseLocationForTest(&mouse_location));
+ EXPECT_EQ("0,0", mouse_location.ToString());
+}
+
+} // namespace test
+} // namespace ash
diff --git a/chromium/ash/wm/base_layout_manager.cc b/chromium/ash/wm/base_layout_manager.cc
new file mode 100644
index 00000000000..6842f3e3c0b
--- /dev/null
+++ b/chromium/ash/wm/base_layout_manager.cc
@@ -0,0 +1,274 @@
+// Copyright (c) 2012 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.
+
+#include "ash/wm/base_layout_manager.h"
+
+#include "ash/screen_ash.h"
+#include "ash/session_state_delegate.h"
+#include "ash/shelf/shelf_layout_manager.h"
+#include "ash/shell.h"
+#include "ash/wm/window_animations.h"
+#include "ash/wm/window_properties.h"
+#include "ash/wm/window_util.h"
+#include "ash/wm/workspace/workspace_window_resizer.h"
+#include "ui/aura/client/activation_client.h"
+#include "ui/aura/client/aura_constants.h"
+#include "ui/aura/root_window.h"
+#include "ui/aura/window.h"
+#include "ui/base/ui_base_types.h"
+#include "ui/compositor/layer.h"
+#include "ui/gfx/screen.h"
+#include "ui/views/corewm/corewm_switches.h"
+#include "ui/views/corewm/window_util.h"
+
+namespace ash {
+namespace internal {
+
+/////////////////////////////////////////////////////////////////////////////
+// BaseLayoutManager, public:
+
+BaseLayoutManager::BaseLayoutManager(aura::RootWindow* root_window)
+ : root_window_(root_window) {
+ Shell::GetInstance()->activation_client()->AddObserver(this);
+ Shell::GetInstance()->AddShellObserver(this);
+ root_window_->AddObserver(this);
+}
+
+BaseLayoutManager::~BaseLayoutManager() {
+ if (root_window_)
+ root_window_->RemoveObserver(this);
+ for (WindowSet::const_iterator i = windows_.begin(); i != windows_.end(); ++i)
+ (*i)->RemoveObserver(this);
+ Shell::GetInstance()->RemoveShellObserver(this);
+ Shell::GetInstance()->activation_client()->RemoveObserver(this);
+}
+
+// static
+gfx::Rect BaseLayoutManager::BoundsWithScreenEdgeVisible(
+ aura::Window* window,
+ const gfx::Rect& restore_bounds) {
+ gfx::Rect max_bounds =
+ ash::ScreenAsh::GetMaximizedWindowBoundsInParent(window);
+ // If the restore_bounds are more than 1 grid step away from the size the
+ // window would be when maximized, inset it.
+ max_bounds.Inset(ash::internal::WorkspaceWindowResizer::kScreenEdgeInset,
+ ash::internal::WorkspaceWindowResizer::kScreenEdgeInset);
+ if (restore_bounds.Contains(max_bounds))
+ return max_bounds;
+ return restore_bounds;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// BaseLayoutManager, LayoutManager overrides:
+
+void BaseLayoutManager::OnWindowResized() {
+}
+
+void BaseLayoutManager::OnWindowAddedToLayout(aura::Window* child) {
+ windows_.insert(child);
+ child->AddObserver(this);
+ // Only update the bounds if the window has a show state that depends on the
+ // workspace area.
+ if (wm::IsWindowMaximized(child) || wm::IsWindowFullscreen(child))
+ UpdateBoundsFromShowState(child);
+}
+
+void BaseLayoutManager::OnWillRemoveWindowFromLayout(aura::Window* child) {
+ windows_.erase(child);
+ child->RemoveObserver(this);
+}
+
+void BaseLayoutManager::OnWindowRemovedFromLayout(aura::Window* child) {
+}
+
+void BaseLayoutManager::OnChildWindowVisibilityChanged(aura::Window* child,
+ bool visible) {
+ if (visible && wm::IsWindowMinimized(child)) {
+ // Attempting to show a minimized window. Unminimize it.
+ child->SetProperty(aura::client::kShowStateKey,
+ child->GetProperty(aura::client::kRestoreShowStateKey));
+ child->ClearProperty(aura::client::kRestoreShowStateKey);
+ }
+}
+
+void BaseLayoutManager::SetChildBounds(aura::Window* child,
+ const gfx::Rect& requested_bounds) {
+ gfx::Rect child_bounds(requested_bounds);
+ // Some windows rely on this to set their initial bounds.
+ if (wm::IsWindowMaximized(child))
+ child_bounds = ScreenAsh::GetMaximizedWindowBoundsInParent(child);
+ else if (wm::IsWindowFullscreen(child))
+ child_bounds = ScreenAsh::GetDisplayBoundsInParent(child);
+ SetChildBoundsDirect(child, child_bounds);
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// BaseLayoutManager, ash::ShellObserver overrides:
+
+void BaseLayoutManager::OnDisplayWorkAreaInsetsChanged() {
+ AdjustAllWindowsBoundsForWorkAreaChange(
+ ADJUST_WINDOW_WORK_AREA_INSETS_CHANGED);
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// BaseLayoutManager, WindowObserver overrides:
+
+void BaseLayoutManager::OnWindowPropertyChanged(aura::Window* window,
+ const void* key,
+ intptr_t old) {
+ if (key == aura::client::kShowStateKey) {
+ ui::WindowShowState old_state = static_cast<ui::WindowShowState>(old);
+ ui::WindowShowState new_state =
+ window->GetProperty(aura::client::kShowStateKey);
+ if (old_state != new_state && old_state != ui::SHOW_STATE_MINIMIZED &&
+ !GetRestoreBoundsInScreen(window) &&
+ ((new_state == ui::SHOW_STATE_MAXIMIZED &&
+ old_state != ui::SHOW_STATE_FULLSCREEN) ||
+ (new_state == ui::SHOW_STATE_FULLSCREEN &&
+ old_state != ui::SHOW_STATE_MAXIMIZED))) {
+ SetRestoreBoundsInParent(window, window->bounds());
+ }
+
+ UpdateBoundsFromShowState(window);
+ ShowStateChanged(window, old_state);
+ }
+}
+
+void BaseLayoutManager::OnWindowDestroying(aura::Window* window) {
+ if (root_window_ == window) {
+ root_window_->RemoveObserver(this);
+ root_window_ = NULL;
+ }
+}
+
+void BaseLayoutManager::OnWindowBoundsChanged(aura::Window* window,
+ const gfx::Rect& old_bounds,
+ const gfx::Rect& new_bounds) {
+ if (root_window_ == window)
+ AdjustAllWindowsBoundsForWorkAreaChange(ADJUST_WINDOW_DISPLAY_SIZE_CHANGED);
+}
+
+//////////////////////////////////////////////////////////////////////////////
+// BaseLayoutManager, aura::client::ActivationChangeObserver implementation:
+
+void BaseLayoutManager::OnWindowActivated(aura::Window* gained_active,
+ aura::Window* lost_active) {
+ if (views::corewm::UseFocusController()) {
+ if (gained_active && wm::IsWindowMinimized(gained_active) &&
+ !gained_active->IsVisible()) {
+ gained_active->Show();
+ DCHECK(!wm::IsWindowMinimized(gained_active));
+ }
+ }
+}
+
+//////////////////////////////////////////////////////////////////////////////
+// BaseLayoutManager, protected:
+
+void BaseLayoutManager::ShowStateChanged(aura::Window* window,
+ ui::WindowShowState last_show_state) {
+ if (wm::IsWindowMinimized(window)) {
+ // Save the previous show state so that we can correctly restore it.
+ window->SetProperty(aura::client::kRestoreShowStateKey, last_show_state);
+ views::corewm::SetWindowVisibilityAnimationType(
+ window, WINDOW_VISIBILITY_ANIMATION_TYPE_MINIMIZE);
+
+ // Hide the window.
+ window->Hide();
+ // Activate another window.
+ if (wm::IsActiveWindow(window))
+ wm::DeactivateWindow(window);
+ } else if ((window->TargetVisibility() ||
+ last_show_state == ui::SHOW_STATE_MINIMIZED) &&
+ !window->layer()->visible()) {
+ // The layer may be hidden if the window was previously minimized. Make
+ // sure it's visible.
+ window->Show();
+ if (last_show_state == ui::SHOW_STATE_MINIMIZED &&
+ !wm::IsWindowMaximized(window) &&
+ !wm::IsWindowFullscreen(window)) {
+ window->ClearProperty(internal::kWindowRestoresToRestoreBounds);
+ }
+ }
+}
+
+void BaseLayoutManager::AdjustAllWindowsBoundsForWorkAreaChange(
+ AdjustWindowReason reason) {
+ // Don't do any adjustments of the insets while we are in screen locked mode.
+ // This would happen if the launcher was auto hidden before the login screen
+ // was shown and then gets shown when the login screen gets presented.
+ if (reason == ADJUST_WINDOW_WORK_AREA_INSETS_CHANGED &&
+ Shell::GetInstance()->session_state_delegate()->IsScreenLocked())
+ return;
+
+ // If a user plugs an external display into a laptop running Aura the
+ // display size will change. Maximized windows need to resize to match.
+ // We also do this when developers running Aura on a desktop manually resize
+ // the host window.
+ // We also need to do this when the work area insets changes.
+ for (WindowSet::const_iterator it = windows_.begin();
+ it != windows_.end();
+ ++it) {
+ AdjustWindowBoundsForWorkAreaChange(*it, reason);
+ }
+}
+
+void BaseLayoutManager::AdjustWindowBoundsForWorkAreaChange(
+ aura::Window* window,
+ AdjustWindowReason reason) {
+ if (wm::IsWindowMaximized(window)) {
+ SetChildBoundsDirect(
+ window, ScreenAsh::GetMaximizedWindowBoundsInParent(window));
+ } else if (wm::IsWindowFullscreen(window)) {
+ SetChildBoundsDirect(
+ window, ScreenAsh::GetDisplayBoundsInParent(window));
+ } else {
+ // The work area may be smaller than the full screen.
+ gfx::Rect display_rect =
+ ScreenAsh::GetDisplayWorkAreaBoundsInParent(window);
+ // Put as much of the window as possible within the display area.
+ gfx::Rect bounds = window->bounds();
+ bounds.AdjustToFit(display_rect);
+ window->SetBounds(bounds);
+ }
+}
+
+//////////////////////////////////////////////////////////////////////////////
+// BaseLayoutManager, private:
+
+void BaseLayoutManager::UpdateBoundsFromShowState(aura::Window* window) {
+ switch (window->GetProperty(aura::client::kShowStateKey)) {
+ case ui::SHOW_STATE_DEFAULT:
+ case ui::SHOW_STATE_NORMAL: {
+ const gfx::Rect* restore = GetRestoreBoundsInScreen(window);
+ if (restore) {
+ gfx::Rect bounds_in_parent =
+ ScreenAsh::ConvertRectFromScreen(window->parent(), *restore);
+ SetChildBoundsDirect(window,
+ BoundsWithScreenEdgeVisible(window,
+ bounds_in_parent));
+ }
+ ClearRestoreBounds(window);
+ break;
+ }
+
+ case ui::SHOW_STATE_MAXIMIZED:
+ SetChildBoundsDirect(window,
+ ScreenAsh::GetMaximizedWindowBoundsInParent(window));
+ break;
+
+ case ui::SHOW_STATE_FULLSCREEN:
+ // Don't animate the full-screen window transition.
+ // TODO(jamescook): Use animation here. Be sure the lock screen works.
+ SetChildBoundsDirect(
+ window, ScreenAsh::GetDisplayBoundsInParent(window));
+ break;
+
+ default:
+ break;
+ }
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/wm/base_layout_manager.h b/chromium/ash/wm/base_layout_manager.h
new file mode 100644
index 00000000000..0fc2b369171
--- /dev/null
+++ b/chromium/ash/wm/base_layout_manager.h
@@ -0,0 +1,121 @@
+// Copyright (c) 2012 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 ASH_WM_BASE_LAYOUT_MANAGER_H_
+#define ASH_WM_BASE_LAYOUT_MANAGER_H_
+
+#include <set>
+
+#include "ash/ash_export.h"
+#include "ash/shell_observer.h"
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "ui/aura/client/activation_change_observer.h"
+#include "ui/aura/layout_manager.h"
+#include "ui/aura/window_observer.h"
+#include "ui/base/events/event_handler.h"
+#include "ui/base/ui_base_types.h"
+
+namespace aura {
+class RootWindow;
+class Window;
+}
+
+namespace ash {
+namespace internal {
+
+// BaseLayoutManager is the simplest possible implementation for a window
+// layout manager. It listens for changes to kShowStateKey and resizes the
+// window appropriately. Subclasses should be sure to invoke the base class
+// for adding and removing windows, otherwise show state will not be tracked
+// properly.
+class ASH_EXPORT BaseLayoutManager
+ : public aura::LayoutManager,
+ public ash::ShellObserver,
+ public aura::WindowObserver,
+ public aura::client::ActivationChangeObserver {
+ public:
+ typedef std::set<aura::Window*> WindowSet;
+
+ explicit BaseLayoutManager(aura::RootWindow* root_window);
+ virtual ~BaseLayoutManager();
+
+ const WindowSet& windows() const { return windows_; }
+
+ // Given a |window| and tentative |restore_bounds|, returns new bounds that
+ // ensure that at least a few pixels of the screen background are visible
+ // outside the edges of the window.
+ static gfx::Rect BoundsWithScreenEdgeVisible(aura::Window* window,
+ const gfx::Rect& restore_bounds);
+
+ // LayoutManager overrides:
+ virtual void OnWindowResized() OVERRIDE;
+ virtual void OnWindowAddedToLayout(aura::Window* child) OVERRIDE;
+ virtual void OnWillRemoveWindowFromLayout(aura::Window* child) OVERRIDE;
+ virtual void OnWindowRemovedFromLayout(aura::Window* child) OVERRIDE;
+ virtual void OnChildWindowVisibilityChanged(aura::Window* child,
+ bool visible) OVERRIDE;
+ virtual void SetChildBounds(aura::Window* child,
+ const gfx::Rect& requested_bounds) OVERRIDE;
+
+ // ash::ShellObserver overrides:
+ virtual void OnDisplayWorkAreaInsetsChanged() OVERRIDE;
+
+ // WindowObserver overrides:
+ virtual void OnWindowPropertyChanged(aura::Window* window,
+ const void* key,
+ intptr_t old) OVERRIDE;
+ virtual void OnWindowDestroying(aura::Window* window) OVERRIDE;
+ virtual void OnWindowBoundsChanged(aura::Window* window,
+ const gfx::Rect& old_bounds,
+ const gfx::Rect& new_bounds) OVERRIDE;
+
+ // aura::client::ActivationChangeObserver overrides:
+ virtual void OnWindowActivated(aura::Window* gained_active,
+ aura::Window* lost_active) OVERRIDE;
+
+ protected:
+ enum AdjustWindowReason {
+ ADJUST_WINDOW_DISPLAY_SIZE_CHANGED,
+ ADJUST_WINDOW_WORK_AREA_INSETS_CHANGED,
+ };
+
+ // Invoked from OnWindowPropertyChanged() if |kShowStateKey| changes.
+ virtual void ShowStateChanged(aura::Window* window,
+ ui::WindowShowState last_show_state);
+
+ // Adjusts the window's bounds when the display area changes for given
+ // window. This happens when the display size, work area insets or
+ // the display on which the window exists has changed.
+ // If this is called for a display size change (i.e. |reason|
+ // is ADJUST_WINDOW_DISPLAY_SIZE_CHANGED), the non-maximized/non-fullscreen
+ // windows are readjusted to make sure the window is completely within the
+ // display region. Otherwise, it makes sure at least some parts of the window
+ // is on display.
+ virtual void AdjustAllWindowsBoundsForWorkAreaChange(
+ AdjustWindowReason reason);
+
+ // Adjusts the sizes of the specific window in respond to a screen change or
+ // display-area size change.
+ virtual void AdjustWindowBoundsForWorkAreaChange(aura::Window* window,
+ AdjustWindowReason reason);
+
+ aura::RootWindow* root_window() { return root_window_; }
+
+ private:
+ // Update window bounds based on a change in show state.
+ void UpdateBoundsFromShowState(aura::Window* window);
+
+ // Set of windows we're listening to.
+ WindowSet windows_;
+
+ aura::RootWindow* root_window_;
+
+ DISALLOW_COPY_AND_ASSIGN(BaseLayoutManager);
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_WM_BASE_LAYOUT_MANAGER_H_
diff --git a/chromium/ash/wm/base_layout_manager_unittest.cc b/chromium/ash/wm/base_layout_manager_unittest.cc
new file mode 100644
index 00000000000..c6f7156e775
--- /dev/null
+++ b/chromium/ash/wm/base_layout_manager_unittest.cc
@@ -0,0 +1,316 @@
+// Copyright (c) 2012 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.
+
+#include "ash/wm/base_layout_manager.h"
+
+#include "ash/screen_ash.h"
+#include "ash/session_state_delegate.h"
+#include "ash/shelf/shelf_layout_manager.h"
+#include "ash/shell.h"
+#include "ash/shell_window_ids.h"
+#include "ash/test/ash_test_base.h"
+#include "ash/wm/property_util.h"
+#include "ash/wm/window_util.h"
+#include "ash/wm/workspace/workspace_window_resizer.h"
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "ui/aura/client/aura_constants.h"
+#include "ui/aura/root_window.h"
+#include "ui/aura/test/test_windows.h"
+#include "ui/aura/window.h"
+#include "ui/base/ui_base_types.h"
+#include "ui/gfx/insets.h"
+#include "ui/gfx/screen.h"
+
+namespace ash {
+
+namespace {
+
+class BaseLayoutManagerTest : public test::AshTestBase {
+ public:
+ BaseLayoutManagerTest() {}
+ virtual ~BaseLayoutManagerTest() {}
+
+ virtual void SetUp() OVERRIDE {
+ test::AshTestBase::SetUp();
+ UpdateDisplay("800x600");
+ aura::Window* default_container = Shell::GetContainer(
+ Shell::GetPrimaryRootWindow(),
+ internal::kShellWindowId_DefaultContainer);
+ default_container->SetLayoutManager(new internal::BaseLayoutManager(
+ Shell::GetPrimaryRootWindow()));
+ }
+
+ aura::Window* CreateTestWindow(const gfx::Rect& bounds) {
+ return CreateTestWindowInShellWithBounds(bounds);
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(BaseLayoutManagerTest);
+};
+
+// Tests normal->maximize->normal.
+TEST_F(BaseLayoutManagerTest, Maximize) {
+ gfx::Rect bounds(100, 100, 200, 200);
+ scoped_ptr<aura::Window> window(CreateTestWindow(bounds));
+ window->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MAXIMIZED);
+ // Maximized window fills the work area, not the whole display.
+ EXPECT_EQ(
+ ScreenAsh::GetMaximizedWindowBoundsInParent(window.get()).ToString(),
+ window->bounds().ToString());
+ window->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_NORMAL);
+ EXPECT_EQ(bounds.ToString(), window->bounds().ToString());
+}
+
+// Tests normal->minimize->normal.
+TEST_F(BaseLayoutManagerTest, Minimize) {
+ gfx::Rect bounds(100, 100, 200, 200);
+ scoped_ptr<aura::Window> window(CreateTestWindow(bounds));
+ window->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MINIMIZED);
+ // Note: Currently minimize doesn't do anything except set the state.
+ // See crbug.com/104571.
+ EXPECT_EQ(bounds.ToString(), window->bounds().ToString());
+ window->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_NORMAL);
+ EXPECT_EQ(bounds.ToString(), window->bounds().ToString());
+}
+
+// A WindowDelegate which sets the focus when the window
+// becomes visible.
+class FocusDelegate : public aura::test::TestWindowDelegate {
+ public:
+ FocusDelegate()
+ : window_(NULL),
+ show_state_(ui::SHOW_STATE_END) {
+ }
+ virtual ~FocusDelegate() {}
+
+ void set_window(aura::Window* window) { window_ = window; }
+
+ // aura::test::TestWindowDelegate overrides:
+ virtual void OnWindowTargetVisibilityChanged(bool visible) OVERRIDE {
+ if (window_) {
+ if (visible)
+ window_->Focus();
+ show_state_ = window_->GetProperty(aura::client::kShowStateKey);
+ }
+ }
+
+ ui::WindowShowState GetShowStateAndReset() {
+ ui::WindowShowState ret = show_state_;
+ show_state_ = ui::SHOW_STATE_END;
+ return ret;
+ }
+
+ private:
+ aura::Window* window_;
+ ui::WindowShowState show_state_;
+
+ DISALLOW_COPY_AND_ASSIGN(FocusDelegate);
+};
+
+// Make sure that the window's show state is correct in
+// |WindowDelegate::OnWindowTargetVisibilityChanged|, and setting
+// focus in this callback doesn't cause DCHECK error. See
+// crbug.com/168383.
+TEST_F(BaseLayoutManagerTest, FocusDuringUnminimize) {
+ FocusDelegate delegate;
+ scoped_ptr<aura::Window> window(CreateTestWindowInShellWithDelegate(
+ &delegate, 0, gfx::Rect(100, 100, 100, 100)));
+ delegate.set_window(window.get());
+ window->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MINIMIZED);
+ EXPECT_FALSE(window->IsVisible());
+ EXPECT_EQ(ui::SHOW_STATE_MINIMIZED, delegate.GetShowStateAndReset());
+ window->Show();
+ EXPECT_TRUE(window->IsVisible());
+ EXPECT_EQ(ui::SHOW_STATE_DEFAULT, delegate.GetShowStateAndReset());
+}
+
+// Tests maximized window size during root window resize.
+TEST_F(BaseLayoutManagerTest, MaximizeRootWindowResize) {
+ gfx::Rect bounds(100, 100, 200, 200);
+ scoped_ptr<aura::Window> window(CreateTestWindow(bounds));
+ window->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MAXIMIZED);
+ gfx::Rect initial_work_area_bounds =
+ ScreenAsh::GetMaximizedWindowBoundsInParent(window.get());
+ EXPECT_EQ(initial_work_area_bounds.ToString(), window->bounds().ToString());
+ // Enlarge the root window. We should still match the work area size.
+ UpdateDisplay("900x700");
+ EXPECT_EQ(
+ ScreenAsh::GetMaximizedWindowBoundsInParent(window.get()).ToString(),
+ window->bounds().ToString());
+ EXPECT_NE(
+ initial_work_area_bounds.ToString(),
+ ScreenAsh::GetMaximizedWindowBoundsInParent(window.get()).ToString());
+}
+
+// Tests normal->fullscreen->normal.
+TEST_F(BaseLayoutManagerTest, Fullscreen) {
+ gfx::Rect bounds(100, 100, 200, 200);
+ scoped_ptr<aura::Window> window(CreateTestWindow(bounds));
+ window->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_FULLSCREEN);
+ // Fullscreen window fills the whole display.
+ EXPECT_EQ(Shell::GetScreen()->GetDisplayNearestWindow(
+ window.get()).bounds().ToString(),
+ window->bounds().ToString());
+ window->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_NORMAL);
+ EXPECT_EQ(bounds.ToString(), window->bounds().ToString());
+}
+
+// Tests fullscreen window size during root window resize.
+TEST_F(BaseLayoutManagerTest, FullscreenRootWindowResize) {
+ gfx::Rect bounds(100, 100, 200, 200);
+ scoped_ptr<aura::Window> window(CreateTestWindow(bounds));
+ // Fullscreen window fills the whole display.
+ window->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_FULLSCREEN);
+ EXPECT_EQ(Shell::GetScreen()->GetDisplayNearestWindow(
+ window.get()).bounds().ToString(),
+ window->bounds().ToString());
+ // Enlarge the root window. We should still match the display size.
+ UpdateDisplay("800x600");
+ EXPECT_EQ(Shell::GetScreen()->GetDisplayNearestWindow(
+ window.get()).bounds().ToString(),
+ window->bounds().ToString());
+}
+
+// Fails on Mac only. Need to be implemented. http://crbug.com/111279.
+#if defined(OS_MACOSX)
+#define MAYBE_RootWindowResizeShrinksWindows \
+ DISABLED_RootWindowResizeShrinksWindows
+#else
+#define MAYBE_RootWindowResizeShrinksWindows RootWindowResizeShrinksWindows
+#endif
+// Tests that when the screen gets smaller the windows aren't bigger than
+// the screen.
+TEST_F(BaseLayoutManagerTest, MAYBE_RootWindowResizeShrinksWindows) {
+ scoped_ptr<aura::Window> window(
+ CreateTestWindow(gfx::Rect(10, 20, 500, 400)));
+ gfx::Rect work_area = Shell::GetScreen()->GetDisplayNearestWindow(
+ window.get()).work_area();
+ // Invariant: Window is smaller than work area.
+ EXPECT_LE(window->bounds().width(), work_area.width());
+ EXPECT_LE(window->bounds().height(), work_area.height());
+
+ // Make the root window narrower than our window.
+ UpdateDisplay("300x400");
+ work_area = Shell::GetScreen()->GetDisplayNearestWindow(
+ window.get()).work_area();
+ EXPECT_LE(window->bounds().width(), work_area.width());
+ EXPECT_LE(window->bounds().height(), work_area.height());
+
+ // Make the root window shorter than our window.
+ UpdateDisplay("300x200");
+ work_area = Shell::GetScreen()->GetDisplayNearestWindow(
+ window.get()).work_area();
+ EXPECT_LE(window->bounds().width(), work_area.width());
+ EXPECT_LE(window->bounds().height(), work_area.height());
+
+ // Enlarging the root window does not change the window bounds.
+ gfx::Rect old_bounds = window->bounds();
+ UpdateDisplay("800x600");
+ EXPECT_EQ(old_bounds.width(), window->bounds().width());
+ EXPECT_EQ(old_bounds.height(), window->bounds().height());
+}
+
+// Tests that a maximized window with too-large restore bounds will be restored
+// to smaller than the full work area.
+TEST_F(BaseLayoutManagerTest, BoundsWithScreenEdgeVisible) {
+ // Create a window with bounds that fill the screen.
+ gfx::Rect bounds = Shell::GetScreen()->GetPrimaryDisplay().bounds();
+ scoped_ptr<aura::Window> window(CreateTestWindow(bounds));
+ // Maximize it, which writes the old bounds to restore bounds.
+ window->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MAXIMIZED);
+ // Restore it.
+ window->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_NORMAL);
+ // It should have the default maximized window bounds, inset by the grid size.
+ int grid_size = internal::WorkspaceWindowResizer::kScreenEdgeInset;
+ gfx::Rect max_bounds =
+ ash::ScreenAsh::GetMaximizedWindowBoundsInParent(window.get());
+ max_bounds.Inset(grid_size, grid_size);
+ EXPECT_EQ(max_bounds.ToString(), window->bounds().ToString());
+}
+
+// Verifies maximizing sets the restore bounds, and restoring
+// restores the bounds.
+TEST_F(BaseLayoutManagerTest, MaximizeSetsRestoreBounds) {
+ scoped_ptr<aura::Window> window(CreateTestWindow(gfx::Rect(1, 2, 3, 4)));
+
+ // Maximize it, which will keep the previous restore bounds.
+ window->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MAXIMIZED);
+ EXPECT_EQ("1,2 3x4", GetRestoreBoundsInParent(window.get()).ToString());
+
+ // Restore it, which should restore bounds and reset restore bounds.
+ window->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_NORMAL);
+ EXPECT_EQ("1,2 3x4", window->bounds().ToString());
+ EXPECT_TRUE(GetRestoreBoundsInScreen(window.get()) == NULL);
+}
+
+// Verifies maximizing keeps the restore bounds if set.
+TEST_F(BaseLayoutManagerTest, MaximizeResetsRestoreBounds) {
+ scoped_ptr<aura::Window> window(CreateTestWindow(gfx::Rect(1, 2, 3, 4)));
+ SetRestoreBoundsInParent(window.get(), gfx::Rect(10, 11, 12, 13));
+
+ // Maximize it, which will keep the previous restore bounds.
+ window->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MAXIMIZED);
+ EXPECT_EQ("10,11 12x13", GetRestoreBoundsInParent(window.get()).ToString());
+}
+
+// Verifies that the restore bounds do not get reset when restoring to a
+// maximzied state from a minimized state.
+TEST_F(BaseLayoutManagerTest, BoundsAfterRestoringToMaximizeFromMinimize) {
+ scoped_ptr<aura::Window> window(CreateTestWindow(gfx::Rect(1, 2, 3, 4)));
+ gfx::Rect bounds(10, 15, 25, 35);
+ window->SetBounds(bounds);
+
+ // Maximize it, which should reset restore bounds.
+ wm::MaximizeWindow(window.get());
+ EXPECT_EQ(bounds.ToString(),
+ GetRestoreBoundsInParent(window.get()).ToString());
+
+ // Minimize the window. The restore bounds should not change.
+ wm::MinimizeWindow(window.get());
+ EXPECT_EQ(bounds.ToString(),
+ GetRestoreBoundsInParent(window.get()).ToString());
+
+ // Show the window again. The window should be maximized, and the restore
+ // bounds should not change.
+ window->Show();
+ EXPECT_EQ(bounds.ToString(),
+ GetRestoreBoundsInParent(window.get()).ToString());
+ EXPECT_TRUE(wm::IsWindowMaximized(window.get()));
+
+ wm::RestoreWindow(window.get());
+ EXPECT_EQ(bounds.ToString(), window->bounds().ToString());
+}
+
+// Verify if the window is not resized during screen lock. See: crbug.com/173127
+TEST_F(BaseLayoutManagerTest, NotResizeWhenScreenIsLocked) {
+ SetCanLockScreen(true);
+ scoped_ptr<aura::Window> window(CreateTestWindow(gfx::Rect(1, 2, 3, 4)));
+ // window with AlwaysOnTop will be managed by BaseLayoutManager.
+ window->SetProperty(aura::client::kAlwaysOnTopKey, true);
+ window->Show();
+
+ internal::ShelfLayoutManager* shelf =
+ internal::ShelfLayoutManager::ForLauncher(window.get());
+ shelf->SetAutoHideBehavior(SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS);
+
+ window->SetBounds(ScreenAsh::GetMaximizedWindowBoundsInParent(window.get()));
+ gfx::Rect window_bounds = window->bounds();
+ EXPECT_EQ(
+ ScreenAsh::GetMaximizedWindowBoundsInParent(window.get()).ToString(),
+ window_bounds.ToString());
+
+ Shell::GetInstance()->session_state_delegate()->LockScreen();
+ shelf->UpdateVisibilityState();
+ EXPECT_NE(
+ ScreenAsh::GetMaximizedWindowBoundsInParent(window.get()).ToString(),
+ window_bounds.ToString());
+
+ Shell::GetInstance()->session_state_delegate()->UnlockScreen();
+ shelf->UpdateVisibilityState();
+ EXPECT_EQ(window_bounds.ToString(), window->bounds().ToString());
+}
+
+} // namespace
+} // namespace ash
diff --git a/chromium/ash/wm/boot_splash_screen_chromeos.cc b/chromium/ash/wm/boot_splash_screen_chromeos.cc
new file mode 100644
index 00000000000..00743409ae5
--- /dev/null
+++ b/chromium/ash/wm/boot_splash_screen_chromeos.cc
@@ -0,0 +1,76 @@
+// Copyright 2013 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.
+
+#include "ash/wm/boot_splash_screen_chromeos.h"
+
+#include "third_party/skia/include/core/SkBitmap.h"
+#include "ui/aura/root_window.h"
+#include "ui/base/x/x11_util.h"
+#include "ui/compositor/layer.h"
+#include "ui/compositor/layer_type.h"
+#include "ui/compositor/scoped_layer_animation_settings.h"
+#include "ui/gfx/canvas.h"
+
+namespace ash {
+namespace internal {
+
+// ui::LayerDelegate that copies the aura host window's content to a ui::Layer.
+class BootSplashScreen::CopyHostContentLayerDelegate
+ : public ui::LayerDelegate {
+ public:
+ explicit CopyHostContentLayerDelegate(aura::RootWindow* root_window)
+ : root_window_(root_window) {
+ }
+
+ virtual ~CopyHostContentLayerDelegate() {}
+
+ // ui::LayerDelegate overrides:
+ virtual void OnPaintLayer(gfx::Canvas* canvas) OVERRIDE {
+ // It'd be safer to copy the area to a canvas in the constructor and then
+ // copy from that canvas to this one here, but this appears to work (i.e. we
+ // only call this before we draw our first frame) and it saves us an extra
+ // copy.
+ // TODO(derat): Instead of copying the data, use GLX_EXT_texture_from_pixmap
+ // to create a zero-copy texture (when possible):
+ // https://codereview.chromium.org/10543125
+ ui::CopyAreaToCanvas(root_window_->GetAcceleratedWidget(),
+ gfx::Rect(root_window_->GetHostOrigin(), root_window_->GetHostSize()),
+ gfx::Point(), canvas);
+ }
+
+ virtual void OnDeviceScaleFactorChanged(float device_scale_factor) OVERRIDE {}
+
+ virtual base::Closure PrepareForLayerBoundsChange() OVERRIDE {
+ return base::Closure();
+ }
+
+ private:
+ aura::RootWindow* root_window_; // not owned
+
+ DISALLOW_COPY_AND_ASSIGN(CopyHostContentLayerDelegate);
+};
+
+BootSplashScreen::BootSplashScreen(aura::RootWindow* root_window)
+ : layer_delegate_(new CopyHostContentLayerDelegate(root_window)),
+ layer_(new ui::Layer(ui::LAYER_TEXTURED)) {
+ layer_->set_delegate(layer_delegate_.get());
+
+ ui::Layer* root_layer = root_window->layer();
+ layer_->SetBounds(gfx::Rect(root_layer->bounds().size()));
+ root_layer->Add(layer_.get());
+ root_layer->StackAtTop(layer_.get());
+}
+
+BootSplashScreen::~BootSplashScreen() {
+}
+
+void BootSplashScreen::StartHideAnimation(base::TimeDelta duration) {
+ ui::ScopedLayerAnimationSettings settings(layer_->GetAnimator());
+ settings.SetTransitionDuration(duration);
+ settings.SetPreemptionStrategy(ui::LayerAnimator::REPLACE_QUEUED_ANIMATIONS);
+ layer_->SetOpacity(0.0f);
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/wm/boot_splash_screen_chromeos.h b/chromium/ash/wm/boot_splash_screen_chromeos.h
new file mode 100644
index 00000000000..e844275ad5f
--- /dev/null
+++ b/chromium/ash/wm/boot_splash_screen_chromeos.h
@@ -0,0 +1,53 @@
+// Copyright 2013 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 ASH_WM_BOOT_SPLASH_SCREEN_CHROMEOS_H_
+#define ASH_WM_BOOT_SPLASH_SCREEN_CHROMEOS_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/time/time.h"
+
+namespace aura {
+class RootWindow;
+}
+
+namespace ui {
+class Layer;
+class LayerDelegate;
+}
+
+namespace ash {
+namespace internal {
+
+// BootSplashScreen manages a ui::Layer, stacked at the top of the root layer's
+// children, that displays a copy of the initial contents of the host window.
+// This allows us to continue displaying the Chrome OS boot splash screen (which
+// is displayed before Ash starts) after the compositor has taken over so we can
+// animate the transition between the splash screen and the login screen.
+class BootSplashScreen {
+ public:
+ explicit BootSplashScreen(aura::RootWindow* root_window);
+ ~BootSplashScreen();
+
+ // Begins animating |layer_|'s opacity to 0 over |duration|.
+ void StartHideAnimation(base::TimeDelta duration);
+
+ private:
+ class CopyHostContentLayerDelegate;
+
+ // Copies the host window's content to |layer_|.
+ scoped_ptr<CopyHostContentLayerDelegate> layer_delegate_;
+
+ scoped_ptr<ui::Layer> layer_;
+
+ DISALLOW_COPY_AND_ASSIGN(BootSplashScreen);
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_WM_BOOT_SPLASH_SCREEN_CHROMEOS_H_
diff --git a/chromium/ash/wm/capture_controller.cc b/chromium/ash/wm/capture_controller.cc
new file mode 100644
index 00000000000..4e17054374f
--- /dev/null
+++ b/chromium/ash/wm/capture_controller.cc
@@ -0,0 +1,74 @@
+// Copyright (c) 2012 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.
+
+#include "ash/wm/capture_controller.h"
+
+#include "ash/shell.h"
+#include "ui/aura/window.h"
+#include "ui/aura/root_window.h"
+
+namespace ash {
+namespace internal {
+
+////////////////////////////////////////////////////////////////////////////////
+// CaptureController, public:
+
+CaptureController::CaptureController()
+ : capture_window_(NULL) {
+}
+
+CaptureController::~CaptureController() {
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// CaptureController, client::CaptureClient implementation:
+
+void CaptureController::SetCapture(aura::Window* new_capture_window) {
+ if (capture_window_ == new_capture_window)
+ return;
+ // Make sure window has a root window.
+ DCHECK(!new_capture_window || new_capture_window->GetRootWindow());
+ DCHECK(!capture_window_ || capture_window_->GetRootWindow());
+
+ aura::Window* old_capture_window = capture_window_;
+ Shell::RootWindowList root_windows = Shell::GetAllRootWindows();
+
+ // If we're actually starting capture, then cancel any touches/gestures
+ // that aren't already locked to the new window, and transfer any on the
+ // old capture window to the new one. When capture is released we have no
+ // distinction between the touches/gestures that were in the window all
+ // along (and so shouldn't be canceled) and those that got moved, so
+ // just leave them all where they are.
+ if (new_capture_window) {
+ for (Shell::RootWindowList::iterator iter = root_windows.begin();
+ iter != root_windows.end(); ++iter) {
+ aura::RootWindow* root_window = *iter;
+ root_window->gesture_recognizer()->
+ TransferEventsTo(old_capture_window, new_capture_window);
+ }
+ }
+
+ capture_window_ = new_capture_window;
+
+ for (Shell::RootWindowList::iterator iter = root_windows.begin();
+ iter != root_windows.end(); ++iter) {
+ aura::RootWindow* root_window = *iter;
+ root_window->UpdateCapture(old_capture_window, new_capture_window);
+ }
+
+ return;
+}
+
+void CaptureController::ReleaseCapture(aura::Window* window) {
+ if (capture_window_ != window)
+ return;
+ SetCapture(NULL);
+}
+
+aura::Window* CaptureController::GetCaptureWindow() {
+ return capture_window_;
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/wm/capture_controller.h b/chromium/ash/wm/capture_controller.h
new file mode 100644
index 00000000000..4468fb35ddd
--- /dev/null
+++ b/chromium/ash/wm/capture_controller.h
@@ -0,0 +1,36 @@
+// Copyright (c) 2012 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 ASH_WM_CAPTURE_CONTROLLER_H_
+#define ASH_WM_CAPTURE_CONTROLLER_H_
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "ui/aura/client/capture_client.h"
+#include "ash/ash_export.h"
+
+namespace ash {
+namespace internal {
+
+class ASH_EXPORT CaptureController : public aura::client::CaptureClient {
+ public:
+ CaptureController();
+ virtual ~CaptureController();
+
+ // Overridden from aura::client::CaptureClient:
+ virtual void SetCapture(aura::Window* window) OVERRIDE;
+ virtual void ReleaseCapture(aura::Window* window) OVERRIDE;
+ virtual aura::Window* GetCaptureWindow() OVERRIDE;
+
+ private:
+ // The current capture window. Null if there is no capture window.
+ aura::Window* capture_window_;
+
+ DISALLOW_COPY_AND_ASSIGN(CaptureController);
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_WM_CAPTURE_CONTROLLER_H_
diff --git a/chromium/ash/wm/coordinate_conversion.cc b/chromium/ash/wm/coordinate_conversion.cc
new file mode 100644
index 00000000000..467a1fc6c2a
--- /dev/null
+++ b/chromium/ash/wm/coordinate_conversion.cc
@@ -0,0 +1,47 @@
+// Copyright (c) 2012 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.
+
+#include "ash/wm/coordinate_conversion.h"
+
+#include "ash/display/display_controller.h"
+#include "ash/shell.h"
+#include "ui/aura/client/screen_position_client.h"
+#include "ui/aura/root_window.h"
+#include "ui/gfx/display.h"
+#include "ui/gfx/point.h"
+#include "ui/gfx/rect.h"
+#include "ui/gfx/screen.h"
+
+namespace ash {
+namespace wm {
+
+aura::RootWindow* GetRootWindowAt(const gfx::Point& point) {
+ const gfx::Display& display =
+ Shell::GetScreen()->GetDisplayNearestPoint(point);
+ DCHECK(display.is_valid());
+ // TODO(yusukes): Move coordinate_conversion.cc and .h to ui/aura/ once
+ // GetRootWindowForDisplayId() is moved to aura::Env.
+ return Shell::GetInstance()->display_controller()->
+ GetRootWindowForDisplayId(display.id());
+}
+
+aura::RootWindow* GetRootWindowMatching(const gfx::Rect& rect) {
+ const gfx::Display& display = Shell::GetScreen()->GetDisplayMatching(rect);
+ return Shell::GetInstance()->display_controller()->
+ GetRootWindowForDisplayId(display.id());
+}
+
+void ConvertPointToScreen(aura::Window* window, gfx::Point* point) {
+ aura::client::GetScreenPositionClient(window->GetRootWindow())->
+ ConvertPointToScreen(window, point);
+}
+
+void ConvertPointFromScreen(aura::Window* window,
+ gfx::Point* point_in_screen) {
+ aura::client::GetScreenPositionClient(window->GetRootWindow())->
+ ConvertPointFromScreen(window, point_in_screen);
+}
+
+} // namespace wm
+} // namespace ash
diff --git a/chromium/ash/wm/coordinate_conversion.h b/chromium/ash/wm/coordinate_conversion.h
new file mode 100644
index 00000000000..b1ee83cd200
--- /dev/null
+++ b/chromium/ash/wm/coordinate_conversion.h
@@ -0,0 +1,44 @@
+// Copyright (c) 2012 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 ASH_WM_COORDINATE_CONVERSION_H_
+#define ASH_WM_COORDINATE_CONVERSION_H_
+
+#include "ash/ash_export.h"
+
+namespace aura {
+class RootWindow;
+class Window;
+} // namespace gfx
+
+namespace gfx {
+class Point;
+class Rect;
+} // namespace gfx
+
+namespace ash {
+namespace wm {
+
+// Returns the RootWindow at |point| in the virtual screen coordinates.
+// Returns NULL if the root window does not exist at the given
+// point.
+ASH_EXPORT aura::RootWindow* GetRootWindowAt(const gfx::Point& point);
+
+// Returns the RootWindow that shares the most area with |rect| in
+// the virtual scren coordinates.
+ASH_EXPORT aura::RootWindow* GetRootWindowMatching(const gfx::Rect& rect);
+
+// Converts the |point| from a given |window|'s coordinates into the screen
+// coordinates.
+ASH_EXPORT void ConvertPointToScreen(aura::Window* window, gfx::Point* point);
+
+// Converts the |point| from the screen coordinates to a given |window|'s
+// coordinates.
+ASH_EXPORT void ConvertPointFromScreen(aura::Window* window,
+ gfx::Point* point_in_screen);
+
+} // namespace wm
+} // namespace ash
+
+#endif // ASH_WM_COORDINATE_CONVERSION_H_
diff --git a/chromium/ash/wm/custom_frame_view_ash.cc b/chromium/ash/wm/custom_frame_view_ash.cc
new file mode 100644
index 00000000000..9852d16a799
--- /dev/null
+++ b/chromium/ash/wm/custom_frame_view_ash.cc
@@ -0,0 +1,217 @@
+// Copyright (c) 2012 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.
+
+#include "ash/wm/custom_frame_view_ash.h"
+
+#include "ash/shell_delegate.h"
+#include "ash/wm/frame_painter.h"
+#include "ash/wm/workspace/frame_maximize_button.h"
+#include "grit/ash_resources.h"
+#include "grit/ui_strings.h" // Accessibility names
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "ui/compositor/layer_animator.h"
+#include "ui/compositor/scoped_animation_duration_scale_mode.h"
+#include "ui/gfx/canvas.h"
+#include "ui/gfx/font.h"
+#include "ui/gfx/image/image.h"
+#include "ui/gfx/rect.h"
+#include "ui/gfx/size.h"
+#include "ui/views/controls/button/image_button.h"
+#include "ui/views/widget/native_widget_aura.h"
+#include "ui/views/widget/widget.h"
+#include "ui/views/widget/widget_delegate.h"
+
+namespace {
+
+const gfx::Font& GetTitleFont() {
+ static gfx::Font* title_font = NULL;
+ if (!title_font)
+ title_font = new gfx::Font(views::NativeWidgetAura::GetWindowTitleFont());
+ return *title_font;
+}
+
+} // namespace
+
+namespace ash {
+
+// static
+const char CustomFrameViewAsh::kViewClassName[] = "CustomFrameViewAsh";
+
+////////////////////////////////////////////////////////////////////////////////
+// CustomFrameViewAsh, public:
+CustomFrameViewAsh::CustomFrameViewAsh()
+ : frame_(NULL),
+ maximize_button_(NULL),
+ close_button_(NULL),
+ window_icon_(NULL),
+ frame_painter_(new ash::FramePainter) {
+}
+
+CustomFrameViewAsh::~CustomFrameViewAsh() {
+}
+
+void CustomFrameViewAsh::Init(views::Widget* frame) {
+ frame_ = frame;
+
+ maximize_button_ = new FrameMaximizeButton(this, this);
+ maximize_button_->SetAccessibleName(
+ l10n_util::GetStringUTF16(IDS_APP_ACCNAME_MAXIMIZE));
+ AddChildView(maximize_button_);
+ close_button_ = new views::ImageButton(this);
+ close_button_->SetAccessibleName(
+ l10n_util::GetStringUTF16(IDS_APP_ACCNAME_CLOSE));
+ AddChildView(close_button_);
+
+ maximize_button_->SetVisible(frame_->widget_delegate()->CanMaximize());
+
+ if (frame_->widget_delegate()->ShouldShowWindowIcon()) {
+ window_icon_ = new views::ImageButton(this);
+ AddChildView(window_icon_);
+ }
+
+ frame_painter_->Init(frame_, window_icon_, maximize_button_, close_button_,
+ FramePainter::SIZE_BUTTON_MAXIMIZES);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// CustomFrameViewAsh, views::NonClientFrameView overrides:
+gfx::Rect CustomFrameViewAsh::GetBoundsForClientView() const {
+ int top_height = NonClientTopBorderHeight();
+ return frame_painter_->GetBoundsForClientView(top_height,
+ bounds());
+}
+
+gfx::Rect CustomFrameViewAsh::GetWindowBoundsForClientBounds(
+ const gfx::Rect& client_bounds) const {
+ int top_height = NonClientTopBorderHeight();
+ return frame_painter_->GetWindowBoundsForClientBounds(top_height,
+ client_bounds);
+}
+
+int CustomFrameViewAsh::NonClientHitTest(const gfx::Point& point) {
+ return frame_painter_->NonClientHitTest(this, point);
+}
+
+void CustomFrameViewAsh::GetWindowMask(const gfx::Size& size,
+ gfx::Path* window_mask) {
+ // No window masks in Aura.
+}
+
+void CustomFrameViewAsh::ResetWindowControls() {
+ maximize_button_->SetState(views::CustomButton::STATE_NORMAL);
+}
+
+void CustomFrameViewAsh::UpdateWindowIcon() {
+ if (window_icon_)
+ window_icon_->SchedulePaint();
+}
+
+void CustomFrameViewAsh::UpdateWindowTitle() {
+ frame_painter_->SchedulePaintForTitle(GetTitleFont());
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// CustomFrameViewAsh, views::View overrides:
+
+gfx::Size CustomFrameViewAsh::GetPreferredSize() {
+ gfx::Size pref = frame_->client_view()->GetPreferredSize();
+ gfx::Rect bounds(0, 0, pref.width(), pref.height());
+ return frame_->non_client_view()->GetWindowBoundsForClientBounds(
+ bounds).size();
+}
+
+void CustomFrameViewAsh::Layout() {
+ // Use the shorter maximized layout headers.
+ frame_painter_->LayoutHeader(this, true);
+}
+
+void CustomFrameViewAsh::OnPaint(gfx::Canvas* canvas) {
+ if (frame_->IsFullscreen())
+ return;
+
+ // Prevent bleeding paint onto the client area below the window frame, which
+ // may become visible when the WebContent is transparent.
+ canvas->Save();
+ canvas->ClipRect(gfx::Rect(0, 0, width(), NonClientTopBorderHeight()));
+
+ bool paint_as_active = ShouldPaintAsActive();
+ int theme_image_id = 0;
+ if (frame_painter_->ShouldUseMinimalHeaderStyle(FramePainter::THEMED_NO))
+ theme_image_id = IDR_AURA_WINDOW_HEADER_BASE_MINIMAL;
+ else if (paint_as_active)
+ theme_image_id = IDR_AURA_WINDOW_HEADER_BASE_ACTIVE;
+ else
+ theme_image_id = IDR_AURA_WINDOW_HEADER_BASE_INACTIVE;
+
+ frame_painter_->PaintHeader(
+ this,
+ canvas,
+ paint_as_active ? FramePainter::ACTIVE : FramePainter::INACTIVE,
+ theme_image_id,
+ 0);
+ frame_painter_->PaintTitleBar(this, canvas, GetTitleFont());
+ frame_painter_->PaintHeaderContentSeparator(this, canvas);
+ canvas->Restore();
+}
+
+const char* CustomFrameViewAsh::GetClassName() const {
+ return kViewClassName;
+}
+
+gfx::Size CustomFrameViewAsh::GetMinimumSize() {
+ return frame_painter_->GetMinimumSize(this);
+}
+
+gfx::Size CustomFrameViewAsh::GetMaximumSize() {
+ return frame_painter_->GetMaximumSize(this);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// views::ButtonListener overrides:
+void CustomFrameViewAsh::ButtonPressed(views::Button* sender,
+ const ui::Event& event) {
+ scoped_ptr<ui::ScopedAnimationDurationScaleMode> slow_duration_mode;
+ if (event.IsShiftDown()) {
+ slow_duration_mode.reset(new ui::ScopedAnimationDurationScaleMode(
+ ui::ScopedAnimationDurationScaleMode::SLOW_DURATION));
+ }
+
+ ash::UserMetricsAction action =
+ ash::UMA_WINDOW_MAXIMIZE_BUTTON_CLICK_MAXIMIZE;
+
+ if (sender == maximize_button_) {
+ // The maximize button may move out from under the cursor.
+ ResetWindowControls();
+ if (frame_->IsMaximized()) {
+ action = ash::UMA_WINDOW_MAXIMIZE_BUTTON_CLICK_RESTORE;
+ frame_->Restore();
+ } else {
+ frame_->Maximize();
+ }
+ // |this| may be deleted - some windows delete their frames on maximize.
+ } else if (sender == close_button_) {
+ action = ash::UMA_WINDOW_CLOSE_BUTTON_CLICK;
+ frame_->Close();
+ } else {
+ return;
+ }
+
+ ash::Shell::GetInstance()->delegate()->RecordUserMetricsAction(action);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// CustomFrameViewAsh, private:
+
+int CustomFrameViewAsh::NonClientTopBorderHeight() const {
+ if (frame_->IsFullscreen())
+ return 0;
+
+ // Reserve enough space to see the buttons, including any offset from top and
+ // reserving space for the separator line.
+ return close_button_->bounds().bottom() +
+ frame_painter_->HeaderContentSeparatorSize();
+}
+
+} // namespace ash
diff --git a/chromium/ash/wm/custom_frame_view_ash.h b/chromium/ash/wm/custom_frame_view_ash.h
new file mode 100644
index 00000000000..b6ccf7fbc9d
--- /dev/null
+++ b/chromium/ash/wm/custom_frame_view_ash.h
@@ -0,0 +1,98 @@
+// Copyright (c) 2012 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 ASH_WM_CUSTOM_FRAME_VIEW_ASH_H_
+#define ASH_WM_CUSTOM_FRAME_VIEW_ASH_H_
+
+#include "ash/ash_export.h"
+#include "base/memory/scoped_ptr.h"
+#include "ui/views/controls/button/button.h" // ButtonListener
+#include "ui/views/window/non_client_view.h"
+
+namespace ash {
+class FramePainter;
+class FrameMaximizeButton;
+}
+namespace gfx {
+class Font;
+}
+namespace views {
+class ImageButton;
+class Widget;
+}
+
+namespace ash {
+
+// A NonClientFrameView used for dialogs and other non-browser windows.
+// See also views::CustomFrameView and BrowserNonClientFrameViewAsh.
+class ASH_EXPORT CustomFrameViewAsh : public views::NonClientFrameView,
+ public views::ButtonListener {
+ public:
+ // Internal class name.
+ static const char kViewClassName[];
+
+ CustomFrameViewAsh();
+ virtual ~CustomFrameViewAsh();
+
+ // For testing.
+ class TestApi {
+ public:
+ explicit TestApi(CustomFrameViewAsh* frame) : frame_(frame) {
+ }
+
+ ash::FrameMaximizeButton* maximize_button() const {
+ return frame_->maximize_button_;
+ }
+
+ private:
+ TestApi();
+ CustomFrameViewAsh* frame_;
+ };
+
+ void Init(views::Widget* frame);
+
+ views::ImageButton* close_button() { return close_button_; }
+
+ // views::NonClientFrameView overrides:
+ virtual gfx::Rect GetBoundsForClientView() const OVERRIDE;
+ virtual gfx::Rect GetWindowBoundsForClientBounds(
+ const gfx::Rect& client_bounds) const OVERRIDE;
+ virtual int NonClientHitTest(const gfx::Point& point) OVERRIDE;
+ virtual void GetWindowMask(const gfx::Size& size,
+ gfx::Path* window_mask) OVERRIDE;
+ virtual void ResetWindowControls() OVERRIDE;
+ virtual void UpdateWindowIcon() OVERRIDE;
+ virtual void UpdateWindowTitle() OVERRIDE;
+
+ // views::View overrides:
+ virtual gfx::Size GetPreferredSize() OVERRIDE;
+ virtual void Layout() OVERRIDE;
+ virtual const char* GetClassName() const OVERRIDE;
+ virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE;
+ virtual gfx::Size GetMinimumSize() OVERRIDE;
+ virtual gfx::Size GetMaximumSize() OVERRIDE;
+
+ // views::ButtonListener overrides:
+ virtual void ButtonPressed(views::Button* sender,
+ const ui::Event& event) OVERRIDE;
+
+ private:
+ // Height from top of window to top of client area.
+ int NonClientTopBorderHeight() const;
+
+ // Not owned.
+ views::Widget* frame_;
+
+ ash::FrameMaximizeButton* maximize_button_;
+ views::ImageButton* close_button_;
+ views::ImageButton* window_icon_;
+
+ scoped_ptr<FramePainter> frame_painter_;
+
+ DISALLOW_COPY_AND_ASSIGN(CustomFrameViewAsh);
+};
+
+} // namespace ash
+
+#endif // ASH_WM_CUSTOM_FRAME_VIEW_ASH_H_
diff --git a/chromium/ash/wm/custom_frame_view_ash_unittest.cc b/chromium/ash/wm/custom_frame_view_ash_unittest.cc
new file mode 100644
index 00000000000..c08704679b8
--- /dev/null
+++ b/chromium/ash/wm/custom_frame_view_ash_unittest.cc
@@ -0,0 +1,871 @@
+// Copyright (c) 2012 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.
+
+#include "ash/wm/custom_frame_view_ash.h"
+
+#include "ash/shell.h"
+#include "ash/test/ash_test_base.h"
+#include "ash/wm/maximize_bubble_controller.h"
+#include "ash/wm/property_util.h"
+#include "ash/wm/window_util.h"
+#include "ash/wm/workspace/frame_maximize_button.h"
+#include "ash/wm/workspace/snap_sizer.h"
+#include "base/command_line.h"
+#include "ui/aura/client/aura_constants.h"
+#include "ui/aura/client/focus_client.h"
+#include "ui/aura/test/event_generator.h"
+#include "ui/aura/root_window.h"
+#include "ui/aura/window.h"
+#include "ui/base/events/event_utils.h"
+#include "ui/base/gestures/gesture_configuration.h"
+#include "ui/views/controls/button/image_button.h"
+#include "ui/views/test/test_views_delegate.h"
+#include "ui/views/widget/widget.h"
+#include "ui/views/widget/widget_delegate.h"
+
+namespace ash {
+namespace internal {
+
+namespace {
+
+
+class CancelCallbackHandler {
+ public:
+ CancelCallbackHandler(int update_events_before_cancel,
+ FrameMaximizeButton* maximize_button) :
+ update_events_before_cancel_(update_events_before_cancel),
+ maximize_button_(maximize_button) {}
+ virtual ~CancelCallbackHandler() {}
+
+ void CountedCancelCallback(ui::EventType event_type,
+ const gfx::Vector2dF& pos) {
+ if (event_type == ui::ET_GESTURE_SCROLL_UPDATE &&
+ !(--update_events_before_cancel_)) {
+ // Make sure that we are in the middle of a resizing operation, cancel it
+ // and then test that it is exited.
+ EXPECT_TRUE(maximize_button_->is_snap_enabled());
+ maximize_button_->DestroyMaximizeMenu();
+ EXPECT_FALSE(maximize_button_->is_snap_enabled());
+ }
+ }
+
+ private:
+ // When this counter reaches 0, the gesture maximize action gets cancelled.
+ int update_events_before_cancel_;
+
+ // The maximize button which needs to get informed of the gesture termination.
+ FrameMaximizeButton* maximize_button_;
+
+ DISALLOW_COPY_AND_ASSIGN(CancelCallbackHandler);
+};
+
+class ShellViewsDelegate : public views::TestViewsDelegate {
+ public:
+ ShellViewsDelegate() {}
+ virtual ~ShellViewsDelegate() {}
+
+ // Overridden from views::TestViewsDelegate:
+ virtual views::NonClientFrameView* CreateDefaultNonClientFrameView(
+ views::Widget* widget) OVERRIDE {
+ // Always test CustomFrameViewAsh, which may not be the ash::Shell default.
+ CustomFrameViewAsh* frame_view = new CustomFrameViewAsh;
+ frame_view->Init(widget);
+ return frame_view;
+ }
+ virtual bool UseTransparentWindows() const OVERRIDE {
+ // Ash uses transparent window frames.
+ return true;
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ShellViewsDelegate);
+};
+
+class TestWidgetDelegate : public views::WidgetDelegateView {
+ public:
+ TestWidgetDelegate() {}
+ virtual ~TestWidgetDelegate() {}
+
+ // Overridden from views::WidgetDelegate:
+ virtual views::View* GetContentsView() OVERRIDE {
+ return this;
+ }
+ virtual bool CanResize() const OVERRIDE {
+ return true;
+ }
+ virtual bool CanMaximize() const OVERRIDE {
+ return true;
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(TestWidgetDelegate);
+};
+
+} // namespace
+
+class CustomFrameViewAshTest : public ash::test::AshTestBase {
+ public:
+ CustomFrameViewAshTest() {}
+ virtual ~CustomFrameViewAshTest() {}
+
+ views::Widget* CreateWidget() {
+ views::Widget::InitParams params(views::Widget::InitParams::TYPE_WINDOW);
+ views::Widget* widget = new views::Widget;
+ params.context = CurrentContext();
+ params.delegate = new TestWidgetDelegate;
+ widget->Init(params);
+ widget->Show();
+ return widget;
+ }
+
+ CustomFrameViewAsh* GetCustomFrameViewAsh(views::Widget* widget) const {
+ return static_cast<CustomFrameViewAsh*>(widget->non_client_view()->
+ frame_view());
+ }
+
+ virtual void SetUp() OVERRIDE {
+ AshTestBase::SetUp();
+ if (!views::ViewsDelegate::views_delegate) {
+ views_delegate_.reset(new ShellViewsDelegate);
+ views::ViewsDelegate::views_delegate = views_delegate_.get();
+ }
+ }
+
+ virtual void TearDown() OVERRIDE {
+ if (views_delegate_) {
+ views::ViewsDelegate::views_delegate = NULL;
+ views_delegate_.reset();
+ }
+ AshTestBase::TearDown();
+ }
+
+ private:
+ scoped_ptr<views::ViewsDelegate> views_delegate_;
+
+ DISALLOW_COPY_AND_ASSIGN(CustomFrameViewAshTest);
+};
+
+// Tests that clicking on the resize-button toggles between maximize and normal
+// state.
+TEST_F(CustomFrameViewAshTest, ResizeButtonToggleMaximize) {
+ views::Widget* widget = CreateWidget();
+ aura::Window* window = widget->GetNativeWindow();
+ CustomFrameViewAsh* frame = GetCustomFrameViewAsh(widget);
+ CustomFrameViewAsh::TestApi test(frame);
+ views::View* view = test.maximize_button();
+ gfx::Point center = view->GetBoundsInScreen().CenterPoint();
+
+ aura::test::EventGenerator generator(window->GetRootWindow(), center);
+
+ EXPECT_FALSE(ash::wm::IsWindowMaximized(window));
+
+ generator.ClickLeftButton();
+ RunAllPendingInMessageLoop();
+ EXPECT_TRUE(ash::wm::IsWindowMaximized(window));
+
+ center = view->GetBoundsInScreen().CenterPoint();
+ generator.MoveMouseTo(center);
+ generator.ClickLeftButton();
+ RunAllPendingInMessageLoop();
+ EXPECT_FALSE(ash::wm::IsWindowMaximized(window));
+
+ generator.GestureTapAt(view->GetBoundsInScreen().CenterPoint());
+ EXPECT_TRUE(ash::wm::IsWindowMaximized(window));
+
+ generator.GestureTapAt(view->GetBoundsInScreen().CenterPoint());
+ EXPECT_FALSE(ash::wm::IsWindowMaximized(window));
+
+ generator.GestureTapDownAndUp(view->GetBoundsInScreen().CenterPoint());
+ EXPECT_TRUE(ash::wm::IsWindowMaximized(window));
+
+ generator.GestureTapDownAndUp(view->GetBoundsInScreen().CenterPoint());
+ EXPECT_FALSE(ash::wm::IsWindowMaximized(window));
+
+ widget->Close();
+}
+
+#if defined(OS_WIN)
+// RootWindow and Display can't resize on Windows Ash. http://crbug.com/165962
+#define MAYBE_ResizeButtonDrag DISABLED_ResizeButtonDrag
+#else
+#define MAYBE_ResizeButtonDrag ResizeButtonDrag
+#endif
+
+// Tests that click+dragging on the resize-button tiles or minimizes the window.
+TEST_F(CustomFrameViewAshTest, MAYBE_ResizeButtonDrag) {
+ views::Widget* widget = CreateWidget();
+ aura::Window* window = widget->GetNativeWindow();
+ CustomFrameViewAsh* frame = GetCustomFrameViewAsh(widget);
+ CustomFrameViewAsh::TestApi test(frame);
+ views::View* view = test.maximize_button();
+ gfx::Point center = view->GetBoundsInScreen().CenterPoint();
+
+ aura::test::EventGenerator generator(window->GetRootWindow(), center);
+
+ EXPECT_TRUE(ash::wm::IsWindowNormal(window));
+
+ // Snap right.
+ {
+ generator.PressLeftButton();
+ generator.MoveMouseBy(10, 0);
+ generator.ReleaseLeftButton();
+ RunAllPendingInMessageLoop();
+
+ EXPECT_FALSE(ash::wm::IsWindowMaximized(window));
+ EXPECT_FALSE(ash::wm::IsWindowMinimized(window));
+ internal::SnapSizer sizer(window, center,
+ internal::SnapSizer::RIGHT_EDGE,
+ internal::SnapSizer::OTHER_INPUT);
+ EXPECT_EQ(sizer.target_bounds().ToString(), window->bounds().ToString());
+ }
+
+ // Snap left.
+ {
+ center = view->GetBoundsInScreen().CenterPoint();
+ generator.MoveMouseTo(center);
+ generator.PressLeftButton();
+ generator.MoveMouseBy(-10, 0);
+ generator.ReleaseLeftButton();
+ RunAllPendingInMessageLoop();
+
+ EXPECT_FALSE(ash::wm::IsWindowMaximized(window));
+ EXPECT_FALSE(ash::wm::IsWindowMinimized(window));
+ internal::SnapSizer sizer(window, center,
+ internal::SnapSizer::LEFT_EDGE,
+ internal::SnapSizer::OTHER_INPUT);
+ EXPECT_EQ(sizer.target_bounds().ToString(), window->bounds().ToString());
+ }
+
+ // Minimize.
+ {
+ center = view->GetBoundsInScreen().CenterPoint();
+ generator.MoveMouseTo(center);
+ generator.PressLeftButton();
+ generator.MoveMouseBy(0, 10);
+ generator.ReleaseLeftButton();
+ RunAllPendingInMessageLoop();
+
+ EXPECT_TRUE(ash::wm::IsWindowMinimized(window));
+ }
+
+ ash::wm::RestoreWindow(window);
+
+ // Now test the same behaviour for gesture events.
+
+ // Snap right.
+ {
+ center = view->GetBoundsInScreen().CenterPoint();
+ gfx::Point end = center;
+ end.Offset(40, 0);
+ generator.GestureScrollSequence(center, end,
+ base::TimeDelta::FromMilliseconds(100),
+ 3);
+ RunAllPendingInMessageLoop();
+
+ EXPECT_FALSE(ash::wm::IsWindowMaximized(window));
+ EXPECT_FALSE(ash::wm::IsWindowMinimized(window));
+ // This is a short resizing distance and different touch behavior
+ // applies which leads in half of the screen being used.
+ EXPECT_EQ("400,0 400x552", window->bounds().ToString());
+ }
+
+ // Snap left.
+ {
+ center = view->GetBoundsInScreen().CenterPoint();
+ gfx::Point end = center;
+ end.Offset(-40, 0);
+ generator.GestureScrollSequence(center, end,
+ base::TimeDelta::FromMilliseconds(100),
+ 3);
+ RunAllPendingInMessageLoop();
+
+ EXPECT_FALSE(ash::wm::IsWindowMaximized(window));
+ EXPECT_FALSE(ash::wm::IsWindowMinimized(window));
+ internal::SnapSizer sizer(window, center,
+ internal::SnapSizer::LEFT_EDGE,
+ internal::SnapSizer::OTHER_INPUT);
+ EXPECT_EQ(sizer.target_bounds().ToString(), window->bounds().ToString());
+ }
+
+ // Minimize.
+ {
+ center = view->GetBoundsInScreen().CenterPoint();
+ gfx::Point end = center;
+ end.Offset(0, 40);
+ generator.GestureScrollSequence(center, end,
+ base::TimeDelta::FromMilliseconds(100),
+ 3);
+ RunAllPendingInMessageLoop();
+
+ EXPECT_TRUE(ash::wm::IsWindowMinimized(window));
+ }
+
+ // Test with gesture events.
+
+ widget->Close();
+}
+
+#if defined(OS_WIN)
+// RootWindow and Display can't resize on Windows Ash. http://crbug.com/165962
+#define MAYBE_TouchDragResizeCloseToCornerDiffersFromMouse \
+ DISABLED_TouchDragResizeCloseToCornerDiffersFromMouse
+#else
+#define MAYBE_TouchDragResizeCloseToCornerDiffersFromMouse \
+ TouchDragResizeCloseToCornerDiffersFromMouse
+#endif
+
+// Tests Left/Right snapping with resize button touch dragging - which should
+// trigger dependent on the available drag distance.
+TEST_F(CustomFrameViewAshTest,
+ MAYBE_TouchDragResizeCloseToCornerDiffersFromMouse) {
+ views::Widget* widget = CreateWidget();
+ aura::Window* window = widget->GetNativeWindow();
+ CustomFrameViewAsh* frame = GetCustomFrameViewAsh(widget);
+ CustomFrameViewAsh::TestApi test(frame);
+ views::View* view = test.maximize_button();
+
+ gfx::Rect work_area = widget->GetWorkAreaBoundsInScreen();
+ gfx::Rect bounds = window->bounds();
+ bounds.set_x(work_area.width() - bounds.width());
+ widget->SetBounds(bounds);
+
+ gfx::Point start_point = view->GetBoundsInScreen().CenterPoint();
+ // We want to move all the way to the right (the few pixels we have).
+ gfx::Point end_point = gfx::Point(work_area.width(), start_point.y());
+
+ aura::test::EventGenerator generator(window->GetRootWindow(), start_point);
+
+ EXPECT_TRUE(ash::wm::IsWindowNormal(window));
+
+ // Snap right with a touch drag.
+ generator.GestureScrollSequence(start_point,
+ end_point,
+ base::TimeDelta::FromMilliseconds(100),
+ 10);
+ RunAllPendingInMessageLoop();
+
+ EXPECT_FALSE(ash::wm::IsWindowMaximized(window));
+ EXPECT_FALSE(ash::wm::IsWindowMinimized(window));
+ gfx::Rect touch_result = window->bounds();
+ EXPECT_NE(bounds.ToString(), touch_result.ToString());
+
+ // Set the position back to where it was before and re-try with a mouse.
+ widget->SetBounds(bounds);
+
+ generator.MoveMouseTo(start_point);
+ generator.PressLeftButton();
+ generator.MoveMouseTo(end_point, 10);
+ generator.ReleaseLeftButton();
+ RunAllPendingInMessageLoop();
+
+ EXPECT_FALSE(ash::wm::IsWindowMaximized(window));
+ EXPECT_FALSE(ash::wm::IsWindowMinimized(window));
+ gfx::Rect mouse_result = window->bounds();
+
+ // The difference between the two operations should be that the mouse
+ // operation should have just started to resize and the touch operation is
+ // already all the way down to the smallest possible size.
+ EXPECT_NE(mouse_result.ToString(), touch_result.ToString());
+ EXPECT_GT(mouse_result.width(), touch_result.width());
+}
+
+
+// Test that closing the (browser) window with an opened balloon does not
+// crash the system. In other words: Make sure that shutting down the frame
+// destroys the opened balloon in an orderly fashion.
+TEST_F(CustomFrameViewAshTest, MaximizeButtonExternalShutDown) {
+ views::Widget* widget = CreateWidget();
+ aura::Window* window = widget->GetNativeWindow();
+ CustomFrameViewAsh* frame = GetCustomFrameViewAsh(widget);
+ CustomFrameViewAsh::TestApi test(frame);
+ ash::FrameMaximizeButton* maximize_button = test.maximize_button();
+ maximize_button->set_bubble_appearance_delay_ms(0);
+ gfx::Point button_pos = maximize_button->GetBoundsInScreen().CenterPoint();
+ gfx::Point off_pos(button_pos.x() + 100, button_pos.y() + 100);
+
+ aura::test::EventGenerator generator(window->GetRootWindow(), off_pos);
+ EXPECT_FALSE(maximize_button->maximizer());
+ EXPECT_TRUE(ash::wm::IsWindowNormal(window));
+
+ // Move the mouse cursor over the button to bring up the maximizer bubble.
+ generator.MoveMouseTo(button_pos);
+ EXPECT_TRUE(maximize_button->maximizer());
+
+ // Even though the widget is closing the bubble menu should not crash upon
+ // its delayed destruction.
+ widget->CloseNow();
+}
+
+// Test that maximizing the browser after hovering in does not crash the system
+// when the observer gets removed in the bubble destruction process.
+TEST_F(CustomFrameViewAshTest, MaximizeOnHoverThenClick) {
+ views::Widget* widget = CreateWidget();
+ aura::Window* window = widget->GetNativeWindow();
+ CustomFrameViewAsh* frame = GetCustomFrameViewAsh(widget);
+ CustomFrameViewAsh::TestApi test(frame);
+ ash::FrameMaximizeButton* maximize_button = test.maximize_button();
+ maximize_button->set_bubble_appearance_delay_ms(0);
+ gfx::Point button_pos = maximize_button->GetBoundsInScreen().CenterPoint();
+ gfx::Point off_pos(button_pos.x() + 100, button_pos.y() + 100);
+
+ aura::test::EventGenerator generator(window->GetRootWindow(), off_pos);
+ EXPECT_FALSE(maximize_button->maximizer());
+ EXPECT_TRUE(ash::wm::IsWindowNormal(window));
+
+ // Move the mouse cursor over the button to bring up the maximizer bubble.
+ generator.MoveMouseTo(button_pos);
+ EXPECT_TRUE(maximize_button->maximizer());
+ generator.ClickLeftButton();
+ EXPECT_TRUE(ash::wm::IsWindowMaximized(window));
+}
+
+// Test that hovering over a button in the balloon dialog will show the phantom
+// window. Moving then away from the button will hide it again. Then check that
+// pressing and dragging the button itself off the button will also release the
+// phantom window.
+TEST_F(CustomFrameViewAshTest, MaximizeLeftButtonDragOut) {
+ views::Widget* widget = CreateWidget();
+ aura::Window* window = widget->GetNativeWindow();
+ CustomFrameViewAsh* frame = GetCustomFrameViewAsh(widget);
+ CustomFrameViewAsh::TestApi test(frame);
+ ash::FrameMaximizeButton* maximize_button = test.maximize_button();
+ maximize_button->set_bubble_appearance_delay_ms(0);
+ gfx::Point button_pos = maximize_button->GetBoundsInScreen().CenterPoint();
+ gfx::Point off_pos(button_pos.x() + 100, button_pos.y() + 100);
+
+ aura::test::EventGenerator generator(window->GetRootWindow(), off_pos);
+ EXPECT_FALSE(maximize_button->maximizer());
+ EXPECT_TRUE(ash::wm::IsWindowNormal(window));
+ EXPECT_FALSE(maximize_button->phantom_window_open());
+
+ // Move the mouse cursor over the button to bring up the maximizer bubble.
+ generator.MoveMouseTo(button_pos);
+ EXPECT_TRUE(maximize_button->maximizer());
+
+ // Move the mouse over the left maximize button.
+ gfx::Point left_max_pos = maximize_button->maximizer()->
+ GetButtonForUnitTest(SNAP_LEFT)->GetBoundsInScreen().CenterPoint();
+
+ generator.MoveMouseTo(left_max_pos);
+ // Expect the phantom window to be open.
+ EXPECT_TRUE(maximize_button->phantom_window_open());
+
+ // Move away to see the window being destroyed.
+ generator.MoveMouseTo(off_pos);
+ EXPECT_FALSE(maximize_button->phantom_window_open());
+
+ // Move back over the button.
+ generator.MoveMouseTo(button_pos);
+ generator.MoveMouseTo(left_max_pos);
+ EXPECT_TRUE(maximize_button->phantom_window_open());
+
+ // Press button and drag out of dialog.
+ generator.PressLeftButton();
+ generator.MoveMouseTo(off_pos);
+ generator.ReleaseLeftButton();
+
+ // Check that the phantom window is also gone.
+ EXPECT_FALSE(maximize_button->phantom_window_open());
+}
+
+// Test that clicking a button in the maximizer bubble (in this case the
+// maximize left button) will do the requested action.
+TEST_F(CustomFrameViewAshTest, MaximizeLeftByButton) {
+ views::Widget* widget = CreateWidget();
+ aura::Window* window = widget->GetNativeWindow();
+ CustomFrameViewAsh* frame = GetCustomFrameViewAsh(widget);
+ CustomFrameViewAsh::TestApi test(frame);
+ ash::FrameMaximizeButton* maximize_button = test.maximize_button();
+ maximize_button->set_bubble_appearance_delay_ms(0);
+ gfx::Point button_pos = maximize_button->GetBoundsInScreen().CenterPoint();
+ gfx::Point off_pos(button_pos.x() + 100, button_pos.y() + 100);
+
+ aura::test::EventGenerator generator(window->GetRootWindow(), off_pos);
+ EXPECT_FALSE(maximize_button->maximizer());
+ EXPECT_TRUE(ash::wm::IsWindowNormal(window));
+ EXPECT_FALSE(maximize_button->phantom_window_open());
+
+ // Move the mouse cursor over the button to bring up the maximizer bubble.
+ generator.MoveMouseTo(button_pos);
+ EXPECT_TRUE(maximize_button->maximizer());
+
+ // Move the mouse over the left maximize button.
+ gfx::Point left_max_pos = maximize_button->maximizer()->
+ GetButtonForUnitTest(SNAP_LEFT)->GetBoundsInScreen().CenterPoint();
+ generator.MoveMouseTo(left_max_pos);
+ EXPECT_TRUE(maximize_button->phantom_window_open());
+ generator.ClickLeftButton();
+
+ EXPECT_FALSE(maximize_button->maximizer());
+ EXPECT_FALSE(maximize_button->phantom_window_open());
+
+ EXPECT_FALSE(ash::wm::IsWindowMaximized(window));
+ EXPECT_FALSE(ash::wm::IsWindowMinimized(window));
+ internal::SnapSizer sizer(window, button_pos,
+ internal::SnapSizer::LEFT_EDGE,
+ internal::SnapSizer::OTHER_INPUT);
+ sizer.SelectDefaultSizeAndDisableResize();
+ EXPECT_EQ(sizer.target_bounds().ToString(), window->bounds().ToString());
+}
+
+// Test that the activation focus does not change when the bubble gets shown.
+TEST_F(CustomFrameViewAshTest, MaximizeKeepFocus) {
+ views::Widget* widget = CreateWidget();
+ aura::Window* window = widget->GetNativeWindow();
+ CustomFrameViewAsh* frame = GetCustomFrameViewAsh(widget);
+ CustomFrameViewAsh::TestApi test(frame);
+ ash::FrameMaximizeButton* maximize_button = test.maximize_button();
+ maximize_button->set_bubble_appearance_delay_ms(0);
+ gfx::Point button_pos = maximize_button->GetBoundsInScreen().CenterPoint();
+ gfx::Point off_pos(button_pos.x() + 100, button_pos.y() + 100);
+
+ aura::test::EventGenerator generator(window->GetRootWindow(), off_pos);
+ EXPECT_FALSE(maximize_button->maximizer());
+ EXPECT_TRUE(ash::wm::IsWindowNormal(window));
+
+ aura::Window* active =
+ aura::client::GetFocusClient(window)->GetFocusedWindow();
+
+ // Move the mouse cursor over the button to bring up the maximizer bubble.
+ generator.MoveMouseTo(button_pos);
+ EXPECT_TRUE(maximize_button->maximizer());
+
+ // Check that the focused window is still the same.
+ EXPECT_EQ(active, aura::client::GetFocusClient(window)->GetFocusedWindow());
+}
+
+TEST_F(CustomFrameViewAshTest, MaximizeTap) {
+ views::Widget* widget = CreateWidget();
+ aura::Window* window = widget->GetNativeWindow();
+ aura::RootWindow* root_window = window->GetRootWindow();
+ CustomFrameViewAsh* frame = GetCustomFrameViewAsh(widget);
+ CustomFrameViewAsh::TestApi test(frame);
+ ash::FrameMaximizeButton* maximize_button = test.maximize_button();
+ gfx::Point button_pos = maximize_button->GetBoundsInScreen().CenterPoint();
+
+ const int touch_default_radius =
+ ui::GestureConfiguration::default_radius();
+ ui::GestureConfiguration::set_default_radius(0);
+
+ const int kTouchId = 2;
+ ui::TouchEvent press(ui::ET_TOUCH_PRESSED,
+ button_pos,
+ kTouchId,
+ ui::EventTimeForNow());
+ root_window->AsRootWindowHostDelegate()->OnHostTouchEvent(&press);
+
+ button_pos.Offset(9, 8);
+ ui::TouchEvent release(
+ ui::ET_TOUCH_RELEASED,
+ button_pos,
+ kTouchId,
+ press.time_stamp() + base::TimeDelta::FromMilliseconds(50));
+ root_window->AsRootWindowHostDelegate()->OnHostTouchEvent(&release);
+
+ ui::GestureConfiguration::set_default_radius(touch_default_radius);
+}
+
+// Test that only the left button will activate the maximize button.
+TEST_F(CustomFrameViewAshTest, OnlyLeftButtonMaximizes) {
+ views::Widget* widget = CreateWidget();
+ aura::Window* window = widget->GetNativeWindow();
+ CustomFrameViewAsh* frame = GetCustomFrameViewAsh(widget);
+ CustomFrameViewAsh::TestApi test(frame);
+ ash::FrameMaximizeButton* maximize_button = test.maximize_button();
+ maximize_button->set_bubble_appearance_delay_ms(0);
+ gfx::Point button_pos = maximize_button->GetBoundsInScreen().CenterPoint();
+ gfx::Point off_pos(button_pos.x() + 100, button_pos.y() + 100);
+
+ aura::test::EventGenerator generator(window->GetRootWindow(), off_pos);
+ EXPECT_FALSE(maximize_button->maximizer());
+ EXPECT_TRUE(ash::wm::IsWindowNormal(window));
+ EXPECT_FALSE(ash::wm::IsWindowMaximized(window));
+
+ // Move the mouse cursor over the button.
+ generator.MoveMouseTo(button_pos);
+ EXPECT_TRUE(maximize_button->maximizer());
+ EXPECT_FALSE(maximize_button->phantom_window_open());
+
+ // After pressing the left button the button should get triggered.
+ generator.PressLeftButton();
+ RunAllPendingInMessageLoop();
+ EXPECT_TRUE(maximize_button->is_snap_enabled());
+ EXPECT_FALSE(ash::wm::IsWindowMaximized(window));
+
+ // Pressing the right button then should cancel the operation.
+ generator.PressRightButton();
+ RunAllPendingInMessageLoop();
+ EXPECT_FALSE(maximize_button->maximizer());
+
+ // After releasing the second button the window shouldn't be maximized.
+ generator.ReleaseRightButton();
+ generator.ReleaseLeftButton();
+ RunAllPendingInMessageLoop();
+ EXPECT_FALSE(ash::wm::IsWindowMaximized(window));
+
+ // Second experiment: Starting with right should also not trigger.
+ generator.MoveMouseTo(off_pos);
+ generator.MoveMouseTo(button_pos);
+ EXPECT_TRUE(maximize_button->maximizer());
+
+ // Pressing first the right button should not activate.
+ generator.PressRightButton();
+ RunAllPendingInMessageLoop();
+ EXPECT_FALSE(maximize_button->is_snap_enabled());
+
+ // Pressing then additionally the left button shouldn't activate either.
+ generator.PressLeftButton();
+ RunAllPendingInMessageLoop();
+ EXPECT_FALSE(maximize_button->is_snap_enabled());
+ generator.ReleaseRightButton();
+ generator.ReleaseLeftButton();
+ EXPECT_FALSE(ash::wm::IsWindowMaximized(window));
+}
+
+// Click a button of window maximize functionality.
+// If |snap_type| is SNAP_NONE the FrameMaximizeButton gets clicked, otherwise
+// the associated snap button.
+// |Window| is the window which owns the maximize button.
+// |maximize_button| is the FrameMaximizeButton which controls the window.
+void ClickMaxButton(
+ ash::FrameMaximizeButton* maximize_button,
+ aura::Window* window,
+ SnapType snap_type) {
+ gfx::Point button_pos = maximize_button->GetBoundsInScreen().CenterPoint();
+ gfx::Point off_pos(button_pos.x() + 100, button_pos.y() + 100);
+
+ aura::test::EventGenerator generator(window->GetRootWindow(), off_pos);
+ generator.MoveMouseTo(off_pos);
+ EXPECT_FALSE(maximize_button->maximizer());
+ EXPECT_FALSE(maximize_button->phantom_window_open());
+
+ // Move the mouse cursor over the button.
+ generator.MoveMouseTo(button_pos);
+ EXPECT_TRUE(maximize_button->maximizer());
+ EXPECT_FALSE(maximize_button->phantom_window_open());
+
+ if (snap_type != SNAP_NONE) {
+ gfx::Point left_max_pos = maximize_button->maximizer()->
+ GetButtonForUnitTest(snap_type)->GetBoundsInScreen().CenterPoint();
+ generator.MoveMouseTo(left_max_pos);
+ EXPECT_TRUE(maximize_button->phantom_window_open());
+ }
+ // After pressing the left button the button should get triggered.
+ generator.ClickLeftButton();
+ EXPECT_FALSE(maximize_button->maximizer());
+}
+
+// Test that the restore from left/right maximize is properly done.
+TEST_F(CustomFrameViewAshTest, MaximizeLeftRestore) {
+ views::Widget* widget = CreateWidget();
+ aura::Window* window = widget->GetNativeWindow();
+ widget->SetBounds(gfx::Rect(10, 10, 100, 100));
+ gfx::Rect initial_bounds = widget->GetWindowBoundsInScreen();
+ CustomFrameViewAsh* frame = GetCustomFrameViewAsh(widget);
+ CustomFrameViewAsh::TestApi test(frame);
+ ash::FrameMaximizeButton* maximize_button = test.maximize_button();
+ maximize_button->set_bubble_appearance_delay_ms(0);
+
+ ClickMaxButton(maximize_button, window, SNAP_LEFT);
+ // The window should not be maximized.
+ EXPECT_FALSE(ash::wm::IsWindowMaximized(window));
+ // But the bounds should be different.
+ gfx::Rect new_bounds = widget->GetWindowBoundsInScreen();
+ EXPECT_EQ(0, new_bounds.x());
+ EXPECT_EQ(0, new_bounds.y());
+
+ // Now click the same button again to see that it restores.
+ ClickMaxButton(maximize_button, window, SNAP_LEFT);
+ // But the bounds should be restored.
+ new_bounds = widget->GetWindowBoundsInScreen();
+ EXPECT_EQ(new_bounds.x(), initial_bounds.x());
+ EXPECT_EQ(new_bounds.y(), initial_bounds.x());
+ EXPECT_EQ(new_bounds.width(), initial_bounds.width());
+ EXPECT_EQ(new_bounds.height(), initial_bounds.height());
+ // Make sure that there is no restore rectangle left.
+ EXPECT_EQ(NULL, GetRestoreBoundsInScreen(window));
+}
+
+// Maximize, left/right maximize and then restore should works.
+TEST_F(CustomFrameViewAshTest, MaximizeMaximizeLeftRestore) {
+ views::Widget* widget = CreateWidget();
+ aura::Window* window = widget->GetNativeWindow();
+ widget->SetBounds(gfx::Rect(10, 10, 100, 100));
+ gfx::Rect initial_bounds = widget->GetWindowBoundsInScreen();
+ CustomFrameViewAsh* frame = GetCustomFrameViewAsh(widget);
+ CustomFrameViewAsh::TestApi test(frame);
+ ash::FrameMaximizeButton* maximize_button = test.maximize_button();
+ maximize_button->set_bubble_appearance_delay_ms(0);
+
+ ClickMaxButton(maximize_button, window, SNAP_NONE);
+ EXPECT_TRUE(ash::wm::IsWindowMaximized(window));
+
+ ClickMaxButton(maximize_button, window, SNAP_LEFT);
+ EXPECT_FALSE(ash::wm::IsWindowMaximized(window));
+ gfx::Rect new_bounds = widget->GetWindowBoundsInScreen();
+ EXPECT_EQ(0, new_bounds.x());
+ EXPECT_EQ(0, new_bounds.y());
+
+ // Now click the same button again to see that it restores.
+ ClickMaxButton(maximize_button, window, SNAP_LEFT);
+ RunAllPendingInMessageLoop();
+ // But the bounds should be restored.
+ new_bounds = widget->GetWindowBoundsInScreen();
+ EXPECT_EQ(new_bounds.x(), initial_bounds.x());
+ EXPECT_EQ(new_bounds.y(), initial_bounds.x());
+ EXPECT_EQ(new_bounds.width(), initial_bounds.width());
+ EXPECT_EQ(new_bounds.height(), initial_bounds.height());
+ // Make sure that there is no restore rectangle left.
+ EXPECT_EQ(NULL, GetRestoreBoundsInScreen(window));
+}
+
+// Left/right maximize, maximize and then restore should work.
+TEST_F(CustomFrameViewAshTest, MaximizeLeftMaximizeRestore) {
+ views::Widget* widget = CreateWidget();
+ aura::Window* window = widget->GetNativeWindow();
+ widget->SetBounds(gfx::Rect(10, 10, 100, 100));
+ gfx::Rect initial_bounds = widget->GetWindowBoundsInScreen();
+ CustomFrameViewAsh* frame = GetCustomFrameViewAsh(widget);
+ CustomFrameViewAsh::TestApi test(frame);
+ ash::FrameMaximizeButton* maximize_button = test.maximize_button();
+ maximize_button->set_bubble_appearance_delay_ms(0);
+
+ ClickMaxButton(maximize_button, window, SNAP_LEFT);
+ EXPECT_FALSE(ash::wm::IsWindowMaximized(window));
+
+ ClickMaxButton(maximize_button, window, SNAP_NONE);
+ EXPECT_TRUE(ash::wm::IsWindowMaximized(window));
+
+ ClickMaxButton(maximize_button, window, SNAP_NONE);
+ EXPECT_FALSE(ash::wm::IsWindowMaximized(window));
+ gfx::Rect new_bounds = widget->GetWindowBoundsInScreen();
+ EXPECT_EQ(new_bounds.x(), initial_bounds.x());
+ EXPECT_EQ(new_bounds.y(), initial_bounds.x());
+ EXPECT_EQ(new_bounds.width(), initial_bounds.width());
+ EXPECT_EQ(new_bounds.height(), initial_bounds.height());
+ // Make sure that there is no restore rectangle left.
+ EXPECT_EQ(NULL, GetRestoreBoundsInScreen(window));
+}
+
+// Starting with a window which has no restore bounds, maximize then left/right
+// maximize should not be centered but left/right maximized.
+TEST_F(CustomFrameViewAshTest, MaximizeThenLeftMaximize) {
+ views::Widget* widget = CreateWidget();
+ aura::Window* window = widget->GetNativeWindow();
+ widget->SetBounds(gfx::Rect(10, 10, 100, 100));
+ gfx::Rect initial_bounds = widget->GetWindowBoundsInScreen();
+ CustomFrameViewAsh* frame = GetCustomFrameViewAsh(widget);
+ CustomFrameViewAsh::TestApi test(frame);
+ ash::FrameMaximizeButton* maximize_button = test.maximize_button();
+ maximize_button->set_bubble_appearance_delay_ms(0);
+ // Make sure that there is no restore rectangle.
+ EXPECT_EQ(NULL, GetRestoreBoundsInScreen(window));
+
+ ClickMaxButton(maximize_button, window, SNAP_NONE);
+ EXPECT_TRUE(ash::wm::IsWindowMaximized(window));
+
+ ClickMaxButton(maximize_button, window, SNAP_LEFT);
+ EXPECT_FALSE(ash::wm::IsWindowMaximized(window));
+
+ gfx::Rect new_bounds = widget->GetWindowBoundsInScreen();
+ EXPECT_EQ(new_bounds.x(), 0);
+ EXPECT_EQ(new_bounds.y(), 0);
+ // Make sure that the restore rectangle is the original rectangle.
+ EXPECT_EQ(initial_bounds.ToString(),
+ GetRestoreBoundsInScreen(window)->ToString());
+}
+
+// Test that minimizing the window per keyboard closes the maximize bubble.
+TEST_F(CustomFrameViewAshTest, MinimizePerKeyClosesBubble) {
+ views::Widget* widget = CreateWidget();
+ aura::Window* window = widget->GetNativeWindow();
+ widget->SetBounds(gfx::Rect(10, 10, 100, 100));
+ CustomFrameViewAsh* frame = GetCustomFrameViewAsh(widget);
+ CustomFrameViewAsh::TestApi test(frame);
+ ash::FrameMaximizeButton* maximize_button = test.maximize_button();
+
+ gfx::Point button_pos = maximize_button->GetBoundsInScreen().CenterPoint();
+ gfx::Point off_pos(button_pos.x() + 100, button_pos.y() + 100);
+
+ aura::test::EventGenerator generator(window->GetRootWindow(), off_pos);
+ generator.MoveMouseTo(off_pos);
+ EXPECT_FALSE(maximize_button->maximizer());
+
+ // Move the mouse cursor over the maximize button.
+ generator.MoveMouseTo(button_pos);
+ EXPECT_TRUE(maximize_button->maximizer());
+
+ // We simulate the keystroke by calling minimizeWindow directly.
+ wm::MinimizeWindow(window);
+
+ EXPECT_TRUE(ash::wm::IsWindowMinimized(window));
+ EXPECT_FALSE(maximize_button->maximizer());
+}
+
+// Tests that dragging down on the maximize button minimizes the window.
+TEST_F(CustomFrameViewAshTest, MaximizeButtonDragDownMinimizes) {
+ views::Widget* widget = CreateWidget();
+ aura::Window* window = widget->GetNativeWindow();
+ widget->SetBounds(gfx::Rect(10, 10, 100, 100));
+ CustomFrameViewAsh* frame = GetCustomFrameViewAsh(widget);
+ CustomFrameViewAsh::TestApi test(frame);
+ FrameMaximizeButton* maximize_button = test.maximize_button();
+
+ // Drag down on a maximized window.
+ wm::MaximizeWindow(window);
+ EXPECT_TRUE(wm::IsWindowMaximized(window));
+ gfx::Point button_pos = maximize_button->GetBoundsInScreen().CenterPoint();
+ gfx::Point off_pos(button_pos.x(), button_pos.y() + 100);
+
+ aura::test::EventGenerator generator(window->GetRootWindow());
+ generator.GestureScrollSequence(button_pos, off_pos,
+ base::TimeDelta::FromMilliseconds(0), 1);
+
+ EXPECT_TRUE(wm::IsWindowMinimized(window));
+ EXPECT_FALSE(maximize_button->maximizer());
+
+ // Drag down on a restored window.
+ wm::RestoreWindow(window);
+
+ button_pos = maximize_button->GetBoundsInScreen().CenterPoint();
+ off_pos = gfx::Point(button_pos.x(), button_pos.y() + 200);
+ generator.GestureScrollSequence(button_pos, off_pos,
+ base::TimeDelta::FromMilliseconds(10), 1);
+ EXPECT_TRUE(wm::IsWindowMinimized(window));
+ EXPECT_FALSE(maximize_button->maximizer());
+}
+
+// Tests that dragging Left and pressing ESC does properly abort.
+TEST_F(CustomFrameViewAshTest, MaximizeButtonDragLeftEscapeExits) {
+ views::Widget* widget = CreateWidget();
+ aura::Window* window = widget->GetNativeWindow();
+ gfx::Rect window_position = gfx::Rect(10, 10, 100, 100);
+ widget->SetBounds(window_position);
+ CustomFrameViewAsh* frame = GetCustomFrameViewAsh(widget);
+ CustomFrameViewAsh::TestApi test(frame);
+ FrameMaximizeButton* maximize_button = test.maximize_button();
+
+ gfx::Point button_pos = maximize_button->GetBoundsInScreen().CenterPoint();
+ gfx::Point off_pos(button_pos.x() - button_pos.x() / 2, button_pos.y());
+
+ const int kGestureSteps = 10;
+ CancelCallbackHandler cancel_handler(kGestureSteps / 2, maximize_button);
+ aura::test::EventGenerator generator(window->GetRootWindow());
+ generator.GestureScrollSequenceWithCallback(
+ button_pos,
+ off_pos,
+ base::TimeDelta::FromMilliseconds(0),
+ kGestureSteps,
+ base::Bind(&CancelCallbackHandler::CountedCancelCallback,
+ base::Unretained(&cancel_handler)));
+
+ // Check that there was no size change.
+ EXPECT_EQ(widget->GetWindowBoundsInScreen().size().ToString(),
+ window_position.size().ToString());
+ // Check that there is no phantom window left open.
+ EXPECT_FALSE(maximize_button->phantom_window_open());
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/wm/default_window_resizer.cc b/chromium/ash/wm/default_window_resizer.cc
new file mode 100644
index 00000000000..a7b54e58d84
--- /dev/null
+++ b/chromium/ash/wm/default_window_resizer.cc
@@ -0,0 +1,71 @@
+// Copyright (c) 2012 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.
+
+#include "ash/wm/default_window_resizer.h"
+
+#include "ash/shell.h"
+#include "ash/wm/property_util.h"
+#include "ui/aura/client/aura_constants.h"
+#include "ui/aura/env.h"
+#include "ui/aura/window.h"
+#include "ui/aura/window_delegate.h"
+#include "ui/base/hit_test.h"
+#include "ui/base/ui_base_types.h"
+#include "ui/gfx/screen.h"
+
+namespace ash {
+
+DefaultWindowResizer::~DefaultWindowResizer() {
+ ash::Shell::GetInstance()->cursor_manager()->UnlockCursor();
+}
+
+// static
+DefaultWindowResizer*
+DefaultWindowResizer::Create(aura::Window* window,
+ const gfx::Point& location,
+ int window_component,
+ aura::client::WindowMoveSource source) {
+ Details details(window, location, window_component, source);
+ return details.is_resizable ? new DefaultWindowResizer(details) : NULL;
+}
+
+void DefaultWindowResizer::Drag(const gfx::Point& location, int event_flags) {
+ gfx::Rect bounds(CalculateBoundsForDrag(details_, location));
+ if (bounds != details_.window->bounds()) {
+ if (!did_move_or_resize_ && !details_.restore_bounds.IsEmpty())
+ ClearRestoreBounds(details_.window);
+ did_move_or_resize_ = true;
+ details_.window->SetBounds(bounds);
+ }
+}
+
+void DefaultWindowResizer::CompleteDrag(int event_flags) {
+}
+
+void DefaultWindowResizer::RevertDrag() {
+ if (!did_move_or_resize_)
+ return;
+
+ details_.window->SetBounds(details_.initial_bounds_in_parent);
+
+ if (!details_.restore_bounds.IsEmpty())
+ SetRestoreBoundsInScreen(details_.window, details_.restore_bounds);
+}
+
+aura::Window* DefaultWindowResizer::GetTarget() {
+ return details_.window;
+}
+
+const gfx::Point& DefaultWindowResizer::GetInitialLocation() const {
+ return details_.initial_location_in_parent;
+}
+
+DefaultWindowResizer::DefaultWindowResizer(const Details& details)
+ : details_(details),
+ did_move_or_resize_(false) {
+ DCHECK(details_.is_resizable);
+ ash::Shell::GetInstance()->cursor_manager()->LockCursor();
+}
+
+} // namespace aura
diff --git a/chromium/ash/wm/default_window_resizer.h b/chromium/ash/wm/default_window_resizer.h
new file mode 100644
index 00000000000..621acd39f3b
--- /dev/null
+++ b/chromium/ash/wm/default_window_resizer.h
@@ -0,0 +1,55 @@
+// Copyright (c) 2012 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 ASH_WM_DEFAULT_WINDOW_RESIZER_H_
+#define ASH_WM_DEFAULT_WINDOW_RESIZER_H_
+
+#include "ash/wm/window_resizer.h"
+#include "base/compiler_specific.h"
+
+namespace ash {
+
+// WindowResizer is used by ToplevelWindowEventFilter to handle dragging, moving
+// or resizing a window. All coordinates passed to this are in the parent
+// windows coordiantes.
+class ASH_EXPORT DefaultWindowResizer : public WindowResizer {
+ public:
+ virtual ~DefaultWindowResizer();
+
+ // Creates a new DefaultWindowResizer. The caller takes ownership of the
+ // returned object. Returns NULL if not resizable.
+ static DefaultWindowResizer* Create(aura::Window* window,
+ const gfx::Point& location,
+ int window_component,
+ aura::client::WindowMoveSource source);
+
+ // Returns true if the drag will result in changing the window in anyway.
+ bool is_resizable() const { return details_.is_resizable; }
+
+ bool changed_size() const {
+ return !(details_.bounds_change & kBoundsChange_Repositions);
+ }
+ aura::Window* target_window() const { return details_.window; }
+
+ // WindowResizer:
+ virtual void Drag(const gfx::Point& location, int event_flags) OVERRIDE;
+ virtual void CompleteDrag(int event_flags) OVERRIDE;
+ virtual void RevertDrag() OVERRIDE;
+ virtual aura::Window* GetTarget() OVERRIDE;
+ virtual const gfx::Point& GetInitialLocation() const OVERRIDE;
+
+ private:
+ explicit DefaultWindowResizer(const Details& details);
+
+ const Details details_;
+
+ // Set to true once Drag() is invoked and the bounds of the window change.
+ bool did_move_or_resize_;
+
+ DISALLOW_COPY_AND_ASSIGN(DefaultWindowResizer);
+};
+
+} // namespace aura
+
+#endif // ASH_WM_DEFAULT_WINDOW_RESIZER_H_
diff --git a/chromium/ash/wm/dock/dock_types.h b/chromium/ash/wm/dock/dock_types.h
new file mode 100644
index 00000000000..1cf31848d5a
--- /dev/null
+++ b/chromium/ash/wm/dock/dock_types.h
@@ -0,0 +1,30 @@
+// Copyright (c) 2013 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 ASH_WM_DOCK_DOCK_TYPES_H_
+#define ASH_WM_DOCK_DOCK_TYPES_H_
+
+namespace ash {
+
+namespace internal {
+
+// Possible values of which side of the screen the windows are docked at.
+// This is used by DockedwindowLayoutManager and DockedWindowResizer to
+// implement docking behavior including magnetism while dragging windows into
+// or out of the docked windows area.
+enum DockedAlignment {
+ // No docked windows.
+ DOCKED_ALIGNMENT_NONE,
+
+ // Some windows are already docked on the left side of the screen.
+ DOCKED_ALIGNMENT_LEFT,
+
+ // Some windows are already docked on the right side of the screen.
+ DOCKED_ALIGNMENT_RIGHT,
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_WM_DOCK_DOCK_TYPES_H_
diff --git a/chromium/ash/wm/dock/docked_window_layout_manager.cc b/chromium/ash/wm/dock/docked_window_layout_manager.cc
new file mode 100644
index 00000000000..1dacf5e9fbd
--- /dev/null
+++ b/chromium/ash/wm/dock/docked_window_layout_manager.cc
@@ -0,0 +1,694 @@
+// Copyright (c) 2013 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.
+
+#include "ash/wm/dock/docked_window_layout_manager.h"
+
+#include "ash/launcher/launcher.h"
+#include "ash/screen_ash.h"
+#include "ash/shelf/shelf_layout_manager.h"
+#include "ash/shelf/shelf_types.h"
+#include "ash/shelf/shelf_widget.h"
+#include "ash/shell.h"
+#include "ash/shell_window_ids.h"
+#include "ash/wm/coordinate_conversion.h"
+#include "ash/wm/window_properties.h"
+#include "ash/wm/window_util.h"
+#include "base/auto_reset.h"
+#include "third_party/skia/include/core/SkColor.h"
+#include "ui/aura/client/activation_client.h"
+#include "ui/aura/client/aura_constants.h"
+#include "ui/aura/focus_manager.h"
+#include "ui/aura/root_window.h"
+#include "ui/aura/window.h"
+#include "ui/gfx/rect.h"
+
+namespace ash {
+namespace internal {
+
+// Minimum, maximum width of the dock area and a width of the gap
+const int DockedWindowLayoutManager::kMinDockWidth = 200;
+const int DockedWindowLayoutManager::kMaxDockWidth = 450;
+const int DockedWindowLayoutManager::kMinDockGap = 2;
+const int kWindowIdealSpacing = 4;
+
+namespace {
+
+const SkColor kDockBackgroundColor = SkColorSetARGB(0xff, 0x10, 0x10, 0x10);
+const float kDockBackgroundOpacity = 0.5f;
+
+class DockedBackgroundWidget : public views::Widget {
+ public:
+ explicit DockedBackgroundWidget(aura::Window* container) {
+ InitWidget(container);
+ }
+
+ private:
+ void InitWidget(aura::Window* parent) {
+ views::Widget::InitParams params;
+ params.type = views::Widget::InitParams::TYPE_POPUP;
+ params.opacity = views::Widget::InitParams::OPAQUE_WINDOW;
+ params.can_activate = false;
+ params.keep_on_top = false;
+ params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
+ params.parent = parent;
+ params.accept_events = false;
+ set_focus_on_creation(false);
+ Init(params);
+ DCHECK_EQ(GetNativeView()->GetRootWindow(), parent->GetRootWindow());
+ views::View* content_view = new views::View;
+ content_view->set_background(
+ views::Background::CreateSolidBackground(kDockBackgroundColor));
+ SetContentsView(content_view);
+ GetNativeWindow()->layer()->SetOpacity(kDockBackgroundOpacity);
+ Hide();
+ }
+
+ DISALLOW_COPY_AND_ASSIGN(DockedBackgroundWidget);
+};
+
+DockedWindowLayoutManager* GetDockLayoutManager(aura::Window* window,
+ const gfx::Point& location) {
+ gfx::Rect near_location(location, gfx::Size());
+ aura::Window* dock = Shell::GetContainer(
+ wm::GetRootWindowMatching(near_location),
+ kShellWindowId_DockedContainer);
+ return static_cast<internal::DockedWindowLayoutManager*>(
+ dock->layout_manager());
+}
+
+// Certain windows (minimized, hidden or popups) do not matter to docking.
+bool IsUsedByLayout(aura::Window* window) {
+ return (window->IsVisible() &&
+ !wm::IsWindowMinimized(window) &&
+ window->type() != aura::client::WINDOW_TYPE_POPUP);
+}
+
+// A functor used to sort the windows in order of their center Y position.
+// |delta| is a pre-calculated distance from the bottom of one window to the top
+// of the next. Its value can be positive (gap) or negative (overlap).
+// Half of |delta| is used as a transition point at which windows could ideally
+// swap positions.
+struct CompareWindowPos {
+ CompareWindowPos(aura::Window* dragged_window, float delta)
+ : dragged_window_(dragged_window),
+ delta_(delta / 2) {}
+
+ bool operator()(const aura::Window* win1, const aura::Window* win2) {
+ const gfx::Rect win1_bounds = win1->GetBoundsInScreen();
+ const gfx::Rect win2_bounds = win2->GetBoundsInScreen();
+ // If one of the windows is the |dragged_window_| attempt to make an
+ // earlier swap between the windows than just based on their centers.
+ // This is possible if the dragged window is at least as tall as the other
+ // window.
+ if (win1 == dragged_window_)
+ return compare_two_windows(win1_bounds, win2_bounds);
+ if (win2 == dragged_window_)
+ return !compare_two_windows(win2_bounds, win1_bounds);
+ // Otherwise just compare the centers.
+ return win1_bounds.CenterPoint().y() < win2_bounds.CenterPoint().y();
+ }
+
+ // Based on center point tries to deduce where the drag is coming from.
+ // When dragging from below up the transition point is lower.
+ // When dragging from above down the transition point is higher.
+ bool compare_bounds(const gfx::Rect dragged, const gfx::Rect other) {
+ if (dragged.CenterPoint().y() < other.CenterPoint().y())
+ return dragged.CenterPoint().y() < other.y() - delta_;
+ return dragged.CenterPoint().y() < other.bottom() + delta_;
+ }
+
+ // Performs comparison both ways and selects stable result.
+ bool compare_two_windows(const gfx::Rect bounds1, const gfx::Rect bounds2) {
+ // Try comparing windows in both possible orders and see if the comparison
+ // is stable.
+ bool result1 = compare_bounds(bounds1, bounds2);
+ bool result2 = compare_bounds(bounds2, bounds1);
+ if (result1 != result2)
+ return result1;
+
+ // Otherwise it is not possible to be sure that the windows will not bounce.
+ // In this case just compare the centers.
+ return bounds1.CenterPoint().y() < bounds2.CenterPoint().y();
+ }
+
+ private:
+ aura::Window* dragged_window_;
+ float delta_;
+};
+
+} // namespace
+
+////////////////////////////////////////////////////////////////////////////////
+// DockLayoutManager public implementation:
+DockedWindowLayoutManager::DockedWindowLayoutManager(
+ aura::Window* dock_container)
+ : dock_container_(dock_container),
+ in_layout_(false),
+ dragged_window_(NULL),
+ is_dragged_window_docked_(false),
+ is_dragged_from_dock_(false),
+ launcher_(NULL),
+ shelf_layout_manager_(NULL),
+ shelf_hidden_(false),
+ docked_width_(0),
+ alignment_(DOCKED_ALIGNMENT_NONE),
+ last_active_window_(NULL),
+ background_widget_(new DockedBackgroundWidget(dock_container_)) {
+ DCHECK(dock_container);
+ aura::client::GetActivationClient(Shell::GetPrimaryRootWindow())->
+ AddObserver(this);
+ Shell::GetInstance()->AddShellObserver(this);
+}
+
+DockedWindowLayoutManager::~DockedWindowLayoutManager() {
+ Shutdown();
+}
+
+void DockedWindowLayoutManager::Shutdown() {
+ if (shelf_layout_manager_) {
+ shelf_layout_manager_->RemoveObserver(this);
+ }
+ shelf_layout_manager_ = NULL;
+ launcher_ = NULL;
+ for (size_t i = 0; i < dock_container_->children().size(); ++i)
+ dock_container_->children()[i]->RemoveObserver(this);
+ aura::client::GetActivationClient(Shell::GetPrimaryRootWindow())->
+ RemoveObserver(this);
+ Shell::GetInstance()->RemoveShellObserver(this);
+}
+
+void DockedWindowLayoutManager::AddObserver(
+ DockedWindowLayoutManagerObserver* observer) {
+ observer_list_.AddObserver(observer);
+}
+
+void DockedWindowLayoutManager::RemoveObserver(
+ DockedWindowLayoutManagerObserver* observer) {
+ observer_list_.RemoveObserver(observer);
+}
+
+void DockedWindowLayoutManager::StartDragging(aura::Window* window) {
+ DCHECK(!dragged_window_);
+ dragged_window_ = window;
+ // Start observing a window unless it is docked container's child in which
+ // case it is already observed.
+ if (dragged_window_->parent() != dock_container_)
+ dragged_window_->AddObserver(this);
+ is_dragged_from_dock_ = window->parent() == dock_container_;
+ DCHECK(!is_dragged_window_docked_);
+}
+
+void DockedWindowLayoutManager::DockDraggedWindow(aura::Window* window) {
+ OnWindowDocked(window);
+ Relayout();
+}
+
+void DockedWindowLayoutManager::UndockDraggedWindow() {
+ OnWindowUndocked();
+ Relayout();
+ is_dragged_from_dock_ = false;
+}
+
+void DockedWindowLayoutManager::FinishDragging() {
+ DCHECK(dragged_window_);
+ if (is_dragged_window_docked_)
+ OnWindowUndocked();
+ DCHECK (!is_dragged_window_docked_);
+ // Stop observing a window unless it is docked container's child in which
+ // case it needs to keep being observed after the drag completes.
+ if (dragged_window_->parent() != dock_container_)
+ dragged_window_->RemoveObserver(this);
+ dragged_window_ = NULL;
+ Relayout();
+ UpdateDockBounds();
+}
+
+void DockedWindowLayoutManager::SetLauncher(ash::Launcher* launcher) {
+ DCHECK(!launcher_);
+ DCHECK(!shelf_layout_manager_);
+ launcher_ = launcher;
+ if (launcher_->shelf_widget()) {
+ shelf_layout_manager_ = ash::internal::ShelfLayoutManager::ForLauncher(
+ launcher_->shelf_widget()->GetNativeWindow());
+ WillChangeVisibilityState(shelf_layout_manager_->visibility_state());
+ shelf_layout_manager_->AddObserver(this);
+ }
+}
+
+DockedAlignment DockedWindowLayoutManager::GetAlignmentOfWindow(
+ const aura::Window* window) const {
+ const gfx::Rect& bounds(window->GetBoundsInScreen());
+ const gfx::Rect docked_bounds = dock_container_->GetBoundsInScreen();
+
+ // Do not allow docking if a window is vertically maximized (as is the case
+ // when it is snapped).
+ const gfx::Rect work_area =
+ Shell::GetScreen()->GetDisplayNearestWindow(dock_container_).work_area();
+ if (bounds.y() == work_area.y() && bounds.height() == work_area.height())
+ return DOCKED_ALIGNMENT_NONE;
+
+ // Do not allow docking on the same side as launcher shelf.
+ ShelfAlignment shelf_alignment = SHELF_ALIGNMENT_BOTTOM;
+ if (launcher_)
+ shelf_alignment = launcher_->alignment();
+
+ if (bounds.x() == docked_bounds.x() &&
+ shelf_alignment != SHELF_ALIGNMENT_LEFT) {
+ return DOCKED_ALIGNMENT_LEFT;
+ }
+ if (bounds.right() == docked_bounds.right() &&
+ shelf_alignment != SHELF_ALIGNMENT_RIGHT) {
+ return DOCKED_ALIGNMENT_RIGHT;
+ }
+ return DOCKED_ALIGNMENT_NONE;
+}
+
+DockedAlignment DockedWindowLayoutManager::CalculateAlignment() const {
+ // Find a child that is not being dragged and is not a popup.
+ // If such exists the current alignment is returned - even if some of the
+ // children are hidden or minimized (so they can be restored without losing
+ // the docked state).
+ for (size_t i = 0; i < dock_container_->children().size(); ++i) {
+ aura::Window* window(dock_container_->children()[i]);
+ if (window != dragged_window_ &&
+ window->type() != aura::client::WINDOW_TYPE_POPUP) {
+ return alignment_;
+ }
+ }
+ // No docked windows remain other than possibly the window being dragged.
+ // Return |NONE| to indicate that windows may get docked on either side.
+ return DOCKED_ALIGNMENT_NONE;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// DockLayoutManager, aura::LayoutManager implementation:
+void DockedWindowLayoutManager::OnWindowResized() {
+ Relayout();
+ // When screen resizes update the insets even when dock width or alignment
+ // does not change.
+ UpdateDockBounds();
+}
+
+void DockedWindowLayoutManager::OnWindowAddedToLayout(aura::Window* child) {
+ if (child->type() == aura::client::WINDOW_TYPE_POPUP)
+ return;
+ // Dragged windows are already observed by StartDragging and do not change
+ // docked alignment during the drag.
+ if (child == dragged_window_)
+ return;
+ // If this is the first window getting docked - update alignment.
+ if (alignment_ == DOCKED_ALIGNMENT_NONE) {
+ alignment_ = GetAlignmentOfWindow(child);
+ DCHECK(alignment_ != DOCKED_ALIGNMENT_NONE);
+ }
+ child->AddObserver(this);
+ Relayout();
+ UpdateDockBounds();
+}
+
+void DockedWindowLayoutManager::OnWindowRemovedFromLayout(aura::Window* child) {
+ if (child->type() == aura::client::WINDOW_TYPE_POPUP)
+ return;
+ // Dragged windows are stopped being observed by FinishDragging and do not
+ // change alignment during the drag. They also cannot be set to be the
+ // |last_active_window_|.
+ if (child == dragged_window_)
+ return;
+ // If this is the last window, set alignment and maximize the workspace.
+ if (!IsAnyWindowDocked()) {
+ alignment_ = DOCKED_ALIGNMENT_NONE;
+ docked_width_ = 0;
+ }
+ if (last_active_window_ == child)
+ last_active_window_ = NULL;
+ child->RemoveObserver(this);
+ Relayout();
+}
+
+void DockedWindowLayoutManager::OnChildWindowVisibilityChanged(
+ aura::Window* child,
+ bool visible) {
+ if (child->type() == aura::client::WINDOW_TYPE_POPUP)
+ return;
+ if (visible)
+ child->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_NORMAL);
+ Relayout();
+ UpdateDockBounds();
+}
+
+void DockedWindowLayoutManager::SetChildBounds(
+ aura::Window* child,
+ const gfx::Rect& requested_bounds) {
+ // Whenever one of our windows is moved or resized enforce layout.
+ SetChildBoundsDirect(child, requested_bounds);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// DockLayoutManager, ash::ShellObserver implementation:
+
+void DockedWindowLayoutManager::OnShelfAlignmentChanged(
+ aura::RootWindow* root_window) {
+ if (dock_container_->GetRootWindow() != root_window)
+ return;
+
+ if (!launcher_ || !launcher_->shelf_widget())
+ return;
+
+ if (alignment_ == DOCKED_ALIGNMENT_NONE)
+ return;
+
+ // Do not allow launcher and dock on the same side. Switch side that
+ // the dock is attached to and move all dock windows to that new side.
+ ShelfAlignment shelf_alignment = launcher_->shelf_widget()->GetAlignment();
+ if (alignment_ == DOCKED_ALIGNMENT_LEFT &&
+ shelf_alignment == SHELF_ALIGNMENT_LEFT) {
+ alignment_ = DOCKED_ALIGNMENT_RIGHT;
+ } else if (alignment_ == DOCKED_ALIGNMENT_RIGHT &&
+ shelf_alignment == SHELF_ALIGNMENT_RIGHT) {
+ alignment_ = DOCKED_ALIGNMENT_LEFT;
+ }
+ Relayout();
+ UpdateDockBounds();
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// DockLayoutManager, WindowObserver implementation:
+
+void DockedWindowLayoutManager::OnWindowPropertyChanged(aura::Window* window,
+ const void* key,
+ intptr_t old) {
+ if (key != aura::client::kShowStateKey)
+ return;
+ // The window property will still be set, but no actual change will occur
+ // until WillChangeVisibilityState is called when the shelf is visible again
+ if (shelf_hidden_)
+ return;
+ if (wm::IsWindowMinimized(window))
+ MinimizeWindow(window);
+ else
+ RestoreWindow(window);
+}
+
+void DockedWindowLayoutManager::OnWindowBoundsChanged(
+ aura::Window* window,
+ const gfx::Rect& old_bounds,
+ const gfx::Rect& new_bounds) {
+ // Only relayout if the dragged window would get docked.
+ if (window == dragged_window_ && is_dragged_window_docked_)
+ Relayout();
+}
+
+void DockedWindowLayoutManager::OnWindowDestroying(aura::Window* window) {
+ if (dragged_window_ == window)
+ FinishDragging();
+ DCHECK(!dragged_window_);
+ DCHECK (!is_dragged_window_docked_);
+ if (window == last_active_window_)
+ last_active_window_ = NULL;
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+// DockLayoutManager, aura::client::ActivationChangeObserver implementation:
+
+void DockedWindowLayoutManager::OnWindowActivated(aura::Window* gained_active,
+ aura::Window* lost_active) {
+ // Ignore if the window that is not managed by this was activated.
+ aura::Window* ancestor = NULL;
+ for (aura::Window* parent = gained_active;
+ parent; parent = parent->parent()) {
+ if (parent->parent() == dock_container_) {
+ ancestor = parent;
+ break;
+ }
+ }
+ if (ancestor)
+ UpdateStacking(ancestor);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// DockLayoutManager, ShelfLayoutManagerObserver implementation:
+
+void DockedWindowLayoutManager::WillChangeVisibilityState(
+ ShelfVisibilityState new_state) {
+ // On entering / leaving full screen mode the shelf visibility state is
+ // changed to / from SHELF_HIDDEN. In this state, docked windows should hide
+ // to allow the full-screen application to use the full screen.
+
+ // TODO(varkha): ShelfLayoutManager::UpdateVisibilityState sets state to
+ // SHELF_AUTO_HIDE when in immersive mode. Distinguish this from
+ // when shelf enters auto-hide state based on mouse hover when auto-hide
+ // setting is enabled and hide all windows (immersive mode should hide docked
+ // windows).
+ shelf_hidden_ = new_state == ash::SHELF_HIDDEN;
+ {
+ // prevent Relayout from getting called multiple times during this
+ base::AutoReset<bool> auto_reset_in_layout(&in_layout_, true);
+ for (size_t i = 0; i < dock_container_->children().size(); ++i) {
+ aura::Window* window = dock_container_->children()[i];
+ if (shelf_hidden_) {
+ if (window->IsVisible())
+ MinimizeWindow(window);
+ } else {
+ if (!wm::IsWindowMinimized(window))
+ RestoreWindow(window);
+ }
+ }
+ }
+ Relayout();
+ UpdateDockBounds();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// DockLayoutManager private implementation:
+
+void DockedWindowLayoutManager::MinimizeWindow(aura::Window* window) {
+ window->Hide();
+ if (wm::IsActiveWindow(window))
+ wm::DeactivateWindow(window);
+}
+
+void DockedWindowLayoutManager::RestoreWindow(aura::Window* window) {
+ window->Show();
+}
+
+void DockedWindowLayoutManager::OnWindowDocked(aura::Window* window) {
+ DCHECK(!is_dragged_window_docked_);
+ is_dragged_window_docked_ = true;
+
+ // If there are no other docked windows update alignment.
+ if (!IsAnyWindowDocked())
+ alignment_ = DOCKED_ALIGNMENT_NONE;
+}
+
+void DockedWindowLayoutManager::OnWindowUndocked() {
+ // If this is the first window getting docked - update alignment.
+ if (!IsAnyWindowDocked())
+ alignment_ = GetAlignmentOfWindow(dragged_window_);
+
+ DCHECK (is_dragged_window_docked_);
+ is_dragged_window_docked_ = false;
+}
+
+bool DockedWindowLayoutManager::IsAnyWindowDocked() {
+ return CalculateAlignment() != DOCKED_ALIGNMENT_NONE;
+}
+
+void DockedWindowLayoutManager::Relayout() {
+ if (in_layout_)
+ return;
+ if (alignment_ == DOCKED_ALIGNMENT_NONE && !is_dragged_window_docked_)
+ return;
+ base::AutoReset<bool> auto_reset_in_layout(&in_layout_, true);
+
+ gfx::Rect dock_bounds = dock_container_->GetBoundsInScreen();
+ aura::Window* active_window = NULL;
+ aura::Window::Windows visible_windows;
+ docked_width_ = 0;
+ for (size_t i = 0; i < dock_container_->children().size(); ++i) {
+ aura::Window* window(dock_container_->children()[i]);
+
+ if (!IsUsedByLayout(window) || window == dragged_window_)
+ continue;
+
+ // If the shelf is currently hidden (full-screen mode), hide window until
+ // full-screen mode is exited.
+ if (shelf_hidden_) {
+ // The call to Hide does not set the minimize property, so the window will
+ // be restored when the shelf becomes visible again.
+ window->Hide();
+ continue;
+ }
+ if (window->HasFocus() ||
+ window->Contains(
+ aura::client::GetFocusClient(window)->GetFocusedWindow())) {
+ DCHECK(!active_window);
+ active_window = window;
+ }
+ visible_windows.push_back(window);
+ }
+
+ // Consider docked dragged_window_ when fanning out other child windows.
+ if (is_dragged_window_docked_) {
+ visible_windows.push_back(dragged_window_);
+ DCHECK(!active_window);
+ active_window = dragged_window_;
+ }
+
+ // Calculate free space or overlap.
+ gfx::Display display = Shell::GetScreen()->GetDisplayNearestWindow(
+ dock_container_);
+ const gfx::Rect work_area = display.work_area();
+ int available_room = work_area.height();
+ for (aura::Window::Windows::const_iterator iter = visible_windows.begin();
+ iter != visible_windows.end(); ++iter) {
+ available_room -= (*iter)->bounds().height();
+ }
+ const int num_windows = visible_windows.size();
+ const float delta = (float)available_room /
+ ((available_room > 0 || num_windows <= 1) ?
+ num_windows + 1 : num_windows - 1);
+ float y_pos = work_area.y() + ((available_room > 0) ? delta : 0);
+
+ // Sort windows by their center positions and fan out overlapping
+ // windows.
+ std::sort(visible_windows.begin(),
+ visible_windows.end(),
+ CompareWindowPos(is_dragged_from_dock_ ? dragged_window_ : NULL,
+ delta));
+ is_dragged_from_dock_ = true;
+ for (aura::Window::Windows::const_iterator iter = visible_windows.begin();
+ iter != visible_windows.end(); ++iter) {
+ aura::Window* window = *iter;
+ gfx::Rect bounds = window->GetBoundsInScreen();
+
+ // Fan out windows evenly distributing the overlap or remaining free space.
+ bounds.set_y(std::max(work_area.y(),
+ std::min(work_area.bottom() - bounds.height(),
+ static_cast<int>(y_pos + 0.5))));
+ y_pos += bounds.height() + delta;
+
+ // All docked windows other than the one currently dragged remain stuck
+ // to the screen edge.
+ switch (alignment_) {
+ case DOCKED_ALIGNMENT_LEFT:
+ bounds.set_x(dock_bounds.x());
+ break;
+ case DOCKED_ALIGNMENT_RIGHT:
+ bounds.set_x(dock_bounds.right() - bounds.width());
+ break;
+ case DOCKED_ALIGNMENT_NONE:
+ break;
+ }
+ if (window == dragged_window_) {
+ dragged_bounds_ = bounds;
+ continue;
+ }
+ DCHECK_NE(alignment_, DOCKED_ALIGNMENT_NONE);
+ // Keep the dock at least kMinDockWidth when all windows in it overhang.
+ docked_width_ = std::min(kMaxDockWidth,
+ std::max(docked_width_,
+ bounds.width() > kMaxDockWidth ?
+ kMinDockWidth : bounds.width()));
+ bounds = ScreenAsh::ConvertRectFromScreen(dock_container_, bounds);
+ SetChildBoundsDirect(window, bounds);
+ }
+ UpdateStacking(active_window);
+}
+
+void DockedWindowLayoutManager::UpdateDockBounds() {
+ int dock_inset = docked_width_ + (docked_width_ > 0 ? kMinDockGap : 0);
+ gfx::Rect bounds = gfx::Rect(
+ alignment_ == DOCKED_ALIGNMENT_RIGHT && dock_inset > 0 ?
+ dock_container_->bounds().right() - dock_inset:
+ dock_container_->bounds().x(),
+ dock_container_->bounds().y(),
+ dock_inset,
+ dock_container_->bounds().height());
+ docked_bounds_ = bounds +
+ dock_container_->GetBoundsInScreen().OffsetFromOrigin();
+ FOR_EACH_OBSERVER(
+ DockedWindowLayoutManagerObserver,
+ observer_list_,
+ OnDockBoundsChanging(bounds));
+
+ // Show or hide background for docked area.
+ gfx::Rect background_bounds(docked_bounds_);
+ const gfx::Rect work_area =
+ Shell::GetScreen()->GetDisplayNearestWindow(dock_container_).work_area();
+ background_bounds.set_height(work_area.height());
+ background_widget_->SetBounds(background_bounds);
+ if (docked_width_ > 0)
+ background_widget_->Show();
+ else
+ background_widget_->Hide();
+}
+
+void DockedWindowLayoutManager::UpdateStacking(aura::Window* active_window) {
+ if (!active_window) {
+ if (!last_active_window_)
+ return;
+ active_window = last_active_window_;
+ }
+
+ // Windows are stacked like a deck of cards:
+ // ,------.
+ // |,------.|
+ // |,------.|
+ // | active |
+ // | window |
+ // |`------'|
+ // |`------'|
+ // `------'
+ // Use the middle of each window to figure out how to stack the window.
+ // This allows us to update the stacking when a window is being dragged around
+ // by the titlebar.
+ std::map<int, aura::Window*> window_ordering;
+ for (aura::Window::Windows::const_iterator it =
+ dock_container_->children().begin();
+ it != dock_container_->children().end(); ++it) {
+ if (!IsUsedByLayout(*it))
+ continue;
+ gfx::Rect bounds = (*it)->bounds();
+ window_ordering.insert(std::make_pair(bounds.y() + bounds.height() / 2,
+ *it));
+ }
+ int active_center_y = active_window->bounds().CenterPoint().y();
+
+ aura::Window* previous_window = NULL;
+ for (std::map<int, aura::Window*>::const_iterator it =
+ window_ordering.begin();
+ it != window_ordering.end() && it->first < active_center_y; ++it) {
+ if (previous_window)
+ dock_container_->StackChildAbove(it->second, previous_window);
+ previous_window = it->second;
+ }
+
+ previous_window = NULL;
+ for (std::map<int, aura::Window*>::const_reverse_iterator it =
+ window_ordering.rbegin();
+ it != window_ordering.rend() && it->first > active_center_y; ++it) {
+ if (previous_window)
+ dock_container_->StackChildAbove(it->second, previous_window);
+ previous_window = it->second;
+ }
+
+ if (active_window->parent() == dock_container_)
+ dock_container_->StackChildAtTop(active_window);
+ if (active_window != dragged_window_)
+ last_active_window_ = active_window;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// keyboard::KeyboardControllerObserver implementation:
+
+void DockedWindowLayoutManager::OnKeyboardBoundsChanging(
+ const gfx::Rect& keyboard_bounds) {
+ // This bounds change will have caused a change to the Shelf which does not
+ // propagate automatically to this class, so manually recalculate bounds.
+ UpdateDockBounds();
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/wm/dock/docked_window_layout_manager.h b/chromium/ash/wm/dock/docked_window_layout_manager.h
new file mode 100644
index 00000000000..a3019aa7175
--- /dev/null
+++ b/chromium/ash/wm/dock/docked_window_layout_manager.h
@@ -0,0 +1,235 @@
+// Copyright (c) 2013 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 ASH_WM_DOCK_DOCKED_WINDOW_LAYOUT_MANAGER_H_
+#define ASH_WM_DOCK_DOCKED_WINDOW_LAYOUT_MANAGER_H_
+
+#include "ash/ash_export.h"
+#include "ash/shelf/shelf_layout_manager_observer.h"
+#include "ash/shell_observer.h"
+#include "ash/wm/dock/dock_types.h"
+#include "ash/wm/property_util.h"
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/gtest_prod_util.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/observer_list.h"
+#include "ui/aura/client/activation_change_observer.h"
+#include "ui/aura/layout_manager.h"
+#include "ui/aura/window_observer.h"
+#include "ui/gfx/rect.h"
+#include "ui/keyboard/keyboard_controller_observer.h"
+
+namespace aura {
+class Window;
+}
+
+namespace gfx {
+class Point;
+}
+
+namespace views {
+class Widget;
+}
+
+namespace ash {
+class Launcher;
+
+namespace internal {
+class DockedWindowLayoutManagerObserver;
+class DockedWindowResizerTest;
+class ShelfLayoutManager;
+
+// DockedWindowLayoutManager is responsible for organizing windows when they are
+// docked to the side of a screen. It is associated with a specific container
+// window (i.e. kShellWindowId_DockContainer) and controls the layout of any
+// windows added to that container.
+//
+// The constructor takes a |dock_container| argument which is expected to set
+// its layout manager to this instance, e.g.:
+// dock_container->SetLayoutManager(
+// new DockedWindowLayoutManager(dock_container));
+//
+// TODO(varkha): extend BaseLayoutManager instead of LayoutManager to inherit
+// common functionality.
+class ASH_EXPORT DockedWindowLayoutManager
+ : public aura::LayoutManager,
+ public ash::ShellObserver,
+ public aura::WindowObserver,
+ public aura::client::ActivationChangeObserver,
+ public keyboard::KeyboardControllerObserver,
+ public ash::ShelfLayoutManagerObserver {
+ public:
+ explicit DockedWindowLayoutManager(aura::Window* dock_container);
+ virtual ~DockedWindowLayoutManager();
+
+ // Disconnects observers before container windows get destroyed.
+ void Shutdown();
+
+ // Management of the observer list.
+ virtual void AddObserver(DockedWindowLayoutManagerObserver* observer);
+ virtual void RemoveObserver(DockedWindowLayoutManagerObserver* observer);
+
+ // Called by a DockedWindowResizer to update which window is being dragged.
+ // Starts observing the window unless it is a child.
+ void StartDragging(aura::Window* window);
+
+ // Called by a DockedWindowResizer when a dragged window is docked.
+ void DockDraggedWindow(aura::Window* window);
+
+ // Called by a DockedWindowResizer when a dragged window is no longer docked.
+ void UndockDraggedWindow();
+
+ // Called by a DockedWindowResizer when a window is no longer being dragged.
+ // Stops observing the window unless it is a child.
+ void FinishDragging();
+
+ ash::Launcher* launcher() { return launcher_; }
+ void SetLauncher(ash::Launcher* launcher);
+
+ // Calculates if a window is touching the screen edges and returns edge.
+ DockedAlignment GetAlignmentOfWindow(const aura::Window* window) const;
+
+ // Used to snap docked windows to the side of screen during drag.
+ DockedAlignment CalculateAlignment() const;
+
+ aura::Window* dock_container() const { return dock_container_; }
+
+ // Returns current bounding rectangle of docked windows area.
+ const gfx::Rect& docked_bounds() const { return docked_bounds_; }
+
+ // Returns last known coordinates of |dragged_window_| after Relayout.
+ const gfx::Rect dragged_bounds() const { return dragged_bounds_;}
+
+ // aura::LayoutManager:
+ virtual void OnWindowResized() OVERRIDE;
+ virtual void OnWindowAddedToLayout(aura::Window* child) OVERRIDE;
+ virtual void OnWillRemoveWindowFromLayout(aura::Window* child) OVERRIDE {}
+ virtual void OnWindowRemovedFromLayout(aura::Window* child) OVERRIDE;
+ virtual void OnChildWindowVisibilityChanged(aura::Window* child,
+ bool visibile) OVERRIDE;
+ virtual void SetChildBounds(aura::Window* child,
+ const gfx::Rect& requested_bounds) OVERRIDE;
+
+ // ash::ShellObserver:
+ virtual void OnShelfAlignmentChanged(aura::RootWindow* root_window) OVERRIDE;
+
+ // aura::WindowObserver:
+ virtual void OnWindowPropertyChanged(aura::Window* window,
+ const void* key,
+ intptr_t old) OVERRIDE;
+ virtual void OnWindowBoundsChanged(aura::Window* window,
+ const gfx::Rect& old_bounds,
+ const gfx::Rect& new_bounds) OVERRIDE;
+ virtual void OnWindowDestroying(aura::Window* window) OVERRIDE;
+
+ // aura::client::ActivationChangeObserver:
+ virtual void OnWindowActivated(aura::Window* gained_active,
+ aura::Window* lost_active) OVERRIDE;
+
+ // ShelfLayoutManagerObserver:
+ virtual void WillChangeVisibilityState(
+ ShelfVisibilityState new_state) OVERRIDE;
+
+ private:
+ FRIEND_TEST_ALL_PREFIXES(DockedWindowResizerTest, AttachTwoWindowsDetachOne);
+ FRIEND_TEST_ALL_PREFIXES(DockedWindowResizerTest, AttachWindowMaximizeOther);
+ FRIEND_TEST_ALL_PREFIXES(DockedWindowResizerTest, AttachOneTestSticky);
+ FRIEND_TEST_ALL_PREFIXES(DockedWindowResizerTest, ResizeTwoWindows);
+ FRIEND_TEST_ALL_PREFIXES(DockedWindowResizerTest, DragToShelf);
+ friend class DockedWindowLayoutManagerTest;
+ friend class DockedWindowResizerTest;
+
+ // Minimum width of the docked windows area.
+ static const int kMinDockWidth;
+
+ // Maximum width of the docked windows area.
+ static const int kMaxDockWidth;
+
+ // Width of the gap between the docked windows and a workspace.
+ static const int kMinDockGap;
+
+ // Minimize / restore window and relayout.
+ void MinimizeWindow(aura::Window* window);
+ void RestoreWindow(aura::Window* window);
+
+ // Updates docked layout state when a window gets inside the dock.
+ void OnWindowDocked(aura::Window* window);
+
+ // Updates docked layout state when a window gets outside the dock.
+ void OnWindowUndocked();
+
+ // Returns true if there are any windows currently docked.
+ bool IsAnyWindowDocked();
+
+ // Called whenever the window layout might change.
+ void Relayout();
+
+ // Updates |docked_bounds_| and workspace insets when bounds of docked windows
+ // area change.
+ void UpdateDockBounds();
+
+ // Called whenever the window stacking order needs to be updated (e.g. focus
+ // changes or a window is moved).
+ void UpdateStacking(aura::Window* active_window);
+
+ // keyboard::KeyboardControllerObserver:
+ virtual void OnKeyboardBoundsChanging(
+ const gfx::Rect& keyboard_bounds) OVERRIDE;
+
+ // Parent window associated with this layout manager.
+ aura::Window* dock_container_;
+ // Protect against recursive calls to Relayout().
+ bool in_layout_;
+
+ // A window that is being dragged (whether docked or not).
+ // Windows are tracked by docked layout manager only if they are docked;
+ // however we need to know if a window is being dragged in order to avoid
+ // positioning it or even considering it for layout.
+ aura::Window* dragged_window_;
+
+ // True if the window being dragged is currently docked.
+ bool is_dragged_window_docked_;
+
+ // Previously docked windows use a more relaxed dragging sorting algorithm
+ // that uses assumption that a window starts being dragged out of position
+ // that was previously established in Relayout. This allows easier reordering.
+ bool is_dragged_from_dock_;
+
+ // The launcher we are observing for launcher icon changes.
+ Launcher* launcher_;
+ // The shelf layout manager being observed for visibility changes.
+ ShelfLayoutManager* shelf_layout_manager_;
+ // Tracks the visibility of the shelf. Defaults to false when there is no
+ // shelf.
+ bool shelf_hidden_;
+ // Current width of the dock.
+ int docked_width_;
+
+ // Last bounds that were sent to observers.
+ gfx::Rect docked_bounds_;
+
+ // Target bounds of a docked window being dragged.
+ gfx::Rect dragged_bounds_;
+
+ // Side of the screen that the dock is positioned at.
+ DockedAlignment alignment_;
+
+ // The last active window. Used to maintain stacking order even if no windows
+ // are currently focused.
+ aura::Window* last_active_window_;
+
+ // Widget used to paint a background for the docked area.
+ scoped_ptr<views::Widget> background_widget_;
+
+ // Observers of dock bounds changes.
+ ObserverList<DockedWindowLayoutManagerObserver> observer_list_;
+
+ DISALLOW_COPY_AND_ASSIGN(DockedWindowLayoutManager);
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_WM_DOCK_DOCKED_WINDOW_LAYOUT_MANAGER_H_
diff --git a/chromium/ash/wm/dock/docked_window_layout_manager_observer.h b/chromium/ash/wm/dock/docked_window_layout_manager_observer.h
new file mode 100644
index 00000000000..20afca99b60
--- /dev/null
+++ b/chromium/ash/wm/dock/docked_window_layout_manager_observer.h
@@ -0,0 +1,31 @@
+// Copyright 2013 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 UI_DOCK_DOCKED_WINDOW_LAYOUT_MANAGER_OBSERVER_H_
+#define UI_DOCK_DOCKED_WINDOW_LAYOUT_MANAGER_OBSERVER_H_
+
+#include "ash/ash_export.h"
+
+namespace gfx {
+class Rect;
+}
+
+namespace ash {
+namespace internal {
+
+// Observers to the DockedWindowLayoutManager are notified of significant
+// events that occur with the docked windows, such as the bounds change.
+class ASH_EXPORT DockedWindowLayoutManagerObserver {
+ public:
+ // Called after the dock bounds are changed.
+ virtual void OnDockBoundsChanging(const gfx::Rect& new_bounds) = 0;
+
+ protected:
+ virtual ~DockedWindowLayoutManagerObserver() {}
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // UI_DOCK_DOCKED_WINDOW_LAYOUT_MANAGER_OBSERVER_H_
diff --git a/chromium/ash/wm/dock/docked_window_layout_manager_unittest.cc b/chromium/ash/wm/dock/docked_window_layout_manager_unittest.cc
new file mode 100644
index 00000000000..b2f6ffd9cf4
--- /dev/null
+++ b/chromium/ash/wm/dock/docked_window_layout_manager_unittest.cc
@@ -0,0 +1,436 @@
+// Copyright (c) 2013 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.
+
+#include "ash/wm/dock/docked_window_layout_manager.h"
+
+#include "ash/ash_switches.h"
+#include "ash/display/display_controller.h"
+#include "ash/launcher/launcher.h"
+#include "ash/launcher/launcher_model.h"
+#include "ash/root_window_controller.h"
+#include "ash/screen_ash.h"
+#include "ash/shelf/shelf_layout_manager.h"
+#include "ash/shelf/shelf_types.h"
+#include "ash/shelf/shelf_widget.h"
+#include "ash/shell.h"
+#include "ash/shell_window_ids.h"
+#include "ash/test/ash_test_base.h"
+#include "ash/test/launcher_view_test_api.h"
+#include "ash/test/shell_test_api.h"
+#include "ash/test/test_launcher_delegate.h"
+#include "ash/wm/panels/panel_layout_manager.h"
+#include "ash/wm/window_properties.h"
+#include "ash/wm/window_resizer.h"
+#include "base/basictypes.h"
+#include "base/command_line.h"
+#include "ui/aura/client/aura_constants.h"
+#include "ui/aura/root_window.h"
+#include "ui/aura/window.h"
+#include "ui/base/hit_test.h"
+#include "ui/gfx/screen.h"
+#include "ui/views/widget/widget.h"
+
+namespace ash {
+namespace internal {
+
+class DockedWindowLayoutManagerTest
+ : public test::AshTestBase,
+ public testing::WithParamInterface<aura::client::WindowType> {
+ public:
+ DockedWindowLayoutManagerTest() : window_type_(GetParam()) {}
+ virtual ~DockedWindowLayoutManagerTest() {}
+
+ virtual void SetUp() OVERRIDE {
+ CommandLine::ForCurrentProcess()->AppendSwitch(
+ ash::switches::kAshEnableStickyEdges);
+ CommandLine::ForCurrentProcess()->AppendSwitch(
+ ash::switches::kAshEnableDockedWindows);
+ AshTestBase::SetUp();
+ UpdateDisplay("600x600");
+ ASSERT_TRUE(test::TestLauncherDelegate::instance());
+
+ launcher_view_test_.reset(new test::LauncherViewTestAPI(
+ Launcher::ForPrimaryDisplay()->GetLauncherViewForTest()));
+ launcher_view_test_->SetAnimationDuration(1);
+ }
+
+ protected:
+ enum DockedEdge {
+ DOCKED_EDGE_NONE,
+ DOCKED_EDGE_LEFT,
+ DOCKED_EDGE_RIGHT,
+ };
+
+ aura::Window* CreateTestWindow(const gfx::Rect& bounds) {
+ aura::Window* window = CreateTestWindowInShellWithDelegateAndType(
+ NULL,
+ window_type_,
+ 0,
+ bounds);
+ if (window_type_ == aura::client::WINDOW_TYPE_PANEL) {
+ test::TestLauncherDelegate* launcher_delegate =
+ test::TestLauncherDelegate::instance();
+ launcher_delegate->AddLauncherItem(window);
+ PanelLayoutManager* manager =
+ static_cast<PanelLayoutManager*>(GetPanelContainer(window)->
+ layout_manager());
+ manager->Relayout();
+ }
+ return window;
+ }
+
+ aura::Window* GetPanelContainer(aura::Window* panel) {
+ return Shell::GetContainer(panel->GetRootWindow(),
+ internal::kShellWindowId_PanelContainer);
+ }
+
+ static WindowResizer* CreateSomeWindowResizer(
+ aura::Window* window,
+ const gfx::Point& point_in_parent,
+ int window_component) {
+ return CreateWindowResizer(
+ window,
+ point_in_parent,
+ window_component,
+ aura::client::WINDOW_MOVE_SOURCE_MOUSE).release();
+ }
+
+ void DragStart(aura::Window* window) {
+ initial_location_in_parent_ = window->bounds().origin();
+ resizer_.reset(CreateSomeWindowResizer(window,
+ initial_location_in_parent_,
+ HTCAPTION));
+ ASSERT_TRUE(resizer_.get());
+ }
+
+ void DragStartAtOffsetFromwindowOrigin(aura::Window* window,
+ int dx,
+ int dy) {
+ initial_location_in_parent_ =
+ window->bounds().origin() + gfx::Vector2d(dx, dy);
+ resizer_.reset(CreateSomeWindowResizer(window,
+ initial_location_in_parent_,
+ HTCAPTION));
+ ASSERT_TRUE(resizer_.get());
+ }
+
+ void DragMove(int dx, int dy) {
+ resizer_->Drag(initial_location_in_parent_ + gfx::Vector2d(dx, dy), 0);
+ }
+
+ void DragEnd() {
+ resizer_->CompleteDrag(0);
+ resizer_.reset();
+ }
+
+ void DragRevert() {
+ resizer_->RevertDrag();
+ resizer_.reset();
+ }
+
+ // Panels are parented by panel container during drags.
+ // Docked windows are parented by dock container during drags.
+ // All other windows that we are testing here have default container as a
+ // parent.
+ int CorrectContainerIdDuringDrag() {
+ if (window_type_ == aura::client::WINDOW_TYPE_PANEL)
+ return internal::kShellWindowId_PanelContainer;
+ return internal::kShellWindowId_DockedContainer;
+ }
+
+ // Test dragging the window vertically (to detach if it is a panel) and then
+ // horizontally to the edge with an added offset from the edge of |dx|.
+ void DragRelativeToEdge(DockedEdge edge,
+ aura::Window* window,
+ int dx) {
+ DragVerticallyAndRelativeToEdge(
+ edge,
+ window,
+ dx,
+ window_type_ == aura::client::WINDOW_TYPE_PANEL ? -100 : 20);
+ }
+
+ void DragToVerticalPositionAndToEdge(DockedEdge edge,
+ aura::Window* window,
+ int y) {
+ DragToVerticalPositionRelativeToEdge(edge, window, 0, y);
+ }
+
+ void DragToVerticalPositionRelativeToEdge(DockedEdge edge,
+ aura::Window* window,
+ int dx,
+ int y) {
+ gfx::Rect initial_bounds = window->GetBoundsInScreen();
+ DragVerticallyAndRelativeToEdge(edge, window, dx, y - initial_bounds.y());
+ }
+
+ // Detach if our window is a panel, then drag it vertically by |dy| and
+ // horizontally to the edge with an added offset from the edge of |dx|.
+ void DragVerticallyAndRelativeToEdge(DockedEdge edge,
+ aura::Window* window,
+ int dx,
+ int dy) {
+ aura::RootWindow* root_window = window->GetRootWindow();
+ gfx::Rect initial_bounds = window->GetBoundsInScreen();
+
+ if (window_type_ == aura::client::WINDOW_TYPE_PANEL) {
+ ASSERT_NO_FATAL_FAILURE(DragStart(window));
+ EXPECT_TRUE(window->GetProperty(kPanelAttachedKey));
+
+ // Drag enough to detach since our tests assume panels to be initially
+ // detached.
+ DragMove(0, dy);
+ EXPECT_EQ(CorrectContainerIdDuringDrag(), window->parent()->id());
+ EXPECT_EQ(initial_bounds.x(), window->GetBoundsInScreen().x());
+ EXPECT_EQ(initial_bounds.y() + dy, window->GetBoundsInScreen().y());
+
+ // The panel should be detached when the drag completes.
+ DragEnd();
+
+ EXPECT_FALSE(window->GetProperty(kPanelAttachedKey));
+ EXPECT_EQ(internal::kShellWindowId_DefaultContainer,
+ window->parent()->id());
+ EXPECT_EQ(root_window, window->GetRootWindow());
+ }
+
+ // avoid snap by clicking away from the border
+ ASSERT_NO_FATAL_FAILURE(DragStartAtOffsetFromwindowOrigin(window, 25, 5));
+
+ // Drag the window left or right to the edge (or almost to it).
+ if (edge == DOCKED_EDGE_LEFT)
+ dx += window->GetRootWindow()->bounds().x() - initial_bounds.x();
+ else if (edge == DOCKED_EDGE_RIGHT)
+ dx += window->GetRootWindow()->bounds().right() - initial_bounds.right();
+ DragMove(dx, window_type_ == aura::client::WINDOW_TYPE_PANEL ? 0 : dy);
+ EXPECT_EQ(CorrectContainerIdDuringDrag(), window->parent()->id());
+ // Release the mouse and the panel should be attached to the dock.
+ DragEnd();
+
+ // x-coordinate can get adjusted by snapping or sticking.
+ // y-coordinate could be changed by possible automatic layout if docked.
+ if (window->parent()->id() != internal::kShellWindowId_DockedContainer)
+ EXPECT_EQ(initial_bounds.y() + dy, window->GetBoundsInScreen().y());
+ }
+
+ private:
+ scoped_ptr<WindowResizer> resizer_;
+ scoped_ptr<test::LauncherViewTestAPI> launcher_view_test_;
+ aura::client::WindowType window_type_;
+
+ // Location at start of the drag in |window->parent()|'s coordinates.
+ gfx::Point initial_location_in_parent_;
+
+ DISALLOW_COPY_AND_ASSIGN(DockedWindowLayoutManagerTest);
+};
+
+// Tests that a created window is successfully added to the dock
+// layout manager.
+TEST_P(DockedWindowLayoutManagerTest, AddOneWindow) {
+ if (!SupportsHostWindowResize())
+ return;
+
+ gfx::Rect bounds(0, 0, 201, 201);
+ scoped_ptr<aura::Window> window(CreateTestWindow(bounds));
+ DragRelativeToEdge(DOCKED_EDGE_RIGHT, window.get(), 0);
+
+ // The window should be attached and snapped to the right side of the screen.
+ EXPECT_EQ(window->GetRootWindow()->bounds().right(),
+ window->GetBoundsInScreen().right());
+ EXPECT_EQ(internal::kShellWindowId_DockedContainer, window->parent()->id());
+}
+
+// Adds two windows and tests that the gaps are evenly distributed.
+TEST_P(DockedWindowLayoutManagerTest, AddTwoWindows) {
+ if (!SupportsHostWindowResize())
+ return;
+
+ scoped_ptr<aura::Window> w1(CreateTestWindow(gfx::Rect(0, 0, 201, 201)));
+ scoped_ptr<aura::Window> w2(CreateTestWindow(gfx::Rect(0, 0, 210, 202)));
+ DragToVerticalPositionAndToEdge(DOCKED_EDGE_RIGHT, w1.get(), 20);
+ DragToVerticalPositionAndToEdge(DOCKED_EDGE_RIGHT, w2.get(), 300);
+
+ // The windows should be attached and snapped to the right side of the screen.
+ EXPECT_EQ(w1->GetRootWindow()->bounds().right(),
+ w1->GetBoundsInScreen().right());
+ EXPECT_EQ(internal::kShellWindowId_DockedContainer, w1->parent()->id());
+ EXPECT_EQ(w2->GetRootWindow()->bounds().right(),
+ w2->GetBoundsInScreen().right());
+ EXPECT_EQ(internal::kShellWindowId_DockedContainer, w2->parent()->id());
+
+ // Test that the gaps differ at most by a single pixel.
+ int gap1 = w1->GetBoundsInScreen().y();
+ int gap2 = w2->GetBoundsInScreen().y() - w1->GetBoundsInScreen().bottom();
+ int gap3 = ScreenAsh::GetDisplayWorkAreaBoundsInParent(w1.get()).bottom() -
+ w2->GetBoundsInScreen().bottom();
+ EXPECT_LE(abs(gap1 - gap2), 1);
+ EXPECT_LE(abs(gap2 - gap3), 1);
+ EXPECT_LE(abs(gap3 - gap1), 1);
+}
+
+// Adds two non-overlapping windows and tests layout after a drag.
+TEST_P(DockedWindowLayoutManagerTest, TwoWindowsDragging) {
+ if (!SupportsHostWindowResize())
+ return;
+
+ scoped_ptr<aura::Window> w1(CreateTestWindow(gfx::Rect(0, 0, 201, 201)));
+ scoped_ptr<aura::Window> w2(CreateTestWindow(gfx::Rect(0, 0, 210, 202)));
+ DragToVerticalPositionAndToEdge(DOCKED_EDGE_RIGHT, w1.get(), 20);
+ DragToVerticalPositionAndToEdge(DOCKED_EDGE_RIGHT, w2.get(), 300);
+
+ // The windows should be attached and snapped to the right side of the screen.
+ EXPECT_EQ(w1->GetRootWindow()->bounds().right(),
+ w1->GetBoundsInScreen().right());
+ EXPECT_EQ(internal::kShellWindowId_DockedContainer, w1->parent()->id());
+ EXPECT_EQ(w2->GetRootWindow()->bounds().right(),
+ w2->GetBoundsInScreen().right());
+ EXPECT_EQ(internal::kShellWindowId_DockedContainer, w2->parent()->id());
+
+ // Drag w2 above w1.
+ ASSERT_NO_FATAL_FAILURE(DragStartAtOffsetFromwindowOrigin(w2.get(), 0, 20));
+ DragMove(0, w1->bounds().y() - w2->bounds().y() - 20);
+ DragEnd();
+
+ // Test the new windows order and that the gaps differ at most by a pixel.
+ int gap1 = w2->GetBoundsInScreen().y();
+ int gap2 = w1->GetBoundsInScreen().y() - w2->GetBoundsInScreen().bottom();
+ int gap3 = ScreenAsh::GetDisplayWorkAreaBoundsInParent(w1.get()).bottom() -
+ w1->GetBoundsInScreen().bottom();
+ EXPECT_LE(abs(gap1 - gap2), 1);
+ EXPECT_LE(abs(gap2 - gap3), 1);
+ EXPECT_LE(abs(gap3 - gap1), 1);
+}
+
+// Adds three overlapping windows and tests layout after a drag.
+TEST_P(DockedWindowLayoutManagerTest, ThreeWindowsDragging) {
+ if (!SupportsHostWindowResize())
+ return;
+
+ scoped_ptr<aura::Window> w1(CreateTestWindow(gfx::Rect(0, 0, 201, 201)));
+ DragToVerticalPositionAndToEdge(DOCKED_EDGE_RIGHT, w1.get(), 20);
+ scoped_ptr<aura::Window> w2(CreateTestWindow(gfx::Rect(0, 0, 210, 202)));
+ DragToVerticalPositionAndToEdge(DOCKED_EDGE_RIGHT, w2.get(), 200);
+ scoped_ptr<aura::Window> w3(CreateTestWindow(gfx::Rect(0, 0, 220, 204)));
+ DragToVerticalPositionAndToEdge(DOCKED_EDGE_RIGHT, w3.get(), 300);
+
+ // All windows should be attached and snapped to the right side of the screen.
+ EXPECT_EQ(w1->GetRootWindow()->bounds().right(),
+ w1->GetBoundsInScreen().right());
+ EXPECT_EQ(internal::kShellWindowId_DockedContainer, w1->parent()->id());
+ EXPECT_EQ(w2->GetRootWindow()->bounds().right(),
+ w2->GetBoundsInScreen().right());
+ EXPECT_EQ(internal::kShellWindowId_DockedContainer, w2->parent()->id());
+ EXPECT_EQ(w3->GetRootWindow()->bounds().right(),
+ w3->GetBoundsInScreen().right());
+ EXPECT_EQ(internal::kShellWindowId_DockedContainer, w3->parent()->id());
+
+ // Test that the top and bottom windows are clamped in work area and
+ // that the overlaps between the windows differ at most by a pixel.
+ int overlap1 = w1->GetBoundsInScreen().y();
+ int overlap2 = w1->GetBoundsInScreen().bottom() - w2->GetBoundsInScreen().y();
+ int overlap3 = w2->GetBoundsInScreen().bottom() - w3->GetBoundsInScreen().y();
+ int overlap4 =
+ ScreenAsh::GetDisplayWorkAreaBoundsInParent(w3.get()).bottom() -
+ w3->GetBoundsInScreen().bottom();
+ EXPECT_EQ(0, overlap1);
+ EXPECT_LE(abs(overlap2 - overlap3), 1);
+ EXPECT_EQ(0, overlap4);
+
+ // Drag w1 below the point where w1 and w2 would swap places. This point is
+ // half way between the tops of those two windows. Add 1 because w2 is taller.
+ ASSERT_NO_FATAL_FAILURE(DragStartAtOffsetFromwindowOrigin(w1.get(), 0, 20));
+ DragMove(0, (w2->bounds().y() - w1->bounds().y()) / 2 + 1);
+
+ // During the drag the windows get rearranged and the top and the bottom
+ // should be clamped by the work area.
+ EXPECT_EQ(0, w2->GetBoundsInScreen().y());
+ EXPECT_GT(w1->GetBoundsInScreen().y(), w2->GetBoundsInScreen().y());
+ EXPECT_EQ(ScreenAsh::GetDisplayWorkAreaBoundsInParent(w3.get()).bottom(),
+ w3->GetBoundsInScreen().bottom());
+ DragEnd();
+
+ // Test the new windows order and that the overlaps differ at most by a pixel.
+ overlap1 = w2->GetBoundsInScreen().y();
+ overlap2 = w2->GetBoundsInScreen().bottom() - w1->GetBoundsInScreen().y();
+ overlap3 = w1->GetBoundsInScreen().bottom() - w3->GetBoundsInScreen().y();
+ overlap4 = ScreenAsh::GetDisplayWorkAreaBoundsInParent(w3.get()).bottom() -
+ w3->GetBoundsInScreen().bottom();
+ EXPECT_EQ(0, overlap1);
+ EXPECT_LE(abs(overlap2 - overlap3), 1);
+ EXPECT_EQ(0, overlap4);
+}
+
+// Adds three windows in bottom display and tests layout after a drag.
+TEST_P(DockedWindowLayoutManagerTest, ThreeWindowsDraggingSecondScreen) {
+ if (!SupportsMultipleDisplays())
+ return;
+ if (!SupportsHostWindowResize())
+ return;
+
+ // Create two screen vertical layout.
+ UpdateDisplay("100+100-600x600,100+700-600x600");
+ // Layout the secondary display to the bottom of the primary.
+ DisplayLayout layout(DisplayLayout::BOTTOM, 0);
+ ASSERT_GT(Shell::GetScreen()->GetNumDisplays(), 1);
+ Shell::GetInstance()->display_controller()->
+ SetLayoutForCurrentDisplays(layout);
+
+ scoped_ptr<aura::Window> w1(CreateTestWindow(gfx::Rect(0, 600, 201, 201)));
+ DragToVerticalPositionAndToEdge(DOCKED_EDGE_RIGHT, w1.get(), 600 + 20);
+ scoped_ptr<aura::Window> w2(CreateTestWindow(gfx::Rect(0, 600, 210, 202)));
+ DragToVerticalPositionAndToEdge(DOCKED_EDGE_RIGHT, w2.get(), 600 + 200);
+ scoped_ptr<aura::Window> w3(CreateTestWindow(gfx::Rect(0, 600, 220, 204)));
+ DragToVerticalPositionAndToEdge(DOCKED_EDGE_RIGHT, w3.get(), 600 + 300);
+
+ // All windows should be attached and snapped to the right side of the screen.
+ EXPECT_EQ(w1->GetRootWindow()->bounds().right(),
+ w1->GetBoundsInScreen().right());
+ EXPECT_EQ(internal::kShellWindowId_DockedContainer, w1->parent()->id());
+ EXPECT_EQ(w2->GetRootWindow()->bounds().right(),
+ w2->GetBoundsInScreen().right());
+ EXPECT_EQ(internal::kShellWindowId_DockedContainer, w2->parent()->id());
+ EXPECT_EQ(w3->GetRootWindow()->bounds().right(),
+ w3->GetBoundsInScreen().right());
+ EXPECT_EQ(internal::kShellWindowId_DockedContainer, w3->parent()->id());
+
+ gfx::Rect work_area =
+ Shell::GetScreen()->GetDisplayNearestWindow(w1.get()).work_area();
+ // Test that the top and bottom windows are clamped in work area and
+ // that the overlaps between the windows differ at most by a pixel.
+ int overlap1 = w1->GetBoundsInScreen().y() - work_area.y();
+ int overlap2 = w1->GetBoundsInScreen().bottom() - w2->GetBoundsInScreen().y();
+ int overlap3 = w2->GetBoundsInScreen().bottom() - w3->GetBoundsInScreen().y();
+ int overlap4 = work_area.bottom() - w3->GetBoundsInScreen().bottom();
+ EXPECT_EQ(0, overlap1);
+ EXPECT_LE(abs(overlap2 - overlap3), 1);
+ EXPECT_EQ(0, overlap4);
+
+ // Drag w1 below the point where w1 and w2 would swap places. This point is
+ // half way between the tops of those two windows. Add 1 because w2 is taller.
+ ASSERT_NO_FATAL_FAILURE(DragStartAtOffsetFromwindowOrigin(w1.get(), 0, 20));
+ DragMove(0, (w2->bounds().y() - w1->bounds().y()) / 2 + 1);
+
+ // During the drag the windows get rearranged and the top and the bottom
+ // should be clamped by the work area.
+ EXPECT_EQ(work_area.y(), w2->GetBoundsInScreen().y());
+ EXPECT_GT(w1->GetBoundsInScreen().y(), w2->GetBoundsInScreen().y());
+ EXPECT_EQ(work_area.bottom(), w3->GetBoundsInScreen().bottom());
+ DragEnd();
+
+ // Test the new windows order and that the overlaps differ at most by a pixel.
+ overlap1 = w2->GetBoundsInScreen().y() - work_area.y();
+ overlap2 = w2->GetBoundsInScreen().bottom() - w1->GetBoundsInScreen().y();
+ overlap3 = w1->GetBoundsInScreen().bottom() - w3->GetBoundsInScreen().y();
+ overlap4 = work_area.bottom() - w3->GetBoundsInScreen().bottom();
+ EXPECT_EQ(0, overlap1);
+ EXPECT_LE(abs(overlap2 - overlap3), 1);
+ EXPECT_EQ(0, overlap4);
+}
+
+// Tests run twice - on both panels and normal windows
+INSTANTIATE_TEST_CASE_P(NormalOrPanel,
+ DockedWindowLayoutManagerTest,
+ testing::Values(aura::client::WINDOW_TYPE_NORMAL,
+ aura::client::WINDOW_TYPE_PANEL));
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/wm/dock/docked_window_resizer.cc b/chromium/ash/wm/dock/docked_window_resizer.cc
new file mode 100644
index 00000000000..3e1ef29b0ad
--- /dev/null
+++ b/chromium/ash/wm/dock/docked_window_resizer.cc
@@ -0,0 +1,360 @@
+// Copyright 2013 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.
+
+#include "ash/wm/dock/docked_window_resizer.h"
+
+#include "ash/ash_switches.h"
+#include "ash/launcher/launcher.h"
+#include "ash/root_window_controller.h"
+#include "ash/screen_ash.h"
+#include "ash/shelf/shelf_types.h"
+#include "ash/shelf/shelf_widget.h"
+#include "ash/shell.h"
+#include "ash/shell_window_ids.h"
+#include "ash/wm/coordinate_conversion.h"
+#include "ash/wm/dock/docked_window_layout_manager.h"
+#include "ash/wm/property_util.h"
+#include "ash/wm/window_properties.h"
+#include "ash/wm/workspace/magnetism_matcher.h"
+#include "ash/wm/workspace/phantom_window_controller.h"
+#include "ash/wm/workspace/workspace_window_resizer.h"
+#include "base/command_line.h"
+#include "ui/aura/client/aura_constants.h"
+#include "ui/aura/env.h"
+#include "ui/aura/root_window.h"
+#include "ui/aura/window.h"
+#include "ui/aura/window_delegate.h"
+#include "ui/base/hit_test.h"
+#include "ui/base/ui_base_types.h"
+#include "ui/gfx/screen.h"
+#include "ui/views/widget/widget.h"
+
+namespace ash {
+namespace internal {
+
+namespace {
+
+DockedWindowLayoutManager* GetDockedLayoutManagerAtPoint(
+ const gfx::Point& point) {
+ aura::Window* dock_container = Shell::GetContainer(
+ wm::GetRootWindowAt(point),
+ kShellWindowId_DockedContainer);
+ return static_cast<DockedWindowLayoutManager*>(
+ dock_container->layout_manager());
+}
+
+} // namespace
+
+DockedWindowResizer::~DockedWindowResizer() {
+ if (destroyed_)
+ *destroyed_ = true;
+}
+
+// static
+DockedWindowResizer*
+DockedWindowResizer::Create(WindowResizer* next_window_resizer,
+ aura::Window* window,
+ const gfx::Point& location,
+ int window_component,
+ aura::client::WindowMoveSource source) {
+ Details details(window, location, window_component, source);
+ return details.is_resizable ?
+ new DockedWindowResizer(next_window_resizer, details) : NULL;
+}
+
+void DockedWindowResizer::Drag(const gfx::Point& location, int event_flags) {
+ last_location_ = location;
+ wm::ConvertPointToScreen(GetTarget()->parent(), &last_location_);
+ bool destroyed = false;
+ if (!did_move_or_resize_) {
+ did_move_or_resize_ = true;
+ StartedDragging();
+ }
+ gfx::Point offset;
+ gfx::Rect bounds(CalculateBoundsForDrag(details_, location));
+ bool set_tracked_by_workspace = MaybeSnapToEdge(bounds, &offset);
+
+ // Temporarily clear kWindowTrackedByWorkspaceKey for windows that are snapped
+ // to screen edges e.g. when they are docked. This prevents the windows from
+ // getting snapped to other nearby windows during the drag.
+ bool tracked_by_workspace = GetTrackedByWorkspace(GetTarget());
+ if (set_tracked_by_workspace)
+ SetTrackedByWorkspace(GetTarget(), false);
+ gfx::Point modified_location(location.x() + offset.x(),
+ location.y() + offset.y());
+ destroyed_ = &destroyed;
+ next_window_resizer_->Drag(modified_location, event_flags);
+
+ // TODO(varkha): Refactor the way WindowResizer calls into other window
+ // resizers to avoid the awkward pattern here for checking if
+ // next_window_resizer_ destroys the resizer object.
+ if (destroyed)
+ return;
+ destroyed_ = NULL;
+ if (set_tracked_by_workspace)
+ SetTrackedByWorkspace(GetTarget(), tracked_by_workspace);
+
+ DockedWindowLayoutManager* new_dock_layout =
+ GetDockedLayoutManagerAtPoint(last_location_);
+ if (new_dock_layout != dock_layout_) {
+ // The window is being dragged to a new display. If the previous
+ // container is the current parent of the window it will be informed of
+ // the end of drag when the window is reparented, otherwise let the
+ // previous container know the drag is complete. If we told the
+ // window's parent that the drag was complete it would begin
+ // positioning the window.
+ if (is_docked_)
+ dock_layout_->UndockDraggedWindow();
+ if (dock_layout_ != initial_dock_layout_)
+ dock_layout_->FinishDragging();
+ is_docked_ = false;
+ dock_layout_ = new_dock_layout;
+ // The window's initial layout manager already knows that the drag is
+ // in progress for this window.
+ if (new_dock_layout != initial_dock_layout_)
+ new_dock_layout->StartDragging(GetTarget());
+ }
+
+ // Show snapping animation when a window touches a screen edge or when
+ // it is about to get docked.
+ DockedAlignment new_docked_alignment = GetDraggedWindowAlignment();
+ if (new_docked_alignment != DOCKED_ALIGNMENT_NONE) {
+ if (!is_docked_) {
+ dock_layout_->DockDraggedWindow(GetTarget());
+ is_docked_ = true;
+ }
+ UpdateSnapPhantomWindow();
+ } else {
+ if (is_docked_) {
+ dock_layout_->UndockDraggedWindow();
+ is_docked_ = false;
+ }
+ // Clear phantom window when a window gets undocked.
+ snap_phantom_window_controller_.reset();
+ }
+}
+
+void DockedWindowResizer::CompleteDrag(int event_flags) {
+ snap_phantom_window_controller_.reset();
+
+ // Temporarily clear kWindowTrackedByWorkspaceKey for panels so that they
+ // don't get forced into the workspace that may be shrunken because of docked
+ // windows.
+ bool tracked_by_workspace = GetTrackedByWorkspace(GetTarget());
+ bool set_tracked_by_workspace = was_docked_;
+ if (set_tracked_by_workspace)
+ SetTrackedByWorkspace(GetTarget(), false);
+ // The root window can change when dragging into a different screen.
+ next_window_resizer_->CompleteDrag(event_flags);
+ FinishedDragging();
+ if (set_tracked_by_workspace)
+ SetTrackedByWorkspace(GetTarget(), tracked_by_workspace);
+}
+
+void DockedWindowResizer::RevertDrag() {
+ snap_phantom_window_controller_.reset();
+
+ // Temporarily clear kWindowTrackedByWorkspaceKey for panels so that they
+ // don't get forced into the workspace that may be shrunken because of docked
+ // windows.
+ bool tracked_by_workspace = GetTrackedByWorkspace(GetTarget());
+ bool set_tracked_by_workspace = was_docked_;
+ if (set_tracked_by_workspace)
+ SetTrackedByWorkspace(GetTarget(), false);
+ next_window_resizer_->RevertDrag();
+ FinishedDragging();
+ if (set_tracked_by_workspace)
+ SetTrackedByWorkspace(GetTarget(), tracked_by_workspace);
+}
+
+aura::Window* DockedWindowResizer::GetTarget() {
+ return next_window_resizer_->GetTarget();
+}
+
+const gfx::Point& DockedWindowResizer::GetInitialLocation() const {
+ return details_.initial_location_in_parent;
+}
+
+DockedWindowResizer::DockedWindowResizer(WindowResizer* next_window_resizer,
+ const Details& details)
+ : details_(details),
+ next_window_resizer_(next_window_resizer),
+ dock_layout_(NULL),
+ initial_dock_layout_(NULL),
+ did_move_or_resize_(false),
+ was_docked_(false),
+ is_docked_(false),
+ destroyed_(NULL) {
+ DCHECK(details_.is_resizable);
+ aura::Window* dock_container = Shell::GetContainer(
+ details.window->GetRootWindow(),
+ kShellWindowId_DockedContainer);
+ dock_layout_ = static_cast<DockedWindowLayoutManager*>(
+ dock_container->layout_manager());
+ initial_dock_layout_ = dock_layout_;
+ was_docked_ = details.window->parent() == dock_container;
+ is_docked_ = was_docked_;
+}
+
+DockedAlignment DockedWindowResizer::GetDraggedWindowAlignment() {
+ aura::Window* window = GetTarget();
+ DockedWindowLayoutManager* layout_manager =
+ GetDockedLayoutManagerAtPoint(last_location_);
+ const DockedAlignment alignment = layout_manager->CalculateAlignment();
+ const gfx::Rect& bounds(window->GetBoundsInScreen());
+
+ // Check if the window is touching the edge - it may need to get docked.
+ if (alignment == DOCKED_ALIGNMENT_NONE)
+ return layout_manager->GetAlignmentOfWindow(window);
+
+ // Both bounds and pointer location are checked because some drags involve
+ // stickiness at the workspace-to-dock boundary and so the |location| may be
+ // outside of the |bounds|.
+ // It is also possible that all the docked windows are minimized or hidden
+ // in which case the dragged window needs to be exactly touching the same
+ // edge that those docked windows were aligned before they got minimized.
+ // TODO(varkha): Consider eliminating sticky behavior on that boundary when
+ // a pointer enters docked area.
+ if ((layout_manager->docked_bounds().Intersects(bounds) &&
+ layout_manager->docked_bounds().Contains(last_location_)) ||
+ alignment == layout_manager->GetAlignmentOfWindow(window)) {
+ // A window is being added to other docked windows (on the same side).
+ return alignment;
+ }
+ return DOCKED_ALIGNMENT_NONE;
+}
+
+bool DockedWindowResizer::MaybeSnapToEdge(const gfx::Rect& bounds,
+ gfx::Point* offset) {
+ aura::Window* dock_container = Shell::GetContainer(
+ wm::GetRootWindowAt(last_location_),
+ kShellWindowId_DockedContainer);
+ DockedAlignment dock_alignment =
+ GetDockedLayoutManagerAtPoint(last_location_)->CalculateAlignment();
+ gfx::Rect dock_bounds = ScreenAsh::ConvertRectFromScreen(
+ GetTarget()->parent(), dock_container->GetBoundsInScreen());
+ // Windows only snap magnetically when they are close to the edge of the
+ // screen and when the cursor is over other docked windows.
+ // When a window being dragged is the last window that was previously
+ // docked it is still allowed to magnetically snap to either side.
+ bool can_snap = was_docked_ ||
+ (GetDraggedWindowAlignment() != DOCKED_ALIGNMENT_NONE);
+ if (!can_snap)
+ return false;
+
+ // Distance in pixels that the cursor must move past an edge for a window
+ // to move beyond that edge. Same constant as in WorkspaceWindowResizer
+ // is used for consistency.
+ const int kStickyDistance = WorkspaceWindowResizer::kStickyDistancePixels;
+
+ // Short-range magnetism when retaining docked state. Same constant as in
+ // MagnetismMatcher is used for consistency.
+ const int kSnapToDockDistance = MagnetismMatcher::kMagneticDistance;
+
+ if (dock_alignment == DOCKED_ALIGNMENT_LEFT ||
+ (dock_alignment == DOCKED_ALIGNMENT_NONE && was_docked_)) {
+ const int distance = bounds.x() - dock_bounds.x();
+ if (distance < (was_docked_ ? kSnapToDockDistance : 0) &&
+ distance > -kStickyDistance) {
+ offset->set_x(-distance);
+ return true;
+ }
+ }
+ if (dock_alignment == DOCKED_ALIGNMENT_RIGHT ||
+ (dock_alignment == DOCKED_ALIGNMENT_NONE && was_docked_)) {
+ const int distance = dock_bounds.right() - bounds.right();
+ if (distance < (was_docked_ ? kSnapToDockDistance : 0) &&
+ distance > -kStickyDistance) {
+ offset->set_x(distance);
+ return true;
+ }
+ }
+ return false;
+}
+
+void DockedWindowResizer::StartedDragging() {
+ // Tell the dock layout manager that we are dragging this window.
+ // At this point we are not yet animating the window as it may not be
+ // inside the docked area.
+ dock_layout_->StartDragging(GetTarget());
+ // Reparent workspace windows during the drag to elevate them above workspace.
+ // Other windows for which the DockedWindowResizer is instantiated include
+ // panels and windows that are already docked. Those do not need reparenting.
+ if (GetTarget()->type() != aura::client::WINDOW_TYPE_PANEL &&
+ GetTarget()->parent()->id() == kShellWindowId_DefaultContainer) {
+ // The window is going to be reparented - avoid completing the drag.
+ GetTarget()->SetProperty(kContinueDragAfterReparent, true);
+
+ // Reparent the window into the docked windows container in order to get it
+ // on top of other docked windows.
+ aura::Window* docked_container = Shell::GetContainer(
+ GetTarget()->GetRootWindow(),
+ kShellWindowId_DockedContainer);
+ docked_container->AddChild(GetTarget());
+ }
+ if (is_docked_)
+ dock_layout_->DockDraggedWindow(GetTarget());
+}
+
+void DockedWindowResizer::FinishedDragging() {
+ if (!did_move_or_resize_)
+ return;
+
+ aura::Window* window = GetTarget();
+ bool should_dock = was_docked_;
+ const bool attached_panel =
+ window->type() == aura::client::WINDOW_TYPE_PANEL &&
+ window->GetProperty(kPanelAttachedKey);
+ // If a window was previously docked then keep it docked if it is resized and
+ // still aligned at the screen edge.
+ if ((was_docked_ ||
+ ((details_.bounds_change & WindowResizer::kBoundsChange_Repositions) &&
+ !(details_.bounds_change & WindowResizer::kBoundsChange_Resizes)))) {
+ should_dock = GetDraggedWindowAlignment() != DOCKED_ALIGNMENT_NONE;
+ }
+
+ // Check if the window needs to be docked or returned to workspace.
+ aura::Window* dock_container = Shell::GetContainer(
+ window->GetRootWindow(),
+ kShellWindowId_DockedContainer);
+ if (!attached_panel &&
+ should_dock != (window->parent() == dock_container)) {
+ if (should_dock) {
+ dock_container->AddChild(window);
+ } else if (window->parent()->id() == kShellWindowId_DockedContainer) {
+ // Reparent the window back to workspace.
+ // We need to be careful to give SetDefaultParentByRootWindow location in
+ // the right root window (matching the logic in DragWindowResizer) based
+ // on which root window a mouse pointer is in. We want to undock into the
+ // right screen near the edge of a multiscreen setup (based on where the
+ // mouse is).
+ gfx::Rect near_last_location(last_location_, gfx::Size());
+ // Reparenting will cause Relayout and possible dock shrinking.
+ window->SetDefaultParentByRootWindow(window->GetRootWindow(),
+ near_last_location);
+ }
+ }
+ dock_layout_->FinishDragging();
+
+ // If we started the drag in one root window and moved into another root
+ // but then canceled the drag we may need to inform the original layout
+ // manager that the drag is finished.
+ if (initial_dock_layout_ != dock_layout_)
+ initial_dock_layout_->FinishDragging();
+ is_docked_ = false;
+}
+
+void DockedWindowResizer::UpdateSnapPhantomWindow() {
+ if (!did_move_or_resize_ || details_.window_component != HTCAPTION)
+ return;
+
+ if (!snap_phantom_window_controller_) {
+ snap_phantom_window_controller_.reset(
+ new PhantomWindowController(GetTarget()));
+ }
+ snap_phantom_window_controller_->Show(dock_layout_->dragged_bounds());
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/wm/dock/docked_window_resizer.h b/chromium/ash/wm/dock/docked_window_resizer.h
new file mode 100644
index 00000000000..41bb4fecd5f
--- /dev/null
+++ b/chromium/ash/wm/dock/docked_window_resizer.h
@@ -0,0 +1,111 @@
+// Copyright (c) 2013 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 ASH_WM_DOCK_DOCK_WINDOW_RESIZER_H_
+#define ASH_WM_DOCK_DOCK_WINDOW_RESIZER_H_
+
+#include "ash/wm/dock/dock_types.h"
+#include "ash/wm/window_resizer.h"
+#include "base/compiler_specific.h"
+#include "base/memory/scoped_ptr.h"
+
+namespace gfx {
+class Point;
+class Rect;
+}
+
+namespace aura {
+class RootWindow;
+}
+
+namespace ash {
+namespace internal {
+
+class DockedWindowLayoutManager;
+class PhantomWindowController;
+
+// DockWindowResizer is used by ToplevelWindowEventFilter to handle dragging,
+// moving or resizing of a window while it is docked to the side of a screen.
+class ASH_EXPORT DockedWindowResizer : public WindowResizer {
+ public:
+ virtual ~DockedWindowResizer();
+
+ // Creates a new DockWindowResizer. The caller takes ownership of the
+ // returned object. The ownership of |next_window_resizer| is taken by the
+ // returned object. Returns NULL if not resizable.
+ static DockedWindowResizer* Create(WindowResizer* next_window_resizer,
+ aura::Window* window,
+ const gfx::Point& location,
+ int window_component,
+ aura::client::WindowMoveSource source);
+
+ // WindowResizer:
+ virtual void Drag(const gfx::Point& location, int event_flags) OVERRIDE;
+ virtual void CompleteDrag(int event_flags) OVERRIDE;
+ virtual void RevertDrag() OVERRIDE;
+ virtual aura::Window* GetTarget() OVERRIDE;
+ virtual const gfx::Point& GetInitialLocation() const OVERRIDE;
+
+ private:
+ // Creates DockWindowResizer that adds the ability to attach / detach
+ // windows to / from the dock. This object takes ownership of
+ // |next_window_resizer|.
+ DockedWindowResizer(WindowResizer* next_window_resizer,
+ const Details& details);
+
+ // Returns the side of the screen that the window should dock to or
+ // DOCKED_ALIGNMENT_NONE when the window is not on a side or when some other
+ // windows are already docked on the other side or when launcher (shelf) is
+ // aligned on the same side.
+ DockedAlignment GetDraggedWindowAlignment();
+
+ // Checks if the provided window bounds should snap to the side of a screen.
+ // If so the offset returned gives the necessary adjustment to snap.
+ bool MaybeSnapToEdge(const gfx::Rect& bounds, gfx::Point* offset);
+
+ // Tracks the window's initial position and attachment at the start of a drag
+ // and informs the DockLayoutManager that a drag has started if necessary.
+ void StartedDragging();
+
+ // Informs the DockLayoutManager that the drag is complete if it was informed
+ // of the drag start.
+ void FinishedDragging();
+
+ // Updates the bounds of the phantom window that is used as a docking hint.
+ void UpdateSnapPhantomWindow();
+
+ const Details details_;
+
+ gfx::Point last_location_;
+
+ // Wraps a window resizer and adds detaching / reattaching during drags.
+ scoped_ptr<WindowResizer> next_window_resizer_;
+
+ // Dock container window.
+ internal::DockedWindowLayoutManager* dock_layout_;
+ internal::DockedWindowLayoutManager* initial_dock_layout_;
+
+ // Set to true once Drag() is invoked and the bounds of the window change.
+ bool did_move_or_resize_;
+
+ // Gives a preview of where the the window will end up.
+ scoped_ptr<PhantomWindowController> snap_phantom_window_controller_;
+
+ // Set to true if the window that is being dragged was docked before drag.
+ bool was_docked_;
+
+ // True if the dragged window is docked during the drag.
+ bool is_docked_;
+
+ // If non-NULL the destructor sets this to true. Used to determine if this has
+ // been deleted.
+ bool* destroyed_;
+
+ DISALLOW_COPY_AND_ASSIGN(DockedWindowResizer);
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_WM_DOCK_DOCK_WINDOW_RESIZER_H_
diff --git a/chromium/ash/wm/dock/docked_window_resizer_unittest.cc b/chromium/ash/wm/dock/docked_window_resizer_unittest.cc
new file mode 100644
index 00000000000..49aeca25318
--- /dev/null
+++ b/chromium/ash/wm/dock/docked_window_resizer_unittest.cc
@@ -0,0 +1,1056 @@
+// Copyright (c) 2013 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.
+
+#include "ash/wm/dock/docked_window_resizer.h"
+
+#include "ash/ash_switches.h"
+#include "ash/launcher/launcher.h"
+#include "ash/launcher/launcher_model.h"
+#include "ash/root_window_controller.h"
+#include "ash/screen_ash.h"
+#include "ash/shelf/shelf_layout_manager.h"
+#include "ash/shelf/shelf_types.h"
+#include "ash/shelf/shelf_widget.h"
+#include "ash/shell.h"
+#include "ash/shell_window_ids.h"
+#include "ash/test/ash_test_base.h"
+#include "ash/test/cursor_manager_test_api.h"
+#include "ash/test/shell_test_api.h"
+#include "ash/test/test_launcher_delegate.h"
+#include "ash/wm/dock/docked_window_layout_manager.h"
+#include "ash/wm/drag_window_resizer.h"
+#include "ash/wm/panels/panel_layout_manager.h"
+#include "ash/wm/window_properties.h"
+#include "ash/wm/window_util.h"
+#include "base/command_line.h"
+#include "ui/aura/client/aura_constants.h"
+#include "ui/aura/root_window.h"
+#include "ui/aura/test/test_window_delegate.h"
+#include "ui/base/hit_test.h"
+#include "ui/base/ui_base_types.h"
+#include "ui/views/widget/widget.h"
+
+namespace ash {
+namespace internal {
+
+class DockedWindowResizerTest
+ : public test::AshTestBase,
+ public testing::WithParamInterface<aura::client::WindowType> {
+ public:
+ DockedWindowResizerTest() : model_(NULL), window_type_(GetParam()) {}
+ virtual ~DockedWindowResizerTest() {}
+
+ virtual void SetUp() OVERRIDE {
+ CommandLine::ForCurrentProcess()->AppendSwitch(
+ ash::switches::kAshEnableStickyEdges);
+ CommandLine::ForCurrentProcess()->AppendSwitch(
+ ash::switches::kAshEnableDockedWindows);
+ AshTestBase::SetUp();
+ UpdateDisplay("600x400");
+ test::ShellTestApi test_api(Shell::GetInstance());
+ model_ = test_api.launcher_model();
+ }
+
+ virtual void TearDown() OVERRIDE {
+ AshTestBase::TearDown();
+ }
+
+ protected:
+ enum DockedEdge {
+ DOCKED_EDGE_NONE,
+ DOCKED_EDGE_LEFT,
+ DOCKED_EDGE_RIGHT,
+ };
+
+ aura::Window* CreateTestWindow(const gfx::Rect& bounds) {
+ aura::Window* window = CreateTestWindowInShellWithDelegateAndType(
+ &delegate_,
+ window_type_,
+ 0,
+ bounds);
+ if (window_type_ == aura::client::WINDOW_TYPE_PANEL) {
+ test::TestLauncherDelegate* launcher_delegate =
+ test::TestLauncherDelegate::instance();
+ launcher_delegate->AddLauncherItem(window);
+ PanelLayoutManager* manager =
+ static_cast<PanelLayoutManager*>(
+ Shell::GetContainer(window->GetRootWindow(),
+ internal::kShellWindowId_PanelContainer)->
+ layout_manager());
+ manager->Relayout();
+ }
+ return window;
+ }
+
+ static WindowResizer* CreateSomeWindowResizer(
+ aura::Window* window,
+ const gfx::Point& point_in_parent,
+ int window_component) {
+ return CreateWindowResizer(
+ window,
+ point_in_parent,
+ window_component,
+ aura::client::WINDOW_MOVE_SOURCE_MOUSE).release();
+ }
+
+ void DragStart(aura::Window* window) {
+ initial_location_in_parent_ = window->bounds().origin();
+ resizer_.reset(CreateSomeWindowResizer(window,
+ initial_location_in_parent_,
+ HTCAPTION));
+ ASSERT_TRUE(resizer_.get());
+ }
+
+ void DragStartAtOffsetFromwindowOrigin(aura::Window* window,
+ int dx,
+ int dy) {
+ initial_location_in_parent_ =
+ window->bounds().origin() + gfx::Vector2d(dx, dy);
+ resizer_.reset(CreateSomeWindowResizer(window,
+ initial_location_in_parent_,
+ HTCAPTION));
+ ASSERT_TRUE(resizer_.get());
+ }
+
+ void ResizeStartAtOffsetFromwindowOrigin(aura::Window* window,
+ int dx,
+ int dy,
+ int window_component) {
+ initial_location_in_parent_ =
+ window->bounds().origin() + gfx::Vector2d(dx, dy);
+ resizer_.reset(CreateSomeWindowResizer(window,
+ initial_location_in_parent_,
+ window_component));
+ ASSERT_TRUE(resizer_.get());
+ }
+
+ void DragMove(int dx, int dy) {
+ resizer_->Drag(initial_location_in_parent_ + gfx::Vector2d(dx, dy), 0);
+ }
+
+ void DragEnd() {
+ resizer_->CompleteDrag(0);
+ resizer_.reset();
+ }
+
+ void DragRevert() {
+ resizer_->RevertDrag();
+ resizer_.reset();
+ }
+
+ // Panels are parented by panel container during drags.
+ // All other windows that are tested here are parented by dock container
+ // during drags.
+ int CorrectContainerIdDuringDrag() {
+ if (window_type_ == aura::client::WINDOW_TYPE_PANEL)
+ return internal::kShellWindowId_PanelContainer;
+ return internal::kShellWindowId_DockedContainer;
+ }
+
+ // Test dragging the window vertically (to detach if it is a panel) and then
+ // horizontally to the edge with an added offset from the edge of |dx|.
+ void DragRelativeToEdge(DockedEdge edge,
+ aura::Window* window,
+ int dx) {
+ DragVerticallyAndRelativeToEdge(
+ edge,
+ window,
+ dx,
+ window_type_ == aura::client::WINDOW_TYPE_PANEL ? -100 : 20);
+ }
+
+ void DragToVerticalPositionAndToEdge(DockedEdge edge,
+ aura::Window* window,
+ int y) {
+ DragToVerticalPositionRelativeToEdge(edge, window, 0, y);
+ }
+
+ void DragToVerticalPositionRelativeToEdge(DockedEdge edge,
+ aura::Window* window,
+ int dx,
+ int y) {
+ gfx::Rect initial_bounds = window->GetBoundsInScreen();
+ DragVerticallyAndRelativeToEdge(edge, window, dx, y - initial_bounds.y());
+ }
+
+ // Detach if our window is a panel, then drag it vertically by |dy| and
+ // horizontally to the edge with an added offset from the edge of |dx|.
+ void DragVerticallyAndRelativeToEdge(DockedEdge edge,
+ aura::Window* window,
+ int dx,
+ int dy) {
+ aura::RootWindow* root_window = window->GetRootWindow();
+ gfx::Rect initial_bounds = window->GetBoundsInScreen();
+
+ if (window_type_ == aura::client::WINDOW_TYPE_PANEL) {
+ ASSERT_NO_FATAL_FAILURE(DragStart(window));
+ EXPECT_TRUE(window->GetProperty(kPanelAttachedKey));
+
+ // Drag enough to detach since our tests assume panels to be initially
+ // detached.
+ DragMove(0, dy);
+ EXPECT_EQ(CorrectContainerIdDuringDrag(), window->parent()->id());
+ EXPECT_EQ(initial_bounds.x(), window->GetBoundsInScreen().x());
+ EXPECT_EQ(initial_bounds.y() + dy, window->GetBoundsInScreen().y());
+
+ // The panel should be detached when the drag completes.
+ DragEnd();
+
+ EXPECT_FALSE(window->GetProperty(kPanelAttachedKey));
+ EXPECT_EQ(internal::kShellWindowId_DefaultContainer,
+ window->parent()->id());
+ EXPECT_EQ(root_window, window->GetRootWindow());
+ }
+
+ // avoid snap by clicking away from the border
+ ASSERT_NO_FATAL_FAILURE(DragStartAtOffsetFromwindowOrigin(window, 25, 5));
+
+ // Drag the window left or right to the edge (or almost to it).
+ if (edge == DOCKED_EDGE_LEFT)
+ dx += window->GetRootWindow()->bounds().x() - initial_bounds.x();
+ else if (edge == DOCKED_EDGE_RIGHT)
+ dx += window->GetRootWindow()->bounds().right() - initial_bounds.right();
+ DragMove(dx, window_type_ == aura::client::WINDOW_TYPE_PANEL ? 0 : dy);
+ EXPECT_EQ(CorrectContainerIdDuringDrag(), window->parent()->id());
+ // Release the mouse and the panel should be attached to the dock.
+ DragEnd();
+
+ // x-coordinate can get adjusted by snapping or sticking.
+ // y-coordinate could be changed by possible automatic layout if docked.
+ if (window->parent()->id() != internal::kShellWindowId_DockedContainer)
+ EXPECT_EQ(initial_bounds.y() + dy, window->GetBoundsInScreen().y());
+ }
+
+ bool test_panels() const {
+ return window_type_ == aura::client::WINDOW_TYPE_PANEL;
+ }
+
+ private:
+ scoped_ptr<WindowResizer> resizer_;
+ LauncherModel* model_;
+ aura::client::WindowType window_type_;
+ aura::test::TestWindowDelegate delegate_;
+
+ // Location at start of the drag in |window->parent()|'s coordinates.
+ gfx::Point initial_location_in_parent_;
+
+ DISALLOW_COPY_AND_ASSIGN(DockedWindowResizerTest);
+};
+
+// Verifies a window can be dragged and attached to the dock.
+TEST_P(DockedWindowResizerTest, AttachRightPrecise) {
+ if (!SupportsHostWindowResize())
+ return;
+
+ scoped_ptr<aura::Window> window(CreateTestWindow(gfx::Rect(0, 0, 201, 201)));
+ DragRelativeToEdge(DOCKED_EDGE_RIGHT, window.get(), 0);
+
+ // The window should be attached and snapped to the right edge.
+ EXPECT_EQ(window->GetRootWindow()->bounds().right(),
+ window->GetBoundsInScreen().right());
+ EXPECT_EQ(internal::kShellWindowId_DockedContainer, window->parent()->id());
+}
+
+// Verifies a window can be dragged and attached to the dock
+// even if we overshoot the screen edge by a few pixels (sticky edge)
+TEST_P(DockedWindowResizerTest, AttachRightOvershoot) {
+ if (!SupportsHostWindowResize())
+ return;
+
+ scoped_ptr<aura::Window> window(CreateTestWindow(gfx::Rect(0, 0, 201, 201)));
+ DragRelativeToEdge(DOCKED_EDGE_RIGHT, window.get(), +4);
+
+ // The window should be attached and snapped to the right edge.
+ EXPECT_EQ(window->GetRootWindow()->bounds().right(),
+ window->GetBoundsInScreen().right());
+ EXPECT_EQ(internal::kShellWindowId_DockedContainer, window->parent()->id());
+}
+
+// Verifies a window can be dragged and then if not quite reaching the screen
+// edge it does not get docked to a screen edge and stays in the desktop.
+TEST_P(DockedWindowResizerTest, AttachRightUndershoot) {
+ if (!SupportsHostWindowResize())
+ return;
+
+ scoped_ptr<aura::Window> window(CreateTestWindow(gfx::Rect(0, 0, 201, 201)));
+ DragRelativeToEdge(DOCKED_EDGE_RIGHT, window.get(), -1);
+
+ // The window should not be attached to the dock.
+ EXPECT_EQ(window->GetRootWindow()->bounds().right() - 1,
+ window->GetBoundsInScreen().right());
+ EXPECT_EQ(internal::kShellWindowId_DefaultContainer,
+ window->parent()->id());
+}
+
+// Verifies a window can be dragged and attached to the dock.
+TEST_P(DockedWindowResizerTest, AttachLeftPrecise) {
+ if (!SupportsHostWindowResize())
+ return;
+
+ scoped_ptr<aura::Window> window(CreateTestWindow(gfx::Rect(0, 0, 201, 201)));
+ DragRelativeToEdge(DOCKED_EDGE_LEFT, window.get(), 0);
+
+ // The window should be attached and snapped to the left dock.
+ EXPECT_EQ(window->GetRootWindow()->bounds().x(),
+ window->GetBoundsInScreen().x());
+ EXPECT_EQ(internal::kShellWindowId_DockedContainer, window->parent()->id());
+}
+
+// Verifies a window can be dragged and attached to the dock
+// even if we overshoot the screen edge by a few pixels (sticky edge)
+TEST_P(DockedWindowResizerTest, AttachLeftOvershoot) {
+ if (!SupportsHostWindowResize())
+ return;
+
+ scoped_ptr<aura::Window> window(CreateTestWindow(gfx::Rect(0, 0, 201, 201)));
+ DragRelativeToEdge(DOCKED_EDGE_LEFT, window.get(), -4);
+
+ // The window should be attached and snapped to the left dock.
+ EXPECT_EQ(window->GetRootWindow()->bounds().x(),
+ window->GetBoundsInScreen().x());
+ EXPECT_EQ(internal::kShellWindowId_DockedContainer, window->parent()->id());
+}
+
+// Verifies a window can be dragged and then if not quite reaching the screen
+// edge it does not get docked to a screen edge and stays in the desktop.
+TEST_P(DockedWindowResizerTest, AttachLeftUndershoot) {
+ if (!SupportsHostWindowResize())
+ return;
+
+ scoped_ptr<aura::Window> window(CreateTestWindow(gfx::Rect(0, 0, 201, 201)));
+ DragRelativeToEdge(DOCKED_EDGE_LEFT, window.get(), 1);
+
+ // The window should not be attached to the dock.
+ EXPECT_EQ(window->GetRootWindow()->bounds().x() + 1,
+ window->GetBoundsInScreen().x());
+ EXPECT_EQ(internal::kShellWindowId_DefaultContainer,
+ window->parent()->id());
+}
+
+// Dock on the right side, change shelf alignment, check that windows move to
+// the opposite side.
+TEST_P(DockedWindowResizerTest, AttachRightChangeShelf) {
+ if (!SupportsHostWindowResize())
+ return;
+
+ scoped_ptr<aura::Window> window(CreateTestWindow(gfx::Rect(0, 0, 201, 201)));
+ DragRelativeToEdge(DOCKED_EDGE_RIGHT, window.get(), 0);
+
+ // The window should be attached and snapped to the right edge.
+ EXPECT_EQ(window->GetRootWindow()->bounds().right(),
+ window->GetBoundsInScreen().right());
+ EXPECT_EQ(internal::kShellWindowId_DockedContainer, window->parent()->id());
+
+ // set launcher shelf to be aligned on the right
+ ash::Shell* shell = ash::Shell::GetInstance();
+ shell->SetShelfAlignment(SHELF_ALIGNMENT_RIGHT,
+ shell->GetPrimaryRootWindow());
+ // The window should have moved and get attached to the left dock.
+ EXPECT_EQ(window->GetRootWindow()->bounds().x(),
+ window->GetBoundsInScreen().x());
+ EXPECT_EQ(internal::kShellWindowId_DockedContainer, window->parent()->id());
+
+ // set launcher shelf to be aligned on the left
+ shell->SetShelfAlignment(SHELF_ALIGNMENT_LEFT,
+ shell->GetPrimaryRootWindow());
+ // The window should have moved and get attached to the right edge.
+ EXPECT_EQ(window->GetRootWindow()->bounds().right(),
+ window->GetBoundsInScreen().right());
+ EXPECT_EQ(internal::kShellWindowId_DockedContainer, window->parent()->id());
+
+ // set launcher shelf to be aligned at the bottom
+ shell->SetShelfAlignment(SHELF_ALIGNMENT_BOTTOM,
+ shell->GetPrimaryRootWindow());
+ // The window should stay in the right edge.
+ EXPECT_EQ(window->GetRootWindow()->bounds().right(),
+ window->GetBoundsInScreen().right());
+ EXPECT_EQ(internal::kShellWindowId_DockedContainer, window->parent()->id());
+}
+
+// Dock on the right side, try to undock, then drag more to really undock
+TEST_P(DockedWindowResizerTest, AttachTryDetach) {
+ if (!SupportsHostWindowResize())
+ return;
+
+ scoped_ptr<aura::Window> window(CreateTestWindow(gfx::Rect(0, 0, 201, 201)));
+ DragRelativeToEdge(DOCKED_EDGE_RIGHT, window.get(), 0);
+
+ // The window should be attached and snapped to the right edge.
+ EXPECT_EQ(window->GetRootWindow()->bounds().right(),
+ window->GetBoundsInScreen().right());
+ EXPECT_EQ(internal::kShellWindowId_DockedContainer, window->parent()->id());
+
+ // Try to detach by dragging left less than kSnapToDockDistance.
+ // The window should stay docked.
+ ASSERT_NO_FATAL_FAILURE(DragStart(window.get()));
+ DragMove(-4, -10);
+ // Release the mouse and the window should be still attached to the dock.
+ DragEnd();
+
+ // The window should be still attached to the right edge.
+ EXPECT_EQ(window->GetRootWindow()->bounds().right(),
+ window->GetBoundsInScreen().right());
+ EXPECT_EQ(internal::kShellWindowId_DockedContainer, window->parent()->id());
+
+ // Try to detach by dragging left by kSnapToDockDistance or more.
+ // The window should get undocked.
+ ASSERT_NO_FATAL_FAILURE(DragStart(window.get()));
+ DragMove(-32, -10);
+ // Release the mouse and the window should be no longer attached to the dock.
+ DragEnd();
+
+ // The window should be floating on the desktop again.
+ EXPECT_EQ(window->GetRootWindow()->bounds().right() - 32,
+ window->GetBoundsInScreen().right());
+ EXPECT_EQ(internal::kShellWindowId_DefaultContainer,
+ window->parent()->id());
+}
+
+// Minimize a docked window, then restore it and check that it is still docked.
+TEST_P(DockedWindowResizerTest, AttachMinimizeRestore) {
+ if (!SupportsHostWindowResize())
+ return;
+
+ scoped_ptr<aura::Window> window(CreateTestWindow(gfx::Rect(0, 0, 201, 201)));
+ DragRelativeToEdge(DOCKED_EDGE_RIGHT, window.get(), 0);
+
+ // The window should be attached and snapped to the right edge.
+ EXPECT_EQ(window->GetRootWindow()->bounds().right(),
+ window->GetBoundsInScreen().right());
+ EXPECT_EQ(internal::kShellWindowId_DockedContainer, window->parent()->id());
+
+ // Minimize the window, it should be hidden.
+ window->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MINIMIZED);
+ RunAllPendingInMessageLoop();
+ EXPECT_FALSE(window->IsVisible());
+ // Restore the window; window should be visible.
+ window->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_NORMAL);
+ RunAllPendingInMessageLoop();
+ EXPECT_TRUE(window->IsVisible());
+}
+
+// Dock two windows, undock one, check that the other one is still docked.
+TEST_P(DockedWindowResizerTest, AttachTwoWindows)
+{
+ if (!SupportsHostWindowResize())
+ return;
+
+ scoped_ptr<aura::Window> w1(CreateTestWindow(gfx::Rect(0, 0, 201, 201)));
+ scoped_ptr<aura::Window> w2(CreateTestWindow(gfx::Rect(0, 0, 201, 201)));
+ DragToVerticalPositionAndToEdge(DOCKED_EDGE_RIGHT, w1.get(), 20);
+ DragToVerticalPositionAndToEdge(DOCKED_EDGE_RIGHT, w2.get(), 50);
+
+ // Both windows should be attached and snapped to the right edge.
+ EXPECT_EQ(w1->GetRootWindow()->bounds().right(),
+ w1->GetBoundsInScreen().right());
+ EXPECT_EQ(internal::kShellWindowId_DockedContainer, w1->parent()->id());
+
+ EXPECT_EQ(w2->GetRootWindow()->bounds().right(),
+ w2->GetBoundsInScreen().right());
+ EXPECT_EQ(internal::kShellWindowId_DockedContainer, w2->parent()->id());
+
+ // Detach by dragging left (should get undocked).
+ ASSERT_NO_FATAL_FAILURE(DragStart(w2.get()));
+ // Drag up as well to avoid attaching panels to launcher shelf.
+ DragMove(-32, -100);
+ // Release the mouse and the window should be no longer attached to the edge.
+ DragEnd();
+
+ // The first window should be still docked.
+ EXPECT_EQ(w1->GetRootWindow()->bounds().right(),
+ w1->GetBoundsInScreen().right());
+ EXPECT_EQ(internal::kShellWindowId_DockedContainer, w1->parent()->id());
+
+ // The second window should be floating on the desktop again.
+ EXPECT_EQ(w2->GetRootWindow()->bounds().right() - 32,
+ w2->GetBoundsInScreen().right());
+ EXPECT_EQ(internal::kShellWindowId_DefaultContainer,
+ w2->parent()->id());
+}
+
+// Dock one window, try to dock another window on the opposite side (should not
+// dock).
+TEST_P(DockedWindowResizerTest, AttachOnTwoSides)
+{
+ if (!SupportsHostWindowResize())
+ return;
+
+ scoped_ptr<aura::Window> w1(CreateTestWindow(gfx::Rect(0, 0, 201, 201)));
+ scoped_ptr<aura::Window> w2(CreateTestWindow(gfx::Rect(0, 0, 201, 201)));
+ DragToVerticalPositionAndToEdge(DOCKED_EDGE_RIGHT, w1.get(), 20);
+ DragToVerticalPositionAndToEdge(DOCKED_EDGE_LEFT, w2.get(), 50);
+
+ // The first window should be attached and snapped to the right edge.
+ EXPECT_EQ(w1->GetRootWindow()->bounds().right(),
+ w1->GetBoundsInScreen().right());
+ EXPECT_EQ(internal::kShellWindowId_DockedContainer, w1->parent()->id());
+
+ // The second window should be near the left edge but not snapped.
+ EXPECT_EQ(w2->GetRootWindow()->bounds().x(), w2->GetBoundsInScreen().x());
+ EXPECT_EQ(internal::kShellWindowId_DefaultContainer, w2->parent()->id());
+}
+
+// Reverting drag
+TEST_P(DockedWindowResizerTest, RevertDragRestoresAttachment) {
+ if (!SupportsHostWindowResize())
+ return;
+
+ scoped_ptr<aura::Window> window(CreateTestWindow(gfx::Rect(0, 0, 201, 201)));
+ DragRelativeToEdge(DOCKED_EDGE_RIGHT, window.get(), 0);
+
+ // The window should be attached and snapped to the right edge.
+ EXPECT_EQ(window->GetRootWindow()->bounds().right(),
+ window->GetBoundsInScreen().right());
+ EXPECT_EQ(internal::kShellWindowId_DockedContainer, window->parent()->id());
+
+ // Drag the window out but revert the drag
+ ASSERT_NO_FATAL_FAILURE(DragStart(window.get()));
+ DragMove(-50, 0);
+ DragRevert();
+ EXPECT_EQ(internal::kShellWindowId_DockedContainer, window->parent()->id());
+
+ // Detach window.
+ ASSERT_NO_FATAL_FAILURE(DragStart(window.get()));
+ DragMove(-50, 0);
+ DragEnd();
+ EXPECT_EQ(internal::kShellWindowId_DefaultContainer,
+ window->parent()->id());
+}
+
+// Move a docked window to the second display
+TEST_P(DockedWindowResizerTest, DragAcrossDisplays) {
+ if (!SupportsMultipleDisplays())
+ return;
+
+ UpdateDisplay("800x800,800x800");
+ Shell::RootWindowList root_windows = Shell::GetAllRootWindows();
+ scoped_ptr<aura::Window> window(CreateTestWindow(gfx::Rect(0, 0, 201, 201)));
+ gfx::Rect initial_bounds = window->GetBoundsInScreen();
+ EXPECT_EQ(root_windows[0], window->GetRootWindow());
+
+ DragRelativeToEdge(DOCKED_EDGE_RIGHT, window.get(), 0);
+ // The window should be attached and snapped to the right edge.
+ EXPECT_EQ(window->GetRootWindow()->bounds().right(),
+ window->GetBoundsInScreen().right());
+ EXPECT_EQ(internal::kShellWindowId_DockedContainer, window->parent()->id());
+
+ // Undock and move to the right - enough to get it peeking at the other screen
+ // but not enough to land in the other screen
+ ASSERT_NO_FATAL_FAILURE(DragStart(window.get()));
+ DragMove(70, 0);
+ EXPECT_EQ(CorrectContainerIdDuringDrag(), window->parent()->id());
+ DragEnd();
+ EXPECT_NE(window->GetRootWindow()->bounds().right(),
+ window->GetBoundsInScreen().right());
+ EXPECT_EQ(internal::kShellWindowId_DefaultContainer,
+ window->parent()->id());
+ EXPECT_EQ(root_windows[0], window->GetRootWindow());
+
+ // Move back left - should dock again.
+ ASSERT_NO_FATAL_FAILURE(DragStart(window.get()));
+ DragMove(-70, 0);
+ EXPECT_EQ(CorrectContainerIdDuringDrag(), window->parent()->id());
+ DragEnd();
+ EXPECT_EQ(window->GetRootWindow()->bounds().right(),
+ window->GetBoundsInScreen().right());
+ EXPECT_EQ(internal::kShellWindowId_DockedContainer,
+ window->parent()->id());
+ EXPECT_EQ(root_windows[0], window->GetRootWindow());
+
+ // Undock and move to the right - enough to get the mouse pointer past the
+ // edge of the screen and into the second screen. The window should now be
+ // in the second screen and not docked.
+ ASSERT_NO_FATAL_FAILURE(DragStartAtOffsetFromwindowOrigin(
+ window.get(),
+ window->bounds().width()/2 + 10,
+ 0));
+ DragMove(window->bounds().width()/2 - 5, 0);
+ EXPECT_EQ(CorrectContainerIdDuringDrag(), window->parent()->id());
+ DragEnd();
+ EXPECT_NE(window->GetRootWindow()->bounds().right(),
+ window->GetBoundsInScreen().right());
+ EXPECT_EQ(internal::kShellWindowId_DefaultContainer,
+ window->parent()->id());
+ EXPECT_EQ(root_windows[1], window->GetRootWindow());
+
+ // Keep dragging it to the right until it docks. The window should now be
+ // in the second screen.
+ ASSERT_NO_FATAL_FAILURE(DragStartAtOffsetFromwindowOrigin(
+ window.get(),
+ window->bounds().width()/2 + 10,
+ 0));
+ DragMove(window->GetRootWindow()->GetBoundsInScreen().x() -
+ window->GetBoundsInScreen().x(),
+ 0);
+ EXPECT_EQ(CorrectContainerIdDuringDrag(), window->parent()->id());
+ DragEnd();
+ EXPECT_EQ(window->GetRootWindow()->GetBoundsInScreen().x(),
+ window->GetBoundsInScreen().x());
+ EXPECT_EQ(internal::kShellWindowId_DockedContainer, window->parent()->id());
+ EXPECT_EQ(root_windows[1], window->GetRootWindow());
+}
+
+// Dock two windows, undock one.
+// Test the docked windows area size and default container resizing.
+TEST_P(DockedWindowResizerTest, AttachTwoWindowsDetachOne)
+{
+ if (!SupportsHostWindowResize())
+ return;
+
+ scoped_ptr<aura::Window> w1(CreateTestWindow(gfx::Rect(0, 0, 201, 201)));
+ scoped_ptr<aura::Window> w2(CreateTestWindow(gfx::Rect(0, 0, 210, 201)));
+ // Work area should cover the whole screen.
+ EXPECT_EQ(ScreenAsh::GetDisplayBoundsInParent(w2.get()).width(),
+ ScreenAsh::GetDisplayWorkAreaBoundsInParent(w2.get()).width());
+
+ DragToVerticalPositionAndToEdge(DOCKED_EDGE_RIGHT, w1.get(), 20);
+ // A window should be attached and snapped to the right edge.
+ EXPECT_EQ(w1->GetRootWindow()->bounds().right(),
+ w1->GetBoundsInScreen().right());
+ EXPECT_EQ(internal::kShellWindowId_DockedContainer, w1->parent()->id());
+ DockedWindowLayoutManager* manager =
+ static_cast<DockedWindowLayoutManager*>(w1->parent()->layout_manager());
+ EXPECT_EQ(DOCKED_ALIGNMENT_RIGHT, manager->alignment_);
+ EXPECT_EQ(w1->bounds().width(), manager->docked_width_);
+
+ DragToVerticalPositionRelativeToEdge(DOCKED_EDGE_RIGHT, w2.get(), 0, 100);
+ // Both windows should now be attached and snapped to the right edge.
+ EXPECT_EQ(w2->GetRootWindow()->bounds().right(),
+ w2->GetBoundsInScreen().right());
+ EXPECT_EQ(internal::kShellWindowId_DockedContainer, w2->parent()->id());
+ // Dock width should be set to a wider window.
+ EXPECT_EQ(DOCKED_ALIGNMENT_RIGHT, manager->alignment_);
+ EXPECT_EQ(std::max(w1->bounds().width(), w2->bounds().width()),
+ manager->docked_width_);
+
+ // Try to detach by dragging left a bit (should not get undocked).
+ // This would normally detach a single docked window but since we have another
+ // window and the mouse pointer does not leave the dock area the window
+ // should stay docked.
+ ASSERT_NO_FATAL_FAILURE(DragStartAtOffsetFromwindowOrigin(w2.get(), 60, 0));
+ // Drag up as well as left to avoid attaching panels to launcher shelf.
+ DragMove(-40, -40);
+ // Release the mouse and the window should be still attached to the edge.
+ DragEnd();
+
+ // The first window should be still docked.
+ EXPECT_EQ(w1->GetRootWindow()->bounds().right(),
+ w1->GetBoundsInScreen().right());
+ EXPECT_EQ(internal::kShellWindowId_DockedContainer, w1->parent()->id());
+
+ // The second window should be still docked.
+ EXPECT_EQ(w2->GetRootWindow()->bounds().right(),
+ w2->GetBoundsInScreen().right());
+ EXPECT_EQ(internal::kShellWindowId_DockedContainer, w2->parent()->id());
+
+ // Detach by dragging left more (should get undocked).
+ ASSERT_NO_FATAL_FAILURE(DragStartAtOffsetFromwindowOrigin(
+ w2.get(),
+ w2->bounds().width()/2 + 10,
+ 0));
+ // Drag up as well to avoid attaching panels to launcher shelf.
+ DragMove(-(w2->bounds().width()/2 + 20), -100);
+ // Release the mouse and the window should be no longer attached to the edge.
+ DragEnd();
+
+ // The second window should be floating on the desktop again.
+ EXPECT_EQ(w2->GetRootWindow()->bounds().right() -
+ (w2->bounds().width()/2 + 20),
+ w2->GetBoundsInScreen().right());
+ EXPECT_EQ(internal::kShellWindowId_DefaultContainer, w2->parent()->id());
+ // Dock width should be set to remaining single docked window.
+ EXPECT_EQ(internal::kShellWindowId_DockedContainer, w1->parent()->id());
+ EXPECT_EQ(DOCKED_ALIGNMENT_RIGHT, manager->alignment_);
+ EXPECT_EQ(w1->bounds().width(), manager->docked_width_);
+}
+
+// Dock one windows. Maximize other testing desktop resizing.
+TEST_P(DockedWindowResizerTest, AttachWindowMaximizeOther)
+{
+ if (!SupportsHostWindowResize())
+ return;
+
+ scoped_ptr<aura::Window> w1(CreateTestWindow(gfx::Rect(0, 0, 201, 201)));
+ scoped_ptr<aura::Window> w2(CreateTestWindow(gfx::Rect(0, 0, 210, 201)));
+ // Work area should cover the whole screen.
+ EXPECT_EQ(ScreenAsh::GetDisplayBoundsInParent(w2.get()).width(),
+ ScreenAsh::GetDisplayWorkAreaBoundsInParent(w2.get()).width());
+
+ DragToVerticalPositionAndToEdge(DOCKED_EDGE_RIGHT, w1.get(), 20);
+ // A window should be attached and snapped to the right edge.
+ EXPECT_EQ(w1->GetRootWindow()->bounds().right(),
+ w1->GetBoundsInScreen().right());
+ EXPECT_EQ(internal::kShellWindowId_DockedContainer, w1->parent()->id());
+ DockedWindowLayoutManager* manager =
+ static_cast<DockedWindowLayoutManager*>(w1->parent()->layout_manager());
+ EXPECT_EQ(DOCKED_ALIGNMENT_RIGHT, manager->alignment_);
+ EXPECT_EQ(w1->bounds().width(), manager->docked_width_);
+
+ DragToVerticalPositionRelativeToEdge(DOCKED_EDGE_RIGHT,
+ w2.get(),
+ -(w2->bounds().width()/2 + 20),
+ 50);
+ // The first window should be still docked.
+ EXPECT_EQ(w1->GetRootWindow()->bounds().right(),
+ w1->GetBoundsInScreen().right());
+ EXPECT_EQ(internal::kShellWindowId_DockedContainer, w1->parent()->id());
+
+ // The second window should be floating on the desktop.
+ EXPECT_EQ(w2->GetRootWindow()->bounds().right() -
+ (w2->bounds().width()/2 + 20),
+ w2->GetBoundsInScreen().right());
+ EXPECT_EQ(internal::kShellWindowId_DefaultContainer, w2->parent()->id());
+ // Dock width should be set to remaining single docked window.
+ EXPECT_EQ(internal::kShellWindowId_DockedContainer, w1->parent()->id());
+ EXPECT_EQ(DOCKED_ALIGNMENT_RIGHT, manager->alignment_);
+ EXPECT_EQ(w1->bounds().width(), manager->docked_width_);
+ // Desktop work area should now shrink.
+ EXPECT_EQ(ScreenAsh::GetDisplayBoundsInParent(w2.get()).width() -
+ manager->docked_width_ - DockedWindowLayoutManager::kMinDockGap,
+ ScreenAsh::GetDisplayWorkAreaBoundsInParent(w2.get()).width());
+
+ // Maximize the second window - Maximized area should be shrunk.
+ const gfx::Rect restored_bounds = w2->bounds();
+ wm::MaximizeWindow(w2.get());
+ EXPECT_EQ(ScreenAsh::GetDisplayBoundsInParent(w2.get()).width() -
+ manager->docked_width_ - DockedWindowLayoutManager::kMinDockGap,
+ w2->bounds().width());
+
+ // Detach the first window (this should require very little drag).
+ ASSERT_NO_FATAL_FAILURE(DragStart(w1.get()));
+ EXPECT_EQ(DOCKED_ALIGNMENT_RIGHT, manager->alignment_);
+ DragMove(-35, 10);
+ // Alignment is set to "NONE" when drag starts.
+ EXPECT_EQ(DOCKED_ALIGNMENT_NONE, manager->alignment_);
+ // Release the mouse and the window should be no longer attached to the edge.
+ DragEnd();
+ EXPECT_EQ(DOCKED_ALIGNMENT_NONE, manager->alignment_);
+ // Dock should get shrunk and desktop should get expanded.
+ EXPECT_EQ(internal::kShellWindowId_DefaultContainer, w1->parent()->id());
+ EXPECT_EQ(internal::kShellWindowId_DefaultContainer, w2->parent()->id());
+ EXPECT_EQ(DOCKED_ALIGNMENT_NONE, manager->alignment_);
+ EXPECT_EQ(0, manager->docked_width_);
+ // The second window should now get resized and take up the whole screen.
+ EXPECT_EQ(ScreenAsh::GetDisplayBoundsInParent(w2.get()).width(),
+ w2->bounds().width());
+
+ // Dock the first window to the left edge.
+ // Click at an offset from origin to prevent snapping.
+ ASSERT_NO_FATAL_FAILURE(DragStartAtOffsetFromwindowOrigin(w1.get(), 10, 0));
+ DragMove(-w1->bounds().x(), 0);
+ // Alignment set to "NONE" during the drag of the window when none are docked.
+ EXPECT_EQ(DOCKED_ALIGNMENT_NONE, manager->alignment_);
+ // Release the mouse and the window should be now attached to the edge.
+ DragEnd();
+ // Dock should get expanded and desktop should get shrunk.
+ EXPECT_EQ(internal::kShellWindowId_DockedContainer, w1->parent()->id());
+ EXPECT_EQ(DOCKED_ALIGNMENT_LEFT, manager->alignment_);
+ EXPECT_EQ(w1->bounds().width(), manager->docked_width_);
+ // Second window should still be in the desktop.
+ EXPECT_EQ(internal::kShellWindowId_DefaultContainer, w2->parent()->id());
+ // Maximized window should be shrunk.
+ EXPECT_EQ(ScreenAsh::GetDisplayBoundsInParent(w2.get()).width() -
+ manager->docked_width_ - DockedWindowLayoutManager::kMinDockGap,
+ w2->bounds().width());
+
+ // Unmaximize the second window.
+ wm::RestoreWindow(w2.get());
+ // Its bounds should get restored.
+ EXPECT_EQ(restored_bounds, w2->bounds());
+}
+
+// Dock one window. Test the sticky behavior near screen or desktop edge.
+TEST_P(DockedWindowResizerTest, AttachOneTestSticky)
+{
+ if (!SupportsHostWindowResize())
+ return;
+
+ scoped_ptr<aura::Window> w1(CreateTestWindow(gfx::Rect(0, 0, 201, 201)));
+ scoped_ptr<aura::Window> w2(CreateTestWindow(gfx::Rect(0, 0, 210, 201)));
+ // Work area should cover the whole screen.
+ EXPECT_EQ(ScreenAsh::GetDisplayBoundsInParent(w2.get()).width(),
+ ScreenAsh::GetDisplayWorkAreaBoundsInParent(w2.get()).width());
+
+ DragToVerticalPositionAndToEdge(DOCKED_EDGE_LEFT, w1.get(), 20);
+ // A window should be attached and snapped to the left edge.
+ EXPECT_EQ(w1->GetRootWindow()->bounds().x(),
+ w1->GetBoundsInScreen().x());
+ EXPECT_EQ(internal::kShellWindowId_DockedContainer, w1->parent()->id());
+ DockedWindowLayoutManager* manager =
+ static_cast<DockedWindowLayoutManager*>(w1->parent()->layout_manager());
+ // The first window should be docked.
+ EXPECT_EQ(w1->GetRootWindow()->bounds().x(),
+ w1->GetBoundsInScreen().x());
+ // Dock width should be set to that of a single docked window.
+ EXPECT_EQ(internal::kShellWindowId_DockedContainer, w1->parent()->id());
+ EXPECT_EQ(DOCKED_ALIGNMENT_LEFT, manager->alignment_);
+ EXPECT_EQ(w1->bounds().width(), manager->docked_width_);
+
+ // Position second window in the desktop just to the right of the docked w1.
+ DragToVerticalPositionRelativeToEdge(DOCKED_EDGE_LEFT,
+ w2.get(),
+ w1->bounds().right() + 20,
+ 50);
+ // The second window should be floating on the desktop.
+ EXPECT_EQ(w2->GetRootWindow()->bounds().x() + (w1->bounds().right() + 20),
+ w2->GetBoundsInScreen().x());
+ EXPECT_EQ(internal::kShellWindowId_DefaultContainer, w2->parent()->id());
+ // Dock width should be set to that of a single docked window.
+ EXPECT_EQ(internal::kShellWindowId_DockedContainer, w1->parent()->id());
+ EXPECT_EQ(DOCKED_ALIGNMENT_LEFT, manager->alignment_);
+ EXPECT_EQ(w1->bounds().width(), manager->docked_width_);
+
+ // Drag w2 almost to the dock, the mouse pointer not quite reaching the dock.
+ ASSERT_NO_FATAL_FAILURE(DragStartAtOffsetFromwindowOrigin(w2.get(), 10, 0));
+ DragMove(1 + manager->docked_width_ - w2->bounds().x(), 0);
+ // Alignment set to "LEFT" during the drag because dock has a window in it.
+ EXPECT_EQ(DOCKED_ALIGNMENT_LEFT, manager->alignment_);
+ // Release the mouse and the window should not be attached to the edge.
+ DragEnd();
+ // Dock should still have only one window in it.
+ EXPECT_EQ(DOCKED_ALIGNMENT_LEFT, manager->alignment_);
+ EXPECT_EQ(w1->bounds().width(), manager->docked_width_);
+ // The second window should still be in the desktop.
+ EXPECT_EQ(internal::kShellWindowId_DockedContainer, w1->parent()->id());
+ EXPECT_EQ(internal::kShellWindowId_DefaultContainer, w2->parent()->id());
+
+ // Drag w2 by a bit more - it should resist the drag (stuck edges)
+ int start_x = w2->bounds().x();
+ ASSERT_NO_FATAL_FAILURE(DragStartAtOffsetFromwindowOrigin(w2.get(), 100, 5));
+ DragMove(-2, 0);
+ // Window should not actually move.
+ EXPECT_EQ(start_x, w2->bounds().x());
+ // Alignment set to "LEFT" during the drag because dock has a window in it.
+ EXPECT_EQ(DOCKED_ALIGNMENT_LEFT, manager->alignment_);
+ // Release the mouse and the window should not be attached to the edge.
+ DragEnd();
+ // Window should be still where it was before the last drag started.
+ EXPECT_EQ(start_x, w2->bounds().x());
+ // Dock should still have only one window in it
+ EXPECT_EQ(DOCKED_ALIGNMENT_LEFT, manager->alignment_);
+ EXPECT_EQ(w1->bounds().width(), manager->docked_width_);
+ // The second window should still be in the desktop
+ EXPECT_EQ(internal::kShellWindowId_DockedContainer, w1->parent()->id());
+ EXPECT_EQ(internal::kShellWindowId_DefaultContainer, w2->parent()->id());
+
+ // Drag w2 by more than the stuck threshold and drop it into the dock.
+ ASSERT_NO_FATAL_FAILURE(DragStart(w2.get()));
+ DragMove(-100, 0);
+ // Window should actually move.
+ EXPECT_NE(start_x, w2->bounds().x());
+ // Alignment set to "LEFT" during the drag because dock has a window in it.
+ EXPECT_EQ(DOCKED_ALIGNMENT_LEFT, manager->alignment_);
+ // Release the mouse and the window should be attached to the edge.
+ DragEnd();
+ // Both windows are docked now.
+ EXPECT_EQ(internal::kShellWindowId_DockedContainer, w1->parent()->id());
+ EXPECT_EQ(internal::kShellWindowId_DockedContainer, w2->parent()->id());
+ // Dock should get expanded and desktop should get shrunk.
+ EXPECT_EQ(DOCKED_ALIGNMENT_LEFT, manager->alignment_);
+ EXPECT_EQ(std::max(w1->bounds().width(), w2->bounds().width()),
+ manager->docked_width_);
+ // Desktop work area should now shrink by dock width.
+ EXPECT_EQ(ScreenAsh::GetDisplayBoundsInParent(w2.get()).width() -
+ manager->docked_width_ - DockedWindowLayoutManager::kMinDockGap,
+ ScreenAsh::GetDisplayWorkAreaBoundsInParent(w2.get()).width());
+}
+
+// Dock two windows, resize one or both.
+// Test the docked windows area size and remaining desktop resizing.
+TEST_P(DockedWindowResizerTest, ResizeTwoWindows)
+{
+ if (!SupportsHostWindowResize())
+ return;
+
+ // Wider display to start since panels are limited to half the display width.
+ UpdateDisplay("1000x400");
+ scoped_ptr<aura::Window> w1(CreateTestWindow(gfx::Rect(0, 0, 201, 201)));
+ scoped_ptr<aura::Window> w2(CreateTestWindow(gfx::Rect(0, 0, 210, 201)));
+ // Work area should cover the whole screen.
+ EXPECT_EQ(ScreenAsh::GetDisplayBoundsInParent(w2.get()).width(),
+ ScreenAsh::GetDisplayWorkAreaBoundsInParent(w2.get()).width());
+
+ DragToVerticalPositionAndToEdge(DOCKED_EDGE_RIGHT, w1.get(), 20);
+ // A window should be attached and snapped to the right edge.
+ EXPECT_EQ(w1->GetRootWindow()->bounds().right(),
+ w1->GetBoundsInScreen().right());
+ EXPECT_EQ(internal::kShellWindowId_DockedContainer, w1->parent()->id());
+ DockedWindowLayoutManager* manager =
+ static_cast<DockedWindowLayoutManager*>(w1->parent()->layout_manager());
+ EXPECT_EQ(DOCKED_ALIGNMENT_RIGHT, manager->alignment_);
+ EXPECT_EQ(w1->bounds().width(), manager->docked_width_);
+
+ DragToVerticalPositionRelativeToEdge(DOCKED_EDGE_RIGHT, w2.get(), 0, 100);
+ // Both windows should now be attached and snapped to the right edge.
+ EXPECT_EQ(w2->GetRootWindow()->bounds().right(),
+ w2->GetBoundsInScreen().right());
+ EXPECT_EQ(internal::kShellWindowId_DockedContainer, w2->parent()->id());
+ // Dock width should be set to a wider window.
+ EXPECT_EQ(DOCKED_ALIGNMENT_RIGHT, manager->alignment_);
+ EXPECT_EQ(std::max(w1->bounds().width(), w2->bounds().width()),
+ manager->docked_width_);
+
+ // Resize the first window left by a bit and test that the dock expands.
+ int previous_width = w1->bounds().width();
+ const int kResizeSpan1 = 30;
+ ASSERT_NO_FATAL_FAILURE(ResizeStartAtOffsetFromwindowOrigin(w1.get(),
+ 0,
+ 20,
+ HTLEFT));
+ DragMove(-kResizeSpan1, 0);
+ // Alignment set to "RIGHT" during the drag because dock has a window in it.
+ EXPECT_EQ(DOCKED_ALIGNMENT_RIGHT, manager->alignment_);
+ // Release the mouse and the window should be attached to the edge.
+ DragEnd();
+ // Dock should still have both windows in it.
+ EXPECT_EQ(internal::kShellWindowId_DockedContainer, w1->parent()->id());
+ EXPECT_EQ(internal::kShellWindowId_DockedContainer, w2->parent()->id());
+ EXPECT_EQ(DOCKED_ALIGNMENT_RIGHT, manager->alignment_);
+ // w1 is now wider than w2 and the dock should expand and be as wide as w1.
+ EXPECT_EQ(previous_width + kResizeSpan1, w1->bounds().width());
+ EXPECT_GT(w1->bounds().width(), w2->bounds().width());
+ EXPECT_EQ(w1->bounds().width(), manager->docked_width_);
+ // Desktop work area should shrink.
+ EXPECT_EQ(ScreenAsh::GetDisplayBoundsInParent(w2.get()).width() -
+ manager->docked_width_ - DockedWindowLayoutManager::kMinDockGap,
+ ScreenAsh::GetDisplayWorkAreaBoundsInParent(w2.get()).width());
+
+ // Resize the first window left by more than the dock maximum width.
+ // This should cause the window to overhang and the dock to shrink to w2.
+ previous_width = w1->bounds().width();
+ const int kResizeSpan2 = 250;
+ ASSERT_NO_FATAL_FAILURE(ResizeStartAtOffsetFromwindowOrigin(w1.get(),
+ 0,
+ 20,
+ HTLEFT));
+ DragMove(-kResizeSpan2, 0);
+ // Alignment set to "RIGHT" during the drag because dock has a window in it.
+ EXPECT_EQ(DOCKED_ALIGNMENT_RIGHT, manager->alignment_);
+ // Release the mouse and the window should be attached to the edge.
+ DragEnd();
+ // Dock should still have both windows in it.
+ EXPECT_EQ(internal::kShellWindowId_DockedContainer, w1->parent()->id());
+ EXPECT_EQ(internal::kShellWindowId_DockedContainer, w2->parent()->id());
+ EXPECT_EQ(DOCKED_ALIGNMENT_RIGHT, manager->alignment_);
+ // w1 is now wider than the maximum dock width and the dock should shrink to
+ // the next widest window (w2).
+ EXPECT_EQ(previous_width + kResizeSpan2, w1->bounds().width());
+ EXPECT_GT(w1->bounds().width(), w2->bounds().width());
+ EXPECT_EQ(w2->bounds().width(), manager->docked_width_);
+ // Desktop work area should shrink.
+ EXPECT_EQ(ScreenAsh::GetDisplayBoundsInParent(w2.get()).width() -
+ manager->docked_width_ - DockedWindowLayoutManager::kMinDockGap,
+ ScreenAsh::GetDisplayWorkAreaBoundsInParent(w2.get()).width());
+
+ // Resize the first window right to get it completely inside the docked area.
+ previous_width = w1->bounds().width();
+ const int kResizeSpan3 = 100;
+ ASSERT_NO_FATAL_FAILURE(ResizeStartAtOffsetFromwindowOrigin(w1.get(),
+ 0,
+ 20,
+ HTLEFT));
+ DragMove(kResizeSpan3, 0);
+ // Alignment set to "RIGHT" during the drag because dock has a window in it.
+ EXPECT_EQ(DOCKED_ALIGNMENT_RIGHT, manager->alignment_);
+ // Release the mouse and the window should be attached to the edge.
+ DragEnd();
+ // Dock should still have both windows in it.
+ EXPECT_EQ(internal::kShellWindowId_DockedContainer, w1->parent()->id());
+ EXPECT_EQ(internal::kShellWindowId_DockedContainer, w2->parent()->id());
+ EXPECT_EQ(DOCKED_ALIGNMENT_RIGHT, manager->alignment_);
+ // w1 is still wider than w2 so the dock should expand and be as wide as w1.
+ EXPECT_EQ(previous_width - kResizeSpan3, w1->bounds().width());
+ EXPECT_GT(w1->bounds().width(), w2->bounds().width());
+ EXPECT_EQ(w1->bounds().width(), manager->docked_width_);
+ // Desktop work area should shrink.
+ EXPECT_EQ(ScreenAsh::GetDisplayBoundsInParent(w2.get()).width() -
+ manager->docked_width_ - DockedWindowLayoutManager::kMinDockGap,
+ ScreenAsh::GetDisplayWorkAreaBoundsInParent(w2.get()).width());
+
+ // Resize the first window left to be overhang again.
+ previous_width = w1->bounds().width();
+ ASSERT_NO_FATAL_FAILURE(ResizeStartAtOffsetFromwindowOrigin(w1.get(),
+ 0,
+ 20,
+ HTLEFT));
+ DragMove(-kResizeSpan3, 0);
+ DragEnd();
+ EXPECT_EQ(previous_width + kResizeSpan3, w1->bounds().width());
+ EXPECT_EQ(internal::kShellWindowId_DockedContainer, w1->parent()->id());
+ // Docked area should be as wide as the second window - the first is too wide.
+ EXPECT_EQ(w2->bounds().width(), manager->docked_width_);
+
+ // Undock the second window. Docked area should shrink to its minimum size.
+ ASSERT_NO_FATAL_FAILURE(DragStart(w2.get()));
+ // Drag up as well to avoid attaching panels to launcher shelf.
+ DragMove(-40, -100);
+ // Alignment set to "RIGHT" since we have another window docked.
+ EXPECT_EQ(DOCKED_ALIGNMENT_RIGHT, manager->alignment_);
+ // Release the mouse and the window should be no longer attached to the edge.
+ DragEnd();
+ EXPECT_EQ(internal::kShellWindowId_DefaultContainer, w2->parent()->id());
+ // Dock should get shrunk to minimum size.
+ EXPECT_EQ(DOCKED_ALIGNMENT_RIGHT, manager->alignment_);
+ EXPECT_EQ(manager->kMinDockWidth, manager->docked_width_);
+ // The first window should be still docked.
+ EXPECT_EQ(internal::kShellWindowId_DockedContainer, w1->parent()->id());
+ // Desktop work area should be inset.
+ EXPECT_EQ(ScreenAsh::GetDisplayBoundsInParent(w2.get()).width() -
+ manager->docked_width_ - DockedWindowLayoutManager::kMinDockGap,
+ ScreenAsh::GetDisplayWorkAreaBoundsInParent(w2.get()).width());
+}
+
+TEST_P(DockedWindowResizerTest, DragToShelf)
+{
+ if (!SupportsHostWindowResize())
+ return;
+
+ scoped_ptr<aura::Window> w1(CreateTestWindow(gfx::Rect(0, 0, 201, 201)));
+ // Work area should cover the whole screen.
+ EXPECT_EQ(ScreenAsh::GetDisplayBoundsInParent(w1.get()).width(),
+ ScreenAsh::GetDisplayWorkAreaBoundsInParent(w1.get()).width());
+
+ DragToVerticalPositionAndToEdge(DOCKED_EDGE_RIGHT, w1.get(), 20);
+ // A window should be attached and snapped to the right edge.
+ EXPECT_EQ(w1->GetRootWindow()->bounds().right(),
+ w1->GetBoundsInScreen().right());
+ EXPECT_EQ(internal::kShellWindowId_DockedContainer, w1->parent()->id());
+ DockedWindowLayoutManager* manager =
+ static_cast<DockedWindowLayoutManager*>(w1->parent()->layout_manager());
+ EXPECT_EQ(DOCKED_ALIGNMENT_RIGHT, manager->alignment_);
+ EXPECT_EQ(w1->bounds().width(), manager->docked_width_);
+
+ // Detach and drag down to shelf.
+ ASSERT_NO_FATAL_FAILURE(DragStart(w1.get()));
+ DragMove(-40, 0);
+ // Alignment is set to "NONE" when drag starts.
+ EXPECT_EQ(DOCKED_ALIGNMENT_NONE, manager->alignment_);
+ // Release the mouse and the window should be no longer attached to the edge.
+ DragEnd();
+ EXPECT_EQ(DOCKED_ALIGNMENT_NONE, manager->alignment_);
+
+ // Drag down almost to shelf. A panel will snap, a regular window won't.
+ ShelfWidget* shelf = Launcher::ForPrimaryDisplay()->shelf_widget();
+ const int shelf_y = shelf->GetWindowBoundsInScreen().y();
+ const int kDistanceFromShelf = 10;
+ ASSERT_NO_FATAL_FAILURE(DragStart(w1.get()));
+ DragMove(0, -kDistanceFromShelf + shelf_y - w1->bounds().bottom());
+ DragEnd();
+ if (test_panels()) {
+ // The panel should be touching the shelf and attached.
+ EXPECT_EQ(shelf_y, w1->bounds().bottom());
+ EXPECT_TRUE(w1->GetProperty(kPanelAttachedKey));
+ } else {
+ // The window should not be touching the shelf.
+ EXPECT_EQ(shelf_y - kDistanceFromShelf, w1->bounds().bottom());
+ }
+}
+
+// Tests run twice - on both panels and normal windows
+INSTANTIATE_TEST_CASE_P(NormalOrPanel,
+ DockedWindowResizerTest,
+ testing::Values(aura::client::WINDOW_TYPE_NORMAL,
+ aura::client::WINDOW_TYPE_PANEL));
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/wm/drag_window_controller.cc b/chromium/ash/wm/drag_window_controller.cc
new file mode 100644
index 00000000000..01ae80fa48a
--- /dev/null
+++ b/chromium/ash/wm/drag_window_controller.cc
@@ -0,0 +1,125 @@
+// Copyright (c) 2012 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.
+
+#include "ash/wm/drag_window_controller.h"
+
+#include "ash/shell_window_ids.h"
+#include "ash/wm/window_util.h"
+#include "ui/aura/client/screen_position_client.h"
+#include "ui/aura/window.h"
+#include "ui/compositor/layer.h"
+#include "ui/compositor/scoped_layer_animation_settings.h"
+#include "ui/views/corewm/shadow_types.h"
+#include "ui/views/corewm/window_util.h"
+#include "ui/views/view.h"
+#include "ui/views/widget/widget.h"
+
+namespace ash {
+namespace internal {
+
+DragWindowController::DragWindowController(aura::Window* window)
+ : window_(window),
+ drag_widget_(NULL),
+ layer_(NULL) {
+}
+
+DragWindowController::~DragWindowController() {
+ Hide();
+}
+
+void DragWindowController::SetDestinationDisplay(
+ const gfx::Display& dst_display) {
+ dst_display_ = dst_display;
+}
+
+void DragWindowController::Show() {
+ if (!drag_widget_)
+ CreateDragWidget(window_->GetBoundsInScreen());
+ drag_widget_->Show();
+}
+
+void DragWindowController::SetBounds(const gfx::Rect& bounds) {
+ DCHECK(drag_widget_);
+ bounds_ = bounds;
+ SetBoundsInternal(bounds);
+}
+
+void DragWindowController::Hide() {
+ if (drag_widget_) {
+ drag_widget_->Close();
+ drag_widget_ = NULL;
+ }
+ if (layer_) {
+ views::corewm::DeepDeleteLayers(layer_);
+ layer_ = NULL;
+ }
+}
+
+void DragWindowController::SetOpacity(float opacity) {
+ DCHECK(drag_widget_);
+ ui::Layer* layer = drag_widget_->GetNativeWindow()->layer();
+ ui::ScopedLayerAnimationSettings scoped_setter(layer->GetAnimator());
+ layer->SetOpacity(opacity);
+}
+
+void DragWindowController::CreateDragWidget(const gfx::Rect& bounds) {
+ DCHECK(!drag_widget_);
+ drag_widget_ = new views::Widget;
+ views::Widget::InitParams params(views::Widget::InitParams::TYPE_POPUP);
+ params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW;
+ params.parent = window_->parent();
+ params.can_activate = false;
+ params.keep_on_top = true;
+ drag_widget_->set_focus_on_creation(false);
+ drag_widget_->Init(params);
+ drag_widget_->SetVisibilityChangedAnimationsEnabled(false);
+ drag_widget_->GetNativeWindow()->SetName("DragWindow");
+ drag_widget_->GetNativeWindow()->set_id(kShellWindowId_PhantomWindow);
+ // Show shadow for the dragging window.
+ SetShadowType(drag_widget_->GetNativeWindow(),
+ views::corewm::SHADOW_TYPE_RECTANGULAR);
+ SetBoundsInternal(bounds);
+ drag_widget_->StackAbove(window_);
+
+ RecreateWindowLayers();
+ aura::Window* window = drag_widget_->GetNativeWindow();
+ layer_->SetVisible(true);
+ window->layer()->Add(layer_);
+ window->layer()->StackAtTop(layer_);
+
+ // Show the widget after all the setups.
+ drag_widget_->Show();
+
+ // Fade the window in.
+ ui::Layer* widget_layer = drag_widget_->GetNativeWindow()->layer();
+ widget_layer->SetOpacity(0);
+ ui::ScopedLayerAnimationSettings scoped_setter(widget_layer->GetAnimator());
+ widget_layer->SetOpacity(1);
+}
+
+void DragWindowController::SetBoundsInternal(const gfx::Rect& bounds) {
+ aura::Window* window = drag_widget_->GetNativeWindow();
+ aura::client::ScreenPositionClient* screen_position_client =
+ aura::client::GetScreenPositionClient(window->GetRootWindow());
+ if (screen_position_client && dst_display_.is_valid())
+ screen_position_client->SetBounds(window, bounds, dst_display_);
+ else
+ drag_widget_->SetBounds(bounds);
+}
+
+void DragWindowController::RecreateWindowLayers() {
+ DCHECK(!layer_);
+ layer_ = views::corewm::RecreateWindowLayers(window_, true);
+ layer_->set_delegate(window_->layer()->delegate());
+ // Place the layer at (0, 0) of the DragWindowController's window.
+ gfx::Rect layer_bounds = layer_->bounds();
+ layer_bounds.set_origin(gfx::Point(0, 0));
+ layer_->SetBounds(layer_bounds);
+ layer_->SetVisible(false);
+ // Detach it from the current container.
+ layer_->parent()->Remove(layer_);
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/wm/drag_window_controller.h b/chromium/ash/wm/drag_window_controller.h
new file mode 100644
index 00000000000..08e115de217
--- /dev/null
+++ b/chromium/ash/wm/drag_window_controller.h
@@ -0,0 +1,97 @@
+// Copyright (c) 2012 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 ASH_WM_DRAG_WINDOW_CONTROLLER_H_
+#define ASH_WM_DRAG_WINDOW_CONTROLLER_H_
+
+#include "ash/ash_export.h"
+#include "base/basictypes.h"
+#include "base/gtest_prod_util.h"
+#include "base/memory/scoped_ptr.h"
+#include "ui/gfx/display.h"
+#include "ui/gfx/rect.h"
+
+namespace aura {
+class Window;
+}
+
+namespace ui {
+class Layer;
+}
+
+namespace views {
+class Widget;
+}
+
+namespace ash {
+namespace internal {
+
+// DragWindowController is responsible for showing a semi-transparent window
+// while dragging a window across displays.
+class ASH_EXPORT DragWindowController {
+ public:
+ explicit DragWindowController(aura::Window* window);
+ virtual ~DragWindowController();
+
+ // Sets the display where the window is placed after the window is dropped.
+ void SetDestinationDisplay(const gfx::Display& dst_display);
+
+ // Shows the drag window at the specified location (coordinates of the
+ // parent). If |layer| is non-NULL, it is shown on top of the drag window.
+ // |layer| is owned by the caller.
+ // This does not immediately show the window.
+ void Show();
+
+ // Hides the drag window.
+ void Hide();
+
+ // This is used to set bounds for the drag window immediately. This should
+ // be called only when the drag window is already visible.
+ void SetBounds(const gfx::Rect& bounds);
+
+ // Sets the opacity of the drag window.
+ void SetOpacity(float opacity);
+
+ private:
+ FRIEND_TEST_ALL_PREFIXES(DragWindowResizerTest, DragWindowController);
+
+ // Creates and shows the |drag_widget_| at |bounds|.
+ // |layer| is shown on top of the drag window if it is non-NULL.
+ // |layer| is not owned by this object.
+ void CreateDragWidget(const gfx::Rect& bounds);
+
+ // Sets bounds of the drag window. The window is shown on |dst_display_|
+ // if its id() is valid. Otherwise, a display nearest to |bounds| is chosen.
+ void SetBoundsInternal(const gfx::Rect& bounds);
+
+ // Recreates a fresh layer for |window_| and all its child windows.
+ void RecreateWindowLayers();
+
+ // Window the drag window is placed beneath.
+ aura::Window* window_;
+
+ // The display where the drag is placed. When dst_display_.id() is
+ // kInvalidDisplayID (i.e. the default), a display nearest to the current
+ // |bounds_| is automatically used.
+ gfx::Display dst_display_;
+
+ // Initially the bounds of |window_|. Each time Show() is invoked
+ // |start_bounds_| is then reset to the bounds of |drag_widget_| and
+ // |bounds_| is set to the value passed into Show(). The animation animates
+ // between these two values.
+ gfx::Rect bounds_;
+
+ views::Widget* drag_widget_;
+
+ // The copy of window_->layer() and its children. This object is the owner of
+ // the layer.
+ ui::Layer* layer_;
+
+ DISALLOW_COPY_AND_ASSIGN(DragWindowController);
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_WM_DRAG_WINDOW_CONTROLLER_H_
diff --git a/chromium/ash/wm/drag_window_resizer.cc b/chromium/ash/wm/drag_window_resizer.cc
new file mode 100644
index 00000000000..e8f977e6418
--- /dev/null
+++ b/chromium/ash/wm/drag_window_resizer.cc
@@ -0,0 +1,210 @@
+// Copyright (c) 2012 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.
+
+#include "ash/wm/drag_window_resizer.h"
+
+#include "ash/display/mouse_cursor_event_filter.h"
+#include "ash/screen_ash.h"
+#include "ash/shell.h"
+#include "ash/wm/coordinate_conversion.h"
+#include "ash/wm/drag_window_controller.h"
+#include "ash/wm/property_util.h"
+#include "ui/aura/client/aura_constants.h"
+#include "ui/aura/env.h"
+#include "ui/aura/root_window.h"
+#include "ui/aura/window.h"
+#include "ui/aura/window_delegate.h"
+#include "ui/base/hit_test.h"
+#include "ui/base/ui_base_types.h"
+#include "ui/gfx/screen.h"
+
+namespace ash {
+namespace internal {
+
+namespace {
+
+// The maximum opacity of the drag phantom window.
+const float kMaxOpacity = 0.8f;
+
+// Returns true if Ash has more than one root window.
+bool HasSecondaryRootWindow() {
+ return Shell::GetAllRootWindows().size() > 1;
+}
+
+// When there are two root windows, returns one of the root windows which is not
+// |root_window|. Returns NULL if only one root window exists.
+aura::RootWindow* GetAnotherRootWindow(aura::RootWindow* root_window) {
+ Shell::RootWindowList root_windows = Shell::GetAllRootWindows();
+ if (root_windows.size() < 2)
+ return NULL;
+ DCHECK_EQ(2U, root_windows.size());
+ if (root_windows[0] == root_window)
+ return root_windows[1];
+ return root_windows[0];
+}
+
+} // namespace
+
+// static
+DragWindowResizer* DragWindowResizer::instance_ = NULL;
+
+DragWindowResizer::~DragWindowResizer() {
+ Shell* shell = Shell::GetInstance();
+ shell->mouse_cursor_filter()->set_mouse_warp_mode(
+ MouseCursorEventFilter::WARP_ALWAYS);
+ shell->mouse_cursor_filter()->HideSharedEdgeIndicator();
+ if (instance_ == this)
+ instance_ = NULL;
+
+ if (destroyed_)
+ *destroyed_ = true;
+}
+
+// static
+DragWindowResizer* DragWindowResizer::Create(
+ WindowResizer* next_window_resizer,
+ aura::Window* window,
+ const gfx::Point& location,
+ int window_component,
+ aura::client::WindowMoveSource source) {
+ Details details(window, location, window_component, source);
+ return details.is_resizable ?
+ new DragWindowResizer(next_window_resizer, details) : NULL;
+}
+
+void DragWindowResizer::Drag(const gfx::Point& location, int event_flags) {
+ bool destroyed = false;
+ destroyed_ = &destroyed;
+ next_window_resizer_->Drag(location, event_flags);
+
+ // TODO(flackr): Refactor the way WindowResizer calls into other window
+ // resizers to avoid the awkward pattern here for checking if
+ // next_window_resizer_ destroys the resizer object.
+ if (destroyed)
+ return;
+ destroyed_ = NULL;
+ last_mouse_location_ = location;
+
+ // Show a phantom window for dragging in another root window.
+ if (HasSecondaryRootWindow()) {
+ gfx::Point location_in_screen = location;
+ wm::ConvertPointToScreen(GetTarget()->parent(), &location_in_screen);
+ const bool in_original_root =
+ wm::GetRootWindowAt(location_in_screen) == GetTarget()->GetRootWindow();
+ UpdateDragWindow(GetTarget()->bounds(), in_original_root);
+ } else {
+ drag_window_controller_.reset();
+ }
+}
+
+void DragWindowResizer::CompleteDrag(int event_flags) {
+ next_window_resizer_->CompleteDrag(event_flags);
+
+ GetTarget()->layer()->SetOpacity(details_.initial_opacity);
+ drag_window_controller_.reset();
+
+ // Check if the destination is another display.
+ gfx::Point last_mouse_location_in_screen = last_mouse_location_;
+ wm::ConvertPointToScreen(GetTarget()->parent(),
+ &last_mouse_location_in_screen);
+ gfx::Screen* screen = Shell::GetScreen();
+ const gfx::Display dst_display =
+ screen->GetDisplayNearestPoint(last_mouse_location_in_screen);
+
+ if (dst_display.id() !=
+ screen->GetDisplayNearestWindow(GetTarget()->GetRootWindow()).id()) {
+ const gfx::Rect dst_bounds =
+ ScreenAsh::ConvertRectToScreen(GetTarget()->parent(),
+ GetTarget()->bounds());
+ GetTarget()->SetBoundsInScreen(dst_bounds, dst_display);
+ }
+}
+
+void DragWindowResizer::RevertDrag() {
+ next_window_resizer_->RevertDrag();
+
+ drag_window_controller_.reset();
+ GetTarget()->layer()->SetOpacity(details_.initial_opacity);
+}
+
+aura::Window* DragWindowResizer::GetTarget() {
+ return next_window_resizer_->GetTarget();
+}
+
+const gfx::Point& DragWindowResizer::GetInitialLocation() const {
+ return details_.initial_location_in_parent;
+}
+
+DragWindowResizer::DragWindowResizer(WindowResizer* next_window_resizer,
+ const Details& details)
+ : next_window_resizer_(next_window_resizer),
+ details_(details),
+ destroyed_(NULL) {
+ // The pointer should be confined in one display during resizing a window
+ // because the window cannot span two displays at the same time anyway. The
+ // exception is window/tab dragging operation. During that operation,
+ // |mouse_warp_mode_| should be set to WARP_DRAG so that the user could move a
+ // window/tab to another display.
+ MouseCursorEventFilter* mouse_cursor_filter =
+ Shell::GetInstance()->mouse_cursor_filter();
+ mouse_cursor_filter->set_mouse_warp_mode(
+ ShouldAllowMouseWarp() ?
+ MouseCursorEventFilter::WARP_DRAG : MouseCursorEventFilter::WARP_NONE);
+ if (ShouldAllowMouseWarp()) {
+ mouse_cursor_filter->ShowSharedEdgeIndicator(
+ details.window->GetRootWindow());
+ }
+ instance_ = this;
+}
+
+void DragWindowResizer::UpdateDragWindow(const gfx::Rect& bounds,
+ bool in_original_root) {
+ if (details_.window_component != HTCAPTION || !ShouldAllowMouseWarp())
+ return;
+
+ // It's available. Show a phantom window on the display if needed.
+ aura::RootWindow* another_root =
+ GetAnotherRootWindow(GetTarget()->GetRootWindow());
+ const gfx::Rect root_bounds_in_screen(another_root->GetBoundsInScreen());
+ const gfx::Rect bounds_in_screen =
+ ScreenAsh::ConvertRectToScreen(GetTarget()->parent(), bounds);
+ gfx::Rect bounds_in_another_root =
+ gfx::IntersectRects(root_bounds_in_screen, bounds_in_screen);
+ const float fraction_in_another_window =
+ (bounds_in_another_root.width() * bounds_in_another_root.height()) /
+ static_cast<float>(bounds.width() * bounds.height());
+
+ if (fraction_in_another_window > 0) {
+ if (!drag_window_controller_) {
+ drag_window_controller_.reset(
+ new DragWindowController(GetTarget()));
+ // Always show the drag phantom on the |another_root| window.
+ drag_window_controller_->SetDestinationDisplay(
+ Shell::GetScreen()->GetDisplayNearestWindow(another_root));
+ drag_window_controller_->Show();
+ } else {
+ // No animation.
+ drag_window_controller_->SetBounds(bounds_in_screen);
+ }
+ const float phantom_opacity =
+ !in_original_root ? 1 : (kMaxOpacity * fraction_in_another_window);
+ const float window_opacity =
+ in_original_root ? 1 : (kMaxOpacity * (1 - fraction_in_another_window));
+ drag_window_controller_->SetOpacity(phantom_opacity);
+ GetTarget()->layer()->SetOpacity(window_opacity);
+ } else {
+ drag_window_controller_.reset();
+ GetTarget()->layer()->SetOpacity(1.0f);
+ }
+}
+
+bool DragWindowResizer::ShouldAllowMouseWarp() {
+ return (details_.window_component == HTCAPTION) &&
+ !GetTarget()->transient_parent() &&
+ (GetTarget()->type() == aura::client::WINDOW_TYPE_NORMAL ||
+ GetTarget()->type() == aura::client::WINDOW_TYPE_PANEL);
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/wm/drag_window_resizer.h b/chromium/ash/wm/drag_window_resizer.h
new file mode 100644
index 00000000000..09e9ca66ac4
--- /dev/null
+++ b/chromium/ash/wm/drag_window_resizer.h
@@ -0,0 +1,78 @@
+// Copyright (c) 2012 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 ASH_WM_DRAG_WINDOW_RESIZER_H_
+#define ASH_WM_DRAG_WINDOW_RESIZER_H_
+
+#include "ash/wm/window_resizer.h"
+#include "base/compiler_specific.h"
+#include "base/gtest_prod_util.h"
+#include "ui/gfx/point.h"
+
+namespace ash {
+namespace internal {
+
+class DragWindowController;
+
+// DragWindowResizer is a decorator of WindowResizer and adds the ability to
+// drag windows across displays.
+class ASH_EXPORT DragWindowResizer : public WindowResizer {
+ public:
+ virtual ~DragWindowResizer();
+
+ // Creates a new DragWindowResizer. The caller takes ownership of the
+ // returned object. The ownership of |next_window_resizer| is taken by the
+ // returned object. Returns NULL if not resizable.
+ static DragWindowResizer* Create(WindowResizer* next_window_resizer,
+ aura::Window* window,
+ const gfx::Point& location,
+ int window_component,
+ aura::client::WindowMoveSource source);
+
+ // WindowResizer:
+ virtual void Drag(const gfx::Point& location, int event_flags) OVERRIDE;
+ virtual void CompleteDrag(int event_flags) OVERRIDE;
+ virtual void RevertDrag() OVERRIDE;
+ virtual aura::Window* GetTarget() OVERRIDE;
+ virtual const gfx::Point& GetInitialLocation() const OVERRIDE;
+
+ private:
+ FRIEND_TEST_ALL_PREFIXES(DragWindowResizerTest, DragWindowController);
+
+ // Creates DragWindowResizer that adds the ability of dragging windows across
+ // displays to |next_window_resizer|. This object takes the ownership of
+ // |next_window_resizer|.
+ explicit DragWindowResizer(WindowResizer* next_window_resizer,
+ const Details& details);
+
+ // Updates the bounds of the phantom window for window dragging. Set true on
+ // |in_original_root| if the pointer is still in |window()->GetRootWindow()|.
+ void UpdateDragWindow(const gfx::Rect& bounds, bool in_original_root);
+
+ // Returns true if we should allow the mouse pointer to warp.
+ bool ShouldAllowMouseWarp();
+
+ scoped_ptr<WindowResizer> next_window_resizer_;
+
+ // Shows a semi-transparent image of the window being dragged.
+ scoped_ptr<DragWindowController> drag_window_controller_;
+
+ const Details details_;
+
+ gfx::Point last_mouse_location_;
+
+ // If non-NULL the destructor sets this to true. Used to determine if this has
+ // been deleted.
+ bool* destroyed_;
+
+ // Current instance for use by the DragWindowResizerTest.
+ static DragWindowResizer* instance_;
+
+ DISALLOW_COPY_AND_ASSIGN(DragWindowResizer);
+};
+
+} // namespace internal
+} // namespace aura
+
+#endif // ASH_WM_DRAG_WINDOW_RESIZER_H_
diff --git a/chromium/ash/wm/drag_window_resizer_unittest.cc b/chromium/ash/wm/drag_window_resizer_unittest.cc
new file mode 100644
index 00000000000..2e8648f14d2
--- /dev/null
+++ b/chromium/ash/wm/drag_window_resizer_unittest.cc
@@ -0,0 +1,566 @@
+// Copyright (c) 2012 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.
+
+#include "ash/wm/drag_window_resizer.h"
+
+#include "ash/display/mouse_cursor_event_filter.h"
+#include "ash/root_window_controller.h"
+#include "ash/shelf/shelf_layout_manager.h"
+#include "ash/shell.h"
+#include "ash/shell_window_ids.h"
+#include "ash/test/ash_test_base.h"
+#include "ash/test/cursor_manager_test_api.h"
+#include "ash/wm/drag_window_controller.h"
+#include "ash/wm/window_util.h"
+#include "base/strings/stringprintf.h"
+#include "ui/aura/client/aura_constants.h"
+#include "ui/aura/root_window.h"
+#include "ui/aura/test/test_window_delegate.h"
+#include "ui/base/hit_test.h"
+#include "ui/base/ui_base_types.h"
+#include "ui/gfx/insets.h"
+#include "ui/gfx/screen.h"
+#include "ui/views/widget/widget.h"
+
+namespace ash {
+namespace internal {
+namespace {
+
+const int kRootHeight = 600;
+
+} // namespace
+
+class DragWindowResizerTest : public test::AshTestBase {
+ public:
+ DragWindowResizerTest() {}
+ virtual ~DragWindowResizerTest() {}
+
+ virtual void SetUp() OVERRIDE {
+ AshTestBase::SetUp();
+ UpdateDisplay(base::StringPrintf("800x%d", kRootHeight));
+
+ aura::RootWindow* root = Shell::GetPrimaryRootWindow();
+ gfx::Rect root_bounds(root->bounds());
+ EXPECT_EQ(kRootHeight, root_bounds.height());
+ EXPECT_EQ(800, root_bounds.width());
+ Shell::GetInstance()->SetDisplayWorkAreaInsets(root, gfx::Insets());
+ window_.reset(new aura::Window(&delegate_));
+ window_->SetType(aura::client::WINDOW_TYPE_NORMAL);
+ window_->Init(ui::LAYER_NOT_DRAWN);
+ SetDefaultParentByPrimaryRootWindow(window_.get());
+ window_->set_id(1);
+
+ always_on_top_window_.reset(new aura::Window(&delegate2_));
+ always_on_top_window_->SetType(aura::client::WINDOW_TYPE_NORMAL);
+ always_on_top_window_->SetProperty(aura::client::kAlwaysOnTopKey, true);
+ always_on_top_window_->Init(ui::LAYER_NOT_DRAWN);
+ SetDefaultParentByPrimaryRootWindow(always_on_top_window_.get());
+ always_on_top_window_->set_id(2);
+
+ system_modal_window_.reset(new aura::Window(&delegate3_));
+ system_modal_window_->SetType(aura::client::WINDOW_TYPE_NORMAL);
+ system_modal_window_->SetProperty(aura::client::kModalKey,
+ ui::MODAL_TYPE_SYSTEM);
+ system_modal_window_->Init(ui::LAYER_NOT_DRAWN);
+ SetDefaultParentByPrimaryRootWindow(system_modal_window_.get());
+ system_modal_window_->set_id(3);
+
+ transient_child_ = new aura::Window(&delegate4_);
+ transient_child_->SetType(aura::client::WINDOW_TYPE_NORMAL);
+ transient_child_->Init(ui::LAYER_NOT_DRAWN);
+ SetDefaultParentByPrimaryRootWindow(transient_child_);
+ transient_child_->set_id(4);
+
+ transient_parent_.reset(new aura::Window(&delegate5_));
+ transient_parent_->SetType(aura::client::WINDOW_TYPE_NORMAL);
+ transient_parent_->Init(ui::LAYER_NOT_DRAWN);
+ SetDefaultParentByPrimaryRootWindow(transient_parent_.get());
+ transient_parent_->AddTransientChild(transient_child_);
+ transient_parent_->set_id(5);
+
+ panel_window_.reset(new aura::Window(&delegate6_));
+ panel_window_->SetType(aura::client::WINDOW_TYPE_PANEL);
+ panel_window_->Init(ui::LAYER_NOT_DRAWN);
+ SetDefaultParentByPrimaryRootWindow(panel_window_.get());
+ }
+
+ virtual void TearDown() OVERRIDE {
+ window_.reset();
+ always_on_top_window_.reset();
+ system_modal_window_.reset();
+ transient_parent_.reset();
+ panel_window_.reset();
+ AshTestBase::TearDown();
+ }
+
+ protected:
+ gfx::Point CalculateDragPoint(const WindowResizer& resizer,
+ int delta_x,
+ int delta_y) const {
+ gfx::Point location = resizer.GetInitialLocation();
+ location.set_x(location.x() + delta_x);
+ location.set_y(location.y() + delta_y);
+ return location;
+ }
+
+ internal::ShelfLayoutManager* shelf_layout_manager() {
+ return Shell::GetPrimaryRootWindowController()->GetShelfLayoutManager();
+ }
+
+ static WindowResizer* CreateDragWindowResizer(
+ aura::Window* window,
+ const gfx::Point& point_in_parent,
+ int window_component) {
+ return CreateWindowResizer(
+ window,
+ point_in_parent,
+ window_component,
+ aura::client::WINDOW_MOVE_SOURCE_MOUSE).release();
+ }
+
+ aura::test::TestWindowDelegate delegate_;
+ aura::test::TestWindowDelegate delegate2_;
+ aura::test::TestWindowDelegate delegate3_;
+ aura::test::TestWindowDelegate delegate4_;
+ aura::test::TestWindowDelegate delegate5_;
+ aura::test::TestWindowDelegate delegate6_;
+
+ scoped_ptr<aura::Window> window_;
+ scoped_ptr<aura::Window> always_on_top_window_;
+ scoped_ptr<aura::Window> system_modal_window_;
+ scoped_ptr<aura::Window> panel_window_;
+ aura::Window* transient_child_;
+ scoped_ptr<aura::Window> transient_parent_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(DragWindowResizerTest);
+};
+
+// Verifies a window can be moved from the primary display to another.
+TEST_F(DragWindowResizerTest, WindowDragWithMultiDisplays) {
+ if (!SupportsMultipleDisplays())
+ return;
+
+ // The secondary display is logically on the right, but on the system (e.g. X)
+ // layer, it's below the primary one. See UpdateDisplay() in ash_test_base.cc.
+ UpdateDisplay("800x600,800x600");
+ Shell::RootWindowList root_windows = Shell::GetAllRootWindows();
+ ASSERT_EQ(2U, root_windows.size());
+
+ window_->SetBoundsInScreen(gfx::Rect(0, 0, 50, 60),
+ Shell::GetScreen()->GetPrimaryDisplay());
+ EXPECT_EQ(root_windows[0], window_->GetRootWindow());
+ {
+ // Grab (0, 0) of the window.
+ scoped_ptr<WindowResizer> resizer(CreateDragWindowResizer(
+ window_.get(), gfx::Point(), HTCAPTION));
+ ASSERT_TRUE(resizer.get());
+ // Drag the pointer to the right. Once it reaches the right edge of the
+ // primary display, it warps to the secondary.
+ resizer->Drag(CalculateDragPoint(*resizer, 800, 10), 0);
+ resizer->CompleteDrag(0);
+ // The whole window is on the secondary display now. The parent should be
+ // changed.
+ EXPECT_EQ(root_windows[1], window_->GetRootWindow());
+ EXPECT_EQ("0,10 50x60", window_->bounds().ToString());
+ }
+
+ window_->SetBoundsInScreen(gfx::Rect(0, 0, 50, 60),
+ Shell::GetScreen()->GetPrimaryDisplay());
+ EXPECT_EQ(root_windows[0], window_->GetRootWindow());
+ {
+ // Grab (0, 0) of the window and move the pointer to (790, 10).
+ scoped_ptr<WindowResizer> resizer(CreateDragWindowResizer(
+ window_.get(), gfx::Point(), HTCAPTION));
+ ASSERT_TRUE(resizer.get());
+ resizer->Drag(CalculateDragPoint(*resizer, 790, 10), 0);
+ resizer->CompleteDrag(0);
+ // Since the pointer is still on the primary root window, the parent should
+ // not be changed.
+ EXPECT_EQ(root_windows[0], window_->GetRootWindow());
+ EXPECT_EQ("790,10 50x60", window_->bounds().ToString());
+ }
+
+ window_->SetBoundsInScreen(gfx::Rect(0, 0, 50, 60),
+ Shell::GetScreen()->GetPrimaryDisplay());
+ EXPECT_EQ(root_windows[0], window_->GetRootWindow());
+ {
+ // Grab the top-right edge of the window and move the pointer to (0, 10)
+ // in the secondary root window's coordinates.
+ scoped_ptr<WindowResizer> resizer(CreateDragWindowResizer(
+ window_.get(), gfx::Point(49, 0), HTCAPTION));
+ ASSERT_TRUE(resizer.get());
+ resizer->Drag(CalculateDragPoint(*resizer, 751, 10), ui::EF_CONTROL_DOWN);
+ resizer->CompleteDrag(0);
+ // Since the pointer is on the secondary, the parent should be changed
+ // even though only small fraction of the window is within the secondary
+ // root window's bounds.
+ EXPECT_EQ(root_windows[1], window_->GetRootWindow());
+ EXPECT_EQ("-49,10 50x60", window_->bounds().ToString());
+ }
+}
+
+// Verifies that dragging the active window to another display makes the new
+// root window the active root window.
+TEST_F(DragWindowResizerTest, WindowDragWithMultiDisplaysActiveRoot) {
+ if (!SupportsMultipleDisplays())
+ return;
+
+ // The secondary display is logically on the right, but on the system (e.g. X)
+ // layer, it's below the primary one. See UpdateDisplay() in ash_test_base.cc.
+ UpdateDisplay("800x600,800x600");
+ Shell::RootWindowList root_windows = Shell::GetAllRootWindows();
+ ASSERT_EQ(2U, root_windows.size());
+
+ aura::test::TestWindowDelegate delegate;
+ scoped_ptr<aura::Window> window(new aura::Window(&delegate));
+ window->SetType(aura::client::WINDOW_TYPE_NORMAL);
+ window->Init(ui::LAYER_TEXTURED);
+ SetDefaultParentByPrimaryRootWindow(window.get());
+ window->SetBoundsInScreen(gfx::Rect(0, 0, 50, 60),
+ Shell::GetScreen()->GetPrimaryDisplay());
+ window->Show();
+ EXPECT_TRUE(ash::wm::CanActivateWindow(window.get()));
+ ash::wm::ActivateWindow(window.get());
+ EXPECT_EQ(root_windows[0], window->GetRootWindow());
+ EXPECT_EQ(root_windows[0], ash::Shell::GetActiveRootWindow());
+ {
+ // Grab (0, 0) of the window.
+ scoped_ptr<WindowResizer> resizer(CreateDragWindowResizer(
+ window.get(), gfx::Point(), HTCAPTION));
+ ASSERT_TRUE(resizer.get());
+ // Drag the pointer to the right. Once it reaches the right edge of the
+ // primary display, it warps to the secondary.
+ resizer->Drag(CalculateDragPoint(*resizer, 800, 10), 0);
+ resizer->CompleteDrag(0);
+ // The whole window is on the secondary display now. The parent should be
+ // changed.
+ EXPECT_EQ(root_windows[1], window->GetRootWindow());
+ EXPECT_EQ(root_windows[1], ash::Shell::GetActiveRootWindow());
+ }
+}
+
+// Verifies a window can be moved from the secondary display to primary.
+TEST_F(DragWindowResizerTest, WindowDragWithMultiDisplaysRightToLeft) {
+ if (!SupportsMultipleDisplays())
+ return;
+
+ UpdateDisplay("800x600,800x600");
+ Shell::RootWindowList root_windows = Shell::GetAllRootWindows();
+ ASSERT_EQ(2U, root_windows.size());
+
+ window_->SetBoundsInScreen(
+ gfx::Rect(800, 00, 50, 60),
+ Shell::GetScreen()->GetDisplayNearestWindow(root_windows[1]));
+ EXPECT_EQ(root_windows[1], window_->GetRootWindow());
+ {
+ // Grab (0, 0) of the window.
+ scoped_ptr<WindowResizer> resizer(CreateDragWindowResizer(
+ window_.get(), gfx::Point(), HTCAPTION));
+ ASSERT_TRUE(resizer.get());
+ // Move the mouse near the right edge, (798, 0), of the primary display.
+ resizer->Drag(CalculateDragPoint(*resizer, -2, 0), ui::EF_CONTROL_DOWN);
+ resizer->CompleteDrag(0);
+ EXPECT_EQ(root_windows[0], window_->GetRootWindow());
+ EXPECT_EQ("798,0 50x60", window_->bounds().ToString());
+ }
+}
+
+// Verifies the drag window is shown correctly.
+TEST_F(DragWindowResizerTest, DragWindowController) {
+ if (!SupportsMultipleDisplays())
+ return;
+
+ UpdateDisplay("800x600,800x600");
+ Shell::RootWindowList root_windows = Shell::GetAllRootWindows();
+ ASSERT_EQ(2U, root_windows.size());
+
+ window_->SetBoundsInScreen(gfx::Rect(0, 0, 50, 60),
+ Shell::GetScreen()->GetPrimaryDisplay());
+ EXPECT_EQ(root_windows[0], window_->GetRootWindow());
+ EXPECT_FLOAT_EQ(1.0f, window_->layer()->opacity());
+ {
+ scoped_ptr<WindowResizer> resizer(CreateDragWindowResizer(
+ window_.get(), gfx::Point(), HTCAPTION));
+ ASSERT_TRUE(resizer.get());
+ internal::DragWindowResizer* drag_resizer = DragWindowResizer::instance_;
+ ASSERT_TRUE(drag_resizer);
+ EXPECT_FALSE(drag_resizer->drag_window_controller_.get());
+
+ // The pointer is inside the primary root. The drag window controller
+ // should be NULL.
+ resizer->Drag(CalculateDragPoint(*resizer, 10, 10), 0);
+ EXPECT_FALSE(drag_resizer->drag_window_controller_.get());
+
+ // The window spans both root windows.
+ resizer->Drag(CalculateDragPoint(*resizer, 798, 10), 0);
+ DragWindowController* controller =
+ drag_resizer->drag_window_controller_.get();
+ ASSERT_TRUE(controller);
+
+ ASSERT_TRUE(controller->drag_widget_);
+ ui::Layer* drag_layer =
+ controller->drag_widget_->GetNativeWindow()->layer();
+ ASSERT_TRUE(drag_layer);
+ // Check if |resizer->layer_| is properly set to the drag widget.
+ const std::vector<ui::Layer*>& layers = drag_layer->children();
+ EXPECT_FALSE(layers.empty());
+ EXPECT_EQ(controller->layer_, layers.back());
+
+ // |window_| should be opaque since the pointer is still on the primary
+ // root window. The drag window should be semi-transparent.
+ EXPECT_FLOAT_EQ(1.0f, window_->layer()->opacity());
+ ASSERT_TRUE(controller->drag_widget_);
+ EXPECT_GT(1.0f, drag_layer->opacity());
+
+ // Enter the pointer to the secondary display.
+ resizer->Drag(CalculateDragPoint(*resizer, 800, 10), 0);
+ controller = drag_resizer->drag_window_controller_.get();
+ ASSERT_TRUE(controller);
+ // |window_| should be transparent, and the drag window should be opaque.
+ EXPECT_GT(1.0f, window_->layer()->opacity());
+ EXPECT_FLOAT_EQ(1.0f, drag_layer->opacity());
+
+ resizer->CompleteDrag(0);
+ EXPECT_EQ(root_windows[1], window_->GetRootWindow());
+ EXPECT_FLOAT_EQ(1.0f, window_->layer()->opacity());
+ }
+
+ // Do the same test with RevertDrag().
+ window_->SetBoundsInScreen(gfx::Rect(0, 0, 50, 60),
+ Shell::GetScreen()->GetPrimaryDisplay());
+ EXPECT_EQ(root_windows[0], window_->GetRootWindow());
+ EXPECT_FLOAT_EQ(1.0f, window_->layer()->opacity());
+ {
+ scoped_ptr<WindowResizer> resizer(CreateDragWindowResizer(
+ window_.get(), gfx::Point(), HTCAPTION));
+ ASSERT_TRUE(resizer.get());
+ internal::DragWindowResizer* drag_resizer = DragWindowResizer::instance_;
+ ASSERT_TRUE(drag_resizer);
+ EXPECT_FALSE(drag_resizer->drag_window_controller_.get());
+
+ resizer->Drag(CalculateDragPoint(*resizer, 0, 610), 0);
+ resizer->RevertDrag();
+ EXPECT_EQ(root_windows[0], window_->GetRootWindow());
+ EXPECT_FLOAT_EQ(1.0f, window_->layer()->opacity());
+ }
+}
+
+// Verifies if the resizer sets and resets
+// MouseCursorEventFilter::mouse_warp_mode_ as expected.
+TEST_F(DragWindowResizerTest, WarpMousePointer) {
+ MouseCursorEventFilter* event_filter =
+ Shell::GetInstance()->mouse_cursor_filter();
+ ASSERT_TRUE(event_filter);
+ window_->SetBounds(gfx::Rect(0, 0, 50, 60));
+
+ EXPECT_EQ(MouseCursorEventFilter::WARP_ALWAYS,
+ event_filter->mouse_warp_mode_);
+ {
+ scoped_ptr<WindowResizer> resizer(CreateDragWindowResizer(
+ window_.get(), gfx::Point(), HTCAPTION));
+ // While dragging a window, warp should be allowed.
+ EXPECT_EQ(MouseCursorEventFilter::WARP_DRAG,
+ event_filter->mouse_warp_mode_);
+ resizer->CompleteDrag(0);
+ }
+ EXPECT_EQ(MouseCursorEventFilter::WARP_ALWAYS,
+ event_filter->mouse_warp_mode_);
+
+ {
+ scoped_ptr<WindowResizer> resizer(CreateDragWindowResizer(
+ window_.get(), gfx::Point(), HTCAPTION));
+ EXPECT_EQ(MouseCursorEventFilter::WARP_DRAG,
+ event_filter->mouse_warp_mode_);
+ resizer->RevertDrag();
+ }
+ EXPECT_EQ(MouseCursorEventFilter::WARP_ALWAYS,
+ event_filter->mouse_warp_mode_);
+
+ {
+ scoped_ptr<WindowResizer> resizer(CreateDragWindowResizer(
+ window_.get(), gfx::Point(), HTRIGHT));
+ // While resizing a window, warp should NOT be allowed.
+ EXPECT_EQ(MouseCursorEventFilter::WARP_NONE,
+ event_filter->mouse_warp_mode_);
+ resizer->CompleteDrag(0);
+ }
+ EXPECT_EQ(MouseCursorEventFilter::WARP_ALWAYS,
+ event_filter->mouse_warp_mode_);
+
+ {
+ scoped_ptr<WindowResizer> resizer(CreateDragWindowResizer(
+ window_.get(), gfx::Point(), HTRIGHT));
+ EXPECT_EQ(MouseCursorEventFilter::WARP_NONE,
+ event_filter->mouse_warp_mode_);
+ resizer->RevertDrag();
+ }
+ EXPECT_EQ(MouseCursorEventFilter::WARP_ALWAYS,
+ event_filter->mouse_warp_mode_);
+}
+
+// Verifies cursor's device scale factor is updated whe a window is moved across
+// root windows with different device scale factors (http://crbug.com/154183).
+TEST_F(DragWindowResizerTest, CursorDeviceScaleFactor) {
+ if (!SupportsMultipleDisplays())
+ return;
+
+ // The secondary display is logically on the right, but on the system (e.g. X)
+ // layer, it's below the primary one. See UpdateDisplay() in ash_test_base.cc.
+ UpdateDisplay("400x400,800x800*2");
+ Shell::RootWindowList root_windows = Shell::GetAllRootWindows();
+ ASSERT_EQ(2U, root_windows.size());
+
+ test::CursorManagerTestApi cursor_test_api(
+ Shell::GetInstance()->cursor_manager());
+ MouseCursorEventFilter* event_filter =
+ Shell::GetInstance()->mouse_cursor_filter();
+ // Move window from the root window with 1.0 device scale factor to the root
+ // window with 2.0 device scale factor.
+ {
+ window_->SetBoundsInScreen(gfx::Rect(0, 0, 50, 60),
+ Shell::GetScreen()->GetPrimaryDisplay());
+ EXPECT_EQ(root_windows[0], window_->GetRootWindow());
+ // Grab (0, 0) of the window.
+ scoped_ptr<WindowResizer> resizer(CreateDragWindowResizer(
+ window_.get(), gfx::Point(), HTCAPTION));
+ EXPECT_EQ(1.0f, cursor_test_api.GetDisplay().device_scale_factor());
+ ASSERT_TRUE(resizer.get());
+ resizer->Drag(CalculateDragPoint(*resizer, 399, 200), 0);
+ event_filter->WarpMouseCursorIfNecessary(root_windows[0],
+ gfx::Point(399, 200));
+ EXPECT_EQ(2.0f, cursor_test_api.GetDisplay().device_scale_factor());
+ resizer->CompleteDrag(0);
+ EXPECT_EQ(2.0f, cursor_test_api.GetDisplay().device_scale_factor());
+ }
+
+ // Move window from the root window with 2.0 device scale factor to the root
+ // window with 1.0 device scale factor.
+ {
+ window_->SetBoundsInScreen(
+ gfx::Rect(600, 0, 50, 60),
+ Shell::GetScreen()->GetDisplayNearestWindow(root_windows[1]));
+ EXPECT_EQ(root_windows[1], window_->GetRootWindow());
+ // Grab (0, 0) of the window.
+ scoped_ptr<WindowResizer> resizer(CreateDragWindowResizer(
+ window_.get(), gfx::Point(), HTCAPTION));
+ EXPECT_EQ(2.0f, cursor_test_api.GetDisplay().device_scale_factor());
+ ASSERT_TRUE(resizer.get());
+ resizer->Drag(CalculateDragPoint(*resizer, -200, 200), 0);
+ event_filter->WarpMouseCursorIfNecessary(root_windows[1],
+ gfx::Point(400, 200));
+ EXPECT_EQ(1.0f, cursor_test_api.GetDisplay().device_scale_factor());
+ resizer->CompleteDrag(0);
+ EXPECT_EQ(1.0f, cursor_test_api.GetDisplay().device_scale_factor());
+ }
+}
+
+// Verifies several kinds of windows can be moved across displays.
+TEST_F(DragWindowResizerTest, MoveWindowAcrossDisplays) {
+ if (!SupportsMultipleDisplays())
+ return;
+
+ // The secondary display is logically on the right, but on the system (e.g. X)
+ // layer, it's below the primary one. See UpdateDisplay() in ash_test_base.cc.
+ UpdateDisplay("400x400,400x400");
+
+ Shell::RootWindowList root_windows = Shell::GetAllRootWindows();
+ ASSERT_EQ(2U, root_windows.size());
+ MouseCursorEventFilter* event_filter =
+ Shell::GetInstance()->mouse_cursor_filter();
+
+ // Normal window can be moved across display.
+ {
+ aura::Window* window = window_.get();
+ window->SetBoundsInScreen(gfx::Rect(0, 0, 50, 60),
+ Shell::GetScreen()->GetPrimaryDisplay());
+ // Grab (0, 0) of the window.
+ scoped_ptr<WindowResizer> resizer(CreateDragWindowResizer(
+ window, gfx::Point(), HTCAPTION));
+ ASSERT_TRUE(resizer.get());
+ resizer->Drag(CalculateDragPoint(*resizer, 399, 200), 0);
+ EXPECT_TRUE(event_filter->WarpMouseCursorIfNecessary(root_windows[0],
+ gfx::Point(399, 200)));
+ resizer->CompleteDrag(0);
+ }
+
+ // Always on top window can be moved across display.
+ {
+ aura::Window* window = always_on_top_window_.get();
+ window->SetBoundsInScreen(gfx::Rect(0, 0, 50, 60),
+ Shell::GetScreen()->GetPrimaryDisplay());
+ // Grab (0, 0) of the window.
+ scoped_ptr<WindowResizer> resizer(CreateDragWindowResizer(
+ window, gfx::Point(), HTCAPTION));
+ ASSERT_TRUE(resizer.get());
+ resizer->Drag(CalculateDragPoint(*resizer, 399, 200), 0);
+ EXPECT_TRUE(event_filter->WarpMouseCursorIfNecessary(root_windows[0],
+ gfx::Point(399, 200)));
+ resizer->CompleteDrag(0);
+ }
+
+ // System modal window can be moved across display.
+ {
+ aura::Window* window = system_modal_window_.get();
+ window->SetBoundsInScreen(gfx::Rect(0, 0, 50, 60),
+ Shell::GetScreen()->GetPrimaryDisplay());
+ // Grab (0, 0) of the window.
+ scoped_ptr<WindowResizer> resizer(CreateDragWindowResizer(
+ window, gfx::Point(), HTCAPTION));
+ ASSERT_TRUE(resizer.get());
+ resizer->Drag(CalculateDragPoint(*resizer, 399, 200), 0);
+ EXPECT_TRUE(event_filter->WarpMouseCursorIfNecessary(root_windows[0],
+ gfx::Point(399, 200)));
+ resizer->CompleteDrag(0);
+ }
+
+ // Transient window cannot be moved across display.
+ {
+ aura::Window* window = transient_child_;
+ window->SetBoundsInScreen(gfx::Rect(0, 0, 50, 60),
+ Shell::GetScreen()->GetPrimaryDisplay());
+ // Grab (0, 0) of the window.
+ scoped_ptr<WindowResizer> resizer(CreateDragWindowResizer(
+ window, gfx::Point(), HTCAPTION));
+ ASSERT_TRUE(resizer.get());
+ resizer->Drag(CalculateDragPoint(*resizer, 399, 200), 0);
+ EXPECT_FALSE(event_filter->WarpMouseCursorIfNecessary(
+ root_windows[0],
+ gfx::Point(399, 200)));
+ resizer->CompleteDrag(0);
+ }
+
+ // The parent of transient window can be moved across display.
+ {
+ aura::Window* window = transient_parent_.get();
+ window->SetBoundsInScreen(gfx::Rect(0, 0, 50, 60),
+ Shell::GetScreen()->GetPrimaryDisplay());
+ // Grab (0, 0) of the window.
+ scoped_ptr<WindowResizer> resizer(CreateDragWindowResizer(
+ window, gfx::Point(), HTCAPTION));
+ ASSERT_TRUE(resizer.get());
+ resizer->Drag(CalculateDragPoint(*resizer, 399, 200), 0);
+ EXPECT_TRUE(event_filter->WarpMouseCursorIfNecessary(root_windows[0],
+ gfx::Point(399, 200)));
+ resizer->CompleteDrag(0);
+ }
+
+ // Panel window can be moved across display.
+ {
+ aura::Window* window = panel_window_.get();
+ window->SetBoundsInScreen(gfx::Rect(0, 0, 50, 60),
+ Shell::GetScreen()->GetPrimaryDisplay());
+ // Grab (0, 0) of the window.
+ scoped_ptr<WindowResizer> resizer(CreateDragWindowResizer(
+ window, gfx::Point(), HTCAPTION));
+ ASSERT_TRUE(resizer.get());
+ resizer->Drag(CalculateDragPoint(*resizer, 399, 200), 0);
+ EXPECT_TRUE(event_filter->WarpMouseCursorIfNecessary(root_windows[0],
+ gfx::Point(399, 200)));
+ resizer->CompleteDrag(0);
+ }
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/wm/event_client_impl.cc b/chromium/ash/wm/event_client_impl.cc
new file mode 100644
index 00000000000..03d4dea0b03
--- /dev/null
+++ b/chromium/ash/wm/event_client_impl.cc
@@ -0,0 +1,60 @@
+// Copyright (c) 2012 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.
+
+#include "ash/wm/event_client_impl.h"
+
+#include "ash/session_state_delegate.h"
+#include "ash/shell.h"
+#include "ash/shell_window_ids.h"
+#include "ui/aura/window.h"
+#include "ui/keyboard/keyboard_util.h"
+
+namespace ash {
+namespace internal {
+
+EventClientImpl::EventClientImpl() {
+}
+
+EventClientImpl::~EventClientImpl() {
+}
+
+bool EventClientImpl::CanProcessEventsWithinSubtree(
+ const aura::Window* window) const {
+ const aura::RootWindow* root_window = window ? window->GetRootWindow() : NULL;
+ if (!root_window ||
+ !Shell::GetInstance()->session_state_delegate()->IsUserSessionBlocked()) {
+ return true;
+ }
+
+ const aura::Window* lock_screen_containers = Shell::GetContainer(
+ root_window,
+ kShellWindowId_LockScreenContainersContainer);
+ const aura::Window* lock_background_containers = Shell::GetContainer(
+ root_window,
+ kShellWindowId_LockScreenBackgroundContainer);
+ const aura::Window* lock_screen_related_containers = Shell::GetContainer(
+ root_window,
+ kShellWindowId_LockScreenRelatedContainersContainer);
+ bool can_process_events = (window->Contains(lock_screen_containers) &&
+ window->Contains(lock_background_containers) &&
+ window->Contains(lock_screen_related_containers)) ||
+ lock_screen_containers->Contains(window) ||
+ lock_background_containers->Contains(window) ||
+ lock_screen_related_containers->Contains(window);
+ if (keyboard::IsKeyboardEnabled()) {
+ const aura::Window* virtual_keyboard_container = Shell::GetContainer(
+ root_window,
+ kShellWindowId_VirtualKeyboardContainer);
+ can_process_events |= (window->Contains(virtual_keyboard_container) ||
+ virtual_keyboard_container->Contains(window));
+ }
+ return can_process_events;
+}
+
+ui::EventTarget* EventClientImpl::GetToplevelEventTarget() {
+ return Shell::GetInstance();
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/wm/event_client_impl.h b/chromium/ash/wm/event_client_impl.h
new file mode 100644
index 00000000000..897e8817e0c
--- /dev/null
+++ b/chromium/ash/wm/event_client_impl.h
@@ -0,0 +1,35 @@
+// Copyright (c) 2012 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 ASH_WM_EVENT_CLIENT_IMPL_H_
+#define ASH_WM_EVENT_CLIENT_IMPL_H_
+
+#include "ash/ash_export.h"
+#include "ui/aura/client/event_client.h"
+
+namespace aura {
+class RootWindow;
+}
+
+namespace ash {
+namespace internal {
+
+class EventClientImpl : public aura::client::EventClient {
+ public:
+ EventClientImpl();
+ virtual ~EventClientImpl();
+
+ private:
+ // Overridden from aura::client::EventClient:
+ virtual bool CanProcessEventsWithinSubtree(
+ const aura::Window* window) const OVERRIDE;
+ virtual ui::EventTarget* GetToplevelEventTarget() OVERRIDE;
+
+ DISALLOW_COPY_AND_ASSIGN(EventClientImpl);
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_WM_EVENT_CLIENT_IMPL_H_
diff --git a/chromium/ash/wm/event_rewriter_event_filter.cc b/chromium/ash/wm/event_rewriter_event_filter.cc
new file mode 100644
index 00000000000..315f9c6f603
--- /dev/null
+++ b/chromium/ash/wm/event_rewriter_event_filter.cc
@@ -0,0 +1,76 @@
+// Copyright (c) 2012 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.
+
+#include "ash/wm/event_rewriter_event_filter.h"
+
+#include "ash/event_rewriter_delegate.h"
+#include "base/logging.h"
+#include "ui/base/events/event.h"
+
+#if defined(OS_CHROMEOS)
+#include "ash/wm/sticky_keys.h"
+#endif // OS_CHROMEOS
+namespace ash {
+namespace internal {
+
+EventRewriterEventFilter::EventRewriterEventFilter() {}
+
+EventRewriterEventFilter::~EventRewriterEventFilter() {}
+
+void EventRewriterEventFilter::SetEventRewriterDelegate(
+ scoped_ptr<EventRewriterDelegate> delegate) {
+ delegate_ = delegate.Pass();
+}
+
+void EventRewriterEventFilter::EnableStickyKeys(bool enabled) {
+#if defined(OS_CHROMEOS)
+ if (enabled)
+ sticky_keys_.reset(new StickyKeys());
+ else
+ sticky_keys_.reset();
+#endif // OS_CHROMEOS
+}
+
+void EventRewriterEventFilter::OnKeyEvent(ui::KeyEvent* event) {
+ if (!delegate_)
+ return;
+
+ // Do not consume a translated key event which is generated by an IME.
+ if (event->type() == ui::ET_TRANSLATED_KEY_PRESS ||
+ event->type() == ui::ET_TRANSLATED_KEY_RELEASE) {
+ return;
+ }
+
+ switch (delegate_->RewriteOrFilterKeyEvent(event)) {
+ case EventRewriterDelegate::ACTION_REWRITE_EVENT:
+ break;
+ case EventRewriterDelegate::ACTION_DROP_EVENT:
+ event->StopPropagation();
+ break;
+ }
+
+ if (event->stopped_propagation())
+ return;
+
+#if defined(OS_CHROMEOS)
+ if (sticky_keys_.get() && sticky_keys_->HandleKeyEvent(event))
+ event->StopPropagation();
+#endif // OS_CHROMEOS
+}
+
+void EventRewriterEventFilter::OnMouseEvent(ui::MouseEvent* event) {
+ if (!delegate_)
+ return;
+
+ switch (delegate_->RewriteOrFilterLocatedEvent(event)) {
+ case EventRewriterDelegate::ACTION_REWRITE_EVENT:
+ return;
+ case EventRewriterDelegate::ACTION_DROP_EVENT:
+ event->StopPropagation();
+ break;
+ }
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/wm/event_rewriter_event_filter.h b/chromium/ash/wm/event_rewriter_event_filter.h
new file mode 100644
index 00000000000..87cf07fa281
--- /dev/null
+++ b/chromium/ash/wm/event_rewriter_event_filter.h
@@ -0,0 +1,50 @@
+// Copyright (c) 2012 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 ASH_WM_EVENT_REWRITER_EVENT_FILTER_
+#define ASH_WM_EVENT_REWRITER_EVENT_FILTER_
+
+#include "ash/ash_export.h"
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/memory/scoped_ptr.h"
+#include "ui/base/events/event_handler.h"
+
+namespace ash {
+
+class EventRewriterDelegate;
+#if defined(OS_CHROMEOS)
+class StickyKeys;
+#endif // OS_CHROMEOS
+
+namespace internal {
+
+// An event filter that rewrites or drops an event.
+class ASH_EXPORT EventRewriterEventFilter : public ui::EventHandler {
+ public:
+ EventRewriterEventFilter();
+ virtual ~EventRewriterEventFilter();
+
+ void SetEventRewriterDelegate(scoped_ptr<EventRewriterDelegate> delegate);
+
+ // Enables or disables sticky keys.
+ void EnableStickyKeys(bool enabled);
+
+ private:
+ // Overridden from ui::EventHandler:
+ virtual void OnKeyEvent(ui::KeyEvent* event) OVERRIDE;
+ virtual void OnMouseEvent(ui::MouseEvent* event) OVERRIDE;
+
+ scoped_ptr<EventRewriterDelegate> delegate_;
+#if defined(OS_CHROMEOS)
+ scoped_ptr<StickyKeys> sticky_keys_;
+#endif // OS_CHROMEOS
+
+ DISALLOW_COPY_AND_ASSIGN(EventRewriterEventFilter);
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_WM_EVENT_REWRITER_EVENT_FILTER_
diff --git a/chromium/ash/wm/frame_painter.cc b/chromium/ash/wm/frame_painter.cc
new file mode 100644
index 00000000000..adf9dd7b7a2
--- /dev/null
+++ b/chromium/ash/wm/frame_painter.cc
@@ -0,0 +1,935 @@
+// Copyright (c) 2012 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.
+
+#include "ash/wm/frame_painter.h"
+
+#include <vector>
+
+#include "ash/ash_constants.h"
+#include "ash/root_window_controller.h"
+#include "ash/shell.h"
+#include "ash/shell_window_ids.h"
+#include "ash/wm/property_util.h"
+#include "ash/wm/window_properties.h"
+#include "ash/wm/window_util.h"
+#include "base/logging.h" // DCHECK
+#include "grit/ash_resources.h"
+#include "third_party/skia/include/core/SkCanvas.h"
+#include "third_party/skia/include/core/SkColor.h"
+#include "third_party/skia/include/core/SkPaint.h"
+#include "third_party/skia/include/core/SkPath.h"
+#include "ui/aura/client/aura_constants.h"
+#include "ui/aura/env.h"
+#include "ui/aura/root_window.h"
+#include "ui/aura/window.h"
+#include "ui/base/animation/slide_animation.h"
+#include "ui/base/hit_test.h"
+#include "ui/base/layout.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "ui/base/theme_provider.h"
+#include "ui/gfx/canvas.h"
+#include "ui/gfx/font.h"
+#include "ui/gfx/image/image.h"
+#include "ui/gfx/screen.h"
+#include "ui/gfx/skia_util.h"
+#include "ui/views/controls/button/image_button.h"
+#include "ui/views/widget/widget.h"
+#include "ui/views/widget/widget_delegate.h"
+
+using aura::RootWindow;
+using aura::Window;
+using views::Widget;
+
+namespace {
+// TODO(jamescook): Border is specified to be a single pixel overlapping
+// the web content and may need to be built into the shadow layers instead.
+const int kBorderThickness = 0;
+// Space between left edge of window and popup window icon.
+const int kIconOffsetX = 9;
+// Height and width of window icon.
+const int kIconSize = 16;
+// Space between the title text and the caption buttons.
+const int kTitleLogoSpacing = 5;
+// Space between window icon and title text.
+const int kTitleIconOffsetX = 5;
+// Space between window edge and title text, when there is no icon.
+const int kTitleNoIconOffsetX = 8;
+// Color for the non-maximized window title text.
+const SkColor kNonMaximizedWindowTitleTextColor = SkColorSetRGB(40, 40, 40);
+// Color for the maximized window title text.
+const SkColor kMaximizedWindowTitleTextColor = SK_ColorWHITE;
+// Size of header/content separator line below the header image.
+const int kHeaderContentSeparatorSize = 1;
+// Color of header bottom edge line.
+const SkColor kHeaderContentSeparatorColor = SkColorSetRGB(128, 128, 128);
+// Space between close button and right edge of window.
+const int kCloseButtonOffsetX = 0;
+// Space between close button and top edge of window.
+const int kCloseButtonOffsetY = 0;
+// The size and close buttons are designed to slightly overlap in order
+// to do fancy hover highlighting.
+const int kSizeButtonOffsetX = -1;
+// In the pre-Ash era the web content area had a frame along the left edge, so
+// user-generated theme images for the new tab page assume they are shifted
+// right relative to the header. Now that we have removed the left edge frame
+// we need to copy the theme image for the window header from a few pixels
+// inset to preserve alignment with the NTP image, or else we'll break a bunch
+// of existing themes. We do something similar on OS X for the same reason.
+const int kThemeFrameImageInsetX = 5;
+// Duration of crossfade animation for activating and deactivating frame.
+const int kActivationCrossfadeDurationMs = 200;
+// Alpha/opacity value for fully-opaque headers.
+const int kFullyOpaque = 255;
+
+// Tiles an image into an area, rounding the top corners. Samples |image|
+// starting |image_inset_x| pixels from the left of the image.
+void TileRoundRect(gfx::Canvas* canvas,
+ const gfx::ImageSkia& image,
+ const SkPaint& paint,
+ const gfx::Rect& bounds,
+ int top_left_corner_radius,
+ int top_right_corner_radius,
+ int image_inset_x) {
+ SkRect rect = gfx::RectToSkRect(bounds);
+ const SkScalar kTopLeftRadius = SkIntToScalar(top_left_corner_radius);
+ const SkScalar kTopRightRadius = SkIntToScalar(top_right_corner_radius);
+ SkScalar radii[8] = {
+ kTopLeftRadius, kTopLeftRadius, // top-left
+ kTopRightRadius, kTopRightRadius, // top-right
+ 0, 0, // bottom-right
+ 0, 0}; // bottom-left
+ SkPath path;
+ path.addRoundRect(rect, radii, SkPath::kCW_Direction);
+ canvas->DrawImageInPath(image, -image_inset_x, 0, path, paint);
+}
+
+// Tiles |frame_image| and |frame_overlay_image| into an area, rounding the top
+// corners.
+void PaintFrameImagesInRoundRect(gfx::Canvas* canvas,
+ const gfx::ImageSkia* frame_image,
+ const gfx::ImageSkia* frame_overlay_image,
+ const SkPaint& paint,
+ const gfx::Rect& bounds,
+ int corner_radius,
+ int image_inset_x) {
+ SkXfermode::Mode normal_mode;
+ SkXfermode::AsMode(NULL, &normal_mode);
+
+ // If |paint| is using an unusual SkXfermode::Mode (this is the case while
+ // crossfading), we must create a new canvas to overlay |frame_image| and
+ // |frame_overlay_image| using |normal_mode| and then paint the result
+ // using the unusual mode. We try to avoid this because creating a new
+ // browser-width canvas is expensive.
+ bool fast_path = (!frame_overlay_image ||
+ SkXfermode::IsMode(paint.getXfermode(), normal_mode));
+ if (fast_path) {
+ TileRoundRect(canvas, *frame_image, paint, bounds, corner_radius,
+ corner_radius, image_inset_x);
+
+ if (frame_overlay_image) {
+ // Adjust |bounds| such that |frame_overlay_image| is not tiled.
+ gfx::Rect overlay_bounds = bounds;
+ overlay_bounds.Intersect(
+ gfx::Rect(bounds.origin(), frame_overlay_image->size()));
+ int top_left_corner_radius = corner_radius;
+ int top_right_corner_radius = corner_radius;
+ if (overlay_bounds.width() < bounds.width() - corner_radius)
+ top_right_corner_radius = 0;
+ TileRoundRect(canvas, *frame_overlay_image, paint, overlay_bounds,
+ top_left_corner_radius, top_right_corner_radius, 0);
+ }
+ } else {
+ gfx::Canvas temporary_canvas(bounds.size(), canvas->scale_factor(), false);
+ temporary_canvas.TileImageInt(*frame_image,
+ image_inset_x, 0,
+ 0, 0,
+ bounds.width(), bounds.height());
+ temporary_canvas.DrawImageInt(*frame_overlay_image, 0, 0);
+ TileRoundRect(canvas, gfx::ImageSkia(temporary_canvas.ExtractImageRep()),
+ paint, bounds, corner_radius, corner_radius, 0);
+ }
+}
+
+// Returns true if |child| and all ancestors are visible. Useful to ensure that
+// a window is individually visible and is not part of a hidden workspace.
+bool IsVisibleToRoot(Window* child) {
+ for (Window* window = child; window; window = window->parent()) {
+ // We must use TargetVisibility() because windows animate in and out and
+ // IsVisible() also tracks the layer visibility state.
+ if (!window->TargetVisibility())
+ return false;
+ }
+ return true;
+}
+
+// Returns true if |window| is a "normal" window for purposes of solo window
+// computations. Returns false for windows that are:
+// * Not drawn (for example, DragDropTracker uses one for mouse capture)
+// * Modal alerts (it looks odd for headers to change when an alert opens)
+// * Constrained windows (ditto)
+bool IsSoloWindowHeaderCandidate(aura::Window* window) {
+ return window &&
+ window->type() == aura::client::WINDOW_TYPE_NORMAL &&
+ window->layer() &&
+ window->layer()->type() != ui::LAYER_NOT_DRAWN &&
+ window->GetProperty(aura::client::kModalKey) == ui::MODAL_TYPE_NONE &&
+ !window->GetProperty(ash::kConstrainedWindowKey);
+}
+
+// Returns a list of windows in |root_window|| that potentially could have
+// a transparent solo-window header.
+std::vector<Window*> GetWindowsForSoloHeaderUpdate(RootWindow* root_window) {
+ std::vector<Window*> windows;
+ // During shutdown there may not be a workspace controller. In that case
+ // we don't care about updating any windows.
+ // Avoid memory allocations for typical window counts.
+ windows.reserve(16);
+ // Collect windows from the desktop.
+ Window* desktop = ash::Shell::GetContainer(
+ root_window, ash::internal::kShellWindowId_DefaultContainer);
+ windows.insert(windows.end(),
+ desktop->children().begin(),
+ desktop->children().end());
+ // Collect "always on top" windows.
+ Window* top_container =
+ ash::Shell::GetContainer(
+ root_window, ash::internal::kShellWindowId_AlwaysOnTopContainer);
+ windows.insert(windows.end(),
+ top_container->children().begin(),
+ top_container->children().end());
+ return windows;
+}
+} // namespace
+
+namespace ash {
+
+// static
+int FramePainter::kActiveWindowOpacity = 255; // 1.0
+int FramePainter::kInactiveWindowOpacity = 255; // 1.0
+int FramePainter::kSoloWindowOpacity = 77; // 0.3
+
+///////////////////////////////////////////////////////////////////////////////
+// FramePainter, public:
+
+FramePainter::FramePainter()
+ : frame_(NULL),
+ window_icon_(NULL),
+ size_button_(NULL),
+ close_button_(NULL),
+ window_(NULL),
+ button_separator_(NULL),
+ top_left_corner_(NULL),
+ top_edge_(NULL),
+ top_right_corner_(NULL),
+ header_left_edge_(NULL),
+ header_right_edge_(NULL),
+ previous_theme_frame_id_(0),
+ previous_theme_frame_overlay_id_(0),
+ previous_opacity_(0),
+ crossfade_theme_frame_id_(0),
+ crossfade_theme_frame_overlay_id_(0),
+ crossfade_opacity_(0),
+ size_button_behavior_(SIZE_BUTTON_MAXIMIZES) {}
+
+FramePainter::~FramePainter() {
+ // Sometimes we are destroyed before the window closes, so ensure we clean up.
+ if (window_) {
+ window_->RemoveObserver(this);
+ }
+}
+
+void FramePainter::Init(views::Widget* frame,
+ views::View* window_icon,
+ views::ImageButton* size_button,
+ views::ImageButton* close_button,
+ SizeButtonBehavior behavior) {
+ DCHECK(frame);
+ // window_icon may be NULL.
+ DCHECK(size_button);
+ DCHECK(close_button);
+ frame_ = frame;
+ window_icon_ = window_icon;
+ size_button_ = size_button;
+ close_button_ = close_button;
+ size_button_behavior_ = behavior;
+
+ // Window frame image parts.
+ ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
+ button_separator_ =
+ rb.GetImageNamed(IDR_AURA_WINDOW_BUTTON_SEPARATOR).ToImageSkia();
+ top_left_corner_ =
+ rb.GetImageNamed(IDR_AURA_WINDOW_HEADER_SHADE_TOP_LEFT).ToImageSkia();
+ top_edge_ =
+ rb.GetImageNamed(IDR_AURA_WINDOW_HEADER_SHADE_TOP).ToImageSkia();
+ top_right_corner_ =
+ rb.GetImageNamed(IDR_AURA_WINDOW_HEADER_SHADE_TOP_RIGHT).ToImageSkia();
+ header_left_edge_ =
+ rb.GetImageNamed(IDR_AURA_WINDOW_HEADER_SHADE_LEFT).ToImageSkia();
+ header_right_edge_ =
+ rb.GetImageNamed(IDR_AURA_WINDOW_HEADER_SHADE_RIGHT).ToImageSkia();
+
+ window_ = frame->GetNativeWindow();
+ gfx::Insets mouse_insets = gfx::Insets(-kResizeOutsideBoundsSize,
+ -kResizeOutsideBoundsSize,
+ -kResizeOutsideBoundsSize,
+ -kResizeOutsideBoundsSize);
+ gfx::Insets touch_insets = mouse_insets.Scale(
+ kResizeOutsideBoundsScaleForTouch);
+ // Ensure we get resize cursors for a few pixels outside our bounds.
+ window_->SetHitTestBoundsOverrideOuter(mouse_insets, touch_insets);
+ // Ensure we get resize cursors just inside our bounds as well.
+ window_->set_hit_test_bounds_override_inner(mouse_insets);
+
+ // Watch for maximize/restore/fullscreen state changes. Observer removes
+ // itself in OnWindowDestroying() below, or in the destructor if we go away
+ // before the window.
+ window_->AddObserver(this);
+
+ // Solo-window header updates are handled by the workspace controller when
+ // this window is added to the desktop.
+}
+
+// static
+void FramePainter::UpdateSoloWindowHeader(RootWindow* root_window) {
+ // Use a separate function here so callers outside of FramePainter don't need
+ // to know about "ignorable_window".
+ UpdateSoloWindowInRoot(root_window, NULL /* ignorable_window */);
+}
+
+gfx::Rect FramePainter::GetBoundsForClientView(
+ int top_height,
+ const gfx::Rect& window_bounds) const {
+ return gfx::Rect(
+ kBorderThickness,
+ top_height,
+ std::max(0, window_bounds.width() - (2 * kBorderThickness)),
+ std::max(0, window_bounds.height() - top_height - kBorderThickness));
+}
+
+gfx::Rect FramePainter::GetWindowBoundsForClientBounds(
+ int top_height,
+ const gfx::Rect& client_bounds) const {
+ return gfx::Rect(std::max(0, client_bounds.x() - kBorderThickness),
+ std::max(0, client_bounds.y() - top_height),
+ client_bounds.width() + (2 * kBorderThickness),
+ client_bounds.height() + top_height + kBorderThickness);
+}
+
+int FramePainter::NonClientHitTest(views::NonClientFrameView* view,
+ const gfx::Point& point) {
+ gfx::Rect expanded_bounds = view->bounds();
+ int outside_bounds = kResizeOutsideBoundsSize;
+
+ if (aura::Env::GetInstance()->is_touch_down())
+ outside_bounds *= kResizeOutsideBoundsScaleForTouch;
+ expanded_bounds.Inset(-outside_bounds, -outside_bounds);
+
+ if (!expanded_bounds.Contains(point))
+ return HTNOWHERE;
+
+ // No avatar button.
+
+ // Check the frame first, as we allow a small area overlapping the contents
+ // to be used for resize handles.
+ bool can_ever_resize = frame_->widget_delegate() ?
+ frame_->widget_delegate()->CanResize() :
+ false;
+ // Don't allow overlapping resize handles when the window is maximized or
+ // fullscreen, as it can't be resized in those states.
+ int resize_border =
+ frame_->IsMaximized() || frame_->IsFullscreen() ? 0 :
+ kResizeInsideBoundsSize;
+ int frame_component = view->GetHTComponentForFrame(point,
+ resize_border,
+ resize_border,
+ kResizeAreaCornerSize,
+ kResizeAreaCornerSize,
+ can_ever_resize);
+ if (frame_component != HTNOWHERE)
+ return frame_component;
+
+ int client_component = frame_->client_view()->NonClientHitTest(point);
+ if (client_component != HTNOWHERE)
+ return client_component;
+
+ // Then see if the point is within any of the window controls.
+ if (close_button_->visible() &&
+ close_button_->GetMirroredBounds().Contains(point))
+ return HTCLOSE;
+ if (size_button_->visible() &&
+ size_button_->GetMirroredBounds().Contains(point))
+ return HTMAXBUTTON;
+
+ // Caption is a safe default.
+ return HTCAPTION;
+}
+
+gfx::Size FramePainter::GetMinimumSize(views::NonClientFrameView* view) {
+ gfx::Size min_size = frame_->client_view()->GetMinimumSize();
+ // Ensure we can display the top of the caption area.
+ gfx::Rect client_bounds = view->GetBoundsForClientView();
+ min_size.Enlarge(0, client_bounds.y());
+ // Ensure we have enough space for the window icon and buttons. We allow
+ // the title string to collapse to zero width.
+ int title_width = GetTitleOffsetX() +
+ size_button_->width() + kSizeButtonOffsetX +
+ close_button_->width() + kCloseButtonOffsetX;
+ if (title_width > min_size.width())
+ min_size.set_width(title_width);
+ return min_size;
+}
+
+gfx::Size FramePainter::GetMaximumSize(views::NonClientFrameView* view) {
+ return frame_->client_view()->GetMaximumSize();
+}
+
+int FramePainter::GetRightInset() const {
+ gfx::Size close_size = close_button_->GetPreferredSize();
+ gfx::Size size_button_size = size_button_->GetPreferredSize();
+ int inset = close_size.width() + kCloseButtonOffsetX +
+ size_button_size.width() + kSizeButtonOffsetX;
+ return inset;
+}
+
+int FramePainter::GetThemeBackgroundXInset() const {
+ return kThemeFrameImageInsetX;
+}
+
+bool FramePainter::ShouldUseMinimalHeaderStyle(Themed header_themed) const {
+ // Use the minimalistic header style whenever |frame_| is maximized or
+ // fullscreen EXCEPT:
+ // - If the user has installed a theme with custom images for the header.
+ // - For windows which are not tracked by the workspace code (which are used
+ // for tab dragging).
+ return ((frame_->IsMaximized() || frame_->IsFullscreen()) &&
+ header_themed == THEMED_NO &&
+ GetTrackedByWorkspace(frame_->GetNativeWindow()));
+}
+
+void FramePainter::PaintHeader(views::NonClientFrameView* view,
+ gfx::Canvas* canvas,
+ HeaderMode header_mode,
+ int theme_frame_id,
+ int theme_frame_overlay_id) {
+ bool initial_paint = (previous_theme_frame_id_ == 0);
+ if (!initial_paint &&
+ (previous_theme_frame_id_ != theme_frame_id ||
+ previous_theme_frame_overlay_id_ != theme_frame_overlay_id)) {
+ aura::Window* parent = frame_->GetNativeWindow()->parent();
+ // Don't animate the header if the parent (a workspace) is already
+ // animating. Doing so results in continually painting during the animation
+ // and gives a slower frame rate.
+ // TODO(sky): expose a better way to determine this rather than assuming
+ // the parent is a workspace.
+ bool parent_animating = parent &&
+ (parent->layer()->GetAnimator()->IsAnimatingProperty(
+ ui::LayerAnimationElement::OPACITY) ||
+ parent->layer()->GetAnimator()->IsAnimatingProperty(
+ ui::LayerAnimationElement::VISIBILITY));
+ if (!parent_animating) {
+ crossfade_animation_.reset(new ui::SlideAnimation(this));
+ crossfade_theme_frame_id_ = previous_theme_frame_id_;
+ crossfade_theme_frame_overlay_id_ = previous_theme_frame_overlay_id_;
+ crossfade_opacity_ = previous_opacity_;
+ crossfade_animation_->SetSlideDuration(kActivationCrossfadeDurationMs);
+ crossfade_animation_->Show();
+ } else {
+ crossfade_animation_.reset();
+ }
+ }
+
+ int opacity =
+ GetHeaderOpacity(header_mode, theme_frame_id, theme_frame_overlay_id);
+ ui::ThemeProvider* theme_provider = frame_->GetThemeProvider();
+ gfx::ImageSkia* theme_frame = theme_provider->GetImageSkiaNamed(
+ theme_frame_id);
+ gfx::ImageSkia* theme_frame_overlay = NULL;
+ if (theme_frame_overlay_id != 0) {
+ theme_frame_overlay = theme_provider->GetImageSkiaNamed(
+ theme_frame_overlay_id);
+ }
+ header_frame_bounds_ = gfx::Rect(0, 0, view->width(), theme_frame->height());
+
+ int corner_radius = GetHeaderCornerRadius();
+ SkPaint paint;
+
+ if (crossfade_animation_.get() && crossfade_animation_->is_animating()) {
+ gfx::ImageSkia* crossfade_theme_frame =
+ theme_provider->GetImageSkiaNamed(crossfade_theme_frame_id_);
+ gfx::ImageSkia* crossfade_theme_frame_overlay = NULL;
+ if (crossfade_theme_frame_overlay_id_ != 0) {
+ crossfade_theme_frame_overlay = theme_provider->GetImageSkiaNamed(
+ crossfade_theme_frame_overlay_id_);
+ }
+ if (!crossfade_theme_frame ||
+ (crossfade_theme_frame_overlay_id_ != 0 &&
+ !crossfade_theme_frame_overlay)) {
+ // Reset the animation. This case occurs when the user switches the theme
+ // that they are using.
+ crossfade_animation_.reset();
+ paint.setAlpha(opacity);
+ } else {
+ double current_value = crossfade_animation_->GetCurrentValue();
+ int old_alpha = (1 - current_value) * crossfade_opacity_;
+ int new_alpha = current_value * opacity;
+
+ // Draw the old header background, clipping the corners to be rounded.
+ paint.setAlpha(old_alpha);
+ paint.setXfermodeMode(SkXfermode::kPlus_Mode);
+ PaintFrameImagesInRoundRect(canvas,
+ crossfade_theme_frame,
+ crossfade_theme_frame_overlay,
+ paint,
+ header_frame_bounds_,
+ corner_radius,
+ GetThemeBackgroundXInset());
+
+ paint.setAlpha(new_alpha);
+ }
+ } else {
+ paint.setAlpha(opacity);
+ }
+
+ // Draw the header background, clipping the corners to be rounded.
+ PaintFrameImagesInRoundRect(canvas,
+ theme_frame,
+ theme_frame_overlay,
+ paint,
+ header_frame_bounds_,
+ corner_radius,
+ GetThemeBackgroundXInset());
+
+ previous_theme_frame_id_ = theme_frame_id;
+ previous_theme_frame_overlay_id_ = theme_frame_overlay_id;
+ previous_opacity_ = opacity;
+
+ // Separator between the maximize and close buttons. It overlaps the left
+ // edge of the close button.
+ gfx::Rect divider(close_button_->x(), close_button_->y(),
+ button_separator_->width(), close_button_->height());
+ canvas->DrawImageInt(*button_separator_,
+ view->GetMirroredXForRect(divider),
+ close_button_->y());
+
+ // We don't need the extra lightness in the edges when we're at the top edge
+ // of the screen or when the header's corners are not rounded.
+ //
+ // TODO(sky): this isn't quite right. What we really want is a method that
+ // returns bounds ignoring transforms on certain windows (such as workspaces)
+ // and is relative to the root.
+ if (frame_->GetNativeWindow()->bounds().y() == 0 || corner_radius == 0)
+ return;
+
+ // Draw the top corners and edge.
+ int top_left_height = top_left_corner_->height();
+ canvas->DrawImageInt(*top_left_corner_,
+ 0, 0, top_left_corner_->width(), top_left_height,
+ 0, 0, top_left_corner_->width(), top_left_height,
+ false);
+ canvas->TileImageInt(*top_edge_,
+ top_left_corner_->width(),
+ 0,
+ view->width() - top_left_corner_->width() - top_right_corner_->width(),
+ top_edge_->height());
+ int top_right_height = top_right_corner_->height();
+ canvas->DrawImageInt(*top_right_corner_,
+ 0, 0,
+ top_right_corner_->width(), top_right_height,
+ view->width() - top_right_corner_->width(), 0,
+ top_right_corner_->width(), top_right_height,
+ false);
+
+ // Header left edge.
+ int header_left_height = theme_frame->height() - top_left_height;
+ canvas->TileImageInt(*header_left_edge_,
+ 0, top_left_height,
+ header_left_edge_->width(), header_left_height);
+
+ // Header right edge.
+ int header_right_height = theme_frame->height() - top_right_height;
+ canvas->TileImageInt(*header_right_edge_,
+ view->width() - header_right_edge_->width(),
+ top_right_height,
+ header_right_edge_->width(),
+ header_right_height);
+
+ // We don't draw edges around the content area. Web content goes flush
+ // to the edge of the window.
+}
+
+void FramePainter::PaintHeaderContentSeparator(views::NonClientFrameView* view,
+ gfx::Canvas* canvas) {
+ // Paint the line just above the content area.
+ gfx::Rect client_bounds = view->GetBoundsForClientView();
+ canvas->FillRect(gfx::Rect(client_bounds.x(),
+ client_bounds.y() - kHeaderContentSeparatorSize,
+ client_bounds.width(),
+ kHeaderContentSeparatorSize),
+ kHeaderContentSeparatorColor);
+}
+
+int FramePainter::HeaderContentSeparatorSize() const {
+ return kHeaderContentSeparatorSize;
+}
+
+void FramePainter::PaintTitleBar(views::NonClientFrameView* view,
+ gfx::Canvas* canvas,
+ const gfx::Font& title_font) {
+ // The window icon is painted by its own views::View.
+ views::WidgetDelegate* delegate = frame_->widget_delegate();
+ if (delegate && delegate->ShouldShowWindowTitle()) {
+ gfx::Rect title_bounds = GetTitleBounds(title_font);
+ SkColor title_color = frame_->IsMaximized() ?
+ kMaximizedWindowTitleTextColor : kNonMaximizedWindowTitleTextColor;
+ canvas->DrawStringInt(delegate->GetWindowTitle(),
+ title_font,
+ title_color,
+ view->GetMirroredXForRect(title_bounds),
+ title_bounds.y(),
+ title_bounds.width(),
+ title_bounds.height(),
+ gfx::Canvas::NO_SUBPIXEL_RENDERING);
+ }
+}
+
+void FramePainter::LayoutHeader(views::NonClientFrameView* view,
+ bool shorter_layout) {
+ // The new assets only make sense if the window is actually maximized or
+ // fullscreen.
+ if (shorter_layout &&
+ (frame_->IsMaximized() || frame_->IsFullscreen()) &&
+ GetTrackedByWorkspace(frame_->GetNativeWindow())) {
+ SetButtonImages(close_button_,
+ IDR_AURA_WINDOW_MAXIMIZED_CLOSE2,
+ IDR_AURA_WINDOW_MAXIMIZED_CLOSE2_H,
+ IDR_AURA_WINDOW_MAXIMIZED_CLOSE2_P);
+ // The chat window cannot be restored but only minimized.
+ if (size_button_behavior_ == SIZE_BUTTON_MINIMIZES) {
+ SetButtonImages(size_button_,
+ IDR_AURA_WINDOW_MINIMIZE_SHORT,
+ IDR_AURA_WINDOW_MINIMIZE_SHORT_H,
+ IDR_AURA_WINDOW_MINIMIZE_SHORT_P);
+ } else {
+ SetButtonImages(size_button_,
+ IDR_AURA_WINDOW_MAXIMIZED_RESTORE2,
+ IDR_AURA_WINDOW_MAXIMIZED_RESTORE2_H,
+ IDR_AURA_WINDOW_MAXIMIZED_RESTORE2_P);
+ }
+ } else if (shorter_layout) {
+ SetButtonImages(close_button_,
+ IDR_AURA_WINDOW_MAXIMIZED_CLOSE,
+ IDR_AURA_WINDOW_MAXIMIZED_CLOSE_H,
+ IDR_AURA_WINDOW_MAXIMIZED_CLOSE_P);
+ // The chat window cannot be restored but only minimized.
+ if (size_button_behavior_ == SIZE_BUTTON_MINIMIZES) {
+ SetButtonImages(size_button_,
+ IDR_AURA_WINDOW_MINIMIZE_SHORT,
+ IDR_AURA_WINDOW_MINIMIZE_SHORT_H,
+ IDR_AURA_WINDOW_MINIMIZE_SHORT_P);
+ } else {
+ SetButtonImages(size_button_,
+ IDR_AURA_WINDOW_MAXIMIZED_RESTORE,
+ IDR_AURA_WINDOW_MAXIMIZED_RESTORE_H,
+ IDR_AURA_WINDOW_MAXIMIZED_RESTORE_P);
+ }
+ } else {
+ SetButtonImages(close_button_,
+ IDR_AURA_WINDOW_CLOSE,
+ IDR_AURA_WINDOW_CLOSE_H,
+ IDR_AURA_WINDOW_CLOSE_P);
+ SetButtonImages(size_button_,
+ IDR_AURA_WINDOW_MAXIMIZE,
+ IDR_AURA_WINDOW_MAXIMIZE_H,
+ IDR_AURA_WINDOW_MAXIMIZE_P);
+ }
+
+ gfx::Size close_size = close_button_->GetPreferredSize();
+ close_button_->SetBounds(
+ view->width() - close_size.width() - kCloseButtonOffsetX,
+ kCloseButtonOffsetY,
+ close_size.width(),
+ close_size.height());
+
+ gfx::Size size_button_size = size_button_->GetPreferredSize();
+ size_button_->SetBounds(
+ close_button_->x() - size_button_size.width() - kSizeButtonOffsetX,
+ close_button_->y(),
+ size_button_size.width(),
+ size_button_size.height());
+
+ if (window_icon_) {
+ // Vertically center the window icon with respect to the close button.
+ int icon_offset_y = GetCloseButtonCenterY() - window_icon_->height() / 2;
+ window_icon_->SetBounds(kIconOffsetX, icon_offset_y, kIconSize, kIconSize);
+ }
+}
+
+void FramePainter::SchedulePaintForTitle(const gfx::Font& title_font) {
+ frame_->non_client_view()->SchedulePaintInRect(GetTitleBounds(title_font));
+}
+
+void FramePainter::OnThemeChanged() {
+ // We do not cache the images for |previous_theme_frame_id_| and
+ // |previous_theme_frame_overlay_id_|. Changing the theme changes the images
+ // returned from ui::ThemeProvider for |previous_theme_frame_id_|
+ // and |previous_theme_frame_overlay_id_|. Reset the image ids to prevent
+ // starting a crossfade animation with these images.
+ previous_theme_frame_id_ = 0;
+ previous_theme_frame_overlay_id_ = 0;
+
+ if (crossfade_animation_.get() && crossfade_animation_->is_animating()) {
+ crossfade_animation_.reset();
+ frame_->non_client_view()->SchedulePaintInRect(header_frame_bounds_);
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// aura::WindowObserver overrides:
+
+void FramePainter::OnWindowPropertyChanged(aura::Window* window,
+ const void* key,
+ intptr_t old) {
+ // When 'kWindowTrackedByWorkspaceKey' changes, we are going to paint the
+ // header differently. Schedule a paint to ensure everything is updated
+ // correctly.
+ if (key == internal::kWindowTrackedByWorkspaceKey &&
+ GetTrackedByWorkspace(window)) {
+ frame_->non_client_view()->SchedulePaint();
+ }
+
+ if (key != aura::client::kShowStateKey)
+ return;
+
+ // Maximized and fullscreen windows don't want resize handles overlapping the
+ // content area, because when the user moves the cursor to the right screen
+ // edge we want them to be able to hit the scroll bar.
+ if (ash::wm::IsWindowMaximized(window) ||
+ ash::wm::IsWindowFullscreen(window)) {
+ window->set_hit_test_bounds_override_inner(gfx::Insets());
+ } else {
+ window->set_hit_test_bounds_override_inner(
+ gfx::Insets(kResizeInsideBoundsSize, kResizeInsideBoundsSize,
+ kResizeInsideBoundsSize, kResizeInsideBoundsSize));
+ }
+}
+
+void FramePainter::OnWindowVisibilityChanged(aura::Window* window,
+ bool visible) {
+ // OnWindowVisibilityChanged can be called for the child windows of |window_|.
+ if (window != window_)
+ return;
+
+ // Window visibility change may trigger the change of window solo-ness in a
+ // different window.
+ UpdateSoloWindowInRoot(window_->GetRootWindow(), visible ? NULL : window_);
+}
+
+void FramePainter::OnWindowDestroying(aura::Window* destroying) {
+ DCHECK_EQ(window_, destroying);
+
+ // Must be removed here and not in the destructor, as the aura::Window is
+ // already destroyed when our destructor runs.
+ window_->RemoveObserver(this);
+
+ // If we have two or more windows open and we close this one, we might trigger
+ // the solo window appearance for another window.
+ UpdateSoloWindowInRoot(window_->GetRootWindow(), window_);
+
+ window_ = NULL;
+}
+
+void FramePainter::OnWindowBoundsChanged(aura::Window* window,
+ const gfx::Rect& old_bounds,
+ const gfx::Rect& new_bounds) {
+ // TODO(sky): this isn't quite right. What we really want is a method that
+ // returns bounds ignoring transforms on certain windows (such as workspaces).
+ if ((!frame_->IsMaximized() && !frame_->IsFullscreen()) &&
+ ((old_bounds.y() == 0 && new_bounds.y() != 0) ||
+ (old_bounds.y() != 0 && new_bounds.y() == 0))) {
+ SchedulePaintForHeader();
+ }
+}
+
+void FramePainter::OnWindowAddedToRootWindow(aura::Window* window) {
+ // Needs to trigger the window appearance change if the window moves across
+ // root windows and a solo window is already in the new root.
+ UpdateSoloWindowInRoot(window->GetRootWindow(), NULL /* ignore_window */);
+}
+
+void FramePainter::OnWindowRemovingFromRootWindow(aura::Window* window) {
+ // Needs to trigger the window appearance change if the window moves across
+ // root windows and only one window is left in the previous root. Because
+ // |window| is not yet moved, |window| has to be ignored.
+ UpdateSoloWindowInRoot(window->GetRootWindow(), window);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// ui::AnimationDelegate overrides:
+
+void FramePainter::AnimationProgressed(const ui::Animation* animation) {
+ frame_->non_client_view()->SchedulePaintInRect(header_frame_bounds_);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// FramePainter, private:
+
+void FramePainter::SetButtonImages(views::ImageButton* button,
+ int normal_image_id,
+ int hot_image_id,
+ int pushed_image_id) {
+ ui::ThemeProvider* theme_provider = frame_->GetThemeProvider();
+ button->SetImage(views::CustomButton::STATE_NORMAL,
+ theme_provider->GetImageSkiaNamed(normal_image_id));
+ button->SetImage(views::CustomButton::STATE_HOVERED,
+ theme_provider->GetImageSkiaNamed(hot_image_id));
+ button->SetImage(views::CustomButton::STATE_PRESSED,
+ theme_provider->GetImageSkiaNamed(pushed_image_id));
+}
+
+void FramePainter::SetToggledButtonImages(views::ToggleImageButton* button,
+ int normal_image_id,
+ int hot_image_id,
+ int pushed_image_id) {
+ ui::ThemeProvider* theme_provider = frame_->GetThemeProvider();
+ button->SetToggledImage(views::CustomButton::STATE_NORMAL,
+ theme_provider->GetImageSkiaNamed(normal_image_id));
+ button->SetToggledImage(views::CustomButton::STATE_HOVERED,
+ theme_provider->GetImageSkiaNamed(hot_image_id));
+ button->SetToggledImage(views::CustomButton::STATE_PRESSED,
+ theme_provider->GetImageSkiaNamed(pushed_image_id));
+}
+
+int FramePainter::GetTitleOffsetX() const {
+ return window_icon_ ?
+ window_icon_->bounds().right() + kTitleIconOffsetX :
+ kTitleNoIconOffsetX;
+}
+
+int FramePainter::GetCloseButtonCenterY() const {
+ return close_button_->y() + close_button_->height() / 2;
+}
+
+int FramePainter::GetHeaderCornerRadius() const {
+ // Use square corners for maximized and fullscreen windows when they are
+ // tracked by the workspace code. (Windows which are not tracked by the
+ // workspace code are used for tab dragging.)
+ bool square_corners = ((frame_->IsMaximized() || frame_->IsFullscreen())) &&
+ GetTrackedByWorkspace(frame_->GetNativeWindow());
+ const int kCornerRadius = 2;
+ return square_corners ? 0 : kCornerRadius;
+}
+
+int FramePainter::GetHeaderOpacity(
+ HeaderMode header_mode,
+ int theme_frame_id,
+ int theme_frame_overlay_id) const {
+ // User-provided themes are painted fully opaque.
+ ui::ThemeProvider* theme_provider = frame_->GetThemeProvider();
+ if (theme_provider->HasCustomImage(theme_frame_id) ||
+ (theme_frame_overlay_id != 0 &&
+ theme_provider->HasCustomImage(theme_frame_overlay_id))) {
+ return kFullyOpaque;
+ }
+
+ // The header is fully opaque when using the minimalistic header style.
+ if (ShouldUseMinimalHeaderStyle(THEMED_NO))
+ return kFullyOpaque;
+
+ // Single browser window is very transparent.
+ if (UseSoloWindowHeader())
+ return kSoloWindowOpacity;
+
+ // Otherwise, change transparency based on window activation status.
+ if (header_mode == ACTIVE)
+ return kActiveWindowOpacity;
+ return kInactiveWindowOpacity;
+}
+
+bool FramePainter::UseSoloWindowHeader() const {
+ // Don't use transparent headers for panels, pop-ups, etc.
+ if (!IsSoloWindowHeaderCandidate(window_))
+ return false;
+ aura::RootWindow* root = window_->GetRootWindow();
+ if (!root || root->GetProperty(internal::kIgnoreSoloWindowFramePainterPolicy))
+ return false;
+ // Don't recompute every time, as it would require many window property
+ // lookups.
+ return root->GetProperty(internal::kSoloWindowHeaderKey);
+}
+
+// static
+bool FramePainter::UseSoloWindowHeaderInRoot(RootWindow* root_window,
+ Window* ignore_window) {
+ int visible_window_count = 0;
+ std::vector<Window*> windows = GetWindowsForSoloHeaderUpdate(root_window);
+ for (std::vector<Window*>::const_iterator it = windows.begin();
+ it != windows.end();
+ ++it) {
+ Window* window = *it;
+ // Various sorts of windows "don't count" for this computation.
+ if (ignore_window == window ||
+ !IsSoloWindowHeaderCandidate(window) ||
+ !IsVisibleToRoot(window))
+ continue;
+ if (wm::IsWindowMaximized(window))
+ return false;
+ ++visible_window_count;
+ if (visible_window_count > 1)
+ return false;
+ }
+ // Count must be tested because all windows might be "don't count" windows
+ // in the loop above.
+ return visible_window_count == 1;
+}
+
+// static
+void FramePainter::UpdateSoloWindowInRoot(RootWindow* root,
+ Window* ignore_window) {
+#if defined(OS_WIN)
+ // Non-Ash Windows doesn't do solo-window counting for transparency effects,
+ // as the desktop background and window frames are managed by the OS.
+ if (!ash::Shell::HasInstance())
+ return;
+#endif
+ if (!root)
+ return;
+ bool old_solo_header = root->GetProperty(internal::kSoloWindowHeaderKey);
+ bool new_solo_header = UseSoloWindowHeaderInRoot(root, ignore_window);
+ if (old_solo_header == new_solo_header)
+ return;
+ root->SetProperty(internal::kSoloWindowHeaderKey, new_solo_header);
+ // Invalidate all the window frames in the desktop. There should only be
+ // a few.
+ std::vector<Window*> windows = GetWindowsForSoloHeaderUpdate(root);
+ for (std::vector<Window*>::const_iterator it = windows.begin();
+ it != windows.end();
+ ++it) {
+ Widget* widget = Widget::GetWidgetForNativeWindow(*it);
+ if (widget && widget->non_client_view())
+ widget->non_client_view()->SchedulePaint();
+ }
+}
+
+void FramePainter::SchedulePaintForHeader() {
+ int top_left_height = top_left_corner_->height();
+ int top_right_height = top_right_corner_->height();
+ frame_->non_client_view()->SchedulePaintInRect(
+ gfx::Rect(0, 0, frame_->non_client_view()->width(),
+ std::max(top_left_height, top_right_height)));
+}
+
+gfx::Rect FramePainter::GetTitleBounds(const gfx::Font& title_font) {
+ int title_x = GetTitleOffsetX();
+ // Center the text with respect to the close button. This way it adapts to
+ // the caption height and aligns exactly with the window icon. Don't use
+ // |window_icon_| for this computation as it may be NULL.
+ int title_y = GetCloseButtonCenterY() - title_font.GetHeight() / 2;
+ return gfx::Rect(
+ title_x,
+ std::max(0, title_y),
+ std::max(0, size_button_->x() - kTitleLogoSpacing - title_x),
+ title_font.GetHeight());
+}
+
+} // namespace ash
diff --git a/chromium/ash/wm/frame_painter.h b/chromium/ash/wm/frame_painter.h
new file mode 100644
index 00000000000..e79b012d2e9
--- /dev/null
+++ b/chromium/ash/wm/frame_painter.h
@@ -0,0 +1,256 @@
+// Copyright (c) 2012 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 ASH_WM_FRAME_PAINTER_H_
+#define ASH_WM_FRAME_PAINTER_H_
+
+#include "ash/ash_export.h"
+#include "base/basictypes.h"
+#include "base/compiler_specific.h" // OVERRIDE
+#include "base/gtest_prod_util.h"
+#include "base/memory/scoped_ptr.h"
+#include "ui/aura/window_observer.h"
+#include "ui/base/animation/animation_delegate.h"
+#include "ui/gfx/rect.h"
+
+namespace aura {
+class RootWindow;
+class Window;
+}
+namespace gfx {
+class Canvas;
+class Font;
+class ImageSkia;
+class Point;
+class Size;
+}
+namespace ui {
+class SlideAnimation;
+}
+namespace views {
+class ImageButton;
+class NonClientFrameView;
+class ToggleImageButton;
+class View;
+class Widget;
+}
+
+namespace ash {
+
+// Helper class for painting window frames. Exists to share code between
+// various implementations of views::NonClientFrameView. Canonical source of
+// layout constants for Ash window frames.
+class ASH_EXPORT FramePainter : public aura::WindowObserver,
+ public ui::AnimationDelegate {
+ public:
+ // Opacity values for the window header in various states, from 0 to 255.
+ static int kActiveWindowOpacity;
+ static int kInactiveWindowOpacity;
+ static int kSoloWindowOpacity;
+
+ enum HeaderMode {
+ ACTIVE,
+ INACTIVE
+ };
+
+ enum Themed {
+ THEMED_YES,
+ THEMED_NO
+ };
+
+ // What happens when the |size_button_| is pressed.
+ enum SizeButtonBehavior {
+ SIZE_BUTTON_MINIMIZES,
+ SIZE_BUTTON_MAXIMIZES
+ };
+
+ FramePainter();
+ virtual ~FramePainter();
+
+ // |frame| and buttons are used for layout and are not owned.
+ void Init(views::Widget* frame,
+ views::View* window_icon,
+ views::ImageButton* size_button,
+ views::ImageButton* close_button,
+ SizeButtonBehavior behavior);
+
+ // Updates the solo-window transparent header appearance for all windows
+ // using frame painters in |root_window|.
+ static void UpdateSoloWindowHeader(aura::RootWindow* root_window);
+
+ // Helpers for views::NonClientFrameView implementations.
+ gfx::Rect GetBoundsForClientView(int top_height,
+ const gfx::Rect& window_bounds) const;
+ gfx::Rect GetWindowBoundsForClientBounds(
+ int top_height,
+ const gfx::Rect& client_bounds) const;
+ int NonClientHitTest(views::NonClientFrameView* view,
+ const gfx::Point& point);
+ gfx::Size GetMinimumSize(views::NonClientFrameView* view);
+ gfx::Size GetMaximumSize(views::NonClientFrameView* view);
+
+ // Returns the inset from the right edge.
+ int GetRightInset() const;
+
+ // Returns the amount that the theme background should be inset.
+ int GetThemeBackgroundXInset() const;
+
+ // Returns true if the header should be painted using a minimalistic style.
+ bool ShouldUseMinimalHeaderStyle(Themed header_themed) const;
+
+ // Paints the frame header.
+ // |theme_frame_overlay_id| is 0 if no overlay image should be used.
+ void PaintHeader(views::NonClientFrameView* view,
+ gfx::Canvas* canvas,
+ HeaderMode header_mode,
+ int theme_frame_id,
+ int theme_frame_overlay_id);
+
+ // Paints the header/content separator line. Exists as a separate function
+ // because some windows with complex headers (e.g. browsers with tab strips)
+ // need to draw their own line.
+ void PaintHeaderContentSeparator(views::NonClientFrameView* view,
+ gfx::Canvas* canvas);
+
+ // Returns size of the header/content separator line in pixels.
+ int HeaderContentSeparatorSize() const;
+
+ // Paint the title bar, primarily the title string.
+ void PaintTitleBar(views::NonClientFrameView* view,
+ gfx::Canvas* canvas,
+ const gfx::Font& title_font);
+
+ // Performs layout for the header based on whether we want the shorter
+ // appearance. |shorter_layout| is typically used for maximized windows, but
+ // not always.
+ void LayoutHeader(views::NonClientFrameView* view, bool shorter_layout);
+
+ // Schedule a re-paint of the entire title.
+ void SchedulePaintForTitle(const gfx::Font& title_font);
+
+ // Called when the browser theme changes.
+ void OnThemeChanged();
+
+ // aura::WindowObserver overrides:
+ virtual void OnWindowPropertyChanged(aura::Window* window,
+ const void* key,
+ intptr_t old) OVERRIDE;
+ virtual void OnWindowVisibilityChanged(aura::Window* window,
+ bool visible) OVERRIDE;
+ virtual void OnWindowDestroying(aura::Window* window) OVERRIDE;
+ virtual void OnWindowBoundsChanged(aura::Window* window,
+ const gfx::Rect& old_bounds,
+ const gfx::Rect& new_bounds) OVERRIDE;
+ virtual void OnWindowAddedToRootWindow(aura::Window* window) OVERRIDE;
+ virtual void OnWindowRemovingFromRootWindow(aura::Window* window) OVERRIDE;
+
+ // Overridden from ui::AnimationDelegate
+ virtual void AnimationProgressed(const ui::Animation* animation) OVERRIDE;
+
+ private:
+ FRIEND_TEST_ALL_PREFIXES(FramePainterTest, CreateAndDeleteSingleWindow);
+ FRIEND_TEST_ALL_PREFIXES(FramePainterTest, UseSoloWindowHeader);
+ FRIEND_TEST_ALL_PREFIXES(FramePainterTest, UseSoloWindowHeaderWithApp);
+ FRIEND_TEST_ALL_PREFIXES(FramePainterTest, UseSoloWindowHeaderWithPanel);
+ FRIEND_TEST_ALL_PREFIXES(FramePainterTest, UseSoloWindowHeaderModal);
+ FRIEND_TEST_ALL_PREFIXES(FramePainterTest, UseSoloWindowHeaderConstrained);
+ FRIEND_TEST_ALL_PREFIXES(FramePainterTest, UseSoloWindowHeaderNotDrawn);
+ FRIEND_TEST_ALL_PREFIXES(FramePainterTest, UseSoloWindowHeaderMultiDisplay);
+ FRIEND_TEST_ALL_PREFIXES(FramePainterTest, GetHeaderOpacity);
+ FRIEND_TEST_ALL_PREFIXES(FramePainterTest, TitleIconAlignment);
+ FRIEND_TEST_ALL_PREFIXES(FramePainterTest, ChildWindowVisibility);
+
+ // Sets the images for a button based on IDs from the |frame_| theme provider.
+ void SetButtonImages(views::ImageButton* button,
+ int normal_image_id,
+ int hot_image_id,
+ int pushed_image_id);
+
+ // Sets the toggled-state button images for a button based on IDs from the
+ // |frame_| theme provider.
+ void SetToggledButtonImages(views::ToggleImageButton* button,
+ int normal_image_id,
+ int hot_image_id,
+ int pushed_image_id);
+
+ // Returns the offset between window left edge and title string.
+ int GetTitleOffsetX() const;
+
+ // Returns the vertical center of the close button in window coordinates.
+ int GetCloseButtonCenterY() const;
+
+ // Returns the opacity value used to paint the header.
+ // |theme_frame_overlay_id| is 0 if no overlay image is used.
+ int GetHeaderOpacity(HeaderMode header_mode,
+ int theme_frame_id,
+ int theme_frame_overlay_id) const;
+
+ // Returns the radius of the header's top corners.
+ int GetHeaderCornerRadius() const;
+
+ // Adjust frame operations for left / right maximized modes.
+ int AdjustFrameHitCodeForMaximizedModes(int hit_code);
+
+ // Returns true if |window_->GetRootWindow()| should be drawing transparent
+ // window headers.
+ bool UseSoloWindowHeader() const;
+
+ // Returns true if |root_window| has exactly one visible, normal-type window.
+ // It ignores |ignore_window| while calculating the number of windows.
+ // Pass NULL for |ignore_window| to consider all windows.
+ static bool UseSoloWindowHeaderInRoot(aura::RootWindow* root_window,
+ aura::Window* ignore_window);
+
+ // Updates the solo-window transparent header appearance for all windows in
+ // |root_window|. If |ignore_window| is not NULL it is ignored for when
+ // counting visible windows. This is useful for updates when a window is about
+ // to be closed or is moving to another root. If the solo window status
+ // changes it schedules paints as necessary.
+ static void UpdateSoloWindowInRoot(aura::RootWindow* root_window,
+ aura::Window* ignore_window);
+
+ // Schedules a paint for the header. Used when transitioning from no header to
+ // a header (or other way around).
+ void SchedulePaintForHeader();
+
+ // Get the bounds for the title. The provided |title_font| is used to
+ // determine the correct dimensions.
+ gfx::Rect GetTitleBounds(const gfx::Font& title_font);
+
+ // Not owned
+ views::Widget* frame_;
+ views::View* window_icon_; // May be NULL.
+ views::ImageButton* size_button_;
+ views::ImageButton* close_button_;
+ aura::Window* window_;
+
+ // Window frame header/caption parts.
+ const gfx::ImageSkia* button_separator_;
+ const gfx::ImageSkia* top_left_corner_;
+ const gfx::ImageSkia* top_edge_;
+ const gfx::ImageSkia* top_right_corner_;
+ const gfx::ImageSkia* header_left_edge_;
+ const gfx::ImageSkia* header_right_edge_;
+
+ // Image ids and opacity last used for painting header.
+ int previous_theme_frame_id_;
+ int previous_theme_frame_overlay_id_;
+ int previous_opacity_;
+
+ // Image ids and opacity we are crossfading from.
+ int crossfade_theme_frame_id_;
+ int crossfade_theme_frame_overlay_id_;
+ int crossfade_opacity_;
+
+ gfx::Rect header_frame_bounds_;
+ scoped_ptr<ui::SlideAnimation> crossfade_animation_;
+
+ SizeButtonBehavior size_button_behavior_;
+
+ DISALLOW_COPY_AND_ASSIGN(FramePainter);
+};
+
+} // namespace ash
+
+#endif // ASH_WM_FRAME_PAINTER_H_
diff --git a/chromium/ash/wm/frame_painter_unittest.cc b/chromium/ash/wm/frame_painter_unittest.cc
new file mode 100644
index 00000000000..ee9313fcf10
--- /dev/null
+++ b/chromium/ash/wm/frame_painter_unittest.cc
@@ -0,0 +1,693 @@
+// Copyright (c) 2012 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.
+
+#include "ash/wm/frame_painter.h"
+
+#include "ash/ash_constants.h"
+#include "ash/shell.h"
+#include "ash/shell_window_ids.h"
+#include "ash/test/ash_test_base.h"
+#include "ash/wm/property_util.h"
+#include "ash/wm/window_properties.h"
+#include "ash/wm/window_util.h"
+#include "base/memory/scoped_ptr.h"
+#include "grit/ash_resources.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/aura/client/aura_constants.h"
+#include "ui/aura/root_window.h"
+#include "ui/aura/window_observer.h"
+#include "ui/base/hit_test.h"
+#include "ui/base/theme_provider.h"
+#include "ui/gfx/font.h"
+#include "ui/gfx/screen.h"
+#include "ui/views/controls/button/button.h"
+#include "ui/views/controls/button/image_button.h"
+#include "ui/views/widget/widget.h"
+#include "ui/views/widget/widget_delegate.h"
+#include "ui/views/window/non_client_view.h"
+
+using ash::FramePainter;
+using ui::ThemeProvider;
+using views::Button;
+using views::ImageButton;
+using views::NonClientFrameView;
+using views::ToggleImageButton;
+using views::Widget;
+
+namespace {
+
+bool ImagesMatch(ImageButton* button,
+ int normal_image_id,
+ int hovered_image_id,
+ int pressed_image_id) {
+ ThemeProvider* theme = button->GetWidget()->GetThemeProvider();
+ gfx::ImageSkia* normal = theme->GetImageSkiaNamed(normal_image_id);
+ gfx::ImageSkia* hovered = theme->GetImageSkiaNamed(hovered_image_id);
+ gfx::ImageSkia* pressed = theme->GetImageSkiaNamed(pressed_image_id);
+ return button->GetImage(Button::STATE_NORMAL).BackedBySameObjectAs(*normal) &&
+ button->GetImage(Button::STATE_HOVERED).BackedBySameObjectAs(*hovered) &&
+ button->GetImage(Button::STATE_PRESSED).BackedBySameObjectAs(*pressed);
+}
+
+class ResizableWidgetDelegate : public views::WidgetDelegate {
+ public:
+ ResizableWidgetDelegate(views::Widget* widget) {
+ widget_ = widget;
+ }
+
+ virtual bool CanResize() const OVERRIDE { return true; }
+ // Implementations of the widget class.
+ virtual views::Widget* GetWidget() OVERRIDE { return widget_; }
+ virtual const views::Widget* GetWidget() const OVERRIDE { return widget_; }
+ virtual void DeleteDelegate() OVERRIDE {
+ delete this;
+ }
+
+ private:
+ views::Widget* widget_;
+
+ DISALLOW_COPY_AND_ASSIGN(ResizableWidgetDelegate);
+};
+
+class WindowRepaintChecker : public aura::WindowObserver {
+ public:
+ explicit WindowRepaintChecker(aura::Window* window)
+ : is_paint_scheduled_(false) {
+ window->AddObserver(this);
+ }
+ virtual ~WindowRepaintChecker() {
+ }
+
+ bool IsPaintScheduledAndReset() {
+ bool result = is_paint_scheduled_;
+ is_paint_scheduled_ = false;
+ return result;
+ }
+
+ private:
+ // aura::WindowObserver overrides:
+ virtual void OnWindowPaintScheduled(aura::Window* window,
+ const gfx::Rect& region) OVERRIDE {
+ is_paint_scheduled_ = true;
+ }
+ virtual void OnWindowDestroying(aura::Window* window) OVERRIDE {
+ window->RemoveObserver(this);
+ }
+
+ bool is_paint_scheduled_;
+
+ DISALLOW_COPY_AND_ASSIGN(WindowRepaintChecker);
+};
+
+// Modifies the values of kInactiveWindowOpacity, kActiveWindowOpacity, and
+// kSoloWindowOpacity for the lifetime of the class. This is useful so that
+// the constants each have different values.
+class ScopedOpacityConstantModifier {
+ public:
+ ScopedOpacityConstantModifier()
+ : initial_active_window_opacity_(
+ ash::FramePainter::kActiveWindowOpacity),
+ initial_inactive_window_opacity_(
+ ash::FramePainter::kInactiveWindowOpacity),
+ initial_solo_window_opacity_(ash::FramePainter::kSoloWindowOpacity) {
+ ash::FramePainter::kActiveWindowOpacity = 100;
+ ash::FramePainter::kInactiveWindowOpacity = 120;
+ ash::FramePainter::kSoloWindowOpacity = 140;
+ }
+ ~ScopedOpacityConstantModifier() {
+ ash::FramePainter::kActiveWindowOpacity = initial_active_window_opacity_;
+ ash::FramePainter::kInactiveWindowOpacity =
+ initial_inactive_window_opacity_;
+ ash::FramePainter::kSoloWindowOpacity = initial_solo_window_opacity_;
+ }
+
+ private:
+ int initial_active_window_opacity_;
+ int initial_inactive_window_opacity_;
+ int initial_solo_window_opacity_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScopedOpacityConstantModifier);
+};
+
+// Creates a new FramePainter with empty buttons. Caller owns the memory.
+FramePainter* CreateTestPainter(Widget* widget) {
+ FramePainter* painter = new FramePainter();
+ ImageButton* size_button = new ImageButton(NULL);
+ ImageButton* close_button = new ImageButton(NULL);
+ // Add the buttons to the widget's non-client frame view so they will be
+ // deleted when the widget is destroyed.
+ NonClientFrameView* frame_view = widget->non_client_view()->frame_view();
+ frame_view->AddChildView(size_button);
+ frame_view->AddChildView(close_button);
+ painter->Init(widget,
+ NULL,
+ size_button,
+ close_button,
+ FramePainter::SIZE_BUTTON_MAXIMIZES);
+ return painter;
+}
+
+} // namespace
+
+namespace ash {
+
+class FramePainterTest : public ash::test::AshTestBase {
+ public:
+ // Creates a test widget that owns its native widget.
+ Widget* CreateTestWidget() {
+ Widget* widget = new Widget;
+ Widget::InitParams params;
+ params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
+ params.context = CurrentContext();
+ widget->Init(params);
+ return widget;
+ }
+
+ Widget* CreateAlwaysOnTopWidget() {
+ Widget* widget = new Widget;
+ Widget::InitParams params;
+ params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
+ params.context = CurrentContext();
+ params.keep_on_top = true;
+ widget->Init(params);
+ return widget;
+ }
+
+ Widget* CreatePanelWidget() {
+ Widget* widget = new Widget;
+ Widget::InitParams params;
+ params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
+ params.context = CurrentContext();
+ params.type = Widget::InitParams::TYPE_PANEL;
+ widget->Init(params);
+ return widget;
+ }
+
+ Widget* CreateResizableWidget() {
+ Widget* widget = new Widget;
+ Widget::InitParams params;
+ params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
+ params.context = CurrentContext();
+ params.keep_on_top = true;
+ params.delegate = new ResizableWidgetDelegate(widget);
+ params.type = Widget::InitParams::TYPE_WINDOW;
+ widget->Init(params);
+ return widget;
+ }
+};
+
+TEST_F(FramePainterTest, CreateAndDeleteSingleWindow) {
+ // Ensure that creating/deleting a window works well and doesn't cause
+ // crashes. See crbug.com/155634
+ aura::RootWindow* root = Shell::GetActiveRootWindow();
+
+ scoped_ptr<Widget> widget(CreateTestWidget());
+ scoped_ptr<FramePainter> painter(CreateTestPainter(widget.get()));
+ widget->Show();
+
+ // We only have one window, so it should use a solo header.
+ EXPECT_TRUE(painter->UseSoloWindowHeader());
+ EXPECT_TRUE(root->GetProperty(internal::kSoloWindowHeaderKey));
+
+ // Close the window.
+ widget.reset();
+ EXPECT_FALSE(root->GetProperty(internal::kSoloWindowHeaderKey));
+
+ // Recreate another window again.
+ widget.reset(CreateTestWidget());
+ painter.reset(CreateTestPainter(widget.get()));
+ widget->Show();
+ EXPECT_TRUE(painter->UseSoloWindowHeader());
+ EXPECT_TRUE(root->GetProperty(internal::kSoloWindowHeaderKey));
+}
+
+TEST_F(FramePainterTest, LayoutHeader) {
+ scoped_ptr<Widget> widget(CreateTestWidget());
+ ImageButton size_button(NULL);
+ ImageButton close_button(NULL);
+ NonClientFrameView* frame_view = widget->non_client_view()->frame_view();
+ frame_view->AddChildView(&size_button);
+ frame_view->AddChildView(&close_button);
+ scoped_ptr<FramePainter> painter(new FramePainter);
+ painter->Init(widget.get(),
+ NULL,
+ &size_button,
+ &close_button,
+ FramePainter::SIZE_BUTTON_MAXIMIZES);
+ widget->Show();
+
+ // Basic layout.
+ painter->LayoutHeader(frame_view, false);
+ EXPECT_TRUE(ImagesMatch(&close_button,
+ IDR_AURA_WINDOW_CLOSE,
+ IDR_AURA_WINDOW_CLOSE_H,
+ IDR_AURA_WINDOW_CLOSE_P));
+ EXPECT_TRUE(ImagesMatch(&size_button,
+ IDR_AURA_WINDOW_MAXIMIZE,
+ IDR_AURA_WINDOW_MAXIMIZE_H,
+ IDR_AURA_WINDOW_MAXIMIZE_P));
+
+ // Shorter layout.
+ painter->LayoutHeader(frame_view, true);
+ EXPECT_TRUE(ImagesMatch(&close_button,
+ IDR_AURA_WINDOW_MAXIMIZED_CLOSE,
+ IDR_AURA_WINDOW_MAXIMIZED_CLOSE_H,
+ IDR_AURA_WINDOW_MAXIMIZED_CLOSE_P));
+ EXPECT_TRUE(ImagesMatch(&size_button,
+ IDR_AURA_WINDOW_MAXIMIZED_RESTORE,
+ IDR_AURA_WINDOW_MAXIMIZED_RESTORE_H,
+ IDR_AURA_WINDOW_MAXIMIZED_RESTORE_P));
+
+ // Maximized shorter layout.
+ widget->Maximize();
+ painter->LayoutHeader(frame_view, true);
+ EXPECT_TRUE(ImagesMatch(&close_button,
+ IDR_AURA_WINDOW_MAXIMIZED_CLOSE2,
+ IDR_AURA_WINDOW_MAXIMIZED_CLOSE2_H,
+ IDR_AURA_WINDOW_MAXIMIZED_CLOSE2_P));
+ EXPECT_TRUE(ImagesMatch(&size_button,
+ IDR_AURA_WINDOW_MAXIMIZED_RESTORE2,
+ IDR_AURA_WINDOW_MAXIMIZED_RESTORE2_H,
+ IDR_AURA_WINDOW_MAXIMIZED_RESTORE2_P));
+
+ // Fullscreen can show the buttons during an immersive reveal, so it should
+ // use the same images as maximized.
+ widget->SetFullscreen(true);
+ painter->LayoutHeader(frame_view, true);
+ EXPECT_TRUE(ImagesMatch(&close_button,
+ IDR_AURA_WINDOW_MAXIMIZED_CLOSE2,
+ IDR_AURA_WINDOW_MAXIMIZED_CLOSE2_H,
+ IDR_AURA_WINDOW_MAXIMIZED_CLOSE2_P));
+ EXPECT_TRUE(ImagesMatch(&size_button,
+ IDR_AURA_WINDOW_MAXIMIZED_RESTORE2,
+ IDR_AURA_WINDOW_MAXIMIZED_RESTORE2_H,
+ IDR_AURA_WINDOW_MAXIMIZED_RESTORE2_P));
+}
+
+TEST_F(FramePainterTest, UseSoloWindowHeader) {
+ // Create a widget and a painter for it.
+ scoped_ptr<Widget> w1(CreateTestWidget());
+ scoped_ptr<FramePainter> p1(CreateTestPainter(w1.get()));
+ w1->Show();
+
+ // We only have one window, so it should use a solo header.
+ EXPECT_TRUE(p1->UseSoloWindowHeader());
+
+ // Create a second widget and painter.
+ scoped_ptr<Widget> w2(CreateTestWidget());
+ scoped_ptr<FramePainter> p2(CreateTestPainter(w2.get()));
+ w2->Show();
+
+ // Now there are two windows, so we should not use solo headers. This only
+ // needs to test |p1| because "solo window headers" are a per-root-window
+ // property.
+ EXPECT_FALSE(p1->UseSoloWindowHeader());
+
+ // Hide one window. Solo should be enabled.
+ w2->Hide();
+ EXPECT_TRUE(p1->UseSoloWindowHeader());
+
+ // Show that window. Solo should be disabled.
+ w2->Show();
+ EXPECT_FALSE(p1->UseSoloWindowHeader());
+
+ // Minimize the second window. Solo should be enabled.
+ w2->Minimize();
+ EXPECT_TRUE(p1->UseSoloWindowHeader());
+
+ // Close the minimized window.
+ w2.reset();
+ EXPECT_TRUE(p1->UseSoloWindowHeader());
+
+ // Open an always-on-top widget (which lives in a different container).
+ scoped_ptr<Widget> w3(CreateAlwaysOnTopWidget());
+ scoped_ptr<FramePainter> p3(CreateTestPainter(w3.get()));
+ w3->Show();
+ EXPECT_FALSE(p3->UseSoloWindowHeader());
+
+ // Close the always-on-top widget.
+ w3.reset();
+ EXPECT_TRUE(p1->UseSoloWindowHeader());
+}
+
+// An open V2 app window should cause browser windows not to use the
+// solo window header.
+TEST_F(FramePainterTest, UseSoloWindowHeaderWithApp) {
+ // Create a widget and a painter for it.
+ scoped_ptr<Widget> w1(CreateTestWidget());
+ scoped_ptr<FramePainter> p1(CreateTestPainter(w1.get()));
+ w1->Show();
+
+ // We only have one window, so it should use a solo header.
+ EXPECT_TRUE(p1->UseSoloWindowHeader());
+
+ // Simulate a V2 app window, which is part of the active workspace but does
+ // not have a frame painter.
+ scoped_ptr<Widget> w2(CreateTestWidget());
+ w2->Show();
+
+ // Now there are two windows, so we should not use solo headers.
+ EXPECT_FALSE(p1->UseSoloWindowHeader());
+
+ // Minimize the app window. The first window should go solo again.
+ w2->Minimize();
+ EXPECT_TRUE(p1->UseSoloWindowHeader());
+
+ // Restoring the app window turns off solo headers.
+ w2->Restore();
+ EXPECT_FALSE(p1->UseSoloWindowHeader());
+}
+
+// Panels should not "count" for computing solo window headers, and the panel
+// itself should always have an opaque header.
+TEST_F(FramePainterTest, UseSoloWindowHeaderWithPanel) {
+ // Create a widget and a painter for it.
+ scoped_ptr<Widget> w1(CreateTestWidget());
+ scoped_ptr<FramePainter> p1(CreateTestPainter(w1.get()));
+ w1->Show();
+
+ // We only have one window, so it should use a solo header.
+ EXPECT_TRUE(p1->UseSoloWindowHeader());
+
+ // Create a panel and a painter for it.
+ scoped_ptr<Widget> w2(CreatePanelWidget());
+ scoped_ptr<FramePainter> p2(CreateTestPainter(w2.get()));
+ w2->Show();
+
+ // Despite two windows, the first window should still be considered "solo"
+ // because panels aren't included in the computation.
+ EXPECT_TRUE(p1->UseSoloWindowHeader());
+
+ // The panel itself is not considered solo.
+ EXPECT_FALSE(p2->UseSoloWindowHeader());
+
+ // Even after closing the first window, the panel is still not considered
+ // solo.
+ w1.reset();
+ EXPECT_FALSE(p2->UseSoloWindowHeader());
+}
+
+// Modal dialogs should not use solo headers.
+TEST_F(FramePainterTest, UseSoloWindowHeaderModal) {
+ // Create a widget and a painter for it.
+ scoped_ptr<Widget> w1(CreateTestWidget());
+ scoped_ptr<FramePainter> p1(CreateTestPainter(w1.get()));
+ w1->Show();
+
+ // We only have one window, so it should use a solo header.
+ EXPECT_TRUE(p1->UseSoloWindowHeader());
+
+ // Create a fake modal window.
+ scoped_ptr<Widget> w2(CreateTestWidget());
+ scoped_ptr<FramePainter> p2(CreateTestPainter(w2.get()));
+ w2->GetNativeWindow()->SetProperty(aura::client::kModalKey,
+ ui::MODAL_TYPE_WINDOW);
+ w2->Show();
+
+ // Despite two windows, the first window should still be considered "solo"
+ // because modal windows aren't included in the computation.
+ EXPECT_TRUE(p1->UseSoloWindowHeader());
+
+ // The modal window itself is not considered solo.
+ EXPECT_FALSE(p2->UseSoloWindowHeader());
+}
+
+// Constrained windows should not use solo headers.
+TEST_F(FramePainterTest, UseSoloWindowHeaderConstrained) {
+ // Create a widget and a painter for it.
+ scoped_ptr<Widget> w1(CreateTestWidget());
+ scoped_ptr<FramePainter> p1(CreateTestPainter(w1.get()));
+ w1->Show();
+
+ // We only have one window, so it should use a solo header.
+ EXPECT_TRUE(p1->UseSoloWindowHeader());
+
+ // Create a fake constrained window.
+ scoped_ptr<Widget> w2(CreateTestWidget());
+ scoped_ptr<FramePainter> p2(CreateTestPainter(w2.get()));
+ w2->GetNativeWindow()->SetProperty(ash::kConstrainedWindowKey, true);
+ w2->Show();
+
+ // Despite two windows, the first window should still be considered "solo"
+ // because constrained windows aren't included in the computation.
+ EXPECT_TRUE(p1->UseSoloWindowHeader());
+
+ // The constrained window itself is not considered solo.
+ EXPECT_FALSE(p2->UseSoloWindowHeader());
+}
+
+// Non-drawing windows should not affect the solo computation.
+TEST_F(FramePainterTest, UseSoloWindowHeaderNotDrawn) {
+ // Create a widget and a painter for it.
+ scoped_ptr<Widget> widget(CreateTestWidget());
+ scoped_ptr<FramePainter> painter(CreateTestPainter(widget.get()));
+ widget->Show();
+
+ // We only have one window, so it should use a solo header.
+ EXPECT_TRUE(painter->UseSoloWindowHeader());
+
+ // Create non-drawing window similar to DragDropTracker.
+ scoped_ptr<aura::Window> window(new aura::Window(NULL));
+ window->SetType(aura::client::WINDOW_TYPE_NORMAL);
+ window->Init(ui::LAYER_NOT_DRAWN);
+ window->SetDefaultParentByRootWindow(
+ widget->GetNativeWindow()->GetRootWindow(), gfx::Rect());
+ window->Show();
+
+ // Despite two windows, the first window should still be considered "solo"
+ // because non-drawing windows aren't included in the computation.
+ EXPECT_TRUE(painter->UseSoloWindowHeader());
+}
+
+#if defined(OS_WIN)
+// Multiple displays are not supported on Windows Ash. http://crbug.com/165962
+#define MAYBE_UseSoloWindowHeaderMultiDisplay \
+ DISABLED_UseSoloWindowHeaderMultiDisplay
+#else
+#define MAYBE_UseSoloWindowHeaderMultiDisplay \
+ UseSoloWindowHeaderMultiDisplay
+#endif
+
+TEST_F(FramePainterTest, MAYBE_UseSoloWindowHeaderMultiDisplay) {
+ if (!SupportsMultipleDisplays())
+ return;
+
+ UpdateDisplay("1000x600,600x400");
+
+ // Create two widgets and painters for them.
+ scoped_ptr<Widget> w1(CreateTestWidget());
+ scoped_ptr<FramePainter> p1(CreateTestPainter(w1.get()));
+ w1->SetBounds(gfx::Rect(0, 0, 100, 100));
+ w1->Show();
+ WindowRepaintChecker checker1(w1->GetNativeWindow());
+ scoped_ptr<Widget> w2(CreateTestWidget());
+ scoped_ptr<FramePainter> p2(CreateTestPainter(w2.get()));
+ w2->SetBounds(gfx::Rect(0, 0, 100, 100));
+ w2->Show();
+ WindowRepaintChecker checker2(w2->GetNativeWindow());
+
+ // Now there are two windows in the same display, so we should not use solo
+ // headers.
+ EXPECT_FALSE(p1->UseSoloWindowHeader());
+ EXPECT_FALSE(p2->UseSoloWindowHeader());
+ EXPECT_TRUE(checker1.IsPaintScheduledAndReset());
+
+ // Moves the second window to the secondary display. Both w1/w2 should be
+ // solo.
+ w2->SetBounds(gfx::Rect(1200, 0, 100, 100));
+ EXPECT_TRUE(p1->UseSoloWindowHeader());
+ EXPECT_TRUE(p2->UseSoloWindowHeader());
+ EXPECT_TRUE(checker1.IsPaintScheduledAndReset());
+ EXPECT_TRUE(checker2.IsPaintScheduledAndReset());
+
+ // Open two more windows in the primary display.
+ scoped_ptr<Widget> w3(CreateTestWidget());
+ scoped_ptr<FramePainter> p3(CreateTestPainter(w3.get()));
+ w3->SetBounds(gfx::Rect(0, 0, 100, 100));
+ w3->Show();
+ scoped_ptr<Widget> w4(CreateTestWidget());
+ scoped_ptr<FramePainter> p4(CreateTestPainter(w4.get()));
+ w4->SetBounds(gfx::Rect(0, 0, 100, 100));
+ w4->Show();
+
+ // Because the primary display has two windows w1 and w3, they shouldn't be
+ // solo. w2 should be solo.
+ EXPECT_FALSE(p1->UseSoloWindowHeader());
+ EXPECT_TRUE(p2->UseSoloWindowHeader());
+ EXPECT_FALSE(p3->UseSoloWindowHeader());
+ EXPECT_FALSE(p4->UseSoloWindowHeader());
+ EXPECT_TRUE(checker1.IsPaintScheduledAndReset());
+
+ // Moves the w4 to the secondary display. Now the w2 shouldn't be solo
+ // anymore.
+ w4->SetBounds(gfx::Rect(1200, 0, 100, 100));
+ EXPECT_FALSE(p1->UseSoloWindowHeader());
+ EXPECT_FALSE(p2->UseSoloWindowHeader());
+ EXPECT_FALSE(p3->UseSoloWindowHeader());
+ EXPECT_FALSE(p4->UseSoloWindowHeader());
+ EXPECT_TRUE(checker2.IsPaintScheduledAndReset());
+
+ // Moves the w3 to the secondary display too. Now w1 should be solo again.
+ w3->SetBounds(gfx::Rect(1200, 0, 100, 100));
+ EXPECT_TRUE(p1->UseSoloWindowHeader());
+ EXPECT_FALSE(p2->UseSoloWindowHeader());
+ EXPECT_FALSE(p3->UseSoloWindowHeader());
+ EXPECT_FALSE(p4->UseSoloWindowHeader());
+ EXPECT_TRUE(checker1.IsPaintScheduledAndReset());
+
+ // Change the w3 state to maximize. Doesn't affect to w1.
+ wm::MaximizeWindow(w3->GetNativeWindow());
+ EXPECT_TRUE(p1->UseSoloWindowHeader());
+ EXPECT_FALSE(p2->UseSoloWindowHeader());
+ EXPECT_FALSE(p3->UseSoloWindowHeader());
+ EXPECT_FALSE(p4->UseSoloWindowHeader());
+
+ // Close the w3 and w4.
+ w3.reset();
+ w4.reset();
+ EXPECT_TRUE(p1->UseSoloWindowHeader());
+ EXPECT_TRUE(p2->UseSoloWindowHeader());
+ EXPECT_TRUE(checker2.IsPaintScheduledAndReset());
+
+ // Move w2 back to the primary display.
+ w2->SetBounds(gfx::Rect(0, 0, 100, 100));
+ EXPECT_FALSE(p1->UseSoloWindowHeader());
+ EXPECT_FALSE(p2->UseSoloWindowHeader());
+ EXPECT_TRUE(checker1.IsPaintScheduledAndReset());
+ EXPECT_TRUE(checker2.IsPaintScheduledAndReset());
+
+ // Close w2.
+ w2.reset();
+ EXPECT_TRUE(p1->UseSoloWindowHeader());
+ EXPECT_TRUE(checker1.IsPaintScheduledAndReset());
+}
+
+TEST_F(FramePainterTest, GetHeaderOpacity) {
+ // Create a widget and a painter for it.
+ scoped_ptr<Widget> w1(CreateTestWidget());
+ scoped_ptr<FramePainter> p1(CreateTestPainter(w1.get()));
+ w1->Show();
+
+ // Modify the values of the opacity constants so that they each have a
+ // different value.
+ ScopedOpacityConstantModifier opacity_constant_modifier;
+
+ // Solo active window has solo window opacity.
+ EXPECT_EQ(FramePainter::kSoloWindowOpacity,
+ p1->GetHeaderOpacity(FramePainter::ACTIVE,
+ IDR_AURA_WINDOW_HEADER_BASE_ACTIVE,
+ 0));
+
+ // Create a second widget and painter.
+ scoped_ptr<Widget> w2(CreateTestWidget());
+ scoped_ptr<FramePainter> p2(CreateTestPainter(w2.get()));
+ w2->Show();
+
+ // Active window has active window opacity.
+ EXPECT_EQ(FramePainter::kActiveWindowOpacity,
+ p2->GetHeaderOpacity(FramePainter::ACTIVE,
+ IDR_AURA_WINDOW_HEADER_BASE_ACTIVE,
+ 0));
+
+ // Inactive window has inactive window opacity.
+ EXPECT_EQ(FramePainter::kInactiveWindowOpacity,
+ p2->GetHeaderOpacity(FramePainter::INACTIVE,
+ IDR_AURA_WINDOW_HEADER_BASE_INACTIVE,
+ 0));
+
+ // Regular maximized windows are fully opaque.
+ ash::wm::MaximizeWindow(w1->GetNativeWindow());
+ EXPECT_EQ(255,
+ p1->GetHeaderOpacity(FramePainter::ACTIVE,
+ IDR_AURA_WINDOW_HEADER_BASE_ACTIVE,
+ 0));
+}
+
+// Test that the minimal header style is used in the proper situations.
+TEST_F(FramePainterTest, MinimalHeaderStyle) {
+ // Create a widget and a painter for it.
+ scoped_ptr<Widget> w(CreateTestWidget());
+ scoped_ptr<FramePainter> p(CreateTestPainter(w.get()));
+ w->Show();
+
+ // Regular non-maximized windows should not use the minimal header style.
+ EXPECT_FALSE(p->ShouldUseMinimalHeaderStyle(FramePainter::THEMED_NO));
+
+ // Regular maximized windows should use the minimal header style.
+ w->Maximize();
+ EXPECT_TRUE(p->ShouldUseMinimalHeaderStyle(FramePainter::THEMED_NO));
+
+ // Test cases where the maximized window should not use the minimal header
+ // style.
+ EXPECT_FALSE(p->ShouldUseMinimalHeaderStyle(FramePainter::THEMED_YES));
+
+ SetTrackedByWorkspace(w->GetNativeWindow(), false);
+ EXPECT_FALSE(p->ShouldUseMinimalHeaderStyle(FramePainter::THEMED_NO));
+ SetTrackedByWorkspace(w->GetNativeWindow(), true);
+}
+
+// Ensure the title text is vertically aligned with the window icon.
+TEST_F(FramePainterTest, TitleIconAlignment) {
+ scoped_ptr<Widget> w(CreateTestWidget());
+ FramePainter p;
+ ImageButton size(NULL);
+ ImageButton close(NULL);
+ views::View window_icon;
+ window_icon.SetBounds(0, 0, 16, 16);
+ p.Init(w.get(),
+ &window_icon,
+ &size,
+ &close,
+ FramePainter::SIZE_BUTTON_MAXIMIZES);
+ w->SetBounds(gfx::Rect(0, 0, 500, 500));
+ w->Show();
+
+ // Title and icon are aligned when shorter_header is false.
+ p.LayoutHeader(w->non_client_view()->frame_view(), false);
+ gfx::Font default_font;
+ gfx::Rect large_header_title_bounds = p.GetTitleBounds(default_font);
+ EXPECT_EQ(window_icon.bounds().CenterPoint().y(),
+ large_header_title_bounds.CenterPoint().y());
+
+ // Title and icon are aligned when shorter_header is true.
+ p.LayoutHeader(w->non_client_view()->frame_view(), true);
+ gfx::Rect short_header_title_bounds = p.GetTitleBounds(default_font);
+ EXPECT_EQ(window_icon.bounds().CenterPoint().y(),
+ short_header_title_bounds.CenterPoint().y());
+}
+
+TEST_F(FramePainterTest, ChildWindowVisibility) {
+ scoped_ptr<Widget> w1(CreateTestWidget());
+ scoped_ptr<FramePainter> p1(CreateTestPainter(w1.get()));
+ w1->Show();
+
+ // Solo active window has solo window opacity.
+ EXPECT_EQ(FramePainter::kSoloWindowOpacity,
+ p1->GetHeaderOpacity(FramePainter::ACTIVE,
+ IDR_AURA_WINDOW_HEADER_BASE_ACTIVE,
+ 0));
+
+ // Create a child window which doesn't affect the solo header.
+ scoped_ptr<Widget> w2(new Widget);
+ Widget::InitParams params(Widget::InitParams::TYPE_CONTROL);
+ params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
+ params.parent = w1->GetNativeView();
+ w2->Init(params);
+ w2->Show();
+
+ // Still has solo header if child window is added.
+ EXPECT_EQ(FramePainter::kSoloWindowOpacity,
+ p1->GetHeaderOpacity(FramePainter::ACTIVE,
+ IDR_AURA_WINDOW_HEADER_BASE_ACTIVE,
+ 0));
+
+ // Change the visibility of w2 and verifies w1 still has solo header.
+ w2->Hide();
+ EXPECT_EQ(FramePainter::kSoloWindowOpacity,
+ p1->GetHeaderOpacity(FramePainter::ACTIVE,
+ IDR_AURA_WINDOW_HEADER_BASE_ACTIVE,
+ 0));
+}
+
+} // namespace ash
diff --git a/chromium/ash/wm/gestures/OWNERS b/chromium/ash/wm/gestures/OWNERS
new file mode 100644
index 00000000000..b8e32c10903
--- /dev/null
+++ b/chromium/ash/wm/gestures/OWNERS
@@ -0,0 +1 @@
+sadrul@chromium.org
diff --git a/chromium/ash/wm/gestures/long_press_affordance_handler.cc b/chromium/ash/wm/gestures/long_press_affordance_handler.cc
new file mode 100644
index 00000000000..5450e97c27f
--- /dev/null
+++ b/chromium/ash/wm/gestures/long_press_affordance_handler.cc
@@ -0,0 +1,377 @@
+// Copyright (c) 2012 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.
+
+#include "ash/wm/gestures/long_press_affordance_handler.h"
+
+#include "ash/display/display_controller.h"
+#include "ash/shell.h"
+#include "ash/root_window_controller.h"
+#include "ash/shell_window_ids.h"
+#include "ash/wm/property_util.h"
+#include "third_party/skia/include/core/SkColor.h"
+#include "third_party/skia/include/core/SkPaint.h"
+#include "third_party/skia/include/core/SkPath.h"
+#include "third_party/skia/include/core/SkRect.h"
+#include "third_party/skia/include/effects/SkGradientShader.h"
+#include "ui/aura/client/screen_position_client.h"
+#include "ui/aura/root_window.h"
+#include "ui/aura/window.h"
+#include "ui/base/gestures/gesture_configuration.h"
+#include "ui/base/gestures/gesture_util.h"
+#include "ui/compositor/layer.h"
+#include "ui/gfx/canvas.h"
+#include "ui/gfx/screen.h"
+#include "ui/gfx/transform.h"
+#include "ui/views/view.h"
+#include "ui/views/widget/widget.h"
+
+namespace {
+
+const int kAffordanceOuterRadius = 60;
+const int kAffordanceInnerRadius = 50;
+
+// Angles from x-axis at which the outer and inner circles start.
+const int kAffordanceOuterStartAngle = -109;
+const int kAffordanceInnerStartAngle = -65;
+
+const int kAffordanceGlowWidth = 20;
+// The following is half width to avoid division by 2.
+const int kAffordanceArcWidth = 3;
+
+// Start and end values for various animations.
+const double kAffordanceScaleStartValue = 0.8;
+const double kAffordanceScaleEndValue = 1.0;
+const double kAffordanceShrinkScaleEndValue = 0.5;
+const double kAffordanceOpacityStartValue = 0.1;
+const double kAffordanceOpacityEndValue = 0.5;
+const int kAffordanceAngleStartValue = 0;
+// The end angle is a bit greater than 360 to make sure the circle completes at
+// the end of the animation.
+const int kAffordanceAngleEndValue = 380;
+const int kAffordanceDelayBeforeShrinkMs = 200;
+const int kAffordanceShrinkAnimationDurationMs = 100;
+
+// Visual constants.
+const SkColor kAffordanceGlowStartColor = SkColorSetARGB(24, 255, 255, 255);
+const SkColor kAffordanceGlowEndColor = SkColorSetARGB(0, 255, 255, 255);
+const SkColor kAffordanceArcColor = SkColorSetARGB(80, 0, 0, 0);
+const int kAffordanceFrameRateHz = 60;
+
+views::Widget* CreateAffordanceWidget(aura::RootWindow* root_window) {
+ views::Widget* widget = new views::Widget;
+ views::Widget::InitParams params;
+ params.type = views::Widget::InitParams::TYPE_WINDOW_FRAMELESS;
+ params.keep_on_top = true;
+ params.accept_events = false;
+ params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
+ params.context = root_window;
+ params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW;
+ widget->Init(params);
+ widget->SetOpacity(0xFF);
+ ash::GetRootWindowController(root_window)->GetContainer(
+ ash::internal::kShellWindowId_OverlayContainer)->AddChild(
+ widget->GetNativeWindow());
+ return widget;
+}
+
+void PaintAffordanceArc(gfx::Canvas* canvas,
+ gfx::Point& center,
+ int radius,
+ int start_angle,
+ int end_angle) {
+ SkPaint paint;
+ paint.setStyle(SkPaint::kStroke_Style);
+ paint.setStrokeWidth(2 * kAffordanceArcWidth);
+ paint.setColor(kAffordanceArcColor);
+ paint.setAntiAlias(true);
+
+ SkPath arc_path;
+ arc_path.addArc(SkRect::MakeXYWH(center.x() - radius,
+ center.y() - radius,
+ 2 * radius,
+ 2 * radius),
+ start_angle, end_angle);
+ canvas->DrawPath(arc_path, paint);
+}
+
+void PaintAffordanceGlow(gfx::Canvas* canvas,
+ gfx::Point& center,
+ int start_radius,
+ int end_radius,
+ SkColor* colors,
+ SkScalar* pos,
+ int num_colors) {
+ SkPoint sk_center;
+ int radius = (end_radius + start_radius) / 2;
+ int glow_width = end_radius - start_radius;
+ sk_center.iset(center.x(), center.y());
+ skia::RefPtr<SkShader> shader = skia::AdoptRef(
+ SkGradientShader::CreateTwoPointRadial(
+ sk_center,
+ SkIntToScalar(start_radius),
+ sk_center,
+ SkIntToScalar(end_radius),
+ colors,
+ pos,
+ num_colors,
+ SkShader::kClamp_TileMode));
+ DCHECK(shader);
+ SkPaint paint;
+ paint.setStyle(SkPaint::kStroke_Style);
+ paint.setStrokeWidth(glow_width);
+ paint.setShader(shader.get());
+ paint.setAntiAlias(true);
+ SkPath arc_path;
+ arc_path.addArc(SkRect::MakeXYWH(center.x() - radius,
+ center.y() - radius,
+ 2 * radius,
+ 2 * radius),
+ 0, 360);
+ canvas->DrawPath(arc_path, paint);
+}
+
+} // namespace
+
+namespace ash {
+namespace internal {
+
+// View of the LongPressAffordanceHandler. Draws the actual contents and
+// updates as the animation proceeds. It also maintains the views::Widget that
+// the animation is shown in.
+class LongPressAffordanceHandler::LongPressAffordanceView
+ : public views::View {
+ public:
+ LongPressAffordanceView(const gfx::Point& event_location,
+ aura::RootWindow* root_window)
+ : views::View(),
+ widget_(CreateAffordanceWidget(root_window)),
+ current_angle_(kAffordanceAngleStartValue),
+ current_scale_(kAffordanceScaleStartValue) {
+ widget_->SetContentsView(this);
+ widget_->SetAlwaysOnTop(true);
+
+ // We are owned by the LongPressAffordance.
+ set_owned_by_client();
+ gfx::Point point = event_location;
+ aura::client::GetScreenPositionClient(root_window)->ConvertPointToScreen(
+ root_window, &point);
+ widget_->SetBounds(gfx::Rect(
+ point.x() - (kAffordanceOuterRadius + kAffordanceGlowWidth),
+ point.y() - (kAffordanceOuterRadius + kAffordanceGlowWidth),
+ GetPreferredSize().width(),
+ GetPreferredSize().height()));
+ widget_->Show();
+ widget_->GetNativeView()->layer()->SetOpacity(kAffordanceOpacityStartValue);
+ }
+
+ virtual ~LongPressAffordanceView() {
+ }
+
+ void UpdateWithGrowAnimation(ui::Animation* animation) {
+ // Update the portion of the circle filled so far and re-draw.
+ current_angle_ = animation->CurrentValueBetween(kAffordanceAngleStartValue,
+ kAffordanceAngleEndValue);
+ current_scale_ = animation->CurrentValueBetween(kAffordanceScaleStartValue,
+ kAffordanceScaleEndValue);
+ widget_->GetNativeView()->layer()->SetOpacity(
+ animation->CurrentValueBetween(kAffordanceOpacityStartValue,
+ kAffordanceOpacityEndValue));
+ SchedulePaint();
+ }
+
+ void UpdateWithShrinkAnimation(ui::Animation* animation) {
+ current_scale_ = animation->CurrentValueBetween(kAffordanceScaleEndValue,
+ kAffordanceShrinkScaleEndValue);
+ widget_->GetNativeView()->layer()->SetOpacity(
+ animation->CurrentValueBetween(kAffordanceOpacityEndValue,
+ kAffordanceOpacityStartValue));
+ SchedulePaint();
+ }
+
+ private:
+ // Overridden from views::View.
+ virtual gfx::Size GetPreferredSize() OVERRIDE {
+ return gfx::Size(2 * (kAffordanceOuterRadius + kAffordanceGlowWidth),
+ 2 * (kAffordanceOuterRadius + kAffordanceGlowWidth));
+ }
+
+ virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE {
+ gfx::Point center(GetPreferredSize().width() / 2,
+ GetPreferredSize().height() / 2);
+ canvas->Save();
+
+ gfx::Transform scale;
+ scale.Scale(current_scale_, current_scale_);
+ // We want to scale from the center.
+ canvas->Translate(center.OffsetFromOrigin());
+ canvas->Transform(scale);
+ canvas->Translate(-center.OffsetFromOrigin());
+
+ // Paint affordance glow
+ int start_radius = kAffordanceInnerRadius - kAffordanceGlowWidth;
+ int end_radius = kAffordanceOuterRadius + kAffordanceGlowWidth;
+ const int num_colors = 3;
+ SkScalar pos[num_colors] = {0, 0.5, 1};
+ SkColor colors[num_colors] = {kAffordanceGlowEndColor,
+ kAffordanceGlowStartColor, kAffordanceGlowEndColor};
+ PaintAffordanceGlow(canvas, center, start_radius, end_radius, colors, pos,
+ num_colors);
+
+ // Paint inner circle.
+ PaintAffordanceArc(canvas, center, kAffordanceInnerRadius,
+ kAffordanceInnerStartAngle, -current_angle_);
+ // Paint outer circle.
+ PaintAffordanceArc(canvas, center, kAffordanceOuterRadius,
+ kAffordanceOuterStartAngle, current_angle_);
+
+ canvas->Restore();
+ }
+
+ scoped_ptr<views::Widget> widget_;
+ int current_angle_;
+ double current_scale_;
+
+ DISALLOW_COPY_AND_ASSIGN(LongPressAffordanceView);
+};
+
+////////////////////////////////////////////////////////////////////////////////
+// LongPressAffordanceHandler, public
+
+LongPressAffordanceHandler::LongPressAffordanceHandler()
+ : ui::LinearAnimation(kAffordanceFrameRateHz, this),
+ tap_down_touch_id_(-1),
+ tap_down_display_id_(0),
+ current_animation_type_(NONE) {}
+
+LongPressAffordanceHandler::~LongPressAffordanceHandler() {}
+
+void LongPressAffordanceHandler::ProcessEvent(aura::Window* target,
+ ui::LocatedEvent* event,
+ int touch_id) {
+ // Once we have a touch id, we are only interested in event of that touch id.
+ if (tap_down_touch_id_ != -1 && tap_down_touch_id_ != touch_id)
+ return;
+ int64 timer_start_time_ms =
+ ui::GestureConfiguration::semi_long_press_time_in_seconds() * 1000;
+ switch (event->type()) {
+ case ui::ET_GESTURE_TAP_DOWN:
+ // Start animation.
+ tap_down_location_ = event->root_location();
+ tap_down_touch_id_ = touch_id;
+ current_animation_type_ = GROW_ANIMATION;
+ tap_down_display_id_ =
+ Shell::GetScreen()->GetDisplayNearestWindow(target).id();
+ timer_.Start(FROM_HERE,
+ base::TimeDelta::FromMilliseconds(timer_start_time_ms),
+ this,
+ &LongPressAffordanceHandler::StartAnimation);
+ break;
+ case ui::ET_TOUCH_MOVED:
+ // If animation is running, We want it to be robust to small finger
+ // movements. So we stop the animation only when the finger moves a
+ // certain distance.
+ if (!ui::gestures::IsInsideManhattanSquare(
+ event->root_location(), tap_down_location_))
+ StopAnimation();
+ break;
+ case ui::ET_TOUCH_CANCELLED:
+ case ui::ET_GESTURE_END:
+ // We will stop the animation on TOUCH_RELEASED.
+ break;
+ case ui::ET_GESTURE_LONG_PRESS:
+ if (is_animating())
+ End();
+ break;
+ default:
+ // On all other touch and gesture events, we hide the animation.
+ StopAnimation();
+ break;
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// LongPressAffordanceHandler, private
+
+void LongPressAffordanceHandler::StartAnimation() {
+ aura::RootWindow* root_window = NULL;
+ switch (current_animation_type_) {
+ case GROW_ANIMATION:
+ root_window = ash::Shell::GetInstance()->display_controller()->
+ GetRootWindowForDisplayId(tap_down_display_id_);
+ if (!root_window) {
+ StopAnimation();
+ return;
+ }
+ view_.reset(new LongPressAffordanceView(tap_down_location_, root_window));
+ SetDuration(
+ ui::GestureConfiguration::long_press_time_in_seconds() * 1000 -
+ ui::GestureConfiguration::semi_long_press_time_in_seconds() * 1000 -
+ kAffordanceDelayBeforeShrinkMs);
+ Start();
+ break;
+ case SHRINK_ANIMATION:
+ SetDuration(kAffordanceShrinkAnimationDurationMs);
+ Start();
+ break;
+ default:
+ NOTREACHED();
+ break;
+ }
+}
+
+void LongPressAffordanceHandler::StopAnimation() {
+ if (timer_.IsRunning())
+ timer_.Stop();
+ // Since, Animation::Stop() calls AnimationEnded(), we need to reset the
+ // |current_animation_type_| before Stop(), otherwise AnimationEnded() may
+ // start the timer again.
+ current_animation_type_ = NONE;
+ if (is_animating())
+ Stop();
+ view_.reset();
+ tap_down_touch_id_ = -1;
+ tap_down_display_id_ = 0;
+}
+
+void LongPressAffordanceHandler::AnimateToState(double state) {
+ DCHECK(view_.get());
+ switch (current_animation_type_) {
+ case GROW_ANIMATION:
+ view_->UpdateWithGrowAnimation(this);
+ break;
+ case SHRINK_ANIMATION:
+ view_->UpdateWithShrinkAnimation(this);
+ break;
+ default:
+ NOTREACHED();
+ break;
+ }
+}
+
+bool LongPressAffordanceHandler::ShouldSendCanceledFromStop() {
+ return false;
+}
+
+void LongPressAffordanceHandler::AnimationEnded(
+ const ui::Animation* animation) {
+ switch (current_animation_type_) {
+ case GROW_ANIMATION:
+ current_animation_type_ = SHRINK_ANIMATION;
+ timer_.Start(FROM_HERE,
+ base::TimeDelta::FromMilliseconds(kAffordanceDelayBeforeShrinkMs),
+ this, &LongPressAffordanceHandler::StartAnimation);
+ break;
+ case SHRINK_ANIMATION:
+ current_animation_type_ = NONE;
+ // fall through to reset the view.
+ default:
+ view_.reset();
+ tap_down_touch_id_ = -1;
+ tap_down_display_id_ = 0;
+ break;
+ }
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/wm/gestures/long_press_affordance_handler.h b/chromium/ash/wm/gestures/long_press_affordance_handler.h
new file mode 100644
index 00000000000..14ff0f3c1ff
--- /dev/null
+++ b/chromium/ash/wm/gestures/long_press_affordance_handler.h
@@ -0,0 +1,80 @@
+// Copyright (c) 2012 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 ASH_WM_GESTURES_LONG_PRESS_AFFORDANCE_HANDLER_H_
+#define ASH_WM_GESTURES_LONG_PRESS_AFFORDANCE_HANDLER_H_
+
+#include "base/timer/timer.h"
+#include "ui/base/animation/animation_delegate.h"
+#include "ui/base/animation/linear_animation.h"
+#include "ui/gfx/point.h"
+
+namespace aura {
+class Window;
+}
+
+namespace ui {
+class LocatedEvent;
+}
+
+namespace ash {
+
+namespace test {
+class SystemGestureEventFilterTest;
+}
+
+namespace internal {
+
+// LongPressAffordanceHandler displays an animated affordance that is shown
+// on a TAP_DOWN gesture. The animation sequence consists of two parts:
+// The first part is a grow animation that starts at semi-long-press and
+// completes on a long-press gesture. The affordance animates to full size
+// during grow animation.
+// The second part is a shrink animation that start after grow and shrinks the
+// affordance out of view.
+class LongPressAffordanceHandler : public ui::AnimationDelegate,
+ public ui::LinearAnimation {
+ public:
+ LongPressAffordanceHandler();
+ virtual ~LongPressAffordanceHandler();
+
+ // Display or removes long press affordance according to the |event|.
+ void ProcessEvent(aura::Window* target,
+ ui::LocatedEvent* event,
+ int touch_id);
+
+ private:
+ friend class ash::test::SystemGestureEventFilterTest;
+
+ enum LongPressAnimationType {
+ NONE,
+ GROW_ANIMATION,
+ SHRINK_ANIMATION,
+ };
+
+ void StartAnimation();
+ void StopAnimation();
+
+ // Overridden from ui::LinearAnimation.
+ virtual void AnimateToState(double state) OVERRIDE;
+ virtual bool ShouldSendCanceledFromStop() OVERRIDE;
+
+ // Overridden from ui::AnimationDelegate.
+ virtual void AnimationEnded(const ui::Animation* animation) OVERRIDE;
+
+ class LongPressAffordanceView;
+ scoped_ptr<LongPressAffordanceView> view_;
+ gfx::Point tap_down_location_;
+ int tap_down_touch_id_;
+ base::OneShotTimer<LongPressAffordanceHandler> timer_;
+ int64 tap_down_display_id_;
+ LongPressAnimationType current_animation_type_;
+
+ DISALLOW_COPY_AND_ASSIGN(LongPressAffordanceHandler);
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_WM_GESTURES_LONG_PRESS_AFFORDANCE_HANDLER_H_
diff --git a/chromium/ash/wm/gestures/shelf_gesture_handler.cc b/chromium/ash/wm/gestures/shelf_gesture_handler.cc
new file mode 100644
index 00000000000..5b99363eb63
--- /dev/null
+++ b/chromium/ash/wm/gestures/shelf_gesture_handler.cc
@@ -0,0 +1,92 @@
+// Copyright (c) 2012 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.
+
+#include "ash/wm/gestures/shelf_gesture_handler.h"
+
+#include "ash/root_window_controller.h"
+#include "ash/session_state_delegate.h"
+#include "ash/shelf/shelf_layout_manager.h"
+#include "ash/shelf/shelf_types.h"
+#include "ash/shelf/shelf_widget.h"
+#include "ash/shell.h"
+#include "ash/system/status_area_widget.h"
+#include "ash/wm/gestures/tray_gesture_handler.h"
+#include "ash/wm/window_properties.h"
+#include "ash/wm/window_util.h"
+#include "ui/aura/window.h"
+#include "ui/compositor/layer.h"
+#include "ui/compositor/scoped_layer_animation_settings.h"
+#include "ui/gfx/transform.h"
+#include "ui/views/widget/widget.h"
+
+namespace ash {
+namespace internal {
+
+ShelfGestureHandler::ShelfGestureHandler()
+ : drag_in_progress_(false) {
+}
+
+ShelfGestureHandler::~ShelfGestureHandler() {
+}
+
+bool ShelfGestureHandler::ProcessGestureEvent(const ui::GestureEvent& event) {
+ Shell* shell = Shell::GetInstance();
+ if (!shell->session_state_delegate()->NumberOfLoggedInUsers() ||
+ shell->session_state_delegate()->IsScreenLocked()) {
+ // The gestures are disabled in the lock/login screen.
+ return false;
+ }
+
+ // TODO(oshima): Find the root window controller from event's location.
+ RootWindowController* controller = Shell::GetPrimaryRootWindowController();
+
+ ShelfLayoutManager* shelf = controller->GetShelfLayoutManager();
+
+ // The gesture are disabled for fullscreen windows that are not in immersive
+ // mode.
+ const aura::Window* fullscreen = controller->GetFullscreenWindow();
+ if (fullscreen && !shelf->FullscreenWithMinimalChrome())
+ return false;
+
+ if (event.type() == ui::ET_GESTURE_SCROLL_BEGIN) {
+ drag_in_progress_ = true;
+ shelf->StartGestureDrag(event);
+ return true;
+ }
+
+ if (!drag_in_progress_)
+ return false;
+
+ if (event.type() == ui::ET_GESTURE_SCROLL_UPDATE) {
+ if (tray_handler_) {
+ if (!tray_handler_->UpdateGestureDrag(event))
+ tray_handler_.reset();
+ } else if (shelf->UpdateGestureDrag(event) ==
+ ShelfLayoutManager::DRAG_TRAY) {
+ tray_handler_.reset(new TrayGestureHandler());
+ }
+
+ return true;
+ }
+
+ drag_in_progress_ = false;
+
+ if (event.type() == ui::ET_GESTURE_SCROLL_END ||
+ event.type() == ui::ET_SCROLL_FLING_START) {
+ if (tray_handler_) {
+ tray_handler_->CompleteGestureDrag(event);
+ tray_handler_.reset();
+ }
+
+ shelf->CompleteGestureDrag(event);
+ return true;
+ }
+
+ // Unexpected event. Reset the state and let the event fall through.
+ shelf->CancelGestureDrag();
+ return false;
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/wm/gestures/shelf_gesture_handler.h b/chromium/ash/wm/gestures/shelf_gesture_handler.h
new file mode 100644
index 00000000000..90dfd7c923e
--- /dev/null
+++ b/chromium/ash/wm/gestures/shelf_gesture_handler.h
@@ -0,0 +1,43 @@
+// Copyright (c) 2012 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 ASH_WM_GESTURES_SHELF_GESTURE_HANDLER_H_
+#define ASH_WM_GESTURES_SHELF_GESTURE_HANDLER_H_
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+
+namespace ui {
+class GestureEvent;
+}
+
+namespace ash {
+namespace internal {
+
+class TrayGestureHandler;
+
+// This manages gestures on the shelf (e.g. launcher, status tray) that affects
+// the shelf visibility.
+class ShelfGestureHandler {
+ public:
+ ShelfGestureHandler();
+ virtual ~ShelfGestureHandler();
+
+ // Processes a gesture event and updates the status of the shelf when
+ // appropriate. Returns true of the gesture has been handled and it should not
+ // be processed any farther, false otherwise.
+ bool ProcessGestureEvent(const ui::GestureEvent& event);
+
+ private:
+ bool drag_in_progress_;
+
+ scoped_ptr<TrayGestureHandler> tray_handler_;
+
+ DISALLOW_COPY_AND_ASSIGN(ShelfGestureHandler);
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_WM_GESTURES_SHELF_GESTURE_HANDLER_H_
diff --git a/chromium/ash/wm/gestures/system_pinch_handler.cc b/chromium/ash/wm/gestures/system_pinch_handler.cc
new file mode 100644
index 00000000000..a0f5d12e00b
--- /dev/null
+++ b/chromium/ash/wm/gestures/system_pinch_handler.cc
@@ -0,0 +1,145 @@
+// Copyright (c) 2012 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.
+
+#include "ash/wm/gestures/system_pinch_handler.h"
+
+#include "ash/launcher/launcher.h"
+#include "ash/screen_ash.h"
+#include "ash/shelf/shelf_widget.h"
+#include "ash/shell.h"
+#include "ash/wm/property_util.h"
+#include "ash/wm/window_animations.h"
+#include "ash/wm/window_util.h"
+#include "ash/wm/workspace/snap_sizer.h"
+#include "ui/aura/window.h"
+#include "ui/base/events/event_constants.h"
+#include "ui/base/gestures/gesture_types.h"
+#include "ui/compositor/scoped_layer_animation_settings.h"
+#include "ui/gfx/rect.h"
+#include "ui/views/widget/widget.h"
+#include "ui/views/widget/widget_delegate.h"
+
+const double kPinchThresholdForMaximize = 1.5;
+const double kPinchThresholdForMinimize = 0.7;
+
+namespace ash {
+namespace internal {
+
+const int SystemPinchHandler::kSystemGesturePoints = 4;
+
+SystemPinchHandler::SystemPinchHandler(aura::Window* target)
+ : target_(target),
+ phantom_(target),
+ phantom_state_(PHANTOM_WINDOW_NORMAL),
+ pinch_factor_(1.) {
+ widget_ = views::Widget::GetWidgetForNativeWindow(target_);
+}
+
+SystemPinchHandler::~SystemPinchHandler() {
+}
+
+SystemGestureStatus SystemPinchHandler::ProcessGestureEvent(
+ const ui::GestureEvent& event) {
+ // The target has changed, somehow. Let's bale.
+ if (!widget_ || !widget_->widget_delegate()->CanResize())
+ return SYSTEM_GESTURE_END;
+
+ switch (event.type()) {
+ case ui::ET_GESTURE_END: {
+ if (event.details().touch_points() > kSystemGesturePoints)
+ break;
+
+ if (phantom_state_ == PHANTOM_WINDOW_MAXIMIZED) {
+ if (!wm::IsWindowMaximized(target_) &&
+ !wm::IsWindowFullscreen(target_))
+ wm::MaximizeWindow(target_);
+ } else if (phantom_state_ == PHANTOM_WINDOW_MINIMIZED) {
+ if (wm::IsWindowMaximized(target_) ||
+ wm::IsWindowFullscreen(target_)) {
+ wm::RestoreWindow(target_);
+ } else {
+ wm::MinimizeWindow(target_);
+
+ // NOTE: Minimizing the window will cause this handler to be
+ // destroyed. So do not access anything from |this| from here.
+ return SYSTEM_GESTURE_END;
+ }
+ }
+ return SYSTEM_GESTURE_END;
+ }
+
+ case ui::ET_GESTURE_PINCH_UPDATE: {
+ // The PINCH_UPDATE events contain incremental scaling updates.
+ pinch_factor_ *= event.details().scale();
+ gfx::Rect bounds =
+ GetPhantomWindowScreenBounds(target_, event.location());
+ if (phantom_state_ != PHANTOM_WINDOW_NORMAL || phantom_.IsShowing())
+ phantom_.Show(bounds);
+ break;
+ }
+
+ case ui::ET_GESTURE_MULTIFINGER_SWIPE: {
+ phantom_.Hide();
+ pinch_factor_ = 1.0;
+ phantom_state_ = PHANTOM_WINDOW_NORMAL;
+
+ if (event.details().swipe_left() || event.details().swipe_right()) {
+ // Snap for left/right swipes.
+ ui::ScopedLayerAnimationSettings settings(
+ target_->layer()->GetAnimator());
+ internal::SnapSizer::SnapWindow(target_,
+ event.details().swipe_left() ? internal::SnapSizer::LEFT_EDGE :
+ internal::SnapSizer::RIGHT_EDGE);
+ } else if (event.details().swipe_up()) {
+ if (!wm::IsWindowMaximized(target_) &&
+ !wm::IsWindowFullscreen(target_))
+ wm::MaximizeWindow(target_);
+ } else if (event.details().swipe_down()) {
+ wm::MinimizeWindow(target_);
+ } else {
+ NOTREACHED() << "Swipe happened without a direction.";
+ }
+ break;
+ }
+
+ default:
+ break;
+ }
+
+ return SYSTEM_GESTURE_PROCESSED;
+}
+
+gfx::Rect SystemPinchHandler::GetPhantomWindowScreenBounds(
+ aura::Window* window,
+ const gfx::Point& point) {
+ if (pinch_factor_ > kPinchThresholdForMaximize) {
+ phantom_state_ = PHANTOM_WINDOW_MAXIMIZED;
+ return ScreenAsh::ConvertRectToScreen(
+ target_->parent(),
+ ScreenAsh::GetMaximizedWindowBoundsInParent(target_));
+ }
+
+ if (pinch_factor_ < kPinchThresholdForMinimize) {
+ if (wm::IsWindowMaximized(window) || wm::IsWindowFullscreen(window)) {
+ const gfx::Rect* restore = GetRestoreBoundsInScreen(window);
+ if (restore) {
+ phantom_state_ = PHANTOM_WINDOW_MINIMIZED;
+ return *restore;
+ }
+ return window->bounds();
+ }
+
+ gfx::Rect rect = GetMinimizeAnimationTargetBoundsInScreen(target_);
+ if (!rect.IsEmpty())
+ rect.Inset(-8, -8);
+ phantom_state_ = PHANTOM_WINDOW_MINIMIZED;
+ return rect;
+ }
+
+ phantom_state_ = PHANTOM_WINDOW_NORMAL;
+ return window->bounds();
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/wm/gestures/system_pinch_handler.h b/chromium/ash/wm/gestures/system_pinch_handler.h
new file mode 100644
index 00000000000..d5c5a0eade9
--- /dev/null
+++ b/chromium/ash/wm/gestures/system_pinch_handler.h
@@ -0,0 +1,85 @@
+// Copyright (c) 2012 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 ASH_WM_GESTURES_SYSTEM_PINCH_HANDLER_H_
+#define ASH_WM_GESTURES_SYSTEM_PINCH_HANDLER_H_
+
+#include "ash/wm/workspace/phantom_window_controller.h"
+
+namespace aura {
+class Window;
+}
+
+namespace gfx {
+class Point;
+}
+
+namespace ui {
+class GestureEvent;
+}
+
+namespace views {
+class Widget;
+}
+
+namespace ash {
+namespace internal {
+
+enum SystemGestureStatus {
+ SYSTEM_GESTURE_PROCESSED, // The system gesture has been processed.
+ SYSTEM_GESTURE_IGNORED, // The system gesture was ignored.
+ SYSTEM_GESTURE_END, // Marks the end of the sytem gesture.
+};
+
+// This handles 4+ finger pinch gestures to maximize/minimize/restore windows.
+class SystemPinchHandler {
+ public:
+ explicit SystemPinchHandler(aura::Window* target);
+ virtual ~SystemPinchHandler();
+
+ // Processes a gesture event. Returns SYSTEM_GESTURE_PROCESSED if the gesture
+ // event has been processed. Returns SYSTEM_GESTURE_END if the gesture event
+ // has been processed, and marks the end of the gesture sequence (i.e. the
+ // handler should receive no more input events).
+ SystemGestureStatus ProcessGestureEvent(const ui::GestureEvent& event);
+
+ static const int kSystemGesturePoints;
+
+ private:
+ // Returns the appropriate bounds for the phantom window depending on the
+ // state of the window, the state of the gesture sequence, and the current
+ // event location.
+ gfx::Rect GetPhantomWindowScreenBounds(aura::Window* window,
+ const gfx::Point& point);
+
+ enum PhantomWindowState {
+ PHANTOM_WINDOW_NORMAL,
+ PHANTOM_WINDOW_MAXIMIZED,
+ PHANTOM_WINDOW_MINIMIZED,
+ };
+
+ aura::Window* target_;
+ views::Widget* widget_;
+
+ // A phantom window is used to provide visual cues for
+ // pinch-to-resize/maximize/minimize gestures.
+ PhantomWindowController phantom_;
+
+ // When the phantom window is in minimized or maximized state, moving the
+ // target window should not move the phantom window. So |phantom_state_| is
+ // used to track the state of the phantom window.
+ PhantomWindowState phantom_state_;
+
+ // PINCH_UPDATE events include incremental pinch-amount. But it is necessary
+ // to keep track of the overall pinch-amount. |pinch_factor_| is used for
+ // that.
+ double pinch_factor_;
+
+ DISALLOW_COPY_AND_ASSIGN(SystemPinchHandler);
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_WM_GESTURES_SYSTEM_PINCH_HANDLER_H_
diff --git a/chromium/ash/wm/gestures/tray_gesture_handler.cc b/chromium/ash/wm/gestures/tray_gesture_handler.cc
new file mode 100644
index 00000000000..78b0fb308c8
--- /dev/null
+++ b/chromium/ash/wm/gestures/tray_gesture_handler.cc
@@ -0,0 +1,109 @@
+// Copyright (c) 2012 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.
+
+#include "ash/wm/gestures/tray_gesture_handler.h"
+
+#include "ash/shell.h"
+#include "ash/system/tray/system_tray.h"
+#include "ash/system/tray/system_tray_bubble.h"
+#include "ui/aura/window.h"
+#include "ui/base/events/event.h"
+#include "ui/compositor/layer.h"
+#include "ui/gfx/transform.h"
+#include "ui/views/widget/widget.h"
+
+const int kMinBubbleHeight = 13;
+
+namespace ash {
+namespace internal {
+
+TrayGestureHandler::TrayGestureHandler()
+ : widget_(NULL),
+ gesture_drag_amount_(0) {
+ // TODO(oshima): Support multiple display case.
+ SystemTray* tray = Shell::GetInstance()->GetPrimarySystemTray();
+ tray->ShowDefaultView(BUBBLE_CREATE_NEW);
+ SystemTrayBubble* bubble = tray->GetSystemBubble();
+ if (!bubble)
+ return;
+ bubble->bubble_view()->set_gesture_dragging(true);
+ widget_ = bubble->bubble_view()->GetWidget();
+ widget_->AddObserver(this);
+
+ gfx::Rect bounds = widget_->GetWindowBoundsInScreen();
+ int height_change = bounds.height() - kMinBubbleHeight;
+ bounds.set_height(kMinBubbleHeight);
+ bounds.set_y(bounds.y() + height_change);
+ widget_->SetBounds(bounds);
+}
+
+TrayGestureHandler::~TrayGestureHandler() {
+ if (widget_)
+ widget_->RemoveObserver(this);
+}
+
+bool TrayGestureHandler::UpdateGestureDrag(const ui::GestureEvent& event) {
+ CHECK_EQ(ui::ET_GESTURE_SCROLL_UPDATE, event.type());
+ if (!widget_)
+ return false;
+
+ gesture_drag_amount_ += event.details().scroll_y();
+ if (gesture_drag_amount_ > 0 && gesture_drag_amount_ < kMinBubbleHeight) {
+ widget_->Close();
+ return false;
+ }
+
+ gfx::Rect bounds = widget_->GetWindowBoundsInScreen();
+ int new_height = std::min(
+ kMinBubbleHeight + std::max(0, static_cast<int>(-gesture_drag_amount_)),
+ widget_->GetContentsView()->GetPreferredSize().height());
+ int height_change = bounds.height() - new_height;
+ bounds.set_height(new_height);
+ bounds.set_y(bounds.y() + height_change);
+ widget_->SetBounds(bounds);
+ return true;
+}
+
+void TrayGestureHandler::CompleteGestureDrag(const ui::GestureEvent& event) {
+ if (!widget_)
+ return;
+
+ widget_->RemoveObserver(this);
+
+ // Close the widget if it hasn't been dragged enough.
+ bool should_close = false;
+ int height = widget_->GetWindowBoundsInScreen().height();
+ int preferred_height =
+ widget_->GetContentsView()->GetPreferredSize().height();
+ if (event.type() == ui::ET_GESTURE_SCROLL_END) {
+ const float kMinThresholdGestureDrag = 0.4f;
+ if (height < preferred_height * kMinThresholdGestureDrag)
+ should_close = true;
+ } else if (event.type() == ui::ET_SCROLL_FLING_START) {
+ const float kMinThresholdGestureDragExposeFling = 0.25f;
+ const float kMinThresholdGestureFling = 1000.f;
+ if (height < preferred_height * kMinThresholdGestureDragExposeFling &&
+ event.details().velocity_y() > -kMinThresholdGestureFling)
+ should_close = true;
+ } else {
+ NOTREACHED();
+ }
+
+ if (should_close) {
+ widget_->Close();
+ } else {
+ SystemTrayBubble* bubble =
+ Shell::GetInstance()->GetPrimarySystemTray()->GetSystemBubble();
+ if (bubble)
+ bubble->bubble_view()->set_gesture_dragging(false);
+ }
+}
+
+void TrayGestureHandler::OnWidgetDestroying(views::Widget* widget) {
+ CHECK_EQ(widget_, widget);
+ widget_ = NULL;
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/wm/gestures/tray_gesture_handler.h b/chromium/ash/wm/gestures/tray_gesture_handler.h
new file mode 100644
index 00000000000..34f033b35f0
--- /dev/null
+++ b/chromium/ash/wm/gestures/tray_gesture_handler.h
@@ -0,0 +1,48 @@
+// Copyright (c) 2012 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 ASH_WM_GESTURES_TRAY_GESTURE_HANDLER_H_
+#define ASH_WM_GESTURES_TRAY_GESTURE_HANDLER_H_
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "ui/views/widget/widget_observer.h"
+
+namespace ui {
+class GestureEvent;
+}
+
+namespace ash {
+namespace internal {
+
+// Handles gesture events on the shelf to show the system tray bubble.
+class TrayGestureHandler : public views::WidgetObserver {
+ public:
+ TrayGestureHandler();
+ virtual ~TrayGestureHandler();
+
+ // Handles a gesture-update event and updates the dragging state of the tray
+ // bubble. Returns true if the handler can continue to process gesture events
+ // for the bubble. Returns false if it should no longer receive gesture
+ // events.
+ bool UpdateGestureDrag(const ui::GestureEvent& event);
+
+ void CompleteGestureDrag(const ui::GestureEvent& event);
+
+ private:
+ virtual void OnWidgetDestroying(views::Widget* widget) OVERRIDE;
+
+ // The widget for the tray-bubble.
+ views::Widget* widget_;
+
+ // The amount that has been dragged.
+ float gesture_drag_amount_;
+
+ DISALLOW_COPY_AND_ASSIGN(TrayGestureHandler);
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_WM_GESTURES_TRAY_GESTURE_HANDLER_H_
diff --git a/chromium/ash/wm/gestures/two_finger_drag_handler.cc b/chromium/ash/wm/gestures/two_finger_drag_handler.cc
new file mode 100644
index 00000000000..49f7ecc02dd
--- /dev/null
+++ b/chromium/ash/wm/gestures/two_finger_drag_handler.cc
@@ -0,0 +1,198 @@
+// Copyright (c) 2012 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.
+
+#include "ash/wm/gestures/two_finger_drag_handler.h"
+
+#include "ash/wm/window_resizer.h"
+#include "ash/wm/window_util.h"
+#include "ash/wm/workspace/snap_sizer.h"
+#include "ui/aura/client/window_types.h"
+#include "ui/aura/window.h"
+#include "ui/aura/window_delegate.h"
+#include "ui/base/events/event.h"
+#include "ui/base/events/event_constants.h"
+#include "ui/base/hit_test.h"
+#include "ui/compositor/layer.h"
+#include "ui/compositor/scoped_layer_animation_settings.h"
+
+namespace {
+
+bool IsTopEdge(int component) {
+ return component == HTTOPLEFT ||
+ component == HTTOP ||
+ component == HTTOPRIGHT;
+}
+
+bool IsBottomEdge(int component) {
+ return component == HTBOTTOMLEFT ||
+ component == HTBOTTOM ||
+ component == HTBOTTOMRIGHT;
+}
+
+bool IsRightEdge(int component) {
+ return component == HTTOPRIGHT ||
+ component == HTRIGHT ||
+ component == HTBOTTOMRIGHT;
+}
+
+bool IsLeftEdge(int component) {
+ return component == HTTOPLEFT ||
+ component == HTLEFT ||
+ component == HTBOTTOMLEFT;
+}
+
+bool IsSomeEdge(int component) {
+ switch (component) {
+ case HTTOPLEFT:
+ case HTTOP:
+ case HTTOPRIGHT:
+ case HTRIGHT:
+ case HTBOTTOMRIGHT:
+ case HTBOTTOM:
+ case HTBOTTOMLEFT:
+ case HTLEFT:
+ return true;
+ }
+ return false;
+}
+
+// Returns whether a window-move should be allowed depending on the hit-test
+// results of the two fingers.
+bool WindowComponentsAllowMoving(int component1, int component2) {
+ return ((IsTopEdge(component1) && IsTopEdge(component2)) ||
+ (IsBottomEdge(component1) && IsBottomEdge(component2)) ||
+ (IsLeftEdge(component1) && IsLeftEdge(component2)) ||
+ (IsRightEdge(component1) && IsRightEdge(component2)) ||
+ (!IsSomeEdge(component1) && !IsSomeEdge(component2)));
+}
+
+} // namespace
+
+namespace ash {
+namespace internal {
+
+TwoFingerDragHandler::TwoFingerDragHandler()
+ : first_finger_hittest_(HTNOWHERE) {
+}
+
+TwoFingerDragHandler::~TwoFingerDragHandler() {
+}
+
+bool TwoFingerDragHandler::ProcessGestureEvent(aura::Window* target,
+ const ui::GestureEvent& event) {
+ if (!target->delegate())
+ return false;
+
+ if (event.type() == ui::ET_GESTURE_BEGIN &&
+ event.details().touch_points() == 1) {
+ first_finger_hittest_ =
+ target->delegate()->GetNonClientComponent(event.location());
+ return false;
+ }
+
+ if (event.type() == ui::ET_GESTURE_BEGIN &&
+ event.details().touch_points() == 2) {
+ if (!window_resizer_.get() && wm::IsWindowNormal(target) &&
+ target->type() == aura::client::WINDOW_TYPE_NORMAL) {
+ if (WindowComponentsAllowMoving(first_finger_hittest_,
+ target->delegate()->GetNonClientComponent(event.location()))) {
+ target->AddObserver(this);
+ window_resizer_ = CreateWindowResizer(
+ target,
+ event.details().bounding_box().CenterPoint(),
+ HTCAPTION,
+ aura::client::WINDOW_MOVE_SOURCE_TOUCH);
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ if (!window_resizer_) {
+ // Consume all two-finger gestures on a normal window.
+ return event.details().touch_points() == 2 &&
+ target->type() == aura::client::WINDOW_TYPE_NORMAL &&
+ wm::IsWindowNormal(target);
+ }
+
+ if (target != window_resizer_->GetTarget())
+ return false;
+
+ switch (event.type()) {
+ case ui::ET_GESTURE_BEGIN:
+ if (event.details().touch_points() > 2)
+ Reset();
+ return false;
+
+ case ui::ET_GESTURE_SCROLL_BEGIN:
+ case ui::ET_GESTURE_PINCH_BEGIN:
+ case ui::ET_GESTURE_SCROLL_END:
+ return true;
+
+ case ui::ET_GESTURE_MULTIFINGER_SWIPE: {
+ // For a swipe, the window either maximizes, minimizes, or snaps. In this
+ // case, cancel the drag, and do the appropriate action.
+ Reset();
+
+ if (event.details().swipe_up()) {
+ if (wm::CanMaximizeWindow(target))
+ wm::MaximizeWindow(target);
+ } else if (event.details().swipe_down() &&
+ wm::CanMinimizeWindow(target)) {
+ wm::MinimizeWindow(target);
+ } else if (wm::CanSnapWindow(target)) {
+ ui::ScopedLayerAnimationSettings scoped_setter(
+ target->layer()->GetAnimator());
+ scoped_setter.SetPreemptionStrategy(
+ ui::LayerAnimator::REPLACE_QUEUED_ANIMATIONS);
+ internal::SnapSizer::SnapWindow(target,
+ event.details().swipe_left() ? internal::SnapSizer::LEFT_EDGE :
+ internal::SnapSizer::RIGHT_EDGE);
+ }
+ return true;
+ }
+
+ case ui::ET_GESTURE_PINCH_UPDATE:
+ case ui::ET_GESTURE_SCROLL_UPDATE:
+ window_resizer_->Drag(event.details().bounding_box().CenterPoint(),
+ event.flags());
+ return true;
+
+ case ui::ET_GESTURE_PINCH_END:
+ window_resizer_->CompleteDrag(event.flags());
+ Reset();
+ return true;
+
+ case ui::ET_GESTURE_END:
+ if (event.details().touch_points() == 2) {
+ window_resizer_->RevertDrag();
+ Reset();
+ return true;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ return false;
+}
+
+void TwoFingerDragHandler::Reset() {
+ window_resizer_->GetTarget()->RemoveObserver(this);
+ window_resizer_.reset();
+}
+
+void TwoFingerDragHandler::OnWindowVisibilityChanged(aura::Window* window,
+ bool visible) {
+ Reset();
+}
+
+void TwoFingerDragHandler::OnWindowDestroying(aura::Window* window) {
+ Reset();
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/wm/gestures/two_finger_drag_handler.h b/chromium/ash/wm/gestures/two_finger_drag_handler.h
new file mode 100644
index 00000000000..06cb86d9405
--- /dev/null
+++ b/chromium/ash/wm/gestures/two_finger_drag_handler.h
@@ -0,0 +1,55 @@
+// Copyright (c) 2012 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 ASH_WM_GESTURES_TWO_FINGER_DRAG_HANDLER_H_
+#define ASH_WM_GESTURES_TWO_FINGER_DRAG_HANDLER_H_
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "ui/aura/window_observer.h"
+
+namespace aura {
+class Window;
+}
+
+namespace ui {
+class GestureEvent;
+}
+
+namespace ash {
+
+class WindowResizer;
+
+namespace internal {
+
+// This handles 2-finger drag gestures to move toplevel windows.
+class TwoFingerDragHandler : public aura::WindowObserver {
+ public:
+ TwoFingerDragHandler();
+ virtual ~TwoFingerDragHandler();
+
+ // Processes a gesture event and starts a drag, or updates/ends an in-progress
+ // drag. Returns true if the event has been handled and should not be
+ // processed any farther, false otherwise.
+ bool ProcessGestureEvent(aura::Window* target, const ui::GestureEvent& event);
+
+ private:
+ void Reset();
+
+ // Overridden from aura::WindowObserver.
+ virtual void OnWindowVisibilityChanged(aura::Window* window,
+ bool visible) OVERRIDE;
+ virtual void OnWindowDestroying(aura::Window* window) OVERRIDE;
+
+ int first_finger_hittest_;
+
+ scoped_ptr<WindowResizer> window_resizer_;
+
+ DISALLOW_COPY_AND_ASSIGN(TwoFingerDragHandler);
+};
+
+}
+} // namespace ash
+
+#endif // ASH_WM_GESTURES_TWO_FINGER_DRAG_HANDLER_H_
diff --git a/chromium/ash/wm/image_cursors.cc b/chromium/ash/wm/image_cursors.cc
new file mode 100644
index 00000000000..33b3582e7dc
--- /dev/null
+++ b/chromium/ash/wm/image_cursors.cc
@@ -0,0 +1,140 @@
+// Copyright (c) 2012 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.
+
+#include "ash/wm/image_cursors.h"
+
+#include <float.h>
+
+#include "base/logging.h"
+#include "base/strings/string16.h"
+#include "ui/base/cursor/cursor.h"
+#include "ui/base/cursor/cursor_loader.h"
+#include "ui/base/cursor/cursors_aura.h"
+#include "ui/gfx/display.h"
+#include "ui/gfx/point.h"
+
+namespace ash {
+
+const int kImageCursorIds[] = {
+ ui::kCursorNull,
+ ui::kCursorPointer,
+ ui::kCursorNoDrop,
+ ui::kCursorNotAllowed,
+ ui::kCursorCopy,
+ ui::kCursorHand,
+ ui::kCursorMove,
+ ui::kCursorNorthEastResize,
+ ui::kCursorSouthWestResize,
+ ui::kCursorSouthEastResize,
+ ui::kCursorNorthWestResize,
+ ui::kCursorNorthResize,
+ ui::kCursorSouthResize,
+ ui::kCursorEastResize,
+ ui::kCursorWestResize,
+ ui::kCursorIBeam,
+ ui::kCursorAlias,
+ ui::kCursorCell,
+ ui::kCursorContextMenu,
+ ui::kCursorCross,
+ ui::kCursorHelp,
+ ui::kCursorVerticalText,
+ ui::kCursorZoomIn,
+ ui::kCursorZoomOut,
+ ui::kCursorRowResize,
+ ui::kCursorColumnResize,
+ ui::kCursorEastWestResize,
+ ui::kCursorNorthSouthResize,
+ ui::kCursorNorthEastSouthWestResize,
+ ui::kCursorNorthWestSouthEastResize,
+ ui::kCursorGrab,
+ ui::kCursorGrabbing,
+};
+
+const int kAnimatedCursorIds[] = {
+ ui::kCursorWait,
+ ui::kCursorProgress
+};
+
+ImageCursors::ImageCursors() : scale_(1.f) {
+}
+
+ImageCursors::~ImageCursors() {
+}
+
+gfx::Display ImageCursors::GetDisplay() const {
+ if (!cursor_loader_) {
+ NOTREACHED();
+ // Returning default on release build as it's not serious enough to crash
+ // even if this ever happens.
+ return gfx::Display();
+ }
+ return cursor_loader_->display();
+}
+
+bool ImageCursors::SetDisplay(const gfx::Display& display) {
+ float device_scale_factor = display.device_scale_factor();
+ if (!cursor_loader_) {
+ cursor_loader_.reset(ui::CursorLoader::Create());
+ cursor_loader_->set_scale(scale_);
+ } else if (cursor_loader_->display().rotation() == display.rotation() &&
+ cursor_loader_->display().device_scale_factor() ==
+ device_scale_factor) {
+ return false;
+ }
+
+ cursor_loader_->set_display(display);
+ ReloadCursors();
+ return true;
+}
+
+void ImageCursors::ReloadCursors() {
+ const gfx::Display& display = cursor_loader_->display();
+ float device_scale_factor = display.device_scale_factor();
+
+ cursor_loader_->UnloadAll();
+
+ for (size_t i = 0; i < arraysize(kImageCursorIds); ++i) {
+ int resource_id = -1;
+ gfx::Point hot_point;
+ bool success = ui::GetCursorDataFor(kImageCursorIds[i],
+ device_scale_factor,
+ &resource_id,
+ &hot_point);
+ DCHECK(success);
+ cursor_loader_->LoadImageCursor(kImageCursorIds[i], resource_id, hot_point);
+ }
+ for (size_t i = 0; i < arraysize(kAnimatedCursorIds); ++i) {
+ int resource_id = -1;
+ gfx::Point hot_point;
+ bool success = ui::GetAnimatedCursorDataFor(kAnimatedCursorIds[i],
+ device_scale_factor,
+ &resource_id,
+ &hot_point);
+ DCHECK(success);
+ cursor_loader_->LoadAnimatedCursor(kAnimatedCursorIds[i],
+ resource_id,
+ hot_point,
+ ui::kAnimatedCursorFrameDelayMs);
+ }
+}
+
+void ImageCursors::SetScale(float scale) {
+ if (scale < FLT_EPSILON) {
+ NOTREACHED() << "Scale must be bigger than 0.";
+ scale = 1.0f;
+ }
+
+ scale_ = scale;
+
+ if (cursor_loader_.get()) {
+ cursor_loader_->set_scale(scale);
+ ReloadCursors();
+ }
+}
+
+void ImageCursors::SetPlatformCursor(gfx::NativeCursor* cursor) {
+ cursor_loader_->SetPlatformCursor(cursor);
+}
+
+} // namespace ash
diff --git a/chromium/ash/wm/image_cursors.h b/chromium/ash/wm/image_cursors.h
new file mode 100644
index 00000000000..72c004f699c
--- /dev/null
+++ b/chromium/ash/wm/image_cursors.h
@@ -0,0 +1,58 @@
+// Copyright (c) 2012 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 ASH_WM_IMAGE_CURSORS_H_
+#define ASH_WM_IMAGE_CURSORS_H_
+
+#include "ash/ash_export.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string16.h"
+#include "ui/gfx/native_widget_types.h"
+
+namespace gfx {
+class Display;
+}
+
+namespace ui {
+class CursorLoader;
+}
+
+namespace ash {
+
+// A utility class that provides cursors for NativeCursors for which we have
+// image resources.
+class ASH_EXPORT ImageCursors {
+ public:
+ ImageCursors();
+ ~ImageCursors();
+
+ // Returns the display the cursors are loaded for. The display must
+ // be set by SetDisplay before using this.
+ gfx::Display GetDisplay() const;
+
+ // Sets the display the cursors are loaded for. The device scale factor
+ // determines the size of the image to load, and the rotation of the display
+ // determines if the image and its host point has to be retated.
+ // Returns true if the cursor image is reloaded.
+ bool SetDisplay(const gfx::Display& display);
+
+ // Sets the scale of the mouse cursor icon.
+ void SetScale(float scale);
+
+ // Sets the platform cursor based on the native type of |cursor|.
+ void SetPlatformCursor(gfx::NativeCursor* cursor);
+
+ private:
+ // Reloads the all loaded cursors in the cursor loader.
+ void ReloadCursors();
+
+ scoped_ptr<ui::CursorLoader> cursor_loader_;
+ float scale_;
+
+ DISALLOW_COPY_AND_ASSIGN(ImageCursors);
+};
+
+} // namespace ash
+
+#endif // ASH_WM_IMAGE_CURSORS_H_
diff --git a/chromium/ash/wm/lock_state_controller.cc b/chromium/ash/wm/lock_state_controller.cc
new file mode 100644
index 00000000000..3b3d1cfdcc4
--- /dev/null
+++ b/chromium/ash/wm/lock_state_controller.cc
@@ -0,0 +1,48 @@
+// Copyright 2013 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.
+
+#include "ash/wm/lock_state_controller.h"
+
+#include "ash/ash_switches.h"
+#include "ash/shell.h"
+#include "ash/shell_delegate.h"
+#include "ash/shell_window_ids.h"
+#include "ash/wm/session_state_animator.h"
+#include "base/command_line.h"
+#include "ui/aura/root_window.h"
+#include "ui/views/corewm/compound_event_filter.h"
+
+namespace ash {
+
+const int LockStateController::kLockTimeoutMs = 400;
+const int LockStateController::kShutdownTimeoutMs = 400;
+const int LockStateController::kLockFailTimeoutMs = 8000;
+const int LockStateController::kLockToShutdownTimeoutMs = 150;
+const int LockStateController::kShutdownRequestDelayMs = 50;
+
+LockStateController::LockStateController()
+ : animator_(new internal::SessionStateAnimator()) {
+}
+
+LockStateController::~LockStateController() {
+}
+
+void LockStateController::SetDelegate(LockStateControllerDelegate* delegate) {
+ delegate_.reset(delegate);
+}
+
+void LockStateController::AddObserver(LockStateObserver* observer) {
+ observers_.AddObserver(observer);
+}
+
+void LockStateController::RemoveObserver(LockStateObserver* observer) {
+ observers_.RemoveObserver(observer);
+}
+
+bool LockStateController::HasObserver(LockStateObserver* observer) {
+ return observers_.HasObserver(observer);
+}
+
+
+} // namespace ash
diff --git a/chromium/ash/wm/lock_state_controller.h b/chromium/ash/wm/lock_state_controller.h
new file mode 100644
index 00000000000..10fce263f78
--- /dev/null
+++ b/chromium/ash/wm/lock_state_controller.h
@@ -0,0 +1,146 @@
+// Copyright 2013 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 ASH_WM_LOCK_STATE_CONTROLLER_H_
+#define ASH_WM_LOCK_STATE_CONTROLLER_H_
+
+#include "ash/ash_export.h"
+#include "ash/shell_observer.h"
+#include "ash/wm/lock_state_observer.h"
+#include "ash/wm/session_state_animator.h"
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/observer_list.h"
+#include "base/time/time.h"
+#include "base/timer/timer.h"
+#include "ui/aura/root_window_observer.h"
+
+namespace gfx {
+class Rect;
+class Size;
+}
+
+namespace ui {
+class Layer;
+}
+
+namespace ash {
+
+namespace test {
+class LockStateControllerImpl2Test;
+class PowerButtonControllerTest;
+}
+
+// Performs system-related functions on behalf of LockStateController.
+class ASH_EXPORT LockStateControllerDelegate {
+ public:
+ LockStateControllerDelegate() {}
+ virtual ~LockStateControllerDelegate() {}
+
+ virtual void RequestLockScreen() = 0;
+ virtual void RequestShutdown() = 0;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(LockStateControllerDelegate);
+};
+
+// Displays onscreen animations and locks or suspends the system in response to
+// the power button being pressed or released.
+class ASH_EXPORT LockStateController : public aura::RootWindowObserver,
+ public ShellObserver {
+ public:
+ // Amount of time that the power button needs to be held before we lock the
+ // screen.
+ static const int kLockTimeoutMs;
+
+ // Amount of time that the power button needs to be held before we shut down.
+ static const int kShutdownTimeoutMs;
+
+ // Amount of time to wait for our lock requests to be honored before giving
+ // up.
+ static const int kLockFailTimeoutMs;
+
+ // When the button has been held continuously from the unlocked state, amount
+ // of time that we wait after the screen locker window is shown before
+ // starting the pre-shutdown animation.
+ static const int kLockToShutdownTimeoutMs;
+
+ // Additional time (beyond kFastCloseAnimMs) to wait after starting the
+ // fast-close shutdown animation before actually requesting shutdown, to give
+ // the animation time to finish.
+ static const int kShutdownRequestDelayMs;
+
+ LockStateController();
+ virtual ~LockStateController();
+
+ void SetDelegate(LockStateControllerDelegate* delegate);
+
+ // Starts locking (with slow animation) that can be cancelled.
+ // After locking and |kLockToShutdownTimeoutMs| StartShutdownAnimation()
+ // will be called unless CancelShutdownAnimation() is called, if
+ // |shutdown_after_lock| is true.
+ virtual void StartLockAnimation(bool shutdown_after_lock) = 0;
+
+ // Starts shutting down (with slow animation) that can be cancelled.
+ virtual void StartShutdownAnimation() = 0;
+
+ // Starts usual lock animation, but locks immediately.
+ // Unlike StartLockAnimation it does no lead to StartShutdownAnimation.
+ virtual void StartLockAnimationAndLockImmediately() = 0;
+
+ // Returns true if we have requested system to lock, but haven't received
+ // confirmation yet.
+ virtual bool LockRequested() = 0;
+
+ // Returns true if we are shutting down.
+ virtual bool ShutdownRequested() = 0;
+
+ // Returns true if we are within cancellable lock timeframe.
+ virtual bool CanCancelLockAnimation() = 0;
+
+ // Cancels locking and reverts lock animation.
+ virtual void CancelLockAnimation() = 0;
+
+ // Returns true if we are within cancellable shutdown timeframe.
+ virtual bool CanCancelShutdownAnimation() = 0;
+
+ // Cancels shutting down and reverts shutdown animation.
+ virtual void CancelShutdownAnimation() = 0;
+
+ // Called when Chrome gets a request to display the lock screen.
+ virtual void OnStartingLock() = 0;
+
+ // Displays the shutdown animation and requests shutdown when it's done.
+ virtual void RequestShutdown() = 0;
+
+ // Called when ScreenLocker is ready to close, but not yet destroyed.
+ // Can be used to display "hiding" animations on unlock.
+ // |callback| will be called when all animations are done.
+ virtual void OnLockScreenHide(base::Closure& callback) = 0;
+
+ // Sets up the callback that should be called once lock animation is finished.
+ // Callback is guaranteed to be called once and then discarded.
+ virtual void SetLockScreenDisplayedCallback(base::Closure& callback) = 0;
+
+ virtual void AddObserver(LockStateObserver* observer);
+ virtual void RemoveObserver(LockStateObserver* observer);
+ virtual bool HasObserver(LockStateObserver* observer);
+
+ protected:
+ friend class test::PowerButtonControllerTest;
+ friend class test::LockStateControllerImpl2Test;
+
+ scoped_ptr<internal::SessionStateAnimator> animator_;
+
+ scoped_ptr<LockStateControllerDelegate> delegate_;
+
+ ObserverList<LockStateObserver> observers_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(LockStateController);
+};
+
+} // namespace ash
+
+#endif // ASH_WM_LOCK_STATE_CONTROLLER_H_
diff --git a/chromium/ash/wm/lock_state_controller_impl2.cc b/chromium/ash/wm/lock_state_controller_impl2.cc
new file mode 100644
index 00000000000..c5301b74099
--- /dev/null
+++ b/chromium/ash/wm/lock_state_controller_impl2.cc
@@ -0,0 +1,646 @@
+// Copyright 2013 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.
+
+#include "ash/wm/lock_state_controller_impl2.h"
+
+#include "ash/ash_switches.h"
+#include "ash/cancel_mode.h"
+#include "ash/shell.h"
+#include "ash/shell_delegate.h"
+#include "ash/shell_window_ids.h"
+#include "ash/wm/session_state_animator.h"
+#include "base/bind_helpers.h"
+#include "base/command_line.h"
+#include "base/timer/timer.h"
+#include "ui/aura/root_window.h"
+#include "ui/compositor/layer_animation_sequence.h"
+#include "ui/compositor/scoped_layer_animation_settings.h"
+#include "ui/views/corewm/compound_event_filter.h"
+
+#if defined(OS_CHROMEOS)
+#include "base/chromeos/chromeos_version.h"
+#endif
+
+namespace ash {
+
+namespace {
+
+aura::Window* GetBackground() {
+ aura::RootWindow* root_window = Shell::GetPrimaryRootWindow();
+ return Shell::GetContainer(root_window,
+ internal::kShellWindowId_DesktopBackgroundContainer);
+}
+
+bool IsBackgroundHidden() {
+ return !GetBackground()->IsVisible();
+}
+
+void ShowBackground() {
+ ui::ScopedLayerAnimationSettings settings(
+ GetBackground()->layer()->GetAnimator());
+ settings.SetTransitionDuration(base::TimeDelta());
+ GetBackground()->Show();
+}
+
+void HideBackground() {
+ ui::ScopedLayerAnimationSettings settings(
+ GetBackground()->layer()->GetAnimator());
+ settings.SetTransitionDuration(base::TimeDelta());
+ GetBackground()->Hide();
+}
+
+// This observer is intended to use in cases when some action has to be taken
+// once some animation successfully completes (i.e. it was not aborted).
+// Observer will count a number of sequences it is attached to, and a number of
+// finished sequences (either Ended or Aborted). Once these two numbers are
+// equal, observer will delete itself, calling callback passed to constructor if
+// there were no aborted animations.
+// This way it can be either used to wait for some animation to be finished in
+// multiple layers, to wait once a sequence of animations is finished in one
+// layer or the mixture of both.
+class AnimationFinishedObserver : public ui::LayerAnimationObserver {
+ public:
+ explicit AnimationFinishedObserver(base::Closure &callback)
+ : callback_(callback),
+ sequences_attached_(0),
+ sequences_completed_(0),
+ paused_(false) {
+ }
+
+ // Pauses observer: no checks will be made while paused. It can be used when
+ // a sequence has some immediate animations in the beginning, and for
+ // animations that can be tested with flag that makes all animations
+ // immediate.
+ void Pause() {
+ paused_ = true;
+ }
+
+ // Unpauses observer. It does a check and calls callback if conditions are
+ // met.
+ void Unpause() {
+ if (!paused_)
+ return;
+ paused_ = false;
+ if (sequences_completed_ == sequences_attached_) {
+ callback_.Run();
+ delete this;
+ }
+ }
+
+ private:
+ virtual ~AnimationFinishedObserver() {
+ }
+
+ // LayerAnimationObserver implementation
+ virtual void OnLayerAnimationEnded(
+ ui::LayerAnimationSequence* sequence) OVERRIDE {
+ sequences_completed_++;
+ if ((sequences_completed_ == sequences_attached_) && !paused_) {
+ callback_.Run();
+ delete this;
+ }
+ }
+
+ virtual void OnLayerAnimationAborted(
+ ui::LayerAnimationSequence* sequence) OVERRIDE {
+ sequences_completed_++;
+ if ((sequences_completed_ == sequences_attached_) && !paused_)
+ delete this;
+ }
+
+ virtual void OnLayerAnimationScheduled(
+ ui::LayerAnimationSequence* sequence) OVERRIDE {
+ }
+
+ virtual void OnAttachedToSequence(
+ ui::LayerAnimationSequence* sequence) OVERRIDE {
+ LayerAnimationObserver::OnAttachedToSequence(sequence);
+ sequences_attached_++;
+ }
+
+ // Callback to be called.
+ base::Closure callback_;
+
+ // Number of sequences this observer was attached to.
+ int sequences_attached_;
+
+ // Number of sequences either ended or aborted.
+ int sequences_completed_;
+
+ bool paused_;
+
+ DISALLOW_COPY_AND_ASSIGN(AnimationFinishedObserver);
+};
+
+} // namespace
+
+LockStateControllerImpl2::TestApi::TestApi(
+ LockStateControllerImpl2* controller)
+ : controller_(controller) {
+}
+
+LockStateControllerImpl2::TestApi::~TestApi() {
+}
+
+LockStateControllerImpl2::LockStateControllerImpl2()
+ : login_status_(user::LOGGED_IN_NONE),
+ system_is_locked_(false),
+ shutting_down_(false),
+ shutdown_after_lock_(false),
+ animating_lock_(false),
+ can_cancel_lock_animation_(false) {
+ Shell::GetPrimaryRootWindow()->AddRootWindowObserver(this);
+}
+
+LockStateControllerImpl2::~LockStateControllerImpl2() {
+ Shell::GetPrimaryRootWindow()->RemoveRootWindowObserver(this);
+}
+
+void LockStateControllerImpl2::OnLoginStateChanged(
+ user::LoginStatus status) {
+ if (status != user::LOGGED_IN_LOCKED)
+ login_status_ = status;
+ system_is_locked_ = (status == user::LOGGED_IN_LOCKED);
+}
+
+void LockStateControllerImpl2::OnAppTerminating() {
+ // If we hear that Chrome is exiting but didn't request it ourselves, all we
+ // can really hope for is that we'll have time to clear the screen.
+ // This is also the case when the user signs off.
+ if (!shutting_down_) {
+ shutting_down_ = true;
+ Shell* shell = ash::Shell::GetInstance();
+ shell->env_filter()->set_cursor_hidden_by_filter(false);
+ shell->cursor_manager()->HideCursor();
+ animator_->StartAnimation(
+ internal::SessionStateAnimator::kAllContainersMask,
+ internal::SessionStateAnimator::ANIMATION_HIDE_IMMEDIATELY,
+ internal::SessionStateAnimator::ANIMATION_SPEED_IMMEDIATE);
+ }
+}
+
+void LockStateControllerImpl2::OnLockStateChanged(bool locked) {
+ if (shutting_down_ || (system_is_locked_ == locked))
+ return;
+
+ system_is_locked_ = locked;
+
+ if (locked) {
+ StartPostLockAnimation();
+ lock_fail_timer_.Stop();
+ } else {
+ StartUnlockAnimationAfterUIDestroyed();
+ }
+}
+
+void LockStateControllerImpl2::SetLockScreenDisplayedCallback(
+ base::Closure& callback) {
+ lock_screen_displayed_callback_ = callback;
+}
+
+void LockStateControllerImpl2::OnStartingLock() {
+ if (shutting_down_ || system_is_locked_)
+ return;
+ if (animating_lock_)
+ return;
+ StartImmediatePreLockAnimation(false /* request_lock_on_completion */);
+}
+
+void LockStateControllerImpl2::StartLockAnimationAndLockImmediately() {
+ if (animating_lock_)
+ return;
+ StartImmediatePreLockAnimation(true /* request_lock_on_completion */);
+}
+
+void LockStateControllerImpl2::StartLockAnimation(
+ bool shutdown_after_lock) {
+ if (animating_lock_)
+ return;
+ shutdown_after_lock_ = shutdown_after_lock;
+ can_cancel_lock_animation_ = true;
+
+ StartCancellablePreLockAnimation();
+}
+
+bool LockStateControllerImpl2::LockRequested() {
+ return lock_fail_timer_.IsRunning();
+}
+
+bool LockStateControllerImpl2::ShutdownRequested() {
+ return shutting_down_;
+}
+
+bool LockStateControllerImpl2::CanCancelLockAnimation() {
+ return can_cancel_lock_animation_;
+}
+
+void LockStateControllerImpl2::CancelLockAnimation() {
+ if (!CanCancelLockAnimation())
+ return;
+ shutdown_after_lock_ = false;
+ animating_lock_ = false;
+ CancelPreLockAnimation();
+}
+
+bool LockStateControllerImpl2::CanCancelShutdownAnimation() {
+ return pre_shutdown_timer_.IsRunning() ||
+ shutdown_after_lock_ ||
+ lock_to_shutdown_timer_.IsRunning();
+}
+
+void LockStateControllerImpl2::StartShutdownAnimation() {
+ StartCancellableShutdownAnimation();
+}
+
+void LockStateControllerImpl2::CancelShutdownAnimation() {
+ if (!CanCancelShutdownAnimation())
+ return;
+ if (lock_to_shutdown_timer_.IsRunning()) {
+ lock_to_shutdown_timer_.Stop();
+ return;
+ }
+ if (shutdown_after_lock_) {
+ shutdown_after_lock_ = false;
+ return;
+ }
+
+ animator_->StartGlobalAnimation(
+ internal::SessionStateAnimator::ANIMATION_UNDO_GRAYSCALE_BRIGHTNESS,
+ internal::SessionStateAnimator::ANIMATION_SPEED_REVERT_SHUTDOWN);
+ pre_shutdown_timer_.Stop();
+}
+
+void LockStateControllerImpl2::RequestShutdown() {
+ if (!shutting_down_)
+ RequestShutdownImpl();
+}
+
+void LockStateControllerImpl2::RequestShutdownImpl() {
+ DCHECK(!shutting_down_);
+ shutting_down_ = true;
+
+ Shell* shell = ash::Shell::GetInstance();
+ shell->env_filter()->set_cursor_hidden_by_filter(false);
+ shell->cursor_manager()->HideCursor();
+
+ StartShutdownAnimationImpl();
+}
+
+void LockStateControllerImpl2::OnRootWindowHostCloseRequested(
+ const aura::RootWindow*) {
+ Shell::GetInstance()->delegate()->Exit();
+}
+
+void LockStateControllerImpl2::OnLockFailTimeout() {
+ DCHECK(!system_is_locked_);
+ // Undo lock animation.
+ StartUnlockAnimationAfterUIDestroyed();
+}
+
+void LockStateControllerImpl2::StartLockToShutdownTimer() {
+ shutdown_after_lock_ = false;
+ lock_to_shutdown_timer_.Stop();
+ lock_to_shutdown_timer_.Start(
+ FROM_HERE,
+ base::TimeDelta::FromMilliseconds(kLockToShutdownTimeoutMs),
+ this, &LockStateControllerImpl2::OnLockToShutdownTimeout);
+}
+
+void LockStateControllerImpl2::OnLockToShutdownTimeout() {
+ DCHECK(system_is_locked_);
+ StartCancellableShutdownAnimation();
+}
+
+void LockStateControllerImpl2::StartCancellableShutdownAnimation() {
+ Shell* shell = ash::Shell::GetInstance();
+ // Hide cursor, but let it reappear if the mouse moves.
+ shell->env_filter()->set_cursor_hidden_by_filter(true);
+ shell->cursor_manager()->HideCursor();
+
+ animator_->StartGlobalAnimation(
+ internal::SessionStateAnimator::ANIMATION_GRAYSCALE_BRIGHTNESS,
+ internal::SessionStateAnimator::ANIMATION_SPEED_SHUTDOWN);
+ StartPreShutdownAnimationTimer();
+}
+
+void LockStateControllerImpl2::StartShutdownAnimationImpl() {
+ animator_->StartGlobalAnimation(
+ internal::SessionStateAnimator::ANIMATION_GRAYSCALE_BRIGHTNESS,
+ internal::SessionStateAnimator::ANIMATION_SPEED_SHUTDOWN);
+ StartRealShutdownTimer(true);
+}
+
+void LockStateControllerImpl2::StartPreShutdownAnimationTimer() {
+ pre_shutdown_timer_.Stop();
+ pre_shutdown_timer_.Start(
+ FROM_HERE,
+ animator_->
+ GetDuration(internal::SessionStateAnimator::ANIMATION_SPEED_SHUTDOWN),
+ this,
+ &LockStateControllerImpl2::OnPreShutdownAnimationTimeout);
+}
+
+void LockStateControllerImpl2::OnPreShutdownAnimationTimeout() {
+ shutting_down_ = true;
+
+ Shell* shell = ash::Shell::GetInstance();
+ shell->env_filter()->set_cursor_hidden_by_filter(false);
+ shell->cursor_manager()->HideCursor();
+
+ StartRealShutdownTimer(false);
+}
+
+void LockStateControllerImpl2::StartRealShutdownTimer(
+ bool with_animation_time) {
+ base::TimeDelta duration =
+ base::TimeDelta::FromMilliseconds(kShutdownRequestDelayMs);
+ if (with_animation_time) {
+ duration += animator_->GetDuration(
+ internal::SessionStateAnimator::ANIMATION_SPEED_SHUTDOWN);
+ }
+ real_shutdown_timer_.Start(
+ FROM_HERE,
+ duration,
+ this,
+ &LockStateControllerImpl2::OnRealShutdownTimeout);
+}
+
+void LockStateControllerImpl2::OnRealShutdownTimeout() {
+ DCHECK(shutting_down_);
+#if defined(OS_CHROMEOS)
+ if (!base::chromeos::IsRunningOnChromeOS()) {
+ ShellDelegate* delegate = Shell::GetInstance()->delegate();
+ if (delegate) {
+ delegate->Exit();
+ return;
+ }
+ }
+#endif
+ Shell::GetInstance()->delegate()->RecordUserMetricsAction(
+ UMA_ACCEL_SHUT_DOWN_POWER_BUTTON);
+ delegate_->RequestShutdown();
+}
+
+void LockStateControllerImpl2::OnLockScreenHide(
+ base::Callback<void(void)>& callback) {
+ StartUnlockAnimationBeforeUIDestroyed(callback);
+}
+
+void LockStateControllerImpl2::LockAnimationCancelled() {
+ can_cancel_lock_animation_ = false;
+ RestoreUnlockedProperties();
+}
+
+void LockStateControllerImpl2::PreLockAnimationFinished(
+ bool request_lock) {
+ can_cancel_lock_animation_ = false;
+
+ if (request_lock) {
+ Shell::GetInstance()->delegate()->RecordUserMetricsAction(
+ shutdown_after_lock_ ?
+ UMA_ACCEL_LOCK_SCREEN_POWER_BUTTON :
+ UMA_ACCEL_LOCK_SCREEN_LOCK_BUTTON);
+ delegate_->RequestLockScreen();
+ }
+
+ lock_fail_timer_.Start(
+ FROM_HERE,
+ base::TimeDelta::FromMilliseconds(kLockFailTimeoutMs),
+ this,
+ &LockStateControllerImpl2::OnLockFailTimeout);
+}
+
+void LockStateControllerImpl2::PostLockAnimationFinished() {
+ animating_lock_ = false;
+
+ FOR_EACH_OBSERVER(LockStateObserver, observers_,
+ OnLockStateEvent(LockStateObserver::EVENT_LOCK_ANIMATION_FINISHED));
+ if (!lock_screen_displayed_callback_.is_null()) {
+ lock_screen_displayed_callback_.Run();
+ lock_screen_displayed_callback_.Reset();
+ }
+ if (shutdown_after_lock_) {
+ shutdown_after_lock_ = false;
+ StartLockToShutdownTimer();
+ }
+}
+
+void LockStateControllerImpl2::
+UnlockAnimationAfterUIDestroyedFinished() {
+ RestoreUnlockedProperties();
+}
+
+void LockStateControllerImpl2::StartImmediatePreLockAnimation(
+ bool request_lock_on_completion) {
+ animating_lock_ = true;
+
+ StoreUnlockedProperties();
+
+ base::Closure next_animation_starter =
+ base::Bind(&LockStateControllerImpl2::PreLockAnimationFinished,
+ base::Unretained(this), request_lock_on_completion);
+ AnimationFinishedObserver* observer =
+ new AnimationFinishedObserver(next_animation_starter);
+
+ observer->Pause();
+
+ animator_->StartAnimationWithObserver(
+ internal::SessionStateAnimator::NON_LOCK_SCREEN_CONTAINERS,
+ internal::SessionStateAnimator::ANIMATION_LIFT,
+ internal::SessionStateAnimator::ANIMATION_SPEED_MOVE_WINDOWS,
+ observer);
+ animator_->StartAnimationWithObserver(
+ internal::SessionStateAnimator::LAUNCHER,
+ internal::SessionStateAnimator::ANIMATION_FADE_OUT,
+ internal::SessionStateAnimator::ANIMATION_SPEED_MOVE_WINDOWS,
+ observer);
+ // Hide the screen locker containers so we can raise them later.
+ animator_->StartAnimation(
+ internal::SessionStateAnimator::LOCK_SCREEN_CONTAINERS,
+ internal::SessionStateAnimator::ANIMATION_HIDE_IMMEDIATELY,
+ internal::SessionStateAnimator::ANIMATION_SPEED_IMMEDIATE);
+ AnimateBackgroundAppearanceIfNecessary(
+ internal::SessionStateAnimator::ANIMATION_SPEED_MOVE_WINDOWS,
+ observer);
+
+ observer->Unpause();
+
+ DispatchCancelMode();
+ FOR_EACH_OBSERVER(LockStateObserver, observers_,
+ OnLockStateEvent(LockStateObserver::EVENT_LOCK_ANIMATION_STARTED));
+}
+
+void LockStateControllerImpl2::StartCancellablePreLockAnimation() {
+ animating_lock_ = true;
+ StoreUnlockedProperties();
+
+ base::Closure next_animation_starter =
+ base::Bind(&LockStateControllerImpl2::PreLockAnimationFinished,
+ base::Unretained(this), true /* request_lock */);
+ AnimationFinishedObserver* observer =
+ new AnimationFinishedObserver(next_animation_starter);
+
+ observer->Pause();
+
+ animator_->StartAnimationWithObserver(
+ internal::SessionStateAnimator::NON_LOCK_SCREEN_CONTAINERS,
+ internal::SessionStateAnimator::ANIMATION_LIFT,
+ internal::SessionStateAnimator::ANIMATION_SPEED_UNDOABLE,
+ observer);
+ animator_->StartAnimationWithObserver(
+ internal::SessionStateAnimator::LAUNCHER,
+ internal::SessionStateAnimator::ANIMATION_FADE_OUT,
+ internal::SessionStateAnimator::ANIMATION_SPEED_UNDOABLE,
+ observer);
+ // Hide the screen locker containers so we can raise them later.
+ animator_->StartAnimation(
+ internal::SessionStateAnimator::LOCK_SCREEN_CONTAINERS,
+ internal::SessionStateAnimator::ANIMATION_HIDE_IMMEDIATELY,
+ internal::SessionStateAnimator::ANIMATION_SPEED_IMMEDIATE);
+ AnimateBackgroundAppearanceIfNecessary(
+ internal::SessionStateAnimator::ANIMATION_SPEED_UNDOABLE,
+ observer);
+
+ DispatchCancelMode();
+ FOR_EACH_OBSERVER(LockStateObserver, observers_,
+ OnLockStateEvent(LockStateObserver::EVENT_PRELOCK_ANIMATION_STARTED));
+ observer->Unpause();
+}
+
+void LockStateControllerImpl2::CancelPreLockAnimation() {
+ base::Closure next_animation_starter =
+ base::Bind(&LockStateControllerImpl2::LockAnimationCancelled,
+ base::Unretained(this));
+ AnimationFinishedObserver* observer =
+ new AnimationFinishedObserver(next_animation_starter);
+
+ observer->Pause();
+
+ animator_->StartAnimationWithObserver(
+ internal::SessionStateAnimator::NON_LOCK_SCREEN_CONTAINERS,
+ internal::SessionStateAnimator::ANIMATION_UNDO_LIFT,
+ internal::SessionStateAnimator::ANIMATION_SPEED_UNDO_MOVE_WINDOWS,
+ observer);
+ animator_->StartAnimationWithObserver(
+ internal::SessionStateAnimator::LAUNCHER,
+ internal::SessionStateAnimator::ANIMATION_FADE_IN,
+ internal::SessionStateAnimator::ANIMATION_SPEED_UNDO_MOVE_WINDOWS,
+ observer);
+ AnimateBackgroundHidingIfNecessary(
+ internal::SessionStateAnimator::ANIMATION_SPEED_UNDO_MOVE_WINDOWS,
+ observer);
+
+ observer->Unpause();
+}
+
+void LockStateControllerImpl2::StartPostLockAnimation() {
+ base::Closure next_animation_starter =
+ base::Bind(&LockStateControllerImpl2::PostLockAnimationFinished,
+ base::Unretained(this));
+
+ AnimationFinishedObserver* observer =
+ new AnimationFinishedObserver(next_animation_starter);
+
+ observer->Pause();
+ animator_->StartAnimationWithObserver(
+ internal::SessionStateAnimator::LOCK_SCREEN_CONTAINERS,
+ internal::SessionStateAnimator::ANIMATION_RAISE_TO_SCREEN,
+ internal::SessionStateAnimator::ANIMATION_SPEED_MOVE_WINDOWS,
+ observer);
+ observer->Unpause();
+}
+
+void LockStateControllerImpl2::StartUnlockAnimationBeforeUIDestroyed(
+ base::Closure& callback) {
+ animator_->StartAnimationWithCallback(
+ internal::SessionStateAnimator::LOCK_SCREEN_CONTAINERS,
+ internal::SessionStateAnimator::ANIMATION_LIFT,
+ internal::SessionStateAnimator::ANIMATION_SPEED_MOVE_WINDOWS,
+ callback);
+}
+
+void LockStateControllerImpl2::StartUnlockAnimationAfterUIDestroyed() {
+ base::Closure next_animation_starter =
+ base::Bind(
+ &LockStateControllerImpl2::
+ UnlockAnimationAfterUIDestroyedFinished,
+ base::Unretained(this));
+
+ AnimationFinishedObserver* observer =
+ new AnimationFinishedObserver(next_animation_starter);
+
+ observer->Pause();
+
+ animator_->StartAnimationWithObserver(
+ internal::SessionStateAnimator::NON_LOCK_SCREEN_CONTAINERS,
+ internal::SessionStateAnimator::ANIMATION_DROP,
+ internal::SessionStateAnimator::ANIMATION_SPEED_MOVE_WINDOWS,
+ observer);
+ animator_->StartAnimationWithObserver(
+ internal::SessionStateAnimator::LAUNCHER,
+ internal::SessionStateAnimator::ANIMATION_FADE_IN,
+ internal::SessionStateAnimator::ANIMATION_SPEED_MOVE_WINDOWS,
+ observer);
+ AnimateBackgroundHidingIfNecessary(
+ internal::SessionStateAnimator::ANIMATION_SPEED_MOVE_WINDOWS,
+ observer);
+ observer->Unpause();
+}
+
+void LockStateControllerImpl2::StoreUnlockedProperties() {
+ if (!unlocked_properties_) {
+ unlocked_properties_.reset(new UnlockedStateProperties());
+ unlocked_properties_->background_is_hidden = IsBackgroundHidden();
+ }
+ if (unlocked_properties_->background_is_hidden) {
+ // Hide background so that it can be animated later.
+ animator_->StartAnimation(
+ internal::SessionStateAnimator::DESKTOP_BACKGROUND,
+ internal::SessionStateAnimator::ANIMATION_HIDE_IMMEDIATELY,
+ internal::SessionStateAnimator::ANIMATION_SPEED_IMMEDIATE);
+ ShowBackground();
+ }
+}
+
+void LockStateControllerImpl2::RestoreUnlockedProperties() {
+ if (!unlocked_properties_)
+ return;
+ if (unlocked_properties_->background_is_hidden) {
+ HideBackground();
+ // Restore background visibility.
+ animator_->StartAnimation(
+ internal::SessionStateAnimator::DESKTOP_BACKGROUND,
+ internal::SessionStateAnimator::ANIMATION_FADE_IN,
+ internal::SessionStateAnimator::ANIMATION_SPEED_IMMEDIATE);
+ }
+ unlocked_properties_.reset();
+}
+
+void LockStateControllerImpl2::AnimateBackgroundAppearanceIfNecessary(
+ internal::SessionStateAnimator::AnimationSpeed speed,
+ ui::LayerAnimationObserver* observer) {
+ if (unlocked_properties_.get() &&
+ unlocked_properties_->background_is_hidden) {
+ animator_->StartAnimationWithObserver(
+ internal::SessionStateAnimator::DESKTOP_BACKGROUND,
+ internal::SessionStateAnimator::ANIMATION_FADE_IN,
+ speed,
+ observer);
+ }
+}
+
+void LockStateControllerImpl2::AnimateBackgroundHidingIfNecessary(
+ internal::SessionStateAnimator::AnimationSpeed speed,
+ ui::LayerAnimationObserver* observer) {
+ if (unlocked_properties_.get() &&
+ unlocked_properties_->background_is_hidden) {
+ animator_->StartAnimationWithObserver(
+ internal::SessionStateAnimator::DESKTOP_BACKGROUND,
+ internal::SessionStateAnimator::ANIMATION_FADE_OUT,
+ speed,
+ observer);
+ }
+}
+
+} // namespace ash
diff --git a/chromium/ash/wm/lock_state_controller_impl2.h b/chromium/ash/wm/lock_state_controller_impl2.h
new file mode 100644
index 00000000000..8908a7eced2
--- /dev/null
+++ b/chromium/ash/wm/lock_state_controller_impl2.h
@@ -0,0 +1,263 @@
+// Copyright 2013 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 ASH_WM_LOCK_STATE_CONTROLLER_IMPL2_H_
+#define ASH_WM_LOCK_STATE_CONTROLLER_IMPL2_H_
+
+#include "ash/ash_export.h"
+#include "ash/shell_observer.h"
+#include "ash/wm/lock_state_controller.h"
+#include "ash/wm/session_state_animator.h"
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/time/time.h"
+#include "base/timer/timer.h"
+#include "ui/aura/root_window_observer.h"
+
+namespace gfx {
+class Rect;
+class Size;
+}
+
+namespace ui {
+class Layer;
+}
+
+namespace ash {
+
+namespace test {
+class LockStateControllerImpl2Test;
+}
+
+// Displays onscreen animations and locks or suspends the system in response to
+// the power button being pressed or released.
+// Lock workflow:
+// Entry points:
+// * StartLockAnimation (bool shutdown after lock) - starts lock that can be
+// cancelled.
+// * StartLockAnimationAndLockImmediately - starts uninterruptible lock
+// animation.
+// This leads to call of either StartImmediatePreLockAnimation or
+// StartCancellablePreLockAnimation. Once they complete
+// PreLockAnimationFinished is called, and system lock is requested.
+// Once system locks and lock UI is created, OnLockStateChanged is called, and
+// StartPostLockAnimation is called. In PostLockAnimationFinished two
+// things happen : EVENT_LOCK_ANIMATION_FINISHED notification is sent (it
+// triggers third part of animation within lock UI), and check for continuing to
+// shutdown is made.
+//
+// Unlock workflow:
+// WebUI does first part of animation, and calls OnLockScreenHide(callback) that
+// triggers StartUnlockAnimationBeforeUIDestroyed(callback). Once callback is
+// called at the end of the animation, lock UI is deleted, system unlocks, and
+// OnLockStateChanged is called. It leads to
+// StartUnlockAnimationAfterUIDestroyed.
+
+class ASH_EXPORT LockStateControllerImpl2 : public LockStateController {
+ public:
+
+ // Helper class used by tests to access internal state.
+ class ASH_EXPORT TestApi {
+ public:
+ explicit TestApi(LockStateControllerImpl2* controller);
+
+ virtual ~TestApi();
+
+ bool lock_fail_timer_is_running() const {
+ return controller_->lock_fail_timer_.IsRunning();
+ }
+ bool lock_to_shutdown_timer_is_running() const {
+ return controller_->lock_to_shutdown_timer_.IsRunning();
+ }
+ bool shutdown_timer_is_running() const {
+ return controller_->pre_shutdown_timer_.IsRunning();
+ }
+ bool real_shutdown_timer_is_running() const {
+ return controller_->real_shutdown_timer_.IsRunning();
+ }
+ bool is_animating_lock() const {
+ return controller_->animating_lock_;
+ }
+ bool is_lock_cancellable() const {
+ return controller_->CanCancelLockAnimation();
+ }
+
+ void trigger_lock_fail_timeout() {
+ controller_->OnLockFailTimeout();
+ controller_->lock_fail_timer_.Stop();
+ }
+ void trigger_lock_to_shutdown_timeout() {
+ controller_->OnLockToShutdownTimeout();
+ controller_->lock_to_shutdown_timer_.Stop();
+ }
+ void trigger_shutdown_timeout() {
+ controller_->OnPreShutdownAnimationTimeout();
+ controller_->pre_shutdown_timer_.Stop();
+ }
+ void trigger_real_shutdown_timeout() {
+ controller_->OnRealShutdownTimeout();
+ controller_->real_shutdown_timer_.Stop();
+ }
+ private:
+ LockStateControllerImpl2* controller_; // not owned
+
+ DISALLOW_COPY_AND_ASSIGN(TestApi);
+ };
+
+ LockStateControllerImpl2();
+ virtual ~LockStateControllerImpl2();
+
+ // RootWindowObserver override:
+ virtual void OnRootWindowHostCloseRequested(
+ const aura::RootWindow* root) OVERRIDE;
+
+ // ShellObserver overrides:
+ virtual void OnLoginStateChanged(user::LoginStatus status) OVERRIDE;
+ virtual void OnAppTerminating() OVERRIDE;
+ virtual void OnLockStateChanged(bool locked) OVERRIDE;
+
+ // LockStateController overrides:
+ virtual void StartLockAnimation(bool shutdown_after_lock) OVERRIDE;
+
+ virtual void StartShutdownAnimation() OVERRIDE;
+ virtual void StartLockAnimationAndLockImmediately() OVERRIDE;
+
+ virtual bool LockRequested() OVERRIDE;
+ virtual bool ShutdownRequested() OVERRIDE;
+
+ virtual bool CanCancelLockAnimation() OVERRIDE;
+ virtual void CancelLockAnimation() OVERRIDE;
+
+ virtual bool CanCancelShutdownAnimation() OVERRIDE;
+ virtual void CancelShutdownAnimation() OVERRIDE;
+
+ virtual void OnStartingLock() OVERRIDE;
+ virtual void RequestShutdown() OVERRIDE;
+
+ virtual void OnLockScreenHide(base::Closure& callback) OVERRIDE;
+ virtual void SetLockScreenDisplayedCallback(base::Closure& callback) OVERRIDE;
+
+ protected:
+ friend class test::LockStateControllerImpl2Test;
+
+ private:
+ struct UnlockedStateProperties {
+ bool background_is_hidden;
+ };
+
+ void RequestShutdownImpl();
+
+ // Reverts the pre-lock animation, reports the error.
+ void OnLockFailTimeout();
+
+ // Starts timer for gap between lock and shutdown.
+ void StartLockToShutdownTimer();
+
+ // Calls StartShutdownAnimation().
+ void OnLockToShutdownTimeout();
+
+ // Starts timer for undoable shutdown animation.
+ void StartPreShutdownAnimationTimer();
+
+ // Calls RequestShutdownImpl();
+ void OnPreShutdownAnimationTimeout();
+
+ // Starts timer for final shutdown animation.
+ // If |with_animation_time| is true, it will also include time of "fade to
+ // white" shutdown animation.
+ void StartRealShutdownTimer(bool with_animation_time);
+
+ // Requests that the machine be shut down.
+ void OnRealShutdownTimeout();
+
+ // Starts shutdown animation that can be cancelled and starts pre-shutdown
+ // timer.
+ void StartCancellableShutdownAnimation();
+
+ // Starts non-cancellable animation and starts real shutdown timer that
+ // includes animation time.
+ void StartShutdownAnimationImpl();
+
+ // Triggers late animations on the lock screen.
+ void OnLockScreenAnimationFinished();
+
+ // If |request_lock_on_completion| is true, a lock request will be sent
+ // after the pre-lock animation completes. (The pre-lock animation is
+ // also displayed in response to already-in-progress lock requests; in
+ // these cases an additional lock request is undesirable.)
+ void StartImmediatePreLockAnimation(bool request_lock_on_completion);
+ void StartCancellablePreLockAnimation();
+ void CancelPreLockAnimation();
+ void StartPostLockAnimation();
+ // This method calls |callback| when animation completes.
+ void StartUnlockAnimationBeforeUIDestroyed(base::Closure &callback);
+ void StartUnlockAnimationAfterUIDestroyed();
+
+ // These methods are called when corresponding animation completes.
+ void LockAnimationCancelled();
+ void PreLockAnimationFinished(bool request_lock);
+ void PostLockAnimationFinished();
+ void UnlockAnimationAfterUIDestroyedFinished();
+
+ // Stores properties of UI that have to be temporarily modified while locking.
+ void StoreUnlockedProperties();
+ void RestoreUnlockedProperties();
+
+ // Fades in background layer with |speed| if it was hidden in unlocked state.
+ void AnimateBackgroundAppearanceIfNecessary(
+ ash::internal::SessionStateAnimator::AnimationSpeed speed,
+ ui::LayerAnimationObserver* observer);
+
+ // Fades out background layer with |speed| if it was hidden in unlocked state.
+ void AnimateBackgroundHidingIfNecessary(
+ ash::internal::SessionStateAnimator::AnimationSpeed speed,
+ ui::LayerAnimationObserver* observer);
+
+ // The current login status, or original login status from before we locked.
+ user::LoginStatus login_status_;
+
+ // Current lock status.
+ bool system_is_locked_;
+
+ // Are we in the process of shutting the machine down?
+ bool shutting_down_;
+
+ // Indicates whether controller should proceed to (cancellable) shutdown after
+ // locking.
+ bool shutdown_after_lock_;
+
+ // Indicates that controller displays lock animation.
+ bool animating_lock_;
+
+ // Indicates that lock animation can be undone.
+ bool can_cancel_lock_animation_;
+
+ scoped_ptr<UnlockedStateProperties> unlocked_properties_;
+
+ // Started when we request that the screen be locked. When it fires, we
+ // assume that our request got dropped.
+ base::OneShotTimer<LockStateControllerImpl2> lock_fail_timer_;
+
+ // Started when the screen is locked while the power button is held. Adds a
+ // delay between the appearance of the lock screen and the beginning of the
+ // pre-shutdown animation.
+ base::OneShotTimer<LockStateControllerImpl2> lock_to_shutdown_timer_;
+
+ // Started when we begin displaying the pre-shutdown animation. When it
+ // fires, we start the shutdown animation and get ready to request shutdown.
+ base::OneShotTimer<LockStateControllerImpl2> pre_shutdown_timer_;
+
+ // Started when we display the shutdown animation. When it fires, we actually
+ // request shutdown. Gives the animation time to complete before Chrome, X,
+ // etc. are shut down.
+ base::OneShotTimer<LockStateControllerImpl2> real_shutdown_timer_;
+
+ base::Closure lock_screen_displayed_callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(LockStateControllerImpl2);
+};
+
+} // namespace ash
+
+#endif // ASH_WM_LOCK_STATE_CONTROLLER_IMPL2_H_
diff --git a/chromium/ash/wm/lock_state_controller_impl2_unittest.cc b/chromium/ash/wm/lock_state_controller_impl2_unittest.cc
new file mode 100644
index 00000000000..62bed7cbc41
--- /dev/null
+++ b/chromium/ash/wm/lock_state_controller_impl2_unittest.cc
@@ -0,0 +1,1070 @@
+// Copyright 2013 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.
+
+#include "ash/ash_switches.h"
+#include "ash/session_state_delegate.h"
+#include "ash/shell.h"
+#include "ash/shell_window_ids.h"
+#include "ash/test/ash_test_base.h"
+#include "ash/test/test_shell_delegate.h"
+#include "ash/wm/lock_state_controller.h"
+#include "ash/wm/lock_state_controller_impl2.h"
+#include "ash/wm/power_button_controller.h"
+#include "ash/wm/session_state_animator.h"
+#include "base/command_line.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/time/time.h"
+#include "ui/aura/env.h"
+#include "ui/aura/root_window.h"
+#include "ui/aura/test/event_generator.h"
+#include "ui/compositor/layer_animator.h"
+#include "ui/compositor/scoped_animation_duration_scale_mode.h"
+#include "ui/compositor/scoped_layer_animation_settings.h"
+#include "ui/gfx/rect.h"
+#include "ui/gfx/size.h"
+
+#if defined(OS_WIN)
+#include "base/win/windows_version.h"
+#endif
+
+namespace ash {
+
+using internal::SessionStateAnimator;
+
+namespace test {
+
+namespace {
+
+bool cursor_visible() {
+ return ash::Shell::GetInstance()->cursor_manager()->IsCursorVisible();
+}
+
+void CheckCalledCallback(bool* flag) {
+ if (flag)
+ (*flag) = true;
+}
+
+aura::Window* GetContainer(int container ) {
+ aura::RootWindow* root_window = Shell::GetPrimaryRootWindow();
+ return Shell::GetContainer(root_window, container);
+}
+
+bool IsBackgroundHidden() {
+ return !GetContainer(internal::kShellWindowId_DesktopBackgroundContainer)->
+ IsVisible();
+}
+
+void ShowBackground() {
+ ui::ScopedLayerAnimationSettings settings(
+ GetContainer(internal::kShellWindowId_DesktopBackgroundContainer)->
+ layer()->GetAnimator());
+ settings.SetTransitionDuration(base::TimeDelta());
+ GetContainer(internal::kShellWindowId_DesktopBackgroundContainer)->Show();
+}
+
+void HideBackground() {
+ ui::ScopedLayerAnimationSettings settings(
+ GetContainer(internal::kShellWindowId_DesktopBackgroundContainer)->
+ layer()->GetAnimator());
+ settings.SetTransitionDuration(base::TimeDelta());
+ GetContainer(internal::kShellWindowId_DesktopBackgroundContainer)->Hide();
+}
+
+} // namespace
+
+// Fake implementation of PowerButtonControllerDelegate that just logs requests
+// to lock the screen and shut down the device.
+class TestLockStateControllerDelegate : public LockStateControllerDelegate {
+ public:
+ TestLockStateControllerDelegate()
+ : num_lock_requests_(0),
+ num_shutdown_requests_(0) {}
+
+ int num_lock_requests() const { return num_lock_requests_; }
+ int num_shutdown_requests() const { return num_shutdown_requests_; }
+
+ // LockStateControllerDelegate implementation.
+ virtual void RequestLockScreen() OVERRIDE {
+ num_lock_requests_++;
+ }
+ virtual void RequestShutdown() OVERRIDE {
+ num_shutdown_requests_++;
+ }
+
+ private:
+ int num_lock_requests_;
+ int num_shutdown_requests_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestLockStateControllerDelegate);
+};
+
+class LockStateControllerImpl2Test : public AshTestBase {
+ public:
+ LockStateControllerImpl2Test() : controller_(NULL), delegate_(NULL) {}
+ virtual ~LockStateControllerImpl2Test() {}
+
+ virtual void SetUp() OVERRIDE {
+ CHECK(!CommandLine::ForCurrentProcess()->HasSwitch(
+ ash::switches::kAshDisableNewLockAnimations));
+
+ AshTestBase::SetUp();
+
+ // We would control animations in a fine way:
+ animation_duration_mode_.reset(new ui::ScopedAnimationDurationScaleMode(
+ ui::ScopedAnimationDurationScaleMode::NORMAL_DURATION));
+ // TODO(antrim) : restore
+ // animator_helper_ = ui::test::CreateLayerAnimatorHelperForTest();
+
+ // Temporary disable animations so that observer is always called, and
+ // no leaks happen during tests.
+ animation_duration_mode_.reset(new ui::ScopedAnimationDurationScaleMode(
+ ui::ScopedAnimationDurationScaleMode::ZERO_DURATION));
+ // TODO(antrim): once there is a way to mock time and run animations, make
+ // sure that animations are finished even in simple tests.
+
+ delegate_ = new TestLockStateControllerDelegate;
+ controller_ = Shell::GetInstance()->power_button_controller();
+ lock_state_controller_ = static_cast<LockStateControllerImpl2*>(
+ Shell::GetInstance()->lock_state_controller());
+ lock_state_controller_->SetDelegate(delegate_); // transfers ownership
+ test_api_.reset(
+ new LockStateControllerImpl2::TestApi(lock_state_controller_));
+ animator_api_.reset(
+ new SessionStateAnimator::TestApi(lock_state_controller_->
+ animator_.get()));
+ shell_delegate_ = reinterpret_cast<TestShellDelegate*>(
+ ash::Shell::GetInstance()->delegate());
+ session_state_delegate_ = Shell::GetInstance()->session_state_delegate();
+ }
+
+ virtual void TearDown() {
+ // TODO(antrim) : restore
+ // animator_helper_->AdvanceUntilDone();
+ AshTestBase::TearDown();
+ }
+
+ protected:
+ void GenerateMouseMoveEvent() {
+ aura::test::EventGenerator generator(
+ Shell::GetPrimaryRootWindow());
+ generator.MoveMouseTo(10, 10);
+ }
+
+ int NumShutdownRequests() {
+ return delegate_->num_shutdown_requests() +
+ shell_delegate_->num_exit_requests();
+ }
+
+ void Advance(SessionStateAnimator::AnimationSpeed speed) {
+ // TODO (antrim) : restore
+ // animator_helper_->Advance(SessionStateAnimator::GetDuration(speed));
+ }
+
+ void AdvancePartially(SessionStateAnimator::AnimationSpeed speed,
+ float factor) {
+// TODO (antrim) : restore
+// base::TimeDelta duration = SessionStateAnimator::GetDuration(speed);
+// base::TimeDelta partial_duration =
+// base::TimeDelta::FromInternalValue(duration.ToInternalValue() * factor);
+// animator_helper_->Advance(partial_duration);
+ }
+
+ void ExpectPreLockAnimationStarted() {
+ //TODO (antrim) : restore EXPECT_TRUE(animator_helper_->IsAnimating());
+ EXPECT_TRUE(
+ animator_api_->ContainersAreAnimated(
+ SessionStateAnimator::NON_LOCK_SCREEN_CONTAINERS,
+ SessionStateAnimator::ANIMATION_LIFT));
+ EXPECT_TRUE(
+ animator_api_->ContainersAreAnimated(
+ SessionStateAnimator::LAUNCHER,
+ SessionStateAnimator::ANIMATION_FADE_OUT));
+ EXPECT_TRUE(
+ animator_api_->ContainersAreAnimated(
+ SessionStateAnimator::LOCK_SCREEN_CONTAINERS,
+ SessionStateAnimator::ANIMATION_HIDE_IMMEDIATELY));
+ EXPECT_TRUE(test_api_->is_animating_lock());
+ }
+
+ void ExpectPreLockAnimationCancel() {
+ EXPECT_TRUE(
+ animator_api_->ContainersAreAnimated(
+ SessionStateAnimator::NON_LOCK_SCREEN_CONTAINERS,
+ SessionStateAnimator::ANIMATION_DROP));
+ EXPECT_TRUE(
+ animator_api_->ContainersAreAnimated(
+ SessionStateAnimator::LAUNCHER,
+ SessionStateAnimator::ANIMATION_FADE_IN));
+ }
+
+ void ExpectPreLockAnimationFinished() {
+ //TODO (antrim) : restore EXPECT_FALSE(animator_helper_->IsAnimating());
+ EXPECT_TRUE(
+ animator_api_->ContainersAreAnimated(
+ SessionStateAnimator::NON_LOCK_SCREEN_CONTAINERS,
+ SessionStateAnimator::ANIMATION_LIFT));
+ EXPECT_TRUE(
+ animator_api_->ContainersAreAnimated(
+ SessionStateAnimator::LAUNCHER,
+ SessionStateAnimator::ANIMATION_FADE_OUT));
+ EXPECT_TRUE(
+ animator_api_->ContainersAreAnimated(
+ SessionStateAnimator::LOCK_SCREEN_CONTAINERS,
+ SessionStateAnimator::ANIMATION_HIDE_IMMEDIATELY));
+ }
+
+ void ExpectPostLockAnimationStarted() {
+ //TODO (antrim) : restore EXPECT_TRUE(animator_helper_->IsAnimating());
+ EXPECT_TRUE(
+ animator_api_->ContainersAreAnimated(
+ SessionStateAnimator::LOCK_SCREEN_CONTAINERS,
+ SessionStateAnimator::ANIMATION_RAISE_TO_SCREEN));
+ }
+
+ void ExpectPastLockAnimationFinished() {
+ //TODO (antrim) : restore EXPECT_FALSE(animator_helper_->IsAnimating());
+ EXPECT_TRUE(
+ animator_api_->ContainersAreAnimated(
+ SessionStateAnimator::LOCK_SCREEN_CONTAINERS,
+ SessionStateAnimator::ANIMATION_RAISE_TO_SCREEN));
+ }
+
+ void ExpectUnlockBeforeUIDestroyedAnimationStarted() {
+ //TODO (antrim) : restore EXPECT_TRUE(animator_helper_->IsAnimating());
+ EXPECT_TRUE(
+ animator_api_->ContainersAreAnimated(
+ SessionStateAnimator::LOCK_SCREEN_CONTAINERS,
+ SessionStateAnimator::ANIMATION_LIFT));
+ }
+
+ void ExpectUnlockBeforeUIDestroyedAnimationFinished() {
+ //TODO (antrim) : restore EXPECT_FALSE(animator_helper_->IsAnimating());
+ EXPECT_TRUE(
+ animator_api_->ContainersAreAnimated(
+ SessionStateAnimator::LOCK_SCREEN_CONTAINERS,
+ SessionStateAnimator::ANIMATION_LIFT));
+ }
+
+ void ExpectUnlockAfterUIDestroyedAnimationStarted() {
+ //TODO (antrim) : restore EXPECT_TRUE(animator_helper_->IsAnimating());
+ EXPECT_TRUE(
+ animator_api_->ContainersAreAnimated(
+ SessionStateAnimator::NON_LOCK_SCREEN_CONTAINERS,
+ SessionStateAnimator::ANIMATION_DROP));
+ EXPECT_TRUE(
+ animator_api_->ContainersAreAnimated(
+ SessionStateAnimator::LAUNCHER,
+ SessionStateAnimator::ANIMATION_FADE_IN));
+ }
+
+ void ExpectUnlockAfterUIDestroyedAnimationFinished() {
+ //TODO (antrim) : restore EXPECT_FALSE(animator_helper_->IsAnimating());
+ EXPECT_TRUE(
+ animator_api_->ContainersAreAnimated(
+ SessionStateAnimator::NON_LOCK_SCREEN_CONTAINERS,
+ SessionStateAnimator::ANIMATION_DROP));
+ EXPECT_TRUE(
+ animator_api_->ContainersAreAnimated(
+ SessionStateAnimator::LAUNCHER,
+ SessionStateAnimator::ANIMATION_FADE_IN));
+ }
+
+ void ExpectShutdownAnimationStarted() {
+ //TODO (antrim) : restore EXPECT_TRUE(animator_helper_->IsAnimating());
+ EXPECT_TRUE(
+ animator_api_->RootWindowIsAnimated(
+ SessionStateAnimator::ANIMATION_GRAYSCALE_BRIGHTNESS));
+ }
+
+ void ExpectShutdownAnimationFinished() {
+ //TODO (antrim) : restore EXPECT_FALSE(animator_helper_->IsAnimating());
+ EXPECT_TRUE(
+ animator_api_->RootWindowIsAnimated(
+ SessionStateAnimator::ANIMATION_GRAYSCALE_BRIGHTNESS));
+ }
+
+ void ExpectShutdownAnimationCancel() {
+ //TODO (antrim) : restore EXPECT_TRUE(animator_helper_->IsAnimating());
+ EXPECT_TRUE(
+ animator_api_->RootWindowIsAnimated(
+ SessionStateAnimator::ANIMATION_UNDO_GRAYSCALE_BRIGHTNESS));
+ }
+
+ void ExpectBackgroundIsShowing() {
+ //TODO (antrim) : restore EXPECT_TRUE(animator_helper_->IsAnimating());
+ EXPECT_TRUE(
+ animator_api_->ContainersAreAnimated(
+ SessionStateAnimator::DESKTOP_BACKGROUND,
+ SessionStateAnimator::ANIMATION_FADE_IN));
+ }
+
+ void ExpectBackgroundIsHiding() {
+ //TODO (antrim) : restore EXPECT_TRUE(animator_helper_->IsAnimating());
+ EXPECT_TRUE(
+ animator_api_->ContainersAreAnimated(
+ SessionStateAnimator::DESKTOP_BACKGROUND,
+ SessionStateAnimator::ANIMATION_FADE_OUT));
+ }
+
+ void ExpectUnlockedState() {
+ //TODO (antrim) : restore EXPECT_FALSE(animator_helper_->IsAnimating());
+ EXPECT_FALSE(session_state_delegate_->IsScreenLocked());
+
+ aura::Window::Windows containers;
+
+ SessionStateAnimator::GetContainers(
+ SessionStateAnimator::LAUNCHER |
+ SessionStateAnimator::NON_LOCK_SCREEN_CONTAINERS,
+ &containers);
+ for (aura::Window::Windows::const_iterator it = containers.begin();
+ it != containers.end(); ++it) {
+ aura::Window* window = *it;
+ ui::Layer* layer = window->layer();
+ EXPECT_EQ(1.0f, layer->opacity());
+ EXPECT_EQ(0.0f, layer->layer_brightness());
+ EXPECT_EQ(0.0f, layer->layer_saturation());
+ EXPECT_EQ(gfx::Transform(), layer->transform());
+ }
+ }
+
+ void ExpectLockedState() {
+ //TODO (antrim) : restore EXPECT_FALSE(animator_helper_->IsAnimating());
+ EXPECT_TRUE(session_state_delegate_->IsScreenLocked());
+
+ aura::Window::Windows containers;
+
+ SessionStateAnimator::GetContainers(
+ SessionStateAnimator::LOCK_SCREEN_RELATED_CONTAINERS |
+ SessionStateAnimator::LOCK_SCREEN_CONTAINERS,
+ &containers);
+ for (aura::Window::Windows::const_iterator it = containers.begin();
+ it != containers.end(); ++it) {
+ aura::Window* window = *it;
+ ui::Layer* layer = window->layer();
+ EXPECT_EQ(1.0f, layer->opacity());
+ EXPECT_EQ(0.0f, layer->layer_brightness());
+ EXPECT_EQ(0.0f, layer->layer_saturation());
+ EXPECT_EQ(gfx::Transform(), layer->transform());
+ }
+ }
+
+ void PressPowerButton() {
+ controller_->OnPowerButtonEvent(true, base::TimeTicks::Now());
+ //TODO (antrim) : restore animator_helper_->Advance(base::TimeDelta());
+ }
+
+ void ReleasePowerButton() {
+ controller_->OnPowerButtonEvent(false, base::TimeTicks::Now());
+ //TODO (antrim) : restore animator_helper_->Advance(base::TimeDelta());
+ }
+
+ void PressLockButton() {
+ controller_->OnLockButtonEvent(true, base::TimeTicks::Now());
+ }
+
+ void ReleaseLockButton() {
+ controller_->OnLockButtonEvent(false, base::TimeTicks::Now());
+ }
+
+ void SystemLocks() {
+ lock_state_controller_->OnLockStateChanged(true);
+ session_state_delegate_->LockScreen();
+ //TODO (antrim) : restore animator_helper_->Advance(base::TimeDelta());
+ }
+
+ void SuccessfulAuthentication(bool* call_flag) {
+ base::Closure closure = base::Bind(&CheckCalledCallback, call_flag);
+ lock_state_controller_->OnLockScreenHide(closure);
+ //TODO (antrim) : restore animator_helper_->Advance(base::TimeDelta());
+ }
+
+ void SystemUnlocks() {
+ lock_state_controller_->OnLockStateChanged(false);
+ session_state_delegate_->UnlockScreen();
+ //TODO (antrim) : restore animator_helper_->Advance(base::TimeDelta());
+ }
+
+ void Initialize(bool legacy_button, user::LoginStatus status) {
+ controller_->set_has_legacy_power_button_for_test(legacy_button);
+ lock_state_controller_->OnLoginStateChanged(status);
+ SetUserLoggedIn(status != user::LOGGED_IN_NONE);
+ if (status == user::LOGGED_IN_GUEST)
+ SetCanLockScreen(false);
+ lock_state_controller_->OnLockStateChanged(false);
+ }
+
+ PowerButtonController* controller_; // not owned
+ LockStateControllerImpl2* lock_state_controller_; // not owned
+ TestLockStateControllerDelegate* delegate_; // not owned
+ TestShellDelegate* shell_delegate_; // not owned
+ SessionStateDelegate* session_state_delegate_; // not owned
+
+ scoped_ptr<ui::ScopedAnimationDurationScaleMode> animation_duration_mode_;
+ scoped_ptr<LockStateControllerImpl2::TestApi> test_api_;
+ scoped_ptr<SessionStateAnimator::TestApi> animator_api_;
+ // TODO(antrim) : restore
+// scoped_ptr<ui::test::AnimationContainerTestHelper> animator_helper_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(LockStateControllerImpl2Test);
+};
+
+// Test the lock-to-shutdown flow for non-Chrome-OS hardware that doesn't
+// correctly report power button releases. We should lock immediately the first
+// time the button is pressed and shut down when it's pressed from the locked
+// state.
+// TODO(antrim): Reenable this: http://crbug.com/167048
+TEST_F(LockStateControllerImpl2Test, DISABLED_LegacyLockAndShutDown) {
+ Initialize(true, user::LOGGED_IN_USER);
+
+ ExpectUnlockedState();
+
+ // We should request that the screen be locked immediately after seeing the
+ // power button get pressed.
+ PressPowerButton();
+
+ ExpectPreLockAnimationStarted();
+
+ EXPECT_FALSE(test_api_->is_lock_cancellable());
+
+ Advance(SessionStateAnimator::ANIMATION_SPEED_MOVE_WINDOWS);
+
+ ExpectPreLockAnimationFinished();
+ EXPECT_EQ(1, delegate_->num_lock_requests());
+
+ // Notify that we locked successfully.
+ lock_state_controller_->OnStartingLock();
+ // We had that animation already.
+ //TODO (antrim) : restore
+ // EXPECT_FALSE(animator_helper_->IsAnimating());
+
+ SystemLocks();
+
+ ExpectPostLockAnimationStarted();
+ Advance(SessionStateAnimator::ANIMATION_SPEED_MOVE_WINDOWS);
+ ExpectPastLockAnimationFinished();
+
+ // We shouldn't progress towards the shutdown state, however.
+ EXPECT_FALSE(test_api_->lock_to_shutdown_timer_is_running());
+ EXPECT_FALSE(test_api_->shutdown_timer_is_running());
+
+ ReleasePowerButton();
+
+ // Hold the button again and check that we start shutting down.
+ PressPowerButton();
+
+ ExpectShutdownAnimationStarted();
+
+ EXPECT_EQ(0, NumShutdownRequests());
+ // Make sure a mouse move event won't show the cursor.
+ GenerateMouseMoveEvent();
+ EXPECT_FALSE(cursor_visible());
+
+ EXPECT_TRUE(test_api_->real_shutdown_timer_is_running());
+ test_api_->trigger_real_shutdown_timeout();
+ EXPECT_EQ(1, NumShutdownRequests());
+}
+
+// Test that we start shutting down immediately if the power button is pressed
+// while we're not logged in on an unofficial system.
+TEST_F(LockStateControllerImpl2Test, LegacyNotLoggedIn) {
+ Initialize(true, user::LOGGED_IN_NONE);
+
+ PressPowerButton();
+ ExpectShutdownAnimationStarted();
+
+ EXPECT_TRUE(test_api_->real_shutdown_timer_is_running());
+}
+
+// Test that we start shutting down immediately if the power button is pressed
+// while we're logged in as a guest on an unofficial system.
+TEST_F(LockStateControllerImpl2Test, LegacyGuest) {
+ Initialize(true, user::LOGGED_IN_GUEST);
+
+ PressPowerButton();
+ ExpectShutdownAnimationStarted();
+
+ EXPECT_TRUE(test_api_->real_shutdown_timer_is_running());
+}
+
+// When we hold the power button while the user isn't logged in, we should shut
+// down the machine directly.
+TEST_F(LockStateControllerImpl2Test, ShutdownWhenNotLoggedIn) {
+ Initialize(false, user::LOGGED_IN_NONE);
+
+ // Press the power button and check that we start the shutdown timer.
+ PressPowerButton();
+ EXPECT_FALSE(test_api_->is_animating_lock());
+ EXPECT_TRUE(test_api_->shutdown_timer_is_running());
+ ExpectShutdownAnimationStarted();
+
+ AdvancePartially(SessionStateAnimator::ANIMATION_SPEED_SHUTDOWN, 0.5f);
+
+ // Release the power button before the shutdown timer fires.
+ ReleasePowerButton();
+
+ EXPECT_FALSE(test_api_->shutdown_timer_is_running());
+ ExpectShutdownAnimationCancel();
+
+ AdvancePartially(SessionStateAnimator::ANIMATION_SPEED_REVERT, 0.5f);
+
+ // Press the button again and make the shutdown timeout fire this time.
+ // Check that we start the timer for actually requesting the shutdown.
+ PressPowerButton();
+
+ EXPECT_TRUE(test_api_->shutdown_timer_is_running());
+
+ Advance(SessionStateAnimator::ANIMATION_SPEED_SHUTDOWN);
+ ExpectShutdownAnimationFinished();
+ test_api_->trigger_shutdown_timeout();
+
+ EXPECT_TRUE(test_api_->real_shutdown_timer_is_running());
+ EXPECT_EQ(0, NumShutdownRequests());
+
+ // When the timout fires, we should request a shutdown.
+ test_api_->trigger_real_shutdown_timeout();
+
+ EXPECT_EQ(1, NumShutdownRequests());
+}
+
+// Test that we lock the screen and deal with unlocking correctly.
+// TODO(antrim): Reenable this: http://crbug.com/167048
+TEST_F(LockStateControllerImpl2Test, DISABLED_LockAndUnlock) {
+ Initialize(false, user::LOGGED_IN_USER);
+
+ ExpectUnlockedState();
+
+ // Press the power button and check that the lock timer is started and that we
+ // start lifting the non-screen-locker containers.
+ PressPowerButton();
+
+ ExpectPreLockAnimationStarted();
+ EXPECT_TRUE(test_api_->is_lock_cancellable());
+ EXPECT_EQ(0, delegate_->num_lock_requests());
+
+ Advance(SessionStateAnimator::ANIMATION_SPEED_UNDOABLE);
+ ExpectPreLockAnimationFinished();
+
+ EXPECT_EQ(1, delegate_->num_lock_requests());
+
+ // Notify that we locked successfully.
+ lock_state_controller_->OnStartingLock();
+ // We had that animation already.
+ //TODO (antrim) : restore EXPECT_FALSE(animator_helper_->IsAnimating());
+
+ SystemLocks();
+
+ ExpectPostLockAnimationStarted();
+ Advance(SessionStateAnimator::ANIMATION_SPEED_MOVE_WINDOWS);
+ ExpectPastLockAnimationFinished();
+
+ // When we release the power button, the lock-to-shutdown timer should be
+ // stopped.
+ ExpectLockedState();
+ EXPECT_TRUE(test_api_->lock_to_shutdown_timer_is_running());
+ ReleasePowerButton();
+ ExpectLockedState();
+ EXPECT_FALSE(test_api_->lock_to_shutdown_timer_is_running());
+
+ // Notify that the screen has been unlocked. We should show the
+ // non-screen-locker windows.
+ bool called = false;
+ SuccessfulAuthentication(&called);
+
+ ExpectUnlockBeforeUIDestroyedAnimationStarted();
+ EXPECT_FALSE(called);
+ Advance(SessionStateAnimator::ANIMATION_SPEED_MOVE_WINDOWS);
+ ExpectUnlockBeforeUIDestroyedAnimationFinished();
+
+ EXPECT_TRUE(called);
+
+ SystemUnlocks();
+
+ ExpectUnlockAfterUIDestroyedAnimationStarted();
+ Advance(SessionStateAnimator::ANIMATION_SPEED_MOVE_WINDOWS);
+ ExpectUnlockAfterUIDestroyedAnimationFinished();
+
+ ExpectUnlockedState();
+}
+
+// Test that we deal with cancelling lock correctly.
+// TODO(antrim): Reenable this: http://crbug.com/167048
+TEST_F(LockStateControllerImpl2Test, DISABLED_LockAndCancel) {
+ Initialize(false, user::LOGGED_IN_USER);
+
+ ExpectUnlockedState();
+
+ // Press the power button and check that the lock timer is started and that we
+ // start lifting the non-screen-locker containers.
+ PressPowerButton();
+
+ ExpectPreLockAnimationStarted();
+ EXPECT_TRUE(test_api_->is_lock_cancellable());
+
+ // forward only half way through
+ AdvancePartially(SessionStateAnimator::ANIMATION_SPEED_UNDOABLE, 0.5f);
+
+ gfx::Transform transform_before_button_released =
+ GetContainer(internal::kShellWindowId_DefaultContainer)->
+ layer()->transform();
+
+ // Release the button before the lock timer fires.
+ ReleasePowerButton();
+
+ ExpectPreLockAnimationCancel();
+
+ gfx::Transform transform_after_button_released =
+ GetContainer(internal::kShellWindowId_DefaultContainer)->
+ layer()->transform();
+ // Expect no flickering, animation should proceed from mid-state.
+ EXPECT_EQ(transform_before_button_released, transform_after_button_released);
+
+ Advance(SessionStateAnimator::ANIMATION_SPEED_MOVE_WINDOWS);
+ ExpectUnlockedState();
+ EXPECT_EQ(0, delegate_->num_lock_requests());
+}
+
+// Test that we deal with cancelling lock correctly.
+// TODO(antrim): Reenable this: http://crbug.com/167048
+TEST_F(LockStateControllerImpl2Test,
+ DISABLED_LockAndCancelAndLockAgain) {
+ Initialize(false, user::LOGGED_IN_USER);
+
+ ExpectUnlockedState();
+
+ // Press the power button and check that the lock timer is started and that we
+ // start lifting the non-screen-locker containers.
+ PressPowerButton();
+
+ ExpectPreLockAnimationStarted();
+ EXPECT_TRUE(test_api_->is_lock_cancellable());
+
+ // forward only half way through
+ AdvancePartially(SessionStateAnimator::ANIMATION_SPEED_UNDOABLE, 0.5f);
+
+ // Release the button before the lock timer fires.
+ ReleasePowerButton();
+ ExpectPreLockAnimationCancel();
+
+ AdvancePartially(SessionStateAnimator::ANIMATION_SPEED_MOVE_WINDOWS, 0.5f);
+
+ PressPowerButton();
+ ExpectPreLockAnimationStarted();
+ EXPECT_TRUE(test_api_->is_lock_cancellable());
+
+ AdvancePartially(SessionStateAnimator::ANIMATION_SPEED_UNDOABLE, 0.6f);
+
+ EXPECT_EQ(0, delegate_->num_lock_requests());
+ ExpectPreLockAnimationStarted();
+
+ AdvancePartially(SessionStateAnimator::ANIMATION_SPEED_UNDOABLE, 0.6f);
+ ExpectPreLockAnimationFinished();
+ EXPECT_EQ(1, delegate_->num_lock_requests());
+}
+
+// Hold the power button down from the unlocked state to eventual shutdown.
+// TODO(antrim): Reenable this: http://crbug.com/167048
+TEST_F(LockStateControllerImpl2Test, DISABLED_LockToShutdown) {
+ Initialize(false, user::LOGGED_IN_USER);
+
+ // Hold the power button and lock the screen.
+ PressPowerButton();
+ EXPECT_TRUE(test_api_->is_animating_lock());
+
+ Advance(SessionStateAnimator::ANIMATION_SPEED_UNDOABLE);
+ SystemLocks();
+ Advance(SessionStateAnimator::ANIMATION_SPEED_MOVE_WINDOWS);
+
+ // When the lock-to-shutdown timeout fires, we should start the shutdown
+ // timer.
+ EXPECT_TRUE(test_api_->lock_to_shutdown_timer_is_running());
+
+ test_api_->trigger_lock_to_shutdown_timeout();
+
+ ExpectShutdownAnimationStarted();
+ EXPECT_TRUE(test_api_->shutdown_timer_is_running());
+
+ // Fire the shutdown timeout and check that we request shutdown.
+ Advance(SessionStateAnimator::ANIMATION_SPEED_SHUTDOWN);
+ ExpectShutdownAnimationFinished();
+ test_api_->trigger_shutdown_timeout();
+
+ EXPECT_TRUE(test_api_->real_shutdown_timer_is_running());
+ EXPECT_EQ(0, NumShutdownRequests());
+ test_api_->trigger_real_shutdown_timeout();
+ EXPECT_EQ(1, NumShutdownRequests());
+}
+
+// Hold the power button down from the unlocked state to eventual shutdown,
+// then release the button while system does locking.
+TEST_F(LockStateControllerImpl2Test, CancelLockToShutdown) {
+ Initialize(false, user::LOGGED_IN_USER);
+
+ PressPowerButton();
+
+ // Hold the power button and lock the screen.
+ EXPECT_TRUE(test_api_->is_animating_lock());
+
+ Advance(SessionStateAnimator::ANIMATION_SPEED_UNDOABLE);
+ SystemLocks();
+ AdvancePartially(SessionStateAnimator::ANIMATION_SPEED_MOVE_WINDOWS, 0.5f);
+
+ // Power button is released while system attempts to lock.
+ ReleasePowerButton();
+
+ Advance(SessionStateAnimator::ANIMATION_SPEED_MOVE_WINDOWS);
+
+ EXPECT_FALSE(lock_state_controller_->ShutdownRequested());
+ EXPECT_FALSE(test_api_->lock_to_shutdown_timer_is_running());
+ EXPECT_FALSE(test_api_->shutdown_timer_is_running());
+}
+
+// Test that we handle the case where lock requests are ignored.
+// TODO(antrim): Reenable this: http://crbug.com/167048
+TEST_F(LockStateControllerImpl2Test, DISABLED_Lock) {
+ // We require animations to have a duration for this test.
+ ui::ScopedAnimationDurationScaleMode normal_duration_mode(
+ ui::ScopedAnimationDurationScaleMode::NORMAL_DURATION);
+
+ Initialize(false, user::LOGGED_IN_USER);
+
+ // Hold the power button and lock the screen.
+ PressPowerButton();
+ ExpectPreLockAnimationStarted();
+
+ Advance(SessionStateAnimator::ANIMATION_SPEED_UNDOABLE);
+
+ EXPECT_EQ(1, delegate_->num_lock_requests());
+ EXPECT_TRUE(test_api_->lock_fail_timer_is_running());
+ // We shouldn't start the lock-to-shutdown timer until the screen has actually
+ // been locked and this was animated.
+ EXPECT_FALSE(test_api_->lock_to_shutdown_timer_is_running());
+
+ // Act as if the request timed out. We should restore the windows.
+ test_api_->trigger_lock_fail_timeout();
+
+ ExpectUnlockAfterUIDestroyedAnimationStarted();
+ Advance(SessionStateAnimator::ANIMATION_SPEED_MOVE_WINDOWS);
+ ExpectUnlockAfterUIDestroyedAnimationFinished();
+ ExpectUnlockedState();
+}
+
+// Test the basic operation of the lock button (not logged in).
+TEST_F(LockStateControllerImpl2Test, LockButtonBasicNotLoggedIn) {
+ // The lock button shouldn't do anything if we aren't logged in.
+ Initialize(false, user::LOGGED_IN_NONE);
+
+ PressLockButton();
+ EXPECT_FALSE(test_api_->is_animating_lock());
+ ReleaseLockButton();
+ EXPECT_EQ(0, delegate_->num_lock_requests());
+}
+
+// Test the basic operation of the lock button (guest).
+TEST_F(LockStateControllerImpl2Test, LockButtonBasicGuest) {
+ // The lock button shouldn't do anything when we're logged in as a guest.
+ Initialize(false, user::LOGGED_IN_GUEST);
+
+ PressLockButton();
+ EXPECT_FALSE(test_api_->is_animating_lock());
+ ReleaseLockButton();
+ EXPECT_EQ(0, delegate_->num_lock_requests());
+}
+
+// Test the basic operation of the lock button.
+// TODO(antrim): Reenable this: http://crbug.com/167048
+TEST_F(LockStateControllerImpl2Test, DISABLED_LockButtonBasic) {
+ // If we're logged in as a regular user, we should start the lock timer and
+ // the pre-lock animation.
+ Initialize(false, user::LOGGED_IN_USER);
+
+ PressLockButton();
+ ExpectPreLockAnimationStarted();
+ AdvancePartially(SessionStateAnimator::ANIMATION_SPEED_UNDOABLE, 0.5f);
+
+ // If the button is released immediately, we shouldn't lock the screen.
+ ReleaseLockButton();
+ ExpectPreLockAnimationCancel();
+ Advance(SessionStateAnimator::ANIMATION_SPEED_MOVE_WINDOWS);
+
+ ExpectUnlockedState();
+ EXPECT_EQ(0, delegate_->num_lock_requests());
+
+ // Press the button again and let the lock timeout fire. We should request
+ // that the screen be locked.
+ PressLockButton();
+ ExpectPreLockAnimationStarted();
+ Advance(SessionStateAnimator::ANIMATION_SPEED_UNDOABLE);
+ EXPECT_EQ(1, delegate_->num_lock_requests());
+
+ // Pressing the lock button while we have a pending lock request shouldn't do
+ // anything.
+ ReleaseLockButton();
+ PressLockButton();
+ ExpectPreLockAnimationFinished();
+ ReleaseLockButton();
+
+ // Pressing the button also shouldn't do anything after the screen is locked.
+ SystemLocks();
+ ExpectPostLockAnimationStarted();
+
+ PressLockButton();
+ ReleaseLockButton();
+ ExpectPostLockAnimationStarted();
+
+ Advance(SessionStateAnimator::ANIMATION_SPEED_MOVE_WINDOWS);
+ ExpectPastLockAnimationFinished();
+
+ PressLockButton();
+ ReleaseLockButton();
+ ExpectPastLockAnimationFinished();
+}
+
+// Test that the power button takes priority over the lock button.
+// TODO(antrim): Reenable this: http://crbug.com/167048
+TEST_F(LockStateControllerImpl2Test,
+ DISABLED_PowerButtonPreemptsLockButton) {
+ Initialize(false, user::LOGGED_IN_USER);
+
+ // While the lock button is down, hold the power button.
+ PressLockButton();
+ ExpectPreLockAnimationStarted();
+ PressPowerButton();
+ ExpectPreLockAnimationStarted();
+
+ AdvancePartially(SessionStateAnimator::ANIMATION_SPEED_UNDOABLE, 0.5f);
+
+ // The lock timer shouldn't be stopped when the lock button is released.
+ ReleaseLockButton();
+ ExpectPreLockAnimationStarted();
+ ReleasePowerButton();
+ ExpectPreLockAnimationCancel();
+
+ Advance(SessionStateAnimator::ANIMATION_SPEED_MOVE_WINDOWS);
+ ExpectUnlockedState();
+
+ // Now press the power button first and then the lock button.
+ PressPowerButton();
+ ExpectPreLockAnimationStarted();
+ PressLockButton();
+ ExpectPreLockAnimationStarted();
+
+ AdvancePartially(SessionStateAnimator::ANIMATION_SPEED_UNDOABLE, 0.5f);
+
+ // Releasing the power button should stop the lock timer.
+ ReleasePowerButton();
+ ExpectPreLockAnimationCancel();
+ ReleaseLockButton();
+ ExpectPreLockAnimationCancel();
+}
+
+// When the screen is locked without going through the usual power-button
+// slow-close path (e.g. via the wrench menu), test that we still show the
+// fast-close animation.
+TEST_F(LockStateControllerImpl2Test, LockWithoutButton) {
+ Initialize(false, user::LOGGED_IN_USER);
+ lock_state_controller_->OnStartingLock();
+
+ ExpectPreLockAnimationStarted();
+ EXPECT_FALSE(test_api_->is_lock_cancellable());
+
+ // TODO(antrim): After time-faking is fixed, let the pre-lock animation
+ // complete here and check that delegate_->num_lock_requests() is 0 to
+ // prevent http://crbug.com/172487 from regressing.
+}
+
+// When we hear that the process is exiting but we haven't had a chance to
+// display an animation, we should just blank the screen.
+TEST_F(LockStateControllerImpl2Test, ShutdownWithoutButton) {
+ Initialize(false, user::LOGGED_IN_USER);
+ lock_state_controller_->OnAppTerminating();
+
+ EXPECT_TRUE(
+ animator_api_->ContainersAreAnimated(
+ SessionStateAnimator::kAllContainersMask,
+ SessionStateAnimator::ANIMATION_HIDE_IMMEDIATELY));
+ GenerateMouseMoveEvent();
+ EXPECT_FALSE(cursor_visible());
+}
+
+// Test that we display the fast-close animation and shut down when we get an
+// outside request to shut down (e.g. from the login or lock screen).
+TEST_F(LockStateControllerImpl2Test, RequestShutdownFromLoginScreen) {
+ Initialize(false, user::LOGGED_IN_NONE);
+
+ lock_state_controller_->RequestShutdown();
+
+ ExpectShutdownAnimationStarted();
+ Advance(SessionStateAnimator::ANIMATION_SPEED_SHUTDOWN);
+
+ GenerateMouseMoveEvent();
+ EXPECT_FALSE(cursor_visible());
+
+ EXPECT_EQ(0, NumShutdownRequests());
+ EXPECT_TRUE(test_api_->real_shutdown_timer_is_running());
+ test_api_->trigger_real_shutdown_timeout();
+ EXPECT_EQ(1, NumShutdownRequests());
+}
+
+TEST_F(LockStateControllerImpl2Test, RequestShutdownFromLockScreen) {
+ Initialize(false, user::LOGGED_IN_USER);
+
+ SystemLocks();
+ Advance(SessionStateAnimator::ANIMATION_SPEED_SHUTDOWN);
+ ExpectPastLockAnimationFinished();
+
+ lock_state_controller_->RequestShutdown();
+
+ ExpectShutdownAnimationStarted();
+ Advance(SessionStateAnimator::ANIMATION_SPEED_SHUTDOWN);
+
+ GenerateMouseMoveEvent();
+ EXPECT_FALSE(cursor_visible());
+
+ EXPECT_EQ(0, NumShutdownRequests());
+ EXPECT_TRUE(test_api_->real_shutdown_timer_is_running());
+ test_api_->trigger_real_shutdown_timeout();
+ EXPECT_EQ(1, NumShutdownRequests());
+}
+
+// TODO(antrim): Reenable this: http://crbug.com/167048
+TEST_F(LockStateControllerImpl2Test,
+ DISABLED_RequestAndCancelShutdownFromLockScreen) {
+ Initialize(false, user::LOGGED_IN_USER);
+
+ SystemLocks();
+ Advance(SessionStateAnimator::ANIMATION_SPEED_SHUTDOWN);
+ ExpectLockedState();
+
+ // Press the power button and check that we start the shutdown timer.
+ PressPowerButton();
+ EXPECT_FALSE(test_api_->is_animating_lock());
+ EXPECT_TRUE(test_api_->shutdown_timer_is_running());
+
+ ExpectShutdownAnimationStarted();
+
+ AdvancePartially(SessionStateAnimator::ANIMATION_SPEED_SHUTDOWN, 0.5f);
+
+ float grayscale_before_button_release =
+ Shell::GetPrimaryRootWindow()->layer()->layer_grayscale();
+
+ // Release the power button before the shutdown timer fires.
+ ReleasePowerButton();
+
+ EXPECT_FALSE(test_api_->shutdown_timer_is_running());
+
+ ExpectShutdownAnimationCancel();
+
+ float grayscale_after_button_release =
+ Shell::GetPrimaryRootWindow()->layer()->layer_grayscale();
+ // Expect no flickering in undo animation.
+ EXPECT_EQ(grayscale_before_button_release, grayscale_after_button_release);
+
+ Advance(SessionStateAnimator::ANIMATION_SPEED_REVERT);
+ ExpectLockedState();
+}
+
+// Test that we ignore power button presses when the screen is turned off.
+TEST_F(LockStateControllerImpl2Test, IgnorePowerButtonIfScreenIsOff) {
+ Initialize(false, user::LOGGED_IN_USER);
+
+ // When the screen brightness is at 0%, we shouldn't do anything in response
+ // to power button presses.
+ controller_->OnScreenBrightnessChanged(0.0);
+
+ PressPowerButton();
+ EXPECT_FALSE(test_api_->is_animating_lock());
+ ReleasePowerButton();
+
+ // After increasing the brightness to 10%, we should start the timer like
+ // usual.
+ controller_->OnScreenBrightnessChanged(10.0);
+
+ PressPowerButton();
+ EXPECT_TRUE(test_api_->is_animating_lock());
+}
+
+// Test that hidden background appears and revers correctly on lock/cancel.
+// TODO(antrim): Reenable this: http://crbug.com/167048
+TEST_F(LockStateControllerImpl2Test,
+ DISABLED_TestHiddenBackgroundLockCancel) {
+ Initialize(false, user::LOGGED_IN_USER);
+ HideBackground();
+
+ EXPECT_TRUE(IsBackgroundHidden());
+ ExpectUnlockedState();
+ PressPowerButton();
+
+ ExpectPreLockAnimationStarted();
+ EXPECT_FALSE(IsBackgroundHidden());
+ ExpectBackgroundIsShowing();
+
+ // Forward only half way through.
+ AdvancePartially(SessionStateAnimator::ANIMATION_SPEED_UNDOABLE, 0.5f);
+
+ // Release the button before the lock timer fires.
+ ReleasePowerButton();
+ ExpectPreLockAnimationCancel();
+ ExpectBackgroundIsHiding();
+ EXPECT_FALSE(IsBackgroundHidden());
+
+ Advance(SessionStateAnimator::ANIMATION_SPEED_MOVE_WINDOWS);
+
+ ExpectUnlockedState();
+ EXPECT_TRUE(IsBackgroundHidden());
+}
+
+// Test that hidden background appears and revers correctly on lock/unlock.
+// TODO(antrim): Reenable this: http://crbug.com/167048
+TEST_F(LockStateControllerImpl2Test,
+ DISABLED_TestHiddenBackgroundLockUnlock) {
+ Initialize(false, user::LOGGED_IN_USER);
+ HideBackground();
+
+ EXPECT_TRUE(IsBackgroundHidden());
+ ExpectUnlockedState();
+
+ // Press the power button and check that the lock timer is started and that we
+ // start lifting the non-screen-locker containers.
+ PressPowerButton();
+
+ ExpectPreLockAnimationStarted();
+ EXPECT_FALSE(IsBackgroundHidden());
+ ExpectBackgroundIsShowing();
+
+ Advance(SessionStateAnimator::ANIMATION_SPEED_UNDOABLE);
+
+ ExpectPreLockAnimationFinished();
+
+ SystemLocks();
+
+ ReleasePowerButton();
+
+ ExpectPostLockAnimationStarted();
+ Advance(SessionStateAnimator::ANIMATION_SPEED_MOVE_WINDOWS);
+ ExpectPastLockAnimationFinished();
+
+ ExpectLockedState();
+
+ SuccessfulAuthentication(NULL);
+
+ ExpectUnlockBeforeUIDestroyedAnimationStarted();
+ Advance(SessionStateAnimator::ANIMATION_SPEED_MOVE_WINDOWS);
+ ExpectUnlockBeforeUIDestroyedAnimationFinished();
+
+ SystemUnlocks();
+
+ ExpectUnlockAfterUIDestroyedAnimationStarted();
+ ExpectBackgroundIsHiding();
+ EXPECT_FALSE(IsBackgroundHidden());
+
+ Advance(SessionStateAnimator::ANIMATION_SPEED_MOVE_WINDOWS);
+ ExpectUnlockAfterUIDestroyedAnimationFinished();
+ EXPECT_TRUE(IsBackgroundHidden());
+
+ ExpectUnlockedState();
+}
+
+} // namespace test
+} // namespace ash
diff --git a/chromium/ash/wm/lock_state_observer.h b/chromium/ash/wm/lock_state_observer.h
new file mode 100644
index 00000000000..d247e828347
--- /dev/null
+++ b/chromium/ash/wm/lock_state_observer.h
@@ -0,0 +1,28 @@
+// Copyright 2013 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 ASH_WM_LOCK_STATE_OBSERVER_H_
+#define ASH_WM_LOCK_STATE_OBSERVER_H_
+
+#include "ash/ash_export.h"
+
+namespace ash {
+
+// Interface for classes that want to be notified by LockStateController when
+// session-related events occur.
+class ASH_EXPORT LockStateObserver {
+ public:
+ enum EventType {
+ EVENT_PRELOCK_ANIMATION_STARTED,
+ EVENT_LOCK_ANIMATION_STARTED,
+ EVENT_LOCK_ANIMATION_FINISHED,
+ };
+
+ virtual void OnLockStateEvent(EventType event) = 0;
+ virtual ~LockStateObserver() {}
+};
+
+} // namespace ash
+
+#endif // ASH_WM_LOCK_STATE_OBSERVER_H_
diff --git a/chromium/ash/wm/maximize_bubble_controller.cc b/chromium/ash/wm/maximize_bubble_controller.cc
new file mode 100644
index 00000000000..ae108ca3baa
--- /dev/null
+++ b/chromium/ash/wm/maximize_bubble_controller.cc
@@ -0,0 +1,864 @@
+// Copyright (c) 2012 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.
+
+#include "ash/wm/maximize_bubble_controller.h"
+
+#include "ash/shell.h"
+#include "ash/shell_delegate.h"
+#include "ash/shell_window_ids.h"
+#include "ash/wm/window_animations.h"
+#include "ash/wm/workspace/frame_maximize_button.h"
+#include "base/timer/timer.h"
+#include "grit/ash_resources.h"
+#include "grit/ash_strings.h"
+#include "third_party/skia/include/core/SkPath.h"
+#include "ui/aura/window.h"
+#include "ui/base/animation/animation.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "ui/gfx/canvas.h"
+#include "ui/gfx/path.h"
+#include "ui/gfx/screen.h"
+#include "ui/views/bubble/bubble_delegate.h"
+#include "ui/views/bubble/bubble_frame_view.h"
+#include "ui/views/controls/button/button.h"
+#include "ui/views/controls/button/image_button.h"
+#include "ui/views/controls/label.h"
+#include "ui/views/layout/box_layout.h"
+#include "ui/views/mouse_watcher.h"
+#include "ui/views/widget/widget.h"
+
+namespace {
+
+// The spacing between two buttons.
+const int kLayoutSpacing = -1;
+
+// The background color.
+const SkColor kBubbleBackgroundColor = 0xFF141414;
+
+// The text color within the bubble.
+const SkColor kBubbleTextColor = SK_ColorWHITE;
+
+// The line width of the bubble.
+const int kLineWidth = 1;
+
+// The spacing for the top and bottom of the info label.
+const int kLabelSpacing = 4;
+
+// The pixel dimensions of the arrow.
+const int kArrowHeight = 10;
+const int kArrowWidth = 20;
+
+// The animation offset in y for the bubble when appearing.
+const int kBubbleAnimationOffsetY = 5;
+
+class MaximizeBubbleBorder : public views::BubbleBorder {
+ public:
+ MaximizeBubbleBorder(views::View* content_view, views::View* anchor);
+
+ virtual ~MaximizeBubbleBorder() {}
+
+ // Get the mouse active area of the window.
+ void GetMask(gfx::Path* mask);
+
+ // Overridden from views::BubbleBorder to match the design specs.
+ virtual gfx::Rect GetBounds(const gfx::Rect& position_relative_to,
+ const gfx::Size& contents_size) const OVERRIDE;
+
+ // Overridden from views::Border.
+ virtual void Paint(const views::View& view, gfx::Canvas* canvas) OVERRIDE;
+
+ private:
+ // Note: Animations can continue after then main window frame was destroyed.
+ // To avoid this problem, the owning screen metrics get extracted upon
+ // creation.
+ gfx::Size anchor_size_;
+ gfx::Point anchor_screen_origin_;
+ views::View* content_view_;
+
+ DISALLOW_COPY_AND_ASSIGN(MaximizeBubbleBorder);
+};
+
+MaximizeBubbleBorder::MaximizeBubbleBorder(views::View* content_view,
+ views::View* anchor)
+ : views::BubbleBorder(views::BubbleBorder::TOP_RIGHT,
+ views::BubbleBorder::NO_SHADOW,
+ kBubbleBackgroundColor),
+ anchor_size_(anchor->size()),
+ anchor_screen_origin_(0, 0),
+ content_view_(content_view) {
+ views::View::ConvertPointToScreen(anchor, &anchor_screen_origin_);
+ set_alignment(views::BubbleBorder::ALIGN_EDGE_TO_ANCHOR_EDGE);
+}
+
+void MaximizeBubbleBorder::GetMask(gfx::Path* mask) {
+ gfx::Insets inset = GetInsets();
+ // Note: Even though the tip could be added as activatable, it is left out
+ // since it would not change the action behavior in any way plus it makes
+ // more sense to keep the focus on the underlying button for clicks.
+ int left = inset.left() - kLineWidth;
+ int right = inset.left() + content_view_->width() + kLineWidth;
+ int top = inset.top() - kLineWidth;
+ int bottom = inset.top() + content_view_->height() + kLineWidth;
+ mask->moveTo(left, top);
+ mask->lineTo(right, top);
+ mask->lineTo(right, bottom);
+ mask->lineTo(left, bottom);
+ mask->lineTo(left, top);
+ mask->close();
+}
+
+gfx::Rect MaximizeBubbleBorder::GetBounds(
+ const gfx::Rect& position_relative_to,
+ const gfx::Size& contents_size) const {
+ gfx::Size border_size(contents_size);
+ gfx::Insets insets = GetInsets();
+ border_size.Enlarge(insets.width(), insets.height());
+
+ // Position the bubble to center the box on the anchor.
+ int x = (-border_size.width() + anchor_size_.width()) / 2;
+ // Position the bubble under the anchor, overlapping the arrow with it.
+ int y = anchor_size_.height() - insets.top();
+
+ gfx::Point view_origin(x + anchor_screen_origin_.x(),
+ y + anchor_screen_origin_.y());
+
+ return gfx::Rect(view_origin, border_size);
+}
+
+void MaximizeBubbleBorder::Paint(const views::View& view, gfx::Canvas* canvas) {
+ gfx::Insets inset = GetInsets();
+
+ // Draw the border line around everything.
+ int y = inset.top();
+ // Top
+ canvas->FillRect(gfx::Rect(inset.left(),
+ y - kLineWidth,
+ content_view_->width(),
+ kLineWidth),
+ kBubbleBackgroundColor);
+ // Bottom
+ canvas->FillRect(gfx::Rect(inset.left(),
+ y + content_view_->height(),
+ content_view_->width(),
+ kLineWidth),
+ kBubbleBackgroundColor);
+ // Left
+ canvas->FillRect(gfx::Rect(inset.left() - kLineWidth,
+ y - kLineWidth,
+ kLineWidth,
+ content_view_->height() + 2 * kLineWidth),
+ kBubbleBackgroundColor);
+ // Right
+ canvas->FillRect(gfx::Rect(inset.left() + content_view_->width(),
+ y - kLineWidth,
+ kLineWidth,
+ content_view_->height() + 2 * kLineWidth),
+ kBubbleBackgroundColor);
+
+ // Draw the arrow afterwards covering the border.
+ SkPath path;
+ path.incReserve(4);
+ // The center of the tip should be in the middle of the button.
+ int tip_x = inset.left() + content_view_->width() / 2;
+ int left_base_x = tip_x - kArrowWidth / 2;
+ int left_base_y = y;
+ int tip_y = left_base_y - kArrowHeight;
+ path.moveTo(SkIntToScalar(left_base_x), SkIntToScalar(left_base_y));
+ path.lineTo(SkIntToScalar(tip_x), SkIntToScalar(tip_y));
+ path.lineTo(SkIntToScalar(left_base_x + kArrowWidth),
+ SkIntToScalar(left_base_y));
+
+ SkPaint paint;
+ paint.setStyle(SkPaint::kFill_Style);
+ paint.setColor(kBubbleBackgroundColor);
+ canvas->DrawPath(path, paint);
+}
+
+} // namespace
+
+namespace ash {
+
+class BubbleContentsButtonRow;
+class BubbleContentsView;
+class BubbleDialogButton;
+
+// The mouse watcher host which makes sure that the bubble does not get closed
+// while the mouse cursor is over the maximize button or the balloon content.
+// Note: This object gets destroyed when the MouseWatcher gets destroyed.
+class BubbleMouseWatcherHost: public views::MouseWatcherHost {
+ public:
+ explicit BubbleMouseWatcherHost(MaximizeBubbleController::Bubble* bubble)
+ : bubble_(bubble) {}
+ virtual ~BubbleMouseWatcherHost() {}
+
+ // Implementation of MouseWatcherHost.
+ virtual bool Contains(const gfx::Point& screen_point,
+ views::MouseWatcherHost::MouseEventType type) OVERRIDE;
+ private:
+ MaximizeBubbleController::Bubble* bubble_;
+
+ DISALLOW_COPY_AND_ASSIGN(BubbleMouseWatcherHost);
+};
+
+// The class which creates and manages the bubble menu element.
+// It creates a 'bubble border' and the content accordingly.
+// Note: Since the SnapSizer will show animations on top of the maximize button
+// this menu gets created as a separate window and the SnapSizer will be
+// created underneath this window.
+class MaximizeBubbleController::Bubble : public views::BubbleDelegateView,
+ public views::MouseWatcherListener {
+ public:
+ explicit Bubble(MaximizeBubbleController* owner, int appearance_delay_ms_);
+ virtual ~Bubble() {}
+
+ // The window of the menu under which the SnapSizer will get created.
+ aura::Window* GetBubbleWindow();
+
+ // Overridden from views::BubbleDelegateView.
+ virtual gfx::Rect GetAnchorRect() OVERRIDE;
+ virtual void AnimationProgressed(const ui::Animation* animation) OVERRIDE;
+ virtual bool CanActivate() const OVERRIDE { return false; }
+
+ // Overridden from views::WidgetDelegateView.
+ virtual bool WidgetHasHitTestMask() const OVERRIDE;
+ virtual void GetWidgetHitTestMask(gfx::Path* mask) const OVERRIDE;
+
+ // Implementation of MouseWatcherListener.
+ virtual void MouseMovedOutOfHost() OVERRIDE;
+
+ // Implementation of MouseWatcherHost.
+ virtual bool Contains(const gfx::Point& screen_point,
+ views::MouseWatcherHost::MouseEventType type);
+
+ // Overridden from views::View.
+ virtual gfx::Size GetPreferredSize() OVERRIDE;
+
+ // Overridden from views::Widget::Observer.
+ virtual void OnWidgetDestroying(views::Widget* widget) OVERRIDE;
+
+ // Called from the controller class to indicate that the menu should get
+ // destroyed.
+ virtual void ControllerRequestsCloseAndDelete();
+
+ // Called from the owning class to change the menu content to the given
+ // |snap_type| so that the user knows what is selected.
+ void SetSnapType(SnapType snap_type);
+
+ // Get the owning MaximizeBubbleController. This might return NULL in case
+ // of an asynchronous shutdown.
+ MaximizeBubbleController* controller() const { return owner_; }
+
+ // Added for unit test: Retrieve the button for an action.
+ // |state| can be either SNAP_LEFT, SNAP_RIGHT or SNAP_MINIMIZE.
+ views::CustomButton* GetButtonForUnitTest(SnapType state);
+
+ private:
+ // True if the shut down has been initiated.
+ bool shutting_down_;
+
+ // Our owning class.
+ MaximizeBubbleController* owner_;
+
+ // The widget which contains our menu and the bubble border.
+ views::Widget* bubble_widget_;
+
+ // The content accessor of the menu.
+ BubbleContentsView* contents_view_;
+
+ // The bubble border.
+ MaximizeBubbleBorder* bubble_border_;
+
+ // The rectangle before the animation starts.
+ gfx::Rect initial_position_;
+
+ // The mouse watcher which takes care of out of window hover events.
+ scoped_ptr<views::MouseWatcher> mouse_watcher_;
+
+ // The fade delay - if 0 it will show / hide immediately.
+ const int appearance_delay_ms_;
+
+ DISALLOW_COPY_AND_ASSIGN(Bubble);
+};
+
+// A class that creates all buttons and put them into a view.
+class BubbleContentsButtonRow : public views::View,
+ public views::ButtonListener {
+ public:
+ explicit BubbleContentsButtonRow(MaximizeBubbleController::Bubble* bubble);
+
+ virtual ~BubbleContentsButtonRow() {}
+
+ // Overridden from ButtonListener.
+ virtual void ButtonPressed(views::Button* sender,
+ const ui::Event& event) OVERRIDE;
+ // Called from BubbleDialogButton.
+ void ButtonHovered(BubbleDialogButton* sender);
+
+ // Added for unit test: Retrieve the button for an action.
+ // |state| can be either SNAP_LEFT, SNAP_RIGHT or SNAP_MINIMIZE.
+ views::CustomButton* GetButtonForUnitTest(SnapType state);
+
+ MaximizeBubbleController::Bubble* bubble() { return bubble_; }
+
+ private:
+ // Functions to add the left and right maximize / restore buttons.
+ void AddMaximizeLeftButton();
+ void AddMaximizeRightButton();
+ void AddMinimizeButton();
+
+ // The owning object which gets notifications.
+ MaximizeBubbleController::Bubble* bubble_;
+
+ // The created buttons for our menu.
+ BubbleDialogButton* left_button_;
+ BubbleDialogButton* minimize_button_;
+ BubbleDialogButton* right_button_;
+
+ DISALLOW_COPY_AND_ASSIGN(BubbleContentsButtonRow);
+};
+
+// A class which creates the content of the bubble: The buttons, and the label.
+class BubbleContentsView : public views::View {
+ public:
+ explicit BubbleContentsView(MaximizeBubbleController::Bubble* bubble);
+
+ virtual ~BubbleContentsView() {}
+
+ // Set the label content to reflect the currently selected |snap_type|.
+ // This function can be executed through the frame maximize button as well as
+ // through hover operations.
+ void SetSnapType(SnapType snap_type);
+
+ // Added for unit test: Retrieve the button for an action.
+ // |state| can be either SNAP_LEFT, SNAP_RIGHT or SNAP_MINIMIZE.
+ views::CustomButton* GetButtonForUnitTest(SnapType state) {
+ return buttons_view_->GetButtonForUnitTest(state);
+ }
+
+ private:
+ // The owning class.
+ MaximizeBubbleController::Bubble* bubble_;
+
+ // The object which owns all the buttons.
+ BubbleContentsButtonRow* buttons_view_;
+
+ // The label object which shows the user the selected action.
+ views::Label* label_view_;
+
+ DISALLOW_COPY_AND_ASSIGN(BubbleContentsView);
+};
+
+// The image button gets overridden to be able to capture mouse hover events.
+// The constructor also assigns all button states and
+class BubbleDialogButton : public views::ImageButton {
+ public:
+ explicit BubbleDialogButton(
+ BubbleContentsButtonRow* button_row_listener,
+ int normal_image,
+ int hovered_image,
+ int pressed_image);
+ virtual ~BubbleDialogButton() {}
+
+ // CustomButton overrides:
+ virtual void OnMouseCaptureLost() OVERRIDE;
+ virtual void OnMouseEntered(const ui::MouseEvent& event) OVERRIDE;
+ virtual void OnMouseExited(const ui::MouseEvent& event) OVERRIDE;
+ virtual bool OnMouseDragged(const ui::MouseEvent& event) OVERRIDE;
+
+ private:
+ // The creating class which needs to get notified in case of a hover event.
+ BubbleContentsButtonRow* button_row_;
+
+ DISALLOW_COPY_AND_ASSIGN(BubbleDialogButton);
+};
+
+MaximizeBubbleController::Bubble::Bubble(
+ MaximizeBubbleController* owner,
+ int appearance_delay_ms)
+ : views::BubbleDelegateView(owner->frame_maximize_button(),
+ views::BubbleBorder::TOP_RIGHT),
+ shutting_down_(false),
+ owner_(owner),
+ bubble_widget_(NULL),
+ contents_view_(NULL),
+ bubble_border_(NULL),
+ appearance_delay_ms_(appearance_delay_ms) {
+ set_margins(gfx::Insets());
+
+ // The window needs to be owned by the root so that the SnapSizer does not
+ // cover it upon animation.
+ aura::Window* parent = Shell::GetContainer(
+ Shell::GetActiveRootWindow(),
+ internal::kShellWindowId_ShelfContainer);
+ set_parent_window(parent);
+
+ set_notify_enter_exit_on_child(true);
+ set_adjust_if_offscreen(false);
+ SetPaintToLayer(true);
+ set_color(kBubbleBackgroundColor);
+ set_close_on_deactivate(false);
+ set_background(
+ views::Background::CreateSolidBackground(kBubbleBackgroundColor));
+
+ SetLayoutManager(new views::BoxLayout(
+ views::BoxLayout::kVertical, 0, 0, kLayoutSpacing));
+
+ contents_view_ = new BubbleContentsView(this);
+ AddChildView(contents_view_);
+
+ // Note that the returned widget has an observer which points to our
+ // functions.
+ bubble_widget_ = views::BubbleDelegateView::CreateBubble(this);
+ bubble_widget_->set_focus_on_creation(false);
+
+ SetAlignment(views::BubbleBorder::ALIGN_EDGE_TO_ANCHOR_EDGE);
+ bubble_widget_->non_client_view()->frame_view()->set_background(NULL);
+
+ bubble_border_ = new MaximizeBubbleBorder(this, anchor_view());
+ GetBubbleFrameView()->SetBubbleBorder(bubble_border_);
+ GetBubbleFrameView()->set_background(NULL);
+
+ // Recalculate size with new border.
+ SizeToContents();
+
+ if (!appearance_delay_ms_)
+ GetWidget()->Show();
+ else
+ StartFade(true);
+
+ ash::Shell::GetInstance()->delegate()->RecordUserMetricsAction(
+ ash::UMA_WINDOW_MAXIMIZE_BUTTON_SHOW_BUBBLE);
+
+ mouse_watcher_.reset(new views::MouseWatcher(
+ new BubbleMouseWatcherHost(this),
+ this));
+ mouse_watcher_->Start();
+}
+
+bool BubbleMouseWatcherHost::Contains(
+ const gfx::Point& screen_point,
+ views::MouseWatcherHost::MouseEventType type) {
+ return bubble_->Contains(screen_point, type);
+}
+
+aura::Window* MaximizeBubbleController::Bubble::GetBubbleWindow() {
+ return bubble_widget_ ? bubble_widget_->GetNativeWindow() : NULL;
+}
+
+gfx::Rect MaximizeBubbleController::Bubble::GetAnchorRect() {
+ if (!owner_)
+ return gfx::Rect();
+
+ gfx::Rect anchor_rect =
+ owner_->frame_maximize_button()->GetBoundsInScreen();
+ return anchor_rect;
+}
+
+void MaximizeBubbleController::Bubble::AnimationProgressed(
+ const ui::Animation* animation) {
+ // First do everything needed for the fade by calling the base function.
+ BubbleDelegateView::AnimationProgressed(animation);
+ // When fading in we are done.
+ if (!shutting_down_)
+ return;
+ // Upon fade out an additional shift is required.
+ int shift = animation->CurrentValueBetween(kBubbleAnimationOffsetY, 0);
+ gfx::Rect rect = initial_position_;
+
+ rect.set_y(rect.y() + shift);
+ bubble_widget_->GetNativeWindow()->SetBounds(rect);
+}
+
+bool MaximizeBubbleController::Bubble::WidgetHasHitTestMask() const {
+ return bubble_border_ != NULL;
+}
+
+void MaximizeBubbleController::Bubble::GetWidgetHitTestMask(
+ gfx::Path* mask) const {
+ DCHECK(mask);
+ DCHECK(bubble_border_);
+ bubble_border_->GetMask(mask);
+}
+
+void MaximizeBubbleController::Bubble::MouseMovedOutOfHost() {
+ if (!owner_ || shutting_down_)
+ return;
+ // When we leave the bubble, we might be still be in gesture mode or over
+ // the maximize button. So only close if none of the other cases apply.
+ if (!owner_->frame_maximize_button()->is_snap_enabled()) {
+ gfx::Point screen_location = Shell::GetScreen()->GetCursorScreenPoint();
+ if (!owner_->frame_maximize_button()->GetBoundsInScreen().Contains(
+ screen_location)) {
+ owner_->RequestDestructionThroughOwner();
+ }
+ }
+}
+
+bool MaximizeBubbleController::Bubble::Contains(
+ const gfx::Point& screen_point,
+ views::MouseWatcherHost::MouseEventType type) {
+ if (!owner_ || shutting_down_)
+ return false;
+ bool inside_button =
+ owner_->frame_maximize_button()->GetBoundsInScreen().Contains(
+ screen_point);
+ if (!owner_->frame_maximize_button()->is_snap_enabled() && inside_button) {
+ SetSnapType(controller()->maximize_type() == FRAME_STATE_FULL ?
+ SNAP_RESTORE : SNAP_MAXIMIZE);
+ return true;
+ }
+ // Check if either a gesture is taking place (=> bubble stays no matter what
+ // the mouse does) or the mouse is over the maximize button or the bubble
+ // content.
+ return (owner_->frame_maximize_button()->is_snap_enabled() ||
+ inside_button ||
+ contents_view_->GetBoundsInScreen().Contains(screen_point));
+}
+
+gfx::Size MaximizeBubbleController::Bubble::GetPreferredSize() {
+ return contents_view_->GetPreferredSize();
+}
+
+void MaximizeBubbleController::Bubble::OnWidgetDestroying(
+ views::Widget* widget) {
+ if (bubble_widget_ == widget) {
+ mouse_watcher_->Stop();
+
+ if (owner_) {
+ // If the bubble destruction was triggered by some other external
+ // influence then ourselves, the owner needs to be informed that the menu
+ // is gone.
+ shutting_down_ = true;
+ owner_->RequestDestructionThroughOwner();
+ owner_ = NULL;
+ }
+ }
+ BubbleDelegateView::OnWidgetDestroying(widget);
+}
+
+void MaximizeBubbleController::Bubble::ControllerRequestsCloseAndDelete() {
+ // This only gets called from the owning base class once it is deleted.
+ if (shutting_down_)
+ return;
+ shutting_down_ = true;
+ owner_ = NULL;
+
+ // Close the widget asynchronously after the hide animation is finished.
+ initial_position_ = bubble_widget_->GetNativeWindow()->bounds();
+ if (!appearance_delay_ms_)
+ bubble_widget_->CloseNow();
+ else
+ StartFade(false);
+}
+
+void MaximizeBubbleController::Bubble::SetSnapType(SnapType snap_type) {
+ if (contents_view_)
+ contents_view_->SetSnapType(snap_type);
+}
+
+views::CustomButton* MaximizeBubbleController::Bubble::GetButtonForUnitTest(
+ SnapType state) {
+ return contents_view_->GetButtonForUnitTest(state);
+}
+
+BubbleContentsButtonRow::BubbleContentsButtonRow(
+ MaximizeBubbleController::Bubble* bubble)
+ : bubble_(bubble),
+ left_button_(NULL),
+ minimize_button_(NULL),
+ right_button_(NULL) {
+ SetLayoutManager(new views::BoxLayout(
+ views::BoxLayout::kHorizontal, 0, 0, kLayoutSpacing));
+ set_background(
+ views::Background::CreateSolidBackground(kBubbleBackgroundColor));
+
+ if (base::i18n::IsRTL()) {
+ AddMaximizeRightButton();
+ AddMinimizeButton();
+ AddMaximizeLeftButton();
+ } else {
+ AddMaximizeLeftButton();
+ AddMinimizeButton();
+ AddMaximizeRightButton();
+ }
+}
+
+// Overridden from ButtonListener.
+void BubbleContentsButtonRow::ButtonPressed(views::Button* sender,
+ const ui::Event& event) {
+ // While shutting down, the connection to the owner might already be broken.
+ if (!bubble_->controller())
+ return;
+ if (sender == left_button_)
+ bubble_->controller()->OnButtonClicked(
+ bubble_->controller()->maximize_type() == FRAME_STATE_SNAP_LEFT ?
+ SNAP_RESTORE : SNAP_LEFT);
+ else if (sender == minimize_button_)
+ bubble_->controller()->OnButtonClicked(SNAP_MINIMIZE);
+ else if (sender == right_button_)
+ bubble_->controller()->OnButtonClicked(
+ bubble_->controller()->maximize_type() == FRAME_STATE_SNAP_RIGHT ?
+ SNAP_RESTORE : SNAP_RIGHT);
+ else
+ NOTREACHED() << "Unknown button pressed.";
+}
+
+// Called from BubbleDialogButton.
+void BubbleContentsButtonRow::ButtonHovered(BubbleDialogButton* sender) {
+ // While shutting down, the connection to the owner might already be broken.
+ if (!bubble_->controller())
+ return;
+ if (sender == left_button_)
+ bubble_->controller()->OnButtonHover(
+ bubble_->controller()->maximize_type() == FRAME_STATE_SNAP_LEFT ?
+ SNAP_RESTORE : SNAP_LEFT);
+ else if (sender == minimize_button_)
+ bubble_->controller()->OnButtonHover(SNAP_MINIMIZE);
+ else if (sender == right_button_)
+ bubble_->controller()->OnButtonHover(
+ bubble_->controller()->maximize_type() == FRAME_STATE_SNAP_RIGHT ?
+ SNAP_RESTORE : SNAP_RIGHT);
+ else
+ bubble_->controller()->OnButtonHover(SNAP_NONE);
+}
+
+views::CustomButton* BubbleContentsButtonRow::GetButtonForUnitTest(
+ SnapType state) {
+ switch (state) {
+ case SNAP_LEFT:
+ return left_button_;
+ case SNAP_MINIMIZE:
+ return minimize_button_;
+ case SNAP_RIGHT:
+ return right_button_;
+ default:
+ NOTREACHED();
+ return NULL;
+ }
+}
+
+void BubbleContentsButtonRow::AddMaximizeLeftButton() {
+ if (bubble_->controller()->maximize_type() == FRAME_STATE_SNAP_LEFT) {
+ left_button_ = new BubbleDialogButton(
+ this,
+ IDR_AURA_WINDOW_POSITION_LEFT_RESTORE,
+ IDR_AURA_WINDOW_POSITION_LEFT_RESTORE_H,
+ IDR_AURA_WINDOW_POSITION_LEFT_RESTORE_P);
+ } else {
+ left_button_ = new BubbleDialogButton(
+ this,
+ IDR_AURA_WINDOW_POSITION_LEFT,
+ IDR_AURA_WINDOW_POSITION_LEFT_H,
+ IDR_AURA_WINDOW_POSITION_LEFT_P);
+ }
+}
+
+void BubbleContentsButtonRow::AddMaximizeRightButton() {
+ if (bubble_->controller()->maximize_type() == FRAME_STATE_SNAP_RIGHT) {
+ right_button_ = new BubbleDialogButton(
+ this,
+ IDR_AURA_WINDOW_POSITION_RIGHT_RESTORE,
+ IDR_AURA_WINDOW_POSITION_RIGHT_RESTORE_H,
+ IDR_AURA_WINDOW_POSITION_RIGHT_RESTORE_P);
+ } else {
+ right_button_ = new BubbleDialogButton(
+ this,
+ IDR_AURA_WINDOW_POSITION_RIGHT,
+ IDR_AURA_WINDOW_POSITION_RIGHT_H,
+ IDR_AURA_WINDOW_POSITION_RIGHT_P);
+ }
+}
+
+void BubbleContentsButtonRow::AddMinimizeButton() {
+ minimize_button_ = new BubbleDialogButton(
+ this,
+ IDR_AURA_WINDOW_POSITION_MIDDLE,
+ IDR_AURA_WINDOW_POSITION_MIDDLE_H,
+ IDR_AURA_WINDOW_POSITION_MIDDLE_P);
+}
+
+BubbleContentsView::BubbleContentsView(
+ MaximizeBubbleController::Bubble* bubble)
+ : bubble_(bubble),
+ buttons_view_(NULL),
+ label_view_(NULL) {
+ SetLayoutManager(new views::BoxLayout(
+ views::BoxLayout::kVertical, 0, 0, kLayoutSpacing));
+ set_background(
+ views::Background::CreateSolidBackground(kBubbleBackgroundColor));
+
+ buttons_view_ = new BubbleContentsButtonRow(bubble);
+ AddChildView(buttons_view_);
+
+ label_view_ = new views::Label();
+ SetSnapType(SNAP_NONE);
+ label_view_->SetBackgroundColor(kBubbleBackgroundColor);
+ label_view_->SetEnabledColor(kBubbleTextColor);
+ label_view_->set_border(views::Border::CreateEmptyBorder(
+ kLabelSpacing, 0, kLabelSpacing, 0));
+ AddChildView(label_view_);
+}
+
+// Set the label content to reflect the currently selected |snap_type|.
+// This function can be executed through the frame maximize button as well as
+// through hover operations.
+void BubbleContentsView::SetSnapType(SnapType snap_type) {
+ if (!bubble_->controller())
+ return;
+
+ ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
+ int id = 0;
+ switch (snap_type) {
+ case SNAP_LEFT:
+ id = IDS_ASH_SNAP_WINDOW_LEFT;
+ break;
+ case SNAP_RIGHT:
+ id = IDS_ASH_SNAP_WINDOW_RIGHT;
+ break;
+ case SNAP_MAXIMIZE:
+ DCHECK_NE(FRAME_STATE_FULL, bubble_->controller()->maximize_type());
+ id = IDS_ASH_MAXIMIZE_WINDOW;
+ break;
+ case SNAP_MINIMIZE:
+ id = IDS_ASH_MINIMIZE_WINDOW;
+ break;
+ case SNAP_RESTORE:
+ DCHECK_NE(FRAME_STATE_NONE, bubble_->controller()->maximize_type());
+ id = IDS_ASH_RESTORE_WINDOW;
+ break;
+ default:
+ // If nothing is selected, we automatically select the click operation.
+ id = bubble_->controller()->maximize_type() == FRAME_STATE_FULL ?
+ IDS_ASH_RESTORE_WINDOW : IDS_ASH_MAXIMIZE_WINDOW;
+ break;
+ }
+ label_view_->SetText(rb.GetLocalizedString(id));
+}
+
+MaximizeBubbleController::MaximizeBubbleController(
+ FrameMaximizeButton* frame_maximize_button,
+ MaximizeBubbleFrameState maximize_type,
+ int appearance_delay_ms)
+ : frame_maximize_button_(frame_maximize_button),
+ bubble_(NULL),
+ maximize_type_(maximize_type),
+ appearance_delay_ms_(appearance_delay_ms) {
+ // Create the task which will create the bubble delayed.
+ base::OneShotTimer<MaximizeBubbleController>* new_timer =
+ new base::OneShotTimer<MaximizeBubbleController>();
+ // Note: Even if there was no delay time given, we need to have a timer.
+ new_timer->Start(
+ FROM_HERE,
+ base::TimeDelta::FromMilliseconds(
+ appearance_delay_ms_ ? appearance_delay_ms_ : 10),
+ this,
+ &MaximizeBubbleController::CreateBubble);
+ timer_.reset(new_timer);
+ if (!appearance_delay_ms_)
+ CreateBubble();
+}
+
+MaximizeBubbleController::~MaximizeBubbleController() {
+ // Note: The destructor only gets initiated through the owner.
+ timer_.reset();
+ if (bubble_) {
+ bubble_->ControllerRequestsCloseAndDelete();
+ bubble_ = NULL;
+ }
+}
+
+void MaximizeBubbleController::SetSnapType(SnapType snap_type) {
+ if (bubble_)
+ bubble_->SetSnapType(snap_type);
+}
+
+aura::Window* MaximizeBubbleController::GetBubbleWindow() {
+ return bubble_ ? bubble_->GetBubbleWindow() : NULL;
+}
+
+void MaximizeBubbleController::DelayCreation() {
+ if (timer_.get() && timer_->IsRunning())
+ timer_->Reset();
+}
+
+void MaximizeBubbleController::OnButtonClicked(SnapType snap_type) {
+ frame_maximize_button_->ExecuteSnapAndCloseMenu(snap_type);
+}
+
+void MaximizeBubbleController::OnButtonHover(SnapType snap_type) {
+ frame_maximize_button_->SnapButtonHovered(snap_type);
+}
+
+views::CustomButton* MaximizeBubbleController::GetButtonForUnitTest(
+ SnapType state) {
+ return bubble_ ? bubble_->GetButtonForUnitTest(state) : NULL;
+}
+
+void MaximizeBubbleController::RequestDestructionThroughOwner() {
+ // Tell the parent to destroy us (if this didn't happen yet).
+ if (timer_) {
+ timer_.reset(NULL);
+ // Informs the owner that the menu is gone and requests |this| destruction.
+ frame_maximize_button_->DestroyMaximizeMenu();
+ // Note: After this call |this| is destroyed.
+ }
+}
+
+void MaximizeBubbleController::CreateBubble() {
+ if (!bubble_)
+ bubble_ = new Bubble(this, appearance_delay_ms_);
+
+ timer_->Stop();
+}
+
+BubbleDialogButton::BubbleDialogButton(
+ BubbleContentsButtonRow* button_row,
+ int normal_image,
+ int hovered_image,
+ int pressed_image)
+ : views::ImageButton(button_row),
+ button_row_(button_row) {
+ ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
+ SetImage(views::CustomButton::STATE_NORMAL,
+ rb.GetImageSkiaNamed(normal_image));
+ SetImage(views::CustomButton::STATE_HOVERED,
+ rb.GetImageSkiaNamed(hovered_image));
+ SetImage(views::CustomButton::STATE_PRESSED,
+ rb.GetImageSkiaNamed(pressed_image));
+ button_row->AddChildView(this);
+}
+
+void BubbleDialogButton::OnMouseCaptureLost() {
+ button_row_->ButtonHovered(NULL);
+ views::ImageButton::OnMouseCaptureLost();
+}
+
+void BubbleDialogButton::OnMouseEntered(const ui::MouseEvent& event) {
+ button_row_->ButtonHovered(this);
+ views::ImageButton::OnMouseEntered(event);
+}
+
+void BubbleDialogButton::OnMouseExited(const ui::MouseEvent& event) {
+ button_row_->ButtonHovered(NULL);
+ views::ImageButton::OnMouseExited(event);
+}
+
+bool BubbleDialogButton::OnMouseDragged(const ui::MouseEvent& event) {
+ if (!button_row_->bubble()->controller())
+ return false;
+
+ // Remove the phantom window when we leave the button.
+ gfx::Point screen_location(event.location());
+ View::ConvertPointToScreen(this, &screen_location);
+ if (!GetBoundsInScreen().Contains(screen_location))
+ button_row_->ButtonHovered(NULL);
+ else
+ button_row_->ButtonHovered(this);
+
+ // Pass the event on to the normal handler.
+ return views::ImageButton::OnMouseDragged(event);
+}
+
+} // namespace ash
diff --git a/chromium/ash/wm/maximize_bubble_controller.h b/chromium/ash/wm/maximize_bubble_controller.h
new file mode 100644
index 00000000000..7328ea29c0d
--- /dev/null
+++ b/chromium/ash/wm/maximize_bubble_controller.h
@@ -0,0 +1,101 @@
+// Copyright (c) 2012 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 ASH_WM_MAXIMIZE_BUBBLE_CONTROLLER_H_
+#define ASH_WM_MAXIMIZE_BUBBLE_CONTROLLER_H_
+
+#include "ash/ash_export.h"
+#include "ash/wm/workspace/maximize_bubble_frame_state.h"
+#include "ash/wm/workspace/snap_types.h"
+#include "base/memory/scoped_ptr.h"
+
+namespace aura {
+class Window;
+}
+
+namespace base {
+class Timer;
+}
+
+namespace views {
+class CustomButton;
+}
+
+namespace ash {
+
+class FrameMaximizeButton;
+
+// A class which shows a helper UI for the maximize button after a delay.
+class ASH_EXPORT MaximizeBubbleController {
+ public:
+ class Bubble;
+
+ MaximizeBubbleController(FrameMaximizeButton* frame_maximize_button,
+ MaximizeBubbleFrameState maximize_type,
+ int appearance_delay_ms);
+ // Called from the outside to destroy the interface to the UI visuals.
+ // The visuals will then delete when possible (maybe asynchronously).
+ virtual ~MaximizeBubbleController();
+
+ // Update the UI visuals to reflect the previewed |snap_type| snapping state.
+ void SetSnapType(SnapType snap_type);
+
+ // To achieve proper Z-sorting with the snap animation, this window will be
+ // presented above the phantom window.
+ aura::Window* GetBubbleWindow();
+
+ // Reset the delay of the menu creation (if it was not created yet).
+ void DelayCreation();
+
+ // Called to tell the owning FrameMaximizeButton that a button was clicked.
+ void OnButtonClicked(SnapType snap_type);
+
+ // Called to tell the the owning FrameMaximizeButton that the hover status
+ // for a button has changed. |snap_type| can be either SNAP_LEFT, SNAP_RIGHT,
+ // SNAP_MINIMIZE or SNAP_NONE.
+ void OnButtonHover(SnapType snap_type);
+
+ // Get the owning FrameMaximizeButton.
+ FrameMaximizeButton* frame_maximize_button() {
+ return frame_maximize_button_;
+ }
+
+ // The status of the associated window: Maximized or normal.
+ MaximizeBubbleFrameState maximize_type() const { return maximize_type_; }
+
+ // A unit test function to return buttons of the sub menu. |state| can be
+ // either SNAP_LEFT, SNAP_RIGHT or SNAP_MINIMIZE.
+ views::CustomButton* GetButtonForUnitTest(SnapType state);
+
+ protected:
+ // Called from the the Bubble class to destroy itself: It tells the owning
+ // object that it will destroy itself asynchronously. The owner will then
+ // destroy |this|.
+ void RequestDestructionThroughOwner();
+
+ private:
+ // The function which creates the bubble once the delay is elapsed.
+ void CreateBubble();
+
+ // The owning button which is also the anchor for the menu.
+ FrameMaximizeButton* frame_maximize_button_;
+
+ // The bubble menu.
+ Bubble* bubble_;
+
+ // The current maximize state of the owning window.
+ const MaximizeBubbleFrameState maximize_type_;
+
+ // The timer for the delayed creation of the menu.
+ scoped_ptr<base::Timer> timer_;
+
+ // The appearance delay in ms (delay and fade in & fade out delay).
+ const int appearance_delay_ms_;
+
+ DISALLOW_COPY_AND_ASSIGN(MaximizeBubbleController);
+};
+
+} // namespace ash
+
+#endif // ASH_WM_MAXIMIZE_BUBBLE_CONTROLLER_H_
diff --git a/chromium/ash/wm/mru_window_tracker.cc b/chromium/ash/wm/mru_window_tracker.cc
new file mode 100644
index 00000000000..b02991cbd94
--- /dev/null
+++ b/chromium/ash/wm/mru_window_tracker.cc
@@ -0,0 +1,182 @@
+// Copyright 2013 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.
+
+#include "ash/wm/mru_window_tracker.h"
+
+#include <algorithm>
+
+#include "ash/session_state_delegate.h"
+#include "ash/shell.h"
+#include "ash/shell_window_ids.h"
+#include "ash/wm/activation_controller.h"
+#include "ash/wm/window_cycle_list.h"
+#include "ash/wm/window_util.h"
+#include "ash/wm/workspace_controller.h"
+#include "ui/aura/root_window.h"
+#include "ui/base/events/event.h"
+#include "ui/base/events/event_handler.h"
+
+namespace ash {
+
+namespace {
+
+// List of containers whose children we will allow switching to.
+const int kContainerIds[] = {
+ internal::kShellWindowId_DefaultContainer,
+ internal::kShellWindowId_AlwaysOnTopContainer
+};
+
+// Adds the windows that can be cycled through for the specified window id to
+// |windows|.
+void AddTrackedWindows(aura::RootWindow* root,
+ int container_id,
+ MruWindowTracker::WindowList* windows) {
+ aura::Window* container = Shell::GetContainer(root, container_id);
+ const MruWindowTracker::WindowList& children(container->children());
+ windows->insert(windows->end(), children.begin(), children.end());
+}
+
+// Returns a list of windows ordered by their stacking order.
+// If |mru_windows| is passed, these windows are moved to the front of the list.
+// If |top_most_at_end|, the list is returned in descending (bottom-most / least
+// recently used) order.
+MruWindowTracker::WindowList BuildWindowListInternal(
+ const std::list<aura::Window*>* mru_windows,
+ bool top_most_at_end) {
+ MruWindowTracker::WindowList windows;
+ Shell::RootWindowList root_windows = Shell::GetAllRootWindows();
+
+ aura::RootWindow* active_root = Shell::GetActiveRootWindow();
+ for (Shell::RootWindowList::const_iterator iter = root_windows.begin();
+ iter != root_windows.end(); ++iter) {
+ if (*iter == active_root)
+ continue;
+ for (size_t i = 0; i < arraysize(kContainerIds); ++i)
+ AddTrackedWindows(*iter, kContainerIds[i], &windows);
+ }
+
+ // Add windows in the active root windows last so that the topmost window
+ // in the active root window becomes the front of the list.
+ for (size_t i = 0; i < arraysize(kContainerIds); ++i)
+ AddTrackedWindows(active_root, kContainerIds[i], &windows);
+
+ // Removes unfocusable windows.
+ MruWindowTracker::WindowList::iterator last =
+ std::remove_if(
+ windows.begin(),
+ windows.end(),
+ std::not1(std::ptr_fun(ash::wm::CanActivateWindow)));
+ windows.erase(last, windows.end());
+
+ // Put the windows in the mru_windows list at the head, if it's available.
+ if (mru_windows) {
+ // Iterate through the list backwards, so that we can move each window to
+ // the front of the windows list as we find them.
+ for (std::list<aura::Window*>::const_reverse_iterator ix =
+ mru_windows->rbegin();
+ ix != mru_windows->rend(); ++ix) {
+ MruWindowTracker::WindowList::iterator window =
+ std::find(windows.begin(), windows.end(), *ix);
+ if (window != windows.end()) {
+ windows.erase(window);
+ windows.push_back(*ix);
+ }
+ }
+ }
+
+ // Window cycling expects the topmost window at the front of the list.
+ if (!top_most_at_end)
+ std::reverse(windows.begin(), windows.end());
+
+ return windows;
+}
+
+} // namespace
+
+//////////////////////////////////////////////////////////////////////////////
+// MruWindowTracker, public:
+
+MruWindowTracker::MruWindowTracker(
+ aura::client::ActivationClient* activation_client)
+ : activation_client_(activation_client),
+ ignore_window_activations_(false) {
+ activation_client_->AddObserver(this);
+}
+
+MruWindowTracker::~MruWindowTracker() {
+ Shell::RootWindowList root_windows = Shell::GetAllRootWindows();
+ for (Shell::RootWindowList::const_iterator iter = root_windows.begin();
+ iter != root_windows.end(); ++iter) {
+ for (size_t i = 0; i < arraysize(kContainerIds); ++i) {
+ aura::Window* container = Shell::GetContainer(*iter, kContainerIds[i]);
+ if (container)
+ container->RemoveObserver(this);
+ }
+ }
+
+ activation_client_->RemoveObserver(this);
+}
+
+// static
+MruWindowTracker::WindowList MruWindowTracker::BuildWindowList(
+ bool top_most_at_end) {
+ return BuildWindowListInternal(NULL, top_most_at_end);
+}
+
+MruWindowTracker::WindowList MruWindowTracker::BuildMruWindowList() {
+ return BuildWindowListInternal(&mru_windows_, false);
+}
+
+void MruWindowTracker::OnRootWindowAdded(aura::RootWindow* root_window) {
+ for (size_t i = 0; i < arraysize(kContainerIds); ++i) {
+ aura::Window* container =
+ Shell::GetContainer(root_window, kContainerIds[i]);
+ container->AddObserver(this);
+ }
+}
+
+void MruWindowTracker::SetIgnoreActivations(bool ignore) {
+ ignore_window_activations_ = ignore;
+
+ // If no longer ignoring window activations, move currently active window
+ // to front.
+ if (!ignore) {
+ aura::Window* active_window = wm::GetActiveWindow();
+ mru_windows_.remove(active_window);
+ mru_windows_.push_front(active_window);
+ }
+}
+
+//////////////////////////////////////////////////////////////////////////////
+// MruWindowTracker, private:
+
+// static
+bool MruWindowTracker::IsTrackedContainer(aura::Window* window) {
+ if (!window)
+ return false;
+ for (size_t i = 0; i < arraysize(kContainerIds); ++i) {
+ if (window->id() == kContainerIds[i])
+ return true;
+ }
+ return false;
+}
+
+void MruWindowTracker::OnWindowActivated(aura::Window* gained_active,
+ aura::Window* lost_active) {
+ if (gained_active && !ignore_window_activations_ &&
+ IsTrackedContainer(gained_active->parent())) {
+ mru_windows_.remove(gained_active);
+ mru_windows_.push_front(gained_active);
+ }
+}
+
+void MruWindowTracker::OnWillRemoveWindow(aura::Window* window) {
+ mru_windows_.remove(window);
+}
+
+void MruWindowTracker::OnWindowDestroying(aura::Window* window) {
+ window->RemoveObserver(this);
+}
+
+} // namespace ash
diff --git a/chromium/ash/wm/mru_window_tracker.h b/chromium/ash/wm/mru_window_tracker.h
new file mode 100644
index 00000000000..9ff3bd11a90
--- /dev/null
+++ b/chromium/ash/wm/mru_window_tracker.h
@@ -0,0 +1,87 @@
+// Copyright 2013 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 ASH_WM_MRU_WINDOW_TRACKER_H_
+#define ASH_WM_MRU_WINDOW_TRACKER_H_
+
+#include <list>
+#include <vector>
+
+#include "ash/ash_export.h"
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "ui/aura/client/activation_change_observer.h"
+#include "ui/aura/window_observer.h"
+
+namespace aura {
+class RootWindow;
+class Window;
+namespace client {
+class ActivationClient;
+}
+}
+
+namespace ash {
+
+// Maintains a most recently used list of windows. This is used for window
+// cycling using Alt+Tab and overview mode.
+class ASH_EXPORT MruWindowTracker
+ : public aura::client::ActivationChangeObserver,
+ public aura::WindowObserver {
+ public:
+ typedef std::vector<aura::Window*> WindowList;
+
+ explicit MruWindowTracker(
+ aura::client::ActivationClient* activation_client);
+ virtual ~MruWindowTracker();
+
+ // Set up the observers to handle window changes for the containers we care
+ // about. Called when a new root window is added.
+ void OnRootWindowAdded(aura::RootWindow* root_window);
+
+ // Returns the set of windows which can be cycled through. This method creates
+ // the vector based on the current set of windows across all valid root
+ // windows. As a result it is not necessarily the same as the set of
+ // windows being iterated over.
+ // If |top_most_at_end| the window list will return in ascending (lowest
+ // window in stacking order first) order instead of the default descending
+ // (top most window first) order.
+ static WindowList BuildWindowList(bool top_most_at_end);
+
+ // Returns the set of windows which can be cycled through using the tracked
+ // list of most recently used windows.
+ WindowList BuildMruWindowList();
+
+ // Starts or stops ignoring window activations. If no longer ignoring
+ // activations the currently active window is moved to the front of the
+ // MRU window list. Used by WindowCycleList to avoid adding all cycled
+ // windows to the front of the MRU window list.
+ void SetIgnoreActivations(bool ignore);
+
+ private:
+ // Checks if the window represents a container whose children we track.
+ static bool IsTrackedContainer(aura::Window* window);
+
+ // Overridden from aura::client::ActivationChangeObserver:
+ virtual void OnWindowActivated(aura::Window* gained_active,
+ aura::Window* lost_active) OVERRIDE;
+
+ // Overridden from WindowObserver:
+ virtual void OnWillRemoveWindow(aura::Window* window) OVERRIDE;
+ virtual void OnWindowDestroying(aura::Window* window) OVERRIDE;
+
+ // List of windows that have been activated in containers that we cycle
+ // through, sorted by most recently used.
+ std::list<aura::Window*> mru_windows_;
+
+ aura::client::ActivationClient* activation_client_;
+
+ bool ignore_window_activations_;
+
+ DISALLOW_COPY_AND_ASSIGN(MruWindowTracker);
+};
+
+} // namespace ash
+
+#endif // ASH_WM_MRU_WINDOW_TRACKER_H_
diff --git a/chromium/ash/wm/overlay_event_filter.cc b/chromium/ash/wm/overlay_event_filter.cc
new file mode 100644
index 00000000000..2319d2fca27
--- /dev/null
+++ b/chromium/ash/wm/overlay_event_filter.cc
@@ -0,0 +1,78 @@
+// Copyright (c) 2012 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.
+
+#include "ash/wm/overlay_event_filter.h"
+
+#include "ash/wm/partial_screenshot_view.h"
+#include "ui/aura/window.h"
+#include "ui/aura/window_delegate.h"
+#include "ui/base/events/event.h"
+#include "ui/views/widget/widget.h"
+
+namespace ash {
+namespace internal {
+
+OverlayEventFilter::OverlayEventFilter()
+ : delegate_(NULL) {
+}
+
+OverlayEventFilter::~OverlayEventFilter() {
+ delegate_ = NULL;
+}
+
+void OverlayEventFilter::OnKeyEvent(ui::KeyEvent* event) {
+ if (!delegate_)
+ return;
+
+ // Do not consume a translated key event which is generated by an IME (e.g.,
+ // ui::VKEY_PROCESSKEY) since the key event is generated in response to a key
+ // press or release before showing the ovelay. This is important not to
+ // confuse key event handling JavaScript code in a page.
+ if (event->type() == ui::ET_TRANSLATED_KEY_PRESS ||
+ event->type() == ui::ET_TRANSLATED_KEY_RELEASE) {
+ return;
+ }
+
+ if (delegate_ && delegate_->IsCancelingKeyEvent(event))
+ Cancel();
+
+ // Handle key events only when they are sent to a child of the delegate's
+ // window.
+ aura::Window* target = static_cast<aura::Window*>(event->target());
+ if (delegate_ && delegate_->GetWindow() &&
+ delegate_->GetWindow()->Contains(target))
+ target->delegate()->OnKeyEvent(event);
+
+ // Always handled: other windows shouldn't receive input while we're
+ // displaying an overlay.
+ event->StopPropagation();
+}
+
+void OverlayEventFilter::OnLoginStateChanged(
+ user::LoginStatus status) {
+ Cancel();
+}
+
+void OverlayEventFilter::OnAppTerminating() {
+ Cancel();
+}
+
+void OverlayEventFilter::OnLockStateChanged(bool locked) {
+ Cancel();
+}
+
+void OverlayEventFilter::Activate(Delegate* delegate) {
+ delegate_ = delegate;
+}
+
+void OverlayEventFilter::Deactivate() {
+ delegate_ = NULL;
+}
+
+void OverlayEventFilter::Cancel() {
+ if (delegate_)
+ delegate_->Cancel();
+}
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/wm/overlay_event_filter.h b/chromium/ash/wm/overlay_event_filter.h
new file mode 100644
index 00000000000..1989e454f15
--- /dev/null
+++ b/chromium/ash/wm/overlay_event_filter.h
@@ -0,0 +1,71 @@
+// Copyright (c) 2012 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 ASH_WM_OVERLAY_EVENT_FILTER_H_
+#define ASH_WM_OVERLAY_EVENT_FILTER_H_
+
+#include "ash/shell_observer.h"
+#include "base/compiler_specific.h"
+#include "ui/aura/window.h"
+#include "ui/base/events/event_handler.h"
+
+namespace ash {
+namespace internal {
+
+// EventFilter for the "overlay window", which intercepts events before they are
+// processed by the usual path (e.g. the partial screenshot UI, the keyboard
+// overlay). It does nothing the first time, but works when |Activate()| is
+// called. The main task of this event filter is just to stop propagation
+// of any key events during activation, and also signal cancellation when keys
+// for canceling are pressed.
+class OverlayEventFilter : public ui::EventHandler,
+ public ShellObserver {
+ public:
+ // Windows that need to receive events from OverlayEventFilter implement this.
+ class Delegate {
+ public:
+ // Invoked when OverlayEventFilter needs to stop handling events.
+ virtual void Cancel() = 0;
+
+ // Returns true if the overlay should be canceled in response to |event|.
+ virtual bool IsCancelingKeyEvent(ui::KeyEvent* event) = 0;
+
+ // Returns the window that needs to receive events. NULL if no window needs
+ // to receive key events from OverlayEventFilter.
+ virtual aura::Window* GetWindow() = 0;
+ };
+
+ OverlayEventFilter();
+ virtual ~OverlayEventFilter();
+
+ // Starts the filtering of events. It also notifies the specified
+ // |delegate| when a key event means cancel (like Esc). It holds the
+ // pointer to the specified |delegate| until Deactivate() is called, but
+ // does not take ownership.
+ void Activate(Delegate* delegate);
+
+ // Ends the filtering of events.
+ void Deactivate();
+
+ // Cancels the partial screenshot UI. Do nothing if it's not activated.
+ void Cancel();
+
+ // ui::EventHandler overrides:
+ virtual void OnKeyEvent(ui::KeyEvent* event) OVERRIDE;
+
+ // ShellObserver overrides:
+ virtual void OnLoginStateChanged(user::LoginStatus status) OVERRIDE;
+ virtual void OnAppTerminating() OVERRIDE;
+ virtual void OnLockStateChanged(bool locked) OVERRIDE;
+
+ private:
+ Delegate* delegate_;
+
+ DISALLOW_COPY_AND_ASSIGN(OverlayEventFilter);
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_WM_OVERLAY_EVENT_FILTER_H_
diff --git a/chromium/ash/wm/panels/OWNERS b/chromium/ash/wm/panels/OWNERS
new file mode 100644
index 00000000000..c9dc2b8e06b
--- /dev/null
+++ b/chromium/ash/wm/panels/OWNERS
@@ -0,0 +1,2 @@
+flackr@chromium.org
+stevenjb@chromium.org
diff --git a/chromium/ash/wm/panels/panel_frame_view.cc b/chromium/ash/wm/panels/panel_frame_view.cc
new file mode 100644
index 00000000000..8c18215ef3f
--- /dev/null
+++ b/chromium/ash/wm/panels/panel_frame_view.cc
@@ -0,0 +1,157 @@
+// Copyright (c) 2012 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.
+
+#include "ash/wm/panels/panel_frame_view.h"
+
+#include "ash/wm/frame_painter.h"
+#include "grit/ash_resources.h"
+#include "grit/ui_strings.h" // Accessibility names
+#include "third_party/skia/include/core/SkPaint.h"
+#include "ui/base/animation/throb_animation.h"
+#include "ui/base/cursor/cursor.h"
+#include "ui/base/hit_test.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "ui/gfx/canvas.h"
+#include "ui/gfx/font.h"
+#include "ui/gfx/image/image.h"
+#include "ui/views/controls/button/image_button.h"
+#include "ui/views/widget/native_widget_aura.h"
+#include "ui/views/widget/widget.h"
+#include "ui/views/widget/widget_delegate.h"
+
+namespace ash {
+
+// static
+const char PanelFrameView::kViewClassName[] = "ash/wm/panels/PanelFrameView";
+
+PanelFrameView::PanelFrameView(views::Widget* frame, FrameType frame_type)
+ : frame_(frame),
+ close_button_(NULL),
+ minimize_button_(NULL),
+ window_icon_(NULL),
+ title_font_(gfx::Font(views::NativeWidgetAura::GetWindowTitleFont())) {
+ if (frame_type != FRAME_NONE)
+ InitFramePainter();
+}
+
+PanelFrameView::~PanelFrameView() {
+}
+
+const char* PanelFrameView::GetClassName() const {
+ return kViewClassName;
+}
+
+void PanelFrameView::InitFramePainter() {
+ frame_painter_.reset(new FramePainter);
+
+ close_button_ = new views::ImageButton(this);
+ close_button_->SetAccessibleName(
+ l10n_util::GetStringUTF16(IDS_APP_ACCNAME_CLOSE));
+ AddChildView(close_button_);
+
+ minimize_button_ = new views::ImageButton(this);
+ minimize_button_->SetAccessibleName(
+ l10n_util::GetStringUTF16(IDS_APP_ACCNAME_MINIMIZE));
+ AddChildView(minimize_button_);
+
+ if (frame_->widget_delegate()->ShouldShowWindowIcon()) {
+ window_icon_ = new views::ImageButton(this);
+ AddChildView(window_icon_);
+ }
+
+ frame_painter_->Init(frame_, window_icon_, minimize_button_, close_button_,
+ FramePainter::SIZE_BUTTON_MINIMIZES);
+}
+
+gfx::Size PanelFrameView::GetMinimumSize() {
+ if (!frame_painter_)
+ return gfx::Size();
+ return frame_painter_->GetMinimumSize(this);
+}
+
+void PanelFrameView::Layout() {
+ if (!frame_painter_)
+ return;
+ frame_painter_->LayoutHeader(this, true);
+}
+
+void PanelFrameView::ResetWindowControls() {
+ NOTIMPLEMENTED();
+}
+
+void PanelFrameView::UpdateWindowIcon() {
+ if (!window_icon_)
+ return;
+ views::WidgetDelegate* delegate = frame_->widget_delegate();
+ if (delegate) {
+ gfx::ImageSkia image = delegate->GetWindowIcon();
+ window_icon_->SetImage(views::CustomButton::STATE_NORMAL, &image);
+ }
+ window_icon_->SchedulePaint();
+}
+
+void PanelFrameView::UpdateWindowTitle() {
+ if (!frame_painter_)
+ return;
+ frame_painter_->SchedulePaintForTitle(title_font_);
+}
+
+void PanelFrameView::GetWindowMask(const gfx::Size&, gfx::Path*) {
+ // Nothing.
+}
+
+int PanelFrameView::NonClientHitTest(const gfx::Point& point) {
+ if (!frame_painter_)
+ return HTNOWHERE;
+ return frame_painter_->NonClientHitTest(this, point);
+}
+
+void PanelFrameView::OnPaint(gfx::Canvas* canvas) {
+ if (!frame_painter_)
+ return;
+ bool paint_as_active = ShouldPaintAsActive();
+ int theme_frame_id = 0;
+ if (frame_painter_->ShouldUseMinimalHeaderStyle(FramePainter::THEMED_NO))
+ theme_frame_id = IDR_AURA_WINDOW_HEADER_BASE_MINIMAL;
+ else if (paint_as_active)
+ theme_frame_id = IDR_AURA_WINDOW_HEADER_BASE_ACTIVE;
+ else
+ theme_frame_id = IDR_AURA_WINDOW_HEADER_BASE_INACTIVE;
+
+ frame_painter_->PaintHeader(
+ this,
+ canvas,
+ paint_as_active ? FramePainter::ACTIVE : FramePainter::INACTIVE,
+ theme_frame_id,
+ 0);
+ frame_painter_->PaintTitleBar(this, canvas, title_font_);
+ frame_painter_->PaintHeaderContentSeparator(this, canvas);
+}
+
+gfx::Rect PanelFrameView::GetBoundsForClientView() const {
+ if (!frame_painter_)
+ return bounds();
+ return frame_painter_->GetBoundsForClientView(
+ close_button_->bounds().bottom(),
+ bounds());
+}
+
+gfx::Rect PanelFrameView::GetWindowBoundsForClientBounds(
+ const gfx::Rect& client_bounds) const {
+ if (!frame_painter_)
+ return client_bounds;
+ return frame_painter_->GetWindowBoundsForClientBounds(
+ close_button_->bounds().bottom(), client_bounds);
+}
+
+void PanelFrameView::ButtonPressed(views::Button* sender,
+ const ui::Event& event) {
+ if (sender == close_button_)
+ GetWidget()->Close();
+ if (sender == minimize_button_)
+ GetWidget()->Minimize();
+}
+
+} // namespace ash
diff --git a/chromium/ash/wm/panels/panel_frame_view.h b/chromium/ash/wm/panels/panel_frame_view.h
new file mode 100644
index 00000000000..ee124dfd07c
--- /dev/null
+++ b/chromium/ash/wm/panels/panel_frame_view.h
@@ -0,0 +1,78 @@
+// Copyright (c) 2012 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 ASH_WM_PANELS_PANEL_FRAME_VIEW_H_
+#define ASH_WM_PANELS_PANEL_FRAME_VIEW_H_
+
+#include "ash/ash_export.h"
+#include "base/basictypes.h"
+#include "ui/aura/aura_export.h"
+#include "ui/gfx/font.h"
+#include "ui/views/controls/button/button.h" // ButtonListener
+#include "ui/views/window/non_client_view.h"
+
+namespace views {
+class ImageButton;
+}
+
+namespace ash {
+
+class FramePainter;
+
+class ASH_EXPORT PanelFrameView : public views::NonClientFrameView,
+ public views::ButtonListener {
+ public:
+ // Internal class name.
+ static const char kViewClassName[];
+
+ enum FrameType {
+ FRAME_NONE,
+ FRAME_ASH
+ };
+
+ PanelFrameView(views::Widget* frame, FrameType frame_type);
+ virtual ~PanelFrameView();
+
+ // Overridden from views::View:
+ virtual const char* GetClassName() const OVERRIDE;
+
+ private:
+ void InitFramePainter();
+
+ // Overridden from views::NonClientFrameView:
+ virtual gfx::Rect GetBoundsForClientView() const OVERRIDE;
+ virtual gfx::Rect GetWindowBoundsForClientBounds(
+ const gfx::Rect& client_bounds) const OVERRIDE;
+ virtual int NonClientHitTest(const gfx::Point& point) OVERRIDE;
+ virtual void GetWindowMask(const gfx::Size& size,
+ gfx::Path* window_mask) OVERRIDE;
+ virtual void ResetWindowControls() OVERRIDE;
+ virtual void UpdateWindowIcon() OVERRIDE;
+ virtual void UpdateWindowTitle() OVERRIDE;
+
+ // Overridden from views::View:
+ virtual gfx::Size GetMinimumSize() OVERRIDE;
+ virtual void Layout() OVERRIDE;
+ virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE;
+
+ // Overridden from views::ButtonListener:
+ virtual void ButtonPressed(views::Button* sender,
+ const ui::Event& event) OVERRIDE;
+
+ // Child View class describing the panel's title bar behavior
+ // and buttons, owned by the view hierarchy
+ scoped_ptr<FramePainter> frame_painter_;
+ views::Widget* frame_;
+ views::ImageButton* close_button_;
+ views::ImageButton* minimize_button_;
+ views::ImageButton* window_icon_;
+ gfx::Rect client_view_bounds_;
+ const gfx::Font title_font_;
+
+ DISALLOW_COPY_AND_ASSIGN(PanelFrameView);
+};
+
+}
+
+#endif // ASH_WM_PANELS_PANEL_FRAME_VIEW_H_
diff --git a/chromium/ash/wm/panels/panel_layout_manager.cc b/chromium/ash/wm/panels/panel_layout_manager.cc
new file mode 100644
index 00000000000..2b00c882b23
--- /dev/null
+++ b/chromium/ash/wm/panels/panel_layout_manager.cc
@@ -0,0 +1,873 @@
+// Copyright (c) 2012 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.
+
+#include "ash/wm/panels/panel_layout_manager.h"
+
+#include <algorithm>
+#include <map>
+
+#include "ash/launcher/launcher.h"
+#include "ash/screen_ash.h"
+#include "ash/shelf/shelf_layout_manager.h"
+#include "ash/shelf/shelf_types.h"
+#include "ash/shelf/shelf_widget.h"
+#include "ash/shell.h"
+#include "ash/shell_window_ids.h"
+#include "ash/wm/frame_painter.h"
+#include "ash/wm/property_util.h"
+#include "ash/wm/window_animations.h"
+#include "ash/wm/window_properties.h"
+#include "ash/wm/window_util.h"
+#include "base/auto_reset.h"
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "third_party/skia/include/core/SkColor.h"
+#include "third_party/skia/include/core/SkPaint.h"
+#include "third_party/skia/include/core/SkPath.h"
+#include "ui/aura/client/activation_client.h"
+#include "ui/aura/client/aura_constants.h"
+#include "ui/aura/focus_manager.h"
+#include "ui/aura/root_window.h"
+#include "ui/aura/window.h"
+#include "ui/compositor/scoped_layer_animation_settings.h"
+#include "ui/gfx/canvas.h"
+#include "ui/gfx/rect.h"
+#include "ui/gfx/vector2d.h"
+#include "ui/views/background.h"
+#include "ui/views/widget/widget.h"
+
+namespace ash {
+namespace internal {
+
+namespace {
+const int kPanelMarginEdge = 4;
+const int kPanelMarginMiddle = 8;
+const int kPanelIdealSpacing = 4;
+
+const float kMaxHeightFactor = .80f;
+const float kMaxWidthFactor = .50f;
+
+// Duration for panel animations.
+const int kPanelSlideDurationMilliseconds = 50;
+const int kCalloutFadeDurationMilliseconds = 50;
+
+// Offset used when sliding panel in/out of the launcher. Used for minimizing,
+// restoring and the initial showing of a panel.
+const int kPanelSlideInOffset = 20;
+
+// Callout arrow dimensions.
+const int kArrowWidth = 18;
+const int kArrowHeight = 9;
+
+class CalloutWidgetBackground : public views::Background {
+ public:
+ CalloutWidgetBackground() : alignment_(SHELF_ALIGNMENT_BOTTOM) {
+ }
+
+ virtual void Paint(gfx::Canvas* canvas, views::View* view) const OVERRIDE {
+ SkPath path;
+ switch (alignment_) {
+ case SHELF_ALIGNMENT_BOTTOM:
+ path.moveTo(SkIntToScalar(0), SkIntToScalar(0));
+ path.lineTo(SkIntToScalar(kArrowWidth / 2),
+ SkIntToScalar(kArrowHeight));
+ path.lineTo(SkIntToScalar(kArrowWidth), SkIntToScalar(0));
+ break;
+ case SHELF_ALIGNMENT_LEFT:
+ path.moveTo(SkIntToScalar(kArrowHeight), SkIntToScalar(kArrowWidth));
+ path.lineTo(SkIntToScalar(0), SkIntToScalar(kArrowWidth / 2));
+ path.lineTo(SkIntToScalar(kArrowHeight), SkIntToScalar(0));
+ break;
+ case SHELF_ALIGNMENT_TOP:
+ path.moveTo(SkIntToScalar(0), SkIntToScalar(kArrowHeight));
+ path.lineTo(SkIntToScalar(kArrowWidth / 2), SkIntToScalar(0));
+ path.lineTo(SkIntToScalar(kArrowWidth), SkIntToScalar(kArrowHeight));
+ break;
+ case SHELF_ALIGNMENT_RIGHT:
+ path.moveTo(SkIntToScalar(0), SkIntToScalar(0));
+ path.lineTo(SkIntToScalar(kArrowHeight),
+ SkIntToScalar(kArrowWidth / 2));
+ path.lineTo(SkIntToScalar(0), SkIntToScalar(kArrowWidth));
+ break;
+ }
+ // Hard code the arrow color for now.
+ SkPaint paint;
+ paint.setStyle(SkPaint::kFill_Style);
+ paint.setColor(SkColorSetARGB(0xff, 0xe5, 0xe5, 0xe5));
+ canvas->DrawPath(path, paint);
+ }
+
+ ShelfAlignment alignment() {
+ return alignment_;
+ }
+
+ void set_alignment(ShelfAlignment alignment) {
+ alignment_ = alignment;
+ }
+
+ private:
+ ShelfAlignment alignment_;
+
+ DISALLOW_COPY_AND_ASSIGN(CalloutWidgetBackground);
+};
+
+struct VisiblePanelPositionInfo {
+ VisiblePanelPositionInfo()
+ : min_major(0),
+ max_major(0),
+ major_pos(0),
+ major_length(0),
+ window(NULL),
+ slide_in(false) {}
+
+ int min_major;
+ int max_major;
+ int major_pos;
+ int major_length;
+ aura::Window* window;
+ bool slide_in;
+};
+
+bool CompareWindowMajor(const VisiblePanelPositionInfo& win1,
+ const VisiblePanelPositionInfo& win2) {
+ return win1.major_pos < win2.major_pos;
+}
+
+void FanOutPanels(std::vector<VisiblePanelPositionInfo>::iterator first,
+ std::vector<VisiblePanelPositionInfo>::iterator last) {
+ int num_panels = last - first;
+ if (num_panels == 1) {
+ (*first).major_pos = std::max((*first).min_major, std::min(
+ (*first).max_major, (*first).major_pos));
+ }
+ if (num_panels <= 1)
+ return;
+
+ if (num_panels == 2) {
+ // If there are two adjacent overlapping windows, separate them by the
+ // minimum major_length necessary.
+ std::vector<VisiblePanelPositionInfo>::iterator second = first + 1;
+ int separation = (*first).major_length / 2 + (*second).major_length / 2 +
+ kPanelIdealSpacing;
+ int overlap = (*first).major_pos + separation - (*second).major_pos;
+ (*first).major_pos = std::max((*first).min_major,
+ (*first).major_pos - overlap / 2);
+ (*second).major_pos = std::min((*second).max_major,
+ (*first).major_pos + separation);
+ // Recalculate the first panel position in case the second one was
+ // constrained on the right.
+ (*first).major_pos = std::max((*first).min_major,
+ (*second).major_pos - separation);
+ return;
+ }
+
+ // If there are more than two overlapping windows, fan them out from minimum
+ // position to maximum position equally spaced.
+ int delta = ((*(last - 1)).max_major - (*first).min_major) / (num_panels - 1);
+ int major_pos = (*first).min_major;
+ for (std::vector<VisiblePanelPositionInfo>::iterator iter = first;
+ iter != last; ++iter) {
+ (*iter).major_pos = std::max((*iter).min_major,
+ std::min((*iter).max_major, major_pos));
+ major_pos += delta;
+ }
+}
+
+bool BoundsAdjacent(const gfx::Rect& bounds1, const gfx::Rect& bounds2) {
+ return bounds1.x() == bounds2.right() ||
+ bounds1.y() == bounds2.bottom() ||
+ bounds1.right() == bounds2.x() ||
+ bounds1.bottom() == bounds2.y();
+}
+
+gfx::Vector2d GetSlideInAnimationOffset(ShelfAlignment alignment) {
+ gfx::Vector2d offset;
+ switch (alignment) {
+ case SHELF_ALIGNMENT_BOTTOM:
+ offset.set_y(kPanelSlideInOffset);
+ break;
+ case SHELF_ALIGNMENT_LEFT:
+ offset.set_x(-kPanelSlideInOffset);
+ break;
+ case SHELF_ALIGNMENT_RIGHT:
+ offset.set_x(kPanelSlideInOffset);
+ break;
+ case SHELF_ALIGNMENT_TOP:
+ offset.set_y(-kPanelSlideInOffset);
+ break;
+ }
+ return offset;
+}
+
+} // namespace
+
+class PanelCalloutWidget : public views::Widget {
+ public:
+ explicit PanelCalloutWidget(aura::Window* container)
+ : background_(NULL) {
+ InitWidget(container);
+ }
+
+ void SetAlignment(ShelfAlignment alignment) {
+ gfx::Rect callout_bounds = GetWindowBoundsInScreen();
+ if (alignment == SHELF_ALIGNMENT_BOTTOM ||
+ alignment == SHELF_ALIGNMENT_TOP) {
+ callout_bounds.set_width(kArrowWidth);
+ callout_bounds.set_height(kArrowHeight);
+ } else {
+ callout_bounds.set_width(kArrowHeight);
+ callout_bounds.set_height(kArrowWidth);
+ }
+ GetNativeWindow()->SetBounds(callout_bounds);
+ if (background_->alignment() != alignment) {
+ background_->set_alignment(alignment);
+ SchedulePaintInRect(gfx::Rect(gfx::Point(), callout_bounds.size()));
+ }
+ }
+
+ private:
+ void InitWidget(aura::Window* parent) {
+ views::Widget::InitParams params;
+ params.type = views::Widget::InitParams::TYPE_POPUP;
+ params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW;
+ params.can_activate = false;
+ params.keep_on_top = true;
+ params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
+ params.parent = parent;
+ params.bounds = ScreenAsh::ConvertRectToScreen(parent, gfx::Rect());
+ params.bounds.set_width(kArrowWidth);
+ params.bounds.set_height(kArrowHeight);
+ // Why do we need this and can_activate = false?
+ set_focus_on_creation(false);
+ Init(params);
+ DCHECK_EQ(GetNativeView()->GetRootWindow(), parent->GetRootWindow());
+ views::View* content_view = new views::View;
+ background_ = new CalloutWidgetBackground;
+ content_view->set_background(background_);
+ SetContentsView(content_view);
+ GetNativeWindow()->layer()->SetOpacity(0);
+ }
+
+ // Weak pointer owned by this widget's content view.
+ CalloutWidgetBackground* background_;
+
+ DISALLOW_COPY_AND_ASSIGN(PanelCalloutWidget);
+};
+
+////////////////////////////////////////////////////////////////////////////////
+// PanelLayoutManager public implementation:
+PanelLayoutManager::PanelLayoutManager(aura::Window* panel_container)
+ : panel_container_(panel_container),
+ in_add_window_(false),
+ in_layout_(false),
+ dragged_panel_(NULL),
+ launcher_(NULL),
+ shelf_layout_manager_(NULL),
+ shelf_hidden_(false),
+ last_active_panel_(NULL),
+ weak_factory_(this) {
+ DCHECK(panel_container);
+ aura::client::GetActivationClient(Shell::GetPrimaryRootWindow())->
+ AddObserver(this);
+ Shell::GetInstance()->display_controller()->AddObserver(this);
+ Shell::GetInstance()->AddShellObserver(this);
+}
+
+PanelLayoutManager::~PanelLayoutManager() {
+ Shutdown();
+}
+
+void PanelLayoutManager::Shutdown() {
+ if (shelf_layout_manager_)
+ shelf_layout_manager_->RemoveObserver(this);
+ shelf_layout_manager_ = NULL;
+ for (PanelList::iterator iter = panel_windows_.begin();
+ iter != panel_windows_.end(); ++iter) {
+ delete iter->callout_widget;
+ }
+ panel_windows_.clear();
+ if (launcher_)
+ launcher_->RemoveIconObserver(this);
+ launcher_ = NULL;
+ aura::client::GetActivationClient(Shell::GetPrimaryRootWindow())->
+ RemoveObserver(this);
+ Shell::GetInstance()->display_controller()->RemoveObserver(this);
+ Shell::GetInstance()->RemoveShellObserver(this);
+}
+
+void PanelLayoutManager::StartDragging(aura::Window* panel) {
+ DCHECK(!dragged_panel_);
+ dragged_panel_ = panel;
+ Relayout();
+}
+
+void PanelLayoutManager::FinishDragging() {
+ dragged_panel_ = NULL;
+ Relayout();
+}
+
+void PanelLayoutManager::SetLauncher(ash::Launcher* launcher) {
+ DCHECK(!launcher_);
+ DCHECK(!shelf_layout_manager_);
+ launcher_ = launcher;
+ launcher_->AddIconObserver(this);
+ if (launcher_->shelf_widget()) {
+ shelf_layout_manager_ = ash::internal::ShelfLayoutManager::ForLauncher(
+ launcher_->shelf_widget()->GetNativeWindow());
+ WillChangeVisibilityState(shelf_layout_manager_->visibility_state());
+ shelf_layout_manager_->AddObserver(this);
+ }
+}
+
+void PanelLayoutManager::ToggleMinimize(aura::Window* panel) {
+ DCHECK(panel->parent() == panel_container_);
+ if (panel->GetProperty(aura::client::kShowStateKey) ==
+ ui::SHOW_STATE_MINIMIZED) {
+ panel->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_NORMAL);
+ } else {
+ panel->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MINIMIZED);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// PanelLayoutManager, aura::LayoutManager implementation:
+void PanelLayoutManager::OnWindowResized() {
+ Relayout();
+}
+
+void PanelLayoutManager::OnWindowAddedToLayout(aura::Window* child) {
+ if (child->type() == aura::client::WINDOW_TYPE_POPUP)
+ return;
+ if (in_add_window_)
+ return;
+ base::AutoReset<bool> auto_reset_in_add_window(&in_add_window_, true);
+ if (!child->GetProperty(kPanelAttachedKey)) {
+ // This should only happen when a window is added to panel container as a
+ // result of bounds change from within the application during a drag.
+ // If so we have already stopped the drag and should reparent the panel
+ // back to appropriate container and ignore it.
+ // TODO(varkha): Updating bounds during a drag can cause problems and a more
+ // general solution is needed. See http://crbug.com/251813 .
+ child->SetDefaultParentByRootWindow(
+ child->GetRootWindow(),
+ child->GetRootWindow()->GetBoundsInScreen());
+ DCHECK(child->parent()->id() != kShellWindowId_PanelContainer);
+ return;
+ }
+ PanelInfo panel_info;
+ panel_info.window = child;
+ panel_info.callout_widget = new PanelCalloutWidget(panel_container_);
+ if (child != dragged_panel_) {
+ // Set the panel to 0 opacity until it has been positioned to prevent it
+ // from flashing briefly at position (0, 0).
+ child->layer()->SetOpacity(0);
+ panel_info.slide_in = true;
+ }
+ panel_windows_.push_back(panel_info);
+ child->AddObserver(this);
+ Relayout();
+}
+
+void PanelLayoutManager::OnWillRemoveWindowFromLayout(aura::Window* child) {
+}
+
+void PanelLayoutManager::OnWindowRemovedFromLayout(aura::Window* child) {
+ if (child->type() == aura::client::WINDOW_TYPE_POPUP)
+ return;
+ PanelList::iterator found =
+ std::find(panel_windows_.begin(), panel_windows_.end(), child);
+ if (found != panel_windows_.end()) {
+ delete found->callout_widget;
+ panel_windows_.erase(found);
+ }
+ child->RemoveObserver(this);
+
+ if (dragged_panel_ == child)
+ dragged_panel_ = NULL;
+
+ if (last_active_panel_ == child)
+ last_active_panel_ = NULL;
+
+ Relayout();
+}
+
+void PanelLayoutManager::OnChildWindowVisibilityChanged(aura::Window* child,
+ bool visible) {
+ Relayout();
+}
+
+void PanelLayoutManager::SetChildBounds(aura::Window* child,
+ const gfx::Rect& requested_bounds) {
+ gfx::Rect bounds(requested_bounds);
+ const gfx::Rect& max_bounds = panel_container_->GetRootWindow()->bounds();
+ const int max_width = max_bounds.width() * kMaxWidthFactor;
+ const int max_height = max_bounds.height() * kMaxHeightFactor;
+ if (bounds.width() > max_width)
+ bounds.set_width(max_width);
+ if (bounds.height() > max_height)
+ bounds.set_height(max_height);
+
+ // Reposition dragged panel in the panel order.
+ if (dragged_panel_ == child) {
+ PanelList::iterator dragged_panel_iter =
+ std::find(panel_windows_.begin(), panel_windows_.end(), dragged_panel_);
+ DCHECK(dragged_panel_iter != panel_windows_.end());
+ PanelList::iterator new_position;
+ for (new_position = panel_windows_.begin();
+ new_position != panel_windows_.end();
+ ++new_position) {
+ const gfx::Rect& bounds = (*new_position).window->bounds();
+ if (bounds.x() + bounds.width()/2 <= requested_bounds.x()) break;
+ }
+ if (new_position != dragged_panel_iter) {
+ PanelInfo dragged_panel_info = *dragged_panel_iter;
+ panel_windows_.erase(dragged_panel_iter);
+ panel_windows_.insert(new_position, dragged_panel_info);
+ }
+ }
+
+ SetChildBoundsDirect(child, bounds);
+ Relayout();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// PanelLayoutManager, ash::LauncherIconObserver implementation:
+
+void PanelLayoutManager::OnLauncherIconPositionsChanged() {
+ // TODO: As this is called for every animation step now. Relayout needs to be
+ // updated to use current icon position instead of use the ideal bounds so
+ // that the panels slide with their icons instead of jumping.
+ Relayout();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// PanelLayoutManager, ash::ShellObserver implementation:
+
+void PanelLayoutManager::OnShelfAlignmentChanged(
+ aura::RootWindow* root_window) {
+ if (panel_container_->GetRootWindow() == root_window)
+ Relayout();
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// PanelLayoutManager, WindowObserver implementation:
+
+void PanelLayoutManager::OnWindowPropertyChanged(aura::Window* window,
+ const void* key,
+ intptr_t old) {
+ if (key != aura::client::kShowStateKey)
+ return;
+ // The window property will still be set, but no actual change will occur
+ // until WillChangeVisibilityState is called when the shelf is visible again.
+ if (shelf_hidden_)
+ return;
+ ui::WindowShowState new_state =
+ window->GetProperty(aura::client::kShowStateKey);
+ if (new_state == ui::SHOW_STATE_MINIMIZED)
+ MinimizePanel(window);
+ else
+ RestorePanel(window);
+}
+
+void PanelLayoutManager::OnWindowVisibilityChanged(
+ aura::Window* window, bool visible) {
+ if (visible)
+ window->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_NORMAL);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// PanelLayoutManager, aura::client::ActivationChangeObserver implementation:
+
+void PanelLayoutManager::OnWindowActivated(aura::Window* gained_active,
+ aura::Window* lost_active) {
+ // Ignore if the panel that is not managed by this was activated.
+ if (gained_active &&
+ gained_active->type() == aura::client::WINDOW_TYPE_PANEL &&
+ gained_active->parent() == panel_container_) {
+ UpdateStacking(gained_active);
+ UpdateCallouts();
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// PanelLayoutManager, DisplayController::Observer implementation:
+
+void PanelLayoutManager::OnDisplayConfigurationChanged() {
+ Relayout();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// PanelLayoutManager, ShelfLayoutManagerObserver implementation:
+
+void PanelLayoutManager::WillChangeVisibilityState(
+ ShelfVisibilityState new_state) {
+ // On entering / leaving full screen mode the shelf visibility state is
+ // changed to / from SHELF_HIDDEN. In this state, panel windows should hide
+ // to allow the full-screen application to use the full screen.
+ shelf_hidden_ = new_state == ash::SHELF_HIDDEN;
+ for (PanelList::iterator iter = panel_windows_.begin();
+ iter != panel_windows_.end(); ++iter) {
+ if (shelf_hidden_) {
+ if (iter->window->IsVisible())
+ MinimizePanel(iter->window);
+ } else {
+ if (iter->window->GetProperty(aura::client::kShowStateKey) !=
+ ui::SHOW_STATE_MINIMIZED) {
+ RestorePanel(iter->window);
+ }
+ }
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// PanelLayoutManager private implementation:
+
+void PanelLayoutManager::MinimizePanel(aura::Window* panel) {
+ views::corewm::SetWindowVisibilityAnimationType(
+ panel, WINDOW_VISIBILITY_ANIMATION_TYPE_MINIMIZE);
+ ui::Layer* layer = panel->layer();
+ ui::ScopedLayerAnimationSettings panel_slide_settings(layer->GetAnimator());
+ panel_slide_settings.SetPreemptionStrategy(
+ ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
+ panel_slide_settings.SetTransitionDuration(
+ base::TimeDelta::FromMilliseconds(kPanelSlideDurationMilliseconds));
+ gfx::Rect bounds(panel->bounds());
+ bounds.Offset(GetSlideInAnimationOffset(
+ launcher_->shelf_widget()->GetAlignment()));
+ SetChildBoundsDirect(panel, bounds);
+ panel->Hide();
+ PanelList::iterator found =
+ std::find(panel_windows_.begin(), panel_windows_.end(), panel);
+ if (found != panel_windows_.end()) {
+ layer->SetOpacity(0);
+ // The next time the window is visible it should slide into place.
+ found->slide_in = true;
+ }
+ if (wm::IsActiveWindow(panel))
+ wm::DeactivateWindow(panel);
+ Relayout();
+}
+
+void PanelLayoutManager::RestorePanel(aura::Window* panel) {
+ panel->Show();
+ Relayout();
+}
+
+void PanelLayoutManager::Relayout() {
+ if (!launcher_ || !launcher_->shelf_widget())
+ return;
+
+ if (in_layout_)
+ return;
+ base::AutoReset<bool> auto_reset_in_layout(&in_layout_, true);
+
+ ShelfAlignment alignment = launcher_->shelf_widget()->GetAlignment();
+ bool horizontal = alignment == SHELF_ALIGNMENT_TOP ||
+ alignment == SHELF_ALIGNMENT_BOTTOM;
+ gfx::Rect launcher_bounds = ash::ScreenAsh::ConvertRectFromScreen(
+ panel_container_, launcher_->shelf_widget()->GetWindowBoundsInScreen());
+ int panel_start_bounds = kPanelIdealSpacing;
+ int panel_end_bounds = horizontal ?
+ panel_container_->bounds().width() - kPanelIdealSpacing :
+ panel_container_->bounds().height() - kPanelIdealSpacing;
+ aura::Window* active_panel = NULL;
+ std::vector<VisiblePanelPositionInfo> visible_panels;
+ for (PanelList::iterator iter = panel_windows_.begin();
+ iter != panel_windows_.end(); ++iter) {
+ aura::Window* panel = iter->window;
+ iter->callout_widget->SetAlignment(alignment);
+
+ // Consider the dragged panel as part of the layout as long as it is
+ // touching the launcher.
+ if (!panel->IsVisible() ||
+ (panel == dragged_panel_ &&
+ !BoundsAdjacent(panel->bounds(), launcher_bounds))) {
+ continue;
+ }
+
+ // If the shelf is currently hidden (full-screen mode), hide panel until
+ // full-screen mode is exited.
+ if (shelf_hidden_) {
+ // The call to Hide does not set the minimize property, so the window will
+ // be restored when the shelf becomes visible again.
+ panel->Hide();
+ continue;
+ }
+
+ gfx::Rect icon_bounds =
+ launcher_->GetScreenBoundsOfItemIconForWindow(panel);
+
+ // If both the icon width and height are 0 then there is no icon in the
+ // launcher. If the launcher is hidden, one of the height or width will be
+ // 0 but the position in the launcher and major dimension is still reported
+ // correctly and the panel can be aligned above where the hidden icon is.
+ if (icon_bounds.width() == 0 && icon_bounds.height() == 0)
+ continue;
+
+ if (panel->HasFocus() ||
+ panel->Contains(
+ aura::client::GetFocusClient(panel)->GetFocusedWindow())) {
+ DCHECK(!active_panel);
+ active_panel = panel;
+ }
+ icon_bounds = ScreenAsh::ConvertRectFromScreen(panel_container_,
+ icon_bounds);
+ gfx::Point icon_origin = icon_bounds.origin();
+ VisiblePanelPositionInfo position_info;
+ int icon_start = horizontal ? icon_origin.x() : icon_origin.y();
+ int icon_end = icon_start + (horizontal ? icon_bounds.width() :
+ icon_bounds.height());
+ position_info.major_length = horizontal ?
+ panel->bounds().width() : panel->bounds().height();
+ position_info.min_major = std::max(
+ panel_start_bounds + position_info.major_length / 2,
+ icon_end - position_info.major_length / 2);
+ position_info.max_major = std::min(
+ icon_start + position_info.major_length / 2,
+ panel_end_bounds - position_info.major_length / 2);
+ position_info.major_pos = (icon_start + icon_end) / 2;
+ position_info.window = panel;
+ position_info.slide_in = iter->slide_in;
+ iter->slide_in = false;
+ visible_panels.push_back(position_info);
+ }
+
+ // Sort panels by their X positions and fan out groups of overlapping panels.
+ // The fan out method may result in new overlapping panels however given that
+ // the panels start at least a full panel width apart this overlap will
+ // never completely obscure a panel.
+ // TODO(flackr): Rearrange panels if new overlaps are introduced.
+ std::sort(visible_panels.begin(), visible_panels.end(), CompareWindowMajor);
+ size_t first_overlapping_panel = 0;
+ for (size_t i = 1; i < visible_panels.size(); ++i) {
+ if (visible_panels[i - 1].major_pos +
+ visible_panels[i - 1].major_length / 2 < visible_panels[i].major_pos -
+ visible_panels[i].major_length / 2) {
+ FanOutPanels(visible_panels.begin() + first_overlapping_panel,
+ visible_panels.begin() + i);
+ first_overlapping_panel = i;
+ }
+ }
+ FanOutPanels(visible_panels.begin() + first_overlapping_panel,
+ visible_panels.end());
+
+ for (size_t i = 0; i < visible_panels.size(); ++i) {
+ if (visible_panels[i].window == dragged_panel_)
+ continue;
+ bool slide_in = visible_panels[i].slide_in;
+ gfx::Rect bounds = visible_panels[i].window->GetTargetBounds();
+ switch (alignment) {
+ case SHELF_ALIGNMENT_BOTTOM:
+ bounds.set_y(launcher_bounds.y() - bounds.height());
+ break;
+ case SHELF_ALIGNMENT_LEFT:
+ bounds.set_x(launcher_bounds.right());
+ break;
+ case SHELF_ALIGNMENT_RIGHT:
+ bounds.set_x(launcher_bounds.x() - bounds.width());
+ break;
+ case SHELF_ALIGNMENT_TOP:
+ bounds.set_y(launcher_bounds.bottom());
+ break;
+ }
+ bool on_launcher = visible_panels[i].window->GetTargetBounds() == bounds;
+
+ if (horizontal) {
+ bounds.set_x(visible_panels[i].major_pos -
+ visible_panels[i].major_length / 2);
+ } else {
+ bounds.set_y(visible_panels[i].major_pos -
+ visible_panels[i].major_length / 2);
+ }
+
+ ui::Layer* layer = visible_panels[i].window->layer();
+ if (slide_in) {
+ // New windows shift up from the launcher into position.
+ gfx::Rect initial_bounds(bounds);
+ initial_bounds.Offset(GetSlideInAnimationOffset(alignment));
+ SetChildBoundsDirect(visible_panels[i].window, initial_bounds);
+ // Set on launcher so that the panel animates into its target position.
+ on_launcher = true;
+ }
+
+ if (on_launcher) {
+ ui::ScopedLayerAnimationSettings panel_slide_settings(
+ layer->GetAnimator());
+ panel_slide_settings.SetPreemptionStrategy(
+ ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
+ panel_slide_settings.SetTransitionDuration(
+ base::TimeDelta::FromMilliseconds(kPanelSlideDurationMilliseconds));
+ SetChildBoundsDirect(visible_panels[i].window, bounds);
+ if (slide_in)
+ layer->SetOpacity(1);
+ } else {
+ // If the launcher moved don't animate, move immediately to the new
+ // target location.
+ SetChildBoundsDirect(visible_panels[i].window, bounds);
+ }
+ }
+
+ UpdateStacking(active_panel);
+ UpdateCallouts();
+}
+
+void PanelLayoutManager::UpdateStacking(aura::Window* active_panel) {
+ if (!active_panel) {
+ if (!last_active_panel_)
+ return;
+ active_panel = last_active_panel_;
+ }
+
+ ShelfAlignment alignment = launcher_->alignment();
+ bool horizontal = alignment == SHELF_ALIGNMENT_TOP ||
+ alignment == SHELF_ALIGNMENT_BOTTOM;
+
+ // We want to to stack the panels like a deck of cards:
+ // ,--,--,--,-------.--.--.
+ // | | | | | | |
+ // | | | | | | |
+ //
+ // We use the middle of each panel to figure out how to stack the panels. This
+ // allows us to update the stacking when a panel is being dragged around by
+ // the titlebar--even though it doesn't update the launcher icon positions, we
+ // still want the visual effect.
+ std::map<int, aura::Window*> window_ordering;
+ for (PanelList::const_iterator it = panel_windows_.begin();
+ it != panel_windows_.end(); ++it) {
+ gfx::Rect bounds = it->window->bounds();
+ window_ordering.insert(std::make_pair(horizontal ?
+ bounds.x() + bounds.width() / 2 :
+ bounds.y() + bounds.height() / 2,
+ it->window));
+ }
+
+ aura::Window* previous_panel = NULL;
+ for (std::map<int, aura::Window*>::const_iterator it =
+ window_ordering.begin();
+ it != window_ordering.end() && it->second != active_panel; ++it) {
+ if (previous_panel)
+ panel_container_->StackChildAbove(it->second, previous_panel);
+ previous_panel = it->second;
+ }
+
+ previous_panel = NULL;
+ for (std::map<int, aura::Window*>::const_reverse_iterator it =
+ window_ordering.rbegin();
+ it != window_ordering.rend() && it->second != active_panel; ++it) {
+ if (previous_panel)
+ panel_container_->StackChildAbove(it->second, previous_panel);
+ previous_panel = it->second;
+ }
+
+ panel_container_->StackChildAtTop(active_panel);
+ if (dragged_panel_ && dragged_panel_->parent() == panel_container_)
+ panel_container_->StackChildAtTop(dragged_panel_);
+ last_active_panel_ = active_panel;
+}
+
+void PanelLayoutManager::UpdateCallouts() {
+ ShelfAlignment alignment = launcher_->alignment();
+ bool horizontal = alignment == SHELF_ALIGNMENT_TOP ||
+ alignment == SHELF_ALIGNMENT_BOTTOM;
+
+ for (PanelList::iterator iter = panel_windows_.begin();
+ iter != panel_windows_.end(); ++iter) {
+ aura::Window* panel = iter->window;
+ views::Widget* callout_widget = iter->callout_widget;
+
+ gfx::Rect current_bounds = panel->GetBoundsInScreen();
+ gfx::Rect bounds = ScreenAsh::ConvertRectToScreen(panel->parent(),
+ panel->GetTargetBounds());
+ gfx::Rect icon_bounds =
+ launcher_->GetScreenBoundsOfItemIconForWindow(panel);
+ if (icon_bounds.IsEmpty() || !panel->layer()->GetTargetVisibility() ||
+ panel == dragged_panel_) {
+ callout_widget->Hide();
+ callout_widget->GetNativeWindow()->layer()->SetOpacity(0);
+ continue;
+ }
+
+ gfx::Rect callout_bounds = callout_widget->GetWindowBoundsInScreen();
+ gfx::Vector2d slide_vector = bounds.origin() - current_bounds.origin();
+ int slide_distance = horizontal ? slide_vector.x() : slide_vector.y();
+ int distance_until_over_panel = 0;
+ if (horizontal) {
+ callout_bounds.set_x(
+ icon_bounds.x() + (icon_bounds.width() - callout_bounds.width()) / 2);
+ distance_until_over_panel = std::max(
+ current_bounds.x() - callout_bounds.x(),
+ callout_bounds.right() - current_bounds.right());
+ } else {
+ callout_bounds.set_y(
+ icon_bounds.y() + (icon_bounds.height() -
+ callout_bounds.height()) / 2);
+ distance_until_over_panel = std::max(
+ current_bounds.y() - callout_bounds.y(),
+ callout_bounds.bottom() - current_bounds.bottom());
+ }
+ switch (alignment) {
+ case SHELF_ALIGNMENT_BOTTOM:
+ callout_bounds.set_y(bounds.bottom());
+ break;
+ case SHELF_ALIGNMENT_LEFT:
+ callout_bounds.set_x(bounds.x() - callout_bounds.width());
+ break;
+ case SHELF_ALIGNMENT_RIGHT:
+ callout_bounds.set_x(bounds.right());
+ break;
+ case SHELF_ALIGNMENT_TOP:
+ callout_bounds.set_y(bounds.y() - callout_bounds.height());
+ break;
+ }
+ callout_bounds = ScreenAsh::ConvertRectFromScreen(
+ callout_widget->GetNativeWindow()->parent(),
+ callout_bounds);
+
+ SetChildBoundsDirect(callout_widget->GetNativeWindow(), callout_bounds);
+ panel_container_->StackChildAbove(callout_widget->GetNativeWindow(),
+ panel);
+ callout_widget->Show();
+
+ ui::Layer* layer = callout_widget->GetNativeWindow()->layer();
+ // If the panel is not over the callout position or has just become visible
+ // then fade in the callout.
+ if (distance_until_over_panel > 0 || layer->GetTargetOpacity() < 1) {
+ if (distance_until_over_panel > 0 &&
+ slide_distance >= distance_until_over_panel) {
+ layer->SetOpacity(0);
+ // If the panel is not yet over the callout, then delay fading in
+ // the callout until after the panel should be over it.
+ int delay = kPanelSlideDurationMilliseconds *
+ distance_until_over_panel / slide_distance;
+ layer->SetOpacity(0);
+ layer->GetAnimator()->StopAnimating();
+ layer->GetAnimator()->SchedulePauseForProperties(
+ base::TimeDelta::FromMilliseconds(delay),
+ ui::LayerAnimationElement::OPACITY);
+ }
+ {
+ ui::ScopedLayerAnimationSettings callout_settings(layer->GetAnimator());
+ callout_settings.SetPreemptionStrategy(
+ ui::LayerAnimator::REPLACE_QUEUED_ANIMATIONS);
+ callout_settings.SetTransitionDuration(
+ base::TimeDelta::FromMilliseconds(
+ kCalloutFadeDurationMilliseconds));
+ layer->SetOpacity(1);
+ }
+ }
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// keyboard::KeyboardControllerObserver implementation:
+
+void PanelLayoutManager::OnKeyboardBoundsChanging(
+ const gfx::Rect& keyboard_bounds) {
+ // This bounds change will have caused a change to the Shelf which does not
+ // propogate automatically to this class, so manually recalculate bounds.
+ OnWindowResized();
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/wm/panels/panel_layout_manager.h b/chromium/ash/wm/panels/panel_layout_manager.h
new file mode 100644
index 00000000000..f3b128cb18e
--- /dev/null
+++ b/chromium/ash/wm/panels/panel_layout_manager.h
@@ -0,0 +1,186 @@
+// Copyright (c) 2012 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 ASH_WM_PANELS_PANEL_LAYOUT_MANAGER_H_
+#define ASH_WM_PANELS_PANEL_LAYOUT_MANAGER_H_
+
+#include <list>
+
+#include "ash/ash_export.h"
+#include "ash/display/display_controller.h"
+#include "ash/launcher/launcher_icon_observer.h"
+#include "ash/shelf/shelf_layout_manager_observer.h"
+#include "ash/shell_observer.h"
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "ui/aura/client/activation_change_observer.h"
+#include "ui/aura/layout_manager.h"
+#include "ui/aura/window_observer.h"
+#include "ui/keyboard/keyboard_controller.h"
+#include "ui/keyboard/keyboard_controller_observer.h"
+
+namespace aura {
+class Window;
+}
+
+namespace gfx {
+class Rect;
+}
+
+namespace views {
+class Widget;
+}
+
+namespace ash {
+class Launcher;
+
+namespace internal {
+class PanelCalloutWidget;
+class ShelfLayoutManager;
+
+// PanelLayoutManager is responsible for organizing panels within the
+// workspace. It is associated with a specific container window (i.e.
+// kShellWindowId_PanelContainer) and controls the layout of any windows
+// added to that container.
+//
+// The constructor takes a |panel_container| argument which is expected to set
+// its layout manager to this instance, e.g.:
+// panel_container->SetLayoutManager(new PanelLayoutManager(panel_container));
+
+class ASH_EXPORT PanelLayoutManager
+ : public aura::LayoutManager,
+ public ash::LauncherIconObserver,
+ public ash::ShellObserver,
+ public aura::WindowObserver,
+ public aura::client::ActivationChangeObserver,
+ public keyboard::KeyboardControllerObserver,
+ public DisplayController::Observer,
+ public ShelfLayoutManagerObserver {
+ public:
+ explicit PanelLayoutManager(aura::Window* panel_container);
+ virtual ~PanelLayoutManager();
+
+ // Call Shutdown() before deleting children of panel_container.
+ void Shutdown();
+
+ void StartDragging(aura::Window* panel);
+ void FinishDragging();
+
+ void ToggleMinimize(aura::Window* panel);
+
+ ash::Launcher* launcher() { return launcher_; }
+ void SetLauncher(ash::Launcher* launcher);
+
+ // Overridden from aura::LayoutManager:
+ virtual void OnWindowResized() OVERRIDE;
+ virtual void OnWindowAddedToLayout(aura::Window* child) OVERRIDE;
+ virtual void OnWillRemoveWindowFromLayout(aura::Window* child) OVERRIDE;
+ virtual void OnWindowRemovedFromLayout(aura::Window* child) OVERRIDE;
+ virtual void OnChildWindowVisibilityChanged(aura::Window* child,
+ bool visibile) OVERRIDE;
+ virtual void SetChildBounds(aura::Window* child,
+ const gfx::Rect& requested_bounds) OVERRIDE;
+
+ // Overridden from ash::LauncherIconObserver
+ virtual void OnLauncherIconPositionsChanged() OVERRIDE;
+
+ // Overridden from ash::ShellObserver
+ virtual void OnShelfAlignmentChanged(aura::RootWindow* root_window) OVERRIDE;
+
+ // Overridden from aura::WindowObserver
+ virtual void OnWindowPropertyChanged(aura::Window* window,
+ const void* key,
+ intptr_t old) OVERRIDE;
+ virtual void OnWindowVisibilityChanged(aura::Window* window,
+ bool visible) OVERRIDE;
+
+ // Overridden from aura::client::ActivationChangeObserver
+ virtual void OnWindowActivated(aura::Window* gained_active,
+ aura::Window* lost_active) OVERRIDE;
+
+ // Overridden from DisplayController::Observer
+ virtual void OnDisplayConfigurationChanged() OVERRIDE;
+
+ // Overridden from ShelfLayoutManagerObserver
+ virtual void WillChangeVisibilityState(
+ ShelfVisibilityState new_state) OVERRIDE;
+
+ private:
+ friend class PanelLayoutManagerTest;
+ friend class PanelWindowResizerTest;
+ friend class DockedWindowResizerTest;
+ friend class DockedWindowLayoutManagerTest;
+
+ views::Widget* CreateCalloutWidget();
+
+ struct PanelInfo{
+ PanelInfo() : window(NULL), callout_widget(NULL), slide_in(false) {}
+
+ bool operator==(const aura::Window* other_window) const {
+ return window == other_window;
+ }
+
+ // A weak pointer to the panel window.
+ aura::Window* window;
+ // The callout widget for this panel. This pointer must be managed
+ // manually as this structure is used in a std::list. See
+ // http://www.chromium.org/developers/smart-pointer-guidelines
+ PanelCalloutWidget* callout_widget;
+
+ // True on new and restored panel windows until the panel has been
+ // positioned. The first time Relayout is called the panel will slide into
+ // position and this will be set to false.
+ bool slide_in;
+ };
+
+ typedef std::list<PanelInfo> PanelList;
+
+ void MinimizePanel(aura::Window* panel);
+ void RestorePanel(aura::Window* panel);
+
+ // Called whenever the panel layout might change.
+ void Relayout();
+
+ // Called whenever the panel stacking order needs to be updated (e.g. focus
+ // changes or a panel is moved).
+ void UpdateStacking(aura::Window* active_panel);
+
+ // Update the callout arrows for all managed panels.
+ void UpdateCallouts();
+
+ // Overridden from keyboard::KeyboardControllerObserver:
+ virtual void OnKeyboardBoundsChanging(
+ const gfx::Rect& keyboard_bounds) OVERRIDE;
+
+ // Parent window associated with this layout manager.
+ aura::Window* panel_container_;
+ // Protect against recursive calls to OnWindowAddedToLayout().
+ bool in_add_window_;
+ // Protect against recursive calls to Relayout().
+ bool in_layout_;
+ // Ordered list of unowned pointers to panel windows.
+ PanelList panel_windows_;
+ // The panel being dragged.
+ aura::Window* dragged_panel_;
+ // The launcher we are observing for launcher icon changes.
+ Launcher* launcher_;
+ // The shelf layout manager being observed for visibility changes.
+ ShelfLayoutManager* shelf_layout_manager_;
+ // Tracks the visibility of the shelf. Defaults to false when there is no
+ // shelf.
+ bool shelf_hidden_;
+ // The last active panel. Used to maintain stacking order even if no panels
+ // are currently focused.
+ aura::Window* last_active_panel_;
+ base::WeakPtrFactory<PanelLayoutManager> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(PanelLayoutManager);
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_WM_PANELS_PANEL_LAYOUT_MANAGER_H_
diff --git a/chromium/ash/wm/panels/panel_layout_manager_unittest.cc b/chromium/ash/wm/panels/panel_layout_manager_unittest.cc
new file mode 100644
index 00000000000..08f53f7aa8b
--- /dev/null
+++ b/chromium/ash/wm/panels/panel_layout_manager_unittest.cc
@@ -0,0 +1,785 @@
+// Copyright (c) 2012 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.
+
+#include "ash/wm/panels/panel_layout_manager.h"
+
+#include "ash/ash_switches.h"
+#include "ash/launcher/launcher.h"
+#include "ash/launcher/launcher_button.h"
+#include "ash/launcher/launcher_model.h"
+#include "ash/launcher/launcher_view.h"
+#include "ash/root_window_controller.h"
+#include "ash/screen_ash.h"
+#include "ash/shelf/shelf_layout_manager.h"
+#include "ash/shelf/shelf_types.h"
+#include "ash/shelf/shelf_widget.h"
+#include "ash/shell.h"
+#include "ash/shell_window_ids.h"
+#include "ash/test/ash_test_base.h"
+#include "ash/test/launcher_view_test_api.h"
+#include "ash/test/shell_test_api.h"
+#include "ash/test/test_launcher_delegate.h"
+#include "ash/wm/window_util.h"
+#include "base/basictypes.h"
+#include "base/command_line.h"
+#include "base/compiler_specific.h"
+#include "base/i18n/rtl.h"
+#include "base/run_loop.h"
+#include "ui/aura/client/aura_constants.h"
+#include "ui/aura/root_window.h"
+#include "ui/aura/test/event_generator.h"
+#include "ui/aura/test/test_windows.h"
+#include "ui/aura/window.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/views/corewm/corewm_switches.h"
+#include "ui/views/widget/widget.h"
+
+namespace ash {
+namespace internal {
+
+using aura::test::WindowIsAbove;
+
+class PanelLayoutManagerTest : public test::AshTestBase {
+ public:
+ PanelLayoutManagerTest() {}
+ virtual ~PanelLayoutManagerTest() {}
+
+ virtual void SetUp() OVERRIDE {
+ test::AshTestBase::SetUp();
+ ASSERT_TRUE(test::TestLauncherDelegate::instance());
+
+ launcher_view_test_.reset(new test::LauncherViewTestAPI(
+ Launcher::ForPrimaryDisplay()->GetLauncherViewForTest()));
+ launcher_view_test_->SetAnimationDuration(1);
+ }
+
+ aura::Window* CreateNormalWindow(const gfx::Rect& bounds) {
+ return CreateTestWindowInShellWithBounds(bounds);
+ }
+
+ aura::Window* CreatePanelWindow(const gfx::Rect& bounds) {
+ aura::Window* window = CreateTestWindowInShellWithDelegateAndType(
+ NULL,
+ aura::client::WINDOW_TYPE_PANEL,
+ 0,
+ bounds);
+ test::TestLauncherDelegate* launcher_delegate =
+ test::TestLauncherDelegate::instance();
+ launcher_delegate->AddLauncherItem(window);
+ PanelLayoutManager* manager =
+ static_cast<PanelLayoutManager*>(GetPanelContainer(window)->
+ layout_manager());
+ manager->Relayout();
+ launcher_view_test()->RunMessageLoopUntilAnimationsDone();
+ return window;
+ }
+
+ aura::Window* GetPanelContainer(aura::Window* panel) {
+ return Shell::GetContainer(panel->GetRootWindow(),
+ internal::kShellWindowId_PanelContainer);
+ }
+
+ views::Widget* GetCalloutWidgetForPanel(aura::Window* panel) {
+ PanelLayoutManager* manager =
+ static_cast<PanelLayoutManager*>(GetPanelContainer(panel)->
+ layout_manager());
+ DCHECK(manager);
+ PanelLayoutManager::PanelList::iterator found = std::find(
+ manager->panel_windows_.begin(), manager->panel_windows_.end(),
+ panel);
+ DCHECK(found != manager->panel_windows_.end());
+ DCHECK(found->callout_widget);
+ return reinterpret_cast<views::Widget*>(found->callout_widget);
+ }
+
+ void PanelInScreen(aura::Window* panel) {
+ gfx::Rect panel_bounds = panel->GetBoundsInRootWindow();
+ gfx::Point root_point = gfx::Point(panel_bounds.x(), panel_bounds.y());
+ gfx::Display display = ScreenAsh::FindDisplayContainingPoint(root_point);
+
+ gfx::Rect panel_bounds_in_screen = panel->GetBoundsInScreen();
+ gfx::Point screen_bottom_right = gfx::Point(
+ panel_bounds_in_screen.right(),
+ panel_bounds_in_screen.bottom());
+ gfx::Rect display_bounds = display.bounds();
+ EXPECT_TRUE(screen_bottom_right.x() < display_bounds.width() &&
+ screen_bottom_right.y() < display_bounds.height());
+ }
+
+ void PanelsNotOverlapping(aura::Window* panel1, aura::Window* panel2) {
+ // Waits until all launcher view animations are done.
+ launcher_view_test()->RunMessageLoopUntilAnimationsDone();
+ gfx::Rect window1_bounds = panel1->GetBoundsInRootWindow();
+ gfx::Rect window2_bounds = panel2->GetBoundsInRootWindow();
+
+ EXPECT_FALSE(window1_bounds.Intersects(window2_bounds));
+ }
+
+ // TODO(dcheng): This should be const, but GetScreenBoundsOfItemIconForWindow
+ // takes a non-const Window. We can probably fix that.
+ void IsPanelAboveLauncherIcon(aura::Window* panel) {
+ // Waits until all launcher view animations are done.
+ launcher_view_test()->RunMessageLoopUntilAnimationsDone();
+
+ Launcher* launcher =
+ RootWindowController::ForLauncher(panel)->shelf()->launcher();
+ gfx::Rect icon_bounds = launcher->GetScreenBoundsOfItemIconForWindow(panel);
+ ASSERT_FALSE(icon_bounds.width() == 0 && icon_bounds.height() == 0);
+
+ gfx::Rect window_bounds = panel->GetBoundsInScreen();
+ ASSERT_LT(icon_bounds.width(), window_bounds.width());
+ ASSERT_LT(icon_bounds.height(), window_bounds.height());
+ gfx::Rect launcher_bounds = launcher->shelf_widget()->
+ GetWindowBoundsInScreen();
+ ShelfAlignment alignment = GetAlignment(panel->GetRootWindow());
+
+ if (IsHorizontal(alignment)) {
+ // The horizontal bounds of the panel window should contain the bounds of
+ // the launcher icon.
+ EXPECT_LE(window_bounds.x(), icon_bounds.x());
+ EXPECT_GE(window_bounds.right(), icon_bounds.right());
+ } else {
+ // The vertical bounds of the panel window should contain the bounds of
+ // the launcher icon.
+ EXPECT_LE(window_bounds.y(), icon_bounds.y());
+ EXPECT_GE(window_bounds.bottom(), icon_bounds.bottom());
+ }
+
+ switch (alignment) {
+ case SHELF_ALIGNMENT_BOTTOM:
+ EXPECT_EQ(launcher_bounds.y(), window_bounds.bottom());
+ break;
+ case SHELF_ALIGNMENT_LEFT:
+ EXPECT_EQ(launcher_bounds.right(), window_bounds.x());
+ break;
+ case SHELF_ALIGNMENT_RIGHT:
+ EXPECT_EQ(launcher_bounds.x(), window_bounds.right());
+ break;
+ case SHELF_ALIGNMENT_TOP:
+ EXPECT_EQ(launcher_bounds.bottom(), window_bounds.y());
+ break;
+ }
+ }
+
+ void IsCalloutAboveLauncherIcon(aura::Window* panel) {
+ // Flush the message loop, since callout updates use a delayed task.
+ base::RunLoop().RunUntilIdle();
+ views::Widget* widget = GetCalloutWidgetForPanel(panel);
+
+ Launcher* launcher =
+ RootWindowController::ForLauncher(panel)->shelf()->launcher();
+ gfx::Rect icon_bounds = launcher->GetScreenBoundsOfItemIconForWindow(panel);
+ ASSERT_FALSE(icon_bounds.IsEmpty());
+
+ gfx::Rect panel_bounds = panel->GetBoundsInScreen();
+ gfx::Rect callout_bounds = widget->GetWindowBoundsInScreen();
+ ASSERT_FALSE(icon_bounds.IsEmpty());
+
+ EXPECT_TRUE(widget->IsVisible());
+
+ ShelfAlignment alignment = GetAlignment(panel->GetRootWindow());
+ switch (alignment) {
+ case SHELF_ALIGNMENT_BOTTOM:
+ EXPECT_EQ(panel_bounds.bottom(), callout_bounds.y());
+ break;
+ case SHELF_ALIGNMENT_LEFT:
+ EXPECT_EQ(panel_bounds.x(), callout_bounds.right());
+ break;
+ case SHELF_ALIGNMENT_RIGHT:
+ EXPECT_EQ(panel_bounds.right(), callout_bounds.x());
+ break;
+ case SHELF_ALIGNMENT_TOP:
+ EXPECT_EQ(panel_bounds.y(), callout_bounds.bottom());
+ break;
+ }
+
+ if (IsHorizontal(alignment)) {
+ EXPECT_NEAR(icon_bounds.CenterPoint().x(),
+ widget->GetWindowBoundsInScreen().CenterPoint().x(),
+ 1);
+ } else {
+ EXPECT_NEAR(icon_bounds.CenterPoint().y(),
+ widget->GetWindowBoundsInScreen().CenterPoint().y(),
+ 1);
+ }
+ }
+
+ bool IsPanelCalloutVisible(aura::Window* panel) {
+ views::Widget* widget = GetCalloutWidgetForPanel(panel);
+ return widget->IsVisible();
+ }
+
+ test::LauncherViewTestAPI* launcher_view_test() {
+ return launcher_view_test_.get();
+ }
+
+ // Clicks the launcher items on |launcher_view| that is
+ /// associated with given |window|.
+ void ClickLauncherItemForWindow(LauncherView* launcher_view,
+ aura::Window* window) {
+ test::LauncherViewTestAPI test_api(launcher_view);
+ test_api.SetAnimationDuration(1);
+ test_api.RunMessageLoopUntilAnimationsDone();
+ LauncherModel* model =
+ test::ShellTestApi(Shell::GetInstance()).launcher_model();
+ test::TestLauncherDelegate* launcher_delegate =
+ test::TestLauncherDelegate::instance();
+ int index = model->ItemIndexByID(launcher_delegate->GetIDByWindow(window));
+ gfx::Rect bounds = test_api.GetButton(index)->GetBoundsInScreen();
+
+ aura::test::EventGenerator& event_generator = GetEventGenerator();
+ event_generator.MoveMouseTo(bounds.CenterPoint());
+ event_generator.ClickLeftButton();
+
+ test_api.RunMessageLoopUntilAnimationsDone();
+ }
+
+ void SetAlignment(aura::RootWindow* root_window, ShelfAlignment alignment) {
+ ash::Shell* shell = ash::Shell::GetInstance();
+ shell->SetShelfAlignment(alignment, root_window);
+ }
+
+ ShelfAlignment GetAlignment(aura::RootWindow* root_window) {
+ ash::Shell* shell = ash::Shell::GetInstance();
+ return shell->GetShelfAlignment(root_window);
+ }
+
+ void SetShelfAutoHideBehavior(aura::Window* window,
+ ShelfAutoHideBehavior behavior) {
+ internal::ShelfLayoutManager* shelf =
+ RootWindowController::ForWindow(window)->shelf()->
+ shelf_layout_manager();
+ shelf->SetAutoHideBehavior(behavior);
+ LauncherView* launcher_view =
+ Launcher::ForWindow(window)->GetLauncherViewForTest();
+ test::LauncherViewTestAPI test_api(launcher_view);
+ test_api.RunMessageLoopUntilAnimationsDone();
+ }
+
+ void SetShelfVisibilityState(aura::Window* window,
+ ShelfVisibilityState visibility_state) {
+ internal::ShelfLayoutManager* shelf =
+ RootWindowController::ForWindow(window)->shelf()->
+ shelf_layout_manager();
+ shelf->SetState(visibility_state);
+ }
+
+ private:
+ scoped_ptr<test::LauncherViewTestAPI> launcher_view_test_;
+
+ bool IsHorizontal(ShelfAlignment alignment) {
+ return alignment == SHELF_ALIGNMENT_BOTTOM ||
+ alignment == SHELF_ALIGNMENT_TOP;
+ }
+
+ DISALLOW_COPY_AND_ASSIGN(PanelLayoutManagerTest);
+};
+
+class PanelLayoutManagerTextDirectionTest
+ : public PanelLayoutManagerTest,
+ public testing::WithParamInterface<bool> {
+ public:
+ PanelLayoutManagerTextDirectionTest() : is_rtl_(GetParam()) {}
+ virtual ~PanelLayoutManagerTextDirectionTest() {}
+
+ virtual void SetUp() OVERRIDE {
+ original_locale = l10n_util::GetApplicationLocale(std::string());
+ if (is_rtl_)
+ base::i18n::SetICUDefaultLocale("he");
+ PanelLayoutManagerTest::SetUp();
+ ASSERT_EQ(is_rtl_, base::i18n::IsRTL());
+ }
+
+ virtual void TearDown() OVERRIDE {
+ if (is_rtl_)
+ base::i18n::SetICUDefaultLocale(original_locale);
+ PanelLayoutManagerTest::TearDown();
+ }
+
+ private:
+ bool is_rtl_;
+ std::string original_locale;
+
+ DISALLOW_COPY_AND_ASSIGN(PanelLayoutManagerTextDirectionTest);
+};
+
+// Tests that a created panel window is above the launcher icon in LTR and RTL.
+TEST_P(PanelLayoutManagerTextDirectionTest, AddOnePanel) {
+ gfx::Rect bounds(0, 0, 201, 201);
+ scoped_ptr<aura::Window> window(CreatePanelWindow(bounds));
+ EXPECT_EQ(GetPanelContainer(window.get()), window->parent());
+ EXPECT_NO_FATAL_FAILURE(IsPanelAboveLauncherIcon(window.get()));
+ EXPECT_NO_FATAL_FAILURE(IsCalloutAboveLauncherIcon(window.get()));
+}
+
+// Tests that a created panel window is successfully aligned over a hidden
+// launcher icon.
+TEST_F(PanelLayoutManagerTest, PanelAlignsToHiddenLauncherIcon) {
+ gfx::Rect bounds(0, 0, 201, 201);
+ SetShelfAutoHideBehavior(Shell::GetPrimaryRootWindow(),
+ SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS);
+ scoped_ptr<aura::Window> normal_window(CreateNormalWindow(bounds));
+ scoped_ptr<aura::Window> window(CreatePanelWindow(bounds));
+ EXPECT_EQ(GetPanelContainer(window.get()), window->parent());
+ EXPECT_NO_FATAL_FAILURE(IsPanelAboveLauncherIcon(window.get()));
+}
+
+TEST_F(PanelLayoutManagerTest, PanelAlignsToHiddenLauncherIconSecondDisplay) {
+ if (!SupportsMultipleDisplays())
+ return;
+
+ // Keep the displays wide so that launchers have enough
+ // space for launcher buttons.
+ UpdateDisplay("400x400,600x400");
+ Shell::RootWindowList root_windows = Shell::GetAllRootWindows();
+
+ scoped_ptr<aura::Window> normal_window(
+ CreateNormalWindow(gfx::Rect(450, 0, 100, 100)));
+ scoped_ptr<aura::Window> panel(CreatePanelWindow(gfx::Rect(400, 0, 50, 50)));
+ EXPECT_EQ(root_windows[1], panel->GetRootWindow());
+ EXPECT_NO_FATAL_FAILURE(IsPanelAboveLauncherIcon(panel.get()));
+ gfx::Rect shelf_visible_position = panel->GetBoundsInScreen();
+
+ SetShelfAutoHideBehavior(root_windows[1],
+ SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS);
+ // Expect the panel X position to remain the same after the shelf is hidden
+ // but the Y to move down.
+ EXPECT_NO_FATAL_FAILURE(IsPanelAboveLauncherIcon(panel.get()));
+ EXPECT_EQ(shelf_visible_position.x(), panel->GetBoundsInScreen().x());
+ EXPECT_GT(panel->GetBoundsInScreen().y(), shelf_visible_position.y());
+}
+
+// Tests interactions between multiple panels
+TEST_F(PanelLayoutManagerTest, MultiplePanelsAreAboveIcons) {
+ gfx::Rect odd_bounds(0, 0, 201, 201);
+ gfx::Rect even_bounds(0, 0, 200, 200);
+
+ scoped_ptr<aura::Window> w1(CreatePanelWindow(odd_bounds));
+ EXPECT_NO_FATAL_FAILURE(IsPanelAboveLauncherIcon(w1.get()));
+
+ scoped_ptr<aura::Window> w2(CreatePanelWindow(even_bounds));
+ EXPECT_NO_FATAL_FAILURE(IsPanelAboveLauncherIcon(w1.get()));
+ EXPECT_NO_FATAL_FAILURE(IsPanelAboveLauncherIcon(w2.get()));
+
+ scoped_ptr<aura::Window> w3(CreatePanelWindow(odd_bounds));
+ EXPECT_NO_FATAL_FAILURE(IsPanelAboveLauncherIcon(w1.get()));
+ EXPECT_NO_FATAL_FAILURE(IsPanelAboveLauncherIcon(w2.get()));
+ EXPECT_NO_FATAL_FAILURE(IsPanelAboveLauncherIcon(w3.get()));
+}
+
+TEST_F(PanelLayoutManagerTest, MultiplePanelStacking) {
+ gfx::Rect bounds(0, 0, 201, 201);
+ scoped_ptr<aura::Window> w1(CreatePanelWindow(bounds));
+ scoped_ptr<aura::Window> w2(CreatePanelWindow(bounds));
+ scoped_ptr<aura::Window> w3(CreatePanelWindow(bounds));
+
+ // Default stacking order.
+ EXPECT_TRUE(WindowIsAbove(w3.get(), w2.get()));
+ EXPECT_TRUE(WindowIsAbove(w2.get(), w1.get()));
+
+ // Changing the active window should update the stacking order.
+ wm::ActivateWindow(w1.get());
+ launcher_view_test()->RunMessageLoopUntilAnimationsDone();
+ EXPECT_TRUE(WindowIsAbove(w1.get(), w2.get()));
+ EXPECT_TRUE(WindowIsAbove(w2.get(), w3.get()));
+
+ wm::ActivateWindow(w2.get());
+ launcher_view_test()->RunMessageLoopUntilAnimationsDone();
+ EXPECT_TRUE(WindowIsAbove(w1.get(), w3.get()));
+ EXPECT_TRUE(WindowIsAbove(w2.get(), w3.get()));
+ EXPECT_TRUE(WindowIsAbove(w2.get(), w1.get()));
+
+ wm::ActivateWindow(w3.get());
+ EXPECT_TRUE(WindowIsAbove(w3.get(), w2.get()));
+ EXPECT_TRUE(WindowIsAbove(w2.get(), w1.get()));
+}
+
+TEST_F(PanelLayoutManagerTest, MultiplePanelStackingVertical) {
+ // set launcher shelf to be aligned on the right
+ SetAlignment(Shell::GetPrimaryRootWindow(), SHELF_ALIGNMENT_RIGHT);
+
+ // Size panels in such a way that ordering them by X coordinate would cause
+ // stacking order to be incorrect. Test that stacking order is based on Y.
+ scoped_ptr<aura::Window> w1(CreatePanelWindow(gfx::Rect(0, 0, 210, 201)));
+ scoped_ptr<aura::Window> w2(CreatePanelWindow(gfx::Rect(0, 0, 220, 201)));
+ scoped_ptr<aura::Window> w3(CreatePanelWindow(gfx::Rect(0, 0, 200, 201)));
+
+ // Default stacking order.
+ EXPECT_TRUE(WindowIsAbove(w3.get(), w2.get()));
+ EXPECT_TRUE(WindowIsAbove(w2.get(), w1.get()));
+
+ // Changing the active window should update the stacking order.
+ wm::ActivateWindow(w1.get());
+ launcher_view_test()->RunMessageLoopUntilAnimationsDone();
+ EXPECT_TRUE(WindowIsAbove(w1.get(), w2.get()));
+ EXPECT_TRUE(WindowIsAbove(w2.get(), w3.get()));
+
+ wm::ActivateWindow(w2.get());
+ launcher_view_test()->RunMessageLoopUntilAnimationsDone();
+ EXPECT_TRUE(WindowIsAbove(w1.get(), w3.get()));
+ EXPECT_TRUE(WindowIsAbove(w2.get(), w3.get()));
+ EXPECT_TRUE(WindowIsAbove(w2.get(), w1.get()));
+
+ wm::ActivateWindow(w3.get());
+ EXPECT_TRUE(WindowIsAbove(w3.get(), w2.get()));
+ EXPECT_TRUE(WindowIsAbove(w2.get(), w1.get()));
+}
+
+TEST_F(PanelLayoutManagerTest, MultiplePanelCallout) {
+ gfx::Rect bounds(0, 0, 200, 200);
+ scoped_ptr<aura::Window> w1(CreatePanelWindow(bounds));
+ scoped_ptr<aura::Window> w2(CreatePanelWindow(bounds));
+ scoped_ptr<aura::Window> w3(CreatePanelWindow(bounds));
+ scoped_ptr<aura::Window> w4(CreateNormalWindow(gfx::Rect()));
+ launcher_view_test()->RunMessageLoopUntilAnimationsDone();
+ EXPECT_TRUE(IsPanelCalloutVisible(w1.get()));
+ EXPECT_TRUE(IsPanelCalloutVisible(w2.get()));
+ EXPECT_TRUE(IsPanelCalloutVisible(w3.get()));
+ wm::ActivateWindow(w1.get());
+ EXPECT_NO_FATAL_FAILURE(IsCalloutAboveLauncherIcon(w1.get()));
+ wm::ActivateWindow(w2.get());
+ EXPECT_NO_FATAL_FAILURE(IsCalloutAboveLauncherIcon(w2.get()));
+ wm::ActivateWindow(w3.get());
+ EXPECT_NO_FATAL_FAILURE(IsCalloutAboveLauncherIcon(w3.get()));
+ wm::ActivateWindow(w4.get());
+ wm::ActivateWindow(w3.get());
+ EXPECT_NO_FATAL_FAILURE(IsCalloutAboveLauncherIcon(w3.get()));
+ w3.reset();
+ if (views::corewm::UseFocusController())
+ EXPECT_NO_FATAL_FAILURE(IsCalloutAboveLauncherIcon(w2.get()));
+}
+
+// Tests removing panels.
+TEST_F(PanelLayoutManagerTest, RemoveLeftPanel) {
+ gfx::Rect bounds(0, 0, 201, 201);
+ scoped_ptr<aura::Window> w1(CreatePanelWindow(bounds));
+ scoped_ptr<aura::Window> w2(CreatePanelWindow(bounds));
+ scoped_ptr<aura::Window> w3(CreatePanelWindow(bounds));
+
+ // At this point, windows should be stacked with 1 < 2 < 3
+ wm::ActivateWindow(w1.get());
+ launcher_view_test()->RunMessageLoopUntilAnimationsDone();
+ // Now, windows should be stacked 1 > 2 > 3
+ w1.reset();
+ EXPECT_NO_FATAL_FAILURE(IsPanelAboveLauncherIcon(w2.get()));
+ EXPECT_NO_FATAL_FAILURE(IsPanelAboveLauncherIcon(w3.get()));
+ EXPECT_TRUE(WindowIsAbove(w2.get(), w3.get()));
+}
+
+TEST_F(PanelLayoutManagerTest, RemoveMiddlePanel) {
+ gfx::Rect bounds(0, 0, 201, 201);
+ scoped_ptr<aura::Window> w1(CreatePanelWindow(bounds));
+ scoped_ptr<aura::Window> w2(CreatePanelWindow(bounds));
+ scoped_ptr<aura::Window> w3(CreatePanelWindow(bounds));
+
+ // At this point, windows should be stacked with 1 < 2 < 3
+ wm::ActivateWindow(w2.get());
+ // Windows should be stacked 1 < 2 > 3
+ w2.reset();
+ EXPECT_NO_FATAL_FAILURE(IsPanelAboveLauncherIcon(w1.get()));
+ EXPECT_NO_FATAL_FAILURE(IsPanelAboveLauncherIcon(w3.get()));
+ EXPECT_TRUE(WindowIsAbove(w3.get(), w1.get()));
+}
+
+TEST_F(PanelLayoutManagerTest, RemoveRightPanel) {
+ gfx::Rect bounds(0, 0, 201, 201);
+ scoped_ptr<aura::Window> w1(CreatePanelWindow(bounds));
+ scoped_ptr<aura::Window> w2(CreatePanelWindow(bounds));
+ scoped_ptr<aura::Window> w3(CreatePanelWindow(bounds));
+
+ // At this point, windows should be stacked with 1 < 2 < 3
+ wm::ActivateWindow(w3.get());
+ // Order shouldn't change.
+ w3.reset();
+ EXPECT_NO_FATAL_FAILURE(IsPanelAboveLauncherIcon(w1.get()));
+ EXPECT_NO_FATAL_FAILURE(IsPanelAboveLauncherIcon(w2.get()));
+ EXPECT_TRUE(WindowIsAbove(w2.get(), w1.get()));
+}
+
+TEST_F(PanelLayoutManagerTest, RemoveNonActivePanel) {
+ gfx::Rect bounds(0, 0, 201, 201);
+ scoped_ptr<aura::Window> w1(CreatePanelWindow(bounds));
+ scoped_ptr<aura::Window> w2(CreatePanelWindow(bounds));
+ scoped_ptr<aura::Window> w3(CreatePanelWindow(bounds));
+
+ // At this point, windows should be stacked with 1 < 2 < 3
+ wm::ActivateWindow(w2.get());
+ // Windows should be stacked 1 < 2 > 3
+ w1.reset();
+ EXPECT_NO_FATAL_FAILURE(IsPanelAboveLauncherIcon(w2.get()));
+ EXPECT_NO_FATAL_FAILURE(IsPanelAboveLauncherIcon(w3.get()));
+ EXPECT_TRUE(WindowIsAbove(w2.get(), w3.get()));
+}
+
+TEST_F(PanelLayoutManagerTest, SplitView) {
+ gfx::Rect bounds(0, 0, 90, 201);
+ scoped_ptr<aura::Window> w1(CreatePanelWindow(bounds));
+ scoped_ptr<aura::Window> w2(CreatePanelWindow(bounds));
+
+ EXPECT_NO_FATAL_FAILURE(PanelsNotOverlapping(w1.get(), w2.get()));
+}
+
+#if defined(OS_WIN)
+// RootWindow and Display can't resize on Windows Ash. http://crbug.com/165962
+#define MAYBE_SplitViewOverlapWhenLarge DISABLED_SplitViewOverlapWhenLarge
+#else
+#define MAYBE_SplitViewOverlapWhenLarge SplitViewOverlapWhenLarge
+#endif
+
+TEST_F(PanelLayoutManagerTest, MAYBE_SplitViewOverlapWhenLarge) {
+ gfx::Rect bounds(0, 0, 600, 201);
+ scoped_ptr<aura::Window> w1(CreatePanelWindow(bounds));
+ scoped_ptr<aura::Window> w2(CreatePanelWindow(bounds));
+
+ EXPECT_NO_FATAL_FAILURE(PanelInScreen(w1.get()));
+ EXPECT_NO_FATAL_FAILURE(PanelInScreen(w2.get()));
+}
+
+TEST_F(PanelLayoutManagerTest, FanWindows) {
+ gfx::Rect bounds(0, 0, 201, 201);
+ scoped_ptr<aura::Window> w1(CreatePanelWindow(bounds));
+ scoped_ptr<aura::Window> w2(CreatePanelWindow(bounds));
+ scoped_ptr<aura::Window> w3(CreatePanelWindow(bounds));
+
+ launcher_view_test()->RunMessageLoopUntilAnimationsDone();
+ int window_x1 = w1->GetBoundsInRootWindow().CenterPoint().x();
+ int window_x2 = w2->GetBoundsInRootWindow().CenterPoint().x();
+ int window_x3 = w3->GetBoundsInRootWindow().CenterPoint().x();
+ Launcher* launcher = Launcher::ForPrimaryDisplay();
+ int icon_x1 = launcher->GetScreenBoundsOfItemIconForWindow(w1.get()).x();
+ int icon_x2 = launcher->GetScreenBoundsOfItemIconForWindow(w2.get()).x();
+ EXPECT_EQ(window_x2 - window_x1, window_x3 - window_x2);
+ int spacing = window_x2 - window_x1;
+ EXPECT_GT(spacing, icon_x2 - icon_x1);
+}
+
+TEST_F(PanelLayoutManagerTest, FanLargeWindow) {
+ gfx::Rect small_bounds(0, 0, 201, 201);
+ gfx::Rect large_bounds(0, 0, 501, 201);
+ scoped_ptr<aura::Window> w1(CreatePanelWindow(small_bounds));
+ scoped_ptr<aura::Window> w2(CreatePanelWindow(large_bounds));
+ scoped_ptr<aura::Window> w3(CreatePanelWindow(small_bounds));
+
+ launcher_view_test()->RunMessageLoopUntilAnimationsDone();
+ int window_x1 = w1->GetBoundsInRootWindow().CenterPoint().x();
+ int window_x2 = w2->GetBoundsInRootWindow().CenterPoint().x();
+ int window_x3 = w3->GetBoundsInRootWindow().CenterPoint().x();
+ // The distances may not be equidistant with a large panel but the panels
+ // should be in the correct order with respect to their midpoints.
+ EXPECT_GT(window_x2, window_x1);
+ EXPECT_GT(window_x3, window_x2);
+}
+
+TEST_F(PanelLayoutManagerTest, MinimizeRestorePanel) {
+ gfx::Rect bounds(0, 0, 201, 201);
+ scoped_ptr<aura::Window> window(CreatePanelWindow(bounds));
+ // Activate the window, ensure callout is visible.
+ wm::ActivateWindow(window.get());
+ RunAllPendingInMessageLoop();
+ EXPECT_TRUE(IsPanelCalloutVisible(window.get()));
+ // Minimize the panel, callout should be hidden.
+ window->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MINIMIZED);
+ RunAllPendingInMessageLoop();
+ EXPECT_FALSE(IsPanelCalloutVisible(window.get()));
+ // Restore the pantel; panel should not be activated by default but callout
+ // should be visible.
+ window->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_NORMAL);
+ RunAllPendingInMessageLoop();
+ EXPECT_TRUE(IsPanelCalloutVisible(window.get()));
+ // Activate the window, ensure callout is visible.
+ wm::ActivateWindow(window.get());
+ RunAllPendingInMessageLoop();
+ EXPECT_TRUE(IsPanelCalloutVisible(window.get()));
+}
+
+TEST_F(PanelLayoutManagerTest, PanelMoveBetweenMultipleDisplays) {
+ if (!SupportsMultipleDisplays())
+ return;
+
+ // Keep the displays wide so that launchers have enough
+ // space for launcher buttons.
+ UpdateDisplay("600x400,600x400");
+ Shell::RootWindowList root_windows = Shell::GetAllRootWindows();
+
+ scoped_ptr<aura::Window> p1_d1(CreatePanelWindow(gfx::Rect(0, 0, 50, 50)));
+ scoped_ptr<aura::Window> p2_d1(CreatePanelWindow(gfx::Rect(0, 0, 50, 50)));
+ scoped_ptr<aura::Window> p1_d2(CreatePanelWindow(gfx::Rect(600, 0, 50, 50)));
+ scoped_ptr<aura::Window> p2_d2(CreatePanelWindow(gfx::Rect(600, 0, 50, 50)));
+
+ LauncherView* launcher_view_1st =
+ Launcher::ForPrimaryDisplay()->GetLauncherViewForTest();
+ LauncherView* launcher_view_2nd =
+ Launcher::ForWindow(root_windows[1])->GetLauncherViewForTest();
+
+ EXPECT_EQ(root_windows[0], p1_d1->GetRootWindow());
+ EXPECT_EQ(root_windows[0], p2_d1->GetRootWindow());
+ EXPECT_EQ(root_windows[1], p1_d2->GetRootWindow());
+ EXPECT_EQ(root_windows[1], p2_d2->GetRootWindow());
+
+ EXPECT_EQ(internal::kShellWindowId_PanelContainer, p1_d1->parent()->id());
+ EXPECT_EQ(internal::kShellWindowId_PanelContainer, p2_d1->parent()->id());
+ EXPECT_EQ(internal::kShellWindowId_PanelContainer, p1_d2->parent()->id());
+ EXPECT_EQ(internal::kShellWindowId_PanelContainer, p2_d2->parent()->id());
+
+ // Test a panel on 1st display.
+ // Clicking on the same display has no effect.
+ ClickLauncherItemForWindow(launcher_view_1st, p1_d1.get());
+ EXPECT_EQ(root_windows[0], p1_d1->GetRootWindow());
+ EXPECT_EQ(root_windows[0], p2_d1->GetRootWindow());
+ EXPECT_EQ(root_windows[1], p1_d2->GetRootWindow());
+ EXPECT_EQ(root_windows[1], p1_d2->GetRootWindow());
+ EXPECT_FALSE(root_windows[1]->GetBoundsInScreen().Contains(
+ p1_d1->GetBoundsInScreen()));
+
+ // Test if clicking on another display moves the panel to
+ // that display.
+ ClickLauncherItemForWindow(launcher_view_2nd, p1_d1.get());
+ EXPECT_EQ(root_windows[1], p1_d1->GetRootWindow());
+ EXPECT_EQ(root_windows[0], p2_d1->GetRootWindow());
+ EXPECT_EQ(root_windows[1], p1_d2->GetRootWindow());
+ EXPECT_EQ(root_windows[1], p2_d2->GetRootWindow());
+ EXPECT_TRUE(root_windows[1]->GetBoundsInScreen().Contains(
+ p1_d1->GetBoundsInScreen()));
+
+ // Test a panel on 2nd display.
+ // Clicking on the same display has no effect.
+ ClickLauncherItemForWindow(launcher_view_2nd, p1_d2.get());
+ EXPECT_EQ(root_windows[1], p1_d1->GetRootWindow());
+ EXPECT_EQ(root_windows[0], p2_d1->GetRootWindow());
+ EXPECT_EQ(root_windows[1], p1_d2->GetRootWindow());
+ EXPECT_EQ(root_windows[1], p2_d2->GetRootWindow());
+ EXPECT_TRUE(root_windows[1]->GetBoundsInScreen().Contains(
+ p1_d2->GetBoundsInScreen()));
+
+ // Test if clicking on another display moves the panel to
+ // that display.
+ ClickLauncherItemForWindow(launcher_view_1st, p1_d2.get());
+ EXPECT_EQ(root_windows[1], p1_d1->GetRootWindow());
+ EXPECT_EQ(root_windows[0], p2_d1->GetRootWindow());
+ EXPECT_EQ(root_windows[0], p1_d2->GetRootWindow());
+ EXPECT_EQ(root_windows[1], p2_d2->GetRootWindow());
+ EXPECT_TRUE(root_windows[0]->GetBoundsInScreen().Contains(
+ p1_d2->GetBoundsInScreen()));
+
+ // Test if clicking on a previously moved window moves the
+ // panel back to the original display.
+ ClickLauncherItemForWindow(launcher_view_1st, p1_d1.get());
+ EXPECT_EQ(root_windows[0], p1_d1->GetRootWindow());
+ EXPECT_EQ(root_windows[0], p2_d1->GetRootWindow());
+ EXPECT_EQ(root_windows[0], p1_d2->GetRootWindow());
+ EXPECT_EQ(root_windows[1], p2_d2->GetRootWindow());
+ EXPECT_TRUE(root_windows[0]->GetBoundsInScreen().Contains(
+ p1_d1->GetBoundsInScreen()));
+}
+
+TEST_F(PanelLayoutManagerTest, PanelAttachPositionMultipleDisplays) {
+ if (!SupportsMultipleDisplays())
+ return;
+
+ // Keep the displays wide so that launchers have enough space for launcher
+ // buttons. Use differently sized displays so the launcher is in a different
+ // position on second display.
+ UpdateDisplay("600x400,600x600");
+ Shell::RootWindowList root_windows = Shell::GetAllRootWindows();
+
+ scoped_ptr<aura::Window> p1_d1(CreatePanelWindow(gfx::Rect(0, 0, 50, 50)));
+ scoped_ptr<aura::Window> p1_d2(CreatePanelWindow(gfx::Rect(600, 0, 50, 50)));
+
+ EXPECT_EQ(root_windows[0], p1_d1->GetRootWindow());
+ EXPECT_EQ(root_windows[1], p1_d2->GetRootWindow());
+
+ IsPanelAboveLauncherIcon(p1_d1.get());
+ IsCalloutAboveLauncherIcon(p1_d1.get());
+ IsPanelAboveLauncherIcon(p1_d2.get());
+ IsCalloutAboveLauncherIcon(p1_d2.get());
+}
+
+TEST_F(PanelLayoutManagerTest, PanelAlignmentSecondDisplay) {
+ if (!SupportsMultipleDisplays())
+ return;
+
+ UpdateDisplay("600x400,600x400");
+ Shell::RootWindowList root_windows = Shell::GetAllRootWindows();
+
+ scoped_ptr<aura::Window> p1_d2(CreatePanelWindow(gfx::Rect(600, 0, 50, 50)));
+ EXPECT_EQ(root_windows[1], p1_d2->GetRootWindow());
+
+ IsPanelAboveLauncherIcon(p1_d2.get());
+ IsCalloutAboveLauncherIcon(p1_d2.get());
+
+ SetAlignment(root_windows[1], SHELF_ALIGNMENT_RIGHT);
+ IsPanelAboveLauncherIcon(p1_d2.get());
+ IsCalloutAboveLauncherIcon(p1_d2.get());
+ SetAlignment(root_windows[1], SHELF_ALIGNMENT_LEFT);
+ IsPanelAboveLauncherIcon(p1_d2.get());
+ IsCalloutAboveLauncherIcon(p1_d2.get());
+ SetAlignment(root_windows[1], SHELF_ALIGNMENT_TOP);
+ IsPanelAboveLauncherIcon(p1_d2.get());
+ IsCalloutAboveLauncherIcon(p1_d2.get());
+}
+
+TEST_F(PanelLayoutManagerTest, AlignmentLeft) {
+ gfx::Rect bounds(0, 0, 201, 201);
+ scoped_ptr<aura::Window> w(CreatePanelWindow(bounds));
+ SetAlignment(Shell::GetPrimaryRootWindow(), SHELF_ALIGNMENT_LEFT);
+ IsPanelAboveLauncherIcon(w.get());
+ IsCalloutAboveLauncherIcon(w.get());
+}
+
+TEST_F(PanelLayoutManagerTest, AlignmentRight) {
+ gfx::Rect bounds(0, 0, 201, 201);
+ scoped_ptr<aura::Window> w(CreatePanelWindow(bounds));
+ SetAlignment(Shell::GetPrimaryRootWindow(), SHELF_ALIGNMENT_RIGHT);
+ IsPanelAboveLauncherIcon(w.get());
+ IsCalloutAboveLauncherIcon(w.get());
+}
+
+TEST_F(PanelLayoutManagerTest, AlignmentTop) {
+ gfx::Rect bounds(0, 0, 201, 201);
+ scoped_ptr<aura::Window> w(CreatePanelWindow(bounds));
+ SetAlignment(Shell::GetPrimaryRootWindow(), SHELF_ALIGNMENT_TOP);
+ IsPanelAboveLauncherIcon(w.get());
+ IsCalloutAboveLauncherIcon(w.get());
+}
+
+// Tests that panels will hide and restore their state with the shelf visibility
+// state. This ensures that entering full-screen mode will hide your panels
+// until you leave it.
+TEST_F(PanelLayoutManagerTest, PanelsHideAndRestoreWithShelf) {
+ gfx::Rect bounds(0, 0, 201, 201);
+
+ scoped_ptr<aura::Window> w1(CreatePanelWindow(bounds));
+ scoped_ptr<aura::Window> w2(CreatePanelWindow(bounds));
+ scoped_ptr<aura::Window> w3;
+ // Minimize w2.
+ w2->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MINIMIZED);
+ RunAllPendingInMessageLoop();
+ EXPECT_TRUE(w1->IsVisible());
+ EXPECT_FALSE(w2->IsVisible());
+
+ SetShelfVisibilityState(Shell::GetPrimaryRootWindow(), SHELF_HIDDEN);
+ RunAllPendingInMessageLoop();
+
+ // w3 is created while in full-screen mode, should only become visible when
+ // we exit fullscreen mode.
+ w3.reset(CreatePanelWindow(bounds));
+
+ EXPECT_FALSE(w1->IsVisible());
+ EXPECT_FALSE(w2->IsVisible());
+ EXPECT_FALSE(w3->IsVisible());
+
+ SetShelfVisibilityState(Shell::GetPrimaryRootWindow(), SHELF_VISIBLE);
+ RunAllPendingInMessageLoop();
+
+ // Windows should be restored to their prior state.
+ EXPECT_TRUE(w1->IsVisible());
+ EXPECT_FALSE(w2->IsVisible());
+ EXPECT_TRUE(w3->IsVisible());
+}
+
+INSTANTIATE_TEST_CASE_P(LtrRtl, PanelLayoutManagerTextDirectionTest,
+ testing::Bool());
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/wm/panels/panel_window_event_handler.cc b/chromium/ash/wm/panels/panel_window_event_handler.cc
new file mode 100644
index 00000000000..7f00e554052
--- /dev/null
+++ b/chromium/ash/wm/panels/panel_window_event_handler.cc
@@ -0,0 +1,50 @@
+// Copyright 2013 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.
+
+#include "ash/wm/panels/panel_window_event_handler.h"
+
+#include "ui/aura/client/aura_constants.h"
+#include "ui/aura/window.h"
+#include "ui/aura/window_delegate.h"
+#include "ui/base/events/event.h"
+#include "ui/base/hit_test.h"
+
+namespace ash {
+namespace internal {
+
+PanelWindowEventHandler::PanelWindowEventHandler(aura::Window* owner)
+ : ToplevelWindowEventHandler(owner) {
+}
+
+PanelWindowEventHandler::~PanelWindowEventHandler() {
+}
+
+void PanelWindowEventHandler::OnMouseEvent(ui::MouseEvent* event) {
+ aura::Window* target = static_cast<aura::Window*>(event->target());
+ if (event->type() == ui::ET_MOUSE_PRESSED &&
+ event->flags() & ui::EF_IS_DOUBLE_CLICK &&
+ event->IsOnlyLeftMouseButton() &&
+ target->delegate()->GetNonClientComponent(event->location()) ==
+ HTCAPTION) {
+ target->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MINIMIZED);
+ return;
+ }
+ ToplevelWindowEventHandler::OnMouseEvent(event);
+}
+
+void PanelWindowEventHandler::OnGestureEvent(ui::GestureEvent* event) {
+ aura::Window* target = static_cast<aura::Window*>(event->target());
+ if (event->type() == ui::ET_GESTURE_TAP &&
+ event->details().tap_count() == 2 &&
+ target->delegate()->GetNonClientComponent(event->location()) ==
+ HTCAPTION) {
+ target->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MINIMIZED);
+ event->StopPropagation();
+ return;
+ }
+ ToplevelWindowEventHandler::OnGestureEvent(event);
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/wm/panels/panel_window_event_handler.h b/chromium/ash/wm/panels/panel_window_event_handler.h
new file mode 100644
index 00000000000..2823b885cc7
--- /dev/null
+++ b/chromium/ash/wm/panels/panel_window_event_handler.h
@@ -0,0 +1,35 @@
+// Copyright 2013 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 ASH_WM_PANELS_PANEL_WINDOW_EVENT_HANDLER_H_
+#define ASH_WM_PANELS_PANEL_WINDOW_EVENT_HANDLER_H_
+
+#include "ash/wm/toplevel_window_event_handler.h"
+
+namespace aura {
+class Window;
+}
+
+namespace ash {
+namespace internal {
+
+// PanelWindowEventHandler minimizes panels when the user double clicks or
+// double taps on the panel header.
+class PanelWindowEventHandler : public ToplevelWindowEventHandler {
+ public:
+ explicit PanelWindowEventHandler(aura::Window* owner);
+ virtual ~PanelWindowEventHandler();
+
+ // TopLevelWindowEventHandler:
+ virtual void OnMouseEvent(ui::MouseEvent* event) OVERRIDE;
+ virtual void OnGestureEvent(ui::GestureEvent* event) OVERRIDE;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(PanelWindowEventHandler);
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_WM_PANELS_PANEL_WINDOW_EVENT_HANDLER_H_
diff --git a/chromium/ash/wm/panels/panel_window_resizer.cc b/chromium/ash/wm/panels/panel_window_resizer.cc
new file mode 100644
index 00000000000..cb151b8bae0
--- /dev/null
+++ b/chromium/ash/wm/panels/panel_window_resizer.cc
@@ -0,0 +1,238 @@
+// Copyright (c) 2012 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.
+
+#include "ash/wm/panels/panel_window_resizer.h"
+
+#include "ash/display/display_controller.h"
+#include "ash/launcher/launcher.h"
+#include "ash/screen_ash.h"
+#include "ash/shelf/shelf_types.h"
+#include "ash/shelf/shelf_widget.h"
+#include "ash/shell.h"
+#include "ash/shell_window_ids.h"
+#include "ash/wm/coordinate_conversion.h"
+#include "ash/wm/panels/panel_layout_manager.h"
+#include "ash/wm/property_util.h"
+#include "ash/wm/window_properties.h"
+#include "ui/aura/client/aura_constants.h"
+#include "ui/aura/env.h"
+#include "ui/aura/root_window.h"
+#include "ui/aura/window.h"
+#include "ui/aura/window_delegate.h"
+#include "ui/base/hit_test.h"
+#include "ui/base/ui_base_types.h"
+#include "ui/gfx/screen.h"
+#include "ui/views/widget/widget.h"
+
+namespace ash {
+
+namespace {
+const int kPanelSnapToLauncherDistance = 30;
+
+internal::PanelLayoutManager* GetPanelLayoutManager(
+ aura::Window* panel_container) {
+ DCHECK(panel_container->id() == internal::kShellWindowId_PanelContainer);
+ return static_cast<internal::PanelLayoutManager*>(
+ panel_container->layout_manager());
+}
+
+} // namespace
+
+PanelWindowResizer::~PanelWindowResizer() {
+ if (destroyed_)
+ *destroyed_ = true;
+}
+
+// static
+PanelWindowResizer*
+PanelWindowResizer::Create(WindowResizer* next_window_resizer,
+ aura::Window* window,
+ const gfx::Point& location,
+ int window_component,
+ aura::client::WindowMoveSource source) {
+ Details details(window, location, window_component, source);
+ return details.is_resizable ?
+ new PanelWindowResizer(next_window_resizer, details) : NULL;
+}
+
+void PanelWindowResizer::Drag(const gfx::Point& location, int event_flags) {
+ last_location_ = location;
+ wm::ConvertPointToScreen(GetTarget()->parent(), &last_location_);
+ bool destroyed = false;
+ if (!did_move_or_resize_) {
+ did_move_or_resize_ = true;
+ StartedDragging();
+ }
+
+ // Check if the destination has changed displays.
+ gfx::Screen* screen = Shell::GetScreen();
+ const gfx::Display dst_display =
+ screen->GetDisplayNearestPoint(last_location_);
+ if (dst_display.id() !=
+ screen->GetDisplayNearestWindow(panel_container_->GetRootWindow()).id()) {
+ // The panel is being dragged to a new display. If the previous container is
+ // the current parent of the panel it will be informed of the end of drag
+ // when the panel is reparented, otherwise let the previous container know
+ // the drag is complete. If we told the panel's parent that the drag was
+ // complete it would begin positioning the panel.
+ if (GetTarget()->parent() != panel_container_)
+ GetPanelLayoutManager(panel_container_)->FinishDragging();
+ aura::RootWindow* dst_root = Shell::GetInstance()->display_controller()->
+ GetRootWindowForDisplayId(dst_display.id());
+ panel_container_ = Shell::GetContainer(
+ dst_root, internal::kShellWindowId_PanelContainer);
+
+ // The panel's parent already knows that the drag is in progress for this
+ // panel.
+ if (panel_container_ && GetTarget()->parent() != panel_container_)
+ GetPanelLayoutManager(panel_container_)->StartDragging(GetTarget());
+ }
+ gfx::Point offset;
+ gfx::Rect bounds(CalculateBoundsForDrag(details_, location));
+ should_attach_ = AttachToLauncher(bounds, &offset);
+ gfx::Point modified_location(location.x() + offset.x(),
+ location.y() + offset.y());
+ destroyed_ = &destroyed;
+ next_window_resizer_->Drag(modified_location, event_flags);
+
+ // TODO(flackr): Refactor the way WindowResizer calls into other window
+ // resizers to avoid the awkward pattern here for checking if
+ // next_window_resizer_ destroys the resizer object.
+ if (destroyed)
+ return;
+ destroyed_ = NULL;
+ if (should_attach_ &&
+ !(details_.bounds_change & WindowResizer::kBoundsChange_Resizes)) {
+ UpdateLauncherPosition();
+ }
+}
+
+void PanelWindowResizer::CompleteDrag(int event_flags) {
+ // The root window can change when dragging into a different screen.
+ next_window_resizer_->CompleteDrag(event_flags);
+ FinishDragging();
+}
+
+void PanelWindowResizer::RevertDrag() {
+ next_window_resizer_->RevertDrag();
+ should_attach_ = was_attached_;
+ FinishDragging();
+}
+
+aura::Window* PanelWindowResizer::GetTarget() {
+ return next_window_resizer_->GetTarget();
+}
+
+const gfx::Point& PanelWindowResizer::GetInitialLocation() const {
+ return details_.initial_location_in_parent;
+}
+
+PanelWindowResizer::PanelWindowResizer(WindowResizer* next_window_resizer,
+ const Details& details)
+ : details_(details),
+ next_window_resizer_(next_window_resizer),
+ panel_container_(NULL),
+ initial_panel_container_(NULL),
+ did_move_or_resize_(false),
+ was_attached_(GetTarget()->GetProperty(internal::kPanelAttachedKey)),
+ should_attach_(was_attached_),
+ destroyed_(NULL) {
+ DCHECK(details_.is_resizable);
+ panel_container_ = Shell::GetContainer(
+ details.window->GetRootWindow(),
+ internal::kShellWindowId_PanelContainer);
+ initial_panel_container_ = panel_container_;
+}
+
+bool PanelWindowResizer::AttachToLauncher(const gfx::Rect& bounds,
+ gfx::Point* offset) {
+ bool should_attach = false;
+ if (panel_container_) {
+ internal::PanelLayoutManager* panel_layout_manager =
+ GetPanelLayoutManager(panel_container_);
+ gfx::Rect launcher_bounds = ScreenAsh::ConvertRectFromScreen(
+ GetTarget()->parent(),
+ panel_layout_manager->launcher()->
+ shelf_widget()->GetWindowBoundsInScreen());
+ switch (panel_layout_manager->launcher()->alignment()) {
+ case SHELF_ALIGNMENT_BOTTOM:
+ if (bounds.bottom() >= (launcher_bounds.y() -
+ kPanelSnapToLauncherDistance)) {
+ should_attach = true;
+ offset->set_y(launcher_bounds.y() - bounds.height() - bounds.y());
+ }
+ break;
+ case SHELF_ALIGNMENT_LEFT:
+ if (bounds.x() <= (launcher_bounds.right() +
+ kPanelSnapToLauncherDistance)) {
+ should_attach = true;
+ offset->set_x(launcher_bounds.right() - bounds.x());
+ }
+ break;
+ case SHELF_ALIGNMENT_RIGHT:
+ if (bounds.right() >= (launcher_bounds.x() -
+ kPanelSnapToLauncherDistance)) {
+ should_attach = true;
+ offset->set_x(launcher_bounds.x() - bounds.width() - bounds.x());
+ }
+ break;
+ case SHELF_ALIGNMENT_TOP:
+ if (bounds.y() <= (launcher_bounds.bottom() +
+ kPanelSnapToLauncherDistance)) {
+ should_attach = true;
+ offset->set_y(launcher_bounds.bottom() - bounds.y());
+ }
+ break;
+ }
+ }
+ return should_attach;
+}
+
+void PanelWindowResizer::StartedDragging() {
+ // Tell the panel layout manager that we are dragging this panel before
+ // attaching it so that it does not get repositioned.
+ if (panel_container_)
+ GetPanelLayoutManager(panel_container_)->StartDragging(GetTarget());
+ if (!was_attached_) {
+ // Attach the panel while dragging placing it in front of other panels.
+ GetTarget()->SetProperty(internal::kContinueDragAfterReparent, true);
+ GetTarget()->SetProperty(internal::kPanelAttachedKey, true);
+ // We use root window coordinates to ensure that during the drag the panel
+ // is reparented to a container in the root window that has that window.
+ GetTarget()->SetDefaultParentByRootWindow(
+ GetTarget()->GetRootWindow(),
+ GetTarget()->GetRootWindow()->GetBoundsInScreen());
+ }
+}
+
+void PanelWindowResizer::FinishDragging() {
+ if (!did_move_or_resize_)
+ return;
+ if (GetTarget()->GetProperty(internal::kPanelAttachedKey) !=
+ should_attach_) {
+ GetTarget()->SetProperty(internal::kPanelAttachedKey, should_attach_);
+ // We use last known location to ensure that after the drag the panel
+ // is reparented to a container in the root window that has that location.
+ GetTarget()->SetDefaultParentByRootWindow(
+ GetTarget()->GetRootWindow(),
+ gfx::Rect(last_location_, gfx::Size()));
+ }
+
+ // If we started the drag in one root window and moved into another root
+ // but then canceled the drag we may need to inform the original layout
+ // manager that the drag is finished.
+ if (initial_panel_container_ != panel_container_)
+ GetPanelLayoutManager(initial_panel_container_)->FinishDragging();
+ if (panel_container_)
+ GetPanelLayoutManager(panel_container_)->FinishDragging();
+}
+
+void PanelWindowResizer::UpdateLauncherPosition() {
+ if (panel_container_) {
+ GetPanelLayoutManager(panel_container_)->launcher()->
+ UpdateIconPositionForWindow(GetTarget());
+ }
+}
+
+} // namespace aura
diff --git a/chromium/ash/wm/panels/panel_window_resizer.h b/chromium/ash/wm/panels/panel_window_resizer.h
new file mode 100644
index 00000000000..56a38f54be7
--- /dev/null
+++ b/chromium/ash/wm/panels/panel_window_resizer.h
@@ -0,0 +1,95 @@
+// Copyright (c) 2012 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 ASH_WM_PANELS_PANEL_WINDOW_RESIZER_H_
+#define ASH_WM_PANELS_PANEL_WINDOW_RESIZER_H_
+
+#include "ash/wm/window_resizer.h"
+#include "base/compiler_specific.h"
+
+namespace gfx {
+class Rect;
+class Point;
+}
+
+namespace ash {
+
+// PanelWindowResizer is used by ToplevelWindowEventFilter to handle dragging,
+// moving or resizing panel window. These can be attached and detached from the
+// launcher.
+class ASH_EXPORT PanelWindowResizer : public WindowResizer {
+ public:
+ virtual ~PanelWindowResizer();
+
+ // Creates a new PanelWindowResizer. The caller takes ownership of the
+ // returned object. The ownership of |next_window_resizer| is taken by the
+ // returned object. Returns NULL if not resizable.
+ static PanelWindowResizer* Create(WindowResizer* next_window_resizer,
+ aura::Window* window,
+ const gfx::Point& location,
+ int window_component,
+ aura::client::WindowMoveSource source);
+
+ // WindowResizer:
+ virtual void Drag(const gfx::Point& location, int event_flags) OVERRIDE;
+ virtual void CompleteDrag(int event_flags) OVERRIDE;
+ virtual void RevertDrag() OVERRIDE;
+ virtual aura::Window* GetTarget() OVERRIDE;
+ virtual const gfx::Point& GetInitialLocation() const OVERRIDE;
+
+ private:
+ // Creates PanelWindowResizer that adds the ability to attach / detach panel
+ // windows as well as reparenting them to the panel layer while dragging to
+ // |next_window_resizer|. This object takes ownership of
+ // |next_window_resizer|.
+ PanelWindowResizer(WindowResizer* next_window_resizer,
+ const Details& details);
+
+ // Checks if the provided window bounds should attach to the launcher. If true
+ // the offset gives the necessary adjustment to snap to the launcher.
+ bool AttachToLauncher(const gfx::Rect& bounds, gfx::Point* offset);
+
+ // Tracks the panel's initial position and attachment at the start of a drag
+ // and informs the PanelLayoutManager that a drag has started if necessary.
+ void StartedDragging();
+
+ // Informs the PanelLayoutManager that the drag is complete if it was informed
+ // of the drag start.
+ void FinishDragging();
+
+ // Updates the dragged panel's index in the launcher.
+ void UpdateLauncherPosition();
+
+ const Details details_;
+
+ // Last pointer location in screen coordinates.
+ gfx::Point last_location_;
+
+ // Wraps a window resizer and adds panel detaching / reattaching and snapping
+ // to launcher behavior during drags.
+ scoped_ptr<WindowResizer> next_window_resizer_;
+
+ // Panel container window.
+ aura::Window* panel_container_;
+ aura::Window* initial_panel_container_;
+
+ // Set to true once Drag() is invoked and the bounds of the window change.
+ bool did_move_or_resize_;
+
+ // True if the window started attached to the launcher.
+ const bool was_attached_;
+
+ // True if the window should attach to the launcher after releasing.
+ bool should_attach_;
+
+ // If non-NULL the destructor sets this to true. Used to determine if this has
+ // been deleted.
+ bool* destroyed_;
+
+ DISALLOW_COPY_AND_ASSIGN(PanelWindowResizer);
+};
+
+} // namespace ash
+
+#endif // ASH_WM_PANELS_PANEL_WINDOW_RESIZER_H_
diff --git a/chromium/ash/wm/panels/panel_window_resizer_unittest.cc b/chromium/ash/wm/panels/panel_window_resizer_unittest.cc
new file mode 100644
index 00000000000..1f1bdf53c3f
--- /dev/null
+++ b/chromium/ash/wm/panels/panel_window_resizer_unittest.cc
@@ -0,0 +1,457 @@
+// Copyright (c) 2013 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.
+
+#include "ash/wm/panels/panel_window_resizer.h"
+
+#include "ash/launcher/launcher.h"
+#include "ash/launcher/launcher_model.h"
+#include "ash/root_window_controller.h"
+#include "ash/shelf/shelf_layout_manager.h"
+#include "ash/shelf/shelf_types.h"
+#include "ash/shelf/shelf_widget.h"
+#include "ash/shell.h"
+#include "ash/shell_window_ids.h"
+#include "ash/test/ash_test_base.h"
+#include "ash/test/cursor_manager_test_api.h"
+#include "ash/test/shell_test_api.h"
+#include "ash/test/test_launcher_delegate.h"
+#include "ash/wm/drag_window_resizer.h"
+#include "ash/wm/panels/panel_layout_manager.h"
+#include "ash/wm/window_properties.h"
+#include "ui/aura/client/aura_constants.h"
+#include "ui/aura/root_window.h"
+#include "ui/base/hit_test.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/base/ui_base_types.h"
+#include "ui/views/widget/widget.h"
+
+namespace ash {
+namespace internal {
+
+class PanelWindowResizerTest : public test::AshTestBase {
+ public:
+ PanelWindowResizerTest() {}
+ virtual ~PanelWindowResizerTest() {}
+
+ virtual void SetUp() OVERRIDE {
+ AshTestBase::SetUp();
+ UpdateDisplay("600x400");
+ test::ShellTestApi test_api(Shell::GetInstance());
+ model_ = test_api.launcher_model();
+ launcher_delegate_ = test::TestLauncherDelegate::instance();
+ }
+
+ virtual void TearDown() OVERRIDE {
+ AshTestBase::TearDown();
+ }
+
+ protected:
+ gfx::Point CalculateDragPoint(const WindowResizer& resizer,
+ int delta_x,
+ int delta_y) const {
+ gfx::Point location = resizer.GetInitialLocation();
+ location.set_x(location.x() + delta_x);
+ location.set_y(location.y() + delta_y);
+ return location;
+ }
+
+ aura::Window* CreatePanelWindow(const gfx::Rect& bounds) {
+ aura::Window* window = CreateTestWindowInShellWithDelegateAndType(
+ NULL,
+ aura::client::WINDOW_TYPE_PANEL,
+ 0,
+ bounds);
+ launcher_delegate_->AddLauncherItem(window);
+ PanelLayoutManager* manager =
+ static_cast<PanelLayoutManager*>(
+ Shell::GetContainer(window->GetRootWindow(),
+ internal::kShellWindowId_PanelContainer)->
+ layout_manager());
+ manager->Relayout();
+ return window;
+ }
+
+ void DragStart(aura::Window* window) {
+ resizer_.reset(CreateWindowResizer(
+ window,
+ window->bounds().origin(),
+ HTCAPTION,
+ aura::client::WINDOW_MOVE_SOURCE_MOUSE).release());
+ ASSERT_TRUE(resizer_.get());
+ }
+
+ void DragMove(int dx, int dy) {
+ resizer_->Drag(CalculateDragPoint(*resizer_, dx, dy), 0);
+ }
+
+ void DragEnd() {
+ resizer_->CompleteDrag(0);
+ resizer_.reset();
+ }
+
+ void DragRevert() {
+ resizer_->RevertDrag();
+ resizer_.reset();
+ }
+
+ // Test dragging the panel slightly, then detaching, and then reattaching
+ // dragging out by the vector (dx, dy).
+ void DetachReattachTest(aura::Window* window, int dx, int dy) {
+ EXPECT_TRUE(window->GetProperty(kPanelAttachedKey));
+ aura::RootWindow* root_window = window->GetRootWindow();
+ EXPECT_EQ(internal::kShellWindowId_PanelContainer, window->parent()->id());
+ DragStart(window);
+ gfx::Rect initial_bounds = window->GetBoundsInScreen();
+
+ // Drag the panel slightly. The window should still be snapped to the
+ // launcher.
+ DragMove(dx * 5, dy * 5);
+ EXPECT_EQ(initial_bounds.x(), window->GetBoundsInScreen().x());
+ EXPECT_EQ(initial_bounds.y(), window->GetBoundsInScreen().y());
+
+ // Drag further out and the window should now move to the cursor.
+ DragMove(dx * 100, dy * 100);
+ EXPECT_EQ(initial_bounds.x() + dx * 100, window->GetBoundsInScreen().x());
+ EXPECT_EQ(initial_bounds.y() + dy * 100, window->GetBoundsInScreen().y());
+
+ // The panel should be detached when the drag completes.
+ DragEnd();
+
+ EXPECT_FALSE(window->GetProperty(kPanelAttachedKey));
+ EXPECT_EQ(internal::kShellWindowId_DefaultContainer,
+ window->parent()->id());
+ EXPECT_EQ(root_window, window->GetRootWindow());
+
+ DragStart(window);
+ // Drag the panel down.
+ DragMove(dx * -95, dy * -95);
+ // Release the mouse and the panel should be reattached.
+ DragEnd();
+
+ // The panel should be reattached and have snapped to the launcher.
+ EXPECT_TRUE(window->GetProperty(kPanelAttachedKey));
+ EXPECT_EQ(initial_bounds.x(), window->GetBoundsInScreen().x());
+ EXPECT_EQ(initial_bounds.y(), window->GetBoundsInScreen().y());
+ EXPECT_EQ(internal::kShellWindowId_PanelContainer, window->parent()->id());
+ }
+
+ void TestWindowOrder(const std::vector<aura::Window*>& window_order) {
+ int panel_index = model_->FirstPanelIndex();
+ EXPECT_EQ((int)(panel_index + window_order.size()), model_->item_count());
+ for (std::vector<aura::Window*>::const_iterator iter =
+ window_order.begin(); iter != window_order.end();
+ ++iter, ++panel_index) {
+ LauncherID id = launcher_delegate_->GetIDByWindow(*iter);
+ EXPECT_EQ(id, model_->items()[panel_index].id);
+ }
+ }
+
+ // Test dragging panel window along the shelf and verify that panel icons
+ // are reordered appropriately.
+ void DragAlongShelfReorder(int dx, int dy) {
+ gfx::Rect bounds(0, 0, 201, 201);
+ scoped_ptr<aura::Window> w1(CreatePanelWindow(bounds));
+ scoped_ptr<aura::Window> w2(CreatePanelWindow(bounds));
+ std::vector<aura::Window*> window_order_original;
+ std::vector<aura::Window*> window_order_swapped;
+ window_order_original.push_back(w1.get());
+ window_order_original.push_back(w2.get());
+ window_order_swapped.push_back(w2.get());
+ window_order_swapped.push_back(w1.get());
+ TestWindowOrder(window_order_original);
+
+ // Drag window #2 to the beginning of the shelf.
+ DragStart(w2.get());
+ DragMove(400 * dx, 400 * dy);
+ TestWindowOrder(window_order_swapped);
+ DragEnd();
+
+ // Expect swapped window order.
+ TestWindowOrder(window_order_swapped);
+
+ // Drag window #2 back to the end.
+ DragStart(w2.get());
+ DragMove(-400 * dx, -400 * dy);
+ TestWindowOrder(window_order_original);
+ DragEnd();
+
+ // Expect original order.
+ TestWindowOrder(window_order_original);
+ }
+
+ private:
+ scoped_ptr<WindowResizer> resizer_;
+ internal::PanelLayoutManager* panel_layout_manager_;
+ LauncherModel* model_;
+ test::TestLauncherDelegate* launcher_delegate_;
+
+ DISALLOW_COPY_AND_ASSIGN(PanelWindowResizerTest);
+};
+
+class PanelWindowResizerTextDirectionTest
+ : public PanelWindowResizerTest,
+ public testing::WithParamInterface<bool> {
+ public:
+ PanelWindowResizerTextDirectionTest() : is_rtl_(GetParam()) {}
+ virtual ~PanelWindowResizerTextDirectionTest() {}
+
+ virtual void SetUp() OVERRIDE {
+ original_locale = l10n_util::GetApplicationLocale(std::string());
+ if (is_rtl_)
+ base::i18n::SetICUDefaultLocale("he");
+ PanelWindowResizerTest::SetUp();
+ ASSERT_EQ(is_rtl_, base::i18n::IsRTL());
+ }
+
+ virtual void TearDown() OVERRIDE {
+ if (is_rtl_)
+ base::i18n::SetICUDefaultLocale(original_locale);
+ PanelWindowResizerTest::TearDown();
+ }
+
+ private:
+ bool is_rtl_;
+ std::string original_locale;
+
+ DISALLOW_COPY_AND_ASSIGN(PanelWindowResizerTextDirectionTest);
+};
+
+// Verifies a window can be dragged from the panel and detached and then
+// reattached.
+TEST_F(PanelWindowResizerTest, PanelDetachReattachBottom) {
+ if (!SupportsHostWindowResize())
+ return;
+
+ scoped_ptr<aura::Window> window(
+ CreatePanelWindow(gfx::Rect(0, 0, 201, 201)));
+ DetachReattachTest(window.get(), 0, -1);
+}
+
+TEST_F(PanelWindowResizerTest, PanelDetachReattachLeft) {
+ if (!SupportsHostWindowResize())
+ return;
+
+ ash::Shell* shell = ash::Shell::GetInstance();
+ shell->SetShelfAlignment(SHELF_ALIGNMENT_LEFT, shell->GetPrimaryRootWindow());
+ scoped_ptr<aura::Window> window(
+ CreatePanelWindow(gfx::Rect(0, 0, 201, 201)));
+ DetachReattachTest(window.get(), 1, 0);
+}
+
+TEST_F(PanelWindowResizerTest, PanelDetachReattachRight) {
+ if (!SupportsHostWindowResize())
+ return;
+
+ ash::Shell* shell = ash::Shell::GetInstance();
+ shell->SetShelfAlignment(SHELF_ALIGNMENT_RIGHT,
+ shell->GetPrimaryRootWindow());
+ scoped_ptr<aura::Window> window(
+ CreatePanelWindow(gfx::Rect(0, 0, 201, 201)));
+ DetachReattachTest(window.get(), -1, 0);
+}
+
+TEST_F(PanelWindowResizerTest, PanelDetachReattachTop) {
+ if (!SupportsHostWindowResize())
+ return;
+
+ ash::Shell* shell = ash::Shell::GetInstance();
+ shell->SetShelfAlignment(SHELF_ALIGNMENT_TOP, shell->GetPrimaryRootWindow());
+ scoped_ptr<aura::Window> window(
+ CreatePanelWindow(gfx::Rect(0, 0, 201, 201)));
+ DetachReattachTest(window.get(), 0, 1);
+}
+
+TEST_F(PanelWindowResizerTest, PanelDetachReattachMultipleDisplays) {
+ if (!SupportsMultipleDisplays())
+ return;
+
+ UpdateDisplay("600x400,600x400");
+ Shell::RootWindowList root_windows = Shell::GetAllRootWindows();
+ scoped_ptr<aura::Window> window(
+ CreatePanelWindow(gfx::Rect(600, 0, 201, 201)));
+ EXPECT_EQ(root_windows[1], window->GetRootWindow());
+ DetachReattachTest(window.get(), 0, -1);
+}
+
+TEST_F(PanelWindowResizerTest, DetachThenDragAcrossDisplays) {
+ if (!SupportsMultipleDisplays())
+ return;
+
+ UpdateDisplay("600x400,600x400");
+ Shell::RootWindowList root_windows = Shell::GetAllRootWindows();
+ scoped_ptr<aura::Window> window(
+ CreatePanelWindow(gfx::Rect(0, 0, 201, 201)));
+ gfx::Rect initial_bounds = window->GetBoundsInScreen();
+ EXPECT_EQ(root_windows[0], window->GetRootWindow());
+ DragStart(window.get());
+ DragMove(0, -100);
+ DragEnd();
+ EXPECT_EQ(root_windows[0], window->GetRootWindow());
+ EXPECT_EQ(initial_bounds.x(), window->GetBoundsInScreen().x());
+ EXPECT_EQ(initial_bounds.y() - 100, window->GetBoundsInScreen().y());
+ EXPECT_FALSE(window->GetProperty(kPanelAttachedKey));
+ EXPECT_EQ(internal::kShellWindowId_DefaultContainer,
+ window->parent()->id());
+
+ DragStart(window.get());
+ DragMove(500, 0);
+ DragEnd();
+ EXPECT_EQ(root_windows[1], window->GetRootWindow());
+ EXPECT_EQ(initial_bounds.x() + 500, window->GetBoundsInScreen().x());
+ EXPECT_EQ(initial_bounds.y() - 100, window->GetBoundsInScreen().y());
+ EXPECT_FALSE(window->GetProperty(kPanelAttachedKey));
+ EXPECT_EQ(internal::kShellWindowId_DefaultContainer,
+ window->parent()->id());
+}
+
+TEST_F(PanelWindowResizerTest, DetachAcrossDisplays) {
+ if (!SupportsMultipleDisplays())
+ return;
+
+ UpdateDisplay("600x400,600x400");
+ Shell::RootWindowList root_windows = Shell::GetAllRootWindows();
+ scoped_ptr<aura::Window> window(
+ CreatePanelWindow(gfx::Rect(0, 0, 201, 201)));
+ gfx::Rect initial_bounds = window->GetBoundsInScreen();
+ EXPECT_EQ(root_windows[0], window->GetRootWindow());
+ DragStart(window.get());
+ DragMove(500, -100);
+ DragEnd();
+ EXPECT_EQ(root_windows[1], window->GetRootWindow());
+ EXPECT_EQ(initial_bounds.x() + 500, window->GetBoundsInScreen().x());
+ EXPECT_EQ(initial_bounds.y() - 100, window->GetBoundsInScreen().y());
+ EXPECT_FALSE(window->GetProperty(kPanelAttachedKey));
+ EXPECT_EQ(internal::kShellWindowId_DefaultContainer,
+ window->parent()->id());
+}
+
+TEST_F(PanelWindowResizerTest, DetachThenAttachToSecondDisplay) {
+ if (!SupportsMultipleDisplays())
+ return;
+
+ UpdateDisplay("600x400,600x600");
+ Shell::RootWindowList root_windows = Shell::GetAllRootWindows();
+ scoped_ptr<aura::Window> window(
+ CreatePanelWindow(gfx::Rect(0, 0, 201, 201)));
+ gfx::Rect initial_bounds = window->GetBoundsInScreen();
+ EXPECT_EQ(root_windows[0], window->GetRootWindow());
+
+ // Detach the window.
+ DragStart(window.get());
+ DragMove(0, -100);
+ DragEnd();
+ EXPECT_EQ(root_windows[0], window->GetRootWindow());
+ EXPECT_FALSE(window->GetProperty(kPanelAttachedKey));
+
+ // Drag the window just above the other display's launcher.
+ DragStart(window.get());
+ DragMove(500, 295);
+ EXPECT_EQ(initial_bounds.x() + 500, window->GetBoundsInScreen().x());
+
+ // Should stick to other launcher.
+ EXPECT_EQ(initial_bounds.y() + 200, window->GetBoundsInScreen().y());
+ DragEnd();
+
+ // When dropped should move to second display's panel container.
+ EXPECT_EQ(root_windows[1], window->GetRootWindow());
+ EXPECT_TRUE(window->GetProperty(kPanelAttachedKey));
+ EXPECT_EQ(internal::kShellWindowId_PanelContainer, window->parent()->id());
+}
+
+TEST_F(PanelWindowResizerTest, AttachToSecondDisplay) {
+ if (!SupportsMultipleDisplays())
+ return;
+
+ UpdateDisplay("600x400,600x600");
+ Shell::RootWindowList root_windows = Shell::GetAllRootWindows();
+ scoped_ptr<aura::Window> window(
+ CreatePanelWindow(gfx::Rect(0, 0, 201, 201)));
+ gfx::Rect initial_bounds = window->GetBoundsInScreen();
+ EXPECT_EQ(root_windows[0], window->GetRootWindow());
+
+ // Drag the window just above the other display's launcher.
+ DragStart(window.get());
+ DragMove(500, 195);
+ EXPECT_EQ(initial_bounds.x() + 500, window->GetBoundsInScreen().x());
+
+ // Should stick to other launcher.
+ EXPECT_EQ(initial_bounds.y() + 200, window->GetBoundsInScreen().y());
+ DragEnd();
+
+ // When dropped should move to second display's panel container.
+ EXPECT_EQ(root_windows[1], window->GetRootWindow());
+ EXPECT_TRUE(window->GetProperty(kPanelAttachedKey));
+ EXPECT_EQ(internal::kShellWindowId_PanelContainer, window->parent()->id());
+}
+
+TEST_F(PanelWindowResizerTest, RevertDragRestoresAttachment) {
+ scoped_ptr<aura::Window> window(
+ CreatePanelWindow(gfx::Rect(0, 0, 201, 201)));
+ EXPECT_TRUE(window->GetProperty(kPanelAttachedKey));
+ EXPECT_EQ(internal::kShellWindowId_PanelContainer, window->parent()->id());
+ DragStart(window.get());
+ DragMove(0, -100);
+ DragRevert();
+ EXPECT_TRUE(window->GetProperty(kPanelAttachedKey));
+ EXPECT_EQ(internal::kShellWindowId_PanelContainer, window->parent()->id());
+
+ // Detach panel.
+ DragStart(window.get());
+ DragMove(0, -100);
+ DragEnd();
+ EXPECT_FALSE(window->GetProperty(kPanelAttachedKey));
+ EXPECT_EQ(internal::kShellWindowId_DefaultContainer,
+ window->parent()->id());
+
+ // Drag back to launcher.
+ DragStart(window.get());
+ DragMove(0, 100);
+
+ // When the drag is reverted it should remain detached.
+ DragRevert();
+ EXPECT_FALSE(window->GetProperty(kPanelAttachedKey));
+ EXPECT_EQ(internal::kShellWindowId_DefaultContainer,
+ window->parent()->id());
+}
+
+TEST_F(PanelWindowResizerTest, DragMovesToPanelLayer) {
+ scoped_ptr<aura::Window> window(CreatePanelWindow(gfx::Rect(0, 0, 201, 201)));
+ DragStart(window.get());
+ DragMove(0, -100);
+ DragEnd();
+ EXPECT_EQ(internal::kShellWindowId_DefaultContainer,
+ window->parent()->id());
+
+ // While moving the panel window should be moved to the panel container.
+ DragStart(window.get());
+ DragMove(20, 0);
+ EXPECT_EQ(internal::kShellWindowId_PanelContainer, window->parent()->id());
+ DragEnd();
+
+ // When dropped it should return to the default container.
+ EXPECT_EQ(internal::kShellWindowId_DefaultContainer,
+ window->parent()->id());
+}
+
+TEST_P(PanelWindowResizerTextDirectionTest, DragReordersPanelsHorizontal) {
+ if (!SupportsHostWindowResize())
+ return;
+
+ DragAlongShelfReorder(base::i18n::IsRTL() ? 1 : -1, 0);
+}
+
+TEST_F(PanelWindowResizerTest, DragReordersPanelsVertical) {
+ if (!SupportsHostWindowResize())
+ return;
+
+ ash::Shell* shell = ash::Shell::GetInstance();
+ shell->SetShelfAlignment(SHELF_ALIGNMENT_LEFT, shell->GetPrimaryRootWindow());
+ DragAlongShelfReorder(0, -1);
+}
+
+INSTANTIATE_TEST_CASE_P(LtrRtl, PanelWindowResizerTextDirectionTest,
+ testing::Bool());
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/wm/partial_screenshot_view.cc b/chromium/ash/wm/partial_screenshot_view.cc
new file mode 100644
index 00000000000..1b2fa6661a0
--- /dev/null
+++ b/chromium/ash/wm/partial_screenshot_view.cc
@@ -0,0 +1,243 @@
+// Copyright (c) 2012 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.
+
+#include "ash/wm/partial_screenshot_view.h"
+
+#include <algorithm>
+
+#include "ash/display/mouse_cursor_event_filter.h"
+#include "ash/screenshot_delegate.h"
+#include "ash/shell.h"
+#include "ash/shell_window_ids.h"
+#include "ash/wm/overlay_event_filter.h"
+#include "ui/aura/client/capture_client.h"
+#include "ui/aura/root_window.h"
+#include "ui/base/cursor/cursor.h"
+#include "ui/base/events/event.h"
+#include "ui/gfx/canvas.h"
+#include "ui/gfx/rect.h"
+#include "ui/views/view.h"
+#include "ui/views/widget/widget.h"
+#include "ui/views/widget/widget_observer.h"
+
+namespace ash {
+
+// A self-owned object to handle the cancel and the finish of current partial
+// screenshot session.
+class PartialScreenshotView::OverlayDelegate
+ : public internal::OverlayEventFilter::Delegate,
+ public views::WidgetObserver {
+ public:
+ OverlayDelegate() {
+ Shell::GetInstance()->overlay_filter()->Activate(this);
+ }
+
+ void RegisterWidget(views::Widget* widget) {
+ widgets_.push_back(widget);
+ widget->AddObserver(this);
+ }
+
+ // Overridden from OverlayEventFilter::Delegate:
+ virtual void Cancel() OVERRIDE {
+ // Make sure the mouse_warp_mode allows warping. It can be stopped by a
+ // partial screenshot view.
+ internal::MouseCursorEventFilter* mouse_cursor_filter =
+ Shell::GetInstance()->mouse_cursor_filter();
+ mouse_cursor_filter->set_mouse_warp_mode(
+ internal::MouseCursorEventFilter::WARP_ALWAYS);
+ for (size_t i = 0; i < widgets_.size(); ++i)
+ widgets_[i]->Close();
+ }
+
+ virtual bool IsCancelingKeyEvent(ui::KeyEvent* event) OVERRIDE {
+ return event->key_code() == ui::VKEY_ESCAPE;
+ }
+
+ virtual aura::Window* GetWindow() OVERRIDE {
+ // Just returns NULL because this class does not handle key events in
+ // OverlayEventFilter, except for cancel keys.
+ return NULL;
+ }
+
+ // Overridden from views::WidgetObserver:
+ virtual void OnWidgetDestroying(views::Widget* widget) OVERRIDE {
+ widget->RemoveObserver(this);
+ widgets_.erase(std::remove(widgets_.begin(), widgets_.end(), widget));
+ if (widgets_.empty())
+ delete this;
+ }
+
+ private:
+ virtual ~OverlayDelegate() {
+ Shell::GetInstance()->overlay_filter()->Deactivate();
+ }
+
+ std::vector<views::Widget*> widgets_;
+
+ DISALLOW_COPY_AND_ASSIGN(OverlayDelegate);
+};
+
+// static
+std::vector<PartialScreenshotView*>
+PartialScreenshotView::StartPartialScreenshot(
+ ScreenshotDelegate* screenshot_delegate) {
+ std::vector<PartialScreenshotView*> views;
+ OverlayDelegate* overlay_delegate = new OverlayDelegate();
+ Shell::RootWindowList root_windows = Shell::GetAllRootWindows();
+ for (Shell::RootWindowList::iterator it = root_windows.begin();
+ it != root_windows.end(); ++it) {
+ PartialScreenshotView* new_view = new PartialScreenshotView(
+ overlay_delegate, screenshot_delegate);
+ new_view->Init(*it);
+ views.push_back(new_view);
+ }
+ return views;
+}
+
+PartialScreenshotView::PartialScreenshotView(
+ PartialScreenshotView::OverlayDelegate* overlay_delegate,
+ ScreenshotDelegate* screenshot_delegate)
+ : is_dragging_(false),
+ overlay_delegate_(overlay_delegate),
+ screenshot_delegate_(screenshot_delegate) {
+}
+
+PartialScreenshotView::~PartialScreenshotView() {
+ overlay_delegate_ = NULL;
+ screenshot_delegate_ = NULL;
+}
+
+void PartialScreenshotView::Init(aura::RootWindow* root_window) {
+ views::Widget* widget = new views::Widget;
+ views::Widget::InitParams params(
+ views::Widget::InitParams::TYPE_WINDOW_FRAMELESS);
+ params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW;
+ params.delegate = this;
+ // The partial screenshot rectangle has to be at the real top of
+ // the screen.
+ params.parent = Shell::GetContainer(
+ root_window,
+ internal::kShellWindowId_OverlayContainer);
+
+ widget->Init(params);
+ widget->SetContentsView(this);
+ widget->SetBounds(root_window->GetBoundsInScreen());
+ widget->GetNativeView()->SetName("PartialScreenshotView");
+ widget->StackAtTop();
+ widget->Show();
+ // Releases the mouse capture to let mouse events come to the view. This
+ // will close the context menu.
+ aura::client::CaptureClient* capture_client =
+ aura::client::GetCaptureClient(root_window);
+ if (capture_client->GetCaptureWindow())
+ capture_client->ReleaseCapture(capture_client->GetCaptureWindow());
+
+ overlay_delegate_->RegisterWidget(widget);
+}
+
+gfx::Rect PartialScreenshotView::GetScreenshotRect() const {
+ int left = std::min(start_position_.x(), current_position_.x());
+ int top = std::min(start_position_.y(), current_position_.y());
+ int width = ::abs(start_position_.x() - current_position_.x());
+ int height = ::abs(start_position_.y() - current_position_.y());
+ return gfx::Rect(left, top, width, height);
+}
+
+void PartialScreenshotView::OnSelectionStarted(const gfx::Point& position) {
+ start_position_ = position;
+}
+
+void PartialScreenshotView::OnSelectionChanged(const gfx::Point& position) {
+ if (is_dragging_ && current_position_ == position)
+ return;
+ current_position_ = position;
+ SchedulePaint();
+ is_dragging_ = true;
+}
+
+void PartialScreenshotView::OnSelectionFinished() {
+ overlay_delegate_->Cancel();
+ if (!is_dragging_)
+ return;
+
+ is_dragging_ = false;
+ if (screenshot_delegate_) {
+ aura::RootWindow *root_window =
+ GetWidget()->GetNativeWindow()->GetRootWindow();
+ screenshot_delegate_->HandleTakePartialScreenshot(
+ root_window,
+ gfx::IntersectRects(root_window->bounds(), GetScreenshotRect()));
+ }
+}
+
+gfx::NativeCursor PartialScreenshotView::GetCursor(
+ const ui::MouseEvent& event) {
+ // Always use "crosshair" cursor.
+ return ui::kCursorCross;
+}
+
+void PartialScreenshotView::OnPaint(gfx::Canvas* canvas) {
+ if (is_dragging_) {
+ // Screenshot area representation: black rectangle with white
+ // rectangle inside. To avoid capturing these rectangles when mouse
+ // release, they should be outside of the actual capturing area.
+ gfx::Rect screenshot_rect = GetScreenshotRect();
+ screenshot_rect.Inset(-1, -1, -1, -1);
+ canvas->DrawRect(screenshot_rect, SK_ColorWHITE);
+ screenshot_rect.Inset(-1, -1, -1, -1);
+ canvas->DrawRect(screenshot_rect, SK_ColorBLACK);
+ }
+}
+
+bool PartialScreenshotView::OnMousePressed(const ui::MouseEvent& event) {
+ // Prevent moving across displays during drag. Capturing a screenshot across
+ // the displays is not supported yet.
+ // TODO(mukai): remove this restriction.
+ internal::MouseCursorEventFilter* mouse_cursor_filter =
+ Shell::GetInstance()->mouse_cursor_filter();
+ mouse_cursor_filter->set_mouse_warp_mode(
+ internal::MouseCursorEventFilter::WARP_NONE);
+ OnSelectionStarted(event.location());
+ return true;
+}
+
+bool PartialScreenshotView::OnMouseDragged(const ui::MouseEvent& event) {
+ OnSelectionChanged(event.location());
+ return true;
+}
+
+bool PartialScreenshotView::OnMouseWheel(const ui::MouseWheelEvent& event) {
+ // Do nothing but do not propagate events futhermore.
+ return true;
+}
+
+void PartialScreenshotView::OnMouseReleased(const ui::MouseEvent& event) {
+ OnSelectionFinished();
+}
+
+void PartialScreenshotView::OnMouseCaptureLost() {
+ is_dragging_ = false;
+ OnSelectionFinished();
+}
+
+void PartialScreenshotView::OnGestureEvent(ui::GestureEvent* event) {
+ switch(event->type()) {
+ case ui::ET_GESTURE_TAP_DOWN:
+ OnSelectionStarted(event->location());
+ break;
+ case ui::ET_GESTURE_SCROLL_UPDATE:
+ OnSelectionChanged(event->location());
+ break;
+ case ui::ET_GESTURE_SCROLL_END:
+ case ui::ET_SCROLL_FLING_START:
+ OnSelectionFinished();
+ break;
+ default:
+ break;
+ }
+
+ event->SetHandled();
+}
+
+} // namespace ash
diff --git a/chromium/ash/wm/partial_screenshot_view.h b/chromium/ash/wm/partial_screenshot_view.h
new file mode 100644
index 00000000000..85b9f81f28b
--- /dev/null
+++ b/chromium/ash/wm/partial_screenshot_view.h
@@ -0,0 +1,79 @@
+// Copyright (c) 2012 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 ASH_WM_PARTIAL_SCREENSHOT_VIEW_H_
+#define ASH_WM_PARTIAL_SCREENSHOT_VIEW_H_
+
+#include <vector>
+
+#include "ash/ash_export.h"
+#include "base/compiler_specific.h"
+#include "base/gtest_prod_util.h"
+#include "ui/gfx/point.h"
+#include "ui/views/widget/widget_delegate.h"
+
+namespace aura {
+class RootWindow;
+}
+
+namespace ash {
+class ScreenshotDelegate;
+
+// The view of taking partial screenshot, i.e.: drawing region
+// rectangles during drag, and changing the mouse cursor to indicate
+// the current mode.
+class ASH_EXPORT PartialScreenshotView : public views::WidgetDelegateView {
+ public:
+ // Starts the UI for taking partial screenshot; dragging to select a region.
+ // PartialScreenshotViews manage their own lifetime so caller must not delete
+ // the returned PartialScreenshotViews.
+ static std::vector<PartialScreenshotView*>
+ StartPartialScreenshot(ScreenshotDelegate* screenshot_delegate);
+
+ private:
+ FRIEND_TEST_ALL_PREFIXES(PartialScreenshotViewTest, BasicMouse);
+ FRIEND_TEST_ALL_PREFIXES(PartialScreenshotViewTest, BasicTouch);
+
+ class OverlayDelegate;
+
+ PartialScreenshotView(OverlayDelegate* overlay_delegate,
+ ScreenshotDelegate* screenshot_delegate);
+ virtual ~PartialScreenshotView();
+
+ // Initializes partial screenshot UI widget for |root_window|.
+ void Init(aura::RootWindow* root_window);
+
+ // Returns the currently selected region.
+ gfx::Rect GetScreenshotRect() const;
+
+ void OnSelectionStarted(const gfx::Point& position);
+ void OnSelectionChanged(const gfx::Point& position);
+ void OnSelectionFinished();
+
+ // Overridden from views::View:
+ virtual gfx::NativeCursor GetCursor(const ui::MouseEvent& event) OVERRIDE;
+ virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE;
+ virtual bool OnMousePressed(const ui::MouseEvent& event) OVERRIDE;
+ virtual bool OnMouseDragged(const ui::MouseEvent& event) OVERRIDE;
+ virtual bool OnMouseWheel(const ui::MouseWheelEvent& event) OVERRIDE;
+ virtual void OnMouseReleased(const ui::MouseEvent& event) OVERRIDE;
+ virtual void OnMouseCaptureLost() OVERRIDE;
+ virtual void OnGestureEvent(ui::GestureEvent* event) OVERRIDE;
+
+ bool is_dragging_;
+ gfx::Point start_position_;
+ gfx::Point current_position_;
+
+ // The delegate to receive Cancel. No ownership.
+ OverlayDelegate* overlay_delegate_;
+
+ // ScreenshotDelegate to take the actual screenshot. No ownership.
+ ScreenshotDelegate* screenshot_delegate_;
+
+ DISALLOW_COPY_AND_ASSIGN(PartialScreenshotView);
+};
+
+} // namespace ash
+
+#endif // #ifndef ASH_WM_PARTIAL_SCREENSHOT_VIEW_H_
diff --git a/chromium/ash/wm/partial_screenshot_view_unittest.cc b/chromium/ash/wm/partial_screenshot_view_unittest.cc
new file mode 100644
index 00000000000..a763577d0ef
--- /dev/null
+++ b/chromium/ash/wm/partial_screenshot_view_unittest.cc
@@ -0,0 +1,103 @@
+// Copyright (c) 2013 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.
+
+#include "ash/wm/partial_screenshot_view.h"
+
+#include "ash/screenshot_delegate.h"
+#include "ash/shell.h"
+#include "ash/shell_window_ids.h"
+#include "ash/test/ash_test_base.h"
+#include "ui/aura/root_window.h"
+#include "ui/aura/test/event_generator.h"
+
+namespace ash {
+
+class FakeScreenshotDelegate : public ScreenshotDelegate {
+ public:
+ FakeScreenshotDelegate() : screenshot_count_(0) {}
+
+ virtual void HandleTakeScreenshotForAllRootWindows() OVERRIDE {}
+ virtual void HandleTakePartialScreenshot(aura::Window* window,
+ const gfx::Rect& rect) OVERRIDE {
+ rect_ = rect;
+ screenshot_count_++;
+ }
+
+ virtual bool CanTakeScreenshot() OVERRIDE {
+ return true;
+ }
+
+ const gfx::Rect& rect() const { return rect_; };
+ int screenshot_count() const { return screenshot_count_; };
+
+ private:
+ gfx::Rect rect_;
+ int screenshot_count_;
+
+ DISALLOW_COPY_AND_ASSIGN(FakeScreenshotDelegate);
+};
+
+class PartialScreenshotViewTest : public test::AshTestBase {
+ public:
+ PartialScreenshotViewTest() : view_(NULL) {}
+ virtual ~PartialScreenshotViewTest() {}
+
+ virtual void SetUp() OVERRIDE {
+ test::AshTestBase::SetUp();
+ delegate_.reset(new FakeScreenshotDelegate());
+ std::vector<PartialScreenshotView*> views =
+ PartialScreenshotView::StartPartialScreenshot(delegate_.get());
+ ASSERT_EQ(1u, views.size());
+ view_ = views[0];
+ }
+
+ protected:
+ PartialScreenshotView* view_;
+ scoped_ptr<FakeScreenshotDelegate> delegate_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(PartialScreenshotViewTest);
+};
+
+TEST_F(PartialScreenshotViewTest, BasicMouse) {
+ aura::test::EventGenerator generator(Shell::GetPrimaryRootWindow());
+
+ generator.MoveMouseTo(100, 100);
+ generator.PressLeftButton();
+ EXPECT_FALSE(view_->is_dragging_);
+ EXPECT_EQ("100,100", view_->start_position_.ToString());
+
+ generator.MoveMouseTo(200, 200);
+ EXPECT_TRUE(view_->is_dragging_);
+ EXPECT_EQ("200,200", view_->current_position_.ToString());
+
+ generator.ReleaseLeftButton();
+ EXPECT_FALSE(view_->is_dragging_);
+ EXPECT_EQ("100,100 100x100", delegate_->rect().ToString());
+ EXPECT_EQ(1, delegate_->screenshot_count());
+}
+
+TEST_F(PartialScreenshotViewTest, BasicTouch) {
+ aura::test::EventGenerator generator(Shell::GetPrimaryRootWindow());
+
+ generator.set_current_location(gfx::Point(100,100));
+ generator.GestureTapDownAndUp(gfx::Point(100,100));
+ EXPECT_FALSE(view_->is_dragging_);
+ EXPECT_EQ(0, delegate_->screenshot_count());
+
+ generator.PressTouch();
+ EXPECT_FALSE(view_->is_dragging_);
+ EXPECT_EQ("100,100", view_->start_position_.ToString());
+
+ generator.MoveTouch(gfx::Point(200, 200));
+ EXPECT_TRUE(view_->is_dragging_);
+ EXPECT_EQ("200,200", view_->current_position_.ToString());
+
+ generator.ReleaseTouch();
+ EXPECT_FALSE(view_->is_dragging_);
+ EXPECT_EQ(1, delegate_->screenshot_count());
+ EXPECT_EQ("100,100 100x100", delegate_->rect().ToString());
+}
+
+} // namespace ash
diff --git a/chromium/ash/wm/power_button_controller.cc b/chromium/ash/wm/power_button_controller.cc
new file mode 100644
index 00000000000..c2e3789f1f0
--- /dev/null
+++ b/chromium/ash/wm/power_button_controller.cc
@@ -0,0 +1,110 @@
+// Copyright (c) 2012 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.
+
+#include "ash/wm/power_button_controller.h"
+
+#include "ash/ash_switches.h"
+#include "ash/session_state_delegate.h"
+#include "ash/shell.h"
+#include "ash/shell_window_ids.h"
+#include "ash/wm/lock_state_controller.h"
+#include "ash/wm/session_state_animator.h"
+#include "base/command_line.h"
+#include "ui/aura/root_window.h"
+#include "ui/views/corewm/compound_event_filter.h"
+
+namespace ash {
+
+PowerButtonController::PowerButtonController(
+ LockStateController* controller)
+ : power_button_down_(false),
+ lock_button_down_(false),
+ screen_is_off_(false),
+ has_legacy_power_button_(
+ CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kAuraLegacyPowerButton)),
+ controller_(controller) {
+}
+
+PowerButtonController::~PowerButtonController() {
+}
+
+void PowerButtonController::OnScreenBrightnessChanged(double percent) {
+ screen_is_off_ = percent <= 0.001;
+}
+
+void PowerButtonController::OnPowerButtonEvent(
+ bool down, const base::TimeTicks& timestamp) {
+ power_button_down_ = down;
+
+ if (controller_->ShutdownRequested())
+ return;
+
+ // Avoid starting the lock/shutdown sequence if the power button is pressed
+ // while the screen is off (http://crbug.com/128451).
+ if (screen_is_off_)
+ return;
+
+ const SessionStateDelegate* session_state_delegate =
+ Shell::GetInstance()->session_state_delegate();
+ if (has_legacy_power_button_) {
+ // If power button releases won't get reported correctly because we're not
+ // running on official hardware, just lock the screen or shut down
+ // immediately.
+ if (down) {
+ if (session_state_delegate->CanLockScreen() &&
+ !session_state_delegate->IsScreenLocked() &&
+ !controller_->LockRequested()) {
+ controller_->StartLockAnimationAndLockImmediately();
+ } else {
+ controller_->RequestShutdown();
+ }
+ }
+ } else { // !has_legacy_power_button_
+ if (down) {
+ // If we already have a pending request to lock the screen, wait.
+ if (controller_->LockRequested())
+ return;
+
+ if (session_state_delegate->CanLockScreen() &&
+ !session_state_delegate->IsScreenLocked()) {
+ controller_->StartLockAnimation(true);
+ } else {
+ controller_->StartShutdownAnimation();
+ }
+ } else { // Button is up.
+ if (controller_->CanCancelLockAnimation())
+ controller_->CancelLockAnimation();
+ else if (controller_->CanCancelShutdownAnimation())
+ controller_->CancelShutdownAnimation();
+ }
+ }
+}
+
+void PowerButtonController::OnLockButtonEvent(
+ bool down, const base::TimeTicks& timestamp) {
+ lock_button_down_ = down;
+
+ const SessionStateDelegate* session_state_delegate =
+ Shell::GetInstance()->session_state_delegate();
+ if (!session_state_delegate->CanLockScreen() ||
+ session_state_delegate->IsScreenLocked() ||
+ controller_->LockRequested() ||
+ controller_->ShutdownRequested()) {
+ return;
+ }
+
+ // Give the power button precedence over the lock button (we don't expect both
+ // buttons to be present, so this is just making sure that we don't do
+ // something completely stupid if that assumption changes later).
+ if (power_button_down_)
+ return;
+
+ if (down)
+ controller_->StartLockAnimation(false);
+ else
+ controller_->CancelLockAnimation();
+}
+
+} // namespace ash
diff --git a/chromium/ash/wm/power_button_controller.h b/chromium/ash/wm/power_button_controller.h
new file mode 100644
index 00000000000..558bf6d0ae1
--- /dev/null
+++ b/chromium/ash/wm/power_button_controller.h
@@ -0,0 +1,69 @@
+// Copyright (c) 2012 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 ASH_WM_POWER_BUTTON_CONTROLLER_H_
+#define ASH_WM_POWER_BUTTON_CONTROLLER_H_
+
+#include "ash/ash_export.h"
+#include "ash/wm/session_state_animator.h"
+#include "base/basictypes.h"
+
+namespace gfx {
+class Rect;
+class Size;
+}
+
+namespace ui {
+class Layer;
+}
+
+namespace ash {
+
+namespace test {
+class PowerButtonControllerTest;
+}
+
+class LockStateController;
+
+// Displays onscreen animations and locks or suspends the system in response to
+// the power button being pressed or released.
+class ASH_EXPORT PowerButtonController {
+ public:
+
+ explicit PowerButtonController(LockStateController* controller);
+ virtual ~PowerButtonController();
+
+ void set_has_legacy_power_button_for_test(bool legacy) {
+ has_legacy_power_button_ = legacy;
+ }
+
+ // Called when the current screen brightness changes.
+ void OnScreenBrightnessChanged(double percent);
+
+ // Called when the power or lock buttons are pressed or released.
+ void OnPowerButtonEvent(bool down, const base::TimeTicks& timestamp);
+ void OnLockButtonEvent(bool down, const base::TimeTicks& timestamp);
+
+ private:
+ friend class test::PowerButtonControllerTest;
+
+ // Are the power or lock buttons currently held?
+ bool power_button_down_;
+ bool lock_button_down_;
+
+ // Is the screen currently turned off?
+ bool screen_is_off_;
+
+ // Was a command-line switch set telling us that we're running on hardware
+ // that misreports power button releases?
+ bool has_legacy_power_button_;
+
+ LockStateController* controller_; // Not owned.
+
+ DISALLOW_COPY_AND_ASSIGN(PowerButtonController);
+};
+
+} // namespace ash
+
+#endif // ASH_WM_POWER_BUTTON_CONTROLLER_H_
diff --git a/chromium/ash/wm/power_button_controller_unittest.cc b/chromium/ash/wm/power_button_controller_unittest.cc
new file mode 100644
index 00000000000..e1ce71bbc2c
--- /dev/null
+++ b/chromium/ash/wm/power_button_controller_unittest.cc
@@ -0,0 +1,637 @@
+// Copyright (c) 2012 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.
+
+#include "ash/ash_switches.h"
+#include "ash/session_state_delegate.h"
+#include "ash/shell.h"
+#include "ash/test/ash_test_base.h"
+#include "ash/test/test_shell_delegate.h"
+#include "ash/wm/lock_state_controller.h"
+#include "ash/wm/power_button_controller.h"
+#include "ash/wm/session_state_animator.h"
+#include "ash/wm/session_state_controller_impl.h"
+#include "base/command_line.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/time/time.h"
+#include "ui/aura/env.h"
+#include "ui/aura/root_window.h"
+#include "ui/aura/test/event_generator.h"
+#include "ui/compositor/scoped_animation_duration_scale_mode.h"
+#include "ui/gfx/rect.h"
+#include "ui/gfx/size.h"
+
+namespace ash {
+namespace test {
+namespace {
+bool cursor_visible() {
+ return ash::Shell::GetInstance()->cursor_manager()->IsCursorVisible();
+}
+}
+
+// Fake implementation of PowerButtonControllerDelegate that just logs requests
+// to lock the screen and shut down the device.
+class TestPowerButtonControllerDelegate :
+ public LockStateControllerDelegate {
+ public:
+ TestPowerButtonControllerDelegate()
+ : num_lock_requests_(0),
+ num_shutdown_requests_(0) {}
+
+ int num_lock_requests() const { return num_lock_requests_; }
+ int num_shutdown_requests() const { return num_shutdown_requests_; }
+
+ // PowerButtonControllerDelegate implementation.
+ virtual void RequestLockScreen() OVERRIDE {
+ num_lock_requests_++;
+ }
+ virtual void RequestShutdown() OVERRIDE {
+ num_shutdown_requests_++;
+ }
+
+ private:
+ int num_lock_requests_;
+ int num_shutdown_requests_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestPowerButtonControllerDelegate);
+};
+
+class PowerButtonControllerTest : public AshTestBase {
+ public:
+ PowerButtonControllerTest() : controller_(NULL), delegate_(NULL) {}
+ virtual ~PowerButtonControllerTest() {}
+
+ virtual void SetUp() OVERRIDE {
+ CommandLine::ForCurrentProcess()->AppendSwitch(
+ ash::switches::kAshDisableNewLockAnimations);
+
+ AshTestBase::SetUp();
+ delegate_ = new TestPowerButtonControllerDelegate;
+ controller_ = Shell::GetInstance()->power_button_controller();
+ lock_state_controller_ = static_cast<SessionStateControllerImpl*>(
+ Shell::GetInstance()->lock_state_controller());
+ lock_state_controller_->SetDelegate(delegate_); // transfers ownership
+ test_api_.reset(new SessionStateControllerImpl::TestApi(
+ lock_state_controller_));
+ animator_api_.reset(new internal::SessionStateAnimator::TestApi(
+ lock_state_controller_->animator_.get()));
+ shell_delegate_ = reinterpret_cast<TestShellDelegate*>(
+ ash::Shell::GetInstance()->delegate());
+ state_delegate_ = Shell::GetInstance()->session_state_delegate();
+ }
+
+ protected:
+ void GenerateMouseMoveEvent() {
+ aura::test::EventGenerator generator(
+ Shell::GetPrimaryRootWindow());
+ generator.MoveMouseTo(10, 10);
+ }
+
+ int NumShutdownRequests() {
+ return delegate_->num_shutdown_requests() +
+ shell_delegate_->num_exit_requests();
+ }
+
+ PowerButtonController* controller_; // not owned
+ SessionStateControllerImpl* lock_state_controller_; // not owned
+ TestPowerButtonControllerDelegate* delegate_; // not owned
+ TestShellDelegate* shell_delegate_; // not owned
+ SessionStateDelegate* state_delegate_; // not owned
+
+ scoped_ptr<SessionStateControllerImpl::TestApi> test_api_;
+ scoped_ptr<internal::SessionStateAnimator::TestApi> animator_api_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(PowerButtonControllerTest);
+};
+
+// Test the lock-to-shutdown flow for non-Chrome-OS hardware that doesn't
+// correctly report power button releases. We should lock immediately the first
+// time the button is pressed and shut down when it's pressed from the locked
+// state.
+TEST_F(PowerButtonControllerTest, LegacyLockAndShutDown) {
+ controller_->set_has_legacy_power_button_for_test(true);
+ lock_state_controller_->OnLoginStateChanged(user::LOGGED_IN_USER);
+ lock_state_controller_->OnLockStateChanged(false);
+
+ // We should request that the screen be locked immediately after seeing the
+ // power button get pressed.
+ controller_->OnPowerButtonEvent(true, base::TimeTicks::Now());
+ EXPECT_TRUE(
+ animator_api_->ContainersAreAnimated(
+ internal::SessionStateAnimator::NON_LOCK_SCREEN_CONTAINERS,
+ internal::SessionStateAnimator::ANIMATION_PARTIAL_CLOSE));
+ EXPECT_FALSE(test_api_->lock_timer_is_running());
+ EXPECT_EQ(1, delegate_->num_lock_requests());
+
+ // Notify that we locked successfully.
+ lock_state_controller_->OnStartingLock();
+ EXPECT_TRUE(
+ animator_api_->ContainersAreAnimated(
+ internal::SessionStateAnimator::LAUNCHER,
+ internal::SessionStateAnimator::ANIMATION_HIDE_IMMEDIATELY));
+ EXPECT_TRUE(
+ animator_api_->ContainersAreAnimated(
+ internal::SessionStateAnimator::NON_LOCK_SCREEN_CONTAINERS,
+ internal::SessionStateAnimator::ANIMATION_FULL_CLOSE));
+ EXPECT_TRUE(
+ animator_api_->ContainersAreAnimated(
+ internal::SessionStateAnimator::LOCK_SCREEN_CONTAINERS,
+ internal::SessionStateAnimator::ANIMATION_HIDE_IMMEDIATELY));
+
+ // Notify that the lock window is visible. We should make it fade in.
+ lock_state_controller_->OnLockStateChanged(true);
+ state_delegate_->LockScreen();
+ EXPECT_TRUE(
+ animator_api_->ContainersAreAnimated(
+ internal::SessionStateAnimator::kAllLockScreenContainersMask,
+ internal::SessionStateAnimator::ANIMATION_FADE_IN));
+
+ // We shouldn't progress towards the shutdown state, however.
+ EXPECT_FALSE(test_api_->lock_to_shutdown_timer_is_running());
+ EXPECT_FALSE(test_api_->shutdown_timer_is_running());
+ controller_->OnPowerButtonEvent(false, base::TimeTicks::Now());
+
+ // Hold the button again and check that we start shutting down.
+ controller_->OnPowerButtonEvent(true, base::TimeTicks::Now());
+ EXPECT_EQ(0, NumShutdownRequests());
+
+ // Previously we're checking that the all containers group was animated which
+ // was in fact checking that
+ // 1. All user session containers have transform (including wallpaper).
+ // They're in this state after lock.
+ // 2. Screen locker and related containers are in fact animating
+ // (as shutdown is in progress).
+ // With http://crbug.com/144737 we no longer animate user session wallpaper
+ // during lock so it makes sense only to check that screen lock and related
+ // containers are animated during shutdown.
+ EXPECT_TRUE(
+ animator_api_->ContainersAreAnimated(
+ internal::SessionStateAnimator::kAllLockScreenContainersMask,
+ internal::SessionStateAnimator::ANIMATION_FULL_CLOSE));
+ // Make sure a mouse move event won't show the cursor.
+ GenerateMouseMoveEvent();
+ EXPECT_FALSE(cursor_visible());
+ EXPECT_TRUE(test_api_->real_shutdown_timer_is_running());
+ test_api_->trigger_real_shutdown_timeout();
+ EXPECT_EQ(1, NumShutdownRequests());
+}
+
+// Test that we start shutting down immediately if the power button is pressed
+// while we're not logged in on an unofficial system.
+TEST_F(PowerButtonControllerTest, LegacyNotLoggedIn) {
+ controller_->set_has_legacy_power_button_for_test(true);
+ lock_state_controller_->OnLoginStateChanged(user::LOGGED_IN_NONE);
+ lock_state_controller_->OnLockStateChanged(false);
+ SetUserLoggedIn(false);
+ controller_->OnPowerButtonEvent(true, base::TimeTicks::Now());
+ EXPECT_TRUE(test_api_->real_shutdown_timer_is_running());
+}
+
+// Test that we start shutting down immediately if the power button is pressed
+// while we're logged in as a guest on an unofficial system.
+TEST_F(PowerButtonControllerTest, LegacyGuest) {
+ controller_->set_has_legacy_power_button_for_test(true);
+ lock_state_controller_->OnLoginStateChanged(user::LOGGED_IN_GUEST);
+ lock_state_controller_->OnLockStateChanged(false);
+ SetCanLockScreen(false);
+ controller_->OnPowerButtonEvent(true, base::TimeTicks::Now());
+ EXPECT_TRUE(test_api_->real_shutdown_timer_is_running());
+}
+
+// When we hold the power button while the user isn't logged in, we should shut
+// down the machine directly.
+TEST_F(PowerButtonControllerTest, ShutdownWhenNotLoggedIn) {
+ controller_->set_has_legacy_power_button_for_test(false);
+ lock_state_controller_->OnLoginStateChanged(user::LOGGED_IN_NONE);
+ lock_state_controller_->OnLockStateChanged(false);
+ SetUserLoggedIn(false);
+
+ // Press the power button and check that we start the shutdown timer.
+ controller_->OnPowerButtonEvent(true, base::TimeTicks::Now());
+ EXPECT_FALSE(test_api_->lock_timer_is_running());
+ EXPECT_TRUE(test_api_->shutdown_timer_is_running());
+ EXPECT_TRUE(
+ animator_api_->ContainersAreAnimated(
+ internal::SessionStateAnimator::kAllContainersMask,
+ internal::SessionStateAnimator::ANIMATION_PARTIAL_CLOSE));
+
+ // Release the power button before the shutdown timer fires.
+ controller_->OnPowerButtonEvent(false, base::TimeTicks::Now());
+ EXPECT_FALSE(test_api_->shutdown_timer_is_running());
+ EXPECT_TRUE(
+ animator_api_->ContainersAreAnimated(
+ internal::SessionStateAnimator::kAllContainersMask,
+ internal::SessionStateAnimator::ANIMATION_UNDO_PARTIAL_CLOSE));
+
+ // Press the button again and make the shutdown timeout fire this time.
+ // Check that we start the timer for actually requesting the shutdown.
+ controller_->OnPowerButtonEvent(true, base::TimeTicks::Now());
+ EXPECT_TRUE(test_api_->shutdown_timer_is_running());
+ test_api_->trigger_shutdown_timeout();
+ EXPECT_TRUE(test_api_->real_shutdown_timer_is_running());
+ EXPECT_EQ(0, NumShutdownRequests());
+ EXPECT_TRUE(
+ animator_api_->ContainersAreAnimated(
+ internal::SessionStateAnimator::LAUNCHER |
+ internal::SessionStateAnimator::NON_LOCK_SCREEN_CONTAINERS,
+ internal::SessionStateAnimator::ANIMATION_HIDE_IMMEDIATELY));
+ EXPECT_TRUE(
+ animator_api_->ContainersAreAnimated(
+ internal::SessionStateAnimator::kAllLockScreenContainersMask,
+ internal::SessionStateAnimator::ANIMATION_FULL_CLOSE));
+
+ // When the timout fires, we should request a shutdown.
+ test_api_->trigger_real_shutdown_timeout();
+ EXPECT_EQ(1, NumShutdownRequests());
+}
+
+// Test that we lock the screen and deal with unlocking correctly.
+TEST_F(PowerButtonControllerTest, LockAndUnlock) {
+ controller_->set_has_legacy_power_button_for_test(false);
+ lock_state_controller_->OnLoginStateChanged(user::LOGGED_IN_USER);
+ lock_state_controller_->OnLockStateChanged(false);
+
+ // We should initially be showing the screen locker containers, since they
+ // also contain login-related windows that we want to show during the
+ // logging-in animation.
+ EXPECT_TRUE(
+ animator_api_->ContainersAreAnimated(
+ internal::SessionStateAnimator::LOCK_SCREEN_CONTAINERS,
+ internal::SessionStateAnimator::ANIMATION_RESTORE));
+
+ // Press the power button and check that the lock timer is started and that we
+ // start scaling the non-screen-locker containers.
+ controller_->OnPowerButtonEvent(true, base::TimeTicks::Now());
+ EXPECT_TRUE(test_api_->lock_timer_is_running());
+ EXPECT_FALSE(test_api_->shutdown_timer_is_running());
+ EXPECT_TRUE(
+ animator_api_->ContainersAreAnimated(
+ internal::SessionStateAnimator::NON_LOCK_SCREEN_CONTAINERS,
+ internal::SessionStateAnimator::ANIMATION_PARTIAL_CLOSE));
+
+ // Release the button before the lock timer fires.
+ controller_->OnPowerButtonEvent(false, base::TimeTicks::Now());
+ EXPECT_FALSE(test_api_->lock_timer_is_running());
+ EXPECT_TRUE(
+ animator_api_->ContainersAreAnimated(
+ internal::SessionStateAnimator::NON_LOCK_SCREEN_CONTAINERS,
+ internal::SessionStateAnimator::ANIMATION_UNDO_PARTIAL_CLOSE));
+
+ // Press the button and fire the lock timer. We should request that the
+ // screen be locked, but we should still be in the slow-close animation.
+ controller_->OnPowerButtonEvent(true, base::TimeTicks::Now());
+ EXPECT_TRUE(test_api_->lock_timer_is_running());
+ EXPECT_EQ(0, delegate_->num_lock_requests());
+ test_api_->trigger_lock_timeout();
+ EXPECT_EQ(1, delegate_->num_lock_requests());
+ EXPECT_TRUE(
+ animator_api_->ContainersAreAnimated(
+ internal::SessionStateAnimator::NON_LOCK_SCREEN_CONTAINERS,
+ internal::SessionStateAnimator::ANIMATION_PARTIAL_CLOSE));
+
+ // Notify that we locked successfully.
+ lock_state_controller_->OnStartingLock();
+ EXPECT_TRUE(
+ animator_api_->ContainersAreAnimated(
+ internal::SessionStateAnimator::LAUNCHER,
+ internal::SessionStateAnimator::ANIMATION_HIDE_IMMEDIATELY));
+ EXPECT_TRUE(
+ animator_api_->ContainersAreAnimated(
+ internal::SessionStateAnimator::NON_LOCK_SCREEN_CONTAINERS,
+ internal::SessionStateAnimator::ANIMATION_FULL_CLOSE));
+ EXPECT_TRUE(
+ animator_api_->ContainersAreAnimated(
+ internal::SessionStateAnimator::LOCK_SCREEN_CONTAINERS,
+ internal::SessionStateAnimator::ANIMATION_HIDE_IMMEDIATELY));
+
+ // Notify that the lock window is visible. We should make it fade in.
+ lock_state_controller_->OnLockStateChanged(true);
+ state_delegate_->LockScreen();
+ EXPECT_TRUE(
+ animator_api_->ContainersAreAnimated(
+ internal::SessionStateAnimator::kAllLockScreenContainersMask,
+ internal::SessionStateAnimator::ANIMATION_FADE_IN));
+
+ // When we release the power button, the lock-to-shutdown timer should be
+ // stopped.
+ EXPECT_TRUE(test_api_->lock_to_shutdown_timer_is_running());
+ controller_->OnPowerButtonEvent(false, base::TimeTicks::Now());
+ EXPECT_FALSE(test_api_->lock_to_shutdown_timer_is_running());
+
+ // Notify that the screen has been unlocked. We should show the
+ // non-screen-locker windows.
+ lock_state_controller_->OnLockStateChanged(false);
+ state_delegate_->UnlockScreen();
+ EXPECT_TRUE(
+ animator_api_->ContainersAreAnimated(
+ internal::SessionStateAnimator::DESKTOP_BACKGROUND |
+ internal::SessionStateAnimator::LAUNCHER |
+ internal::SessionStateAnimator::NON_LOCK_SCREEN_CONTAINERS,
+ internal::SessionStateAnimator::ANIMATION_RESTORE));
+}
+
+// Hold the power button down from the unlocked state to eventual shutdown.
+TEST_F(PowerButtonControllerTest, LockToShutdown) {
+ controller_->set_has_legacy_power_button_for_test(false);
+ lock_state_controller_->OnLoginStateChanged(user::LOGGED_IN_USER);
+ lock_state_controller_->OnLockStateChanged(false);
+
+ // Hold the power button and lock the screen.
+ controller_->OnPowerButtonEvent(true, base::TimeTicks::Now());
+ EXPECT_TRUE(test_api_->lock_timer_is_running());
+ test_api_->trigger_lock_timeout();
+ lock_state_controller_->OnStartingLock();
+ lock_state_controller_->OnLockStateChanged(true);
+ state_delegate_->LockScreen();
+
+ // When the lock-to-shutdown timeout fires, we should start the shutdown
+ // timer.
+ EXPECT_TRUE(test_api_->lock_to_shutdown_timer_is_running());
+ test_api_->trigger_lock_to_shutdown_timeout();
+ EXPECT_TRUE(test_api_->shutdown_timer_is_running());
+ EXPECT_TRUE(
+ animator_api_->ContainersAreAnimated(
+ internal::SessionStateAnimator::kAllContainersMask,
+ internal::SessionStateAnimator::ANIMATION_PARTIAL_CLOSE));
+
+ // Fire the shutdown timeout and check that we request shutdown.
+ test_api_->trigger_shutdown_timeout();
+ EXPECT_TRUE(test_api_->real_shutdown_timer_is_running());
+ EXPECT_EQ(0, NumShutdownRequests());
+ test_api_->trigger_real_shutdown_timeout();
+ EXPECT_EQ(1, NumShutdownRequests());
+}
+
+
+// Hold the power button down from the unlocked state to eventual shutdown,
+// then release the button while system does locking.
+TEST_F(PowerButtonControllerTest, CancelLockToShutdown) {
+ controller_->set_has_legacy_power_button_for_test(false);
+
+ lock_state_controller_->OnLoginStateChanged(user::LOGGED_IN_USER);
+ lock_state_controller_->OnLockStateChanged(false);
+
+ // Hold the power button and lock the screen.
+ controller_->OnPowerButtonEvent(true, base::TimeTicks::Now());
+ EXPECT_TRUE(test_api_->lock_timer_is_running());
+ test_api_->trigger_lock_timeout();
+ lock_state_controller_->OnStartingLock();
+
+ // Power button is released while system attempts to lock.
+ controller_->OnPowerButtonEvent(false, base::TimeTicks::Now());
+ lock_state_controller_->OnLockStateChanged(true);
+ state_delegate_->LockScreen();
+
+ EXPECT_FALSE(lock_state_controller_->ShutdownRequested());
+ EXPECT_FALSE(test_api_->lock_to_shutdown_timer_is_running());
+ EXPECT_FALSE(test_api_->shutdown_timer_is_running());
+}
+
+// Test that we handle the case where lock requests are ignored.
+TEST_F(PowerButtonControllerTest, LockFail) {
+ // We require animations to have a duration for this test.
+ ui::ScopedAnimationDurationScaleMode normal_duration_mode(
+ ui::ScopedAnimationDurationScaleMode::NORMAL_DURATION);
+
+ controller_->set_has_legacy_power_button_for_test(false);
+ lock_state_controller_->OnLoginStateChanged(user::LOGGED_IN_USER);
+ lock_state_controller_->OnLockStateChanged(false);
+
+ // Hold the power button and lock the screen.
+ controller_->OnPowerButtonEvent(true, base::TimeTicks::Now());
+ EXPECT_TRUE(test_api_->lock_timer_is_running());
+ EXPECT_TRUE(
+ animator_api_->ContainersAreAnimated(
+ internal::SessionStateAnimator::DESKTOP_BACKGROUND |
+ internal::SessionStateAnimator::LAUNCHER |
+ internal::SessionStateAnimator::NON_LOCK_SCREEN_CONTAINERS,
+ internal::SessionStateAnimator::ANIMATION_RESTORE));
+ test_api_->trigger_lock_timeout();
+ EXPECT_EQ(1, delegate_->num_lock_requests());
+ EXPECT_TRUE(test_api_->lock_fail_timer_is_running());
+
+ // We shouldn't start the lock-to-shutdown timer until the screen has actually
+ // been locked.
+ EXPECT_FALSE(test_api_->lock_to_shutdown_timer_is_running());
+
+ // Act as if the request timed out. We should restore the windows.
+ test_api_->trigger_lock_fail_timeout();
+ EXPECT_TRUE(
+ animator_api_->ContainersAreAnimated(
+ internal::SessionStateAnimator::NON_LOCK_SCREEN_CONTAINERS,
+ internal::SessionStateAnimator::ANIMATION_RESTORE));
+}
+
+// Test the basic operation of the lock button.
+TEST_F(PowerButtonControllerTest, LockButtonBasic) {
+ controller_->set_has_legacy_power_button_for_test(false);
+ // The lock button shouldn't do anything if we aren't logged in.
+ lock_state_controller_->OnLoginStateChanged(user::LOGGED_IN_NONE);
+ lock_state_controller_->OnLockStateChanged(false);
+ SetUserLoggedIn(false);
+ controller_->OnLockButtonEvent(true, base::TimeTicks::Now());
+ EXPECT_FALSE(test_api_->lock_timer_is_running());
+ controller_->OnLockButtonEvent(false, base::TimeTicks::Now());
+ EXPECT_EQ(0, delegate_->num_lock_requests());
+
+ // Ditto for when we're logged in as a guest.
+ lock_state_controller_->OnLoginStateChanged(user::LOGGED_IN_GUEST);
+ SetUserLoggedIn(true);
+ SetCanLockScreen(false);
+ controller_->OnLockButtonEvent(true, base::TimeTicks::Now());
+ EXPECT_FALSE(test_api_->lock_timer_is_running());
+ controller_->OnLockButtonEvent(false, base::TimeTicks::Now());
+ EXPECT_EQ(0, delegate_->num_lock_requests());
+
+ // If we're logged in as a regular user, we should start the lock timer and
+ // the pre-lock animation.
+ lock_state_controller_->OnLoginStateChanged(user::LOGGED_IN_USER);
+ SetCanLockScreen(true);
+ controller_->OnLockButtonEvent(true, base::TimeTicks::Now());
+ EXPECT_TRUE(test_api_->lock_timer_is_running());
+ EXPECT_TRUE(
+ animator_api_->ContainersAreAnimated(
+ internal::SessionStateAnimator::NON_LOCK_SCREEN_CONTAINERS,
+ internal::SessionStateAnimator::ANIMATION_PARTIAL_CLOSE));
+
+ // If the button is released immediately, we shouldn't lock the screen.
+ controller_->OnLockButtonEvent(false, base::TimeTicks::Now());
+ EXPECT_FALSE(test_api_->lock_timer_is_running());
+ EXPECT_TRUE(
+ animator_api_->ContainersAreAnimated(
+ internal::SessionStateAnimator::NON_LOCK_SCREEN_CONTAINERS,
+ internal::SessionStateAnimator::ANIMATION_UNDO_PARTIAL_CLOSE));
+ EXPECT_EQ(0, delegate_->num_lock_requests());
+
+ // Press the button again and let the lock timeout fire. We should request
+ // that the screen be locked.
+ controller_->OnLockButtonEvent(true, base::TimeTicks::Now());
+ EXPECT_TRUE(test_api_->lock_timer_is_running());
+ test_api_->trigger_lock_timeout();
+ EXPECT_EQ(1, delegate_->num_lock_requests());
+
+ // Pressing the lock button while we have a pending lock request shouldn't do
+ // anything.
+ controller_->OnLockButtonEvent(false, base::TimeTicks::Now());
+ controller_->OnLockButtonEvent(true, base::TimeTicks::Now());
+ EXPECT_FALSE(test_api_->lock_timer_is_running());
+ controller_->OnLockButtonEvent(false, base::TimeTicks::Now());
+
+ // Pressing the button also shouldn't do anything after the screen is locked.
+ lock_state_controller_->OnStartingLock();
+ lock_state_controller_->OnLockStateChanged(true);
+ state_delegate_->LockScreen();
+ controller_->OnLockButtonEvent(true, base::TimeTicks::Now());
+ EXPECT_FALSE(test_api_->lock_timer_is_running());
+ controller_->OnLockButtonEvent(false, base::TimeTicks::Now());
+}
+
+// Test that the power button takes priority over the lock button.
+TEST_F(PowerButtonControllerTest, PowerButtonPreemptsLockButton) {
+ controller_->set_has_legacy_power_button_for_test(false);
+ lock_state_controller_->OnLoginStateChanged(user::LOGGED_IN_USER);
+ lock_state_controller_->OnLockStateChanged(false);
+
+ // While the lock button is down, hold the power button.
+ controller_->OnLockButtonEvent(true, base::TimeTicks::Now());
+ EXPECT_TRUE(test_api_->lock_timer_is_running());
+ controller_->OnPowerButtonEvent(true, base::TimeTicks::Now());
+ EXPECT_TRUE(test_api_->lock_timer_is_running());
+
+ // The lock timer shouldn't be stopped when the lock button is released.
+ controller_->OnLockButtonEvent(false, base::TimeTicks::Now());
+ EXPECT_TRUE(test_api_->lock_timer_is_running());
+ controller_->OnPowerButtonEvent(false, base::TimeTicks::Now());
+ EXPECT_FALSE(test_api_->lock_timer_is_running());
+
+ // Now press the power button first and then the lock button.
+ controller_->OnPowerButtonEvent(true, base::TimeTicks::Now());
+ EXPECT_TRUE(test_api_->lock_timer_is_running());
+ controller_->OnLockButtonEvent(true, base::TimeTicks::Now());
+ EXPECT_TRUE(test_api_->lock_timer_is_running());
+
+ // Releasing the power button should stop the lock timer.
+ controller_->OnPowerButtonEvent(false, base::TimeTicks::Now());
+ EXPECT_FALSE(test_api_->lock_timer_is_running());
+ controller_->OnLockButtonEvent(false, base::TimeTicks::Now());
+ EXPECT_FALSE(test_api_->lock_timer_is_running());
+}
+
+// When the screen is locked without going through the usual power-button
+// slow-close path (e.g. via the wrench menu), test that we still show the
+// fast-close animation.
+TEST_F(PowerButtonControllerTest, LockWithoutButton) {
+ lock_state_controller_->OnLoginStateChanged(user::LOGGED_IN_USER);
+ lock_state_controller_->OnStartingLock();
+ EXPECT_TRUE(
+ animator_api_->ContainersAreAnimated(
+ internal::SessionStateAnimator::NON_LOCK_SCREEN_CONTAINERS,
+ internal::SessionStateAnimator::ANIMATION_FULL_CLOSE));
+}
+
+// When we hear that the process is exiting but we haven't had a chance to
+// display an animation, we should just blank the screen.
+TEST_F(PowerButtonControllerTest, ShutdownWithoutButton) {
+ lock_state_controller_->OnLoginStateChanged(user::LOGGED_IN_USER);
+ lock_state_controller_->OnAppTerminating();
+ EXPECT_TRUE(
+ animator_api_->ContainersAreAnimated(
+ internal::SessionStateAnimator::kAllContainersMask,
+ internal::SessionStateAnimator::ANIMATION_HIDE_IMMEDIATELY));
+ GenerateMouseMoveEvent();
+ EXPECT_FALSE(cursor_visible());
+}
+
+// Test that we display the fast-close animation and shut down when we get an
+// outside request to shut down (e.g. from the login or lock screen).
+TEST_F(PowerButtonControllerTest, RequestShutdownFromLoginScreen) {
+ lock_state_controller_->OnLoginStateChanged(user::LOGGED_IN_NONE);
+ lock_state_controller_->OnLockStateChanged(false);
+ SetUserLoggedIn(false);
+ lock_state_controller_->RequestShutdown();
+ EXPECT_TRUE(
+ animator_api_->ContainersAreAnimated(
+ internal::SessionStateAnimator::NON_LOCK_SCREEN_CONTAINERS,
+ internal::SessionStateAnimator::ANIMATION_HIDE_IMMEDIATELY));
+ EXPECT_TRUE(
+ animator_api_->ContainersAreAnimated(
+ internal::SessionStateAnimator::kAllLockScreenContainersMask,
+ internal::SessionStateAnimator::ANIMATION_FULL_CLOSE));
+ GenerateMouseMoveEvent();
+ EXPECT_FALSE(cursor_visible());
+
+ EXPECT_EQ(0, NumShutdownRequests());
+ EXPECT_TRUE(test_api_->real_shutdown_timer_is_running());
+ test_api_->trigger_real_shutdown_timeout();
+ EXPECT_EQ(1, NumShutdownRequests());
+}
+
+TEST_F(PowerButtonControllerTest, RequestShutdownFromLockScreen) {
+ lock_state_controller_->OnLoginStateChanged(user::LOGGED_IN_USER);
+ lock_state_controller_->OnLockStateChanged(true);
+ state_delegate_->LockScreen();
+ lock_state_controller_->RequestShutdown();
+ EXPECT_TRUE(
+ animator_api_->ContainersAreAnimated(
+ internal::SessionStateAnimator::NON_LOCK_SCREEN_CONTAINERS,
+ internal::SessionStateAnimator::ANIMATION_HIDE_IMMEDIATELY));
+ EXPECT_TRUE(
+ animator_api_->ContainersAreAnimated(
+ internal::SessionStateAnimator::kAllLockScreenContainersMask,
+ internal::SessionStateAnimator::ANIMATION_FULL_CLOSE));
+ GenerateMouseMoveEvent();
+ EXPECT_FALSE(cursor_visible());
+
+ EXPECT_EQ(0, NumShutdownRequests());
+ EXPECT_TRUE(test_api_->real_shutdown_timer_is_running());
+ test_api_->trigger_real_shutdown_timeout();
+ EXPECT_EQ(1, NumShutdownRequests());
+}
+
+TEST_F(PowerButtonControllerTest, RequestAndCancelShutdownFromLockScreen) {
+ lock_state_controller_->OnLoginStateChanged(user::LOGGED_IN_USER);
+ lock_state_controller_->OnLockStateChanged(true);
+ state_delegate_->LockScreen();
+
+ // Press the power button and check that we start the shutdown timer.
+ controller_->OnPowerButtonEvent(true, base::TimeTicks::Now());
+ EXPECT_FALSE(test_api_->lock_timer_is_running());
+ EXPECT_TRUE(test_api_->shutdown_timer_is_running());
+ EXPECT_TRUE(
+ animator_api_->ContainersAreAnimated(
+ internal::SessionStateAnimator::kAllContainersMask,
+ internal::SessionStateAnimator::ANIMATION_PARTIAL_CLOSE));
+
+ // Release the power button before the shutdown timer fires.
+ controller_->OnPowerButtonEvent(false, base::TimeTicks::Now());
+ EXPECT_FALSE(test_api_->shutdown_timer_is_running());
+ EXPECT_TRUE(
+ animator_api_->ContainersAreAnimated(
+ internal::SessionStateAnimator::kAllLockScreenContainersMask,
+ internal::SessionStateAnimator::ANIMATION_UNDO_PARTIAL_CLOSE));
+ EXPECT_TRUE(
+ animator_api_->ContainersAreAnimated(
+ internal::SessionStateAnimator::DESKTOP_BACKGROUND,
+ internal::SessionStateAnimator::ANIMATION_RESTORE));
+}
+
+// Test that we ignore power button presses when the screen is turned off.
+TEST_F(PowerButtonControllerTest, IgnorePowerButtonIfScreenIsOff) {
+ lock_state_controller_->OnLoginStateChanged(user::LOGGED_IN_USER);
+
+ // When the screen brightness is at 0%, we shouldn't do anything in response
+ // to power button presses.
+ controller_->OnScreenBrightnessChanged(0.0);
+ controller_->OnPowerButtonEvent(true, base::TimeTicks::Now());
+ EXPECT_FALSE(test_api_->lock_timer_is_running());
+
+ // After increasing the brightness to 10%, we should start the timer like
+ // usual.
+ controller_->OnScreenBrightnessChanged(10.0);
+ controller_->OnPowerButtonEvent(true, base::TimeTicks::Now());
+ EXPECT_TRUE(test_api_->lock_timer_is_running());
+}
+
+} // namespace test
+} // namespace ash
diff --git a/chromium/ash/wm/property_util.cc b/chromium/ash/wm/property_util.cc
new file mode 100644
index 00000000000..413eeb97a43
--- /dev/null
+++ b/chromium/ash/wm/property_util.cc
@@ -0,0 +1,78 @@
+// Copyright (c) 2012 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.
+
+#include "ash/wm/property_util.h"
+
+#include "ash/ash_export.h"
+#include "ash/screen_ash.h"
+#include "ash/wm/window_properties.h"
+#include "ash/wm/window_util.h"
+#include "ui/aura/client/aura_constants.h"
+#include "ui/aura/root_window.h"
+#include "ui/aura/window.h"
+#include "ui/base/ui_base_types.h"
+#include "ui/gfx/rect.h"
+
+namespace ash {
+
+void SetRestoreBoundsInScreen(aura::Window* window, const gfx::Rect& bounds) {
+ window->SetProperty(aura::client::kRestoreBoundsKey, new gfx::Rect(bounds));
+}
+
+void SetRestoreBoundsInParent(aura::Window* window, const gfx::Rect& bounds) {
+ SetRestoreBoundsInScreen(window,
+ ScreenAsh::ConvertRectToScreen(window->parent(), bounds));
+}
+
+const gfx::Rect* GetRestoreBoundsInScreen(aura::Window* window) {
+ return window->GetProperty(aura::client::kRestoreBoundsKey);
+}
+
+gfx::Rect GetRestoreBoundsInParent(aura::Window* window) {
+ const gfx::Rect* rect = GetRestoreBoundsInScreen(window);
+ if (!rect)
+ return gfx::Rect();
+ return ScreenAsh::ConvertRectFromScreen(window->parent(), *rect);
+}
+
+void ClearRestoreBounds(aura::Window* window) {
+ window->ClearProperty(aura::client::kRestoreBoundsKey);
+}
+
+void SetTrackedByWorkspace(aura::Window* window, bool value) {
+ window->SetProperty(internal::kWindowTrackedByWorkspaceKey, value);
+}
+
+bool GetTrackedByWorkspace(const aura::Window* window) {
+ return window->GetProperty(internal::kWindowTrackedByWorkspaceKey);
+}
+
+void SetIgnoredByShelf(aura::Window* window, bool value) {
+ window->SetProperty(internal::kIgnoredByShelfKey, value);
+}
+
+bool GetIgnoredByShelf(const aura::Window* window) {
+ return window->GetProperty(internal::kIgnoredByShelfKey);
+}
+
+void SetWindowAlwaysRestoresToRestoreBounds(aura::Window* window, bool value) {
+ window->SetProperty(internal::kWindowRestoresToRestoreBounds, value);
+}
+
+bool GetWindowAlwaysRestoresToRestoreBounds(const aura::Window* window) {
+ return window->GetProperty(internal::kWindowRestoresToRestoreBounds);
+}
+
+internal::RootWindowController* GetRootWindowController(
+ const aura::RootWindow* root_window) {
+ return root_window ?
+ root_window->GetProperty(internal::kRootWindowControllerKey) : NULL;
+}
+
+void SetRootWindowController(aura::RootWindow* root_window,
+ internal::RootWindowController* controller) {
+ root_window->SetProperty(internal::kRootWindowControllerKey, controller);
+}
+
+} // namespace ash
diff --git a/chromium/ash/wm/property_util.h b/chromium/ash/wm/property_util.h
new file mode 100644
index 00000000000..32ae278ca9f
--- /dev/null
+++ b/chromium/ash/wm/property_util.h
@@ -0,0 +1,79 @@
+// Copyright (c) 2012 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 ASH_WM_PROPERTY_UTIL_H_
+#define ASH_WM_PROPERTY_UTIL_H_
+
+#include "ash/ash_export.h"
+
+namespace aura {
+class RootWindow;
+class Window;
+}
+
+namespace gfx {
+class Rect;
+}
+
+namespace ash {
+namespace internal {
+class RootWindowController;
+}
+
+// Sets the restore bounds property on |window| in the virtual screen
+// coordinates. Deletes existing bounds value if exists.
+ASH_EXPORT void SetRestoreBoundsInScreen(aura::Window* window,
+ const gfx::Rect& screen_bounds);
+// Same as |SetRestoreBoundsInScreen| except that the bounds is in the
+// parent's coordinates.
+ASH_EXPORT void SetRestoreBoundsInParent(aura::Window* window,
+ const gfx::Rect& parent_bounds);
+
+// Returns the restore bounds property on |window| in the virtual screen
+// coordinates. The bounds can be NULL if the bounds property does not
+// exist for |window|. |window| owns the bounds object.
+ASH_EXPORT const gfx::Rect* GetRestoreBoundsInScreen(aura::Window* window);
+// Same as |GetRestoreBoundsInScreen| except that it returns the
+// bounds in the parent's coordinates.
+ASH_EXPORT gfx::Rect GetRestoreBoundsInParent(aura::Window* window);
+
+// Deletes and clears the restore bounds property on |window|.
+ASH_EXPORT void ClearRestoreBounds(aura::Window* window);
+
+// Sets whether |window| is ignored when determining whether the shelf should
+// be darkened when overlapped.
+ASH_EXPORT void SetIgnoredByShelf(aura::Window* window, bool value);
+ASH_EXPORT bool GetIgnoredByShelf(const aura::Window* window);
+
+// Sets whether |window| should always be restored to the restore bounds
+// (sometimes the workspace layout manager restores the window to its original
+// bounds instead of the restore bounds. Setting this key overrides that
+// behaviour). The flag is reset to the default value after the window is
+// restored.
+ASH_EXPORT void SetWindowAlwaysRestoresToRestoreBounds(aura::Window* window,
+ bool value);
+ASH_EXPORT bool GetWindowAlwaysRestoresToRestoreBounds(
+ const aura::Window* window);
+
+// Sets whether the specified window is tracked by workspace code. Default is
+// true. If set to false the workspace does not switch the current workspace,
+// nor does it attempt to impose constraints on the bounds of the window. This
+// is intended for tab dragging.
+ASH_EXPORT void SetTrackedByWorkspace(aura::Window* window, bool value);
+ASH_EXPORT bool GetTrackedByWorkspace(const aura::Window* window);
+
+// Sets the default value for whether windows persist across all workspaces.
+// The default is false.
+ASH_EXPORT void SetDefaultPersistsAcrossAllWorkspaces(bool value);
+
+// Sets/Gets the RootWindowController for |root_window|.
+ASH_EXPORT void SetRootWindowController(
+ aura::RootWindow* root_window,
+ internal::RootWindowController* controller);
+ASH_EXPORT internal::RootWindowController* GetRootWindowController(
+ const aura::RootWindow* root_window);
+
+}
+
+#endif // ASH_WM_PROPERTY_UTIL_H_
diff --git a/chromium/ash/wm/resize_shadow.cc b/chromium/ash/wm/resize_shadow.cc
new file mode 100644
index 00000000000..cdf573d93b6
--- /dev/null
+++ b/chromium/ash/wm/resize_shadow.cc
@@ -0,0 +1,110 @@
+// Copyright (c) 2012 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.
+
+#include "ash/wm/resize_shadow.h"
+
+#include "base/time/time.h"
+#include "grit/ash_resources.h"
+#include "ui/aura/window.h"
+#include "ui/base/hit_test.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "ui/compositor/layer.h"
+#include "ui/compositor/scoped_layer_animation_settings.h"
+#include "ui/views/corewm/image_grid.h"
+
+namespace {
+
+// Final opacity for resize effect.
+const float kShadowTargetOpacity = 0.25f;
+// Animation time for resize effect in milliseconds.
+const int kShadowAnimationDurationMs = 100;
+
+// Sets up a layer as invisible and fully transparent, without animating.
+void InitLayer(ui::Layer* layer) {
+ layer->SetVisible(false);
+ layer->SetOpacity(0.f);
+}
+
+// Triggers an opacity animation that will make |layer| become |visible|.
+void ShowLayer(ui::Layer* layer, bool visible) {
+ ui::ScopedLayerAnimationSettings settings(layer->GetAnimator());
+ settings.SetTransitionDuration(
+ base::TimeDelta::FromMilliseconds(kShadowAnimationDurationMs));
+ layer->SetOpacity(visible ? kShadowTargetOpacity : 0.f);
+ // Sets the layer visibility after a delay, which will be identical to the
+ // opacity animation duration.
+ layer->SetVisible(visible);
+}
+
+} // namespace
+
+namespace ash {
+namespace internal {
+
+ResizeShadow::ResizeShadow() : last_hit_test_(HTNOWHERE) {}
+
+ResizeShadow::~ResizeShadow() {}
+
+void ResizeShadow::Init(aura::Window* window) {
+ // Set up our image grid and images.
+ ResourceBundle& res = ResourceBundle::GetSharedInstance();
+ image_grid_.reset(new views::corewm::ImageGrid);
+ image_grid_->SetImages(
+ &res.GetImageNamed(IDR_AURA_RESIZE_SHADOW_TOP_LEFT),
+ &res.GetImageNamed(IDR_AURA_RESIZE_SHADOW_TOP),
+ &res.GetImageNamed(IDR_AURA_RESIZE_SHADOW_TOP_RIGHT),
+ &res.GetImageNamed(IDR_AURA_RESIZE_SHADOW_LEFT),
+ NULL,
+ &res.GetImageNamed(IDR_AURA_RESIZE_SHADOW_RIGHT),
+ &res.GetImageNamed(IDR_AURA_RESIZE_SHADOW_BOTTOM_LEFT),
+ &res.GetImageNamed(IDR_AURA_RESIZE_SHADOW_BOTTOM),
+ &res.GetImageNamed(IDR_AURA_RESIZE_SHADOW_BOTTOM_RIGHT));
+ // Initialize all layers to invisible/transparent.
+ InitLayer(image_grid_->top_left_layer());
+ InitLayer(image_grid_->top_layer());
+ InitLayer(image_grid_->top_right_layer());
+ InitLayer(image_grid_->left_layer());
+ InitLayer(image_grid_->right_layer());
+ InitLayer(image_grid_->bottom_left_layer());
+ InitLayer(image_grid_->bottom_layer());
+ InitLayer(image_grid_->bottom_right_layer());
+ // Add image grid as a child of the window's layer so it follows the window
+ // as it moves.
+ window->layer()->Add(image_grid_->layer());
+}
+
+void ResizeShadow::ShowForHitTest(int hit) {
+ // Don't start animations unless something changed.
+ if (hit == last_hit_test_)
+ return;
+ last_hit_test_ = hit;
+
+ // Show affected corners.
+ ShowLayer(image_grid_->top_left_layer(), hit == HTTOPLEFT);
+ ShowLayer(image_grid_->top_right_layer(), hit == HTTOPRIGHT);
+ ShowLayer(image_grid_->bottom_left_layer(), hit == HTBOTTOMLEFT);
+ ShowLayer(image_grid_->bottom_right_layer(), hit == HTBOTTOMRIGHT);
+
+ // Show affected edges.
+ ShowLayer(image_grid_->top_layer(),
+ hit == HTTOPLEFT || hit == HTTOP || hit == HTTOPRIGHT);
+ ShowLayer(image_grid_->left_layer(),
+ hit == HTTOPLEFT || hit == HTLEFT || hit == HTBOTTOMLEFT);
+ ShowLayer(image_grid_->right_layer(),
+ hit == HTTOPRIGHT || hit == HTRIGHT || hit == HTBOTTOMRIGHT);
+ ShowLayer(image_grid_->bottom_layer(),
+ hit == HTBOTTOMLEFT || hit == HTBOTTOM || hit == HTBOTTOMRIGHT);
+}
+
+void ResizeShadow::Hide() {
+ ShowForHitTest(HTNOWHERE);
+}
+
+void ResizeShadow::Layout(const gfx::Rect& content_bounds) {
+ gfx::Rect local_bounds(content_bounds.size());
+ image_grid_->SetContentBounds(local_bounds);
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/wm/resize_shadow.h b/chromium/ash/wm/resize_shadow.h
new file mode 100644
index 00000000000..f61f393b572
--- /dev/null
+++ b/chromium/ash/wm/resize_shadow.h
@@ -0,0 +1,68 @@
+// Copyright (c) 2012 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 ASH_WM_RESIZE_SHADOW_H_
+#define ASH_WM_RESIZE_SHADOW_H_
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+
+namespace aura {
+class Window;
+}
+namespace gfx {
+class Rect;
+}
+namespace ui {
+class Layer;
+}
+namespace views {
+namespace corewm {
+class ImageGrid;
+}
+}
+
+namespace ash {
+namespace internal {
+
+// A class to render the resize edge effect when the user moves their mouse
+// over a sizing edge. This is just a visual effect; the actual resize is
+// handled by the EventFilter.
+class ResizeShadow {
+ public:
+ ResizeShadow();
+ ~ResizeShadow();
+
+ // Initializes the resize effect layers for a given |window|.
+ void Init(aura::Window* window);
+
+ // Shows resize effects for one or more edges based on a |hit_test| code, such
+ // as HTRIGHT or HTBOTTOMRIGHT.
+ void ShowForHitTest(int hit_test);
+
+ // Hides all resize effects.
+ void Hide();
+
+ // Updates the effect positions based on the |bounds| of the window.
+ void Layout(const gfx::Rect& bounds);
+
+ int GetLastHitTestForTest() const {
+ return last_hit_test_;
+ }
+
+ private:
+ // Images for the shadow effect.
+ scoped_ptr<views::corewm::ImageGrid> image_grid_;
+
+ // Hit test value from last call to ShowForHitTest(). Used to prevent
+ // repeatedly triggering the same animations for the same hit.
+ int last_hit_test_;
+
+ DISALLOW_COPY_AND_ASSIGN(ResizeShadow);
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_WM_RESIZE_SHADOW_H_
diff --git a/chromium/ash/wm/resize_shadow_controller.cc b/chromium/ash/wm/resize_shadow_controller.cc
new file mode 100644
index 00000000000..85778d337a8
--- /dev/null
+++ b/chromium/ash/wm/resize_shadow_controller.cc
@@ -0,0 +1,74 @@
+// Copyright (c) 2012 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.
+
+#include "ash/wm/resize_shadow_controller.h"
+
+#include <utility>
+
+#include "ash/wm/resize_shadow.h"
+#include "ui/aura/window.h"
+
+namespace ash {
+namespace internal {
+
+ResizeShadowController::ResizeShadowController() {
+}
+
+ResizeShadowController::~ResizeShadowController() {
+ for (WindowShadowMap::const_iterator it = window_shadows_.begin();
+ it != window_shadows_.end(); ++it) {
+ it->first->RemoveObserver(this);
+ }
+}
+
+void ResizeShadowController::ShowShadow(aura::Window* window, int hit_test) {
+ ResizeShadow* shadow = GetShadowForWindow(window);
+ if (!shadow)
+ shadow = CreateShadow(window);
+ shadow->ShowForHitTest(hit_test);
+}
+
+void ResizeShadowController::HideShadow(aura::Window* window) {
+ ResizeShadow* shadow = GetShadowForWindow(window);
+ if (shadow)
+ shadow->Hide();
+}
+
+ResizeShadow* ResizeShadowController::GetShadowForWindowForTest(
+ aura::Window* window) {
+ return GetShadowForWindow(window);
+}
+
+void ResizeShadowController::OnWindowBoundsChanged(
+ aura::Window* window,
+ const gfx::Rect& old_bounds,
+ const gfx::Rect& new_bounds) {
+ ResizeShadow* shadow = GetShadowForWindow(window);
+ if (shadow)
+ shadow->Layout(new_bounds);
+}
+
+void ResizeShadowController::OnWindowDestroyed(aura::Window* window) {
+ window_shadows_.erase(window);
+}
+
+ResizeShadow* ResizeShadowController::CreateShadow(aura::Window* window) {
+ linked_ptr<ResizeShadow> shadow(new ResizeShadow());
+ window_shadows_.insert(std::make_pair(window, shadow));
+ // Attach the layers to this window.
+ shadow->Init(window);
+ // Ensure initial bounds are correct.
+ shadow->Layout(window->bounds());
+ // Watch for bounds changes.
+ window->AddObserver(this);
+ return shadow.get();
+}
+
+ResizeShadow* ResizeShadowController::GetShadowForWindow(aura::Window* window) {
+ WindowShadowMap::const_iterator it = window_shadows_.find(window);
+ return it != window_shadows_.end() ? it->second.get() : NULL;
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/wm/resize_shadow_controller.h b/chromium/ash/wm/resize_shadow_controller.h
new file mode 100644
index 00000000000..1eccf4c8364
--- /dev/null
+++ b/chromium/ash/wm/resize_shadow_controller.h
@@ -0,0 +1,65 @@
+// Copyright (c) 2012 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 ASH_WM_RESIZE_SHADOW_CONTROLLER_H_
+#define ASH_WM_RESIZE_SHADOW_CONTROLLER_H_
+
+#include <map>
+
+#include "ash/ash_export.h"
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/memory/linked_ptr.h"
+#include "ui/aura/window_observer.h"
+
+namespace aura {
+class Window;
+}
+
+namespace ash {
+namespace internal {
+
+class ResizeShadow;
+
+// ResizeShadowController observes changes to resizable windows and shows
+// a resize handle visual effect when the cursor is near the edges.
+class ASH_EXPORT ResizeShadowController : public aura::WindowObserver {
+ public:
+ ResizeShadowController();
+ virtual ~ResizeShadowController();
+
+ // Shows the appropriate shadow for a given |window| and |hit_test| location.
+ void ShowShadow(aura::Window* window, int hit_test);
+
+ // Hides the shadow for a |window|, if it has one.
+ void HideShadow(aura::Window* window);
+
+ ResizeShadow* GetShadowForWindowForTest(aura::Window* window);
+
+ // aura::WindowObserver overrides:
+ virtual void OnWindowBoundsChanged(
+ aura::Window* window,
+ const gfx::Rect& old_bounds,
+ const gfx::Rect& new_bounds) OVERRIDE;
+ virtual void OnWindowDestroyed(aura::Window* window) OVERRIDE;
+
+ private:
+ typedef std::map<aura::Window*, linked_ptr<ResizeShadow> > WindowShadowMap;
+
+ // Creates a shadow for a given window and returns it. |window_shadows_|
+ // owns the memory.
+ ResizeShadow* CreateShadow(aura::Window* window);
+
+ // Returns the resize shadow for |window| or NULL if no shadow exists.
+ ResizeShadow* GetShadowForWindow(aura::Window* window);
+
+ WindowShadowMap window_shadows_;
+
+ DISALLOW_COPY_AND_ASSIGN(ResizeShadowController);
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_WM_RESIZE_SHADOW_CONTROLLER_H_
diff --git a/chromium/ash/wm/root_window_layout_manager.cc b/chromium/ash/wm/root_window_layout_manager.cc
new file mode 100644
index 00000000000..584bc065718
--- /dev/null
+++ b/chromium/ash/wm/root_window_layout_manager.cc
@@ -0,0 +1,79 @@
+// Copyright (c) 2012 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.
+
+#include "ash/wm/root_window_layout_manager.h"
+
+#include "ash/desktop_background/desktop_background_widget_controller.h"
+#include "ash/root_window_controller.h"
+#include "ash/wm/property_util.h"
+#include "ui/aura/root_window.h"
+#include "ui/compositor/layer.h"
+#include "ui/views/widget/widget.h"
+
+namespace ash {
+namespace internal {
+
+////////////////////////////////////////////////////////////////////////////////
+// RootWindowLayoutManager, public:
+
+RootWindowLayoutManager::RootWindowLayoutManager(aura::Window* owner)
+ : owner_(owner) {
+}
+
+RootWindowLayoutManager::~RootWindowLayoutManager() {
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+// RootWindowLayoutManager, aura::LayoutManager implementation:
+
+void RootWindowLayoutManager::OnWindowResized() {
+ gfx::Rect fullscreen_bounds =
+ gfx::Rect(owner_->bounds().width(), owner_->bounds().height());
+
+ // Resize both our immediate children (the containers-of-containers animated
+ // by PowerButtonController) and their children (the actual containers).
+ aura::Window::Windows::const_iterator i;
+ for (i = owner_->children().begin(); i != owner_->children().end(); ++i) {
+ (*i)->SetBounds(fullscreen_bounds);
+ aura::Window::Windows::const_iterator j;
+ for (j = (*i)->children().begin(); j != (*i)->children().end(); ++j)
+ (*j)->SetBounds(fullscreen_bounds);
+ }
+ RootWindowController* root_window_controller = GetRootWindowController(
+ static_cast<aura::RootWindow*>(owner_));
+ DesktopBackgroundWidgetController* background =
+ root_window_controller->wallpaper_controller();
+
+ if (!background && root_window_controller->animating_wallpaper_controller()) {
+ background = root_window_controller->animating_wallpaper_controller()->
+ GetController(false);
+ }
+ if (background)
+ background->SetBounds(fullscreen_bounds);
+}
+
+void RootWindowLayoutManager::OnWindowAddedToLayout(aura::Window* child) {
+}
+
+void RootWindowLayoutManager::OnWillRemoveWindowFromLayout(
+ aura::Window* child) {
+}
+
+void RootWindowLayoutManager::OnWindowRemovedFromLayout(aura::Window* child) {
+}
+
+void RootWindowLayoutManager::OnChildWindowVisibilityChanged(
+ aura::Window* child,
+ bool visible) {
+}
+
+void RootWindowLayoutManager::SetChildBounds(
+ aura::Window* child,
+ const gfx::Rect& requested_bounds) {
+ SetChildBoundsDirect(child, requested_bounds);
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/wm/root_window_layout_manager.h b/chromium/ash/wm/root_window_layout_manager.h
new file mode 100644
index 00000000000..708e306b75b
--- /dev/null
+++ b/chromium/ash/wm/root_window_layout_manager.h
@@ -0,0 +1,56 @@
+// Copyright (c) 2012 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 ASH_WM_ROOT_WINDOW_LAYOUT_MANAGER_H_
+#define ASH_WM_ROOT_WINDOW_LAYOUT_MANAGER_H_
+
+#include "ash/shell_window_ids.h"
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/memory/scoped_ptr.h"
+#include "ui/aura/layout_manager.h"
+
+namespace aura {
+class Window;
+}
+namespace gfx {
+class Rect;
+}
+namespace ui {
+class Layer;
+}
+namespace views {
+class Widget;
+}
+
+namespace ash {
+namespace internal {
+
+// A layout manager for the root window.
+// Resizes all of its immediate children to fill the bounds of the root window.
+class RootWindowLayoutManager : public aura::LayoutManager {
+ public:
+ explicit RootWindowLayoutManager(aura::Window* owner);
+ virtual ~RootWindowLayoutManager();
+
+ // Overridden from aura::LayoutManager:
+ virtual void OnWindowResized() OVERRIDE;
+ virtual void OnWindowAddedToLayout(aura::Window* child) OVERRIDE;
+ virtual void OnWillRemoveWindowFromLayout(aura::Window* child) OVERRIDE;
+ virtual void OnWindowRemovedFromLayout(aura::Window* child) OVERRIDE;
+ virtual void OnChildWindowVisibilityChanged(aura::Window* child,
+ bool visible) OVERRIDE;
+ virtual void SetChildBounds(aura::Window* child,
+ const gfx::Rect& requested_bounds) OVERRIDE;
+
+ private:
+ aura::Window* owner_;
+
+ DISALLOW_COPY_AND_ASSIGN(RootWindowLayoutManager);
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_WM_ROOT_WINDOW_LAYOUT_MANAGER_H_
diff --git a/chromium/ash/wm/screen_dimmer.cc b/chromium/ash/wm/screen_dimmer.cc
new file mode 100644
index 00000000000..101d69b8d44
--- /dev/null
+++ b/chromium/ash/wm/screen_dimmer.cc
@@ -0,0 +1,69 @@
+// Copyright (c) 2012 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.
+
+#include "ash/wm/screen_dimmer.h"
+
+#include "ash/shell.h"
+#include "base/time/time.h"
+#include "ui/aura/root_window.h"
+#include "ui/compositor/layer.h"
+#include "ui/compositor/scoped_layer_animation_settings.h"
+#include "ui/gfx/rect.h"
+#include "ui/gfx/size.h"
+
+namespace ash {
+namespace internal {
+
+namespace {
+
+// Opacity for |dimming_layer_| when it's dimming the screen.
+const float kDimmingLayerOpacity = 0.4f;
+
+// Duration for dimming animations, in milliseconds.
+const int kDimmingTransitionMs = 200;
+
+} // namespace
+
+ScreenDimmer::ScreenDimmer(aura::RootWindow* root_window)
+ : root_window_(root_window),
+ currently_dimming_(false) {
+ root_window_->AddObserver(this);
+}
+
+ScreenDimmer::~ScreenDimmer() {
+ root_window_->RemoveObserver(this);
+}
+
+void ScreenDimmer::SetDimming(bool should_dim) {
+ if (should_dim == currently_dimming_)
+ return;
+
+ if (!dimming_layer_) {
+ dimming_layer_.reset(new ui::Layer(ui::LAYER_SOLID_COLOR));
+ dimming_layer_->SetColor(SK_ColorBLACK);
+ dimming_layer_->SetOpacity(0.0f);
+ ui::Layer* root_layer = root_window_->layer();
+ dimming_layer_->SetBounds(root_layer->bounds());
+ root_layer->Add(dimming_layer_.get());
+ root_layer->StackAtTop(dimming_layer_.get());
+ }
+
+ currently_dimming_ = should_dim;
+
+ ui::ScopedLayerAnimationSettings scoped_settings(
+ dimming_layer_->GetAnimator());
+ scoped_settings.SetTransitionDuration(
+ base::TimeDelta::FromMilliseconds(kDimmingTransitionMs));
+ dimming_layer_->SetOpacity(should_dim ? kDimmingLayerOpacity : 0.0f);
+}
+
+void ScreenDimmer::OnWindowBoundsChanged(aura::Window* root,
+ const gfx::Rect& old_bounds,
+ const gfx::Rect& new_bounds) {
+ if (dimming_layer_)
+ dimming_layer_->SetBounds(gfx::Rect(root->bounds().size()));
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/wm/screen_dimmer.h b/chromium/ash/wm/screen_dimmer.h
new file mode 100644
index 00000000000..476c27449a3
--- /dev/null
+++ b/chromium/ash/wm/screen_dimmer.h
@@ -0,0 +1,75 @@
+// Copyright (c) 2012 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 ASH_WM_SCREEN_DIMMER_H_
+#define ASH_WM_SCREEN_DIMMER_H_
+
+#include "ash/ash_export.h"
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/memory/scoped_ptr.h"
+#include "ui/aura/window_observer.h"
+
+namespace aura {
+class RootWindow;
+}
+
+namespace ui {
+class Layer;
+}
+
+namespace ash {
+namespace internal {
+
+// ScreenDimmer displays a partially-opaque layer above everything
+// else in the root window to darken the display. It shouldn't be used
+// for long-term brightness adjustments due to performance
+// considerations -- it's only intended for cases where we want to
+// briefly dim the screen (e.g. to indicate to the user that we're
+// about to suspend a machine that lacks an internal backlight that
+// can be adjusted).
+class ASH_EXPORT ScreenDimmer : public aura::WindowObserver {
+ public:
+ class TestApi {
+ public:
+ explicit TestApi(ScreenDimmer* dimmer) : dimmer_(dimmer) {}
+
+ ui::Layer* layer() { return dimmer_->dimming_layer_.get(); }
+
+ private:
+ ScreenDimmer* dimmer_; // not owned
+
+ DISALLOW_COPY_AND_ASSIGN(TestApi);
+ };
+
+ explicit ScreenDimmer(aura::RootWindow* root_window);
+ virtual ~ScreenDimmer();
+
+ // Dim or undim the root window.
+ void SetDimming(bool should_dim);
+
+ // aura::WindowObserver overrides:
+ virtual void OnWindowBoundsChanged(aura::Window* root_window,
+ const gfx::Rect& old_bounds,
+ const gfx::Rect& new_bounds) OVERRIDE;
+
+ private:
+ friend class TestApi;
+
+ aura::RootWindow* root_window_;
+
+ // Partially-opaque layer that's stacked above all of the root window's
+ // children and used to dim the screen. NULL until the first time we dim.
+ scoped_ptr<ui::Layer> dimming_layer_;
+
+ // Are we currently dimming the screen?
+ bool currently_dimming_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScreenDimmer);
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_WM_SCREEN_DIMMER_H_
diff --git a/chromium/ash/wm/screen_dimmer_unittest.cc b/chromium/ash/wm/screen_dimmer_unittest.cc
new file mode 100644
index 00000000000..5b74e0f505e
--- /dev/null
+++ b/chromium/ash/wm/screen_dimmer_unittest.cc
@@ -0,0 +1,79 @@
+// Copyright (c) 2012 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.
+
+#include "ash/wm/screen_dimmer.h"
+
+#include "ash/root_window_controller.h"
+#include "ash/shell.h"
+#include "ash/test/ash_test_base.h"
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "ui/aura/root_window.h"
+#include "ui/compositor/layer.h"
+
+namespace ash {
+namespace test {
+
+class ScreenDimmerTest : public AshTestBase {
+ public:
+ ScreenDimmerTest() : dimmer_(NULL) {}
+ virtual ~ScreenDimmerTest() {}
+
+ virtual void SetUp() OVERRIDE {
+ AshTestBase::SetUp();
+ dimmer_ = Shell::GetPrimaryRootWindowController()->screen_dimmer();
+ test_api_.reset(new internal::ScreenDimmer::TestApi(dimmer_));
+ }
+
+ protected:
+ internal::ScreenDimmer* dimmer_; // not owned
+
+ scoped_ptr<internal::ScreenDimmer::TestApi> test_api_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ScreenDimmerTest);
+};
+
+TEST_F(ScreenDimmerTest, DimAndUndim) {
+ // Don't create a layer until we need to.
+ EXPECT_TRUE(test_api_->layer() == NULL);
+ dimmer_->SetDimming(false);
+ EXPECT_TRUE(test_api_->layer() == NULL);
+
+ // When we enable dimming, the layer should be created and stacked at the top
+ // of the root's children.
+ dimmer_->SetDimming(true);
+ ASSERT_TRUE(test_api_->layer() != NULL);
+ ui::Layer* root_layer = Shell::GetPrimaryRootWindow()->layer();
+ ASSERT_TRUE(!root_layer->children().empty());
+ EXPECT_EQ(test_api_->layer(), root_layer->children().back());
+ EXPECT_TRUE(test_api_->layer()->visible());
+ EXPECT_GT(test_api_->layer()->GetTargetOpacity(), 0.0f);
+
+ // When we disable dimming, the layer should be animated back to full
+ // transparency.
+ dimmer_->SetDimming(false);
+ ASSERT_TRUE(test_api_->layer() != NULL);
+ EXPECT_TRUE(test_api_->layer()->visible());
+ EXPECT_FLOAT_EQ(0.0f, test_api_->layer()->GetTargetOpacity());
+}
+
+TEST_F(ScreenDimmerTest, ResizeLayer) {
+ // The dimming layer should be initially sized to cover the root window.
+ dimmer_->SetDimming(true);
+ ui::Layer* dimming_layer = test_api_->layer();
+ ASSERT_TRUE(dimming_layer != NULL);
+ ui::Layer* root_layer = Shell::GetPrimaryRootWindow()->layer();
+ EXPECT_EQ(gfx::Rect(root_layer->bounds().size()).ToString(),
+ dimming_layer->bounds().ToString());
+
+ // When we resize the root window, the dimming layer should be resized to
+ // match.
+ gfx::Size kNewSize(400, 300);
+ Shell::GetPrimaryRootWindow()->SetHostSize(kNewSize);
+ EXPECT_EQ(kNewSize.ToString(), dimming_layer->bounds().size().ToString());
+}
+
+} // namespace test
+} // namespace ash
diff --git a/chromium/ash/wm/session_state_animator.cc b/chromium/ash/wm/session_state_animator.cc
new file mode 100644
index 00000000000..35ac4cc49d7
--- /dev/null
+++ b/chromium/ash/wm/session_state_animator.cc
@@ -0,0 +1,634 @@
+// Copyright (c) 2012 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.
+
+#include "ash/wm/session_state_animator.h"
+
+#include "ash/shell.h"
+#include "ash/shell_window_ids.h"
+#include "ash/wm/window_animations.h"
+#include "ui/aura/client/aura_constants.h"
+#include "ui/aura/root_window.h"
+#include "ui/compositor/layer_animation_observer.h"
+#include "ui/compositor/layer_animation_sequence.h"
+#include "ui/compositor/scoped_layer_animation_settings.h"
+#include "ui/views/widget/widget.h"
+
+namespace ash {
+namespace internal {
+
+namespace {
+
+// Slightly-smaller size that we scale the screen down to for the pre-lock and
+// pre-shutdown states.
+const float kSlowCloseSizeRatio = 0.95f;
+
+// Maximum opacity of white layer when animating pre-shutdown state.
+const float kPartialFadeRatio = 0.3f;
+
+// Minimum size. Not zero as it causes numeric issues.
+const float kMinimumScale = 1e-4f;
+
+// Returns the transform that should be applied to containers for the slow-close
+// animation.
+gfx::Transform GetSlowCloseTransform() {
+ gfx::Size root_size = Shell::GetPrimaryRootWindow()->bounds().size();
+ gfx::Transform transform;
+ transform.Translate(
+ floor(0.5 * (1.0 - kSlowCloseSizeRatio) * root_size.width() + 0.5),
+ floor(0.5 * (1.0 - kSlowCloseSizeRatio) * root_size.height() + 0.5));
+ transform.Scale(kSlowCloseSizeRatio, kSlowCloseSizeRatio);
+ return transform;
+}
+
+// Returns the transform that should be applied to containers for the fast-close
+// animation.
+gfx::Transform GetFastCloseTransform() {
+ gfx::Size root_size = Shell::GetPrimaryRootWindow()->bounds().size();
+ gfx::Transform transform;
+ transform.Translate(floor(0.5 * root_size.width() + 0.5),
+ floor(0.5 * root_size.height() + 0.5));
+ transform.Scale(kMinimumScale, kMinimumScale);
+ return transform;
+}
+
+// Slowly shrinks |window| to a slightly-smaller size.
+void StartSlowCloseAnimationForWindow(aura::Window* window,
+ base::TimeDelta duration,
+ ui::LayerAnimationObserver* observer) {
+ ui::LayerAnimator* animator = window->layer()->GetAnimator();
+ animator->set_preemption_strategy(
+ ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
+ ui::LayerAnimationSequence* sequence = new ui::LayerAnimationSequence(
+ ui::LayerAnimationElement::CreateTransformElement(
+ GetSlowCloseTransform(),
+ duration));
+ if (observer)
+ sequence->AddObserver(observer);
+ animator->StartAnimation(sequence);
+}
+
+// Quickly undoes the effects of the slow-close animation on |window|.
+void StartUndoSlowCloseAnimationForWindow(
+ aura::Window* window,
+ base::TimeDelta duration,
+ ui::LayerAnimationObserver* observer) {
+ ui::LayerAnimator* animator = window->layer()->GetAnimator();
+ animator->set_preemption_strategy(
+ ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
+ ui::LayerAnimationSequence* sequence = new ui::LayerAnimationSequence(
+ ui::LayerAnimationElement::CreateTransformElement(
+ gfx::Transform(),
+ duration));
+ if (observer)
+ sequence->AddObserver(observer);
+ animator->StartAnimation(sequence);
+}
+
+// Quickly shrinks |window| down to a point in the center of the screen and
+// fades it out to 0 opacity.
+void StartFastCloseAnimationForWindow(aura::Window* window,
+ base::TimeDelta duration,
+ ui::LayerAnimationObserver* observer) {
+ ui::LayerAnimator* animator = window->layer()->GetAnimator();
+ animator->set_preemption_strategy(
+ ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
+ animator->StartAnimation(
+ new ui::LayerAnimationSequence(
+ ui::LayerAnimationElement::CreateTransformElement(
+ GetFastCloseTransform(), duration)));
+ ui::LayerAnimationSequence* sequence = new ui::LayerAnimationSequence(
+ ui::LayerAnimationElement::CreateOpacityElement(0.0, duration));
+ if (observer)
+ sequence->AddObserver(observer);
+ animator->StartAnimation(sequence);
+}
+
+// Fades |window| to |target_opacity| over |duration|.
+void StartPartialFadeAnimation(aura::Window* window,
+ float target_opacity,
+ base::TimeDelta duration,
+ ui::LayerAnimationObserver* observer) {
+ ui::LayerAnimator* animator = window->layer()->GetAnimator();
+ animator->set_preemption_strategy(
+ ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
+ ui::LayerAnimationSequence* sequence = new ui::LayerAnimationSequence(
+ ui::LayerAnimationElement::CreateOpacityElement(
+ target_opacity, duration));
+ if (observer)
+ sequence->AddObserver(observer);
+ animator->StartAnimation(sequence);
+}
+
+// Fades |window| to |opacity| over |duration|.
+void StartOpacityAnimationForWindow(aura::Window* window,
+ float opacity,
+ base::TimeDelta duration,
+ ui::LayerAnimationObserver* observer) {
+ ui::LayerAnimator* animator = window->layer()->GetAnimator();
+ animator->set_preemption_strategy(
+ ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
+ ui::LayerAnimationSequence* sequence = new ui::LayerAnimationSequence(
+ ui::LayerAnimationElement::CreateOpacityElement(opacity, duration));
+ if (observer)
+ sequence->AddObserver(observer);
+ animator->StartAnimation(sequence);
+}
+
+// Makes |window| fully transparent instantaneously.
+void HideWindowImmediately(aura::Window* window,
+ ui::LayerAnimationObserver* observer) {
+ window->layer()->SetOpacity(0.0);
+ if (observer)
+ observer->OnLayerAnimationEnded(NULL);
+}
+
+// Restores |window| to its original position and scale and full opacity
+// instantaneously.
+void RestoreWindow(aura::Window* window, ui::LayerAnimationObserver* observer) {
+ window->layer()->SetTransform(gfx::Transform());
+ window->layer()->SetOpacity(1.0);
+ if (observer)
+ observer->OnLayerAnimationEnded(NULL);
+}
+
+void HideWindow(aura::Window* window,
+ base::TimeDelta duration,
+ bool above,
+ ui::LayerAnimationObserver* observer) {
+ ui::Layer* layer = window->layer();
+ ui::ScopedLayerAnimationSettings settings(layer->GetAnimator());
+
+ settings.SetPreemptionStrategy(
+ ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
+ settings.SetTransitionDuration(duration);
+
+ settings.SetTweenType(ui::Tween::EASE_OUT);
+ SetTransformForScaleAnimation(layer,
+ above ? LAYER_SCALE_ANIMATION_ABOVE : LAYER_SCALE_ANIMATION_BELOW);
+
+ settings.SetTweenType(ui::Tween::EASE_IN_OUT);
+ layer->SetOpacity(0.0f);
+
+ // After the animation completes snap the transform back to the identity,
+ // otherwise any one that asks for screen bounds gets a slightly scaled
+ // version.
+ settings.SetPreemptionStrategy(ui::LayerAnimator::ENQUEUE_NEW_ANIMATION);
+ settings.SetTransitionDuration(base::TimeDelta());
+ layer->SetTransform(gfx::Transform());
+
+ // A bit of a dirty trick: we need to catch the end of the animation we don't
+ // control. So we use two facts we know: which animator will be used and the
+ // target opacity to add "Do nothing" animation sequence.
+ // Unfortunately, we can not just use empty LayerAnimationSequence, because
+ // it does not call NotifyEnded().
+ if (observer) {
+ ui::LayerAnimationSequence* sequence = new ui::LayerAnimationSequence(
+ ui::LayerAnimationElement::CreateOpacityElement(
+ 0.0, base::TimeDelta()));
+ sequence->AddObserver(observer);
+ layer->GetAnimator()->ScheduleAnimation(sequence);
+ }
+}
+
+// Animates |window| to identity transform and full opacity over |duration|.
+void TransformWindowToBaseState(aura::Window* window,
+ base::TimeDelta duration,
+ ui::LayerAnimationObserver* observer) {
+ ui::Layer* layer = window->layer();
+ ui::ScopedLayerAnimationSettings settings(layer->GetAnimator());
+
+ // Animate to target values.
+ settings.SetPreemptionStrategy(
+ ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
+ settings.SetTransitionDuration(duration);
+
+ settings.SetTweenType(ui::Tween::EASE_OUT);
+ layer->SetTransform(gfx::Transform());
+
+ settings.SetTweenType(ui::Tween::EASE_IN_OUT);
+ layer->SetOpacity(1.0f);
+
+ // A bit of a dirty trick: we need to catch the end of the animation we don't
+ // control. So we use two facts we know: which animator will be used and the
+ // target opacity to add "Do nothing" animation sequence.
+ // Unfortunately, we can not just use empty LayerAnimationSequence, because
+ // it does not call NotifyEnded().
+ if (observer) {
+ ui::LayerAnimationSequence* sequence = new ui::LayerAnimationSequence(
+ ui::LayerAnimationElement::CreateOpacityElement(
+ 1.0, base::TimeDelta()));
+ sequence->AddObserver(observer);
+ layer->GetAnimator()->ScheduleAnimation(sequence);
+ }
+}
+
+void ShowWindow(aura::Window* window,
+ base::TimeDelta duration,
+ bool above,
+ ui::LayerAnimationObserver* observer) {
+ ui::Layer* layer = window->layer();
+ ui::ScopedLayerAnimationSettings settings(layer->GetAnimator());
+
+ // Set initial state of animation
+ settings.SetPreemptionStrategy(
+ ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
+ settings.SetTransitionDuration(base::TimeDelta());
+ SetTransformForScaleAnimation(layer,
+ above ? LAYER_SCALE_ANIMATION_ABOVE : LAYER_SCALE_ANIMATION_BELOW);
+
+ TransformWindowToBaseState(window, duration, observer);
+}
+
+// Starts grayscale/brightness animation for |window| over |duration|. Target
+// value for both grayscale and brightness are specified by |target|.
+void StartGrayscaleBrightnessAnimationForWindow(
+ aura::Window* window,
+ float target,
+ base::TimeDelta duration,
+ ui::Tween::Type tween_type,
+ ui::LayerAnimationObserver* observer) {
+ ui::LayerAnimator* animator = window->layer()->GetAnimator();
+
+ scoped_ptr<ui::LayerAnimationSequence> brightness_sequence(
+ new ui::LayerAnimationSequence());
+ scoped_ptr<ui::LayerAnimationSequence> grayscale_sequence(
+ new ui::LayerAnimationSequence());
+
+ scoped_ptr<ui::LayerAnimationElement> brightness_element(
+ ui::LayerAnimationElement::CreateBrightnessElement(
+ target, duration));
+ brightness_element->set_tween_type(tween_type);
+ brightness_sequence->AddElement(brightness_element.release());
+
+ scoped_ptr<ui::LayerAnimationElement> grayscale_element(
+ ui::LayerAnimationElement::CreateGrayscaleElement(
+ target, duration));
+ grayscale_element->set_tween_type(tween_type);
+ grayscale_sequence->AddElement(grayscale_element.release());
+
+ std::vector<ui::LayerAnimationSequence*> animations;
+ animations.push_back(brightness_sequence.release());
+ animations.push_back(grayscale_sequence.release());
+
+ if (observer)
+ animations[0]->AddObserver(observer);
+
+ animator->set_preemption_strategy(
+ ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
+
+ animator->StartTogether(animations);
+}
+
+// Animation observer that will drop animated foreground once animation is
+// finished. It is used in when undoing shutdown animation.
+class CallbackAnimationObserver : public ui::LayerAnimationObserver {
+ public:
+ explicit CallbackAnimationObserver(base::Callback<void(void)> &callback)
+ : callback_(callback) {
+ }
+ virtual ~CallbackAnimationObserver() {
+ }
+
+ private:
+ // Overridden from ui::LayerAnimationObserver:
+ virtual void OnLayerAnimationEnded(ui::LayerAnimationSequence* seq)
+ OVERRIDE {
+ // Drop foreground once animation is over.
+ callback_.Run();
+ delete this;
+ }
+
+ virtual void OnLayerAnimationAborted(ui::LayerAnimationSequence* seq)
+ OVERRIDE {
+ // Drop foreground once animation is over.
+ callback_.Run();
+ delete this;
+ }
+
+ virtual void OnLayerAnimationScheduled(ui::LayerAnimationSequence* seq)
+ OVERRIDE {}
+
+ base::Callback<void(void)> callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(CallbackAnimationObserver);
+};
+
+
+bool IsLayerAnimated(ui::Layer* layer,
+ SessionStateAnimator::AnimationType type) {
+ switch (type) {
+ case SessionStateAnimator::ANIMATION_PARTIAL_CLOSE:
+ if (layer->GetTargetTransform() != GetSlowCloseTransform())
+ return false;
+ break;
+ case SessionStateAnimator::ANIMATION_UNDO_PARTIAL_CLOSE:
+ if (layer->GetTargetTransform() != gfx::Transform())
+ return false;
+ break;
+ case SessionStateAnimator::ANIMATION_FULL_CLOSE:
+ if (layer->GetTargetTransform() != GetFastCloseTransform() ||
+ layer->GetTargetOpacity() > 0.0001)
+ return false;
+ break;
+ case SessionStateAnimator::ANIMATION_FADE_IN:
+ if (layer->GetTargetOpacity() < 0.9999)
+ return false;
+ break;
+ case SessionStateAnimator::ANIMATION_FADE_OUT:
+ if (layer->GetTargetOpacity() > 0.0001)
+ return false;
+ break;
+ case SessionStateAnimator::ANIMATION_HIDE_IMMEDIATELY:
+ if (layer->GetTargetOpacity() > 0.0001)
+ return false;
+ break;
+ case SessionStateAnimator::ANIMATION_RESTORE:
+ if (layer->opacity() < 0.9999 || layer->transform() != gfx::Transform())
+ return false;
+ break;
+ case SessionStateAnimator::ANIMATION_GRAYSCALE_BRIGHTNESS:
+ if ((layer->GetTargetBrightness() < 0.9999) ||
+ (layer->GetTargetGrayscale() < 0.9999))
+ return false;
+ break;
+ case SessionStateAnimator::ANIMATION_UNDO_GRAYSCALE_BRIGHTNESS:
+ if ((layer->GetTargetBrightness() > 0.0001) ||
+ (layer->GetTargetGrayscale() > 0.0001))
+ return false;
+ break;
+ case SessionStateAnimator::ANIMATION_DROP:
+ case SessionStateAnimator::ANIMATION_UNDO_LIFT:
+ //ToDo(antim) : check other effects
+ if (layer->GetTargetOpacity() < 0.9999)
+ return false;
+ break;
+ //ToDo(antim) : check other effects
+ case SessionStateAnimator::ANIMATION_LIFT:
+ if (layer->GetTargetOpacity() > 0.0001)
+ return false;
+ break;
+ case SessionStateAnimator::ANIMATION_RAISE_TO_SCREEN:
+ //ToDo(antim) : check other effects
+ if (layer->GetTargetOpacity() < 0.9999)
+ return false;
+ break;
+ //ToDo(antim) : check other effects
+ case SessionStateAnimator::ANIMATION_LOWER_BELOW_SCREEN:
+ if (layer->GetTargetOpacity() > 0.0001)
+ return false;
+ break;
+ default:
+ NOTREACHED() << "Unhandled animation type " << type;
+ return false;
+ }
+ return true;
+}
+
+} // namespace
+
+bool SessionStateAnimator::TestApi::ContainersAreAnimated(
+ int container_mask, AnimationType type) const {
+ aura::Window::Windows containers;
+ animator_->GetContainers(container_mask, &containers);
+ for (aura::Window::Windows::const_iterator it = containers.begin();
+ it != containers.end(); ++it) {
+ aura::Window* window = *it;
+ ui::Layer* layer = window->layer();
+ if (!IsLayerAnimated(layer, type))
+ return false;
+ }
+ return true;
+}
+
+bool SessionStateAnimator::TestApi::RootWindowIsAnimated(AnimationType type)
+ const {
+ aura::RootWindow* root_window = Shell::GetPrimaryRootWindow();
+ ui::Layer* layer = root_window->layer();
+ return IsLayerAnimated(layer, type);
+}
+
+const int SessionStateAnimator::kAllLockScreenContainersMask =
+ SessionStateAnimator::LOCK_SCREEN_BACKGROUND |
+ SessionStateAnimator::LOCK_SCREEN_CONTAINERS |
+ SessionStateAnimator::LOCK_SCREEN_RELATED_CONTAINERS;
+
+const int SessionStateAnimator::kAllContainersMask =
+ SessionStateAnimator::kAllLockScreenContainersMask |
+ SessionStateAnimator::DESKTOP_BACKGROUND |
+ SessionStateAnimator::LAUNCHER |
+ SessionStateAnimator::NON_LOCK_SCREEN_CONTAINERS;
+
+SessionStateAnimator::SessionStateAnimator() {
+}
+
+SessionStateAnimator::~SessionStateAnimator() {
+}
+
+base::TimeDelta SessionStateAnimator::GetDuration(AnimationSpeed speed) {
+ switch (speed) {
+ case ANIMATION_SPEED_IMMEDIATE:
+ return base::TimeDelta();
+ case ANIMATION_SPEED_UNDOABLE:
+ return base::TimeDelta::FromMilliseconds(400);
+ case ANIMATION_SPEED_REVERT:
+ return base::TimeDelta::FromMilliseconds(150);
+ case ANIMATION_SPEED_FAST:
+ return base::TimeDelta::FromMilliseconds(150);
+ case ANIMATION_SPEED_SHOW_LOCK_SCREEN:
+ return base::TimeDelta::FromMilliseconds(200);
+ case ANIMATION_SPEED_MOVE_WINDOWS:
+ return base::TimeDelta::FromMilliseconds(350);
+ case ANIMATION_SPEED_UNDO_MOVE_WINDOWS:
+ return base::TimeDelta::FromMilliseconds(350);
+ case ANIMATION_SPEED_SHUTDOWN:
+ return base::TimeDelta::FromMilliseconds(1000);
+ case ANIMATION_SPEED_REVERT_SHUTDOWN:
+ return base::TimeDelta::FromMilliseconds(500);
+ }
+ // Satisfy compilers that do not understand that we will return from switch
+ // above anyway.
+ DCHECK(false) << "Unhandled animation speed " << speed;
+ return base::TimeDelta();
+}
+
+// Fills |containers| with the containers described by |container_mask|.
+void SessionStateAnimator::GetContainers(int container_mask,
+ aura::Window::Windows* containers) {
+ aura::RootWindow* root_window = Shell::GetPrimaryRootWindow();
+ containers->clear();
+
+ if (container_mask & DESKTOP_BACKGROUND) {
+ containers->push_back(Shell::GetContainer(
+ root_window,
+ internal::kShellWindowId_DesktopBackgroundContainer));
+ }
+ if (container_mask & LAUNCHER) {
+ containers->push_back(Shell::GetContainer(
+ root_window,
+ internal::kShellWindowId_ShelfContainer));
+ }
+ if (container_mask & NON_LOCK_SCREEN_CONTAINERS) {
+ // TODO(antrim): Figure out a way to eliminate a need to exclude launcher
+ // in such way.
+ aura::Window* non_lock_screen_containers = Shell::GetContainer(
+ root_window,
+ internal::kShellWindowId_NonLockScreenContainersContainer);
+ aura::Window::Windows children = non_lock_screen_containers->children();
+
+ for (aura::Window::Windows::const_iterator it = children.begin();
+ it != children.end(); ++it) {
+ aura::Window* window = *it;
+ if (window->id() == internal::kShellWindowId_ShelfContainer)
+ continue;
+ containers->push_back(window);
+ }
+ }
+ if (container_mask & LOCK_SCREEN_BACKGROUND) {
+ containers->push_back(Shell::GetContainer(
+ root_window,
+ internal::kShellWindowId_LockScreenBackgroundContainer));
+ }
+ if (container_mask & LOCK_SCREEN_CONTAINERS) {
+ containers->push_back(Shell::GetContainer(
+ root_window,
+ internal::kShellWindowId_LockScreenContainersContainer));
+ }
+ if (container_mask & LOCK_SCREEN_RELATED_CONTAINERS) {
+ containers->push_back(Shell::GetContainer(
+ root_window,
+ internal::kShellWindowId_LockScreenRelatedContainersContainer));
+ }
+}
+
+void SessionStateAnimator::StartAnimation(int container_mask,
+ AnimationType type,
+ AnimationSpeed speed) {
+ aura::Window::Windows containers;
+ GetContainers(container_mask, &containers);
+ for (aura::Window::Windows::const_iterator it = containers.begin();
+ it != containers.end(); ++it) {
+ RunAnimationForWindow(*it, type, speed, NULL);
+ }
+}
+
+void SessionStateAnimator::StartAnimationWithCallback(
+ int container_mask,
+ AnimationType type,
+ AnimationSpeed speed,
+ base::Callback<void(void)>& callback) {
+ aura::Window::Windows containers;
+ GetContainers(container_mask, &containers);
+ for (aura::Window::Windows::const_iterator it = containers.begin();
+ it != containers.end(); ++it) {
+ ui::LayerAnimationObserver* observer =
+ new CallbackAnimationObserver(callback);
+ RunAnimationForWindow(*it, type, speed, observer);
+ }
+}
+
+void SessionStateAnimator::StartAnimationWithObserver(
+ int container_mask,
+ AnimationType type,
+ AnimationSpeed speed,
+ ui::LayerAnimationObserver* observer) {
+ aura::Window::Windows containers;
+ GetContainers(container_mask, &containers);
+ for (aura::Window::Windows::const_iterator it = containers.begin();
+ it != containers.end(); ++it) {
+ RunAnimationForWindow(*it, type, speed, observer);
+ }
+}
+
+void SessionStateAnimator::StartGlobalAnimation(AnimationType type,
+ AnimationSpeed speed) {
+ aura::RootWindow* root_window = Shell::GetPrimaryRootWindow();
+ RunAnimationForWindow(root_window, type, speed, NULL);
+}
+
+void SessionStateAnimator::RunAnimationForWindow(
+ aura::Window* window,
+ AnimationType type,
+ AnimationSpeed speed,
+ ui::LayerAnimationObserver* observer) {
+ base::TimeDelta duration = GetDuration(speed);
+
+ switch (type) {
+ case ANIMATION_PARTIAL_CLOSE:
+ StartSlowCloseAnimationForWindow(window, duration, observer);
+ break;
+ case ANIMATION_UNDO_PARTIAL_CLOSE:
+ StartUndoSlowCloseAnimationForWindow(window, duration, observer);
+ break;
+ case ANIMATION_FULL_CLOSE:
+ StartFastCloseAnimationForWindow(window, duration, observer);
+ break;
+ case ANIMATION_FADE_IN:
+ StartOpacityAnimationForWindow(window, 1.0, duration, observer);
+ break;
+ case ANIMATION_FADE_OUT:
+ StartOpacityAnimationForWindow(window, 0.0, duration, observer);
+ break;
+ case ANIMATION_HIDE_IMMEDIATELY:
+ DCHECK_EQ(speed, ANIMATION_SPEED_IMMEDIATE);
+ HideWindowImmediately(window, observer);
+ break;
+ case ANIMATION_RESTORE:
+ DCHECK_EQ(speed, ANIMATION_SPEED_IMMEDIATE);
+ RestoreWindow(window, observer);
+ break;
+ case ANIMATION_LIFT:
+ HideWindow(window, duration, true, observer);
+ break;
+ case ANIMATION_DROP:
+ ShowWindow(window, duration, true, observer);
+ break;
+ case ANIMATION_UNDO_LIFT:
+ TransformWindowToBaseState(window, duration, observer);
+ break;
+ case ANIMATION_RAISE_TO_SCREEN:
+ ShowWindow(window, duration, false, observer);
+ break;
+ case ANIMATION_LOWER_BELOW_SCREEN:
+ HideWindow(window, duration, false, observer);
+ break;
+ case ANIMATION_PARTIAL_FADE_IN:
+ StartPartialFadeAnimation(
+ window, kPartialFadeRatio, duration, observer);
+ break;
+ case ANIMATION_UNDO_PARTIAL_FADE_IN:
+ StartPartialFadeAnimation(window, 0.0, duration, observer);
+ break;
+ case ANIMATION_FULL_FADE_IN:
+ StartPartialFadeAnimation(window, 1.0, duration, observer);
+ break;
+ case ANIMATION_GRAYSCALE_BRIGHTNESS:
+ StartGrayscaleBrightnessAnimationForWindow(
+ window, 1.0, duration, ui::Tween::EASE_IN, observer);
+ break;
+ case ANIMATION_UNDO_GRAYSCALE_BRIGHTNESS:
+ StartGrayscaleBrightnessAnimationForWindow(
+ window, 0.0, duration, ui::Tween::EASE_IN_OUT, observer);
+ break;
+ }
+}
+
+void SessionStateAnimator::CreateForeground() {
+ if (foreground_)
+ return;
+ aura::Window* window = Shell::GetContainer(
+ Shell::GetPrimaryRootWindow(),
+ internal::kShellWindowId_PowerButtonAnimationContainer);
+ HideWindowImmediately(window, NULL);
+ foreground_.reset(
+ new ColoredWindowController(window, "SessionStateAnimatorForeground"));
+ foreground_->SetColor(SK_ColorWHITE);
+ foreground_->GetWidget()->Show();
+}
+
+void SessionStateAnimator::DropForeground() {
+ foreground_.reset();
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/wm/session_state_animator.h b/chromium/ash/wm/session_state_animator.h
new file mode 100644
index 00000000000..19ead2f26cb
--- /dev/null
+++ b/chromium/ash/wm/session_state_animator.h
@@ -0,0 +1,191 @@
+// Copyright (c) 2012 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 ASH_WM_SESSION_STATE_ANIMATOR_H_
+#define ASH_WM_SESSION_STATE_ANIMATOR_H_
+
+#include "ash/ash_export.h"
+#include "ash/wm/workspace/colored_window_controller.h"
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/timer/timer.h"
+#include "ui/aura/window.h"
+#include "ui/compositor/layer_animation_observer.h"
+
+namespace gfx {
+class Rect;
+class Size;
+}
+
+namespace ui {
+class Layer;
+}
+
+namespace ash {
+namespace internal {
+
+// Displays onscreen animations for session state changes (lock/unlock, sign
+// out, shut down).
+class ASH_EXPORT SessionStateAnimator {
+ public:
+ // Animations that can be applied to groups of containers.
+ enum AnimationType {
+ ANIMATION_PARTIAL_CLOSE = 0,
+ ANIMATION_UNDO_PARTIAL_CLOSE,
+ ANIMATION_FULL_CLOSE,
+ ANIMATION_FADE_IN,
+ ANIMATION_FADE_OUT,
+ ANIMATION_HIDE_IMMEDIATELY,
+ ANIMATION_RESTORE,
+ // Animations that raise/lower windows to/from area "in front" of the
+ // screen.
+ ANIMATION_LIFT,
+ ANIMATION_UNDO_LIFT,
+ ANIMATION_DROP,
+ // Animations that raise/lower windows from/to area "behind" of the screen.
+ ANIMATION_RAISE_TO_SCREEN,
+ ANIMATION_LOWER_BELOW_SCREEN,
+ ANIMATION_PARTIAL_FADE_IN,
+ ANIMATION_UNDO_PARTIAL_FADE_IN,
+ ANIMATION_FULL_FADE_IN,
+ ANIMATION_GRAYSCALE_BRIGHTNESS,
+ ANIMATION_UNDO_GRAYSCALE_BRIGHTNESS,
+ };
+
+ // Constants for determining animation speed.
+ enum AnimationSpeed {
+ // Immediately change state.
+ ANIMATION_SPEED_IMMEDIATE = 0,
+ // Speed for animations associated with user action that can be undone.
+ // Used for pre-lock and pre-shutdown animations.
+ ANIMATION_SPEED_UNDOABLE,
+ // Speed for animation that reverts undoable action. Used for aborting
+ // pre-lock and pre-shutdown animations.
+ ANIMATION_SPEED_REVERT,
+ // Speed for user action that can not be undone, Used for lock and shutdown
+ // animations requested via menus/shortcuts and for animating remaining
+ // parts of partial lock/shutdown animations.
+ ANIMATION_SPEED_FAST,
+ // Speed for lock screen appearance in "old" animation set.
+ ANIMATION_SPEED_SHOW_LOCK_SCREEN,
+ // Speed for workspace-like animations in "new" animation set.
+ ANIMATION_SPEED_MOVE_WINDOWS,
+ // Speed for undoing workspace-like animations in "new" animation set.
+ ANIMATION_SPEED_UNDO_MOVE_WINDOWS,
+ // Speed for shutdown in "new" animation set.
+ ANIMATION_SPEED_SHUTDOWN,
+ // Speed for reverting shutdown in "new" animation set.
+ ANIMATION_SPEED_REVERT_SHUTDOWN,
+ };
+
+ // Specific containers or groups of containers that can be animated.
+ enum Container {
+ DESKTOP_BACKGROUND = 1 << 0,
+ LAUNCHER = 1 << 1,
+
+ // All user session related containers including system background but
+ // not including desktop background (wallpaper).
+ NON_LOCK_SCREEN_CONTAINERS = 1 << 2,
+
+ // Desktop wallpaper is moved to this layer when screen is locked.
+ // This layer is excluded from lock animation so that wallpaper stays as is,
+ // user session windows are hidden and lock UI is shown on top of it.
+ // This layer is included in shutdown animation.
+ LOCK_SCREEN_BACKGROUND = 1 << 3,
+
+ // Lock screen and lock screen modal containers.
+ LOCK_SCREEN_CONTAINERS = 1 << 4,
+
+ // Multiple system layers belong here like status, menu, tooltip
+ // and overlay layers.
+ LOCK_SCREEN_RELATED_CONTAINERS = 1 << 5,
+ };
+
+ // Helper class used by tests to access internal state.
+ class ASH_EXPORT TestApi {
+ public:
+ explicit TestApi(SessionStateAnimator* animator)
+ : animator_(animator) {}
+
+ // Returns true if containers of a given |container_mask|
+ // were last animated with |type| (probably; the analysis is fairly ad-hoc).
+ // |container_mask| is a bitfield of a Container.
+ bool ContainersAreAnimated(int container_mask, AnimationType type) const;
+
+ // Returns true if root window was last animated with |type| (probably;
+ // the analysis is fairly ad-hoc).
+ bool RootWindowIsAnimated(AnimationType type) const;
+
+ private:
+ SessionStateAnimator* animator_; // not owned
+
+ DISALLOW_COPY_AND_ASSIGN(TestApi);
+ };
+
+ // A bitfield mask including LOCK_SCREEN_WALLPAPER,
+ // LOCK_SCREEN_CONTAINERS, and LOCK_SCREEN_RELATED_CONTAINERS.
+ const static int kAllLockScreenContainersMask;
+
+ // A bitfield mask of all containers.
+ const static int kAllContainersMask;
+
+ SessionStateAnimator();
+ virtual ~SessionStateAnimator();
+
+ // Reports animation duration for |speed|.
+ static base::TimeDelta GetDuration(AnimationSpeed speed);
+
+ // Fills |containers| with the containers included in |container_mask|.
+ static void GetContainers(int container_mask,
+ aura::Window::Windows* containers);
+
+ // Create |foreground_| layer if it doesn't already exist, but makes it
+ // completely transparent.
+ void CreateForeground();
+ // Destroy |foreground_| when it is not needed anymore.
+ void DropForeground();
+
+ // Apply animation |type| to all containers included in |container_mask| with
+ // specified |speed|.
+ void StartAnimation(int container_mask,
+ AnimationType type,
+ AnimationSpeed speed);
+
+ // Apply animation |type| to all containers included in |container_mask| with
+ // specified |speed| and call a |callback| at the end of the animation, if it
+ // is not null.
+ void StartAnimationWithCallback(int container_mask,
+ AnimationType type,
+ AnimationSpeed speed,
+ base::Callback<void(void)>& callback);
+
+// Apply animation |type| to all containers included in |container_mask| with
+// specified |speed| and add |observer| to all animations.
+ void StartAnimationWithObserver(int container_mask,
+ AnimationType type,
+ AnimationSpeed speed,
+ ui::LayerAnimationObserver* observer);
+
+ // Applies animation |type| whith specified |speed| to the root container.
+ void StartGlobalAnimation(AnimationType type,
+ AnimationSpeed speed);
+
+ // Apply animation |type| to window |window| with |speed| and add |observer|
+ // if it is not NULL to the last animation sequence.
+ void RunAnimationForWindow(aura::Window* window,
+ AnimationType type,
+ AnimationSpeed speed,
+ ui::LayerAnimationObserver* observer);
+
+ // White foreground that is used during shutdown animation to "fade
+ // everything into white".
+ scoped_ptr<ColoredWindowController> foreground_;
+
+ DISALLOW_COPY_AND_ASSIGN(SessionStateAnimator);
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_WM_SESSION_STATE_ANIMATOR_H_
diff --git a/chromium/ash/wm/session_state_controller_impl.cc b/chromium/ash/wm/session_state_controller_impl.cc
new file mode 100644
index 00000000000..dbd97a7b75c
--- /dev/null
+++ b/chromium/ash/wm/session_state_controller_impl.cc
@@ -0,0 +1,344 @@
+// Copyright (c) 2012 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.
+
+#include "ash/wm/session_state_controller_impl.h"
+
+#include "ash/ash_switches.h"
+#include "ash/cancel_mode.h"
+#include "ash/shell.h"
+#include "ash/shell_delegate.h"
+#include "ash/shell_window_ids.h"
+#include "ash/wm/session_state_animator.h"
+#include "base/command_line.h"
+#include "ui/aura/root_window.h"
+#include "ui/views/corewm/compound_event_filter.h"
+
+#if defined(OS_CHROMEOS)
+#include "base/chromeos/chromeos_version.h"
+#endif
+
+namespace ash {
+
+SessionStateControllerImpl::TestApi::TestApi(
+ SessionStateControllerImpl* controller)
+ : controller_(controller) {
+}
+
+SessionStateControllerImpl::TestApi::~TestApi() {
+}
+
+SessionStateControllerImpl::SessionStateControllerImpl()
+ : login_status_(user::LOGGED_IN_NONE),
+ system_is_locked_(false),
+ shutting_down_(false),
+ shutdown_after_lock_(false) {
+ Shell::GetPrimaryRootWindow()->AddRootWindowObserver(this);
+}
+
+SessionStateControllerImpl::~SessionStateControllerImpl() {
+ Shell::GetPrimaryRootWindow()->RemoveRootWindowObserver(this);
+}
+
+void SessionStateControllerImpl::OnLoginStateChanged(user::LoginStatus status) {
+ if (status != user::LOGGED_IN_LOCKED)
+ login_status_ = status;
+ system_is_locked_ = (status == user::LOGGED_IN_LOCKED);
+}
+
+void SessionStateControllerImpl::OnAppTerminating() {
+ // If we hear that Chrome is exiting but didn't request it ourselves, all we
+ // can really hope for is that we'll have time to clear the screen.
+ if (!shutting_down_) {
+ shutting_down_ = true;
+ Shell* shell = ash::Shell::GetInstance();
+ shell->env_filter()->set_cursor_hidden_by_filter(false);
+ shell->cursor_manager()->HideCursor();
+ animator_->StartAnimation(
+ internal::SessionStateAnimator::kAllContainersMask,
+ internal::SessionStateAnimator::ANIMATION_HIDE_IMMEDIATELY,
+ internal::SessionStateAnimator::ANIMATION_SPEED_IMMEDIATE);
+ }
+}
+
+void SessionStateControllerImpl::OnLockStateChanged(bool locked) {
+ if (shutting_down_ || (system_is_locked_ == locked))
+ return;
+
+ system_is_locked_ = locked;
+
+ if (locked) {
+ animator_->StartAnimation(
+ internal::SessionStateAnimator::LOCK_SCREEN_CONTAINERS,
+ internal::SessionStateAnimator::ANIMATION_FADE_IN,
+ internal::SessionStateAnimator::ANIMATION_SPEED_SHOW_LOCK_SCREEN);
+ DispatchCancelMode();
+ FOR_EACH_OBSERVER(LockStateObserver, observers_,
+ OnLockStateEvent(LockStateObserver::EVENT_LOCK_ANIMATION_STARTED));
+ lock_timer_.Stop();
+ lock_fail_timer_.Stop();
+
+ if (shutdown_after_lock_) {
+ shutdown_after_lock_ = false;
+ StartLockToShutdownTimer();
+ }
+ } else {
+ animator_->StartAnimation(
+ internal::SessionStateAnimator::DESKTOP_BACKGROUND |
+ internal::SessionStateAnimator::LAUNCHER |
+ internal::SessionStateAnimator::NON_LOCK_SCREEN_CONTAINERS,
+ internal::SessionStateAnimator::ANIMATION_RESTORE,
+ internal::SessionStateAnimator::ANIMATION_SPEED_IMMEDIATE);
+ }
+}
+
+void SessionStateControllerImpl::OnStartingLock() {
+ if (shutting_down_ || system_is_locked_)
+ return;
+
+ animator_->StartAnimation(
+ internal::SessionStateAnimator::LAUNCHER,
+ internal::SessionStateAnimator::ANIMATION_HIDE_IMMEDIATELY,
+ internal::SessionStateAnimator::ANIMATION_SPEED_IMMEDIATE);
+
+ animator_->StartAnimation(
+ internal::SessionStateAnimator::NON_LOCK_SCREEN_CONTAINERS,
+ internal::SessionStateAnimator::ANIMATION_FULL_CLOSE,
+ internal::SessionStateAnimator::ANIMATION_SPEED_FAST);
+
+ DispatchCancelMode();
+ FOR_EACH_OBSERVER(LockStateObserver, observers_,
+ OnLockStateEvent(LockStateObserver::EVENT_LOCK_ANIMATION_STARTED));
+
+ // Hide the screen locker containers so we can make them fade in later.
+ animator_->StartAnimation(
+ internal::SessionStateAnimator::LOCK_SCREEN_CONTAINERS,
+ internal::SessionStateAnimator::ANIMATION_HIDE_IMMEDIATELY,
+ internal::SessionStateAnimator::ANIMATION_SPEED_IMMEDIATE);
+}
+
+void SessionStateControllerImpl::StartLockAnimationAndLockImmediately() {
+ animator_->StartAnimation(
+ internal::SessionStateAnimator::NON_LOCK_SCREEN_CONTAINERS,
+ internal::SessionStateAnimator::ANIMATION_PARTIAL_CLOSE,
+ internal::SessionStateAnimator::ANIMATION_SPEED_UNDOABLE);
+ DispatchCancelMode();
+ FOR_EACH_OBSERVER(LockStateObserver, observers_,
+ OnLockStateEvent(LockStateObserver::EVENT_LOCK_ANIMATION_STARTED));
+ OnLockTimeout();
+}
+
+void SessionStateControllerImpl::StartLockAnimation(bool shutdown_after_lock) {
+ shutdown_after_lock_ = shutdown_after_lock;
+
+ animator_->StartAnimation(
+ internal::SessionStateAnimator::NON_LOCK_SCREEN_CONTAINERS,
+ internal::SessionStateAnimator::ANIMATION_PARTIAL_CLOSE,
+ internal::SessionStateAnimator::ANIMATION_SPEED_UNDOABLE);
+ DispatchCancelMode();
+ FOR_EACH_OBSERVER(LockStateObserver, observers_,
+ OnLockStateEvent(LockStateObserver::EVENT_PRELOCK_ANIMATION_STARTED));
+ StartLockTimer();
+}
+
+void SessionStateControllerImpl::StartShutdownAnimation() {
+ animator_->StartAnimation(
+ internal::SessionStateAnimator::kAllContainersMask,
+ internal::SessionStateAnimator::ANIMATION_PARTIAL_CLOSE,
+ internal::SessionStateAnimator::ANIMATION_SPEED_UNDOABLE);
+
+ StartPreShutdownAnimationTimer();
+}
+
+bool SessionStateControllerImpl::LockRequested() {
+ return lock_fail_timer_.IsRunning();
+}
+
+bool SessionStateControllerImpl::ShutdownRequested() {
+ return shutting_down_;
+}
+
+bool SessionStateControllerImpl::CanCancelLockAnimation() {
+ return lock_timer_.IsRunning();
+}
+
+void SessionStateControllerImpl::CancelLockAnimation() {
+ if (!CanCancelLockAnimation())
+ return;
+ shutdown_after_lock_ = false;
+ animator_->StartAnimation(
+ internal::SessionStateAnimator::NON_LOCK_SCREEN_CONTAINERS,
+ internal::SessionStateAnimator::ANIMATION_UNDO_PARTIAL_CLOSE,
+ internal::SessionStateAnimator::ANIMATION_SPEED_REVERT);
+ lock_timer_.Stop();
+}
+
+bool SessionStateControllerImpl::CanCancelShutdownAnimation() {
+ return pre_shutdown_timer_.IsRunning() ||
+ shutdown_after_lock_ ||
+ lock_to_shutdown_timer_.IsRunning();
+}
+
+void SessionStateControllerImpl::CancelShutdownAnimation() {
+ if (!CanCancelShutdownAnimation())
+ return;
+ if (lock_to_shutdown_timer_.IsRunning()) {
+ lock_to_shutdown_timer_.Stop();
+ return;
+ }
+ if (shutdown_after_lock_) {
+ shutdown_after_lock_ = false;
+ return;
+ }
+
+ if (system_is_locked_) {
+ // If we've already started shutdown transition at lock screen
+ // desktop background needs to be restored immediately.
+ animator_->StartAnimation(
+ internal::SessionStateAnimator::DESKTOP_BACKGROUND,
+ internal::SessionStateAnimator::ANIMATION_RESTORE,
+ internal::SessionStateAnimator::ANIMATION_SPEED_IMMEDIATE);
+ animator_->StartAnimation(
+ internal::SessionStateAnimator::kAllLockScreenContainersMask,
+ internal::SessionStateAnimator::ANIMATION_UNDO_PARTIAL_CLOSE,
+ internal::SessionStateAnimator::ANIMATION_SPEED_REVERT);
+ } else {
+ animator_->StartAnimation(
+ internal::SessionStateAnimator::kAllContainersMask,
+ internal::SessionStateAnimator::ANIMATION_UNDO_PARTIAL_CLOSE,
+ internal::SessionStateAnimator::ANIMATION_SPEED_REVERT);
+ }
+ pre_shutdown_timer_.Stop();
+}
+
+void SessionStateControllerImpl::RequestShutdown() {
+ if (!shutting_down_)
+ RequestShutdownImpl();
+}
+
+void SessionStateControllerImpl::RequestShutdownImpl() {
+ DCHECK(!shutting_down_);
+ shutting_down_ = true;
+
+ Shell* shell = ash::Shell::GetInstance();
+ shell->env_filter()->set_cursor_hidden_by_filter(false);
+ shell->cursor_manager()->HideCursor();
+
+ if (login_status_ != user::LOGGED_IN_NONE) {
+ // Hide the other containers before starting the animation.
+ // ANIMATION_FULL_CLOSE will make the screen locker windows partially
+ // transparent, and we don't want the other windows to show through.
+ animator_->StartAnimation(
+ internal::SessionStateAnimator::NON_LOCK_SCREEN_CONTAINERS |
+ internal::SessionStateAnimator::LAUNCHER,
+ internal::SessionStateAnimator::ANIMATION_HIDE_IMMEDIATELY,
+ internal::SessionStateAnimator::ANIMATION_SPEED_IMMEDIATE);
+ animator_->StartAnimation(
+ internal::SessionStateAnimator::kAllLockScreenContainersMask,
+ internal::SessionStateAnimator::ANIMATION_FULL_CLOSE,
+ internal::SessionStateAnimator::ANIMATION_SPEED_FAST);
+ } else {
+ animator_->StartAnimation(
+ internal::SessionStateAnimator::kAllContainersMask,
+ internal::SessionStateAnimator::ANIMATION_FULL_CLOSE,
+ internal::SessionStateAnimator::ANIMATION_SPEED_FAST);
+ }
+ StartRealShutdownTimer();
+}
+
+void SessionStateControllerImpl::OnRootWindowHostCloseRequested(
+ const aura::RootWindow*) {
+ Shell::GetInstance()->delegate()->Exit();
+}
+
+void SessionStateControllerImpl::StartLockTimer() {
+ lock_timer_.Stop();
+ lock_timer_.Start(
+ FROM_HERE,
+ animator_->GetDuration(
+ internal::SessionStateAnimator::ANIMATION_SPEED_UNDOABLE),
+ this, &SessionStateControllerImpl::OnLockTimeout);
+}
+
+void SessionStateControllerImpl::OnLockTimeout() {
+ delegate_->RequestLockScreen();
+ lock_fail_timer_.Start(
+ FROM_HERE,
+ base::TimeDelta::FromMilliseconds(kLockFailTimeoutMs),
+ this, &SessionStateControllerImpl::OnLockFailTimeout);
+}
+
+void SessionStateControllerImpl::OnLockFailTimeout() {
+ DCHECK(!system_is_locked_);
+ // Undo lock animation.
+ animator_->StartAnimation(
+ internal::SessionStateAnimator::LAUNCHER |
+ internal::SessionStateAnimator::NON_LOCK_SCREEN_CONTAINERS,
+ internal::SessionStateAnimator::ANIMATION_RESTORE,
+ internal::SessionStateAnimator::ANIMATION_SPEED_IMMEDIATE);
+}
+
+void SessionStateControllerImpl::StartLockToShutdownTimer() {
+ shutdown_after_lock_ = false;
+ lock_to_shutdown_timer_.Stop();
+ lock_to_shutdown_timer_.Start(
+ FROM_HERE,
+ base::TimeDelta::FromMilliseconds(kLockToShutdownTimeoutMs),
+ this, &SessionStateControllerImpl::OnLockToShutdownTimeout);
+}
+
+
+void SessionStateControllerImpl::OnLockToShutdownTimeout() {
+ DCHECK(system_is_locked_);
+ StartShutdownAnimation();
+}
+
+void SessionStateControllerImpl::StartPreShutdownAnimationTimer() {
+ pre_shutdown_timer_.Stop();
+ pre_shutdown_timer_.Start(
+ FROM_HERE,
+ base::TimeDelta::FromMilliseconds(kShutdownTimeoutMs),
+ this, &SessionStateControllerImpl::OnPreShutdownAnimationTimeout);
+}
+
+void SessionStateControllerImpl::OnPreShutdownAnimationTimeout() {
+ if (!shutting_down_)
+ RequestShutdownImpl();
+}
+
+void SessionStateControllerImpl::StartRealShutdownTimer() {
+ base::TimeDelta duration =
+ base::TimeDelta::FromMilliseconds(kShutdownRequestDelayMs);
+ duration += animator_->GetDuration(
+ internal::SessionStateAnimator::ANIMATION_SPEED_FAST);
+
+ real_shutdown_timer_.Start(
+ FROM_HERE,
+ duration,
+ this, &SessionStateControllerImpl::OnRealShutdownTimeout);
+}
+
+void SessionStateControllerImpl::OnRealShutdownTimeout() {
+ DCHECK(shutting_down_);
+#if defined(OS_CHROMEOS)
+ if (!base::chromeos::IsRunningOnChromeOS()) {
+ ShellDelegate* delegate = Shell::GetInstance()->delegate();
+ if (delegate) {
+ delegate->Exit();
+ return;
+ }
+ }
+#endif
+ delegate_->RequestShutdown();
+}
+
+void SessionStateControllerImpl::OnLockScreenHide(base::Closure& callback) {
+ callback.Run();
+}
+
+void SessionStateControllerImpl::SetLockScreenDisplayedCallback(
+ base::Closure& callback) {
+ NOTIMPLEMENTED();
+}
+
+} // namespace ash
diff --git a/chromium/ash/wm/session_state_controller_impl.h b/chromium/ash/wm/session_state_controller_impl.h
new file mode 100644
index 00000000000..3ffc221661a
--- /dev/null
+++ b/chromium/ash/wm/session_state_controller_impl.h
@@ -0,0 +1,195 @@
+// Copyright (c) 2012 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 ASH_WM_SESSION_STATE_CONTROLLER_IMPL_H_
+#define ASH_WM_SESSION_STATE_CONTROLLER_IMPL_H_
+
+#include "ash/ash_export.h"
+#include "ash/shell_observer.h"
+#include "ash/wm/lock_state_controller.h"
+#include "ash/wm/session_state_animator.h"
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/time/time.h"
+#include "base/timer/timer.h"
+#include "ui/aura/root_window_observer.h"
+
+namespace gfx {
+class Rect;
+class Size;
+}
+
+namespace ui {
+class Layer;
+}
+
+namespace ash {
+
+namespace test {
+class PowerButtonControllerTest;
+}
+
+// Displays onscreen animations and locks or suspends the system in response to
+// the power button being pressed or released.
+class ASH_EXPORT SessionStateControllerImpl :
+ public LockStateController {
+ public:
+
+ // Helper class used by tests to access internal state.
+ class ASH_EXPORT TestApi {
+ public:
+ explicit TestApi(SessionStateControllerImpl* controller);
+
+ virtual ~TestApi();
+
+ bool lock_timer_is_running() const {
+ return controller_->lock_timer_.IsRunning();
+ }
+ bool lock_fail_timer_is_running() const {
+ return controller_->lock_fail_timer_.IsRunning();
+ }
+ bool lock_to_shutdown_timer_is_running() const {
+ return controller_->lock_to_shutdown_timer_.IsRunning();
+ }
+ bool shutdown_timer_is_running() const {
+ return controller_->pre_shutdown_timer_.IsRunning();
+ }
+ bool real_shutdown_timer_is_running() const {
+ return controller_->real_shutdown_timer_.IsRunning();
+ }
+
+ void trigger_lock_timeout() {
+ controller_->OnLockTimeout();
+ controller_->lock_timer_.Stop();
+ }
+ void trigger_lock_fail_timeout() {
+ controller_->OnLockFailTimeout();
+ controller_->lock_fail_timer_.Stop();
+ }
+ void trigger_lock_to_shutdown_timeout() {
+ controller_->OnLockToShutdownTimeout();
+ controller_->lock_to_shutdown_timer_.Stop();
+ }
+ void trigger_shutdown_timeout() {
+ controller_->OnPreShutdownAnimationTimeout();
+ controller_->pre_shutdown_timer_.Stop();
+ }
+ void trigger_real_shutdown_timeout() {
+ controller_->OnRealShutdownTimeout();
+ controller_->real_shutdown_timer_.Stop();
+ }
+ private:
+ SessionStateControllerImpl* controller_; // not owned
+
+ DISALLOW_COPY_AND_ASSIGN(TestApi);
+ };
+
+ SessionStateControllerImpl();
+ virtual ~SessionStateControllerImpl();
+
+ // RootWindowObserver override:
+ virtual void OnRootWindowHostCloseRequested(
+ const aura::RootWindow* root) OVERRIDE;
+
+ // ShellObserver overrides:
+ virtual void OnLoginStateChanged(user::LoginStatus status) OVERRIDE;
+ virtual void OnAppTerminating() OVERRIDE;
+ virtual void OnLockStateChanged(bool locked) OVERRIDE;
+
+ // SessionLockStateController overrides:
+ virtual void StartLockAnimation(bool shutdown_after_lock) OVERRIDE;
+
+ virtual void StartShutdownAnimation() OVERRIDE;
+ virtual void StartLockAnimationAndLockImmediately() OVERRIDE;
+
+ virtual bool LockRequested() OVERRIDE;
+ virtual bool ShutdownRequested() OVERRIDE;
+
+ virtual bool CanCancelLockAnimation() OVERRIDE;
+ virtual void CancelLockAnimation() OVERRIDE;
+
+ virtual bool CanCancelShutdownAnimation() OVERRIDE;
+ virtual void CancelShutdownAnimation() OVERRIDE;
+
+ virtual void OnStartingLock() OVERRIDE;
+ virtual void RequestShutdown() OVERRIDE;
+
+ virtual void OnLockScreenHide(base::Closure& callback) OVERRIDE;
+ virtual void SetLockScreenDisplayedCallback(base::Closure& callback) OVERRIDE;
+
+ protected:
+ friend class test::PowerButtonControllerTest;
+
+ private:
+ void RequestShutdownImpl();
+
+ // Starts lock timer.
+ void StartLockTimer();
+
+ // Requests that the screen be locked and starts |lock_fail_timer_|.
+ void OnLockTimeout();
+
+ // Reverts the pre-lock animation, reports the error.
+ void OnLockFailTimeout();
+
+ // Starts timer for gap between lock and shutdown.
+ void StartLockToShutdownTimer();
+
+ // Calls StartShutdownAnimation().
+ void OnLockToShutdownTimeout();
+
+ // Starts timer for undoable shutdown animation.
+ void StartPreShutdownAnimationTimer();
+
+ // Calls RequestShutdownImpl();
+ void OnPreShutdownAnimationTimeout();
+
+ // Starts timer for final shutdown animation.
+ void StartRealShutdownTimer();
+
+ // Requests that the machine be shut down.
+ void OnRealShutdownTimeout();
+
+ // The current login status, or original login status from before we locked..
+ user::LoginStatus login_status_;
+
+ // Current lock status.
+ bool system_is_locked_;
+
+ // Are we in the process of shutting the machine down?
+ bool shutting_down_;
+
+ // Indicates whether controller should proceed to (cancellable) shutdown after
+ // locking.
+ bool shutdown_after_lock_;
+
+ // Started when the user first presses the power button while in a
+ // logged-in-as-a-non-guest-user, unlocked state. When it fires, we lock the
+ // screen.
+ base::OneShotTimer<SessionStateControllerImpl> lock_timer_;
+
+ // Started when we request that the screen be locked. When it fires, we
+ // assume that our request got dropped.
+ base::OneShotTimer<SessionStateControllerImpl> lock_fail_timer_;
+
+ // Started when the screen is locked while the power button is held. Adds a
+ // delay between the appearance of the lock screen and the beginning of the
+ // pre-shutdown animation.
+ base::OneShotTimer<SessionStateControllerImpl> lock_to_shutdown_timer_;
+
+ // Started when we begin displaying the pre-shutdown animation. When it
+ // fires, we start the shutdown animation and get ready to request shutdown.
+ base::OneShotTimer<SessionStateControllerImpl> pre_shutdown_timer_;
+
+ // Started when we display the shutdown animation. When it fires, we actually
+ // request shutdown. Gives the animation time to complete before Chrome, X,
+ // etc. are shut down.
+ base::OneShotTimer<SessionStateControllerImpl> real_shutdown_timer_;
+
+ DISALLOW_COPY_AND_ASSIGN(SessionStateControllerImpl);
+};
+
+} // namespace ash
+
+#endif // ASH_WM_SESSION_STATE_CONTROLLER_IMPL_H_
diff --git a/chromium/ash/wm/stacking_controller.cc b/chromium/ash/wm/stacking_controller.cc
new file mode 100644
index 00000000000..026924c2350
--- /dev/null
+++ b/chromium/ash/wm/stacking_controller.cc
@@ -0,0 +1,153 @@
+// Copyright (c) 2012 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.
+
+#include "ash/wm/stacking_controller.h"
+
+#include "ash/root_window_controller.h"
+#include "ash/session_state_delegate.h"
+#include "ash/shell.h"
+#include "ash/shell_window_ids.h"
+#include "ash/wm/always_on_top_controller.h"
+#include "ash/wm/coordinate_conversion.h"
+#include "ash/wm/property_util.h"
+#include "ash/wm/window_properties.h"
+#include "ui/aura/client/aura_constants.h"
+#include "ui/aura/root_window.h"
+#include "ui/aura/window.h"
+#include "ui/base/ui_base_types.h"
+
+namespace ash {
+namespace {
+
+// Find a root window that matches the |bounds|. If the virtual screen
+// coordinates is enabled and the bounds is specified, the root window
+// that matches the window's bound will be used. Otherwise, it'll
+// return the active root window.
+aura::RootWindow* FindContainerRoot(const gfx::Rect& bounds) {
+ if (bounds.x() == 0 && bounds.y() == 0 && bounds.IsEmpty())
+ return Shell::GetActiveRootWindow();
+ return wm::GetRootWindowMatching(bounds);
+}
+
+aura::Window* GetContainerById(aura::RootWindow* root, int id) {
+ return Shell::GetContainer(root, id);
+}
+
+aura::Window* GetContainerForWindow(aura::Window* window) {
+ aura::Window* container = window->parent();
+ while (container && container->type() != aura::client::WINDOW_TYPE_UNKNOWN)
+ container = container->parent();
+ return container;
+}
+
+bool IsSystemModal(aura::Window* window) {
+ return window->GetProperty(aura::client::kModalKey) == ui::MODAL_TYPE_SYSTEM;
+}
+
+bool HasTransientParentWindow(aura::Window* window) {
+ return window->transient_parent() &&
+ window->transient_parent()->type() != aura::client::WINDOW_TYPE_UNKNOWN;
+}
+
+bool IsPanelAttached(aura::Window* window) {
+ return window->GetProperty(internal::kPanelAttachedKey);
+}
+
+internal::AlwaysOnTopController*
+GetAlwaysOnTopController(aura::RootWindow* root_window) {
+ return GetRootWindowController(root_window)->always_on_top_controller();
+}
+
+} // namespace
+
+////////////////////////////////////////////////////////////////////////////////
+// StackingController, public:
+
+StackingController::StackingController() {
+}
+
+StackingController::~StackingController() {
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// StackingController, aura::StackingClient implementation:
+
+aura::Window* StackingController::GetDefaultParent(aura::Window* context,
+ aura::Window* window,
+ const gfx::Rect& bounds) {
+ aura::RootWindow* target_root = NULL;
+ if (window->transient_parent()) {
+ // Transient window should use the same root as its transient parent.
+ target_root = window->transient_parent()->GetRootWindow();
+ } else {
+ target_root = FindContainerRoot(bounds);
+ }
+
+ switch (window->type()) {
+ case aura::client::WINDOW_TYPE_NORMAL:
+ case aura::client::WINDOW_TYPE_POPUP:
+ if (IsSystemModal(window))
+ return GetSystemModalContainer(target_root, window);
+ else if (HasTransientParentWindow(window))
+ return GetContainerForWindow(window->transient_parent());
+ return GetAlwaysOnTopController(target_root)->GetContainer(window);
+ case aura::client::WINDOW_TYPE_CONTROL:
+ return GetContainerById(
+ target_root, internal::kShellWindowId_UnparentedControlContainer);
+ case aura::client::WINDOW_TYPE_PANEL:
+ if (IsPanelAttached(window))
+ return GetContainerById(target_root,
+ internal::kShellWindowId_PanelContainer);
+ else
+ return GetAlwaysOnTopController(target_root)->GetContainer(window);
+ case aura::client::WINDOW_TYPE_MENU:
+ return GetContainerById(
+ target_root, internal::kShellWindowId_MenuContainer);
+ case aura::client::WINDOW_TYPE_TOOLTIP:
+ return GetContainerById(
+ target_root, internal::kShellWindowId_DragImageAndTooltipContainer);
+ default:
+ NOTREACHED() << "Window " << window->id()
+ << " has unhandled type " << window->type();
+ break;
+ }
+ return NULL;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// StackingController, private:
+
+aura::Window* StackingController::GetSystemModalContainer(
+ aura::RootWindow* root,
+ aura::Window* window) const {
+ DCHECK(IsSystemModal(window));
+
+ // If screen lock is not active and user session is active,
+ // all modal windows are placed into the normal modal container.
+ // In case of missing transient parent (it could happen for alerts from
+ // background pages) assume that the window belongs to user session.
+ SessionStateDelegate* session_state_delegate =
+ Shell::GetInstance()->session_state_delegate();
+ if (!session_state_delegate->IsUserSessionBlocked() ||
+ !window->transient_parent()) {
+ return GetContainerById(root,
+ internal::kShellWindowId_SystemModalContainer);
+ }
+
+ // Otherwise those that originate from LockScreen container and above are
+ // placed in the screen lock modal container.
+ int window_container_id = window->transient_parent()->parent()->id();
+ aura::Window* container = NULL;
+ if (window_container_id < internal::kShellWindowId_LockScreenContainer) {
+ container = GetContainerById(
+ root, internal::kShellWindowId_SystemModalContainer);
+ } else {
+ container = GetContainerById(
+ root, internal::kShellWindowId_LockSystemModalContainer);
+ }
+
+ return container;
+}
+
+} // namespace ash
diff --git a/chromium/ash/wm/stacking_controller.h b/chromium/ash/wm/stacking_controller.h
new file mode 100644
index 00000000000..07003cbeced
--- /dev/null
+++ b/chromium/ash/wm/stacking_controller.h
@@ -0,0 +1,48 @@
+// Copyright (c) 2012 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 ASH_WM_STACKING_CONTROLLER_H_
+#define ASH_WM_STACKING_CONTROLLER_H_
+
+#include "ash/ash_export.h"
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/memory/scoped_ptr.h"
+#include "ui/aura/client/stacking_client.h"
+
+namespace aura{
+class RootWindow;
+}
+
+namespace ash {
+
+namespace internal {
+class AlwaysOnTopController;
+}
+
+class ASH_EXPORT StackingController : public aura::client::StackingClient {
+ public:
+ StackingController();
+ virtual ~StackingController();
+
+ // Overridden from aura::client::StackingClient:
+ virtual aura::Window* GetDefaultParent(aura::Window* context,
+ aura::Window* window,
+ const gfx::Rect& bounds) OVERRIDE;
+
+ private:
+ // Returns corresponding system modal container for a modal window.
+ // If screen lock is not active, all system modal windows are placed into the
+ // normal modal container.
+ // Otherwise those that originate from LockScreen container and above are
+ // placed in the screen lock modal container.
+ aura::Window* GetSystemModalContainer(aura::RootWindow* root,
+ aura::Window* window) const;
+
+ DISALLOW_COPY_AND_ASSIGN(StackingController);
+};
+
+} // namespace ash
+
+#endif // ASH_WM_STACKING_CONTROLLER_H_
diff --git a/chromium/ash/wm/stacking_controller_unittest.cc b/chromium/ash/wm/stacking_controller_unittest.cc
new file mode 100644
index 00000000000..6f5d1f8bdd6
--- /dev/null
+++ b/chromium/ash/wm/stacking_controller_unittest.cc
@@ -0,0 +1,64 @@
+// Copyright (c) 2012 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.
+
+#include "ash/shell.h"
+#include "ash/shell_window_ids.h"
+#include "ash/test/ash_test_base.h"
+#include "ash/wm/property_util.h"
+#include "ash/wm/window_properties.h"
+#include "ash/wm/window_util.h"
+#include "ui/aura/client/aura_constants.h"
+#include "ui/aura/root_window.h"
+#include "ui/aura/window.h"
+
+using aura::Window;
+
+namespace ash {
+namespace internal {
+
+class StackingControllerTest : public test::AshTestBase {
+ public:
+ StackingControllerTest() {}
+ virtual ~StackingControllerTest() {}
+
+ aura::Window* CreateTestWindow() {
+ aura::Window* window = new aura::Window(NULL);
+ window->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_NORMAL);
+ window->SetType(aura::client::WINDOW_TYPE_NORMAL);
+ window->Init(ui::LAYER_TEXTURED);
+ return window;
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(StackingControllerTest);
+};
+
+// Verifies a window with a transient parent is in the same container as its
+// transient parent.
+TEST_F(StackingControllerTest, TransientParent) {
+ // Normal window .
+ scoped_ptr<Window> w2(CreateTestWindow());
+ w2->SetBounds(gfx::Rect(10, 11, 250, 251));
+ aura::Window* launcher = Shell::GetContainer(Shell::GetPrimaryRootWindow(),
+ kShellWindowId_ShelfContainer);
+ launcher->AddChild(w2.get());
+ w2->Show();
+
+ wm::ActivateWindow(w2.get());
+
+ // Window with a transient parent.
+ scoped_ptr<Window> w1(CreateTestWindow());
+ w2->AddTransientChild(w1.get());
+ w1->SetBounds(gfx::Rect(10, 11, 250, 251));
+ SetDefaultParentByPrimaryRootWindow(w1.get());
+ w1->Show();
+ wm::ActivateWindow(w1.get());
+
+ // The window with the transient parent should get added to the same container
+ // as its transient parent.
+ EXPECT_EQ(launcher, w1->parent());
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/wm/status_area_layout_manager.cc b/chromium/ash/wm/status_area_layout_manager.cc
new file mode 100644
index 00000000000..d9e0424d240
--- /dev/null
+++ b/chromium/ash/wm/status_area_layout_manager.cc
@@ -0,0 +1,81 @@
+// Copyright (c) 2012 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.
+
+#include "ash/wm/status_area_layout_manager.h"
+
+#include "ash/shelf/shelf_layout_manager.h"
+#include "ash/shelf/shelf_widget.h"
+#include "ash/system/status_area_widget.h"
+#include "base/auto_reset.h"
+#include "ui/aura/window.h"
+#include "ui/views/widget/widget.h"
+
+namespace ash {
+namespace internal {
+
+////////////////////////////////////////////////////////////////////////////////
+// StatusAreaLayoutManager, public:
+
+StatusAreaLayoutManager::StatusAreaLayoutManager(ShelfWidget* shelf)
+ : in_layout_(false),
+ shelf_(shelf) {
+}
+
+StatusAreaLayoutManager::~StatusAreaLayoutManager() {
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// StatusAreaLayoutManager, aura::LayoutManager implementation:
+
+void StatusAreaLayoutManager::OnWindowResized() {
+ LayoutStatusArea();
+}
+
+void StatusAreaLayoutManager::OnWindowAddedToLayout(aura::Window* child) {
+}
+
+void StatusAreaLayoutManager::OnWillRemoveWindowFromLayout(
+ aura::Window* child) {
+}
+
+void StatusAreaLayoutManager::OnWindowRemovedFromLayout(aura::Window* child) {
+}
+
+void StatusAreaLayoutManager::OnChildWindowVisibilityChanged(
+ aura::Window* child, bool visible) {
+}
+
+void StatusAreaLayoutManager::SetChildBounds(
+ aura::Window* child,
+ const gfx::Rect& requested_bounds) {
+ // Only need to have the shelf do a layout if the child changing is the status
+ // area and the shelf isn't in the process of doing a layout.
+ if (child != shelf_->status_area_widget()->GetNativeView() || in_layout_) {
+ SetChildBoundsDirect(child, requested_bounds);
+ return;
+ }
+
+ // If the size matches, no need to do anything. We don't check the location as
+ // that is managed by the shelf.
+ if (requested_bounds == child->bounds())
+ return;
+
+ SetChildBoundsDirect(child, requested_bounds);
+ LayoutStatusArea();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// StatusAreaLayoutManager, private:
+
+void StatusAreaLayoutManager::LayoutStatusArea() {
+ // Shelf layout manager may be already doing layout.
+ if (shelf_->shelf_layout_manager()->updating_bounds())
+ return;
+
+ base::AutoReset<bool> auto_reset_in_layout(&in_layout_, true);
+ shelf_->shelf_layout_manager()->LayoutShelf();
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/wm/status_area_layout_manager.h b/chromium/ash/wm/status_area_layout_manager.h
new file mode 100644
index 00000000000..8e6944dfacd
--- /dev/null
+++ b/chromium/ash/wm/status_area_layout_manager.h
@@ -0,0 +1,51 @@
+// Copyright (c) 2012 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 ASH_WM_STATUS_AREA_LAYOUT_MANAGER_H_
+#define ASH_WM_STATUS_AREA_LAYOUT_MANAGER_H_
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "ui/aura/layout_manager.h"
+
+namespace ash {
+class ShelfWidget;
+namespace internal {
+
+// StatusAreaLayoutManager is a layout manager responsible for the status area.
+// In any case when status area needs relayout it redirects this call to
+// ShelfLayoutManager.
+class StatusAreaLayoutManager : public aura::LayoutManager {
+ public:
+ explicit StatusAreaLayoutManager(ShelfWidget* shelf);
+ virtual ~StatusAreaLayoutManager();
+
+ // Overridden from aura::LayoutManager:
+ virtual void OnWindowResized() OVERRIDE;
+ virtual void OnWindowAddedToLayout(aura::Window* child) OVERRIDE;
+ virtual void OnWillRemoveWindowFromLayout(aura::Window* child) OVERRIDE;
+ virtual void OnWindowRemovedFromLayout(aura::Window* child) OVERRIDE;
+ virtual void OnChildWindowVisibilityChanged(aura::Window* child,
+ bool visible) OVERRIDE;
+ virtual void SetChildBounds(aura::Window* child,
+ const gfx::Rect& requested_bounds) OVERRIDE;
+
+ private:
+ // Updates layout of the status area. Effectively calls ShelfLayoutManager
+ // to update layout of the shelf.
+ void LayoutStatusArea();
+
+ // True when inside LayoutStatusArea method.
+ // Used to prevent calling itself again from SetChildBounds().
+ bool in_layout_;
+
+ ShelfWidget* shelf_;
+
+ DISALLOW_COPY_AND_ASSIGN(StatusAreaLayoutManager);
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_WM_STATUS_AREA_LAYOUT_MANAGER_H_
diff --git a/chromium/ash/wm/sticky_keys.cc b/chromium/ash/wm/sticky_keys.cc
new file mode 100644
index 00000000000..55c5c9b887c
--- /dev/null
+++ b/chromium/ash/wm/sticky_keys.cc
@@ -0,0 +1,223 @@
+// Copyright 2013 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.
+
+#include "ash/wm/sticky_keys.h"
+
+#include <X11/Xlib.h>
+#undef RootWindow
+
+#include "base/basictypes.h"
+#include "base/debug/stack_trace.h"
+#include "ui/aura/root_window.h"
+#include "ui/base/events/event.h"
+#include "ui/base/keycodes/keyboard_code_conversion.h"
+
+namespace ash {
+
+namespace {
+
+// An implementation of StickyKeysHandler::StickyKeysHandlerDelegate.
+class StickyKeysHandlerDelegateImpl :
+ public StickyKeysHandler::StickyKeysHandlerDelegate {
+ public:
+ StickyKeysHandlerDelegateImpl();
+ virtual ~StickyKeysHandlerDelegateImpl();
+
+ // StickyKeysHandlerDelegate overrides.
+ virtual void DispatchKeyEvent(ui::KeyEvent* event,
+ aura::Window* target) OVERRIDE;
+ private:
+ DISALLOW_COPY_AND_ASSIGN(StickyKeysHandlerDelegateImpl);
+};
+
+StickyKeysHandlerDelegateImpl::StickyKeysHandlerDelegateImpl() {
+}
+
+StickyKeysHandlerDelegateImpl::~StickyKeysHandlerDelegateImpl() {
+}
+
+void StickyKeysHandlerDelegateImpl::DispatchKeyEvent(ui::KeyEvent* event,
+ aura::Window* target) {
+ DCHECK(target);
+ target->GetRootWindow()->AsRootWindowHostDelegate()->OnHostKeyEvent(event);
+}
+
+} // namespace
+
+///////////////////////////////////////////////////////////////////////////////
+// StickyKeys
+StickyKeys::StickyKeys()
+ : shift_sticky_key_(
+ new StickyKeysHandler(ui::EF_SHIFT_DOWN,
+ new StickyKeysHandlerDelegateImpl())),
+ alt_sticky_key_(
+ new StickyKeysHandler(ui::EF_ALT_DOWN,
+ new StickyKeysHandlerDelegateImpl())),
+ ctrl_sticky_key_(
+ new StickyKeysHandler(ui::EF_CONTROL_DOWN,
+ new StickyKeysHandlerDelegateImpl())) {
+}
+
+StickyKeys::~StickyKeys() {
+}
+
+bool StickyKeys::HandleKeyEvent(ui::KeyEvent* event) {
+ return shift_sticky_key_->HandleKeyEvent(event) ||
+ alt_sticky_key_->HandleKeyEvent(event) ||
+ ctrl_sticky_key_->HandleKeyEvent(event);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// StickyKeysHandler
+StickyKeysHandler::StickyKeysHandler(ui::EventFlags target_modifier_flag,
+ StickyKeysHandlerDelegate* delegate)
+ : modifier_flag_(target_modifier_flag),
+ current_state_(DISABLED),
+ keyevent_from_myself_(false),
+ delegate_(delegate) {
+}
+
+StickyKeysHandler::~StickyKeysHandler() {
+}
+
+StickyKeysHandler::StickyKeysHandlerDelegate::StickyKeysHandlerDelegate() {
+}
+
+StickyKeysHandler::StickyKeysHandlerDelegate::~StickyKeysHandlerDelegate() {
+}
+
+bool StickyKeysHandler::HandleKeyEvent(ui::KeyEvent* event) {
+ if (keyevent_from_myself_)
+ return false; // Do not handle self-generated key event.
+ switch (current_state_) {
+ case DISABLED:
+ return HandleDisabledState(event);
+ case ENABLED:
+ return HandleEnabledState(event);
+ case LOCKED:
+ return HandleLockedState(event);
+ }
+ NOTREACHED();
+ return false;
+}
+
+StickyKeysHandler::KeyEventType
+ StickyKeysHandler::TranslateKeyEvent(ui::KeyEvent* event) {
+ bool is_target_key = false;
+ if (event->key_code() == ui::VKEY_SHIFT ||
+ event->key_code() == ui::VKEY_LSHIFT ||
+ event->key_code() == ui::VKEY_RSHIFT) {
+ is_target_key = (modifier_flag_ == ui::EF_SHIFT_DOWN);
+ } else if (event->key_code() == ui::VKEY_CONTROL ||
+ event->key_code() == ui::VKEY_LCONTROL ||
+ event->key_code() == ui::VKEY_RCONTROL) {
+ is_target_key = (modifier_flag_ == ui::EF_CONTROL_DOWN);
+ } else if (event->key_code() == ui::VKEY_MENU ||
+ event->key_code() == ui::VKEY_LMENU ||
+ event->key_code() == ui::VKEY_RMENU) {
+ is_target_key = (modifier_flag_ == ui::EF_ALT_DOWN);
+ } else {
+ return event->type() == ui::ET_KEY_PRESSED ?
+ NORMAL_KEY_DOWN : NORMAL_KEY_UP;
+ }
+
+ if (is_target_key) {
+ return event->type() == ui::ET_KEY_PRESSED ?
+ TARGET_MODIFIER_DOWN : TARGET_MODIFIER_UP;
+ }
+ return event->type() == ui::ET_KEY_PRESSED ?
+ OTHER_MODIFIER_DOWN : OTHER_MODIFIER_UP;
+}
+
+bool StickyKeysHandler::HandleDisabledState(ui::KeyEvent* event) {
+ switch (TranslateKeyEvent(event)) {
+ case TARGET_MODIFIER_UP:
+ current_state_ = ENABLED;
+ modifier_up_event_.reset(event->Copy());
+ return true;
+ case TARGET_MODIFIER_DOWN:
+ case NORMAL_KEY_DOWN:
+ case NORMAL_KEY_UP:
+ case OTHER_MODIFIER_DOWN:
+ case OTHER_MODIFIER_UP:
+ return false;
+ }
+ NOTREACHED();
+ return false;
+}
+
+bool StickyKeysHandler::HandleEnabledState(ui::KeyEvent* event) {
+ switch (TranslateKeyEvent(event)) {
+ case NORMAL_KEY_UP:
+ case TARGET_MODIFIER_DOWN:
+ return true;
+ case TARGET_MODIFIER_UP:
+ current_state_ = LOCKED;
+ modifier_up_event_.reset();
+ return true;
+ case NORMAL_KEY_DOWN: {
+ DCHECK(modifier_up_event_.get());
+ aura::Window* target = static_cast<aura::Window*>(event->target());
+ DCHECK(target);
+
+ current_state_ = DISABLED;
+ AppendModifier(event);
+ // We can't post event, so dispatch current keyboard event first then
+ // dispatch next keyboard event.
+ keyevent_from_myself_ = true;
+ delegate_->DispatchKeyEvent(event, target);
+ delegate_->DispatchKeyEvent(modifier_up_event_.get(), target);
+ keyevent_from_myself_ = false;
+ return true;
+ }
+ case OTHER_MODIFIER_DOWN:
+ case OTHER_MODIFIER_UP:
+ return false;
+ }
+ NOTREACHED();
+ return false;
+}
+
+bool StickyKeysHandler::HandleLockedState(ui::KeyEvent* event) {
+ switch (TranslateKeyEvent(event)) {
+ case TARGET_MODIFIER_DOWN:
+ return true;
+ case TARGET_MODIFIER_UP:
+ current_state_ = DISABLED;
+ return false;
+ case NORMAL_KEY_DOWN:
+ case NORMAL_KEY_UP:
+ AppendModifier(event);
+ return false;
+ case OTHER_MODIFIER_DOWN:
+ case OTHER_MODIFIER_UP:
+ return false;
+ }
+ NOTREACHED();
+ return false;
+}
+
+void StickyKeysHandler::AppendModifier(ui::KeyEvent* event) {
+ XEvent* xev = event->native_event();
+ XKeyEvent* xkey = &(xev->xkey);
+ switch (modifier_flag_) {
+ case ui::EF_CONTROL_DOWN:
+ xkey->state |= ControlMask;
+ break;
+ case ui::EF_ALT_DOWN:
+ xkey->state |= Mod1Mask;
+ break;
+ case ui::EF_SHIFT_DOWN:
+ xkey->state |= ShiftMask;
+ break;
+ default:
+ NOTREACHED();
+ }
+ event->set_flags(event->flags() | modifier_flag_);
+ event->set_character(ui::GetCharacterFromKeyCode(event->key_code(),
+ event->flags()));
+ event->NormalizeFlags();
+}
+
+} // namespace ash
diff --git a/chromium/ash/wm/sticky_keys.h b/chromium/ash/wm/sticky_keys.h
new file mode 100644
index 00000000000..8a4bc68054d
--- /dev/null
+++ b/chromium/ash/wm/sticky_keys.h
@@ -0,0 +1,180 @@
+// Copyright 2013 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 ASH_WM_STICKY_KEYS_H_
+#define ASH_WM_STICKY_KEYS_H_
+
+#include "ash/ash_export.h"
+#include "base/memory/scoped_ptr.h"
+#include "ui/base/events/event_constants.h"
+
+namespace ui {
+class KeyEvent;
+} // namespace ui
+
+namespace aura {
+class Window;
+} // namespace aura
+
+namespace ash {
+
+class StickyKeysHandler;
+
+// StickyKeys is an accessibility feature for users to be able to compose
+// key event with modifier keys without simultaneous key press event. Instead,
+// they can compose modified key events separately pressing each of the keys
+// involved.
+// e.g. Composing Ctrl + T
+// User Action : The KeyEvent widget will receives
+// ----------------------------------------------------------
+// 1. Press Ctrl key : Ctrl Keydown.
+// 2. Release Ctrl key : No event
+// 3. Press T key : T keydown event with ctrl modifier.
+// 4. : Ctrl Keyup
+// 5. Release T key : T keyup without ctrl modifier (Windows behavior)
+//
+// By typing same modifier keys twice, users can generate bunch of modified key
+// events.
+// e.g. To focus tabs consistently by Ctrl + 1, Ctrl + 2 ...
+// User Action : The KeyEvent widget will receives
+// ----------------------------------------------------------
+// 1. Press Ctrl key : Ctrl Keydown
+// 2. Release Ctrl key : No event
+// 3. Press Ctrl key : No event
+// 4. Release Ctrl key : No event
+// 5. Press 1 key : 1 Keydown event with Ctrl modifier.
+// 6. Release 1 key : 1 Keyup event with Ctrl modifier.
+// 7. Press 2 key : 2 Keydown event with Ctrl modifier.
+// 8. Release 2 key : 2 Keyup event with Ctrl modifier.
+// 9. Press Ctrl key : No event
+// 10. Release Ctrl key: Ctrl Keyup
+//
+// In the case of Chrome OS, StickyKeys supports Shift,Alt,Ctrl modifiers. Each
+// handling or state is performed independently.
+//
+// StickyKeys is disabled by default.
+class ASH_EXPORT StickyKeys {
+ public:
+ StickyKeys();
+ ~StickyKeys();
+
+ // Handles keyboard event. Returns true if Sticky key consumes keyboard event.
+ bool HandleKeyEvent(ui::KeyEvent* event);
+
+ private:
+ // Sticky key handlers.
+ scoped_ptr<StickyKeysHandler> shift_sticky_key_;
+ scoped_ptr<StickyKeysHandler> alt_sticky_key_;
+ scoped_ptr<StickyKeysHandler> ctrl_sticky_key_;
+
+ DISALLOW_COPY_AND_ASSIGN(StickyKeys);
+};
+
+// StickyKeysHandler handles key event and performs StickyKeys for specific
+// modifier keys. If monitored keyboard events are recieved, StickyKeysHandler
+// changes internal state. If non modifier keyboard events are received,
+// StickyKeysHandler will append modifier based on internal state. For other
+// events, StickyKeysHandler does nothing.
+//
+// The DISABLED state is default state and any incomming non modifier keyboard
+// events will not be modified. The ENABLED state is one shot modification
+// state. Only next keyboard event will be modified. After that, internal state
+// will be back to DISABLED state with sending modifier keyup event. In the case
+// of LOCKED state, all incomming keyboard events will be modified. The LOCKED
+// state will be back to DISABLED state by next monitoring modifier key.
+//
+// The detailed state flow as follows:
+// Current state
+// | DISABLED | ENABLED | LOCKED |
+// ----------------------------------------------------------------|
+// Modifier KeyDown | noop | noop(*) | noop(*) |
+// Modifier KeyUp | To ENABLED(*) | To LOCKED(*) | To DISABLED |
+// Normal KeyDown | noop | To DISABLED(#) | noop(#) |
+// Normal KeyUp | noop | noop | noop(#) |
+// Other KeyUp/Down | noop | noop | noop |
+//
+// Here, (*) means key event will be consumed by StickyKeys, and (#) means event
+// is modified.
+class ASH_EXPORT StickyKeysHandler {
+ public:
+ class StickyKeysHandlerDelegate {
+ public:
+ StickyKeysHandlerDelegate();
+ virtual ~StickyKeysHandlerDelegate();
+
+ // Dispatches keyboard event synchronously.
+ virtual void DispatchKeyEvent(ui::KeyEvent* event,
+ aura::Window* target) = 0;
+ };
+ // Represents Sticky Key state.
+ enum StickyKeyState {
+ // The sticky key is disabled. Incomming non modifier key events are not
+ // affected.
+ DISABLED,
+ // The sticky key is enabled. Incomming non modifier key down events are
+ // modified with |modifier_flag_|. After that, sticky key state become
+ // DISABLED.
+ ENABLED,
+ // The sticky key is locked. Incomming non modifier key down events are
+ // modified with |modifier_flag_|.
+ LOCKED,
+ };
+
+ // This class takes an ownership of |delegate|.
+ StickyKeysHandler(ui::EventFlags modifier_flag,
+ StickyKeysHandlerDelegate* delegate);
+ ~StickyKeysHandler();
+
+ // Handles key event. Returns true if key is consumed.
+ bool HandleKeyEvent(ui::KeyEvent* event);
+
+ // Returns current internal state.
+ StickyKeyState current_state() const { return current_state_; }
+
+ private:
+ // Represents event type in Sticky Key context.
+ enum KeyEventType {
+ TARGET_MODIFIER_DOWN, // The monitoring modifier key is down.
+ TARGET_MODIFIER_UP, // The monitoring modifier key is up.
+ NORMAL_KEY_DOWN, // The non modifier key is down.
+ NORMAL_KEY_UP, // The non modifier key is up.
+ OTHER_MODIFIER_DOWN, // The modifier key but not monitored key is down.
+ OTHER_MODIFIER_UP, // The modifier key but not monitored key is up.
+ };
+
+ // Translates |event| to sticky keys event type.
+ KeyEventType TranslateKeyEvent(ui::KeyEvent* event);
+
+ // Handles key event in DISABLED state.
+ bool HandleDisabledState(ui::KeyEvent* event);
+
+ // Handles key event in ENABLED state.
+ bool HandleEnabledState(ui::KeyEvent* event);
+
+ // Handles key event in LOCKED state.
+ bool HandleLockedState(ui::KeyEvent* event);
+
+ // Adds |modifier_flags_| into |event|.
+ void AppendModifier(ui::KeyEvent* event);
+
+ // The modifier flag to be monitored and appended.
+ const ui::EventFlags modifier_flag_;
+
+ // The current sticky key status.
+ StickyKeyState current_state_;
+
+ // True if the received key event is sent by StickyKeyHandler.
+ bool keyevent_from_myself_;
+
+ // The modifier up key event to be sent on non modifier key on ENABLED state.
+ scoped_ptr<ui::KeyEvent> modifier_up_event_;
+
+ scoped_ptr<StickyKeysHandlerDelegate> delegate_;
+
+ DISALLOW_COPY_AND_ASSIGN(StickyKeysHandler);
+};
+
+} // namespace ash
+
+#endif // ASH_WM_STICKY_KEYS_H_
diff --git a/chromium/ash/wm/sticky_keys_unittest.cc b/chromium/ash/wm/sticky_keys_unittest.cc
new file mode 100644
index 00000000000..813fe4ebc5d
--- /dev/null
+++ b/chromium/ash/wm/sticky_keys_unittest.cc
@@ -0,0 +1,244 @@
+// Copyright 2013 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.
+
+#include "ash/wm/sticky_keys.h"
+
+#include <X11/Xlib.h>
+#undef None
+#undef Bool
+
+#include "ash/test/ash_test_base.h"
+#include "base/memory/scoped_vector.h"
+#include "ui/base/x/x11_util.h"
+
+namespace ash {
+
+// A stub implementation of EventTarget.
+class StubEventTarget : public ui::EventTarget {
+ public:
+ StubEventTarget() {}
+ virtual ~StubEventTarget() {}
+
+ virtual bool CanAcceptEvent(const ui::Event& event) OVERRIDE {
+ return true;
+ }
+
+ virtual EventTarget* GetParentTarget() OVERRIDE {
+ return NULL;
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(StubEventTarget);
+};
+
+// A testable and StickyKeysHandler.
+class MockStickyKeysHandlerDelegate :
+ public StickyKeysHandler::StickyKeysHandlerDelegate {
+ public:
+ MockStickyKeysHandlerDelegate() {}
+ virtual ~MockStickyKeysHandlerDelegate() {}
+
+ // StickyKeysHandler override.
+ virtual void DispatchKeyEvent(ui::KeyEvent* event,
+ aura::Window* target) OVERRIDE {
+ key_events_.push_back(event->Copy());
+ }
+
+ // Returns the count of dispatched keyboard event.
+ size_t GetKeyEventCount() const {
+ return key_events_.size();
+ }
+
+ // Returns the |index|-th dispatched keyboard event.
+ const ui::KeyEvent* GetKeyEvent(size_t index) const {
+ return key_events_[index];
+ }
+
+ private:
+ ScopedVector<ui::KeyEvent> key_events_;
+
+ DISALLOW_COPY_AND_ASSIGN(MockStickyKeysHandlerDelegate);
+};
+
+class StickyKeysTest : public test::AshTestBase {
+ protected:
+ virtual void SetUp() OVERRIDE {
+ target_.reset(new StubEventTarget());
+ test::AshTestBase::SetUp();
+ }
+
+ virtual void TearDown() OVERRIDE {
+ test::AshTestBase::TearDown();
+ target_.reset();
+ }
+
+ // Generates keyboard event.
+ ui::KeyEvent* GenerateKey(bool is_key_press, ui::KeyboardCode code) {
+ XEvent* xev = new XEvent();
+ ui::InitXKeyEventForTesting(
+ is_key_press ? ui::ET_KEY_PRESSED : ui::ET_KEY_RELEASED,
+ code,
+ 0,
+ xev);
+ xevs_.push_back(xev);
+ ui::KeyEvent* event = new ui::KeyEvent(xev, false);
+ ui::Event::DispatcherApi dispatcher(event);
+ dispatcher.set_target(target_.get());
+ return event;
+ }
+
+ private:
+ scoped_ptr<StubEventTarget> target_;
+ ScopedVector<XEvent> xevs_;
+};
+
+TEST_F(StickyKeysTest, BasicOneshotScenarioTest) {
+ scoped_ptr<ui::KeyEvent> ev;
+ MockStickyKeysHandlerDelegate* mock_delegate =
+ new MockStickyKeysHandlerDelegate();
+ StickyKeysHandler sticky_key(ui::EF_SHIFT_DOWN, mock_delegate);
+
+ EXPECT_EQ(StickyKeysHandler::DISABLED, sticky_key.current_state());
+ ev.reset(GenerateKey(true, ui::VKEY_SHIFT));
+ sticky_key.HandleKeyEvent(ev.get());
+
+ ev.reset(GenerateKey(false, ui::VKEY_SHIFT));
+ sticky_key.HandleKeyEvent(ev.get());
+
+ // By typing Shift key, internal state become ENABLED.
+ EXPECT_EQ(StickyKeysHandler::ENABLED, sticky_key.current_state());
+
+ ev.reset(GenerateKey(true, ui::VKEY_A));
+ sticky_key.HandleKeyEvent(ev.get());
+
+ // Next keyboard event is shift modified.
+ EXPECT_TRUE(ev->flags() & ui::EF_SHIFT_DOWN);
+
+ ev.reset(GenerateKey(false, ui::VKEY_A));
+ sticky_key.HandleKeyEvent(ev.get());
+
+ EXPECT_EQ(StickyKeysHandler::DISABLED, sticky_key.current_state());
+ // Making sure Shift up keyboard event is dispatched.
+ ASSERT_EQ(2U, mock_delegate->GetKeyEventCount());
+ EXPECT_EQ(ui::VKEY_A, mock_delegate->GetKeyEvent(0)->key_code());
+ EXPECT_EQ(ui::ET_KEY_PRESSED, mock_delegate->GetKeyEvent(0)->type());
+ EXPECT_EQ(ui::VKEY_SHIFT, mock_delegate->GetKeyEvent(1)->key_code());
+ EXPECT_EQ(ui::ET_KEY_RELEASED, mock_delegate->GetKeyEvent(1)->type());
+
+ // Enabled state is one shot, so next key event should not be shift modified.
+ ev.reset(GenerateKey(true, ui::VKEY_A));
+ sticky_key.HandleKeyEvent(ev.get());
+ EXPECT_FALSE(ev->flags() & ui::EF_SHIFT_DOWN);
+
+ ev.reset(GenerateKey(false, ui::VKEY_A));
+ sticky_key.HandleKeyEvent(ev.get());
+ EXPECT_FALSE(ev->flags() & ui::EF_SHIFT_DOWN);
+}
+
+TEST_F(StickyKeysTest, BasicLockedScenarioTest) {
+ scoped_ptr<ui::KeyEvent> ev;
+ MockStickyKeysHandlerDelegate* mock_delegate =
+ new MockStickyKeysHandlerDelegate();
+ StickyKeysHandler sticky_key(ui::EF_SHIFT_DOWN, mock_delegate);
+
+ EXPECT_EQ(StickyKeysHandler::DISABLED, sticky_key.current_state());
+ ev.reset(GenerateKey(true, ui::VKEY_SHIFT));
+ sticky_key.HandleKeyEvent(ev.get());
+
+ ev.reset(GenerateKey(false, ui::VKEY_SHIFT));
+ sticky_key.HandleKeyEvent(ev.get());
+
+ // By typing shift key, internal state become ENABLED.
+ EXPECT_EQ(StickyKeysHandler::ENABLED, sticky_key.current_state());
+
+ ev.reset(GenerateKey(true, ui::VKEY_SHIFT));
+ sticky_key.HandleKeyEvent(ev.get());
+
+ ev.reset(GenerateKey(false, ui::VKEY_SHIFT));
+ sticky_key.HandleKeyEvent(ev.get());
+
+ // By typing shift key again, internal state become LOCKED.
+ EXPECT_EQ(StickyKeysHandler::LOCKED, sticky_key.current_state());
+
+ // All keyboard events including keyUp become shift modified.
+ ev.reset(GenerateKey(true, ui::VKEY_A));
+ sticky_key.HandleKeyEvent(ev.get());
+ EXPECT_TRUE(ev->flags() & ui::EF_SHIFT_DOWN);
+
+ ev.reset(GenerateKey(false, ui::VKEY_A));
+ sticky_key.HandleKeyEvent(ev.get());
+ EXPECT_TRUE(ev->flags() & ui::EF_SHIFT_DOWN);
+
+ // Locked state keeps after normal keyboard event.
+ EXPECT_EQ(StickyKeysHandler::LOCKED, sticky_key.current_state());
+
+ ev.reset(GenerateKey(true, ui::VKEY_B));
+ sticky_key.HandleKeyEvent(ev.get());
+ EXPECT_TRUE(ev->flags() & ui::EF_SHIFT_DOWN);
+
+ ev.reset(GenerateKey(false, ui::VKEY_B));
+ sticky_key.HandleKeyEvent(ev.get());
+ EXPECT_TRUE(ev->flags() & ui::EF_SHIFT_DOWN);
+
+ EXPECT_EQ(StickyKeysHandler::LOCKED, sticky_key.current_state());
+
+ ev.reset(GenerateKey(true, ui::VKEY_SHIFT));
+ sticky_key.HandleKeyEvent(ev.get());
+
+ ev.reset(GenerateKey(false, ui::VKEY_SHIFT));
+ sticky_key.HandleKeyEvent(ev.get());
+
+ // By typing shift key again, internal state become back to DISABLED.
+ EXPECT_EQ(StickyKeysHandler::DISABLED, sticky_key.current_state());
+}
+
+TEST_F(StickyKeysTest, NonTargetModifierTest) {
+ scoped_ptr<ui::KeyEvent> ev;
+ MockStickyKeysHandlerDelegate* mock_delegate =
+ new MockStickyKeysHandlerDelegate();
+ StickyKeysHandler sticky_key(ui::EF_SHIFT_DOWN, mock_delegate);
+
+ EXPECT_EQ(StickyKeysHandler::DISABLED, sticky_key.current_state());
+
+ // Non target modifier key does not affect internal state
+ ev.reset(GenerateKey(true, ui::VKEY_MENU));
+ sticky_key.HandleKeyEvent(ev.get());
+ EXPECT_EQ(StickyKeysHandler::DISABLED, sticky_key.current_state());
+
+ ev.reset(GenerateKey(false, ui::VKEY_MENU));
+ sticky_key.HandleKeyEvent(ev.get());
+ EXPECT_EQ(StickyKeysHandler::DISABLED, sticky_key.current_state());
+
+ ev.reset(GenerateKey(true, ui::VKEY_SHIFT));
+ sticky_key.HandleKeyEvent(ev.get());
+ ev.reset(GenerateKey(false, ui::VKEY_SHIFT));
+ sticky_key.HandleKeyEvent(ev.get());
+ EXPECT_EQ(StickyKeysHandler::ENABLED, sticky_key.current_state());
+
+ // Non target modifier key does not affect internal state
+ ev.reset(GenerateKey(true, ui::VKEY_MENU));
+ sticky_key.HandleKeyEvent(ev.get());
+ EXPECT_EQ(StickyKeysHandler::ENABLED, sticky_key.current_state());
+
+ ev.reset(GenerateKey(false, ui::VKEY_MENU));
+ sticky_key.HandleKeyEvent(ev.get());
+ EXPECT_EQ(StickyKeysHandler::ENABLED, sticky_key.current_state());
+
+ ev.reset(GenerateKey(true, ui::VKEY_SHIFT));
+ sticky_key.HandleKeyEvent(ev.get());
+ ev.reset(GenerateKey(false, ui::VKEY_SHIFT));
+ sticky_key.HandleKeyEvent(ev.get());
+ EXPECT_EQ(StickyKeysHandler::LOCKED, sticky_key.current_state());
+
+ // Non target modifier key does not affect internal state
+ ev.reset(GenerateKey(true, ui::VKEY_MENU));
+ sticky_key.HandleKeyEvent(ev.get());
+ EXPECT_EQ(StickyKeysHandler::LOCKED, sticky_key.current_state());
+
+ ev.reset(GenerateKey(false, ui::VKEY_MENU));
+ sticky_key.HandleKeyEvent(ev.get());
+ EXPECT_EQ(StickyKeysHandler::LOCKED, sticky_key.current_state());
+}
+
+} // namespace ash
diff --git a/chromium/ash/wm/system_background_controller.cc b/chromium/ash/wm/system_background_controller.cc
new file mode 100644
index 00000000000..588073546de
--- /dev/null
+++ b/chromium/ash/wm/system_background_controller.cc
@@ -0,0 +1,54 @@
+// Copyright (c) 2012 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.
+
+#include "ash/wm/system_background_controller.h"
+
+#include "ui/aura/root_window.h"
+#include "ui/compositor/layer.h"
+#include "ui/compositor/layer_type.h"
+
+namespace ash {
+namespace internal {
+
+namespace {
+
+#if defined(OS_CHROMEOS)
+// Background color used for the Chrome OS boot splash screen.
+const SkColor kChromeOsBootColor = SkColorSetARGB(0xff, 0xfe, 0xfe, 0xfe);
+#endif
+
+} // namespace
+
+SystemBackgroundController::SystemBackgroundController(
+ aura::RootWindow* root_window,
+ SkColor color)
+ : root_window_(root_window),
+ layer_(new ui::Layer(ui::LAYER_SOLID_COLOR)) {
+ root_window_->AddObserver(this);
+ layer_->SetColor(color);
+
+ ui::Layer* root_layer = root_window_->layer();
+ layer_->SetBounds(gfx::Rect(root_layer->bounds().size()));
+ root_layer->Add(layer_.get());
+ root_layer->StackAtBottom(layer_.get());
+}
+
+SystemBackgroundController::~SystemBackgroundController() {
+ root_window_->RemoveObserver(this);
+}
+
+void SystemBackgroundController::SetColor(SkColor color) {
+ layer_->SetColor(color);
+}
+
+void SystemBackgroundController::OnWindowBoundsChanged(
+ aura::Window* root,
+ const gfx::Rect& old_bounds,
+ const gfx::Rect& new_bounds) {
+ DCHECK_EQ(root_window_, root);
+ layer_->SetBounds(gfx::Rect(root_window_->layer()->bounds().size()));
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/wm/system_background_controller.h b/chromium/ash/wm/system_background_controller.h
new file mode 100644
index 00000000000..36382646b3c
--- /dev/null
+++ b/chromium/ash/wm/system_background_controller.h
@@ -0,0 +1,58 @@
+// Copyright (c) 2012 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 ASH_WM_SYSTEM_BACKGROUND_CONTROLLER_H_
+#define ASH_WM_SYSTEM_BACKGROUND_CONTROLLER_H_
+
+#include <string>
+
+#include "ash/ash_export.h"
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "third_party/skia/include/core/SkColor.h"
+#include "ui/aura/window_observer.h"
+
+namespace aura {
+class RootWindow;
+}
+
+namespace ui {
+class Layer;
+}
+
+namespace ash {
+namespace internal {
+
+// SystemBackgroundController manages a ui::Layer that's stacked at the bottom
+// of an aura::RootWindow's children. It exists solely to obscure portions of
+// the root layer that aren't covered by any other layers (e.g. before the
+// desktop background image is loaded at startup, or when we scale down all of
+// the other layers as part of a power-button or window-management animation).
+// It should never be transformed or restacked.
+class SystemBackgroundController : public aura::WindowObserver {
+ public:
+ SystemBackgroundController(aura::RootWindow* root_window, SkColor color);
+ virtual ~SystemBackgroundController();
+
+ void SetColor(SkColor color);
+
+ // aura::WindowObserver overrides:
+ virtual void OnWindowBoundsChanged(aura::Window* root,
+ const gfx::Rect& old_bounds,
+ const gfx::Rect& new_bounds) OVERRIDE;
+
+ private:
+ class HostContentLayerDelegate;
+
+ aura::RootWindow* root_window_; // not owned
+
+ scoped_ptr<ui::Layer> layer_;
+
+ DISALLOW_COPY_AND_ASSIGN(SystemBackgroundController);
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_WM_SYSTEM_BACKGROUND_CONTROLLER_H_
diff --git a/chromium/ash/wm/system_gesture_event_filter.cc b/chromium/ash/wm/system_gesture_event_filter.cc
new file mode 100644
index 00000000000..b36882278f2
--- /dev/null
+++ b/chromium/ash/wm/system_gesture_event_filter.cc
@@ -0,0 +1,148 @@
+// Copyright (c) 2012 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.
+
+#include "ash/wm/system_gesture_event_filter.h"
+
+#include "ash/accelerators/accelerator_controller.h"
+#include "ash/accelerators/accelerator_table.h"
+#include "ash/ash_switches.h"
+#include "ash/root_window_controller.h"
+#include "ash/shell.h"
+#include "ash/shell_delegate.h"
+#include "ash/shell_window_ids.h"
+#include "ash/wm/gestures/long_press_affordance_handler.h"
+#include "ash/wm/gestures/system_pinch_handler.h"
+#include "ash/wm/gestures/two_finger_drag_handler.h"
+#include "ash/wm/property_util.h"
+#include "ash/wm/window_util.h"
+#include "base/command_line.h"
+#include "ui/aura/root_window.h"
+#include "ui/base/events/event.h"
+#include "ui/base/ui_base_switches.h"
+
+#if defined(OS_CHROMEOS)
+#include "ui/base/touch/touch_factory_x11.h"
+#endif
+
+namespace {
+
+aura::Window* GetTargetForSystemGestureEvent(aura::Window* target) {
+ aura::Window* system_target = target;
+ if (!system_target || system_target == target->GetRootWindow())
+ system_target = ash::wm::GetActiveWindow();
+ if (system_target)
+ system_target = system_target->GetToplevelWindow();
+ return system_target;
+}
+
+} // namespace
+
+namespace ash {
+namespace internal {
+
+SystemGestureEventFilter::SystemGestureEventFilter()
+ : system_gestures_enabled_(CommandLine::ForCurrentProcess()->
+ HasSwitch(ash::switches::kAshEnableAdvancedGestures)),
+ long_press_affordance_(new LongPressAffordanceHandler),
+ two_finger_drag_(new TwoFingerDragHandler) {
+}
+
+SystemGestureEventFilter::~SystemGestureEventFilter() {
+}
+
+void SystemGestureEventFilter::OnMouseEvent(ui::MouseEvent* event) {
+#if defined(OS_CHROMEOS) && !defined(USE_OZONE)
+ if (event->type() == ui::ET_MOUSE_PRESSED && event->native_event() &&
+ ui::TouchFactory::GetInstance()->IsTouchDevicePresent() &&
+ Shell::GetInstance()->delegate()) {
+ Shell::GetInstance()->delegate()->RecordUserMetricsAction(
+ UMA_MOUSE_DOWN);
+ }
+#endif
+}
+
+void SystemGestureEventFilter::OnTouchEvent(ui::TouchEvent* event) {
+ aura::Window* target = static_cast<aura::Window*>(event->target());
+ ash::TouchUMA::GetInstance()->RecordTouchEvent(target, *event);
+ long_press_affordance_->ProcessEvent(target, event, event->touch_id());
+}
+
+void SystemGestureEventFilter::OnGestureEvent(ui::GestureEvent* event) {
+ aura::Window* target = static_cast<aura::Window*>(event->target());
+ ash::TouchUMA::GetInstance()->RecordGestureEvent(target, *event);
+ long_press_affordance_->ProcessEvent(target, event,
+ event->GetLowestTouchId());
+
+ if (two_finger_drag_->ProcessGestureEvent(target, *event)) {
+ event->StopPropagation();
+ return;
+ }
+
+ if (!system_gestures_enabled_)
+ return;
+
+ aura::Window* system_target = GetTargetForSystemGestureEvent(target);
+ if (!system_target)
+ return;
+
+ RootWindowController* root_controller =
+ GetRootWindowController(system_target->GetRootWindow());
+ CHECK(root_controller);
+ aura::Window* desktop_container = root_controller->GetContainer(
+ ash::internal::kShellWindowId_DesktopBackgroundContainer);
+ if (desktop_container->Contains(system_target)) {
+ // The gesture was on the desktop window.
+ if (event->type() == ui::ET_GESTURE_MULTIFINGER_SWIPE &&
+ event->details().swipe_up() &&
+ event->details().touch_points() ==
+ SystemPinchHandler::kSystemGesturePoints) {
+ ash::AcceleratorController* accelerator =
+ ash::Shell::GetInstance()->accelerator_controller();
+ if (accelerator->PerformAction(CYCLE_FORWARD_MRU, ui::Accelerator()))
+ event->StopPropagation();
+ }
+ return;
+ }
+
+ WindowPinchHandlerMap::iterator find = pinch_handlers_.find(system_target);
+ if (find != pinch_handlers_.end()) {
+ SystemGestureStatus status =
+ (*find).second->ProcessGestureEvent(*event);
+ if (status == SYSTEM_GESTURE_END)
+ ClearGestureHandlerForWindow(system_target);
+ event->StopPropagation();
+ } else {
+ if (event->type() == ui::ET_GESTURE_BEGIN &&
+ event->details().touch_points() >=
+ SystemPinchHandler::kSystemGesturePoints) {
+ pinch_handlers_[system_target] = new SystemPinchHandler(system_target);
+ system_target->AddObserver(this);
+ event->StopPropagation();
+ }
+ }
+}
+
+void SystemGestureEventFilter::OnWindowVisibilityChanged(aura::Window* window,
+ bool visible) {
+ if (!visible)
+ ClearGestureHandlerForWindow(window);
+}
+
+void SystemGestureEventFilter::OnWindowDestroying(aura::Window* window) {
+ ClearGestureHandlerForWindow(window);
+}
+
+void SystemGestureEventFilter::ClearGestureHandlerForWindow(
+ aura::Window* window) {
+ WindowPinchHandlerMap::iterator find = pinch_handlers_.find(window);
+ if (find == pinch_handlers_.end()) {
+ // The handler may have already been removed.
+ return;
+ }
+ delete (*find).second;
+ pinch_handlers_.erase(find);
+ window->RemoveObserver(this);
+}
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/wm/system_gesture_event_filter.h b/chromium/ash/wm/system_gesture_event_filter.h
new file mode 100644
index 00000000000..a2a5c738db7
--- /dev/null
+++ b/chromium/ash/wm/system_gesture_event_filter.h
@@ -0,0 +1,78 @@
+// Copyright (c) 2012 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 ASH_WM_SYSTEM_GESTURE_EVENT_FILTER_H_
+#define ASH_WM_SYSTEM_GESTURE_EVENT_FILTER_H_
+
+#include "ash/shell.h"
+#include "ash/touch/touch_uma.h"
+#include "base/timer/timer.h"
+#include "ui/aura/window_observer.h"
+#include "ui/base/animation/animation_delegate.h"
+#include "ui/base/animation/linear_animation.h"
+#include "ui/base/events/event_handler.h"
+#include "ui/gfx/point.h"
+
+#include <map>
+
+namespace aura {
+class Window;
+}
+
+namespace ui {
+class LocatedEvent;
+}
+
+namespace ash {
+
+namespace test {
+class SystemGestureEventFilterTest;
+}
+
+namespace internal {
+class LongPressAffordanceHandler;
+class SystemPinchHandler;
+class TouchUMA;
+class TwoFingerDragHandler;
+
+// An event filter which handles system level gesture events.
+class SystemGestureEventFilter : public ui::EventHandler,
+ public aura::WindowObserver {
+ public:
+ SystemGestureEventFilter();
+ virtual ~SystemGestureEventFilter();
+
+ // Overridden from ui::EventHandler:
+ virtual void OnMouseEvent(ui::MouseEvent* event) OVERRIDE;
+ virtual void OnTouchEvent(ui::TouchEvent* event) OVERRIDE;
+ virtual void OnGestureEvent(ui::GestureEvent* event) OVERRIDE;
+
+ // Overridden from aura::WindowObserver.
+ virtual void OnWindowVisibilityChanged(aura::Window* window,
+ bool visible) OVERRIDE;
+ virtual void OnWindowDestroying(aura::Window* window) OVERRIDE;
+
+ private:
+ friend class ash::test::SystemGestureEventFilterTest;
+
+ // Removes system-gesture handlers for a window.
+ void ClearGestureHandlerForWindow(aura::Window* window);
+
+ typedef std::map<aura::Window*, SystemPinchHandler*> WindowPinchHandlerMap;
+ // Created on demand when a system-level pinch gesture is initiated. Destroyed
+ // when the system-level pinch gesture ends for the window.
+ WindowPinchHandlerMap pinch_handlers_;
+
+ bool system_gestures_enabled_;
+
+ scoped_ptr<LongPressAffordanceHandler> long_press_affordance_;
+ scoped_ptr<TwoFingerDragHandler> two_finger_drag_;
+
+ DISALLOW_COPY_AND_ASSIGN(SystemGestureEventFilter);
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_WM_SYSTEM_GESTURE_EVENT_FILTER_H_
diff --git a/chromium/ash/wm/system_gesture_event_filter_unittest.cc b/chromium/ash/wm/system_gesture_event_filter_unittest.cc
new file mode 100644
index 00000000000..b54364ea058
--- /dev/null
+++ b/chromium/ash/wm/system_gesture_event_filter_unittest.cc
@@ -0,0 +1,565 @@
+// Copyright (c) 2012 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.
+
+#include "ash/wm/system_gesture_event_filter.h"
+
+#include "ash/accelerators/accelerator_controller.h"
+#include "ash/ash_switches.h"
+#include "ash/display/display_manager.h"
+#include "ash/launcher/launcher.h"
+#include "ash/launcher/launcher_model.h"
+#include "ash/shell.h"
+#include "ash/system/brightness/brightness_control_delegate.h"
+#include "ash/system/tray/system_tray_delegate.h"
+#include "ash/test/ash_test_base.h"
+#include "ash/test/display_manager_test_api.h"
+#include "ash/test/shell_test_api.h"
+#include "ash/test/test_launcher_delegate.h"
+#include "ash/volume_control_delegate.h"
+#include "ash/wm/gestures/long_press_affordance_handler.h"
+#include "ash/wm/window_util.h"
+#include "base/command_line.h"
+#include "base/time/time.h"
+#include "base/timer/timer.h"
+#include "ui/aura/root_window.h"
+#include "ui/aura/test/event_generator.h"
+#include "ui/aura/test/test_windows.h"
+#include "ui/base/events/event.h"
+#include "ui/base/events/event_utils.h"
+#include "ui/base/gestures/gesture_configuration.h"
+#include "ui/base/hit_test.h"
+#include "ui/base/ui_base_switches.h"
+#include "ui/gfx/screen.h"
+#include "ui/gfx/size.h"
+#include "ui/views/widget/widget_delegate.h"
+
+namespace ash {
+namespace test {
+
+namespace {
+
+class DelegatePercentTracker {
+ public:
+ explicit DelegatePercentTracker()
+ : handle_percent_count_(0),
+ handle_percent_(0){
+ }
+ int handle_percent_count() const {
+ return handle_percent_count_;
+ }
+ double handle_percent() const {
+ return handle_percent_;
+ }
+ void SetPercent(double percent) {
+ handle_percent_ = percent;
+ handle_percent_count_++;
+ }
+
+ private:
+ int handle_percent_count_;
+ int handle_percent_;
+
+ DISALLOW_COPY_AND_ASSIGN(DelegatePercentTracker);
+};
+
+class DummyVolumeControlDelegate : public VolumeControlDelegate,
+ public DelegatePercentTracker {
+ public:
+ explicit DummyVolumeControlDelegate() {}
+ virtual ~DummyVolumeControlDelegate() {}
+
+ virtual bool HandleVolumeMute(const ui::Accelerator& accelerator) OVERRIDE {
+ return true;
+ }
+ virtual bool HandleVolumeDown(const ui::Accelerator& accelerator) OVERRIDE {
+ return true;
+ }
+ virtual bool HandleVolumeUp(const ui::Accelerator& accelerator) OVERRIDE {
+ return true;
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(DummyVolumeControlDelegate);
+};
+
+class DummyBrightnessControlDelegate : public BrightnessControlDelegate,
+ public DelegatePercentTracker {
+ public:
+ explicit DummyBrightnessControlDelegate() {}
+ virtual ~DummyBrightnessControlDelegate() {}
+
+ virtual bool HandleBrightnessDown(
+ const ui::Accelerator& accelerator) OVERRIDE { return true; }
+ virtual bool HandleBrightnessUp(
+ const ui::Accelerator& accelerator) OVERRIDE { return true; }
+ virtual void SetBrightnessPercent(double percent, bool gradual) OVERRIDE {
+ SetPercent(percent);
+ }
+ virtual void GetBrightnessPercent(
+ const base::Callback<void(double)>& callback) OVERRIDE {
+ callback.Run(100.0);
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(DummyBrightnessControlDelegate);
+};
+
+class ResizableWidgetDelegate : public views::WidgetDelegateView {
+ public:
+ ResizableWidgetDelegate() {}
+ virtual ~ResizableWidgetDelegate() {}
+
+ private:
+ virtual bool CanResize() const OVERRIDE { return true; }
+ virtual bool CanMaximize() const OVERRIDE { return true; }
+ virtual void DeleteDelegate() OVERRIDE { delete this; }
+
+ DISALLOW_COPY_AND_ASSIGN(ResizableWidgetDelegate);
+};
+
+// Support class for testing windows with a maximum size.
+class MaxSizeNCFV : public views::NonClientFrameView {
+ public:
+ MaxSizeNCFV() {}
+ private:
+ virtual gfx::Size GetMaximumSize() OVERRIDE {
+ return gfx::Size(200, 200);
+ }
+ virtual gfx::Rect GetBoundsForClientView() const OVERRIDE {
+ return gfx::Rect();
+ };
+
+ virtual gfx::Rect GetWindowBoundsForClientBounds(
+ const gfx::Rect& client_bounds) const OVERRIDE {
+ return gfx::Rect();
+ };
+
+ // This function must ask the ClientView to do a hittest. We don't do this in
+ // the parent NonClientView because that makes it more difficult to calculate
+ // hittests for regions that are partially obscured by the ClientView, e.g.
+ // HTSYSMENU.
+ virtual int NonClientHitTest(const gfx::Point& point) OVERRIDE {
+ return HTNOWHERE;
+ }
+ virtual void GetWindowMask(const gfx::Size& size,
+ gfx::Path* window_mask) OVERRIDE {}
+ virtual void ResetWindowControls() OVERRIDE {}
+ virtual void UpdateWindowIcon() OVERRIDE {}
+ virtual void UpdateWindowTitle() OVERRIDE {}
+
+ DISALLOW_COPY_AND_ASSIGN(MaxSizeNCFV);
+};
+
+class MaxSizeWidgetDelegate : public views::WidgetDelegateView {
+ public:
+ MaxSizeWidgetDelegate() {}
+ virtual ~MaxSizeWidgetDelegate() {}
+
+ private:
+ virtual bool CanResize() const OVERRIDE { return true; }
+ virtual bool CanMaximize() const OVERRIDE { return false; }
+ virtual void DeleteDelegate() OVERRIDE { delete this; }
+ virtual views::NonClientFrameView* CreateNonClientFrameView(
+ views::Widget* widget) OVERRIDE {
+ return new MaxSizeNCFV;
+ }
+
+ DISALLOW_COPY_AND_ASSIGN(MaxSizeWidgetDelegate);
+};
+
+} // namespace
+
+class SystemGestureEventFilterTest : public AshTestBase {
+ public:
+ SystemGestureEventFilterTest() : AshTestBase() {}
+ virtual ~SystemGestureEventFilterTest() {}
+
+ internal::LongPressAffordanceHandler* GetLongPressAffordance() {
+ ShellTestApi shell_test(Shell::GetInstance());
+ return shell_test.system_gesture_event_filter()->
+ long_press_affordance_.get();
+ }
+
+ base::OneShotTimer<internal::LongPressAffordanceHandler>*
+ GetLongPressAffordanceTimer() {
+ return &GetLongPressAffordance()->timer_;
+ }
+
+ int GetLongPressAffordanceTouchId() {
+ return GetLongPressAffordance()->tap_down_touch_id_;
+ }
+
+ views::View* GetLongPressAffordanceView() {
+ return reinterpret_cast<views::View*>(
+ GetLongPressAffordance()->view_.get());
+ }
+
+ // Overridden from AshTestBase:
+ virtual void SetUp() OVERRIDE {
+ CommandLine::ForCurrentProcess()->AppendSwitch(
+ ash::switches::kAshEnableAdvancedGestures);
+ test::AshTestBase::SetUp();
+ // Enable brightness key.
+ test::DisplayManagerTestApi(Shell::GetInstance()->display_manager()).
+ SetFirstDisplayAsInternalDisplay();
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(SystemGestureEventFilterTest);
+};
+
+ui::GestureEvent* CreateGesture(ui::EventType type,
+ int x,
+ int y,
+ float delta_x,
+ float delta_y,
+ int touch_id) {
+ return new ui::GestureEvent(type, x, y, 0,
+ base::TimeDelta::FromMilliseconds(base::Time::Now().ToDoubleT() * 1000),
+ ui::GestureEventDetails(type, delta_x, delta_y), 1 << touch_id);
+}
+
+void MoveToDeviceControlBezelStartPosition(
+ aura::RootWindow* root_window,
+ DelegatePercentTracker* delegate,
+ double expected_value,
+ int xpos,
+ int ypos,
+ int ypos_half,
+ int touch_id) {
+ // Get a target for kTouchId
+ ui::TouchEvent press1(ui::ET_TOUCH_PRESSED,
+ gfx::Point(-10, ypos + ypos_half),
+ touch_id,
+ ui::EventTimeForNow());
+ root_window->AsRootWindowHostDelegate()->OnHostTouchEvent(&press1);
+
+ // There is a noise filter which will require several calls before it
+ // allows the touch event through.
+ int initial_count = delegate->handle_percent_count();
+
+ // Position the initial touch down slightly underneath the position of
+ // interest to avoid a conflict with the noise filter.
+ scoped_ptr<ui::GestureEvent> event1(CreateGesture(
+ ui::ET_GESTURE_SCROLL_BEGIN, xpos, ypos + ypos_half - 10,
+ 0, 0, touch_id));
+ bool consumed = root_window->DispatchGestureEvent(event1.get());
+
+ EXPECT_TRUE(consumed);
+ EXPECT_EQ(initial_count, delegate->handle_percent_count());
+
+ // No move at the beginning will produce no events.
+ scoped_ptr<ui::GestureEvent> event2(CreateGesture(
+ ui::ET_GESTURE_SCROLL_UPDATE,
+ xpos, ypos + ypos_half - 10, 0, 0, touch_id));
+ consumed = root_window->DispatchGestureEvent(event2.get());
+
+ EXPECT_TRUE(consumed);
+ EXPECT_EQ(initial_count, delegate->handle_percent_count());
+
+ // A move to a new Y location will produce an event.
+ scoped_ptr<ui::GestureEvent> event3(CreateGesture(
+ ui::ET_GESTURE_SCROLL_UPDATE, xpos, ypos + ypos_half,
+ 0, 10, touch_id));
+
+ int count = initial_count;
+ int loop_counter = 0;
+ while (count == initial_count && loop_counter++ < 100) {
+ EXPECT_TRUE(root_window->DispatchGestureEvent(event3.get()));
+ count = delegate->handle_percent_count();
+ }
+ EXPECT_TRUE(loop_counter && loop_counter < 100);
+ EXPECT_EQ(initial_count + 1, count);
+ EXPECT_EQ(expected_value, delegate->handle_percent());
+}
+
+TEST_F(SystemGestureEventFilterTest, LongPressAffordanceStateOnCaptureLoss) {
+ aura::RootWindow* root_window = Shell::GetPrimaryRootWindow();
+
+ aura::test::TestWindowDelegate delegate;
+ scoped_ptr<aura::Window> window0(
+ aura::test::CreateTestWindowWithDelegate(
+ &delegate, 9, gfx::Rect(0, 0, 100, 100), root_window));
+ scoped_ptr<aura::Window> window1(
+ aura::test::CreateTestWindowWithDelegate(
+ &delegate, 10, gfx::Rect(0, 0, 100, 50), window0.get()));
+ scoped_ptr<aura::Window> window2(
+ aura::test::CreateTestWindowWithDelegate(
+ &delegate, 11, gfx::Rect(0, 50, 100, 50), window0.get()));
+
+ const int kTouchId = 5;
+
+ // Capture first window.
+ window1->SetCapture();
+ EXPECT_TRUE(window1->HasCapture());
+
+ // Send touch event to first window.
+ ui::TouchEvent press(ui::ET_TOUCH_PRESSED,
+ gfx::Point(10, 10),
+ kTouchId,
+ ui::EventTimeForNow());
+ root_window->AsRootWindowHostDelegate()->OnHostTouchEvent(&press);
+ EXPECT_TRUE(window1->HasCapture());
+
+ base::OneShotTimer<internal::LongPressAffordanceHandler>* timer =
+ GetLongPressAffordanceTimer();
+ EXPECT_TRUE(timer->IsRunning());
+ EXPECT_EQ(kTouchId, GetLongPressAffordanceTouchId());
+
+ // Force timeout so that the affordance animation can start.
+ timer->user_task().Run();
+ timer->Stop();
+ EXPECT_TRUE(GetLongPressAffordance()->is_animating());
+
+ // Change capture.
+ window2->SetCapture();
+ EXPECT_TRUE(window2->HasCapture());
+
+ EXPECT_TRUE(GetLongPressAffordance()->is_animating());
+ EXPECT_EQ(kTouchId, GetLongPressAffordanceTouchId());
+
+ // Animate to completion.
+ GetLongPressAffordance()->End(); // end grow animation.
+ // Force timeout to start shrink animation.
+ EXPECT_TRUE(timer->IsRunning());
+ timer->user_task().Run();
+ timer->Stop();
+ EXPECT_TRUE(GetLongPressAffordance()->is_animating());
+ GetLongPressAffordance()->End(); // end shrink animation.
+
+ // Check if state has reset.
+ EXPECT_EQ(-1, GetLongPressAffordanceTouchId());
+ EXPECT_EQ(NULL, GetLongPressAffordanceView());
+}
+
+TEST_F(SystemGestureEventFilterTest, MultiFingerSwipeGestures) {
+ aura::RootWindow* root_window = Shell::GetPrimaryRootWindow();
+ views::Widget* toplevel = views::Widget::CreateWindowWithContextAndBounds(
+ new ResizableWidgetDelegate, root_window, gfx::Rect(0, 0, 600, 600));
+ toplevel->Show();
+
+ const int kSteps = 15;
+ const int kTouchPoints = 4;
+ gfx::Point points[kTouchPoints] = {
+ gfx::Point(250, 250),
+ gfx::Point(250, 350),
+ gfx::Point(350, 250),
+ gfx::Point(350, 350)
+ };
+
+ aura::test::EventGenerator generator(root_window,
+ toplevel->GetNativeWindow());
+
+ // Swipe down to minimize.
+ generator.GestureMultiFingerScroll(kTouchPoints, points, 15, kSteps, 0, 150);
+ EXPECT_TRUE(wm::IsWindowMinimized(toplevel->GetNativeWindow()));
+
+ toplevel->Restore();
+
+ // Swipe up to maximize.
+ generator.GestureMultiFingerScroll(kTouchPoints, points, 15, kSteps, 0, -150);
+ EXPECT_TRUE(wm::IsWindowMaximized(toplevel->GetNativeWindow()));
+
+ toplevel->Restore();
+
+ // Swipe right to snap.
+ gfx::Rect normal_bounds = toplevel->GetWindowBoundsInScreen();
+ generator.GestureMultiFingerScroll(kTouchPoints, points, 15, kSteps, 150, 0);
+ gfx::Rect right_tile_bounds = toplevel->GetWindowBoundsInScreen();
+ EXPECT_NE(normal_bounds.ToString(), right_tile_bounds.ToString());
+
+ // Swipe left to snap.
+ gfx::Point left_points[kTouchPoints];
+ for (int i = 0; i < kTouchPoints; ++i) {
+ left_points[i] = points[i];
+ left_points[i].Offset(right_tile_bounds.x(), right_tile_bounds.y());
+ }
+ generator.GestureMultiFingerScroll(kTouchPoints, left_points, 15, kSteps,
+ -150, 0);
+ gfx::Rect left_tile_bounds = toplevel->GetWindowBoundsInScreen();
+ EXPECT_NE(normal_bounds.ToString(), left_tile_bounds.ToString());
+ EXPECT_NE(right_tile_bounds.ToString(), left_tile_bounds.ToString());
+
+ // Swipe right again.
+ generator.GestureMultiFingerScroll(kTouchPoints, points, 15, kSteps, 150, 0);
+ gfx::Rect current_bounds = toplevel->GetWindowBoundsInScreen();
+ EXPECT_NE(current_bounds.ToString(), left_tile_bounds.ToString());
+ EXPECT_EQ(current_bounds.ToString(), right_tile_bounds.ToString());
+}
+
+TEST_F(SystemGestureEventFilterTest, TwoFingerDrag) {
+ gfx::Rect bounds(0, 0, 600, 600);
+ aura::RootWindow* root_window = Shell::GetPrimaryRootWindow();
+ views::Widget* toplevel = views::Widget::CreateWindowWithContextAndBounds(
+ new ResizableWidgetDelegate, root_window, bounds);
+ toplevel->Show();
+
+ const int kSteps = 15;
+ const int kTouchPoints = 2;
+ gfx::Point points[kTouchPoints] = {
+ gfx::Point(250, 250),
+ gfx::Point(350, 350),
+ };
+
+ aura::test::EventGenerator generator(root_window,
+ toplevel->GetNativeWindow());
+
+ // Swipe down to minimize.
+ generator.GestureMultiFingerScroll(kTouchPoints, points, 15, kSteps, 0, 150);
+ EXPECT_TRUE(wm::IsWindowMinimized(toplevel->GetNativeWindow()));
+
+ toplevel->Restore();
+ toplevel->GetNativeWindow()->SetBounds(bounds);
+
+ // Swipe up to maximize.
+ generator.GestureMultiFingerScroll(kTouchPoints, points, 15, kSteps, 0, -150);
+ EXPECT_TRUE(wm::IsWindowMaximized(toplevel->GetNativeWindow()));
+
+ toplevel->Restore();
+ toplevel->GetNativeWindow()->SetBounds(bounds);
+
+ // Swipe right to snap.
+ gfx::Rect normal_bounds = toplevel->GetWindowBoundsInScreen();
+ generator.GestureMultiFingerScroll(kTouchPoints, points, 15, kSteps, 150, 0);
+ gfx::Rect right_tile_bounds = toplevel->GetWindowBoundsInScreen();
+ EXPECT_NE(normal_bounds.ToString(), right_tile_bounds.ToString());
+
+ // Swipe left to snap.
+ gfx::Point left_points[kTouchPoints];
+ for (int i = 0; i < kTouchPoints; ++i) {
+ left_points[i] = points[i];
+ left_points[i].Offset(right_tile_bounds.x(), right_tile_bounds.y());
+ }
+ generator.GestureMultiFingerScroll(kTouchPoints, left_points, 15, kSteps,
+ -150, 0);
+ gfx::Rect left_tile_bounds = toplevel->GetWindowBoundsInScreen();
+ EXPECT_NE(normal_bounds.ToString(), left_tile_bounds.ToString());
+ EXPECT_NE(right_tile_bounds.ToString(), left_tile_bounds.ToString());
+
+ // Swipe right again.
+ generator.GestureMultiFingerScroll(kTouchPoints, points, 15, kSteps, 150, 0);
+ gfx::Rect current_bounds = toplevel->GetWindowBoundsInScreen();
+ EXPECT_NE(current_bounds.ToString(), left_tile_bounds.ToString());
+ EXPECT_EQ(current_bounds.ToString(), right_tile_bounds.ToString());
+}
+
+TEST_F(SystemGestureEventFilterTest, TwoFingerDragTwoWindows) {
+ aura::RootWindow* root_window = Shell::GetPrimaryRootWindow();
+ ui::GestureConfiguration::set_max_separation_for_gesture_touches_in_pixels(0);
+ views::Widget* first = views::Widget::CreateWindowWithContextAndBounds(
+ new ResizableWidgetDelegate, root_window, gfx::Rect(0, 0, 50, 100));
+ first->Show();
+ views::Widget* second = views::Widget::CreateWindowWithContextAndBounds(
+ new ResizableWidgetDelegate, root_window, gfx::Rect(100, 0, 100, 100));
+ second->Show();
+
+ // Start a two-finger drag on |first|, and then try to use another two-finger
+ // drag to move |second|. The attempt to move |second| should fail.
+ const gfx::Rect& first_bounds = first->GetWindowBoundsInScreen();
+ const gfx::Rect& second_bounds = second->GetWindowBoundsInScreen();
+ const int kSteps = 15;
+ const int kTouchPoints = 4;
+ gfx::Point points[kTouchPoints] = {
+ first_bounds.origin() + gfx::Vector2d(5, 5),
+ first_bounds.origin() + gfx::Vector2d(30, 10),
+ second_bounds.origin() + gfx::Vector2d(5, 5),
+ second_bounds.origin() + gfx::Vector2d(40, 20)
+ };
+
+ aura::test::EventGenerator generator(root_window);
+ generator.GestureMultiFingerScroll(kTouchPoints, points,
+ 15, kSteps, 0, 150);
+
+ EXPECT_NE(first_bounds.ToString(),
+ first->GetWindowBoundsInScreen().ToString());
+ EXPECT_EQ(second_bounds.ToString(),
+ second->GetWindowBoundsInScreen().ToString());
+}
+
+TEST_F(SystemGestureEventFilterTest, WindowsWithMaxSizeDontSnap) {
+ gfx::Rect bounds(150, 150, 100, 100);
+ aura::RootWindow* root_window = Shell::GetPrimaryRootWindow();
+ views::Widget* toplevel = views::Widget::CreateWindowWithContextAndBounds(
+ new MaxSizeWidgetDelegate, root_window, bounds);
+ toplevel->Show();
+
+ const int kSteps = 15;
+ const int kTouchPoints = 2;
+ gfx::Point points[kTouchPoints] = {
+ gfx::Point(150+10, 150+30),
+ gfx::Point(150+30, 150+20),
+ };
+
+ aura::test::EventGenerator generator(root_window,
+ toplevel->GetNativeWindow());
+
+ // Swipe down to minimize.
+ generator.GestureMultiFingerScroll(kTouchPoints, points, 15, kSteps, 0, 150);
+ EXPECT_TRUE(wm::IsWindowMinimized(toplevel->GetNativeWindow()));
+
+ toplevel->Restore();
+ toplevel->GetNativeWindow()->SetBounds(bounds);
+
+ // Check that swiping up doesn't maximize.
+ generator.GestureMultiFingerScroll(kTouchPoints, points, 15, kSteps, 0, -150);
+ EXPECT_FALSE(wm::IsWindowMaximized(toplevel->GetNativeWindow()));
+
+ toplevel->Restore();
+ toplevel->GetNativeWindow()->SetBounds(bounds);
+
+ // Check that swiping right doesn't snap.
+ gfx::Rect normal_bounds = toplevel->GetWindowBoundsInScreen();
+ generator.GestureMultiFingerScroll(kTouchPoints, points, 15, kSteps, 150, 0);
+ normal_bounds.set_x(normal_bounds.x() + 150);
+ EXPECT_EQ(normal_bounds.ToString(),
+ toplevel->GetWindowBoundsInScreen().ToString());
+
+ toplevel->GetNativeWindow()->SetBounds(bounds);
+
+ // Check that swiping left doesn't snap.
+ normal_bounds = toplevel->GetWindowBoundsInScreen();
+ generator.GestureMultiFingerScroll(kTouchPoints, points, 15, kSteps, -150, 0);
+ normal_bounds.set_x(normal_bounds.x() - 150);
+ EXPECT_EQ(normal_bounds.ToString(),
+ toplevel->GetWindowBoundsInScreen().ToString());
+
+ toplevel->GetNativeWindow()->SetBounds(bounds);
+
+ // Swipe right again, make sure the window still doesn't snap.
+ normal_bounds = toplevel->GetWindowBoundsInScreen();
+ normal_bounds.set_x(normal_bounds.x() + 150);
+ generator.GestureMultiFingerScroll(kTouchPoints, points, 15, kSteps, 150, 0);
+ EXPECT_EQ(normal_bounds.ToString(),
+ toplevel->GetWindowBoundsInScreen().ToString());
+}
+
+TEST_F(SystemGestureEventFilterTest, TwoFingerDragEdge) {
+ gfx::Rect bounds(0, 0, 100, 100);
+ aura::RootWindow* root_window = Shell::GetPrimaryRootWindow();
+ views::Widget* toplevel = views::Widget::CreateWindowWithContextAndBounds(
+ new ResizableWidgetDelegate, root_window, bounds);
+ toplevel->Show();
+
+ const int kSteps = 15;
+ const int kTouchPoints = 2;
+ gfx::Point points[kTouchPoints] = {
+ gfx::Point(30, 20), // Caption
+ gfx::Point(0, 40), // Left edge
+ };
+
+ EXPECT_EQ(HTLEFT, toplevel->GetNativeWindow()->delegate()->
+ GetNonClientComponent(points[1]));
+
+ aura::test::EventGenerator generator(root_window,
+ toplevel->GetNativeWindow());
+
+ bounds = toplevel->GetNativeWindow()->bounds();
+ // Swipe down. Nothing should happen.
+ generator.GestureMultiFingerScroll(kTouchPoints, points, 15, kSteps, 0, 150);
+ EXPECT_EQ(bounds.ToString(),
+ toplevel->GetNativeWindow()->bounds().ToString());
+}
+
+} // namespace test
+} // namespace ash
diff --git a/chromium/ash/wm/system_modal_container_event_filter.cc b/chromium/ash/wm/system_modal_container_event_filter.cc
new file mode 100644
index 00000000000..532bb44acd6
--- /dev/null
+++ b/chromium/ash/wm/system_modal_container_event_filter.cc
@@ -0,0 +1,36 @@
+// Copyright (c) 2012 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.
+
+#include "ash/wm/system_modal_container_event_filter.h"
+
+#include "ash/wm/system_modal_container_event_filter_delegate.h"
+#include "ui/aura/window.h"
+#include "ui/base/events/event.h"
+
+namespace ash {
+namespace internal {
+
+SystemModalContainerEventFilter::SystemModalContainerEventFilter(
+ SystemModalContainerEventFilterDelegate* delegate)
+ : delegate_(delegate) {
+}
+
+SystemModalContainerEventFilter::~SystemModalContainerEventFilter() {
+}
+
+void SystemModalContainerEventFilter::OnKeyEvent(ui::KeyEvent* event) {
+ aura::Window* target = static_cast<aura::Window*>(event->target());
+ if (!delegate_->CanWindowReceiveEvents(target))
+ event->StopPropagation();
+}
+
+void SystemModalContainerEventFilter::OnMouseEvent(
+ ui::MouseEvent* event) {
+ aura::Window* target = static_cast<aura::Window*>(event->target());
+ if (!delegate_->CanWindowReceiveEvents(target))
+ event->StopPropagation();
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/wm/system_modal_container_event_filter.h b/chromium/ash/wm/system_modal_container_event_filter.h
new file mode 100644
index 00000000000..64d522da4f0
--- /dev/null
+++ b/chromium/ash/wm/system_modal_container_event_filter.h
@@ -0,0 +1,37 @@
+// Copyright (c) 2012 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 ASH_WM_SYSTEM_MODAL_CONTAINER_EVENT_FILTER_H_
+#define ASH_WM_SYSTEM_MODAL_CONTAINER_EVENT_FILTER_H_
+
+#include "ash/ash_export.h"
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "ui/base/events/event_handler.h"
+
+namespace ash {
+namespace internal {
+
+class SystemModalContainerEventFilterDelegate;
+
+class ASH_EXPORT SystemModalContainerEventFilter : public ui::EventHandler {
+ public:
+ explicit SystemModalContainerEventFilter(
+ SystemModalContainerEventFilterDelegate* delegate);
+ virtual ~SystemModalContainerEventFilter();
+
+ // Overridden from ui::EventHandler:
+ virtual void OnKeyEvent(ui::KeyEvent* event) OVERRIDE;
+ virtual void OnMouseEvent(ui::MouseEvent* event) OVERRIDE;
+
+ private:
+ SystemModalContainerEventFilterDelegate* delegate_;
+
+ DISALLOW_COPY_AND_ASSIGN(SystemModalContainerEventFilter);
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_WM_SYSTEM_MODAL_CONTAINER_EVENT_FILTER_H_
diff --git a/chromium/ash/wm/system_modal_container_event_filter_delegate.h b/chromium/ash/wm/system_modal_container_event_filter_delegate.h
new file mode 100644
index 00000000000..4e27d0000c9
--- /dev/null
+++ b/chromium/ash/wm/system_modal_container_event_filter_delegate.h
@@ -0,0 +1,26 @@
+// Copyright (c) 2012 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 ASH_WM_MODALITY_EVENT_FILTER_DELEGATE_H_
+#define ASH_WM_MODALITY_EVENT_FILTER_DELEGATE_H_
+
+#include "ash/ash_export.h"
+
+namespace aura {
+class Window;
+}
+
+namespace ash {
+namespace internal {
+
+class ASH_EXPORT SystemModalContainerEventFilterDelegate {
+ public:
+ // Returns true if |window| can receive the specified event.
+ virtual bool CanWindowReceiveEvents(aura::Window* window) = 0;
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_WM_MODALITY_EVENT_FILTER_DELEGATE_H_
diff --git a/chromium/ash/wm/system_modal_container_layout_manager.cc b/chromium/ash/wm/system_modal_container_layout_manager.cc
new file mode 100644
index 00000000000..9b23e62f3d9
--- /dev/null
+++ b/chromium/ash/wm/system_modal_container_layout_manager.cc
@@ -0,0 +1,224 @@
+// Copyright (c) 2012 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.
+
+#include "ash/wm/system_modal_container_layout_manager.h"
+
+#include "ash/session_state_delegate.h"
+#include "ash/shell.h"
+#include "ash/shell_window_ids.h"
+#include "ash/wm/system_modal_container_event_filter.h"
+#include "ash/wm/window_animations.h"
+#include "ash/wm/window_util.h"
+#include "base/bind.h"
+#include "ui/aura/client/aura_constants.h"
+#include "ui/aura/client/capture_client.h"
+#include "ui/aura/root_window.h"
+#include "ui/aura/window.h"
+#include "ui/base/events/event.h"
+#include "ui/base/ui_base_switches_util.h"
+#include "ui/compositor/layer.h"
+#include "ui/compositor/layer_animator.h"
+#include "ui/compositor/scoped_layer_animation_settings.h"
+#include "ui/gfx/screen.h"
+#include "ui/views/corewm/compound_event_filter.h"
+#include "ui/views/view.h"
+#include "ui/views/widget/widget.h"
+
+namespace ash {
+namespace internal {
+
+////////////////////////////////////////////////////////////////////////////////
+// SystemModalContainerLayoutManager, public:
+
+SystemModalContainerLayoutManager::SystemModalContainerLayoutManager(
+ aura::Window* container)
+ : container_(container),
+ modal_background_(NULL) {
+}
+
+SystemModalContainerLayoutManager::~SystemModalContainerLayoutManager() {
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// SystemModalContainerLayoutManager, aura::LayoutManager implementation:
+
+void SystemModalContainerLayoutManager::OnWindowResized() {
+ if (modal_background_) {
+ // Note: we have to set the entire bounds with the screen offset.
+ modal_background_->SetBounds(
+ Shell::GetScreen()->GetDisplayNearestWindow(container_).bounds());
+ }
+ if (!modal_windows_.empty()) {
+ aura::Window::Windows::iterator it = modal_windows_.begin();
+ for (it = modal_windows_.begin(); it != modal_windows_.end(); ++it) {
+ gfx::Rect bounds = (*it)->bounds();
+ bounds.AdjustToFit(container_->bounds());
+ (*it)->SetBounds(bounds);
+ }
+ }
+}
+
+void SystemModalContainerLayoutManager::OnWindowAddedToLayout(
+ aura::Window* child) {
+ DCHECK((modal_background_ && child == modal_background_->GetNativeView()) ||
+ child->type() == aura::client::WINDOW_TYPE_NORMAL ||
+ child->type() == aura::client::WINDOW_TYPE_POPUP);
+ DCHECK(
+ container_->id() != internal::kShellWindowId_LockSystemModalContainer ||
+ Shell::GetInstance()->session_state_delegate()->IsUserSessionBlocked());
+
+ child->AddObserver(this);
+ if (child->GetProperty(aura::client::kModalKey) != ui::MODAL_TYPE_NONE)
+ AddModalWindow(child);
+}
+
+void SystemModalContainerLayoutManager::OnWillRemoveWindowFromLayout(
+ aura::Window* child) {
+ child->RemoveObserver(this);
+ if (child->GetProperty(aura::client::kModalKey) != ui::MODAL_TYPE_NONE)
+ RemoveModalWindow(child);
+}
+
+void SystemModalContainerLayoutManager::OnWindowRemovedFromLayout(
+ aura::Window* child) {
+}
+
+void SystemModalContainerLayoutManager::OnChildWindowVisibilityChanged(
+ aura::Window* child,
+ bool visible) {
+}
+
+void SystemModalContainerLayoutManager::SetChildBounds(
+ aura::Window* child,
+ const gfx::Rect& requested_bounds) {
+ SetChildBoundsDirect(child, requested_bounds);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// SystemModalContainerLayoutManager, aura::WindowObserver implementation:
+
+void SystemModalContainerLayoutManager::OnWindowPropertyChanged(
+ aura::Window* window,
+ const void* key,
+ intptr_t old) {
+ if (key != aura::client::kModalKey)
+ return;
+
+ if (window->GetProperty(aura::client::kModalKey) != ui::MODAL_TYPE_NONE) {
+ AddModalWindow(window);
+ } else if (static_cast<ui::ModalType>(old) != ui::MODAL_TYPE_NONE) {
+ RemoveModalWindow(window);
+ Shell::GetInstance()->OnModalWindowRemoved(window);
+ }
+}
+
+void SystemModalContainerLayoutManager::OnWindowDestroying(
+ aura::Window* window) {
+ if (modal_background_ && modal_background_->GetNativeView() == window)
+ modal_background_ = NULL;
+}
+
+bool SystemModalContainerLayoutManager::CanWindowReceiveEvents(
+ aura::Window* window) {
+ // We could get when we're at lock screen and there is modal window at
+ // system modal window layer which added event filter.
+ // Now this lock modal windows layer layout manager should not block events
+ // for windows at lock layer.
+ // See SystemModalContainerLayoutManagerTest.EventFocusContainers and
+ // http://crbug.com/157469
+ if (modal_windows_.empty())
+ return true;
+ // This container can not handle events if the screen is locked and it is not
+ // above the lock screen layer (crbug.com/110920).
+ if (Shell::GetInstance()->session_state_delegate()->IsUserSessionBlocked() &&
+ container_->id() < ash::internal::kShellWindowId_LockScreenContainer)
+ return true;
+ return wm::GetActivatableWindow(window) == modal_window();
+}
+
+bool SystemModalContainerLayoutManager::ActivateNextModalWindow() {
+ if (modal_windows_.empty())
+ return false;
+ wm::ActivateWindow(modal_window());
+ return true;
+}
+
+void SystemModalContainerLayoutManager::CreateModalBackground() {
+ if (!modal_background_) {
+ modal_background_ = new views::Widget;
+ views::Widget::InitParams params(views::Widget::InitParams::TYPE_CONTROL);
+ params.parent = container_;
+ params.bounds = Shell::GetScreen()->GetDisplayNearestWindow(
+ container_).bounds();
+ modal_background_->Init(params);
+ modal_background_->GetNativeView()->SetName(
+ "SystemModalContainerLayoutManager.ModalBackground");
+ views::View* contents_view = new views::View();
+ // TODO(jamescook): This could also be SK_ColorWHITE if using the new
+ // dialog style via switches::IsNewDialogStyleEnabled().
+ contents_view->set_background(
+ views::Background::CreateSolidBackground(SK_ColorBLACK));
+ modal_background_->SetContentsView(contents_view);
+ modal_background_->GetNativeView()->layer()->SetOpacity(0.0f);
+ }
+
+ ui::ScopedLayerAnimationSettings settings(
+ modal_background_->GetNativeView()->layer()->GetAnimator());
+ modal_background_->Show();
+ modal_background_->GetNativeView()->layer()->SetOpacity(0.5f);
+ container_->StackChildAtTop(modal_background_->GetNativeView());
+}
+
+void SystemModalContainerLayoutManager::DestroyModalBackground() {
+ // modal_background_ can be NULL when a root window is shutting down
+ // and OnWindowDestroying is called first.
+ if (modal_background_) {
+ ui::ScopedLayerAnimationSettings settings(
+ modal_background_->GetNativeView()->layer()->GetAnimator());
+ modal_background_->Close();
+ settings.AddObserver(views::corewm::CreateHidingWindowAnimationObserver(
+ modal_background_->GetNativeView()));
+ modal_background_->GetNativeView()->layer()->SetOpacity(0.0f);
+ modal_background_ = NULL;
+ }
+}
+
+// static
+bool SystemModalContainerLayoutManager::IsModalBackground(
+ aura::Window* window) {
+ int id = window->parent()->id();
+ if (id != internal::kShellWindowId_SystemModalContainer &&
+ id != internal::kShellWindowId_LockSystemModalContainer)
+ return false;
+ SystemModalContainerLayoutManager* layout_manager =
+ static_cast<SystemModalContainerLayoutManager*>(
+ window->parent()->layout_manager());
+ return layout_manager->modal_background_ &&
+ layout_manager->modal_background_->GetNativeWindow() == window;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// SystemModalContainerLayoutManager, private:
+
+void SystemModalContainerLayoutManager::AddModalWindow(aura::Window* window) {
+ if (modal_windows_.empty()) {
+ aura::Window* capture_window = aura::client::GetCaptureWindow(container_);
+ if (capture_window)
+ capture_window->ReleaseCapture();
+ }
+ modal_windows_.push_back(window);
+ Shell::GetInstance()->CreateModalBackground(window);
+ window->parent()->StackChildAtTop(window);
+}
+
+void SystemModalContainerLayoutManager::RemoveModalWindow(
+ aura::Window* window) {
+ aura::Window::Windows::iterator it =
+ std::find(modal_windows_.begin(), modal_windows_.end(), window);
+ if (it != modal_windows_.end())
+ modal_windows_.erase(it);
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/wm/system_modal_container_layout_manager.h b/chromium/ash/wm/system_modal_container_layout_manager.h
new file mode 100644
index 00000000000..76618ece071
--- /dev/null
+++ b/chromium/ash/wm/system_modal_container_layout_manager.h
@@ -0,0 +1,98 @@
+// Copyright (c) 2012 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 ASH_WM_SYSTEM_MODAL_CONTAINER_LAYOUT_MANAGER_H_
+#define ASH_WM_SYSTEM_MODAL_CONTAINER_LAYOUT_MANAGER_H_
+
+#include <vector>
+
+#include "ash/ash_export.h"
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/memory/scoped_ptr.h"
+#include "ui/aura/layout_manager.h"
+#include "ui/aura/window_observer.h"
+
+namespace aura {
+class Window;
+class EventFilter;
+}
+namespace gfx {
+class Rect;
+}
+namespace views {
+class Widget;
+}
+
+namespace ash {
+namespace internal {
+
+// LayoutManager for the modal window container.
+class ASH_EXPORT SystemModalContainerLayoutManager
+ : public aura::LayoutManager,
+ public aura::WindowObserver {
+ public:
+ explicit SystemModalContainerLayoutManager(aura::Window* container);
+ virtual ~SystemModalContainerLayoutManager();
+
+ bool has_modal_background() const { return modal_background_ != NULL; }
+
+ // Overridden from aura::LayoutManager:
+ virtual void OnWindowResized() OVERRIDE;
+ virtual void OnWindowAddedToLayout(aura::Window* child) OVERRIDE;
+ virtual void OnWillRemoveWindowFromLayout(aura::Window* child) OVERRIDE;
+ virtual void OnWindowRemovedFromLayout(aura::Window* child) OVERRIDE;
+ virtual void OnChildWindowVisibilityChanged(aura::Window* child,
+ bool visibile) OVERRIDE;
+ virtual void SetChildBounds(aura::Window* child,
+ const gfx::Rect& requested_bounds) OVERRIDE;
+
+ // Overridden from aura::WindowObserver:
+ virtual void OnWindowPropertyChanged(aura::Window* window,
+ const void* key,
+ intptr_t old) OVERRIDE;
+ virtual void OnWindowDestroying(aura::Window* window) OVERRIDE;
+
+ // Can a given |window| receive and handle input events?
+ bool CanWindowReceiveEvents(aura::Window* window);
+
+ // Activates next modal window if any. Returns false if there
+ // are no more modal windows in this layout manager.
+ bool ActivateNextModalWindow();
+
+ // Creates modal background window, which is a partially-opaque
+ // fullscreen window. If there is already a modal background window,
+ // it will bring it the top.
+ void CreateModalBackground();
+
+ void DestroyModalBackground();
+
+ // Is the |window| modal background?
+ static bool IsModalBackground(aura::Window* window);
+
+ private:
+ void AddModalWindow(aura::Window* window);
+ void RemoveModalWindow(aura::Window* window);
+
+ aura::Window* modal_window() {
+ return !modal_windows_.empty() ? modal_windows_.back() : NULL;
+ }
+
+ // The container that owns the layout manager.
+ aura::Window* container_;
+
+ // A widget that dims the windows behind the modal window(s) being
+ // shown in |container_|.
+ views::Widget* modal_background_;
+
+ // A stack of modal windows. Only the topmost can receive events.
+ std::vector<aura::Window*> modal_windows_;
+
+ DISALLOW_COPY_AND_ASSIGN(SystemModalContainerLayoutManager);
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_WM_SYSTEM_MODAL_CONTAINER_LAYOUT_MANAGER_H_
diff --git a/chromium/ash/wm/system_modal_container_layout_manager_unittest.cc b/chromium/ash/wm/system_modal_container_layout_manager_unittest.cc
new file mode 100644
index 00000000000..52643f499bb
--- /dev/null
+++ b/chromium/ash/wm/system_modal_container_layout_manager_unittest.cc
@@ -0,0 +1,503 @@
+// Copyright (c) 2012 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.
+
+#include "ash/wm/system_modal_container_layout_manager.h"
+
+#include "ash/root_window_controller.h"
+#include "ash/session_state_delegate.h"
+#include "ash/shell.h"
+#include "ash/shell_window_ids.h"
+#include "ash/test/ash_test_base.h"
+#include "ash/wm/window_util.h"
+#include "base/compiler_specific.h"
+#include "base/run_loop.h"
+#include "ui/aura/root_window.h"
+#include "ui/aura/test/event_generator.h"
+#include "ui/aura/window.h"
+#include "ui/compositor/layer.h"
+#include "ui/gfx/screen.h"
+#include "ui/views/test/capture_tracking_view.h"
+#include "ui/views/widget/widget.h"
+#include "ui/views/widget/widget_delegate.h"
+
+namespace ash {
+namespace test {
+
+namespace {
+
+aura::Window* GetModalContainer() {
+ return Shell::GetPrimaryRootWindowController()->GetContainer(
+ ash::internal::kShellWindowId_SystemModalContainer);
+}
+
+bool AllRootWindowsHaveModalBackgroundsForContainer(int container_id) {
+ std::vector<aura::Window*> containers =
+ Shell::GetContainersFromAllRootWindows(container_id, NULL);
+ bool has_modal_screen = !containers.empty();
+ for (std::vector<aura::Window*>::iterator iter = containers.begin();
+ iter != containers.end(); ++iter) {
+ has_modal_screen &=
+ static_cast<internal::SystemModalContainerLayoutManager*>(
+ (*iter)->layout_manager())->has_modal_background();
+ }
+ return has_modal_screen;
+}
+
+bool AllRootWindowsHaveLockedModalBackgrounds() {
+ return AllRootWindowsHaveModalBackgroundsForContainer(
+ internal::kShellWindowId_LockSystemModalContainer);
+}
+
+bool AllRootWindowsHaveModalBackgrounds() {
+ return AllRootWindowsHaveModalBackgroundsForContainer(
+ internal::kShellWindowId_SystemModalContainer);
+}
+
+class TestWindow : public views::WidgetDelegateView {
+ public:
+ explicit TestWindow(bool modal) : modal_(modal) {}
+ virtual ~TestWindow() {}
+
+ // The window needs be closed from widget in order for
+ // aura::client::kModalKey property to be reset.
+ static void CloseTestWindow(aura::Window* window) {
+ views::Widget::GetWidgetForNativeWindow(window)->Close();
+ }
+
+ // Overridden from views::View:
+ virtual gfx::Size GetPreferredSize() OVERRIDE {
+ return gfx::Size(50, 50);
+ }
+
+ // Overridden from views::WidgetDelegate:
+ virtual views::View* GetContentsView() OVERRIDE {
+ return this;
+ }
+ virtual ui::ModalType GetModalType() const OVERRIDE {
+ return modal_ ? ui::MODAL_TYPE_SYSTEM : ui::MODAL_TYPE_NONE;
+ }
+
+ private:
+ bool modal_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestWindow);
+};
+
+class EventTestWindow : public TestWindow {
+ public:
+ explicit EventTestWindow(bool modal) : TestWindow(modal),
+ mouse_presses_(0) {}
+ virtual ~EventTestWindow() {}
+
+ aura::Window* OpenTestWindowWithContext(aura::RootWindow* context) {
+ views::Widget* widget =
+ views::Widget::CreateWindowWithContext(this, context);
+ widget->Show();
+ return widget->GetNativeView();
+ }
+
+ aura::Window* OpenTestWindowWithParent(aura::Window* parent) {
+ DCHECK(parent);
+ views::Widget* widget =
+ views::Widget::CreateWindowWithParent(this, parent);
+ widget->Show();
+ return widget->GetNativeView();
+ }
+
+ // Overridden from views::View:
+ virtual bool OnMousePressed(const ui::MouseEvent& event) OVERRIDE {
+ mouse_presses_++;
+ return false;
+ }
+
+ int mouse_presses() const { return mouse_presses_; }
+ private:
+ int mouse_presses_;
+
+ DISALLOW_COPY_AND_ASSIGN(EventTestWindow);
+};
+
+class TransientWindowObserver : public aura::WindowObserver {
+ public:
+ TransientWindowObserver() : destroyed_(false) {}
+ virtual ~TransientWindowObserver() {}
+
+ bool destroyed() const { return destroyed_; }
+
+ // Overridden from aura::WindowObserver:
+ virtual void OnWindowDestroyed(aura::Window* window) OVERRIDE {
+ destroyed_ = true;
+ }
+
+ private:
+ bool destroyed_;
+
+ DISALLOW_COPY_AND_ASSIGN(TransientWindowObserver);
+};
+
+} // namespace
+
+class SystemModalContainerLayoutManagerTest : public AshTestBase {
+ public:
+ aura::Window* OpenToplevelTestWindow(bool modal) {
+ views::Widget* widget = views::Widget::CreateWindowWithContext(
+ new TestWindow(modal), CurrentContext());
+ widget->Show();
+ return widget->GetNativeView();
+ }
+
+ aura::Window* OpenTestWindowWithParent(aura::Window* parent, bool modal) {
+ views::Widget* widget =
+ views::Widget::CreateWindowWithParent(new TestWindow(modal), parent);
+ widget->Show();
+ return widget->GetNativeView();
+ }
+};
+
+TEST_F(SystemModalContainerLayoutManagerTest, NonModalTransient) {
+ scoped_ptr<aura::Window> parent(OpenToplevelTestWindow(false));
+ aura::Window* transient = OpenTestWindowWithParent(parent.get(), false);
+ TransientWindowObserver destruction_observer;
+ transient->AddObserver(&destruction_observer);
+
+ EXPECT_EQ(parent.get(), transient->transient_parent());
+ EXPECT_EQ(parent->parent(), transient->parent());
+
+ // The transient should be destroyed with its parent.
+ parent.reset();
+ EXPECT_TRUE(destruction_observer.destroyed());
+}
+
+TEST_F(SystemModalContainerLayoutManagerTest, ModalTransient) {
+ scoped_ptr<aura::Window> parent(OpenToplevelTestWindow(false));
+ // parent should be active.
+ EXPECT_TRUE(wm::IsActiveWindow(parent.get()));
+ aura::Window* t1 = OpenTestWindowWithParent(parent.get(), true);
+
+ TransientWindowObserver do1;
+ t1->AddObserver(&do1);
+
+ EXPECT_EQ(parent.get(), t1->transient_parent());
+ EXPECT_EQ(GetModalContainer(), t1->parent());
+
+ // t1 should now be active.
+ EXPECT_TRUE(wm::IsActiveWindow(t1));
+
+ // Attempting to click the parent should result in no activation change.
+ aura::test::EventGenerator e1(Shell::GetPrimaryRootWindow(), parent.get());
+ e1.ClickLeftButton();
+ EXPECT_TRUE(wm::IsActiveWindow(t1));
+
+ // Now open another modal transient parented to the original modal transient.
+ aura::Window* t2 = OpenTestWindowWithParent(t1, true);
+ TransientWindowObserver do2;
+ t2->AddObserver(&do2);
+
+ EXPECT_TRUE(wm::IsActiveWindow(t2));
+
+ EXPECT_EQ(t1, t2->transient_parent());
+ EXPECT_EQ(GetModalContainer(), t2->parent());
+
+ // t2 should still be active, even after clicking on t1.
+ aura::test::EventGenerator e2(Shell::GetPrimaryRootWindow(), t1);
+ e2.ClickLeftButton();
+ EXPECT_TRUE(wm::IsActiveWindow(t2));
+
+ // Both transients should be destroyed with parent.
+ parent.reset();
+ EXPECT_TRUE(do1.destroyed());
+ EXPECT_TRUE(do2.destroyed());
+}
+
+TEST_F(SystemModalContainerLayoutManagerTest, ModalNonTransient) {
+ scoped_ptr<aura::Window> t1(OpenToplevelTestWindow(true));
+ // parent should be active.
+ EXPECT_TRUE(wm::IsActiveWindow(t1.get()));
+ TransientWindowObserver do1;
+ t1->AddObserver(&do1);
+
+ EXPECT_EQ(NULL, t1->transient_parent());
+ EXPECT_EQ(GetModalContainer(), t1->parent());
+
+ // t1 should now be active.
+ EXPECT_TRUE(wm::IsActiveWindow(t1.get()));
+
+ // Attempting to click the parent should result in no activation change.
+ aura::test::EventGenerator e1(Shell::GetPrimaryRootWindow(),
+ Shell::GetPrimaryRootWindow());
+ e1.ClickLeftButton();
+ EXPECT_TRUE(wm::IsActiveWindow(t1.get()));
+
+ // Now open another modal transient parented to the original modal transient.
+ aura::Window* t2 = OpenTestWindowWithParent(t1.get(), true);
+ TransientWindowObserver do2;
+ t2->AddObserver(&do2);
+
+ EXPECT_TRUE(wm::IsActiveWindow(t2));
+
+ EXPECT_EQ(t1, t2->transient_parent());
+ EXPECT_EQ(GetModalContainer(), t2->parent());
+
+ // t2 should still be active, even after clicking on t1.
+ aura::test::EventGenerator e2(Shell::GetPrimaryRootWindow(), t1.get());
+ e2.ClickLeftButton();
+ EXPECT_TRUE(wm::IsActiveWindow(t2));
+
+ // Both transients should be destroyed with parent.
+ t1.reset();
+ EXPECT_TRUE(do1.destroyed());
+ EXPECT_TRUE(do2.destroyed());
+}
+
+// Fails on Mac only. Needs to be implemented. http://crbug.com/111279.
+#if defined(OS_MACOSX)
+#define MAYBE_CanActivateAfterEndModalSession \
+ DISABLED_CanActivateAfterEndModalSession
+#else
+#define MAYBE_CanActivateAfterEndModalSession CanActivateAfterEndModalSession
+#endif
+// Tests that we can activate an unrelated window after a modal window is closed
+// for a window.
+TEST_F(SystemModalContainerLayoutManagerTest,
+ MAYBE_CanActivateAfterEndModalSession) {
+ scoped_ptr<aura::Window> unrelated(OpenToplevelTestWindow(false));
+ unrelated->SetBounds(gfx::Rect(100, 100, 50, 50));
+ scoped_ptr<aura::Window> parent(OpenToplevelTestWindow(false));
+ // parent should be active.
+ EXPECT_TRUE(wm::IsActiveWindow(parent.get()));
+
+ scoped_ptr<aura::Window> transient(
+ OpenTestWindowWithParent(parent.get(), true));
+ // t1 should now be active.
+ EXPECT_TRUE(wm::IsActiveWindow(transient.get()));
+
+ // Attempting to click the parent should result in no activation change.
+ aura::test::EventGenerator e1(Shell::GetPrimaryRootWindow(), parent.get());
+ e1.ClickLeftButton();
+ EXPECT_TRUE(wm::IsActiveWindow(transient.get()));
+
+ // Now close the transient.
+ transient->Hide();
+ TestWindow::CloseTestWindow(transient.release());
+
+ base::RunLoop().RunUntilIdle();
+
+ // parent should now be active again.
+ EXPECT_TRUE(wm::IsActiveWindow(parent.get()));
+
+ // Attempting to click unrelated should activate it.
+ aura::test::EventGenerator e2(Shell::GetPrimaryRootWindow(), unrelated.get());
+ e2.ClickLeftButton();
+ EXPECT_TRUE(wm::IsActiveWindow(unrelated.get()));
+}
+
+TEST_F(SystemModalContainerLayoutManagerTest, EventFocusContainers) {
+ // Create a normal window and attempt to receive a click event.
+ EventTestWindow* main_delegate = new EventTestWindow(false);
+ scoped_ptr<aura::Window> main(
+ main_delegate->OpenTestWindowWithContext(CurrentContext()));
+ EXPECT_TRUE(wm::IsActiveWindow(main.get()));
+ aura::test::EventGenerator e1(Shell::GetPrimaryRootWindow(), main.get());
+ e1.ClickLeftButton();
+ EXPECT_EQ(1, main_delegate->mouse_presses());
+
+ // Create a modal window for the main window and verify that the main window
+ // no longer receives mouse events.
+ EventTestWindow* transient_delegate = new EventTestWindow(true);
+ aura::Window* transient =
+ transient_delegate->OpenTestWindowWithParent(main.get());
+ EXPECT_TRUE(wm::IsActiveWindow(transient));
+ e1.ClickLeftButton();
+ EXPECT_EQ(1, transient_delegate->mouse_presses());
+
+ for (int block_reason = FIRST_BLOCK_REASON;
+ block_reason < NUMBER_OF_BLOCK_REASONS;
+ ++block_reason) {
+ // Create a window in the lock screen container and ensure that it receives
+ // the mouse event instead of the modal window (crbug.com/110920).
+ BlockUserSession(static_cast<UserSessionBlockReason>(block_reason));
+ EventTestWindow* lock_delegate = new EventTestWindow(false);
+ scoped_ptr<aura::Window> lock(lock_delegate->OpenTestWindowWithParent(
+ Shell::GetPrimaryRootWindowController()->GetContainer(
+ ash::internal::kShellWindowId_LockScreenContainer)));
+ EXPECT_TRUE(wm::IsActiveWindow(lock.get()));
+ e1.ClickLeftButton();
+ EXPECT_EQ(1, lock_delegate->mouse_presses());
+
+ // Make sure that a modal container created by the lock screen can still
+ // receive mouse events.
+ EventTestWindow* lock_modal_delegate = new EventTestWindow(true);
+ aura::Window* lock_modal =
+ lock_modal_delegate->OpenTestWindowWithParent(lock.get());
+ EXPECT_TRUE(wm::IsActiveWindow(lock_modal));
+ e1.ClickLeftButton();
+ // Verify that none of the other containers received any more mouse presses.
+ EXPECT_EQ(1, lock_modal_delegate->mouse_presses());
+ EXPECT_EQ(1, lock_delegate->mouse_presses());
+ EXPECT_EQ(1, main_delegate->mouse_presses());
+ EXPECT_EQ(1, transient_delegate->mouse_presses());
+ UnblockUserSession();
+ }
+}
+
+// Makes sure we don't crash if a modal window is shown while the parent window
+// is hidden.
+TEST_F(SystemModalContainerLayoutManagerTest, ShowModalWhileHidden) {
+ // Hide the lock screen.
+ Shell::GetPrimaryRootWindowController()->GetContainer(
+ internal::kShellWindowId_SystemModalContainer)->layer()->SetOpacity(0);
+
+ // Create a modal window.
+ scoped_ptr<aura::Window> parent(OpenToplevelTestWindow(false));
+ scoped_ptr<aura::Window> modal_window(
+ OpenTestWindowWithParent(parent.get(), true));
+ parent->Show();
+ modal_window->Show();
+}
+
+// Verifies we generate a capture lost when showing a modal window.
+TEST_F(SystemModalContainerLayoutManagerTest, ChangeCapture) {
+ views::Widget* widget = views::Widget::CreateWindowWithContext(
+ new TestWindow(false), CurrentContext());
+ scoped_ptr<aura::Window> widget_window(widget->GetNativeView());
+ views::test::CaptureTrackingView* view = new views::test::CaptureTrackingView;
+ widget->GetContentsView()->AddChildView(view);
+ view->SetBoundsRect(widget->GetContentsView()->bounds());
+ widget->Show();
+
+ gfx::Point center(view->width() / 2, view->height() / 2);
+ views::View::ConvertPointToScreen(view, &center);
+ aura::test::EventGenerator generator(Shell::GetPrimaryRootWindow(), center);
+ generator.PressLeftButton();
+ EXPECT_TRUE(view->got_press());
+ scoped_ptr<aura::Window> modal_window(
+ OpenTestWindowWithParent(widget->GetNativeView(), true));
+ modal_window->Show();
+ EXPECT_TRUE(view->got_capture_lost());
+}
+
+// Verifies that the window gets moved into the visible screen area upon screen
+// resize.
+TEST_F(SystemModalContainerLayoutManagerTest, KeepVisible) {
+ GetModalContainer()->SetBounds(gfx::Rect(0, 0, 1024, 768));
+ scoped_ptr<aura::Window> main(OpenTestWindowWithParent(GetModalContainer(),
+ true));
+ main->SetBounds(gfx::Rect(924, 668, 100, 100));
+ // We set now the bounds of the root window to something new which will
+ // Then trigger the repos operation.
+ GetModalContainer()->SetBounds(gfx::Rect(0, 0, 800, 600));
+
+ gfx::Rect bounds = main->bounds();
+ EXPECT_EQ(bounds, gfx::Rect(700, 500, 100, 100));
+}
+
+TEST_F(SystemModalContainerLayoutManagerTest, ShowNormalBackgroundOrLocked) {
+ scoped_ptr<aura::Window> parent(OpenToplevelTestWindow(false));
+ scoped_ptr<aura::Window> modal_window(
+ OpenTestWindowWithParent(parent.get(), true));
+ parent->Show();
+ modal_window->Show();
+
+ // Normal system modal window. Shows normal system modal background and not
+ // locked.
+ EXPECT_TRUE(AllRootWindowsHaveModalBackgrounds());
+ EXPECT_FALSE(AllRootWindowsHaveLockedModalBackgrounds());
+
+ TestWindow::CloseTestWindow(modal_window.release());
+ EXPECT_FALSE(AllRootWindowsHaveModalBackgrounds());
+ EXPECT_FALSE(AllRootWindowsHaveLockedModalBackgrounds());
+
+ for (int block_reason = FIRST_BLOCK_REASON;
+ block_reason < NUMBER_OF_BLOCK_REASONS;
+ ++block_reason) {
+ // Normal system modal window while blocked. Shows blocked system modal
+ // background.
+ BlockUserSession(static_cast<UserSessionBlockReason>(block_reason));
+ scoped_ptr<aura::Window> lock_parent(OpenTestWindowWithParent(
+ Shell::GetPrimaryRootWindowController()->GetContainer(
+ ash::internal::kShellWindowId_LockScreenContainer),
+ false));
+ scoped_ptr<aura::Window> lock_modal_window(OpenTestWindowWithParent(
+ lock_parent.get(), true));
+ lock_parent->Show();
+ lock_modal_window->Show();
+ EXPECT_FALSE(AllRootWindowsHaveModalBackgrounds());
+ EXPECT_TRUE(AllRootWindowsHaveLockedModalBackgrounds());
+ TestWindow::CloseTestWindow(lock_modal_window.release());
+
+ // Normal system modal window while blocked, but it belongs to the normal
+ // window. Shouldn't show blocked system modal background, but normal.
+ scoped_ptr<aura::Window> modal_window(
+ OpenTestWindowWithParent(parent.get(), true));
+ modal_window->Show();
+ EXPECT_TRUE(AllRootWindowsHaveModalBackgrounds());
+ EXPECT_FALSE(AllRootWindowsHaveLockedModalBackgrounds());
+ TestWindow::CloseTestWindow(modal_window.release());
+ UnblockUserSession();
+ // Here we should check the behavior of the locked system modal dialog when
+ // unlocked, but such case isn't handled very well right now.
+ // See crbug.com/157660
+ // TODO(mukai): add the test case when the bug is fixed.
+ }
+}
+
+TEST_F(SystemModalContainerLayoutManagerTest, MultiDisplays) {
+ if (!SupportsMultipleDisplays())
+ return;
+
+ UpdateDisplay("500x500,500x500");
+
+ scoped_ptr<aura::Window> normal(OpenToplevelTestWindow(false));
+ normal->SetBounds(gfx::Rect(100, 100, 50, 50));
+
+ Shell::RootWindowList root_windows = Shell::GetAllRootWindows();
+ EXPECT_EQ(2U, root_windows.size());
+ aura::Window* container1 = Shell::GetContainer(
+ root_windows[0], ash::internal::kShellWindowId_SystemModalContainer);
+ aura::Window* container2 = Shell::GetContainer(
+ root_windows[1], ash::internal::kShellWindowId_SystemModalContainer);
+
+ scoped_ptr<aura::Window> modal1(
+ OpenTestWindowWithParent(container1, true));
+ EXPECT_TRUE(AllRootWindowsHaveModalBackgrounds());
+ EXPECT_TRUE(wm::IsActiveWindow(modal1.get()));
+
+ scoped_ptr<aura::Window> modal11(
+ OpenTestWindowWithParent(container1, true));
+ EXPECT_TRUE(wm::IsActiveWindow(modal11.get()));
+
+ scoped_ptr<aura::Window> modal2(
+ OpenTestWindowWithParent(container2, true));
+ EXPECT_TRUE(wm::IsActiveWindow(modal2.get()));
+
+ // Sanity check if they're on the correct containers.
+ EXPECT_EQ(container1, modal1->parent());
+ EXPECT_EQ(container1, modal11->parent());
+ EXPECT_EQ(container2, modal2->parent());
+
+ TestWindow::CloseTestWindow(modal2.release());
+ EXPECT_TRUE(AllRootWindowsHaveModalBackgrounds());
+ EXPECT_TRUE(wm::IsActiveWindow(modal11.get()));
+
+ TestWindow::CloseTestWindow(modal11.release());
+ EXPECT_TRUE(AllRootWindowsHaveModalBackgrounds());
+ EXPECT_TRUE(wm::IsActiveWindow(modal1.get()));
+
+ UpdateDisplay("500x500");
+ EXPECT_TRUE(AllRootWindowsHaveModalBackgrounds());
+ EXPECT_TRUE(wm::IsActiveWindow(modal1.get()));
+
+ UpdateDisplay("500x500,600x600");
+ EXPECT_TRUE(AllRootWindowsHaveModalBackgrounds());
+ EXPECT_TRUE(wm::IsActiveWindow(modal1.get()));
+
+ // No more modal screen.
+ modal1->Hide();
+ TestWindow::CloseTestWindow(modal1.release());
+ EXPECT_FALSE(AllRootWindowsHaveModalBackgrounds());
+ EXPECT_TRUE(wm::IsActiveWindow(normal.get()));
+}
+
+} // namespace test
+} // namespace ash
diff --git a/chromium/ash/wm/toplevel_window_event_handler.cc b/chromium/ash/wm/toplevel_window_event_handler.cc
new file mode 100644
index 00000000000..a0d4b5e7e0b
--- /dev/null
+++ b/chromium/ash/wm/toplevel_window_event_handler.cc
@@ -0,0 +1,534 @@
+// Copyright (c) 2012 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.
+
+#include "ash/wm/toplevel_window_event_handler.h"
+
+#include "ash/shell.h"
+#include "ash/wm/property_util.h"
+#include "ash/wm/resize_shadow_controller.h"
+#include "ash/wm/window_properties.h"
+#include "ash/wm/window_resizer.h"
+#include "ash/wm/window_util.h"
+#include "ash/wm/workspace/snap_sizer.h"
+#include "base/message_loop/message_loop.h"
+#include "base/run_loop.h"
+#include "ui/aura/client/aura_constants.h"
+#include "ui/aura/client/cursor_client.h"
+#include "ui/aura/env.h"
+#include "ui/aura/root_window.h"
+#include "ui/aura/window.h"
+#include "ui/aura/window_delegate.h"
+#include "ui/aura/window_observer.h"
+#include "ui/base/cursor/cursor.h"
+#include "ui/base/events/event.h"
+#include "ui/base/events/event_utils.h"
+#include "ui/base/gestures/gesture_recognizer.h"
+#include "ui/base/hit_test.h"
+#include "ui/base/ui_base_types.h"
+#include "ui/compositor/layer.h"
+#include "ui/compositor/scoped_layer_animation_settings.h"
+#include "ui/gfx/screen.h"
+
+namespace {
+const double kMinHorizVelocityForWindowSwipe = 1100;
+const double kMinVertVelocityForWindowMinimize = 1000;
+}
+
+namespace ash {
+
+namespace {
+
+gfx::Point ConvertPointToParent(aura::Window* window,
+ const gfx::Point& point) {
+ gfx::Point result(point);
+ aura::Window::ConvertPointToTarget(window, window->parent(), &result);
+ return result;
+}
+
+} // namespace
+
+// ScopedWindowResizer ---------------------------------------------------------
+
+// Wraps a WindowResizer and installs an observer on its target window. When
+// the window is destroyed ResizerWindowDestroyed() is invoked back on the
+// ToplevelWindowEventHandler to clean up.
+class ToplevelWindowEventHandler::ScopedWindowResizer
+ : public aura::WindowObserver {
+ public:
+ ScopedWindowResizer(ToplevelWindowEventHandler* handler,
+ WindowResizer* resizer);
+ virtual ~ScopedWindowResizer();
+
+ WindowResizer* resizer() { return resizer_.get(); }
+
+ // WindowObserver overrides:
+ virtual void OnWindowHierarchyChanging(
+ const HierarchyChangeParams& params) OVERRIDE;
+ virtual void OnWindowPropertyChanged(aura::Window* window,
+ const void* key,
+ intptr_t old) OVERRIDE;
+ virtual void OnWindowDestroying(aura::Window* window) OVERRIDE;
+
+ private:
+ void AddHandlers(aura::Window* container);
+ void RemoveHandlers();
+
+ ToplevelWindowEventHandler* handler_;
+ scoped_ptr<WindowResizer> resizer_;
+
+ // If not NULL, this is an additional container that the dragged window has
+ // moved to which ScopedWindowResizer has temporarily added observers on.
+ aura::Window* target_container_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScopedWindowResizer);
+};
+
+ToplevelWindowEventHandler::ScopedWindowResizer::ScopedWindowResizer(
+ ToplevelWindowEventHandler* handler,
+ WindowResizer* resizer)
+ : handler_(handler),
+ resizer_(resizer),
+ target_container_(NULL) {
+ if (resizer_)
+ resizer_->GetTarget()->AddObserver(this);
+}
+
+ToplevelWindowEventHandler::ScopedWindowResizer::~ScopedWindowResizer() {
+ RemoveHandlers();
+ if (resizer_)
+ resizer_->GetTarget()->RemoveObserver(this);
+}
+
+void ToplevelWindowEventHandler::ScopedWindowResizer::OnWindowHierarchyChanging(
+ const HierarchyChangeParams& params) {
+ if (params.receiver != resizer_->GetTarget())
+ return;
+
+ if (params.receiver->GetProperty(internal::kContinueDragAfterReparent)) {
+ params.receiver->SetProperty(internal::kContinueDragAfterReparent, false);
+ AddHandlers(params.new_parent);
+ } else {
+ handler_->CompleteDrag(DRAG_COMPLETE, 0);
+ }
+}
+
+void ToplevelWindowEventHandler::ScopedWindowResizer::OnWindowPropertyChanged(
+ aura::Window* window,
+ const void* key,
+ intptr_t old) {
+ if (key == aura::client::kShowStateKey && !wm::IsWindowNormal(window))
+ handler_->CompleteDrag(DRAG_COMPLETE, 0);
+}
+
+void ToplevelWindowEventHandler::ScopedWindowResizer::OnWindowDestroying(
+ aura::Window* window) {
+ DCHECK(resizer_.get());
+ DCHECK_EQ(resizer_->GetTarget(), window);
+ handler_->ResizerWindowDestroyed();
+}
+
+void ToplevelWindowEventHandler::ScopedWindowResizer::AddHandlers(
+ aura::Window* container) {
+ RemoveHandlers();
+ if (!handler_->owner()->Contains(container)) {
+ container->AddPreTargetHandler(handler_);
+ container->AddPostTargetHandler(handler_);
+ target_container_ = container;
+ }
+}
+
+void ToplevelWindowEventHandler::ScopedWindowResizer::RemoveHandlers() {
+ if (target_container_) {
+ target_container_->RemovePreTargetHandler(handler_);
+ target_container_->RemovePostTargetHandler(handler_);
+ target_container_ = NULL;
+ }
+}
+
+
+// ToplevelWindowEventHandler --------------------------------------------------
+
+ToplevelWindowEventHandler::ToplevelWindowEventHandler(aura::Window* owner)
+ : owner_(owner),
+ in_move_loop_(false),
+ move_cancelled_(false),
+ in_gesture_drag_(false),
+ destroyed_(NULL) {
+ aura::client::SetWindowMoveClient(owner, this);
+ Shell::GetInstance()->display_controller()->AddObserver(this);
+ owner->AddPreTargetHandler(this);
+ owner->AddPostTargetHandler(this);
+}
+
+ToplevelWindowEventHandler::~ToplevelWindowEventHandler() {
+ Shell::GetInstance()->display_controller()->RemoveObserver(this);
+ if (destroyed_)
+ *destroyed_ = true;
+}
+
+void ToplevelWindowEventHandler::OnKeyEvent(ui::KeyEvent* event) {
+ if (window_resizer_.get() && event->type() == ui::ET_KEY_PRESSED &&
+ event->key_code() == ui::VKEY_ESCAPE) {
+ CompleteDrag(DRAG_REVERT, event->flags());
+ }
+}
+
+void ToplevelWindowEventHandler::OnMouseEvent(
+ ui::MouseEvent* event) {
+ if ((event->flags() &
+ (ui::EF_MIDDLE_MOUSE_BUTTON | ui::EF_RIGHT_MOUSE_BUTTON)) != 0)
+ return;
+
+ if (in_gesture_drag_)
+ return;
+
+ aura::Window* target = static_cast<aura::Window*>(event->target());
+ switch (event->type()) {
+ case ui::ET_MOUSE_PRESSED:
+ HandleMousePressed(target, event);
+ break;
+ case ui::ET_MOUSE_DRAGGED:
+ HandleDrag(target, event);
+ break;
+ case ui::ET_MOUSE_CAPTURE_CHANGED:
+ case ui::ET_MOUSE_RELEASED:
+ HandleMouseReleased(target, event);
+ break;
+ case ui::ET_MOUSE_MOVED:
+ HandleMouseMoved(target, event);
+ break;
+ case ui::ET_MOUSE_EXITED:
+ HandleMouseExited(target, event);
+ break;
+ default:
+ break;
+ }
+}
+
+void ToplevelWindowEventHandler::OnGestureEvent(ui::GestureEvent* event) {
+ aura::Window* target = static_cast<aura::Window*>(event->target());
+ if (!target->delegate())
+ return;
+
+ if (in_move_loop_ && !in_gesture_drag_)
+ return;
+
+ switch (event->type()) {
+ case ui::ET_GESTURE_TAP_DOWN: {
+ int component =
+ target->delegate()->GetNonClientComponent(event->location());
+ if (!(WindowResizer::GetBoundsChangeForWindowComponent(component) &
+ WindowResizer::kBoundsChange_Resizes))
+ return;
+ internal::ResizeShadowController* controller =
+ Shell::GetInstance()->resize_shadow_controller();
+ if (controller)
+ controller->ShowShadow(target, component);
+ return;
+ }
+ case ui::ET_GESTURE_END: {
+ internal::ResizeShadowController* controller =
+ Shell::GetInstance()->resize_shadow_controller();
+ if (controller)
+ controller->HideShadow(target);
+ return;
+ }
+ case ui::ET_GESTURE_SCROLL_BEGIN: {
+ if (in_gesture_drag_)
+ return;
+ int component =
+ target->delegate()->GetNonClientComponent(event->location());
+ if (WindowResizer::GetBoundsChangeForWindowComponent(component) == 0) {
+ window_resizer_.reset();
+ return;
+ }
+ in_gesture_drag_ = true;
+ pre_drag_window_bounds_ = target->bounds();
+ gfx::Point location_in_parent(
+ ConvertPointToParent(target, event->location()));
+ CreateScopedWindowResizer(target, location_in_parent, component,
+ aura::client::WINDOW_MOVE_SOURCE_TOUCH);
+ break;
+ }
+ case ui::ET_GESTURE_SCROLL_UPDATE: {
+ if (!in_gesture_drag_)
+ return;
+ if (window_resizer_.get() &&
+ window_resizer_->resizer()->GetTarget() != target) {
+ return;
+ }
+ HandleDrag(target, event);
+ break;
+ }
+ case ui::ET_GESTURE_SCROLL_END:
+ case ui::ET_SCROLL_FLING_START: {
+ if (!in_gesture_drag_)
+ return;
+ if (window_resizer_.get() &&
+ window_resizer_->resizer()->GetTarget() != target) {
+ return;
+ }
+
+ CompleteDrag(DRAG_COMPLETE, event->flags());
+ if (in_move_loop_) {
+ quit_closure_.Run();
+ in_move_loop_ = false;
+ }
+ in_gesture_drag_ = false;
+
+ if (event->type() == ui::ET_GESTURE_SCROLL_END) {
+ event->StopPropagation();
+ return;
+ }
+
+ int component =
+ target->delegate()->GetNonClientComponent(event->location());
+ if (WindowResizer::GetBoundsChangeForWindowComponent(component) == 0)
+ return;
+ if (!wm::IsWindowNormal(target))
+ return;
+
+ if (fabs(event->details().velocity_y()) >
+ kMinVertVelocityForWindowMinimize) {
+ // Minimize/maximize.
+ if (event->details().velocity_y() > 0 &&
+ wm::CanMinimizeWindow(target)) {
+ wm::MinimizeWindow(target);
+ SetWindowAlwaysRestoresToRestoreBounds(target, true);
+ SetRestoreBoundsInParent(target, pre_drag_window_bounds_);
+ } else if (wm::CanMaximizeWindow(target)) {
+ SetRestoreBoundsInParent(target, pre_drag_window_bounds_);
+ wm::MaximizeWindow(target);
+ }
+ } else if (wm::CanSnapWindow(target) &&
+ fabs(event->details().velocity_x()) >
+ kMinHorizVelocityForWindowSwipe) {
+ // Snap left/right.
+ ui::ScopedLayerAnimationSettings scoped_setter(
+ target->layer()->GetAnimator());
+ scoped_setter.SetPreemptionStrategy(
+ ui::LayerAnimator::REPLACE_QUEUED_ANIMATIONS);
+ internal::SnapSizer::SnapWindow(target,
+ event->details().velocity_x() < 0 ?
+ internal::SnapSizer::LEFT_EDGE : internal::SnapSizer::RIGHT_EDGE);
+ }
+ break;
+ }
+ default:
+ return;
+ }
+
+ event->StopPropagation();
+}
+
+aura::client::WindowMoveResult ToplevelWindowEventHandler::RunMoveLoop(
+ aura::Window* source,
+ const gfx::Vector2d& drag_offset,
+ aura::client::WindowMoveSource move_source) {
+ DCHECK(!in_move_loop_); // Can only handle one nested loop at a time.
+ in_move_loop_ = true;
+ move_cancelled_ = false;
+ aura::RootWindow* root_window = source->GetRootWindow();
+ DCHECK(root_window);
+ gfx::Point drag_location;
+ if (move_source == aura::client::WINDOW_MOVE_SOURCE_TOUCH &&
+ aura::Env::GetInstance()->is_touch_down()) {
+ in_gesture_drag_ = true;
+ bool has_point = root_window->gesture_recognizer()->
+ GetLastTouchPointForTarget(source, &drag_location);
+ DCHECK(has_point);
+ } else {
+ drag_location = root_window->GetLastMouseLocationInRoot();
+ aura::Window::ConvertPointToTarget(
+ root_window, source->parent(), &drag_location);
+ }
+ // Set the cursor before calling CreateScopedWindowResizer(), as that will
+ // eventually call LockCursor() and prevent the cursor from changing.
+ aura::client::CursorClient* cursor_client =
+ aura::client::GetCursorClient(root_window);
+ if (cursor_client)
+ cursor_client->SetCursor(ui::kCursorPointer);
+ CreateScopedWindowResizer(source, drag_location, HTCAPTION, move_source);
+ bool destroyed = false;
+ destroyed_ = &destroyed;
+#if !defined(OS_MACOSX)
+ base::MessageLoopForUI* loop = base::MessageLoopForUI::current();
+ base::MessageLoop::ScopedNestableTaskAllower allow_nested(loop);
+ base::RunLoop run_loop(aura::Env::GetInstance()->GetDispatcher());
+ quit_closure_ = run_loop.QuitClosure();
+ run_loop.Run();
+#endif // !defined(OS_MACOSX)
+ if (destroyed)
+ return aura::client::MOVE_CANCELED;
+ destroyed_ = NULL;
+ in_gesture_drag_ = in_move_loop_ = false;
+ return move_cancelled_ ? aura::client::MOVE_CANCELED :
+ aura::client::MOVE_SUCCESSFUL;
+}
+
+void ToplevelWindowEventHandler::EndMoveLoop() {
+ if (!in_move_loop_)
+ return;
+
+ in_move_loop_ = false;
+ if (window_resizer_) {
+ window_resizer_->resizer()->RevertDrag();
+ window_resizer_.reset();
+ }
+ quit_closure_.Run();
+}
+
+void ToplevelWindowEventHandler::OnDisplayConfigurationChanging() {
+ if (in_move_loop_) {
+ move_cancelled_ = true;
+ EndMoveLoop();
+ } else if (window_resizer_) {
+ window_resizer_->resizer()->RevertDrag();
+ window_resizer_.reset();
+ }
+}
+
+void ToplevelWindowEventHandler::CreateScopedWindowResizer(
+ aura::Window* window,
+ const gfx::Point& point_in_parent,
+ int window_component,
+ aura::client::WindowMoveSource source) {
+ window_resizer_.reset();
+ WindowResizer* resizer =
+ CreateWindowResizer(window, point_in_parent, window_component,
+ source).release();
+ if (resizer)
+ window_resizer_.reset(new ScopedWindowResizer(this, resizer));
+}
+
+void ToplevelWindowEventHandler::CompleteDrag(DragCompletionStatus status,
+ int event_flags) {
+ scoped_ptr<ScopedWindowResizer> resizer(window_resizer_.release());
+ if (resizer) {
+ if (status == DRAG_COMPLETE)
+ resizer->resizer()->CompleteDrag(event_flags);
+ else
+ resizer->resizer()->RevertDrag();
+ }
+}
+
+void ToplevelWindowEventHandler::HandleMousePressed(
+ aura::Window* target,
+ ui::MouseEvent* event) {
+ // Move/size operations are initiated post-target handling to give the target
+ // an opportunity to cancel this default behavior by returning ER_HANDLED.
+ if (ui::EventCanceledDefaultHandling(*event))
+ return;
+
+ // We also update the current window component here because for the
+ // mouse-drag-release-press case, where the mouse is released and
+ // pressed without mouse move event.
+ int component =
+ target->delegate()->GetNonClientComponent(event->location());
+ if ((event->flags() &
+ (ui::EF_IS_DOUBLE_CLICK | ui::EF_IS_TRIPLE_CLICK)) == 0 &&
+ WindowResizer::GetBoundsChangeForWindowComponent(component)) {
+ gfx::Point location_in_parent(
+ ConvertPointToParent(target, event->location()));
+ CreateScopedWindowResizer(target, location_in_parent, component,
+ aura::client::WINDOW_MOVE_SOURCE_MOUSE);
+ } else {
+ window_resizer_.reset();
+ }
+ if (WindowResizer::GetBoundsChangeForWindowComponent(component) != 0)
+ event->StopPropagation();
+}
+
+void ToplevelWindowEventHandler::HandleMouseReleased(
+ aura::Window* target,
+ ui::MouseEvent* event) {
+ if (event->phase() != ui::EP_PRETARGET)
+ return;
+
+ CompleteDrag(event->type() == ui::ET_MOUSE_RELEASED ?
+ DRAG_COMPLETE : DRAG_REVERT,
+ event->flags());
+ if (in_move_loop_) {
+ quit_closure_.Run();
+ in_move_loop_ = false;
+ }
+ // Completing the drag may result in hiding the window. If this happens
+ // return true so no other handlers/observers see the event. Otherwise
+ // they see the event on a hidden window.
+ if (event->type() == ui::ET_MOUSE_CAPTURE_CHANGED &&
+ !target->IsVisible()) {
+ event->StopPropagation();
+ }
+}
+
+void ToplevelWindowEventHandler::HandleDrag(
+ aura::Window* target,
+ ui::LocatedEvent* event) {
+ // This function only be triggered to move window
+ // by mouse drag or touch move event.
+ DCHECK(event->type() == ui::ET_MOUSE_DRAGGED ||
+ event->type() == ui::ET_TOUCH_MOVED ||
+ event->type() == ui::ET_GESTURE_SCROLL_UPDATE);
+
+ // Drag actions are performed pre-target handling to prevent spurious mouse
+ // moves from the move/size operation from being sent to the target.
+ if (event->phase() != ui::EP_PRETARGET)
+ return;
+
+ if (!window_resizer_)
+ return;
+ window_resizer_->resizer()->Drag(
+ ConvertPointToParent(target, event->location()), event->flags());
+ event->StopPropagation();
+}
+
+void ToplevelWindowEventHandler::HandleMouseMoved(
+ aura::Window* target,
+ ui::LocatedEvent* event) {
+ // Shadow effects are applied after target handling. Note that we don't
+ // respect ER_HANDLED here right now since we have not had a reason to allow
+ // the target to cancel shadow rendering.
+ if (event->phase() != ui::EP_POSTTARGET)
+ return;
+
+ // TODO(jamescook): Move the resize cursor update code into here from
+ // CompoundEventFilter?
+ internal::ResizeShadowController* controller =
+ Shell::GetInstance()->resize_shadow_controller();
+ if (controller) {
+ if (event->flags() & ui::EF_IS_NON_CLIENT) {
+ int component =
+ target->delegate()->GetNonClientComponent(event->location());
+ controller->ShowShadow(target, component);
+ } else {
+ controller->HideShadow(target);
+ }
+ }
+}
+
+void ToplevelWindowEventHandler::HandleMouseExited(
+ aura::Window* target,
+ ui::LocatedEvent* event) {
+ // Shadow effects are applied after target handling. Note that we don't
+ // respect ER_HANDLED here right now since we have not had a reason to allow
+ // the target to cancel shadow rendering.
+ if (event->phase() != ui::EP_POSTTARGET)
+ return;
+
+ internal::ResizeShadowController* controller =
+ Shell::GetInstance()->resize_shadow_controller();
+ if (controller)
+ controller->HideShadow(target);
+}
+
+void ToplevelWindowEventHandler::ResizerWindowDestroyed() {
+ // We explicitly don't invoke RevertDrag() since that may do things to window.
+ // Instead we destroy the resizer.
+ window_resizer_.reset();
+
+ // End the move loop. This does nothing if we're not in a move loop.
+ EndMoveLoop();
+}
+
+} // namespace ash
diff --git a/chromium/ash/wm/toplevel_window_event_handler.h b/chromium/ash/wm/toplevel_window_event_handler.h
new file mode 100644
index 00000000000..89ad46aa56d
--- /dev/null
+++ b/chromium/ash/wm/toplevel_window_event_handler.h
@@ -0,0 +1,123 @@
+// Copyright (c) 2012 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 ASH_WM_TOPLEVEL_WINDOW_EVENT_HANDLER_H_
+#define ASH_WM_TOPLEVEL_WINDOW_EVENT_HANDLER_H_
+
+#include <set>
+
+#include "ash/ash_export.h"
+#include "ash/display/display_controller.h"
+#include "base/callback.h"
+#include "base/compiler_specific.h"
+#include "base/memory/scoped_ptr.h"
+#include "ui/aura/client/window_move_client.h"
+#include "ui/base/events/event_handler.h"
+#include "ui/gfx/point.h"
+#include "ui/gfx/rect.h"
+
+namespace aura {
+class Window;
+}
+
+namespace ui {
+class LocatedEvent;
+}
+
+namespace ash {
+
+class WindowResizer;
+
+class ASH_EXPORT ToplevelWindowEventHandler
+ : public ui::EventHandler,
+ public aura::client::WindowMoveClient,
+ public DisplayController::Observer {
+ public:
+ explicit ToplevelWindowEventHandler(aura::Window* owner);
+ virtual ~ToplevelWindowEventHandler();
+
+ const aura::Window* owner() const { return owner_; }
+
+ // Overridden from ui::EventHandler:
+ virtual void OnKeyEvent(ui::KeyEvent* event) OVERRIDE;
+ virtual void OnMouseEvent(ui::MouseEvent* event) OVERRIDE;
+ virtual void OnGestureEvent(ui::GestureEvent* event) OVERRIDE;
+
+ // Overridden form aura::client::WindowMoveClient:
+ virtual aura::client::WindowMoveResult RunMoveLoop(
+ aura::Window* source,
+ const gfx::Vector2d& drag_offset,
+ aura::client::WindowMoveSource move_source) OVERRIDE;
+ virtual void EndMoveLoop() OVERRIDE;
+
+ // Overridden form ash::DisplayController::Observer:
+ virtual void OnDisplayConfigurationChanging() OVERRIDE;
+
+ private:
+ class ScopedWindowResizer;
+
+ enum DragCompletionStatus {
+ DRAG_COMPLETE,
+ DRAG_REVERT
+ };
+
+ void CreateScopedWindowResizer(aura::Window* window,
+ const gfx::Point& point_in_parent,
+ int window_component,
+ aura::client::WindowMoveSource source);
+
+ // Finishes the drag.
+ void CompleteDrag(DragCompletionStatus status, int event_flags);
+
+ void HandleMousePressed(aura::Window* target, ui::MouseEvent* event);
+ void HandleMouseReleased(aura::Window* target, ui::MouseEvent* event);
+
+ // Called during a drag to resize/position the window.
+ // The return value is returned by OnMouseEvent() above.
+ void HandleDrag(aura::Window* target, ui::LocatedEvent* event);
+
+ // Called during mouse moves to update window resize shadows.
+ // Return value is returned by OnMouseEvent() above.
+ void HandleMouseMoved(aura::Window* target, ui::LocatedEvent* event);
+
+ // Called for mouse exits to hide window resize shadows.
+ // Return value is returned by OnMouseEvent() above.
+ void HandleMouseExited(aura::Window* target, ui::LocatedEvent* event);
+
+ // Invoked from ScopedWindowResizer if the window is destroyed.
+ void ResizerWindowDestroyed();
+
+ // The container which this event handler is handling events on.
+ aura::Window* owner_;
+
+ // Are we running a nested message loop from RunMoveLoop().
+ bool in_move_loop_;
+
+ // Was the move operation cancelled? Used only when the nested loop
+ // is used to move a window.
+ bool move_cancelled_;
+
+ // Is a window move/resize in progress because of gesture events?
+ bool in_gesture_drag_;
+
+ // The window bounds before it started the drag.
+ // When a window is moved using a touch gesture, and it is swiped up/down
+ // maximize/minimize, the restore bounds should be set to the bounds of the
+ // window when the drag started.
+ gfx::Rect pre_drag_window_bounds_;
+
+ scoped_ptr<ScopedWindowResizer> window_resizer_;
+
+ base::Closure quit_closure_;
+
+ // Used to track if this object is deleted while running a nested message
+ // loop. If non-null the destructor sets this to true.
+ bool* destroyed_;
+
+ DISALLOW_COPY_AND_ASSIGN(ToplevelWindowEventHandler);
+};
+
+} // namespace aura
+
+#endif // ASH_WM_TOPLEVEL_WINDOW_EVENT_HANDLER_H_
diff --git a/chromium/ash/wm/toplevel_window_event_handler_unittest.cc b/chromium/ash/wm/toplevel_window_event_handler_unittest.cc
new file mode 100644
index 00000000000..c84f8b3f122
--- /dev/null
+++ b/chromium/ash/wm/toplevel_window_event_handler_unittest.cc
@@ -0,0 +1,816 @@
+// Copyright (c) 2012 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.
+
+#include "ash/wm/toplevel_window_event_handler.h"
+
+#include "ash/ash_constants.h"
+#include "ash/root_window_controller.h"
+#include "ash/shell.h"
+#include "ash/shell_window_ids.h"
+#include "ash/test/ash_test_base.h"
+#include "ash/wm/lock_state_controller_impl2.h"
+#include "ash/wm/property_util.h"
+#include "ash/wm/resize_shadow.h"
+#include "ash/wm/resize_shadow_controller.h"
+#include "ash/wm/window_util.h"
+#include "ash/wm/workspace/snap_sizer.h"
+#include "ash/wm/workspace_controller.h"
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/aura/client/aura_constants.h"
+#include "ui/aura/root_window.h"
+#include "ui/aura/test/aura_test_base.h"
+#include "ui/aura/test/event_generator.h"
+#include "ui/aura/test/test_activation_client.h"
+#include "ui/aura/test/test_window_delegate.h"
+#include "ui/base/events/event.h"
+#include "ui/base/hit_test.h"
+#include "ui/gfx/screen.h"
+
+#if defined(OS_WIN)
+// Windows headers define macros for these function names which screw with us.
+#if defined(CreateWindow)
+#undef CreateWindow
+#endif
+#endif
+
+namespace ash {
+namespace test {
+
+namespace {
+
+// A simple window delegate that returns the specified hit-test code when
+// requested and applies a minimum size constraint if there is one.
+class TestWindowDelegate : public aura::test::TestWindowDelegate {
+ public:
+ explicit TestWindowDelegate(int hittest_code) {
+ set_window_component(hittest_code);
+ }
+ virtual ~TestWindowDelegate() {}
+
+ private:
+ // Overridden from aura::Test::TestWindowDelegate:
+ virtual void OnWindowDestroyed() OVERRIDE {
+ delete this;
+ }
+
+ DISALLOW_COPY_AND_ASSIGN(TestWindowDelegate);
+};
+
+class ToplevelWindowEventHandlerTest : public AshTestBase {
+ public:
+ ToplevelWindowEventHandlerTest() {}
+ virtual ~ToplevelWindowEventHandlerTest() {}
+
+ protected:
+ aura::Window* CreateWindow(int hittest_code) {
+ TestWindowDelegate* d1 = new TestWindowDelegate(hittest_code);
+ aura::Window* w1 = new aura::Window(d1);
+ w1->SetType(aura::client::WINDOW_TYPE_NORMAL);
+ w1->set_id(1);
+ w1->Init(ui::LAYER_TEXTURED);
+ aura::Window* parent =
+ Shell::GetContainer(Shell::GetPrimaryRootWindow(),
+ internal::kShellWindowId_AlwaysOnTopContainer);
+ parent->AddChild(w1);
+ w1->SetBounds(gfx::Rect(0, 0, 100, 100));
+ w1->Show();
+ return w1;
+ }
+
+ void DragFromCenterBy(aura::Window* window, int dx, int dy) {
+ aura::test::EventGenerator generator(Shell::GetPrimaryRootWindow(), window);
+ generator.DragMouseBy(dx, dy);
+ }
+
+ void TouchDragFromCenterBy(aura::Window* window, int dx, int dy) {
+ aura::test::EventGenerator generator(Shell::GetPrimaryRootWindow(), window);
+ generator.PressMoveAndReleaseTouchBy(dx, dy);
+ }
+
+ scoped_ptr<ToplevelWindowEventHandler> handler_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ToplevelWindowEventHandlerTest);
+};
+
+}
+
+TEST_F(ToplevelWindowEventHandlerTest, Caption) {
+ scoped_ptr<aura::Window> w1(CreateWindow(HTCAPTION));
+ gfx::Size size = w1->bounds().size();
+ DragFromCenterBy(w1.get(), 100, 100);
+ // Position should have been offset by 100,100.
+ EXPECT_EQ("100,100", w1->bounds().origin().ToString());
+ // Size should not have.
+ EXPECT_EQ(size.ToString(), w1->bounds().size().ToString());
+
+ TouchDragFromCenterBy(w1.get(), 100, 100);
+ // Position should have been offset by 100,100.
+ EXPECT_EQ("200,200", w1->bounds().origin().ToString());
+ // Size should not have.
+ EXPECT_EQ(size.ToString(), w1->bounds().size().ToString());
+}
+
+TEST_F(ToplevelWindowEventHandlerTest, BottomRight) {
+ scoped_ptr<aura::Window> w1(CreateWindow(HTBOTTOMRIGHT));
+ gfx::Point position = w1->bounds().origin();
+ DragFromCenterBy(w1.get(), 100, 100);
+ // Position should not have changed.
+ EXPECT_EQ(position, w1->bounds().origin());
+ // Size should have increased by 100,100.
+ EXPECT_EQ(gfx::Size(200, 200), w1->bounds().size());
+}
+
+TEST_F(ToplevelWindowEventHandlerTest, GrowBox) {
+ scoped_ptr<aura::Window> w1(CreateWindow(HTGROWBOX));
+ TestWindowDelegate* window_delegate =
+ static_cast<TestWindowDelegate*>(w1->delegate());
+ window_delegate->set_minimum_size(gfx::Size(40, 40));
+
+ gfx::Point position = w1->bounds().origin();
+ aura::test::EventGenerator generator(Shell::GetPrimaryRootWindow());
+ generator.MoveMouseToCenterOf(w1.get());
+ generator.DragMouseBy(100, 100);
+ // Position should not have changed.
+ EXPECT_EQ(position, w1->bounds().origin());
+ // Size should have increased by 100,100.
+ EXPECT_EQ(gfx::Size(200, 200), w1->bounds().size());
+
+ // Shrink the wnidow by (-100, -100).
+ generator.DragMouseBy(-100, -100);
+ // Position should not have changed.
+ EXPECT_EQ(position, w1->bounds().origin());
+ // Size should have decreased by 100,100.
+ EXPECT_EQ(gfx::Size(100, 100), w1->bounds().size());
+
+ // Enforce minimum size.
+ generator.DragMouseBy(-60, -60);
+ EXPECT_EQ(position, w1->bounds().origin());
+ EXPECT_EQ(gfx::Size(40, 40), w1->bounds().size());
+}
+
+TEST_F(ToplevelWindowEventHandlerTest, Right) {
+ scoped_ptr<aura::Window> w1(CreateWindow(HTRIGHT));
+ gfx::Point position = w1->bounds().origin();
+ DragFromCenterBy(w1.get(), 100, 100);
+ // Position should not have changed.
+ EXPECT_EQ(position, w1->bounds().origin());
+ // Size should have increased by 100,0.
+ EXPECT_EQ(gfx::Size(200, 100), w1->bounds().size());
+}
+
+TEST_F(ToplevelWindowEventHandlerTest, Bottom) {
+ scoped_ptr<aura::Window> w1(CreateWindow(HTBOTTOM));
+ gfx::Point position = w1->bounds().origin();
+ DragFromCenterBy(w1.get(), 100, 100);
+ // Position should not have changed.
+ EXPECT_EQ(position, w1->bounds().origin());
+ // Size should have increased by 0,100.
+ EXPECT_EQ(gfx::Size(100, 200), w1->bounds().size());
+}
+
+TEST_F(ToplevelWindowEventHandlerTest, TopRight) {
+ scoped_ptr<aura::Window> w1(CreateWindow(HTTOPRIGHT));
+ DragFromCenterBy(w1.get(), -50, 50);
+ // Position should have been offset by 0,50.
+ EXPECT_EQ(gfx::Point(0, 50), w1->bounds().origin());
+ // Size should have decreased by 50,50.
+ EXPECT_EQ(gfx::Size(50, 50), w1->bounds().size());
+}
+
+TEST_F(ToplevelWindowEventHandlerTest, Top) {
+ scoped_ptr<aura::Window> w1(CreateWindow(HTTOP));
+ DragFromCenterBy(w1.get(), 50, 50);
+ // Position should have been offset by 0,50.
+ EXPECT_EQ(gfx::Point(0, 50), w1->bounds().origin());
+ // Size should have decreased by 0,50.
+ EXPECT_EQ(gfx::Size(100, 50), w1->bounds().size());
+}
+
+TEST_F(ToplevelWindowEventHandlerTest, Left) {
+ scoped_ptr<aura::Window> w1(CreateWindow(HTLEFT));
+ DragFromCenterBy(w1.get(), 50, 50);
+ // Position should have been offset by 50,0.
+ EXPECT_EQ(gfx::Point(50, 0), w1->bounds().origin());
+ // Size should have decreased by 50,0.
+ EXPECT_EQ(gfx::Size(50, 100), w1->bounds().size());
+}
+
+TEST_F(ToplevelWindowEventHandlerTest, BottomLeft) {
+ scoped_ptr<aura::Window> w1(CreateWindow(HTBOTTOMLEFT));
+ DragFromCenterBy(w1.get(), 50, -50);
+ // Position should have been offset by 50,0.
+ EXPECT_EQ(gfx::Point(50, 0), w1->bounds().origin());
+ // Size should have decreased by 50,50.
+ EXPECT_EQ(gfx::Size(50, 50), w1->bounds().size());
+}
+
+TEST_F(ToplevelWindowEventHandlerTest, TopLeft) {
+ scoped_ptr<aura::Window> w1(CreateWindow(HTTOPLEFT));
+ DragFromCenterBy(w1.get(), 50, 50);
+ // Position should have been offset by 50,50.
+ EXPECT_EQ(gfx::Point(50, 50), w1->bounds().origin());
+ // Size should have decreased by 50,50.
+ EXPECT_EQ(gfx::Size(50, 50), w1->bounds().size());
+}
+
+TEST_F(ToplevelWindowEventHandlerTest, Client) {
+ scoped_ptr<aura::Window> w1(CreateWindow(HTCLIENT));
+ gfx::Rect bounds = w1->bounds();
+ DragFromCenterBy(w1.get(), 100, 100);
+ // Neither position nor size should have changed.
+ EXPECT_EQ(bounds, w1->bounds());
+}
+
+TEST_F(ToplevelWindowEventHandlerTest, LeftPastMinimum) {
+ scoped_ptr<aura::Window> w1(CreateWindow(HTLEFT));
+ TestWindowDelegate* window_delegate =
+ static_cast<TestWindowDelegate*>(w1->delegate());
+ window_delegate->set_minimum_size(gfx::Size(40, 40));
+
+ // Simulate a large left-to-right drag. Window width should be clamped to
+ // minimum and position change should be limited as well.
+ DragFromCenterBy(w1.get(), 333, 0);
+ EXPECT_EQ(gfx::Point(60, 0), w1->bounds().origin());
+ EXPECT_EQ(gfx::Size(40, 100), w1->bounds().size());
+}
+
+TEST_F(ToplevelWindowEventHandlerTest, RightPastMinimum) {
+ scoped_ptr<aura::Window> w1(CreateWindow(HTRIGHT));
+ TestWindowDelegate* window_delegate =
+ static_cast<TestWindowDelegate*>(w1->delegate());
+ window_delegate->set_minimum_size(gfx::Size(40, 40));
+ gfx::Point position = w1->bounds().origin();
+
+ // Simulate a large right-to-left drag. Window width should be clamped to
+ // minimum and position should not change.
+ DragFromCenterBy(w1.get(), -333, 0);
+ EXPECT_EQ(position, w1->bounds().origin());
+ EXPECT_EQ(gfx::Size(40, 100), w1->bounds().size());
+}
+
+TEST_F(ToplevelWindowEventHandlerTest, TopLeftPastMinimum) {
+ scoped_ptr<aura::Window> w1(CreateWindow(HTTOPLEFT));
+ TestWindowDelegate* window_delegate =
+ static_cast<TestWindowDelegate*>(w1->delegate());
+ window_delegate->set_minimum_size(gfx::Size(40, 40));
+
+ // Simulate a large top-left to bottom-right drag. Window width should be
+ // clamped to minimum and position should be limited.
+ DragFromCenterBy(w1.get(), 333, 444);
+ EXPECT_EQ(gfx::Point(60, 60), w1->bounds().origin());
+ EXPECT_EQ(gfx::Size(40, 40), w1->bounds().size());
+}
+
+TEST_F(ToplevelWindowEventHandlerTest, TopRightPastMinimum) {
+ scoped_ptr<aura::Window> w1(CreateWindow(HTTOPRIGHT));
+ TestWindowDelegate* window_delegate =
+ static_cast<TestWindowDelegate*>(w1->delegate());
+ window_delegate->set_minimum_size(gfx::Size(40, 40));
+
+ // Simulate a large top-right to bottom-left drag. Window size should be
+ // clamped to minimum, x position should not change, and y position should
+ // be clamped.
+ DragFromCenterBy(w1.get(), -333, 444);
+ EXPECT_EQ(gfx::Point(0, 60), w1->bounds().origin());
+ EXPECT_EQ(gfx::Size(40, 40), w1->bounds().size());
+}
+
+TEST_F(ToplevelWindowEventHandlerTest, BottomLeftPastMinimum) {
+ scoped_ptr<aura::Window> w1(CreateWindow(HTBOTTOMLEFT));
+ TestWindowDelegate* window_delegate =
+ static_cast<TestWindowDelegate*>(w1->delegate());
+ window_delegate->set_minimum_size(gfx::Size(40, 40));
+
+ // Simulate a large bottom-left to top-right drag. Window size should be
+ // clamped to minimum, x position should be clamped, and y position should
+ // not change.
+ DragFromCenterBy(w1.get(), 333, -444);
+ EXPECT_EQ(gfx::Point(60, 0), w1->bounds().origin());
+ EXPECT_EQ(gfx::Size(40, 40), w1->bounds().size());
+}
+
+TEST_F(ToplevelWindowEventHandlerTest, BottomRightPastMinimum) {
+ scoped_ptr<aura::Window> w1(CreateWindow(HTBOTTOMRIGHT));
+ TestWindowDelegate* window_delegate =
+ static_cast<TestWindowDelegate*>(w1->delegate());
+ window_delegate->set_minimum_size(gfx::Size(40, 40));
+ gfx::Point position = w1->bounds().origin();
+
+ // Simulate a large bottom-right to top-left drag. Window size should be
+ // clamped to minimum and position should not change.
+ DragFromCenterBy(w1.get(), -333, -444);
+ EXPECT_EQ(position, w1->bounds().origin());
+ EXPECT_EQ(gfx::Size(40, 40), w1->bounds().size());
+}
+
+TEST_F(ToplevelWindowEventHandlerTest, BottomRightWorkArea) {
+ scoped_ptr<aura::Window> target(CreateWindow(HTBOTTOMRIGHT));
+ gfx::Rect work_area = Shell::GetScreen()->GetDisplayNearestWindow(
+ target.get()).work_area();
+ gfx::Point position = target->bounds().origin();
+ // Drag further than work_area bottom.
+ DragFromCenterBy(target.get(), 100, work_area.height());
+ // Position should not have changed.
+ EXPECT_EQ(position, target->bounds().origin());
+ // Size should have increased by 100, work_area.height() - target->bounds.y()
+ EXPECT_EQ(gfx::Size(200, work_area.height() - target->bounds().y()),
+ target->bounds().size());
+}
+
+TEST_F(ToplevelWindowEventHandlerTest, BottomLeftWorkArea) {
+ scoped_ptr<aura::Window> target(CreateWindow(HTBOTTOMLEFT));
+ gfx::Rect work_area = Shell::GetScreen()->GetDisplayNearestWindow(
+ target.get()).work_area();
+ gfx::Point position = target->bounds().origin();
+ // Drag further than work_area bottom.
+ DragFromCenterBy(target.get(), -30, work_area.height());
+ // origin is now at 70, 100.
+ EXPECT_EQ(position.x() - 30, target->bounds().x());
+ EXPECT_EQ(position.y(), target->bounds().y());
+ // Size should have increased by 30, work_area.height() - target->bounds.y()
+ EXPECT_EQ(gfx::Size(130, work_area.height() - target->bounds().y()),
+ target->bounds().size());
+}
+
+TEST_F(ToplevelWindowEventHandlerTest, BottomWorkArea) {
+ scoped_ptr<aura::Window> target(CreateWindow(HTBOTTOM));
+ gfx::Rect work_area = Shell::GetScreen()->GetDisplayNearestWindow(
+ target.get()).work_area();
+ gfx::Point position = target->bounds().origin();
+ // Drag further than work_area bottom.
+ DragFromCenterBy(target.get(), 0, work_area.height());
+ // Position should not have changed.
+ EXPECT_EQ(position, target->bounds().origin());
+ // Size should have increased by 0, work_area.height() - target->bounds.y()
+ EXPECT_EQ(gfx::Size(100, work_area.height() - target->bounds().y()),
+ target->bounds().size());
+}
+
+// Verifies we don't let windows drag to a -y location.
+TEST_F(ToplevelWindowEventHandlerTest, DontDragToNegativeY) {
+ scoped_ptr<aura::Window> target(CreateWindow(HTTOP));
+ aura::test::EventGenerator generator(Shell::GetPrimaryRootWindow(),
+ target.get());
+ generator.MoveMouseTo(0, 5);
+ generator.DragMouseBy(0, -5);
+ // The y location and height should not have changed.
+ EXPECT_EQ(0, target->bounds().y());
+ EXPECT_EQ(100, target->bounds().height());
+}
+
+// Verifies we don't let windows go bigger than the display width.
+TEST_F(ToplevelWindowEventHandlerTest, DontGotWiderThanScreen) {
+ scoped_ptr<aura::Window> target(CreateWindow(HTRIGHT));
+ gfx::Rect work_area = Shell::GetScreen()->GetDisplayNearestWindow(
+ target.get()).bounds();
+ DragFromCenterBy(target.get(), work_area.width() * 2, 0);
+ // The y location and height should not have changed.
+ EXPECT_EQ(work_area.width(), target->bounds().width());
+}
+
+// Verifies that touch-gestures drag the window correctly.
+TEST_F(ToplevelWindowEventHandlerTest, GestureDrag) {
+ scoped_ptr<aura::Window> target(
+ CreateTestWindowInShellWithDelegate(
+ new TestWindowDelegate(HTCAPTION),
+ 0,
+ gfx::Rect(0, 0, 100, 100)));
+ aura::test::EventGenerator generator(Shell::GetPrimaryRootWindow(),
+ target.get());
+ gfx::Rect old_bounds = target->bounds();
+ gfx::Point location(5, 5);
+ target->SetProperty(aura::client::kCanMaximizeKey, true);
+
+ gfx::Point end = location;
+
+ // Snap right;
+ {
+ // Get the expected snapped bounds before snapping.
+ internal::SnapSizer sizer(target.get(), location,
+ internal::SnapSizer::RIGHT_EDGE,
+ internal::SnapSizer::OTHER_INPUT);
+ gfx::Rect snapped_bounds = sizer.GetSnapBounds(target->bounds());
+
+ end.Offset(100, 0);
+ generator.GestureScrollSequence(location, end,
+ base::TimeDelta::FromMilliseconds(5),
+ 10);
+ RunAllPendingInMessageLoop();
+
+ // Verify that the window has moved after the gesture.
+ EXPECT_NE(old_bounds.ToString(), target->bounds().ToString());
+ EXPECT_EQ(snapped_bounds.ToString(), target->bounds().ToString());
+ }
+
+ old_bounds = target->bounds();
+
+ // Snap left.
+ {
+ // Get the expected snapped bounds before snapping.
+ internal::SnapSizer sizer(target.get(), location,
+ internal::SnapSizer::LEFT_EDGE,
+ internal::SnapSizer::OTHER_INPUT);
+ gfx::Rect snapped_bounds = sizer.GetSnapBounds(target->bounds());
+ end = location = target->GetBoundsInRootWindow().CenterPoint();
+ end.Offset(-100, 0);
+ generator.GestureScrollSequence(location, end,
+ base::TimeDelta::FromMilliseconds(5),
+ 10);
+ RunAllPendingInMessageLoop();
+
+ EXPECT_NE(old_bounds.ToString(), target->bounds().ToString());
+ EXPECT_EQ(snapped_bounds.ToString(), target->bounds().ToString());
+ }
+
+ gfx::Rect bounds_before_maximization = target->bounds();
+ bounds_before_maximization.Offset(0, 100);
+ target->SetBounds(bounds_before_maximization);
+ old_bounds = target->bounds();
+
+ // Maximize.
+ end = location = target->GetBoundsInRootWindow().CenterPoint();
+ end.Offset(0, -100);
+ generator.GestureScrollSequence(location, end,
+ base::TimeDelta::FromMilliseconds(5),
+ 10);
+ RunAllPendingInMessageLoop();
+ EXPECT_NE(old_bounds.ToString(), target->bounds().ToString());
+ EXPECT_TRUE(wm::IsWindowMaximized(target.get()));
+ EXPECT_EQ(old_bounds.ToString(),
+ GetRestoreBoundsInScreen(target.get())->ToString());
+
+ wm::RestoreWindow(target.get());
+ target->SetBounds(old_bounds);
+
+ // Minimize.
+ end = location = target->GetBoundsInRootWindow().CenterPoint();
+ end.Offset(0, 100);
+ generator.GestureScrollSequence(location, end,
+ base::TimeDelta::FromMilliseconds(5),
+ 10);
+ RunAllPendingInMessageLoop();
+ EXPECT_NE(old_bounds.ToString(), target->bounds().ToString());
+ EXPECT_TRUE(wm::IsWindowMinimized(target.get()));
+ EXPECT_TRUE(GetWindowAlwaysRestoresToRestoreBounds(target.get()));
+ EXPECT_EQ(old_bounds.ToString(),
+ GetRestoreBoundsInScreen(target.get())->ToString());
+}
+
+// Tests that a gesture cannot minimize a window in login/lock screen.
+TEST_F(ToplevelWindowEventHandlerTest, GestureDragMinimizeLoginScreen) {
+ LockStateControllerImpl2* state_controller =
+ static_cast<LockStateControllerImpl2*>
+ (Shell::GetInstance()->lock_state_controller());
+ state_controller->OnLoginStateChanged(user::LOGGED_IN_NONE);
+ state_controller->OnLockStateChanged(false);
+ SetUserLoggedIn(false);
+
+ scoped_ptr<aura::Window> target(CreateWindow(HTCAPTION));
+ aura::Window* lock = internal::RootWindowController::ForWindow(target.get())->
+ GetContainer(internal::kShellWindowId_LockSystemModalContainer);
+ lock->AddChild(target.get());
+ aura::test::EventGenerator generator(Shell::GetPrimaryRootWindow(),
+ target.get());
+ gfx::Rect old_bounds = target->bounds();
+ gfx::Point location(5, 5);
+ target->SetProperty(aura::client::kCanMaximizeKey, true);
+
+ gfx::Point end = location;
+ end.Offset(0, 100);
+ generator.GestureScrollSequence(location, end,
+ base::TimeDelta::FromMilliseconds(5),
+ 10);
+ RunAllPendingInMessageLoop();
+ EXPECT_FALSE(wm::IsWindowMinimized(target.get()));
+}
+
+TEST_F(ToplevelWindowEventHandlerTest, GestureDragToRestore) {
+ scoped_ptr<aura::Window> window(
+ CreateTestWindowInShellWithDelegate(
+ new TestWindowDelegate(HTCAPTION),
+ 0,
+ gfx::Rect(10, 20, 30, 40)));
+ window->Show();
+ ash::wm::ActivateWindow(window.get());
+
+ aura::test::EventGenerator generator(Shell::GetPrimaryRootWindow(),
+ window.get());
+ gfx::Rect old_bounds = window->bounds();
+ gfx::Point location, end;
+ end = location = window->GetBoundsInRootWindow().CenterPoint();
+ end.Offset(0, 100);
+ generator.GestureScrollSequence(location, end,
+ base::TimeDelta::FromMilliseconds(5),
+ 10);
+ RunAllPendingInMessageLoop();
+ EXPECT_NE(old_bounds.ToString(), window->bounds().ToString());
+ EXPECT_TRUE(wm::IsWindowMinimized(window.get()));
+ EXPECT_TRUE(GetWindowAlwaysRestoresToRestoreBounds(window.get()));
+ EXPECT_EQ(old_bounds.ToString(),
+ GetRestoreBoundsInScreen(window.get())->ToString());
+}
+
+// Tests that an unresizable window cannot be dragged or snapped using gestures.
+TEST_F(ToplevelWindowEventHandlerTest, GestureDragForUnresizableWindow) {
+ scoped_ptr<aura::Window> target(CreateWindow(HTCAPTION));
+
+ aura::test::EventGenerator generator(Shell::GetPrimaryRootWindow(),
+ target.get());
+ gfx::Rect old_bounds = target->bounds();
+ gfx::Point location(5, 5);
+
+ target->SetProperty(aura::client::kCanResizeKey, false);
+
+ gfx::Point end = location;
+
+ // Try to snap right. The window is not resizable. So it should not snap.
+ {
+ // Get the expected snapped bounds before the gesture.
+ internal::SnapSizer sizer(target.get(), location,
+ internal::SnapSizer::RIGHT_EDGE,
+ internal::SnapSizer::OTHER_INPUT);
+ gfx::Rect snapped_bounds = sizer.GetSnapBounds(target->bounds());
+
+ end.Offset(100, 0);
+ generator.GestureScrollSequence(location, end,
+ base::TimeDelta::FromMilliseconds(5),
+ 10);
+ RunAllPendingInMessageLoop();
+
+ // Verify that the window has moved after the gesture.
+ gfx::Rect expected_bounds(old_bounds);
+ expected_bounds.Offset(gfx::Vector2d(100, 0));
+ EXPECT_EQ(expected_bounds.ToString(), target->bounds().ToString());
+
+ // Verify that the window did not snap left.
+ EXPECT_NE(snapped_bounds.ToString(), target->bounds().ToString());
+ }
+
+ old_bounds = target->bounds();
+
+ // Try to snap left. It should not snap.
+ {
+ // Get the expected snapped bounds before the gesture.
+ internal::SnapSizer sizer(target.get(), location,
+ internal::SnapSizer::LEFT_EDGE,
+ internal::SnapSizer::OTHER_INPUT);
+ gfx::Rect snapped_bounds = sizer.GetSnapBounds(target->bounds());
+ end = location = target->GetBoundsInRootWindow().CenterPoint();
+ end.Offset(-100, 0);
+ generator.GestureScrollSequence(location, end,
+ base::TimeDelta::FromMilliseconds(5),
+ 10);
+ RunAllPendingInMessageLoop();
+
+ // Verify that the window has moved after the gesture.
+ gfx::Rect expected_bounds(old_bounds);
+ expected_bounds.Offset(gfx::Vector2d(-100, 0));
+ EXPECT_EQ(expected_bounds.ToString(), target->bounds().ToString());
+
+ // Verify that the window did not snap left.
+ EXPECT_NE(snapped_bounds.ToString(), target->bounds().ToString());
+ }
+}
+
+// Tests that dragging multiple windows at the same time is not allowed.
+TEST_F(ToplevelWindowEventHandlerTest, GestureDragMultipleWindows) {
+ scoped_ptr<aura::Window> target(
+ CreateTestWindowInShellWithDelegate(
+ new TestWindowDelegate(HTCAPTION),
+ 0,
+ gfx::Rect(0, 0, 100, 100)));
+ scoped_ptr<aura::Window> notmoved(
+ CreateTestWindowInShellWithDelegate(
+ new TestWindowDelegate(HTCAPTION),
+ 1, gfx::Rect(100, 0, 100, 100)));
+
+ aura::test::EventGenerator generator(Shell::GetPrimaryRootWindow(),
+ target.get());
+ gfx::Rect old_bounds = target->bounds();
+ gfx::Point location(5, 5);
+ target->SetProperty(aura::client::kCanMaximizeKey, true);
+
+ // Send some touch events to start dragging |target|.
+ generator.MoveTouch(location);
+ generator.PressTouch();
+ location.Offset(40, 5);
+ generator.MoveTouch(location);
+
+ // Try to drag |notmoved| window. This should not move the window.
+ {
+ gfx::Rect bounds = notmoved->bounds();
+ aura::test::EventGenerator gen(Shell::GetPrimaryRootWindow(),
+ notmoved.get());
+ gfx::Point start = notmoved->bounds().origin() + gfx::Vector2d(10, 10);
+ gfx::Point end = start + gfx::Vector2d(100, 10);
+ gen.GestureScrollSequence(start, end,
+ base::TimeDelta::FromMilliseconds(10),
+ 10);
+ EXPECT_EQ(bounds.ToString(), notmoved->bounds().ToString());
+ }
+}
+
+// Verifies pressing escape resets the bounds to the original bounds.
+// Disabled crbug.com/166219.
+#if defined(OS_MACOSX) || defined(OS_WIN)
+#define MAYBE_EscapeReverts DISABLED_EscapeReverts
+#else
+#define MAYBE_EscapeReverts EscapeReverts
+#endif
+TEST_F(ToplevelWindowEventHandlerTest, MAYBE_EscapeReverts) {
+ scoped_ptr<aura::Window> target(CreateWindow(HTBOTTOMRIGHT));
+ aura::test::EventGenerator generator(Shell::GetPrimaryRootWindow(),
+ target.get());
+ generator.PressLeftButton();
+ generator.MoveMouseBy(10, 11);
+
+ // Execute any scheduled draws so that pending mouse events are processed.
+ RunAllPendingInMessageLoop();
+
+ EXPECT_EQ("0,0 110x111", target->bounds().ToString());
+ generator.PressKey(ui::VKEY_ESCAPE, 0);
+ generator.ReleaseKey(ui::VKEY_ESCAPE, 0);
+ EXPECT_EQ("0,0 100x100", target->bounds().ToString());
+}
+
+// Verifies window minimization/maximization completes drag.
+// Disabled crbug.com/166219.
+#if defined(OS_WIN)
+#define MAYBE_MinimizeMaximizeCompletes DISABLED_MinimizeMaximizeCompletes
+#else
+#define MAYBE_MinimizeMaximizeCompletes MinimizeMaximizeCompletes
+#endif
+TEST_F(ToplevelWindowEventHandlerTest, MAYBE_MinimizeMaximizeCompletes) {
+ // Once window is minimized, window dragging completes.
+ {
+ scoped_ptr<aura::Window> target(CreateWindow(HTCAPTION));
+ target->Focus();
+ aura::test::EventGenerator generator(Shell::GetPrimaryRootWindow(),
+ target.get());
+ generator.PressLeftButton();
+ generator.MoveMouseBy(10, 11);
+ RunAllPendingInMessageLoop();
+ EXPECT_EQ("10,11 100x100", target->bounds().ToString());
+
+ wm::MinimizeWindow(target.get());
+ wm::RestoreWindow(target.get());
+
+ generator.PressLeftButton();
+ generator.MoveMouseBy(10, 11);
+ RunAllPendingInMessageLoop();
+ EXPECT_EQ("10,11 100x100", target->bounds().ToString());
+ }
+
+ // Once window is maximized, window dragging completes.
+ {
+ scoped_ptr<aura::Window> target(CreateWindow(HTCAPTION));
+ target->Focus();
+ aura::test::EventGenerator generator(Shell::GetPrimaryRootWindow(),
+ target.get());
+ generator.PressLeftButton();
+ generator.MoveMouseBy(10, 11);
+ RunAllPendingInMessageLoop();
+ EXPECT_EQ("10,11 100x100", target->bounds().ToString());
+
+ wm::MaximizeWindow(target.get());
+ wm::RestoreWindow(target.get());
+
+ generator.PressLeftButton();
+ generator.MoveMouseBy(10, 11);
+ RunAllPendingInMessageLoop();
+ EXPECT_EQ("10,11 100x100", target->bounds().ToString());
+ }
+}
+
+// Test class for mouse and touch resize shadow tests.
+class ToplevelWindowEventHandlerResizeTest
+ : public ToplevelWindowEventHandlerTest {
+ public:
+ ToplevelWindowEventHandlerResizeTest() : delegate_(NULL) {}
+ virtual ~ToplevelWindowEventHandlerResizeTest() {}
+
+ virtual void SetUp() OVERRIDE {
+ ToplevelWindowEventHandlerTest::SetUp();
+
+ delegate_ = new TestWindowDelegate(HTNOWHERE);
+ target_.reset(CreateTestWindowInShellWithDelegate(
+ delegate_, 0, gfx::Rect(0, 0, 100, 100)));
+
+ gfx::Insets mouse_insets = gfx::Insets(-ash::kResizeOutsideBoundsSize,
+ -ash::kResizeOutsideBoundsSize,
+ -ash::kResizeOutsideBoundsSize,
+ -ash::kResizeOutsideBoundsSize);
+ gfx::Insets touch_insets =
+ mouse_insets.Scale(ash::kResizeOutsideBoundsScaleForTouch);
+ target_->SetHitTestBoundsOverrideOuter(mouse_insets, touch_insets);
+ target_->set_hit_test_bounds_override_inner(mouse_insets);
+ }
+
+ virtual void TearDown() OVERRIDE {
+ target_.reset();
+ ToplevelWindowEventHandlerTest::TearDown();
+ }
+
+ // Called on each scroll event. Checks if the correct resize shadow is shown.
+ void ProcessEvent(ui::EventType type, const gfx::Vector2dF& delta) {
+ if (type == ui::ET_GESTURE_SCROLL_END) {
+ // After gesture scroll ends, there should be no resize shadow.
+ EXPECT_FALSE(HasResizeShadow());
+ } else {
+ // Check if there is a resize shadow under the correct border.
+ ASSERT_TRUE(HasResizeShadow());
+ EXPECT_EQ(HTBOTTOMRIGHT, ResizeShadowLastHitTest());
+ }
+ }
+
+ protected:
+ void SetHittestCode(int hittest_code) {
+ delegate_->set_window_component(hittest_code);
+ }
+
+ aura::Window* target() { return target_.get(); }
+
+ bool HasResizeShadow() const {
+ // There is no shadow if no ResizeShadow object is associated with the
+ // window or there is one but its hit test is set to HTNOWHERE. Since we
+ // don't want to tie tests to that implementation detail, both cases are
+ // considered here.
+ return ResizeShadow() && ResizeShadowLastHitTest() != HTNOWHERE;
+ }
+
+ int ResizeShadowLastHitTest() const {
+ return ResizeShadow()->GetLastHitTestForTest();
+ }
+
+ private:
+ internal::ResizeShadow* ResizeShadow() const {
+ return Shell::GetInstance()->resize_shadow_controller()->
+ GetShadowForWindowForTest(target_.get());
+ }
+
+ TestWindowDelegate* delegate_;
+ scoped_ptr<aura::Window> target_;
+
+ DISALLOW_COPY_AND_ASSIGN(ToplevelWindowEventHandlerResizeTest);
+};
+
+// Tests resize shadows for touch resizing.
+TEST_F(ToplevelWindowEventHandlerResizeTest, TouchResizeShadows) {
+ aura::test::EventGenerator generator(Shell::GetPrimaryRootWindow(), target());
+
+ // Drag bottom right border of the window and check for the resize shadows.
+ // Shadows are checked in the callback function.
+ SetHittestCode(HTBOTTOMRIGHT);
+ generator.GestureScrollSequenceWithCallback(
+ gfx::Point(105, 105),
+ gfx::Point(150, 150),
+ base::TimeDelta::FromMilliseconds(100),
+ 3,
+ base::Bind(&ToplevelWindowEventHandlerResizeTest::ProcessEvent,
+ base::Unretained(this)));
+ RunAllPendingInMessageLoop();
+}
+
+// Tests resize shadows for mouse resizing.
+TEST_F(ToplevelWindowEventHandlerResizeTest, MouseResizeShadows) {
+ aura::test::EventGenerator generator(Shell::GetPrimaryRootWindow(), target());
+
+ // There should be no shadow at the beginning.
+ EXPECT_FALSE(HasResizeShadow());
+
+ // Move mouse over the right border. Shadows should appear.
+ SetHittestCode(HTRIGHT);
+ generator.MoveMouseTo(gfx::Point(100, 50));
+ ASSERT_TRUE(HasResizeShadow());
+ EXPECT_EQ(HTRIGHT, ResizeShadowLastHitTest());
+
+ // Move mouse over the bottom right border. Shadows should stay.
+ SetHittestCode(HTBOTTOMRIGHT);
+ generator.MoveMouseTo(100, 100);
+ ASSERT_TRUE(HasResizeShadow());
+ EXPECT_EQ(HTBOTTOMRIGHT, ResizeShadowLastHitTest());
+
+ // Move mouse into the window. Shadows should disappear.
+ SetHittestCode(HTCLIENT);
+ generator.MoveMouseTo(50, 50);
+ EXPECT_FALSE(HasResizeShadow());
+
+ // Move mouse over the bottom order. Shadows should reappear.
+ SetHittestCode(HTBOTTOM);
+ generator.MoveMouseTo(50, 100);
+ ASSERT_TRUE(HasResizeShadow());
+ EXPECT_EQ(HTBOTTOM, ResizeShadowLastHitTest());
+
+ // Move mouse out of the window. Shadows should disappear.
+ generator.MoveMouseTo(150, 150);
+ EXPECT_FALSE(HasResizeShadow());
+
+ RunAllPendingInMessageLoop();
+}
+
+} // namespace test
+} // namespace ash
diff --git a/chromium/ash/wm/user_activity_detector.cc b/chromium/ash/wm/user_activity_detector.cc
new file mode 100644
index 00000000000..269b9efe860
--- /dev/null
+++ b/chromium/ash/wm/user_activity_detector.cc
@@ -0,0 +1,84 @@
+// Copyright (c) 2012 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.
+
+#include "ash/wm/user_activity_detector.h"
+
+#include "ash/wm/property_util.h"
+#include "ash/wm/user_activity_observer.h"
+#include "ui/base/events/event.h"
+
+namespace ash {
+
+const int UserActivityDetector::kNotifyIntervalMs = 200;
+
+// Too low and mouse events generated at the tail end of reconfiguration
+// will be reported as user activity and turn the screen back on; too high
+// and we'll ignore legitimate activity.
+const int UserActivityDetector::kDisplayPowerChangeIgnoreMouseMs = 1000;
+
+UserActivityDetector::UserActivityDetector() {
+}
+
+UserActivityDetector::~UserActivityDetector() {
+}
+
+bool UserActivityDetector::HasObserver(UserActivityObserver* observer) const {
+ return observers_.HasObserver(observer);
+}
+
+void UserActivityDetector::AddObserver(UserActivityObserver* observer) {
+ observers_.AddObserver(observer);
+}
+
+void UserActivityDetector::RemoveObserver(UserActivityObserver* observer) {
+ observers_.RemoveObserver(observer);
+}
+
+void UserActivityDetector::OnDisplayPowerChanging() {
+ honor_mouse_events_time_ = GetCurrentTime() +
+ base::TimeDelta::FromMilliseconds(kDisplayPowerChangeIgnoreMouseMs);
+}
+
+void UserActivityDetector::OnKeyEvent(ui::KeyEvent* event) {
+ HandleActivity(event);
+}
+
+void UserActivityDetector::OnMouseEvent(ui::MouseEvent* event) {
+ if (event->flags() & ui::EF_IS_SYNTHESIZED)
+ return;
+ if (!honor_mouse_events_time_.is_null() &&
+ GetCurrentTime() < honor_mouse_events_time_)
+ return;
+
+ HandleActivity(event);
+}
+
+void UserActivityDetector::OnScrollEvent(ui::ScrollEvent* event) {
+ HandleActivity(event);
+}
+
+void UserActivityDetector::OnTouchEvent(ui::TouchEvent* event) {
+ HandleActivity(event);
+}
+
+void UserActivityDetector::OnGestureEvent(ui::GestureEvent* event) {
+ HandleActivity(event);
+}
+
+base::TimeTicks UserActivityDetector::GetCurrentTime() const {
+ return !now_for_test_.is_null() ? now_for_test_ : base::TimeTicks::Now();
+}
+
+void UserActivityDetector::HandleActivity(const ui::Event* event) {
+ base::TimeTicks now = GetCurrentTime();
+ last_activity_time_ = now;
+ if (last_observer_notification_time_.is_null() ||
+ (now - last_observer_notification_time_).InMillisecondsF() >=
+ kNotifyIntervalMs) {
+ FOR_EACH_OBSERVER(UserActivityObserver, observers_, OnUserActivity(event));
+ last_observer_notification_time_ = now;
+ }
+}
+
+} // namespace ash
diff --git a/chromium/ash/wm/user_activity_detector.h b/chromium/ash/wm/user_activity_detector.h
new file mode 100644
index 00000000000..ca01ec1c92a
--- /dev/null
+++ b/chromium/ash/wm/user_activity_detector.h
@@ -0,0 +1,81 @@
+// Copyright (c) 2012 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 ASH_WM_USER_ACTIVITY_DETECTOR_H_
+#define ASH_WM_USER_ACTIVITY_DETECTOR_H_
+
+#include "ash/ash_export.h"
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/observer_list.h"
+#include "base/time/time.h"
+#include "ui/base/events/event_handler.h"
+
+namespace ash {
+
+class UserActivityObserver;
+
+// Watches for input events and notifies observers that the user is active.
+class ASH_EXPORT UserActivityDetector : public ui::EventHandler {
+ public:
+ // Minimum amount of time between notifications to observers.
+ static const int kNotifyIntervalMs;
+
+ // Amount of time that mouse events should be ignored after notification
+ // is received that displays' power states are being changed.
+ static const int kDisplayPowerChangeIgnoreMouseMs;
+
+ UserActivityDetector();
+ virtual ~UserActivityDetector();
+
+ base::TimeTicks last_activity_time() const { return last_activity_time_; }
+
+ void set_now_for_test(base::TimeTicks now) { now_for_test_ = now; }
+
+ bool HasObserver(UserActivityObserver* observer) const;
+ void AddObserver(UserActivityObserver* observer);
+ void RemoveObserver(UserActivityObserver* observer);
+
+ // Called when displays are about to be turned on or off.
+ void OnDisplayPowerChanging();
+
+ // ui::EventHandler implementation.
+ virtual void OnKeyEvent(ui::KeyEvent* event) OVERRIDE;
+ virtual void OnMouseEvent(ui::MouseEvent* event) OVERRIDE;
+ virtual void OnScrollEvent(ui::ScrollEvent* event) OVERRIDE;
+ virtual void OnTouchEvent(ui::TouchEvent* event) OVERRIDE;
+ virtual void OnGestureEvent(ui::GestureEvent* event) OVERRIDE;
+
+ private:
+ // Returns |now_for_test_| if set or base::TimeTicks::Now() otherwise.
+ base::TimeTicks GetCurrentTime() const;
+
+ // Updates |last_activity_time_|. Additionally notifies observers and
+ // updates |last_observer_notification_time_| if enough time has passed
+ // since the last notification.
+ void HandleActivity(const ui::Event* event);
+
+ ObserverList<UserActivityObserver> observers_;
+
+ // Last time at which user activity was observed.
+ base::TimeTicks last_activity_time_;
+
+ // Last time at which we notified observers that the user was active.
+ base::TimeTicks last_observer_notification_time_;
+
+ // If set, used when the current time is needed. This can be set by tests to
+ // simulate the passage of time.
+ base::TimeTicks now_for_test_;
+
+ // If set, mouse events will be ignored until this time is reached. This
+ // is to avoid reporting mouse events that occur when displays are turned
+ // on or off as user activity.
+ base::TimeTicks honor_mouse_events_time_;
+
+ DISALLOW_COPY_AND_ASSIGN(UserActivityDetector);
+};
+
+} // namespace ash
+
+#endif // ASH_WM_USER_ACTIVITY_DETECTOR_H_
diff --git a/chromium/ash/wm/user_activity_detector_unittest.cc b/chromium/ash/wm/user_activity_detector_unittest.cc
new file mode 100644
index 00000000000..271d7648701
--- /dev/null
+++ b/chromium/ash/wm/user_activity_detector_unittest.cc
@@ -0,0 +1,225 @@
+// Copyright (c) 2012 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.
+
+#include "ash/wm/user_activity_detector.h"
+
+#include "ash/shell.h"
+#include "ash/test/ash_test_base.h"
+#include "ash/wm/user_activity_observer.h"
+#include "base/compiler_specific.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/time/time.h"
+#include "ui/aura/test/test_windows.h"
+#include "ui/aura/window.h"
+#include "ui/base/events/event.h"
+#include "ui/base/events/event_constants.h"
+#include "ui/base/keycodes/keyboard_codes.h"
+#include "ui/gfx/point.h"
+
+namespace {
+
+void SetEventTarget(ui::EventTarget* target, ui::Event* event) {
+ ui::Event::DispatcherApi dispatch_helper(event);
+ dispatch_helper.set_target(target);
+}
+
+}
+
+namespace ash {
+namespace test {
+
+// Implementation that just counts the number of times we've been told that the
+// user is active.
+class TestUserActivityObserver : public UserActivityObserver {
+ public:
+ TestUserActivityObserver() : num_invocations_(0) {}
+
+ int num_invocations() const { return num_invocations_; }
+ void reset_stats() { num_invocations_ = 0; }
+
+ // UserActivityObserver implementation.
+ virtual void OnUserActivity(const ui::Event* event) OVERRIDE {
+ num_invocations_++;
+ }
+
+ private:
+ // Number of times that OnUserActivity() has been called.
+ int num_invocations_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestUserActivityObserver);
+};
+
+class UserActivityDetectorTest : public AshTestBase {
+ public:
+ UserActivityDetectorTest() {}
+ virtual ~UserActivityDetectorTest() {}
+
+ virtual void SetUp() OVERRIDE {
+ AshTestBase::SetUp();
+ observer_.reset(new TestUserActivityObserver);
+ detector_ = Shell::GetInstance()->user_activity_detector();
+ detector_->AddObserver(observer_.get());
+
+ now_ = base::TimeTicks::Now();
+ detector_->set_now_for_test(now_);
+ }
+
+ virtual void TearDown() OVERRIDE {
+ detector_->RemoveObserver(observer_.get());
+ AshTestBase::TearDown();
+ }
+
+ protected:
+ // Move |detector_|'s idea of the current time forward by |delta|.
+ void AdvanceTime(base::TimeDelta delta) {
+ now_ += delta;
+ detector_->set_now_for_test(now_);
+ }
+
+ UserActivityDetector* detector_; // not owned
+
+ scoped_ptr<TestUserActivityObserver> observer_;
+
+ base::TimeTicks now_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(UserActivityDetectorTest);
+};
+
+// Checks that the observer is notified in response to different types of input
+// events.
+TEST_F(UserActivityDetectorTest, Basic) {
+ scoped_ptr<aura::Window> window(CreateTestWindowInShellWithId(12345));
+
+ ui::KeyEvent key_event(ui::ET_KEY_PRESSED, ui::VKEY_A, ui::EF_NONE, false);
+ SetEventTarget(window.get(), &key_event);
+ detector_->OnKeyEvent(&key_event);
+ EXPECT_FALSE(key_event.handled());
+ EXPECT_EQ(now_.ToInternalValue(),
+ detector_->last_activity_time().ToInternalValue());
+ EXPECT_EQ(1, observer_->num_invocations());
+ observer_->reset_stats();
+
+ base::TimeDelta advance_delta = base::TimeDelta::FromMilliseconds(
+ UserActivityDetector::kNotifyIntervalMs);
+ AdvanceTime(advance_delta);
+ ui::MouseEvent mouse_event(
+ ui::ET_MOUSE_MOVED, gfx::Point(), gfx::Point(), ui::EF_NONE);
+ SetEventTarget(window.get(), &mouse_event);
+ detector_->OnMouseEvent(&mouse_event);
+ EXPECT_FALSE(mouse_event.handled());
+ EXPECT_EQ(now_.ToInternalValue(),
+ detector_->last_activity_time().ToInternalValue());
+ EXPECT_EQ(1, observer_->num_invocations());
+ observer_->reset_stats();
+
+ base::TimeTicks time_before_ignore = now_;
+
+ // Temporarily ignore mouse events when displays are turned on or off.
+ detector_->OnDisplayPowerChanging();
+ detector_->OnMouseEvent(&mouse_event);
+ EXPECT_FALSE(mouse_event.handled());
+ EXPECT_EQ(time_before_ignore.ToInternalValue(),
+ detector_->last_activity_time().ToInternalValue());
+ EXPECT_EQ(0, observer_->num_invocations());
+ observer_->reset_stats();
+
+ const base::TimeDelta kIgnoreMouseTime =
+ base::TimeDelta::FromMilliseconds(
+ UserActivityDetector::kDisplayPowerChangeIgnoreMouseMs);
+ AdvanceTime(kIgnoreMouseTime / 2);
+ detector_->OnMouseEvent(&mouse_event);
+ EXPECT_FALSE(mouse_event.handled());
+ EXPECT_EQ(time_before_ignore.ToInternalValue(),
+ detector_->last_activity_time().ToInternalValue());
+ EXPECT_EQ(0, observer_->num_invocations());
+ observer_->reset_stats();
+
+ // After enough time has passed, mouse events should be reported again.
+ AdvanceTime(std::max(kIgnoreMouseTime, advance_delta));
+ detector_->OnMouseEvent(&mouse_event);
+ EXPECT_FALSE(mouse_event.handled());
+ EXPECT_EQ(now_.ToInternalValue(),
+ detector_->last_activity_time().ToInternalValue());
+ EXPECT_EQ(1, observer_->num_invocations());
+ observer_->reset_stats();
+
+ AdvanceTime(advance_delta);
+ ui::TouchEvent touch_event(
+ ui::ET_TOUCH_PRESSED, gfx::Point(), 0, base::TimeDelta());
+ SetEventTarget(window.get(), &touch_event);
+ detector_->OnTouchEvent(&touch_event);
+ EXPECT_FALSE(touch_event.handled());
+ EXPECT_EQ(now_.ToInternalValue(),
+ detector_->last_activity_time().ToInternalValue());
+ EXPECT_EQ(1, observer_->num_invocations());
+ observer_->reset_stats();
+
+ AdvanceTime(advance_delta);
+ ui::GestureEvent gesture_event(
+ ui::ET_GESTURE_TAP, 0, 0, ui::EF_NONE,
+ base::TimeDelta::FromMilliseconds(base::Time::Now().ToDoubleT() * 1000),
+ ui::GestureEventDetails(ui::ET_GESTURE_TAP, 0, 0), 0U);
+ SetEventTarget(window.get(), &gesture_event);
+ detector_->OnGestureEvent(&gesture_event);
+ EXPECT_FALSE(gesture_event.handled());
+ EXPECT_EQ(now_.ToInternalValue(),
+ detector_->last_activity_time().ToInternalValue());
+ EXPECT_EQ(1, observer_->num_invocations());
+ observer_->reset_stats();
+}
+
+// Checks that observers aren't notified too frequently.
+TEST_F(UserActivityDetectorTest, RateLimitNotifications) {
+ scoped_ptr<aura::Window> window(CreateTestWindowInShellWithId(12345));
+
+ // The observer should be notified about a key event.
+ ui::KeyEvent event(ui::ET_KEY_PRESSED, ui::VKEY_A, ui::EF_NONE, false);
+ SetEventTarget(window.get(), &event);
+ detector_->OnKeyEvent(&event);
+ EXPECT_FALSE(event.handled());
+ EXPECT_EQ(1, observer_->num_invocations());
+ observer_->reset_stats();
+
+ // It shouldn't be notified if a second event occurs
+ // in the same instant in time.
+ detector_->OnKeyEvent(&event);
+ EXPECT_FALSE(event.handled());
+ EXPECT_EQ(0, observer_->num_invocations());
+ observer_->reset_stats();
+
+ // Advance the time, but not quite enough for another notification to be sent.
+ AdvanceTime(
+ base::TimeDelta::FromMilliseconds(
+ UserActivityDetector::kNotifyIntervalMs - 100));
+ detector_->OnKeyEvent(&event);
+ EXPECT_FALSE(event.handled());
+ EXPECT_EQ(0, observer_->num_invocations());
+ observer_->reset_stats();
+
+ // Advance time by the notification interval, definitely moving out of the
+ // rate limit. This should let us trigger another notification.
+ AdvanceTime(base::TimeDelta::FromMilliseconds(
+ UserActivityDetector::kNotifyIntervalMs));
+
+ detector_->OnKeyEvent(&event);
+ EXPECT_FALSE(event.handled());
+ EXPECT_EQ(1, observer_->num_invocations());
+}
+
+// Checks that the detector ignores synthetic mouse events.
+TEST_F(UserActivityDetectorTest, IgnoreSyntheticMouseEvents) {
+ scoped_ptr<aura::Window> window(CreateTestWindowInShellWithId(12345));
+ ui::MouseEvent mouse_event(
+ ui::ET_MOUSE_MOVED, gfx::Point(), gfx::Point(), ui::EF_IS_SYNTHESIZED);
+ SetEventTarget(window.get(), &mouse_event);
+ detector_->OnMouseEvent(&mouse_event);
+ EXPECT_FALSE(mouse_event.handled());
+ EXPECT_EQ(base::TimeTicks().ToInternalValue(),
+ detector_->last_activity_time().ToInternalValue());
+ EXPECT_EQ(0, observer_->num_invocations());
+}
+
+} // namespace test
+} // namespace ash
diff --git a/chromium/ash/wm/user_activity_observer.h b/chromium/ash/wm/user_activity_observer.h
new file mode 100644
index 00000000000..a65ebfa9667
--- /dev/null
+++ b/chromium/ash/wm/user_activity_observer.h
@@ -0,0 +1,35 @@
+// Copyright (c) 2012 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 ASH_WM_USER_ACTIVITY_OBSERVER_H_
+#define ASH_WM_USER_ACTIVITY_OBSERVER_H_
+
+#include "ash/ash_export.h"
+#include "base/basictypes.h"
+
+namespace ui {
+class Event;
+}
+
+namespace ash {
+
+// Interface for classes that want to be notified about user activity.
+// Implementations should register themselves with UserActivityDetector.
+class ASH_EXPORT UserActivityObserver {
+ public:
+ // Invoked periodically while the user is active (i.e. generating input
+ // events). |event| is the event that triggered the notification; it may
+ // be NULL in some cases (e.g. testing or synthetic invocations).
+ virtual void OnUserActivity(const ui::Event* event) = 0;
+
+ protected:
+ UserActivityObserver() {}
+ virtual ~UserActivityObserver() {}
+
+ DISALLOW_COPY_AND_ASSIGN(UserActivityObserver);
+};
+
+} // namespace ash
+
+#endif // ASH_WM_USER_ACTIVITY_OBSERVER_H_
diff --git a/chromium/ash/wm/video_detector.cc b/chromium/ash/wm/video_detector.cc
new file mode 100644
index 00000000000..743b49cb784
--- /dev/null
+++ b/chromium/ash/wm/video_detector.cc
@@ -0,0 +1,134 @@
+// Copyright (c) 2012 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.
+
+#include "ash/wm/video_detector.h"
+
+#include "ash/shell.h"
+#include "ash/wm/window_util.h"
+#include "ui/aura/env.h"
+#include "ui/aura/root_window.h"
+#include "ui/aura/window.h"
+#include "ui/gfx/rect.h"
+
+namespace ash {
+
+const int VideoDetector::kMinUpdateWidth = 333;
+const int VideoDetector::kMinUpdateHeight = 250;
+const int VideoDetector::kMinFramesPerSecond = 15;
+const double VideoDetector::kNotifyIntervalSec = 1.0;
+
+// Stores information about updates to a window and determines whether it's
+// likely that a video is playing in it.
+class VideoDetector::WindowInfo {
+ public:
+ WindowInfo() : buffer_start_(0), buffer_size_(0) {}
+
+ // Handles an update within a window, returning true if it appears that
+ // video is currently playing in the window.
+ bool RecordUpdateAndCheckForVideo(const gfx::Rect& region,
+ base::TimeTicks now) {
+ if (region.width() < kMinUpdateWidth || region.height() < kMinUpdateHeight)
+ return false;
+
+ // If the buffer is full, drop the first timestamp.
+ if (buffer_size_ == static_cast<size_t>(kMinFramesPerSecond)) {
+ buffer_start_ = (buffer_start_ + 1) % kMinFramesPerSecond;
+ buffer_size_--;
+ }
+
+ update_times_[(buffer_start_ + buffer_size_) % kMinFramesPerSecond] = now;
+ buffer_size_++;
+
+ return buffer_size_ == static_cast<size_t>(kMinFramesPerSecond) &&
+ (now - update_times_[buffer_start_]).InSecondsF() <= 1.0;
+ }
+
+ private:
+ // Circular buffer containing update times of the last (up to
+ // |kMinFramesPerSecond|) video-sized updates to this window.
+ base::TimeTicks update_times_[kMinFramesPerSecond];
+
+ // Index into |update_times_| of the oldest update.
+ size_t buffer_start_;
+
+ // Number of updates stored in |update_times_|.
+ size_t buffer_size_;
+
+ DISALLOW_COPY_AND_ASSIGN(WindowInfo);
+};
+
+VideoDetector::VideoDetector()
+ : observer_manager_(this),
+ is_shutting_down_(false) {
+ aura::Env::GetInstance()->AddObserver(this);
+ Shell::GetInstance()->AddShellObserver(this);
+}
+
+VideoDetector::~VideoDetector() {
+ Shell::GetInstance()->RemoveShellObserver(this);
+ aura::Env::GetInstance()->RemoveObserver(this);
+}
+
+void VideoDetector::AddObserver(VideoDetectorObserver* observer) {
+ observers_.AddObserver(observer);
+}
+
+void VideoDetector::RemoveObserver(VideoDetectorObserver* observer) {
+ observers_.RemoveObserver(observer);
+}
+
+void VideoDetector::OnWindowInitialized(aura::Window* window) {
+ observer_manager_.Add(window);
+}
+
+void VideoDetector::OnWindowPaintScheduled(aura::Window* window,
+ const gfx::Rect& region) {
+ if (is_shutting_down_)
+ return;
+ linked_ptr<WindowInfo>& info = window_infos_[window];
+ if (!info.get())
+ info.reset(new WindowInfo);
+
+ base::TimeTicks now =
+ !now_for_test_.is_null() ? now_for_test_ : base::TimeTicks::Now();
+ if (info->RecordUpdateAndCheckForVideo(region, now))
+ MaybeNotifyObservers(window, now);
+}
+
+void VideoDetector::OnWindowDestroyed(aura::Window* window) {
+ window_infos_.erase(window);
+ observer_manager_.Remove(window);
+}
+
+void VideoDetector::OnAppTerminating() {
+ // Stop checking video activity once the shutdown
+ // process starts. crbug.com/231696.
+ is_shutting_down_ = true;
+}
+
+void VideoDetector::MaybeNotifyObservers(aura::Window* window,
+ base::TimeTicks now) {
+ if (!last_observer_notification_time_.is_null() &&
+ (now - last_observer_notification_time_).InSecondsF() <
+ kNotifyIntervalSec)
+ return;
+
+ if (!window->IsVisible())
+ return;
+
+ gfx::Rect root_bounds = window->GetRootWindow()->bounds();
+ if (!window->GetBoundsInRootWindow().Intersects(root_bounds))
+ return;
+
+ aura::Window* toplevel_window = wm::GetActivatableWindow(window);
+ bool is_fullscreen =
+ toplevel_window ? wm::IsWindowFullscreen(toplevel_window) : false;
+
+ FOR_EACH_OBSERVER(VideoDetectorObserver,
+ observers_,
+ OnVideoDetected(is_fullscreen));
+ last_observer_notification_time_ = now;
+}
+
+} // namespace ash
diff --git a/chromium/ash/wm/video_detector.h b/chromium/ash/wm/video_detector.h
new file mode 100644
index 00000000000..7a2b112a5a5
--- /dev/null
+++ b/chromium/ash/wm/video_detector.h
@@ -0,0 +1,109 @@
+// Copyright (c) 2012 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 ASH_WM_VIDEO_DETECTOR_H_
+#define ASH_WM_VIDEO_DETECTOR_H_
+
+#include <map>
+
+#include "ash/ash_export.h"
+#include "ash/shell_observer.h"
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/memory/linked_ptr.h"
+#include "base/observer_list.h"
+#include "base/scoped_observer.h"
+#include "base/time/time.h"
+#include "ui/aura/env_observer.h"
+#include "ui/aura/window_observer.h"
+
+namespace aura {
+class Window;
+}
+
+namespace gfx {
+class Rect;
+}
+
+namespace ash {
+
+class ASH_EXPORT VideoDetectorObserver {
+ public:
+ // Invoked periodically while a video is being played onscreen.
+ virtual void OnVideoDetected(bool is_fullscreen) = 0;
+
+ protected:
+ virtual ~VideoDetectorObserver() {}
+};
+
+// Watches for updates to windows and tries to detect when a video is playing.
+// We err on the side of false positives and can be fooled by things like
+// continuous scrolling of a page.
+class ASH_EXPORT VideoDetector : public aura::EnvObserver,
+ public aura::WindowObserver,
+ public ShellObserver {
+ public:
+ // Minimum dimensions in pixels that a window update must have to be
+ // considered a potential video frame.
+ static const int kMinUpdateWidth;
+ static const int kMinUpdateHeight;
+
+ // Number of video-sized updates that we must see within a second in a window
+ // before we assume that a video is playing.
+ static const int kMinFramesPerSecond;
+
+ // Minimum amount of time between notifications to observers that a video is
+ // playing.
+ static const double kNotifyIntervalSec;
+
+ VideoDetector();
+ virtual ~VideoDetector();
+
+ void set_now_for_test(base::TimeTicks now) { now_for_test_ = now; }
+
+ void AddObserver(VideoDetectorObserver* observer);
+ void RemoveObserver(VideoDetectorObserver* observer);
+
+ // EnvObserver overrides.
+ virtual void OnWindowInitialized(aura::Window* window) OVERRIDE;
+
+ // WindowObserver overrides.
+ virtual void OnWindowPaintScheduled(aura::Window* window,
+ const gfx::Rect& region) OVERRIDE;
+ virtual void OnWindowDestroyed(aura::Window* window) OVERRIDE;
+
+ // ShellObserver overrides.
+ virtual void OnAppTerminating() OVERRIDE;
+
+ private:
+ class WindowInfo;
+ typedef std::map<aura::Window*, linked_ptr<WindowInfo> > WindowInfoMap;
+
+ // Possibly notifies observers in response to detection of a video in
+ // |window|. Notifications are rate-limited and don't get sent if the window
+ // is invisible or offscreen.
+ void MaybeNotifyObservers(aura::Window* window, base::TimeTicks now);
+
+ // Maps from a window that we're tracking to information about it.
+ WindowInfoMap window_infos_;
+
+ ObserverList<VideoDetectorObserver> observers_;
+
+ // Last time at which we notified observers that a video was playing.
+ base::TimeTicks last_observer_notification_time_;
+
+ // If set, used when the current time is needed. This can be set by tests to
+ // simulate the passage of time.
+ base::TimeTicks now_for_test_;
+
+ ScopedObserver<aura::Window, aura::WindowObserver> observer_manager_;
+
+ bool is_shutting_down_;
+
+ DISALLOW_COPY_AND_ASSIGN(VideoDetector);
+};
+
+} // namespace ash
+
+#endif // ASH_WM_VIDEO_DETECTOR_H_
diff --git a/chromium/ash/wm/video_detector_unittest.cc b/chromium/ash/wm/video_detector_unittest.cc
new file mode 100644
index 00000000000..01c79cf8797
--- /dev/null
+++ b/chromium/ash/wm/video_detector_unittest.cc
@@ -0,0 +1,295 @@
+// Copyright (c) 2012 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.
+
+#include "ash/wm/video_detector.h"
+
+#include "ash/shell.h"
+#include "ash/test/ash_test_base.h"
+#include "ash/wm/window_util.h"
+#include "base/compiler_specific.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/time/time.h"
+#include "third_party/skia/include/core/SkColor.h"
+#include "ui/aura/client/aura_constants.h"
+#include "ui/aura/client/window_types.h"
+#include "ui/aura/root_window.h"
+#include "ui/aura/test/test_windows.h"
+#include "ui/aura/window.h"
+#include "ui/gfx/rect.h"
+
+namespace ash {
+namespace test {
+
+// Implementation that just counts the number of times we've been told that a
+// video is playing.
+class TestVideoDetectorObserver : public VideoDetectorObserver {
+ public:
+ TestVideoDetectorObserver() : num_invocations_(0),
+ num_fullscreens_(0),
+ num_not_fullscreens_(0) {}
+
+ int num_invocations() const { return num_invocations_; }
+ int num_fullscreens() const { return num_fullscreens_; }
+ int num_not_fullscreens() const { return num_not_fullscreens_; }
+ void reset_stats() {
+ num_invocations_ = 0;
+ num_fullscreens_ = 0;
+ num_not_fullscreens_ = 0;
+ }
+
+ // VideoDetectorObserver implementation.
+ virtual void OnVideoDetected(bool is_fullscreen) OVERRIDE {
+ num_invocations_++;
+ if (is_fullscreen)
+ num_fullscreens_++;
+ else
+ num_not_fullscreens_++;
+ }
+
+ private:
+ // Number of times that OnVideoDetected() has been called.
+ int num_invocations_;
+ // Number of times that OnVideoDetected() has been called with is_fullscreen
+ // == true.
+ int num_fullscreens_;
+ // Number of times that OnVideoDetected() has been called with is_fullscreen
+ // == false.
+ int num_not_fullscreens_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestVideoDetectorObserver);
+};
+
+class VideoDetectorTest : public AshTestBase {
+ public:
+ VideoDetectorTest() {}
+ virtual ~VideoDetectorTest() {}
+
+ virtual void SetUp() OVERRIDE {
+ AshTestBase::SetUp();
+ observer_.reset(new TestVideoDetectorObserver);
+ detector_ = Shell::GetInstance()->video_detector();
+ detector_->AddObserver(observer_.get());
+
+ now_ = base::TimeTicks::Now();
+ detector_->set_now_for_test(now_);
+ }
+
+ virtual void TearDown() OVERRIDE {
+ detector_->RemoveObserver(observer_.get());
+ AshTestBase::TearDown();
+ }
+
+ protected:
+ // Move |detector_|'s idea of the current time forward by |delta|.
+ void AdvanceTime(base::TimeDelta delta) {
+ now_ += delta;
+ detector_->set_now_for_test(now_);
+ }
+
+ VideoDetector* detector_; // not owned
+
+ scoped_ptr<TestVideoDetectorObserver> observer_;
+
+ base::TimeTicks now_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(VideoDetectorTest);
+};
+
+TEST_F(VideoDetectorTest, Basic) {
+ gfx::Rect window_bounds(gfx::Point(), gfx::Size(1024, 768));
+ scoped_ptr<aura::Window> window(
+ CreateTestWindowInShell(SK_ColorRED, 12345, window_bounds));
+
+ // Send enough updates, but make them be too small to trigger detection.
+ gfx::Rect update_region(
+ gfx::Point(),
+ gfx::Size(VideoDetector::kMinUpdateWidth - 1,
+ VideoDetector::kMinUpdateHeight));
+ for (int i = 0; i < VideoDetector::kMinFramesPerSecond; ++i)
+ detector_->OnWindowPaintScheduled(window.get(), update_region);
+ EXPECT_EQ(0, observer_->num_invocations());
+
+ // Send not-quite-enough adaquately-sized updates.
+ observer_->reset_stats();
+ AdvanceTime(base::TimeDelta::FromSeconds(2));
+ update_region.set_size(
+ gfx::Size(VideoDetector::kMinUpdateWidth,
+ VideoDetector::kMinUpdateHeight));
+ for (int i = 0; i < VideoDetector::kMinFramesPerSecond - 1; ++i)
+ detector_->OnWindowPaintScheduled(window.get(), update_region);
+ EXPECT_EQ(0, observer_->num_invocations());
+
+ // We should get notified after the next update, but not in response to
+ // additional updates.
+ detector_->OnWindowPaintScheduled(window.get(), update_region);
+ EXPECT_EQ(1, observer_->num_invocations());
+ EXPECT_EQ(0, observer_->num_fullscreens());
+ EXPECT_EQ(1, observer_->num_not_fullscreens());
+ detector_->OnWindowPaintScheduled(window.get(), update_region);
+ EXPECT_EQ(1, observer_->num_invocations());
+ EXPECT_EQ(0, observer_->num_fullscreens());
+ EXPECT_EQ(1, observer_->num_not_fullscreens());
+
+ // Spread out the frames over a longer period of time, but send enough
+ // over a one-second window that the observer should be notified.
+ observer_->reset_stats();
+ AdvanceTime(base::TimeDelta::FromSeconds(2));
+ detector_->OnWindowPaintScheduled(window.get(), update_region);
+ EXPECT_EQ(0, observer_->num_invocations());
+
+ AdvanceTime(base::TimeDelta::FromMilliseconds(500));
+ const int kNumFrames = VideoDetector::kMinFramesPerSecond + 1;
+ base::TimeDelta kInterval =
+ base::TimeDelta::FromMilliseconds(1000 / kNumFrames);
+ for (int i = 0; i < kNumFrames; ++i) {
+ AdvanceTime(kInterval);
+ detector_->OnWindowPaintScheduled(window.get(), update_region);
+ }
+ EXPECT_EQ(1, observer_->num_invocations());
+
+ // Keep going and check that the observer is notified again.
+ for (int i = 0; i < kNumFrames; ++i) {
+ AdvanceTime(kInterval);
+ detector_->OnWindowPaintScheduled(window.get(), update_region);
+ }
+ EXPECT_EQ(2, observer_->num_invocations());
+
+ // Send updates at a slower rate and check that the observer isn't notified.
+ base::TimeDelta kSlowInterval = base::TimeDelta::FromMilliseconds(
+ 1000 / (VideoDetector::kMinFramesPerSecond - 2));
+ for (int i = 0; i < kNumFrames; ++i) {
+ AdvanceTime(kSlowInterval);
+ detector_->OnWindowPaintScheduled(window.get(), update_region);
+ }
+ EXPECT_EQ(2, observer_->num_invocations());
+}
+
+TEST_F(VideoDetectorTest, Shutdown) {
+ gfx::Rect window_bounds(gfx::Point(), gfx::Size(1024, 768));
+ scoped_ptr<aura::Window> window(
+ CreateTestWindowInShell(SK_ColorRED, 12345, window_bounds));
+ gfx::Rect update_region(
+ gfx::Point(),
+ gfx::Size(VideoDetector::kMinUpdateWidth,
+ VideoDetector::kMinUpdateHeight));
+
+ // It should not detect video during the shutdown.
+ Shell::GetInstance()->OnAppTerminating();
+ for (int i = 0; i < VideoDetector::kMinFramesPerSecond; ++i)
+ detector_->OnWindowPaintScheduled(window.get(), update_region);
+ EXPECT_EQ(0, observer_->num_invocations());
+}
+
+TEST_F(VideoDetectorTest, WindowNotVisible) {
+ gfx::Rect window_bounds(gfx::Point(), gfx::Size(1024, 768));
+ scoped_ptr<aura::Window> window(
+ CreateTestWindowInShell(SK_ColorRED, 12345, window_bounds));
+
+ // Reparent the window to the root to make sure that visibility changes aren't
+ // animated.
+ Shell::GetPrimaryRootWindow()->AddChild(window.get());
+
+ // We shouldn't report video that's played in a hidden window.
+ window->Hide();
+ gfx::Rect update_region(
+ gfx::Point(),
+ gfx::Size(VideoDetector::kMinUpdateWidth,
+ VideoDetector::kMinUpdateHeight));
+ for (int i = 0; i < VideoDetector::kMinFramesPerSecond; ++i)
+ detector_->OnWindowPaintScheduled(window.get(), update_region);
+ EXPECT_EQ(0, observer_->num_invocations());
+
+ // Make the window visible and send more updates.
+ observer_->reset_stats();
+ AdvanceTime(base::TimeDelta::FromSeconds(2));
+ window->Show();
+ for (int i = 0; i < VideoDetector::kMinFramesPerSecond; ++i)
+ detector_->OnWindowPaintScheduled(window.get(), update_region);
+ EXPECT_EQ(1, observer_->num_invocations());
+ EXPECT_EQ(0, observer_->num_fullscreens());
+ EXPECT_EQ(1, observer_->num_not_fullscreens());
+
+ // We also shouldn't report video in a window that's fully offscreen.
+ observer_->reset_stats();
+ AdvanceTime(base::TimeDelta::FromSeconds(2));
+ gfx::Rect offscreen_bounds(
+ gfx::Point(Shell::GetPrimaryRootWindow()->bounds().width(), 0),
+ window_bounds.size());
+ window->SetBounds(offscreen_bounds);
+ ASSERT_EQ(offscreen_bounds, window->bounds());
+ for (int i = 0; i < VideoDetector::kMinFramesPerSecond; ++i)
+ detector_->OnWindowPaintScheduled(window.get(), update_region);
+ EXPECT_EQ(0, observer_->num_invocations());
+}
+
+TEST_F(VideoDetectorTest, MultipleWindows) {
+ // Create two windows.
+ gfx::Rect window_bounds(gfx::Point(), gfx::Size(1024, 768));
+ scoped_ptr<aura::Window> window1(
+ CreateTestWindowInShell(SK_ColorRED, 12345, window_bounds));
+ scoped_ptr<aura::Window> window2(
+ CreateTestWindowInShell(SK_ColorBLUE, 23456, window_bounds));
+
+ // Even if there's video playing in both, the observer should only receive a
+ // single notification.
+ gfx::Rect update_region(
+ gfx::Point(),
+ gfx::Size(VideoDetector::kMinUpdateWidth,
+ VideoDetector::kMinUpdateHeight));
+ for (int i = 0; i < VideoDetector::kMinFramesPerSecond; ++i)
+ detector_->OnWindowPaintScheduled(window1.get(), update_region);
+ for (int i = 0; i < VideoDetector::kMinFramesPerSecond; ++i)
+ detector_->OnWindowPaintScheduled(window2.get(), update_region);
+ EXPECT_EQ(1, observer_->num_invocations());
+ EXPECT_EQ(0, observer_->num_fullscreens());
+ EXPECT_EQ(1, observer_->num_not_fullscreens());
+}
+
+// Test that the observer receives repeated notifications.
+TEST_F(VideoDetectorTest, RepeatedNotifications) {
+ gfx::Rect window_bounds(gfx::Point(), gfx::Size(1024, 768));
+ scoped_ptr<aura::Window> window(
+ CreateTestWindowInShell(SK_ColorRED, 12345, window_bounds));
+
+ gfx::Rect update_region(
+ gfx::Point(),
+ gfx::Size(VideoDetector::kMinUpdateWidth,
+ VideoDetector::kMinUpdateHeight));
+ for (int i = 0; i < VideoDetector::kMinFramesPerSecond; ++i)
+ detector_->OnWindowPaintScheduled(window.get(), update_region);
+ EXPECT_EQ(1, observer_->num_invocations());
+ EXPECT_EQ(0, observer_->num_fullscreens());
+ EXPECT_EQ(1, observer_->num_not_fullscreens());
+ // Let enough time pass that a second notification should be sent.
+ observer_->reset_stats();
+ AdvanceTime(base::TimeDelta::FromSeconds(
+ static_cast<int64>(VideoDetector::kNotifyIntervalSec + 1)));
+ for (int i = 0; i < VideoDetector::kMinFramesPerSecond; ++i)
+ detector_->OnWindowPaintScheduled(window.get(), update_region);
+ EXPECT_EQ(1, observer_->num_invocations());
+ EXPECT_EQ(0, observer_->num_fullscreens());
+ EXPECT_EQ(1, observer_->num_not_fullscreens());
+}
+
+// Test that the observer receives a true value when the window is fullscreen.
+TEST_F(VideoDetectorTest, FullscreenWindow) {
+ gfx::Rect window_bounds(gfx::Point(), gfx::Size(1024, 768));
+ scoped_ptr<aura::Window> window(
+ CreateTestWindowInShell(SK_ColorRED, 12345, window_bounds));
+ window->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_FULLSCREEN);
+ window->Focus();
+ gfx::Rect update_region(
+ gfx::Point(),
+ gfx::Size(VideoDetector::kMinUpdateWidth,
+ VideoDetector::kMinUpdateHeight));
+ for (int i = 0; i < VideoDetector::kMinFramesPerSecond; ++i)
+ detector_->OnWindowPaintScheduled(window.get(), update_region);
+ EXPECT_EQ(1, observer_->num_invocations());
+ EXPECT_EQ(1, observer_->num_fullscreens());
+ EXPECT_EQ(0, observer_->num_not_fullscreens());
+}
+
+} // namespace test
+} // namespace ash
diff --git a/chromium/ash/wm/window_animations.cc b/chromium/ash/wm/window_animations.cc
new file mode 100644
index 00000000000..a3c0ca80621
--- /dev/null
+++ b/chromium/ash/wm/window_animations.cc
@@ -0,0 +1,560 @@
+// Copyright (c) 2012 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.
+
+#include "ash/wm/window_animations.h"
+
+#include <math.h>
+
+#include <algorithm>
+#include <vector>
+
+#include "ash/launcher/launcher.h"
+#include "ash/screen_ash.h"
+#include "ash/shelf/shelf_layout_manager.h"
+#include "ash/shelf/shelf_widget.h"
+#include "ash/shell.h"
+#include "ash/wm/window_util.h"
+#include "ash/wm/workspace_controller.h"
+#include "base/command_line.h"
+#include "base/compiler_specific.h"
+#include "base/logging.h"
+#include "base/message_loop/message_loop.h"
+#include "base/stl_util.h"
+#include "base/time/time.h"
+#include "ui/aura/client/aura_constants.h"
+#include "ui/aura/window.h"
+#include "ui/aura/window_observer.h"
+#include "ui/aura/window_property.h"
+#include "ui/compositor/compositor_observer.h"
+#include "ui/compositor/layer.h"
+#include "ui/compositor/layer_animation_observer.h"
+#include "ui/compositor/layer_animation_sequence.h"
+#include "ui/compositor/layer_animator.h"
+#include "ui/compositor/scoped_layer_animation_settings.h"
+#include "ui/gfx/interpolated_transform.h"
+#include "ui/gfx/screen.h"
+#include "ui/gfx/vector3d_f.h"
+#include "ui/views/corewm/window_util.h"
+#include "ui/views/view.h"
+#include "ui/views/widget/widget.h"
+
+namespace ash {
+namespace {
+const int kLayerAnimationsForMinimizeDurationMS = 200;
+
+// Durations for the cross-fade animation, in milliseconds.
+const float kCrossFadeDurationMinMs = 200.f;
+const float kCrossFadeDurationMaxMs = 400.f;
+
+// Durations for the brightness/grayscale fade animation, in milliseconds.
+const int kBrightnessGrayscaleFadeDurationMs = 1000;
+
+// Brightness/grayscale values for hide/show window animations.
+const float kWindowAnimation_HideBrightnessGrayscale = 1.f;
+const float kWindowAnimation_ShowBrightnessGrayscale = 0.f;
+
+const float kWindowAnimation_HideOpacity = 0.f;
+const float kWindowAnimation_ShowOpacity = 1.f;
+// TODO(sky): if we end up sticking with 0, nuke the code doing the rotation.
+const float kWindowAnimation_MinimizeRotate = 0.f;
+
+// Tween type when cross fading a workspace window.
+const ui::Tween::Type kCrossFadeTweenType = ui::Tween::EASE_IN_OUT;
+
+// Scales for AshWindow above/below current workspace.
+const float kLayerScaleAboveSize = 1.1f;
+const float kLayerScaleBelowSize = .9f;
+
+int64 Round64(float f) {
+ return static_cast<int64>(f + 0.5f);
+}
+
+} // namespace
+
+const int kCrossFadeDurationMS = 200;
+
+void AddLayerAnimationsForMinimize(aura::Window* window, bool show) {
+ // Recalculate the transform at restore time since the launcher item may have
+ // moved while the window was minimized.
+ gfx::Rect bounds = window->bounds();
+ gfx::Rect target_bounds = GetMinimizeAnimationTargetBoundsInScreen(window);
+ target_bounds =
+ ScreenAsh::ConvertRectFromScreen(window->parent(), target_bounds);
+
+ float scale_x = static_cast<float>(target_bounds.width()) / bounds.width();
+ float scale_y = static_cast<float>(target_bounds.height()) / bounds.height();
+
+ scoped_ptr<ui::InterpolatedTransform> scale(
+ new ui::InterpolatedScale(gfx::Point3F(1, 1, 1),
+ gfx::Point3F(scale_x, scale_y, 1)));
+
+ scoped_ptr<ui::InterpolatedTransform> translation(
+ new ui::InterpolatedTranslation(
+ gfx::Point(),
+ gfx::Point(target_bounds.x() - bounds.x(),
+ target_bounds.y() - bounds.y())));
+
+ scoped_ptr<ui::InterpolatedTransform> rotation(
+ new ui::InterpolatedRotation(0, kWindowAnimation_MinimizeRotate));
+
+ scoped_ptr<ui::InterpolatedTransform> rotation_about_pivot(
+ new ui::InterpolatedTransformAboutPivot(
+ gfx::Point(bounds.width() * 0.5, bounds.height() * 0.5),
+ rotation.release()));
+
+ scale->SetChild(translation.release());
+ rotation_about_pivot->SetChild(scale.release());
+
+ rotation_about_pivot->SetReversed(show);
+
+ base::TimeDelta duration = base::TimeDelta::FromMilliseconds(
+ kLayerAnimationsForMinimizeDurationMS);
+
+ scoped_ptr<ui::LayerAnimationElement> transition(
+ ui::LayerAnimationElement::CreateInterpolatedTransformElement(
+ rotation_about_pivot.release(), duration));
+
+ transition->set_tween_type(
+ show ? ui::Tween::EASE_IN : ui::Tween::EASE_IN_OUT);
+
+ window->layer()->GetAnimator()->ScheduleAnimation(
+ new ui::LayerAnimationSequence(transition.release()));
+
+ // When hiding a window, turn off blending until the animation is 3 / 4 done
+ // to save bandwidth and reduce jank.
+ if (!show) {
+ window->layer()->GetAnimator()->SchedulePauseForProperties(
+ (duration * 3) / 4, ui::LayerAnimationElement::OPACITY, -1);
+ }
+
+ // Fade in and out quickly when the window is small to reduce jank.
+ float opacity = show ? 1.0f : 0.0f;
+ window->layer()->GetAnimator()->ScheduleAnimation(
+ new ui::LayerAnimationSequence(
+ ui::LayerAnimationElement::CreateOpacityElement(
+ opacity, duration / 4)));
+}
+
+void AnimateShowWindow_Minimize(aura::Window* window) {
+ window->layer()->set_delegate(window);
+ window->layer()->SetOpacity(kWindowAnimation_HideOpacity);
+ AddLayerAnimationsForMinimize(window, true);
+
+ // Now that the window has been restored, we need to clear its animation style
+ // to default so that normal animation applies.
+ views::corewm::SetWindowVisibilityAnimationType(
+ window, views::corewm::WINDOW_VISIBILITY_ANIMATION_TYPE_DEFAULT);
+}
+
+void AnimateHideWindow_Minimize(aura::Window* window) {
+ window->layer()->set_delegate(NULL);
+
+ // Property sets within this scope will be implicitly animated.
+ ui::ScopedLayerAnimationSettings settings(window->layer()->GetAnimator());
+ base::TimeDelta duration = base::TimeDelta::FromMilliseconds(
+ kLayerAnimationsForMinimizeDurationMS);
+ settings.SetTransitionDuration(duration);
+ settings.AddObserver(
+ views::corewm::CreateHidingWindowAnimationObserver(window));
+ window->layer()->SetVisible(false);
+
+ AddLayerAnimationsForMinimize(window, false);
+}
+
+void AnimateShowHideWindowCommon_BrightnessGrayscale(aura::Window* window,
+ bool show) {
+ window->layer()->set_delegate(window);
+
+ float start_value, end_value;
+ if (show) {
+ start_value = kWindowAnimation_HideBrightnessGrayscale;
+ end_value = kWindowAnimation_ShowBrightnessGrayscale;
+ } else {
+ start_value = kWindowAnimation_ShowBrightnessGrayscale;
+ end_value = kWindowAnimation_HideBrightnessGrayscale;
+ }
+
+ window->layer()->SetLayerBrightness(start_value);
+ window->layer()->SetLayerGrayscale(start_value);
+ if (show) {
+ window->layer()->SetOpacity(kWindowAnimation_ShowOpacity);
+ window->layer()->SetVisible(true);
+ }
+
+ base::TimeDelta duration =
+ base::TimeDelta::FromMilliseconds(kBrightnessGrayscaleFadeDurationMs);
+
+ ui::ScopedLayerAnimationSettings settings(window->layer()->GetAnimator());
+ settings.SetTransitionDuration(duration);
+ if (!show) {
+ settings.AddObserver(
+ views::corewm::CreateHidingWindowAnimationObserver(window));
+ }
+
+ window->layer()->GetAnimator()->
+ ScheduleTogether(
+ CreateBrightnessGrayscaleAnimationSequence(end_value, duration));
+ if (!show) {
+ window->layer()->SetOpacity(kWindowAnimation_HideOpacity);
+ window->layer()->SetVisible(false);
+ }
+}
+
+void AnimateShowWindow_BrightnessGrayscale(aura::Window* window) {
+ AnimateShowHideWindowCommon_BrightnessGrayscale(window, true);
+}
+
+void AnimateHideWindow_BrightnessGrayscale(aura::Window* window) {
+ AnimateShowHideWindowCommon_BrightnessGrayscale(window, false);
+}
+
+bool AnimateShowWindow(aura::Window* window) {
+ if (!views::corewm::HasWindowVisibilityAnimationTransition(
+ window, views::corewm::ANIMATE_SHOW)) {
+ return false;
+ }
+
+ switch (views::corewm::GetWindowVisibilityAnimationType(window)) {
+ case WINDOW_VISIBILITY_ANIMATION_TYPE_MINIMIZE:
+ AnimateShowWindow_Minimize(window);
+ return true;
+ case WINDOW_VISIBILITY_ANIMATION_TYPE_BRIGHTNESS_GRAYSCALE:
+ AnimateShowWindow_BrightnessGrayscale(window);
+ return true;
+ default:
+ NOTREACHED();
+ return false;
+ }
+}
+
+bool AnimateHideWindow(aura::Window* window) {
+ if (!views::corewm::HasWindowVisibilityAnimationTransition(
+ window, views::corewm::ANIMATE_HIDE)) {
+ return false;
+ }
+
+ switch (views::corewm::GetWindowVisibilityAnimationType(window)) {
+ case WINDOW_VISIBILITY_ANIMATION_TYPE_MINIMIZE:
+ AnimateHideWindow_Minimize(window);
+ return true;
+ case WINDOW_VISIBILITY_ANIMATION_TYPE_BRIGHTNESS_GRAYSCALE:
+ AnimateHideWindow_BrightnessGrayscale(window);
+ return true;
+ default:
+ NOTREACHED();
+ return false;
+ }
+}
+
+// Observer for a window cross-fade animation. If either the window closes or
+// the layer's animation completes or compositing is aborted due to GPU crash,
+// it deletes the layer and removes itself as an observer.
+class CrossFadeObserver : public ui::CompositorObserver,
+ public aura::WindowObserver,
+ public ui::ImplicitAnimationObserver {
+ public:
+ // Observes |window| for destruction, but does not take ownership.
+ // Takes ownership of |layer| and its child layers.
+ CrossFadeObserver(aura::Window* window, ui::Layer* layer)
+ : window_(window),
+ layer_(layer) {
+ window_->AddObserver(this);
+ layer_->GetCompositor()->AddObserver(this);
+ }
+ virtual ~CrossFadeObserver() {
+ window_->RemoveObserver(this);
+ window_ = NULL;
+ layer_->GetCompositor()->RemoveObserver(this);
+ views::corewm::DeepDeleteLayers(layer_);
+ layer_ = NULL;
+ }
+
+ // ui::CompositorObserver overrides:
+ virtual void OnCompositingDidCommit(ui::Compositor* compositor) OVERRIDE {
+ }
+ virtual void OnCompositingStarted(ui::Compositor* compositor,
+ base::TimeTicks start_time) OVERRIDE {
+ }
+ virtual void OnCompositingEnded(ui::Compositor* compositor) OVERRIDE {
+ }
+ virtual void OnCompositingAborted(ui::Compositor* compositor) OVERRIDE {
+ // Triggers OnImplicitAnimationsCompleted() to be called and deletes us.
+ layer_->GetAnimator()->StopAnimating();
+ }
+ virtual void OnCompositingLockStateChanged(
+ ui::Compositor* compositor) OVERRIDE {
+ }
+ virtual void OnUpdateVSyncParameters(ui::Compositor* compositor,
+ base::TimeTicks timebase,
+ base::TimeDelta interval) OVERRIDE {
+ }
+
+ // aura::WindowObserver overrides:
+ virtual void OnWindowDestroying(aura::Window* window) OVERRIDE {
+ // Triggers OnImplicitAnimationsCompleted() to be called and deletes us.
+ layer_->GetAnimator()->StopAnimating();
+ }
+
+ // ui::ImplicitAnimationObserver overrides:
+ virtual void OnImplicitAnimationsCompleted() OVERRIDE {
+ delete this;
+ }
+
+ private:
+ aura::Window* window_; // not owned
+ ui::Layer* layer_; // owned
+
+ DISALLOW_COPY_AND_ASSIGN(CrossFadeObserver);
+};
+
+// Implementation of cross fading. Window is the window being cross faded. It
+// should be at the target bounds. |old_layer| the previous layer from |window|.
+// This takes ownership of |old_layer| and deletes when the animation is done.
+// |pause_duration| is the duration to pause at the current bounds before
+// animating. Returns the duration of the fade.
+base::TimeDelta CrossFadeImpl(aura::Window* window,
+ ui::Layer* old_layer,
+ ui::Tween::Type tween_type) {
+ const gfx::Rect old_bounds(old_layer->bounds());
+ const gfx::Rect new_bounds(window->bounds());
+ const bool old_on_top = (old_bounds.width() > new_bounds.width());
+
+ // Shorten the animation if there's not much visual movement.
+ const base::TimeDelta duration = GetCrossFadeDuration(old_bounds, new_bounds);
+
+ // Scale up the old layer while translating to new position.
+ {
+ old_layer->GetAnimator()->StopAnimating();
+ ui::ScopedLayerAnimationSettings settings(old_layer->GetAnimator());
+
+ // Animation observer owns the old layer and deletes itself.
+ settings.AddObserver(new CrossFadeObserver(window, old_layer));
+ settings.SetTransitionDuration(duration);
+ settings.SetTweenType(tween_type);
+ gfx::Transform out_transform;
+ float scale_x = static_cast<float>(new_bounds.width()) /
+ static_cast<float>(old_bounds.width());
+ float scale_y = static_cast<float>(new_bounds.height()) /
+ static_cast<float>(old_bounds.height());
+ out_transform.Translate(new_bounds.x() - old_bounds.x(),
+ new_bounds.y() - old_bounds.y());
+ out_transform.Scale(scale_x, scale_y);
+ old_layer->SetTransform(out_transform);
+ if (old_on_top) {
+ // The old layer is on top, and should fade out. The new layer below will
+ // stay opaque to block the desktop.
+ old_layer->SetOpacity(kWindowAnimation_HideOpacity);
+ }
+ // In tests |old_layer| is deleted here, as animations have zero duration.
+ old_layer = NULL;
+ }
+
+ // Set the new layer's current transform, such that the user sees a scaled
+ // version of the window with the original bounds at the original position.
+ gfx::Transform in_transform;
+ const float scale_x = static_cast<float>(old_bounds.width()) /
+ static_cast<float>(new_bounds.width());
+ const float scale_y = static_cast<float>(old_bounds.height()) /
+ static_cast<float>(new_bounds.height());
+ in_transform.Translate(old_bounds.x() - new_bounds.x(),
+ old_bounds.y() - new_bounds.y());
+ in_transform.Scale(scale_x, scale_y);
+ window->layer()->SetTransform(in_transform);
+ if (!old_on_top) {
+ // The new layer is on top and should fade in. The old layer below will
+ // stay opaque and block the desktop.
+ window->layer()->SetOpacity(kWindowAnimation_HideOpacity);
+ }
+ {
+ // Animate the new layer to the identity transform, so the window goes to
+ // its newly set bounds.
+ ui::ScopedLayerAnimationSettings settings(window->layer()->GetAnimator());
+ settings.SetTransitionDuration(duration);
+ settings.SetTweenType(tween_type);
+ window->layer()->SetTransform(gfx::Transform());
+ if (!old_on_top) {
+ // New layer is on top, fade it in.
+ window->layer()->SetOpacity(kWindowAnimation_ShowOpacity);
+ }
+ }
+ return duration;
+}
+
+void CrossFadeToBounds(aura::Window* window, const gfx::Rect& new_bounds) {
+ // Some test results in invoking CrossFadeToBounds when window is not visible.
+ // No animation is necessary in that case, thus just change the bounds and
+ // quit.
+ if (!window->TargetVisibility()) {
+ window->SetBounds(new_bounds);
+ return;
+ }
+
+ const gfx::Rect old_bounds = window->bounds();
+
+ // Create fresh layers for the window and all its children to paint into.
+ // Takes ownership of the old layer and all its children, which will be
+ // cleaned up after the animation completes.
+ // Specify |set_bounds| to true here to keep the old bounds in the child
+ // windows of |window|.
+ ui::Layer* old_layer = views::corewm::RecreateWindowLayers(window, true);
+ ui::Layer* new_layer = window->layer();
+
+ // Resize the window to the new size, which will force a layout and paint.
+ window->SetBounds(new_bounds);
+
+ // Ensure the higher-resolution layer is on top.
+ bool old_on_top = (old_bounds.width() > new_bounds.width());
+ if (old_on_top)
+ old_layer->parent()->StackBelow(new_layer, old_layer);
+ else
+ old_layer->parent()->StackAbove(new_layer, old_layer);
+
+ CrossFadeImpl(window, old_layer, ui::Tween::EASE_OUT);
+}
+
+void CrossFadeWindowBetweenWorkspaces(aura::Window* new_workspace,
+ aura::Window* window,
+ ui::Layer* old_layer) {
+ ui::Layer* layer_parent = new_workspace->layer()->parent();
+ layer_parent->Add(old_layer);
+ const bool restoring = old_layer->bounds().width() > window->bounds().width();
+ if (restoring)
+ layer_parent->StackAbove(old_layer, new_workspace->layer());
+ else
+ layer_parent->StackBelow(old_layer, new_workspace->layer());
+
+ CrossFadeImpl(window, old_layer, kCrossFadeTweenType);
+}
+
+base::TimeDelta GetCrossFadeDuration(const gfx::Rect& old_bounds,
+ const gfx::Rect& new_bounds) {
+ if (views::corewm::WindowAnimationsDisabled(NULL))
+ return base::TimeDelta();
+
+ int old_area = old_bounds.width() * old_bounds.height();
+ int new_area = new_bounds.width() * new_bounds.height();
+ int max_area = std::max(old_area, new_area);
+ // Avoid divide by zero.
+ if (max_area == 0)
+ return base::TimeDelta::FromMilliseconds(kCrossFadeDurationMS);
+
+ int delta_area = std::abs(old_area - new_area);
+ // If the area didn't change, the animation is instantaneous.
+ if (delta_area == 0)
+ return base::TimeDelta::FromMilliseconds(kCrossFadeDurationMS);
+
+ float factor =
+ static_cast<float>(delta_area) / static_cast<float>(max_area);
+ const float kRange = kCrossFadeDurationMaxMs - kCrossFadeDurationMinMs;
+ return base::TimeDelta::FromMilliseconds(
+ Round64(kCrossFadeDurationMinMs + (factor * kRange)));
+}
+
+bool AnimateOnChildWindowVisibilityChanged(aura::Window* window, bool visible) {
+ if (views::corewm::WindowAnimationsDisabled(window))
+ return false;
+
+ // Attempt to run CoreWm supplied animation types.
+ if (views::corewm::AnimateOnChildWindowVisibilityChanged(window, visible))
+ return true;
+
+ // Otherwise try to run an Ash-specific animation.
+ if (visible)
+ return AnimateShowWindow(window);
+ // Don't start hiding the window again if it's already being hidden.
+ return window->layer()->GetTargetOpacity() != 0.0f &&
+ AnimateHideWindow(window);
+}
+
+std::vector<ui::LayerAnimationSequence*>
+CreateBrightnessGrayscaleAnimationSequence(float target_value,
+ base::TimeDelta duration) {
+ ui::Tween::Type animation_type = ui::Tween::EASE_OUT;
+ scoped_ptr<ui::LayerAnimationSequence> brightness_sequence(
+ new ui::LayerAnimationSequence());
+ scoped_ptr<ui::LayerAnimationSequence> grayscale_sequence(
+ new ui::LayerAnimationSequence());
+
+ scoped_ptr<ui::LayerAnimationElement> brightness_element(
+ ui::LayerAnimationElement::CreateBrightnessElement(
+ target_value, duration));
+ brightness_element->set_tween_type(animation_type);
+ brightness_sequence->AddElement(brightness_element.release());
+
+ scoped_ptr<ui::LayerAnimationElement> grayscale_element(
+ ui::LayerAnimationElement::CreateGrayscaleElement(
+ target_value, duration));
+ grayscale_element->set_tween_type(animation_type);
+ grayscale_sequence->AddElement(grayscale_element.release());
+
+ std::vector<ui::LayerAnimationSequence*> animations;
+ animations.push_back(brightness_sequence.release());
+ animations.push_back(grayscale_sequence.release());
+
+ return animations;
+}
+
+// Returns scale related to the specified AshWindowScaleType.
+void SetTransformForScaleAnimation(ui::Layer* layer,
+ LayerScaleAnimationDirection type) {
+ const float scale =
+ type == LAYER_SCALE_ANIMATION_ABOVE ? kLayerScaleAboveSize :
+ kLayerScaleBelowSize;
+ gfx::Transform transform;
+ transform.Translate(-layer->bounds().width() * (scale - 1.0f) / 2,
+ -layer->bounds().height() * (scale - 1.0f) / 2);
+ transform.Scale(scale, scale);
+ layer->SetTransform(transform);
+}
+
+gfx::Rect GetMinimizeAnimationTargetBoundsInScreen(aura::Window* window) {
+ Launcher* launcher = Launcher::ForWindow(window);
+ // Launcher is created lazily and can be NULL.
+ if (!launcher)
+ return gfx::Rect();
+ gfx::Rect item_rect = launcher->
+ GetScreenBoundsOfItemIconForWindow(window);
+
+ // The launcher item is visible and has an icon.
+ if (!item_rect.IsEmpty())
+ return item_rect;
+
+ // If both the icon width and height are 0, then there is no icon in the
+ // launcher for |window| or the icon is hidden in the overflow menu. If the
+ // launcher is auto hidden, one of the height or width will be 0 but the
+ // position in the launcher and the major dimension are still reported
+ // correctly and the window can be animated to the launcher item's light
+ // bar.
+ if (item_rect.width() != 0 || item_rect.height() != 0) {
+ internal::ShelfLayoutManager* layout_manager =
+ internal::ShelfLayoutManager::ForLauncher(window);
+ if (layout_manager->visibility_state() == SHELF_AUTO_HIDE) {
+ gfx::Rect shelf_bounds =
+ launcher->shelf_widget()->GetWindowBoundsInScreen();
+ switch (layout_manager->GetAlignment()) {
+ case SHELF_ALIGNMENT_BOTTOM:
+ item_rect.set_y(shelf_bounds.y());
+ break;
+ case SHELF_ALIGNMENT_LEFT:
+ item_rect.set_x(shelf_bounds.right());
+ break;
+ case SHELF_ALIGNMENT_RIGHT:
+ item_rect.set_x(shelf_bounds.x());
+ break;
+ case SHELF_ALIGNMENT_TOP:
+ item_rect.set_y(shelf_bounds.bottom());
+ break;
+ }
+ return item_rect;
+ }
+ }
+
+ // Assume the launcher is overflowed, zoom off to the bottom right of the
+ // work area.
+ gfx::Rect work_area =
+ Shell::GetScreen()->GetDisplayNearestWindow(window).work_area();
+ return gfx::Rect(work_area.right(), work_area.bottom(), 0, 0);
+}
+
+} // namespace ash
diff --git a/chromium/ash/wm/window_animations.h b/chromium/ash/wm/window_animations.h
new file mode 100644
index 00000000000..b0e4a2efec4
--- /dev/null
+++ b/chromium/ash/wm/window_animations.h
@@ -0,0 +1,83 @@
+// Copyright (c) 2012 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 ASH_WM_WINDOW_ANIMATIONS_H_
+#define ASH_WM_WINDOW_ANIMATIONS_H_
+
+#include "ash/ash_export.h"
+#include "ui/gfx/transform.h"
+#include "ui/views/corewm/window_animations.h"
+
+namespace aura {
+class Window;
+}
+namespace ui {
+class Layer;
+}
+
+// This is only for animations specific to Ash. For window animations shared
+// with desktop Chrome, see ui/views/corewm/window_animations.h.
+namespace ash {
+
+// An extension of the window animations provided by CoreWm. These should be
+// Ash-specific only.
+enum WindowVisibilityAnimationType {
+ // Window scale/rotates down to its launcher icon.
+ WINDOW_VISIBILITY_ANIMATION_TYPE_MINIMIZE =
+ views::corewm::WINDOW_VISIBILITY_ANIMATION_MAX,
+ // Fade in/out using brightness and grayscale web filters.
+ WINDOW_VISIBILITY_ANIMATION_TYPE_BRIGHTNESS_GRAYSCALE
+};
+
+// Direction for ash-specific window animations used in workspaces and
+// lock/unlock animations.
+enum LayerScaleAnimationDirection {
+ LAYER_SCALE_ANIMATION_ABOVE,
+ LAYER_SCALE_ANIMATION_BELOW,
+};
+
+// Amount of time for the cross fade animation.
+extern const int kCrossFadeDurationMS;
+
+// Animate a cross-fade of |window| from its current bounds to |new_bounds|.
+ASH_EXPORT void CrossFadeToBounds(aura::Window* window,
+ const gfx::Rect& new_bounds);
+
+// Cross fades |layer| (which is a clone of |window|s layer before it was
+// resized) to |window|s current bounds. |new_workspace| is the Window of the
+// workspace |window| was added to.
+// This takes ownership of |layer|.
+ASH_EXPORT void CrossFadeWindowBetweenWorkspaces(aura::Window* new_workspace,
+ aura::Window* window,
+ ui::Layer* layer);
+
+// Returns the duration of the cross-fade animation based on the |old_bounds|
+// and |new_bounds| of the window.
+ASH_EXPORT base::TimeDelta GetCrossFadeDuration(const gfx::Rect& old_bounds,
+ const gfx::Rect& new_bounds);
+
+ASH_EXPORT bool AnimateOnChildWindowVisibilityChanged(aura::Window* window,
+ bool visible);
+
+// Creates vector of animation sequences that lasts for |duration| and changes
+// brightness and grayscale to |target_value|. Caller takes ownership of
+// returned LayerAnimationSequence objects.
+ASH_EXPORT std::vector<ui::LayerAnimationSequence*>
+CreateBrightnessGrayscaleAnimationSequence(float target_value,
+ base::TimeDelta duration);
+
+// Applies scale related to the specified AshWindowScaleType.
+ASH_EXPORT void SetTransformForScaleAnimation(
+ ui::Layer* layer,
+ LayerScaleAnimationDirection type);
+
+// Returns the approximate bounds to which |window| will be animated when it
+// is minimized. The bounds are approximate because the minimize animation
+// involves rotation.
+ASH_EXPORT gfx::Rect GetMinimizeAnimationTargetBoundsInScreen(
+ aura::Window* window);
+
+} // namespace ash
+
+#endif // ASH_WM_WINDOW_ANIMATIONS_H_
diff --git a/chromium/ash/wm/window_animations_unittest.cc b/chromium/ash/wm/window_animations_unittest.cc
new file mode 100644
index 00000000000..d5680cc5bcd
--- /dev/null
+++ b/chromium/ash/wm/window_animations_unittest.cc
@@ -0,0 +1,137 @@
+// Copyright (c) 2012 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.
+
+#include "ash/wm/window_animations.h"
+
+#include "ash/shell_window_ids.h"
+#include "ash/test/ash_test_base.h"
+#include "ash/wm/workspace_controller.h"
+#include "base/time/time.h"
+#include "ui/aura/test/test_windows.h"
+#include "ui/aura/window.h"
+#include "ui/base/animation/animation_container_element.h"
+#include "ui/compositor/layer.h"
+#include "ui/compositor/layer_animator.h"
+#include "ui/compositor/scoped_animation_duration_scale_mode.h"
+
+using aura::Window;
+using ui::Layer;
+
+namespace ash {
+namespace internal {
+
+class WindowAnimationsTest : public ash::test::AshTestBase {
+ public:
+ WindowAnimationsTest() {}
+
+ virtual void TearDown() OVERRIDE {
+ AshTestBase::TearDown();
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(WindowAnimationsTest);
+};
+
+TEST_F(WindowAnimationsTest, HideShowBrightnessGrayscaleAnimation) {
+ scoped_ptr<aura::Window> window(CreateTestWindowInShellWithId(0));
+ window->Show();
+ EXPECT_TRUE(window->layer()->visible());
+
+ // Hiding.
+ views::corewm::SetWindowVisibilityAnimationType(
+ window.get(),
+ WINDOW_VISIBILITY_ANIMATION_TYPE_BRIGHTNESS_GRAYSCALE);
+ AnimateOnChildWindowVisibilityChanged(window.get(), false);
+ EXPECT_EQ(0.0f, window->layer()->GetTargetOpacity());
+ EXPECT_FALSE(window->layer()->GetTargetVisibility());
+ EXPECT_FALSE(window->layer()->visible());
+
+ // Showing.
+ views::corewm::SetWindowVisibilityAnimationType(
+ window.get(),
+ WINDOW_VISIBILITY_ANIMATION_TYPE_BRIGHTNESS_GRAYSCALE);
+ AnimateOnChildWindowVisibilityChanged(window.get(), true);
+ EXPECT_EQ(0.0f, window->layer()->GetTargetBrightness());
+ EXPECT_EQ(0.0f, window->layer()->GetTargetGrayscale());
+ EXPECT_TRUE(window->layer()->visible());
+
+ // Stays shown.
+ ui::AnimationContainerElement* element =
+ static_cast<ui::AnimationContainerElement*>(
+ window->layer()->GetAnimator());
+ element->Step(base::TimeTicks::Now() +
+ base::TimeDelta::FromSeconds(5));
+ EXPECT_EQ(0.0f, window->layer()->GetTargetBrightness());
+ EXPECT_EQ(0.0f, window->layer()->GetTargetGrayscale());
+ EXPECT_TRUE(window->layer()->visible());
+}
+
+TEST_F(WindowAnimationsTest, LayerTargetVisibility) {
+ scoped_ptr<aura::Window> window(CreateTestWindowInShellWithId(0));
+
+ // Layer target visibility changes according to Show/Hide.
+ window->Show();
+ EXPECT_TRUE(window->layer()->GetTargetVisibility());
+ window->Hide();
+ EXPECT_FALSE(window->layer()->GetTargetVisibility());
+ window->Show();
+ EXPECT_TRUE(window->layer()->GetTargetVisibility());
+}
+
+TEST_F(WindowAnimationsTest, CrossFadeToBounds) {
+ ui::ScopedAnimationDurationScaleMode normal_duration_mode(
+ ui::ScopedAnimationDurationScaleMode::NORMAL_DURATION);
+
+ scoped_ptr<Window> window(CreateTestWindowInShellWithId(0));
+ window->SetBounds(gfx::Rect(5, 10, 320, 240));
+ window->Show();
+
+ Layer* old_layer = window->layer();
+ EXPECT_EQ(1.0f, old_layer->GetTargetOpacity());
+
+ // Cross fade to a larger size, as in a maximize animation.
+ CrossFadeToBounds(window.get(), gfx::Rect(0, 0, 640, 480));
+ // Window's layer has been replaced.
+ EXPECT_NE(old_layer, window->layer());
+ // Original layer stays opaque and stretches to new size.
+ EXPECT_EQ(1.0f, old_layer->GetTargetOpacity());
+ EXPECT_EQ("5,10 320x240", old_layer->bounds().ToString());
+ gfx::Transform grow_transform;
+ grow_transform.Translate(-5.f, -10.f);
+ grow_transform.Scale(640.f / 320.f, 480.f / 240.f);
+ EXPECT_EQ(grow_transform, old_layer->GetTargetTransform());
+ // New layer animates in to the identity transform.
+ EXPECT_EQ(1.0f, window->layer()->GetTargetOpacity());
+ EXPECT_EQ(gfx::Transform(), window->layer()->GetTargetTransform());
+
+ // Run the animations to completion.
+ static_cast<ui::AnimationContainerElement*>(old_layer->GetAnimator())->Step(
+ base::TimeTicks::Now() + base::TimeDelta::FromSeconds(1));
+ static_cast<ui::AnimationContainerElement*>(window->layer()->GetAnimator())->
+ Step(base::TimeTicks::Now() + base::TimeDelta::FromSeconds(1));
+
+ // Cross fade to a smaller size, as in a restore animation.
+ old_layer = window->layer();
+ CrossFadeToBounds(window.get(), gfx::Rect(5, 10, 320, 240));
+ // Again, window layer has been replaced.
+ EXPECT_NE(old_layer, window->layer());
+ // Original layer fades out and stretches down to new size.
+ EXPECT_EQ(0.0f, old_layer->GetTargetOpacity());
+ EXPECT_EQ("0,0 640x480", old_layer->bounds().ToString());
+ gfx::Transform shrink_transform;
+ shrink_transform.Translate(5.f, 10.f);
+ shrink_transform.Scale(320.f / 640.f, 240.f / 480.f);
+ EXPECT_EQ(shrink_transform, old_layer->GetTargetTransform());
+ // New layer animates in to the identity transform.
+ EXPECT_EQ(1.0f, window->layer()->GetTargetOpacity());
+ EXPECT_EQ(gfx::Transform(), window->layer()->GetTargetTransform());
+
+ static_cast<ui::AnimationContainerElement*>(old_layer->GetAnimator())->Step(
+ base::TimeTicks::Now() + base::TimeDelta::FromSeconds(1));
+ static_cast<ui::AnimationContainerElement*>(window->layer()->GetAnimator())->
+ Step(base::TimeTicks::Now() + base::TimeDelta::FromSeconds(1));
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/wm/window_cycle_controller.cc b/chromium/ash/wm/window_cycle_controller.cc
new file mode 100644
index 00000000000..413d0da958d
--- /dev/null
+++ b/chromium/ash/wm/window_cycle_controller.cc
@@ -0,0 +1,139 @@
+// Copyright (c) 2012 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.
+
+#include "ash/wm/window_cycle_controller.h"
+
+#include <algorithm>
+
+#include "ash/session_state_delegate.h"
+#include "ash/shell.h"
+#include "ash/shell_window_ids.h"
+#include "ash/wm/mru_window_tracker.h"
+#include "ash/wm/window_cycle_list.h"
+#include "ash/wm/window_util.h"
+#include "ash/wm/workspace_controller.h"
+#include "ui/aura/root_window.h"
+#include "ui/base/events/event.h"
+#include "ui/base/events/event_handler.h"
+
+namespace ash {
+
+namespace {
+
+// Filter to watch for the termination of a keyboard gesture to cycle through
+// multiple windows.
+class WindowCycleEventFilter : public ui::EventHandler {
+ public:
+ WindowCycleEventFilter();
+ virtual ~WindowCycleEventFilter();
+
+ // Overridden from ui::EventHandler:
+ virtual void OnKeyEvent(ui::KeyEvent* event) OVERRIDE;
+ private:
+ DISALLOW_COPY_AND_ASSIGN(WindowCycleEventFilter);
+};
+
+// Watch for all keyboard events by filtering the root window.
+WindowCycleEventFilter::WindowCycleEventFilter() {
+}
+
+WindowCycleEventFilter::~WindowCycleEventFilter() {
+}
+
+void WindowCycleEventFilter::OnKeyEvent(ui::KeyEvent* event) {
+ // Views uses VKEY_MENU for both left and right Alt keys.
+ if (event->key_code() == ui::VKEY_MENU &&
+ event->type() == ui::ET_KEY_RELEASED) {
+ Shell::GetInstance()->window_cycle_controller()->AltKeyReleased();
+ // Warning: |this| will be deleted from here on.
+ }
+}
+
+} // namespace
+
+//////////////////////////////////////////////////////////////////////////////
+// WindowCycleController, public:
+
+WindowCycleController::WindowCycleController() {
+}
+
+WindowCycleController::~WindowCycleController() {
+ StopCycling();
+}
+
+// static
+bool WindowCycleController::CanCycle() {
+ // Don't allow window cycling if the screen is locked or a modal dialog is
+ // open.
+ return !Shell::GetInstance()->session_state_delegate()->IsScreenLocked() &&
+ !Shell::GetInstance()->IsSystemModalWindowOpen();
+}
+
+void WindowCycleController::HandleCycleWindow(Direction direction,
+ bool is_alt_down) {
+ if (!CanCycle())
+ return;
+
+ if (is_alt_down) {
+ if (!IsCycling()) {
+ // This is the start of an alt-tab cycle through multiple windows, so
+ // listen for the alt key being released to stop cycling.
+ StartCycling();
+ Step(direction);
+ InstallEventFilter();
+ } else {
+ // We're in the middle of an alt-tab cycle, just step forward.
+ Step(direction);
+ }
+ } else {
+ // This is a simple, single-step window cycle.
+ StartCycling();
+ Step(direction);
+ StopCycling();
+ }
+}
+
+void WindowCycleController::HandleLinearCycleWindow() {
+ if (!CanCycle() || IsCycling())
+ return;
+
+ // Use the reversed list of windows to prevent a 2-cycle of the most recent
+ // windows occurring.
+ WindowCycleList cycle_list(MruWindowTracker::BuildWindowList(true));
+ cycle_list.Step(WindowCycleList::FORWARD);
+}
+
+void WindowCycleController::AltKeyReleased() {
+ StopCycling();
+}
+
+//////////////////////////////////////////////////////////////////////////////
+// WindowCycleController, private:
+
+void WindowCycleController::StartCycling() {
+ windows_.reset(new WindowCycleList(ash::Shell::GetInstance()->
+ mru_window_tracker()->BuildMruWindowList()));
+}
+
+void WindowCycleController::Step(Direction direction) {
+ DCHECK(windows_.get());
+ windows_->Step(direction == FORWARD ? WindowCycleList::FORWARD :
+ WindowCycleList::BACKWARD);
+}
+
+void WindowCycleController::StopCycling() {
+ windows_.reset();
+ // Remove our key event filter.
+ if (event_handler_) {
+ Shell::GetInstance()->RemovePreTargetHandler(event_handler_.get());
+ event_handler_.reset();
+ }
+}
+
+void WindowCycleController::InstallEventFilter() {
+ event_handler_.reset(new WindowCycleEventFilter());
+ Shell::GetInstance()->AddPreTargetHandler(event_handler_.get());
+}
+
+} // namespace ash
diff --git a/chromium/ash/wm/window_cycle_controller.h b/chromium/ash/wm/window_cycle_controller.h
new file mode 100644
index 00000000000..12822c0e0fa
--- /dev/null
+++ b/chromium/ash/wm/window_cycle_controller.h
@@ -0,0 +1,82 @@
+// Copyright (c) 2012 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 ASH_WM_WINDOW_CYCLE_CONTROLLER_H_
+#define ASH_WM_WINDOW_CYCLE_CONTROLLER_H_
+
+#include "ash/ash_export.h"
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+
+namespace ui {
+class EventHandler;
+}
+
+namespace ash {
+
+class WindowCycleList;
+
+// Controls cycling through windows with the keyboard, for example, via alt-tab.
+// Windows are sorted primarily by most recently used, and then by screen order.
+// We activate windows as you cycle through them, so the order on the screen
+// may change during the gesture, but the most recently used list isn't updated
+// until the cycling ends. Thus we maintain the state of the windows
+// at the beginning of the gesture so you can cycle through in a consistent
+// order.
+class ASH_EXPORT WindowCycleController {
+ public:
+ enum Direction {
+ FORWARD,
+ BACKWARD
+ };
+ WindowCycleController();
+ virtual ~WindowCycleController();
+
+ // Returns true if cycling through windows is enabled. This is false at
+ // certain times, such as when the lock screen is visible.
+ static bool CanCycle();
+
+ // Cycles between windows in the given |direction|. If |is_alt_down| then
+ // interprets this call as the start of a multi-step cycle sequence and
+ // installs a key filter to watch for alt being released.
+ void HandleCycleWindow(Direction direction, bool is_alt_down);
+
+ // Cycles between windows without maintaining a multi-step cycle sequence
+ // (see above).
+ void HandleLinearCycleWindow();
+
+ // Informs the controller that the Alt key has been released and it can
+ // terminate the existing multi-step cycle.
+ void AltKeyReleased();
+
+ // Returns true if we are in the middle of a window cycling gesture.
+ bool IsCycling() const { return windows_.get() != NULL; }
+
+ // Returns the WindowCycleList. Really only useful for testing.
+ const WindowCycleList* windows() const { return windows_.get(); }
+
+ private:
+ // Call to start cycling windows. You must call StopCycling() when done.
+ void StartCycling();
+
+ // Cycles to the next or previous window based on |direction|.
+ void Step(Direction direction);
+
+ // Installs an event filter to watch for release of the alt key.
+ void InstallEventFilter();
+
+ // Stops the current window cycle and cleans up the event filter.
+ void StopCycling();
+
+ scoped_ptr<WindowCycleList> windows_;
+
+ // Event handler to watch for release of alt key.
+ scoped_ptr<ui::EventHandler> event_handler_;
+
+ DISALLOW_COPY_AND_ASSIGN(WindowCycleController);
+};
+
+} // namespace ash
+
+#endif // ASH_WM_WINDOW_CYCLE_CONTROLLER_H_
diff --git a/chromium/ash/wm/window_cycle_controller_unittest.cc b/chromium/ash/wm/window_cycle_controller_unittest.cc
new file mode 100644
index 00000000000..8e745434254
--- /dev/null
+++ b/chromium/ash/wm/window_cycle_controller_unittest.cc
@@ -0,0 +1,442 @@
+// Copyright (c) 2012 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.
+
+#include "ash/wm/window_cycle_controller.h"
+
+#include <algorithm>
+
+#include "ash/session_state_delegate.h"
+#include "ash/shell.h"
+#include "ash/shell_window_ids.h"
+#include "ash/test/ash_test_base.h"
+#include "ash/test/test_shell_delegate.h"
+#include "ash/wm/window_cycle_list.h"
+#include "ash/wm/window_util.h"
+#include "base/memory/scoped_ptr.h"
+#include "ui/aura/client/aura_constants.h"
+#include "ui/aura/client/screen_position_client.h"
+#include "ui/aura/env.h"
+#include "ui/aura/test/test_windows.h"
+#include "ui/aura/window.h"
+#include "ui/gfx/rect.h"
+#include "ui/gfx/screen.h"
+
+namespace ash {
+
+namespace {
+
+using aura::test::CreateTestWindowWithId;
+using aura::test::TestWindowDelegate;
+using aura::Window;
+
+typedef test::AshTestBase WindowCycleControllerTest;
+
+TEST_F(WindowCycleControllerTest, HandleCycleWindowBaseCases) {
+ WindowCycleController* controller =
+ Shell::GetInstance()->window_cycle_controller();
+
+ // Cycling doesn't crash if there are no windows.
+ controller->HandleCycleWindow(WindowCycleController::FORWARD, false);
+
+ // Create a single test window.
+ scoped_ptr<Window> window0(CreateTestWindowInShellWithId(0));
+ wm::ActivateWindow(window0.get());
+ EXPECT_TRUE(wm::IsActiveWindow(window0.get()));
+
+ // Cycling works for a single window, even though nothing changes.
+ controller->HandleCycleWindow(WindowCycleController::FORWARD, false);
+ EXPECT_TRUE(wm::IsActiveWindow(window0.get()));
+}
+
+// Verifies if there is only one window and it isn't active that cycling
+// activates it.
+TEST_F(WindowCycleControllerTest, SingleWindowNotActive) {
+ WindowCycleController* controller =
+ Shell::GetInstance()->window_cycle_controller();
+
+ // Create a single test window.
+ scoped_ptr<Window> window0(CreateTestWindowInShellWithId(0));
+ wm::ActivateWindow(window0.get());
+ EXPECT_TRUE(wm::IsActiveWindow(window0.get()));
+
+ // Rotate focus, this should move focus to another window that isn't part of
+ // the default container.
+ Shell::GetInstance()->RotateFocus(Shell::FORWARD);
+ EXPECT_FALSE(wm::IsActiveWindow(window0.get()));
+
+ // Cycling should activate the window.
+ controller->HandleCycleWindow(WindowCycleController::FORWARD, false);
+ EXPECT_TRUE(wm::IsActiveWindow(window0.get()));
+}
+
+TEST_F(WindowCycleControllerTest, HandleCycleWindow) {
+ WindowCycleController* controller =
+ Shell::GetInstance()->window_cycle_controller();
+
+ // Set up several windows to use to test cycling. Create them in reverse
+ // order so they are stacked 0 over 1 over 2.
+ scoped_ptr<Window> window2(CreateTestWindowInShellWithId(2));
+ scoped_ptr<Window> window1(CreateTestWindowInShellWithId(1));
+ scoped_ptr<Window> window0(CreateTestWindowInShellWithId(0));
+ wm::ActivateWindow(window0.get());
+
+ // Simulate pressing and releasing Alt-tab.
+ EXPECT_TRUE(wm::IsActiveWindow(window0.get()));
+ controller->HandleCycleWindow(WindowCycleController::FORWARD, true);
+
+ // Window lists should return the topmost window in front.
+ ASSERT_TRUE(controller->windows());
+ ASSERT_EQ(3u, controller->windows()->windows().size());
+ ASSERT_EQ(window0.get(), controller->windows()->windows()[0]);
+ ASSERT_EQ(window1.get(), controller->windows()->windows()[1]);
+ ASSERT_EQ(window2.get(), controller->windows()->windows()[2]);
+
+ controller->AltKeyReleased();
+ EXPECT_TRUE(wm::IsActiveWindow(window1.get()));
+
+ // Pressing and releasing Alt-tab again should cycle back to the most-
+ // recently-used window in the current child order.
+ controller->HandleCycleWindow(WindowCycleController::FORWARD, true);
+ controller->AltKeyReleased();
+ EXPECT_TRUE(wm::IsActiveWindow(window0.get()));
+
+ // Pressing Alt-tab multiple times without releasing Alt should cycle through
+ // all the windows and wrap around.
+ controller->HandleCycleWindow(WindowCycleController::FORWARD, true);
+ EXPECT_TRUE(controller->IsCycling());
+ EXPECT_TRUE(wm::IsActiveWindow(window1.get()));
+
+ controller->HandleCycleWindow(WindowCycleController::FORWARD, true);
+ EXPECT_TRUE(controller->IsCycling());
+ EXPECT_TRUE(wm::IsActiveWindow(window2.get()));
+
+ controller->HandleCycleWindow(WindowCycleController::FORWARD, true);
+ EXPECT_TRUE(controller->IsCycling());
+ EXPECT_TRUE(wm::IsActiveWindow(window0.get()));
+
+ controller->AltKeyReleased();
+ EXPECT_FALSE(controller->IsCycling());
+ EXPECT_TRUE(wm::IsActiveWindow(window0.get()));
+
+ // Reset our stacking order.
+ wm::ActivateWindow(window2.get());
+ wm::ActivateWindow(window1.get());
+ wm::ActivateWindow(window0.get());
+
+ // Likewise we can cycle backwards through all the windows.
+ controller->HandleCycleWindow(WindowCycleController::BACKWARD, true);
+ EXPECT_TRUE(wm::IsActiveWindow(window2.get()));
+ controller->HandleCycleWindow(WindowCycleController::BACKWARD, true);
+ EXPECT_TRUE(wm::IsActiveWindow(window1.get()));
+ controller->HandleCycleWindow(WindowCycleController::BACKWARD, true);
+ EXPECT_TRUE(wm::IsActiveWindow(window0.get()));
+ controller->AltKeyReleased();
+ EXPECT_TRUE(wm::IsActiveWindow(window0.get()));
+
+ // Passing false for is_alt_down does not start a cycle gesture.
+ controller->HandleCycleWindow(WindowCycleController::FORWARD, false);
+ EXPECT_FALSE(controller->IsCycling());
+ EXPECT_TRUE(wm::IsActiveWindow(window1.get()));
+
+ controller->HandleCycleWindow(WindowCycleController::FORWARD, false);
+ EXPECT_FALSE(controller->IsCycling());
+ EXPECT_TRUE(wm::IsActiveWindow(window0.get()));
+
+ // When the screen is locked, cycling window does not take effect.
+ Shell::GetInstance()->session_state_delegate()->LockScreen();
+ EXPECT_TRUE(wm::IsActiveWindow(window0.get()));
+ controller->HandleCycleWindow(WindowCycleController::FORWARD, false);
+ EXPECT_TRUE(wm::IsActiveWindow(window0.get()));
+ controller->HandleCycleWindow(WindowCycleController::BACKWARD, false);
+ EXPECT_TRUE(wm::IsActiveWindow(window0.get()));
+
+ Shell::GetInstance()->session_state_delegate()->UnlockScreen();
+ EXPECT_TRUE(wm::IsActiveWindow(window0.get()));
+ controller->HandleCycleWindow(WindowCycleController::FORWARD, false);
+ EXPECT_TRUE(wm::IsActiveWindow(window1.get()));
+ controller->HandleCycleWindow(WindowCycleController::FORWARD, false);
+ EXPECT_TRUE(wm::IsActiveWindow(window0.get()));
+
+ // When a modal window is active, cycling window does not take effect.
+ aura::Window* modal_container =
+ ash::Shell::GetContainer(
+ Shell::GetPrimaryRootWindow(),
+ internal::kShellWindowId_SystemModalContainer);
+ scoped_ptr<Window> modal_window(
+ CreateTestWindowWithId(-2, modal_container));
+ modal_window->SetProperty(aura::client::kModalKey, ui::MODAL_TYPE_SYSTEM);
+ wm::ActivateWindow(modal_window.get());
+ EXPECT_TRUE(wm::IsActiveWindow(modal_window.get()));
+ controller->HandleCycleWindow(WindowCycleController::FORWARD, false);
+ EXPECT_TRUE(wm::IsActiveWindow(modal_window.get()));
+ EXPECT_FALSE(wm::IsActiveWindow(window0.get()));
+ EXPECT_FALSE(wm::IsActiveWindow(window1.get()));
+ EXPECT_FALSE(wm::IsActiveWindow(window2.get()));
+ controller->HandleCycleWindow(WindowCycleController::BACKWARD, false);
+ EXPECT_TRUE(wm::IsActiveWindow(modal_window.get()));
+ EXPECT_FALSE(wm::IsActiveWindow(window0.get()));
+ EXPECT_FALSE(wm::IsActiveWindow(window1.get()));
+ EXPECT_FALSE(wm::IsActiveWindow(window2.get()));
+}
+
+// Cycles between a maximized and normal window.
+TEST_F(WindowCycleControllerTest, MaximizedWindow) {
+ // Create a couple of test windows.
+ scoped_ptr<Window> window0(CreateTestWindowInShellWithId(0));
+ scoped_ptr<Window> window1(CreateTestWindowInShellWithId(1));
+
+ wm::MaximizeWindow(window1.get());
+ wm::ActivateWindow(window1.get());
+ EXPECT_TRUE(wm::IsActiveWindow(window1.get()));
+
+ // Rotate focus, this should move focus to window0.
+ WindowCycleController* controller =
+ Shell::GetInstance()->window_cycle_controller();
+ controller->HandleCycleWindow(WindowCycleController::FORWARD, false);
+ EXPECT_TRUE(wm::IsActiveWindow(window0.get()));
+
+ // One more time.
+ controller->HandleCycleWindow(WindowCycleController::FORWARD, false);
+ EXPECT_TRUE(wm::IsActiveWindow(window1.get()));
+}
+
+// Cycles to a minimized window.
+TEST_F(WindowCycleControllerTest, Minimized) {
+ // Create a couple of test windows.
+ scoped_ptr<Window> window0(CreateTestWindowInShellWithId(0));
+ scoped_ptr<Window> window1(CreateTestWindowInShellWithId(1));
+
+ wm::MinimizeWindow(window1.get());
+ wm::ActivateWindow(window0.get());
+ EXPECT_TRUE(wm::IsActiveWindow(window0.get()));
+
+ // Rotate focus, this should move focus to window1 and unminimize it.
+ WindowCycleController* controller =
+ Shell::GetInstance()->window_cycle_controller();
+ controller->HandleCycleWindow(WindowCycleController::FORWARD, false);
+ EXPECT_FALSE(wm::IsWindowMinimized(window1.get()));
+ EXPECT_TRUE(wm::IsActiveWindow(window1.get()));
+
+ // One more time back to w0.
+ controller->HandleCycleWindow(WindowCycleController::FORWARD, false);
+ EXPECT_TRUE(wm::IsActiveWindow(window0.get()));
+}
+
+TEST_F(WindowCycleControllerTest, AlwaysOnTopWindow) {
+ WindowCycleController* controller =
+ Shell::GetInstance()->window_cycle_controller();
+
+ // Set up several windows to use to test cycling.
+ scoped_ptr<Window> window0(CreateTestWindowInShellWithId(0));
+ scoped_ptr<Window> window1(CreateTestWindowInShellWithId(1));
+
+ Window* top_container =
+ Shell::GetContainer(
+ Shell::GetPrimaryRootWindow(),
+ internal::kShellWindowId_AlwaysOnTopContainer);
+ scoped_ptr<Window> window2(CreateTestWindowWithId(2, top_container));
+ wm::ActivateWindow(window0.get());
+
+ // Simulate pressing and releasing Alt-tab.
+ EXPECT_TRUE(wm::IsActiveWindow(window0.get()));
+ controller->HandleCycleWindow(WindowCycleController::FORWARD, true);
+
+ // Window lists should return the topmost window in front.
+ ASSERT_TRUE(controller->windows());
+ ASSERT_EQ(3u, controller->windows()->windows().size());
+ EXPECT_EQ(window0.get(), controller->windows()->windows()[0]);
+ EXPECT_EQ(window2.get(), controller->windows()->windows()[1]);
+ EXPECT_EQ(window1.get(), controller->windows()->windows()[2]);
+
+ controller->AltKeyReleased();
+ EXPECT_TRUE(wm::IsActiveWindow(window2.get()));
+
+ controller->HandleCycleWindow(WindowCycleController::FORWARD, true);
+ EXPECT_TRUE(wm::IsActiveWindow(window0.get()));
+
+ controller->AltKeyReleased();
+
+ controller->HandleCycleWindow(WindowCycleController::FORWARD, true);
+ EXPECT_TRUE(wm::IsActiveWindow(window2.get()));
+
+ controller->HandleCycleWindow(WindowCycleController::FORWARD, true);
+ EXPECT_TRUE(wm::IsActiveWindow(window1.get()));
+
+ controller->HandleCycleWindow(WindowCycleController::FORWARD, true);
+ EXPECT_TRUE(wm::IsActiveWindow(window0.get()));
+}
+
+TEST_F(WindowCycleControllerTest, AlwaysOnTopMultiWindow) {
+ WindowCycleController* controller =
+ Shell::GetInstance()->window_cycle_controller();
+
+ // Set up several windows to use to test cycling.
+ scoped_ptr<Window> window0(CreateTestWindowInShellWithId(0));
+ scoped_ptr<Window> window1(CreateTestWindowInShellWithId(1));
+
+ Window* top_container =
+ Shell::GetContainer(
+ Shell::GetPrimaryRootWindow(),
+ internal::kShellWindowId_AlwaysOnTopContainer);
+ scoped_ptr<Window> window2(CreateTestWindowWithId(2, top_container));
+ scoped_ptr<Window> window3(CreateTestWindowWithId(3, top_container));
+ wm::ActivateWindow(window0.get());
+
+ // Simulate pressing and releasing Alt-tab.
+ EXPECT_TRUE(wm::IsActiveWindow(window0.get()));
+ controller->HandleCycleWindow(WindowCycleController::FORWARD, true);
+
+ // Window lists should return the topmost window in front.
+ ASSERT_TRUE(controller->windows());
+ ASSERT_EQ(4u, controller->windows()->windows().size());
+ EXPECT_EQ(window0.get(), controller->windows()->windows()[0]);
+ EXPECT_EQ(window3.get(), controller->windows()->windows()[1]);
+ EXPECT_EQ(window2.get(), controller->windows()->windows()[2]);
+ EXPECT_EQ(window1.get(), controller->windows()->windows()[3]);
+
+ controller->AltKeyReleased();
+ EXPECT_TRUE(wm::IsActiveWindow(window3.get()));
+
+ controller->HandleCycleWindow(WindowCycleController::FORWARD, true);
+ EXPECT_TRUE(wm::IsActiveWindow(window0.get()));
+
+ controller->AltKeyReleased();
+
+ controller->HandleCycleWindow(WindowCycleController::FORWARD, true);
+ EXPECT_TRUE(wm::IsActiveWindow(window3.get()));
+
+ controller->HandleCycleWindow(WindowCycleController::FORWARD, true);
+ EXPECT_TRUE(wm::IsActiveWindow(window2.get()));
+
+ controller->HandleCycleWindow(WindowCycleController::FORWARD, true);
+ EXPECT_TRUE(wm::IsActiveWindow(window1.get()));
+
+ controller->HandleCycleWindow(WindowCycleController::FORWARD, true);
+ EXPECT_TRUE(wm::IsActiveWindow(window0.get()));
+}
+
+TEST_F(WindowCycleControllerTest, AlwaysOnTopMultipleRootWindows) {
+ if (!SupportsMultipleDisplays())
+ return;
+
+ // Set up a second root window
+ UpdateDisplay("1000x600,600x400");
+ Shell::RootWindowList root_windows = Shell::GetAllRootWindows();
+ ASSERT_EQ(2U, root_windows.size());
+
+ WindowCycleController* controller =
+ Shell::GetInstance()->window_cycle_controller();
+
+ Shell::GetInstance()->set_active_root_window(root_windows[0]);
+
+ // Create two windows in the primary root.
+ scoped_ptr<Window> window0(CreateTestWindowInShellWithId(0));
+ EXPECT_EQ(root_windows[0], window0->GetRootWindow());
+ Window* top_container0 =
+ Shell::GetContainer(
+ root_windows[0],
+ internal::kShellWindowId_AlwaysOnTopContainer);
+ scoped_ptr<Window> window1(CreateTestWindowWithId(1, top_container0));
+ EXPECT_EQ(root_windows[0], window1->GetRootWindow());
+
+ // And two on the secondary root.
+ Shell::GetInstance()->set_active_root_window(root_windows[1]);
+ scoped_ptr<Window> window2(CreateTestWindowInShellWithId(2));
+ EXPECT_EQ(root_windows[1], window2->GetRootWindow());
+
+ Window* top_container1 =
+ Shell::GetContainer(
+ root_windows[1],
+ internal::kShellWindowId_AlwaysOnTopContainer);
+ scoped_ptr<Window> window3(CreateTestWindowWithId(3, top_container1));
+ EXPECT_EQ(root_windows[1], window3->GetRootWindow());
+
+ // Move the active root window to the secondary.
+ Shell::GetInstance()->set_active_root_window(root_windows[1]);
+
+ wm::ActivateWindow(window2.get());
+
+ EXPECT_EQ(root_windows[0], window0->GetRootWindow());
+ EXPECT_EQ(root_windows[0], window1->GetRootWindow());
+ EXPECT_EQ(root_windows[1], window2->GetRootWindow());
+ EXPECT_EQ(root_windows[1], window3->GetRootWindow());
+
+ // Simulate pressing and releasing Alt-tab.
+ EXPECT_TRUE(wm::IsActiveWindow(window2.get()));
+ controller->HandleCycleWindow(WindowCycleController::FORWARD, true);
+
+ // Window lists should return the topmost window in front.
+ ASSERT_TRUE(controller->windows());
+ ASSERT_EQ(4u, controller->windows()->windows().size());
+ EXPECT_EQ(window2.get(), controller->windows()->windows()[0]);
+ EXPECT_EQ(window3.get(), controller->windows()->windows()[1]);
+ EXPECT_EQ(window1.get(), controller->windows()->windows()[2]);
+ EXPECT_EQ(window0.get(), controller->windows()->windows()[3]);
+
+ controller->AltKeyReleased();
+ EXPECT_TRUE(wm::IsActiveWindow(window3.get()));
+
+ controller->HandleCycleWindow(WindowCycleController::FORWARD, true);
+ EXPECT_TRUE(wm::IsActiveWindow(window2.get()));
+
+ controller->AltKeyReleased();
+
+ controller->HandleCycleWindow(WindowCycleController::FORWARD, true);
+ EXPECT_TRUE(wm::IsActiveWindow(window3.get()));
+
+ controller->HandleCycleWindow(WindowCycleController::FORWARD, true);
+ EXPECT_TRUE(wm::IsActiveWindow(window1.get()));
+
+ controller->HandleCycleWindow(WindowCycleController::FORWARD, true);
+ EXPECT_TRUE(wm::IsActiveWindow(window0.get()));
+
+ controller->HandleCycleWindow(WindowCycleController::FORWARD, true);
+ EXPECT_TRUE(wm::IsActiveWindow(window2.get()));
+}
+
+TEST_F(WindowCycleControllerTest, MostRecentlyUsed) {
+ WindowCycleController* controller =
+ Shell::GetInstance()->window_cycle_controller();
+
+ // Set up several windows to use to test cycling.
+ scoped_ptr<Window> window0(CreateTestWindowInShellWithId(0));
+ scoped_ptr<Window> window1(CreateTestWindowInShellWithId(1));
+ scoped_ptr<Window> window2(CreateTestWindowInShellWithId(2));
+
+ wm::ActivateWindow(window0.get());
+
+ // Simulate pressing and releasing Alt-tab.
+ EXPECT_TRUE(wm::IsActiveWindow(window0.get()));
+ controller->HandleCycleWindow(WindowCycleController::FORWARD, true);
+
+ // Window lists should return the topmost window in front.
+ ASSERT_TRUE(controller->windows());
+ ASSERT_EQ(3u, controller->windows()->windows().size());
+ EXPECT_EQ(window0.get(), controller->windows()->windows()[0]);
+ EXPECT_EQ(window2.get(), controller->windows()->windows()[1]);
+ EXPECT_EQ(window1.get(), controller->windows()->windows()[2]);
+
+ controller->HandleCycleWindow(WindowCycleController::FORWARD, true);
+ controller->AltKeyReleased();
+ EXPECT_TRUE(wm::IsActiveWindow(window1.get()));
+
+
+ controller->HandleCycleWindow(WindowCycleController::FORWARD, true);
+ EXPECT_TRUE(wm::IsActiveWindow(window0.get()));
+
+ controller->AltKeyReleased();
+
+ controller->HandleCycleWindow(WindowCycleController::FORWARD, true);
+ EXPECT_TRUE(wm::IsActiveWindow(window1.get()));
+
+ controller->HandleCycleWindow(WindowCycleController::FORWARD, true);
+ EXPECT_TRUE(wm::IsActiveWindow(window2.get()));
+
+ controller->HandleCycleWindow(WindowCycleController::FORWARD, true);
+ EXPECT_TRUE(wm::IsActiveWindow(window0.get()));
+}
+
+} // namespace
+
+} // namespace ash
diff --git a/chromium/ash/wm/window_cycle_list.cc b/chromium/ash/wm/window_cycle_list.cc
new file mode 100644
index 00000000000..428804f66f0
--- /dev/null
+++ b/chromium/ash/wm/window_cycle_list.cc
@@ -0,0 +1,87 @@
+// Copyright (c) 2012 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.
+
+#include "ash/wm/window_cycle_list.h"
+
+#include "ash/shell.h"
+#include "ash/wm/mru_window_tracker.h"
+#include "ash/wm/window_util.h"
+#include "ui/aura/window.h"
+#include "ui/views/corewm/window_animations.h"
+
+namespace ash {
+
+WindowCycleList::WindowCycleList(const WindowList& windows)
+ : windows_(windows),
+ current_index_(-1) {
+ ash::Shell::GetInstance()->mru_window_tracker()->SetIgnoreActivations(true);
+ // Locate the currently active window in the list to use as our start point.
+ aura::Window* active_window = wm::GetActiveWindow();
+
+ // The active window may not be in the cycle list, which is expected if there
+ // are additional modal windows on the screen.
+ current_index_ = GetWindowIndex(active_window);
+
+ for (WindowList::const_iterator i = windows_.begin(); i != windows_.end();
+ ++i) {
+ (*i)->AddObserver(this);
+ }
+}
+
+WindowCycleList::~WindowCycleList() {
+ ash::Shell::GetInstance()->mru_window_tracker()->SetIgnoreActivations(false);
+ for (WindowList::const_iterator i = windows_.begin(); i != windows_.end();
+ ++i) {
+ (*i)->RemoveObserver(this);
+ }
+}
+
+void WindowCycleList::Step(Direction direction) {
+ if (windows_.empty())
+ return;
+
+ if (current_index_ == -1) {
+ // We weren't able to find our active window in the shell delegate's
+ // provided window list. Just switch to the first (or last) one.
+ current_index_ = (direction == FORWARD ? 0 : windows_.size() - 1);
+ } else {
+ // When there is only one window, we should give a feedback to user.
+ if (windows_.size() == 1) {
+ AnimateWindow(windows_[0],
+ views::corewm::WINDOW_ANIMATION_TYPE_BOUNCE);
+ return;
+ }
+ // We're in a valid cycle, so step forward or backward.
+ current_index_ += (direction == FORWARD ? 1 : -1);
+ }
+ // Wrap to window list size.
+ current_index_ = (current_index_ + windows_.size()) % windows_.size();
+ DCHECK(windows_[current_index_]);
+ // Make sure the next window is visible.
+ windows_[current_index_]->Show();
+ wm::ActivateWindow(windows_[current_index_]);
+}
+
+int WindowCycleList::GetWindowIndex(aura::Window* window) {
+ WindowList::const_iterator it =
+ std::find(windows_.begin(), windows_.end(), window);
+ if (it == windows_.end())
+ return -1; // Not found.
+ return it - windows_.begin();
+}
+
+void WindowCycleList::OnWindowDestroyed(aura::Window* window) {
+ window->RemoveObserver(this);
+
+ WindowList::iterator i = std::find(windows_.begin(), windows_.end(), window);
+ DCHECK(i != windows_.end());
+ int removed_index = static_cast<int>(i - windows_.begin());
+ windows_.erase(i);
+ if (current_index_ > removed_index)
+ current_index_--;
+ else if (current_index_ == static_cast<int>(windows_.size()))
+ current_index_--;
+}
+
+} // namespace ash
diff --git a/chromium/ash/wm/window_cycle_list.h b/chromium/ash/wm/window_cycle_list.h
new file mode 100644
index 00000000000..6e0c75928c2
--- /dev/null
+++ b/chromium/ash/wm/window_cycle_list.h
@@ -0,0 +1,59 @@
+// Copyright (c) 2012 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 ASH_WM_WINDOW_CYCLE_LIST_H_
+#define ASH_WM_WINDOW_CYCLE_LIST_H_
+
+#include <vector>
+
+#include "ash/ash_export.h"
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "ui/aura/window_observer.h"
+
+namespace ash {
+
+// Tracks a set of Windows that can be stepped through. This class is used by
+// the WindowCycleController.
+class ASH_EXPORT WindowCycleList : public aura::WindowObserver {
+ public:
+ typedef std::vector<aura::Window*> WindowList;
+
+ enum Direction {
+ FORWARD,
+ BACKWARD
+ };
+
+ explicit WindowCycleList(const WindowList& windows);
+ virtual ~WindowCycleList();
+
+ bool empty() const { return windows_.empty(); }
+
+ // Cycles to the next or previous window based on |direction|.
+ void Step(Direction direction);
+
+ const WindowList& windows() const { return windows_; }
+
+ private:
+ // Returns the index of |window| in |windows_| or -1 if it isn't there.
+ int GetWindowIndex(aura::Window* window);
+
+ // aura::WindowObserver overrides:
+ virtual void OnWindowDestroyed(aura::Window* window) OVERRIDE;
+
+ // List of weak pointers to windows to use while cycling with the keyboard.
+ // List is built when the user initiates the gesture (e.g. hits alt-tab the
+ // first time) and is emptied when the gesture is complete (e.g. releases the
+ // alt key).
+ WindowList windows_;
+
+ // Current position in the |windows_| list or -1 if we're not cycling.
+ int current_index_;
+
+ DISALLOW_COPY_AND_ASSIGN(WindowCycleList);
+};
+
+} // namespace ash
+
+#endif // ASH_WM_WINDOW_CYCLE_LIST_H_
diff --git a/chromium/ash/wm/window_manager_unittest.cc b/chromium/ash/wm/window_manager_unittest.cc
new file mode 100644
index 00000000000..936b29afeae
--- /dev/null
+++ b/chromium/ash/wm/window_manager_unittest.cc
@@ -0,0 +1,840 @@
+// Copyright (c) 2012 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.
+
+#include "ash/shell.h"
+#include "ash/shell_window_ids.h"
+#include "ash/test/ash_test_base.h"
+#include "ash/test/shell_test_api.h"
+#include "ash/test/test_activation_delegate.h"
+#include "ash/wm/activation_controller.h"
+#include "ash/wm/window_util.h"
+#include "ui/aura/client/activation_client.h"
+#include "ui/aura/client/activation_delegate.h"
+#include "ui/aura/client/cursor_client_observer.h"
+#include "ui/aura/client/focus_client.h"
+#include "ui/aura/env.h"
+#include "ui/aura/root_window.h"
+#include "ui/aura/test/aura_test_base.h"
+#include "ui/aura/test/event_generator.h"
+#include "ui/aura/test/test_event_handler.h"
+#include "ui/aura/test/test_window_delegate.h"
+#include "ui/aura/test/test_windows.h"
+#include "ui/base/cursor/cursor.h"
+#include "ui/base/events/event.h"
+#include "ui/base/events/event_utils.h"
+#include "ui/base/hit_test.h"
+#include "ui/gfx/screen.h"
+#include "ui/views/corewm/compound_event_filter.h"
+#include "ui/views/corewm/corewm_switches.h"
+#include "ui/views/corewm/input_method_event_filter.h"
+
+namespace {
+
+class TestingCursorClientObserver : public aura::client::CursorClientObserver {
+ public:
+ TestingCursorClientObserver()
+ : cursor_visibility_(false),
+ did_visibility_change_(false) {}
+ void reset() { cursor_visibility_ = did_visibility_change_ = false; }
+ bool is_cursor_visible() const { return cursor_visibility_; }
+ bool did_visibility_change() const { return did_visibility_change_; }
+
+ // Overridden from aura::client::CursorClientObserver:
+ virtual void OnCursorVisibilityChanged(bool is_visible) OVERRIDE {
+ cursor_visibility_ = is_visible;
+ did_visibility_change_ = true;
+ }
+
+ private:
+ bool cursor_visibility_;
+ bool did_visibility_change_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestingCursorClientObserver);
+};
+
+base::TimeDelta getTime() {
+ return ui::EventTimeForNow();
+}
+
+// A slightly changed TestEventHandler which can be configured to return a
+// specified value for key/mouse event handling.
+class CustomEventHandler : public aura::test::TestEventHandler {
+ public:
+ CustomEventHandler()
+ : key_result_(ui::ER_UNHANDLED),
+ mouse_result_(ui::ER_UNHANDLED) {
+ }
+
+ virtual ~CustomEventHandler() {}
+
+ void set_key_event_handling_result(ui::EventResult result) {
+ key_result_ = result;
+ }
+
+ void set_mouse_event_handling_result(ui::EventResult result) {
+ mouse_result_ = result;
+ }
+
+ // Overridden from ui::EventHandler:
+ virtual void OnKeyEvent(ui::KeyEvent* event) OVERRIDE {
+ aura::test::TestEventHandler::OnKeyEvent(event);
+ if (key_result_ & ui::ER_HANDLED)
+ event->SetHandled();
+ if (key_result_ & ui::ER_CONSUMED)
+ event->StopPropagation();
+ }
+
+ virtual void OnMouseEvent(ui::MouseEvent* event) OVERRIDE {
+ aura::test::TestEventHandler::OnMouseEvent(event);
+ if (mouse_result_ & ui::ER_HANDLED)
+ event->SetHandled();
+ if (mouse_result_ & ui::ER_CONSUMED)
+ event->StopPropagation();
+ }
+
+ private:
+ ui::EventResult key_result_;
+ ui::EventResult mouse_result_;
+
+ DISALLOW_COPY_AND_ASSIGN(CustomEventHandler);
+};
+
+} // namespace
+
+namespace ash {
+
+typedef test::AshTestBase WindowManagerTest;
+
+class NonFocusableDelegate : public aura::test::TestWindowDelegate {
+ public:
+ NonFocusableDelegate() {}
+
+ private:
+ virtual bool CanFocus() OVERRIDE {
+ return false;
+ }
+
+ DISALLOW_COPY_AND_ASSIGN(NonFocusableDelegate);
+};
+
+class HitTestWindowDelegate : public aura::test::TestWindowDelegate {
+ public:
+ HitTestWindowDelegate()
+ : hittest_code_(HTNOWHERE) {
+ }
+ virtual ~HitTestWindowDelegate() {}
+ void set_hittest_code(int hittest_code) { hittest_code_ = hittest_code; }
+
+ private:
+ // Overridden from TestWindowDelegate:
+ virtual int GetNonClientComponent(const gfx::Point& point) const OVERRIDE {
+ return hittest_code_;
+ }
+
+ int hittest_code_;
+
+ DISALLOW_COPY_AND_ASSIGN(HitTestWindowDelegate);
+};
+
+TEST_F(WindowManagerTest, Focus) {
+ // The IME event filter interferes with the basic key event propagation we
+ // attempt to do here, so we remove it.
+ test::ShellTestApi shell_test(Shell::GetInstance());
+ Shell::GetInstance()->RemovePreTargetHandler(
+ shell_test.input_method_event_filter());
+
+ aura::RootWindow* root_window = Shell::GetPrimaryRootWindow();
+ root_window->SetBounds(gfx::Rect(0, 0, 510, 510));
+
+ // Supplied ids are negative so as not to collide with shell ids.
+ // TODO(beng): maybe introduce a MAKE_SHELL_ID() macro that generates a safe
+ // id beyond shell id max?
+ scoped_ptr<aura::Window> w1(CreateTestWindowInShell(
+ SK_ColorWHITE, -1, gfx::Rect(10, 10, 500, 500)));
+ scoped_ptr<aura::Window> w11(aura::test::CreateTestWindow(
+ SK_ColorGREEN, -11, gfx::Rect(5, 5, 100, 100), w1.get()));
+ scoped_ptr<aura::Window> w111(aura::test::CreateTestWindow(
+ SK_ColorCYAN, -111, gfx::Rect(5, 5, 75, 75), w11.get()));
+ scoped_ptr<aura::Window> w1111(aura::test::CreateTestWindow(
+ SK_ColorRED, -1111, gfx::Rect(5, 5, 50, 50), w111.get()));
+ scoped_ptr<aura::Window> w12(aura::test::CreateTestWindow(
+ SK_ColorMAGENTA, -12, gfx::Rect(10, 420, 25, 25), w1.get()));
+ aura::test::ColorTestWindowDelegate* w121delegate =
+ new aura::test::ColorTestWindowDelegate(SK_ColorYELLOW);
+ scoped_ptr<aura::Window> w121(aura::test::CreateTestWindowWithDelegate(
+ w121delegate, -121, gfx::Rect(5, 5, 5, 5), w12.get()));
+ aura::test::ColorTestWindowDelegate* w122delegate =
+ new aura::test::ColorTestWindowDelegate(SK_ColorRED);
+ scoped_ptr<aura::Window> w122(aura::test::CreateTestWindowWithDelegate(
+ w122delegate, -122, gfx::Rect(10, 5, 5, 5), w12.get()));
+ aura::test::ColorTestWindowDelegate* w123delegate =
+ new aura::test::ColorTestWindowDelegate(SK_ColorRED);
+ scoped_ptr<aura::Window> w123(aura::test::CreateTestWindowWithDelegate(
+ w123delegate, -123, gfx::Rect(15, 5, 5, 5), w12.get()));
+ scoped_ptr<aura::Window> w13(aura::test::CreateTestWindow(
+ SK_ColorGRAY, -13, gfx::Rect(5, 470, 50, 50), w1.get()));
+
+ // Click on a sub-window (w121) to focus it.
+ aura::test::EventGenerator generator(Shell::GetPrimaryRootWindow(),
+ w121.get());
+ generator.ClickLeftButton();
+
+ aura::client::FocusClient* focus_client =
+ aura::client::GetFocusClient(w121.get());
+ EXPECT_EQ(w121.get(), focus_client->GetFocusedWindow());
+
+ // The key press should be sent to the focused sub-window.
+ ui::KeyEvent keyev(ui::ET_KEY_PRESSED, ui::VKEY_E, 0, false);
+ root_window->AsRootWindowHostDelegate()->OnHostKeyEvent(&keyev);
+ EXPECT_EQ(ui::VKEY_E, w121delegate->last_key_code());
+
+ // Touch on a sub-window (w122) to focus it.
+ gfx::Point click_point = w122->bounds().CenterPoint();
+ aura::Window::ConvertPointToTarget(w122->parent(), root_window, &click_point);
+ ui::TouchEvent touchev(ui::ET_TOUCH_PRESSED, click_point, 0, getTime());
+ root_window->AsRootWindowHostDelegate()->OnHostTouchEvent(&touchev);
+ focus_client = aura::client::GetFocusClient(w122.get());
+ EXPECT_EQ(w122.get(), focus_client->GetFocusedWindow());
+
+ // The key press should be sent to the focused sub-window.
+ root_window->AsRootWindowHostDelegate()->OnHostKeyEvent(&keyev);
+ EXPECT_EQ(ui::VKEY_E, w122delegate->last_key_code());
+
+ // Hiding the focused window will set the focus to its parent if
+ // it's focusable.
+ w122->Hide();
+ EXPECT_EQ(aura::client::GetFocusClient(w12.get()),
+ aura::client::GetFocusClient(w122.get()));
+ EXPECT_EQ(w12.get(),
+ aura::client::GetFocusClient(w12.get())->GetFocusedWindow());
+
+ // Sets the focus back to w122.
+ w122->Show();
+ w122->Focus();
+ EXPECT_EQ(w122.get(),
+ aura::client::GetFocusClient(w12.get())->GetFocusedWindow());
+
+ // Removing the focused window from parent should set the focus to
+ // its parent if it's focusable.
+ w12->RemoveChild(w122.get());
+ EXPECT_EQ(NULL, aura::client::GetFocusClient(w122.get()));
+ EXPECT_EQ(w12.get(),
+ aura::client::GetFocusClient(w12.get())->GetFocusedWindow());
+
+ // Set the focus to w123, but make the w1 not activatable.
+ test::TestActivationDelegate activation_delegate(false);
+ w123->Focus();
+ EXPECT_EQ(w123.get(),
+ aura::client::GetFocusClient(w12.get())->GetFocusedWindow());
+ aura::client::SetActivationDelegate(w1.get(), &activation_delegate);
+
+ // Hiding the focused window will set the focus to NULL because
+ // parent window is not focusable.
+ w123->Hide();
+ EXPECT_EQ(aura::client::GetFocusClient(w12.get()),
+ aura::client::GetFocusClient(w123.get()));
+ EXPECT_EQ(NULL, aura::client::GetFocusClient(w12.get())->GetFocusedWindow());
+ EXPECT_FALSE(root_window->AsRootWindowHostDelegate()->OnHostKeyEvent(&keyev));
+
+ // Set the focus back to w123
+ aura::client::SetActivationDelegate(w1.get(), NULL);
+ w123->Show();
+ w123->Focus();
+ EXPECT_EQ(w123.get(),
+ aura::client::GetFocusClient(w12.get())->GetFocusedWindow());
+ aura::client::SetActivationDelegate(w1.get(), &activation_delegate);
+
+ // Removing the focused window will set the focus to NULL because
+ // parent window is not focusable.
+ w12->RemoveChild(w123.get());
+ EXPECT_EQ(NULL, aura::client::GetFocusClient(w123.get()));
+ EXPECT_FALSE(root_window->AsRootWindowHostDelegate()->OnHostKeyEvent(&keyev));
+}
+
+// Various assertion testing for activating windows.
+TEST_F(WindowManagerTest, ActivateOnMouse) {
+ aura::RootWindow* root_window = Shell::GetPrimaryRootWindow();
+
+ test::TestActivationDelegate d1;
+ aura::test::TestWindowDelegate wd;
+ scoped_ptr<aura::Window> w1(CreateTestWindowInShellWithDelegate(
+ &wd, -1, gfx::Rect(10, 10, 50, 50)));
+ d1.SetWindow(w1.get());
+ test::TestActivationDelegate d2;
+ scoped_ptr<aura::Window> w2(CreateTestWindowInShellWithDelegate(
+ &wd, -2, gfx::Rect(70, 70, 50, 50)));
+ d2.SetWindow(w2.get());
+
+ aura::client::FocusClient* focus_client =
+ aura::client::GetFocusClient(w1.get());
+
+ d1.Clear();
+ d2.Clear();
+
+ // Activate window1.
+ wm::ActivateWindow(w1.get());
+ EXPECT_TRUE(wm::IsActiveWindow(w1.get()));
+ EXPECT_EQ(w1.get(), focus_client->GetFocusedWindow());
+ EXPECT_EQ(1, d1.activated_count());
+ EXPECT_EQ(0, d1.lost_active_count());
+ d1.Clear();
+
+ {
+ // Click on window2.
+ aura::test::EventGenerator generator(Shell::GetPrimaryRootWindow(),
+ w2.get());
+ generator.ClickLeftButton();
+
+ // Window2 should have become active.
+ EXPECT_TRUE(wm::IsActiveWindow(w2.get()));
+ EXPECT_EQ(w2.get(), focus_client->GetFocusedWindow());
+ EXPECT_EQ(0, d1.activated_count());
+ EXPECT_EQ(1, d1.lost_active_count());
+ EXPECT_EQ(1, d2.activated_count());
+ EXPECT_EQ(0, d2.lost_active_count());
+ d1.Clear();
+ d2.Clear();
+ }
+
+ {
+ // Click back on window1, but set it up so w1 doesn't activate on click.
+ aura::test::EventGenerator generator(Shell::GetPrimaryRootWindow(),
+ w1.get());
+ d1.set_activate(false);
+ generator.ClickLeftButton();
+
+ // Window2 should still be active and focused.
+ EXPECT_TRUE(wm::IsActiveWindow(w2.get()));
+ EXPECT_EQ(w2.get(), focus_client->GetFocusedWindow());
+ EXPECT_EQ(0, d1.activated_count());
+ EXPECT_EQ(0, d1.lost_active_count());
+ EXPECT_EQ(0, d2.activated_count());
+ EXPECT_EQ(0, d2.lost_active_count());
+ d1.Clear();
+ d2.Clear();
+ }
+
+ // Destroy window2, this should make window1 active.
+ d1.set_activate(true);
+ w2.reset();
+ EXPECT_EQ(0, d2.activated_count());
+ EXPECT_EQ(views::corewm::UseFocusController() ? 1 : 0,
+ d2.lost_active_count());
+ EXPECT_TRUE(wm::IsActiveWindow(w1.get()));
+ EXPECT_EQ(w1.get(), focus_client->GetFocusedWindow());
+ EXPECT_EQ(1, d1.activated_count());
+ EXPECT_EQ(0, d1.lost_active_count());
+
+ // Clicking an active window with a child shouldn't steal the
+ // focus from the child.
+ {
+ scoped_ptr<aura::Window> w11(CreateTestWindowWithDelegate(
+ &wd, -11, gfx::Rect(10, 10, 10, 10), w1.get()));
+ aura::test::EventGenerator generator(Shell::GetPrimaryRootWindow(),
+ w11.get());
+ // First set the focus to the child |w11|.
+ generator.ClickLeftButton();
+ EXPECT_EQ(w11.get(), focus_client->GetFocusedWindow());
+ EXPECT_EQ(w1.get(), wm::GetActiveWindow());
+
+ // Then click the parent active window. The focus shouldn't move.
+ gfx::Point left_top = w1->bounds().origin();
+ aura::Window::ConvertPointToTarget(w1->parent(), root_window, &left_top);
+ left_top.Offset(1, 1);
+ generator.MoveMouseTo(left_top);
+ generator.ClickLeftButton();
+ EXPECT_EQ(w11.get(), focus_client->GetFocusedWindow());
+ EXPECT_EQ(w1.get(), wm::GetActiveWindow());
+ }
+
+ // Clicking on a non-focusable window inside a background window should still
+ // give focus to the background window.
+ {
+ NonFocusableDelegate nfd;
+ scoped_ptr<aura::Window> w11(CreateTestWindowWithDelegate(
+ &nfd, -1, gfx::Rect(10, 10, 10, 10), w1.get()));
+ // Move focus to |w2| first.
+ scoped_ptr<aura::Window> w2(CreateTestWindowInShellWithDelegate(
+ &wd, -1, gfx::Rect(70, 70, 50, 50)));
+ aura::test::EventGenerator generator(Shell::GetPrimaryRootWindow(),
+ w2.get());
+ generator.ClickLeftButton();
+ EXPECT_EQ(w2.get(), focus_client->GetFocusedWindow());
+ EXPECT_FALSE(w11->CanFocus());
+
+ // Click on |w11|. This should focus w1.
+ generator.MoveMouseToCenterOf(w11.get());
+ generator.ClickLeftButton();
+ EXPECT_EQ(w1.get(), focus_client->GetFocusedWindow());
+ }
+}
+
+TEST_F(WindowManagerTest, PanelActivation) {
+ aura::test::TestWindowDelegate wd;
+ scoped_ptr<aura::Window> w1(CreateTestWindowInShellWithDelegate(
+ &wd, -1, gfx::Rect(10, 10, 50, 50)));
+ aura::test::TestWindowDelegate pd;
+ scoped_ptr<aura::Window> p1(CreateTestWindowInShellWithDelegateAndType(
+ &pd, aura::client::WINDOW_TYPE_PANEL, -1, gfx::Rect(10, 10, 50, 50)));
+ aura::client::FocusClient* focus_client =
+ aura::client::GetFocusClient(w1.get());
+
+ // Activate w1.
+ wm::ActivateWindow(w1.get());
+ EXPECT_TRUE(wm::IsActiveWindow(w1.get()));
+
+ // Activate p1.
+ wm::ActivateWindow(p1.get());
+ EXPECT_TRUE(wm::IsActiveWindow(p1.get()));
+ EXPECT_EQ(p1.get(), focus_client->GetFocusedWindow());
+
+ // Activate w1.
+ wm::ActivateWindow(w1.get());
+ EXPECT_TRUE(wm::IsActiveWindow(w1.get()));
+ EXPECT_EQ(w1.get(), focus_client->GetFocusedWindow());
+
+ // Clicking on a non-activatable window should not change the active window.
+ {
+ NonFocusableDelegate nfd;
+ scoped_ptr<aura::Window> w3(CreateTestWindowInShellWithDelegate(
+ &nfd, -1, gfx::Rect(70, 70, 50, 50)));
+ aura::test::EventGenerator generator3(Shell::GetPrimaryRootWindow(),
+ w3.get());
+ wm::ActivateWindow(p1.get());
+ EXPECT_TRUE(wm::IsActiveWindow(p1.get()));
+ generator3.ClickLeftButton();
+ EXPECT_TRUE(wm::IsActiveWindow(p1.get()));
+ }
+}
+
+// Essentially the same as ActivateOnMouse, but for touch events.
+TEST_F(WindowManagerTest, ActivateOnTouch) {
+ aura::RootWindow* root_window = Shell::GetPrimaryRootWindow();
+
+ test::TestActivationDelegate d1;
+ aura::test::TestWindowDelegate wd;
+ scoped_ptr<aura::Window> w1(CreateTestWindowInShellWithDelegate(
+ &wd, -1, gfx::Rect(10, 10, 50, 50)));
+ d1.SetWindow(w1.get());
+ test::TestActivationDelegate d2;
+ scoped_ptr<aura::Window> w2(CreateTestWindowInShellWithDelegate(
+ &wd, -2, gfx::Rect(70, 70, 50, 50)));
+ d2.SetWindow(w2.get());
+
+ aura::client::FocusClient* focus_client =
+ aura::client::GetFocusClient(w1.get());
+
+ d1.Clear();
+ d2.Clear();
+
+ // Activate window1.
+ wm::ActivateWindow(w1.get());
+ EXPECT_TRUE(wm::IsActiveWindow(w1.get()));
+ EXPECT_EQ(w1.get(), focus_client->GetFocusedWindow());
+ EXPECT_EQ(1, d1.activated_count());
+ EXPECT_EQ(0, d1.lost_active_count());
+ d1.Clear();
+
+ // Touch window2.
+ gfx::Point press_point = w2->bounds().CenterPoint();
+ aura::Window::ConvertPointToTarget(w2->parent(), root_window, &press_point);
+ ui::TouchEvent touchev1(ui::ET_TOUCH_PRESSED, press_point, 0, getTime());
+ root_window->AsRootWindowHostDelegate()->OnHostTouchEvent(&touchev1);
+
+ // Window2 should have become active.
+ EXPECT_TRUE(wm::IsActiveWindow(w2.get()));
+ EXPECT_EQ(w2.get(), focus_client->GetFocusedWindow());
+ EXPECT_EQ(0, d1.activated_count());
+ EXPECT_EQ(1, d1.lost_active_count());
+ EXPECT_EQ(1, d2.activated_count());
+ EXPECT_EQ(0, d2.lost_active_count());
+ d1.Clear();
+ d2.Clear();
+
+ // Touch window1, but set it up so w1 doesn't activate on touch.
+ press_point = w1->bounds().CenterPoint();
+ aura::Window::ConvertPointToTarget(w1->parent(), root_window, &press_point);
+ d1.set_activate(false);
+ ui::TouchEvent touchev2(ui::ET_TOUCH_PRESSED, press_point, 1, getTime());
+ root_window->AsRootWindowHostDelegate()->OnHostTouchEvent(&touchev2);
+
+ // Window2 should still be active and focused.
+ EXPECT_TRUE(wm::IsActiveWindow(w2.get()));
+ EXPECT_EQ(w2.get(), focus_client->GetFocusedWindow());
+ EXPECT_EQ(0, d1.activated_count());
+ EXPECT_EQ(0, d1.lost_active_count());
+ EXPECT_EQ(0, d2.activated_count());
+ EXPECT_EQ(0, d2.lost_active_count());
+ d1.Clear();
+ d2.Clear();
+
+ // Destroy window2, this should make window1 active.
+ d1.set_activate(true);
+ w2.reset();
+ EXPECT_EQ(0, d2.activated_count());
+ EXPECT_EQ(views::corewm::UseFocusController() ? 1 : 0,
+ d2.lost_active_count());
+ EXPECT_TRUE(wm::IsActiveWindow(w1.get()));
+ EXPECT_EQ(w1.get(), focus_client->GetFocusedWindow());
+ EXPECT_EQ(1, d1.activated_count());
+ EXPECT_EQ(0, d1.lost_active_count());
+}
+
+TEST_F(WindowManagerTest, MouseEventCursors) {
+ aura::RootWindow* root_window = Shell::GetPrimaryRootWindow();
+
+ // Create a window.
+ const int kWindowLeft = 123;
+ const int kWindowTop = 45;
+ HitTestWindowDelegate window_delegate;
+ scoped_ptr<aura::Window> window(CreateTestWindowInShellWithDelegate(
+ &window_delegate,
+ -1,
+ gfx::Rect(kWindowLeft, kWindowTop, 640, 480)));
+
+ // Create two mouse movement events we can switch between.
+ gfx::Point point1(kWindowLeft, kWindowTop);
+ aura::Window::ConvertPointToTarget(window->parent(), root_window, &point1);
+
+ gfx::Point point2(kWindowLeft + 1, kWindowTop + 1);
+ aura::Window::ConvertPointToTarget(window->parent(), root_window, &point2);
+
+ // Cursor starts as a pointer (set during Shell::Init()).
+ EXPECT_EQ(ui::kCursorPointer, root_window->last_cursor().native_type());
+
+ {
+ // Resize edges and corners show proper cursors.
+ window_delegate.set_hittest_code(HTBOTTOM);
+ ui::MouseEvent move1(ui::ET_MOUSE_MOVED, point1, point1, 0x0);
+ root_window->AsRootWindowHostDelegate()->OnHostMouseEvent(&move1);
+ EXPECT_EQ(ui::kCursorSouthResize, root_window->last_cursor().native_type());
+ }
+
+ {
+ window_delegate.set_hittest_code(HTBOTTOMLEFT);
+ ui::MouseEvent move2(ui::ET_MOUSE_MOVED, point2, point2, 0x0);
+ root_window->AsRootWindowHostDelegate()->OnHostMouseEvent(&move2);
+ EXPECT_EQ(ui::kCursorSouthWestResize,
+ root_window->last_cursor().native_type());
+ }
+
+ {
+ window_delegate.set_hittest_code(HTBOTTOMRIGHT);
+ ui::MouseEvent move1(ui::ET_MOUSE_MOVED, point1, point1, 0x0);
+ root_window->AsRootWindowHostDelegate()->OnHostMouseEvent(&move1);
+ EXPECT_EQ(ui::kCursorSouthEastResize,
+ root_window->last_cursor().native_type());
+ }
+
+ {
+ window_delegate.set_hittest_code(HTLEFT);
+ ui::MouseEvent move2(ui::ET_MOUSE_MOVED, point2, point2, 0x0);
+ root_window->AsRootWindowHostDelegate()->OnHostMouseEvent(&move2);
+ EXPECT_EQ(ui::kCursorWestResize, root_window->last_cursor().native_type());
+ }
+
+ {
+ window_delegate.set_hittest_code(HTRIGHT);
+ ui::MouseEvent move1(ui::ET_MOUSE_MOVED, point1, point1, 0x0);
+ root_window->AsRootWindowHostDelegate()->OnHostMouseEvent(&move1);
+ EXPECT_EQ(ui::kCursorEastResize, root_window->last_cursor().native_type());
+ }
+
+ {
+ window_delegate.set_hittest_code(HTTOP);
+ ui::MouseEvent move2(ui::ET_MOUSE_MOVED, point2, point2, 0x0);
+ root_window->AsRootWindowHostDelegate()->OnHostMouseEvent(&move2);
+ EXPECT_EQ(ui::kCursorNorthResize, root_window->last_cursor().native_type());
+ }
+
+ {
+ window_delegate.set_hittest_code(HTTOPLEFT);
+ ui::MouseEvent move1(ui::ET_MOUSE_MOVED, point1, point1, 0x0);
+ root_window->AsRootWindowHostDelegate()->OnHostMouseEvent(&move1);
+ EXPECT_EQ(ui::kCursorNorthWestResize,
+ root_window->last_cursor().native_type());
+ }
+
+ {
+ window_delegate.set_hittest_code(HTTOPRIGHT);
+ ui::MouseEvent move2(ui::ET_MOUSE_MOVED, point2, point2, 0x0);
+ root_window->AsRootWindowHostDelegate()->OnHostMouseEvent(&move2);
+ EXPECT_EQ(ui::kCursorNorthEastResize,
+ root_window->last_cursor().native_type());
+ }
+
+ {
+ // Client area uses null cursor.
+ window_delegate.set_hittest_code(HTCLIENT);
+ ui::MouseEvent move1(ui::ET_MOUSE_MOVED, point1, point1, 0x0);
+ root_window->AsRootWindowHostDelegate()->OnHostMouseEvent(&move1);
+ EXPECT_EQ(ui::kCursorNull, root_window->last_cursor().native_type());
+ }
+}
+
+#if defined(OS_MACOSX) || defined(OS_WIN)
+#define MAYBE_TransformActivate DISABLED_TransformActivate
+#else
+#define MAYBE_TransformActivate TransformActivate
+#endif
+TEST_F(WindowManagerTest, MAYBE_TransformActivate) {
+ aura::RootWindow* root_window = Shell::GetPrimaryRootWindow();
+ gfx::Size size = root_window->bounds().size();
+ EXPECT_EQ(gfx::Rect(size).ToString(),
+ Shell::GetScreen()->GetDisplayNearestPoint(
+ gfx::Point()).bounds().ToString());
+
+ // Rotate it clock-wise 90 degrees.
+ gfx::Transform transform;
+ transform.Translate(size.width(), 0);
+ transform.Rotate(90.0f);
+ root_window->SetTransform(transform);
+
+ test::TestActivationDelegate d1;
+ aura::test::TestWindowDelegate wd;
+ scoped_ptr<aura::Window> w1(
+ CreateTestWindowInShellWithDelegate(&wd, 1, gfx::Rect(0, 10, 50, 50)));
+ d1.SetWindow(w1.get());
+ w1->Show();
+
+ gfx::Point miss_point(5, 5);
+ transform.TransformPoint(miss_point);
+ ui::MouseEvent mouseev1(ui::ET_MOUSE_PRESSED,
+ miss_point,
+ miss_point,
+ ui::EF_LEFT_MOUSE_BUTTON);
+ root_window->AsRootWindowHostDelegate()->OnHostMouseEvent(&mouseev1);
+ EXPECT_EQ(NULL, aura::client::GetFocusClient(w1.get())->GetFocusedWindow());
+ ui::MouseEvent mouseup(ui::ET_MOUSE_RELEASED,
+ miss_point,
+ miss_point,
+ ui::EF_LEFT_MOUSE_BUTTON);
+ root_window->AsRootWindowHostDelegate()->OnHostMouseEvent(&mouseup);
+
+ gfx::Point hit_point(5, 15);
+ transform.TransformPoint(hit_point);
+ ui::MouseEvent mouseev2(ui::ET_MOUSE_PRESSED,
+ hit_point,
+ hit_point,
+ ui::EF_LEFT_MOUSE_BUTTON);
+ root_window->AsRootWindowHostDelegate()->OnHostMouseEvent(&mouseev2);
+ EXPECT_TRUE(wm::IsActiveWindow(w1.get()));
+ EXPECT_EQ(w1.get(),
+ aura::client::GetFocusClient(w1.get())->GetFocusedWindow());
+}
+
+TEST_F(WindowManagerTest, AdditionalFilters) {
+ // The IME event filter interferes with the basic key event propagation we
+ // attempt to do here, so we remove it.
+ test::ShellTestApi shell_test(Shell::GetInstance());
+ Shell::GetInstance()->RemovePreTargetHandler(
+ shell_test.input_method_event_filter());
+
+ aura::RootWindow* root_window = Shell::GetPrimaryRootWindow();
+
+ // Creates a window and make it active
+ scoped_ptr<aura::Window> w1(CreateTestWindowInShell(
+ SK_ColorWHITE, -1, gfx::Rect(0, 0, 100, 100)));
+ wm::ActivateWindow(w1.get());
+
+ // Creates two addition filters
+ scoped_ptr<CustomEventHandler> f1(new CustomEventHandler);
+ scoped_ptr<CustomEventHandler> f2(new CustomEventHandler);
+
+ // Adds them to root window event filter.
+ views::corewm::CompoundEventFilter* env_filter =
+ Shell::GetInstance()->env_filter();
+ env_filter->AddHandler(f1.get());
+ env_filter->AddHandler(f2.get());
+
+ // Dispatches mouse and keyboard events.
+ ui::KeyEvent key_event(ui::ET_KEY_PRESSED, ui::VKEY_A, 0, false);
+ root_window->AsRootWindowHostDelegate()->OnHostKeyEvent(&key_event);
+ ui::MouseEvent mouse_pressed(
+ ui::ET_MOUSE_PRESSED, gfx::Point(0, 0), gfx::Point(0, 0), 0x0);
+ root_window->AsRootWindowHostDelegate()->OnHostMouseEvent(&mouse_pressed);
+
+ // Both filters should get the events.
+ EXPECT_EQ(1, f1->num_key_events());
+ EXPECT_EQ(1, f1->num_mouse_events());
+ EXPECT_EQ(1, f2->num_key_events());
+ EXPECT_EQ(1, f2->num_mouse_events());
+
+ f1->Reset();
+ f2->Reset();
+
+ // Makes f1 consume events.
+ f1->set_key_event_handling_result(ui::ER_CONSUMED);
+ f1->set_mouse_event_handling_result(ui::ER_CONSUMED);
+
+ // Dispatches events.
+ root_window->AsRootWindowHostDelegate()->OnHostKeyEvent(&key_event);
+ ui::MouseEvent mouse_released(
+ ui::ET_MOUSE_RELEASED, gfx::Point(0, 0), gfx::Point(0, 0), 0x0);
+ root_window->AsRootWindowHostDelegate()->OnHostMouseEvent(&mouse_released);
+
+ // f1 should still get the events but f2 no longer gets them.
+ EXPECT_EQ(1, f1->num_key_events());
+ EXPECT_EQ(1, f1->num_mouse_events());
+ EXPECT_EQ(0, f2->num_key_events());
+ EXPECT_EQ(0, f2->num_mouse_events());
+
+ f1->Reset();
+ f2->Reset();
+
+ // Remove f1 from additonal filters list.
+ env_filter->RemoveHandler(f1.get());
+
+ // Dispatches events.
+ root_window->AsRootWindowHostDelegate()->OnHostKeyEvent(&key_event);
+ root_window->AsRootWindowHostDelegate()->OnHostMouseEvent(&mouse_pressed);
+
+ // f1 should get no events since it's out and f2 should get them.
+ EXPECT_EQ(0, f1->num_key_events());
+ EXPECT_EQ(0, f1->num_mouse_events());
+ EXPECT_EQ(1, f2->num_key_events());
+ EXPECT_EQ(1, f2->num_mouse_events());
+
+ env_filter->RemoveHandler(f2.get());
+}
+
+// We should show and hide the cursor in response to mouse and touch events as
+// requested.
+TEST_F(WindowManagerTest, UpdateCursorVisibility) {
+ aura::test::EventGenerator& generator = GetEventGenerator();
+ views::corewm::CursorManager* cursor_manager =
+ ash::Shell::GetInstance()->cursor_manager();
+
+ generator.MoveMouseTo(gfx::Point(0, 0));
+ EXPECT_TRUE(cursor_manager->IsCursorVisible());
+ EXPECT_TRUE(cursor_manager->IsMouseEventsEnabled());
+ generator.PressTouch();
+ EXPECT_FALSE(cursor_manager->IsCursorVisible());
+ EXPECT_FALSE(cursor_manager->IsMouseEventsEnabled());
+ generator.MoveMouseTo(gfx::Point(0, 0));
+ EXPECT_TRUE(cursor_manager->IsCursorVisible());
+ EXPECT_TRUE(cursor_manager->IsMouseEventsEnabled());
+ generator.ReleaseTouch();
+ EXPECT_TRUE(cursor_manager->IsCursorVisible());
+ EXPECT_TRUE(cursor_manager->IsMouseEventsEnabled());
+
+ // If someone else made cursor invisible keep it invisible even after it
+ // received mouse events.
+ cursor_manager->EnableMouseEvents();
+ cursor_manager->HideCursor();
+ generator.MoveMouseTo(gfx::Point(0, 0));
+ EXPECT_FALSE(cursor_manager->IsCursorVisible());
+ EXPECT_TRUE(cursor_manager->IsMouseEventsEnabled());
+ generator.PressTouch();
+ EXPECT_FALSE(cursor_manager->IsCursorVisible());
+ EXPECT_FALSE(cursor_manager->IsMouseEventsEnabled());
+ generator.MoveMouseTo(gfx::Point(0, 0));
+ EXPECT_FALSE(cursor_manager->IsCursorVisible());
+ EXPECT_TRUE(cursor_manager->IsMouseEventsEnabled());
+ generator.ReleaseTouch();
+ EXPECT_FALSE(cursor_manager->IsCursorVisible());
+ EXPECT_TRUE(cursor_manager->IsMouseEventsEnabled());
+
+ // Back to normal.
+ cursor_manager->EnableMouseEvents();
+ cursor_manager->ShowCursor();
+ generator.MoveMouseTo(gfx::Point(0, 0));
+ EXPECT_TRUE(cursor_manager->IsCursorVisible());
+ EXPECT_TRUE(cursor_manager->IsMouseEventsEnabled());
+ generator.PressTouch();
+ EXPECT_FALSE(cursor_manager->IsCursorVisible());
+ EXPECT_FALSE(cursor_manager->IsMouseEventsEnabled());
+ generator.MoveMouseTo(gfx::Point(0, 0));
+ EXPECT_TRUE(cursor_manager->IsCursorVisible());
+ EXPECT_TRUE(cursor_manager->IsMouseEventsEnabled());
+ generator.ReleaseTouch();
+ EXPECT_TRUE(cursor_manager->IsCursorVisible());
+ EXPECT_TRUE(cursor_manager->IsMouseEventsEnabled());
+}
+
+TEST_F(WindowManagerTest, UpdateCursorVisibilityOnKeyEvent) {
+ aura::test::EventGenerator& generator = GetEventGenerator();
+ views::corewm::CursorManager* cursor_manager =
+ ash::Shell::GetInstance()->cursor_manager();
+
+ // Pressing a key hides the cursor but does not disable mouse events.
+ generator.PressKey(ui::VKEY_A, ui::EF_NONE);
+ EXPECT_FALSE(cursor_manager->IsCursorVisible());
+ EXPECT_TRUE(cursor_manager->IsMouseEventsEnabled());
+ // Moving mouse shows the cursor.
+ generator.MoveMouseTo(gfx::Point(0, 0));
+ EXPECT_TRUE(cursor_manager->IsCursorVisible());
+ EXPECT_TRUE(cursor_manager->IsMouseEventsEnabled());
+ // Releasing a key also hides the cursor but does not disable mouse events.
+ generator.ReleaseKey(ui::VKEY_A, ui::EF_NONE);
+ EXPECT_FALSE(cursor_manager->IsCursorVisible());
+ EXPECT_TRUE(cursor_manager->IsMouseEventsEnabled());
+ // Moving mouse shows the cursor again.
+ generator.MoveMouseTo(gfx::Point(0, 0));
+ EXPECT_TRUE(cursor_manager->IsCursorVisible());
+ EXPECT_TRUE(cursor_manager->IsMouseEventsEnabled());
+}
+
+TEST_F(WindowManagerTest, TestCursorClientObserver) {
+ aura::test::EventGenerator& generator = GetEventGenerator();
+ views::corewm::CursorManager* cursor_manager =
+ ash::Shell::GetInstance()->cursor_manager();
+
+ scoped_ptr<aura::Window> w1(CreateTestWindowInShell(
+ SK_ColorWHITE, -1, gfx::Rect(0, 0, 100, 100)));
+ wm::ActivateWindow(w1.get());
+
+ // Add two observers. Both should have OnCursorVisibilityChanged()
+ // invoked when an event changes the visibility of the cursor.
+ TestingCursorClientObserver observer_a;
+ TestingCursorClientObserver observer_b;
+ cursor_manager->AddObserver(&observer_a);
+ cursor_manager->AddObserver(&observer_b);
+
+ // Initial state before any events have been sent.
+ observer_a.reset();
+ observer_b.reset();
+ EXPECT_FALSE(observer_a.did_visibility_change());
+ EXPECT_FALSE(observer_b.did_visibility_change());
+ EXPECT_FALSE(observer_a.is_cursor_visible());
+ EXPECT_FALSE(observer_b.is_cursor_visible());
+
+ // Keypress should hide the cursor.
+ generator.PressKey(ui::VKEY_A, ui::EF_NONE);
+ EXPECT_TRUE(observer_a.did_visibility_change());
+ EXPECT_TRUE(observer_b.did_visibility_change());
+ EXPECT_FALSE(observer_a.is_cursor_visible());
+ EXPECT_FALSE(observer_b.is_cursor_visible());
+
+ // Mouse move should show the cursor.
+ observer_a.reset();
+ observer_b.reset();
+ generator.MoveMouseTo(50, 50);
+ EXPECT_TRUE(observer_a.did_visibility_change());
+ EXPECT_TRUE(observer_b.did_visibility_change());
+ EXPECT_TRUE(observer_a.is_cursor_visible());
+ EXPECT_TRUE(observer_b.is_cursor_visible());
+
+ // Remove observer_b. Its OnCursorVisibilityChanged() should
+ // not be invoked past this point.
+ cursor_manager->RemoveObserver(&observer_b);
+
+ // Gesture tap should hide the cursor.
+ observer_a.reset();
+ observer_b.reset();
+ generator.GestureTapAt(gfx::Point(25, 25));
+ EXPECT_TRUE(observer_a.did_visibility_change());
+ EXPECT_FALSE(observer_b.did_visibility_change());
+ EXPECT_FALSE(observer_a.is_cursor_visible());
+
+ // Mouse move should show the cursor.
+ observer_a.reset();
+ observer_b.reset();
+ generator.MoveMouseTo(50, 50);
+ EXPECT_TRUE(observer_a.did_visibility_change());
+ EXPECT_FALSE(observer_b.did_visibility_change());
+ EXPECT_TRUE(observer_a.is_cursor_visible());
+}
+
+} // namespace ash
diff --git a/chromium/ash/wm/window_modality_controller_unittest.cc b/chromium/ash/wm/window_modality_controller_unittest.cc
new file mode 100644
index 00000000000..d7def40f16b
--- /dev/null
+++ b/chromium/ash/wm/window_modality_controller_unittest.cc
@@ -0,0 +1,557 @@
+// Copyright (c) 2012 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.
+
+#include "ui/views/corewm/window_modality_controller.h"
+
+#include "ash/shell.h"
+#include "ash/test/ash_test_base.h"
+#include "ash/wm/window_util.h"
+#include "ui/aura/client/aura_constants.h"
+#include "ui/aura/root_window.h"
+#include "ui/aura/test/event_generator.h"
+#include "ui/aura/test/test_window_delegate.h"
+#include "ui/aura/test/test_windows.h"
+#include "ui/aura/window.h"
+#include "ui/base/ui_base_types.h"
+#include "ui/views/test/capture_tracking_view.h"
+#include "ui/views/test/child_modal_window.h"
+#include "ui/views/widget/widget.h"
+
+namespace ash {
+namespace internal {
+
+typedef test::AshTestBase WindowModalityControllerTest;
+
+namespace {
+
+bool ValidateStacking(aura::Window* parent, int ids[], int count) {
+ for (int i = 0; i < count; ++i) {
+ if (parent->children().at(i)->id() != ids[i])
+ return false;
+ }
+ return true;
+}
+
+} // namespace
+
+// Creates three windows, w1, w11, and w12. w11 is a non-modal transient, w12 is
+// a modal transient.
+// Validates:
+// - it should be possible to activate w12 even when w11 is open.
+// - activating w1 activates w12 and updates stacking order appropriately.
+// - closing a window passes focus up the stack.
+TEST_F(WindowModalityControllerTest, BasicActivation) {
+ aura::test::TestWindowDelegate d;
+ scoped_ptr<aura::Window> w1(
+ CreateTestWindowInShellWithDelegate(&d, -1, gfx::Rect()));
+ scoped_ptr<aura::Window> w11(
+ CreateTestWindowInShellWithDelegate(&d, -11, gfx::Rect()));
+ scoped_ptr<aura::Window> w12(
+ CreateTestWindowInShellWithDelegate(&d, -12, gfx::Rect()));
+
+ w1->AddTransientChild(w11.get());
+ wm::ActivateWindow(w1.get());
+ EXPECT_TRUE(wm::IsActiveWindow(w1.get()));
+ wm::ActivateWindow(w11.get());
+ EXPECT_TRUE(wm::IsActiveWindow(w11.get()));
+
+ w12->SetProperty(aura::client::kModalKey, ui::MODAL_TYPE_WINDOW);
+ w1->AddTransientChild(w12.get());
+ wm::ActivateWindow(w12.get());
+ EXPECT_TRUE(wm::IsActiveWindow(w12.get()));
+
+ wm::ActivateWindow(w11.get());
+ EXPECT_TRUE(wm::IsActiveWindow(w11.get()));
+
+ int check1[] = { -1, -12, -11 };
+ EXPECT_TRUE(ValidateStacking(w1->parent(), check1, arraysize(check1)));
+
+ wm::ActivateWindow(w1.get());
+ EXPECT_TRUE(wm::IsActiveWindow(w12.get()));
+ // Transient children are always stacked above their transient parent, which
+ // is why this order is not -11, -1, -12.
+ int check2[] = { -1, -11, -12 };
+ EXPECT_TRUE(ValidateStacking(w1->parent(), check2, arraysize(check2)));
+
+ w12.reset();
+ EXPECT_TRUE(wm::IsActiveWindow(w11.get()));
+ w11.reset();
+ EXPECT_TRUE(wm::IsActiveWindow(w1.get()));
+}
+
+// Create two toplevel windows w1 and w2, and nest two modals w11 and w111 below
+// w1.
+// Validates:
+// - activating w1 while w11/w111 is showing always activates most deeply nested
+// descendant.
+// - closing a window passes focus up the stack.
+TEST_F(WindowModalityControllerTest, NestedModals) {
+ aura::test::TestWindowDelegate d;
+ scoped_ptr<aura::Window> w1(
+ CreateTestWindowInShellWithDelegate(&d, -1, gfx::Rect()));
+ scoped_ptr<aura::Window> w11(
+ CreateTestWindowInShellWithDelegate(&d, -11, gfx::Rect()));
+ scoped_ptr<aura::Window> w111(
+ CreateTestWindowInShellWithDelegate(&d, -111, gfx::Rect()));
+ scoped_ptr<aura::Window> w2(
+ CreateTestWindowInShellWithDelegate(&d, -2, gfx::Rect()));
+
+ w1->AddTransientChild(w11.get());
+ w11->AddTransientChild(w111.get());
+
+ wm::ActivateWindow(w1.get());
+ EXPECT_TRUE(wm::IsActiveWindow(w1.get()));
+ wm::ActivateWindow(w2.get());
+ EXPECT_TRUE(wm::IsActiveWindow(w2.get()));
+
+ // Set up modality.
+ w11->SetProperty(aura::client::kModalKey, ui::MODAL_TYPE_WINDOW);
+ w111->SetProperty(aura::client::kModalKey, ui::MODAL_TYPE_WINDOW);
+
+ wm::ActivateWindow(w1.get());
+ EXPECT_TRUE(wm::IsActiveWindow(w111.get()));
+ int check1[] = { -2, -1, -11, -111 };
+ EXPECT_TRUE(ValidateStacking(w1->parent(), check1, arraysize(check1)));
+
+ wm::ActivateWindow(w11.get());
+ EXPECT_TRUE(wm::IsActiveWindow(w111.get()));
+ EXPECT_TRUE(ValidateStacking(w1->parent(), check1, arraysize(check1)));
+
+ wm::ActivateWindow(w111.get());
+ EXPECT_TRUE(wm::IsActiveWindow(w111.get()));
+ EXPECT_TRUE(ValidateStacking(w1->parent(), check1, arraysize(check1)));
+
+ wm::ActivateWindow(w2.get());
+ EXPECT_TRUE(wm::IsActiveWindow(w2.get()));
+ int check2[] = { -1, -11, -111, -2 };
+ EXPECT_TRUE(ValidateStacking(w1->parent(), check2, arraysize(check2)));
+
+ w2.reset();
+ EXPECT_TRUE(wm::IsActiveWindow(w111.get()));
+ w111.reset();
+ EXPECT_TRUE(wm::IsActiveWindow(w11.get()));
+ w11.reset();
+ EXPECT_TRUE(wm::IsActiveWindow(w1.get()));
+}
+
+// Create two toplevel windows w1 and w2, and nest two modals w11 and w111 below
+// w1.
+// Validates:
+// - destroying w11 while w111 is focused activates w1.
+TEST_F(WindowModalityControllerTest, NestedModalsOuterClosed) {
+ aura::test::TestWindowDelegate d;
+ scoped_ptr<aura::Window> w1(
+ CreateTestWindowInShellWithDelegate(&d, -1, gfx::Rect()));
+ scoped_ptr<aura::Window> w11(
+ CreateTestWindowInShellWithDelegate(&d, -11, gfx::Rect()));
+ // |w111| will be owned and deleted by |w11|.
+ aura::Window* w111 =
+ CreateTestWindowInShellWithDelegate(&d, -111, gfx::Rect());
+ scoped_ptr<aura::Window> w2(
+ CreateTestWindowInShellWithDelegate(&d, -2, gfx::Rect()));
+
+ w1->AddTransientChild(w11.get());
+ w11->AddTransientChild(w111);
+
+ wm::ActivateWindow(w1.get());
+ EXPECT_TRUE(wm::IsActiveWindow(w1.get()));
+ wm::ActivateWindow(w2.get());
+ EXPECT_TRUE(wm::IsActiveWindow(w2.get()));
+
+ // Set up modality.
+ w11->SetProperty(aura::client::kModalKey, ui::MODAL_TYPE_WINDOW);
+ w111->SetProperty(aura::client::kModalKey, ui::MODAL_TYPE_WINDOW);
+
+ wm::ActivateWindow(w1.get());
+ EXPECT_TRUE(wm::IsActiveWindow(w111));
+
+ w111->Hide();
+ EXPECT_TRUE(wm::IsActiveWindow(w11.get()));
+
+ // TODO(oshima): Re-showing doesn't set the focus back to
+ // modal window. There is no such use case right now, but it
+ // probably should.
+
+ w11.reset();
+ EXPECT_TRUE(wm::IsActiveWindow(w1.get()));
+}
+
+// Modality also prevents events from being passed to the transient parent.
+TEST_F(WindowModalityControllerTest, Events) {
+ aura::test::TestWindowDelegate d;
+ scoped_ptr<aura::Window> w1(CreateTestWindowInShellWithDelegate(&d, -1,
+ gfx::Rect(0, 0, 100, 100)));
+ scoped_ptr<aura::Window> w11(CreateTestWindowInShellWithDelegate(&d, -11,
+ gfx::Rect(20, 20, 50, 50)));
+
+ w1->AddTransientChild(w11.get());
+
+ {
+ // Clicking a point within w1 should activate that window.
+ aura::test::EventGenerator generator(Shell::GetPrimaryRootWindow(),
+ gfx::Point(10, 10));
+ generator.ClickLeftButton();
+ EXPECT_TRUE(wm::IsActiveWindow(w1.get()));
+ }
+
+ w11->SetProperty(aura::client::kModalKey, ui::MODAL_TYPE_WINDOW);
+
+ {
+ // Clicking a point within w1 should activate w11.
+ aura::test::EventGenerator generator(Shell::GetPrimaryRootWindow(),
+ gfx::Point(10, 10));
+ generator.ClickLeftButton();
+ EXPECT_TRUE(wm::IsActiveWindow(w11.get()));
+ }
+}
+
+// Creates windows w1 and non activatiable child w11. Creates transient window
+// w2 and adds it as a transeint child of w1. Ensures that w2 is parented to
+// the parent of w1, and that GetModalTransient(w11) returns w2.
+TEST_F(WindowModalityControllerTest, GetModalTransient) {
+ aura::test::TestWindowDelegate d;
+ scoped_ptr<aura::Window> w1(
+ CreateTestWindowInShellWithDelegate(&d, -1, gfx::Rect()));
+ scoped_ptr<aura::Window> w11(
+ aura::test::CreateTestWindowWithDelegate(&d, -11, gfx::Rect(), w1.get()));
+ scoped_ptr<aura::Window> w2(
+ CreateTestWindowInShellWithDelegate(&d, -2, gfx::Rect()));
+ w2->SetProperty(aura::client::kModalKey, ui::MODAL_TYPE_WINDOW);
+
+ aura::Window* wt;
+ wt = views::corewm::GetModalTransient(w1.get());
+ ASSERT_EQ(static_cast<aura::Window*>(NULL), wt);
+
+ // Parent w2 to w1. It should get parented to the parent of w1.
+ w1->AddTransientChild(w2.get());
+ ASSERT_EQ(2U, w1->parent()->children().size());
+ EXPECT_EQ(-2, w1->parent()->children().at(1)->id());
+
+ // Request the modal transient window for w1, it should be w2.
+ wt = views::corewm::GetModalTransient(w1.get());
+ ASSERT_NE(static_cast<aura::Window*>(NULL), wt);
+ EXPECT_EQ(-2, wt->id());
+
+ // Request the modal transient window for w11, it should also be w2.
+ wt = views::corewm::GetModalTransient(w11.get());
+ ASSERT_NE(static_cast<aura::Window*>(NULL), wt);
+ EXPECT_EQ(-2, wt->id());
+}
+
+// Verifies we generate a capture lost when showing a modal window.
+TEST_F(WindowModalityControllerTest, ChangeCapture) {
+ views::Widget* widget = views::Widget::CreateWindowWithContext(
+ NULL, Shell::GetPrimaryRootWindow());
+ scoped_ptr<aura::Window> widget_window(widget->GetNativeView());
+ views::test::CaptureTrackingView* view = new views::test::CaptureTrackingView;
+ widget->client_view()->AddChildView(view);
+ widget->SetBounds(gfx::Rect(0, 0, 200, 200));
+ view->SetBoundsRect(widget->client_view()->GetLocalBounds());
+ widget->Show();
+
+ gfx::Point center(view->width() / 2, view->height() / 2);
+ views::View::ConvertPointToScreen(view, &center);
+ aura::test::EventGenerator generator(Shell::GetPrimaryRootWindow(), center);
+ generator.PressLeftButton();
+ EXPECT_TRUE(view->got_press());
+
+ views::Widget* modal_widget =
+ views::Widget::CreateWindowWithParent(NULL, widget->GetNativeView());
+ scoped_ptr<aura::Window> modal_window(modal_widget->GetNativeView());
+ modal_window->SetProperty(aura::client::kModalKey, ui::MODAL_TYPE_WINDOW);
+ views::test::CaptureTrackingView* modal_view =
+ new views::test::CaptureTrackingView;
+ modal_widget->client_view()->AddChildView(modal_view);
+ modal_widget->SetBounds(gfx::Rect(50, 50, 200, 200));
+ modal_view->SetBoundsRect(modal_widget->client_view()->GetLocalBounds());
+ modal_widget->Show();
+
+ EXPECT_TRUE(view->got_capture_lost());
+ generator.ReleaseLeftButton();
+
+ view->reset();
+
+ EXPECT_FALSE(modal_view->got_capture_lost());
+ EXPECT_FALSE(modal_view->got_press());
+
+ gfx::Point modal_center(modal_view->width() / 2, modal_view->height() / 2);
+ views::View::ConvertPointToScreen(modal_view, &modal_center);
+ generator.MoveMouseTo(modal_center, 1);
+ generator.PressLeftButton();
+ EXPECT_TRUE(modal_view->got_press());
+ EXPECT_FALSE(modal_view->got_capture_lost());
+ EXPECT_FALSE(view->got_capture_lost());
+ EXPECT_FALSE(view->got_press());
+}
+
+class TouchTrackerWindowDelegate : public aura::test::TestWindowDelegate {
+ public:
+ TouchTrackerWindowDelegate() : received_touch_(false) {}
+ virtual ~TouchTrackerWindowDelegate() {}
+
+ void reset() {
+ received_touch_ = false;
+ }
+
+ bool received_touch() const { return received_touch_; }
+
+ private:
+ // Overridden from aura::test::TestWindowDelegate.
+ virtual void OnTouchEvent(ui::TouchEvent* event) OVERRIDE {
+ received_touch_ = true;
+ aura::test::TestWindowDelegate::OnTouchEvent(event);
+ }
+
+ bool received_touch_;
+
+ DISALLOW_COPY_AND_ASSIGN(TouchTrackerWindowDelegate);
+};
+
+// Modality should prevent events from being passed to the transient parent.
+TEST_F(WindowModalityControllerTest, TouchEvent) {
+ TouchTrackerWindowDelegate d1;
+ scoped_ptr<aura::Window> w1(CreateTestWindowInShellWithDelegate(&d1,
+ -1, gfx::Rect(0, 0, 100, 100)));
+ TouchTrackerWindowDelegate d11;
+ scoped_ptr<aura::Window> w11(CreateTestWindowInShellWithDelegate(&d11,
+ -11, gfx::Rect(20, 20, 50, 50)));
+
+ w1->AddTransientChild(w11.get());
+ d1.reset();
+ d11.reset();
+
+ {
+ // Clicking a point within w1 should activate that window.
+ aura::test::EventGenerator generator(Shell::GetPrimaryRootWindow(),
+ gfx::Point(10, 10));
+ generator.PressMoveAndReleaseTouchTo(gfx::Point(10, 10));
+ EXPECT_TRUE(wm::IsActiveWindow(w1.get()));
+ EXPECT_TRUE(d1.received_touch());
+ EXPECT_FALSE(d11.received_touch());
+ }
+
+ w11->SetProperty(aura::client::kModalKey, ui::MODAL_TYPE_WINDOW);
+ d1.reset();
+ d11.reset();
+
+ {
+ // Clicking a point within w1 should activate w11.
+ aura::test::EventGenerator generator(Shell::GetPrimaryRootWindow(),
+ gfx::Point(10, 10));
+ generator.PressMoveAndReleaseTouchTo(gfx::Point(10, 10));
+ EXPECT_TRUE(wm::IsActiveWindow(w11.get()));
+ EXPECT_FALSE(d1.received_touch());
+ EXPECT_FALSE(d11.received_touch());
+ }
+}
+
+// Child-modal test.
+// Creates:
+// - A |parent| window that hosts a |modal_parent| window within itself. The
+// |parent| and |modal_parent| windows are not the same window. The
+// |modal_parent| window is not activatable, because it's contained within the
+// |parent| window.
+// - A |child| window with parent window |parent|, but is modal to
+// |modal_parent| window.
+// Validates:
+// - Clicking on the |modal_parent| should activate the |child| window.
+// - Clicking on the |parent| window outside of the |modal_parent| bounds should
+// activate the |parent| window.
+// - Clicking on the |child| while |parent| is active should activate the
+// |child| window.
+// - Focus should follow the active window.
+TEST_F(WindowModalityControllerTest, ChildModal) {
+ views::test::ChildModalParent* delegate =
+ new views::test::ChildModalParent(CurrentContext());
+ views::Widget* widget = views::Widget::CreateWindowWithContextAndBounds(
+ delegate, CurrentContext(), gfx::Rect(0, 0, 400, 400));
+ widget->Show();
+
+ aura::Window* parent = widget->GetNativeView();
+ EXPECT_TRUE(wm::IsActiveWindow(parent));
+
+ aura::Window* modal_parent = delegate->GetModalParent();
+ EXPECT_NE(static_cast<aura::Window*>(NULL), modal_parent);
+ EXPECT_NE(parent, modal_parent);
+ EXPECT_FALSE(wm::IsActiveWindow(modal_parent));
+
+ delegate->ShowChild();
+ aura::Window* child = delegate->GetChild();
+ EXPECT_NE(static_cast<aura::Window*>(NULL), child);
+
+ EXPECT_TRUE(wm::IsActiveWindow(child));
+ EXPECT_FALSE(wm::IsActiveWindow(modal_parent));
+ EXPECT_FALSE(wm::IsActiveWindow(parent));
+
+ EXPECT_TRUE(child->HasFocus());
+ EXPECT_FALSE(modal_parent->HasFocus());
+ EXPECT_FALSE(parent->HasFocus());
+
+ wm::ActivateWindow(modal_parent);
+
+ EXPECT_TRUE(wm::IsActiveWindow(child));
+ EXPECT_FALSE(wm::IsActiveWindow(modal_parent));
+ EXPECT_FALSE(wm::IsActiveWindow(parent));
+
+ EXPECT_TRUE(child->HasFocus());
+ EXPECT_FALSE(modal_parent->HasFocus());
+ EXPECT_FALSE(parent->HasFocus());
+
+ wm::ActivateWindow(parent);
+
+ EXPECT_FALSE(wm::IsActiveWindow(child));
+ EXPECT_FALSE(wm::IsActiveWindow(modal_parent));
+ EXPECT_TRUE(wm::IsActiveWindow(parent));
+
+ EXPECT_FALSE(child->HasFocus());
+ EXPECT_FALSE(modal_parent->HasFocus());
+ EXPECT_TRUE(parent->HasFocus());
+
+ wm::ActivateWindow(child);
+
+ EXPECT_TRUE(wm::IsActiveWindow(child));
+ EXPECT_FALSE(wm::IsActiveWindow(modal_parent));
+ EXPECT_FALSE(wm::IsActiveWindow(parent));
+
+ EXPECT_TRUE(child->HasFocus());
+ EXPECT_FALSE(modal_parent->HasFocus());
+ EXPECT_FALSE(parent->HasFocus());
+}
+
+// Same as |ChildModal| test, but using |EventGenerator| rather than bypassing
+// it by calling |ActivateWindow|.
+TEST_F(WindowModalityControllerTest, ChildModalEventGenerator) {
+ views::test::ChildModalParent* delegate =
+ new views::test::ChildModalParent(CurrentContext());
+ views::Widget* widget = views::Widget::CreateWindowWithContextAndBounds(
+ delegate, CurrentContext(), gfx::Rect(0, 0, 400, 400));
+ widget->Show();
+
+ aura::Window* parent = widget->GetNativeView();
+ EXPECT_TRUE(wm::IsActiveWindow(parent));
+
+ aura::Window* modal_parent = delegate->GetModalParent();
+ EXPECT_NE(static_cast<aura::Window*>(NULL), modal_parent);
+ EXPECT_NE(parent, modal_parent);
+ EXPECT_FALSE(wm::IsActiveWindow(modal_parent));
+
+ delegate->ShowChild();
+ aura::Window* child = delegate->GetChild();
+ EXPECT_NE(static_cast<aura::Window*>(NULL), child);
+
+ EXPECT_TRUE(wm::IsActiveWindow(child));
+ EXPECT_FALSE(wm::IsActiveWindow(modal_parent));
+ EXPECT_FALSE(wm::IsActiveWindow(parent));
+
+ EXPECT_TRUE(child->HasFocus());
+ EXPECT_FALSE(modal_parent->HasFocus());
+ EXPECT_FALSE(parent->HasFocus());
+
+ {
+ aura::test::EventGenerator generator(
+ Shell::GetPrimaryRootWindow(),
+ parent->bounds().origin() +
+ gfx::Vector2d(10, parent->bounds().height() - 10));
+ generator.ClickLeftButton();
+ generator.ClickLeftButton();
+
+ EXPECT_TRUE(wm::IsActiveWindow(child));
+ EXPECT_FALSE(wm::IsActiveWindow(modal_parent));
+ EXPECT_FALSE(wm::IsActiveWindow(parent));
+
+ EXPECT_TRUE(child->HasFocus());
+ EXPECT_FALSE(modal_parent->HasFocus());
+ EXPECT_FALSE(parent->HasFocus());
+ }
+
+ {
+ aura::test::EventGenerator generator(
+ Shell::GetPrimaryRootWindow(),
+ parent->bounds().origin() + gfx::Vector2d(10, 10));
+ generator.ClickLeftButton();
+
+ EXPECT_FALSE(wm::IsActiveWindow(child));
+ EXPECT_FALSE(wm::IsActiveWindow(modal_parent));
+ EXPECT_TRUE(wm::IsActiveWindow(parent));
+
+ EXPECT_FALSE(child->HasFocus());
+ EXPECT_FALSE(modal_parent->HasFocus());
+ EXPECT_TRUE(parent->HasFocus());
+ }
+
+ {
+ aura::test::EventGenerator generator(
+ Shell::GetPrimaryRootWindow(),
+ child->bounds().origin() + gfx::Vector2d(10, 10));
+ generator.ClickLeftButton();
+
+ EXPECT_TRUE(wm::IsActiveWindow(child));
+ EXPECT_FALSE(wm::IsActiveWindow(modal_parent));
+ EXPECT_FALSE(wm::IsActiveWindow(parent));
+
+ EXPECT_TRUE(child->HasFocus());
+ EXPECT_FALSE(modal_parent->HasFocus());
+ EXPECT_FALSE(parent->HasFocus());
+ }
+}
+
+// Window-modal test for the case when the originally clicked window is an
+// ancestor of the modal parent.
+TEST_F(WindowModalityControllerTest, WindowModalAncestor) {
+ aura::test::TestWindowDelegate d;
+ scoped_ptr<aura::Window> w1(
+ CreateTestWindowInShellWithDelegate(&d, -1, gfx::Rect()));
+ scoped_ptr<aura::Window> w2(
+ aura::test::CreateTestWindowWithDelegate(&d, -11, gfx::Rect(), w1.get()));
+ scoped_ptr<aura::Window> w3(
+ aura::test::CreateTestWindowWithDelegate(&d, -11, gfx::Rect(), w2.get()));
+ scoped_ptr<aura::Window> w4(
+ CreateTestWindowInShellWithDelegate(&d, -2, gfx::Rect()));
+ w4->SetProperty(aura::client::kModalKey, ui::MODAL_TYPE_WINDOW);
+ w1->AddTransientChild(w4.get());
+
+ wm::ActivateWindow(w1.get());
+ EXPECT_TRUE(wm::IsActiveWindow(w4.get()));
+
+ wm::ActivateWindow(w2.get());
+ EXPECT_TRUE(wm::IsActiveWindow(w4.get()));
+
+ wm::ActivateWindow(w3.get());
+ EXPECT_TRUE(wm::IsActiveWindow(w4.get()));
+
+ wm::ActivateWindow(w4.get());
+ EXPECT_TRUE(wm::IsActiveWindow(w4.get()));
+}
+
+// Child-modal test for the case when the originally clicked window is an
+// ancestor of the modal parent.
+TEST_F(WindowModalityControllerTest, ChildModalAncestor) {
+ aura::test::TestWindowDelegate d;
+ scoped_ptr<aura::Window> w1(
+ CreateTestWindowInShellWithDelegate(&d, -1, gfx::Rect()));
+ scoped_ptr<aura::Window> w2(
+ aura::test::CreateTestWindowWithDelegate(&d, -11, gfx::Rect(), w1.get()));
+ scoped_ptr<aura::Window> w3(
+ aura::test::CreateTestWindowWithDelegate(&d, -11, gfx::Rect(), w2.get()));
+ scoped_ptr<aura::Window> w4(
+ CreateTestWindowInShellWithDelegate(&d, -2, gfx::Rect()));
+ w4->SetProperty(aura::client::kModalKey, ui::MODAL_TYPE_CHILD);
+ views::corewm::SetModalParent(w4.get(), w2.get());
+ w1->AddTransientChild(w4.get());
+
+ wm::ActivateWindow(w1.get());
+ EXPECT_TRUE(wm::IsActiveWindow(w1.get()));
+
+ wm::ActivateWindow(w2.get());
+ EXPECT_TRUE(wm::IsActiveWindow(w4.get()));
+
+ wm::ActivateWindow(w3.get());
+ EXPECT_TRUE(wm::IsActiveWindow(w4.get()));
+
+ wm::ActivateWindow(w4.get());
+ EXPECT_TRUE(wm::IsActiveWindow(w4.get()));
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/wm/window_properties.cc b/chromium/ash/wm/window_properties.cc
new file mode 100644
index 00000000000..1db17a82f56
--- /dev/null
+++ b/chromium/ash/wm/window_properties.cc
@@ -0,0 +1,36 @@
+// Copyright (c) 2012 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.
+
+#include "ash/wm/window_properties.h"
+
+#include "ash/root_window_controller.h"
+#include "ash/wm/frame_painter.h"
+#include "ui/aura/window_property.h"
+#include "ui/gfx/rect.h"
+
+DECLARE_EXPORTED_WINDOW_PROPERTY_TYPE(ASH_EXPORT, ash::FramePainter*);
+DECLARE_WINDOW_PROPERTY_TYPE(ash::internal::RootWindowController*);
+
+namespace ash {
+namespace internal {
+DEFINE_WINDOW_PROPERTY_KEY(bool, kContinueDragAfterReparent, false);
+DEFINE_WINDOW_PROPERTY_KEY(bool, kFullscreenUsesMinimalChromeKey, false);
+DEFINE_WINDOW_PROPERTY_KEY(bool, kIgnoreSoloWindowFramePainterPolicy, false);
+DEFINE_WINDOW_PROPERTY_KEY(bool, kIgnoredByShelfKey, false);
+DEFINE_WINDOW_PROPERTY_KEY(bool, kPanelAttachedKey, true);
+DEFINE_WINDOW_PROPERTY_KEY(RootWindowController*,
+ kRootWindowControllerKey, NULL);
+DEFINE_WINDOW_PROPERTY_KEY(bool, kSoloWindowHeaderKey, false);
+DEFINE_WINDOW_PROPERTY_KEY(bool, kStayInSameRootWindowKey, false);
+DEFINE_WINDOW_PROPERTY_KEY(bool, kUsesScreenCoordinatesKey, false);
+DEFINE_WINDOW_PROPERTY_KEY(bool, kUserChangedWindowPositionOrSizeKey, false);
+DEFINE_OWNED_WINDOW_PROPERTY_KEY(gfx::Rect,
+ kPreAutoManagedWindowBoundsKey,
+ NULL);
+DEFINE_WINDOW_PROPERTY_KEY(bool, kWindowPositionManagedKey, false);
+DEFINE_WINDOW_PROPERTY_KEY(bool, kWindowRestoresToRestoreBounds, false);
+DEFINE_WINDOW_PROPERTY_KEY(bool, kWindowTrackedByWorkspaceKey, true);
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/wm/window_properties.h b/chromium/ash/wm/window_properties.h
new file mode 100644
index 00000000000..d32e368db43
--- /dev/null
+++ b/chromium/ash/wm/window_properties.h
@@ -0,0 +1,98 @@
+// Copyright (c) 2012 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 ASH_WM_WINDOW_PROPERTIES_H_
+#define ASH_WM_WINDOW_PROPERTIES_H_
+
+#include "ash/ash_export.h"
+#include "ash/wm/property_util.h"
+#include "ui/aura/window.h"
+#include "ui/base/ui_base_types.h"
+
+namespace gfx {
+class Rect;
+}
+
+namespace ash {
+class FramePainter;
+namespace internal {
+class RootWindowController;
+
+// Shell-specific window property keys.
+
+// Alphabetical sort.
+
+// A property key to indicate that an in progress drag should be continued
+// after the window is reparented to another container.
+extern const aura::WindowProperty<bool>* const kContinueDragAfterReparent;
+
+// A property key to store display_id an aura::RootWindow is mapped to.
+extern const aura::WindowProperty<int64>* const kDisplayIdKey;
+
+// A property key to indicate whether there is any chrome at all that cannot be
+// hidden when the window is fullscreen. This is unrelated to whether the full
+// chrome can be revealed by hovering the mouse at the top of the screen.
+ASH_EXPORT extern const aura::WindowProperty<bool>* const
+ kFullscreenUsesMinimalChromeKey;
+
+// A property key to disable the frame painter policy for solo windows.
+// It is only available for root windows.
+ASH_EXPORT extern const aura::WindowProperty<bool>* const
+ kIgnoreSoloWindowFramePainterPolicy;
+
+// True if the window is ignored by the shelf layout manager for purposes of
+// darkening the shelf.
+extern const aura::WindowProperty<bool>* const
+ kIgnoredByShelfKey;
+
+// True if this window is an attached panel.
+ASH_EXPORT extern const aura::WindowProperty<bool>* const kPanelAttachedKey;
+
+extern const aura::WindowProperty<RootWindowController*>* const
+ kRootWindowControllerKey;
+
+// RootWindow property to indicate if the window in the active workspace should
+// use the transparent "solo-window" header style.
+ASH_EXPORT extern const aura::WindowProperty<bool>* const
+ kSoloWindowHeaderKey;
+
+// If this is set to true, the window stays in the same root window
+// even if the bounds outside of its root window is set.
+// This is exported as it's used in the tests.
+ASH_EXPORT extern const aura::WindowProperty<bool>* const
+ kStayInSameRootWindowKey;
+
+// A property key to remember if a windows position or size was changed by a
+// user.
+ASH_EXPORT extern const aura::WindowProperty<bool>* const
+ kUserChangedWindowPositionOrSizeKey;
+
+// A property to remember the window position which was set before the
+// auto window position manager changed the window bounds, so that it can get
+// restored when only this one window gets shown.
+ASH_EXPORT extern const aura::WindowProperty<gfx::Rect*>* const
+ kPreAutoManagedWindowBoundsKey;
+
+// Property to tell if the container uses the screen coordinates.
+extern const aura::WindowProperty<bool>* const kUsesScreenCoordinatesKey;
+
+// A property key to remember if a windows position can be managed by the
+// workspace manager or not.
+ASH_EXPORT extern const aura::WindowProperty<bool>* const
+ kWindowPositionManagedKey;
+
+// A property key to tell the workspace layout manager to always restore the
+// window to the restore-bounds (false by default).
+extern const aura::WindowProperty<bool>* const kWindowRestoresToRestoreBounds;
+
+// True if the window is controlled by the workspace manager.
+extern const aura::WindowProperty<bool>* const
+ kWindowTrackedByWorkspaceKey;
+
+// Alphabetical sort.
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_WM_WINDOW_PROPERTIES_H_
diff --git a/chromium/ash/wm/window_resizer.cc b/chromium/ash/wm/window_resizer.cc
new file mode 100644
index 00000000000..1ea317a1a3f
--- /dev/null
+++ b/chromium/ash/wm/window_resizer.cc
@@ -0,0 +1,425 @@
+// Copyright (c) 2012 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.
+
+#include "ash/wm/window_resizer.h"
+
+#include "ash/screen_ash.h"
+#include "ash/shell.h"
+#include "ash/shell_window_ids.h"
+#include "ash/wm/coordinate_conversion.h"
+#include "ash/wm/dock/docked_window_layout_manager.h"
+#include "ash/wm/property_util.h"
+#include "ash/wm/window_util.h"
+#include "ui/aura/client/aura_constants.h"
+#include "ui/aura/root_window.h"
+#include "ui/aura/window.h"
+#include "ui/aura/window_delegate.h"
+#include "ui/base/hit_test.h"
+#include "ui/base/ui_base_types.h"
+#include "ui/compositor/scoped_layer_animation_settings.h"
+#include "ui/gfx/display.h"
+#include "ui/gfx/screen.h"
+
+namespace ash {
+
+namespace {
+
+int GetPositionChangeDirectionForWindowComponent(int window_component) {
+ int pos_change_direction = WindowResizer::kBoundsChangeDirection_None;
+ switch (window_component) {
+ case HTTOPLEFT:
+ case HTBOTTOMRIGHT:
+ case HTGROWBOX:
+ case HTCAPTION:
+ pos_change_direction |=
+ WindowResizer::kBoundsChangeDirection_Horizontal |
+ WindowResizer::kBoundsChangeDirection_Vertical;
+ break;
+ case HTTOP:
+ case HTTOPRIGHT:
+ case HTBOTTOM:
+ pos_change_direction |= WindowResizer::kBoundsChangeDirection_Vertical;
+ break;
+ case HTBOTTOMLEFT:
+ case HTRIGHT:
+ case HTLEFT:
+ pos_change_direction |= WindowResizer::kBoundsChangeDirection_Horizontal;
+ break;
+ default:
+ break;
+ }
+ return pos_change_direction;
+}
+
+int GetSizeChangeDirectionForWindowComponent(int window_component) {
+ int size_change_direction = WindowResizer::kBoundsChangeDirection_None;
+ switch (window_component) {
+ case HTTOPLEFT:
+ case HTTOPRIGHT:
+ case HTBOTTOMLEFT:
+ case HTBOTTOMRIGHT:
+ case HTGROWBOX:
+ case HTCAPTION:
+ size_change_direction |=
+ WindowResizer::kBoundsChangeDirection_Horizontal |
+ WindowResizer::kBoundsChangeDirection_Vertical;
+ break;
+ case HTTOP:
+ case HTBOTTOM:
+ size_change_direction |= WindowResizer::kBoundsChangeDirection_Vertical;
+ break;
+ case HTRIGHT:
+ case HTLEFT:
+ size_change_direction |= WindowResizer::kBoundsChangeDirection_Horizontal;
+ break;
+ default:
+ break;
+ }
+ return size_change_direction;
+}
+
+// Returns true for resize components along the right edge, where a drag in
+// positive x will make the window larger.
+bool IsRightEdge(int window_component) {
+ return window_component == HTTOPRIGHT ||
+ window_component == HTRIGHT ||
+ window_component == HTBOTTOMRIGHT ||
+ window_component == HTGROWBOX;
+}
+
+} // namespace
+
+// static
+const int WindowResizer::kBoundsChange_None = 0;
+// static
+const int WindowResizer::kBoundsChange_Repositions = 1;
+// static
+const int WindowResizer::kBoundsChange_Resizes = 2;
+
+// static
+const int WindowResizer::kBoundsChangeDirection_None = 0;
+// static
+const int WindowResizer::kBoundsChangeDirection_Horizontal = 1;
+// static
+const int WindowResizer::kBoundsChangeDirection_Vertical = 2;
+
+WindowResizer::Details::Details()
+ : window(NULL),
+ window_component(HTNOWHERE),
+ bounds_change(0),
+ position_change_direction(0),
+ size_change_direction(0),
+ is_resizable(false),
+ source(aura::client::WINDOW_MOVE_SOURCE_MOUSE) {
+}
+
+WindowResizer::Details::Details(aura::Window* window,
+ const gfx::Point& location,
+ int window_component,
+ aura::client::WindowMoveSource source)
+ : window(window),
+ initial_bounds_in_parent(window->bounds()),
+ restore_bounds(gfx::Rect()),
+ initial_location_in_parent(location),
+ initial_opacity(window->layer()->opacity()),
+ window_component(window_component),
+ bounds_change(GetBoundsChangeForWindowComponent(window_component)),
+ position_change_direction(
+ GetPositionChangeDirectionForWindowComponent(window_component)),
+ size_change_direction(
+ GetSizeChangeDirectionForWindowComponent(window_component)),
+ is_resizable(bounds_change != kBoundsChangeDirection_None),
+ source(source) {
+ if (wm::IsWindowNormal(window) &&
+ GetRestoreBoundsInScreen(window) &&
+ window_component == HTCAPTION)
+ restore_bounds = *GetRestoreBoundsInScreen(window);
+}
+
+WindowResizer::Details::~Details() {
+}
+
+WindowResizer::WindowResizer() {
+}
+
+WindowResizer::~WindowResizer() {
+}
+
+// static
+int WindowResizer::GetBoundsChangeForWindowComponent(int component) {
+ int bounds_change = WindowResizer::kBoundsChange_None;
+ switch (component) {
+ case HTTOPLEFT:
+ case HTTOP:
+ case HTTOPRIGHT:
+ case HTLEFT:
+ case HTBOTTOMLEFT:
+ bounds_change |= WindowResizer::kBoundsChange_Repositions |
+ WindowResizer::kBoundsChange_Resizes;
+ break;
+ case HTCAPTION:
+ bounds_change |= WindowResizer::kBoundsChange_Repositions;
+ break;
+ case HTRIGHT:
+ case HTBOTTOMRIGHT:
+ case HTBOTTOM:
+ case HTGROWBOX:
+ bounds_change |= WindowResizer::kBoundsChange_Resizes;
+ break;
+ default:
+ break;
+ }
+ return bounds_change;
+}
+
+// static
+gfx::Rect WindowResizer::CalculateBoundsForDrag(
+ const Details& details,
+ const gfx::Point& passed_location) {
+ if (!details.is_resizable)
+ return details.initial_bounds_in_parent;
+
+ gfx::Point location = passed_location;
+ int delta_x = location.x() - details.initial_location_in_parent.x();
+ int delta_y = location.y() - details.initial_location_in_parent.y();
+
+ AdjustDeltaForTouchResize(details, &delta_x, &delta_y);
+
+ // The minimize size constraint may limit how much we change the window
+ // position. For example, dragging the left edge to the right should stop
+ // repositioning the window when the minimize size is reached.
+ gfx::Size size = GetSizeForDrag(details, &delta_x, &delta_y);
+ gfx::Point origin = GetOriginForDrag(details, delta_x, delta_y);
+ gfx::Rect new_bounds(origin, size);
+
+ // Sizing has to keep the result on the screen. Note that this correction
+ // has to come first since it might have an impact on the origin as well as
+ // on the size.
+ if (details.bounds_change & kBoundsChange_Resizes) {
+ gfx::Rect work_area =
+ Shell::GetScreen()->GetDisplayNearestWindow(details.window).work_area();
+ aura::Window* dock_container = Shell::GetContainer(
+ details.window->GetRootWindow(),
+ internal::kShellWindowId_DockedContainer);
+ internal::DockedWindowLayoutManager* dock_layout =
+ static_cast<internal::DockedWindowLayoutManager*>(
+ dock_container->layout_manager());
+
+ work_area.Union(dock_layout->docked_bounds());
+ work_area = ScreenAsh::ConvertRectFromScreen(details.window->parent(),
+ work_area);
+ if (details.size_change_direction & kBoundsChangeDirection_Horizontal) {
+ if (IsRightEdge(details.window_component) &&
+ new_bounds.right() < work_area.x() + kMinimumOnScreenArea) {
+ int delta = work_area.x() + kMinimumOnScreenArea - new_bounds.right();
+ new_bounds.set_width(new_bounds.width() + delta);
+ } else if (new_bounds.x() > work_area.right() - kMinimumOnScreenArea) {
+ int width = new_bounds.right() - work_area.right() +
+ kMinimumOnScreenArea;
+ new_bounds.set_x(work_area.right() - kMinimumOnScreenArea);
+ new_bounds.set_width(width);
+ }
+ }
+ if (details.size_change_direction & kBoundsChangeDirection_Vertical) {
+ if (!IsBottomEdge(details.window_component) &&
+ new_bounds.y() > work_area.bottom() - kMinimumOnScreenArea) {
+ int height = new_bounds.bottom() - work_area.bottom() +
+ kMinimumOnScreenArea;
+ new_bounds.set_y(work_area.bottom() - kMinimumOnScreenArea);
+ new_bounds.set_height(height);
+ } else if (details.window_component == HTBOTTOM ||
+ details.window_component == HTBOTTOMRIGHT ||
+ details.window_component == HTBOTTOMLEFT) {
+ // Update bottom edge to stay in the work area when we are resizing
+ // by dragging the bottom edge or corners.
+ if (new_bounds.bottom() > work_area.bottom())
+ new_bounds.Inset(0, 0, 0,
+ new_bounds.bottom() - work_area.bottom());
+ }
+ }
+ if (details.bounds_change & kBoundsChange_Repositions &&
+ new_bounds.y() < 0) {
+ int delta = new_bounds.y();
+ new_bounds.set_y(0);
+ new_bounds.set_height(new_bounds.height() + delta);
+ }
+ }
+
+ if (details.bounds_change & kBoundsChange_Repositions) {
+ // When we might want to reposition a window which is also restored to its
+ // previous size, to keep the cursor within the dragged window.
+ if (!details.restore_bounds.IsEmpty()) {
+ // However - it is not desirable to change the origin if the window would
+ // be still hit by the cursor.
+ if (details.initial_location_in_parent.x() >
+ details.initial_bounds_in_parent.x() + details.restore_bounds.width())
+ new_bounds.set_x(location.x() - details.restore_bounds.width() / 2);
+ }
+
+ // Make sure that |new_bounds| doesn't leave any of the displays. Note that
+ // the |work_area| above isn't good for this check since it is the work area
+ // for the current display but the window can move to a different one.
+ aura::Window* parent = details.window->parent();
+ gfx::Rect new_bounds_in_screen =
+ ScreenAsh::ConvertRectToScreen(parent, new_bounds);
+ const gfx::Display& display =
+ Shell::GetScreen()->GetDisplayMatching(new_bounds_in_screen);
+ aura::Window* dock_container = Shell::GetContainer(
+ wm::GetRootWindowMatching(new_bounds_in_screen),
+ internal::kShellWindowId_DockedContainer);
+ internal::DockedWindowLayoutManager* dock_layout =
+ static_cast<internal::DockedWindowLayoutManager*>(
+ dock_container->layout_manager());
+
+ gfx::Rect screen_work_area = display.work_area();
+ screen_work_area.Union(dock_layout->docked_bounds());
+ screen_work_area.Inset(kMinimumOnScreenArea, 0);
+ if (!screen_work_area.Intersects(new_bounds_in_screen)) {
+ // Make sure that the x origin does not leave the current display.
+ new_bounds_in_screen.set_x(
+ std::max(screen_work_area.x() - new_bounds.width(),
+ std::min(screen_work_area.right(),
+ new_bounds_in_screen.x())));
+ new_bounds =
+ ScreenAsh::ConvertRectFromScreen(parent, new_bounds_in_screen);
+ }
+ }
+
+ return new_bounds;
+}
+
+// static
+bool WindowResizer::IsBottomEdge(int window_component) {
+ return window_component == HTBOTTOMLEFT ||
+ window_component == HTBOTTOM ||
+ window_component == HTBOTTOMRIGHT ||
+ window_component == HTGROWBOX;
+}
+
+// static
+void WindowResizer::AdjustDeltaForTouchResize(const Details& details,
+ int* delta_x,
+ int* delta_y) {
+ if (details.source != aura::client::WINDOW_MOVE_SOURCE_TOUCH ||
+ !(details.bounds_change & kBoundsChange_Resizes))
+ return;
+
+ if (details.size_change_direction & kBoundsChangeDirection_Horizontal) {
+ if (IsRightEdge(details.window_component)) {
+ *delta_x += details.initial_location_in_parent.x() -
+ details.initial_bounds_in_parent.right();
+ } else {
+ *delta_x += details.initial_location_in_parent.x() -
+ details.initial_bounds_in_parent.x();
+ }
+ }
+ if (details.size_change_direction & kBoundsChangeDirection_Vertical) {
+ if (IsBottomEdge(details.window_component)) {
+ *delta_y += details.initial_location_in_parent.y() -
+ details.initial_bounds_in_parent.bottom();
+ } else {
+ *delta_y += details.initial_location_in_parent.y() -
+ details.initial_bounds_in_parent.y();
+ }
+ }
+}
+
+// static
+gfx::Point WindowResizer::GetOriginForDrag(const Details& details,
+ int delta_x,
+ int delta_y) {
+ gfx::Point origin = details.initial_bounds_in_parent.origin();
+ if (details.bounds_change & kBoundsChange_Repositions) {
+ int pos_change_direction =
+ GetPositionChangeDirectionForWindowComponent(details.window_component);
+ if (pos_change_direction & kBoundsChangeDirection_Horizontal)
+ origin.Offset(delta_x, 0);
+ if (pos_change_direction & kBoundsChangeDirection_Vertical)
+ origin.Offset(0, delta_y);
+ }
+ return origin;
+}
+
+// static
+gfx::Size WindowResizer::GetSizeForDrag(const Details& details,
+ int* delta_x,
+ int* delta_y) {
+ gfx::Size size = details.initial_bounds_in_parent.size();
+ if (details.bounds_change & kBoundsChange_Resizes) {
+ gfx::Size min_size = details.window->delegate()->GetMinimumSize();
+ size.SetSize(GetWidthForDrag(details, min_size.width(), delta_x),
+ GetHeightForDrag(details, min_size.height(), delta_y));
+ } else if (!details.restore_bounds.IsEmpty()) {
+ size = details.restore_bounds.size();
+ }
+ return size;
+}
+
+// static
+int WindowResizer::GetWidthForDrag(const Details& details,
+ int min_width,
+ int* delta_x) {
+ int width = details.initial_bounds_in_parent.width();
+ if (details.size_change_direction & kBoundsChangeDirection_Horizontal) {
+ // Along the right edge, positive delta_x increases the window size.
+ int x_multiplier = IsRightEdge(details.window_component) ? 1 : -1;
+ width += x_multiplier * (*delta_x);
+
+ // Ensure we don't shrink past the minimum width and clamp delta_x
+ // for the window origin computation.
+ if (width < min_width) {
+ width = min_width;
+ *delta_x = -x_multiplier * (details.initial_bounds_in_parent.width() -
+ min_width);
+ }
+
+ // And don't let the window go bigger than the display.
+ int max_width = Shell::GetScreen()->GetDisplayNearestWindow(
+ details.window).bounds().width();
+ gfx::Size max_size = details.window->delegate()->GetMaximumSize();
+ if (max_size.width() != 0)
+ max_width = std::min(max_width, max_size.width());
+ if (width > max_width) {
+ width = max_width;
+ *delta_x = -x_multiplier * (details.initial_bounds_in_parent.width() -
+ max_width);
+ }
+ }
+ return width;
+}
+
+// static
+int WindowResizer::GetHeightForDrag(const Details& details,
+ int min_height,
+ int* delta_y) {
+ int height = details.initial_bounds_in_parent.height();
+ if (details.size_change_direction & kBoundsChangeDirection_Vertical) {
+ // Along the bottom edge, positive delta_y increases the window size.
+ int y_multiplier = IsBottomEdge(details.window_component) ? 1 : -1;
+ height += y_multiplier * (*delta_y);
+
+ // Ensure we don't shrink past the minimum height and clamp delta_y
+ // for the window origin computation.
+ if (height < min_height) {
+ height = min_height;
+ *delta_y = -y_multiplier * (details.initial_bounds_in_parent.height() -
+ min_height);
+ }
+
+ // And don't let the window go bigger than the display.
+ int max_height = Shell::GetScreen()->GetDisplayNearestWindow(
+ details.window).bounds().height();
+ gfx::Size max_size = details.window->delegate()->GetMaximumSize();
+ if (max_size.height() != 0)
+ max_height = std::min(max_height, max_size.height());
+ if (height > max_height) {
+ height = max_height;
+ *delta_y = -y_multiplier * (details.initial_bounds_in_parent.height() -
+ max_height);
+ }
+ }
+ return height;
+}
+
+} // namespace aura
diff --git a/chromium/ash/wm/window_resizer.h b/chromium/ash/wm/window_resizer.h
new file mode 100644
index 00000000000..6583d8913b0
--- /dev/null
+++ b/chromium/ash/wm/window_resizer.h
@@ -0,0 +1,149 @@
+// Copyright (c) 2012 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 ASH_WM_WINDOW_RESIZER_H_
+#define ASH_WM_WINDOW_RESIZER_H_
+
+#include "ash/ash_export.h"
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "ui/aura/client/window_move_client.h"
+#include "ui/gfx/rect.h"
+
+namespace aura {
+class Window;
+}
+
+namespace ash {
+
+// WindowResizer is used by ToplevelWindowEventFilter to handle dragging, moving
+// or resizing a window. All coordinates passed to this are in the parent
+// windows coordinates.
+class ASH_EXPORT WindowResizer {
+ public:
+ // Constants to identify the type of resize.
+ static const int kBoundsChange_None;
+ static const int kBoundsChange_Repositions;
+ static const int kBoundsChange_Resizes;
+
+ // Used to indicate which direction the resize occurs in.
+ static const int kBoundsChangeDirection_None;
+ static const int kBoundsChangeDirection_Horizontal;
+ static const int kBoundsChangeDirection_Vertical;
+
+ WindowResizer();
+ virtual ~WindowResizer();
+
+ // Returns a bitmask of the kBoundsChange_ values.
+ static int GetBoundsChangeForWindowComponent(int component);
+
+ // Invoked to drag/move/resize the window. |location| is in the coordinates
+ // of the window supplied to the constructor. |event_flags| is the event
+ // flags from the event.
+ virtual void Drag(const gfx::Point& location, int event_flags) = 0;
+
+ // Invoked to complete the drag.
+ virtual void CompleteDrag(int event_flags) = 0;
+
+ // Reverts the drag.
+ virtual void RevertDrag() = 0;
+
+ // Returns the target window the resizer was created for.
+ virtual aura::Window* GetTarget() = 0;
+
+ // See comment for |Details::initial_location_in_parent|.
+ virtual const gfx::Point& GetInitialLocation() const = 0;
+
+ protected:
+ struct Details {
+ Details();
+ Details(aura::Window* window,
+ const gfx::Point& location,
+ int window_component,
+ aura::client::WindowMoveSource source);
+ ~Details();
+
+ // The window we're resizing.
+ aura::Window* window;
+
+ // Initial bounds of the window in parent coordinates.
+ gfx::Rect initial_bounds_in_parent;
+
+ // Restore bounds (in screen coordinates) of the window before the drag
+ // started. Only set if the window is normal and is being dragged.
+ gfx::Rect restore_bounds;
+
+ // Location passed to the constructor, in |window->parent()|'s coordinates.
+ gfx::Point initial_location_in_parent;
+
+ // Initial opacity of the window.
+ float initial_opacity;
+
+ // The component the user pressed on.
+ int window_component;
+
+ // Bitmask of the |kBoundsChange_| constants.
+ int bounds_change;
+
+ // Bitmask of the |kBoundsChangeDirection_| constants.
+ int position_change_direction;
+
+ // Bitmask of the |kBoundsChangeDirection_| constants.
+ int size_change_direction;
+
+ // Will the drag actually modify the window?
+ bool is_resizable;
+
+ // Source of the event initiating the drag.
+ aura::client::WindowMoveSource source;
+ };
+
+ static gfx::Rect CalculateBoundsForDrag(const Details& details,
+ const gfx::Point& location);
+
+ static gfx::Rect AdjustBoundsToGrid(const gfx::Rect& bounds,
+ int grid_size);
+
+ static bool IsBottomEdge(int component);
+
+ private:
+ // In case of touch resizing, adjusts deltas so that the border is positioned
+ // just under the touch point.
+ static void AdjustDeltaForTouchResize(const Details& details,
+ int* delta_x,
+ int* delta_y);
+
+ // Returns the new origin of the window. The arguments are the difference
+ // between the current location and the initial location.
+ static gfx::Point GetOriginForDrag(const Details& details,
+ int delta_x,
+ int delta_y);
+
+ // Returns the size of the window for the drag.
+ static gfx::Size GetSizeForDrag(const Details& details,
+ int* delta_x,
+ int* delta_y);
+
+ // Returns the width of the window.
+ static int GetWidthForDrag(const Details& details,
+ int min_width,
+ int* delta_x);
+
+ // Returns the height of the drag.
+ static int GetHeightForDrag(const Details& details,
+ int min_height,
+ int* delta_y);
+};
+
+// Creates a WindowResizer for |window|. This can return a scoped_ptr
+// initialized with NULL if |window| should not be resized nor dragged.
+ASH_EXPORT scoped_ptr<WindowResizer> CreateWindowResizer(
+ aura::Window* window,
+ const gfx::Point& point_in_parent,
+ int window_component,
+ aura::client::WindowMoveSource source);
+
+} // namespace ash
+
+#endif // ASH_WM_WINDOW_RESIZER_H_
diff --git a/chromium/ash/wm/window_selector.cc b/chromium/ash/wm/window_selector.cc
new file mode 100644
index 00000000000..47e20159572
--- /dev/null
+++ b/chromium/ash/wm/window_selector.cc
@@ -0,0 +1,550 @@
+// Copyright 2013 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.
+
+#include "ash/wm/window_selector.h"
+
+#include <algorithm>
+
+#include "ash/screen_ash.h"
+#include "ash/shell.h"
+#include "ash/shell_window_ids.h"
+#include "ash/wm/window_selector_delegate.h"
+#include "ash/wm/window_util.h"
+#include "base/memory/scoped_ptr.h"
+#include "third_party/skia/include/core/SkColor.h"
+#include "ui/aura/client/aura_constants.h"
+#include "ui/aura/client/screen_position_client.h"
+#include "ui/aura/root_window.h"
+#include "ui/aura/window.h"
+#include "ui/base/events/event.h"
+#include "ui/compositor/layer_animation_observer.h"
+#include "ui/compositor/scoped_layer_animation_settings.h"
+#include "ui/gfx/display.h"
+#include "ui/gfx/interpolated_transform.h"
+#include "ui/gfx/transform_util.h"
+#include "ui/views/corewm/shadow_types.h"
+#include "ui/views/corewm/window_animations.h"
+#include "ui/views/corewm/window_util.h"
+#include "ui/views/widget/widget.h"
+
+namespace ash {
+
+namespace {
+
+const float kCardAspectRatio = 4.0f / 3.0f;
+const int kWindowMargin = 30;
+const int kMinCardsMajor = 3;
+const int kOverviewTransitionMilliseconds = 100;
+const SkColor kWindowSelectorSelectionColor = SK_ColorBLACK;
+const float kWindowSelectorSelectionOpacity = 0.5f;
+const int kWindowSelectorSelectionPadding = 15;
+
+// Creates a copy of |window| with |recreated_layer| in the |target_root|.
+views::Widget* CreateCopyOfWindow(aura::RootWindow* target_root,
+ aura::Window* src_window,
+ ui::Layer* recreated_layer) {
+ views::Widget* widget = new views::Widget;
+ views::Widget::InitParams params(views::Widget::InitParams::TYPE_POPUP);
+ params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW;
+ params.parent = src_window->parent();
+ params.can_activate = false;
+ params.keep_on_top = true;
+ widget->set_focus_on_creation(false);
+ widget->Init(params);
+ widget->SetVisibilityChangedAnimationsEnabled(false);
+ std::string name = src_window->name() + " (Copy)";
+ widget->GetNativeWindow()->SetName(name);
+ views::corewm::SetShadowType(widget->GetNativeWindow(),
+ views::corewm::SHADOW_TYPE_RECTANGULAR);
+
+ // Set the bounds in the target root window.
+ gfx::Display target_display =
+ Shell::GetScreen()->GetDisplayNearestWindow(target_root);
+ aura::client::ScreenPositionClient* screen_position_client =
+ aura::client::GetScreenPositionClient(src_window->GetRootWindow());
+ if (screen_position_client && target_display.is_valid()) {
+ screen_position_client->SetBounds(widget->GetNativeWindow(),
+ src_window->GetBoundsInScreen(), target_display);
+ } else {
+ widget->SetBounds(src_window->GetBoundsInScreen());
+ }
+ widget->StackAbove(src_window);
+
+ // Move the |recreated_layer| to the newly created window.
+ recreated_layer->set_delegate(src_window->layer()->delegate());
+ gfx::Rect layer_bounds = recreated_layer->bounds();
+ layer_bounds.set_origin(gfx::Point(0, 0));
+ recreated_layer->SetBounds(layer_bounds);
+ recreated_layer->SetVisible(false);
+ recreated_layer->parent()->Remove(recreated_layer);
+
+ aura::Window* window = widget->GetNativeWindow();
+ recreated_layer->SetVisible(true);
+ window->layer()->Add(recreated_layer);
+ window->layer()->StackAtTop(recreated_layer);
+ window->layer()->SetOpacity(1);
+ window->Show();
+ return widget;
+}
+
+// An observer which closes the widget and deletes the layer after an
+// animation finishes.
+class CleanupWidgetAfterAnimationObserver : public ui::LayerAnimationObserver {
+ public:
+ CleanupWidgetAfterAnimationObserver(views::Widget* widget, ui::Layer* layer);
+
+ virtual void OnLayerAnimationEnded(
+ ui::LayerAnimationSequence* sequence) OVERRIDE;
+ virtual void OnLayerAnimationAborted(
+ ui::LayerAnimationSequence* sequence) OVERRIDE;
+ virtual void OnLayerAnimationScheduled(
+ ui::LayerAnimationSequence* sequence) OVERRIDE;
+
+ protected:
+ virtual ~CleanupWidgetAfterAnimationObserver();
+
+ private:
+ views::Widget* widget_;
+ ui::Layer* layer_;
+
+ DISALLOW_COPY_AND_ASSIGN(CleanupWidgetAfterAnimationObserver);
+};
+
+CleanupWidgetAfterAnimationObserver::CleanupWidgetAfterAnimationObserver(
+ views::Widget* widget,
+ ui::Layer* layer)
+ : widget_(widget),
+ layer_(layer) {
+ widget_->GetNativeWindow()->layer()->GetAnimator()->AddObserver(this);
+}
+
+void CleanupWidgetAfterAnimationObserver::OnLayerAnimationEnded(
+ ui::LayerAnimationSequence* sequence) {
+ delete this;
+}
+
+void CleanupWidgetAfterAnimationObserver::OnLayerAnimationAborted(
+ ui::LayerAnimationSequence* sequence) {
+ delete this;
+}
+
+void CleanupWidgetAfterAnimationObserver::OnLayerAnimationScheduled(
+ ui::LayerAnimationSequence* sequence) {
+}
+
+CleanupWidgetAfterAnimationObserver::~CleanupWidgetAfterAnimationObserver() {
+ widget_->GetNativeWindow()->layer()->GetAnimator()->RemoveObserver(this);
+ widget_->Close();
+ widget_ = NULL;
+ if (layer_) {
+ views::corewm::DeepDeleteLayers(layer_);
+ layer_ = NULL;
+ }
+}
+
+// The animation settings used for window selector animations.
+class WindowSelectorAnimationSettings
+ : public ui::ScopedLayerAnimationSettings {
+ public:
+ WindowSelectorAnimationSettings(aura::Window* window) :
+ ui::ScopedLayerAnimationSettings(window->layer()->GetAnimator()) {
+ SetPreemptionStrategy(
+ ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
+ SetTransitionDuration(
+ base::TimeDelta::FromMilliseconds(kOverviewTransitionMilliseconds));
+ }
+
+ virtual ~WindowSelectorAnimationSettings() {
+ }
+};
+
+} // namespace
+
+// TODO(flackr): Split up into separate file under subdirectory in ash/wm.
+class WindowSelectorWindow {
+ public:
+ explicit WindowSelectorWindow(aura::Window* window);
+ virtual ~WindowSelectorWindow();
+
+ aura::Window* window() { return window_; }
+ const aura::Window* window() const { return window_; }
+
+ // Returns true if this window selector window contains the |target|. This is
+ // used to determine if an event targetted this window.
+ bool Contains(const aura::Window* target) const;
+
+ // Restores this window on exit rather than returning it to a minimized state
+ // if it was minimized on entering overview mode.
+ void RestoreWindowOnExit();
+
+ // Informs the WindowSelectorWindow that the window being watched was
+ // destroyed. This resets the internal window pointer to avoid calling
+ // anything on the window at destruction time.
+ void OnWindowDestroyed();
+
+ // Applies a transform to the window to fit within |target_bounds| while
+ // maintaining its aspect ratio.
+ void TransformToFitBounds(aura::RootWindow* root_window,
+ const gfx::Rect& target_bounds);
+
+ gfx::Rect bounds() { return fit_bounds_; }
+
+ private:
+ // A weak pointer to the real window in the overview.
+ aura::Window* window_;
+
+ // A copy of the window used to transition the window to another root.
+ views::Widget* window_copy_;
+
+ // A weak pointer to a deep copy of the window's layers.
+ ui::Layer* layer_;
+
+ // If true, the window was minimized and should be restored if the window
+ // was not selected.
+ bool minimized_;
+
+ // The original transform of the window before entering overview mode.
+ gfx::Transform original_transform_;
+
+ // The bounds this window is fit to.
+ gfx::Rect fit_bounds_;
+
+ DISALLOW_COPY_AND_ASSIGN(WindowSelectorWindow);
+};
+
+WindowSelectorWindow::WindowSelectorWindow(aura::Window* window)
+ : window_(window),
+ window_copy_(NULL),
+ layer_(NULL),
+ minimized_(window->GetProperty(aura::client::kShowStateKey) ==
+ ui::SHOW_STATE_MINIMIZED),
+ original_transform_(window->layer()->transform()) {
+ if (minimized_)
+ window_->Show();
+}
+
+WindowSelectorWindow::~WindowSelectorWindow() {
+ if (window_) {
+ WindowSelectorAnimationSettings animation_settings(window_);
+ gfx::Transform transform;
+ window_->SetTransform(original_transform_);
+ if (minimized_) {
+ // Setting opacity 0 and visible false ensures that the property change
+ // to SHOW_STATE_MINIMIZED will not animate the window from its original
+ // bounds to the minimized position.
+ window_->layer()->SetOpacity(0);
+ window_->layer()->SetVisible(false);
+ window_->SetProperty(aura::client::kShowStateKey,
+ ui::SHOW_STATE_MINIMIZED);
+ }
+ }
+ // If a copy of the window was created, clean it up.
+ if (window_copy_) {
+ if (window_) {
+ // If the initial window wasn't destroyed, the copy needs to be animated
+ // out. CleanupWidgetAfterAnimationObserver will destroy the widget and
+ // layer after the animation is complete.
+ new CleanupWidgetAfterAnimationObserver(window_copy_, layer_);
+ WindowSelectorAnimationSettings animation_settings(
+ window_copy_->GetNativeWindow());
+ window_copy_->GetNativeWindow()->SetTransform(original_transform_);
+ } else {
+ window_copy_->Close();
+ if (layer_)
+ views::corewm::DeepDeleteLayers(layer_);
+ }
+ window_copy_ = NULL;
+ layer_ = NULL;
+ }
+}
+
+bool WindowSelectorWindow::Contains(const aura::Window* window) const {
+ if (window_copy_ && window_copy_->GetNativeWindow()->Contains(window))
+ return true;
+ return window_->Contains(window);
+}
+
+void WindowSelectorWindow::RestoreWindowOnExit() {
+ minimized_ = false;
+ original_transform_ = gfx::Transform();
+}
+
+void WindowSelectorWindow::OnWindowDestroyed() {
+ window_ = NULL;
+}
+
+void WindowSelectorWindow::TransformToFitBounds(
+ aura::RootWindow* root_window,
+ const gfx::Rect& target_bounds) {
+ fit_bounds_ = target_bounds;
+ const gfx::Rect bounds = window_->GetBoundsInScreen();
+ float scale = std::min(1.0f,
+ std::min(static_cast<float>(target_bounds.width()) / bounds.width(),
+ static_cast<float>(target_bounds.height()) / bounds.height()));
+ gfx::Transform transform;
+ gfx::Vector2d offset(
+ 0.5 * (target_bounds.width() - scale * bounds.width()),
+ 0.5 * (target_bounds.height() - scale * bounds.height()));
+ transform.Translate(target_bounds.x() - bounds.x() + offset.x(),
+ target_bounds.y() - bounds.y() + offset.y());
+ transform.Scale(scale, scale);
+ if (root_window != window_->GetRootWindow()) {
+ if (!window_copy_) {
+ DCHECK(!layer_);
+ layer_ = views::corewm::RecreateWindowLayers(window_, true);
+ window_copy_ = CreateCopyOfWindow(root_window, window_, layer_);
+ }
+ WindowSelectorAnimationSettings animation_settings(
+ window_copy_->GetNativeWindow());
+ window_copy_->GetNativeWindow()->SetTransform(transform);
+ }
+ WindowSelectorAnimationSettings animation_settings(window_);
+ window_->SetTransform(transform);
+}
+
+// A comparator for locating a given target window.
+struct WindowSelectorWindowComparator
+ : public std::unary_function<WindowSelectorWindow*, bool> {
+ explicit WindowSelectorWindowComparator(const aura::Window* target_window)
+ : target(target_window) {
+ }
+
+ bool operator()(const WindowSelectorWindow* window) const {
+ return target == window->window();
+ }
+
+ const aura::Window* target;
+};
+
+WindowSelector::WindowSelector(const WindowList& windows,
+ WindowSelector::Mode mode,
+ WindowSelectorDelegate* delegate)
+ : mode_(mode),
+ delegate_(delegate),
+ selected_window_(0),
+ selection_root_(NULL) {
+ DCHECK(delegate_);
+ for (size_t i = 0; i < windows.size(); ++i) {
+ windows[i]->AddObserver(this);
+ windows_.push_back(new WindowSelectorWindow(windows[i]));
+ }
+ if (mode == WindowSelector::CYCLE)
+ selection_root_ = ash::Shell::GetActiveRootWindow();
+ PositionWindows();
+ ash::Shell::GetInstance()->AddPreTargetHandler(this);
+}
+
+WindowSelector::~WindowSelector() {
+ for (size_t i = 0; i < windows_.size(); i++) {
+ windows_[i]->window()->RemoveObserver(this);
+ }
+ ash::Shell::GetInstance()->RemovePreTargetHandler(this);
+}
+
+void WindowSelector::Step(WindowSelector::Direction direction) {
+ DCHECK(windows_.size() > 0);
+ if (!selection_widget_)
+ InitializeSelectionWidget();
+ selected_window_ = (selected_window_ + windows_.size() +
+ (direction == WindowSelector::FORWARD ? 1 : -1)) % windows_.size();
+ UpdateSelectionLocation(true);
+}
+
+void WindowSelector::SelectWindow() {
+ delegate_->OnWindowSelected(windows_[selected_window_]->window());
+}
+
+void WindowSelector::OnEvent(ui::Event* event) {
+ // If the event is targetted at any of the windows in the overview, then
+ // prevent it from propagating.
+ aura::Window* target = static_cast<aura::Window*>(event->target());
+ for (size_t i = 0; i < windows_.size(); ++i) {
+ if (windows_[i]->Contains(target)) {
+ // TODO(flackr): StopPropogation prevents generation of gesture events.
+ // We should find a better way to prevent events from being delivered to
+ // the window, perhaps a transparent window in front of the target window
+ // or using EventClientImpl::CanProcessEventsWithinSubtree.
+ event->StopPropagation();
+ break;
+ }
+ }
+
+ // This object may not be valid after this call as a selection event can
+ // trigger deletion of the window selector.
+ ui::EventHandler::OnEvent(event);
+}
+
+void WindowSelector::OnMouseEvent(ui::MouseEvent* event) {
+ if (event->type() != ui::ET_MOUSE_RELEASED)
+ return;
+ WindowSelectorWindow* target = GetEventTarget(event);
+ if (!target)
+ return;
+
+ HandleSelectionEvent(target);
+}
+
+void WindowSelector::OnTouchEvent(ui::TouchEvent* event) {
+ if (event->type() != ui::ET_TOUCH_PRESSED)
+ return;
+ WindowSelectorWindow* target = GetEventTarget(event);
+ if (!target)
+ return;
+
+ HandleSelectionEvent(target);
+}
+
+void WindowSelector::OnWindowDestroyed(aura::Window* window) {
+ ScopedVector<WindowSelectorWindow>::iterator iter =
+ std::find_if(windows_.begin(), windows_.end(),
+ WindowSelectorWindowComparator(window));
+ DCHECK(iter != windows_.end());
+ size_t deleted_index = iter - windows_.begin();
+ (*iter)->OnWindowDestroyed();
+ windows_.erase(iter);
+ if (windows_.empty()) {
+ delegate_->OnSelectionCanceled();
+ return;
+ }
+ if (selected_window_ >= deleted_index) {
+ if (selected_window_ > deleted_index)
+ selected_window_--;
+ selected_window_ = selected_window_ % windows_.size();
+ UpdateSelectionLocation(true);
+ }
+
+ PositionWindows();
+}
+
+WindowSelectorWindow* WindowSelector::GetEventTarget(ui::LocatedEvent* event) {
+ aura::Window* target = static_cast<aura::Window*>(event->target());
+ // If the target window doesn't actually contain the event location (i.e.
+ // mouse down over the window and mouse up elsewhere) then do not select the
+ // window.
+ if (!target->HitTest(event->location()))
+ return NULL;
+
+ for (size_t i = 0; i < windows_.size(); i++) {
+ if (windows_[i]->Contains(target))
+ return windows_[i];
+ }
+ return NULL;
+}
+
+void WindowSelector::HandleSelectionEvent(WindowSelectorWindow* target) {
+ // The selected window should not be minimized when window selection is
+ // ended.
+ target->RestoreWindowOnExit();
+ delegate_->OnWindowSelected(target->window());
+}
+
+void WindowSelector::PositionWindows() {
+ if (selection_root_) {
+ DCHECK_EQ(mode_, CYCLE);
+ std::vector<WindowSelectorWindow*> windows;
+ for (size_t i = 0; i < windows_.size(); ++i)
+ windows.push_back(windows_[i]);
+ PositionWindowsOnRoot(selection_root_, windows);
+ } else {
+ DCHECK_EQ(mode_, OVERVIEW);
+ Shell::RootWindowList root_window_list = Shell::GetAllRootWindows();
+ for (size_t i = 0; i < root_window_list.size(); ++i)
+ PositionWindowsFromRoot(root_window_list[i]);
+ }
+}
+
+void WindowSelector::PositionWindowsFromRoot(aura::RootWindow* root_window) {
+ std::vector<WindowSelectorWindow*> windows;
+ for (size_t i = 0; i < windows_.size(); ++i) {
+ if (windows_[i]->window()->GetRootWindow() == root_window)
+ windows.push_back(windows_[i]);
+ }
+ PositionWindowsOnRoot(root_window, windows);
+}
+
+void WindowSelector::PositionWindowsOnRoot(
+ aura::RootWindow* root_window,
+ const std::vector<WindowSelectorWindow*>& windows) {
+ if (windows.empty())
+ return;
+
+ gfx::Size window_size;
+ gfx::Rect total_bounds = ScreenAsh::ConvertRectToScreen(root_window,
+ ScreenAsh::GetDisplayWorkAreaBoundsInParent(
+ Shell::GetContainer(root_window,
+ internal::kShellWindowId_DefaultContainer)));
+
+ // Find the minimum number of windows per row that will fit all of the
+ // windows on screen.
+ size_t columns = std::max(
+ total_bounds.width() > total_bounds.height() ? kMinCardsMajor : 1,
+ static_cast<int>(ceil(sqrt(total_bounds.width() * windows.size() /
+ (kCardAspectRatio * total_bounds.height())))));
+ size_t rows = ((windows.size() + columns - 1) / columns);
+ window_size.set_width(std::min(
+ static_cast<int>(total_bounds.width() / columns),
+ static_cast<int>(total_bounds.height() * kCardAspectRatio / rows)));
+ window_size.set_height(window_size.width() / kCardAspectRatio);
+
+ // Calculate the X and Y offsets necessary to center the grid.
+ int x_offset = total_bounds.x() + ((windows.size() >= columns ? 0 :
+ (columns - windows.size()) * window_size.width()) +
+ (total_bounds.width() - columns * window_size.width())) / 2;
+ int y_offset = total_bounds.y() + (total_bounds.height() -
+ rows * window_size.height()) / 2;
+ for (size_t i = 0; i < windows.size(); ++i) {
+ gfx::Transform transform;
+ int column = i % columns;
+ int row = i / columns;
+ gfx::Rect target_bounds(window_size.width() * column + x_offset,
+ window_size.height() * row + y_offset,
+ window_size.width(),
+ window_size.height());
+ target_bounds.Inset(kWindowMargin, kWindowMargin);
+ windows[i]->TransformToFitBounds(root_window, target_bounds);
+ }
+}
+
+void WindowSelector::InitializeSelectionWidget() {
+ selection_widget_.reset(new views::Widget);
+ views::Widget::InitParams params;
+ params.type = views::Widget::InitParams::TYPE_POPUP;
+ params.can_activate = false;
+ params.keep_on_top = false;
+ params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
+ params.opacity = views::Widget::InitParams::OPAQUE_WINDOW;
+ params.parent = Shell::GetContainer(
+ selection_root_,
+ internal::kShellWindowId_DefaultContainer);
+ params.accept_events = false;
+ selection_widget_->set_focus_on_creation(false);
+ selection_widget_->Init(params);
+ views::View* content_view = new views::View;
+ content_view->set_background(
+ views::Background::CreateSolidBackground(kWindowSelectorSelectionColor));
+ selection_widget_->SetContentsView(content_view);
+ UpdateSelectionLocation(false);
+ selection_widget_->GetNativeWindow()->parent()->StackChildAtBottom(
+ selection_widget_->GetNativeWindow());
+ selection_widget_->Show();
+ selection_widget_->GetNativeWindow()->layer()->SetOpacity(
+ kWindowSelectorSelectionOpacity);
+}
+
+void WindowSelector::UpdateSelectionLocation(bool animate) {
+ if (!selection_widget_)
+ return;
+ gfx::Rect target_bounds = windows_[selected_window_]->bounds();
+ target_bounds.Inset(-kWindowSelectorSelectionPadding,
+ -kWindowSelectorSelectionPadding);
+ if (animate) {
+ WindowSelectorAnimationSettings animation_settings(
+ selection_widget_->GetNativeWindow());
+ selection_widget_->SetBounds(target_bounds);
+ } else {
+ selection_widget_->SetBounds(target_bounds);
+ }
+}
+
+} // namespace ash
diff --git a/chromium/ash/wm/window_selector.h b/chromium/ash/wm/window_selector.h
new file mode 100644
index 00000000000..9cd1e04fd22
--- /dev/null
+++ b/chromium/ash/wm/window_selector.h
@@ -0,0 +1,120 @@
+// Copyright 2013 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 ASH_WM_WINDOW_SELECTOR_H_
+#define ASH_WM_WINDOW_SELECTOR_H_
+
+#include <vector>
+
+#include "base/compiler_specific.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/scoped_vector.h"
+#include "ui/aura/window_observer.h"
+#include "ui/base/events/event_handler.h"
+#include "ui/gfx/transform.h"
+
+namespace aura {
+class RootWindow;
+}
+
+namespace ui {
+class LocatedEvent;
+}
+
+namespace views {
+class Widget;
+}
+
+namespace ash {
+
+class WindowSelectorDelegate;
+class WindowSelectorWindow;
+
+// The WindowSelector shows a grid of all of your windows and allows selecting
+// a window by clicking or tapping on it (OVERVIEW mode) or by alt-tabbing to
+// it (CYCLE mode).
+class WindowSelector : public ui::EventHandler,
+ public aura::WindowObserver {
+ public:
+ enum Direction {
+ FORWARD,
+ BACKWARD
+ };
+ enum Mode {
+ CYCLE,
+ OVERVIEW
+ };
+
+ typedef std::vector<aura::Window*> WindowList;
+
+ WindowSelector(const WindowList& windows,
+ Mode mode,
+ WindowSelectorDelegate* delegate);
+ virtual ~WindowSelector();
+
+ // Step to the next window in |direction|.
+ void Step(Direction direction);
+
+ // Select the current window.
+ void SelectWindow();
+
+ Mode mode() { return mode_; }
+
+ // ui::EventHandler:
+ virtual void OnEvent(ui::Event* event) OVERRIDE;
+ virtual void OnMouseEvent(ui::MouseEvent* event) OVERRIDE;
+ virtual void OnTouchEvent(ui::TouchEvent* event) OVERRIDE;
+
+ // aura::WindowObserver:
+ virtual void OnWindowDestroyed(aura::Window* window) OVERRIDE;
+
+ private:
+ // Returns the target of |event| or NULL if the event is not targeted at
+ // any of the windows in the selector.
+ WindowSelectorWindow* GetEventTarget(ui::LocatedEvent* event);
+
+ // Handles a selection event for |target|.
+ void HandleSelectionEvent(WindowSelectorWindow* target);
+
+ // Position all of the windows based on the current selection mode.
+ void PositionWindows();
+ // Position all of the windows from |root_window| on |root_window|.
+ void PositionWindowsFromRoot(aura::RootWindow* root_window);
+ // Position all of the |windows| to fit on the |root_window|.
+ void PositionWindowsOnRoot(aura::RootWindow* root_window,
+ const std::vector<WindowSelectorWindow*>& windows);
+
+ void InitializeSelectionWidget();
+
+ // Updates the selection widget's location to the currently selected window.
+ // If |animate| the transition to the new location is animated.
+ void UpdateSelectionLocation(bool animate);
+
+ // The collection of windows in the overview wrapped by a helper class which
+ // restores their state and helps transform them to other root windows.
+ ScopedVector<WindowSelectorWindow> windows_;
+
+ // The window selection mode.
+ Mode mode_;
+
+ // Weak pointer to the selector delegate which will be called when a
+ // selection is made.
+ WindowSelectorDelegate* delegate_;
+
+ // Index of the currently selected window if the mode is CYCLE.
+ size_t selected_window_;
+
+ // Widget indicating which window is currently selected.
+ scoped_ptr<views::Widget> selection_widget_;
+
+ // In CYCLE mode, the root window in which selection is taking place.
+ // NULL otherwise.
+ aura::RootWindow* selection_root_;
+
+ DISALLOW_COPY_AND_ASSIGN(WindowSelector);
+};
+
+} // namespace ash
+
+#endif // ASH_WM_WINDOW_SELECTOR_H_
diff --git a/chromium/ash/wm/window_selector_controller.cc b/chromium/ash/wm/window_selector_controller.cc
new file mode 100644
index 00000000000..e6cd3837a64
--- /dev/null
+++ b/chromium/ash/wm/window_selector_controller.cc
@@ -0,0 +1,122 @@
+// Copyright 2013 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.
+
+#include "ash/wm/window_selector_controller.h"
+
+#include "ash/session_state_delegate.h"
+#include "ash/shell.h"
+#include "ash/wm/mru_window_tracker.h"
+#include "ash/wm/window_selector.h"
+#include "ash/wm/window_util.h"
+#include "ui/base/events/event.h"
+#include "ui/base/events/event_handler.h"
+
+namespace ash {
+
+namespace {
+
+// Filter to watch for the termination of a keyboard gesture to cycle through
+// multiple windows.
+class WindowSelectorEventFilter : public ui::EventHandler {
+ public:
+ WindowSelectorEventFilter();
+ virtual ~WindowSelectorEventFilter();
+
+ // Overridden from ui::EventHandler:
+ virtual void OnKeyEvent(ui::KeyEvent* event) OVERRIDE;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(WindowSelectorEventFilter);
+};
+
+// Watch for all keyboard events by filtering the root window.
+WindowSelectorEventFilter::WindowSelectorEventFilter() {
+ Shell::GetInstance()->AddPreTargetHandler(this);
+}
+
+WindowSelectorEventFilter::~WindowSelectorEventFilter() {
+ Shell::GetInstance()->RemovePreTargetHandler(this);
+}
+
+void WindowSelectorEventFilter::OnKeyEvent(ui::KeyEvent* event) {
+ // Views uses VKEY_MENU for both left and right Alt keys.
+ if (event->key_code() == ui::VKEY_MENU &&
+ event->type() == ui::ET_KEY_RELEASED) {
+ Shell::GetInstance()->window_selector_controller()->AltKeyReleased();
+ // Warning: |this| will be deleted from here on.
+ }
+}
+
+} // namespace
+
+WindowSelectorController::WindowSelectorController() {
+}
+
+WindowSelectorController::~WindowSelectorController() {
+}
+
+// static
+bool WindowSelectorController::CanSelect() {
+ // Don't allow a window overview if the screen is locked or a modal dialog is
+ // open.
+ return !Shell::GetInstance()->session_state_delegate()->IsScreenLocked() &&
+ !Shell::GetInstance()->IsSystemModalWindowOpen();
+}
+
+void WindowSelectorController::ToggleOverview() {
+ if (window_selector_.get()) {
+ window_selector_.reset();
+ } else {
+ std::vector<aura::Window*> windows = ash::Shell::GetInstance()->
+ mru_window_tracker()->BuildMruWindowList();
+ // Don't enter overview mode with no windows.
+ if (windows.empty())
+ return;
+
+ // Deactivating the window will hide popup windows like the omnibar or
+ // open menus.
+ aura::Window* active_window = wm::GetActiveWindow();
+ if (active_window)
+ wm::DeactivateWindow(active_window);
+ window_selector_.reset(
+ new WindowSelector(windows, WindowSelector::OVERVIEW, this));
+ }
+}
+
+void WindowSelectorController::HandleCycleWindow(
+ WindowSelector::Direction direction) {
+ if (!CanSelect())
+ return;
+
+ if (!IsSelecting()) {
+ event_handler_.reset(new WindowSelectorEventFilter());
+ std::vector<aura::Window*> windows = ash::Shell::GetInstance()->
+ mru_window_tracker()->BuildMruWindowList();
+ window_selector_.reset(
+ new WindowSelector(windows, WindowSelector::CYCLE, this));
+ window_selector_->Step(direction);
+ } else if (window_selector_->mode() == WindowSelector::CYCLE) {
+ window_selector_->Step(direction);
+ }
+}
+
+void WindowSelectorController::AltKeyReleased() {
+ event_handler_.reset();
+ window_selector_->SelectWindow();
+}
+
+bool WindowSelectorController::IsSelecting() {
+ return window_selector_.get() != NULL;
+}
+
+void WindowSelectorController::OnWindowSelected(aura::Window* window) {
+ window_selector_.reset();
+ wm::ActivateWindow(window);
+}
+
+void WindowSelectorController::OnSelectionCanceled() {
+ window_selector_.reset();
+}
+
+} // namespace ash
diff --git a/chromium/ash/wm/window_selector_controller.h b/chromium/ash/wm/window_selector_controller.h
new file mode 100644
index 00000000000..385c30ef245
--- /dev/null
+++ b/chromium/ash/wm/window_selector_controller.h
@@ -0,0 +1,72 @@
+// Copyright 2013 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 ASH_WM_WINDOW_SELECTOR_CONTROLLER_H_
+#define ASH_WM_WINDOW_SELECTOR_CONTROLLER_H_
+
+#include <list>
+#include <vector>
+
+#include "ash/ash_export.h"
+#include "ash/wm/window_selector.h"
+#include "ash/wm/window_selector_delegate.h"
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "ui/aura/window_observer.h"
+
+namespace aura {
+class Window;
+}
+
+namespace ui {
+class EventHandler;
+}
+
+namespace ash {
+
+class WindowSelector;
+
+// Manages a window selector which displays an overview of all windows and
+// allows selecting a window to activate it.
+class ASH_EXPORT WindowSelectorController
+ : public WindowSelectorDelegate {
+ public:
+ WindowSelectorController();
+ virtual ~WindowSelectorController();
+
+ // Returns true if selecting windows in an overview is enabled. This is false
+ // at certain times, such as when the lock screen is visible.
+ static bool CanSelect();
+
+ // Enters overview mode. This is essentially the window cycling mode however
+ // not released on releasing the alt key and allows selecting with the mouse
+ // or touch rather than keypresses.
+ void ToggleOverview();
+
+ // Cycles between windows in the given |direction|. It is assumed that the
+ // alt key is held down and a key filter is installed to watch for alt being
+ // released.
+ void HandleCycleWindow(WindowSelector::Direction direction);
+
+ // Informs the controller that the Alt key has been released and it can
+ // terminate the existing multi-step cycle.
+ void AltKeyReleased();
+
+ // Returns true if window selection mode is active.
+ bool IsSelecting();
+
+ // WindowSelectorDelegate:
+ virtual void OnWindowSelected(aura::Window* window) OVERRIDE;
+ virtual void OnSelectionCanceled() OVERRIDE;
+
+ private:
+ scoped_ptr<WindowSelector> window_selector_;
+ scoped_ptr<ui::EventHandler> event_handler_;
+
+ DISALLOW_COPY_AND_ASSIGN(WindowSelectorController);
+};
+
+} // namespace ash
+
+#endif // ASH_WM_WINDOW_SELECTOR_CONTROLLER_H_
diff --git a/chromium/ash/wm/window_selector_delegate.h b/chromium/ash/wm/window_selector_delegate.h
new file mode 100644
index 00000000000..fd7677f1e77
--- /dev/null
+++ b/chromium/ash/wm/window_selector_delegate.h
@@ -0,0 +1,32 @@
+// Copyright 2013 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 ASH_WM_WINDOW_SELECTOR_DELEGATE_H_
+#define ASH_WM_WINDOW_SELECTOR_DELEGATE_H_
+
+#include "ash/ash_export.h"
+#include "base/compiler_specific.h"
+
+namespace aura {
+class Window;
+}
+
+namespace ash {
+
+// Implement this class to handle the selection event from WindowSelector.
+class ASH_EXPORT WindowSelectorDelegate {
+ public:
+ // Invoked when a window is selected.
+ virtual void OnWindowSelected(aura::Window* window) = 0;
+
+ // Invoked if selection is canceled.
+ virtual void OnSelectionCanceled() = 0;
+
+ protected:
+ virtual ~WindowSelectorDelegate() {}
+};
+
+} // namespace ash
+
+#endif // ASH_WM_WINDOW_SELECTOR_DELEGATE_H_
diff --git a/chromium/ash/wm/window_selector_unittest.cc b/chromium/ash/wm/window_selector_unittest.cc
new file mode 100644
index 00000000000..9ac15d688cb
--- /dev/null
+++ b/chromium/ash/wm/window_selector_unittest.cc
@@ -0,0 +1,250 @@
+// Copyright 2013 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.
+
+#include "ash/root_window_controller.h"
+#include "ash/screen_ash.h"
+#include "ash/shell.h"
+#include "ash/test/ash_test_base.h"
+#include "ash/test/shell_test_api.h"
+#include "ash/wm/mru_window_tracker.h"
+#include "ash/wm/window_selector_controller.h"
+#include "ash/wm/window_util.h"
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/memory/scoped_vector.h"
+#include "base/run_loop.h"
+#include "ui/aura/client/aura_constants.h"
+#include "ui/aura/root_window.h"
+#include "ui/aura/test/event_generator.h"
+#include "ui/aura/test/test_window_delegate.h"
+#include "ui/aura/test/test_windows.h"
+#include "ui/aura/window.h"
+#include "ui/compositor/layer_animator.h"
+#include "ui/gfx/rect_conversions.h"
+#include "ui/gfx/transform.h"
+
+namespace ash {
+namespace internal {
+
+namespace {
+
+class LayerAnimationObserver : public ui::LayerAnimationObserver {
+ public:
+ LayerAnimationObserver(ui::Layer* layer)
+ : layer_(layer), animating_(false), message_loop_running_(false) {
+ layer_->GetAnimator()->AddObserver(this);
+ }
+
+ virtual ~LayerAnimationObserver() {
+ layer_->GetAnimator()->RemoveObserver(this);
+ }
+
+ virtual void OnLayerAnimationEnded(
+ ui::LayerAnimationSequence* sequence) OVERRIDE {
+ AnimationDone();
+ }
+
+ virtual void OnLayerAnimationScheduled(
+ ui::LayerAnimationSequence* sequence) OVERRIDE {
+ animating_ = true;
+ }
+
+ virtual void OnLayerAnimationAborted(
+ ui::LayerAnimationSequence* sequence) OVERRIDE {
+ AnimationDone();
+ }
+
+ void WaitUntilDone() {
+ while (animating_) {
+ message_loop_running_ = true;
+ base::MessageLoop::current()->Run();
+ message_loop_running_ = false;
+ }
+ }
+
+ private:
+ void AnimationDone() {
+ animating_ = false;
+ if (message_loop_running_)
+ base::MessageLoop::current()->Quit();
+ }
+
+ ui::Layer* layer_;
+ bool animating_;
+ bool message_loop_running_;
+
+ DISALLOW_COPY_AND_ASSIGN(LayerAnimationObserver);
+};
+
+} // namespace
+
+class WindowSelectorTest : public test::AshTestBase {
+ public:
+ WindowSelectorTest() {}
+ virtual ~WindowSelectorTest() {}
+
+ aura::Window* CreateWindow(const gfx::Rect& bounds) {
+ return CreateTestWindowInShellWithDelegate(&wd, -1, bounds);
+ }
+
+ bool WindowsOverlapping(aura::Window* window1, aura::Window* window2) {
+ gfx::RectF window1_bounds = GetTransformedTargetBounds(window1);
+ gfx::RectF window2_bounds = GetTransformedTargetBounds(window2);
+ return window1_bounds.Intersects(window2_bounds);
+ }
+
+ void ToggleOverview() {
+ std::vector<aura::Window*> windows = ash::Shell::GetInstance()->
+ mru_window_tracker()->BuildMruWindowList();
+ ScopedVector<LayerAnimationObserver> animations;
+ for (size_t i = 0; i < windows.size(); ++i) {
+ animations.push_back(new LayerAnimationObserver(windows[i]->layer()));
+ }
+ ash::Shell::GetInstance()->window_selector_controller()->ToggleOverview();
+ for (size_t i = 0; i < animations.size(); ++i) {
+ animations[i]->WaitUntilDone();
+ }
+ }
+
+ void Cycle(WindowSelector::Direction direction) {
+ if (!IsSelecting()) {
+ std::vector<aura::Window*> windows = ash::Shell::GetInstance()->
+ mru_window_tracker()->BuildMruWindowList();
+ ScopedVector<LayerAnimationObserver> animations;
+ for (size_t i = 0; i < windows.size(); ++i)
+ animations.push_back(new LayerAnimationObserver(windows[i]->layer()));
+ ash::Shell::GetInstance()->window_selector_controller()->
+ HandleCycleWindow(direction);
+ for (size_t i = 0; i < animations.size(); ++i)
+ animations[i]->WaitUntilDone();
+ } else {
+ ash::Shell::GetInstance()->window_selector_controller()->
+ HandleCycleWindow(direction);
+ }
+ }
+
+ void StopCycling() {
+ ash::Shell::GetInstance()->window_selector_controller()->AltKeyReleased();
+ }
+
+ gfx::RectF GetTransformedBounds(aura::Window* window) {
+ gfx::RectF bounds(window->layer()->bounds());
+ window->layer()->transform().TransformRect(&bounds);
+ return bounds;
+ }
+
+ gfx::RectF GetTransformedTargetBounds(aura::Window* window) {
+ gfx::RectF bounds(window->layer()->GetTargetBounds());
+ window->layer()->GetTargetTransform().TransformRect(&bounds);
+ return bounds;
+ }
+
+ void ClickWindow(aura::Window* window) {
+ aura::test::EventGenerator event_generator(window->GetRootWindow(), window);
+ gfx::RectF target = GetTransformedBounds(window);
+ event_generator.ClickLeftButton();
+ }
+
+ bool IsSelecting() {
+ return ash::Shell::GetInstance()->window_selector_controller()->
+ IsSelecting();
+ }
+
+ private:
+ aura::test::TestWindowDelegate wd;
+
+ DISALLOW_COPY_AND_ASSIGN(WindowSelectorTest);
+};
+
+// Tests entering overview mode with two windows and selecting one.
+TEST_F(WindowSelectorTest, Basic) {
+ gfx::Rect bounds(0, 0, 400, 400);
+ scoped_ptr<aura::Window> window1(CreateWindow(bounds));
+ scoped_ptr<aura::Window> window2(CreateWindow(bounds));
+ EXPECT_TRUE(WindowsOverlapping(window1.get(), window2.get()));
+ wm::ActivateWindow(window2.get());
+ EXPECT_FALSE(wm::IsActiveWindow(window1.get()));
+ EXPECT_TRUE(wm::IsActiveWindow(window2.get()));
+
+ // In overview mode the windows should no longer overlap.
+ ToggleOverview();
+ EXPECT_FALSE(WindowsOverlapping(window1.get(), window2.get()));
+
+ // Clicking window 1 should activate it.
+ ClickWindow(window1.get());
+ EXPECT_TRUE(wm::IsActiveWindow(window1.get()));
+ EXPECT_FALSE(wm::IsActiveWindow(window2.get()));
+}
+
+// Tests entering overview mode with three windows and cycling through them.
+TEST_F(WindowSelectorTest, BasicCycle) {
+ gfx::Rect bounds(0, 0, 400, 400);
+ scoped_ptr<aura::Window> window1(CreateWindow(bounds));
+ scoped_ptr<aura::Window> window2(CreateWindow(bounds));
+ scoped_ptr<aura::Window> window3(CreateWindow(bounds));
+ wm::ActivateWindow(window3.get());
+ wm::ActivateWindow(window2.get());
+ wm::ActivateWindow(window1.get());
+ EXPECT_TRUE(wm::IsActiveWindow(window1.get()));
+ EXPECT_FALSE(wm::IsActiveWindow(window2.get()));
+ EXPECT_FALSE(wm::IsActiveWindow(window3.get()));
+
+ Cycle(WindowSelector::FORWARD);
+ EXPECT_TRUE(IsSelecting());
+ Cycle(WindowSelector::FORWARD);
+ StopCycling();
+ EXPECT_FALSE(IsSelecting());
+ EXPECT_FALSE(wm::IsActiveWindow(window1.get()));
+ EXPECT_FALSE(wm::IsActiveWindow(window2.get()));
+ EXPECT_TRUE(wm::IsActiveWindow(window3.get()));
+}
+
+// Tests that overview mode is exited if the last remaining window is destroyed.
+TEST_F(WindowSelectorTest, LastWindowDestroyed) {
+ gfx::Rect bounds(0, 0, 400, 400);
+ scoped_ptr<aura::Window> window1(CreateWindow(bounds));
+ scoped_ptr<aura::Window> window2(CreateWindow(bounds));
+ ToggleOverview();
+
+ window1.reset();
+ window2.reset();
+ EXPECT_FALSE(IsSelecting());
+}
+
+// Tests that windows remain on the display they are currently on in overview
+// mode.
+TEST_F(WindowSelectorTest, MultipleDisplays) {
+ if (!SupportsMultipleDisplays())
+ return;
+
+ UpdateDisplay("400x400,400x400");
+ Shell::RootWindowList root_windows = Shell::GetAllRootWindows();
+
+ scoped_ptr<aura::Window> window1(CreateWindow(gfx::Rect(0, 0, 100, 100)));
+ scoped_ptr<aura::Window> window2(CreateWindow(gfx::Rect(0, 0, 100, 100)));
+ scoped_ptr<aura::Window> window3(CreateWindow(gfx::Rect(450, 0, 100, 100)));
+ scoped_ptr<aura::Window> window4(CreateWindow(gfx::Rect(450, 0, 100, 100)));
+ EXPECT_EQ(root_windows[0], window1->GetRootWindow());
+ EXPECT_EQ(root_windows[0], window2->GetRootWindow());
+ EXPECT_EQ(root_windows[1], window3->GetRootWindow());
+ EXPECT_EQ(root_windows[1], window4->GetRootWindow());
+
+ // In overview mode, each window remains in the same root window.
+ ToggleOverview();
+ EXPECT_EQ(root_windows[0], window1->GetRootWindow());
+ EXPECT_EQ(root_windows[0], window2->GetRootWindow());
+ EXPECT_EQ(root_windows[1], window3->GetRootWindow());
+ EXPECT_EQ(root_windows[1], window4->GetRootWindow());
+ root_windows[0]->bounds().Contains(
+ ToEnclosingRect(GetTransformedBounds(window1.get())));
+ root_windows[0]->bounds().Contains(
+ ToEnclosingRect(GetTransformedBounds(window2.get())));
+ root_windows[1]->bounds().Contains(
+ ToEnclosingRect(GetTransformedBounds(window3.get())));
+ root_windows[1]->bounds().Contains(
+ ToEnclosingRect(GetTransformedBounds(window4.get())));
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/wm/window_util.cc b/chromium/ash/wm/window_util.cc
new file mode 100644
index 00000000000..67dcd95c4c1
--- /dev/null
+++ b/chromium/ash/wm/window_util.cc
@@ -0,0 +1,208 @@
+// Copyright (c) 2012 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.
+
+#include "ash/wm/window_util.h"
+
+#include <vector>
+
+#include "ash/ash_constants.h"
+#include "ash/root_window_controller.h"
+#include "ash/shell.h"
+#include "ash/shell_window_ids.h"
+#include "ash/wm/activation_controller.h"
+#include "ash/wm/window_properties.h"
+#include "ui/aura/client/activation_client.h"
+#include "ui/aura/client/aura_constants.h"
+#include "ui/aura/root_window.h"
+#include "ui/aura/window.h"
+#include "ui/aura/window_delegate.h"
+#include "ui/compositor/layer.h"
+#include "ui/gfx/display.h"
+#include "ui/gfx/rect.h"
+#include "ui/gfx/screen.h"
+#include "ui/views/corewm/window_util.h"
+#include "ui/views/view.h"
+#include "ui/views/widget/widget.h"
+
+namespace ash {
+namespace wm {
+
+// TODO(beng): replace many of these functions with the corewm versions.
+void ActivateWindow(aura::Window* window) {
+ views::corewm::ActivateWindow(window);
+}
+
+void DeactivateWindow(aura::Window* window) {
+ views::corewm::DeactivateWindow(window);
+}
+
+bool IsActiveWindow(aura::Window* window) {
+ return views::corewm::IsActiveWindow(window);
+}
+
+aura::Window* GetActiveWindow() {
+ return aura::client::GetActivationClient(Shell::GetPrimaryRootWindow())->
+ GetActiveWindow();
+}
+
+aura::Window* GetActivatableWindow(aura::Window* window) {
+ return views::corewm::GetActivatableWindow(window);
+}
+
+bool CanActivateWindow(aura::Window* window) {
+ return views::corewm::CanActivateWindow(window);
+}
+
+bool CanMaximizeWindow(const aura::Window* window) {
+ return window->GetProperty(aura::client::kCanMaximizeKey);
+}
+
+bool CanMinimizeWindow(const aura::Window* window) {
+ internal::RootWindowController* controller =
+ internal::RootWindowController::ForWindow(window);
+ if (!controller)
+ return false;
+ aura::Window* lockscreen = controller->GetContainer(
+ internal::kShellWindowId_LockScreenContainersContainer);
+ if (lockscreen->Contains(window))
+ return false;
+
+ return true;
+}
+
+bool CanResizeWindow(const aura::Window* window) {
+ return window->GetProperty(aura::client::kCanResizeKey);
+}
+
+bool CanSnapWindow(aura::Window* window) {
+ if (!CanResizeWindow(window))
+ return false;
+ // If a window has a maximum size defined, snapping may make it too big.
+ return window->delegate() ? window->delegate()->GetMaximumSize().IsEmpty() :
+ true;
+}
+
+bool IsWindowNormal(const aura::Window* window) {
+ return IsWindowStateNormal(window->GetProperty(aura::client::kShowStateKey));
+}
+
+bool IsWindowStateNormal(ui::WindowShowState state) {
+ return state == ui::SHOW_STATE_NORMAL || state == ui::SHOW_STATE_DEFAULT;
+}
+
+bool IsWindowMaximized(const aura::Window* window) {
+ return window->GetProperty(aura::client::kShowStateKey) ==
+ ui::SHOW_STATE_MAXIMIZED;
+}
+
+bool IsWindowMinimized(const aura::Window* window) {
+ return window->GetProperty(aura::client::kShowStateKey) ==
+ ui::SHOW_STATE_MINIMIZED;
+}
+
+bool IsWindowFullscreen(const aura::Window* window) {
+ return window->GetProperty(aura::client::kShowStateKey) ==
+ ui::SHOW_STATE_FULLSCREEN;
+}
+
+void MaximizeWindow(aura::Window* window) {
+ window->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MAXIMIZED);
+}
+
+void MinimizeWindow(aura::Window* window) {
+ window->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MINIMIZED);
+}
+
+void RestoreWindow(aura::Window* window) {
+ window->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_NORMAL);
+}
+
+void ToggleMaximizedWindow(aura::Window* window) {
+ if (ash::wm::IsWindowMaximized(window))
+ ash::wm::RestoreWindow(window);
+ else if (ash::wm::CanMaximizeWindow(window))
+ ash::wm::MaximizeWindow(window);
+}
+
+void CenterWindow(aura::Window* window) {
+ const gfx::Display display =
+ Shell::GetScreen()->GetDisplayNearestWindow(window);
+ gfx::Rect center = display.work_area();
+ center.ClampToCenteredSize(window->bounds().size());
+ window->SetBoundsInScreen(center, display);
+}
+
+bool IsWindowPositionManaged(const aura::Window* window) {
+ return window->GetProperty(ash::internal::kWindowPositionManagedKey);
+}
+
+void SetWindowPositionManaged(aura::Window* window, bool managed) {
+ window->SetProperty(ash::internal::kWindowPositionManagedKey, managed);
+}
+
+bool HasUserChangedWindowPositionOrSize(const aura::Window* window) {
+ return window->GetProperty(
+ ash::internal::kUserChangedWindowPositionOrSizeKey);
+}
+
+void SetUserHasChangedWindowPositionOrSize(aura::Window* window, bool changed) {
+ window->SetProperty(ash::internal::kUserChangedWindowPositionOrSizeKey,
+ changed);
+}
+
+const gfx::Rect* GetPreAutoManageWindowBounds(const aura::Window* window) {
+ return window->GetProperty(ash::internal::kPreAutoManagedWindowBoundsKey);
+}
+
+void SetPreAutoManageWindowBounds(aura::Window* window,
+ const gfx::Rect& bounds) {
+ window->SetProperty(ash::internal::kPreAutoManagedWindowBoundsKey,
+ new gfx::Rect(bounds));
+}
+
+void AdjustBoundsToEnsureMinimumWindowVisibility(const gfx::Rect& work_area,
+ gfx::Rect* bounds) {
+ AdjustBoundsToEnsureWindowVisibility(
+ work_area, kMinimumOnScreenArea, kMinimumOnScreenArea, bounds);
+}
+
+void AdjustBoundsToEnsureWindowVisibility(const gfx::Rect& work_area,
+ int min_width,
+ int min_height,
+ gfx::Rect* bounds) {
+ bounds->set_width(std::min(bounds->width(), work_area.width()));
+ bounds->set_height(std::min(bounds->height(), work_area.height()));
+
+ min_width = std::min(min_width, work_area.width());
+ min_height = std::min(min_height, work_area.height());
+
+ if (bounds->x() + min_width > work_area.right()) {
+ bounds->set_x(work_area.right() - min_width);
+ } else if (bounds->right() - min_width < 0) {
+ bounds->set_x(min_width - bounds->width());
+ }
+ if (bounds->y() + min_height > work_area.bottom()) {
+ bounds->set_y(work_area.bottom() - min_height);
+ } else if (bounds->bottom() - min_height < 0) {
+ bounds->set_y(min_height - bounds->height());
+ }
+}
+
+bool MoveWindowToEventRoot(aura::Window* window, const ui::Event& event) {
+ views::View* target = static_cast<views::View*>(event.target());
+ if (!target)
+ return false;
+ aura::RootWindow* target_root =
+ target->GetWidget()->GetNativeView()->GetRootWindow();
+ if (!target_root || target_root == window->GetRootWindow())
+ return false;
+ aura::Window* window_container =
+ ash::Shell::GetContainer(target_root, window->parent()->id());
+ // Move the window to the target launcher.
+ window_container->AddChild(window);
+ return true;
+}
+
+} // namespace wm
+} // namespace ash
diff --git a/chromium/ash/wm/window_util.h b/chromium/ash/wm/window_util.h
new file mode 100644
index 00000000000..1ffeb7db8a3
--- /dev/null
+++ b/chromium/ash/wm/window_util.h
@@ -0,0 +1,133 @@
+// Copyright (c) 2012 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 ASH_WM_WINDOW_UTIL_H_
+#define ASH_WM_WINDOW_UTIL_H_
+
+#include "ash/ash_export.h"
+#include "base/compiler_specific.h"
+#include "ui/base/ui_base_types.h"
+
+namespace aura {
+class RootWindow;
+class Window;
+}
+
+namespace gfx {
+class Rect;
+}
+
+namespace ui {
+class Event;
+class Layer;
+}
+
+namespace ash {
+// We force at least this many DIPs for any window on the screen.
+const int kMinimumOnScreenArea = 10;
+
+namespace wm {
+
+// Convenience setters/getters for |aura::client::kRootWindowActiveWindow|.
+ASH_EXPORT void ActivateWindow(aura::Window* window);
+ASH_EXPORT void DeactivateWindow(aura::Window* window);
+ASH_EXPORT bool IsActiveWindow(aura::Window* window);
+ASH_EXPORT aura::Window* GetActiveWindow();
+ASH_EXPORT bool CanActivateWindow(aura::Window* window);
+
+// Retrieves the activatable window for |window|. If |window| is activatable,
+// this will just return it, otherwise it will climb the parent/transient parent
+// chain looking for a window that is activatable, per the ActivationController.
+// If you're looking for a function to get the activatable "top level" window,
+// this is probably what you're looking for.
+ASH_EXPORT aura::Window* GetActivatableWindow(aura::Window* window);
+
+// Returns true if |window| can be maximized.
+ASH_EXPORT bool CanMaximizeWindow(const aura::Window* window);
+
+// Returns true if |window| can be minimized.
+ASH_EXPORT bool CanMinimizeWindow(const aura::Window* window);
+
+// Returns true if |window| can be resized.
+ASH_EXPORT bool CanResizeWindow(const aura::Window* window);
+
+// Returns true if |window| can be snapped to the left or right.
+ASH_EXPORT bool CanSnapWindow(aura::Window* window);
+
+// Returns true if |window| is normal or default.
+ASH_EXPORT bool IsWindowNormal(const aura::Window* window);
+
+// Returns true if |state| is normal or default.
+ASH_EXPORT bool IsWindowStateNormal(const ui::WindowShowState state);
+
+// Returns true if |window| is in the maximized state.
+ASH_EXPORT bool IsWindowMaximized(const aura::Window* window);
+
+// Returns true if |window| is minimized.
+ASH_EXPORT bool IsWindowMinimized(const aura::Window* window);
+
+// Returns true if |window| is in the fullscreen state.
+ASH_EXPORT bool IsWindowFullscreen(const aura::Window* window);
+
+// Maximizes |window|, which must not be NULL.
+ASH_EXPORT void MaximizeWindow(aura::Window* window);
+
+// Minimizes |window|, which must not be NULL.
+ASH_EXPORT void MinimizeWindow(aura::Window* window);
+
+// Restores |window|, which must not be NULL.
+ASH_EXPORT void RestoreWindow(aura::Window* window);
+
+// Maximizes or restores |window| based on its state. |window| must not be NULL.
+ASH_EXPORT void ToggleMaximizedWindow(aura::Window* window);
+
+// Moves the window to the center of the display.
+ASH_EXPORT void CenterWindow(aura::Window* window);
+
+// Returns true if |window|'s position can automatically be managed.
+ASH_EXPORT bool IsWindowPositionManaged(const aura::Window* window);
+
+// Change the |window|'s position manageability to |managed|.
+ASH_EXPORT void SetWindowPositionManaged(aura::Window* window, bool managed);
+
+// Returns true if the user has changed the |window|'s position or size.
+ASH_EXPORT bool HasUserChangedWindowPositionOrSize(const aura::Window* window);
+
+// Marks a |window|'s coordinates to be changed by a user.
+ASH_EXPORT void SetUserHasChangedWindowPositionOrSize(aura::Window* window,
+ bool changed);
+
+// Get |window| bounds of the window before it was moved by the auto window
+// management. As long as it was not managed, it will return NULL.
+ASH_EXPORT const gfx::Rect* GetPreAutoManageWindowBounds(
+ const aura::Window* window);
+
+// Remember the |bounds| of a |window| before an automated window management
+// operation takes place.
+ASH_EXPORT void SetPreAutoManageWindowBounds(aura::Window* window,
+ const gfx::Rect& bounds);
+
+// Move the given bounds inside the given work area, including a safety margin
+// given by |kMinimumOnScreenArea|.
+ASH_EXPORT void AdjustBoundsToEnsureMinimumWindowVisibility(
+ const gfx::Rect& work_area,
+ gfx::Rect* bounds);
+
+// Move the given bounds inside the given work area, including a safety margin
+// given by |min_width| and |min_height|.
+ASH_EXPORT void AdjustBoundsToEnsureWindowVisibility(
+ const gfx::Rect& work_area,
+ int min_width,
+ int min_height,
+ gfx::Rect* bounds);
+
+// Moves |window| to the root window where the |event| occured if it is not
+// already in the same root window. Returns true if |window| was moved.
+ASH_EXPORT bool MoveWindowToEventRoot(aura::Window* window,
+ const ui::Event& event);
+
+} // namespace wm
+} // namespace ash
+
+#endif // ASH_WM_WINDOW_UTIL_H_
diff --git a/chromium/ash/wm/window_util_unittest.cc b/chromium/ash/wm/window_util_unittest.cc
new file mode 100644
index 00000000000..8073a38cc87
--- /dev/null
+++ b/chromium/ash/wm/window_util_unittest.cc
@@ -0,0 +1,32 @@
+// Copyright (c) 2013 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.
+
+#include "ash/wm/window_util.h"
+
+#include "ash/screen_ash.h"
+#include "ash/test/ash_test_base.h"
+#include "ui/aura/window.h"
+
+namespace ash {
+
+typedef test::AshTestBase WindowUtilTest;
+
+TEST_F(WindowUtilTest, CenterWindow) {
+ if (!SupportsMultipleDisplays())
+ return;
+
+ UpdateDisplay("500x400, 600x400");
+ scoped_ptr<aura::Window> window(
+ CreateTestWindowInShellWithBounds(gfx::Rect(12, 20, 100, 100)));
+ wm::CenterWindow(window.get());
+ EXPECT_EQ("200,126 100x100", window->bounds().ToString());
+ EXPECT_EQ("200,126 100x100", window->GetBoundsInScreen().ToString());
+ window->SetBoundsInScreen(gfx::Rect(600, 0, 100, 100),
+ ScreenAsh::GetSecondaryDisplay());
+ wm::CenterWindow(window.get());
+ EXPECT_EQ("250,126 100x100", window->bounds().ToString());
+ EXPECT_EQ("750,126 100x100", window->GetBoundsInScreen().ToString());
+}
+
+} // namespace ash
diff --git a/chromium/ash/wm/workspace/auto_window_management.cc b/chromium/ash/wm/workspace/auto_window_management.cc
new file mode 100644
index 00000000000..64a45649087
--- /dev/null
+++ b/chromium/ash/wm/workspace/auto_window_management.cc
@@ -0,0 +1,201 @@
+// Copyright (c) 2012 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.
+
+#include "ash/wm/workspace/auto_window_management.h"
+
+#include "ash/ash_switches.h"
+#include "ash/shell.h"
+#include "ash/wm/property_util.h"
+#include "ash/wm/window_animations.h"
+#include "ash/wm/window_util.h"
+#include "base/command_line.h"
+#include "ui/aura/window.h"
+#include "ui/aura/client/aura_constants.h"
+#include "ui/compositor/layer_animator.h"
+#include "ui/compositor/scoped_layer_animation_settings.h"
+#include "ui/gfx/screen.h"
+
+namespace ash {
+namespace internal {
+
+namespace {
+
+// The time in milliseconds which should be used to visually move a window
+// through an automatic "intelligent" window management option.
+const int kWindowAutoMoveDurationMS = 125;
+
+// Check if any management should be performed (with a given |window|).
+bool UseAutoWindowMagerForWindow(const aura::Window* window) {
+ return !CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kAshDisableAutoWindowPlacement) &&
+ GetTrackedByWorkspace(window) &&
+ wm::IsWindowPositionManaged(window);
+}
+
+// Check if a given |window| can be managed. This includes that it's state is
+// not minimized/maximized/the user has changed it's size by hand already.
+// It furthermore checks for the WindowIsManaged status.
+bool WindowPositionCanBeManaged(const aura::Window* window) {
+ return (wm::IsWindowPositionManaged(window) &&
+ !wm::IsWindowMinimized(window) &&
+ !wm::IsWindowMaximized(window) &&
+ !wm::HasUserChangedWindowPositionOrSize(window));
+}
+
+// Given a |window|, return the only |other_window| which has an impact on
+// the automated windows location management. If there is more then one window,
+// false is returned, but the |other_window| will be set to the first one
+// found.
+// If the return value is true a single window was found.
+bool GetOtherVisibleAndManageableWindow(const aura::Window* window,
+ aura::Window** other_window) {
+ *other_window = NULL;
+ const aura::Window::Windows& windows = window->parent()->children();
+ // Find a single open managed window.
+ for (size_t i = 0; i < windows.size(); i++) {
+ aura::Window* iterated_window = windows[i];
+ if (window != iterated_window &&
+ iterated_window->type() == aura::client::WINDOW_TYPE_NORMAL &&
+ iterated_window->TargetVisibility() &&
+ wm::IsWindowPositionManaged(iterated_window)) {
+ // Bail if we find a second usable window.
+ if (*other_window)
+ return false;
+ *other_window = iterated_window;
+ }
+ }
+ return *other_window != NULL;
+}
+
+// Get the work area for a given |window|.
+gfx::Rect GetWorkAreaForWindow(aura::Window* window) {
+#if defined(OS_WIN)
+ // On Win 8, the host window can't be resized, so
+ // use window's bounds instead.
+ // TODO(oshima): Emulate host window resize on win8.
+ gfx::Rect work_area = gfx::Rect(window->parent()->bounds().size());
+ work_area.Inset(Shell::GetScreen()->GetDisplayMatching(
+ work_area).GetWorkAreaInsets());
+ return work_area;
+#else
+ return Shell::GetScreen()->GetDisplayNearestWindow(window).work_area();
+#endif
+}
+
+// Move the given |bounds| on the available |parent_width| to the
+// direction. If |move_right| is true, the rectangle gets moved to the right
+// corner, otherwise to the left one.
+bool MoveRectToOneSide(int parent_width, bool move_right, gfx::Rect* bounds) {
+ if (move_right) {
+ if (parent_width > bounds->right()) {
+ bounds->set_x(parent_width - bounds->width());
+ return true;
+ }
+ } else {
+ if (0 < bounds->x()) {
+ bounds->set_x(0);
+ return true;
+ }
+ }
+ return false;
+}
+
+// Move a |window| to a new |bound|. Animate if desired by user.
+// Note: The function will do nothing if the bounds did not change.
+void SetBoundsAnimated(aura::Window* window, const gfx::Rect& bounds) {
+ if (bounds == window->GetTargetBounds())
+ return;
+
+ if (views::corewm::WindowAnimationsDisabled(window)) {
+ window->SetBounds(bounds);
+ return;
+ }
+
+ ui::ScopedLayerAnimationSettings settings(window->layer()->GetAnimator());
+ settings.SetTransitionDuration(
+ base::TimeDelta::FromMilliseconds(kWindowAutoMoveDurationMS));
+ window->SetBounds(bounds);
+}
+
+// Move |window| into the center of the screen - or restore it to the previous
+// position.
+void AutoPlaceSingleWindow(aura::Window* window, bool animated) {
+ gfx::Rect work_area = GetWorkAreaForWindow(window);
+ gfx::Rect bounds = window->bounds();
+ const gfx::Rect* user_defined_area =
+ ash::wm::GetPreAutoManageWindowBounds(window);
+ if (user_defined_area) {
+ bounds = *user_defined_area;
+ ash::wm::AdjustBoundsToEnsureMinimumWindowVisibility(work_area, &bounds);
+ } else {
+ // Center the window (only in x).
+ bounds.set_x((work_area.width() - bounds.width()) / 2);
+ }
+
+ if (animated)
+ SetBoundsAnimated(window, bounds);
+ else
+ window->SetBounds(bounds);
+}
+
+} // namespace
+
+void RearrangeVisibleWindowOnHideOrRemove(const aura::Window* removed_window) {
+ if (!UseAutoWindowMagerForWindow(removed_window))
+ return;
+ // Find a single open browser window.
+ aura::Window* other_shown_window = NULL;
+ if (!GetOtherVisibleAndManageableWindow(removed_window,
+ &other_shown_window) ||
+ !WindowPositionCanBeManaged(other_shown_window))
+ return;
+ AutoPlaceSingleWindow(other_shown_window, true);
+}
+
+void RearrangeVisibleWindowOnShow(aura::Window* added_window) {
+ if (!UseAutoWindowMagerForWindow(added_window) ||
+ wm::HasUserChangedWindowPositionOrSize(added_window) ||
+ !added_window->TargetVisibility())
+ return;
+ // Find a single open managed window.
+ aura::Window* other_shown_window = NULL;
+ if (!GetOtherVisibleAndManageableWindow(added_window,
+ &other_shown_window)) {
+ // It could be that this window is the first window joining the workspace.
+ if (!WindowPositionCanBeManaged(added_window) || other_shown_window)
+ return;
+ // Since we might be going from 0 to 1 window, we have to arrange the new
+ // window to a good default.
+ AutoPlaceSingleWindow(added_window, false);
+ return;
+ }
+
+ // When going from one to two windows both windows loose their "positioned
+ // by user" flags.
+ ash::wm::SetUserHasChangedWindowPositionOrSize(added_window, false);
+ ash::wm::SetUserHasChangedWindowPositionOrSize(other_shown_window, false);
+
+ if (WindowPositionCanBeManaged(other_shown_window)) {
+ gfx::Rect work_area = GetWorkAreaForWindow(added_window);
+
+ // Push away the other window after remembering its current position.
+ gfx::Rect other_bounds = other_shown_window->bounds();
+ ash::wm::SetPreAutoManageWindowBounds(other_shown_window, other_bounds);
+
+ bool move_right = other_bounds.CenterPoint().x() > work_area.width() / 2;
+ if (MoveRectToOneSide(work_area.width(), move_right, &other_bounds))
+ SetBoundsAnimated(other_shown_window, other_bounds);
+
+ // Remember the current location of the new window and push it also to the
+ // opposite location (if needed).
+ // Since it is just coming into view, we do not need to animate it.
+ gfx::Rect added_bounds = added_window->bounds();
+ ash::wm::SetPreAutoManageWindowBounds(added_window, added_bounds);
+ if (MoveRectToOneSide(work_area.width(), !move_right, &added_bounds))
+ added_window->SetBounds(added_bounds);
+ }
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/wm/workspace/auto_window_management.h b/chromium/ash/wm/workspace/auto_window_management.h
new file mode 100644
index 00000000000..138cb546a3e
--- /dev/null
+++ b/chromium/ash/wm/workspace/auto_window_management.h
@@ -0,0 +1,20 @@
+// Copyright (c) 2012 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.
+
+#include "ui/aura/window.h"
+
+namespace ash {
+namespace internal {
+
+// Check if after removal or hide of the given |removed_window| an automated
+// desktop location management can be performed and rearrange accordingly.
+void RearrangeVisibleWindowOnHideOrRemove(const aura::Window* removed_window);
+
+// Check if after insertion or showing of the given |added_window| an automated
+// desktop location management can be performed and rearrange accordingly.
+void RearrangeVisibleWindowOnShow(aura::Window* added_window);
+
+} // namespace internal
+} // namespace ash
+
diff --git a/chromium/ash/wm/workspace/colored_window_controller.cc b/chromium/ash/wm/workspace/colored_window_controller.cc
new file mode 100644
index 00000000000..08c3dc010a9
--- /dev/null
+++ b/chromium/ash/wm/workspace/colored_window_controller.cc
@@ -0,0 +1,92 @@
+// Copyright (c) 2012 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.
+
+#include "ash/wm/workspace/colored_window_controller.h"
+
+#include "ash/shell_window_ids.h"
+#include "ash/wm/property_util.h"
+#include "ui/aura/client/aura_constants.h"
+#include "ui/aura/root_window.h"
+#include "ui/gfx/canvas.h"
+#include "ui/views/widget/widget.h"
+#include "ui/views/widget/widget_delegate.h"
+
+namespace ash {
+namespace internal {
+
+// View implementation responsible for rendering the background.
+class ColoredWindowController::View : public views::WidgetDelegateView {
+ public:
+ explicit View(ColoredWindowController* controller);
+ virtual ~View();
+
+ // Closes the hosting widget.
+ void Close();
+
+ // WidgetDelegate overrides:
+ virtual views::View* GetContentsView() OVERRIDE;
+
+ private:
+ ColoredWindowController* controller_;
+
+ DISALLOW_COPY_AND_ASSIGN(View);
+};
+
+ColoredWindowController::View::View(ColoredWindowController* controller)
+ : controller_(controller) {
+}
+
+ColoredWindowController::View::~View() {
+ if (controller_)
+ controller_->view_ = NULL;
+}
+
+void ColoredWindowController::View::Close() {
+ controller_ = NULL;
+ GetWidget()->Close();
+}
+
+views::View* ColoredWindowController::View::GetContentsView() {
+ return this;
+}
+
+ColoredWindowController::ColoredWindowController(aura::Window* parent,
+ const std::string& window_name)
+ : view_(new View(this)) {
+ views::Widget* widget = new views::Widget;
+ views::Widget::InitParams params(
+ views::Widget::InitParams::TYPE_WINDOW_FRAMELESS);
+ params.delegate = view_;
+ params.parent = parent;
+ params.can_activate = false;
+ params.accept_events = false;
+ params.layer_type = ui::LAYER_SOLID_COLOR;
+ widget->Init(params);
+ // Do this so the parent doesn't attempt to enforce any bounds constraints on
+ // us.
+ SetTrackedByWorkspace(widget->GetNativeView(), false);
+ widget->GetNativeView()->SetProperty(aura::client::kAnimationsDisabledKey,
+ true);
+ widget->GetNativeView()->SetName(window_name);
+ // The bounds should match the parent exactly. We don't go through
+ // Widget::SetBounds() as that may try to place on a different display.
+ widget->GetNativeWindow()->SetBounds(gfx::Rect(parent->bounds()));
+}
+
+ColoredWindowController::~ColoredWindowController() {
+ if (view_)
+ view_->Close();
+}
+
+void ColoredWindowController::SetColor(SkColor color) {
+ if (view_)
+ view_->GetWidget()->GetNativeView()->layer()->SetColor(color);
+}
+
+views::Widget* ColoredWindowController::GetWidget() {
+ return view_ ? view_->GetWidget() : NULL;
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/wm/workspace/colored_window_controller.h b/chromium/ash/wm/workspace/colored_window_controller.h
new file mode 100644
index 00000000000..d70a83b7a31
--- /dev/null
+++ b/chromium/ash/wm/workspace/colored_window_controller.h
@@ -0,0 +1,52 @@
+// Copyright (c) 2012 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 ASH_WM_WORKSPACE_COLORED_WINDOW_CONTROLLER_H_
+#define ASH_WM_WORKSPACE_COLORED_WINDOW_CONTROLLER_H_
+
+#include <string>
+
+#include "ash/ash_export.h"
+#include "base/basictypes.h"
+
+typedef unsigned int SkColor;
+
+namespace aura {
+class Window;
+}
+
+namespace views {
+class Widget;
+}
+
+namespace ash {
+namespace internal {
+
+// ColoredWindowController creates a Widget whose layer is LAYER_SOLID_COLOR.
+// The Widget is sized to the supplied Window and parented to the specified
+// Window. It is used for animations.
+class ASH_EXPORT ColoredWindowController {
+ public:
+ ColoredWindowController(aura::Window* parent, const std::string& window_name);
+ ~ColoredWindowController();
+
+ // Changes the background color.
+ void SetColor(SkColor color);
+
+ views::Widget* GetWidget();
+
+ private:
+ class View;
+
+ // View responsible for rendering the background. This is non-NULL if the
+ // widget containing it is deleted. It is owned by the widget.
+ View* view_;
+
+ DISALLOW_COPY_AND_ASSIGN(ColoredWindowController);
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_WM_WORKSPACE_COLORED_WINDOW_CONTROLLER_H_
diff --git a/chromium/ash/wm/workspace/desktop_background_fade_controller.cc b/chromium/ash/wm/workspace/desktop_background_fade_controller.cc
new file mode 100644
index 00000000000..563d07bfe36
--- /dev/null
+++ b/chromium/ash/wm/workspace/desktop_background_fade_controller.cc
@@ -0,0 +1,65 @@
+// Copyright (c) 2012 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.
+
+#include "ash/wm/workspace/desktop_background_fade_controller.h"
+
+#include "ash/wm/window_animations.h"
+#include "ash/wm/workspace/colored_window_controller.h"
+#include "base/time/time.h"
+#include "ui/aura/window.h"
+#include "ui/compositor/scoped_layer_animation_settings.h"
+#include "ui/views/widget/widget.h"
+
+namespace ash {
+namespace internal {
+
+DesktopBackgroundFadeController::DesktopBackgroundFadeController(
+ aura::Window* parent,
+ aura::Window* position_above,
+ base::TimeDelta duration,
+ Direction direction) {
+ SkColor start_color, target_color;
+ ui::Tween::Type tween_type;
+ if (direction == FADE_OUT) {
+ start_color = SkColorSetARGB(0, 0, 0, 0);
+ target_color = SK_ColorBLACK;
+ tween_type = ui::Tween::EASE_IN_OUT;
+ } else {
+ start_color = SK_ColorBLACK;
+ target_color = SkColorSetARGB(0, 0, 0, 0);
+ tween_type = ui::Tween::EASE_IN_OUT;
+ }
+
+ window_controller_.reset(
+ new ColoredWindowController(parent, "DesktopFade"));
+
+ // Force the window to be directly on top of the desktop.
+ aura::Window* fade_window = window_controller_->GetWidget()->GetNativeView();
+ parent->StackChildBelow(fade_window, position_above);
+ parent->StackChildAbove(fade_window, position_above);
+ window_controller_->SetColor(start_color);
+ views::corewm::SetWindowVisibilityAnimationTransition(
+ window_controller_->GetWidget()->GetNativeView(),
+ views::corewm::ANIMATE_NONE);
+ window_controller_->GetWidget()->Show();
+ {
+ ui::ScopedLayerAnimationSettings scoped_setter(
+ fade_window->layer()->GetAnimator());
+ scoped_setter.AddObserver(this);
+ scoped_setter.SetTweenType(tween_type);
+ scoped_setter.SetTransitionDuration(duration);
+ window_controller_->SetColor(target_color);
+ }
+}
+
+DesktopBackgroundFadeController::~DesktopBackgroundFadeController() {
+ StopObservingImplicitAnimations();
+}
+
+void DesktopBackgroundFadeController::OnImplicitAnimationsCompleted() {
+ window_controller_.reset();
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/wm/workspace/desktop_background_fade_controller.h b/chromium/ash/wm/workspace/desktop_background_fade_controller.h
new file mode 100644
index 00000000000..3cc0137da6f
--- /dev/null
+++ b/chromium/ash/wm/workspace/desktop_background_fade_controller.h
@@ -0,0 +1,68 @@
+// Copyright (c) 2012 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 ASH_WM_WORKSPACE_DESKTOP_BACKGROUND_FADE_CONTROLLER_H_
+#define ASH_WM_WORKSPACE_DESKTOP_BACKGROUND_FADE_CONTROLLER_H_
+
+#include "ash/ash_export.h"
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "ui/compositor/layer_animation_observer.h"
+
+namespace aura {
+class Window;
+}
+
+namespace base {
+class TimeDelta;
+}
+
+namespace ash {
+namespace internal {
+
+class ColoredWindowController;
+
+// DesktopBackgroundFadeController handles fading in or out the desktop. It is
+// used when maximizing or restoring a window. It is implemented as a colored
+// layer whose opacity varies. This results in fading in or out all the windows
+// the DesktopBackgroundFadeController is placed on top of. This is used
+// instead of varying the opacity for two reasons:
+// . The window showing background and the desktop workspace do not have a
+// common parent that can be animated. This could be fixed, but wouldn't
+// address the following.
+// . When restoring the window is moved back to the desktop workspace. If we
+// animated the opacity of the desktop workspace the cross fade would be
+// effected.
+class ASH_EXPORT DesktopBackgroundFadeController
+ : public ui::ImplicitAnimationObserver {
+ public:
+ // Direction to fade.
+ enum Direction {
+ FADE_IN,
+ FADE_OUT,
+ };
+
+ // Creates a new DesktopBackgroundFadeController. |parent| is the Window to
+// parent the newly created window to. The newly created window is stacked
+// directly on top of |position_above|. The window animating the fade is
+// destroyed as soon as the animation completes.
+ DesktopBackgroundFadeController(aura::Window* parent,
+ aura::Window* position_above,
+ base::TimeDelta duration,
+ Direction direction);
+ virtual ~DesktopBackgroundFadeController();
+
+ private:
+ // ImplicitAnimationObserver overrides:
+ virtual void OnImplicitAnimationsCompleted() OVERRIDE;
+
+ scoped_ptr<ColoredWindowController> window_controller_;
+
+ DISALLOW_COPY_AND_ASSIGN(DesktopBackgroundFadeController);
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_WM_WORKSPACE_DESKTOP_BACKGROUND_FADE_CONTROLLER_H_
diff --git a/chromium/ash/wm/workspace/frame_maximize_button.cc b/chromium/ash/wm/workspace/frame_maximize_button.cc
new file mode 100644
index 00000000000..78438f02f15
--- /dev/null
+++ b/chromium/ash/wm/workspace/frame_maximize_button.cc
@@ -0,0 +1,624 @@
+// Copyright (c) 2012 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.
+
+#include "ash/wm/workspace/frame_maximize_button.h"
+
+#include "ash/launcher/launcher.h"
+#include "ash/screen_ash.h"
+#include "ash/shelf/shelf_widget.h"
+#include "ash/shell.h"
+#include "ash/shell_delegate.h"
+#include "ash/touch/touch_uma.h"
+#include "ash/wm/maximize_bubble_controller.h"
+#include "ash/wm/property_util.h"
+#include "ash/wm/window_animations.h"
+#include "ash/wm/window_properties.h"
+#include "ash/wm/window_util.h"
+#include "ash/wm/workspace/phantom_window_controller.h"
+#include "ash/wm/workspace/snap_sizer.h"
+#include "grit/ash_strings.h"
+#include "ui/aura/window.h"
+#include "ui/base/events/event.h"
+#include "ui/base/events/event_handler.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "ui/gfx/image/image.h"
+#include "ui/gfx/screen.h"
+#include "ui/views/widget/widget.h"
+#include "ui/views/window/non_client_view.h"
+
+using ash::internal::SnapSizer;
+
+namespace ash {
+
+namespace {
+
+// Delay before forcing an update of the snap location.
+const int kUpdateDelayMS = 400;
+
+// The delay of the bubble appearance.
+const int kBubbleAppearanceDelayMS = 500;
+
+// The minimum sanp size in percent of the screen width.
+const int kMinSnapSizePercent = 50;
+}
+
+// EscapeEventFilter is installed on the RootWindow to track when the escape key
+// is pressed. We use an EventFilter for this as the FrameMaximizeButton
+// normally does not get focus.
+class FrameMaximizeButton::EscapeEventFilter : public ui::EventHandler {
+ public:
+ explicit EscapeEventFilter(FrameMaximizeButton* button);
+ virtual ~EscapeEventFilter();
+
+ // EventFilter overrides:
+ virtual void OnKeyEvent(ui::KeyEvent* event) OVERRIDE;
+
+ private:
+ FrameMaximizeButton* button_;
+
+ DISALLOW_COPY_AND_ASSIGN(EscapeEventFilter);
+};
+
+FrameMaximizeButton::EscapeEventFilter::EscapeEventFilter(
+ FrameMaximizeButton* button)
+ : button_(button) {
+ Shell::GetInstance()->AddPreTargetHandler(this);
+}
+
+FrameMaximizeButton::EscapeEventFilter::~EscapeEventFilter() {
+ Shell::GetInstance()->RemovePreTargetHandler(this);
+}
+
+void FrameMaximizeButton::EscapeEventFilter::OnKeyEvent(
+ ui::KeyEvent* event) {
+ if (event->type() == ui::ET_KEY_PRESSED &&
+ event->key_code() == ui::VKEY_ESCAPE) {
+ button_->Cancel(false);
+ }
+}
+
+// FrameMaximizeButton ---------------------------------------------------------
+
+FrameMaximizeButton::FrameMaximizeButton(views::ButtonListener* listener,
+ views::NonClientFrameView* frame)
+ : ImageButton(listener),
+ frame_(frame),
+ is_snap_enabled_(false),
+ exceeded_drag_threshold_(false),
+ widget_(NULL),
+ press_is_gesture_(false),
+ snap_type_(SNAP_NONE),
+ bubble_appearance_delay_ms_(kBubbleAppearanceDelayMS) {
+ // TODO(sky): nuke this. It's temporary while we don't have good images.
+ SetImageAlignment(ALIGN_LEFT, ALIGN_BOTTOM);
+
+ if (ash::Shell::IsForcedMaximizeMode())
+ views::View::SetVisible(false);
+}
+
+FrameMaximizeButton::~FrameMaximizeButton() {
+ // Before the window gets destroyed, the maximizer dialog needs to be shut
+ // down since it would otherwise call into a deleted object.
+ maximizer_.reset();
+ if (widget_)
+ OnWindowDestroying(widget_->GetNativeWindow());
+}
+
+void FrameMaximizeButton::SnapButtonHovered(SnapType type) {
+ // Make sure to only show hover operations when no button is pressed and
+ // a similar snap operation in progress does not get re-applied.
+ if (is_snap_enabled_ || (type == snap_type_ && snap_sizer_))
+ return;
+ // Prime the mouse location with the center of the (local) button.
+ press_location_ = gfx::Point(width() / 2, height() / 2);
+ // Then get an adjusted mouse position to initiate the effect.
+ gfx::Point location = press_location_;
+ switch (type) {
+ case SNAP_LEFT:
+ location.set_x(location.x() - width());
+ break;
+ case SNAP_RIGHT:
+ location.set_x(location.x() + width());
+ break;
+ case SNAP_MINIMIZE:
+ location.set_y(location.y() + height());
+ break;
+ case SNAP_RESTORE:
+ // Simulate a mouse button move over the according button.
+ if (GetMaximizeBubbleFrameState() == FRAME_STATE_SNAP_LEFT)
+ location.set_x(location.x() - width());
+ else if (GetMaximizeBubbleFrameState() == FRAME_STATE_SNAP_RIGHT)
+ location.set_x(location.x() + width());
+ break;
+ case SNAP_MAXIMIZE:
+ break;
+ case SNAP_NONE:
+ Cancel(true);
+ return;
+ default:
+ // We should not come here.
+ NOTREACHED();
+ }
+ // Note: There is no hover with touch - we can therefore pass false for touch
+ // operations.
+ UpdateSnap(location, true, false);
+}
+
+void FrameMaximizeButton::ExecuteSnapAndCloseMenu(SnapType snap_type) {
+ // We can come here with no snap type set in case that the mouse opened the
+ // maximize button and a touch event "touched" a button.
+ if (snap_type_ == SNAP_NONE)
+ SnapButtonHovered(snap_type);
+
+ Cancel(true);
+ // Tell our menu to close.
+ maximizer_.reset();
+ snap_type_ = snap_type;
+ // Since Snap might destroy |this|, but the snap_sizer needs to be destroyed,
+ // The ownership of the snap_sizer is taken now.
+ scoped_ptr<SnapSizer> snap_sizer(snap_sizer_.release());
+ Snap(*snap_sizer.get());
+}
+
+void FrameMaximizeButton::DestroyMaximizeMenu() {
+ Cancel(false);
+}
+
+void FrameMaximizeButton::OnWindowBoundsChanged(
+ aura::Window* window,
+ const gfx::Rect& old_bounds,
+ const gfx::Rect& new_bounds) {
+ Cancel(false);
+}
+
+void FrameMaximizeButton::OnWindowPropertyChanged(aura::Window* window,
+ const void* key,
+ intptr_t old) {
+ // Changing the window position is managed status should not Cancel.
+ // Note that this case might happen when a non user managed window
+ // transitions from maximized to L/R maximized.
+ if (key != ash::internal::kWindowPositionManagedKey)
+ Cancel(false);
+}
+
+void FrameMaximizeButton::OnWindowDestroying(aura::Window* window) {
+ maximizer_.reset();
+ if (widget_) {
+ CHECK_EQ(widget_->GetNativeWindow(), window);
+ widget_->GetNativeWindow()->RemoveObserver(this);
+ widget_->RemoveObserver(this);
+ widget_ = NULL;
+ }
+}
+
+void FrameMaximizeButton::OnWidgetActivationChanged(views::Widget* widget,
+ bool active) {
+ // Upon losing focus, the control bubble should hide.
+ if (!active && maximizer_)
+ maximizer_.reset();
+}
+
+bool FrameMaximizeButton::OnMousePressed(const ui::MouseEvent& event) {
+ // If we are already in a mouse click / drag operation, a second button down
+ // call will cancel (this addresses crbug.com/143755).
+ if (is_snap_enabled_) {
+ Cancel(false);
+ } else {
+ is_snap_enabled_ = event.IsOnlyLeftMouseButton();
+ if (is_snap_enabled_)
+ ProcessStartEvent(event);
+ }
+ ImageButton::OnMousePressed(event);
+ return true;
+}
+
+void FrameMaximizeButton::OnMouseEntered(const ui::MouseEvent& event) {
+ ImageButton::OnMouseEntered(event);
+ if (!maximizer_) {
+ DCHECK(GetWidget());
+ if (!widget_) {
+ widget_ = frame_->GetWidget();
+ widget_->GetNativeWindow()->AddObserver(this);
+ widget_->AddObserver(this);
+ }
+ maximizer_.reset(new MaximizeBubbleController(
+ this,
+ GetMaximizeBubbleFrameState(),
+ bubble_appearance_delay_ms_));
+ }
+}
+
+void FrameMaximizeButton::OnMouseExited(const ui::MouseEvent& event) {
+ ImageButton::OnMouseExited(event);
+ // Remove the bubble menu when the button is not pressed and the mouse is not
+ // within the bubble.
+ if (!is_snap_enabled_ && maximizer_) {
+ if (maximizer_->GetBubbleWindow()) {
+ gfx::Point screen_location = Shell::GetScreen()->GetCursorScreenPoint();
+ if (!maximizer_->GetBubbleWindow()->GetBoundsInScreen().Contains(
+ screen_location)) {
+ maximizer_.reset();
+ // Make sure that all remaining snap hover states get removed.
+ SnapButtonHovered(SNAP_NONE);
+ }
+ } else {
+ // The maximize dialog does not show up immediately after creating the
+ // |mazimizer_|. Destroy the dialog therefore before it shows up.
+ maximizer_.reset();
+ }
+ }
+}
+
+bool FrameMaximizeButton::OnMouseDragged(const ui::MouseEvent& event) {
+ if (is_snap_enabled_)
+ ProcessUpdateEvent(event);
+ return ImageButton::OnMouseDragged(event);
+}
+
+void FrameMaximizeButton::OnMouseReleased(const ui::MouseEvent& event) {
+ maximizer_.reset();
+ bool snap_was_enabled = is_snap_enabled_;
+ if (!ProcessEndEvent(event) && snap_was_enabled)
+ ImageButton::OnMouseReleased(event);
+ // At this point |this| might be already destroyed.
+}
+
+void FrameMaximizeButton::OnMouseCaptureLost() {
+ Cancel(false);
+ ImageButton::OnMouseCaptureLost();
+}
+
+void FrameMaximizeButton::OnGestureEvent(ui::GestureEvent* event) {
+ if (event->type() == ui::ET_GESTURE_TAP_DOWN) {
+ is_snap_enabled_ = true;
+ ProcessStartEvent(*event);
+ event->SetHandled();
+ return;
+ }
+
+ if (event->type() == ui::ET_GESTURE_TAP ||
+ (event->type() == ui::ET_GESTURE_SCROLL_END && is_snap_enabled_) ||
+ event->type() == ui::ET_SCROLL_FLING_START) {
+ // The position of the event may have changed from the previous event (both
+ // for TAP and SCROLL_END). So it is necessary to update the snap-state for
+ // the current event.
+ ProcessUpdateEvent(*event);
+ if (event->type() == ui::ET_GESTURE_TAP) {
+ snap_type_ = SnapTypeForLocation(event->location());
+ TouchUMA::GetInstance()->RecordGestureAction(
+ TouchUMA::GESTURE_FRAMEMAXIMIZE_TAP);
+ }
+ ProcessEndEvent(*event);
+ event->SetHandled();
+ return;
+ }
+
+ if (is_snap_enabled_) {
+ if (event->type() == ui::ET_GESTURE_END &&
+ event->details().touch_points() == 1) {
+ // The position of the event may have changed from the previous event. So
+ // it is necessary to update the snap-state for the current event.
+ ProcessUpdateEvent(*event);
+ snap_type_ = SnapTypeForLocation(event->location());
+ ProcessEndEvent(*event);
+ event->SetHandled();
+ return;
+ }
+
+ if (event->type() == ui::ET_GESTURE_SCROLL_UPDATE ||
+ event->type() == ui::ET_GESTURE_SCROLL_BEGIN) {
+ ProcessUpdateEvent(*event);
+ event->SetHandled();
+ return;
+ }
+ }
+
+ ImageButton::OnGestureEvent(event);
+}
+
+void FrameMaximizeButton::SetVisible(bool visible) {
+ // In the enforced maximized mode we do not allow to be made visible.
+ if (ash::Shell::IsForcedMaximizeMode())
+ return;
+
+ views::View::SetVisible(visible);
+}
+
+void FrameMaximizeButton::ProcessStartEvent(const ui::LocatedEvent& event) {
+ DCHECK(is_snap_enabled_);
+ // Prepare the help menu.
+ if (!maximizer_) {
+ maximizer_.reset(new MaximizeBubbleController(
+ this,
+ GetMaximizeBubbleFrameState(),
+ bubble_appearance_delay_ms_));
+ } else {
+ // If the menu did not show up yet, we delay it even a bit more.
+ maximizer_->DelayCreation();
+ }
+ snap_sizer_.reset(NULL);
+ InstallEventFilter();
+ snap_type_ = SNAP_NONE;
+ press_location_ = event.location();
+ press_is_gesture_ = event.IsGestureEvent();
+ exceeded_drag_threshold_ = false;
+ update_timer_.Start(
+ FROM_HERE,
+ base::TimeDelta::FromMilliseconds(kUpdateDelayMS),
+ this,
+ &FrameMaximizeButton::UpdateSnapFromEventLocation);
+}
+
+void FrameMaximizeButton::ProcessUpdateEvent(const ui::LocatedEvent& event) {
+ DCHECK(is_snap_enabled_);
+ if (!exceeded_drag_threshold_) {
+ exceeded_drag_threshold_ = views::View::ExceededDragThreshold(
+ event.location() - press_location_);
+ }
+ if (exceeded_drag_threshold_)
+ UpdateSnap(event.location(), false, event.IsGestureEvent());
+}
+
+bool FrameMaximizeButton::ProcessEndEvent(const ui::LocatedEvent& event) {
+ update_timer_.Stop();
+ UninstallEventFilter();
+ bool should_snap = is_snap_enabled_;
+ is_snap_enabled_ = false;
+
+ // Remove our help bubble.
+ maximizer_.reset();
+
+ if (!should_snap || snap_type_ == SNAP_NONE)
+ return false;
+
+ SetState(views::CustomButton::STATE_NORMAL);
+ // SetState will not call SchedulePaint() if state was already set to
+ // STATE_NORMAL during a drag.
+ SchedulePaint();
+ phantom_window_.reset();
+ // Since Snap might destroy |this|, but the snap_sizer needs to be destroyed,
+ // The ownership of the snap_sizer is taken now.
+ scoped_ptr<SnapSizer> snap_sizer(snap_sizer_.release());
+ Snap(*snap_sizer.get());
+ return true;
+}
+
+void FrameMaximizeButton::Cancel(bool keep_menu_open) {
+ if (!keep_menu_open) {
+ maximizer_.reset();
+ UninstallEventFilter();
+ is_snap_enabled_ = false;
+ snap_sizer_.reset();
+ }
+ phantom_window_.reset();
+ snap_type_ = SNAP_NONE;
+ update_timer_.Stop();
+ SchedulePaint();
+}
+
+void FrameMaximizeButton::InstallEventFilter() {
+ if (escape_event_filter_)
+ return;
+
+ escape_event_filter_.reset(new EscapeEventFilter(this));
+}
+
+void FrameMaximizeButton::UninstallEventFilter() {
+ escape_event_filter_.reset(NULL);
+}
+
+void FrameMaximizeButton::UpdateSnapFromEventLocation() {
+ // If the drag threshold has been exceeded the snap location is up to date.
+ if (exceeded_drag_threshold_)
+ return;
+ exceeded_drag_threshold_ = true;
+ UpdateSnap(press_location_, false, press_is_gesture_);
+}
+
+void FrameMaximizeButton::UpdateSnap(const gfx::Point& location,
+ bool select_default,
+ bool is_touch) {
+ SnapType type = SnapTypeForLocation(location);
+ if (type == snap_type_) {
+ if (snap_sizer_) {
+ snap_sizer_->Update(LocationForSnapSizer(location));
+ phantom_window_->Show(ScreenAsh::ConvertRectToScreen(
+ frame_->GetWidget()->GetNativeView()->parent(),
+ snap_sizer_->target_bounds()));
+ }
+ return;
+ }
+
+ snap_type_ = type;
+ snap_sizer_.reset();
+ SchedulePaint();
+
+ if (snap_type_ == SNAP_NONE) {
+ phantom_window_.reset();
+ return;
+ }
+
+ if (snap_type_ == SNAP_LEFT || snap_type_ == SNAP_RIGHT) {
+ SnapSizer::Edge snap_edge = snap_type_ == SNAP_LEFT ?
+ SnapSizer::LEFT_EDGE : SnapSizer::RIGHT_EDGE;
+ SnapSizer::InputType input_type =
+ is_touch ? SnapSizer::TOUCH_MAXIMIZE_BUTTON_INPUT :
+ SnapSizer::OTHER_INPUT;
+ snap_sizer_.reset(new SnapSizer(frame_->GetWidget()->GetNativeWindow(),
+ LocationForSnapSizer(location),
+ snap_edge,
+ input_type));
+ if (select_default)
+ snap_sizer_->SelectDefaultSizeAndDisableResize();
+ }
+ if (!phantom_window_) {
+ phantom_window_.reset(new internal::PhantomWindowController(
+ frame_->GetWidget()->GetNativeWindow()));
+ }
+ if (maximizer_) {
+ phantom_window_->set_phantom_below_window(maximizer_->GetBubbleWindow());
+ maximizer_->SetSnapType(snap_type_);
+ }
+ phantom_window_->Show(
+ ScreenBoundsForType(snap_type_, *snap_sizer_.get()));
+}
+
+SnapType FrameMaximizeButton::SnapTypeForLocation(
+ const gfx::Point& location) const {
+ MaximizeBubbleFrameState maximize_type = GetMaximizeBubbleFrameState();
+ gfx::Vector2d delta(location - press_location_);
+ if (!views::View::ExceededDragThreshold(delta))
+ return maximize_type != FRAME_STATE_FULL ? SNAP_MAXIMIZE : SNAP_RESTORE;
+ if (delta.x() < 0 && delta.y() > delta.x() && delta.y() < -delta.x())
+ return maximize_type == FRAME_STATE_SNAP_LEFT ? SNAP_RESTORE : SNAP_LEFT;
+ if (delta.x() > 0 && delta.y() > -delta.x() && delta.y() < delta.x())
+ return maximize_type == FRAME_STATE_SNAP_RIGHT ? SNAP_RESTORE : SNAP_RIGHT;
+ if (delta.y() > 0)
+ return SNAP_MINIMIZE;
+ return maximize_type != FRAME_STATE_FULL ? SNAP_MAXIMIZE : SNAP_RESTORE;
+}
+
+gfx::Rect FrameMaximizeButton::ScreenBoundsForType(
+ SnapType type,
+ const SnapSizer& snap_sizer) const {
+ aura::Window* window = frame_->GetWidget()->GetNativeWindow();
+ switch (type) {
+ case SNAP_LEFT:
+ case SNAP_RIGHT:
+ return ScreenAsh::ConvertRectToScreen(
+ frame_->GetWidget()->GetNativeView()->parent(),
+ snap_sizer.target_bounds());
+ case SNAP_MAXIMIZE:
+ return ScreenAsh::ConvertRectToScreen(
+ window->parent(),
+ ScreenAsh::GetMaximizedWindowBoundsInParent(window));
+ case SNAP_MINIMIZE: {
+ gfx::Rect rect = GetMinimizeAnimationTargetBoundsInScreen(window);
+ if (!rect.IsEmpty()) {
+ // PhantomWindowController insets slightly, outset it so the phantom
+ // doesn't appear inset.
+ rect.Inset(-8, -8);
+ }
+ return rect;
+ }
+ case SNAP_RESTORE: {
+ const gfx::Rect* restore = GetRestoreBoundsInScreen(window);
+ return restore ?
+ *restore : frame_->GetWidget()->GetWindowBoundsInScreen();
+ }
+ case SNAP_NONE:
+ NOTREACHED();
+ }
+ return gfx::Rect();
+}
+
+gfx::Point FrameMaximizeButton::LocationForSnapSizer(
+ const gfx::Point& location) const {
+ gfx::Point result(location);
+ views::View::ConvertPointToScreen(this, &result);
+ return result;
+}
+
+void FrameMaximizeButton::Snap(const SnapSizer& snap_sizer) {
+ ash::Shell* shell = ash::Shell::GetInstance();
+ views::Widget* widget = frame_->GetWidget();
+ switch (snap_type_) {
+ case SNAP_LEFT:
+ case SNAP_RIGHT: {
+ shell->delegate()->RecordUserMetricsAction(
+ snap_type_ == SNAP_LEFT ?
+ ash::UMA_WINDOW_MAXIMIZE_BUTTON_MAXIMIZE_LEFT :
+ ash::UMA_WINDOW_MAXIMIZE_BUTTON_MAXIMIZE_RIGHT);
+ // Get the bounds in screen coordinates for restore purposes.
+ gfx::Rect restore = widget->GetWindowBoundsInScreen();
+ if (widget->IsMaximized() || widget->IsFullscreen()) {
+ aura::Window* window = widget->GetNativeWindow();
+ // In case of maximized we have a restore boundary.
+ DCHECK(ash::GetRestoreBoundsInScreen(window));
+ // If it was maximized we need to recover the old restore set.
+ restore = *ash::GetRestoreBoundsInScreen(window);
+
+ // The auto position manager will kick in when this is the only window.
+ // To avoid interference with it we tell it temporarily to not change
+ // the coordinates of this window.
+ bool is_managed = ash::wm::IsWindowPositionManaged(window);
+ if (is_managed)
+ ash::wm::SetWindowPositionManaged(window, false);
+
+ // Set the restore size we want to restore to.
+ ash::SetRestoreBoundsInScreen(window,
+ ScreenBoundsForType(snap_type_,
+ snap_sizer));
+ widget->Restore();
+
+ // After the window is where we want it to be we allow the window to be
+ // auto managed again.
+ if (is_managed)
+ ash::wm::SetWindowPositionManaged(window, true);
+ } else {
+ // Others might also have set up a restore rectangle already. If so,
+ // we should not overwrite the restore rectangle.
+ bool restore_set =
+ GetRestoreBoundsInScreen(widget->GetNativeWindow()) != NULL;
+ widget->SetBounds(ScreenBoundsForType(snap_type_, snap_sizer));
+ if (restore_set)
+ break;
+ }
+ // Remember the widow's bounds for restoration.
+ ash::SetRestoreBoundsInScreen(widget->GetNativeWindow(), restore);
+ break;
+ }
+ case SNAP_MAXIMIZE:
+ widget->Maximize();
+ shell->delegate()->RecordUserMetricsAction(
+ ash::UMA_WINDOW_MAXIMIZE_BUTTON_MAXIMIZE);
+ break;
+ case SNAP_MINIMIZE:
+ widget->Minimize();
+ shell->delegate()->RecordUserMetricsAction(
+ ash::UMA_WINDOW_MAXIMIZE_BUTTON_MINIMIZE);
+ break;
+ case SNAP_RESTORE:
+ widget->Restore();
+ shell->delegate()->RecordUserMetricsAction(
+ ash::UMA_WINDOW_MAXIMIZE_BUTTON_RESTORE);
+ break;
+ case SNAP_NONE:
+ NOTREACHED();
+ }
+}
+
+MaximizeBubbleFrameState
+FrameMaximizeButton::GetMaximizeBubbleFrameState() const {
+ // When there are no restore bounds, we are in normal mode.
+ if (!ash::GetRestoreBoundsInScreen(
+ frame_->GetWidget()->GetNativeWindow()))
+ return FRAME_STATE_NONE;
+ // The normal maximized test can be used.
+ if (frame_->GetWidget()->IsMaximized())
+ return FRAME_STATE_FULL;
+ // For Left/right maximize we need to check the dimensions.
+ gfx::Rect bounds = frame_->GetWidget()->GetWindowBoundsInScreen();
+ gfx::Rect screen = Shell::GetScreen()->GetDisplayNearestWindow(
+ frame_->GetWidget()->GetNativeView()).work_area();
+ if (bounds.width() < (screen.width() * kMinSnapSizePercent) / 100)
+ return FRAME_STATE_NONE;
+ // We might still have a horizontally filled window at this point which we
+ // treat as no special state.
+ if (bounds.y() != screen.y() || bounds.height() != screen.height())
+ return FRAME_STATE_NONE;
+
+ // We have to be in a maximize mode at this point.
+ if (bounds.x() == screen.x())
+ return FRAME_STATE_SNAP_LEFT;
+ if (bounds.right() == screen.right())
+ return FRAME_STATE_SNAP_RIGHT;
+ // If we come here, it is likely caused by the fact that the
+ // "VerticalResizeDoubleClick" stored a restore rectangle. In that case
+ // we allow all maximize operations (and keep the restore rectangle).
+ return FRAME_STATE_NONE;
+}
+
+} // namespace ash
diff --git a/chromium/ash/wm/workspace/frame_maximize_button.h b/chromium/ash/wm/workspace/frame_maximize_button.h
new file mode 100644
index 00000000000..f01df6c267a
--- /dev/null
+++ b/chromium/ash/wm/workspace/frame_maximize_button.h
@@ -0,0 +1,183 @@
+// Copyright (c) 2012 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 ASH_WM_WORKSPACE_FRAME_MAXIMIZE_BUTTON_H_
+#define ASH_WM_WORKSPACE_FRAME_MAXIMIZE_BUTTON_H_
+
+#include "ash/ash_export.h"
+#include "ash/wm/workspace/maximize_bubble_frame_state.h"
+#include "ash/wm/workspace/snap_types.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/timer/timer.h"
+#include "ui/aura/window_observer.h"
+#include "ui/views/controls/button/image_button.h"
+#include "ui/views/widget/widget_observer.h"
+
+namespace views {
+class NonClientFrameView;
+}
+
+namespace ash {
+
+namespace internal {
+class PhantomWindowController;
+class SnapSizer;
+}
+
+class MaximizeBubbleController;
+
+// Button used for the maximize control on the frame. Handles snapping logic.
+class ASH_EXPORT FrameMaximizeButton : public views::ImageButton,
+ public views::WidgetObserver,
+ public aura::WindowObserver {
+ public:
+ FrameMaximizeButton(views::ButtonListener* listener,
+ views::NonClientFrameView* frame);
+ virtual ~FrameMaximizeButton();
+
+ // Updates |snap_type_| based on a a given snap type. This is used by
+ // external hover events from the button menu.
+ void SnapButtonHovered(SnapType type);
+
+ // The user clicked the |type| button and the action needs to be performed,
+ // which will at the same time close the window.
+ void ExecuteSnapAndCloseMenu(SnapType type);
+
+ // Remove the maximize menu from the screen (and destroy it).
+ void DestroyMaximizeMenu();
+
+ // Returns true when the user clicks and drags the button.
+ bool is_snap_enabled() const { return is_snap_enabled_; }
+
+ // WindowObserver overrides:
+ virtual void OnWindowBoundsChanged(aura::Window* window,
+ const gfx::Rect& old_bounds,
+ const gfx::Rect& new_bounds) OVERRIDE;
+ virtual void OnWindowPropertyChanged(aura::Window* window,
+ const void* key,
+ intptr_t old) OVERRIDE;
+ virtual void OnWindowDestroying(aura::Window* window) OVERRIDE;
+
+ // WidgetObserver overrides:
+ virtual void OnWidgetActivationChanged(views::Widget* widget,
+ bool active) OVERRIDE;
+
+ // ImageButton overrides:
+ virtual bool OnMousePressed(const ui::MouseEvent& event) OVERRIDE;
+ virtual void OnMouseEntered(const ui::MouseEvent& event) OVERRIDE;
+ virtual void OnMouseExited(const ui::MouseEvent& event) OVERRIDE;
+ virtual bool OnMouseDragged(const ui::MouseEvent& event) OVERRIDE;
+ virtual void OnMouseReleased(const ui::MouseEvent& event) OVERRIDE;
+ virtual void OnMouseCaptureLost() OVERRIDE;
+
+ // ui::EventHandler overrides:
+ virtual void OnGestureEvent(ui::GestureEvent* event) OVERRIDE;
+
+ // views::View overwrite:
+ virtual void SetVisible(bool visible) OVERRIDE;
+
+ // Unit test overwrite: Change the UI delay used for the bubble show up.
+ void set_bubble_appearance_delay_ms(int bubble_appearance_delay_ms) {
+ bubble_appearance_delay_ms_ = bubble_appearance_delay_ms;
+ }
+
+ // Unit test accessor for the maximize bubble.
+ MaximizeBubbleController* maximizer() { return maximizer_.get(); }
+
+ // Unit test to see if phantom window is open.
+ bool phantom_window_open() { return phantom_window_.get() != NULL; }
+
+ private:
+ class EscapeEventFilter;
+
+ // Initializes the snap-gesture based on the event. This should only be called
+ // when the event is confirmed to have started a snap gesture.
+ void ProcessStartEvent(const ui::LocatedEvent& event);
+
+ // Updates the snap-state based on the current event. This should only be
+ // called after the snap gesture has already started.
+ void ProcessUpdateEvent(const ui::LocatedEvent& event);
+
+ // Returns true if the window was snapped. Returns false otherwise.
+ bool ProcessEndEvent(const ui::LocatedEvent& event);
+
+ // Cancels snap behavior. If |keep_menu_open| is set, a possibly opened
+ // bubble help will remain open.
+ void Cancel(bool keep_menu_open);
+
+ // Installs/uninstalls an EventFilter to track when escape is pressed.
+ void InstallEventFilter();
+ void UninstallEventFilter();
+
+ // Updates the snap position from the event location. This is invoked by
+ // |update_timer_|.
+ void UpdateSnapFromEventLocation();
+
+ // Updates |snap_type_| based on a mouse drag. If |select_default| is set,
+ // the single button click default setting of the snap sizer should be used.
+ // Set |is_touch| to true to make touch snapping at the corners possible.
+ void UpdateSnap(const gfx::Point& location,
+ bool select_default,
+ bool is_touch);
+
+ // Returns the type of snap based on the specified location.
+ SnapType SnapTypeForLocation(const gfx::Point& location) const;
+
+ // Returns the bounds of the resulting window for the specified type.
+ gfx::Rect ScreenBoundsForType(SnapType type,
+ const internal::SnapSizer& snap_sizer) const;
+
+ // Converts location to screen coordinates and returns it. These are the
+ // coordinates used by the SnapSizer.
+ gfx::Point LocationForSnapSizer(const gfx::Point& location) const;
+
+ // Snaps the window to the current snap position.
+ void Snap(const internal::SnapSizer& snap_sizer);
+
+ // Determine the maximize type of this window.
+ MaximizeBubbleFrameState GetMaximizeBubbleFrameState() const;
+
+ // Frame that the maximize button acts on.
+ views::NonClientFrameView* frame_;
+
+ // Renders the snap position.
+ scoped_ptr<internal::PhantomWindowController> phantom_window_;
+
+ // Is snapping enabled? Set on press so that in drag we know whether we
+ // should show the snap locations.
+ bool is_snap_enabled_;
+
+ // Did the user drag far enough to trigger snapping?
+ bool exceeded_drag_threshold_;
+
+ // Remember the widget on which we have put some an observers,
+ // so that we can remove it upon destruction.
+ views::Widget* widget_;
+
+ // Location of the press.
+ gfx::Point press_location_;
+
+ // True if the press was triggered by a gesture/touch.
+ bool press_is_gesture_;
+
+ // Current snap type.
+ SnapType snap_type_;
+
+ scoped_ptr<internal::SnapSizer> snap_sizer_;
+
+ scoped_ptr<EscapeEventFilter> escape_event_filter_;
+
+ base::OneShotTimer<FrameMaximizeButton> update_timer_;
+
+ scoped_ptr<MaximizeBubbleController> maximizer_;
+
+ // The delay of the bubble appearance.
+ int bubble_appearance_delay_ms_;
+
+ DISALLOW_COPY_AND_ASSIGN(FrameMaximizeButton);
+};
+
+} // namespace ash
+
+#endif // ASH_WM_WORKSPACE_FRAME_MAXIMIZE_BUTTON_H_
diff --git a/chromium/ash/wm/workspace/magnetism_matcher.cc b/chromium/ash/wm/workspace/magnetism_matcher.cc
new file mode 100644
index 00000000000..e7d674b850c
--- /dev/null
+++ b/chromium/ash/wm/workspace/magnetism_matcher.cc
@@ -0,0 +1,194 @@
+// Copyright (c) 2012 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.
+
+#include "ash/wm/workspace/magnetism_matcher.h"
+
+#include <algorithm>
+#include <cmath>
+
+namespace ash {
+namespace internal {
+
+namespace {
+
+// Returns true if |a| is close enough to |b| that the two edges snap.
+bool IsCloseEnough(int a, int b) {
+ return abs(a - b) <= MagnetismMatcher::kMagneticDistance;
+}
+
+// Returns true if the specified SecondaryMagnetismEdge can be matched with a
+// primary edge of |primary|. |edges| is a bitmask of the allowed
+// MagnetismEdges.
+bool CanMatchSecondaryEdge(MagnetismEdge primary,
+ SecondaryMagnetismEdge secondary,
+ uint32 edges) {
+ // Convert |secondary| to a MagnetismEdge so we can compare it to |edges|.
+ MagnetismEdge secondary_as_magnetism_edge = MAGNETISM_EDGE_TOP;
+ switch (primary) {
+ case MAGNETISM_EDGE_TOP:
+ case MAGNETISM_EDGE_BOTTOM:
+ if (secondary == SECONDARY_MAGNETISM_EDGE_LEADING)
+ secondary_as_magnetism_edge = MAGNETISM_EDGE_LEFT;
+ else if (secondary == SECONDARY_MAGNETISM_EDGE_TRAILING)
+ secondary_as_magnetism_edge = MAGNETISM_EDGE_RIGHT;
+ else
+ NOTREACHED();
+ break;
+ case MAGNETISM_EDGE_LEFT:
+ case MAGNETISM_EDGE_RIGHT:
+ if (secondary == SECONDARY_MAGNETISM_EDGE_LEADING)
+ secondary_as_magnetism_edge = MAGNETISM_EDGE_TOP;
+ else if (secondary == SECONDARY_MAGNETISM_EDGE_TRAILING)
+ secondary_as_magnetism_edge = MAGNETISM_EDGE_BOTTOM;
+ else
+ NOTREACHED();
+ break;
+ }
+ return (edges & secondary_as_magnetism_edge) != 0;
+}
+
+} // namespace
+
+// MagnetismEdgeMatcher --------------------------------------------------------
+
+MagnetismEdgeMatcher::MagnetismEdgeMatcher(const gfx::Rect& bounds,
+ MagnetismEdge edge)
+ : bounds_(bounds),
+ edge_(edge) {
+ ranges_.push_back(GetSecondaryRange(bounds_));
+}
+
+MagnetismEdgeMatcher::~MagnetismEdgeMatcher() {
+}
+
+bool MagnetismEdgeMatcher::ShouldAttach(const gfx::Rect& bounds) {
+ if (is_edge_obscured())
+ return false;
+
+ if (IsCloseEnough(GetPrimaryCoordinate(bounds_, edge_),
+ GetPrimaryCoordinate(bounds, FlipEdge(edge_)))) {
+ const Range range(GetSecondaryRange(bounds));
+ Ranges::const_iterator i =
+ std::lower_bound(ranges_.begin(), ranges_.end(), range);
+ // Close enough, but only attach if some portion of the edge is visible.
+ if ((i != ranges_.begin() && RangesIntersect(*(i - 1), range)) ||
+ (i != ranges_.end() && RangesIntersect(*i, range))) {
+ return true;
+ }
+ }
+ // NOTE: this checks against the current bounds, we may want to allow some
+ // flexibility here.
+ const Range primary_range(GetPrimaryRange(bounds));
+ if (primary_range.first <= GetPrimaryCoordinate(bounds_, edge_) &&
+ primary_range.second >= GetPrimaryCoordinate(bounds_, edge_)) {
+ UpdateRanges(GetSecondaryRange(bounds));
+ }
+ return false;
+}
+
+void MagnetismEdgeMatcher::UpdateRanges(const Range& range) {
+ Ranges::const_iterator it =
+ std::lower_bound(ranges_.begin(), ranges_.end(), range);
+ if (it != ranges_.begin() && RangesIntersect(*(it - 1), range))
+ --it;
+ if (it == ranges_.end())
+ return;
+
+ for (size_t i = it - ranges_.begin();
+ i < ranges_.size() && RangesIntersect(ranges_[i], range); ) {
+ if (range.first <= ranges_[i].first &&
+ range.second >= ranges_[i].second) {
+ ranges_.erase(ranges_.begin() + i);
+ } else if (range.first < ranges_[i].first) {
+ DCHECK_GT(range.second, ranges_[i].first);
+ ranges_[i] = Range(range.second, ranges_[i].second);
+ ++i;
+ } else {
+ Range existing(ranges_[i]);
+ ranges_[i].second = range.first;
+ ++i;
+ if (existing.second > range.second) {
+ ranges_.insert(ranges_.begin() + i,
+ Range(range.second, existing.second));
+ ++i;
+ }
+ }
+ }
+}
+
+// MagnetismMatcher ------------------------------------------------------------
+
+// static
+const int MagnetismMatcher::kMagneticDistance = 8;
+
+MagnetismMatcher::MagnetismMatcher(const gfx::Rect& bounds, uint32 edges)
+ : edges_(edges) {
+ if (edges & MAGNETISM_EDGE_TOP)
+ matchers_.push_back(new MagnetismEdgeMatcher(bounds, MAGNETISM_EDGE_TOP));
+ if (edges & MAGNETISM_EDGE_LEFT)
+ matchers_.push_back(new MagnetismEdgeMatcher(bounds, MAGNETISM_EDGE_LEFT));
+ if (edges & MAGNETISM_EDGE_BOTTOM) {
+ matchers_.push_back(new MagnetismEdgeMatcher(bounds,
+ MAGNETISM_EDGE_BOTTOM));
+ }
+ if (edges & MAGNETISM_EDGE_RIGHT)
+ matchers_.push_back(new MagnetismEdgeMatcher(bounds, MAGNETISM_EDGE_RIGHT));
+}
+
+MagnetismMatcher::~MagnetismMatcher() {
+}
+
+bool MagnetismMatcher::ShouldAttach(const gfx::Rect& bounds,
+ MatchedEdge* edge) {
+ for (size_t i = 0; i < matchers_.size(); ++i) {
+ if (matchers_[i]->ShouldAttach(bounds)) {
+ edge->primary_edge = matchers_[i]->edge();
+ AttachToSecondaryEdge(bounds, edge->primary_edge,
+ &(edge->secondary_edge));
+ return true;
+ }
+ }
+ return false;
+}
+
+bool MagnetismMatcher::AreEdgesObscured() const {
+ for (size_t i = 0; i < matchers_.size(); ++i) {
+ if (!matchers_[i]->is_edge_obscured())
+ return false;
+ }
+ return true;
+}
+
+void MagnetismMatcher::AttachToSecondaryEdge(
+ const gfx::Rect& bounds,
+ MagnetismEdge edge,
+ SecondaryMagnetismEdge* secondary_edge) const {
+ const gfx::Rect& src_bounds(matchers_[0]->bounds());
+ if (edge == MAGNETISM_EDGE_LEFT || edge == MAGNETISM_EDGE_RIGHT) {
+ if (CanMatchSecondaryEdge(edge, SECONDARY_MAGNETISM_EDGE_LEADING, edges_) &&
+ IsCloseEnough(bounds.y(), src_bounds.y())) {
+ *secondary_edge = SECONDARY_MAGNETISM_EDGE_LEADING;
+ } else if (CanMatchSecondaryEdge(edge, SECONDARY_MAGNETISM_EDGE_TRAILING,
+ edges_) &&
+ IsCloseEnough(bounds.bottom(), src_bounds.bottom())) {
+ *secondary_edge = SECONDARY_MAGNETISM_EDGE_TRAILING;
+ } else {
+ *secondary_edge = SECONDARY_MAGNETISM_EDGE_NONE;
+ }
+ } else {
+ if (CanMatchSecondaryEdge(edge, SECONDARY_MAGNETISM_EDGE_LEADING, edges_) &&
+ IsCloseEnough(bounds.x(), src_bounds.x())) {
+ *secondary_edge = SECONDARY_MAGNETISM_EDGE_LEADING;
+ } else if (CanMatchSecondaryEdge(edge, SECONDARY_MAGNETISM_EDGE_TRAILING,
+ edges_) &&
+ IsCloseEnough(bounds.right(), src_bounds.right())) {
+ *secondary_edge = SECONDARY_MAGNETISM_EDGE_TRAILING;
+ } else {
+ *secondary_edge = SECONDARY_MAGNETISM_EDGE_NONE;
+ }
+ }
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/wm/workspace/magnetism_matcher.h b/chromium/ash/wm/workspace/magnetism_matcher.h
new file mode 100644
index 00000000000..ff1f52810e9
--- /dev/null
+++ b/chromium/ash/wm/workspace/magnetism_matcher.h
@@ -0,0 +1,191 @@
+// Copyright (c) 2012 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 ASH_WM_WORKSPACE_MAGNETISM_MATCHER_H_
+#define ASH_WM_WORKSPACE_MAGNETISM_MATCHER_H_
+
+#include <utility>
+#include <vector>
+
+#include "ash/ash_export.h"
+#include "base/compiler_specific.h"
+#include "base/logging.h"
+#include "base/memory/scoped_vector.h"
+#include "ui/gfx/rect.h"
+
+namespace ash {
+namespace internal {
+
+enum MagnetismEdge {
+ MAGNETISM_EDGE_TOP = 1 << 0,
+ MAGNETISM_EDGE_LEFT = 1 << 1,
+ MAGNETISM_EDGE_BOTTOM = 1 << 2,
+ MAGNETISM_EDGE_RIGHT = 1 << 3,
+};
+
+const uint32 kAllMagnetismEdges =
+ MAGNETISM_EDGE_TOP | MAGNETISM_EDGE_LEFT | MAGNETISM_EDGE_BOTTOM |
+ MAGNETISM_EDGE_RIGHT;
+
+// MagnetismEdgeMatcher is used for matching a particular edge of a window. You
+// shouldn't need to use this directly, instead use MagnetismMatcher which takes
+// care of all edges.
+// MagnetismEdgeMatcher maintains a range of the visible portions of the
+// edge. As ShouldAttach() is invoked the visible range is updated.
+class MagnetismEdgeMatcher {
+ public:
+ MagnetismEdgeMatcher(const gfx::Rect& bounds, MagnetismEdge edge);
+ ~MagnetismEdgeMatcher();
+
+ MagnetismEdge edge() const { return edge_; }
+ const gfx::Rect& bounds() const { return bounds_; }
+
+ // Returns true if the edge is completely obscured. If true ShouldAttach()
+ // will return false.
+ bool is_edge_obscured() const { return ranges_.empty(); }
+
+ // Returns true if should attach to the specified bounds.
+ bool ShouldAttach(const gfx::Rect& bounds);
+
+ private:
+ typedef std::pair<int,int> Range;
+ typedef std::vector<Range> Ranges;
+
+ // Removes |range| from |ranges_|.
+ void UpdateRanges(const Range& range);
+
+ static int GetPrimaryCoordinate(const gfx::Rect& bounds, MagnetismEdge edge) {
+ switch (edge) {
+ case MAGNETISM_EDGE_TOP:
+ return bounds.y();
+ case MAGNETISM_EDGE_LEFT:
+ return bounds.x();
+ case MAGNETISM_EDGE_BOTTOM:
+ return bounds.bottom();
+ case MAGNETISM_EDGE_RIGHT:
+ return bounds.right();
+ }
+ NOTREACHED();
+ return 0;
+ }
+
+ static MagnetismEdge FlipEdge(MagnetismEdge edge) {
+ switch (edge) {
+ case MAGNETISM_EDGE_TOP:
+ return MAGNETISM_EDGE_BOTTOM;
+ case MAGNETISM_EDGE_BOTTOM:
+ return MAGNETISM_EDGE_TOP;
+ case MAGNETISM_EDGE_LEFT:
+ return MAGNETISM_EDGE_RIGHT;
+ case MAGNETISM_EDGE_RIGHT:
+ return MAGNETISM_EDGE_LEFT;
+ }
+ NOTREACHED();
+ return MAGNETISM_EDGE_LEFT;
+ }
+
+ Range GetPrimaryRange(const gfx::Rect& bounds) const {
+ switch (edge_) {
+ case MAGNETISM_EDGE_TOP:
+ case MAGNETISM_EDGE_BOTTOM:
+ return Range(bounds.y(), bounds.bottom());
+ case MAGNETISM_EDGE_LEFT:
+ case MAGNETISM_EDGE_RIGHT:
+ return Range(bounds.x(), bounds.right());
+ }
+ NOTREACHED();
+ return Range();
+ }
+
+ Range GetSecondaryRange(const gfx::Rect& bounds) const {
+ switch (edge_) {
+ case MAGNETISM_EDGE_TOP:
+ case MAGNETISM_EDGE_BOTTOM:
+ return Range(bounds.x(), bounds.right());
+ case MAGNETISM_EDGE_LEFT:
+ case MAGNETISM_EDGE_RIGHT:
+ return Range(bounds.y(), bounds.bottom());
+ }
+ NOTREACHED();
+ return Range();
+ }
+
+ static bool RangesIntersect(const Range& r1, const Range& r2) {
+ return r2.first < r1.second && r2.second > r1.first;
+ }
+
+ // The bounds of window.
+ const gfx::Rect bounds_;
+
+ // The edge this matcher checks.
+ const MagnetismEdge edge_;
+
+ // Visible ranges of the edge. Initialized with GetSecondaryRange() and
+ // updated as ShouldAttach() is invoked. When empty the edge is completely
+ // obscured by other bounds.
+ Ranges ranges_;
+
+ DISALLOW_COPY_AND_ASSIGN(MagnetismEdgeMatcher);
+};
+
+enum SecondaryMagnetismEdge {
+ SECONDARY_MAGNETISM_EDGE_LEADING,
+ SECONDARY_MAGNETISM_EDGE_TRAILING,
+ SECONDARY_MAGNETISM_EDGE_NONE,
+};
+
+// Used to identify a matched edge. |primary_edge| is relative to the source and
+// indicates the edge the two are to share. For example, if |primary_edge| is
+// MAGNETISM_EDGE_RIGHT then the right edge of the source should snap to to the
+// left edge of the target. |secondary_edge| indicates one of the edges along
+// the opposite axis should should also be aligned. For example, if
+// |primary_edge| is MAGNETISM_EDGE_RIGHT and |secondary_edge| is
+// SECONDARY_MAGNETISM_EDGE_LEADING then the source should snap to the left top
+// corner of the target.
+struct MatchedEdge {
+ MagnetismEdge primary_edge;
+ SecondaryMagnetismEdge secondary_edge;
+};
+
+// MagnetismMatcher is used to test if a window should snap to another window.
+// To use MagnetismMatcher do the following:
+// . Create it with the bounds of the window being dragged.
+// . Iterate over the child windows checking if the window being dragged should
+// attach to it using ShouldAttach().
+// . Use AreEdgesObscured() to test if no other windows can match (because all
+// edges are completely obscured).
+class ASH_EXPORT MagnetismMatcher {
+ public:
+ static const int kMagneticDistance;
+
+ // |edges| is a bitmask of MagnetismEdges to match against.
+ MagnetismMatcher(const gfx::Rect& bounds, uint32 edges);
+ ~MagnetismMatcher();
+
+ // Returns true if |bounds| is close enough to the initial bounds that the two
+ // should be attached. If true is returned |edge| is set to indicates how the
+ // two should snap together. See description of MatchedEdge for details.
+ bool ShouldAttach(const gfx::Rect& bounds, MatchedEdge* edge);
+
+ // Returns true if no other matches are possible.
+ bool AreEdgesObscured() const;
+
+ private:
+ // Sets |secondary_edge| based on whether the secondary edges should snap.
+ void AttachToSecondaryEdge(const gfx::Rect& bounds,
+ MagnetismEdge edge,
+ SecondaryMagnetismEdge* secondary_edge) const;
+
+ // The edges to match against.
+ const int32 edges_;
+
+ ScopedVector<MagnetismEdgeMatcher> matchers_;
+
+ DISALLOW_COPY_AND_ASSIGN(MagnetismMatcher);
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_WM_WORKSPACE_MAGNETISM_MATCHER_H_
diff --git a/chromium/ash/wm/workspace/magnetism_matcher_unittest.cc b/chromium/ash/wm/workspace/magnetism_matcher_unittest.cc
new file mode 100644
index 00000000000..054622ff572
--- /dev/null
+++ b/chromium/ash/wm/workspace/magnetism_matcher_unittest.cc
@@ -0,0 +1,171 @@
+// Copyright (c) 2012 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.
+
+#include "ash/wm/workspace/magnetism_matcher.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace ash {
+namespace internal {
+
+// Trivial test case verifying assertions on left edge.
+TEST(MagnetismMatcherTest, TrivialLeft) {
+ const int distance = MagnetismMatcher::kMagneticDistance;
+ const gfx::Rect initial_bounds(20, 10, 50, 60);
+ MagnetismMatcher matcher(initial_bounds, kAllMagnetismEdges);
+ EXPECT_FALSE(matcher.AreEdgesObscured());
+ MatchedEdge edge;
+ EXPECT_FALSE(matcher.ShouldAttach(
+ gfx::Rect(initial_bounds.x() - distance - 10,
+ initial_bounds.y() - distance - 10, 2, 3), &edge));
+ EXPECT_FALSE(matcher.AreEdgesObscured());
+ EXPECT_TRUE(matcher.ShouldAttach(
+ gfx::Rect(initial_bounds.x() - 2, initial_bounds.y(), 1, 1),
+ &edge));
+ EXPECT_EQ(MAGNETISM_EDGE_LEFT, edge.primary_edge);
+ EXPECT_EQ(SECONDARY_MAGNETISM_EDGE_LEADING, edge.secondary_edge);
+
+ EXPECT_TRUE(matcher.ShouldAttach(
+ gfx::Rect(initial_bounds.x() - 2,
+ initial_bounds.y() + distance + 1 , 1, 1),
+ &edge));
+ EXPECT_EQ(MAGNETISM_EDGE_LEFT, edge.primary_edge);
+ EXPECT_EQ(SECONDARY_MAGNETISM_EDGE_NONE, edge.secondary_edge);
+}
+
+// Trivial test case verifying assertions on bottom edge.
+TEST(MagnetismMatcherTest, TrivialBottom) {
+ const int distance = MagnetismMatcher::kMagneticDistance;
+ const gfx::Rect initial_bounds(20, 10, 50, 60);
+ MagnetismMatcher matcher(initial_bounds, kAllMagnetismEdges);
+ EXPECT_FALSE(matcher.AreEdgesObscured());
+ MatchedEdge edge;
+ EXPECT_FALSE(matcher.ShouldAttach(
+ gfx::Rect(initial_bounds.x() - distance - 10,
+ initial_bounds.y() - distance - 10, 2, 3), &edge));
+ EXPECT_FALSE(matcher.AreEdgesObscured());
+ EXPECT_TRUE(matcher.ShouldAttach(
+ gfx::Rect(initial_bounds.x() - 2,
+ initial_bounds.bottom() + 4, 10, 1), &edge));
+ EXPECT_EQ(MAGNETISM_EDGE_BOTTOM, edge.primary_edge);
+ EXPECT_EQ(SECONDARY_MAGNETISM_EDGE_LEADING, edge.secondary_edge);
+
+ EXPECT_TRUE(matcher.ShouldAttach(
+ gfx::Rect(initial_bounds.x() + distance + 1,
+ initial_bounds.bottom() + 4, 10, 1), &edge));
+ EXPECT_EQ(MAGNETISM_EDGE_BOTTOM, edge.primary_edge);
+ EXPECT_EQ(SECONDARY_MAGNETISM_EDGE_NONE, edge.secondary_edge);
+
+ EXPECT_TRUE(matcher.ShouldAttach(
+ gfx::Rect(initial_bounds.right() - 10 - 1,
+ initial_bounds.bottom() + 4, 10, 1), &edge));
+ EXPECT_EQ(MAGNETISM_EDGE_BOTTOM, edge.primary_edge);
+ EXPECT_EQ(SECONDARY_MAGNETISM_EDGE_TRAILING, edge.secondary_edge);
+}
+
+// Verifies we don't match an obscured corner.
+TEST(MagnetismMatcherTest, ObscureLeading) {
+ const int distance = MagnetismMatcher::kMagneticDistance;
+ const gfx::Rect initial_bounds(20, 10, 150, 160);
+ MagnetismMatcher matcher(initial_bounds, kAllMagnetismEdges);
+ MatchedEdge edge;
+ // Overlap with the upper right corner.
+ EXPECT_FALSE(matcher.ShouldAttach(
+ gfx::Rect(initial_bounds.right() - distance * 2,
+ initial_bounds.y() - distance - 2,
+ distance * 3,
+ (distance + 2) * 2), &edge));
+ EXPECT_FALSE(matcher.AreEdgesObscured());
+ // Verify doesn't match the following which is obscured by first.
+ EXPECT_FALSE(matcher.ShouldAttach(
+ gfx::Rect(initial_bounds.right() + 1,
+ initial_bounds.y(),
+ distance,
+ 5), &edge));
+ // Should match the following which extends into non-overlapping region.
+ EXPECT_TRUE(matcher.ShouldAttach(
+ gfx::Rect(initial_bounds.right() + 1,
+ initial_bounds.y() + distance + 1,
+ distance,
+ 15), &edge));
+ EXPECT_EQ(MAGNETISM_EDGE_RIGHT, edge.primary_edge);
+ EXPECT_EQ(SECONDARY_MAGNETISM_EDGE_NONE, edge.secondary_edge);
+}
+
+// Verifies obscuring one side doesn't obscure the other.
+TEST(MagnetismMatcherTest, DontObscureOtherSide) {
+ const int distance = MagnetismMatcher::kMagneticDistance;
+ const gfx::Rect initial_bounds(20, 10, 150, 160);
+ MagnetismMatcher matcher(initial_bounds, kAllMagnetismEdges);
+ MatchedEdge edge;
+ // Overlap with the left side.
+ EXPECT_FALSE(matcher.ShouldAttach(
+ gfx::Rect(initial_bounds.x() - distance + 1,
+ initial_bounds.y() + 2,
+ distance * 2 + 2,
+ initial_bounds.height() + distance * 4), &edge));
+ EXPECT_FALSE(matcher.AreEdgesObscured());
+ // Should match the right side since it isn't obscured.
+ EXPECT_TRUE(matcher.ShouldAttach(
+ gfx::Rect(initial_bounds.right() - 1,
+ initial_bounds.y() + distance + 1,
+ distance,
+ 5), &edge));
+ EXPECT_EQ(MAGNETISM_EDGE_RIGHT, edge.primary_edge);
+ EXPECT_EQ(SECONDARY_MAGNETISM_EDGE_NONE, edge.secondary_edge);
+}
+
+// Verifies we don't match an obscured center.
+TEST(MagnetismMatcherTest, ObscureCenter) {
+ const int distance = MagnetismMatcher::kMagneticDistance;
+ const gfx::Rect initial_bounds(20, 10, 150, 160);
+ MagnetismMatcher matcher(initial_bounds, kAllMagnetismEdges);
+ MatchedEdge edge;
+ // Overlap with the center bottom edge.
+ EXPECT_FALSE(matcher.ShouldAttach(
+ gfx::Rect(100, initial_bounds.bottom() - distance - 2,
+ 20,
+ (distance + 2) * 2), &edge));
+ EXPECT_FALSE(matcher.AreEdgesObscured());
+ // Verify doesn't match the following which is obscured by first.
+ EXPECT_FALSE(matcher.ShouldAttach(
+ gfx::Rect(110, initial_bounds.bottom() + 1,
+ 10, 5), &edge));
+ // Should match the following which extends into non-overlapping region.
+ EXPECT_TRUE(matcher.ShouldAttach(
+ gfx::Rect(90,
+ initial_bounds.bottom() + 1,
+ 10, 5), &edge));
+ EXPECT_EQ(MAGNETISM_EDGE_BOTTOM, edge.primary_edge);
+ EXPECT_EQ(SECONDARY_MAGNETISM_EDGE_NONE, edge.secondary_edge);
+}
+
+// Verifies we don't match an obscured trailing edge.
+TEST(MagnetismMatcherTest, ObscureTrailing) {
+ const int distance = MagnetismMatcher::kMagneticDistance;
+ const gfx::Rect initial_bounds(20, 10, 150, 160);
+ MagnetismMatcher matcher(initial_bounds, kAllMagnetismEdges);
+ MatchedEdge edge;
+ // Overlap with the trailing left edge.
+ EXPECT_FALSE(matcher.ShouldAttach(
+ gfx::Rect(initial_bounds.x() - distance - 2,
+ 150,
+ (distance + 2) * 2,
+ 50), &edge));
+ EXPECT_FALSE(matcher.AreEdgesObscured());
+ // Verify doesn't match the following which is obscured by first.
+ EXPECT_FALSE(matcher.ShouldAttach(
+ gfx::Rect(initial_bounds.x() - 4,
+ 160, 3, 20), &edge));
+ // Should match the following which extends into non-overlapping region.
+ EXPECT_TRUE(matcher.ShouldAttach(
+ gfx::Rect(initial_bounds.x() - 4,
+ 140, 3, 20), &edge));
+ EXPECT_EQ(MAGNETISM_EDGE_LEFT, edge.primary_edge);
+ EXPECT_EQ(SECONDARY_MAGNETISM_EDGE_NONE, edge.secondary_edge);
+}
+
+} // namespace internal
+} // namespace ash
+
diff --git a/chromium/ash/wm/workspace/maximize_bubble_frame_state.h b/chromium/ash/wm/workspace/maximize_bubble_frame_state.h
new file mode 100644
index 00000000000..80fb835d8b6
--- /dev/null
+++ b/chromium/ash/wm/workspace/maximize_bubble_frame_state.h
@@ -0,0 +1,20 @@
+// Copyright (c) 2012 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 ASH_WM_WORKSPACE_MAXIMIZE_BUBBLE_FRAME_STATE_H_
+#define ASH_WM_WORKSPACE_MAXIMIZE_BUBBLE_FRAME_STATE_H_
+
+namespace ash {
+
+// These are the types of maximization we know.
+enum MaximizeBubbleFrameState {
+ FRAME_STATE_NONE = 0,
+ FRAME_STATE_FULL = 1, // This is the full maximized state.
+ FRAME_STATE_SNAP_LEFT = 2,
+ FRAME_STATE_SNAP_RIGHT = 3
+};
+
+} // namespace views
+
+#endif // ASH_WM_WORKSPACE_MAXIMIZE_BUBBLE_FRAME_STATE_H_
diff --git a/chromium/ash/wm/workspace/multi_window_resize_controller.cc b/chromium/ash/wm/workspace/multi_window_resize_controller.cc
new file mode 100644
index 00000000000..c447b9ced83
--- /dev/null
+++ b/chromium/ash/wm/workspace/multi_window_resize_controller.cc
@@ -0,0 +1,545 @@
+// Copyright (c) 2012 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.
+
+#include "ash/wm/workspace/multi_window_resize_controller.h"
+
+#include "ash/screen_ash.h"
+#include "ash/shell.h"
+#include "ash/shell_window_ids.h"
+#include "ash/wm/coordinate_conversion.h"
+#include "ash/wm/window_animations.h"
+#include "ash/wm/workspace/workspace_event_handler.h"
+#include "ash/wm/workspace/workspace_window_resizer.h"
+#include "grit/ash_resources.h"
+#include "ui/aura/client/screen_position_client.h"
+#include "ui/aura/root_window.h"
+#include "ui/aura/window.h"
+#include "ui/aura/window_delegate.h"
+#include "ui/base/hit_test.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "ui/gfx/canvas.h"
+#include "ui/gfx/image/image.h"
+#include "ui/gfx/screen.h"
+#include "ui/views/corewm/compound_event_filter.h"
+#include "ui/views/view.h"
+#include "ui/views/widget/widget.h"
+#include "ui/views/widget/widget_delegate.h"
+
+using aura::Window;
+
+namespace ash {
+namespace internal {
+
+namespace {
+
+// Delay before showing.
+const int kShowDelayMS = 400;
+
+// Delay before hiding.
+const int kHideDelayMS = 500;
+
+// Padding from the bottom/right edge the resize widget is shown at.
+const int kResizeWidgetPadding = 15;
+
+bool ContainsX(Window* window, int x) {
+ return window->bounds().x() <= x && window->bounds().right() >= x;
+}
+
+bool ContainsY(Window* window, int y) {
+ return window->bounds().y() <= y && window->bounds().bottom() >= y;
+}
+
+bool Intersects(int x1, int max_1, int x2, int max_2) {
+ return x2 <= max_1 && max_2 > x1;
+}
+
+} // namespace
+
+// View contained in the widget. Passes along mouse events to the
+// MultiWindowResizeController so that it can start/stop the resize loop.
+class MultiWindowResizeController::ResizeView : public views::View {
+ public:
+ explicit ResizeView(MultiWindowResizeController* controller,
+ Direction direction)
+ : controller_(controller),
+ direction_(direction),
+ image_(NULL) {
+ ResourceBundle& rb = ResourceBundle::GetSharedInstance();
+ int image_id =
+ direction == TOP_BOTTOM ? IDR_AURA_MULTI_WINDOW_RESIZE_H :
+ IDR_AURA_MULTI_WINDOW_RESIZE_V;
+ image_ = rb.GetImageNamed(image_id).ToImageSkia();
+ }
+
+ // views::View overrides:
+ virtual gfx::Size GetPreferredSize() OVERRIDE {
+ return gfx::Size(image_->width(), image_->height());
+ }
+ virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE {
+ canvas->DrawImageInt(*image_, 0, 0);
+ }
+ virtual bool OnMousePressed(const ui::MouseEvent& event) OVERRIDE {
+ gfx::Point location(event.location());
+ views::View::ConvertPointToScreen(this, &location);
+ controller_->StartResize(location);
+ return true;
+ }
+ virtual bool OnMouseDragged(const ui::MouseEvent& event) OVERRIDE {
+ gfx::Point location(event.location());
+ views::View::ConvertPointToScreen(this, &location);
+ controller_->Resize(location, event.flags());
+ return true;
+ }
+ virtual void OnMouseReleased(const ui::MouseEvent& event) OVERRIDE {
+ controller_->CompleteResize(event.flags());
+ }
+ virtual void OnMouseCaptureLost() OVERRIDE {
+ controller_->CancelResize();
+ }
+ virtual gfx::NativeCursor GetCursor(
+ const ui::MouseEvent& event) OVERRIDE {
+ int component = (direction_ == LEFT_RIGHT) ? HTRIGHT : HTBOTTOM;
+ return views::corewm::CompoundEventFilter::CursorForWindowComponent(
+ component);
+ }
+
+ private:
+ MultiWindowResizeController* controller_;
+ const Direction direction_;
+ const gfx::ImageSkia* image_;
+
+ DISALLOW_COPY_AND_ASSIGN(ResizeView);
+};
+
+// MouseWatcherHost implementation for MultiWindowResizeController. Forwards
+// Contains() to MultiWindowResizeController.
+class MultiWindowResizeController::ResizeMouseWatcherHost :
+ public views::MouseWatcherHost {
+ public:
+ ResizeMouseWatcherHost(MultiWindowResizeController* host) : host_(host) {}
+
+ // MouseWatcherHost overrides:
+ virtual bool Contains(const gfx::Point& point_in_screen,
+ MouseEventType type) OVERRIDE {
+ return host_->IsOverWindows(point_in_screen);
+ }
+
+ private:
+ MultiWindowResizeController* host_;
+
+ DISALLOW_COPY_AND_ASSIGN(ResizeMouseWatcherHost);
+};
+
+MultiWindowResizeController::ResizeWindows::ResizeWindows()
+ : window1(NULL),
+ window2(NULL),
+ direction(TOP_BOTTOM){
+}
+
+MultiWindowResizeController::ResizeWindows::~ResizeWindows() {
+}
+
+bool MultiWindowResizeController::ResizeWindows::Equals(
+ const ResizeWindows& other) const {
+ return window1 == other.window1 &&
+ window2 == other.window2 &&
+ direction == other.direction;
+}
+
+MultiWindowResizeController::MultiWindowResizeController() {
+}
+
+MultiWindowResizeController::~MultiWindowResizeController() {
+ window_resizer_.reset();
+ Hide();
+}
+
+void MultiWindowResizeController::Show(Window* window,
+ int component,
+ const gfx::Point& point_in_window) {
+ // When the resize widget is showing we ignore Show() requests. Instead we
+ // only care about mouse movements from MouseWatcher. This is necessary as
+ // WorkspaceEventHandler only sees mouse movements over the windows, not all
+ // windows or over the desktop.
+ if (resize_widget_)
+ return;
+
+ ResizeWindows windows(DetermineWindows(window, component, point_in_window));
+ if (IsShowing()) {
+ if (windows_.Equals(windows))
+ return; // Over the same windows.
+ DelayedHide();
+ }
+
+ if (!windows.is_valid())
+ return;
+ Hide();
+ windows_ = windows;
+ windows_.window1->AddObserver(this);
+ windows_.window2->AddObserver(this);
+ show_location_in_parent_ = point_in_window;
+ Window::ConvertPointToTarget(
+ window, window->parent(), &show_location_in_parent_);
+ if (show_timer_.IsRunning())
+ return;
+ show_timer_.Start(
+ FROM_HERE, base::TimeDelta::FromMilliseconds(kShowDelayMS),
+ this, &MultiWindowResizeController::ShowIfValidMouseLocation);
+}
+
+void MultiWindowResizeController::Hide() {
+ hide_timer_.Stop();
+ if (window_resizer_)
+ return; // Ignore hides while actively resizing.
+
+ if (windows_.window1) {
+ windows_.window1->RemoveObserver(this);
+ windows_.window1 = NULL;
+ }
+ if (windows_.window2) {
+ windows_.window2->RemoveObserver(this);
+ windows_.window2 = NULL;
+ }
+
+ show_timer_.Stop();
+
+ if (!resize_widget_)
+ return;
+
+ for (size_t i = 0; i < windows_.other_windows.size(); ++i)
+ windows_.other_windows[i]->RemoveObserver(this);
+ mouse_watcher_.reset();
+ resize_widget_.reset();
+ windows_ = ResizeWindows();
+}
+
+void MultiWindowResizeController::MouseMovedOutOfHost() {
+ Hide();
+}
+
+void MultiWindowResizeController::OnWindowDestroying(
+ aura::Window* window) {
+ // Have to explicitly reset the WindowResizer, otherwise Hide() does nothing.
+ window_resizer_.reset();
+ Hide();
+}
+
+MultiWindowResizeController::ResizeWindows
+MultiWindowResizeController::DetermineWindowsFromScreenPoint(
+ aura::Window* window) const {
+ gfx::Point mouse_location(
+ gfx::Screen::GetScreenFor(window)->GetCursorScreenPoint());
+ wm::ConvertPointFromScreen(window, &mouse_location);
+ const int component =
+ window->delegate()->GetNonClientComponent(mouse_location);
+ return DetermineWindows(window, component, mouse_location);
+}
+
+MultiWindowResizeController::ResizeWindows
+MultiWindowResizeController::DetermineWindows(
+ Window* window,
+ int window_component,
+ const gfx::Point& point) const {
+ ResizeWindows result;
+ gfx::Point point_in_parent(point);
+ Window::ConvertPointToTarget(window, window->parent(), &point_in_parent);
+ switch (window_component) {
+ case HTRIGHT:
+ result.direction = LEFT_RIGHT;
+ result.window1 = window;
+ result.window2 = FindWindowByEdge(
+ window, HTLEFT, window->bounds().right(), point_in_parent.y());
+ break;
+ case HTLEFT:
+ result.direction = LEFT_RIGHT;
+ result.window1 = FindWindowByEdge(
+ window, HTRIGHT, window->bounds().x(), point_in_parent.y());
+ result.window2 = window;
+ break;
+ case HTTOP:
+ result.direction = TOP_BOTTOM;
+ result.window1 = FindWindowByEdge(
+ window, HTBOTTOM, point_in_parent.x(), window->bounds().y());
+ result.window2 = window;
+ break;
+ case HTBOTTOM:
+ result.direction = TOP_BOTTOM;
+ result.window1 = window;
+ result.window2 = FindWindowByEdge(
+ window, HTTOP, point_in_parent.x(), window->bounds().bottom());
+ break;
+ default:
+ break;
+ }
+ return result;
+}
+
+Window* MultiWindowResizeController::FindWindowByEdge(
+ Window* window_to_ignore,
+ int edge_want,
+ int x,
+ int y) const {
+ Window* parent = window_to_ignore->parent();
+ const Window::Windows& windows(parent->children());
+ for (Window::Windows::const_reverse_iterator i = windows.rbegin();
+ i != windows.rend(); ++i) {
+ Window* window = *i;
+ if (window == window_to_ignore || !window->IsVisible())
+ continue;
+ switch (edge_want) {
+ case HTLEFT:
+ if (ContainsY(window, y) && window->bounds().x() == x)
+ return window;
+ break;
+ case HTRIGHT:
+ if (ContainsY(window, y) && window->bounds().right() == x)
+ return window;
+ break;
+ case HTTOP:
+ if (ContainsX(window, x) && window->bounds().y() == y)
+ return window;
+ break;
+ case HTBOTTOM:
+ if (ContainsX(window, x) && window->bounds().bottom() == y)
+ return window;
+ break;
+ default:
+ NOTREACHED();
+ }
+ // Window doesn't contain the edge, but if window contains |point|
+ // it's obscuring any other window that could be at the location.
+ if (window->bounds().Contains(x, y))
+ return NULL;
+ }
+ return NULL;
+}
+
+aura::Window* MultiWindowResizeController::FindWindowTouching(
+ aura::Window* window,
+ Direction direction) const {
+ int right = window->bounds().right();
+ int bottom = window->bounds().bottom();
+ Window* parent = window->parent();
+ const Window::Windows& windows(parent->children());
+ for (Window::Windows::const_reverse_iterator i = windows.rbegin();
+ i != windows.rend(); ++i) {
+ Window* other = *i;
+ if (other == window || !other->IsVisible())
+ continue;
+ switch (direction) {
+ case TOP_BOTTOM:
+ if (other->bounds().y() == bottom &&
+ Intersects(other->bounds().x(), other->bounds().right(),
+ window->bounds().x(), window->bounds().right())) {
+ return other;
+ }
+ break;
+ case LEFT_RIGHT:
+ if (other->bounds().x() == right &&
+ Intersects(other->bounds().y(), other->bounds().bottom(),
+ window->bounds().y(), window->bounds().bottom())) {
+ return other;
+ }
+ break;
+ default:
+ NOTREACHED();
+ }
+ }
+ return NULL;
+}
+
+void MultiWindowResizeController::FindWindowsTouching(
+ aura::Window* start,
+ Direction direction,
+ std::vector<aura::Window*>* others) const {
+ while (start) {
+ start = FindWindowTouching(start, direction);
+ if (start)
+ others->push_back(start);
+ }
+}
+
+void MultiWindowResizeController::DelayedHide() {
+ if (hide_timer_.IsRunning())
+ return;
+
+ hide_timer_.Start(FROM_HERE,
+ base::TimeDelta::FromMilliseconds(kHideDelayMS),
+ this, &MultiWindowResizeController::Hide);
+}
+
+void MultiWindowResizeController::ShowIfValidMouseLocation() {
+ if (DetermineWindowsFromScreenPoint(windows_.window1).Equals(windows_) ||
+ DetermineWindowsFromScreenPoint(windows_.window2).Equals(windows_)) {
+ ShowNow();
+ } else {
+ Hide();
+ }
+}
+
+void MultiWindowResizeController::ShowNow() {
+ DCHECK(!resize_widget_.get());
+ DCHECK(windows_.is_valid());
+ show_timer_.Stop();
+ resize_widget_.reset(new views::Widget);
+ views::Widget::InitParams params(views::Widget::InitParams::TYPE_POPUP);
+ params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW;
+ params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
+ params.parent = Shell::GetContainer(
+ Shell::GetActiveRootWindow(),
+ internal::kShellWindowId_AlwaysOnTopContainer);
+ params.can_activate = false;
+ ResizeView* view = new ResizeView(this, windows_.direction);
+ resize_widget_->set_focus_on_creation(false);
+ resize_widget_->Init(params);
+ views::corewm::SetWindowVisibilityAnimationType(
+ resize_widget_->GetNativeWindow(),
+ views::corewm::WINDOW_VISIBILITY_ANIMATION_TYPE_FADE);
+ resize_widget_->GetNativeWindow()->SetName("MultiWindowResizeController");
+ resize_widget_->SetContentsView(view);
+ show_bounds_in_screen_ = ScreenAsh::ConvertRectToScreen(
+ windows_.window1->parent(),
+ CalculateResizeWidgetBounds(show_location_in_parent_));
+ resize_widget_->SetBounds(show_bounds_in_screen_);
+ resize_widget_->Show();
+ mouse_watcher_.reset(new views::MouseWatcher(
+ new ResizeMouseWatcherHost(this),
+ this));
+ mouse_watcher_->set_notify_on_exit_time(
+ base::TimeDelta::FromMilliseconds(kHideDelayMS));
+ mouse_watcher_->Start();
+}
+
+bool MultiWindowResizeController::IsShowing() const {
+ return resize_widget_.get() || show_timer_.IsRunning();
+}
+
+void MultiWindowResizeController::StartResize(
+ const gfx::Point& location_in_screen) {
+ DCHECK(!window_resizer_.get());
+ DCHECK(windows_.is_valid());
+ hide_timer_.Stop();
+ gfx::Point location_in_parent(location_in_screen);
+ aura::client::GetScreenPositionClient(windows_.window2->GetRootWindow())->
+ ConvertPointFromScreen(windows_.window2->parent(), &location_in_parent);
+ std::vector<aura::Window*> windows;
+ windows.push_back(windows_.window2);
+ DCHECK(windows_.other_windows.empty());
+ FindWindowsTouching(windows_.window2, windows_.direction,
+ &windows_.other_windows);
+ for (size_t i = 0; i < windows_.other_windows.size(); ++i) {
+ windows_.other_windows[i]->AddObserver(this);
+ windows.push_back(windows_.other_windows[i]);
+ }
+ int component = windows_.direction == LEFT_RIGHT ? HTRIGHT : HTBOTTOM;
+ window_resizer_.reset(WorkspaceWindowResizer::Create(
+ windows_.window1,
+ location_in_parent,
+ component,
+ aura::client::WINDOW_MOVE_SOURCE_MOUSE,
+ windows));
+}
+
+void MultiWindowResizeController::Resize(const gfx::Point& location_in_screen,
+ int event_flags) {
+ gfx::Point location_in_parent(location_in_screen);
+ aura::client::GetScreenPositionClient(windows_.window1->GetRootWindow())->
+ ConvertPointFromScreen(windows_.window1->parent(), &location_in_parent);
+ window_resizer_->Drag(location_in_parent, event_flags);
+ gfx::Rect bounds = ScreenAsh::ConvertRectToScreen(
+ windows_.window1->parent(),
+ CalculateResizeWidgetBounds(location_in_parent));
+
+ if (windows_.direction == LEFT_RIGHT)
+ bounds.set_y(show_bounds_in_screen_.y());
+ else
+ bounds.set_x(show_bounds_in_screen_.x());
+ resize_widget_->SetBounds(bounds);
+}
+
+void MultiWindowResizeController::CompleteResize(int event_flags) {
+ window_resizer_->CompleteDrag(event_flags);
+ window_resizer_.reset();
+
+ // Mouse may still be over resizer, if not hide.
+ gfx::Point screen_loc = Shell::GetScreen()->GetCursorScreenPoint();
+ if (!resize_widget_->GetWindowBoundsInScreen().Contains(screen_loc)) {
+ Hide();
+ } else {
+ // If the mouse is over the resizer we need to remove observers on any of
+ // the |other_windows|. If we start another resize we'll recalculate the
+ // |other_windows| and invoke AddObserver() as necessary.
+ for (size_t i = 0; i < windows_.other_windows.size(); ++i)
+ windows_.other_windows[i]->RemoveObserver(this);
+ windows_.other_windows.clear();
+ }
+}
+
+void MultiWindowResizeController::CancelResize() {
+ if (!window_resizer_)
+ return; // Happens if window was destroyed and we nuked the WindowResizer.
+ window_resizer_->RevertDrag();
+ window_resizer_.reset();
+ Hide();
+}
+
+gfx::Rect MultiWindowResizeController::CalculateResizeWidgetBounds(
+ const gfx::Point& location_in_parent) const {
+ gfx::Size pref = resize_widget_->GetContentsView()->GetPreferredSize();
+ int x = 0, y = 0;
+ if (windows_.direction == LEFT_RIGHT) {
+ x = windows_.window1->bounds().right() - pref.width() / 2;
+ y = location_in_parent.y() + kResizeWidgetPadding;
+ if (y + pref.height() / 2 > windows_.window1->bounds().bottom() &&
+ y + pref.height() / 2 > windows_.window2->bounds().bottom()) {
+ y = location_in_parent.y() - kResizeWidgetPadding - pref.height();
+ }
+ } else {
+ x = location_in_parent.x() + kResizeWidgetPadding;
+ if (x + pref.height() / 2 > windows_.window1->bounds().right() &&
+ x + pref.height() / 2 > windows_.window2->bounds().right()) {
+ x = location_in_parent.x() - kResizeWidgetPadding - pref.width();
+ }
+ y = windows_.window1->bounds().bottom() - pref.height() / 2;
+ }
+ return gfx::Rect(x, y, pref.width(), pref.height());
+}
+
+bool MultiWindowResizeController::IsOverWindows(
+ const gfx::Point& location_in_screen) const {
+ if (window_resizer_)
+ return true; // Ignore hides while actively resizing.
+
+ if (resize_widget_->GetWindowBoundsInScreen().Contains(location_in_screen))
+ return true;
+
+ int hit1, hit2;
+ if (windows_.direction == TOP_BOTTOM) {
+ hit1 = HTBOTTOM;
+ hit2 = HTTOP;
+ } else {
+ hit1 = HTRIGHT;
+ hit2 = HTLEFT;
+ }
+
+ return IsOverWindow(windows_.window1, location_in_screen, hit1) ||
+ IsOverWindow(windows_.window2, location_in_screen, hit2);
+}
+
+bool MultiWindowResizeController::IsOverWindow(
+ aura::Window* window,
+ const gfx::Point& location_in_screen,
+ int component) const {
+ if (!window->delegate())
+ return false;
+
+ gfx::Point window_loc(location_in_screen);
+ aura::Window::ConvertPointToTarget(
+ window->GetRootWindow(), window, &window_loc);
+ return window->HitTest(window_loc) &&
+ window->delegate()->GetNonClientComponent(window_loc) == component;
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/wm/workspace/multi_window_resize_controller.h b/chromium/ash/wm/workspace/multi_window_resize_controller.h
new file mode 100644
index 00000000000..02eeb2511a4
--- /dev/null
+++ b/chromium/ash/wm/workspace/multi_window_resize_controller.h
@@ -0,0 +1,186 @@
+// Copyright (c) 2012 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 ASH_WM_WORKSPACE_MULTI_WINDOW_RESIZE_CONTROLLER_H_
+#define ASH_WM_WORKSPACE_MULTI_WINDOW_RESIZE_CONTROLLER_H_
+
+#include <vector>
+
+#include "ash/ash_export.h"
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/timer/timer.h"
+#include "ui/aura/window_observer.h"
+#include "ui/gfx/rect.h"
+#include "ui/views/mouse_watcher.h"
+
+namespace aura {
+class Window;
+}
+
+namespace views {
+class Widget;
+}
+
+namespace ash {
+namespace internal {
+
+class MultiWindowResizeControllerTest;
+class WorkspaceWindowResizer;
+
+// Two directions resizes happen in.
+enum Direction {
+ TOP_BOTTOM,
+ LEFT_RIGHT,
+};
+
+// MultiWindowResizeController is responsible for determining and showing a
+// widget that allows resizing multiple windows at the same time.
+// MultiWindowResizeController is driven by WorkspaceEventFilter.
+class ASH_EXPORT MultiWindowResizeController :
+ public views::MouseWatcherListener, public aura::WindowObserver {
+ public:
+ MultiWindowResizeController();
+ virtual ~MultiWindowResizeController();
+
+ // If necessary, shows the resize widget. |window| is the window the mouse
+ // is over, |component| the edge and |point| the location of the mouse.
+ void Show(aura::Window* window, int component, const gfx::Point& point);
+
+ // Hides the resize widget.
+ void Hide();
+
+ // MouseWatcherListenre overrides:
+ virtual void MouseMovedOutOfHost() OVERRIDE;
+
+ // WindowObserver overrides:
+ virtual void OnWindowDestroying(aura::Window* window) OVERRIDE;
+
+ private:
+ friend class MultiWindowResizeControllerTest;
+
+ // Used to track the two resizable windows and direction.
+ struct ResizeWindows {
+ ResizeWindows();
+ ~ResizeWindows();
+
+ // Returns true if |other| equals this ResizeWindows. This does *not*
+ // consider the windows in |other_windows|.
+ bool Equals(const ResizeWindows& other) const;
+
+ // Returns true if this ResizeWindows is valid.
+ bool is_valid() const { return window1 && window2; }
+
+ // The left/top window to resize.
+ aura::Window* window1;
+
+ // Other window to resize.
+ aura::Window* window2;
+
+ // Direction
+ Direction direction;
+
+ // Windows after |window2| that are to be resized. Determined at the time
+ // the resize starts.
+ std::vector<aura::Window*> other_windows;
+ };
+
+ class ResizeMouseWatcherHost;
+ class ResizeView;
+
+ // Returns a ResizeWindows based on the specified arguments. Use is_valid()
+ // to test if the return value is a valid multi window resize location.
+ ResizeWindows DetermineWindows(aura::Window* window,
+ int window_component,
+ const gfx::Point& point) const;
+
+ // Variant of DetermineWindows() that uses the current location of the mouse
+ // to determine the resize windows.
+ ResizeWindows DetermineWindowsFromScreenPoint(aura::Window* window) const;
+
+ // Finds a window by edge (one of the constants HitTestCompat.
+ aura::Window* FindWindowByEdge(aura::Window* window_to_ignore,
+ int edge_want,
+ int x,
+ int y) const;
+
+ // Returns the first window touching |window|.
+ aura::Window* FindWindowTouching(aura::Window* window,
+ Direction direction) const;
+
+ // Places any windows touching |start| into |others|.
+ void FindWindowsTouching(aura::Window* start,
+ Direction direction,
+ std::vector<aura::Window*>* others) const;
+
+ // Hides the window after a delay.
+ void DelayedHide();
+
+ // Shows the resizer if the mouse is still at a valid location. This is called
+ // from the |show_timer_|.
+ void ShowIfValidMouseLocation();
+
+ // Shows the widget immediately.
+ void ShowNow();
+
+ // Returns true if the widget is showing.
+ bool IsShowing() const;
+
+ // Initiates a resize.
+ void StartResize(const gfx::Point& location_in_screen);
+
+ // Resizes to the new location.
+ void Resize(const gfx::Point& location_in_screen, int event_flags);
+
+ // Completes the resize.
+ void CompleteResize(int event_flags);
+
+ // Cancels the resize.
+ void CancelResize();
+
+ // Returns the bounds for the resize widget.
+ gfx::Rect CalculateResizeWidgetBounds(
+ const gfx::Point& location_in_parent) const;
+
+ // Returns true if |location_in_screen| is over the resize windows
+ // (or the resize widget itself).
+ bool IsOverWindows(const gfx::Point& location_in_screen) const;
+
+ // Returns true if |location_in_screen| is over |window|.
+ bool IsOverWindow(aura::Window* window,
+ const gfx::Point& location_in_screen,
+ int component) const;
+
+ // Windows and direction to resize.
+ ResizeWindows windows_;
+
+ // Timer before hiding.
+ base::OneShotTimer<MultiWindowResizeController> hide_timer_;
+
+ // Timer used before showing.
+ base::OneShotTimer<MultiWindowResizeController> show_timer_;
+
+ scoped_ptr<views::Widget> resize_widget_;
+
+ // If non-null we're in a resize loop.
+ scoped_ptr<WorkspaceWindowResizer> window_resizer_;
+
+ // Mouse coordinate passed to Show() in container's coodinates.
+ gfx::Point show_location_in_parent_;
+
+ // Bounds the widget was last shown at in screen coordinates.
+ gfx::Rect show_bounds_in_screen_;
+
+ // Used to detect whether the mouse is over the windows. While
+ // |resize_widget_| is non-NULL (ie the widget is showing) we ignore calls
+ // to Show().
+ scoped_ptr<views::MouseWatcher> mouse_watcher_;
+
+ DISALLOW_COPY_AND_ASSIGN(MultiWindowResizeController);
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_WM_WORKSPACE_MULTI_WINDOW_RESIZE_CONTROLLER_H_
diff --git a/chromium/ash/wm/workspace/multi_window_resize_controller_unittest.cc b/chromium/ash/wm/workspace/multi_window_resize_controller_unittest.cc
new file mode 100644
index 00000000000..9a3d43cff52
--- /dev/null
+++ b/chromium/ash/wm/workspace/multi_window_resize_controller_unittest.cc
@@ -0,0 +1,258 @@
+// Copyright (c) 2012 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.
+
+#include "ash/wm/workspace/multi_window_resize_controller.h"
+
+#include "ash/shell.h"
+#include "ash/test/ash_test_base.h"
+#include "ash/test/shell_test_api.h"
+#include "ash/wm/window_util.h"
+#include "ash/wm/workspace_controller.h"
+#include "ash/wm/workspace_controller_test_helper.h"
+#include "ash/wm/workspace/workspace_event_handler_test_helper.h"
+#include "ui/aura/test/event_generator.h"
+#include "ui/aura/test/test_window_delegate.h"
+#include "ui/aura/window.h"
+#include "ui/base/hit_test.h"
+#include "ui/gfx/screen.h"
+#include "ui/views/widget/widget.h"
+
+namespace ash {
+namespace internal {
+
+class MultiWindowResizeControllerTest : public test::AshTestBase {
+ public:
+ MultiWindowResizeControllerTest() : resize_controller_(NULL) {}
+ virtual ~MultiWindowResizeControllerTest() {}
+
+ virtual void SetUp() OVERRIDE {
+ test::AshTestBase::SetUp();
+ WorkspaceController* wc =
+ test::ShellTestApi(Shell::GetInstance()).workspace_controller();
+ WorkspaceEventHandler* event_handler =
+ WorkspaceControllerTestHelper(wc).GetEventHandler();
+ resize_controller_ = WorkspaceEventHandlerTestHelper(event_handler).
+ resize_controller();
+ }
+
+ protected:
+ aura::Window* CreateTestWindow(aura::WindowDelegate* delegate,
+ const gfx::Rect& bounds) {
+ aura::Window* window = new aura::Window(delegate);
+ window->SetType(aura::client::WINDOW_TYPE_NORMAL);
+ window->Init(ui::LAYER_TEXTURED);
+ SetDefaultParentByPrimaryRootWindow(window);
+ window->SetBounds(bounds);
+ window->Show();
+ return window;
+ }
+
+ void ShowNow() {
+ resize_controller_->ShowNow();
+ }
+
+ bool IsShowing() {
+ return resize_controller_->IsShowing();
+ }
+
+ bool HasPendingShow() {
+ return resize_controller_->show_timer_.IsRunning();
+ }
+
+ bool HasPendingHide() {
+ return resize_controller_->hide_timer_.IsRunning();
+ }
+
+ void Hide() {
+ resize_controller_->Hide();
+ }
+
+ bool HasTarget(aura::Window* window) {
+ if (!resize_controller_->windows_.is_valid())
+ return false;
+ if ((resize_controller_->windows_.window1 == window ||
+ resize_controller_->windows_.window2 == window))
+ return true;
+ for (size_t i = 0;
+ i < resize_controller_->windows_.other_windows.size(); ++i) {
+ if (resize_controller_->windows_.other_windows[i] == window)
+ return true;
+ }
+ return false;
+ }
+
+ bool IsOverWindows(const gfx::Point& loc) {
+ return resize_controller_->IsOverWindows(loc);
+ }
+
+ views::Widget* resize_widget() {
+ return resize_controller_->resize_widget_.get();
+ }
+
+ MultiWindowResizeController* resize_controller_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MultiWindowResizeControllerTest);
+};
+
+// Assertions around moving mouse over 2 windows.
+TEST_F(MultiWindowResizeControllerTest, BasicTests) {
+ aura::test::TestWindowDelegate delegate1;
+ scoped_ptr<aura::Window> w1(
+ CreateTestWindow(&delegate1, gfx::Rect(0, 0, 100, 100)));
+ delegate1.set_window_component(HTRIGHT);
+ aura::test::TestWindowDelegate delegate2;
+ scoped_ptr<aura::Window> w2(
+ CreateTestWindow(&delegate2, gfx::Rect(100, 0, 100, 100)));
+ delegate2.set_window_component(HTRIGHT);
+ aura::test::EventGenerator generator(w1->GetRootWindow());
+ generator.MoveMouseTo(99, 50);
+ EXPECT_TRUE(HasPendingShow());
+ EXPECT_TRUE(IsShowing());
+ EXPECT_FALSE(HasPendingHide());
+
+ // Force a show now.
+ ShowNow();
+ EXPECT_FALSE(HasPendingShow());
+ EXPECT_TRUE(IsShowing());
+ EXPECT_FALSE(HasPendingHide());
+
+ EXPECT_FALSE(IsOverWindows(gfx::Point(200, 200)));
+
+ // Have to explicitly invoke this as MouseWatcher listens for native events.
+ resize_controller_->MouseMovedOutOfHost();
+ EXPECT_FALSE(HasPendingShow());
+ EXPECT_FALSE(IsShowing());
+ EXPECT_FALSE(HasPendingHide());
+}
+
+// Makes sure deleting a window hides.
+TEST_F(MultiWindowResizeControllerTest, DeleteWindow) {
+ aura::test::TestWindowDelegate delegate1;
+ scoped_ptr<aura::Window> w1(
+ CreateTestWindow(&delegate1, gfx::Rect(0, 0, 100, 100)));
+ delegate1.set_window_component(HTRIGHT);
+ aura::test::TestWindowDelegate delegate2;
+ scoped_ptr<aura::Window> w2(
+ CreateTestWindow(&delegate2, gfx::Rect(100, 0, 100, 100)));
+ delegate2.set_window_component(HTRIGHT);
+ aura::test::EventGenerator generator(w1->GetRootWindow());
+ generator.MoveMouseTo(99, 50);
+ EXPECT_TRUE(HasPendingShow());
+ EXPECT_TRUE(IsShowing());
+ EXPECT_FALSE(HasPendingHide());
+
+ // Force a show now.
+ ShowNow();
+ EXPECT_FALSE(HasPendingShow());
+ EXPECT_TRUE(IsShowing());
+ EXPECT_FALSE(HasPendingHide());
+
+ // Move the mouse over the resize widget.
+ ASSERT_TRUE(resize_widget());
+ gfx::Rect bounds(resize_widget()->GetWindowBoundsInScreen());
+ generator.MoveMouseTo(bounds.x() + 1, bounds.y() + 1);
+ EXPECT_FALSE(HasPendingShow());
+ EXPECT_TRUE(IsShowing());
+ EXPECT_FALSE(HasPendingHide());
+
+ // Move the resize widget
+ generator.PressLeftButton();
+ generator.MoveMouseTo(bounds.x() + 10, bounds.y() + 10);
+
+ // Delete w2.
+ w2.reset();
+ EXPECT_TRUE(resize_widget() == NULL);
+ EXPECT_FALSE(HasPendingShow());
+ EXPECT_FALSE(IsShowing());
+ EXPECT_FALSE(HasPendingHide());
+ EXPECT_FALSE(HasTarget(w1.get()));
+}
+
+// Tests resizing.
+TEST_F(MultiWindowResizeControllerTest, Drag) {
+ aura::test::TestWindowDelegate delegate1;
+ scoped_ptr<aura::Window> w1(
+ CreateTestWindow(&delegate1, gfx::Rect(0, 0, 100, 100)));
+ delegate1.set_window_component(HTRIGHT);
+ aura::test::TestWindowDelegate delegate2;
+ scoped_ptr<aura::Window> w2(
+ CreateTestWindow(&delegate2, gfx::Rect(100, 0, 100, 100)));
+ delegate2.set_window_component(HTRIGHT);
+ aura::test::EventGenerator generator(w1->GetRootWindow());
+ generator.MoveMouseTo(99, 50);
+ EXPECT_TRUE(HasPendingShow());
+ EXPECT_TRUE(IsShowing());
+ EXPECT_FALSE(HasPendingHide());
+
+ // Force a show now.
+ ShowNow();
+ EXPECT_FALSE(HasPendingShow());
+ EXPECT_TRUE(IsShowing());
+ EXPECT_FALSE(HasPendingHide());
+
+ // Move the mouse over the resize widget.
+ ASSERT_TRUE(resize_widget());
+ gfx::Rect bounds(resize_widget()->GetWindowBoundsInScreen());
+ generator.MoveMouseTo(bounds.x() + 1, bounds.y() + 1);
+ EXPECT_FALSE(HasPendingShow());
+ EXPECT_TRUE(IsShowing());
+ EXPECT_FALSE(HasPendingHide());
+
+ // Move the resize widget
+ generator.PressLeftButton();
+ generator.MoveMouseTo(bounds.x() + 11, bounds.y() + 10);
+ generator.ReleaseLeftButton();
+
+ EXPECT_TRUE(resize_widget());
+ EXPECT_FALSE(HasPendingShow());
+ EXPECT_TRUE(IsShowing());
+ EXPECT_FALSE(HasPendingHide());
+ EXPECT_EQ("0,0 110x100", w1->bounds().ToString());
+ EXPECT_EQ("110,0 100x100", w2->bounds().ToString());
+}
+
+// Makes sure three windows are picked up.
+TEST_F(MultiWindowResizeControllerTest, Three) {
+ aura::test::TestWindowDelegate delegate1;
+ scoped_ptr<aura::Window> w1(
+ CreateTestWindow(&delegate1, gfx::Rect(0, 0, 100, 100)));
+ delegate1.set_window_component(HTRIGHT);
+ aura::test::TestWindowDelegate delegate2;
+ scoped_ptr<aura::Window> w2(
+ CreateTestWindow(&delegate2, gfx::Rect(100, 0, 100, 100)));
+ delegate2.set_window_component(HTRIGHT);
+ aura::test::TestWindowDelegate delegate3;
+ scoped_ptr<aura::Window> w3(
+ CreateTestWindow(&delegate2, gfx::Rect(200, 0, 100, 100)));
+ delegate3.set_window_component(HTRIGHT);
+
+ aura::test::EventGenerator generator(w1->GetRootWindow());
+ generator.MoveMouseTo(99, 50);
+ EXPECT_TRUE(HasPendingShow());
+ EXPECT_TRUE(IsShowing());
+ EXPECT_FALSE(HasPendingHide());
+ EXPECT_FALSE(HasTarget(w3.get()));
+
+ ShowNow();
+ EXPECT_FALSE(HasPendingShow());
+ EXPECT_TRUE(IsShowing());
+ EXPECT_FALSE(HasPendingHide());
+
+ // w3 should be picked up when resize is started.
+ gfx::Rect bounds(resize_widget()->GetWindowBoundsInScreen());
+ generator.MoveMouseTo(bounds.x() + 1, bounds.y() + 1);
+ generator.PressLeftButton();
+ generator.MoveMouseTo(bounds.x() + 11, bounds.y() + 10);
+
+ EXPECT_TRUE(HasTarget(w3.get()));
+
+ // Release the mouse. The resizer should still be visible and a subsequent
+ // press should not trigger a DCHECK.
+ generator.ReleaseLeftButton();
+ EXPECT_TRUE(IsShowing());
+ generator.PressLeftButton();
+}
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/wm/workspace/phantom_window_controller.cc b/chromium/ash/wm/workspace/phantom_window_controller.cc
new file mode 100644
index 00000000000..67272c18a39
--- /dev/null
+++ b/chromium/ash/wm/workspace/phantom_window_controller.cc
@@ -0,0 +1,214 @@
+// Copyright (c) 2012 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.
+
+#include "ash/wm/workspace/phantom_window_controller.h"
+
+#include "ash/shell.h"
+#include "ash/shell_window_ids.h"
+#include "ash/wm/coordinate_conversion.h"
+#include "third_party/skia/include/core/SkCanvas.h"
+#include "ui/aura/root_window.h"
+#include "ui/aura/window.h"
+#include "ui/base/animation/slide_animation.h"
+#include "ui/compositor/layer.h"
+#include "ui/compositor/scoped_layer_animation_settings.h"
+#include "ui/gfx/canvas.h"
+#include "ui/gfx/skia_util.h"
+#include "ui/views/painter.h"
+#include "ui/views/view.h"
+#include "ui/views/widget/widget.h"
+
+
+namespace ash {
+namespace internal {
+
+// EdgePainter ----------------------------------------------------------------
+
+namespace {
+
+// Paints the background of the phantom window for window snapping.
+class EdgePainter : public views::Painter {
+ public:
+ EdgePainter();
+ virtual ~EdgePainter();
+
+ // views::Painter:
+ virtual gfx::Size GetMinimumSize() const OVERRIDE;
+ virtual void Paint(gfx::Canvas* canvas, const gfx::Size& size) OVERRIDE;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(EdgePainter);
+};
+
+} // namespace
+
+
+EdgePainter::EdgePainter() {
+}
+
+EdgePainter::~EdgePainter() {
+}
+
+gfx::Size EdgePainter::GetMinimumSize() const {
+ return gfx::Size();
+}
+
+void EdgePainter::Paint(gfx::Canvas* canvas, const gfx::Size& size) {
+ const int kInsetSize = 4;
+ int x = kInsetSize;
+ int y = kInsetSize;
+ int w = size.width() - kInsetSize * 2;
+ int h = size.height() - kInsetSize * 2;
+ bool inset = (w > 0 && h > 0);
+ if (!inset) {
+ x = 0;
+ y = 0;
+ w = size.width();
+ h = size.height();
+ }
+ SkPaint paint;
+ paint.setColor(SkColorSetARGB(100, 0, 0, 0));
+ paint.setStyle(SkPaint::kFill_Style);
+ paint.setAntiAlias(true);
+ const int kRoundRectSize = 4;
+ canvas->sk_canvas()->drawRoundRect(
+ gfx::RectToSkRect(gfx::Rect(x, y, w, h)),
+ SkIntToScalar(kRoundRectSize), SkIntToScalar(kRoundRectSize), paint);
+ if (!inset)
+ return;
+
+ paint.setColor(SkColorSetARGB(200, 255, 255, 255));
+ paint.setStyle(SkPaint::kStroke_Style);
+ paint.setStrokeWidth(SkIntToScalar(2));
+ canvas->sk_canvas()->drawRoundRect(
+ gfx::RectToSkRect(gfx::Rect(x, y, w, h)), SkIntToScalar(kRoundRectSize),
+ SkIntToScalar(kRoundRectSize), paint);
+}
+
+
+// PhantomWindowController ----------------------------------------------------
+
+PhantomWindowController::PhantomWindowController(aura::Window* window)
+ : window_(window),
+ phantom_below_window_(NULL),
+ phantom_widget_(NULL),
+ phantom_widget_start_(NULL) {
+}
+
+PhantomWindowController::~PhantomWindowController() {
+ Hide();
+}
+
+void PhantomWindowController::Show(const gfx::Rect& bounds_in_screen) {
+ if (bounds_in_screen == bounds_in_screen_)
+ return;
+ bounds_in_screen_ = bounds_in_screen;
+ aura::RootWindow* target_root = wm::GetRootWindowMatching(bounds_in_screen);
+ // Show the phantom at the current bounds of the window. We'll animate to the
+ // target bounds. If phantom exists, update the start bounds.
+ if (!phantom_widget_)
+ start_bounds_ = window_->GetBoundsInScreen();
+ else
+ start_bounds_ = phantom_widget_->GetWindowBoundsInScreen();
+ if (phantom_widget_ &&
+ phantom_widget_->GetNativeWindow()->GetRootWindow() != target_root) {
+ phantom_widget_->Close();
+ phantom_widget_ = NULL;
+ }
+ if (!phantom_widget_)
+ phantom_widget_ = CreatePhantomWidget(target_root, start_bounds_);
+
+ // Create a secondary widget in a second screen if start_bounds_ lie at least
+ // partially in that other screen. This allows animations to start or restart
+ // in one root window and progress into another root.
+ aura::RootWindow* start_root = wm::GetRootWindowMatching(start_bounds_);
+ if (start_root == target_root) {
+ Shell::RootWindowList root_windows = Shell::GetAllRootWindows();
+ for (size_t i = 0; i < root_windows.size(); ++i) {
+ if (root_windows[i] != target_root &&
+ root_windows[i]->GetBoundsInScreen().Intersects(start_bounds_)) {
+ start_root = root_windows[i];
+ break;
+ }
+ }
+ }
+ if (phantom_widget_start_ &&
+ (phantom_widget_start_->GetNativeWindow()->GetRootWindow() != start_root
+ || start_root == target_root)) {
+ phantom_widget_start_->Close();
+ phantom_widget_start_ = NULL;
+ }
+ if (!phantom_widget_start_ && start_root != target_root)
+ phantom_widget_start_ = CreatePhantomWidget(start_root, start_bounds_);
+
+ animation_.reset(new ui::SlideAnimation(this));
+ animation_->SetTweenType(ui::Tween::EASE_IN);
+ const int kAnimationDurationMS = 200;
+ animation_->SetSlideDuration(kAnimationDurationMS);
+ animation_->Show();
+}
+
+void PhantomWindowController::Hide() {
+ if (phantom_widget_)
+ phantom_widget_->Close();
+ phantom_widget_ = NULL;
+ if (phantom_widget_start_)
+ phantom_widget_start_->Close();
+ phantom_widget_start_ = NULL;
+}
+
+bool PhantomWindowController::IsShowing() const {
+ return phantom_widget_ != NULL;
+}
+
+void PhantomWindowController::AnimationProgressed(
+ const ui::Animation* animation) {
+ const gfx::Rect current_bounds =
+ animation->CurrentValueBetween(start_bounds_, bounds_in_screen_);
+ if (phantom_widget_start_)
+ phantom_widget_start_->SetBounds(current_bounds);
+ phantom_widget_->SetBounds(current_bounds);
+}
+
+views::Widget* PhantomWindowController::CreatePhantomWidget(
+ aura::RootWindow* root_window,
+ const gfx::Rect& bounds_in_screen) {
+ views::Widget* phantom_widget = new views::Widget;
+ views::Widget::InitParams params(views::Widget::InitParams::TYPE_POPUP);
+ params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW;
+ // PhantomWindowController is used by FrameMaximizeButton to highlight the
+ // launcher button. Put the phantom in the same window as the launcher so that
+ // the phantom is visible.
+ params.parent = Shell::GetContainer(root_window,
+ kShellWindowId_ShelfContainer);
+ params.can_activate = false;
+ params.keep_on_top = true;
+ phantom_widget->set_focus_on_creation(false);
+ phantom_widget->Init(params);
+ phantom_widget->SetVisibilityChangedAnimationsEnabled(false);
+ phantom_widget->GetNativeWindow()->SetName("PhantomWindow");
+ phantom_widget->GetNativeWindow()->set_id(kShellWindowId_PhantomWindow);
+ views::View* content_view = new views::View;
+ content_view->set_background(
+ views::Background::CreateBackgroundPainter(true, new EdgePainter));
+ phantom_widget->SetContentsView(content_view);
+ phantom_widget->SetBounds(bounds_in_screen);
+ if (phantom_below_window_)
+ phantom_widget->StackBelow(phantom_below_window_);
+ else
+ phantom_widget->StackAbove(window_);
+
+ // Show the widget after all the setups.
+ phantom_widget->Show();
+
+ // Fade the window in.
+ ui::Layer* widget_layer = phantom_widget->GetNativeWindow()->layer();
+ widget_layer->SetOpacity(0);
+ ui::ScopedLayerAnimationSettings scoped_setter(widget_layer->GetAnimator());
+ widget_layer->SetOpacity(1);
+ return phantom_widget;
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/wm/workspace/phantom_window_controller.h b/chromium/ash/wm/workspace/phantom_window_controller.h
new file mode 100644
index 00000000000..5cd40daa2cd
--- /dev/null
+++ b/chromium/ash/wm/workspace/phantom_window_controller.h
@@ -0,0 +1,104 @@
+// Copyright (c) 2012 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 ASH_WM_WORKSPACE_PHANTOM_WINDOW_CONTROLLER_H_
+#define ASH_WM_WORKSPACE_PHANTOM_WINDOW_CONTROLLER_H_
+
+#include "ash/ash_export.h"
+#include "base/basictypes.h"
+#include "base/gtest_prod_util.h"
+#include "base/memory/scoped_ptr.h"
+#include "ui/base/animation/animation_delegate.h"
+#include "ui/gfx/rect.h"
+
+namespace aura {
+class RootWindow;
+class Window;
+}
+
+namespace ui {
+class SlideAnimation;
+}
+
+namespace views {
+class Widget;
+}
+
+namespace ash {
+namespace internal {
+
+// PhantomWindowController is responsible for showing a phantom representation
+// of a window. It's used used during dragging a window to show a snap location.
+class ASH_EXPORT PhantomWindowController : public ui::AnimationDelegate {
+ public:
+ explicit PhantomWindowController(aura::Window* window);
+ virtual ~PhantomWindowController();
+
+ // Bounds last passed to Show().
+ const gfx::Rect& bounds_in_screen() const { return bounds_in_screen_; }
+
+ // Animates the phantom window towards |bounds_in_screen|.
+ // Creates two (if start bounds intersect any root window other than the
+ // root window that matches the target bounds) or one (otherwise) phantom
+ // widgets to display animated rectangle in each root.
+ // This does not immediately show the window.
+ void Show(const gfx::Rect& bounds_in_screen);
+
+ // Hides the phantom.
+ void Hide();
+
+ // Returns true if the phantom is showing.
+ bool IsShowing() const;
+
+ // If set, the phantom window is stacked below this window, otherwise it
+ // is stacked above the window passed to the constructor.
+ void set_phantom_below_window(aura::Window* phantom_below_window) {
+ phantom_below_window_ = phantom_below_window;
+ }
+
+ // ui::AnimationDelegate overrides:
+ virtual void AnimationProgressed(const ui::Animation* animation) OVERRIDE;
+
+ private:
+ FRIEND_TEST_ALL_PREFIXES(WorkspaceWindowResizerTest, PhantomWindowShow);
+
+ // Creates, shows and returns a phantom widget at |bounds|
+ // with kShellWindowId_ShelfContainer in |root_window| as a parent.
+ views::Widget* CreatePhantomWidget(aura::RootWindow* root_window,
+ const gfx::Rect& bounds_in_screen);
+
+ // Window the phantom is placed beneath.
+ aura::Window* window_;
+
+ // If set, the phantom window should get stacked below this window.
+ aura::Window* phantom_below_window_;
+
+ // Initially the bounds of |window_| (in screen coordinates).
+ // Each time Show() is invoked |start_bounds_| is then reset to the bounds of
+ // |phantom_widget_| and |bounds_| is set to the value passed into Show().
+ // The animation animates between these two values.
+ gfx::Rect start_bounds_;
+
+ // Target bounds of the animation in screen coordinates.
+ gfx::Rect bounds_in_screen_;
+
+ // The primary phantom representation of the window. It is parented by the
+ // root window matching the target bounds.
+ views::Widget* phantom_widget_;
+
+ // If the animation starts on another display, this is the secondary phantom
+ // representation of the window used on the initial display, otherwise this is
+ // NULL. This allows animation to progress from one display into the other.
+ views::Widget* phantom_widget_start_;
+
+ // Used to transition the bounds.
+ scoped_ptr<ui::SlideAnimation> animation_;
+
+ DISALLOW_COPY_AND_ASSIGN(PhantomWindowController);
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_WM_WORKSPACE_PHANTOM_WINDOW_CONTROLLER_H_
diff --git a/chromium/ash/wm/workspace/snap_sizer.cc b/chromium/ash/wm/workspace/snap_sizer.cc
new file mode 100644
index 00000000000..11b59470fcd
--- /dev/null
+++ b/chromium/ash/wm/workspace/snap_sizer.cc
@@ -0,0 +1,227 @@
+// Copyright (c) 2012 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.
+
+#include "ash/wm/workspace/snap_sizer.h"
+
+#include <cmath>
+
+#include "ash/screen_ash.h"
+#include "ash/wm/property_util.h"
+#include "ash/wm/window_resizer.h"
+#include "ash/wm/window_util.h"
+#include "ui/aura/window.h"
+#include "ui/gfx/screen.h"
+
+namespace ash {
+namespace internal {
+
+namespace {
+
+// A list of ideal window width in pixel which will be used to populate the
+// |usable_width_| list.
+const int kIdealWidth[] = { 1280, 1024, 768, 640 };
+
+// Windows are initially snapped to the size in |usable_width_| at index 0.
+// The index into |usable_width_| is changed if any of the following happen:
+// . The user stops moving the mouse for |kDelayBeforeIncreaseMS| and then
+// moves the mouse again.
+// . The mouse moves |kPixelsBeforeAdjust| horizontal pixels.
+// . The mouse is against the edge of the screen and the mouse is moved
+// |kMovesBeforeAdjust| times.
+const int kDelayBeforeIncreaseMS = 500;
+const int kMovesBeforeAdjust = 25;
+const int kPixelsBeforeAdjust = 100;
+
+// When the smallest resolution does not fit on the screen, we take this
+// fraction of the available space.
+const int kMinimumScreenPercent = 90;
+
+// Create the list of possible width for the current screen configuration:
+// Fill the |usable_width_| list with items from |kIdealWidth| which fit on
+// the screen and supplement it with the 'half of screen' size. Furthermore,
+// add an entry for 90% of the screen size if it is smaller then the biggest
+// value in the |kIdealWidth| list (to get a step between the values).
+std::vector<int> BuildIdealWidthList(aura::Window* window) {
+ std::vector<int> ideal_width_list;
+ gfx::Rect work_area(ScreenAsh::GetDisplayWorkAreaBoundsInParent(window));
+ int half_size = work_area.width() / 2;
+ int maximum_width = (kMinimumScreenPercent * work_area.width()) / 100;
+ for (size_t i = 0; i < arraysize(kIdealWidth); i++) {
+ if (maximum_width >= kIdealWidth[i]) {
+ if (i && !ideal_width_list.size() && maximum_width != kIdealWidth[i])
+ ideal_width_list.push_back(maximum_width);
+ if (half_size > kIdealWidth[i])
+ ideal_width_list.push_back(half_size);
+ if (half_size >= kIdealWidth[i])
+ half_size = 0;
+ ideal_width_list.push_back(kIdealWidth[i]);
+ }
+ }
+ if (half_size)
+ ideal_width_list.push_back(half_size);
+
+ return ideal_width_list;
+}
+
+} // namespace
+
+SnapSizer::SnapSizer(aura::Window* window,
+ const gfx::Point& start,
+ Edge edge,
+ InputType input_type)
+ : window_(window),
+ edge_(edge),
+ time_last_update_(base::TimeTicks::Now()),
+ size_index_(0),
+ resize_disabled_(false),
+ num_moves_since_adjust_(0),
+ last_adjust_x_(start.x()),
+ last_update_x_(start.x()),
+ start_x_(start.x()),
+ input_type_(input_type),
+ usable_width_(BuildIdealWidthList(window)) {
+ DCHECK(!usable_width_.empty());
+ target_bounds_ = GetTargetBounds();
+}
+
+SnapSizer::~SnapSizer() {
+}
+
+void SnapSizer::SnapWindow(aura::Window* window, SnapSizer::Edge edge) {
+ if (!wm::CanSnapWindow(window))
+ return;
+ internal::SnapSizer sizer(window, gfx::Point(), edge,
+ internal::SnapSizer::OTHER_INPUT);
+ if (wm::IsWindowFullscreen(window) || wm::IsWindowMaximized(window)) {
+ // Before we can set the bounds we need to restore the window.
+ // Restoring the window will set the window to its restored bounds.
+ // To avoid an unnecessary bounds changes (which may have side effects)
+ // we set the restore bounds to the bounds we want, restore the window,
+ // then reset the restore bounds. This way no unnecessary bounds
+ // changes occurs and the original restore bounds is remembered.
+ gfx::Rect restore = *GetRestoreBoundsInScreen(window);
+ SetRestoreBoundsInParent(window, sizer.GetSnapBounds(window->bounds()));
+ wm::RestoreWindow(window);
+ SetRestoreBoundsInScreen(window, restore);
+ } else {
+ window->SetBounds(sizer.GetSnapBounds(window->bounds()));
+ }
+}
+
+void SnapSizer::Update(const gfx::Point& location) {
+ // See description above for details on this behavior.
+ num_moves_since_adjust_++;
+ if ((base::TimeTicks::Now() - time_last_update_).InMilliseconds() >
+ kDelayBeforeIncreaseMS) {
+ ChangeBounds(location.x(),
+ CalculateIncrement(location.x(), last_update_x_));
+ } else {
+ bool along_edge = AlongEdge(location.x());
+ int pixels_before_adjust = kPixelsBeforeAdjust;
+ if (input_type_ == TOUCH_MAXIMIZE_BUTTON_INPUT) {
+ const gfx::Rect& workspace_bounds = window_->parent()->bounds();
+ if (start_x_ > location.x()) {
+ pixels_before_adjust =
+ std::min(pixels_before_adjust, start_x_ / 10);
+ } else {
+ pixels_before_adjust =
+ std::min(pixels_before_adjust,
+ (workspace_bounds.width() - start_x_) / 10);
+ }
+ }
+ if (std::abs(location.x() - last_adjust_x_) >= pixels_before_adjust ||
+ (along_edge && num_moves_since_adjust_ >= kMovesBeforeAdjust)) {
+ ChangeBounds(location.x(),
+ CalculateIncrement(location.x(), last_adjust_x_));
+ }
+ }
+ last_update_x_ = location.x();
+ time_last_update_ = base::TimeTicks::Now();
+}
+
+gfx::Rect SnapSizer::GetSnapBounds(const gfx::Rect& bounds) {
+ int current = 0;
+ if (!resize_disabled_) {
+ for (current = usable_width_.size() - 1; current >= 0; current--) {
+ gfx::Rect target = GetTargetBoundsForSize(current);
+ if (target == bounds) {
+ ++current;
+ break;
+ }
+ }
+ }
+ return GetTargetBoundsForSize(current % usable_width_.size());
+}
+
+void SnapSizer::SelectDefaultSizeAndDisableResize() {
+ resize_disabled_ = true;
+ size_index_ = 0;
+ target_bounds_ = GetTargetBounds();
+}
+
+gfx::Rect SnapSizer::GetTargetBoundsForSize(size_t size_index) const {
+ gfx::Rect work_area(ScreenAsh::GetDisplayWorkAreaBoundsInParent(window_));
+ int y = work_area.y();
+ // We don't align to the bottom of the grid as the launcher may not
+ // necessarily align to the grid (happens when auto-hidden).
+ int max_y = work_area.bottom();
+ int width = 0;
+ if (resize_disabled_) {
+ // Make sure that we keep the size of the window smaller then a certain
+ // fraction of the screen space.
+ int minimum_size = (kMinimumScreenPercent * work_area.width()) / 100;
+ width = std::max(std::min(minimum_size, 1024), work_area.width() / 2);
+ } else {
+ DCHECK(size_index < usable_width_.size());
+ width = usable_width_[size_index];
+ }
+
+ if (edge_ == LEFT_EDGE) {
+ int x = work_area.x();
+ int mid_x = x + width;
+ return gfx::Rect(x, y, mid_x - x, max_y - y);
+ }
+ int max_x = work_area.right();
+ int x = max_x - width;
+ return gfx::Rect(x , y, max_x - x, max_y - y);
+}
+
+int SnapSizer::CalculateIncrement(int x, int reference_x) const {
+ if (AlongEdge(x))
+ return 1;
+ if (x == reference_x)
+ return 0;
+ if (edge_ == LEFT_EDGE) {
+ if (x < reference_x)
+ return 1;
+ return -1;
+ }
+ // edge_ == RIGHT_EDGE.
+ if (x > reference_x)
+ return 1;
+ return -1;
+}
+
+void SnapSizer::ChangeBounds(int x, int delta) {
+ int index = std::min(static_cast<int>(usable_width_.size()) - 1,
+ std::max(size_index_ + delta, 0));
+ if (index != size_index_) {
+ size_index_ = index;
+ target_bounds_ = GetTargetBounds();
+ }
+ num_moves_since_adjust_ = 0;
+ last_adjust_x_ = x;
+}
+
+gfx::Rect SnapSizer::GetTargetBounds() const {
+ return GetTargetBoundsForSize(size_index_);
+}
+
+bool SnapSizer::AlongEdge(int x) const {
+ gfx::Rect area(ScreenAsh::GetDisplayBoundsInParent(window_));
+ return (x <= area.x()) || (x >= area.right() - 1);
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/wm/workspace/snap_sizer.h b/chromium/ash/wm/workspace/snap_sizer.h
new file mode 100644
index 00000000000..bd5f719ae48
--- /dev/null
+++ b/chromium/ash/wm/workspace/snap_sizer.h
@@ -0,0 +1,130 @@
+// Copyright (c) 2012 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 ASH_WM_WORKSPACE_SNAP_SIZER_H_
+#define ASH_WM_WORKSPACE_SNAP_SIZER_H_
+
+#include <vector>
+
+#include "ash/ash_export.h"
+#include "base/basictypes.h"
+#include "base/time/time.h"
+#include "ui/gfx/rect.h"
+
+namespace aura {
+class Window;
+}
+
+namespace ash {
+namespace internal {
+
+// SnapSizer is responsible for determining the resulting bounds of a window
+// that is being snapped to the left or right side of the screen.
+// The bounds used in this class are in the container's coordinates.
+class ASH_EXPORT SnapSizer {
+ public:
+ enum Edge {
+ LEFT_EDGE,
+ RIGHT_EDGE
+ };
+
+ enum InputType {
+ TOUCH_MAXIMIZE_BUTTON_INPUT,
+ OTHER_INPUT
+ };
+
+ // Set |input_type| to |TOUCH_MAXIMIZE_BUTTON_INPUT| when called by a touch
+ // operation by the maximize button. This will allow the user to snap resize
+ // the window beginning close to the border.
+ SnapSizer(aura::Window* window,
+ const gfx::Point& start,
+ Edge edge,
+ InputType input_type);
+ virtual ~SnapSizer();
+
+ // Snaps a window left or right.
+ static void SnapWindow(aura::Window* window, Edge edge);
+
+ // Updates the target bounds based on a mouse move.
+ void Update(const gfx::Point& location);
+
+ // Bounds to position the window at.
+ const gfx::Rect& target_bounds() const { return target_bounds_; }
+
+ // Returns the appropriate snap bounds (e.g. if a window is already snapped,
+ // then it returns the next snap-bounds).
+ gfx::Rect GetSnapBounds(const gfx::Rect& bounds);
+
+ // Set the snap sizer to the button press default size and prevent resizing.
+ void SelectDefaultSizeAndDisableResize();
+
+ // Returns the target bounds based on the edge and the provided |size_index|.
+ // For unit test purposes this function is not private.
+ gfx::Rect GetTargetBoundsForSize(size_t size_index) const;
+
+ private:
+ // Calculates the amount to increment by. This returns one of -1, 0 or 1 and
+ // is intended to by applied to |size_index_|. |x| is the current
+ // x-coordinate, and |reference_x| is used to determine whether to increase
+ // or decrease the position. It's one of |last_adjust_x_| or |last_update_x_|.
+ int CalculateIncrement(int x, int reference_x) const;
+
+ // Changes the bounds. |x| is the current x-coordinate and |delta| the amount
+ // to increase by. |delta| comes from CalculateIncrement() and is applied
+ // to |size_index_|.
+ void ChangeBounds(int x, int delta);
+
+ // Returns the target bounds based on the edge and |size_index_|.
+ gfx::Rect GetTargetBounds() const;
+
+ // Returns true if the specified point is along the edge of the screen.
+ bool AlongEdge(int x) const;
+
+ // Window being snapped.
+ aura::Window* window_;
+
+ const Edge edge_;
+
+ // Current target bounds for the snap.
+ gfx::Rect target_bounds_;
+
+ // Time Update() was last invoked.
+ base::TimeTicks time_last_update_;
+
+ // Index into |kSizes| that dictates the width of the screen the target
+ // bounds should get.
+ int size_index_;
+
+ // If set, |size_index_| will get ignored and the single button default
+ // setting will be used instead.
+ bool resize_disabled_;
+
+ // Number of times Update() has been invoked since last ChangeBounds().
+ int num_moves_since_adjust_;
+
+ // X-coordinate the last time ChangeBounds() was invoked.
+ int last_adjust_x_;
+
+ // X-coordinate last supplied to Update().
+ int last_update_x_;
+
+ // Initial x-coordinate.
+ const int start_x_;
+
+ // |TOUCH_MAXIMIZE_BUTTON_INPUT| if the snap sizer was created through a
+ // touch & drag operation of the maximizer button. It changes the behavior of
+ // the drag / resize behavior when the dragging starts close to the border.
+ const InputType input_type_;
+
+ // A list of usable window widths for size. This gets created when the
+ // sizer gets created.
+ const std::vector<int> usable_width_;
+
+ DISALLOW_COPY_AND_ASSIGN(SnapSizer);
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_WM_WORKSPACE_SNAP_SIZER_H_
diff --git a/chromium/ash/wm/workspace/snap_types.h b/chromium/ash/wm/workspace/snap_types.h
new file mode 100644
index 00000000000..482a7dd583b
--- /dev/null
+++ b/chromium/ash/wm/workspace/snap_types.h
@@ -0,0 +1,23 @@
+// Copyright (c) 2012 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 ASH_WM_WORKSPACE_SNAP_TYPES_H_
+#define ASH_WM_WORKSPACE_SNAP_TYPES_H_
+
+namespace ash {
+
+// These are the window snap types which can be used for window resizing.
+// Their main use case is the class FrameMaximizeButton.
+enum SnapType {
+ SNAP_LEFT,
+ SNAP_RIGHT,
+ SNAP_MAXIMIZE,
+ SNAP_MINIMIZE,
+ SNAP_RESTORE,
+ SNAP_NONE
+};
+
+} // namespace ash
+
+#endif // ASH_WM_WORKSPACE_SNAP_TYPES_H_
diff --git a/chromium/ash/wm/workspace/workspace_event_handler.cc b/chromium/ash/wm/workspace/workspace_event_handler.cc
new file mode 100644
index 00000000000..b46d3deff83
--- /dev/null
+++ b/chromium/ash/wm/workspace/workspace_event_handler.cc
@@ -0,0 +1,196 @@
+// Copyright (c) 2012 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.
+
+#include "ash/wm/workspace/workspace_event_handler.h"
+
+#include "ash/screen_ash.h"
+#include "ash/shell.h"
+#include "ash/shell_delegate.h"
+#include "ash/touch/touch_uma.h"
+#include "ash/wm/coordinate_conversion.h"
+#include "ash/wm/property_util.h"
+#include "ash/wm/window_util.h"
+#include "ash/wm/workspace/workspace_window_resizer.h"
+#include "ui/aura/client/aura_constants.h"
+#include "ui/aura/window.h"
+#include "ui/aura/window_delegate.h"
+#include "ui/base/events/event.h"
+#include "ui/base/events/event_utils.h"
+#include "ui/base/hit_test.h"
+#include "ui/compositor/scoped_layer_animation_settings.h"
+#include "ui/gfx/screen.h"
+
+namespace ash {
+namespace {
+
+void SingleAxisMaximize(aura::Window* window,
+ const gfx::Rect& maximize_rect_in_screen) {
+ gfx::Rect bounds_in_screen =
+ ScreenAsh::ConvertRectToScreen(window->parent(), window->bounds());
+ SetRestoreBoundsInScreen(window, bounds_in_screen);
+ gfx::Rect bounds_in_parent =
+ ScreenAsh::ConvertRectFromScreen(window->parent(),
+ maximize_rect_in_screen);
+ window->SetBounds(bounds_in_parent);
+}
+
+void SingleAxisUnmaximize(aura::Window* window,
+ const gfx::Rect& restore_bounds_in_screen) {
+ gfx::Rect restore_bounds = ScreenAsh::ConvertRectFromScreen(
+ window->parent(), restore_bounds_in_screen);
+ window->SetBounds(restore_bounds);
+ ClearRestoreBounds(window);
+}
+
+void ToggleMaximizedState(aura::Window* window) {
+ if (GetRestoreBoundsInScreen(window)) {
+ if (window->GetProperty(aura::client::kShowStateKey) ==
+ ui::SHOW_STATE_NORMAL) {
+ window->SetBounds(GetRestoreBoundsInParent(window));
+ ClearRestoreBounds(window);
+ } else {
+ window->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_NORMAL);
+ }
+ } else if (wm::CanMaximizeWindow(window)) {
+ window->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MAXIMIZED);
+ }
+}
+
+} // namespace
+
+namespace internal {
+
+WorkspaceEventHandler::WorkspaceEventHandler(aura::Window* owner)
+ : ToplevelWindowEventHandler(owner),
+ destroyed_(NULL) {
+}
+
+WorkspaceEventHandler::~WorkspaceEventHandler() {
+ if (destroyed_)
+ *destroyed_ = true;
+}
+
+void WorkspaceEventHandler::OnMouseEvent(ui::MouseEvent* event) {
+ aura::Window* target = static_cast<aura::Window*>(event->target());
+ switch (event->type()) {
+ case ui::ET_MOUSE_MOVED: {
+ int component =
+ target->delegate()->GetNonClientComponent(event->location());
+ multi_window_resize_controller_.Show(target, component,
+ event->location());
+ break;
+ }
+ case ui::ET_MOUSE_ENTERED:
+ break;
+ case ui::ET_MOUSE_CAPTURE_CHANGED:
+ case ui::ET_MOUSE_EXITED:
+ break;
+ case ui::ET_MOUSE_PRESSED: {
+ // Maximize behavior is implemented as post-target handling so the target
+ // can cancel it.
+ if (ui::EventCanceledDefaultHandling(*event)) {
+ ToplevelWindowEventHandler::OnMouseEvent(event);
+ return;
+ }
+
+ if (event->flags() & ui::EF_IS_DOUBLE_CLICK &&
+ event->IsOnlyLeftMouseButton() &&
+ target->delegate()->GetNonClientComponent(event->location()) ==
+ HTCAPTION &&
+ !ash::Shell::IsForcedMaximizeMode()) {
+ bool destroyed = false;
+ destroyed_ = &destroyed;
+ ash::Shell::GetInstance()->delegate()->RecordUserMetricsAction(
+ ash::UMA_TOGGLE_MAXIMIZE_CAPTION_CLICK);
+ ToggleMaximizedState(target);
+ if (destroyed)
+ return;
+ destroyed_ = NULL;
+ }
+ multi_window_resize_controller_.Hide();
+ HandleVerticalResizeDoubleClick(target, event);
+ break;
+ }
+ default:
+ break;
+ }
+ ToplevelWindowEventHandler::OnMouseEvent(event);
+}
+
+void WorkspaceEventHandler::OnGestureEvent(ui::GestureEvent* event) {
+ aura::Window* target = static_cast<aura::Window*>(event->target());
+ if (event->type() == ui::ET_GESTURE_TAP &&
+ target->delegate()->GetNonClientComponent(event->location()) ==
+ HTCAPTION) {
+ if (event->details().tap_count() == 2) {
+ ash::Shell::GetInstance()->delegate()->RecordUserMetricsAction(
+ ash::UMA_TOGGLE_MAXIMIZE_CAPTION_GESTURE);
+ // Note: TouchUMA::GESTURE_FRAMEVIEW_TAP is counted twice each time
+ // TouchUMA::GESTURE_MAXIMIZE_DOUBLETAP is counted once.
+ TouchUMA::GetInstance()->RecordGestureAction(
+ TouchUMA::GESTURE_MAXIMIZE_DOUBLETAP);
+ ToggleMaximizedState(target); // |this| may be destroyed from here.
+ event->StopPropagation();
+ return;
+ } else {
+ // Note: TouchUMA::GESTURE_FRAMEVIEW_TAP is counted twice for each tap.
+ TouchUMA::GetInstance()->RecordGestureAction(
+ TouchUMA::GESTURE_FRAMEVIEW_TAP);
+ }
+ }
+ ToplevelWindowEventHandler::OnGestureEvent(event);
+}
+
+void WorkspaceEventHandler::HandleVerticalResizeDoubleClick(
+ aura::Window* target,
+ ui::MouseEvent* event) {
+ gfx::Rect max_size(target->delegate()->GetMaximumSize());
+ if (event->flags() & ui::EF_IS_DOUBLE_CLICK &&
+ !wm::IsWindowMaximized(target)) {
+ int component =
+ target->delegate()->GetNonClientComponent(event->location());
+ gfx::Rect work_area =
+ Shell::GetScreen()->GetDisplayNearestWindow(target).work_area();
+ const gfx::Rect* restore_bounds =
+ GetRestoreBoundsInScreen(target);
+ if (component == HTBOTTOM || component == HTTOP) {
+ // Don't maximize vertically if the window has a max height defined.
+ if (max_size.height() != 0)
+ return;
+ if (restore_bounds &&
+ (target->bounds().height() == work_area.height() &&
+ target->bounds().y() == work_area.y())) {
+ SingleAxisUnmaximize(target, *restore_bounds);
+ } else {
+ gfx::Point origin = target->bounds().origin();
+ wm::ConvertPointToScreen(target->parent(), &origin);
+ SingleAxisMaximize(target,
+ gfx::Rect(origin.x(),
+ work_area.y(),
+ target->bounds().width(),
+ work_area.height()));
+ }
+ } else if (component == HTLEFT || component == HTRIGHT) {
+ // Don't maximize horizontally if the window has a max width defined.
+ if (max_size.width() != 0)
+ return;
+ if (restore_bounds &&
+ (target->bounds().width() == work_area.width() &&
+ target->bounds().x() == work_area.x())) {
+ SingleAxisUnmaximize(target, *restore_bounds);
+ } else {
+ gfx::Point origin = target->bounds().origin();
+ wm::ConvertPointToScreen(target->parent(), &origin);
+ SingleAxisMaximize(target,
+ gfx::Rect(work_area.x(),
+ origin.y(),
+ work_area.width(),
+ target->bounds().height()));
+ }
+ }
+ }
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/wm/workspace/workspace_event_handler.h b/chromium/ash/wm/workspace/workspace_event_handler.h
new file mode 100644
index 00000000000..f175408c518
--- /dev/null
+++ b/chromium/ash/wm/workspace/workspace_event_handler.h
@@ -0,0 +1,50 @@
+// Copyright (c) 2012 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 ASH_WM_WORKSPACE_WORKSPACE_EVENT_HANDLER_H_
+#define ASH_WM_WORKSPACE_WORKSPACE_EVENT_HANDLER_H_
+
+#include "ash/wm/toplevel_window_event_handler.h"
+#include "ash/wm/workspace/multi_window_resize_controller.h"
+
+namespace aura {
+class Window;
+}
+
+namespace ash {
+namespace internal {
+
+class WorkspaceEventHandlerTestHelper;
+
+class WorkspaceEventHandler : public ToplevelWindowEventHandler {
+ public:
+ explicit WorkspaceEventHandler(aura::Window* owner);
+ virtual ~WorkspaceEventHandler();
+
+ // Overridden from ToplevelWindowEventHandler:
+ virtual void OnMouseEvent(ui::MouseEvent* event) OVERRIDE;
+ virtual void OnGestureEvent(ui::GestureEvent* event) OVERRIDE;
+
+ private:
+ friend class WorkspaceEventHandlerTestHelper;
+
+ // Determines if |event| corresponds to a double click on either the top or
+ // bottom vertical resize edge, and if so toggles the vertical height of the
+ // window between its restored state and the full available height of the
+ // workspace.
+ void HandleVerticalResizeDoubleClick(aura::Window* target,
+ ui::MouseEvent* event);
+
+ MultiWindowResizeController multi_window_resize_controller_;
+
+ // If non-NULL, set to true in the destructor.
+ bool* destroyed_;
+
+ DISALLOW_COPY_AND_ASSIGN(WorkspaceEventHandler);
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_WM_WORKSPACE_WORKSPACE_EVENT_HANDLER_H_
diff --git a/chromium/ash/wm/workspace/workspace_event_handler_test_helper.cc b/chromium/ash/wm/workspace/workspace_event_handler_test_helper.cc
new file mode 100644
index 00000000000..55b707b4402
--- /dev/null
+++ b/chromium/ash/wm/workspace/workspace_event_handler_test_helper.cc
@@ -0,0 +1,19 @@
+// Copyright (c) 2012 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.
+
+#include "ash/wm/workspace/workspace_event_handler_test_helper.h"
+
+namespace ash {
+namespace internal {
+
+WorkspaceEventHandlerTestHelper::WorkspaceEventHandlerTestHelper(
+ WorkspaceEventHandler* handler)
+ : handler_(handler) {
+}
+
+WorkspaceEventHandlerTestHelper::~WorkspaceEventHandlerTestHelper() {
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/wm/workspace/workspace_event_handler_test_helper.h b/chromium/ash/wm/workspace/workspace_event_handler_test_helper.h
new file mode 100644
index 00000000000..21e1adb981b
--- /dev/null
+++ b/chromium/ash/wm/workspace/workspace_event_handler_test_helper.h
@@ -0,0 +1,31 @@
+// Copyright (c) 2012 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 ASH_WM_WORKSPACE_WORKSPACE_EVENT_HANDLER_TEST_HELPER_H_
+#define ASH_WM_WORKSPACE_WORKSPACE_EVENT_HANDLER_TEST_HELPER_H_
+
+#include "ash/wm/workspace/workspace_event_handler.h"
+
+namespace ash {
+namespace internal {
+
+class WorkspaceEventHandlerTestHelper {
+ public:
+ explicit WorkspaceEventHandlerTestHelper(WorkspaceEventHandler* handler);
+ ~WorkspaceEventHandlerTestHelper();
+
+ MultiWindowResizeController* resize_controller() {
+ return &(handler_->multi_window_resize_controller_);
+ }
+
+ private:
+ WorkspaceEventHandler* handler_;
+
+ DISALLOW_COPY_AND_ASSIGN(WorkspaceEventHandlerTestHelper);
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_WM_WORKSPACE_WORKSPACE_EVENT_HANDLER_TEST_HELPER_H_
diff --git a/chromium/ash/wm/workspace/workspace_event_handler_unittest.cc b/chromium/ash/wm/workspace/workspace_event_handler_unittest.cc
new file mode 100644
index 00000000000..9f4d326ed59
--- /dev/null
+++ b/chromium/ash/wm/workspace/workspace_event_handler_unittest.cc
@@ -0,0 +1,341 @@
+// Copyright (c) 2012 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.
+
+#include "ash/wm/workspace/workspace_event_handler.h"
+
+#include "ash/screen_ash.h"
+#include "ash/shell.h"
+#include "ash/test/ash_test_base.h"
+#include "ash/wm/property_util.h"
+#include "ash/wm/window_util.h"
+#include "ash/wm/workspace_controller.h"
+#include "ash/wm/workspace_controller_test_helper.h"
+#include "ui/aura/client/aura_constants.h"
+#include "ui/aura/root_window.h"
+#include "ui/aura/test/event_generator.h"
+#include "ui/aura/test/test_window_delegate.h"
+#include "ui/aura/window.h"
+#include "ui/base/hit_test.h"
+#include "ui/gfx/screen.h"
+
+#if defined(OS_WIN)
+#include "base/win/windows_version.h"
+#endif
+
+namespace ash {
+namespace internal {
+
+class WorkspaceEventHandlerTest : public test::AshTestBase {
+ public:
+ WorkspaceEventHandlerTest() {}
+ virtual ~WorkspaceEventHandlerTest() {}
+
+ protected:
+ aura::Window* CreateTestWindow(aura::WindowDelegate* delegate,
+ const gfx::Rect& bounds) {
+ aura::Window* window = new aura::Window(delegate);
+ window->SetType(aura::client::WINDOW_TYPE_NORMAL);
+ window->Init(ui::LAYER_TEXTURED);
+ SetDefaultParentByPrimaryRootWindow(window);
+ window->SetBounds(bounds);
+ window->Show();
+ return window;
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(WorkspaceEventHandlerTest);
+};
+
+// Keeps track of the properties changed of a particular window.
+class WindowPropertyObserver : public aura::WindowObserver {
+ public:
+ explicit WindowPropertyObserver(aura::Window* window)
+ : window_(window) {
+ window->AddObserver(this);
+ }
+
+ virtual ~WindowPropertyObserver() {
+ window_->RemoveObserver(this);
+ }
+
+ bool DidPropertyChange(const void* property) const {
+ return std::find(properties_changed_.begin(),
+ properties_changed_.end(),
+ property) != properties_changed_.end();
+ }
+
+ private:
+ virtual void OnWindowPropertyChanged(aura::Window* window,
+ const void* key,
+ intptr_t old) OVERRIDE {
+ properties_changed_.push_back(key);
+ }
+
+ aura::Window* window_;
+ std::vector<const void*> properties_changed_;
+
+ DISALLOW_COPY_AND_ASSIGN(WindowPropertyObserver);
+};
+
+TEST_F(WorkspaceEventHandlerTest, DoubleClickSingleAxisResizeEdge) {
+ // Double clicking the vertical resize edge of a window should maximize it
+ // vertically.
+ gfx::Rect restored_bounds(10, 10, 50, 50);
+ aura::test::TestWindowDelegate wd;
+ scoped_ptr<aura::Window> window(CreateTestWindow(&wd, restored_bounds));
+
+ wm::ActivateWindow(window.get());
+
+ gfx::Rect work_area = Shell::GetScreen()->GetDisplayNearestWindow(
+ window.get()).work_area();
+
+ aura::test::EventGenerator generator(Shell::GetPrimaryRootWindow(),
+ window.get());
+
+ // Double-click the top resize edge.
+ wd.set_window_component(HTTOP);
+ // On X a double click actually generates a drag between each press/release.
+ // Explicitly trigger this path since we had bugs in dealing with it
+ // correctly.
+ generator.PressLeftButton();
+ generator.ReleaseLeftButton();
+ generator.set_flags(ui::EF_IS_DOUBLE_CLICK);
+ generator.PressLeftButton();
+ generator.MoveMouseTo(generator.current_location(), 1);
+ generator.ReleaseLeftButton();
+ gfx::Rect bounds_in_screen = window->GetBoundsInScreen();
+ EXPECT_EQ(restored_bounds.x(), bounds_in_screen.x());
+ EXPECT_EQ(restored_bounds.width(), bounds_in_screen.width());
+ EXPECT_EQ(work_area.y(), bounds_in_screen.y());
+ EXPECT_EQ(work_area.height(), bounds_in_screen.height());
+ // Single-axis maximization is not considered real maximization.
+ EXPECT_FALSE(wm::IsWindowMaximized(window.get()));
+
+ // Restore.
+ generator.DoubleClickLeftButton();
+ bounds_in_screen = window->GetBoundsInScreen();
+ EXPECT_EQ(restored_bounds.ToString(), bounds_in_screen.ToString());
+ // Note that it should not even be restored at this point, it should have
+ // also cleared the restore rectangle.
+ EXPECT_EQ(NULL, GetRestoreBoundsInScreen(window.get()));
+
+ // Double-click the top resize edge again to maximize vertically, then double
+ // click again to restore.
+ generator.DoubleClickLeftButton();
+ wd.set_window_component(HTCAPTION);
+ generator.DoubleClickLeftButton();
+ EXPECT_FALSE(wm::IsWindowMaximized(window.get()));
+ bounds_in_screen = window->GetBoundsInScreen();
+ EXPECT_EQ(restored_bounds.ToString(), bounds_in_screen.ToString());
+
+ // Double clicking the left resize edge should maximize horizontally.
+ wd.set_window_component(HTLEFT);
+ generator.DoubleClickLeftButton();
+ bounds_in_screen = window->GetBoundsInScreen();
+ EXPECT_EQ(restored_bounds.y(), bounds_in_screen.y());
+ EXPECT_EQ(restored_bounds.height(), bounds_in_screen.height());
+ EXPECT_EQ(work_area.x(), bounds_in_screen.x());
+ EXPECT_EQ(work_area.width(), bounds_in_screen.width());
+ // Single-axis maximization is not considered real maximization.
+ EXPECT_FALSE(wm::IsWindowMaximized(window.get()));
+
+ // Restore.
+ wd.set_window_component(HTCAPTION);
+ generator.DoubleClickLeftButton();
+ EXPECT_EQ(restored_bounds.ToString(), window->GetBoundsInScreen().ToString());
+
+#if defined(OS_WIN)
+ // Multi display test does not run on Win8 bot. crbug.com/247427.
+ if (base::win::GetVersion() >= base::win::VERSION_WIN8)
+ return;
+#endif
+
+ // Verify the double clicking the resize edge works on 2nd display too.
+ UpdateDisplay("200x200,400x300");
+ gfx::Rect work_area2 = ScreenAsh::GetSecondaryDisplay().work_area();
+ restored_bounds.SetRect(220,20, 50, 50);
+ window->SetBoundsInScreen(restored_bounds, ScreenAsh::GetSecondaryDisplay());
+ aura::RootWindow* second_root = Shell::GetAllRootWindows()[1];
+ EXPECT_EQ(second_root, window->GetRootWindow());
+ aura::test::EventGenerator generator2(second_root, window.get());
+
+ // Y-axis maximization.
+ wd.set_window_component(HTTOP);
+ generator2.PressLeftButton();
+ generator2.ReleaseLeftButton();
+ generator2.set_flags(ui::EF_IS_DOUBLE_CLICK);
+ generator2.PressLeftButton();
+ generator2.MoveMouseTo(generator.current_location(), 1);
+ generator2.ReleaseLeftButton();
+ generator.DoubleClickLeftButton();
+ bounds_in_screen = window->GetBoundsInScreen();
+ EXPECT_EQ(restored_bounds.x(), bounds_in_screen.x());
+ EXPECT_EQ(restored_bounds.width(), bounds_in_screen.width());
+ EXPECT_EQ(work_area2.y(), bounds_in_screen.y());
+ EXPECT_EQ(work_area2.height(), bounds_in_screen.height());
+ EXPECT_FALSE(wm::IsWindowMaximized(window.get()));
+
+ // Restore.
+ wd.set_window_component(HTCAPTION);
+ generator2.DoubleClickLeftButton();
+ EXPECT_EQ(restored_bounds.ToString(), window->GetBoundsInScreen().ToString());
+
+ // X-axis maximization.
+ wd.set_window_component(HTLEFT);
+ generator2.DoubleClickLeftButton();
+ bounds_in_screen = window->GetBoundsInScreen();
+ EXPECT_EQ(restored_bounds.y(), bounds_in_screen.y());
+ EXPECT_EQ(restored_bounds.height(), bounds_in_screen.height());
+ EXPECT_EQ(work_area2.x(), bounds_in_screen.x());
+ EXPECT_EQ(work_area2.width(), bounds_in_screen.width());
+ EXPECT_FALSE(wm::IsWindowMaximized(window.get()));
+
+ // Restore.
+ wd.set_window_component(HTCAPTION);
+ generator2.DoubleClickLeftButton();
+ EXPECT_EQ(restored_bounds.ToString(), window->GetBoundsInScreen().ToString());
+}
+
+TEST_F(WorkspaceEventHandlerTest,
+ DoubleClickSingleAxisDoesntResizeVerticalEdgeIfConstrained) {
+ gfx::Rect restored_bounds(10, 10, 50, 50);
+ aura::test::TestWindowDelegate wd;
+ scoped_ptr<aura::Window> window(CreateTestWindow(&wd, restored_bounds));
+
+ wm::ActivateWindow(window.get());
+
+ gfx::Rect work_area = Shell::GetScreen()->GetDisplayNearestWindow(
+ window.get()).work_area();
+
+ wd.set_maximum_size(gfx::Size(0, 100));
+
+ aura::test::EventGenerator generator(Shell::GetPrimaryRootWindow(),
+ window.get());
+ // Double-click the top resize edge.
+ wd.set_window_component(HTTOP);
+ generator.DoubleClickLeftButton();
+
+ // The size of the window should be unchanged.
+ EXPECT_EQ(restored_bounds.y(), window->bounds().y());
+ EXPECT_EQ(restored_bounds.height(), window->bounds().height());
+}
+
+TEST_F(WorkspaceEventHandlerTest,
+ DoubleClickSingleAxisDoesntResizeHorizontalEdgeIfConstrained) {
+ gfx::Rect restored_bounds(10, 10, 50, 50);
+ aura::test::TestWindowDelegate wd;
+ scoped_ptr<aura::Window> window(CreateTestWindow(&wd, restored_bounds));
+
+ wm::ActivateWindow(window.get());
+
+ gfx::Rect work_area = Shell::GetScreen()->GetDisplayNearestWindow(
+ window.get()).work_area();
+
+ wd.set_maximum_size(gfx::Size(100, 0));
+
+ aura::test::EventGenerator generator(Shell::GetPrimaryRootWindow(),
+ window.get());
+ // Double-click the top resize edge.
+ wd.set_window_component(HTRIGHT);
+ generator.DoubleClickLeftButton();
+
+ // The size of the window should be unchanged.
+ EXPECT_EQ(restored_bounds.x(), window->bounds().x());
+ EXPECT_EQ(restored_bounds.width(), window->bounds().width());
+}
+
+TEST_F(WorkspaceEventHandlerTest, DoubleClickCaptionTogglesMaximize) {
+ aura::test::TestWindowDelegate wd;
+ scoped_ptr<aura::Window> window(
+ CreateTestWindow(&wd, gfx::Rect(1, 2, 30, 40)));
+ window->SetProperty(aura::client::kCanMaximizeKey, true);
+ wd.set_window_component(HTCAPTION);
+ EXPECT_FALSE(wm::IsWindowMaximized(window.get()));
+ aura::RootWindow* root = Shell::GetPrimaryRootWindow();
+ aura::test::EventGenerator generator(root, window.get());
+ generator.DoubleClickLeftButton();
+ EXPECT_NE("1,2 30x40", window->bounds().ToString());
+
+ EXPECT_TRUE(wm::IsWindowMaximized(window.get()));
+ generator.DoubleClickLeftButton();
+
+ EXPECT_FALSE(wm::IsWindowMaximized(window.get()));
+ EXPECT_EQ("1,2 30x40", window->bounds().ToString());
+
+ // Double-clicking the middle button shouldn't toggle the maximized state.
+ WindowPropertyObserver observer(window.get());
+ ui::MouseEvent press(ui::ET_MOUSE_PRESSED, generator.current_location(),
+ generator.current_location(),
+ ui::EF_MIDDLE_MOUSE_BUTTON | ui::EF_IS_DOUBLE_CLICK);
+ root->AsRootWindowHostDelegate()->OnHostMouseEvent(&press);
+ ui::MouseEvent release(ui::ET_MOUSE_RELEASED, generator.current_location(),
+ generator.current_location(),
+ ui::EF_IS_DOUBLE_CLICK);
+ root->AsRootWindowHostDelegate()->OnHostMouseEvent(&release);
+
+ EXPECT_FALSE(wm::IsWindowMaximized(window.get()));
+ EXPECT_EQ("1,2 30x40", window->bounds().ToString());
+ EXPECT_FALSE(observer.DidPropertyChange(aura::client::kShowStateKey));
+}
+
+TEST_F(WorkspaceEventHandlerTest, DoubleTapCaptionTogglesMaximize) {
+ aura::test::TestWindowDelegate wd;
+ gfx::Rect bounds(10, 20, 30, 40);
+ scoped_ptr<aura::Window> window(CreateTestWindow(&wd, bounds));
+ window->SetProperty(aura::client::kCanMaximizeKey, true);
+ wd.set_window_component(HTCAPTION);
+ EXPECT_FALSE(wm::IsWindowMaximized(window.get()));
+ aura::test::EventGenerator generator(Shell::GetPrimaryRootWindow(),
+ window.get());
+ generator.GestureTapAt(gfx::Point(25, 25));
+ generator.GestureTapAt(gfx::Point(25, 25));
+ RunAllPendingInMessageLoop();
+ EXPECT_NE(bounds.ToString(), window->bounds().ToString());
+ EXPECT_TRUE(wm::IsWindowMaximized(window.get()));
+
+ generator.GestureTapAt(gfx::Point(5, 5));
+ generator.GestureTapAt(gfx::Point(10, 10));
+
+ EXPECT_FALSE(wm::IsWindowMaximized(window.get()));
+ EXPECT_EQ(bounds.ToString(), window->bounds().ToString());
+}
+
+// Verifies deleting the window while dragging doesn't crash.
+TEST_F(WorkspaceEventHandlerTest, DeleteWhenDragging) {
+ // Create a large window in the background. This is necessary so that when we
+ // delete |window| WorkspaceEventHandler is still the active event handler.
+ aura::test::TestWindowDelegate wd2;
+ scoped_ptr<aura::Window> window2(
+ CreateTestWindow(&wd2, gfx::Rect(0, 0, 500, 500)));
+
+ aura::test::TestWindowDelegate wd;
+ const gfx::Rect bounds(10, 20, 30, 40);
+ scoped_ptr<aura::Window> window(CreateTestWindow(&wd, bounds));
+ wd.set_window_component(HTCAPTION);
+ aura::test::EventGenerator generator(window->GetRootWindow());
+ generator.MoveMouseToCenterOf(window.get());
+ generator.PressLeftButton();
+ generator.MoveMouseTo(generator.current_location() + gfx::Vector2d(50, 50));
+ DCHECK_NE(bounds.origin().ToString(), window->bounds().origin().ToString());
+ window.reset();
+ generator.MoveMouseTo(generator.current_location() + gfx::Vector2d(50, 50));
+}
+
+// Verifies deleting the window while in a run loop doesn't crash.
+TEST_F(WorkspaceEventHandlerTest, DeleteWhileInRunLoop) {
+ aura::test::TestWindowDelegate wd;
+ const gfx::Rect bounds(10, 20, 30, 40);
+ scoped_ptr<aura::Window> window(CreateTestWindow(&wd, bounds));
+ wd.set_window_component(HTCAPTION);
+
+ ASSERT_TRUE(aura::client::GetWindowMoveClient(window->parent()));
+ base::MessageLoop::current()->DeleteSoon(FROM_HERE, window.get());
+ aura::client::GetWindowMoveClient(window->parent())
+ ->RunMoveLoop(window.release(),
+ gfx::Vector2d(),
+ aura::client::WINDOW_MOVE_SOURCE_MOUSE);
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/wm/workspace/workspace_layout_manager.cc b/chromium/ash/wm/workspace/workspace_layout_manager.cc
new file mode 100644
index 00000000000..69bcc16069a
--- /dev/null
+++ b/chromium/ash/wm/workspace/workspace_layout_manager.cc
@@ -0,0 +1,354 @@
+// Copyright (c) 2012 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.
+
+#include "ash/wm/workspace/workspace_layout_manager.h"
+
+#include "ash/display/display_controller.h"
+#include "ash/root_window_controller.h"
+#include "ash/screen_ash.h"
+#include "ash/shelf/shelf_layout_manager.h"
+#include "ash/shell.h"
+#include "ash/wm/always_on_top_controller.h"
+#include "ash/wm/base_layout_manager.h"
+#include "ash/wm/frame_painter.h"
+#include "ash/wm/property_util.h"
+#include "ash/wm/window_animations.h"
+#include "ash/wm/window_properties.h"
+#include "ash/wm/window_util.h"
+#include "ash/wm/workspace/auto_window_management.h"
+#include "ui/aura/client/aura_constants.h"
+#include "ui/aura/root_window.h"
+#include "ui/aura/window.h"
+#include "ui/aura/window_observer.h"
+#include "ui/base/events/event.h"
+#include "ui/base/ui_base_types.h"
+#include "ui/views/corewm/window_util.h"
+
+using aura::Window;
+
+namespace ash {
+
+namespace internal {
+
+namespace {
+
+// This specifies how much percent 30% of a window rect (width / height)
+// must be visible when the window is added to the workspace.
+const float kMinimumPercentOnScreenArea = 0.3f;
+
+bool IsMaximizedState(ui::WindowShowState state) {
+ return state == ui::SHOW_STATE_MAXIMIZED ||
+ state == ui::SHOW_STATE_FULLSCREEN;
+}
+
+void MoveToDisplayForRestore(aura::Window* window) {
+ const gfx::Rect* restore_bounds = GetRestoreBoundsInScreen(window);
+ if (!restore_bounds)
+ return;
+
+ // Move only if the restore bounds is outside of
+ // the root window. There is no information about in which
+ // display it should be restored, so this is best guess.
+ // TODO(oshima): Restore information should contain the
+ // work area information like WindowResizer does for the
+ // last window location.
+ if (!window->GetRootWindow()->GetBoundsInScreen().Intersects(
+ *restore_bounds)) {
+ DisplayController* display_controller =
+ Shell::GetInstance()->display_controller();
+ const gfx::Display& display =
+ display_controller->GetDisplayMatching(*restore_bounds);
+ aura::RootWindow* new_root =
+ display_controller->GetRootWindowForDisplayId(display.id());
+ if (new_root != window->GetRootWindow()) {
+ aura::Window* new_container =
+ Shell::GetContainer(new_root, window->parent()->id());
+ new_container->AddChild(window);
+ }
+ }
+}
+
+} // namespace
+
+WorkspaceLayoutManager::WorkspaceLayoutManager(aura::Window* window)
+ : BaseLayoutManager(window->GetRootWindow()),
+ shelf_(NULL),
+ window_(window),
+ work_area_(ScreenAsh::GetDisplayWorkAreaBoundsInParent(
+ window->parent())) {
+}
+
+WorkspaceLayoutManager::~WorkspaceLayoutManager() {
+}
+
+void WorkspaceLayoutManager::SetShelf(internal::ShelfLayoutManager* shelf) {
+ shelf_ = shelf;
+}
+
+void WorkspaceLayoutManager::OnWindowAddedToLayout(Window* child) {
+ // Adjust window bounds in case that the new child is given the bounds that
+ // is out of the workspace. Exclude the case where bounds is empty
+ // (this happens when a views::Widget is created), or the window
+ // is added with the bounds because a user explicitly moved to
+ // this position (drag and drop for example).
+ if (!child->bounds().IsEmpty() &&
+ !wm::HasUserChangedWindowPositionOrSize(child))
+ AdjustWindowBoundsWhenAdded(child);
+ BaseLayoutManager::OnWindowAddedToLayout(child);
+ UpdateDesktopVisibility();
+ RearrangeVisibleWindowOnShow(child);
+}
+
+void WorkspaceLayoutManager::OnWillRemoveWindowFromLayout(Window* child) {
+ BaseLayoutManager::OnWillRemoveWindowFromLayout(child);
+ if (child->TargetVisibility())
+ RearrangeVisibleWindowOnHideOrRemove(child);
+}
+
+void WorkspaceLayoutManager::OnWindowRemovedFromLayout(Window* child) {
+ BaseLayoutManager::OnWindowRemovedFromLayout(child);
+ UpdateDesktopVisibility();
+}
+
+void WorkspaceLayoutManager::OnChildWindowVisibilityChanged(Window* child,
+ bool visible) {
+ BaseLayoutManager::OnChildWindowVisibilityChanged(child, visible);
+ if (child->TargetVisibility())
+ RearrangeVisibleWindowOnShow(child);
+ else
+ RearrangeVisibleWindowOnHideOrRemove(child);
+ UpdateDesktopVisibility();
+}
+
+void WorkspaceLayoutManager::SetChildBounds(
+ Window* child,
+ const gfx::Rect& requested_bounds) {
+ if (!GetTrackedByWorkspace(child)) {
+ SetChildBoundsDirect(child, requested_bounds);
+ return;
+ }
+ gfx::Rect child_bounds(requested_bounds);
+ // Some windows rely on this to set their initial bounds.
+ if (!SetMaximizedOrFullscreenBounds(child)) {
+ // Non-maximized/full-screen windows have their size constrained to the
+ // work-area.
+ child_bounds.set_width(std::min(work_area_.width(), child_bounds.width()));
+ child_bounds.set_height(
+ std::min(work_area_.height(), child_bounds.height()));
+ SetChildBoundsDirect(child, child_bounds);
+ }
+ UpdateDesktopVisibility();
+}
+
+void WorkspaceLayoutManager::OnDisplayWorkAreaInsetsChanged() {
+ const gfx::Rect work_area(ScreenAsh::GetDisplayWorkAreaBoundsInParent(
+ window_->parent()));
+ if (work_area != work_area_) {
+ AdjustAllWindowsBoundsForWorkAreaChange(
+ ADJUST_WINDOW_WORK_AREA_INSETS_CHANGED);
+ }
+}
+
+void WorkspaceLayoutManager::OnWindowPropertyChanged(Window* window,
+ const void* key,
+ intptr_t old) {
+ if (key == aura::client::kShowStateKey) {
+ ui::WindowShowState old_state = static_cast<ui::WindowShowState>(old);
+ ui::WindowShowState new_state =
+ window->GetProperty(aura::client::kShowStateKey);
+ if (old_state != ui::SHOW_STATE_MINIMIZED &&
+ GetRestoreBoundsInScreen(window) == NULL &&
+ IsMaximizedState(new_state) &&
+ !IsMaximizedState(old_state)) {
+ SetRestoreBoundsInParent(window, window->bounds());
+ }
+ // When restoring from a minimized state, we want to restore to the
+ // previous (maybe L/R maximized) state. Since we do also want to keep the
+ // restore rectangle, we set the restore rectangle to the rectangle we want
+ // to restore to and restore it after we switched so that it is preserved.
+ gfx::Rect restore;
+ if (old_state == ui::SHOW_STATE_MINIMIZED &&
+ (new_state == ui::SHOW_STATE_NORMAL ||
+ new_state == ui::SHOW_STATE_DEFAULT) &&
+ GetRestoreBoundsInScreen(window) &&
+ !GetWindowAlwaysRestoresToRestoreBounds(window)) {
+ restore = *GetRestoreBoundsInScreen(window);
+ SetRestoreBoundsInScreen(window, window->GetBoundsInScreen());
+ }
+
+ UpdateBoundsFromShowState(window);
+ ShowStateChanged(window, old_state);
+
+ // Set the restore rectangle to the previously set restore rectangle.
+ if (!restore.IsEmpty())
+ SetRestoreBoundsInScreen(window, restore);
+ }
+
+ if (key == internal::kWindowTrackedByWorkspaceKey &&
+ GetTrackedByWorkspace(window)) {
+ SetMaximizedOrFullscreenBounds(window);
+ }
+
+ if (key == aura::client::kAlwaysOnTopKey &&
+ window->GetProperty(aura::client::kAlwaysOnTopKey)) {
+ internal::AlwaysOnTopController* controller =
+ GetRootWindowController(window->GetRootWindow())->
+ always_on_top_controller();
+ controller->GetContainer(window)->AddChild(window);
+ }
+}
+
+void WorkspaceLayoutManager::ShowStateChanged(
+ Window* window,
+ ui::WindowShowState last_show_state) {
+ BaseLayoutManager::ShowStateChanged(window, last_show_state);
+ UpdateDesktopVisibility();
+}
+
+void WorkspaceLayoutManager::AdjustAllWindowsBoundsForWorkAreaChange(
+ AdjustWindowReason reason) {
+ work_area_ = ScreenAsh::GetDisplayWorkAreaBoundsInParent(window_->parent());
+ BaseLayoutManager::AdjustAllWindowsBoundsForWorkAreaChange(reason);
+}
+
+void WorkspaceLayoutManager::AdjustWindowBoundsForWorkAreaChange(
+ Window* window,
+ AdjustWindowReason reason) {
+ if (!GetTrackedByWorkspace(window))
+ return;
+
+ // Use cross fade transition for the maximized window if the adjustment
+ // happens due to the shelf's visibility change. Otherwise the background
+ // can be seen slightly between the bottom edge of resized-window and
+ // the animating shelf.
+ // TODO(mukai): this cause slight blur at the window frame because of the
+ // cross fade. I think this is better, but should reconsider if someone
+ // raises voice for this.
+ if (wm::IsWindowMaximized(window) &&
+ reason == ADJUST_WINDOW_WORK_AREA_INSETS_CHANGED) {
+ CrossFadeToBounds(window, ScreenAsh::GetMaximizedWindowBoundsInParent(
+ window->parent()->parent()));
+ return;
+ }
+
+ if (SetMaximizedOrFullscreenBounds(window))
+ return;
+
+ gfx::Rect bounds = window->bounds();
+ switch (reason) {
+ case ADJUST_WINDOW_DISPLAY_SIZE_CHANGED:
+ // The work area may be smaller than the full screen. Put as much of the
+ // window as possible within the display area.
+ bounds.AdjustToFit(work_area_);
+ break;
+ case ADJUST_WINDOW_WORK_AREA_INSETS_CHANGED:
+ ash::wm::AdjustBoundsToEnsureMinimumWindowVisibility(work_area_, &bounds);
+ break;
+ }
+ if (window->bounds() != bounds)
+ window->SetBounds(bounds);
+}
+
+void WorkspaceLayoutManager::AdjustWindowBoundsWhenAdded(
+ Window* window) {
+ if (!GetTrackedByWorkspace(window))
+ return;
+
+ if (SetMaximizedOrFullscreenBounds(window))
+ return;
+
+ gfx::Rect bounds = window->bounds();
+ int min_width = bounds.width() * kMinimumPercentOnScreenArea;
+ int min_height = bounds.height() * kMinimumPercentOnScreenArea;
+ ash::wm::AdjustBoundsToEnsureWindowVisibility(
+ work_area_, min_width, min_height, &bounds);
+
+ if (window->bounds() != bounds)
+ window->SetBounds(bounds);
+}
+
+void WorkspaceLayoutManager::UpdateDesktopVisibility() {
+ if (shelf_)
+ shelf_->UpdateVisibilityState();
+ FramePainter::UpdateSoloWindowHeader(window_->GetRootWindow());
+}
+
+void WorkspaceLayoutManager::UpdateBoundsFromShowState(Window* window) {
+ // See comment in SetMaximizedOrFullscreenBounds() as to why we use parent in
+ // these calculation.
+ switch (window->GetProperty(aura::client::kShowStateKey)) {
+ case ui::SHOW_STATE_DEFAULT:
+ case ui::SHOW_STATE_NORMAL: {
+ const gfx::Rect* restore = GetRestoreBoundsInScreen(window);
+ // Make sure that the part of the window is always visible
+ // when restored.
+ gfx::Rect bounds_in_parent;
+ if (restore) {
+ bounds_in_parent =
+ ScreenAsh::ConvertRectFromScreen(window->parent()->parent(),
+ *restore);
+
+ ash::wm::AdjustBoundsToEnsureMinimumWindowVisibility(
+ work_area_, &bounds_in_parent);
+ } else {
+ // Minimized windows have no restore bounds.
+ // Use the current bounds instead.
+ bounds_in_parent = window->bounds();
+ ash::wm::AdjustBoundsToEnsureMinimumWindowVisibility(
+ work_area_, &bounds_in_parent);
+ // Don't start animation if the bounds didn't change.
+ if (bounds_in_parent == window->bounds())
+ bounds_in_parent.SetRect(0, 0, 0, 0);
+ }
+ if (!bounds_in_parent.IsEmpty()) {
+ CrossFadeToBounds(
+ window,
+ BaseLayoutManager::BoundsWithScreenEdgeVisible(
+ window->parent()->parent(),
+ bounds_in_parent));
+ }
+ ClearRestoreBounds(window);
+ break;
+ }
+
+ case ui::SHOW_STATE_MAXIMIZED:
+ MoveToDisplayForRestore(window);
+ CrossFadeToBounds(window, ScreenAsh::GetMaximizedWindowBoundsInParent(
+ window->parent()->parent()));
+ break;
+ case ui::SHOW_STATE_FULLSCREEN:
+ MoveToDisplayForRestore(window);
+ SetChildBoundsDirect(window, ScreenAsh::GetDisplayBoundsInParent(
+ window->parent()->parent()));
+ break;
+ default:
+ break;
+ }
+}
+
+bool WorkspaceLayoutManager::SetMaximizedOrFullscreenBounds(
+ aura::Window* window) {
+ if (!GetTrackedByWorkspace(window))
+ return false;
+
+ // During animations there is a transform installed on the workspace
+ // windows. For this reason this code uses the parent so that the transform is
+ // ignored.
+ if (wm::IsWindowMaximized(window)) {
+ SetChildBoundsDirect(
+ window, ScreenAsh::GetMaximizedWindowBoundsInParent(
+ window->parent()->parent()));
+ return true;
+ }
+ if (wm::IsWindowFullscreen(window)) {
+ SetChildBoundsDirect(
+ window,
+ ScreenAsh::GetDisplayBoundsInParent(window->parent()->parent()));
+ return true;
+ }
+ return false;
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/wm/workspace/workspace_layout_manager.h b/chromium/ash/wm/workspace/workspace_layout_manager.h
new file mode 100644
index 00000000000..4476ddc99c5
--- /dev/null
+++ b/chromium/ash/wm/workspace/workspace_layout_manager.h
@@ -0,0 +1,93 @@
+// Copyright (c) 2012 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 ASH_WM_WORKSPACE_WORKSPACE_LAYOUT_MANAGER_H_
+#define ASH_WM_WORKSPACE_WORKSPACE_LAYOUT_MANAGER_H_
+
+#include <set>
+
+#include "ash/shell_observer.h"
+#include "ash/wm/base_layout_manager.h"
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "ui/base/ui_base_types.h"
+#include "ui/aura/window_observer.h"
+#include "ui/gfx/rect.h"
+
+namespace aura {
+class RootWindow;
+class Window;
+}
+
+namespace ui {
+class Layer;
+}
+
+namespace ash {
+
+namespace internal {
+
+class ShelfLayoutManager;
+
+// LayoutManager used on the window created for a workspace.
+class ASH_EXPORT WorkspaceLayoutManager : public BaseLayoutManager {
+ public:
+ explicit WorkspaceLayoutManager(aura::Window* window);
+ virtual ~WorkspaceLayoutManager();
+
+ void SetShelf(internal::ShelfLayoutManager* shelf);
+
+ // Overridden from aura::LayoutManager:
+ virtual void OnWindowResized() OVERRIDE {}
+ virtual void OnWindowAddedToLayout(aura::Window* child) OVERRIDE;
+ virtual void OnWillRemoveWindowFromLayout(aura::Window* child) OVERRIDE;
+ virtual void OnWindowRemovedFromLayout(aura::Window* child) OVERRIDE;
+ virtual void OnChildWindowVisibilityChanged(aura::Window* child,
+ bool visibile) OVERRIDE;
+ virtual void SetChildBounds(aura::Window* child,
+ const gfx::Rect& requested_bounds) OVERRIDE;
+
+ // ash::ShellObserver overrides:
+ virtual void OnDisplayWorkAreaInsetsChanged() OVERRIDE;
+
+ // Overriden from WindowObserver:
+ virtual void OnWindowPropertyChanged(aura::Window* window,
+ const void* key,
+ intptr_t old) OVERRIDE;
+
+ private:
+ // Overridden from BaseLayoutManager:
+ virtual void ShowStateChanged(aura::Window* window,
+ ui::WindowShowState last_show_state) OVERRIDE;
+ virtual void AdjustAllWindowsBoundsForWorkAreaChange(
+ AdjustWindowReason reason) OVERRIDE;
+ virtual void AdjustWindowBoundsForWorkAreaChange(
+ aura::Window* window,
+ AdjustWindowReason reason) OVERRIDE;
+
+ void AdjustWindowBoundsWhenAdded(aura::Window* window);
+
+ void UpdateDesktopVisibility();
+
+ // Updates the bounds of the window from a show state change.
+ void UpdateBoundsFromShowState(aura::Window* window);
+
+ // If |window| is maximized or fullscreen the bounds of the window are set and
+ // true is returned. Does nothing otherwise.
+ bool SetMaximizedOrFullscreenBounds(aura::Window* window);
+
+ internal::ShelfLayoutManager* shelf_;
+ aura::Window* window_;
+
+ // The work area. Cached to avoid unnecessarily moving windows during a
+ // workspace switch.
+ gfx::Rect work_area_;
+
+ DISALLOW_COPY_AND_ASSIGN(WorkspaceLayoutManager);
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_WM_WORKSPACE_WORKSPACE_LAYOUT_MANAGER_H_
diff --git a/chromium/ash/wm/workspace/workspace_layout_manager_unittest.cc b/chromium/ash/wm/workspace/workspace_layout_manager_unittest.cc
new file mode 100644
index 00000000000..3dce5b015c9
--- /dev/null
+++ b/chromium/ash/wm/workspace/workspace_layout_manager_unittest.cc
@@ -0,0 +1,362 @@
+// Copyright (c) 2012 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.
+
+#include "ash/wm/workspace/workspace_layout_manager.h"
+
+#include "ash/root_window_controller.h"
+#include "ash/screen_ash.h"
+#include "ash/shelf/shelf_layout_manager.h"
+#include "ash/shelf/shelf_widget.h"
+#include "ash/shell.h"
+#include "ash/test/ash_test_base.h"
+#include "ash/wm/property_util.h"
+#include "ash/wm/window_util.h"
+#include "ui/aura/client/aura_constants.h"
+#include "ui/aura/root_window.h"
+#include "ui/aura/test/test_windows.h"
+#include "ui/aura/window.h"
+#include "ui/gfx/insets.h"
+#include "ui/views/widget/widget.h"
+#include "ui/views/widget/widget_delegate.h"
+
+namespace ash {
+namespace {
+
+class MaximizeDelegateView : public views::WidgetDelegateView {
+ public:
+ MaximizeDelegateView(const gfx::Rect& initial_bounds)
+ : initial_bounds_(initial_bounds) {
+ }
+ virtual ~MaximizeDelegateView() {}
+
+ virtual bool GetSavedWindowPlacement(
+ gfx::Rect* bounds,
+ ui::WindowShowState* show_state) const OVERRIDE {
+ *bounds = initial_bounds_;
+ *show_state = ui::SHOW_STATE_MAXIMIZED;
+ return true;
+ }
+
+ private:
+ const gfx::Rect initial_bounds_;
+
+ DISALLOW_COPY_AND_ASSIGN(MaximizeDelegateView);
+};
+
+} // namespace
+
+typedef test::AshTestBase WorkspaceLayoutManagerTest;
+
+// Verifies that a window containing a restore coordinate will be restored to
+// to the size prior to minimize, keeping the restore rectangle in tact (if
+// there is one).
+TEST_F(WorkspaceLayoutManagerTest, RestoreFromMinimizeKeepsRestore) {
+ scoped_ptr<aura::Window> window(
+ CreateTestWindowInShellWithBounds(gfx::Rect(1, 2, 3, 4)));
+ gfx::Rect bounds(10, 15, 25, 35);
+ window->SetBounds(bounds);
+ // This will not be used for un-minimizing window.
+ SetRestoreBoundsInScreen(window.get(), gfx::Rect(0, 0, 100, 100));
+ wm::MinimizeWindow(window.get());
+ wm::RestoreWindow(window.get());
+ EXPECT_EQ("0,0 100x100", GetRestoreBoundsInScreen(window.get())->ToString());
+ EXPECT_EQ("10,15 25x35", window.get()->bounds().ToString());
+
+ if (!SupportsMultipleDisplays())
+ return;
+
+ UpdateDisplay("400x300,500x400");
+ window->SetBoundsInScreen(gfx::Rect(600, 0, 100, 100),
+ ScreenAsh::GetSecondaryDisplay());
+ EXPECT_EQ(Shell::GetAllRootWindows()[1], window->GetRootWindow());
+ wm::MinimizeWindow(window.get());
+ // This will not be used for un-minimizing window.
+ SetRestoreBoundsInScreen(window.get(), gfx::Rect(0, 0, 100, 100));
+ wm::RestoreWindow(window.get());
+ EXPECT_EQ("600,0 100x100", window->GetBoundsInScreen().ToString());
+
+ // Make sure the unminimized window moves inside the display when
+ // 2nd display is disconnected.
+ wm::MinimizeWindow(window.get());
+ UpdateDisplay("400x300");
+ wm::RestoreWindow(window.get());
+ EXPECT_EQ(Shell::GetPrimaryRootWindow(), window->GetRootWindow());
+ EXPECT_TRUE(
+ Shell::GetPrimaryRootWindow()->bounds().Intersects(window->bounds()));
+}
+
+TEST_F(WorkspaceLayoutManagerTest, KeepRestoredWindowInDisplay) {
+ if (!SupportsHostWindowResize())
+ return;
+ scoped_ptr<aura::Window> window(
+ CreateTestWindowInShellWithBounds(gfx::Rect(1, 2, 30, 40)));
+ // Maximized -> Normal transition.
+ wm::MaximizeWindow(window.get());
+ SetRestoreBoundsInScreen(window.get(), gfx::Rect(-100, -100, 30, 40));
+ wm::RestoreWindow(window.get());
+ EXPECT_TRUE(
+ Shell::GetPrimaryRootWindow()->bounds().Intersects(window->bounds()));
+ EXPECT_EQ("-20,-30 30x40", window->bounds().ToString());
+
+ // Minimized -> Normal transition.
+ window->SetBounds(gfx::Rect(-100, -100, 30, 40));
+ wm::MinimizeWindow(window.get());
+ EXPECT_FALSE(
+ Shell::GetPrimaryRootWindow()->bounds().Intersects(window->bounds()));
+ EXPECT_EQ("-100,-100 30x40", window->bounds().ToString());
+ window->Show();
+ EXPECT_TRUE(
+ Shell::GetPrimaryRootWindow()->bounds().Intersects(window->bounds()));
+ EXPECT_EQ("-20,-30 30x40", window->bounds().ToString());
+
+ // Fullscreen -> Normal transition.
+ window->SetBounds(gfx::Rect(0, 0, 30, 40)); // reset bounds.
+ ASSERT_EQ("0,0 30x40", window->bounds().ToString());
+ window->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_FULLSCREEN);
+ EXPECT_EQ(window->bounds(), window->GetRootWindow()->bounds());
+ SetRestoreBoundsInScreen(window.get(), gfx::Rect(-100, -100, 30, 40));
+ wm::RestoreWindow(window.get());
+ EXPECT_TRUE(
+ Shell::GetPrimaryRootWindow()->bounds().Intersects(window->bounds()));
+ EXPECT_EQ("-20,-30 30x40", window->bounds().ToString());
+}
+
+TEST_F(WorkspaceLayoutManagerTest, MaximizeInDisplayToBeRestored) {
+ if (!SupportsMultipleDisplays())
+ return;
+ UpdateDisplay("300x400,400x500");
+
+ Shell::RootWindowList root_windows = Shell::GetAllRootWindows();
+
+ scoped_ptr<aura::Window> window(
+ CreateTestWindowInShellWithBounds(gfx::Rect(1, 2, 30, 40)));
+ EXPECT_EQ(root_windows[0], window->GetRootWindow());
+
+ SetRestoreBoundsInScreen(window.get(), gfx::Rect(400, 0, 30, 40));
+ // Maximize the window in 2nd display as the restore bounds
+ // is inside 2nd display.
+ wm::MaximizeWindow(window.get());
+ EXPECT_EQ(root_windows[1], window->GetRootWindow());
+ EXPECT_EQ("300,0 400x452", window->GetBoundsInScreen().ToString());
+
+ wm::RestoreWindow(window.get());
+ EXPECT_EQ(root_windows[1], window->GetRootWindow());
+ EXPECT_EQ("400,0 30x40", window->GetBoundsInScreen().ToString());
+
+ // If the restore bounds intersects with the current display,
+ // don't move.
+ SetRestoreBoundsInScreen(window.get(), gfx::Rect(280, 0, 30, 40));
+ wm::MaximizeWindow(window.get());
+ EXPECT_EQ(root_windows[1], window->GetRootWindow());
+ EXPECT_EQ("300,0 400x452", window->GetBoundsInScreen().ToString());
+
+ wm::RestoreWindow(window.get());
+ EXPECT_EQ(root_windows[1], window->GetRootWindow());
+ EXPECT_EQ("280,0 30x40", window->GetBoundsInScreen().ToString());
+
+ // Restoring widget state.
+ scoped_ptr<views::Widget> w1(new views::Widget);
+ views::Widget::InitParams params;
+ params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
+ params.delegate = new MaximizeDelegateView(gfx::Rect(400, 0, 30, 40));
+ params.context = root_windows[0];
+ w1->Init(params);
+ w1->Show();
+ EXPECT_TRUE(w1->IsMaximized());
+ EXPECT_EQ(root_windows[1], w1->GetNativeView()->GetRootWindow());
+ EXPECT_EQ("300,0 400x452", w1->GetWindowBoundsInScreen().ToString());
+ w1->Restore();
+ EXPECT_EQ(root_windows[1], w1->GetNativeView()->GetRootWindow());
+ EXPECT_EQ("400,0 30x40", w1->GetWindowBoundsInScreen().ToString());
+}
+
+TEST_F(WorkspaceLayoutManagerTest, FullscreenInDisplayToBeRestored) {
+ if (!SupportsMultipleDisplays())
+ return;
+ UpdateDisplay("300x400,400x500");
+
+ Shell::RootWindowList root_windows = Shell::GetAllRootWindows();
+
+ scoped_ptr<aura::Window> window(
+ CreateTestWindowInShellWithBounds(gfx::Rect(1, 2, 30, 40)));
+ EXPECT_EQ(root_windows[0], window->GetRootWindow());
+
+ SetRestoreBoundsInScreen(window.get(), gfx::Rect(400, 0, 30, 40));
+ // Maximize the window in 2nd display as the restore bounds
+ // is inside 2nd display.
+ window->SetProperty(aura::client::kShowStateKey,
+ ui::SHOW_STATE_FULLSCREEN);
+ EXPECT_EQ(root_windows[1], window->GetRootWindow());
+ EXPECT_EQ("300,0 400x500", window->GetBoundsInScreen().ToString());
+
+ wm::RestoreWindow(window.get());
+ EXPECT_EQ(root_windows[1], window->GetRootWindow());
+ EXPECT_EQ("400,0 30x40", window->GetBoundsInScreen().ToString());
+
+ // If the restore bounds intersects with the current display,
+ // don't move.
+ SetRestoreBoundsInScreen(window.get(), gfx::Rect(280, 0, 30, 40));
+ window->SetProperty(aura::client::kShowStateKey,
+ ui::SHOW_STATE_FULLSCREEN);
+ EXPECT_EQ(root_windows[1], window->GetRootWindow());
+ EXPECT_EQ("300,0 400x500", window->GetBoundsInScreen().ToString());
+
+ wm::RestoreWindow(window.get());
+ EXPECT_EQ(root_windows[1], window->GetRootWindow());
+ EXPECT_EQ("280,0 30x40", window->GetBoundsInScreen().ToString());
+}
+
+// WindowObserver implementation used by DontClobberRestoreBoundsWindowObserver.
+// This code mirrors what BrowserFrameAura does. In particular when this code
+// sees the window was maximized it changes the bounds of a secondary
+// window. The secondary window mirrors the status window.
+class DontClobberRestoreBoundsWindowObserver : public aura::WindowObserver {
+ public:
+ DontClobberRestoreBoundsWindowObserver() : window_(NULL) {}
+
+ void set_window(aura::Window* window) { window_ = window; }
+
+ virtual void OnWindowPropertyChanged(aura::Window* window,
+ const void* key,
+ intptr_t old) OVERRIDE {
+ if (!window_)
+ return;
+
+ if (wm::IsWindowMaximized(window)) {
+ aura::Window* w = window_;
+ window_ = NULL;
+
+ gfx::Rect shelf_bounds(Shell::GetPrimaryRootWindowController()->
+ GetShelfLayoutManager()->GetIdealBounds());
+ const gfx::Rect& window_bounds(w->bounds());
+ w->SetBounds(gfx::Rect(window_bounds.x(), shelf_bounds.y() - 1,
+ window_bounds.width(), window_bounds.height()));
+ }
+ }
+
+ private:
+ aura::Window* window_;
+
+ DISALLOW_COPY_AND_ASSIGN(DontClobberRestoreBoundsWindowObserver);
+};
+
+// Creates a window, maximized the window and from within the maximized
+// notification sets the bounds of a window to overlap the shelf. Verifies this
+// doesn't effect the restore bounds.
+TEST_F(WorkspaceLayoutManagerTest, DontClobberRestoreBounds) {
+ DontClobberRestoreBoundsWindowObserver window_observer;
+ scoped_ptr<aura::Window> window(new aura::Window(NULL));
+ window->SetType(aura::client::WINDOW_TYPE_NORMAL);
+ window->Init(ui::LAYER_TEXTURED);
+ window->SetBounds(gfx::Rect(10, 20, 30, 40));
+ // NOTE: for this test to exercise the failure the observer needs to be added
+ // before the parent set. This mimics what BrowserFrameAura does.
+ window->AddObserver(&window_observer);
+ SetDefaultParentByPrimaryRootWindow(window.get());
+ window->Show();
+ wm::ActivateWindow(window.get());
+
+ scoped_ptr<aura::Window> window2(
+ CreateTestWindowInShellWithBounds(gfx::Rect(12, 20, 30, 40)));
+ window->AddTransientChild(window2.get());
+ window2->Show();
+
+ window_observer.set_window(window2.get());
+ wm::MaximizeWindow(window.get());
+ EXPECT_EQ("10,20 30x40", GetRestoreBoundsInScreen(window.get())->ToString());
+ window->RemoveObserver(&window_observer);
+}
+
+// Verifies when a window is maximized all descendant windows have a size.
+TEST_F(WorkspaceLayoutManagerTest, ChildBoundsResetOnMaximize) {
+ scoped_ptr<aura::Window> window(
+ CreateTestWindowInShellWithBounds(gfx::Rect(10, 20, 30, 40)));
+ window->Show();
+ wm::ActivateWindow(window.get());
+ scoped_ptr<aura::Window> child_window(
+ aura::test::CreateTestWindowWithBounds(gfx::Rect(5, 6, 7, 8),
+ window.get()));
+ child_window->Show();
+ wm::MaximizeWindow(window.get());
+ EXPECT_EQ("5,6 7x8", child_window->bounds().ToString());
+}
+
+TEST_F(WorkspaceLayoutManagerTest, WindowShouldBeOnScreenWhenAdded) {
+ // Normal window bounds shouldn't be changed.
+ gfx::Rect window_bounds(100, 100, 200, 200);
+ scoped_ptr<aura::Window> window(
+ CreateTestWindowInShellWithBounds(window_bounds));
+ EXPECT_EQ(window_bounds, window->bounds());
+
+ // If the window is out of the workspace, it would be moved on screen.
+ gfx::Rect root_window_bounds =
+ Shell::GetInstance()->GetPrimaryRootWindow()->bounds();
+ window_bounds.Offset(root_window_bounds.width(), root_window_bounds.height());
+ ASSERT_FALSE(window_bounds.Intersects(root_window_bounds));
+ scoped_ptr<aura::Window> out_window(
+ CreateTestWindowInShellWithBounds(window_bounds));
+ EXPECT_EQ(window_bounds.size(), out_window->bounds().size());
+ gfx::Rect bounds = out_window->bounds();
+ bounds.Intersect(root_window_bounds);
+
+ // 30% of the window edge must be visible.
+ EXPECT_GT(bounds.width(), out_window->bounds().width() * 0.29);
+ EXPECT_GT(bounds.height(), out_window->bounds().height() * 0.29);
+
+ // Make sure we always make more than 1/3 of the window edge visible even
+ // if the initial bounds intersects with display.
+ window_bounds.SetRect(-150, -150, 200, 200);
+ bounds = window_bounds;
+ bounds.Intersect(root_window_bounds);
+
+ // Make sure that the initial bounds' visible area is less than 26%
+ // so that the auto adjustment logic kicks in.
+ ASSERT_LT(bounds.width(), out_window->bounds().width() * 0.26);
+ ASSERT_LT(bounds.height(), out_window->bounds().height() * 0.26);
+ ASSERT_TRUE(window_bounds.Intersects(root_window_bounds));
+
+ scoped_ptr<aura::Window> partially_out_window(
+ CreateTestWindowInShellWithBounds(window_bounds));
+ EXPECT_EQ(window_bounds.size(), partially_out_window->bounds().size());
+ bounds = partially_out_window->bounds();
+ bounds.Intersect(root_window_bounds);
+ EXPECT_GT(bounds.width(), out_window->bounds().width() * 0.29);
+ EXPECT_GT(bounds.height(), out_window->bounds().height() * 0.29);
+
+ // Make sure the window whose 30% width/height is bigger than display
+ // will be placed correctly.
+ window_bounds.SetRect(-1900, -1900, 3000, 3000);
+ scoped_ptr<aura::Window> window_bigger_than_display(
+ CreateTestWindowInShellWithBounds(window_bounds));
+ EXPECT_GE(root_window_bounds.width(),
+ window_bigger_than_display->bounds().width());
+ EXPECT_GE(root_window_bounds.height(),
+ window_bigger_than_display->bounds().height());
+
+ bounds = window_bigger_than_display->bounds();
+ bounds.Intersect(root_window_bounds);
+ EXPECT_GT(bounds.width(), out_window->bounds().width() * 0.29);
+ EXPECT_GT(bounds.height(), out_window->bounds().height() * 0.29);
+}
+
+// Verifies the size of a window is enforced to be smaller than the work area.
+TEST_F(WorkspaceLayoutManagerTest, SizeToWorkArea) {
+ // Normal window bounds shouldn't be changed.
+ gfx::Size work_area(
+ Shell::GetScreen()->GetPrimaryDisplay().work_area().size());
+ const gfx::Rect window_bounds(
+ 100, 101, work_area.width() + 1, work_area.height() + 2);
+ scoped_ptr<aura::Window> window(
+ CreateTestWindowInShellWithBounds(window_bounds));
+ EXPECT_EQ(gfx::Rect(gfx::Point(100, 101), work_area).ToString(),
+ window->bounds().ToString());
+
+ // Directly setting the bounds triggers a slightly different code path. Verify
+ // that too.
+ window->SetBounds(window_bounds);
+ EXPECT_EQ(gfx::Rect(gfx::Point(100, 101), work_area).ToString(),
+ window->bounds().ToString());
+}
+
+} // namespace ash
diff --git a/chromium/ash/wm/workspace/workspace_types.h b/chromium/ash/wm/workspace/workspace_types.h
new file mode 100644
index 00000000000..9572a328d8b
--- /dev/null
+++ b/chromium/ash/wm/workspace/workspace_types.h
@@ -0,0 +1,27 @@
+// Copyright (c) 2012 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 ASH_WM_WORKSPACE_WORKSPACE_TYPES_H_
+#define ASH_WM_WORKSPACE_WORKSPACE_TYPES_H_
+
+namespace ash {
+
+// Enumeration of the possible window states.
+enum WorkspaceWindowState {
+ // There's a full screen window.
+ WORKSPACE_WINDOW_STATE_FULL_SCREEN,
+
+ // There's a maximized window.
+ WORKSPACE_WINDOW_STATE_MAXIMIZED,
+
+ // At least one window overlaps the shelf.
+ WORKSPACE_WINDOW_STATE_WINDOW_OVERLAPS_SHELF,
+
+ // None of the windows are fullscreen, maximized or touch the shelf.
+ WORKSPACE_WINDOW_STATE_DEFAULT,
+};
+
+} // namespace ash
+
+#endif // ASH_WM_WORKSPACE_WORKSPACE_TYPES_H_
diff --git a/chromium/ash/wm/workspace/workspace_window_resizer.cc b/chromium/ash/wm/workspace/workspace_window_resizer.cc
new file mode 100644
index 00000000000..4b8c0aaf7e9
--- /dev/null
+++ b/chromium/ash/wm/workspace/workspace_window_resizer.cc
@@ -0,0 +1,936 @@
+// Copyright (c) 2012 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.
+
+#include "ash/wm/workspace/workspace_window_resizer.h"
+
+#include <algorithm>
+#include <cmath>
+#include <utility>
+#include <vector>
+
+#include "ash/ash_switches.h"
+#include "ash/root_window_controller.h"
+#include "ash/screen_ash.h"
+#include "ash/shell.h"
+#include "ash/shell_window_ids.h"
+#include "ash/wm/coordinate_conversion.h"
+#include "ash/wm/default_window_resizer.h"
+#include "ash/wm/dock/docked_window_resizer.h"
+#include "ash/wm/drag_window_resizer.h"
+#include "ash/wm/panels/panel_window_resizer.h"
+#include "ash/wm/property_util.h"
+#include "ash/wm/window_properties.h"
+#include "ash/wm/window_util.h"
+#include "ash/wm/workspace/phantom_window_controller.h"
+#include "ash/wm/workspace/snap_sizer.h"
+#include "base/command_line.h"
+#include "ui/aura/client/aura_constants.h"
+#include "ui/aura/client/screen_position_client.h"
+#include "ui/aura/client/window_types.h"
+#include "ui/aura/root_window.h"
+#include "ui/aura/window.h"
+#include "ui/aura/window_delegate.h"
+#include "ui/base/hit_test.h"
+#include "ui/compositor/layer.h"
+#include "ui/gfx/screen.h"
+#include "ui/gfx/transform.h"
+
+namespace ash {
+
+scoped_ptr<WindowResizer> CreateWindowResizer(
+ aura::Window* window,
+ const gfx::Point& point_in_parent,
+ int window_component,
+ aura::client::WindowMoveSource source) {
+ DCHECK(window);
+ // No need to return a resizer when the window cannot get resized.
+ if (!wm::CanResizeWindow(window) && window_component != HTCAPTION)
+ return scoped_ptr<WindowResizer>();
+
+ // TODO(varkha): The chaining of window resizers causes some of the logic
+ // to be repeated and the logic flow difficult to control. With some windows
+ // classes using reparenting during drag operations it becomes challenging to
+ // implement proper transition from one resizer to another during or at the
+ // end of the drag. This also causes http://crbug.com/247085.
+ // It seems the only thing the panel or dock resizer needs to do is notify the
+ // layout manager when a docked window is being dragged. We should have a
+ // better way of doing this, perhaps by having a way of observing drags or
+ // having a generic drag window wrapper which informs a layout manager that a
+ // drag has started or stopped.
+ // It may be possible to refactor and eliminate chaining.
+ WindowResizer* window_resizer = NULL;
+ if (window->parent() &&
+ (window->parent()->id() == internal::kShellWindowId_DefaultContainer ||
+ window->parent()->id() == internal::kShellWindowId_DockedContainer ||
+ window->parent()->id() == internal::kShellWindowId_PanelContainer)) {
+ // Allow dragging maximized windows if it's not tracked by workspace. This
+ // is set by tab dragging code.
+ if (!wm::IsWindowNormal(window) &&
+ (window_component != HTCAPTION || GetTrackedByWorkspace(window)))
+ return scoped_ptr<WindowResizer>();
+ window_resizer = internal::WorkspaceWindowResizer::Create(
+ window,
+ point_in_parent,
+ window_component,
+ source,
+ std::vector<aura::Window*>());
+ } else if (wm::IsWindowNormal(window)) {
+ window_resizer = DefaultWindowResizer::Create(
+ window, point_in_parent, window_component, source);
+ }
+ if (window_resizer) {
+ window_resizer = internal::DragWindowResizer::Create(
+ window_resizer, window, point_in_parent, window_component, source);
+ }
+ if (window_resizer && window->type() == aura::client::WINDOW_TYPE_PANEL) {
+ window_resizer = PanelWindowResizer::Create(
+ window_resizer, window, point_in_parent, window_component, source);
+ }
+ if (CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kAshEnableDockedWindows) &&
+ window_resizer && window->parent() &&
+ (window->parent()->id() == internal::kShellWindowId_DefaultContainer ||
+ window->parent()->id() == internal::kShellWindowId_DockedContainer ||
+ window->parent()->id() == internal::kShellWindowId_PanelContainer)) {
+ window_resizer = internal::DockedWindowResizer::Create(
+ window_resizer, window, point_in_parent, window_component, source);
+ }
+ return make_scoped_ptr<WindowResizer>(window_resizer);
+}
+
+namespace internal {
+
+namespace {
+
+// Snapping distance used instead of WorkspaceWindowResizer::kScreenEdgeInset
+// when resizing a window using touchscreen.
+const int kScreenEdgeInsetForTouchResize = 32;
+
+// Returns true if the window should stick to the edge.
+bool ShouldStickToEdge(int distance_from_edge, int sticky_size) {
+ if (CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kAshEnableStickyEdges) ||
+ CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kAshEnableDockedWindows)) {
+ return distance_from_edge < 0 &&
+ distance_from_edge > -sticky_size;
+ }
+ return distance_from_edge < sticky_size &&
+ distance_from_edge > -sticky_size * 2;
+}
+
+// Returns the coordinate along the secondary axis to snap to.
+int CoordinateAlongSecondaryAxis(SecondaryMagnetismEdge edge,
+ int leading,
+ int trailing,
+ int none) {
+ switch (edge) {
+ case SECONDARY_MAGNETISM_EDGE_LEADING:
+ return leading;
+ case SECONDARY_MAGNETISM_EDGE_TRAILING:
+ return trailing;
+ case SECONDARY_MAGNETISM_EDGE_NONE:
+ return none;
+ }
+ NOTREACHED();
+ return none;
+}
+
+// Returns the origin for |src| when magnetically attaching to |attach_to| along
+// the edges |edges|. |edges| is a bitmask of the MagnetismEdges.
+gfx::Point OriginForMagneticAttach(const gfx::Rect& src,
+ const gfx::Rect& attach_to,
+ const MatchedEdge& edge) {
+ int x = 0, y = 0;
+ switch (edge.primary_edge) {
+ case MAGNETISM_EDGE_TOP:
+ y = attach_to.bottom();
+ break;
+ case MAGNETISM_EDGE_LEFT:
+ x = attach_to.right();
+ break;
+ case MAGNETISM_EDGE_BOTTOM:
+ y = attach_to.y() - src.height();
+ break;
+ case MAGNETISM_EDGE_RIGHT:
+ x = attach_to.x() - src.width();
+ break;
+ }
+ switch (edge.primary_edge) {
+ case MAGNETISM_EDGE_TOP:
+ case MAGNETISM_EDGE_BOTTOM:
+ x = CoordinateAlongSecondaryAxis(
+ edge.secondary_edge, attach_to.x(), attach_to.right() - src.width(),
+ src.x());
+ break;
+ case MAGNETISM_EDGE_LEFT:
+ case MAGNETISM_EDGE_RIGHT:
+ y = CoordinateAlongSecondaryAxis(
+ edge.secondary_edge, attach_to.y(), attach_to.bottom() - src.height(),
+ src.y());
+ break;
+ }
+ return gfx::Point(x, y);
+}
+
+// Returns the bounds for a magnetic attach when resizing. |src| is the bounds
+// of window being resized, |attach_to| the bounds of the window to attach to
+// and |edge| identifies the edge to attach to.
+gfx::Rect BoundsForMagneticResizeAttach(const gfx::Rect& src,
+ const gfx::Rect& attach_to,
+ const MatchedEdge& edge) {
+ int x = src.x();
+ int y = src.y();
+ int w = src.width();
+ int h = src.height();
+ gfx::Point attach_origin(OriginForMagneticAttach(src, attach_to, edge));
+ switch (edge.primary_edge) {
+ case MAGNETISM_EDGE_LEFT:
+ x = attach_origin.x();
+ w = src.right() - x;
+ break;
+ case MAGNETISM_EDGE_RIGHT:
+ w += attach_origin.x() - src.x();
+ break;
+ case MAGNETISM_EDGE_TOP:
+ y = attach_origin.y();
+ h = src.bottom() - y;
+ break;
+ case MAGNETISM_EDGE_BOTTOM:
+ h += attach_origin.y() - src.y();
+ break;
+ }
+ switch (edge.primary_edge) {
+ case MAGNETISM_EDGE_LEFT:
+ case MAGNETISM_EDGE_RIGHT:
+ if (edge.secondary_edge == SECONDARY_MAGNETISM_EDGE_LEADING) {
+ y = attach_origin.y();
+ h = src.bottom() - y;
+ } else if (edge.secondary_edge == SECONDARY_MAGNETISM_EDGE_TRAILING) {
+ h += attach_origin.y() - src.y();
+ }
+ break;
+ case MAGNETISM_EDGE_TOP:
+ case MAGNETISM_EDGE_BOTTOM:
+ if (edge.secondary_edge == SECONDARY_MAGNETISM_EDGE_LEADING) {
+ x = attach_origin.x();
+ w = src.right() - x;
+ } else if (edge.secondary_edge == SECONDARY_MAGNETISM_EDGE_TRAILING) {
+ w += attach_origin.x() - src.x();
+ }
+ break;
+ }
+ return gfx::Rect(x, y, w, h);
+}
+
+// Converts a window component edge to the magnetic edge to snap to.
+uint32 WindowComponentToMagneticEdge(int window_component) {
+ switch (window_component) {
+ case HTTOPLEFT:
+ return MAGNETISM_EDGE_LEFT | MAGNETISM_EDGE_TOP;
+ case HTTOPRIGHT:
+ return MAGNETISM_EDGE_TOP | MAGNETISM_EDGE_RIGHT;
+ case HTBOTTOMLEFT:
+ return MAGNETISM_EDGE_LEFT | MAGNETISM_EDGE_BOTTOM;
+ case HTBOTTOMRIGHT:
+ return MAGNETISM_EDGE_RIGHT | MAGNETISM_EDGE_BOTTOM;
+ case HTTOP:
+ return MAGNETISM_EDGE_TOP;
+ case HTBOTTOM:
+ return MAGNETISM_EDGE_BOTTOM;
+ case HTRIGHT:
+ return MAGNETISM_EDGE_RIGHT;
+ case HTLEFT:
+ return MAGNETISM_EDGE_LEFT;
+ default:
+ break;
+ }
+ return 0;
+}
+
+} // namespace
+
+// static
+const int WorkspaceWindowResizer::kMinOnscreenSize = 20;
+
+// static
+const int WorkspaceWindowResizer::kMinOnscreenHeight = 32;
+
+// static
+const int WorkspaceWindowResizer::kScreenEdgeInset = 8;
+
+// static
+const int WorkspaceWindowResizer::kStickyDistancePixels = 64;
+
+// Represents the width or height of a window with constraints on its minimum
+// and maximum size. 0 represents a lack of a constraint.
+class WindowSize {
+ public:
+ WindowSize(int size, int min, int max)
+ : size_(size),
+ min_(min),
+ max_(max) {
+ // Grow the min/max bounds to include the starting size.
+ if (is_underflowing())
+ min_ = size_;
+ if (is_overflowing())
+ max_ = size_;
+ }
+
+ bool is_at_capacity(bool shrinking) {
+ return size_ == (shrinking ? min_ : max_);
+ }
+
+ int size() const {
+ return size_;
+ }
+
+ bool has_min() const {
+ return min_ != 0;
+ }
+
+ bool has_max() const {
+ return max_ != 0;
+ }
+
+ bool is_valid() const {
+ return !is_overflowing() && !is_underflowing();
+ }
+
+ bool is_overflowing() const {
+ return has_max() && size_ > max_;
+ }
+
+ bool is_underflowing() const {
+ return has_min() && size_ < min_;
+ }
+
+ // Add |amount| to this WindowSize not exceeding min or max size constraints.
+ // Returns by how much |size_| + |amount| exceeds the min/max constraints.
+ int Add(int amount) {
+ DCHECK(is_valid());
+ int new_value = size_ + amount;
+
+ if (has_min() && new_value < min_) {
+ size_ = min_;
+ return new_value - min_;
+ }
+
+ if (has_max() && new_value > max_) {
+ size_ = max_;
+ return new_value - max_;
+ }
+
+ size_ = new_value;
+ return 0;
+ }
+
+ private:
+ int size_;
+ int min_;
+ int max_;
+};
+
+WorkspaceWindowResizer::~WorkspaceWindowResizer() {
+ Shell* shell = Shell::GetInstance();
+ shell->cursor_manager()->UnlockCursor();
+}
+
+// static
+WorkspaceWindowResizer* WorkspaceWindowResizer::Create(
+ aura::Window* window,
+ const gfx::Point& location_in_parent,
+ int window_component,
+ aura::client::WindowMoveSource source,
+ const std::vector<aura::Window*>& attached_windows) {
+ Details details(window, location_in_parent, window_component, source);
+ return details.is_resizable ?
+ new WorkspaceWindowResizer(details, attached_windows) : NULL;
+}
+
+void WorkspaceWindowResizer::Drag(const gfx::Point& location_in_parent,
+ int event_flags) {
+ last_mouse_location_ = location_in_parent;
+
+ int sticky_size;
+ if (event_flags & ui::EF_CONTROL_DOWN) {
+ sticky_size = 0;
+ } else if (CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kAshEnableStickyEdges) ||
+ CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kAshEnableDockedWindows)) {
+ sticky_size = kStickyDistancePixels;
+ } else if ((details_.bounds_change & kBoundsChange_Resizes) &&
+ details_.source == aura::client::WINDOW_MOVE_SOURCE_TOUCH) {
+ sticky_size = kScreenEdgeInsetForTouchResize;
+ } else {
+ sticky_size = kScreenEdgeInset;
+ }
+ // |bounds| is in |window()->parent()|'s coordinates.
+ gfx::Rect bounds = CalculateBoundsForDrag(details_, location_in_parent);
+
+ if (wm::IsWindowNormal(window()))
+ AdjustBoundsForMainWindow(sticky_size, &bounds);
+
+ if (bounds != window()->bounds()) {
+ if (!did_move_or_resize_) {
+ if (!details_.restore_bounds.IsEmpty())
+ ClearRestoreBounds(window());
+ RestackWindows();
+ }
+ did_move_or_resize_ = true;
+ }
+
+ gfx::Point location_in_screen = location_in_parent;
+ wm::ConvertPointToScreen(window()->parent(), &location_in_screen);
+ const bool in_original_root =
+ wm::GetRootWindowAt(location_in_screen) == window()->GetRootWindow();
+ // Hide a phantom window for snapping if the cursor is in another root window.
+ if (in_original_root && wm::CanResizeWindow(window())) {
+ UpdateSnapPhantomWindow(location_in_parent, bounds);
+ } else {
+ snap_type_ = SNAP_NONE;
+ snap_phantom_window_controller_.reset();
+ }
+
+ if (!attached_windows_.empty())
+ LayoutAttachedWindows(&bounds);
+ if (bounds != window()->bounds())
+ window()->SetBounds(bounds);
+}
+
+void WorkspaceWindowResizer::CompleteDrag(int event_flags) {
+ wm::SetUserHasChangedWindowPositionOrSize(details_.window, true);
+ snap_phantom_window_controller_.reset();
+ if (!did_move_or_resize_ || details_.window_component != HTCAPTION)
+ return;
+
+ // When the window is not in the normal show state, we do not snap the window.
+ // This happens when the user minimizes or maximizes the window by keyboard
+ // shortcut while dragging it. If the window is the result of dragging a tab
+ // out of a maximized window, it's already in the normal show state when this
+ // is called, so it does not matter.
+ if (wm::IsWindowNormal(window()) &&
+ (window()->type() != aura::client::WINDOW_TYPE_PANEL ||
+ !window()->GetProperty(kPanelAttachedKey)) &&
+ (snap_type_ == SNAP_LEFT_EDGE || snap_type_ == SNAP_RIGHT_EDGE)) {
+ if (!GetRestoreBoundsInScreen(window())) {
+ gfx::Rect initial_bounds = ScreenAsh::ConvertRectToScreen(
+ window()->parent(), details_.initial_bounds_in_parent);
+ SetRestoreBoundsInScreen(window(), details_.restore_bounds.IsEmpty() ?
+ initial_bounds :
+ details_.restore_bounds);
+ }
+ window()->SetBounds(snap_sizer_->target_bounds());
+ return;
+ }
+}
+
+void WorkspaceWindowResizer::RevertDrag() {
+ snap_phantom_window_controller_.reset();
+
+ if (!did_move_or_resize_)
+ return;
+
+ window()->SetBounds(details_.initial_bounds_in_parent);
+ if (!details_.restore_bounds.IsEmpty())
+ SetRestoreBoundsInScreen(details_.window, details_.restore_bounds);
+
+ if (details_.window_component == HTRIGHT) {
+ int last_x = details_.initial_bounds_in_parent.right();
+ for (size_t i = 0; i < attached_windows_.size(); ++i) {
+ gfx::Rect bounds(attached_windows_[i]->bounds());
+ bounds.set_x(last_x);
+ bounds.set_width(initial_size_[i]);
+ attached_windows_[i]->SetBounds(bounds);
+ last_x = attached_windows_[i]->bounds().right();
+ }
+ } else {
+ int last_y = details_.initial_bounds_in_parent.bottom();
+ for (size_t i = 0; i < attached_windows_.size(); ++i) {
+ gfx::Rect bounds(attached_windows_[i]->bounds());
+ bounds.set_y(last_y);
+ bounds.set_height(initial_size_[i]);
+ attached_windows_[i]->SetBounds(bounds);
+ last_y = attached_windows_[i]->bounds().bottom();
+ }
+ }
+}
+
+aura::Window* WorkspaceWindowResizer::GetTarget() {
+ return details_.window;
+}
+
+const gfx::Point& WorkspaceWindowResizer::GetInitialLocation() const {
+ return details_.initial_location_in_parent;
+}
+
+WorkspaceWindowResizer::WorkspaceWindowResizer(
+ const Details& details,
+ const std::vector<aura::Window*>& attached_windows)
+ : details_(details),
+ attached_windows_(attached_windows),
+ did_move_or_resize_(false),
+ total_min_(0),
+ total_initial_size_(0),
+ snap_type_(SNAP_NONE),
+ num_mouse_moves_since_bounds_change_(0),
+ magnetism_window_(NULL) {
+ DCHECK(details_.is_resizable);
+
+ Shell* shell = Shell::GetInstance();
+ shell->cursor_manager()->LockCursor();
+
+ // Only support attaching to the right/bottom.
+ DCHECK(attached_windows_.empty() ||
+ (details.window_component == HTRIGHT ||
+ details.window_component == HTBOTTOM));
+
+ // TODO: figure out how to deal with window going off the edge.
+
+ // Calculate sizes so that we can maintain the ratios if we need to resize.
+ int total_available = 0;
+ for (size_t i = 0; i < attached_windows_.size(); ++i) {
+ gfx::Size min(attached_windows_[i]->delegate()->GetMinimumSize());
+ int initial_size = PrimaryAxisSize(attached_windows_[i]->bounds().size());
+ initial_size_.push_back(initial_size);
+ // If current size is smaller than the min, use the current size as the min.
+ // This way we don't snap on resize.
+ int min_size = std::min(initial_size,
+ std::max(PrimaryAxisSize(min), kMinOnscreenSize));
+ total_min_ += min_size;
+ total_initial_size_ += initial_size;
+ total_available += std::max(min_size, initial_size) - min_size;
+ }
+}
+
+gfx::Rect WorkspaceWindowResizer::GetFinalBounds(
+ const gfx::Rect& bounds) const {
+ if (snap_phantom_window_controller_.get() &&
+ snap_phantom_window_controller_->IsShowing()) {
+ return snap_phantom_window_controller_->bounds_in_screen();
+ }
+ return bounds;
+}
+
+void WorkspaceWindowResizer::LayoutAttachedWindows(
+ gfx::Rect* bounds) {
+ gfx::Rect work_area(ScreenAsh::GetDisplayWorkAreaBoundsInParent(window()));
+ int initial_size = PrimaryAxisSize(details_.initial_bounds_in_parent.size());
+ int current_size = PrimaryAxisSize(bounds->size());
+ int start = PrimaryAxisCoordinate(bounds->right(), bounds->bottom());
+ int end = PrimaryAxisCoordinate(work_area.right(), work_area.bottom());
+
+ int delta = current_size - initial_size;
+ int available_size = end - start;
+ std::vector<int> sizes;
+ int leftovers = CalculateAttachedSizes(delta, available_size, &sizes);
+
+ // leftovers > 0 means that the attached windows can't grow to compensate for
+ // the shrinkage of the main window. This line causes the attached windows to
+ // be moved so they are still flush against the main window, rather than the
+ // main window being prevented from shrinking.
+ leftovers = std::min(0, leftovers);
+ // Reallocate any leftover pixels back into the main window. This is
+ // necessary when, for example, the main window shrinks, but none of the
+ // attached windows can grow without exceeding their max size constraints.
+ // Adding the pixels back to the main window effectively prevents the main
+ // window from resizing too far.
+ if (details_.window_component == HTRIGHT)
+ bounds->set_width(bounds->width() + leftovers);
+ else
+ bounds->set_height(bounds->height() + leftovers);
+
+ DCHECK_EQ(attached_windows_.size(), sizes.size());
+ int last = PrimaryAxisCoordinate(bounds->right(), bounds->bottom());
+ for (size_t i = 0; i < attached_windows_.size(); ++i) {
+ gfx::Rect attached_bounds(attached_windows_[i]->bounds());
+ if (details_.window_component == HTRIGHT) {
+ attached_bounds.set_x(last);
+ attached_bounds.set_width(sizes[i]);
+ } else {
+ attached_bounds.set_y(last);
+ attached_bounds.set_height(sizes[i]);
+ }
+ attached_windows_[i]->SetBounds(attached_bounds);
+ last += sizes[i];
+ }
+}
+
+int WorkspaceWindowResizer::CalculateAttachedSizes(
+ int delta,
+ int available_size,
+ std::vector<int>* sizes) const {
+ std::vector<WindowSize> window_sizes;
+ CreateBucketsForAttached(&window_sizes);
+
+ // How much we need to grow the attached by (collectively).
+ int grow_attached_by = 0;
+ if (delta > 0) {
+ // If the attached windows don't fit when at their initial size, we will
+ // have to shrink them by how much they overflow.
+ if (total_initial_size_ >= available_size)
+ grow_attached_by = available_size - total_initial_size_;
+ } else {
+ // If we're shrinking, we grow the attached so the total size remains
+ // constant.
+ grow_attached_by = -delta;
+ }
+
+ int leftover_pixels = 0;
+ while (grow_attached_by != 0) {
+ int leftovers = GrowFairly(grow_attached_by, window_sizes);
+ if (leftovers == grow_attached_by) {
+ leftover_pixels = leftovers;
+ break;
+ }
+ grow_attached_by = leftovers;
+ }
+
+ for (size_t i = 0; i < window_sizes.size(); ++i)
+ sizes->push_back(window_sizes[i].size());
+
+ return leftover_pixels;
+}
+
+int WorkspaceWindowResizer::GrowFairly(
+ int pixels,
+ std::vector<WindowSize>& sizes) const {
+ bool shrinking = pixels < 0;
+ std::vector<WindowSize*> nonfull_windows;
+ for (size_t i = 0; i < sizes.size(); ++i) {
+ if (!sizes[i].is_at_capacity(shrinking))
+ nonfull_windows.push_back(&sizes[i]);
+ }
+ std::vector<float> ratios;
+ CalculateGrowthRatios(nonfull_windows, &ratios);
+
+ int remaining_pixels = pixels;
+ bool add_leftover_pixels_to_last = true;
+ for (size_t i = 0; i < nonfull_windows.size(); ++i) {
+ int grow_by = pixels * ratios[i];
+ // Put any leftover pixels into the last window.
+ if (i == nonfull_windows.size() - 1 && add_leftover_pixels_to_last)
+ grow_by = remaining_pixels;
+ int remainder = nonfull_windows[i]->Add(grow_by);
+ int consumed = grow_by - remainder;
+ remaining_pixels -= consumed;
+ if (nonfull_windows[i]->is_at_capacity(shrinking) && remainder > 0) {
+ // Because this window overflowed, some of the pixels in
+ // |remaining_pixels| aren't there due to rounding errors. Rather than
+ // unfairly giving all those pixels to the last window, we refrain from
+ // allocating them so that this function can be called again to distribute
+ // the pixels fairly.
+ add_leftover_pixels_to_last = false;
+ }
+ }
+ return remaining_pixels;
+}
+
+void WorkspaceWindowResizer::CalculateGrowthRatios(
+ const std::vector<WindowSize*>& sizes,
+ std::vector<float>* out_ratios) const {
+ DCHECK(out_ratios->empty());
+ int total_value = 0;
+ for (size_t i = 0; i < sizes.size(); ++i)
+ total_value += sizes[i]->size();
+
+ for (size_t i = 0; i < sizes.size(); ++i)
+ out_ratios->push_back(
+ (static_cast<float>(sizes[i]->size())) / total_value);
+}
+
+void WorkspaceWindowResizer::CreateBucketsForAttached(
+ std::vector<WindowSize>* sizes) const {
+ for (size_t i = 0; i < attached_windows_.size(); i++) {
+ int initial_size = initial_size_[i];
+ aura::WindowDelegate* delegate = attached_windows_[i]->delegate();
+ int min = PrimaryAxisSize(delegate->GetMinimumSize());
+ int max = PrimaryAxisSize(delegate->GetMaximumSize());
+
+ sizes->push_back(WindowSize(initial_size, min, max));
+ }
+}
+
+void WorkspaceWindowResizer::MagneticallySnapToOtherWindows(gfx::Rect* bounds) {
+ if (UpdateMagnetismWindow(*bounds, kAllMagnetismEdges)) {
+ gfx::Point point = OriginForMagneticAttach(
+ ScreenAsh::ConvertRectToScreen(window()->parent(), *bounds),
+ magnetism_window_->GetBoundsInScreen(),
+ magnetism_edge_);
+ aura::client::GetScreenPositionClient(window()->GetRootWindow())->
+ ConvertPointFromScreen(window()->parent(), &point);
+ bounds->set_origin(point);
+ }
+}
+
+void WorkspaceWindowResizer::MagneticallySnapResizeToOtherWindows(
+ gfx::Rect* bounds) {
+ const uint32 edges = WindowComponentToMagneticEdge(details_.window_component);
+ if (UpdateMagnetismWindow(*bounds, edges)) {
+ *bounds = ScreenAsh::ConvertRectFromScreen(
+ window()->parent(),
+ BoundsForMagneticResizeAttach(
+ ScreenAsh::ConvertRectToScreen(window()->parent(), *bounds),
+ magnetism_window_->GetBoundsInScreen(),
+ magnetism_edge_));
+ }
+}
+
+bool WorkspaceWindowResizer::UpdateMagnetismWindow(const gfx::Rect& bounds,
+ uint32 edges) {
+ // |bounds| are in coordinates of original window's parent.
+ gfx::Rect bounds_in_screen =
+ ScreenAsh::ConvertRectToScreen(window()->parent(), bounds);
+ MagnetismMatcher matcher(bounds_in_screen, edges);
+
+ // If we snapped to a window then check it first. That way we don't bounce
+ // around when close to multiple edges.
+ if (magnetism_window_) {
+ if (window_tracker_.Contains(magnetism_window_) &&
+ matcher.ShouldAttach(magnetism_window_->GetBoundsInScreen(),
+ &magnetism_edge_)) {
+ return true;
+ }
+ window_tracker_.Remove(magnetism_window_);
+ magnetism_window_ = NULL;
+ }
+
+ // Avoid magnetically snapping to popups, menus, tooltips, controls and
+ // windows that are not tracked by workspace.
+ if (!wm::CanResizeWindow(window()) || !GetTrackedByWorkspace(window()))
+ return false;
+
+ Shell::RootWindowList root_windows = Shell::GetAllRootWindows();
+ for (Shell::RootWindowList::iterator iter = root_windows.begin();
+ iter != root_windows.end(); ++iter) {
+ const aura::RootWindow* root_window = *iter;
+ // Test all children from the desktop in each root window.
+ const aura::Window::Windows& children = Shell::GetContainer(
+ root_window, kShellWindowId_DefaultContainer)->children();
+ for (aura::Window::Windows::const_reverse_iterator i = children.rbegin();
+ i != children.rend() && !matcher.AreEdgesObscured(); ++i) {
+ aura::Window* other = *i;
+ if (other == window() ||
+ !other->IsVisible() ||
+ !wm::IsWindowNormal(other) ||
+ !wm::CanResizeWindow(other)) {
+ continue;
+ }
+ if (matcher.ShouldAttach(other->GetBoundsInScreen(), &magnetism_edge_)) {
+ magnetism_window_ = other;
+ window_tracker_.Add(magnetism_window_);
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+void WorkspaceWindowResizer::AdjustBoundsForMainWindow(
+ int sticky_size,
+ gfx::Rect* bounds) {
+ gfx::Point last_mouse_location_in_screen = last_mouse_location_;
+ wm::ConvertPointToScreen(window()->parent(), &last_mouse_location_in_screen);
+ gfx::Display display = Shell::GetScreen()->GetDisplayNearestPoint(
+ last_mouse_location_in_screen);
+ gfx::Rect work_area =
+ ScreenAsh::ConvertRectFromScreen(window()->parent(), display.work_area());
+ if (details_.window_component == HTCAPTION) {
+ // Adjust the bounds to the work area where the mouse cursor is located.
+ // Always keep kMinOnscreenHeight on the bottom.
+ int max_y = work_area.bottom() - kMinOnscreenHeight;
+ if (bounds->y() > max_y) {
+ bounds->set_y(max_y);
+ } else if (bounds->y() <= work_area.y()) {
+ // Don't allow dragging above the top of the display until the mouse
+ // cursor reaches the work area above if any.
+ bounds->set_y(work_area.y());
+ }
+
+ if (sticky_size > 0) {
+ // Possibly stick to edge except when a mouse pointer is outside the
+ // work area.
+ if (!(display.work_area().Contains(last_mouse_location_in_screen) &&
+ StickToWorkAreaOnMove(work_area, sticky_size, bounds))) {
+ MagneticallySnapToOtherWindows(bounds);
+ }
+ }
+ } else if (sticky_size > 0) {
+ MagneticallySnapResizeToOtherWindows(bounds);
+ if (!magnetism_window_ && sticky_size > 0)
+ StickToWorkAreaOnResize(work_area, sticky_size, bounds);
+ }
+
+ if (attached_windows_.empty())
+ return;
+
+ if (details_.window_component == HTRIGHT) {
+ bounds->set_width(std::min(bounds->width(),
+ work_area.right() - total_min_ - bounds->x()));
+ } else {
+ DCHECK_EQ(HTBOTTOM, details_.window_component);
+ bounds->set_height(std::min(bounds->height(),
+ work_area.bottom() - total_min_ - bounds->y()));
+ }
+}
+
+bool WorkspaceWindowResizer::StickToWorkAreaOnMove(
+ const gfx::Rect& work_area,
+ int sticky_size,
+ gfx::Rect* bounds) const {
+ const int left_edge = work_area.x();
+ const int right_edge = work_area.right();
+ const int top_edge = work_area.y();
+ const int bottom_edge = work_area.bottom();
+ if (ShouldStickToEdge(bounds->x() - left_edge, sticky_size)) {
+ bounds->set_x(left_edge);
+ return true;
+ } else if (ShouldStickToEdge(right_edge - bounds->right(), sticky_size)) {
+ bounds->set_x(right_edge - bounds->width());
+ return true;
+ }
+ if (ShouldStickToEdge(bounds->y() - top_edge, sticky_size)) {
+ bounds->set_y(top_edge);
+ return true;
+ } else if (ShouldStickToEdge(bottom_edge - bounds->bottom(), sticky_size) &&
+ bounds->height() < (bottom_edge - top_edge)) {
+ // Only snap to the bottom if the window is smaller than the work area.
+ // Doing otherwise can lead to window snapping in weird ways as it bounces
+ // between snapping to top then bottom.
+ bounds->set_y(bottom_edge - bounds->height());
+ return true;
+ }
+ return false;
+}
+
+void WorkspaceWindowResizer::StickToWorkAreaOnResize(
+ const gfx::Rect& work_area,
+ int sticky_size,
+ gfx::Rect* bounds) const {
+ const uint32 edges = WindowComponentToMagneticEdge(details_.window_component);
+ const int left_edge = work_area.x();
+ const int right_edge = work_area.right();
+ const int top_edge = work_area.y();
+ const int bottom_edge = work_area.bottom();
+ if (edges & MAGNETISM_EDGE_TOP &&
+ ShouldStickToEdge(bounds->y() - top_edge, sticky_size)) {
+ bounds->set_height(bounds->bottom() - top_edge);
+ bounds->set_y(top_edge);
+ }
+ if (edges & MAGNETISM_EDGE_LEFT &&
+ ShouldStickToEdge(bounds->x() - left_edge, sticky_size)) {
+ bounds->set_width(bounds->right() - left_edge);
+ bounds->set_x(left_edge);
+ }
+ if (edges & MAGNETISM_EDGE_BOTTOM &&
+ ShouldStickToEdge(bottom_edge - bounds->bottom(), sticky_size)) {
+ bounds->set_height(bottom_edge - bounds->y());
+ }
+ if (edges & MAGNETISM_EDGE_RIGHT &&
+ ShouldStickToEdge(right_edge - bounds->right(), sticky_size)) {
+ bounds->set_width(right_edge - bounds->x());
+ }
+}
+
+int WorkspaceWindowResizer::PrimaryAxisSize(const gfx::Size& size) const {
+ return PrimaryAxisCoordinate(size.width(), size.height());
+}
+
+int WorkspaceWindowResizer::PrimaryAxisCoordinate(int x, int y) const {
+ switch (details_.window_component) {
+ case HTRIGHT:
+ return x;
+ case HTBOTTOM:
+ return y;
+ default:
+ NOTREACHED();
+ }
+ return 0;
+}
+
+void WorkspaceWindowResizer::UpdateSnapPhantomWindow(const gfx::Point& location,
+ const gfx::Rect& bounds) {
+ if (!did_move_or_resize_ || details_.window_component != HTCAPTION)
+ return;
+
+ if (!wm::CanSnapWindow(window()))
+ return;
+
+ if (window()->type() == aura::client::WINDOW_TYPE_PANEL &&
+ window()->GetProperty(kPanelAttachedKey)) {
+ return;
+ }
+
+ SnapType last_type = snap_type_;
+ snap_type_ = GetSnapType(location);
+ if (snap_type_ == SNAP_NONE || snap_type_ != last_type) {
+ snap_phantom_window_controller_.reset();
+ snap_sizer_.reset();
+ if (snap_type_ == SNAP_NONE)
+ return;
+ }
+ if (!snap_sizer_) {
+ SnapSizer::Edge edge = (snap_type_ == SNAP_LEFT_EDGE) ?
+ SnapSizer::LEFT_EDGE : SnapSizer::RIGHT_EDGE;
+ snap_sizer_.reset(new SnapSizer(window(),
+ location,
+ edge,
+ internal::SnapSizer::OTHER_INPUT));
+ } else {
+ snap_sizer_->Update(location);
+ }
+ if (!snap_phantom_window_controller_) {
+ snap_phantom_window_controller_.reset(
+ new PhantomWindowController(window()));
+ }
+ snap_phantom_window_controller_->Show(ScreenAsh::ConvertRectToScreen(
+ window()->parent(), snap_sizer_->target_bounds()));
+}
+
+void WorkspaceWindowResizer::RestackWindows() {
+ if (attached_windows_.empty())
+ return;
+ // Build a map from index in children to window, returning if there is a
+ // window with a different parent.
+ typedef std::map<size_t, aura::Window*> IndexToWindowMap;
+ IndexToWindowMap map;
+ aura::Window* parent = window()->parent();
+ const aura::Window::Windows& windows(parent->children());
+ map[std::find(windows.begin(), windows.end(), window()) -
+ windows.begin()] = window();
+ for (std::vector<aura::Window*>::const_iterator i =
+ attached_windows_.begin(); i != attached_windows_.end(); ++i) {
+ if ((*i)->parent() != parent)
+ return;
+ size_t index =
+ std::find(windows.begin(), windows.end(), *i) - windows.begin();
+ map[index] = *i;
+ }
+
+ // Reorder the windows starting at the topmost.
+ parent->StackChildAtTop(map.rbegin()->second);
+ for (IndexToWindowMap::const_reverse_iterator i = map.rbegin();
+ i != map.rend(); ) {
+ aura::Window* window = i->second;
+ ++i;
+ if (i != map.rend())
+ parent->StackChildBelow(i->second, window);
+ }
+}
+
+WorkspaceWindowResizer::SnapType WorkspaceWindowResizer::GetSnapType(
+ const gfx::Point& location) const {
+ // TODO: this likely only wants total display area, not the area of a single
+ // display.
+ gfx::Rect area(ScreenAsh::GetDisplayBoundsInParent(window()));
+ if (location.x() <= area.x())
+ return SNAP_LEFT_EDGE;
+ if (location.x() >= area.right() - 1)
+ return SNAP_RIGHT_EDGE;
+ return SNAP_NONE;
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/wm/workspace/workspace_window_resizer.h b/chromium/ash/wm/workspace/workspace_window_resizer.h
new file mode 100644
index 00000000000..76176b1a2b4
--- /dev/null
+++ b/chromium/ash/wm/workspace/workspace_window_resizer.h
@@ -0,0 +1,220 @@
+// Copyright (c) 2012 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 ASH_WM_WORKSPACE_WINDOW_RESIZER_H_
+#define ASH_WM_WORKSPACE_WINDOW_RESIZER_H_
+
+#include <vector>
+
+#include "ash/wm/window_resizer.h"
+#include "ash/wm/workspace/magnetism_matcher.h"
+#include "base/compiler_specific.h"
+#include "base/gtest_prod_util.h"
+#include "base/memory/scoped_ptr.h"
+#include "ui/aura/window_tracker.h"
+
+namespace ash {
+namespace internal {
+
+class PhantomWindowController;
+class SnapSizer;
+class WindowSize;
+
+// WindowResizer implementation for workspaces. This enforces that windows are
+// not allowed to vertically move or resize outside of the work area. As windows
+// are moved outside the work area they are shrunk. We remember the height of
+// the window before it was moved so that if the window is again moved up we
+// attempt to restore the old height.
+class ASH_EXPORT WorkspaceWindowResizer : public WindowResizer {
+ public:
+ // When dragging an attached window this is the min size we'll make sure is
+ // visible. In the vertical direction we take the max of this and that from
+ // the delegate.
+ static const int kMinOnscreenSize;
+
+ // Min height we'll force on screen when dragging the caption.
+ // TODO: this should come from a property on the window.
+ static const int kMinOnscreenHeight;
+
+ // Snap region when dragging close to the edges. That is, as the window gets
+ // this close to an edge of the screen it snaps to the edge.
+ static const int kScreenEdgeInset;
+
+ // Distance in pixels that the cursor must move past an edge for a window
+ // to move or resize beyond that edge.
+ static const int kStickyDistancePixels;
+
+ virtual ~WorkspaceWindowResizer();
+
+ static WorkspaceWindowResizer* Create(
+ aura::Window* window,
+ const gfx::Point& location_in_parent,
+ int window_component,
+ aura::client::WindowMoveSource source,
+ const std::vector<aura::Window*>& attached_windows);
+
+ // WindowResizer:
+ virtual void Drag(const gfx::Point& location_in_parent,
+ int event_flags) OVERRIDE;
+ virtual void CompleteDrag(int event_flags) OVERRIDE;
+ virtual void RevertDrag() OVERRIDE;
+ virtual aura::Window* GetTarget() OVERRIDE;
+ virtual const gfx::Point& GetInitialLocation() const OVERRIDE;
+
+ private:
+ WorkspaceWindowResizer(const Details& details,
+ const std::vector<aura::Window*>& attached_windows);
+
+ private:
+ FRIEND_TEST_ALL_PREFIXES(WorkspaceWindowResizerTest, CancelSnapPhantom);
+ FRIEND_TEST_ALL_PREFIXES(WorkspaceWindowResizerTest, PhantomSnapMaxSize);
+ FRIEND_TEST_ALL_PREFIXES(WorkspaceWindowResizerTest, PhantomWindowShow);
+
+ // Type of snapping.
+ enum SnapType {
+ // Snap to the left/right edge of the screen.
+ SNAP_LEFT_EDGE,
+ SNAP_RIGHT_EDGE,
+
+ // No snap position.
+ SNAP_NONE
+ };
+
+ // Returns the final bounds to place the window at. This differs from
+ // the current when snapping.
+ gfx::Rect GetFinalBounds(const gfx::Rect& bounds) const;
+
+ // Lays out the attached windows. |bounds| is the bounds of the main window.
+ void LayoutAttachedWindows(gfx::Rect* bounds);
+
+ // Calculates the new sizes of the attached windows, given that the main
+ // window has been resized (along the primary axis) by |delta|.
+ // |available_size| is the maximum length of the space that the attached
+ // windows are allowed to occupy (ie: the distance between the right/bottom
+ // edge of the primary window and the right/bottom of the desktop area).
+ // Populates |sizes| with the desired sizes of the attached windows, and
+ // returns the number of pixels that couldn't be allocated to the attached
+ // windows (due to min/max size constraints).
+ // Note the return value can be positive or negative, a negative value
+ // indicating that that many pixels couldn't be removed from the attached
+ // windows.
+ int CalculateAttachedSizes(
+ int delta,
+ int available_size,
+ std::vector<int>* sizes) const;
+
+ // Divides |amount| evenly between |sizes|. If |amount| is negative it
+ // indicates how many pixels |sizes| should be shrunk by.
+ // Returns how many pixels failed to be allocated/removed from |sizes|.
+ int GrowFairly(int amount, std::vector<WindowSize>& sizes) const;
+
+ // Calculate the ratio of pixels that each WindowSize in |sizes| should
+ // receive when growing or shrinking.
+ void CalculateGrowthRatios(const std::vector<WindowSize*>& sizes,
+ std::vector<float>* out_ratios) const;
+
+ // Adds a WindowSize to |sizes| for each attached window.
+ void CreateBucketsForAttached(std::vector<WindowSize>* sizes) const;
+
+ // If possible snaps the window to a neary window. Updates |bounds| if there
+ // was a close enough window.
+ void MagneticallySnapToOtherWindows(gfx::Rect* bounds);
+
+ // If possible snaps the resize to a neary window. Updates |bounds| if there
+ // was a close enough window.
+ void MagneticallySnapResizeToOtherWindows(gfx::Rect* bounds);
+
+ // Finds the neareset window to magentically snap to. Updates
+ // |magnetism_window_| and |magnetism_edge_| appropriately. |edges| is a
+ // bitmask of the MagnetismEdges to match again. Returns true if a match is
+ // found.
+ bool UpdateMagnetismWindow(const gfx::Rect& bounds, uint32 edges);
+
+ // Adjusts the bounds of the window: magnetically snapping, ensuring the
+ // window has enough on screen... |snap_size| is the distance from an edge of
+ // the work area before the window is snapped. A value of 0 results in no
+ // snapping.
+ void AdjustBoundsForMainWindow(int snap_size, gfx::Rect* bounds);
+
+ // Stick the window bounds to the work area during a move.
+ bool StickToWorkAreaOnMove(const gfx::Rect& work_area,
+ int sticky_size,
+ gfx::Rect* bounds) const;
+
+ // Stick the window bounds to the work area during a resize.
+ void StickToWorkAreaOnResize(const gfx::Rect& work_area,
+ int sticky_size,
+ gfx::Rect* bounds) const;
+
+ // Returns a coordinate along the primary axis. Used to share code for
+ // left/right multi window resize and top/bottom resize.
+ int PrimaryAxisSize(const gfx::Size& size) const;
+ int PrimaryAxisCoordinate(int x, int y) const;
+
+ // Updates the bounds of the phantom window for window snapping.
+ void UpdateSnapPhantomWindow(const gfx::Point& location,
+ const gfx::Rect& bounds);
+
+ // Restacks the windows z-order position so that one of the windows is at the
+ // top of the z-order, and the rest directly underneath it.
+ void RestackWindows();
+
+ // Returns the SnapType for the specified point. SNAP_NONE is used if no
+ // snapping should be used.
+ SnapType GetSnapType(const gfx::Point& location) const;
+
+ aura::Window* window() const { return details_.window; }
+
+ const Details details_;
+
+ const std::vector<aura::Window*> attached_windows_;
+
+ // Set to true once Drag() is invoked and the bounds of the window change.
+ bool did_move_or_resize_;
+
+ // The initial size of each of the windows in |attached_windows_| along the
+ // primary axis.
+ std::vector<int> initial_size_;
+
+ // Sum of the minimum sizes of the attached windows.
+ int total_min_;
+
+ // Sum of the sizes in |initial_size_|.
+ int total_initial_size_;
+
+ // Gives a previews of where the the window will end up. Only used if there
+ // is a grid and the caption is being dragged.
+ scoped_ptr<PhantomWindowController> snap_phantom_window_controller_;
+
+ // Used to determine the target position of a snap.
+ scoped_ptr<SnapSizer> snap_sizer_;
+
+ // Last SnapType.
+ SnapType snap_type_;
+
+ // Number of mouse moves since the last bounds change. Only used for phantom
+ // placement to track when the mouse is moved while pushed against the edge of
+ // the screen.
+ int num_mouse_moves_since_bounds_change_;
+
+ // The mouse location passed to Drag().
+ gfx::Point last_mouse_location_;
+
+ // Window the drag has magnetically attached to.
+ aura::Window* magnetism_window_;
+
+ // Used to verify |magnetism_window_| is still valid.
+ aura::WindowTracker window_tracker_;
+
+ // If |magnetism_window_| is non-NULL this indicates how the two windows
+ // should attach.
+ MatchedEdge magnetism_edge_;
+
+ DISALLOW_COPY_AND_ASSIGN(WorkspaceWindowResizer);
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_WM_WORKSPACE_WINDOW_RESIZER_H_
diff --git a/chromium/ash/wm/workspace/workspace_window_resizer_unittest.cc b/chromium/ash/wm/workspace/workspace_window_resizer_unittest.cc
new file mode 100644
index 00000000000..8459c54ef5f
--- /dev/null
+++ b/chromium/ash/wm/workspace/workspace_window_resizer_unittest.cc
@@ -0,0 +1,1978 @@
+// Copyright (c) 2012 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.
+
+#include "ash/wm/workspace/workspace_window_resizer.h"
+
+#include "ash/ash_constants.h"
+#include "ash/ash_switches.h"
+#include "ash/display/display_controller.h"
+#include "ash/root_window_controller.h"
+#include "ash/screen_ash.h"
+#include "ash/shelf/shelf_layout_manager.h"
+#include "ash/shell.h"
+#include "ash/shell_window_ids.h"
+#include "ash/test/ash_test_base.h"
+#include "ash/wm/property_util.h"
+#include "ash/wm/window_util.h"
+#include "ash/wm/workspace/phantom_window_controller.h"
+#include "ash/wm/workspace/snap_sizer.h"
+#include "ash/wm/workspace_controller.h"
+#include "base/command_line.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/stringprintf.h"
+#include "ui/aura/client/aura_constants.h"
+#include "ui/aura/root_window.h"
+#include "ui/aura/test/event_generator.h"
+#include "ui/aura/test/test_window_delegate.h"
+#include "ui/base/hit_test.h"
+#include "ui/gfx/insets.h"
+#include "ui/gfx/screen.h"
+#include "ui/views/widget/widget.h"
+
+namespace ui {
+
+// Class to provide access to SlideAnimation internals for testing.
+class SlideAnimation::TestApi {
+ public:
+ explicit TestApi(SlideAnimation* animation) : animation_(animation) {}
+
+ void SetStartTime(base::TimeTicks ticks) {
+ animation_->SetStartTime(ticks);
+ }
+
+ void Step(base::TimeTicks ticks) {
+ animation_->Step(ticks);
+ }
+
+ void RunTillComplete() {
+ SetStartTime(base::TimeTicks());
+ Step(base::TimeTicks() +
+ base::TimeDelta::FromMilliseconds(animation_->GetSlideDuration()));
+ EXPECT_EQ(1.0, animation_->GetCurrentValue());
+ }
+
+ private:
+ SlideAnimation* animation_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestApi);
+};
+
+}
+
+namespace ash {
+namespace internal {
+namespace {
+
+const int kRootHeight = 600;
+
+// A simple window delegate that returns the specified min size.
+class TestWindowDelegate : public aura::test::TestWindowDelegate {
+ public:
+ TestWindowDelegate() {
+ }
+ virtual ~TestWindowDelegate() {}
+
+ void set_min_size(const gfx::Size& size) {
+ min_size_ = size;
+ }
+
+ void set_max_size(const gfx::Size& size) {
+ max_size_ = size;
+ }
+
+ private:
+ // Overridden from aura::Test::TestWindowDelegate:
+ virtual gfx::Size GetMinimumSize() const OVERRIDE {
+ return min_size_;
+ }
+
+ virtual gfx::Size GetMaximumSize() const OVERRIDE {
+ return max_size_;
+ }
+
+ gfx::Size min_size_;
+ gfx::Size max_size_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestWindowDelegate);
+};
+
+class WorkspaceWindowResizerTest : public test::AshTestBase {
+ public:
+ WorkspaceWindowResizerTest() {}
+ virtual ~WorkspaceWindowResizerTest() {}
+
+ virtual void SetUp() OVERRIDE {
+ AshTestBase::SetUp();
+ UpdateDisplay(base::StringPrintf("800x%d", kRootHeight));
+
+ aura::RootWindow* root = Shell::GetPrimaryRootWindow();
+ gfx::Rect root_bounds(root->bounds());
+#if defined(OS_WIN)
+ // RootWindow and Display can't resize on Windows Ash.
+ // http://crbug.com/165962
+ EXPECT_EQ(kRootHeight, root_bounds.height());
+#endif
+ EXPECT_EQ(800, root_bounds.width());
+ Shell::GetInstance()->SetDisplayWorkAreaInsets(root, gfx::Insets());
+ window_.reset(new aura::Window(&delegate_));
+ window_->SetType(aura::client::WINDOW_TYPE_NORMAL);
+ window_->Init(ui::LAYER_NOT_DRAWN);
+ SetDefaultParentByPrimaryRootWindow(window_.get());
+ window_->set_id(1);
+
+ window2_.reset(new aura::Window(&delegate2_));
+ window2_->SetType(aura::client::WINDOW_TYPE_NORMAL);
+ window2_->Init(ui::LAYER_NOT_DRAWN);
+ SetDefaultParentByPrimaryRootWindow(window2_.get());
+ window2_->set_id(2);
+
+ window3_.reset(new aura::Window(&delegate3_));
+ window3_->SetType(aura::client::WINDOW_TYPE_NORMAL);
+ window3_->Init(ui::LAYER_NOT_DRAWN);
+ SetDefaultParentByPrimaryRootWindow(window3_.get());
+ window3_->set_id(3);
+
+ window4_.reset(new aura::Window(&delegate4_));
+ window4_->SetType(aura::client::WINDOW_TYPE_NORMAL);
+ window4_->Init(ui::LAYER_NOT_DRAWN);
+ SetDefaultParentByPrimaryRootWindow(window4_.get());
+ window4_->set_id(4);
+ }
+
+ virtual void TearDown() OVERRIDE {
+ window_.reset();
+ window2_.reset();
+ window3_.reset();
+ window4_.reset();
+ touch_resize_window_.reset();
+ AshTestBase::TearDown();
+ }
+
+ // Returns a string identifying the z-order of each of the known child windows
+ // of |parent|. The returned string constains the id of the known windows and
+ // is ordered from topmost to bottomost windows.
+ std::string WindowOrderAsString(aura::Window* parent) const {
+ std::string result;
+ const aura::Window::Windows& windows = parent->children();
+ for (aura::Window::Windows::const_reverse_iterator i = windows.rbegin();
+ i != windows.rend(); ++i) {
+ if (*i == window_ || *i == window2_ || *i == window3_) {
+ if (!result.empty())
+ result += " ";
+ result += base::IntToString((*i)->id());
+ }
+ }
+ return result;
+ }
+
+ protected:
+ gfx::Point CalculateDragPoint(const WorkspaceWindowResizer& resizer,
+ int delta_x,
+ int delta_y) const {
+ gfx::Point location = resizer.GetInitialLocation();
+ location.set_x(location.x() + delta_x);
+ location.set_y(location.y() + delta_y);
+ return location;
+ }
+
+ std::vector<aura::Window*> empty_windows() const {
+ return std::vector<aura::Window*>();
+ }
+
+ internal::ShelfLayoutManager* shelf_layout_manager() {
+ return Shell::GetPrimaryRootWindowController()->GetShelfLayoutManager();
+ }
+
+ void InitTouchResizeWindow(const gfx::Rect& bounds, int window_component) {
+ touch_resize_delegate_.set_window_component(window_component);
+ touch_resize_window_.reset(
+ CreateTestWindowInShellWithDelegate(&touch_resize_delegate_, 0,
+ bounds));
+ gfx::Insets mouse_insets = gfx::Insets(-ash::kResizeOutsideBoundsSize,
+ -ash::kResizeOutsideBoundsSize,
+ -ash::kResizeOutsideBoundsSize,
+ -ash::kResizeOutsideBoundsSize);
+ gfx::Insets touch_insets = mouse_insets.Scale(
+ ash::kResizeOutsideBoundsScaleForTouch);
+ touch_resize_window_->SetHitTestBoundsOverrideOuter(mouse_insets,
+ touch_insets);
+ touch_resize_window_->set_hit_test_bounds_override_inner(mouse_insets);
+ }
+
+ // Simulate running the animation.
+ void RunAnimationTillComplete(ui::SlideAnimation* animation) {
+ ui::SlideAnimation::TestApi test_api(animation);
+ test_api.RunTillComplete();
+ }
+
+ TestWindowDelegate delegate_;
+ TestWindowDelegate delegate2_;
+ TestWindowDelegate delegate3_;
+ TestWindowDelegate delegate4_;
+ scoped_ptr<aura::Window> window_;
+ scoped_ptr<aura::Window> window2_;
+ scoped_ptr<aura::Window> window3_;
+ scoped_ptr<aura::Window> window4_;
+
+ TestWindowDelegate touch_resize_delegate_;
+ scoped_ptr<aura::Window> touch_resize_window_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(WorkspaceWindowResizerTest);
+};
+
+class WorkspaceWindowResizerTestSticky : public WorkspaceWindowResizerTest {
+ public:
+ WorkspaceWindowResizerTestSticky() {}
+ virtual ~WorkspaceWindowResizerTestSticky() {}
+
+ virtual void SetUp() OVERRIDE {
+ CommandLine::ForCurrentProcess()->AppendSwitch(
+ ash::switches::kAshEnableStickyEdges);
+ WorkspaceWindowResizerTest::SetUp();
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(WorkspaceWindowResizerTestSticky);
+};
+
+} // namespace
+
+// Assertions around attached window resize dragging from the right with 2
+// windows.
+TEST_F(WorkspaceWindowResizerTest, AttachedResize_RIGHT_2) {
+ window_->SetBounds(gfx::Rect(0, 300, 400, 300));
+ window2_->SetBounds(gfx::Rect(400, 200, 100, 200));
+
+ std::vector<aura::Window*> windows;
+ windows.push_back(window2_.get());
+ scoped_ptr<WorkspaceWindowResizer> resizer(WorkspaceWindowResizer::Create(
+ window_.get(), gfx::Point(), HTRIGHT,
+ aura::client::WINDOW_MOVE_SOURCE_MOUSE, windows));
+ ASSERT_TRUE(resizer.get());
+ // Move it 100 to the right, which should expand w1 and push w2.
+ resizer->Drag(CalculateDragPoint(*resizer, 100, 10), 0);
+ EXPECT_EQ("0,300 500x300", window_->bounds().ToString());
+ EXPECT_EQ("500,200 100x200", window2_->bounds().ToString());
+
+ // Push off the screen, w2 should be resized to its min.
+ delegate2_.set_min_size(gfx::Size(20, 20));
+ resizer->Drag(CalculateDragPoint(*resizer, 800, 20), 0);
+ EXPECT_EQ("0,300 780x300", window_->bounds().ToString());
+ EXPECT_EQ("780,200 20x200", window2_->bounds().ToString());
+
+ // Move back to 100 and verify w2 gets its original size.
+ resizer->Drag(CalculateDragPoint(*resizer, 100, 10), 0);
+ EXPECT_EQ("0,300 500x300", window_->bounds().ToString());
+ EXPECT_EQ("500,200 100x200", window2_->bounds().ToString());
+
+ // Revert and make sure everything moves back.
+ resizer->Drag(CalculateDragPoint(*resizer, 800, 20), 0);
+ resizer->RevertDrag();
+ EXPECT_EQ("0,300 400x300", window_->bounds().ToString());
+ EXPECT_EQ("400,200 100x200", window2_->bounds().ToString());
+}
+
+// Assertions around collapsing and expanding.
+TEST_F(WorkspaceWindowResizerTest, AttachedResize_RIGHT_Compress) {
+ window_->SetBounds(gfx::Rect( 0, 300, 400, 300));
+ window2_->SetBounds(gfx::Rect(400, 200, 100, 200));
+
+ std::vector<aura::Window*> windows;
+ windows.push_back(window2_.get());
+ scoped_ptr<WorkspaceWindowResizer> resizer(WorkspaceWindowResizer::Create(
+ window_.get(), gfx::Point(), HTRIGHT,
+ aura::client::WINDOW_MOVE_SOURCE_MOUSE, windows));
+ ASSERT_TRUE(resizer.get());
+ // Move it 100 to the left, which should expand w2 and collapse w1.
+ resizer->Drag(CalculateDragPoint(*resizer, -100, 10), 0);
+ EXPECT_EQ("0,300 300x300", window_->bounds().ToString());
+ EXPECT_EQ("300,200 200x200", window2_->bounds().ToString());
+
+ // Collapse all the way to w1's min.
+ delegate_.set_min_size(gfx::Size(20, 20));
+ resizer->Drag(CalculateDragPoint(*resizer, -800, 20), 0);
+ EXPECT_EQ("0,300 20x300", window_->bounds().ToString());
+ EXPECT_EQ("20,200 480x200", window2_->bounds().ToString());
+
+ // Move 100 to the left.
+ resizer->Drag(CalculateDragPoint(*resizer, 100, 10), 0);
+ EXPECT_EQ("0,300 500x300", window_->bounds().ToString());
+ EXPECT_EQ("500,200 100x200", window2_->bounds().ToString());
+
+ // Back to -100.
+ resizer->Drag(CalculateDragPoint(*resizer, -100, 20), 0);
+ EXPECT_EQ("0,300 300x300", window_->bounds().ToString());
+ EXPECT_EQ("300,200 200x200", window2_->bounds().ToString());
+}
+
+// Assertions around attached window resize dragging from the right with 3
+// windows.
+TEST_F(WorkspaceWindowResizerTest, AttachedResize_RIGHT_3) {
+ window_->SetBounds(gfx::Rect( 100, 300, 200, 300));
+ window2_->SetBounds(gfx::Rect(300, 300, 150, 200));
+ window3_->SetBounds(gfx::Rect(450, 300, 100, 200));
+ delegate2_.set_min_size(gfx::Size(52, 50));
+ delegate3_.set_min_size(gfx::Size(38, 50));
+
+ std::vector<aura::Window*> windows;
+ windows.push_back(window2_.get());
+ windows.push_back(window3_.get());
+ scoped_ptr<WorkspaceWindowResizer> resizer(WorkspaceWindowResizer::Create(
+ window_.get(), gfx::Point(), HTRIGHT,
+ aura::client::WINDOW_MOVE_SOURCE_MOUSE, windows));
+ ASSERT_TRUE(resizer.get());
+ // Move it 100 to the right, which should expand w1 and push w2 and w3.
+ resizer->Drag(CalculateDragPoint(*resizer, 100, -10), 0);
+ EXPECT_EQ("100,300 300x300", window_->bounds().ToString());
+ EXPECT_EQ("400,300 150x200", window2_->bounds().ToString());
+ EXPECT_EQ("550,300 100x200", window3_->bounds().ToString());
+
+ // Move it 300, things should compress.
+ resizer->Drag(CalculateDragPoint(*resizer, 300, -10), 0);
+ EXPECT_EQ("100,300 500x300", window_->bounds().ToString());
+ EXPECT_EQ("600,300 120x200", window2_->bounds().ToString());
+ EXPECT_EQ("720,300 80x200", window3_->bounds().ToString());
+
+ // Move it so much the last two end up at their min.
+ resizer->Drag(CalculateDragPoint(*resizer, 800, 50), 0);
+ EXPECT_EQ("100,300 610x300", window_->bounds().ToString());
+ EXPECT_EQ("710,300 52x200", window2_->bounds().ToString());
+ EXPECT_EQ("762,300 38x200", window3_->bounds().ToString());
+
+ // Revert and make sure everything moves back.
+ resizer->RevertDrag();
+ EXPECT_EQ("100,300 200x300", window_->bounds().ToString());
+ EXPECT_EQ("300,300 150x200", window2_->bounds().ToString());
+ EXPECT_EQ("450,300 100x200", window3_->bounds().ToString());
+}
+
+// Assertions around attached window resizing (collapsing and expanding) with
+// 3 windows.
+TEST_F(WorkspaceWindowResizerTest, AttachedResize_RIGHT_3_Compress) {
+ window_->SetBounds(gfx::Rect( 100, 300, 200, 300));
+ window2_->SetBounds(gfx::Rect(300, 300, 200, 200));
+ window3_->SetBounds(gfx::Rect(450, 300, 100, 200));
+ delegate2_.set_min_size(gfx::Size(52, 50));
+ delegate3_.set_min_size(gfx::Size(38, 50));
+
+ std::vector<aura::Window*> windows;
+ windows.push_back(window2_.get());
+ windows.push_back(window3_.get());
+ scoped_ptr<WorkspaceWindowResizer> resizer(WorkspaceWindowResizer::Create(
+ window_.get(), gfx::Point(), HTRIGHT,
+ aura::client::WINDOW_MOVE_SOURCE_MOUSE, windows));
+ ASSERT_TRUE(resizer.get());
+ // Move it -100 to the right, which should collapse w1 and expand w2 and w3.
+ resizer->Drag(CalculateDragPoint(*resizer, -100, -10), 0);
+ EXPECT_EQ("100,300 100x300", window_->bounds().ToString());
+ EXPECT_EQ("200,300 266x200", window2_->bounds().ToString());
+ EXPECT_EQ("466,300 134x200", window3_->bounds().ToString());
+
+ // Move it 100 to the right.
+ resizer->Drag(CalculateDragPoint(*resizer, 100, -10), 0);
+ EXPECT_EQ("100,300 300x300", window_->bounds().ToString());
+ EXPECT_EQ("400,300 200x200", window2_->bounds().ToString());
+ EXPECT_EQ("600,300 100x200", window3_->bounds().ToString());
+
+ // 100 to the left again.
+ resizer->Drag(CalculateDragPoint(*resizer, -100, -10), 0);
+ EXPECT_EQ("100,300 100x300", window_->bounds().ToString());
+ EXPECT_EQ("200,300 266x200", window2_->bounds().ToString());
+ EXPECT_EQ("466,300 134x200", window3_->bounds().ToString());
+}
+
+// Assertions around collapsing and expanding from the bottom.
+TEST_F(WorkspaceWindowResizerTest, AttachedResize_BOTTOM_Compress) {
+ window_->SetBounds(gfx::Rect( 0, 100, 400, 300));
+ window2_->SetBounds(gfx::Rect(400, 400, 100, 200));
+
+ std::vector<aura::Window*> windows;
+ windows.push_back(window2_.get());
+ scoped_ptr<WorkspaceWindowResizer> resizer(WorkspaceWindowResizer::Create(
+ window_.get(), gfx::Point(), HTBOTTOM,
+ aura::client::WINDOW_MOVE_SOURCE_MOUSE, windows));
+ ASSERT_TRUE(resizer.get());
+ // Move it up 100, which should expand w2 and collapse w1.
+ resizer->Drag(CalculateDragPoint(*resizer, 10, -100), 0);
+ EXPECT_EQ("0,100 400x200", window_->bounds().ToString());
+ EXPECT_EQ("400,300 100x300", window2_->bounds().ToString());
+
+ // Collapse all the way to w1's min.
+ delegate_.set_min_size(gfx::Size(20, 20));
+ resizer->Drag(CalculateDragPoint(*resizer, 20, -800), 0);
+ EXPECT_EQ("0,100 400x20", window_->bounds().ToString());
+ EXPECT_EQ("400,120 100x480", window2_->bounds().ToString());
+
+ // Move 100 down.
+ resizer->Drag(CalculateDragPoint(*resizer, 10, 100), 0);
+ EXPECT_EQ("0,100 400x400", window_->bounds().ToString());
+ EXPECT_EQ("400,500 100x100", window2_->bounds().ToString());
+
+ // Back to -100.
+ resizer->Drag(CalculateDragPoint(*resizer, 20, -100), 0);
+ EXPECT_EQ("0,100 400x200", window_->bounds().ToString());
+ EXPECT_EQ("400,300 100x300", window2_->bounds().ToString());
+}
+
+// Assertions around attached window resize dragging from the bottom with 2
+// windows.
+TEST_F(WorkspaceWindowResizerTest, AttachedResize_BOTTOM_2) {
+ window_->SetBounds(gfx::Rect( 0, 50, 400, 200));
+ window2_->SetBounds(gfx::Rect(0, 250, 200, 100));
+
+ std::vector<aura::Window*> windows;
+ windows.push_back(window2_.get());
+ scoped_ptr<WorkspaceWindowResizer> resizer(WorkspaceWindowResizer::Create(
+ window_.get(), gfx::Point(), HTBOTTOM,
+ aura::client::WINDOW_MOVE_SOURCE_MOUSE, windows));
+ ASSERT_TRUE(resizer.get());
+ // Move it 100 to the bottom, which should expand w1 and push w2.
+ resizer->Drag(CalculateDragPoint(*resizer, 10, 100), 0);
+ EXPECT_EQ("0,50 400x300", window_->bounds().ToString());
+ EXPECT_EQ("0,350 200x100", window2_->bounds().ToString());
+
+ // Push off the screen, w2 should be resized to its min.
+ delegate2_.set_min_size(gfx::Size(20, 20));
+ resizer->Drag(CalculateDragPoint(*resizer, 50, 820), 0);
+ EXPECT_EQ("0,50 400x530", window_->bounds().ToString());
+ EXPECT_EQ("0,580 200x20", window2_->bounds().ToString());
+
+ // Move back to 100 and verify w2 gets its original size.
+ resizer->Drag(CalculateDragPoint(*resizer, 10, 100), 0);
+ EXPECT_EQ("0,50 400x300", window_->bounds().ToString());
+ EXPECT_EQ("0,350 200x100", window2_->bounds().ToString());
+
+ // Revert and make sure everything moves back.
+ resizer->Drag(CalculateDragPoint(*resizer, 800, 20), 0);
+ resizer->RevertDrag();
+ EXPECT_EQ("0,50 400x200", window_->bounds().ToString());
+ EXPECT_EQ("0,250 200x100", window2_->bounds().ToString());
+}
+
+#if defined(OS_WIN)
+// RootWindow and Display can't resize on Windows Ash. http://crbug.com/165962
+#define MAYBE_AttachedResize_BOTTOM_3 DISABLED_AttachedResize_BOTTOM_3
+#else
+#define MAYBE_AttachedResize_BOTTOM_3 AttachedResize_BOTTOM_3
+#endif
+
+// Assertions around attached window resize dragging from the bottom with 3
+// windows.
+TEST_F(WorkspaceWindowResizerTest, MAYBE_AttachedResize_BOTTOM_3) {
+ UpdateDisplay("600x800");
+ aura::RootWindow* root = Shell::GetPrimaryRootWindow();
+ Shell::GetInstance()->SetDisplayWorkAreaInsets(root, gfx::Insets());
+
+ window_->SetBounds(gfx::Rect( 300, 100, 300, 200));
+ window2_->SetBounds(gfx::Rect(300, 300, 200, 150));
+ window3_->SetBounds(gfx::Rect(300, 450, 200, 100));
+ delegate2_.set_min_size(gfx::Size(50, 52));
+ delegate3_.set_min_size(gfx::Size(50, 38));
+
+ std::vector<aura::Window*> windows;
+ windows.push_back(window2_.get());
+ windows.push_back(window3_.get());
+ scoped_ptr<WorkspaceWindowResizer> resizer(WorkspaceWindowResizer::Create(
+ window_.get(), gfx::Point(), HTBOTTOM,
+ aura::client::WINDOW_MOVE_SOURCE_MOUSE, windows));
+ ASSERT_TRUE(resizer.get());
+ // Move it 100 down, which should expand w1 and push w2 and w3.
+ resizer->Drag(CalculateDragPoint(*resizer, -10, 100), 0);
+ EXPECT_EQ("300,100 300x300", window_->bounds().ToString());
+ EXPECT_EQ("300,400 200x150", window2_->bounds().ToString());
+ EXPECT_EQ("300,550 200x100", window3_->bounds().ToString());
+
+ // Move it 296 things should compress.
+ resizer->Drag(CalculateDragPoint(*resizer, -10, 296), 0);
+ EXPECT_EQ("300,100 300x496", window_->bounds().ToString());
+ EXPECT_EQ("300,596 200x123", window2_->bounds().ToString());
+ EXPECT_EQ("300,719 200x81", window3_->bounds().ToString());
+
+ // Move it so much everything ends up at its min.
+ resizer->Drag(CalculateDragPoint(*resizer, 50, 798), 0);
+ EXPECT_EQ("300,100 300x610", window_->bounds().ToString());
+ EXPECT_EQ("300,710 200x52", window2_->bounds().ToString());
+ EXPECT_EQ("300,762 200x38", window3_->bounds().ToString());
+
+ // Revert and make sure everything moves back.
+ resizer->RevertDrag();
+ EXPECT_EQ("300,100 300x200", window_->bounds().ToString());
+ EXPECT_EQ("300,300 200x150", window2_->bounds().ToString());
+ EXPECT_EQ("300,450 200x100", window3_->bounds().ToString());
+}
+
+// Assertions around attached window resizing (collapsing and expanding) with
+// 3 windows.
+TEST_F(WorkspaceWindowResizerTest, AttachedResize_BOTTOM_3_Compress) {
+ window_->SetBounds(gfx::Rect( 0, 0, 200, 200));
+ window2_->SetBounds(gfx::Rect(10, 200, 200, 200));
+ window3_->SetBounds(gfx::Rect(20, 400, 100, 100));
+ delegate2_.set_min_size(gfx::Size(52, 50));
+ delegate3_.set_min_size(gfx::Size(38, 50));
+
+ std::vector<aura::Window*> windows;
+ windows.push_back(window2_.get());
+ windows.push_back(window3_.get());
+ scoped_ptr<WorkspaceWindowResizer> resizer(WorkspaceWindowResizer::Create(
+ window_.get(), gfx::Point(), HTBOTTOM,
+ aura::client::WINDOW_MOVE_SOURCE_MOUSE, windows));
+ ASSERT_TRUE(resizer.get());
+ // Move it 100 up, which should collapse w1 and expand w2 and w3.
+ resizer->Drag(CalculateDragPoint(*resizer, -10, -100), 0);
+ EXPECT_EQ("0,0 200x100", window_->bounds().ToString());
+ EXPECT_EQ("10,100 200x266", window2_->bounds().ToString());
+ EXPECT_EQ("20,366 100x134", window3_->bounds().ToString());
+
+ // Move it 100 down.
+ resizer->Drag(CalculateDragPoint(*resizer, 10, 100), 0);
+ EXPECT_EQ("0,0 200x300", window_->bounds().ToString());
+ EXPECT_EQ("10,300 200x200", window2_->bounds().ToString());
+ EXPECT_EQ("20,500 100x100", window3_->bounds().ToString());
+
+ // 100 up again.
+ resizer->Drag(CalculateDragPoint(*resizer, -10, -100), 0);
+ EXPECT_EQ("0,0 200x100", window_->bounds().ToString());
+ EXPECT_EQ("10,100 200x266", window2_->bounds().ToString());
+ EXPECT_EQ("20,366 100x134", window3_->bounds().ToString());
+}
+
+
+// Assertions around dragging to the left/right edge of the screen.
+TEST_F(WorkspaceWindowResizerTest, Edge) {
+ int bottom =
+ ScreenAsh::GetDisplayWorkAreaBoundsInParent(window_.get()).bottom();
+ window_->SetBounds(gfx::Rect(20, 30, 50, 60));
+ {
+ scoped_ptr<WorkspaceWindowResizer> resizer(WorkspaceWindowResizer::Create(
+ window_.get(), gfx::Point(), HTCAPTION,
+ aura::client::WINDOW_MOVE_SOURCE_MOUSE, empty_windows()));
+ ASSERT_TRUE(resizer.get());
+ resizer->Drag(CalculateDragPoint(*resizer, 0, 10), 0);
+ resizer->CompleteDrag(0);
+ EXPECT_EQ("0,0 720x" + base::IntToString(bottom),
+ window_->bounds().ToString());
+ ASSERT_TRUE(GetRestoreBoundsInScreen(window_.get()));
+ EXPECT_EQ("20,30 50x60",
+ GetRestoreBoundsInScreen(window_.get())->ToString());
+ }
+ // Try the same with the right side.
+ {
+ scoped_ptr<WorkspaceWindowResizer> resizer(WorkspaceWindowResizer::Create(
+ window_.get(), gfx::Point(), HTCAPTION,
+ aura::client::WINDOW_MOVE_SOURCE_MOUSE, empty_windows()));
+ ASSERT_TRUE(resizer.get());
+ resizer->Drag(CalculateDragPoint(*resizer, 800, 10), 0);
+ resizer->CompleteDrag(0);
+ EXPECT_EQ("80,0 720x" + base::IntToString(bottom),
+ window_->bounds().ToString());
+ ASSERT_TRUE(GetRestoreBoundsInScreen(window_.get()));
+ EXPECT_EQ("20,30 50x60",
+ GetRestoreBoundsInScreen(window_.get())->ToString());
+ }
+
+ // Test if the restore bounds is correct in multiple displays.
+ ClearRestoreBounds(window_.get());
+
+ if (!SupportsMultipleDisplays())
+ return;
+
+ UpdateDisplay("800x600,200x600");
+ Shell::RootWindowList root_windows = Shell::GetAllRootWindows();
+ EXPECT_EQ(root_windows[0], window_->GetRootWindow());
+ window_->SetBoundsInScreen(gfx::Rect(800, 10, 50, 60),
+ ScreenAsh::GetSecondaryDisplay());
+ EXPECT_EQ(root_windows[1], window_->GetRootWindow());
+ {
+ bottom =
+ ScreenAsh::GetDisplayWorkAreaBoundsInParent(window_.get()).bottom();
+ EXPECT_EQ("800,10 50x60", window_->GetBoundsInScreen().ToString());
+
+ scoped_ptr<WorkspaceWindowResizer> resizer(WorkspaceWindowResizer::Create(
+ window_.get(), gfx::Point(), HTCAPTION,
+ aura::client::WINDOW_MOVE_SOURCE_MOUSE, empty_windows()));
+ ASSERT_TRUE(resizer.get());
+
+ resizer->Drag(CalculateDragPoint(*resizer, 199, 00), 0);
+ resizer->CompleteDrag(0);
+ // With the resolution of 200x600 we will hit in this case the 50% screen
+ // size setting.
+ EXPECT_EQ("100,0 100x" + base::IntToString(bottom),
+ window_->bounds().ToString());
+ EXPECT_EQ("800,10 50x60",
+ GetRestoreBoundsInScreen(window_.get())->ToString());
+ }
+}
+
+// Check that non resizable windows will not get resized.
+TEST_F(WorkspaceWindowResizerTest, NonResizableWindows) {
+ window_->SetBounds(gfx::Rect(20, 30, 50, 60));
+ window_->SetProperty(aura::client::kCanResizeKey, false);
+
+ scoped_ptr<WorkspaceWindowResizer> resizer(WorkspaceWindowResizer::Create(
+ window_.get(), gfx::Point(), HTCAPTION,
+ aura::client::WINDOW_MOVE_SOURCE_MOUSE, empty_windows()));
+ ASSERT_TRUE(resizer.get());
+ resizer->Drag(CalculateDragPoint(*resizer, -20, 0), 0);
+ resizer->CompleteDrag(0);
+ EXPECT_EQ("0,30 50x60", window_->bounds().ToString());
+}
+
+TEST_F(WorkspaceWindowResizerTest, CancelSnapPhantom) {
+ if (!SupportsMultipleDisplays())
+ return;
+
+ UpdateDisplay("800x600,800x600");
+ Shell::RootWindowList root_windows = Shell::GetAllRootWindows();
+ ASSERT_EQ(2U, root_windows.size());
+
+ window_->SetBoundsInScreen(gfx::Rect(0, 0, 50, 60),
+ Shell::GetScreen()->GetPrimaryDisplay());
+ EXPECT_EQ(root_windows[0], window_->GetRootWindow());
+ EXPECT_FLOAT_EQ(1.0f, window_->layer()->opacity());
+ {
+ scoped_ptr<WorkspaceWindowResizer> resizer(WorkspaceWindowResizer::Create(
+ window_.get(), gfx::Point(), HTCAPTION,
+ aura::client::WINDOW_MOVE_SOURCE_MOUSE, empty_windows()));
+ ASSERT_TRUE(resizer.get());
+ EXPECT_FALSE(resizer->snap_phantom_window_controller_.get());
+
+ // The pointer is on the edge but not shared. The snap phantom window
+ // controller should be non-NULL.
+ resizer->Drag(CalculateDragPoint(*resizer, 799, 0), 0);
+ EXPECT_TRUE(resizer->snap_phantom_window_controller_.get());
+
+ // Move the cursor across the edge. Now the snap phantom window controller
+ // should be canceled.
+ resizer->Drag(CalculateDragPoint(*resizer, 800, 0), 0);
+ EXPECT_FALSE(resizer->snap_phantom_window_controller_.get());
+ }
+}
+
+// Verifies windows are correctly restacked when reordering multiple windows.
+TEST_F(WorkspaceWindowResizerTest, RestackAttached) {
+ window_->SetBounds(gfx::Rect( 0, 0, 200, 300));
+ window2_->SetBounds(gfx::Rect(200, 0, 100, 200));
+ window3_->SetBounds(gfx::Rect(300, 0, 100, 100));
+
+ {
+ std::vector<aura::Window*> windows;
+ windows.push_back(window2_.get());
+ scoped_ptr<WorkspaceWindowResizer> resizer(WorkspaceWindowResizer::Create(
+ window_.get(), gfx::Point(), HTRIGHT,
+ aura::client::WINDOW_MOVE_SOURCE_MOUSE, windows));
+ ASSERT_TRUE(resizer.get());
+ // Move it 100 to the right, which should expand w1 and push w2 and w3.
+ resizer->Drag(CalculateDragPoint(*resizer, 100, -10), 0);
+
+ // 2 should be topmost since it's initially the highest in the stack.
+ EXPECT_EQ("2 1 3", WindowOrderAsString(window_->parent()));
+ }
+
+ {
+ std::vector<aura::Window*> windows;
+ windows.push_back(window3_.get());
+ scoped_ptr<WorkspaceWindowResizer> resizer(WorkspaceWindowResizer::Create(
+ window2_.get(), gfx::Point(), HTRIGHT,
+ aura::client::WINDOW_MOVE_SOURCE_MOUSE, windows));
+ ASSERT_TRUE(resizer.get());
+ // Move it 100 to the right, which should expand w1 and push w2 and w3.
+ resizer->Drag(CalculateDragPoint(*resizer, 100, -10), 0);
+
+ // 2 should be topmost since it's initially the highest in the stack.
+ EXPECT_EQ("2 3 1", WindowOrderAsString(window_->parent()));
+ }
+}
+
+// Makes sure we don't allow dragging below the work area.
+TEST_F(WorkspaceWindowResizerTest, DontDragOffBottom) {
+ Shell::GetInstance()->SetDisplayWorkAreaInsets(
+ Shell::GetPrimaryRootWindow(), gfx::Insets(0, 0, 10, 0));
+
+ ASSERT_EQ(1, Shell::GetScreen()->GetNumDisplays());
+
+ window_->SetBounds(gfx::Rect(100, 200, 300, 400));
+ scoped_ptr<WorkspaceWindowResizer> resizer(WorkspaceWindowResizer::Create(
+ window_.get(), gfx::Point(), HTCAPTION,
+ aura::client::WINDOW_MOVE_SOURCE_MOUSE, empty_windows()));
+ ASSERT_TRUE(resizer.get());
+ resizer->Drag(CalculateDragPoint(*resizer, 0, 600), 0);
+ int expected_y =
+ kRootHeight - WorkspaceWindowResizer::kMinOnscreenHeight - 10;
+ EXPECT_EQ("100," + base::IntToString(expected_y) + " 300x400",
+ window_->bounds().ToString());
+}
+
+// Makes sure we don't allow dragging on the work area with multidisplay.
+TEST_F(WorkspaceWindowResizerTest, DontDragOffBottomWithMultiDisplay) {
+ if (!SupportsMultipleDisplays())
+ return;
+
+ UpdateDisplay("800x600,800x600");
+ ASSERT_EQ(2, Shell::GetScreen()->GetNumDisplays());
+
+ Shell::GetInstance()->SetDisplayWorkAreaInsets(
+ Shell::GetPrimaryRootWindow(), gfx::Insets(0, 0, 10, 0));
+
+ // Positions the secondary display at the bottom the primary display.
+ Shell::GetInstance()->display_controller()->SetLayoutForCurrentDisplays(
+ ash::DisplayLayout(ash::DisplayLayout::BOTTOM, 0));
+
+ {
+ window_->SetBounds(gfx::Rect(100, 200, 300, 400));
+ scoped_ptr<WorkspaceWindowResizer> resizer(WorkspaceWindowResizer::Create(
+ window_.get(), gfx::Point(), HTCAPTION,
+ aura::client::WINDOW_MOVE_SOURCE_MOUSE, empty_windows()));
+ ASSERT_TRUE(resizer.get());
+ resizer->Drag(CalculateDragPoint(*resizer, 0, 400), 0);
+ int expected_y =
+ kRootHeight - WorkspaceWindowResizer::kMinOnscreenHeight - 10;
+ // When the mouse cursor is in the primary display, the window cannot move
+ // on non-work area with kMinOnscreenHeight margin.
+ EXPECT_EQ("100," + base::IntToString(expected_y) + " 300x400",
+ window_->bounds().ToString());
+ }
+
+ {
+ window_->SetBounds(gfx::Rect(100, 200, 300, 400));
+ scoped_ptr<WorkspaceWindowResizer> resizer(WorkspaceWindowResizer::Create(
+ window_.get(), gfx::Point(), HTCAPTION,
+ aura::client::WINDOW_MOVE_SOURCE_MOUSE, empty_windows()));
+ ASSERT_TRUE(resizer.get());
+ resizer->Drag(CalculateDragPoint(*resizer, 0, 600), 0);
+ // The window can move to the secondary display beyond non-work area of
+ // the primary display.
+ EXPECT_EQ("100,800 300x400", window_->bounds().ToString());
+ }
+}
+
+// Makes sure we don't allow dragging off the top of the work area.
+TEST_F(WorkspaceWindowResizerTest, DontDragOffTop) {
+ Shell::GetInstance()->SetDisplayWorkAreaInsets(
+ Shell::GetPrimaryRootWindow(), gfx::Insets(10, 0, 0, 0));
+
+ window_->SetBounds(gfx::Rect(100, 200, 300, 400));
+ scoped_ptr<WorkspaceWindowResizer> resizer(WorkspaceWindowResizer::Create(
+ window_.get(), gfx::Point(), HTCAPTION,
+ aura::client::WINDOW_MOVE_SOURCE_MOUSE, empty_windows()));
+ ASSERT_TRUE(resizer.get());
+ resizer->Drag(CalculateDragPoint(*resizer, 0, -600), 0);
+ EXPECT_EQ("100,10 300x400", window_->bounds().ToString());
+}
+
+TEST_F(WorkspaceWindowResizerTest, ResizeBottomOutsideWorkArea) {
+ Shell::GetInstance()->SetDisplayWorkAreaInsets(
+ Shell::GetPrimaryRootWindow(), gfx::Insets(0, 0, 50, 0));
+
+ window_->SetBounds(gfx::Rect(100, 200, 300, 380));
+ scoped_ptr<WorkspaceWindowResizer> resizer(WorkspaceWindowResizer::Create(
+ window_.get(), gfx::Point(), HTTOP,
+ aura::client::WINDOW_MOVE_SOURCE_MOUSE, empty_windows()));
+ ASSERT_TRUE(resizer.get());
+ resizer->Drag(CalculateDragPoint(*resizer, 8, 0), 0);
+ EXPECT_EQ("100,200 300x380", window_->bounds().ToString());
+}
+
+TEST_F(WorkspaceWindowResizerTest, ResizeWindowOutsideLeftWorkArea) {
+ Shell::GetInstance()->SetDisplayWorkAreaInsets(
+ Shell::GetPrimaryRootWindow(), gfx::Insets(0, 0, 50, 0));
+ int left = ScreenAsh::GetDisplayWorkAreaBoundsInParent(window_.get()).x();
+ int pixels_to_left_border = 50;
+ int window_width = 300;
+ int window_x = left - window_width + pixels_to_left_border;
+ window_->SetBounds(gfx::Rect(window_x, 100, window_width, 380));
+ scoped_ptr<WorkspaceWindowResizer> resizer(WorkspaceWindowResizer::Create(
+ window_.get(), gfx::Point(pixels_to_left_border, 0), HTRIGHT,
+ aura::client::WINDOW_MOVE_SOURCE_MOUSE, empty_windows()));
+ ASSERT_TRUE(resizer.get());
+ resizer->Drag(CalculateDragPoint(*resizer, -window_width, 0), 0);
+ EXPECT_EQ(base::IntToString(window_x) + ",100 " +
+ base::IntToString(kMinimumOnScreenArea - window_x) +
+ "x380", window_->bounds().ToString());
+}
+
+TEST_F(WorkspaceWindowResizerTest, ResizeWindowOutsideRightWorkArea) {
+ Shell::GetInstance()->SetDisplayWorkAreaInsets(
+ Shell::GetPrimaryRootWindow(), gfx::Insets(0, 0, 50, 0));
+ int right = ScreenAsh::GetDisplayWorkAreaBoundsInParent(
+ window_.get()).right();
+ int pixels_to_right_border = 50;
+ int window_width = 300;
+ int window_x = right - pixels_to_right_border;
+ window_->SetBounds(gfx::Rect(window_x, 100, window_width, 380));
+ scoped_ptr<WorkspaceWindowResizer> resizer(WorkspaceWindowResizer::Create(
+ window_.get(), gfx::Point(window_x, 0), HTLEFT,
+ aura::client::WINDOW_MOVE_SOURCE_MOUSE, empty_windows()));
+ ASSERT_TRUE(resizer.get());
+ resizer->Drag(CalculateDragPoint(*resizer, window_width, 0), 0);
+ EXPECT_EQ(base::IntToString(right - kMinimumOnScreenArea) +
+ ",100 " +
+ base::IntToString(window_width - pixels_to_right_border +
+ kMinimumOnScreenArea) +
+ "x380", window_->bounds().ToString());
+}
+
+TEST_F(WorkspaceWindowResizerTest, ResizeWindowOutsideBottomWorkArea) {
+ Shell::GetInstance()->SetDisplayWorkAreaInsets(
+ Shell::GetPrimaryRootWindow(), gfx::Insets(0, 0, 50, 0));
+ int bottom = ScreenAsh::GetDisplayWorkAreaBoundsInParent(
+ window_.get()).bottom();
+ int delta_to_bottom = 50;
+ int height = 380;
+ window_->SetBounds(gfx::Rect(100, bottom - delta_to_bottom, 300, height));
+ scoped_ptr<WorkspaceWindowResizer> resizer(WorkspaceWindowResizer::Create(
+ window_.get(), gfx::Point(0, bottom - delta_to_bottom), HTTOP,
+ aura::client::WINDOW_MOVE_SOURCE_MOUSE, empty_windows()));
+ ASSERT_TRUE(resizer.get());
+ resizer->Drag(CalculateDragPoint(*resizer, 0, bottom), 0);
+ EXPECT_EQ("100," +
+ base::IntToString(bottom - kMinimumOnScreenArea) +
+ " 300x" +
+ base::IntToString(height - (delta_to_bottom -
+ kMinimumOnScreenArea)),
+ window_->bounds().ToString());
+}
+
+// Verifies that 'outside' check of the resizer take into account the extended
+// desktop in case of repositions.
+TEST_F(WorkspaceWindowResizerTest, DragWindowOutsideRightToSecondaryDisplay) {
+ // Only primary display. Changes the window position to fit within the
+ // display.
+ Shell::GetInstance()->SetDisplayWorkAreaInsets(
+ Shell::GetPrimaryRootWindow(), gfx::Insets(0, 0, 50, 0));
+ int right = ScreenAsh::GetDisplayWorkAreaBoundsInParent(
+ window_.get()).right();
+ int pixels_to_right_border = 50;
+ int window_width = 300;
+ int window_x = right - pixels_to_right_border;
+ window_->SetBounds(gfx::Rect(window_x, 100, window_width, 380));
+ scoped_ptr<WorkspaceWindowResizer> resizer(WorkspaceWindowResizer::Create(
+ window_.get(), gfx::Point(window_x, 0), HTCAPTION,
+ aura::client::WINDOW_MOVE_SOURCE_MOUSE, empty_windows()));
+ ASSERT_TRUE(resizer.get());
+ resizer->Drag(CalculateDragPoint(*resizer, window_width, 0), 0);
+ EXPECT_EQ(base::IntToString(right - kMinimumOnScreenArea) +
+ ",100 " +
+ base::IntToString(window_width) +
+ "x380", window_->bounds().ToString());
+
+ if (!SupportsMultipleDisplays())
+ return;
+
+ // With secondary display. Operation itself is same but doesn't change
+ // the position because the window is still within the secondary display.
+ UpdateDisplay("1000x600,600x400");
+ Shell::GetInstance()->SetDisplayWorkAreaInsets(
+ Shell::GetPrimaryRootWindow(), gfx::Insets(0, 0, 50, 0));
+ window_->SetBounds(gfx::Rect(window_x, 100, window_width, 380));
+ resizer->Drag(CalculateDragPoint(*resizer, window_width, 0), 0);
+ EXPECT_EQ(base::IntToString(window_x + window_width) +
+ ",100 " +
+ base::IntToString(window_width) +
+ "x380", window_->bounds().ToString());
+}
+
+// Verifies snapping to edges works.
+TEST_F(WorkspaceWindowResizerTest, SnapToEdge) {
+ Shell::GetPrimaryRootWindowController()->GetShelfLayoutManager()->
+ SetAutoHideBehavior(SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS);
+ window_->SetBounds(gfx::Rect(96, 112, 320, 160));
+ // Click 50px to the right so that the mouse pointer does not leave the
+ // workspace ensuring sticky behavior.
+ scoped_ptr<WorkspaceWindowResizer> resizer(WorkspaceWindowResizer::Create(
+ window_.get(),
+ window_->bounds().origin() + gfx::Vector2d(50, 0),
+ HTCAPTION,
+ aura::client::WINDOW_MOVE_SOURCE_MOUSE, empty_windows()));
+ ASSERT_TRUE(resizer.get());
+ // Move to an x-coordinate of 15, which should not snap.
+ resizer->Drag(CalculateDragPoint(*resizer, 15 - 96, 0), 0);
+ // An x-coordinate of 7 should snap.
+ resizer->Drag(CalculateDragPoint(*resizer, 7 - 96, 0), 0);
+ EXPECT_EQ("0,112 320x160", window_->bounds().ToString());
+ // Move to -15, should still snap to 0.
+ resizer->Drag(CalculateDragPoint(*resizer, -15 - 96, 0), 0);
+ EXPECT_EQ("0,112 320x160", window_->bounds().ToString());
+ // At -32 should move past snap points.
+ resizer->Drag(CalculateDragPoint(*resizer, -32 - 96, 0), 0);
+ EXPECT_EQ("-32,112 320x160", window_->bounds().ToString());
+ resizer->Drag(CalculateDragPoint(*resizer, -33 - 96, 0), 0);
+ EXPECT_EQ("-33,112 320x160", window_->bounds().ToString());
+
+ // Right side should similarly snap.
+ resizer->Drag(CalculateDragPoint(*resizer, 800 - 320 - 96 - 15, 0), 0);
+ EXPECT_EQ("465,112 320x160", window_->bounds().ToString());
+ resizer->Drag(CalculateDragPoint(*resizer, 800 - 320 - 96 - 7, 0), 0);
+ EXPECT_EQ("480,112 320x160", window_->bounds().ToString());
+ resizer->Drag(CalculateDragPoint(*resizer, 800 - 320 - 96 + 15, 0), 0);
+ EXPECT_EQ("480,112 320x160", window_->bounds().ToString());
+ resizer->Drag(CalculateDragPoint(*resizer, 800 - 320 - 96 + 32, 0), 0);
+ EXPECT_EQ("512,112 320x160", window_->bounds().ToString());
+ resizer->Drag(CalculateDragPoint(*resizer, 800 - 320 - 96 + 33, 0), 0);
+ EXPECT_EQ("513,112 320x160", window_->bounds().ToString());
+
+ // And the bottom should snap too.
+ resizer->Drag(CalculateDragPoint(*resizer, 0, 600 - 160 - 112 - 3 - 7), 0);
+ EXPECT_EQ("96,437 320x160", window_->bounds().ToString());
+ resizer->Drag(CalculateDragPoint(*resizer, 0, 600 - 160 - 112 - 3 + 15), 0);
+ EXPECT_EQ("96,437 320x160", window_->bounds().ToString());
+ resizer->Drag(CalculateDragPoint(*resizer, 0, 600 - 160 - 112 - 2 + 32), 0);
+ EXPECT_EQ("96,470 320x160", window_->bounds().ToString());
+ resizer->Drag(CalculateDragPoint(*resizer, 0, 600 - 160 - 112 - 2 + 33), 0);
+ EXPECT_EQ("96,471 320x160", window_->bounds().ToString());
+
+ // And the top should snap too.
+ resizer->Drag(CalculateDragPoint(*resizer, 0, -112 + 20), 0);
+ EXPECT_EQ("96,20 320x160", window_->bounds().ToString());
+ resizer->Drag(CalculateDragPoint(*resizer, 0, -112 + 7), 0);
+ EXPECT_EQ("96,0 320x160", window_->bounds().ToString());
+ // No need to test dragging < 0 as we force that to 0.
+}
+
+// Verifies a resize snap when dragging TOPLEFT.
+TEST_F(WorkspaceWindowResizerTest, SnapToWorkArea_TOPLEFT) {
+ window_->SetBounds(gfx::Rect(100, 200, 20, 30));
+ scoped_ptr<WorkspaceWindowResizer> resizer(WorkspaceWindowResizer::Create(
+ window_.get(), gfx::Point(), HTTOPLEFT,
+ aura::client::WINDOW_MOVE_SOURCE_MOUSE, empty_windows()));
+ ASSERT_TRUE(resizer.get());
+ resizer->Drag(CalculateDragPoint(*resizer, -98, -199), 0);
+ EXPECT_EQ("0,0 120x230", window_->bounds().ToString());
+}
+
+// Verifies a resize snap when dragging TOPRIGHT.
+TEST_F(WorkspaceWindowResizerTest, SnapToWorkArea_TOPRIGHT) {
+ window_->SetBounds(gfx::Rect(100, 200, 20, 30));
+ gfx::Rect work_area(ScreenAsh::GetDisplayWorkAreaBoundsInParent(
+ window_.get()));
+ scoped_ptr<WorkspaceWindowResizer> resizer(WorkspaceWindowResizer::Create(
+ window_.get(), gfx::Point(), HTTOPRIGHT,
+ aura::client::WINDOW_MOVE_SOURCE_MOUSE, empty_windows()));
+ ASSERT_TRUE(resizer.get());
+ resizer->Drag(
+ CalculateDragPoint(*resizer, work_area.right() - 120 - 1, -199), 0);
+ EXPECT_EQ(100, window_->bounds().x());
+ EXPECT_EQ(work_area.y(), window_->bounds().y());
+ EXPECT_EQ(work_area.right() - 100, window_->bounds().width());
+ EXPECT_EQ(230, window_->bounds().height());
+}
+
+// Verifies a resize snap when dragging BOTTOMRIGHT.
+TEST_F(WorkspaceWindowResizerTest, SnapToWorkArea_BOTTOMRIGHT) {
+ window_->SetBounds(gfx::Rect(100, 200, 20, 30));
+ gfx::Rect work_area(ScreenAsh::GetDisplayWorkAreaBoundsInParent(
+ window_.get()));
+ scoped_ptr<WorkspaceWindowResizer> resizer(WorkspaceWindowResizer::Create(
+ window_.get(), gfx::Point(), HTBOTTOMRIGHT,
+ aura::client::WINDOW_MOVE_SOURCE_MOUSE, empty_windows()));
+ ASSERT_TRUE(resizer.get());
+ resizer->Drag(
+ CalculateDragPoint(*resizer, work_area.right() - 120 - 1,
+ work_area.bottom() - 220 - 2), 0);
+ EXPECT_EQ(100, window_->bounds().x());
+ EXPECT_EQ(200, window_->bounds().y());
+ EXPECT_EQ(work_area.right() - 100, window_->bounds().width());
+ EXPECT_EQ(work_area.bottom() - 200, window_->bounds().height());
+}
+
+// Verifies a resize snap when dragging BOTTOMLEFT.
+TEST_F(WorkspaceWindowResizerTest, SnapToWorkArea_BOTTOMLEFT) {
+ window_->SetBounds(gfx::Rect(100, 200, 20, 30));
+ gfx::Rect work_area(ScreenAsh::GetDisplayWorkAreaBoundsInParent(
+ window_.get()));
+ scoped_ptr<WorkspaceWindowResizer> resizer(WorkspaceWindowResizer::Create(
+ window_.get(), gfx::Point(), HTBOTTOMLEFT,
+ aura::client::WINDOW_MOVE_SOURCE_MOUSE, empty_windows()));
+ ASSERT_TRUE(resizer.get());
+ resizer->Drag(
+ CalculateDragPoint(*resizer, -98, work_area.bottom() - 220 - 2), 0);
+ EXPECT_EQ(0, window_->bounds().x());
+ EXPECT_EQ(200, window_->bounds().y());
+ EXPECT_EQ(120, window_->bounds().width());
+ EXPECT_EQ(work_area.bottom() - 200, window_->bounds().height());
+}
+
+// Verifies sticking to edges works.
+TEST_F(WorkspaceWindowResizerTestSticky, StickToEdge) {
+ Shell::GetPrimaryRootWindowController()->GetShelfLayoutManager()->
+ SetAutoHideBehavior(SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS);
+ window_->SetBounds(gfx::Rect(96, 112, 320, 160));
+ // Click 50px to the right so that the mouse pointer does not leave the
+ // workspace ensuring sticky behavior.
+ scoped_ptr<WorkspaceWindowResizer> resizer(WorkspaceWindowResizer::Create(
+ window_.get(),
+ window_->bounds().origin() + gfx::Vector2d(50, 0),
+ HTCAPTION,
+ aura::client::WINDOW_MOVE_SOURCE_MOUSE, empty_windows()));
+ ASSERT_TRUE(resizer.get());
+ // Move to an x-coordinate of 15, which should not stick.
+ resizer->Drag(CalculateDragPoint(*resizer, 15 - 96, 0), 0);
+ // Move to -15, should still stick to 0.
+ resizer->Drag(CalculateDragPoint(*resizer, -15 - 96, 0), 0);
+ EXPECT_EQ("0,112 320x160", window_->bounds().ToString());
+ // At -100 should move past edge.
+ resizer->Drag(CalculateDragPoint(*resizer, -100 - 96, 0), 0);
+ EXPECT_EQ("-100,112 320x160", window_->bounds().ToString());
+ resizer->Drag(CalculateDragPoint(*resizer, -101 - 96, 0), 0);
+ EXPECT_EQ("-101,112 320x160", window_->bounds().ToString());
+
+ // Right side should similarly stick.
+ resizer->Drag(CalculateDragPoint(*resizer, 800 - 320 - 96 - 15, 0), 0);
+ EXPECT_EQ("465,112 320x160", window_->bounds().ToString());
+ resizer->Drag(CalculateDragPoint(*resizer, 800 - 320 - 96 + 15, 0), 0);
+ EXPECT_EQ("480,112 320x160", window_->bounds().ToString());
+ resizer->Drag(CalculateDragPoint(*resizer, 800 - 320 - 96 + 100, 0), 0);
+ EXPECT_EQ("580,112 320x160", window_->bounds().ToString());
+ resizer->Drag(CalculateDragPoint(*resizer, 800 - 320 - 96 + 101, 0), 0);
+ EXPECT_EQ("581,112 320x160", window_->bounds().ToString());
+
+ // And the bottom should stick too.
+ resizer->Drag(CalculateDragPoint(*resizer, 0, 600 - 160 - 112 - 3 + 15), 0);
+ EXPECT_EQ("96,437 320x160", window_->bounds().ToString());
+ resizer->Drag(CalculateDragPoint(*resizer, 0, 600 - 160 - 112 - 2 + 100), 0);
+ EXPECT_EQ("96,538 320x160", window_->bounds().ToString());
+ resizer->Drag(CalculateDragPoint(*resizer, 0, 600 - 160 - 112 - 2 + 101), 0);
+ EXPECT_EQ("96,539 320x160", window_->bounds().ToString());
+
+ // No need to test dragging < 0 as we force that to 0.
+}
+
+// Verifies not sticking to edges when a mouse pointer is outside of work area.
+TEST_F(WorkspaceWindowResizerTestSticky, NoStickToEdgeWhenOutside) {
+ Shell::GetPrimaryRootWindowController()->GetShelfLayoutManager()->
+ SetAutoHideBehavior(SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS);
+ window_->SetBounds(gfx::Rect(96, 112, 320, 160));
+ scoped_ptr<WorkspaceWindowResizer> resizer(WorkspaceWindowResizer::Create(
+ window_.get(), gfx::Point(), HTCAPTION,
+ aura::client::WINDOW_MOVE_SOURCE_MOUSE, empty_windows()));
+ ASSERT_TRUE(resizer.get());
+ // Move to an x-coordinate of 15, which should not stick.
+ resizer->Drag(CalculateDragPoint(*resizer, 15 - 96, 0), 0);
+ // Move to -15, should still stick to 0.
+ resizer->Drag(CalculateDragPoint(*resizer, -15 - 96, 0), 0);
+ EXPECT_EQ("-15,112 320x160", window_->bounds().ToString());
+}
+
+// Verifies a resize sticks when dragging TOPLEFT.
+TEST_F(WorkspaceWindowResizerTestSticky, StickToWorkArea_TOPLEFT) {
+ window_->SetBounds(gfx::Rect(100, 200, 20, 30));
+ scoped_ptr<WorkspaceWindowResizer> resizer(WorkspaceWindowResizer::Create(
+ window_.get(), gfx::Point(), HTTOPLEFT,
+ aura::client::WINDOW_MOVE_SOURCE_MOUSE, empty_windows()));
+ ASSERT_TRUE(resizer.get());
+ resizer->Drag(CalculateDragPoint(*resizer, -15 - 100, -15 -200), 0);
+ EXPECT_EQ("0,0 120x230", window_->bounds().ToString());
+}
+
+// Verifies a resize sticks when dragging TOPRIGHT.
+TEST_F(WorkspaceWindowResizerTestSticky, StickToWorkArea_TOPRIGHT) {
+ window_->SetBounds(gfx::Rect(100, 200, 20, 30));
+ gfx::Rect work_area(ScreenAsh::GetDisplayWorkAreaBoundsInParent(
+ window_.get()));
+ scoped_ptr<WorkspaceWindowResizer> resizer(WorkspaceWindowResizer::Create(
+ window_.get(), gfx::Point(), HTTOPRIGHT,
+ aura::client::WINDOW_MOVE_SOURCE_MOUSE, empty_windows()));
+ ASSERT_TRUE(resizer.get());
+ resizer->Drag(CalculateDragPoint(*resizer, work_area.right() - 100 + 20,
+ -200 - 15), 0);
+ EXPECT_EQ(100, window_->bounds().x());
+ EXPECT_EQ(work_area.y(), window_->bounds().y());
+ EXPECT_EQ(work_area.right() - 100, window_->bounds().width());
+ EXPECT_EQ(230, window_->bounds().height());
+}
+
+// Verifies a resize snap when dragging BOTTOMRIGHT.
+TEST_F(WorkspaceWindowResizerTestSticky, StickToWorkArea_BOTTOMRIGHT) {
+ window_->SetBounds(gfx::Rect(100, 200, 20, 30));
+ gfx::Rect work_area(ScreenAsh::GetDisplayWorkAreaBoundsInParent(
+ window_.get()));
+ scoped_ptr<WorkspaceWindowResizer> resizer(WorkspaceWindowResizer::Create(
+ window_.get(), gfx::Point(), HTBOTTOMRIGHT,
+ aura::client::WINDOW_MOVE_SOURCE_MOUSE, empty_windows()));
+ ASSERT_TRUE(resizer.get());
+ resizer->Drag(CalculateDragPoint(*resizer, work_area.right() - 100 - 20 + 15,
+ work_area.bottom() - 200 - 30 + 15), 0);
+ EXPECT_EQ(100, window_->bounds().x());
+ EXPECT_EQ(200, window_->bounds().y());
+ EXPECT_EQ(work_area.right() - 100, window_->bounds().width());
+ EXPECT_EQ(work_area.bottom() - 200, window_->bounds().height());
+}
+
+// Verifies a resize snap when dragging BOTTOMLEFT.
+TEST_F(WorkspaceWindowResizerTestSticky, StickToWorkArea_BOTTOMLEFT) {
+ window_->SetBounds(gfx::Rect(100, 200, 20, 30));
+ gfx::Rect work_area(ScreenAsh::GetDisplayWorkAreaBoundsInParent(
+ window_.get()));
+ scoped_ptr<WorkspaceWindowResizer> resizer(WorkspaceWindowResizer::Create(
+ window_.get(), gfx::Point(), HTBOTTOMLEFT,
+ aura::client::WINDOW_MOVE_SOURCE_MOUSE, empty_windows()));
+ ASSERT_TRUE(resizer.get());
+ resizer->Drag(CalculateDragPoint(*resizer, -15 - 100,
+ work_area.bottom() - 200 - 30 + 15), 0);
+ EXPECT_EQ(0, window_->bounds().x());
+ EXPECT_EQ(200, window_->bounds().y());
+ EXPECT_EQ(120, window_->bounds().width());
+ EXPECT_EQ(work_area.bottom() - 200, window_->bounds().height());
+}
+
+TEST_F(WorkspaceWindowResizerTest, CtrlDragResizeToExactPosition) {
+ window_->SetBounds(gfx::Rect(96, 112, 320, 160));
+ scoped_ptr<WorkspaceWindowResizer> resizer(WorkspaceWindowResizer::Create(
+ window_.get(), gfx::Point(), HTBOTTOMRIGHT,
+ aura::client::WINDOW_MOVE_SOURCE_MOUSE, empty_windows()));
+ ASSERT_TRUE(resizer.get());
+ // Resize the right bottom to add 10 in width, 12 in height.
+ resizer->Drag(CalculateDragPoint(*resizer, 10, 12), ui::EF_CONTROL_DOWN);
+ // Both bottom and right sides to resize to exact size requested.
+ EXPECT_EQ("96,112 330x172", window_->bounds().ToString());
+}
+
+TEST_F(WorkspaceWindowResizerTest, CtrlCompleteDragMoveToExactPosition) {
+ window_->SetBounds(gfx::Rect(96, 112, 320, 160));
+ scoped_ptr<WorkspaceWindowResizer> resizer(WorkspaceWindowResizer::Create(
+ window_.get(), gfx::Point(), HTCAPTION,
+ aura::client::WINDOW_MOVE_SOURCE_MOUSE, empty_windows()));
+ ASSERT_TRUE(resizer.get());
+ // Ctrl + drag the window to new poistion by adding (10, 12) to its origin,
+ // the window should move to the exact position.
+ resizer->Drag(CalculateDragPoint(*resizer, 10, 12), 0);
+ resizer->CompleteDrag(ui::EF_CONTROL_DOWN);
+ EXPECT_EQ("106,124 320x160", window_->bounds().ToString());
+}
+
+// Check that only usable sizes get returned by the resizer.
+TEST_F(WorkspaceWindowResizerTest, TestProperSizerResolutions) {
+ // Check that we have the correct work area resolution which fits our
+ // expected test result.
+ gfx::Rect work_area(ScreenAsh::GetDisplayWorkAreaBoundsInParent(
+ window_.get()));
+ EXPECT_EQ(800, work_area.width());
+
+ window_->SetBounds(gfx::Rect(96, 112, 320, 160));
+ scoped_ptr<SnapSizer> resizer(new SnapSizer(
+ window_.get(),
+ gfx::Point(),
+ SnapSizer::LEFT_EDGE,
+ SnapSizer::OTHER_INPUT));
+ ASSERT_TRUE(resizer.get());
+ shelf_layout_manager()->SetAutoHideBehavior(
+ SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS);
+
+ // Check that the list is declining and contains elements of the
+ // ideal size list [1280, 1024, 768, 640] as well as 50% and 90% the work
+ // area.
+ gfx::Rect rect = resizer->GetTargetBoundsForSize(0);
+ EXPECT_EQ("0,0 720x597", rect.ToString());
+ rect = resizer->GetTargetBoundsForSize(1);
+ EXPECT_EQ("0,0 640x597", rect.ToString());
+ rect = resizer->GetTargetBoundsForSize(2);
+ EXPECT_EQ("0,0 400x597", rect.ToString());
+ shelf_layout_manager()->SetAutoHideBehavior(
+ SHELF_AUTO_HIDE_BEHAVIOR_NEVER);
+ rect = resizer->GetTargetBoundsForSize(0);
+ EXPECT_EQ("0,0 720x552", rect.ToString());
+ rect = resizer->GetTargetBoundsForSize(1);
+ EXPECT_EQ("0,0 640x552", rect.ToString());
+ rect = resizer->GetTargetBoundsForSize(2);
+ EXPECT_EQ("0,0 400x552", rect.ToString());
+}
+
+// Verifies that a dragged window will restore to its pre-maximized size.
+TEST_F(WorkspaceWindowResizerTest, RestoreToPreMaximizeCoordinates) {
+ window_->SetBounds(gfx::Rect(0, 0, 1000, 1000));
+ SetRestoreBoundsInScreen(window_.get(), gfx::Rect(96, 112, 320, 160));
+ scoped_ptr<WorkspaceWindowResizer> resizer(WorkspaceWindowResizer::Create(
+ window_.get(), gfx::Point(), HTCAPTION,
+ aura::client::WINDOW_MOVE_SOURCE_MOUSE, empty_windows()));
+ ASSERT_TRUE(resizer.get());
+ // Drag the window to new position by adding (10, 10) to original point,
+ // the window should get restored.
+ resizer->Drag(CalculateDragPoint(*resizer, 10, 10), 0);
+ resizer->CompleteDrag(0);
+ EXPECT_EQ("10,10 320x160", window_->bounds().ToString());
+ // The restore rectangle should get cleared as well.
+ EXPECT_EQ(NULL, GetRestoreBoundsInScreen(window_.get()));
+}
+
+// Verifies that a dragged window will restore to its pre-maximized size.
+TEST_F(WorkspaceWindowResizerTest, RevertResizeOperation) {
+ const gfx::Rect initial_bounds(0, 0, 200, 400);
+ window_->SetBounds(initial_bounds);
+ SetRestoreBoundsInScreen(window_.get(), gfx::Rect(96, 112, 320, 160));
+ scoped_ptr<WorkspaceWindowResizer> resizer(WorkspaceWindowResizer::Create(
+ window_.get(), gfx::Point(), HTCAPTION,
+ aura::client::WINDOW_MOVE_SOURCE_MOUSE, empty_windows()));
+ ASSERT_TRUE(resizer.get());
+ // Drag the window to new poistion by adding (180, 16) to original point,
+ // the window should get restored.
+ resizer->Drag(CalculateDragPoint(*resizer, 180, 16), 0);
+ resizer->RevertDrag();
+ EXPECT_EQ(initial_bounds.ToString(), window_->bounds().ToString());
+ EXPECT_EQ("96,112 320x160",
+ GetRestoreBoundsInScreen(window_.get())->ToString());
+}
+
+// Check that only usable sizes get returned by the resizer.
+TEST_F(WorkspaceWindowResizerTest, MagneticallyAttach) {
+ window_->SetBounds(gfx::Rect(10, 10, 20, 30));
+ window2_->SetBounds(gfx::Rect(150, 160, 25, 20));
+ window2_->Show();
+
+ scoped_ptr<WorkspaceWindowResizer> resizer(WorkspaceWindowResizer::Create(
+ window_.get(), gfx::Point(), HTCAPTION,
+ aura::client::WINDOW_MOVE_SOURCE_MOUSE, empty_windows()));
+ ASSERT_TRUE(resizer.get());
+ // Move |window| one pixel to the left of |window2|. Should snap to right and
+ // top.
+ resizer->Drag(CalculateDragPoint(*resizer, 119, 145), 0);
+ EXPECT_EQ("130,160 20x30", window_->bounds().ToString());
+
+ // Move |window| one pixel to the right of |window2|. Should snap to left and
+ // top.
+ resizer->Drag(CalculateDragPoint(*resizer, 164, 145), 0);
+ EXPECT_EQ("175,160 20x30", window_->bounds().ToString());
+
+ // Move |window| one pixel above |window2|. Should snap to top and left.
+ resizer->Drag(CalculateDragPoint(*resizer, 142, 119), 0);
+ EXPECT_EQ("150,130 20x30", window_->bounds().ToString());
+
+ // Move |window| one pixel above the bottom of |window2|. Should snap to
+ // bottom and left.
+ resizer->Drag(CalculateDragPoint(*resizer, 142, 169), 0);
+ EXPECT_EQ("150,180 20x30", window_->bounds().ToString());
+}
+
+// The following variants verify magnetic snapping during resize when dragging a
+// particular edge.
+TEST_F(WorkspaceWindowResizerTest, MagneticallyResize_TOP) {
+ window_->SetBounds(gfx::Rect(100, 200, 20, 30));
+ window2_->SetBounds(gfx::Rect(99, 179, 10, 20));
+ window2_->Show();
+
+ scoped_ptr<WorkspaceWindowResizer> resizer(WorkspaceWindowResizer::Create(
+ window_.get(), gfx::Point(), HTTOP,
+ aura::client::WINDOW_MOVE_SOURCE_MOUSE, empty_windows()));
+ ASSERT_TRUE(resizer.get());
+ resizer->Drag(CalculateDragPoint(*resizer, 0, 0), 0);
+ EXPECT_EQ("100,199 20x31", window_->bounds().ToString());
+}
+
+TEST_F(WorkspaceWindowResizerTest, MagneticallyResize_TOPLEFT) {
+ window_->SetBounds(gfx::Rect(100, 200, 20, 30));
+ window2_->SetBounds(gfx::Rect(99, 179, 10, 20));
+ window2_->Show();
+
+ {
+ scoped_ptr<WorkspaceWindowResizer> resizer(WorkspaceWindowResizer::Create(
+ window_.get(), gfx::Point(), HTTOPLEFT,
+ aura::client::WINDOW_MOVE_SOURCE_MOUSE, empty_windows()));
+ ASSERT_TRUE(resizer.get());
+ resizer->Drag(CalculateDragPoint(*resizer, 0, 0), 0);
+ EXPECT_EQ("99,199 21x31", window_->bounds().ToString());
+ resizer->RevertDrag();
+ }
+
+ {
+ window2_->SetBounds(gfx::Rect(88, 201, 10, 20));
+ scoped_ptr<WorkspaceWindowResizer> resizer(WorkspaceWindowResizer::Create(
+ window_.get(), gfx::Point(), HTTOPLEFT,
+ aura::client::WINDOW_MOVE_SOURCE_MOUSE, empty_windows()));
+ ASSERT_TRUE(resizer.get());
+ resizer->Drag(CalculateDragPoint(*resizer, 0, 0), 0);
+ EXPECT_EQ("98,201 22x29", window_->bounds().ToString());
+ resizer->RevertDrag();
+ }
+}
+
+TEST_F(WorkspaceWindowResizerTest, MagneticallyResize_TOPRIGHT) {
+ window_->SetBounds(gfx::Rect(100, 200, 20, 30));
+ window2_->Show();
+
+ {
+ window2_->SetBounds(gfx::Rect(111, 179, 10, 20));
+ scoped_ptr<WorkspaceWindowResizer> resizer(WorkspaceWindowResizer::Create(
+ window_.get(), gfx::Point(), HTTOPRIGHT,
+ aura::client::WINDOW_MOVE_SOURCE_MOUSE, empty_windows()));
+ ASSERT_TRUE(resizer.get());
+ resizer->Drag(CalculateDragPoint(*resizer, 0, 0), 0);
+ EXPECT_EQ("100,199 21x31", window_->bounds().ToString());
+ resizer->RevertDrag();
+ }
+
+ {
+ window2_->SetBounds(gfx::Rect(121, 199, 10, 20));
+ scoped_ptr<WorkspaceWindowResizer> resizer(WorkspaceWindowResizer::Create(
+ window_.get(), gfx::Point(), HTTOPRIGHT,
+ aura::client::WINDOW_MOVE_SOURCE_MOUSE, empty_windows()));
+ ASSERT_TRUE(resizer.get());
+ resizer->Drag(CalculateDragPoint(*resizer, 0, 0), 0);
+ EXPECT_EQ("100,199 21x31", window_->bounds().ToString());
+ resizer->RevertDrag();
+ }
+}
+
+TEST_F(WorkspaceWindowResizerTest, MagneticallyResize_RIGHT) {
+ window_->SetBounds(gfx::Rect(100, 200, 20, 30));
+ window2_->SetBounds(gfx::Rect(121, 199, 10, 20));
+ window2_->Show();
+
+ scoped_ptr<WorkspaceWindowResizer> resizer(WorkspaceWindowResizer::Create(
+ window_.get(), gfx::Point(), HTRIGHT,
+ aura::client::WINDOW_MOVE_SOURCE_MOUSE, empty_windows()));
+ ASSERT_TRUE(resizer.get());
+ resizer->Drag(CalculateDragPoint(*resizer, 0, 0), 0);
+ EXPECT_EQ("100,200 21x30", window_->bounds().ToString());
+}
+
+TEST_F(WorkspaceWindowResizerTest, MagneticallyResize_BOTTOMRIGHT) {
+ window_->SetBounds(gfx::Rect(100, 200, 20, 30));
+ window2_->Show();
+
+ {
+ window2_->SetBounds(gfx::Rect(122, 212, 10, 20));
+ scoped_ptr<WorkspaceWindowResizer> resizer(WorkspaceWindowResizer::Create(
+ window_.get(), gfx::Point(), HTBOTTOMRIGHT,
+ aura::client::WINDOW_MOVE_SOURCE_MOUSE, empty_windows()));
+ ASSERT_TRUE(resizer.get());
+ resizer->Drag(CalculateDragPoint(*resizer, 0, 0), 0);
+ EXPECT_EQ("100,200 22x32", window_->bounds().ToString());
+ resizer->RevertDrag();
+ }
+
+ {
+ window2_->SetBounds(gfx::Rect(111, 233, 10, 20));
+ scoped_ptr<WorkspaceWindowResizer> resizer(WorkspaceWindowResizer::Create(
+ window_.get(), gfx::Point(), HTBOTTOMRIGHT,
+ aura::client::WINDOW_MOVE_SOURCE_MOUSE, empty_windows()));
+ ASSERT_TRUE(resizer.get());
+ resizer->Drag(CalculateDragPoint(*resizer, 0, 0), 0);
+ EXPECT_EQ("100,200 21x33", window_->bounds().ToString());
+ resizer->RevertDrag();
+ }
+}
+
+TEST_F(WorkspaceWindowResizerTest, MagneticallyResize_BOTTOM) {
+ window_->SetBounds(gfx::Rect(100, 200, 20, 30));
+ window2_->SetBounds(gfx::Rect(111, 233, 10, 20));
+ window2_->Show();
+
+ scoped_ptr<WorkspaceWindowResizer> resizer(WorkspaceWindowResizer::Create(
+ window_.get(), gfx::Point(), HTBOTTOM,
+ aura::client::WINDOW_MOVE_SOURCE_MOUSE, empty_windows()));
+ ASSERT_TRUE(resizer.get());
+ resizer->Drag(CalculateDragPoint(*resizer, 0, 0), 0);
+ EXPECT_EQ("100,200 20x33", window_->bounds().ToString());
+}
+
+TEST_F(WorkspaceWindowResizerTest, MagneticallyResize_BOTTOMLEFT) {
+ window_->SetBounds(gfx::Rect(100, 200, 20, 30));
+ window2_->Show();
+
+ {
+ window2_->SetBounds(gfx::Rect(99, 231, 10, 20));
+ scoped_ptr<WorkspaceWindowResizer> resizer(WorkspaceWindowResizer::Create(
+ window_.get(), gfx::Point(), HTBOTTOMLEFT,
+ aura::client::WINDOW_MOVE_SOURCE_MOUSE, empty_windows()));
+ ASSERT_TRUE(resizer.get());
+ resizer->Drag(CalculateDragPoint(*resizer, 0, 0), 0);
+ EXPECT_EQ("99,200 21x31", window_->bounds().ToString());
+ resizer->RevertDrag();
+ }
+
+ {
+ window2_->SetBounds(gfx::Rect(89, 209, 10, 20));
+ scoped_ptr<WorkspaceWindowResizer> resizer(WorkspaceWindowResizer::Create(
+ window_.get(), gfx::Point(), HTBOTTOMLEFT,
+ aura::client::WINDOW_MOVE_SOURCE_MOUSE, empty_windows()));
+ ASSERT_TRUE(resizer.get());
+ resizer->Drag(CalculateDragPoint(*resizer, 0, 0), 0);
+ EXPECT_EQ("99,200 21x29", window_->bounds().ToString());
+ resizer->RevertDrag();
+ }
+}
+
+TEST_F(WorkspaceWindowResizerTest, MagneticallyResize_LEFT) {
+ window2_->SetBounds(gfx::Rect(89, 209, 10, 20));
+ window_->SetBounds(gfx::Rect(100, 200, 20, 30));
+ window2_->Show();
+
+ scoped_ptr<WorkspaceWindowResizer> resizer(WorkspaceWindowResizer::Create(
+ window_.get(), gfx::Point(), HTLEFT,
+ aura::client::WINDOW_MOVE_SOURCE_MOUSE, empty_windows()));
+ ASSERT_TRUE(resizer.get());
+ resizer->Drag(CalculateDragPoint(*resizer, 0, 0), 0);
+ EXPECT_EQ("99,200 21x30", window_->bounds().ToString());
+}
+
+// Test that the user user moved window flag is getting properly set.
+TEST_F(WorkspaceWindowResizerTest, CheckUserWindowMangedFlags) {
+ window_->SetBounds(gfx::Rect( 0, 50, 400, 200));
+
+ std::vector<aura::Window*> no_attached_windows;
+ // Check that an abort doesn't change anything.
+ {
+ scoped_ptr<WorkspaceWindowResizer> resizer(WorkspaceWindowResizer::Create(
+ window_.get(), gfx::Point(), HTCAPTION,
+ aura::client::WINDOW_MOVE_SOURCE_MOUSE, no_attached_windows));
+ ASSERT_TRUE(resizer.get());
+ // Move it 100 to the bottom.
+ resizer->Drag(CalculateDragPoint(*resizer, 0, 100), 0);
+ EXPECT_EQ("0,150 400x200", window_->bounds().ToString());
+ resizer->RevertDrag();
+
+ EXPECT_FALSE(ash::wm::HasUserChangedWindowPositionOrSize(window_.get()));
+ }
+
+ // Check that a completed move / size does change the user coordinates.
+ {
+ scoped_ptr<WorkspaceWindowResizer> resizer(WorkspaceWindowResizer::Create(
+ window_.get(), gfx::Point(), HTCAPTION,
+ aura::client::WINDOW_MOVE_SOURCE_MOUSE, no_attached_windows));
+ ASSERT_TRUE(resizer.get());
+ // Move it 100 to the bottom.
+ resizer->Drag(CalculateDragPoint(*resizer, 0, 100), 0);
+ EXPECT_EQ("0,150 400x200", window_->bounds().ToString());
+ resizer->CompleteDrag(0);
+ EXPECT_TRUE(ash::wm::HasUserChangedWindowPositionOrSize(window_.get()));
+ }
+}
+
+// Test that a window with a specified max size doesn't exceed it when dragged.
+TEST_F(WorkspaceWindowResizerTest, TestMaxSizeEnforced) {
+ window_->SetBounds(gfx::Rect(0, 0, 400, 300));
+ delegate_.set_max_size(gfx::Size(401, 301));
+
+ scoped_ptr<WorkspaceWindowResizer> resizer(WorkspaceWindowResizer::Create(
+ window_.get(), gfx::Point(), HTBOTTOMRIGHT,
+ aura::client::WINDOW_MOVE_SOURCE_MOUSE, empty_windows()));
+ resizer->Drag(CalculateDragPoint(*resizer, 2, 2), 0);
+ EXPECT_EQ(401, window_->bounds().width());
+ EXPECT_EQ(301, window_->bounds().height());
+}
+
+// Test that a window with a specified max width doesn't restrict its height.
+TEST_F(WorkspaceWindowResizerTest, TestPartialMaxSizeEnforced) {
+ window_->SetBounds(gfx::Rect(0, 0, 400, 300));
+ delegate_.set_max_size(gfx::Size(401, 0));
+
+ scoped_ptr<WorkspaceWindowResizer> resizer(WorkspaceWindowResizer::Create(
+ window_.get(), gfx::Point(), HTBOTTOMRIGHT,
+ aura::client::WINDOW_MOVE_SOURCE_MOUSE, empty_windows()));
+ resizer->Drag(CalculateDragPoint(*resizer, 2, 2), 0);
+ EXPECT_EQ(401, window_->bounds().width());
+ EXPECT_EQ(302, window_->bounds().height());
+}
+
+// Test that a window with a specified max size can't be snapped.
+TEST_F(WorkspaceWindowResizerTest, PhantomSnapMaxSize) {
+ {
+ // With max size not set we get a phantom window controller for dragging off
+ // the right hand side.
+ window_->SetBounds(gfx::Rect(0, 0, 300, 200));
+
+ scoped_ptr<WorkspaceWindowResizer> resizer(WorkspaceWindowResizer::Create(
+ window_.get(), gfx::Point(), HTCAPTION,
+ aura::client::WINDOW_MOVE_SOURCE_MOUSE, empty_windows()));
+ EXPECT_FALSE(resizer->snap_phantom_window_controller_.get());
+ resizer->Drag(CalculateDragPoint(*resizer, 801, 0), 0);
+ EXPECT_TRUE(resizer->snap_phantom_window_controller_.get());
+ }
+ {
+ // With max size defined, we get no phantom window.
+ window_->SetBounds(gfx::Rect(0, 0, 300, 200));
+ delegate_.set_max_size(gfx::Size(300, 200));
+
+ scoped_ptr<WorkspaceWindowResizer> resizer(WorkspaceWindowResizer::Create(
+ window_.get(), gfx::Point(), HTCAPTION,
+ aura::client::WINDOW_MOVE_SOURCE_MOUSE, empty_windows()));
+ resizer->Drag(CalculateDragPoint(*resizer, 801, 0), 0);
+ EXPECT_FALSE(resizer->snap_phantom_window_controller_.get());
+ }
+}
+
+TEST_F(WorkspaceWindowResizerTest, DontRewardRightmostWindowForOverflows) {
+ UpdateDisplay("600x800");
+ aura::RootWindow* root = Shell::GetPrimaryRootWindow();
+ Shell::GetInstance()->SetDisplayWorkAreaInsets(root, gfx::Insets());
+
+ // Four 100x100 windows flush against eachother, starting at 100,100.
+ window_->SetBounds(gfx::Rect( 100, 100, 100, 100));
+ window2_->SetBounds(gfx::Rect(200, 100, 100, 100));
+ window3_->SetBounds(gfx::Rect(300, 100, 100, 100));
+ window4_->SetBounds(gfx::Rect(400, 100, 100, 100));
+ delegate2_.set_max_size(gfx::Size(101, 0));
+
+ std::vector<aura::Window*> windows;
+ windows.push_back(window2_.get());
+ windows.push_back(window3_.get());
+ windows.push_back(window4_.get());
+ scoped_ptr<WorkspaceWindowResizer> resizer(WorkspaceWindowResizer::Create(
+ window_.get(), gfx::Point(), HTRIGHT,
+ aura::client::WINDOW_MOVE_SOURCE_MOUSE, windows));
+ ASSERT_TRUE(resizer.get());
+ // Move it 51 to the left, which should contract w1 and expand w2-4.
+ // w2 will hit its max size straight away, and in doing so will leave extra
+ // pixels that a naive implementation may award to the rightmost window. A
+ // fair implementation will give 25 pixels to each of the other windows.
+ resizer->Drag(CalculateDragPoint(*resizer, -51, 0), 0);
+ EXPECT_EQ("100,100 49x100", window_->bounds().ToString());
+ EXPECT_EQ("149,100 101x100", window2_->bounds().ToString());
+ EXPECT_EQ("250,100 125x100", window3_->bounds().ToString());
+ EXPECT_EQ("375,100 125x100", window4_->bounds().ToString());
+}
+
+TEST_F(WorkspaceWindowResizerTest, DontExceedMaxWidth) {
+ UpdateDisplay("600x800");
+ aura::RootWindow* root = Shell::GetPrimaryRootWindow();
+ Shell::GetInstance()->SetDisplayWorkAreaInsets(root, gfx::Insets());
+
+ // Four 100x100 windows flush against eachother, starting at 100,100.
+ window_->SetBounds(gfx::Rect( 100, 100, 100, 100));
+ window2_->SetBounds(gfx::Rect(200, 100, 100, 100));
+ window3_->SetBounds(gfx::Rect(300, 100, 100, 100));
+ window4_->SetBounds(gfx::Rect(400, 100, 100, 100));
+ delegate2_.set_max_size(gfx::Size(101, 0));
+ delegate3_.set_max_size(gfx::Size(101, 0));
+
+ std::vector<aura::Window*> windows;
+ windows.push_back(window2_.get());
+ windows.push_back(window3_.get());
+ windows.push_back(window4_.get());
+ scoped_ptr<WorkspaceWindowResizer> resizer(WorkspaceWindowResizer::Create(
+ window_.get(), gfx::Point(), HTRIGHT,
+ aura::client::WINDOW_MOVE_SOURCE_MOUSE, windows));
+ ASSERT_TRUE(resizer.get());
+ // Move it 52 to the left, which should contract w1 and expand w2-4.
+ resizer->Drag(CalculateDragPoint(*resizer, -52, 0), 0);
+ EXPECT_EQ("100,100 48x100", window_->bounds().ToString());
+ EXPECT_EQ("148,100 101x100", window2_->bounds().ToString());
+ EXPECT_EQ("249,100 101x100", window3_->bounds().ToString());
+ EXPECT_EQ("350,100 150x100", window4_->bounds().ToString());
+}
+
+TEST_F(WorkspaceWindowResizerTest, DontExceedMaxHeight) {
+ UpdateDisplay("600x800");
+ aura::RootWindow* root = Shell::GetPrimaryRootWindow();
+ Shell::GetInstance()->SetDisplayWorkAreaInsets(root, gfx::Insets());
+
+ // Four 100x100 windows flush against eachother, starting at 100,100.
+ window_->SetBounds(gfx::Rect( 100, 100, 100, 100));
+ window2_->SetBounds(gfx::Rect(100, 200, 100, 100));
+ window3_->SetBounds(gfx::Rect(100, 300, 100, 100));
+ window4_->SetBounds(gfx::Rect(100, 400, 100, 100));
+ delegate2_.set_max_size(gfx::Size(0, 101));
+ delegate3_.set_max_size(gfx::Size(0, 101));
+
+ std::vector<aura::Window*> windows;
+ windows.push_back(window2_.get());
+ windows.push_back(window3_.get());
+ windows.push_back(window4_.get());
+ scoped_ptr<WorkspaceWindowResizer> resizer(WorkspaceWindowResizer::Create(
+ window_.get(), gfx::Point(), HTBOTTOM,
+ aura::client::WINDOW_MOVE_SOURCE_MOUSE, windows));
+ ASSERT_TRUE(resizer.get());
+ // Move it 52 up, which should contract w1 and expand w2-4.
+ resizer->Drag(CalculateDragPoint(*resizer, 0, -52), 0);
+ EXPECT_EQ("100,100 100x48", window_->bounds().ToString());
+ EXPECT_EQ("100,148 100x101", window2_->bounds().ToString());
+ EXPECT_EQ("100,249 100x101", window3_->bounds().ToString());
+ EXPECT_EQ("100,350 100x150", window4_->bounds().ToString());
+}
+
+#if defined(OS_WIN)
+// RootWindow and Display can't resize on Windows Ash. http://crbug.com/165962
+#define MAYBE_DontExceedMinHeight DISABLED_DontExceedMinHeight
+#else
+#define MAYBE_DontExceedMinHeight DontExceedMinHeight
+#endif
+
+TEST_F(WorkspaceWindowResizerTest, MAYBE_DontExceedMinHeight) {
+ UpdateDisplay("600x500");
+ aura::RootWindow* root = Shell::GetPrimaryRootWindow();
+ Shell::GetInstance()->SetDisplayWorkAreaInsets(root, gfx::Insets());
+
+ // Four 100x100 windows flush against eachother, starting at 100,100.
+ window_->SetBounds(gfx::Rect( 100, 100, 100, 100));
+ window2_->SetBounds(gfx::Rect(100, 200, 100, 100));
+ window3_->SetBounds(gfx::Rect(100, 300, 100, 100));
+ window4_->SetBounds(gfx::Rect(100, 400, 100, 100));
+ delegate2_.set_min_size(gfx::Size(0, 99));
+ delegate3_.set_min_size(gfx::Size(0, 99));
+
+ std::vector<aura::Window*> windows;
+ windows.push_back(window2_.get());
+ windows.push_back(window3_.get());
+ windows.push_back(window4_.get());
+ scoped_ptr<WorkspaceWindowResizer> resizer(WorkspaceWindowResizer::Create(
+ window_.get(), gfx::Point(), HTBOTTOM,
+ aura::client::WINDOW_MOVE_SOURCE_MOUSE, windows));
+ ASSERT_TRUE(resizer.get());
+ // Move it 52 down, which should expand w1 and contract w2-4.
+ resizer->Drag(CalculateDragPoint(*resizer, 0, 52), 0);
+ EXPECT_EQ("100,100 100x152", window_->bounds().ToString());
+ EXPECT_EQ("100,252 100x99", window2_->bounds().ToString());
+ EXPECT_EQ("100,351 100x99", window3_->bounds().ToString());
+ EXPECT_EQ("100,450 100x50", window4_->bounds().ToString());
+}
+
+TEST_F(WorkspaceWindowResizerTest, DontExpandRightmostPastMaxWidth) {
+ UpdateDisplay("600x800");
+ aura::RootWindow* root = Shell::GetPrimaryRootWindow();
+ Shell::GetInstance()->SetDisplayWorkAreaInsets(root, gfx::Insets());
+
+ // Three 100x100 windows flush against eachother, starting at 100,100.
+ window_->SetBounds(gfx::Rect( 100, 100, 100, 100));
+ window2_->SetBounds(gfx::Rect(200, 100, 100, 100));
+ window3_->SetBounds(gfx::Rect(300, 100, 100, 100));
+ delegate3_.set_max_size(gfx::Size(101, 0));
+
+ std::vector<aura::Window*> windows;
+ windows.push_back(window2_.get());
+ windows.push_back(window3_.get());
+ windows.push_back(window4_.get());
+ scoped_ptr<WorkspaceWindowResizer> resizer(WorkspaceWindowResizer::Create(
+ window_.get(), gfx::Point(), HTRIGHT,
+ aura::client::WINDOW_MOVE_SOURCE_MOUSE, windows));
+ ASSERT_TRUE(resizer.get());
+ // Move it 51 to the left, which should contract w1 and expand w2-3.
+ resizer->Drag(CalculateDragPoint(*resizer, -51, 0), 0);
+ EXPECT_EQ("100,100 49x100", window_->bounds().ToString());
+ EXPECT_EQ("149,100 150x100", window2_->bounds().ToString());
+ EXPECT_EQ("299,100 101x100", window3_->bounds().ToString());
+}
+
+TEST_F(WorkspaceWindowResizerTest, MoveAttachedWhenGrownToMaxSize) {
+ UpdateDisplay("600x800");
+ aura::RootWindow* root = Shell::GetPrimaryRootWindow();
+ Shell::GetInstance()->SetDisplayWorkAreaInsets(root, gfx::Insets());
+
+ // Three 100x100 windows flush against eachother, starting at 100,100.
+ window_->SetBounds(gfx::Rect( 100, 100, 100, 100));
+ window2_->SetBounds(gfx::Rect(200, 100, 100, 100));
+ window3_->SetBounds(gfx::Rect(300, 100, 100, 100));
+ delegate2_.set_max_size(gfx::Size(101, 0));
+ delegate3_.set_max_size(gfx::Size(101, 0));
+
+ std::vector<aura::Window*> windows;
+ windows.push_back(window2_.get());
+ windows.push_back(window3_.get());
+ windows.push_back(window4_.get());
+ scoped_ptr<WorkspaceWindowResizer> resizer(WorkspaceWindowResizer::Create(
+ window_.get(), gfx::Point(), HTRIGHT,
+ aura::client::WINDOW_MOVE_SOURCE_MOUSE, windows));
+ ASSERT_TRUE(resizer.get());
+ // Move it 52 to the left, which should contract w1 and expand and move w2-3.
+ resizer->Drag(CalculateDragPoint(*resizer, -52, 0), 0);
+ EXPECT_EQ("100,100 48x100", window_->bounds().ToString());
+ EXPECT_EQ("148,100 101x100", window2_->bounds().ToString());
+ EXPECT_EQ("249,100 101x100", window3_->bounds().ToString());
+}
+
+#if defined(OS_WIN)
+// RootWindow and Display can't resize on Windows Ash. http://crbug.com/165962
+#define MAYBE_MainWindowHonoursMaxWidth DISABLED_MainWindowHonoursMaxWidth
+#else
+#define MAYBE_MainWindowHonoursMaxWidth MainWindowHonoursMaxWidth
+#endif
+
+TEST_F(WorkspaceWindowResizerTest, MAYBE_MainWindowHonoursMaxWidth) {
+ UpdateDisplay("400x800");
+ aura::RootWindow* root = Shell::GetPrimaryRootWindow();
+ Shell::GetInstance()->SetDisplayWorkAreaInsets(root, gfx::Insets());
+
+ // Three 100x100 windows flush against eachother, starting at 100,100.
+ window_->SetBounds(gfx::Rect( 100, 100, 100, 100));
+ window2_->SetBounds(gfx::Rect(200, 100, 100, 100));
+ window3_->SetBounds(gfx::Rect(300, 100, 100, 100));
+ delegate_.set_max_size(gfx::Size(102, 0));
+
+ std::vector<aura::Window*> windows;
+ windows.push_back(window2_.get());
+ windows.push_back(window3_.get());
+ windows.push_back(window4_.get());
+ scoped_ptr<WorkspaceWindowResizer> resizer(WorkspaceWindowResizer::Create(
+ window_.get(), gfx::Point(), HTRIGHT,
+ aura::client::WINDOW_MOVE_SOURCE_MOUSE, windows));
+ ASSERT_TRUE(resizer.get());
+ // Move it 50 to the right, which should expand w1 and contract w2-3, as they
+ // won't fit in the root window in their original sizes.
+ resizer->Drag(CalculateDragPoint(*resizer, 50, 0), 0);
+ EXPECT_EQ("100,100 102x100", window_->bounds().ToString());
+ EXPECT_EQ("202,100 99x100", window2_->bounds().ToString());
+ EXPECT_EQ("301,100 99x100", window3_->bounds().ToString());
+}
+
+TEST_F(WorkspaceWindowResizerTest, MainWindowHonoursMinWidth) {
+ UpdateDisplay("400x800");
+ aura::RootWindow* root = Shell::GetPrimaryRootWindow();
+ Shell::GetInstance()->SetDisplayWorkAreaInsets(root, gfx::Insets());
+
+ // Three 100x100 windows flush against eachother, starting at 100,100.
+ window_->SetBounds(gfx::Rect( 100, 100, 100, 100));
+ window2_->SetBounds(gfx::Rect(200, 100, 100, 100));
+ window3_->SetBounds(gfx::Rect(300, 100, 100, 100));
+ delegate_.set_min_size(gfx::Size(98, 0));
+
+ std::vector<aura::Window*> windows;
+ windows.push_back(window2_.get());
+ windows.push_back(window3_.get());
+ scoped_ptr<WorkspaceWindowResizer> resizer(WorkspaceWindowResizer::Create(
+ window_.get(), gfx::Point(), HTRIGHT,
+ aura::client::WINDOW_MOVE_SOURCE_MOUSE, windows));
+ ASSERT_TRUE(resizer.get());
+ // Move it 50 to the left, which should contract w1 and expand w2-3.
+ resizer->Drag(CalculateDragPoint(*resizer, -50, 0), 0);
+ EXPECT_EQ("100,100 98x100", window_->bounds().ToString());
+ EXPECT_EQ("198,100 101x100", window2_->bounds().ToString());
+ EXPECT_EQ("299,100 101x100", window3_->bounds().ToString());
+}
+
+// The following variants test that windows are resized correctly to the edges
+// of the screen using touch, when touch point is off of the window border.
+TEST_F(WorkspaceWindowResizerTest, TouchResizeToEdge_RIGHT) {
+ shelf_layout_manager()->SetAutoHideBehavior(SHELF_AUTO_HIDE_ALWAYS_HIDDEN);
+
+ InitTouchResizeWindow(gfx::Rect(100, 100, 600, kRootHeight - 200), HTRIGHT);
+ EXPECT_EQ(gfx::Rect(100, 100, 600, kRootHeight - 200).ToString(),
+ touch_resize_window_->bounds().ToString());
+
+ aura::test::EventGenerator generator(Shell::GetPrimaryRootWindow(),
+ touch_resize_window_.get());
+
+ // Drag out of the right border a bit and check if the border is aligned with
+ // the touch point.
+ generator.GestureScrollSequence(gfx::Point(715, kRootHeight / 2),
+ gfx::Point(725, kRootHeight / 2),
+ base::TimeDelta::FromMilliseconds(100),
+ 1);
+ EXPECT_EQ(gfx::Rect(100, 100, 625, kRootHeight - 200).ToString(),
+ touch_resize_window_->bounds().ToString());
+ // Drag more, but stop before being snapped to the edge.
+ generator.GestureScrollSequence(gfx::Point(725, kRootHeight / 2),
+ gfx::Point(760, kRootHeight / 2),
+ base::TimeDelta::FromMilliseconds(100),
+ 1);
+ EXPECT_EQ(gfx::Rect(100, 100, 660, kRootHeight - 200).ToString(),
+ touch_resize_window_->bounds().ToString());
+ // Drag even more to snap to the edge.
+ generator.GestureScrollSequence(gfx::Point(760, kRootHeight / 2),
+ gfx::Point(775, kRootHeight / 2),
+ base::TimeDelta::FromMilliseconds(100),
+ 1);
+ EXPECT_EQ(gfx::Rect(100, 100, 700, kRootHeight - 200).ToString(),
+ touch_resize_window_->bounds().ToString());
+}
+
+TEST_F(WorkspaceWindowResizerTest, TouchResizeToEdge_LEFT) {
+ shelf_layout_manager()->SetAutoHideBehavior(SHELF_AUTO_HIDE_ALWAYS_HIDDEN);
+
+ InitTouchResizeWindow(gfx::Rect(100, 100, 600, kRootHeight - 200), HTLEFT);
+ EXPECT_EQ(gfx::Rect(100, 100, 600, kRootHeight - 200).ToString(),
+ touch_resize_window_->bounds().ToString());
+
+ aura::test::EventGenerator generator(Shell::GetPrimaryRootWindow(),
+ touch_resize_window_.get());
+
+ // Drag out of the left border a bit and check if the border is aligned with
+ // the touch point.
+ generator.GestureScrollSequence(gfx::Point(85, kRootHeight / 2),
+ gfx::Point(75, kRootHeight / 2),
+ base::TimeDelta::FromMilliseconds(100),
+ 1);
+ EXPECT_EQ(gfx::Rect(75, 100, 625, kRootHeight - 200).ToString(),
+ touch_resize_window_->bounds().ToString());
+ // Drag more, but stop before being snapped to the edge.
+ generator.GestureScrollSequence(gfx::Point(75, kRootHeight / 2),
+ gfx::Point(40, kRootHeight / 2),
+ base::TimeDelta::FromMilliseconds(100),
+ 1);
+ EXPECT_EQ(gfx::Rect(40, 100, 660, kRootHeight - 200).ToString(),
+ touch_resize_window_->bounds().ToString());
+ // Drag even more to snap to the edge.
+ generator.GestureScrollSequence(gfx::Point(40, kRootHeight / 2),
+ gfx::Point(25, kRootHeight / 2),
+ base::TimeDelta::FromMilliseconds(100),
+ 1);
+ EXPECT_EQ(gfx::Rect(0, 100, 700, kRootHeight - 200).ToString(),
+ touch_resize_window_->bounds().ToString());
+}
+
+TEST_F(WorkspaceWindowResizerTest, TouchResizeToEdge_TOP) {
+ shelf_layout_manager()->SetAutoHideBehavior(SHELF_AUTO_HIDE_ALWAYS_HIDDEN);
+
+ InitTouchResizeWindow(gfx::Rect(100, 100, 600, kRootHeight - 200), HTTOP);
+ EXPECT_EQ(gfx::Rect(100, 100, 600, kRootHeight - 200).ToString(),
+ touch_resize_window_->bounds().ToString());
+
+ aura::test::EventGenerator generator(Shell::GetPrimaryRootWindow(),
+ touch_resize_window_.get());
+
+ // Drag out of the top border a bit and check if the border is aligned with
+ // the touch point.
+ generator.GestureScrollSequence(gfx::Point(400, 85),
+ gfx::Point(400, 75),
+ base::TimeDelta::FromMilliseconds(100),
+ 1);
+ EXPECT_EQ(gfx::Rect(100, 75, 600, kRootHeight - 175).ToString(),
+ touch_resize_window_->bounds().ToString());
+ // Drag more, but stop before being snapped to the edge.
+ generator.GestureScrollSequence(gfx::Point(400, 75),
+ gfx::Point(400, 40),
+ base::TimeDelta::FromMilliseconds(100),
+ 1);
+ EXPECT_EQ(gfx::Rect(100, 40, 600, kRootHeight - 140).ToString(),
+ touch_resize_window_->bounds().ToString());
+ // Drag even more to snap to the edge.
+ generator.GestureScrollSequence(gfx::Point(400, 40),
+ gfx::Point(400, 25),
+ base::TimeDelta::FromMilliseconds(100),
+ 1);
+ EXPECT_EQ(gfx::Rect(100, 0, 600, kRootHeight - 100).ToString(),
+ touch_resize_window_->bounds().ToString());
+}
+
+TEST_F(WorkspaceWindowResizerTest, TouchResizeToEdge_BOTTOM) {
+ shelf_layout_manager()->SetAutoHideBehavior(SHELF_AUTO_HIDE_ALWAYS_HIDDEN);
+
+ InitTouchResizeWindow(gfx::Rect(100, 100, 600, kRootHeight - 200), HTBOTTOM);
+ EXPECT_EQ(gfx::Rect(100, 100, 600, kRootHeight - 200).ToString(),
+ touch_resize_window_->bounds().ToString());
+
+ aura::test::EventGenerator generator(Shell::GetPrimaryRootWindow(),
+ touch_resize_window_.get());
+
+ // Drag out of the bottom border a bit and check if the border is aligned with
+ // the touch point.
+ generator.GestureScrollSequence(gfx::Point(400, kRootHeight - 85),
+ gfx::Point(400, kRootHeight - 75),
+ base::TimeDelta::FromMilliseconds(100),
+ 1);
+ EXPECT_EQ(gfx::Rect(100, 100, 600, kRootHeight - 175).ToString(),
+ touch_resize_window_->bounds().ToString());
+ // Drag more, but stop before being snapped to the edge.
+ generator.GestureScrollSequence(gfx::Point(400, kRootHeight - 75),
+ gfx::Point(400, kRootHeight - 40),
+ base::TimeDelta::FromMilliseconds(100),
+ 1);
+ EXPECT_EQ(gfx::Rect(100, 100, 600, kRootHeight - 140).ToString(),
+ touch_resize_window_->bounds().ToString());
+ // Drag even more to snap to the edge.
+ generator.GestureScrollSequence(gfx::Point(400, kRootHeight - 40),
+ gfx::Point(400, kRootHeight - 25),
+ base::TimeDelta::FromMilliseconds(100),
+ 1);
+ EXPECT_EQ(gfx::Rect(100, 100, 600, kRootHeight - 100).ToString(),
+ touch_resize_window_->bounds().ToString());
+}
+
+TEST_F(WorkspaceWindowResizerTest, PhantomWindowShow) {
+ if (!SupportsMultipleDisplays())
+ return;
+
+ UpdateDisplay("500x400,500x400");
+ window_->SetBoundsInScreen(gfx::Rect(0, 0, 50, 60),
+ Shell::GetScreen()->GetPrimaryDisplay());
+ Shell::RootWindowList root_windows = Shell::GetAllRootWindows();
+ EXPECT_EQ(root_windows[0], window_->GetRootWindow());
+
+ scoped_ptr<WorkspaceWindowResizer> resizer(WorkspaceWindowResizer::Create(
+ window_.get(), gfx::Point(), HTCAPTION,
+ aura::client::WINDOW_MOVE_SOURCE_MOUSE, empty_windows()));
+ ASSERT_TRUE(resizer.get());
+ EXPECT_FALSE(resizer->snap_phantom_window_controller_.get());
+
+ // The pointer is on the edge but not shared. The snap phantom window
+ // controller should be non-NULL.
+ resizer->Drag(CalculateDragPoint(*resizer, 499, 0), 0);
+ EXPECT_TRUE(resizer->snap_phantom_window_controller_.get());
+ PhantomWindowController* phantom_controller(
+ resizer->snap_phantom_window_controller_.get());
+
+ // phantom widget only in the left screen.
+ phantom_controller->Show(gfx::Rect(100, 100, 50, 60));
+ EXPECT_TRUE(phantom_controller->phantom_widget_);
+ EXPECT_FALSE(phantom_controller->phantom_widget_start_);
+ EXPECT_EQ(
+ root_windows[0],
+ phantom_controller->phantom_widget_->GetNativeWindow()->GetRootWindow());
+
+ // Move phantom widget into the right screen. Test that 2 widgets got created.
+ phantom_controller->Show(gfx::Rect(600, 100, 50, 60));
+ EXPECT_TRUE(phantom_controller->phantom_widget_);
+ EXPECT_TRUE(phantom_controller->phantom_widget_start_);
+ EXPECT_EQ(
+ root_windows[1],
+ phantom_controller->phantom_widget_->GetNativeWindow()->GetRootWindow());
+ EXPECT_EQ(
+ root_windows[0],
+ phantom_controller->phantom_widget_start_->GetNativeWindow()->
+ GetRootWindow());
+ RunAnimationTillComplete(phantom_controller->animation_.get());
+
+ // Move phantom widget only in the right screen. Start widget should close.
+ phantom_controller->Show(gfx::Rect(700, 100, 50, 60));
+ EXPECT_TRUE(phantom_controller->phantom_widget_);
+ EXPECT_FALSE(phantom_controller->phantom_widget_start_);
+ EXPECT_EQ(
+ root_windows[1],
+ phantom_controller->phantom_widget_->GetNativeWindow()->GetRootWindow());
+ RunAnimationTillComplete(phantom_controller->animation_.get());
+
+ // Move phantom widget into the left screen. Start widget should open.
+ phantom_controller->Show(gfx::Rect(100, 100, 50, 60));
+ EXPECT_TRUE(phantom_controller->phantom_widget_);
+ EXPECT_TRUE(phantom_controller->phantom_widget_start_);
+ EXPECT_EQ(
+ root_windows[0],
+ phantom_controller->phantom_widget_->GetNativeWindow()->GetRootWindow());
+ EXPECT_EQ(
+ root_windows[1],
+ phantom_controller->phantom_widget_start_->GetNativeWindow()->
+ GetRootWindow());
+ RunAnimationTillComplete(phantom_controller->animation_.get());
+
+ // Move phantom widget while in the left screen. Start widget should close.
+ phantom_controller->Show(gfx::Rect(200, 100, 50, 60));
+ EXPECT_TRUE(phantom_controller->phantom_widget_);
+ EXPECT_FALSE(phantom_controller->phantom_widget_start_);
+ EXPECT_EQ(
+ root_windows[0],
+ phantom_controller->phantom_widget_->GetNativeWindow()->GetRootWindow());
+ RunAnimationTillComplete(phantom_controller->animation_.get());
+
+ // Move phantom widget spanning both screens with most of the window in the
+ // right screen. Two widgets are created.
+ phantom_controller->Show(gfx::Rect(495, 100, 50, 60));
+ EXPECT_TRUE(phantom_controller->phantom_widget_);
+ EXPECT_TRUE(phantom_controller->phantom_widget_start_);
+ EXPECT_EQ(
+ root_windows[1],
+ phantom_controller->phantom_widget_->GetNativeWindow()->GetRootWindow());
+ EXPECT_EQ(
+ root_windows[0],
+ phantom_controller->phantom_widget_start_->GetNativeWindow()->
+ GetRootWindow());
+ RunAnimationTillComplete(phantom_controller->animation_.get());
+
+ // Move phantom widget back into the left screen. Phantom widgets should swap.
+ phantom_controller->Show(gfx::Rect(200, 100, 50, 60));
+ EXPECT_TRUE(phantom_controller->phantom_widget_);
+ EXPECT_TRUE(phantom_controller->phantom_widget_start_);
+ EXPECT_EQ(
+ root_windows[0],
+ phantom_controller->phantom_widget_->GetNativeWindow()->GetRootWindow());
+ EXPECT_EQ(
+ root_windows[1],
+ phantom_controller->phantom_widget_start_->GetNativeWindow()->
+ GetRootWindow());
+ RunAnimationTillComplete(phantom_controller->animation_.get());
+
+ // Hide phantom controller. Both widgets should close.
+ phantom_controller->Hide();
+ EXPECT_FALSE(phantom_controller->phantom_widget_);
+ EXPECT_FALSE(phantom_controller->phantom_widget_start_);
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/wm/workspace_controller.cc b/chromium/ash/wm/workspace_controller.cc
new file mode 100644
index 00000000000..7fbbda2f7af
--- /dev/null
+++ b/chromium/ash/wm/workspace_controller.cc
@@ -0,0 +1,128 @@
+// Copyright (c) 2012 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.
+
+#include "ash/wm/workspace_controller.h"
+
+#include "ash/shelf/shelf_layout_manager.h"
+#include "ash/shell.h"
+#include "ash/shell_window_ids.h"
+#include "ash/wm/base_layout_manager.h"
+#include "ash/wm/property_util.h"
+#include "ash/wm/window_animations.h"
+#include "ash/wm/window_properties.h"
+#include "ash/wm/window_util.h"
+#include "ash/wm/workspace/workspace_event_handler.h"
+#include "ash/wm/workspace/workspace_layout_manager.h"
+#include "ui/aura/client/activation_client.h"
+#include "ui/aura/client/aura_constants.h"
+#include "ui/aura/root_window.h"
+#include "ui/aura/window.h"
+#include "ui/compositor/layer.h"
+#include "ui/compositor/scoped_layer_animation_settings.h"
+#include "ui/views/corewm/visibility_controller.h"
+#include "ui/views/corewm/window_animations.h"
+
+namespace ash {
+namespace internal {
+namespace {
+
+// Amount of time to pause before animating anything. Only used during initial
+// animation (when logging in).
+const int kInitialPauseTimeMS = 750;
+
+} // namespace
+
+WorkspaceController::WorkspaceController(aura::Window* viewport)
+ : viewport_(viewport),
+ shelf_(NULL),
+ event_handler_(new WorkspaceEventHandler(viewport_)) {
+ SetWindowVisibilityAnimationTransition(
+ viewport_, views::corewm::ANIMATE_NONE);
+
+ // The layout-manager cannot be created in the initializer list since it
+ // depends on the window to have been initialized.
+ layout_manager_ = new WorkspaceLayoutManager(viewport_);
+ viewport_->SetLayoutManager(layout_manager_);
+
+ viewport_->Show();
+}
+
+WorkspaceController::~WorkspaceController() {
+ viewport_->SetLayoutManager(NULL);
+ viewport_->SetEventFilter(NULL);
+ viewport_->RemovePreTargetHandler(event_handler_.get());
+ viewport_->RemovePostTargetHandler(event_handler_.get());
+}
+
+WorkspaceWindowState WorkspaceController::GetWindowState() const {
+ if (!shelf_)
+ return WORKSPACE_WINDOW_STATE_DEFAULT;
+
+ const gfx::Rect shelf_bounds(shelf_->GetIdealBounds());
+ const aura::Window::Windows& windows(viewport_->children());
+ bool window_overlaps_launcher = false;
+ bool has_maximized_window = false;
+ for (aura::Window::Windows::const_iterator i = windows.begin();
+ i != windows.end(); ++i) {
+ if (GetIgnoredByShelf(*i))
+ continue;
+ ui::Layer* layer = (*i)->layer();
+ if (!layer->GetTargetVisibility() || layer->GetTargetOpacity() == 0.0f)
+ continue;
+ if (wm::IsWindowMaximized(*i)) {
+ // An untracked window may still be fullscreen so we keep iterating when
+ // we hit a maximized window.
+ has_maximized_window = true;
+ } else if (wm::IsWindowFullscreen(*i)) {
+ return WORKSPACE_WINDOW_STATE_FULL_SCREEN;
+ }
+ if (!window_overlaps_launcher && (*i)->bounds().Intersects(shelf_bounds))
+ window_overlaps_launcher = true;
+ }
+ if (has_maximized_window)
+ return WORKSPACE_WINDOW_STATE_MAXIMIZED;
+
+ return window_overlaps_launcher ?
+ WORKSPACE_WINDOW_STATE_WINDOW_OVERLAPS_SHELF :
+ WORKSPACE_WINDOW_STATE_DEFAULT;
+}
+
+void WorkspaceController::SetShelf(ShelfLayoutManager* shelf) {
+ shelf_ = shelf;
+ layout_manager_->SetShelf(shelf);
+}
+
+void WorkspaceController::DoInitialAnimation() {
+ viewport_->Show();
+
+ viewport_->layer()->SetOpacity(0.0f);
+ SetTransformForScaleAnimation(
+ viewport_->layer(), LAYER_SCALE_ANIMATION_ABOVE);
+
+ // In order for pause to work we need to stop animations.
+ viewport_->layer()->GetAnimator()->StopAnimating();
+
+ {
+ ui::ScopedLayerAnimationSettings settings(
+ viewport_->layer()->GetAnimator());
+
+ settings.SetPreemptionStrategy(ui::LayerAnimator::ENQUEUE_NEW_ANIMATION);
+ viewport_->layer()->GetAnimator()->SchedulePauseForProperties(
+ base::TimeDelta::FromMilliseconds(kInitialPauseTimeMS),
+ ui::LayerAnimationElement::TRANSFORM,
+ ui::LayerAnimationElement::OPACITY,
+ ui::LayerAnimationElement::BRIGHTNESS,
+ ui::LayerAnimationElement::VISIBILITY,
+ -1);
+
+ settings.SetTweenType(ui::Tween::EASE_OUT);
+ settings.SetTransitionDuration(
+ base::TimeDelta::FromMilliseconds(kCrossFadeDurationMS));
+ viewport_->layer()->SetTransform(gfx::Transform());
+ viewport_->layer()->SetOpacity(1.0f);
+ }
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/wm/workspace_controller.h b/chromium/ash/wm/workspace_controller.h
new file mode 100644
index 00000000000..84df41c91d3
--- /dev/null
+++ b/chromium/ash/wm/workspace_controller.h
@@ -0,0 +1,55 @@
+// Copyright (c) 2012 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 ASH_WM_WORKSPACE_CONTROLLER_H_
+#define ASH_WM_WORKSPACE_CONTROLLER_H_
+
+#include "ash/ash_export.h"
+#include "ash/wm/workspace/workspace_types.h"
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+
+namespace aura {
+class Window;
+}
+
+namespace ash {
+namespace internal {
+
+class ShelfLayoutManager;
+class WorkspaceControllerTestHelper;
+class WorkspaceEventHandler;
+class WorkspaceLayoutManager;
+
+// WorkspaceController acts as a central place that ties together all the
+// various workspace pieces.
+class ASH_EXPORT WorkspaceController {
+ public:
+ explicit WorkspaceController(aura::Window* viewport);
+ virtual ~WorkspaceController();
+
+ // Returns the current window state.
+ WorkspaceWindowState GetWindowState() const;
+
+ void SetShelf(ShelfLayoutManager* shelf);
+
+ // Starts the animation that occurs on first login.
+ void DoInitialAnimation();
+
+ private:
+ friend class WorkspaceControllerTestHelper;
+
+ aura::Window* viewport_;
+
+ internal::ShelfLayoutManager* shelf_;
+ scoped_ptr<internal::WorkspaceEventHandler> event_handler_;
+ internal::WorkspaceLayoutManager* layout_manager_;
+
+ DISALLOW_COPY_AND_ASSIGN(WorkspaceController);
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_WM_WORKSPACE_CONTROLLER_H_
diff --git a/chromium/ash/wm/workspace_controller_test_helper.cc b/chromium/ash/wm/workspace_controller_test_helper.cc
new file mode 100644
index 00000000000..5ecfa2f5edf
--- /dev/null
+++ b/chromium/ash/wm/workspace_controller_test_helper.cc
@@ -0,0 +1,32 @@
+// Copyright (c) 2012 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.
+
+#include "ash/wm/workspace_controller_test_helper.h"
+
+#include "ash/wm/workspace_controller.h"
+#include "ash/wm/workspace/workspace_event_handler_test_helper.h"
+#include "ui/aura/window.h"
+
+namespace ash {
+namespace internal {
+
+WorkspaceControllerTestHelper::WorkspaceControllerTestHelper(
+ WorkspaceController* controller)
+ : controller_(controller) {
+}
+
+WorkspaceControllerTestHelper::~WorkspaceControllerTestHelper() {
+}
+
+WorkspaceEventHandler* WorkspaceControllerTestHelper::GetEventHandler() {
+ return controller_->event_handler_.get();
+}
+
+MultiWindowResizeController*
+WorkspaceControllerTestHelper::GetMultiWindowResizeController() {
+ return WorkspaceEventHandlerTestHelper(GetEventHandler()).resize_controller();
+}
+
+} // namespace internal
+} // namespace ash
diff --git a/chromium/ash/wm/workspace_controller_test_helper.h b/chromium/ash/wm/workspace_controller_test_helper.h
new file mode 100644
index 00000000000..ecdccccca07
--- /dev/null
+++ b/chromium/ash/wm/workspace_controller_test_helper.h
@@ -0,0 +1,33 @@
+// Copyright (c) 2012 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 ASH_WM_WORKSPACE_CONTROLLER_TEST_HELPER_H_
+#define ASH_WM_WORKSPACE_CONTROLLER_TEST_HELPER_H_
+
+#include "ash/wm/workspace_controller.h"
+
+namespace ash {
+namespace internal {
+
+class MultiWindowResizeController;
+class WorkspaceEventHandler;
+
+class WorkspaceControllerTestHelper {
+ public:
+ explicit WorkspaceControllerTestHelper(WorkspaceController* controller);
+ ~WorkspaceControllerTestHelper();
+
+ WorkspaceEventHandler* GetEventHandler();
+ MultiWindowResizeController* GetMultiWindowResizeController();
+
+ private:
+ WorkspaceController* controller_;
+
+ DISALLOW_COPY_AND_ASSIGN(WorkspaceControllerTestHelper);
+};
+
+} // namespace internal
+} // namespace ash
+
+#endif // ASH_WM_WORKSPACE_CONTROLLER_TEST_HELPER_H_
diff --git a/chromium/ash/wm/workspace_controller_unittest.cc b/chromium/ash/wm/workspace_controller_unittest.cc
new file mode 100644
index 00000000000..7dca75a08ba
--- /dev/null
+++ b/chromium/ash/wm/workspace_controller_unittest.cc
@@ -0,0 +1,1261 @@
+// Copyright 2013 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.
+
+#include "ash/wm/workspace_controller.h"
+
+#include <map>
+
+#include "ash/ash_switches.h"
+#include "ash/root_window_controller.h"
+#include "ash/screen_ash.h"
+#include "ash/shelf/shelf_layout_manager.h"
+#include "ash/shelf/shelf_widget.h"
+#include "ash/shell.h"
+#include "ash/shell_window_ids.h"
+#include "ash/system/status_area_widget.h"
+#include "ash/test/ash_test_base.h"
+#include "ash/test/shell_test_api.h"
+#include "ash/wm/activation_controller.h"
+#include "ash/wm/property_util.h"
+#include "ash/wm/window_properties.h"
+#include "ash/wm/window_util.h"
+#include "base/command_line.h"
+#include "base/strings/string_number_conversions.h"
+#include "ui/aura/client/aura_constants.h"
+#include "ui/aura/root_window.h"
+#include "ui/aura/test/event_generator.h"
+#include "ui/aura/test/test_window_delegate.h"
+#include "ui/aura/test/test_windows.h"
+#include "ui/aura/window.h"
+#include "ui/base/hit_test.h"
+#include "ui/base/ui_base_types.h"
+#include "ui/compositor/layer.h"
+#include "ui/compositor/scoped_animation_duration_scale_mode.h"
+#include "ui/gfx/screen.h"
+#include "ui/views/corewm/window_animations.h"
+#include "ui/views/widget/widget.h"
+
+using aura::Window;
+
+namespace ash {
+namespace internal {
+
+// Returns a string containing the names of all the children of |window| (in
+// order). Each entry is separated by a space.
+std::string GetWindowNames(const aura::Window* window) {
+ std::string result;
+ for (size_t i = 0; i < window->children().size(); ++i) {
+ if (i != 0)
+ result += " ";
+ result += window->children()[i]->name();
+ }
+ return result;
+}
+
+// Returns a string containing the names of windows corresponding to each of the
+// child layers of |window|'s layer. Any layers that don't correspond to a child
+// Window of |window| are ignored. The result is ordered based on the layer
+// ordering.
+std::string GetLayerNames(const aura::Window* window) {
+ typedef std::map<const ui::Layer*, std::string> LayerToWindowNameMap;
+ LayerToWindowNameMap window_names;
+ for (size_t i = 0; i < window->children().size(); ++i) {
+ window_names[window->children()[i]->layer()] =
+ window->children()[i]->name();
+ }
+
+ std::string result;
+ const std::vector<ui::Layer*>& layers(window->layer()->children());
+ for (size_t i = 0; i < layers.size(); ++i) {
+ LayerToWindowNameMap::iterator layer_i =
+ window_names.find(layers[i]);
+ if (layer_i != window_names.end()) {
+ if (!result.empty())
+ result += " ";
+ result += layer_i->second;
+ }
+ }
+ return result;
+}
+
+class WorkspaceControllerTest : public test::AshTestBase {
+ public:
+ WorkspaceControllerTest() {}
+ virtual ~WorkspaceControllerTest() {}
+
+ aura::Window* CreateTestWindowUnparented() {
+ aura::Window* window = new aura::Window(NULL);
+ window->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_NORMAL);
+ window->SetType(aura::client::WINDOW_TYPE_NORMAL);
+ window->Init(ui::LAYER_TEXTURED);
+ return window;
+ }
+
+ aura::Window* CreateTestWindow() {
+ aura::Window* window = new aura::Window(NULL);
+ window->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_NORMAL);
+ window->SetType(aura::client::WINDOW_TYPE_NORMAL);
+ window->Init(ui::LAYER_TEXTURED);
+ SetDefaultParentByPrimaryRootWindow(window);
+ return window;
+ }
+
+ aura::Window* CreateAppTestWindow(aura::Window* parent) {
+ aura::Window* window = new aura::Window(NULL);
+ window->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_NORMAL);
+ window->SetType(aura::client::WINDOW_TYPE_POPUP);
+ window->Init(ui::LAYER_TEXTURED);
+ if (!parent)
+ SetDefaultParentByPrimaryRootWindow(window);
+ else
+ parent->AddChild(window);
+ return window;
+ }
+
+ aura::Window* GetDesktop() {
+ return Shell::GetContainer(Shell::GetPrimaryRootWindow(),
+ kShellWindowId_DefaultContainer);
+ }
+
+ gfx::Rect GetFullscreenBounds(aura::Window* window) {
+ return Shell::GetScreen()->GetDisplayNearestWindow(window).bounds();
+ }
+
+ ShelfWidget* shelf_widget() {
+ return Shell::GetPrimaryRootWindowController()->shelf();
+ }
+
+ ShelfLayoutManager* shelf_layout_manager() {
+ return Shell::GetPrimaryRootWindowController()->GetShelfLayoutManager();
+ }
+
+ bool GetWindowOverlapsShelf() {
+ return shelf_layout_manager()->window_overlaps_shelf();
+ }
+
+ private:
+ scoped_ptr<ActivationController> activation_controller_;
+
+ DISALLOW_COPY_AND_ASSIGN(WorkspaceControllerTest);
+};
+
+// Assertions around adding a normal window.
+TEST_F(WorkspaceControllerTest, AddNormalWindowWhenEmpty) {
+ scoped_ptr<Window> w1(CreateTestWindow());
+ w1->SetBounds(gfx::Rect(0, 0, 250, 251));
+
+ EXPECT_TRUE(GetRestoreBoundsInScreen(w1.get()) == NULL);
+
+ w1->Show();
+
+ EXPECT_TRUE(GetRestoreBoundsInScreen(w1.get()) == NULL);
+
+ ASSERT_TRUE(w1->layer() != NULL);
+ EXPECT_TRUE(w1->layer()->visible());
+
+ EXPECT_EQ("0,0 250x251", w1->bounds().ToString());
+
+ EXPECT_EQ(w1.get(), GetDesktop()->children()[0]);
+}
+
+// Assertions around maximizing/unmaximizing.
+TEST_F(WorkspaceControllerTest, SingleMaximizeWindow) {
+ scoped_ptr<Window> w1(CreateTestWindow());
+ w1->SetBounds(gfx::Rect(0, 0, 250, 251));
+
+ w1->Show();
+ wm::ActivateWindow(w1.get());
+
+ EXPECT_TRUE(wm::IsActiveWindow(w1.get()));
+
+ ASSERT_TRUE(w1->layer() != NULL);
+ EXPECT_TRUE(w1->layer()->visible());
+
+ EXPECT_EQ("0,0 250x251", w1->bounds().ToString());
+
+ // Maximize the window.
+ w1->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MAXIMIZED);
+
+ EXPECT_TRUE(wm::IsActiveWindow(w1.get()));
+
+ EXPECT_EQ(w1.get(), GetDesktop()->children()[0]);
+ EXPECT_EQ(ScreenAsh::GetMaximizedWindowBoundsInParent(w1.get()).width(),
+ w1->bounds().width());
+ EXPECT_EQ(ScreenAsh::GetMaximizedWindowBoundsInParent(w1.get()).height(),
+ w1->bounds().height());
+
+ // Restore the window.
+ w1->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_NORMAL);
+
+ EXPECT_EQ(w1.get(), GetDesktop()->children()[0]);
+ EXPECT_EQ("0,0 250x251", w1->bounds().ToString());
+}
+
+// Assertions around two windows and toggling one to be fullscreen.
+TEST_F(WorkspaceControllerTest, FullscreenWithNormalWindow) {
+ scoped_ptr<Window> w1(CreateTestWindow());
+ scoped_ptr<Window> w2(CreateTestWindow());
+ w1->SetBounds(gfx::Rect(0, 0, 250, 251));
+ w1->Show();
+
+ ASSERT_TRUE(w1->layer() != NULL);
+ EXPECT_TRUE(w1->layer()->visible());
+
+ w2->SetBounds(gfx::Rect(0, 0, 50, 51));
+ w2->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_FULLSCREEN);
+ w2->Show();
+ wm::ActivateWindow(w2.get());
+
+ // Both windows should be in the same workspace.
+ EXPECT_EQ(w1.get(), GetDesktop()->children()[0]);
+ EXPECT_EQ(w2.get(), GetDesktop()->children()[1]);
+
+ gfx::Rect work_area(
+ ScreenAsh::GetMaximizedWindowBoundsInParent(w1.get()));
+ EXPECT_EQ(work_area.width(), w2->bounds().width());
+ EXPECT_EQ(work_area.height(), w2->bounds().height());
+
+ // Restore w2, which should then go back to one workspace.
+ w2->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_NORMAL);
+ EXPECT_EQ(50, w2->bounds().width());
+ EXPECT_EQ(51, w2->bounds().height());
+ EXPECT_TRUE(wm::IsActiveWindow(w2.get()));
+}
+
+// Makes sure requests to change the bounds of a normal window go through.
+TEST_F(WorkspaceControllerTest, ChangeBoundsOfNormalWindow) {
+ scoped_ptr<Window> w1(CreateTestWindow());
+ w1->Show();
+
+ // Setting the bounds should go through since the window is in the normal
+ // workspace.
+ w1->SetBounds(gfx::Rect(0, 0, 200, 500));
+ EXPECT_EQ(200, w1->bounds().width());
+ EXPECT_EQ(500, w1->bounds().height());
+}
+
+// Verifies the bounds is not altered when showing and grid is enabled.
+TEST_F(WorkspaceControllerTest, SnapToGrid) {
+ scoped_ptr<Window> w1(CreateTestWindowUnparented());
+ w1->SetBounds(gfx::Rect(1, 6, 25, 30));
+ SetDefaultParentByPrimaryRootWindow(w1.get());
+ // We are not aligning this anymore this way. When the window gets shown
+ // the window is expected to be handled differently, but this cannot be
+ // tested with this test. So the result of this test should be that the
+ // bounds are exactly as passed in.
+ EXPECT_EQ("1,6 25x30", w1->bounds().ToString());
+}
+
+// Assertions around a fullscreen window.
+TEST_F(WorkspaceControllerTest, SingleFullscreenWindow) {
+ scoped_ptr<Window> w1(CreateTestWindow());
+ w1->SetBounds(gfx::Rect(0, 0, 250, 251));
+ // Make the window fullscreen.
+ w1->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_FULLSCREEN);
+ w1->Show();
+ wm::ActivateWindow(w1.get());
+
+ EXPECT_EQ(w1.get(), GetDesktop()->children()[0]);
+ EXPECT_EQ(GetFullscreenBounds(w1.get()).width(), w1->bounds().width());
+ EXPECT_EQ(GetFullscreenBounds(w1.get()).height(), w1->bounds().height());
+
+ // Restore the window. Use SHOW_STATE_DEFAULT as that is what we'll end up
+ // with when using views::Widget.
+ w1->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_DEFAULT);
+ EXPECT_EQ("0,0 250x251", w1->bounds().ToString());
+
+ EXPECT_EQ(w1.get(), GetDesktop()->children()[0]);
+ EXPECT_EQ(250, w1->bounds().width());
+ EXPECT_EQ(251, w1->bounds().height());
+
+ // Back to fullscreen.
+ w1->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_FULLSCREEN);
+ EXPECT_EQ(w1.get(), GetDesktop()->children()[0]);
+ EXPECT_EQ(GetFullscreenBounds(w1.get()).width(), w1->bounds().width());
+ EXPECT_EQ(GetFullscreenBounds(w1.get()).height(), w1->bounds().height());
+ ASSERT_TRUE(GetRestoreBoundsInScreen(w1.get()));
+ EXPECT_EQ("0,0 250x251", GetRestoreBoundsInScreen(w1.get())->ToString());
+}
+
+// Assertions around minimizing a single window.
+TEST_F(WorkspaceControllerTest, MinimizeSingleWindow) {
+ scoped_ptr<Window> w1(CreateTestWindow());
+
+ w1->Show();
+
+ w1->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MINIMIZED);
+ EXPECT_FALSE(w1->layer()->IsDrawn());
+
+ // Show the window.
+ w1->Show();
+ EXPECT_TRUE(wm::IsWindowNormal(w1.get()));
+ EXPECT_TRUE(w1->layer()->IsDrawn());
+}
+
+// Assertions around minimizing a fullscreen window.
+TEST_F(WorkspaceControllerTest, MinimizeFullscreenWindow) {
+ // Two windows, w1 normal, w2 fullscreen.
+ scoped_ptr<Window> w1(CreateTestWindow());
+ scoped_ptr<Window> w2(CreateTestWindow());
+ w1->Show();
+ w2->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_FULLSCREEN);
+ w2->Show();
+ wm::ActivateWindow(w2.get());
+
+ // Minimize w2.
+ w2->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MINIMIZED);
+ EXPECT_TRUE(w1->layer()->IsDrawn());
+ EXPECT_FALSE(w2->layer()->IsDrawn());
+
+ // Show the window, which should trigger unminimizing.
+ w2->Show();
+ wm::ActivateWindow(w2.get());
+
+ EXPECT_TRUE(wm::IsWindowFullscreen(w2.get()));
+ EXPECT_TRUE(w1->layer()->IsDrawn());
+ EXPECT_TRUE(w2->layer()->IsDrawn());
+
+ // Minimize the window, which should hide the window.
+ EXPECT_TRUE(wm::IsActiveWindow(w2.get()));
+ w2->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MINIMIZED);
+ EXPECT_FALSE(wm::IsActiveWindow(w2.get()));
+ EXPECT_FALSE(w2->layer()->IsDrawn());
+ EXPECT_TRUE(wm::IsActiveWindow(w1.get()));
+
+ // Make the window normal.
+ w2->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_NORMAL);
+ EXPECT_EQ(w1.get(), GetDesktop()->children()[0]);
+ EXPECT_EQ(w2.get(), GetDesktop()->children()[1]);
+ EXPECT_TRUE(w2->layer()->IsDrawn());
+}
+
+// Verifies ShelfLayoutManager's visibility/auto-hide state is correctly
+// updated.
+TEST_F(WorkspaceControllerTest, ShelfStateUpdated) {
+ // Since ShelfLayoutManager queries for mouse location, move the mouse so
+ // it isn't over the shelf.
+ aura::test::EventGenerator generator(
+ Shell::GetPrimaryRootWindow(), gfx::Point());
+ generator.MoveMouseTo(0, 0);
+
+ scoped_ptr<Window> w1(CreateTestWindow());
+ const gfx::Rect w1_bounds(0, 1, 101, 102);
+ ShelfLayoutManager* shelf = shelf_layout_manager();
+ shelf->SetAutoHideBehavior(ash::SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS);
+ const gfx::Rect touches_shelf_bounds(
+ 0, shelf->GetIdealBounds().y() - 10, 101, 102);
+ // Move |w1| to overlap the shelf.
+ w1->SetBounds(touches_shelf_bounds);
+ EXPECT_FALSE(GetWindowOverlapsShelf());
+
+ // A visible ignored window should not trigger the overlap.
+ scoped_ptr<Window> w_ignored(CreateTestWindow());
+ w_ignored->SetBounds(touches_shelf_bounds);
+ SetIgnoredByShelf(&(*w_ignored), true);
+ w_ignored->Show();
+ EXPECT_FALSE(GetWindowOverlapsShelf());
+
+ // Make it visible, since visible shelf overlaps should be true.
+ w1->Show();
+ EXPECT_TRUE(GetWindowOverlapsShelf());
+
+ wm::ActivateWindow(w1.get());
+ w1->SetBounds(w1_bounds);
+ w1->Show();
+ wm::ActivateWindow(w1.get());
+
+ EXPECT_EQ(SHELF_AUTO_HIDE, shelf->visibility_state());
+
+ // Maximize the window.
+ w1->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MAXIMIZED);
+ EXPECT_EQ(SHELF_AUTO_HIDE, shelf->visibility_state());
+ EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->auto_hide_state());
+
+ // Restore.
+ w1->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_NORMAL);
+ EXPECT_EQ(SHELF_AUTO_HIDE, shelf->visibility_state());
+ EXPECT_EQ("0,1 101x102", w1->bounds().ToString());
+
+ // Fullscreen.
+ w1->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_FULLSCREEN);
+ EXPECT_EQ(SHELF_HIDDEN, shelf->visibility_state());
+
+ // Normal.
+ w1->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_NORMAL);
+ EXPECT_EQ(SHELF_AUTO_HIDE, shelf->visibility_state());
+ EXPECT_EQ("0,1 101x102", w1->bounds().ToString());
+ EXPECT_FALSE(GetWindowOverlapsShelf());
+
+ // Move window so it obscures shelf.
+ w1->SetBounds(touches_shelf_bounds);
+ EXPECT_TRUE(GetWindowOverlapsShelf());
+
+ // Move it back.
+ w1->SetBounds(w1_bounds);
+ EXPECT_FALSE(GetWindowOverlapsShelf());
+
+ // Maximize again.
+ w1->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MAXIMIZED);
+ EXPECT_EQ(SHELF_AUTO_HIDE, shelf->visibility_state());
+ EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->auto_hide_state());
+
+ // Minimize.
+ w1->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MINIMIZED);
+ EXPECT_EQ(SHELF_AUTO_HIDE, shelf->visibility_state());
+
+ // Since the restore from minimize will restore to the pre-minimize
+ // state (tested elsewhere), we abandon the current size and restore
+ // rect and set them to the window.
+ gfx::Rect restore = *GetRestoreBoundsInScreen(w1.get());
+ EXPECT_EQ("0,0 800x597", w1->bounds().ToString());
+ EXPECT_EQ("0,1 101x102", restore.ToString());
+ ClearRestoreBounds(w1.get());
+ w1->SetBounds(restore);
+
+ // Restore.
+ w1->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_NORMAL);
+ EXPECT_EQ(SHELF_AUTO_HIDE, shelf->visibility_state());
+ EXPECT_EQ("0,1 101x102", w1->bounds().ToString());
+
+ // Create another window, maximized.
+ scoped_ptr<Window> w2(CreateTestWindow());
+ w2->SetBounds(gfx::Rect(10, 11, 250, 251));
+ w2->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MAXIMIZED);
+ w2->Show();
+ wm::ActivateWindow(w2.get());
+ EXPECT_EQ(SHELF_AUTO_HIDE, shelf->visibility_state());
+ EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->auto_hide_state());
+ EXPECT_EQ("0,1 101x102", w1->bounds().ToString());
+
+ // Switch to w1.
+ wm::ActivateWindow(w1.get());
+ EXPECT_EQ(SHELF_AUTO_HIDE, shelf->visibility_state());
+ EXPECT_EQ("0,1 101x102", w1->bounds().ToString());
+ EXPECT_EQ(ScreenAsh::GetMaximizedWindowBoundsInParent(
+ w2->parent()).ToString(),
+ w2->bounds().ToString());
+
+ // Switch to w2.
+ wm::ActivateWindow(w2.get());
+ EXPECT_EQ(SHELF_AUTO_HIDE, shelf->visibility_state());
+ EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->auto_hide_state());
+ EXPECT_EQ("0,1 101x102", w1->bounds().ToString());
+ EXPECT_EQ(ScreenAsh::GetMaximizedWindowBoundsInParent(w2.get()).ToString(),
+ w2->bounds().ToString());
+
+ // Turn off auto-hide, switch back to w2 (maximized) and verify overlap.
+ shelf->SetAutoHideBehavior(SHELF_AUTO_HIDE_BEHAVIOR_NEVER);
+ wm::ActivateWindow(w2.get());
+ EXPECT_FALSE(GetWindowOverlapsShelf());
+
+ // Move w1 to overlap shelf, it shouldn't change window overlaps shelf since
+ // the window isn't in the visible workspace.
+ w1->SetBounds(touches_shelf_bounds);
+ EXPECT_FALSE(GetWindowOverlapsShelf());
+
+ // Activate w1. Although w1 is visible, the overlap state is still false since
+ // w2 is maximized.
+ wm::ActivateWindow(w1.get());
+ EXPECT_FALSE(GetWindowOverlapsShelf());
+
+ // Restore w2.
+ w2->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_NORMAL);
+ EXPECT_TRUE(GetWindowOverlapsShelf());
+}
+
+// Verifies going from maximized to minimized sets the right state for painting
+// the background of the launcher.
+TEST_F(WorkspaceControllerTest, MinimizeResetsVisibility) {
+ scoped_ptr<Window> w1(CreateTestWindow());
+ w1->Show();
+ wm::ActivateWindow(w1.get());
+ w1->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MAXIMIZED);
+ EXPECT_EQ(SHELF_BACKGROUND_MAXIMIZED, shelf_widget()->GetBackgroundType());
+
+ w1->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MINIMIZED);
+ EXPECT_EQ(SHELF_VISIBLE,
+ shelf_layout_manager()->visibility_state());
+ EXPECT_EQ(SHELF_BACKGROUND_DEFAULT, shelf_widget()->GetBackgroundType());
+}
+
+// Verifies window visibility during various workspace changes.
+TEST_F(WorkspaceControllerTest, VisibilityTests) {
+ scoped_ptr<Window> w1(CreateTestWindow());
+ w1->Show();
+ EXPECT_TRUE(w1->IsVisible());
+ EXPECT_EQ(1.0f, w1->layer()->GetCombinedOpacity());
+
+ // Create another window, activate it and make it fullscreen.
+ scoped_ptr<Window> w2(CreateTestWindow());
+ w2->Show();
+ wm::ActivateWindow(w2.get());
+ w2->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_FULLSCREEN);
+ EXPECT_TRUE(w2->IsVisible());
+ EXPECT_EQ(1.0f, w2->layer()->GetCombinedOpacity());
+ EXPECT_TRUE(w1->IsVisible());
+
+ // Switch to w1. |w1| should be visible on top of |w2|.
+ wm::ActivateWindow(w1.get());
+ EXPECT_TRUE(w1->IsVisible());
+ EXPECT_EQ(1.0f, w1->layer()->GetCombinedOpacity());
+ EXPECT_TRUE(w2->IsVisible());
+
+ // Switch back to |w2|.
+ wm::ActivateWindow(w2.get());
+ EXPECT_TRUE(w2->IsVisible());
+ EXPECT_EQ(1.0f, w2->layer()->GetCombinedOpacity());
+ EXPECT_TRUE(w1->IsVisible());
+
+ // Restore |w2|, both windows should be visible.
+ w2->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_NORMAL);
+ EXPECT_TRUE(w1->IsVisible());
+ EXPECT_EQ(1.0f, w1->layer()->GetCombinedOpacity());
+ EXPECT_TRUE(w2->IsVisible());
+ EXPECT_EQ(1.0f, w2->layer()->GetCombinedOpacity());
+
+ // Make |w2| fullscreen again, then close it.
+ w2->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_FULLSCREEN);
+ w2->Hide();
+ EXPECT_FALSE(w2->IsVisible());
+ EXPECT_EQ(1.0f, w1->layer()->GetCombinedOpacity());
+ EXPECT_TRUE(w1->IsVisible());
+
+ // Create |w2| and maximize it.
+ w2.reset(CreateTestWindow());
+ w2->Show();
+ wm::ActivateWindow(w2.get());
+ w2->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MAXIMIZED);
+ EXPECT_TRUE(w2->IsVisible());
+ EXPECT_EQ(1.0f, w2->layer()->GetCombinedOpacity());
+ EXPECT_TRUE(w1->IsVisible());
+
+ // Close |w2|.
+ w2.reset();
+ EXPECT_EQ(1.0f, w1->layer()->GetCombinedOpacity());
+ EXPECT_TRUE(w1->IsVisible());
+}
+
+// Verifies windows that are offscreen don't move when switching workspaces.
+TEST_F(WorkspaceControllerTest, DontMoveOnSwitch) {
+ aura::test::EventGenerator generator(
+ Shell::GetPrimaryRootWindow(), gfx::Point());
+ generator.MoveMouseTo(0, 0);
+
+ scoped_ptr<Window> w1(CreateTestWindow());
+ ShelfLayoutManager* shelf = shelf_layout_manager();
+ const gfx::Rect touches_shelf_bounds(
+ 0, shelf->GetIdealBounds().y() - 10, 101, 102);
+ // Move |w1| to overlap the shelf.
+ w1->SetBounds(touches_shelf_bounds);
+ w1->Show();
+ wm::ActivateWindow(w1.get());
+
+ // Create another window and maximize it.
+ scoped_ptr<Window> w2(CreateTestWindow());
+ w2->SetBounds(gfx::Rect(10, 11, 250, 251));
+ w2->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MAXIMIZED);
+ w2->Show();
+ wm::ActivateWindow(w2.get());
+
+ // Switch to w1.
+ wm::ActivateWindow(w1.get());
+ EXPECT_EQ(touches_shelf_bounds.ToString(), w1->bounds().ToString());
+}
+
+// Verifies that windows that are completely offscreen move when switching
+// workspaces.
+TEST_F(WorkspaceControllerTest, MoveOnSwitch) {
+ aura::test::EventGenerator generator(
+ Shell::GetPrimaryRootWindow(), gfx::Point());
+ generator.MoveMouseTo(0, 0);
+
+ scoped_ptr<Window> w1(CreateTestWindow());
+ ShelfLayoutManager* shelf = shelf_layout_manager();
+ const gfx::Rect w1_bounds(0, shelf->GetIdealBounds().y(), 100, 200);
+ // Move |w1| so that the top edge is the same as the top edge of the shelf.
+ w1->SetBounds(w1_bounds);
+ w1->Show();
+ wm::ActivateWindow(w1.get());
+ EXPECT_EQ(w1_bounds.ToString(), w1->bounds().ToString());
+
+ // Create another window and maximize it.
+ scoped_ptr<Window> w2(CreateTestWindow());
+ w2->SetBounds(gfx::Rect(10, 11, 250, 251));
+ w2->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MAXIMIZED);
+ w2->Show();
+ wm::ActivateWindow(w2.get());
+
+ // Increase the size of the WorkAreaInsets. This would make |w1| fall
+ // completely out of the display work area.
+ gfx::Insets insets =
+ Shell::GetScreen()->GetPrimaryDisplay().GetWorkAreaInsets();
+ insets.Set(0, 0, insets.bottom() + 30, 0);
+ Shell::GetInstance()->SetDisplayWorkAreaInsets(w1.get(), insets);
+
+ // Switch to w1. The window should have moved.
+ wm::ActivateWindow(w1.get());
+ EXPECT_NE(w1_bounds.ToString(), w1->bounds().ToString());
+}
+
+namespace {
+
+// WindowDelegate used by DontCrashOnChangeAndActivate.
+class DontCrashOnChangeAndActivateDelegate
+ : public aura::test::TestWindowDelegate {
+ public:
+ DontCrashOnChangeAndActivateDelegate() : window_(NULL) {}
+
+ void set_window(aura::Window* window) { window_ = window; }
+
+ // WindowDelegate overrides:
+ virtual void OnBoundsChanged(const gfx::Rect& old_bounds,
+ const gfx::Rect& new_bounds) OVERRIDE {
+ if (window_) {
+ wm::ActivateWindow(window_);
+ window_ = NULL;
+ }
+ }
+
+ private:
+ aura::Window* window_;
+
+ DISALLOW_COPY_AND_ASSIGN(DontCrashOnChangeAndActivateDelegate);
+};
+
+} // namespace
+
+// Exercises possible crash in W2. Here's the sequence:
+// . minimize a maximized window.
+// . remove the window (which happens when switching displays).
+// . add the window back.
+// . show the window and during the bounds change activate it.
+TEST_F(WorkspaceControllerTest, DontCrashOnChangeAndActivate) {
+ // Force the shelf
+ ShelfLayoutManager* shelf = shelf_layout_manager();
+ shelf->SetAutoHideBehavior(SHELF_AUTO_HIDE_BEHAVIOR_NEVER);
+
+ DontCrashOnChangeAndActivateDelegate delegate;
+ scoped_ptr<Window> w1(CreateTestWindowInShellWithDelegate(
+ &delegate, 1000, gfx::Rect(10, 11, 250, 251)));
+
+ w1->Show();
+ wm::ActivateWindow(w1.get());
+ wm::MaximizeWindow(w1.get());
+ wm::MinimizeWindow(w1.get());
+
+ w1->parent()->RemoveChild(w1.get());
+
+ // Do this so that when we Show() the window a resize occurs and we make the
+ // window active.
+ shelf->SetAutoHideBehavior(SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS);
+
+ SetDefaultParentByPrimaryRootWindow(w1.get());
+ delegate.set_window(w1.get());
+ w1->Show();
+}
+
+// Verifies a window with a transient parent not managed by workspace works.
+TEST_F(WorkspaceControllerTest, TransientParent) {
+ // Normal window with no transient parent.
+ scoped_ptr<Window> w2(CreateTestWindow());
+ w2->SetBounds(gfx::Rect(10, 11, 250, 251));
+ w2->Show();
+ wm::ActivateWindow(w2.get());
+
+ // Window with a transient parent. We set the transient parent to the root,
+ // which would never happen but is enough to exercise the bug.
+ scoped_ptr<Window> w1(CreateTestWindowUnparented());
+ Shell::GetInstance()->GetPrimaryRootWindow()->AddTransientChild(w1.get());
+ w1->SetBounds(gfx::Rect(10, 11, 250, 251));
+ SetDefaultParentByPrimaryRootWindow(w1.get());
+ w1->Show();
+ wm::ActivateWindow(w1.get());
+
+ // The window with the transient parent should get added to the same parent as
+ // the normal window.
+ EXPECT_EQ(w2->parent(), w1->parent());
+}
+
+// Verifies changing TrackedByWorkspace works.
+TEST_F(WorkspaceControllerTest, TrackedByWorkspace) {
+ // Create a fullscreen window.
+ scoped_ptr<Window> w1(CreateTestWindow());
+ w1->Show();
+ wm::ActivateWindow(w1.get());
+ w1->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_FULLSCREEN);
+ EXPECT_TRUE(wm::IsActiveWindow(w1.get()));
+ EXPECT_TRUE(w1->IsVisible());
+
+ // Create a second fullscreen window and mark it not tracked by workspace
+ // manager.
+ scoped_ptr<Window> w2(CreateTestWindowUnparented());
+ w2->SetBounds(gfx::Rect(1, 6, 25, 30));
+ w2->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_FULLSCREEN);
+ SetDefaultParentByPrimaryRootWindow(w2.get());
+ w2->Show();
+ SetTrackedByWorkspace(w2.get(), false);
+ wm::ActivateWindow(w2.get());
+
+ // Activating |w2| should force it to have the same parent as |w1|.
+ EXPECT_EQ(w1->parent(), w2->parent());
+ EXPECT_TRUE(wm::IsActiveWindow(w2.get()));
+ EXPECT_TRUE(w1->IsVisible());
+ EXPECT_TRUE(w2->IsVisible());
+
+ // Because |w2| isn't tracked we should be able to set the bounds of it.
+ gfx::Rect bounds(w2->bounds());
+ bounds.Offset(4, 5);
+ w2->SetBounds(bounds);
+ EXPECT_EQ(bounds.ToString(), w2->bounds().ToString());
+
+ // Transition it to tracked by worskpace. It should end up in the desktop
+ // workspace.
+ SetTrackedByWorkspace(w2.get(), true);
+ EXPECT_TRUE(wm::IsActiveWindow(w2.get()));
+ EXPECT_TRUE(w1->IsVisible());
+ EXPECT_TRUE(w2->IsVisible());
+ EXPECT_EQ(w1->parent(), w2->parent());
+}
+
+// Test the basic auto placement of one and or two windows in a "simulated
+// session" of sequential window operations.
+TEST_F(WorkspaceControllerTest, BasicAutoPlacing) {
+ // Test 1: In case there is no manageable window, no window should shift.
+
+ scoped_ptr<aura::Window> window1(CreateTestWindowInShellWithId(0));
+ window1->SetBounds(gfx::Rect(16, 32, 640, 320));
+ gfx::Rect desktop_area = window1->parent()->bounds();
+
+ scoped_ptr<aura::Window> window2(CreateTestWindowInShellWithId(1));
+ // Trigger the auto window placement function by making it visible.
+ // Note that the bounds are getting changed while it is invisible.
+ window2->Hide();
+ window2->SetBounds(gfx::Rect(32, 48, 256, 512));
+ window2->Show();
+
+ // Check the initial position of the windows is unchanged.
+ EXPECT_EQ("16,32 640x320", window1->bounds().ToString());
+ EXPECT_EQ("32,48 256x512", window2->bounds().ToString());
+
+ // Remove the second window and make sure that the first window
+ // does NOT get centered.
+ window2.reset();
+ EXPECT_EQ("16,32 640x320", window1->bounds().ToString());
+
+ // Test 2: Set up two managed windows and check their auto positioning.
+ ash::wm::SetWindowPositionManaged(window1.get(), true);
+ scoped_ptr<aura::Window> window3(CreateTestWindowInShellWithId(2));
+ ash::wm::SetWindowPositionManaged(window3.get(), true);
+ // To avoid any auto window manager changes due to SetBounds, the window
+ // gets first hidden and then shown again.
+ window3->Hide();
+ window3->SetBounds(gfx::Rect(32, 48, 256, 512));
+ window3->Show();
+ // |window1| should be flush right and |window3| flush left.
+ EXPECT_EQ("0,32 640x320", window1->bounds().ToString());
+ EXPECT_EQ(base::IntToString(
+ desktop_area.width() - window3->bounds().width()) +
+ ",48 256x512", window3->bounds().ToString());
+
+ // After removing |window3|, |window1| should be centered again.
+ window3.reset();
+ EXPECT_EQ(
+ base::IntToString(
+ (desktop_area.width() - window1->bounds().width()) / 2) +
+ ",32 640x320", window1->bounds().ToString());
+
+ // Test 3: Set up a manageable and a non manageable window and check
+ // positioning.
+ scoped_ptr<aura::Window> window4(CreateTestWindowInShellWithId(3));
+ // To avoid any auto window manager changes due to SetBounds, the window
+ // gets first hidden and then shown again.
+ window1->Hide();
+ window1->SetBounds(gfx::Rect(16, 32, 640, 320));
+ window4->SetBounds(gfx::Rect(32, 48, 256, 512));
+ window1->Show();
+ // |window1| should be centered and |window4| untouched.
+ EXPECT_EQ(
+ base::IntToString(
+ (desktop_area.width() - window1->bounds().width()) / 2) +
+ ",32 640x320", window1->bounds().ToString());
+ EXPECT_EQ("32,48 256x512", window4->bounds().ToString());
+
+ // Test4: A single manageable window should get centered.
+ window4.reset();
+ ash::wm::SetUserHasChangedWindowPositionOrSize(window1.get(), false);
+ // Trigger the auto window placement function by showing (and hiding) it.
+ window1->Hide();
+ window1->Show();
+ // |window1| should be centered.
+ EXPECT_EQ(
+ base::IntToString(
+ (desktop_area.width() - window1->bounds().width()) / 2) +
+ ",32 640x320", window1->bounds().ToString());
+}
+
+// Test the proper usage of user window movement interaction.
+TEST_F(WorkspaceControllerTest, TestUserMovedWindowRepositioning) {
+ scoped_ptr<aura::Window> window1(CreateTestWindowInShellWithId(0));
+ window1->SetBounds(gfx::Rect(16, 32, 640, 320));
+ gfx::Rect desktop_area = window1->parent()->bounds();
+ scoped_ptr<aura::Window> window2(CreateTestWindowInShellWithId(1));
+ window2->SetBounds(gfx::Rect(32, 48, 256, 512));
+ window1->Hide();
+ window2->Hide();
+ ash::wm::SetWindowPositionManaged(window1.get(), true);
+ ash::wm::SetWindowPositionManaged(window2.get(), true);
+ EXPECT_FALSE(ash::wm::HasUserChangedWindowPositionOrSize(window1.get()));
+ EXPECT_FALSE(ash::wm::HasUserChangedWindowPositionOrSize(window2.get()));
+
+ // Check that the current location gets preserved if the user has
+ // positioned it previously.
+ ash::wm::SetUserHasChangedWindowPositionOrSize(window1.get(), true);
+ window1->Show();
+ EXPECT_EQ("16,32 640x320", window1->bounds().ToString());
+ // Flag should be still set.
+ EXPECT_TRUE(ash::wm::HasUserChangedWindowPositionOrSize(window1.get()));
+ EXPECT_FALSE(ash::wm::HasUserChangedWindowPositionOrSize(window2.get()));
+
+ // Turn on the second window and make sure that both windows are now
+ // positionable again (user movement cleared).
+ window2->Show();
+
+ // |window1| should be flush left and |window3| flush right.
+ EXPECT_EQ("0,32 640x320", window1->bounds().ToString());
+ EXPECT_EQ(
+ base::IntToString(desktop_area.width() - window2->bounds().width()) +
+ ",48 256x512", window2->bounds().ToString());
+ // FLag should now be reset.
+ EXPECT_FALSE(ash::wm::HasUserChangedWindowPositionOrSize(window1.get()));
+ EXPECT_FALSE(ash::wm::HasUserChangedWindowPositionOrSize(window1.get()));
+
+ // Going back to one shown window should keep the state.
+ ash::wm::SetUserHasChangedWindowPositionOrSize(window1.get(), true);
+ window2->Hide();
+ EXPECT_EQ("0,32 640x320", window1->bounds().ToString());
+ EXPECT_TRUE(ash::wm::HasUserChangedWindowPositionOrSize(window1.get()));
+}
+
+// Test that user placed windows go back to their user placement after the user
+// closes all other windows.
+TEST_F(WorkspaceControllerTest, TestUserHandledWindowRestore) {
+ scoped_ptr<aura::Window> window1(CreateTestWindowInShellWithId(0));
+ gfx::Rect user_pos = gfx::Rect(16, 42, 640, 320);
+ window1->SetBounds(user_pos);
+ ash::wm::SetPreAutoManageWindowBounds(window1.get(), user_pos);
+ gfx::Rect desktop_area = window1->parent()->bounds();
+
+ // Create a second window to let the auto manager kick in.
+ scoped_ptr<aura::Window> window2(CreateTestWindowInShellWithId(1));
+ window2->SetBounds(gfx::Rect(32, 48, 256, 512));
+ window1->Hide();
+ window2->Hide();
+ ash::wm::SetWindowPositionManaged(window1.get(), true);
+ ash::wm::SetWindowPositionManaged(window2.get(), true);
+ window1->Show();
+ EXPECT_EQ(user_pos.ToString(), window1->bounds().ToString());
+ window2->Show();
+
+ // |window1| should be flush left and |window2| flush right.
+ EXPECT_EQ("0," + base::IntToString(user_pos.y()) +
+ " 640x320", window1->bounds().ToString());
+ EXPECT_EQ(
+ base::IntToString(desktop_area.width() - window2->bounds().width()) +
+ ",48 256x512", window2->bounds().ToString());
+ window2->Hide();
+
+ // After the other window get hidden the window has to move back to the
+ // previous position and the bounds should still be set and unchanged.
+ EXPECT_EQ(user_pos.ToString(), window1->bounds().ToString());
+ ASSERT_TRUE(ash::wm::GetPreAutoManageWindowBounds(window1.get()));
+ EXPECT_EQ(user_pos.ToString(),
+ ash::wm::GetPreAutoManageWindowBounds(window1.get())->ToString());
+}
+
+// Test that a window from normal to minimize will repos the remaining.
+TEST_F(WorkspaceControllerTest, ToMinimizeRepositionsRemaining) {
+ scoped_ptr<aura::Window> window1(CreateTestWindowInShellWithId(0));
+ ash::wm::SetWindowPositionManaged(window1.get(), true);
+ window1->SetBounds(gfx::Rect(16, 32, 640, 320));
+ gfx::Rect desktop_area = window1->parent()->bounds();
+
+ scoped_ptr<aura::Window> window2(CreateTestWindowInShellWithId(1));
+ ash::wm::SetWindowPositionManaged(window2.get(), true);
+ window2->SetBounds(gfx::Rect(32, 48, 256, 512));
+
+ ash::wm::MinimizeWindow(window1.get());
+
+ // |window2| should be centered now.
+ EXPECT_TRUE(window2->IsVisible());
+ EXPECT_TRUE(ash::wm::IsWindowNormal(window2.get()));
+ EXPECT_EQ(base::IntToString(
+ (desktop_area.width() - window2->bounds().width()) / 2) +
+ ",48 256x512", window2->bounds().ToString());
+
+ ash::wm::RestoreWindow(window1.get());
+ // |window1| should be flush right and |window3| flush left.
+ EXPECT_EQ(base::IntToString(
+ desktop_area.width() - window1->bounds().width()) +
+ ",32 640x320", window1->bounds().ToString());
+ EXPECT_EQ("0,48 256x512", window2->bounds().ToString());
+}
+
+// Test that minimizing an initially maximized window will repos the remaining.
+TEST_F(WorkspaceControllerTest, MaxToMinRepositionsRemaining) {
+ scoped_ptr<aura::Window> window1(CreateTestWindowInShellWithId(0));
+ ash::wm::SetWindowPositionManaged(window1.get(), true);
+ gfx::Rect desktop_area = window1->parent()->bounds();
+
+ scoped_ptr<aura::Window> window2(CreateTestWindowInShellWithId(1));
+ ash::wm::SetWindowPositionManaged(window2.get(), true);
+ window2->SetBounds(gfx::Rect(32, 48, 256, 512));
+
+ ash::wm::MaximizeWindow(window1.get());
+ ash::wm::MinimizeWindow(window1.get());
+
+ // |window2| should be centered now.
+ EXPECT_TRUE(window2->IsVisible());
+ EXPECT_TRUE(ash::wm::IsWindowNormal(window2.get()));
+ EXPECT_EQ(base::IntToString(
+ (desktop_area.width() - window2->bounds().width()) / 2) +
+ ",48 256x512", window2->bounds().ToString());
+}
+
+// Test that nomral, maximize, minimizing will repos the remaining.
+TEST_F(WorkspaceControllerTest, NormToMaxToMinRepositionsRemaining) {
+ scoped_ptr<aura::Window> window1(CreateTestWindowInShellWithId(0));
+ window1->SetBounds(gfx::Rect(16, 32, 640, 320));
+ ash::wm::SetWindowPositionManaged(window1.get(), true);
+ gfx::Rect desktop_area = window1->parent()->bounds();
+
+ scoped_ptr<aura::Window> window2(CreateTestWindowInShellWithId(1));
+ ash::wm::SetWindowPositionManaged(window2.get(), true);
+ window2->SetBounds(gfx::Rect(32, 40, 256, 512));
+
+ // Trigger the auto window placement function by showing (and hiding) it.
+ window1->Hide();
+ window1->Show();
+
+ // |window1| should be flush right and |window3| flush left.
+ EXPECT_EQ(base::IntToString(
+ desktop_area.width() - window1->bounds().width()) +
+ ",32 640x320", window1->bounds().ToString());
+ EXPECT_EQ("0,40 256x512", window2->bounds().ToString());
+
+ ash::wm::MaximizeWindow(window1.get());
+ ash::wm::MinimizeWindow(window1.get());
+
+ // |window2| should be centered now.
+ EXPECT_TRUE(window2->IsVisible());
+ EXPECT_TRUE(ash::wm::IsWindowNormal(window2.get()));
+ EXPECT_EQ(base::IntToString(
+ (desktop_area.width() - window2->bounds().width()) / 2) +
+ ",40 256x512", window2->bounds().ToString());
+}
+
+// Test that nomral, maximize, normal will repos the remaining.
+TEST_F(WorkspaceControllerTest, NormToMaxToNormRepositionsRemaining) {
+ scoped_ptr<aura::Window> window1(CreateTestWindowInShellWithId(0));
+ window1->SetBounds(gfx::Rect(16, 32, 640, 320));
+ ash::wm::SetWindowPositionManaged(window1.get(), true);
+ gfx::Rect desktop_area = window1->parent()->bounds();
+
+ scoped_ptr<aura::Window> window2(CreateTestWindowInShellWithId(1));
+ ash::wm::SetWindowPositionManaged(window2.get(), true);
+ window2->SetBounds(gfx::Rect(32, 40, 256, 512));
+
+ // Trigger the auto window placement function by showing (and hiding) it.
+ window1->Hide();
+ window1->Show();
+
+ // |window1| should be flush right and |window3| flush left.
+ EXPECT_EQ(base::IntToString(
+ desktop_area.width() - window1->bounds().width()) +
+ ",32 640x320", window1->bounds().ToString());
+ EXPECT_EQ("0,40 256x512", window2->bounds().ToString());
+
+ ash::wm::MaximizeWindow(window1.get());
+ ash::wm::RestoreWindow(window1.get());
+
+ // |window1| should be flush right and |window2| flush left.
+ EXPECT_EQ(base::IntToString(
+ desktop_area.width() - window1->bounds().width()) +
+ ",32 640x320", window1->bounds().ToString());
+ EXPECT_EQ("0,40 256x512", window2->bounds().ToString());
+}
+
+// Test that animations are triggered.
+TEST_F(WorkspaceControllerTest, AnimatedNormToMaxToNormRepositionsRemaining) {
+ ui::ScopedAnimationDurationScaleMode normal_duration_mode(
+ ui::ScopedAnimationDurationScaleMode::NORMAL_DURATION);
+ scoped_ptr<aura::Window> window1(CreateTestWindowInShellWithId(0));
+ window1->Hide();
+ window1->SetBounds(gfx::Rect(16, 32, 640, 320));
+ gfx::Rect desktop_area = window1->parent()->bounds();
+ scoped_ptr<aura::Window> window2(CreateTestWindowInShellWithId(1));
+ window2->Hide();
+ window2->SetBounds(gfx::Rect(32, 48, 256, 512));
+
+ ash::wm::SetWindowPositionManaged(window1.get(), true);
+ ash::wm::SetWindowPositionManaged(window2.get(), true);
+ // Make sure nothing is animating.
+ window1->layer()->GetAnimator()->StopAnimating();
+ window2->layer()->GetAnimator()->StopAnimating();
+ window2->Show();
+
+ // The second window should now animate.
+ EXPECT_FALSE(window1->layer()->GetAnimator()->is_animating());
+ EXPECT_TRUE(window2->layer()->GetAnimator()->is_animating());
+ window2->layer()->GetAnimator()->StopAnimating();
+
+ window1->Show();
+ EXPECT_TRUE(window1->layer()->GetAnimator()->is_animating());
+ EXPECT_TRUE(window2->layer()->GetAnimator()->is_animating());
+
+ window1->layer()->GetAnimator()->StopAnimating();
+ window2->layer()->GetAnimator()->StopAnimating();
+ // |window1| should be flush right and |window2| flush left.
+ EXPECT_EQ(base::IntToString(
+ desktop_area.width() - window1->bounds().width()) +
+ ",32 640x320", window1->bounds().ToString());
+ EXPECT_EQ("0,48 256x512", window2->bounds().ToString());
+}
+
+// This tests simulates a browser and an app and verifies the ordering of the
+// windows and layers doesn't get out of sync as various operations occur. Its
+// really testing code in FocusController, but easier to simulate here. Just as
+// with a real browser the browser here has a transient child window
+// (corresponds to the status bubble).
+TEST_F(WorkspaceControllerTest, VerifyLayerOrdering) {
+ scoped_ptr<Window> browser(
+ aura::test::CreateTestWindowWithDelegate(
+ NULL,
+ aura::client::WINDOW_TYPE_NORMAL,
+ gfx::Rect(5, 6, 7, 8),
+ NULL));
+ browser->SetName("browser");
+ SetDefaultParentByPrimaryRootWindow(browser.get());
+ browser->Show();
+ wm::ActivateWindow(browser.get());
+
+ // |status_bubble| is made a transient child of |browser| and as a result
+ // owned by |browser|.
+ aura::test::TestWindowDelegate* status_bubble_delegate =
+ aura::test::TestWindowDelegate::CreateSelfDestroyingDelegate();
+ status_bubble_delegate->set_can_focus(false);
+ Window* status_bubble =
+ aura::test::CreateTestWindowWithDelegate(
+ status_bubble_delegate,
+ aura::client::WINDOW_TYPE_POPUP,
+ gfx::Rect(5, 6, 7, 8),
+ NULL);
+ browser->AddTransientChild(status_bubble);
+ SetDefaultParentByPrimaryRootWindow(status_bubble);
+ status_bubble->SetName("status_bubble");
+
+ scoped_ptr<Window> app(
+ aura::test::CreateTestWindowWithDelegate(
+ NULL,
+ aura::client::WINDOW_TYPE_NORMAL,
+ gfx::Rect(5, 6, 7, 8),
+ NULL));
+ app->SetName("app");
+ SetDefaultParentByPrimaryRootWindow(app.get());
+
+ aura::Window* parent = browser->parent();
+
+ app->Show();
+ wm::ActivateWindow(app.get());
+ EXPECT_EQ(GetWindowNames(parent), GetLayerNames(parent));
+
+ // Minimize the app, focus should go the browser.
+ app->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MINIMIZED);
+ EXPECT_TRUE(wm::IsActiveWindow(browser.get()));
+ EXPECT_EQ(GetWindowNames(parent), GetLayerNames(parent));
+
+ // Minimize the browser (neither windows are focused).
+ browser->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MINIMIZED);
+ EXPECT_FALSE(wm::IsActiveWindow(browser.get()));
+ EXPECT_FALSE(wm::IsActiveWindow(app.get()));
+ EXPECT_EQ(GetWindowNames(parent), GetLayerNames(parent));
+
+ // Show the browser (which should restore it).
+ browser->Show();
+ EXPECT_EQ(GetWindowNames(parent), GetLayerNames(parent));
+
+ // Activate the browser.
+ ash::wm::ActivateWindow(browser.get());
+ EXPECT_TRUE(wm::IsActiveWindow(browser.get()));
+ EXPECT_EQ(GetWindowNames(parent), GetLayerNames(parent));
+
+ // Restore the app. This differs from above code for |browser| as internally
+ // the app code does this. Restoring this way or using Show() should not make
+ // a difference.
+ app->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_NORMAL);
+ EXPECT_EQ(GetWindowNames(parent), GetLayerNames(parent));
+
+ // Activate the app.
+ ash::wm::ActivateWindow(app.get());
+ EXPECT_TRUE(wm::IsActiveWindow(app.get()));
+ EXPECT_EQ(GetWindowNames(parent), GetLayerNames(parent));
+}
+
+namespace {
+
+// Used by DragMaximizedNonTrackedWindow to track how many times the window
+// hierarchy changes.
+class DragMaximizedNonTrackedWindowObserver
+ : public aura::WindowObserver {
+ public:
+ DragMaximizedNonTrackedWindowObserver() : change_count_(0) {
+ }
+
+ // Number of times OnWindowHierarchyChanged() has been received.
+ void clear_change_count() { change_count_ = 0; }
+ int change_count() const {
+ return change_count_;
+ }
+
+ // aura::WindowObserver overrides:
+ // Counts number of times a window is reparented. Ignores reparenting into and
+ // from a docked container which is expected when a tab is dragged.
+ virtual void OnWindowHierarchyChanged(
+ const HierarchyChangeParams& params) OVERRIDE {
+ if ((params.old_parent->id() == kShellWindowId_DefaultContainer &&
+ params.new_parent->id() == kShellWindowId_DockedContainer) ||
+ (params.old_parent->id() == kShellWindowId_DockedContainer &&
+ params.new_parent->id() == kShellWindowId_DefaultContainer)) {
+ return;
+ }
+ change_count_++;
+ }
+
+ private:
+ int change_count_;
+
+ DISALLOW_COPY_AND_ASSIGN(DragMaximizedNonTrackedWindowObserver);
+};
+
+} // namespace
+
+// Verifies setting tracked by workspace to false and then dragging a fullscreen
+// window doesn't result in changing the window hierarchy (which typically
+// indicates new workspaces have been created).
+TEST_F(WorkspaceControllerTest, DragFullscreenNonTrackedWindow) {
+ aura::test::EventGenerator generator(
+ Shell::GetPrimaryRootWindow(), gfx::Point());
+ generator.MoveMouseTo(5, 5);
+
+ aura::test::TestWindowDelegate delegate;
+ delegate.set_window_component(HTCAPTION);
+ scoped_ptr<Window> w1(
+ aura::test::CreateTestWindowWithDelegate(&delegate,
+ aura::client::WINDOW_TYPE_NORMAL,
+ gfx::Rect(5, 6, 7, 8),
+ NULL));
+ SetDefaultParentByPrimaryRootWindow(w1.get());
+ w1->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_FULLSCREEN);
+ w1->Show();
+ wm::ActivateWindow(w1.get());
+ DragMaximizedNonTrackedWindowObserver observer;
+ w1->parent()->parent()->AddObserver(&observer);
+ const gfx::Rect max_bounds(w1->bounds());
+
+ generator.PressLeftButton();
+ generator.MoveMouseTo(100, 100);
+ // The bounds shouldn't change (drag should result in nothing happening
+ // now.
+ EXPECT_EQ(max_bounds.ToString(), w1->bounds().ToString());
+
+ generator.ReleaseLeftButton();
+ EXPECT_EQ(0, observer.change_count());
+
+ // Set tracked to false and repeat, now the window should move.
+ SetTrackedByWorkspace(w1.get(), false);
+ generator.MoveMouseTo(5, 5);
+ generator.PressLeftButton();
+ generator.MoveMouseBy(100, 100);
+ EXPECT_EQ(gfx::Rect(max_bounds.x() + 100, max_bounds.y() + 100,
+ max_bounds.width(), max_bounds.height()).ToString(),
+ w1->bounds().ToString());
+
+ generator.ReleaseLeftButton();
+ SetTrackedByWorkspace(w1.get(), true);
+ // Marking the window tracked again should snap back to origin.
+ EXPECT_EQ(max_bounds.ToString(), w1->bounds().ToString());
+ EXPECT_EQ(0, observer.change_count());
+
+ w1->parent()->parent()->RemoveObserver(&observer);
+}
+
+// Verifies setting tracked by workspace to false and then dragging a maximized
+// window can change the bound.
+TEST_F(WorkspaceControllerTest, DragMaximizedNonTrackedWindow) {
+ aura::test::EventGenerator generator(
+ Shell::GetPrimaryRootWindow(), gfx::Point());
+ generator.MoveMouseTo(5, 5);
+
+ aura::test::TestWindowDelegate delegate;
+ delegate.set_window_component(HTCAPTION);
+ scoped_ptr<Window> w1(
+ aura::test::CreateTestWindowWithDelegate(&delegate,
+ aura::client::WINDOW_TYPE_NORMAL,
+ gfx::Rect(5, 6, 7, 8),
+ NULL));
+ SetDefaultParentByPrimaryRootWindow(w1.get());
+ w1->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MAXIMIZED);
+ w1->Show();
+ wm::ActivateWindow(w1.get());
+ DragMaximizedNonTrackedWindowObserver observer;
+ w1->parent()->parent()->AddObserver(&observer);
+ const gfx::Rect max_bounds(w1->bounds());
+
+ generator.PressLeftButton();
+ generator.MoveMouseTo(100, 100);
+ // The bounds shouldn't change (drag should result in nothing happening
+ // now.
+ EXPECT_EQ(max_bounds.ToString(), w1->bounds().ToString());
+
+ generator.ReleaseLeftButton();
+ EXPECT_EQ(0, observer.change_count());
+
+ // Set tracked to false and repeat, now the window should move.
+ SetTrackedByWorkspace(w1.get(), false);
+ generator.MoveMouseTo(5, 5);
+ generator.PressLeftButton();
+ generator.MoveMouseBy(100, 100);
+ EXPECT_EQ(gfx::Rect(max_bounds.x() + 100, max_bounds.y() + 100,
+ max_bounds.width(), max_bounds.height()).ToString(),
+ w1->bounds().ToString());
+
+ generator.ReleaseLeftButton();
+ SetTrackedByWorkspace(w1.get(), true);
+ // Marking the window tracked again should snap back to origin.
+ EXPECT_EQ(max_bounds.ToString(), w1->bounds().ToString());
+ EXPECT_EQ(0, observer.change_count());
+
+ w1->parent()->parent()->RemoveObserver(&observer);
+}
+
+// Verifies that a new maximized window becomes visible after its activation
+// is requested, even though it does not become activated because a system
+// modal window is active.
+TEST_F(WorkspaceControllerTest, SwitchFromModal) {
+ scoped_ptr<Window> modal_window(CreateTestWindowUnparented());
+ modal_window->SetBounds(gfx::Rect(10, 11, 21, 22));
+ modal_window->SetProperty(aura::client::kModalKey, ui::MODAL_TYPE_SYSTEM);
+ SetDefaultParentByPrimaryRootWindow(modal_window.get());
+ modal_window->Show();
+ wm::ActivateWindow(modal_window.get());
+
+ scoped_ptr<Window> maximized_window(CreateTestWindow());
+ maximized_window->SetProperty(
+ aura::client::kShowStateKey, ui::SHOW_STATE_MAXIMIZED);
+ maximized_window->Show();
+ wm::ActivateWindow(maximized_window.get());
+ EXPECT_TRUE(maximized_window->IsVisible());
+}
+
+} // namespace internal
+} // namespace ash