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