diff --git a/AndroidManifest.xml b/AndroidManifest.xml index f9e123a1e42..03d47961818 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -2012,13 +2012,21 @@ - + + + + - Supply power + Charging connected device - Charge the connected device. Works only with devices that support USB charging. + Other settings unavailable when turned on - Transfer files + File Transfer @@ -8016,23 +8016,31 @@ - Transfer photos (PTP) + PTP Transfer photos or files if MTP is not supported (PTP) + + USB tethering - Use device as MIDI + MIDI Use this device as MIDI - Use USB to + for this device should be used for. These options are more commonly used. + Choices are usb_use_file_transfer.--> + Use USB for + + Also use USB for USB @@ -8040,13 +8048,27 @@ Charging this device - Supplying power + Charging connected device - Transferring files + File transfer + + USB tethering - Transferring photos (PTP) + PTP - Using device as MIDI + MIDI + + File transfer and supplying power + + USB tethering and supplying power + + PTP and supplying power + + MIDI and supplying power SMS Mirroring diff --git a/res/xml/connected_devices_advanced.xml b/res/xml/connected_devices_advanced.xml index 8ca6b81aa84..0b75abff270 100644 --- a/res/xml/connected_devices_advanced.xml +++ b/res/xml/connected_devices_advanced.xml @@ -56,16 +56,6 @@ android:summary="@string/bluetooth_on_while_driving_summary" android:order="-2"/> - - - - + android:targetClass="com.android.settings.connecteddevice.usb.UsbModeChooserActivity"/> + + + + + + + + + + diff --git a/src/com/android/settings/Settings.java b/src/com/android/settings/Settings.java index b4908ddc706..c233271e610 100644 --- a/src/com/android/settings/Settings.java +++ b/src/com/android/settings/Settings.java @@ -96,6 +96,7 @@ public class Settings extends SettingsActivity { 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 2a136bca373..9ac6ebdee3c 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.R; 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 7097b3625fe..bde5e81e3f1 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.R; 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 3cccc15782f..3d5d0e58e8f 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.Preference; 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 class ConnectedDeviceGroupController extends AbstractPreferenceController 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 07a76915af4..00000000000 --- 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 0468b0f5a35..dd2990299aa 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 @@ public class ConnectedUsbDeviceUpdater { @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 class ConnectedUsbDeviceUpdater { } }; - 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 @@ public class ConnectedUsbDeviceUpdater { 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 @@ public class ConnectedUsbDeviceUpdater { // 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 00000000000..add985c53c0 --- /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/deviceinfo/UsbBackend.java b/src/com/android/settings/connecteddevice/usb/UsbBackend.java similarity index 56% rename from src/com/android/settings/deviceinfo/UsbBackend.java rename to src/com/android/settings/connecteddevice/usb/UsbBackend.java index 5d2502ba9b2..cdfb6b09b13 100644 --- a/src/com/android/settings/deviceinfo/UsbBackend.java +++ b/src/com/android/settings/connecteddevice/usb/UsbBackend.java @@ -13,15 +13,14 @@ * 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 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.net.ConnectivityManager; import android.os.UserHandle; import android.os.UserManager; import android.support.annotation.VisibleForTesting; @@ -32,34 +31,52 @@ public class UsbBackend { 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_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 = 0x02 << 1; - public static final int MODE_DATA_MIDI = 0x03 << 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 mRestricted; - private final boolean mRestrictedBySystem; - private final boolean mMidi; + 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)); + this(context, new UserRestrictionUtil(context), null); } @VisibleForTesting - public UsbBackend(Context context, UserRestrictionUtil userRestrictionUtil) { + public UsbBackend(Context context, UserRestrictionUtil userRestrictionUtil, + UsbManagerPassThrough usbManagerPassThrough) { mContext = context; mUsbManager = context.getSystemService(UsbManager.class); - mRestricted = userRestrictionUtil.isUsbFileTransferRestricted(); - mRestrictedBySystem = userRestrictionUtil.isUsbFileTransferRestrictedBySystem(); - mMidi = context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_MIDI); + 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) { @@ -81,6 +98,7 @@ public class UsbBackend { 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(); } @@ -88,38 +106,35 @@ public class UsbBackend { } public int getUsbDataMode() { - if (!isUsbDataUnlocked()) { - return MODE_DATA_NONE; - } else if (mUsbManager.isFunctionEnabled(UsbManager.USB_FUNCTION_MTP)) { + long functions = mUsbManagerPassThrough.getCurrentFunctions(); + if (functions == UsbManager.FUNCTION_MTP) { return MODE_DATA_MTP; - } else if (mUsbManager.isFunctionEnabled(UsbManager.USB_FUNCTION_PTP)) { + } else if (functions == UsbManager.FUNCTION_PTP) { return MODE_DATA_PTP; - } else if (mUsbManager.isFunctionEnabled(UsbManager.USB_FUNCTION_MIDI)) { + } 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 boolean isUsbDataUnlocked() { - Intent intent = mContext.registerReceiver(null, - new IntentFilter(UsbManager.ACTION_USB_STATE)); - return intent == null ? - false : intent.getBooleanExtra(UsbManager.USB_DATA_UNLOCKED, false); + return MODE_DATA_NONE; } private void setUsbFunction(int mode) { switch (mode) { case MODE_DATA_MTP: - mUsbManager.setCurrentFunction(UsbManager.USB_FUNCTION_MTP, true); + mUsbManager.setCurrentFunctions(UsbManager.FUNCTION_MTP); break; case MODE_DATA_PTP: - mUsbManager.setCurrentFunction(UsbManager.USB_FUNCTION_PTP, true); + mUsbManager.setCurrentFunctions(UsbManager.FUNCTION_PTP); break; case MODE_DATA_MIDI: - mUsbManager.setCurrentFunction(UsbManager.USB_FUNCTION_MIDI, true); + mUsbManager.setCurrentFunctions(UsbManager.FUNCTION_MIDI); + break; + case MODE_DATA_TETHER: + mUsbManager.setCurrentFunctions(UsbManager.FUNCTION_RNDIS); break; default: - mUsbManager.setCurrentFunction(null, false); + mUsbManager.setCurrentFunctions(UsbManager.FUNCTION_NONE); break; } } @@ -144,28 +159,32 @@ public class UsbBackend { } 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. + 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 (mRestrictedBySystem && (mode & MODE_DATA_MASK) != MODE_DATA_NONE - && (mode & MODE_DATA_MASK) != MODE_DATA_MIDI) { - // No USB data modes are supported. + 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 (!mMidi && (mode & MODE_DATA_MASK) == MODE_DATA_MIDI) { + 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) { @@ -194,9 +213,35 @@ public class UsbBackend { 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 00000000000..91d22dcceb6 --- /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 00000000000..09c75549dcf --- /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 00000000000..c8611880392 --- /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 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 getPreferenceControllers(Context context) { + mUsbBackend = new UsbBackend(context); + mControllers = createControllerList(context, mUsbBackend, this); + mUsbReceiver = new UsbConnectionBroadcastReceiver(context, mUsbConnectionListener, + mUsbBackend); + this.getLifecycle().addObserver(mUsbReceiver); + + List ret = new ArrayList<>(); + ret.addAll(mControllers); + return ret; + } + + private static List createControllerList(Context context, + UsbBackend usbBackend, DashboardFragment fragment) { + List 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 getXmlResourcesToIndex( + Context context, boolean enabled) { + return new ArrayList<>(); + } + + @Override + public List getNonIndexableKeys(Context context) { + return super.getNonIndexableKeys(context); + } + + @Override + public List getPreferenceControllers( + Context context) { + List 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 00000000000..7ac02350fe7 --- /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 00000000000..1375b4c7c3b --- /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 mOptions; + private String mKey; + + public UsbDetailsProfilesController(Context context, PreferenceFragment fragment, + UsbBackend backend, List 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 99% rename from src/com/android/settings/deviceinfo/UsbModeChooserActivity.java rename to src/com/android/settings/connecteddevice/usb/UsbModeChooserActivity.java index 8ba378156fb..b3b0718a1d3 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 869352006c5..e342460fef6 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 @@ public class UsbModePreferenceController extends AbstractPreferenceController 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 @@ public class UsbModePreferenceController extends AbstractPreferenceController 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 @@ public class UsbModePreferenceController extends AbstractPreferenceController } } } - } diff --git a/src/com/android/settings/core/gateway/SettingsGateway.java b/src/com/android/settings/core/gateway/SettingsGateway.java index b07cf84774a..a9358ef5543 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.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.DataPlanUsageSummary; import com.android.settings.datausage.DataUsageList; import com.android.settings.datausage.DataUsageSummary; @@ -246,6 +247,7 @@ public class SettingsGateway { 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 77a9a754708..63eb24c7cc5 100644 --- a/src/com/android/settings/development/SelectUsbConfigPreferenceController.java +++ b/src/com/android/settings/development/SelectUsbConfigPreferenceController.java @@ -27,11 +27,11 @@ import android.support.annotation.VisibleForTesting; 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 @@ public class SelectUsbConfigPreferenceController extends 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 @@ public class SelectUsbConfigPreferenceController extends 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 @@ public class SelectUsbConfigPreferenceController extends return false; } - writeUsbConfigurationOption(newValue.toString()); + writeUsbConfigurationOption(mUsbManagerPassThrough + .usbFunctionsFromString(newValue.toString())); updateUsbConfigurationValues(); return true; } @@ -129,14 +133,15 @@ public class SelectUsbConfigPreferenceController extends } @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 @@ public class SelectUsbConfigPreferenceController extends 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/search/SearchIndexableResourcesImpl.java b/src/com/android/settings/search/SearchIndexableResourcesImpl.java index 38dc15dc4d6..1edc2deab9b 100644 --- a/src/com/android/settings/search/SearchIndexableResourcesImpl.java +++ b/src/com/android/settings/search/SearchIndexableResourcesImpl.java @@ -21,6 +21,8 @@ import android.support.annotation.VisibleForTesting; 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.backup.BackupSettingsFragment; 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 @@ public class SearchIndexableResourcesImpl implements SearchIndexableResources { 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 00000000000..b9bccd22f62 --- /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 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 78be742df6e..b478c4ed960 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 android.support.v7.preference.PreferenceScreen; 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 16cd3a7a94f..011d620265b 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; @@ -41,6 +43,8 @@ public class ConnectedUsbDeviceUpdaterTest { private Context mContext; private ConnectedUsbDeviceUpdater mDeviceUpdater; + @Mock + private DashboardFragment mFragment; @Mock private UsbConnectionBroadcastReceiver mUsbReceiver; @Mock @@ -53,7 +57,8 @@ public class ConnectedUsbDeviceUpdaterTest { 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 @@ public class ConnectedUsbDeviceUpdaterTest { @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 ce384a5d973..40cfd73f356 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 static org.mockito.Mockito.when; 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 @@ public class UsbBackendTest { private UsbManager mUsbManager; @Mock private UsbBackend.UserRestrictionUtil mUserRestrictionUtil; + @Mock + private ConnectivityManager mConnectivityManager; @Before public void setUp() { @@ -53,22 +57,13 @@ public class UsbBackendTest { 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 82% rename from tests/robotests/src/com/android/settings/connecteddevice/UsbConnectionBroadcastReceiverTest.java rename to tests/robotests/src/com/android/settings/connecteddevice/usb/UsbConnectionBroadcastReceiverTest.java index 06bd5b7834d..50b47e087f3 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 @@ public class UsbConnectionBroadcastReceiverTest { @Mock private UsbConnectionBroadcastReceiver.UsbConnectionListener mListener; + @Mock + private UsbBackend mUsbBackend; @Before public void setUp() { @@ -59,27 +61,42 @@ public class UsbConnectionBroadcastReceiverTest { 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 00000000000..e1f90786370 --- /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 00000000000..557d8365a0c --- /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 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 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 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 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 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 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 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 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 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 getProfileSwitches() { + ArrayList 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 1817bfb2902..c02212b8911 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 static org.mockito.Mockito.verify; 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 7edde6e064b..d15a57f5560 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 com.google.common.truth.Truth.assertThat; 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 @@ public class UsbModePreferenceControllerTest { 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 @@ public class UsbModePreferenceControllerTest { 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 8719bb4246a..67a6d6b3eb0 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 junit.framework.Assert.assertTrue; 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.arch.lifecycle.LifecycleOwner; 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 @@ public class SelectUsbConfigPreferenceControllerTest { private UsbManager mUsbManager; @Mock private PackageManager mPackageManager; + @Mock + private UsbBackend.UsbManagerPassThrough mUsbManagerPassThrough; private Context mContext; private LifecycleOwner mLifecycleOwner; @@ -101,6 +106,13 @@ public class SelectUsbConfigPreferenceControllerTest { 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 @@ public class SelectUsbConfigPreferenceControllerTest { @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 @@ public class SelectUsbConfigPreferenceControllerTest { @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 @@ public class SelectUsbConfigPreferenceControllerTest { @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 @@ public class SelectUsbConfigPreferenceControllerTest { @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 742fbf8cccc..fc19b44e581 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 @@ import org.robolectric.annotation.Implements; 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 class ShadowConnectivityManager extends org.robolectric.shadows.ShadowCon public boolean isNetworkSupported(int networkType) { return mSupportedNetworkTypes.get(networkType); } + + public void setTetheringSupported(boolean supported) { + mTetheringSupported = supported; + } + + @Implementation + public boolean isTetheringSupported() { + return mTetheringSupported; + } }