1d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko/*
2944779887775bd950cf1abf348d2df461593f6abLive Channels Team * Copyright (C) 2017 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
14944779887775bd950cf1abf348d2df461593f6abLive Channels Team * limitations under the License.
15d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko */
16d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalkopackage com.android.tv.dvr;
17d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko
18d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalkoimport android.content.ContentProviderOperation;
19d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalkoimport android.content.ContentResolver;
20d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalkoimport android.content.Context;
21d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalkoimport android.content.OperationApplicationException;
22d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalkoimport android.database.Cursor;
23633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalkoimport android.media.tv.TvInputInfo;
24d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalkoimport android.net.Uri;
25d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalkoimport android.os.AsyncTask;
26d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalkoimport android.os.RemoteException;
27944779887775bd950cf1abf348d2df461593f6abLive Channels Teamimport android.support.media.tv.TvContractCompat;
28d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalkoimport android.util.Log;
29944779887775bd950cf1abf348d2df461593f6abLive Channels Teamimport com.android.tv.TvSingletons;
30944779887775bd950cf1abf348d2df461593f6abLive Channels Teamimport com.android.tv.common.recording.RecordingStorageStatusManager;
31944779887775bd950cf1abf348d2df461593f6abLive Channels Teamimport com.android.tv.common.util.CommonUtils;
32633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalkoimport com.android.tv.util.TvInputManagerHelper;
33d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalkoimport java.io.File;
34d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalkoimport java.util.ArrayList;
35d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalkoimport java.util.List;
36d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko
37944779887775bd950cf1abf348d2df461593f6abLive Channels Team/** A class for extending TV app-specific function to {@link RecordingStorageStatusManager}. */
38944779887775bd950cf1abf348d2df461593f6abLive Channels Teampublic class DvrStorageStatusManager extends RecordingStorageStatusManager {
39d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko    private static final String TAG = "DvrStorageStatusManager";
4038fef3bf253578f518d1bc727da4afb263194398Nick Chalko
41944779887775bd950cf1abf348d2df461593f6abLive Channels Team    private final Context mContext;
42944779887775bd950cf1abf348d2df461593f6abLive Channels Team    private CleanUpDbTask mCleanUpDbTask;
43d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko
44d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko    private static final String[] PROJECTION = {
45944779887775bd950cf1abf348d2df461593f6abLive Channels Team        TvContractCompat.RecordedPrograms._ID,
46944779887775bd950cf1abf348d2df461593f6abLive Channels Team        TvContractCompat.RecordedPrograms.COLUMN_PACKAGE_NAME,
47944779887775bd950cf1abf348d2df461593f6abLive Channels Team        TvContractCompat.RecordedPrograms.COLUMN_RECORDING_DATA_URI
48d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko    };
4995961816a768da387f0b5523cf4363ace2044089Nick Chalko    private static final int BATCH_OPERATION_COUNT = 100;
5095961816a768da387f0b5523cf4363ace2044089Nick Chalko
51944779887775bd950cf1abf348d2df461593f6abLive Channels Team    public DvrStorageStatusManager(Context context) {
52944779887775bd950cf1abf348d2df461593f6abLive Channels Team        super(context);
53d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko        mContext = context;
54d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko    }
55d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko
56944779887775bd950cf1abf348d2df461593f6abLive Channels Team    @Override
57944779887775bd950cf1abf348d2df461593f6abLive Channels Team    protected void cleanUpDbIfNeeded() {
58944779887775bd950cf1abf348d2df461593f6abLive Channels Team        if (mCleanUpDbTask != null) {
59944779887775bd950cf1abf348d2df461593f6abLive Channels Team            mCleanUpDbTask.cancel(true);
60d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko        }
61944779887775bd950cf1abf348d2df461593f6abLive Channels Team        mCleanUpDbTask = new CleanUpDbTask();
62944779887775bd950cf1abf348d2df461593f6abLive Channels Team        mCleanUpDbTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
63d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko    }
64d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko
65633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko    private class CleanUpDbTask extends AsyncTask<Void, Void, Boolean> {
66d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko        private final ContentResolver mContentResolver;
67d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko
68d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko        private CleanUpDbTask() {
69d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko            mContentResolver = mContext.getContentResolver();
70d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko        }
71d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko
72d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko        @Override
73633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko        protected Boolean doInBackground(Void... params) {
74944779887775bd950cf1abf348d2df461593f6abLive Channels Team            @StorageStatus int storageStatus = getDvrStorageStatus();
75944779887775bd950cf1abf348d2df461593f6abLive Channels Team            if (storageStatus == STORAGE_STATUS_MISSING) {
76d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko                return null;
77d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko            }
78944779887775bd950cf1abf348d2df461593f6abLive Channels Team            if (storageStatus == STORAGE_STATUS_TOTAL_CAPACITY_TOO_SMALL) {
79633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko                return true;
80633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko            }
81633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko            List<ContentProviderOperation> ops = getDeleteOps();
82d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko            if (ops == null || ops.isEmpty()) {
83d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko                return null;
84d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko            }
8595961816a768da387f0b5523cf4363ace2044089Nick Chalko            Log.i(
8695961816a768da387f0b5523cf4363ace2044089Nick Chalko                    TAG,
8795961816a768da387f0b5523cf4363ace2044089Nick Chalko                    "New device storage mounted. # of recordings to be forgotten : " + ops.size());
8895961816a768da387f0b5523cf4363ace2044089Nick Chalko            for (int i = 0; i < ops.size() && !isCancelled(); i += BATCH_OPERATION_COUNT) {
8995961816a768da387f0b5523cf4363ace2044089Nick Chalko                int toIndex =
9095961816a768da387f0b5523cf4363ace2044089Nick Chalko                        (i + BATCH_OPERATION_COUNT) > ops.size()
9195961816a768da387f0b5523cf4363ace2044089Nick Chalko                                ? ops.size()
9295961816a768da387f0b5523cf4363ace2044089Nick Chalko                                : (i + BATCH_OPERATION_COUNT);
93d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko                ArrayList<ContentProviderOperation> batchOps =
94d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko                        new ArrayList<>(ops.subList(i, toIndex));
95d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko                try {
96944779887775bd950cf1abf348d2df461593f6abLive Channels Team                    mContext.getContentResolver().applyBatch(TvContractCompat.AUTHORITY, batchOps);
97d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko                } catch (RemoteException | OperationApplicationException e) {
98d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko                    Log.e(TAG, "Failed to clean up  RecordedPrograms.", e);
99d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko                }
100d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko            }
101d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko            return null;
102d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko        }
103d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko
104d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko        @Override
105633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko        protected void onPostExecute(Boolean forgetStorage) {
106633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko            if (forgetStorage != null && forgetStorage == true) {
107944779887775bd950cf1abf348d2df461593f6abLive Channels Team                DvrManager dvrManager = TvSingletons.getSingletons(mContext).getDvrManager();
108633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko                TvInputManagerHelper tvInputManagerHelper =
109944779887775bd950cf1abf348d2df461593f6abLive Channels Team                        TvSingletons.getSingletons(mContext).getTvInputManagerHelper();
110633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko                List<TvInputInfo> tvInputInfoList =
111633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko                        tvInputManagerHelper.getTvInputInfos(true, false);
112633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko                if (tvInputInfoList == null || tvInputInfoList.isEmpty()) {
113633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko                    return;
114633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko                }
115633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko                for (TvInputInfo info : tvInputInfoList) {
116944779887775bd950cf1abf348d2df461593f6abLive Channels Team                    if (CommonUtils.isBundledInput(info.getId())) {
117633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko                        dvrManager.forgetStorage(info.getId());
118633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko                    }
119633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko                }
120633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko            }
121d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko            if (mCleanUpDbTask == this) {
122d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko                mCleanUpDbTask = null;
123d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko            }
124d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko        }
125d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko
126633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko        private List<ContentProviderOperation> getDeleteOps() {
127d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko            List<ContentProviderOperation> ops = new ArrayList<>();
128d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko
12995961816a768da387f0b5523cf4363ace2044089Nick Chalko            try (Cursor c =
13095961816a768da387f0b5523cf4363ace2044089Nick Chalko                    mContentResolver.query(
131944779887775bd950cf1abf348d2df461593f6abLive Channels Team                            TvContractCompat.RecordedPrograms.CONTENT_URI,
13295961816a768da387f0b5523cf4363ace2044089Nick Chalko                            PROJECTION,
13395961816a768da387f0b5523cf4363ace2044089Nick Chalko                            null,
13495961816a768da387f0b5523cf4363ace2044089Nick Chalko                            null,
13595961816a768da387f0b5523cf4363ace2044089Nick Chalko                            null)) {
136d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko                if (c == null) {
137d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko                    return null;
138d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko                }
139d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko                while (c.moveToNext()) {
140944779887775bd950cf1abf348d2df461593f6abLive Channels Team                    @StorageStatus int storageStatus = getDvrStorageStatus();
141944779887775bd950cf1abf348d2df461593f6abLive Channels Team                    if (isCancelled() || storageStatus == STORAGE_STATUS_MISSING) {
142d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko                        ops.clear();
143d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko                        break;
144d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko                    }
145d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko                    String id = c.getString(0);
146d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko                    String packageName = c.getString(1);
147d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko                    String dataUriString = c.getString(2);
148d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko                    if (dataUriString == null) {
149d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko                        continue;
150d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko                    }
151d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko                    Uri dataUri = Uri.parse(dataUriString);
152944779887775bd950cf1abf348d2df461593f6abLive Channels Team                    if (!CommonUtils.isInBundledPackageSet(packageName)
15395961816a768da387f0b5523cf4363ace2044089Nick Chalko                            || dataUri == null
15495961816a768da387f0b5523cf4363ace2044089Nick Chalko                            || dataUri.getPath() == null
155d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko                            || !ContentResolver.SCHEME_FILE.equals(dataUri.getScheme())) {
156d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko                        continue;
157d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko                    }
158d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko                    File recordedProgramDir = new File(dataUri.getPath());
159633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko                    if (!recordedProgramDir.exists()) {
16095961816a768da387f0b5523cf4363ace2044089Nick Chalko                        ops.add(
16195961816a768da387f0b5523cf4363ace2044089Nick Chalko                                ContentProviderOperation.newDelete(
162944779887775bd950cf1abf348d2df461593f6abLive Channels Team                                                TvContractCompat.buildRecordedProgramUri(
16395961816a768da387f0b5523cf4363ace2044089Nick Chalko                                                        Long.parseLong(id)))
16495961816a768da387f0b5523cf4363ace2044089Nick Chalko                                        .build());
165d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko                    }
166d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko                }
167d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko                return ops;
168d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko            }
169d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko        }
170d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko    }
171d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko}
172