Clean up USB notifications:

Add support for separate USB connected and configuration events

Include both USB connected/disconnected and configuration state
in USB_STATE Intent

Remove redundant USB_CONNECTED and USB_DISCONNECTED Intents
Now we just have the sticky USB_STATE broadcast

Move USB disconnnect rebouncing from Tethering to UsbService

Change-Id: Id13eb0c5d51152d2a538985d680ba1db7d2241dc
Signed-off-by: Mike Lockwood <lockwood@android.com>
diff --git a/services/java/com/android/server/UsbService.java b/services/java/com/android/server/UsbService.java
index b90731f..a2bf75d 100644
--- a/services/java/com/android/server/UsbService.java
+++ b/services/java/com/android/server/UsbService.java
@@ -41,15 +41,33 @@
     private static final String TAG = UsbService.class.getSimpleName();
     private static final boolean LOG = false;
 
-    private static final String USB_CONFIGURATION_MATCH = "DEVPATH=/devices/virtual/switch/usb_configuration";
-    private static final String USB_FUNCTIONS_MATCH = "DEVPATH=/devices/virtual/usb_composite/";
-    private static final String USB_CONFIGURATION_PATH = "/sys/class/switch/usb_configuration/state";
-    private static final String USB_COMPOSITE_CLASS_PATH = "/sys/class/usb_composite";
+    private static final String USB_CONNECTED_MATCH =
+            "DEVPATH=/devices/virtual/switch/usb_connected";
+    private static final String USB_CONFIGURATION_MATCH =
+            "DEVPATH=/devices/virtual/switch/usb_configuration";
+    private static final String USB_FUNCTIONS_MATCH =
+            "DEVPATH=/devices/virtual/usb_composite/";
+    private static final String USB_CONNECTED_PATH =
+            "/sys/class/switch/usb_connected/state";
+    private static final String USB_CONFIGURATION_PATH =
+            "/sys/class/switch/usb_configuration/state";
+    private static final String USB_COMPOSITE_CLASS_PATH =
+            "/sys/class/usb_composite";
 
     private static final int MSG_UPDATE = 0;
 
-    private int mUsbConfig = 0;
-    private int mPreviousUsbConfig = 0;
+    // Delay for debouncing USB disconnects.
+    // We often get rapid connect/disconnect events when enabling USB functions,
+    // which need debouncing.
+    private static final int UPDATE_DELAY = 1000;
+
+    // current connected and configuration state
+    private int mConnected;
+    private int mConfiguration;
+
+    // last broadcasted connected and configuration state
+    private int mLastConnected = -1;
+    private int mLastConfiguration = -1;
 
     // lists of enabled and disabled USB functions
     private final ArrayList<String> mEnabledFunctions = new ArrayList<String>();
@@ -59,8 +77,6 @@
 
     private final Context mContext;
 
-    private PowerManagerService mPowerManager;
-
     private final UEventObserver mUEventObserver = new UEventObserver() {
         @Override
         public void onUEvent(UEventObserver.UEvent event) {
@@ -69,16 +85,23 @@
             }
 
             synchronized (this) {
-                String switchState = event.get("SWITCH_STATE");
-                if (switchState != null) {
+                String name = event.get("SWITCH_NAME");
+                String state = event.get("SWITCH_STATE");
+                if (name != null && state != null) {
                     try {
-                        int newConfig = Integer.parseInt(switchState);
-                        if (newConfig != mUsbConfig) {
-                            mPreviousUsbConfig = mUsbConfig;
-                            mUsbConfig = newConfig;
+                        int intState = Integer.parseInt(state);
+                        if ("usb_connected".equals(name)) {
+                            mConnected = intState;
                             // trigger an Intent broadcast
                             if (mSystemReady) {
-                                update();
+                                // debounce disconnects
+                                update(mConnected == 0);
+                            }
+                        } else if ("usb_configuration".equals(name)) {
+                            mConfiguration = intState;
+                            // trigger an Intent broadcast
+                            if (mSystemReady) {
+                                update(mConnected == 0);
                             }
                         }
                     } catch (NumberFormatException e) {
@@ -112,6 +135,7 @@
         mContext = context;
         init();  // set initial status
 
+        mUEventObserver.startObserving(USB_CONNECTED_MATCH);
         mUEventObserver.startObserving(USB_CONFIGURATION_MATCH);
         mUEventObserver.startObserving(USB_FUNCTIONS_MATCH);
     }
@@ -120,10 +144,15 @@
         char[] buffer = new char[1024];
 
         try {
-            FileReader file = new FileReader(USB_CONFIGURATION_PATH);
+            FileReader file = new FileReader(USB_CONNECTED_PATH);
             int len = file.read(buffer, 0, 1024);
             file.close();
-            mPreviousUsbConfig = mUsbConfig = Integer.valueOf((new String(buffer, 0, len)).trim());
+            mConnected = Integer.valueOf((new String(buffer, 0, len)).trim());
+
+            file = new FileReader(USB_CONFIGURATION_PATH);
+            len = file.read(buffer, 0, 1024);
+            file.close();
+            mConfiguration = Integer.valueOf((new String(buffer, 0, len)).trim());
 
         } catch (FileNotFoundException e) {
             Slog.w(TAG, "This kernel does not have USB configuration switch support");
@@ -190,13 +219,14 @@
                 initHostSupport();
             }
 
-            update();
+            update(false);
             mSystemReady = true;
         }
     }
 
-    private final void update() {
-        mHandler.sendEmptyMessage(MSG_UPDATE);
+    private final void update(boolean delayed) {
+        mHandler.removeMessages(MSG_UPDATE);
+        mHandler.sendEmptyMessageDelayed(MSG_UPDATE, delayed ? UPDATE_DELAY : 0);
     }
 
     private final Handler mHandler = new Handler() {
@@ -215,31 +245,26 @@
             switch (msg.what) {
                 case MSG_UPDATE:
                     synchronized (this) {
-                        final ContentResolver cr = mContext.getContentResolver();
+                        if (mConnected != mLastConnected || mConfiguration != mLastConfiguration) {
 
-                        if (Settings.Secure.getInt(cr,
-                                Settings.Secure.DEVICE_PROVISIONED, 0) == 0) {
-                            Slog.i(TAG, "Device not provisioned, skipping USB broadcast");
-                            return;
-                        }
-                        // Send an Intent containing connected/disconnected state
-                        // and the enabled/disabled state of all USB functions
-                        Intent intent;
-                        boolean usbConnected = (mUsbConfig != 0);
-                        if (usbConnected) {
-                            intent = new Intent(UsbManager.ACTION_USB_CONNECTED);
+                            final ContentResolver cr = mContext.getContentResolver();
+                            if (Settings.Secure.getInt(cr,
+                                    Settings.Secure.DEVICE_PROVISIONED, 0) == 0) {
+                                Slog.i(TAG, "Device not provisioned, skipping USB broadcast");
+                                return;
+                            }
+
+                            mLastConnected = mConnected;
+                            mLastConfiguration = mConfiguration;
+
+                            // send a sticky broadcast containing current USB state
+                            Intent intent = new Intent(UsbManager.ACTION_USB_STATE);
+                            intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
+                            intent.putExtra(UsbManager.USB_CONNECTED, mConnected != 0);
+                            intent.putExtra(UsbManager.USB_CONFIGURATION, mConfiguration);
                             addEnabledFunctions(intent);
-                        } else {
-                            intent = new Intent(UsbManager.ACTION_USB_DISCONNECTED);
+                            mContext.sendStickyBroadcast(intent);
                         }
-                        mContext.sendBroadcast(intent);
-
-                        // send a sticky broadcast for clients interested in both connect and disconnect
-                        intent = new Intent(UsbManager.ACTION_USB_STATE);
-                        intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
-                        intent.putExtra(UsbManager.USB_CONNECTED, usbConnected);
-                        addEnabledFunctions(intent);
-                        mContext.sendStickyBroadcast(intent);
                     }
                     break;
             }