Utils.java revision 91033e05806d697a70bfee6140d394f9c6c98841
1/*
2 * Copyright (C) 2014 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.util;
18
19import android.content.ContentUris;
20import android.content.ContentValues;
21import android.content.Context;
22import android.content.Intent;
23import android.content.SharedPreferences;
24import android.content.pm.ActivityInfo;
25import android.content.pm.PackageManager;
26import android.content.pm.ResolveInfo;
27import android.content.pm.ServiceInfo;
28import android.database.Cursor;
29import android.media.tv.TvContract;
30import android.media.tv.TvInputInfo;
31import android.net.Uri;
32import android.preference.PreferenceManager;
33import android.text.TextUtils;
34import android.util.Base64;
35
36import com.android.tv.data.Channel;
37import com.android.tv.data.Program;
38import com.android.tv.data.StreamInfo;
39
40import java.util.List;
41
42/**
43 * A class that includes convenience methods for accessing TvProvider database.
44 */
45public class Utils {
46    public static final String SERVICE_INTERFACE = "android.media.tv.TvInputService";
47    public static final String EXTRA_SERVICE_NAME = "serviceName";
48    public static final String EXTRA_KEYCODE = "keycode";
49
50    public static final String CHANNEL_SORT_ORDER_BY_DISPLAY_NUMBER =
51            "CAST(" + TvContract.Channels.COLUMN_DISPLAY_NUMBER + " AS INTEGER), "
52            + "CAST(SUBSTR(LTRIM(" + TvContract.Channels.COLUMN_DISPLAY_NUMBER
53            + ",'0123456789'),2) AS INTEGER)";
54
55    // preferences stored in the default preference.
56    private static final String PREF_KEY_LAST_SELECTED_TV_INPUT = "last_selected_tv_input";
57    private static final String PREF_KEY_LAST_SELECTED_PHYS_TV_INPUT =
58            "last_selected_phys_tv_input";
59
60    private static final String PREFIX_PREF_NAME = "com.android.tv.";
61    // preferences stored in the preference of a specific tv input.
62    private static final String PREF_KEY_LAST_WATCHED_CHANNEL_ID = "last_watched_channel_id";
63
64    // STOPSHIP: Use the one defined in the contract class instead.
65    private static final String TvContract_Programs_COLUMN_VIDEO_RESOLUTION = "video_resolution";
66
67    private static int VIDEO_SD_WIDTH = 704;
68    private static int VIDEO_SD_HEIGHT = 480;
69    private static int VIDEO_HD_WIDTH = 1280;
70    private static int VIDEO_HD_HEIGHT = 720;
71    private static int VIDEO_FULL_HD_WIDTH = 1920;
72    private static int VIDEO_FULL_HD_HEIGHT = 1080;
73    private static int VIDEO_ULTRA_HD_WIDTH = 2048;
74    private static int VIDEO_ULTRA_HD_HEIGHT = 1536;
75
76    private enum AspectRatio {
77        ASPECT_RATIO_4_3(4, 3),
78        ASPECT_RATIO_16_9(16, 9),
79        ASPECT_RATIO_21_9(21, 9);
80
81        final int width;
82        final int height;
83
84        AspectRatio(int width, int height) {
85            this.width = width;
86            this.height = height;
87        }
88
89        @Override
90        public String toString() {
91            return String.format("%d:%d", width, height);
92        }
93    }
94
95    private Utils() { /* cannot be instantiated */ }
96
97    public static Uri getChannelUri(long channelId) {
98        return ContentUris.withAppendedId(TvContract.Channels.CONTENT_URI, channelId);
99    }
100
101    public static String getInputIdForChannel(Context context, long channelId) {
102        if (channelId == Channel.INVALID_ID) {
103            return null;
104        }
105        Uri channelUri = ContentUris.withAppendedId(TvContract.Channels.CONTENT_URI, channelId);
106        return getInputIdForChannel(context, channelUri);
107    }
108
109    public static String getInputIdForChannel(Context context, Uri channelUri) {
110        if (channelUri == null) {
111            return null;
112        }
113        String[] projection = { TvContract.Channels.COLUMN_INPUT_ID };
114        Cursor cursor = null;
115        try {
116            cursor = context.getContentResolver().query(channelUri, projection, null, null, null);
117            if (cursor != null && cursor.moveToNext()) {
118                return cursor.getString(0);
119            }
120        } finally {
121            if (cursor != null) {
122                cursor.close();
123            }
124        }
125        return null;
126    }
127
128    public static void setLastWatchedChannelId(Context context, String inputId, String physInputId,
129            long channelId) {
130        if (TextUtils.isEmpty(inputId)) {
131            throw new IllegalArgumentException("inputId cannot be empty");
132        }
133        context.getSharedPreferences(getPreferenceName(inputId), Context.MODE_PRIVATE).edit()
134                .putLong(PREF_KEY_LAST_WATCHED_CHANNEL_ID, channelId).apply();
135        PreferenceManager.getDefaultSharedPreferences(context).edit()
136                .putString(PREF_KEY_LAST_SELECTED_TV_INPUT, inputId).apply();
137        PreferenceManager.getDefaultSharedPreferences(context).edit()
138                .putString(PREF_KEY_LAST_SELECTED_PHYS_TV_INPUT, physInputId).apply();
139    }
140
141    public static long getLastWatchedChannelId(Context context) {
142        String inputId = getLastSelectedInputId(context);
143        if (inputId == null) {
144            return Channel.INVALID_ID;
145        }
146        return getLastWatchedChannelId(context, inputId);
147    }
148
149    public static long getLastWatchedChannelId(Context context, String inputId) {
150        if (TextUtils.isEmpty(inputId)) {
151            throw new IllegalArgumentException("inputId cannot be empty");
152        }
153        return context.getSharedPreferences(getPreferenceName(inputId),
154                Context.MODE_PRIVATE).getLong(PREF_KEY_LAST_WATCHED_CHANNEL_ID, Channel.INVALID_ID);
155    }
156
157    public static String getLastSelectedInputId(Context context) {
158        return PreferenceManager.getDefaultSharedPreferences(context)
159                .getString(PREF_KEY_LAST_SELECTED_TV_INPUT, null);
160    }
161
162    public static String getLastSelectedPhysInputId(Context context) {
163        return PreferenceManager.getDefaultSharedPreferences(context)
164                .getString(PREF_KEY_LAST_SELECTED_PHYS_TV_INPUT, null);
165    }
166
167    public static Program getCurrentProgram(Context context, Uri channelUri) {
168        if (channelUri == null) {
169            return null;
170        }
171        long time = System.currentTimeMillis();
172        Uri uri = TvContract.buildProgramsUriForChannel(channelUri, time, time);
173        String[] projection = {
174                TvContract.Programs.COLUMN_TITLE,
175                TvContract.Programs.COLUMN_SHORT_DESCRIPTION,
176                TvContract.Programs.COLUMN_START_TIME_UTC_MILLIS,
177                TvContract.Programs.COLUMN_END_TIME_UTC_MILLIS,
178                TvContract_Programs_COLUMN_VIDEO_RESOLUTION,
179                TvContract.Programs.COLUMN_POSTER_ART_URI,
180                TvContract.Programs.COLUMN_THUMBNAIL_URI };
181        Cursor cursor = context.getContentResolver().query(uri, projection, null, null, null);
182        String title = null;
183        String description = null;
184        long startTime = -1;
185        long endTime = -1;
186        String posterArtUri = null;
187        String thumbnailUri = null;
188        String videoDefinitionLevel = "";
189        if (cursor.moveToNext()) {
190            title = cursor.getString(0);
191            description = cursor.getString(1);
192            startTime = cursor.getLong(2);
193            endTime = cursor.getLong(3);
194            videoDefinitionLevel = cursor.getString(4);
195            posterArtUri = cursor.getString(5);
196            thumbnailUri = cursor.getString(6);
197        }
198        cursor.close();
199
200        // TODO: Consider providing the entire data if needed.
201        return new Program.Builder()
202                .setTitle(title)
203                .setDescription(description)
204                .setStartTimeUtcMillis(startTime)
205                .setEndTimeUtcMillis(endTime)
206                .setVideoDefinitionLevel(videoDefinitionLevel)
207                .setPosterArtUri(posterArtUri)
208                .setThumbnailUri(thumbnailUri).build();
209    }
210
211    public static void updateCurrentVideoResolution(Context context, Long channelId, int format) {
212        if (channelId == Channel.INVALID_ID) {
213            return;
214        }
215        Uri channelUri = TvContract.buildChannelUri(channelId);
216        long time = System.currentTimeMillis();
217        Uri uri = TvContract.buildProgramsUriForChannel(channelUri, time, time);
218        Cursor cursor = null;
219        String[] projection = { TvContract.Programs._ID };
220        long programId = Program.INVALID_ID;
221        try {
222            cursor = context.getContentResolver().query(uri, projection, null, null, null);
223            if (cursor == null || !cursor.moveToNext()) {
224                return;
225            }
226            programId = cursor.getLong(0);
227        } finally {
228            if (cursor != null) {
229                cursor.close();
230            }
231        }
232
233        String videoResolution = getVideoDefinitionLevelString(format);
234        if (TextUtils.isEmpty(videoResolution)) {
235            return;
236        }
237        Uri programUri = TvContract.buildProgramUri(programId);
238        ContentValues values = new ContentValues();
239        values.put(TvContract_Programs_COLUMN_VIDEO_RESOLUTION, videoResolution);
240        context.getContentResolver().update(programUri, values, null, null);
241    }
242
243    public static boolean hasChannel(Context context, TvInputInfo name) {
244        return hasChannel(context, name, true);
245    }
246
247    public static boolean hasChannel(Context context, TvInputInfo name, boolean browsableOnly) {
248        Uri uri = TvContract.buildChannelsUriForInput(name.getId(), browsableOnly);
249        String[] projection = { TvContract.Channels._ID };
250        Cursor cursor = null;
251        try {
252            cursor = context.getContentResolver().query(uri, projection, null, null, null);
253            return cursor != null && cursor.getCount() > 0;
254        } finally {
255            if (cursor != null) {
256                cursor.close();
257            }
258        }
259    }
260
261    public static SharedPreferences getSharedPreferencesOfDisplayNameForInput(Context context) {
262        return context.getSharedPreferences(TvSettings.PREFS_FILE, Context.MODE_PRIVATE);
263    }
264
265    public static String getDisplayNameForInput(Context context, TvInputInfo info) {
266        SharedPreferences preferences = getSharedPreferencesOfDisplayNameForInput(context);
267        return preferences.getString(TvSettings.PREF_DISPLAY_INPUT_NAME + info.getId(),
268                info.loadLabel(context).toString());
269    }
270
271    public static boolean hasActivity(Context context, TvInputInfo input, String action) {
272        return getActivityInfo(context, input, action) != null;
273    }
274
275    public static String getAspectRatioString(int width, int height) {
276        if (width == 0 || height == 0) {
277            return "";
278        }
279
280        for (AspectRatio ratio: AspectRatio.values()) {
281            if (Math.abs((float) ratio.height / ratio.width - (float) height / width) < 0.05f) {
282                return ratio.toString();
283            }
284        }
285        return "";
286    }
287
288    public static int getVideoDefinitionLevelFromSize(int width, int height) {
289        if (width >= VIDEO_ULTRA_HD_WIDTH && height >= VIDEO_ULTRA_HD_HEIGHT) {
290            return StreamInfo.VIDEO_DEFINITION_LEVEL_ULTRA_HD;
291        } else if (width >= VIDEO_FULL_HD_WIDTH && height >= VIDEO_FULL_HD_HEIGHT) {
292            return StreamInfo.VIDEO_DEFINITION_LEVEL_FULL_HD;
293        } else if (width >= VIDEO_HD_WIDTH && height >= VIDEO_HD_HEIGHT) {
294            return StreamInfo.VIDEO_DEFINITION_LEVEL_HD;
295        } else if (width >= VIDEO_SD_WIDTH && height >= VIDEO_SD_HEIGHT) {
296            return StreamInfo.VIDEO_DEFINITION_LEVEL_SD;
297        }
298        return StreamInfo.VIDEO_DEFINITION_LEVEL_UNKNOWN;
299    }
300
301    public static String getVideoDefinitionLevelString(int videoFormat) {
302        switch (videoFormat) {
303            case StreamInfo.VIDEO_DEFINITION_LEVEL_ULTRA_HD:
304                return "Ultra HD";
305            case StreamInfo.VIDEO_DEFINITION_LEVEL_FULL_HD:
306                return "Full HD";
307            case StreamInfo.VIDEO_DEFINITION_LEVEL_HD:
308                return "HD";
309            case StreamInfo.VIDEO_DEFINITION_LEVEL_SD:
310                return "SD";
311        }
312        return "";
313    }
314
315    public static String getAudioChannelString(int channelCount) {
316        switch (channelCount) {
317            case 1:
318                return "MONO";
319            case 2:
320                return "STEREO";
321            case 6:
322                return "5.1";
323            case 8:
324                return "7.1";
325        }
326        return "";
327    }
328
329    private static ActivityInfo getActivityInfo(Context context, TvInputInfo input, String action) {
330        if (input == null) {
331            return null;
332        }
333
334        List<ResolveInfo> infos = context.getPackageManager().queryIntentActivities(
335                new Intent(action), PackageManager.GET_ACTIVITIES);
336        if (infos == null) {
337            return null;
338        }
339
340        for (ResolveInfo info : infos) {
341            if (info.activityInfo.packageName.equals(input.getServiceInfo().packageName)) {
342                return info.activityInfo;
343            }
344        }
345        return null;
346    }
347
348    private static String getPreferenceName(String inputId) {
349        return PREFIX_PREF_NAME + Base64.encodeToString(inputId.getBytes(), Base64.URL_SAFE);
350    }
351}
352