blob: ad65367b1a75d2c595da4f105c337d40733dee8d [file] [log] [blame]
/*
* Copyright (C) 2017 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.cts.comp;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertTrue;
import static junit.framework.Assert.fail;
import android.app.admin.DevicePolicyManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
import android.os.IInterface;
import android.os.Looper;
import android.os.Process;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
import android.test.AndroidTestCase;
import android.test.MoreAsserts;
import android.util.Log;
import java.util.List;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
/**
* Testing various scenarios when a profile owner / device owner tries to bind a service
* in the other profile, and everything is setup correctly.
*/
public class BindDeviceAdminServiceGoodSetupTest extends AndroidTestCase {
private static final String TAG = "BindDeviceAdminTest";
private static final String NON_MANAGING_PACKAGE = AdminReceiver.COMP_DPC_2_PACKAGE_NAME;
private static final ServiceConnection EMPTY_SERVICE_CONNECTION = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {}
@Override
public void onServiceDisconnected(ComponentName name) {}
};
private static final IInterface NOT_IN_MAIN_THREAD_POISON_PILL = () -> null;
private DevicePolicyManager mDpm;
private List<UserHandle> mTargetUsers;
@Override
public void setUp() {
mDpm = (DevicePolicyManager)
mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
assertEquals(AdminReceiver.COMP_DPC_PACKAGE_NAME, mContext.getPackageName());
mTargetUsers = mDpm.getBindDeviceAdminTargetUsers(AdminReceiver.getComponentName(mContext));
assertTrue("No target users found", mTargetUsers.size() > 0);
}
public void testOnlyDeviceOwnerCanHaveMoreThanOneTargetUser() {
if (!mDpm.isDeviceOwnerApp(AdminReceiver.getComponentName(mContext).getPackageName())) {
assertEquals(1, mTargetUsers.size());
}
}
/**
* If the intent is implicit, expected to throw {@link IllegalArgumentException}.
*/
public void testCannotBind_implicitIntent() throws Exception {
final Intent implicitIntent = new Intent(Intent.ACTION_VIEW);
for (UserHandle targetUser : mTargetUsers) {
try {
bind(implicitIntent, EMPTY_SERVICE_CONNECTION, targetUser);
fail("IllegalArgumentException should be thrown for target user " + targetUser);
} catch (IllegalArgumentException ex) {
MoreAsserts.assertContainsRegex("Service intent must be explicit", ex.getMessage());
}
}
}
/**
* If the intent is not resolvable, it should return {@code null}.
*/
public void testCannotBind_notResolvableIntent() throws Exception {
final Intent notResolvableIntent = new Intent();
notResolvableIntent.setClassName(mContext, "NotExistService");
for (UserHandle targetUser : mTargetUsers) {
assertFalse("Should not be allowed to bind to target user " + targetUser,
bind(notResolvableIntent, EMPTY_SERVICE_CONNECTION, targetUser));
}
}
/**
* Make sure we cannot bind exported service.
*/
public void testCannotBind_exportedCrossUserService() throws Exception {
final Intent serviceIntent = new Intent(mContext, ExportedCrossUserService.class);
for (UserHandle targetUser : mTargetUsers) {
try {
bind(serviceIntent, EMPTY_SERVICE_CONNECTION, targetUser);
fail("SecurityException should be thrown for target user " + targetUser);
} catch (SecurityException ex) {
MoreAsserts.assertContainsRegex("must be unexported", ex.getMessage());
}
}
}
/**
* Talk to a DPC package that is neither device owner nor profile owner.
*/
public void testCheckCannotBind_nonManagingPackage() throws Exception {
final Intent serviceIntent = new Intent();
serviceIntent.setClassName(NON_MANAGING_PACKAGE, CrossUserService.class.getName());
for (UserHandle targetUser : mTargetUsers) {
try {
bind(serviceIntent, EMPTY_SERVICE_CONNECTION, targetUser);
fail("SecurityException should be thrown for target user " + targetUser);
} catch (SecurityException ex) {
MoreAsserts.assertContainsRegex("Only allow to bind service", ex.getMessage());
}
}
}
/**
* Talk to the same DPC in same user, that is talking to itself.
*/
public void testCannotBind_sameUser() throws Exception {
try {
final Intent serviceIntent = new Intent(mContext, CrossUserService.class);
bind(serviceIntent, EMPTY_SERVICE_CONNECTION, Process.myUserHandle());
fail("IllegalArgumentException should be thrown");
} catch (IllegalArgumentException ex) {
MoreAsserts.assertContainsRegex("target user id must be different", ex.getMessage());
}
}
/**
* Send a String to other side and expect the exact same string is echoed back.
*/
public void testCrossProfileCall_echo() throws Exception {
final String ANSWER = "42";
for (UserHandle targetUser : mTargetUsers) {
assertCrossProfileCall(ANSWER, service -> service.echo(ANSWER), targetUser);
}
}
/**
* Make sure we are talking to the target user.
*/
public void testCrossProfileCall_getUserHandle() throws Exception {
for (UserHandle targetUser : mTargetUsers) {
assertCrossProfileCall(targetUser, service -> service.getUserHandle(), targetUser);
}
}
/**
* Convenient method for you to execute a cross user call and assert the return value of it.
* @param expected The expected result of the cross user call.
* @param callable It is called when the service is bound, use this to make the service call.
* @param targetUserHandle Which user are we talking to.
* @param <T> The return type of the service call.
*/
private <T> void assertCrossProfileCall(
T expected, CrossUserCallable<T> callable, UserHandle targetUserHandle)
throws Exception {
final LinkedBlockingQueue<IInterface> queue = new LinkedBlockingQueue<>();
final ServiceConnection serviceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.d(TAG, "onServiceConnected is called in " + Thread.currentThread().getName());
// Ensure onServiceConnected is running in main thread.
if (Looper.myLooper() != Looper.getMainLooper()) {
// Not running in main thread, failed the test.
Log.e(TAG, "onServiceConnected is not running in main thread!");
queue.add(NOT_IN_MAIN_THREAD_POISON_PILL);
return;
}
queue.add(ICrossUserService.Stub.asInterface(service));
}
@Override
public void onServiceDisconnected(ComponentName name) {
Log.d(TAG, "onServiceDisconnected is called");
}
};
final Intent serviceIntent = new Intent(mContext, CrossUserService.class);
assertTrue(bind(serviceIntent, serviceConnection, targetUserHandle));
IInterface service = queue.poll(5, TimeUnit.SECONDS);
assertNotNull("binding to the target service timed out", service);
try {
if (NOT_IN_MAIN_THREAD_POISON_PILL.equals(service)) {
fail("onServiceConnected should be called in main thread");
}
ICrossUserService crossUserService = (ICrossUserService) service;
assertEquals(expected, callable.call(crossUserService));
} finally {
mContext.unbindService(serviceConnection);
}
}
private boolean bind(Intent serviceIntent, ServiceConnection serviceConnection,
UserHandle userHandle) {
return mDpm.bindDeviceAdminServiceAsUser(AdminReceiver.getComponentName(mContext),
serviceIntent, serviceConnection, Context.BIND_AUTO_CREATE, userHandle);
}
interface CrossUserCallable<T> {
T call(ICrossUserService service) throws RemoteException;
}
}