• 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.PendingIntent;
21 import android.content.BroadcastReceiver;
22 import android.content.ComponentName;
23 import android.content.Context;
24 import android.content.Intent;
25 import android.content.SharedPreferences;
26 import android.content.pm.PackageManager;
27 import android.hardware.usb.UsbDevice;
28 import android.hardware.usb.UsbManager;
29 import android.net.ConnectivityManager;
30 import android.net.NetworkInfo;
31 import android.os.AsyncTask;
32 import android.os.Handler;
33 import android.os.Looper;
34 import android.os.Message;
35 import android.os.SystemClock;
36 import android.preference.PreferenceManager;
37 import android.support.annotation.NonNull;
38 import android.text.TextUtils;
39 import android.util.Log;
40 import android.widget.Toast;
41 
42 import com.android.tv.Features;
43 import com.android.tv.R;
44 import com.android.tv.TvApplication;
45 import com.android.tv.common.SoftPreconditions;
46 import com.android.tv.tuner.setup.TunerSetupActivity;
47 import com.android.tv.tuner.tvinput.TunerTvInputService;
48 import com.android.tv.tuner.util.SystemPropertiesProxy;
49 import com.android.tv.tuner.util.TunerInputInfoUtils;
50 
51 import java.text.ParseException;
52 import java.text.SimpleDateFormat;
53 import java.util.Map;
54 import java.util.Set;
55 import java.util.concurrent.TimeUnit;
56 
57 /**
58  * Controls the package visibility of {@link TunerTvInputService}.
59  * <p>
60  * Listens to broadcast intent for {@link Intent#ACTION_BOOT_COMPLETED},
61  * {@code UsbManager.ACTION_USB_DEVICE_ATTACHED}, and {@code UsbManager.ACTION_USB_DEVICE_ATTACHED}
62  * to update the connection status of the supported USB TV tuners.
63  */
64 public class TunerInputController {
65     private static final boolean DEBUG = true;
66     private static final String TAG = "TunerInputController";
67     private static final String PREFERENCE_IS_NETWORK_TUNER_ATTACHED = "network_tuner";
68     private static final String SECURITY_PATCH_LEVEL_KEY = "ro.build.version.security_patch";
69     private static final String SECURITY_PATCH_LEVEL_FORMAT = "yyyy-MM-dd";
70 
71     /**
72      * Action of {@link Intent} to check network connection repeatedly when it is necessary.
73      */
74     private static final String CHECKING_NETWORK_CONNECTION =
75             "com.android.tv.action.CHECKING_NETWORK_CONNECTION";
76 
77     private static final String EXTRA_CHECKING_DURATION =
78             "com.android.tv.action.extra.CHECKING_DURATION";
79 
80     private static final long INITIAL_CHECKING_DURATION_MS = TimeUnit.SECONDS.toMillis(10);
81     private static final long MAXIMUM_CHECKING_DURATION_MS = TimeUnit.MINUTES.toMillis(10);
82 
83     private static final TunerDevice[] TUNER_DEVICES = {
84         new TunerDevice(0x2040, 0xb123, null), // WinTV-HVR-955Q
85         new TunerDevice(0x07ca, 0x0837, null), // AverTV Volar Hybrid Q
86         // WinTV-dualHD (bulk) will be supported after 2017 April security patch.
87         new TunerDevice(0x2040, 0x826d, "2017-04-01"), // WinTV-dualHD (bulk)
88         // STOPSHIP: Add WinTV-soloHD (Isoc) temporary for test. Remove this after test complete.
89         new TunerDevice(0x2040, 0x0264, null),
90     };
91 
92     private static final int MSG_ENABLE_INPUT_SERVICE = 1000;
93     private static final long DVB_DRIVER_CHECK_DELAY_MS = 300;
94 
95     /**
96      * Checks status of USB devices to see if there are available USB tuners connected.
97      */
onCheckingUsbTunerStatus(Context context, String action)98     public static void onCheckingUsbTunerStatus(Context context, String action) {
99         onCheckingUsbTunerStatus(context, action, new CheckDvbDeviceHandler());
100     }
101 
onCheckingUsbTunerStatus(Context context, String action, @NonNull CheckDvbDeviceHandler handler)102     private static void onCheckingUsbTunerStatus(Context context, String action,
103             @NonNull CheckDvbDeviceHandler handler) {
104         SharedPreferences sharedPreferences =
105                 PreferenceManager.getDefaultSharedPreferences(context);
106         if (TunerHal.useBuiltInTuner(context)) {
107             enableTunerTvInputService(context, true, false, TunerHal.TUNER_TYPE_BUILT_IN);
108             return;
109         }
110         // Falls back to the below to check USB tuner devices.
111         boolean enabled = isUsbTunerConnected(context);
112         handler.removeMessages(MSG_ENABLE_INPUT_SERVICE);
113         if (enabled) {
114             // Need to check if DVB driver is accessible. Since the driver creation
115             // could be happen after the USB event, delay the checking by
116             // DVB_DRIVER_CHECK_DELAY_MS.
117             handler.sendMessageDelayed(handler.obtainMessage(MSG_ENABLE_INPUT_SERVICE, context),
118                     DVB_DRIVER_CHECK_DELAY_MS);
119         } else {
120             if (sharedPreferences.getBoolean(PREFERENCE_IS_NETWORK_TUNER_ATTACHED, false)) {
121                 // Since network tuner is attached, do not disable TunerTvInput,
122                 // just updates the TvInputInfo.
123                 TunerInputInfoUtils.updateTunerInputInfo(context);
124                 return;
125             }
126             enableTunerTvInputService(context, false, false, TextUtils
127                     .equals(action, UsbManager.ACTION_USB_DEVICE_DETACHED) ?
128                     TunerHal.TUNER_TYPE_USB : null);
129         }
130     }
131 
onNetworkTunerChanged(Context context, boolean enabled)132     private static void onNetworkTunerChanged(Context context, boolean enabled) {
133         SharedPreferences sharedPreferences =
134                 PreferenceManager.getDefaultSharedPreferences(context);
135         if (enabled) {
136             // Network tuner detection is initiated by UI. So the app should not
137             // be killed.
138             sharedPreferences.edit().putBoolean(PREFERENCE_IS_NETWORK_TUNER_ATTACHED, true).apply();
139             enableTunerTvInputService(context, true, true, TunerHal.TUNER_TYPE_NETWORK);
140         } else {
141             sharedPreferences.edit()
142                     .putBoolean(PREFERENCE_IS_NETWORK_TUNER_ATTACHED, false).apply();
143             if(!isUsbTunerConnected(context) && !TunerHal.useBuiltInTuner(context)) {
144                 // Network tuner detection is initiated by UI. So the app should not
145                 // be killed.
146                 enableTunerTvInputService(context, false, true, TunerHal.TUNER_TYPE_NETWORK);
147             } else {
148                 // Since USB tuner is attached, do not disable TunerTvInput,
149                 // just updates the TvInputInfo.
150                 TunerInputInfoUtils.updateTunerInputInfo(context);
151             }
152         }
153     }
154 
155     /**
156      * See if any USB tuner hardware is attached in the system.
157      *
158      * @param context {@link Context} instance
159      * @return {@code true} if any tuner device we support is plugged in
160      */
isUsbTunerConnected(Context context)161     private static boolean isUsbTunerConnected(Context context) {
162         UsbManager manager = (UsbManager) context.getSystemService(Context.USB_SERVICE);
163         Map<String, UsbDevice> deviceList = manager.getDeviceList();
164         String currentSecurityLevel =
165                 SystemPropertiesProxy.getString(SECURITY_PATCH_LEVEL_KEY, null);
166 
167         for (UsbDevice device : deviceList.values()) {
168             if (DEBUG) {
169                 Log.d(TAG, "Device: " + device);
170             }
171             for (TunerDevice tuner : TUNER_DEVICES) {
172                 if (tuner.equals(device) && tuner.isSupported(currentSecurityLevel)) {
173                     Log.i(TAG, "Tuner found");
174                     return true;
175                 }
176             }
177         }
178         return false;
179     }
180 
181     /**
182      * Enable/disable the component {@link TunerTvInputService}.
183      *
184      * @param context {@link Context} instance
185      * @param enabled {@code true} to enable the service; otherwise {@code false}
186      */
enableTunerTvInputService(Context context, boolean enabled, boolean forceDontKillApp, Integer tunerType)187     private static void enableTunerTvInputService(Context context, boolean enabled,
188             boolean forceDontKillApp, Integer tunerType) {
189         if (DEBUG) Log.d(TAG, "enableTunerTvInputService: " + enabled);
190         PackageManager pm  = context.getPackageManager();
191         ComponentName componentName = new ComponentName(context, TunerTvInputService.class);
192 
193         // Don't kill app by enabling/disabling TvActivity. If LC is killed by enabling/disabling
194         // TvActivity, the following pm.setComponentEnabledSetting doesn't work.
195         ((TvApplication) context.getApplicationContext()).handleInputCountChanged(
196                 true, enabled, true);
197         // Since PackageManager.DONT_KILL_APP delays the operation by 10 seconds
198         // (PackageManagerService.BROADCAST_DELAY), we'd better avoid using it. It is used only
199         // when the LiveChannels app is active since we don't want to kill the running app.
200         int flags = forceDontKillApp
201                 || TvApplication.getSingletons(context).getMainActivityWrapper().isCreated()
202                 ? PackageManager.DONT_KILL_APP : 0;
203         int newState = enabled ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED
204                 : PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
205         if (newState != pm.getComponentEnabledSetting(componentName)) {
206             // Send/cancel the USB tuner TV input setup notification.
207             TunerSetupActivity.onTvInputEnabled(context, enabled, tunerType);
208             // Enable/disable the USB tuner TV input.
209             pm.setComponentEnabledSetting(componentName, newState, flags);
210             if (!enabled && tunerType != null) {
211                 if (tunerType == TunerHal.TUNER_TYPE_USB) {
212                     Toast.makeText(context, R.string.msg_usb_tuner_disconnected,
213                             Toast.LENGTH_SHORT).show();
214                 } else if (tunerType == TunerHal.TUNER_TYPE_NETWORK) {
215                     Toast.makeText(context, R.string.msg_network_tuner_disconnected,
216                             Toast.LENGTH_SHORT).show();
217                 }
218             }
219             if (DEBUG) Log.d(TAG, "Status updated:" + enabled);
220         } else if (enabled) {
221             // When # of tuners is changed or the tuner input service is switching from/to using
222             // network tuners or the device just boots.
223             TunerInputInfoUtils.updateTunerInputInfo(context);
224         }
225     }
226 
227     /**
228      * Discovers a network tuner. If the network connection is down, it won't repeatedly checking.
229      */
executeNetworkTunerDiscoveryAsyncTask(final Context context)230     public static void executeNetworkTunerDiscoveryAsyncTask(final Context context) {
231         boolean runningInMainProcess =
232                 TvApplication.getSingletons(context).isRunningInMainProcess();
233         SoftPreconditions.checkState(runningInMainProcess);
234         if (!runningInMainProcess) {
235             return;
236         }
237         executeNetworkTunerDiscoveryAsyncTask(context, 0);
238     }
239 
240     /**
241      * Discovers a network tuner.
242      * @param context {@link Context}
243      * @param repeatedDurationMs the time length to wait to repeatedly check network status to start
244      *                           finding network tuner when the network connection is not available.
245      *                           {@code 0} to disable repeatedly checking.
246      */
executeNetworkTunerDiscoveryAsyncTask(final Context context, final long repeatedDurationMs)247     private static void executeNetworkTunerDiscoveryAsyncTask(final Context context,
248             final long repeatedDurationMs) {
249         if (!Features.NETWORK_TUNER.isEnabled(context)) {
250             return;
251         }
252         new AsyncTask<Void, Void, Boolean>() {
253             @Override
254             protected Boolean doInBackground(Void... params) {
255                 if (isNetworkConnected(context)) {
256                     // Implement and execute network tuner discovery AsyncTask here.
257                 } else if (repeatedDurationMs > 0) {
258                     AlarmManager alarmManager =
259                             (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
260                     Intent networkCheckingIntent = new Intent(context, IntentReceiver.class);
261                     networkCheckingIntent.setAction(CHECKING_NETWORK_CONNECTION);
262                     networkCheckingIntent.putExtra(EXTRA_CHECKING_DURATION, repeatedDurationMs);
263                     PendingIntent alarmIntent = PendingIntent.getBroadcast(
264                             context, 0, networkCheckingIntent, PendingIntent.FLAG_UPDATE_CURRENT);
265                     alarmManager.set(AlarmManager.ELAPSED_REALTIME, SystemClock.elapsedRealtime()
266                             + repeatedDurationMs, alarmIntent);
267                 }
268                 return null;
269             }
270 
271             @Override
272             protected void onPostExecute(Boolean result) {
273                 if (result == null) {
274                     return;
275                 }
276                 onNetworkTunerChanged(context, result);
277             }
278         }.execute();
279     }
280 
isNetworkConnected(Context context)281     private static boolean isNetworkConnected(Context context) {
282         ConnectivityManager cm = (ConnectivityManager)
283                 context.getSystemService(Context.CONNECTIVITY_SERVICE);
284         NetworkInfo networkInfo = cm.getActiveNetworkInfo();
285         return networkInfo != null && networkInfo.isConnected();
286     }
287 
288     public static class IntentReceiver extends BroadcastReceiver {
289         private final CheckDvbDeviceHandler mHandler = new CheckDvbDeviceHandler();
290 
291         @Override
onReceive(Context context, Intent intent)292         public void onReceive(Context context, Intent intent) {
293             if (DEBUG) Log.d(TAG, "Broadcast intent received:" + intent);
294             TvApplication.setCurrentRunningProcess(context, true);
295             if (!Features.TUNER.isEnabled(context)) {
296                 enableTunerTvInputService(context, false, false, null);
297                 return;
298             }
299             switch (intent.getAction()) {
300                 case Intent.ACTION_BOOT_COMPLETED:
301                     executeNetworkTunerDiscoveryAsyncTask(context, INITIAL_CHECKING_DURATION_MS);
302                 case TvApplication.ACTION_APPLICATION_FIRST_LAUNCHED:
303                 case UsbManager.ACTION_USB_DEVICE_ATTACHED:
304                 case UsbManager.ACTION_USB_DEVICE_DETACHED:
305                     onCheckingUsbTunerStatus(context, intent.getAction(), mHandler);
306                     break;
307                 case CHECKING_NETWORK_CONNECTION:
308                     long repeatedDurationMs = intent.getLongExtra(EXTRA_CHECKING_DURATION,
309                             INITIAL_CHECKING_DURATION_MS);
310                     executeNetworkTunerDiscoveryAsyncTask(context,
311                             Math.min(repeatedDurationMs * 2, MAXIMUM_CHECKING_DURATION_MS));
312                     break;
313             }
314         }
315     }
316 
317     /**
318      * Simple data holder for a USB device. Used to represent a tuner model, and compare
319      * against {@link UsbDevice}.
320      */
321     private static class TunerDevice {
322         private final int vendorId;
323         private final int productId;
324 
325         // security patch level from which the specific tuner type is supported.
326         private final String minSecurityLevel;
327 
TunerDevice(int vendorId, int productId, String minSecurityLevel)328         private TunerDevice(int vendorId, int productId, String minSecurityLevel) {
329             this.vendorId = vendorId;
330             this.productId = productId;
331             this.minSecurityLevel = minSecurityLevel;
332         }
333 
equals(UsbDevice device)334         private boolean equals(UsbDevice device) {
335             return device.getVendorId() == vendorId && device.getProductId() == productId;
336         }
337 
isSupported(String currentSecurityLevel)338         private boolean isSupported(String currentSecurityLevel) {
339             if (minSecurityLevel == null) {
340                 return true;
341             }
342 
343             long supportSecurityLevelTimeStamp = 0;
344             long currentSecurityLevelTimestamp = 0;
345             try {
346                 SimpleDateFormat format = new SimpleDateFormat(SECURITY_PATCH_LEVEL_FORMAT);
347                 supportSecurityLevelTimeStamp = format.parse(minSecurityLevel).getTime();
348                 currentSecurityLevelTimestamp = format.parse(currentSecurityLevel).getTime();
349             } catch (ParseException e) {
350             }
351             return supportSecurityLevelTimeStamp != 0
352                     && supportSecurityLevelTimeStamp <= currentSecurityLevelTimestamp;
353         }
354     }
355 
356     private static class CheckDvbDeviceHandler extends Handler {
357         private DvbDeviceAccessor mDvbDeviceAccessor;
358 
CheckDvbDeviceHandler()359         CheckDvbDeviceHandler() {
360             super(Looper.getMainLooper());
361         }
362 
363         @Override
handleMessage(Message msg)364         public void handleMessage(Message msg) {
365             switch (msg.what) {
366                 case MSG_ENABLE_INPUT_SERVICE:
367                     Context context = (Context) msg.obj;
368                     if (mDvbDeviceAccessor == null) {
369                         mDvbDeviceAccessor = new DvbDeviceAccessor(context);
370                     }
371                     boolean enabled = mDvbDeviceAccessor.isDvbDeviceAvailable();
372                     enableTunerTvInputService(
373                             context, enabled, false, enabled ? TunerHal.TUNER_TYPE_USB : null);
374                     break;
375             }
376         }
377     }
378 }
379