blob: 50af60a0ad92f5887352de64d9f9a174463a4964 [file] [log] [blame]
/*
* Copyright 2019 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 android.media.tv.tuner;
import android.annotation.BytesLong;
import android.annotation.CallbackExecutor;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.content.Context;
import android.hardware.tv.tuner.V1_0.Constants;
import android.media.tv.TvInputService;
import android.media.tv.tuner.dvr.DvrPlayback;
import android.media.tv.tuner.dvr.DvrRecorder;
import android.media.tv.tuner.dvr.OnPlaybackStatusChangedListener;
import android.media.tv.tuner.dvr.OnRecordStatusChangedListener;
import android.media.tv.tuner.filter.Filter;
import android.media.tv.tuner.filter.Filter.Subtype;
import android.media.tv.tuner.filter.Filter.Type;
import android.media.tv.tuner.filter.FilterCallback;
import android.media.tv.tuner.filter.TimeFilter;
import android.media.tv.tuner.frontend.Atsc3PlpInfo;
import android.media.tv.tuner.frontend.FrontendInfo;
import android.media.tv.tuner.frontend.FrontendSettings;
import android.media.tv.tuner.frontend.FrontendStatus;
import android.media.tv.tuner.frontend.FrontendStatus.FrontendStatusType;
import android.media.tv.tuner.frontend.OnTuneEventListener;
import android.media.tv.tuner.frontend.ScanCallback;
import android.media.tv.tunerresourcemanager.ResourceClientProfile;
import android.media.tv.tunerresourcemanager.TunerDemuxRequest;
import android.media.tv.tunerresourcemanager.TunerDescramblerRequest;
import android.media.tv.tunerresourcemanager.TunerFrontendInfo;
import android.media.tv.tunerresourcemanager.TunerFrontendRequest;
import android.media.tv.tunerresourcemanager.TunerLnbRequest;
import android.media.tv.tunerresourcemanager.TunerResourceManager;
import android.os.Handler;
import android.os.HandlerExecutor;
import android.os.Looper;
import android.os.Message;
import android.util.Log;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.Executor;
/**
* This class is used to interact with hardware tuners devices.
*
* <p> Each TvInputService Session should create one instance of this class.
*
* <p> This class controls the TIS interaction with Tuner HAL.
*
* @hide
*/
@SystemApi
public class Tuner implements AutoCloseable {
/**
* Invalid TS packet ID.
*/
public static final int INVALID_TS_PID = Constants.Constant.INVALID_TS_PID;
/**
* Invalid stream ID.
*/
public static final int INVALID_STREAM_ID = Constants.Constant.INVALID_STREAM_ID;
/**
* Invalid filter ID.
*/
public static final int INVALID_FILTER_ID = Constants.Constant.INVALID_FILTER_ID;
/**
* Invalid AV Sync ID.
*/
public static final int INVALID_AV_SYNC_ID = Constants.Constant.INVALID_AV_SYNC_ID;
/**
* Invalid timestamp.
*
* <p>Returned by {@link android.media.tv.tuner.filter.TimeFilter#getSourceTime()},
* {@link android.media.tv.tuner.filter.TimeFilter#getTimeStamp()}, or
* {@link Tuner#getAvSyncTime(int)} when the requested timestamp is not available.
*
* @see android.media.tv.tuner.filter.TimeFilter#getSourceTime()
* @see android.media.tv.tuner.filter.TimeFilter#getTimeStamp()
* @see Tuner#getAvSyncTime(int)
*/
public static final long INVALID_TIMESTAMP = -1L;
/** @hide */
@IntDef(prefix = "SCAN_TYPE_", value = {SCAN_TYPE_UNDEFINED, SCAN_TYPE_AUTO, SCAN_TYPE_BLIND})
@Retention(RetentionPolicy.SOURCE)
public @interface ScanType {}
/**
* Scan type undefined.
*/
public static final int SCAN_TYPE_UNDEFINED = Constants.FrontendScanType.SCAN_UNDEFINED;
/**
* Scan type auto.
*
* <p> Tuner will send {@link android.media.tv.tuner.frontend.ScanCallback#onLocked}
*/
public static final int SCAN_TYPE_AUTO = Constants.FrontendScanType.SCAN_AUTO;
/**
* Blind scan.
*
* <p>Frequency range is not specified. The {@link android.media.tv.tuner.Tuner} will scan an
* implementation specific range.
*/
public static final int SCAN_TYPE_BLIND = Constants.FrontendScanType.SCAN_BLIND;
/** @hide */
@IntDef({RESULT_SUCCESS, RESULT_UNAVAILABLE, RESULT_NOT_INITIALIZED, RESULT_INVALID_STATE,
RESULT_INVALID_ARGUMENT, RESULT_OUT_OF_MEMORY, RESULT_UNKNOWN_ERROR})
@Retention(RetentionPolicy.SOURCE)
public @interface Result {}
/**
* Operation succeeded.
*/
public static final int RESULT_SUCCESS = Constants.Result.SUCCESS;
/**
* Operation failed because the corresponding resources are not available.
*/
public static final int RESULT_UNAVAILABLE = Constants.Result.UNAVAILABLE;
/**
* Operation failed because the corresponding resources are not initialized.
*/
public static final int RESULT_NOT_INITIALIZED = Constants.Result.NOT_INITIALIZED;
/**
* Operation failed because it's not in a valid state.
*/
public static final int RESULT_INVALID_STATE = Constants.Result.INVALID_STATE;
/**
* Operation failed because there are invalid arguments.
*/
public static final int RESULT_INVALID_ARGUMENT = Constants.Result.INVALID_ARGUMENT;
/**
* Memory allocation failed.
*/
public static final int RESULT_OUT_OF_MEMORY = Constants.Result.OUT_OF_MEMORY;
/**
* Operation failed due to unknown errors.
*/
public static final int RESULT_UNKNOWN_ERROR = Constants.Result.UNKNOWN_ERROR;
private static final String TAG = "MediaTvTuner";
private static final boolean DEBUG = false;
private static final int MSG_RESOURCE_LOST = 1;
private static final int MSG_ON_FILTER_EVENT = 2;
private static final int MSG_ON_FILTER_STATUS = 3;
private static final int MSG_ON_LNB_EVENT = 4;
/** @hide */
@IntDef(prefix = "DVR_TYPE_", value = {DVR_TYPE_RECORD, DVR_TYPE_PLAYBACK})
@Retention(RetentionPolicy.SOURCE)
public @interface DvrType {}
/**
* DVR for recording.
* @hide
*/
public static final int DVR_TYPE_RECORD = Constants.DvrType.RECORD;
/**
* DVR for playback of recorded programs.
* @hide
*/
public static final int DVR_TYPE_PLAYBACK = Constants.DvrType.PLAYBACK;
static {
try {
System.loadLibrary("media_tv_tuner");
nativeInit();
} catch (UnsatisfiedLinkError e) {
Log.d(TAG, "tuner JNI library not found!");
}
}
private final Context mContext;
private final TunerResourceManager mTunerResourceManager;
private final int mClientId;
private Frontend mFrontend;
private EventHandler mHandler;
@Nullable
private FrontendInfo mFrontendInfo;
private Integer mFrontendHandle;
private int mFrontendType = FrontendSettings.TYPE_UNDEFINED;
private Lnb mLnb;
private Integer mLnbHandle;
@Nullable
private OnTuneEventListener mOnTuneEventListener;
@Nullable
private Executor mOnTunerEventExecutor;
@Nullable
private ScanCallback mScanCallback;
@Nullable
private Executor mScanCallbackExecutor;
@Nullable
private OnResourceLostListener mOnResourceLostListener;
@Nullable
private Executor mOnResourceLostListenerExecutor;
private Integer mDemuxHandle;
private Map<Integer, Descrambler> mDescramblers = new HashMap<>();
private List<Filter> mFilters = new ArrayList<>();
private final TunerResourceManager.ResourcesReclaimListener mResourceListener =
new TunerResourceManager.ResourcesReclaimListener() {
@Override
public void onReclaimResources() {
mHandler.sendMessage(mHandler.obtainMessage(MSG_RESOURCE_LOST));
}
};
/**
* Constructs a Tuner instance.
*
* @param context the context of the caller.
* @param tvInputSessionId the session ID of the TV input.
* @param useCase the use case of this Tuner instance.
*/
@RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
public Tuner(@NonNull Context context, @Nullable String tvInputSessionId,
@TvInputService.PriorityHintUseCaseType int useCase) {
nativeSetup();
mContext = context;
mTunerResourceManager = (TunerResourceManager)
context.getSystemService(Context.TV_TUNER_RESOURCE_MGR_SERVICE);
if (mHandler == null) {
mHandler = createEventHandler();
}
mHandler = createEventHandler();
int[] clientId = new int[1];
ResourceClientProfile profile = new ResourceClientProfile(tvInputSessionId, useCase);
mTunerResourceManager.registerClientProfile(
profile, new HandlerExecutor(mHandler), mResourceListener, clientId);
mClientId = clientId[0];
setFrontendInfoList();
setLnbIds();
}
private void setFrontendInfoList() {
List<Integer> ids = getFrontendIds();
if (ids == null) {
return;
}
TunerFrontendInfo[] infos = new TunerFrontendInfo[ids.size()];
for (int i = 0; i < ids.size(); i++) {
int id = ids.get(i);
FrontendInfo frontendInfo = getFrontendInfoById(id);
if (frontendInfo == null) {
continue;
}
TunerFrontendInfo tunerFrontendInfo = new TunerFrontendInfo(
id, frontendInfo.getType(), frontendInfo.getExclusiveGroupId());
infos[i] = tunerFrontendInfo;
}
mTunerResourceManager.setFrontendInfoList(infos);
}
/** @hide */
public List<Integer> getFrontendIds() {
return nativeGetFrontendIds();
}
private void setLnbIds() {
int[] ids = nativeGetLnbIds();
if (ids == null) {
return;
}
mTunerResourceManager.setLnbInfoList(ids);
}
/**
* Sets the listener for resource lost.
*
* @param executor the executor on which the listener should be invoked.
* @param listener the listener that will be run.
*/
public void setResourceLostListener(@NonNull @CallbackExecutor Executor executor,
@NonNull OnResourceLostListener listener) {
Objects.requireNonNull(executor, "OnResourceLostListener must not be null");
Objects.requireNonNull(listener, "executor must not be null");
mOnResourceLostListener = listener;
mOnResourceLostListenerExecutor = executor;
}
/**
* Removes the listener for resource lost.
*/
public void clearResourceLostListener() {
mOnResourceLostListener = null;
mOnResourceLostListenerExecutor = null;
}
/**
* Shares the frontend resource with another Tuner instance
*
* @param tuner the Tuner instance to share frontend resource with.
*/
public void shareFrontendFromTuner(@NonNull Tuner tuner) {
mTunerResourceManager.shareFrontend(mClientId, tuner.mClientId);
mFrontendHandle = tuner.mFrontendHandle;
mFrontend = nativeOpenFrontendByHandle(mFrontendHandle);
}
/**
* Updates client priority with an arbitrary value along with a nice value.
*
* <p>Tuner resource manager (TRM) uses the client priority value to decide whether it is able
* to reclaim insufficient resources from another client.
* <p>The nice value represents how much the client intends to give up the resource when an
* insufficient resource situation happens.
*
* @param priority the new priority.
* @param niceValue the nice value.
*/
public void updateResourcePriority(int priority, int niceValue) {
mTunerResourceManager.updateClientPriority(mClientId, priority, niceValue);
}
private long mNativeContext; // used by native jMediaTuner
/**
* Releases the Tuner instance.
*/
@Override
public void close() {
if (mFrontendHandle != null) {
int res = nativeCloseFrontend(mFrontendHandle);
if (res != Tuner.RESULT_SUCCESS) {
TunerUtils.throwExceptionForResult(res, "failed to close frontend");
}
mTunerResourceManager.releaseFrontend(mFrontendHandle, mClientId);
mFrontendHandle = null;
mFrontend = null;
}
if (mLnb != null) {
mLnb.close();
}
if (!mDescramblers.isEmpty()) {
for (Map.Entry<Integer, Descrambler> d : mDescramblers.entrySet()) {
d.getValue().close();
mTunerResourceManager.releaseDescrambler(d.getKey(), mClientId);
}
mDescramblers.clear();
}
if (!mFilters.isEmpty()) {
for (Filter f : mFilters) {
f.close();
}
mFilters.clear();
}
if (mDemuxHandle != null) {
int res = nativeCloseDemux(mDemuxHandle);
if (res != Tuner.RESULT_SUCCESS) {
TunerUtils.throwExceptionForResult(res, "failed to close demux");
}
mTunerResourceManager.releaseDemux(mDemuxHandle, mClientId);
mFrontendHandle = null;
}
TunerUtils.throwExceptionForResult(nativeClose(), "failed to close tuner");
}
/**
* Native Initialization.
*/
private static native void nativeInit();
/**
* Native setup.
*/
private native void nativeSetup();
/**
* Native method to get all frontend IDs.
*/
private native List<Integer> nativeGetFrontendIds();
/**
* Native method to open frontend of the given ID.
*/
private native Frontend nativeOpenFrontendByHandle(int handle);
@Result
private native int nativeCloseFrontendByHandle(int handle);
private native int nativeTune(int type, FrontendSettings settings);
private native int nativeStopTune();
private native int nativeScan(int settingsType, FrontendSettings settings, int scanType);
private native int nativeStopScan();
private native int nativeSetLnb(int lnbId);
private native int nativeSetLna(boolean enable);
private native FrontendStatus nativeGetFrontendStatus(int[] statusTypes);
private native Integer nativeGetAvSyncHwId(Filter filter);
private native Long nativeGetAvSyncTime(int avSyncId);
private native int nativeConnectCiCam(int ciCamId);
private native int nativeDisconnectCiCam();
private native FrontendInfo nativeGetFrontendInfo(int id);
private native Filter nativeOpenFilter(int type, int subType, long bufferSize);
private native TimeFilter nativeOpenTimeFilter();
private native int[] nativeGetLnbIds();
private native Lnb nativeOpenLnbByHandle(int handle);
private native Lnb nativeOpenLnbByName(String name);
private native Descrambler nativeOpenDescramblerByHandle(int handle);
private native int nativeOpenDemuxByhandle(int handle);
private native DvrRecorder nativeOpenDvrRecorder(long bufferSize);
private native DvrPlayback nativeOpenDvrPlayback(long bufferSize);
private static native DemuxCapabilities nativeGetDemuxCapabilities();
private native int nativeCloseDemux(int handle);
private native int nativeCloseFrontend(int handle);
private native int nativeClose();
/**
* Listener for resource lost.
*
* <p>Insufficient resources are reclaimed by higher priority clients.
*/
public interface OnResourceLostListener {
/**
* Invoked when resource lost.
*
* @param tuner the tuner instance whose resource is being reclaimed.
*/
void onResourceLost(@NonNull Tuner tuner);
}
@Nullable
private EventHandler createEventHandler() {
Looper looper;
if ((looper = Looper.myLooper()) != null) {
return new EventHandler(looper);
} else if ((looper = Looper.getMainLooper()) != null) {
return new EventHandler(looper);
}
return null;
}
private class EventHandler extends Handler {
private EventHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_ON_FILTER_STATUS: {
Filter filter = (Filter) msg.obj;
if (filter.getCallback() != null) {
filter.getCallback().onFilterStatusChanged(filter, msg.arg1);
}
break;
}
case MSG_RESOURCE_LOST: {
if (mOnResourceLostListener != null
&& mOnResourceLostListenerExecutor != null) {
mOnResourceLostListenerExecutor.execute(
() -> mOnResourceLostListener.onResourceLost(Tuner.this));
}
break;
}
default:
// fall through
}
}
}
private class Frontend {
private int mId;
private Frontend(int id) {
mId = id;
}
}
/**
* Listens for tune events.
*
* <p>
* Tuner events are started when {@link #tune(FrontendSettings)} is called and end when {@link
* #cancelTuning()} is called.
*
* @param eventListener receives tune events.
* @throws SecurityException if the caller does not have appropriate permissions.
* @see #tune(FrontendSettings)
*/
public void setOnTuneEventListener(@NonNull @CallbackExecutor Executor executor,
@NonNull OnTuneEventListener eventListener) {
mOnTuneEventListener = eventListener;
mOnTunerEventExecutor = executor;
}
/**
* Clears the {@link OnTuneEventListener} and its associated {@link Executor}.
*
* @throws SecurityException if the caller does not have appropriate permissions.
* @see #setOnTuneEventListener(Executor, OnTuneEventListener)
*/
public void clearOnTuneEventListener() {
mOnTuneEventListener = null;
mOnTunerEventExecutor = null;
}
/**
* Tunes the frontend to using the settings given.
*
* <p>Tuner resource manager (TRM) uses the client priority value to decide whether it is able
* to get frontend resource. If the client can't get the resource, this call returns {@link
* Result#RESULT_UNAVAILABLE}.
*
* <p>
* This locks the frontend to a frequency by providing signal
* delivery information. If previous tuning isn't completed, this stop the previous tuning, and
* start a new tuning.
*
* <p>
* Tune is an async call, with {@link OnTuneEventListener#SIGNAL_LOCKED} and {@link
* OnTuneEventListener#SIGNAL_NO_SIGNAL} events sent to the {@link OnTuneEventListener}
* specified in {@link #setOnTuneEventListener(Executor, OnTuneEventListener)}.
*
* @param settings Signal delivery information the frontend uses to
* search and lock the signal.
* @return result status of tune operation.
* @throws SecurityException if the caller does not have appropriate permissions.
* @see #setOnTuneEventListener(Executor, OnTuneEventListener)
*/
@Result
public int tune(@NonNull FrontendSettings settings) {
mFrontendType = settings.getType();
if (checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND)) {
mFrontendInfo = null;
return nativeTune(settings.getType(), settings);
}
return RESULT_UNAVAILABLE;
}
/**
* Stops a previous tuning.
*
* <p>If the method completes successfully, the frontend is no longer tuned and no data
* will be sent to attached filters.
*
* @return result status of the operation.
*/
@Result
public int cancelTuning() {
return nativeStopTune();
}
/**
* Scan for channels.
*
* <p>Details for channels found are returned via {@link ScanCallback}.
*
* @param settings A {@link FrontendSettings} to configure the frontend.
* @param scanType The scan type.
* @throws SecurityException if the caller does not have appropriate permissions.
* @throws IllegalStateException if {@code scan} is called again before
* {@link #cancelScanning()} is called.
*/
@Result
public int scan(@NonNull FrontendSettings settings, @ScanType int scanType,
@NonNull @CallbackExecutor Executor executor, @NonNull ScanCallback scanCallback) {
if (mScanCallback != null || mScanCallbackExecutor != null) {
throw new IllegalStateException(
"Scan already in progress. stopScan must be called before a new scan can be "
+ "started.");
}
mFrontendType = settings.getType();
if (checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND)) {
mScanCallback = scanCallback;
mScanCallbackExecutor = executor;
mFrontendInfo = null;
return nativeScan(settings.getType(), settings, scanType);
}
return RESULT_UNAVAILABLE;
}
/**
* Stops a previous scanning.
*
* <p>
* The {@link ScanCallback} and it's {@link Executor} will be removed.
*
* <p>
* If the method completes successfully, the frontend stopped previous scanning.
*
* @throws SecurityException if the caller does not have appropriate permissions.
*/
@Result
public int cancelScanning() {
int retVal = nativeStopScan();
mScanCallback = null;
mScanCallbackExecutor = null;
return retVal;
}
private boolean requestFrontend() {
int[] feHandle = new int[1];
TunerFrontendRequest request = new TunerFrontendRequest(mClientId, mFrontendType);
boolean granted = mTunerResourceManager.requestFrontend(request, feHandle);
if (granted) {
mFrontendHandle = feHandle[0];
mFrontend = nativeOpenFrontendByHandle(mFrontendHandle);
}
return granted;
}
/**
* Sets Low-Noise Block downconverter (LNB) for satellite frontend.
*
* <p>This assigns a hardware LNB resource to the satellite tuner. It can be
* called multiple times to update LNB assignment.
*
* @param lnb the LNB instance.
*
* @return result status of the operation.
*/
@Result
private int setLnb(@NonNull Lnb lnb) {
return nativeSetLnb(lnb.mId);
}
/**
* Enable or Disable Low Noise Amplifier (LNA).
*
* @param enable {@code true} to activate LNA module; {@code false} to deactivate LNA.
*
* @return result status of the operation.
*/
@Result
public int setLnaEnabled(boolean enable) {
return nativeSetLna(enable);
}
/**
* Gets the statuses of the frontend.
*
* <p>This retrieve the statuses of the frontend for given status types.
*
* @param statusTypes an array of status types which the caller requests.
* @return statuses which response the caller's requests. {@code null} if the operation failed.
*/
@Nullable
public FrontendStatus getFrontendStatus(@NonNull @FrontendStatusType int[] statusTypes) {
if (mFrontend == null) {
throw new IllegalStateException("frontend is not initialized");
}
return nativeGetFrontendStatus(statusTypes);
}
/**
* Gets hardware sync ID for audio and video.
*
* @param filter the filter instance for the hardware sync ID.
* @return the id of hardware A/V sync.
*/
public int getAvSyncHwId(@NonNull Filter filter) {
if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX)) {
return INVALID_AV_SYNC_ID;
}
Integer id = nativeGetAvSyncHwId(filter);
return id == null ? INVALID_AV_SYNC_ID : id;
}
/**
* Gets the current timestamp for Audio/Video sync
*
* <p>The timestamp is maintained by hardware. The timestamp based on 90KHz, and it's format is
* the same as PTS (Presentation Time Stamp).
*
* @param avSyncHwId the hardware id of A/V sync.
* @return the current timestamp of hardware A/V sync.
*/
public long getAvSyncTime(int avSyncHwId) {
if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX)) {
return INVALID_TIMESTAMP;
}
Long time = nativeGetAvSyncTime(avSyncHwId);
return time == null ? INVALID_TIMESTAMP : time;
}
/**
* Connects Conditional Access Modules (CAM) through Common Interface (CI)
*
* <p>The demux uses the output from the frontend as the input by default, and must change to
* use the output from CI-CAM as the input after this call.
*
* @param ciCamId specify CI-CAM Id to connect.
* @return result status of the operation.
*/
@Result
public int connectCiCam(int ciCamId) {
if (checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX)) {
return nativeConnectCiCam(ciCamId);
}
return RESULT_UNAVAILABLE;
}
/**
* Disconnects Conditional Access Modules (CAM)
*
* <p>The demux will use the output from the frontend as the input after this call.
*
* @return result status of the operation.
*/
@Result
public int disconnectCiCam() {
if (checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX)) {
return nativeDisconnectCiCam();
}
return RESULT_UNAVAILABLE;
}
/**
* Gets the frontend information.
*
* @return The frontend information. {@code null} if the operation failed.
*/
@Nullable
public FrontendInfo getFrontendInfo() {
if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND)) {
return null;
}
if (mFrontend == null) {
throw new IllegalStateException("frontend is not initialized");
}
if (mFrontendInfo == null) {
mFrontendInfo = getFrontendInfoById(mFrontend.mId);
}
return mFrontendInfo;
}
/** @hide */
public FrontendInfo getFrontendInfoById(int id) {
return nativeGetFrontendInfo(id);
}
/**
* Gets Demux capabilities.
*
* @return A {@link DemuxCapabilities} instance that represents the demux capabilities.
* {@code null} if the operation failed.
*/
@Nullable
public DemuxCapabilities getDemuxCapabilities() {
return nativeGetDemuxCapabilities();
}
private void onFrontendEvent(int eventType) {
if (mOnTunerEventExecutor != null && mOnTuneEventListener != null) {
mOnTunerEventExecutor.execute(() -> mOnTuneEventListener.onTuneEvent(eventType));
}
}
private void onLocked() {
if (mScanCallbackExecutor != null && mScanCallback != null) {
mScanCallbackExecutor.execute(() -> mScanCallback.onLocked());
}
}
private void onScanStopped() {
if (mScanCallbackExecutor != null && mScanCallback != null) {
mScanCallbackExecutor.execute(() -> mScanCallback.onScanStopped());
}
}
private void onProgress(int percent) {
if (mScanCallbackExecutor != null && mScanCallback != null) {
mScanCallbackExecutor.execute(() -> mScanCallback.onProgress(percent));
}
}
private void onFrequenciesReport(int[] frequency) {
if (mScanCallbackExecutor != null && mScanCallback != null) {
mScanCallbackExecutor.execute(() -> mScanCallback.onFrequenciesReported(frequency));
}
}
private void onSymbolRates(int[] rate) {
if (mScanCallbackExecutor != null && mScanCallback != null) {
mScanCallbackExecutor.execute(() -> mScanCallback.onSymbolRatesReported(rate));
}
}
private void onHierarchy(int hierarchy) {
if (mScanCallbackExecutor != null && mScanCallback != null) {
mScanCallbackExecutor.execute(() -> mScanCallback.onHierarchyReported(hierarchy));
}
}
private void onSignalType(int signalType) {
if (mScanCallbackExecutor != null && mScanCallback != null) {
mScanCallbackExecutor.execute(() -> mScanCallback.onSignalTypeReported(signalType));
}
}
private void onPlpIds(int[] plpIds) {
if (mScanCallbackExecutor != null && mScanCallback != null) {
mScanCallbackExecutor.execute(() -> mScanCallback.onPlpIdsReported(plpIds));
}
}
private void onGroupIds(int[] groupIds) {
if (mScanCallbackExecutor != null && mScanCallback != null) {
mScanCallbackExecutor.execute(() -> mScanCallback.onGroupIdsReported(groupIds));
}
}
private void onInputStreamIds(int[] inputStreamIds) {
if (mScanCallbackExecutor != null && mScanCallback != null) {
mScanCallbackExecutor.execute(
() -> mScanCallback.onInputStreamIdsReported(inputStreamIds));
}
}
private void onDvbsStandard(int dvbsStandandard) {
if (mScanCallbackExecutor != null && mScanCallback != null) {
mScanCallbackExecutor.execute(
() -> mScanCallback.onDvbsStandardReported(dvbsStandandard));
}
}
private void onDvbtStandard(int dvbtStandard) {
if (mScanCallbackExecutor != null && mScanCallback != null) {
mScanCallbackExecutor.execute(() -> mScanCallback.onDvbtStandardReported(dvbtStandard));
}
}
private void onAnalogSifStandard(int sif) {
if (mScanCallbackExecutor != null && mScanCallback != null) {
mScanCallbackExecutor.execute(() -> mScanCallback.onAnalogSifStandardReported(sif));
}
}
private void onAtsc3PlpInfos(Atsc3PlpInfo[] atsc3PlpInfos) {
if (mScanCallbackExecutor != null && mScanCallback != null) {
mScanCallbackExecutor.execute(
() -> mScanCallback.onAtsc3PlpInfosReported(atsc3PlpInfos));
}
}
/**
* Opens a filter object based on the given types and buffer size.
*
* @param mainType the main type of the filter.
* @param subType the subtype of the filter.
* @param bufferSize the buffer size of the filter to be opened in bytes. The buffer holds the
* data output from the filter.
* @param executor the executor on which callback will be invoked. The default event handler
* executor is used if it's {@code null}.
* @param cb the callback to receive notifications from filter.
* @return the opened filter. {@code null} if the operation failed.
*/
@Nullable
public Filter openFilter(@Type int mainType, @Subtype int subType,
@BytesLong long bufferSize, @CallbackExecutor @Nullable Executor executor,
@Nullable FilterCallback cb) {
if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX)) {
return null;
}
Filter filter = nativeOpenFilter(
mainType, TunerUtils.getFilterSubtype(mainType, subType), bufferSize);
if (filter != null) {
filter.setMainType(mainType);
filter.setSubtype(subType);
filter.setCallback(cb, executor);
if (mHandler == null) {
mHandler = createEventHandler();
}
mFilters.add(filter);
}
return filter;
}
/**
* Opens an LNB (low-noise block downconverter) object.
*
* <p>If there is an existing Lnb object, it will be replace by the newly opened one.
*
* @param executor the executor on which callback will be invoked. The default event handler
* executor is used if it's {@code null}.
* @param cb the callback to receive notifications from LNB.
* @return the opened LNB object. {@code null} if the operation failed.
*/
@Nullable
public Lnb openLnb(@CallbackExecutor @NonNull Executor executor, @NonNull LnbCallback cb) {
Objects.requireNonNull(executor, "executor must not be null");
Objects.requireNonNull(cb, "LnbCallback must not be null");
if (mLnb != null) {
mLnb.setCallback(executor, cb, this);
return mLnb;
}
if (checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_LNB) && mLnb != null) {
mLnb.setCallback(executor, cb, this);
setLnb(mLnb);
return mLnb;
}
return null;
}
/**
* Opens an LNB (low-noise block downconverter) object specified by the give name.
*
* @param name the LNB name.
* @param executor the executor on which callback will be invoked. The default event handler
* executor is used if it's {@code null}.
* @param cb the callback to receive notifications from LNB.
* @return the opened LNB object. {@code null} if the operation failed.
*/
@Nullable
public Lnb openLnbByName(@NonNull String name, @CallbackExecutor @NonNull Executor executor,
@NonNull LnbCallback cb) {
Objects.requireNonNull(name, "LNB name must not be null");
Objects.requireNonNull(executor, "executor must not be null");
Objects.requireNonNull(cb, "LnbCallback must not be null");
Lnb newLnb = nativeOpenLnbByName(name);
if (newLnb != null) {
if (mLnb != null) {
mLnb.close();
mLnbHandle = null;
}
mLnb = newLnb;
mLnb.setCallback(executor, cb, this);
setLnb(mLnb);
}
return mLnb;
}
private boolean requestLnb() {
int[] lnbHandle = new int[1];
TunerLnbRequest request = new TunerLnbRequest(mClientId);
boolean granted = mTunerResourceManager.requestLnb(request, lnbHandle);
if (granted) {
mLnbHandle = lnbHandle[0];
mLnb = nativeOpenLnbByHandle(mLnbHandle);
}
return granted;
}
/**
* Open a time filter object.
*
* @return the opened time filter object. {@code null} if the operation failed.
*/
@Nullable
public TimeFilter openTimeFilter() {
if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX)) {
return null;
}
return nativeOpenTimeFilter();
}
/**
* Opens a Descrambler in tuner.
*
* @return a {@link Descrambler} object.
*/
@RequiresPermission(android.Manifest.permission.ACCESS_TV_DESCRAMBLER)
@Nullable
public Descrambler openDescrambler() {
if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX)) {
return null;
}
return requestDescrambler();
}
/**
* Open a DVR (Digital Video Record) recorder instance.
*
* @param bufferSize the buffer size of the output in bytes. It's used to hold output data of
* the attached filters.
* @param executor the executor on which callback will be invoked. The default event handler
* executor is used if it's {@code null}.
* @param l the listener to receive notifications from DVR recorder.
* @return the opened DVR recorder object. {@code null} if the operation failed.
*/
@Nullable
public DvrRecorder openDvrRecorder(
@BytesLong long bufferSize,
@CallbackExecutor @NonNull Executor executor,
@NonNull OnRecordStatusChangedListener l) {
Objects.requireNonNull(executor, "executor must not be null");
Objects.requireNonNull(l, "OnRecordStatusChangedListener must not be null");
if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX)) {
return null;
}
DvrRecorder dvr = nativeOpenDvrRecorder(bufferSize);
dvr.setListener(executor, l);
return dvr;
}
/**
* Open a DVR (Digital Video Record) playback instance.
*
* @param bufferSize the buffer size of the output in bytes. It's used to hold output data of
* the attached filters.
* @param executor the executor on which callback will be invoked. The default event handler
* executor is used if it's {@code null}.
* @param l the listener to receive notifications from DVR recorder.
* @return the opened DVR playback object. {@code null} if the operation failed.
*/
@Nullable
public DvrPlayback openDvrPlayback(
@BytesLong long bufferSize,
@CallbackExecutor @NonNull Executor executor,
@NonNull OnPlaybackStatusChangedListener l) {
Objects.requireNonNull(executor, "executor must not be null");
Objects.requireNonNull(l, "OnPlaybackStatusChangedListener must not be null");
if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX)) {
return null;
}
DvrPlayback dvr = nativeOpenDvrPlayback(bufferSize);
dvr.setListener(executor, l);
return dvr;
}
private boolean requestDemux() {
int[] demuxHandle = new int[1];
TunerDemuxRequest request = new TunerDemuxRequest(mClientId);
boolean granted = mTunerResourceManager.requestDemux(request, demuxHandle);
if (granted) {
mDemuxHandle = demuxHandle[0];
nativeOpenDemuxByhandle(mDemuxHandle);
}
return granted;
}
private Descrambler requestDescrambler() {
int[] descramblerHandle = new int[1];
TunerDescramblerRequest request = new TunerDescramblerRequest(mClientId);
boolean granted = mTunerResourceManager.requestDescrambler(request, descramblerHandle);
if (!granted) {
return null;
}
int handle = descramblerHandle[0];
Descrambler descrambler = nativeOpenDescramblerByHandle(handle);
if (descrambler != null) {
mDescramblers.put(handle, descrambler);
} else {
mTunerResourceManager.releaseDescrambler(handle, mClientId);
}
return descrambler;
}
private boolean checkResource(int resourceType) {
switch (resourceType) {
case TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND: {
if (mFrontendHandle == null && !requestFrontend()) {
return false;
}
break;
}
case TunerResourceManager.TUNER_RESOURCE_TYPE_LNB: {
if (mLnb == null && !requestLnb()) {
return false;
}
break;
}
case TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX: {
if (mDemuxHandle == null && !requestDemux()) {
return false;
}
break;
}
default:
return false;
}
return true;
}
/* package */ void releaseLnb() {
mTunerResourceManager.releaseLnb(mLnbHandle, mClientId);
mLnbHandle = null;
mLnb = null;
}
}