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