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/AndroidManifest.xml b/AndroidManifest.xml
index f9e123a..03d4796 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -2012,13 +2012,21 @@
</intent-filter>
</activity>
- <activity android:name=".deviceinfo.UsbModeChooserActivity"
+ <activity android:name=".connecteddevice.usb.UsbModeChooserActivity"
android:excludeFromRecents="true"
android:exported="true"
android:permission="android.permission.MANAGE_USB"
android:theme="@*android:style/Theme.DeviceDefault.Settings.Dialog.NoActionBar">
</activity>
+ <activity android:name=".Settings$UsbDetailsActivity"
+ android:excludeFromRecents="true"
+ android:permission="android.permission.MANAGE_USB"
+ android:exported="true">
+ <meta-data android:name="com.android.settings.FRAGMENT_CLASS"
+ android:value="com.android.settings.connecteddevice.usb.UsbDetailsFragment"/>
+ </activity>
+
<activity android:name=".RemoteBugreportActivity"
android:excludeFromRecents="true"
android:exported="true"
diff --git a/res/values/strings.xml b/res/values/strings.xml
index fe32e9d..4e48c7d 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -8000,15 +8000,15 @@
<!-- Title of one of the choices in a dialog (with title defined in usb_use) that lets the user
select what the USB connection for this device should be used for. This choice
is for powering the other device only. -->
- <string name="usb_use_power_only">Supply power</string>
+ <string name="usb_use_power_only">Charging connected device</string>
<!-- Decription of one of the choices in a dialog (with title defined in usb_use) that lets the
user select what the USB connection for this device should be used for. This choice
is for powering the other device. -->
- <string name="usb_use_power_only_desc">Charge the connected device. Works only with devices that support USB charging.</string>
+ <string name="usb_use_power_only_desc">Other settings unavailable when turned on</string>
<!-- Title of one of the choices in a dialog (with title defined in usb_use) that lets the user
select what the USB connection for this device should be used for. This choice
is for transferring files via MTP. -->
- <string name="usb_use_file_transfers">Transfer files</string>
+ <string name="usb_use_file_transfers">File Transfer</string>
<!-- Description of one of the choices in a dialog (with title defined in usb_use) that lets the user
select what the USB connection for this device should be used for. This choice
is for transferring files via MTP. -->
@@ -8016,23 +8016,31 @@
<!-- Title of one of the choices in a dialog (with title defined in usb_use) that lets the user
select what the USB connection for this device should be used for. This choice
is for transferring photos via PTP. -->
- <string name="usb_use_photo_transfers">Transfer photos (PTP)</string>
+ <string name="usb_use_photo_transfers">PTP</string>
<!-- Description of one of the choices in a dialog (with title defined in usb_use) that lets the user
select what the USB connection for this device should be used for. This choice
is for transferring photos via PTP. -->
<string name="usb_use_photo_transfers_desc">Transfer photos or files if MTP is not supported (PTP)</string>
<!-- Title of one of the choices in a dialog (with title defined in usb_use) that lets the user
select what the USB connection for this device should be used for. This choice
+ is for USB tethering. -->
+ <string name="usb_use_tethering">USB tethering</string>
+ <!-- Title of one of the choices in a dialog (with title defined in usb_use) that lets the user
+ select what the USB connection for this device should be used for. This choice
is for entering MIDI mode. -->
- <string name="usb_use_MIDI">Use device as MIDI</string>
+ <string name="usb_use_MIDI">MIDI</string>
<!-- Description of one of the choices in a dialog (with title defined in usb_use) that lets the user
select what the USB connection for this device should be used for. This choice
is for entering MIDI mode. -->
<string name="usb_use_MIDI_desc">Use this device as MIDI</string>
<!-- The title used in a dialog which lets the user select what the USB connection
- for this device should be used for. Choices are usb_use_charging_only,
- usb_use_file_transfer, use_use_photo_transfer, and usb_use_MIDI -->
- <string name="usb_use">Use USB to</string>
+ for this device should be used for. These options are more commonly used.
+ Choices are usb_use_file_transfer.-->
+ <string name="usb_use">Use USB for</string>
+ <!-- The title used in a dialog which lets the user select what the USB connection
+ for this device should be used for. These options are less commonly used.
+ Choices are usb_use_tethering, usb_use_photo_transfers, usb_use_MIDI, and usb_use_power_only.-->
+ <string name="usb_use_also">Also use USB for</string>
<!-- Settings item title for USB preference [CHAR LIMIT=35] -->
<string name="usb_pref">USB</string>
@@ -8040,13 +8048,27 @@
<!-- Settings item summary for USB preference when set to charging only [CHAR LIMIT=NONE] -->
<string name="usb_summary_charging_only">Charging this device</string>
<!-- Settings item summary for USB preference when set to powering the other device only [CHAR LIMIT=NONE] -->
- <string name="usb_summary_power_only">Supplying power</string>
+ <string name="usb_summary_power_only">Charging connected device</string>
<!-- Settings item summary for USB preference when set to transferring files via MTP [CHAR LIMIT=NONE] -->
- <string name="usb_summary_file_transfers">Transferring files</string>
+ <string name="usb_summary_file_transfers">File transfer</string>
+ <!-- Settings item summary for USB preference when set to USB tethering [CHAR LIMIT=NONE] -->
+ <string name="usb_summary_tether">USB tethering</string>
<!-- Settings item summary for USB preference when set to transferring photos via PTP [CHAR LIMIT=NONE] -->
- <string name="usb_summary_photo_transfers">Transferring photos (PTP)</string>
+ <string name="usb_summary_photo_transfers">PTP</string>
<!-- Settings item summary for USB preference when set to entering MIDI mode [CHAR LIMIT=NONE] -->
- <string name="usb_summary_MIDI">Using device as MIDI</string>
+ <string name="usb_summary_MIDI">MIDI</string>
+ <!-- Settings item summary for USB preference when set to transferring files via MTP
+ and powering other device [CHAR LIMIT=NONE] -->
+ <string name="usb_summary_file_transfers_power">File transfer and supplying power</string>
+ <!-- Settings item summary for USB preference when set to USB tethering
+ and powering other device [CHAR LIMIT=NONE] -->
+ <string name="usb_summary_tether_power">USB tethering and supplying power</string>
+ <!-- Settings item summary for USB preference when set to transferring photos via PTP
+ and powering other device [CHAR LIMIT=NONE] -->
+ <string name="usb_summary_photo_transfers_power">PTP and supplying power</string>
+ <!-- Settings item summary for USB preference when set to entering MIDI mode
+ and powering other device [CHAR LIMIT=NONE] -->
+ <string name="usb_summary_MIDI_power">MIDI and supplying power</string>
<!-- Settings item title for SMS Mirroring preference [CHAR LIMIT=35] -->
<string name="sms_mirroring_pref">SMS Mirroring</string>
diff --git a/res/xml/connected_devices_advanced.xml b/res/xml/connected_devices_advanced.xml
index 8ca6b81..0b75abf 100644
--- a/res/xml/connected_devices_advanced.xml
+++ b/res/xml/connected_devices_advanced.xml
@@ -57,16 +57,6 @@
android:order="-2"/>
<Preference
- android:key="usb_mode"
- android:title="@string/usb_pref"
- android:icon="@drawable/ic_usb"
- android:order="-1">
- <intent android:action="android.intent.action.MAIN"
- android:targetPackage="com.android.settings"
- android:targetClass="com.android.settings.deviceinfo.UsbModeChooserActivity"/>
- </Preference>
-
- <Preference
android:key="bt_received_files"
android:icon="@drawable/ic_folder_vd_theme_24"
android:title="@string/bluetooth_show_received_files" />
diff --git a/res/xml/connected_devices_old.xml b/res/xml/connected_devices_old.xml
index 3eb62ea..cc7b5b4 100644
--- a/res/xml/connected_devices_old.xml
+++ b/res/xml/connected_devices_old.xml
@@ -53,7 +53,7 @@
android:order="-2">
<intent android:action="android.intent.action.MAIN"
android:targetPackage="com.android.settings"
- android:targetClass="com.android.settings.deviceinfo.UsbModeChooserActivity"/>
+ android:targetClass="com.android.settings.connecteddevice.usb.UsbModeChooserActivity"/>
</Preference>
<PreferenceCategory
diff --git a/res/xml/usb_details_fragment.xml b/res/xml/usb_details_fragment.xml
new file mode 100644
index 0000000..30ca993
--- /dev/null
+++ b/res/xml/usb_details_fragment.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+ -->
+<PreferenceScreen
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:settings="http://schemas.android.com/apk/res-auto"
+ android:title="@string/device_details_title">
+
+ <com.android.settings.applications.LayoutPreference
+ android:key="usb_device_header"
+ android:layout="@layout/settings_entity_header"
+ android:selectable="false"/>
+
+ <PreferenceCategory
+ android:key="usb_main_options"
+ android:title="@string/usb_use"/>
+
+ <PreferenceCategory
+ android:key="usb_secondary_options"
+ android:title="@string/usb_use_also"/>
+
+</PreferenceScreen>
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);
diff --git a/tests/robotests/src/android/hardware/usb/UsbManagerExtras.java b/tests/robotests/src/android/hardware/usb/UsbManagerExtras.java
new file mode 100644
index 0000000..b9bccd2
--- /dev/null
+++ b/tests/robotests/src/android/hardware/usb/UsbManagerExtras.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2010 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 android.hardware.usb;
+
+import android.annotation.SystemService;
+import android.content.Context;
+import android.hardware.usb.gadget.V1_0.GadgetFunction;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.StringJoiner;
+
+/**
+ * Definitions that were added to UsbManager in P.
+ *
+ * Copied partially from frameworks/base/core/java/android/hardware/usb/UsbManager to
+ * fix issues with roboelectric during test.
+ */
+@SystemService(Context.USB_SERVICE)
+public class UsbManagerExtras {
+ public static final long NONE = 0;
+ public static final long MTP = GadgetFunction.MTP;
+ public static final long PTP = GadgetFunction.PTP;
+ public static final long RNDIS = GadgetFunction.RNDIS;
+ public static final long MIDI = GadgetFunction.MIDI;
+ public static final long ACCESSORY = GadgetFunction.ACCESSORY;
+ public static final long AUDIO_SOURCE = GadgetFunction.AUDIO_SOURCE;
+ public static final long ADB = GadgetFunction.ADB;
+
+ private static final long SETTABLE_FUNCTIONS = MTP | PTP | RNDIS | MIDI;
+
+ private static final Map<String, Long> STR_MAP = new HashMap<>();
+
+ static {
+ STR_MAP.put(UsbManager.USB_FUNCTION_MTP, MTP);
+ STR_MAP.put(UsbManager.USB_FUNCTION_PTP, PTP);
+ STR_MAP.put(UsbManager.USB_FUNCTION_RNDIS, RNDIS);
+ STR_MAP.put(UsbManager.USB_FUNCTION_MIDI, MIDI);
+ STR_MAP.put(UsbManager.USB_FUNCTION_ACCESSORY, ACCESSORY);
+ STR_MAP.put(UsbManager.USB_FUNCTION_AUDIO_SOURCE, AUDIO_SOURCE);
+ STR_MAP.put(UsbManager.USB_FUNCTION_ADB, ADB);
+ }
+
+ /**
+ * Returns whether the given functions are valid inputs to UsbManager.
+ * Currently the empty functions or any of MTP, PTP, RNDIS, MIDI are accepted.
+ */
+ public static boolean isSettableFunctions(long functions) {
+ return (~SETTABLE_FUNCTIONS & functions) == 0;
+ }
+
+ /**
+ * Returns the string representation of the given functions.
+ */
+ public static String usbFunctionsToString(long functions) {
+ StringJoiner joiner = new StringJoiner(",");
+ if ((functions | MTP) != 0) {
+ joiner.add(UsbManager.USB_FUNCTION_MTP);
+ }
+ if ((functions | PTP) != 0) {
+ joiner.add(UsbManager.USB_FUNCTION_PTP);
+ }
+ if ((functions | RNDIS) != 0) {
+ joiner.add(UsbManager.USB_FUNCTION_RNDIS);
+ }
+ if ((functions | MIDI) != 0) {
+ joiner.add(UsbManager.USB_FUNCTION_MIDI);
+ }
+ if ((functions | ACCESSORY) != 0) {
+ joiner.add(UsbManager.USB_FUNCTION_ACCESSORY);
+ }
+ if ((functions | AUDIO_SOURCE) != 0) {
+ joiner.add(UsbManager.USB_FUNCTION_AUDIO_SOURCE);
+ }
+ if ((functions | ADB) != 0) {
+ joiner.add(UsbManager.USB_FUNCTION_ADB);
+ }
+ return joiner.toString();
+ }
+
+ /**
+ * Parses a string of usb functions and returns a mask of the same functions.
+ */
+ public static long usbFunctionsFromString(String functions) {
+ if (functions == null) {
+ return 0;
+ }
+ long ret = 0;
+ for (String function : functions.split(",")) {
+ if (STR_MAP.containsKey(function)) {
+ ret |= STR_MAP.get(function);
+ }
+ }
+ return ret;
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/connecteddevice/ConnectedDeviceGroupControllerTest.java b/tests/robotests/src/com/android/settings/connecteddevice/ConnectedDeviceGroupControllerTest.java
index 78be742..b478c4e 100644
--- a/tests/robotests/src/com/android/settings/connecteddevice/ConnectedDeviceGroupControllerTest.java
+++ b/tests/robotests/src/com/android/settings/connecteddevice/ConnectedDeviceGroupControllerTest.java
@@ -31,6 +31,7 @@
import com.android.settings.TestConfig;
import com.android.settings.bluetooth.ConnectedBluetoothDeviceUpdater;
+import com.android.settings.connecteddevice.usb.ConnectedUsbDeviceUpdater;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.testutils.SettingsRobolectricTestRunner;
import com.android.settingslib.core.lifecycle.Lifecycle;
diff --git a/tests/robotests/src/com/android/settings/connecteddevice/ConnectedUsbDeviceUpdaterTest.java b/tests/robotests/src/com/android/settings/connecteddevice/usb/ConnectedUsbDeviceUpdaterTest.java
similarity index 79%
rename from tests/robotests/src/com/android/settings/connecteddevice/ConnectedUsbDeviceUpdaterTest.java
rename to tests/robotests/src/com/android/settings/connecteddevice/usb/ConnectedUsbDeviceUpdaterTest.java
index 16cd3a7..011d620 100644
--- a/tests/robotests/src/com/android/settings/connecteddevice/ConnectedUsbDeviceUpdaterTest.java
+++ b/tests/robotests/src/com/android/settings/connecteddevice/usb/ConnectedUsbDeviceUpdaterTest.java
@@ -13,18 +13,20 @@
* 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 static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
import android.content.Context;
import com.android.settings.R;
import com.android.settings.TestConfig;
-import com.android.settings.deviceinfo.UsbBackend;
+import com.android.settings.connecteddevice.DevicePreferenceCallback;
+import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.testutils.SettingsRobolectricTestRunner;
import org.junit.Before;
@@ -42,6 +44,8 @@
private ConnectedUsbDeviceUpdater mDeviceUpdater;
@Mock
+ private DashboardFragment mFragment;
+ @Mock
private UsbConnectionBroadcastReceiver mUsbReceiver;
@Mock
private DevicePreferenceCallback mDevicePreferenceCallback;
@@ -53,7 +57,8 @@
MockitoAnnotations.initMocks(this);
mContext = RuntimeEnvironment.application;
- mDeviceUpdater = new ConnectedUsbDeviceUpdater(mContext, mDevicePreferenceCallback,
+ when(mFragment.getContext()).thenReturn(mContext);
+ mDeviceUpdater = new ConnectedUsbDeviceUpdater(mFragment, mDevicePreferenceCallback,
mUsbBackend);
mDeviceUpdater.mUsbReceiver = mUsbReceiver;
}
@@ -70,18 +75,18 @@
@Test
public void testInitUsbPreference_usbConnected_preferenceAdded() {
- doReturn(true).when(mUsbReceiver).isConnected();
-
mDeviceUpdater.initUsbPreference(mContext);
+ mDeviceUpdater.mUsbConnectionListener.onUsbConnectionChanged(true /* connected */,
+ UsbBackend.MODE_DATA_NONE);
verify(mDevicePreferenceCallback).onDeviceAdded(mDeviceUpdater.mUsbPreference);
}
@Test
public void testInitUsbPreference_usbDisconnected_preferenceRemoved() {
- doReturn(false).when(mUsbReceiver).isConnected();
-
mDeviceUpdater.initUsbPreference(mContext);
+ mDeviceUpdater.mUsbConnectionListener.onUsbConnectionChanged(false /* connected */,
+ UsbBackend.MODE_DATA_NONE);
verify(mDevicePreferenceCallback).onDeviceRemoved(mDeviceUpdater.mUsbPreference);
}
diff --git a/tests/robotests/src/com/android/settings/deviceinfo/UsbBackendTest.java b/tests/robotests/src/com/android/settings/connecteddevice/usb/UsbBackendTest.java
similarity index 81%
rename from tests/robotests/src/com/android/settings/deviceinfo/UsbBackendTest.java
rename to tests/robotests/src/com/android/settings/connecteddevice/usb/UsbBackendTest.java
index ce384a5..40cfd73 100644
--- a/tests/robotests/src/com/android/settings/deviceinfo/UsbBackendTest.java
+++ b/tests/robotests/src/com/android/settings/connecteddevice/usb/UsbBackendTest.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.settings.deviceinfo;
+package com.android.settings.connecteddevice.usb;
import static org.mockito.Answers.RETURNS_DEEP_STUBS;
import static org.mockito.Matchers.argThat;
@@ -25,7 +25,9 @@
import android.content.Context;
import android.content.pm.PackageManager;
import android.hardware.usb.UsbManager;
+import android.net.ConnectivityManager;
+import com.android.settings.connecteddevice.usb.UsbBackend;
import com.android.settings.testutils.SettingsRobolectricTestRunner;
import com.android.settings.TestConfig;
@@ -46,6 +48,8 @@
private UsbManager mUsbManager;
@Mock
private UsbBackend.UserRestrictionUtil mUserRestrictionUtil;
+ @Mock
+ private ConnectivityManager mConnectivityManager;
@Before
public void setUp() {
@@ -53,22 +57,13 @@
when(mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_MIDI))
.thenReturn(true);
when((Object)mContext.getSystemService(UsbManager.class)).thenReturn(mUsbManager);
+ when(mContext.getSystemService(Context.CONNECTIVITY_SERVICE))
+ .thenReturn((Object) mConnectivityManager);
}
@Test
public void constructor_noUsbPort_shouldNotCrash() {
- UsbBackend usbBackend = new UsbBackend(mContext, mUserRestrictionUtil);
+ UsbBackend usbBackend = new UsbBackend(mContext, mUserRestrictionUtil, null);
// Should not crash
}
-
- @Test
- public void getCurrentMode_shouldRegisterReceiverToGetUsbState() {
- UsbBackend usbBackend = new UsbBackend(mContext, mUserRestrictionUtil);
-
- usbBackend.getCurrentMode();
-
- verify(mContext).registerReceiver(eq(null),
- argThat(intentFilter -> intentFilter != null &&
- UsbManager.ACTION_USB_STATE.equals(intentFilter.getAction(0))));
- }
}
diff --git a/tests/robotests/src/com/android/settings/connecteddevice/UsbConnectionBroadcastReceiverTest.java b/tests/robotests/src/com/android/settings/connecteddevice/usb/UsbConnectionBroadcastReceiverTest.java
similarity index 81%
rename from tests/robotests/src/com/android/settings/connecteddevice/UsbConnectionBroadcastReceiverTest.java
rename to tests/robotests/src/com/android/settings/connecteddevice/usb/UsbConnectionBroadcastReceiverTest.java
index 06bd5b7..50b47e0 100644
--- a/tests/robotests/src/com/android/settings/connecteddevice/UsbConnectionBroadcastReceiverTest.java
+++ b/tests/robotests/src/com/android/settings/connecteddevice/usb/UsbConnectionBroadcastReceiverTest.java
@@ -13,7 +13,7 @@
* 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 static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
@@ -52,6 +52,8 @@
@Mock
private UsbConnectionBroadcastReceiver.UsbConnectionListener mListener;
+ @Mock
+ private UsbBackend mUsbBackend;
@Before
public void setUp() {
@@ -59,27 +61,42 @@
mShadowApplication = ShadowApplication.getInstance();
mContext = RuntimeEnvironment.application;
- mReceiver = new UsbConnectionBroadcastReceiver(mContext, mListener);
+ mReceiver = new UsbConnectionBroadcastReceiver(mContext, mListener, mUsbBackend);
}
@Test
public void testOnReceive_usbConnected_invokeCallback() {
final Intent intent = new Intent();
+ intent.setAction(UsbManager.ACTION_USB_STATE);
intent.putExtra(UsbManager.USB_CONNECTED, true);
mReceiver.onReceive(mContext, intent);
- verify(mListener).onUsbConnectionChanged(true);
+ verify(mListener).onUsbConnectionChanged(true /* connected */, UsbBackend.MODE_DATA_NONE);
}
@Test
public void testOnReceive_usbDisconnected_invokeCallback() {
final Intent intent = new Intent();
+ intent.setAction(UsbManager.ACTION_USB_STATE);
intent.putExtra(UsbManager.USB_CONNECTED, false);
mReceiver.onReceive(mContext, intent);
- verify(mListener).onUsbConnectionChanged(false);
+ verify(mListener).onUsbConnectionChanged(false /* connected */, UsbBackend.MODE_DATA_NONE);
+ }
+
+ @Test
+ public void testOnReceive_usbConnectedMtpEnabled_invokeCallback() {
+ final Intent intent = new Intent();
+ intent.setAction(UsbManager.ACTION_USB_STATE);
+ intent.putExtra(UsbManager.USB_CONNECTED, true);
+ intent.putExtra(UsbManager.USB_FUNCTION_MTP, true);
+ intent.putExtra(UsbManager.USB_DATA_UNLOCKED, true);
+
+ mReceiver.onReceive(mContext, intent);
+
+ verify(mListener).onUsbConnectionChanged(true /* connected */, UsbBackend.MODE_DATA_MTP);
}
@Test
diff --git a/tests/robotests/src/com/android/settings/connecteddevice/usb/UsbDetailsHeaderControllerTest.java b/tests/robotests/src/com/android/settings/connecteddevice/usb/UsbDetailsHeaderControllerTest.java
new file mode 100644
index 0000000..e1f9078
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/connecteddevice/usb/UsbDetailsHeaderControllerTest.java
@@ -0,0 +1,122 @@
+/*
+ * 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 static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.Activity;
+import android.arch.lifecycle.LifecycleOwner;
+import android.content.Context;
+import android.support.v7.preference.PreferenceManager;
+import android.support.v7.preference.PreferenceScreen;
+import android.support.v14.preference.PreferenceFragment;
+
+import com.android.settings.R;
+import com.android.settings.TestConfig;
+import com.android.settings.applications.LayoutPreference;
+import com.android.settings.testutils.FakeFeatureFactory;
+import com.android.settings.testutils.SettingsRobolectricTestRunner;
+import com.android.settings.testutils.shadow.SettingsShadowResources;
+import com.android.settings.testutils.shadow.ShadowEntityHeaderController;
+import com.android.settings.widget.EntityHeaderController;
+import com.android.settingslib.core.lifecycle.Lifecycle;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Answers;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION,
+ shadows = {ShadowEntityHeaderController.class, SettingsShadowResources.class})
+public class UsbDetailsHeaderControllerTest {
+
+ private UsbDetailsHeaderController mDetailsHeaderController;
+ private Context mContext;
+ private Lifecycle mLifecycle;
+ private LifecycleOwner mLifecycleOwner;
+ private LayoutPreference mPreference;
+ private PreferenceManager mPreferenceManager;
+ private PreferenceScreen mScreen;
+
+ @Mock
+ private UsbBackend mUsbBackend;
+ @Mock
+ private PreferenceFragment mFragment;
+ @Mock
+ private Activity mActivity;
+ @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+ private EntityHeaderController mHeaderController;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ mContext = RuntimeEnvironment.application;
+ mLifecycleOwner = () -> mLifecycle;
+ mLifecycle = new Lifecycle(mLifecycleOwner);
+ mPreferenceManager = new PreferenceManager(mContext);
+ mScreen = mPreferenceManager.createPreferenceScreen(mContext);
+
+ when(mFragment.getActivity()).thenReturn(mActivity);
+ when(mActivity.getApplicationContext()).thenReturn(mContext);
+ when(mFragment.getContext()).thenReturn(mContext);
+ when(mFragment.getPreferenceManager()).thenReturn(mPreferenceManager);
+ when(mFragment.getPreferenceScreen()).thenReturn(mScreen);
+
+ ShadowEntityHeaderController.setUseMock(mHeaderController);
+ mDetailsHeaderController = new UsbDetailsHeaderController(mContext, mFragment, mUsbBackend);
+ mPreference = new LayoutPreference(mContext, R.layout.settings_entity_header);
+ mPreference.setKey(mDetailsHeaderController.getPreferenceKey());
+ mScreen.addPreference(mPreference);
+ }
+
+ @After
+ public void tearDown() {
+ ShadowEntityHeaderController.reset();
+ }
+
+ @Test
+ public void displayRefresh_charging_shouldSetHeader() {
+ mDetailsHeaderController.displayPreference(mScreen);
+ mDetailsHeaderController.refresh(UsbBackend.MODE_DATA_NONE);
+ verify(mHeaderController).setLabel(mContext.getString(R.string.usb_pref));
+ verify(mHeaderController).setIcon(mContext.getDrawable(R.drawable.ic_usb));
+ verify(mHeaderController).setSummary(
+ mContext.getString(R.string.usb_summary_charging_only));
+ verify(mHeaderController).done(mActivity, true);
+ }
+
+ @Test
+ public void displayRefresh_mtp_shouldSetHeader() {
+ mDetailsHeaderController.displayPreference(mScreen);
+ mDetailsHeaderController.refresh(UsbBackend.MODE_DATA_MTP);
+ verify(mHeaderController).setLabel(mContext.getString(R.string.usb_pref));
+ verify(mHeaderController).setIcon(mContext.getDrawable(R.drawable.ic_usb));
+ verify(mHeaderController).setSummary(
+ mContext.getString(R.string.usb_summary_file_transfers));
+ verify(mHeaderController).done(mActivity, true);
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/connecteddevice/usb/UsbDetailsProfilesControllerTest.java b/tests/robotests/src/com/android/settings/connecteddevice/usb/UsbDetailsProfilesControllerTest.java
new file mode 100644
index 0000000..557d836
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/connecteddevice/usb/UsbDetailsProfilesControllerTest.java
@@ -0,0 +1,238 @@
+/*
+ * 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 static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.Activity;
+import android.content.Context;
+import android.hardware.usb.UsbManager;
+import android.support.v7.preference.PreferenceCategory;
+import android.support.v7.preference.PreferenceManager;
+import android.support.v7.preference.PreferenceScreen;
+import android.support.v14.preference.PreferenceFragment;
+import android.support.v14.preference.SwitchPreference;
+
+import com.android.settings.R;
+import com.android.settings.TestConfig;
+import com.android.settings.testutils.FakeFeatureFactory;
+import com.android.settings.testutils.SettingsRobolectricTestRunner;
+import com.android.settings.testutils.shadow.SettingsShadowResources;
+import com.android.settingslib.core.lifecycle.Lifecycle;
+
+import com.google.android.collect.Lists;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Answers;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class UsbDetailsProfilesControllerTest {
+
+ private UsbDetailsProfilesController mDetailsProfilesController;
+ private Context mContext;
+ private Lifecycle mLifecycle;
+ private PreferenceCategory mPreference;
+ private PreferenceManager mPreferenceManager;
+ private PreferenceScreen mScreen;
+ private List<String> mOptions;
+
+ @Mock
+ private UsbBackend mUsbBackend;
+ @Mock
+ private PreferenceFragment mFragment;
+ @Mock
+ private Activity mActivity;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ mContext = RuntimeEnvironment.application;
+ mLifecycle = new Lifecycle(() -> mLifecycle);
+ mPreferenceManager = new PreferenceManager(mContext);
+ mScreen = mPreferenceManager.createPreferenceScreen(mContext);
+
+ when(mFragment.getActivity()).thenReturn(mActivity);
+ when(mActivity.getApplicationContext()).thenReturn(mContext);
+ when(mFragment.getContext()).thenReturn(mContext);
+ when(mFragment.getPreferenceManager()).thenReturn(mPreferenceManager);
+ when(mFragment.getPreferenceScreen()).thenReturn(mScreen);
+
+ mOptions = Lists.newArrayList(UsbManager.USB_FUNCTION_MTP, UsbManager.USB_FUNCTION_PTP,
+ UsbManager.USB_FUNCTION_MIDI, UsbDetailsProfilesController.KEY_POWER);
+ mDetailsProfilesController = new UsbDetailsProfilesController(mContext, mFragment,
+ mUsbBackend, mOptions, "usb_options");
+ mPreference = new PreferenceCategory(mContext);
+ mPreference.setKey(mDetailsProfilesController.getPreferenceKey());
+ mScreen.addPreference(mPreference);
+ }
+
+ @Test
+ public void testDisplayRefresh_allAllowed_shouldCreateSwitches() {
+ when(mUsbBackend.isModeSupported(anyInt())).thenReturn(true);
+ when(mUsbBackend.isModeDisallowed(anyInt())).thenReturn(false);
+ when(mUsbBackend.isModeDisallowedBySystem(anyInt())).thenReturn(false);
+
+ mDetailsProfilesController.displayPreference(mScreen);
+ mDetailsProfilesController.refresh(UsbBackend.MODE_DATA_NONE);
+ List<SwitchPreference> switches = getProfileSwitches();
+
+ for (int i = 0; i < switches.size(); i++) {
+ assertThat(switches.get(i).getKey().equals(mOptions.get(i)));
+ }
+ }
+
+ @Test
+ public void testDisplayRefresh_onlyMidiAllowed_shouldCreateOnlyMidiSwitch() {
+ when(mUsbBackend.isModeSupported(anyInt())).thenReturn(true);
+ when(mUsbBackend.isModeDisallowed(anyInt())).thenReturn(false);
+ when(mUsbBackend.isModeDisallowedBySystem(UsbBackend.MODE_DATA_MIDI)).thenReturn(false);
+ when(mUsbBackend.isModeDisallowedBySystem(UsbBackend.MODE_DATA_MTP)).thenReturn(true);
+ when(mUsbBackend.isModeDisallowedBySystem(UsbBackend.MODE_DATA_PTP)).thenReturn(true);
+ when(mUsbBackend.isModeDisallowedBySystem(UsbBackend.MODE_POWER_SOURCE)).thenReturn(true);
+
+ mDetailsProfilesController.displayPreference(mScreen);
+ mDetailsProfilesController.refresh(UsbBackend.MODE_DATA_NONE);
+ List<SwitchPreference> switches = getProfileSwitches();
+ assertThat(switches.size()).isEqualTo(1);
+ assertThat(switches.get(0).getKey()).isEqualTo(UsbManager.USB_FUNCTION_MIDI);
+ }
+
+ @Test
+ public void testDisplayRefresh_mtpEnabled_shouldCheckSwitches() {
+ when(mUsbBackend.isModeSupported(anyInt())).thenReturn(true);
+ when(mUsbBackend.isModeDisallowed(anyInt())).thenReturn(false);
+ when(mUsbBackend.isModeDisallowedBySystem(anyInt())).thenReturn(false);
+
+ mDetailsProfilesController.displayPreference(mScreen);
+ mDetailsProfilesController.refresh(UsbBackend.MODE_DATA_MTP);
+ List<SwitchPreference> switches = getProfileSwitches();
+
+ assertThat(switches.get(0).getKey().equals(UsbManager.USB_FUNCTION_MTP));
+ assertThat(switches.get(0).isChecked());
+ }
+
+ @Test
+ public void testDisplayRefresh_mtpSupplyPowerEnabled_shouldCheckSwitches() {
+ when(mUsbBackend.isModeSupported(anyInt())).thenReturn(true);
+ when(mUsbBackend.isModeDisallowed(anyInt())).thenReturn(false);
+ when(mUsbBackend.isModeDisallowedBySystem(anyInt())).thenReturn(false);
+
+ mDetailsProfilesController.displayPreference(mScreen);
+ mDetailsProfilesController.refresh(UsbBackend.MODE_DATA_MTP | UsbBackend.MODE_POWER_SOURCE);
+ List<SwitchPreference> switches = getProfileSwitches();
+
+ assertThat(switches.get(0).getKey()).isEqualTo(UsbManager.USB_FUNCTION_MTP);
+ assertThat(switches.get(0).isChecked());
+ assertThat(switches.get(3).getKey()).isEqualTo(UsbDetailsProfilesController.KEY_POWER);
+ assertThat(switches.get(3).isChecked());
+ }
+
+ @Test
+ public void testOnClickMtp_noneEnabled_shouldEnableMtp() {
+ when(mUsbBackend.isModeSupported(anyInt())).thenReturn(true);
+ when(mUsbBackend.isModeDisallowed(anyInt())).thenReturn(false);
+ when(mUsbBackend.isModeDisallowedBySystem(anyInt())).thenReturn(false);
+
+ mDetailsProfilesController.displayPreference(mScreen);
+ mDetailsProfilesController.refresh(UsbBackend.MODE_DATA_NONE);
+ List<SwitchPreference> switches = getProfileSwitches();
+ switches.get(0).performClick();
+
+ assertThat(switches.get(0).getKey()).isEqualTo(UsbManager.USB_FUNCTION_MTP);
+ verify(mUsbBackend).setMode(UsbBackend.MODE_DATA_MTP);
+ assertThat(switches.get(0).isChecked());
+ }
+
+ @Test
+ public void testOnClickMtp_supplyingPowerEnabled_shouldEnableBoth() {
+ when(mUsbBackend.isModeSupported(anyInt())).thenReturn(true);
+ when(mUsbBackend.isModeDisallowed(anyInt())).thenReturn(false);
+ when(mUsbBackend.isModeDisallowedBySystem(anyInt())).thenReturn(false);
+
+ mDetailsProfilesController.displayPreference(mScreen);
+ mDetailsProfilesController.refresh(UsbBackend.MODE_POWER_SOURCE);
+ when(mUsbBackend.getCurrentMode()).thenReturn(UsbBackend.MODE_POWER_SOURCE);
+ List<SwitchPreference> switches = getProfileSwitches();
+ switches.get(0).performClick();
+
+ assertThat(switches.get(0).getKey()).isEqualTo(UsbManager.USB_FUNCTION_MTP);
+ verify(mUsbBackend).setMode(UsbBackend.MODE_DATA_MTP | UsbBackend.MODE_POWER_SOURCE);
+ assertThat(switches.get(0).isChecked());
+ assertThat(switches.get(3).getKey()).isEqualTo(UsbDetailsProfilesController.KEY_POWER);
+ assertThat(switches.get(3).isChecked());
+ }
+
+ @Test
+ public void testOnClickMtp_ptpEnabled_shouldEnableMtpOnly() {
+ when(mUsbBackend.isModeSupported(anyInt())).thenReturn(true);
+ when(mUsbBackend.isModeDisallowed(anyInt())).thenReturn(false);
+ when(mUsbBackend.isModeDisallowedBySystem(anyInt())).thenReturn(false);
+
+ mDetailsProfilesController.displayPreference(mScreen);
+ mDetailsProfilesController.refresh(UsbBackend.MODE_DATA_PTP);
+ when(mUsbBackend.getCurrentMode()).thenReturn(UsbBackend.MODE_DATA_PTP);
+ List<SwitchPreference> switches = getProfileSwitches();
+ switches.get(0).performClick();
+
+ assertThat(switches.get(0).getKey()).isEqualTo(UsbManager.USB_FUNCTION_MTP);
+ verify(mUsbBackend).setMode(UsbBackend.MODE_DATA_MTP);
+ assertThat(switches.get(0).isChecked());
+ assertThat(switches.get(1).getKey()).isEqualTo(UsbManager.USB_FUNCTION_PTP);
+ assertThat(!switches.get(1).isChecked());
+ }
+
+ @Test
+ public void testOnClickMtp_mtpEnabled_shouldDisableMtp() {
+ when(mUsbBackend.isModeSupported(anyInt())).thenReturn(true);
+ when(mUsbBackend.isModeDisallowed(anyInt())).thenReturn(false);
+ when(mUsbBackend.isModeDisallowedBySystem(anyInt())).thenReturn(false);
+
+ mDetailsProfilesController.displayPreference(mScreen);
+ mDetailsProfilesController.refresh(UsbBackend.MODE_DATA_MTP);
+ when(mUsbBackend.getCurrentMode()).thenReturn(UsbBackend.MODE_DATA_MTP);
+ List<SwitchPreference> switches = getProfileSwitches();
+ switches.get(0).performClick();
+
+ assertThat(switches.get(0).getKey()).isEqualTo(UsbManager.USB_FUNCTION_MTP);
+ verify(mUsbBackend).setMode(UsbBackend.MODE_DATA_NONE);
+ assertThat(!switches.get(0).isChecked());
+ }
+
+ private List<SwitchPreference> getProfileSwitches() {
+ ArrayList<SwitchPreference> result = new ArrayList<>();
+ for (int i = 0; i < mPreference.getPreferenceCount(); i++) {
+ result.add((SwitchPreference) mPreference.getPreference(i));
+ }
+ return result;
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/deviceinfo/UsbModeChooserActivityTest.java b/tests/robotests/src/com/android/settings/connecteddevice/usb/UsbModeChooserActivityTest.java
similarity index 96%
rename from tests/robotests/src/com/android/settings/deviceinfo/UsbModeChooserActivityTest.java
rename to tests/robotests/src/com/android/settings/connecteddevice/usb/UsbModeChooserActivityTest.java
index 1817bfb..c02212b 100644
--- a/tests/robotests/src/com/android/settings/deviceinfo/UsbModeChooserActivityTest.java
+++ b/tests/robotests/src/com/android/settings/connecteddevice/usb/UsbModeChooserActivityTest.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.settings.deviceinfo;
+package com.android.settings.connecteddevice.usb;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Matchers.anyInt;
@@ -22,6 +22,7 @@
import android.widget.TextView;
import com.android.settings.R;
+import com.android.settings.connecteddevice.usb.UsbModeChooserActivity;
import com.android.settings.testutils.SettingsRobolectricTestRunner;
import com.android.settings.TestConfig;
import org.junit.Before;
diff --git a/tests/robotests/src/com/android/settings/connecteddevice/UsbModePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/connecteddevice/usb/UsbModePreferenceControllerTest.java
similarity index 67%
rename from tests/robotests/src/com/android/settings/connecteddevice/UsbModePreferenceControllerTest.java
rename to tests/robotests/src/com/android/settings/connecteddevice/usb/UsbModePreferenceControllerTest.java
index 7edde6e..d15a57f 100644
--- a/tests/robotests/src/com/android/settings/connecteddevice/UsbModePreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/connecteddevice/usb/UsbModePreferenceControllerTest.java
@@ -1,16 +1,12 @@
-package com.android.settings.connecteddevice;
+package com.android.settings.connecteddevice.usb;
import android.content.Context;
-import android.content.Intent;
-import android.hardware.usb.UsbManager;
import android.support.v7.preference.Preference;
import android.support.v7.preference.PreferenceScreen;
import com.android.settings.R;
import com.android.settings.testutils.SettingsRobolectricTestRunner;
import com.android.settings.TestConfig;
-import com.android.settings.deviceinfo.UsbBackend;
-import com.android.settings.deviceinfo.UsbModeChooserActivity;
import org.junit.Before;
import org.junit.Test;
@@ -24,6 +20,7 @@
import static org.mockito.Answers.RETURNS_DEEP_STUBS;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.verify;
@RunWith(SettingsRobolectricTestRunner.class)
@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
@@ -33,6 +30,8 @@
private UsbBackend mUsbBackend;
@Mock(answer = RETURNS_DEEP_STUBS)
private PreferenceScreen mScreen;
+ @Mock
+ private UsbConnectionBroadcastReceiver mUsbConnectionBroadcastReceiver;
private Context mContext;
private UsbModePreferenceController mController;
@@ -42,61 +41,67 @@
MockitoAnnotations.initMocks(this);
mContext = ShadowApplication.getInstance().getApplicationContext();
mController = new UsbModePreferenceController(mContext, mUsbBackend);
+ mController.mUsbReceiver = mUsbConnectionBroadcastReceiver;
}
@Test
public void testGetSummary_chargeDevice() {
- assertThat(mController.getSummary(UsbModeChooserActivity.DEFAULT_MODES[0]))
+ assertThat(mController.getSummary(0))
.isEqualTo(R.string.usb_summary_charging_only);
}
@Test
public void testGetSummary_supplyPower() {
- assertThat(mController.getSummary(UsbModeChooserActivity.DEFAULT_MODES[1]))
+ assertThat(mController.getSummary(UsbBackend.MODE_POWER_SOURCE))
.isEqualTo(R.string.usb_summary_power_only);
}
@Test
public void testGetSummary_TransferFiles() {
- assertThat(mController.getSummary(UsbModeChooserActivity.DEFAULT_MODES[2]))
+ assertThat(mController.getSummary(UsbBackend.MODE_DATA_MTP))
.isEqualTo(R.string.usb_summary_file_transfers);
}
@Test
public void testGetSummary_TransferPhoto() {
- assertThat(mController.getSummary(UsbModeChooserActivity.DEFAULT_MODES[3]))
+ assertThat(mController.getSummary(UsbBackend.MODE_DATA_PTP))
.isEqualTo(R.string.usb_summary_photo_transfers);
}
@Test
public void testGetSummary_MIDI() {
- assertThat(mController.getSummary(UsbModeChooserActivity.DEFAULT_MODES[4]))
+ assertThat(mController.getSummary(UsbBackend.MODE_DATA_MIDI))
.isEqualTo(R.string.usb_summary_MIDI);
}
@Test
+ public void testGetSummary_Tethering() {
+ assertThat(mController.getSummary(UsbBackend.MODE_DATA_TETHER))
+ .isEqualTo(R.string.usb_summary_tether);
+ }
+
+ @Test
public void testPreferenceSummary_usbDisconnected() {
final Preference preference = new Preference(mContext);
preference.setKey("usb_mode");
preference.setEnabled(true);
+ when(mUsbBackend.getCurrentMode()).thenReturn(UsbBackend.MODE_POWER_SINK);
+ when(mUsbConnectionBroadcastReceiver.isConnected()).thenReturn(false);
mController.updateState(preference);
+
+ assertThat(preference.getKey()).isEqualTo("usb_mode");
assertThat(preference.getSummary()).isEqualTo(
mContext.getString(R.string.disconnected));
}
@Test
- public void testUsbBoradcastReceiver_usbConnected_shouldUpdateSummary() {
+ public void testUsbBroadcastReceiver_usbConnected_shouldUpdateSummary() {
final Preference preference = new Preference(mContext);
preference.setKey("usb_mode");
preference.setEnabled(true);
- when(mUsbBackend.getCurrentMode()).thenReturn(UsbModeChooserActivity.DEFAULT_MODES[0]);
- when(mScreen.findPreference("usb_mode")).thenReturn(preference);
-
- mController.displayPreference(mScreen);
- mController.onResume();
- final Intent intent = new Intent(UsbManager.ACTION_USB_STATE);
- intent.putExtra(UsbManager.USB_CONNECTED, true);
- mContext.sendStickyBroadcast(intent);
+ when(mUsbBackend.getCurrentMode()).thenReturn(UsbBackend.MODE_POWER_SINK);
+ when(mUsbConnectionBroadcastReceiver.isConnected()).thenReturn(true);
+ mController.updateState(preference);
assertThat(preference.getSummary()).isEqualTo(
mContext.getString(R.string.usb_summary_charging_only));
diff --git a/tests/robotests/src/com/android/settings/development/SelectUsbConfigPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/development/SelectUsbConfigPreferenceControllerTest.java
index 8719bb4..67a6d6b 100644
--- a/tests/robotests/src/com/android/settings/development/SelectUsbConfigPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/development/SelectUsbConfigPreferenceControllerTest.java
@@ -25,6 +25,7 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
@@ -37,11 +38,13 @@
import android.content.Context;
import android.content.pm.PackageManager;
import android.hardware.usb.UsbManager;
+import android.hardware.usb.UsbManagerExtras;
import android.support.v7.preference.ListPreference;
import android.support.v7.preference.PreferenceScreen;
import com.android.settings.R;
import com.android.settings.TestConfig;
+import com.android.settings.connecteddevice.usb.UsbBackend;
import com.android.settings.testutils.SettingsRobolectricTestRunner;
import com.android.settings.testutils.shadow.ShadowUtils;
import com.android.settingslib.core.lifecycle.Lifecycle;
@@ -69,6 +72,8 @@
private UsbManager mUsbManager;
@Mock
private PackageManager mPackageManager;
+ @Mock
+ private UsbBackend.UsbManagerPassThrough mUsbManagerPassThrough;
private Context mContext;
private LifecycleOwner mLifecycleOwner;
@@ -101,6 +106,13 @@
mController = spy(new SelectUsbConfigPreferenceController(mContext, mLifecycle));
when(mScreen.findPreference(mController.getPreferenceKey())).thenReturn(mPreference);
mController.displayPreference(mScreen);
+ mController.mUsbManagerPassThrough = mUsbManagerPassThrough;
+
+ when(mUsbManagerPassThrough.usbFunctionsFromString("mtp")).thenReturn(UsbManagerExtras.MTP);
+ when(mUsbManagerPassThrough.usbFunctionsFromString("rndis"))
+ .thenReturn(UsbManagerExtras.RNDIS);
+ when(mUsbManagerPassThrough.usbFunctionsFromString("none"))
+ .thenReturn(UsbManagerExtras.NONE);
}
@@ -111,11 +123,13 @@
@Test
public void onPreferenceChange_setCharging_shouldEnableCharging() {
- when(mUsbManager.isFunctionEnabled(mValues[0])).thenReturn(true);
- doNothing().when(mController).setCurrentFunction(anyString(), anyBoolean());
+ when(mUsbManagerPassThrough.getCurrentFunctions()).thenReturn(
+ UsbManagerExtras.usbFunctionsFromString(mValues[0]));
+ doNothing().when(mController).setCurrentFunctions(anyLong());
mController.onPreferenceChange(mPreference, mValues[0]);
- verify(mController).setCurrentFunction(mValues[0], false /* usb data unlock */);
+ verify(mController).setCurrentFunctions(
+ UsbManagerExtras.usbFunctionsFromString(mValues[0]));
}
@Test
@@ -144,28 +158,32 @@
@Test
public void onPreferenceChange_setMtp_shouldEnableMtp() {
- when(mUsbManager.isFunctionEnabled(mValues[1])).thenReturn(true);
- doNothing().when(mController).setCurrentFunction(anyString(), anyBoolean());
+ when(mUsbManagerPassThrough.getCurrentFunctions())
+ .thenReturn(UsbManagerExtras.usbFunctionsFromString(mValues[1]));
+ doNothing().when(mController).setCurrentFunctions(anyLong());
mController.onPreferenceChange(mPreference, mValues[1]);
- verify(mController).setCurrentFunction(mValues[1], true /* usb data unlock */);
+ verify(mController).setCurrentFunctions(
+ UsbManagerExtras.usbFunctionsFromString(mValues[1]));
}
@Test
public void onPreferenceChange_monkeyUser_shouldReturnFalse() {
- when(mUsbManager.isFunctionEnabled(mValues[1])).thenReturn(true);
+ when(mUsbManagerPassThrough.getCurrentFunctions())
+ .thenReturn(UsbManagerExtras.usbFunctionsFromString(mValues[1]));
ShadowUtils.setIsUserAMonkey(true);
- doNothing().when(mController).setCurrentFunction(anyString(), anyBoolean());
+ doNothing().when(mController).setCurrentFunctions(anyLong());
final boolean isHandled = mController.onPreferenceChange(mPreference, mValues[1]);
assertThat(isHandled).isFalse();
- verify(mController, never()).setCurrentFunction(any(), anyBoolean());
+ verify(mController, never()).setCurrentFunctions(anyLong());
}
@Test
public void updateState_chargingEnabled_shouldSetPreferenceToCharging() {
- when(mUsbManager.isFunctionEnabled(mValues[0])).thenReturn(true);
+ when(mUsbManagerPassThrough.getCurrentFunctions())
+ .thenReturn(UsbManagerExtras.usbFunctionsFromString(mValues[0]));
mController.updateState(mPreference);
@@ -175,7 +193,8 @@
@Test
public void updateState_RndisEnabled_shouldEnableRndis() {
- when(mUsbManager.isFunctionEnabled(mValues[3])).thenReturn(true);
+ when(mUsbManagerPassThrough.getCurrentFunctions())
+ .thenReturn(UsbManagerExtras.usbFunctionsFromString(mValues[3]));
mController.updateState(mPreference);
@@ -185,6 +204,7 @@
@Test
public void updateState_noValueSet_shouldEnableChargingAsDefault() {
+ when(mUsbManagerPassThrough.getCurrentFunctions()).thenReturn(UsbManagerExtras.NONE);
mController.updateState(mPreference);
verify(mPreference).setValue(mValues[0]);
diff --git a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowConnectivityManager.java b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowConnectivityManager.java
index 742fbf8..fc19b44 100644
--- a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowConnectivityManager.java
+++ b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowConnectivityManager.java
@@ -26,6 +26,7 @@
public class ShadowConnectivityManager extends org.robolectric.shadows.ShadowConnectivityManager {
private final SparseBooleanArray mSupportedNetworkTypes = new SparseBooleanArray();
+ private boolean mTetheringSupported = false;
public void setNetworkSupported(int networkType, boolean supported) {
mSupportedNetworkTypes.put(networkType, supported);
@@ -35,4 +36,13 @@
public boolean isNetworkSupported(int networkType) {
return mSupportedNetworkTypes.get(networkType);
}
+
+ public void setTetheringSupported(boolean supported) {
+ mTetheringSupported = supported;
+ }
+
+ @Implementation
+ public boolean isTetheringSupported() {
+ return mTetheringSupported;
+ }
}