UsbDeviceManager: Fix race condition entering USB accessory mode

When switching USB modes there are often spurious connect and disconnect events
that occur before reenumeration is complete.  There is currently a 1000ms timer
to "debounce" the disconnect events. But with some USB accessories, this timeout
is not long enough, which results in an endless cycle of attempts to enter
USB accessory mode when the phone is connected.

To fix this, we now wait up to 10 seconds for the host to successfully configure
the device when entering USB accessory mode before giving up.
This is separate from the existing debounce timer, so the behavior of the
USB state change broadcasts are not affected.

Bug: 12877769
Change-Id: I7aa61f8a618546d749a7ddfc97bf103029a73d03
diff --git a/services/java/com/android/server/usb/UsbDeviceManager.java b/services/java/com/android/server/usb/UsbDeviceManager.java
index 5a60de0..8a5e291 100644
--- a/services/java/com/android/server/usb/UsbDeviceManager.java
+++ b/services/java/com/android/server/usb/UsbDeviceManager.java
@@ -98,6 +98,13 @@
     // which need debouncing.
     private static final int UPDATE_DELAY = 1000;
 
+    // Time we received a request to enter USB accessory mode
+    private long mAccessoryModeRequestTime = 0;
+
+    // Timeout for entering USB request mode.
+    // Request is cancelled if host does not configure device within 10 seconds.
+    private static final int ACCESSORY_REQUEST_TIMEOUT = 10 * 1000;
+
     private static final String BOOT_MODE_PROPERTY = "ro.bootmode";
 
     private UsbHandler mHandler;
@@ -205,6 +212,8 @@
     }
 
     private void startAccessoryMode() {
+        if (!mHasUsbAccessory) return;
+
         mAccessoryStrings = nativeGetAccessoryStrings();
         boolean enableAudio = (nativeGetAudioMode() == AUDIO_MODE_SOURCE);
         // don't start accessory mode if our mandatory strings have not been set
@@ -223,6 +232,7 @@
         }
 
         if (functions != null) {
+            mAccessoryModeRequestTime = SystemClock.elapsedRealtime();
             setCurrentFunctions(functions, false);
         }
     }
@@ -456,6 +466,8 @@
         }
 
         private void setEnabledFunctions(String functions, boolean makeDefault) {
+            if (DEBUG) Slog.d(TAG, "setEnabledFunctions " + functions
+                    + " makeDefault: " + makeDefault);
 
             // Do not update persystent.sys.usb.config if the device is booted up
             // with OEM specific mode.
@@ -517,9 +529,17 @@
         }
 
         private void updateCurrentAccessory() {
-            if (!mHasUsbAccessory) return;
+            // We are entering accessory mode if we have received a request from the host
+            // and the request has not timed out yet.
+            boolean enteringAccessoryMode =
+                    mAccessoryModeRequestTime > 0 &&
+                        SystemClock.elapsedRealtime() <
+                            mAccessoryModeRequestTime + ACCESSORY_REQUEST_TIMEOUT;
 
-            if (mConfigured) {
+            if (mConfigured && enteringAccessoryMode) {
+                // successfully entered accessory mode
+                mAccessoryModeRequestTime = 0;
+
                 if (mAccessoryStrings != null) {
                     mCurrentAccessory = new UsbAccessory(mAccessoryStrings);
                     Slog.d(TAG, "entering USB accessory mode: " + mCurrentAccessory);
@@ -530,7 +550,7 @@
                 } else {
                     Slog.e(TAG, "nativeGetAccessoryStrings failed");
                 }
-            } else if (!mConnected) {
+            } else if (!enteringAccessoryMode) {
                 // make sure accessory mode is off
                 // and restore default functions
                 Slog.d(TAG, "exited USB accessory mode");
@@ -560,6 +580,8 @@
                 }
             }
 
+            if (DEBUG) Slog.d(TAG, "broadcasting " + intent + " connected: " + mConnected
+                                    + " configured: " + mConfigured);
             mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
         }
 
@@ -599,9 +621,7 @@
                     if (containsFunction(mCurrentFunctions,
                             UsbManager.USB_FUNCTION_ACCESSORY)) {
                         updateCurrentAccessory();
-                    }
-
-                    if (!mConnected) {
+                    } else if (!mConnected) {
                         // restore defaults when USB is disconnected
                         setEnabledFunctions(mDefaultFunctions, false);
                     }