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.content.pm.PackageParser; 24 import android.os.FileUtils; 25 import android.util.AtomicFile; 26 import android.util.Log; 27 28 import libcore.io.IoUtils; 29 30 import java.io.BufferedInputStream; 31 import java.io.BufferedOutputStream; 32 import java.io.FileNotFoundException; 33 import java.io.FileOutputStream; 34 import java.io.IOException; 35 import java.io.InputStream; 36 import java.nio.charset.StandardCharsets; 37 import java.util.Map; 38 39 class PackageUsage extends AbstractStatsBase<Map<String, PackageParser.Package>> { 40 41 private static final String USAGE_FILE_MAGIC = "PACKAGE_USAGE__VERSION_"; 42 private static final String USAGE_FILE_MAGIC_VERSION_1 = USAGE_FILE_MAGIC + "1"; 43 44 private boolean mIsHistoricalPackageUsageAvailable = true; 45 PackageUsage()46 PackageUsage() { 47 super("package-usage.list", "PackageUsage_DiskWriter", /* lock */ true); 48 } 49 isHistoricalPackageUsageAvailable()50 boolean isHistoricalPackageUsageAvailable() { 51 return mIsHistoricalPackageUsageAvailable; 52 } 53 54 @Override writeInternal(Map<String, PackageParser.Package> packages)55 protected void writeInternal(Map<String, PackageParser.Package> packages) { 56 AtomicFile file = getFile(); 57 FileOutputStream f = null; 58 try { 59 f = file.startWrite(); 60 BufferedOutputStream out = new BufferedOutputStream(f); 61 FileUtils.setPermissions(file.getBaseFile().getPath(), 62 0640, SYSTEM_UID, PACKAGE_INFO_GID); 63 StringBuilder sb = new StringBuilder(); 64 65 sb.append(USAGE_FILE_MAGIC_VERSION_1); 66 sb.append('\n'); 67 out.write(sb.toString().getBytes(StandardCharsets.US_ASCII)); 68 69 for (PackageParser.Package pkg : packages.values()) { 70 if (pkg.getLatestPackageUseTimeInMills() == 0L) { 71 continue; 72 } 73 sb.setLength(0); 74 sb.append(pkg.packageName); 75 for (long usageTimeInMillis : pkg.mLastPackageUsageTimeInMills) { 76 sb.append(' '); 77 sb.append(usageTimeInMillis); 78 } 79 sb.append('\n'); 80 out.write(sb.toString().getBytes(StandardCharsets.US_ASCII)); 81 } 82 out.flush(); 83 file.finishWrite(f); 84 } catch (IOException e) { 85 if (f != null) { 86 file.failWrite(f); 87 } 88 Log.e(PackageManagerService.TAG, "Failed to write package usage times", e); 89 } 90 } 91 92 @Override readInternal(Map<String, PackageParser.Package> packages)93 protected void readInternal(Map<String, PackageParser.Package> packages) { 94 AtomicFile file = getFile(); 95 BufferedInputStream in = null; 96 try { 97 in = new BufferedInputStream(file.openRead()); 98 StringBuffer sb = new StringBuffer(); 99 100 String firstLine = readLine(in, sb); 101 if (firstLine == null) { 102 // Empty file. Do nothing. 103 } else if (USAGE_FILE_MAGIC_VERSION_1.equals(firstLine)) { 104 readVersion1LP(packages, in, sb); 105 } else { 106 readVersion0LP(packages, in, sb, firstLine); 107 } 108 } catch (FileNotFoundException expected) { 109 mIsHistoricalPackageUsageAvailable = false; 110 } catch (IOException e) { 111 Log.w(PackageManagerService.TAG, "Failed to read package usage times", e); 112 } finally { 113 IoUtils.closeQuietly(in); 114 } 115 } 116 readVersion0LP(Map<String, PackageParser.Package> packages, InputStream in, StringBuffer sb, String firstLine)117 private void readVersion0LP(Map<String, PackageParser.Package> packages, InputStream in, 118 StringBuffer sb, String firstLine) 119 throws IOException { 120 // Initial version of the file had no version number and stored one 121 // package-timestamp pair per line. 122 // Note that the first line has already been read from the InputStream. 123 for (String line = firstLine; line != null; line = readLine(in, sb)) { 124 String[] tokens = line.split(" "); 125 if (tokens.length != 2) { 126 throw new IOException("Failed to parse " + line + 127 " as package-timestamp pair."); 128 } 129 130 String packageName = tokens[0]; 131 PackageParser.Package pkg = packages.get(packageName); 132 if (pkg == null) { 133 continue; 134 } 135 136 long timestamp = parseAsLong(tokens[1]); 137 for (int reason = 0; 138 reason < PackageManager.NOTIFY_PACKAGE_USE_REASONS_COUNT; 139 reason++) { 140 pkg.mLastPackageUsageTimeInMills[reason] = timestamp; 141 } 142 } 143 } 144 readVersion1LP(Map<String, PackageParser.Package> packages, InputStream in, StringBuffer sb)145 private void readVersion1LP(Map<String, PackageParser.Package> packages, InputStream in, 146 StringBuffer sb) throws IOException { 147 // Version 1 of the file started with the corresponding version 148 // number and then stored a package name and eight timestamps per line. 149 String line; 150 while ((line = readLine(in, sb)) != null) { 151 String[] tokens = line.split(" "); 152 if (tokens.length != PackageManager.NOTIFY_PACKAGE_USE_REASONS_COUNT + 1) { 153 throw new IOException("Failed to parse " + line + " as a timestamp array."); 154 } 155 156 String packageName = tokens[0]; 157 PackageParser.Package pkg = packages.get(packageName); 158 if (pkg == null) { 159 continue; 160 } 161 162 for (int reason = 0; 163 reason < PackageManager.NOTIFY_PACKAGE_USE_REASONS_COUNT; 164 reason++) { 165 pkg.mLastPackageUsageTimeInMills[reason] = parseAsLong(tokens[reason + 1]); 166 } 167 } 168 } 169 parseAsLong(String token)170 private long parseAsLong(String token) throws IOException { 171 try { 172 return Long.parseLong(token); 173 } catch (NumberFormatException e) { 174 throw new IOException("Failed to parse " + token + " as a long.", e); 175 } 176 } 177 readLine(InputStream in, StringBuffer sb)178 private String readLine(InputStream in, StringBuffer sb) throws IOException { 179 return readToken(in, sb, '\n'); 180 } 181 readToken(InputStream in, StringBuffer sb, char endOfToken)182 private String readToken(InputStream in, StringBuffer sb, char endOfToken) 183 throws IOException { 184 sb.setLength(0); 185 while (true) { 186 int ch = in.read(); 187 if (ch == -1) { 188 if (sb.length() == 0) { 189 return null; 190 } 191 throw new IOException("Unexpected EOF"); 192 } 193 if (ch == endOfToken) { 194 return sb.toString(); 195 } 196 sb.append((char)ch); 197 } 198 } 199 }