/* * 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.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.content.Context; import android.content.pm.PackageManager; import android.hardware.tv.tuner.Constant; import android.hardware.tv.tuner.Constant64Bit; import android.hardware.tv.tuner.FrontendScanType; import android.media.MediaCodec; 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.SharedFilter; import android.media.tv.tuner.filter.SharedFilterCallback; 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.FrontendStatusReadiness; 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.TunerCiCamRequest; import android.media.tv.tunerresourcemanager.TunerDemuxRequest; import android.media.tv.tunerresourcemanager.TunerDescramblerRequest; 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.Looper; import android.os.Message; import android.os.Process; import android.util.Log; import com.android.internal.util.FrameworkStatsLog; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.concurrent.Executor; import java.util.concurrent.locks.ReentrantLock; /** * This class is used to interact with hardware tuners devices. * *
Each TvInputService Session should create one instance of this class. * *
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 = Constant.INVALID_TS_PID; /** * Invalid stream ID. */ public static final int INVALID_STREAM_ID = Constant.INVALID_STREAM_ID; /** * Invalid filter ID. */ public static final int INVALID_FILTER_ID = Constant.INVALID_FILTER_ID; /** * Invalid AV Sync ID. */ public static final int INVALID_AV_SYNC_ID = Constant.INVALID_AV_SYNC_ID; /** * Invalid timestamp. * *
Returned by {@link android.media.tv.tuner.filter.TimeFilter#getSourceTime()}, * {@link android.media.tv.tuner.filter.TimeFilter#getTimeStamp()}, * {@link Tuner#getAvSyncTime(int)} or {@link TsRecordEvent#getPts()} and * {@link MmtpRecordEvent#getPts()} 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) * @see android.media.tv.tuner.filter.TsRecordEvent#getPts() * @see android.media.tv.tuner.filter.MmtpRecordEvent#getPts() */ public static final long INVALID_TIMESTAMP = Constant64Bit.INVALID_PRESENTATION_TIME_STAMP; /** * Invalid mpu sequence number in MmtpRecordEvent. * *
Returned by {@link MmtpRecordEvent#getMpuSequenceNumber()} when the requested sequence * number is not available. * * @see android.media.tv.tuner.filter.MmtpRecordEvent#getMpuSequenceNumber() */ public static final int INVALID_MMTP_RECORD_EVENT_MPT_SEQUENCE_NUM = Constant.INVALID_MMTP_RECORD_EVENT_MPT_SEQUENCE_NUM; /** * Invalid first macroblock address in MmtpRecordEvent and TsRecordEvent. * *
Returned by {@link MmtpRecordEvent#getMbInSlice()} and * {@link TsRecordEvent#getMbInSlice()} when the requested sequence number is not available. * * @see android.media.tv.tuner.filter.MmtpRecordEvent#getMbInSlice() * @see android.media.tv.tuner.filter.TsRecordEvent#getMbInSlice() */ public static final int INVALID_FIRST_MACROBLOCK_IN_SLICE = Constant.INVALID_FIRST_MACROBLOCK_IN_SLICE; /** * Invalid local transport stream id. * *
Returned by {@link #linkFrontendToCiCam(int)} when the requested failed * or the hal implementation does not support the operation. * * @see #linkFrontendToCiCam(int) */ public static final int INVALID_LTS_ID = Constant.INVALID_LTS_ID; /** * Invalid 64-bit filter ID. */ public static final long INVALID_FILTER_ID_LONG = Constant64Bit.INVALID_FILTER_ID_64BIT; /** * Invalid frequency that is used as the default frontend frequency setting. */ public static final int INVALID_FRONTEND_SETTING_FREQUENCY = Constant.INVALID_FRONTEND_SETTING_FREQUENCY; /** * Invalid frontend id. */ public static final int INVALID_FRONTEND_ID = Constant.INVALID_FRONTEND_ID; /** * Invalid LNB id. * * @hide */ public static final int INVALID_LNB_ID = Constant.INVALID_LNB_ID; /** * A void key token. It is used to remove the current key from descrambler. * *
If the current keyToken comes from a MediaCas session, App is recommended to * to use this constant to remove current key before closing MediaCas session. */ @NonNull public static final byte[] VOID_KEYTOKEN = {Constant.INVALID_KEYTOKEN}; /** @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 = FrontendScanType.SCAN_UNDEFINED; /** * Scan type auto. * *
Tuner will send {@link android.media.tv.tuner.frontend.ScanCallback#onLocked} */ public static final int SCAN_TYPE_AUTO = FrontendScanType.SCAN_AUTO; /** * Blind scan. * *
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 = 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 = android.hardware.tv.tuner.Result.SUCCESS;
/**
* Operation failed because the corresponding resources are not available.
*/
public static final int RESULT_UNAVAILABLE = android.hardware.tv.tuner.Result.UNAVAILABLE;
/**
* Operation failed because the corresponding resources are not initialized.
*/
public static final int RESULT_NOT_INITIALIZED =
android.hardware.tv.tuner.Result.NOT_INITIALIZED;
/**
* Operation failed because it's not in a valid state.
*/
public static final int RESULT_INVALID_STATE = android.hardware.tv.tuner.Result.INVALID_STATE;
/**
* Operation failed because there are invalid arguments.
*/
public static final int RESULT_INVALID_ARGUMENT =
android.hardware.tv.tuner.Result.INVALID_ARGUMENT;
/**
* Memory allocation failed.
*/
public static final int RESULT_OUT_OF_MEMORY = android.hardware.tv.tuner.Result.OUT_OF_MEMORY;
/**
* Operation failed due to unknown errors.
*/
public static final int RESULT_UNKNOWN_ERROR = android.hardware.tv.tuner.Result.UNKNOWN_ERROR;
private static final String TAG = "MediaTvTuner";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
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;
private static final int FILTER_CLEANUP_THRESHOLD = 256;
/** @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 = android.hardware.tv.tuner.DvrType.RECORD;
/**
* DVR for playback of recorded programs.
* @hide
*/
public static final int DVR_TYPE_PLAYBACK = android.hardware.tv.tuner.DvrType.PLAYBACK;
static {
try {
System.loadLibrary("media_tv_tuner");
nativeInit();
// Load and initialize MediaCodec to avoid flaky cts test result.
Class.forName(MediaCodec.class.getName());
} catch (UnsatisfiedLinkError e) {
Log.d(TAG, "tuner JNI library not found!");
} catch (ClassNotFoundException e) {
Log.e(TAG, "MediaCodec class not found!", e);
}
}
private final Context mContext;
private final TunerResourceManager mTunerResourceManager;
private final int mClientId;
private static int sTunerVersion = TunerVersionChecker.TUNER_VERSION_UNKNOWN;
private DemuxInfo mDesiredDemuxInfo = new DemuxInfo(Filter.TYPE_UNDEFINED);
private Frontend mFrontend;
private EventHandler mHandler;
@Nullable
private FrontendInfo mFrontendInfo;
private Integer mFrontendHandle;
private Tuner mFeOwnerTuner = null;
private int mFrontendType = FrontendSettings.TYPE_UNDEFINED;
private Integer mDesiredFrontendId = null;
private int mUserId;
private Lnb mLnb;
private Integer mLnbHandle;
@Nullable
private OnTuneEventListener mOnTuneEventListener;
@Nullable
private Executor mOnTuneEventExecutor;
@Nullable
private ScanCallback mScanCallback;
@Nullable
private Executor mScanCallbackExecutor;
@Nullable
private OnResourceLostListener mOnResourceLostListener;
@Nullable
private Executor mOnResourceLostListenerExecutor;
private final Object mOnTuneEventLock = new Object();
private final Object mScanCallbackLock = new Object();
private final Object mOnResourceLostListenerLock = new Object();
private final ReentrantLock mFrontendLock = new ReentrantLock();
private final ReentrantLock mLnbLock = new ReentrantLock();
private final ReentrantLock mFrontendCiCamLock = new ReentrantLock();
private final ReentrantLock mDemuxLock = new ReentrantLock();
private int mRequestedCiCamId;
private Integer mDemuxHandle;
private Integer mFrontendCiCamHandle;
private Integer mFrontendCiCamId;
private Map Tuner resource manager (TRM) uses the client priority value to decide whether it is able
* to reclaim insufficient resources from another client.
*
* 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. Any negative value would cause no-op on priority setting
* and the API would only process nice value setting in that case.
* @param niceValue the nice value.
*/
@RequiresPermission(android.Manifest.permission.TUNER_RESOURCE_ACCESS)
public void updateResourcePriority(int priority, int niceValue) {
mTunerResourceManager.updateClientPriority(mClientId, priority, niceValue);
}
/**
* Checks if there is an unused frontend resource available.
*
* @param frontendType {@link android.media.tv.tuner.frontend.FrontendSettings.Type} for the
* query to be done for.
*/
@RequiresPermission(android.Manifest.permission.TUNER_RESOURCE_ACCESS)
public boolean hasUnusedFrontend(int frontendType) {
return mTunerResourceManager.hasUnusedFrontend(frontendType);
}
/**
* Checks if the calling Tuner object has the lowest priority as a client to
* {@link TunerResourceManager}
*
* The priority comparison is done against the current holders of the frontend resource.
*
* The behavior of this function is independent of the availability of unused resources.
*
* The function returns {@code true} in any of the following sceanrios:
* 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: {
synchronized (mOnResourceLostListenerLock) {
if (mOnResourceLostListener != null
&& mOnResourceLostListenerExecutor != null) {
mOnResourceLostListenerExecutor.execute(() -> {
synchronized (mOnResourceLostListenerLock) {
if (mOnResourceLostListener != null) {
mOnResourceLostListener.onResourceLost(Tuner.this);
}
}
});
}
}
break;
}
default:
// fall through
}
}
}
private class Frontend {
private int mId;
private Frontend(int id) {
mId = id;
}
}
/**
* Listens for tune events.
*
*
* 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) {
synchronized (mOnTuneEventLock) {
mOnTuneEventListener = eventListener;
mOnTuneEventExecutor = 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() {
synchronized (mOnTuneEventLock) {
mOnTuneEventListener = null;
mOnTuneEventExecutor = null;
}
}
/**
* Tunes the frontend to using the settings given.
*
* 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_UNAVAILABLE}.
*
*
* 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.
*
*
* 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)}.
*
* Tuning with {@link android.media.tv.tuner.frontend.DtmbFrontendSettings} is only
* supported in Tuner 1.1 or higher version. Unsupported version will cause no-op. Use {@link
* TunerVersionChecker#getTunerVersion()} to get the version information.
*
* Tuning with {@link
* android.media.tv.tuner.frontend.IsdbtFrontendSettings.PartialReceptionFlag} or {@link
* android.media.tv.tuner.frontend.IsdbtFrontendSettings.IsdbtLayerSettings} is only supported
* in Tuner 2.0 or higher version. Unsupported version will cause no-op. Use {@link
* TunerVersionChecker#getTunerVersion()} to get the version information.
*
* Tuning with {@link
* android.media.tv.tuner.frontend.IptvFrontendSettings} is only supported
* in Tuner 3.0 or higher version. Unsupported version will cause no-op. Use {@link
* TunerVersionChecker#getTunerVersion()} to get the version information.
*
* @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) {
mFrontendLock.lock();
try {
if (mFeOwnerTuner != null) {
Log.d(TAG, "Operation cannot be done by sharee of tuner");
return RESULT_INVALID_STATE;
}
final int type = settings.getType();
if (mFrontendHandle != null && type != mFrontendType) {
Log.e(TAG, "Frontend was opened with type " + mFrontendType
+ ", new type is " + type);
return RESULT_INVALID_STATE;
}
Log.d(TAG, "Tune to " + settings.getFrequencyLong());
mFrontendType = type;
if (mFrontendType == FrontendSettings.TYPE_DTMB) {
if (!TunerVersionChecker.checkHigherOrEqualVersionTo(
TunerVersionChecker.TUNER_VERSION_1_1, "Tuner with DTMB Frontend")) {
return RESULT_UNAVAILABLE;
}
}
if (mFrontendType == FrontendSettings.TYPE_IPTV) {
if (!TunerVersionChecker.checkHigherOrEqualVersionTo(
TunerVersionChecker.TUNER_VERSION_3_0, "Tuner with IPTV Frontend")) {
return RESULT_UNAVAILABLE;
}
}
if (checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND, mFrontendLock)) {
mFrontendInfo = null;
Log.d(TAG, "Write Stats Log for tuning.");
FrameworkStatsLog
.write(FrameworkStatsLog.TV_TUNER_STATE_CHANGED, mUserId,
FrameworkStatsLog.TV_TUNER_STATE_CHANGED__STATE__TUNING);
int res = nativeTune(settings.getType(), settings);
return res;
} else {
return RESULT_UNAVAILABLE;
}
} finally {
mFrontendLock.unlock();
}
}
/**
* Stops a previous tuning.
*
* 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() {
mFrontendLock.lock();
try {
if (mFeOwnerTuner != null) {
Log.d(TAG, "Operation cannot be done by sharee of tuner");
return RESULT_INVALID_STATE;
}
return nativeStopTune();
} finally {
mFrontendLock.unlock();
}
}
/**
* Scan for channels.
*
* Details for channels found are returned via {@link ScanCallback}.
*
* Scanning with {@link android.media.tv.tuner.frontend.DtmbFrontendSettings} is only
* supported in Tuner 1.1 or higher version. Unsupported version will cause no-op. Use {@link
* TunerVersionChecker#getTunerVersion()} to get the version information.
*
* * Scanning with {@link
* android.media.tv.tuner.frontend.IsdbtFrontendSettings.PartialReceptionFlag} or {@link
* android.media.tv.tuner.frontend.IsdbtFrontendSettings.IsdbtLayerSettings} is only supported
* in Tuner 2.0 or higher version. Unsupported version will cause no-op. Use {@link
* TunerVersionChecker#getTunerVersion()} to get the version information.
*
* @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) {
mFrontendLock.lock();
try {
if (mFeOwnerTuner != null) {
Log.d(TAG, "Operation cannot be done by sharee of tuner");
return RESULT_INVALID_STATE;
}
synchronized (mScanCallbackLock) {
// Scan can be called again for blink scan if scanCallback and executor are same as
//before.
if (((mScanCallback != null) && (mScanCallback != scanCallback))
|| ((mScanCallbackExecutor != null)
&& (mScanCallbackExecutor != executor))) {
throw new IllegalStateException(
"Different Scan session already in progress. stopScan must be called "
+ "before a new scan session can be " + "started.");
}
mFrontendType = settings.getType();
if (mFrontendType == FrontendSettings.TYPE_DTMB) {
if (!TunerVersionChecker.checkHigherOrEqualVersionTo(
TunerVersionChecker.TUNER_VERSION_1_1,
"Scan with DTMB Frontend")) {
return RESULT_UNAVAILABLE;
}
}
if (mFrontendType == FrontendSettings.TYPE_IPTV) {
if (!TunerVersionChecker.checkHigherOrEqualVersionTo(
TunerVersionChecker.TUNER_VERSION_3_0,
"Tuner with IPTV Frontend")) {
return RESULT_UNAVAILABLE;
}
}
if (checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND,
mFrontendLock)) {
mScanCallback = scanCallback;
mScanCallbackExecutor = executor;
mFrontendInfo = null;
FrameworkStatsLog
.write(FrameworkStatsLog.TV_TUNER_STATE_CHANGED, mUserId,
FrameworkStatsLog.TV_TUNER_STATE_CHANGED__STATE__SCANNING);
return nativeScan(settings.getType(), settings, scanType);
}
return RESULT_UNAVAILABLE;
}
} finally {
mFrontendLock.unlock();
}
}
/**
* Stops a previous scanning.
*
*
* The {@link ScanCallback} and it's {@link Executor} will be removed.
*
*
* If the method completes successfully, the frontend stopped previous scanning.
*
* @throws SecurityException if the caller does not have appropriate permissions.
*/
@Result
public int cancelScanning() {
mFrontendLock.lock();
try {
if (mFeOwnerTuner != null) {
Log.d(TAG, "Operation cannot be done by sharee of tuner");
return RESULT_INVALID_STATE;
}
synchronized (mScanCallbackLock) {
FrameworkStatsLog.write(FrameworkStatsLog.TV_TUNER_STATE_CHANGED, mUserId,
FrameworkStatsLog.TV_TUNER_STATE_CHANGED__STATE__SCAN_STOPPED);
int retVal = nativeStopScan();
mScanCallback = null;
mScanCallbackExecutor = null;
return retVal;
}
} finally {
mFrontendLock.unlock();
}
}
private boolean requestFrontend() {
int[] feHandle = new int[1];
boolean granted = false;
try {
TunerFrontendRequest request = new TunerFrontendRequest();
request.clientId = mClientId;
request.frontendType = mFrontendType;
request.desiredId = mDesiredFrontendId == null
? TunerFrontendRequest.DEFAULT_DESIRED_ID
: mDesiredFrontendId;
granted = mTunerResourceManager.requestFrontend(request, feHandle);
} finally {
mDesiredFrontendId = null;
}
if (granted) {
mFrontendHandle = feHandle[0];
mFrontend = nativeOpenFrontendByHandle(mFrontendHandle);
}
// For satellite type, set Lnb if valid handle exists.
// This is necessary as now that we support closeFrontend().
if (mFrontendType == FrontendSettings.TYPE_DVBS
|| mFrontendType == FrontendSettings.TYPE_ISDBS
|| mFrontendType == FrontendSettings.TYPE_ISDBS3) {
mLnbLock.lock();
try {
if (mLnbHandle != null && mLnb != null) {
nativeSetLnb(mLnb);
}
} finally {
mLnbLock.unlock();
}
}
return granted;
}
/**
* Sets Low-Noise Block downconverter (LNB) for satellite frontend.
*
* 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) {
mLnbLock.lock();
try {
return nativeSetLnb(lnb);
} finally {
mLnbLock.unlock();
}
}
/**
* Is Low Noise Amplifier (LNA) supported by the Tuner.
*
* This API is only supported by Tuner HAL 3.0 or higher.
* Unsupported version would throw UnsupportedOperationException. Use
* {@link TunerVersionChecker#getTunerVersion()} to check the version.
*
* @return {@code true} if supported, otherwise {@code false}.
* @throws UnsupportedOperationException if the Tuner HAL version is lower than 3.0
* @see android.media.tv.tuner.TunerVersionChecker#TUNER_VERSION_3_0
*/
public boolean isLnaSupported() {
if (!TunerVersionChecker.checkHigherOrEqualVersionTo(
TunerVersionChecker.TUNER_VERSION_3_0, "isLnaSupported")) {
throw new UnsupportedOperationException("Tuner HAL version "
+ TunerVersionChecker.getTunerVersion() + " doesn't support this method.");
}
return nativeIsLnaSupported();
}
/**
* 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. {@link #RESULT_UNAVAILABLE} if the device doesn't
* support LNA.
*/
@Result
public int setLnaEnabled(boolean enable) {
return nativeSetLna(enable);
}
/**
* Gets the statuses of the frontend.
*
* This retrieve the statuses of the frontend for given status types.
*
* @param statusTypes an array of status types which the caller requests. Any types that are not
* in {@link FrontendInfo#getStatusCapabilities()} would be ignored.
* @return statuses which response the caller's requests. {@code null} if the operation failed.
*/
@Nullable
public FrontendStatus getFrontendStatus(@NonNull @FrontendStatusType int[] statusTypes) {
mFrontendLock.lock();
try {
if (mFrontend == null) {
throw new IllegalStateException("frontend is not initialized");
}
if (mFeOwnerTuner != null) {
throw new IllegalStateException("Operation cannot be done by sharee of tuner");
}
return nativeGetFrontendStatus(statusTypes);
} finally {
mFrontendLock.unlock();
}
}
/**
* 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) {
mDemuxLock.lock();
try {
if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX, mDemuxLock)) {
return INVALID_AV_SYNC_ID;
}
Integer id = nativeGetAvSyncHwId(filter);
return id == null ? INVALID_AV_SYNC_ID : id;
} finally {
mDemuxLock.unlock();
}
}
/**
* Gets the current timestamp for Audio/Video sync
*
* 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) {
mDemuxLock.lock();
try {
if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX, mDemuxLock)) {
return INVALID_TIMESTAMP;
}
Long time = nativeGetAvSyncTime(avSyncHwId);
return time == null ? INVALID_TIMESTAMP : time;
} finally {
mDemuxLock.unlock();
}
}
/**
* Connects Conditional Access Modules (CAM) through Common Interface (CI).
*
* 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.
*
* Note that this API is used to connect the CI-CAM to the Demux module while
* {@link #connectFrontendToCiCam(int)} is used to connect CI-CAM to the Frontend module.
*
* @param ciCamId specify CI-CAM Id to connect.
* @return result status of the operation.
*/
@Result
public int connectCiCam(int ciCamId) {
mDemuxLock.lock();
try {
if (checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX, mDemuxLock)) {
return nativeConnectCiCam(ciCamId);
}
return RESULT_UNAVAILABLE;
} finally {
mDemuxLock.unlock();
}
}
/**
* Connect Conditional Access Modules (CAM) Frontend to support Common Interface (CI)
* by-pass mode.
*
* It is used by the client to link CI-CAM to a Frontend. CI by-pass mode requires that
* the CICAM also receives the TS concurrently from the frontend when the Demux is receiving
* the TS directly from the frontend.
*
* Note that this API is used to connect the CI-CAM to the Frontend module while
* {@link #connectCiCam(int)} is used to connect CI-CAM to the Demux module.
*
* Use {@link #disconnectFrontendToCiCam(int)} to disconnect.
*
* This API is only supported by Tuner HAL 1.1 or higher. Unsupported version would cause
* no-op and return {@link #INVALID_LTS_ID}. Use {@link TunerVersionChecker#getTunerVersion()}
* to check the version.
*
* @param ciCamId specify CI-CAM Id, which is the id of the Conditional Access Modules (CAM)
* Common Interface (CI), to link.
* @return Local transport stream id when connection is successfully established. Failed
* operation returns {@link #INVALID_LTS_ID} while unsupported version also returns
* {@link #INVALID_LTS_ID}. Check the current HAL version using
* {@link TunerVersionChecker#getTunerVersion()}.
*/
public int connectFrontendToCiCam(int ciCamId) {
// TODO: change this so TRMS lock is held only when the resource handles for
// CiCam/Frontend is null. Current implementation can only handle one local lock for that.
acquireTRMSLock("connectFrontendToCiCam()");
mFrontendCiCamLock.lock();
mFrontendLock.lock();
try {
if (mFrontendHandle == null) {
Log.d(TAG, "Operation cannot be done without frontend");
return RESULT_INVALID_STATE;
}
if (mFeOwnerTuner != null) {
Log.d(TAG, "Operation cannot be done by sharee of tuner");
return RESULT_INVALID_STATE;
}
if (TunerVersionChecker.checkHigherOrEqualVersionTo(
TunerVersionChecker.TUNER_VERSION_1_1,
"linkFrontendToCiCam")) {
mRequestedCiCamId = ciCamId;
// No need to unlock mFrontendCiCamLock and mFrontendLock below becauase
// TRMS lock is already acquired. Pass null to disable lock related operations
if (checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND_CICAM, null)
&& checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND, null)
) {
return nativeLinkCiCam(ciCamId);
}
}
return INVALID_LTS_ID;
} finally {
releaseTRMSLock();
mFrontendCiCamLock.unlock();
mFrontendLock.unlock();
}
}
/**
* Disconnects Conditional Access Modules (CAM).
*
* The demux will use the output from the frontend as the input after this call.
*
* Note that this API is used to disconnect the CI-CAM to the Demux module while
* {@link #disconnectFrontendToCiCam(int)} is used to disconnect CI-CAM to the Frontend module.
*
* @return result status of the operation.
*/
@Result
public int disconnectCiCam() {
mDemuxLock.lock();
try {
if (mDemuxHandle != null) {
return nativeDisconnectCiCam();
}
return RESULT_UNAVAILABLE;
} finally {
mDemuxLock.unlock();
}
}
/**
* Disconnect Conditional Access Modules (CAM) Frontend.
*
* It is used by the client to unlink CI-CAM to a Frontend.
*
* Note that this API is used to disconnect the CI-CAM to the Demux module while
* {@link #disconnectCiCam(int)} is used to disconnect CI-CAM to the Frontend module.
*
* This API is only supported by Tuner HAL 1.1 or higher. Unsupported version would cause
* no-op. Use {@link TunerVersionChecker#getTunerVersion()} to check the version.
*
* @param ciCamId specify CI-CAM Id, which is the id of the Conditional Access Modules (CAM)
* Common Interface (CI), to disconnect.
* @return result status of the operation. Unsupported version would return
* {@link #RESULT_UNAVAILABLE}
*/
@Result
public int disconnectFrontendToCiCam(int ciCamId) {
acquireTRMSLock("disconnectFrontendToCiCam()");
try {
if (mFrontendHandle == null) {
Log.d(TAG, "Operation cannot be done without frontend");
return RESULT_INVALID_STATE;
}
if (mFeOwnerTuner != null) {
Log.d(TAG, "Operation cannot be done by sharee of tuner");
return RESULT_INVALID_STATE;
}
if (TunerVersionChecker.checkHigherOrEqualVersionTo(
TunerVersionChecker.TUNER_VERSION_1_1,
"unlinkFrontendToCiCam")) {
mFrontendCiCamLock.lock();
if (mFrontendCiCamHandle != null && mFrontendCiCamId != null
&& mFrontendCiCamId == ciCamId) {
int result = nativeUnlinkCiCam(ciCamId);
if (result == RESULT_SUCCESS) {
mTunerResourceManager.releaseCiCam(mFrontendCiCamHandle, mClientId);
mFrontendCiCamId = null;
mFrontendCiCamHandle = null;
}
return result;
}
}
return RESULT_UNAVAILABLE;
} finally {
if (mFrontendCiCamLock.isLocked()) {
mFrontendCiCamLock.unlock();
}
releaseTRMSLock();
}
}
/**
* Remove PID (packet identifier) from frontend output.
*
* It is used by the client to remove a video or audio PID of other program to reduce the
* total amount of recorded TS.
*
* This API is only supported by Tuner HAL 2.0 or higher. Unsupported version would cause
* no-op. Use {@link TunerVersionChecker#getTunerVersion()} to check the version.
*
* @return result status of the operation. Unsupported version or if current active frontend
* doesn’t support PID filtering out would return {@link #RESULT_UNAVAILABLE}.
* @throws IllegalStateException if there is no active frontend currently.
*/
@Result
public int removeOutputPid(@IntRange(from = 0) int pid) {
mFrontendLock.lock();
try {
if (!TunerVersionChecker.checkHigherOrEqualVersionTo(
TunerVersionChecker.TUNER_VERSION_2_0, "Remove output PID")) {
return RESULT_UNAVAILABLE;
}
if (mFrontend == null) {
throw new IllegalStateException("frontend is not initialized");
}
if (mFeOwnerTuner != null) {
Log.d(TAG, "Operation cannot be done by sharee of tuner");
return RESULT_INVALID_STATE;
}
return nativeRemoveOutputPid(pid);
} finally {
mFrontendLock.unlock();
}
}
/**
* Gets Frontend Status Readiness statuses for given status types.
*
* This API is only supported by Tuner HAL 2.0 or higher. Unsupported versions would cause
* no-op. Use {@link TunerVersionChecker#getTunerVersion()} to check the version.
*
* @param statusTypes an array of status types.
*
* @return a list of current readiness states. It is empty if the operation fails or unsupported
* versions.
* @throws IllegalStateException if there is no active frontend currently.
*/
@NonNull
public List This API is only supported by Tuner HAL 2.0 or higher. Unsupported version would return
* {@code null}. Use {@link TunerVersionChecker#getTunerVersion()} to check the version.
*
* @return The active frontend hardware information. {@code null} if the operation failed.
* @throws IllegalStateException if there is no active frontend currently.
*/
@Nullable
public String getCurrentFrontendHardwareInfo() {
mFrontendLock.lock();
try {
if (!TunerVersionChecker.checkHigherOrEqualVersionTo(
TunerVersionChecker.TUNER_VERSION_2_0, "Get Frontend hardware info")) {
return null;
}
if (mFrontend == null) {
throw new IllegalStateException("frontend is not initialized");
}
if (mFeOwnerTuner != null) {
throw new IllegalStateException("Operation cannot be done by sharee of tuner");
}
return nativeGetFrontendHardwareInfo();
} finally {
mFrontendLock.unlock();
}
}
/**
* Sets the maximum usable frontends number of a given frontend type. It is used to enable or
* disable frontends when cable connection status is changed by user.
*
* This API is only supported by Tuner HAL 2.0 or higher. Unsupported version would return
* {@link RESULT_UNAVAILABLE}. Use {@link TunerVersionChecker#getTunerVersion()} to check the
* version.
*
* @param frontendType the {@link android.media.tv.tuner.frontend.FrontendSettings.Type} which
* the maximum usable number will be set.
* @param maxNumber the new maximum usable number.
* @return result status of the operation.
*/
@Result
public int setMaxNumberOfFrontends(
@FrontendSettings.Type int frontendType, @IntRange(from = 0) int maxNumber) {
if (!TunerVersionChecker.checkHigherOrEqualVersionTo(
TunerVersionChecker.TUNER_VERSION_2_0, "Set maximum Frontends")) {
return RESULT_UNAVAILABLE;
}
if (maxNumber < 0) {
return RESULT_INVALID_ARGUMENT;
}
if (mFeOwnerTuner != null) {
Log.d(TAG, "Operation cannot be done by sharee of tuner");
return RESULT_INVALID_STATE;
}
int res = nativeSetMaxNumberOfFrontends(frontendType, maxNumber);
if (res == RESULT_SUCCESS) {
if (!mTunerResourceManager.setMaxNumberOfFrontends(frontendType, maxNumber)) {
res = RESULT_INVALID_ARGUMENT;
}
}
return res;
}
/**
* Get the maximum usable frontends number of a given frontend type.
*
* This API is only supported by Tuner HAL 2.0 or higher. Unsupported version would return
* {@code -1}. Use {@link TunerVersionChecker#getTunerVersion()} to check the version.
*
* @param frontendType the {@link android.media.tv.tuner.frontend.FrontendSettings.Type} which
* the maximum usable number will be queried.
* @return the maximum usable number of the queried frontend type.
*/
@IntRange(from = -1)
public int getMaxNumberOfFrontends(@FrontendSettings.Type int frontendType) {
if (!TunerVersionChecker.checkHigherOrEqualVersionTo(
TunerVersionChecker.TUNER_VERSION_2_0, "Set maximum Frontends")) {
return -1;
}
int maxNumFromHAL = nativeGetMaxNumberOfFrontends(frontendType);
int maxNumFromTRM = mTunerResourceManager.getMaxNumberOfFrontends(frontendType);
if (maxNumFromHAL != maxNumFromTRM) {
Log.w(TAG, "max num of usable frontend is out-of-sync b/w " + maxNumFromHAL
+ " != " + maxNumFromTRM);
}
return maxNumFromHAL;
}
/** @hide */
public FrontendInfo getFrontendInfoById(int id) {
mFrontendLock.lock();
try {
return nativeGetFrontendInfo(id);
} finally {
mFrontendLock.unlock();
}
}
/**
* Gets Demux capabilities.
*
* @return A {@link DemuxCapabilities} instance that represents the demux capabilities.
* {@code null} if the operation failed.
*/
@Nullable
public DemuxCapabilities getDemuxCapabilities() {
mDemuxLock.lock();
try {
return nativeGetDemuxCapabilities();
} finally {
mDemuxLock.unlock();
}
}
/**
* Gets DemuxInfo of the currently held demux
*
* @return A {@link DemuxInfo} of currently held demux resource.
* Returns null if no demux resource is held.
*/
@Nullable
public DemuxInfo getCurrentDemuxInfo() {
mDemuxLock.lock();
try {
if (mDemuxHandle == null) {
return null;
}
return nativeGetDemuxInfo(mDemuxHandle);
} finally {
mDemuxLock.unlock();
}
}
/** @hide */
public DemuxInfo getDesiredDemuxInfo() {
return mDesiredDemuxInfo;
}
private void onFrontendEvent(int eventType) {
Log.d(TAG, "Got event from tuning. Event type: " + eventType + " for " + this);
synchronized (mOnTuneEventLock) {
if (mOnTuneEventExecutor != null && mOnTuneEventListener != null) {
mOnTuneEventExecutor.execute(() -> {
synchronized (mOnTuneEventLock) {
if (mOnTuneEventListener != null) {
mOnTuneEventListener.onTuneEvent(eventType);
}
}
});
}
}
Log.d(TAG, "Wrote Stats Log for the events from tuning.");
if (eventType == OnTuneEventListener.SIGNAL_LOCKED) {
FrameworkStatsLog
.write(FrameworkStatsLog.TV_TUNER_STATE_CHANGED, mUserId,
FrameworkStatsLog.TV_TUNER_STATE_CHANGED__STATE__LOCKED);
} else if (eventType == OnTuneEventListener.SIGNAL_NO_SIGNAL) {
FrameworkStatsLog
.write(FrameworkStatsLog.TV_TUNER_STATE_CHANGED, mUserId,
FrameworkStatsLog.TV_TUNER_STATE_CHANGED__STATE__NOT_LOCKED);
} else if (eventType == OnTuneEventListener.SIGNAL_LOST_LOCK) {
FrameworkStatsLog
.write(FrameworkStatsLog.TV_TUNER_STATE_CHANGED, mUserId,
FrameworkStatsLog.TV_TUNER_STATE_CHANGED__STATE__SIGNAL_LOST);
}
}
private void onLocked() {
Log.d(TAG, "Wrote Stats Log for locked event from scanning.");
FrameworkStatsLog.write(
FrameworkStatsLog.TV_TUNER_STATE_CHANGED, mUserId,
FrameworkStatsLog.TV_TUNER_STATE_CHANGED__STATE__LOCKED);
synchronized (mScanCallbackLock) {
if (mScanCallbackExecutor != null && mScanCallback != null) {
mScanCallbackExecutor.execute(() -> {
synchronized (mScanCallbackLock) {
if (mScanCallback != null) {
mScanCallback.onLocked();
}
}
});
}
}
}
private void onUnlocked() {
Log.d(TAG, "Wrote Stats Log for unlocked event from scanning.");
FrameworkStatsLog.write(FrameworkStatsLog.TV_TUNER_STATE_CHANGED, mUserId,
FrameworkStatsLog.TV_TUNER_STATE_CHANGED__STATE__LOCKED);
synchronized (mScanCallbackLock) {
if (mScanCallbackExecutor != null && mScanCallback != null) {
mScanCallbackExecutor.execute(() -> {
synchronized (mScanCallbackLock) {
if (mScanCallback != null) {
mScanCallback.onUnlocked();
}
}
});
}
}
}
private void onScanStopped() {
synchronized (mScanCallbackLock) {
if (mScanCallbackExecutor != null && mScanCallback != null) {
mScanCallbackExecutor.execute(() -> {
synchronized (mScanCallbackLock) {
if (mScanCallback != null) {
mScanCallback.onScanStopped();
}
}
});
}
}
}
private void onProgress(int percent) {
synchronized (mScanCallbackLock) {
if (mScanCallbackExecutor != null && mScanCallback != null) {
mScanCallbackExecutor.execute(() -> {
synchronized (mScanCallbackLock) {
if (mScanCallback != null) {
mScanCallback.onProgress(percent);
}
}
});
}
}
}
private void onFrequenciesReport(long[] frequencies) {
synchronized (mScanCallbackLock) {
if (mScanCallbackExecutor != null && mScanCallback != null) {
mScanCallbackExecutor.execute(() -> {
synchronized (mScanCallbackLock) {
if (mScanCallback != null) {
mScanCallback.onFrequenciesLongReported(frequencies);
}
}
});
}
}
}
private void onSymbolRates(int[] rate) {
synchronized (mScanCallbackLock) {
if (mScanCallbackExecutor != null && mScanCallback != null) {
mScanCallbackExecutor.execute(() -> {
synchronized (mScanCallbackLock) {
if (mScanCallback != null) {
mScanCallback.onSymbolRatesReported(rate);
}
}
});
}
}
}
private void onHierarchy(int hierarchy) {
synchronized (mScanCallbackLock) {
if (mScanCallbackExecutor != null && mScanCallback != null) {
mScanCallbackExecutor.execute(() -> {
synchronized (mScanCallbackLock) {
if (mScanCallback != null) {
mScanCallback.onHierarchyReported(hierarchy);
}
}
});
}
}
}
private void onSignalType(int signalType) {
synchronized (mScanCallbackLock) {
if (mScanCallbackExecutor != null && mScanCallback != null) {
mScanCallbackExecutor.execute(() -> {
synchronized (mScanCallbackLock) {
if (mScanCallback != null) {
mScanCallback.onSignalTypeReported(signalType);
}
}
});
}
}
}
private void onPlpIds(int[] plpIds) {
synchronized (mScanCallbackLock) {
if (mScanCallbackExecutor != null && mScanCallback != null) {
mScanCallbackExecutor.execute(() -> {
synchronized (mScanCallbackLock) {
if (mScanCallback != null) {
mScanCallback.onPlpIdsReported(plpIds);
}
}
});
}
}
}
private void onGroupIds(int[] groupIds) {
synchronized (mScanCallbackLock) {
if (mScanCallbackExecutor != null && mScanCallback != null) {
mScanCallbackExecutor.execute(() -> {
synchronized (mScanCallbackLock) {
if (mScanCallback != null) {
mScanCallback.onGroupIdsReported(groupIds);
}
}
});
}
}
}
private void onInputStreamIds(int[] inputStreamIds) {
synchronized (mScanCallbackLock) {
if (mScanCallbackExecutor != null && mScanCallback != null) {
mScanCallbackExecutor.execute(() -> {
synchronized (mScanCallbackLock) {
if (mScanCallback != null) {
mScanCallback.onInputStreamIdsReported(inputStreamIds);
}
}
});
}
}
}
private void onDvbsStandard(int dvbsStandandard) {
synchronized (mScanCallbackLock) {
if (mScanCallbackExecutor != null && mScanCallback != null) {
mScanCallbackExecutor.execute(() -> {
synchronized (mScanCallbackLock) {
if (mScanCallback != null) {
mScanCallback.onDvbsStandardReported(dvbsStandandard);
}
}
});
}
}
}
private void onDvbtStandard(int dvbtStandard) {
synchronized (mScanCallbackLock) {
if (mScanCallbackExecutor != null && mScanCallback != null) {
mScanCallbackExecutor.execute(() -> {
synchronized (mScanCallbackLock) {
if (mScanCallback != null) {
mScanCallback.onDvbtStandardReported(dvbtStandard);
}
}
});
}
}
}
private void onAnalogSifStandard(int sif) {
synchronized (mScanCallbackLock) {
if (mScanCallbackExecutor != null && mScanCallback != null) {
mScanCallbackExecutor.execute(() -> {
synchronized (mScanCallbackLock) {
if (mScanCallback != null) {
mScanCallback.onAnalogSifStandardReported(sif);
}
}
});
}
}
}
private void onAtsc3PlpInfos(Atsc3PlpInfo[] atsc3PlpInfos) {
synchronized (mScanCallbackLock) {
if (mScanCallbackExecutor != null && mScanCallback != null) {
mScanCallbackExecutor.execute(() -> {
synchronized (mScanCallbackLock) {
if (mScanCallback != null) {
mScanCallback.onAtsc3PlpInfosReported(atsc3PlpInfos);
}
}
});
}
}
}
private void onModulationReported(int modulation) {
synchronized (mScanCallbackLock) {
if (mScanCallbackExecutor != null && mScanCallback != null) {
mScanCallbackExecutor.execute(() -> {
synchronized (mScanCallbackLock) {
if (mScanCallback != null) {
mScanCallback.onModulationReported(modulation);
}
}
});
}
}
}
private void onPriorityReported(boolean isHighPriority) {
synchronized (mScanCallbackLock) {
if (mScanCallbackExecutor != null && mScanCallback != null) {
mScanCallbackExecutor.execute(() -> {
synchronized (mScanCallbackLock) {
if (mScanCallback != null) {
mScanCallback.onPriorityReported(isHighPriority);
}
}
});
}
}
}
private void onDvbcAnnexReported(int dvbcAnnex) {
synchronized (mScanCallbackLock) {
if (mScanCallbackExecutor != null && mScanCallback != null) {
mScanCallbackExecutor.execute(() -> {
synchronized (mScanCallbackLock) {
if (mScanCallback != null) {
mScanCallback.onDvbcAnnexReported(dvbcAnnex);
}
}
});
}
}
}
private void onDvbtCellIdsReported(int[] dvbtCellIds) {
synchronized (mScanCallbackLock) {
if (mScanCallbackExecutor != null && mScanCallback != null) {
mScanCallbackExecutor.execute(() -> {
synchronized (mScanCallbackLock) {
if (mScanCallback != null) {
mScanCallback.onDvbtCellIdsReported(dvbtCellIds);
}
}
});
}
}
}
/**
* Opens a filter object based on the given types and buffer size.
*
* For TUNER_VERSION_3_0 and above, configureDemuxInternal() will be called with mainType.
* However, unlike when configureDemux() is called directly, the desired filter types will not
* be changed when previously set desired filter types are the superset of the newly desired
* ones.
*
* @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) {
mDemuxLock.lock();
try {
int tunerMajorVersion = TunerVersionChecker.getMajorVersion(sTunerVersion);
if (sTunerVersion >= TunerVersionChecker.TUNER_VERSION_3_0) {
DemuxInfo demuxInfo = new DemuxInfo(mainType);
int res = configureDemuxInternal(demuxInfo, false /* reduceDesiredFilterTypes */);
if (res != RESULT_SUCCESS) {
Log.e(TAG, "openFilter called for unsupported mainType: " + mainType);
return null;
}
}
if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX, mDemuxLock)) {
return null;
}
Filter filter = nativeOpenFilter(
mainType, TunerUtils.getFilterSubtype(mainType, subType), bufferSize);
if (filter != null) {
filter.setType(mainType, subType);
filter.setCallback(cb, executor);
if (mHandler == null) {
mHandler = createEventHandler();
}
synchronized (mFilters) {
WeakReference 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) {
mLnbLock.lock();
try {
Objects.requireNonNull(executor, "executor must not be null");
Objects.requireNonNull(cb, "LnbCallback must not be null");
if (mLnb != null) {
mLnb.setCallbackAndOwner(this, executor, cb);
return mLnb;
}
if (checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_LNB, mLnbLock)
&& mLnb != null) {
mLnb.setCallbackAndOwner(this, executor, cb);
if (mFrontendHandle != null && mFrontend != null) {
setLnb(mLnb);
}
return mLnb;
}
return null;
} finally {
mLnbLock.unlock();
}
}
/**
* 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) {
acquireTRMSLock("openLnbByName");
mLnbLock.lock();
try {
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.closeInternal();
mLnbHandle = null;
}
mLnb = newLnb;
mLnb.setCallbackAndOwner(this, executor, cb);
if (mFrontendHandle != null && mFrontend != null) {
setLnb(mLnb);
}
}
return mLnb;
} finally {
releaseTRMSLock();
mLnbLock.unlock();
}
}
private boolean requestLnb() {
int[] lnbHandle = new int[1];
TunerLnbRequest request = new TunerLnbRequest();
request.clientId = 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() {
mDemuxLock.lock();
try {
if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX, mDemuxLock)) {
return null;
}
return nativeOpenTimeFilter();
} finally {
mDemuxLock.unlock();
}
}
/**
* Opens a Descrambler in tuner.
*
* @return a {@link Descrambler} object.
*/
@RequiresPermission(android.Manifest.permission.ACCESS_TV_DESCRAMBLER)
@Nullable
public Descrambler openDescrambler() {
acquireTRMSLock("openDescrambler()");
mDemuxLock.lock();
try {
// no need to unlock mDemuxLock (so pass null instead) as TRMS lock is already acquired
if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX, null)) {
return null;
}
return requestDescrambler();
} finally {
releaseTRMSLock();
mDemuxLock.unlock();
}
}
/**
* 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) {
mDemuxLock.lock();
try {
Objects.requireNonNull(executor, "executor must not be null");
Objects.requireNonNull(l, "OnRecordStatusChangedListener must not be null");
if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX, mDemuxLock)) {
return null;
}
DvrRecorder dvr = nativeOpenDvrRecorder(bufferSize);
dvr.setListener(executor, l);
return dvr;
} finally {
mDemuxLock.unlock();
}
}
/**
* 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) {
mDemuxLock.lock();
try {
Objects.requireNonNull(executor, "executor must not be null");
Objects.requireNonNull(l, "OnPlaybackStatusChangedListener must not be null");
if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX, mDemuxLock)) {
return null;
}
DvrPlayback dvr = nativeOpenDvrPlayback(bufferSize);
dvr.setListener(executor, l);
return dvr;
} finally {
mDemuxLock.unlock();
}
}
/**
* Request a frontend by frontend info.
*
* This API is used if the applications want to select a desired frontend before
* {@link tune} to use a specific satellite or sending SatCR DiSEqC command for {@link tune}.
*
* @param desiredFrontendInfo the FrontendInfo of the desired fronted. It can be retrieved by
* {@link getAvailableFrontendInfos}
*
* @return result status of open operation.
* @throws SecurityException if the caller does not have appropriate permissions.
*/
@Result
public int applyFrontend(@NonNull FrontendInfo desiredFrontendInfo) {
Objects.requireNonNull(desiredFrontendInfo, "desiredFrontendInfo must not be null");
mFrontendLock.lock();
try {
if (mFeOwnerTuner != null) {
Log.e(TAG, "Operation connot be done by sharee of tuner");
return RESULT_INVALID_STATE;
}
if (mFrontendHandle != null) {
Log.e(TAG, "A frontend has been opened before");
return RESULT_INVALID_STATE;
}
mFrontendType = desiredFrontendInfo.getType();
mDesiredFrontendId = desiredFrontendInfo.getId();
if (DEBUG) {
Log.d(TAG, "Applying frontend with type " + mFrontendType + ", id "
+ mDesiredFrontendId);
}
if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND, mFrontendLock)) {
return RESULT_UNAVAILABLE;
}
return RESULT_SUCCESS;
} finally {
mFrontendLock.unlock();
}
}
/**
* Open a shared filter instance.
*
* @param context the context of the caller.
* @param sharedFilterToken the token of the shared filter being opened.
* @param executor the executor on which callback will be invoked.
* @param cb the listener to receive notifications from shared filter.
* @return the opened shared filter object. {@code null} if the operation failed.
*/
@RequiresPermission(android.Manifest.permission.ACCESS_TV_SHARED_FILTER)
@Nullable
static public SharedFilter openSharedFilter(@NonNull Context context,
@NonNull String sharedFilterToken, @CallbackExecutor @NonNull Executor executor,
@NonNull SharedFilterCallback cb) {
// TODO: check what happenes when onReclaimResources() is called and see if
// this needs to be protected with TRMS lock
Objects.requireNonNull(sharedFilterToken, "sharedFilterToken must not be null");
Objects.requireNonNull(executor, "executor must not be null");
Objects.requireNonNull(cb, "SharedFilterCallback must not be null");
if (context.checkCallingOrSelfPermission(
android.Manifest.permission.ACCESS_TV_SHARED_FILTER)
!= PackageManager.PERMISSION_GRANTED) {
throw new SecurityException("Caller must have ACCESS_TV_SHAREDFILTER permission.");
}
SharedFilter filter = nativeOpenSharedFilter(sharedFilterToken);
if (filter != null) {
filter.setCallback(cb, executor);
}
return filter;
}
/**
* Configures the desired {@link DemuxInfo}
*
* The already held demux and filters will be released when desiredDemuxInfo is null or the
* desireDemuxInfo.getFilterTypes() is not supported by the already held demux.
*
* @param desiredDemuxInfo the desired {@link DemuxInfo}, which includes information such as
* filterTypes ({@link DemuxFilterMainType}).
* @return result status of configure demux operation. {@link #RESULT_UNAVAILABLE} is returned
* when a) the desired capabilities are not supported by the system,
* b) this API is called on unsupported version, or
* c) either getDemuxCapabilities or getFilterTypeCapabilityList()
* returns an empty array
*/
@Result
public int configureDemux(@Nullable DemuxInfo desiredDemuxInfo) {
int tunerMajorVersion = TunerVersionChecker.getMajorVersion(sTunerVersion);
if (sTunerVersion < TunerVersionChecker.TUNER_VERSION_3_0) {
Log.e(TAG, "configureDemux() is not supported for tuner version:"
+ TunerVersionChecker.getMajorVersion(sTunerVersion) + "."
+ TunerVersionChecker.getMinorVersion(sTunerVersion) + ".");
return RESULT_UNAVAILABLE;
}
synchronized (mDemuxLock) {
return configureDemuxInternal(desiredDemuxInfo, true /* reduceDesiredFilterTypes */);
}
}
private int configureDemuxInternal(@Nullable DemuxInfo desiredDemuxInfo,
boolean reduceDesiredFilterTypes) {
// release the currently held demux if the desired demux info is null
if (desiredDemuxInfo == null) {
if (mDemuxHandle != null) {
releaseFilters();
releaseDemux();
}
return RESULT_SUCCESS;
}
int desiredFilterTypes = desiredDemuxInfo.getFilterTypes();
// just update and return success if the desiredFilterTypes is equal to or a subset of
// a previously configured value
if ((mDesiredDemuxInfo.getFilterTypes() & desiredFilterTypes)
== desiredFilterTypes) {
if (reduceDesiredFilterTypes) {
mDesiredDemuxInfo.setFilterTypes(desiredFilterTypes);
}
return RESULT_SUCCESS;
}
// check if the desire capability is supported
DemuxCapabilities caps = nativeGetDemuxCapabilities();
if (caps == null) {
Log.e(TAG, "configureDemuxInternal:failed to get DemuxCapabilities");
return RESULT_UNAVAILABLE;
}
int[] filterCapsList = caps.getFilterTypeCapabilityList();
if (filterCapsList.length <= 0) {
Log.e(TAG, "configureDemuxInternal: getFilterTypeCapabilityList()"
+ " returned an empty array");
return RESULT_UNAVAILABLE;
}
boolean supported = false;
for (int filterCaps : filterCapsList) {
if ((desiredFilterTypes & filterCaps) == desiredFilterTypes) {
supported = true;
break;
}
}
if (!supported) {
Log.e(TAG, "configureDemuxInternal: requested caps:" + desiredFilterTypes
+ " is not supported by the system");
return RESULT_UNAVAILABLE;
}
// close demux if not compatible
if (mDemuxHandle != null) {
if (desiredFilterTypes != Filter.TYPE_UNDEFINED) {
// Release the existing demux only if
// the desired caps is not supported
DemuxInfo currentDemuxInfo = nativeGetDemuxInfo(mDemuxHandle);
if (currentDemuxInfo != null) {
if ((desiredFilterTypes & currentDemuxInfo.getFilterTypes())
!= desiredFilterTypes) {
releaseFilters();
releaseDemux();
}
}
}
}
mDesiredDemuxInfo.setFilterTypes(desiredFilterTypes);
return RESULT_SUCCESS;
}
private boolean requestDemux() {
int[] demuxHandle = new int[1];
TunerDemuxRequest request = new TunerDemuxRequest();
request.clientId = mClientId;
request.desiredFilterTypes = mDesiredDemuxInfo.getFilterTypes();
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();
request.clientId = mClientId;
boolean granted = mTunerResourceManager.requestDescrambler(request, descramblerHandle);
if (!granted) {
return null;
}
int handle = descramblerHandle[0];
Descrambler descrambler = nativeOpenDescramblerByHandle(handle);
if (descrambler != null) {
synchronized (mDescramblers) {
WeakReference weakDescrambler = new WeakReference
*
* @param frontendType {@link android.media.tv.tuner.frontend.FrontendSettings.Type} for the
* query to be done for.
*
* @return {@code false} only if someone else with strictly lower priority is holding the
* resourece.
* {@code true} otherwise.
*/
public boolean isLowestPriority(int frontendType) {
return mTunerResourceManager.isLowestPriority(mClientId, frontendType);
}
private long mNativeContext; // used by native jMediaTuner
/**
* Registers a tuner as a listener for frontend callbacks.
*/
private void registerFrontendCallbackListener(Tuner tuner) {
nativeRegisterFeCbListener(tuner.getNativeContext());
}
/**
* Unregisters a tuner as a listener for frontend callbacks.
*/
private void unregisterFrontendCallbackListener(Tuner tuner) {
nativeUnregisterFeCbListener(tuner.getNativeContext());
}
/**
* Returns the pointer to the associated JTuner.
*/
long getNativeContext() {
return mNativeContext;
}
/**
* Releases the Tuner instance.
*/
@Override
public void close() {
acquireTRMSLock("close()");
try {
releaseAll();
mTunerResourceManager.unregisterClientProfile(mClientId);
TunerUtils.throwExceptionForResult(nativeClose(), "failed to close tuner");
} finally {
releaseTRMSLock();
}
}
/**
* Either unshares the frontend resource (for sharee) or release Frontend (for owner)
*/
public void closeFrontend() {
acquireTRMSLock("closeFrontend()");
try {
releaseFrontend();
} finally {
releaseTRMSLock();
}
}
/**
* Releases frontend resource for the owner. Unshares frontend resource for the sharee.
*/
private void releaseFrontend() {
if (DEBUG) {
Log.d(TAG, "Tuner#releaseFrontend");
}
mFrontendLock.lock();
try {
if (mFrontendHandle != null) {
if (DEBUG) {
Log.d(TAG, "mFrontendHandle not null");
}
if (mFeOwnerTuner != null) {
if (DEBUG) {
Log.d(TAG, "mFeOwnerTuner not null - sharee");
}
// unregister self from the Frontend callback
mFeOwnerTuner.unregisterFrontendCallbackListener(this);
mFeOwnerTuner = null;
nativeUnshareFrontend();
} else {
if (DEBUG) {
Log.d(TAG, "mFeOwnerTuner null - owner");
}
// close resource as owner
int res = nativeCloseFrontend(mFrontendHandle);
if (res != Tuner.RESULT_SUCCESS) {
TunerUtils.throwExceptionForResult(res, "failed to close frontend");
}
}
if (DEBUG) {
Log.d(TAG, "call TRM#releaseFrontend :" + mFrontendHandle + ", " + mClientId);
}
mTunerResourceManager.releaseFrontend(mFrontendHandle, mClientId);
FrameworkStatsLog
.write(FrameworkStatsLog.TV_TUNER_STATE_CHANGED, mUserId,
FrameworkStatsLog.TV_TUNER_STATE_CHANGED__STATE__UNKNOWN);
replicateFrontendSettings(null);
}
} finally {
mFrontendLock.unlock();
}
}
/**
* Releases CiCam resource if held. No-op otherwise.
*/
private void releaseCiCam() {
mFrontendCiCamLock.lock();
try {
if (mFrontendCiCamHandle != null) {
if (DEBUG) {
Log.d(TAG, "unlinking CiCam : " + mFrontendCiCamHandle + " for " + mClientId);
}
int result = nativeUnlinkCiCam(mFrontendCiCamId);
if (result == RESULT_SUCCESS) {
mTunerResourceManager.releaseCiCam(mFrontendCiCamHandle, mClientId);
replicateCiCamSettings(null);
} else {
Log.e(TAG, "nativeUnlinkCiCam(" + mFrontendCiCamHandle + ") for mClientId:"
+ mClientId + "failed with result:" + result);
}
} else {
if (DEBUG) {
Log.d(TAG, "NOT unlinking CiCam : " + mClientId);
}
}
} finally {
mFrontendCiCamLock.unlock();
}
}
/**
* Releases Lnb resource if held. TRMS lock must be acquired prior to calling this function.
*/
private void closeLnb() {
mLnbLock.lock();
try {
// mLnb will be non-null only for owner tuner
if (mLnb != null) {
if (DEBUG) {
Log.d(TAG, "calling mLnb.close() : " + mClientId);
}
mLnb.closeInternal();
} else {
if (DEBUG) {
Log.d(TAG, "NOT calling mLnb.close() : " + mClientId);
}
}
} finally {
mLnbLock.unlock();
}
}
private void releaseFilters() {
synchronized (mFilters) {
if (!mFilters.isEmpty()) {
for (WeakReference