• 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.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