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);
+ }
}