Bind to MTP service on USB state change

Currently MTP service is started by MtpReceiver based on USB state
change broadcasts. But in some cases activity manager freezes the
process resulting in MTP not working. To resolve this we will be binding
to MtpService from UsbService on USB function changes.

Test: Manually verified that MTP and PTP works. Also verified that the
process android.process.media is not being frozen
Bug: 332256525
Flag: com.android.server.usb.flags.enable_bind_to_mtp_service

Change-Id: I886de2d9527651eaa46e9893e62b08a4b377ba0f
diff --git a/services/usb/java/com/android/server/usb/UsbDeviceManager.java b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
index 9acda5f..e8bac66 100644
--- a/services/usb/java/com/android/server/usb/UsbDeviceManager.java
+++ b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
@@ -37,6 +37,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.ServiceConnection;
 import android.content.SharedPreferences;
 import android.content.pm.PackageManager;
 import android.content.res.Resources;
@@ -62,6 +63,7 @@
 import android.os.FileUtils;
 import android.os.Handler;
 import android.os.HwBinder;
+import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
 import android.os.ParcelFileDescriptor;
@@ -89,6 +91,7 @@
 import com.android.internal.util.dump.DualDumpOutputStream;
 import com.android.server.FgThread;
 import com.android.server.LocalServices;
+import com.android.server.usb.flags.Flags;
 import com.android.server.usb.hal.gadget.UsbGadgetHal;
 import com.android.server.usb.hal.gadget.UsbGadgetHalInstance;
 import com.android.server.utils.EventLogger;
@@ -586,6 +589,22 @@
          */
         protected static final String USB_PERSISTENT_CONFIG_PROPERTY = "persist.sys.usb.config";
 
+        protected static final String MTP_PACKAGE_NAME = "com.android.mtp";
+        protected static final String MTP_SERVICE_CLASS_NAME = "com.android.mtp.MtpService";
+
+        private boolean mIsMtpServiceBound = false;
+
+        /**
+         * {@link ServiceConnection} for {@link MtpService}.
+         */
+        private ServiceConnection mMtpServiceConnection = new ServiceConnection() {
+            @Override
+            public void onServiceConnected(ComponentName className, IBinder service) {}
+
+            @Override
+            public void onServiceDisconnected(ComponentName arg0) {}
+        };
+
         UsbHandler(Looper looper, Context context, UsbDeviceManager deviceManager,
                 UsbAlsaManager alsaManager, UsbPermissionManager permissionManager) {
             super(looper);
@@ -915,6 +934,7 @@
 
         private void updateUsbFunctions() {
             updateMidiFunction();
+            updateMtpFunction();
         }
 
         private void updateMidiFunction() {
@@ -941,6 +961,67 @@
                     mMidiEnabled && mConfigured, mMidiCard, mMidiDevice);
         }
 
+        /**
+         * Bind to MtpService when MTP or PTP is enabled. This is done to prevent activity manager
+         * from freezing the corresponding process.
+         */
+        private void updateMtpFunction() {
+            if (!Flags.enableBindToMtpService()) {
+                return;
+            }
+
+            boolean mtpEnabled = ((mCurrentFunctions & UsbManager.FUNCTION_MTP) != 0);
+            boolean ptpEnabled = ((mCurrentFunctions & UsbManager.FUNCTION_PTP) != 0);
+
+            if (DEBUG) {
+                Slog.d(TAG, "updateMtpFunction "
+                        + ", mtpEnabled: " + mtpEnabled
+                        + ", ptpEnabled: " + ptpEnabled
+                        + ", mIsMtpServiceBound: " + mIsMtpServiceBound
+                );
+            }
+
+            if (mConfigured && (mtpEnabled || ptpEnabled)) {
+                bindToMtpService();
+            } else if (mIsMtpServiceBound) {
+                unbindMtpService();
+            }
+        }
+
+        private void bindToMtpService() {
+            Intent intent = new Intent();
+            intent.setComponent(new ComponentName(MTP_PACKAGE_NAME, MTP_SERVICE_CLASS_NAME));
+
+            if (DEBUG) Slog.d(TAG, "Binding to MtpService");
+
+            try {
+                mIsMtpServiceBound = mContext.bindServiceAsUser(
+                    intent,
+                    mMtpServiceConnection,
+                    Context.BIND_AUTO_CREATE,
+                    UserHandle.CURRENT
+                );
+            } catch (SecurityException exception) {
+                Slog.e(TAG, "Unable to bind to MtpService due to SecurityException", exception);
+            }
+
+            // Unbinding from the service if binding was not successful to release the connection.
+            // https://developer.android.com/reference/android/content/Context#bindService(android.content.Intent,%20android.content.ServiceConnection,%20int)
+            if (!mIsMtpServiceBound) {
+                unbindMtpService();
+                Slog.e(TAG, "Binding to MtpService failed");
+            }
+
+            if (DEBUG && mIsMtpServiceBound) Slog.d(TAG, "Successfully bound to MtpService");
+        }
+
+        private void unbindMtpService() {
+            if (DEBUG) Slog.d(TAG, "Unbinding from MtpService");
+
+            mContext.unbindService(mMtpServiceConnection);
+            mIsMtpServiceBound = false;
+        }
+
         private void setScreenUnlockedFunctions(int operationId) {
             setEnabledFunctions(mScreenUnlockedFunctions, false, operationId);
         }
diff --git a/services/usb/java/com/android/server/usb/flags/usb_flags.aconfig b/services/usb/java/com/android/server/usb/flags/usb_flags.aconfig
index a7c5ddb..cd96d76 100644
--- a/services/usb/java/com/android/server/usb/flags/usb_flags.aconfig
+++ b/services/usb/java/com/android/server/usb/flags/usb_flags.aconfig
@@ -7,3 +7,10 @@
     description: "This flag controls the restriction of usb overlay activities"
     bug: "307231174"
 }
+
+flag {
+    name: "enable_bind_to_mtp_service"
+    namespace: "usb"
+    description: "This flag enables binding to MtpService when in mtp/ptp modes"
+    bug: "332256525"
+}