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