script-astra/Android/Sdk/sources/android-35/android/media/tv/tuner/Tuner.java

2841 lines
107 KiB
Java
Raw Normal View History

2025-01-20 15:15:20 +00:00
/*
* 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.
*
* <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 = 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.
*
* <p>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.
*
* <p>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.
*
* <p>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.
*
* <p>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.
*
* <p>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.
*
* <p> Tuner will send {@link android.media.tv.tuner.frontend.ScanCallback#onLocked}
*/
public static final int SCAN_TYPE_AUTO = 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 = 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<Integer, WeakReference<Descrambler>> mDescramblers = new HashMap<>();
private List<WeakReference<Filter>> mFilters = new ArrayList<WeakReference<Filter>>();
private final TunerResourceManager.ResourcesReclaimListener mResourceListener =
new TunerResourceManager.ResourcesReclaimListener() {
@Override
public void onReclaimResources() {
if (mFrontend != null) {
FrameworkStatsLog
.write(FrameworkStatsLog.TV_TUNER_STATE_CHANGED, mUserId,
FrameworkStatsLog.TV_TUNER_STATE_CHANGED__STATE__UNKNOWN);
}
releaseAll();
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) {
mContext = context;
mTunerResourceManager = mContext.getSystemService(TunerResourceManager.class);
// The Tuner Resource Manager is only started when the device has the tuner feature.
if (mTunerResourceManager == null) {
throw new IllegalStateException(
"Tuner instance is created, but the device doesn't have tuner feature");
}
// This code will start tuner server if the device is running on the lazy tuner HAL.
nativeSetup();
sTunerVersion = nativeGetTunerVersion();
if (sTunerVersion == TunerVersionChecker.TUNER_VERSION_UNKNOWN) {
Log.e(TAG, "Unknown Tuner version!");
} else {
Log.d(TAG, "Current Tuner version is "
+ TunerVersionChecker.getMajorVersion(sTunerVersion) + "."
+ TunerVersionChecker.getMinorVersion(sTunerVersion) + ".");
}
if (mHandler == null) {
mHandler = createEventHandler();
}
int[] clientId = new int[1];
ResourceClientProfile profile = new ResourceClientProfile();
profile.tvInputSessionId = tvInputSessionId;
profile.useCase = useCase;
mTunerResourceManager.registerClientProfile(
profile, Runnable::run, mResourceListener, clientId);
mClientId = clientId[0];
mUserId = Process.myUid();
}
/**
* Get frontend info list from native and build them into a {@link FrontendInfo} list. Any
* {@code null} FrontendInfo element would be removed.
*/
private FrontendInfo[] getFrontendInfoListInternal() {
List<Integer> ids = getFrontendIds();
if (ids == null) {
return null;
}
FrontendInfo[] infos = new FrontendInfo[ids.size()];
for (int i = 0; i < ids.size(); i++) {
int id = ids.get(i);
FrontendInfo frontendInfo = getFrontendInfoById(id);
if (frontendInfo == null) {
Log.e(TAG, "Failed to get a FrontendInfo on frontend id:" + id + "!");
continue;
}
infos[i] = frontendInfo;
}
return Arrays.stream(infos).filter(Objects::nonNull).toArray(FrontendInfo[]::new);
}
/** @hide */
public static int getTunerVersion() {
return sTunerVersion;
}
/** @hide */
public List<Integer> getFrontendIds() {
mFrontendLock.lock();
try {
return nativeGetFrontendIds();
} finally {
mFrontendLock.unlock();
}
}
/**
* 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) {
synchronized (mOnResourceLostListenerLock) {
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() {
synchronized (mOnResourceLostListenerLock) {
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) {
acquireTRMSLock("shareFrontendFromTuner()");
mFrontendLock.lock();
try {
if (mFeOwnerTuner != null) {
// unregister self from the Frontend callback
mFeOwnerTuner.unregisterFrontendCallbackListener(this);
mFeOwnerTuner = null;
nativeUnshareFrontend();
}
mTunerResourceManager.shareFrontend(mClientId, tuner.mClientId);
mFeOwnerTuner = tuner;
mFeOwnerTuner.registerFrontendCallbackListener(this);
mFrontendHandle = mFeOwnerTuner.mFrontendHandle;
mFrontend = mFeOwnerTuner.mFrontend;
nativeShareFrontend(mFrontend.mId);
} finally {
releaseTRMSLock();
mFrontendLock.unlock();
}
}
/**
* Transfers the ownership of shared frontend and its associated resources.
*
* @param newOwner the Tuner instance to be the new owner.
*
* @return result status of tune operation.
*/
public int transferOwner(@NonNull Tuner newOwner) {
acquireTRMSLock("transferOwner()");
mFrontendLock.lock();
mFrontendCiCamLock.lock();
mLnbLock.lock();
try {
if (!isFrontendOwner() || !isNewOwnerQualifiedForTransfer(newOwner)) {
return RESULT_INVALID_STATE;
}
int res = transferFeOwner(newOwner);
if (res != RESULT_SUCCESS) {
return res;
}
res = transferCiCamOwner(newOwner);
if (res != RESULT_SUCCESS) {
return res;
}
res = transferLnbOwner(newOwner);
if (res != RESULT_SUCCESS) {
return res;
}
} finally {
mFrontendLock.unlock();
mFrontendCiCamLock.unlock();
mLnbLock.unlock();
releaseTRMSLock();
}
return RESULT_SUCCESS;
}
/**
* Resets or copies Frontend related settings.
*/
private void replicateFrontendSettings(@Nullable Tuner src) {
mFrontendLock.lock();
try {
if (src == null) {
if (DEBUG) {
Log.d(TAG, "resetting Frontend params for " + mClientId);
}
mFrontend = null;
mFrontendHandle = null;
mFrontendInfo = null;
mFrontendType = FrontendSettings.TYPE_UNDEFINED;
} else {
if (DEBUG) {
Log.d(TAG, "copying Frontend params from " + src.mClientId
+ " to " + mClientId);
}
mFrontend = src.mFrontend;
mFrontendHandle = src.mFrontendHandle;
mFrontendInfo = src.mFrontendInfo;
mFrontendType = src.mFrontendType;
}
} finally {
mFrontendLock.unlock();
}
}
/**
* Sets the frontend owner. mFeOwnerTuner should be null for the owner Tuner instance.
*/
private void setFrontendOwner(Tuner owner) {
mFrontendLock.lock();
try {
mFeOwnerTuner = owner;
} finally {
mFrontendLock.unlock();
}
}
/**
* Resets or copies the CiCam related settings.
*/
private void replicateCiCamSettings(@Nullable Tuner src) {
mFrontendCiCamLock.lock();
try {
if (src == null) {
if (DEBUG) {
Log.d(TAG, "resetting CiCamParams: " + mClientId);
}
mFrontendCiCamHandle = null;
mFrontendCiCamId = null;
} else {
if (DEBUG) {
Log.d(TAG, "copying CiCamParams from " + src.mClientId + " to " + mClientId);
Log.d(TAG, "mFrontendCiCamHandle:" + src.mFrontendCiCamHandle + ", "
+ "mFrontendCiCamId:" + src.mFrontendCiCamId);
}
mFrontendCiCamHandle = src.mFrontendCiCamHandle;
mFrontendCiCamId = src.mFrontendCiCamId;
}
} finally {
mFrontendCiCamLock.unlock();
}
}
/**
* Resets or copies Lnb related settings.
*/
private void replicateLnbSettings(@Nullable Tuner src) {
mLnbLock.lock();
try {
if (src == null) {
if (DEBUG) {
Log.d(TAG, "resetting Lnb params");
}
mLnb = null;
mLnbHandle = null;
} else {
if (DEBUG) {
Log.d(TAG, "copying Lnb params from " + src.mClientId + " to " + mClientId);
}
mLnb = src.mLnb;
mLnbHandle = src.mLnbHandle;
}
} finally {
mLnbLock.unlock();
}
}
/**
* Checks if it is a frontend resource owner.
* Proper mutex must be held prior to calling this.
*/
private boolean isFrontendOwner() {
boolean notAnOwner = (mFeOwnerTuner != null);
if (notAnOwner) {
Log.e(TAG, "transferOwner() - cannot be called on the non-owner");
return false;
}
return true;
}
/**
* Checks if the newOwner is qualified.
* Proper mutex must be held prior to calling this.
*/
private boolean isNewOwnerQualifiedForTransfer(@NonNull Tuner newOwner) {
// new owner must be the current sharee
boolean newOwnerIsTheCurrentSharee = (newOwner.mFeOwnerTuner == this)
&& (newOwner.mFrontendHandle.equals(mFrontendHandle));
if (!newOwnerIsTheCurrentSharee) {
Log.e(TAG, "transferOwner() - new owner must be the current sharee");
return false;
}
// new owner must not be holding any of the to-be-shared resources
boolean newOwnerAlreadyHoldsToBeSharedResource =
(newOwner.mFrontendCiCamHandle != null || newOwner.mLnb != null);
if (newOwnerAlreadyHoldsToBeSharedResource) {
Log.e(TAG, "transferOwner() - new owner cannot be holding CiCam"
+ " nor Lnb resource");
return false;
}
return true;
}
/**
* Transfers the ownership of the already held frontend resource.
* Proper mutex must be held prior to calling this.
*/
private int transferFeOwner(@NonNull Tuner newOwner) {
// handle native resource first
newOwner.nativeUpdateFrontend(getNativeContext());
nativeUpdateFrontend(0);
// transfer frontend related settings
newOwner.replicateFrontendSettings(this);
// transfer the frontend owner info
setFrontendOwner(newOwner);
newOwner.setFrontendOwner(null);
// handle TRM
if (mTunerResourceManager.transferOwner(
TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND,
mClientId, newOwner.mClientId)) {
return RESULT_SUCCESS;
} else {
return RESULT_UNKNOWN_ERROR;
}
}
/**
* Transfers the ownership of CiCam resource.
* This is a no-op if the CiCam resource is not held.
* Proper mutex must be held prior to calling this.
*/
private int transferCiCamOwner(Tuner newOwner) {
boolean notAnOwner = (mFrontendCiCamHandle == null);
if (notAnOwner) {
// There is nothing to do here if there is no CiCam
return RESULT_SUCCESS;
}
// no need to handle at native level
// transfer the CiCam info at Tuner level
newOwner.replicateCiCamSettings(this);
replicateCiCamSettings(null);
// handle TRM
if (mTunerResourceManager.transferOwner(
TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND_CICAM,
mClientId, newOwner.mClientId)) {
return RESULT_SUCCESS;
} else {
return RESULT_UNKNOWN_ERROR;
}
}
/**
* Transfers the ownership of Lnb resource.
* This is a no-op if the Lnb resource is not held.
* Proper mutex must be held prior to calling this.
*/
private int transferLnbOwner(Tuner newOwner) {
boolean notAnOwner = (mLnb == null);
if (notAnOwner) {
// There is nothing to do here if there is no Lnb
return RESULT_SUCCESS;
}
// no need to handle at native level
// set the new owner
mLnb.setOwner(newOwner);
newOwner.replicateLnbSettings(this);
replicateLnbSettings(null);
// handle TRM
if (mTunerResourceManager.transferOwner(
TunerResourceManager.TUNER_RESOURCE_TYPE_LNB,
mClientId, newOwner.mClientId)) {
return RESULT_SUCCESS;
} else {
return RESULT_UNKNOWN_ERROR;
}
}
/**
* 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. 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}
*
* <p>The priority comparison is done against the current holders of the frontend resource.
*
* <p>The behavior of this function is independent of the availability of unused resources.
*
* <p>The function returns {@code true} in any of the following sceanrios:
* <ul>
* <li>The caller has the priority <= other clients</li>
* <li>No one is holding the frontend resource of the specified type</li>
* <li>The caller is the only one who is holding the resource</li>
* <li>The frontend resource of the specified type does not exist</li>
*
* </ul>
* @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<Filter> weakFilter : mFilters) {
Filter filter = weakFilter.get();
if (filter != null) {
filter.close();
}
}
mFilters.clear();
}
}
}
private void releaseDescramblers() {
synchronized (mDescramblers) {
if (!mDescramblers.isEmpty()) {
for (Map.Entry<Integer, WeakReference<Descrambler>> d : mDescramblers.entrySet()) {
Descrambler descrambler = d.getValue().get();
if (descrambler != null) {
descrambler.close();
}
mTunerResourceManager.releaseDescrambler(d.getKey(), mClientId);
}
mDescramblers.clear();
}
}
}
private void releaseDemux() {
mDemuxLock.lock();
try {
if (mDemuxHandle != null) {
int res = nativeCloseDemux(mDemuxHandle);
if (res != Tuner.RESULT_SUCCESS) {
TunerUtils.throwExceptionForResult(res, "failed to close demux");
}
mTunerResourceManager.releaseDemux(mDemuxHandle, mClientId);
mDemuxHandle = null;
}
} finally {
mDemuxLock.unlock();
}
}
private void releaseAll() {
// release CiCam before frontend because frontend handle is needed to unlink CiCam
releaseCiCam();
releaseFrontend();
closeLnb();
releaseDescramblers();
releaseFilters();
releaseDemux();
}
/**
* Native Initialization.
*/
private static native void nativeInit();
/**
* Native setup.
*/
private native void nativeSetup();
/**
* Native method to get all frontend IDs.
*/
private native int nativeGetTunerVersion();
/**
* 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);
private native int nativeShareFrontend(int id);
private native int nativeUnshareFrontend();
private native void nativeRegisterFeCbListener(long nativeContext);
private native void nativeUnregisterFeCbListener(long nativeContext);
// nativeUpdateFrontend must be called on the new owner first
private native void nativeUpdateFrontend(long nativeContext);
@Result
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(Lnb lnb);
private native boolean nativeIsLnaSupported();
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 nativeLinkCiCam(int ciCamId);
private native int nativeDisconnectCiCam();
private native int nativeUnlinkCiCam(int ciCamId);
private native FrontendInfo nativeGetFrontendInfo(int id);
private native Filter nativeOpenFilter(int type, int subType, long bufferSize);
private native TimeFilter nativeOpenTimeFilter();
private native String nativeGetFrontendHardwareInfo();
private native int nativeSetMaxNumberOfFrontends(int frontendType, int maxNumber);
private native int nativeGetMaxNumberOfFrontends(int frontendType);
private native int nativeRemoveOutputPid(int pid);
private native Lnb nativeOpenLnbByHandle(int handle);
private native Lnb nativeOpenLnbByName(String name);
private native FrontendStatusReadiness[] nativeGetFrontendStatusReadiness(int[] statusTypes);
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 native DemuxCapabilities nativeGetDemuxCapabilities();
private native DemuxInfo nativeGetDemuxInfo(int demuxHandle);
private native int nativeCloseDemux(int handle);
private native int nativeCloseFrontend(int handle);
private native int nativeClose();
private static native SharedFilter nativeOpenSharedFilter(String token);
/**
* 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: {
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.
*
* <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) {
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.
*
* <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_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)}.
*
* <p>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.
*
* <p>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.
*
* <p>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.
*
* <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() {
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.
*
* <p>Details for channels found are returned via {@link ScanCallback}.
*
* <p>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.
*
* * <p>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.
*
* <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() {
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.
*
* <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) {
mLnbLock.lock();
try {
return nativeSetLnb(lnb);
} finally {
mLnbLock.unlock();
}
}
/**
* Is Low Noise Amplifier (LNA) supported by the Tuner.
*
* <p>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.
*
* <p>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
*
* <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) {
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).
*
* <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.
*
* <p> 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.
*
* <p>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.
*
* <p> 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.
*
* <p>Use {@link #disconnectFrontendToCiCam(int)} to disconnect.
*
* <p>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).
*
* <p>The demux will use the output from the frontend as the input after this call.
*
* <p> 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.
*
* <p>It is used by the client to unlink CI-CAM to a Frontend.
*
* <p> 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.
*
* <p>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.
*
* <p>It is used by the client to remove a video or audio PID of other program to reduce the
* total amount of recorded TS.
*
* <p>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
* doesnt 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.
*
* <p>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<FrontendStatusReadiness> getFrontendStatusReadiness(
@NonNull @FrontendStatusType int[] statusTypes) {
mFrontendLock.lock();
try {
if (!TunerVersionChecker.checkHigherOrEqualVersionTo(
TunerVersionChecker.TUNER_VERSION_2_0, "Get fronted status readiness")) {
return Collections.EMPTY_LIST;
}
if (mFrontend == null) {
throw new IllegalStateException("frontend is not initialized");
}
if (mFeOwnerTuner != null) {
throw new IllegalStateException("Operation cannot be done by sharee of tuner");
}
FrontendStatusReadiness[] readiness = nativeGetFrontendStatusReadiness(statusTypes);
if (readiness == null) {
return Collections.EMPTY_LIST;
}
return Arrays.asList(readiness);
} finally {
mFrontendLock.unlock();
}
}
/**
* Gets the currently initialized and activated frontend information. To get all the available
* frontend info on the device, use {@link getAvailableFrontendInfos()}.
*
* @return The active frontend information. {@code null} if the operation failed.
* @throws IllegalStateException if there is no active frontend currently.
*/
@Nullable
public FrontendInfo getFrontendInfo() {
mFrontendLock.lock();
try {
if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND, mFrontendLock)) {
return null;
}
if (mFrontend == null) {
throw new IllegalStateException("frontend is not initialized");
}
if (mFrontendInfo == null) {
mFrontendInfo = getFrontendInfoById(mFrontend.mId);
}
return mFrontendInfo;
} finally {
mFrontendLock.unlock();
}
}
/**
* Gets a list of all the available frontend information on the device. To get the information
* of the currently active frontend, use {@link getFrontendInfo()}. The active frontend
* information is also included in the list of the available frontend information.
*
* @return The list of all the available frontend information. {@code null} if the operation
* failed.
*/
@Nullable
@SuppressLint("NullableCollection")
public List<FrontendInfo> getAvailableFrontendInfos() {
FrontendInfo[] feInfoList = getFrontendInfoListInternal();
if (feInfoList == null) {
return null;
}
return Arrays.asList(feInfoList);
}
/**
* Gets the currently initialized and activated frontend hardware information. The return values
* would differ per device makers. E.g. RF chip version, Demod chip version, detailed status of
* dvbs blind scan, etc
*
* <p>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.
*
* <p>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.
*
* <p>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.
*
* <p>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<Filter> weakFilter = new WeakReference<Filter>(filter);
mFilters.add(weakFilter);
if (mFilters.size() > FILTER_CLEANUP_THRESHOLD) {
Iterator<WeakReference<Filter>> iterator = mFilters.iterator();
while (iterator.hasNext()) {
WeakReference<Filter> wFilter = iterator.next();
if (wFilter.get() == null) {
iterator.remove();
}
}
}
}
}
return filter;
} finally {
mDemuxLock.unlock();
}
}
/**
* 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) {
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.
*
* <p> 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}
*
* <p>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<Descrambler>(descrambler);
mDescramblers.put(handle, weakDescrambler);
}
} else {
mTunerResourceManager.releaseDescrambler(handle, mClientId);
}
return descrambler;
}
private boolean requestFrontendCiCam(int ciCamId) {
int[] ciCamHandle = new int[1];
TunerCiCamRequest request = new TunerCiCamRequest();
request.clientId = mClientId;
request.ciCamId = ciCamId;
boolean granted = mTunerResourceManager.requestCiCam(request, ciCamHandle);
if (granted) {
mFrontendCiCamHandle = ciCamHandle[0];
mFrontendCiCamId = ciCamId;
}
return granted;
}
private boolean checkResource(int resourceType, ReentrantLock localLock) {
switch (resourceType) {
case TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND: {
if (mFrontendHandle == null && !requestResource(resourceType, localLock)) {
return false;
}
break;
}
case TunerResourceManager.TUNER_RESOURCE_TYPE_LNB: {
if (mLnb == null && !requestResource(resourceType, localLock)) {
return false;
}
break;
}
case TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX: {
if (mDemuxHandle == null && !requestResource(resourceType, localLock)) {
return false;
}
break;
}
case TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND_CICAM: {
if (mFrontendCiCamHandle == null && !requestResource(resourceType, localLock)) {
return false;
}
break;
}
default:
return false;
}
return true;
}
// Expected flow of how to use this function is:
// 1) lock the localLock and check if the resource is already held
// 2) if yes, no need to call this function and continue with the handle with the lock held
// 3) if no, then first release the held lock and grab the TRMS lock to avoid deadlock
// 4) grab the local lock again and release the TRMS lock
// If localLock is null, we'll assume the caller does not want the lock related operations
private boolean requestResource(int resourceType, ReentrantLock localLock) {
boolean enableLockOperations = localLock != null;
// release the local lock first to avoid deadlock
if (enableLockOperations) {
if (localLock.isLocked()) {
localLock.unlock();
} else {
throw new IllegalStateException("local lock must be locked beforehand");
}
}
// now safe to grab TRMS lock
if (enableLockOperations) {
acquireTRMSLock("requestResource:" + resourceType);
}
try {
// lock the local lock
if (enableLockOperations) {
localLock.lock();
}
switch (resourceType) {
case TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND: {
return requestFrontend();
}
case TunerResourceManager.TUNER_RESOURCE_TYPE_LNB: {
return requestLnb();
}
case TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX: {
return requestDemux();
}
case TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND_CICAM: {
return requestFrontendCiCam(mRequestedCiCamId);
}
default:
return false;
}
} finally {
if (enableLockOperations) {
releaseTRMSLock();
}
}
}
// Must be called while TRMS lock is being held
/* package */ void releaseLnb() {
mLnbLock.lock();
try {
if (mLnbHandle != null) {
// LNB handle can be null if it's opened by name.
if (DEBUG) {
Log.d(TAG, "releasing Lnb");
}
mTunerResourceManager.releaseLnb(mLnbHandle, mClientId);
mLnbHandle = null;
} else {
if (DEBUG) {
Log.d(TAG, "NOT releasing Lnb because mLnbHandle is null");
}
}
mLnb = null;
} finally {
mLnbLock.unlock();
}
}
/** @hide */
public int getClientId() {
return mClientId;
}
/* package */ TunerResourceManager getTunerResourceManager() {
return mTunerResourceManager;
}
private void acquireTRMSLock(String functionNameForLog) {
if (DEBUG) {
Log.d(TAG, "ATTEMPT:acquireLock() in " + functionNameForLog
+ "for clientId:" + mClientId);
}
if (!mTunerResourceManager.acquireLock(mClientId)) {
Log.e(TAG, "FAILED:acquireLock() in " + functionNameForLog
+ " for clientId:" + mClientId + " - this can cause deadlock between"
+ " Tuner API calls and onReclaimResources()");
}
}
private void releaseTRMSLock() {
mTunerResourceManager.releaseLock(mClientId);
}
}