2841 lines
107 KiB
Java
2841 lines
107 KiB
Java
/*
|
||
* 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
|
||
* 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.
|
||
*
|
||
* <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);
|
||
}
|
||
}
|