TestCarContext.java
/*
* Copyright 2020 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 androidx.car.app.testing;
import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
import static java.util.Objects.requireNonNull;
import android.content.Context;
import android.content.Intent;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
import androidx.car.app.CarContext;
import androidx.car.app.HostDispatcher;
import androidx.car.app.ICarHost;
import androidx.car.app.IStartCarApp;
import androidx.car.app.OnRequestPermissionsListener;
import androidx.car.app.managers.Manager;
import androidx.car.app.testing.navigation.TestNavigationManager;
import androidx.car.app.utils.CollectionUtils;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executor;
/**
* The {@link CarContext} that is used for testing.
*
* <p>This class will return the test version of car services for tracking calls during testing.
*
* <p>It also allows retrieving the car services already cast to the testing class by calling {@link
* #getCarService} with the class name of the test services:
*
* <pre>{@code testCarContext.getCarService(TestAppManager.class)}</pre>
*
* <pre>{@code testCarContext.getCarService(TestNavigationManager.class)}</pre>
*
* <pre>{@code testCarContext.getCarService(TestScreenManager.class)}</pre>
*
* <p>Allows retrieving all {@link Intent}s sent via {@link CarContext#startCarApp(Intent)}.
*/
public class TestCarContext extends CarContext {
private final Map<String, Object> mOverriddenService = new HashMap<>();
private final IStartCarApp mStartCarApp = new StartCarAppStub();
private final FakeHost mFakeHost;
private final TestLifecycleOwner mTestLifecycleOwner;
private final TestAppManager mTestAppManager;
private final TestNavigationManager mTestNavigationManager;
private final TestScreenManager mTestScreenManager;
final List<Intent> mStartCarAppIntents = new ArrayList<>();
@Nullable
private PermissionRequestInfo mLastPermissionRequestInfo = null;
private boolean mHasCalledFinishCarApp;
/** Resets the values tracked by this {@link TestCarContext}. */
public void reset() {
mStartCarAppIntents.clear();
}
@NonNull
@Override
public <T> T getCarService(@NonNull Class<T> serviceClass) {
String serviceName;
if (serviceClass.isInstance(mTestAppManager)) {
serviceName = APP_SERVICE;
} else if (serviceClass.isInstance(mTestNavigationManager)) {
serviceName = NAVIGATION_SERVICE;
} else if (serviceClass.isInstance(mTestScreenManager)) {
serviceName = SCREEN_SERVICE;
} else {
serviceName = getCarServiceName(serviceClass);
}
return requireNonNull(serviceClass.cast(getCarService(serviceName)));
}
@Override
@NonNull
public Object getCarService(@NonNull String name) {
Object service = mOverriddenService.get(name);
if (service != null) {
return service;
}
switch (name) {
case CarContext.APP_SERVICE:
return mTestAppManager;
case CarContext.NAVIGATION_SERVICE:
return mTestNavigationManager;
case CarContext.SCREEN_SERVICE:
return mTestScreenManager;
default:
// Fall out
}
return super.getCarService(name);
}
@Override
public void startCarApp(@NonNull Intent intent) {
mStartCarAppIntents.add(intent);
}
@Override
public void finishCarApp() {
mHasCalledFinishCarApp = true;
}
@Override
public void requestPermissions(@NonNull List<String> permissions, @NonNull Executor executor,
@NonNull OnRequestPermissionsListener listener) {
mLastPermissionRequestInfo = new PermissionRequestInfo(requireNonNull(permissions),
requireNonNull(listener));
super.requestPermissions(permissions, executor, listener);
}
/**
* Creates a {@link TestCarContext} to use for testing.
*
* @throws NullPointerException if {@code testContext} is null
*/
@NonNull
public static TestCarContext createCarContext(@NonNull Context testContext) {
requireNonNull(testContext);
TestCarContext carContext = new TestCarContext(new TestLifecycleOwner(),
new HostDispatcher());
carContext.attachBaseContext(testContext);
carContext.setCarHost(carContext.mFakeHost.getCarHost());
return carContext;
}
/**
* Returns all {@link Intent}s sent via {@link CarContext#startCarApp(Intent)}.
*
* <p>The {@link Intent}s are stored in the order of when they were sent, where the first
* intent in the list, is the first intent sent.
*
* <p>The results will be stored until {@link #reset} is called.
*/
@NonNull
public List<Intent> getStartCarAppIntents() {
return CollectionUtils.unmodifiableCopy(mStartCarAppIntents);
}
/**
* Returns a {@link PermissionRequestInfo} including the information with the last call made to
* {@link CarContext#requestPermissions}, or {@code null} if no call was made.
*/
@Nullable
public PermissionRequestInfo getLastPermissionRequestInfo() {
return mLastPermissionRequestInfo;
}
/** Verifies if {@link CarContext#finishCarApp} has been called. */
public boolean hasCalledFinishCarApp() {
return mHasCalledFinishCarApp;
}
/**
* Retrieve the {@link FakeHost} being used.
*/
@NonNull
public FakeHost getFakeHost() {
return mFakeHost;
}
/**
* Sets the {@code service} as the service instance for the given {@code serviceClass}.
*
* <p>This can be used to mock a car service.
*
* <p>Internal use only.
*
* @throws NullPointerException if either {@code serviceClass} or {@code service} are {@code
* null}
* @hide
*/
@RestrictTo(LIBRARY_GROUP)
public void overrideCarService(@NonNull Class<? extends Manager> serviceClass,
@NonNull Object service) {
requireNonNull(service);
requireNonNull(serviceClass);
String serviceName = getCarServiceName(serviceClass);
mOverriddenService.put(serviceName, service);
}
/**
* Returns the {@link TestLifecycleOwner} that is used for this CarContext.
*
* @hide
*/
@RestrictTo(LIBRARY_GROUP)
@NonNull
public TestLifecycleOwner getLifecycleOwner() {
return mTestLifecycleOwner;
}
/**
* Returns the {@link IStartCarApp} instance that is being used by this CarContext.
*
* @hide
*/
@RestrictTo(LIBRARY_GROUP)
@NonNull
public IStartCarApp getStartCarAppStub() {
return mStartCarApp;
}
ICarHost getCarHostStub() {
return mFakeHost.getCarHost();
}
/** Testing version of the start car app binder for notifications. */
class StartCarAppStub extends IStartCarApp.Stub {
@Override
public void startCarApp(Intent intent) {
mStartCarAppIntents.add(intent);
}
}
/**
* A representation of a permission request including the permissions that were requested as
* well as the callback provided.
*/
public static class PermissionRequestInfo {
private final List<String> mPermissionsRequested;
private final OnRequestPermissionsListener mListener;
@SuppressWarnings("ExecutorRegistration")
PermissionRequestInfo(List<String> permissionsRequested,
OnRequestPermissionsListener callback) {
mPermissionsRequested = requireNonNull(permissionsRequested);
mListener = requireNonNull(callback);
}
/**
* Returns the listener that was provided in the permission request.
*/
@NonNull
public OnRequestPermissionsListener getListener() {
return mListener;
}
/**
* Returns the permissions that were requested.
*/
@NonNull
public List<String> getPermissionsRequested() {
return mPermissionsRequested;
}
}
private TestCarContext(TestLifecycleOwner testLifecycleOwner, HostDispatcher hostDispatcher) {
super(testLifecycleOwner.mRegistry, hostDispatcher);
this.mFakeHost = new FakeHost(this);
this.mTestLifecycleOwner = testLifecycleOwner;
this.mTestAppManager = new TestAppManager(this, hostDispatcher);
this.mTestNavigationManager = new TestNavigationManager(this, hostDispatcher);
this.mTestScreenManager = new TestScreenManager(this);
}
}