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

Commit 88d36827 authored by Felipe Leme's avatar Felipe Leme
Browse files

Introducing NamedLock.

This class should be used as a synchronization lock, so stack traces
show exactly what the lock's for (other than just it's internal address).

For example, instead of:

private final Object mRestrictionsLock = new Object();
private final Object mAppRestrictionsLock = new Object();

UserManagerService could use:

private final Object mRestrictionsLock = NamedLock.create("mRestrictionsLock");
private final Object mAppRestrictionsLock = NamedLock.create("mAppRestrictionsLock");

Test: atest FrameworksCoreTests --test-filter '.*NamedLockTest'
Bug: 436854624
Flag: EXEMPT new utility class

Change-Id: I6330bc9d697939f2486ab659f1317714adb20e42
parent 8fc01b34
Loading
Loading
Loading
Loading
+55 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 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.internal.util;

import java.util.Objects;

/**
 * A lock with a name!
 *
 * <p>This class should be used as a synchronization lock (i.e., instead of
 * {@code mLock = new Object()}, so stack traces show exactly what the lock's for (other than just
 * it's internal address). In other words, it solves the "A Lock has no Name!" issue).
 *
 * @hide
 */
public final class NamedLock {

    private final String mName;

    private NamedLock(String name) {
        mName = Objects.requireNonNull(name, "name cannot be null");
        String stripped = name.strip();
        Preconditions.checkArgument(name.equals(stripped),
                "name (%s) cannot start or end with blank characters", name);
        Preconditions.checkArgument(!name.isEmpty(), "name cannot be empty");
    }

    /**
     * Creates a lock with the given name.
     *
     * @throws IllegalArgumentException if the name is empty, starts with a blank character, or ends
     *             with a blank character.
     */
    public static Object create(String name) {
        return new NamedLock(name);
    }

    @Override
    public String toString() {
        return mName;
    }
}
+93 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 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.internal.util;

import static com.android.internal.util.NamedLock.create;

import static com.google.common.truth.Truth.assertWithMessage;

import static org.junit.Assert.assertThrows;

import org.junit.Test;

public final class NamedLockTest {

    @Test
    public void testFactoryMethod_null() {
        assertThrows(NullPointerException.class, () -> create(null));
    }

    @Test
    public void testFactoryMethod_empty() {
        assertThrows(IllegalArgumentException.class, () -> create(""));
        assertThrows(IllegalArgumentException.class, () -> create(" "));
        assertThrows(IllegalArgumentException.class, () -> create("\t"));
    }

    @Test
    public void testFactoryMethod_startsWithSpace() {
        assertThrows(IllegalArgumentException.class,
                () -> create(" NAME, Y U START WITH SPACE?"));
    }

    @Test
    public void testFactoryMethod_startsWithTab() {
        assertThrows(IllegalArgumentException.class,
                () -> create("\tNAME, Y U START WITH TAB?"));
    }

    @Test
    public void testFactoryMethod_endsWithSpace() {
        assertThrows(IllegalArgumentException.class,
                () -> create("NAME, Y U END WITH SPACE? "));
    }

    @Test
    public void testFactoryMethod_endsWithTab() {
        assertThrows(IllegalArgumentException.class,
                () -> create("NAME, Y U END WITH TAB?\t"));
    }

    @Test
    public void testOneInstance() {
        var namedLock = create("Bond, James Bond");

        assertWithMessage("create()").that(namedLock).isNotNull();

        assertWithMessage("toString()").that(namedLock.toString()).isEqualTo("Bond, James Bond");
    }

    @Test
    public void testMultipleInstances_sameName() {
        String commonName = "Bond, James Bond";
        var namedLock1 = create(commonName);
        var namedLock2 = create(commonName);

        assertWithMessage("namedLock1").that(namedLock1).isNotSameInstanceAs(namedLock2);
        assertWithMessage("namedLock1").that(namedLock1).isNotEqualTo(namedLock2);
        assertWithMessage("namedLock2").that(namedLock2).isNotEqualTo(namedLock1);
    }

    @Test
    public void testMultipleInstances_differentNames() {
        var namedLock1 = create("Bond, James Bond");
        var namedLock2 = create("A Lock has a Name");

        assertWithMessage("namedLock1").that(namedLock1).isNotSameInstanceAs(namedLock2);
        assertWithMessage("namedLock1").that(namedLock1).isNotEqualTo(namedLock2);
        assertWithMessage("namedLock2").that(namedLock2).isNotEqualTo(namedLock1);
    }
}