1 /*
2  * Copyright (C) 2021 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.telemetry.sessioncontroller;
18 
19 import android.annotation.IntDef;
20 import android.annotation.NonNull;
21 import android.car.hardware.power.CarPowerManager;
22 import android.car.hardware.power.ICarPowerStateListener;
23 import android.content.Context;
24 import android.os.Handler;
25 import android.os.RemoteException;
26 import android.os.SystemClock;
27 import android.os.SystemProperties;
28 import android.provider.Settings;
29 
30 import com.android.car.power.CarPowerManagementService;
31 
32 import java.lang.annotation.Retention;
33 import java.lang.annotation.RetentionPolicy;
34 import java.util.ArrayList;
35 
36 /**
37  * SessionController tracks driving sessions and informs listeners when a session ends or a new
38  * session starts. There can be only one ongoing driving session at a time. Definition of a driving
39  * session is contained in the implementation of this class.
40  */
41 public class SessionController {
42     public static final int STATE_DEFAULT = 0;
43     public static final int STATE_EXIT_DRIVING_SESSION = 1;
44     public static final int STATE_ENTER_DRIVING_SESSION = 2;
45     private static final String SYSTEM_BOOT_REASON = "sys.boot.reason";
46 
47     @IntDef(
48             prefix = {"STATE_"},
49             value = {
50                     STATE_DEFAULT,
51                     STATE_EXIT_DRIVING_SESSION,
52                     STATE_ENTER_DRIVING_SESSION,
53             })
54     @Retention(RetentionPolicy.SOURCE)
55     public @interface SessionControllerState {
56     }
57 
58     private int mSessionId = 0;
59     private int mSessionState = STATE_EXIT_DRIVING_SESSION;
60     private long mStateChangedAtMillisSinceBoot; // uses SystemClock.elapsedRealtime();
61     private long mStateChangedAtMillis; // unix time
62     private String mBootReason;
63     private int mBootCount;
64     private final ArrayList<SessionControllerCallback> mSessionControllerListeners =
65             new ArrayList<>();
66     private final ICarPowerStateListener.Stub mCarPowerStateListener =
67             new ICarPowerStateListener.Stub() {
68                 @Override
69                 public void onStateChanged(int state, long expirationTime) throws RemoteException {
70                     mTelemetryHandler.post(() -> {
71                         onCarPowerStateChanged(state);
72                         // completeHandlingPowerStateChange must be called to allow
73                         // CarPowerManagementService to move on to the next power state.
74                         mCarPowerManagementService.completeHandlingPowerStateChange(state, this);
75                     });
76                 }
77             };
78 
79     private Context mContext;
80     private CarPowerManagementService mCarPowerManagementService;
81     private Handler mTelemetryHandler;
82 
83     /**
84      * Clients register {@link SessionControllerCallback} object with SessionController to receive
85      * updates whenever a new driving session starts or the ongoing session ends.
86      */
87     public interface SessionControllerCallback {
88         /**
89          * {@link SessionController} uses this method to notify listeners that a session state
90          * changed and provides additional information in the input.
91          *
92          * @param annotation Encapsulates all information relevant to session state change in a
93          *                   {@link SessionAnnotation} object.
94          */
onSessionStateChanged(SessionAnnotation annotation)95         void onSessionStateChanged(SessionAnnotation annotation);
96     }
97 
SessionController( Context context, CarPowerManagementService carPowerManagementService, Handler telemetryHandler)98     public SessionController(
99             Context context,
100             CarPowerManagementService carPowerManagementService,
101             Handler telemetryHandler) {
102         mContext = context;
103         mTelemetryHandler = telemetryHandler;
104         mCarPowerManagementService = carPowerManagementService;
105         mCarPowerManagementService.registerInternalListener(mCarPowerStateListener);
106     }
107 
onCarPowerStateChanged(int state)108     private void onCarPowerStateChanged(int state) {
109         // Driving session transitions are entirely driven by changes in the state of
110         // CarPowerManagementService. In particular, driving session begins when ON state is
111         // entered and exits when SHUTDOWN_PREPARE is entered.
112         switch (state) {
113             case CarPowerManager.STATE_SHUTDOWN_PREPARE:
114                 notifySessionStateChange(STATE_EXIT_DRIVING_SESSION);
115                 break;
116             case CarPowerManager.STATE_ON:
117                 notifySessionStateChange(STATE_ENTER_DRIVING_SESSION);
118                 break;
119             default:
120                 break;
121         }
122     }
123 
124     /**
125      * Initializes session state in cases when power state is ON by the time boot has completed.
126      * In particular, we need to handle a case when the system crashes during a drive.
127      * Calling this method after boot will start a new session and it will trigger pulling of data.
128      *
129      * <p> It must be called each time during instantiation of CarTelemetryService.
130      */
initSession()131     public void initSession() {
132         mBootReason = SystemProperties.get(SYSTEM_BOOT_REASON);
133         mBootCount = Settings.Global.getInt(
134                 mContext.getContentResolver(), Settings.Global.BOOT_COUNT, 0);
135         // Read the current power state and handle it.
136         onCarPowerStateChanged(mCarPowerManagementService.getPowerState());
137     }
138 
139     /**
140      * Returns relevant information about current session state. This information will include
141      * whether the system is in driving session, when did the session state changed most recently,
142      * etc. Please refer to the implementation of {@link SessionAnnotation} class to see what kinds
143      * of information will be returned by the method.
144      *
145      * @return Session information contained in the returned instance of {@link SessionAnnotation}
146      * class.
147      */
getSessionAnnotation()148     public SessionAnnotation getSessionAnnotation() {
149         return new SessionAnnotation(
150                 mSessionId, mSessionState, mStateChangedAtMillisSinceBoot, mStateChangedAtMillis,
151                 mBootReason, mBootCount);
152     }
153 
updateSessionState(@essionControllerState int sessionState)154     private void updateSessionState(@SessionControllerState int sessionState) {
155         mStateChangedAtMillisSinceBoot = SystemClock.elapsedRealtime();
156         mStateChangedAtMillis = System.currentTimeMillis();
157         mSessionState = sessionState;
158         if (sessionState == STATE_ENTER_DRIVING_SESSION) {
159             mSessionId++;
160         }
161     }
162 
163     /**
164      * A client uses this method to registers a callback and get informed about session state change
165      * when it happens. All session state callback instances handled one at a time on the common
166      * telemetry worker thread.
167      *
168      * @param callback An instance of {@link SessionControllerCallback} implementation.
169      */
registerCallback(@onNull SessionControllerCallback callback)170     public void registerCallback(@NonNull SessionControllerCallback callback) {
171         if (!mSessionControllerListeners.contains(callback)) {
172             mSessionControllerListeners.add(callback);
173         }
174     }
175 
176     /**
177      * Removes provided instance of a listener from the callback list.
178      *
179      * @param callback An instance of {@link SessionControllerCallback} implementation.
180      */
unregisterCallback(@onNull SessionControllerCallback callback)181     public void unregisterCallback(@NonNull SessionControllerCallback callback) {
182         mSessionControllerListeners.remove(callback);
183     }
184 
notifySessionStateChange(@essionControllerState int newSessionState)185     private void notifySessionStateChange(@SessionControllerState int newSessionState) {
186         if (mSessionState == newSessionState) {
187             return;
188         }
189         updateSessionState(newSessionState);
190         SessionAnnotation annotation = getSessionAnnotation();
191         for (SessionControllerCallback listener : mSessionControllerListeners) {
192             listener.onSessionStateChanged(annotation);
193         }
194     }
195 
196     /** Gracefully cleans up the class state when the car telemetry service is shutting down. */
release()197     public void release() {
198         mCarPowerManagementService.unregisterInternalListener(mCarPowerStateListener);
199     }
200 }
201