All Downloads are FREE. Search and download functionalities are using the official Maven repository.

src.android.media.MediaCas Maven / Gradle / Ivy

Go to download

A library jar that provides APIs for Applications written for the Google Android Platform.

There is a newer version: 15-robolectric-12650502
Show newest version
/*
 * Copyright (C) 2017 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;

import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.TestApi;
import android.content.Context;
import android.hardware.cas.V1_0.HidlCasPluginDescriptor;
import android.hardware.cas.V1_0.ICas;
import android.hardware.cas.V1_0.IMediaCasService;
import android.hardware.cas.V1_2.ICasListener;
import android.hardware.cas.V1_2.Status;
import android.media.MediaCasException.*;
import android.media.tv.TvInputService.PriorityHintUseCaseType;
import android.media.tv.tunerresourcemanager.CasSessionRequest;
import android.media.tv.tunerresourcemanager.ResourceClientProfile;
import android.media.tv.tunerresourcemanager.TunerResourceManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IHwBinder;
import android.os.Looper;
import android.os.Message;
import android.os.Process;
import android.os.RemoteException;
import android.util.Log;
import android.util.Singleton;

import com.android.internal.util.FrameworkStatsLog;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;

/**
 * MediaCas can be used to obtain keys for descrambling protected media streams, in
 * conjunction with {@link android.media.MediaDescrambler}. The MediaCas APIs are
 * designed to support conditional access such as those in the ISO/IEC13818-1.
 * The CA system is identified by a 16-bit integer CA_system_id. The scrambling
 * algorithms are usually proprietary and implemented by vendor-specific CA plugins
 * installed on the device.
 * 

* The app is responsible for constructing a MediaCas object for the CA system it * intends to use. The app can query if a certain CA system is supported using static * method {@link #isSystemIdSupported}. It can also obtain the entire list of supported * CA systems using static method {@link #enumeratePlugins}. *

* Once the MediaCas object is constructed, the app should properly provision it by * using method {@link #provision} and/or {@link #processEmm}. The EMMs (Entitlement * management messages) can be distributed out-of-band, or in-band with the stream. *

* To descramble elementary streams, the app first calls {@link #openSession} to * generate a {@link Session} object that will uniquely identify a session. A session * provides a context for subsequent key updates and descrambling activities. The ECMs * (Entitlement control messages) are sent to the session via method * {@link Session#processEcm}. *

* The app next constructs a MediaDescrambler object, and initializes it with the * session using {@link MediaDescrambler#setMediaCasSession}. This ties the * descrambler to the session, and the descrambler can then be used to descramble * content secured with the session's key, either during extraction, or during decoding * with {@link android.media.MediaCodec}. *

* If the app handles sample extraction using its own extractor, it can use * MediaDescrambler to descramble samples into clear buffers (if the session's license * doesn't require secure decoders), or descramble a small amount of data to retrieve * information necessary for the downstream pipeline to process the sample (if the * session's license requires secure decoders). *

* If the session requires a secure decoder, a MediaDescrambler needs to be provided to * MediaCodec to descramble samples queued by {@link MediaCodec#queueSecureInputBuffer} * into protected buffers. The app should use {@link MediaCodec#configure(MediaFormat, * android.view.Surface, int, MediaDescrambler)} instead of the normal {@link * MediaCodec#configure(MediaFormat, android.view.Surface, MediaCrypto, int)} method * to configure MediaCodec. *

*

Using Android's MediaExtractor

*

* If the app uses {@link MediaExtractor}, it can delegate the CAS session * management to MediaExtractor by calling {@link MediaExtractor#setMediaCas}. * MediaExtractor will take over and call {@link #openSession}, {@link #processEmm} * and/or {@link Session#processEcm}, etc.. if necessary. *

* When using {@link MediaExtractor}, the app would still need a MediaDescrambler * to use with {@link MediaCodec} if the licensing requires a secure decoder. The * session associated with the descrambler of a track can be retrieved by calling * {@link MediaExtractor#getCasInfo}, and used to initialize a MediaDescrambler * object for MediaCodec. *

*

Listeners

*

The app may register a listener to receive events from the CA system using * method {@link #setEventListener}. The exact format of the event is scheme-specific * and is not specified by this API. */ public final class MediaCas implements AutoCloseable { private static final String TAG = "MediaCas"; private ICas mICas; private android.hardware.cas.V1_1.ICas mICasV11; private android.hardware.cas.V1_2.ICas mICasV12; private EventListener mListener; private HandlerThread mHandlerThread; private EventHandler mEventHandler; private @PriorityHintUseCaseType int mPriorityHint; private String mTvInputServiceSessionId; private int mClientId; private int mCasSystemId; private int mUserId; private TunerResourceManager mTunerResourceManager = null; private final Map mSessionMap = new HashMap<>(); /** * Scrambling modes used to open cas sessions. * * @hide */ @IntDef(prefix = "SCRAMBLING_MODE_", value = {SCRAMBLING_MODE_RESERVED, SCRAMBLING_MODE_DVB_CSA1, SCRAMBLING_MODE_DVB_CSA2, SCRAMBLING_MODE_DVB_CSA3_STANDARD, SCRAMBLING_MODE_DVB_CSA3_MINIMAL, SCRAMBLING_MODE_DVB_CSA3_ENHANCE, SCRAMBLING_MODE_DVB_CISSA_V1, SCRAMBLING_MODE_DVB_IDSA, SCRAMBLING_MODE_MULTI2, SCRAMBLING_MODE_AES128, SCRAMBLING_MODE_AES_ECB, SCRAMBLING_MODE_AES_SCTE52, SCRAMBLING_MODE_TDES_ECB, SCRAMBLING_MODE_TDES_SCTE52}) @Retention(RetentionPolicy.SOURCE) public @interface ScramblingMode {} /** * DVB (Digital Video Broadcasting) reserved mode. */ public static final int SCRAMBLING_MODE_RESERVED = android.hardware.cas.V1_2.ScramblingMode.RESERVED; /** * DVB (Digital Video Broadcasting) Common Scrambling Algorithm (CSA) 1. */ public static final int SCRAMBLING_MODE_DVB_CSA1 = android.hardware.cas.V1_2.ScramblingMode.DVB_CSA1; /** * DVB CSA 2. */ public static final int SCRAMBLING_MODE_DVB_CSA2 = android.hardware.cas.V1_2.ScramblingMode.DVB_CSA2; /** * DVB CSA 3 in standard mode. */ public static final int SCRAMBLING_MODE_DVB_CSA3_STANDARD = android.hardware.cas.V1_2.ScramblingMode.DVB_CSA3_STANDARD; /** * DVB CSA 3 in minimally enhanced mode. */ public static final int SCRAMBLING_MODE_DVB_CSA3_MINIMAL = android.hardware.cas.V1_2.ScramblingMode.DVB_CSA3_MINIMAL; /** * DVB CSA 3 in fully enhanced mode. */ public static final int SCRAMBLING_MODE_DVB_CSA3_ENHANCE = android.hardware.cas.V1_2.ScramblingMode.DVB_CSA3_ENHANCE; /** * DVB Common IPTV Software-oriented Scrambling Algorithm (CISSA) Version 1. */ public static final int SCRAMBLING_MODE_DVB_CISSA_V1 = android.hardware.cas.V1_2.ScramblingMode.DVB_CISSA_V1; /** * ATIS-0800006 IIF Default Scrambling Algorithm (IDSA). */ public static final int SCRAMBLING_MODE_DVB_IDSA = android.hardware.cas.V1_2.ScramblingMode.DVB_IDSA; /** * A symmetric key algorithm. */ public static final int SCRAMBLING_MODE_MULTI2 = android.hardware.cas.V1_2.ScramblingMode.MULTI2; /** * Advanced Encryption System (AES) 128-bit Encryption mode. */ public static final int SCRAMBLING_MODE_AES128 = android.hardware.cas.V1_2.ScramblingMode.AES128; /** * Advanced Encryption System (AES) Electronic Code Book (ECB) mode. */ public static final int SCRAMBLING_MODE_AES_ECB = android.hardware.cas.V1_2.ScramblingMode.AES_ECB; /** * Advanced Encryption System (AES) Society of Cable Telecommunications Engineers (SCTE) 52 * mode. */ public static final int SCRAMBLING_MODE_AES_SCTE52 = android.hardware.cas.V1_2.ScramblingMode.AES_SCTE52; /** * Triple Data Encryption Algorithm (TDES) Electronic Code Book (ECB) mode. */ public static final int SCRAMBLING_MODE_TDES_ECB = android.hardware.cas.V1_2.ScramblingMode.TDES_ECB; /** * Triple Data Encryption Algorithm (TDES) Society of Cable Telecommunications Engineers (SCTE) * 52 mode. */ public static final int SCRAMBLING_MODE_TDES_SCTE52 = android.hardware.cas.V1_2.ScramblingMode.TDES_SCTE52; /** * Usages used to open cas sessions. * * @hide */ @IntDef(prefix = "SESSION_USAGE_", value = {SESSION_USAGE_LIVE, SESSION_USAGE_PLAYBACK, SESSION_USAGE_RECORD, SESSION_USAGE_TIMESHIFT}) @Retention(RetentionPolicy.SOURCE) public @interface SessionUsage {} /** * Cas session is used to descramble live streams. */ public static final int SESSION_USAGE_LIVE = android.hardware.cas.V1_2.SessionIntent.LIVE; /** * Cas session is used to descramble recoreded streams. */ public static final int SESSION_USAGE_PLAYBACK = android.hardware.cas.V1_2.SessionIntent.PLAYBACK; /** * Cas session is used to descramble live streams and encrypt local recorded content */ public static final int SESSION_USAGE_RECORD = android.hardware.cas.V1_2.SessionIntent.RECORD; /** * Cas session is used to descramble live streams , encrypt local recorded content and playback * local encrypted content. */ public static final int SESSION_USAGE_TIMESHIFT = android.hardware.cas.V1_2.SessionIntent.TIMESHIFT; /** * Plugin status events sent from cas system. * * @hide */ @IntDef(prefix = "PLUGIN_STATUS_", value = {PLUGIN_STATUS_PHYSICAL_MODULE_CHANGED, PLUGIN_STATUS_SESSION_NUMBER_CHANGED}) @Retention(RetentionPolicy.SOURCE) public @interface PluginStatus {} /** * The event to indicate that the status of CAS system is changed by the removal or insertion of * physical CAS modules. */ public static final int PLUGIN_STATUS_PHYSICAL_MODULE_CHANGED = android.hardware.cas.V1_2.StatusEvent.PLUGIN_PHYSICAL_MODULE_CHANGED; /** * The event to indicate that the number of CAS system's session is changed. */ public static final int PLUGIN_STATUS_SESSION_NUMBER_CHANGED = android.hardware.cas.V1_2.StatusEvent.PLUGIN_SESSION_NUMBER_CHANGED; private static final Singleton sService = new Singleton() { @Override protected IMediaCasService create() { try { Log.d(TAG, "Trying to get [email protected] service"); android.hardware.cas.V1_2.IMediaCasService serviceV12 = android.hardware.cas.V1_2.IMediaCasService.getService(true /*wait*/); if (serviceV12 != null) { return serviceV12; } } catch (Exception eV1_2) { Log.d(TAG, "Failed to get [email protected] service"); } try { Log.d(TAG, "Trying to get [email protected] service"); android.hardware.cas.V1_1.IMediaCasService serviceV11 = android.hardware.cas.V1_1.IMediaCasService.getService(true /*wait*/); if (serviceV11 != null) { return serviceV11; } } catch (Exception eV1_1) { Log.d(TAG, "Failed to get [email protected] service"); } try { Log.d(TAG, "Trying to get [email protected] service"); return IMediaCasService.getService(true /*wait*/); } catch (Exception eV1_0) { Log.d(TAG, "Failed to get [email protected] service"); } return null; } }; static IMediaCasService getService() { return sService.get(); } private void validateInternalStates() { if (mICas == null) { throw new IllegalStateException(); } } private void cleanupAndRethrowIllegalState() { mICas = null; mICasV11 = null; mICasV12 = null; throw new IllegalStateException(); } private class EventHandler extends Handler { private static final int MSG_CAS_EVENT = 0; private static final int MSG_CAS_SESSION_EVENT = 1; private static final int MSG_CAS_STATUS_EVENT = 2; private static final int MSG_CAS_RESOURCE_LOST = 3; private static final String SESSION_KEY = "sessionId"; private static final String DATA_KEY = "data"; public EventHandler(Looper looper) { super(looper); } @Override public void handleMessage(Message msg) { if (msg.what == MSG_CAS_EVENT) { mListener.onEvent(MediaCas.this, msg.arg1, msg.arg2, toBytes((ArrayList) msg.obj)); } else if (msg.what == MSG_CAS_SESSION_EVENT) { Bundle bundle = msg.getData(); ArrayList sessionId = toByteArray(bundle.getByteArray(SESSION_KEY)); mListener.onSessionEvent(MediaCas.this, createFromSessionId(sessionId), msg.arg1, msg.arg2, bundle.getByteArray(DATA_KEY)); } else if (msg.what == MSG_CAS_STATUS_EVENT) { if ((msg.arg1 == PLUGIN_STATUS_SESSION_NUMBER_CHANGED) && (mTunerResourceManager != null)) { mTunerResourceManager.updateCasInfo(mCasSystemId, msg.arg2); } mListener.onPluginStatusUpdate(MediaCas.this, msg.arg1, msg.arg2); } else if (msg.what == MSG_CAS_RESOURCE_LOST) { mListener.onResourceLost(MediaCas.this); } } } private final ICasListener.Stub mBinder = new ICasListener.Stub() { @Override public void onEvent(int event, int arg, @Nullable ArrayList data) throws RemoteException { if (mEventHandler != null) { mEventHandler.sendMessage(mEventHandler.obtainMessage( EventHandler.MSG_CAS_EVENT, event, arg, data)); } } @Override public void onSessionEvent(@NonNull ArrayList sessionId, int event, int arg, @Nullable ArrayList data) throws RemoteException { if (mEventHandler != null) { Message msg = mEventHandler.obtainMessage(); msg.what = EventHandler.MSG_CAS_SESSION_EVENT; msg.arg1 = event; msg.arg2 = arg; Bundle bundle = new Bundle(); bundle.putByteArray(EventHandler.SESSION_KEY, toBytes(sessionId)); bundle.putByteArray(EventHandler.DATA_KEY, toBytes(data)); msg.setData(bundle); mEventHandler.sendMessage(msg); } } @Override public void onStatusUpdate(byte status, int arg) throws RemoteException { if (mEventHandler != null) { mEventHandler.sendMessage(mEventHandler.obtainMessage( EventHandler.MSG_CAS_STATUS_EVENT, status, arg)); } } }; private final TunerResourceManager.ResourcesReclaimListener mResourceListener = new TunerResourceManager.ResourcesReclaimListener() { @Override public void onReclaimResources() { synchronized (mSessionMap) { List sessionList = new ArrayList<>(mSessionMap.keySet()); for (Session casSession: sessionList) { casSession.close(); } } mEventHandler.sendMessage(mEventHandler.obtainMessage( EventHandler.MSG_CAS_RESOURCE_LOST)); } }; /** * Describe a CAS plugin with its CA_system_ID and string name. * * Returned as results of {@link #enumeratePlugins}. * */ public static class PluginDescriptor { private final int mCASystemId; private final String mName; private PluginDescriptor() { mCASystemId = 0xffff; mName = null; } PluginDescriptor(@NonNull HidlCasPluginDescriptor descriptor) { mCASystemId = descriptor.caSystemId; mName = descriptor.name; } public int getSystemId() { return mCASystemId; } @NonNull public String getName() { return mName; } @Override public String toString() { return "PluginDescriptor {" + mCASystemId + ", " + mName + "}"; } } private ArrayList toByteArray(@NonNull byte[] data, int offset, int length) { ArrayList byteArray = new ArrayList(length); for (int i = 0; i < length; i++) { byteArray.add(Byte.valueOf(data[offset + i])); } return byteArray; } private ArrayList toByteArray(@Nullable byte[] data) { if (data == null) { return new ArrayList(); } return toByteArray(data, 0, data.length); } private byte[] toBytes(@NonNull ArrayList byteArray) { byte[] data = null; if (byteArray != null) { data = new byte[byteArray.size()]; for (int i = 0; i < data.length; i++) { data[i] = byteArray.get(i); } } return data; } /** * Class for an open session with the CA system. */ public final class Session implements AutoCloseable { final ArrayList mSessionId; boolean mIsClosed = false; Session(@NonNull ArrayList sessionId) { mSessionId = new ArrayList(sessionId); } private void validateSessionInternalStates() { if (mICas == null) { throw new IllegalStateException(); } if (mIsClosed) { MediaCasStateException.throwExceptionIfNeeded(Status.ERROR_CAS_SESSION_NOT_OPENED); } } /** * Query if an object equal current Session object. * * @param obj an object to compare to current Session object. * * @return Whether input object equal current Session object. */ public boolean equals(Object obj) { if (obj instanceof Session) { return mSessionId.equals(((Session) obj).mSessionId); } return false; } /** * Set the private data for a session. * * @param data byte array of the private data. * * @throws IllegalStateException if the MediaCas instance is not valid. * @throws MediaCasException for CAS-specific errors. * @throws MediaCasStateException for CAS-specific state exceptions. */ public void setPrivateData(@NonNull byte[] data) throws MediaCasException { validateSessionInternalStates(); try { MediaCasException.throwExceptionIfNeeded( mICas.setSessionPrivateData(mSessionId, toByteArray(data, 0, data.length))); } catch (RemoteException e) { cleanupAndRethrowIllegalState(); } } /** * Send a received ECM packet to the specified session of the CA system. * * @param data byte array of the ECM data. * @param offset position within data where the ECM data begins. * @param length length of the data (starting from offset). * * @throws IllegalStateException if the MediaCas instance is not valid. * @throws MediaCasException for CAS-specific errors. * @throws MediaCasStateException for CAS-specific state exceptions. */ public void processEcm(@NonNull byte[] data, int offset, int length) throws MediaCasException { validateSessionInternalStates(); try { MediaCasException.throwExceptionIfNeeded( mICas.processEcm(mSessionId, toByteArray(data, offset, length))); } catch (RemoteException e) { cleanupAndRethrowIllegalState(); } } /** * Send a received ECM packet to the specified session of the CA system. * This is similar to {@link Session#processEcm(byte[], int, int)} * except that the entire byte array is sent. * * @param data byte array of the ECM data. * * @throws IllegalStateException if the MediaCas instance is not valid. * @throws MediaCasException for CAS-specific errors. * @throws MediaCasStateException for CAS-specific state exceptions. */ public void processEcm(@NonNull byte[] data) throws MediaCasException { processEcm(data, 0, data.length); } /** * Send a session event to a CA system. The format of the event is * scheme-specific and is opaque to the framework. * * @param event an integer denoting a scheme-specific event to be sent. * @param arg a scheme-specific integer argument for the event. * @param data a byte array containing scheme-specific data for the event. * * @throws IllegalStateException if the MediaCas instance is not valid. * @throws MediaCasException for CAS-specific errors. * @throws MediaCasStateException for CAS-specific state exceptions. */ public void sendSessionEvent(int event, int arg, @Nullable byte[] data) throws MediaCasException { validateSessionInternalStates(); if (mICasV11 == null) { Log.d(TAG, "Send Session Event isn't supported by [email protected] interface"); throw new UnsupportedCasException("Send Session Event is not supported"); } try { MediaCasException.throwExceptionIfNeeded( mICasV11.sendSessionEvent(mSessionId, event, arg, toByteArray(data))); } catch (RemoteException e) { cleanupAndRethrowIllegalState(); } } /** * Get Session Id. * * @return session Id of the session. * * @throws IllegalStateException if the MediaCas instance is not valid. */ @NonNull public byte[] getSessionId() { validateSessionInternalStates(); return toBytes(mSessionId); } /** * Close the session. * * @throws IllegalStateException if the MediaCas instance is not valid. * @throws MediaCasStateException for CAS-specific state exceptions. */ @Override public void close() { validateSessionInternalStates(); try { MediaCasStateException.throwExceptionIfNeeded( mICas.closeSession(mSessionId)); mIsClosed = true; removeSessionFromResourceMap(this); } catch (RemoteException e) { cleanupAndRethrowIllegalState(); } } } Session createFromSessionId(@NonNull ArrayList sessionId) { if (sessionId == null || sessionId.size() == 0) { return null; } return new Session(sessionId); } /** * Query if a certain CA system is supported on this device. * * @param CA_system_id the id of the CA system. * * @return Whether the specified CA system is supported on this device. */ public static boolean isSystemIdSupported(int CA_system_id) { IMediaCasService service = getService(); if (service != null) { try { return service.isSystemIdSupported(CA_system_id); } catch (RemoteException e) { } } return false; } /** * List all available CA plugins on the device. * * @return an array of descriptors for the available CA plugins. */ public static PluginDescriptor[] enumeratePlugins() { IMediaCasService service = getService(); if (service != null) { try { ArrayList descriptors = service.enumeratePlugins(); if (descriptors.size() == 0) { return null; } PluginDescriptor[] results = new PluginDescriptor[descriptors.size()]; for (int i = 0; i < results.length; i++) { results[i] = new PluginDescriptor(descriptors.get(i)); } return results; } catch (RemoteException e) { } } return null; } private void createPlugin(int casSystemId) throws UnsupportedCasException { try { mCasSystemId = casSystemId; mUserId = Process.myUid(); IMediaCasService service = getService(); android.hardware.cas.V1_2.IMediaCasService serviceV12 = android.hardware.cas.V1_2.IMediaCasService.castFrom(service); if (serviceV12 == null) { android.hardware.cas.V1_1.IMediaCasService serviceV11 = android.hardware.cas.V1_1.IMediaCasService.castFrom(service); if (serviceV11 == null) { Log.d(TAG, "Used cas@1_0 interface to create plugin"); mICas = service.createPlugin(casSystemId, mBinder); } else { Log.d(TAG, "Used [email protected] interface to create plugin"); mICas = mICasV11 = serviceV11.createPluginExt(casSystemId, mBinder); } } else { Log.d(TAG, "Used [email protected] interface to create plugin"); mICas = mICasV11 = mICasV12 = android.hardware.cas.V1_2.ICas .castFrom(serviceV12.createPluginExt(casSystemId, mBinder)); } } catch(Exception e) { Log.e(TAG, "Failed to create plugin: " + e); mICas = null; } finally { if (mICas == null) { throw new UnsupportedCasException( "Unsupported casSystemId " + casSystemId); } } } private void registerClient(@NonNull Context context, @Nullable String tvInputServiceSessionId, @PriorityHintUseCaseType int priorityHint) { mTunerResourceManager = (TunerResourceManager) context.getSystemService(Context.TV_TUNER_RESOURCE_MGR_SERVICE); if (mTunerResourceManager != null) { int[] clientId = new int[1]; ResourceClientProfile profile = new ResourceClientProfile(); profile.tvInputSessionId = tvInputServiceSessionId; profile.useCase = priorityHint; mTunerResourceManager.registerClientProfile( profile, context.getMainExecutor(), mResourceListener, clientId); mClientId = clientId[0]; } } /** * Instantiate a CA system of the specified system id. * * @param casSystemId The system id of the CA system. * * @throws UnsupportedCasException if the device does not support the * specified CA system. */ public MediaCas(int casSystemId) throws UnsupportedCasException { createPlugin(casSystemId); } /** * Instantiate a CA system of the specified system id. * * @param context the context of the caller. * @param casSystemId The system id of the CA system. * @param tvInputServiceSessionId The Id of the session opened in TV Input Service (TIS) * {@link android.media.tv.TvInputService#onCreateSession(String, String)} * @param priorityHint priority hint from the use case type for new created CAS system. * * @throws UnsupportedCasException if the device does not support the * specified CA system. */ public MediaCas(@NonNull Context context, int casSystemId, @Nullable String tvInputServiceSessionId, @PriorityHintUseCaseType int priorityHint) throws UnsupportedCasException { Objects.requireNonNull(context, "context must not be null"); createPlugin(casSystemId); registerClient(context, tvInputServiceSessionId, priorityHint); } /** * Instantiate a CA system of the specified system id with EvenListener. * * @param context the context of the caller. * @param casSystemId The system id of the CA system. * @param tvInputServiceSessionId The Id of the session opened in TV Input Service (TIS) * {@link android.media.tv.TvInputService#onCreateSession(String, String)} * @param priorityHint priority hint from the use case type for new created CAS system. * @param listener the event listener to be set. * @param handler the handler whose looper the event listener will be called on. * If handler is null, we'll try to use current thread's looper, or the main * looper. If neither are available, an internal thread will be created instead. * * @throws UnsupportedCasException if the device does not support the * specified CA system. */ public MediaCas(@NonNull Context context, int casSystemId, @Nullable String tvInputServiceSessionId, @PriorityHintUseCaseType int priorityHint, @Nullable Handler handler, @Nullable EventListener listener) throws UnsupportedCasException { Objects.requireNonNull(context, "context must not be null"); setEventListener(listener, handler); createPlugin(casSystemId); registerClient(context, tvInputServiceSessionId, priorityHint); } IHwBinder getBinder() { validateInternalStates(); return mICas.asBinder(); } /** * An interface registered by the caller to {@link #setEventListener} * to receives scheme-specific notifications from a MediaCas instance. */ public interface EventListener { /** * Notify the listener of a scheme-specific event from the CA system. * * @param mediaCas the MediaCas object to receive this event. * @param event an integer whose meaning is scheme-specific. * @param arg an integer whose meaning is scheme-specific. * @param data a byte array of data whose format and meaning are * scheme-specific. */ void onEvent(@NonNull MediaCas mediaCas, int event, int arg, @Nullable byte[] data); /** * Notify the listener of a scheme-specific session event from CA system. * * @param mediaCas the MediaCas object to receive this event. * @param session session object which the event is for. * @param event an integer whose meaning is scheme-specific. * @param arg an integer whose meaning is scheme-specific. * @param data a byte array of data whose format and meaning are * scheme-specific. */ default void onSessionEvent(@NonNull MediaCas mediaCas, @NonNull Session session, int event, int arg, @Nullable byte[] data) { Log.d(TAG, "Received MediaCas Session event"); } /** * Notify the listener that the cas plugin status is updated. * * @param mediaCas the MediaCas object to receive this event. * @param status the plugin status which is updated. * @param arg an integer whose meaning is specific to the status to be updated. */ default void onPluginStatusUpdate(@NonNull MediaCas mediaCas, @PluginStatus int status, int arg) { Log.d(TAG, "Received MediaCas Plugin Status event"); } /** * Notify the listener that the session resources was lost. * * @param mediaCas the MediaCas object to receive this event. */ default void onResourceLost(@NonNull MediaCas mediaCas) { Log.d(TAG, "Received MediaCas Resource Reclaim event"); } } /** * Set an event listener to receive notifications from the MediaCas instance. * * @param listener the event listener to be set. * @param handler the handler whose looper the event listener will be called on. * If handler is null, we'll try to use current thread's looper, or the main * looper. If neither are available, an internal thread will be created instead. */ public void setEventListener( @Nullable EventListener listener, @Nullable Handler handler) { mListener = listener; if (mListener == null) { mEventHandler = null; return; } Looper looper = (handler != null) ? handler.getLooper() : null; if (looper == null && (looper = Looper.myLooper()) == null && (looper = Looper.getMainLooper()) == null) { if (mHandlerThread == null || !mHandlerThread.isAlive()) { mHandlerThread = new HandlerThread("MediaCasEventThread", Process.THREAD_PRIORITY_FOREGROUND); mHandlerThread.start(); } looper = mHandlerThread.getLooper(); } mEventHandler = new EventHandler(looper); } /** * Send the private data for the CA system. * * @param data byte array of the private data. * * @throws IllegalStateException if the MediaCas instance is not valid. * @throws MediaCasException for CAS-specific errors. * @throws MediaCasStateException for CAS-specific state exceptions. */ public void setPrivateData(@NonNull byte[] data) throws MediaCasException { validateInternalStates(); try { MediaCasException.throwExceptionIfNeeded( mICas.setPrivateData(toByteArray(data, 0, data.length))); } catch (RemoteException e) { cleanupAndRethrowIllegalState(); } } private class OpenSessionCallback implements android.hardware.cas.V1_1.ICas.openSessionCallback{ public Session mSession; public int mStatus; @Override public void onValues(int status, ArrayList sessionId) { mStatus = status; mSession = createFromSessionId(sessionId); } } private class OpenSession_1_2_Callback implements android.hardware.cas.V1_2.ICas.openSession_1_2Callback { public Session mSession; public int mStatus; @Override public void onValues(int status, ArrayList sessionId) { mStatus = status; mSession = createFromSessionId(sessionId); } } private int getSessionResourceHandle() throws MediaCasException { validateInternalStates(); int[] sessionResourceHandle = new int[1]; sessionResourceHandle[0] = -1; if (mTunerResourceManager != null) { CasSessionRequest casSessionRequest = new CasSessionRequest(); casSessionRequest.clientId = mClientId; casSessionRequest.casSystemId = mCasSystemId; if (!mTunerResourceManager .requestCasSession(casSessionRequest, sessionResourceHandle)) { throw new MediaCasException.InsufficientResourceException( "insufficient resource to Open Session"); } } return sessionResourceHandle[0]; } private void addSessionToResourceMap(Session session, int sessionResourceHandle) { if (sessionResourceHandle != TunerResourceManager.INVALID_RESOURCE_HANDLE) { synchronized (mSessionMap) { mSessionMap.put(session, sessionResourceHandle); } } } private void removeSessionFromResourceMap(Session session) { synchronized (mSessionMap) { if (mSessionMap.get(session) != null) { mTunerResourceManager.releaseCasSession(mSessionMap.get(session), mClientId); mSessionMap.remove(session); } } } /** * Open a session to descramble one or more streams scrambled by the * conditional access system. * *

Tuner resource manager (TRM) uses the client priority value to decide whether it is able * to get cas session resource if cas session resources is limited. If the client can't get the * resource, this call returns {@link MediaCasException.InsufficientResourceException }. * * @return session the newly opened session. * * @throws IllegalStateException if the MediaCas instance is not valid. * @throws MediaCasException for CAS-specific errors. * @throws MediaCasStateException for CAS-specific state exceptions. */ public Session openSession() throws MediaCasException { int sessionResourceHandle = getSessionResourceHandle(); try { OpenSessionCallback cb = new OpenSessionCallback(); mICas.openSession(cb); MediaCasException.throwExceptionIfNeeded(cb.mStatus); addSessionToResourceMap(cb.mSession, sessionResourceHandle); Log.d(TAG, "Write Stats Log for succeed to Open Session."); FrameworkStatsLog .write(FrameworkStatsLog.TV_CAS_SESSION_OPEN_STATUS, mUserId, mCasSystemId, FrameworkStatsLog.TV_CAS_SESSION_OPEN_STATUS__STATE__SUCCEEDED); return cb.mSession; } catch (RemoteException e) { cleanupAndRethrowIllegalState(); } Log.d(TAG, "Write Stats Log for fail to Open Session."); FrameworkStatsLog .write(FrameworkStatsLog.TV_CAS_SESSION_OPEN_STATUS, mUserId, mCasSystemId, FrameworkStatsLog.TV_CAS_SESSION_OPEN_STATUS__STATE__FAILED); return null; } /** * Open a session with usage and scrambling information, so that descrambler can be configured * to descramble one or more streams scrambled by the conditional access system. * *

Tuner resource manager (TRM) uses the client priority value to decide whether it is able * to get cas session resource if cas session resources is limited. If the client can't get the * resource, this call returns {@link MediaCasException.InsufficientResourceException}. * * @param sessionUsage used for the created session. * @param scramblingMode used for the created session. * * @return session the newly opened session. * * @throws IllegalStateException if the MediaCas instance is not valid. * @throws MediaCasException for CAS-specific errors. * @throws MediaCasStateException for CAS-specific state exceptions. */ @Nullable public Session openSession(@SessionUsage int sessionUsage, @ScramblingMode int scramblingMode) throws MediaCasException { int sessionResourceHandle = getSessionResourceHandle(); if (mICasV12 == null) { Log.d(TAG, "Open Session with scrambling mode is only supported by [email protected]+ interface"); throw new UnsupportedCasException("Open Session with scrambling mode is not supported"); } try { OpenSession_1_2_Callback cb = new OpenSession_1_2_Callback(); mICasV12.openSession_1_2(sessionUsage, scramblingMode, cb); MediaCasException.throwExceptionIfNeeded(cb.mStatus); addSessionToResourceMap(cb.mSession, sessionResourceHandle); Log.d(TAG, "Write Stats Log for succeed to Open Session."); FrameworkStatsLog .write(FrameworkStatsLog.TV_CAS_SESSION_OPEN_STATUS, mUserId, mCasSystemId, FrameworkStatsLog.TV_CAS_SESSION_OPEN_STATUS__STATE__SUCCEEDED); return cb.mSession; } catch (RemoteException e) { cleanupAndRethrowIllegalState(); } Log.d(TAG, "Write Stats Log for fail to Open Session."); FrameworkStatsLog .write(FrameworkStatsLog.TV_CAS_SESSION_OPEN_STATUS, mUserId, mCasSystemId, FrameworkStatsLog.TV_CAS_SESSION_OPEN_STATUS__STATE__FAILED); return null; } /** * Send a received EMM packet to the CA system. * * @param data byte array of the EMM data. * @param offset position within data where the EMM data begins. * @param length length of the data (starting from offset). * * @throws IllegalStateException if the MediaCas instance is not valid. * @throws MediaCasException for CAS-specific errors. * @throws MediaCasStateException for CAS-specific state exceptions. */ public void processEmm(@NonNull byte[] data, int offset, int length) throws MediaCasException { validateInternalStates(); try { MediaCasException.throwExceptionIfNeeded( mICas.processEmm(toByteArray(data, offset, length))); } catch (RemoteException e) { cleanupAndRethrowIllegalState(); } } /** * Send a received EMM packet to the CA system. This is similar to * {@link #processEmm(byte[], int, int)} except that the entire byte * array is sent. * * @param data byte array of the EMM data. * * @throws IllegalStateException if the MediaCas instance is not valid. * @throws MediaCasException for CAS-specific errors. * @throws MediaCasStateException for CAS-specific state exceptions. */ public void processEmm(@NonNull byte[] data) throws MediaCasException { processEmm(data, 0, data.length); } /** * Send an event to a CA system. The format of the event is scheme-specific * and is opaque to the framework. * * @param event an integer denoting a scheme-specific event to be sent. * @param arg a scheme-specific integer argument for the event. * @param data a byte array containing scheme-specific data for the event. * * @throws IllegalStateException if the MediaCas instance is not valid. * @throws MediaCasException for CAS-specific errors. * @throws MediaCasStateException for CAS-specific state exceptions. */ public void sendEvent(int event, int arg, @Nullable byte[] data) throws MediaCasException { validateInternalStates(); try { MediaCasException.throwExceptionIfNeeded( mICas.sendEvent(event, arg, toByteArray(data))); } catch (RemoteException e) { cleanupAndRethrowIllegalState(); } } /** * Initiate a provisioning operation for a CA system. * * @param provisionString string containing information needed for the * provisioning operation, the format of which is scheme and implementation * specific. * * @throws IllegalStateException if the MediaCas instance is not valid. * @throws MediaCasException for CAS-specific errors. * @throws MediaCasStateException for CAS-specific state exceptions. */ public void provision(@NonNull String provisionString) throws MediaCasException { validateInternalStates(); try { MediaCasException.throwExceptionIfNeeded( mICas.provision(provisionString)); } catch (RemoteException e) { cleanupAndRethrowIllegalState(); } } /** * Notify the CA system to refresh entitlement keys. * * @param refreshType the type of the refreshment. * @param refreshData private data associated with the refreshment. * * @throws IllegalStateException if the MediaCas instance is not valid. * @throws MediaCasException for CAS-specific errors. * @throws MediaCasStateException for CAS-specific state exceptions. */ public void refreshEntitlements(int refreshType, @Nullable byte[] refreshData) throws MediaCasException { validateInternalStates(); try { MediaCasException.throwExceptionIfNeeded( mICas.refreshEntitlements(refreshType, toByteArray(refreshData))); } catch (RemoteException e) { cleanupAndRethrowIllegalState(); } } /** * Release Cas session. This is primarily used as a test API for CTS. * @hide */ @TestApi public void forceResourceLost() { if (mResourceListener != null) { mResourceListener.onReclaimResources(); } } @Override public void close() { if (mICas != null) { try { mICas.release(); } catch (RemoteException e) { } finally { mICas = null; } } if (mTunerResourceManager != null) { mTunerResourceManager.unregisterClientProfile(mClientId); mTunerResourceManager = null; } if (mHandlerThread != null) { mHandlerThread.quit(); mHandlerThread = null; } } @Override protected void finalize() { close(); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy