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