blob: 38433bf31a80ce54a2b7009c164e9ecae2f78a0e [file] [log] [blame]
/*
* Copyright (C) 2021 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.bedstead.nene.users;
import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
import static android.os.Build.VERSION_CODES.R;
import android.content.Intent;
import android.os.UserHandle;
import android.util.Log;
import com.android.bedstead.nene.TestApis;
import com.android.bedstead.nene.exceptions.AdbException;
import com.android.bedstead.nene.exceptions.NeneException;
import com.android.bedstead.nene.permissions.PermissionContext;
import com.android.bedstead.nene.users.User.UserState;
import com.android.bedstead.nene.utils.ShellCommand;
import com.android.bedstead.nene.utils.ShellCommandUtils;
import com.android.bedstead.nene.utils.Versions;
import com.android.compatibility.common.util.BlockingBroadcastReceiver;
import javax.annotation.Nullable;
/**
* A representation of a User on device which may or may not exist.
*
* <p>To resolve the user into a {@link User}, see {@link #resolve()}.
*/
public abstract class UserReference implements AutoCloseable {
private static final String LOG_TAG = "UserReference";
private final int mId;
UserReference(int id) {
mId = id;
}
public final int id() {
return mId;
}
/**
* Get a {@link UserHandle} for the {@link #id()}.
*/
public final UserHandle userHandle() {
return UserHandle.of(mId);
}
/**
* Get the current state of the {@link User} from the device, or {@code null} if the user does
* not exist.
*/
@Nullable
public final User resolve() {
return TestApis.users().fetchUser(mId);
}
/**
* Remove the user from the device.
*
* <p>If the user does not exist, or the removal fails for any other reason, a
* {@link NeneException} will be thrown.
*/
public final void remove() {
// TODO(scottjonathan): There's a potential issue here as when the user is marked as
// "is removing" the DPC still can't be uninstalled because it's set as the profile owner.
try {
// Expected success string is "Success: removed user"
ShellCommand.builder("pm remove-user")
.addOperand(mId)
.validate(ShellCommandUtils::startsWithSuccess)
.execute();
TestApis.users().waitForUserToNotExistOrMatch(this, User::isRemoving);
} catch (AdbException e) {
throw new NeneException("Could not remove user " + this, e);
}
}
/**
* Start the user.
*
* <p>After calling this command, the user will be in the {@link UserState#RUNNING_UNLOCKED}
* state.
*
* <p>If the user does not exist, or the start fails for any other reason, a
* {@link NeneException} will be thrown.
*/
//TODO(scottjonathan): Deal with users who won't unlock
public UserReference start() {
try {
// Expected success string is "Success: user started"
ShellCommand.builder("am start-user")
.addOperand(mId)
.addOperand("-w")
.validate(ShellCommandUtils::startsWithSuccess)
.execute();
User waitedUser = TestApis.users().waitForUserToNotExistOrMatch(
this, (user) -> user.state() == UserState.RUNNING_UNLOCKED);
if (waitedUser == null) {
throw new NeneException("User does not exist " + this);
}
} catch (AdbException e) {
throw new NeneException("Could not start user " + this, e);
}
return this;
}
/**
* Stop the user.
*
* <p>After calling this command, the user will be in the {@link UserState#NOT_RUNNING} state.
*/
public UserReference stop() {
try {
// Expects no output on success or failure - stderr output on failure
ShellCommand.builder("am stop-user")
.addOperand("-f") // Force stop
.addOperand(mId)
.allowEmptyOutput(true)
.validate(String::isEmpty)
.execute();
User waitedUser = TestApis.users().waitForUserToNotExistOrMatch(
this, (user) -> user.state() == UserState.NOT_RUNNING);
if (waitedUser == null) {
throw new NeneException("User does not exist " + this);
}
} catch (AdbException e) {
throw new NeneException("Could not stop user " + this, e);
}
return this;
}
/**
* Make the user the foreground user.
*/
public UserReference switchTo() {
// This is created outside of the try because we don't want to wait for the broadcast
// on versions less than R
BlockingBroadcastReceiver broadcastReceiver =
new BlockingBroadcastReceiver(TestApis.context().instrumentedContext(),
Intent.ACTION_USER_FOREGROUND,
(intent) ->((UserHandle)
intent.getParcelableExtra(Intent.EXTRA_USER))
.getIdentifier() == mId);
try {
if (Versions.meetsMinimumSdkVersionRequirement(R)) {
try (PermissionContext p =
TestApis.permissions().withPermission(INTERACT_ACROSS_USERS_FULL)) {
broadcastReceiver.registerForAllUsers();
}
}
// Expects no output on success or failure
ShellCommand.builder("am switch-user")
.addOperand(mId)
.allowEmptyOutput(true)
.validate(String::isEmpty)
.execute();
if (Versions.meetsMinimumSdkVersionRequirement(R)) {
broadcastReceiver.awaitForBroadcast();
} else {
Thread.sleep(20000);
}
} catch (AdbException e) {
throw new NeneException("Could not switch to user", e);
} catch (InterruptedException e) {
Log.e(LOG_TAG, "Interrupted while switching user", e);
} finally {
broadcastReceiver.unregisterQuietly();
}
return this;
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof UserReference)) {
return false;
}
UserReference other = (UserReference) obj;
return other.id() == id();
}
@Override
public int hashCode() {
return id();
}
/** See {@link #remove}. */
@Override
public void close() {
remove();
}
}