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