• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
TunerInputController(ComponentName embeddedTuner)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. */
onCheckingUsbTunerStatus(Context context, String action)127     public void onCheckingUsbTunerStatus(Context context, String action) {
128         onCheckingUsbTunerStatus(context, action, mHandler);
129     }
130 
onCheckingUsbTunerStatus( Context context, String action, @NonNull CheckDvbDeviceHandler handler)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 
onNetworkTunerChanged(Context context, boolean enabled)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      */
getConnectedUsbTuners(Context context)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 
handleTunerStatusChanged( Context context, boolean forceDontKillApp, Set<TunerDevice> connectedUsbTuners, Integer triggerType)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      */
enableTunerTvInputService( Context context, boolean enabled, boolean forceDontKillApp, Integer tunerType, ComponentName serviceComponent)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      */
executeNetworkTunerDiscoveryAsyncTask(final Context context)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      */
executeNetworkTunerDiscoveryAsyncTask( final Context context, final long repeatedDurationMs, final int deviceIp)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 
isNetworkConnected(Context context)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 
sendCheckingAlarm(Context context, Intent intent, long delayMs)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 
isTunerPackageInstalled( Context context, ComponentName serviceComponent)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 
sendNotificationToInstallPackage(Context context, ComponentName serviceComponent)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 
cancelNotificationToInstallPackage( Context context, ComponentName serviceComponent)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 
createNotificationChannel( Context context, NotificationManager notificationManager)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
onReceive(Context context, Intent intent)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 
TunerDevice(int vendorId, int productId, String minSecurityLevel)497         private TunerDevice(int vendorId, int productId, String minSecurityLevel) {
498             this.vendorId = vendorId;
499             this.productId = productId;
500             this.minSecurityLevel = minSecurityLevel;
501         }
502 
equalsTo(UsbDevice device)503         private boolean equalsTo(UsbDevice device) {
504             return device.getVendorId() == vendorId && device.getProductId() == productId;
505         }
506 
isSupported(String currentSecurityLevel)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 
CheckDvbDeviceHandler(TunerInputController tunerInputController)530         CheckDvbDeviceHandler(TunerInputController tunerInputController) {
531             super(Looper.getMainLooper());
532             this.mTunerInputController = tunerInputController;
533         }
534 
535         @Override
handleMessage(Message msg)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