| /* |
| * 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; |
| } |
| } |