Update USB dialog for USB-C power option

Also includes a bit of visual updates, options now have summaries
as well.

Code has been refactored a bit, and is more flexible to easily
support any kind of power/data flow combination once its time
to support that.

Currently devices don't have USB-C ports, they can be simulated
with the following commands:
  $ adb shell dumpsys usb add-port "matrix" dual
  # ?s control whether these can be changed
  $ adb shell dumpsys usb connect-port "matrix" ufp? sink? device?
  # Do testing here
  $ adb shell dumpsys usb disconnect-port "matrix"
  $ adb shell dumpsys usb remove-port "matrix"
  $ adb shell dumpsys usb reset
  # Use the help for more info
  $ adb shell dumpsys usb -h

Bug: 21615151
Change-Id: I53ad4de51ff10a197c87bf2741756c1821ee9e74
This commit is contained in:
Jason Monk
2015-07-29 14:53:35 -04:00
parent e86b170d7f
commit 83290eef8c
5 changed files with 293 additions and 80 deletions

View File

@@ -36,7 +36,7 @@
android:ellipsize="marquee" /> android:ellipsize="marquee" />
<TextView android:id="@+android:id/summary" <TextView android:id="@android:id/summary"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:paddingBottom="4dp" android:paddingBottom="4dp"

View File

@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2015 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingTop="10dp"
android:paddingBottom="5dp">
<LinearLayout
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" />
</ScrollView>

View File

@@ -6760,22 +6760,46 @@
<!-- Description of how many more permissions to view on next page [CHAR LIMIT=30] --> <!-- Description of how many more permissions to view on next page [CHAR LIMIT=30] -->
<string name="additional_permissions_more"><xliff:g id="count" example="2">%1$d</xliff:g> more</string> <string name="additional_permissions_more"><xliff:g id="count" example="2">%1$d</xliff:g> more</string>
<!-- One of the choices in a dialog (with title defined in usb_use) that lets the user <!-- Title of one of the choices in a dialog (with title defined in usb_use) that lets the user
select what the USB connection for this device should be used for. This choice select what the USB connection for this device should be used for. This choice
is for charging only. --> is for charging only. -->
<string name="usb_use_charging_only">Charging only</string> <string name="usb_use_charging_only">Charging</string>
<!-- One of the choices in a dialog (with title defined in usb_use) that lets the user <!-- Decription of one of the choices in a dialog (with title defined in usb_use) that lets the
user select what the USB connection for this device should be used for. This choice
is for charging only. -->
<string name="usb_use_charging_only_desc">Just charge this device</string>
<!-- Title of one of the choices in a dialog (with title defined in usb_use) that lets the user
select what the USB connection for this device should be used for. This choice
is for powering the other device only. -->
<string name="usb_use_power_only">Power supply</string>
<!-- Decription of one of the choices in a dialog (with title defined in usb_use) that lets the
user select what the USB connection for this device should be used for. This choice
is for powering the other device only. -->
<string name="usb_use_power_only_desc">Charge the other connected device</string>
<!-- Title of one of the choices in a dialog (with title defined in usb_use) that lets the user
select what the USB connection for this device should be used for. This choice select what the USB connection for this device should be used for. This choice
is for transferring files via MTP. --> is for transferring files via MTP. -->
<string name="usb_use_file_transfers">Transfer files (MTP)</string> <string name="usb_use_file_transfers">File transfers</string>
<!-- One of the choices in a dialog (with title defined in usb_use) that lets the user <!-- Description of one of the choices in a dialog (with title defined in usb_use) that lets the user
select what the USB connection for this device should be used for. This choice
is for transferring files via MTP. -->
<string name="usb_use_file_transfers_desc">Transfer files to Windows or Mac (MTP)</string>
<!-- Title of one of the choices in a dialog (with title defined in usb_use) that lets the user
select what the USB connection for this device should be used for. This choice select what the USB connection for this device should be used for. This choice
is for transferring photos via PTP. --> is for transferring photos via PTP. -->
<string name="usb_use_photo_transfers">Transfer photos (PTP)</string> <string name="usb_use_photo_transfers">Photo transfer (PTP)</string>
<!-- One of the choices in a dialog (with title defined in usb_use) that lets the user <!-- Description of one of the choices in a dialog (with title defined in usb_use) that lets the user
select what the USB connection for this device should be used for. This choice
is for transferring photos via PTP. -->
<string name="usb_use_photo_transfers_desc">Transfer photos or files if MTP is not supported (PTP)</string>
<!-- Title of one of the choices in a dialog (with title defined in usb_use) that lets the user
select what the USB connection for this device should be used for. This choice select what the USB connection for this device should be used for. This choice
is for entering MIDI mode. --> is for entering MIDI mode. -->
<string name="usb_use_MIDI">MIDI</string> <string name="usb_use_MIDI">MIDI</string>
<!-- Description of one of the choices in a dialog (with title defined in usb_use) that lets the user
select what the USB connection for this device should be used for. This choice
is for entering MIDI mode. -->
<string name="usb_use_MIDI_desc">Use device for MIDI input</string>
<!-- The title used in a dialog which lets the user select what the USB connection <!-- The title used in a dialog which lets the user select what the USB connection
for this device should be used for. Choices are usb_use_charging_only, for this device should be used for. Choices are usb_use_charging_only,
usb_use_file_transfer, use_use_photo_transfer, and usb_use_MIDI --> usb_use_file_transfer, use_use_photo_transfer, and usb_use_MIDI -->

View File

@@ -0,0 +1,145 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.deviceinfo;
import android.content.Context;
import android.hardware.usb.UsbManager;
import android.hardware.usb.UsbPort;
import android.hardware.usb.UsbPortStatus;
import android.os.UserManager;
public class UsbBackend {
private static final int MODE_POWER_MASK = 0x01;
public static final int MODE_POWER_SINK = 0x00;
public static final int MODE_POWER_SOURCE = 0x01;
private static final int MODE_DATA_MASK = 0x03 << 1;
public static final int MODE_DATA_NONE = 0x00 << 1;
public static final int MODE_DATA_MTP = 0x01 << 1;
public static final int MODE_DATA_PTP = 0x02 << 1;
public static final int MODE_DATA_MIDI = 0x03 << 1;
private final boolean mRestricted;
private UserManager mUserManager;
private UsbManager mUsbManager;
private UsbPort mPort;
private UsbPortStatus mPortStatus;
public UsbBackend(Context context) {
mUserManager = UserManager.get(context);
mUsbManager = context.getSystemService(UsbManager.class);
mRestricted = mUserManager.hasUserRestriction(UserManager.DISALLOW_USB_FILE_TRANSFER);
UsbPort[] ports = mUsbManager.getPorts();
// For now look for a connected port, in the future we should identify port in the
// notification and pick based on that.
final int N = ports.length;
for (int i = 0; i < N; i++) {
UsbPortStatus status = mUsbManager.getPortStatus(ports[i]);
if (status.isConnected()) {
mPort = ports[i];
mPortStatus = status;
break;
}
}
}
public int getCurrentMode() {
if (mPort != null) {
int power = mPortStatus.getCurrentPowerRole() == UsbPort.POWER_ROLE_SOURCE
? MODE_POWER_SOURCE : MODE_POWER_SINK;
return power | (mPortStatus.getCurrentDataRole() == UsbPort.DATA_ROLE_DEVICE
? getUsbDataMode() : MODE_DATA_NONE);
}
return MODE_POWER_SINK | getUsbDataMode();
}
public int getUsbDataMode() {
if (!mUsbManager.isUsbDataUnlocked()) {
return MODE_DATA_NONE;
} else if (mUsbManager.isFunctionEnabled(UsbManager.USB_FUNCTION_MTP)) {
return MODE_DATA_MTP;
} else if (mUsbManager.isFunctionEnabled(UsbManager.USB_FUNCTION_PTP)) {
return MODE_DATA_MTP;
} else if (mUsbManager.isFunctionEnabled(UsbManager.USB_FUNCTION_MIDI)) {
return MODE_DATA_MIDI;
}
return MODE_DATA_NONE; // ...
}
private void setUsbFunction(int mode) {
switch (mode) {
case MODE_DATA_MTP:
mUsbManager.setCurrentFunction(UsbManager.USB_FUNCTION_MTP);
mUsbManager.setUsbDataUnlocked(true);
break;
case MODE_DATA_PTP:
mUsbManager.setCurrentFunction(UsbManager.USB_FUNCTION_PTP);
mUsbManager.setUsbDataUnlocked(true);
break;
case MODE_DATA_MIDI:
mUsbManager.setCurrentFunction(UsbManager.USB_FUNCTION_MIDI);
mUsbManager.setUsbDataUnlocked(true);
break;
default:
mUsbManager.setCurrentFunction(null);
mUsbManager.setUsbDataUnlocked(false);
break;
}
}
public void setMode(int mode) {
if (mPort != null) {
int powerRole = modeToPower(mode);
// If we aren't using any data modes and we support host mode, then go to host mode
// so maybe? the other device can provide data if it wants, otherwise go into device
// mode because we have no choice.
int dataRole = (mode & MODE_DATA_MASK) == MODE_DATA_NONE
&& mPortStatus.isRoleCombinationSupported(powerRole, UsbPort.DATA_ROLE_HOST)
? UsbPort.DATA_ROLE_HOST : UsbPort.DATA_ROLE_DEVICE;
mUsbManager.setPortRoles(mPort, powerRole, dataRole);
}
setUsbFunction(mode & MODE_DATA_MASK);
}
private int modeToPower(int mode) {
return (mode & MODE_POWER_MASK) == MODE_POWER_SOURCE
? UsbPort.POWER_ROLE_SOURCE : UsbPort.POWER_ROLE_SINK;
}
public boolean isModeSupported(int mode) {
if (mRestricted && (mode & MODE_DATA_MASK) != MODE_DATA_NONE) {
// No USB data modes are supported.
return false;
}
if (mPort != null) {
int power = modeToPower(mode);
if ((mode & MODE_DATA_MASK) != 0) {
// We have a port and data, need to be in device mode.
return mPortStatus.isRoleCombinationSupported(power,
UsbPort.DATA_ROLE_DEVICE);
} else {
// No data needed, we can do this power mode in either device or host.
return mPortStatus.isRoleCombinationSupported(power, UsbPort.DATA_ROLE_DEVICE)
|| mPortStatus.isRoleCombinationSupported(power, UsbPort.DATA_ROLE_HOST);
}
}
// No port, support sink modes only.
return (mode & MODE_POWER_MASK) != MODE_POWER_SOURCE;
}
}

View File

@@ -20,11 +20,14 @@ import android.annotation.Nullable;
import android.app.Activity; import android.app.Activity;
import android.app.ActivityManager; import android.app.ActivityManager;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.hardware.usb.UsbManager;
import android.os.Bundle; import android.os.Bundle;
import android.os.UserManager; import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Checkable;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.android.settings.R; import com.android.settings.R;
@@ -34,90 +37,101 @@ import com.android.settings.R;
*/ */
public class UsbModeChooserActivity extends Activity { public class UsbModeChooserActivity extends Activity {
private UsbManager mUsbManager; public static final int[] DEFAULT_MODES = {
UsbBackend.MODE_POWER_SINK | UsbBackend.MODE_DATA_NONE,
UsbBackend.MODE_POWER_SOURCE | UsbBackend.MODE_DATA_NONE,
UsbBackend.MODE_POWER_SINK | UsbBackend.MODE_DATA_MTP,
UsbBackend.MODE_POWER_SINK | UsbBackend.MODE_DATA_PTP,
UsbBackend.MODE_POWER_SINK | UsbBackend.MODE_DATA_MIDI
};
private UsbBackend mBackend;
private AlertDialog mDialog;
private LayoutInflater mLayoutInflater;
@Override @Override
protected void onCreate(@Nullable Bundle savedInstanceState) { protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
mUsbManager = (UsbManager) getSystemService(Context.USB_SERVICE);
CharSequence[] items;
UserManager userManager =
(UserManager) getSystemService(Context.USER_SERVICE);
if (userManager.hasUserRestriction(UserManager.DISALLOW_USB_FILE_TRANSFER)) {
items = new CharSequence[] { getText(R.string.usb_use_charging_only) };
} else {
items = getResources().getTextArray(R.array.usb_available_functions);
}
final AlertDialog levelDialog; mLayoutInflater = LayoutInflater.from(this);
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(R.string.usb_use); mDialog = new AlertDialog.Builder(this)
builder.setSingleChoiceItems(items, getCurrentFunction(), .setTitle(R.string.usb_use)
new DialogInterface.OnClickListener() { .setView(R.layout.usb_dialog_container)
@Override .setOnDismissListener(new DialogInterface.OnDismissListener() {
public void onClick(DialogInterface dialog, int which) {
if (!ActivityManager.isUserAMonkey()) {
setCurrentFunction(which);
}
dialog.dismiss();
UsbModeChooserActivity.this.finish();
}
});
builder.setOnDismissListener(new DialogInterface.OnDismissListener() {
@Override @Override
public void onDismiss(DialogInterface dialog) { public void onDismiss(DialogInterface dialog) {
UsbModeChooserActivity.this.finish(); finish();
} }
}); })
builder.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
@Override @Override
public void onClick(DialogInterface dialog, int which) { public void onClick(DialogInterface dialog, int which) {
UsbModeChooserActivity.this.finish(); finish();
}
}).create();
mDialog.show();
LinearLayout container = (LinearLayout) mDialog.findViewById(R.id.container);
mBackend = new UsbBackend(this);
int current = mBackend.getCurrentMode();
for (int i = 0; i < DEFAULT_MODES.length; i++) {
if (mBackend.isModeSupported(DEFAULT_MODES[i])) {
inflateOption(DEFAULT_MODES[i], current == DEFAULT_MODES[i], container);
}
}
}
private void inflateOption(final int mode, boolean selected, LinearLayout container) {
View v = mLayoutInflater.inflate(R.layout.radio_with_summary, container, false);
((TextView) v.findViewById(android.R.id.title)).setText(getTitle(mode));
((TextView) v.findViewById(android.R.id.summary)).setText(getSummary(mode));
v.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (!ActivityManager.isUserAMonkey()) {
mBackend.setMode(mode);
}
mDialog.dismiss();
finish();
} }
}); });
levelDialog = builder.create(); ((Checkable) v).setChecked(selected);
levelDialog.show(); container.addView(v);
} }
/* private static int getSummary(int mode) {
* If you change the numbers here, you also need to change R.array.usb_available_functions switch (mode) {
* so that everything matches. case UsbBackend.MODE_POWER_SINK | UsbBackend.MODE_DATA_NONE:
*/ return R.string.usb_use_charging_only_desc;
private int getCurrentFunction() { case UsbBackend.MODE_POWER_SOURCE | UsbBackend.MODE_DATA_NONE:
if (!mUsbManager.isUsbDataUnlocked()) { return R.string.usb_use_power_only_desc;
return 0; case UsbBackend.MODE_POWER_SINK | UsbBackend.MODE_DATA_MTP:
} else if (mUsbManager.isFunctionEnabled(UsbManager.USB_FUNCTION_MTP)) { return R.string.usb_use_file_transfers_desc;
return 1; case UsbBackend.MODE_POWER_SINK | UsbBackend.MODE_DATA_PTP:
} else if (mUsbManager.isFunctionEnabled(UsbManager.USB_FUNCTION_PTP)) { return R.string.usb_use_photo_transfers_desc;
return 2; case UsbBackend.MODE_POWER_SINK | UsbBackend.MODE_DATA_MIDI:
} else if (mUsbManager.isFunctionEnabled(UsbManager.USB_FUNCTION_MIDI)) { return R.string.usb_use_MIDI_desc;
return 3;
} }
return 0; return 0;
} }
/* private static int getTitle(int mode) {
* If you change the numbers here, you also need to change R.array.usb_available_functions switch (mode) {
* so that everything matches. case UsbBackend.MODE_POWER_SINK | UsbBackend.MODE_DATA_NONE:
*/ return R.string.usb_use_charging_only;
private void setCurrentFunction(int which) { case UsbBackend.MODE_POWER_SOURCE | UsbBackend.MODE_DATA_NONE:
switch (which) { return R.string.usb_use_power_only;
case 0: case UsbBackend.MODE_POWER_SINK | UsbBackend.MODE_DATA_MTP:
mUsbManager.setCurrentFunction(null); return R.string.usb_use_file_transfers;
mUsbManager.setUsbDataUnlocked(false); case UsbBackend.MODE_POWER_SINK | UsbBackend.MODE_DATA_PTP:
break; return R.string.usb_use_photo_transfers;
case 1: case UsbBackend.MODE_POWER_SINK | UsbBackend.MODE_DATA_MIDI:
mUsbManager.setCurrentFunction(UsbManager.USB_FUNCTION_MTP); return R.string.usb_use_MIDI;
mUsbManager.setUsbDataUnlocked(true); }
break; return 0;
case 2:
mUsbManager.setCurrentFunction(UsbManager.USB_FUNCTION_PTP);
mUsbManager.setUsbDataUnlocked(true);
break;
case 3:
mUsbManager.setCurrentFunction(UsbManager.USB_FUNCTION_MIDI);
mUsbManager.setUsbDataUnlocked(true);
break;
}
} }
} }