MTK-USB模式动态设置
需求:在应用里面实现USB模式动态设置。
场景:不同客户根据自己需求会默认USB模式。 我自己作为开发者来说,如果客户可以动态设置USB模式岂不更好,但是动态设置交给应用,不要去系统设置里面去设置,去系统设置里面设置就不方便了,而且部分客户产品对系统设置是不可见的。
文章目录
- USB模式基础
- 文件传输(MTP - Media Transfer Protocol)
- USB 网络共享(USB Tethering)
- MIDI(Musical Instrument Digital Interface)
- PTP(Picture Transfer Protocol)
- 不用于数据传输(仅充电)
- 如何选择?
- 涉及系统源码参考
- 代码分析
- 从设置模块进入USB模式,找对应相关信息
- UsbDefaultFragment - 显示界面-模式选择
- 设置选项 setDefaultKey 方法
- 获取选项- FUNCTIONS_MAP
- 获取默认模式
- UsbBackend - 提供USB系统功能入口类
- 设置USB模式 setDefaultUsbFunctions
- 获取USB模式 getDefaultUsbFunctions
- UsbDetailsFunctionsController-模式选择的button
- UsbManager 相关key定义
- GadgetFunction 相关key定义
- 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
-
仅充电/安全 → 不用于数据传输
涉及系统源码参考
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 日志有相关内容如下:
D Switching to fragment com.android.settings.connecteddevice.usb.UsbDefaultFragmentD Launching fragment com.android.settings.connecteddevice.usb.UsbDefaultFragment
所以我么找下这个文件,对应的其实是设置里面的类,大概目录如下:
这不就是设置里面跟USB 模块相关的内容吗,UsbDefaultFragment 只是展示内容而已。
UsbDefaultFragment - 显示界面-模式选择
看注释,童工默认的USB模式设置
/*** 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
@Overrideprotected 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 functionsif (mUsbBackend.areFunctionsSupported(option)) {ret.add(new CandidateInfo(true /* enabled */) {@Overridepublic CharSequence loadLabel() {return title;}@Overridepublic Drawable loadIcon() {return null;}@Overridepublic String getKey() {return key;}});}}return ret;}
这里关注到 UsbDetailsFunctionsController.FUNCTIONS_MAP ,后面讲解
获取默认模式
从源码里面可以看到获取默认模式的方法的。
mUsbBackend.getDefaultUsbFunctions();
UsbBackend - 提供USB系统功能入口类
看注释:
Provides access to underlying system USB functionality.
看方法 setDefaultUsbFunctions,最终调用了 UsbManager.java 类的 setScreenUnlockedFunctions 方法了。
设置USB模式 setDefaultUsbFunctions
public void setDefaultUsbFunctions(long functions) {mUsbManager.setScreenUnlockedFunctions(functions);}
上面已经分析获取默认的模式方法
获取USB模式 getDefaultUsbFunctions
public long getDefaultUsbFunctions() {return mUsbManager.getScreenUnlockedFunctions();}
UsbDetailsFunctionsController-模式选择的button
看注释
/*** This class controls the radio buttons for choosing between different USB functions.*/
看上面引用到的核心代码
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 源码位置
/frameworks/base/core/java/android/hardware/usb/UsbManager.java
查看FUNCTION_MTP 、 FUNCTION_RNDIS 、FUNCTION_MIDI、FUNCTION_PTP、FUNCTION_NONE 是如何定义的。
找到===》@SystemApipublic static final long FUNCTION_NONE = 0;@SystemApipublic static final long FUNCTION_RNDIS = GadgetFunction.RNDIS;@SystemApipublic static final long FUNCTION_PTP = GadgetFunction.PTP;@SystemApipublic static final long FUNCTION_MIDI = GadgetFunction.MIDI;@SystemApipublic static final long FUNCTION_MTP = GadgetFunction.MTP;
他们是系统的API,居然又是通过GadgeFunction 类定义的。
GadgetFunction 相关key定义
继续追踪 GadgetFunction 代码:
/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 值:
/*** 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();
public long getDefaultUsbFunctions() {return mUsbManager.getScreenUnlockedFunctions();}
- 设置USB模式 mUsbBackend.setDefaultUsbFunctions(functions)
ublic void setDefaultUsbFunctions(long functions) {mUsbManager.setScreenUnlockedFunctions(functions);}
那么我们跟踪到UsbManager类,方法如下:
/*** 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();}}
都是隐藏的方法,那么我们通过 应用来获取看看到底什么值
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模式相关切换的源码分析
备注:在实际场景中 开机后系统默认某个模式,网上也大量这样的讲解,这里暂不讨论,只针对动态设置模式的需求进行研究一次。