Merge changes from topic "usb_internal" into main

* changes:
  Add UsbManagerInternal interface
  Add support for internal USB data signal disable requests.
diff --git a/core/java/android/hardware/usb/flags/system_sw_usb_flags.aconfig b/core/java/android/hardware/usb/flags/system_sw_usb_flags.aconfig
index 51024ba..6a39365 100644
--- a/core/java/android/hardware/usb/flags/system_sw_usb_flags.aconfig
+++ b/core/java/android/hardware/usb/flags/system_sw_usb_flags.aconfig
@@ -43,3 +43,10 @@
     description: "Enable usb state update based on udc sysfs"
     bug: "339241080"
 }
+
+flag {
+    name: "enable_usb_data_signal_staking_internal"
+    namespace: "preload_safety"
+    description: "Enables signal API with staking for internal local service callers"
+    bug: "369382558"
+}
\ No newline at end of file
diff --git a/services/usb/java/com/UsbDataSignalDisableRequesters.java b/services/usb/java/com/UsbDataSignalDisableRequesters.java
new file mode 100644
index 0000000..d4d6492
--- /dev/null
+++ b/services/usb/java/com/UsbDataSignalDisableRequesters.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.usb;
+
+import android.util.ArraySet;
+
+/**
+ * A helper class to store and manage the request for disabling USB port data signaling.
+ *
+ * External requesters are identified by UIDs.
+ * Internal requesters are identified by a reason code enumerated in UsbManagerInternal.
+ *
+ * @hide
+ */
+public final class UsbDataSignalDisableRequesters {
+    final ArraySet<Integer> mExternalUids = new ArraySet<>();
+    final ArraySet<Integer> mInternalReasons  = new ArraySet<>();
+
+    public boolean isEmpty() {
+        return mExternalUids.isEmpty() && mInternalReasons.isEmpty();
+    }
+}
\ No newline at end of file
diff --git a/services/usb/java/com/android/server/usb/UsbManagerInternal.java b/services/usb/java/com/android/server/usb/UsbManagerInternal.java
new file mode 100644
index 0000000..c97df6b
--- /dev/null
+++ b/services/usb/java/com/android/server/usb/UsbManagerInternal.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.usb;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.hardware.usb.IUsbOperationInternal;
+import android.hardware.usb.UsbPort;
+import android.util.ArraySet;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * UsbManagerInternal provides internal APIs for the UsbService to
+ * reduce IPC overhead costs and support internal USB data signal stakers.
+ *
+ * @hide Only for use within the system server.
+ */
+public abstract class UsbManagerInternal {
+
+  public static final int OS_USB_DISABLE_REASON_AAPM = 0;
+
+  @Retention(RetentionPolicy.SOURCE)
+  @IntDef(value = {OS_USB_DISABLE_REASON_AAPM})
+  public @interface OsUsbDisableReason {
+  }
+
+  public abstract boolean enableUsbData(String portId, boolean enable,
+      int operationId, IUsbOperationInternal callback, @OsUsbDisableReason int disableReason);
+
+  public abstract UsbPort[] getPorts();
+
+}
\ No newline at end of file
diff --git a/services/usb/java/com/android/server/usb/UsbService.java b/services/usb/java/com/android/server/usb/UsbService.java
index 9470c0a..ba9dff6 100644
--- a/services/usb/java/com/android/server/usb/UsbService.java
+++ b/services/usb/java/com/android/server/usb/UsbService.java
@@ -46,6 +46,7 @@
 import android.hardware.usb.UsbManager;
 import android.hardware.usb.UsbPort;
 import android.hardware.usb.UsbPortStatus;
+
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.Looper;
@@ -69,6 +70,7 @@
 import com.android.internal.util.dump.DualDumpOutputStream;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.server.FgThread;
+import com.android.server.LocalServices;
 import com.android.server.SystemServerInitThreadPool;
 import com.android.server.SystemService;
 
@@ -165,8 +167,10 @@
     private final Object mLock = new Object();
 
     // Key: USB port id
-    // Value: A set of UIDs of requesters who request disabling usb data
-    private final ArrayMap<String, ArraySet<Integer>> mUsbDisableRequesters = new ArrayMap<>();
+    // Value: UsbDataSignalDisableRequesters: UIDs of requesters who request
+    // disabling usb data and disable request reasons by local service callers
+    private final ArrayMap<String, UsbDataSignalDisableRequesters>
+        mUsbDisableRequesters = new ArrayMap<>();
 
     /**
      * @return the {@link UsbUserSettingsManager} for the given userId
@@ -221,6 +225,9 @@
         filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
         filter.addAction(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED);
         mContext.registerReceiverAsUser(receiver, UserHandle.ALL, filter, null, null);
+        if(android.hardware.usb.flags.Flags.enableUsbDataSignalStakingInternal()) {
+            LocalServices.addService(UsbManagerInternal.class, new UsbManagerInternalImpl());
+        }
     }
 
     // Ideally we should use the injector pattern so we wouldn't need this constructor  for test
@@ -236,6 +243,10 @@
         mUserManager = userManager;
         mSettingsManager = usbSettingsManager;
         mPermissionManager = new UsbPermissionManager(context, this);
+
+        if(android.hardware.usb.flags.Flags.enableUsbDataSignalStakingInternal()) {
+            LocalServices.addService(UsbManagerInternal.class, new UsbManagerInternalImpl());
+        }
     }
 
     /**
@@ -903,15 +914,21 @@
     @Override
     public boolean enableUsbData(String portId, boolean enable, int operationId,
                                  IUsbOperationInternal callback) {
-        return enableUsbDataInternal(portId, enable, operationId, callback, Binder.getCallingUid());
+        return enableUsbDataInternal(portId, enable, operationId, callback,
+            Binder.getCallingUid(), false);
     }
 
     /**
-     *  Internal function abstracted for testing with callerUid
+     *  Manages the enablement of USB data. Requester field could mean two things:
+     *  1. UID of the app that requested USB data to be disabled if caller is external.
+     *  2. Enumberated disable request reason if the caller is internal.
+     *
+     *  For internal requests, isInternalRequest should be set to true. Since
+     * internal requests all share the same UID, the request managed separately.
      */
     @VisibleForTesting
     boolean enableUsbDataInternal(String portId, boolean enable, int operationId,
-            IUsbOperationInternal callback, int callerUid) {
+            IUsbOperationInternal callback, int requester, boolean isInternalRequest) {
         Objects.requireNonNull(portId, "enableUsbData: portId must not be null. opId:"
                 + operationId);
         Objects.requireNonNull(callback, "enableUsbData: callback must not be null. opId:"
@@ -919,7 +936,7 @@
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
 
         if (android.hardware.usb.flags.Flags.enableUsbDataSignalStaking()) {
-            if (!shouldUpdateUsbSignaling(portId, enable, callerUid)) {
+            if (!shouldUpdateUsbSignaling(portId, enable, requester, isInternalRequest)) {
                 try {
                     callback.onOperationComplete(USB_OPERATION_ERROR_INTERNAL);
                 } catch (RemoteException e) {
@@ -949,25 +966,42 @@
     }
 
     /**
+     * Function to determine if USB data signaling state should be updated.
+     * Depending on if request is internal, input requester should be UID or enumerated disable
+     * reason.
+     *
      * If enable = true, exclude UID from update list.
      * If enable = false, include UID in update list.
      * Return false if enable = true and the list is empty (no updates).
      * Return true otherwise (let downstream decide on updates).
      */
-    private boolean shouldUpdateUsbSignaling(String portId, boolean enable, int uid) {
+    private boolean shouldUpdateUsbSignaling(String portId, boolean enable,
+        int requester, boolean isInternalRequest) {
+        if(isInternalRequest &&
+               !android.hardware.usb.flags.Flags.enableUsbDataSignalStakingInternal())
+          return false;
         synchronized (mUsbDisableRequesters) {
             if (!mUsbDisableRequesters.containsKey(portId)) {
-                mUsbDisableRequesters.put(portId, new ArraySet<>());
+                mUsbDisableRequesters.put(portId, new UsbDataSignalDisableRequesters());
             }
-
-            ArraySet<Integer> uidsOfDisableRequesters = mUsbDisableRequesters.get(portId);
+            UsbDataSignalDisableRequesters disableRequests =
+                mUsbDisableRequesters.get(portId);
 
             if (enable) {
-                uidsOfDisableRequesters.remove(uid);
-                // re-enable USB port (return true) if there are no other disable requesters
-                return uidsOfDisableRequesters.isEmpty();
+                if(isInternalRequest) {
+                    disableRequests.mInternalReasons.remove(requester);
+                } else {
+                    disableRequests.mExternalUids.remove(requester);
+                }
+                // re-enable USB port (return true) if there are no other
+                // disable requesters
+                return disableRequests.isEmpty();
             } else {
-                uidsOfDisableRequesters.add(uid);
+                if(isInternalRequest) {
+                    disableRequests.mInternalReasons.add(requester);
+                } else {
+                    disableRequests.mExternalUids.add(requester);
+                }
             }
         }
         return true;
@@ -976,7 +1010,8 @@
     @Override
     public void enableUsbDataWhileDocked(String portId, int operationId,
                                          IUsbOperationInternal callback) {
-        enableUsbDataWhileDockedInternal(portId, operationId, callback, Binder.getCallingUid());
+        enableUsbDataWhileDockedInternal(portId, operationId, callback,
+            Binder.getCallingUid(), false);
     }
 
     /**
@@ -984,7 +1019,7 @@
      */
     @VisibleForTesting
      void enableUsbDataWhileDockedInternal(String portId, int operationId,
-            IUsbOperationInternal callback, int callerUid) {
+            IUsbOperationInternal callback, int callerUid, boolean isInternalRequest) {
         Objects.requireNonNull(portId, "enableUsbDataWhileDocked: portId must not be null. opId:"
                 + operationId);
         Objects.requireNonNull(callback,
@@ -993,7 +1028,7 @@
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
 
         if (android.hardware.usb.flags.Flags.enableUsbDataSignalStaking()) {
-            if (!shouldUpdateUsbSignaling(portId, true, callerUid)) {
+            if (!shouldUpdateUsbSignaling(portId, true, callerUid, isInternalRequest)) {
                 try {
                     callback.onOperationComplete(USB_OPERATION_ERROR_INTERNAL);
                 } catch (RemoteException e) {
@@ -1455,10 +1490,11 @@
         public void onUidRemoved(int uid) {
             synchronized (mUsbDisableRequesters) {
                 for (String portId : mUsbDisableRequesters.keySet()) {
-                    ArraySet<Integer> disabledUid = mUsbDisableRequesters.get(portId);
-                    if (disabledUid != null) {
-                        disabledUid.remove(uid);
-                        if (disabledUid.isEmpty()) {
+                    UsbDataSignalDisableRequesters disableRequesters =
+                        mUsbDisableRequesters.get(portId);
+                    if (disableRequesters != null) {
+                        disableRequesters.mExternalUids.remove(uid);
+                        if (disableRequesters.isEmpty()) {
                             enableUsbData(portId, true, PACKAGE_MONITOR_OPERATION_ID,
                                     new IUsbOperationInternal.Default());
                         }
@@ -1496,4 +1532,19 @@
             }
         }
     }
+
+    private class UsbManagerInternalImpl extends UsbManagerInternal {
+        @Override
+        public boolean enableUsbData(String portId, boolean enable,
+                int operationId, IUsbOperationInternal callback,
+            @OsUsbDisableReason int disableReason) {
+            return enableUsbDataInternal(portId, enable, operationId, callback,
+                disableReason, true);
+        }
+
+        @Override
+        public UsbPort[] getPorts() {
+            return mPortManager.getPorts();
+        }
+    }
 }
diff --git a/tests/UsbTests/src/com/android/server/usb/UsbServiceTest.java b/tests/UsbTests/src/com/android/server/usb/UsbServiceTest.java
index 56845ae..51d57f0 100644
--- a/tests/UsbTests/src/com/android/server/usb/UsbServiceTest.java
+++ b/tests/UsbTests/src/com/android/server/usb/UsbServiceTest.java
@@ -18,6 +18,10 @@
 
 import static android.hardware.usb.UsbOperationInternal.USB_OPERATION_ERROR_INTERNAL;
 
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
@@ -31,12 +35,15 @@
 import android.content.Context;
 import android.hardware.usb.IUsbOperationInternal;
 import android.hardware.usb.flags.Flags;
+import android.hardware.usb.UsbPort;
 import android.os.RemoteException;
 import android.os.UserManager;
 import android.platform.test.flag.junit.SetFlagsRule;
 
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.server.LocalServices;
+
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -71,26 +78,38 @@
 
     private static final int TEST_SECOND_CALLER_ID = 2000;
 
+    private static final int TEST_INTERNAL_REQUESTER_REASON_1 = 100;
+
+    private static final int TEST_INTERNAL_REQUESTER_REASON_2 = 200;
+
     private UsbService mUsbService;
 
+    private UsbManagerInternal mUsbManagerInternal;
+
     @Rule
     public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
 
     @Before
     public void setUp() {
         mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_USB_DATA_SIGNAL_STAKING);
+        mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_USB_DATA_SIGNAL_STAKING_INTERNAL);
+        LocalServices.removeAllServicesForTest();
         MockitoAnnotations.initMocks(this);
 
-        when(mUsbPortManager.enableUsbData(eq(TEST_PORT_ID), anyBoolean(), eq(TEST_TRANSACTION_ID),
-                eq(mCallback), any())).thenReturn(true);
+        when(mUsbPortManager.enableUsbData(eq(TEST_PORT_ID), anyBoolean(),
+                 eq(TEST_TRANSACTION_ID), eq(mCallback), any())).thenReturn(true);
 
         mUsbService = new UsbService(mContext, mUsbPortManager, mUsbAlsaManager,
                 mUserManager, mUsbSettingsManager);
+        mUsbManagerInternal = LocalServices.getService(UsbManagerInternal.class);
+        assertWithMessage("LocalServices.getService(UsbManagerInternal.class)")
+            .that(mUsbManagerInternal).isNotNull();
     }
 
-    private void assertToggleUsbSuccessfully(int uid, boolean enable) {
+    private void assertToggleUsbSuccessfully(int requester, boolean enable,
+        boolean isInternalRequest) {
         assertTrue(mUsbService.enableUsbDataInternal(TEST_PORT_ID, enable,
-                TEST_TRANSACTION_ID, mCallback, uid));
+                TEST_TRANSACTION_ID, mCallback, requester, isInternalRequest));
 
         verify(mUsbPortManager).enableUsbData(TEST_PORT_ID,
                 enable, TEST_TRANSACTION_ID, mCallback, null);
@@ -100,9 +119,10 @@
         clearInvocations(mCallback);
     }
 
-    private void assertToggleUsbFailed(int uid, boolean enable) throws Exception {
+    private void assertToggleUsbFailed(int requester, boolean enable,
+        boolean isInternalRequest) throws Exception {
         assertFalse(mUsbService.enableUsbDataInternal(TEST_PORT_ID, enable,
-                TEST_TRANSACTION_ID, mCallback, uid));
+                TEST_TRANSACTION_ID, mCallback, requester, isInternalRequest));
 
         verifyZeroInteractions(mUsbPortManager);
         verify(mCallback).onOperationComplete(USB_OPERATION_ERROR_INTERNAL);
@@ -116,15 +136,16 @@
      */
     @Test
     public void disableUsb_successfullyDisable() {
-        assertToggleUsbSuccessfully(TEST_FIRST_CALLER_ID, false);
+        assertToggleUsbSuccessfully(TEST_FIRST_CALLER_ID, false, false);
     }
 
     /**
-     * Verify enableUsbData successfully enables USB port without error given no other stakers
+     * Verify enableUsbData successfully enables USB port without error given
+     * no other stakers
      */
     @Test
     public void enableUsbWhenNoOtherStakers_successfullyEnable() {
-        assertToggleUsbSuccessfully(TEST_FIRST_CALLER_ID, true);
+        assertToggleUsbSuccessfully(TEST_FIRST_CALLER_ID, true, false);
     }
 
     /**
@@ -132,47 +153,132 @@
      */
     @Test
     public void enableUsbPortWithOtherStakers_failsToEnable() throws Exception {
-        assertToggleUsbSuccessfully(TEST_FIRST_CALLER_ID, false);
+        assertToggleUsbSuccessfully(TEST_FIRST_CALLER_ID, false, false);
 
-        assertToggleUsbFailed(TEST_SECOND_CALLER_ID, true);
+        assertToggleUsbFailed(TEST_SECOND_CALLER_ID, true, false);
     }
 
     /**
-     * Verify enableUsbData successfully enables USB port when the last staker is removed
+     * Verify enableUsbData successfully enables USB port when the last staker
+     * is removed
      */
     @Test
     public void enableUsbByTheOnlyStaker_successfullyEnable() {
-        assertToggleUsbSuccessfully(TEST_FIRST_CALLER_ID, false);
+        assertToggleUsbSuccessfully(TEST_FIRST_CALLER_ID, false, false);
 
-        assertToggleUsbSuccessfully(TEST_FIRST_CALLER_ID, true);
+        assertToggleUsbSuccessfully(TEST_FIRST_CALLER_ID, true, false);
     }
 
     /**
-     * Verify enableUsbDataWhileDockedInternal does not enable USB port if other stakers are present
+     * Verify enableUsbDataWhileDockedInternal does not enable USB port if other
+     * stakers are present
      */
     @Test
     public void enableUsbWhileDockedWhenThereAreOtherStakers_failsToEnable()
             throws RemoteException {
-        assertToggleUsbSuccessfully(TEST_FIRST_CALLER_ID, false);
+        assertToggleUsbSuccessfully(TEST_FIRST_CALLER_ID, false, false);
 
         mUsbService.enableUsbDataWhileDockedInternal(TEST_PORT_ID, TEST_TRANSACTION_ID,
-                mCallback, TEST_SECOND_CALLER_ID);
+                mCallback, TEST_SECOND_CALLER_ID, false);
 
         verifyZeroInteractions(mUsbPortManager);
         verify(mCallback).onOperationComplete(USB_OPERATION_ERROR_INTERNAL);
     }
 
     /**
-     * Verify enableUsbDataWhileDockedInternal does enable USB port if other stakers are
-     * not present
+     * Verify enableUsbDataWhileDockedInternal does enable USB port if other
+     * stakers are not present
      */
     @Test
     public void enableUsbWhileDockedWhenThereAreNoStakers_SuccessfullyEnable() {
         mUsbService.enableUsbDataWhileDockedInternal(TEST_PORT_ID, TEST_TRANSACTION_ID,
-                mCallback, TEST_SECOND_CALLER_ID);
+                mCallback, TEST_SECOND_CALLER_ID, false);
 
         verify(mUsbPortManager).enableUsbDataWhileDocked(TEST_PORT_ID, TEST_TRANSACTION_ID,
                         mCallback, null);
         verifyZeroInteractions(mCallback);
     }
+
+    /**
+     * Verify enableUsbData successfully enables USB port without error given no
+     * other stakers for internal requests
+     */
+    @Test
+    public void enableUsbWhenNoOtherStakers_forInternalRequest_successfullyEnable() {
+        assertToggleUsbSuccessfully(TEST_INTERNAL_REQUESTER_REASON_1, true, true);
+    }
+
+    /**
+     * Verify enableUsbData does not enable USB port if other internal stakers
+     * are present for internal requests
+     */
+    @Test
+    public void enableUsbPortWithOtherInternalStakers_forInternalRequest_failsToEnable()
+        throws Exception {
+        assertToggleUsbSuccessfully(TEST_INTERNAL_REQUESTER_REASON_1, false, true);
+
+        assertToggleUsbFailed(TEST_INTERNAL_REQUESTER_REASON_2, true, true);
+    }
+
+    /**
+     * Verify enableUsbData does not enable USB port if other external stakers
+     * are present for internal requests
+     */
+    @Test
+    public void enableUsbPortWithOtherExternalStakers_forInternalRequest_failsToEnable()
+        throws Exception {
+        assertToggleUsbSuccessfully(TEST_FIRST_CALLER_ID, false, false);
+
+        assertToggleUsbFailed(TEST_INTERNAL_REQUESTER_REASON_2, true, true);
+    }
+
+    /**
+     * Verify enableUsbData does not enable USB port if other internal stakers
+     * are present for external requests
+     */
+    @Test
+    public void enableUsbPortWithOtherInternalStakers_forExternalRequest_failsToEnable()
+        throws Exception {
+        assertToggleUsbSuccessfully(TEST_INTERNAL_REQUESTER_REASON_1, false, true);
+
+        assertToggleUsbFailed(TEST_FIRST_CALLER_ID, true, false);
+    }
+
+    /**
+     * Verify enableUsbData successfully enables USB port when the last staker
+     * is removed for internal requests
+     */
+    @Test
+    public void enableUsbByTheOnlyStaker_forInternalRequest_successfullyEnable() {
+        assertToggleUsbSuccessfully(TEST_INTERNAL_REQUESTER_REASON_1, false, false);
+
+        assertToggleUsbSuccessfully(TEST_INTERNAL_REQUESTER_REASON_1, true, false);
+    }
+
+    /**
+     * Verify USB Manager internal calls mPortManager to get UsbPorts
+     */
+    @Test
+    public void usbManagerInternal_getPorts_callsPortManager() {
+        when(mUsbPortManager.getPorts()).thenReturn(new UsbPort[] {});
+
+        UsbPort[] ports = mUsbManagerInternal.getPorts();
+
+        verify(mUsbPortManager).getPorts();
+        assertEquals(ports.length, 0);
+    }
+
+    @Test
+    public void usbManagerInternal_enableUsbData_successfullyEnable() {
+        boolean desiredEnableState = true;
+
+        assertTrue(mUsbManagerInternal.enableUsbData(TEST_PORT_ID, desiredEnableState,
+        TEST_TRANSACTION_ID, mCallback, TEST_INTERNAL_REQUESTER_REASON_1));
+
+        verify(mUsbPortManager).enableUsbData(TEST_PORT_ID,
+                desiredEnableState, TEST_TRANSACTION_ID, mCallback, null);
+        verifyZeroInteractions(mCallback);
+        clearInvocations(mUsbPortManager);
+        clearInvocations(mCallback);
+    }
 }