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 19a93542f9d341897f3206f775fd5720663b17504fAmith Yamasaniimport android.os.Environment; 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; 45a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani 460a11e69428d4c00dfcb368c1eb4e60ad8e0dc918Amith Yamasani/** 470a11e69428d4c00dfcb368c1eb4e60ad8e0dc918Amith Yamasani * Keeps track of recent active state changes in apps. 480a11e69428d4c00dfcb368c1eb4e60ad8e0dc918Amith Yamasani * Access should be guarded by a lock by the caller. 490a11e69428d4c00dfcb368c1eb4e60ad8e0dc918Amith Yamasani */ 500a11e69428d4c00dfcb368c1eb4e60ad8e0dc918Amith Yamasanipublic class AppIdleHistory { 510a11e69428d4c00dfcb368c1eb4e60ad8e0dc918Amith Yamasani 52a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani private static final String TAG = "AppIdleHistory"; 53a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani 54a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani // History for all users and all packages 55a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani private SparseArray<ArrayMap<String,PackageHistory>> mIdleHistory = new SparseArray<>(); 56a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani private long mLastPeriod = 0; 570a11e69428d4c00dfcb368c1eb4e60ad8e0dc918Amith Yamasani private static final long ONE_MINUTE = 60 * 1000; 580a11e69428d4c00dfcb368c1eb4e60ad8e0dc918Amith Yamasani private static final int HISTORY_SIZE = 100; 590a11e69428d4c00dfcb368c1eb4e60ad8e0dc918Amith Yamasani private static final int FLAG_LAST_STATE = 2; 600a11e69428d4c00dfcb368c1eb4e60ad8e0dc918Amith Yamasani private static final int FLAG_PARTIAL_ACTIVE = 1; 616776849dc5ff851a225745393f082b702754e278Amith Yamasani private static final long PERIOD_DURATION = UsageStatsService.COMPRESS_TIME ? ONE_MINUTE 620a11e69428d4c00dfcb368c1eb4e60ad8e0dc918Amith Yamasani : 60 * ONE_MINUTE; 630a11e69428d4c00dfcb368c1eb4e60ad8e0dc918Amith Yamasani 64a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani @VisibleForTesting 65a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani static final String APP_IDLE_FILENAME = "app_idle_stats.xml"; 66a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani private static final String TAG_PACKAGES = "packages"; 67a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani private static final String TAG_PACKAGE = "package"; 68a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani private static final String ATTR_NAME = "name"; 69a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani // Screen on timebase time when app was last used 70a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani private static final String ATTR_SCREEN_IDLE = "screenIdleTime"; 71a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani // Elapsed timebase time when app was last used 72a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani private static final String ATTR_ELAPSED_IDLE = "elapsedIdleTime"; 73a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani 74a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani // device on time = mElapsedDuration + (timeNow - mElapsedSnapshot) 75a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani private long mElapsedSnapshot; // Elapsed time snapshot when last write of mDeviceOnDuration 76a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani private long mElapsedDuration; // Total device on duration since device was "born" 77a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani 78a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani // screen on time = mScreenOnDuration + (timeNow - mScreenOnSnapshot) 79a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani private long mScreenOnSnapshot; // Elapsed time snapshot when last write of mScreenOnDuration 80a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani private long mScreenOnDuration; // Total screen on duration since device was "born" 81a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani 82a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani private long mElapsedTimeThreshold; 83a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani private long mScreenOnTimeThreshold; 84a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani private final File mStorageDir; 85a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani 86a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani private boolean mScreenOn; 87a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani 88a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani private static class PackageHistory { 89a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani final byte[] recent = new byte[HISTORY_SIZE]; 90a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani long lastUsedElapsedTime; 91a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani long lastUsedScreenTime; 92a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani } 93a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani 94a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani AppIdleHistory(long elapsedRealtime) { 95a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani this(Environment.getDataSystemDirectory(), elapsedRealtime); 96a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani } 97a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani 98a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani @VisibleForTesting 99a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani AppIdleHistory(File storageDir, long elapsedRealtime) { 100a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani mElapsedSnapshot = elapsedRealtime; 101a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani mScreenOnSnapshot = elapsedRealtime; 102a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani mStorageDir = storageDir; 10361d5fd7fee3250bdf4b6ddfbccbd6bceae9436c6Amith Yamasani readScreenOnTime(); 104a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani } 105a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani 106a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani public void setThresholds(long elapsedTimeThreshold, long screenOnTimeThreshold) { 107a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani mElapsedTimeThreshold = elapsedTimeThreshold; 108a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani mScreenOnTimeThreshold = screenOnTimeThreshold; 109a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani } 110a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani 11161d5fd7fee3250bdf4b6ddfbccbd6bceae9436c6Amith Yamasani public void updateDisplay(boolean screenOn, long elapsedRealtime) { 112a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani if (screenOn == mScreenOn) return; 113a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani 114a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani mScreenOn = screenOn; 115a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani if (mScreenOn) { 116a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani mScreenOnSnapshot = elapsedRealtime; 117a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani } else { 118a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani mScreenOnDuration += elapsedRealtime - mScreenOnSnapshot; 119a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani mElapsedDuration += elapsedRealtime - mElapsedSnapshot; 120a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani mElapsedSnapshot = elapsedRealtime; 121a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani } 122a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani } 123a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani 12461d5fd7fee3250bdf4b6ddfbccbd6bceae9436c6Amith Yamasani public long getScreenOnTime(long elapsedRealtime) { 125a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani long screenOnTime = mScreenOnDuration; 126a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani if (mScreenOn) { 127a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani screenOnTime += elapsedRealtime - mScreenOnSnapshot; 128a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani } 129a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani return screenOnTime; 130a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani } 131a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani 132a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani @VisibleForTesting 133a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani File getScreenOnTimeFile() { 134a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani return new File(mStorageDir, "screen_on_time"); 135a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani } 136a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani 13761d5fd7fee3250bdf4b6ddfbccbd6bceae9436c6Amith Yamasani private void readScreenOnTime() { 138a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani File screenOnTimeFile = getScreenOnTimeFile(); 139a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani if (screenOnTimeFile.exists()) { 140a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani try { 141a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani BufferedReader reader = new BufferedReader(new FileReader(screenOnTimeFile)); 142a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani mScreenOnDuration = Long.parseLong(reader.readLine()); 143a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani mElapsedDuration = Long.parseLong(reader.readLine()); 144a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani reader.close(); 145a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani } catch (IOException | NumberFormatException e) { 146a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani } 147a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani } else { 14861d5fd7fee3250bdf4b6ddfbccbd6bceae9436c6Amith Yamasani writeScreenOnTime(); 149a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani } 150a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani } 151a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani 15261d5fd7fee3250bdf4b6ddfbccbd6bceae9436c6Amith Yamasani private void writeScreenOnTime() { 153a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani AtomicFile screenOnTimeFile = new AtomicFile(getScreenOnTimeFile()); 154a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani FileOutputStream fos = null; 155a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani try { 156a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani fos = screenOnTimeFile.startWrite(); 157a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani fos.write((Long.toString(mScreenOnDuration) + "\n" 158a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani + Long.toString(mElapsedDuration) + "\n").getBytes()); 159a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani screenOnTimeFile.finishWrite(fos); 160a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani } catch (IOException ioe) { 161a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani screenOnTimeFile.failWrite(fos); 162a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani } 163a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani } 164a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani 165a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani /** 166a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani * To be called periodically to keep track of elapsed time when app idle times are written 167a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani */ 16861d5fd7fee3250bdf4b6ddfbccbd6bceae9436c6Amith Yamasani public void writeAppIdleDurations() { 169a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani final long elapsedRealtime = SystemClock.elapsedRealtime(); 170a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani // Only bump up and snapshot the elapsed time. Don't change screen on duration. 171a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani mElapsedDuration += elapsedRealtime - mElapsedSnapshot; 172a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani mElapsedSnapshot = elapsedRealtime; 17361d5fd7fee3250bdf4b6ddfbccbd6bceae9436c6Amith Yamasani writeScreenOnTime(); 174a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani } 175a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani 17661d5fd7fee3250bdf4b6ddfbccbd6bceae9436c6Amith Yamasani public void reportUsage(String packageName, int userId, long elapsedRealtime) { 17761d5fd7fee3250bdf4b6ddfbccbd6bceae9436c6Amith Yamasani ArrayMap<String, PackageHistory> userHistory = getUserHistory(userId); 17861d5fd7fee3250bdf4b6ddfbccbd6bceae9436c6Amith Yamasani PackageHistory packageHistory = getPackageHistory(userHistory, packageName, 179a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani elapsedRealtime); 180a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani 181a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani shiftHistoryToNow(userHistory, elapsedRealtime); 182a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani 183a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani packageHistory.lastUsedElapsedTime = mElapsedDuration 184a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani + (elapsedRealtime - mElapsedSnapshot); 18561d5fd7fee3250bdf4b6ddfbccbd6bceae9436c6Amith Yamasani packageHistory.lastUsedScreenTime = getScreenOnTime(elapsedRealtime); 186a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani packageHistory.recent[HISTORY_SIZE - 1] = FLAG_LAST_STATE | FLAG_PARTIAL_ACTIVE; 187a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani } 188a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani 189a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani public void setIdle(String packageName, int userId, long elapsedRealtime) { 19061d5fd7fee3250bdf4b6ddfbccbd6bceae9436c6Amith Yamasani ArrayMap<String, PackageHistory> userHistory = getUserHistory(userId); 19161d5fd7fee3250bdf4b6ddfbccbd6bceae9436c6Amith Yamasani PackageHistory packageHistory = getPackageHistory(userHistory, packageName, 192a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani elapsedRealtime); 193a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani 194a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani shiftHistoryToNow(userHistory, elapsedRealtime); 1956776849dc5ff851a225745393f082b702754e278Amith Yamasani 196a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani packageHistory.recent[HISTORY_SIZE - 1] &= ~FLAG_LAST_STATE; 197a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani } 198a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani 199a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani private void shiftHistoryToNow(ArrayMap<String, PackageHistory> userHistory, 200a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani long elapsedRealtime) { 201a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani long thisPeriod = elapsedRealtime / PERIOD_DURATION; 2020a11e69428d4c00dfcb368c1eb4e60ad8e0dc918Amith Yamasani // Has the period switched over? Slide all users' package histories 203a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani if (mLastPeriod != 0 && mLastPeriod < thisPeriod 204a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani && (thisPeriod - mLastPeriod) < HISTORY_SIZE - 1) { 205a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani int diff = (int) (thisPeriod - mLastPeriod); 2066776849dc5ff851a225745393f082b702754e278Amith Yamasani final int NUSERS = mIdleHistory.size(); 2070a11e69428d4c00dfcb368c1eb4e60ad8e0dc918Amith Yamasani for (int u = 0; u < NUSERS; u++) { 2086776849dc5ff851a225745393f082b702754e278Amith Yamasani userHistory = mIdleHistory.valueAt(u); 209a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani for (PackageHistory idleState : userHistory.values()) { 2100a11e69428d4c00dfcb368c1eb4e60ad8e0dc918Amith Yamasani // Shift left 211a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani System.arraycopy(idleState.recent, diff, idleState.recent, 0, 212a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani HISTORY_SIZE - diff); 2130a11e69428d4c00dfcb368c1eb4e60ad8e0dc918Amith Yamasani // Replicate last state across the diff 2140a11e69428d4c00dfcb368c1eb4e60ad8e0dc918Amith Yamasani for (int i = 0; i < diff; i++) { 215a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani idleState.recent[HISTORY_SIZE - i - 1] = 216a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani (byte) (idleState.recent[HISTORY_SIZE - diff - 1] & FLAG_LAST_STATE); 2170a11e69428d4c00dfcb368c1eb4e60ad8e0dc918Amith Yamasani } 2180a11e69428d4c00dfcb368c1eb4e60ad8e0dc918Amith Yamasani } 2190a11e69428d4c00dfcb368c1eb4e60ad8e0dc918Amith Yamasani } 2200a11e69428d4c00dfcb368c1eb4e60ad8e0dc918Amith Yamasani } 221a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani mLastPeriod = thisPeriod; 2220a11e69428d4c00dfcb368c1eb4e60ad8e0dc918Amith Yamasani } 2230a11e69428d4c00dfcb368c1eb4e60ad8e0dc918Amith Yamasani 22461d5fd7fee3250bdf4b6ddfbccbd6bceae9436c6Amith Yamasani private ArrayMap<String, PackageHistory> getUserHistory(int userId) { 225a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani ArrayMap<String, PackageHistory> userHistory = mIdleHistory.get(userId); 2266776849dc5ff851a225745393f082b702754e278Amith Yamasani if (userHistory == null) { 2276776849dc5ff851a225745393f082b702754e278Amith Yamasani userHistory = new ArrayMap<>(); 2286776849dc5ff851a225745393f082b702754e278Amith Yamasani mIdleHistory.put(userId, userHistory); 22961d5fd7fee3250bdf4b6ddfbccbd6bceae9436c6Amith Yamasani readAppIdleTimes(userId, userHistory); 2306776849dc5ff851a225745393f082b702754e278Amith Yamasani } 2316776849dc5ff851a225745393f082b702754e278Amith Yamasani return userHistory; 2326776849dc5ff851a225745393f082b702754e278Amith Yamasani } 2336776849dc5ff851a225745393f082b702754e278Amith Yamasani 23461d5fd7fee3250bdf4b6ddfbccbd6bceae9436c6Amith Yamasani private PackageHistory getPackageHistory(ArrayMap<String, PackageHistory> userHistory, 235a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani String packageName, long elapsedRealtime) { 236a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani PackageHistory packageHistory = userHistory.get(packageName); 2376776849dc5ff851a225745393f082b702754e278Amith Yamasani if (packageHistory == null) { 238a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani packageHistory = new PackageHistory(); 23961d5fd7fee3250bdf4b6ddfbccbd6bceae9436c6Amith Yamasani packageHistory.lastUsedElapsedTime = getElapsedTime(elapsedRealtime); 24061d5fd7fee3250bdf4b6ddfbccbd6bceae9436c6Amith Yamasani packageHistory.lastUsedScreenTime = getScreenOnTime(elapsedRealtime); 2416776849dc5ff851a225745393f082b702754e278Amith Yamasani userHistory.put(packageName, packageHistory); 2426776849dc5ff851a225745393f082b702754e278Amith Yamasani } 2436776849dc5ff851a225745393f082b702754e278Amith Yamasani return packageHistory; 2446776849dc5ff851a225745393f082b702754e278Amith Yamasani } 2456776849dc5ff851a225745393f082b702754e278Amith Yamasani 246a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani public void onUserRemoved(int userId) { 2476776849dc5ff851a225745393f082b702754e278Amith Yamasani mIdleHistory.remove(userId); 2486776849dc5ff851a225745393f082b702754e278Amith Yamasani } 2496776849dc5ff851a225745393f082b702754e278Amith Yamasani 25061d5fd7fee3250bdf4b6ddfbccbd6bceae9436c6Amith Yamasani public boolean isIdle(String packageName, int userId, long elapsedRealtime) { 25161d5fd7fee3250bdf4b6ddfbccbd6bceae9436c6Amith Yamasani ArrayMap<String, PackageHistory> userHistory = getUserHistory(userId); 252a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani PackageHistory packageHistory = 25361d5fd7fee3250bdf4b6ddfbccbd6bceae9436c6Amith Yamasani getPackageHistory(userHistory, packageName, elapsedRealtime); 254a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani if (packageHistory == null) { 255a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani return false; // Default to not idle 256a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani } else { 25761d5fd7fee3250bdf4b6ddfbccbd6bceae9436c6Amith Yamasani return hasPassedThresholds(packageHistory, elapsedRealtime); 258a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani } 259a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani } 260a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani 26161d5fd7fee3250bdf4b6ddfbccbd6bceae9436c6Amith Yamasani private long getElapsedTime(long elapsedRealtime) { 262a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani return (elapsedRealtime - mElapsedSnapshot + mElapsedDuration); 263a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani } 264a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani 26561d5fd7fee3250bdf4b6ddfbccbd6bceae9436c6Amith Yamasani public void setIdle(String packageName, int userId, boolean idle, long elapsedRealtime) { 26661d5fd7fee3250bdf4b6ddfbccbd6bceae9436c6Amith Yamasani ArrayMap<String, PackageHistory> userHistory = getUserHistory(userId); 26761d5fd7fee3250bdf4b6ddfbccbd6bceae9436c6Amith Yamasani PackageHistory packageHistory = getPackageHistory(userHistory, packageName, 268a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani elapsedRealtime); 26961d5fd7fee3250bdf4b6ddfbccbd6bceae9436c6Amith Yamasani packageHistory.lastUsedElapsedTime = getElapsedTime(elapsedRealtime) 270a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani - mElapsedTimeThreshold; 27161d5fd7fee3250bdf4b6ddfbccbd6bceae9436c6Amith Yamasani packageHistory.lastUsedScreenTime = getScreenOnTime(elapsedRealtime) 272a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani - (idle ? mScreenOnTimeThreshold : 0) - 1000 /* just a second more */; 273a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani } 274a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani 27561d5fd7fee3250bdf4b6ddfbccbd6bceae9436c6Amith Yamasani public void clearUsage(String packageName, int userId) { 27661d5fd7fee3250bdf4b6ddfbccbd6bceae9436c6Amith Yamasani ArrayMap<String, PackageHistory> userHistory = getUserHistory(userId); 277bdda1e076eb30bf88749a1a349f9bb6f52434ecdAmith Yamasani userHistory.remove(packageName); 278bdda1e076eb30bf88749a1a349f9bb6f52434ecdAmith Yamasani } 279bdda1e076eb30bf88749a1a349f9bb6f52434ecdAmith Yamasani 28061d5fd7fee3250bdf4b6ddfbccbd6bceae9436c6Amith Yamasani private boolean hasPassedThresholds(PackageHistory packageHistory, long elapsedRealtime) { 281a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani return (packageHistory.lastUsedScreenTime 28261d5fd7fee3250bdf4b6ddfbccbd6bceae9436c6Amith Yamasani <= getScreenOnTime(elapsedRealtime) - mScreenOnTimeThreshold) 283a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani && (packageHistory.lastUsedElapsedTime 28461d5fd7fee3250bdf4b6ddfbccbd6bceae9436c6Amith Yamasani <= getElapsedTime(elapsedRealtime) - mElapsedTimeThreshold); 285a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani } 286a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani 287a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani private File getUserFile(int userId) { 288a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani return new File(new File(new File(mStorageDir, "users"), 289a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani Integer.toString(userId)), APP_IDLE_FILENAME); 290a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani } 291a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani 29261d5fd7fee3250bdf4b6ddfbccbd6bceae9436c6Amith Yamasani private void readAppIdleTimes(int userId, ArrayMap<String, PackageHistory> userHistory) { 293a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani FileInputStream fis = null; 294a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani try { 295a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani AtomicFile appIdleFile = new AtomicFile(getUserFile(userId)); 296a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani fis = appIdleFile.openRead(); 297a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani XmlPullParser parser = Xml.newPullParser(); 298a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani parser.setInput(fis, StandardCharsets.UTF_8.name()); 299a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani 300a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani int type; 301a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani while ((type = parser.next()) != XmlPullParser.START_TAG 302a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani && type != XmlPullParser.END_DOCUMENT) { 303a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani // Skip 304a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani } 305a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani 306a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani if (type != XmlPullParser.START_TAG) { 307a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani Slog.e(TAG, "Unable to read app idle file for user " + userId); 308a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani return; 309a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani } 310a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani if (!parser.getName().equals(TAG_PACKAGES)) { 311a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani return; 312a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani } 313a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) { 314a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani if (type == XmlPullParser.START_TAG) { 315a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani final String name = parser.getName(); 316a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani if (name.equals(TAG_PACKAGE)) { 317a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani final String packageName = parser.getAttributeValue(null, ATTR_NAME); 318a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani PackageHistory packageHistory = new PackageHistory(); 319a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani packageHistory.lastUsedElapsedTime = 320a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani Long.parseLong(parser.getAttributeValue(null, ATTR_ELAPSED_IDLE)); 321a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani packageHistory.lastUsedScreenTime = 322a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani Long.parseLong(parser.getAttributeValue(null, ATTR_SCREEN_IDLE)); 323a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani userHistory.put(packageName, packageHistory); 324a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani } 325a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani } 326a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani } 327a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani } catch (IOException | XmlPullParserException e) { 328a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani Slog.e(TAG, "Unable to read app idle file for user " + userId); 329a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani } finally { 330a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani IoUtils.closeQuietly(fis); 331a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani } 332a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani } 333a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani 33461d5fd7fee3250bdf4b6ddfbccbd6bceae9436c6Amith Yamasani public void writeAppIdleTimes(int userId) { 335a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani FileOutputStream fos = null; 336a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani AtomicFile appIdleFile = new AtomicFile(getUserFile(userId)); 337a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani try { 338a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani fos = appIdleFile.startWrite(); 339a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani final BufferedOutputStream bos = new BufferedOutputStream(fos); 340a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani 341a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani FastXmlSerializer xml = new FastXmlSerializer(); 342a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani xml.setOutput(bos, StandardCharsets.UTF_8.name()); 343a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani xml.startDocument(null, true); 344a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani xml.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); 345a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani 346a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani xml.startTag(null, TAG_PACKAGES); 347a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani 34861d5fd7fee3250bdf4b6ddfbccbd6bceae9436c6Amith Yamasani ArrayMap<String,PackageHistory> userHistory = getUserHistory(userId); 349a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani final int N = userHistory.size(); 350a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani for (int i = 0; i < N; i++) { 351a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani String packageName = userHistory.keyAt(i); 352a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani PackageHistory history = userHistory.valueAt(i); 353a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani xml.startTag(null, TAG_PACKAGE); 354a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani xml.attribute(null, ATTR_NAME, packageName); 355a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani xml.attribute(null, ATTR_ELAPSED_IDLE, 356a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani Long.toString(history.lastUsedElapsedTime)); 357a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani xml.attribute(null, ATTR_SCREEN_IDLE, 358a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani Long.toString(history.lastUsedScreenTime)); 359a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani xml.endTag(null, TAG_PACKAGE); 360a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani } 361a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani 362a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani xml.endTag(null, TAG_PACKAGES); 363a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani xml.endDocument(); 364a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani appIdleFile.finishWrite(fos); 365a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani } catch (Exception e) { 366a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani appIdleFile.failWrite(fos); 367a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani Slog.e(TAG, "Error writing app idle file for user " + userId); 368a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani } 3690a11e69428d4c00dfcb368c1eb4e60ad8e0dc918Amith Yamasani } 3700a11e69428d4c00dfcb368c1eb4e60ad8e0dc918Amith Yamasani 3710a11e69428d4c00dfcb368c1eb4e60ad8e0dc918Amith Yamasani public void dump(IndentingPrintWriter idpw, int userId) { 372a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani idpw.println("Package idle stats:"); 373a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani idpw.increaseIndent(); 374a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani ArrayMap<String, PackageHistory> userHistory = mIdleHistory.get(userId); 375a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani final long elapsedRealtime = SystemClock.elapsedRealtime(); 37661d5fd7fee3250bdf4b6ddfbccbd6bceae9436c6Amith Yamasani final long totalElapsedTime = getElapsedTime(elapsedRealtime); 37761d5fd7fee3250bdf4b6ddfbccbd6bceae9436c6Amith Yamasani final long screenOnTime = getScreenOnTime(elapsedRealtime); 378a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani if (userHistory == null) return; 379a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani final int P = userHistory.size(); 380a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani for (int p = 0; p < P; p++) { 381a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani final String packageName = userHistory.keyAt(p); 382a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani final PackageHistory packageHistory = userHistory.valueAt(p); 383a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani idpw.print("package=" + packageName); 384a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani idpw.print(" lastUsedElapsed="); 385a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani TimeUtils.formatDuration(totalElapsedTime - packageHistory.lastUsedElapsedTime, idpw); 386a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani idpw.print(" lastUsedScreenOn="); 387a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani TimeUtils.formatDuration(screenOnTime - packageHistory.lastUsedScreenTime, idpw); 38861d5fd7fee3250bdf4b6ddfbccbd6bceae9436c6Amith Yamasani idpw.print(" idle=" + (isIdle(packageName, userId, elapsedRealtime) ? "y" : "n")); 389a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani idpw.println(); 390a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani } 391a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani idpw.println(); 392a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani idpw.print("totalElapsedTime="); 39361d5fd7fee3250bdf4b6ddfbccbd6bceae9436c6Amith Yamasani TimeUtils.formatDuration(getElapsedTime(elapsedRealtime), idpw); 394a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani idpw.println(); 395a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani idpw.print("totalScreenOnTime="); 39661d5fd7fee3250bdf4b6ddfbccbd6bceae9436c6Amith Yamasani TimeUtils.formatDuration(getScreenOnTime(elapsedRealtime), idpw); 397a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani idpw.println(); 398a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani idpw.decreaseIndent(); 399a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani } 400a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani 401a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani public void dumpHistory(IndentingPrintWriter idpw, int userId) { 402a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani ArrayMap<String, PackageHistory> userHistory = mIdleHistory.get(userId); 403a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani final long elapsedRealtime = SystemClock.elapsedRealtime(); 4040a11e69428d4c00dfcb368c1eb4e60ad8e0dc918Amith Yamasani if (userHistory == null) return; 4050a11e69428d4c00dfcb368c1eb4e60ad8e0dc918Amith Yamasani final int P = userHistory.size(); 4060a11e69428d4c00dfcb368c1eb4e60ad8e0dc918Amith Yamasani for (int p = 0; p < P; p++) { 4070a11e69428d4c00dfcb368c1eb4e60ad8e0dc918Amith Yamasani final String packageName = userHistory.keyAt(p); 408a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani final byte[] history = userHistory.valueAt(p).recent; 4090a11e69428d4c00dfcb368c1eb4e60ad8e0dc918Amith Yamasani for (int i = 0; i < HISTORY_SIZE; i++) { 4100a11e69428d4c00dfcb368c1eb4e60ad8e0dc918Amith Yamasani idpw.print(history[i] == 0 ? '.' : 'A'); 4110a11e69428d4c00dfcb368c1eb4e60ad8e0dc918Amith Yamasani } 41261d5fd7fee3250bdf4b6ddfbccbd6bceae9436c6Amith Yamasani idpw.print(" idle=" + (isIdle(packageName, userId, elapsedRealtime) ? "y" : "n")); 4130a11e69428d4c00dfcb368c1eb4e60ad8e0dc918Amith Yamasani idpw.print(" " + packageName); 4140a11e69428d4c00dfcb368c1eb4e60ad8e0dc918Amith Yamasani idpw.println(); 4150a11e69428d4c00dfcb368c1eb4e60ad8e0dc918Amith Yamasani } 4160a11e69428d4c00dfcb368c1eb4e60ad8e0dc918Amith Yamasani } 417a93542f9d341897f3206f775fd5720663b17504fAmith Yamasani} 418