Home | History | Annotate | Download | only in storage
      1 /*
      2  * Copyright (C) 2016 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
      5  * use this file except in compliance with the License. You may obtain a copy of
      6  * the License at
      7  *
      8  * http://www.apache.org/licenses/LICENSE2.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, WITHOUT
     12  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
     13  * License for the specific language governing permissions and limitations under
     14  * the License.
     15  */
     16 
     17 package com.android.server.storage;
     18 
     19 import android.app.job.JobInfo;
     20 import android.app.job.JobParameters;
     21 import android.app.job.JobScheduler;
     22 import android.app.job.JobService;
     23 import android.content.ComponentName;
     24 import android.content.ContentResolver;
     25 import android.content.Context;
     26 import android.content.pm.PackageStats;
     27 import android.os.AsyncTask;
     28 import android.os.BatteryManager;
     29 import android.os.Environment;
     30 import android.os.Environment.UserEnvironment;
     31 import android.os.UserHandle;
     32 import android.os.storage.VolumeInfo;
     33 import android.provider.Settings;
     34 import android.util.Log;
     35 
     36 import com.android.internal.annotations.VisibleForTesting;
     37 import com.android.server.storage.FileCollector.MeasurementResult;
     38 
     39 import java.io.File;
     40 import java.io.IOException;
     41 import java.util.List;
     42 import java.util.concurrent.TimeUnit;
     43 
     44 /**
     45  * DiskStatsLoggingService is a JobService which collects storage categorization information and
     46  * app size information on a roughly daily cadence.
     47  */
     48 public class DiskStatsLoggingService extends JobService {
     49     private static final String TAG = "DiskStatsLogService";
     50     public static final String DUMPSYS_CACHE_PATH = "/data/system/diskstats_cache.json";
     51     private static final int JOB_DISKSTATS_LOGGING = 0x4449534b; // DISK
     52     private static ComponentName sDiskStatsLoggingService = new ComponentName(
     53             "android",
     54             DiskStatsLoggingService.class.getName());
     55 
     56     @Override
     57     public boolean onStartJob(JobParameters params) {
     58         // We need to check the preconditions again because they may not be enforced for
     59         // subsequent runs.
     60         if (!isCharging(this) || !isDumpsysTaskEnabled(getContentResolver())) {
     61             jobFinished(params, true);
     62             return false;
     63         }
     64 
     65 
     66         VolumeInfo volume = getPackageManager().getPrimaryStorageCurrentVolume();
     67         // volume is null if the primary storage is not yet mounted.
     68         if (volume == null) {
     69             return false;
     70         }
     71         AppCollector collector = new AppCollector(this, volume);
     72 
     73         final int userId = UserHandle.myUserId();
     74         UserEnvironment environment = new UserEnvironment(userId);
     75         LogRunnable task = new LogRunnable();
     76         task.setDownloadsDirectory(
     77                 environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS));
     78         task.setSystemSize(FileCollector.getSystemSize(this));
     79         task.setLogOutputFile(new File(DUMPSYS_CACHE_PATH));
     80         task.setAppCollector(collector);
     81         task.setJobService(this, params);
     82         task.setContext(this);
     83         AsyncTask.execute(task);
     84         return true;
     85     }
     86 
     87     @Override
     88     public boolean onStopJob(JobParameters params) {
     89         // TODO: Try to stop being handled.
     90         return false;
     91     }
     92 
     93     /**
     94      * Schedules a DiskStats collection task. This task only runs on device idle while charging
     95      * once every 24 hours.
     96      * @param context Context to use to get a job scheduler.
     97      */
     98     public static void schedule(Context context) {
     99         JobScheduler js = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
    100 
    101         js.schedule(new JobInfo.Builder(JOB_DISKSTATS_LOGGING, sDiskStatsLoggingService)
    102                 .setRequiresDeviceIdle(true)
    103                 .setRequiresCharging(true)
    104                 .setPeriodic(TimeUnit.DAYS.toMillis(1))
    105                 .build());
    106     }
    107 
    108     private static boolean isCharging(Context context) {
    109         BatteryManager batteryManager =
    110                 (BatteryManager) context.getSystemService(Context.BATTERY_SERVICE);
    111         if (batteryManager != null) {
    112             return batteryManager.isCharging();
    113         }
    114         return false;
    115     }
    116 
    117     @VisibleForTesting
    118     static boolean isDumpsysTaskEnabled(ContentResolver resolver) {
    119         // The default is to treat the task as enabled.
    120         return Settings.Global.getInt(resolver, Settings.Global.ENABLE_DISKSTATS_LOGGING, 1) != 0;
    121     }
    122 
    123     @VisibleForTesting
    124     static class LogRunnable implements Runnable {
    125         private static final long TIMEOUT_MILLIS = TimeUnit.MINUTES.toMillis(10);
    126 
    127         private JobService mJobService;
    128         private JobParameters mParams;
    129         private AppCollector mCollector;
    130         private File mOutputFile;
    131         private File mDownloadsDirectory;
    132         private Context mContext;
    133         private long mSystemSize;
    134 
    135         public void setDownloadsDirectory(File file) {
    136             mDownloadsDirectory = file;
    137         }
    138 
    139         public void setAppCollector(AppCollector collector) {
    140             mCollector = collector;
    141         }
    142 
    143         public void setLogOutputFile(File file) {
    144             mOutputFile = file;
    145         }
    146 
    147         public void setSystemSize(long size) {
    148             mSystemSize = size;
    149         }
    150 
    151         public void setContext(Context context) {
    152             mContext = context;
    153         }
    154 
    155         public void setJobService(JobService jobService, JobParameters params) {
    156             mJobService = jobService;
    157             mParams = params;
    158         }
    159 
    160         public void run() {
    161             FileCollector.MeasurementResult mainCategories;
    162             try {
    163                 mainCategories = FileCollector.getMeasurementResult(mContext);
    164             } catch (IllegalStateException e) {
    165                 // This can occur if installd has an issue.
    166                 Log.e(TAG, "Error while measuring storage", e);
    167                 finishJob(true);
    168                 return;
    169             }
    170             FileCollector.MeasurementResult downloads =
    171                     FileCollector.getMeasurementResult(mDownloadsDirectory);
    172 
    173             boolean needsReschedule = true;
    174             List<PackageStats> stats = mCollector.getPackageStats(TIMEOUT_MILLIS);
    175             if (stats != null) {
    176                 needsReschedule = false;
    177                 logToFile(mainCategories, downloads, stats, mSystemSize);
    178             } else {
    179                 Log.w(TAG, "Timed out while fetching package stats.");
    180             }
    181 
    182             finishJob(needsReschedule);
    183         }
    184 
    185         private void logToFile(MeasurementResult mainCategories, MeasurementResult downloads,
    186                 List<PackageStats> stats, long systemSize) {
    187             DiskStatsFileLogger logger = new DiskStatsFileLogger(mainCategories, downloads, stats,
    188                     systemSize);
    189             try {
    190                 mOutputFile.createNewFile();
    191                 logger.dumpToFile(mOutputFile);
    192             } catch (IOException e) {
    193                 Log.e(TAG, "Exception while writing opportunistic disk file cache.", e);
    194             }
    195         }
    196 
    197         private void finishJob(boolean needsReschedule) {
    198             if (mJobService != null) {
    199                 mJobService.jobFinished(mParams, needsReschedule);
    200             }
    201         }
    202     }
    203 }