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