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

Commit a0746158 authored by George Chan's avatar George Chan Committed by Android (Google) Code Review
Browse files

Merge changes from topic "usb_internal" into main

* changes:
  Add UsbManagerInternal interface
  Add support for internal USB data signal disable requests.
parents fea3fb19 60a54ee2
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
+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
+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
+72 −21
Original line number Diff line number Diff line
@@ -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;
@@ -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;

@@ -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
@@ -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
@@ -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());
        }
    }

    /**
@@ -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:"
@@ -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) {
@@ -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;
@@ -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);
    }

    /**
@@ -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,
@@ -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) {
@@ -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());
                        }
@@ -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();
        }
    }
}
+126 −20
Original line number Diff line number Diff line
@@ -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;
@@ -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;
@@ -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);
@@ -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);
@@ -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);
    }

    /**
@@ -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);
    }
}