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