diff options
Diffstat (limited to 'src/com/android/tv/tuner/TunerInputController.java')
-rw-r--r-- | src/com/android/tv/tuner/TunerInputController.java | 195 |
1 files changed, 176 insertions, 19 deletions
diff --git a/src/com/android/tv/tuner/TunerInputController.java b/src/com/android/tv/tuner/TunerInputController.java index d89b6a0c..65bbbdd0 100644 --- a/src/com/android/tv/tuner/TunerInputController.java +++ b/src/com/android/tv/tuner/TunerInputController.java @@ -16,30 +16,40 @@ 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.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.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.concurrent.TimeUnit; /** * Controls the package visibility of {@link TunerTvInputService}. @@ -51,10 +61,39 @@ import java.util.Map; public class TunerInputController extends BroadcastReceiver { 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. + */ + public static final String CHECKING_NETWORK_CONNECTION = + "com.android.tv.action.CHECKING_NETWORK_CONNECTION"; + + /** + * Action of {@link Intent} when network tuner is attached. + */ + public static final String NETWORK_TUNER_ATTACHED = + "com.android.tv.action.NETWORK_TUNER_ATTACHED"; + + /** + * Action of {@link Intent} when network tuner is detached. + */ + public static final String NETWORK_TUNER_DETACHED = + "com.android.tv.action.NETWORK_TUNER_DETACHED"; + + 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) }; private static final int MSG_ENABLE_INPUT_SERVICE = 1000; @@ -70,7 +109,9 @@ public class TunerInputController extends BroadcastReceiver { if (mDvbDeviceAccessor == null) { mDvbDeviceAccessor = new DvbDeviceAccessor(context); } - enableTunerTvInputService(context, mDvbDeviceAccessor.isDvbDeviceAvailable()); + boolean enabled = mDvbDeviceAccessor.isDvbDeviceAvailable(); + enableTunerTvInputService( + context, enabled, false, enabled ? TunerHal.TUNER_TYPE_USB : null); break; } } @@ -84,14 +125,35 @@ public class TunerInputController extends BroadcastReceiver { private final int vendorId; private final int productId; - private TunerDevice(int vendorId, 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; + } } @Override @@ -99,17 +161,20 @@ public class TunerInputController extends BroadcastReceiver { if (DEBUG) Log.d(TAG, "Broadcast intent received:" + intent); TvApplication.setCurrentRunningProcess(context, true); if (!Features.TUNER.isEnabled(context)) { - enableTunerTvInputService(context, false); + enableTunerTvInputService(context, false, false, null); return; } + SharedPreferences sharedPreferences = + PreferenceManager.getDefaultSharedPreferences(context); 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: - if (TunerInputInfoUtils.isBuiltInTuner(context)) { - enableTunerTvInputService(context, true); + if (TunerHal.useBuiltInTuner(context)) { + enableTunerTvInputService(context, true, false, TunerHal.TUNER_TYPE_BUILT_IN); break; } // Falls back to the below to check USB tuner devices. @@ -123,7 +188,41 @@ public class TunerInputController extends BroadcastReceiver { mHandler.obtainMessage(MSG_ENABLE_INPUT_SERVICE, context), DVB_DRIVER_CHECK_DELAY_MS); } else { - enableTunerTvInputService(context, false); + 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); + break; + } + enableTunerTvInputService(context, false, false, TextUtils + .equals(intent.getAction(), UsbManager.ACTION_USB_DEVICE_DETACHED) ? + TunerHal.TUNER_TYPE_USB : null); + } + 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; + case NETWORK_TUNER_ATTACHED: + // 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); + break; + case NETWORK_TUNER_DETACHED: + 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); } break; } @@ -138,12 +237,15 @@ public class TunerInputController extends BroadcastReceiver { private 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 +260,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 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,7 +273,8 @@ 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; @@ -179,14 +283,67 @@ public class TunerInputController extends BroadcastReceiver { TunerSetupActivity.onTvInputEnabled(context, enabled); // 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) { + 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, Void>() { + @Override + protected Void 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, TunerInputController.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; + } + }.execute(); + } + + private static boolean isNetworkConnected(Context context) { + ConnectivityManager cm = (ConnectivityManager) + context.getSystemService(Context.CONNECTIVITY_SERVICE); + NetworkInfo networkInfo = cm.getActiveNetworkInfo(); + return networkInfo != null && networkInfo.isConnected(); + } } |