Added battery level and time to the status tray's accessible name.

Appended already written accessible strings for time and battery to the status tray's current name.
Ensured the dispatched accessibility event for any events coming from the status tray reads the status tray name and not the names of the buttons inside the status tray bar.

BUG= 392666

Review URL: https://codereview.chromium.org/380943002

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@284818 0039d316-1c4b-4281-b951-d872f2087c98
diff --git a/ash/ash_strings.grd b/ash/ash_strings.grd
index 676f855..e6ba0b3 100644
--- a/ash/ash_strings.grd
+++ b/ash/ash_strings.grd
@@ -204,6 +204,9 @@
       <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_ACCESSIBLE_DESCRIPTION" desc="The accessible description of the status tray and the information on it.">
+        Status tray, time <ph name="time">$1<ex>9:50</ex></ph>, <ph name="battery">$2<ex>Battery is full.</ex></ph>
+      </message>
       <message name="IDS_ASH_STATUS_TRAY_BRAILLE_DISPLAY_CONNECTED_BUBBLE" desc="The message shown on a bubble when a braille display is connected">
 Braille display connected.
       </message>
diff --git a/ash/system/chromeos/power/power_status.cc b/ash/system/chromeos/power/power_status.cc
index 8940e9e..591c318 100644
--- a/ash/system/chromeos/power/power_status.cc
+++ b/ash/system/chromeos/power/power_status.cc
@@ -235,7 +235,8 @@
   return gfx::ImageSkiaOperations::ExtractSubset(*all.ToImageSkia(), region);
 }
 
-base::string16 PowerStatus::GetAccessibleNameString() const {
+base::string16 PowerStatus::GetAccessibleNameString(
+    bool full_description) const {
   ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
   if (IsBatteryFull()) {
     return rb.GetLocalizedString(
@@ -247,6 +248,9 @@
       IDS_ASH_STATUS_TRAY_BATTERY_PERCENT_CHARGING_ACCESSIBLE :
       IDS_ASH_STATUS_TRAY_BATTERY_PERCENT_ACCESSIBLE,
       base::IntToString16(GetRoundedBatteryPercent()));
+  if (!full_description)
+    return battery_percentage_accessible;
+
   base::string16 battery_time_accessible = base::string16();
   const base::TimeDelta time = IsBatteryCharging() ? GetBatteryTimeToFull() :
       GetBatteryTimeToEmpty();
diff --git a/ash/system/chromeos/power/power_status.h b/ash/system/chromeos/power/power_status.h
index 88f3585..236e05c 100644
--- a/ash/system/chromeos/power/power_status.h
+++ b/ash/system/chromeos/power/power_status.h
@@ -129,7 +129,7 @@
   gfx::ImageSkia GetBatteryImage(IconSet icon_set) const;
 
   // Returns an string describing the current state for accessibility.
-  base::string16 GetAccessibleNameString() const;
+  base::string16 GetAccessibleNameString(bool full_description) const;
 
   // Updates |proto_|. Does not notify observers.
   void SetProtoForTesting(const power_manager::PowerSupplyProperties& proto);
diff --git a/ash/system/chromeos/power/tray_power.cc b/ash/system/chromeos/power/tray_power.cc
index 5442266..e4f094f4 100644
--- a/ash/system/chromeos/power/tray_power.cc
+++ b/ash/system/chromeos/power/tray_power.cc
@@ -69,7 +69,7 @@
     SetVisible(PowerStatus::Get()->IsBatteryPresent());
 
     if (battery_alert) {
-      accessible_name_ = PowerStatus::Get()->GetAccessibleNameString();
+      accessible_name_ = PowerStatus::Get()->GetAccessibleNameString(true);
       NotifyAccessibilityEvent(ui::AX_EVENT_ALERT, true);
     }
   }
diff --git a/ash/system/chromeos/settings/tray_settings.cc b/ash/system/chromeos/settings/tray_settings.cc
index bb27a17..b6f4e76 100644
--- a/ash/system/chromeos/settings/tray_settings.cc
+++ b/ash/system/chromeos/settings/tray_settings.cc
@@ -108,8 +108,8 @@
 
     base::string16 accessible_name = label_ ?
         label_->text() + base::ASCIIToUTF16(", ") +
-            PowerStatus::Get()->GetAccessibleNameString() :
-        PowerStatus::Get()->GetAccessibleNameString();
+            PowerStatus::Get()->GetAccessibleNameString(true) :
+        PowerStatus::Get()->GetAccessibleNameString(true);
     SetAccessibleName(accessible_name);
   }
 
diff --git a/ash/system/tray/system_tray.cc b/ash/system/tray/system_tray.cc
index b4ba58e..ef0d0fd 100644
--- a/ash/system/tray/system_tray.cc
+++ b/ash/system/tray/system_tray.cc
@@ -50,6 +50,7 @@
 #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/power_status.h"
 #include "ash/system/chromeos/power/tray_power.h"
 #include "ash/system/chromeos/rotation/tray_rotation_lock.h"
 #include "ash/system/chromeos/screen_security/screen_capture_tray_item.h"
@@ -402,6 +403,16 @@
   }
 }
 
+base::string16 SystemTray::GetAccessibleNameForTray() {
+  base::string16 time = GetAccessibleTimeString(base::Time::Now());
+  base::string16 battery = base::ASCIIToUTF16("");
+#if defined(OS_CHROMEOS)
+  battery = PowerStatus::Get()->GetAccessibleNameString(false);
+#endif
+  return l10n_util::GetStringFUTF16(
+      IDS_ASH_STATUS_TRAY_ACCESSIBLE_DESCRIPTION, time, battery);
+}
+
 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 &&
@@ -588,6 +599,14 @@
   status_area_widget()->web_notification_tray()->SetSystemTrayHeight(height);
 }
 
+base::string16 SystemTray::GetAccessibleTimeString(
+    const base::Time& now) const {
+  base::HourClockType hour_type =
+      ash::Shell::GetInstance()->system_tray_delegate()->GetHourClockType();
+  return base::TimeFormatTimeOfDayWithHourClockType(
+      now, hour_type, base::kKeepAmPm);
+}
+
 void SystemTray::SetShelfAlignment(ShelfAlignment alignment) {
   if (alignment == shelf_alignment())
     return;
@@ -615,10 +634,6 @@
   }
 }
 
-base::string16 SystemTray::GetAccessibleNameForTray() {
-  return l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_ACCESSIBLE_NAME);
-}
-
 void SystemTray::BubbleResized(const TrayBubbleView* bubble_view) {
   UpdateWebNotifications();
 }
diff --git a/ash/system/tray/system_tray.h b/ash/system/tray/system_tray.h
index 99f5de0..2df725d 100644
--- a/ash/system/tray/system_tray.h
+++ b/ash/system/tray/system_tray.h
@@ -161,6 +161,10 @@
   // Resets |notification_bubble_| and clears any related state.
   void DestroyNotificationBubble();
 
+  // Returns a string with the current time for accessibility on the status
+  // tray bar.
+  base::string16 GetAccessibleTimeString(const base::Time& now) const;
+
   // 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;
diff --git a/chrome/browser/ui/views/accessibility/accessibility_event_router_views.cc b/chrome/browser/ui/views/accessibility/accessibility_event_router_views.cc
index bab270e..5317d74 100644
--- a/chrome/browser/ui/views/accessibility/accessibility_event_router_views.cc
+++ b/chrome/browser/ui/views/accessibility/accessibility_event_router_views.cc
@@ -24,9 +24,12 @@
 #include "ui/views/widget/widget.h"
 
 using views::FocusManager;
+using views::ViewStorage;
 
 AccessibilityEventRouterViews::AccessibilityEventRouterViews()
-    : most_recent_profile_(NULL) {
+    : most_recent_profile_(NULL),
+      most_recent_view_id_(
+          ViewStorage::GetInstance()->CreateStorageID()) {
   // Register for notification when profile is destroyed to ensure that all
   // observers are detatched at that time.
   registrar_.Add(this, chrome::NOTIFICATION_PROFILE_DESTROYED,
@@ -68,7 +71,7 @@
   // event loop, to handle cases where the view's state changes after
   // the call to post the event. It's safe to use base::Unretained(this)
   // because AccessibilityEventRouterViews is a singleton.
-  views::ViewStorage* view_storage = views::ViewStorage::GetInstance();
+  ViewStorage* view_storage = ViewStorage::GetInstance();
   int view_storage_id = view_storage->CreateStorageID();
   view_storage->StoreView(view_storage_id, view);
   base::MessageLoop::current()->PostTask(
@@ -120,7 +123,7 @@
 void AccessibilityEventRouterViews::DispatchEventOnViewStorageId(
     int view_storage_id,
     ui::AXEvent type) {
-  views::ViewStorage* view_storage = views::ViewStorage::GetInstance();
+  ViewStorage* view_storage = ViewStorage::GetInstance();
   views::View* view = view_storage->RetrieveView(view_storage_id);
   view_storage->RemoveView(view_storage_id);
   if (!view)
@@ -163,6 +166,20 @@
     return;
   }
 
+  view = FindFirstAccessibleAncestor(view);
+
+  // Since multiple items could share a highest focusable view, these items
+  // could all dispatch the same accessibility hover events, which isn't
+  // necessary.
+  if (type == ui::AX_EVENT_HOVER &&
+      ViewStorage::GetInstance()->RetrieveView(most_recent_view_id_) == view) {
+    return;
+  }
+  // If there was already a view stored here from before, it must be removed
+  // before storing a new view.
+  ViewStorage::GetInstance()->RemoveView(most_recent_view_id_);
+  ViewStorage::GetInstance()->StoreView(most_recent_view_id_, view);
+
   ui::AXViewState state;
   view->GetAccessibleState(&state);
 
@@ -552,3 +569,12 @@
   }
   return std::string();
 }
+
+// static
+views::View* AccessibilityEventRouterViews::FindFirstAccessibleAncestor(
+    views::View* view) {
+  while (view->parent() && !view->IsAccessibilityFocusable()) {
+    view = view->parent();
+  }
+  return view;
+}
diff --git a/chrome/browser/ui/views/accessibility/accessibility_event_router_views.h b/chrome/browser/ui/views/accessibility/accessibility_event_router_views.h
index 6b24f9f..20530a3 100644
--- a/chrome/browser/ui/views/accessibility/accessibility_event_router_views.h
+++ b/chrome/browser/ui/views/accessibility/accessibility_event_router_views.h
@@ -158,11 +158,21 @@
   // subview with a role of STATIC_TEXT.
   static std::string RecursiveGetStaticText(views::View* view);
 
+  // Returns the first ancestor of |view| (including |view|) that is
+  // accessible.
+  static views::View* FindFirstAccessibleAncestor(views::View* view);
+
   // The profile associated with the most recent window event  - used to
   // figure out where to route a few events that can't be directly traced
   // to a window with a profile (like menu events).
   Profile* most_recent_profile_;
 
+  // The most recent accessibility focusable view is stored in view storage
+  // and is used to prevent multiple events from being dispatched on a
+  // hoverable view from its multiple children. This is the id for the most
+  // recent view we put in view storage.
+  const int most_recent_view_id_;
+
   // Notification registrar so we can clear most_recent_profile_ when a
   // profile is destroyed.
   content::NotificationRegistrar registrar_;