blob: f5124a6d7f187b633a22133f2e192752f286ca2e [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.server.supplementalprocess;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertThrows;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.UserHandle;
import android.supplementalprocess.IRemoteCodeCallback;
import android.supplementalprocess.SupplementalProcessManager;
import android.util.ArrayMap;
import android.view.SurfaceControlViewHost;
import androidx.test.InstrumentationRegistry;
import com.android.supplemental.process.ISupplementalProcessManagerToSupplementalProcessCallback;
import com.android.supplemental.process.ISupplementalProcessService;
import com.android.supplemental.process.ISupplementalProcessToSupplementalProcessManagerCallback;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
/**
* Unit tests for {@link SupplementalProcessManagerService}.
*/
public class SupplementalProcessManagerServiceUnitTest {
private SupplementalProcessManagerService mService;
private FakeSupplementalProcessService mSupplementalProcessService;
private static final String CODE_PROVIDER_PACKAGE = "com.android.codeprovider";
@Before
public void setup() {
Context context = InstrumentationRegistry.getContext();
mSupplementalProcessService = new FakeSupplementalProcessService();
SupplementalProcessServiceProvider provider =
new FakeSupplementalProcessProvider(mSupplementalProcessService);
mService = new SupplementalProcessManagerService(context, provider);
}
@Test
public void testLoadCodeIsSuccessful() throws Exception {
FakeInitCodeCallback callback = new FakeInitCodeCallback();
mService.loadCode(CODE_PROVIDER_PACKAGE, "123", new Bundle(), callback);
// Assume SupplementalProcess loads successfully
mSupplementalProcessService.sendLoadCodeSuccessful();
assertThat(callback.isLoadCodeSuccessful()).isTrue();
}
@Test
public void testLoadCodePackageDoesNotExist() throws Exception {
FakeInitCodeCallback callback = new FakeInitCodeCallback();
mService.loadCode("does.not.exist", "1", new Bundle(), callback);
// Verify loading failed
assertThat(callback.isLoadCodeSuccessful()).isFalse();
assertThat(callback.getLoadCodeErrorCode())
.isEqualTo(SupplementalProcessManager.LOAD_CODE_NOT_FOUND);
assertThat(callback.getLoadCodeErrorMsg()).contains("not found for loading");
}
@Test
public void testLoadCode_errorFromSupplementalProcess() throws Exception {
FakeInitCodeCallback callback = new FakeInitCodeCallback();
mService.loadCode(CODE_PROVIDER_PACKAGE, "123", new Bundle(), callback);
mSupplementalProcessService.sendLoadCodeError();
// Verify loading failed
assertThat(callback.isLoadCodeSuccessful()).isFalse();
assertThat(callback.getLoadCodeErrorCode()).isEqualTo(
SupplementalProcessManager.LOAD_CODE_INTERNAL_ERROR);
}
@Test
public void testLoadCode_successOnFirstLoad_errorOnLoadAgain() throws Exception {
// Load it once
{
FakeInitCodeCallback callback = new FakeInitCodeCallback();
mService.loadCode(CODE_PROVIDER_PACKAGE, "123", new Bundle(), callback);
// Assume SupplementalProcess loads successfully
mSupplementalProcessService.sendLoadCodeSuccessful();
assertThat(callback.isLoadCodeSuccessful()).isTrue();
}
// Load it again
{
FakeInitCodeCallback callback = new FakeInitCodeCallback();
mService.loadCode(CODE_PROVIDER_PACKAGE, "123", new Bundle(), callback);
// Verify loading failed
assertThat(callback.isLoadCodeSuccessful()).isFalse();
assertThat(callback.getLoadCodeErrorCode()).isEqualTo(
SupplementalProcessManager.LOAD_CODE_ALREADY_LOADED);
assertThat(callback.getLoadCodeErrorMsg()).contains("has been loaded already");
}
}
@Test
public void testLoadCode_errorOnFirstLoad_canBeLoadedAgain() throws Exception {
// Load code, but make it fail
{
FakeInitCodeCallback callback = new FakeInitCodeCallback();
mService.loadCode(CODE_PROVIDER_PACKAGE, "123", new Bundle(), callback);
// Assume SupplementalProcess load fails
mSupplementalProcessService.sendLoadCodeError();
assertThat(callback.isLoadCodeSuccessful()).isFalse();
}
// Caller should be able to retry loading the code
{
FakeInitCodeCallback callback = new FakeInitCodeCallback();
mService.loadCode(CODE_PROVIDER_PACKAGE, "123", new Bundle(), callback);
// Assume SupplementalProcess loads successfully
mSupplementalProcessService.sendLoadCodeSuccessful();
assertThat(callback.isLoadCodeSuccessful()).isTrue();
}
}
@Test
public void testRequestSurfacePackageCodeNotLoaded() throws Exception {
// Trying to request package without using proper codeToken should fail
SecurityException thrown = assertThrows(
SecurityException.class,
() -> mService.requestSurfacePackage(new Binder(), new Binder(),
0, new Bundle())
);
assertThat(thrown).hasMessageThat().contains("codeToken is invalid");
}
@Test
public void testRequestSurfacePackage() throws Exception {
// 1. We first need to collect a proper codeToken by calling loadCode
FakeInitCodeCallback callback = new FakeInitCodeCallback();
mService.loadCode(CODE_PROVIDER_PACKAGE, "123", new Bundle(), callback);
mSupplementalProcessService.sendLoadCodeSuccessful();
assertThat(callback.isLoadCodeSuccessful()).isTrue();
// Verify codeToken is not null
IBinder codeToken = callback.getCodeToken();
assertThat(codeToken).isNotNull();
// 2. Call request package with the retrieved codeToken
mService.requestSurfacePackage(codeToken, new Binder(), 0, new Bundle());
mSupplementalProcessService.sendSurfacePackageReady();
assertThat(callback.isRequestSurfacePackageSuccessful()).isTrue();
}
@Test
public void testRequestSurfacePackageFailedAfterAppDied() throws Exception {
FakeInitCodeCallback callback = spy(new FakeInitCodeCallback());
doReturn(mock(Binder.class)).when(callback).asBinder();
ArgumentCaptor<IBinder.DeathRecipient> deathRecipient = ArgumentCaptor
.forClass(IBinder.DeathRecipient.class);
mService.loadCode(CODE_PROVIDER_PACKAGE, "123", new Bundle(), callback);
mSupplementalProcessService.sendLoadCodeSuccessful();
assertThat(callback.isLoadCodeSuccessful()).isTrue();
verify(callback.asBinder()).linkToDeath(deathRecipient.capture(), eq(0));
// App Died
deathRecipient.getValue().binderDied();
// After App Died
SecurityException thrown = assertThrows(
SecurityException.class,
() -> mService.requestSurfacePackage(callback.getCodeToken(), new Binder(),
0, new Bundle())
);
assertThat(thrown).hasMessageThat().contains("codeToken is invalid");
}
@Test
public void testSurfacePackageError() throws Exception {
FakeInitCodeCallback callback = new FakeInitCodeCallback();
mService.loadCode(CODE_PROVIDER_PACKAGE, "123", new Bundle(), callback);
// Assume SurfacePackage encounters an error.
mSupplementalProcessService.sendSurfacePackageError(
SupplementalProcessManager.SURFACE_PACKAGE_INTERNAL_ERROR, "bad surface");
assertThat(callback.getSurfacePackageErrorMsg()).contains("bad surface");
assertThat(callback.getSurfacePackageErrorCode())
.isEqualTo(SupplementalProcessManager.SURFACE_PACKAGE_INTERNAL_ERROR);
}
@Test(expected = SecurityException.class)
public void testDumpWithoutPermission() {
mService.dump(new FileDescriptor(), new PrintWriter(new StringWriter()), new String[0]);
}
// ManagerToAppCallback
private static class FakeInitCodeCallback extends IRemoteCodeCallback.Stub {
private final CountDownLatch mLoadCodeLatch = new CountDownLatch(1);
private final CountDownLatch mSurfacePackageLatch = new CountDownLatch(1);
private boolean mLoadCodeSuccess;
private boolean mSurfacePackageSuccess;
private int mErrorCode;
private String mErrorMsg;
private IBinder mCodeToken;
@Override
public void onLoadCodeSuccess(IBinder codeToken, Bundle params) {
mCodeToken = codeToken;
mLoadCodeSuccess = true;
mLoadCodeLatch.countDown();
}
@Override
public void onLoadCodeFailure(int errorCode, String errorMsg) {
mLoadCodeSuccess = false;
mErrorCode = errorCode;
mErrorMsg = errorMsg;
mLoadCodeLatch.countDown();
}
@Override
public void onSurfacePackageError(int errorCode, String errorMsg) {
mSurfacePackageSuccess = false;
mErrorCode = errorCode;
mErrorMsg = errorMsg;
mSurfacePackageLatch.countDown();
}
@Override
public void onSurfacePackageReady(SurfaceControlViewHost.SurfacePackage surfacePackage,
int surfacePackageId, Bundle params) {
mSurfacePackageSuccess = true;
mSurfacePackageLatch.countDown();
}
void waitForLatch(CountDownLatch latch) {
try {
// Wait for callback to be called
if (!latch.await(2, TimeUnit.SECONDS)) {
throw new IllegalStateException("Callback not called within 2 seconds");
}
} catch (InterruptedException e) {
throw new IllegalStateException(
"Interrupted while waiting on callback: " + e.getMessage());
}
}
boolean isLoadCodeSuccessful() throws InterruptedException {
waitForLatch(mLoadCodeLatch);
return mLoadCodeSuccess;
}
int getLoadCodeErrorCode() {
waitForLatch(mLoadCodeLatch);
assertThat(mLoadCodeSuccess).isFalse();
return mErrorCode;
}
String getLoadCodeErrorMsg() {
waitForLatch(mLoadCodeLatch);
assertThat(mLoadCodeSuccess).isFalse();
return mErrorMsg;
}
int getSurfacePackageErrorCode() {
waitForLatch(mSurfacePackageLatch);
assertThat(mSurfacePackageSuccess).isFalse();
return mErrorCode;
}
String getSurfacePackageErrorMsg() {
waitForLatch(mSurfacePackageLatch);
assertThat(mSurfacePackageSuccess).isFalse();
return mErrorMsg;
}
IBinder getCodeToken() {
waitForLatch(mLoadCodeLatch);
assertThat(mLoadCodeSuccess).isTrue();
return mCodeToken;
}
boolean isRequestSurfacePackageSuccessful() throws InterruptedException {
waitForLatch(mSurfacePackageLatch);
return mSurfacePackageSuccess;
}
}
/**
* Fake service provider that returns local instance of {@link FakeSupplementalProcessService}
*/
private static class FakeSupplementalProcessProvider
implements SupplementalProcessServiceProvider {
private final ISupplementalProcessService mSupplementalProcessService;
private final ArrayMap<UserHandle, ISupplementalProcessService> mService = new ArrayMap<>();
FakeSupplementalProcessProvider(ISupplementalProcessService service) {
mSupplementalProcessService = service;
}
@Override
public ISupplementalProcessService bindService(int callingUid, IBinder appBinder) {
final UserHandle callingUser = UserHandle.getUserHandleForUid(callingUid);
if (mService.containsKey(callingUser)) {
return mService.get(callingUser);
}
mService.put(callingUser, mSupplementalProcessService);
return mService.get(callingUser);
}
@Override
public boolean isServiceBound(int callingUid) {
final UserHandle callingUser = UserHandle.getUserHandleForUid(callingUid);
return mService.containsKey(callingUser);
}
}
private static class FakeSupplementalProcessService extends ISupplementalProcessService.Stub {
private ISupplementalProcessToSupplementalProcessManagerCallback mCodeToManagerCallback;
private final ISupplementalProcessManagerToSupplementalProcessCallback
mManagerToCodeCallback;
boolean mSurfacePackageRequested = false;
FakeSupplementalProcessService() {
mManagerToCodeCallback = new FakeManagerToCodeCallback();
}
@Override
public void loadCode(IBinder codeToken, ApplicationInfo info, String codeProviderClassName,
Bundle params, ISupplementalProcessToSupplementalProcessManagerCallback callback) {
mCodeToManagerCallback = callback;
}
void sendLoadCodeSuccessful() throws RemoteException {
mCodeToManagerCallback.onLoadCodeSuccess(new Bundle(), mManagerToCodeCallback);
}
void sendLoadCodeError() throws RemoteException {
mCodeToManagerCallback.onLoadCodeError(
SupplementalProcessManager.LOAD_CODE_INTERNAL_ERROR, "Internal error");
}
void sendSurfacePackageReady() throws RemoteException {
if (mSurfacePackageRequested) {
mCodeToManagerCallback.onSurfacePackageReady(
/*hostToken=*/null, /*displayId=*/0, /*params=*/null);
}
}
void sendSurfacePackageError(int errorCode, String errorMsg) throws RemoteException {
mCodeToManagerCallback.onSurfacePackageError(errorCode, errorMsg);
}
private class FakeManagerToCodeCallback extends
ISupplementalProcessManagerToSupplementalProcessCallback.Stub {
@Override
public void onSurfacePackageRequested(IBinder hostToken,
int displayId, Bundle extraParams) {
mSurfacePackageRequested = true;
}
}
}
}