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