1 /*
2  * Copyright (C) 2018 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.internal.os;
18 
19 
20 import android.os.SystemClock;
21 
22 import com.android.internal.annotations.GuardedBy;
23 import com.android.internal.annotations.VisibleForTesting;
24 
25 import java.util.ArrayList;
26 
27 /**
28  * Stores the device state (e.g. charging/on battery, screen on/off) to be shared with
29  * the System Server telemetry services.
30  *
31  * @hide
32  */
33 @android.ravenwood.annotation.RavenwoodKeepWholeClass
34 public class CachedDeviceState {
35     private volatile boolean mScreenInteractive;
36     private volatile boolean mCharging;
37     private final Object mStopwatchesLock = new Object();
38     @GuardedBy("mStopwatchLock")
39     private final ArrayList<TimeInStateStopwatch> mOnBatteryStopwatches = new ArrayList<>();
40 
CachedDeviceState()41     public CachedDeviceState() {
42         mCharging = true;
43         mScreenInteractive = false;
44     }
45 
46     @VisibleForTesting
CachedDeviceState(boolean isCharging, boolean isScreenInteractive)47     public CachedDeviceState(boolean isCharging, boolean isScreenInteractive) {
48         mCharging = isCharging;
49         mScreenInteractive = isScreenInteractive;
50     }
51 
setScreenInteractive(boolean screenInteractive)52     public void setScreenInteractive(boolean screenInteractive) {
53         mScreenInteractive = screenInteractive;
54     }
55 
setCharging(boolean charging)56     public void setCharging(boolean charging) {
57         if (mCharging != charging) {
58             mCharging = charging;
59             updateStopwatches(/* shouldStart= */ !charging);
60         }
61     }
62 
updateStopwatches(boolean shouldStart)63     private void updateStopwatches(boolean shouldStart) {
64         synchronized (mStopwatchesLock) {
65             final int size = mOnBatteryStopwatches.size();
66             for (int i = 0; i < size; i++) {
67                 if (shouldStart) {
68                     mOnBatteryStopwatches.get(i).start();
69                 } else {
70                     mOnBatteryStopwatches.get(i).stop();
71                 }
72             }
73         }
74     }
75 
getReadonlyClient()76     public Readonly getReadonlyClient() {
77         return new CachedDeviceState.Readonly();
78     }
79 
80     /**
81      * Allows for only a readonly access to the device state.
82      */
83     public class Readonly {
isCharging()84         public boolean isCharging() {
85             return mCharging;
86         }
87 
isScreenInteractive()88         public boolean isScreenInteractive() {
89             return mScreenInteractive;
90         }
91 
92         /** Creates a {@link TimeInStateStopwatch stopwatch} that tracks the time on battery. */
createTimeOnBatteryStopwatch()93         public TimeInStateStopwatch createTimeOnBatteryStopwatch() {
94             synchronized (mStopwatchesLock) {
95                 final TimeInStateStopwatch stopwatch = new TimeInStateStopwatch();
96                 mOnBatteryStopwatches.add(stopwatch);
97                 if (!mCharging) {
98                     stopwatch.start();
99                 }
100                 return stopwatch;
101             }
102         }
103     }
104 
105     /** Tracks the time the device spent in a given state. */
106     public class TimeInStateStopwatch implements AutoCloseable {
107         private final Object mLock = new Object();
108         @GuardedBy("mLock")
109         private long mStartTimeMillis;
110         @GuardedBy("mLock")
111         private long mTotalTimeMillis;
112 
113         /** Returns the time in state since the last call to {@link TimeInStateStopwatch#reset}. */
getMillis()114         public long getMillis() {
115             synchronized (mLock) {
116                 return mTotalTimeMillis + elapsedTime();
117             }
118         }
119 
120         /** Resets the time in state to 0 without stopping the timer if it's started. */
reset()121         public void reset() {
122             synchronized (mLock) {
123                 mTotalTimeMillis = 0;
124                 mStartTimeMillis = isRunning() ? SystemClock.elapsedRealtime() : 0;
125             }
126         }
127 
start()128         private void start() {
129             synchronized (mLock) {
130                 if (!isRunning()) {
131                     mStartTimeMillis = SystemClock.elapsedRealtime();
132                 }
133             }
134         }
135 
stop()136         private void stop() {
137             synchronized (mLock) {
138                 if (isRunning()) {
139                     mTotalTimeMillis += elapsedTime();
140                     mStartTimeMillis = 0;
141                 }
142             }
143         }
144 
elapsedTime()145         private long elapsedTime() {
146             return isRunning() ? SystemClock.elapsedRealtime() - mStartTimeMillis : 0;
147         }
148 
149         @VisibleForTesting
isRunning()150         public boolean isRunning() {
151             return mStartTimeMillis > 0;
152         }
153 
154         @Override
close()155         public void close() {
156             synchronized (mStopwatchesLock) {
157                 mOnBatteryStopwatches.remove(this);
158             }
159         }
160     }
161 }
162