Loading core/java/android/hardware/usb/flags/system_sw_usb_flags.aconfig +7 −0 Original line number Diff line number Diff line Loading @@ -43,3 +43,10 @@ flag { 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 services/usb/java/com/UsbDataSignalDisableRequesters.java 0 → 100644 +36 −0 Original line number Diff line number Diff line /* * 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 services/usb/java/com/android/server/usb/UsbManagerInternal.java 0 → 100644 +48 −0 Original line number Diff line number Diff line /* * 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 services/usb/java/com/android/server/usb/UsbService.java +72 −21 Original line number Diff line number Diff line Loading @@ -46,6 +46,7 @@ import android.hardware.usb.UsbDevice; 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; Loading @@ -69,6 +70,7 @@ import com.android.internal.util.Preconditions; 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; Loading Loading @@ -165,8 +167,10 @@ public class UsbService extends IUsbManager.Stub { 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 Loading Loading @@ -221,6 +225,9 @@ public class UsbService extends IUsbManager.Stub { 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 Loading @@ -236,6 +243,10 @@ public class UsbService extends IUsbManager.Stub { mUserManager = userManager; mSettingsManager = usbSettingsManager; mPermissionManager = new UsbPermissionManager(context, this); if(android.hardware.usb.flags.Flags.enableUsbDataSignalStakingInternal()) { LocalServices.addService(UsbManagerInternal.class, new UsbManagerInternalImpl()); } } /** Loading Loading @@ -903,15 +914,21 @@ public class UsbService extends IUsbManager.Stub { @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:" Loading @@ -919,7 +936,7 @@ public class UsbService extends IUsbManager.Stub { 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) { Loading Loading @@ -949,25 +966,42 @@ public class UsbService extends IUsbManager.Stub { } /** * 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 { if(isInternalRequest) { disableRequests.mInternalReasons.add(requester); } else { uidsOfDisableRequesters.add(uid); disableRequests.mExternalUids.add(requester); } } } return true; Loading @@ -976,7 +1010,8 @@ public class UsbService extends IUsbManager.Stub { @Override public void enableUsbDataWhileDocked(String portId, int operationId, IUsbOperationInternal callback) { enableUsbDataWhileDockedInternal(portId, operationId, callback, Binder.getCallingUid()); enableUsbDataWhileDockedInternal(portId, operationId, callback, Binder.getCallingUid(), false); } /** Loading @@ -984,7 +1019,7 @@ public class UsbService extends IUsbManager.Stub { */ @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, Loading @@ -993,7 +1028,7 @@ public class UsbService extends IUsbManager.Stub { 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) { Loading Loading @@ -1455,10 +1490,11 @@ public class UsbService extends IUsbManager.Stub { 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()); } Loading Loading @@ -1496,4 +1532,19 @@ public class UsbService extends IUsbManager.Stub { } } } 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(); } } } tests/UsbTests/src/com/android/server/usb/UsbServiceTest.java +126 −20 Original line number Diff line number Diff line Loading @@ -18,6 +18,10 @@ package com.android.server.usb; 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; Loading @@ -31,12 +35,15 @@ import static org.mockito.Mockito.when; 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; Loading Loading @@ -71,26 +78,38 @@ public class UsbServiceTest { 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); Loading @@ -100,9 +119,10 @@ public class UsbServiceTest { 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); Loading @@ -116,15 +136,16 @@ public class UsbServiceTest { */ @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); } /** Loading @@ -132,47 +153,132 @@ public class UsbServiceTest { */ @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); } } Loading
core/java/android/hardware/usb/flags/system_sw_usb_flags.aconfig +7 −0 Original line number Diff line number Diff line Loading @@ -43,3 +43,10 @@ flag { 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
services/usb/java/com/UsbDataSignalDisableRequesters.java 0 → 100644 +36 −0 Original line number Diff line number Diff line /* * 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
services/usb/java/com/android/server/usb/UsbManagerInternal.java 0 → 100644 +48 −0 Original line number Diff line number Diff line /* * 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
services/usb/java/com/android/server/usb/UsbService.java +72 −21 Original line number Diff line number Diff line Loading @@ -46,6 +46,7 @@ import android.hardware.usb.UsbDevice; 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; Loading @@ -69,6 +70,7 @@ import com.android.internal.util.Preconditions; 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; Loading Loading @@ -165,8 +167,10 @@ public class UsbService extends IUsbManager.Stub { 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 Loading Loading @@ -221,6 +225,9 @@ public class UsbService extends IUsbManager.Stub { 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 Loading @@ -236,6 +243,10 @@ public class UsbService extends IUsbManager.Stub { mUserManager = userManager; mSettingsManager = usbSettingsManager; mPermissionManager = new UsbPermissionManager(context, this); if(android.hardware.usb.flags.Flags.enableUsbDataSignalStakingInternal()) { LocalServices.addService(UsbManagerInternal.class, new UsbManagerInternalImpl()); } } /** Loading Loading @@ -903,15 +914,21 @@ public class UsbService extends IUsbManager.Stub { @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:" Loading @@ -919,7 +936,7 @@ public class UsbService extends IUsbManager.Stub { 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) { Loading Loading @@ -949,25 +966,42 @@ public class UsbService extends IUsbManager.Stub { } /** * 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 { if(isInternalRequest) { disableRequests.mInternalReasons.add(requester); } else { uidsOfDisableRequesters.add(uid); disableRequests.mExternalUids.add(requester); } } } return true; Loading @@ -976,7 +1010,8 @@ public class UsbService extends IUsbManager.Stub { @Override public void enableUsbDataWhileDocked(String portId, int operationId, IUsbOperationInternal callback) { enableUsbDataWhileDockedInternal(portId, operationId, callback, Binder.getCallingUid()); enableUsbDataWhileDockedInternal(portId, operationId, callback, Binder.getCallingUid(), false); } /** Loading @@ -984,7 +1019,7 @@ public class UsbService extends IUsbManager.Stub { */ @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, Loading @@ -993,7 +1028,7 @@ public class UsbService extends IUsbManager.Stub { 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) { Loading Loading @@ -1455,10 +1490,11 @@ public class UsbService extends IUsbManager.Stub { 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()); } Loading Loading @@ -1496,4 +1532,19 @@ public class UsbService extends IUsbManager.Stub { } } } 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(); } } }
tests/UsbTests/src/com/android/server/usb/UsbServiceTest.java +126 −20 Original line number Diff line number Diff line Loading @@ -18,6 +18,10 @@ package com.android.server.usb; 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; Loading @@ -31,12 +35,15 @@ import static org.mockito.Mockito.when; 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; Loading Loading @@ -71,26 +78,38 @@ public class UsbServiceTest { 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); Loading @@ -100,9 +119,10 @@ public class UsbServiceTest { 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); Loading @@ -116,15 +136,16 @@ public class UsbServiceTest { */ @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); } /** Loading @@ -132,47 +153,132 @@ public class UsbServiceTest { */ @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); } }