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 package com.android.tv.util;
17 
18 import android.content.Context;
19 import android.media.tv.TvTrackInfo;
20 import android.os.Build;
21 import android.os.LocaleList;
22 import android.text.TextUtils;
23 import android.util.Log;
24 
25 import com.android.tv.R;
26 
27 import com.google.common.collect.Iterables;
28 
29 import java.util.ArrayList;
30 import java.util.Comparator;
31 import java.util.HashSet;
32 import java.util.List;
33 import java.util.Locale;
34 import java.util.Objects;
35 import java.util.Set;
36 
37 /** Static utilities for {@link TvTrackInfo}. */
38 public class TvTrackInfoUtils {
39 
40     private static final String TAG = "TvTrackInfoUtils";
41     private static final int AUDIO_CHANNEL_NONE = 0;
42     private static final int AUDIO_CHANNEL_MONO = 1;
43     private static final int AUDIO_CHANNEL_STEREO = 2;
44     private static final int AUDIO_CHANNEL_SURROUND_6 = 6;
45     private static final int AUDIO_CHANNEL_SURROUND_8 = 8;
46 
47     /**
48      * Compares how closely two {@link android.media.tv.TvTrackInfo}s match {@code languages},
49      * {@code channelCount} and {@code id} in that precedence. A listed sorted with this comparator
50      * has the worst matches first.
51      *
52      * @param id The track id to match.
53      * @param languages The prioritized list of languages. Languages earlier in the list are a
54      *     better match.
55      * @param channelCount The channel count to match.
56      * @return -1 if lhs is a worse match, 0 if lhs and rhs match equally and 1 if lhs is a better
57      *     match.
58      */
createComparator( final String id, final List<String> languages, final int channelCount)59     public static Comparator<TvTrackInfo> createComparator(
60             final String id, final List<String> languages, final int channelCount) {
61         return (TvTrackInfo lhs, TvTrackInfo rhs) -> {
62             if (Objects.equals(lhs, rhs)) {
63                 return 0;
64             }
65             if (lhs == null) {
66                 return -1;
67             }
68             if (rhs == null) {
69                 return 1;
70             }
71             // Find the first language that matches the lhs and rhs tracks. The  earlier match is
72             // better. If there is no match set the index to the size of the language list since
73             // its the worst match.
74             int lhsLangIndex =
75                     Iterables.indexOf(languages, s -> Utils.isEqualLanguage(lhs.getLanguage(), s));
76             if (lhsLangIndex == -1) {
77                 lhsLangIndex = languages.size();
78             }
79             int rhsLangIndex =
80                     Iterables.indexOf(languages, s -> Utils.isEqualLanguage(rhs.getLanguage(), s));
81             if (rhsLangIndex == -1) {
82                 rhsLangIndex = languages.size();
83             }
84             if (lhsLangIndex != rhsLangIndex) {
85                 // Return the track with lower index as best
86                 return Integer.compare(rhsLangIndex, lhsLangIndex);
87             }
88             boolean lhsCountMatch =
89                     lhs.getType() != TvTrackInfo.TYPE_AUDIO
90                             || lhs.getAudioChannelCount() == channelCount;
91             boolean rhsCountMatch =
92                     rhs.getType() != TvTrackInfo.TYPE_AUDIO
93                             || rhs.getAudioChannelCount() == channelCount;
94             if (lhsCountMatch && rhsCountMatch) {
95                 return Boolean.compare(lhs.getId().equals(id), rhs.getId().equals(id));
96             } else if (lhsCountMatch || rhsCountMatch) {
97                 return Boolean.compare(lhsCountMatch, rhsCountMatch);
98             } else {
99                 return Integer.compare(lhs.getAudioChannelCount(), rhs.getAudioChannelCount());
100             }
101         };
102     }
103 
104     /**
105      * Selects the best TvTrackInfo available or the first if none matches.
106      *
107      * @param tracks The tracks to choose from
108      * @param id The track id to match.
109      * @param language The language to match.
110      * @param channelCount The channel count to match.
111      * @return the best matching track or the first one if none matches.
112      */
113     public static TvTrackInfo getBestTrackInfo(
114             List<TvTrackInfo> tracks, String id, String language, int channelCount) {
115         if (tracks == null) {
116             return null;
117         }
118         List<String> languages = new ArrayList<>();
119         if (language == null) {
120             if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
121                 LocaleList locales = LocaleList.getDefault();
122                 for (int i = 0; i < locales.size(); i++) {
123                     languages.add(locales.get(i).getLanguage());
124                 }
125             } else {
126                 languages.add(Locale.getDefault().getLanguage());
127             }
128         } else {
129             languages.add(language);
130         }
131         Comparator<TvTrackInfo> comparator = createComparator(id, languages, channelCount);
132         TvTrackInfo best = null;
133         for (TvTrackInfo track : tracks) {
134             if (comparator.compare(track, best) > 0) {
135                 best = track;
136             }
137         }
138         return best;
139     }
140 
141     public static boolean needToShowSampleRate(Context context, List<TvTrackInfo> tracks) {
142         Set<String> multiAudioStrings = new HashSet<>();
143         for (TvTrackInfo track : tracks) {
144             String multiAudioString = getMultiAudioString(context, track, false);
145             if (multiAudioStrings.contains(multiAudioString)) {
146                 return true;
147             }
148             multiAudioStrings.add(multiAudioString);
149         }
150         return false;
151     }
152 
153     public static String getMultiAudioString(
154             Context context, TvTrackInfo track, boolean showSampleRate) {
155         if (track.getType() != TvTrackInfo.TYPE_AUDIO) {
156             throw new IllegalArgumentException("Not an audio track: " + toString(track));
157         }
158         String language = context.getString(R.string.multi_audio_unknown_language);
159         if (!TextUtils.isEmpty(track.getLanguage())) {
160             language = new Locale(track.getLanguage()).getDisplayName();
161         } else {
162             Log.d(TAG, "No language information found for the audio track: " + toString(track));
163         }
164 
165         StringBuilder metadata = new StringBuilder();
166         switch (track.getAudioChannelCount()) {
167             case AUDIO_CHANNEL_NONE:
168                 break;
169             case AUDIO_CHANNEL_MONO:
170                 metadata.append(context.getString(R.string.multi_audio_channel_mono));
171                 break;
172             case AUDIO_CHANNEL_STEREO:
173                 metadata.append(context.getString(R.string.multi_audio_channel_stereo));
174                 break;
175             case AUDIO_CHANNEL_SURROUND_6:
176                 metadata.append(context.getString(R.string.multi_audio_channel_surround_6));
177                 break;
178             case AUDIO_CHANNEL_SURROUND_8:
179                 metadata.append(context.getString(R.string.multi_audio_channel_surround_8));
180                 break;
181             default:
182                 if (track.getAudioChannelCount() > 0) {
183                     metadata.append(
184                             context.getString(
185                                     R.string.multi_audio_channel_suffix,
186                                     track.getAudioChannelCount()));
187                 } else {
188                     Log.d(
189                             TAG,
190                             "Invalid audio channel count ("
191                                     + track.getAudioChannelCount()
192                                     + ") found for the audio track: "
193                                     + toString(track));
194                 }
195                 break;
196         }
197         if (showSampleRate) {
198             int sampleRate = track.getAudioSampleRate();
199             if (sampleRate > 0) {
200                 if (metadata.length() > 0) {
201                     metadata.append(", ");
202                 }
203                 int integerPart = sampleRate / 1000;
204                 int tenths = (sampleRate % 1000) / 100;
205                 metadata.append(integerPart);
206                 if (tenths != 0) {
207                     metadata.append(".");
208                     metadata.append(tenths);
209                 }
210                 metadata.append("kHz");
211             }
212         }
213 
214         if (metadata.length() == 0) {
215             return language;
216         }
217         return context.getString(
218                 R.string.multi_audio_display_string_with_channel, language, metadata.toString());
219     }
220 
221     private static String trackTypeToString(int trackType) {
222         switch (trackType) {
223             case TvTrackInfo.TYPE_AUDIO:
224                 return "Audio";
225             case TvTrackInfo.TYPE_VIDEO:
226                 return "Video";
227             case TvTrackInfo.TYPE_SUBTITLE:
228                 return "Subtitle";
229             default:
230                 return "Invalid Type";
231         }
232     }
233 
234     public static String toString(TvTrackInfo info) {
235         int trackType = info.getType();
236         return "TvTrackInfo{"
237                 + "type="
238                 + trackTypeToString(trackType)
239                 + ", id="
240                 + info.getId()
241                 + ", language="
242                 + info.getLanguage()
243                 + ", description="
244                 + info.getDescription()
245                 + (trackType == TvTrackInfo.TYPE_AUDIO
246                         ? (", audioChannelCount="
247                                 + info.getAudioChannelCount()
248                                 + ", audioSampleRate="
249                                 + info.getAudioSampleRate())
250                         : "")
251                 + (trackType == TvTrackInfo.TYPE_VIDEO
252                         ? (", videoWidth="
253                                 + info.getVideoWidth()
254                                 + ", videoHeight="
255                                 + info.getVideoHeight()
256                                 + ", videoFrameRate="
257                                 + info.getVideoFrameRate()
258                                 + ", videoPixelAspectRatio="
259                                 + info.getVideoPixelAspectRatio())
260                         : "")
261                 + "}";
262     }
263 
264     private TvTrackInfoUtils() {}
265 }
266