add CarUserNoticeService

- The Service launches UserNoticeUI Service for following conditions:
  cold boot, user switching, wake up from sleep.
- UI is set from config_userNoticeUiService CarService resource.
- The feature is disabled if CarService's resource string is empty.
- UI launching can be disabled by setting
  CarSettings.Secure.KEY_ENABLE_INITIAL_NOTICE_SCREEN_TO_USER to 0.
- UI Service should implement IUserNoticeUI binder.
- Added dummy UI to Kitchensink for manual testing. It is intentionally
  handling binder messages without classes generated from aidl so that it can
  be implemented without adding public API.
- The dummy UI uses AlertDialog with TYPE_APPLICATION_OVERLAY window type to
  show it above normal Activities.
- Target package specified will be auto-granted TYPE_SYSTEM_ALERT permission
  through appops. Permission granting only works for TYPE_APPLICATION_OVERLAY.
  UI using any higher priority window should resolve necessary permission by
  itself.

Bug: 140875332
Bug: 140032243

Test: Test with added dummy UI and confirm that it shows up for mentioned
      events.
      adb reboot
      user switching
      suspend to ram and wakeup:
        call once: adb shell setprop android.car.garagemodeduration 1
	adb shell dumpsys car_service suspend
	adb shell dumpsys car_service resume

Merged-In: I544d2c7fe2e821ee9794e49d00d35c5977912632
Change-Id: I544d2c7fe2e821ee9794e49d00d35c5977912632
diff --git a/car-lib/src/android/car/settings/CarSettings.java b/car-lib/src/android/car/settings/CarSettings.java
index c307cab..ab7c906 100644
--- a/car-lib/src/android/car/settings/CarSettings.java
+++ b/car-lib/src/android/car/settings/CarSettings.java
@@ -144,5 +144,14 @@
          */
         public static final String KEY_BLUETOOTH_PROFILES_INHIBITED =
                 "android.car.BLUETOOTH_PROFILES_INHIBITED";
+
+        /**
+         * Key to enable / disable initial notice screen that will be shown for all user-starting
+         * moments including cold boot, wake up from suspend, and user switching.
+         * The value is boolean (1 or 0).
+         * @hide
+         */
+        public static final String KEY_ENABLE_INITIAL_NOTICE_SCREEN_TO_USER =
+                "android.car.ENABLE_INITIAL_NOTICE_SCREEN_TO_USER";
     }
 }
diff --git a/car-lib/src/android/car/user/IUserNotice.aidl b/car-lib/src/android/car/user/IUserNotice.aidl
new file mode 100644
index 0000000..1debc84
--- /dev/null
+++ b/car-lib/src/android/car/user/IUserNotice.aidl
@@ -0,0 +1,30 @@
+/*
+ * 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 android.car.user;
+
+/**
+ * Binder for UserNotice UI to notify status change to CarUserNoticeService/CarService.
+ * This binder is implemented inside CarService.
+ * @hide
+*/
+interface IUserNotice {
+    /**
+     * Notify CarUserNoticeService/CarSercice that UI dialog is dismissed.
+     * CarUserNoticeService will unbind the UI servie to finish it.
+     */
+    void onDialogDismissed();
+}
\ No newline at end of file
diff --git a/car-lib/src/android/car/user/IUserNoticeUI.aidl b/car-lib/src/android/car/user/IUserNoticeUI.aidl
new file mode 100644
index 0000000..44717d0
--- /dev/null
+++ b/car-lib/src/android/car/user/IUserNoticeUI.aidl
@@ -0,0 +1,32 @@
+/*
+ * 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 android.car.user;
+
+import android.car.user.IUserNotice;
+
+/**
+ * Binder for CarUserNoticeService/CarService to pass IUserNotice binder to UserNotice UI.
+ * UserNotice UI implements this binder.
+ * @hide
+*/
+oneway interface IUserNoticeUI {
+    /**
+     * CarUserNoticeService will use this call to pass IUserNotice binder which can be used
+     * to notify dismissal of UI dialog.
+     */
+    void setCallbackBinder(in IUserNotice binder);
+}
diff --git a/service/AndroidManifest.xml b/service/AndroidManifest.xml
index 7d85445..ebea889 100644
--- a/service/AndroidManifest.xml
+++ b/service/AndroidManifest.xml
@@ -492,6 +492,7 @@
     <uses-permission android:name="android.permission.WRITE_SETTINGS" />
     <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
     <uses-permission android:name="android.permission.BLUETOOTH" />
+    <uses-permission android:name="android.permission.MANAGE_APP_OPS_MODES" />
     <uses-permission android:name="android.permission.MANAGE_USERS" />
     <uses-permission android:name="android.permission.LOCATION_HARDWARE" />
     <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
diff --git a/service/res/values/config.xml b/service/res/values/config.xml
index 370fe5d..3eb1007 100644
--- a/service/res/values/config.xml
+++ b/service/res/values/config.xml
@@ -229,4 +229,11 @@
          There is no default bugreporting app.-->
     <string name="config_car_bugreport_application" translatable="false"></string>
 
+    <!-- Specifies notice UI that will be launched when user starts a car or do user
+         switching. It is recommended to use dialog with at least TYPE_APPLICATION_OVERLAY window
+         type to show the UI regardless of activity launches. Target package will be auto-granted
+         necessary permission for TYPE_APPLICATION_OVERLAY window type. The UI package should
+         resolve permission by itself to use any higher priority window type.
+         Setting this string to empty will disable the feature. -->
+    <string name="config_userNoticeUiService" translatable="false">com.google.android.car.kitchensink/.UserNoiticeDemoUiService</string>
 </resources>
diff --git a/service/src/com/android/car/CarLog.java b/service/src/com/android/car/CarLog.java
index ede5565..a77a549 100644
--- a/service/src/com/android/car/CarLog.java
+++ b/service/src/com/android/car/CarLog.java
@@ -26,9 +26,9 @@
     public static final String TAG_CAMERA = "CAR.CAMERA";
     public static final String TAG_CAN_BUS = "CAR.CAN_BUS";
     public static final String TAG_CLUSTER = "CAR.CLUSTER";
+    public static final String TAG_DIAGNOSTIC = "CAR.DIAGNOSTIC";
     public static final String TAG_HAL = "CAR.HAL";
     public static final String TAG_HVAC = "CAR.HVAC";
-    public static final String TAG_VENDOR_EXT = "CAR.VENDOR_EXT";
     public static final String TAG_INFO = "CAR.INFO";
     public static final String TAG_INPUT = "CAR.INPUT";
     public static final String TAG_MEDIA = "CAR.MEDIA";
@@ -40,10 +40,11 @@
     public static final String TAG_PROPERTY = "CAR.PROPERTY";
     public static final String TAG_SENSOR = "CAR.SENSOR";
     public static final String TAG_SERVICE = "CAR.SERVICE";
+    public static final String TAG_STORAGE = "CAR.STORAGE";
     public static final String TAG_SYS = "CAR.SYS";
     public static final String TAG_TEST = "CAR.TEST";
-    public static final String TAG_DIAGNOSTIC = "CAR.DIAGNOSTIC";
-    public static final String TAG_STORAGE = "CAR.STORAGE";
+    public static final String TAG_USER = "CAR.USER";
+    public static final String TAG_VENDOR_EXT = "CAR.VENDOR_EXT";
 
     public static String concatTag(String tagPrefix, Class clazz) {
         String tag = tagPrefix + "." + clazz.getSimpleName();
diff --git a/service/src/com/android/car/ICarImpl.java b/service/src/com/android/car/ICarImpl.java
index 8527208..fe7c4d6 100644
--- a/service/src/com/android/car/ICarImpl.java
+++ b/service/src/com/android/car/ICarImpl.java
@@ -45,6 +45,7 @@
 import com.android.car.pm.CarPackageManagerService;
 import com.android.car.systeminterface.SystemInterface;
 import com.android.car.trust.CarTrustedDeviceService;
+import com.android.car.user.CarUserNoticeService;
 import com.android.car.user.CarUserService;
 import com.android.car.vms.VmsBrokerService;
 import com.android.car.vms.VmsClientManager;
@@ -92,6 +93,7 @@
     private final CarMediaService mCarMediaService;
     private final CarUserManagerHelper mUserManagerHelper;
     private final CarUserService mCarUserService;
+    private final CarUserNoticeService mCarUserNoticeService;
     private final VmsClientManager mVmsClientManager;
     private final VmsBrokerService mVmsBrokerService;
     private final VmsSubscriberService mVmsSubscriberService;
@@ -129,6 +131,7 @@
         mSystemActivityMonitoringService = new SystemActivityMonitoringService(serviceContext);
         mCarPowerManagementService = new CarPowerManagementService(mContext, mHal.getPowerHal(),
                 systemInterface, mUserManagerHelper);
+        mCarUserNoticeService = new CarUserNoticeService(serviceContext);
         mCarPropertyService = new CarPropertyService(serviceContext, mHal.getPropertyHal());
         mCarDrivingStateService = new CarDrivingStateService(serviceContext, mCarPropertyService);
         mCarUXRestrictionsService = new CarUxRestrictionsManagerService(serviceContext,
@@ -186,6 +189,7 @@
         allServices.add(mCarPackageManagerService);
         allServices.add(mCarInputService);
         allServices.add(mGarageModeService);
+        allServices.add(mCarUserNoticeService);
         allServices.add(mAppFocusService);
         allServices.add(mCarAudioService);
         allServices.add(mCarNightService);
diff --git a/service/src/com/android/car/user/CarUserNoticeService.java b/service/src/com/android/car/user/CarUserNoticeService.java
new file mode 100644
index 0000000..22640e2
--- /dev/null
+++ b/service/src/com/android/car/user/CarUserNoticeService.java
@@ -0,0 +1,397 @@
+/*
+ * 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.user;
+
+import static android.car.hardware.power.CarPowerManager.CarPowerStateListener;
+
+import static com.android.car.CarLog.TAG_USER;
+
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.app.ActivityManager;
+import android.app.AppOpsManager;
+import android.car.CarNotConnectedException;
+import android.car.hardware.power.CarPowerManager;
+import android.car.settings.CarSettings;
+import android.car.user.IUserNotice;
+import android.car.user.IUserNoticeUI;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.ServiceConnection;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.PowerManager;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.util.Log;
+import android.view.IWindowManager;
+import android.view.WindowManagerGlobal;
+
+import com.android.car.CarLocalServices;
+import com.android.car.CarServiceBase;
+import com.android.car.R;
+import com.android.internal.annotations.GuardedBy;
+
+import java.io.PrintWriter;
+
+/**
+ * Service to show initial notice UI to user. It only launches it when setting is enabled and
+ * it is up to notice UI (=Service) to dismiss itself upon user's request.
+ *
+ * <p>Conditions to show notice UI are:
+ * <ol>
+ *   <li>Cold boot
+ *   <li><User switching
+ *   <li>Car power state change to ON (happens in wakeup from suspend to RAM)
+ * </ol>
+ */
+public final class CarUserNoticeService implements CarServiceBase {
+
+    // Keyguard unlocking can be only polled as we cannot dismiss keyboard.
+    // Polling will stop when keyguard is unlocked.
+    private static final long KEYGUARD_POLLING_INTERVAL_MS = 100;
+
+    private final Context mContext;
+
+    // null means feature disabled.
+    @Nullable
+    private final Intent mServiceIntent;
+
+    private final Handler mMainHandler = new Handler(Looper.getMainLooper());
+
+    private final Object mLock = new Object();
+
+    // This one records if there is a service bound. This will be cleared as soon as service is
+    // unbound (=UI dismissed)
+    @GuardedBy("mLock")
+    private boolean mServiceBound = false;
+
+    // This one represents if UI is shown for the current session. This should be kept until
+    // next event to show UI comes up.
+    @GuardedBy("mLock")
+    private boolean mUiShown = false;
+
+    @GuardedBy("mLock")
+    @UserIdInt
+    private int mUserId = UserHandle.USER_NULL;
+
+    @GuardedBy("mLock")
+    private CarPowerManager mCarPowerManager;
+
+    @GuardedBy("mLock")
+    private IUserNoticeUI mUiService;
+
+    private final CarUserService.UserCallback mUserCallback = new CarUserService.UserCallback() {
+        @Override
+        public void onUserLockChanged(@UserIdInt int userId, boolean unlocked) {
+            // Nothing to do
+        }
+
+        @Override
+        public void onSwitchUser(@UserIdInt int userId) {
+            mMainHandler.post(() -> {
+                stopUi(/* clearUiShown= */ true);
+                synchronized (mLock) {
+                    // This should be the only place to change user
+                    mUserId = userId;
+                }
+                startNoticeUiIfNecessary();
+            });
+        }
+    };
+
+    private final CarPowerStateListener mPowerStateListener = new CarPowerStateListener() {
+        @Override
+        public void onStateChanged(int state) {
+            if (state == CarPowerManager.CarPowerStateListener.SHUTDOWN_PREPARE) {
+                mMainHandler.post(() -> stopUi(/* clearUiShown= */ true));
+            } else if (state == CarPowerManager.CarPowerStateListener.ON) {
+                // Only ON can be relied on as car can restart while in garage mode.
+                mMainHandler.post(() -> startNoticeUiIfNecessary());
+            }
+        }
+    };
+
+    private final BroadcastReceiver mDisplayBroadcastReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            // Runs in main thread, so do not use Handler.
+            if (Intent.ACTION_SCREEN_OFF.equals(intent.getAction())) {
+                if (isDisplayOn()) {
+                    Log.i(TAG_USER, "SCREEN_OFF while display is already on");
+                    return;
+                }
+                Log.i(TAG_USER, "Display off, stopping UI");
+                stopUi(/* clearUiShown= */ true);
+            } else if (Intent.ACTION_SCREEN_ON.equals(intent.getAction())) {
+                if (!isDisplayOn()) {
+                    Log.i(TAG_USER, "SCREEN_ON while display is already off");
+                    return;
+                }
+                Log.i(TAG_USER, "Display on, starting UI");
+                startNoticeUiIfNecessary();
+            }
+        }
+    };
+
+    private final IUserNotice.Stub mIUserNotice = new IUserNotice.Stub() {
+        @Override
+        public void onDialogDismissed() {
+            mMainHandler.post(() -> stopUi(/* clearUiShown= */ false));
+        }
+    };
+
+    private final ServiceConnection mUiServiceConnection = new ServiceConnection() {
+        public void onServiceConnected(ComponentName name, IBinder service) {
+            synchronized (mLock) {
+                if (!mServiceBound) {
+                    // already unbound but passed due to timing. This should be just ignored.
+                    return;
+                }
+            }
+            IUserNoticeUI binder = IUserNoticeUI.Stub.asInterface(service);
+            try {
+                binder.setCallbackBinder(mIUserNotice);
+            } catch (RemoteException e) {
+                Log.w(TAG_USER, "UserNoticeUI Service died", e);
+                // Wait for reconnect
+                binder = null;
+            }
+            synchronized (mLock) {
+                mUiService = binder;
+            }
+        }
+
+        public void onServiceDisconnected(ComponentName name) {
+            // UI crashed. Stop it so that it does not come again.
+            stopUi(/* clearUiShown= */ true);
+        }
+    };
+
+    // added for debugging purpose
+    @GuardedBy("mLock")
+    private int mKeyguardPollingCounter;
+
+    private final Runnable mKeyguardPollingRunnable = () -> {
+        synchronized (mLock) {
+            mKeyguardPollingCounter++;
+        }
+        startNoticeUiIfNecessary();
+    };
+
+    public CarUserNoticeService(Context context) {
+        Resources res = context.getResources();
+        String componentName = res.getString(R.string.config_userNoticeUiService);
+        if (componentName.isEmpty()) {
+            // feature disabled
+            mContext = null;
+            mServiceIntent = null;
+            return;
+        }
+        mContext = context;
+        mServiceIntent = new Intent();
+        mServiceIntent.setComponent(ComponentName.unflattenFromString(componentName));
+    }
+
+    private boolean checkKeyguardLockedWithPolling() {
+        mMainHandler.removeCallbacks(mKeyguardPollingRunnable);
+        IWindowManager wm = WindowManagerGlobal.getWindowManagerService();
+        boolean locked = true;
+        if (wm != null) {
+            try {
+                locked = wm.isKeyguardLocked();
+            } catch (RemoteException e) {
+                Log.w(TAG_USER, "system server crashed", e);
+            }
+        }
+        if (locked) {
+            mMainHandler.postDelayed(mKeyguardPollingRunnable, KEYGUARD_POLLING_INTERVAL_MS);
+        }
+        return locked;
+    }
+
+    private boolean isNoticeScreenEnabledInSetting(@UserIdInt int userId) {
+        return Settings.Secure.getIntForUser(mContext.getContentResolver(),
+                CarSettings.Secure.KEY_ENABLE_INITIAL_NOTICE_SCREEN_TO_USER,
+                1 /*enable by default*/, userId) == 1;
+    }
+
+    private boolean isDisplayOn() {
+        PowerManager pm = mContext.getSystemService(PowerManager.class);
+        if (pm == null) {
+            return false;
+        }
+        return pm.isInteractive();
+    }
+
+    private boolean grantSystemAlertWindowPermission(@UserIdInt int userId) {
+        AppOpsManager appOpsManager = mContext.getSystemService(AppOpsManager.class);
+        if (appOpsManager == null) {
+            Log.w(TAG_USER, "AppOpsManager not ready yet");
+            return false;
+        }
+        String packageName = mServiceIntent.getComponent().getPackageName();
+        int packageUid;
+        try {
+            packageUid = mContext.getPackageManager().getPackageUidAsUser(packageName, userId);
+        } catch (PackageManager.NameNotFoundException e) {
+            Log.wtf(TAG_USER, "Target package for config_userNoticeUiService not found:"
+                    + packageName + " userId:" + userId);
+            return false;
+        }
+        appOpsManager.setMode(AppOpsManager.OP_SYSTEM_ALERT_WINDOW, packageUid, packageName,
+                AppOpsManager.MODE_ALLOWED);
+        Log.i(TAG_USER, "Granted SYSTEM_ALERT_WINDOW permission to package:" + packageName
+                + " package uid:" + packageUid);
+        return true;
+    }
+
+    private void startNoticeUiIfNecessary() {
+        int userId;
+        synchronized (mLock) {
+            if (mUiShown || mServiceBound) {
+                return;
+            }
+            userId = mUserId;
+        }
+        if (userId == UserHandle.USER_NULL) {
+            return;
+        }
+        // headless user 0 is ignored.
+        if (userId == UserHandle.USER_SYSTEM) {
+            return;
+        }
+        if (!isNoticeScreenEnabledInSetting(userId)) {
+            return;
+        }
+        if (userId != ActivityManager.getCurrentUser()) {
+            // user has switched. will be handled by user switch callback
+            return;
+        }
+        // Dialog can be not shown if display is off.
+        // DISPLAY_ON broadcast will handle this later.
+        if (!isDisplayOn()) {
+            return;
+        }
+        // Do not show it until keyguard is dismissed.
+        if (checkKeyguardLockedWithPolling()) {
+            return;
+        }
+        if (!grantSystemAlertWindowPermission(userId)) {
+            return;
+        }
+        boolean bound = mContext.bindServiceAsUser(mServiceIntent, mUiServiceConnection,
+                Context.BIND_AUTO_CREATE, UserHandle.of(userId));
+        if (bound) {
+            Log.i(TAG_USER, "Bound UserNoticeUI Service Service:" + mServiceIntent);
+            synchronized (mLock) {
+                mServiceBound = true;
+                mUiShown = true;
+            }
+        } else {
+            Log.w(TAG_USER, "Cannot bind to UserNoticeUI Service Service" + mServiceIntent);
+        }
+    }
+
+    private void stopUi(boolean clearUiShown) {
+        mMainHandler.removeCallbacks(mKeyguardPollingRunnable);
+        boolean serviceBound;
+        synchronized (mLock) {
+            mUiService = null;
+            serviceBound = mServiceBound;
+            mServiceBound = false;
+            if (clearUiShown) {
+                mUiShown = false;
+            }
+        }
+        if (serviceBound) {
+            Log.i(TAG_USER, "Unbound UserNoticeUI Service");
+            mContext.unbindService(mUiServiceConnection);
+        }
+    }
+
+    @Override
+    public void init() {
+        if (mServiceIntent == null) {
+            // feature disabled
+            return;
+        }
+
+        CarPowerManager carPowerManager;
+        synchronized (mLock) {
+            mCarPowerManager = CarLocalServices.createCarPowerManager(mContext);
+            carPowerManager = mCarPowerManager;
+        }
+        try {
+            carPowerManager.setListener(mPowerStateListener);
+        } catch (CarNotConnectedException e) {
+            // should not happen
+            throw new RuntimeException("CarNotConnectedException from CarPowerManager", e);
+        }
+        CarUserService userService = CarLocalServices.getService(CarUserService.class);
+        userService.addUserCallback(mUserCallback);
+        IntentFilter intentFilter = new IntentFilter();
+        intentFilter.addAction(Intent.ACTION_SCREEN_OFF);
+        intentFilter.addAction(Intent.ACTION_SCREEN_ON);
+        mContext.registerReceiver(mDisplayBroadcastReceiver, intentFilter);
+    }
+
+    @Override
+    public void release() {
+        if (mServiceIntent == null) {
+            // feature disabled
+            return;
+        }
+        mContext.unregisterReceiver(mDisplayBroadcastReceiver);
+        CarUserService userService = CarLocalServices.getService(CarUserService.class);
+        userService.removeUserCallback(mUserCallback);
+        CarPowerManager carPowerManager;
+        synchronized (mLock) {
+            carPowerManager = mCarPowerManager;
+            mUserId = UserHandle.USER_NULL;
+        }
+        carPowerManager.clearListener();
+        stopUi(/* clearUiShown= */ true);
+    }
+
+    @Override
+    public void dump(PrintWriter writer) {
+        synchronized (mLock) {
+            if (mServiceIntent == null) {
+                writer.println("*CarUserNoticeService* disabled");
+                return;
+            }
+            if (mUserId == UserHandle.USER_NULL) {
+                writer.println("*CarUserNoticeService* User not started yet.");
+                return;
+            }
+            writer.println("*CarUserNoticeService* mServiceIntent:" + mServiceIntent
+                    + ", mUserId:" + mUserId
+                    + ", mUiShown:" + mUiShown
+                    + ", mServiceBound:" + mServiceBound
+                    + ", mKeyguardPollingCounter:" + mKeyguardPollingCounter
+                    + " Setting enabled:" + isNoticeScreenEnabledInSetting(mUserId));
+        }
+    }
+}
diff --git a/tests/EmbeddedKitchenSinkApp/AndroidManifest.xml b/tests/EmbeddedKitchenSinkApp/AndroidManifest.xml
index a8b8356..8997e32 100644
--- a/tests/EmbeddedKitchenSinkApp/AndroidManifest.xml
+++ b/tests/EmbeddedKitchenSinkApp/AndroidManifest.xml
@@ -108,6 +108,8 @@
                  android:exported="false" android:directBootAware="true">
         </service>
 
+        <service android:name=".UserNoiticeDemoUiService" android:directBootAware="true" />
+
         <!-- Content provider for images -->
         <provider android:name=".cluster.ClusterContentProvider"
                   android:authorities="com.google.android.car.kitchensink.cluster.clustercontentprovider"
diff --git a/tests/EmbeddedKitchenSinkApp/res/values/strings.xml b/tests/EmbeddedKitchenSinkApp/res/values/strings.xml
index 27844f3..6575ce1 100644
--- a/tests/EmbeddedKitchenSinkApp/res/values/strings.xml
+++ b/tests/EmbeddedKitchenSinkApp/res/values/strings.xml
@@ -327,4 +327,9 @@
     <string name="disconnect" translatable="false">disconnect</string>
     <string name="createcar" translatable="false">createCar</string>
     <string name="createcar_with_status_change" translatable="false">createCarWithStatusChange</string>
+
+    <!-- UserNoiticeDemoUiService -->
+    <string name="usernotice" translatable="false">This screen is for showing initial user notice and is not for product. Plz change config_userNoticeUiService in CarService before shipping.</string>
+    <string name="dismiss_now" translatable="false">Dismiss for now</string>
+    <string name="dismiss_forever" translatable="false">Do not show again</string>
 </resources>
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/UserNoiticeDemoUiService.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/UserNoiticeDemoUiService.java
new file mode 100644
index 0000000..dfe18dc
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/UserNoiticeDemoUiService.java
@@ -0,0 +1,173 @@
+/*
+ * 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.google.android.car.kitchensink;
+
+import android.app.AlertDialog;
+import android.app.Service;
+import android.car.settings.CarSettings;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Parcel;
+import android.os.RemoteException;
+import android.provider.Settings;
+import android.util.Log;
+import android.view.WindowManager;
+
+import com.android.internal.annotations.GuardedBy;
+
+/**
+ * Example service of implementing UserNoticeUI.
+ * <p>IUserNotice and IUserNoticeUI are intentionally accessed / implemented without using the
+ * generated code from aidl so that this can be done without accessing hidden API.
+ */
+public class UserNoiticeDemoUiService extends Service {
+
+    private static final String TAG = UserNoiticeDemoUiService.class.getSimpleName();
+
+    private static final String IUSER_NOTICE_BINDER_DESCRIPTOR = "android.car.user.IUserNotice";
+    private static final int IUSER_NOTICE_TR_ON_DIALOG_DISMISSED =
+            android.os.IBinder.FIRST_CALL_TRANSACTION;
+
+    private static final String IUSER_NOTICE_UI_BINDER_DESCRIPTOR =
+            "android.car.user.IUserNoticeUI";
+    private static final int IUSER_NOTICE_UI_BINDER_TR_SET_CALLBACK =
+            android.os.IBinder.FIRST_CALL_TRANSACTION;
+
+    private final Handler mMainHandler = new Handler(Looper.getMainLooper());
+
+    private final Object mLock = new Object();
+
+    // Do not use IUserNoticeUI class intentionally to show how it can be
+    // implemented without accessing the hidden API.
+    private IBinder mIUserNoticeUiBinder = new Binder() {
+        @Override
+        protected boolean onTransact(int code, Parcel data, Parcel reply, int flags)
+                throws RemoteException {
+            switch (code) {
+                case IUSER_NOTICE_UI_BINDER_TR_SET_CALLBACK:
+                    data.enforceInterface(IUSER_NOTICE_UI_BINDER_DESCRIPTOR);
+                    IBinder binder = data.readStrongBinder();
+                    onSetCallbackBinder(binder);
+                    return true;
+                default:
+                    return super.onTransact(code, data, reply, flags);
+            }
+        }
+    };
+
+    @GuardedBy("mLock")
+    private IBinder mIUserNoticeService;
+
+    @GuardedBy("mLock")
+    private AlertDialog mDialog;
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return mIUserNoticeUiBinder;
+    }
+
+    @Override
+    public boolean onUnbind(Intent intent) {
+        stopDialog(true);
+        return false;
+    }
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+        stopDialog(true);
+    }
+
+    private void onSetCallbackBinder(IBinder binder) {
+        if (binder == null) {
+            Log.wtf(TAG, "No binder set in onSetCallbackBinder call", new RuntimeException());
+            return;
+        }
+        mMainHandler.post(() -> {
+            synchronized (mLock) {
+                mIUserNoticeService = binder;
+            }
+            startDialog();
+        });
+    }
+
+    private void startDialog() {
+        synchronized (mLock) {
+            if (mDialog != null) {
+                Log.wtf(TAG, "Dialog already created", new RuntimeException());
+                return;
+            }
+            mDialog = createDialog();
+            // Necessary permission is auto-granted by car service before starting this.
+            mDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY);
+            mDialog.setCancelable(false);
+        }
+        mDialog.show();
+    }
+
+    private void stopDialog(boolean dismiss) {
+        IBinder userNotice;
+        AlertDialog dialog;
+        synchronized (mLock) {
+            userNotice = mIUserNoticeService;
+            dialog = mDialog;
+            mDialog = null;
+            mIUserNoticeService = null;
+        }
+        if (userNotice != null) {
+            sendOnDialogDismissedToCarService(userNotice);
+        }
+        if (dialog != null && dismiss) {
+            dialog.dismiss();
+        }
+        stopSelf();
+    }
+
+    private void sendOnDialogDismissedToCarService(IBinder userNotice) {
+        Parcel data = Parcel.obtain();
+        data.writeInterfaceToken(IUSER_NOTICE_BINDER_DESCRIPTOR);
+        try {
+            userNotice.transact(IUSER_NOTICE_TR_ON_DIALOG_DISMISSED, data, null, 0);
+        } catch (RemoteException e) {
+            Log.w(TAG, "CarService crashed, finish now");
+            stopSelf();
+        }
+    }
+
+    private AlertDialog createDialog() {
+        AlertDialog.Builder builder = new AlertDialog.Builder(this);
+        AlertDialog dialog = builder.setMessage(R.string.usernotice)
+                .setPositiveButton(R.string.dismiss_now, (DialogInterface d, int w) -> {
+                    stopDialog(true);
+                })
+                .setNegativeButton(R.string.dismiss_forever, (DialogInterface d, int w) -> {
+                    Settings.Secure.putInt(getContentResolver(),
+                            CarSettings.Secure.KEY_ENABLE_INITIAL_NOTICE_SCREEN_TO_USER,
+                            /* enable= */ 0);
+                    stopDialog(true);
+                })
+                .setOnDismissListener((DialogInterface d) -> {
+                    stopDialog(false);
+                })
+                .create();
+        return dialog;
+    }
+}