• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2018 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 
17 package com.android.tv.util;
18 
19 import static java.lang.Boolean.TRUE;
20 
21 import android.content.Context;
22 import android.media.tv.TvContract;
23 import android.net.Uri;
24 import android.os.Build;
25 import android.os.Bundle;
26 import android.support.annotation.StringDef;
27 import android.support.annotation.VisibleForTesting;
28 import android.support.annotation.WorkerThread;
29 import android.util.Log;
30 import com.android.tv.data.BaseProgram;
31 import com.android.tv.features.PartnerFeatures;
32 import java.lang.annotation.Retention;
33 import java.lang.annotation.RetentionPolicy;
34 import java.util.ArrayList;
35 import java.util.Arrays;
36 import java.util.Collections;
37 import java.util.HashSet;
38 import java.util.List;
39 import java.util.Set;
40 
41 /** A utility class related to TvProvider. */
42 public final class TvProviderUtils {
43     private static final String TAG = "TvProviderUtils";
44 
45     public static final String EXTRA_PROGRAM_COLUMN_SERIES_ID = BaseProgram.COLUMN_SERIES_ID;
46     public static final String EXTRA_PROGRAM_COLUMN_STATE = BaseProgram.COLUMN_STATE;
47 
48     /** Possible extra columns in TV provider. */
49     @Retention(RetentionPolicy.SOURCE)
50     @StringDef({EXTRA_PROGRAM_COLUMN_SERIES_ID, EXTRA_PROGRAM_COLUMN_STATE})
51     public @interface TvProviderExtraColumn {}
52 
53     private static boolean sProgramHasSeriesIdColumn;
54     private static boolean sRecordedProgramHasSeriesIdColumn;
55     private static boolean sRecordedProgramHasStateColumn;
56 
57     /**
58      * Checks whether a table contains a series ID column.
59      *
60      * <p>This method is different from {@link #getProgramHasSeriesIdColumn()} and {@link
61      * #getRecordedProgramHasSeriesIdColumn()} because it may access to database, so it should be
62      * run in worker thread.
63      *
64      * @return {@code true} if the corresponding table contains a series ID column; {@code false}
65      *     otherwise.
66      */
67     @WorkerThread
checkSeriesIdColumn(Context context, Uri uri)68     public static synchronized boolean checkSeriesIdColumn(Context context, Uri uri) {
69         boolean canCreateColumn = (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O);
70         if (!canCreateColumn) {
71             return false;
72         }
73         return (Utils.isRecordedProgramsUri(uri)
74                         && checkRecordedProgramTableSeriesIdColumn(context, uri))
75                 || (Utils.isProgramsUri(uri) && checkProgramTableSeriesIdColumn(context, uri));
76     }
77 
78     @WorkerThread
checkProgramTableSeriesIdColumn(Context context, Uri uri)79     private static synchronized boolean checkProgramTableSeriesIdColumn(Context context, Uri uri) {
80         if (!sProgramHasSeriesIdColumn) {
81             if (getExistingColumns(context, uri).contains(EXTRA_PROGRAM_COLUMN_SERIES_ID)) {
82                 sProgramHasSeriesIdColumn = true;
83             } else if (addColumnToTable(context, uri, EXTRA_PROGRAM_COLUMN_SERIES_ID)) {
84                 sProgramHasSeriesIdColumn = true;
85             }
86         }
87         return sProgramHasSeriesIdColumn;
88     }
89 
90     @WorkerThread
checkRecordedProgramTableSeriesIdColumn( Context context, Uri uri)91     private static synchronized boolean checkRecordedProgramTableSeriesIdColumn(
92             Context context, Uri uri) {
93         if (!sRecordedProgramHasSeriesIdColumn) {
94             if (getExistingColumns(context, uri).contains(EXTRA_PROGRAM_COLUMN_SERIES_ID)) {
95                 sRecordedProgramHasSeriesIdColumn = true;
96             } else if (addColumnToTable(context, uri, EXTRA_PROGRAM_COLUMN_SERIES_ID)) {
97                 sRecordedProgramHasSeriesIdColumn = true;
98             }
99         }
100         return sRecordedProgramHasSeriesIdColumn;
101     }
102 
103     /**
104      * Checks whether a table contains a state column.
105      *
106      * <p>This method is different from {@link #getRecordedProgramHasStateColumn()} because it may
107      * access to database, so it should be run in worker thread.
108      *
109      * @return {@code true} if the corresponding table contains a state column; {@code false}
110      *     otherwise.
111      */
112     @WorkerThread
checkStateColumn(Context context, Uri uri)113     public static synchronized boolean checkStateColumn(Context context, Uri uri) {
114         boolean canCreateColumn = (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O);
115         if (!canCreateColumn) {
116             return false;
117         }
118         return (Utils.isRecordedProgramsUri(uri)
119                 && checkRecordedProgramTableStateColumn(context, uri));
120     }
121 
122     @WorkerThread
checkRecordedProgramTableStateColumn( Context context, Uri uri)123     private static synchronized boolean checkRecordedProgramTableStateColumn(
124             Context context, Uri uri) {
125         if (!sRecordedProgramHasStateColumn) {
126             if (getExistingColumns(context, uri).contains(EXTRA_PROGRAM_COLUMN_STATE)) {
127                 sRecordedProgramHasStateColumn = true;
128             } else if (addColumnToTable(context, uri, EXTRA_PROGRAM_COLUMN_STATE)) {
129                 sRecordedProgramHasStateColumn = true;
130             }
131         }
132         return sRecordedProgramHasStateColumn;
133     }
134 
getProgramHasSeriesIdColumn()135     public static synchronized boolean getProgramHasSeriesIdColumn() {
136         return TRUE.equals(sProgramHasSeriesIdColumn);
137     }
138 
getRecordedProgramHasSeriesIdColumn()139     public static synchronized boolean getRecordedProgramHasSeriesIdColumn() {
140         return TRUE.equals(sRecordedProgramHasSeriesIdColumn);
141     }
142 
getRecordedProgramHasStateColumn()143     public static synchronized boolean getRecordedProgramHasStateColumn() {
144         return TRUE.equals(sRecordedProgramHasStateColumn);
145     }
146 
addExtraColumnsToProjection(String[] projection, @TvProviderExtraColumn String column)147     public static String[] addExtraColumnsToProjection(String[] projection,
148             @TvProviderExtraColumn String column) {
149         List<String> projectionList = new ArrayList<>(Arrays.asList(projection));
150         if (!projectionList.contains(column)) {
151             projectionList.add(column);
152         }
153         projection = projectionList.toArray(projection);
154         return projection;
155     }
156 
157     /**
158      * Gets column names of a table
159      *
160      * @param uri the corresponding URI of the table
161      */
162     @VisibleForTesting
getExistingColumns(Context context, Uri uri)163     static Set<String> getExistingColumns(Context context, Uri uri) {
164         Bundle result = null;
165         try {
166             result =
167                     context.getContentResolver()
168                             .call(uri, TvContract.METHOD_GET_COLUMNS, uri.toString(), null);
169         } catch (Exception e) {
170             Log.e(TAG, "Error trying to get existing columns.", e);
171         }
172         if (result != null) {
173             String[] columns = result.getStringArray(TvContract.EXTRA_EXISTING_COLUMN_NAMES);
174             if (columns != null) {
175                 return new HashSet<>(Arrays.asList(columns));
176             }
177         }
178         Log.e(TAG, "Query existing column names from " + uri + " returned null");
179         return Collections.emptySet();
180     }
181 
182     /**
183      * Add a column to the table
184      *
185      * @return {@code true} if the column is added successfully; {@code false} otherwise.
186      */
addColumnToTable(Context context, Uri contentUri, String columnName)187     private static boolean addColumnToTable(Context context, Uri contentUri, String columnName) {
188         Bundle extra = new Bundle();
189         extra.putCharSequence(TvContract.EXTRA_COLUMN_NAME, columnName);
190         extra.putCharSequence(TvContract.EXTRA_DATA_TYPE, "TEXT");
191         // If the add operation fails, the following just returns null without crashing.
192         Bundle allColumns = null;
193         try {
194             allColumns =
195                     context.getContentResolver()
196                             .call(
197                                     contentUri,
198                                     TvContract.METHOD_ADD_COLUMN,
199                                     contentUri.toString(),
200                                     extra);
201         } catch (Exception e) {
202             Log.e(TAG, "Error trying to add column.", e);
203         }
204         if (allColumns == null) {
205             Log.w(TAG, "Adding new column failed. Uri=" + contentUri);
206         }
207         return allColumns != null;
208     }
209 
TvProviderUtils()210     private TvProviderUtils() {}
211 }
212