TunerStorageCleanUpService.java revision d41f0075a7d2ea826204e81fcec57d0aa57171a9
1/*
2 * Copyright (C) 2016 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.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,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License
15 */
16
17package com.android.tv.tuner.tvinput;
18
19import android.app.job.JobParameters;
20import android.app.job.JobService;
21import android.content.ContentResolver;
22import android.content.Context;
23import android.database.Cursor;
24import android.media.tv.TvContract;
25import android.net.Uri;
26import android.os.AsyncTask;
27
28import com.android.tv.TvApplication;
29import com.android.tv.dvr.DvrStorageStatusManager;
30import com.android.tv.util.Utils;
31
32import java.io.File;
33import java.io.IOException;
34import java.util.HashSet;
35import java.util.Set;
36import java.util.concurrent.TimeUnit;
37
38/**
39 * Creates {@link JobService} to clean up recorded program files which are not referenced
40 * from database.
41 */
42public class TunerStorageCleanUpService extends JobService {
43    private CleanUpStorageTask mTask;
44
45    @Override
46    public void onCreate() {
47        TvApplication.setCurrentRunningProcess(this, false);
48        super.onCreate();
49        mTask = new CleanUpStorageTask(this, this);
50    }
51
52    @Override
53    public boolean onStartJob(JobParameters params) {
54        mTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, params);
55        return true;
56    }
57
58    @Override
59    public boolean onStopJob(JobParameters params) {
60        return false;
61    }
62
63    /**
64     * Cleans up recorded program files which are not referenced from database.
65     * Cleaning up will be done periodically.
66     */
67    public static class CleanUpStorageTask extends AsyncTask<JobParameters, Void, JobParameters[]> {
68        private final static String[] mProjection = {
69                TvContract.RecordedPrograms.COLUMN_PACKAGE_NAME,
70                TvContract.RecordedPrograms.COLUMN_RECORDING_DATA_URI
71        };
72        private final static long ELAPSED_MILLIS_TO_DELETE = TimeUnit.DAYS.toMillis(1);
73
74        private final Context mContext;
75        private final DvrStorageStatusManager mDvrStorageStatusManager;
76        private final JobService mJobService;
77        private final ContentResolver mContentResolver;
78
79        /**
80         * Creates a recurring storage cleaning task.
81         *
82         * @param context {@link Context}
83         * @param jobService {@link JobService}
84         */
85        public CleanUpStorageTask(Context context, JobService jobService) {
86            mContext = context;
87            mDvrStorageStatusManager =
88                    TvApplication.getSingletons(mContext).getDvrStorageStatusManager();
89            mJobService = jobService;
90            mContentResolver = mContext.getContentResolver();
91        }
92
93        private Set<String> getRecordedProgramsDirs() {
94            try (Cursor c = mContentResolver.query(
95                    TvContract.RecordedPrograms.CONTENT_URI, mProjection, null, null, null)) {
96                if (c == null) {
97                    return null;
98                }
99                Set<String> recordedProgramDirs = new HashSet<>();
100                while (c.moveToNext()) {
101                    String packageName = c.getString(0);
102                    String dataUriString = c.getString(1);
103                    if (dataUriString == null) {
104                        continue;
105                    }
106                    Uri dataUri = Uri.parse(dataUriString);
107                    if (!Utils.isInBundledPackageSet(packageName)
108                            || dataUri == null || dataUri.getPath() == null
109                            || !ContentResolver.SCHEME_FILE.equals(dataUri.getScheme())) {
110                        continue;
111                    }
112                    File recordedProgramDir = new File(dataUri.getPath());
113                    try {
114                        recordedProgramDirs.add(recordedProgramDir.getCanonicalPath());
115                    } catch (IOException | SecurityException e) {
116                    }
117                }
118                return recordedProgramDirs;
119            }
120        }
121
122        @Override
123        protected JobParameters[] doInBackground(JobParameters... params) {
124            if (mDvrStorageStatusManager.getDvrStorageStatus()
125                    == DvrStorageStatusManager.STORAGE_STATUS_MISSING) {
126                return params;
127            }
128            File dvrRecordingDir = mDvrStorageStatusManager.getRecordingRootDataDirectory();
129            if (dvrRecordingDir == null || !dvrRecordingDir.isDirectory()) {
130                return params;
131            }
132            Set<String> recordedProgramDirs = getRecordedProgramsDirs();
133            if (recordedProgramDirs == null) {
134                return params;
135            }
136            File[] files = dvrRecordingDir.listFiles();
137            if (files == null || files.length == 0) {
138                return params;
139            }
140            for (File recordingDir : files) {
141                try {
142                    if (!recordedProgramDirs.contains(recordingDir.getCanonicalPath())) {
143                        long lastModified = recordingDir.lastModified();
144                        long now = System.currentTimeMillis();
145                        if (lastModified != 0
146                                && lastModified < now - ELAPSED_MILLIS_TO_DELETE) {
147                            // To prevent current recordings from being deleted,
148                            // deletes recordings which was not modified for long enough time.
149                            Utils.deleteDirOrFile(recordingDir);
150                        }
151                    }
152                } catch (IOException | SecurityException e) {
153                    // would not happen
154                }
155            }
156            return params;
157        }
158
159        @Override
160        protected void onPostExecute(JobParameters[] params) {
161            for (JobParameters param : params) {
162                mJobService.jobFinished(param, false);
163            }
164        }
165    }
166}
167