1 /*
2  * Copyright (C) 2022 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.systemui.log;
18 
19 import static android.app.StatusBarManager.ALL_SESSIONS;
20 import static android.app.StatusBarManager.SESSION_BIOMETRIC_PROMPT;
21 import static android.app.StatusBarManager.SESSION_KEYGUARD;
22 
23 import android.annotation.Nullable;
24 import android.os.RemoteException;
25 import android.util.Log;
26 
27 import androidx.annotation.NonNull;
28 
29 import com.android.internal.logging.InstanceId;
30 import com.android.internal.logging.InstanceIdSequence;
31 import com.android.internal.logging.UiEvent;
32 import com.android.internal.logging.UiEventLogger;
33 import com.android.internal.statusbar.IStatusBarService;
34 import com.android.keyguard.KeyguardUpdateMonitor;
35 import com.android.keyguard.KeyguardUpdateMonitorCallback;
36 import com.android.systemui.CoreStartable;
37 import com.android.systemui.biometrics.AuthController;
38 import com.android.systemui.dagger.SysUISingleton;
39 import com.android.systemui.statusbar.policy.KeyguardStateController;
40 
41 import java.io.PrintWriter;
42 import java.util.HashMap;
43 import java.util.Map;
44 
45 import javax.inject.Inject;
46 
47 /**
48  * Track Session InstanceIds to be used for metrics logging to correlate logs in the same
49  * session. Can be used across processes via StatusBarManagerService#registerSessionListener
50  */
51 @SysUISingleton
52 public class SessionTracker implements CoreStartable {
53     private static final String TAG = "SessionTracker";
54 
55     // To enable logs: `adb shell setprop log.tag.SessionTracker DEBUG` & restart sysui
56     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
57 
58     // At most 20 bits: ~1m possibilities, ~0.5% probability of collision in 100 values
59     private final InstanceIdSequence mInstanceIdGenerator = new InstanceIdSequence(1 << 20);
60 
61     private final IStatusBarService mStatusBarManagerService;
62     private final AuthController mAuthController;
63     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
64     private final KeyguardStateController mKeyguardStateController;
65     private final UiEventLogger mUiEventLogger;
66     private final Map<Integer, InstanceId> mSessionToInstanceId = new HashMap<>();
67 
68     private boolean mKeyguardSessionStarted;
69 
70     @Inject
SessionTracker( IStatusBarService statusBarService, AuthController authController, KeyguardUpdateMonitor keyguardUpdateMonitor, KeyguardStateController keyguardStateController, UiEventLogger uiEventLogger )71     public SessionTracker(
72             IStatusBarService statusBarService,
73             AuthController authController,
74             KeyguardUpdateMonitor keyguardUpdateMonitor,
75             KeyguardStateController keyguardStateController,
76             UiEventLogger uiEventLogger
77     ) {
78         mStatusBarManagerService = statusBarService;
79         mAuthController = authController;
80         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
81         mKeyguardStateController = keyguardStateController;
82         mUiEventLogger = uiEventLogger;
83     }
84 
85     @Override
start()86     public void start() {
87         mAuthController.addCallback(mAuthControllerCallback);
88         mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateMonitorCallback);
89         mKeyguardStateController.addCallback(mKeyguardStateCallback);
90 
91         if (mKeyguardStateController.isShowing()) {
92             mKeyguardSessionStarted = true;
93             startSession(SESSION_KEYGUARD);
94         }
95     }
96 
97     /**
98      * Get the session ID associated with the passed session type.
99      */
getSessionId(int type)100     public @Nullable InstanceId getSessionId(int type) {
101         return mSessionToInstanceId.getOrDefault(type, null);
102     }
103 
startSession(int type)104     private void startSession(int type) {
105         if (mSessionToInstanceId.getOrDefault(type, null) != null) {
106             Log.e(TAG, "session [" + getString(type) + "] was already started");
107             return;
108         }
109 
110         final InstanceId instanceId = mInstanceIdGenerator.newInstanceId();
111         mSessionToInstanceId.put(type, instanceId);
112         try {
113             if (DEBUG) {
114                 Log.d(TAG, "Session start for [" + getString(type) + "] id=" + instanceId);
115             }
116             mStatusBarManagerService.onSessionStarted(type, instanceId);
117         } catch (RemoteException e) {
118             Log.e(TAG, "Unable to send onSessionStarted for session="
119                     + "[" + getString(type) + "]", e);
120         }
121     }
122 
endSession(int type)123     private void endSession(int type) {
124         endSession(type, null);
125     }
126 
endSession(int type, @Nullable SessionUiEvent endSessionUiEvent)127     private void endSession(int type, @Nullable SessionUiEvent endSessionUiEvent) {
128         if (mSessionToInstanceId.getOrDefault(type, null) == null) {
129             Log.e(TAG, "session [" + getString(type) + "] was not started");
130             return;
131         }
132 
133         final InstanceId instanceId = mSessionToInstanceId.get(type);
134         mSessionToInstanceId.put(type, null);
135         try {
136             if (DEBUG) {
137                 Log.d(TAG, "Session end for [" + getString(type) + "] id=" + instanceId);
138             }
139             if (endSessionUiEvent != null) {
140                 mUiEventLogger.log(endSessionUiEvent, instanceId);
141             }
142             mStatusBarManagerService.onSessionEnded(type, instanceId);
143         } catch (RemoteException e) {
144             Log.e(TAG, "Unable to send onSessionEnded for session="
145                     + "[" + getString(type) + "]", e);
146         }
147     }
148 
149     public KeyguardUpdateMonitorCallback mKeyguardUpdateMonitorCallback =
150             new KeyguardUpdateMonitorCallback() {
151         @Override
152         public void onStartedGoingToSleep(int why) {
153             if (mKeyguardSessionStarted) {
154                 endSession(SESSION_KEYGUARD, SessionUiEvent.KEYGUARD_SESSION_END_GOING_TO_SLEEP);
155             }
156 
157             // Start a new session whenever the device goes to sleep
158             mKeyguardSessionStarted = true;
159             startSession(SESSION_KEYGUARD);
160         }
161     };
162 
163 
164     public KeyguardStateController.Callback mKeyguardStateCallback =
165             new KeyguardStateController.Callback() {
166         public void onKeyguardShowingChanged() {
167             boolean wasSessionStarted = mKeyguardSessionStarted;
168             boolean keyguardShowing = mKeyguardStateController.isShowing();
169             if (keyguardShowing && !wasSessionStarted) {
170                 // the keyguard can start showing without the device going to sleep (ie: lockdown
171                 // from the power button), so we start a new keyguard session when the keyguard is
172                 // newly shown in addition to when the device starts going to sleep
173                 mKeyguardSessionStarted = true;
174                 startSession(SESSION_KEYGUARD);
175             } else if (!keyguardShowing && wasSessionStarted) {
176                 mKeyguardSessionStarted = false;
177                 endSession(SESSION_KEYGUARD,
178                         SessionUiEvent.KEYGUARD_SESSION_END_KEYGUARD_GOING_AWAY);
179             }
180         }
181     };
182 
183     public AuthController.Callback mAuthControllerCallback = new AuthController.Callback() {
184         @Override
185         public void onBiometricPromptShown() {
186             startSession(SESSION_BIOMETRIC_PROMPT);
187         }
188 
189         @Override
190         public void onBiometricPromptDismissed() {
191             endSession(SESSION_BIOMETRIC_PROMPT);
192         }
193     };
194 
195     @Override
dump(@onNull PrintWriter pw, @NonNull String[] args)196     public void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
197         for (int session : ALL_SESSIONS) {
198             pw.println("  " + getString(session)
199                     + " instanceId=" + mSessionToInstanceId.get(session));
200         }
201     }
202 
203     /**
204      * @return the string representation of a SINGLE SessionFlag. Combined SessionFlags will be
205      * considered unknown.
206      */
getString(int sessionType)207     public static String getString(int sessionType) {
208         if (sessionType == SESSION_KEYGUARD) {
209             return "KEYGUARD";
210         } else if (sessionType == SESSION_BIOMETRIC_PROMPT) {
211             return "BIOMETRIC_PROMPT";
212         }
213 
214         return "unknownType=" + sessionType;
215     }
216 
217     enum SessionUiEvent implements UiEventLogger.UiEventEnum {
218         @UiEvent(doc = "A keyguard session ended due to the keyguard going away.")
219         KEYGUARD_SESSION_END_KEYGUARD_GOING_AWAY(1354),
220 
221         @UiEvent(doc = "A keyguard session ended due to display going to sleep.")
222         KEYGUARD_SESSION_END_GOING_TO_SLEEP(1355);
223 
224         private final int mId;
SessionUiEvent(int id)225         SessionUiEvent(int id) {
226             mId = id;
227         }
228 
229         @Override
getId()230         public int getId() {
231             return mId;
232         }
233     }
234 }
235