1 /* 2 * Copyright (C) 2016 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server.pm; 18 19 import static android.os.Process.PACKAGE_INFO_GID; 20 import static android.os.Process.SYSTEM_UID; 21 22 import android.content.pm.PackageManager; 23 import android.os.FileUtils; 24 import android.util.AtomicFile; 25 import android.util.Log; 26 27 import libcore.io.IoUtils; 28 29 import java.io.BufferedInputStream; 30 import java.io.BufferedOutputStream; 31 import java.io.FileNotFoundException; 32 import java.io.FileOutputStream; 33 import java.io.IOException; 34 import java.io.InputStream; 35 import java.nio.charset.StandardCharsets; 36 import java.util.Map; 37 38 class PackageUsage extends AbstractStatsBase<Map<String, PackageSetting>> { 39 40 private static final String USAGE_FILE_MAGIC = "PACKAGE_USAGE__VERSION_"; 41 private static final String USAGE_FILE_MAGIC_VERSION_1 = USAGE_FILE_MAGIC + "1"; 42 43 private boolean mIsHistoricalPackageUsageAvailable = true; 44 PackageUsage()45 PackageUsage() { 46 super("package-usage.list", "PackageUsage_DiskWriter", /* lock */ true); 47 } 48 isHistoricalPackageUsageAvailable()49 boolean isHistoricalPackageUsageAvailable() { 50 return mIsHistoricalPackageUsageAvailable; 51 } 52 53 @Override writeInternal(Map<String, PackageSetting> pkgSettings)54 protected void writeInternal(Map<String, PackageSetting> pkgSettings) { 55 AtomicFile file = getFile(); 56 FileOutputStream f = null; 57 try { 58 f = file.startWrite(); 59 BufferedOutputStream out = new BufferedOutputStream(f); 60 FileUtils.setPermissions(file.getBaseFile().getPath(), 61 0640, SYSTEM_UID, PACKAGE_INFO_GID); 62 StringBuilder sb = new StringBuilder(); 63 64 sb.append(USAGE_FILE_MAGIC_VERSION_1); 65 sb.append('\n'); 66 out.write(sb.toString().getBytes(StandardCharsets.US_ASCII)); 67 68 for (PackageSetting pkgSetting : pkgSettings.values()) { 69 if (pkgSetting == null || pkgSetting.getPkgState() == null 70 || pkgSetting.getPkgState().getLatestPackageUseTimeInMills() == 0L) { 71 continue; 72 } 73 sb.setLength(0); 74 sb.append(pkgSetting.getPackageName()); 75 for (long usageTimeInMillis : pkgSetting.getPkgState() 76 .getLastPackageUsageTimeInMills()) { 77 sb.append(' '); 78 sb.append(usageTimeInMillis); 79 } 80 sb.append('\n'); 81 out.write(sb.toString().getBytes(StandardCharsets.US_ASCII)); 82 } 83 out.flush(); 84 file.finishWrite(f); 85 } catch (IOException e) { 86 if (f != null) { 87 file.failWrite(f); 88 } 89 Log.e(PackageManagerService.TAG, "Failed to write package usage times", e); 90 } 91 } 92 93 @Override readInternal(Map<String, PackageSetting> pkgSettings)94 protected void readInternal(Map<String, PackageSetting> pkgSettings) { 95 AtomicFile file = getFile(); 96 BufferedInputStream in = null; 97 try { 98 in = new BufferedInputStream(file.openRead()); 99 StringBuilder sb = new StringBuilder(); 100 101 String firstLine = readLine(in, sb); 102 if (firstLine == null) { 103 // Empty file. Do nothing. 104 } else if (USAGE_FILE_MAGIC_VERSION_1.equals(firstLine)) { 105 readVersion1LP(pkgSettings, in, sb); 106 } else { 107 readVersion0LP(pkgSettings, in, sb, firstLine); 108 } 109 } catch (FileNotFoundException expected) { 110 mIsHistoricalPackageUsageAvailable = false; 111 } catch (IOException e) { 112 Log.w(PackageManagerService.TAG, "Failed to read package usage times", e); 113 } finally { 114 IoUtils.closeQuietly(in); 115 } 116 } 117 readVersion0LP(Map<String, PackageSetting> pkgSettings, InputStream in, StringBuilder sb, String firstLine)118 private void readVersion0LP(Map<String, PackageSetting> pkgSettings, InputStream in, 119 StringBuilder sb, String firstLine) 120 throws IOException { 121 // Initial version of the file had no version number and stored one 122 // package-timestamp pair per line. 123 // Note that the first line has already been read from the InputStream. 124 for (String line = firstLine; line != null; line = readLine(in, sb)) { 125 String[] tokens = line.split(" "); 126 if (tokens.length != 2) { 127 throw new IOException("Failed to parse " + line + 128 " as package-timestamp pair."); 129 } 130 131 String packageName = tokens[0]; 132 PackageSetting pkgSetting = pkgSettings.get(packageName); 133 if (pkgSetting == null) { 134 continue; 135 } 136 137 long timestamp = parseAsLong(tokens[1]); 138 for (int reason = 0; 139 reason < PackageManager.NOTIFY_PACKAGE_USE_REASONS_COUNT; 140 reason++) { 141 pkgSetting.getPkgState().setLastPackageUsageTimeInMills(reason, timestamp); 142 } 143 } 144 } 145 readVersion1LP(Map<String, PackageSetting> pkgSettings, InputStream in, StringBuilder sb)146 private void readVersion1LP(Map<String, PackageSetting> pkgSettings, InputStream in, 147 StringBuilder sb) throws IOException { 148 // Version 1 of the file started with the corresponding version 149 // number and then stored a package name and eight timestamps per line. 150 String line; 151 while ((line = readLine(in, sb)) != null) { 152 String[] tokens = line.split(" "); 153 if (tokens.length != PackageManager.NOTIFY_PACKAGE_USE_REASONS_COUNT + 1) { 154 throw new IOException("Failed to parse " + line + " as a timestamp array."); 155 } 156 157 String packageName = tokens[0]; 158 PackageSetting pkgSetting = pkgSettings.get(packageName); 159 if (pkgSetting == null) { 160 continue; 161 } 162 163 for (int reason = 0; 164 reason < PackageManager.NOTIFY_PACKAGE_USE_REASONS_COUNT; 165 reason++) { 166 pkgSetting.getPkgState().setLastPackageUsageTimeInMills(reason, 167 parseAsLong(tokens[reason + 1])); 168 } 169 } 170 } 171 parseAsLong(String token)172 private long parseAsLong(String token) throws IOException { 173 try { 174 return Long.parseLong(token); 175 } catch (NumberFormatException e) { 176 throw new IOException("Failed to parse " + token + " as a long.", e); 177 } 178 } 179 readLine(InputStream in, StringBuilder sb)180 private String readLine(InputStream in, StringBuilder sb) throws IOException { 181 return readToken(in, sb, '\n'); 182 } 183 readToken(InputStream in, StringBuilder sb, char endOfToken)184 private String readToken(InputStream in, StringBuilder sb, char endOfToken) 185 throws IOException { 186 sb.setLength(0); 187 while (true) { 188 int ch = in.read(); 189 if (ch == -1) { 190 if (sb.length() == 0) { 191 return null; 192 } 193 throw new IOException("Unexpected EOF"); 194 } 195 if (ch == endOfToken) { 196 return sb.toString(); 197 } 198 sb.append((char)ch); 199 } 200 } 201 } 202