aboutsummaryrefslogtreecommitdiff
path: root/src/com/android/tv/tuner/TunerInputController.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android/tv/tuner/TunerInputController.java')
-rw-r--r--src/com/android/tv/tuner/TunerInputController.java338
1 files changed, 262 insertions, 76 deletions
diff --git a/src/com/android/tv/tuner/TunerInputController.java b/src/com/android/tv/tuner/TunerInputController.java
index d89b6a0c..e06b9b4a 100644
--- a/src/com/android/tv/tuner/TunerInputController.java
+++ b/src/com/android/tv/tuner/TunerInputController.java
@@ -16,30 +16,43 @@
package com.android.tv.tuner;
+import android.app.AlarmManager;
+import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbManager;
-import android.media.tv.TvInputInfo;
-import android.media.tv.TvInputManager;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.os.AsyncTask;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
-import android.support.v4.os.BuildCompat;
+import android.os.SystemClock;
+import android.preference.PreferenceManager;
+import android.support.annotation.NonNull;
+import android.text.TextUtils;
import android.util.Log;
import android.widget.Toast;
import com.android.tv.Features;
+import com.android.tv.R;
import com.android.tv.TvApplication;
-import com.android.tv.tuner.R;
+import com.android.tv.common.SoftPreconditions;
import com.android.tv.tuner.setup.TunerSetupActivity;
import com.android.tv.tuner.tvinput.TunerTvInputService;
+import com.android.tv.tuner.util.SystemPropertiesProxy;
import com.android.tv.tuner.util.TunerInputInfoUtils;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
/**
* Controls the package visibility of {@link TunerTvInputService}.
@@ -48,84 +61,94 @@ import java.util.Map;
* {@code UsbManager.ACTION_USB_DEVICE_ATTACHED}, and {@code UsbManager.ACTION_USB_DEVICE_ATTACHED}
* to update the connection status of the supported USB TV tuners.
*/
-public class TunerInputController extends BroadcastReceiver {
+public class TunerInputController {
private static final boolean DEBUG = true;
private static final String TAG = "TunerInputController";
+ private static final String PREFERENCE_IS_NETWORK_TUNER_ATTACHED = "network_tuner";
+ private static final String SECURITY_PATCH_LEVEL_KEY = "ro.build.version.security_patch";
+ private static final String SECURITY_PATCH_LEVEL_FORMAT = "yyyy-MM-dd";
+
+ /**
+ * Action of {@link Intent} to check network connection repeatedly when it is necessary.
+ */
+ private static final String CHECKING_NETWORK_CONNECTION =
+ "com.android.tv.action.CHECKING_NETWORK_CONNECTION";
+
+ private static final String EXTRA_CHECKING_DURATION =
+ "com.android.tv.action.extra.CHECKING_DURATION";
+
+ private static final long INITIAL_CHECKING_DURATION_MS = TimeUnit.SECONDS.toMillis(10);
+ private static final long MAXIMUM_CHECKING_DURATION_MS = TimeUnit.MINUTES.toMillis(10);
private static final TunerDevice[] TUNER_DEVICES = {
- new TunerDevice(0x2040, 0xb123), // WinTV-HVR-955Q
- new TunerDevice(0x07ca, 0x0837) // AverTV Volar Hybrid Q
+ new TunerDevice(0x2040, 0xb123, null), // WinTV-HVR-955Q
+ new TunerDevice(0x07ca, 0x0837, null), // AverTV Volar Hybrid Q
+ // WinTV-dualHD (bulk) will be supported after 2017 April security patch.
+ new TunerDevice(0x2040, 0x826d, "2017-04-01"), // WinTV-dualHD (bulk)
+ // STOPSHIP: Add WinTV-soloHD (Isoc) temporary for test. Remove this after test complete.
+ new TunerDevice(0x2040, 0x0264, null),
};
private static final int MSG_ENABLE_INPUT_SERVICE = 1000;
private static final long DVB_DRIVER_CHECK_DELAY_MS = 300;
- private DvbDeviceAccessor mDvbDeviceAccessor;
- private final Handler mHandler = new Handler(Looper.getMainLooper()) {
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case MSG_ENABLE_INPUT_SERVICE:
- Context context = (Context) msg.obj;
- if (mDvbDeviceAccessor == null) {
- mDvbDeviceAccessor = new DvbDeviceAccessor(context);
- }
- enableTunerTvInputService(context, mDvbDeviceAccessor.isDvbDeviceAvailable());
- break;
- }
- }
- };
-
/**
- * Simple data holder for a USB device. Used to represent a tuner model, and compare
- * against {@link UsbDevice}.
+ * Checks status of USB devices to see if there are available USB tuners connected.
*/
- private static class TunerDevice {
- private final int vendorId;
- private final int productId;
-
- private TunerDevice(int vendorId, int productId) {
- this.vendorId = vendorId;
- this.productId = productId;
- }
-
- private boolean equals(UsbDevice device) {
- return device.getVendorId() == vendorId && device.getProductId() == productId;
- }
+ public static void onCheckingUsbTunerStatus(Context context, String action) {
+ onCheckingUsbTunerStatus(context, action, new CheckDvbDeviceHandler());
}
- @Override
- public void onReceive(Context context, Intent intent) {
- if (DEBUG) Log.d(TAG, "Broadcast intent received:" + intent);
- TvApplication.setCurrentRunningProcess(context, true);
- if (!Features.TUNER.isEnabled(context)) {
- enableTunerTvInputService(context, false);
+ private static void onCheckingUsbTunerStatus(Context context, String action,
+ @NonNull CheckDvbDeviceHandler handler) {
+ SharedPreferences sharedPreferences =
+ PreferenceManager.getDefaultSharedPreferences(context);
+ if (TunerHal.useBuiltInTuner(context)) {
+ enableTunerTvInputService(context, true, false, TunerHal.TUNER_TYPE_BUILT_IN);
return;
}
+ // Falls back to the below to check USB tuner devices.
+ boolean enabled = isUsbTunerConnected(context);
+ handler.removeMessages(MSG_ENABLE_INPUT_SERVICE);
+ if (enabled) {
+ // Need to check if DVB driver is accessible. Since the driver creation
+ // could be happen after the USB event, delay the checking by
+ // DVB_DRIVER_CHECK_DELAY_MS.
+ handler.sendMessageDelayed(handler.obtainMessage(MSG_ENABLE_INPUT_SERVICE, context),
+ DVB_DRIVER_CHECK_DELAY_MS);
+ } else {
+ if (sharedPreferences.getBoolean(PREFERENCE_IS_NETWORK_TUNER_ATTACHED, false)) {
+ // Since network tuner is attached, do not disable TunerTvInput,
+ // just updates the TvInputInfo.
+ TunerInputInfoUtils.updateTunerInputInfo(context);
+ return;
+ }
+ enableTunerTvInputService(context, false, false, TextUtils
+ .equals(action, UsbManager.ACTION_USB_DEVICE_DETACHED) ?
+ TunerHal.TUNER_TYPE_USB : null);
+ }
+ }
- switch (intent.getAction()) {
- case Intent.ACTION_BOOT_COMPLETED:
- case TvApplication.ACTION_APPLICATION_FIRST_LAUNCHED:
- case UsbManager.ACTION_USB_DEVICE_ATTACHED:
- case UsbManager.ACTION_USB_DEVICE_DETACHED:
- if (TunerInputInfoUtils.isBuiltInTuner(context)) {
- enableTunerTvInputService(context, true);
- break;
- }
- // Falls back to the below to check USB tuner devices.
- boolean enabled = isUsbTunerConnected(context);
- mHandler.removeMessages(MSG_ENABLE_INPUT_SERVICE);
- if (enabled) {
- // Need to check if DVB driver is accessible. Since the driver creation
- // could be happen after the USB event, delay the checking by
- // DVB_DRIVER_CHECK_DELAY_MS.
- mHandler.sendMessageDelayed(
- mHandler.obtainMessage(MSG_ENABLE_INPUT_SERVICE, context),
- DVB_DRIVER_CHECK_DELAY_MS);
- } else {
- enableTunerTvInputService(context, false);
- }
- break;
+ private static void onNetworkTunerChanged(Context context, boolean enabled) {
+ SharedPreferences sharedPreferences =
+ PreferenceManager.getDefaultSharedPreferences(context);
+ if (enabled) {
+ // Network tuner detection is initiated by UI. So the app should not
+ // be killed.
+ sharedPreferences.edit().putBoolean(PREFERENCE_IS_NETWORK_TUNER_ATTACHED, true).apply();
+ enableTunerTvInputService(context, true, true, TunerHal.TUNER_TYPE_NETWORK);
+ } else {
+ sharedPreferences.edit()
+ .putBoolean(PREFERENCE_IS_NETWORK_TUNER_ATTACHED, false).apply();
+ if(!isUsbTunerConnected(context) && !TunerHal.useBuiltInTuner(context)) {
+ // Network tuner detection is initiated by UI. So the app should not
+ // be killed.
+ enableTunerTvInputService(context, false, true, TunerHal.TUNER_TYPE_NETWORK);
+ } else {
+ // Since USB tuner is attached, do not disable TunerTvInput,
+ // just updates the TvInputInfo.
+ TunerInputInfoUtils.updateTunerInputInfo(context);
+ }
}
}
@@ -135,15 +158,18 @@ public class TunerInputController extends BroadcastReceiver {
* @param context {@link Context} instance
* @return {@code true} if any tuner device we support is plugged in
*/
- private boolean isUsbTunerConnected(Context context) {
+ private static boolean isUsbTunerConnected(Context context) {
UsbManager manager = (UsbManager) context.getSystemService(Context.USB_SERVICE);
Map<String, UsbDevice> deviceList = manager.getDeviceList();
+ String currentSecurityLevel =
+ SystemPropertiesProxy.getString(SECURITY_PATCH_LEVEL_KEY, null);
+
for (UsbDevice device : deviceList.values()) {
if (DEBUG) {
Log.d(TAG, "Device: " + device);
}
for (TunerDevice tuner : TUNER_DEVICES) {
- if (tuner.equals(device)) {
+ if (tuner.equals(device) && tuner.isSupported(currentSecurityLevel)) {
Log.i(TAG, "Tuner found");
return true;
}
@@ -158,7 +184,8 @@ public class TunerInputController extends BroadcastReceiver {
* @param context {@link Context} instance
* @param enabled {@code true} to enable the service; otherwise {@code false}
*/
- private void enableTunerTvInputService(Context context, boolean enabled) {
+ private static void enableTunerTvInputService(Context context, boolean enabled,
+ boolean forceDontKillApp, Integer tunerType) {
if (DEBUG) Log.d(TAG, "enableTunerTvInputService: " + enabled);
PackageManager pm = context.getPackageManager();
ComponentName componentName = new ComponentName(context, TunerTvInputService.class);
@@ -170,23 +197,182 @@ public class TunerInputController extends BroadcastReceiver {
// Since PackageManager.DONT_KILL_APP delays the operation by 10 seconds
// (PackageManagerService.BROADCAST_DELAY), we'd better avoid using it. It is used only
// when the LiveChannels app is active since we don't want to kill the running app.
- int flags = TvApplication.getSingletons(context).getMainActivityWrapper().isCreated()
+ int flags = forceDontKillApp
+ || TvApplication.getSingletons(context).getMainActivityWrapper().isCreated()
? PackageManager.DONT_KILL_APP : 0;
int newState = enabled ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED
: PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
if (newState != pm.getComponentEnabledSetting(componentName)) {
- // Send/cancel the USB tuner TV input setup recommendation card.
- TunerSetupActivity.onTvInputEnabled(context, enabled);
+ // Send/cancel the USB tuner TV input setup notification.
+ TunerSetupActivity.onTvInputEnabled(context, enabled, tunerType);
// Enable/disable the USB tuner TV input.
pm.setComponentEnabledSetting(componentName, newState, flags);
- if (!enabled) {
- Toast.makeText(
- context, R.string.msg_usb_device_detached, Toast.LENGTH_SHORT).show();
+ if (!enabled && tunerType != null) {
+ if (tunerType == TunerHal.TUNER_TYPE_USB) {
+ Toast.makeText(context, R.string.msg_usb_tuner_disconnected,
+ Toast.LENGTH_SHORT).show();
+ } else if (tunerType == TunerHal.TUNER_TYPE_NETWORK) {
+ Toast.makeText(context, R.string.msg_network_tuner_disconnected,
+ Toast.LENGTH_SHORT).show();
+ }
}
if (DEBUG) Log.d(TAG, "Status updated:" + enabled);
} else if (enabled) {
- // When # of USB tuners is changed or the device just boots.
+ // When # of tuners is changed or the tuner input service is switching from/to using
+ // network tuners or the device just boots.
TunerInputInfoUtils.updateTunerInputInfo(context);
}
}
+
+ /**
+ * Discovers a network tuner. If the network connection is down, it won't repeatedly checking.
+ */
+ public static void executeNetworkTunerDiscoveryAsyncTask(final Context context) {
+ boolean runningInMainProcess =
+ TvApplication.getSingletons(context).isRunningInMainProcess();
+ SoftPreconditions.checkState(runningInMainProcess);
+ if (!runningInMainProcess) {
+ return;
+ }
+ executeNetworkTunerDiscoveryAsyncTask(context, 0);
+ }
+
+ /**
+ * Discovers a network tuner.
+ * @param context {@link Context}
+ * @param repeatedDurationMs the time length to wait to repeatedly check network status to start
+ * finding network tuner when the network connection is not available.
+ * {@code 0} to disable repeatedly checking.
+ */
+ private static void executeNetworkTunerDiscoveryAsyncTask(final Context context,
+ final long repeatedDurationMs) {
+ if (!Features.NETWORK_TUNER.isEnabled(context)) {
+ return;
+ }
+ new AsyncTask<Void, Void, Boolean>() {
+ @Override
+ protected Boolean doInBackground(Void... params) {
+ if (isNetworkConnected(context)) {
+ // Implement and execute network tuner discovery AsyncTask here.
+ } else if (repeatedDurationMs > 0) {
+ AlarmManager alarmManager =
+ (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
+ Intent networkCheckingIntent = new Intent(context, IntentReceiver.class);
+ networkCheckingIntent.setAction(CHECKING_NETWORK_CONNECTION);
+ networkCheckingIntent.putExtra(EXTRA_CHECKING_DURATION, repeatedDurationMs);
+ PendingIntent alarmIntent = PendingIntent.getBroadcast(
+ context, 0, networkCheckingIntent, PendingIntent.FLAG_UPDATE_CURRENT);
+ alarmManager.set(AlarmManager.ELAPSED_REALTIME, SystemClock.elapsedRealtime()
+ + repeatedDurationMs, alarmIntent);
+ }
+ return null;
+ }
+
+ @Override
+ protected void onPostExecute(Boolean result) {
+ if (result == null) {
+ return;
+ }
+ onNetworkTunerChanged(context, result);
+ }
+ }.execute();
+ }
+
+ private static boolean isNetworkConnected(Context context) {
+ ConnectivityManager cm = (ConnectivityManager)
+ context.getSystemService(Context.CONNECTIVITY_SERVICE);
+ NetworkInfo networkInfo = cm.getActiveNetworkInfo();
+ return networkInfo != null && networkInfo.isConnected();
+ }
+
+ public static class IntentReceiver extends BroadcastReceiver {
+ private final CheckDvbDeviceHandler mHandler = new CheckDvbDeviceHandler();
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (DEBUG) Log.d(TAG, "Broadcast intent received:" + intent);
+ TvApplication.setCurrentRunningProcess(context, true);
+ if (!Features.TUNER.isEnabled(context)) {
+ enableTunerTvInputService(context, false, false, null);
+ return;
+ }
+ switch (intent.getAction()) {
+ case Intent.ACTION_BOOT_COMPLETED:
+ executeNetworkTunerDiscoveryAsyncTask(context, INITIAL_CHECKING_DURATION_MS);
+ case TvApplication.ACTION_APPLICATION_FIRST_LAUNCHED:
+ case UsbManager.ACTION_USB_DEVICE_ATTACHED:
+ case UsbManager.ACTION_USB_DEVICE_DETACHED:
+ onCheckingUsbTunerStatus(context, intent.getAction(), mHandler);
+ break;
+ case CHECKING_NETWORK_CONNECTION:
+ long repeatedDurationMs = intent.getLongExtra(EXTRA_CHECKING_DURATION,
+ INITIAL_CHECKING_DURATION_MS);
+ executeNetworkTunerDiscoveryAsyncTask(context,
+ Math.min(repeatedDurationMs * 2, MAXIMUM_CHECKING_DURATION_MS));
+ break;
+ }
+ }
+ }
+
+ /**
+ * Simple data holder for a USB device. Used to represent a tuner model, and compare
+ * against {@link UsbDevice}.
+ */
+ private static class TunerDevice {
+ private final int vendorId;
+ private final int productId;
+
+ // security patch level from which the specific tuner type is supported.
+ private final String minSecurityLevel;
+
+ private TunerDevice(int vendorId, int productId, String minSecurityLevel) {
+ this.vendorId = vendorId;
+ this.productId = productId;
+ this.minSecurityLevel = minSecurityLevel;
+ }
+
+ private boolean equals(UsbDevice device) {
+ return device.getVendorId() == vendorId && device.getProductId() == productId;
+ }
+
+ private boolean isSupported(String currentSecurityLevel) {
+ if (minSecurityLevel == null) {
+ return true;
+ }
+
+ long supportSecurityLevelTimeStamp = 0;
+ long currentSecurityLevelTimestamp = 0;
+ try {
+ SimpleDateFormat format = new SimpleDateFormat(SECURITY_PATCH_LEVEL_FORMAT);
+ supportSecurityLevelTimeStamp = format.parse(minSecurityLevel).getTime();
+ currentSecurityLevelTimestamp = format.parse(currentSecurityLevel).getTime();
+ } catch (ParseException e) {
+ }
+ return supportSecurityLevelTimeStamp != 0
+ && supportSecurityLevelTimeStamp <= currentSecurityLevelTimestamp;
+ }
+ }
+
+ private static class CheckDvbDeviceHandler extends Handler {
+ private DvbDeviceAccessor mDvbDeviceAccessor;
+
+ CheckDvbDeviceHandler() {
+ super(Looper.getMainLooper());
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_ENABLE_INPUT_SERVICE:
+ Context context = (Context) msg.obj;
+ if (mDvbDeviceAccessor == null) {
+ mDvbDeviceAccessor = new DvbDeviceAccessor(context);
+ }
+ boolean enabled = mDvbDeviceAccessor.isDvbDeviceAvailable();
+ enableTunerTvInputService(
+ context, enabled, false, enabled ? TunerHal.TUNER_TYPE_USB : null);
+ break;
+ }
+ }
+ }
}