1 /* 2 * Copyright (C) 2019 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.car.user; 18 19 import static android.car.hardware.power.CarPowerManager.CarPowerStateListener; 20 import static android.car.user.CarUserManager.USER_LIFECYCLE_EVENT_TYPE_SWITCHING; 21 22 import static com.android.car.CarServiceUtils.getCommonHandlerThread; 23 import static com.android.car.CarServiceUtils.getContentResolverForUser; 24 import static com.android.car.CarServiceUtils.isEventOfType; 25 import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO; 26 27 import android.annotation.Nullable; 28 import android.annotation.UserIdInt; 29 import android.app.ActivityManager; 30 import android.app.AppOpsManager; 31 import android.car.CarNotConnectedException; 32 import android.car.builtin.app.KeyguardManagerHelper; 33 import android.car.builtin.content.pm.PackageManagerHelper; 34 import android.car.builtin.os.UserManagerHelper; 35 import android.car.builtin.util.Slogf; 36 import android.car.hardware.power.CarPowerManager; 37 import android.car.settings.CarSettings; 38 import android.car.user.CarUserManager.UserLifecycleListener; 39 import android.car.user.IUserNotice; 40 import android.car.user.IUserNoticeUI; 41 import android.car.user.UserLifecycleEventFilter; 42 import android.content.BroadcastReceiver; 43 import android.content.ComponentName; 44 import android.content.Context; 45 import android.content.Intent; 46 import android.content.IntentFilter; 47 import android.content.ServiceConnection; 48 import android.content.pm.PackageManager; 49 import android.content.res.Resources; 50 import android.os.Handler; 51 import android.os.IBinder; 52 import android.os.PowerManager; 53 import android.os.RemoteException; 54 import android.os.UserHandle; 55 import android.provider.Settings; 56 import android.util.Log; 57 58 import com.android.car.CarLocalServices; 59 import com.android.car.CarLog; 60 import com.android.car.CarServiceBase; 61 import com.android.car.R; 62 import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport; 63 import com.android.car.internal.util.IndentingPrintWriter; 64 import com.android.internal.annotations.GuardedBy; 65 import com.android.internal.annotations.VisibleForTesting; 66 67 /** 68 * Service to show initial notice UI to user. It only launches it when setting is enabled and 69 * it is up to notice UI (=Service) to dismiss itself upon user's request. 70 * 71 * <p>Conditions to show notice UI are: 72 * <ol> 73 * <li>Cold boot 74 * <li><User switching 75 * <li>Car power state change to ON (happens in wakeup from suspend to RAM) 76 * </ol> 77 */ 78 public final class CarUserNoticeService implements CarServiceBase { 79 80 @VisibleForTesting 81 static final String TAG = CarLog.tagFor(CarUserNoticeService.class); 82 83 private static final boolean DBG = Slogf.isLoggable(TAG, Log.DEBUG); 84 85 // Keyguard unlocking can be only polled as we cannot dismiss keyboard. 86 // Polling will stop when keyguard is unlocked. 87 private static final long KEYGUARD_POLLING_INTERVAL_MS = 100; 88 89 // Value of the settings when it's enabled 90 private static final int INITIAL_NOTICE_SCREEN_TO_USER_ENABLED = 1; 91 92 private final Context mContext; 93 94 // null means feature disabled. 95 @Nullable 96 private final Intent mServiceIntent; 97 98 private final Handler mCommonThreadHandler; 99 100 private final Object mLock = new Object(); 101 102 // This one records if there is a service bound. This will be cleared as soon as service is 103 // unbound (=UI dismissed) 104 @GuardedBy("mLock") 105 private boolean mServiceBound = false; 106 107 // This one represents if UI is shown for the current session. This should be kept until 108 // next event to show UI comes up. 109 @GuardedBy("mLock") 110 private boolean mUiShown = false; 111 112 @GuardedBy("mLock") 113 @UserIdInt 114 private int mUserId = UserManagerHelper.USER_NULL; 115 116 @GuardedBy("mLock") 117 private CarPowerManager mCarPowerManager; 118 119 @GuardedBy("mLock") 120 private IUserNoticeUI mUiService; 121 122 @GuardedBy("mLock") 123 @UserIdInt 124 private int mIgnoreUserId = UserManagerHelper.USER_NULL; 125 126 private final UserLifecycleListener mUserLifecycleListener = event -> { 127 if (!isEventOfType(TAG, event, USER_LIFECYCLE_EVENT_TYPE_SWITCHING)) { 128 return; 129 } 130 131 int userId = event.getUserId(); 132 if (DBG) { 133 Slogf.d(TAG, "User switch event received. Target User: %d", userId); 134 } 135 136 CarUserNoticeService.this.mCommonThreadHandler.post(() -> { 137 stopUi(/* clearUiShown= */ true); 138 synchronized (mLock) { 139 // This should be the only place to change user 140 mUserId = userId; 141 } 142 startNoticeUiIfNecessary(); 143 }); 144 }; 145 146 private final CarPowerStateListener mPowerStateListener = new CarPowerStateListener() { 147 @Override 148 public void onStateChanged(int state) { 149 if (state == CarPowerManager.STATE_SHUTDOWN_PREPARE) { 150 mCommonThreadHandler.post(() -> stopUi(/* clearUiShown= */ true)); 151 } else if (state == CarPowerManager.STATE_ON) { 152 // Only ON can be relied on as car can restart while in garage mode. 153 mCommonThreadHandler.post(() -> startNoticeUiIfNecessary()); 154 } 155 } 156 }; 157 158 private final BroadcastReceiver mDisplayBroadcastReceiver = new BroadcastReceiver() { 159 @Override 160 public void onReceive(Context context, Intent intent) { 161 // Runs in main thread, so do not use Handler. 162 if (Intent.ACTION_SCREEN_OFF.equals(intent.getAction())) { 163 if (isDisplayOn()) { 164 Slogf.i(TAG, "SCREEN_OFF while display is already on"); 165 return; 166 } 167 Slogf.i(TAG, "Display off, stopping UI"); 168 stopUi(/* clearUiShown= */ true); 169 } else if (Intent.ACTION_SCREEN_ON.equals(intent.getAction())) { 170 if (!isDisplayOn()) { 171 Slogf.i(TAG, "SCREEN_ON while display is already off"); 172 return; 173 } 174 Slogf.i(TAG, "Display on, starting UI"); 175 startNoticeUiIfNecessary(); 176 } 177 } 178 }; 179 180 private final IUserNotice.Stub mIUserNotice = new IUserNotice.Stub() { 181 @Override 182 public void onDialogDismissed() { 183 mCommonThreadHandler.post(() -> stopUi(/* clearUiShown= */ false)); 184 } 185 }; 186 187 private final ServiceConnection mUiServiceConnection = new ServiceConnection() { 188 @Override 189 public void onServiceConnected(ComponentName name, IBinder service) { 190 synchronized (mLock) { 191 if (!mServiceBound) { 192 // already unbound but passed due to timing. This should be just ignored. 193 return; 194 } 195 } 196 IUserNoticeUI binder = IUserNoticeUI.Stub.asInterface(service); 197 try { 198 binder.setCallbackBinder(mIUserNotice); 199 } catch (RemoteException e) { 200 Slogf.w(TAG, "UserNoticeUI Service died", e); 201 // Wait for reconnect 202 binder = null; 203 } 204 synchronized (mLock) { 205 mUiService = binder; 206 } 207 } 208 209 @Override 210 public void onServiceDisconnected(ComponentName name) { 211 // UI crashed. Stop it so that it does not come again. 212 stopUi(/* clearUiShown= */ true); 213 } 214 }; 215 216 // added for debugging purpose 217 @GuardedBy("mLock") 218 private int mKeyguardPollingCounter; 219 220 private final Runnable mKeyguardPollingRunnable = () -> { 221 synchronized (mLock) { 222 mKeyguardPollingCounter++; 223 } 224 startNoticeUiIfNecessary(); 225 }; 226 CarUserNoticeService(Context context)227 public CarUserNoticeService(Context context) { 228 this(context, new Handler(getCommonHandlerThread().getLooper())); 229 } 230 231 @VisibleForTesting CarUserNoticeService(Context context, Handler handler)232 CarUserNoticeService(Context context, Handler handler) { 233 mCommonThreadHandler = handler; 234 Resources res = context.getResources(); 235 String componentName = res.getString(R.string.config_userNoticeUiService); 236 if (componentName.isEmpty()) { 237 // feature disabled 238 mContext = null; 239 mServiceIntent = null; 240 return; 241 } 242 mContext = context; 243 mServiceIntent = new Intent(); 244 mServiceIntent.setComponent(ComponentName.unflattenFromString(componentName)); 245 } 246 ignoreUserNotice(int userId)247 public void ignoreUserNotice(int userId) { 248 synchronized (mLock) { 249 mIgnoreUserId = userId; 250 } 251 } 252 checkKeyguardLockedWithPolling()253 private boolean checkKeyguardLockedWithPolling() { 254 removeCallbacks(); 255 boolean locked = KeyguardManagerHelper.isKeyguardLocked(); 256 if (locked) { 257 mCommonThreadHandler.postDelayed(mKeyguardPollingRunnable, 258 KEYGUARD_POLLING_INTERVAL_MS); 259 } 260 return locked; 261 } 262 263 @VisibleForTesting removeCallbacks()264 void removeCallbacks() { 265 mCommonThreadHandler.removeCallbacks(mKeyguardPollingRunnable); 266 } 267 isNoticeScreenEnabledInSetting(@serIdInt int userId)268 private boolean isNoticeScreenEnabledInSetting(@UserIdInt int userId) { 269 return Settings.Secure.getInt(getContentResolverForUser(mContext, userId), 270 CarSettings.Secure.KEY_ENABLE_INITIAL_NOTICE_SCREEN_TO_USER, 271 INITIAL_NOTICE_SCREEN_TO_USER_ENABLED) == INITIAL_NOTICE_SCREEN_TO_USER_ENABLED; 272 } 273 isDisplayOn()274 private boolean isDisplayOn() { 275 PowerManager pm = mContext.getSystemService(PowerManager.class); 276 if (pm == null) { 277 return false; 278 } 279 return pm.isInteractive(); 280 } 281 grantSystemAlertWindowPermission(@serIdInt int userId)282 private boolean grantSystemAlertWindowPermission(@UserIdInt int userId) { 283 AppOpsManager appOpsManager = mContext.getSystemService(AppOpsManager.class); 284 if (appOpsManager == null) { 285 Slogf.w(TAG, "AppOpsManager not ready yet"); 286 return false; 287 } 288 String packageName = mServiceIntent.getComponent().getPackageName(); 289 int packageUid; 290 try { 291 packageUid = PackageManagerHelper.getPackageUidAsUser(mContext.getPackageManager(), 292 packageName, userId); 293 } catch (PackageManager.NameNotFoundException e) { 294 Slogf.wtf(TAG, "Target package for config_userNoticeUiService not found:" 295 + packageName + " userId:" + userId); 296 return false; 297 } 298 appOpsManager.setMode(AppOpsManager.OPSTR_SYSTEM_ALERT_WINDOW, packageUid, packageName, 299 AppOpsManager.MODE_ALLOWED); 300 Slogf.i(TAG, "Granted SYSTEM_ALERT_WINDOW permission to package:" + packageName 301 + " package uid:" + packageUid); 302 return true; 303 } 304 startNoticeUiIfNecessary()305 private void startNoticeUiIfNecessary() { 306 int userId; 307 synchronized (mLock) { 308 if (mUiShown || mServiceBound) { 309 if (DBG) { 310 Slogf.d(TAG, "Notice UI not necessary: mUiShown " + mUiShown + " mServiceBound " 311 + mServiceBound); 312 } 313 return; 314 } 315 userId = mUserId; 316 if (mIgnoreUserId == userId) { 317 if (DBG) { 318 Slogf.d(TAG, "Notice UI not necessary: mIgnoreUserId " + mIgnoreUserId 319 + " userId " + userId); 320 } 321 return; 322 } else { 323 mIgnoreUserId = UserManagerHelper.USER_NULL; 324 } 325 } 326 if (userId == UserManagerHelper.USER_NULL) { 327 if (DBG) Slogf.d(TAG, "Notice UI not necessary: userId " + userId); 328 return; 329 } 330 // headless user 0 is ignored. 331 if (userId == UserHandle.SYSTEM.getIdentifier()) { 332 if (DBG) Slogf.d(TAG, "Notice UI not necessary: userId " + userId); 333 return; 334 } 335 if (!isNoticeScreenEnabledInSetting(userId)) { 336 if (DBG) { 337 Slogf.d(TAG, "Notice UI not necessary as notice screen not enabled in settings."); 338 } 339 return; 340 } 341 if (userId != ActivityManager.getCurrentUser()) { 342 if (DBG) { 343 Slogf.d(TAG, "Notice UI not necessary as user has switched. will be handled by user" 344 + " switch callback."); 345 } 346 return; 347 } 348 // Dialog can be not shown if display is off. 349 // DISPLAY_ON broadcast will handle this later. 350 if (!isDisplayOn()) { 351 if (DBG) Slogf.d(TAG, "Notice UI not necessary as display is off."); 352 return; 353 } 354 // Do not show it until keyguard is dismissed. 355 if (checkKeyguardLockedWithPolling()) { 356 if (DBG) Slogf.d(TAG, "Notice UI not necessary as keyguard is not dismissed."); 357 return; 358 } 359 if (!grantSystemAlertWindowPermission(userId)) { 360 if (DBG) { 361 Slogf.d(TAG, "Notice UI not necessary as System Alert Window Permission not" 362 + " granted."); 363 } 364 return; 365 } 366 boolean bound = mContext.bindServiceAsUser(mServiceIntent, mUiServiceConnection, 367 Context.BIND_AUTO_CREATE, UserHandle.of(userId)); 368 if (bound) { 369 Slogf.i(TAG, "Bound UserNoticeUI Service: " + mServiceIntent); 370 synchronized (mLock) { 371 mServiceBound = true; 372 mUiShown = true; 373 } 374 } else { 375 Slogf.w(TAG, "Cannot bind to UserNoticeUI Service Service" + mServiceIntent); 376 } 377 } 378 stopUi(boolean clearUiShown)379 private void stopUi(boolean clearUiShown) { 380 removeCallbacks(); 381 boolean serviceBound; 382 synchronized (mLock) { 383 mUiService = null; 384 serviceBound = mServiceBound; 385 mServiceBound = false; 386 if (clearUiShown) { 387 mUiShown = false; 388 } 389 } 390 if (serviceBound) { 391 Slogf.i(TAG, "Unbound UserNoticeUI Service"); 392 mContext.unbindService(mUiServiceConnection); 393 } 394 } 395 396 @Override init()397 public void init() { 398 if (mServiceIntent == null) { 399 // feature disabled 400 return; 401 } 402 403 CarPowerManager carPowerManager; 404 synchronized (mLock) { 405 mCarPowerManager = CarLocalServices.createCarPowerManager(mContext); 406 carPowerManager = mCarPowerManager; 407 } 408 try { 409 carPowerManager.setListener(mContext.getMainExecutor(), mPowerStateListener); 410 } catch (CarNotConnectedException e) { 411 // should not happen 412 throw new RuntimeException("CarNotConnectedException from CarPowerManager", e); 413 } 414 CarUserService userService = CarLocalServices.getService(CarUserService.class); 415 UserLifecycleEventFilter userSwitchingEventFilter = new UserLifecycleEventFilter.Builder() 416 .addEventType(USER_LIFECYCLE_EVENT_TYPE_SWITCHING).build(); 417 userService.addUserLifecycleListener(userSwitchingEventFilter, mUserLifecycleListener); 418 IntentFilter intentFilter = new IntentFilter(); 419 intentFilter.addAction(Intent.ACTION_SCREEN_OFF); 420 intentFilter.addAction(Intent.ACTION_SCREEN_ON); 421 mContext.registerReceiver(mDisplayBroadcastReceiver, intentFilter, 422 Context.RECEIVER_NOT_EXPORTED); 423 } 424 425 @Override release()426 public void release() { 427 if (mServiceIntent == null) { 428 // feature disabled 429 return; 430 } 431 mContext.unregisterReceiver(mDisplayBroadcastReceiver); 432 CarUserService userService = CarLocalServices.getService(CarUserService.class); 433 userService.removeUserLifecycleListener(mUserLifecycleListener); 434 CarPowerManager carPowerManager; 435 synchronized (mLock) { 436 carPowerManager = mCarPowerManager; 437 mUserId = UserManagerHelper.USER_NULL; 438 } 439 carPowerManager.clearListener(); 440 stopUi(/* clearUiShown= */ true); 441 } 442 443 @Override 444 @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO) dump(IndentingPrintWriter writer)445 public void dump(IndentingPrintWriter writer) { 446 synchronized (mLock) { 447 if (mServiceIntent == null) { 448 writer.println("*CarUserNoticeService* disabled"); 449 return; 450 } 451 if (mUserId == UserManagerHelper.USER_NULL) { 452 writer.println("*CarUserNoticeService* User not started yet."); 453 return; 454 } 455 writer.println("*CarUserNoticeService* mServiceIntent:" + mServiceIntent 456 + ", mUserId:" + mUserId 457 + ", mUiShown:" + mUiShown 458 + ", mServiceBound:" + mServiceBound 459 + ", mKeyguardPollingCounter:" + mKeyguardPollingCounter 460 + ", Setting enabled:" + isNoticeScreenEnabledInSetting(mUserId) 461 + ", Ignore User: " + mIgnoreUserId); 462 } 463 } 464 } 465