This paper analyzes the startup of the MTP server, and analyzes how the data (files, directories) in the storage is mapped to the PC after switching the MTP mode.

First you need to know how to switch MTP modes. When the phone connects to the computer via USB, a notification about USB will appear. After clicking on the notification, an interface similar to the following will appear

The File Transfer option is MTP mode.

Based on the in-depth analysis of the Android MTP UsbService startup, it can be seen that when the USB function is switched, a broadcast of the USB status change will be sent. So who receives this broadcast? What are you doing with this broadcast?

The first broadcast receiver is the MtpReceiver of the MediaProvider module

public class MtpReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        final String action = intent.getAction();
        if (Intent.ACTION_BOOT_COMPLETED.equals(action)) {
            // Because the usb state change broadcast is sticky broadcast, it can be obtained in this way
            final Intent usbState = context.registerReceiver(
                    null.new IntentFilter(UsbManager.ACTION_USB_STATE));
            if(usbState ! =null) { handleUsbState(context, usbState); }}else if(UsbManager.ACTION_USB_STATE.equals(action)) { handleUsbState(context, intent); }}}Copy the code

First we can see that handleUsbState() is used to handleUsbState changes.

Then we should also note that receiving the boot broadcast here also handles a USB state change. The way to register the broadcast receiver is different from the way we normally use, the broadcast receiver parameter is null, this is because the USB state change broadcast is sticky broadcast.

Now take a look at how handleUsbState() handles USB state change broadcasts.

    private void handleUsbState(Context context, Intent intent) {
        Bundle extras = intent.getExtras();
        boolean configured = extras.getBoolean(UsbManager.USB_CONFIGURED);
        boolean connected = extras.getBoolean(UsbManager.USB_CONNECTED);
        boolean mtpEnabled = extras.getBoolean(UsbManager.USB_FUNCTION_MTP);
        boolean ptpEnabled = extras.getBoolean(UsbManager.USB_FUNCTION_PTP);
        boolean unlocked = extras.getBoolean(UsbManager.USB_DATA_UNLOCKED);
        boolean isCurrentUser = UserHandle.myUserId() == ActivityManager.getCurrentUser();

        if (configured && (mtpEnabled || ptpEnabled)) {
            // Handles USB connection and is in PTP or MTP mode
            if(! isCurrentUser)return;
            intent = new Intent(context, MtpService.class);
            // Note that a parameter is passed to indicate whether the data is unlocked
            intent.putExtra(UsbManager.USB_DATA_UNLOCKED, unlocked);
            // Handle PTP mode cases
            if (ptpEnabled) {
                intent.putExtra(UsbManager.USB_FUNCTION_PTP, true);
            }
            context.startService(intent);
        } else if(! connected || ! (mtpEnabled || ptpEnabled)) {// Handle usb disconnection or not MTP and PTP
            / / stop MtpService
            boolean status = context.stopService(newIntent(context, MtpService.class)); }}Copy the code

First, it gets the various parameters of the Intent, all of which were covered in the previous article and explained here.

Both configured and Connected values are true when the phone successfully connects to the computer over a USB cable, and false when the phone is disconnected.

MtpEnabled Indicates whether the MTP function is enabled. PtpEnabled indicates whether the PTP function is enabled. Unlocked Indicates whether data is unlocked. MTP and PTP data are unlocked.

The unlocked state means that the mapping files stored on the phone can be seen on the PC.

After getting the parameters, the next logic is to start MtpService if the USB connection is successful and in MTP or PTP mode, or stop MtpService. This also shows that MtpService only handles MTP and PTP schemas.

The next step is to analyze the creation and startup process of MtpService. We’ll start by analyzing the creation process, which will call onCreate()

    public void onCreate(a) {
        // Get all the storage
        mVolumes = StorageManager.getVolumeList(getUserId(), 0);
        // mVolumeMap saves the mounted storage
        mVolumeMap = new HashMap<>();

        mStorageManager = this.getSystemService(StorageManager.class);
        // Register storage state change events
        mStorageManager.registerListener(mStorageEventListener);
    }
Copy the code

During the creation, two things are done: one is to obtain all the storage in the mobile phone; the other is to register a listener to monitor the change of storage status. For example, when a new storage is mounted and in MTP mode, it will notify the PC to carry out storage mapping. This process will be analyzed later.

Once MtpService is created, onStartCommand() is then called to perform the task

    public synchronized int onStartCommand(Intent intent, int flags, int startId) {
        // Indicates whether data is unlocked
        mUnlocked = intent.getBooleanExtra(UsbManager.USB_DATA_UNLOCKED, false);
        // mPtpMode false indicates MTP mode because this Service only handles MTP and PTP modes
        mPtpMode = intent.getBooleanExtra(UsbManager.USB_FUNCTION_PTP, false);
        
        // Get the mounted storage
        for (StorageVolume v : mVolumes) {
            if (v.getState().equals(Environment.MEDIA_MOUNTED)) {
                mVolumeMap.put(v.getPath(), v);
            }
        }
        
        String[] subdirs = null;
        if (mPtpMode) {
            // ...
        }
        
        // Get the primary store, which is used for PTP mode
        final StorageVolume primary = StorageManager.getPrimaryVolume(mVolumes);
        // Start the service
        // In MTP mode, the second argument is null and the first argument has no effect
        startServer(primary, subdirs);
        return START_REDELIVER_INTENT;
    }
Copy the code

The two parameters passed in are parsed, the mounted storage is saved with mVolumeMap, and a server is started.

This article analyzes only the MTP mode. The two parameters passed when starting the server through startServer() are not useful for the MTP mode, but only for the PTP mode, as we will see later in the analysis.

Now how does this server start

    private synchronized void startServer(StorageVolume primary, String[] subdirs) {
        if(! (UserHandle.myUserId() == ActivityManager.getCurrentUser())) {return;
        }
        synchronized (MtpService.class) {
            // If sServerHolder is not null, the service has been started
            if(sServerHolder ! =null) {
                return;
            }

            // 1. Create the MtpDatabase object
            // MtpDatabase provides an interface for upper-level MTP operations
            final MtpDatabase database = new MtpDatabase(this, subdirs);
            
            // My project does not support Hal layer, so the controlFd obtained here is null
            IUsbManager usbMgr = IUsbManager.Stub.asInterface(ServiceManager.getService(
                    Context.USB_SERVICE));
            ParcelFileDescriptor controlFd = null;
            try {
                controlFd = usbMgr.getControlFd(
                        mPtpMode ? UsbManager.FUNCTION_PTP : UsbManager.FUNCTION_MTP);
            } catch (RemoteException e) {
                Log.e(TAG, "Error communicating with UsbManager: " + e);
            }
            FileDescriptor fd = null;
            if (controlFd == null) {
                Log.i(TAG, "Couldn't get control FD!");
            } else {
                fd = controlFd.getFileDescriptor();
            }

            // 2. Create the MtpServer object
            // MtpServer is the interface between the upper layer and the JNI layer
            final MtpServer server =
                    new MtpServer(database, fd, mPtpMode,
                            new OnServerTerminated(), Build.MANUFACTURER,
                            Build.MODEL, "1.0");
                            
            database.setServer(server);
            sServerHolder = new ServerHolder(server, database);


            // 3. Notify the PC to start storage mapping
            // Note that storage is mapped only when data is unlocked
            if (mUnlocked) {
                if (mPtpMode) {
                    // In PTP mode, only primary storage is mapped
                    addStorage(primary);
                } else {
                    // Map all mounted storage in MTP mode
                    for(StorageVolume v : mVolumeMap.values()) { addStorage(v); }}}// 4. Start the server
            // What does this service do?server.start(); }}Copy the code

The code here looks simple, but it’s actually quite complex, so I’ll describe the logic in general and then break it down into modules.

The first step is to create an MtpDatabase object, which is simply an interface for the Framework to operate on MTP. It manages storage through MtpStorageManager, and listens to the file system, for example, when the mobile end adds a new file, it will notify MtpDatabase through callback, and then MtpDatabase will notify the PC side to map the newly added file.

The second step is to create an MtpServer object. The MtpServer class is the framework layer interface for manipulating JNI. Inside the MtpDatabase is the MtpServer object that sends information to the PC.

Step 3 notify the PC to perform storage mapping. When the PC receives the notification, it sends a request to the mobile phone to obtain the stored data for mapping.

Step 4: Start a server that handles requests from the PC side. For example, handle the request for storage data in step 3.

Here, I’ll explain these processes in four parts.

Create an interface for upper-level operations on MTP

MtpDatabase is the framework layer interface to manipulate MTP, that is, if you want to do some MTP operations from the top, you must use this class.

Now what does the constructor of MtpDatabase do

    public MtpDatabase(Context context, String[] subDirectories) {
        // 1. JNI layer initialization
        // Use mNativeContext to save the JNI layer MtpDatabase object
        native_setup();
        
        // 2. Obtain the client interface provided by media to obtain various information about media files
        mContext = Objects.requireNonNull(context);
        mMediaProvider = context.getContentResolver()
                .acquireContentProviderClient(MediaStore.AUTHORITY);

        // 3. Create the MtpStorageMananger object
        // MtpStorageManager listens for file system changes, and then notifies MtpDatabase with callbacks. MtpDatabse notifies PC via MtpServer
        mManager = new MtpStorageManager(new MtpStorageManager.MtpNotifier() {
            @Override
            public void sendObjectAdded(int id) {
                if (MtpDatabase.this.mServer ! =null)
                    MtpDatabase.this.mServer.sendObjectAdded(id);
            }

            @Override
            public void sendObjectRemoved(int id) {
                if (MtpDatabase.this.mServer ! =null)
                    MtpDatabase.this.mServer.sendObjectRemoved(id);
            }

            @Override
            public void sendObjectInfoChanged(int id) {
                if (MtpDatabase.this.mServer ! =null)
                    MtpDatabase.this.mServer.sendObjectInfoChanged(id);
            }
        }, subDirectories == null ? null : Sets.newHashSet(subDirectories));

        
        / /... Leave out some code that doesn't matter
    }
Copy the code

You’ve just introduced the role of MtpDatabase, which is represented here by variable creation and initialization. Let’s focus on the first step, which completes initialization in the JNI layer.

Media operation JNI libraries path for frameworks/base/media/JNI.

Native_setup () is produced by the frameworks/base/media/jni/android_mtp_MtpDatabase android_mtp_MtpDatabase_setup of CPP () implementation

static void
android_mtp_MtpDatabase_setup(JNIEnv *env, jobject thiz)
{   
    // Create a JNI layer MtpDatabase object
    MtpDatabase* database = new MtpDatabase(env, thiz);
    // Reference the MtpDtaabase object created by the JNI layer with the mNativeContext of the Java layer MtpDatabase object
    env->SetLongField(thiz, field_context, (jlong)database);
    checkAndClearExceptionFromCallback(env, __FUNCTION__);
}
Copy the code

This article does not cover the basics of JNI; this article requires you to have some JNI basics.

JNI layer of initialization, originally just using the Java layer MtpDatabase. MNativeContext preserved the native MtpDatabase objects created.

The MtpDatabase class is a nested class in android_mtP_mtpDatabase.cpp. Take a look at its constructor

MtpDatabase::MtpDatabase(JNIEnv *env, jobject client)
    :   mDatabase(env->NewGlobalRef(client)), // mDatabase points to the Java layer MtpDatabase object
        mIntBuffer(NULL),
        mLongBuffer(NULL),
        mStringBuffer(NULL)
{
    jintArray intArray = env->NewIntArray(3);
    if(! intArray) {return; 
    }
    mIntBuffer = (jintArray)env->NewGlobalRef(intArray);
    
    jlongArray longArray = env->NewLongArray(2);
    if(! longArray) {return; 
    }
    mLongBuffer = (jlongArray)env->NewGlobalRef(longArray);

    jcharArray charArray = env->NewCharArray(PATH_MAX + 1);
    if(! charArray) {return; 
    }
    mStringBuffer = (jcharArray)env->NewGlobalRef(charArray);
}
Copy the code

The most important piece of code here is the initialization of the mDatabase variable, which is mDatabase(env->NewGlobalRef(client)). MDatabase is a branch reference that points to the Java layer MtpDatabase object. In this way, MtpDatabase objects in the JNI layer also hold MtpDatabase references in the Java layer.

Now we can see that after the initialization of the JNI layer, MtpDatabase objects in the Java layer and MtpDatabase objects in the JNI layer refer to each other. This allows the JNI and Java layers to interoperate.

Read the source code, we learn to apply, for example, here the upper layer and native how to establish a mapping relationship.

Create MtpServer

After analyzing the creation process of MtpDatabase, let’s take a look at the creation process of MtpServer. Before analyzing this, LET me remind you of the role of MtpServer, which is the interface between the Framework layer and the JNI layer.

    public MtpServer(
            MtpDatabase database,
            FileDescriptor controlFd,
            boolean usePtp,
            Runnable onTerminate,
            String deviceInfoManufacturer,
            String deviceInfoModel,
            String deviceInfoDeviceVersion) {
        mDatabase = Preconditions.checkNotNull(database);
        mOnTerminate = Preconditions.checkNotNull(onTerminate);
        mContext = mDatabase.getContext();

        final String strID_PREFS_NAME = "mtp-cfg";
        final String strID_PREFS_KEY = "mtp-id";
        String strRandomId = null;
        String deviceInfoSerialNumber;

        SharedPreferences sharedPref =
                mContext.getSharedPreferences(strID_PREFS_NAME, Context.MODE_PRIVATE);
        if (sharedPref.contains(strID_PREFS_KEY)) {
            strRandomId = sharedPref.getString(strID_PREFS_KEY, null);

            // Check for format consistence (regenerate upon corruption)
            if(strRandomId.length() ! = sID_LEN_STR) { strRandomId =null;
            } else {
                // Only accept hex digit
                for (int ii = 0; ii < strRandomId.length(); ii++)
                    if (Character.digit(strRandomId.charAt(ii), 16) = = -1) {
                        strRandomId = null;
                        break; }}}if (strRandomId == null) {
            strRandomId = getRandId();
            sharedPref.edit().putString(strID_PREFS_KEY, strRandomId).apply();
        }
        
        // 1. Obtain a device serial number
        deviceInfoSerialNumber = strRandomId;
        
        // 2. Perform initialization of the JNI layer
        native_setup(
                database,
                controlFd,
                usePtp,
                deviceInfoManufacturer,
                deviceInfoModel,
                deviceInfoDeviceVersion,
                deviceInfoSerialNumber);
        
        // This step should be redundant, so MtpService does it immediately
        database.setServer(this);
    }
Copy the code

The first step is to get a random device serial number. You can see that it is saved/retrieved using SharedPreferences.

The second step is to perform initialization of the JNI layer. It by frameworks/base/media/jni/android_mtp_MtpServer android_mtp_MtpServer_setup of CPP () implementation

static void
android_mtp_MtpServer_setup(JNIEnv *env, jobject thiz, jobject javaDatabase, jobject jControlFd, jboolean usePtp, jstring deviceInfoManufacturer, jstring deviceInfoModel, jstring deviceInfoDeviceVersion, jstring deviceInfoSerialNumber)
{
    // Convert string arguments passed in by the Java layer to native Pointers
    const char *deviceInfoManufacturerStr = env->GetStringUTFChars(deviceInfoManufacturer, NULL);
    const char *deviceInfoModelStr = env->GetStringUTFChars(deviceInfoModel, NULL);
    const char *deviceInfoDeviceVersionStr = env->GetStringUTFChars(deviceInfoDeviceVersion, NULL);
    const char *deviceInfoSerialNumberStr = env->GetStringUTFChars(deviceInfoSerialNumber, NULL);
    int controlFd = dup(jniGetFDFromFileDescriptor(env, jControlFd));
    
    // 1. Create MtpServer for JNI layer
    MtpServer* server = newMtpServer(getMtpDatabase(env, javaDatabase), controlFd, usePtp, (deviceInfoManufacturerStr ! = NULL) ? deviceInfoManufacturerStr :"", (deviceInfoModelStr ! = NULL) ? deviceInfoModelStr :"", (deviceInfoDeviceVersionStr ! = NULL) ? deviceInfoDeviceVersionStr :"", (deviceInfoSerialNumberStr ! = NULL) ? deviceInfoSerialNumberStr :"");
            
    // Free the memory of the string to which the pointer points
    if(deviceInfoManufacturerStr ! = NULL) { env->ReleaseStringUTFChars(deviceInfoManufacturer, deviceInfoManufacturerStr); }if(deviceInfoModelStr ! = NULL) { env->ReleaseStringUTFChars(deviceInfoModel, deviceInfoModelStr); }if(deviceInfoDeviceVersionStr ! = NULL) { env->ReleaseStringUTFChars(deviceInfoDeviceVersion, deviceInfoDeviceVersionStr); }if(deviceInfoSerialNumberStr ! = NULL) { env->ReleaseStringUTFChars(deviceInfoSerialNumber, deviceInfoSerialNumberStr); }// 2. Use Java layer mtpServer. mNativeContext to reference JNI layer MtpServer object
    env->SetLongField(thiz, field_MtpServer_nativeContext, (jlong)server);
}
Copy the code

The first step is to create the native layer MtpServer object, and the second step is to reference the native layer MtpServer object with the Java layer mtpServer.mnativecontext variable.

Now MtpServer native layer under the creation process, its path for frameworks/av/media/MTP/MtpServer CPP

The path of the libmtp.so library is frameworks/av/media/ MTP

MtpServer::MtpServer(IMtpDatabase* database, int controlFd, bool ptp,
                    const char *deviceInfoManufacturer,
                    const char *deviceInfoModel,
                    const char *deviceInfoDeviceVersion,
                    const char *deviceInfoSerialNumber)
    :   mDatabase(database), // mDatabase points to the native layer MtpDatabase
        mPtp(ptp),
        mDeviceInfoManufacturer(deviceInfoManufacturer),
        mDeviceInfoModel(deviceInfoModel),
        mDeviceInfoDeviceVersion(deviceInfoDeviceVersion),
        mDeviceInfoSerialNumber(deviceInfoSerialNumber),
        mSessionID(0),
        mSessionOpen(false),
        mSendObjectHandle(kInvalidObjectHandle),
        mSendObjectFormat(0),
        mSendObjectFileSize(0),
        mSendObjectModifiedTime(0)
{
    bool ffs_ok = access(FFS_MTP_EP0, W_OK) == 0;
    if (ffs_ok) {
        
    } else {
        // mHandle points to an IMtpHandle object
        mHandle = newMtpDevHandle(); }}Copy the code

Most of the process of creating MtpServer objects involves initialization or assignment of variables, but there are two things to note. First, the mDatabase variable points to the native layer MtpDatabase, which is the MtpDatabase object created earlier. Then the mHandle pointer points to the MtpDevHandle object of the IMtpHandle implementation class, which defines the interface of the native layer to operate MTP.

Now summarize the process of creating the Java layer MtpServer

  1. MNativeContext of MtpServer is used to bind MtpServer objects in native layer.
  2. Native layer MtpServer objects use mDatabase to point to native layer MtpDatabase objects.

Data mapping

Now everything is ready except the east wind. Let’s analyze the mapping process from mobile phone storage to PC. The code snippet is as follows

    private synchronized void startServer(StorageVolume primary, String[] subdirs) {
        // ...
        synchronized (MtpService.class) {
            // 1. Create the MtpDatabase object
            // 2. Create the MtpServer object

            // 3. Notify the PC to start storage mapping
            // Note that storage is mapped only when data is unlocked
            if (mUnlocked) {
                if (mPtpMode) {
                    // In PTP mode, only primary storage is mapped
                    addStorage(primary);
                } else {
                    // Map all mounted storage in MTP mode
                    for(StorageVolume v : mVolumeMap.values()) { addStorage(v); }}}// 4. Start the server...}}Copy the code

As you can see from the code, storage mapping takes place only when the data is unlocked.

If THE USB mode is MTP, all mounted storage devices will be mapped. When USB mode is PTP, only the primary storage is mapped.

Now how does addStorage() perform the storage mapping

    private void addStorage(StorageVolume volume) {
        synchronized (MtpService.class) {
            if(sServerHolder ! =null) { sServerHolder.database.addStorage(volume); }}}Copy the code

It is very simple to use MtpDatabase for MTP operations, which confirms that MtpDatabase is the interface for upper-level MTP operations.

Now look at the addStorage() method of MtpDatabase

    public void addStorage(StorageVolume storage) {
        // Assign an ID to the storage and create an MtpStorage object representing the storage
        MtpStorage mtpStorage = mManager.addMtpStorage(storage);
        // Save the storage
        mStorageMap.put(storage.getPath(), mtpStorage);
        // Add storage via MtpServer
        if(mServer ! =null) { mServer.addStorage(mtpStorage); }}Copy the code

The MtpStroageManager first assigns an ID to the storage to be added and creates an MtpStorage object that represents the storage. MtpDatabase then stores the MtpStorage object. Finally, the operation of adding storage is handed over to the MtpServer.

As mentioned above, MtpServer is the interface between the upper layer and THE JNI layer. Here, the operation of adding storage is handed over to MtpServer, which is actually to map storage through the JNI layer and inform the PC side. Let’s look at the addStorage() implementation of MtpServer

    public void addStorage(MtpStorage storage) {
        native_add_storage(storage);
    }
Copy the code

As we said earlier, the JNI layer methods are called. This method is by the frameworks/base/media/jni/android_mtp_MtpServer android_mtp_MtpServer_add_storage of CPP () implementation

static void
android_mtp_MtpServer_add_storage(JNIEnv *env, jobject thiz, jobject jstorage)
{
    Mutex::Autolock autoLock(sMutex);
    
    // 1. Get MtpServer of native layer
    MtpServer* server = getMtpServer(env, thiz);
    if (server) {
        // Get the values of various variables from the Java layer MtpStorage object
        jint storageID = env->GetIntField(jstorage, field_MtpStorage_storageId);
        jstring path = (jstring)env->GetObjectField(jstorage, field_MtpStorage_path);
        jstring description = (jstring)env->GetObjectField(jstorage, field_MtpStorage_description);
        jboolean removable = env->GetBooleanField(jstorage, field_MtpStorage_removable);
        jlong maxFileSize = env->GetLongField(jstorage, field_MtpStorage_maxFileSize);

        const char *pathStr = env->GetStringUTFChars(path, NULL);
        if(pathStr ! =NULL) {
            const char *descriptionStr = env->GetStringUTFChars(description, NULL);
            if(descriptionStr ! =NULL) {
                // 2. Create native layer MtpStorage object
                MtpStorage* storage = new MtpStorage(storageID, pathStr, descriptionStr,
                        removable, maxFileSize);
                // 3. Add storage through native layer MtpServer
                server->addStorage(storage);
                env->ReleaseStringUTFChars(path, pathStr);
                env->ReleaseStringUTFChars(description, descriptionStr);
            } else{ env->ReleaseStringUTFChars(path, pathStr); }}}else {
        ALOGE("server is null in add_storage"); }}Copy the code

Here the operation is also very simple, first obtain various properties of Java layer MtpStorage object, and then use these properties to create native layer MtpStorage object, and finally give the operation of adding storage to native layer MtpServer to perform.

Now look at native layer MtpServer addStorage()

void MtpServer::addStorage(MtpStorage* storage) {
    std::lock_guard<std::mutex> lg(mMutex);
    // Save to the collection
    mStorages.push_back(storage);
    sendStoreAdded(storage->getStorageID());
}

void MtpServer::sendStoreAdded(MtpStorageID id) {
    sendEvent(MTP_EVENT_STORE_ADDED, id);
}

void MtpServer::sendEvent(MtpEventCode code, uint32_t param1) {
    if (mSessionOpen) {
        // Perform data populating
        mEvent.setEventCode(code);
        mEvent.setTransactionID(mRequest.getTransactionID());
        mEvent.setParameter(1, param1);
        // Send a message to the driver via IMtpHandle's sendEvent() interface
        if (mEvent.write(mHandle))
            ALOGE("Mtp send event failed: %s", strerror(errno)); }}Copy the code

Firstly, the data is encapsulated according to THE MTP protocol, and then a message is sent to the driver through the mHandle(Native MTP interface). The driver completes the function of informing the PC.

I’m not going to break down the code for the content of the MTP protocol, and the same goes for the content of the USB driver. However, if you want to extend some operations of MTP, you must first read the MTP protocol in detail and analyze the code step by step.

I spent a day or two on the project reading the MTP protocol and analyzing the source code implementation. Without this foundation, there is no need to talk about the realization of their OWN MTP function, which is called sharpening the knife without miscutting firewood.

Assuming that the information has been successfully sent to the PC through the USB driver, the PC will send a request to the phone to get information about the storage, such as the size of the storage, what files are in the storage, and so on. Then the PC side uses this information to create the corresponding storage mapping, which is the stored file we see on the PC side.

In previous versions of Android, mass Storage was used instead of MTP. Mass Storage directly mounts the mobile phone storage to the PC, so that the actual mobile phone storage is operated on the PC. However, this will lead to a very serious problem, that is, the mobile phone cannot use the storage at this time. A typical example is that the camera cannot take photos after switching the Mass storage. MTP just sets up the storage mapping, so even if you switch to MTP mode, the phone can still use the storage. However, COMPARED with Mass storage, MTP also has a disadvantage. That is, file operation is not as fast as Mass storage, especially when processing a large number of files, such as file replication, the speed is relatively slow, because it requires a process of data synchronization.

Process MTP requests

Storage mapping actually leaves a problem. When the PC receives the event mapped to the storage of the mobile phone, the PC will send a request to the mobile phone, which is used to obtain storage information. Then how does the mobile phone deal with the request of the PC? So let’s move on.

    private synchronized void startServer(StorageVolume primary, String[] subdirs) {
        if(! (UserHandle.myUserId() == ActivityManager.getCurrentUser())) {return;
        }
        synchronized (MtpService.class) {
            // If sServerHolder is not null, the service has been started
            if(sServerHolder ! =null) {
                return;
            }

            // 1. Create the MtpDatabase object
            
            // 2. Create the MtpServer object

            // 3. Notify the PC to start storage mapping

            // 4. Start the server to process the MTP requestserver.start(); }}Copy the code

Here the start() method of MtpServe is called

    public void start(a) {
        Thread thread = new Thread(this."MtpServer");
        thread.start();
    }
Copy the code

It’s very simple. You start a single thread, and what is that thread doing

    public void run(a) {
        // The underlying layer processes PC requests through an infinite loop
        native_run();
        
        // The following operations usually occur when the MTP connection is disconnected. These are clean-up operations
        native_cleanup();
        mDatabase.close();
        mOnTerminate.run();
    }
Copy the code

Native_run () starts an infinite loop underneath to process PC requests. If the MTP breaks down or an exception occurs in the processing request, the subsequent cleanup operations are performed.

Native_run () is implemented by android_mtp_MtpServer_run() from android_mtp_mtpserver.cpp

static void
android_mtp_MtpServer_run(JNIEnv *env, jobject thiz)
{
    MtpServer* server = getMtpServer(env, thiz);
    if (server)
        server->run();
    else
        ALOGE("server is null in run");
}
Copy the code

Processing requests is left to the native layer’s MtpServer’s Run () method

void MtpServer::run() {
    // Open the MTP node
    if (mHandle->start(mPtp)) {
        ALOGE("Failed to start usb driver!");
        mHandle->close();
        return;
    }
    
    // Process PC requests through an infinite loop
    while (1) {
        int ret = mRequest.read(mHandle);
        if (ret < 0) {
            ALOGE("request read returned %d, errno: %d", ret, errno);
            if (errno == ECANCELED) {
                // return to top of loop and wait for next command
                continue;
            }
            break;
        }
        MtpOperationCode operation = mRequest.getOperationCode();
        MtpTransactionID transaction = mRequest.getTransactionID();

        // If the PC sends data, it reads it
        bool dataIn = (operation == MTP_OPERATION_SEND_OBJECT_INFO
                    || operation == MTP_OPERATION_SET_OBJECT_REFERENCES
                    || operation == MTP_OPERATION_SET_OBJECT_PROP_VALUE
                    || operation == MTP_OPERATION_SET_DEVICE_PROP_VALUE);
        if (dataIn) {
            int ret = mData.read(mHandle);
            if (ret < 0) {
                ALOGE("data read returned %d, errno: %d", ret, errno);
                if (errno == ECANCELED) {
                    // return to top of loop and wait for next command
                    continue;
                }
                break;
            }
            ALOGV("received data:");
        } else {
            mData.reset();
        }
        
        // Process MTP requests
        if (handleRequest()) {
            // After processing the request, the data is sent to the PC
            if(! dataIn && mData.hasData()) { mData.setOperationCode(operation); mData.setTransactionID(transaction); ALOGV("sending data:");
                ret = mData.write(mHandle);
                if (ret < 0) {
                    ALOGE("request write returned %d, errno: %d", ret, errno);
                    if (errno == ECANCELED) {
                        // return to top of loop and wait for next command
                        continue;
                    }
                    break; }}// Send the result of processing data to the PC
            mResponse.setTransactionID(transaction);
            ret = mResponse.write(mHandle);
            const int savedErrno = errno;
            if (ret < 0) {
                ALOGE("request write returned %d, errno: %d", ret, errno);
                if (savedErrno == ECANCELED) {
                    // return to top of loop and wait for next command
                    continue;
                }
                break; }}else {
            ALOGV("skipping response\n"); }}// This is an exception to the request
    
    // Submit some edits that are already open
    int count = mObjectEditList.size();
    for (int i = 0; i < count; i++) {
        ObjectEdit* edit = mObjectEditList[i];
        commitEdit(edit);
        delete edit;
    }
    mObjectEditList.clear();
    
    // Close the MTP node
    mHandle->close();
}
Copy the code

The code here is long, including data reading, request processing, data sending, and so on. All of this is based on the MTP protocol, so be sure to familiarize yourself with the MTP protocol if you want to extend your operations here.

But when we analyze code, we need to look at the big picture. You can see that the request is processed through an infinite loop of while followed by handleRequest(). For example, the request for storage information from the PC is handled here.

The end of the

This article analyzes how mobile phone storage is mapped on the PC side from a holistic perspective, but it does not involve the internalization of the specific MTP protocol, let alone the driver content. If you want to use MTP to do something, first need to read the MTP protocol, then look at the source implementation, and then do what you want to do, and this article is a complete version of the MTP framework analysis.