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