| /* |
| * Copyright (C) 2016 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); you may not |
| * use this file except in compliance with the License. You may obtain a copy of |
| * the License at |
| * |
| * http://www.apache.org/licenses/LICENSE2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| * License for the specific language governing permissions and limitations under |
| * the License. |
| */ |
| |
| package com.android.server.storage; |
| |
| import android.app.job.JobInfo; |
| import android.app.job.JobParameters; |
| import android.app.job.JobScheduler; |
| import android.app.job.JobService; |
| import android.content.ComponentName; |
| import android.content.ContentResolver; |
| import android.content.Context; |
| import android.content.pm.PackageStats; |
| import android.os.AsyncTask; |
| import android.os.BatteryManager; |
| import android.os.Environment; |
| import android.os.Environment.UserEnvironment; |
| import android.os.UserHandle; |
| import android.os.storage.VolumeInfo; |
| import android.provider.Settings; |
| import android.util.Log; |
| |
| import com.android.internal.annotations.VisibleForTesting; |
| import com.android.server.storage.FileCollector.MeasurementResult; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.util.List; |
| import java.util.concurrent.TimeUnit; |
| |
| /** |
| * DiskStatsLoggingService is a JobService which collects storage categorization information and |
| * app size information on a roughly daily cadence. |
| */ |
| public class DiskStatsLoggingService extends JobService { |
| private static final String TAG = "DiskStatsLogService"; |
| public static final String DUMPSYS_CACHE_PATH = "/data/system/diskstats_cache.json"; |
| private static final int JOB_DISKSTATS_LOGGING = 0x4449534b; // DISK |
| private static ComponentName sDiskStatsLoggingService = new ComponentName( |
| "android", |
| DiskStatsLoggingService.class.getName()); |
| |
| @Override |
| public boolean onStartJob(JobParameters params) { |
| // We need to check the preconditions again because they may not be enforced for |
| // subsequent runs. |
| if (!isCharging(this) || !isDumpsysTaskEnabled(getContentResolver())) { |
| jobFinished(params, true); |
| return false; |
| } |
| |
| |
| VolumeInfo volume = getPackageManager().getPrimaryStorageCurrentVolume(); |
| // volume is null if the primary storage is not yet mounted. |
| if (volume == null) { |
| return false; |
| } |
| AppCollector collector = new AppCollector(this, volume); |
| |
| final int userId = UserHandle.myUserId(); |
| UserEnvironment environment = new UserEnvironment(userId); |
| LogRunnable task = new LogRunnable(); |
| task.setDownloadsDirectory( |
| environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)); |
| task.setSystemSize(FileCollector.getSystemSize(this)); |
| task.setLogOutputFile(new File(DUMPSYS_CACHE_PATH)); |
| task.setAppCollector(collector); |
| task.setJobService(this, params); |
| task.setContext(this); |
| AsyncTask.execute(task); |
| return true; |
| } |
| |
| @Override |
| public boolean onStopJob(JobParameters params) { |
| // TODO: Try to stop being handled. |
| return false; |
| } |
| |
| /** |
| * Schedules a DiskStats collection task. This task only runs on device idle while charging |
| * once every 24 hours. |
| * @param context Context to use to get a job scheduler. |
| */ |
| public static void schedule(Context context) { |
| JobScheduler js = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE); |
| |
| js.schedule(new JobInfo.Builder(JOB_DISKSTATS_LOGGING, sDiskStatsLoggingService) |
| .setRequiresDeviceIdle(true) |
| .setRequiresCharging(true) |
| .setPeriodic(TimeUnit.DAYS.toMillis(1)) |
| .build()); |
| } |
| |
| private static boolean isCharging(Context context) { |
| BatteryManager batteryManager = |
| (BatteryManager) context.getSystemService(Context.BATTERY_SERVICE); |
| if (batteryManager != null) { |
| return batteryManager.isCharging(); |
| } |
| return false; |
| } |
| |
| @VisibleForTesting |
| static boolean isDumpsysTaskEnabled(ContentResolver resolver) { |
| // The default is to treat the task as enabled. |
| return Settings.Global.getInt(resolver, Settings.Global.ENABLE_DISKSTATS_LOGGING, 1) != 0; |
| } |
| |
| @VisibleForTesting |
| static class LogRunnable implements Runnable { |
| private static final long TIMEOUT_MILLIS = TimeUnit.MINUTES.toMillis(10); |
| |
| private JobService mJobService; |
| private JobParameters mParams; |
| private AppCollector mCollector; |
| private File mOutputFile; |
| private File mDownloadsDirectory; |
| private Context mContext; |
| private long mSystemSize; |
| |
| public void setDownloadsDirectory(File file) { |
| mDownloadsDirectory = file; |
| } |
| |
| public void setAppCollector(AppCollector collector) { |
| mCollector = collector; |
| } |
| |
| public void setLogOutputFile(File file) { |
| mOutputFile = file; |
| } |
| |
| public void setSystemSize(long size) { |
| mSystemSize = size; |
| } |
| |
| public void setContext(Context context) { |
| mContext = context; |
| } |
| |
| public void setJobService(JobService jobService, JobParameters params) { |
| mJobService = jobService; |
| mParams = params; |
| } |
| |
| public void run() { |
| FileCollector.MeasurementResult mainCategories; |
| try { |
| mainCategories = FileCollector.getMeasurementResult(mContext); |
| } catch (IllegalStateException e) { |
| // This can occur if installd has an issue. |
| Log.e(TAG, "Error while measuring storage", e); |
| finishJob(true); |
| return; |
| } |
| FileCollector.MeasurementResult downloads = |
| FileCollector.getMeasurementResult(mDownloadsDirectory); |
| |
| boolean needsReschedule = true; |
| List<PackageStats> stats = mCollector.getPackageStats(TIMEOUT_MILLIS); |
| if (stats != null) { |
| needsReschedule = false; |
| logToFile(mainCategories, downloads, stats, mSystemSize); |
| } else { |
| Log.w(TAG, "Timed out while fetching package stats."); |
| } |
| |
| finishJob(needsReschedule); |
| } |
| |
| private void logToFile(MeasurementResult mainCategories, MeasurementResult downloads, |
| List<PackageStats> stats, long systemSize) { |
| DiskStatsFileLogger logger = new DiskStatsFileLogger(mainCategories, downloads, stats, |
| systemSize); |
| try { |
| mOutputFile.createNewFile(); |
| logger.dumpToFile(mOutputFile); |
| } catch (IOException e) { |
| Log.e(TAG, "Exception while writing opportunistic disk file cache.", e); |
| } |
| } |
| |
| private void finishJob(boolean needsReschedule) { |
| if (mJobService != null) { |
| mJobService.jobFinished(mParams, needsReschedule); |
| } |
| } |
| } |
| } |