blob: 9a08ac3b4ecaa84c62130d3abb96b1af0f62fd1e [file] [log] [blame]
Daniel Nishi090b2d92016-12-13 10:38:42 -08001/*
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
17package com.android.server.storage;
18
19import android.app.job.JobInfo;
20import android.app.job.JobParameters;
21import android.app.job.JobScheduler;
22import android.app.job.JobService;
23import android.content.ComponentName;
Daniel Nishi88e45dc2017-01-25 11:43:55 -080024import android.content.ContentResolver;
Daniel Nishi090b2d92016-12-13 10:38:42 -080025import android.content.Context;
26import android.content.pm.PackageStats;
27import android.os.AsyncTask;
28import android.os.BatteryManager;
29import android.os.Environment;
30import android.os.Environment.UserEnvironment;
31import android.os.UserHandle;
Daniel Nishid54f3a42017-02-21 16:04:20 -080032import android.os.storage.VolumeInfo;
Daniel Nishi88e45dc2017-01-25 11:43:55 -080033import android.provider.Settings;
Daniel Nishi090b2d92016-12-13 10:38:42 -080034import android.util.Log;
35
36import com.android.internal.annotations.VisibleForTesting;
37import com.android.server.storage.FileCollector.MeasurementResult;
38
39import java.io.File;
40import java.io.IOException;
41import java.util.List;
42import 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 */
48public 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.
Daniel Nishi88e45dc2017-01-25 11:43:55 -080060 if (!isCharging(this) || !isDumpsysTaskEnabled(getContentResolver())) {
Daniel Nishi090b2d92016-12-13 10:38:42 -080061 jobFinished(params, true);
62 return false;
63 }
64
Daniel Nishid54f3a42017-02-21 16:04:20 -080065
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
Daniel Nishi090b2d92016-12-13 10:38:42 -080073 final int userId = UserHandle.myUserId();
74 UserEnvironment environment = new UserEnvironment(userId);
Daniel Nishi090b2d92016-12-13 10:38:42 -080075 LogRunnable task = new LogRunnable();
Daniel Nishi090b2d92016-12-13 10:38:42 -080076 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);
Daniel Nishi5d230dc2017-04-10 11:21:26 -070082 task.setContext(this);
Daniel Nishi090b2d92016-12-13 10:38:42 -080083 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) {
Daniel Nishi5d230dc2017-04-10 11:21:26 -0700109 BatteryManager batteryManager =
110 (BatteryManager) context.getSystemService(Context.BATTERY_SERVICE);
Daniel Nishi090b2d92016-12-13 10:38:42 -0800111 if (batteryManager != null) {
112 return batteryManager.isCharging();
113 }
114 return false;
115 }
116
117 @VisibleForTesting
Daniel Nishi88e45dc2017-01-25 11:43:55 -0800118 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
Daniel Nishi090b2d92016-12-13 10:38:42 -0800124 static class LogRunnable implements Runnable {
Daniel Nishicf76a162016-12-22 11:06:51 -0800125 private static final long TIMEOUT_MILLIS = TimeUnit.MINUTES.toMillis(10);
Daniel Nishi090b2d92016-12-13 10:38:42 -0800126
127 private JobService mJobService;
128 private JobParameters mParams;
129 private AppCollector mCollector;
130 private File mOutputFile;
Daniel Nishi090b2d92016-12-13 10:38:42 -0800131 private File mDownloadsDirectory;
Daniel Nishi5d230dc2017-04-10 11:21:26 -0700132 private Context mContext;
Daniel Nishi090b2d92016-12-13 10:38:42 -0800133 private long mSystemSize;
134
Daniel Nishi090b2d92016-12-13 10:38:42 -0800135 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
Daniel Nishi5d230dc2017-04-10 11:21:26 -0700151 public void setContext(Context context) {
152 mContext = context;
153 }
154
Daniel Nishi090b2d92016-12-13 10:38:42 -0800155 public void setJobService(JobService jobService, JobParameters params) {
156 mJobService = jobService;
157 mParams = params;
158 }
159
160 public void run() {
Daniel Nishi5d230dc2017-04-10 11:21:26 -0700161 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 }
Daniel Nishi090b2d92016-12-13 10:38:42 -0800170 FileCollector.MeasurementResult downloads =
171 FileCollector.getMeasurementResult(mDownloadsDirectory);
172
Daniel Nishicf76a162016-12-22 11:06:51 -0800173 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 {
Daniel Nishi5d230dc2017-04-10 11:21:26 -0700179 Log.w(TAG, "Timed out while fetching package stats.");
Daniel Nishicf76a162016-12-22 11:06:51 -0800180 }
Daniel Nishi090b2d92016-12-13 10:38:42 -0800181
Daniel Nishi5d230dc2017-04-10 11:21:26 -0700182 finishJob(needsReschedule);
Daniel Nishi090b2d92016-12-13 10:38:42 -0800183 }
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 }
Daniel Nishi5d230dc2017-04-10 11:21:26 -0700196
197 private void finishJob(boolean needsReschedule) {
198 if (mJobService != null) {
199 mJobService.jobFinished(mParams, needsReschedule);
200 }
201 }
Daniel Nishi090b2d92016-12-13 10:38:42 -0800202 }
203}