Home | History | Annotate | Download | only in util
      1 /*
      2  * Copyright (C) 2015 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 android.annotation.SuppressLint;
     20 import android.content.ComponentName;
     21 import android.content.ContentResolver;
     22 import android.content.ContentValues;
     23 import android.content.Context;
     24 import android.content.Intent;
     25 import android.content.pm.PackageManager;
     26 import android.content.res.ColorStateList;
     27 import android.content.res.Configuration;
     28 import android.content.res.Resources;
     29 import android.content.res.Resources.Theme;
     30 import android.database.Cursor;
     31 import android.media.tv.TvContract;
     32 import android.media.tv.TvContract.Channels;
     33 import android.media.tv.TvInputInfo;
     34 import android.media.tv.TvTrackInfo;
     35 import android.net.Uri;
     36 import android.os.Build;
     37 import android.preference.PreferenceManager;
     38 import android.support.annotation.Nullable;
     39 import android.support.annotation.VisibleForTesting;
     40 import android.support.annotation.WorkerThread;
     41 import android.text.TextUtils;
     42 import android.text.format.DateUtils;
     43 import android.util.Log;
     44 import android.view.View;
     45 import android.widget.Toast;
     46 
     47 import com.android.tv.R;
     48 import com.android.tv.TvApplication;
     49 import com.android.tv.data.Channel;
     50 import com.android.tv.data.Program;
     51 import com.android.tv.data.StreamInfo;
     52 
     53 import java.text.SimpleDateFormat;
     54 import java.util.ArrayList;
     55 import java.util.Calendar;
     56 import java.util.Collection;
     57 import java.util.Date;
     58 import java.util.HashSet;
     59 import java.util.List;
     60 import java.util.Locale;
     61 import java.util.Set;
     62 import java.util.TimeZone;
     63 import java.util.concurrent.TimeUnit;
     64 
     65 /**
     66  * A class that includes convenience methods for accessing TvProvider database.
     67  */
     68 public class Utils {
     69     private static final String TAG = "Utils";
     70     private static final boolean DEBUG = false;
     71 
     72     private static final SimpleDateFormat ISO_8601 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ");
     73 
     74     public static final String EXTRA_KEY_KEYCODE = "keycode";
     75     public static final String EXTRA_KEY_ACTION = "action";
     76     public static final String EXTRA_ACTION_SHOW_TV_INPUT ="show_tv_input";
     77     public static final String EXTRA_KEY_FROM_LAUNCHER = "from_launcher";
     78     public static final String EXTRA_KEY_RECORDING_URI = "recording_uri";
     79 
     80     // Query parameter in the intent of starting MainActivity.
     81     public static final String PARAM_SOURCE = "source";
     82 
     83     private static final String PATH_CHANNEL = "channel";
     84     private static final String PATH_PROGRAM = "program";
     85 
     86     private static final String PREF_KEY_LAST_WATCHED_CHANNEL_ID = "last_watched_channel_id";
     87     private static final String PREF_KEY_LAST_WATCHED_CHANNEL_ID_FOR_INPUT =
     88             "last_watched_channel_id_for_input_";
     89     private static final String PREF_KEY_LAST_WATCHED_CHANNEL_URI = "last_watched_channel_uri";
     90 
     91     private static final int VIDEO_SD_WIDTH = 704;
     92     private static final int VIDEO_SD_HEIGHT = 480;
     93     private static final int VIDEO_HD_WIDTH = 1280;
     94     private static final int VIDEO_HD_HEIGHT = 720;
     95     private static final int VIDEO_FULL_HD_WIDTH = 1920;
     96     private static final int VIDEO_FULL_HD_HEIGHT = 1080;
     97     private static final int VIDEO_ULTRA_HD_WIDTH = 2048;
     98     private static final int VIDEO_ULTRA_HD_HEIGHT = 1536;
     99 
    100     private static final int AUDIO_CHANNEL_NONE = 0;
    101     private static final int AUDIO_CHANNEL_MONO = 1;
    102     private static final int AUDIO_CHANNEL_STEREO = 2;
    103     private static final int AUDIO_CHANNEL_SURROUND_6 = 6;
    104     private static final int AUDIO_CHANNEL_SURROUND_8 = 8;
    105 
    106     private enum AspectRatio {
    107         ASPECT_RATIO_4_3(4, 3),
    108         ASPECT_RATIO_16_9(16, 9),
    109         ASPECT_RATIO_21_9(21, 9);
    110 
    111         final int width;
    112         final int height;
    113 
    114         AspectRatio(int width, int height) {
    115             this.width = width;
    116             this.height = height;
    117         }
    118 
    119         @Override
    120         @SuppressLint("DefaultLocale")
    121         public String toString() {
    122             return String.format("%d:%d", width, height);
    123         }
    124     }
    125 
    126     private Utils() {
    127     }
    128 
    129     public static String buildSelectionForIds(String idName, List<Long> ids) {
    130         StringBuilder sb = new StringBuilder();
    131         sb.append(idName).append(" in (")
    132                 .append(ids.get(0));
    133         for (int i = 1; i < ids.size(); ++i) {
    134             sb.append(",").append(ids.get(i));
    135         }
    136         sb.append(")");
    137         return sb.toString();
    138     }
    139 
    140     @WorkerThread
    141     public static String getInputIdForChannel(Context context, long channelId) {
    142         if (channelId == Channel.INVALID_ID) {
    143             return null;
    144         }
    145         Uri channelUri = TvContract.buildChannelUri(channelId);
    146         String[] projection = {TvContract.Channels.COLUMN_INPUT_ID};
    147         try (Cursor cursor = context.getContentResolver()
    148                 .query(channelUri, projection, null, null, null)) {
    149             if (cursor != null && cursor.moveToNext()) {
    150                 return Utils.intern(cursor.getString(0));
    151             }
    152         }
    153         return null;
    154     }
    155 
    156     public static void setLastWatchedChannel(Context context, Channel channel) {
    157         if (channel == null) {
    158             Log.e(TAG, "setLastWatchedChannel: channel cannot be null");
    159             return;
    160         }
    161         PreferenceManager.getDefaultSharedPreferences(context).edit()
    162                 .putString(PREF_KEY_LAST_WATCHED_CHANNEL_URI, channel.getUri().toString()).apply();
    163         if (!channel.isPassthrough()) {
    164             long channelId = channel.getId();
    165             if (channel.getId() < 0) {
    166                 throw new IllegalArgumentException("channelId should be equal to or larger than 0");
    167             }
    168             PreferenceManager.getDefaultSharedPreferences(context).edit()
    169                     .putLong(PREF_KEY_LAST_WATCHED_CHANNEL_ID, channelId).apply();
    170             PreferenceManager.getDefaultSharedPreferences(context).edit()
    171                     .putLong(PREF_KEY_LAST_WATCHED_CHANNEL_ID_FOR_INPUT + channel.getInputId(),
    172                             channelId).apply();
    173         }
    174     }
    175 
    176     public static long getLastWatchedChannelId(Context context) {
    177         return PreferenceManager.getDefaultSharedPreferences(context)
    178                 .getLong(PREF_KEY_LAST_WATCHED_CHANNEL_ID, Channel.INVALID_ID);
    179     }
    180 
    181     public static long getLastWatchedChannelIdForInput(Context context, String inputId) {
    182         return PreferenceManager.getDefaultSharedPreferences(context)
    183                 .getLong(PREF_KEY_LAST_WATCHED_CHANNEL_ID_FOR_INPUT + inputId, Channel.INVALID_ID);
    184     }
    185 
    186     public static String getLastWatchedChannelUri(Context context) {
    187         return PreferenceManager.getDefaultSharedPreferences(context)
    188                 .getString(PREF_KEY_LAST_WATCHED_CHANNEL_URI, null);
    189     }
    190 
    191     /**
    192      * Returns {@code true}, if {@code uri} specifies an input, which is usually generated
    193      * from {@link TvContract#buildChannelsUriForInput}.
    194      */
    195     public static boolean isChannelUriForInput(Uri uri) {
    196         return isTvUri(uri) && PATH_CHANNEL.equals(uri.getPathSegments().get(0))
    197                 && !TextUtils.isEmpty(uri.getQueryParameter("input"));
    198     }
    199 
    200     /**
    201      * Returns {@code true}, if {@code uri} is a channel URI for a specific channel. It is copied
    202      * from the hidden method TvContract.isChannelUri.
    203      */
    204     public static boolean isChannelUriForOneChannel(Uri uri) {
    205         return isChannelUriForTunerInput(uri) || TvContract.isChannelUriForPassthroughInput(uri);
    206     }
    207 
    208     /**
    209      * Returns {@code true}, if {@code uri} is a channel URI for a tuner input. It is copied from
    210      * the hidden method TvContract.isChannelUriForTunerInput.
    211      */
    212     public static boolean isChannelUriForTunerInput(Uri uri) {
    213         return isTvUri(uri) && isTwoSegmentUriStartingWith(uri, PATH_CHANNEL);
    214     }
    215 
    216     private static boolean isTvUri(Uri uri) {
    217         return uri != null && ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())
    218                 && TvContract.AUTHORITY.equals(uri.getAuthority());
    219     }
    220 
    221     private static boolean isTwoSegmentUriStartingWith(Uri uri, String pathSegment) {
    222         List<String> pathSegments = uri.getPathSegments();
    223         return pathSegments.size() == 2 && pathSegment.equals(pathSegments.get(0));
    224     }
    225 
    226     /**
    227      * Returns {@code true}, if {@code uri} is a programs URI.
    228      */
    229     public static boolean isProgramsUri(Uri uri) {
    230         return isTvUri(uri) && PATH_PROGRAM.equals(uri.getPathSegments().get(0));
    231     }
    232 
    233     /**
    234      * Gets the info of the program on particular time.
    235      */
    236     @WorkerThread
    237     public static Program getProgramAt(Context context, long channelId, long timeMs) {
    238         if (channelId == Channel.INVALID_ID) {
    239             Log.e(TAG, "getCurrentProgramAt - channelId is invalid");
    240             return null;
    241         }
    242         if (context.getMainLooper().getThread().equals(Thread.currentThread())) {
    243             String message = "getCurrentProgramAt called on main thread";
    244             if (DEBUG) {
    245                 // Generating a stack trace can be expensive, only do it in debug mode.
    246                 Log.w(TAG, message, new IllegalStateException(message));
    247             } else {
    248                 Log.w(TAG, message);
    249             }
    250         }
    251         Uri uri = TvContract.buildProgramsUriForChannel(TvContract.buildChannelUri(channelId),
    252                 timeMs, timeMs);
    253         try (Cursor cursor = context.getContentResolver().query(uri, Program.PROJECTION,
    254                 null, null, null)) {
    255             if (cursor != null && cursor.moveToNext()) {
    256                 return Program.fromCursor(cursor);
    257             }
    258         }
    259         return null;
    260     }
    261 
    262     /**
    263      * Gets the info of the current program.
    264      */
    265     @WorkerThread
    266     public static Program getCurrentProgram(Context context, long channelId) {
    267         return getProgramAt(context, channelId, System.currentTimeMillis());
    268     }
    269 
    270     /**
    271      * Returns duration string according to the date & time format.
    272      * If {@code startUtcMillis} and {@code endUtcMills} are equal,
    273      * formatted time will be returned instead.
    274      *
    275      * @param startUtcMillis start of duration in millis. Should be less than {code endUtcMillis}.
    276      * @param endUtcMillis end of duration in millis. Should be larger than {@code startUtcMillis}.
    277      * @param useShortFormat {@code true} if abbreviation is needed to save space.
    278      *                       In that case, date will be omitted if duration starts from today
    279      *                       and is less than a day. If it's necessary,
    280      *                       {@link DateUtils#FORMAT_NUMERIC_DATE} is used otherwise.
    281      */
    282     public static String getDurationString(
    283             Context context, long startUtcMillis, long endUtcMillis, boolean useShortFormat) {
    284         return getDurationString(context, System.currentTimeMillis(), startUtcMillis, endUtcMillis,
    285                 useShortFormat, 0);
    286     }
    287 
    288     @VisibleForTesting
    289     static String getDurationString(Context context, long baseMillis,
    290             long startUtcMillis, long endUtcMillis, boolean useShortFormat, int flag) {
    291         flag |= DateUtils.FORMAT_ABBREV_MONTH | DateUtils.FORMAT_SHOW_TIME
    292                 | ((useShortFormat) ? DateUtils.FORMAT_NUMERIC_DATE : 0);
    293         if (!isInGivenDay(baseMillis, startUtcMillis)) {
    294             flag |= DateUtils.FORMAT_SHOW_DATE;
    295         }
    296         if (startUtcMillis != endUtcMillis && useShortFormat) {
    297             // Do special handling for 12:00 AM when checking if it's in the given day.
    298             // If it's start, it's considered as beginning of the day. (e.g. 12:00 AM - 12:30 AM)
    299             // If it's end, it's considered as end of the day (e.g. 11:00 PM - 12:00 AM)
    300             if (!isInGivenDay(startUtcMillis, endUtcMillis - 1)
    301                     && endUtcMillis - startUtcMillis < TimeUnit.HOURS.toMillis(11)) {
    302                 // Do not show date for short format.
    303                 // Extracting a day is needed because {@link DateUtils@formatDateRange}
    304                 // adds date if the duration covers multiple days.
    305                 return DateUtils.formatDateRange(context,
    306                         startUtcMillis, endUtcMillis - TimeUnit.DAYS.toMillis(1), flag);
    307             }
    308         }
    309         return DateUtils.formatDateRange(context, startUtcMillis, endUtcMillis, flag);
    310     }
    311 
    312     @VisibleForTesting
    313     public static boolean isInGivenDay(long dayToMatchInMillis, long subjectTimeInMillis) {
    314         final long DAY_IN_MS = TimeUnit.DAYS.toMillis(1);
    315         TimeZone timeZone = Calendar.getInstance().getTimeZone();
    316         long offset = timeZone.getRawOffset();
    317         if (timeZone.inDaylightTime(new Date(dayToMatchInMillis))) {
    318             offset += timeZone.getDSTSavings();
    319         }
    320         return Utils.floorTime(dayToMatchInMillis + offset, DAY_IN_MS)
    321                 == Utils.floorTime(subjectTimeInMillis + offset, DAY_IN_MS);
    322     }
    323 
    324     public static String getAspectRatioString(int width, int height) {
    325         if (width == 0 || height == 0) {
    326             return "";
    327         }
    328 
    329         for (AspectRatio ratio: AspectRatio.values()) {
    330             if (Math.abs((float) ratio.height / ratio.width - (float) height / width) < 0.05f) {
    331                 return ratio.toString();
    332             }
    333         }
    334         return "";
    335     }
    336 
    337     public static String getAspectRatioString(float videoDisplayAspectRatio) {
    338         if (videoDisplayAspectRatio <= 0) {
    339             return "";
    340         }
    341 
    342         for (AspectRatio ratio : AspectRatio.values()) {
    343             if (Math.abs((float) ratio.width / ratio.height - videoDisplayAspectRatio) < 0.05f) {
    344                 return ratio.toString();
    345             }
    346         }
    347         return "";
    348     }
    349 
    350     public static int getVideoDefinitionLevelFromSize(int width, int height) {
    351         if (width >= VIDEO_ULTRA_HD_WIDTH && height >= VIDEO_ULTRA_HD_HEIGHT) {
    352             return StreamInfo.VIDEO_DEFINITION_LEVEL_ULTRA_HD;
    353         } else if (width >= VIDEO_FULL_HD_WIDTH && height >= VIDEO_FULL_HD_HEIGHT) {
    354             return StreamInfo.VIDEO_DEFINITION_LEVEL_FULL_HD;
    355         } else if (width >= VIDEO_HD_WIDTH && height >= VIDEO_HD_HEIGHT) {
    356             return StreamInfo.VIDEO_DEFINITION_LEVEL_HD;
    357         } else if (width >= VIDEO_SD_WIDTH && height >= VIDEO_SD_HEIGHT) {
    358             return StreamInfo.VIDEO_DEFINITION_LEVEL_SD;
    359         }
    360         return StreamInfo.VIDEO_DEFINITION_LEVEL_UNKNOWN;
    361     }
    362 
    363     public static String getVideoDefinitionLevelString(Context context, int videoFormat) {
    364         switch (videoFormat) {
    365             case StreamInfo.VIDEO_DEFINITION_LEVEL_ULTRA_HD:
    366                 return context.getResources().getString(
    367                         R.string.video_definition_level_ultra_hd);
    368             case StreamInfo.VIDEO_DEFINITION_LEVEL_FULL_HD:
    369                 return context.getResources().getString(
    370                         R.string.video_definition_level_full_hd);
    371             case StreamInfo.VIDEO_DEFINITION_LEVEL_HD:
    372                 return context.getResources().getString(R.string.video_definition_level_hd);
    373             case StreamInfo.VIDEO_DEFINITION_LEVEL_SD:
    374                 return context.getResources().getString(R.string.video_definition_level_sd);
    375         }
    376         return "";
    377     }
    378 
    379     public static String getAudioChannelString(Context context, int channelCount) {
    380         switch (channelCount) {
    381             case 1:
    382                 return context.getResources().getString(R.string.audio_channel_mono);
    383             case 2:
    384                 return context.getResources().getString(R.string.audio_channel_stereo);
    385             case 6:
    386                 return context.getResources().getString(R.string.audio_channel_5_1);
    387             case 8:
    388                 return context.getResources().getString(R.string.audio_channel_7_1);
    389         }
    390         return "";
    391     }
    392 
    393     public static boolean needToShowSampleRate(Context context, List<TvTrackInfo> tracks) {
    394         Set<String> multiAudioStrings = new HashSet<>();
    395         for (TvTrackInfo track : tracks) {
    396             String multiAudioString = getMultiAudioString(context, track, false);
    397             if (multiAudioStrings.contains(multiAudioString)) {
    398                 return true;
    399             }
    400             multiAudioStrings.add(multiAudioString);
    401         }
    402         return false;
    403     }
    404 
    405     public static String getMultiAudioString(Context context, TvTrackInfo track,
    406             boolean showSampleRate) {
    407         if (track.getType() != TvTrackInfo.TYPE_AUDIO) {
    408             throw new IllegalArgumentException("Not an audio track: " + track);
    409         }
    410         String language = context.getString(R.string.default_language);
    411         if (!TextUtils.isEmpty(track.getLanguage())) {
    412             language = new Locale(track.getLanguage()).getDisplayName();
    413         } else {
    414             Log.d(TAG, "No language information found for the audio track: " + track);
    415         }
    416 
    417         StringBuilder metadata = new StringBuilder();
    418         switch (track.getAudioChannelCount()) {
    419             case AUDIO_CHANNEL_NONE:
    420                 break;
    421             case AUDIO_CHANNEL_MONO:
    422                 metadata.append(context.getString(R.string.multi_audio_channel_mono));
    423                 break;
    424             case AUDIO_CHANNEL_STEREO:
    425                 metadata.append(context.getString(R.string.multi_audio_channel_stereo));
    426                 break;
    427             case AUDIO_CHANNEL_SURROUND_6:
    428                 metadata.append(context.getString(R.string.multi_audio_channel_surround_6));
    429                 break;
    430             case AUDIO_CHANNEL_SURROUND_8:
    431                 metadata.append(context.getString(R.string.multi_audio_channel_surround_8));
    432                 break;
    433             default:
    434                 if (track.getAudioChannelCount() > 0) {
    435                     metadata.append(context.getString(R.string.multi_audio_channel_suffix,
    436                             track.getAudioChannelCount()));
    437                 } else {
    438                     Log.d(TAG, "Invalid audio channel count (" + track.getAudioChannelCount()
    439                             + ") found for the audio track: " + track);
    440                 }
    441                 break;
    442         }
    443         if (showSampleRate) {
    444             int sampleRate = track.getAudioSampleRate();
    445             if (sampleRate > 0) {
    446                 if (metadata.length() > 0) {
    447                     metadata.append(", ");
    448                 }
    449                 int integerPart = sampleRate / 1000;
    450                 int tenths = (sampleRate % 1000) / 100;
    451                 metadata.append(integerPart);
    452                 if (tenths != 0) {
    453                     metadata.append(".");
    454                     metadata.append(tenths);
    455                 }
    456                 metadata.append("kHz");
    457             }
    458         }
    459 
    460         if (metadata.length() == 0) {
    461             return language;
    462         }
    463         return context.getString(R.string.multi_audio_display_string_with_channel, language,
    464                 metadata.toString());
    465     }
    466 
    467     public static boolean isEqualLanguage(String lang1, String lang2) {
    468         if (lang1 == null) {
    469             return lang2 == null;
    470         } else if (lang2 == null) {
    471             return false;
    472         }
    473         try {
    474             return TextUtils.equals(
    475                     new Locale(lang1).getISO3Language(), new Locale(lang2).getISO3Language());
    476         } catch (Exception ignored) {
    477         }
    478         return false;
    479     }
    480 
    481     public static boolean isIntentAvailable(Context context, Intent intent) {
    482        return context.getPackageManager().queryIntentActivities(
    483                intent, PackageManager.MATCH_DEFAULT_ONLY).size() > 0;
    484     }
    485 
    486     /**
    487      * Returns the label for a given input. Returns the custom label, if any.
    488      */
    489     public static String loadLabel(Context context, TvInputInfo input) {
    490         if (input == null) {
    491             return null;
    492         }
    493         CharSequence customLabel = input.loadCustomLabel(context);
    494         String label = (customLabel == null) ? null : customLabel.toString();
    495         if (TextUtils.isEmpty(label)) {
    496             label = input.loadLabel(context).toString();
    497         }
    498         return label;
    499     }
    500 
    501     /**
    502      * Enable all channels synchronously.
    503      */
    504     @WorkerThread
    505     public static void enableAllChannels(Context context) {
    506         ContentValues values = new ContentValues();
    507         values.put(Channels.COLUMN_BROWSABLE, 1);
    508         context.getContentResolver().update(Channels.CONTENT_URI, values, null, null);
    509     }
    510 
    511     /**
    512      * Converts time in milliseconds to a String.
    513      */
    514     public static String toTimeString(long timeMillis) {
    515         return new Date(timeMillis).toString();
    516     }
    517 
    518     /**
    519      * Converts time in milliseconds to a ISO 8061 string.
    520      */
    521     public static String toIsoDateTimeString(long timeMillis) {
    522         return ISO_8601.format(new Date(timeMillis));
    523     }
    524 
    525     /**
    526      * Returns a {@link String} object which contains the layout information of the {@code view}.
    527      */
    528     public static String toRectString(View view) {
    529         return "{"
    530                 + "l=" + view.getLeft()
    531                 + ",r=" + view.getRight()
    532                 + ",t=" + view.getTop()
    533                 + ",b=" + view.getBottom()
    534                 + ",w=" + view.getWidth()
    535                 + ",h=" + view.getHeight() + "}";
    536     }
    537 
    538     /**
    539      * Floors time to the given {@code timeUnit}. For example, if time is 5:32:11 and timeUnit is
    540      * one hour (60 * 60 * 1000), then the output will be 5:00:00.
    541      */
    542     public static long floorTime(long timeMs, long timeUnit) {
    543         return timeMs - (timeMs % timeUnit);
    544     }
    545 
    546     /**
    547      * Ceils time to the given {@code timeUnit}. For example, if time is 5:32:11 and timeUnit is
    548      * one hour (60 * 60 * 1000), then the output will be 6:00:00.
    549      */
    550     public static long ceilTime(long timeMs, long timeUnit) {
    551         return timeMs + timeUnit - (timeMs % timeUnit);
    552     }
    553 
    554     /**
    555      * Returns an {@link String#intern() interned} string or null if the input is null.
    556      */
    557     @Nullable
    558     public static String intern(@Nullable String string) {
    559         return string == null ? null : string.intern();
    560     }
    561 
    562     /**
    563      * Check if the index is valid for the collection,
    564      * @param collection the collection
    565      * @param index the index position to test
    566      * @return index >= 0 && index < collection.size().
    567      */
    568     public static boolean isIndexValid(@Nullable Collection<?> collection, int index) {
    569         return collection == null ? false : index >= 0 && index < collection.size();
    570     }
    571 
    572     /**
    573      * Returns a color integer associated with a particular resource ID.
    574      *
    575      * @see #getColor(android.content.res.Resources,int,Theme)
    576      */
    577     public static int getColor(Resources res, int id) {
    578         return getColor(res, id, null);
    579     }
    580 
    581     /**
    582      * Returns a color integer associated with a particular resource ID.
    583      *
    584      * <p>In M version, {@link android.content.res.Resources#getColor(int)} was deprecated and
    585      * {@link android.content.res.Resources#getColor(int,Theme)} was newly added.
    586      *
    587      * @see android.content.res.Resources#getColor(int)
    588      */
    589     public static int getColor(Resources res, int id, @Nullable Theme theme) {
    590         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
    591             return res.getColor(id, theme);
    592         } else {
    593             return res.getColor(id);
    594         }
    595     }
    596 
    597     /**
    598      * Returns a color state list associated with a particular resource ID.
    599      *
    600      * @see #getColorStateList(android.content.res.Resources,int,Theme)
    601      */
    602     public static ColorStateList getColorStateList(Resources res, int id) {
    603         return getColorStateList(res, id, null);
    604     }
    605 
    606     /**
    607      * Returns a color state list associated with a particular resource ID.
    608      *
    609      * <p>In M version, {@link android.content.res.Resources#getColorStateList(int)} was deprecated
    610      * and {@link android.content.res.Resources#getColorStateList(int,Theme)} was newly added.
    611      *
    612      * @see android.content.res.Resources#getColorStateList(int)
    613      */
    614     public static ColorStateList getColorStateList(Resources res, int id, @Nullable Theme theme) {
    615         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
    616             return res.getColorStateList(id, theme);
    617         } else {
    618             return res.getColorStateList(id);
    619         }
    620     }
    621 
    622     /**
    623      * Returns a localized version of the text resource specified by resourceId.
    624      */
    625     public static CharSequence getTextForLocale(Context context, Locale locale, int resourceId) {
    626         if (locale.equals(context.getResources().getConfiguration().locale)) {
    627             return context.getText(resourceId);
    628         }
    629         Configuration config = new Configuration(context.getResources().getConfiguration());
    630         config.setLocale(locale);
    631         return context.createConfigurationContext(config).getText(resourceId);
    632     }
    633 
    634     /**
    635      * Returns the internal TV inputs.
    636      */
    637     public static List<TvInputInfo> getInternalTvInputs(Context context, boolean tunerInputOnly) {
    638         List<TvInputInfo> inputs = new ArrayList<>();
    639         String contextPackageName = context.getPackageName();
    640         for (TvInputInfo input : TvApplication.getSingletons(context).getTvInputManagerHelper()
    641                 .getTvInputInfos(true, tunerInputOnly)) {
    642             if (contextPackageName.equals(ComponentName.unflattenFromString(input.getId())
    643                     .getPackageName())) {
    644                 inputs.add(input);
    645             }
    646         }
    647         return inputs;
    648     }
    649 
    650     /**
    651      * Checks whether the input is internal or not.
    652      */
    653     public static boolean isInternalTvInput(Context context, String inputId) {
    654         return context.getPackageName().equals(ComponentName.unflattenFromString(inputId)
    655                 .getPackageName());
    656     }
    657 
    658     /**
    659      * Shows a toast message to notice that the current feature is a developer feature.
    660      */
    661     public static void showToastMessageForDeveloperFeature(Context context) {
    662         Toast.makeText(context, "This feature is for developer preview.", Toast.LENGTH_SHORT)
    663                 .show();
    664     }
    665 }
    666