MTK-USB模式动态设置

需求:在应用里面实现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

总结

  1. 实现应用层对USB模式的动态设置,对UsbManager类中关联的方法进行反射调用
  2. USB模式相关切换的源码分析

备注:在实际场景中 开机后系统默认某个模式,网上也大量这样的讲解,这里暂不讨论,只针对动态设置模式的需求进行研究一次。