gossip
The company took over the project to develop an online upgrade function, in which I needed to realize the communication between mobile and PC. The company chose to use MTP to implement this requirement, so I analyzed a lot of MTP code, from the Frameworks layer to the app to the JNI layer. In view of the lack of online articles about this, and my development process is relatively long, so I decided to write down the framework, APP, JNI layer analysis, hoping to help develop similar functions of partners.
Deng Fanping, a teacher who wrote a series of in-depth understanding of Android books, some of which are no longer published, but who posted all the content of his articles on his blog, said knowledge needs to be passed on. This, I deeply admire.
Service is open
UsbService is a system service that is created and registered in the system_server process.
private static final String USB_SERVICE_CLASS =
"com.android.server.usb.UsbService$Lifecycle";
private void startOtherServices(a) {
// ...
mSystemServiceManager.startService(USB_SERVICE_CLASS);
// ...
}
Copy the code
SystemServiceManager creates the UsbService$Lifecycle object (Lifecycle is an internal class of the UsbService) through reflection, adds it to the List collection, and finally calls the onStart method of the Lifcycle object.
SystemServiceManager stores the various services and tells the service about the various phases of system startup. We can look at the various Lifecycle callbacks of UsbService$Lifecycle
public class UsbService extends IUsbManager.Stub {
public static class Lifecycle extends SystemService {
// Service creation phase
@Override
public void onStart(a) {
mUsbService = new UsbService(getContext());
publishBinderService(Context.USB_SERVICE, mUsbService);
}
// Respond to the system startup phase
@Override
public void onBootPhase(int phase) {
if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) {
// System ready
mUsbService.systemReady();
} else if (phase == SystemService.PHASE_BOOT_COMPLETED) {
// The system is startedmUsbService.bootCompleted(); }}}}Copy the code
As you can see, a service goes through creation phases, system ready phases, and system startup phases. Next, the UsbService startup process is analyzed in three parts.
Service creation phase
In the service creation phase, a UsbService object is first created, and since UsbService is a Binder object, the service is then published to the ServiceManager. Once the service is published, clients can access it.
Now look at the UsbService constructor
public UsbService(Context context) {
// ...
if (new File("/sys/class/android_usb").exists()) {
mDeviceManager = new UsbDeviceManager(context, mAlsaManager, mSettingsManager);
}
// ...
}
Copy the code
In the constructor, the main MTP-related code is to create the UsbDeviceManager object.
In MTP mode, the Android Device functions as the Device side and UsbDeviceManager is used to process transactions on the Device side.
Now what does the constructor of UsbDeviceManager do
public UsbDeviceManager(Context context, UsbAlsaManager alsaManager, UsbSettingsManager settingsManager) {
// ...
// My project does not support Hal layer of MTP
if (halNotPresent) {
1. Initialize mHandler
mHandler = new UsbHandlerLegacy(FgThread.get().getLooper(), mContext, this,
alsaManager, settingsManager);
} else {
// ...
}
// 2. Register various broadcasts
/ /... There are actually a number of broadcast receivers registered (but with the code omitted)
BroadcastReceiver chargingReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
int chargePlug = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1);
booleanusbCharging = chargePlug == BatteryManager.BATTERY_PLUGGED_USB; mHandler.sendMessage(MSG_UPDATE_CHARGING_STATE, usbCharging); }}; mContext.registerReceiver(chargingReceiver,new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
// 3. Listen for USB status changes
mUEventObserver = new UsbUEventObserver();
mUEventObserver.startObserving(USB_STATE_MATCH);
mUEventObserver.startObserving(USB_STATE_MATCH_SEC);
mUEventObserver.startObserving(ACCESSORY_START_MATCH);
}
Copy the code
The constructor for UsbDeviceManager does three things.
First thing, initialize the mHandler object. Since my project does not support the Hal layer of MTP, mHandler is initialized using the UsbHandlerLegacy object.
Second, register various broadcast receivers, such as port changes, language changes, etc. Here I’ve shown the radio receiver code for charging. When we connect the phone to the computer through the USB cable, the phone will charge and a notification about USB charging will appear on the phone. By opening this notification about USB, we can switch USB functions, such as MTP, PTP, etc.
Third, listen for USB state changes through the Linux Uevent mechanism. When your phone connects to your computer over a USB cable, the USB state changes from DISCONNECTED to CONNECTED to CONFIGURED. The USB status update operation is handled when the state changes, which will be analyzed later.
Now look at the creation of the UsbHandlerLegacy object.
UsbHandlerLegacy(Looper looper, Context context, UsbDeviceManager deviceManager,
UsbAlsaManager alsaManager, UsbSettingsManager settingsManager) {
// The superclass constructor initializes some parameters, which you can analyze for yourself as needed
super(looper, context, deviceManager, alsaManager, settingsManager);
try {
// 1. Read the OEM coverage configuration
readOemUsbOverrideConfig(context);
// 2. Read the values of various attributes
// 2.1 In normal mode, the value of the persist.sys.usb.config property is read
mCurrentOemFunctions = getSystemProperty(getPersistProp(false),
UsbManager.USB_FUNCTION_NONE);
// If the value of ro.bootmode is normal or unknown, it indicates normal startup
if (isNormalBoot()) {
// 2.2 Read the value of the sys.usb.config property, which represents the currently set USB function
mCurrentFunctionsStr = getSystemProperty(USB_CONFIG_PROPERTY,
UsbManager.USB_FUNCTION_NONE);
// 2.3 Compare the value of the sys.usb.config property with that of the sys.usb.state property
// The sys.usb.state property represents the actual function of the USB
// If the two properties are equal, all usb Settings are in effect
mCurrentFunctionsApplied = mCurrentFunctionsStr.equals(
getSystemProperty(USB_STATE_PROPERTY, UsbManager.USB_FUNCTION_NONE));
} else{}// mCurrentFunctions represents the CURRENT USB function to be set. The initial value is 0
mCurrentFunctions = UsbManager.FUNCTION_NONE;
mCurrentUsbFunctionsReceived = true;
// 3. Read the USB status once and then update it
String state = FileUtils.readTextFile(new File(STATE_PATH), 0.null).trim();
updateState(state);
} catch (Exception e) {
Slog.e(TAG, "Error initializing UsbHandler", e); }}Copy the code
The constructors for UsbHandlerLegacy are roughly divided into three steps. First look at the first step, read the OEM’s coverage configuration for USB functionality.
private void readOemUsbOverrideConfig(Context context) {
Bootmode :[original USB mode]:[USB mode used]
String[] configList = context.getResources().getStringArray(
com.android.internal.R.array.config_oemUsbModeOverride);
if(configList ! =null) {
for (String config : configList) {
String[] items = config.split(":");
if (items.length == 3 || items.length == 4) {
if (mOemModeMap == null) {
mOemModeMap = new HashMap<>();
}
HashMap<String, Pair<String, String>> overrideMap =
mOemModeMap.get(items[0]);
if (overrideMap == null) {
overrideMap = new HashMap<>();
mOemModeMap.put(items[0], overrideMap);
}
// Favoring the first combination if duplicate exists
if(! overrideMap.containsKey(items[1]) {if (items.length == 3) {
overrideMap.put(items[1].new Pair<>(items[2].""));
} else {
overrideMap.put(items[1].new Pair<>(items[2], items[3]));
}
}
}
}
}
}
Copy the code
The config_oemUsbModeOverride array is read and saved to mOemModeMap. The format of each item is [bootmode]:[original USB mode]:[USB mode used], The saved format can be roughly described as HashMap
. In my project, this array is empty.
Then the second step reads the various property values (considering only normal startup mode) as follows.
- MCurrentOemFunctions value is
persist.sys.usb.config
Property value. According to the source code comments, this property value stores the status of ADB enabled (if ADB is enabled, the value will contain the ADB string). In addition, the source code notes that this property can also be used for carrier custom functions, but only for testing purposes. - MCurrentFunctionsStr value is
sys.usb.config
Attribute values. This property represents the value of the usb function currently set. On a daily basis, we can set this property value via adb shell commands to switch USB functions, for exampleadb shell setprop sys.usb.config mtp,adb
You can switch to MTP. - If through
sys.usb.config
Property switch succeeded, thensys.usb.state
The property value is equal tosys.usb.config
Property values are the same. That is to say,sys.usb.state
A value that represents the actual function of the USB. Therefore, you can compare the two values to determine whether all usb functions have been successfully switched. If so, mCurrentFunctionsApplied is 1, otherwise it is 0.
In the third step, the current USB state is read and an update operation is performed. The update operation sends notifications, as well as broadcasts, but this operation cannot be performed now that the service creation phase is being processed, so it is not analyzed here. However, when dealing with the system readiness phase or the system startup phase, you can do something about it, as you’ll see in the analysis below.
System readiness stage
According to the previous code, during the system readiness phase, the systemRead() method of UsbService is called and then the systemRead() method of UsbDeviceManager is passed on
public void systemReady(a) {
// Register a callback about the screen state. There are two methods
LocalServices.getService(ActivityTaskManagerInternal.class).registerScreenObserver(this);
mHandler.sendEmptyMessage(MSG_SYSTEM_READY);
}
Copy the code
First, a screen callback is registered, which is used to set up usb functions under the security lock screen. However, this feature appears to be in development and can only be operated with the ADB shell command. You can see the help by typing adb shell SVC USB.
Next, a message MSG_SYSTEM_READY is sent. How is this message handled
case MSG_SYSTEM_READY:
// The Notification service interface is obtained
mNotificationManager = (NotificationManager)
mContext.getSystemService(Context.NOTIFICATION_SERVICE);
// Register a callback with adb Service to state the ADB related state
LocalServices.getService(
AdbManagerInternal.class).registerTransport(new AdbTransport(this));
// Ensure that the notification channels are set up
if (isTv()) {
// ...
}
// Set the system ready flag bit
mSystemReady = true;
// The system has not been started yet
// This is probably due to historical code redundancy
finishBoot();
break;
Copy the code
As you can see, the interface of the notification service was obtained during the system readiness phase, which also proves that notifications cannot be sent during the UsbService creation phase. However, I am a little confused. The notification service is in the same process as the UsbService, and the notification service also has an internal interface for the system service to call. Why do we need to send a broadcast through NotificationManager? Just for the convenience of writing code, but in doing so, an unnecessary Binder communication (maybe I’m lying!) was created. ?
Once you get the notification service interface, you register a callback with the ADB service, through which you can receive messages about ADB opening/closing.
System startup phase
Now let’s look at the final stage, the system boot up phase. Based on the previous code, the bootCompleted () method of UsbService is called, followed by the bootCompleted () method of UsbDeviceManager
public void bootCompleted(a) {
mHandler.sendEmptyMessage(MSG_BOOT_COMPLETED);
}
Copy the code
I’m just sending a message. How is the message handled
case MSG_BOOT_COMPLETED:
// Set a flag indicating that the system has started
mBootCompleted = true;
finishBoot();
break;
Copy the code
Quite simply, a start flag is set and the finishBoot() method is called to complete the final task
protected void finishBoot(a) {
if (mBootCompleted && mCurrentUsbFunctionsReceived && mSystemReady) {
// mPendingBootBroadcast is set during service creation
if (mPendingBootBroadcast) {
// 1. Send/update a broadcast of the USB status change
updateUsbStateBroadcastIfNeeded(getAppliedFunctions(mCurrentFunctions));
mPendingBootBroadcast = false;
}
if(! mScreenLocked && mScreenUnlockedFunctions ! = UsbManager.FUNCTION_NONE) {// This function is still in the debugging stage, not analyzed
setScreenUnlockedFunctions();
} else {
// 2. Set USB function to NONE
setEnabledFunctions(UsbManager.FUNCTION_NONE, false);
}
// About Accessory function
if(mCurrentAccessory ! =null) {
mUsbDeviceManager.getCurrentSettings().accessoryAttached(mCurrentAccessory);
}
// 3. Send usb notification if the phone is connected to the computer, through which you can select USB mode
updateUsbNotification(false);
// 4. If ADB is enabled and the phone is connected to the computer, send adb notification
updateAdbNotification(false);
// About MIDI functionalityupdateUsbFunctions(); }}Copy the code
Step 1 sending USB status broadcast, step 3 USB notification, and step 4 ADB notification cannot be performed if the phone is not currently connected to the computer via USB cable. The only thing that can be done is the second step, setting the USB function to NONE.
OK, now finally comes the crucial step, setting up the USB function, which calls the setEnabledFunctions() method. The method itself is an abstract method, and in my project, the implementation class is UsbHandlerLegacy
protected void setEnabledFunctions(long usbFunctions, boolean forceRestart) {
// Check whether data is unlocked. Only MTP and PTP data are unlocked
boolean usbDataUnlocked = isUsbDataTransferActive(usbFunctions);
// Handle data unlock status changes
if(usbDataUnlocked ! = mUsbDataUnlocked) {// Update the data unlock status
mUsbDataUnlocked = usbDataUnlocked;
// Update USB notifications
updateUsbNotification(false);
// If forceRestart is set to true, the USB function needs to be forcibly restarted
forceRestart = true;
}
// Before setting the new USB function, save the old state to avoid the failure of setting the new function and can be restored
final long oldFunctions = mCurrentFunctions;
final boolean oldFunctionsApplied = mCurrentFunctionsApplied;
// Try setting usb new features
if (trySetEnabledFunctions(usbFunctions, forceRestart)) {
return;
}
// The new function failed to be set
if(oldFunctionsApplied && oldFunctions ! = usbFunctions) { Slog.e(TAG,"Failsafe 1: Restoring previous USB functions.");
if (trySetEnabledFunctions(oldFunctions, false)) {
return; }}// If the callback fails, set the USB function to NONE
if (trySetEnabledFunctions(UsbManager.FUNCTION_NONE, false)) {
return;
}
// If setting NONE still fails, try setting NONE again
if (trySetEnabledFunctions(UsbManager.FUNCTION_NONE, false)) {
return;
}
// If this is the case, it is not the case.
Slog.e(TAG, "Unable to set any USB functions!");
}
Copy the code
First, check whether the data of the new USB function to be set is unlocked. Only the data of MTP and PTP mode is unlocked. This is why you can see the file in the phone on the PC after setting MTP or PTP mode.
If the data unlock status changes, then the status is updated, the USB broadcast is updated, and most importantly, the forceRestart variable is set to true, which means the USB function is forced to restart.
Finally, set up the new USB function. If you fail, fall back. Now how does the trySetEnabledFunctions() method set the new function
private boolean trySetEnabledFunctions(long usbFunctions, boolean forceRestart) {
// 1. Convert the new USB function to a string
String functions = null;
// If the new function is not NONE, convert
if(usbFunctions ! = UsbManager.FUNCTION_NONE) { functions = UsbManager.usbFunctionsToString(usbFunctions); }// Save the new USB function to be set
mCurrentFunctions = usbFunctions;
// If the converted function is empty, fetch it from somewhere else
if (functions == null || applyAdbFunction(functions)
.equals(UsbManager.USB_FUNCTION_NONE)) {
// Get the value of the persist.sys.usb.config property
functions = getSystemProperty(getPersistProp(true),
UsbManager.USB_FUNCTION_NONE);
// If persist. Sys.usb. config is still NONE
if (functions.equals(UsbManager.USB_FUNCTION_NONE))
If adb is enabled, adb is returned, otherwise MTP is returned
functions = UsbManager.usbFunctionsToString(getChargingFunctions());
}
// if adb is enabled, append ADB, otherwise remove ADB
functions = applyAdbFunction(functions);
// 2. Obtain the USB function covered by OEM
String oemFunctions = applyOemOverrideFunction(functions);
// Handle abnormal startup mode, ignore
if(! isNormalBoot() && ! mCurrentFunctionsStr.equals(functions)) { setSystemProperty(getPersistProp(true), functions);
}
// 3. Set the new function
if((! functions.equals(oemFunctions) && ! mCurrentOemFunctions.equals(oemFunctions)) || ! mCurrentFunctionsStr.equals(functions) || ! mCurrentFunctionsApplied || forceRestart) { Slog.i(TAG,"Setting USB config to " + functions);
// Save the string value corresponding to the new function to be set
mCurrentFunctionsStr = functions;
// Save the string value of the OEM override function
mCurrentOemFunctions = oemFunctions;
mCurrentFunctionsApplied = false;
// Disconnect the existing USB connection
setUsbConfig(UsbManager.USB_FUNCTION_NONE);
// Check whether it succeeded
if(! waitForState(UsbManager.USB_FUNCTION_NONE)) { Slog.e(TAG,"Failed to kick USB config");
return false;
}
// Set the new feature. Note that OEM covered features are used here
setUsbConfig(oemFunctions);
// If the new feature includes MTP or PTP, update the USB status change broadcast
// Broadcast receiver will map files in main memory to PC
if (mBootCompleted
&& (containsFunction(functions, UsbManager.USB_FUNCTION_MTP)
|| containsFunction(functions, UsbManager.USB_FUNCTION_PTP))) {
updateUsbStateBroadcastIfNeeded(getAppliedFunctions(mCurrentFunctions));
}
// Wait for the new function to complete
if(! waitForState(oemFunctions)) { Slog.e(TAG,"Failed to switch USB config to " + functions);
return false;
}
mCurrentFunctionsApplied = true;
}
return true;
}
Copy the code
I’ve broken down the logic here into three steps.
First step, the USB function to be set into a string, there are two cases
- If new work is equal to
FUNCTION_NONE
, then the converted value changes frompersist.sys.usb.config
If NONE, it checks whether ADB is enabled. If it is enabled, the converted value is ADB. If not, the converted value is MTP. As the previous analysis said,persist.sys.usb.config
It mainly contains functions to determine whether ADB is enabled, and then some vendor-specific functions for testing purposes. For example, for the Qualcomm project, this value might beadb,diag
, this diAG is Qualcomm’s own function. - If the new feature is not
FUNCTION_NONE
To convert directly. New energy, for exampleFUNCTION_MTP
, then the converted string ismtp
.
After converting the string, decide whether to add or remove adb attributes from the converted string, depending on whether ADB is enabled.
Step 2: Obtain OEM covered features. As mentioned earlier, the default system does not use the override function, so the override function obtained here is the same as the string converted from the new function.
As I analyzed the code, I kept thinking about how to use this override feature. Based on my analysis of the code, the only rule is that major functionality cannot be covered. For example, if the string of the newly set function is MTP, then one of the elements in the override array should have the value normal: MTP: MTP,diag, where nomral means normal startup, MTP means original function, and MTP,diag means overwritten function. Note that After overwriting the function must save MTP this main function. Of course, this is just my personal analysis of the code and has not been verified. Here I have to poke fun at the designers of this feature. Is it so hard to write an example and precautions?
Step 3: Set up new features. However, before setting up a new feature, first disconnect the existing connection and then set up the new feature. The new functionality is set using the setUsbConfig() method
private void setUsbConfig(String config) {
/ / set the sys. Usb. Config
setSystemProperty(USB_CONFIG_PROPERTY, config);
}
Copy the code
Shock!!! The original setting is the property value of sys.usb.config. Remember that this property value was explained in the previous analysis, which represents the usb function currently set, as evidenced here.
This also indicates that you can set this property with the ADB shell setprop command to control usb switching. In the actual work, it is always successful.
How do you determine success after setting this property? That’s what waitForState() does
private boolean waitForState(String state) {
String value = null;
for (int i = 0; i < 20; i++) {
// Get the sys.usb.stat value
value = getSystemProperty(USB_STATE_PROPERTY, "");
// Compare this to the value of the sys.usb.config property you just set
if (state.equals(value)) return true;
SystemClock.sleep(50);
}
return false;
}
Copy the code
To tell you the truth, I saw this code, did eat a whale! This code is executed 20 times in one second to get the value of the sys.usb.state property and compare it to the value of the set sys.usb.config property. If the value is equal, the function is set successfully.
Remember? In the previous analysis, I also explained the use of the sys.USb. state property, which represents the actual functionality of USB and can be verified here.
So now there is a question, how to achieve the bottom of the USB function switch? Of course it is in response to the sys.usb.config property change, such as the underlying echo code of my project
on property:sys.usb.config=mtp,adb && property:sys.usb.configfs=0
# write 0
write /sys/class/android_usb/android0/enable 0
# write serial number
write /sys/class/android_usb/android0/iSerial ${ro.serialno}
# write vid and pid
write /sys/class/android_usb/android0/idVendor 05C6
write /sys/class/android_usb/android0/idProduct 9039
Set USB function to MTP, ADB
write /sys/class/android_usb/android0/functions mtp,adb
# Rewrite 1 enables the function
write /sys/class/android_usb/android0/enable 1
# start the adb
start adbd
Set sys.usb.state to sys.usb.config
setprop sys.usb.state ${sys.usb.config}
Copy the code
According to the comments, you should have a good idea of the process.
So, you think that’s the end of it? Not yet, if the new Settings function is MTP or PTP, then update the broadcast.
protected void updateUsbStateBroadcastIfNeeded(long functions) {
// send a sticky broadcast containing current USB state
Intent intent = new Intent(UsbManager.ACTION_USB_STATE);
intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING
| Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND
| Intent.FLAG_RECEIVER_FOREGROUND);
// Save the USB status value
intent.putExtra(UsbManager.USB_CONNECTED, mConnected);
intent.putExtra(UsbManager.USB_HOST_CONNECTED, mHostConnected);
intent.putExtra(UsbManager.USB_CONFIGURED, mConfigured);
intent.putExtra(UsbManager.USB_DATA_UNLOCKED,
isUsbTransferAllowed() && isUsbDataTransferActive(mCurrentFunctions));
// Save the value of the new function to be set. For example, if MTP is set, the key is MTP and the value is true
long remainingFunctions = functions;
while(remainingFunctions ! =0) {
intent.putExtra(UsbManager.usbFunctionsToString(
Long.highestOneBit(remainingFunctions)), true);
remainingFunctions -= Long.highestOneBit(remainingFunctions);
}
// If the status does not change, no broadcast is sent
if(! isUsbStateChanged(intent)) {return;
}
// Note that a sticky broadcast is sent
sendStickyBroadcast(intent);
mBroadcastedIntent = intent;
Copy the code
Note that a sticky broadcast is sent here. So who was the receiver of this broadcast? What did you do to receive this broadcast? That’s what the next article will be about.
The end of the
UsbService is an implementation of the USB protocol, such as MTP. PTP is built on the USB protocol, and UsbService is implemented. While this article is an MTP analysis, it’s not a small article to miss if you’re developing or customizing usb functionality.