Android USB預設連線模式為MTP

weixin_34308389發表於2017-06-14

很多時候,手機專案開發,客戶都要求配置USB的預設連線方式,但是在Android 6.0以及之後的版本就直接配置USB連線模式,看到的USB連線模式還是僅充電,而這是google的預設設計。那麼對於這個問題,也看了很多網上的一些解法,如:Android 5.0可以直接配置預設值,6.0就不可行了。另外,還有在USB連線的時候進行設定,當連線之後就執行一次設定USB連線模式,將MTP設定為當前連線模式,這樣的做法在7.0上面也是可以的。

事實上,當僅充電的時候,檢視當前USB模式的配置,就算是mtp,但是還是沒有顯示SD卡和內部儲存器,主要是一個標誌的問題。

現有的一種解法

那麼這裡先說一下在連線USB的情況下,執行一次USB連線模式的設定,這裡有一位大牛的方法,並且提到Android 6.0以前的做法,Android 6.0 USB連線模式預設選為MTP ,大家去參考學習一下,那麼我說一下這位大牛的改法,在Android 6.0和7.0上面修改後不同的一個地方:

Android版本 在鎖屏的情況下 在解鎖的情況下
6.0 僅充電 MTP
7.0 MTP MTP

對於這樣的解法,我們的測試就提了一個不安全的bug,沒有解鎖就能直接訪問SD卡、內部儲存器的資料,這是不安全的。那好像也說得有道理,那麼就改。但是這個不解鎖能訪問儲存器的功能,可能又符合某些公司的客戶需求。

公司中6.0的解法

對於上面的解法不怎麼像6.0的行為,接著我就去看了一下,我們公司以前Android 6.0是怎麼修改的,是直接修改了值,將這個值修改後,就能顯示SD卡、內部儲存器了。

修改檔案frameworks/base/services/usb/java/com/android/server/usb/UsbDeviceManager.java

public class UsbDeviceManager {
                  ···省略很多程式碼···
        private boolean mConfigured;
        //modified in 2017-05-22 start
        //解鎖資料,連線電腦,就能看到預設連線模式為MTP
        private boolean mUsbDataUnlocked = true;
        //modified in 2017-05-22 end
        private String mCurrentFunctions;
        private String mDefaultFunctions;
        
                ······
                
         @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MSG_UPDATE_STATE:
                    mConnected = (msg.arg1 == 1);
                    mConfigured = (msg.arg2 == 1);
                    mUsbConfigured = mConfigured;
                    if (!mConnected) {
                        //modified in 2017-05-22 start
                        //default usb connect mode as mtp
                        // When a disconnect occurs, relock access to sensitive user data
                        //斷開連線的時候,保持當前連線模式,下次連線的時候還是MTP
                        mUsbDataUnlocked = true;
                        //modified in 2017-05-22 end
                    }
                    updateUsbNotification();
                    updateAdbNotification();
                    if (UsbManager.containsFunction(mCurrentFunctions,
                            UsbManager.USB_FUNCTION_ACCESSORY)) {
                        updateCurrentAccessory();
                    } else if (!mConnected) {
                        //modified  in 2017-05-22 start
                        //change default usb connect mode as mtp,do not restore
                        // 這裡不恢復預設連線方式,保持保持當前的連線模式
                        setEnabledFunctions(null, true);
                        //modified  in 2017-05-22 end
                    }
                    if (mBootCompleted) {
                        updateUsbStateBroadcastIfNeeded();
                        updateUsbFunctions();
                    }
                      ······
    }

上面的這種修改方式比較簡單,在android6.0和7.0上都是可以的,但是還是有差異:

Android版本 在鎖屏的情況下 在解鎖的情況下
6.0 僅充電 MTP
7.0 MTP MTP

這中修改方式的最終結果還是跟上面那位大牛的修改方式表現結果一致。那麼應該說是Google在7.0上又修改了這一部分的程式碼,使用公司以前6.0的改法,還是不行。

現在Android 7.0的解法

但是還是對不上公司測試提的問題,那還是需要接續修改。那就仔細看看這類,再上網瞭解一下USB連線模式這塊,這一塊還是很深的...,涉及硬體的,都是稍微複雜一點,還要跟底層通訊什麼的,有一點大概的瞭解之後,再回來解決一下這個問題,那麼從切換USB連線模式的上層實現,那麼就是修改mUsbDataUnlocked的值,應該說是在適當的時候修改mUsbDataUnlocked的值,那就看到USB連線模式是MTP,那麼下面分析一下有幾種情況需要改變:

  1. 手機是鎖屏情況下連線USB,連線模式是僅充電。
  2. 手機是解鎖的情況下連線USB,連線模式是MTP。
  3. 手機熄屏的情況下斷開USB,連線方式要更新為僅充電。
  4. 手機解鎖的情況下斷開USB,連線方式要更新為僅充電。

應該就是這四種情況,那麼下面主要要解決的問題是:

  1. 使用者解鎖了,通過主測廣播:Intent.ACTION_USER_PRESENT判斷是否解鎖後使用者到前臺。
  2. USB連線狀態有對應的回撥,並且這個時候更新USB連線模式。
  3. 熄屏了,通過註冊廣播Intent.ACTION_SCREEN_OFF判斷,還需要註冊廣播Intent.ACTION_SCREEN_ON,作為螢幕狀態值的切換。

在UsbService.java類中註冊以上說到的廣播接收者,再對UsbDeviceManager.java類中的標識進行更新。

1.修改frameworks/base/services/usb/java/com/android/server/usb/UsbService.java

public class UsbService extends IUsbManager.Stub {
                ······
        final IntentFilter filter = new IntentFilter();
        filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
        filter.addAction(Intent.ACTION_USER_SWITCHED);
        filter.addAction(Intent.ACTION_USER_STOPPED);
        //add by xx in 2017-06-12 for bug 169853 start
        //註冊三個廣播
        filter.addAction(Intent.ACTION_USER_PRESENT);
        filter.addAction(Intent.ACTION_SCREEN_OFF);
        filter.addAction(Intent.ACTION_SCREEN_ON);
        //add by xx in 2017-06-12 for bug 169853 end
        filter.addAction(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED);
        mContext.registerReceiver(mReceiver, filter, null, null);
        
                ······

    private BroadcastReceiver mReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
            final String action = intent.getAction();
            if (Intent.ACTION_USER_SWITCHED.equals(action)) {
                setCurrentUser(userId);
            } else if (Intent.ACTION_USER_STOPPED.equals(action)) {
                synchronized (mLock) {
                    mSettingsByUser.remove(userId);
                }
            } else if (DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED
                    .equals(action)) {
                if (mDeviceManager != null) {
                    mDeviceManager.updateUserRestrictions();
                }
            //add by xx in 2017-06-12 for bug 169853 start
            //處理三個廣播
            }else if (Intent.ACTION_USER_PRESENT.equals(action)) {

                if (mDeviceManager != null) {
                    mDeviceManager.usbDataUnlocked(true);//這個方法系統有的
                    mDeviceManager.setUserPresent();//這個方法是新增的
                }
            } else if (Intent.ACTION_SCREEN_OFF.equals(action)) {
                if (mDeviceManager != null) {
                    mDeviceManager.updateScreentSate(true);//這個方法是新增的,為了更新螢幕狀態
                }
            } else if (Intent.ACTION_SCREEN_ON.equals(action)) {
              if (mDeviceManager != null) {
                    mDeviceManager.updateScreentSate(false);//這個方法是新增的,為了更新螢幕狀態
                }
            //add by xx in 2017-06-12 for bug 169853 end
            }
        }
    };
}

2.接下來修改frameworks/base/services/usb/java/com/android/server/usb/UsbDeviceManager.java

public class UsbDeviceManager {
        ·····
    //新增兩個全域性變數,作為標識
    //modified by xx in 2017-06-12 for bug 169853 start
    private boolean mUserPresent = false;//使用者是否結果到前臺
    private boolean screenOff = false;//螢幕是否是熄屏
    private boolean changeByUser = false;//切換USB模式的時候,是這裡程式碼切換的,還是使用者點選切換的。
    //modified by xx in 2017-06-12 for bug 169853 end
    
            ·····
    //新增這兩個方法,在UsbService.java中用到,更新這邊的狀態
    //modified by xx in 2017-06-12 for bug 169853 start
    public void setUserPresent(){
        mUserPresent = true;
    }

    public void updateScreentSate(boolean state){
        screenOff = state;
        if(!screenOff) {
             mUserPresent = false;
        }
        if(mHandler != null){
            mHandler.updateUsbMode();
        }
    }

    public void usbDataUnlocked(){
        changeByUser  = false;
        mHandler.sendMessage(MSG_SET_USB_DATA_UNLOCKED, true);
    }
    //modified by xx in 2017-06-12 for bug 169853 end
    
            ·····
      
      //更新USB連線狀態
      public void updateState(String state) {
            int connected, configured;

            if ("HWDISCONNECTED".equals(state)) {
                connected = 0;
                configured = 0;
                mHwDisconnected = true;
                //add by xx in 2017-08-03 for swtich usb mode start
                changeByUser = false;
                //add by xx in 2017-08-03 for swtich usb mode end
            } else if ("DISCONNECTED".equals(state)) {
                connected = 0;
                configured = 0;
                mHwDisconnected = false;

                //modified by xx in 2017-06-12 for bug 169853 start
                //當熄屏的情況下,更新使用者不在前臺的標識
                if(screenOff){
                    mUserPresent = false;
                 }
                 //modified by xxx in 2017-06-12 for bug 169853 end

            } else if ("CONNECTED".equals(state)) {
                connected = 1;
                configured = 0;
                
                    ·····
        
        
        private final class UsbHandler extends Handler { 
        
                        ·····
                        
        //在收到更新USB狀態的訊息之後,更新USB模式,當然要根據使用者是否在前臺進行判斷
         @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MSG_UPDATE_STATE:
                    mConnected = (msg.arg1 == 1);
                    mConfigured = (msg.arg2 == 1);
                    mUsbConfigured = mConfigured;
                    //modified by xx in 2017-06-12 for bug 169853 start
                    //update usb state first
                    updateUsbMode();
                    if (mUserPresent && !changeByUser) {   //使用者在前臺的
                        mUsbDataUnlocked = true;//解鎖資料,那麼連線電腦就能看到連線模式為MTP了
                    }
                    //modified by xx in 2017-06-12 for bug 169853 end

                    if (!mConnected) {
                        // When a disconnect occurs, relock access to sensitive user data
                        mUsbDataUnlocked = false;
                    }
                    updateUsbNotification();
                    updateAdbNotification();
                    if (UsbManager.containsFunction(mCurrentFunctions,
                            UsbManager.USB_FUNCTION_ACCESSORY)) {
                        updateCurrentAccessory();
                    } else if (!mConnected) {
                    }
                            ·····//省略程式碼
                case MSG_SET_USB_DATA_UNLOCKED:
                    //add by xx in 2017-08-03 for swtich usb mode start
                    if(!changeByUser && mUsbDataUnlocked) return;
                    //add by xx in 2017-08-03 for swtich usb mode end
                    setUsbDataUnlocked(msg.arg1 == 1);
                    break;
                }
                            ·····
            //在UsbHandler類中的方法,主要是因為用到USB狀態值:mConnected
            //add by xx in 2017-06-12 for bug 169853 start
            private void updateUsbMode(){
                if(!mConnected && screenOff){
                    mUserPresent = false;
                }
            }
            //add by xx in 2017-06-12 for bug 169853 end
        }
                ·····
   }
   //省略其他程式碼
   public void setCurrentFunctions(String functions) {
        if (DEBUG) Slog.d(TAG, "setCurrentFunctions(" + functions + ")");
        //add by xx in 2017-08-03 for swtich usb mode start
        changeByUser = true;
        //add by xx in 2017-08-03 for swtich usb mode end
        mHandler.sendMessage(MSG_SET_CURRENT_FUNCTIONS, functions);
    }
    //省略其他程式碼
}

主要修改上面兩個類,實現以下情況:

Android版本 在鎖屏的情況下 在解鎖的情況下
7.0 僅充電 MTP

那麼這樣的行為就跟Android 6.0 的表現一樣了,問題可以說已經解決了。
像這些修改一個預設值的問題還是比較簡單的。事實上,對USB完全沒有接觸,還是要多看,有那麼多巨人,就借個肩膀來站站唄,就像本文一開始提到的那位大神一樣,在這裡表示感謝。

感謝 SymphonyZhang 的提醒,在Android 7.1中“只要persist.sys.usb.config這個屬性的值設為mtp,後面不要加上adb那些什麼的,就可以了”

相關文章