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

Commit d7e8280b authored by Yasin Kilicdere's avatar Yasin Kilicdere Committed by Automerger Merge Worker
Browse files

Merge "Wait for ACTION_MEDIA_MOUNTED before removing started users." into tm-dev am: f171d009

parents c2001728 f171d009
Loading
Loading
Loading
Loading
+122 −0
Original line number Original line Diff line number Diff line
/*
 * Copyright (C) 2022 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 android.multiuser;

import static java.util.concurrent.TimeUnit.SECONDS;

import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.text.TextUtils;
import android.util.Log;

import java.io.Closeable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Semaphore;

public class BroadcastWaiter implements Closeable {
    private final Context mContext;
    private final String mTag;
    private final int mTimeoutInSecond;
    private final Set<String> mActions;

    private final Set<String> mActionReceivedForUser = new HashSet<>();
    private final List<BroadcastReceiver> mBroadcastReceivers = new ArrayList<>();

    private final Map<String, Semaphore> mSemaphoresMap = new ConcurrentHashMap<>();
    private Semaphore getSemaphore(final String action, final int userId) {
        final String key = action + userId;
        return mSemaphoresMap.computeIfAbsent(key, (String absentKey) -> new Semaphore(0));
    }

    public BroadcastWaiter(Context context, String tag, int timeoutInSecond, String... actions) {
        mContext = context;
        mTag = tag + "_" + BroadcastWaiter.class.getSimpleName();
        mTimeoutInSecond = timeoutInSecond;

        mActions = new HashSet<>(Arrays.asList(actions));
        mActions.forEach(this::registerBroadcastReceiver);
    }

    private void registerBroadcastReceiver(String action) {
        Log.d(mTag, "#registerBroadcastReceiver for " + action);

        final IntentFilter filter = new IntentFilter(action);
        if (action.equals(Intent.ACTION_MEDIA_MOUNTED)) {
            filter.addDataScheme(ContentResolver.SCHEME_FILE);
        }

        final BroadcastReceiver receiver = new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                if (action.equals(intent.getAction())) {
                    final int userId = getSendingUserId();
                    final String data = intent.getDataString();
                    Log.d(mTag, "Received " + action + " for user " + userId
                            + (!TextUtils.isEmpty(data) ? " with " + data : ""));
                    mActionReceivedForUser.add(action + userId);
                    getSemaphore(action, userId).release();
                }
            }
        };

        mContext.registerReceiverForAllUsers(receiver, filter, null, null);
        mBroadcastReceivers.add(receiver);
    }

    @Override
    public void close() {
        mBroadcastReceivers.forEach(mContext::unregisterReceiver);
    }

    public boolean hasActionBeenReceivedForUser(String action, int userId) {
        return mActionReceivedForUser.contains(action + userId);
    }

    public boolean waitActionForUser(String action, int userId) {
        Log.d(mTag, "#waitActionForUser(action: " + action + ", userId: " + userId + ")");

        if (!mActions.contains(action)) {
            Log.d(mTag, "No broadcast receivers registered for " + action);
            return false;
        }

        try {
            if (!getSemaphore(action, userId).tryAcquire(1, mTimeoutInSecond, SECONDS)) {
                Log.e(mTag, action + " broadcast wasn't received for user " + userId);
                return false;
            }
        } catch (InterruptedException e) {
            Log.e(mTag, "Interrupted while waiting " + action + " for user " + userId);
            return false;
        }
        return true;
    }

    public boolean waitActionForUserIfNotReceivedYet(String action, int userId) {
        return hasActionBeenReceivedForUser(action, userId)
                || waitActionForUser(action, userId);
    }
}
+33 −73
Original line number Original line Diff line number Diff line
@@ -26,7 +26,6 @@ import android.app.IStopUserCallback;
import android.app.UserSwitchObserver;
import android.app.UserSwitchObserver;
import android.app.WaitResult;
import android.app.WaitResult;
import android.content.BroadcastReceiver;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Context;
import android.content.IIntentReceiver;
import android.content.IIntentReceiver;
import android.content.IIntentSender;
import android.content.IIntentSender;
@@ -115,6 +114,7 @@ public class UserLifecycleTests {
    private PackageManager mPm;
    private PackageManager mPm;
    private ArrayList<Integer> mUsersToRemove;
    private ArrayList<Integer> mUsersToRemove;
    private boolean mHasManagedUserFeature;
    private boolean mHasManagedUserFeature;
    private BroadcastWaiter mBroadcastWaiter;


    private final BenchmarkRunner mRunner = new BenchmarkRunner();
    private final BenchmarkRunner mRunner = new BenchmarkRunner();
    @Rule
    @Rule
@@ -129,6 +129,10 @@ public class UserLifecycleTests {
        mUsersToRemove = new ArrayList<>();
        mUsersToRemove = new ArrayList<>();
        mPm = context.getPackageManager();
        mPm = context.getPackageManager();
        mHasManagedUserFeature = mPm.hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS);
        mHasManagedUserFeature = mPm.hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS);
        mBroadcastWaiter = new BroadcastWaiter(context, TAG, TIMEOUT_IN_SECOND,
                Intent.ACTION_USER_STARTED,
                Intent.ACTION_MEDIA_MOUNTED,
                Intent.ACTION_USER_UNLOCKED);
        removeAnyPreviousTestUsers();
        removeAnyPreviousTestUsers();
        if (mAm.getCurrentUser() != UserHandle.USER_SYSTEM) {
        if (mAm.getCurrentUser() != UserHandle.USER_SYSTEM) {
            Log.w(TAG, "WARNING: Tests are being run from user " + mAm.getCurrentUser()
            Log.w(TAG, "WARNING: Tests are being run from user " + mAm.getCurrentUser()
@@ -138,6 +142,7 @@ public class UserLifecycleTests {


    @After
    @After
    public void tearDown() {
    public void tearDown() {
        mBroadcastWaiter.close();
        for (int userId : mUsersToRemove) {
        for (int userId : mUsersToRemove) {
            try {
            try {
                mUm.removeUser(userId);
                mUm.removeUser(userId);
@@ -168,12 +173,10 @@ public class UserLifecycleTests {
            Log.i(TAG, "Starting timer");
            Log.i(TAG, "Starting timer");
            final int userId = createUserNoFlags();
            final int userId = createUserNoFlags();


            final CountDownLatch latch = new CountDownLatch(1);
            registerBroadcastReceiver(Intent.ACTION_USER_STARTED, latch, userId);
            // Don't use this.startUserInBackgroundAndWaitForUnlock() since only waiting until
            // Don't use this.startUserInBackgroundAndWaitForUnlock() since only waiting until
            // ACTION_USER_STARTED.
            // ACTION_USER_STARTED.
            mIam.startUserInBackground(userId);
            mIam.startUserInBackground(userId);
            waitForLatch("Failed to achieve ACTION_USER_STARTED for user " + userId, latch);
            waitForBroadcast(Intent.ACTION_USER_STARTED, userId);


            mRunner.pauseTiming();
            mRunner.pauseTiming();
            Log.i(TAG, "Stopping timer");
            Log.i(TAG, "Stopping timer");
@@ -191,13 +194,11 @@ public class UserLifecycleTests {
        while (mRunner.keepRunning()) {
        while (mRunner.keepRunning()) {
            mRunner.pauseTiming();
            mRunner.pauseTiming();
            final int userId = createUserNoFlags();
            final int userId = createUserNoFlags();
            final CountDownLatch latch = new CountDownLatch(1);
            registerBroadcastReceiver(Intent.ACTION_USER_STARTED, latch, userId);
            mRunner.resumeTiming();
            mRunner.resumeTiming();
            Log.i(TAG, "Starting timer");
            Log.i(TAG, "Starting timer");


            mIam.startUserInBackground(userId);
            mIam.startUserInBackground(userId);
            waitForLatch("Failed to achieve ACTION_USER_STARTED for user " + userId, latch);
            waitForBroadcast(Intent.ACTION_USER_STARTED, userId);


            mRunner.pauseTiming();
            mRunner.pauseTiming();
            Log.i(TAG, "Stopping timer");
            Log.i(TAG, "Stopping timer");
@@ -255,14 +256,11 @@ public class UserLifecycleTests {
            mRunner.pauseTiming();
            mRunner.pauseTiming();
            final int startUser = mAm.getCurrentUser();
            final int startUser = mAm.getCurrentUser();
            final int testUser = initializeNewUserAndSwitchBack(/* stopNewUser */ true);
            final int testUser = initializeNewUserAndSwitchBack(/* stopNewUser */ true);
            final CountDownLatch latch = new CountDownLatch(1);
            registerBroadcastReceiver(Intent.ACTION_USER_UNLOCKED, latch, testUser);
            mRunner.resumeTiming();
            mRunner.resumeTiming();
            Log.i(TAG, "Starting timer");
            Log.i(TAG, "Starting timer");


            mAm.switchUser(testUser);
            mAm.switchUser(testUser);
            waitForLatch("Failed to achieve 2nd ACTION_USER_UNLOCKED for user " + testUser, latch);
            waitForBroadcast(Intent.ACTION_USER_UNLOCKED, testUser);



            mRunner.pauseTiming();
            mRunner.pauseTiming();
            Log.i(TAG, "Stopping timer");
            Log.i(TAG, "Stopping timer");
@@ -298,13 +296,11 @@ public class UserLifecycleTests {
        while (mRunner.keepRunning()) {
        while (mRunner.keepRunning()) {
            mRunner.pauseTiming();
            mRunner.pauseTiming();
            final int userId = createUserNoFlags();
            final int userId = createUserNoFlags();
            final CountDownLatch latch1 = new CountDownLatch(1);
            final CountDownLatch latch2 = new CountDownLatch(1);
            registerBroadcastReceiver(Intent.ACTION_USER_STARTED, latch1, userId);
            registerMediaBroadcastReceiver(latch2, userId);
            mIam.startUserInBackground(userId);
            mIam.startUserInBackground(userId);
            waitForLatch("Failed to achieve ACTION_USER_STARTED for user " + userId, latch1);

            waitForLatch("Failed to achieve ACTION_MEDIA_MOUNTED for user " + userId, latch2);
            waitForBroadcast(Intent.ACTION_USER_STARTED, userId);
            waitForBroadcast(Intent.ACTION_MEDIA_MOUNTED, userId);

            mRunner.resumeTiming();
            mRunner.resumeTiming();
            Log.i(TAG, "Starting timer");
            Log.i(TAG, "Starting timer");


@@ -347,10 +343,9 @@ public class UserLifecycleTests {
            mRunner.pauseTiming();
            mRunner.pauseTiming();
            final int startUser = mAm.getCurrentUser();
            final int startUser = mAm.getCurrentUser();
            final int userId = createUserWithFlags(UserInfo.FLAG_EPHEMERAL | UserInfo.FLAG_DEMO);
            final int userId = createUserWithFlags(UserInfo.FLAG_EPHEMERAL | UserInfo.FLAG_DEMO);
            final CountDownLatch prelatch = new CountDownLatch(1);
            registerMediaBroadcastReceiver(prelatch, userId);
            switchUser(userId);
            switchUser(userId);
            waitForLatch("Failed to achieve ACTION_MEDIA_MOUNTED for user " + userId, prelatch);
            waitForBroadcast(Intent.ACTION_MEDIA_MOUNTED, userId);

            final CountDownLatch latch = new CountDownLatch(1);
            final CountDownLatch latch = new CountDownLatch(1);
            InstrumentationRegistry.getContext().registerReceiver(new BroadcastReceiver() {
            InstrumentationRegistry.getContext().registerReceiver(new BroadcastReceiver() {
                @Override
                @Override
@@ -552,10 +547,9 @@ public class UserLifecycleTests {
        while (mRunner.keepRunning()) {
        while (mRunner.keepRunning()) {
            mRunner.pauseTiming();
            mRunner.pauseTiming();
            final int userId = createManagedProfile();
            final int userId = createManagedProfile();
            final CountDownLatch prelatch = new CountDownLatch(1);
            registerMediaBroadcastReceiver(prelatch, userId);
            startUserInBackgroundAndWaitForUnlock(userId);
            startUserInBackgroundAndWaitForUnlock(userId);
            waitForLatch("Failed to achieve ACTION_MEDIA_MOUNTED for user " + userId, prelatch);
            waitForBroadcast(Intent.ACTION_MEDIA_MOUNTED, userId);

            mRunner.resumeTiming();
            mRunner.resumeTiming();
            Log.i(TAG, "Starting timer");
            Log.i(TAG, "Starting timer");


@@ -710,13 +704,9 @@ public class UserLifecycleTests {
        final int origUser = mAm.getCurrentUser();
        final int origUser = mAm.getCurrentUser();
        // First, create and switch to testUser, waiting for its ACTION_USER_UNLOCKED
        // First, create and switch to testUser, waiting for its ACTION_USER_UNLOCKED
        final int testUser = createUserNoFlags();
        final int testUser = createUserNoFlags();
        final CountDownLatch latch1 = new CountDownLatch(1);
        final CountDownLatch latch2 = new CountDownLatch(1);
        registerBroadcastReceiver(Intent.ACTION_USER_UNLOCKED, latch1, testUser);
        registerMediaBroadcastReceiver(latch2, testUser);
        mAm.switchUser(testUser);
        mAm.switchUser(testUser);
        waitForLatch("Failed to achieve initial ACTION_USER_UNLOCKED for user " + testUser, latch1);
        waitForBroadcast(Intent.ACTION_USER_UNLOCKED, testUser);
        waitForLatch("Failed to achieve initial ACTION_MEDIA_MOUNTED for user " + testUser, latch2);
        waitForBroadcast(Intent.ACTION_MEDIA_MOUNTED, testUser);


        // Second, switch back to origUser, waiting merely for switchUser() to finish
        // Second, switch back to origUser, waiting merely for switchUser() to finish
        switchUser(origUser);
        switchUser(origUser);
@@ -786,50 +776,6 @@ public class UserLifecycleTests {
                }, TAG);
                }, TAG);
    }
    }


    private void registerBroadcastReceiver(final String action, final CountDownLatch latch,
            final int userId) {
        InstrumentationRegistry.getContext().registerReceiverAsUser(new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                if (action.equals(intent.getAction()) && intent.getIntExtra(
                        Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL) == userId) {
                    latch.countDown();
                }
            }
        }, UserHandle.of(userId), new IntentFilter(action), null, null);
    }

    /**
     * Register for a broadcast to indicate that Storage has processed the given user.
     * Without this as part of setup, for tests dealing with already-switched users, Storage may not
     * have finished, making the resulting processing inconsistent.
     *
     * Strictly speaking, the receiver should always be unregistered afterwards, but we don't
     * necessarily bother since receivers from failed tests will be removed on test uninstallation.
     */
    private void registerMediaBroadcastReceiver(final CountDownLatch latch, final int userId) {
        final String action = Intent.ACTION_MEDIA_MOUNTED;

        final IntentFilter filter = new IntentFilter();
        filter.addAction(action);
        filter.addDataScheme(ContentResolver.SCHEME_FILE);

        final Context context = InstrumentationRegistry.getContext();
        context.registerReceiverAsUser(new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                final String data = intent.getDataString();
                if (action.equals(intent.getAction())) {
                    Log.d(TAG, "Received ACTION_MEDIA_MOUNTED with " + data);
                    if (data != null && data.contains("/" + userId)) {
                        latch.countDown();
                        context.unregisterReceiver(this);
                    }
                }
            }
        }, UserHandle.of(userId), filter, null, null);
    }

    private class ProgressWaiter extends IProgressListener.Stub {
    private class ProgressWaiter extends IProgressListener.Stub {
        private final CountDownLatch mFinishedLatch = new CountDownLatch(1);
        private final CountDownLatch mFinishedLatch = new CountDownLatch(1);


@@ -854,6 +800,17 @@ public class UserLifecycleTests {
        }
        }
    }
    }


    /**
     * Waits TIMEOUT_IN_SECOND for the broadcast to be received, otherwise declares the given error.
     * It only works for the broadcasts provided in {@link #mBroadcastWaiter}'s instantiation above.
     * @param action action of the broadcast, i.e. {@link Intent#ACTION_USER_STARTED}
     * @param userId sendingUserId of the broadcast. See {@link BroadcastReceiver#getSendingUserId}
     */
    private void waitForBroadcast(String action, int userId) {
        attestTrue("Failed to achieve " + action + " for user " + userId,
                mBroadcastWaiter.waitActionForUser(action, userId));
    }

    /** Waits TIMEOUT_IN_SECOND for the latch to complete, otherwise declares the given error. */
    /** Waits TIMEOUT_IN_SECOND for the latch to complete, otherwise declares the given error. */
    private void waitForLatch(String errMsg, CountDownLatch latch) {
    private void waitForLatch(String errMsg, CountDownLatch latch) {
        boolean success = false;
        boolean success = false;
@@ -880,6 +837,9 @@ public class UserLifecycleTests {
    }
    }


    private void removeUser(int userId) {
    private void removeUser(int userId) {
        if (mBroadcastWaiter.hasActionBeenReceivedForUser(Intent.ACTION_USER_STARTED, userId)) {
            mBroadcastWaiter.waitActionForUserIfNotReceivedYet(Intent.ACTION_MEDIA_MOUNTED, userId);
        }
        try {
        try {
            mUm.removeUser(userId);
            mUm.removeUser(userId);
            final long startTime = System.currentTimeMillis();
            final long startTime = System.currentTimeMillis();