Loading core/java/com/android/internal/os/BinderCallsStats.java +13 −1 Original line number Diff line number Diff line Loading @@ -82,6 +82,7 @@ public class BinderCallsStats implements BinderInternal.Observer { private boolean mAddDebugEntries = false; private CachedDeviceState.Readonly mDeviceState; private CachedDeviceState.TimeInStateStopwatch mBatteryStopwatch; /** Injector for {@link BinderCallsStats}. */ public static class Injector { Loading @@ -95,7 +96,11 @@ public class BinderCallsStats implements BinderInternal.Observer { } public void setDeviceState(@NonNull CachedDeviceState.Readonly deviceState) { if (mBatteryStopwatch != null) { mBatteryStopwatch.close(); } mDeviceState = deviceState; mBatteryStopwatch = deviceState.createTimeOnBatteryStopwatch(); } @Override Loading Loading @@ -320,9 +325,11 @@ public class BinderCallsStats implements BinderInternal.Observer { } // Debug entries added to help validate the data. if (mAddDebugEntries) { if (mAddDebugEntries && mBatteryStopwatch != null) { resultCallStats.add(createDebugEntry("start_time_millis", mStartTime)); resultCallStats.add(createDebugEntry("end_time_millis", System.currentTimeMillis())); resultCallStats.add( createDebugEntry("battery_time_millis", mBatteryStopwatch.getMillis())); } return resultCallStats; Loading Loading @@ -362,6 +369,8 @@ public class BinderCallsStats implements BinderInternal.Observer { long totalCpuTime = 0; pw.print("Start time: "); pw.println(DateFormat.format("yyyy-MM-dd HH:mm:ss", mStartTime)); pw.print("On battery time (ms): "); pw.println(mBatteryStopwatch != null ? mBatteryStopwatch.getMillis() : 0); pw.println("Sampling interval period: " + mPeriodicSamplingInterval); final List<UidEntry> entries = new ArrayList<>(); Loading Loading @@ -521,6 +530,9 @@ public class BinderCallsStats implements BinderInternal.Observer { mUidEntries.clear(); mExceptionCounts.clear(); mStartTime = System.currentTimeMillis(); if (mBatteryStopwatch != null) { mBatteryStopwatch.reset(); } } } Loading core/java/com/android/internal/os/CachedDeviceState.java +95 −1 Original line number Diff line number Diff line Loading @@ -16,8 +16,14 @@ 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. Loading @@ -27,6 +33,9 @@ import com.android.internal.annotations.VisibleForTesting; public class CachedDeviceState { private volatile boolean mScreenInteractive; private volatile boolean mCharging; private final Object mStopwatchesLock = new Object(); @GuardedBy("mStopwatchLock") private final ArrayList<TimeInStateStopwatch> mOnBatteryStopwatches = new ArrayList<>(); public CachedDeviceState() { mCharging = true; Loading @@ -44,7 +53,23 @@ public class CachedDeviceState { } 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() { Loading @@ -62,5 +87,74 @@ public class CachedDeviceState { 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); } } } } core/java/com/android/internal/os/LooperStats.java +16 −1 Original line number Diff line number Diff line Loading @@ -49,6 +49,7 @@ public class LooperStats implements Looper.Observer { private final int mEntriesSizeCap; private int mSamplingInterval; private CachedDeviceState.Readonly mDeviceState; private CachedDeviceState.TimeInStateStopwatch mBatteryStopwatch; private long mStartTime = System.currentTimeMillis(); private boolean mAddDebugEntries = false; Loading @@ -58,7 +59,12 @@ public class LooperStats implements Looper.Observer { } public void setDeviceState(@NonNull CachedDeviceState.Readonly deviceState) { if (mBatteryStopwatch != null) { mBatteryStopwatch.close(); } mDeviceState = deviceState; mBatteryStopwatch = deviceState.createTimeOnBatteryStopwatch(); } public void setAddDebugEntries(boolean addDebugEntries) { Loading Loading @@ -148,9 +154,11 @@ public class LooperStats implements Looper.Observer { maybeAddSpecialEntry(exportedEntries, mOverflowEntry); maybeAddSpecialEntry(exportedEntries, mHashCollisionEntry); // Debug entries added to help validate the data. if (mAddDebugEntries) { if (mAddDebugEntries && mBatteryStopwatch != null) { exportedEntries.add(createDebugEntry("start_time_millis", mStartTime)); exportedEntries.add(createDebugEntry("end_time_millis", System.currentTimeMillis())); exportedEntries.add( createDebugEntry("battery_time_millis", mBatteryStopwatch.getMillis())); } return exportedEntries; } Loading @@ -168,6 +176,10 @@ public class LooperStats implements Looper.Observer { return mStartTime; } public long getBatteryTimeMillis() { return mBatteryStopwatch != null ? mBatteryStopwatch.getMillis() : 0; } private void maybeAddSpecialEntry(List<ExportedEntry> exportedEntries, Entry specialEntry) { synchronized (specialEntry) { if (specialEntry.messageCount > 0 || specialEntry.exceptionCount > 0) { Loading @@ -188,6 +200,9 @@ public class LooperStats implements Looper.Observer { mOverflowEntry.reset(); } mStartTime = System.currentTimeMillis(); if (mBatteryStopwatch != null) { mBatteryStopwatch.reset(); } } public void setSamplingInterval(int samplingInterval) { Loading core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java +13 −4 Original line number Diff line number Diff line Loading @@ -388,8 +388,7 @@ public class BinderCallsStatsTest { @Test public void testNoDataCollectedBeforeInitialDeviceStateSet() { TestBinderCallsStats bcs = new TestBinderCallsStats(); bcs.setDeviceState(null); TestBinderCallsStats bcs = new TestBinderCallsStats(null); bcs.setDetailedTracking(true); Binder binder = new Binder(); CallSession callSession = bcs.callStarted(binder, 1); Loading Loading @@ -620,7 +619,7 @@ public class BinderCallsStatsTest { TestBinderCallsStats bcs = new TestBinderCallsStats(); bcs.setAddDebugEntries(true); ArrayList<BinderCallsStats.ExportedCallStat> callStats = bcs.getExportedCallStats(); assertEquals(2, callStats.size()); assertEquals(3, callStats.size()); BinderCallsStats.ExportedCallStat debugEntry1 = callStats.get(0); assertEquals("", debugEntry1.className); assertEquals("__DEBUG_start_time_millis", debugEntry1.methodName); Loading @@ -629,6 +628,10 @@ public class BinderCallsStatsTest { assertEquals("", debugEntry2.className); assertEquals("__DEBUG_end_time_millis", debugEntry2.methodName); assertTrue(debugEntry1.maxReplySizeBytes <= debugEntry2.maxReplySizeBytes); BinderCallsStats.ExportedCallStat debugEntry3 = callStats.get(2); assertEquals("", debugEntry3.className); assertEquals("__DEBUG_battery_time_millis", debugEntry3.methodName); assertTrue(debugEntry3.maxReplySizeBytes >= 0); } class TestBinderCallsStats extends BinderCallsStats { Loading @@ -638,6 +641,10 @@ public class BinderCallsStatsTest { public long elapsedTime = 0; TestBinderCallsStats() { this(mDeviceState); } TestBinderCallsStats(CachedDeviceState deviceState) { // Make random generator not random. super(new Injector() { public Random getRandomGenerator() { Loading @@ -651,8 +658,10 @@ public class BinderCallsStatsTest { } }); setSamplingInterval(1); setDeviceState(mDeviceState.getReadonlyClient()); setAddDebugEntries(false); if (deviceState != null) { setDeviceState(deviceState.getReadonlyClient()); } } @Override Loading core/tests/coretests/src/com/android/internal/os/LooperStatsTest.java +15 −6 Original line number Diff line number Diff line Loading @@ -273,8 +273,7 @@ public final class LooperStatsTest { @Test public void testDataNotCollectedBeforeDeviceStateSet() { TestableLooperStats looperStats = new TestableLooperStats(1, 100); looperStats.setDeviceState(null); TestableLooperStats looperStats = new TestableLooperStats(1, 100, null); Object token1 = looperStats.messageDispatchStarting(); looperStats.messageDispatched(token1, mHandlerFirst.obtainMessage(1000)); Loading Loading @@ -439,7 +438,7 @@ public final class LooperStatsTest { looperStats.messageDispatched(token, message); List<LooperStats.ExportedEntry> entries = looperStats.getEntries(); assertThat(entries).hasSize(3); assertThat(entries).hasSize(4); LooperStats.ExportedEntry debugEntry1 = entries.get(1); assertThat(debugEntry1.handlerClassName).isEqualTo(""); assertThat(debugEntry1.messageName).isEqualTo("__DEBUG_start_time_millis"); Loading @@ -448,6 +447,10 @@ public final class LooperStatsTest { assertThat(debugEntry2.handlerClassName).isEqualTo(""); assertThat(debugEntry2.messageName).isEqualTo("__DEBUG_end_time_millis"); assertThat(debugEntry2.maxDelayMillis).isAtLeast(looperStats.getStartTimeMillis()); LooperStats.ExportedEntry debugEntry3 = entries.get(3); assertThat(debugEntry3.handlerClassName).isEqualTo(""); assertThat(debugEntry3.messageName).isEqualTo("__DEBUG_battery_time_millis"); assertThat(debugEntry3.maxDelayMillis).isAtLeast(0L); } private static void assertThrows(Class<? extends Exception> exceptionClass, Runnable r) { Loading @@ -468,10 +471,16 @@ public final class LooperStatsTest { private int mSamplingInterval; TestableLooperStats(int samplingInterval, int sizeCap) { this(samplingInterval, sizeCap, mDeviceState); } TestableLooperStats(int samplingInterval, int sizeCap, CachedDeviceState deviceState) { super(samplingInterval, sizeCap); this.mSamplingInterval = samplingInterval; this.setDeviceState(mDeviceState.getReadonlyClient()); this.setAddDebugEntries(false); mSamplingInterval = samplingInterval; setAddDebugEntries(false); if (deviceState != null) { setDeviceState(deviceState.getReadonlyClient()); } } void tickRealtime(long micros) { Loading Loading
core/java/com/android/internal/os/BinderCallsStats.java +13 −1 Original line number Diff line number Diff line Loading @@ -82,6 +82,7 @@ public class BinderCallsStats implements BinderInternal.Observer { private boolean mAddDebugEntries = false; private CachedDeviceState.Readonly mDeviceState; private CachedDeviceState.TimeInStateStopwatch mBatteryStopwatch; /** Injector for {@link BinderCallsStats}. */ public static class Injector { Loading @@ -95,7 +96,11 @@ public class BinderCallsStats implements BinderInternal.Observer { } public void setDeviceState(@NonNull CachedDeviceState.Readonly deviceState) { if (mBatteryStopwatch != null) { mBatteryStopwatch.close(); } mDeviceState = deviceState; mBatteryStopwatch = deviceState.createTimeOnBatteryStopwatch(); } @Override Loading Loading @@ -320,9 +325,11 @@ public class BinderCallsStats implements BinderInternal.Observer { } // Debug entries added to help validate the data. if (mAddDebugEntries) { if (mAddDebugEntries && mBatteryStopwatch != null) { resultCallStats.add(createDebugEntry("start_time_millis", mStartTime)); resultCallStats.add(createDebugEntry("end_time_millis", System.currentTimeMillis())); resultCallStats.add( createDebugEntry("battery_time_millis", mBatteryStopwatch.getMillis())); } return resultCallStats; Loading Loading @@ -362,6 +369,8 @@ public class BinderCallsStats implements BinderInternal.Observer { long totalCpuTime = 0; pw.print("Start time: "); pw.println(DateFormat.format("yyyy-MM-dd HH:mm:ss", mStartTime)); pw.print("On battery time (ms): "); pw.println(mBatteryStopwatch != null ? mBatteryStopwatch.getMillis() : 0); pw.println("Sampling interval period: " + mPeriodicSamplingInterval); final List<UidEntry> entries = new ArrayList<>(); Loading Loading @@ -521,6 +530,9 @@ public class BinderCallsStats implements BinderInternal.Observer { mUidEntries.clear(); mExceptionCounts.clear(); mStartTime = System.currentTimeMillis(); if (mBatteryStopwatch != null) { mBatteryStopwatch.reset(); } } } Loading
core/java/com/android/internal/os/CachedDeviceState.java +95 −1 Original line number Diff line number Diff line Loading @@ -16,8 +16,14 @@ 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. Loading @@ -27,6 +33,9 @@ import com.android.internal.annotations.VisibleForTesting; public class CachedDeviceState { private volatile boolean mScreenInteractive; private volatile boolean mCharging; private final Object mStopwatchesLock = new Object(); @GuardedBy("mStopwatchLock") private final ArrayList<TimeInStateStopwatch> mOnBatteryStopwatches = new ArrayList<>(); public CachedDeviceState() { mCharging = true; Loading @@ -44,7 +53,23 @@ public class CachedDeviceState { } 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() { Loading @@ -62,5 +87,74 @@ public class CachedDeviceState { 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); } } } }
core/java/com/android/internal/os/LooperStats.java +16 −1 Original line number Diff line number Diff line Loading @@ -49,6 +49,7 @@ public class LooperStats implements Looper.Observer { private final int mEntriesSizeCap; private int mSamplingInterval; private CachedDeviceState.Readonly mDeviceState; private CachedDeviceState.TimeInStateStopwatch mBatteryStopwatch; private long mStartTime = System.currentTimeMillis(); private boolean mAddDebugEntries = false; Loading @@ -58,7 +59,12 @@ public class LooperStats implements Looper.Observer { } public void setDeviceState(@NonNull CachedDeviceState.Readonly deviceState) { if (mBatteryStopwatch != null) { mBatteryStopwatch.close(); } mDeviceState = deviceState; mBatteryStopwatch = deviceState.createTimeOnBatteryStopwatch(); } public void setAddDebugEntries(boolean addDebugEntries) { Loading Loading @@ -148,9 +154,11 @@ public class LooperStats implements Looper.Observer { maybeAddSpecialEntry(exportedEntries, mOverflowEntry); maybeAddSpecialEntry(exportedEntries, mHashCollisionEntry); // Debug entries added to help validate the data. if (mAddDebugEntries) { if (mAddDebugEntries && mBatteryStopwatch != null) { exportedEntries.add(createDebugEntry("start_time_millis", mStartTime)); exportedEntries.add(createDebugEntry("end_time_millis", System.currentTimeMillis())); exportedEntries.add( createDebugEntry("battery_time_millis", mBatteryStopwatch.getMillis())); } return exportedEntries; } Loading @@ -168,6 +176,10 @@ public class LooperStats implements Looper.Observer { return mStartTime; } public long getBatteryTimeMillis() { return mBatteryStopwatch != null ? mBatteryStopwatch.getMillis() : 0; } private void maybeAddSpecialEntry(List<ExportedEntry> exportedEntries, Entry specialEntry) { synchronized (specialEntry) { if (specialEntry.messageCount > 0 || specialEntry.exceptionCount > 0) { Loading @@ -188,6 +200,9 @@ public class LooperStats implements Looper.Observer { mOverflowEntry.reset(); } mStartTime = System.currentTimeMillis(); if (mBatteryStopwatch != null) { mBatteryStopwatch.reset(); } } public void setSamplingInterval(int samplingInterval) { Loading
core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java +13 −4 Original line number Diff line number Diff line Loading @@ -388,8 +388,7 @@ public class BinderCallsStatsTest { @Test public void testNoDataCollectedBeforeInitialDeviceStateSet() { TestBinderCallsStats bcs = new TestBinderCallsStats(); bcs.setDeviceState(null); TestBinderCallsStats bcs = new TestBinderCallsStats(null); bcs.setDetailedTracking(true); Binder binder = new Binder(); CallSession callSession = bcs.callStarted(binder, 1); Loading Loading @@ -620,7 +619,7 @@ public class BinderCallsStatsTest { TestBinderCallsStats bcs = new TestBinderCallsStats(); bcs.setAddDebugEntries(true); ArrayList<BinderCallsStats.ExportedCallStat> callStats = bcs.getExportedCallStats(); assertEquals(2, callStats.size()); assertEquals(3, callStats.size()); BinderCallsStats.ExportedCallStat debugEntry1 = callStats.get(0); assertEquals("", debugEntry1.className); assertEquals("__DEBUG_start_time_millis", debugEntry1.methodName); Loading @@ -629,6 +628,10 @@ public class BinderCallsStatsTest { assertEquals("", debugEntry2.className); assertEquals("__DEBUG_end_time_millis", debugEntry2.methodName); assertTrue(debugEntry1.maxReplySizeBytes <= debugEntry2.maxReplySizeBytes); BinderCallsStats.ExportedCallStat debugEntry3 = callStats.get(2); assertEquals("", debugEntry3.className); assertEquals("__DEBUG_battery_time_millis", debugEntry3.methodName); assertTrue(debugEntry3.maxReplySizeBytes >= 0); } class TestBinderCallsStats extends BinderCallsStats { Loading @@ -638,6 +641,10 @@ public class BinderCallsStatsTest { public long elapsedTime = 0; TestBinderCallsStats() { this(mDeviceState); } TestBinderCallsStats(CachedDeviceState deviceState) { // Make random generator not random. super(new Injector() { public Random getRandomGenerator() { Loading @@ -651,8 +658,10 @@ public class BinderCallsStatsTest { } }); setSamplingInterval(1); setDeviceState(mDeviceState.getReadonlyClient()); setAddDebugEntries(false); if (deviceState != null) { setDeviceState(deviceState.getReadonlyClient()); } } @Override Loading
core/tests/coretests/src/com/android/internal/os/LooperStatsTest.java +15 −6 Original line number Diff line number Diff line Loading @@ -273,8 +273,7 @@ public final class LooperStatsTest { @Test public void testDataNotCollectedBeforeDeviceStateSet() { TestableLooperStats looperStats = new TestableLooperStats(1, 100); looperStats.setDeviceState(null); TestableLooperStats looperStats = new TestableLooperStats(1, 100, null); Object token1 = looperStats.messageDispatchStarting(); looperStats.messageDispatched(token1, mHandlerFirst.obtainMessage(1000)); Loading Loading @@ -439,7 +438,7 @@ public final class LooperStatsTest { looperStats.messageDispatched(token, message); List<LooperStats.ExportedEntry> entries = looperStats.getEntries(); assertThat(entries).hasSize(3); assertThat(entries).hasSize(4); LooperStats.ExportedEntry debugEntry1 = entries.get(1); assertThat(debugEntry1.handlerClassName).isEqualTo(""); assertThat(debugEntry1.messageName).isEqualTo("__DEBUG_start_time_millis"); Loading @@ -448,6 +447,10 @@ public final class LooperStatsTest { assertThat(debugEntry2.handlerClassName).isEqualTo(""); assertThat(debugEntry2.messageName).isEqualTo("__DEBUG_end_time_millis"); assertThat(debugEntry2.maxDelayMillis).isAtLeast(looperStats.getStartTimeMillis()); LooperStats.ExportedEntry debugEntry3 = entries.get(3); assertThat(debugEntry3.handlerClassName).isEqualTo(""); assertThat(debugEntry3.messageName).isEqualTo("__DEBUG_battery_time_millis"); assertThat(debugEntry3.maxDelayMillis).isAtLeast(0L); } private static void assertThrows(Class<? extends Exception> exceptionClass, Runnable r) { Loading @@ -468,10 +471,16 @@ public final class LooperStatsTest { private int mSamplingInterval; TestableLooperStats(int samplingInterval, int sizeCap) { this(samplingInterval, sizeCap, mDeviceState); } TestableLooperStats(int samplingInterval, int sizeCap, CachedDeviceState deviceState) { super(samplingInterval, sizeCap); this.mSamplingInterval = samplingInterval; this.setDeviceState(mDeviceState.getReadonlyClient()); this.setAddDebugEntries(false); mSamplingInterval = samplingInterval; setAddDebugEntries(false); if (deviceState != null) { setDeviceState(deviceState.getReadonlyClient()); } } void tickRealtime(long micros) { Loading