• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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