aboutsummaryrefslogtreecommitdiff
path: root/src/com/android/tv/util
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android/tv/util')
-rw-r--r--src/com/android/tv/util/AsyncDbTask.java4
-rw-r--r--src/com/android/tv/util/BitmapUtils.java10
-rw-r--r--src/com/android/tv/util/Debug.java60
-rw-r--r--src/com/android/tv/util/DurationTimer.java91
-rw-r--r--src/com/android/tv/util/ImageLoader.java3
-rw-r--r--src/com/android/tv/util/LocationUtils.java25
-rw-r--r--src/com/android/tv/util/NetworkTrafficTags.java64
-rw-r--r--src/com/android/tv/util/OnboardingUtils.java41
-rw-r--r--src/com/android/tv/util/Partner.java181
-rw-r--r--src/com/android/tv/util/PermissionUtils.java5
-rw-r--r--src/com/android/tv/util/PipInputManager.java432
-rw-r--r--src/com/android/tv/util/RecurringRunner.java9
-rw-r--r--src/com/android/tv/util/SearchManagerHelper.java61
-rw-r--r--src/com/android/tv/util/SetupUtils.java25
-rw-r--r--src/com/android/tv/util/StringUtils.java38
-rw-r--r--src/com/android/tv/util/TimeShiftUtils.java4
-rw-r--r--src/com/android/tv/util/TvInputManagerHelper.java358
-rw-r--r--src/com/android/tv/util/TvSettings.java160
-rw-r--r--src/com/android/tv/util/TvTrackInfoUtils.java37
-rw-r--r--src/com/android/tv/util/TvUriMatcher.java (renamed from src/com/android/tv/util/TvProviderUriMatcher.java)14
-rw-r--r--src/com/android/tv/util/Utils.java100
-rw-r--r--src/com/android/tv/util/ViewCache.java100
22 files changed, 1114 insertions, 708 deletions
diff --git a/src/com/android/tv/util/AsyncDbTask.java b/src/com/android/tv/util/AsyncDbTask.java
index 78243642..477412e4 100644
--- a/src/com/android/tv/util/AsyncDbTask.java
+++ b/src/com/android/tv/util/AsyncDbTask.java
@@ -31,7 +31,7 @@ import android.util.Range;
import com.android.tv.common.SoftPreconditions;
import com.android.tv.data.Channel;
import com.android.tv.data.Program;
-import com.android.tv.dvr.RecordedProgram;
+import com.android.tv.dvr.data.RecordedProgram;
import java.util.ArrayList;
import java.util.List;
@@ -76,7 +76,7 @@ public abstract class AsyncDbTask<Params, Progress, Result>
* accepted for execution
* @throws NullPointerException if command is null
*/
- public static void execute(Runnable command) {
+ public static void executeOnDbThread(Runnable command) {
DB_EXECUTOR.execute(command);
}
diff --git a/src/com/android/tv/util/BitmapUtils.java b/src/com/android/tv/util/BitmapUtils.java
index d45a8dce..fbaab023 100644
--- a/src/com/android/tv/util/BitmapUtils.java
+++ b/src/com/android/tv/util/BitmapUtils.java
@@ -24,6 +24,7 @@ import android.graphics.BitmapFactory;
import android.graphics.PorterDuff;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
+import android.net.TrafficStats;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.text.TextUtils;
@@ -56,6 +57,12 @@ public final class BitmapUtils {
return Bitmap.createScaledBitmap(bm, rect.right, rect.bottom, false);
}
+ public static Bitmap getScaledMutableBitmap(Bitmap bm, int maxWidth, int maxHeight) {
+ Bitmap scaledBitmap = scaleBitmap(bm, maxWidth, maxHeight);
+ return scaledBitmap.isMutable() ? scaledBitmap
+ : scaledBitmap.copy(Bitmap.Config.ARGB_8888, true);
+ }
+
private static Rect calculateNewSize(Bitmap bm, int maxWidth, int maxHeight) {
final double ratio = maxHeight / (double) maxWidth;
final double bmRatio = bm.getHeight() / (double) bm.getWidth();
@@ -89,6 +96,8 @@ public final class BitmapUtils {
boolean isResourceUri = isContentResolverUri(uri);
URLConnection urlConnection = null;
InputStream inputStream = null;
+ final int oldTag = TrafficStats.getThreadStatsTag();
+ TrafficStats.setThreadStatsTag(NetworkTrafficTags.LOGO_FETCHER);
try {
if (isResourceUri) {
inputStream = context.getContentResolver().openInputStream(uri);
@@ -142,6 +151,7 @@ public final class BitmapUtils {
return null;
} finally {
close(inputStream, urlConnection);
+ TrafficStats.setThreadStatsTag(oldTag);
}
}
diff --git a/src/com/android/tv/util/Debug.java b/src/com/android/tv/util/Debug.java
new file mode 100644
index 00000000..67a2683d
--- /dev/null
+++ b/src/com/android/tv/util/Debug.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tv.util;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A class only for help developers.
+ */
+public class Debug {
+ /**
+ * A threshold of start up time, when the start up time of Live TV is more than it,
+ * a warning will show to the developer.
+ */
+ public static final long TIME_START_UP_DURATION_THRESHOLD = TimeUnit.SECONDS.toMillis(6);
+ /**
+ * Tag for measuring start up time of Live TV.
+ */
+ public static final String TAG_START_UP_TIMER = "start_up_timer";
+
+ /**
+ * A global map for duration timers.
+ */
+ private final static Map<String, DurationTimer> sTimerMap = new HashMap<>();
+
+ /**
+ * Returns the global duration timer by tag.
+ */
+ public static DurationTimer getTimer(String tag) {
+ if (sTimerMap.get(tag) != null) {
+ return sTimerMap.get(tag);
+ }
+ DurationTimer timer = new DurationTimer(tag, true);
+ sTimerMap.put(tag, timer);
+ return timer;
+ }
+
+ /**
+ * Removes the global duration timer by tag.
+ */
+ public static DurationTimer removeTimer(String tag) {
+ return sTimerMap.remove(tag);
+ }
+}
diff --git a/src/com/android/tv/util/DurationTimer.java b/src/com/android/tv/util/DurationTimer.java
new file mode 100644
index 00000000..1f057bf6
--- /dev/null
+++ b/src/com/android/tv/util/DurationTimer.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tv.util;
+
+import android.os.SystemClock;
+import android.util.Log;
+
+import com.android.tv.common.BuildConfig;
+
+/**
+ * Times a duration.
+ */
+public final class DurationTimer {
+ private static final String TAG = "DurationTimer";
+ public static final long TIME_NOT_SET = -1;
+
+ private long mStartTimeMs = TIME_NOT_SET;
+ private String mTag = TAG;
+ private boolean mLogEngOnly;
+
+ public DurationTimer() { }
+
+ public DurationTimer(String tag, boolean logEngOnly) {
+ mTag = tag;
+ mLogEngOnly = logEngOnly;
+ }
+
+ /**
+ * Returns true if the timer is running.
+ */
+ public boolean isRunning() {
+ return mStartTimeMs != TIME_NOT_SET;
+ }
+
+ /**
+ * Start the timer.
+ */
+ public void start() {
+ mStartTimeMs = SystemClock.elapsedRealtime();
+ }
+
+ /**
+ * Returns true if timer is started.
+ */
+ public boolean isStarted() {
+ return mStartTimeMs != TIME_NOT_SET;
+ }
+
+ /**
+ * Returns the current duration in milliseconds or {@link #TIME_NOT_SET} if the timer is not
+ * running.
+ */
+ public long getDuration() {
+ return isRunning() ? SystemClock.elapsedRealtime() - mStartTimeMs : TIME_NOT_SET;
+ }
+
+ /**
+ * Stops the timer and resets its value to {@link #TIME_NOT_SET}.
+ *
+ * @return the current duration in milliseconds or {@link #TIME_NOT_SET} if the timer is not
+ * running.
+ */
+ public long reset() {
+ long duration = getDuration();
+ mStartTimeMs = TIME_NOT_SET;
+ return duration;
+ }
+
+ /**
+ * Adds information and duration time to the log.
+ */
+ public void log(String message) {
+ if (isRunning() && (!mLogEngOnly || BuildConfig.ENG)) {
+ Log.i(mTag, message + " : " + getDuration() + "ms");
+ }
+ }
+}
diff --git a/src/com/android/tv/util/ImageLoader.java b/src/com/android/tv/util/ImageLoader.java
index 04bb478a..86bb94c1 100644
--- a/src/com/android/tv/util/ImageLoader.java
+++ b/src/com/android/tv/util/ImageLoader.java
@@ -292,7 +292,8 @@ public final class ImageLoader {
* Checks if a reload would be needed if the results of other was available.
*/
private boolean isReloadNeeded(LoadBitmapTask other) {
- return mMaxHeight >= other.mMaxHeight * 2 || mMaxWidth >= other.mMaxWidth * 2;
+ return (other.mMaxHeight != Integer.MAX_VALUE && mMaxHeight >= other.mMaxHeight * 2)
+ || (other.mMaxWidth != Integer.MAX_VALUE && mMaxWidth >= other.mMaxWidth * 2);
}
@Nullable
diff --git a/src/com/android/tv/util/LocationUtils.java b/src/com/android/tv/util/LocationUtils.java
index 8e3b59e9..d5d7bee3 100644
--- a/src/com/android/tv/util/LocationUtils.java
+++ b/src/com/android/tv/util/LocationUtils.java
@@ -16,15 +16,20 @@
package com.android.tv.util;
+import android.Manifest;
import android.content.Context;
+import android.content.pm.PackageManager;
import android.location.Address;
import android.location.Geocoder;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.text.TextUtils;
import android.util.Log;
+import com.android.tv.tuner.util.PostalCodeUtils;
import java.io.IOException;
import java.util.List;
@@ -39,6 +44,7 @@ public class LocationUtils {
private static Context sApplicationContext;
private static Address sAddress;
+ private static String sCountry;
private static IOException sError;
/**
@@ -59,6 +65,18 @@ public class LocationUtils {
return null;
}
+ /** Returns the current country. */
+ @NonNull
+ public static synchronized String getCurrentCountry(Context context) {
+ if (sCountry != null) {
+ return sCountry;
+ }
+ if (TextUtils.isEmpty(sCountry)) {
+ sCountry = context.getResources().getConfiguration().locale.getCountry();
+ }
+ return sCountry;
+ }
+
private static void updateAddress(Location location) {
if (DEBUG) Log.d(TAG, "Updating address with " + location);
if (location == null) {
@@ -68,9 +86,14 @@ public class LocationUtils {
try {
List<Address> addresses = geocoder.getFromLocation(
location.getLatitude(), location.getLongitude(), 1);
- if (addresses != null) {
+ if (addresses != null && !addresses.isEmpty()) {
sAddress = addresses.get(0);
if (DEBUG) Log.d(TAG, "Got " + sAddress);
+ try {
+ PostalCodeUtils.updatePostalCode(sApplicationContext);
+ } catch (Exception e) {
+ // Do nothing
+ }
} else {
if (DEBUG) Log.d(TAG, "No address returned");
}
diff --git a/src/com/android/tv/util/NetworkTrafficTags.java b/src/com/android/tv/util/NetworkTrafficTags.java
new file mode 100644
index 00000000..2dca613c
--- /dev/null
+++ b/src/com/android/tv/util/NetworkTrafficTags.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.tv.util;
+
+import android.net.TrafficStats;
+import android.support.annotation.NonNull;
+
+import java.util.concurrent.Executor;
+
+/** Constants for tagging network traffic in the Live channels app. */
+public final class NetworkTrafficTags {
+
+ public static final int DEFAULT_LIVE_CHANNELS = 1;
+ public static final int LOGO_FETCHER = 2;
+ public static final int HDHOMERUN = 3;
+ public static final int EPG_FETCH = 4;
+
+ /**
+ * An executor which simply wraps a provided delegate executor, but calls {@link
+ * TrafficStats#setThreadStatsTag(int)} before executing any task.
+ */
+ public static class TrafficStatsTaggingExecutor implements Executor {
+ private final Executor delegateExecutor;
+ private final int tag;
+
+ public TrafficStatsTaggingExecutor(Executor delegateExecutor, int tag) {
+ this.delegateExecutor = delegateExecutor;
+ this.tag = tag;
+ }
+
+ @Override
+ public void execute(final @NonNull Runnable command) {
+ // TODO(b/62038127): robolectric does not support lamdas in unbundled apps
+ delegateExecutor.execute(
+ new Runnable() {
+ @Override
+ public void run() {
+ TrafficStats.setThreadStatsTag(tag);
+ try {
+ command.run();
+ } finally {
+ TrafficStats.clearThreadStatsTag();
+ }
+ }
+ });
+ }
+ }
+
+ private NetworkTrafficTags() {}
+}
diff --git a/src/com/android/tv/util/OnboardingUtils.java b/src/com/android/tv/util/OnboardingUtils.java
index 3040020e..49b02b82 100644
--- a/src/com/android/tv/util/OnboardingUtils.java
+++ b/src/com/android/tv/util/OnboardingUtils.java
@@ -16,17 +16,10 @@
package com.android.tv.util;
-import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
-import android.database.Cursor;
-import android.media.tv.TvContract.Channels;
import android.net.Uri;
import android.preference.PreferenceManager;
-import android.support.annotation.UiThread;
-
-import com.android.tv.TvApplication;
-import com.android.tv.data.ChannelDataManager;
/**
* A utility class related to onboarding experience.
@@ -82,41 +75,11 @@ public final class OnboardingUtils {
}
/**
- * Checks whether the onboarding screen should be shown or not.
- */
- public static boolean needToShowOnboarding(Context context) {
- return isFirstRunWithCurrentVersion(context) || !areChannelsAvailable(context);
- }
-
- /**
- * Checks if there are any available tuner channels.
- */
- @UiThread
- public static boolean areChannelsAvailable(Context context) {
- ChannelDataManager manager = TvApplication.getSingletons(context).getChannelDataManager();
- if (manager.isDbLoadFinished()) {
- return manager.getChannelCount() != 0;
- }
- // This method should block the UI thread.
- ContentResolver resolver = context.getContentResolver();
- try (Cursor c = resolver.query(Channels.CONTENT_URI, new String[] {Channels._ID}, null,
- null, null)) {
- return c != null && c.getCount() != 0;
- }
- }
-
- /**
- * Checks if there are any available TV inputs.
- */
- public static boolean areInputsAvailable(Context context) {
- return TvApplication.getSingletons(context).getTvInputManagerHelper()
- .getTvInputInfos(true, false).size() > 0;
- }
-
- /**
* Returns merchant collection URL.
*/
private static String getMerchantCollectionUrl() {
return "TODO: add a merchant collection url";
}
+
+ private OnboardingUtils() {}
}
diff --git a/src/com/android/tv/util/Partner.java b/src/com/android/tv/util/Partner.java
new file mode 100644
index 00000000..e3688392
--- /dev/null
+++ b/src/com/android/tv/util/Partner.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tv.util;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.res.Resources;
+import android.media.tv.TvInputInfo;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * This file refers to Partner.java in LeanbackLauncher. Interact with partner customizations. There
+ * can only be one set of customizations on a device, and it must be bundled with the system.
+ */
+public class Partner {
+ private static final String TAG = "Partner";
+ /** Marker action used to discover partner */
+ private static final String ACTION_PARTNER_CUSTOMIZATION =
+ "com.google.android.leanbacklauncher.action.PARTNER_CUSTOMIZATION";
+
+ /** ID tags for device input types */
+ public static final String INPUT_TYPE_BUNDLED_TUNER = "input_type_combined_tuners";
+ public static final String INPUT_TYPE_TUNER = "input_type_tuner";
+ public static final String INPUT_TYPE_CEC_LOGICAL = "input_type_cec_logical";
+ public static final String INPUT_TYPE_CEC_RECORDER = "input_type_cec_recorder";
+ public static final String INPUT_TYPE_CEC_PLAYBACK = "input_type_cec_playback";
+ public static final String INPUT_TYPE_MHL_MOBILE = "input_type_mhl_mobile";
+ public static final String INPUT_TYPE_HDMI = "input_type_hdmi";
+ public static final String INPUT_TYPE_DVI = "input_type_dvi";
+ public static final String INPUT_TYPE_COMPONENT = "input_type_component";
+ public static final String INPUT_TYPE_SVIDEO = "input_type_svideo";
+ public static final String INPUT_TYPE_COMPOSITE = "input_type_composite";
+ public static final String INPUT_TYPE_DISPLAY_PORT = "input_type_displayport";
+ public static final String INPUT_TYPE_VGA = "input_type_vga";
+ public static final String INPUT_TYPE_SCART = "input_type_scart";
+ public static final String INPUT_TYPE_OTHER = "input_type_other";
+
+ private static final String INPUTS_ORDER = "home_screen_inputs_ordering";
+ private static final String TYPE_ARRAY = "array";
+
+ private static Partner sPartner;
+ private static final Object sLock = new Object();
+
+ private final String mPackageName;
+ private final String mReceiverName;
+ private final Resources mResources;
+
+ private static final Map<String, Integer> INPUT_TYPE_MAP = new HashMap<>();
+ static {
+ INPUT_TYPE_MAP.put(INPUT_TYPE_BUNDLED_TUNER, TvInputManagerHelper.TYPE_BUNDLED_TUNER);
+ INPUT_TYPE_MAP.put(INPUT_TYPE_TUNER, TvInputInfo.TYPE_TUNER);
+ INPUT_TYPE_MAP.put(INPUT_TYPE_CEC_LOGICAL, TvInputManagerHelper.TYPE_CEC_DEVICE);
+ INPUT_TYPE_MAP.put(INPUT_TYPE_CEC_RECORDER, TvInputManagerHelper.TYPE_CEC_DEVICE_RECORDER);
+ INPUT_TYPE_MAP.put(INPUT_TYPE_CEC_PLAYBACK, TvInputManagerHelper.TYPE_CEC_DEVICE_PLAYBACK);
+ INPUT_TYPE_MAP.put(INPUT_TYPE_MHL_MOBILE, TvInputManagerHelper.TYPE_MHL_MOBILE);
+ INPUT_TYPE_MAP.put(INPUT_TYPE_HDMI, TvInputInfo.TYPE_HDMI);
+ INPUT_TYPE_MAP.put(INPUT_TYPE_DVI, TvInputInfo.TYPE_DVI);
+ INPUT_TYPE_MAP.put(INPUT_TYPE_COMPONENT, TvInputInfo.TYPE_COMPONENT);
+ INPUT_TYPE_MAP.put(INPUT_TYPE_SVIDEO, TvInputInfo.TYPE_SVIDEO);
+ INPUT_TYPE_MAP.put(INPUT_TYPE_COMPOSITE, TvInputInfo.TYPE_COMPOSITE);
+ INPUT_TYPE_MAP.put(INPUT_TYPE_DISPLAY_PORT, TvInputInfo.TYPE_DISPLAY_PORT);
+ INPUT_TYPE_MAP.put(INPUT_TYPE_VGA, TvInputInfo.TYPE_VGA);
+ INPUT_TYPE_MAP.put(INPUT_TYPE_SCART, TvInputInfo.TYPE_SCART);
+ INPUT_TYPE_MAP.put(INPUT_TYPE_OTHER, TvInputInfo.TYPE_OTHER);
+ }
+
+ private Partner(String packageName, String receiverName, Resources res) {
+ mPackageName = packageName;
+ mReceiverName = receiverName;
+ mResources = res;
+ }
+
+ /** Returns partner instance. */
+ public static Partner getInstance(Context context) {
+ PackageManager pm = context.getPackageManager();
+ synchronized (sLock) {
+ ResolveInfo info = getPartnerResolveInfo(pm);
+ if (info != null) {
+ final String packageName = info.activityInfo.packageName;
+ final String receiverName = info.activityInfo.name;
+ try {
+ final Resources res = pm.getResourcesForApplication(packageName);
+ sPartner = new Partner(packageName, receiverName, res);
+ sPartner.sendInitBroadcast(context);
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.w(TAG, "Failed to find resources for " + packageName);
+ }
+ }
+ if (sPartner == null) {
+ sPartner = new Partner(null, null, null);
+ }
+ }
+ return sPartner;
+ }
+
+ /** Resets the Partner instance to handle the partner package has changed. */
+ public static void reset(Context context, String packageName) {
+ synchronized (sLock) {
+ if (sPartner != null && !TextUtils.isEmpty(packageName)) {
+ if (packageName.equals(sPartner.mPackageName)) {
+ // Force a refresh, so we send an Init to the updated package
+ sPartner = null;
+ getInstance(context);
+ }
+ }
+ }
+ }
+
+ /** This method is used to send init broadcast to the new/changed partner package. */
+ private void sendInitBroadcast(Context context) {
+ if (!TextUtils.isEmpty(mPackageName) && !TextUtils.isEmpty(mReceiverName)) {
+ Intent intent = new Intent(ACTION_PARTNER_CUSTOMIZATION);
+ final ComponentName componentName = new ComponentName(mPackageName, mReceiverName);
+ intent.setComponent(componentName);
+ intent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+ context.sendBroadcast(intent);
+ }
+ }
+
+ /** Returns the order of inputs. */
+ public Map<Integer, Integer> getInputsOrderMap() {
+ HashMap<Integer, Integer> map = new HashMap<>();
+ if (mResources != null && !TextUtils.isEmpty(mPackageName)) {
+ String[] inputsArray = null;
+ final int resId = mResources.getIdentifier(INPUTS_ORDER, TYPE_ARRAY, mPackageName);
+ if (resId != 0) {
+ inputsArray = mResources.getStringArray(resId);
+ }
+ if (inputsArray != null) {
+ int priority = 0;
+ for (String input : inputsArray) {
+ Integer type = INPUT_TYPE_MAP.get(input);
+ if (type != null) {
+ map.put(type, priority++);
+ }
+ }
+ }
+ }
+ return map;
+ }
+
+ private static ResolveInfo getPartnerResolveInfo(PackageManager pm) {
+ final Intent intent = new Intent(ACTION_PARTNER_CUSTOMIZATION);
+ ResolveInfo partnerInfo = null;
+ for (ResolveInfo info : pm.queryBroadcastReceivers(intent, 0)) {
+ if (isSystemApp(info)) {
+ partnerInfo = info;
+ break;
+ }
+ }
+ return partnerInfo;
+ }
+
+ protected static boolean isSystemApp(ResolveInfo info) {
+ return (info.activityInfo != null
+ && info.activityInfo.applicationInfo != null
+ && (info.activityInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0);
+ }
+}
diff --git a/src/com/android/tv/util/PermissionUtils.java b/src/com/android/tv/util/PermissionUtils.java
index 453885a4..a355be99 100644
--- a/src/com/android/tv/util/PermissionUtils.java
+++ b/src/com/android/tv/util/PermissionUtils.java
@@ -47,4 +47,9 @@ public class PermissionUtils {
return context.checkSelfPermission(PERMISSION_READ_TV_LISTINGS)
== PackageManager.PERMISSION_GRANTED;
}
+
+ public static boolean hasInternet(Context context) {
+ return context.checkSelfPermission("android.permission.INTERNET")
+ == PackageManager.PERMISSION_GRANTED;
+ }
}
diff --git a/src/com/android/tv/util/PipInputManager.java b/src/com/android/tv/util/PipInputManager.java
deleted file mode 100644
index 2c51d5a0..00000000
--- a/src/com/android/tv/util/PipInputManager.java
+++ /dev/null
@@ -1,432 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tv.util;
-
-import android.content.Context;
-import android.media.tv.TvInputInfo;
-import android.media.tv.TvInputManager;
-import android.media.tv.TvInputManager.TvInputCallback;
-import android.util.ArraySet;
-import android.util.Log;
-
-import com.android.tv.ChannelTuner;
-import com.android.tv.R;
-import com.android.tv.data.Channel;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * A class that manages inputs for PIP. All tuner inputs are represented to one tuner input for PIP.
- * Hidden inputs should not be visible to the users.
- */
-public class PipInputManager {
- private static final String TAG = "PipInputManager";
-
- // Tuner inputs aren't distinguished each other in PipInput. They are handled as one input.
- // Therefore, we define a fake input id for the unified input.
- private static final String TUNER_INPUT_ID = "tuner_input_id";
-
- private final Context mContext;
- private final TvInputManagerHelper mInputManager;
- private final ChannelTuner mChannelTuner;
- private boolean mStarted;
- private final Map<String, PipInput> mPipInputMap = new HashMap<>(); // inputId -> PipInput
- private final Set<Listener> mListeners = new ArraySet<>();
-
- private final TvInputCallback mTvInputCallback = new TvInputCallback() {
- @Override
- public void onInputAdded(String inputId) {
- TvInputInfo input = mInputManager.getTvInputInfo(inputId);
- if (input.isPassthroughInput()) {
- boolean available = mInputManager.getInputState(input)
- == TvInputManager.INPUT_STATE_CONNECTED;
- mPipInputMap.put(inputId, new PipInput(inputId, available));
- } else if (!mPipInputMap.containsKey(TUNER_INPUT_ID)) {
- boolean available = mChannelTuner.getBrowsableChannelCount() != 0;
- mPipInputMap.put(TUNER_INPUT_ID, new PipInput(TUNER_INPUT_ID, available));
- } else {
- return;
- }
- for (Listener l : mListeners) {
- l.onPipInputListUpdated();
- }
- }
-
- @Override
- public void onInputRemoved(String inputId) {
- PipInput pipInput = mPipInputMap.remove(inputId);
- if (pipInput == null) {
- if (!mPipInputMap.containsKey(TUNER_INPUT_ID)) {
- Log.w(TAG, "A TV input (" + inputId + ") isn't tracked in PipInputManager");
- return;
- }
- if (mInputManager.getTunerTvInputSize() > 0) {
- return;
- }
- mPipInputMap.remove(TUNER_INPUT_ID);
- }
- for (Listener l : mListeners) {
- l.onPipInputListUpdated();
- }
- }
-
- @Override
- public void onInputStateChanged(String inputId, int state) {
- PipInput pipInput = mPipInputMap.get(inputId);
- if (pipInput == null) {
- // For tuner input, state change is handled in mChannelTunerListener.
- return;
- }
- pipInput.updateAvailability();
- }
- };
-
- private final ChannelTuner.Listener mChannelTunerListener = new ChannelTuner.Listener() {
- @Override
- public void onLoadFinished() { }
-
- @Override
- public void onCurrentChannelUnavailable(Channel channel) { }
-
- @Override
- public void onBrowsableChannelListChanged() {
- PipInput tunerInput = mPipInputMap.get(TUNER_INPUT_ID);
- if (tunerInput == null) {
- return;
- }
- tunerInput.updateAvailability();
- }
-
- @Override
- public void onChannelChanged(Channel previousChannel, Channel currentChannel) {
- if (previousChannel != null && currentChannel != null
- && !previousChannel.isPassthrough() && !currentChannel.isPassthrough()) {
- // Channel change between channels for tuner inputs.
- return;
- }
- PipInput previousMainInput = getPipInput(previousChannel);
- if (previousMainInput != null) {
- previousMainInput.updateAvailability();
- }
- PipInput currentMainInput = getPipInput(currentChannel);
- if (currentMainInput != null) {
- currentMainInput.updateAvailability();
- }
- }
- };
-
- public PipInputManager(Context context, TvInputManagerHelper inputManager,
- ChannelTuner channelTuner) {
- mContext = context;
- mInputManager = inputManager;
- mChannelTuner = channelTuner;
- }
-
- /**
- * Starts {@link PipInputManager}.
- */
- public void start() {
- if (mStarted) {
- return;
- }
- mStarted = true;
- mInputManager.addCallback(mTvInputCallback);
- mChannelTuner.addListener(mChannelTunerListener);
- initializePipInputList();
- }
-
- /**
- * Stops {@link PipInputManager}.
- */
- public void stop() {
- if (!mStarted) {
- return;
- }
- mStarted = false;
- mInputManager.removeCallback(mTvInputCallback);
- mChannelTuner.removeListener(mChannelTunerListener);
- mPipInputMap.clear();
- }
-
- /**
- * Adds a {@link PipInputManager.Listener}.
- */
- public void addListener(Listener listener) {
- mListeners.add(listener);
- }
-
- /**
- * Removes a {@link PipInputManager.Listener}.
- */
- public void removeListener(Listener listener) {
- mListeners.remove(listener);
- }
-
- /**
- * Gets the size of inputs for PIP.
- *
- * <p>The hidden inputs are not counted.
- *
- * @param availableOnly If {@code true}, it counts only available PIP inputs. Please see {@link
- * PipInput#isAvailable()} for the details of availability.
- */
- public int getPipInputSize(boolean availableOnly) {
- int count = 0;
- for (PipInput pipInput : mPipInputMap.values()) {
- if (!pipInput.isHidden() && (!availableOnly || pipInput.mAvailable)) {
- ++count;
- }
- if (pipInput.isPassthrough()) {
- TvInputInfo info = pipInput.getInputInfo();
- // Do not count HDMI ports if a CEC device is directly connected to the port.
- if (info.getParentId() != null && !info.isConnectedToHdmiSwitch()) {
- --count;
- }
- }
- }
- return count;
- }
-
- /**
- * Gets the list of inputs for PIP..
- *
- * <p>The hidden inputs are excluded.
- *
- * @param availableOnly If true, it returns only available PIP inputs. Please see {@link
- * PipInput#isAvailable()} for the details of availability.
- */
- public List<PipInput> getPipInputList(boolean availableOnly) {
- List<PipInput> pipInputs = new ArrayList<>();
- List<PipInput> removeInputs = new ArrayList<>();
- for (PipInput pipInput : mPipInputMap.values()) {
- if (!pipInput.isHidden() && (!availableOnly || pipInput.mAvailable)) {
- pipInputs.add(pipInput);
- }
- if (pipInput.isPassthrough()) {
- TvInputInfo info = pipInput.getInputInfo();
- // Do not show HDMI ports if a CEC device is directly connected to the port.
- if (info.getParentId() != null && !info.isConnectedToHdmiSwitch()) {
- removeInputs.add(mPipInputMap.get(info.getParentId()));
- }
- }
- }
- if (!removeInputs.isEmpty()) {
- pipInputs.removeAll(removeInputs);
- }
- Collections.sort(pipInputs, new Comparator<PipInput>() {
- @Override
- public int compare(PipInput lhs, PipInput rhs) {
- if (!lhs.mIsPassthrough) {
- return -1;
- }
- if (!rhs.mIsPassthrough) {
- return 1;
- }
- String a = lhs.getLabel();
- String b = rhs.getLabel();
- return a.compareTo(b);
- }
- });
- return pipInputs;
- }
-
- /**
- * Returns an PIP input corresponding to {@code channel}.
- */
- public PipInput getPipInput(Channel channel) {
- if (channel == null) {
- return null;
- }
- if (channel.isPassthrough()) {
- return mPipInputMap.get(channel.getInputId());
- } else {
- return mPipInputMap.get(TUNER_INPUT_ID);
- }
- }
-
- /**
- * Returns true, if {@code channel1} and {@code channel2} belong to the same input. For example,
- * two channels from different tuner inputs are also in the same input "Tuner" from PIP
- * point of view.
- */
- public boolean areInSamePipInput(Channel channel1, Channel channel2) {
- PipInput input1 = getPipInput(channel1);
- PipInput input2 = getPipInput(channel2);
- return input1 != null && input2 != null
- && getPipInput(channel1).equals(getPipInput(channel2));
- }
-
- private void initializePipInputList() {
- boolean hasTunerInput = false;
- for (TvInputInfo input : mInputManager.getTvInputInfos(false, false)) {
- if (input.isPassthroughInput()) {
- boolean available = mInputManager.getInputState(input)
- == TvInputManager.INPUT_STATE_CONNECTED;
- mPipInputMap.put(input.getId(), new PipInput(input.getId(), available));
- } else if (!hasTunerInput) {
- hasTunerInput = true;
- boolean available = mChannelTuner.getBrowsableChannelCount() != 0;
- mPipInputMap.put(TUNER_INPUT_ID, new PipInput(TUNER_INPUT_ID, available));
- }
- }
- PipInput input = getPipInput(mChannelTuner.getCurrentChannel());
- if (input != null) {
- input.updateAvailability();
- }
- for (Listener l : mListeners) {
- l.onPipInputListUpdated();
- }
- }
-
- /**
- * Listeners to notify PIP input state changes.
- */
- public interface Listener {
- /**
- * Called when the state (availability) of PIP inputs is changed.
- */
- void onPipInputStateUpdated();
-
- /**
- * Called when the list of PIP inputs is changed.
- */
- void onPipInputListUpdated();
- }
-
- /**
- * Input class for PIP. It has useful methods for PIP handling.
- */
- public class PipInput {
- private final String mInputId;
- private final boolean mIsPassthrough;
- private final TvInputInfo mInputInfo;
- private boolean mAvailable;
-
- private PipInput(String inputId, boolean available) {
- mInputId = inputId;
- mIsPassthrough = !mInputId.equals(TUNER_INPUT_ID);
- if (mIsPassthrough) {
- mInputInfo = mInputManager.getTvInputInfo(mInputId);
- } else {
- mInputInfo = null;
- }
- mAvailable = available;
- }
-
- /**
- * Returns the {@link TvInputInfo} object that matches to this PIP input.
- */
- public TvInputInfo getInputInfo() {
- return mInputInfo;
- }
-
- /**
- * Returns {@code true}, if the input is available for PIP. If a channel of an input is
- * already played or an input is not connected state or there is no browsable channel, the
- * input is unavailable.
- */
- public boolean isAvailable() {
- return mAvailable;
- }
-
- /**
- * Returns true, if the input is a passthrough TV input.
- */
- public boolean isPassthrough() {
- return mIsPassthrough;
- }
-
- /**
- * Gets a channel to play in a PIP view.
- */
- public Channel getChannel() {
- if (mIsPassthrough) {
- return Channel.createPassthroughChannel(mInputId);
- } else {
- return mChannelTuner.findNearestBrowsableChannel(
- Utils.getLastWatchedChannelId(mContext));
- }
- }
-
- /**
- * Gets a label of the input.
- */
- public String getLabel() {
- if (mIsPassthrough) {
- return mInputInfo.loadLabel(mContext).toString();
- } else {
- return mContext.getString(R.string.input_selector_tuner_label);
- }
- }
-
- /**
- * Gets a long label including a customized label.
- */
- public String getLongLabel() {
- if (mIsPassthrough) {
- String customizedLabel = Utils.loadLabel(mContext, mInputInfo);
- String label = getLabel();
- if (label.equals(customizedLabel)) {
- return customizedLabel;
- }
- return customizedLabel + " (" + label + ")";
- } else {
- return mContext.getString(R.string.input_long_label_for_tuner);
- }
- }
-
- /**
- * Updates availability. It returns true, if availability is changed.
- */
- private void updateAvailability() {
- boolean available;
- // current playing input cannot be available for PIP.
- Channel currentChannel = mChannelTuner.getCurrentChannel();
- if (mIsPassthrough) {
- if (currentChannel != null && currentChannel.getInputId().equals(mInputId)) {
- available = false;
- } else {
- available = mInputManager.getInputState(mInputId)
- == TvInputManager.INPUT_STATE_CONNECTED;
- }
- } else {
- if (currentChannel != null && !currentChannel.isPassthrough()) {
- available = false;
- } else {
- available = mChannelTuner.getBrowsableChannelCount() > 0;
- }
- }
- if (mAvailable != available) {
- mAvailable = available;
- for (Listener l : mListeners) {
- l.onPipInputStateUpdated();
- }
- }
- }
-
- private boolean isHidden() {
- // mInputInfo is null for the tuner input and it's always visible.
- return mInputInfo != null && mInputInfo.isHidden(mContext);
- }
- }
-}
diff --git a/src/com/android/tv/util/RecurringRunner.java b/src/com/android/tv/util/RecurringRunner.java
index 4135bd4e..8b45131b 100644
--- a/src/com/android/tv/util/RecurringRunner.java
+++ b/src/com/android/tv/util/RecurringRunner.java
@@ -57,12 +57,15 @@ public final class RecurringRunner {
mHandler = new Handler(mContext.getMainLooper());
}
- public void start() {
+ public void start(boolean resetNextRunTime) {
SoftPreconditions.checkState(!mRunning, TAG, mName + " start is called twice.");
if (mRunning) {
return;
}
mRunning = true;
+ if (resetNextRunTime) {
+ resetNextRunTime();
+ }
new AsyncTask<Void, Void, Long>() {
@Override
protected Long doInBackground(Void... params) {
@@ -76,6 +79,10 @@ public final class RecurringRunner {
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
+ public void start() {
+ start(false);
+ }
+
public void stop() {
mRunning = false;
mHandler.removeCallbacksAndMessages(null);
diff --git a/src/com/android/tv/util/SearchManagerHelper.java b/src/com/android/tv/util/SearchManagerHelper.java
deleted file mode 100644
index b6e34d7a..00000000
--- a/src/com/android/tv/util/SearchManagerHelper.java
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tv.util;
-
-import android.app.SearchManager;
-import android.content.Context;
-import android.os.Bundle;
-import android.os.UserHandle;
-import android.util.Log;
-
-import java.lang.reflect.InvocationTargetException;
-
-/**
- * A convenience class for calling methods in android.app.SearchManager.
- */
-public final class SearchManagerHelper {
- private static final String TAG = "SearchManagerHelper";
-
- private static final Object sLock = new Object();
- private static SearchManagerHelper sInstance;
-
- private final SearchManager mSearchManager;
-
- private SearchManagerHelper(Context context) {
- mSearchManager = ((android.app.SearchManager) context.getSystemService(
- Context.SEARCH_SERVICE));
- }
-
- public static SearchManagerHelper getInstance(Context context) {
- synchronized (sLock) {
- if (sInstance == null) {
- sInstance = new SearchManagerHelper(context.getApplicationContext());
- }
- return sInstance;
- }
- }
-
- public void launchAssistAction() {
- try {
- SearchManager.class.getDeclaredMethod("launchLegacyAssist", String.class, Integer.TYPE,
- Bundle.class).invoke(mSearchManager, null, UserHandle.myUserId(), null);
- } catch (NoSuchMethodException | IllegalArgumentException | IllegalAccessException
- | InvocationTargetException e) {
- Log.e(TAG, "Fail to call SearchManager.launchAssistAction", e);
- }
- }
-}
diff --git a/src/com/android/tv/util/SetupUtils.java b/src/com/android/tv/util/SetupUtils.java
index 8223a81c..32e3a81f 100644
--- a/src/com/android/tv/util/SetupUtils.java
+++ b/src/com/android/tv/util/SetupUtils.java
@@ -37,8 +37,6 @@ import com.android.tv.TvApplication;
import com.android.tv.common.SoftPreconditions;
import com.android.tv.data.Channel;
import com.android.tv.data.ChannelDataManager;
-import com.android.tv.data.epg.EpgFetcher;
-import com.android.tv.experiments.Experiments;
import com.android.tv.tuner.tvinput.TunerTvInputService;
import java.util.Collections;
@@ -114,7 +112,7 @@ public class SetupUtils {
@Override
public void onLoadFinished() {
manager.removeListener(this);
- updateChannelBrowsable(mTvApplication, inputId, postRunnable);
+ updateChannelsAfterSetup(mTvApplication, inputId, postRunnable);
}
@Override
@@ -124,17 +122,18 @@ public class SetupUtils {
public void onChannelBrowsableChanged() { }
});
} else {
- updateChannelBrowsable(mTvApplication, inputId, postRunnable);
+ updateChannelsAfterSetup(mTvApplication, inputId, postRunnable);
}
}
- private static void updateChannelBrowsable(Context context, final String inputId,
+ private static void updateChannelsAfterSetup(Context context, final String inputId,
final Runnable postRunnable) {
ApplicationSingletons appSingletons = TvApplication.getSingletons(context);
final ChannelDataManager manager = appSingletons.getChannelDataManager();
manager.updateChannels(new Runnable() {
@Override
public void run() {
+ Channel firstChannelForInput = null;
boolean browsableChanged = false;
for (Channel channel : manager.getChannelList()) {
if (channel.getInputId().equals(inputId)) {
@@ -142,8 +141,14 @@ public class SetupUtils {
manager.updateBrowsable(channel.getId(), true, true);
browsableChanged = true;
}
+ if (firstChannelForInput == null) {
+ firstChannelForInput = channel;
+ }
}
}
+ if (firstChannelForInput != null) {
+ Utils.setLastWatchedChannel(context, firstChannelForInput);
+ }
if (browsableChanged) {
manager.notifyChannelBrowsableChanged();
manager.applyUpdatedValuesToDb();
@@ -382,13 +387,5 @@ public class SetupUtils {
mSetUpInputs.add(inputId);
mSharedPreferences.edit().putStringSet(PREF_KEY_SET_UP_INPUTS, mSetUpInputs).apply();
}
- // Start fetching program guide data for internal tuners.
- Context context = mTvApplication.getApplicationContext();
- if (Utils.isInternalTvInput(context, inputId)) {
- if (context.checkSelfPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION)
- == PackageManager.PERMISSION_GRANTED && Experiments.CLOUD_EPG.get()) {
- EpgFetcher.getInstance(context).startImmediately();
- }
- }
}
-}
+} \ No newline at end of file
diff --git a/src/com/android/tv/util/StringUtils.java b/src/com/android/tv/util/StringUtils.java
new file mode 100644
index 00000000..659807e2
--- /dev/null
+++ b/src/com/android/tv/util/StringUtils.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tv.util;
+
+/**
+ * Utility class for handling {@link String}.
+ */
+public final class StringUtils {
+
+ private StringUtils() { }
+
+ /**
+ * Returns compares two strings lexicographically and handles null values quietly.
+ */
+ public static int compare(String a, String b) {
+ if (a == null) {
+ return b == null ? 0 : -1;
+ }
+ if (b == null) {
+ return 1;
+ }
+ return a.compareTo(b);
+ }
+}
diff --git a/src/com/android/tv/util/TimeShiftUtils.java b/src/com/android/tv/util/TimeShiftUtils.java
index 238d0e74..8038a78f 100644
--- a/src/com/android/tv/util/TimeShiftUtils.java
+++ b/src/com/android/tv/util/TimeShiftUtils.java
@@ -18,7 +18,6 @@ package com.android.tv.util;
import java.util.concurrent.TimeUnit;
-// TODO: move related functions in TimeShiftManger here.
/**
* A class that includes convenience methods for time shift plays.
*/
@@ -40,7 +39,7 @@ public class TimeShiftUtils {
* Returns real speeds used in time shift play. This method is only for fast-forwarding and
* rewinding. The normal play speed is not addressed here.
*
- * @param speedLevel the valid value is ranged from 0 to {@link MAX_SPPED_LEVEL}.
+ * @param speedLevel the valid value is ranged from 0 to {@link #MAX_SPEED_LEVEL}.
* @param programDurationMillis the length of program under playing.
* @throws IndexOutOfBoundsException if speed level is out of its range.
*/
@@ -60,4 +59,3 @@ public class TimeShiftUtils {
: SHORT_PROGRAM_SPEED_FACTORS[MAX_SPEED_LEVEL];
}
}
-
diff --git a/src/com/android/tv/util/TvInputManagerHelper.java b/src/com/android/tv/util/TvInputManagerHelper.java
index 121f56ed..730a985b 100644
--- a/src/com/android/tv/util/TvInputManagerHelper.java
+++ b/src/com/android/tv/util/TvInputManagerHelper.java
@@ -18,20 +18,26 @@ package com.android.tv.util;
import android.content.Context;
import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.graphics.drawable.Drawable;
+import android.hardware.hdmi.HdmiDeviceInfo;
import android.media.tv.TvInputInfo;
import android.media.tv.TvInputManager;
import android.media.tv.TvInputManager.TvInputCallback;
import android.os.Handler;
import android.support.annotation.VisibleForTesting;
import android.text.TextUtils;
+import android.util.ArrayMap;
import android.util.Log;
import com.android.tv.Features;
import com.android.tv.common.SoftPreconditions;
+import com.android.tv.common.TvCommonUtils;
import com.android.tv.parental.ContentRatingsManager;
import com.android.tv.parental.ParentalControlSettings;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
@@ -42,14 +48,64 @@ import java.util.Map;
public class TvInputManagerHelper {
private static final String TAG = "TvInputManagerHelper";
private static final boolean DEBUG = false;
+
+ /**
+ * Types of HDMI device and bundled tuner.
+ */
+ public static final int TYPE_CEC_DEVICE = -2;
+ public static final int TYPE_BUNDLED_TUNER = -3;
+ public static final int TYPE_CEC_DEVICE_RECORDER = -4;
+ public static final int TYPE_CEC_DEVICE_PLAYBACK = -5;
+ public static final int TYPE_MHL_MOBILE = -6;
+
+ private static final String PERMISSION_ACCESS_ALL_EPG_DATA =
+ "com.android.providers.tv.permission.ACCESS_ALL_EPG_DATA";
+ private static final String [] mPhysicalTunerBlackList = {
+ };
+ private static final String META_LABEL_SORT_KEY = "input_sort_key";
+
+ /**
+ * The default tv input priority to show.
+ */
+ private static final ArrayList<Integer> DEFAULT_TV_INPUT_PRIORITY = new ArrayList<>();
+ static {
+ DEFAULT_TV_INPUT_PRIORITY.add(TYPE_BUNDLED_TUNER);
+ DEFAULT_TV_INPUT_PRIORITY.add(TvInputInfo.TYPE_TUNER);
+ DEFAULT_TV_INPUT_PRIORITY.add(TYPE_CEC_DEVICE);
+ DEFAULT_TV_INPUT_PRIORITY.add(TYPE_CEC_DEVICE_RECORDER);
+ DEFAULT_TV_INPUT_PRIORITY.add(TYPE_CEC_DEVICE_PLAYBACK);
+ DEFAULT_TV_INPUT_PRIORITY.add(TYPE_MHL_MOBILE);
+ DEFAULT_TV_INPUT_PRIORITY.add(TvInputInfo.TYPE_HDMI);
+ DEFAULT_TV_INPUT_PRIORITY.add(TvInputInfo.TYPE_DVI);
+ DEFAULT_TV_INPUT_PRIORITY.add(TvInputInfo.TYPE_COMPONENT);
+ DEFAULT_TV_INPUT_PRIORITY.add(TvInputInfo.TYPE_SVIDEO);
+ DEFAULT_TV_INPUT_PRIORITY.add(TvInputInfo.TYPE_COMPOSITE);
+ DEFAULT_TV_INPUT_PRIORITY.add(TvInputInfo.TYPE_DISPLAY_PORT);
+ DEFAULT_TV_INPUT_PRIORITY.add(TvInputInfo.TYPE_VGA);
+ DEFAULT_TV_INPUT_PRIORITY.add(TvInputInfo.TYPE_SCART);
+ DEFAULT_TV_INPUT_PRIORITY.add(TvInputInfo.TYPE_OTHER);
+ }
+
private static final String[] PARTNER_TUNER_INPUT_PREFIX_BLACKLIST = {
};
+ private static final String[] TESTABLE_INPUTS = {
+ "com.android.tv.testinput/.TestTvInputService"
+ };
+
private final Context mContext;
+ private final PackageManager mPackageManager;
private final TvInputManager mTvInputManager;
private final Map<String, Integer> mInputStateMap = new HashMap<>();
private final Map<String, TvInputInfo> mInputMap = new HashMap<>();
+ private final Map<String, String> mTvInputLabels = new ArrayMap<>();
+ private final Map<String, String> mTvInputCustomLabels = new ArrayMap<>();
private final Map<String, Boolean> mInputIdToPartnerInputMap = new HashMap<>();
+
+ private final Map<String, CharSequence> mTvInputApplicationLabels = new ArrayMap<>();
+ private final Map<String, Drawable> mTvInputApplicationIcons = new ArrayMap<>();
+ private final Map<String, Drawable> mTvInputAppliactionBanners = new ArrayMap<>();
+
private final TvInputCallback mInternalCallback = new TvInputCallback() {
@Override
public void onInputStateChanged(String inputId, int state) {
@@ -72,6 +128,11 @@ public class TvInputManagerHelper {
TvInputInfo info = mTvInputManager.getTvInputInfo(inputId);
if (info != null) {
mInputMap.put(inputId, info);
+ mTvInputLabels.put(inputId, info.loadLabel(mContext).toString());
+ CharSequence inputCustomLabel = info.loadCustomLabel(mContext);
+ if (inputCustomLabel != null) {
+ mTvInputCustomLabels.put(inputId, inputCustomLabel.toString());
+ }
mInputStateMap.put(inputId, mTvInputManager.getInputState(inputId));
mInputIdToPartnerInputMap.put(inputId, isPartnerInput(info));
}
@@ -85,6 +146,11 @@ public class TvInputManagerHelper {
public void onInputRemoved(String inputId) {
if (DEBUG) Log.d(TAG, "onInputRemoved " + inputId);
mInputMap.remove(inputId);
+ mTvInputLabels.remove(inputId);
+ mTvInputCustomLabels.remove(inputId);
+ mTvInputApplicationLabels.remove(inputId);
+ mTvInputApplicationIcons.remove(inputId);
+ mTvInputAppliactionBanners.remove(inputId);
mInputStateMap.remove(inputId);
mInputIdToPartnerInputMap.remove(inputId);
mContentRatingsManager.update();
@@ -103,6 +169,14 @@ public class TvInputManagerHelper {
}
TvInputInfo info = mTvInputManager.getTvInputInfo(inputId);
mInputMap.put(inputId, info);
+ mTvInputLabels.put(inputId, info.loadLabel(mContext).toString());
+ CharSequence inputCustomLabel = info.loadCustomLabel(mContext);
+ if (inputCustomLabel != null) {
+ mTvInputCustomLabels.put(inputId, inputCustomLabel.toString());
+ }
+ mTvInputApplicationLabels.remove(inputId);
+ mTvInputApplicationIcons.remove(inputId);
+ mTvInputAppliactionBanners.remove(inputId);
for (TvInputCallback callback : mCallbacks) {
callback.onInputUpdated(inputId);
}
@@ -114,6 +188,11 @@ public class TvInputManagerHelper {
public void onTvInputInfoUpdated(TvInputInfo inputInfo) {
if (DEBUG) Log.d(TAG, "onTvInputInfoUpdated " + inputInfo);
mInputMap.put(inputInfo.getId(), inputInfo);
+ mTvInputLabels.put(inputInfo.getId(), inputInfo.loadLabel(mContext).toString());
+ CharSequence inputCustomLabel = inputInfo.loadCustomLabel(mContext);
+ if (inputCustomLabel != null) {
+ mTvInputCustomLabels.put(inputInfo.getId(), inputCustomLabel.toString());
+ }
for (TvInputCallback callback : mCallbacks) {
callback.onTvInputInfoUpdated(inputInfo);
}
@@ -131,13 +210,18 @@ public class TvInputManagerHelper {
public TvInputManagerHelper(Context context) {
mContext = context.getApplicationContext();
+ mPackageManager = context.getPackageManager();
mTvInputManager = (TvInputManager) context.getSystemService(Context.TV_INPUT_SERVICE);
mContentRatingsManager = new ContentRatingsManager(context);
mParentalControlSettings = new ParentalControlSettings(context);
- mTvInputInfoComparator = new TvInputInfoComparator(this);
+ mTvInputInfoComparator = new InputComparatorInternal(this);
}
public void start() {
+ if (!hasTvInputManager()) {
+ // Not a TV device
+ return;
+ }
if (mStarted) {
return;
}
@@ -145,6 +229,11 @@ public class TvInputManagerHelper {
mStarted = true;
mTvInputManager.registerCallback(mInternalCallback, mHandler);
mInputMap.clear();
+ mTvInputLabels.clear();
+ mTvInputCustomLabels.clear();
+ mTvInputApplicationLabels.clear();
+ mTvInputApplicationIcons.clear();
+ mTvInputAppliactionBanners.clear();
mInputStateMap.clear();
mInputIdToPartnerInputMap.clear();
for (TvInputInfo input : mTvInputManager.getTvInputList()) {
@@ -171,9 +260,23 @@ public class TvInputManagerHelper {
mStarted = false;
mInputStateMap.clear();
mInputMap.clear();
+ mTvInputLabels.clear();
+ mTvInputCustomLabels.clear();
+ mTvInputApplicationLabels.clear();
+ mTvInputApplicationIcons.clear();
+ mTvInputAppliactionBanners.clear();;
mInputIdToPartnerInputMap.clear();
}
+ /**
+ * Clears the TvInput labels map.
+ */
+ public void clearTvInputLabels() {
+ mTvInputLabels.clear();
+ mTvInputCustomLabels.clear();
+ mTvInputApplicationLabels.clear();
+ }
+
public List<TvInputInfo> getTvInputInfos(boolean availableOnly, boolean tunerOnly) {
ArrayList<TvInputInfo> list = new ArrayList<>();
for (Map.Entry<String, Integer> pair : mInputStateMap.entrySet()) {
@@ -192,7 +295,7 @@ public class TvInputManagerHelper {
/**
* Returns the default comparator for {@link TvInputInfo}.
- * See {@link TvInputInfoComparator} for detail.
+ * See {@link InputComparatorInternal} for detail.
*/
public Comparator<TvInputInfo> getDefaultTvInputInfoComparator() {
return mTvInputInfoComparator;
@@ -237,15 +340,81 @@ public class TvInputManagerHelper {
}
/**
- * Loads label of {@code info}.
+ * Is (Context.TV_INPUT_SERVICE) available.
*
- * It's visible for comparator test to mock TvInputInfo.
- * Package private is enough for this method, but public is necessary to workaround mockito
- * bug.
+ * <p>This is only available on TV devices.
+ */
+ public boolean hasTvInputManager() {
+ return mTvInputManager != null;
+ }
+
+ /**
+ * Loads label of {@code info}.
*/
- @VisibleForTesting
public String loadLabel(TvInputInfo info) {
- return info.loadLabel(mContext).toString();
+ String label = mTvInputLabels.get(info.getId());
+ if (label == null) {
+ label = info.loadLabel(mContext).toString();
+ mTvInputLabels.put(info.getId(), label);
+ }
+ return label;
+ }
+
+ /**
+ * Loads custom label of {@code info}
+ */
+ public String loadCustomLabel(TvInputInfo info) {
+ String customLabel = mTvInputCustomLabels.get(info.getId());
+ if (customLabel == null) {
+ CharSequence customLabelCharSequence = info.loadCustomLabel(mContext);
+ if (customLabelCharSequence != null) {
+ customLabel = customLabelCharSequence.toString();
+ mTvInputCustomLabels.put(info.getId(), customLabel);
+ }
+ }
+ return customLabel;
+ }
+
+ /**
+ * Gets the tv input application's label.
+ */
+ public CharSequence getTvInputApplicationLabel(CharSequence inputId) {
+ return mTvInputApplicationLabels.get(inputId);
+ }
+
+ /**
+ * Stores the tv input application's label.
+ */
+ public void setTvInputApplicationLabel(String inputId, CharSequence label) {
+ mTvInputApplicationLabels.put(inputId, label);
+ }
+
+ /**
+ * Gets the tv input application's icon.
+ */
+ public Drawable getTvInputApplicationIcon(String inputId) {
+ return mTvInputApplicationIcons.get(inputId);
+ }
+
+ /**
+ * Stores the tv input application's icon.
+ */
+ public void setTvInputApplicationIcon(String inputId, Drawable icon) {
+ mTvInputApplicationIcons.put(inputId, icon);
+ }
+
+ /**
+ * Gets the tv input application's banner.
+ */
+ public Drawable getTvInputApplicationBanner(String inputId) {
+ return mTvInputAppliactionBanners.get(inputId);
+ }
+
+ /**
+ * Stores the tv input application's banner.
+ */
+ public void setTvInputApplicationBanner(String inputId, Drawable banner) {
+ mTvInputAppliactionBanners.put(inputId, banner);
}
/**
@@ -321,14 +490,54 @@ public class TvInputManagerHelper {
return mContentRatingsManager;
}
- private boolean isInBlackList(String inputId) {
- if (!Features.USE_PARTNER_INPUT_BLACKLIST.isEnabled(mContext)) {
+ private int getInputSortKey(TvInputInfo input) {
+ return input.getServiceInfo().metaData.getInt(META_LABEL_SORT_KEY,
+ Integer.MAX_VALUE);
+ }
+
+ private boolean isInputPhysicalTuner(TvInputInfo input) {
+ String packageName = input.getServiceInfo().packageName;
+ if (Arrays.asList(mPhysicalTunerBlackList).contains(packageName)) {
return false;
}
- for (String disabledTunerInputPrefix : PARTNER_TUNER_INPUT_PREFIX_BLACKLIST) {
- if (inputId.contains(disabledTunerInputPrefix)) {
- return true;
+
+ if (input.createSetupIntent() == null) {
+ return false;
+ } else {
+ boolean mayBeTunerInput = mPackageManager.checkPermission(
+ PERMISSION_ACCESS_ALL_EPG_DATA, input.getServiceInfo().packageName)
+ == PackageManager.PERMISSION_GRANTED;
+ if (!mayBeTunerInput) {
+ try {
+ ApplicationInfo ai = mPackageManager.getApplicationInfo(
+ input.getServiceInfo().packageName, 0);
+ if ((ai.flags & (ApplicationInfo.FLAG_SYSTEM
+ | ApplicationInfo.FLAG_UPDATED_SYSTEM_APP)) == 0) {
+ return false;
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ private boolean isInBlackList(String inputId) {
+ if (Features.USE_PARTNER_INPUT_BLACKLIST.isEnabled(mContext)) {
+ for (String disabledTunerInputPrefix : PARTNER_TUNER_INPUT_PREFIX_BLACKLIST) {
+ if (inputId.contains(disabledTunerInputPrefix)) {
+ return true;
+ }
+ }
+ }
+ if (TvCommonUtils.isRunningInTest()) {
+ for (String testableInput : TESTABLE_INPUTS) {
+ if (testableInput.equals(inputId)) {
+ return false;
+ }
}
+ return true;
}
return false;
}
@@ -342,10 +551,10 @@ public class TvInputManagerHelper {
* (i.e. Mockito's spy doesn't work)
*/
@VisibleForTesting
- static class TvInputInfoComparator implements Comparator<TvInputInfo> {
+ static class InputComparatorInternal implements Comparator<TvInputInfo> {
private final TvInputManagerHelper mInputManager;
- public TvInputInfoComparator(TvInputManagerHelper inputManager) {
+ public InputComparatorInternal(TvInputManagerHelper inputManager) {
mInputManager = inputManager;
}
@@ -357,4 +566,123 @@ public class TvInputManagerHelper {
return mInputManager.loadLabel(lhs).compareTo(mInputManager.loadLabel(rhs));
}
}
+
+ /**
+ * A comparator used for {@link com.android.tv.ui.SelectInputView} to show the list of
+ * TV inputs.
+ */
+ public static class HardwareInputComparator implements Comparator<TvInputInfo> {
+ private Map<Integer, Integer> mTypePriorities = new HashMap<>();
+ private final TvInputManagerHelper mTvInputManagerHelper;
+ private final Context mContext;
+
+ public HardwareInputComparator(Context context, TvInputManagerHelper tvInputManagerHelper) {
+ mContext = context;
+ mTvInputManagerHelper = tvInputManagerHelper;
+ setupDeviceTypePriorities();
+ }
+
+ @Override
+ public int compare(TvInputInfo lhs, TvInputInfo rhs) {
+ if (lhs == null) {
+ return (rhs == null) ? 0 : 1;
+ }
+ if (rhs == null) {
+ return -1;
+ }
+
+ boolean enabledL = (mTvInputManagerHelper.getInputState(lhs)
+ != TvInputManager.INPUT_STATE_DISCONNECTED);
+ boolean enabledR = (mTvInputManagerHelper.getInputState(rhs)
+ != TvInputManager.INPUT_STATE_DISCONNECTED);
+ if (enabledL != enabledR) {
+ return enabledL ? -1 : 1;
+ }
+
+ int priorityL = getPriority(lhs);
+ int priorityR = getPriority(rhs);
+ if (priorityL != priorityR) {
+ return priorityL - priorityR;
+ }
+
+ if (lhs.getType() == TvInputInfo.TYPE_TUNER
+ && rhs.getType() == TvInputInfo.TYPE_TUNER) {
+ boolean isPhysicalL = mTvInputManagerHelper.isInputPhysicalTuner(lhs);
+ boolean isPhysicalR = mTvInputManagerHelper.isInputPhysicalTuner(rhs);
+ if (isPhysicalL != isPhysicalR) {
+ return isPhysicalL ? -1 : 1;
+ }
+ }
+
+ int sortKeyL = mTvInputManagerHelper.getInputSortKey(lhs);
+ int sortKeyR = mTvInputManagerHelper.getInputSortKey(rhs);
+ if (sortKeyL != sortKeyR) {
+ return sortKeyR - sortKeyL;
+ }
+
+ String parentLabelL = lhs.getParentId() != null
+ ? getLabel(mTvInputManagerHelper.getTvInputInfo(lhs.getParentId()))
+ : getLabel(mTvInputManagerHelper.getTvInputInfo(lhs.getId()));
+ String parentLabelR = rhs.getParentId() != null
+ ? getLabel(mTvInputManagerHelper.getTvInputInfo(rhs.getParentId()))
+ : getLabel(mTvInputManagerHelper.getTvInputInfo(rhs.getId()));
+
+ if (!TextUtils.equals(parentLabelL, parentLabelR)) {
+ return parentLabelL.compareToIgnoreCase(parentLabelR);
+ }
+ return getLabel(lhs).compareToIgnoreCase(getLabel(rhs));
+ }
+
+ private String getLabel(TvInputInfo input) {
+ if (input == null) {
+ return "";
+ }
+ String label = mTvInputManagerHelper.loadCustomLabel(input);
+ if (TextUtils.isEmpty(label)) {
+ label = mTvInputManagerHelper.loadLabel(input);
+ }
+ return label;
+ }
+
+ private int getPriority(TvInputInfo info) {
+ Integer priority = null;
+ if (mTypePriorities != null) {
+ priority = mTypePriorities.get(getTvInputTypeForPriority(info));
+ }
+ if (priority != null) {
+ return priority;
+ }
+ return Integer.MAX_VALUE;
+ }
+
+ private void setupDeviceTypePriorities() {
+ mTypePriorities = Partner.getInstance(mContext).getInputsOrderMap();
+
+ // Fill in any missing priorities in the map we got from the OEM
+ int priority = mTypePriorities.size();
+ for (int type : DEFAULT_TV_INPUT_PRIORITY) {
+ if (!mTypePriorities.containsKey(type)) {
+ mTypePriorities.put(type, priority++);
+ }
+ }
+ }
+
+ private int getTvInputTypeForPriority(TvInputInfo info) {
+ if (info.getHdmiDeviceInfo() != null) {
+ if (info.getHdmiDeviceInfo().isCecDevice()) {
+ switch (info.getHdmiDeviceInfo().getDeviceType()) {
+ case HdmiDeviceInfo.DEVICE_RECORDER:
+ return TYPE_CEC_DEVICE_RECORDER;
+ case HdmiDeviceInfo.DEVICE_PLAYBACK:
+ return TYPE_CEC_DEVICE_PLAYBACK;
+ default:
+ return TYPE_CEC_DEVICE;
+ }
+ } else if (info.getHdmiDeviceInfo().isMhlDevice()) {
+ return TYPE_MHL_MOBILE;
+ }
+ }
+ return info.getType();
+ }
+ }
}
diff --git a/src/com/android/tv/util/TvSettings.java b/src/com/android/tv/util/TvSettings.java
index 97ff59d6..c5fde317 100644
--- a/src/com/android/tv/util/TvSettings.java
+++ b/src/com/android/tv/util/TvSettings.java
@@ -17,6 +17,8 @@
package com.android.tv.util;
import android.content.Context;
+import android.content.SharedPreferences;
+import android.media.tv.TvTrackInfo;
import android.preference.PreferenceManager;
import android.support.annotation.IntDef;
@@ -26,53 +28,27 @@ import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
-
/**
* A class about the constants for TV settings.
* Objects that are returned from the various {@code get} methods must be treated as immutable.
*/
public final class TvSettings {
- private TvSettings() {}
-
public static final String PREF_DISPLAY_MODE = "display_mode"; // int value
- public static final String PREF_PIP_LAYOUT = "pip_layout"; // int value
- public static final String PREF_PIP_SIZE = "pip_size"; // int value
public static final String PREF_PIN = "pin"; // 4-digit string value. Otherwise, it's not set.
- // PIP sounds
- @Retention(RetentionPolicy.SOURCE)
- @IntDef({
- PIP_SOUND_MAIN, PIP_SOUND_PIP_WINDOW })
- public @interface PipSound {}
- public static final int PIP_SOUND_MAIN = 0;
- public static final int PIP_SOUND_PIP_WINDOW = PIP_SOUND_MAIN + 1;
-
- // PIP layouts
- @Retention(RetentionPolicy.SOURCE)
- @IntDef({
- PIP_LAYOUT_BOTTOM_RIGHT, PIP_LAYOUT_TOP_RIGHT, PIP_LAYOUT_TOP_LEFT,
- PIP_LAYOUT_BOTTOM_LEFT, PIP_LAYOUT_SIDE_BY_SIDE })
- public @interface PipLayout {}
- public static final int PIP_LAYOUT_BOTTOM_RIGHT = 0;
- public static final int PIP_LAYOUT_TOP_RIGHT = PIP_LAYOUT_BOTTOM_RIGHT + 1;
- public static final int PIP_LAYOUT_TOP_LEFT = PIP_LAYOUT_TOP_RIGHT + 1;
- public static final int PIP_LAYOUT_BOTTOM_LEFT = PIP_LAYOUT_TOP_LEFT + 1;
- public static final int PIP_LAYOUT_SIDE_BY_SIDE = PIP_LAYOUT_BOTTOM_LEFT + 1;
- public static final int PIP_LAYOUT_LAST = PIP_LAYOUT_SIDE_BY_SIDE;
-
- // PIP sizes
- @Retention(RetentionPolicy.SOURCE)
- @IntDef({ PIP_SIZE_SMALL, PIP_SIZE_BIG })
- public @interface PipSize {}
- public static final int PIP_SIZE_SMALL = 0;
- public static final int PIP_SIZE_BIG = PIP_SIZE_SMALL + 1;
- public static final int PIP_SIZE_LAST = PIP_SIZE_BIG;
-
// Multi-track audio settings
private static final String PREF_MULTI_AUDIO_ID = "pref.multi_audio_id";
private static final String PREF_MULTI_AUDIO_LANGUAGE = "pref.multi_audio_language";
private static final String PREF_MULTI_AUDIO_CHANNEL_COUNT = "pref.multi_audio_channel_count";
+ // DVR Multi-audio and subtitle settings
+ private static final String PREF_DVR_MULTI_AUDIO_ID = "pref.dvr_multi_audio_id";
+ private static final String PREF_DVR_MULTI_AUDIO_LANGUAGE = "pref.dvr_multi_audio_language";
+ private static final String PREF_DVR_MULTI_AUDIO_CHANNEL_COUNT =
+ "pref.dvr_multi_audio_channel_count";
+ private static final String PREF_DVR_SUBTITLE_ID = "pref.dvr_subtitle_id";
+ private static final String PREF_DVR_SUBTITLE_LANGUAGE = "pref.dvr_subtitle_language";
+
// Parental Control settings
private static final String PREF_CONTENT_RATING_SYSTEMS = "pref.content_rating_systems";
private static final String PREF_CONTENT_RATING_LEVEL = "pref.content_rating_level";
@@ -89,58 +65,7 @@ public final class TvSettings {
public static final int CONTENT_RATING_LEVEL_LOW = 3;
public static final int CONTENT_RATING_LEVEL_CUSTOM = 4;
- // PIP settings
- /**
- * Returns the layout of the PIP window stored in the shared preferences.
- *
- * @return the saved layout of the PIP window. This value is one of
- * {@link #PIP_LAYOUT_TOP_LEFT}, {@link #PIP_LAYOUT_TOP_RIGHT},
- * {@link #PIP_LAYOUT_BOTTOM_LEFT}, {@link #PIP_LAYOUT_BOTTOM_RIGHT} and
- * {@link #PIP_LAYOUT_SIDE_BY_SIDE}. If the preference value does not exist,
- * {@link #PIP_LAYOUT_BOTTOM_RIGHT} is returned.
- */
- @SuppressWarnings("ResourceType")
- @PipLayout
- public static int getPipLayout(Context context) {
- return PreferenceManager.getDefaultSharedPreferences(context).getInt(
- PREF_PIP_LAYOUT, PIP_LAYOUT_BOTTOM_RIGHT);
- }
-
- /**
- * Stores the layout of PIP window to the shared preferences.
- *
- * @param pipLayout This value should be one of {@link #PIP_LAYOUT_TOP_LEFT},
- * {@link #PIP_LAYOUT_TOP_RIGHT}, {@link #PIP_LAYOUT_BOTTOM_LEFT},
- * {@link #PIP_LAYOUT_BOTTOM_RIGHT} and {@link #PIP_LAYOUT_SIDE_BY_SIDE}.
- */
- public static void setPipLayout(Context context, @PipLayout int pipLayout) {
- PreferenceManager.getDefaultSharedPreferences(context).edit().putInt(
- PREF_PIP_LAYOUT, pipLayout).apply();
- }
-
- /**
- * Returns the size of the PIP view stored in the shared preferences.
- *
- * @return the saved size of the PIP view. This value is one of
- * {@link #PIP_SIZE_SMALL} and {@link #PIP_SIZE_BIG}. If the preference value does not
- * exist, {@link #PIP_SIZE_SMALL} is returned.
- */
- @SuppressWarnings("ResourceType")
- @PipSize
- public static int getPipSize(Context context) {
- return PreferenceManager.getDefaultSharedPreferences(context).getInt(
- PREF_PIP_SIZE, PIP_SIZE_SMALL);
- }
-
- /**
- * Stores the size of PIP view to the shared preferences.
- *
- * @param pipSize This value should be one of {@link #PIP_SIZE_SMALL} and {@link #PIP_SIZE_BIG}.
- */
- public static void setPipSize(Context context, @PipSize int pipSize) {
- PreferenceManager.getDefaultSharedPreferences(context).edit().putInt(
- PREF_PIP_SIZE, pipSize).apply();
- }
+ private TvSettings() {}
// Multi-track audio settings
public static String getMultiAudioId(Context context) {
@@ -173,26 +98,61 @@ public final class TvSettings {
PREF_MULTI_AUDIO_CHANNEL_COUNT, channelCount).apply();
}
- // Parental Control settings
- public static void addContentRatingSystems(Context context, Set<String> ids) {
- Set<String> contentRatingSystemSet = getContentRatingSystemSet(context);
- if (contentRatingSystemSet.addAll(ids)) {
- PreferenceManager.getDefaultSharedPreferences(context).edit()
- .putStringSet(PREF_CONTENT_RATING_SYSTEMS, contentRatingSystemSet).apply();
+ public static void setDvrPlaybackTrackSettings(Context context, int trackType,
+ TvTrackInfo info) {
+ if (trackType == TvTrackInfo.TYPE_AUDIO) {
+ if (info == null) {
+ PreferenceManager.getDefaultSharedPreferences(context).edit()
+ .remove(PREF_DVR_MULTI_AUDIO_ID).apply();
+ } else {
+ PreferenceManager.getDefaultSharedPreferences(context).edit()
+ .putString(PREF_DVR_MULTI_AUDIO_LANGUAGE, info.getLanguage())
+ .putInt(PREF_DVR_MULTI_AUDIO_CHANNEL_COUNT, info.getAudioChannelCount())
+ .putString(PREF_DVR_MULTI_AUDIO_ID, info.getId()).apply();
+ }
+ } else if (trackType == TvTrackInfo.TYPE_SUBTITLE) {
+ if (info == null) {
+ PreferenceManager.getDefaultSharedPreferences(context).edit()
+ .remove(PREF_DVR_SUBTITLE_ID).apply();
+ } else {
+ PreferenceManager.getDefaultSharedPreferences(context).edit()
+ .putString(PREF_DVR_SUBTITLE_LANGUAGE, info.getLanguage())
+ .putString(PREF_DVR_SUBTITLE_ID, info.getId()).apply();
+ }
}
}
- public static void addContentRatingSystem(Context context, String id) {
- Set<String> contentRatingSystemSet = getContentRatingSystemSet(context);
- if (contentRatingSystemSet.add(id)) {
- PreferenceManager.getDefaultSharedPreferences(context).edit()
- .putStringSet(PREF_CONTENT_RATING_SYSTEMS, contentRatingSystemSet).apply();
+ public static TvTrackInfo getDvrPlaybackTrackSettings(Context context,
+ int trackType) {
+ String language;
+ String trackId;
+ int channelCount;
+ SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(context);
+ if (trackType == TvTrackInfo.TYPE_AUDIO) {
+ trackId = pref.getString(PREF_DVR_MULTI_AUDIO_ID, null);
+ if (trackId == null) {
+ return null;
+ }
+ language = pref.getString(PREF_DVR_MULTI_AUDIO_LANGUAGE, null);
+ channelCount = pref.getInt(PREF_DVR_MULTI_AUDIO_CHANNEL_COUNT, 0);
+ return new TvTrackInfo.Builder(trackType, trackId)
+ .setLanguage(language).setAudioChannelCount(channelCount).build();
+ } else if (trackType == TvTrackInfo.TYPE_SUBTITLE) {
+ trackId = pref.getString(PREF_DVR_SUBTITLE_ID, null);
+ if (trackId == null) {
+ return null;
+ }
+ language = pref.getString(PREF_DVR_SUBTITLE_LANGUAGE, null);
+ return new TvTrackInfo.Builder(trackType, trackId).setLanguage(language).build();
+ } else {
+ return null;
}
}
- public static void removeContentRatingSystems(Context context, Set<String> ids) {
+ // Parental Control settings
+ public static void addContentRatingSystem(Context context, String id) {
Set<String> contentRatingSystemSet = getContentRatingSystemSet(context);
- if (contentRatingSystemSet.removeAll(ids)) {
+ if (contentRatingSystemSet.add(id)) {
PreferenceManager.getDefaultSharedPreferences(context).edit()
.putStringSet(PREF_CONTENT_RATING_SYSTEMS, contentRatingSystemSet).apply();
}
@@ -254,4 +214,4 @@ public final class TvSettings {
PreferenceManager.getDefaultSharedPreferences(context).edit().putLong(
PREF_DISABLE_PIN_UNTIL, timeMillis).apply();
}
-}
+} \ No newline at end of file
diff --git a/src/com/android/tv/util/TvTrackInfoUtils.java b/src/com/android/tv/util/TvTrackInfoUtils.java
index c004f001..667cc9bf 100644
--- a/src/com/android/tv/util/TvTrackInfoUtils.java
+++ b/src/com/android/tv/util/TvTrackInfoUtils.java
@@ -52,35 +52,22 @@ public class TvTrackInfoUtils {
}
// Assumes {@code null} language matches to any language since it means user hasn't
// selected any track before or selected a track without language information.
- boolean rhsLangMatch = language == null || Utils.isEqualLanguage(rhs.getLanguage(),
- language);
boolean lhsLangMatch = language == null || Utils.isEqualLanguage(lhs.getLanguage(),
language);
- if (rhsLangMatch) {
- if (lhsLangMatch) {
- boolean rhsCountMatch = rhs.getAudioChannelCount() == channelCount;
- boolean lhsCountMatch = lhs.getAudioChannelCount() == channelCount;
- if (rhsCountMatch) {
- if (lhsCountMatch) {
- boolean rhsIdMatch = rhs.getId().equals(id);
- boolean lhsIdMatch = lhs.getId().equals(id);
- if (rhsIdMatch) {
- return lhsIdMatch ? 0 : -1;
- } else {
- return lhsIdMatch ? 1 : 0;
- }
-
- } else {
- return -1;
- }
- } else {
- return lhsCountMatch ? 1 : 0;
- }
+ boolean rhsLangMatch = language == null || Utils.isEqualLanguage(rhs.getLanguage(),
+ language);
+ if (lhsLangMatch && rhsLangMatch) {
+ boolean lhsCountMatch = lhs.getType() != TvTrackInfo.TYPE_AUDIO
+ || lhs.getAudioChannelCount() == channelCount;
+ boolean rhsCountMatch = rhs.getType() != TvTrackInfo.TYPE_AUDIO
+ || rhs.getAudioChannelCount() == channelCount;
+ if (lhsCountMatch && rhsCountMatch) {
+ return Boolean.compare(lhs.getId().equals(id), rhs.getId().equals(id));
} else {
- return -1;
+ return Boolean.compare(lhsCountMatch, rhsCountMatch);
}
} else {
- return lhsLangMatch ? 1 : 0;
+ return Boolean.compare(lhsLangMatch, rhsLangMatch);
}
}
};
@@ -112,4 +99,4 @@ public class TvTrackInfoUtils {
private TvTrackInfoUtils() {
}
-}
+} \ No newline at end of file
diff --git a/src/com/android/tv/util/TvProviderUriMatcher.java b/src/com/android/tv/util/TvUriMatcher.java
index 749e4aa3..3d91cdad 100644
--- a/src/com/android/tv/util/TvProviderUriMatcher.java
+++ b/src/com/android/tv/util/TvUriMatcher.java
@@ -16,23 +16,27 @@
package com.android.tv.util;
+import android.app.SearchManager;
import android.content.UriMatcher;
import android.media.tv.TvContract;
import android.net.Uri;
import android.support.annotation.IntDef;
+import com.android.tv.search.LocalSearchProvider;
+
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* Utility class to aid in matching URIs in TvProvider.
*/
-public class TvProviderUriMatcher {
+public class TvUriMatcher {
private static final UriMatcher URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
@Retention(RetentionPolicy.SOURCE)
@IntDef({MATCH_CHANNEL, MATCH_CHANNEL_ID, MATCH_PROGRAM, MATCH_PROGRAM_ID,
- MATCH_RECORDED_PROGRAM, MATCH_RECORDED_PROGRAM_ID, MATCH_WATCHED_PROGRAM_ID})
+ MATCH_RECORDED_PROGRAM, MATCH_RECORDED_PROGRAM_ID, MATCH_WATCHED_PROGRAM_ID,
+ MATCH_ON_DEVICE_SEARCH})
private @interface TvProviderUriMatchCode {}
/** The code for the channels URI. */
public static final int MATCH_CHANNEL = 1;
@@ -48,6 +52,8 @@ public class TvProviderUriMatcher {
public static final int MATCH_RECORDED_PROGRAM_ID = 6;
/** The code for the watched program URI. */
public static final int MATCH_WATCHED_PROGRAM_ID = 7;
+ /** The code for the on-device search URI. */
+ public static final int MATCH_ON_DEVICE_SEARCH = 8;
static {
URI_MATCHER.addURI(TvContract.AUTHORITY, "channel", MATCH_CHANNEL);
URI_MATCHER.addURI(TvContract.AUTHORITY, "channel/#", MATCH_CHANNEL_ID);
@@ -56,9 +62,11 @@ public class TvProviderUriMatcher {
URI_MATCHER.addURI(TvContract.AUTHORITY, "recorded_program", MATCH_RECORDED_PROGRAM);
URI_MATCHER.addURI(TvContract.AUTHORITY, "recorded_program/#", MATCH_RECORDED_PROGRAM_ID);
URI_MATCHER.addURI(TvContract.AUTHORITY, "watched_program/#", MATCH_WATCHED_PROGRAM_ID);
+ URI_MATCHER.addURI(LocalSearchProvider.AUTHORITY,
+ SearchManager.SUGGEST_URI_PATH_QUERY + "/*", MATCH_ON_DEVICE_SEARCH);
}
- private TvProviderUriMatcher() { }
+ private TvUriMatcher() { }
/**
* Try to match against the path in a url.
diff --git a/src/com/android/tv/util/Utils.java b/src/com/android/tv/util/Utils.java
index 99d34431..d11bab3c 100644
--- a/src/com/android/tv/util/Utils.java
+++ b/src/com/android/tv/util/Utils.java
@@ -31,6 +31,7 @@ import android.media.tv.TvContract.Programs.Genres;
import android.media.tv.TvInputInfo;
import android.media.tv.TvTrackInfo;
import android.net.Uri;
+import android.os.Looper;
import android.preference.PreferenceManager;
import android.support.annotation.Nullable;
import android.support.annotation.VisibleForTesting;
@@ -44,11 +45,13 @@ import android.view.View;
import com.android.tv.ApplicationSingletons;
import com.android.tv.R;
import com.android.tv.TvApplication;
+import com.android.tv.common.BuildConfig;
import com.android.tv.common.SoftPreconditions;
import com.android.tv.data.Channel;
import com.android.tv.data.GenreItems;
import com.android.tv.data.Program;
import com.android.tv.data.StreamInfo;
+import com.android.tv.experiments.Experiments;
import java.io.File;
import java.text.SimpleDateFormat;
@@ -62,6 +65,8 @@ import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.TimeZone;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
/**
@@ -74,7 +79,6 @@ public class Utils {
private static final SimpleDateFormat ISO_8601 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ",
Locale.US);
- public static final String EXTRA_KEY_KEYCODE = "keycode";
public static final String EXTRA_KEY_ACTION = "action";
public static final String EXTRA_ACTION_SHOW_TV_INPUT ="show_tv_input";
public static final String EXTRA_KEY_FROM_LAUNCHER = "from_launcher";
@@ -83,11 +87,9 @@ public class Utils {
public static final String EXTRA_KEY_RECORDED_PROGRAM_PIN_CHECKED =
"recorded_program_pin_checked";
- // Query parameter in the intent of starting MainActivity.
- public static final String PARAM_SOURCE = "source";
-
private static final String PATH_CHANNEL = "channel";
private static final String PATH_PROGRAM = "program";
+ private static final String PATH_RECORDED_PROGRAM = "recorded_program";
private static final String PREF_KEY_LAST_WATCHED_CHANNEL_ID = "last_watched_channel_id";
private static final String PREF_KEY_LAST_WATCHED_CHANNEL_ID_FOR_INPUT =
@@ -97,6 +99,8 @@ public class Utils {
"last_watched_tuner_input_id";
private static final String PREF_KEY_RECORDING_FAILED_REASONS =
"recording_failed_reasons";
+ private static final String PREF_KEY_FAILED_SCHEDULED_RECORDING_INFO_SET =
+ "failed_scheduled_recording_info_set";
private static final int VIDEO_SD_WIDTH = 704;
private static final int VIDEO_SD_HEIGHT = 480;
@@ -114,6 +118,7 @@ public class Utils {
private static final int AUDIO_CHANNEL_SURROUND_8 = 8;
private static final long RECORDING_FAILED_REASON_NONE = 0;
+ private static final long HALF_MINUTE_MS = TimeUnit.SECONDS.toMillis(30);
private static final long ONE_DAY_MS = TimeUnit.DAYS.toMillis(1);
// Hardcoded list for known bundled inputs not written by OEM/SOCs.
@@ -207,6 +212,28 @@ public class Utils {
}
/**
+ * Adds the info of failed scheduled recording.
+ */
+ public static void addFailedScheduledRecordingInfo(Context context,
+ String scheduledRecordingInfo) {
+ Set<String> failedScheduledRecordingInfoSet = getFailedScheduledRecordingInfoSet(context);
+ failedScheduledRecordingInfoSet.add(scheduledRecordingInfo);
+ PreferenceManager.getDefaultSharedPreferences(context).edit()
+ .putStringSet(PREF_KEY_FAILED_SCHEDULED_RECORDING_INFO_SET,
+ failedScheduledRecordingInfoSet)
+ .apply();
+ }
+
+ /**
+ * Clears the failed scheduled recording info set.
+ */
+ public static void clearFailedScheduledRecordingInfoSet(Context context) {
+ PreferenceManager.getDefaultSharedPreferences(context).edit()
+ .remove(PREF_KEY_FAILED_SCHEDULED_RECORDING_INFO_SET)
+ .apply();
+ }
+
+ /**
* Clears recording failed reason.
*/
public static void clearRecordingFailedReason(Context context, int reason) {
@@ -246,6 +273,14 @@ public class Utils {
}
/**
+ * Returns the failed scheduled recordings info set.
+ */
+ public static Set<String> getFailedScheduledRecordingInfoSet(Context context) {
+ return PreferenceManager.getDefaultSharedPreferences(context)
+ .getStringSet(PREF_KEY_FAILED_SCHEDULED_RECORDING_INFO_SET, new HashSet<>());
+ }
+
+ /**
* Checks do recording failed reason exist.
*/
public static boolean hasRecordingFailedReason(Context context, int reason) {
@@ -296,6 +331,13 @@ public class Utils {
}
/**
+ * Returns {@code true}, if {@code uri} is a programs URI.
+ */
+ public static boolean isRecordedProgramsUri(Uri uri) {
+ return isTvUri(uri) && PATH_RECORDED_PROGRAM.equals(uri.getPathSegments().get(0));
+ }
+
+ /**
* Gets the info of the program on particular time.
*/
@WorkerThread
@@ -333,6 +375,14 @@ public class Utils {
}
/**
+ * Returns the round off minutes when convert milliseconds to minutes.
+ */
+ public static int getRoundOffMinsFromMs(long millis) {
+ // Round off the result by adding half minute to the original ms.
+ return (int) TimeUnit.MILLISECONDS.toMinutes(millis + HALF_MINUTE_MS);
+ }
+
+ /**
* Returns duration string according to the date & time format.
* If {@code startUtcMillis} and {@code endUtcMills} are equal,
* formatted time will be returned instead.
@@ -392,16 +442,18 @@ public class Utils {
: DateUtils.formatDateRange(context, startUtcMillis, endUtcMillis + 1, flag);
}
- @VisibleForTesting
+ /**
+ * Checks if two given time (in milliseconds) are in the same day with regard to the
+ * locale timezone.
+ */
public static boolean isInGivenDay(long dayToMatchInMillis, long subjectTimeInMillis) {
- final long DAY_IN_MS = TimeUnit.DAYS.toMillis(1);
TimeZone timeZone = Calendar.getInstance().getTimeZone();
long offset = timeZone.getRawOffset();
if (timeZone.inDaylightTime(new Date(dayToMatchInMillis))) {
offset += timeZone.getDSTSavings();
}
- return Utils.floorTime(dayToMatchInMillis + offset, DAY_IN_MS)
- == Utils.floorTime(subjectTimeInMillis + offset, DAY_IN_MS);
+ return Utils.floorTime(dayToMatchInMillis + offset, ONE_DAY_MS)
+ == Utils.floorTime(subjectTimeInMillis + offset, ONE_DAY_MS);
}
/**
@@ -523,7 +575,7 @@ public class Utils {
if (track.getType() != TvTrackInfo.TYPE_AUDIO) {
throw new IllegalArgumentException("Not an audio track: " + track);
}
- String language = context.getString(R.string.default_language);
+ String language = context.getString(R.string.multi_audio_unknown_language);
if (!TextUtils.isEmpty(track.getLanguage())) {
language = new Locale(track.getLanguage()).getDisplayName();
} else {
@@ -606,10 +658,12 @@ public class Utils {
if (input == null) {
return null;
}
- CharSequence customLabel = input.loadCustomLabel(context);
+ TvInputManagerHelper inputManager =
+ TvApplication.getSingletons(context).getTvInputManagerHelper();
+ CharSequence customLabel = inputManager.loadCustomLabel(input);
String label = (customLabel == null) ? null : customLabel.toString();
if (TextUtils.isEmpty(label)) {
- label = input.loadLabel(context).toString();
+ label = inputManager.loadLabel(input).toString();
}
return label;
}
@@ -860,4 +914,28 @@ public class Utils {
}
return Genres.encode(genres);
}
+
+ /**
+ * Returns true if the current user is a developer.
+ */
+ public static boolean isDeveloper() {
+ return BuildConfig.ENG || Experiments.ENABLE_DEVELOPER_FEATURES.get();
+ }
+
+ /**
+ * Runs the method in main thread. If the current thread is not main thread, block it util
+ * the method is finished.
+ */
+ public static void runInMainThreadAndWait(Runnable runnable) {
+ if (Looper.myLooper() == Looper.getMainLooper()) {
+ runnable.run();
+ } else {
+ Future<?> temp = MainThreadExecutor.getInstance().submit(runnable);
+ try {
+ temp.get();
+ } catch (InterruptedException | ExecutionException e) {
+ Log.e(TAG, "failed to finish the execution", e);
+ }
+ }
+ }
}
diff --git a/src/com/android/tv/util/ViewCache.java b/src/com/android/tv/util/ViewCache.java
new file mode 100644
index 00000000..ed9a8ff6
--- /dev/null
+++ b/src/com/android/tv/util/ViewCache.java
@@ -0,0 +1,100 @@
+package com.android.tv.util;
+
+import android.content.Context;
+import android.util.SparseArray;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import java.util.ArrayList;
+
+/**
+ * A cache for the views.
+ */
+public class ViewCache {
+ private final static SparseArray<ArrayList<View>> mViews = new SparseArray();
+
+ private static ViewCache sViewCache;
+
+ private ViewCache() { }
+
+ /**
+ * Returns an instance of the view cache.
+ */
+ public static ViewCache getInstance() {
+ if (sViewCache == null) {
+ sViewCache = new ViewCache();
+ }
+ return sViewCache;
+ }
+
+ /**
+ * Returns if the view cache is empty.
+ */
+ public boolean isEmpty() {
+ return mViews.size() == 0;
+ }
+
+ /**
+ * Stores a view into this view cache.
+ */
+ public void putView(int resId, View view) {
+ ArrayList<View> views = mViews.get(resId);
+ if (views == null) {
+ views = new ArrayList();
+ mViews.put(resId, views);
+ }
+ views.add(view);
+ }
+
+ /**
+ * Stores multi specific views into the view cache.
+ */
+ public void putView(Context context, int resId, ViewGroup fakeParent, int num) {
+ LayoutInflater inflater =
+ (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ ArrayList<View> views = mViews.get(resId);
+ if (views == null) {
+ views = new ArrayList<>();
+ mViews.put(resId, views);
+ }
+ for (int i = 0; i < num; i++) {
+ View view = inflater.inflate(resId, fakeParent, false);
+ views.add(view);
+ }
+ }
+
+ /**
+ * Returns the view for specific resource id.
+ */
+ public View getView(int resId) {
+ ArrayList<View> views = mViews.get(resId);
+ if (views != null && !views.isEmpty()) {
+ View view = views.remove(views.size() - 1);
+ if (views.isEmpty()) {
+ mViews.remove(resId);
+ }
+ return view;
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Returns the view if exists, or create a new view for the specific resource id.
+ */
+ public View getOrCreateView(LayoutInflater inflater, int resId, ViewGroup container) {
+ View view = getView(resId);
+ if (view == null) {
+ view = inflater.inflate(resId, container, false);
+ }
+ return view;
+ }
+
+ /**
+ * Clears the view cache.
+ */
+ public void clear() {
+ mViews.clear();
+ }
+}