blob: 7c43162ec692e6418e56f69e1766f0b19e1e8248 [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 Nishi88e45dc2017-01-25 11:43:55 -080032import android.provider.Settings;
Daniel Nishi090b2d92016-12-13 10:38:42 -080033import android.util.Log;
34
35import com.android.internal.annotations.VisibleForTesting;
36import com.android.server.storage.FileCollector.MeasurementResult;
37
38import java.io.File;
39import java.io.IOException;
40import java.util.List;
41import java.util.concurrent.TimeUnit;
42
43/**
44 * DiskStatsLoggingService is a JobService which collects storage categorization information and
45 * app size information on a roughly daily cadence.
46 */
47public class DiskStatsLoggingService extends JobService {
48 private static final String TAG = "DiskStatsLogService";
49 public static final String DUMPSYS_CACHE_PATH = "/data/system/diskstats_cache.json";
50 private static final int JOB_DISKSTATS_LOGGING = 0x4449534b; // DISK
51 private static ComponentName sDiskStatsLoggingService = new ComponentName(
52 "android",
53 DiskStatsLoggingService.class.getName());
54
55 @Override
56 public boolean onStartJob(JobParameters params) {
57 // We need to check the preconditions again because they may not be enforced for
58 // subsequent runs.
Daniel Nishi88e45dc2017-01-25 11:43:55 -080059 if (!isCharging(this) || !isDumpsysTaskEnabled(getContentResolver())) {
Daniel Nishi090b2d92016-12-13 10:38:42 -080060 jobFinished(params, true);
61 return false;
62 }
63
64 final int userId = UserHandle.myUserId();
65 UserEnvironment environment = new UserEnvironment(userId);
66 AppCollector collector = new AppCollector(this,
67 getPackageManager().getPrimaryStorageCurrentVolume());
68 LogRunnable task = new LogRunnable();
69 task.setRootDirectory(environment.getExternalStorageDirectory());
70 task.setDownloadsDirectory(
71 environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS));
72 task.setSystemSize(FileCollector.getSystemSize(this));
73 task.setLogOutputFile(new File(DUMPSYS_CACHE_PATH));
74 task.setAppCollector(collector);
75 task.setJobService(this, params);
76 AsyncTask.execute(task);
77 return true;
78 }
79
80 @Override
81 public boolean onStopJob(JobParameters params) {
82 // TODO: Try to stop being handled.
83 return false;
84 }
85
86 /**
87 * Schedules a DiskStats collection task. This task only runs on device idle while charging
88 * once every 24 hours.
89 * @param context Context to use to get a job scheduler.
90 */
91 public static void schedule(Context context) {
92 JobScheduler js = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
93
94 js.schedule(new JobInfo.Builder(JOB_DISKSTATS_LOGGING, sDiskStatsLoggingService)
95 .setRequiresDeviceIdle(true)
96 .setRequiresCharging(true)
97 .setPeriodic(TimeUnit.DAYS.toMillis(1))
98 .build());
99 }
100
101 private static boolean isCharging(Context context) {
102 BatteryManager batteryManager = context.getSystemService(BatteryManager.class);
103 if (batteryManager != null) {
104 return batteryManager.isCharging();
105 }
106 return false;
107 }
108
109 @VisibleForTesting
Daniel Nishi88e45dc2017-01-25 11:43:55 -0800110 static boolean isDumpsysTaskEnabled(ContentResolver resolver) {
111 // The default is to treat the task as enabled.
112 return Settings.Global.getInt(resolver, Settings.Global.ENABLE_DISKSTATS_LOGGING, 1) != 0;
113 }
114
115 @VisibleForTesting
Daniel Nishi090b2d92016-12-13 10:38:42 -0800116 static class LogRunnable implements Runnable {
Daniel Nishicf76a162016-12-22 11:06:51 -0800117 private static final long TIMEOUT_MILLIS = TimeUnit.MINUTES.toMillis(10);
Daniel Nishi090b2d92016-12-13 10:38:42 -0800118
119 private JobService mJobService;
120 private JobParameters mParams;
121 private AppCollector mCollector;
122 private File mOutputFile;
123 private File mRootDirectory;
124 private File mDownloadsDirectory;
125 private long mSystemSize;
126
127 public void setRootDirectory(File file) {
128 mRootDirectory = file;
129 }
130
131 public void setDownloadsDirectory(File file) {
132 mDownloadsDirectory = file;
133 }
134
135 public void setAppCollector(AppCollector collector) {
136 mCollector = collector;
137 }
138
139 public void setLogOutputFile(File file) {
140 mOutputFile = file;
141 }
142
143 public void setSystemSize(long size) {
144 mSystemSize = size;
145 }
146
147 public void setJobService(JobService jobService, JobParameters params) {
148 mJobService = jobService;
149 mParams = params;
150 }
151
152 public void run() {
153 FileCollector.MeasurementResult mainCategories =
154 FileCollector.getMeasurementResult(mRootDirectory);
155 FileCollector.MeasurementResult downloads =
156 FileCollector.getMeasurementResult(mDownloadsDirectory);
157
Daniel Nishicf76a162016-12-22 11:06:51 -0800158 boolean needsReschedule = true;
159 List<PackageStats> stats = mCollector.getPackageStats(TIMEOUT_MILLIS);
160 if (stats != null) {
161 needsReschedule = false;
162 logToFile(mainCategories, downloads, stats, mSystemSize);
163 } else {
164 Log.w("TAG", "Timed out while fetching package stats.");
165 }
Daniel Nishi090b2d92016-12-13 10:38:42 -0800166
167 if (mJobService != null) {
Daniel Nishicf76a162016-12-22 11:06:51 -0800168 mJobService.jobFinished(mParams, needsReschedule);
Daniel Nishi090b2d92016-12-13 10:38:42 -0800169 }
170 }
171
172 private void logToFile(MeasurementResult mainCategories, MeasurementResult downloads,
173 List<PackageStats> stats, long systemSize) {
174 DiskStatsFileLogger logger = new DiskStatsFileLogger(mainCategories, downloads, stats,
175 systemSize);
176 try {
177 mOutputFile.createNewFile();
178 logger.dumpToFile(mOutputFile);
179 } catch (IOException e) {
180 Log.e(TAG, "Exception while writing opportunistic disk file cache.", e);
181 }
182 }
183 }
184}