【Android】USB偏好设置-mtp 文件传输
USB偏好设置
在 Android 设备上,USB 偏好设置(USB Preferences)允许用户自定义设备通过 USB 连接电脑或其他主机时的行为,例如选择文件传输模式、充电模式或网络共享等。这个功能通常在连接 USB 数据线后,通过通知栏或系统设置进行配置。
USB偏好设置的主要选项
当 Android 设备通过 USB 连接电脑时,通常会弹出通知或提示,让用户选择 USB 用途。常见的选项包括:
- 文件传输(MTP)
用途:在电脑和手机之间传输文件(照片、视频、文档等)
适用场景:备份照片或音乐到电脑;从电脑导入文件到手机。
特点:手机存储以“媒体设备”形式显示在电脑上;不会影响手机正常使用(可同时使用其他应用)。 - 照片传输(PTP)
用途:仅传输照片和视频(适用于老式相机或某些软件)
适用场景:需要快速导出照片(如 Adobe Lightroom 导入);兼容性更好的旧设备连接。
特点:电脑仅识别 DCIM(相机照片)文件夹;比 MTP 更简单,但功能有限。 - USB 网络共享(USB Tethering)
用途:将手机的移动网络共享给电脑使用(类似有线热点)。
适用场景:电脑没有 Wi-Fi 时(如台式机)通过手机上网;比 Wi-Fi 热点更稳定、耗电更低。
特点:需要手机开启移动数据;部分运营商可能限制此功能。 - MIDI(音乐设备数字接口)
用途:连接 MIDI 设备(如电子琴、音乐控制器)。
适用场景:音乐制作(如使用 FL Studio、GarageBand);外接 MIDI 键盘或鼓机。
特点:低延迟音频传输;仅适用于专业音乐应用。 - 仅充电
用途:仅通过 USB 充电,不传输数据。
适用场景:在公共 USB 端口(如机场、网吧)充电时避免数据泄露;快速充电(某些电脑 USB 端口电流较低)。
特点:电脑无法访问手机文件,更安全。 - 默认 USB 配置(Android 10+)
在 开发者选项 中,可以设置默认的 USB 行为(如自动进入文件传输模式)。
路径:设置 > 系统 > 开发者选项 > 默认 USB 配置。
mtp 文件传输
MTP(Media Transfer Protocol,媒体传输协议)是一种由微软开发的通信协议,主要用于在计算机和便携设备(如智能手机、数码相机、媒体播放器等)之间传输媒体文件。它是传统USB大容量存储(USB Mass Storage, UMS)的替代方案,解决了UMS在文件系统兼容性和设备独占访问等方面的局限性。
典型应用场景
- Android设备传输文件:连接电脑后通过MTP管理照片、音乐等。
- 数码相机/播放器:导出媒体内容而不影响设备正常运行。
- 云服务同步:部分应用通过MTP协议与本地设备交互。
源码分析
点击 “设置app” -> “已连接的设备” -> “USB” 即可进入到 USB 偏好设置界面。
设置app进入到一个界面后,可以用以下 adb 命令打印一下日志:
adb logcat -s SettingsActivity > SettingsActivity.txt
随后可以看到以下日志信息:
--------- beginning of main
05-24 17:40:07.273 5090 15496 D SettingsActivity: No enabled state changed, skipping updateCategory call
05-24 17:40:13.650 5090 5090 D SettingsActivity: Starting onCreate
05-24 17:40:13.654 5090 5090 D SettingsActivity: Starting to set activity title
05-24 17:40:13.654 5090 5090 D SettingsActivity: Done setting title
05-24 17:40:13.654 5090 5090 D SettingsActivity: Switching to fragment com.android.settings.connecteddevice.ConnectedDeviceDashboardFragment
05-24 17:40:13.662 5090 5090 D SettingsActivity: MetricsCategory is 747
05-24 17:40:13.667 5090 5090 D SettingsActivity: Executed frag manager pendingTransactions
05-24 17:40:13.668 5090 5090 D SettingsActivity: MetricsCategory is 747
05-24 17:40:13.693 5090 15496 D SettingsActivity: No enabled state changed, skipping updateCategory call
05-24 17:40:15.221 5090 5090 D SettingsActivity: Starting onCreate
05-24 17:40:15.224 5090 5090 D SettingsActivity: Starting to set activity title
05-24 17:40:15.224 5090 5090 D SettingsActivity: Done setting title
05-24 17:40:15.224 5090 5090 D SettingsActivity: Switching to fragment com.android.settings.connecteddevice.usb.UsbDetailsFragment
05-24 17:40:15.230 5090 5090 D SettingsActivity: MetricsCategory is 1291
05-24 17:40:15.236 5090 5090 D SettingsActivity: Executed frag manager pendingTransactions
05-24 17:40:15.237 5090 5090 D SettingsActivity: MetricsCategory is 1291
05-24 17:40:15.247 5090 15496 D SettingsActivity: No enabled state changed, skipping updateCategory call
于是,我们可以定位到 UsbDetailsFragment 就是 USB 偏好设置的 fragment:
USBDetailsFragment.java
public class UsbDetailsFragment extends DashboardFragment {
private static final String TAG = UsbDetailsFragment.class.getSimpleName();
private List<UsbDetailsController> mControllers;
private UsbBackend mUsbBackend;
private boolean mUserAuthenticated = false;
@VisibleForTesting
UsbConnectionBroadcastReceiver mUsbReceiver;
private UsbConnectionBroadcastReceiver.UsbConnectionListener mUsbConnectionListener =
(connected, functions, powerRole, dataRole, isUsbFigured) -> {
for (UsbDetailsController controller : mControllers) {
controller.refresh(connected, functions, powerRole, dataRole);
}
};
private UsbConnectionBroadcastReceiver.UsbConnectionListener mUsbConnectionListener =
(connected, functions, powerRole, dataRole, isUsbFigured) -> {
for (UsbDetailsController controller : mControllers) {
controller.refresh(connected, functions, powerRole, dataRole);
}
};
boolean isUserAuthenticated() {
return mUserAuthenticated;
}
void setUserAuthenticated(boolean userAuthenticated) {
mUserAuthenticated = userAuthenticated;
}
@Override
public void onStart() {
super.onStart();
mUserAuthenticated = false;
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
Utils.setActionBarShadowAnimation(getActivity(), getSettingsLifecycle(), getListView());
}
@Override
public int getMetricsCategory() {
return SettingsEnums.USB_DEVICE_DETAILS;
}
@Override
protected String getLogTag() {
return TAG;
}
@Override
protected int getPreferenceScreenResId() {
return R.xml.usb_details_fragment;
}
@Override
protected List<AbstractPreferenceController> createPreferenceControllers(Context context) {
mUsbBackend = new UsbBackend(context);
mControllers = createControllerList(context, mUsbBackend, this);
mUsbReceiver = new UsbConnectionBroadcastReceiver(context, mUsbConnectionListener,
mUsbBackend);
this.getSettingsLifecycle().addObserver(mUsbReceiver);
return new ArrayList<>(mControllers);
}
private static List<UsbDetailsController> createControllerList(Context context,
UsbBackend usbBackend, UsbDetailsFragment fragment) {
List<UsbDetailsController> ret = new ArrayList<>();
ret.add(new UsbDetailsHeaderController(context, fragment, usbBackend));
ret.add(new UsbDetailsDataRoleController(context, fragment, usbBackend));
ret.add(new UsbDetailsFunctionsController(context, fragment, usbBackend));
ret.add(new UsbDetailsPowerRoleController(context, fragment, usbBackend));
ret.add(new UsbDetailsTranscodeMtpController(context, fragment, usbBackend));
return ret;
}
/**
* For Search.
*/
public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
new BaseSearchIndexProvider(R.xml.usb_details_fragment) {
@Override
protected boolean isPageSearchEnabled(Context context) {
return checkIfUsbDataSignalingIsDisabled(
context, UserHandle.myUserId()) == null;
}
@Override
public List<AbstractPreferenceController> createPreferenceControllers(
Context context) {
return new ArrayList<>(
createControllerList(context, new UsbBackend(context), null));
}
};
}
于是可以走到 R.xml.usb_details_fragment.xml 中看一下布局的代码(这个代码较为简单,此不列出),于是可以得知“USB 的用途”的列表项是通过 java 代码进行动态加载的。
定位到 USBDetailsFragment.java 中的 createPreferenceControllers(Context context)方法,然后再定位到 createControllerList(Context context, UsbBackend usbBackend, UsbDetailsFragment fragment) 方法,此时可知 UsbDetailsFunctionsController.java 便是与“USB 的用途”的列表项有关的 controller 类,其核心代码如下:
UsbDetailsFunctionsController.java
private SelectorWithWidgetPreference getProfilePreference(String key, int titleId) {
SelectorWithWidgetPreference pref = mProfilesContainer.findPreference(key);
if (pref == null) {
pref = new SelectorWithWidgetPreference(mProfilesContainer.getContext());
pref.setKey(key);
pref.setTitle(titleId);
pref.setSingleLineTitle(false);
pref.setOnClickListener(this);
mProfilesContainer.addPreference(pref);
}
return pref;
}
@Override
protected void refresh(boolean connected, long functions, int powerRole, int dataRole) {
if (DEBUG) {
Log.d(TAG, "refresh() connected : " + connected + ", functions : " + functions
+ ", powerRole : " + powerRole + ", dataRole : " + dataRole);
}
if (!connected || dataRole != DATA_ROLE_DEVICE) {
mProfilesContainer.setEnabled(false);
} else {
// Functions are only available in device mode
mProfilesContainer.setEnabled(true);
}
SelectorWithWidgetPreference pref;
for (long option : FUNCTIONS_MAP.keySet()) {
int title = FUNCTIONS_MAP.get(option);
pref = getProfilePreference(UsbBackend.usbFunctionsToString(option), title);
// Only show supported options
if (mUsbBackend.areFunctionsSupported(option)) {
if (isAccessoryMode(functions)) {
pref.setChecked(UsbManager.FUNCTION_MTP == option);
} else if (functions == UsbManager.FUNCTION_NCM) {
pref.setChecked(UsbManager.FUNCTION_RNDIS == option);
} else {
pref.setChecked(functions == option);
}
} else {
mProfilesContainer.removePreference(pref);
}
}
}
那么此时我们就可以得知“USB 的用途”的列表项显示与否,就取决于 mUsbBackend.areFunctionsSupported(option) 的回调值了。那么定位到 UsbBackend.java 的核心代码:
UsbBackend.java
public class UsbBackend {
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 final boolean mUVCEnabled;
private final boolean mIsAdminUser;
private UsbManager mUsbManager;
@Nullable
private UsbPort mPort;
@Nullable
private UsbPortStatus mPortStatus;
public UsbBackend(Context context) {
this(context, (UserManager) context.getSystemService(Context.USER_SERVICE));
}
@VisibleForTesting
public UsbBackend(Context context, UserManager userManager) {
mUsbManager = context.getSystemService(UsbManager.class);
mFileTransferRestricted = isUsbFileTransferRestricted(userManager);
mFileTransferRestrictedBySystem = isUsbFileTransferRestrictedBySystem(userManager);
mTetheringRestricted = isUsbTetheringRestricted(userManager);
mTetheringRestrictedBySystem = isUsbTetheringRestrictedBySystem(userManager);
mUVCEnabled = isUvcEnabled();
mIsAdminUser = userManager.isAdminUser();
mMidiSupported = context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_MIDI);
final TetheringManager tm = context.getSystemService(TetheringManager.class);
mTetheringSupported = tm.isTetheringSupported();
updatePorts();
}
public boolean areFunctionsSupported(long functions) {
if ((!mMidiSupported && (functions & UsbManager.FUNCTION_MIDI) != 0)
|| (!mTetheringSupported && (functions & UsbManager.FUNCTION_RNDIS) != 0)) {
return false;
}
return !(areFunctionDisallowed(functions) || areFunctionsDisallowedBySystem(functions)
|| areFunctionsDisallowedByNonAdminUser(functions));
}
private boolean areFunctionDisallowed(long functions) {
return (mFileTransferRestricted && ((functions & UsbManager.FUNCTION_MTP) != 0
|| (functions & UsbManager.FUNCTION_PTP) != 0))
|| (mTetheringRestricted && ((functions & UsbManager.FUNCTION_RNDIS) != 0));
}
private boolean areFunctionsDisallowedBySystem(long functions) {
return (mFileTransferRestrictedBySystem && ((functions & UsbManager.FUNCTION_MTP) != 0
|| (functions & UsbManager.FUNCTION_PTP) != 0))
|| (mTetheringRestrictedBySystem && ((functions & UsbManager.FUNCTION_RNDIS) != 0))
|| (!mUVCEnabled && ((functions & UsbManager.FUNCTION_UVC) != 0));
}
}
设置新的 “USB 的用途”,定位到 UsbManager.java:
UsbManager.java
@SystemApi
@RequiresPermission(Manifest.permission.MANAGE_USB)
public void setCurrentFunctions(@UsbFunctionMode long functions) {
int operationId = sUsbOperationCount.incrementAndGet() + Binder.getCallingUid();
try {
mService.setCurrentFunctions(functions, operationId);
} catch (RemoteException e) {
Log.e(TAG, "setCurrentFunctions: failed to call setCurrentFunctions. functions:"
+ functions + ", opId:" + operationId, e);
throw e.rethrowFromSystemServer();
}
}
/**
* Sets the current USB functions when in device mode.
*
* @deprecated use setCurrentFunctions(long) instead.
* @param functions the USB function(s) to set.
* @param usbDataUnlocked unused
* @hide
*/
@Deprecated
@UnsupportedAppUsage
public void setCurrentFunction(String functions, boolean usbDataUnlocked) {
int operationId = sUsbOperationCount.incrementAndGet() + Binder.getCallingUid();
try {
mService.setCurrentFunction(functions, usbDataUnlocked, operationId);
} catch (RemoteException e) {
Log.e(TAG, "setCurrentFunction: failed to call setCurrentFunction. functions:"
+ functions + ", opId:" + operationId, e);
throw e.rethrowFromSystemServer();
}
}
因此,若想选择“USB 用途”,例如选择开启/关闭mtp,可以直接调用以下代码:
usbManager.setCurrentFunction(UsbManager.USB_FUNCTION_MTP, true);// 选择“文件传输mtp”列表项
usbManager.setCurrentFunction(UsbManager.USB_FUNCTION_NONE, true);// 选择“不用于文件传输”列表项
有时候,我们会有禁用掉一些“USB 用途”的需求,例如启用/禁用mtp,可以调用以下代码:
mUserManager.setUserRestriction(UserManager.DISALLOW_USB_FILE_TRANSFER, true);// 启用 mtp
mUserManager.setUserRestriction(UserManager.DISALLOW_USB_FILE_TRANSFER, false);// 禁用 mtp
从 UsbDetailsFragment.java 的 createPreferenceControllers(Context context) 函数中,可以看到已经用观察者模式注册了 mUsbConnectionListener 的回调,而该回调通过打日志,也不难发现确实每次我们在设置一个“USB 用途”的时候都会回调 Controller 类的 refresh() 方法,也就是刷新界面。但是当我们调用 mUserManager.setUserRestriction() 时,会发现界面并不能按我们预料的那般刷新界面。
既然在 refresh() 函数中,会判断 mUsbBackend.areFunctionsSupported(option) 来刷新界面,那大概率问题也出在这里面。以开启/关闭 mtp 为例,对于上面列出的 UsbBackend.java 源码,我们易发现变量 mFileTransferRestricted 和变量 mFileTransferRestrictedBySystem 都是 final 类型的,亦即进入“USB 偏好设置”后,mUsbBackend.areFunctionsSupported(option) 获取到的结果其实是只会获取一次的,因此无法做到在调用 mUserManager.setUserRestriction()后动态刷新界面。想要实现在开启/禁用 mtp 后动态刷新“USB 偏好设置”界面,只需要作出如下修改:
--- a/src/com/android/settings/connecteddevice/usb/UsbBackend.java
+++ b/src/com/android/settings/connecteddevice/usb/UsbBackend.java
@@ -47,15 +47,16 @@ public class UsbBackend {
static final int PD_ROLE_SWAP_TIMEOUT_MS = 4000;
static final int NONPD_ROLE_SWAP_TIMEOUT_MS = 15000;
- private final boolean mFileTransferRestricted;
- private final boolean mFileTransferRestrictedBySystem;
+ private boolean mFileTransferRestricted;
+ private boolean mFileTransferRestrictedBySystem;
private final boolean mTetheringRestricted;
private final boolean mTetheringRestrictedBySystem;
private final boolean mMidiSupported;
private final boolean mTetheringSupported;
private final boolean mUVCEnabled;
private final boolean mIsAdminUser;
-
+
+ private UserManager mUserManager;
private UsbManager mUsbManager;
@Nullable
@@ -77,6 +78,7 @@ public class UsbBackend {
mTetheringRestrictedBySystem = isUsbTetheringRestrictedBySystem(userManager);
mUVCEnabled = isUvcEnabled();
mIsAdminUser = userManager.isAdminUser();
+ mUserManager = userManager;
mMidiSupported = context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_MIDI);
final TetheringManager tm = context.getSystemService(TetheringManager.class);
@@ -206,12 +208,14 @@ public class UsbBackend {
}
private boolean areFunctionDisallowed(long functions) {
+ mFileTransferRestricted = isUsbFileTransferRestricted(mUserManager);
return (mFileTransferRestricted && ((functions & UsbManager.FUNCTION_MTP) != 0
|| (functions & UsbManager.FUNCTION_PTP) != 0))
|| (mTetheringRestricted && ((functions & UsbManager.FUNCTION_RNDIS) != 0));
}
private boolean areFunctionsDisallowedBySystem(long functions) {
+ mFileTransferRestrictedBySystem = isUsbFileTransferRestrictedBySystem(mUserManager);
return (mFileTransferRestrictedBySystem && ((functions & UsbManager.FUNCTION_MTP) != 0
|| (functions & UsbManager.FUNCTION_PTP) != 0))
|| (mTetheringRestrictedBySystem && ((functions & UsbManager.FUNCTION_RNDIS) != 0))
浙公网安备 33010602011771号