Adding accessibility support to the Status Bar.

1. Added content description to pretty much all animals
   in the zoo including buttons in the navigation bar,
   notifications and status icons for battery, signal,
   data, etc.

2. Rectored to avoid ovelaying views since they block
   touch exploratino. In general overlaying views
   cause trouble for touch exploration and accessibility
   in general.

3. Avoid sending accessibility events in case the user is
   touching outside of the StatauBAr panels to avoid
   confusion.

4. Added records to accessibility events in the places where
   this would help the presentation. So the event comes from
   a given "leaf" view and its predecessor is adding a record
   to the event for itself to provide more cotext. It is up
   to the accessiiblity service to choose how to present that.

bug:4686943

Change-Id: I1c1bd123d828fb10911bca92130e9a05c1f020b3
diff --git a/packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java b/packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java
index bc0a508..cca7947 100644
--- a/packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java
@@ -22,7 +22,6 @@
 import android.animation.Animator;
 import android.animation.LayoutTransition;
 import android.app.ActivityManager;
-import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
@@ -48,6 +47,7 @@
 import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.MenuItem;
+import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.AdapterView;
@@ -112,7 +112,7 @@
             position = _pos;
             packageName = _packageName;
         }
-    };
+    }
 
     private final class OnLongClickDelegate implements View.OnLongClickListener {
         View mOtherView;
@@ -252,6 +252,19 @@
         mChoreo.setPanelHeight(mRecentsContainer.getHeight());
     }
 
+    @Override
+    public boolean dispatchHoverEvent(MotionEvent event) {
+        // Ignore hover events outside of this panel bounds since such events
+        // generate spurious accessibility events with the panel content when
+        // tapping outside of it, thus confusing the user.
+        final int x = (int) event.getX();
+        final int y = (int) event.getY();
+        if (x >= 0 && x < getWidth() && y >= 0 && y < getHeight()) {
+            return super.dispatchHoverEvent(event);
+        }
+        return true;
+    }
+
     /**
      * Whether the panel is showing, or, if it's animating, whether it will be
      * when the animation is done.
@@ -316,7 +329,6 @@
 
 
         mRecentsGlowView = findViewById(R.id.recents_glow);
-        mRecentsScrim = (View) findViewById(R.id.recents_bg_protect);
         mChoreo = new Choreographer(this, mRecentsScrim, mRecentsGlowView, this);
         mRecentsDismissButton = findViewById(R.id.recents_dismiss_button);
         mRecentsDismissButton.setOnClickListener(new OnClickListener() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LatestItemView.java b/packages/SystemUI/src/com/android/systemui/statusbar/LatestItemView.java
index 64ec063..6419777 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LatestItemView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LatestItemView.java
@@ -18,8 +18,8 @@
 
 import android.content.Context;
 import android.util.AttributeSet;
-import android.util.Slog;
-import android.view.MotionEvent;
+import android.view.View;
+import android.view.accessibility.AccessibilityEvent;
 import android.widget.FrameLayout;
 
 public class LatestItemView extends FrameLayout {
@@ -27,7 +27,22 @@
         super(context, attrs);
     }
 
+    @Override
     public void setOnClickListener(OnClickListener l) {
         super.setOnClickListener(l);
     }
+
+    @Override
+    public boolean onRequestSendAccessibilityEvent(View child, AccessibilityEvent event) {
+        if (super.onRequestSendAccessibilityEvent(child, event)) {
+            // Add a record for the entire layout since its content is somehow small.
+            // The event comes from a leaf view that is interacted with.
+            AccessibilityEvent record = AccessibilityEvent.obtain();
+            onInitializeAccessibilityEvent(record);
+            dispatchPopulateAccessibilityEvent(record);
+            event.appendRecord(record);
+            return true;
+        }
+        return false;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
index d9d9c06..be4b395 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar;
 
+import android.app.Notification;
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.content.res.Resources;
@@ -23,11 +24,11 @@
 import android.graphics.Canvas;
 import android.graphics.Paint;
 import android.graphics.Rect;
+import android.text.TextUtils;
 import android.util.Slog;
 import android.util.Log;
-import android.view.View;
 import android.view.ViewDebug;
-import android.widget.FrameLayout;
+import android.view.accessibility.AccessibilityEvent;
 
 import java.text.NumberFormat;
 
@@ -45,8 +46,9 @@
     private int mNumberX;
     private int mNumberY;
     private String mNumberText;
+    private Notification mNotification;
 
-    public StatusBarIconView(Context context, String slot) {
+    public StatusBarIconView(Context context, String slot, Notification notification) {
         super(context);
         final Resources res = context.getResources();
         mSlot = slot;
@@ -54,6 +56,8 @@
         mNumberPain.setTextAlign(Paint.Align.CENTER);
         mNumberPain.setColor(res.getColor(R.drawable.notification_number_text_color));
         mNumberPain.setAntiAlias(true);
+        mNotification = notification;
+        setContentDescription(notification);
     }
 
     private static boolean streq(String a, String b) {
@@ -83,6 +87,7 @@
         final boolean numberEquals = mIcon != null
                 && mIcon.number == icon.number;
         mIcon = icon.clone();
+        setContentDescription(icon.contentDescription);
         if (!iconEquals) {
             Drawable drawable = getIcon(icon);
             if (drawable == null) {
@@ -159,6 +164,15 @@
         return mIcon;
     }
 
+    @Override
+    public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
+        super.onInitializeAccessibilityEvent(event);
+        if (mNotification != null) {
+            event.setParcelableData(mNotification);
+        }
+    }
+
+    @Override
     protected void onSizeChanged(int w, int h, int oldw, int oldh) {
         super.onSizeChanged(w, h, oldw, oldh);
         if (mNumberBackground != null) {
@@ -166,6 +180,7 @@
         }
     }
 
+    @Override
     protected void onDraw(Canvas canvas) {
         super.onDraw(canvas);
 
@@ -175,6 +190,7 @@
         }
     }
 
+    @Override
     protected void debug(int depth) {
         super.debug(depth);
         Log.d("View", debugIndent(depth) + "slot=" + mSlot);
@@ -213,4 +229,13 @@
         mNumberY = h-r.bottom-((dh-r.top-th-r.bottom)/2);
         mNumberBackground.setBounds(w-dw, h-dh, w, h);
     }
+
+    private void setContentDescription(Notification notification) {
+        if (notification != null) {
+            CharSequence tickerText = notification.tickerText;
+            if (!TextUtils.isEmpty(tickerText)) {
+                setContentDescription(tickerText);
+            }
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/IconMerger.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/IconMerger.java
index e1d17a8..f6aa159 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/IconMerger.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/IconMerger.java
@@ -33,7 +33,8 @@
 
     private int mIconSize;
     private StatusBarIconView mMoreView;
-    private StatusBarIcon mMoreIcon = new StatusBarIcon(null, R.drawable.stat_notify_more, 0);
+    private StatusBarIcon mMoreIcon = new StatusBarIcon(null, R.drawable.stat_notify_more, 0, 0,
+            null);
 
     public IconMerger(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -41,7 +42,7 @@
         mIconSize = context.getResources().getDimensionPixelSize(
                 com.android.internal.R.dimen.status_bar_icon_size);
 
-        mMoreView = new StatusBarIconView(context, "more");
+        mMoreView = new StatusBarIconView(context, "more", null);
         mMoreView.set(mMoreIcon);
         addView(mMoreView, 0, new LinearLayout.LayoutParams(mIconSize, mIconSize));
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
index f4c4bbc..b93ad68 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -472,7 +472,7 @@
     public void addIcon(String slot, int index, int viewIndex, StatusBarIcon icon) {
         if (SPEW) Slog.d(TAG, "addIcon slot=" + slot + " index=" + index + " viewIndex=" + viewIndex
                 + " icon=" + icon);
-        StatusBarIconView view = new StatusBarIconView(mContext, slot);
+        StatusBarIconView view = new StatusBarIconView(mContext, slot, null);
         view.set(icon);
         mStatusIcons.addView(view, viewIndex, new LinearLayout.LayoutParams(mIconSize, mIconSize));
     }
@@ -607,7 +607,7 @@
                 // Update the icon.
                 final StatusBarIcon ic = new StatusBarIcon(notification.pkg,
                         notification.notification.icon, notification.notification.iconLevel,
-                        notification.notification.number);
+                        notification.notification.number, notification.notification.tickerText);
                 if (!oldEntry.icon.set(ic)) {
                     handleNotificationError(key, notification, "Couldn't update icon: " + ic);
                     return;
@@ -765,9 +765,11 @@
         final View expanded = views[2];
         // Construct the icon.
         final StatusBarIconView iconView = new StatusBarIconView(mContext,
-                notification.pkg + "/0x" + Integer.toHexString(notification.id));
+                notification.pkg + "/0x" + Integer.toHexString(notification.id),
+                notification.notification);
         final StatusBarIcon ic = new StatusBarIcon(notification.pkg, notification.notification.icon,
-                    notification.notification.iconLevel, notification.notification.number);
+                    notification.notification.iconLevel, notification.notification.number,
+                    notification.notification.tickerText);
         if (!iconView.set(ic)) {
             handleNotificationError(key, notification, "Coulding create icon: " + ic);
             return null;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
index af5c72d..7b50985 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
@@ -18,25 +18,17 @@
 
 import android.app.StatusBarManager;
 import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.BluetoothProfile;
-import android.bluetooth.BluetoothPbap;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
-import android.content.res.TypedArray;
-import android.graphics.PixelFormat;
-import android.graphics.Typeface;
-import android.graphics.drawable.Drawable;
 import android.location.LocationManager;
 import android.media.AudioManager;
 import android.net.ConnectivityManager;
 import android.net.NetworkInfo;
-import android.net.Uri;
 import android.net.wifi.WifiManager;
 import android.os.Binder;
 import android.os.Handler;
-import android.os.Message;
 import android.os.RemoteException;
 import android.os.storage.StorageManager;
 import android.provider.Settings;
@@ -44,18 +36,7 @@
 import android.telephony.ServiceState;
 import android.telephony.SignalStrength;
 import android.telephony.TelephonyManager;
-import android.text.format.DateFormat;
-import android.text.style.CharacterStyle;
-import android.text.style.RelativeSizeSpan;
-import android.text.style.ForegroundColorSpan;
-import android.text.style.StyleSpan;
-import android.text.Spannable;
-import android.text.SpannableStringBuilder;
 import android.util.Slog;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
 
 import com.android.internal.app.IBatteryStats;
 import com.android.internal.telephony.IccCard;
@@ -63,7 +44,6 @@
 import com.android.internal.telephony.cdma.EriInfo;
 import com.android.internal.telephony.cdma.TtyIntent;
 import com.android.server.am.BatteryStatsService;
-
 import com.android.systemui.R;
 
 /**
@@ -447,6 +427,32 @@
               R.drawable.stat_sys_data_fully_inandout_1x }
             };
 
+    // Accessibility;
+
+    private static final int[] sPhoneSignalStrength = {
+        R.string.accessibility_no_phone,
+        R.string.accessibility_phone_one_bar,
+        R.string.accessibility_phone_two_bars,
+        R.string.accessibility_phone_three_bars,
+        R.string.accessibility_phone_signal_full
+    };
+
+    private static final int[] sDataConnectionStrength = {
+        R.string.accessibility_no_data,
+        R.string.accessibility_data_one_bar,
+        R.string.accessibility_data_two_bars,
+        R.string.accessibility_data_three_bars,
+        R.string.accessibility_data_signal_full
+    };
+
+    private static final int[] sWifiConnectionStrength = {
+        R.string.accessibility_no_wifi,
+        R.string.accessibility_wifi_one_bar,
+        R.string.accessibility_wifi_two_bars,
+        R.string.accessibility_wifi_three_bars,
+        R.string.accessibility_wifi_signal_full
+    };
+
     // Assume it's all good unless we hear otherwise.  We don't always seem
     // to get broadcasts that it *is* there.
     IccCard.State mSimState = IccCard.State.READY;
@@ -546,12 +552,13 @@
                 new com.android.systemui.usb.StorageNotification(context));
 
         // battery
-        mService.setIcon("battery", com.android.internal.R.drawable.stat_sys_battery_unknown, 0);
+        mService.setIcon("battery", com.android.internal.R.drawable.stat_sys_battery_unknown, 0,
+                null);
 
         // phone_signal
         mPhone = (TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE);
         mPhoneSignalIconId = R.drawable.stat_sys_signal_null;
-        mService.setIcon("phone_signal", mPhoneSignalIconId, 0);
+        mService.setIcon("phone_signal", mPhoneSignalIconId, 0, null);
 
         // register for phone state notifications.
         ((TelephonyManager)mContext.getSystemService(Context.TELEPHONY_SERVICE))
@@ -563,24 +570,24 @@
                         | PhoneStateListener.LISTEN_DATA_ACTIVITY);
 
         // data_connection
-        mService.setIcon("data_connection", R.drawable.stat_sys_data_connected_g, 0);
+        mService.setIcon("data_connection", R.drawable.stat_sys_data_connected_g, 0, null);
         mService.setIconVisibility("data_connection", false);
 
         // wifi
-        mService.setIcon("wifi", sWifiSignalImages[0][0], 0);
+        mService.setIcon("wifi", sWifiSignalImages[0][0], 0, null);
         mService.setIconVisibility("wifi", false);
         // wifi will get updated by the sticky intents
 
         // TTY status
-        mService.setIcon("tty",  R.drawable.stat_sys_tty_mode, 0);
+        mService.setIcon("tty",  R.drawable.stat_sys_tty_mode, 0, null);
         mService.setIconVisibility("tty", false);
 
         // Cdma Roaming Indicator, ERI
-        mService.setIcon("cdma_eri", R.drawable.stat_sys_roaming_cdma_0, 0);
+        mService.setIcon("cdma_eri", R.drawable.stat_sys_roaming_cdma_0, 0, null);
         mService.setIconVisibility("cdma_eri", false);
 
         // bluetooth status
-        mService.setIcon("bluetooth", R.drawable.stat_sys_data_bluetooth, 0);
+        mService.setIcon("bluetooth", R.drawable.stat_sys_data_bluetooth, 0, null);
         BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
         if (adapter != null) {
             mBluetoothEnabled = adapter.isEnabled();
@@ -590,21 +597,23 @@
         mService.setIconVisibility("bluetooth", mBluetoothEnabled);
 
         // Gps status
-        mService.setIcon("gps", R.drawable.stat_sys_gps_acquiring_anim, 0);
+        mService.setIcon("gps", R.drawable.stat_sys_gps_acquiring_anim, 0, null);
         mService.setIconVisibility("gps", false);
 
         // Alarm clock
-        mService.setIcon("alarm_clock", R.drawable.stat_notify_alarm, 0);
+        mService.setIcon("alarm_clock", R.drawable.stat_notify_alarm, 0, null);
         mService.setIconVisibility("alarm_clock", false);
 
         // Sync state
-        mService.setIcon("sync_active", com.android.internal.R.drawable.stat_notify_sync_anim0, 0);
-        mService.setIcon("sync_failing", com.android.internal.R.drawable.stat_notify_sync_error, 0);
+        mService.setIcon("sync_active", com.android.internal.R.drawable.stat_notify_sync_anim0,
+                0, null);
+        mService.setIcon("sync_failing", com.android.internal.R.drawable.stat_notify_sync_error,
+                0, null);
         mService.setIconVisibility("sync_active", false);
         mService.setIconVisibility("sync_failing", false);
 
         // volume
-        mService.setIcon("volume", R.drawable.stat_sys_ringer_silent, 0);
+        mService.setIcon("volume", R.drawable.stat_sys_ringer_silent, 0, null);
         mService.setIconVisibility("volume", false);
         updateVolume();
 
@@ -655,7 +664,8 @@
     private final void updateBattery(Intent intent) {
         final int id = intent.getIntExtra("icon-small", 0);
         int level = intent.getIntExtra("level", 0);
-        mService.setIcon("battery", id, level);
+        String contentDescription = mContext.getString(R.string.accessibility_battery_level, level);
+        mService.setIcon("battery", id, level, contentDescription);
     }
 
     private void updateConnectivity(Intent intent) {
@@ -677,12 +687,16 @@
             if (info.isConnected()) {
                 mIsWifiConnected = true;
                 int iconId;
+                String contentDescription = null;
                 if (mLastWifiSignalLevel == -1) {
                     iconId = sWifiSignalImages[mInetCondition][0];
+                    contentDescription = mContext.getString(sWifiConnectionStrength[0]);
                 } else {
                     iconId = sWifiSignalImages[mInetCondition][mLastWifiSignalLevel];
-                }
-                mService.setIcon("wifi", iconId, 0);
+                    contentDescription = mContext.getString(
+                            sWifiConnectionStrength[mLastWifiSignalLevel]);
+                } 
+                mService.setIcon("wifi", iconId, 0, contentDescription);
                 // Show the icon since wi-fi is connected
                 mService.setIconVisibility("wifi", true);
             } else {
@@ -690,7 +704,8 @@
                 mIsWifiConnected = false;
                 int iconId = sWifiSignalImages[0][0];
 
-                mService.setIcon("wifi", iconId, 0);
+                String contentDescription = mContext.getString(R.string.accessibility_no_wifi);
+                mService.setIcon("wifi", iconId, 0, contentDescription);
                 // Hide the icon since we're not connected
                 mService.setIconVisibility("wifi", false);
             }
@@ -781,6 +796,7 @@
 
     private final void updateSignalStrength() {
         int[] iconList;
+        String contentDescription = null;
 
         // Display signal strength while in "emergency calls only" mode
         if (mServiceState == null || (!hasService() && !mServiceState.isEmergencyOnly())) {
@@ -788,10 +804,12 @@
             if (Settings.System.getInt(mContext.getContentResolver(),
                     Settings.System.AIRPLANE_MODE_ON, 0) == 1) {
                 mPhoneSignalIconId = R.drawable.stat_sys_signal_flightmode;
+                contentDescription = mContext.getString(R.string.accessibility_airplane_mode);
             } else {
                 mPhoneSignalIconId = R.drawable.stat_sys_signal_null;
+                contentDescription = mContext.getString(R.string.accessibility_no_phone);
             }
-            mService.setIcon("phone_signal", mPhoneSignalIconId, 0);
+            mService.setIcon("phone_signal", mPhoneSignalIconId, 0, contentDescription);
             return;
         }
 
@@ -805,8 +823,11 @@
         } else {
             iconList = sSignalImages[mInetCondition];
         }
-        mPhoneSignalIconId = iconList[mSignalStrength.getLevel()];
-        mService.setIcon("phone_signal", mPhoneSignalIconId, 0);
+
+        final int signalLevel = mSignalStrength.getLevel();
+        mPhoneSignalIconId = iconList[signalLevel];
+        contentDescription = mContext.getString(sPhoneSignalStrength[signalLevel]);
+        mService.setIcon("phone_signal", mPhoneSignalIconId, 0, contentDescription);
     }
 
     private final void updateDataNetType(int net) {
@@ -850,6 +871,7 @@
 
     private final void updateDataIcon() {
         int iconId;
+        String contentDescription = null;
         boolean visible = true;
 
         if (!isCdma()) {
@@ -870,13 +892,15 @@
                             iconId = mDataIconList[0];
                             break;
                     }
-                    mService.setIcon("data_connection", iconId, 0);
+                    contentDescription = mContext.getString(sDataConnectionStrength[mDataActivity]);
+                    mService.setIcon("data_connection", iconId, 0, contentDescription);
                 } else {
                     visible = false;
                 }
             } else {
                 iconId = R.drawable.stat_sys_no_sim;
-                mService.setIcon("data_connection", iconId, 0);
+                contentDescription = mContext.getString(R.string.accessibility_no_sim);
+                mService.setIcon("data_connection", iconId, 0, contentDescription);
             }
         } else {
             // CDMA case, mDataActivity can be also DATA_ACTIVITY_DORMANT
@@ -896,7 +920,7 @@
                         iconId = mDataIconList[0];
                         break;
                 }
-                mService.setIcon("data_connection", iconId, 0);
+                mService.setIcon("data_connection", iconId, 0, null);
             } else {
                 visible = false;
             }
@@ -921,12 +945,19 @@
         final int ringerMode = audioManager.getRingerMode();
         final boolean visible = ringerMode == AudioManager.RINGER_MODE_SILENT ||
                 ringerMode == AudioManager.RINGER_MODE_VIBRATE;
-        final int iconId = audioManager.shouldVibrate(AudioManager.VIBRATE_TYPE_RINGER)
-                ? R.drawable.stat_sys_ringer_vibrate
-                : R.drawable.stat_sys_ringer_silent;
+
+        final int iconId;
+        String contentDescription = null;
+        if (audioManager.shouldVibrate(AudioManager.VIBRATE_TYPE_RINGER)) {
+            iconId = R.drawable.stat_sys_ringer_vibrate;
+            contentDescription = mContext.getString(R.string.accessibility_ringer_vibrate);
+        } else {
+            iconId =  R.drawable.stat_sys_ringer_silent;
+            contentDescription = mContext.getString(R.string.accessibility_ringer_silent);
+        }
 
         if (visible) {
-            mService.setIcon("volume", iconId, 0);
+            mService.setIcon("volume", iconId, 0, contentDescription);
         }
         if (visible != mVolumeVisible) {
             mService.setIconVisibility("volume", visible);
@@ -936,6 +967,7 @@
 
     private final void updateBluetooth(Intent intent) {
         int iconId = R.drawable.stat_sys_data_bluetooth;
+        String contentDescription = null;
         String action = intent.getAction();
         if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
             int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR);
@@ -945,12 +977,16 @@
                 BluetoothAdapter.STATE_DISCONNECTED);
             if (state == BluetoothAdapter.STATE_CONNECTED) {
                 iconId = R.drawable.stat_sys_data_bluetooth_connected;
+                contentDescription = mContext.getString(R.string.accessibility_bluetooth_connected);
+            } else {
+                contentDescription = mContext.getString(
+                        R.string.accessibility_bluetooth_disconnected);
             }
         } else {
             return;
         }
 
-        mService.setIcon("bluetooth", iconId, 0);
+        mService.setIcon("bluetooth", iconId, 0, contentDescription);
         mService.setIconVisibility("bluetooth", mBluetoothEnabled);
     }
 
@@ -974,6 +1010,7 @@
             }
         } else if (action.equals(WifiManager.RSSI_CHANGED_ACTION)) {
             int iconId;
+            String contentDescription = null;
             final int newRssi = intent.getIntExtra(WifiManager.EXTRA_NEW_RSSI, -200);
             int newSignalLevel = WifiManager.calculateSignalLevel(newRssi,
                                                                   sWifiSignalImages[0].length);
@@ -981,10 +1018,13 @@
                 mLastWifiSignalLevel = newSignalLevel;
                 if (mIsWifiConnected) {
                     iconId = sWifiSignalImages[mInetCondition][newSignalLevel];
+                    contentDescription = mContext.getString(
+                            sWifiConnectionStrength[newSignalLevel]);
                 } else {
                     iconId = sWifiTemporarilyNotConnectedImage;
+                    contentDescription = mContext.getString(R.string.accessibility_no_wifi);
                 }
-                mService.setIcon("wifi", iconId, 0);
+                mService.setIcon("wifi", iconId, 0, contentDescription);
             }
         }
     }
@@ -995,14 +1035,16 @@
 
         if (action.equals(LocationManager.GPS_FIX_CHANGE_ACTION) && enabled) {
             // GPS is getting fixes
-            mService.setIcon("gps", com.android.internal.R.drawable.stat_sys_gps_on, 0);
+            mService.setIcon("gps", com.android.internal.R.drawable.stat_sys_gps_on, 0,
+                    mContext.getString(R.string.accessibility_gps_enabled));
             mService.setIconVisibility("gps", true);
         } else if (action.equals(LocationManager.GPS_ENABLED_CHANGE_ACTION) && !enabled) {
             // GPS is off
             mService.setIconVisibility("gps", false);
         } else {
             // GPS is on, but not receiving fixes
-            mService.setIcon("gps", R.drawable.stat_sys_gps_acquiring_anim, 0);
+            mService.setIcon("gps", R.drawable.stat_sys_gps_acquiring_anim, 0,
+                    mContext.getString(R.string.accessibility_gps_acquiring));
             mService.setIconVisibility("gps", true);
         }
     }
@@ -1016,7 +1058,8 @@
         if (enabled) {
             // TTY is on
             if (false) Slog.v(TAG, "updateTTY: set TTY on");
-            mService.setIcon("tty", R.drawable.stat_sys_tty_mode, 0);
+            mService.setIcon("tty", R.drawable.stat_sys_tty_mode, 0,
+                    mContext.getString(R.string.accessibility_tty_enabled));
             mService.setIconVisibility("tty", true);
         } else {
             // TTY is off
@@ -1058,15 +1101,15 @@
 
         switch (iconMode) {
             case EriInfo.ROAMING_ICON_MODE_NORMAL:
-                mService.setIcon("cdma_eri", iconList[iconIndex], 0);
+                mService.setIcon("cdma_eri", iconList[iconIndex], 0, null);
                 mService.setIconVisibility("cdma_eri", true);
                 break;
             case EriInfo.ROAMING_ICON_MODE_FLASH:
-                mService.setIcon("cdma_eri", R.drawable.stat_sys_roaming_cdma_flash, 0);
+                mService.setIcon("cdma_eri", R.drawable.stat_sys_roaming_cdma_flash, 0, null);
                 mService.setIconVisibility("cdma_eri", true);
                 break;
 
         }
-        mService.setIcon("phone_signal", mPhoneSignalIconId, 0);
+        mService.setIcon("phone_signal", mPhoneSignalIconId, 0, null);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
index 84c524a..c2390e8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
@@ -26,6 +26,7 @@
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.ViewParent;
+import android.view.accessibility.AccessibilityEvent;
 import android.widget.FrameLayout;
 
 import com.android.systemui.R;
@@ -203,5 +204,19 @@
         return mService.interceptTouchEvent(event)
                 ? true : super.onInterceptTouchEvent(event);
     }
-}
 
+    @Override
+    public boolean onRequestSendAccessibilityEvent(View child, AccessibilityEvent event) {
+        if (super.onRequestSendAccessibilityEvent(child, event)) {
+            // The status bar is very small so augment the view that the user is touching
+            // with the content of the status bar a whole. This way an accessibility service
+            // may announce the current item as well as the entire content if appropriate.
+            AccessibilityEvent record = AccessibilityEvent.obtain();
+            onInitializeAccessibilityEvent(record);
+            dispatchPopulateAccessibilityEvent(record);
+            event.appendRecord(record);
+            return true;
+        }
+        return false;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/Ticker.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/Ticker.java
index 8ee12de..e76fe51 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/Ticker.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/Ticker.java
@@ -183,7 +183,8 @@
         }
 
         final Drawable icon = StatusBarIconView.getIcon(mContext,
-                new StatusBarIcon(n.pkg, n.notification.icon, n.notification.iconLevel, 0));
+                new StatusBarIcon(n.pkg, n.notification.icon, n.notification.iconLevel, 0,
+                        n.notification.tickerText));
         final Segment newSegment = new Segment(n, icon, n.notification.tickerText);
 
         // If there's already a notification schedule for this package and id, remove it.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessibilityContentDescriptions.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessibilityContentDescriptions.java
new file mode 100644
index 0000000..13fb03e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessibilityContentDescriptions.java
@@ -0,0 +1,37 @@
+// Copyright 2011 Google Inc. All Rights Reserved.
+
+package com.android.systemui.statusbar.policy;
+
+import com.android.systemui.R;
+
+/**
+ * Content descriptions for accessibility support.
+ */
+public class AccessibilityContentDescriptions {
+
+    private AccessibilityContentDescriptions() {}
+    
+    static final int[] PHONE_SIGNAL_STRENGTH = {
+        R.string.accessibility_no_phone,
+        R.string.accessibility_phone_one_bar,
+        R.string.accessibility_phone_two_bars,
+        R.string.accessibility_phone_three_bars,
+        R.string.accessibility_phone_signal_full
+    };
+
+    static final int[] DATA_CONNECTION_STRENGTH = {
+        R.string.accessibility_no_data,
+        R.string.accessibility_data_one_bar,
+        R.string.accessibility_data_two_bars,
+        R.string.accessibility_data_three_bars,
+        R.string.accessibility_data_signal_full
+    };
+
+    static final int[] WIFI_CONNECTION_STRENGTH = {
+        R.string.accessibility_no_wifi,
+        R.string.accessibility_wifi_one_bar,
+        R.string.accessibility_wifi_two_bars,
+        R.string.accessibility_wifi_three_bars,
+        R.string.accessibility_wifi_signal_full
+    };
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
index ae2b6b2..3957c1b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
@@ -62,12 +62,15 @@
                 ImageView v = mIconViews.get(i);
                 v.setImageResource(icon);
                 v.setImageLevel(level);
+                v.setContentDescription(mContext.getString(R.string.accessibility_battery_level,
+                        level));
             }
             N = mLabelViews.size();
             for (int i=0; i<N; i++) {
                 //final boolean plugged = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0) != 0;
                 TextView v = mLabelViews.get(i);
-                v.setText(mContext.getString(R.string.status_bar_settings_battery_meter_format, level));
+                v.setText(mContext.getString(R.string.status_bar_settings_battery_meter_format,
+                        level));
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothController.java
index 0525054..c6f416f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothController.java
@@ -19,12 +19,10 @@
 import java.util.ArrayList;
 
 import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.BluetoothDevice;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
-import android.util.Slog;
 import android.view.View;
 import android.widget.ImageView;
 
@@ -54,26 +52,24 @@
 
     @Override
     public void onReceive(Context context, Intent intent) {
-        String action = intent.getAction();
-        if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
-            int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR);
-            mEnabled = state == BluetoothAdapter.STATE_ON;
-        } else if (action.equals(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED)) {
-            int state = intent.getIntExtra(BluetoothAdapter.EXTRA_CONNECTION_STATE,
+        int state = intent.getIntExtra(BluetoothAdapter.EXTRA_CONNECTION_STATE,
                 BluetoothAdapter.STATE_DISCONNECTED);
-            if (state == BluetoothAdapter.STATE_CONNECTED) {
-                mIconId = R.drawable.stat_sys_data_bluetooth_connected;
-            } else {
-                mIconId = R.drawable.stat_sys_data_bluetooth;
-            }
-        }
+        int contentDescriptionResId = 0;
 
+        if (state == BluetoothAdapter.STATE_CONNECTED) {
+            mIconId = R.drawable.stat_sys_data_bluetooth_connected;
+            contentDescriptionResId = R.string.accessibility_bluetooth_connected;
+        } else {
+            mIconId = R.drawable.stat_sys_data_bluetooth;
+            contentDescriptionResId = R.string.accessibility_bluetooth_disconnected;
+        }
 
         int N = mIconViews.size();
         for (int i=0; i<N; i++) {
             ImageView v = mIconViews.get(i);
             v.setImageResource(mIconId);
             v.setVisibility(mEnabled ? View.VISIBLE : View.GONE);
+            v.setContentDescription(mContext.getString(contentDescriptionResId));
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java
index 3175a99..829855b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java
@@ -28,14 +28,11 @@
 import android.content.res.Resources;
 import android.net.ConnectivityManager;
 import android.net.NetworkInfo;
-import android.net.wifi.SupplicantState;
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiInfo;
 import android.net.wifi.WifiManager;
 import android.os.Binder;
 import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Looper;
 import android.os.Message;
 import android.os.Messenger;
 import android.os.RemoteException;
@@ -87,6 +84,11 @@
     int mDataTypeIconId;
     boolean mDataActive;
 
+    String mContentDescriptionPhoneSignal;
+    String mContentDescriptionWifi;
+    String mContentDescriptionCombinedSignal;
+    String mContentDescriptionDataType;
+
     // wifi
     final WifiManager mWifiManager;
     AsyncChannel mWifiChannel;
@@ -366,6 +368,8 @@
             if (mSignalStrength == null) {
                 mPhoneSignalIconId = R.drawable.stat_sys_signal_null;
                 mDataSignalIconId = R.drawable.stat_sys_signal_0; // note we use 0 instead of null
+                mContentDescriptionPhoneSignal = mContext.getString(
+                        AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[0]);
             } else {
                 int iconLevel;
                 int[] iconList;
@@ -385,6 +389,9 @@
                     }
                 }
                 mPhoneSignalIconId = iconList[iconLevel];
+                mContentDescriptionPhoneSignal = mContext.getString(
+                        AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[iconLevel]);
+
                 mDataSignalIconId = TelephonyIcons.DATA_SIGNAL_STRENGTH[mInetCondition][iconLevel];
             }
         }
@@ -395,14 +402,20 @@
             case TelephonyManager.NETWORK_TYPE_UNKNOWN:
                 mDataIconList = TelephonyIcons.DATA_G[mInetCondition];
                 mDataTypeIconId = 0;
+                mContentDescriptionDataType = mContext.getString(
+                        R.string.accessibility_data_connection_gprs);
                 break;
             case TelephonyManager.NETWORK_TYPE_EDGE:
                 mDataIconList = TelephonyIcons.DATA_E[mInetCondition];
                 mDataTypeIconId = R.drawable.stat_sys_signal_edge;
+                mContentDescriptionDataType = mContext.getString(
+                        R.string.accessibility_data_connection_edge);
                 break;
             case TelephonyManager.NETWORK_TYPE_UMTS:
                 mDataIconList = TelephonyIcons.DATA_3G[mInetCondition];
                 mDataTypeIconId = R.drawable.stat_sys_signal_3g;
+                mContentDescriptionDataType = mContext.getString(
+                        R.string.accessibility_data_connection_3g);
                 break;
             case TelephonyManager.NETWORK_TYPE_HSDPA:
             case TelephonyManager.NETWORK_TYPE_HSUPA:
@@ -410,19 +423,27 @@
                 if (mHspaDataDistinguishable) {
                     mDataIconList = TelephonyIcons.DATA_H[mInetCondition];
                     mDataTypeIconId = R.drawable.stat_sys_signal_hsdpa;
+                    mContentDescriptionDataType = mContext.getString(
+                            R.string.accessibility_data_connection_3_5g);
                 } else {
                     mDataIconList = TelephonyIcons.DATA_3G[mInetCondition];
                     mDataTypeIconId = R.drawable.stat_sys_signal_3g;
+                    mContentDescriptionDataType = mContext.getString(
+                            R.string.accessibility_data_connection_3g);
                 }
                 break;
             case TelephonyManager.NETWORK_TYPE_CDMA:
                 // display 1xRTT for IS95A/B
                 mDataIconList = TelephonyIcons.DATA_1X[mInetCondition];
                 mDataTypeIconId = R.drawable.stat_sys_signal_1x;
+                mContentDescriptionDataType = mContext.getString(
+                        R.string.accessibility_data_connection_cdma);
                 break;
             case TelephonyManager.NETWORK_TYPE_1xRTT:
                 mDataIconList = TelephonyIcons.DATA_1X[mInetCondition];
                 mDataTypeIconId = R.drawable.stat_sys_signal_1x;
+                mContentDescriptionDataType = mContext.getString(
+                        R.string.accessibility_data_connection_cdma);
                 break;
             case TelephonyManager.NETWORK_TYPE_EVDO_0: //fall through
             case TelephonyManager.NETWORK_TYPE_EVDO_A:
@@ -430,14 +451,20 @@
             case TelephonyManager.NETWORK_TYPE_EHRPD:
                 mDataIconList = TelephonyIcons.DATA_3G[mInetCondition];
                 mDataTypeIconId = R.drawable.stat_sys_signal_3g;
+                mContentDescriptionDataType = mContext.getString(
+                        R.string.accessibility_data_connection_3g);
                 break;
             case TelephonyManager.NETWORK_TYPE_LTE:
                 mDataIconList = TelephonyIcons.DATA_4G[mInetCondition];
                 mDataTypeIconId = R.drawable.stat_sys_signal_4g;
+                mContentDescriptionDataType = mContext.getString(
+                        R.string.accessibility_data_connection_4g);
                 break;
             default:
                 mDataIconList = TelephonyIcons.DATA_G[mInetCondition];
                 mDataTypeIconId = R.drawable.stat_sys_signal_gprs;
+                mContentDescriptionDataType = mContext.getString(
+                        R.string.accessibility_data_connection_gprs);
                 break;
         }
         if ((isCdma() && isCdmaEri()) || mPhone.isNetworkRoaming()) {
@@ -618,8 +645,11 @@
     private void updateWifiIcons() {
         if (mWifiConnected) {
             mWifiIconId = WifiIcons.WIFI_SIGNAL_STRENGTH[mInetCondition][mWifiLevel];
+            mContentDescriptionWifi = mContext.getString(
+                    AccessibilityContentDescriptions.WIFI_CONNECTION_STRENGTH[mWifiLevel]);
         } else {
             mWifiIconId = WifiIcons.WIFI_SIGNAL_STRENGTH[0][0];
+            mContentDescriptionWifi = mContext.getString(R.string.accessibility_no_wifi);
         }
     }
 
@@ -704,6 +734,7 @@
                 }
             }
             combinedSignalIconId = mWifiIconId;
+            mContentDescriptionCombinedSignal = mContentDescriptionWifi;
             dataTypeIconId = 0;
         } else if (mDataConnected) {
             label = mNetworkName;
@@ -723,22 +754,29 @@
                     break;
             }
             combinedSignalIconId = mDataSignalIconId;
+            mContentDescriptionCombinedSignal = mContentDescriptionDataType;
             dataTypeIconId = mDataTypeIconId;
         } else if (mBluetoothTethered) {
             label = mContext.getString(R.string.bluetooth_tethered);
             combinedSignalIconId = mBluetoothTetherIconId;
+            mContentDescriptionCombinedSignal = mContext.getString(
+                    R.string.accessibility_bluetooth_tether);
             dataTypeIconId = 0;
         } else if (mAirplaneMode &&
                 (mServiceState == null || (!hasService() && !mServiceState.isEmergencyOnly()))) {
             // Only display the flight-mode icon if not in "emergency calls only" mode.
             label = context.getString(R.string.status_bar_settings_signal_meter_disconnected);
             combinedSignalIconId = R.drawable.stat_sys_signal_flightmode;
+            mContentDescriptionCombinedSignal = mContext.getString(
+                    R.string.accessibility_airplane_mode);
             dataTypeIconId = 0;
         } else {
             label = context.getString(R.string.status_bar_settings_signal_meter_disconnected);
             // On devices without mobile radios, we want to show the wifi icon
             combinedSignalIconId =
                 hasMobileDataFeature() ? mDataSignalIconId : mWifiIconId;
+            mContentDescriptionCombinedSignal = hasMobileDataFeature()
+                ? mContentDescriptionDataType : mContentDescriptionWifi;
             dataTypeIconId = 0;
         }
 
@@ -764,6 +802,7 @@
             for (int i=0; i<N; i++) {
                 final ImageView v = mPhoneSignalIconViews.get(i);
                 v.setImageResource(mPhoneSignalIconId);
+                v.setContentDescription(mContentDescriptionPhoneSignal);
             }
         }
 
@@ -774,6 +813,7 @@
             for (int i=0; i<N; i++) {
                 final ImageView v = mDataDirectionIconViews.get(i);
                 v.setImageResource(mDataDirectionIconId);
+                v.setContentDescription(mContentDescriptionDataType);
             }
         }
 
@@ -784,6 +824,7 @@
             for (int i=0; i<N; i++) {
                 final ImageView v = mWifiIconViews.get(i);
                 v.setImageResource(mWifiIconId);
+                v.setContentDescription(mContentDescriptionWifi);
             }
         }
 
@@ -794,6 +835,7 @@
             for (int i=0; i<N; i++) {
                 final ImageView v = mCombinedSignalIconViews.get(i);
                 v.setImageResource(combinedSignalIconId);
+                v.setContentDescription(mContentDescriptionCombinedSignal);
             }
         }
 
@@ -808,6 +850,7 @@
                 } else {
                     v.setVisibility(View.VISIBLE);
                     v.setImageResource(dataTypeIconId);
+                    v.setContentDescription(mContentDescriptionDataType);
                 }
             }
         }
@@ -826,6 +869,7 @@
                 } else {
                     v.setVisibility(View.VISIBLE);
                     v.setImageResource(dataDirectionOverlayIconId);
+                    v.setContentDescription(mContentDescriptionDataType);
                 }
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/CompatModePanel.java b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/CompatModePanel.java
index c62c4ad..8c4ae19 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/CompatModePanel.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/CompatModePanel.java
@@ -22,6 +22,7 @@
 import android.os.RemoteException;
 import android.util.AttributeSet;
 import android.util.Slog;
+import android.view.MotionEvent;
 import android.view.View;
 import android.widget.FrameLayout;
 import android.widget.ImageView;
@@ -90,6 +91,19 @@
         return false;
     }
 
+    @Override
+    public boolean dispatchHoverEvent(MotionEvent event) {
+        // Ignore hover events outside of this panel bounds since such events
+        // generate spurious accessibility events with the panel content when
+        // tapping outside of it, thus confusing the user.
+        final int x = (int) event.getX();
+        final int y = (int) event.getY();
+        if (x >= 0 && x < getWidth() && y >= 0 && y < getHeight()) {
+            return super.dispatchHoverEvent(event);
+        }
+        return true;
+    }
+
     public void setTrigger(View v) {
         mTrigger = v;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/InputMethodsPanel.java b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/InputMethodsPanel.java
index 339e3f3..1e417ac 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/InputMethodsPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/InputMethodsPanel.java
@@ -30,6 +30,7 @@
 import android.util.AttributeSet;
 import android.util.Log;
 import android.util.Pair;
+import android.view.MotionEvent;
 import android.view.View;
 import android.view.inputmethod.InputMethodInfo;
 import android.view.inputmethod.InputMethodManager;
@@ -160,6 +161,19 @@
         }
     }
 
+    @Override
+    public boolean dispatchHoverEvent(MotionEvent event) {
+        // Ignore hover events outside of this panel bounds since such events
+        // generate spurious accessibility events with the panel content when
+        // tapping outside of it, thus confusing the user.
+        final int x = (int) event.getX();
+        final int y = (int) event.getY();
+        if (x >= 0 && x < getWidth() && y >= 0 && y < getHeight()) {
+            return super.dispatchHoverEvent(event);
+        }
+        return true;
+    }
+
     private void updateHardKeyboardEnabled() {
         if (mHardKeyboardAvailable) {
             final boolean checked = mHardKeyboardSwitch.isChecked();
@@ -222,6 +236,7 @@
             itemSubtitle.setText(imiName);
         }
         subtypeIcon.setImageDrawable(icon);
+        subtypeIcon.setContentDescription(itemTitle.getText());
         final String settingsActivity = imi.getSettingsActivity();
         if (!TextUtils.isEmpty(settingsActivity)) {
             settingsIcon.setOnClickListener(new View.OnClickListener() {
@@ -463,4 +478,5 @@
     public interface OnHardKeyboardEnabledChangeListener {
         public void onHardKeyboardEnabledChange(boolean enabled);
     }
+
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/NotificationArea.java b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/NotificationArea.java
new file mode 100644
index 0000000..42bdf3d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/NotificationArea.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.tablet;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.accessibility.AccessibilityEvent;
+import android.widget.LinearLayout;
+
+public class NotificationArea extends LinearLayout {
+
+    public NotificationArea(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    @Override
+    public boolean onRequestSendAccessibilityEvent(View child, AccessibilityEvent event) {
+        if (super.onRequestSendAccessibilityEvent(child, event)) {
+            // The event is coming from a descendant like battery but append
+            // the content of the entire notification area so accessibility
+            // services can choose how to present the content to the user.
+            AccessibilityEvent record = AccessibilityEvent.obtain();
+            onInitializeAccessibilityEvent(record);
+            dispatchPopulateAccessibilityEvent(record);
+            event.appendRecord(record);
+            return true;
+        }
+        return false;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/NotificationPanel.java b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/NotificationPanel.java
index 64a4f16..a316e4b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/NotificationPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/NotificationPanel.java
@@ -20,27 +20,15 @@
 import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
-import android.animation.ValueAnimator;
 import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.Canvas;
 import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
 import android.util.AttributeSet;
 import android.util.Slog;
-import android.view.accessibility.AccessibilityEvent;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
-import android.view.SoundEffectConstants;
 import android.view.View;
 import android.view.ViewGroup;
-import android.view.animation.AccelerateInterpolator;
-import android.widget.FrameLayout;
-import android.widget.FrameLayout;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
 import android.widget.RelativeLayout;
-import android.widget.TextView;
 
 import com.android.systemui.R;
 
@@ -53,8 +41,7 @@
 
     boolean mShowing;
     int mNotificationCount = 0;
-    View mTitleArea;
-    ModeToggle mModeToggle;
+    NotificationPanelTitle mTitleArea;
     View mSettingsButton;
     View mNotificationButton;
     View mNotificationScroller;
@@ -68,48 +55,6 @@
 
     Choreographer mChoreo = new Choreographer();
 
-    static class ModeToggle extends View {
-        NotificationPanel mPanel;
-        View mTitle;
-        public ModeToggle(Context context, AttributeSet attrs) {
-            super(context, attrs);
-        }
-        public void setPanel(NotificationPanel p) {
-            mPanel = p;
-        }
-        public void setTitleArea(View v) {
-            mTitle = v;
-        }
-        @Override
-        public boolean onTouchEvent(MotionEvent e) {
-            final int x = (int)e.getX();
-            final int y = (int)e.getY();
-            switch (e.getAction()) {
-                case MotionEvent.ACTION_DOWN:
-                    mTitle.setPressed(true);
-                    break;
-                case MotionEvent.ACTION_MOVE:
-                    mTitle.setPressed(x >= 0
-                            && x < getWidth()
-                            && y >= 0
-                            && y < getHeight());
-                    break;
-                case MotionEvent.ACTION_CANCEL:
-                    mTitle.setPressed(false);
-                    break;
-                case MotionEvent.ACTION_UP:
-                    if (mTitle.isPressed()) {
-                        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
-                        playSoundEffect(SoundEffectConstants.CLICK);
-                        mPanel.swapPanels();
-                        mTitle.setPressed(false);
-                    }
-                    break;
-            }
-            return true;
-        }
-    }
-
     public NotificationPanel(Context context, AttributeSet attrs) {
         this(context, attrs, 0);
     }
@@ -126,14 +71,11 @@
 
         mContentParent = (ViewGroup)findViewById(R.id.content_parent);
         mContentParent.bringToFront();
-        mTitleArea = findViewById(R.id.title_area);
-        mModeToggle = (ModeToggle) findViewById(R.id.mode_toggle);
-        mModeToggle.setOnClickListener(this);
-        mModeToggle.setPanel(this);
-        mModeToggle.setTitleArea(mTitleArea);
+        mTitleArea = (NotificationPanelTitle) findViewById(R.id.title_area);
+        mTitleArea.setPanel(this);
 
-        mSettingsButton = (ImageView)findViewById(R.id.settings_button);
-        mNotificationButton = (ImageView)findViewById(R.id.notification_button);
+        mSettingsButton = findViewById(R.id.settings_button);
+        mNotificationButton = findViewById(R.id.notification_button);
 
         mNotificationScroller = findViewById(R.id.notification_scroller);
         mContentFrame = (ViewGroup)findViewById(R.id.content_frame);
@@ -185,6 +127,19 @@
         }
     }
 
+    @Override
+    public boolean dispatchHoverEvent(MotionEvent event) {
+        // Ignore hover events outside of this panel bounds since such events
+        // generate spurious accessibility events with the panel content when
+        // tapping outside of it, thus confusing the user.
+        final int x = (int) event.getX();
+        final int y = (int) event.getY();
+        if (x >= 0 && x < getWidth() && y >= 0 && y < getHeight()) {
+            return super.dispatchHoverEvent(event);
+        }
+        return true;
+    }
+
     /*
     @Override
     protected void onLayout(boolean changed, int l, int t, int r, int b) {
@@ -205,7 +160,7 @@
     */
 
     public void onClick(View v) {
-        if (v == mModeToggle) {
+        if (v == mTitleArea) {
             swapPanels();
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/NotificationPanelTitle.java b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/NotificationPanelTitle.java
new file mode 100644
index 0000000..689bc36
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/NotificationPanelTitle.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.tablet;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.SoundEffectConstants;
+import android.view.View;
+import android.view.accessibility.AccessibilityEvent;
+import android.widget.RelativeLayout;
+
+public class NotificationPanelTitle extends RelativeLayout implements View.OnClickListener {
+    private NotificationPanel mPanel;
+
+    public NotificationPanelTitle(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        setOnClickListener(this);
+    }
+
+    public void setPanel(NotificationPanel p) {
+        mPanel = p;
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent e) {
+        switch (e.getAction()) {
+            case MotionEvent.ACTION_DOWN:
+                setPressed(true);
+                break;
+            case MotionEvent.ACTION_MOVE:
+                final int x = (int) e.getX();
+                final int y = (int) e.getY();
+                setPressed(x > 0 && x < getWidth() && y > 0 && y < getHeight());
+                break;
+            case MotionEvent.ACTION_UP:
+                if (isPressed()) {
+                    playSoundEffect(SoundEffectConstants.CLICK);
+                    mPanel.swapPanels();
+                    setPressed(false);
+                }
+                break;
+            case MotionEvent.ACTION_CANCEL:
+                setPressed(false);
+                break;
+        }
+        return true;
+    }
+
+    @Override
+    public void onClick(View v) {
+        if (v == this) {
+            mPanel.swapPanels();
+        }
+    }
+
+    @Override
+    public boolean onRequestSendAccessibilityEvent(View child, AccessibilityEvent event) {
+        if (super.onRequestSendAccessibilityEvent(child, event)) {
+            AccessibilityEvent record = AccessibilityEvent.obtain();
+            onInitializeAccessibilityEvent(record);
+            dispatchPopulateAccessibilityEvent(record);
+            event.appendRecord(record);
+            return true;
+        }
+        return false;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/NotificationPeekPanel.java b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/NotificationPeekPanel.java
index 8b68240..ba28306 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/NotificationPeekPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/NotificationPeekPanel.java
@@ -18,12 +18,9 @@
 
 import android.content.Context;
 import android.util.AttributeSet;
-import android.util.Slog;
 import android.view.MotionEvent;
 import android.widget.RelativeLayout;
 
-import com.android.systemui.R;
-
 public class NotificationPeekPanel extends RelativeLayout implements StatusBarPanel {
     TabletStatusBar mBar;
 
@@ -54,5 +51,17 @@
         mBar.resetNotificationPeekFadeTimer();
         return false;
     }
-}
 
+    @Override
+    public boolean dispatchHoverEvent(MotionEvent event) {
+        // Ignore hover events outside of this panel bounds since such events
+        // generate spurious accessibility events with the panel content when
+        // tapping outside of it, thus confusing the user.
+        final int x = (int) event.getX();
+        final int y = (int) event.getY();
+        if (x >= 0 && x < getWidth() && y >= 0 && y < getHeight()) {
+            return super.dispatchHoverEvent(event);
+        }
+        return true;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBar.java
index df09f84..13846ed 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBar.java
@@ -808,7 +808,8 @@
                 // Update the icon.
                 final StatusBarIcon ic = new StatusBarIcon(notification.pkg,
                         notification.notification.icon, notification.notification.iconLevel,
-                        notification.notification.number);
+                        notification.notification.number,
+                        notification.notification.tickerText);
                 if (!oldEntry.icon.set(ic)) {
                     handleNotificationError(key, notification, "Couldn't update icon: " + ic);
                     return;
@@ -1012,10 +1013,7 @@
 
         mCompatModeButton.refresh();
         if (mCompatModeButton.getVisibility() == View.VISIBLE) {
-            if (DEBUG_COMPAT_HELP
-                    || ! Prefs.read(mContext).getBoolean(Prefs.SHOWN_COMPAT_MODE_HELP, false)) {
                 showCompatibilityHelp();
-            }
         } else {
             hideCompatibilityHelp();
             mCompatModePanel.closePanel();
@@ -1451,13 +1449,15 @@
         }
         // Construct the icon.
         final StatusBarIconView iconView = new StatusBarIconView(mContext,
-                notification.pkg + "/0x" + Integer.toHexString(notification.id));
+                notification.pkg + "/0x" + Integer.toHexString(notification.id),
+                notification.notification);
         iconView.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
 
         final StatusBarIcon ic = new StatusBarIcon(notification.pkg,
                     notification.notification.icon,
                     notification.notification.iconLevel,
-                    notification.notification.number);
+                    notification.notification.number,
+                    notification.notification.tickerText);
         if (!iconView.set(ic)) {
             handleNotificationError(key, notification, "Couldn't attach StatusBarIcon: " + ic);
             return null;
@@ -1501,11 +1501,6 @@
         // alternate behavior in DND mode
         if (mNotificationDNDMode) {
             if (mIconLayout.getChildCount() == 0) {
-                final StatusBarIconView iconView = new StatusBarIconView(mContext, "_dnd");
-                iconView.setImageResource(R.drawable.ic_notification_dnd);
-                iconView.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
-                iconView.setPadding(mIconHPadding, 0, mIconHPadding, 0);
-
                 final Notification dndNotification = new Notification.Builder(mContext)
                     .setContentTitle(mContext.getText(R.string.notifications_off_title))
                     .setContentText(mContext.getText(R.string.notifications_off_text))
@@ -1513,6 +1508,12 @@
                     .setOngoing(true)
                     .getNotification();
 
+                final StatusBarIconView iconView = new StatusBarIconView(mContext, "_dnd",
+                        dndNotification);
+                iconView.setImageResource(R.drawable.ic_notification_dnd);
+                iconView.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
+                iconView.setPadding(mIconHPadding, 0, mIconHPadding, 0);
+
                 mNotificationDNDDummyEntry = new NotificationData.Entry(
                         null,
                         new StatusBarNotification("", 0, "", 0, 0, dndNotification),
@@ -1634,19 +1635,24 @@
         } else {
             if ((sbn.notification.flags & Notification.FLAG_ONGOING_EVENT) == 0) {
                 vetoButton.setVisibility(View.INVISIBLE);
+                vetoButton.setContentDescription("VETO");
             } else {
                 vetoButton.setVisibility(View.GONE);
             }
         }
+        vetoButton.setContentDescription(mContext.getString(
+                R.string.accessibility_remove_notification));
 
         // the large icon
         ImageView largeIcon = (ImageView)row.findViewById(R.id.large_icon);
         if (sbn.notification.largeIcon != null) {
             largeIcon.setImageBitmap(sbn.notification.largeIcon);
+            largeIcon.setContentDescription(sbn.notification.tickerText);
         } else {
             largeIcon.getLayoutParams().width = 0;
             largeIcon.setVisibility(View.INVISIBLE);
         }
+        largeIcon.setContentDescription(sbn.notification.tickerText);
 
         // bind the click event to the content area
         ViewGroup content = (ViewGroup)row.findViewById(R.id.content);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletTicker.java b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletTicker.java
index a8f4262..6045e31 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletTicker.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletTicker.java
@@ -284,7 +284,7 @@
         } else if (n.tickerText != null) {
             group = (ViewGroup)inflater.inflate(R.layout.status_bar_ticker_compat, mWindow, false);
             final Drawable icon = StatusBarIconView.getIcon(mContext,
-                    new StatusBarIcon(notification.pkg, n.icon, n.iconLevel, 0));
+                    new StatusBarIcon(notification.pkg, n.icon, n.iconLevel, 0, n.tickerText));
             ImageView iv = (ImageView)group.findViewById(iconId);
             iv.setImageDrawable(icon);
             iv.setVisibility(View.VISIBLE);