Home | History | Annotate | Download | only in tuner
      1 /*
      2  * Copyright (C) 2015 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.tv.tuner;
     18 
     19 import android.app.AlarmManager;
     20 import android.app.Notification;
     21 import android.app.NotificationChannel;
     22 import android.app.NotificationManager;
     23 import android.app.PendingIntent;
     24 import android.content.BroadcastReceiver;
     25 import android.content.ComponentName;
     26 import android.content.Context;
     27 import android.content.Intent;
     28 import android.content.SharedPreferences;
     29 import android.content.pm.PackageManager;
     30 import android.content.pm.PackageManager.NameNotFoundException;
     31 import android.graphics.Bitmap;
     32 import android.graphics.BitmapFactory;
     33 import android.hardware.usb.UsbDevice;
     34 import android.hardware.usb.UsbManager;
     35 import android.net.ConnectivityManager;
     36 import android.net.NetworkInfo;
     37 import android.net.Uri;
     38 import android.os.AsyncTask;
     39 import android.os.Handler;
     40 import android.os.Looper;
     41 import android.os.Message;
     42 import android.os.SystemClock;
     43 import android.preference.PreferenceManager;
     44 import android.support.annotation.NonNull;
     45 import android.text.TextUtils;
     46 import android.util.Log;
     47 import android.widget.Toast;
     48 import com.android.tv.R;
     49 import com.android.tv.Starter;
     50 import com.android.tv.TvApplication;
     51 import com.android.tv.TvSingletons;
     52 import com.android.tv.common.BuildConfig;
     53 import com.android.tv.common.util.SystemPropertiesProxy;
     54 
     55 
     56 import com.android.tv.tuner.setup.BaseTunerSetupActivity;
     57 import com.android.tv.tuner.util.TunerInputInfoUtils;
     58 import java.text.ParseException;
     59 import java.text.SimpleDateFormat;
     60 import java.util.Collections;
     61 import java.util.HashMap;
     62 import java.util.HashSet;
     63 import java.util.Map;
     64 import java.util.Set;
     65 import java.util.concurrent.TimeUnit;
     66 
     67 /**
     68  * Controls the package visibility of {@link BaseTunerTvInputService}.
     69  *
     70  * <p>Listens to broadcast intent for {@link Intent#ACTION_BOOT_COMPLETED}, {@code
     71  * UsbManager.ACTION_USB_DEVICE_ATTACHED}, and {@code UsbManager.ACTION_USB_DEVICE_ATTACHED} to
     72  * update the connection status of the supported USB TV tuners.
     73  */
     74 public class TunerInputController {
     75     private static final boolean DEBUG = false;
     76     private static final String TAG = "TunerInputController";
     77     private static final String PREFERENCE_IS_NETWORK_TUNER_ATTACHED = "network_tuner";
     78     private static final String SECURITY_PATCH_LEVEL_KEY = "ro.build.version.security_patch";
     79     private static final String SECURITY_PATCH_LEVEL_FORMAT = "yyyy-MM-dd";
     80     private static final String PLAY_STORE_LINK_TEMPLATE = "market://details?id=%s";
     81 
     82     /** Action of {@link Intent} to check network connection repeatedly when it is necessary. */
     83     private static final String CHECKING_NETWORK_TUNER_STATUS =
     84             "com.android.tv.action.CHECKING_NETWORK_TUNER_STATUS";
     85 
     86     private static final String EXTRA_CHECKING_DURATION =
     87             "com.android.tv.action.extra.CHECKING_DURATION";
     88     private static final String EXTRA_DEVICE_IP = "com.android.tv.action.extra.DEVICE_IP";
     89 
     90     private static final long INITIAL_CHECKING_DURATION_MS = TimeUnit.SECONDS.toMillis(10);
     91     private static final long MAXIMUM_CHECKING_DURATION_MS = TimeUnit.MINUTES.toMillis(10);
     92     private static final String NOTIFICATION_CHANNEL_ID = "tuner_discovery_notification";
     93 
     94     // TODO: Load settings from XML file
     95     private static final TunerDevice[] TUNER_DEVICES = {
     96         new TunerDevice(0x2040, 0xb123, null), // WinTV-HVR-955Q
     97         new TunerDevice(0x07ca, 0x0837, null), // AverTV Volar Hybrid Q
     98         // WinTV-dualHD (bulk) will be supported after 2017 April security patch.
     99         new TunerDevice(0x2040, 0x826d, "2017-04-01"), // WinTV-dualHD (bulk)
    100         new TunerDevice(0x2040, 0x0264, null),
    101     };
    102 
    103     private static final int MSG_ENABLE_INPUT_SERVICE = 1000;
    104     private static final long DVB_DRIVER_CHECK_DELAY_MS = 300;
    105 
    106     private final ComponentName usbTunerComponent;
    107     private final ComponentName networkTunerComponent;
    108     private final ComponentName builtInTunerComponent;
    109     private final Map<TunerDevice, ComponentName> mTunerServiceMapping = new HashMap<>();
    110 
    111     private final Map<ComponentName, String> mTunerApplicationNames = new HashMap<>();
    112     private final Map<ComponentName, String> mNotificationMessages = new HashMap<>();
    113     private final Map<ComponentName, Bitmap> mNotificationLargeIcons = new HashMap<>();
    114 
    115     private final CheckDvbDeviceHandler mHandler = new CheckDvbDeviceHandler(this);
    116 
    117     public TunerInputController(ComponentName embeddedTuner) {
    118         usbTunerComponent = embeddedTuner;
    119         networkTunerComponent = usbTunerComponent;
    120         builtInTunerComponent = usbTunerComponent;
    121         for (TunerDevice device : TUNER_DEVICES) {
    122             mTunerServiceMapping.put(device, usbTunerComponent);
    123         }
    124     }
    125 
    126     /** Checks status of USB devices to see if there are available USB tuners connected. */
    127     public void onCheckingUsbTunerStatus(Context context, String action) {
    128         onCheckingUsbTunerStatus(context, action, mHandler);
    129     }
    130 
    131     private void onCheckingUsbTunerStatus(
    132             Context context, String action, @NonNull CheckDvbDeviceHandler handler) {
    133         Set<TunerDevice> connectedUsbTuners = getConnectedUsbTuners(context);
    134         handler.removeMessages(MSG_ENABLE_INPUT_SERVICE);
    135         if (!connectedUsbTuners.isEmpty()) {
    136             // Need to check if DVB driver is accessible. Since the driver creation
    137             // could be happen after the USB event, delay the checking by
    138             // DVB_DRIVER_CHECK_DELAY_MS.
    139             handler.sendMessageDelayed(
    140                     handler.obtainMessage(MSG_ENABLE_INPUT_SERVICE, context),
    141                     DVB_DRIVER_CHECK_DELAY_MS);
    142         } else {
    143             handleTunerStatusChanged(
    144                     context,
    145                     false,
    146                     connectedUsbTuners,
    147                     TextUtils.equals(action, UsbManager.ACTION_USB_DEVICE_DETACHED)
    148                             ? TunerHal.TUNER_TYPE_USB
    149                             : null);
    150         }
    151     }
    152 
    153     private void onNetworkTunerChanged(Context context, boolean enabled) {
    154         SharedPreferences sharedPreferences =
    155                 PreferenceManager.getDefaultSharedPreferences(context);
    156         if (sharedPreferences.contains(PREFERENCE_IS_NETWORK_TUNER_ATTACHED)
    157                 && sharedPreferences.getBoolean(PREFERENCE_IS_NETWORK_TUNER_ATTACHED, false)
    158                         == enabled) {
    159             // the status is not changed
    160             return;
    161         }
    162         if (enabled) {
    163             sharedPreferences.edit().putBoolean(PREFERENCE_IS_NETWORK_TUNER_ATTACHED, true).apply();
    164         } else {
    165             sharedPreferences
    166                     .edit()
    167                     .putBoolean(PREFERENCE_IS_NETWORK_TUNER_ATTACHED, false)
    168                     .apply();
    169         }
    170         // Network tuner detection is initiated by UI. So the app should not
    171         // be killed.
    172         handleTunerStatusChanged(
    173                 context, true, getConnectedUsbTuners(context), TunerHal.TUNER_TYPE_NETWORK);
    174     }
    175 
    176     /**
    177      * See if any USB tuner hardware is attached in the system.
    178      *
    179      * @param context {@link Context} instance
    180      * @return {@code true} if any tuner device we support is plugged in
    181      */
    182     private Set<TunerDevice> getConnectedUsbTuners(Context context) {
    183         UsbManager manager = (UsbManager) context.getSystemService(Context.USB_SERVICE);
    184         Map<String, UsbDevice> deviceList = manager.getDeviceList();
    185         String currentSecurityLevel =
    186                 SystemPropertiesProxy.getString(SECURITY_PATCH_LEVEL_KEY, null);
    187 
    188         Set<TunerDevice> devices = new HashSet<>();
    189         for (UsbDevice device : deviceList.values()) {
    190             if (DEBUG) {
    191                 Log.d(TAG, "Device: " + device);
    192             }
    193             for (TunerDevice tuner : TUNER_DEVICES) {
    194                 if (tuner.equalsTo(device) && tuner.isSupported(currentSecurityLevel)) {
    195                     Log.i(TAG, "Tuner found");
    196                     devices.add(tuner);
    197                 }
    198             }
    199         }
    200         return devices;
    201     }
    202 
    203     private void handleTunerStatusChanged(
    204             Context context,
    205             boolean forceDontKillApp,
    206             Set<TunerDevice> connectedUsbTuners,
    207             Integer triggerType) {
    208         Map<ComponentName, Integer> serviceToEnable = new HashMap<>();
    209         Set<ComponentName> serviceToDisable = new HashSet<>();
    210         serviceToDisable.add(builtInTunerComponent);
    211         serviceToDisable.add(networkTunerComponent);
    212         if (TunerFeatures.TUNER.isEnabled(context)) {
    213             // TODO: support both built-in tuner and other tuners at the same time?
    214             if (TunerHal.useBuiltInTuner(context)) {
    215                 enableTunerTvInputService(
    216                         context, true, false, TunerHal.TUNER_TYPE_BUILT_IN, builtInTunerComponent);
    217                 return;
    218             }
    219             SharedPreferences sharedPreferences =
    220                     PreferenceManager.getDefaultSharedPreferences(context);
    221             if (sharedPreferences.getBoolean(PREFERENCE_IS_NETWORK_TUNER_ATTACHED, false)) {
    222                 serviceToEnable.put(networkTunerComponent, TunerHal.TUNER_TYPE_NETWORK);
    223             }
    224         }
    225         for (TunerDevice device : TUNER_DEVICES) {
    226             if (TunerFeatures.TUNER.isEnabled(context) && connectedUsbTuners.contains(device)) {
    227                 serviceToEnable.put(mTunerServiceMapping.get(device), TunerHal.TUNER_TYPE_USB);
    228             } else {
    229                 serviceToDisable.add(mTunerServiceMapping.get(device));
    230             }
    231         }
    232         serviceToDisable.removeAll(serviceToEnable.keySet());
    233         for (ComponentName serviceComponent : serviceToEnable.keySet()) {
    234             if (isTunerPackageInstalled(context, serviceComponent)) {
    235                 enableTunerTvInputService(
    236                         context,
    237                         true,
    238                         forceDontKillApp,
    239                         serviceToEnable.get(serviceComponent),
    240                         serviceComponent);
    241             } else {
    242                 sendNotificationToInstallPackage(context, serviceComponent);
    243             }
    244         }
    245         for (ComponentName serviceComponent : serviceToDisable) {
    246             if (isTunerPackageInstalled(context, serviceComponent)) {
    247                 enableTunerTvInputService(
    248                         context, false, forceDontKillApp, triggerType, serviceComponent);
    249             } else {
    250                 cancelNotificationToInstallPackage(context, serviceComponent);
    251             }
    252         }
    253     }
    254 
    255     /**
    256      * Enable/disable the component {@link BaseTunerTvInputService}.
    257      *
    258      * @param context {@link Context} instance
    259      * @param enabled {@code true} to enable the service; otherwise {@code false}
    260      */
    261     private static void enableTunerTvInputService(
    262             Context context,
    263             boolean enabled,
    264             boolean forceDontKillApp,
    265             Integer tunerType,
    266             ComponentName serviceComponent) {
    267         if (DEBUG) Log.d(TAG, "enableTunerTvInputService: " + enabled);
    268         PackageManager pm = context.getPackageManager();
    269         int newState =
    270                 enabled
    271                         ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED
    272                         : PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
    273         if (newState != pm.getComponentEnabledSetting(serviceComponent)) {
    274             int flags = forceDontKillApp ? PackageManager.DONT_KILL_APP : 0;
    275             if (serviceComponent.getPackageName().equals(context.getPackageName())) {
    276                 // Don't kill APP when handling input count changing. Or the following
    277                 // setComponentEnabledSetting() call won't work.
    278                 ((TvApplication) context.getApplicationContext())
    279                         .handleInputCountChanged(true, enabled, true);
    280                 // Bundled input. Don't kill app if LiveChannels app is active since we don't want
    281                 // to kill the running app.
    282                 if (TvSingletons.getSingletons(context).getMainActivityWrapper().isCreated()) {
    283                     flags |= PackageManager.DONT_KILL_APP;
    284                 }
    285                 // Send/cancel the USB tuner TV input setup notification.
    286                 BaseTunerSetupActivity.onTvInputEnabled(context, enabled, tunerType);
    287                 if (!enabled && tunerType != null) {
    288                     if (tunerType == TunerHal.TUNER_TYPE_USB) {
    289                         Toast.makeText(
    290                                         context,
    291                                         R.string.msg_usb_tuner_disconnected,
    292                                         Toast.LENGTH_SHORT)
    293                                 .show();
    294                     } else if (tunerType == TunerHal.TUNER_TYPE_NETWORK) {
    295                         Toast.makeText(
    296                                         context,
    297                                         R.string.msg_network_tuner_disconnected,
    298                                         Toast.LENGTH_SHORT)
    299                                 .show();
    300                     }
    301                 }
    302             }
    303             // Enable/disable the USB tuner TV input.
    304             pm.setComponentEnabledSetting(serviceComponent, newState, flags);
    305             if (DEBUG) Log.d(TAG, "Status updated:" + enabled);
    306         } else if (enabled && serviceComponent.getPackageName().equals(context.getPackageName())) {
    307             // When # of tuners is changed or the tuner input service is switching from/to using
    308             // network tuners or the device just boots.
    309             TunerInputInfoUtils.updateTunerInputInfo(context);
    310         }
    311     }
    312 
    313     /**
    314      * Discovers a network tuner. If the network connection is down, it won't repeatedly checking.
    315      */
    316     public void executeNetworkTunerDiscoveryAsyncTask(final Context context) {
    317         executeNetworkTunerDiscoveryAsyncTask(context, 0, 0);
    318     }
    319 
    320     /**
    321      * Discovers a network tuner.
    322      *
    323      * @param context {@link Context}
    324      * @param repeatedDurationMs The time length to wait to repeatedly check network status to start
    325      *     finding network tuner when the network connection is not available. {@code 0} to disable
    326      *     repeatedly checking.
    327      * @param deviceIp The previous discovered device IP, 0 if none.
    328      */
    329     private void executeNetworkTunerDiscoveryAsyncTask(
    330             final Context context, final long repeatedDurationMs, final int deviceIp) {
    331         if (!TunerFeatures.NETWORK_TUNER.isEnabled(context)) {
    332             return;
    333         }
    334         final Intent networkCheckingIntent = new Intent(context, IntentReceiver.class);
    335         networkCheckingIntent.setAction(CHECKING_NETWORK_TUNER_STATUS);
    336         if (!isNetworkConnected(context) && repeatedDurationMs > 0) {
    337             sendCheckingAlarm(context, networkCheckingIntent, repeatedDurationMs);
    338         } else {
    339             new AsyncTask<Void, Void, Boolean>() {
    340                 @Override
    341                 protected Boolean doInBackground(Void... params) {
    342                     Boolean result = null;
    343                     // Implement and execute network tuner discovery AsyncTask here.
    344                     return result;
    345                 }
    346 
    347                 @Override
    348                 protected void onPostExecute(Boolean foundNetworkTuner) {
    349                     if (foundNetworkTuner == null) {
    350                         return;
    351                     }
    352                     sendCheckingAlarm(
    353                             context,
    354                             networkCheckingIntent,
    355                             foundNetworkTuner ? INITIAL_CHECKING_DURATION_MS : repeatedDurationMs);
    356                     onNetworkTunerChanged(context, foundNetworkTuner);
    357                 }
    358             }.execute();
    359         }
    360     }
    361 
    362     private static boolean isNetworkConnected(Context context) {
    363         ConnectivityManager cm =
    364                 (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
    365         NetworkInfo networkInfo = cm.getActiveNetworkInfo();
    366         return networkInfo != null && networkInfo.isConnected();
    367     }
    368 
    369     private static void sendCheckingAlarm(Context context, Intent intent, long delayMs) {
    370         AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
    371         intent.putExtra(EXTRA_CHECKING_DURATION, delayMs);
    372         PendingIntent alarmIntent =
    373                 PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
    374         alarmManager.set(
    375                 AlarmManager.ELAPSED_REALTIME,
    376                 SystemClock.elapsedRealtime() + delayMs,
    377                 alarmIntent);
    378     }
    379 
    380     private static boolean isTunerPackageInstalled(
    381             Context context, ComponentName serviceComponent) {
    382         try {
    383             context.getPackageManager().getPackageInfo(serviceComponent.getPackageName(), 0);
    384             return true;
    385         } catch (NameNotFoundException e) {
    386             return false;
    387         }
    388     }
    389 
    390     private void sendNotificationToInstallPackage(Context context, ComponentName serviceComponent) {
    391         if (!BuildConfig.ENG) {
    392             return;
    393         }
    394         String applicationName = mTunerApplicationNames.get(serviceComponent);
    395         if (applicationName == null) {
    396             applicationName = context.getString(R.string.tuner_install_default_application_name);
    397         }
    398         String contentTitle =
    399                 context.getString(
    400                         R.string.tuner_install_notification_content_title, applicationName);
    401         String contentText = mNotificationMessages.get(serviceComponent);
    402         if (contentText == null) {
    403             contentText = context.getString(R.string.tuner_install_notification_content_text);
    404         }
    405         Bitmap largeIcon = mNotificationLargeIcons.get(serviceComponent);
    406         if (largeIcon == null) {
    407             // TODO: Make a better default image.
    408             largeIcon = BitmapFactory.decodeResource(context.getResources(), R.drawable.ic_store);
    409         }
    410         NotificationManager notificationManager =
    411                 (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
    412         if (notificationManager.getNotificationChannel(NOTIFICATION_CHANNEL_ID) == null) {
    413             createNotificationChannel(context, notificationManager);
    414         }
    415         Intent intent = new Intent(Intent.ACTION_VIEW);
    416         intent.setData(
    417                 Uri.parse(
    418                         String.format(
    419                                 PLAY_STORE_LINK_TEMPLATE, serviceComponent.getPackageName())));
    420         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    421         Notification.Builder builder = new Notification.Builder(context, NOTIFICATION_CHANNEL_ID);
    422         builder.setAutoCancel(true)
    423                 .setSmallIcon(R.drawable.ic_launcher_s)
    424                 .setLargeIcon(largeIcon)
    425                 .setContentTitle(contentTitle)
    426                 .setContentText(contentText)
    427                 .setCategory(Notification.CATEGORY_RECOMMENDATION)
    428                 .setContentIntent(PendingIntent.getActivity(context, 0, intent, 0));
    429         notificationManager.notify(serviceComponent.getPackageName(), 0, builder.build());
    430     }
    431 
    432     private static void cancelNotificationToInstallPackage(
    433             Context context, ComponentName serviceComponent) {
    434         NotificationManager notificationManager =
    435                 (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
    436         notificationManager.cancel(serviceComponent.getPackageName(), 0);
    437     }
    438 
    439     private static void createNotificationChannel(
    440             Context context, NotificationManager notificationManager) {
    441         notificationManager.createNotificationChannel(
    442                 new NotificationChannel(
    443                         NOTIFICATION_CHANNEL_ID,
    444                         context.getResources()
    445                                 .getString(R.string.ut_setup_notification_channel_name),
    446                         NotificationManager.IMPORTANCE_HIGH));
    447     }
    448 
    449     public static class IntentReceiver extends BroadcastReceiver {
    450 
    451         @Override
    452         public void onReceive(Context context, Intent intent) {
    453             if (DEBUG) Log.d(TAG, "Broadcast intent received:" + intent);
    454             Starter.start(context);
    455             TunerInputController tunerInputController =
    456                     TvSingletons.getSingletons(context).getTunerInputController();
    457             if (!TunerFeatures.TUNER.isEnabled(context)) {
    458                 tunerInputController.handleTunerStatusChanged(
    459                         context, false, Collections.emptySet(), null);
    460                 return;
    461             }
    462             switch (intent.getAction()) {
    463                 case Intent.ACTION_BOOT_COMPLETED:
    464                     tunerInputController.executeNetworkTunerDiscoveryAsyncTask(
    465                             context, INITIAL_CHECKING_DURATION_MS, 0);
    466                     // fall through
    467                 case TvApplication.ACTION_APPLICATION_FIRST_LAUNCHED:
    468                 case UsbManager.ACTION_USB_DEVICE_ATTACHED:
    469                 case UsbManager.ACTION_USB_DEVICE_DETACHED:
    470                     tunerInputController.onCheckingUsbTunerStatus(context, intent.getAction());
    471                     break;
    472                 case CHECKING_NETWORK_TUNER_STATUS:
    473                     long repeatedDurationMs =
    474                             intent.getLongExtra(
    475                                     EXTRA_CHECKING_DURATION, INITIAL_CHECKING_DURATION_MS);
    476                     tunerInputController.executeNetworkTunerDiscoveryAsyncTask(
    477                             context,
    478                             Math.min(repeatedDurationMs * 2, MAXIMUM_CHECKING_DURATION_MS),
    479                             intent.getIntExtra(EXTRA_DEVICE_IP, 0));
    480                     break;
    481                 default: // fall out
    482             }
    483         }
    484     }
    485 
    486     /**
    487      * Simple data holder for a USB device. Used to represent a tuner model, and compare against
    488      * {@link UsbDevice}.
    489      */
    490     private static class TunerDevice {
    491         private final int vendorId;
    492         private final int productId;
    493 
    494         // security patch level from which the specific tuner type is supported.
    495         private final String minSecurityLevel;
    496 
    497         private TunerDevice(int vendorId, int productId, String minSecurityLevel) {
    498             this.vendorId = vendorId;
    499             this.productId = productId;
    500             this.minSecurityLevel = minSecurityLevel;
    501         }
    502 
    503         private boolean equalsTo(UsbDevice device) {
    504             return device.getVendorId() == vendorId && device.getProductId() == productId;
    505         }
    506 
    507         private boolean isSupported(String currentSecurityLevel) {
    508             if (minSecurityLevel == null) {
    509                 return true;
    510             }
    511 
    512             long supportSecurityLevelTimeStamp = 0;
    513             long currentSecurityLevelTimestamp = 0;
    514             try {
    515                 SimpleDateFormat format = new SimpleDateFormat(SECURITY_PATCH_LEVEL_FORMAT);
    516                 supportSecurityLevelTimeStamp = format.parse(minSecurityLevel).getTime();
    517                 currentSecurityLevelTimestamp = format.parse(currentSecurityLevel).getTime();
    518             } catch (ParseException e) {
    519             }
    520             return supportSecurityLevelTimeStamp != 0
    521                     && supportSecurityLevelTimeStamp <= currentSecurityLevelTimestamp;
    522         }
    523     }
    524 
    525     private static class CheckDvbDeviceHandler extends Handler {
    526 
    527         private final TunerInputController mTunerInputController;
    528         private DvbDeviceAccessor mDvbDeviceAccessor;
    529 
    530         CheckDvbDeviceHandler(TunerInputController tunerInputController) {
    531             super(Looper.getMainLooper());
    532             this.mTunerInputController = tunerInputController;
    533         }
    534 
    535         @Override
    536         public void handleMessage(Message msg) {
    537             switch (msg.what) {
    538                 case MSG_ENABLE_INPUT_SERVICE:
    539                     Context context = (Context) msg.obj;
    540                     if (mDvbDeviceAccessor == null) {
    541                         mDvbDeviceAccessor = new DvbDeviceAccessor(context);
    542                     }
    543                     boolean enabled = mDvbDeviceAccessor.isDvbDeviceAvailable();
    544                     mTunerInputController.handleTunerStatusChanged(
    545                             context,
    546                             false,
    547                             enabled
    548                                     ? mTunerInputController.getConnectedUsbTuners(context)
    549                                     : Collections.emptySet(),
    550                             TunerHal.TUNER_TYPE_USB);
    551                     break;
    552                 default: // fall out
    553             }
    554         }
    555     }
    556 }
    557