/*
 * Copyright (C) 2016 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.server.power.stats;

import android.os.BatteryStats;
import android.os.Parcel;

import androidx.test.filters.SmallTest;

import junit.framework.TestCase;

/**
 * Test BatteryStatsImpl.DurationTimer.
 *
 * In these tests, unless otherwise commented, the time increments by
 * 2x + 100, to make the subtraction unlikely to alias to another time.
 */
public class BatteryStatsDurationTimerTest extends TestCase {

    @SmallTest
    public void testStartStop() throws Exception {
        final MockClock clocks = new MockClock();

        final BatteryStatsImpl.TimeBase timeBase = new BatteryStatsImpl.TimeBase();
        timeBase.init(clocks.uptimeMillis(), clocks.elapsedRealtime());

        final BatteryStatsImpl.DurationTimer timer = new BatteryStatsImpl.DurationTimer(clocks,
                null, BatteryStats.WAKE_TYPE_PARTIAL, null, timeBase);

        // TimeBase running, timer not running: current and max are 0
        timeBase.setRunning(true, /* uptimeUs */ 0, /* realtimeUs */ 100*1000);
        assertFalse(timer.isRunningLocked());
        assertEquals(0, timer.getCurrentDurationMsLocked(300));
        assertEquals(0, timer.getMaxDurationMsLocked(301));
        assertEquals(0, timer.getTotalDurationMsLocked(301));

        // Start timer: current, total, and max advance
        timer.startRunningLocked(700);
        assertTrue(timer.isRunningLocked());
        assertEquals(800, timer.getCurrentDurationMsLocked(1500));
        assertEquals(801, timer.getMaxDurationMsLocked(1501));
        assertEquals(802, timer.getTotalDurationMsLocked(1502));

        // Stop timer: current resets to 0; total and max remain
        timer.stopRunningLocked(3100);
        assertFalse(timer.isRunningLocked());
        assertEquals(0, timer.getCurrentDurationMsLocked(6300));
        assertEquals(2400, timer.getMaxDurationMsLocked(6301));
        assertEquals(2400, timer.getTotalDurationMsLocked(6302));

        // Start time again, but check with a short time, and make sure max doesn't
        // increment.
        timer.startRunningLocked(12700);
        assertTrue(timer.isRunningLocked());
        assertEquals(100, timer.getCurrentDurationMsLocked(12800));
        assertEquals(2400, timer.getMaxDurationMsLocked(12801));
        assertEquals(2502, timer.getTotalDurationMsLocked(12802));

        // And stop it again, but with a short time, and make sure it doesn't increment.
        timer.stopRunningLocked(12900);
        assertFalse(timer.isRunningLocked());
        assertEquals(0, timer.getCurrentDurationMsLocked(13000));
        assertEquals(2400, timer.getMaxDurationMsLocked(13001));
        assertEquals(2600, timer.getTotalDurationMsLocked(13002));

        // Now start and check that the time doesn't increase if the two times are the same.
        timer.startRunningLocked(27000);
        assertTrue(timer.isRunningLocked());
        assertEquals(0, timer.getCurrentDurationMsLocked(27000));
        assertEquals(2400, timer.getMaxDurationMsLocked(27000));
        assertEquals(2600, timer.getTotalDurationMsLocked(27000));

        // Stop the TimeBase. The values should be frozen.
        timeBase.setRunning(false, /* uptimeUs */ 10, /* realtimeUs */ 55000*1000);
        assertTrue(timer.isRunningLocked());
        assertEquals(28000, timer.getCurrentDurationMsLocked(110100));
        assertEquals(28000, timer.getMaxDurationMsLocked(110101));
        assertEquals(30600, timer.getTotalDurationMsLocked(110102));

        // Start the TimeBase. The values should be the old value plus the delta
        // between when the timer restarted and the current time.
        timeBase.setRunning(true, /* uptimeUs */ 10, /* realtimeUs */ 220100*1000);
        assertTrue(timer.isRunningLocked());
        assertEquals(28200, timer.getCurrentDurationMsLocked(220300));
        assertEquals(28201, timer.getMaxDurationMsLocked(220301));
        assertEquals(30802, timer.getTotalDurationMsLocked(220302));
    }

    /**
     * Tests that reset(boolean detachIfReset) clears the correct times if detachIfReset is false.
     */
    @SmallTest
    public void testReset() throws Exception {
        final MockClock clocks = new MockClock();

        final BatteryStatsImpl.TimeBase timeBase = new BatteryStatsImpl.TimeBase();
        timeBase.init(clocks.uptimeMillis(), clocks.elapsedRealtime());

        final BatteryStatsImpl.DurationTimer timer = new BatteryStatsImpl.DurationTimer(clocks,
                null, BatteryStats.WAKE_TYPE_PARTIAL, null, timeBase);

        timeBase.setRunning(true, /* uptimeUs */ 0, /* realtimeUs */ 100_000);
        timer.startRunningLocked(700);
        timer.stopRunningLocked(3_100);
        assertFalse(timer.isRunningLocked());
        assertEquals(0, timer.getCurrentDurationMsLocked(6_300));
        assertEquals(2_400, timer.getMaxDurationMsLocked(6_301));
        assertEquals(2_400, timer.getTotalDurationMsLocked(6_302));

        timer.reset(false);
        assertEquals(0, timer.getCurrentDurationMsLocked(12_000));
        assertEquals(0, timer.getMaxDurationMsLocked(12_001));
        assertEquals(0, timer.getTotalDurationMsLocked(12_002));

        assertEquals(true, timeBase.hasObserver(timer));

        timer.startRunningLocked(24_100);
        clocks.uptime = clocks.realtime = 24_200;
        timer.reset(false);
        assertEquals(34_300, timer.getCurrentDurationMsLocked(58_500));
        assertEquals(34_301, timer.getMaxDurationMsLocked(58_501));
        assertEquals(34_302, timer.getTotalDurationMsLocked(58_502));
    }

    /**
     * Tests that reset(boolean detachIfReset) clears the correct times if detachIfReset is true.
     */
    @SmallTest
    public void testResetAndDetach() throws Exception {
        final MockClock clocks = new MockClock();

        final BatteryStatsImpl.TimeBase timeBase = new BatteryStatsImpl.TimeBase();
        timeBase.init(clocks.uptimeMillis(), clocks.elapsedRealtime());

        final BatteryStatsImpl.DurationTimer timer = new BatteryStatsImpl.DurationTimer(clocks,
                null, BatteryStats.WAKE_TYPE_PARTIAL, null, timeBase);

        timeBase.setRunning(true, /* uptimeUs */ 0, /* realtimeUs */ 100_000);
        timer.startRunningLocked(700);
        timer.stopRunningLocked(3_100);
        assertFalse(timer.isRunningLocked());
        assertEquals(0, timer.getCurrentDurationMsLocked(6_300));
        assertEquals(2_400, timer.getMaxDurationMsLocked(6_301));
        assertEquals(2_400, timer.getTotalDurationMsLocked(6_302));

        timer.reset(true);
        clocks.uptime = clocks.realtime = 7_000;
        assertEquals(0, timer.getCurrentDurationMsLocked(8_000));
        assertEquals(0, timer.getMaxDurationMsLocked(8_001));
        assertEquals(0, timer.getTotalDurationMsLocked(8_002));

        assertEquals(false, timeBase.hasObserver(timer));
    }

    @SmallTest
    public void testParceling() throws Exception {
        final MockClock clocks = new MockClock();

        final BatteryStatsImpl.TimeBase timeBase = new BatteryStatsImpl.TimeBase();
        timeBase.init(clocks.uptimeMillis(), clocks.elapsedRealtime());

        final BatteryStatsImpl.DurationTimer timer = new BatteryStatsImpl.DurationTimer(clocks,
                null, BatteryStats.WAKE_TYPE_PARTIAL, null, timeBase);

        // Start running on battery.
        clocks.realtime = 100;
        clocks.uptime = 10;
        timeBase.setRunning(true, clocks.uptimeMillis()*1000, clocks.elapsedRealtime()*1000);

        timer.startRunningLocked(300);

        // Check that it did start running
        assertEquals(400, timer.getMaxDurationMsLocked(700));
        assertEquals(401, timer.getCurrentDurationMsLocked(701));
        assertEquals(402, timer.getTotalDurationMsLocked(702));

        // Write summary
        final Parcel summaryParcel = Parcel.obtain();
        timer.writeSummaryFromParcelLocked(summaryParcel, 1500*1000);
        summaryParcel.setDataPosition(0);

        // Read summary
        final BatteryStatsImpl.DurationTimer summary = new BatteryStatsImpl.DurationTimer(clocks,
                null, BatteryStats.WAKE_TYPE_PARTIAL, null, timeBase);
        summary.startRunningLocked(3100);
        summary.readSummaryFromParcelLocked(summaryParcel);
        // The new one shouldn't be running, and therefore 0 for current time if using
        // summary parcel
        assertFalse(summary.isRunningLocked());
        assertEquals(0, summary.getCurrentDurationMsLocked(6300));
        // The new one should have the max and total durations that we had when we wrote it
        assertEquals(1200, summary.getMaxDurationMsLocked(6301));
        assertEquals(1200, summary.getTotalDurationMsLocked(6302));

        // Write full
        final Parcel fullParcel = Parcel.obtain();
        timer.writeToParcel(fullParcel, 1500*1000);
        fullParcel.setDataPosition(0);

        // Read full - Should be the same as the summary as far as DurationTimer is concerned.
        final BatteryStatsImpl.DurationTimer full = new BatteryStatsImpl.DurationTimer(clocks,
                null, BatteryStats.WAKE_TYPE_PARTIAL, null, timeBase, fullParcel);
        // The new one shouldn't be running
        assertFalse(full.isRunningLocked());
        // The new one should have the current, max and total durations that we had when we wrote it
        assertEquals(1200, full.getCurrentDurationMsLocked(6300));
        assertEquals(1200, full.getMaxDurationMsLocked(6301));
        assertEquals(1200, full.getTotalDurationMsLocked(6302));
    }
}
