• 博客园logo
  • 会员
  • 众包
  • 新闻
  • 博问
  • 闪存
  • 赞助商
  • HarmonyOS
  • Chat2DB
    • 搜索
      所有博客
    • 搜索
      当前博客
  • 写随笔 我的博客 短消息 简洁模式
    用户头像
    我的博客 我的园子 账号设置 会员中心 简洁模式 ... 退出登录
    注册 登录

RomanLin

  • 博客园
  • 联系
  • 订阅
  • 管理

公告

View Post

【Android】USB偏好设置-mtp 文件传输

USB偏好设置

在 Android 设备上,USB 偏好设置(USB Preferences)允许用户自定义设备通过 USB 连接电脑或其他主机时的行为,例如选择文件传输模式、充电模式或网络共享等。这个功能通常在连接 USB 数据线后,通过通知栏或系统设置进行配置。

USB偏好设置的主要选项

当 Android 设备通过 USB 连接电脑时,通常会弹出通知或提示,让用户选择 USB 用途。常见的选项包括:

  1. 文件传输(MTP)
    用途:在电脑和手机之间传输文件(照片、视频、文档等)
    适用场景:备份照片或音乐到电脑;从电脑导入文件到手机。
    特点:手机存储以“媒体设备”形式显示在电脑上;不会影响手机正常使用(可同时使用其他应用)。
  2. 照片传输(PTP)
    用途:仅传输照片和视频(适用于老式相机或某些软件)
    适用场景:需要快速导出照片(如 Adobe Lightroom 导入);兼容性更好的旧设备连接。
    特点:电脑仅识别 DCIM(相机照片)文件夹;比 MTP 更简单,但功能有限。
  3. USB 网络共享(USB Tethering)
    用途:将手机的移动网络共享给电脑使用(类似有线热点)。
    适用场景:电脑没有 Wi-Fi 时(如台式机)通过手机上网;比 Wi-Fi 热点更稳定、耗电更低。
    特点:需要手机开启移动数据;部分运营商可能限制此功能。
  4. MIDI(音乐设备数字接口)
    用途:连接 MIDI 设备(如电子琴、音乐控制器)。
    适用场景:音乐制作(如使用 FL Studio、GarageBand);外接 MIDI 键盘或鼓机。
    特点:低延迟音频传输;仅适用于专业音乐应用。
  5. 仅充电
    用途:仅通过 USB 充电,不传输数据。
    适用场景:在公共 USB 端口(如机场、网吧)充电时避免数据泄露;快速充电(某些电脑 USB 端口电流较低)。
    特点:电脑无法访问手机文件,更安全。
  6. 默认 USB 配置(Android 10+)
    在 开发者选项 中,可以设置默认的 USB 行为(如自动进入文件传输模式)。
    路径:设置 > 系统 > 开发者选项 > 默认 USB 配置。

mtp 文件传输

MTP(Media Transfer Protocol,媒体传输协议)是一种由微软开发的通信协议,主要用于在计算机和便携设备(如智能手机、数码相机、媒体播放器等)之间传输媒体文件。它是传统USB大容量存储(USB Mass Storage, UMS)的替代方案,解决了UMS在文件系统兼容性和设备独占访问等方面的局限性。

典型应用场景

  1. Android设备传输文件:连接电脑后通过MTP管理照片、音乐等。
  2. 数码相机/播放器:导出媒体内容而不影响设备正常运行。
  3. 云服务同步:部分应用通过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))

posted on 2025-06-03 18:10  RomanLin  阅读(778)  评论(0)    收藏  举报

刷新页面返回顶部
 
博客园  ©  2004-2025
浙公网安备 33010602011771号 浙ICP备2021040463号-3