1 /* 2 * Copyright (C) 2016 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.hdhomerun; 18 19 import android.content.Context; 20 import android.text.TextUtils; 21 import android.util.Log; 22 import com.android.tv.common.SoftPreconditions; 23 import com.android.tv.common.compat.TvInputConstantCompat; 24 import com.android.tv.tuner.api.Tuner; 25 import com.android.tv.tuner.data.TunerChannel; 26 import java.io.BufferedInputStream; 27 import java.io.IOException; 28 import java.net.HttpURLConnection; 29 import java.net.MalformedURLException; 30 import java.net.URL; 31 import java.net.URLConnection; 32 33 /** Tuner implementation for HdHomeRun */ 34 public class HdHomeRunTunerHal implements Tuner { 35 private static final String TAG = "HdHomeRunTunerHal"; 36 private static final boolean DEBUG = false; 37 38 private static final String CABLECARD_MODEL = "cablecard"; 39 private static final String ATSC_MODEL = "atsc"; 40 private static final String DVBC_MODEL = "dvbc"; 41 private static final String DVBT_MODEL = "dvbt"; 42 43 private final HdHomeRunTunerManager mTunerManager; 44 private HdHomeRunDevice mDevice; 45 private BufferedInputStream mInputStream; 46 private HttpURLConnection mConnection; 47 private String mHttpConnectionAddress; 48 private final Context mContext; 49 50 @DeliverySystemType private int mDeliverySystemType = DELIVERY_SYSTEM_UNDEFINED; 51 52 public static final char VCHANNEL_SEPARATOR = '.'; 53 public static final int CONNECTION_TIMEOUT_MS_FOR_URLCONNECTION = 3000; // 3 sec 54 public static final int READ_TIMEOUT_MS_FOR_URLCONNECTION = 10000; // 10 sec 55 HdHomeRunTunerHal(Context context)56 public HdHomeRunTunerHal(Context context) { 57 mTunerManager = HdHomeRunTunerManager.getInstance(); 58 mContext = context; 59 } 60 61 @Override openFirstAvailable()62 public boolean openFirstAvailable() { 63 SoftPreconditions.checkState(mDevice == null); 64 try { 65 mDevice = mTunerManager.acquireDevice(mContext); 66 if (mDevice != null) { 67 if (mDeliverySystemType == DELIVERY_SYSTEM_UNDEFINED) { 68 mDeliverySystemType = nativeGetDeliverySystemType(getDeviceId()); 69 } 70 } 71 return mDevice != null; 72 } catch (Exception e) { 73 Log.w(TAG, "Failed to open first available device", e); 74 return false; 75 } 76 } 77 78 @Override isDeviceOpen()79 public boolean isDeviceOpen() { 80 return mDevice != null; 81 } 82 83 @Override isReusable()84 public boolean isReusable() { 85 return false; 86 } 87 88 @Override getDeviceId()89 public long getDeviceId() { 90 return mDevice == null ? 0 : mDevice.getDeviceId(); 91 } 92 93 @Override close()94 public void close() throws Exception { 95 closeInputStreamAndDisconnect(); 96 if (mDevice != null) { 97 mTunerManager.releaseDevice(mDevice); 98 mDevice = null; 99 } 100 } 101 102 @Override tune( int frequency, @ModulationType String modulation, String channelNumber)103 public synchronized boolean tune( 104 int frequency, @ModulationType String modulation, String channelNumber) { 105 if (DEBUG) { 106 Log.d( 107 TAG, 108 "tune(frequency=" 109 + frequency 110 + ", modulation=" 111 + modulation 112 + ", channelNumber=" 113 + channelNumber 114 + ")"); 115 } 116 closeInputStreamAndDisconnect(); 117 if (TextUtils.isEmpty(channelNumber)) { 118 return false; 119 } 120 channelNumber = 121 channelNumber.replace(TunerChannel.CHANNEL_NUMBER_SEPARATOR, VCHANNEL_SEPARATOR); 122 mHttpConnectionAddress = "http://" + getIpAddress() + ":5004/auto/v" + channelNumber; 123 return connectAndOpenInputStream(); 124 } 125 connectAndOpenInputStream()126 private boolean connectAndOpenInputStream() { 127 URL url; 128 try { 129 url = new URL(mHttpConnectionAddress); 130 } catch (MalformedURLException e) { 131 Log.e(TAG, "Invalid address: " + mHttpConnectionAddress, e); 132 return false; 133 } 134 URLConnection connection; 135 try { 136 connection = url.openConnection(); 137 connection.setConnectTimeout(CONNECTION_TIMEOUT_MS_FOR_URLCONNECTION); 138 connection.setReadTimeout(READ_TIMEOUT_MS_FOR_URLCONNECTION); 139 if (connection instanceof HttpURLConnection) { 140 mConnection = (HttpURLConnection) connection; 141 } 142 } catch (IOException e) { 143 Log.e(TAG, "Connection failed: " + mHttpConnectionAddress, e); 144 return false; 145 } 146 try { 147 mInputStream = new BufferedInputStream(connection.getInputStream()); 148 } catch (IOException e) { 149 closeInputStreamAndDisconnect(); 150 Log.e(TAG, "Failed to get input stream from " + mHttpConnectionAddress, e); 151 return false; 152 } 153 if (DEBUG) Log.d(TAG, "tuning to " + mHttpConnectionAddress); 154 return true; 155 } 156 157 @Override addPidFilter(int pid, @FilterType int filterType)158 public synchronized boolean addPidFilter(int pid, @FilterType int filterType) { 159 // no-op 160 return true; 161 } 162 163 @Override stopTune()164 public synchronized void stopTune() { 165 closeInputStreamAndDisconnect(); 166 } 167 168 @Override readTsStream(byte[] javaBuffer, int javaBufferSize)169 public synchronized int readTsStream(byte[] javaBuffer, int javaBufferSize) { 170 if (mInputStream != null) { 171 try { 172 // Note: this call sometimes take more than 500ms, because the data is 173 // streamed through network unlike connected tuner devices. 174 return mInputStream.read(javaBuffer, 0, javaBufferSize); 175 } catch (IOException e) { 176 Log.e(TAG, "Failed to read stream", e); 177 closeInputStreamAndDisconnect(); 178 } 179 } 180 if (connectAndOpenInputStream()) { 181 Log.w(TAG, "Tuned by http connection again"); 182 } else { 183 Log.e(TAG, "Tuned by http connection again failed"); 184 } 185 return 0; 186 } 187 188 @Override setHasPendingTune(boolean hasPendingTune)189 public void setHasPendingTune(boolean hasPendingTune) { 190 // no-op 191 } 192 nativeGetDeliverySystemType(long deviceId)193 protected int nativeGetDeliverySystemType(long deviceId) { 194 String deviceModel = mDevice.getDeviceModel(); 195 if (SoftPreconditions.checkState(!TextUtils.isEmpty(deviceModel))) { 196 if (deviceModel.contains(CABLECARD_MODEL) || deviceModel.contains(ATSC_MODEL)) { 197 return DELIVERY_SYSTEM_ATSC; 198 } else if (deviceModel.contains(DVBC_MODEL)) { 199 return DELIVERY_SYSTEM_DVBC; 200 } else if (deviceModel.contains(DVBT_MODEL)) { 201 return DELIVERY_SYSTEM_DVBT; 202 } 203 } 204 return DELIVERY_SYSTEM_UNDEFINED; 205 } 206 closeInputStreamAndDisconnect()207 private void closeInputStreamAndDisconnect() { 208 if (mInputStream != null) { 209 try { 210 mInputStream.close(); 211 } catch (IOException e) { 212 Log.e(TAG, "Failed to close input stream", e); 213 } 214 mInputStream = null; 215 } 216 if (mConnection != null) { 217 mConnection.disconnect(); 218 mConnection = null; 219 } 220 } 221 222 /** Gets the number of tuners in a given HDHomeRun devices. */ getNumberOfDevices()223 public static int getNumberOfDevices() { 224 return HdHomeRunTunerManager.getInstance().getTunerCount(); 225 } 226 227 /** Returns the IP address. */ getIpAddress()228 public String getIpAddress() { 229 return HdHomeRunUtils.getIpString(mDevice.getIpAddress()); 230 } 231 232 /** 233 * Marks the device associated to this instance as a scanned device. Scanned device has higher 234 * priority among multiple HDHomeRun devices. 235 */ markAsScannedDevice(Context context)236 public void markAsScannedDevice(Context context) { 237 HdHomeRunTunerManager.markAsScannedDevice(context, mDevice); 238 } 239 240 @Override 241 @DeliverySystemType getDeliverySystemType()242 public int getDeliverySystemType() { 243 return Tuner.DELIVERY_SYSTEM_UNDEFINED; 244 } 245 246 @Override getSignalStrength()247 public int getSignalStrength() { 248 return TvInputConstantCompat.SIGNAL_STRENGTH_NOT_USED; 249 } 250 } 251