Refactor and clean up USB, add tests

Change UsbManager apis to use long instead of string, to match
usb hal. Change UsbDeviceManager internals to match as well.

Remove isFunctionEnabled and add getEnabledFunctions. Callers
would often call isFunctionEnabled for every possible function
to get the list of functions, so getEnabledFunctions reduces the
number of aidl calls.

Separate out dependencies between UsbHandler and UsbDeviceManager
and staticize the UsbHandler classes. Add unit tests with
mocked out dependencies to test state transitions for UsbHandler.

Bug: 62876645
Test: atest UsbTests
Change-Id: I785c4c24121a70e725de9742c6af50a6bf1baea0
diff --git a/Android.bp b/Android.bp
index 129d676..d5e04f9 100644
--- a/Android.bp
+++ b/Android.bp
@@ -676,6 +676,7 @@
         "android.hardware.vibrator-V1.1-java-constants",
         "android.hardware.wifi-V1.0-java-constants",
         "android.hardware.radio-V1.0-java",
+        "android.hardware.usb.gadget-V1.0-java",
     ],
 
     // Loaded with System.loadLibrary by android.view.textclassifier
diff --git a/cmds/svc/src/com/android/commands/svc/UsbCommand.java b/cmds/svc/src/com/android/commands/svc/UsbCommand.java
index 34f6d7d..3893be4 100644
--- a/cmds/svc/src/com/android/commands/svc/UsbCommand.java
+++ b/cmds/svc/src/com/android/commands/svc/UsbCommand.java
@@ -21,7 +21,6 @@
 import android.hardware.usb.UsbManager;
 import android.os.RemoteException;
 import android.os.ServiceManager;
-import android.os.SystemProperties;
 
 public class UsbCommand extends Svc.Command {
     public UsbCommand() {
@@ -37,41 +36,41 @@
     public String longHelp() {
         return shortHelp() + "\n"
                 + "\n"
-                + "usage: svc usb setFunction [function] [usbDataUnlocked=false]\n"
-                + "         Set the current usb function and optionally the data lock state.\n\n"
+                + "usage: svc usb setFunctions [function]\n"
+                + "         Set the current usb function. If function is blank, sets to charging.\n"
                 + "       svc usb setScreenUnlockedFunctions [function]\n"
-                + "         Sets the functions which, if the device was charging,"
-                    + " become current on screen unlock.\n"
-                + "       svc usb getFunction\n"
-                + "          Gets the list of currently enabled functions\n";
+                + "         Sets the functions which, if the device was charging, become current on"
+                    + "screen unlock. If function is blank, turn off this feature.\n"
+                + "       svc usb getFunctions\n"
+                + "          Gets the list of currently enabled functions\n\n"
+                + "possible values of [function] are any of 'mtp', 'ptp', 'rndis', 'midi'\n";
     }
 
     @Override
     public void run(String[] args) {
-        boolean validCommand = false;
         if (args.length >= 2) {
-            if ("setFunction".equals(args[1])) {
-                IUsbManager usbMgr = IUsbManager.Stub.asInterface(ServiceManager.getService(
-                        Context.USB_SERVICE));
-                boolean unlockData = false;
-                if (args.length >= 4) {
-                    unlockData = Boolean.valueOf(args[3]);
-                }
+            IUsbManager usbMgr = IUsbManager.Stub.asInterface(ServiceManager.getService(
+                    Context.USB_SERVICE));
+            if ("setFunctions".equals(args[1])) {
                 try {
-                    usbMgr.setCurrentFunction((args.length >=3 ? args[2] : null), unlockData);
+                    usbMgr.setCurrentFunctions(UsbManager.usbFunctionsFromString(
+                            args.length >= 3 ? args[2] : ""));
                 } catch (RemoteException e) {
                     System.err.println("Error communicating with UsbManager: " + e);
                 }
                 return;
-            } else if ("getFunction".equals(args[1])) {
-                System.err.println(SystemProperties.get("sys.usb.config"));
+            } else if ("getFunctions".equals(args[1])) {
+                try {
+                    System.err.println(
+                            UsbManager.usbFunctionsToString(usbMgr.getCurrentFunctions()));
+                } catch (RemoteException e) {
+                    System.err.println("Error communicating with UsbManager: " + e);
+                }
                 return;
             } else if ("setScreenUnlockedFunctions".equals(args[1])) {
-                IUsbManager usbMgr = IUsbManager.Stub.asInterface(ServiceManager.getService(
-                        Context.USB_SERVICE));
                 try {
-                    usbMgr.setScreenUnlockedFunctions((args.length >= 3 ? args[2] :
-                            UsbManager.USB_FUNCTION_NONE));
+                    usbMgr.setScreenUnlockedFunctions(UsbManager.usbFunctionsFromString(
+                            args.length >= 3 ? args[2] : ""));
                 } catch (RemoteException e) {
                     System.err.println("Error communicating with UsbManager: " + e);
                 }
diff --git a/core/java/android/hardware/usb/IUsbManager.aidl b/core/java/android/hardware/usb/IUsbManager.aidl
index 398dda1..91bbdc7 100644
--- a/core/java/android/hardware/usb/IUsbManager.aidl
+++ b/core/java/android/hardware/usb/IUsbManager.aidl
@@ -88,18 +88,22 @@
     /* Returns true if the specified USB function is enabled. */
     boolean isFunctionEnabled(String function);
 
-    /* Sets the current USB function as well as whether USB data
-     * (for example, MTP exposed pictures) should be made available
-     * on the USB connection. Unlocking data should only be done with
-     * user involvement, since exposing pictures or other data could
-     * leak sensitive user information.
-     */
+    /* Sets the current USB function. */
+    void setCurrentFunctions(long functions);
+
+    /* Compatibility version of setCurrentFunctions(long). */
     void setCurrentFunction(String function, boolean usbDataUnlocked);
 
+    /* Gets the current USB functions. */
+    long getCurrentFunctions();
+
     /* Sets the screen unlocked USB function(s), which will be set automatically
      * when the screen is unlocked.
      */
-    void setScreenUnlockedFunctions(String function);
+    void setScreenUnlockedFunctions(long functions);
+
+    /* Gets the current screen unlocked functions. */
+    long getScreenUnlockedFunctions();
 
     /* Allow USB debugging from the attached host. If alwaysAllow is true, add the
      * the public key to list of host keys that the user has approved.
diff --git a/core/java/android/hardware/usb/UsbManager.java b/core/java/android/hardware/usb/UsbManager.java
index 7617c2b..8daecac 100644
--- a/core/java/android/hardware/usb/UsbManager.java
+++ b/core/java/android/hardware/usb/UsbManager.java
@@ -28,6 +28,7 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.PackageManager.NameNotFoundException;
+import android.hardware.usb.gadget.V1_0.GadgetFunction;
 import android.os.Bundle;
 import android.os.ParcelFileDescriptor;
 import android.os.Process;
@@ -37,6 +38,8 @@
 import com.android.internal.util.Preconditions;
 
 import java.util.HashMap;
+import java.util.Map;
+import java.util.StringJoiner;
 
 /**
  * This class allows you to access the state of USB and communicate with USB devices.
@@ -70,7 +73,7 @@
      * MTP function is enabled
      * <li> {@link #USB_FUNCTION_PTP} boolean extra indicating whether the
      * PTP function is enabled
-     * <li> {@link #USB_FUNCTION_PTP} boolean extra indicating whether the
+     * <li> {@link #USB_FUNCTION_ACCESSORY} boolean extra indicating whether the
      * accessory function is enabled
      * <li> {@link #USB_FUNCTION_AUDIO_SOURCE} boolean extra indicating whether the
      * audio source function is enabled
@@ -196,8 +199,7 @@
 
     /**
      * A placeholder indicating that no USB function is being specified.
-     * Used to distinguish between selecting no function vs. the default function in
-     * {@link #setCurrentFunction(String)}.
+     * Used for compatibility with old init scripts to indicate no functions vs. charging function.
      *
      * {@hide}
      */
@@ -298,6 +300,69 @@
      */
     public static final String EXTRA_PERMISSION_GRANTED = "permission";
 
+    /**
+     * Code for the charging usb function. Passed into {@link #setCurrentFunctions(long)}
+     * {@hide}
+     */
+    public static final long FUNCTION_NONE = 0;
+
+    /**
+     * Code for the mtp usb function. Passed as a mask into {@link #setCurrentFunctions(long)}
+     * {@hide}
+     */
+    public static final long FUNCTION_MTP = GadgetFunction.MTP;
+
+    /**
+     * Code for the ptp usb function. Passed as a mask into {@link #setCurrentFunctions(long)}
+     * {@hide}
+     */
+    public static final long FUNCTION_PTP = GadgetFunction.PTP;
+
+    /**
+     * Code for the rndis usb function. Passed as a mask into {@link #setCurrentFunctions(long)}
+     * {@hide}
+     */
+    public static final long FUNCTION_RNDIS = GadgetFunction.RNDIS;
+
+    /**
+     * Code for the midi usb function. Passed as a mask into {@link #setCurrentFunctions(long)}
+     * {@hide}
+     */
+    public static final long FUNCTION_MIDI = GadgetFunction.MIDI;
+
+    /**
+     * Code for the accessory usb function.
+     * {@hide}
+     */
+    public static final long FUNCTION_ACCESSORY = GadgetFunction.ACCESSORY;
+
+    /**
+     * Code for the audio source usb function.
+     * {@hide}
+     */
+    public static final long FUNCTION_AUDIO_SOURCE = GadgetFunction.AUDIO_SOURCE;
+
+    /**
+     * Code for the adb usb function.
+     * {@hide}
+     */
+    public static final long FUNCTION_ADB = GadgetFunction.ADB;
+
+    private static final long SETTABLE_FUNCTIONS = FUNCTION_MTP | FUNCTION_PTP | FUNCTION_RNDIS
+            | FUNCTION_MIDI;
+
+    private static final Map<String, Long> FUNCTION_NAME_TO_CODE = new HashMap<>();
+
+    static {
+        FUNCTION_NAME_TO_CODE.put(UsbManager.USB_FUNCTION_MTP, FUNCTION_MTP);
+        FUNCTION_NAME_TO_CODE.put(UsbManager.USB_FUNCTION_PTP, FUNCTION_PTP);
+        FUNCTION_NAME_TO_CODE.put(UsbManager.USB_FUNCTION_RNDIS, FUNCTION_RNDIS);
+        FUNCTION_NAME_TO_CODE.put(UsbManager.USB_FUNCTION_MIDI, FUNCTION_MIDI);
+        FUNCTION_NAME_TO_CODE.put(UsbManager.USB_FUNCTION_ACCESSORY, FUNCTION_ACCESSORY);
+        FUNCTION_NAME_TO_CODE.put(UsbManager.USB_FUNCTION_AUDIO_SOURCE, FUNCTION_AUDIO_SOURCE);
+        FUNCTION_NAME_TO_CODE.put(UsbManager.USB_FUNCTION_ADB, FUNCTION_ADB);
+    }
+
     private final Context mContext;
     private final IUsbManager mService;
 
@@ -548,15 +613,14 @@
      * services offered by the device.
      * </p>
      *
+     * @deprecated use getCurrentFunctions() instead.
      * @param function name of the USB function
      * @return true if the USB function is enabled
      *
      * {@hide}
      */
+    @Deprecated
     public boolean isFunctionEnabled(String function) {
-        if (mService == null) {
-            return false;
-        }
         try {
             return mService.isFunctionEnabled(function);
         } catch (RemoteException e) {
@@ -565,7 +629,7 @@
     }
 
     /**
-     * Sets the current USB function when in device mode.
+     * Sets the current USB functions when in device mode.
      * <p>
      * USB functions represent interfaces which are published to the host to access
      * services offered by the device.
@@ -574,27 +638,59 @@
      * automatically activate additional functions such as {@link #USB_FUNCTION_ADB}
      * or {@link #USB_FUNCTION_ACCESSORY} based on other settings and states.
      * </p><p>
-     * The allowed values are: {@link #USB_FUNCTION_NONE}, {@link #USB_FUNCTION_AUDIO_SOURCE},
-     * {@link #USB_FUNCTION_MIDI}, {@link #USB_FUNCTION_MTP}, {@link #USB_FUNCTION_PTP},
-     * or {@link #USB_FUNCTION_RNDIS}.
-     * </p><p>
-     * Also sets whether USB data (for example, MTP exposed pictures) should be made available
-     * on the USB connection when in device mode. Unlocking usb data should only be done with
-     * user involvement, since exposing pictures or other data could leak sensitive
-     * user information.
+     * An argument of 0 indicates that the device is charging, and can pick any
+     * appropriate function for that purpose.
      * </p><p>
      * Note: This function is asynchronous and may fail silently without applying
      * the requested changes.
      * </p>
      *
-     * @param function name of the USB function, or null to restore the default function
-     * @param usbDataUnlocked whether user data is accessible
+     * @param functions the USB function(s) to set, as a bitwise mask.
+     *                  Must satisfy {@link UsbManager#areSettableFunctions}
      *
      * {@hide}
      */
-    public void setCurrentFunction(String function, boolean usbDataUnlocked) {
+    public void setCurrentFunctions(long functions) {
         try {
-            mService.setCurrentFunction(function, usbDataUnlocked);
+            mService.setCurrentFunctions(functions);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Sets the current USB functions when in device mode.
+     *
+     * @deprecated use setCurrentFunctions(long) instead.
+     * @param functions the USB function(s) to set.
+     * @param usbDataUnlocked unused
+
+     * {@hide}
+     */
+    @Deprecated
+    public void setCurrentFunction(String functions, boolean usbDataUnlocked) {
+        try {
+            mService.setCurrentFunction(functions, usbDataUnlocked);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Returns the current USB functions in device mode.
+     * <p>
+     * This function returns the state of primary USB functions and can return a
+     * mask containing any usb function(s) except for ADB.
+     * </p>
+     *
+     * @return The currently enabled functions, in a bitwise mask.
+     * A zero mask indicates that the current function is the charging function.
+     *
+     * {@hide}
+     */
+    public long getCurrentFunctions() {
+        try {
+            return mService.getCurrentFunctions();
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -604,23 +700,37 @@
      * Sets the screen unlocked functions, which are persisted and set as the current functions
      * whenever the screen is unlocked.
      * <p>
-     * The allowed values are: {@link #USB_FUNCTION_NONE},
-     * {@link #USB_FUNCTION_MIDI}, {@link #USB_FUNCTION_MTP}, {@link #USB_FUNCTION_PTP},
-     * or {@link #USB_FUNCTION_RNDIS}.
-     * {@link #USB_FUNCTION_NONE} has the effect of switching off this feature, so functions
+     * A zero mask has the effect of switching off this feature, so functions
      * no longer change on screen unlock.
      * </p><p>
      * Note: When the screen is on, this method will apply given functions as current functions,
      * which is asynchronous and may fail silently without applying the requested changes.
      * </p>
      *
-     * @param function function to set as default
+     * @param functions functions to set, in a bitwise mask.
+     *                  Must satisfy {@link UsbManager#areSettableFunctions}
      *
      * {@hide}
      */
-    public void setScreenUnlockedFunctions(String function) {
+    public void setScreenUnlockedFunctions(long functions) {
         try {
-            mService.setScreenUnlockedFunctions(function);
+            mService.setScreenUnlockedFunctions(functions);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Gets the current screen unlocked functions.
+     *
+     * @return The currently set screen enabled functions.
+     * A zero mask indicates that the screen unlocked functions feature is not enabled.
+     *
+     * {@hide}
+     */
+    public long getScreenUnlockedFunctions() {
+        try {
+            return mService.getScreenUnlockedFunctions();
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -719,51 +829,71 @@
         }
     }
 
-    /** @hide */
-    public static String addFunction(String functions, String function) {
-        if (USB_FUNCTION_NONE.equals(functions)) {
-            return function;
-        }
-        if (!containsFunction(functions, function)) {
-            if (functions.length() > 0) {
-                functions += ",";
-            }
-            functions += function;
-        }
-        return functions;
+    /**
+     * Returns whether the given functions are valid inputs to UsbManager.
+     * Currently the empty functions or any of MTP, PTP, RNDIS, MIDI are accepted.
+     *
+     * @return Whether the mask is settable.
+     *
+     * {@hide}
+     */
+    public static boolean areSettableFunctions(long functions) {
+        return functions == FUNCTION_NONE
+                || ((~SETTABLE_FUNCTIONS & functions) == 0 && Long.bitCount(functions) == 1);
     }
 
-    /** @hide */
-    public static String removeFunction(String functions, String function) {
-        String[] split = functions.split(",");
-        for (int i = 0; i < split.length; i++) {
-            if (function.equals(split[i])) {
-                split[i] = null;
-            }
+    /**
+     * Converts the given function mask to string. Maintains ordering with respect to init scripts.
+     *
+     * @return String representation of given mask
+     *
+     * {@hide}
+     */
+    public static String usbFunctionsToString(long functions) {
+        StringJoiner joiner = new StringJoiner(",");
+        if ((functions & FUNCTION_MTP) != 0) {
+            joiner.add(UsbManager.USB_FUNCTION_MTP);
         }
-        if (split.length == 1 && split[0] == null) {
-            return USB_FUNCTION_NONE;
+        if ((functions & FUNCTION_PTP) != 0) {
+            joiner.add(UsbManager.USB_FUNCTION_PTP);
         }
-        StringBuilder builder = new StringBuilder();
-        for (int i = 0; i < split.length; i++) {
-            String s = split[i];
-            if (s != null) {
-                if (builder.length() > 0) {
-                    builder.append(",");
-                }
-                builder.append(s);
-            }
+        if ((functions & FUNCTION_RNDIS) != 0) {
+            joiner.add(UsbManager.USB_FUNCTION_RNDIS);
         }
-        return builder.toString();
+        if ((functions & FUNCTION_MIDI) != 0) {
+            joiner.add(UsbManager.USB_FUNCTION_MIDI);
+        }
+        if ((functions & FUNCTION_ACCESSORY) != 0) {
+            joiner.add(UsbManager.USB_FUNCTION_ACCESSORY);
+        }
+        if ((functions & FUNCTION_AUDIO_SOURCE) != 0) {
+            joiner.add(UsbManager.USB_FUNCTION_AUDIO_SOURCE);
+        }
+        if ((functions & FUNCTION_ADB) != 0) {
+            joiner.add(UsbManager.USB_FUNCTION_ADB);
+        }
+        return joiner.toString();
     }
 
-    /** @hide */
-    public static boolean containsFunction(String functions, String function) {
-        int index = functions.indexOf(function);
-        if (index < 0) return false;
-        if (index > 0 && functions.charAt(index - 1) != ',') return false;
-        int charAfter = index + function.length();
-        if (charAfter < functions.length() && functions.charAt(charAfter) != ',') return false;
-        return true;
+    /**
+     * Parses a string of usb functions that are comma separated.
+     *
+     * @return A mask of all valid functions in the string
+     *
+     * {@hide}
+     */
+    public static long usbFunctionsFromString(String functions) {
+        if (functions == null || functions.equals(USB_FUNCTION_NONE)) {
+            return FUNCTION_NONE;
+        }
+        long ret = 0;
+        for (String function : functions.split(",")) {
+            if (FUNCTION_NAME_TO_CODE.containsKey(function)) {
+                ret |= FUNCTION_NAME_TO_CODE.get(function);
+            } else if (function.length() > 0) {
+                throw new IllegalArgumentException("Invalid usb function " + functions);
+            }
+        }
+        return ret;
     }
 }
diff --git a/services/core/java/com/android/server/connectivity/Tethering.java b/services/core/java/com/android/server/connectivity/Tethering.java
index be6c4a1..9a9cdbd 100644
--- a/services/core/java/com/android/server/connectivity/Tethering.java
+++ b/services/core/java/com/android/server/connectivity/Tethering.java
@@ -1095,7 +1095,8 @@
         if (VDBG) Log.d(TAG, "setUsbTethering(" + enable + ")");
         UsbManager usbManager = (UsbManager) mContext.getSystemService(Context.USB_SERVICE);
         synchronized (mPublicSync) {
-            usbManager.setCurrentFunction(enable ? UsbManager.USB_FUNCTION_RNDIS : null, false);
+            usbManager.setCurrentFunctions(enable ? UsbManager.FUNCTION_RNDIS
+                    : UsbManager.FUNCTION_NONE);
         }
         return ConnectivityManager.TETHER_ERROR_NO_ERROR;
     }
diff --git a/services/usb/java/com/android/server/usb/UsbDeviceManager.java b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
index e3e5e3e..1ea2f97 100644
--- a/services/usb/java/com/android/server/usb/UsbDeviceManager.java
+++ b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
@@ -41,7 +41,6 @@
 import android.hardware.usb.UsbManager;
 import android.hardware.usb.UsbPort;
 import android.hardware.usb.UsbPortStatus;
-import android.hardware.usb.gadget.V1_0.GadgetFunction;
 import android.hardware.usb.gadget.V1_0.IUsbGadget;
 import android.hardware.usb.gadget.V1_0.IUsbGadgetCallback;
 import android.hardware.usb.gadget.V1_0.Status;
@@ -68,6 +67,8 @@
 import android.util.Slog;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
 import com.android.internal.notification.SystemNotificationChannels;
 import com.android.internal.os.SomeArgs;
@@ -86,21 +87,20 @@
 import java.util.NoSuchElementException;
 import java.util.Scanner;
 import java.util.Set;
-import java.util.StringJoiner;
 
 /**
  * UsbDeviceManager manages USB state in device mode.
  */
 public class UsbDeviceManager implements ActivityManagerInternal.ScreenObserver {
 
-    private static final String TAG = "UsbDeviceManager";
+    private static final String TAG = UsbDeviceManager.class.getSimpleName();
     private static final boolean DEBUG = false;
 
     /**
      * The SharedPreference setting per user that stores the screen unlocked functions between
      * sessions.
      */
-    private static final String UNLOCKED_CONFIG_PREF = "usb-screen-unlocked-config-%d";
+    static final String UNLOCKED_CONFIG_PREF = "usb-screen-unlocked-config-%d";
 
     /**
      * ro.bootmode value when phone boots into usual Android.
@@ -156,8 +156,6 @@
 
     private static final String ADB_NOTIFICATION_CHANNEL_ID_TV = "usbdevicemanager.adb.tv";
     private UsbHandler mHandler;
-    private boolean mBootCompleted;
-    private boolean mSystemReady;
 
     private final Object mLock = new Object();
 
@@ -165,22 +163,13 @@
     private final ContentResolver mContentResolver;
     @GuardedBy("mLock")
     private UsbProfileGroupSettingsManager mCurrentSettings;
-    private NotificationManager mNotificationManager;
     private final boolean mHasUsbAccessory;
-    private boolean mUseUsbNotification;
-    private boolean mAdbEnabled;
-    private boolean mAudioSourceEnabled;
-    private boolean mMidiEnabled;
-    private int mMidiCard;
-    private int mMidiDevice;
+    @GuardedBy("mLock")
     private String[] mAccessoryStrings;
     private UsbDebuggingManager mDebuggingManager;
-    private final UsbAlsaManager mUsbAlsaManager;
-    private final UsbSettingsManager mSettingsManager;
-    private Intent mBroadcastedIntent;
-    private boolean mPendingBootBroadcast;
+    private final UEventObserver mUEventObserver;
+
     private static Set<Integer> sBlackListedInterfaces;
-    private SharedPreferences mSettings;
 
     static {
         sBlackListedInterfaces = new HashSet<>();
@@ -213,7 +202,7 @@
     /*
      * Listens for uevent messages from the kernel to monitor the USB state
      */
-    private final UEventObserver mUEventObserver = new UEventObserver() {
+    private final class UsbUEventObserver extends UEventObserver {
         @Override
         public void onUEvent(UEventObserver.UEvent event) {
             if (DEBUG) Slog.v(TAG, "USB UEVENT: " + event.toString());
@@ -227,7 +216,7 @@
                 startAccessoryMode();
             }
         }
-    };
+    }
 
     @Override
     public void onKeyguardStateChanged(boolean isShowing) {
@@ -257,8 +246,6 @@
     public UsbDeviceManager(Context context, UsbAlsaManager alsaManager,
             UsbSettingsManager settingsManager) {
         mContext = context;
-        mUsbAlsaManager = alsaManager;
-        mSettingsManager = settingsManager;
         mContentResolver = context.getContentResolver();
         PackageManager pm = mContext.getPackageManager();
         mHasUsbAccessory = pm.hasSystemFeature(PackageManager.FEATURE_USB_ACCESSORY);
@@ -274,16 +261,24 @@
             Slog.i(TAG, "USB GADGET HAL not present in the device", e);
         }
 
+        boolean secureAdbEnabled = SystemProperties.getBoolean("ro.adb.secure", false);
+        boolean dataEncrypted = "1".equals(SystemProperties.get("vold.decrypt"));
+        if (secureAdbEnabled && !dataEncrypted) {
+            mDebuggingManager = new UsbDebuggingManager(context);
+        }
+
         if (halNotPresent) {
             /**
              * Initialze the legacy UsbHandler
              */
-            mHandler = new UsbHandlerLegacy(FgThread.get().getLooper(), mContext);
+            mHandler = new UsbHandlerLegacy(FgThread.get().getLooper(), mContext, this,
+                    mDebuggingManager, alsaManager, settingsManager);
         } else {
             /**
              * Initialize HAL based UsbHandler
              */
-            mHandler = new UsbHandlerHal(FgThread.get().getLooper());
+            mHandler = new UsbHandlerHal(FgThread.get().getLooper(), mContext, this,
+                    mDebuggingManager, alsaManager, settingsManager);
         }
 
         if (nativeIsStartRequested()) {
@@ -291,12 +286,6 @@
             startAccessoryMode();
         }
 
-        boolean secureAdbEnabled = SystemProperties.getBoolean("ro.adb.secure", false);
-        boolean dataEncrypted = "1".equals(SystemProperties.get("vold.decrypt"));
-        if (secureAdbEnabled && !dataEncrypted) {
-            mDebuggingManager = new UsbDebuggingManager(context);
-        }
-
         BroadcastReceiver portReceiver = new BroadcastReceiver() {
             @Override
             public void onReceive(Context context, Intent intent) {
@@ -347,41 +336,35 @@
 
         mContext.registerReceiver(languageChangedReceiver,
                 new IntentFilter(Intent.ACTION_LOCALE_CHANGED));
+
+        // Watch for USB configuration changes
+        mUEventObserver = new UsbUEventObserver();
+        mUEventObserver.startObserving(USB_STATE_MATCH);
+        mUEventObserver.startObserving(ACCESSORY_START_MATCH);
+
+        // register observer to listen for settings changes
+        mContentResolver.registerContentObserver(
+                Settings.Global.getUriFor(Settings.Global.ADB_ENABLED),
+                false, new AdbSettingsObserver());
     }
 
-    private UsbProfileGroupSettingsManager getCurrentSettings() {
+    UsbProfileGroupSettingsManager getCurrentSettings() {
         synchronized (mLock) {
             return mCurrentSettings;
         }
     }
 
+    String[] getAccessoryStrings() {
+        synchronized (mLock) {
+            return mAccessoryStrings;
+        }
+    }
+
     public void systemReady() {
         if (DEBUG) Slog.d(TAG, "systemReady");
 
         LocalServices.getService(ActivityManagerInternal.class).registerScreenObserver(this);
 
-        mNotificationManager = (NotificationManager)
-                mContext.getSystemService(Context.NOTIFICATION_SERVICE);
-
-        // Ensure that the notification channels are set up
-        if (isTv()) {
-            // TV-specific notification channel
-            mNotificationManager.createNotificationChannel(
-                    new NotificationChannel(ADB_NOTIFICATION_CHANNEL_ID_TV,
-                            mContext.getString(
-                                    com.android.internal.R.string
-                                            .adb_debugging_notification_channel_tv),
-                            NotificationManager.IMPORTANCE_HIGH));
-        }
-
-        // We do not show the USB notification if the primary volume supports mass storage.
-        // The legacy mass storage UI will be used instead.
-        boolean massStorageSupported;
-        final StorageManager storageManager = StorageManager.from(mContext);
-        final StorageVolume primary = storageManager.getPrimaryVolume();
-        massStorageSupported = primary != null && primary.allowMassStorage();
-        mUseUsbNotification = !massStorageSupported && mContext.getResources().getBoolean(
-                com.android.internal.R.bool.config_usbChargingMessage);
         mHandler.sendEmptyMessage(MSG_SYSTEM_READY);
     }
 
@@ -410,21 +393,19 @@
         boolean enableAccessory = (mAccessoryStrings != null &&
                 mAccessoryStrings[UsbAccessory.MANUFACTURER_STRING] != null &&
                 mAccessoryStrings[UsbAccessory.MODEL_STRING] != null);
-        String functions = null;
 
-        if (enableAccessory && enableAudio) {
-            functions = UsbManager.USB_FUNCTION_ACCESSORY + ","
-                    + UsbManager.USB_FUNCTION_AUDIO_SOURCE;
-        } else if (enableAccessory) {
-            functions = UsbManager.USB_FUNCTION_ACCESSORY;
-        } else if (enableAudio) {
-            functions = UsbManager.USB_FUNCTION_AUDIO_SOURCE;
+        long functions = UsbManager.FUNCTION_NONE;
+        if (enableAccessory) {
+            functions |= UsbManager.FUNCTION_ACCESSORY;
+        }
+        if (enableAudio) {
+            functions |= UsbManager.FUNCTION_AUDIO_SOURCE;
         }
 
-        if (functions != null) {
+        if (functions != UsbManager.FUNCTION_NONE) {
             mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_ACCESSORY_MODE_ENTER_TIMEOUT),
                     ACCESSORY_REQUEST_TIMEOUT);
-            setCurrentFunctions(functions, false);
+            setCurrentFunctions(functions);
         }
     }
 
@@ -451,19 +432,7 @@
         }
     }
 
-    private boolean isTv() {
-        return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK);
-    }
-
-    private SharedPreferences getPinnedSharedPrefs(Context context) {
-        final File prefsFile = new File(new File(
-                Environment.getDataUserCePackageDirectory(StorageManager.UUID_PRIVATE_INTERNAL,
-                        context.getUserId(), context.getPackageName()), "shared_prefs"),
-                UsbDeviceManager.class.getSimpleName() + ".xml");
-        return context.getSharedPreferences(prefsFile, Context.MODE_PRIVATE);
-    }
-
-    private abstract class UsbHandler extends Handler {
+    abstract static class UsbHandler extends Handler {
 
         // current USB state
         private boolean mConnected;
@@ -471,21 +440,40 @@
         private boolean mSourcePower;
         private boolean mSinkPower;
         private boolean mConfigured;
-        protected boolean mUsbDataUnlocked;
         private boolean mAudioAccessoryConnected;
         private boolean mAudioAccessorySupported;
-        protected String mCurrentFunctions;
-        protected boolean mCurrentFunctionsApplied;
+
         private UsbAccessory mCurrentAccessory;
         private int mUsbNotificationId;
         private boolean mAdbNotificationShown;
-        private int mCurrentUser;
         private boolean mUsbCharging;
         private boolean mHideUsbNotification;
         private boolean mSupportsAllCombinations;
-        private String mScreenUnlockedFunctions = UsbManager.USB_FUNCTION_NONE;
         private boolean mScreenLocked;
-        protected boolean mCurrentUsbFunctionsRequested;
+        private boolean mSystemReady;
+        private Intent mBroadcastedIntent;
+        private boolean mPendingBootBroadcast;
+        private boolean mAudioSourceEnabled;
+        private boolean mMidiEnabled;
+        private int mMidiCard;
+        private int mMidiDevice;
+
+        private final Context mContext;
+        private final UsbDebuggingManager mDebuggingManager;
+        private final UsbAlsaManager mUsbAlsaManager;
+        private final UsbSettingsManager mSettingsManager;
+        private NotificationManager mNotificationManager;
+
+        protected long mScreenUnlockedFunctions;
+        protected boolean mAdbEnabled;
+        protected boolean mBootCompleted;
+        protected boolean mCurrentFunctionsApplied;
+        protected boolean mUseUsbNotification;
+        protected long mCurrentFunctions;
+        protected final UsbDeviceManager mUsbDeviceManager;
+        protected final ContentResolver mContentResolver;
+        protected SharedPreferences mSettings;
+        protected int mCurrentUser;
         protected boolean mCurrentUsbFunctionsReceived;
 
         /**
@@ -494,31 +482,36 @@
          */
         protected static final String USB_PERSISTENT_CONFIG_PROPERTY = "persist.sys.usb.config";
 
-        public UsbHandler(Looper looper) {
+        UsbHandler(Looper looper, Context context, UsbDeviceManager deviceManager,
+                UsbDebuggingManager debuggingManager, UsbAlsaManager alsaManager,
+                UsbSettingsManager settingsManager) {
             super(looper);
+            mContext = context;
+            mDebuggingManager = debuggingManager;
+            mUsbDeviceManager = deviceManager;
+            mUsbAlsaManager = alsaManager;
+            mSettingsManager = settingsManager;
+            mContentResolver = context.getContentResolver();
 
             mCurrentUser = ActivityManager.getCurrentUser();
+            mScreenUnlockedFunctions = UsbManager.FUNCTION_NONE;
             mScreenLocked = true;
 
             /*
              * Use the normal bootmode persistent prop to maintain state of adb across
              * all boot modes.
              */
-            mAdbEnabled = UsbManager.containsFunction(
-                    SystemProperties.get(USB_PERSISTENT_CONFIG_PROPERTY),
-                    UsbManager.USB_FUNCTION_ADB);
+            mAdbEnabled = (UsbManager.usbFunctionsFromString(getSystemProperty(
+                    USB_PERSISTENT_CONFIG_PROPERTY, "")) & UsbManager.FUNCTION_ADB) != 0;
 
-            /*
-             * Previous versions can set persist config to mtp/ptp but it does not
-             * get reset on OTA. Reset the property here instead.
-             */
-            String persisted = SystemProperties.get(USB_PERSISTENT_CONFIG_PROPERTY);
-            if (UsbManager.containsFunction(persisted, UsbManager.USB_FUNCTION_MTP)
-                    || UsbManager.containsFunction(persisted, UsbManager.USB_FUNCTION_PTP)) {
-                SystemProperties.set(USB_PERSISTENT_CONFIG_PROPERTY,
-                        UsbManager.removeFunction(UsbManager.removeFunction(persisted,
-                                UsbManager.USB_FUNCTION_MTP), UsbManager.USB_FUNCTION_PTP));
-            }
+            // We do not show the USB notification if the primary volume supports mass storage.
+            // The legacy mass storage UI will be used instead.
+            final StorageManager storageManager = StorageManager.from(mContext);
+            final StorageVolume primary = storageManager.getPrimaryVolume();
+
+            boolean massStorageSupported = primary != null && primary.allowMassStorage();
+            mUseUsbNotification = !massStorageSupported && mContext.getResources().getBoolean(
+                    com.android.internal.R.bool.config_usbChargingMessage);
         }
 
         public void sendMessage(int what, boolean arg) {
@@ -602,20 +595,14 @@
             if (DEBUG) Slog.d(TAG, "setAdbEnabled: " + enable);
             if (enable != mAdbEnabled) {
                 mAdbEnabled = enable;
-                String oldFunctions = mCurrentFunctions;
 
-                // Persist the adb setting
-                String newFunction = applyAdbFunction(SystemProperties.get(
-                        USB_PERSISTENT_CONFIG_PROPERTY, UsbManager.USB_FUNCTION_NONE));
-                SystemProperties.set(USB_PERSISTENT_CONFIG_PROPERTY, newFunction);
-
-                // Remove mtp from the config if file transfer is not enabled
-                if (oldFunctions.equals(UsbManager.USB_FUNCTION_MTP) &&
-                        !mUsbDataUnlocked && enable) {
-                    oldFunctions = UsbManager.USB_FUNCTION_NONE;
+                if (enable) {
+                    setSystemProperty(USB_PERSISTENT_CONFIG_PROPERTY, UsbManager.USB_FUNCTION_ADB);
+                } else {
+                    setSystemProperty(USB_PERSISTENT_CONFIG_PROPERTY, "");
                 }
 
-                setEnabledFunctions(oldFunctions, true, mUsbDataUnlocked);
+                setEnabledFunctions(mCurrentFunctions, true);
                 updateAdbNotification(false);
             }
 
@@ -624,21 +611,7 @@
             }
         }
 
-        protected String applyAdbFunction(String functions) {
-            // Do not pass null pointer to the UsbManager.
-            // There isnt a check there.
-            if (functions == null) {
-                functions = "";
-            }
-            if (mAdbEnabled) {
-                functions = UsbManager.addFunction(functions, UsbManager.USB_FUNCTION_ADB);
-            } else {
-                functions = UsbManager.removeFunction(functions, UsbManager.USB_FUNCTION_ADB);
-            }
-            return functions;
-        }
-
-        private boolean isUsbTransferAllowed() {
+        protected boolean isUsbTransferAllowed() {
             UserManager userManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
             return !userManager.hasUserRestriction(UserManager.DISALLOW_USB_FILE_TRANSFER);
         }
@@ -650,12 +623,13 @@
 
             if (mConfigured && enteringAccessoryMode) {
                 // successfully entered accessory mode
-                if (mAccessoryStrings != null) {
-                    mCurrentAccessory = new UsbAccessory(mAccessoryStrings);
+                String[] accessoryStrings = mUsbDeviceManager.getAccessoryStrings();
+                if (accessoryStrings != null) {
+                    mCurrentAccessory = new UsbAccessory(accessoryStrings);
                     Slog.d(TAG, "entering USB accessory mode: " + mCurrentAccessory);
                     // defer accessoryAttached if system is not ready
                     if (mBootCompleted) {
-                        getCurrentSettings().accessoryAttached(mCurrentAccessory);
+                        mUsbDeviceManager.getCurrentSettings().accessoryAttached(mCurrentAccessory);
                     } // else handle in boot completed
                 } else {
                     Slog.e(TAG, "nativeGetAccessoryStrings failed");
@@ -673,17 +647,24 @@
             // make sure accessory mode is off
             // and restore default functions
             Slog.d(TAG, "exited USB accessory mode");
-            setEnabledFunctions(null, false, false);
+            setEnabledFunctions(UsbManager.FUNCTION_NONE, false);
 
             if (mCurrentAccessory != null) {
                 if (mBootCompleted) {
                     mSettingsManager.usbAccessoryRemoved(mCurrentAccessory);
                 }
                 mCurrentAccessory = null;
-                mAccessoryStrings = null;
             }
         }
 
+        protected SharedPreferences getPinnedSharedPrefs(Context context) {
+            final File prefsFile = new File(new File(
+                    Environment.getDataUserCePackageDirectory(StorageManager.UUID_PRIVATE_INTERNAL,
+                            context.getUserId(), context.getPackageName()), "shared_prefs"),
+                    UsbDeviceManager.class.getSimpleName() + ".xml");
+            return context.getSharedPreferences(prefsFile, Context.MODE_PRIVATE);
+        }
+
         private boolean isUsbStateChanged(Intent intent) {
             final Set<String> keySet = intent.getExtras().keySet();
             if (mBroadcastedIntent == null) {
@@ -706,7 +687,8 @@
             return false;
         }
 
-        protected void updateUsbStateBroadcastIfNeeded(boolean configChanged) {
+        protected void updateUsbStateBroadcastIfNeeded(long functions,
+                boolean configChanged) {
             // send a sticky broadcast containing current USB state
             Intent intent = new Intent(UsbManager.ACTION_USB_STATE);
             intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING
@@ -716,18 +698,14 @@
             intent.putExtra(UsbManager.USB_HOST_CONNECTED, mHostConnected);
             intent.putExtra(UsbManager.USB_CONFIGURED, mConfigured);
             intent.putExtra(UsbManager.USB_DATA_UNLOCKED,
-                    isUsbTransferAllowed() && mUsbDataUnlocked);
+                    isUsbTransferAllowed() && isUsbDataTransferActive(mCurrentFunctions));
             intent.putExtra(UsbManager.USB_CONFIG_CHANGED, configChanged);
 
-            if (mCurrentFunctions != null) {
-                String[] functions = mCurrentFunctions.split(",");
-                for (int i = 0; i < functions.length; i++) {
-                    final String function = functions[i];
-                    if (UsbManager.USB_FUNCTION_NONE.equals(function)) {
-                        continue;
-                    }
-                    intent.putExtra(function, true);
-                }
+            long remainingFunctions = functions;
+            while (remainingFunctions != 0) {
+                intent.putExtra(UsbManager.usbFunctionsToString(
+                        Long.highestOneBit(remainingFunctions)), true);
+                remainingFunctions -= Long.highestOneBit(remainingFunctions);
             }
 
             // send broadcast intent only if the USB state has changed
@@ -739,18 +717,21 @@
             }
 
             if (DEBUG) Slog.d(TAG, "broadcasting " + intent + " extras: " + intent.getExtras());
-            mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
+            sendStickyBroadcast(intent);
             mBroadcastedIntent = intent;
         }
 
+        protected void sendStickyBroadcast(Intent intent) {
+            mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
+        }
+
         private void updateUsbFunctions() {
             updateAudioSourceFunction();
             updateMidiFunction();
         }
 
         private void updateAudioSourceFunction() {
-            boolean enabled = UsbManager.containsFunction(mCurrentFunctions,
-                    UsbManager.USB_FUNCTION_AUDIO_SOURCE);
+            boolean enabled = (mCurrentFunctions & UsbManager.FUNCTION_AUDIO_SOURCE) != 0;
             if (enabled != mAudioSourceEnabled) {
                 int card = -1;
                 int device = -1;
@@ -775,8 +756,7 @@
         }
 
         private void updateMidiFunction() {
-            boolean enabled = UsbManager.containsFunction(mCurrentFunctions,
-                    UsbManager.USB_FUNCTION_MIDI);
+            boolean enabled = (mCurrentFunctions & UsbManager.FUNCTION_MIDI) != 0;
             if (enabled != mMidiEnabled) {
                 if (enabled) {
                     Scanner scanner = null;
@@ -800,11 +780,21 @@
         }
 
         private void setScreenUnlockedFunctions() {
-            setEnabledFunctions(mScreenUnlockedFunctions, false,
-                    UsbManager.containsFunction(mScreenUnlockedFunctions,
-                            UsbManager.USB_FUNCTION_MTP)
-                            || UsbManager.containsFunction(mScreenUnlockedFunctions,
-                            UsbManager.USB_FUNCTION_PTP));
+            setEnabledFunctions(mScreenUnlockedFunctions, false);
+        }
+
+        /**
+         * Returns the functions that are passed down to the low level driver once adb and
+         * charging are accounted for.
+         */
+        long getAppliedFunctions(long functions) {
+            if (functions == UsbManager.FUNCTION_NONE) {
+                return getChargingFunctions();
+            }
+            if (mAdbEnabled) {
+                return functions | UsbManager.FUNCTION_ADB;
+            }
+            return functions;
         }
 
         @Override
@@ -817,10 +807,10 @@
                     updateUsbNotification(false);
                     updateAdbNotification(false);
                     if (mBootCompleted) {
-                        updateUsbStateBroadcastIfNeeded(false);
+                        updateUsbStateBroadcastIfNeeded(getAppliedFunctions(mCurrentFunctions),
+                                false);
                     }
-                    if (UsbManager.containsFunction(mCurrentFunctions,
-                            UsbManager.USB_FUNCTION_ACCESSORY)) {
+                    if ((mCurrentFunctions & UsbManager.FUNCTION_ACCESSORY) != 0) {
                         updateCurrentAccessory();
                     }
                     if (mBootCompleted) {
@@ -828,11 +818,10 @@
                                 && !hasMessages(MSG_FUNCTION_SWITCH_TIMEOUT)) {
                             // restore defaults when USB is disconnected
                             if (!mScreenLocked
-                                    && !UsbManager.USB_FUNCTION_NONE.equals(
-                                    mScreenUnlockedFunctions)) {
+                                    && mScreenUnlockedFunctions != UsbManager.FUNCTION_NONE) {
                                 setScreenUnlockedFunctions();
                             } else {
-                                setEnabledFunctions(null, !mAdbEnabled, false);
+                                setEnabledFunctions(UsbManager.FUNCTION_NONE, !mAdbEnabled);
                             }
                         }
                         updateUsbFunctions();
@@ -867,7 +856,8 @@
                     updateUsbNotification(false);
                     if (mBootCompleted) {
                         if (mHostConnected || prevHostConnected) {
-                            updateUsbStateBroadcastIfNeeded(false);
+                            updateUsbStateBroadcastIfNeeded(getAppliedFunctions(mCurrentFunctions),
+                                    false);
                         }
                     } else {
                         mPendingBootBroadcast = true;
@@ -913,17 +903,17 @@
                     setAdbEnabled(msg.arg1 == 1);
                     break;
                 case MSG_SET_CURRENT_FUNCTIONS:
-                    String functions = (String) msg.obj;
-                    setEnabledFunctions(functions, false, msg.arg1 == 1);
+                    long functions = (Long) msg.obj;
+                    setEnabledFunctions(functions, false);
                     break;
                 case MSG_SET_SCREEN_UNLOCKED_FUNCTIONS:
-                    mScreenUnlockedFunctions = (String) msg.obj;
+                    mScreenUnlockedFunctions = (Long) msg.obj;
                     SharedPreferences.Editor editor = mSettings.edit();
                     editor.putString(String.format(Locale.ENGLISH, UNLOCKED_CONFIG_PREF,
-                            mCurrentUser), mScreenUnlockedFunctions);
+                            mCurrentUser),
+                            UsbManager.usbFunctionsToString(mScreenUnlockedFunctions));
                     editor.commit();
-                    if (!mScreenLocked && !UsbManager.USB_FUNCTION_NONE.equals(
-                            mScreenUnlockedFunctions)) {
+                    if (!mScreenLocked && mScreenUnlockedFunctions != UsbManager.FUNCTION_NONE) {
                         // If the screen is unlocked, also set current functions.
                         setScreenUnlockedFunctions();
                     }
@@ -936,22 +926,21 @@
                     if (mSettings == null && !mScreenLocked) {
                         // Shared preferences aren't accessible until the user has been unlocked.
                         mSettings = getPinnedSharedPrefs(mContext);
-                        mScreenUnlockedFunctions = mSettings.getString(
+                        mScreenUnlockedFunctions = UsbManager.usbFunctionsFromString(
+                                mSettings.getString(
                                 String.format(Locale.ENGLISH, UNLOCKED_CONFIG_PREF, mCurrentUser),
-                                UsbManager.USB_FUNCTION_NONE);
+                                ""));
                     }
                     if (!mBootCompleted) {
                         break;
                     }
                     if (mScreenLocked) {
                         if (!mConnected) {
-                            setEnabledFunctions(null, false, false);
+                            setEnabledFunctions(UsbManager.FUNCTION_NONE, false);
                         }
                     } else {
-                        if (!UsbManager.USB_FUNCTION_NONE.equals(mScreenUnlockedFunctions)
-                                && (UsbManager.USB_FUNCTION_ADB.equals(mCurrentFunctions)
-                                || (UsbManager.USB_FUNCTION_MTP.equals(mCurrentFunctions)
-                                && !mUsbDataUnlocked))) {
+                        if (mScreenUnlockedFunctions != UsbManager.FUNCTION_NONE
+                                && mCurrentFunctions == UsbManager.FUNCTION_NONE) {
                             // Set the screen unlocked functions if current function is charging.
                             setScreenUnlockedFunctions();
                         }
@@ -959,13 +948,24 @@
                     break;
                 case MSG_UPDATE_USER_RESTRICTIONS:
                     // Restart the USB stack if USB transfer is enabled but no longer allowed.
-                    final boolean forceRestart = mUsbDataUnlocked
-                            && isUsbDataTransferActive()
-                            && !isUsbTransferAllowed();
-                    setEnabledFunctions(
-                            mCurrentFunctions, forceRestart, mUsbDataUnlocked && !forceRestart);
+                    if (isUsbDataTransferActive(mCurrentFunctions) && !isUsbTransferAllowed()) {
+                        setEnabledFunctions(UsbManager.FUNCTION_NONE, true);
+                    }
                     break;
                 case MSG_SYSTEM_READY:
+                    mNotificationManager = (NotificationManager)
+                            mContext.getSystemService(Context.NOTIFICATION_SERVICE);
+
+                    // Ensure that the notification channels are set up
+                    if (isTv()) {
+                        // TV-specific notification channel
+                        mNotificationManager.createNotificationChannel(
+                                new NotificationChannel(ADB_NOTIFICATION_CHANNEL_ID_TV,
+                                        mContext.getString(
+                                                com.android.internal.R.string
+                                                        .adb_debugging_notification_channel_tv),
+                                        NotificationManager.IMPORTANCE_HIGH));
+                    }
                     mSystemReady = true;
                     finishBoot();
                     break;
@@ -984,10 +984,14 @@
                         }
                         mCurrentUser = msg.arg1;
                         mScreenLocked = true;
-                        mScreenUnlockedFunctions = mSettings.getString(
-                                String.format(Locale.ENGLISH, UNLOCKED_CONFIG_PREF, mCurrentUser),
-                                UsbManager.USB_FUNCTION_NONE);
-                        setEnabledFunctions(null, false, false);
+                        if (mSettings != null) {
+                            mScreenUnlockedFunctions = UsbManager.usbFunctionsFromString(
+                                    mSettings.getString(
+                                            String.format(Locale.ENGLISH, UNLOCKED_CONFIG_PREF,
+                                            mCurrentUser),
+                                    ""));
+                        }
+                        setEnabledFunctions(UsbManager.FUNCTION_NONE, false);
                     }
                     break;
                 }
@@ -995,9 +999,7 @@
                     if (DEBUG) {
                         Slog.v(TAG, "Accessory mode enter timeout: " + mConnected);
                     }
-                    if (!mConnected || !UsbManager.containsFunction(
-                            mCurrentFunctions,
-                            UsbManager.USB_FUNCTION_ACCESSORY)) {
+                    if (!mConnected || (mCurrentFunctions & UsbManager.FUNCTION_ACCESSORY) == 0) {
                         notifyAccessoryModeExit();
                     }
                     break;
@@ -1008,17 +1010,17 @@
         protected void finishBoot() {
             if (mBootCompleted && mCurrentUsbFunctionsReceived && mSystemReady) {
                 if (mPendingBootBroadcast) {
-                    updateUsbStateBroadcastIfNeeded(false);
+                    updateUsbStateBroadcastIfNeeded(getAppliedFunctions(mCurrentFunctions), false);
                     mPendingBootBroadcast = false;
                 }
                 if (!mScreenLocked
-                        && !UsbManager.USB_FUNCTION_NONE.equals(mScreenUnlockedFunctions)) {
+                        && mScreenUnlockedFunctions != UsbManager.FUNCTION_NONE) {
                     setScreenUnlockedFunctions();
                 } else {
-                    setEnabledFunctions(null, false, false);
+                    setEnabledFunctions(UsbManager.FUNCTION_NONE, false);
                 }
                 if (mCurrentAccessory != null) {
-                    getCurrentSettings().accessoryAttached(mCurrentAccessory);
+                    mUsbDeviceManager.getCurrentSettings().accessoryAttached(mCurrentAccessory);
                 }
                 if (mDebuggingManager != null) {
                     mDebuggingManager.setAdbEnabled(mAdbEnabled);
@@ -1026,8 +1028,8 @@
 
                 // make sure the ADB_ENABLED setting value matches the current state
                 try {
-                    Settings.Global.putInt(mContentResolver,
-                            Settings.Global.ADB_ENABLED, mAdbEnabled ? 1 : 0);
+                    putGlobalSettings(mContentResolver, Settings.Global.ADB_ENABLED,
+                            mAdbEnabled ? 1 : 0);
                 } catch (SecurityException e) {
                     // If UserManager.DISALLOW_DEBUGGING_FEATURES is on, that this setting can't
                     // be changed.
@@ -1040,9 +1042,9 @@
             }
         }
 
-        private boolean isUsbDataTransferActive() {
-            return UsbManager.containsFunction(mCurrentFunctions, UsbManager.USB_FUNCTION_MTP)
-                    || UsbManager.containsFunction(mCurrentFunctions, UsbManager.USB_FUNCTION_PTP);
+        protected boolean isUsbDataTransferActive(long functions) {
+            return (functions & UsbManager.FUNCTION_MTP) != 0
+                    || (functions & UsbManager.FUNCTION_PTP) != 0;
         }
 
         public UsbAccessory getCurrentAccessory() {
@@ -1051,7 +1053,7 @@
 
         protected void updateUsbNotification(boolean force) {
             if (mNotificationManager == null || !mUseUsbNotification
-                    || ("0".equals(SystemProperties.get("persist.charging.notify")))) {
+                    || ("0".equals(getSystemProperty("persist.charging.notify", "")))) {
                 return;
             }
 
@@ -1074,20 +1076,16 @@
                 titleRes = com.android.internal.R.string.usb_unsupported_audio_accessory_title;
                 id = SystemMessage.NOTE_USB_AUDIO_ACCESSORY_NOT_SUPPORTED;
             } else if (mConnected) {
-                if (UsbManager.containsFunction(mCurrentFunctions,
-                        UsbManager.USB_FUNCTION_MTP) && mUsbDataUnlocked) {
+                if (mCurrentFunctions == UsbManager.FUNCTION_MTP) {
                     titleRes = com.android.internal.R.string.usb_mtp_notification_title;
                     id = SystemMessage.NOTE_USB_MTP;
-                } else if (UsbManager.containsFunction(mCurrentFunctions,
-                        UsbManager.USB_FUNCTION_PTP) && mUsbDataUnlocked) {
+                } else if (mCurrentFunctions == UsbManager.FUNCTION_PTP) {
                     titleRes = com.android.internal.R.string.usb_ptp_notification_title;
                     id = SystemMessage.NOTE_USB_PTP;
-                } else if (UsbManager.containsFunction(mCurrentFunctions,
-                        UsbManager.USB_FUNCTION_MIDI)) {
+                } else if (mCurrentFunctions == UsbManager.FUNCTION_MIDI) {
                     titleRes = com.android.internal.R.string.usb_midi_notification_title;
                     id = SystemMessage.NOTE_USB_MIDI;
-                } else if (UsbManager.containsFunction(mCurrentFunctions,
-                        UsbManager.USB_FUNCTION_ACCESSORY)) {
+                } else if (mCurrentFunctions == UsbManager.FUNCTION_ACCESSORY) {
                     titleRes = com.android.internal.R.string.usb_accessory_notification_title;
                     id = SystemMessage.NOTE_USB_ACCESSORY;
                 } else if (mSourcePower) {
@@ -1184,7 +1182,7 @@
             final int titleRes = com.android.internal.R.string.adb_active_notification_title;
 
             if (mAdbEnabled && mConnected) {
-                if ("0".equals(SystemProperties.get("persist.adb.notify"))) return;
+                if ("0".equals(getSystemProperty("persist.adb.notify", ""))) return;
 
                 if (force && mAdbNotificationShown) {
                     mAdbNotificationShown = false;
@@ -1230,20 +1228,41 @@
             }
         }
 
-        protected String getChargingFunctions() {
+        private boolean isTv() {
+            return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK);
+        }
+
+        protected long getChargingFunctions() {
             // if ADB is enabled, reset functions to ADB
             // else enable MTP as usual.
             if (mAdbEnabled) {
-                return UsbManager.USB_FUNCTION_ADB;
+                return UsbManager.FUNCTION_ADB;
             } else {
-                return UsbManager.USB_FUNCTION_MTP;
+                return UsbManager.FUNCTION_MTP;
             }
         }
 
-        public boolean isFunctionEnabled(String function) {
-            return UsbManager.containsFunction(mCurrentFunctions, function);
+        protected void setSystemProperty(String prop, String val) {
+            SystemProperties.set(prop, val);
         }
 
+        protected String getSystemProperty(String prop, String def) {
+            return SystemProperties.get(prop, def);
+        }
+
+        protected void putGlobalSettings(ContentResolver contentResolver, String setting, int val) {
+            Settings.Global.putInt(contentResolver, setting, val);
+        }
+
+        public long getEnabledFunctions() {
+            return mCurrentFunctions;
+        }
+
+        public long getScreenUnlockedFunctions() {
+            return mScreenUnlockedFunctions;
+        }
+
+
         public void dump(IndentingPrintWriter pw) {
             pw.println("USB Device State:");
             pw.println("  mCurrentFunctions: " + mCurrentFunctions);
@@ -1252,7 +1271,6 @@
             pw.println("  mScreenLocked: " + mScreenLocked);
             pw.println("  mConnected: " + mConnected);
             pw.println("  mConfigured: " + mConfigured);
-            pw.println("  mUsbDataUnlocked: " + mUsbDataUnlocked);
             pw.println("  mCurrentAccessory: " + mCurrentAccessory);
             pw.println("  mHostConnected: " + mHostConnected);
             pw.println("  mSourcePower: " + mSourcePower);
@@ -1275,12 +1293,10 @@
         /**
          * Evaluates USB function policies and applies the change accordingly.
          */
-        protected abstract void setEnabledFunctions(String functions, boolean forceRestart,
-                boolean usbDataUnlocked);
-
+        protected abstract void setEnabledFunctions(long functions, boolean forceRestart);
     }
 
-    private final class UsbHandlerLegacy extends UsbHandler {
+    private static final class UsbHandlerLegacy extends UsbHandler {
         /**
          * The non-persistent property which stores the current USB settings.
          */
@@ -1293,46 +1309,44 @@
 
         private HashMap<String, HashMap<String, Pair<String, String>>> mOemModeMap;
         private String mCurrentOemFunctions;
+        private String mCurrentFunctionsStr;
+        private boolean mUsbDataUnlocked;
 
-        UsbHandlerLegacy(Looper looper, Context context) {
-            super(looper);
+        UsbHandlerLegacy(Looper looper, Context context, UsbDeviceManager deviceManager,
+                UsbDebuggingManager debuggingManager, UsbAlsaManager alsaManager,
+                UsbSettingsManager settingsManager) {
+            super(looper, context, deviceManager, debuggingManager, alsaManager, settingsManager);
             try {
                 readOemUsbOverrideConfig(context);
                 // Restore default functions.
-                mCurrentOemFunctions = SystemProperties.get(getPersistProp(false),
+                mCurrentOemFunctions = getSystemProperty(getPersistProp(false),
                         UsbManager.USB_FUNCTION_NONE);
                 if (isNormalBoot()) {
-                    mCurrentFunctions = SystemProperties.get(USB_CONFIG_PROPERTY,
+                    mCurrentFunctionsStr = getSystemProperty(USB_CONFIG_PROPERTY,
                             UsbManager.USB_FUNCTION_NONE);
-                    mCurrentFunctionsApplied = mCurrentFunctions.equals(
-                            SystemProperties.get(USB_STATE_PROPERTY));
+                    mCurrentFunctionsApplied = mCurrentFunctionsStr.equals(
+                            getSystemProperty(USB_STATE_PROPERTY, UsbManager.USB_FUNCTION_NONE));
                 } else {
-                    mCurrentFunctions = SystemProperties.get(getPersistProp(true),
+                    mCurrentFunctionsStr = getSystemProperty(getPersistProp(true),
                             UsbManager.USB_FUNCTION_NONE);
-                    mCurrentFunctionsApplied = SystemProperties.get(USB_CONFIG_PROPERTY,
+                    mCurrentFunctionsApplied = getSystemProperty(USB_CONFIG_PROPERTY,
                             UsbManager.USB_FUNCTION_NONE).equals(
-                            SystemProperties.get(USB_STATE_PROPERTY));
+                            getSystemProperty(USB_STATE_PROPERTY, UsbManager.USB_FUNCTION_NONE));
                 }
+                // Mask out adb, since it is stored in mAdbEnabled
+                mCurrentFunctions = UsbManager.usbFunctionsFromString(mCurrentFunctionsStr)
+                        & ~UsbManager.FUNCTION_ADB;
                 mCurrentUsbFunctionsReceived = true;
 
                 String state = FileUtils.readTextFile(new File(STATE_PATH), 0, null).trim();
                 updateState(state);
-
-                // register observer to listen for settings changes
-                mContentResolver.registerContentObserver(
-                        Settings.Global.getUriFor(Settings.Global.ADB_ENABLED),
-                        false, new AdbSettingsObserver());
-
-                // Watch for USB configuration changes
-                mUEventObserver.startObserving(USB_STATE_MATCH);
-                mUEventObserver.startObserving(ACCESSORY_START_MATCH);
             } catch (Exception e) {
                 Slog.e(TAG, "Error initializing UsbHandler", e);
             }
         }
 
         private void readOemUsbOverrideConfig(Context context) {
-            String[] configList = mContext.getResources().getStringArray(
+            String[] configList = context.getResources().getStringArray(
                     com.android.internal.R.array.config_oemUsbModeOverride);
 
             if (configList != null) {
@@ -1367,7 +1381,7 @@
                 return usbFunctions;
             }
 
-            String bootMode = SystemProperties.get(BOOT_MODE_PROPERTY, "unknown");
+            String bootMode = getSystemProperty(BOOT_MODE_PROPERTY, "unknown");
             Slog.d(TAG, "applyOemOverride usbfunctions=" + usbFunctions + " bootmode=" + bootMode);
 
             Map<String, Pair<String, String>> overridesMap =
@@ -1386,25 +1400,22 @@
                     if (!overrideFunctions.second.equals("")) {
                         String newFunction;
                         if (mAdbEnabled) {
-                            newFunction = UsbManager.addFunction(overrideFunctions.second,
+                            newFunction = addFunction(overrideFunctions.second,
                                     UsbManager.USB_FUNCTION_ADB);
                         } else {
                             newFunction = overrideFunctions.second;
                         }
                         Slog.d(TAG, "OEM USB override persisting: " + newFunction + "in prop: "
                                 + getPersistProp(false));
-                        SystemProperties.set(getPersistProp(false),
-                                newFunction);
+                        setSystemProperty(getPersistProp(false), newFunction);
                     }
                     return overrideFunctions.first;
                 } else if (mAdbEnabled) {
-                    String newFunction = UsbManager.addFunction(UsbManager.USB_FUNCTION_NONE,
+                    String newFunction = addFunction(UsbManager.USB_FUNCTION_NONE,
                             UsbManager.USB_FUNCTION_ADB);
-                    SystemProperties.set(getPersistProp(false),
-                            newFunction);
+                    setSystemProperty(getPersistProp(false), newFunction);
                 } else {
-                    SystemProperties.set(getPersistProp(false),
-                            UsbManager.USB_FUNCTION_NONE);
+                    setSystemProperty(getPersistProp(false), UsbManager.USB_FUNCTION_NONE);
                 }
             }
             // return passed in functions as is.
@@ -1417,7 +1428,7 @@
             String value = null;
             for (int i = 0; i < 20; i++) {
                 // State transition is done when sys.usb.state is set to the new configuration
-                value = SystemProperties.get(USB_STATE_PROPERTY);
+                value = getSystemProperty(USB_STATE_PROPERTY, "");
                 if (state.equals(value)) return true;
                 SystemClock.sleep(50);
             }
@@ -1432,14 +1443,14 @@
              * we always set it due to b/23631400, where adbd was getting killed
              * and not restarted due to property timeouts on some devices
              */
-            SystemProperties.set(USB_CONFIG_PROPERTY, config);
+            setSystemProperty(USB_CONFIG_PROPERTY, config);
         }
 
         @Override
-        protected void setEnabledFunctions(String functions, boolean forceRestart,
-                boolean usbDataUnlocked) {
+        protected void setEnabledFunctions(long usbFunctions, boolean forceRestart) {
+            boolean usbDataUnlocked = isUsbDataTransferActive(usbFunctions);
             if (DEBUG) {
-                Slog.d(TAG, "setEnabledFunctions functions=" + functions + ", "
+                Slog.d(TAG, "setEnabledFunctions functions=" + usbFunctions + ", "
                         + "forceRestart=" + forceRestart + ", usbDataUnlocked=" + usbDataUnlocked);
             }
 
@@ -1452,9 +1463,9 @@
             /**
              * Try to set the enabled functions.
              */
-            final String oldFunctions = mCurrentFunctions;
+            final long oldFunctions = mCurrentFunctions;
             final boolean oldFunctionsApplied = mCurrentFunctionsApplied;
-            if (trySetEnabledFunctions(functions, forceRestart)) {
+            if (trySetEnabledFunctions(usbFunctions, forceRestart)) {
                 return;
             }
 
@@ -1464,7 +1475,7 @@
              * user restrictions independently of any other new functions we were
              * trying to activate.
              */
-            if (oldFunctionsApplied && !oldFunctions.equals(functions)) {
+            if (oldFunctionsApplied && oldFunctions != usbFunctions) {
                 Slog.e(TAG, "Failsafe 1: Restoring previous USB functions.");
                 if (trySetEnabledFunctions(oldFunctions, false)) {
                     return;
@@ -1475,7 +1486,7 @@
              * Still didn't work.  Try to restore the default functions.
              */
             Slog.e(TAG, "Failsafe 2: Restoring default USB functions.");
-            if (trySetEnabledFunctions(null, false)) {
+            if (trySetEnabledFunctions(UsbManager.FUNCTION_NONE, false)) {
                 return;
             }
 
@@ -1484,7 +1495,7 @@
              * Try to get ADB working if enabled.
              */
             Slog.e(TAG, "Failsafe 3: Restoring empty function list (with ADB if enabled).");
-            if (trySetEnabledFunctions(UsbManager.USB_FUNCTION_NONE, false)) {
+            if (trySetEnabledFunctions(UsbManager.FUNCTION_NONE, false)) {
                 return;
             }
 
@@ -1495,30 +1506,49 @@
         }
 
         private boolean isNormalBoot() {
-            String bootMode = SystemProperties.get(BOOT_MODE_PROPERTY, "unknown");
+            String bootMode = getSystemProperty(BOOT_MODE_PROPERTY, "unknown");
             return bootMode.equals(NORMAL_BOOT) || bootMode.equals("unknown");
         }
 
-        private boolean trySetEnabledFunctions(String functions, boolean forceRestart) {
+        protected String applyAdbFunction(String functions) {
+            // Do not pass null pointer to the UsbManager.
+            // There isn't a check there.
+            if (functions == null) {
+                functions = "";
+            }
+            if (mAdbEnabled) {
+                functions = addFunction(functions, UsbManager.USB_FUNCTION_ADB);
+            } else {
+                functions = removeFunction(functions, UsbManager.USB_FUNCTION_ADB);
+            }
+            return functions;
+        }
+
+        private boolean trySetEnabledFunctions(long usbFunctions, boolean forceRestart) {
+            String functions = null;
+            if (usbFunctions != UsbManager.FUNCTION_NONE) {
+                functions = UsbManager.usbFunctionsToString(usbFunctions);
+            }
+            mCurrentFunctions = usbFunctions;
             if (functions == null || applyAdbFunction(functions)
                     .equals(UsbManager.USB_FUNCTION_NONE)) {
-                functions = getChargingFunctions();
+                functions = UsbManager.usbFunctionsToString(getChargingFunctions());
             }
             functions = applyAdbFunction(functions);
 
             String oemFunctions = applyOemOverrideFunction(functions);
 
-            if (!isNormalBoot() && !mCurrentFunctions.equals(functions)) {
-                SystemProperties.set(getPersistProp(true), functions);
+            if (!isNormalBoot() && !mCurrentFunctionsStr.equals(functions)) {
+                setSystemProperty(getPersistProp(true), functions);
             }
 
             if ((!functions.equals(oemFunctions)
                     && !mCurrentOemFunctions.equals(oemFunctions))
-                    || !mCurrentFunctions.equals(functions)
+                    || !mCurrentFunctionsStr.equals(functions)
                     || !mCurrentFunctionsApplied
                     || forceRestart) {
                 Slog.i(TAG, "Setting USB config to " + functions);
-                mCurrentFunctions = functions;
+                mCurrentFunctionsStr = functions;
                 mCurrentOemFunctions = oemFunctions;
                 mCurrentFunctionsApplied = false;
 
@@ -1538,12 +1568,12 @@
                 setUsbConfig(oemFunctions);
 
                 if (mBootCompleted
-                        && (UsbManager.containsFunction(functions, UsbManager.USB_FUNCTION_MTP)
-                        || UsbManager.containsFunction(functions, UsbManager.USB_FUNCTION_PTP))) {
+                        && (containsFunction(functions, UsbManager.USB_FUNCTION_MTP)
+                        || containsFunction(functions, UsbManager.USB_FUNCTION_PTP))) {
                     /**
                      * Start up dependent services.
                      */
-                    updateUsbStateBroadcastIfNeeded(true);
+                    updateUsbStateBroadcastIfNeeded(getAppliedFunctions(mCurrentFunctions), true);
                 }
 
                 if (!waitForState(oemFunctions)) {
@@ -1557,7 +1587,7 @@
         }
 
         private String getPersistProp(boolean functions) {
-            String bootMode = SystemProperties.get(BOOT_MODE_PROPERTY, "unknown");
+            String bootMode = getSystemProperty(BOOT_MODE_PROPERTY, "unknown");
             String persistProp = USB_PERSISTENT_CONFIG_PROPERTY;
             if (!(bootMode.equals(NORMAL_BOOT) || bootMode.equals("unknown"))) {
                 if (functions) {
@@ -1568,9 +1598,54 @@
             }
             return persistProp;
         }
+
+        private String addFunction(String functions, String function) {
+            if (UsbManager.USB_FUNCTION_NONE.equals(functions)) {
+                return function;
+            }
+            if (!containsFunction(functions, function)) {
+                if (functions.length() > 0) {
+                    functions += ",";
+                }
+                functions += function;
+            }
+            return functions;
+        }
+
+        private String removeFunction(String functions, String function) {
+            String[] split = functions.split(",");
+            for (int i = 0; i < split.length; i++) {
+                if (function.equals(split[i])) {
+                    split[i] = null;
+                }
+            }
+            if (split.length == 1 && split[0] == null) {
+                return UsbManager.USB_FUNCTION_NONE;
+            }
+            StringBuilder builder = new StringBuilder();
+            for (int i = 0; i < split.length; i++) {
+                String s = split[i];
+                if (s != null) {
+                    if (builder.length() > 0) {
+                        builder.append(",");
+                    }
+                    builder.append(s);
+                }
+            }
+            return builder.toString();
+        }
+
+        private boolean containsFunction(String functions, String function) {
+            int index = functions.indexOf(function);
+            if (index < 0) return false;
+            if (index > 0 && functions.charAt(index - 1) != ',') return false;
+            int charAfter = index + function.length();
+            if (charAfter < functions.length() && functions.charAt(charAfter) != ',') return false;
+            return true;
+        }
     }
 
-    private final class UsbHandlerHal extends UsbHandler {
+    private static final class UsbHandlerHal extends UsbHandler {
 
         /**
          * Proxy object for the usb gadget hal daemon.
@@ -1627,9 +1702,12 @@
          */
         protected static final String ADBD = "adbd";
 
+        protected boolean mCurrentUsbFunctionsRequested;
 
-        UsbHandlerHal(Looper looper) {
-            super(looper);
+        UsbHandlerHal(Looper looper, Context context, UsbDeviceManager deviceManager,
+                UsbDebuggingManager debuggingManager, UsbAlsaManager alsaManager,
+                UsbSettingsManager settingsManager) {
+            super(looper, context, deviceManager, debuggingManager, alsaManager, settingsManager);
             try {
                 ServiceNotification serviceNotification = new ServiceNotification();
 
@@ -1645,25 +1723,12 @@
                     mGadgetProxy = IUsbGadget.getService(true);
                     mGadgetProxy.linkToDeath(new UsbGadgetDeathRecipient(),
                             USB_GADGET_HAL_DEATH_COOKIE);
-                    mCurrentFunctions = UsbManager.USB_FUNCTION_NONE;
+                    mCurrentFunctions = UsbManager.FUNCTION_NONE;
                     mGadgetProxy.getCurrentUsbFunctions(new UsbGadgetCallback());
                     mCurrentUsbFunctionsRequested = true;
                 }
                 String state = FileUtils.readTextFile(new File(STATE_PATH), 0, null).trim();
                 updateState(state);
-
-                /**
-                 * Register observer to listen for settings changes.
-                 */
-                mContentResolver.registerContentObserver(
-                        Settings.Global.getUriFor(Settings.Global.ADB_ENABLED),
-                        false, new AdbSettingsObserver());
-
-                /**
-                 * Watch for USB configuration changes.
-                 */
-                mUEventObserver.startObserving(USB_STATE_MATCH);
-                mUEventObserver.startObserving(ACCESSORY_START_MATCH);
             } catch (NoSuchElementException e) {
                 Slog.e(TAG, "Usb gadget hal not found", e);
             } catch (RemoteException e) {
@@ -1696,7 +1761,7 @@
                         mGadgetProxy.linkToDeath(new UsbGadgetDeathRecipient(),
                                 USB_GADGET_HAL_DEATH_COOKIE);
                         if (!mCurrentFunctionsApplied) {
-                            setCurrentFunctions(mCurrentFunctions, mUsbDataUnlocked);
+                            setEnabledFunctions(mCurrentFunctions, false);
                         }
                     } catch (NoSuchElementException e) {
                         Slog.e(TAG, "Usb gadget hal not found", e);
@@ -1711,12 +1776,12 @@
         public void handleMessage(Message msg) {
             switch (msg.what) {
                 case MSG_SET_CHARGING_FUNCTIONS:
-                    setEnabledFunctions(null, false, mUsbDataUnlocked);
+                    setEnabledFunctions(UsbManager.FUNCTION_NONE, false);
                     break;
                 case MSG_SET_FUNCTIONS_TIMEOUT:
                     Slog.e(TAG, "Set functions timed out! no reply from usb hal");
                     if (msg.arg1 != 1) {
-                        setEnabledFunctions(null, false, mUsbDataUnlocked);
+                        setEnabledFunctions(UsbManager.FUNCTION_NONE, false);
                     }
                     break;
                 case MSG_GET_CURRENT_USB_FUNCTIONS:
@@ -1725,7 +1790,8 @@
 
                     if (mCurrentUsbFunctionsRequested) {
                         Slog.e(TAG, "updating mCurrentFunctions");
-                        mCurrentFunctions = functionListToString((Long) msg.obj);
+                        // Mask out adb, since it is stored in mAdbEnabled
+                        mCurrentFunctions = ((Long) msg.obj) & ~UsbManager.FUNCTION_ADB;
                         Slog.e(TAG,
                                 "mCurrentFunctions:" + mCurrentFunctions + "applied:" + msg.arg1);
                         mCurrentFunctionsApplied = msg.arg1 == 1;
@@ -1737,7 +1803,7 @@
                      * Dont force to default when the configuration is already set to default.
                      */
                     if (msg.arg1 != 1) {
-                        setEnabledFunctions(null, !mAdbEnabled, false);
+                        setEnabledFunctions(UsbManager.FUNCTION_NONE, !mAdbEnabled);
                     }
                     break;
                 default:
@@ -1789,70 +1855,7 @@
             }
         }
 
-        private long stringToFunctionList(String config) {
-            long functionsMask = 0;
-            String[] functions = config.split(",");
-            for (int i = 0; i < functions.length; i++) {
-                switch (functions[i]) {
-                    case "none":
-                        functionsMask |= GadgetFunction.NONE;
-                        break;
-                    case "adb":
-                        functionsMask |= GadgetFunction.ADB;
-                        break;
-                    case "mtp":
-                        functionsMask |= GadgetFunction.MTP;
-                        break;
-                    case "ptp":
-                        functionsMask |= GadgetFunction.PTP;
-                        break;
-                    case "midi":
-                        functionsMask |= GadgetFunction.MIDI;
-                        break;
-                    case "accessory":
-                        functionsMask |= GadgetFunction.ACCESSORY;
-                        break;
-                    case "rndis":
-                        functionsMask |= GadgetFunction.RNDIS;
-                        break;
-                }
-            }
-            return functionsMask;
-        }
-
-        private String functionListToString(Long functionList) {
-            StringJoiner functions = new StringJoiner(",");
-            if (functionList == GadgetFunction.NONE) {
-                functions.add("none");
-                return functions.toString();
-            }
-            if ((functionList & GadgetFunction.ADB) != 0) {
-                functions.add("adb");
-            }
-            if ((functionList & GadgetFunction.MTP) != 0) {
-                functions.add("mtp");
-            }
-            if ((functionList & GadgetFunction.PTP) != 0) {
-                functions.add("ptp");
-            }
-            if ((functionList & GadgetFunction.MIDI) != 0) {
-                functions.add("midi");
-            }
-            if ((functionList & GadgetFunction.ACCESSORY) != 0) {
-                functions.add("accessory");
-            }
-            if ((functionList & GadgetFunction.RNDIS) != 0) {
-                functions.add("rndis");
-            }
-            /**
-             * Remove the trailing comma.
-             */
-            return functions.toString();
-        }
-
-
-        private void setUsbConfig(String config, boolean chargingFunctions) {
-            Long functions = stringToFunctionList(config);
+        private void setUsbConfig(long config, boolean chargingFunctions) {
             if (true) Slog.d(TAG, "setUsbConfig(" + config + ") request:" + ++mCurrentRequest);
             /**
              * Cancel any ongoing requests, if present.
@@ -1867,20 +1870,20 @@
                     return;
                 }
                 try {
-                    if ((functions & GadgetFunction.ADB) != 0) {
+                    if ((config & UsbManager.FUNCTION_ADB) != 0) {
                         /**
                          * Start adbd if ADB function is included in the configuration.
                          */
-                        SystemProperties.set(CTL_START, ADBD);
+                        setSystemProperty(CTL_START, ADBD);
                     } else {
                         /**
                          * Stop adbd otherwise.
                          */
-                        SystemProperties.set(CTL_STOP, ADBD);
+                        setSystemProperty(CTL_STOP, ADBD);
                     }
                     UsbGadgetCallback usbGadgetCallback = new UsbGadgetCallback(mCurrentRequest,
-                            functions, chargingFunctions);
-                    mGadgetProxy.setCurrentUsbFunctions(functions, usbGadgetCallback,
+                            config, chargingFunctions);
+                    mGadgetProxy.setCurrentUsbFunctions(config, usbGadgetCallback,
                             SET_FUNCTIONS_TIMEOUT_MS - SET_FUNCTIONS_LEEWAY_MS);
                     sendMessageDelayed(MSG_SET_FUNCTIONS_TIMEOUT, chargingFunctions,
                             SET_FUNCTIONS_TIMEOUT_MS);
@@ -1894,49 +1897,29 @@
         }
 
         @Override
-        protected void setEnabledFunctions(String functions, boolean forceRestart,
-                boolean usbDataUnlocked) {
+        protected void setEnabledFunctions(long functions, boolean forceRestart) {
             if (DEBUG) {
                 Slog.d(TAG, "setEnabledFunctions functions=" + functions + ", "
-                        + "forceRestart=" + forceRestart + ", usbDataUnlocked=" + usbDataUnlocked);
+                        + "forceRestart=" + forceRestart);
             }
-
-            if (usbDataUnlocked != mUsbDataUnlocked) {
-                mUsbDataUnlocked = usbDataUnlocked;
-                updateUsbNotification(false);
-                forceRestart = true;
-            }
-
-            trySetEnabledFunctions(functions, forceRestart);
-        }
-
-        private void trySetEnabledFunctions(String functions, boolean forceRestart) {
-            boolean chargingFunctions = false;
-
-            if (functions == null || applyAdbFunction(functions)
-                    .equals(UsbManager.USB_FUNCTION_NONE)) {
-                functions = getChargingFunctions();
-                chargingFunctions = true;
-            }
-            functions = applyAdbFunction(functions);
-
-            if (!mCurrentFunctions.equals(functions)
+            if (mCurrentFunctions != functions
                     || !mCurrentFunctionsApplied
                     || forceRestart) {
-                Slog.i(TAG, "Setting USB config to " + functions);
+                Slog.i(TAG, "Setting USB config to " + UsbManager.usbFunctionsToString(functions));
                 mCurrentFunctions = functions;
                 mCurrentFunctionsApplied = false;
                 // set the flag to false as that would be stale value
                 mCurrentUsbFunctionsRequested = false;
 
-                // Set the new USB configuration.
-                setUsbConfig(mCurrentFunctions, chargingFunctions);
+                boolean chargingFunctions = functions == UsbManager.FUNCTION_NONE;
+                functions = getAppliedFunctions(functions);
 
-                if (mBootCompleted
-                        && (UsbManager.containsFunction(functions, UsbManager.USB_FUNCTION_MTP)
-                        || UsbManager.containsFunction(functions, UsbManager.USB_FUNCTION_PTP))) {
+                // Set the new USB configuration.
+                setUsbConfig(functions, chargingFunctions);
+
+                if (mBootCompleted && isUsbDataTransferActive(functions)) {
                     // Start up dependent services.
-                    updateUsbStateBroadcastIfNeeded(true);
+                    updateUsbStateBroadcastIfNeeded(functions, true);
                 }
             }
         }
@@ -1968,27 +1951,37 @@
         return nativeOpenAccessory();
     }
 
-    /**
-     * Checks whether the function is present in the USB configuration.
-     *
-     * @param function function to be checked.
-     */
-    public boolean isFunctionEnabled(String function) {
-        return mHandler.isFunctionEnabled(function);
+    public long getCurrentFunctions() {
+        return mHandler.getEnabledFunctions();
+    }
+
+    public long getScreenUnlockedFunctions() {
+        return mHandler.getScreenUnlockedFunctions();
     }
 
     /**
      * Adds function to the current USB configuration.
      *
-     * @param functions name of the USB function, or null to restore the default function.
-     * @param usbDataUnlocked whether user data is accessible.
+     * @param functions The functions to set, or empty to set the charging function.
      */
-    public void setCurrentFunctions(String functions, boolean usbDataUnlocked) {
+    public void setCurrentFunctions(long functions) {
         if (DEBUG) {
-            Slog.d(TAG, "setCurrentFunctions(" + functions + ", "
-                    + usbDataUnlocked + ")");
+            Slog.d(TAG, "setCurrentFunctions(" + UsbManager.usbFunctionsToString(functions) + ")");
         }
-        mHandler.sendMessage(MSG_SET_CURRENT_FUNCTIONS, functions, usbDataUnlocked);
+        if (functions == UsbManager.FUNCTION_NONE) {
+            MetricsLogger.action(mContext, MetricsEvent.ACTION_USB_CONFIG_CHARGING);
+        } else if (functions == UsbManager.FUNCTION_MTP) {
+            MetricsLogger.action(mContext, MetricsEvent.ACTION_USB_CONFIG_MTP);
+        } else if (functions == UsbManager.FUNCTION_PTP) {
+            MetricsLogger.action(mContext, MetricsEvent.ACTION_USB_CONFIG_PTP);
+        } else if (functions == UsbManager.FUNCTION_MIDI) {
+            MetricsLogger.action(mContext, MetricsEvent.ACTION_USB_CONFIG_MIDI);
+        } else if (functions == UsbManager.FUNCTION_RNDIS) {
+            MetricsLogger.action(mContext, MetricsEvent.ACTION_USB_CONFIG_RNDIS);
+        } else if (functions == UsbManager.FUNCTION_ACCESSORY) {
+            MetricsLogger.action(mContext, MetricsEvent.ACTION_USB_CONFIG_ACCESSORY);
+        }
+        mHandler.sendMessage(MSG_SET_CURRENT_FUNCTIONS, functions);
     }
 
     /**
@@ -1996,9 +1989,10 @@
      *
      * @param functions Functions to set.
      */
-    public void setScreenUnlockedFunctions(String functions) {
+    public void setScreenUnlockedFunctions(long functions) {
         if (DEBUG) {
-            Slog.d(TAG, "setScreenUnlockedFunctions(" + functions + ")");
+            Slog.d(TAG, "setScreenUnlockedFunctions("
+                    + UsbManager.usbFunctionsToString(functions) + ")");
         }
         mHandler.sendMessage(MSG_SET_SCREEN_UNLOCKED_FUNCTIONS, functions);
     }
diff --git a/services/usb/java/com/android/server/usb/UsbService.java b/services/usb/java/com/android/server/usb/UsbService.java
index 1a20819..2f6e531 100644
--- a/services/usb/java/com/android/server/usb/UsbService.java
+++ b/services/usb/java/com/android/server/usb/UsbService.java
@@ -382,59 +382,44 @@
     }
 
     @Override
+    public void setCurrentFunctions(long functions) {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
+        Preconditions.checkArgument(UsbManager.areSettableFunctions(functions));
+        Preconditions.checkState(mDeviceManager != null);
+        mDeviceManager.setCurrentFunctions(functions);
+    }
+
+    @Override
+    public void setCurrentFunction(String functions, boolean usbDataUnlocked) {
+        setCurrentFunctions(UsbManager.usbFunctionsFromString(functions));
+    }
+
+    @Override
     public boolean isFunctionEnabled(String function) {
-        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
-        return mDeviceManager != null && mDeviceManager.isFunctionEnabled(function);
+        return (getCurrentFunctions() & UsbManager.usbFunctionsFromString(function)) != 0;
     }
 
     @Override
-    public void setCurrentFunction(String function, boolean usbDataUnlocked) {
+    public long getCurrentFunctions() {
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
-
-        if (!isSupportedCurrentFunction(function)) {
-            Slog.w(TAG, "Caller of setCurrentFunction() requested unsupported USB function: "
-                    + function);
-            function = UsbManager.USB_FUNCTION_NONE;
-        }
-
-        if (mDeviceManager != null) {
-            mDeviceManager.setCurrentFunctions(function, usbDataUnlocked);
-        } else {
-            throw new IllegalStateException("USB device mode not supported");
-        }
+        Preconditions.checkState(mDeviceManager != null);
+        return mDeviceManager.getCurrentFunctions();
     }
 
     @Override
-    public void setScreenUnlockedFunctions(String function) {
+    public void setScreenUnlockedFunctions(long functions) {
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
+        Preconditions.checkArgument(UsbManager.areSettableFunctions(functions));
+        Preconditions.checkState(mDeviceManager != null);
 
-        if (!isSupportedCurrentFunction(function)) {
-            Slog.w(TAG, "Caller of setScreenUnlockedFunctions() requested unsupported USB function:"
-                    + function);
-            function = UsbManager.USB_FUNCTION_NONE;
-        }
-
-        if (mDeviceManager != null) {
-            mDeviceManager.setScreenUnlockedFunctions(function);
-        } else {
-            throw new IllegalStateException("USB device mode not supported");
-        }
+        mDeviceManager.setScreenUnlockedFunctions(functions);
     }
 
-    private static boolean isSupportedCurrentFunction(String function) {
-        if (function == null) return true;
-
-        switch (function) {
-            case UsbManager.USB_FUNCTION_NONE:
-            case UsbManager.USB_FUNCTION_AUDIO_SOURCE:
-            case UsbManager.USB_FUNCTION_MIDI:
-            case UsbManager.USB_FUNCTION_MTP:
-            case UsbManager.USB_FUNCTION_PTP:
-            case UsbManager.USB_FUNCTION_RNDIS:
-                return true;
-        }
-
-        return false;
+    @Override
+    public long getScreenUnlockedFunctions() {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
+        Preconditions.checkState(mDeviceManager != null);
+        return mDeviceManager.getScreenUnlockedFunctions();
     }
 
     @Override
diff --git a/tests/UsbTests/Android.mk b/tests/UsbTests/Android.mk
new file mode 100644
index 0000000..a04f32a
--- /dev/null
+++ b/tests/UsbTests/Android.mk
@@ -0,0 +1,44 @@
+#
+# Copyright (C) 2018 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.
+#
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    frameworks-base-testutils \
+    android-support-test \
+    mockito-target-inline-minus-junit4 \
+    platform-test-annotations \
+    services.core \
+    services.net \
+    services.usb \
+    truth-prebuilt \
+
+LOCAL_JNI_SHARED_LIBRARIES := \
+    libdexmakerjvmtiagent \
+
+LOCAL_CERTIFICATE := platform
+
+LOCAL_PACKAGE_NAME := UsbTests
+
+LOCAL_COMPATIBILITY_SUITE := device-tests
+
+include $(BUILD_PACKAGE)
diff --git a/tests/UsbTests/AndroidManifest.xml b/tests/UsbTests/AndroidManifest.xml
new file mode 100644
index 0000000..5d60695
--- /dev/null
+++ b/tests/UsbTests/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.android.server.usb" >
+
+    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
+    <uses-permission android:name="android.permission.MANAGE_USERS" />
+
+    <application android:debuggable="true">
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="com.android.server.usb"
+                     android:label="UsbTests"/>
+</manifest>
diff --git a/tests/UsbTests/AndroidTest.xml b/tests/UsbTests/AndroidTest.xml
new file mode 100644
index 0000000..0b623fb
--- /dev/null
+++ b/tests/UsbTests/AndroidTest.xml
@@ -0,0 +1,29 @@
+<!-- Copyright (C) 2018 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.
+-->
+<configuration description="Runs sample instrumentation test.">
+    <target_preparer class="com.android.tradefed.targetprep.TestFilePushSetup"/>
+    <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+        <option name="test-file-name" value="UsbTests.apk"/>
+    </target_preparer>
+    <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer"/>
+    <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"/>
+    <option name="test-suite-tag" value="apct"/>
+    <option name="test-tag" value="UsbTests"/>
+
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+        <option name="package" value="com.android.server.usb"/>
+        <option name="runner" value="android.support.test.runner.AndroidJUnitRunner"/>
+    </test>
+</configuration>
\ No newline at end of file
diff --git a/tests/UsbTests/src/com/android/server/usb/UsbHandlerTest.java b/tests/UsbTests/src/com/android/server/usb/UsbHandlerTest.java
new file mode 100644
index 0000000..c491b46
--- /dev/null
+++ b/tests/UsbTests/src/com/android/server/usb/UsbHandlerTest.java
@@ -0,0 +1,316 @@
+/*
+ * Copyright (C) 2018 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.server.usb;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.hardware.usb.UsbManager;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import com.android.server.FgThread;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+
+/**
+ * Tests for UsbHandler state changes.
+ */
+@RunWith(AndroidJUnit4.class)
+public class UsbHandlerTest {
+    private static final String TAG = UsbHandlerTest.class.getSimpleName();
+
+    @Mock
+    private UsbDeviceManager mUsbDeviceManager;
+    @Mock
+    private UsbDebuggingManager mUsbDebuggingManager;
+    @Mock
+    private UsbAlsaManager mUsbAlsaManager;
+    @Mock
+    private UsbSettingsManager mUsbSettingsManager;
+    @Mock
+    private SharedPreferences mSharedPreferences;
+    @Mock
+    private SharedPreferences.Editor mEditor;
+
+    private MockUsbHandler mUsbHandler;
+
+    private static final int MSG_UPDATE_STATE = 0;
+    private static final int MSG_ENABLE_ADB = 1;
+    private static final int MSG_SET_CURRENT_FUNCTIONS = 2;
+    private static final int MSG_SYSTEM_READY = 3;
+    private static final int MSG_BOOT_COMPLETED = 4;
+    private static final int MSG_USER_SWITCHED = 5;
+    private static final int MSG_UPDATE_USER_RESTRICTIONS = 6;
+    private static final int MSG_SET_SCREEN_UNLOCKED_FUNCTIONS = 12;
+    private static final int MSG_UPDATE_SCREEN_LOCK = 13;
+
+    private Map<String, String> mMockProperties;
+    private Map<String, Integer> mMockGlobalSettings;
+
+    private class MockUsbHandler extends UsbDeviceManager.UsbHandler {
+        boolean mIsUsbTransferAllowed;
+        Intent mBroadcastedIntent;
+
+        MockUsbHandler(Looper looper, Context context, UsbDeviceManager deviceManager,
+                UsbDebuggingManager debuggingManager, UsbAlsaManager alsaManager,
+                UsbSettingsManager settingsManager) {
+            super(looper, context, deviceManager, debuggingManager, alsaManager, settingsManager);
+            mUseUsbNotification = false;
+            mIsUsbTransferAllowed = true;
+            mCurrentUsbFunctionsReceived = true;
+        }
+
+        @Override
+        protected void setEnabledFunctions(long functions, boolean force) {
+            mCurrentFunctions = functions;
+        }
+
+        @Override
+        protected void setSystemProperty(String property, String value) {
+            mMockProperties.put(property, value);
+        }
+
+        @Override
+        protected void putGlobalSettings(ContentResolver resolver, String setting, int val) {
+            mMockGlobalSettings.put(setting, val);
+        }
+
+        @Override
+        protected String getSystemProperty(String property, String def) {
+            if (mMockProperties.containsKey(property)) {
+                return mMockProperties.get(property);
+            }
+            return def;
+        }
+
+        @Override
+        protected boolean isUsbTransferAllowed() {
+            return mIsUsbTransferAllowed;
+        }
+
+        @Override
+        protected SharedPreferences getPinnedSharedPrefs(Context context) {
+            return mSharedPreferences;
+        }
+
+        @Override
+        protected void sendStickyBroadcast(Intent intent) {
+            mBroadcastedIntent = intent;
+        }
+    }
+
+    @Before
+    public void before() {
+        MockitoAnnotations.initMocks(this);
+        mMockProperties = new HashMap<>();
+        mMockGlobalSettings = new HashMap<>();
+        when(mSharedPreferences.edit()).thenReturn(mEditor);
+
+        mUsbHandler = new MockUsbHandler(FgThread.get().getLooper(),
+                InstrumentationRegistry.getContext(), mUsbDeviceManager, mUsbDebuggingManager,
+                mUsbAlsaManager, mUsbSettingsManager);
+    }
+
+    @SmallTest
+    public void setFunctionsMtp() {
+        mUsbHandler.handleMessage(mUsbHandler.obtainMessage(MSG_SET_CURRENT_FUNCTIONS,
+                UsbManager.FUNCTION_MTP));
+        assertNotEquals(mUsbHandler.getEnabledFunctions() & UsbManager.FUNCTION_MTP, 0);
+    }
+
+    @SmallTest
+    public void setFunctionsPtp() {
+        mUsbHandler.handleMessage(mUsbHandler.obtainMessage(MSG_SET_CURRENT_FUNCTIONS,
+                UsbManager.FUNCTION_PTP));
+        assertNotEquals(mUsbHandler.getEnabledFunctions() & UsbManager.FUNCTION_PTP, 0);
+    }
+
+    @SmallTest
+    public void setFunctionsMidi() {
+        mUsbHandler.handleMessage(mUsbHandler.obtainMessage(MSG_SET_CURRENT_FUNCTIONS,
+                UsbManager.FUNCTION_MIDI));
+        assertNotEquals(mUsbHandler.getEnabledFunctions() & UsbManager.FUNCTION_MIDI, 0);
+    }
+
+    @SmallTest
+    public void setFunctionsRndis() {
+        mUsbHandler.handleMessage(mUsbHandler.obtainMessage(MSG_SET_CURRENT_FUNCTIONS,
+                UsbManager.FUNCTION_RNDIS));
+        assertNotEquals(mUsbHandler.getEnabledFunctions() & UsbManager.FUNCTION_RNDIS, 0);
+    }
+
+    @SmallTest
+    public void enableAdb() {
+        sendBootCompleteMessages(mUsbHandler);
+        mUsbHandler.handleMessage(mUsbHandler.obtainMessage(MSG_ENABLE_ADB, 1));
+        assertEquals(mUsbHandler.getEnabledFunctions(), UsbManager.FUNCTION_NONE);
+        assertTrue(mUsbHandler.mAdbEnabled);
+        assertEquals(mMockProperties.get(UsbDeviceManager.UsbHandler
+                .USB_PERSISTENT_CONFIG_PROPERTY), UsbManager.USB_FUNCTION_ADB);
+        verify(mUsbDebuggingManager).setAdbEnabled(true);
+
+        mUsbHandler.handleMessage(mUsbHandler.obtainMessage(MSG_UPDATE_STATE, 1, 1));
+
+        assertTrue(mUsbHandler.mBroadcastedIntent.getBooleanExtra(UsbManager.USB_CONNECTED, false));
+        assertTrue(mUsbHandler.mBroadcastedIntent
+                .getBooleanExtra(UsbManager.USB_CONFIGURED, false));
+        assertTrue(mUsbHandler.mBroadcastedIntent
+                .getBooleanExtra(UsbManager.USB_FUNCTION_ADB, false));
+    }
+
+    @SmallTest
+    public void disableAdb() {
+        mMockProperties.put(UsbDeviceManager.UsbHandler.USB_PERSISTENT_CONFIG_PROPERTY,
+                UsbManager.USB_FUNCTION_ADB);
+        mUsbHandler = new MockUsbHandler(FgThread.get().getLooper(),
+                InstrumentationRegistry.getContext(), mUsbDeviceManager, mUsbDebuggingManager,
+                mUsbAlsaManager, mUsbSettingsManager);
+
+        sendBootCompleteMessages(mUsbHandler);
+        mUsbHandler.handleMessage(mUsbHandler.obtainMessage(MSG_ENABLE_ADB, 0));
+        assertEquals(mUsbHandler.getEnabledFunctions(), UsbManager.FUNCTION_NONE);
+        assertFalse(mUsbHandler.mAdbEnabled);
+        assertEquals(mMockProperties.get(UsbDeviceManager.UsbHandler
+                .USB_PERSISTENT_CONFIG_PROPERTY), "");
+        verify(mUsbDebuggingManager).setAdbEnabled(false);
+    }
+
+    @SmallTest
+    public void bootCompletedCharging() {
+        sendBootCompleteMessages(mUsbHandler);
+        assertEquals(mUsbHandler.getEnabledFunctions(), UsbManager.FUNCTION_NONE);
+    }
+
+    @Test
+    @SmallTest
+    public void bootCompletedAdbEnabled() {
+        mMockProperties.put(UsbDeviceManager.UsbHandler.USB_PERSISTENT_CONFIG_PROPERTY, "adb");
+        mUsbHandler = new MockUsbHandler(FgThread.get().getLooper(),
+                InstrumentationRegistry.getContext(), mUsbDeviceManager, mUsbDebuggingManager,
+                mUsbAlsaManager, mUsbSettingsManager);
+
+        sendBootCompleteMessages(mUsbHandler);
+        assertEquals(mUsbHandler.getEnabledFunctions(), UsbManager.FUNCTION_NONE);
+        assertEquals(mMockGlobalSettings.get(Settings.Global.ADB_ENABLED).intValue(), 1);
+        assertTrue(mUsbHandler.mAdbEnabled);
+        verify(mUsbDebuggingManager).setAdbEnabled(true);
+    }
+
+    @SmallTest
+    public void userSwitchedDisablesMtp() {
+        mUsbHandler.handleMessage(mUsbHandler.obtainMessage(MSG_SET_CURRENT_FUNCTIONS,
+                UsbManager.FUNCTION_MTP));
+        assertNotEquals(mUsbHandler.getEnabledFunctions() & UsbManager.FUNCTION_MTP, 0);
+
+        mUsbHandler.handleMessage(mUsbHandler.obtainMessage(MSG_USER_SWITCHED,
+                UserHandle.getCallingUserId() + 1));
+        assertEquals(mUsbHandler.getEnabledFunctions(), UsbManager.FUNCTION_NONE);
+    }
+
+    @SmallTest
+    public void changedRestrictionsDisablesMtp() {
+        mUsbHandler.handleMessage(mUsbHandler.obtainMessage(MSG_SET_CURRENT_FUNCTIONS,
+                UsbManager.FUNCTION_MTP));
+        assertNotEquals(mUsbHandler.getEnabledFunctions() & UsbManager.FUNCTION_MTP, 0);
+
+        mUsbHandler.mIsUsbTransferAllowed = false;
+        mUsbHandler.handleMessage(mUsbHandler.obtainMessage(MSG_UPDATE_USER_RESTRICTIONS));
+        assertEquals(mUsbHandler.getEnabledFunctions(), UsbManager.FUNCTION_NONE);
+    }
+
+    @SmallTest
+    public void disconnectResetsCharging() {
+        sendBootCompleteMessages(mUsbHandler);
+
+        mUsbHandler.handleMessage(mUsbHandler.obtainMessage(MSG_SET_CURRENT_FUNCTIONS,
+                UsbManager.FUNCTION_MTP));
+        assertNotEquals(mUsbHandler.getEnabledFunctions() & UsbManager.FUNCTION_MTP, 0);
+
+        mUsbHandler.handleMessage(mUsbHandler.obtainMessage(MSG_UPDATE_STATE, 0, 0));
+
+        assertEquals(mUsbHandler.getEnabledFunctions(), UsbManager.FUNCTION_NONE);
+    }
+
+    @SmallTest
+    public void configuredSendsBroadcast() {
+        sendBootCompleteMessages(mUsbHandler);
+        mUsbHandler.handleMessage(mUsbHandler.obtainMessage(MSG_SET_CURRENT_FUNCTIONS,
+                UsbManager.FUNCTION_MTP));
+        assertNotEquals(mUsbHandler.getEnabledFunctions() & UsbManager.FUNCTION_MTP, 0);
+
+        mUsbHandler.handleMessage(mUsbHandler.obtainMessage(MSG_UPDATE_STATE, 1, 1));
+
+        assertNotEquals(mUsbHandler.getEnabledFunctions() & UsbManager.FUNCTION_MTP, 0);
+        assertTrue(mUsbHandler.mBroadcastedIntent.getBooleanExtra(UsbManager.USB_CONNECTED, false));
+        assertTrue(mUsbHandler.mBroadcastedIntent
+                .getBooleanExtra(UsbManager.USB_CONFIGURED, false));
+        assertTrue(mUsbHandler.mBroadcastedIntent
+                .getBooleanExtra(UsbManager.USB_FUNCTION_MTP, false));
+    }
+
+    @SmallTest
+    public void setScreenUnlockedFunctions() {
+        sendBootCompleteMessages(mUsbHandler);
+        mUsbHandler.handleMessage(mUsbHandler.obtainMessage(MSG_UPDATE_SCREEN_LOCK, 0));
+
+        mUsbHandler.handleMessage(mUsbHandler.obtainMessage(MSG_SET_SCREEN_UNLOCKED_FUNCTIONS,
+                UsbManager.FUNCTION_MTP));
+        assertNotEquals(mUsbHandler.getScreenUnlockedFunctions() & UsbManager.FUNCTION_MTP, 0);
+        assertNotEquals(mUsbHandler.getEnabledFunctions() & UsbManager.FUNCTION_MTP, 0);
+        verify(mEditor).putString(String.format(Locale.ENGLISH,
+                UsbDeviceManager.UNLOCKED_CONFIG_PREF, mUsbHandler.mCurrentUser),
+                UsbManager.USB_FUNCTION_MTP);
+    }
+
+    @SmallTest
+    public void unlockScreen() {
+        when(mSharedPreferences.getString(String.format(Locale.ENGLISH,
+                UsbDeviceManager.UNLOCKED_CONFIG_PREF, mUsbHandler.mCurrentUser), ""))
+                .thenReturn(UsbManager.USB_FUNCTION_MTP);
+        sendBootCompleteMessages(mUsbHandler);
+        mUsbHandler.handleMessage(mUsbHandler.obtainMessage(MSG_UPDATE_SCREEN_LOCK, 0));
+
+        assertNotEquals(mUsbHandler.getScreenUnlockedFunctions() & UsbManager.FUNCTION_MTP, 0);
+        assertNotEquals(mUsbHandler.getEnabledFunctions() & UsbManager.FUNCTION_MTP, 0);
+    }
+
+    private static void sendBootCompleteMessages(Handler handler) {
+        handler.handleMessage(handler.obtainMessage(MSG_BOOT_COMPLETED));
+        handler.handleMessage(handler.obtainMessage(MSG_SYSTEM_READY));
+    }
+}
\ No newline at end of file
diff --git a/tests/net/java/com/android/server/connectivity/TetheringTest.java b/tests/net/java/com/android/server/connectivity/TetheringTest.java
index 099cfd4..e692652 100644
--- a/tests/net/java/com/android/server/connectivity/TetheringTest.java
+++ b/tests/net/java/com/android/server/connectivity/TetheringTest.java
@@ -311,7 +311,7 @@
         // Emulate pressing the USB tethering button in Settings UI.
         mTethering.startTethering(TETHERING_USB, null, false);
         mLooper.dispatchAll();
-        verify(mUsbManager, times(1)).setCurrentFunction(UsbManager.USB_FUNCTION_RNDIS, false);
+        verify(mUsbManager, times(1)).setCurrentFunctions(UsbManager.FUNCTION_RNDIS);
 
         // Pretend we receive a USB connected broadcast. Here we also pretend
         // that the RNDIS function is somehow enabled, so that we see if we