From 5414f62f72af433b9e86e8aef9b046c0bf7af291 Mon Sep 17 00:00:00 2001 From: Jordan Jozwiak Date: Wed, 26 Feb 2020 15:57:42 -0800 Subject: Fix gaze service creation through intent The zero arg constructor is necessary to so that bindServiceAsUser can properly start the service. The GazeSupplier can't assume that it will get a Context from a constructor, so I made some changes related to that. The additional tests are related to this instantion: - GazeDriverAwarenessSupplier test validates that the service can be created from an intent, which requires a zero arg constructor. - Driver Distraction Service test that validates that the strings specified in the config are all services that can be bound to with the proper binder type. - Driver Distraction Service test that validates the init logic, and tests that the service attempts to bind to the services declared in the config. There's no end-to-end test here. I'd like to have one, but that may be better suited for a CTS test. Bug: 150327895 Test: atest DriverDistractionExperimentalFeatureServiceTest Change-Id: I5a81ee3e3f7659a042656575d31de3d9a299abb1 --- .../DriverAwarenessSupplierService.java | 4 +- ...riverDistractionExperimentalFeatureService.java | 14 +- .../GazeDriverAwarenessSupplier.java | 34 +++-- .../TouchDriverAwarenessSupplier.java | 4 +- ...rDistractionExperimentalFeatureServiceTest.java | 143 +++++++++++++++++++++ .../GazeDriverAwarenessSupplierTest.java | 21 +++ 6 files changed, 207 insertions(+), 13 deletions(-) (limited to 'experimental') diff --git a/experimental/experimental_api/src/android/car/experimental/DriverAwarenessSupplierService.java b/experimental/experimental_api/src/android/car/experimental/DriverAwarenessSupplierService.java index df48d101ca..c904428374 100644 --- a/experimental/experimental_api/src/android/car/experimental/DriverAwarenessSupplierService.java +++ b/experimental/experimental_api/src/android/car/experimental/DriverAwarenessSupplierService.java @@ -24,6 +24,7 @@ import android.os.RemoteException; import android.util.Log; import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; /** * The supplier for providing a stream of the driver's current situational awareness. @@ -130,7 +131,8 @@ public abstract class DriverAwarenessSupplierService extends Service { * The binder between this service and * {@link com.android.experimentalcar.DriverDistractionExperimentalFeatureService}. */ - private class SupplierBinder extends IDriverAwarenessSupplier.Stub { + @VisibleForTesting + public class SupplierBinder extends IDriverAwarenessSupplier.Stub { @Override public void onReady() { diff --git a/experimental/service/src/com/android/experimentalcar/DriverDistractionExperimentalFeatureService.java b/experimental/service/src/com/android/experimentalcar/DriverDistractionExperimentalFeatureService.java index e6848a174f..83b64d1df9 100644 --- a/experimental/service/src/com/android/experimentalcar/DriverDistractionExperimentalFeatureService.java +++ b/experimental/service/src/com/android/experimentalcar/DriverDistractionExperimentalFeatureService.java @@ -40,6 +40,7 @@ import android.content.ServiceConnection; import android.os.Handler; import android.os.HandlerThread; import android.os.IBinder; +import android.os.Looper; import android.os.RemoteCallbackList; import android.os.RemoteException; import android.os.UserHandle; @@ -189,6 +190,7 @@ public final class DriverDistractionExperimentalFeatureService extends private final Context mContext; private final ITimeSource mTimeSource; + private final Looper mLooper; /** * Create an instance of {@link DriverDistractionExperimentalFeatureService}. @@ -201,6 +203,15 @@ public final class DriverDistractionExperimentalFeatureService extends Context context, ITimeSource timeSource, ITimer timer) { + this(context, timeSource, timer, Looper.myLooper()); + } + + @VisibleForTesting + DriverDistractionExperimentalFeatureService( + Context context, + ITimeSource timeSource, + ITimer timer, + Looper looper) { mContext = context; mTimeSource = timeSource; mExpiredDriverAwarenessTimer = timer; @@ -211,6 +222,7 @@ public final class DriverDistractionExperimentalFeatureService extends mClientDispatchHandlerThread = new HandlerThread(TAG); mClientDispatchHandlerThread.start(); mClientDispatchHandler = new Handler(mClientDispatchHandlerThread.getLooper()); + mLooper = looper; } @Override @@ -220,7 +232,7 @@ public final class DriverDistractionExperimentalFeatureService extends ComponentName touchComponent = new ComponentName(mContext, TouchDriverAwarenessSupplier.class); TouchDriverAwarenessSupplier touchSupplier = new TouchDriverAwarenessSupplier(mContext, - new DriverAwarenessSupplierCallback(touchComponent)); + new DriverAwarenessSupplierCallback(touchComponent), mLooper); addDriverAwarenessSupplier(touchComponent, touchSupplier, /* priority= */ 0); touchSupplier.onReady(); diff --git a/experimental/service/src/com/android/experimentalcar/GazeDriverAwarenessSupplier.java b/experimental/service/src/com/android/experimentalcar/GazeDriverAwarenessSupplier.java index 7fbacbf5de..aeb31e7ef8 100644 --- a/experimental/service/src/com/android/experimentalcar/GazeDriverAwarenessSupplier.java +++ b/experimental/service/src/com/android/experimentalcar/GazeDriverAwarenessSupplier.java @@ -17,6 +17,7 @@ package com.android.experimentalcar; import android.annotation.NonNull; +import android.annotation.Nullable; import android.car.Car; import android.car.experimental.DriverAwarenessEvent; import android.car.experimental.DriverAwarenessSupplierService; @@ -44,12 +45,17 @@ public class GazeDriverAwarenessSupplier extends DriverAwarenessSupplierService /* Maximum allowable staleness before gaze data should be considered unreliable for attention * monitoring, in milliseconds. */ - private static final long MAX_STALENESS_MILLIS = 500; + @VisibleForTesting + static final long MAX_STALENESS_MILLIS = 500; - private final Context mContext; private final Object mLock = new Object(); private final ITimeSource mTimeSource; - private final GazeAttentionProcessor.Configuration mConfiguration; + + @GuardedBy("mLock") + private GazeAttentionProcessor.Configuration mConfiguration; + + @Nullable + private Context mContext; @GuardedBy("mLock") private Car mCar; @@ -58,18 +64,27 @@ public class GazeDriverAwarenessSupplier extends DriverAwarenessSupplierService private OccupantAwarenessManager mOasManager; @GuardedBy("mLock") - private final GazeAttentionProcessor mProcessor; + private GazeAttentionProcessor mProcessor; - public GazeDriverAwarenessSupplier(Context context) { - this(context, new SystemTimeSource()); + /** + * Empty constructor allows system service creation. + */ + public GazeDriverAwarenessSupplier() { + this(/* context= */ null, new SystemTimeSource()); } @VisibleForTesting GazeDriverAwarenessSupplier(Context context, ITimeSource timeSource) { mContext = context; mTimeSource = timeSource; - mConfiguration = loadConfiguration(); - mProcessor = new GazeAttentionProcessor(mConfiguration); + } + + @Override + public void onCreate() { + super.onCreate(); + if (mContext == null) { + mContext = this; + } } /** @@ -90,8 +105,9 @@ public class GazeDriverAwarenessSupplier extends DriverAwarenessSupplierService @Override public void onReady() { synchronized (mLock) { + mConfiguration = loadConfiguration(); + mProcessor = new GazeAttentionProcessor(mConfiguration); mCar = Car.createCar(mContext); - if (mCar != null) { if (mOasManager == null && mCar.isFeatureEnabled(Car.OCCUPANT_AWARENESS_SERVICE)) { mOasManager = diff --git a/experimental/service/src/com/android/experimentalcar/TouchDriverAwarenessSupplier.java b/experimental/service/src/com/android/experimentalcar/TouchDriverAwarenessSupplier.java index 4ed17cda86..fa8bc34c70 100644 --- a/experimental/service/src/com/android/experimentalcar/TouchDriverAwarenessSupplier.java +++ b/experimental/service/src/com/android/experimentalcar/TouchDriverAwarenessSupplier.java @@ -84,9 +84,9 @@ public class TouchDriverAwarenessSupplier extends IDriverAwarenessSupplier.Stub private InputEventReceiver mInputEventReceiver; TouchDriverAwarenessSupplier(Context context, - IDriverAwarenessSupplierCallback driverAwarenessSupplierCallback) { + IDriverAwarenessSupplierCallback driverAwarenessSupplierCallback, Looper looper) { this(context, driverAwarenessSupplierCallback, Executors.newScheduledThreadPool(1), - Looper.myLooper(), new SystemTimeSource()); + looper, new SystemTimeSource()); } @VisibleForTesting diff --git a/experimental/tests/experimentalcarservice_unit_test/src/com/android/experimentalcar/DriverDistractionExperimentalFeatureServiceTest.java b/experimental/tests/experimentalcarservice_unit_test/src/com/android/experimentalcar/DriverDistractionExperimentalFeatureServiceTest.java index bcdac80f42..4cc23882fd 100644 --- a/experimental/tests/experimentalcarservice_unit_test/src/com/android/experimentalcar/DriverDistractionExperimentalFeatureServiceTest.java +++ b/experimental/tests/experimentalcarservice_unit_test/src/com/android/experimentalcar/DriverDistractionExperimentalFeatureServiceTest.java @@ -20,6 +20,12 @@ import static com.android.experimentalcar.DriverDistractionExperimentalFeatureSe import static com.google.common.truth.Truth.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + import android.car.Car; import android.car.VehiclePropertyIds; import android.car.experimental.CarDriverDistractionManager; @@ -30,25 +36,51 @@ import android.car.experimental.DriverDistractionChangeEvent; import android.car.experimental.IDriverAwarenessSupplier; import android.car.experimental.IDriverAwarenessSupplierCallback; import android.car.hardware.CarPropertyValue; +import android.content.ComponentName; import android.content.Context; +import android.content.ContextWrapper; +import android.content.Intent; +import android.content.ServiceConnection; +import android.content.res.Resources; +import android.hardware.input.InputManager; +import android.os.Handler; +import android.os.IBinder; import android.os.Looper; import android.os.RemoteException; +import android.os.UserHandle; import android.util.Pair; +import android.view.InputChannel; +import android.view.InputMonitor; + +import androidx.test.InstrumentationRegistry; +import androidx.test.core.app.ApplicationProvider; +import androidx.test.rule.ServiceTestRule; + +import com.android.internal.annotations.GuardedBy; import org.junit.After; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; +import java.util.ArrayList; import java.util.Arrays; +import java.util.List; import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; @RunWith(MockitoJUnitRunner.class) public class DriverDistractionExperimentalFeatureServiceTest { + private static final String TAG = "Car.DriverDistractionServiceTest"; + + private static final String SERVICE_BIND_GAZE_SUPPLIER = + "com.android.experimentalcar/.GazeDriverAwarenessSupplier"; + private static final long INITIAL_TIME = 1000L; private static final long PREFERRED_SUPPLIER_STALENESS = 10L; @@ -98,6 +130,18 @@ public class DriverDistractionExperimentalFeatureServiceTest { @Mock private Context mContext; + @Mock + private InputManager mInputManager; + + @Mock + private InputMonitor mInputMonitor; + + @Mock + private IBinder mIBinder; + + @Rule + public final ServiceTestRule serviceRule = new ServiceTestRule(); + private DriverDistractionExperimentalFeatureService mService; private CarDriverDistractionManager mManager; private FakeTimeSource mTimeSource; @@ -119,6 +163,58 @@ public class DriverDistractionExperimentalFeatureServiceTest { resetChangeEventWait(); } + @Test + public void testConfig_servicesCanBeBound() throws Exception { + Context realContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); + + // Get the actual suppliers defined in the config + String[] preferredDriverAwarenessSuppliers = realContext.getResources().getStringArray( + R.array.preferredDriverAwarenessSuppliers); + + for (String supplierStringName : preferredDriverAwarenessSuppliers) { + ComponentName supplierComponent = ComponentName.unflattenFromString(supplierStringName); + Class supplerClass = Class.forName(supplierComponent.getClassName()); + Intent serviceIntent = + new Intent(ApplicationProvider.getApplicationContext(), supplerClass); + + // Bind the service and grab a reference to the binder. + IBinder binder = serviceRule.bindService(serviceIntent); + + assertThat(binder instanceof DriverAwarenessSupplierService.SupplierBinder).isTrue(); + } + } + + @Test + public void testInit_bindsToServicesInXmlConfig() throws Exception { + Context spyContext = spy(InstrumentationRegistry.getInstrumentation().getTargetContext()); + + // Mock the config to load a gaze supplier + Resources spyResources = spy(spyContext.getResources()); + doReturn(spyResources).when(spyContext).getResources(); + doReturn(new String[]{SERVICE_BIND_GAZE_SUPPLIER}).when(spyResources).getStringArray( + anyInt()); + + // Mock the InputManager that will be used by TouchDriverAwarenessSupplier + doReturn(mInputManager).when(spyContext).getSystemService(Context.INPUT_SERVICE); + when(mInputManager.monitorGestureInput(any(), anyInt())).thenReturn(mInputMonitor); + // InputChannel cannot be mocked because it passes to InputEventReceiver. + final InputChannel[] inputChannels = InputChannel.openInputChannelPair(TAG); + inputChannels[0].dispose(); + when(mInputMonitor.getInputChannel()).thenReturn(inputChannels[1]); + + // Create a special context that allows binders to succeed and keeps track of them. Doesn't + // actually start the intents / services. + ServiceLauncherContext serviceLauncherContext = new ServiceLauncherContext(spyContext); + mService = new DriverDistractionExperimentalFeatureService(serviceLauncherContext, + mTimeSource, mTimer, spyContext.getMainLooper()); + mService.init(); + + serviceLauncherContext.assertBoundService(SERVICE_BIND_GAZE_SUPPLIER); + + serviceLauncherContext.reset(); + inputChannels[0].dispose(); + } + @Test public void testHandleDriverAwarenessEvent_updatesCurrentValue_withLatestEvent() throws Exception { @@ -443,4 +539,51 @@ public class DriverDistractionExperimentalFeatureServiceTest { supplier, maxStaleness)); } + + + /** Overrides framework behavior to succeed on binding/starting processes. */ + public class ServiceLauncherContext extends ContextWrapper { + private final Object mLock = new Object(); + + @GuardedBy("mLock") + private List mBoundIntents = new ArrayList<>(); + + ServiceLauncherContext(Context base) { + super(base); + } + + @Override + public boolean bindServiceAsUser(Intent service, ServiceConnection conn, int flags, + Handler handler, UserHandle user) { + synchronized (mLock) { + mBoundIntents.add(service); + } + conn.onServiceConnected(service.getComponent(), mIBinder); + return true; + } + + @Override + public boolean bindServiceAsUser(Intent service, ServiceConnection conn, + int flags, UserHandle user) { + return bindServiceAsUser(service, conn, flags, null, user); + } + + @Override + public void unbindService(ServiceConnection conn) { + // do nothing + } + + void assertBoundService(String service) { + synchronized (mLock) { + assertThat(mBoundIntents.stream().map(Intent::getComponent).collect( + Collectors.toList())).contains(ComponentName.unflattenFromString(service)); + } + } + + void reset() { + synchronized (mLock) { + mBoundIntents.clear(); + } + } + } } diff --git a/experimental/tests/experimentalcarservice_unit_test/src/com/android/experimentalcar/GazeDriverAwarenessSupplierTest.java b/experimental/tests/experimentalcarservice_unit_test/src/com/android/experimentalcar/GazeDriverAwarenessSupplierTest.java index c1f7581ae8..042c40da37 100644 --- a/experimental/tests/experimentalcarservice_unit_test/src/com/android/experimentalcar/GazeDriverAwarenessSupplierTest.java +++ b/experimental/tests/experimentalcarservice_unit_test/src/com/android/experimentalcar/GazeDriverAwarenessSupplierTest.java @@ -16,6 +16,8 @@ package com.android.experimentalcar; +import static com.google.common.truth.Truth.assertThat; + import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -24,10 +26,15 @@ import android.car.experimental.DriverAwarenessEvent; import android.car.occupantawareness.GazeDetection; import android.car.occupantawareness.OccupantAwarenessDetection; import android.content.Context; +import android.content.Intent; +import android.os.IBinder; +import androidx.test.core.app.ApplicationProvider; import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.rule.ServiceTestRule; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.junit.MockitoJUnitRunner; @@ -45,6 +52,9 @@ public class GazeDriverAwarenessSupplierTest { private float mDecayRate; private FakeTimeSource mTimeSource; + @Rule + public final ServiceTestRule serviceRule = new ServiceTestRule(); + @Before public void setUp() throws Exception { mSpyContext = spy(InstrumentationRegistry.getInstrumentation().getTargetContext()); @@ -62,6 +72,17 @@ public class GazeDriverAwarenessSupplierTest { mGazeSupplier = spy(new GazeDriverAwarenessSupplier(mSpyContext, mTimeSource)); } + @Test + public void testWithBoundService() throws Exception { + Intent serviceIntent = + new Intent(ApplicationProvider.getApplicationContext(), + GazeDriverAwarenessSupplier.class); + + // Bind the service and grab a reference to the binder. + IBinder binder = serviceRule.bindService(serviceIntent); + assertThat(binder instanceof GazeDriverAwarenessSupplier.SupplierBinder).isTrue(); + } + @Test public void testonReady_initialCallbackIsGenerated() throws Exception { // Supplier should return an initial callback after onReady(). -- cgit v1.2.3