Merge "Remove IBinder from CarProjectionManager API"
diff --git a/service/src/com/android/car/vms/VmsClientManager.java b/service/src/com/android/car/vms/VmsClientManager.java
index 0657cba..0cf62e7 100644
--- a/service/src/com/android/car/vms/VmsClientManager.java
+++ b/service/src/com/android/car/vms/VmsClientManager.java
@@ -23,7 +23,6 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.ServiceConnection;
-import android.content.pm.UserInfo;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
@@ -56,10 +55,10 @@
         /**
          * Called when a client connection is established or re-established.
          *
-         * @param clientName String that uniquely identifies the service and user.
-         * @param binder Binder for communicating with the client.
+         * @param clientName    String that uniquely identifies the service and user.
+         * @param clientService The IBinder of the client's communication channel.
          */
-        void onClientConnected(String clientName, IBinder binder);
+        void onClientConnected(String clientName, IBinder clientService);
 
         /**
          * Called when a client connection is terminated.
@@ -114,7 +113,7 @@
     /**
      * Constructor for client managers.
      *
-     * @param context Context to use for registering receivers and binding services.
+     * @param context           Context to use for registering receivers and binding services.
      * @param userManagerHelper User manager for querying current user state.
      */
     public VmsClientManager(Context context, CarUserManagerHelper userManagerHelper) {
@@ -142,10 +141,10 @@
         mContext.unregisterReceiver(mBootCompletedReceiver);
         mContext.unregisterReceiver(mUserSwitchReceiver);
         synchronized (mSystemClients) {
-            unbind(mSystemClients);
+            terminate(mSystemClients);
         }
         synchronized (mCurrentUserClients) {
-            unbind(mCurrentUserClients);
+            terminate(mCurrentUserClients);
         }
     }
 
@@ -194,12 +193,12 @@
     }
 
     private void bindToCurrentUserClients() {
-        UserInfo userInfo = mUserManagerHelper.getCurrentForegroundUserInfo();
+        int currentUserId = mUserManagerHelper.getCurrentForegroundUserId();
         synchronized (mCurrentUserClients) {
-            if (mCurrentUser != userInfo.id) {
-                unbind(mCurrentUserClients);
+            if (mCurrentUser != currentUserId) {
+                terminate(mCurrentUserClients);
             }
-            mCurrentUser = userInfo.id;
+            mCurrentUser = currentUserId;
 
             // To avoid the risk of double-binding, clients running as the system user must only
             // ever be bound in bindToSystemClients().
@@ -212,8 +211,9 @@
             String[] clientNames = mContext.getResources().getStringArray(
                     R.array.vmsPublisherUserClients);
             Log.i(TAG, "Attempting to bind " + clientNames.length + " user client(s)");
+            UserHandle currentUserHandle = UserHandle.of(mCurrentUser);
             for (String clientName : clientNames) {
-                bind(mCurrentUserClients, clientName, userInfo.getUserHandle());
+                bind(mCurrentUserClients, clientName, currentUserHandle);
             }
         }
     }
@@ -244,17 +244,17 @@
         }
     }
 
-    private void unbind(Map<String, ClientConnection> connectionMap) {
+    private void terminate(Map<String, ClientConnection> connectionMap) {
         for (ClientConnection connection : connectionMap.values()) {
-            connection.unbind();
+            connection.terminate();
         }
         connectionMap.clear();
     }
 
-    private void notifyListenersOnClientConnected(String clientName, IBinder binder) {
+    private void notifyListenersOnClientConnected(String clientName, IBinder clientService) {
         synchronized (mListeners) {
             for (ConnectionListener listener : mListeners) {
-                listener.onClientConnected(clientName, binder);
+                listener.onClientConnected(clientName, clientService);
             }
         }
     }
@@ -272,7 +272,8 @@
         private final UserHandle mUser;
         private final String mFullName;
         private boolean mIsBound = false;
-        private IBinder mBinder;
+        private boolean mIsTerminated = false;
+        private IBinder mClientService;
 
         ClientConnection(ComponentName name, UserHandle user) {
             mName = name;
@@ -281,10 +282,12 @@
         }
 
         synchronized boolean bind() {
-            // Ignore if already bound
             if (mIsBound) {
                 return true;
             }
+            if (mIsTerminated) {
+                return false;
+            }
 
             if (DBG) Log.d(TAG, "binding: " + mFullName);
             Intent intent = new Intent();
@@ -307,26 +310,34 @@
                 Log.e(TAG, "While unbinding " + mFullName, t);
             }
             mIsBound = false;
-            if (mBinder != null) {
+            if (mClientService != null) {
                 notifyListenersOnClientDisconnected(mFullName);
             }
-            mBinder = null;
+            mClientService = null;
         }
 
-        void rebind() {
+        synchronized void rebind() {
             unbind();
             if (DBG) {
                 Log.d(TAG,
                         String.format("rebinding %s after %dms", mFullName, mMillisBeforeRebind));
             }
-            mHandler.postDelayed(this::bind, mMillisBeforeRebind);
+            if (!mIsTerminated) {
+                mHandler.postDelayed(this::bind, mMillisBeforeRebind);
+            }
+        }
+
+        synchronized void terminate() {
+            if (DBG) Log.d(TAG, "terminating: " + mFullName);
+            mIsTerminated = true;
+            unbind();
         }
 
         @Override
-        public void onServiceConnected(ComponentName name, IBinder binder) {
+        public void onServiceConnected(ComponentName name, IBinder service) {
             if (DBG) Log.d(TAG, "onServiceConnected: " + mFullName);
-            mBinder = binder;
-            notifyListenersOnClientConnected(mFullName, mBinder);
+            mClientService = service;
+            notifyListenersOnClientConnected(mFullName, mClientService);
         }
 
         @Override
@@ -344,7 +355,7 @@
         @Override
         public void onNullBinding(ComponentName name) {
             if (DBG) Log.d(TAG, "onNullBinding: " + mFullName);
-            unbind();
+            terminate();
         }
 
         @Override
diff --git a/tests/carservice_unit_test/src/com/android/car/vms/VmsClientManagerTest.java b/tests/carservice_unit_test/src/com/android/car/vms/VmsClientManagerTest.java
index a1ff59f..15e7feb 100644
--- a/tests/carservice_unit_test/src/com/android/car/vms/VmsClientManagerTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/vms/VmsClientManagerTest.java
@@ -23,6 +23,7 @@
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.ArgumentMatchers.isNull;
 import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
@@ -37,7 +38,6 @@
 import android.content.IntentFilter;
 import android.content.ServiceConnection;
 import android.content.pm.PackageManager;
-import android.content.pm.UserInfo;
 import android.content.res.Resources;
 import android.os.Binder;
 import android.os.Handler;
@@ -70,7 +70,7 @@
     private Resources mResources;
     @Mock
     private CarUserManagerHelper mUserManager;
-    private UserInfo mUserInfo;
+    private int mUserId;
 
     @Mock
     private VmsClientManager.ConnectionListener mConnectionListener;
@@ -89,16 +89,17 @@
                 5);
         when(mResources.getStringArray(
                 com.android.car.R.array.vmsPublisherSystemClients)).thenReturn(
-                        new String[]{
-                                "com.google.android.apps.vms.test/.VmsSystemClient"
-                        });
+                new String[]{
+                        "com.google.android.apps.vms.test/.VmsSystemClient"
+                });
         when(mResources.getStringArray(
                 com.android.car.R.array.vmsPublisherUserClients)).thenReturn(
-                        new String[]{
-                                "com.google.android.apps.vms.test/.VmsUserClient"
-                        });
-        mUserInfo = new UserInfo(10, "Driver", 0);
-        when(mUserManager.getCurrentForegroundUserInfo()).thenReturn(mUserInfo);
+                new String[]{
+                        "com.google.android.apps.vms.test/.VmsUserClient"
+                });
+
+        mUserId = 10;
+        when(mUserManager.getCurrentForegroundUserId()).thenAnswer((invocation) -> mUserId);
 
         mClientManager = new VmsClientManager(mContext, mUserManager);
         mClientManager.registerConnectionListener(mConnectionListener);
@@ -244,12 +245,12 @@
 
     @Test
     public void testUserSwitchedToSystemUser() {
-        mUserInfo = new UserInfo(UserHandle.USER_SYSTEM, "Owner", 0);
-        when(mUserManager.getCurrentForegroundUserInfo()).thenReturn(mUserInfo);
+        mUserId = UserHandle.USER_SYSTEM;
         notifyUserSwitched();
 
         // System user should not trigger any binding
-        verifyUserBind(0);
+        verifySystemBind(0);
+        verifyNoBind();
     }
 
     @Test
@@ -502,16 +503,14 @@
         resetContext();
         reset(mConnectionListener);
 
-        mUserInfo = new UserInfo(11, "Driver", 0);
-        when(mUserManager.getCurrentForegroundUserInfo()).thenReturn(mUserInfo);
+        mUserId = 11;
         notifyUserSwitched();
 
         verify(mContext).unbindService(connection);
         verify(mConnectionListener).onClientDisconnected(
                 eq("com.google.android.apps.vms.test/com.google.android.apps.vms.test"
                         + ".VmsUserClient U=10"));
-        verifyBind(1, "com.google.android.apps.vms.test/.VmsUserClient",
-                mUserInfo.getUserHandle());
+        verifyUserBind(1);
     }
 
     @Test
@@ -523,8 +522,7 @@
         resetContext();
         reset(mConnectionListener);
 
-        mUserInfo = new UserInfo(UserHandle.USER_SYSTEM, "Owner", 0);
-        when(mUserManager.getCurrentForegroundUserInfo()).thenReturn(mUserInfo);
+        mUserId = UserHandle.USER_SYSTEM;
         notifyUserSwitched();
 
         verify(mContext).unbindService(connection);
@@ -532,8 +530,7 @@
                 eq("com.google.android.apps.vms.test/com.google.android.apps.vms.test"
                         + ".VmsUserClient U=10"));
         // User processes will not be bound for system user
-        verifyBind(0, "com.google.android.apps.vms.test/.VmsUserClient",
-                mUserInfo.getUserHandle());
+        verifyNoBind();
     }
 
     @Test
@@ -543,13 +540,11 @@
         ServiceConnection connection = mConnectionCaptor.getValue();
         resetContext();
 
-        mUserInfo = new UserInfo(11, "Driver", 0);
-        when(mUserManager.getCurrentForegroundUserInfo()).thenReturn(mUserInfo);
+        mUserId = 11;
         notifyUserSwitched();
 
         verify(mContext).unbindService(connection);
-        verifyBind(1, "com.google.android.apps.vms.test/.VmsUserClient",
-                mUserInfo.getUserHandle());
+        verifyUserBind(1);
     }
 
     @Test
@@ -561,16 +556,14 @@
         resetContext();
         reset(mConnectionListener);
 
-        mUserInfo = new UserInfo(11, "Driver", 0);
-        when(mUserManager.getCurrentForegroundUserInfo()).thenReturn(mUserInfo);
+        mUserId = 11;
         notifyUserUnlocked();
 
         verify(mContext).unbindService(connection);
         verify(mConnectionListener).onClientDisconnected(
                 eq("com.google.android.apps.vms.test/com.google.android.apps.vms.test"
                         + ".VmsUserClient U=10"));
-        verifyBind(1, "com.google.android.apps.vms.test/.VmsUserClient",
-                mUserInfo.getUserHandle());
+        verifyUserBind(1);
     }
 
     @Test
@@ -582,8 +575,7 @@
         resetContext();
         reset(mConnectionListener);
 
-        mUserInfo = new UserInfo(UserHandle.USER_SYSTEM, "Owner", 0);
-        when(mUserManager.getCurrentForegroundUserInfo()).thenReturn(mUserInfo);
+        mUserId = UserHandle.USER_SYSTEM;
         notifyUserUnlocked();
 
         verify(mContext).unbindService(connection);
@@ -591,8 +583,7 @@
                 eq("com.google.android.apps.vms.test/com.google.android.apps.vms.test"
                         + ".VmsUserClient U=10"));
         // User processes will not be bound for system user
-        verifyBind(0, "com.google.android.apps.vms.test/.VmsUserClient",
-                mUserInfo.getUserHandle());
+        verifyNoBind();
     }
 
     @Test
@@ -602,13 +593,11 @@
         ServiceConnection connection = mConnectionCaptor.getValue();
         resetContext();
 
-        mUserInfo = new UserInfo(11, "Driver", 0);
-        when(mUserManager.getCurrentForegroundUserInfo()).thenReturn(mUserInfo);
+        mUserId = 11;
         notifyUserUnlocked();
 
         verify(mContext).unbindService(connection);
-        verifyBind(1, "com.google.android.apps.vms.test/.VmsUserClient",
-                mUserInfo.getUserHandle());
+        verifyUserBind(1);
     }
 
     private void resetContext() {
@@ -640,7 +629,12 @@
 
     private void verifyUserBind(int times) {
         verifyBind(times, "com.google.android.apps.vms.test/.VmsUserClient",
-                mUserInfo.getUserHandle());
+                UserHandle.of(mUserId));
+    }
+
+    private void verifyNoBind() {
+        verify(mContext, never()).bindServiceAsUser(any(Intent.class), any(ServiceConnection.class),
+                anyInt(), any(Handler.class), any(UserHandle.class));
     }
 
     private void verifyBind(int times, String componentName,
diff --git a/tests/vehiclehal_test/src/com/android/car/vehiclehal/test/E2eCarTestBase.java b/tests/vehiclehal_test/src/com/android/car/vehiclehal/test/E2eCarTestBase.java
index d78713f..24ee1ff 100644
--- a/tests/vehiclehal_test/src/com/android/car/vehiclehal/test/E2eCarTestBase.java
+++ b/tests/vehiclehal_test/src/com/android/car/vehiclehal/test/E2eCarTestBase.java
@@ -47,7 +47,7 @@
 
 public class E2eCarTestBase {
     private static final String TAG = Utils.concatTag(E2eCarTestBase.class);
-    private static final int DEFAULT_WAIT_TIMEOUT_MS = 1000;
+    private static final int DEFAULT_WAIT_TIMEOUT_MS = 5000;
 
     protected IVehicle mVehicle;
     protected Car mCar;
@@ -56,7 +56,7 @@
 
     @Before
     public void connectToVehicleHal() throws Exception {
-        mVehicle = Utils.getVehicle();
+        mVehicle = Utils.getVehicleWithTimeout(DEFAULT_WAIT_TIMEOUT_MS);
         mVehicle.getPropConfigs(
                 Lists.newArrayList(VhalEventGenerator.GENERATE_FAKE_DATA_CONTROLLING_PROPERTY),
                 (status, propConfigs) -> assumeTrue(status == StatusCode.OK));
diff --git a/tests/vehiclehal_test/src/com/android/car/vehiclehal/test/E2ePerformanceTest.java b/tests/vehiclehal_test/src/com/android/car/vehiclehal/test/E2ePerformanceTest.java
index 56ce568..6399549 100644
--- a/tests/vehiclehal_test/src/com/android/car/vehiclehal/test/E2ePerformanceTest.java
+++ b/tests/vehiclehal_test/src/com/android/car/vehiclehal/test/E2ePerformanceTest.java
@@ -183,10 +183,10 @@
 
 
         final int EXPECTED_INVOCATIONS = 1000;  // How many time get/set will be called.
-        final int EXPECTED_DURATION_MS = 2500;
+        final int EXPECTED_DURATION_MS = 3000;
         // This is a stress test and it can be flaky because it shares resources with all currently
         // running process. Let's have this number of attempt before giving up.
-        final int ATTEMPTS = 3;
+        final int ATTEMPTS = 5;
 
         for (int curAttempt = 0; curAttempt < ATTEMPTS; curAttempt++) {
             long missingInvocations = stressTestHvacProperties(mgr, cfg,
diff --git a/tests/vehiclehal_test/src/com/android/car/vehiclehal/test/Obd2FreezeFrameTest.java b/tests/vehiclehal_test/src/com/android/car/vehiclehal/test/Obd2FreezeFrameTest.java
index 0d6048d..fb09ebd 100644
--- a/tests/vehiclehal_test/src/com/android/car/vehiclehal/test/Obd2FreezeFrameTest.java
+++ b/tests/vehiclehal_test/src/com/android/car/vehiclehal/test/Obd2FreezeFrameTest.java
@@ -18,6 +18,7 @@
 
 import static com.android.car.vehiclehal.test.Utils.isVhalPropertyAvailable;
 import static com.android.car.vehiclehal.test.Utils.readVhalProperty;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assume.assumeTrue;
@@ -37,12 +38,12 @@
 /** Test retrieving the OBD2_FREEZE_FRAME property from VHAL */
 public class Obd2FreezeFrameTest {
     private static final String TAG = Utils.concatTag(Obd2FreezeFrameTest.class);
-
+    private static final int DEFAULT_WAIT_TIMEOUT_MS = 5000;
     private IVehicle mVehicle = null;
 
     @Before
     public void setUp() throws Exception {
-        mVehicle = Utils.getVehicle();
+        mVehicle = Utils.getVehicleWithTimeout(DEFAULT_WAIT_TIMEOUT_MS);
         assumeTrue("Freeze frame not available, test-case ignored.", isFreezeFrameAvailable());
     }
 
diff --git a/tests/vehiclehal_test/src/com/android/car/vehiclehal/test/Obd2LiveFrameTest.java b/tests/vehiclehal_test/src/com/android/car/vehiclehal/test/Obd2LiveFrameTest.java
index 25f2454..627e032 100644
--- a/tests/vehiclehal_test/src/com/android/car/vehiclehal/test/Obd2LiveFrameTest.java
+++ b/tests/vehiclehal_test/src/com/android/car/vehiclehal/test/Obd2LiveFrameTest.java
@@ -18,6 +18,7 @@
 
 import static com.android.car.vehiclehal.test.Utils.isVhalPropertyAvailable;
 import static com.android.car.vehiclehal.test.Utils.readVhalProperty;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assume.assumeTrue;
@@ -35,12 +36,12 @@
 /** Test retrieving the OBD2_LIVE_FRAME property from VHAL */
 public class Obd2LiveFrameTest {
     private static final String TAG = Utils.concatTag(Obd2LiveFrameTest.class);
-
+    private static final int DEFAULT_WAIT_TIMEOUT_MS = 5000;
     private IVehicle mVehicle = null;
 
     @Before
     public void setUp() throws Exception {
-        mVehicle = Utils.getVehicle();
+        mVehicle = Utils.getVehicleWithTimeout(DEFAULT_WAIT_TIMEOUT_MS);
         assumeTrue("Live frame not available, test-case ignored.", isLiveFrameAvailable());
     }
 
diff --git a/tests/vehiclehal_test/src/com/android/car/vehiclehal/test/Utils.java b/tests/vehiclehal_test/src/com/android/car/vehiclehal/test/Utils.java
index 496b504..952f430 100644
--- a/tests/vehiclehal_test/src/com/android/car/vehiclehal/test/Utils.java
+++ b/tests/vehiclehal_test/src/com/android/car/vehiclehal/test/Utils.java
@@ -16,6 +16,9 @@
 
 package com.android.car.vehiclehal.test;
 
+import static android.os.SystemClock.elapsedRealtime;
+
+import android.annotation.Nullable;
 import android.car.hardware.CarPropertyValue;
 import android.hardware.automotive.vehicle.V2_0.IVehicle;
 import android.hardware.automotive.vehicle.V2_0.VehiclePropConfig;
@@ -81,15 +84,32 @@
         return readVhalProperty(vehicle, request, f);
     }
 
-    static IVehicle getVehicle() throws RemoteException {
-        IVehicle service;
+    @Nullable
+    private static IVehicle getVehicle() {
         try {
-            service = IVehicle.getService();
+            return IVehicle.getService();
         } catch (NoSuchElementException ex) {
-            throw new RuntimeException("Couldn't connect to vehicle@2.0", ex);
+            Log.e(TAG, "IVehicle service not registered yet", ex);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to get IVehicle Service ", e);
         }
-        Log.d(TAG, "Connected to IVehicle service: " + service);
-        return service;
+        Log.d(TAG, "Failed to connect to IVehicle service");
+        return null;
+    }
+
+    static IVehicle getVehicleWithTimeout(long waitMilliseconds) {
+        IVehicle vehicle = getVehicle();
+        long endTime = elapsedRealtime() + waitMilliseconds;
+        while (vehicle == null && endTime > elapsedRealtime()) {
+            try {
+                Thread.sleep(100);
+            } catch (InterruptedException e) {
+                throw new RuntimeException("Sleep was interrupted", e);
+            }
+            vehicle = getVehicle();
+        }
+
+        return vehicle;
     }
 
     /**