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