137e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe/* 237e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe * Copyright (C) 2016 The Android Open Source Project 337e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe * 437e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe * Licensed under the Apache License, Version 2.0 (the "License"); 537e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe * you may not use this file except in compliance with the License. 637e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe * You may obtain a copy of the License at 737e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe * 837e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe * http://www.apache.org/licenses/LICENSE-2.0 937e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe * 1037e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe * Unless required by applicable law or agreed to in writing, software 1137e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe * distributed under the License is distributed on an "AS IS" BASIS, 1237e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1337e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe * See the License for the specific language governing permissions and 1437e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe * limitations under the License. 1537e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe */ 1637e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe 1737e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampepackage com.android.server.pm; 1837e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe 1937e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampeimport static android.os.Process.PACKAGE_INFO_GID; 2037e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampeimport static android.os.Process.SYSTEM_UID; 2137e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe 2237e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampeimport android.content.pm.PackageManager; 2337e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampeimport android.content.pm.PackageParser; 2437e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampeimport android.os.FileUtils; 2537e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampeimport android.util.AtomicFile; 2637e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampeimport android.util.Log; 2737e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe 2837e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampeimport libcore.io.IoUtils; 2937e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe 3037e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampeimport java.io.BufferedInputStream; 3137e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampeimport java.io.BufferedOutputStream; 3237e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampeimport java.io.FileNotFoundException; 3337e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampeimport java.io.FileOutputStream; 3437e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampeimport java.io.IOException; 3537e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampeimport java.io.InputStream; 3637e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampeimport java.nio.charset.StandardCharsets; 3737e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampeimport java.util.Map; 3837e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe 3937e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampeclass PackageUsage extends AbstractStatsBase<Map<String, PackageParser.Package>> { 4037e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe 4137e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe private static final String USAGE_FILE_MAGIC = "PACKAGE_USAGE__VERSION_"; 4237e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe private static final String USAGE_FILE_MAGIC_VERSION_1 = USAGE_FILE_MAGIC + "1"; 4337e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe 4437e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe private boolean mIsHistoricalPackageUsageAvailable = true; 4537e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe 4637e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe PackageUsage() { 4737e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe super("package-usage.list", "PackageUsage_DiskWriter", /* lock */ true); 4837e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe } 4937e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe 5037e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe boolean isHistoricalPackageUsageAvailable() { 5137e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe return mIsHistoricalPackageUsageAvailable; 5237e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe } 5337e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe 5437e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe @Override 5537e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe protected void writeInternal(Map<String, PackageParser.Package> packages) { 5637e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe AtomicFile file = getFile(); 5737e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe FileOutputStream f = null; 5837e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe try { 5937e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe f = file.startWrite(); 6037e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe BufferedOutputStream out = new BufferedOutputStream(f); 6137e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe FileUtils.setPermissions(file.getBaseFile().getPath(), 6237e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe 0640, SYSTEM_UID, PACKAGE_INFO_GID); 6337e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe StringBuilder sb = new StringBuilder(); 6437e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe 6537e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe sb.append(USAGE_FILE_MAGIC_VERSION_1); 6637e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe sb.append('\n'); 6737e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe out.write(sb.toString().getBytes(StandardCharsets.US_ASCII)); 6837e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe 6937e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe for (PackageParser.Package pkg : packages.values()) { 7037e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe if (pkg.getLatestPackageUseTimeInMills() == 0L) { 7137e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe continue; 7237e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe } 7337e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe sb.setLength(0); 7437e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe sb.append(pkg.packageName); 7537e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe for (long usageTimeInMillis : pkg.mLastPackageUsageTimeInMills) { 7637e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe sb.append(' '); 7737e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe sb.append(usageTimeInMillis); 7837e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe } 7937e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe sb.append('\n'); 8037e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe out.write(sb.toString().getBytes(StandardCharsets.US_ASCII)); 8137e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe } 8237e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe out.flush(); 8337e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe file.finishWrite(f); 8437e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe } catch (IOException e) { 8537e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe if (f != null) { 8637e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe file.failWrite(f); 8737e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe } 8837e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe Log.e(PackageManagerService.TAG, "Failed to write package usage times", e); 8937e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe } 9037e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe } 9137e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe 9237e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe @Override 9337e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe protected void readInternal(Map<String, PackageParser.Package> packages) { 9437e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe AtomicFile file = getFile(); 9537e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe BufferedInputStream in = null; 9637e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe try { 9737e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe in = new BufferedInputStream(file.openRead()); 9837e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe StringBuffer sb = new StringBuffer(); 9937e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe 10037e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe String firstLine = readLine(in, sb); 10137e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe if (firstLine == null) { 10237e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe // Empty file. Do nothing. 10337e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe } else if (USAGE_FILE_MAGIC_VERSION_1.equals(firstLine)) { 10437e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe readVersion1LP(packages, in, sb); 10537e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe } else { 10637e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe readVersion0LP(packages, in, sb, firstLine); 10737e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe } 10837e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe } catch (FileNotFoundException expected) { 10937e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe mIsHistoricalPackageUsageAvailable = false; 11037e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe } catch (IOException e) { 11137e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe Log.w(PackageManagerService.TAG, "Failed to read package usage times", e); 11237e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe } finally { 11337e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe IoUtils.closeQuietly(in); 11437e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe } 11537e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe } 11637e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe 11737e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe private void readVersion0LP(Map<String, PackageParser.Package> packages, InputStream in, 11837e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe StringBuffer sb, String firstLine) 11937e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe throws IOException { 12037e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe // Initial version of the file had no version number and stored one 12137e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe // package-timestamp pair per line. 12237e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe // Note that the first line has already been read from the InputStream. 12337e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe for (String line = firstLine; line != null; line = readLine(in, sb)) { 12437e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe String[] tokens = line.split(" "); 12537e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe if (tokens.length != 2) { 12637e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe throw new IOException("Failed to parse " + line + 12737e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe " as package-timestamp pair."); 12837e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe } 12937e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe 13037e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe String packageName = tokens[0]; 13137e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe PackageParser.Package pkg = packages.get(packageName); 13237e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe if (pkg == null) { 13337e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe continue; 13437e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe } 13537e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe 13637e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe long timestamp = parseAsLong(tokens[1]); 13737e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe for (int reason = 0; 13837e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe reason < PackageManager.NOTIFY_PACKAGE_USE_REASONS_COUNT; 13937e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe reason++) { 14037e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe pkg.mLastPackageUsageTimeInMills[reason] = timestamp; 14137e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe } 14237e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe } 14337e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe } 14437e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe 14537e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe private void readVersion1LP(Map<String, PackageParser.Package> packages, InputStream in, 14637e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe StringBuffer sb) throws IOException { 14737e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe // Version 1 of the file started with the corresponding version 14837e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe // number and then stored a package name and eight timestamps per line. 14937e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe String line; 15037e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe while ((line = readLine(in, sb)) != null) { 15137e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe String[] tokens = line.split(" "); 15237e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe if (tokens.length != PackageManager.NOTIFY_PACKAGE_USE_REASONS_COUNT + 1) { 15337e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe throw new IOException("Failed to parse " + line + " as a timestamp array."); 15437e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe } 15537e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe 15637e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe String packageName = tokens[0]; 15737e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe PackageParser.Package pkg = packages.get(packageName); 15837e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe if (pkg == null) { 15937e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe continue; 16037e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe } 16137e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe 16237e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe for (int reason = 0; 16337e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe reason < PackageManager.NOTIFY_PACKAGE_USE_REASONS_COUNT; 16437e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe reason++) { 16537e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe pkg.mLastPackageUsageTimeInMills[reason] = parseAsLong(tokens[reason + 1]); 16637e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe } 16737e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe } 16837e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe } 16937e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe 17037e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe private long parseAsLong(String token) throws IOException { 17137e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe try { 17237e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe return Long.parseLong(token); 17337e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe } catch (NumberFormatException e) { 17437e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe throw new IOException("Failed to parse " + token + " as a long.", e); 17537e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe } 17637e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe } 17737e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe 17837e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe private String readLine(InputStream in, StringBuffer sb) throws IOException { 17937e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe return readToken(in, sb, '\n'); 18037e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe } 18137e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe 18237e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe private String readToken(InputStream in, StringBuffer sb, char endOfToken) 18337e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe throws IOException { 18437e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe sb.setLength(0); 18537e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe while (true) { 18637e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe int ch = in.read(); 18737e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe if (ch == -1) { 18837e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe if (sb.length() == 0) { 18937e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe return null; 19037e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe } 19137e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe throw new IOException("Unexpected EOF"); 19237e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe } 19337e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe if (ch == endOfToken) { 19437e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe return sb.toString(); 19537e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe } 19637e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe sb.append((char)ch); 19737e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe } 19837e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe } 19937e5fdc6b4963f3533caecdd92b129f79da69dd8Andreas Gampe}