huangcm
2025-05-08 76a1e955045b9ab0f6ff3d883403d08e1fcd2752
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
/*
 * 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);
            }
        }
    }
}