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
diff --git a/experimental/experimental_api/src/android/car/experimental/DriverAwarenessSupplierService.java b/experimental/experimental_api/src/android/car/experimental/DriverAwarenessSupplierService.java
index df48d10..c904428 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.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 @@
      * 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 e6848a1..83b64d1 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.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 @@
 
     private final Context mContext;
     private final ITimeSource mTimeSource;
+    private final Looper mLooper;
 
     /**
      * Create an instance of {@link DriverDistractionExperimentalFeatureService}.
@@ -201,6 +203,15 @@
             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 @@
         mClientDispatchHandlerThread = new HandlerThread(TAG);
         mClientDispatchHandlerThread.start();
         mClientDispatchHandler = new Handler(mClientDispatchHandlerThread.getLooper());
+        mLooper = looper;
     }
 
     @Override
@@ -220,7 +232,7 @@
         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 7fbacbf..aeb31e7 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 @@
 
     /* 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 @@
     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 @@
     @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 4ed17cd..fa8bc34 100644
--- a/experimental/service/src/com/android/experimentalcar/TouchDriverAwarenessSupplier.java
+++ b/experimental/service/src/com/android/experimentalcar/TouchDriverAwarenessSupplier.java
@@ -84,9 +84,9 @@
     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 bcdac80..4cc23882 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.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.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 @@
     @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;
@@ -120,6 +164,58 @@
     }
 
     @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 {
         mService.setDriverAwarenessSuppliers(Arrays.asList(
@@ -443,4 +539,51 @@
                         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<Intent> 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 c1f7581..042c40d 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.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 @@
     private float mDecayRate;
     private FakeTimeSource mTimeSource;
 
+    @Rule
+    public final ServiceTestRule serviceRule = new ServiceTestRule();
+
     @Before
     public void setUp() throws Exception {
         mSpyContext = spy(InstrumentationRegistry.getInstrumentation().getTargetContext());
@@ -63,6 +73,17 @@
     }
 
     @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().
         mGazeSupplier.onReady();