/* * Copyright (C) 2018 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.internal.os; import android.os.SystemClock; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import java.util.ArrayList; /** * Stores the device state (e.g. charging/on battery, screen on/off) to be shared with * the System Server telemetry services. * * @hide */ @android.ravenwood.annotation.RavenwoodKeepWholeClass public class CachedDeviceState { private volatile boolean mScreenInteractive; private volatile boolean mCharging; private final Object mStopwatchesLock = new Object(); @GuardedBy("mStopwatchLock") private final ArrayList mOnBatteryStopwatches = new ArrayList<>(); public CachedDeviceState() { mCharging = true; mScreenInteractive = false; } @VisibleForTesting public CachedDeviceState(boolean isCharging, boolean isScreenInteractive) { mCharging = isCharging; mScreenInteractive = isScreenInteractive; } public void setScreenInteractive(boolean screenInteractive) { mScreenInteractive = screenInteractive; } public void setCharging(boolean charging) { if (mCharging != charging) { mCharging = charging; updateStopwatches(/* shouldStart= */ !charging); } } private void updateStopwatches(boolean shouldStart) { synchronized (mStopwatchesLock) { final int size = mOnBatteryStopwatches.size(); for (int i = 0; i < size; i++) { if (shouldStart) { mOnBatteryStopwatches.get(i).start(); } else { mOnBatteryStopwatches.get(i).stop(); } } } } public Readonly getReadonlyClient() { return new CachedDeviceState.Readonly(); } /** * Allows for only a readonly access to the device state. */ public class Readonly { public boolean isCharging() { return mCharging; } public boolean isScreenInteractive() { return mScreenInteractive; } /** Creates a {@link TimeInStateStopwatch stopwatch} that tracks the time on battery. */ public TimeInStateStopwatch createTimeOnBatteryStopwatch() { synchronized (mStopwatchesLock) { final TimeInStateStopwatch stopwatch = new TimeInStateStopwatch(); mOnBatteryStopwatches.add(stopwatch); if (!mCharging) { stopwatch.start(); } return stopwatch; } } } /** Tracks the time the device spent in a given state. */ public class TimeInStateStopwatch implements AutoCloseable { private final Object mLock = new Object(); @GuardedBy("mLock") private long mStartTimeMillis; @GuardedBy("mLock") private long mTotalTimeMillis; /** Returns the time in state since the last call to {@link TimeInStateStopwatch#reset}. */ public long getMillis() { synchronized (mLock) { return mTotalTimeMillis + elapsedTime(); } } /** Resets the time in state to 0 without stopping the timer if it's started. */ public void reset() { synchronized (mLock) { mTotalTimeMillis = 0; mStartTimeMillis = isRunning() ? SystemClock.elapsedRealtime() : 0; } } private void start() { synchronized (mLock) { if (!isRunning()) { mStartTimeMillis = SystemClock.elapsedRealtime(); } } } private void stop() { synchronized (mLock) { if (isRunning()) { mTotalTimeMillis += elapsedTime(); mStartTimeMillis = 0; } } } private long elapsedTime() { return isRunning() ? SystemClock.elapsedRealtime() - mStartTimeMillis : 0; } @VisibleForTesting public boolean isRunning() { return mStartTimeMillis > 0; } @Override public void close() { synchronized (mStopwatchesLock) { mOnBatteryStopwatches.remove(this); } } } }