• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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