Merge "Add user 0 unlock monitoring to postpone user 0 data access"
diff --git a/car-lib/src/android/car/ICar.aidl b/car-lib/src/android/car/ICar.aidl
index 9f8c367..33e2655 100644
--- a/car-lib/src/android/car/ICar.aidl
+++ b/car-lib/src/android/car/ICar.aidl
@@ -18,12 +18,24 @@
 
 /** @hide */
 interface ICar {
+    // All oneway methods are called from system server and should be placed in top positions.
+    // Do not change the order of oneway methods as system server make binder call based on this
+    // order.
+
     /**
      * IBinder is ICarServiceHelper but passed as IBinder due to aidl hidden.
-     * Only this method is oneway as it is called from system server.
+     *
      * This should be the 1st method. Do not change the order.
      */
     oneway void setCarServiceHelper(in IBinder helper) = 0;
-    IBinder getCarService(in String serviceName) = 1;
-    int getCarConnectionType() = 2;
+    /**
+     * Notify lock / unlock of user id to car service.
+     * unlocked: 1 if unlocked 0 otherwise.
+     *
+     * This should be the 2nd method. Do not change the order.
+     */
+    oneway void setUserLockStatus(in int userHandle, in int unlocked) = 1;
+
+    IBinder getCarService(in String serviceName) = 2;
+    int getCarConnectionType() = 3;
 }
diff --git a/service/src/com/android/car/CarLocalServices.java b/service/src/com/android/car/CarLocalServices.java
new file mode 100644
index 0000000..2c619af
--- /dev/null
+++ b/service/src/com/android/car/CarLocalServices.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2019 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.car;
+
+import android.util.ArrayMap;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * Copy of frameworks/base/core/java/com/android/server/LocalServices.java
+ * This is for accessing other car service components.
+ */
+public class CarLocalServices {
+    private CarLocalServices() {}
+
+    private static final ArrayMap<Class<?>, Object> sLocalServiceObjects =
+            new ArrayMap<Class<?>, Object>();
+
+    /**
+     * Returns a local service instance that implements the specified interface.
+     *
+     * @param type The type of service.
+     * @return The service object.
+     */
+    @SuppressWarnings("unchecked")
+    public static <T> T getService(Class<T> type) {
+        synchronized (sLocalServiceObjects) {
+            return (T) sLocalServiceObjects.get(type);
+        }
+    }
+
+    /**
+     * Adds a service instance of the specified interface to the global registry of local services.
+     */
+    public static <T> void addService(Class<T> type, T service) {
+        synchronized (sLocalServiceObjects) {
+            if (sLocalServiceObjects.containsKey(type)) {
+                throw new IllegalStateException("Overriding service registration");
+            }
+            sLocalServiceObjects.put(type, service);
+        }
+    }
+
+    /**
+     * Remove a service instance, must be only used in tests.
+     */
+    @VisibleForTesting
+    public static <T> void removeServiceForTest(Class<T> type) {
+        synchronized (sLocalServiceObjects) {
+            sLocalServiceObjects.remove(type);
+        }
+    }
+
+    /**
+     * Remove all registered services. Should be called when car service restarts.
+     */
+    public static void removeAllServices() {
+        synchronized (sLocalServiceObjects) {
+            sLocalServiceObjects.clear();
+        }
+    }
+}
diff --git a/service/src/com/android/car/ICarImpl.java b/service/src/com/android/car/ICarImpl.java
index e2176d3..e02c65c 100644
--- a/service/src/com/android/car/ICarImpl.java
+++ b/service/src/com/android/car/ICarImpl.java
@@ -86,9 +86,8 @@
     private final CarStorageMonitoringService mCarStorageMonitoringService;
     private final CarConfigurationService mCarConfigurationService;
     private final CarTrustAgentEnrollmentService mCarTrustAgentEnrollmentService;
-
     private final CarUserManagerHelper mUserManagerHelper;
-    private CarUserService mCarUserService;
+    private final CarUserService mCarUserService;
     private final VmsClientManager mVmsClientManager;
     private final VmsSubscriberService mVmsSubscriberService;
     private final VmsPublisherService mVmsPublisherService;
@@ -116,6 +115,7 @@
         mHal = new VehicleHal(vehicle);
         mVehicleInterfaceName = vehicleInterfaceName;
         mUserManagerHelper = new CarUserManagerHelper(serviceContext);
+        mCarUserService = new CarUserService(serviceContext, mUserManagerHelper);
         mSystemActivityMonitoringService = new SystemActivityMonitoringService(serviceContext);
         mCarPowerManagementService = new CarPowerManagementService(mContext, mHal.getPowerHal(),
                 systemInterface);
@@ -153,8 +153,11 @@
                 mContext, mCarPropertyService, mUserManagerHelper);
         mCarTrustAgentEnrollmentService = new CarTrustAgentEnrollmentService(serviceContext);
 
+        CarLocalServices.addService(CarUserService.class, mCarUserService);
+
         // Be careful with order. Service depending on other service should be inited later.
         List<CarServiceBase> allServices = new ArrayList<>();
+        allServices.add(mCarUserService);
         allServices.add(mSystemActivityMonitoringService);
         allServices.add(mCarPowerManagementService);
         allServices.add(mCarPropertyService);
@@ -178,9 +181,6 @@
         allServices.add(mVmsSubscriberService);
         allServices.add(mVmsPublisherService);
         allServices.add(mCarTrustAgentEnrollmentService);
-        if (mUserManagerHelper.isHeadlessSystemUser()) {
-            allServices.add(new CarUserService(serviceContext, mUserManagerHelper));
-        }
         allServices.add(mCarLocationService);
         mAllServices = allServices.toArray(new CarServiceBase[allServices.size()]);
     }
@@ -204,6 +204,7 @@
             mAllServices[i].release();
         }
         mHal.release();
+        CarLocalServices.removeAllServices();
     }
 
     void vehicleHalReconnected(IVehicle vehicle) {
@@ -226,6 +227,15 @@
     }
 
     @Override
+    public void setUserLockStatus(int userHandle, int unlocked) {
+        int uid = Binder.getCallingUid();
+        if (uid != Process.SYSTEM_UID) {
+            throw new SecurityException("Only allowed from system");
+        }
+        mCarUserService.setUserLockStatus(userHandle, unlocked == 1);
+    }
+
+    @Override
     public IBinder getCarService(String serviceName) {
         switch (serviceName) {
             case Car.AUDIO_SERVICE:
diff --git a/service/src/com/android/car/cluster/InstrumentClusterService.java b/service/src/com/android/car/cluster/InstrumentClusterService.java
index b491350..fd16da5 100644
--- a/service/src/com/android/car/cluster/InstrumentClusterService.java
+++ b/service/src/com/android/car/cluster/InstrumentClusterService.java
@@ -39,9 +39,11 @@
 import com.android.car.AppFocusService.FocusOwnershipCallback;
 import com.android.car.CarInputService;
 import com.android.car.CarInputService.KeyEventListener;
+import com.android.car.CarLocalServices;
 import com.android.car.CarLog;
 import com.android.car.CarServiceBase;
 import com.android.car.R;
+import com.android.car.user.CarUserService;
 import com.android.internal.annotations.GuardedBy;
 
 import java.io.PrintWriter;
@@ -133,7 +135,11 @@
 
         mAppFocusService.registerContextOwnerChangedCallback(this /* FocusOwnershipCallback */);
         mCarInputService.setInstrumentClusterKeyListener(this /* KeyEventListener */);
-        mRendererBound = bindInstrumentClusterRendererService();
+        // TODO(b/124246323) Start earlier once data storage for cluster is clarified
+        //  for early boot.
+        CarLocalServices.getService(CarUserService.class).runOnUser0Unlock(() -> {
+            mRendererBound = bindInstrumentClusterRendererService();
+        });
     }
 
     @Override
diff --git a/service/src/com/android/car/user/CarUserService.java b/service/src/com/android/car/user/CarUserService.java
index 72e313f..760b435 100644
--- a/service/src/com/android/car/user/CarUserService.java
+++ b/service/src/com/android/car/user/CarUserService.java
@@ -30,8 +30,10 @@
 import android.util.Log;
 
 import com.android.car.CarServiceBase;
+import com.android.internal.annotations.GuardedBy;
 
 import java.io.PrintWriter;
+import java.util.ArrayList;
 
 /**
  * User service for cars. Manages users at boot time. Including:
@@ -46,6 +48,12 @@
     private final Context mContext;
     private final CarUserManagerHelper mCarUserManagerHelper;
 
+    private final Object mLock = new Object();
+    @GuardedBy("mLock")
+    private boolean mUser0Unlocked;
+    @GuardedBy("mLock")
+    private final ArrayList<Runnable> mUser0UnlockTasks = new ArrayList<>();
+
     public CarUserService(
                 @Nullable Context context, @Nullable CarUserManagerHelper carUserManagerHelper) {
         if (Log.isLoggable(TAG, Log.DEBUG)) {
@@ -60,11 +68,13 @@
         if (Log.isLoggable(TAG, Log.DEBUG)) {
             Log.d(TAG, "init");
         }
-        IntentFilter filter = new IntentFilter();
-        filter.addAction(Intent.ACTION_LOCKED_BOOT_COMPLETED);
-        filter.addAction(Intent.ACTION_USER_SWITCHED);
+        if (mCarUserManagerHelper.isHeadlessSystemUser()) {
+            IntentFilter filter = new IntentFilter();
+            filter.addAction(Intent.ACTION_LOCKED_BOOT_COMPLETED);
+            filter.addAction(Intent.ACTION_USER_SWITCHED);
 
-        mContext.registerReceiver(this, filter);
+            mContext.registerReceiver(this, filter);
+        }
     }
 
     @Override
@@ -79,6 +89,11 @@
     public void dump(PrintWriter writer) {
         writer.println(TAG);
         writer.println("Context: " + mContext);
+        boolean user0Unlocked;
+        synchronized (mLock) {
+            user0Unlocked = mUser0Unlocked;
+        }
+        writer.println("User0Unlocked: " + user0Unlocked);
     }
 
     @Override
@@ -100,7 +115,7 @@
 
         } else if (Intent.ACTION_USER_SWITCHED.equals(intent.getAction())) {
             // Update last active user if the switched-to user is a persistent, non-system user.
-            int currentUser = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
+            final int currentUser = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
             if (currentUser > UserHandle.USER_SYSTEM
                         && mCarUserManagerHelper.isPersistentUser(currentUser)) {
                 mCarUserManagerHelper.setLastActiveUser(currentUser);
@@ -108,6 +123,51 @@
         }
     }
 
+    /**
+     * Set user lock / unlocking status. This is coming from system server through ICar binder call.
+     * @param userHandle Handle of user
+     * @param unlocked unlocked (=true) or locked (=false)
+     */
+    public void setUserLockStatus(int userHandle, boolean unlocked) {
+        if (userHandle != UserHandle.USER_SYSTEM) {
+            return;
+        }
+        ArrayList<Runnable> tasks = null;
+        synchronized (mLock) {
+            // Assumes that car service need to do it only once during boot-up
+            if (unlocked && !mUser0Unlocked) {
+                tasks = new ArrayList<>(mUser0UnlockTasks);
+                mUser0UnlockTasks.clear();
+                mUser0Unlocked = unlocked;
+            }
+        }
+        if (tasks != null && tasks.size() > 0) {
+            Log.d(TAG, "User0 unlocked, run queued tasks:" + tasks.size());
+            for (Runnable r : tasks) {
+                r.run();
+            }
+        }
+    }
+
+    /**
+     * Run give runnable when user 0 is unlocked. If user 0 is already unlocked, it is
+     * run inside this call.
+     * @param r Runnable to run.
+     */
+    public void runOnUser0Unlock(Runnable r) {
+        boolean runNow = false;
+        synchronized (mLock) {
+            if (mUser0Unlocked) {
+                runNow = true;
+            } else {
+                mUser0UnlockTasks.add(r);
+            }
+        }
+        if (runNow) {
+            r.run();
+        }
+    }
+
     private void setSystemUserRestrictions() {
         // Disable adding accounts for system user.
         mCarUserManagerHelper.setUserRestriction(mCarUserManagerHelper.getSystemUserInfo(),
diff --git a/tests/carservice_unit_test/src/com/android/car/user/CarUserServiceTest.java b/tests/carservice_unit_test/src/com/android/car/user/CarUserServiceTest.java
index 1947c8f..5d1ea95 100644
--- a/tests/carservice_unit_test/src/com/android/car/user/CarUserServiceTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/user/CarUserServiceTest.java
@@ -18,6 +18,8 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.never;
@@ -73,6 +75,9 @@
 
     private static final String DEFAULT_ADMIN_NAME = "defaultName";
 
+    private boolean mUser0TaskExecuted;
+
+
     /**
      * Initialize all of the objects with the @Mock annotation.
      */
@@ -98,6 +103,9 @@
      */
     @Test
     public void testRegistersToReceiveEvents() {
+        if (!mCarUserManagerHelper.isHeadlessSystemUser()) {
+            return;
+        }
         ArgumentCaptor<IntentFilter> argument = ArgumentCaptor.forClass(IntentFilter.class);
         mCarUserService.init();
         verify(mMockContext).registerReceiver(eq(mCarUserService), argument.capture());
@@ -202,6 +210,27 @@
         verify(mCarUserManagerHelper, never()).initDefaultGuestRestrictions();
     }
 
+    @Test
+    public void testRunOnUser0UnlockImmediate() {
+        mUser0TaskExecuted = false;
+        mCarUserService.setUserLockStatus(UserHandle.USER_SYSTEM, true);
+        mCarUserService.runOnUser0Unlock(() -> {
+            mUser0TaskExecuted = true;
+        });
+        assertTrue(mUser0TaskExecuted);
+    }
+
+    @Test
+    public void testRunOnUser0UnlockLater() {
+        mUser0TaskExecuted = false;
+        mCarUserService.runOnUser0Unlock(() -> {
+            mUser0TaskExecuted = true;
+        });
+        assertFalse(mUser0TaskExecuted);
+        mCarUserService.setUserLockStatus(UserHandle.USER_SYSTEM, true);
+        assertTrue(mUser0TaskExecuted);
+    }
+
     private void putSettingsInt(String key, int value) {
         Settings.Global.putInt(InstrumentationRegistry.getTargetContext().getContentResolver(),
                 key, value);