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.usbtuner;
18 
19 import android.content.BroadcastReceiver;
20 import android.content.ComponentName;
21 import android.content.Context;
22 import android.content.Intent;
23 import android.content.pm.PackageManager;
24 import android.hardware.usb.UsbDevice;
25 import android.hardware.usb.UsbManager;
26 import android.media.tv.TvInputInfo;
27 import android.media.tv.TvInputManager;
28 import android.media.tv.TvInputService;
29 import android.os.Build;
30 import android.os.Handler;
31 import android.os.Looper;
32 import android.os.Message;
33 import android.support.v4.os.BuildCompat;
34 import android.util.Log;
35 
36 import com.android.tv.Features;
37 import com.android.tv.TvApplication;
38 import com.android.usbtuner.setup.TunerSetupActivity;
39 import com.android.usbtuner.tvinput.UsbTunerTvInputService;
40 
41 import java.util.Map;
42 
43 /**
44  * Controls the package visibility of {@link UsbTunerTvInputService}.
45  * <p>
46  * Listens to broadcast intent for {@link Intent#ACTION_BOOT_COMPLETED},
47  * {@code UsbManager.ACTION_USB_DEVICE_ATTACHED}, and {@code UsbManager.ACTION_USB_DEVICE_ATTACHED}
48  * to update the connection status of the supported USB TV tuners.
49  */
50 public class UsbInputController extends BroadcastReceiver {
51     private static final boolean DEBUG = true;
52     private static final String TAG = "UsbInputController";
53 
54     private static final TunerDevice[] TUNER_DEVICES = {
55         new TunerDevice(0x2040, 0xb123),  // WinTV-HVR-955Q
56         new TunerDevice(0x07ca, 0x0837)   // AverTV Volar Hybrid Q
57     };
58 
59     private static final boolean IS_MNC_OR_LATER = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M;
60 
61     private static final int MSG_ENABLE_INPUT_SERVICE = 1000;
62     private static final long DVB_DRIVER_CHECK_DELAY_MS = 300;
63 
64     private DvbDeviceAccessor mDvbDeviceAccessor;
65     private final Handler mHandler = new Handler(Looper.getMainLooper()) {
66         @Override
67         public void handleMessage(Message msg) {
68             switch (msg.what) {
69                 case MSG_ENABLE_INPUT_SERVICE:
70                     Context context = (Context) msg.obj;
71                     if (mDvbDeviceAccessor == null) {
72                         mDvbDeviceAccessor = new DvbDeviceAccessor(context);
73                     }
74                     enableUsbTunerTvInputService(context,
75                             mDvbDeviceAccessor.isDvbDeviceAvailable());
76                     break;
77             }
78         }
79     };
80 
81     /**
82      * Simple data holder for a USB device. Used to represent a tuner model, and compare
83      * against {@link UsbDevice}.
84      */
85     private static class TunerDevice {
86         private final int vendorId;
87         private final int productId;
88 
TunerDevice(int vendorId, int productId)89         private TunerDevice(int vendorId, int productId) {
90             this.vendorId = vendorId;
91             this.productId = productId;
92         }
93 
equals(UsbDevice device)94         private boolean equals(UsbDevice device) {
95             return device.getVendorId() == vendorId && device.getProductId() == productId;
96         }
97     }
98 
99     @Override
onReceive(Context context, Intent intent)100     public void onReceive(Context context, Intent intent) {
101         if (DEBUG) Log.d(TAG, "Broadcast intent received:" + intent);
102 
103         if (!Features.USB_TUNER.isEnabled(context)) {
104             enableUsbTunerTvInputService(context, false);
105             return;
106         }
107 
108         switch (intent.getAction()) {
109             case Intent.ACTION_BOOT_COMPLETED:
110             case UsbManager.ACTION_USB_DEVICE_ATTACHED:
111             case UsbManager.ACTION_USB_DEVICE_DETACHED:
112                 // Tuner is supported on MNC and later version only.
113                 boolean enabled = IS_MNC_OR_LATER && isTunerConnected(context);
114                 mHandler.removeMessages(MSG_ENABLE_INPUT_SERVICE);
115                 if (enabled) {
116                     // Need to check if DVB driver is accessible. Since the driver creation
117                     // could be happen after the USB event, delay the checking by
118                     // DVB_DRIVER_CHECK_DELAY_MS.
119                     mHandler.sendMessageDelayed(
120                             mHandler.obtainMessage(MSG_ENABLE_INPUT_SERVICE, context),
121                             DVB_DRIVER_CHECK_DELAY_MS);
122                 } else {
123                     enableUsbTunerTvInputService(context, false);
124                 }
125                 break;
126         }
127     }
128 
129     /**
130      * See if any USB tuner hardware is attached in the system.
131      *
132      * @param context {@link Context} instance
133      * @return {@code true} if any tuner device we support is plugged in
134      */
isTunerConnected(Context context)135     private boolean isTunerConnected(Context context) {
136         UsbManager manager = (UsbManager) context.getSystemService(Context.USB_SERVICE);
137         Map<String, UsbDevice> deviceList = manager.getDeviceList();
138         for (UsbDevice device : deviceList.values()) {
139             if (DEBUG) {
140                 Log.d(TAG, "Device: " + device);
141             }
142             for (TunerDevice tuner : TUNER_DEVICES) {
143                 if (tuner.equals(device)) {
144                     Log.i(TAG, "Tuner found");
145                     return true;
146                 }
147             }
148         }
149         return false;
150     }
151 
152     /**
153      * Enable/disable the component {@link UsbTunerTvInputService}.
154      *
155      * @param context {@link Context} instance
156      * @param enabled {@code true} to enable the service; otherwise {@code false}
157      */
enableUsbTunerTvInputService(Context context, boolean enabled)158     private void enableUsbTunerTvInputService(Context context, boolean enabled) {
159         PackageManager pm  = context.getPackageManager();
160         ComponentName USBTUNER = new ComponentName(context, UsbTunerTvInputService.class);
161 
162         // Don't kill app by enabling/disabling TvActivity. If LC is killed by enabling/disabling
163         // TvActivity, the following pm.setComponentEnabledSetting doesn't work.
164         ((TvApplication) context.getApplicationContext()).handleInputCountChanged(
165                 true, enabled, true);
166         // Since PackageManager.DONT_KILL_APP delays the operation by 10 seconds
167         // (PackageManagerService.BROADCAST_DELAY), we'd better avoid using it. It is used only
168         // when the LiveChannels app is active since we don't want to kill the running app.
169         int flags = TvApplication.getSingletons(context).getMainActivityWrapper().isCreated()
170                 ? PackageManager.DONT_KILL_APP : 0;
171         int newState = enabled ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED
172                 : PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
173         if (newState != pm.getComponentEnabledSetting(USBTUNER)) {
174             // Send/cancel the USB tuner TV input setup recommendation card.
175             TunerSetupActivity.onTvInputEnabled(context, enabled);
176             // Enable/disable the USB tuner TV input.
177             pm.setComponentEnabledSetting(USBTUNER, newState, flags);
178             if (DEBUG) Log.d(TAG, "Status updated:" + enabled);
179         }
180         if (enabled && BuildCompat.isAtLeastN()) {
181             TvInputInfo info = mDvbDeviceAccessor.buildTvInputInfo(context);
182             if (info != null) {
183                 Log.i(TAG, "TvInputInfo updated: " + info.toString());
184                 ((TvInputManager) context.getSystemService(Context.TV_INPUT_SERVICE))
185                         .updateTvInputInfo(info);
186             }
187         }
188     }
189 }
190