Add new USB details screen for Connected Devices 2.0

Also updated UsbBackend to use the new UsbFunctions
api.

Added new unit tests for UsbDetailsHeaderController
and UsbDetailsProfilesController.

Bug: 69333961
Test: make RunSettingsRoboTests
Change-Id: I133750190bb61dfe0e20b06f50e50ea13b347f1e
diff --git a/src/com/android/settings/Settings.java b/src/com/android/settings/Settings.java
index b4908dd..c233271 100644
--- a/src/com/android/settings/Settings.java
+++ b/src/com/android/settings/Settings.java
@@ -96,6 +96,7 @@
     public static class ZenAccessSettingsActivity extends SettingsActivity { /* empty */ }
     public static class ConditionProviderSettingsActivity extends SettingsActivity { /* empty */ }
     public static class UsbSettingsActivity extends SettingsActivity { /* empty */ }
+    public static class UsbDetailsActivity extends SettingsActivity { /* empty */ }
     public static class TrustedCredentialsSettingsActivity extends SettingsActivity { /* empty */ }
     public static class PaymentSettingsActivity extends SettingsActivity { /* empty */ }
     public static class PrintSettingsActivity extends SettingsActivity { /* empty */ }
diff --git a/src/com/android/settings/connecteddevice/AdvancedConnectedDeviceDashboardFragment.java b/src/com/android/settings/connecteddevice/AdvancedConnectedDeviceDashboardFragment.java
index 2a136bc..9ac6ebd 100644
--- a/src/com/android/settings/connecteddevice/AdvancedConnectedDeviceDashboardFragment.java
+++ b/src/com/android/settings/connecteddevice/AdvancedConnectedDeviceDashboardFragment.java
@@ -24,8 +24,9 @@
 import com.android.settings.bluetooth.BluetoothFilesPreferenceController;
 import com.android.settings.bluetooth.BluetoothMasterSwitchPreferenceController;
 import com.android.settings.bluetooth.BluetoothSwitchPreferenceController;
+import com.android.settings.connecteddevice.usb.UsbBackend;
+import com.android.settings.connecteddevice.usb.UsbModePreferenceController;
 import com.android.settings.dashboard.DashboardFragment;
-import com.android.settings.deviceinfo.UsbBackend;
 import com.android.settings.nfc.NfcPreferenceController;
 import com.android.settings.overlay.FeatureFactory;
 import com.android.settings.search.BaseSearchIndexProvider;
diff --git a/src/com/android/settings/connecteddevice/ConnectedDeviceDashboardFragmentOld.java b/src/com/android/settings/connecteddevice/ConnectedDeviceDashboardFragmentOld.java
index 7097b36..bde5e81 100644
--- a/src/com/android/settings/connecteddevice/ConnectedDeviceDashboardFragmentOld.java
+++ b/src/com/android/settings/connecteddevice/ConnectedDeviceDashboardFragmentOld.java
@@ -26,9 +26,10 @@
 import com.android.settings.SettingsActivity;
 import com.android.settings.bluetooth.BluetoothMasterSwitchPreferenceController;
 import com.android.settings.bluetooth.Utils;
+import com.android.settings.connecteddevice.usb.UsbBackend;
+import com.android.settings.connecteddevice.usb.UsbModePreferenceController;
 import com.android.settings.dashboard.DashboardFragment;
 import com.android.settings.dashboard.SummaryLoader;
-import com.android.settings.deviceinfo.UsbBackend;
 import com.android.settings.nfc.NfcPreferenceController;
 import com.android.settings.overlay.FeatureFactory;
 import com.android.settings.search.BaseSearchIndexProvider;
diff --git a/src/com/android/settings/connecteddevice/ConnectedDeviceGroupController.java b/src/com/android/settings/connecteddevice/ConnectedDeviceGroupController.java
index 3cccc15..3d5d0e5 100644
--- a/src/com/android/settings/connecteddevice/ConnectedDeviceGroupController.java
+++ b/src/com/android/settings/connecteddevice/ConnectedDeviceGroupController.java
@@ -20,6 +20,7 @@
 import android.support.v7.preference.PreferenceGroup;
 import android.support.v7.preference.PreferenceScreen;
 
+import com.android.settings.connecteddevice.usb.ConnectedUsbDeviceUpdater;
 import com.android.settings.core.PreferenceControllerMixin;
 import com.android.settings.bluetooth.BluetoothDeviceUpdater;
 import com.android.settings.bluetooth.ConnectedBluetoothDeviceUpdater;
@@ -48,7 +49,7 @@
     public ConnectedDeviceGroupController(DashboardFragment fragment, Lifecycle lifecycle) {
         super(fragment.getContext());
         init(lifecycle, new ConnectedBluetoothDeviceUpdater(fragment, this),
-                new ConnectedUsbDeviceUpdater(fragment.getContext(), this));
+                new ConnectedUsbDeviceUpdater(fragment, this));
     }
 
     @VisibleForTesting
diff --git a/src/com/android/settings/connecteddevice/UsbConnectionBroadcastReceiver.java b/src/com/android/settings/connecteddevice/UsbConnectionBroadcastReceiver.java
deleted file mode 100644
index 07a7691..0000000
--- a/src/com/android/settings/connecteddevice/UsbConnectionBroadcastReceiver.java
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright (C) 2017 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.settings.connecteddevice;
-
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.hardware.usb.UsbManager;
-
-/**
- * Receiver to receive usb update and use {@link UsbConnectionListener} to invoke callback
- */
-public class UsbConnectionBroadcastReceiver extends BroadcastReceiver {
-    private Context mContext;
-    private UsbConnectionListener mUsbConnectionListener;
-    private boolean mListeningToUsbEvents;
-    private boolean mConnected;
-
-    public UsbConnectionBroadcastReceiver(Context context,
-            UsbConnectionListener usbConnectionListener) {
-        mContext = context;
-        mUsbConnectionListener = usbConnectionListener;
-    }
-
-    @Override
-    public void onReceive(Context context, Intent intent) {
-        mConnected = intent != null
-                && intent.getExtras().getBoolean(UsbManager.USB_CONNECTED);
-        if (mUsbConnectionListener != null) {
-            mUsbConnectionListener.onUsbConnectionChanged(mConnected);
-        }
-    }
-
-    public void register() {
-        if (!mListeningToUsbEvents) {
-            final IntentFilter intentFilter = new IntentFilter(UsbManager.ACTION_USB_STATE);
-            final Intent intent = mContext.registerReceiver(this, intentFilter);
-            mConnected = intent != null
-                    && intent.getExtras().getBoolean(UsbManager.USB_CONNECTED);
-            mListeningToUsbEvents = true;
-        }
-    }
-
-    public void unregister() {
-        if (mListeningToUsbEvents) {
-            mContext.unregisterReceiver(this);
-            mListeningToUsbEvents = false;
-        }
-    }
-
-    public boolean isConnected() {
-        return mConnected;
-    }
-
-    /**
-     * Interface definition for a callback to be invoked when usb connection is changed.
-     */
-    interface UsbConnectionListener {
-        void onUsbConnectionChanged(boolean connected);
-    }
-}
diff --git a/src/com/android/settings/connecteddevice/ConnectedUsbDeviceUpdater.java b/src/com/android/settings/connecteddevice/usb/ConnectedUsbDeviceUpdater.java
similarity index 65%
rename from src/com/android/settings/connecteddevice/ConnectedUsbDeviceUpdater.java
rename to src/com/android/settings/connecteddevice/usb/ConnectedUsbDeviceUpdater.java
index 0468b0f..dd29902 100644
--- a/src/com/android/settings/connecteddevice/ConnectedUsbDeviceUpdater.java
+++ b/src/com/android/settings/connecteddevice/usb/ConnectedUsbDeviceUpdater.java
@@ -13,22 +13,24 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.settings.connecteddevice;
+package com.android.settings.connecteddevice.usb;
 
 import android.content.Context;
-import android.content.Intent;
+import android.os.Bundle;
 import android.support.annotation.VisibleForTesting;
+import android.support.v14.preference.PreferenceFragment;
 
 import com.android.settings.R;
-import com.android.settings.deviceinfo.UsbBackend;
-import com.android.settings.deviceinfo.UsbModeChooserActivity;
+import com.android.settings.SettingsActivity;
+import com.android.settings.connecteddevice.DevicePreferenceCallback;
+import com.android.settings.dashboard.DashboardFragment;
 import com.android.settings.widget.GearPreference;
 
 /**
  * Controller to maintain connected usb device
  */
 public class ConnectedUsbDeviceUpdater {
-    private Context mContext;
+    private PreferenceFragment mFragment;
     private UsbBackend mUsbBackend;
     private DevicePreferenceCallback mDevicePreferenceCallback;
     @VisibleForTesting
@@ -36,8 +38,9 @@
     @VisibleForTesting
     UsbConnectionBroadcastReceiver mUsbReceiver;
 
-    private UsbConnectionBroadcastReceiver.UsbConnectionListener mUsbConnectionListener =
-            (connected) -> {
+    @VisibleForTesting
+    UsbConnectionBroadcastReceiver.UsbConnectionListener mUsbConnectionListener =
+            (connected, newMode) -> {
                 if (connected) {
                     mUsbPreference.setSummary(
                             UsbModePreferenceController.getSummary(mUsbBackend.getCurrentMode()));
@@ -47,18 +50,19 @@
                 }
             };
 
-    public ConnectedUsbDeviceUpdater(Context context,
+    public ConnectedUsbDeviceUpdater(DashboardFragment fragment,
             DevicePreferenceCallback devicePreferenceCallback) {
-        this(context, devicePreferenceCallback, new UsbBackend(context));
+        this(fragment, devicePreferenceCallback, new UsbBackend(fragment.getContext()));
     }
 
     @VisibleForTesting
-    ConnectedUsbDeviceUpdater(Context context, DevicePreferenceCallback devicePreferenceCallback,
-            UsbBackend usbBackend) {
-        mContext = context;
+    ConnectedUsbDeviceUpdater(DashboardFragment fragment,
+            DevicePreferenceCallback devicePreferenceCallback, UsbBackend usbBackend) {
+        mFragment = fragment;
         mDevicePreferenceCallback = devicePreferenceCallback;
         mUsbBackend = usbBackend;
-        mUsbReceiver = new UsbConnectionBroadcastReceiver(context, mUsbConnectionListener);
+        mUsbReceiver = new UsbConnectionBroadcastReceiver(fragment.getContext(),
+                mUsbConnectionListener, mUsbBackend);
     }
 
     public void registerCallback() {
@@ -76,8 +80,12 @@
         mUsbPreference.setIcon(R.drawable.ic_usb);
         mUsbPreference.setSelectable(false);
         mUsbPreference.setOnGearClickListener((GearPreference p) -> {
-            final Intent intent = new Intent(mContext, UsbModeChooserActivity.class);
-            mContext.startActivity(intent);
+            // New version - uses a separate screen.
+            final Bundle args = new Bundle();
+            final SettingsActivity activity = (SettingsActivity) mFragment.getContext();
+            activity.startPreferencePanel(mFragment,
+                    UsbDetailsFragment.class.getName(), args,
+                    R.string.device_details_title, null /* titleText */, null /* resultTo */, 0);
         });
 
         forceUpdate();
@@ -87,6 +95,5 @@
         // Register so we can get the connection state from sticky intent.
         //TODO(b/70336520): Use an API to get data instead of sticky intent
         mUsbReceiver.register();
-        mUsbConnectionListener.onUsbConnectionChanged(mUsbReceiver.isConnected());
     }
 }
diff --git a/src/com/android/settings/connecteddevice/usb/OWNERS b/src/com/android/settings/connecteddevice/usb/OWNERS
new file mode 100644
index 0000000..add985c
--- /dev/null
+++ b/src/com/android/settings/connecteddevice/usb/OWNERS
@@ -0,0 +1,3 @@
+# Default reviewers for this and subdirectories.
+zhangjerry@google.com
+badhri@google.com
diff --git a/src/com/android/settings/connecteddevice/usb/UsbBackend.java b/src/com/android/settings/connecteddevice/usb/UsbBackend.java
new file mode 100644
index 0000000..cdfb6b0
--- /dev/null
+++ b/src/com/android/settings/connecteddevice/usb/UsbBackend.java
@@ -0,0 +1,247 @@
+/*
+ * Copyright (C) 2015 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.settings.connecteddevice.usb;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.hardware.usb.UsbManager;
+import android.hardware.usb.UsbPort;
+import android.hardware.usb.UsbPortStatus;
+import android.net.ConnectivityManager;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.support.annotation.VisibleForTesting;
+
+public class UsbBackend {
+
+    public static final int MODE_POWER_MASK  = 0x01;
+    public static final int MODE_POWER_SINK   = 0x00;
+    public static final int MODE_POWER_SOURCE = 0x01;
+
+    public static final int MODE_DATA_MASK  = 0x0f << 1;
+    public static final int MODE_DATA_NONE   = 0;
+    public static final int MODE_DATA_MTP    = 0x01 << 1;
+    public static final int MODE_DATA_PTP    = 0x01 << 2;
+    public static final int MODE_DATA_MIDI   = 0x01 << 3;
+    public static final int MODE_DATA_TETHER   = 0x01 << 4;
+
+    private final boolean mFileTransferRestricted;
+    private final boolean mFileTransferRestrictedBySystem;
+    private final boolean mTetheringRestricted;
+    private final boolean mTetheringRestrictedBySystem;
+    private final boolean mMidiSupported;
+    private final boolean mTetheringSupported;
+
+    private UsbManager mUsbManager;
+    @VisibleForTesting
+    UsbManagerPassThrough mUsbManagerPassThrough;
+    private UsbPort mPort;
+    private UsbPortStatus mPortStatus;
+
+    private Context mContext;
+
+    public UsbBackend(Context context) {
+        this(context, new UserRestrictionUtil(context), null);
+    }
+
+    @VisibleForTesting
+    public UsbBackend(Context context, UserRestrictionUtil userRestrictionUtil,
+            UsbManagerPassThrough usbManagerPassThrough) {
+        mContext = context;
+        mUsbManager = context.getSystemService(UsbManager.class);
+
+        mUsbManagerPassThrough = usbManagerPassThrough;
+        if (mUsbManagerPassThrough == null) {
+            mUsbManagerPassThrough = new UsbManagerPassThrough(mUsbManager);
+        }
+
+        mFileTransferRestricted = userRestrictionUtil.isUsbFileTransferRestricted();
+        mFileTransferRestrictedBySystem = userRestrictionUtil.isUsbFileTransferRestrictedBySystem();
+        mTetheringRestricted = userRestrictionUtil.isUsbTetheringRestricted();
+        mTetheringRestrictedBySystem = userRestrictionUtil.isUsbTetheringRestrictedBySystem();
+
+        mMidiSupported = context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_MIDI);
+        ConnectivityManager cm =
+                (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
+        mTetheringSupported = cm.isTetheringSupported();
+
+        UsbPort[] ports = mUsbManager.getPorts();
+        if (ports == null) {
+            return;
+        }
+        // For now look for a connected port, in the future we should identify port in the
+        // notification and pick based on that.
+        final int N = ports.length;
+        for (int i = 0; i < N; i++) {
+            UsbPortStatus status = mUsbManager.getPortStatus(ports[i]);
+            if (status.isConnected()) {
+                mPort = ports[i];
+                mPortStatus = status;
+                break;
+            }
+        }
+    }
+
+    public int getCurrentMode() {
+        if (mPort != null) {
+            int power = mPortStatus.getCurrentPowerRole() == UsbPort.POWER_ROLE_SOURCE
+                    && mPortStatus.isConnected()
+                    ? MODE_POWER_SOURCE : MODE_POWER_SINK;
+            return power | getUsbDataMode();
+        }
+        return MODE_POWER_SINK | getUsbDataMode();
+    }
+
+    public int getUsbDataMode() {
+        long functions = mUsbManagerPassThrough.getCurrentFunctions();
+        if (functions == UsbManager.FUNCTION_MTP) {
+            return MODE_DATA_MTP;
+        } else if (functions == UsbManager.FUNCTION_PTP) {
+            return MODE_DATA_PTP;
+        } else if (functions == UsbManager.FUNCTION_MIDI) {
+            return MODE_DATA_MIDI;
+        } else if (functions == UsbManager.FUNCTION_RNDIS) {
+            return MODE_DATA_TETHER;
+        }
+        return MODE_DATA_NONE;
+    }
+
+    private void setUsbFunction(int mode) {
+        switch (mode) {
+            case MODE_DATA_MTP:
+                mUsbManager.setCurrentFunctions(UsbManager.FUNCTION_MTP);
+                break;
+            case MODE_DATA_PTP:
+                mUsbManager.setCurrentFunctions(UsbManager.FUNCTION_PTP);
+                break;
+            case MODE_DATA_MIDI:
+                mUsbManager.setCurrentFunctions(UsbManager.FUNCTION_MIDI);
+                break;
+            case MODE_DATA_TETHER:
+                mUsbManager.setCurrentFunctions(UsbManager.FUNCTION_RNDIS);
+                break;
+            default:
+                mUsbManager.setCurrentFunctions(UsbManager.FUNCTION_NONE);
+                break;
+        }
+    }
+
+    public void setMode(int mode) {
+        if (mPort != null) {
+            int powerRole = modeToPower(mode);
+            // If we aren't using any data modes and we support host mode, then go to host mode
+            // so maybe? the other device can provide data if it wants, otherwise go into device
+            // mode because we have no choice.
+            int dataRole = (mode & MODE_DATA_MASK) == MODE_DATA_NONE
+                    && mPortStatus.isRoleCombinationSupported(powerRole, UsbPort.DATA_ROLE_HOST)
+                    ? UsbPort.DATA_ROLE_HOST : UsbPort.DATA_ROLE_DEVICE;
+            mUsbManager.setPortRoles(mPort, powerRole, dataRole);
+        }
+        setUsbFunction(mode & MODE_DATA_MASK);
+    }
+
+    private int modeToPower(int mode) {
+        return (mode & MODE_POWER_MASK) == MODE_POWER_SOURCE
+                    ? UsbPort.POWER_ROLE_SOURCE : UsbPort.POWER_ROLE_SINK;
+    }
+
+    public boolean isModeDisallowed(int mode) {
+        if (mFileTransferRestricted && ((mode & MODE_DATA_MASK) == MODE_DATA_MTP
+                || (mode & MODE_DATA_MASK) == MODE_DATA_PTP)) {
+            return true;
+        } else if (mTetheringRestricted && ((mode & MODE_DATA_MASK) == MODE_DATA_TETHER)) {
+            return true;
+        }
+        return false;
+    }
+
+    public boolean isModeDisallowedBySystem(int mode) {
+        if (mFileTransferRestrictedBySystem && ((mode & MODE_DATA_MASK) == MODE_DATA_MTP
+                || (mode & MODE_DATA_MASK) == MODE_DATA_PTP)) {
+            return true;
+        } else if (mTetheringRestrictedBySystem && ((mode & MODE_DATA_MASK) == MODE_DATA_TETHER)) {
+            return true;
+        }
+        return false;
+    }
+
+    public boolean isModeSupported(int mode) {
+        if (!mMidiSupported && (mode & MODE_DATA_MASK) == MODE_DATA_MIDI) {
+            return false;
+        }
+        if (!mTetheringSupported && (mode & MODE_DATA_MASK) == MODE_DATA_TETHER) {
+                return false;
+        }
+        if (mPort != null) {
+            int power = modeToPower(mode);
+            if ((mode & MODE_DATA_MASK) != 0) {
+                // We have a port and data, need to be in device mode.
+                return mPortStatus.isRoleCombinationSupported(power,
+                        UsbPort.DATA_ROLE_DEVICE);
+            } else {
+                // No data needed, we can do this power mode in either device or host.
+                return mPortStatus.isRoleCombinationSupported(power, UsbPort.DATA_ROLE_DEVICE)
+                        || mPortStatus.isRoleCombinationSupported(power, UsbPort.DATA_ROLE_HOST);
+            }
+        }
+        // No port, support sink modes only.
+        return (mode & MODE_POWER_MASK) != MODE_POWER_SOURCE;
+    }
+
+    // Wrapper class to enable testing with UserManager APIs
+    public static class UserRestrictionUtil {
+        private UserManager mUserManager;
+
+        public UserRestrictionUtil(Context context) {
+            mUserManager = UserManager.get(context);
+        }
+
+        public boolean isUsbFileTransferRestricted() {
+            return mUserManager.hasUserRestriction(UserManager.DISALLOW_USB_FILE_TRANSFER);
+        }
+
+        public boolean isUsbTetheringRestricted() {
+            return mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_TETHERING);
+        }
+
+        public boolean isUsbFileTransferRestrictedBySystem() {
+            return mUserManager.hasBaseUserRestriction(
+                UserManager.DISALLOW_USB_FILE_TRANSFER, UserHandle.of(UserHandle.myUserId()));
+        }
+
+        public boolean isUsbTetheringRestrictedBySystem() {
+            return mUserManager.hasBaseUserRestriction(
+                UserManager.DISALLOW_CONFIG_TETHERING, UserHandle.of(UserHandle.myUserId()));
+        }
+    }
+
+    // Temporary pass-through to allow roboelectric to use getCurrentFunctions()
+    public static class UsbManagerPassThrough {
+        private UsbManager mUsbManager;
+
+        public UsbManagerPassThrough(UsbManager manager) {
+            mUsbManager = manager;
+        }
+
+        public long getCurrentFunctions() {
+            return mUsbManager.getCurrentFunctions();
+        }
+
+        public long usbFunctionsFromString(String str) {
+            return UsbManager.usbFunctionsFromString(str);
+        }
+    }
+}
diff --git a/src/com/android/settings/connecteddevice/usb/UsbConnectionBroadcastReceiver.java b/src/com/android/settings/connecteddevice/usb/UsbConnectionBroadcastReceiver.java
new file mode 100644
index 0000000..91d22dc
--- /dev/null
+++ b/src/com/android/settings/connecteddevice/usb/UsbConnectionBroadcastReceiver.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2017 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.settings.connecteddevice.usb;
+
+
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.hardware.usb.UsbManager;
+import android.hardware.usb.UsbPort;
+import android.hardware.usb.UsbPortStatus;
+
+import com.android.settingslib.core.lifecycle.LifecycleObserver;
+import com.android.settingslib.core.lifecycle.events.OnResume;
+import com.android.settingslib.core.lifecycle.events.OnPause;
+
+/**
+ * Receiver to receive usb update and use {@link UsbConnectionListener} to invoke callback
+ */
+public class UsbConnectionBroadcastReceiver extends BroadcastReceiver implements LifecycleObserver,
+        OnResume, OnPause {
+    private Context mContext;
+    private UsbConnectionListener mUsbConnectionListener;
+    private boolean mListeningToUsbEvents;
+    private int mMode;
+    private boolean mConnected;
+    private UsbBackend mUsbBackend;
+
+    public UsbConnectionBroadcastReceiver(Context context,
+            UsbConnectionListener usbConnectionListener, UsbBackend backend) {
+        mContext = context;
+        mUsbConnectionListener = usbConnectionListener;
+        mUsbBackend = backend;
+    }
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        if (UsbManager.ACTION_USB_STATE.equals(intent.getAction())) {
+            mConnected = intent.getExtras().getBoolean(UsbManager.USB_CONNECTED)
+                    || intent.getExtras().getBoolean(UsbManager.USB_HOST_CONNECTED);
+            if (mConnected) {
+                mMode &= UsbBackend.MODE_POWER_MASK;
+                if (intent.getExtras().getBoolean(UsbManager.USB_FUNCTION_MTP)
+                        && intent.getExtras().getBoolean(UsbManager.USB_DATA_UNLOCKED, false)) {
+                    mMode |= UsbBackend.MODE_DATA_MTP;
+                }
+                if (intent.getExtras().getBoolean(UsbManager.USB_FUNCTION_PTP)
+                        && intent.getExtras().getBoolean(UsbManager.USB_DATA_UNLOCKED, false)) {
+                    mMode |= UsbBackend.MODE_DATA_PTP;
+                }
+                if (intent.getExtras().getBoolean(UsbManager.USB_FUNCTION_MIDI)) {
+                    mMode |= UsbBackend.MODE_DATA_MIDI;
+                }
+                if (intent.getExtras().getBoolean(UsbManager.USB_FUNCTION_RNDIS)) {
+                    mMode |= UsbBackend.MODE_DATA_TETHER;
+                }
+            }
+        } else if (UsbManager.ACTION_USB_PORT_CHANGED.equals(intent.getAction())) {
+            mMode &= UsbBackend.MODE_DATA_MASK;
+            UsbPortStatus portStatus = intent.getExtras()
+                    .getParcelable(UsbManager.EXTRA_PORT_STATUS);
+            if (portStatus != null) {
+                mConnected = portStatus.isConnected();
+                if (mConnected) {
+                    mMode |= portStatus.getCurrentPowerRole() == UsbPort.POWER_ROLE_SOURCE
+                            ? UsbBackend.MODE_POWER_SOURCE : UsbBackend.MODE_POWER_SINK;
+                }
+            }
+        }
+        if (mUsbConnectionListener != null) {
+            mUsbConnectionListener.onUsbConnectionChanged(mConnected, mMode);
+        }
+    }
+
+    public void register() {
+        if (!mListeningToUsbEvents) {
+            mMode = mUsbBackend.getCurrentMode();
+            mConnected = false;
+            final IntentFilter intentFilter = new IntentFilter();
+            intentFilter.addAction(UsbManager.ACTION_USB_STATE);
+            intentFilter.addAction(UsbManager.ACTION_USB_PORT_CHANGED);
+            mContext.registerReceiver(this, intentFilter);
+            mListeningToUsbEvents = true;
+        }
+    }
+
+    public void unregister() {
+        if (mListeningToUsbEvents) {
+            mContext.unregisterReceiver(this);
+            mListeningToUsbEvents = false;
+        }
+    }
+
+    public boolean isConnected() {
+        return mConnected;
+    }
+
+    @Override
+    public void onResume() {
+        register();
+    }
+
+    @Override
+    public void onPause() {
+        unregister();
+    }
+
+    /**
+     * Interface definition for a callback to be invoked when usb connection is changed.
+     */
+    interface UsbConnectionListener {
+        void onUsbConnectionChanged(boolean connected, int newMode);
+    }
+}
diff --git a/src/com/android/settings/connecteddevice/usb/UsbDetailsController.java b/src/com/android/settings/connecteddevice/usb/UsbDetailsController.java
new file mode 100644
index 0000000..09c7554
--- /dev/null
+++ b/src/com/android/settings/connecteddevice/usb/UsbDetailsController.java
@@ -0,0 +1,54 @@
+/*
+ * 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.settings.connecteddevice.usb;
+
+import android.content.Context;
+import android.support.annotation.UiThread;
+import android.support.v14.preference.PreferenceFragment;
+
+import com.android.settings.core.PreferenceControllerMixin;
+import com.android.settingslib.core.AbstractPreferenceController;
+
+/**
+ * This class provides common members and refresh functionality for usb controllers.
+ */
+public abstract class UsbDetailsController extends AbstractPreferenceController
+        implements PreferenceControllerMixin {
+
+    protected final Context mContext;
+    protected final PreferenceFragment mFragment;
+    protected final UsbBackend mUsbBackend;
+
+    public UsbDetailsController(Context context, PreferenceFragment fragment, UsbBackend backend) {
+        super(context);
+        mContext = context;
+        mFragment = fragment;
+        mUsbBackend = backend;
+    }
+
+    @Override
+    public boolean isAvailable() {
+        return true;
+    }
+
+    /**
+     * This method is called when the USB mode has changed and the controller needs to update.
+     * @param newMode the new mode, made up of OR'd values from UsbBackend
+     */
+    @UiThread
+    protected abstract void refresh(int newMode);
+}
diff --git a/src/com/android/settings/connecteddevice/usb/UsbDetailsFragment.java b/src/com/android/settings/connecteddevice/usb/UsbDetailsFragment.java
new file mode 100644
index 0000000..c861188
--- /dev/null
+++ b/src/com/android/settings/connecteddevice/usb/UsbDetailsFragment.java
@@ -0,0 +1,131 @@
+/*
+ * 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.settings.connecteddevice.usb;
+
+import android.content.Context;
+import android.hardware.usb.UsbManager;
+import android.os.Bundle;
+import android.provider.SearchIndexableResource;
+import android.support.annotation.VisibleForTesting;
+
+import com.android.internal.logging.nano.MetricsProto;
+import com.android.settings.R;
+
+import com.android.settings.dashboard.DashboardFragment;
+import com.android.settings.search.BaseSearchIndexProvider;
+import com.android.settings.search.Indexable;
+import com.android.settingslib.core.AbstractPreferenceController;
+
+import com.google.android.collect.Lists;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Controls the USB device details and provides updates to individual controllers.
+ */
+public class UsbDetailsFragment extends DashboardFragment {
+    private static final String TAG = UsbDetailsFragment.class.getSimpleName();
+
+    private List<UsbDetailsController> mControllers;
+    private UsbBackend mUsbBackend;
+
+    @VisibleForTesting
+    UsbConnectionBroadcastReceiver mUsbReceiver;
+
+    private UsbConnectionBroadcastReceiver.UsbConnectionListener mUsbConnectionListener =
+            (connected, newMode) -> {
+                if (!connected) {
+                    this.finish();
+                } else {
+                    for (UsbDetailsController controller : mControllers) {
+                        controller.refresh(newMode);
+                    }
+                }
+            };
+
+    @Override
+    public int getMetricsCategory() {
+        return MetricsProto.MetricsEvent.USB_DEVICE_DETAILS;
+    }
+
+    @Override
+    protected String getLogTag() {
+        return TAG;
+    }
+
+    @Override
+    protected int getPreferenceScreenResId() {
+        return R.xml.usb_details_fragment;
+    }
+
+    @Override
+    public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
+        super.onCreatePreferences(savedInstanceState, rootKey);
+    }
+
+    @Override
+    protected List<AbstractPreferenceController> getPreferenceControllers(Context context) {
+        mUsbBackend = new UsbBackend(context);
+        mControllers = createControllerList(context, mUsbBackend, this);
+        mUsbReceiver = new UsbConnectionBroadcastReceiver(context, mUsbConnectionListener,
+                mUsbBackend);
+        this.getLifecycle().addObserver(mUsbReceiver);
+
+        List<AbstractPreferenceController> ret = new ArrayList<>();
+        ret.addAll(mControllers);
+        return ret;
+    }
+
+    private static List<UsbDetailsController> createControllerList(Context context,
+            UsbBackend usbBackend, DashboardFragment fragment) {
+        List<UsbDetailsController> ret = new ArrayList<>();
+        ret.add(new UsbDetailsHeaderController(context, fragment, usbBackend));
+        ret.add(new UsbDetailsProfilesController(context, fragment,
+                usbBackend, Lists.newArrayList(UsbManager.USB_FUNCTION_MTP), "usb_main_options"));
+        ret.add(new UsbDetailsProfilesController(context, fragment,
+                usbBackend, Lists.newArrayList(UsbDetailsProfilesController.KEY_POWER,
+                UsbManager.USB_FUNCTION_RNDIS, UsbManager.USB_FUNCTION_MIDI,
+                UsbManager.USB_FUNCTION_PTP), "usb_secondary_options"));
+        return ret;
+    }
+
+    /**
+     * For Search.
+     */
+    public static final Indexable.SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
+            new BaseSearchIndexProvider() {
+                @Override
+                public List<SearchIndexableResource> getXmlResourcesToIndex(
+                        Context context, boolean enabled) {
+                    return new ArrayList<>();
+                }
+
+                @Override
+                public List<String> getNonIndexableKeys(Context context) {
+                    return super.getNonIndexableKeys(context);
+                }
+
+                @Override
+                public List<AbstractPreferenceController> getPreferenceControllers(
+                        Context context) {
+                    List<AbstractPreferenceController> ret = new ArrayList<>();
+                    ret.addAll(createControllerList(context, new UsbBackend(context), null));
+                    return ret;
+                }
+            };
+}
diff --git a/src/com/android/settings/connecteddevice/usb/UsbDetailsHeaderController.java b/src/com/android/settings/connecteddevice/usb/UsbDetailsHeaderController.java
new file mode 100644
index 0000000..7ac0235
--- /dev/null
+++ b/src/com/android/settings/connecteddevice/usb/UsbDetailsHeaderController.java
@@ -0,0 +1,64 @@
+/*
+ * 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.settings.connecteddevice.usb;
+
+import android.content.Context;
+import android.support.v14.preference.PreferenceFragment;
+import android.support.v7.preference.PreferenceScreen;
+
+import com.android.settings.R;
+import com.android.settings.applications.LayoutPreference;
+import com.android.settings.widget.EntityHeaderController;
+
+/**
+ * This class adds a header with device name and current function.
+ */
+public class UsbDetailsHeaderController extends UsbDetailsController {
+    private static final String KEY_DEVICE_HEADER = "usb_device_header";
+
+    private EntityHeaderController mHeaderController;
+
+    public UsbDetailsHeaderController(Context context, PreferenceFragment fragment,
+            UsbBackend backend) {
+        super(context, fragment, backend);
+    }
+
+    @Override
+    public void displayPreference(PreferenceScreen screen) {
+        super.displayPreference(screen);
+        final LayoutPreference headerPreference =
+                (LayoutPreference) screen.findPreference(KEY_DEVICE_HEADER);
+        mHeaderController = EntityHeaderController.newInstance(mFragment.getActivity(), mFragment,
+                headerPreference.findViewById(R.id.entity_header));
+        screen.addPreference(headerPreference);
+    }
+
+
+    @Override
+    protected void refresh(int newMode) {
+        mHeaderController.setLabel(mContext.getString(R.string.usb_pref));
+        mHeaderController.setIcon(mContext.getDrawable(R.drawable.ic_usb));
+        mHeaderController.setSummary(
+                mContext.getString(UsbModePreferenceController.getSummary(newMode)));
+        mHeaderController.done(mFragment.getActivity(), true /* rebindActions */);
+    }
+
+    @Override
+    public String getPreferenceKey() {
+        return KEY_DEVICE_HEADER;
+    }
+}
diff --git a/src/com/android/settings/connecteddevice/usb/UsbDetailsProfilesController.java b/src/com/android/settings/connecteddevice/usb/UsbDetailsProfilesController.java
new file mode 100644
index 0000000..1375b4c
--- /dev/null
+++ b/src/com/android/settings/connecteddevice/usb/UsbDetailsProfilesController.java
@@ -0,0 +1,147 @@
+/*
+ * 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.settings.connecteddevice.usb;
+
+import com.android.settings.R;
+import android.content.Context;
+import android.hardware.usb.UsbManager;
+import android.support.v14.preference.PreferenceFragment;
+import android.support.v14.preference.SwitchPreference;
+import android.support.v7.preference.Preference;
+import android.support.v7.preference.PreferenceCategory;
+import android.support.v7.preference.PreferenceScreen;
+
+import java.util.List;
+
+/**
+ * This class adds switches for toggling individual USB options, such as "transfer files",
+ * "supply power", "usb tethering", etc.
+ */
+public class UsbDetailsProfilesController extends UsbDetailsController
+        implements Preference.OnPreferenceClickListener {
+
+    static final String KEY_POWER = "power";
+
+    private PreferenceCategory mProfilesContainer;
+    private List<String> mOptions;
+    private String mKey;
+
+    public UsbDetailsProfilesController(Context context, PreferenceFragment fragment,
+            UsbBackend backend, List<String> options, String key) {
+        super(context, fragment, backend);
+        mOptions = options;
+        mKey = key;
+    }
+
+    @Override
+    public void displayPreference(PreferenceScreen screen) {
+        super.displayPreference(screen);
+        mProfilesContainer = (PreferenceCategory) screen.findPreference(getPreferenceKey());
+    }
+
+    /**
+     * Gets a switch preference for the particular option, creating it if needed.
+     */
+    private SwitchPreference getProfilePreference(String key, int titleId) {
+        SwitchPreference pref = (SwitchPreference) mProfilesContainer.findPreference(key);
+        if (pref == null) {
+            pref = new SwitchPreference(mProfilesContainer.getContext());
+            pref.setKey(key);
+            pref.setTitle(titleId);
+            pref.setOnPreferenceClickListener(this);
+            mProfilesContainer.addPreference(pref);
+        }
+        return pref;
+    }
+
+    @Override
+    protected void refresh(int mode) {
+        SwitchPreference pref;
+        for (String option : mOptions) {
+            int newMode;
+            int summary = -1;
+            int title;
+            if (option.equals(UsbManager.USB_FUNCTION_MTP)) {
+                newMode = UsbBackend.MODE_DATA_MTP;
+                title = R.string.usb_use_file_transfers;
+            } else if (option.equals(KEY_POWER)) {
+                newMode = UsbBackend.MODE_POWER_SOURCE;
+                title = R.string.usb_use_power_only;
+                summary = R.string.usb_use_power_only_desc;
+            } else if (option.equals(UsbManager.USB_FUNCTION_PTP)) {
+                newMode = UsbBackend.MODE_DATA_PTP;
+                title = R.string.usb_use_photo_transfers;
+            } else if (option.equals(UsbManager.USB_FUNCTION_MIDI)) {
+                newMode = UsbBackend.MODE_DATA_MIDI;
+                title = R.string.usb_use_MIDI;
+            } else if (option.equals(UsbManager.USB_FUNCTION_RNDIS)) {
+                newMode = UsbBackend.MODE_DATA_TETHER;
+                title = R.string.usb_use_tethering;
+            } else {
+                continue;
+            }
+
+            pref = getProfilePreference(option, title);
+            // Only show supported and allowed options
+            if (mUsbBackend.isModeSupported(newMode)
+                    && !mUsbBackend.isModeDisallowedBySystem(newMode)
+                    && !mUsbBackend.isModeDisallowed(newMode)) {
+                if (summary != -1) {
+                    pref.setSummary(summary);
+                }
+                pref.setChecked((mode & newMode) != 0);
+            } else {
+                mProfilesContainer.removePreference(pref);
+            }
+        }
+    }
+
+    @Override
+    public boolean onPreferenceClick(Preference preference) {
+        SwitchPreference profilePref = (SwitchPreference) preference;
+        String key = profilePref.getKey();
+        int mode = mUsbBackend.getCurrentMode();
+        int thisMode = 0;
+        if (key.equals(KEY_POWER)) {
+            thisMode = UsbBackend.MODE_POWER_SOURCE;
+        } else if (key.equals(UsbManager.USB_FUNCTION_MTP)) {
+            thisMode = UsbBackend.MODE_DATA_MTP;
+        } else if (key.equals(UsbManager.USB_FUNCTION_PTP)) {
+            thisMode = UsbBackend.MODE_DATA_PTP;
+        } else if (key.equals(UsbManager.USB_FUNCTION_RNDIS)) {
+            thisMode = UsbBackend.MODE_DATA_TETHER;
+        } else if (key.equals(UsbManager.USB_FUNCTION_MIDI)) {
+            thisMode = UsbBackend.MODE_DATA_MIDI;
+        }
+        if (profilePref.isChecked()) {
+            if (!key.equals(KEY_POWER)) {
+                // Only one non power mode can currently be set at once.
+                mode &= UsbBackend.MODE_POWER_MASK;
+            }
+            mode |= thisMode;
+        } else {
+            mode &= ~thisMode;
+        }
+        mUsbBackend.setMode(mode);
+        return false;
+    }
+
+    @Override
+    public String getPreferenceKey() {
+        return mKey;
+    }
+}
diff --git a/src/com/android/settings/deviceinfo/UsbModeChooserActivity.java b/src/com/android/settings/connecteddevice/usb/UsbModeChooserActivity.java
similarity index 98%
rename from src/com/android/settings/deviceinfo/UsbModeChooserActivity.java
rename to src/com/android/settings/connecteddevice/usb/UsbModeChooserActivity.java
index 8ba3781..b3b0718 100644
--- a/src/com/android/settings/deviceinfo/UsbModeChooserActivity.java
+++ b/src/com/android/settings/connecteddevice/usb/UsbModeChooserActivity.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.settings.deviceinfo;
+package com.android.settings.connecteddevice.usb;
 
 import android.annotation.Nullable;
 import android.app.Activity;
diff --git a/src/com/android/settings/connecteddevice/UsbModePreferenceController.java b/src/com/android/settings/connecteddevice/usb/UsbModePreferenceController.java
similarity index 72%
rename from src/com/android/settings/connecteddevice/UsbModePreferenceController.java
rename to src/com/android/settings/connecteddevice/usb/UsbModePreferenceController.java
index 8693520..e342460 100644
--- a/src/com/android/settings/connecteddevice/UsbModePreferenceController.java
+++ b/src/com/android/settings/connecteddevice/usb/UsbModePreferenceController.java
@@ -13,15 +13,15 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.settings.connecteddevice;
+package com.android.settings.connecteddevice.usb;
 
 import android.content.Context;
 import android.support.v7.preference.Preference;
 import android.support.v7.preference.PreferenceScreen;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.settings.R;
 import com.android.settings.core.PreferenceControllerMixin;
-import com.android.settings.deviceinfo.UsbBackend;
 import com.android.settingslib.core.AbstractPreferenceController;
 import com.android.settingslib.core.lifecycle.LifecycleObserver;
 import com.android.settingslib.core.lifecycle.events.OnPause;
@@ -33,27 +33,27 @@
     private static final String KEY_USB_MODE = "usb_mode";
 
     private UsbBackend mUsbBackend;
-    private UsbConnectionBroadcastReceiver mUsbReceiver;
+    @VisibleForTesting
+    UsbConnectionBroadcastReceiver mUsbReceiver;
     private Preference mUsbPreference;
 
     public UsbModePreferenceController(Context context, UsbBackend usbBackend) {
         super(context);
         mUsbBackend = usbBackend;
-        mUsbReceiver = new UsbConnectionBroadcastReceiver(mContext, (connected) -> {
-            updateSummary(mUsbPreference);
-        });
+        mUsbReceiver = new UsbConnectionBroadcastReceiver(mContext, (connected, newMode) -> {
+            updateSummary(mUsbPreference, connected, newMode);
+        }, mUsbBackend);
     }
 
     @Override
     public void displayPreference(PreferenceScreen screen) {
         super.displayPreference(screen);
         mUsbPreference = screen.findPreference(KEY_USB_MODE);
-        updateSummary(mUsbPreference);
     }
 
     @Override
     public void updateState(Preference preference) {
-        updateSummary(preference);
+        updateSummary(preference, mUsbReceiver.isConnected(), mUsbBackend.getCurrentMode());
     }
 
     @Override
@@ -88,17 +88,24 @@
                 return R.string.usb_summary_photo_transfers;
             case UsbBackend.MODE_POWER_SINK | UsbBackend.MODE_DATA_MIDI:
                 return R.string.usb_summary_MIDI;
+            case UsbBackend.MODE_POWER_SINK | UsbBackend.MODE_DATA_TETHER:
+                return R.string.usb_summary_tether;
+            case UsbBackend.MODE_POWER_SOURCE | UsbBackend.MODE_DATA_MTP:
+                return R.string.usb_summary_file_transfers_power;
+            case UsbBackend.MODE_POWER_SOURCE | UsbBackend.MODE_DATA_PTP:
+                return R.string.usb_summary_photo_transfers_power;
+            case UsbBackend.MODE_POWER_SOURCE | UsbBackend.MODE_DATA_MIDI:
+                return R.string.usb_summary_MIDI_power;
+            case UsbBackend.MODE_POWER_SOURCE | UsbBackend.MODE_DATA_TETHER:
+                return R.string.usb_summary_tether_power;
+            default:
+                return R.string.usb_summary_charging_only;
         }
-        return 0;
     }
 
-    private void updateSummary(Preference preference) {
-        updateSummary(preference, mUsbBackend.getCurrentMode());
-    }
-
-    private void updateSummary(Preference preference, int mode) {
+    private void updateSummary(Preference preference, boolean connected, int mode) {
         if (preference != null) {
-            if (mUsbReceiver.isConnected()) {
+            if (connected) {
                 preference.setEnabled(true);
                 preference.setSummary(getSummary(mode));
             } else {
@@ -107,5 +114,4 @@
             }
         }
     }
-
 }
diff --git a/src/com/android/settings/core/gateway/SettingsGateway.java b/src/com/android/settings/core/gateway/SettingsGateway.java
index b07cf84..a9358ef 100644
--- a/src/com/android/settings/core/gateway/SettingsGateway.java
+++ b/src/com/android/settings/core/gateway/SettingsGateway.java
@@ -60,6 +60,7 @@
 import com.android.settings.connecteddevice.AdvancedConnectedDeviceDashboardFragment;
 import com.android.settings.connecteddevice.ConnectedDeviceDashboardFragment;
 import com.android.settings.connecteddevice.ConnectedDeviceDashboardFragmentOld;
+import com.android.settings.connecteddevice.usb.UsbDetailsFragment;
 import com.android.settings.datausage.DataPlanUsageSummary;
 import com.android.settings.datausage.DataUsageList;
 import com.android.settings.datausage.DataUsageSummary;
@@ -246,6 +247,7 @@
             NetworkDashboardFragment.class.getName(),
             ConnectedDeviceDashboardFragment.class.getName(),
             ConnectedDeviceDashboardFragmentOld.class.getName(),
+            UsbDetailsFragment.class.getName(),
             AppAndNotificationDashboardFragment.class.getName(),
             AccountDashboardFragment.class.getName(),
             EnterprisePrivacySettings.class.getName(),
diff --git a/src/com/android/settings/development/SelectUsbConfigPreferenceController.java b/src/com/android/settings/development/SelectUsbConfigPreferenceController.java
index 77a9a75..63eb24c 100644
--- a/src/com/android/settings/development/SelectUsbConfigPreferenceController.java
+++ b/src/com/android/settings/development/SelectUsbConfigPreferenceController.java
@@ -27,11 +27,11 @@
 import android.support.v7.preference.ListPreference;
 import android.support.v7.preference.Preference;
 import android.support.v7.preference.PreferenceScreen;
-import android.text.TextUtils;
 
 import com.android.settings.R;
 import com.android.settings.Utils;
 import com.android.settings.core.PreferenceControllerMixin;
+import com.android.settings.connecteddevice.usb.UsbBackend;
 import com.android.settingslib.core.lifecycle.Lifecycle;
 import com.android.settingslib.core.lifecycle.LifecycleObserver;
 import com.android.settingslib.core.lifecycle.events.OnCreate;
@@ -48,6 +48,8 @@
     private final String[] mListValues;
     private final String[] mListSummaries;
     private final UsbManager mUsbManager;
+    @VisibleForTesting
+    UsbBackend.UsbManagerPassThrough mUsbManagerPassThrough;
     private BroadcastReceiver mUsbReceiver;
     private ListPreference mPreference;
 
@@ -57,6 +59,7 @@
         mListValues = context.getResources().getStringArray(R.array.usb_configuration_values);
         mListSummaries = context.getResources().getStringArray(R.array.usb_configuration_titles);
         mUsbManager = (UsbManager) context.getSystemService(Context.USB_SERVICE);
+        mUsbManagerPassThrough = new UsbBackend.UsbManagerPassThrough(mUsbManager);
         mUsbReceiver = new BroadcastReceiver() {
             @Override
             public void onReceive(Context context, Intent intent) {
@@ -95,7 +98,8 @@
             return false;
         }
 
-        writeUsbConfigurationOption(newValue.toString());
+        writeUsbConfigurationOption(mUsbManagerPassThrough
+                .usbFunctionsFromString(newValue.toString()));
         updateUsbConfigurationValues();
         return true;
     }
@@ -129,14 +133,15 @@
     }
 
     @VisibleForTesting
-    void setCurrentFunction(String newValue, boolean usbDataUnlocked) {
-        mUsbManager.setCurrentFunction(newValue, usbDataUnlocked);
+    void setCurrentFunctions(long functions) {
+        mUsbManager.setCurrentFunctions(functions);
     }
 
     private void updateUsbConfigurationValues() {
+        long functions = mUsbManagerPassThrough.getCurrentFunctions();
         int index = 0;
         for (int i = 0; i < mListValues.length; i++) {
-            if (mUsbManager.isFunctionEnabled(mListValues[i])) {
+            if (functions == mUsbManagerPassThrough.usbFunctionsFromString(mListValues[i])) {
                 index = i;
                 break;
             }
@@ -145,11 +150,7 @@
         mPreference.setSummary(mListSummaries[index]);
     }
 
-    private void writeUsbConfigurationOption(String newValue) {
-        if (TextUtils.equals(newValue, "none")) {
-            setCurrentFunction(newValue, false);
-        } else {
-            setCurrentFunction(newValue, true);
-        }
+    private void writeUsbConfigurationOption(long newValue) {
+        setCurrentFunctions(newValue);
     }
 }
diff --git a/src/com/android/settings/deviceinfo/UsbBackend.java b/src/com/android/settings/deviceinfo/UsbBackend.java
deleted file mode 100644
index 5d2502b..0000000
--- a/src/com/android/settings/deviceinfo/UsbBackend.java
+++ /dev/null
@@ -1,202 +0,0 @@
-/*
- * Copyright (C) 2015 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.settings.deviceinfo;
-
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.PackageManager;
-import android.hardware.usb.UsbManager;
-import android.hardware.usb.UsbPort;
-import android.hardware.usb.UsbPortStatus;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.support.annotation.VisibleForTesting;
-
-public class UsbBackend {
-
-    public static final int MODE_POWER_MASK  = 0x01;
-    public static final int MODE_POWER_SINK   = 0x00;
-    public static final int MODE_POWER_SOURCE = 0x01;
-
-    public static final int MODE_DATA_MASK  = 0x03 << 1;
-    public static final int MODE_DATA_NONE   = 0x00 << 1;
-    public static final int MODE_DATA_MTP    = 0x01 << 1;
-    public static final int MODE_DATA_PTP    = 0x02 << 1;
-    public static final int MODE_DATA_MIDI   = 0x03 << 1;
-
-    private final boolean mRestricted;
-    private final boolean mRestrictedBySystem;
-    private final boolean mMidi;
-
-    private UsbManager mUsbManager;
-    private UsbPort mPort;
-    private UsbPortStatus mPortStatus;
-
-    private Context mContext;
-
-    public UsbBackend(Context context) {
-        this(context, new UserRestrictionUtil(context));
-    }
-
-    @VisibleForTesting
-    public UsbBackend(Context context, UserRestrictionUtil userRestrictionUtil) {
-        mContext = context;
-        mUsbManager = context.getSystemService(UsbManager.class);
-
-        mRestricted = userRestrictionUtil.isUsbFileTransferRestricted();
-        mRestrictedBySystem = userRestrictionUtil.isUsbFileTransferRestrictedBySystem();
-        mMidi = context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_MIDI);
-
-        UsbPort[] ports = mUsbManager.getPorts();
-        if (ports == null) {
-            return;
-        }
-        // For now look for a connected port, in the future we should identify port in the
-        // notification and pick based on that.
-        final int N = ports.length;
-        for (int i = 0; i < N; i++) {
-            UsbPortStatus status = mUsbManager.getPortStatus(ports[i]);
-            if (status.isConnected()) {
-                mPort = ports[i];
-                mPortStatus = status;
-                break;
-            }
-        }
-    }
-
-    public int getCurrentMode() {
-        if (mPort != null) {
-            int power = mPortStatus.getCurrentPowerRole() == UsbPort.POWER_ROLE_SOURCE
-                    ? MODE_POWER_SOURCE : MODE_POWER_SINK;
-            return power | getUsbDataMode();
-        }
-        return MODE_POWER_SINK | getUsbDataMode();
-    }
-
-    public int getUsbDataMode() {
-        if (!isUsbDataUnlocked()) {
-            return MODE_DATA_NONE;
-        } else if (mUsbManager.isFunctionEnabled(UsbManager.USB_FUNCTION_MTP)) {
-            return MODE_DATA_MTP;
-        } else if (mUsbManager.isFunctionEnabled(UsbManager.USB_FUNCTION_PTP)) {
-            return MODE_DATA_PTP;
-        } else if (mUsbManager.isFunctionEnabled(UsbManager.USB_FUNCTION_MIDI)) {
-            return MODE_DATA_MIDI;
-        }
-        return MODE_DATA_NONE; // ...
-    }
-
-    private boolean isUsbDataUnlocked() {
-        Intent intent = mContext.registerReceiver(null,
-            new IntentFilter(UsbManager.ACTION_USB_STATE));
-        return intent == null ?
-            false : intent.getBooleanExtra(UsbManager.USB_DATA_UNLOCKED, false);
-    }
-
-    private void setUsbFunction(int mode) {
-        switch (mode) {
-            case MODE_DATA_MTP:
-                mUsbManager.setCurrentFunction(UsbManager.USB_FUNCTION_MTP, true);
-                break;
-            case MODE_DATA_PTP:
-                mUsbManager.setCurrentFunction(UsbManager.USB_FUNCTION_PTP, true);
-                break;
-            case MODE_DATA_MIDI:
-                mUsbManager.setCurrentFunction(UsbManager.USB_FUNCTION_MIDI, true);
-                break;
-            default:
-                mUsbManager.setCurrentFunction(null, false);
-                break;
-        }
-    }
-
-    public void setMode(int mode) {
-        if (mPort != null) {
-            int powerRole = modeToPower(mode);
-            // If we aren't using any data modes and we support host mode, then go to host mode
-            // so maybe? the other device can provide data if it wants, otherwise go into device
-            // mode because we have no choice.
-            int dataRole = (mode & MODE_DATA_MASK) == MODE_DATA_NONE
-                    && mPortStatus.isRoleCombinationSupported(powerRole, UsbPort.DATA_ROLE_HOST)
-                    ? UsbPort.DATA_ROLE_HOST : UsbPort.DATA_ROLE_DEVICE;
-            mUsbManager.setPortRoles(mPort, powerRole, dataRole);
-        }
-        setUsbFunction(mode & MODE_DATA_MASK);
-    }
-
-    private int modeToPower(int mode) {
-        return (mode & MODE_POWER_MASK) == MODE_POWER_SOURCE
-                    ? UsbPort.POWER_ROLE_SOURCE : UsbPort.POWER_ROLE_SINK;
-    }
-
-    public boolean isModeDisallowed(int mode) {
-        if (mRestricted && (mode & MODE_DATA_MASK) != MODE_DATA_NONE
-                && (mode & MODE_DATA_MASK) != MODE_DATA_MIDI) {
-            // No USB data modes are supported.
-            return true;
-        }
-        return false;
-    }
-
-    public boolean isModeDisallowedBySystem(int mode) {
-        if (mRestrictedBySystem && (mode & MODE_DATA_MASK) != MODE_DATA_NONE
-                && (mode & MODE_DATA_MASK) != MODE_DATA_MIDI) {
-            // No USB data modes are supported.
-            return true;
-        }
-        return false;
-    }
-
-    public boolean isModeSupported(int mode) {
-        if (!mMidi && (mode & MODE_DATA_MASK) == MODE_DATA_MIDI) {
-            return false;
-        }
-
-        if (mPort != null) {
-            int power = modeToPower(mode);
-            if ((mode & MODE_DATA_MASK) != 0) {
-                // We have a port and data, need to be in device mode.
-                return mPortStatus.isRoleCombinationSupported(power,
-                        UsbPort.DATA_ROLE_DEVICE);
-            } else {
-                // No data needed, we can do this power mode in either device or host.
-                return mPortStatus.isRoleCombinationSupported(power, UsbPort.DATA_ROLE_DEVICE)
-                        || mPortStatus.isRoleCombinationSupported(power, UsbPort.DATA_ROLE_HOST);
-            }
-        }
-        // No port, support sink modes only.
-        return (mode & MODE_POWER_MASK) != MODE_POWER_SOURCE;
-    }
-
-    // Wrapper class to enable testing with UserManager APIs
-    public static class UserRestrictionUtil {
-        private UserManager mUserManager;
-
-        public UserRestrictionUtil(Context context) {
-            mUserManager = UserManager.get(context);
-        }
-
-        public boolean isUsbFileTransferRestricted() {
-            return mUserManager.hasUserRestriction(UserManager.DISALLOW_USB_FILE_TRANSFER);
-        }
-
-        public boolean isUsbFileTransferRestrictedBySystem() {
-            return mUserManager.hasBaseUserRestriction(
-                UserManager.DISALLOW_USB_FILE_TRANSFER, UserHandle.of(UserHandle.myUserId()));
-        }
-    }
-}
diff --git a/src/com/android/settings/search/SearchIndexableResourcesImpl.java b/src/com/android/settings/search/SearchIndexableResourcesImpl.java
index 38dc15d..1edc2de 100644
--- a/src/com/android/settings/search/SearchIndexableResourcesImpl.java
+++ b/src/com/android/settings/search/SearchIndexableResourcesImpl.java
@@ -21,6 +21,8 @@
 import com.android.settings.DateTimeSettings;
 import com.android.settings.DisplaySettings;
 import com.android.settings.LegalSettings;
+import com.android.settings.connecteddevice.ConnectedDeviceDashboardFragmentOld;
+import com.android.settings.deviceinfo.aboutphone.MyDeviceInfoFragment;
 import com.android.settings.accessibility.AccessibilitySettings;
 import com.android.settings.accessibility.AccessibilityShortcutPreferenceFragment;
 import com.android.settings.accessibility.MagnificationPreferenceFragment;
@@ -34,7 +36,7 @@
 import com.android.settings.bluetooth.BluetoothSettings;
 import com.android.settings.connecteddevice.AdvancedConnectedDeviceDashboardFragment;
 import com.android.settings.connecteddevice.ConnectedDeviceDashboardFragment;
-import com.android.settings.connecteddevice.ConnectedDeviceDashboardFragmentOld;
+import com.android.settings.connecteddevice.usb.UsbDetailsFragment;
 import com.android.settings.datausage.DataUsageSummary;
 import com.android.settings.deletionhelper.AutomaticStorageManagerSettings;
 import com.android.settings.development.DevelopmentSettingsDashboardFragment;
@@ -167,6 +169,7 @@
         addIndex(PowerUsageSummary.class);
         addIndex(BatterySaverSettings.class);
         addIndex(LockscreenDashboardFragment.class);
+        addIndex(UsbDetailsFragment.class);
         addIndex(WifiDisplaySettings.class);
         addIndex(ZenModeBehaviorSettings.class);
         addIndex(ZenModeAutomationSettings.class);