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