| /* |
| * Copyright (C) 2015 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.usbtuner; |
| |
| import android.content.BroadcastReceiver; |
| import android.content.ComponentName; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.pm.PackageManager; |
| import android.hardware.usb.UsbDevice; |
| import android.hardware.usb.UsbManager; |
| import android.media.tv.TvInputInfo; |
| import android.media.tv.TvInputManager; |
| import android.media.tv.TvInputService; |
| import android.os.Build; |
| import android.os.Handler; |
| import android.os.Looper; |
| import android.os.Message; |
| import android.support.v4.os.BuildCompat; |
| import android.util.Log; |
| |
| import com.android.tv.Features; |
| import com.android.tv.TvApplication; |
| import com.android.usbtuner.setup.TunerSetupActivity; |
| import com.android.usbtuner.tvinput.UsbTunerTvInputService; |
| |
| import java.util.Map; |
| |
| /** |
| * Controls the package visibility of {@link UsbTunerTvInputService}. |
| * <p> |
| * Listens to broadcast intent for {@link Intent#ACTION_BOOT_COMPLETED}, |
| * {@code UsbManager.ACTION_USB_DEVICE_ATTACHED}, and {@code UsbManager.ACTION_USB_DEVICE_ATTACHED} |
| * to update the connection status of the supported USB TV tuners. |
| */ |
| public class UsbInputController extends BroadcastReceiver { |
| private static final boolean DEBUG = true; |
| private static final String TAG = "UsbInputController"; |
| |
| private static final TunerDevice[] TUNER_DEVICES = { |
| new TunerDevice(0x2040, 0xb123), // WinTV-HVR-955Q |
| new TunerDevice(0x07ca, 0x0837) // AverTV Volar Hybrid Q |
| }; |
| |
| private static final boolean IS_MNC_OR_LATER = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M; |
| |
| private static final int MSG_ENABLE_INPUT_SERVICE = 1000; |
| private static final long DVB_DRIVER_CHECK_DELAY_MS = 300; |
| |
| private DvbDeviceAccessor mDvbDeviceAccessor; |
| private final Handler mHandler = new Handler(Looper.getMainLooper()) { |
| @Override |
| public void handleMessage(Message msg) { |
| switch (msg.what) { |
| case MSG_ENABLE_INPUT_SERVICE: |
| Context context = (Context) msg.obj; |
| if (mDvbDeviceAccessor == null) { |
| mDvbDeviceAccessor = new DvbDeviceAccessor(context); |
| } |
| enableUsbTunerTvInputService(context, |
| mDvbDeviceAccessor.isDvbDeviceAvailable()); |
| break; |
| } |
| } |
| }; |
| |
| /** |
| * Simple data holder for a USB device. Used to represent a tuner model, and compare |
| * against {@link UsbDevice}. |
| */ |
| private static class TunerDevice { |
| private final int vendorId; |
| private final int productId; |
| |
| private TunerDevice(int vendorId, int productId) { |
| this.vendorId = vendorId; |
| this.productId = productId; |
| } |
| |
| private boolean equals(UsbDevice device) { |
| return device.getVendorId() == vendorId && device.getProductId() == productId; |
| } |
| } |
| |
| @Override |
| public void onReceive(Context context, Intent intent) { |
| if (DEBUG) Log.d(TAG, "Broadcast intent received:" + intent); |
| |
| if (!Features.USB_TUNER.isEnabled(context)) { |
| enableUsbTunerTvInputService(context, false); |
| return; |
| } |
| |
| switch (intent.getAction()) { |
| case Intent.ACTION_BOOT_COMPLETED: |
| case UsbManager.ACTION_USB_DEVICE_ATTACHED: |
| case UsbManager.ACTION_USB_DEVICE_DETACHED: |
| // Tuner is supported on MNC and later version only. |
| boolean enabled = IS_MNC_OR_LATER && isTunerConnected(context); |
| mHandler.removeMessages(MSG_ENABLE_INPUT_SERVICE); |
| if (enabled) { |
| // Need to check if DVB driver is accessible. Since the driver creation |
| // could be happen after the USB event, delay the checking by |
| // DVB_DRIVER_CHECK_DELAY_MS. |
| mHandler.sendMessageDelayed( |
| mHandler.obtainMessage(MSG_ENABLE_INPUT_SERVICE, context), |
| DVB_DRIVER_CHECK_DELAY_MS); |
| } else { |
| enableUsbTunerTvInputService(context, false); |
| } |
| break; |
| } |
| } |
| |
| /** |
| * See if any USB tuner hardware is attached in the system. |
| * |
| * @param context {@link Context} instance |
| * @return {@code true} if any tuner device we support is plugged in |
| */ |
| private boolean isTunerConnected(Context context) { |
| UsbManager manager = (UsbManager) context.getSystemService(Context.USB_SERVICE); |
| Map<String, UsbDevice> deviceList = manager.getDeviceList(); |
| for (UsbDevice device : deviceList.values()) { |
| if (DEBUG) { |
| Log.d(TAG, "Device: " + device); |
| } |
| for (TunerDevice tuner : TUNER_DEVICES) { |
| if (tuner.equals(device)) { |
| Log.i(TAG, "Tuner found"); |
| return true; |
| } |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Enable/disable the component {@link UsbTunerTvInputService}. |
| * |
| * @param context {@link Context} instance |
| * @param enabled {@code true} to enable the service; otherwise {@code false} |
| */ |
| private void enableUsbTunerTvInputService(Context context, boolean enabled) { |
| PackageManager pm = context.getPackageManager(); |
| ComponentName USBTUNER = new ComponentName(context, UsbTunerTvInputService.class); |
| |
| // Don't kill app by enabling/disabling TvActivity. If LC is killed by enabling/disabling |
| // TvActivity, the following pm.setComponentEnabledSetting doesn't work. |
| ((TvApplication) context.getApplicationContext()).handleInputCountChanged( |
| true, enabled, true); |
| // Since PackageManager.DONT_KILL_APP delays the operation by 10 seconds |
| // (PackageManagerService.BROADCAST_DELAY), we'd better avoid using it. It is used only |
| // when the LiveChannels app is active since we don't want to kill the running app. |
| int flags = TvApplication.getSingletons(context).getMainActivityWrapper().isCreated() |
| ? PackageManager.DONT_KILL_APP : 0; |
| int newState = enabled ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED |
| : PackageManager.COMPONENT_ENABLED_STATE_DISABLED; |
| if (newState != pm.getComponentEnabledSetting(USBTUNER)) { |
| // Send/cancel the USB tuner TV input setup recommendation card. |
| TunerSetupActivity.onTvInputEnabled(context, enabled); |
| // Enable/disable the USB tuner TV input. |
| pm.setComponentEnabledSetting(USBTUNER, newState, flags); |
| if (DEBUG) Log.d(TAG, "Status updated:" + enabled); |
| } |
| if (enabled && BuildCompat.isAtLeastN()) { |
| TvInputInfo info = mDvbDeviceAccessor.buildTvInputInfo(context); |
| if (info != null) { |
| Log.i(TAG, "TvInputInfo updated: " + info.toString()); |
| ((TvInputManager) context.getSystemService(Context.TV_INPUT_SERVICE)) |
| .updateTvInputInfo(info); |
| } |
| } |
| } |
| } |