blob: 0f333073476cee8e7a71e7f64d795113f02bb27e [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.dpmwrapper;
import static android.Manifest.permission.INTERACT_ACROSS_USERS;
import static com.android.bedstead.dpmwrapper.DataFormatter.addArg;
import static com.android.bedstead.dpmwrapper.DataFormatter.getArg;
import static com.android.bedstead.dpmwrapper.Utils.ACTION_WRAPPED_MANAGER_CALL;
import static com.android.bedstead.dpmwrapper.Utils.EXTRA_CLASS;
import static com.android.bedstead.dpmwrapper.Utils.EXTRA_METHOD;
import static com.android.bedstead.dpmwrapper.Utils.EXTRA_NUMBER_ARGS;
import static com.android.bedstead.dpmwrapper.Utils.VERBOSE;
import static com.android.bedstead.dpmwrapper.Utils.getHandler;
import android.annotation.Nullable;
import android.app.admin.DevicePolicyManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.net.wifi.WifiManager;
import android.os.Bundle;
import android.os.HardwarePropertiesManager;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.Log;
import org.mockito.stubbing.Answer;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Locale;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
//TODO(b/176993670): STOPSHIP - it currently uses ordered broadcasts and a Mockito spy to implement
//the IPC between users, but before S is shipped it should be changed to use the connected apps SDK
//or the new CTS infrastructure.
/**
* Class used to create to provide a {@link DevicePolicyManager} implementation (and other managers
* that must be run by the device owner user) that automatically funnels calls between the user
* running the tests and the user that is the device owner.
*/
public final class TestAppSystemServiceFactory {
private static final String TAG = TestAppSystemServiceFactory.class.getSimpleName();
private static final int RESULT_NOT_SENT_TO_ANY_RECEIVER = 108;
static final int RESULT_OK = 42;
static final int RESULT_EXCEPTION = 666;
// Must be high enough to outlast long tests like NetworkLoggingTest, which waits up to
// 6 minutes for network monitoring events.
private static final long TIMEOUT_MS = TimeUnit.MINUTES.toMillis(10);
// Caches whether the package declares the required receiver (otherwise each test would be
// querying package manager, which is expensive)
private static final HashMap<String, Boolean> sHasRequiredReceiver = new HashMap<>();
/**
* Gets the proper {@link DevicePolicyManager} instance to be used by the test.
*/
public static DevicePolicyManager getDevicePolicyManager(Context context,
Class<? extends BroadcastReceiver> receiverClass, boolean forDeviceOwner) {
return getSystemService(context, DevicePolicyManager.class, receiverClass, forDeviceOwner);
}
/**
* Gets the proper {@link WifiManager} instance to be used by device owner tests.
*/
public static WifiManager getWifiManager(Context context,
Class<? extends BroadcastReceiver> receiverClass) {
return getSystemService(context, WifiManager.class, receiverClass,
/* forDeviceOwner= */ true);
}
/**
* Gets the proper {@link HardwarePropertiesManager} instance to be used by device owner tests.
*/
public static HardwarePropertiesManager getHardwarePropertiesManager(Context context,
Class<? extends BroadcastReceiver> receiverClass) {
return getSystemService(context, HardwarePropertiesManager.class, receiverClass,
/* forDeviceOwner= */ true);
}
/**
* Gets the proper {@link UserManager} instance to be used by device owner tests.
*/
public static UserManager getUserManager(Context context,
Class<? extends BroadcastReceiver> receiverClass) {
return getSystemService(context, UserManager.class, receiverClass,
/* forDeviceOwner= */ true);
}
/**
* Gets the proper {@link GenericManager} instance to be used by the test.
*/
public static GenericManager getGenericManager(Context context,
Class<? extends BroadcastReceiver> receiverClass) {
return getSystemService(context, GenericManager.class, receiverClass,
/* forDeviceOwner= */ true);
}
private static void assertHasRequiredReceiver(Context context) {
if (!UserManager.isHeadlessSystemUserMode()) return;
String packageName = context.getPackageName();
Boolean hasIt = sHasRequiredReceiver.get(packageName);
if (hasIt != null && hasIt) {
return;
}
PackageManager pm = context.getPackageManager();
Class<?> targetClass = TestAppCallbacksReceiver.class;
PackageInfo packageInfo;
try {
packageInfo = pm.getPackageInfo(packageName, PackageManager.GET_RECEIVERS);
} catch (NameNotFoundException e) {
Log.wtf(TAG, "Could not get receivers for " + packageName);
return;
}
int numberReceivers = (packageInfo.receivers == null ? 0 : packageInfo.receivers.length);
Log.d(TAG, "assertHasRequiredReceiver(" + packageName + "): userId=" + context.getUserId()
+ ", info=" + packageInfo + ", receivers=" + numberReceivers);
if (packageInfo.receivers != null) {
for (ActivityInfo receiver : packageInfo.receivers) {
Log.v(TAG, "checking receiver " + receiver);
Class<?> receiverClass = null;
try {
receiverClass = Class.forName(receiver.name);
} catch (ClassNotFoundException e) {
Log.e(TAG, "Invalid receiver class on manifest: " + receiver.name);
continue;
}
if (TestAppCallbacksReceiver.class.isAssignableFrom(receiverClass)) {
Log.d(TAG, "Found " + receiverClass.getName() + " on " + packageName);
sHasRequiredReceiver.put(packageName, Boolean.TRUE);
return;
}
}
}
if (numberReceivers == 0) {
// This is happening sometimes on headless system user; most likely it's a permission
// issue querying pm, but given that the DpmWrapper is temporary and this check is more
// of a validation to avoid other issues, it's ok to just log...
Log.wtf(TAG, "Package " + packageName + " has no receivers");
return;
}
fail("Package " + packageName + " has " + numberReceivers + " receivers, but not extends "
+ TestAppCallbacksReceiver.class.getName() + " - did you add one to the manifest?");
}
private static <T> T getSystemService(Context context, Class<T> serviceClass,
Class<? extends BroadcastReceiver> receiverClass, boolean forDeviceOwner) {
ServiceManagerWrapper<T> wrapper = null;
Class<?> wrappedClass;
@SuppressWarnings("unchecked")
T manager = null;
if (serviceClass.equals(DevicePolicyManager.class)) {
wrappedClass = DevicePolicyManager.class;
@SuppressWarnings("unchecked")
ServiceManagerWrapper<T> safeCastWrapper =
(ServiceManagerWrapper<T>) new DevicePolicyManagerWrapper();
wrapper = safeCastWrapper;
} else if (serviceClass.equals(WifiManager.class)) {
@SuppressWarnings("unchecked")
ServiceManagerWrapper<T> safeCastWrapper =
(ServiceManagerWrapper<T>) new WifiManagerWrapper();
wrapper = safeCastWrapper;
wrappedClass = WifiManager.class;
} else if (serviceClass.equals(HardwarePropertiesManager.class)) {
@SuppressWarnings("unchecked")
ServiceManagerWrapper<T> safeCastWrapper =
(ServiceManagerWrapper<T>) new HardwarePropertiesManagerWrapper();
wrapper = safeCastWrapper;
wrappedClass = HardwarePropertiesManager.class;
} else if (serviceClass.equals(UserManager.class)) {
@SuppressWarnings("unchecked")
ServiceManagerWrapper<T> safeCastWrapper =
(ServiceManagerWrapper<T>) new UserManagerWrapper();
wrapper = safeCastWrapper;
wrappedClass = UserManager.class;
} else if (serviceClass.equals(GenericManager.class)) {
@SuppressWarnings("unchecked")
ServiceManagerWrapper<T> safeCastWrapper =
(ServiceManagerWrapper<T>) new GenericManagerWrapper();
@SuppressWarnings("unchecked")
T safeCastManager = (T) new GenericManagerImpl(context);
wrapper = safeCastWrapper;
wrappedClass = GenericManager.class;
manager = safeCastManager;
} else {
throw new IllegalArgumentException("invalid service class: " + serviceClass);
}
if (manager == null) {
manager = (T) context.getSystemService(wrappedClass);
}
if (manager == null) {
fail("Could not get a manager of type " + serviceClass);
}
if (!forDeviceOwner) return manager;
assertHasRequiredReceiver(context);
int userId = context.getUserId();
if (userId == UserHandle.USER_SYSTEM || !UserManager.isHeadlessSystemUserMode()) {
Log.i(TAG, "get(): returning 'pure' DevicePolicyManager for user " + userId);
return manager;
}
String receiverClassName = receiverClass.getName();
final String wrappedClassName = wrappedClass.getName();
if (VERBOSE) {
Log.v(TAG, "get(): receiverClassName: " + receiverClassName
+ ", wrappedClassName: " + wrappedClassName);
}
Answer<?> answer = (inv) -> {
Object[] args = inv.getArguments();
if (VERBOSE) {
Log.v(TAG, "spying " + inv + " method: " + inv.getMethod());
} else {
Log.i(TAG, "spying " + inv.getMethod());
}
String methodName = inv.getMethod().getName();
Intent intent = new Intent(ACTION_WRAPPED_MANAGER_CALL)
.setClassName(context, receiverClassName)
.putExtra(EXTRA_CLASS, wrappedClassName)
.putExtra(EXTRA_METHOD, methodName)
.putExtra(EXTRA_NUMBER_ARGS, args.length);
for (int i = 0; i < args.length; i++) {
addArg(intent, args, i);
}
final CountDownLatch latch = new CountDownLatch(1);
final AtomicReference<Result> resultRef = new AtomicReference<>();
BroadcastReceiver myReceiver = new BroadcastReceiver() {
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (VERBOSE) {
Log.v(TAG, "spy received intent " + action + " for user "
+ context.getUserId());
}
Result result = new Result(this);
if (VERBOSE) Log.v(TAG, "result:" + result);
resultRef.set(result);
latch.countDown();
};
};
if (VERBOSE) {
Log.v(TAG, "Sending ordered broadcast (" + Utils.toString(intent) + ") from user "
+ userId + " to user " + UserHandle.SYSTEM);
}
// NOTE: method below used to be wrapped under runWithShellPermissionIdentity() to get
// INTERACT_ACROSS_USERS permission, but that's not needed anymore (as the permission
// is granted by the test. Besides, this class is now also used by DO apps that are not
// instrumented, so it was removed
if (context.checkSelfPermission(INTERACT_ACROSS_USERS)
!= PackageManager.PERMISSION_GRANTED) {
fail("Package " + context.getPackageName() + " doesn't have "
+ INTERACT_ACROSS_USERS + " - did you add it to the manifest and called "
+ "grantDpmWrapper() (for user " + userId + ") in the host-side test?");
}
context.sendOrderedBroadcastAsUser(intent,
UserHandle.SYSTEM, /* permission= */ null, myReceiver, getHandler(),
RESULT_NOT_SENT_TO_ANY_RECEIVER, /* initialData= */ null,
/* initialExtras= */ null);
if (VERBOSE) {
Log.d(TAG, "Waiting up to " + TIMEOUT_MS + "ms for response on "
+ Thread.currentThread());
}
if (!latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
fail("Ordered broadcast for %s() not received in %dms", methodName, TIMEOUT_MS);
}
Result result = resultRef.get();
Log.d(TAG, "Received result on user " + userId + ". Code: "
+ resultCodeToString(result.code));
if (VERBOSE) {
// Some results - like network logging events - are quite large
Log.v(TAG, "Result: " + result);
}
switch (result.code) {
case RESULT_OK:
return result.value;
case RESULT_EXCEPTION:
Exception e = (Exception) result.value;
throw (e instanceof InvocationTargetException) ? e.getCause() : e;
case RESULT_NOT_SENT_TO_ANY_RECEIVER:
fail("Didn't receive result from ordered broadcast - did you override "
+ receiverClassName + ".onReceive() to call "
+ "DeviceOwnerHelper.runManagerMethod()?");
return null;
default:
fail("Received invalid result for method %s: %s", methodName, result);
return null;
}
};
T spy = wrapper.getWrapper(context, manager, answer);
return spy;
}
static String resultCodeToString(int code) {
// Can't use DebugUtils.constantToString() because some valus are private
switch (code) {
case RESULT_NOT_SENT_TO_ANY_RECEIVER:
return "RESULT_NOT_SENT_TO_ANY_RECEIVER";
case RESULT_OK:
return "RESULT_OK";
case RESULT_EXCEPTION:
return "RESULT_EXCEPTION";
default:
return "RESULT_UNKNOWN:" + code;
}
}
private static void fail(String template, Object... args) {
throw new AssertionError(String.format(Locale.ENGLISH, template, args));
}
private static final class Result {
public final int code;
@Nullable public final String error;
@Nullable public final Bundle extras;
@Nullable public final Object value;
Result(BroadcastReceiver receiver) {
int resultCode = receiver.getResultCode();
String data = receiver.getResultData();
extras = receiver.getResultExtras(/* makeMap= */ true);
Object parsedValue = null;
try {
if (extras != null && !extras.isEmpty()) {
Object[] result = new Object[1];
int index = 0;
getArg(extras, result, /* parameterTypes= */ null, index);
parsedValue = result[index];
}
} catch (Exception e) {
Log.e(TAG, "error parsing extras (code=" + resultCode + ", data=" + data, e);
data = "error parsing extras";
resultCode = RESULT_EXCEPTION;
}
code = resultCode;
error = data;
value = parsedValue;
}
@Override
public String toString() {
return "Result[code=" + resultCodeToString(code) + ", error=" + error
+ ", extras=" + extras + ", value=" + value + "]";
}
}
abstract static class ServiceManagerWrapper<T> {
abstract T getWrapper(Context context, T manager, Answer<?> answer);
}
private TestAppSystemServiceFactory() {
throw new UnsupportedOperationException("contains only static methods");
}
}