blob: ac1f739cd9f8228bcea9b3cb804ed6668466582c [file] [log] [blame]
Andreas Gampe37e5fdc2016-07-12 22:42:41 -07001/*
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
17package com.android.server.pm;
18
19import static android.os.Process.PACKAGE_INFO_GID;
20import static android.os.Process.SYSTEM_UID;
21
22import android.content.pm.PackageManager;
23import android.content.pm.PackageParser;
24import android.os.FileUtils;
25import android.util.AtomicFile;
26import android.util.Log;
27
28import libcore.io.IoUtils;
29
30import java.io.BufferedInputStream;
31import java.io.BufferedOutputStream;
32import java.io.FileNotFoundException;
33import java.io.FileOutputStream;
34import java.io.IOException;
35import java.io.InputStream;
36import java.nio.charset.StandardCharsets;
37import java.util.Map;
38
39class 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
46 PackageUsage() {
47 super("package-usage.list", "PackageUsage_DiskWriter", /* lock */ true);
48 }
49
50 boolean isHistoricalPackageUsageAvailable() {
51 return mIsHistoricalPackageUsageAvailable;
52 }
53
54 @Override
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
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
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
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
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
178 private String readLine(InputStream in, StringBuffer sb) throws IOException {
179 return readToken(in, sb, '\n');
180 }
181
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}