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
17package com.android.usbtuner;
18
19import android.content.BroadcastReceiver;
20import android.content.ComponentName;
21import android.content.Context;
22import android.content.Intent;
23import android.content.pm.PackageManager;
24import android.hardware.usb.UsbDevice;
25import android.hardware.usb.UsbManager;
26import android.media.tv.TvInputInfo;
27import android.media.tv.TvInputManager;
28import android.media.tv.TvInputService;
29import android.os.Build;
30import android.os.Handler;
31import android.os.Looper;
32import android.os.Message;
33import android.support.v4.os.BuildCompat;
34import android.util.Log;
35
36import com.android.tv.Features;
37import com.android.tv.TvApplication;
38import com.android.usbtuner.setup.TunerSetupActivity;
39import com.android.usbtuner.tvinput.UsbTunerTvInputService;
40
41import 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 */
50public 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
89        private TunerDevice(int vendorId, int productId) {
90            this.vendorId = vendorId;
91            this.productId = productId;
92        }
93
94        private boolean equals(UsbDevice device) {
95            return device.getVendorId() == vendorId && device.getProductId() == productId;
96        }
97    }
98
99    @Override
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     */
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     */
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