Home | History | Annotate | Download | only in pm
      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.content.pm.PackageParser;
     24 import android.os.FileUtils;
     25 import android.util.AtomicFile;
     26 import android.util.Log;
     27 
     28 import libcore.io.IoUtils;
     29 
     30 import java.io.BufferedInputStream;
     31 import java.io.BufferedOutputStream;
     32 import java.io.FileNotFoundException;
     33 import java.io.FileOutputStream;
     34 import java.io.IOException;
     35 import java.io.InputStream;
     36 import java.nio.charset.StandardCharsets;
     37 import java.util.Map;
     38 
     39 class 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 }