1 /* 2 * Copyright 2019 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 android.media.tv.tuner; 18 19 import android.annotation.BytesLong; 20 import android.annotation.CallbackExecutor; 21 import android.annotation.IntDef; 22 import android.annotation.NonNull; 23 import android.annotation.Nullable; 24 import android.annotation.RequiresPermission; 25 import android.annotation.SystemApi; 26 import android.app.ActivityManager; 27 import android.content.Context; 28 import android.hardware.tv.tuner.V1_0.Constants; 29 import android.media.tv.TvInputService; 30 import android.media.tv.tuner.dvr.DvrPlayback; 31 import android.media.tv.tuner.dvr.DvrRecorder; 32 import android.media.tv.tuner.dvr.OnPlaybackStatusChangedListener; 33 import android.media.tv.tuner.dvr.OnRecordStatusChangedListener; 34 import android.media.tv.tuner.filter.Filter; 35 import android.media.tv.tuner.filter.Filter.Subtype; 36 import android.media.tv.tuner.filter.Filter.Type; 37 import android.media.tv.tuner.filter.FilterCallback; 38 import android.media.tv.tuner.filter.TimeFilter; 39 import android.media.tv.tuner.frontend.Atsc3PlpInfo; 40 import android.media.tv.tuner.frontend.FrontendInfo; 41 import android.media.tv.tuner.frontend.FrontendSettings; 42 import android.media.tv.tuner.frontend.FrontendStatus; 43 import android.media.tv.tuner.frontend.FrontendStatus.FrontendStatusType; 44 import android.media.tv.tuner.frontend.OnTuneEventListener; 45 import android.media.tv.tuner.frontend.ScanCallback; 46 import android.media.tv.tunerresourcemanager.ResourceClientProfile; 47 import android.media.tv.tunerresourcemanager.TunerDemuxRequest; 48 import android.media.tv.tunerresourcemanager.TunerDescramblerRequest; 49 import android.media.tv.tunerresourcemanager.TunerFrontendInfo; 50 import android.media.tv.tunerresourcemanager.TunerFrontendRequest; 51 import android.media.tv.tunerresourcemanager.TunerLnbRequest; 52 import android.media.tv.tunerresourcemanager.TunerResourceManager; 53 import android.os.Handler; 54 import android.os.HandlerExecutor; 55 import android.os.Looper; 56 import android.os.Message; 57 import android.util.Log; 58 59 import com.android.internal.util.FrameworkStatsLog; 60 61 import java.lang.annotation.Retention; 62 import java.lang.annotation.RetentionPolicy; 63 import java.util.ArrayList; 64 import java.util.HashMap; 65 import java.util.List; 66 import java.util.Map; 67 import java.util.Objects; 68 import java.util.concurrent.Executor; 69 70 /** 71 * This class is used to interact with hardware tuners devices. 72 * 73 * <p> Each TvInputService Session should create one instance of this class. 74 * 75 * <p> This class controls the TIS interaction with Tuner HAL. 76 * 77 * @hide 78 */ 79 @SystemApi 80 public class Tuner implements AutoCloseable { 81 /** 82 * Invalid TS packet ID. 83 */ 84 public static final int INVALID_TS_PID = Constants.Constant.INVALID_TS_PID; 85 /** 86 * Invalid stream ID. 87 */ 88 public static final int INVALID_STREAM_ID = Constants.Constant.INVALID_STREAM_ID; 89 /** 90 * Invalid filter ID. 91 */ 92 public static final int INVALID_FILTER_ID = Constants.Constant.INVALID_FILTER_ID; 93 /** 94 * Invalid AV Sync ID. 95 */ 96 public static final int INVALID_AV_SYNC_ID = Constants.Constant.INVALID_AV_SYNC_ID; 97 /** 98 * Invalid timestamp. 99 * 100 * <p>Returned by {@link android.media.tv.tuner.filter.TimeFilter#getSourceTime()}, 101 * {@link android.media.tv.tuner.filter.TimeFilter#getTimeStamp()}, or 102 * {@link Tuner#getAvSyncTime(int)} when the requested timestamp is not available. 103 * 104 * @see android.media.tv.tuner.filter.TimeFilter#getSourceTime() 105 * @see android.media.tv.tuner.filter.TimeFilter#getTimeStamp() 106 * @see Tuner#getAvSyncTime(int) 107 */ 108 public static final long INVALID_TIMESTAMP = -1L; 109 110 111 /** @hide */ 112 @IntDef(prefix = "SCAN_TYPE_", value = {SCAN_TYPE_UNDEFINED, SCAN_TYPE_AUTO, SCAN_TYPE_BLIND}) 113 @Retention(RetentionPolicy.SOURCE) 114 public @interface ScanType {} 115 /** 116 * Scan type undefined. 117 */ 118 public static final int SCAN_TYPE_UNDEFINED = Constants.FrontendScanType.SCAN_UNDEFINED; 119 /** 120 * Scan type auto. 121 * 122 * <p> Tuner will send {@link android.media.tv.tuner.frontend.ScanCallback#onLocked} 123 */ 124 public static final int SCAN_TYPE_AUTO = Constants.FrontendScanType.SCAN_AUTO; 125 /** 126 * Blind scan. 127 * 128 * <p>Frequency range is not specified. The {@link android.media.tv.tuner.Tuner} will scan an 129 * implementation specific range. 130 */ 131 public static final int SCAN_TYPE_BLIND = Constants.FrontendScanType.SCAN_BLIND; 132 133 134 /** @hide */ 135 @IntDef({RESULT_SUCCESS, RESULT_UNAVAILABLE, RESULT_NOT_INITIALIZED, RESULT_INVALID_STATE, 136 RESULT_INVALID_ARGUMENT, RESULT_OUT_OF_MEMORY, RESULT_UNKNOWN_ERROR}) 137 @Retention(RetentionPolicy.SOURCE) 138 public @interface Result {} 139 140 /** 141 * Operation succeeded. 142 */ 143 public static final int RESULT_SUCCESS = Constants.Result.SUCCESS; 144 /** 145 * Operation failed because the corresponding resources are not available. 146 */ 147 public static final int RESULT_UNAVAILABLE = Constants.Result.UNAVAILABLE; 148 /** 149 * Operation failed because the corresponding resources are not initialized. 150 */ 151 public static final int RESULT_NOT_INITIALIZED = Constants.Result.NOT_INITIALIZED; 152 /** 153 * Operation failed because it's not in a valid state. 154 */ 155 public static final int RESULT_INVALID_STATE = Constants.Result.INVALID_STATE; 156 /** 157 * Operation failed because there are invalid arguments. 158 */ 159 public static final int RESULT_INVALID_ARGUMENT = Constants.Result.INVALID_ARGUMENT; 160 /** 161 * Memory allocation failed. 162 */ 163 public static final int RESULT_OUT_OF_MEMORY = Constants.Result.OUT_OF_MEMORY; 164 /** 165 * Operation failed due to unknown errors. 166 */ 167 public static final int RESULT_UNKNOWN_ERROR = Constants.Result.UNKNOWN_ERROR; 168 169 170 171 private static final String TAG = "MediaTvTuner"; 172 private static final boolean DEBUG = false; 173 174 private static final int MSG_RESOURCE_LOST = 1; 175 private static final int MSG_ON_FILTER_EVENT = 2; 176 private static final int MSG_ON_FILTER_STATUS = 3; 177 private static final int MSG_ON_LNB_EVENT = 4; 178 179 /** @hide */ 180 @IntDef(prefix = "DVR_TYPE_", value = {DVR_TYPE_RECORD, DVR_TYPE_PLAYBACK}) 181 @Retention(RetentionPolicy.SOURCE) 182 public @interface DvrType {} 183 184 /** 185 * DVR for recording. 186 * @hide 187 */ 188 public static final int DVR_TYPE_RECORD = Constants.DvrType.RECORD; 189 /** 190 * DVR for playback of recorded programs. 191 * @hide 192 */ 193 public static final int DVR_TYPE_PLAYBACK = Constants.DvrType.PLAYBACK; 194 195 static { 196 try { 197 System.loadLibrary("media_tv_tuner"); nativeInit()198 nativeInit(); 199 } catch (UnsatisfiedLinkError e) { 200 Log.d(TAG, "tuner JNI library not found!"); 201 } 202 } 203 204 private final Context mContext; 205 private final TunerResourceManager mTunerResourceManager; 206 private final int mClientId; 207 208 private Frontend mFrontend; 209 private EventHandler mHandler; 210 @Nullable 211 private FrontendInfo mFrontendInfo; 212 private Integer mFrontendHandle; 213 private int mFrontendType = FrontendSettings.TYPE_UNDEFINED; 214 private int mUserId; 215 private Lnb mLnb; 216 private Integer mLnbHandle; 217 @Nullable 218 private OnTuneEventListener mOnTuneEventListener; 219 @Nullable 220 private Executor mOnTunerEventExecutor; 221 @Nullable 222 private ScanCallback mScanCallback; 223 @Nullable 224 private Executor mScanCallbackExecutor; 225 @Nullable 226 private OnResourceLostListener mOnResourceLostListener; 227 @Nullable 228 private Executor mOnResourceLostListenerExecutor; 229 230 private Integer mDemuxHandle; 231 private Map<Integer, Descrambler> mDescramblers = new HashMap<>(); 232 private List<Filter> mFilters = new ArrayList<>(); 233 234 private final TunerResourceManager.ResourcesReclaimListener mResourceListener = 235 new TunerResourceManager.ResourcesReclaimListener() { 236 @Override 237 public void onReclaimResources() { 238 if (mFrontend != null) { 239 FrameworkStatsLog 240 .write(FrameworkStatsLog.TV_TUNER_STATE_CHANGED, mUserId, 241 FrameworkStatsLog.TV_TUNER_STATE_CHANGED__STATE__UNKNOWN); 242 } 243 mHandler.sendMessage(mHandler.obtainMessage(MSG_RESOURCE_LOST)); 244 } 245 }; 246 247 /** 248 * Constructs a Tuner instance. 249 * 250 * @param context the context of the caller. 251 * @param tvInputSessionId the session ID of the TV input. 252 * @param useCase the use case of this Tuner instance. 253 */ 254 @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) Tuner(@onNull Context context, @Nullable String tvInputSessionId, @TvInputService.PriorityHintUseCaseType int useCase)255 public Tuner(@NonNull Context context, @Nullable String tvInputSessionId, 256 @TvInputService.PriorityHintUseCaseType int useCase) { 257 nativeSetup(); 258 mContext = context; 259 mTunerResourceManager = (TunerResourceManager) 260 context.getSystemService(Context.TV_TUNER_RESOURCE_MGR_SERVICE); 261 if (mHandler == null) { 262 mHandler = createEventHandler(); 263 } 264 265 mHandler = createEventHandler(); 266 int[] clientId = new int[1]; 267 ResourceClientProfile profile = new ResourceClientProfile(tvInputSessionId, useCase); 268 mTunerResourceManager.registerClientProfile( 269 profile, new HandlerExecutor(mHandler), mResourceListener, clientId); 270 mClientId = clientId[0]; 271 272 mUserId = ActivityManager.getCurrentUser(); 273 274 setFrontendInfoList(); 275 setLnbIds(); 276 } 277 setFrontendInfoList()278 private void setFrontendInfoList() { 279 List<Integer> ids = getFrontendIds(); 280 if (ids == null) { 281 return; 282 } 283 TunerFrontendInfo[] infos = new TunerFrontendInfo[ids.size()]; 284 for (int i = 0; i < ids.size(); i++) { 285 int id = ids.get(i); 286 FrontendInfo frontendInfo = getFrontendInfoById(id); 287 if (frontendInfo == null) { 288 continue; 289 } 290 TunerFrontendInfo tunerFrontendInfo = new TunerFrontendInfo( 291 id, frontendInfo.getType(), frontendInfo.getExclusiveGroupId()); 292 infos[i] = tunerFrontendInfo; 293 } 294 mTunerResourceManager.setFrontendInfoList(infos); 295 } 296 297 /** @hide */ getFrontendIds()298 public List<Integer> getFrontendIds() { 299 return nativeGetFrontendIds(); 300 } 301 setLnbIds()302 private void setLnbIds() { 303 int[] ids = nativeGetLnbIds(); 304 if (ids == null) { 305 return; 306 } 307 mTunerResourceManager.setLnbInfoList(ids); 308 } 309 310 /** 311 * Sets the listener for resource lost. 312 * 313 * @param executor the executor on which the listener should be invoked. 314 * @param listener the listener that will be run. 315 */ setResourceLostListener(@onNull @allbackExecutor Executor executor, @NonNull OnResourceLostListener listener)316 public void setResourceLostListener(@NonNull @CallbackExecutor Executor executor, 317 @NonNull OnResourceLostListener listener) { 318 Objects.requireNonNull(executor, "OnResourceLostListener must not be null"); 319 Objects.requireNonNull(listener, "executor must not be null"); 320 mOnResourceLostListener = listener; 321 mOnResourceLostListenerExecutor = executor; 322 } 323 324 /** 325 * Removes the listener for resource lost. 326 */ clearResourceLostListener()327 public void clearResourceLostListener() { 328 mOnResourceLostListener = null; 329 mOnResourceLostListenerExecutor = null; 330 } 331 332 /** 333 * Shares the frontend resource with another Tuner instance 334 * 335 * @param tuner the Tuner instance to share frontend resource with. 336 */ shareFrontendFromTuner(@onNull Tuner tuner)337 public void shareFrontendFromTuner(@NonNull Tuner tuner) { 338 mTunerResourceManager.shareFrontend(mClientId, tuner.mClientId); 339 mFrontendHandle = tuner.mFrontendHandle; 340 mFrontend = nativeOpenFrontendByHandle(mFrontendHandle); 341 } 342 343 /** 344 * Updates client priority with an arbitrary value along with a nice value. 345 * 346 * <p>Tuner resource manager (TRM) uses the client priority value to decide whether it is able 347 * to reclaim insufficient resources from another client. 348 * <p>The nice value represents how much the client intends to give up the resource when an 349 * insufficient resource situation happens. 350 * 351 * @param priority the new priority. 352 * @param niceValue the nice value. 353 */ updateResourcePriority(int priority, int niceValue)354 public void updateResourcePriority(int priority, int niceValue) { 355 mTunerResourceManager.updateClientPriority(mClientId, priority, niceValue); 356 } 357 358 private long mNativeContext; // used by native jMediaTuner 359 360 /** 361 * Releases the Tuner instance. 362 */ 363 @Override close()364 public void close() { 365 if (mFrontendHandle != null) { 366 int res = nativeCloseFrontend(mFrontendHandle); 367 if (res != Tuner.RESULT_SUCCESS) { 368 TunerUtils.throwExceptionForResult(res, "failed to close frontend"); 369 } 370 mTunerResourceManager.releaseFrontend(mFrontendHandle, mClientId); 371 FrameworkStatsLog 372 .write(FrameworkStatsLog.TV_TUNER_STATE_CHANGED, mUserId, 373 FrameworkStatsLog.TV_TUNER_STATE_CHANGED__STATE__UNKNOWN); 374 mFrontendHandle = null; 375 mFrontend = null; 376 } 377 if (mLnb != null) { 378 mLnb.close(); 379 } 380 if (!mDescramblers.isEmpty()) { 381 for (Map.Entry<Integer, Descrambler> d : mDescramblers.entrySet()) { 382 d.getValue().close(); 383 mTunerResourceManager.releaseDescrambler(d.getKey(), mClientId); 384 } 385 mDescramblers.clear(); 386 } 387 if (!mFilters.isEmpty()) { 388 for (Filter f : mFilters) { 389 f.close(); 390 } 391 mFilters.clear(); 392 } 393 if (mDemuxHandle != null) { 394 int res = nativeCloseDemux(mDemuxHandle); 395 if (res != Tuner.RESULT_SUCCESS) { 396 TunerUtils.throwExceptionForResult(res, "failed to close demux"); 397 } 398 mTunerResourceManager.releaseDemux(mDemuxHandle, mClientId); 399 mFrontendHandle = null; 400 } 401 TunerUtils.throwExceptionForResult(nativeClose(), "failed to close tuner"); 402 } 403 404 /** 405 * Native Initialization. 406 */ nativeInit()407 private static native void nativeInit(); 408 409 /** 410 * Native setup. 411 */ nativeSetup()412 private native void nativeSetup(); 413 414 /** 415 * Native method to get all frontend IDs. 416 */ nativeGetFrontendIds()417 private native List<Integer> nativeGetFrontendIds(); 418 419 /** 420 * Native method to open frontend of the given ID. 421 */ nativeOpenFrontendByHandle(int handle)422 private native Frontend nativeOpenFrontendByHandle(int handle); 423 @Result nativeCloseFrontendByHandle(int handle)424 private native int nativeCloseFrontendByHandle(int handle); nativeTune(int type, FrontendSettings settings)425 private native int nativeTune(int type, FrontendSettings settings); nativeStopTune()426 private native int nativeStopTune(); nativeScan(int settingsType, FrontendSettings settings, int scanType)427 private native int nativeScan(int settingsType, FrontendSettings settings, int scanType); nativeStopScan()428 private native int nativeStopScan(); nativeSetLnb(int lnbId)429 private native int nativeSetLnb(int lnbId); nativeSetLna(boolean enable)430 private native int nativeSetLna(boolean enable); nativeGetFrontendStatus(int[] statusTypes)431 private native FrontendStatus nativeGetFrontendStatus(int[] statusTypes); nativeGetAvSyncHwId(Filter filter)432 private native Integer nativeGetAvSyncHwId(Filter filter); nativeGetAvSyncTime(int avSyncId)433 private native Long nativeGetAvSyncTime(int avSyncId); nativeConnectCiCam(int ciCamId)434 private native int nativeConnectCiCam(int ciCamId); nativeDisconnectCiCam()435 private native int nativeDisconnectCiCam(); nativeGetFrontendInfo(int id)436 private native FrontendInfo nativeGetFrontendInfo(int id); nativeOpenFilter(int type, int subType, long bufferSize)437 private native Filter nativeOpenFilter(int type, int subType, long bufferSize); nativeOpenTimeFilter()438 private native TimeFilter nativeOpenTimeFilter(); 439 nativeGetLnbIds()440 private native int[] nativeGetLnbIds(); nativeOpenLnbByHandle(int handle)441 private native Lnb nativeOpenLnbByHandle(int handle); nativeOpenLnbByName(String name)442 private native Lnb nativeOpenLnbByName(String name); 443 nativeOpenDescramblerByHandle(int handle)444 private native Descrambler nativeOpenDescramblerByHandle(int handle); nativeOpenDemuxByhandle(int handle)445 private native int nativeOpenDemuxByhandle(int handle); 446 nativeOpenDvrRecorder(long bufferSize)447 private native DvrRecorder nativeOpenDvrRecorder(long bufferSize); nativeOpenDvrPlayback(long bufferSize)448 private native DvrPlayback nativeOpenDvrPlayback(long bufferSize); 449 nativeGetDemuxCapabilities()450 private native DemuxCapabilities nativeGetDemuxCapabilities(); 451 nativeCloseDemux(int handle)452 private native int nativeCloseDemux(int handle); nativeCloseFrontend(int handle)453 private native int nativeCloseFrontend(int handle); nativeClose()454 private native int nativeClose(); 455 456 457 /** 458 * Listener for resource lost. 459 * 460 * <p>Insufficient resources are reclaimed by higher priority clients. 461 */ 462 public interface OnResourceLostListener { 463 /** 464 * Invoked when resource lost. 465 * 466 * @param tuner the tuner instance whose resource is being reclaimed. 467 */ onResourceLost(@onNull Tuner tuner)468 void onResourceLost(@NonNull Tuner tuner); 469 } 470 471 @Nullable createEventHandler()472 private EventHandler createEventHandler() { 473 Looper looper; 474 if ((looper = Looper.myLooper()) != null) { 475 return new EventHandler(looper); 476 } else if ((looper = Looper.getMainLooper()) != null) { 477 return new EventHandler(looper); 478 } 479 return null; 480 } 481 482 private class EventHandler extends Handler { EventHandler(Looper looper)483 private EventHandler(Looper looper) { 484 super(looper); 485 } 486 487 @Override handleMessage(Message msg)488 public void handleMessage(Message msg) { 489 switch (msg.what) { 490 case MSG_ON_FILTER_STATUS: { 491 Filter filter = (Filter) msg.obj; 492 if (filter.getCallback() != null) { 493 filter.getCallback().onFilterStatusChanged(filter, msg.arg1); 494 } 495 break; 496 } 497 case MSG_RESOURCE_LOST: { 498 if (mOnResourceLostListener != null 499 && mOnResourceLostListenerExecutor != null) { 500 mOnResourceLostListenerExecutor.execute( 501 () -> mOnResourceLostListener.onResourceLost(Tuner.this)); 502 } 503 break; 504 } 505 default: 506 // fall through 507 } 508 } 509 } 510 511 private class Frontend { 512 private int mId; 513 Frontend(int id)514 private Frontend(int id) { 515 mId = id; 516 } 517 } 518 519 /** 520 * Listens for tune events. 521 * 522 * <p> 523 * Tuner events are started when {@link #tune(FrontendSettings)} is called and end when {@link 524 * #cancelTuning()} is called. 525 * 526 * @param eventListener receives tune events. 527 * @throws SecurityException if the caller does not have appropriate permissions. 528 * @see #tune(FrontendSettings) 529 */ setOnTuneEventListener(@onNull @allbackExecutor Executor executor, @NonNull OnTuneEventListener eventListener)530 public void setOnTuneEventListener(@NonNull @CallbackExecutor Executor executor, 531 @NonNull OnTuneEventListener eventListener) { 532 mOnTuneEventListener = eventListener; 533 mOnTunerEventExecutor = executor; 534 } 535 536 /** 537 * Clears the {@link OnTuneEventListener} and its associated {@link Executor}. 538 * 539 * @throws SecurityException if the caller does not have appropriate permissions. 540 * @see #setOnTuneEventListener(Executor, OnTuneEventListener) 541 */ clearOnTuneEventListener()542 public void clearOnTuneEventListener() { 543 mOnTuneEventListener = null; 544 mOnTunerEventExecutor = null; 545 546 } 547 548 /** 549 * Tunes the frontend to using the settings given. 550 * 551 * <p>Tuner resource manager (TRM) uses the client priority value to decide whether it is able 552 * to get frontend resource. If the client can't get the resource, this call returns {@link 553 * Result#RESULT_UNAVAILABLE}. 554 * 555 * <p> 556 * This locks the frontend to a frequency by providing signal 557 * delivery information. If previous tuning isn't completed, this stop the previous tuning, and 558 * start a new tuning. 559 * 560 * <p> 561 * Tune is an async call, with {@link OnTuneEventListener#SIGNAL_LOCKED} and {@link 562 * OnTuneEventListener#SIGNAL_NO_SIGNAL} events sent to the {@link OnTuneEventListener} 563 * specified in {@link #setOnTuneEventListener(Executor, OnTuneEventListener)}. 564 * 565 * @param settings Signal delivery information the frontend uses to 566 * search and lock the signal. 567 * @return result status of tune operation. 568 * @throws SecurityException if the caller does not have appropriate permissions. 569 * @see #setOnTuneEventListener(Executor, OnTuneEventListener) 570 */ 571 @Result tune(@onNull FrontendSettings settings)572 public int tune(@NonNull FrontendSettings settings) { 573 Log.d(TAG, "Tune to " + settings.getFrequency()); 574 mFrontendType = settings.getType(); 575 if (checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND)) { 576 mFrontendInfo = null; 577 Log.d(TAG, "Write Stats Log for tuning."); 578 FrameworkStatsLog 579 .write(FrameworkStatsLog.TV_TUNER_STATE_CHANGED, mUserId, 580 FrameworkStatsLog.TV_TUNER_STATE_CHANGED__STATE__TUNING); 581 return nativeTune(settings.getType(), settings); 582 } 583 return RESULT_UNAVAILABLE; 584 } 585 586 /** 587 * Stops a previous tuning. 588 * 589 * <p>If the method completes successfully, the frontend is no longer tuned and no data 590 * will be sent to attached filters. 591 * 592 * @return result status of the operation. 593 */ 594 @Result cancelTuning()595 public int cancelTuning() { 596 return nativeStopTune(); 597 } 598 599 /** 600 * Scan for channels. 601 * 602 * <p>Details for channels found are returned via {@link ScanCallback}. 603 * 604 * @param settings A {@link FrontendSettings} to configure the frontend. 605 * @param scanType The scan type. 606 * @throws SecurityException if the caller does not have appropriate permissions. 607 * @throws IllegalStateException if {@code scan} is called again before 608 * {@link #cancelScanning()} is called. 609 */ 610 @Result scan(@onNull FrontendSettings settings, @ScanType int scanType, @NonNull @CallbackExecutor Executor executor, @NonNull ScanCallback scanCallback)611 public int scan(@NonNull FrontendSettings settings, @ScanType int scanType, 612 @NonNull @CallbackExecutor Executor executor, @NonNull ScanCallback scanCallback) { 613 if (mScanCallback != null || mScanCallbackExecutor != null) { 614 throw new IllegalStateException( 615 "Scan already in progress. stopScan must be called before a new scan can be " 616 + "started."); 617 } 618 mFrontendType = settings.getType(); 619 if (checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND)) { 620 mScanCallback = scanCallback; 621 mScanCallbackExecutor = executor; 622 mFrontendInfo = null; 623 FrameworkStatsLog 624 .write(FrameworkStatsLog.TV_TUNER_STATE_CHANGED, mUserId, 625 FrameworkStatsLog.TV_TUNER_STATE_CHANGED__STATE__SCANNING); 626 return nativeScan(settings.getType(), settings, scanType); 627 } 628 return RESULT_UNAVAILABLE; 629 } 630 631 /** 632 * Stops a previous scanning. 633 * 634 * <p> 635 * The {@link ScanCallback} and it's {@link Executor} will be removed. 636 * 637 * <p> 638 * If the method completes successfully, the frontend stopped previous scanning. 639 * 640 * @throws SecurityException if the caller does not have appropriate permissions. 641 */ 642 @Result cancelScanning()643 public int cancelScanning() { 644 FrameworkStatsLog 645 .write(FrameworkStatsLog.TV_TUNER_STATE_CHANGED, mUserId, 646 FrameworkStatsLog.TV_TUNER_STATE_CHANGED__STATE__SCAN_STOPPED); 647 648 int retVal = nativeStopScan(); 649 mScanCallback = null; 650 mScanCallbackExecutor = null; 651 return retVal; 652 } 653 requestFrontend()654 private boolean requestFrontend() { 655 int[] feHandle = new int[1]; 656 TunerFrontendRequest request = new TunerFrontendRequest(mClientId, mFrontendType); 657 boolean granted = mTunerResourceManager.requestFrontend(request, feHandle); 658 if (granted) { 659 mFrontendHandle = feHandle[0]; 660 mFrontend = nativeOpenFrontendByHandle(mFrontendHandle); 661 } 662 return granted; 663 } 664 665 /** 666 * Sets Low-Noise Block downconverter (LNB) for satellite frontend. 667 * 668 * <p>This assigns a hardware LNB resource to the satellite tuner. It can be 669 * called multiple times to update LNB assignment. 670 * 671 * @param lnb the LNB instance. 672 * 673 * @return result status of the operation. 674 */ 675 @Result setLnb(@onNull Lnb lnb)676 private int setLnb(@NonNull Lnb lnb) { 677 return nativeSetLnb(lnb.mId); 678 } 679 680 /** 681 * Enable or Disable Low Noise Amplifier (LNA). 682 * 683 * @param enable {@code true} to activate LNA module; {@code false} to deactivate LNA. 684 * 685 * @return result status of the operation. 686 */ 687 @Result setLnaEnabled(boolean enable)688 public int setLnaEnabled(boolean enable) { 689 return nativeSetLna(enable); 690 } 691 692 /** 693 * Gets the statuses of the frontend. 694 * 695 * <p>This retrieve the statuses of the frontend for given status types. 696 * 697 * @param statusTypes an array of status types which the caller requests. 698 * @return statuses which response the caller's requests. {@code null} if the operation failed. 699 */ 700 @Nullable getFrontendStatus(@onNull @rontendStatusType int[] statusTypes)701 public FrontendStatus getFrontendStatus(@NonNull @FrontendStatusType int[] statusTypes) { 702 if (mFrontend == null) { 703 throw new IllegalStateException("frontend is not initialized"); 704 } 705 return nativeGetFrontendStatus(statusTypes); 706 } 707 708 /** 709 * Gets hardware sync ID for audio and video. 710 * 711 * @param filter the filter instance for the hardware sync ID. 712 * @return the id of hardware A/V sync. 713 */ getAvSyncHwId(@onNull Filter filter)714 public int getAvSyncHwId(@NonNull Filter filter) { 715 if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX)) { 716 return INVALID_AV_SYNC_ID; 717 } 718 Integer id = nativeGetAvSyncHwId(filter); 719 return id == null ? INVALID_AV_SYNC_ID : id; 720 } 721 722 /** 723 * Gets the current timestamp for Audio/Video sync 724 * 725 * <p>The timestamp is maintained by hardware. The timestamp based on 90KHz, and it's format is 726 * the same as PTS (Presentation Time Stamp). 727 * 728 * @param avSyncHwId the hardware id of A/V sync. 729 * @return the current timestamp of hardware A/V sync. 730 */ getAvSyncTime(int avSyncHwId)731 public long getAvSyncTime(int avSyncHwId) { 732 if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX)) { 733 return INVALID_TIMESTAMP; 734 } 735 Long time = nativeGetAvSyncTime(avSyncHwId); 736 return time == null ? INVALID_TIMESTAMP : time; 737 } 738 739 /** 740 * Connects Conditional Access Modules (CAM) through Common Interface (CI) 741 * 742 * <p>The demux uses the output from the frontend as the input by default, and must change to 743 * use the output from CI-CAM as the input after this call. 744 * 745 * @param ciCamId specify CI-CAM Id to connect. 746 * @return result status of the operation. 747 */ 748 @Result connectCiCam(int ciCamId)749 public int connectCiCam(int ciCamId) { 750 if (checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX)) { 751 return nativeConnectCiCam(ciCamId); 752 } 753 return RESULT_UNAVAILABLE; 754 } 755 756 /** 757 * Disconnects Conditional Access Modules (CAM) 758 * 759 * <p>The demux will use the output from the frontend as the input after this call. 760 * 761 * @return result status of the operation. 762 */ 763 @Result disconnectCiCam()764 public int disconnectCiCam() { 765 if (checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX)) { 766 return nativeDisconnectCiCam(); 767 } 768 return RESULT_UNAVAILABLE; 769 } 770 771 /** 772 * Gets the frontend information. 773 * 774 * @return The frontend information. {@code null} if the operation failed. 775 */ 776 @Nullable getFrontendInfo()777 public FrontendInfo getFrontendInfo() { 778 if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND)) { 779 return null; 780 } 781 if (mFrontend == null) { 782 throw new IllegalStateException("frontend is not initialized"); 783 } 784 if (mFrontendInfo == null) { 785 mFrontendInfo = getFrontendInfoById(mFrontend.mId); 786 } 787 return mFrontendInfo; 788 } 789 790 /** @hide */ getFrontendInfoById(int id)791 public FrontendInfo getFrontendInfoById(int id) { 792 return nativeGetFrontendInfo(id); 793 } 794 795 /** 796 * Gets Demux capabilities. 797 * 798 * @return A {@link DemuxCapabilities} instance that represents the demux capabilities. 799 * {@code null} if the operation failed. 800 */ 801 @Nullable getDemuxCapabilities()802 public DemuxCapabilities getDemuxCapabilities() { 803 return nativeGetDemuxCapabilities(); 804 } 805 onFrontendEvent(int eventType)806 private void onFrontendEvent(int eventType) { 807 Log.d(TAG, "Got event from tuning. Event type: " + eventType); 808 if (mOnTunerEventExecutor != null && mOnTuneEventListener != null) { 809 mOnTunerEventExecutor.execute(() -> mOnTuneEventListener.onTuneEvent(eventType)); 810 } 811 812 Log.d(TAG, "Wrote Stats Log for the events from tuning."); 813 if (eventType == OnTuneEventListener.SIGNAL_LOCKED) { 814 FrameworkStatsLog 815 .write(FrameworkStatsLog.TV_TUNER_STATE_CHANGED, mUserId, 816 FrameworkStatsLog.TV_TUNER_STATE_CHANGED__STATE__LOCKED); 817 } else if (eventType == OnTuneEventListener.SIGNAL_NO_SIGNAL) { 818 FrameworkStatsLog 819 .write(FrameworkStatsLog.TV_TUNER_STATE_CHANGED, mUserId, 820 FrameworkStatsLog.TV_TUNER_STATE_CHANGED__STATE__NOT_LOCKED); 821 } else if (eventType == OnTuneEventListener.SIGNAL_LOST_LOCK) { 822 FrameworkStatsLog 823 .write(FrameworkStatsLog.TV_TUNER_STATE_CHANGED, mUserId, 824 FrameworkStatsLog.TV_TUNER_STATE_CHANGED__STATE__SIGNAL_LOST); 825 } 826 } 827 onLocked()828 private void onLocked() { 829 Log.d(TAG, "Wrote Stats Log for locked event from scanning."); 830 FrameworkStatsLog 831 .write(FrameworkStatsLog.TV_TUNER_STATE_CHANGED, mUserId, 832 FrameworkStatsLog.TV_TUNER_STATE_CHANGED__STATE__LOCKED); 833 834 if (mScanCallbackExecutor != null && mScanCallback != null) { 835 mScanCallbackExecutor.execute(() -> mScanCallback.onLocked()); 836 } 837 } 838 onScanStopped()839 private void onScanStopped() { 840 if (mScanCallbackExecutor != null && mScanCallback != null) { 841 mScanCallbackExecutor.execute(() -> mScanCallback.onScanStopped()); 842 } 843 } 844 onProgress(int percent)845 private void onProgress(int percent) { 846 if (mScanCallbackExecutor != null && mScanCallback != null) { 847 mScanCallbackExecutor.execute(() -> mScanCallback.onProgress(percent)); 848 } 849 } 850 onFrequenciesReport(int[] frequency)851 private void onFrequenciesReport(int[] frequency) { 852 if (mScanCallbackExecutor != null && mScanCallback != null) { 853 mScanCallbackExecutor.execute(() -> mScanCallback.onFrequenciesReported(frequency)); 854 } 855 } 856 onSymbolRates(int[] rate)857 private void onSymbolRates(int[] rate) { 858 if (mScanCallbackExecutor != null && mScanCallback != null) { 859 mScanCallbackExecutor.execute(() -> mScanCallback.onSymbolRatesReported(rate)); 860 } 861 } 862 onHierarchy(int hierarchy)863 private void onHierarchy(int hierarchy) { 864 if (mScanCallbackExecutor != null && mScanCallback != null) { 865 mScanCallbackExecutor.execute(() -> mScanCallback.onHierarchyReported(hierarchy)); 866 } 867 } 868 onSignalType(int signalType)869 private void onSignalType(int signalType) { 870 if (mScanCallbackExecutor != null && mScanCallback != null) { 871 mScanCallbackExecutor.execute(() -> mScanCallback.onSignalTypeReported(signalType)); 872 } 873 } 874 onPlpIds(int[] plpIds)875 private void onPlpIds(int[] plpIds) { 876 if (mScanCallbackExecutor != null && mScanCallback != null) { 877 mScanCallbackExecutor.execute(() -> mScanCallback.onPlpIdsReported(plpIds)); 878 } 879 } 880 onGroupIds(int[] groupIds)881 private void onGroupIds(int[] groupIds) { 882 if (mScanCallbackExecutor != null && mScanCallback != null) { 883 mScanCallbackExecutor.execute(() -> mScanCallback.onGroupIdsReported(groupIds)); 884 } 885 } 886 onInputStreamIds(int[] inputStreamIds)887 private void onInputStreamIds(int[] inputStreamIds) { 888 if (mScanCallbackExecutor != null && mScanCallback != null) { 889 mScanCallbackExecutor.execute( 890 () -> mScanCallback.onInputStreamIdsReported(inputStreamIds)); 891 } 892 } 893 onDvbsStandard(int dvbsStandandard)894 private void onDvbsStandard(int dvbsStandandard) { 895 if (mScanCallbackExecutor != null && mScanCallback != null) { 896 mScanCallbackExecutor.execute( 897 () -> mScanCallback.onDvbsStandardReported(dvbsStandandard)); 898 } 899 } 900 onDvbtStandard(int dvbtStandard)901 private void onDvbtStandard(int dvbtStandard) { 902 if (mScanCallbackExecutor != null && mScanCallback != null) { 903 mScanCallbackExecutor.execute(() -> mScanCallback.onDvbtStandardReported(dvbtStandard)); 904 } 905 } 906 onAnalogSifStandard(int sif)907 private void onAnalogSifStandard(int sif) { 908 if (mScanCallbackExecutor != null && mScanCallback != null) { 909 mScanCallbackExecutor.execute(() -> mScanCallback.onAnalogSifStandardReported(sif)); 910 } 911 } 912 onAtsc3PlpInfos(Atsc3PlpInfo[] atsc3PlpInfos)913 private void onAtsc3PlpInfos(Atsc3PlpInfo[] atsc3PlpInfos) { 914 if (mScanCallbackExecutor != null && mScanCallback != null) { 915 mScanCallbackExecutor.execute( 916 () -> mScanCallback.onAtsc3PlpInfosReported(atsc3PlpInfos)); 917 } 918 } 919 920 /** 921 * Opens a filter object based on the given types and buffer size. 922 * 923 * @param mainType the main type of the filter. 924 * @param subType the subtype of the filter. 925 * @param bufferSize the buffer size of the filter to be opened in bytes. The buffer holds the 926 * data output from the filter. 927 * @param executor the executor on which callback will be invoked. The default event handler 928 * executor is used if it's {@code null}. 929 * @param cb the callback to receive notifications from filter. 930 * @return the opened filter. {@code null} if the operation failed. 931 */ 932 @Nullable openFilter(@ype int mainType, @Subtype int subType, @BytesLong long bufferSize, @CallbackExecutor @Nullable Executor executor, @Nullable FilterCallback cb)933 public Filter openFilter(@Type int mainType, @Subtype int subType, 934 @BytesLong long bufferSize, @CallbackExecutor @Nullable Executor executor, 935 @Nullable FilterCallback cb) { 936 if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX)) { 937 return null; 938 } 939 Filter filter = nativeOpenFilter( 940 mainType, TunerUtils.getFilterSubtype(mainType, subType), bufferSize); 941 if (filter != null) { 942 filter.setType(mainType, subType); 943 filter.setCallback(cb, executor); 944 if (mHandler == null) { 945 mHandler = createEventHandler(); 946 } 947 mFilters.add(filter); 948 } 949 return filter; 950 } 951 952 /** 953 * Opens an LNB (low-noise block downconverter) object. 954 * 955 * <p>If there is an existing Lnb object, it will be replace by the newly opened one. 956 * 957 * @param executor the executor on which callback will be invoked. The default event handler 958 * executor is used if it's {@code null}. 959 * @param cb the callback to receive notifications from LNB. 960 * @return the opened LNB object. {@code null} if the operation failed. 961 */ 962 @Nullable openLnb(@allbackExecutor @onNull Executor executor, @NonNull LnbCallback cb)963 public Lnb openLnb(@CallbackExecutor @NonNull Executor executor, @NonNull LnbCallback cb) { 964 Objects.requireNonNull(executor, "executor must not be null"); 965 Objects.requireNonNull(cb, "LnbCallback must not be null"); 966 if (mLnb != null) { 967 mLnb.setCallback(executor, cb, this); 968 return mLnb; 969 } 970 if (checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_LNB) && mLnb != null) { 971 mLnb.setCallback(executor, cb, this); 972 setLnb(mLnb); 973 return mLnb; 974 } 975 return null; 976 } 977 978 /** 979 * Opens an LNB (low-noise block downconverter) object specified by the give name. 980 * 981 * @param name the LNB name. 982 * @param executor the executor on which callback will be invoked. The default event handler 983 * executor is used if it's {@code null}. 984 * @param cb the callback to receive notifications from LNB. 985 * @return the opened LNB object. {@code null} if the operation failed. 986 */ 987 @Nullable openLnbByName(@onNull String name, @CallbackExecutor @NonNull Executor executor, @NonNull LnbCallback cb)988 public Lnb openLnbByName(@NonNull String name, @CallbackExecutor @NonNull Executor executor, 989 @NonNull LnbCallback cb) { 990 Objects.requireNonNull(name, "LNB name must not be null"); 991 Objects.requireNonNull(executor, "executor must not be null"); 992 Objects.requireNonNull(cb, "LnbCallback must not be null"); 993 Lnb newLnb = nativeOpenLnbByName(name); 994 if (newLnb != null) { 995 if (mLnb != null) { 996 mLnb.close(); 997 mLnbHandle = null; 998 } 999 mLnb = newLnb; 1000 mLnb.setCallback(executor, cb, this); 1001 setLnb(mLnb); 1002 } 1003 return mLnb; 1004 } 1005 requestLnb()1006 private boolean requestLnb() { 1007 int[] lnbHandle = new int[1]; 1008 TunerLnbRequest request = new TunerLnbRequest(mClientId); 1009 boolean granted = mTunerResourceManager.requestLnb(request, lnbHandle); 1010 if (granted) { 1011 mLnbHandle = lnbHandle[0]; 1012 mLnb = nativeOpenLnbByHandle(mLnbHandle); 1013 } 1014 return granted; 1015 } 1016 1017 /** 1018 * Open a time filter object. 1019 * 1020 * @return the opened time filter object. {@code null} if the operation failed. 1021 */ 1022 @Nullable openTimeFilter()1023 public TimeFilter openTimeFilter() { 1024 if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX)) { 1025 return null; 1026 } 1027 return nativeOpenTimeFilter(); 1028 } 1029 1030 /** 1031 * Opens a Descrambler in tuner. 1032 * 1033 * @return a {@link Descrambler} object. 1034 */ 1035 @RequiresPermission(android.Manifest.permission.ACCESS_TV_DESCRAMBLER) 1036 @Nullable openDescrambler()1037 public Descrambler openDescrambler() { 1038 if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX)) { 1039 return null; 1040 } 1041 return requestDescrambler(); 1042 } 1043 1044 /** 1045 * Open a DVR (Digital Video Record) recorder instance. 1046 * 1047 * @param bufferSize the buffer size of the output in bytes. It's used to hold output data of 1048 * the attached filters. 1049 * @param executor the executor on which callback will be invoked. The default event handler 1050 * executor is used if it's {@code null}. 1051 * @param l the listener to receive notifications from DVR recorder. 1052 * @return the opened DVR recorder object. {@code null} if the operation failed. 1053 */ 1054 @Nullable openDvrRecorder( @ytesLong long bufferSize, @CallbackExecutor @NonNull Executor executor, @NonNull OnRecordStatusChangedListener l)1055 public DvrRecorder openDvrRecorder( 1056 @BytesLong long bufferSize, 1057 @CallbackExecutor @NonNull Executor executor, 1058 @NonNull OnRecordStatusChangedListener l) { 1059 Objects.requireNonNull(executor, "executor must not be null"); 1060 Objects.requireNonNull(l, "OnRecordStatusChangedListener must not be null"); 1061 if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX)) { 1062 return null; 1063 } 1064 DvrRecorder dvr = nativeOpenDvrRecorder(bufferSize); 1065 dvr.setListener(executor, l); 1066 return dvr; 1067 } 1068 1069 /** 1070 * Open a DVR (Digital Video Record) playback instance. 1071 * 1072 * @param bufferSize the buffer size of the output in bytes. It's used to hold output data of 1073 * the attached filters. 1074 * @param executor the executor on which callback will be invoked. The default event handler 1075 * executor is used if it's {@code null}. 1076 * @param l the listener to receive notifications from DVR recorder. 1077 * @return the opened DVR playback object. {@code null} if the operation failed. 1078 */ 1079 @Nullable openDvrPlayback( @ytesLong long bufferSize, @CallbackExecutor @NonNull Executor executor, @NonNull OnPlaybackStatusChangedListener l)1080 public DvrPlayback openDvrPlayback( 1081 @BytesLong long bufferSize, 1082 @CallbackExecutor @NonNull Executor executor, 1083 @NonNull OnPlaybackStatusChangedListener l) { 1084 Objects.requireNonNull(executor, "executor must not be null"); 1085 Objects.requireNonNull(l, "OnPlaybackStatusChangedListener must not be null"); 1086 if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX)) { 1087 return null; 1088 } 1089 DvrPlayback dvr = nativeOpenDvrPlayback(bufferSize); 1090 dvr.setListener(executor, l); 1091 return dvr; 1092 } 1093 requestDemux()1094 private boolean requestDemux() { 1095 int[] demuxHandle = new int[1]; 1096 TunerDemuxRequest request = new TunerDemuxRequest(mClientId); 1097 boolean granted = mTunerResourceManager.requestDemux(request, demuxHandle); 1098 if (granted) { 1099 mDemuxHandle = demuxHandle[0]; 1100 nativeOpenDemuxByhandle(mDemuxHandle); 1101 } 1102 return granted; 1103 } 1104 requestDescrambler()1105 private Descrambler requestDescrambler() { 1106 int[] descramblerHandle = new int[1]; 1107 TunerDescramblerRequest request = new TunerDescramblerRequest(mClientId); 1108 boolean granted = mTunerResourceManager.requestDescrambler(request, descramblerHandle); 1109 if (!granted) { 1110 return null; 1111 } 1112 int handle = descramblerHandle[0]; 1113 Descrambler descrambler = nativeOpenDescramblerByHandle(handle); 1114 if (descrambler != null) { 1115 mDescramblers.put(handle, descrambler); 1116 } else { 1117 mTunerResourceManager.releaseDescrambler(handle, mClientId); 1118 } 1119 return descrambler; 1120 } 1121 checkResource(int resourceType)1122 private boolean checkResource(int resourceType) { 1123 switch (resourceType) { 1124 case TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND: { 1125 if (mFrontendHandle == null && !requestFrontend()) { 1126 return false; 1127 } 1128 break; 1129 } 1130 case TunerResourceManager.TUNER_RESOURCE_TYPE_LNB: { 1131 if (mLnb == null && !requestLnb()) { 1132 return false; 1133 } 1134 break; 1135 } 1136 case TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX: { 1137 if (mDemuxHandle == null && !requestDemux()) { 1138 return false; 1139 } 1140 break; 1141 } 1142 default: 1143 return false; 1144 } 1145 return true; 1146 } 1147 releaseLnb()1148 /* package */ void releaseLnb() { 1149 if (mLnbHandle != null) { 1150 // LNB handle can be null if it's opened by name. 1151 mTunerResourceManager.releaseLnb(mLnbHandle, mClientId); 1152 mLnbHandle = null; 1153 } 1154 mLnb = null; 1155 } 1156 } 1157