需求:在应用里面实现USB模式动态设置。
场景:不同客户根据自己需求会默认USB模式。 我自己作为开发者来说,如果客户可以动态设置USB模式岂不更好,但是动态设置交给应用,不要去系统设置里面去设置,去系统设置里面设置就不方便了,而且部分客户产品对系统设置是不可见的。
文章目录
- USB模式基础
-
- [文件传输(MTP - Media Transfer Protocol)](#文件传输(MTP - Media Transfer Protocol))
- [USB 网络共享(USB Tethering)](#USB 网络共享(USB Tethering))
- [MIDI(Musical Instrument Digital Interface)](#MIDI(Musical Instrument Digital Interface))
- [PTP(Picture Transfer Protocol)](#PTP(Picture Transfer Protocol))
- 不用于数据传输(仅充电)
- 如何选择?
- 涉及系统源码参考
- 代码分析
-
- 从设置模块进入USB模式,找对应相关信息
- [UsbDefaultFragment - 显示界面-模式选择](#UsbDefaultFragment - 显示界面-模式选择)
-
- [设置选项 setDefaultKey 方法](#设置选项 setDefaultKey 方法)
- [获取选项- FUNCTIONS_MAP](#获取选项- FUNCTIONS_MAP)
- [UsbBackend - 提供USB系统功能入口类](#UsbBackend - 提供USB系统功能入口类)
-
- [设置USB模式 setDefaultUsbFunctions](#设置USB模式 setDefaultUsbFunctions)
- [获取USB模式 getDefaultUsbFunctions](#获取USB模式 getDefaultUsbFunctions)
- UsbDetailsFunctionsController-模式选择的button
-
- [UsbManager 相关key定义](#UsbManager 相关key定义)
- [GadgetFunction 相关key定义](#GadgetFunction 相关key定义)
- [UsbManager - USB模式控制核心](#UsbManager - USB模式控制核心)
- 总结
USB模式基础
我们先看一下手机端吧,大家每次连接将自己的Android手机连接电脑充电时候,会弹出一个框,这个界面是大家常见到的,用户能够感受到的界面,这里先看看弹框图。
从用户的角度,用户能理解的是仅充电、传输文件和传输照片,从研发角度需要知晓每个选项的实际对应的场景。
提到的 USB 模式 是 Android 设备连接电脑时的几种常见选项,每种模式有不同的用途,下面简要说明
文件传输(MTP - Media Transfer Protocol)
-
用途:在电脑和手机之间传输文件(如照片、视频、文档等)。
-
适用场景:需要管理手机存储中的文件时使用。
USB 网络共享(USB Tethering)
- 用途:将手机的移动网络通过 USB 共享给电脑,使电脑可以上网。
- 注意:会消耗手机流量,需确保有足够的流量或 Wi-Fi 热点不可用时使用。
MIDI(Musical Instrument Digital Interface)
- 用途:连接音乐设备(如 MIDI 键盘、音频接口),用于音乐制作或音频应用。
- 适用场景:音乐创作、DAW(数字音频工作站)软件控制。
PTP(Picture Transfer Protocol)
- 用途:传输照片(早期相机常用协议),比 MTP 更简单,但功能有限。
- 注意:现代安卓设备通常优先用 MTP,PTP 兼容性更好但仅支持图片。
不用于数据传输(仅充电)
- 用途:仅通过 USB 充电,禁止数据传输(保护隐私或节省电量)。
如何选择?
-
传文件 → 文件传输(MTP)
-
共享网络 → USB 网络共享
-
音乐制作 → MIDI
-
旧设备传图 → PTP
-
仅充电/安全 → 不用于数据传输
涉及系统源码参考
java
MtkSettings/src/com/android/settings/connecteddevice/usb/UsbDefaultFragment.java
MtkSettings/src/com/android/settings/connecteddevice/usb/UsbBackend.java
MtkSettings/src/com/android/settings/connecteddevice/usb/UsbDetailsFunctionsController.java
frameworks/base/core/java/android/hardware/usb/UsbManager.java
代码分析
从设置模块进入USB模式,找对应相关信息
进入设置->开发者选项-》默认USB配置
我们观察到 logcat 日志有相关内容如下:
java
D Switching to fragment com.android.settings.connecteddevice.usb.UsbDefaultFragment
D Launching fragment com.android.settings.connecteddevice.usb.UsbDefaultFragment
所以我么找下这个文件,对应的其实是设置里面的类,大概目录如下:

这不就是设置里面跟USB 模块相关的内容吗,UsbDefaultFragment 只是展示内容而已。
UsbDefaultFragment - 显示界面-模式选择
看注释,童工默认的USB模式设置
java
/**
* Provides options for selecting the default USB mode.
*/
代码量不大,这里我们分析一下设置和获取逻辑,找到对应的位置:
设置选项 setDefaultKey 方法
@Override
protected boolean setDefaultKey(String key) {
long functions = UsbBackend.usbFunctionsFromString(key);
mPreviousFunctions = mUsbBackend.getCurrentFunctions();
if (!Utils.isMonkeyRunning()) {
if (functions == UsbManager.FUNCTION_RNDIS || functions == UsbManager.FUNCTION_NCM) {
// We need to have entitlement check for usb tethering, so use API in
// TetheringManager.
mCurrentFunctions = functions;
startTethering();
} else {
mIsStartTethering = false;
mCurrentFunctions = functions;
mUsbBackend.setDefaultUsbFunctions(functions);
}
}
return true;
}
这里关注到一个重要的类 mUsbBackend,方法,后面讲解
获取选项- FUNCTIONS_MAP
java
@Override
protected List<? extends CandidateInfo> getCandidates() {
List<CandidateInfo> ret = Lists.newArrayList();
for (final long option : UsbDetailsFunctionsController.FUNCTIONS_MAP.keySet()) {
final String title = getContext().getString(
UsbDetailsFunctionsController.FUNCTIONS_MAP.get(option));
final String key = UsbBackend.usbFunctionsToString(option);
// Only show supported functions
if (mUsbBackend.areFunctionsSupported(option)) {
ret.add(new CandidateInfo(true /* enabled */) {
@Override
public CharSequence loadLabel() {
return title;
}
@Override
public Drawable loadIcon() {
return null;
}
@Override
public String getKey() {
return key;
}
});
}
}
return ret;
}
这里关注到 UsbDetailsFunctionsController.FUNCTIONS_MAP ,后面讲解
获取默认模式
从源码里面可以看到获取默认模式的方法的。
java
mUsbBackend.getDefaultUsbFunctions();


UsbBackend - 提供USB系统功能入口类
看注释:
java
Provides access to underlying system USB functionality.
看方法 setDefaultUsbFunctions,最终调用了 UsbManager.java 类的 setScreenUnlockedFunctions 方法了。
设置USB模式 setDefaultUsbFunctions
java
public void setDefaultUsbFunctions(long functions) {
mUsbManager.setScreenUnlockedFunctions(functions);
}
上面已经分析获取默认的模式方法
获取USB模式 getDefaultUsbFunctions
java
public long getDefaultUsbFunctions() {
return mUsbManager.getScreenUnlockedFunctions();
}
UsbDetailsFunctionsController-模式选择的button
看注释
java
/**
* This class controls the radio buttons for choosing between different USB functions.
*/
看上面引用到的核心代码
java
static final Map<Long, Integer> FUNCTIONS_MAP = new LinkedHashMap<>();
static {
FUNCTIONS_MAP.put(UsbManager.FUNCTION_MTP, R.string.usb_use_file_transfers);
FUNCTIONS_MAP.put(UsbManager.FUNCTION_RNDIS, R.string.usb_use_tethering);
FUNCTIONS_MAP.put(UsbManager.FUNCTION_MIDI, R.string.usb_use_MIDI);
FUNCTIONS_MAP.put(UsbManager.FUNCTION_PTP, R.string.usb_use_photo_transfers);
FUNCTIONS_MAP.put(UsbManager.FUNCTION_NONE, R.string.usb_use_charging_only);
}
所以,这里可以这么理解:
- 在UsbDefaultFragment 类中,显示的不同的model 值包括实际的key 值,都是从 UsbDetailsFunctionsController 类中获取的。然后创建对应的Preference
- UsbDetailsFunctionsController 类中的定义了key 和 value 值,key 又是在UsbManager里面定义的。
UsbManager 相关key定义
UsbManager 源码位置
java
/frameworks/base/core/java/android/hardware/usb/UsbManager.java
查看FUNCTION_MTP 、 FUNCTION_RNDIS 、FUNCTION_MIDI、FUNCTION_PTP、FUNCTION_NONE 是如何定义的。
java
找到===》
@SystemApi
public static final long FUNCTION_NONE = 0;
@SystemApi
public static final long FUNCTION_RNDIS = GadgetFunction.RNDIS;
@SystemApi
public static final long FUNCTION_PTP = GadgetFunction.PTP;
@SystemApi
public static final long FUNCTION_MIDI = GadgetFunction.MIDI;
@SystemApi
public static final long FUNCTION_MTP = GadgetFunction.MTP;
他们是系统的API,居然又是通过GadgeFunction 类定义的。
GadgetFunction 相关key定义
继续追踪 GadgetFunction 代码:
java
/out_sys/soong/.intermediates/hardware/interfaces/usb/gadget/1.2/android.hardware.usb.gadget-V1.2-java_gen_java/gen/srcs/android/hardware/usb/gadget/V1_2/GadgetFunction.java
是编译出来的代码,查看对应的 RNDIS PTP MIDI MTP 值:
java
/**
* Media Transfer protocol function.
*/
public static final long MTP = 4L /* 1 << 2 */;
/**
* Peripheral mode USB Midi function.
*/
public static final long MIDI = 8L /* 1 << 3 */;
/**
* Picture transfer protocol function.
*/
public static final long PTP = 16L /* 1 << 4 */;
/**
* Tethering function.
*/
public static final long RNDIS = 32L /* 1 << 5 */;
这里隐隐约约感觉模式的值 设置和获取的值就是这些0L 、4L、8L、16L、32L
UsbManager - USB模式控制核心
上面已经分析了相关的业务,其中模式的获取和设置在 UsbBackend 类中 分析了其最终调用的方法如下
- 获取USB模式 mUsbManager.getDefaultUsbFunctions();
java
public long getDefaultUsbFunctions() {
return mUsbManager.getScreenUnlockedFunctions();
}
- 设置USB模式 mUsbBackend.setDefaultUsbFunctions(functions)
java
ublic void setDefaultUsbFunctions(long functions) {
mUsbManager.setScreenUnlockedFunctions(functions);
}
那么我们跟踪到UsbManager类,方法如下:
java
/**
* Sets the screen unlocked functions, which are persisted and set as the current functions
* whenever the screen is unlocked.
* <p>
* A zero mask has the effect of switching off this feature, so functions
* no longer change on screen unlock.
* </p><p>
* Note: When the screen is on, this method will apply given functions as current functions,
* which is asynchronous and may fail silently without applying the requested changes.
* </p>
*
* @param functions functions to set, in a bitwise mask.
* Must satisfy {@link UsbManager#areSettableFunctions}
*
* {@hide}
*/
public void setScreenUnlockedFunctions(long functions) {
try {
mService.setScreenUnlockedFunctions(functions);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Gets the current screen unlocked functions.
*
* @return The currently set screen enabled functions.
* A zero mask indicates that the screen unlocked functions feature is not enabled.
*
* {@hide}
*/
public long getScreenUnlockedFunctions() {
try {
return mService.getScreenUnlockedFunctions();
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
都是隐藏的方法,那么我们通过 应用来获取看看到底什么值
java
fun getScreenUnlockedFunctions(): Long? {
return try {
// 获取 UsbManager 实例
val usbManager = ContextProvider.get().context.getSystemService(Context.USB_SERVICE) as UsbManager
// 获取 UsbManager 的 Class 对象
val usbManagerClass: Class<*> = usbManager.javaClass
// 获取隐藏的 getScreenUnlockedFunctions 方法
val method: Method = usbManagerClass.getMethod("getScreenUnlockedFunctions")
// 调用方法并返回结果
method.invoke(usbManager) as Long
} catch (e: Exception) {
Log.d(TAG," getScreenUnlockedFunctions error ")
e.printStackTrace()
null
}
}
/**
* 设置屏幕解锁时的 USB 功能
* @param context 上下文对象
* @param functions 要设置的功能值
* @return 是否设置成功
*/
fun setScreenUnlockedFunctions(functions: Long): Boolean {
return try {
// 获取 UsbManager 实例
val usbManager = ContextProvider.get().context.getSystemService(Context.USB_SERVICE) as UsbManager
// 获取 UsbManager 的 Class 对象
val usbManagerClass: Class<*> = usbManager.javaClass
// 获取隐藏的 setScreenUnlockedFunctions 方法
val method: Method = usbManagerClass.getMethod(
"setScreenUnlockedFunctions",
Long::class.javaPrimitiveType
)
// 调用方法
method.invoke(usbManager, functions)
true
} catch (e: Exception) {
e.printStackTrace()
false
}
}
简单的测试界面如下,配合实验验证:
通过实验发现,系统设置里面切换不同的USB模式,在应用里面去获取,得到的模式值如下:
模式 | 得到的值 |
---|---|
不用于数据传输 | 0 |
文件传输 | 4 |
MIDI | 8 |
PTP | 16 |
USB 网络共享 | 32 |
设置值恰好就是获取的值,对应的其实就是UsbManager 中的常量值:
FUNCTION_MTP 、 FUNCTION_RNDIS 、FUNCTION_MIDI、FUNCTION_PTP、FUNCTION_NONE
总结
- 实现应用层对USB模式的动态设置,对UsbManager类中关联的方法进行反射调用
- USB模式相关切换的源码分析
备注:在实际场景中 开机后系统默认某个模式,网上也大量这样的讲解,这里暂不讨论,只针对动态设置模式的需求进行研究一次。