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.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO; 23 import static com.android.car.util.Utils.getContentResolverForUser; 24 import static com.android.car.util.Utils.isEventOfType; 25 26 import android.annotation.Nullable; 27 import android.annotation.UserIdInt; 28 import android.app.ActivityManager; 29 import android.app.AppOpsManager; 30 import android.car.CarNotConnectedException; 31 import android.car.builtin.app.KeyguardManagerHelper; 32 import android.car.builtin.content.pm.PackageManagerHelper; 33 import android.car.builtin.os.UserManagerHelper; 34 import android.car.builtin.util.Slogf; 35 import android.car.hardware.power.CarPowerManager; 36 import android.car.settings.CarSettings; 37 import android.car.user.CarUserManager.UserLifecycleListener; 38 import android.car.user.IUserNotice; 39 import android.car.user.IUserNoticeUI; 40 import android.car.user.UserLifecycleEventFilter; 41 import android.content.BroadcastReceiver; 42 import android.content.ComponentName; 43 import android.content.Context; 44 import android.content.Intent; 45 import android.content.IntentFilter; 46 import android.content.ServiceConnection; 47 import android.content.pm.PackageManager; 48 import android.content.res.Resources; 49 import android.os.Handler; 50 import android.os.IBinder; 51 import android.os.PowerManager; 52 import android.os.RemoteException; 53 import android.os.UserHandle; 54 import android.provider.Settings; 55 import android.util.Log; 56 57 import com.android.car.CarLocalServices; 58 import com.android.car.CarLog; 59 import com.android.car.CarServiceBase; 60 import com.android.car.CarServiceUtils; 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(CarServiceUtils.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 mCommonThreadHandler.removeCallbacks(mKeyguardPollingRunnable); 255 boolean locked = KeyguardManagerHelper.isKeyguardLocked(); 256 if (locked) { 257 mCommonThreadHandler.postDelayed(mKeyguardPollingRunnable, 258 KEYGUARD_POLLING_INTERVAL_MS); 259 } 260 return locked; 261 } 262 isNoticeScreenEnabledInSetting(@serIdInt int userId)263 private boolean isNoticeScreenEnabledInSetting(@UserIdInt int userId) { 264 return Settings.Secure.getInt(getContentResolverForUser(mContext, userId), 265 CarSettings.Secure.KEY_ENABLE_INITIAL_NOTICE_SCREEN_TO_USER, 266 INITIAL_NOTICE_SCREEN_TO_USER_ENABLED) == INITIAL_NOTICE_SCREEN_TO_USER_ENABLED; 267 } 268 isDisplayOn()269 private boolean isDisplayOn() { 270 PowerManager pm = mContext.getSystemService(PowerManager.class); 271 if (pm == null) { 272 return false; 273 } 274 return pm.isInteractive(); 275 } 276 grantSystemAlertWindowPermission(@serIdInt int userId)277 private boolean grantSystemAlertWindowPermission(@UserIdInt int userId) { 278 AppOpsManager appOpsManager = mContext.getSystemService(AppOpsManager.class); 279 if (appOpsManager == null) { 280 Slogf.w(TAG, "AppOpsManager not ready yet"); 281 return false; 282 } 283 String packageName = mServiceIntent.getComponent().getPackageName(); 284 int packageUid; 285 try { 286 packageUid = PackageManagerHelper.getPackageUidAsUser(mContext.getPackageManager(), 287 packageName, userId); 288 } catch (PackageManager.NameNotFoundException e) { 289 Slogf.wtf(TAG, "Target package for config_userNoticeUiService not found:" 290 + packageName + " userId:" + userId); 291 return false; 292 } 293 appOpsManager.setMode(AppOpsManager.OPSTR_SYSTEM_ALERT_WINDOW, packageUid, packageName, 294 AppOpsManager.MODE_ALLOWED); 295 Slogf.i(TAG, "Granted SYSTEM_ALERT_WINDOW permission to package:" + packageName 296 + " package uid:" + packageUid); 297 return true; 298 } 299 startNoticeUiIfNecessary()300 private void startNoticeUiIfNecessary() { 301 int userId; 302 synchronized (mLock) { 303 if (mUiShown || mServiceBound) { 304 if (DBG) { 305 Slogf.d(TAG, "Notice UI not necessary: mUiShown " + mUiShown + " mServiceBound " 306 + mServiceBound); 307 } 308 return; 309 } 310 userId = mUserId; 311 if (mIgnoreUserId == userId) { 312 if (DBG) { 313 Slogf.d(TAG, "Notice UI not necessary: mIgnoreUserId " + mIgnoreUserId 314 + " userId " + userId); 315 } 316 return; 317 } else { 318 mIgnoreUserId = UserManagerHelper.USER_NULL; 319 } 320 } 321 if (userId == UserManagerHelper.USER_NULL) { 322 if (DBG) Slogf.d(TAG, "Notice UI not necessary: userId " + userId); 323 return; 324 } 325 // headless user 0 is ignored. 326 if (userId == UserHandle.SYSTEM.getIdentifier()) { 327 if (DBG) Slogf.d(TAG, "Notice UI not necessary: userId " + userId); 328 return; 329 } 330 if (!isNoticeScreenEnabledInSetting(userId)) { 331 if (DBG) { 332 Slogf.d(TAG, "Notice UI not necessary as notice screen not enabled in settings."); 333 } 334 return; 335 } 336 if (userId != ActivityManager.getCurrentUser()) { 337 if (DBG) { 338 Slogf.d(TAG, "Notice UI not necessary as user has switched. will be handled by user" 339 + " switch callback."); 340 } 341 return; 342 } 343 // Dialog can be not shown if display is off. 344 // DISPLAY_ON broadcast will handle this later. 345 if (!isDisplayOn()) { 346 if (DBG) Slogf.d(TAG, "Notice UI not necessary as display is off."); 347 return; 348 } 349 // Do not show it until keyguard is dismissed. 350 if (checkKeyguardLockedWithPolling()) { 351 if (DBG) Slogf.d(TAG, "Notice UI not necessary as keyguard is not dismissed."); 352 return; 353 } 354 if (!grantSystemAlertWindowPermission(userId)) { 355 if (DBG) { 356 Slogf.d(TAG, "Notice UI not necessary as System Alert Window Permission not" 357 + " granted."); 358 } 359 return; 360 } 361 boolean bound = mContext.bindServiceAsUser(mServiceIntent, mUiServiceConnection, 362 Context.BIND_AUTO_CREATE, UserHandle.of(userId)); 363 if (bound) { 364 Slogf.i(TAG, "Bound UserNoticeUI Service: " + mServiceIntent); 365 synchronized (mLock) { 366 mServiceBound = true; 367 mUiShown = true; 368 } 369 } else { 370 Slogf.w(TAG, "Cannot bind to UserNoticeUI Service Service" + mServiceIntent); 371 } 372 } 373 stopUi(boolean clearUiShown)374 private void stopUi(boolean clearUiShown) { 375 mCommonThreadHandler.removeCallbacks(mKeyguardPollingRunnable); 376 boolean serviceBound; 377 synchronized (mLock) { 378 mUiService = null; 379 serviceBound = mServiceBound; 380 mServiceBound = false; 381 if (clearUiShown) { 382 mUiShown = false; 383 } 384 } 385 if (serviceBound) { 386 Slogf.i(TAG, "Unbound UserNoticeUI Service"); 387 mContext.unbindService(mUiServiceConnection); 388 } 389 } 390 391 @Override init()392 public void init() { 393 if (mServiceIntent == null) { 394 // feature disabled 395 return; 396 } 397 398 CarPowerManager carPowerManager; 399 synchronized (mLock) { 400 mCarPowerManager = CarLocalServices.createCarPowerManager(mContext); 401 carPowerManager = mCarPowerManager; 402 } 403 try { 404 carPowerManager.setListener(mContext.getMainExecutor(), mPowerStateListener); 405 } catch (CarNotConnectedException e) { 406 // should not happen 407 throw new RuntimeException("CarNotConnectedException from CarPowerManager", e); 408 } 409 CarUserService userService = CarLocalServices.getService(CarUserService.class); 410 UserLifecycleEventFilter userSwitchingEventFilter = new UserLifecycleEventFilter.Builder() 411 .addEventType(USER_LIFECYCLE_EVENT_TYPE_SWITCHING).build(); 412 userService.addUserLifecycleListener(userSwitchingEventFilter, mUserLifecycleListener); 413 IntentFilter intentFilter = new IntentFilter(); 414 intentFilter.addAction(Intent.ACTION_SCREEN_OFF); 415 intentFilter.addAction(Intent.ACTION_SCREEN_ON); 416 mContext.registerReceiver(mDisplayBroadcastReceiver, intentFilter, 417 Context.RECEIVER_NOT_EXPORTED); 418 } 419 420 @Override release()421 public void release() { 422 if (mServiceIntent == null) { 423 // feature disabled 424 return; 425 } 426 mContext.unregisterReceiver(mDisplayBroadcastReceiver); 427 CarUserService userService = CarLocalServices.getService(CarUserService.class); 428 userService.removeUserLifecycleListener(mUserLifecycleListener); 429 CarPowerManager carPowerManager; 430 synchronized (mLock) { 431 carPowerManager = mCarPowerManager; 432 mUserId = UserManagerHelper.USER_NULL; 433 } 434 carPowerManager.clearListener(); 435 stopUi(/* clearUiShown= */ true); 436 } 437 438 @Override 439 @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO) dump(IndentingPrintWriter writer)440 public void dump(IndentingPrintWriter writer) { 441 synchronized (mLock) { 442 if (mServiceIntent == null) { 443 writer.println("*CarUserNoticeService* disabled"); 444 return; 445 } 446 if (mUserId == UserManagerHelper.USER_NULL) { 447 writer.println("*CarUserNoticeService* User not started yet."); 448 return; 449 } 450 writer.println("*CarUserNoticeService* mServiceIntent:" + mServiceIntent 451 + ", mUserId:" + mUserId 452 + ", mUiShown:" + mUiShown 453 + ", mServiceBound:" + mServiceBound 454 + ", mKeyguardPollingCounter:" + mKeyguardPollingCounter 455 + ", Setting enabled:" + isNoticeScreenEnabledInSetting(mUserId) 456 + ", Ignore User: " + mIgnoreUserId); 457 } 458 } 459 } 460