AppIdleHistory.java revision e878931414e46eaaf1e10e227cd50bcf5435dee8
10a11e69428d4c00dfcb368c1eb4e60ad8e0dc918Amith Yamasani/**
20a11e69428d4c00dfcb368c1eb4e60ad8e0dc918Amith Yamasani * Copyright (C) 2015 The Android Open Source Project
30a11e69428d4c00dfcb368c1eb4e60ad8e0dc918Amith Yamasani *
40a11e69428d4c00dfcb368c1eb4e60ad8e0dc918Amith Yamasani * Licensed under the Apache License, Version 2.0 (the "License"); you may not
50a11e69428d4c00dfcb368c1eb4e60ad8e0dc918Amith Yamasani * use this file except in compliance with the License. You may obtain a copy
60a11e69428d4c00dfcb368c1eb4e60ad8e0dc918Amith Yamasani * of the License at
70a11e69428d4c00dfcb368c1eb4e60ad8e0dc918Amith Yamasani *
80a11e69428d4c00dfcb368c1eb4e60ad8e0dc918Amith Yamasani * http://www.apache.org/licenses/LICENSE-2.0
90a11e69428d4c00dfcb368c1eb4e60ad8e0dc918Amith Yamasani *
100a11e69428d4c00dfcb368c1eb4e60ad8e0dc918Amith Yamasani * Unless required by applicable law or agreed to in writing, software
110a11e69428d4c00dfcb368c1eb4e60ad8e0dc918Amith Yamasani * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
120a11e69428d4c00dfcb368c1eb4e60ad8e0dc918Amith Yamasani * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
130a11e69428d4c00dfcb368c1eb4e60ad8e0dc918Amith Yamasani * License for the specific language governing permissions and limitations
140a11e69428d4c00dfcb368c1eb4e60ad8e0dc918Amith Yamasani * under the License.
150a11e69428d4c00dfcb368c1eb4e60ad8e0dc918Amith Yamasani */
160a11e69428d4c00dfcb368c1eb4e60ad8e0dc918Amith Yamasani
170a11e69428d4c00dfcb368c1eb4e60ad8e0dc918Amith Yamasanipackage com.android.server.usage;
180a11e69428d4c00dfcb368c1eb4e60ad8e0dc918Amith Yamasani
19afbccb7d37647f6da61ebcc52a598c7a9f54bc3fAmith Yamasaniimport android.app.usage.UsageStatsManager;
20a93542f9d341897f3206f775fd5720663b17504fAmith Yamasaniimport android.os.SystemClock;
210a11e69428d4c00dfcb368c1eb4e60ad8e0dc918Amith Yamasaniimport android.util.ArrayMap;
22a93542f9d341897f3206f775fd5720663b17504fAmith Yamasaniimport android.util.AtomicFile;
23a93542f9d341897f3206f775fd5720663b17504fAmith Yamasaniimport android.util.Slog;
240a11e69428d4c00dfcb368c1eb4e60ad8e0dc918Amith Yamasaniimport android.util.SparseArray;
25a93542f9d341897f3206f775fd5720663b17504fAmith Yamasaniimport android.util.TimeUtils;
26a93542f9d341897f3206f775fd5720663b17504fAmith Yamasaniimport android.util.Xml;
270a11e69428d4c00dfcb368c1eb4e60ad8e0dc918Amith Yamasani
28a93542f9d341897f3206f775fd5720663b17504fAmith Yamasaniimport com.android.internal.annotations.VisibleForTesting;
29a93542f9d341897f3206f775fd5720663b17504fAmith Yamasaniimport com.android.internal.util.FastXmlSerializer;
300a11e69428d4c00dfcb368c1eb4e60ad8e0dc918Amith Yamasaniimport com.android.internal.util.IndentingPrintWriter;
310a11e69428d4c00dfcb368c1eb4e60ad8e0dc918Amith Yamasani
32a93542f9d341897f3206f775fd5720663b17504fAmith Yamasaniimport libcore.io.IoUtils;
33a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani
34a93542f9d341897f3206f775fd5720663b17504fAmith Yamasaniimport org.xmlpull.v1.XmlPullParser;
35a93542f9d341897f3206f775fd5720663b17504fAmith Yamasaniimport org.xmlpull.v1.XmlPullParserException;
36a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani
37a93542f9d341897f3206f775fd5720663b17504fAmith Yamasaniimport java.io.BufferedOutputStream;
38a93542f9d341897f3206f775fd5720663b17504fAmith Yamasaniimport java.io.BufferedReader;
39a93542f9d341897f3206f775fd5720663b17504fAmith Yamasaniimport java.io.File;
40a93542f9d341897f3206f775fd5720663b17504fAmith Yamasaniimport java.io.FileInputStream;
41a93542f9d341897f3206f775fd5720663b17504fAmith Yamasaniimport java.io.FileOutputStream;
42a93542f9d341897f3206f775fd5720663b17504fAmith Yamasaniimport java.io.FileReader;
43a93542f9d341897f3206f775fd5720663b17504fAmith Yamasaniimport java.io.IOException;
44a93542f9d341897f3206f775fd5720663b17504fAmith Yamasaniimport java.nio.charset.StandardCharsets;
45e878931414e46eaaf1e10e227cd50bcf5435dee8Amith Yamasaniimport java.util.HashMap;
46e878931414e46eaaf1e10e227cd50bcf5435dee8Amith Yamasaniimport java.util.Map;
47a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani
480a11e69428d4c00dfcb368c1eb4e60ad8e0dc918Amith Yamasani/**
490a11e69428d4c00dfcb368c1eb4e60ad8e0dc918Amith Yamasani * Keeps track of recent active state changes in apps.
500a11e69428d4c00dfcb368c1eb4e60ad8e0dc918Amith Yamasani * Access should be guarded by a lock by the caller.
510a11e69428d4c00dfcb368c1eb4e60ad8e0dc918Amith Yamasani */
520a11e69428d4c00dfcb368c1eb4e60ad8e0dc918Amith Yamasanipublic class AppIdleHistory {
530a11e69428d4c00dfcb368c1eb4e60ad8e0dc918Amith Yamasani
54a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani    private static final String TAG = "AppIdleHistory";
55a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani
5617fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani    private static final boolean DEBUG = AppStandbyController.DEBUG;
5717fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani
58a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani    // History for all users and all packages
5917fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani    private SparseArray<ArrayMap<String,AppUsageHistory>> mIdleHistory = new SparseArray<>();
60a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani    private long mLastPeriod = 0;
610a11e69428d4c00dfcb368c1eb4e60ad8e0dc918Amith Yamasani    private static final long ONE_MINUTE = 60 * 1000;
620a11e69428d4c00dfcb368c1eb4e60ad8e0dc918Amith Yamasani    private static final int HISTORY_SIZE = 100;
630a11e69428d4c00dfcb368c1eb4e60ad8e0dc918Amith Yamasani    private static final int FLAG_LAST_STATE = 2;
640a11e69428d4c00dfcb368c1eb4e60ad8e0dc918Amith Yamasani    private static final int FLAG_PARTIAL_ACTIVE = 1;
656776849dc5ff851a225745393f082b702754e278Amith Yamasani    private static final long PERIOD_DURATION = UsageStatsService.COMPRESS_TIME ? ONE_MINUTE
660a11e69428d4c00dfcb368c1eb4e60ad8e0dc918Amith Yamasani            : 60 * ONE_MINUTE;
670a11e69428d4c00dfcb368c1eb4e60ad8e0dc918Amith Yamasani
68a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani    @VisibleForTesting
69a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani    static final String APP_IDLE_FILENAME = "app_idle_stats.xml";
70a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani    private static final String TAG_PACKAGES = "packages";
71a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani    private static final String TAG_PACKAGE = "package";
72a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani    private static final String ATTR_NAME = "name";
73a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani    // Screen on timebase time when app was last used
74a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani    private static final String ATTR_SCREEN_IDLE = "screenIdleTime";
75a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani    // Elapsed timebase time when app was last used
76a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani    private static final String ATTR_ELAPSED_IDLE = "elapsedIdleTime";
77bd7b302f91f225f2dd2367cc37db9d2b75aec521Amith Yamasani    // Elapsed timebase time when the app bucket was last predicted externally
78bd7b302f91f225f2dd2367cc37db9d2b75aec521Amith Yamasani    private static final String ATTR_LAST_PREDICTED_TIME = "lastPredictedTime";
79bd7b302f91f225f2dd2367cc37db9d2b75aec521Amith Yamasani    // The standby bucket for the app
8017fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani    private static final String ATTR_CURRENT_BUCKET = "appLimitBucket";
81bd7b302f91f225f2dd2367cc37db9d2b75aec521Amith Yamasani    // The reason the app was put in the above bucket
8217fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani    private static final String ATTR_BUCKETING_REASON = "bucketReason";
8317fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani
84a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani    // device on time = mElapsedDuration + (timeNow - mElapsedSnapshot)
85a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani    private long mElapsedSnapshot; // Elapsed time snapshot when last write of mDeviceOnDuration
86a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani    private long mElapsedDuration; // Total device on duration since device was "born"
87a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani
88a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani    // screen on time = mScreenOnDuration + (timeNow - mScreenOnSnapshot)
89a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani    private long mScreenOnSnapshot; // Elapsed time snapshot when last write of mScreenOnDuration
90a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani    private long mScreenOnDuration; // Total screen on duration since device was "born"
91a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani
92a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani    private final File mStorageDir;
93a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani
94a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani    private boolean mScreenOn;
95a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani
96bd7b302f91f225f2dd2367cc37db9d2b75aec521Amith Yamasani    static class AppUsageHistory {
97bd7b302f91f225f2dd2367cc37db9d2b75aec521Amith Yamasani        // Debug
98a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani        final byte[] recent = new byte[HISTORY_SIZE];
99bd7b302f91f225f2dd2367cc37db9d2b75aec521Amith Yamasani        // Last used time using elapsed timebase
100a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani        long lastUsedElapsedTime;
101bd7b302f91f225f2dd2367cc37db9d2b75aec521Amith Yamasani        // Last used time using screen_on timebase
102a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani        long lastUsedScreenTime;
103bd7b302f91f225f2dd2367cc37db9d2b75aec521Amith Yamasani        // Last predicted time using elapsed timebase
104bd7b302f91f225f2dd2367cc37db9d2b75aec521Amith Yamasani        long lastPredictedTime;
105bd7b302f91f225f2dd2367cc37db9d2b75aec521Amith Yamasani        // Standby bucket
106afbccb7d37647f6da61ebcc52a598c7a9f54bc3fAmith Yamasani        @UsageStatsManager.StandbyBuckets
107afbccb7d37647f6da61ebcc52a598c7a9f54bc3fAmith Yamasani        int currentBucket;
108bd7b302f91f225f2dd2367cc37db9d2b75aec521Amith Yamasani        // Reason for setting the standby bucket. TODO: Switch to int.
10917fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani        String bucketingReason;
110bd7b302f91f225f2dd2367cc37db9d2b75aec521Amith Yamasani        // In-memory only, last bucket for which the listeners were informed
11184cd7b7a9e5ad6a604c075bc620f6bd9ab6b1486Amith Yamasani        int lastInformedBucket;
112a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani    }
113a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani
114a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani    AppIdleHistory(File storageDir, long elapsedRealtime) {
115a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani        mElapsedSnapshot = elapsedRealtime;
116a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani        mScreenOnSnapshot = elapsedRealtime;
117a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani        mStorageDir = storageDir;
11861d5fd7fee3250bdf4b6ddfbccbd6bceae9436c6Amith Yamasani        readScreenOnTime();
119a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani    }
120a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani
12161d5fd7fee3250bdf4b6ddfbccbd6bceae9436c6Amith Yamasani    public void updateDisplay(boolean screenOn, long elapsedRealtime) {
122a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani        if (screenOn == mScreenOn) return;
123a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani
124a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani        mScreenOn = screenOn;
125a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani        if (mScreenOn) {
126a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani            mScreenOnSnapshot = elapsedRealtime;
127a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani        } else {
128a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani            mScreenOnDuration += elapsedRealtime - mScreenOnSnapshot;
129a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani            mElapsedDuration += elapsedRealtime - mElapsedSnapshot;
130a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani            mElapsedSnapshot = elapsedRealtime;
131a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani        }
13217fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani        if (DEBUG) Slog.d(TAG, "mScreenOnSnapshot=" + mScreenOnSnapshot
13317fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani                + ", mScreenOnDuration=" + mScreenOnDuration
13417fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani                + ", mScreenOn=" + mScreenOn);
135a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani    }
136a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani
13761d5fd7fee3250bdf4b6ddfbccbd6bceae9436c6Amith Yamasani    public long getScreenOnTime(long elapsedRealtime) {
138a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani        long screenOnTime = mScreenOnDuration;
139a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani        if (mScreenOn) {
140a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani            screenOnTime += elapsedRealtime - mScreenOnSnapshot;
141a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani        }
142a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani        return screenOnTime;
143a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani    }
144a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani
145a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani    @VisibleForTesting
146a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani    File getScreenOnTimeFile() {
147a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani        return new File(mStorageDir, "screen_on_time");
148a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani    }
149a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani
15061d5fd7fee3250bdf4b6ddfbccbd6bceae9436c6Amith Yamasani    private void readScreenOnTime() {
151a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani        File screenOnTimeFile = getScreenOnTimeFile();
152a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani        if (screenOnTimeFile.exists()) {
153a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani            try {
154a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani                BufferedReader reader = new BufferedReader(new FileReader(screenOnTimeFile));
155a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani                mScreenOnDuration = Long.parseLong(reader.readLine());
156a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani                mElapsedDuration = Long.parseLong(reader.readLine());
157a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani                reader.close();
158a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani            } catch (IOException | NumberFormatException e) {
159a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani            }
160a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani        } else {
16161d5fd7fee3250bdf4b6ddfbccbd6bceae9436c6Amith Yamasani            writeScreenOnTime();
162a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani        }
163a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani    }
164a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani
16561d5fd7fee3250bdf4b6ddfbccbd6bceae9436c6Amith Yamasani    private void writeScreenOnTime() {
166a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani        AtomicFile screenOnTimeFile = new AtomicFile(getScreenOnTimeFile());
167a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani        FileOutputStream fos = null;
168a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani        try {
169a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani            fos = screenOnTimeFile.startWrite();
170a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani            fos.write((Long.toString(mScreenOnDuration) + "\n"
171a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani                    + Long.toString(mElapsedDuration) + "\n").getBytes());
172a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani            screenOnTimeFile.finishWrite(fos);
173a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani        } catch (IOException ioe) {
174a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani            screenOnTimeFile.failWrite(fos);
175a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani        }
176a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani    }
177a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani
178a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani    /**
179a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani     * To be called periodically to keep track of elapsed time when app idle times are written
180a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani     */
18161d5fd7fee3250bdf4b6ddfbccbd6bceae9436c6Amith Yamasani    public void writeAppIdleDurations() {
182a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani        final long elapsedRealtime = SystemClock.elapsedRealtime();
183a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani        // Only bump up and snapshot the elapsed time. Don't change screen on duration.
184a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani        mElapsedDuration += elapsedRealtime - mElapsedSnapshot;
185a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani        mElapsedSnapshot = elapsedRealtime;
18661d5fd7fee3250bdf4b6ddfbccbd6bceae9436c6Amith Yamasani        writeScreenOnTime();
187a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani    }
188a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani
189803eab6906427517b22c7fd42b21dbf57456eb72Amith Yamasani    public int reportUsage(String packageName, int userId, long elapsedRealtime) {
19017fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani        ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
19117fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani        AppUsageHistory appUsageHistory = getPackageHistory(userHistory, packageName,
19217fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani                elapsedRealtime, true);
193a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani
194a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani        shiftHistoryToNow(userHistory, elapsedRealtime);
195a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani
19617fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani        appUsageHistory.lastUsedElapsedTime = mElapsedDuration
197a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani                + (elapsedRealtime - mElapsedSnapshot);
19817fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani        appUsageHistory.lastUsedScreenTime = getScreenOnTime(elapsedRealtime);
19917fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani        appUsageHistory.recent[HISTORY_SIZE - 1] = FLAG_LAST_STATE | FLAG_PARTIAL_ACTIVE;
200afbccb7d37647f6da61ebcc52a598c7a9f54bc3fAmith Yamasani        if (appUsageHistory.currentBucket > UsageStatsManager.STANDBY_BUCKET_ACTIVE) {
201afbccb7d37647f6da61ebcc52a598c7a9f54bc3fAmith Yamasani            appUsageHistory.currentBucket = UsageStatsManager.STANDBY_BUCKET_ACTIVE;
202803eab6906427517b22c7fd42b21dbf57456eb72Amith Yamasani            if (DEBUG) {
203803eab6906427517b22c7fd42b21dbf57456eb72Amith Yamasani                Slog.d(TAG, "Moved " + packageName + " to bucket=" + appUsageHistory.currentBucket
204803eab6906427517b22c7fd42b21dbf57456eb72Amith Yamasani                        + ", reason=" + appUsageHistory.bucketingReason);
205803eab6906427517b22c7fd42b21dbf57456eb72Amith Yamasani            }
206803eab6906427517b22c7fd42b21dbf57456eb72Amith Yamasani        }
207afbccb7d37647f6da61ebcc52a598c7a9f54bc3fAmith Yamasani        appUsageHistory.bucketingReason = UsageStatsManager.REASON_USAGE;
208803eab6906427517b22c7fd42b21dbf57456eb72Amith Yamasani
209803eab6906427517b22c7fd42b21dbf57456eb72Amith Yamasani        return appUsageHistory.currentBucket;
210803eab6906427517b22c7fd42b21dbf57456eb72Amith Yamasani    }
211803eab6906427517b22c7fd42b21dbf57456eb72Amith Yamasani
212803eab6906427517b22c7fd42b21dbf57456eb72Amith Yamasani    public int reportMildUsage(String packageName, int userId, long elapsedRealtime) {
213803eab6906427517b22c7fd42b21dbf57456eb72Amith Yamasani        ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
214803eab6906427517b22c7fd42b21dbf57456eb72Amith Yamasani        AppUsageHistory appUsageHistory = getPackageHistory(userHistory, packageName,
215803eab6906427517b22c7fd42b21dbf57456eb72Amith Yamasani                elapsedRealtime, true);
216afbccb7d37647f6da61ebcc52a598c7a9f54bc3fAmith Yamasani        if (appUsageHistory.currentBucket > UsageStatsManager.STANDBY_BUCKET_WORKING_SET) {
217afbccb7d37647f6da61ebcc52a598c7a9f54bc3fAmith Yamasani            appUsageHistory.currentBucket = UsageStatsManager.STANDBY_BUCKET_WORKING_SET;
218803eab6906427517b22c7fd42b21dbf57456eb72Amith Yamasani            if (DEBUG) {
219803eab6906427517b22c7fd42b21dbf57456eb72Amith Yamasani                Slog.d(TAG, "Moved " + packageName + " to bucket=" + appUsageHistory.currentBucket
220803eab6906427517b22c7fd42b21dbf57456eb72Amith Yamasani                        + ", reason=" + appUsageHistory.bucketingReason);
221803eab6906427517b22c7fd42b21dbf57456eb72Amith Yamasani            }
22217fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani        }
223803eab6906427517b22c7fd42b21dbf57456eb72Amith Yamasani        // TODO: Should this be a different reason for partial usage?
224afbccb7d37647f6da61ebcc52a598c7a9f54bc3fAmith Yamasani        appUsageHistory.bucketingReason = UsageStatsManager.REASON_USAGE;
225803eab6906427517b22c7fd42b21dbf57456eb72Amith Yamasani
226803eab6906427517b22c7fd42b21dbf57456eb72Amith Yamasani        return appUsageHistory.currentBucket;
227a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani    }
228a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani
229a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani    public void setIdle(String packageName, int userId, long elapsedRealtime) {
23017fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani        ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
23117fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani        AppUsageHistory appUsageHistory = getPackageHistory(userHistory, packageName,
23217fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani                elapsedRealtime, true);
233a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani
234a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani        shiftHistoryToNow(userHistory, elapsedRealtime);
2356776849dc5ff851a225745393f082b702754e278Amith Yamasani
23617fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani        appUsageHistory.recent[HISTORY_SIZE - 1] &= ~FLAG_LAST_STATE;
237a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani    }
238a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani
23917fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani    private void shiftHistoryToNow(ArrayMap<String, AppUsageHistory> userHistory,
240a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani            long elapsedRealtime) {
241a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani        long thisPeriod = elapsedRealtime / PERIOD_DURATION;
2420a11e69428d4c00dfcb368c1eb4e60ad8e0dc918Amith Yamasani        // Has the period switched over? Slide all users' package histories
243a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani        if (mLastPeriod != 0 && mLastPeriod < thisPeriod
244a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani                && (thisPeriod - mLastPeriod) < HISTORY_SIZE - 1) {
245a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani            int diff = (int) (thisPeriod - mLastPeriod);
2466776849dc5ff851a225745393f082b702754e278Amith Yamasani            final int NUSERS = mIdleHistory.size();
2470a11e69428d4c00dfcb368c1eb4e60ad8e0dc918Amith Yamasani            for (int u = 0; u < NUSERS; u++) {
2486776849dc5ff851a225745393f082b702754e278Amith Yamasani                userHistory = mIdleHistory.valueAt(u);
24917fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani                for (AppUsageHistory idleState : userHistory.values()) {
2500a11e69428d4c00dfcb368c1eb4e60ad8e0dc918Amith Yamasani                    // Shift left
251a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani                    System.arraycopy(idleState.recent, diff, idleState.recent, 0,
252a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani                            HISTORY_SIZE - diff);
2530a11e69428d4c00dfcb368c1eb4e60ad8e0dc918Amith Yamasani                    // Replicate last state across the diff
2540a11e69428d4c00dfcb368c1eb4e60ad8e0dc918Amith Yamasani                    for (int i = 0; i < diff; i++) {
255a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani                        idleState.recent[HISTORY_SIZE - i - 1] =
256a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani                            (byte) (idleState.recent[HISTORY_SIZE - diff - 1] & FLAG_LAST_STATE);
2570a11e69428d4c00dfcb368c1eb4e60ad8e0dc918Amith Yamasani                    }
2580a11e69428d4c00dfcb368c1eb4e60ad8e0dc918Amith Yamasani                }
2590a11e69428d4c00dfcb368c1eb4e60ad8e0dc918Amith Yamasani            }
2600a11e69428d4c00dfcb368c1eb4e60ad8e0dc918Amith Yamasani        }
261a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani        mLastPeriod = thisPeriod;
2620a11e69428d4c00dfcb368c1eb4e60ad8e0dc918Amith Yamasani    }
2630a11e69428d4c00dfcb368c1eb4e60ad8e0dc918Amith Yamasani
26417fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani    private ArrayMap<String, AppUsageHistory> getUserHistory(int userId) {
26517fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani        ArrayMap<String, AppUsageHistory> userHistory = mIdleHistory.get(userId);
2666776849dc5ff851a225745393f082b702754e278Amith Yamasani        if (userHistory == null) {
2676776849dc5ff851a225745393f082b702754e278Amith Yamasani            userHistory = new ArrayMap<>();
2686776849dc5ff851a225745393f082b702754e278Amith Yamasani            mIdleHistory.put(userId, userHistory);
26961d5fd7fee3250bdf4b6ddfbccbd6bceae9436c6Amith Yamasani            readAppIdleTimes(userId, userHistory);
2706776849dc5ff851a225745393f082b702754e278Amith Yamasani        }
2716776849dc5ff851a225745393f082b702754e278Amith Yamasani        return userHistory;
2726776849dc5ff851a225745393f082b702754e278Amith Yamasani    }
2736776849dc5ff851a225745393f082b702754e278Amith Yamasani
27417fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani    private AppUsageHistory getPackageHistory(ArrayMap<String, AppUsageHistory> userHistory,
27517fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani            String packageName, long elapsedRealtime, boolean create) {
27617fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani        AppUsageHistory appUsageHistory = userHistory.get(packageName);
27717fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani        if (appUsageHistory == null && create) {
27817fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani            appUsageHistory = new AppUsageHistory();
27917fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani            appUsageHistory.lastUsedElapsedTime = getElapsedTime(elapsedRealtime);
28017fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani            appUsageHistory.lastUsedScreenTime = getScreenOnTime(elapsedRealtime);
281bd7b302f91f225f2dd2367cc37db9d2b75aec521Amith Yamasani            appUsageHistory.lastPredictedTime = getElapsedTime(0);
282afbccb7d37647f6da61ebcc52a598c7a9f54bc3fAmith Yamasani            appUsageHistory.currentBucket = UsageStatsManager.STANDBY_BUCKET_NEVER;
283afbccb7d37647f6da61ebcc52a598c7a9f54bc3fAmith Yamasani            appUsageHistory.bucketingReason = UsageStatsManager.REASON_DEFAULT;
284afbccb7d37647f6da61ebcc52a598c7a9f54bc3fAmith Yamasani            appUsageHistory.lastInformedBucket = -1;
28517fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani            userHistory.put(packageName, appUsageHistory);
2866776849dc5ff851a225745393f082b702754e278Amith Yamasani        }
28717fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani        return appUsageHistory;
2886776849dc5ff851a225745393f082b702754e278Amith Yamasani    }
2896776849dc5ff851a225745393f082b702754e278Amith Yamasani
290a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani    public void onUserRemoved(int userId) {
2916776849dc5ff851a225745393f082b702754e278Amith Yamasani        mIdleHistory.remove(userId);
2926776849dc5ff851a225745393f082b702754e278Amith Yamasani    }
2936776849dc5ff851a225745393f082b702754e278Amith Yamasani
29461d5fd7fee3250bdf4b6ddfbccbd6bceae9436c6Amith Yamasani    public boolean isIdle(String packageName, int userId, long elapsedRealtime) {
29517fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani        ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
29617fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani        AppUsageHistory appUsageHistory =
29717fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani                getPackageHistory(userHistory, packageName, elapsedRealtime, true);
29817fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani        if (appUsageHistory == null) {
299a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani            return false; // Default to not idle
300a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani        } else {
301afbccb7d37647f6da61ebcc52a598c7a9f54bc3fAmith Yamasani            return appUsageHistory.currentBucket >= UsageStatsManager.STANDBY_BUCKET_RARE;
30217fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani            // Whether or not it's passed will now be externally calculated and the
30317fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani            // bucket will be pushed to the history using setAppStandbyBucket()
30417fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani            //return hasPassedThresholds(appUsageHistory, elapsedRealtime);
30517fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani        }
30617fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani    }
30717fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani
308bd7b302f91f225f2dd2367cc37db9d2b75aec521Amith Yamasani    public AppUsageHistory getAppUsageHistory(String packageName, int userId,
309bd7b302f91f225f2dd2367cc37db9d2b75aec521Amith Yamasani            long elapsedRealtime) {
310bd7b302f91f225f2dd2367cc37db9d2b75aec521Amith Yamasani        ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
311bd7b302f91f225f2dd2367cc37db9d2b75aec521Amith Yamasani        AppUsageHistory appUsageHistory =
312bd7b302f91f225f2dd2367cc37db9d2b75aec521Amith Yamasani                getPackageHistory(userHistory, packageName, elapsedRealtime, true);
313bd7b302f91f225f2dd2367cc37db9d2b75aec521Amith Yamasani        return appUsageHistory;
314bd7b302f91f225f2dd2367cc37db9d2b75aec521Amith Yamasani    }
315bd7b302f91f225f2dd2367cc37db9d2b75aec521Amith Yamasani
31617fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani    public void setAppStandbyBucket(String packageName, int userId, long elapsedRealtime,
31717fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani            int bucket, String reason) {
31817fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani        ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
31917fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani        AppUsageHistory appUsageHistory =
32017fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani                getPackageHistory(userHistory, packageName, elapsedRealtime, true);
32117fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani        appUsageHistory.currentBucket = bucket;
32217fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani        appUsageHistory.bucketingReason = reason;
323bd7b302f91f225f2dd2367cc37db9d2b75aec521Amith Yamasani        if (reason.startsWith(UsageStatsManager.REASON_PREDICTED)) {
324bd7b302f91f225f2dd2367cc37db9d2b75aec521Amith Yamasani            appUsageHistory.lastPredictedTime = getElapsedTime(elapsedRealtime);
325bd7b302f91f225f2dd2367cc37db9d2b75aec521Amith Yamasani        }
32617fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani        if (DEBUG) {
32717fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani            Slog.d(TAG, "Moved " + packageName + " to bucket=" + appUsageHistory.currentBucket
32817fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani                    + ", reason=" + appUsageHistory.bucketingReason);
329a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani        }
330a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani    }
331a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani
33217fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani    public int getAppStandbyBucket(String packageName, int userId, long elapsedRealtime) {
33317fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani        ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
33417fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani        AppUsageHistory appUsageHistory =
33517fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani                getPackageHistory(userHistory, packageName, elapsedRealtime, true);
33617fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani        return appUsageHistory.currentBucket;
33717fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani    }
33817fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani
339e878931414e46eaaf1e10e227cd50bcf5435dee8Amith Yamasani    public Map<String, Integer> getAppStandbyBuckets(int userId, long elapsedRealtime) {
340e878931414e46eaaf1e10e227cd50bcf5435dee8Amith Yamasani        ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
341e878931414e46eaaf1e10e227cd50bcf5435dee8Amith Yamasani        int size = userHistory.size();
342e878931414e46eaaf1e10e227cd50bcf5435dee8Amith Yamasani        HashMap<String, Integer> buckets = new HashMap<>(size);
343e878931414e46eaaf1e10e227cd50bcf5435dee8Amith Yamasani        for (int i = 0; i < size; i++) {
344e878931414e46eaaf1e10e227cd50bcf5435dee8Amith Yamasani            buckets.put(userHistory.keyAt(i), userHistory.valueAt(i).currentBucket);
345e878931414e46eaaf1e10e227cd50bcf5435dee8Amith Yamasani        }
346e878931414e46eaaf1e10e227cd50bcf5435dee8Amith Yamasani        return buckets;
347e878931414e46eaaf1e10e227cd50bcf5435dee8Amith Yamasani    }
348e878931414e46eaaf1e10e227cd50bcf5435dee8Amith Yamasani
34917fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani    public String getAppStandbyReason(String packageName, int userId, long elapsedRealtime) {
35017fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani        ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
35117fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani        AppUsageHistory appUsageHistory =
35217fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani                getPackageHistory(userHistory, packageName, elapsedRealtime, false);
35317fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani        return appUsageHistory != null ? appUsageHistory.bucketingReason : null;
35417fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani    }
35517fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani
356bd7b302f91f225f2dd2367cc37db9d2b75aec521Amith Yamasani    public long getElapsedTime(long elapsedRealtime) {
357a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani        return (elapsedRealtime - mElapsedSnapshot + mElapsedDuration);
358a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani    }
359a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani
360a732f014c5743af0dbb7eb2e63474a7147576f9dChristopher Tate    /* Returns the new standby bucket the app is assigned to */
361a732f014c5743af0dbb7eb2e63474a7147576f9dChristopher Tate    public int setIdle(String packageName, int userId, boolean idle, long elapsedRealtime) {
36217fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani        ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
36317fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani        AppUsageHistory appUsageHistory = getPackageHistory(userHistory, packageName,
36417fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani                elapsedRealtime, true);
36517fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani        if (idle) {
366afbccb7d37647f6da61ebcc52a598c7a9f54bc3fAmith Yamasani            appUsageHistory.currentBucket = UsageStatsManager.STANDBY_BUCKET_RARE;
367afbccb7d37647f6da61ebcc52a598c7a9f54bc3fAmith Yamasani            appUsageHistory.bucketingReason = UsageStatsManager.REASON_FORCED;
36817fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani        } else {
369afbccb7d37647f6da61ebcc52a598c7a9f54bc3fAmith Yamasani            appUsageHistory.currentBucket = UsageStatsManager.STANDBY_BUCKET_ACTIVE;
37017fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani            // This is to pretend that the app was just used, don't freeze the state anymore.
371afbccb7d37647f6da61ebcc52a598c7a9f54bc3fAmith Yamasani            appUsageHistory.bucketingReason = UsageStatsManager.REASON_USAGE;
37217fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani        }
373a732f014c5743af0dbb7eb2e63474a7147576f9dChristopher Tate        return appUsageHistory.currentBucket;
374a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani    }
375a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani
37661d5fd7fee3250bdf4b6ddfbccbd6bceae9436c6Amith Yamasani    public void clearUsage(String packageName, int userId) {
37717fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani        ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
378bdda1e076eb30bf88749a1a349f9bb6f52434ecdAmith Yamasani        userHistory.remove(packageName);
379bdda1e076eb30bf88749a1a349f9bb6f52434ecdAmith Yamasani    }
380bdda1e076eb30bf88749a1a349f9bb6f52434ecdAmith Yamasani
38117fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani    boolean shouldInformListeners(String packageName, int userId,
38284cd7b7a9e5ad6a604c075bc620f6bd9ab6b1486Amith Yamasani            long elapsedRealtime, int bucket) {
38317fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani        ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
38417fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani        AppUsageHistory appUsageHistory = getPackageHistory(userHistory, packageName,
38517fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani                elapsedRealtime, true);
38684cd7b7a9e5ad6a604c075bc620f6bd9ab6b1486Amith Yamasani        if (appUsageHistory.lastInformedBucket != bucket) {
38784cd7b7a9e5ad6a604c075bc620f6bd9ab6b1486Amith Yamasani            appUsageHistory.lastInformedBucket = bucket;
38817fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani            return true;
38917fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani        }
39017fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani        return false;
39117fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani    }
39217fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani
39317fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani    /**
39417fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani     * Returns the index in the arrays of screenTimeThresholds and elapsedTimeThresholds
39517fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani     * that corresponds to how long since the app was used.
39617fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani     * @param packageName
39717fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani     * @param userId
39817fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani     * @param elapsedRealtime current time
39917fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani     * @param screenTimeThresholds Array of screen times, in ascending order, first one is 0
40017fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani     * @param elapsedTimeThresholds Array of elapsed time, in ascending order, first one is 0
40117fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani     * @return The index whose values the app's used time exceeds (in both arrays)
40217fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani     */
40317fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani    int getThresholdIndex(String packageName, int userId, long elapsedRealtime,
40417fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani            long[] screenTimeThresholds, long[] elapsedTimeThresholds) {
40517fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani        ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
40617fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani        AppUsageHistory appUsageHistory = getPackageHistory(userHistory, packageName,
40717fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani                elapsedRealtime, false);
40817fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani        // If we don't have any state for the app, assume never used
40917fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani        if (appUsageHistory == null) return screenTimeThresholds.length - 1;
41017fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani
41117fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani        long screenOnDelta = getScreenOnTime(elapsedRealtime) - appUsageHistory.lastUsedScreenTime;
41217fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani        long elapsedDelta = getElapsedTime(elapsedRealtime) - appUsageHistory.lastUsedElapsedTime;
41317fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani
41417fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani        if (DEBUG) Slog.d(TAG, packageName
41517fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani                + " lastUsedScreen=" + appUsageHistory.lastUsedScreenTime
41617fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani                + " lastUsedElapsed=" + appUsageHistory.lastUsedElapsedTime);
41717fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani        if (DEBUG) Slog.d(TAG, packageName + " screenOn=" + screenOnDelta
41817fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani                + ", elapsed=" + elapsedDelta);
41917fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani        for (int i = screenTimeThresholds.length - 1; i >= 0; i--) {
42017fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani            if (screenOnDelta >= screenTimeThresholds[i]
42117fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani                && elapsedDelta >= elapsedTimeThresholds[i]) {
42217fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani                return i;
42317fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani            }
42417fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani        }
42517fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani        return 0;
426a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani    }
427a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani
42817fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani    @VisibleForTesting
42917fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani    File getUserFile(int userId) {
430a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani        return new File(new File(new File(mStorageDir, "users"),
431a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani                Integer.toString(userId)), APP_IDLE_FILENAME);
432a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani    }
433a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani
43417fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani    private void readAppIdleTimes(int userId, ArrayMap<String, AppUsageHistory> userHistory) {
435a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani        FileInputStream fis = null;
436a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani        try {
437a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani            AtomicFile appIdleFile = new AtomicFile(getUserFile(userId));
438a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani            fis = appIdleFile.openRead();
439a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani            XmlPullParser parser = Xml.newPullParser();
440a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani            parser.setInput(fis, StandardCharsets.UTF_8.name());
441a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani
442a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani            int type;
443a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani            while ((type = parser.next()) != XmlPullParser.START_TAG
444a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani                    && type != XmlPullParser.END_DOCUMENT) {
445a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani                // Skip
446a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani            }
447a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani
448a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani            if (type != XmlPullParser.START_TAG) {
449a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani                Slog.e(TAG, "Unable to read app idle file for user " + userId);
450a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani                return;
451a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani            }
452a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani            if (!parser.getName().equals(TAG_PACKAGES)) {
453a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani                return;
454a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani            }
455a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani            while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
456a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani                if (type == XmlPullParser.START_TAG) {
457a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani                    final String name = parser.getName();
458a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani                    if (name.equals(TAG_PACKAGE)) {
459a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani                        final String packageName = parser.getAttributeValue(null, ATTR_NAME);
46017fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani                        AppUsageHistory appUsageHistory = new AppUsageHistory();
46117fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani                        appUsageHistory.lastUsedElapsedTime =
462a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani                                Long.parseLong(parser.getAttributeValue(null, ATTR_ELAPSED_IDLE));
46317fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani                        appUsageHistory.lastUsedScreenTime =
464a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani                                Long.parseLong(parser.getAttributeValue(null, ATTR_SCREEN_IDLE));
465bd7b302f91f225f2dd2367cc37db9d2b75aec521Amith Yamasani                        String lastPredictedTimeString = parser.getAttributeValue(null,
466bd7b302f91f225f2dd2367cc37db9d2b75aec521Amith Yamasani                                ATTR_LAST_PREDICTED_TIME);
467bd7b302f91f225f2dd2367cc37db9d2b75aec521Amith Yamasani                        if (lastPredictedTimeString != null) {
468bd7b302f91f225f2dd2367cc37db9d2b75aec521Amith Yamasani                            appUsageHistory.lastPredictedTime =
469bd7b302f91f225f2dd2367cc37db9d2b75aec521Amith Yamasani                                    Long.parseLong(lastPredictedTimeString);
470bd7b302f91f225f2dd2367cc37db9d2b75aec521Amith Yamasani                        }
47117fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani                        String currentBucketString = parser.getAttributeValue(null,
47217fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani                                ATTR_CURRENT_BUCKET);
47317fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani                        appUsageHistory.currentBucket = currentBucketString == null
474afbccb7d37647f6da61ebcc52a598c7a9f54bc3fAmith Yamasani                                ? UsageStatsManager.STANDBY_BUCKET_ACTIVE
47517fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani                                : Integer.parseInt(currentBucketString);
47617fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani                        appUsageHistory.bucketingReason =
47717fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani                                parser.getAttributeValue(null, ATTR_BUCKETING_REASON);
47817fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani                        if (appUsageHistory.bucketingReason == null) {
479afbccb7d37647f6da61ebcc52a598c7a9f54bc3fAmith Yamasani                            appUsageHistory.bucketingReason = UsageStatsManager.REASON_DEFAULT;
48017fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani                        }
481bd7b302f91f225f2dd2367cc37db9d2b75aec521Amith Yamasani                        appUsageHistory.lastInformedBucket = -1;
48217fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani                        userHistory.put(packageName, appUsageHistory);
483a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani                    }
484a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani                }
485a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani            }
486a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani        } catch (IOException | XmlPullParserException e) {
487a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani            Slog.e(TAG, "Unable to read app idle file for user " + userId);
488a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani        } finally {
489a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani            IoUtils.closeQuietly(fis);
490a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani        }
491a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani    }
492a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani
49361d5fd7fee3250bdf4b6ddfbccbd6bceae9436c6Amith Yamasani    public void writeAppIdleTimes(int userId) {
494a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani        FileOutputStream fos = null;
495a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani        AtomicFile appIdleFile = new AtomicFile(getUserFile(userId));
496a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani        try {
497a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani            fos = appIdleFile.startWrite();
498a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani            final BufferedOutputStream bos = new BufferedOutputStream(fos);
499a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani
500a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani            FastXmlSerializer xml = new FastXmlSerializer();
501a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani            xml.setOutput(bos, StandardCharsets.UTF_8.name());
502a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani            xml.startDocument(null, true);
503a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani            xml.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
504a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani
505a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani            xml.startTag(null, TAG_PACKAGES);
506a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani
50717fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani            ArrayMap<String,AppUsageHistory> userHistory = getUserHistory(userId);
508a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani            final int N = userHistory.size();
509a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani            for (int i = 0; i < N; i++) {
510a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani                String packageName = userHistory.keyAt(i);
51117fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani                AppUsageHistory history = userHistory.valueAt(i);
512a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani                xml.startTag(null, TAG_PACKAGE);
513a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani                xml.attribute(null, ATTR_NAME, packageName);
514a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani                xml.attribute(null, ATTR_ELAPSED_IDLE,
515a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani                        Long.toString(history.lastUsedElapsedTime));
516a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani                xml.attribute(null, ATTR_SCREEN_IDLE,
517a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani                        Long.toString(history.lastUsedScreenTime));
518bd7b302f91f225f2dd2367cc37db9d2b75aec521Amith Yamasani                xml.attribute(null, ATTR_LAST_PREDICTED_TIME,
519bd7b302f91f225f2dd2367cc37db9d2b75aec521Amith Yamasani                        Long.toString(history.lastPredictedTime));
52017fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani                xml.attribute(null, ATTR_CURRENT_BUCKET,
52117fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani                        Integer.toString(history.currentBucket));
52217fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani                xml.attribute(null, ATTR_BUCKETING_REASON, history.bucketingReason);
523a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani                xml.endTag(null, TAG_PACKAGE);
524a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani            }
525a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani
526a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani            xml.endTag(null, TAG_PACKAGES);
527a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani            xml.endDocument();
528a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani            appIdleFile.finishWrite(fos);
529a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani        } catch (Exception e) {
530a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani            appIdleFile.failWrite(fos);
531a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani            Slog.e(TAG, "Error writing app idle file for user " + userId);
532a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani        }
5330a11e69428d4c00dfcb368c1eb4e60ad8e0dc918Amith Yamasani    }
5340a11e69428d4c00dfcb368c1eb4e60ad8e0dc918Amith Yamasani
535c81983a0c3d7bfe8384dbf48909f4bcf300e36d2Dianne Hackborn    public void dump(IndentingPrintWriter idpw, int userId, String pkg) {
536a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani        idpw.println("Package idle stats:");
537a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani        idpw.increaseIndent();
53817fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani        ArrayMap<String, AppUsageHistory> userHistory = mIdleHistory.get(userId);
539a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani        final long elapsedRealtime = SystemClock.elapsedRealtime();
54061d5fd7fee3250bdf4b6ddfbccbd6bceae9436c6Amith Yamasani        final long totalElapsedTime = getElapsedTime(elapsedRealtime);
54161d5fd7fee3250bdf4b6ddfbccbd6bceae9436c6Amith Yamasani        final long screenOnTime = getScreenOnTime(elapsedRealtime);
542a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani        if (userHistory == null) return;
543a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani        final int P = userHistory.size();
544a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani        for (int p = 0; p < P; p++) {
545a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani            final String packageName = userHistory.keyAt(p);
54617fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani            final AppUsageHistory appUsageHistory = userHistory.valueAt(p);
547c81983a0c3d7bfe8384dbf48909f4bcf300e36d2Dianne Hackborn            if (pkg != null && !pkg.equals(packageName)) {
548c81983a0c3d7bfe8384dbf48909f4bcf300e36d2Dianne Hackborn                continue;
549c81983a0c3d7bfe8384dbf48909f4bcf300e36d2Dianne Hackborn            }
550a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani            idpw.print("package=" + packageName);
551a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani            idpw.print(" lastUsedElapsed=");
55217fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani            TimeUtils.formatDuration(totalElapsedTime - appUsageHistory.lastUsedElapsedTime, idpw);
553a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani            idpw.print(" lastUsedScreenOn=");
55417fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani            TimeUtils.formatDuration(screenOnTime - appUsageHistory.lastUsedScreenTime, idpw);
555bd7b302f91f225f2dd2367cc37db9d2b75aec521Amith Yamasani            idpw.print(" lastPredictedTime=");
556bd7b302f91f225f2dd2367cc37db9d2b75aec521Amith Yamasani            TimeUtils.formatDuration(totalElapsedTime - appUsageHistory.lastPredictedTime, idpw);
55761d5fd7fee3250bdf4b6ddfbccbd6bceae9436c6Amith Yamasani            idpw.print(" idle=" + (isIdle(packageName, userId, elapsedRealtime) ? "y" : "n"));
55817fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani            idpw.print(" bucket=" + appUsageHistory.currentBucket
55917fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani                    + " reason=" + appUsageHistory.bucketingReason);
560a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani            idpw.println();
561a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani        }
562a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani        idpw.println();
563a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani        idpw.print("totalElapsedTime=");
56461d5fd7fee3250bdf4b6ddfbccbd6bceae9436c6Amith Yamasani        TimeUtils.formatDuration(getElapsedTime(elapsedRealtime), idpw);
565a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani        idpw.println();
566a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani        idpw.print("totalScreenOnTime=");
56761d5fd7fee3250bdf4b6ddfbccbd6bceae9436c6Amith Yamasani        TimeUtils.formatDuration(getScreenOnTime(elapsedRealtime), idpw);
568a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani        idpw.println();
569a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani        idpw.decreaseIndent();
570a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani    }
571a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani
572a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani    public void dumpHistory(IndentingPrintWriter idpw, int userId) {
57317fffee4908f11038ba9cc5a672d15cb25be3dfeAmith Yamasani        ArrayMap<String, AppUsageHistory> userHistory = mIdleHistory.get(userId);
574a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani        final long elapsedRealtime = SystemClock.elapsedRealtime();
5750a11e69428d4c00dfcb368c1eb4e60ad8e0dc918Amith Yamasani        if (userHistory == null) return;
5760a11e69428d4c00dfcb368c1eb4e60ad8e0dc918Amith Yamasani        final int P = userHistory.size();
5770a11e69428d4c00dfcb368c1eb4e60ad8e0dc918Amith Yamasani        for (int p = 0; p < P; p++) {
5780a11e69428d4c00dfcb368c1eb4e60ad8e0dc918Amith Yamasani            final String packageName = userHistory.keyAt(p);
579a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani            final byte[] history = userHistory.valueAt(p).recent;
5800a11e69428d4c00dfcb368c1eb4e60ad8e0dc918Amith Yamasani            for (int i = 0; i < HISTORY_SIZE; i++) {
5810a11e69428d4c00dfcb368c1eb4e60ad8e0dc918Amith Yamasani                idpw.print(history[i] == 0 ? '.' : 'A');
5820a11e69428d4c00dfcb368c1eb4e60ad8e0dc918Amith Yamasani            }
58361d5fd7fee3250bdf4b6ddfbccbd6bceae9436c6Amith Yamasani            idpw.print(" idle=" + (isIdle(packageName, userId, elapsedRealtime) ? "y" : "n"));
5840a11e69428d4c00dfcb368c1eb4e60ad8e0dc918Amith Yamasani            idpw.print("  " + packageName);
5850a11e69428d4c00dfcb368c1eb4e60ad8e0dc918Amith Yamasani            idpw.println();
5860a11e69428d4c00dfcb368c1eb4e60ad8e0dc918Amith Yamasani        }
5870a11e69428d4c00dfcb368c1eb4e60ad8e0dc918Amith Yamasani    }
588a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani}
589