Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 8973771c authored by George Chan's avatar George Chan
Browse files

Add support for internal USB data signal disable requests.

Internal requests are identified by a reason code enumerated in UsbManagerInternal.

Change-Id: I2a19e483e72694b29828e662872c6ebd6ba2ce23
Test: atest
Flag: android.hardware.usb.flags.enable_usb_data_signal_staking_internal
Bug: 369382558
parent 98e0bab7
Loading
Loading
Loading
Loading
+7 −0
Original line number Diff line number Diff line
@@ -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
+34 −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.
 */
public 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
+44 −21
Original line number Diff line number Diff line
@@ -165,8 +165,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
@@ -903,15 +905,19 @@ 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:"
@@ -919,7 +925,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) {
@@ -949,25 +955,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 {
                uidsOfDisableRequesters.add(uid);
                    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 {
                    disableRequests.mExternalUids.add(requester);
                }
            }
        }
        return true;
@@ -976,7 +999,7 @@ 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);
    }

    /**
@@ -984,7 +1007,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,
@@ -993,7 +1016,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) {
@@ -1455,10 +1478,10 @@ 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());
                        }
+68 −13
Original line number Diff line number Diff line
@@ -71,6 +71,10 @@ 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;

    @Rule
@@ -79,6 +83,7 @@ public class UsbServiceTest {
    @Before
    public void setUp() {
        mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_USB_DATA_SIGNAL_STAKING);
        mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_USB_DATA_SIGNAL_STAKING_INTERNAL);
        MockitoAnnotations.initMocks(this);

        when(mUsbPortManager.enableUsbData(eq(TEST_PORT_ID), anyBoolean(), eq(TEST_TRANSACTION_ID),
@@ -88,9 +93,10 @@ public class UsbServiceTest {
                mUserManager, mUsbSettingsManager);
    }

    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 +106,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);
@@ -116,7 +123,7 @@ public class UsbServiceTest {
     */
    @Test
    public void disableUsb_successfullyDisable() {
        assertToggleUsbSuccessfully(TEST_FIRST_CALLER_ID, false);
        assertToggleUsbSuccessfully(TEST_FIRST_CALLER_ID, false, false);
    }

    /**
@@ -124,7 +131,7 @@ public class UsbServiceTest {
     */
    @Test
    public void enableUsbWhenNoOtherStakers_successfullyEnable() {
        assertToggleUsbSuccessfully(TEST_FIRST_CALLER_ID, true);
        assertToggleUsbSuccessfully(TEST_FIRST_CALLER_ID, true, false);
    }

    /**
@@ -132,9 +139,9 @@ 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);
    }

    /**
@@ -142,9 +149,9 @@ public class UsbServiceTest {
     */
    @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);
    }

    /**
@@ -153,10 +160,10 @@ public class UsbServiceTest {
    @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);
@@ -169,10 +176,58 @@ public class UsbServiceTest {
    @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);
    }
}